#include "icsneo/communication/message/callback/streamoutput/a2bwavoutput.h" #include "icsneo/device/tree/rada2b/rada2b.h" #include "icsneo/icsneocpp.h" namespace icsneo { A2BWAVOutput::A2BWAVOutput( const char* filename, const ChannelMap& channelMap, PCMType bitDepth, size_t numWAVChannels, uint32_t sampleRate ) : StreamOutput(filename), wavSampleRate(sampleRate), numChannelsWAV(numWAVChannels), chMap(channelMap) { switch(bitDepth) { case PCMType::L16: bytesPerSampleWAV = 2; break; case PCMType::L24: bytesPerSampleWAV = 3; break; case PCMType::L32: bytesPerSampleWAV = 4; break; } if(initialize()) { initialized = true; } } A2BWAVOutput::A2BWAVOutput( std::ostream& os, const ChannelMap& channelMap, PCMType bitDepth, size_t numWAVChannels, uint32_t sampleRate ) : StreamOutput(os), wavSampleRate(sampleRate), numChannelsWAV(numWAVChannels), chMap(channelMap) { switch(bitDepth) { case PCMType::L16: bytesPerSampleWAV = 2; break; case PCMType::L24: bytesPerSampleWAV = 3; break; case PCMType::L32: bytesPerSampleWAV = 4; break; } if(initialize()) { initialized = true; } } A2BWAVOutput::~A2BWAVOutput() { if(!closed) { close(); } } bool A2BWAVOutput::initialize() { static constexpr size_t maxWAVChannels = 256; if(numChannelsWAV > maxWAVChannels) { return false; } maxMessageChannel = 0; // Check if the inputted channel map has invalid mappings and compute maxMessageChannel for(auto [wavChannel, messageChannel] : chMap) { maxMessageChannel = std::max(maxMessageChannel, messageChannel); if(wavChannel >= numChannelsWAV) { return false; } } WAVHeader header = WAVHeader( static_cast(chMap.size()), wavSampleRate, static_cast(bytesPerSampleWAV * 8) ); if(!stream->write(reinterpret_cast(&header), sizeof(WAVHeader))) { return false; } streamStartPos = static_cast(stream->tellp()); wavBuffer = std::vector(wavBufferSize, 0); wavBufferOffset = 0; return true; } bool A2BWAVOutput::callIfMatch(const std::shared_ptr& message) const { if(!initialized) { return false; } if(closed) { return false; } if(message->type != Message::Type::BusMessage) { return false; } const auto& frameMsg = std::dynamic_pointer_cast(message); if(!frameMsg) { return false; } if(frameMsg->network.getType() != Network::Type::A2B) return false; const auto& a2bMsg = std::dynamic_pointer_cast(frameMsg); if(!a2bMsg) { return false; } size_t frameSize = a2bMsg->getFrameSize(); size_t wavFrameSize = numChannelsWAV * bytesPerSampleWAV; size_t bytesPerChannel = static_cast(a2bMsg->getBytesPerChannel()); size_t numMessageChannels = 2 * a2bMsg->numChannels; size_t numFrames = a2bMsg->getNumFrames(); const uint8_t* audioBuffer = a2bMsg->data.data(); if(maxMessageChannel >= numMessageChannels) { // The max message channel in our channel map is larger than the number of channels in this message // this is likely due to the user inputting incorrect settings return false; } for(size_t frame = 0; frame < numFrames; frame++) { // Check to see if we can read another frame in wavBuffer, otherwise write and clear the buffer if(wavBufferOffset + wavFrameSize >= wavBufferSize) { if(!writeCurrentBuffer()) { return false; } } for(size_t wavChannel = 0; wavChannel < numChannelsWAV; wavChannel++) { if(auto iter = chMap.find(static_cast(wavChannel)); iter != chMap.end()) { auto messageChannel = iter->second; size_t messageChannelOffset = messageChannel * bytesPerChannel + frameSize* frame; // Samples in the WAV are little endian signed integers // Samples in the message channels are little endian signed integers that are // most significant bit aligned if(a2bMsg->channelSize16) { // In this case, the channel size will be less than or equal to the sample we are writing // so we zero out any of the least significant bytes which won't be occupied by a sample byte for(size_t zeroByte = 0; zeroByte < bytesPerSampleWAV - bytesPerChannel; zeroByte++) { wavBuffer[wavBufferOffset++] = 0; } // Write the channel data in the most signifant bytes of the wav sample, this effectively // writes a sample which is scaled up. for(size_t channelByte = 0; channelByte < bytesPerChannel; channelByte++) { wavBuffer[wavBufferOffset++] = audioBuffer[messageChannelOffset + channelByte]; } } else { // In this case, the channel size will be greater than or equal to the sample we are reading // Align the wav sample with the most significant bytes of the channel size_t channelByte = messageChannelOffset + (bytesPerChannel - bytesPerSampleWAV); // Read the most significant bytes of the channel into the wavBuffer for(size_t sampleByte = 0; sampleByte < bytesPerSampleWAV; sampleByte++, channelByte++) { wavBuffer[wavBufferOffset++] = audioBuffer[channelByte]; } } } else { // If this channel wasn't specified in the channel map, set a zero sample for( size_t sampleByte = 0; sampleByte < bytesPerSampleWAV; sampleByte++ ) { wavBuffer[wavBufferOffset++] = 0; } } } } return true; } void A2BWAVOutput::close() const { if(closed) { return; } if(!initialized) { return; } // Write any left over data in the buffer if(wavBufferOffset > 0) { writeCurrentBuffer(); } // Seek back in the output stream and write the WAV chunk sizes uint32_t streamEndPos = static_cast(stream->tellp()); uint32_t subChunk2Size = streamEndPos - streamStartPos; uint32_t chunkSize = streamEndPos - 8; stream->seekp(streamStartPos - 4); stream->write(reinterpret_cast(&subChunk2Size), 4); stream->seekp(4, std::ios::beg); stream->write(reinterpret_cast(&chunkSize), 4); closed = true; } bool A2BWAVOutput::writeCurrentBuffer() const { if(!stream->write(reinterpret_cast(wavBuffer.data()), wavBufferOffset)) { return false; } wavBufferOffset = 0; return true; } }