diff --git a/CMakeLists.txt b/CMakeLists.txt index 7f2b018..b4944b4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -172,6 +172,7 @@ endforeach() set(SRC_FILES communication/message/flexray/control/flexraycontrolmessage.cpp communication/message/callback/streamoutput/a2bwavoutput.cpp + communication/message/callback/streamoutput/a2bdecoder.cpp communication/message/neomessage.cpp communication/message/ethphymessage.cpp communication/message/linmessage.cpp diff --git a/communication/decoder.cpp b/communication/decoder.cpp index c265f02..16eaa52 100644 --- a/communication/decoder.cpp +++ b/communication/decoder.cpp @@ -140,6 +140,30 @@ bool Decoder::decode(std::shared_ptr& result, const std::shared_ptrdata); + + if(!result) { + report(APIEvent::Type::PacketDecodingError, APIEvent::Severity::Error); + return false; // A nullptr was returned, the packet was not long enough to decode + } + + A2BMessage& msg = *static_cast(result.get()); + msg.network = packet->network; + return true; + } + case Network::Type::LIN: { + result = HardwareLINPacket::DecodeToMessage(packet->data); + + if(!result) { + report(APIEvent::Type::PacketDecodingError, APIEvent::Severity::Error); + return false; // A nullptr was returned, the packet was not long enough to decode + } + + LINMessage& msg = *static_cast(result.get()); + msg.network = packet->network; + return true; + } case Network::Type::Internal: { switch(packet->network.getNetID()) { case Network::NetID::Reset_Status: { @@ -352,33 +376,9 @@ bool Decoder::decode(std::shared_ptr& result, const std::shared_ptrdata); - - if(!result) { - report(APIEvent::Type::PacketDecodingError, APIEvent::Severity::Error); - return false; // A nullptr was returned, the packet was not long enough to decode - } - - A2BMessage& msg = *static_cast(result.get()); - msg.network = packet->network; - return true; - } - case Network::Type::LIN: { - result = HardwareLINPacket::DecodeToMessage(packet->data); - - if(!result) { - report(APIEvent::Type::PacketDecodingError, APIEvent::Severity::Error); - return false; // A nullptr was returned, the packet was not long enough to decode - } - - LINMessage& msg = *static_cast(result.get()); - msg.network = packet->network; - return true; - } } // For the moment other types of messages will automatically be decoded as raw messages result = std::make_shared(packet->network, packet->data); return true; -} \ No newline at end of file +} diff --git a/communication/message/callback/streamoutput/a2bdecoder.cpp b/communication/message/callback/streamoutput/a2bdecoder.cpp new file mode 100644 index 0000000..38bb843 --- /dev/null +++ b/communication/message/callback/streamoutput/a2bdecoder.cpp @@ -0,0 +1,156 @@ +#include "icsneo/communication/message/callback/streamoutput/a2bdecoder.h" +#include +#include "icsneo/icsneocpp.h" + + +namespace icsneo { + +static constexpr uint8_t maxChannel = 255; + + +size_t A2BAudioChannelMap::getChannelIndex(Channel channel, A2BMessage::A2BDirection dir) const { + size_t output = (size_t)channel; + + if(dir == A2BMessage::A2BDirection::Upstream) { + output++; + } + return output; +} + +A2BAudioChannelMap::A2BAudioChannelMap(uint8_t tdm) { + rawMap.resize(2*tdm, maxChannel); +} + +void A2BAudioChannelMap::set(Channel outChannel, A2BMessage::A2BDirection dir, Channel inChannel) { + auto index = getChannelIndex(outChannel, dir); + rawMap[index] = inChannel; +} + +void A2BAudioChannelMap::setAll(Channel inChannel) { + std::fill(rawMap.begin(), rawMap.end(), inChannel); +} + +Channel A2BAudioChannelMap::get(Channel outChannel, A2BMessage::A2BDirection dir) const { + auto index = getChannelIndex(outChannel, dir); + + return rawMap[index]; +} + +size_t A2BAudioChannelMap::A2BAudioChannelMap::size() const { + return rawMap.size(); +} + +uint8_t A2BAudioChannelMap::getTDM() const { + return (uint8_t)(rawMap.size() / 2); +} + +Channel& A2BAudioChannelMap::operator[](size_t idx) { + return rawMap[idx]; +} + +A2BAudioChannelMap::operator const std::vector&() const { + return rawMap; +} + +A2BDecoder::A2BDecoder( + std::unique_ptr&& streamOut, + bool chSize16, + const A2BAudioChannelMap& chMap +) : channelSize16(chSize16), channelMap(chMap) { + stream = std::move(streamOut); + tdm = chMap.getTDM(); + initializeFromHeader(); +} + +A2BDecoder::A2BDecoder( + const char* filename, + bool chSize16, + const A2BAudioChannelMap& chMap +) : A2BDecoder(std::make_unique(filename, std::ios::binary), chSize16, chMap) { } + +A2BDecoder::operator bool() const { + return initialized && *stream; +} + +void A2BDecoder::initializeFromHeader() { + WaveFileHeader header; + if(!stream->read((char*)&header, sizeof(header))) { + initialized = false; + return; + } + + // Only allow 16 or 24 bit samples + if(header.bitsPerSample != 16 && header.bitsPerSample != 24) { + initialized = false; + return; + } + + audioBytesPerSample = header.bitsPerSample == 16 ? 2 : 3; + channelsInWave = (uint8_t)header.numChannels; + + size_t bytesPerSample = channelSize16 ? 2 : 4; + size_t frameSize = 2*tdm*bytesPerSample; + size_t frameSizeWave = (size_t)(channelsInWave) * (size_t)(audioBytesPerSample); + + frame.resize(frameSize, 0); + frameWave.resize(frameSizeWave, 0); + + initialized = true; +} + +std::shared_ptr A2BDecoder::decode() { + if(!*(this)) { + return nullptr; + } + + auto a2bMessagePtr = std::make_shared( + tdm, + channelSize16, + 2048 + ); + + A2BMessage& a2bMessage = *a2bMessagePtr.get(); + + a2bMessage.setMonitorBit(false); // Probably not necessary + a2bMessage.setTxMsgBit(true); + + a2bMessage.network = Network(Network::NetID::A2B2); + + for(uint32_t frameIndex = 0; frameIndex < a2bMessage.getNumFrames(); frameIndex++) { + if(!stream->read((char*)frameWave.data(), frameWave.size())) { + break; + } + + for(size_t icsChannel = 0; icsChannel < channelMap.size(); icsChannel++) { + + if(channelMap[icsChannel] >= maxChannel) { + continue; + } + + size_t wBegin = audioBytesPerSample * channelMap[icsChannel]; + A2BPCMSample sample = 0; + uint8_t* sampBytes = (uint8_t*)&sample; + + std::copy(frameWave.begin() + wBegin, frameWave.begin() + wBegin + audioBytesPerSample, sampBytes); + a2bMessage[frameIndex][icsChannel] = sample; + } + } + + return a2bMessagePtr; +} + +bool A2BDecoder::outputAll(std::shared_ptr& device) { + const auto& networks = device->getSupportedTXNetworks(); + + if(std::none_of(networks.begin(), networks.end(), [](const Network& net) { return net.getNetID() == Network::NetID::A2B2; })) { + return false; + } + + while(*this) { + device->transmit(decode()); + } + + return true; +} + +} \ No newline at end of file diff --git a/communication/message/callback/streamoutput/a2bwavoutput.cpp b/communication/message/callback/streamoutput/a2bwavoutput.cpp index 5ad5632..8a5076b 100644 --- a/communication/message/callback/streamoutput/a2bwavoutput.cpp +++ b/communication/message/callback/streamoutput/a2bwavoutput.cpp @@ -1,23 +1,26 @@ #include "icsneo/communication/message/callback/streamoutput/a2bwavoutput.h" +#include "icsneo/device/tree/rada2b/rada2b.h" +#include "icsneo/icsneocpp.h" -namespace icsneo -{ +namespace icsneo { void A2BWAVOutput::writeHeader(const std::shared_ptr& firstMsg) const { WaveFileHeader header = WaveFileHeader(2 * firstMsg->getNumChannels(), wavSampleRate, firstMsg->getBitDepth()); header.write(stream); streamStartPos = static_cast(stream->tellp()); + } bool A2BWAVOutput::callIfMatch(const std::shared_ptr& message) const { - if(closed) - { + + if(closed) { return false; } - if(message->type != Message::Type::Frame) + if(message->type != Message::Type::Frame) { return false; + } const auto& frame = std::static_pointer_cast(message); @@ -31,23 +34,21 @@ bool A2BWAVOutput::callIfMatch(const std::shared_ptr& message) const { firstMessageFlag = false; } - if(!writeSamples(a2bmsg, A2BMessage::A2BDirection::DownStream)) { - close(); - - return false; + // Might need to readd this block of code later if sample alignment fix is necessary + /* + std::streamsize bps = (std::streamsize)a2bmsg->getBytesPerSample(); + for(size_t i=0; igetNumSamples(); i++) { + A2BPCMSample samp = *(a2bmsg->getSample(i)); + write((void*)&samp, bps); } + */ - if(!writeSamples(a2bmsg, A2BMessage::A2BDirection::UpStream)) { - close(); - - return false; - } + write((void*)a2bmsg->getAudioBuffer(), a2bmsg->getAudioBufferSize()); return true; } -void A2BWAVOutput::close() const -{ +void A2BWAVOutput::close() const { if(closed) { return; } @@ -65,36 +66,4 @@ void A2BWAVOutput::close() const closed = true; } -bool A2BWAVOutput::writeSamples(const std::shared_ptr& msg, A2BMessage::A2BDirection dir) const -{ - uint8_t numChannels = msg->getNumChannels(); - - uint8_t channel = 0; - uint32_t frame = 0; - uint8_t bitDepth = msg->getBitDepth(); - - while(true) { - auto sample = msg->getSample(dir, channel, frame); - - if(!sample) { - if(channel == 0) { - break; - } - - return false; - } - - uint32_t audioSample = sample.value() >> (32 - bitDepth); - - write((void*)(&audioSample), A2BPCM_SAMPLE_SIZE); - - channel = (channel + 1) % numChannels; - if(channel == 0) { - frame++; - } - } - - return true; -} - } \ No newline at end of file diff --git a/communication/packet/a2bpacket.cpp b/communication/packet/a2bpacket.cpp index b89b737..032c2eb 100644 --- a/communication/packet/a2bpacket.cpp +++ b/communication/packet/a2bpacket.cpp @@ -1,6 +1,6 @@ #include "icsneo/communication/packet/a2bpacket.h" #include - +#include namespace icsneo { @@ -14,148 +14,49 @@ std::shared_ptr HardwareA2BPacket::DecodeToMessage(const std::vector(bytes[i]) << (i * 8); - } - - return result; - }; - const HardwareA2BPacket *data = (const HardwareA2BPacket*)bytestream.data(); - uint32_t totalPackedLength = static_cast(bytestream.size()) - static_cast(coreMiniMessageHeaderSize); // First 28 bytes are message header. + size_t totalPackedLength = static_cast(bytestream.size()) - static_cast(coreMiniMessageHeaderSize); // First 28 bytes are message header. - uint8_t bytesPerChannel = data->header.channelSize16 ? 2 : 4; - uint8_t numChannels = data->header.channelNum; - uint8_t bitDepth = data->header.channelSize16 ? A2BPCM_L16 : A2BPCM_L24; + std::shared_ptr msg = std::make_shared( + (uint8_t)data->header.channelNum, + data->header.channelSize16, + totalPackedLength + ); - std::shared_ptr msg = std::make_shared(bitDepth, bytesPerChannel, numChannels); - msg->channelSize16 = data->header.channelSize16; - msg->monitor = data->header.monitor; - msg->txmsg = data->header.txmsg; - msg->errIndicator = data->header.errIndicator; - msg->syncFrame = data->header.syncFrame; - msg->rfu2 = data->header.rfu2; - - const uint8_t *bytes = bytestream.data(); - bytes+=coreMiniMessageHeaderSize; - - uint8_t channel = 0; - - for(uint32_t i = 0; i < totalPackedLength; i += 2 * static_cast(bytesPerChannel), bytes += 2 * bytesPerChannel, channel = (channel + 1) % numChannels) { - - msg->addSample( - getSampleFromBytes(bytesPerChannel, bytes), - A2BMessage::A2BDirection::DownStream, - channel - ); - - msg->addSample( - getSampleFromBytes(bytesPerChannel, bytes + bytesPerChannel), - A2BMessage::A2BDirection::UpStream, - channel - ); - - } + msg->setMonitorBit(data->header.monitor); + msg->setTxMsgBit(data->header.txmsg); + msg->setErrIndicatorBit(data->header.errIndicator); + msg->setSyncFrameBit(data->header.syncFrame); + msg->setRFU2(data->header.rfu2); + msg->setAudioBuffer(bytestream.begin() + coreMiniMessageHeaderSize, bytestream.end()); return msg; } bool HardwareA2BPacket::EncodeFromMessage(const A2BMessage& message, std::vector& bytestream, const device_eventhandler_t& report) { + constexpr size_t a2btxMessageHeaderSize = 6; if(message.getBytesPerSample() != 2 && message.getBytesPerSample() != 4) { report(APIEvent::Type::MessageFormattingError, APIEvent::Severity::Error); return false; } - size_t sampleBytes = message.getNumSamples() * static_cast(message.getBytesPerSample()); - size_t totalSize = coreMiniMessageHeaderSize + sampleBytes; + size_t sampleBytes = message.getAudioBufferSize(); + size_t totalSize = a2btxMessageHeaderSize + sampleBytes; - if(totalSize > a2bMessageMaxLength) { - report(APIEvent::Type::MessageMaxLengthExceeded, APIEvent::Severity::Error); - return false; - } - - bytestream.reserve(totalSize); - - bytestream.push_back(message.getNumChannels()); - bytestream.push_back(message.channelSize16 ? 1 : 0); + bytestream.resize(totalSize, 0); + uint32_t offset = 0; - uint8_t a2b2Bits = 0; - - if(message.monitor) { - a2b2Bits = a2b2Bits | 1; - } + bytestream[offset++] = 0; + bytestream[offset++] = 0; + bytestream[offset++] = (uint8_t)(sampleBytes & 0xFF); + bytestream[offset++] = (uint8_t)((sampleBytes >> 8) & 0xFF); + bytestream[offset++] = (uint8_t)((message.description >> 8) & 0xFF); + bytestream[offset++] = (uint8_t)(message.description & 0xFF); - if(message.txmsg) { - a2b2Bits = a2b2Bits | (1 << 1); - } - - if(message.errIndicator) { - a2b2Bits = a2b2Bits | (1 << 2); - } - - if(message.syncFrame) { - a2b2Bits = a2b2Bits | (1 << 3); - } - - bytestream.push_back(a2b2Bits); - bytestream.push_back(0); - bytestream.push_back(static_cast(message.rfu2)); - bytestream.push_back(static_cast(message.rfu2 >> 8)); - - for(size_t i = 0; i < (coreMiniMessageHeaderSize - a2bHeaderSize); i++) - bytestream.push_back(0); - - uint8_t numChannels = message.getNumChannels(); - - uint8_t channel = 0; - uint32_t frame = 0; - - auto writeSample = [&](A2BPCMSample&& sample) { - for(uint32_t i = 0; i < static_cast(message.getBytesPerSample()); i++) { - bytestream.push_back(static_cast((sample >> (i*8)))); - } - }; - - while(true) { - auto dsSample = message.getSample(A2BMessage::A2BDirection::DownStream, channel, frame); - auto usSample = message.getSample(A2BMessage::A2BDirection::UpStream, channel, frame); - - - // Check if getSample failed for both downstream and upstream - if(!dsSample && !usSample) { - - if(channel != 0) { - //Incomplete frame, the frame we are currently on does not contain all channel samples - report(APIEvent::Type::A2BMessageIncompleteFrame, APIEvent::Severity::Error); - return false; - } - // Since no samples have been written for the current frame yet and there are no more - // samples in both upstream and downstream, we can break and end parsing. - break; - } - // Since the first case failed, at least one of the streams still has samples. - // This case checks to see if the other stream does not have a sample. - else if(!dsSample || !usSample) { - // Report an error since we must have a one to one correspondence between upstream - // and downstream. - report(APIEvent::Type::A2BMessageIncompleteFrame, APIEvent::Severity::Error); - return false; - } - - writeSample(std::move(dsSample.value())); - writeSample(std::move(usSample.value())); - - channel = (channel + 1) % numChannels; - if(channel == 0) - frame++; - } + std::copy(message.data.begin(), message.data.end(), bytestream.begin() + offset); return true; } diff --git a/docs/Usage.rst b/docs/Usage.rst index 0af74f0..77ca51a 100644 --- a/docs/Usage.rst +++ b/docs/Usage.rst @@ -105,14 +105,13 @@ A2B Wave Output ~~~~~~~~~~~~~~~~~~~~ Users may add a ``icsneo::A2BWAVOutput`` message callback to their device in order to write A2B PCM data to a WAVE file. The message callback listens for ``icsneo::A2BMessage`` messages and writes both downstream and upstream channels to a single wave file. If downstream and upstream each have ``32`` channels, the wave file will contain ``2*32 = 64`` -total channels. The first half of the channels, channels ``0-31`` in the outputted wave file, represent downstream channel ``0-31``. Likewise, the second half of the channels, -channels ``32-63`` in the outputted wave file, represent upstream channel ``0-31``. Let ``NUM_CHANNELS`` be the total number of channels in a single stream. If we introduce a -variable ``IS_UPSTREAM`` which is ``0`` when downstream and ``1`` when upstream and desired a channel ``CHANNEL_NUM`` in either downstream or upstream the -channel ``IS_UPSTREAM * NUM_CHANNELS + CHANNEL_NUM`` would correspond to the channel in the outputted wave file. +total channels. Channels are indexed at 0 and interleaved such that downstream are on even number channels and upstream on odd number channels. If we introduce a +variable ``IS_UPSTREAM`` which is ``0`` when downstream and ``1`` when upstream and desired a channel ``CHANNEL_NUM`` the corresponding channel in the wave file would be +``2*CHANNEL_NUM + IS_UPSTREAM``. Wave files may be split by channel using programs such as ``FFmpeg``. Consider a file ``out.wav`` which was generated using a ``icsneo::A2BWAVOutput`` object and contains ``32`` channels per stream. The ``icsneo::A2BWavoutput`` object injested PCM data with a sample rate of ``44.1 kHz`` and bit depth of ``24``. The corresponding -channel of upstream channel ``8`` in ``out.wav`` would be ``1*32 + 8 = 40``. The following ``FFmpeg`` command may be ran in a linux environment to create a new wave +channel of upstream channel ``8`` in ``out.wav`` would be ``2*CHANNEL_NUM + IS_UPSTREAM = 2*8 + 1 = 17``. The following ``FFmpeg`` command may be ran in a linux environment to create a new wave file ``out_upstream_ch8.wav`` which contains only PCM samples off of upstream channel ``8``. -``ffmpeg -i out.wav -ar 44100 -acodec pcm_s24le -map_channel 0.0.40 out_upstream_ch8.wav`` +``ffmpeg -i out.wav -ar 44100 -acodec pcm_s24le -map_channel 0.0.17 out_upstream_ch8.wav`` diff --git a/examples/cpp/a2b/src/a2b.cpp b/examples/cpp/a2b/src/a2b.cpp index ec99f09..362bf9a 100644 --- a/examples/cpp/a2b/src/a2b.cpp +++ b/examples/cpp/a2b/src/a2b.cpp @@ -1,52 +1,285 @@ +// libicsneo A2B example +// Example must be ran with rada2b as slave on TDM4 32 bit channel size and one ADI master node +// Options: +// -h, --help Display help message. +// -e, --example [EXAMPLE_NUM] Example to run. +// Example usage: ./libicsneocpp-a2b.exe --example 1 +// Example usage: ./libicsneocpp-a2b.exe -h + #include -#include -#include -#include +#include +#include +#include +#include +#include -#include "icsneo/icsneocpp.h" +static constexpr size_t numFramesInWave = 48; -const std::string rada2bSerial = "Your RADA2B serial number."; +std::string makeWave() { + icsneo::WaveFileHeader header = icsneo::WaveFileHeader(1, 48000, 24); + std::vector sineWaveSamples = { + 0x00, 0x2B, 0x98, 0x08, 0x25, 0x01, 0x10, 0xD3, 0xEF, 0x18, 0x40, 0xA3, 0x20, 0x33, 0x4C, 0x26, + 0xCE, 0xCA, 0x2D, 0x5B, 0x41, 0x32, 0xB9, 0x3C, 0x37, 0x6E, 0x50, 0x3B, 0x29, 0x18, 0x3D, 0xC2, + 0x96, 0x3F, 0x86, 0xD3, 0x3F, 0xEC, 0x35, 0x3F, 0x85, 0xA7, 0x3D, 0xC4, 0x19, 0x3B, 0x28, 0xC9, + 0x37, 0x6B, 0x5A, 0x32, 0xC1, 0xC4, 0x2D, 0x4A, 0xEE, 0x26, 0xE8, 0xB1, 0x20, 0x0E, 0xCF, 0x18, + 0x6F, 0xAA, 0x10, 0x9C, 0x17, 0x08, 0x53, 0x35, 0x00, 0x01, 0xFD, 0xF7, 0xA9, 0x29, 0xEF, 0x66, + 0x87, 0xE7, 0x8F, 0x37, 0xDF, 0xF0, 0x8A, 0xD9, 0x19, 0xB3, 0xD2, 0xB1, 0x3E, 0xCD, 0x42, 0xE9, + 0xC8, 0x8F, 0xE0, 0xC4, 0xDB, 0x39, 0xC2, 0x39, 0x78, 0xC0, 0x7A, 0x8A, 0xC0, 0x16, 0x38, 0xC0, + 0x74, 0x18, 0xC2, 0x44, 0xBF, 0xC4, 0xCE, 0x26, 0xC8, 0x9A, 0x99, 0xCD, 0x3F, 0x53, 0xD2, 0xA8, + 0xBD, 0xD9, 0x32, 0xF5, 0xDF, 0xC2, 0x68, 0xE7, 0xD5, 0xFD, 0xEF, 0x02, 0x15, 0xF8, 0x3B, 0x33, + }; -int main() { - std::cout << "Start example\n"; - auto devices = icsneo::FindAllDevices(); + std::vector sineWave; + sineWave.reserve(sineWaveSamples.size() + sizeof(header)); - std::shared_ptr rada2b; - for(auto& device : devices) { - if(device->getSerial() == rada2bSerial) { - rada2b = device; + sineWave.insert(sineWave.begin(), (uint8_t*)&header, (uint8_t*)(&header) + sizeof(header)); + std::copy(sineWaveSamples.begin(), sineWaveSamples.end(), std::back_inserter(sineWave)); + + return std::string(sineWave.begin(), sineWave.end()); +} + +// Example 0: TX +void example0(std::shared_ptr& rada2b) { + std::cout << "Transmitting a sine wave..." << std::endl; + + // Create sine tone in wave format + std::string waveString = makeWave(); + + // Audio map to map which channel in wave to stream on a2b bus. + icsneo::A2BAudioChannelMap a2bmap(4); + + + a2bmap.set( + 2, // Channel on a2b bus + icsneo::A2BMessage::A2BDirection::Downstream, // Direction + 0 // Channel in wave file + ); + + a2bmap.setAll(0); + + icsneo::A2BDecoder decoder( + std::make_unique(waveString), // Wave file stream + false, // True when using 16 bit samples + a2bmap + ); + + + double elapsedTime = 0.0; + // Play roughly 5 seconds of sine tone. + while(elapsedTime < 5.0) { + + decoder.outputAll(rada2b); // Output entire wave file + + elapsedTime += ((double)numFramesInWave) * 1.0/48000.0; + + decoder.stream->clear(); + decoder.stream->seekg(0, std::ios::beg); + // Also outputs entire wave file + while(decoder && elapsedTime < 5.0) { + auto msg = decoder.decode(); + rada2b->transmit(msg); + elapsedTime += ((double)msg->getNumFrames())*1.0/48000.0; } + + decoder.stream->clear(); + decoder.stream->seekg(0, std::ios::beg); } - rada2b->open(); - rada2b->goOnline(); - std::cout << rada2b->describe() << "\n"; +} - auto handler1 = rada2b->addMessageCallback(std::make_shared("examples/cpp/a2b/src/out.wav")); // Starts writing A2B PCM data to out.wav +// Example 1: RX +void example1(std::shared_ptr& rada2b) { + std::cout << "Receiving 5 seconds of audio data..." << std::endl; - auto handler2 = rada2b->addMessageCallback( - std::make_shared( - [](std::shared_ptr message) { - std::cout << "Got in callback " << std::endl; - if(message->type == icsneo::Message::Type::Frame) { - std::shared_ptr frame = std::static_pointer_cast(message); + // Add WAV output message callback + // Saves samples to "out.wav" + auto handler = rada2b->addMessageCallback(std::make_shared("out.wav", 48000)); - if(frame->network.getType() == icsneo::Network::Type::A2B) { - std::shared_ptr msg = std::static_pointer_cast(frame); - std::cout << "Got A2B Message" << std::endl; - } - } - })); + // Sleep this thread for 5 seconds, message callback still runs + std::this_thread::sleep_for(std::chrono::seconds(5)); - std::this_thread::sleep_for(std::chrono::seconds(5)); // captures 5 seconds of A2B data. + // Remove callback + rada2b->removeMessageCallback(handler); +} - rada2b->removeMessageCallback(handler1); - rada2b->removeMessageCallback(handler2); - std::cout << "End A2B example\n"; +// Example 2: RADA2B settings +void example2(std::shared_ptr& rada2b) { + uint32_t tdm; + { + // Get device settings + auto* settings = rada2b->settings.get(); + auto* rada2bSettings = (icsneo::RADA2BSettings*)settings; + + // Check if monitor mode is enabled + auto type = rada2bSettings->getNodeType(icsneo::RADA2BSettings::RADA2BDevice::Monitor); + if(type == icsneo::RADA2BSettings::NodeType::Monitor) { + std::cout << "Device is in monitor mode" << std::endl; + } + else { + std::cout << "Device is not in monitor mode" << std::endl; + } + + // Get current tdm mode + tdm = rada2bSettings->getTDMModeInt(icsneo::RADA2BSettings::RADA2BDevice::Node); + + std::cout << "Current tdm mode: " << tdm << std::endl; + // Set node type to master node. + rada2bSettings->setNodeType(icsneo::RADA2BSettings::RADA2BDevice::Node, icsneo::RADA2BSettings::NodeType::Master); + + // Set TDM mode to TDM8 + rada2bSettings->setTDMMode(icsneo::RADA2BSettings::RADA2BDevice::Node, icsneo::RADA2BSettings::TDMMode::TDM8); + + // Apply local settings to device + rada2bSettings->apply(); + } +} + +// Example 3: A2BMessage API +void example3() { + icsneo::A2BMessage msg = icsneo::A2BMessage(4, false, 2048); // Create new A2BMessage + + msg[0][0] = 60; // Set sample using operator[][] + msg[0][3] = 60; // Frame 0, channel 2 upstream + msg[6][2] = 32; // Frame 6, channel 1 downstream + + // Equivalent to last line + msg.setSample(icsneo::A2BMessage::A2BDirection::Downstream, 1, 6, 32); + + // Get sample + std::cout << "Channel 1 downstream sample for frame 6: " << msg.getSample(icsneo::A2BMessage::A2BDirection::Downstream, 1, 6).value() << std::endl; + + // Get number of frames + auto numFrames = msg.getNumFrames(); + std::cout << "Num frames: " << numFrames << std::endl; + + icsneo::A2BPCMSample sample1 = 40; + icsneo::A2BPCMSample sample2 = 60; + msg.fill(sample1); // Fill whole message with sample 40 + + msg.fillFrame(sample2, numFrames/2); // Fill frame numFrames/2 with sample2 + + // Print msg sample contents + std::cout << "A2B message contents:" << std::endl; + for(size_t y = 0; y < numFrames; y++) { + for(size_t x = 0; x < ((size_t)(msg.getNumChannels())*2); x++) { // Num channels including upstream and downstream + std::cout << msg[y][x] << " "; + } + std::cout << std::endl; + } + + // Set and get bits + msg.setSyncFrameBit(true); + + std::cout << "Was received from monitor: " << msg.isMonitorMsg() << std::endl; +} + + +void displayUsage() { + std::cout << "libicsneo A2B example" << std::endl; + std::cout << "Example must be ran with rada2b as slave on TDM4 32 bit channel size and one ADI master node" << std::endl; + std::cout << "Options:" << std::endl; + std::cout << "-h, --help\tDisplay help message." << std::endl; + std::cout << "-e, --example [EXAMPLE_NUM]\tExample to run." << std::endl; + std::cout << "Example usage: ./libicsneocpp-a2b.exe --example 1" << std::endl; + std::cout << "Example usage: ./libicsneocpp-a2b.exe -h" << std::endl; + std::cout << std::endl; + std::cout << "Example options:" << std::endl; + std::cout << "0\ttx" << std::endl; + std::cout << "1\trx" << std::endl; + std::cout << "2\trx split channel" << std::endl; + std::cout << "3\tA2BMessage API" << std::endl; +} + +int main(int argc, char **argv) { + std::vector arguments(argv, argv + argc); + if(argc > 4 || argc == 1) { + std::cerr << "Invalid usage." << std::endl; + displayUsage(); + return EXIT_FAILURE; + } + + if(std::any_of(arguments.begin(), arguments.end(), [](const std::string& arg) { return arg == "-h" || arg == "--help"; })) { + displayUsage(); + return EXIT_SUCCESS; + } + + if(arguments[1] != "-e" && arguments[1] != "--example") { + std::cerr << "Invalid usage." << std::endl; + displayUsage(); + return EXIT_FAILURE; + } + + int option = atoi(arguments[2].c_str()); + + if(option < 0 || option > 3) { + std::cerr << "Invalid usage." << std::endl; + displayUsage(); + return EXIT_FAILURE; + } + + std::cout << icsneo::GetVersion() << std::endl; + const auto& devices = icsneo::FindAllDevices(); + + auto it = std::find_if(devices.begin(), devices.end(), [&](const auto& dev) { + const auto& txNetworks = dev->getSupportedTXNetworks(); + const auto& rxNetworks = dev->getSupportedRXNetworks(); + + if(std::none_of(txNetworks.begin(), txNetworks.end(), [](const icsneo::Network& net) { return net.getType() == icsneo::Network::Type::A2B; })) { + return false; + } + + if(std::none_of(rxNetworks.begin(), rxNetworks.end(), [](const icsneo::Network& net) { return net.getType() == icsneo::Network::Type::A2B; })) { + return false; + } + + return true; + } + ); + + if(it == devices.end()) { + std::cerr << "Could not find RADA2B." << std::endl; + return EXIT_FAILURE; + } + + std::shared_ptr rada2b = *it; + if(!rada2b->open()) { + std::cout << "Failed to open RADA2B." << std::endl; + std::cout << icsneo::GetLastError() << std::endl; + return EXIT_FAILURE; + } + else { + std::cout << "Opened RADA2B." << std::endl; + } + + if(!rada2b->goOnline()) { + std::cout << "Failed to go online with RADA2B." << std::endl; + std::cout << icsneo::GetLastError() << std::endl; + return EXIT_FAILURE; + } + else { + std::cout << "RADA2B online." << std::endl; + } + + switch(option) { + case 0: + example0(rada2b); + break; + case 1: + example1(rada2b); + break; + case 2: + example2(rada2b); + break; + case 3: + example3(); + break; + default: + break; + } rada2b->goOffline(); rada2b->close(); - return 0; -} \ No newline at end of file + return EXIT_SUCCESS; +} diff --git a/include/icsneo/communication/message/a2bmessage.h b/include/icsneo/communication/message/a2bmessage.h index 12706ee..9df776a 100644 --- a/include/icsneo/communication/message/a2bmessage.h +++ b/include/icsneo/communication/message/a2bmessage.h @@ -4,140 +4,436 @@ #ifdef __cplusplus #include "icsneo/communication/message/message.h" +#include "icsneo/api/eventmanager.h" +#include +#include +#include -#define A2BMESSAGE_UPSTREAM 1 -#define A2BMESSAGE_DOWNSTREAM 0 - -#define A2BPCM_SAMPLE_SIZE 4 - -#define A2BPCM_L16 16 -#define A2BPCM_L24 24 - -#define A2BPCM_SAMPLERATE_44100 44100 -#define A2BPCM_SAMPLERATE_48000 48000 - namespace icsneo { typedef uint32_t A2BPCMSample; -typedef std::vector ChannelBuffer; class A2BMessage : public Frame { -public: - enum class A2BDirection : uint8_t { - DownStream = 0, - UpStream = 1 +private: + class FrameView { + private: + class SampleView { + public: + SampleView(uint8_t* vPtr, uint8_t bps, size_t ind) : + index(ind), viewPtr(vPtr), bytesPerSample(bps) {} + + operator A2BPCMSample() const { + if(!viewPtr) { + return 0; + } + A2BPCMSample sample = 0; + + std::copy(viewPtr+index*bytesPerSample, viewPtr+(index+1)*bytesPerSample, (uint8_t*)&sample); + if(bytesPerSample == 4) { + sample = sample >> 8; + } + + return sample; + } + + SampleView& operator=(A2BPCMSample sample) { + if(!viewPtr) { + return *this; + } + + if(bytesPerSample == 4) { + sample = sample << 8; + } + std::copy((uint8_t*)&sample, (uint8_t*)&sample + bytesPerSample, viewPtr + index*bytesPerSample); + return *this; + } + + SampleView(const SampleView&) = delete; + SampleView& operator=(const SampleView&) = delete; + + private: + + size_t index; + uint8_t* viewPtr; + uint8_t bytesPerSample; + }; + + public: + FrameView(uint8_t* vPtr, uint8_t nChannels, uint8_t bps) : viewPtr(vPtr), tdm(nChannels), bytesPerSample(bps) {} + + SampleView operator[](size_t index) { + + if(index >= ((size_t)tdm) * 2) { + EventManager::GetInstance().add(APIEvent(APIEvent::Type::ParameterOutOfRange, APIEvent::Severity::Error)); + return SampleView(nullptr, 0, 0); + } + return SampleView(viewPtr, bytesPerSample, index); + } + + FrameView& operator=(const std::vector& samples) { + if(!viewPtr) { + return *this; + } + + if(samples.size() != (size_t)(tdm)*2) { + EventManager::GetInstance().add(APIEvent(APIEvent::Type::BufferInsufficient, APIEvent::Severity::Error)); + return *this; + } + + for(size_t icsChannel = 0; icsChannel < ((size_t)(tdm) * 2); icsChannel++) { + operator[](icsChannel) = samples[icsChannel]; + } + + return *this; + } + + FrameView(const FrameView&) = delete; + FrameView& operator=(const FrameView&) = delete; + private: + uint8_t* viewPtr; + uint8_t tdm; + uint8_t bytesPerSample; }; - A2BMessage() = delete; +public: + enum class A2BDirection : uint8_t { + Downstream = 0, + Upstream = 1 + }; - A2BMessage(uint8_t bitDepth, uint8_t bytesPerSample, uint8_t numChannels) : - bDepth(bitDepth), - bps(bytesPerSample) - { - downstream.resize(numChannels); - upstream.resize(numChannels); + A2BMessage(uint8_t nChannels, bool chSize16, size_t size) : + numChannels(nChannels), + channelSize16(chSize16) + { + data.resize(std::min(roundNextMultiple(size, getFrameSize()),(size_t)maxSize), 0); } - void addSample(A2BPCMSample&& sample, A2BDirection dir, uint8_t channel) { - if(dir == A2BDirection::DownStream) { - downstream[channel].push_back(std::move(sample)); + bool allocateSpace(size_t numSpaceToAdd) { + size_t spaceToAdd = roundNextMultiple(numSpaceToAdd, getFrameSize()); + + if(spaceToAdd + data.size() > maxSize) { + return false; + } + + data.resize(data.size() + numSpaceToAdd, 0); + return true; + } + + bool addFrame(const std::vector& frame) { + if(frame.size() != ((size_t)numChannels)*2) { + return false; + } + + size_t oldSize = data.size(); + + if(!allocateSpace(getFrameSize())) { + return false; + } + + auto it = data.begin() + oldSize; + size_t offset = 0; + for(A2BPCMSample sample: frame) { + if(!channelSize16) { + sample = sample << 8; + } + std::copy((uint8_t*)&sample, (uint8_t*)&sample + getBytesPerSample(), it + offset); + offset+=getBytesPerSample(); + } + + return true; + } + + bool setFrame(const std::vector& frame, size_t frameNum) { + if(frame.size() != ((size_t)numChannels)*2 || frameNum >= getNumFrames()) { + return false; + } + + auto it = data.begin() + frameNum*getFrameSize(); + size_t offset = 0; + for(A2BPCMSample sample: frame) { + if(!channelSize16) { + sample = sample << 8; + } + std::copy((uint8_t*)&sample, (uint8_t*)&sample + getBytesPerSample(), it + offset); + offset+=getBytesPerSample(); + } + + return true; + } + + bool fillChannelAudioBuffer(A2BDirection dir, uint8_t channel, std::vector& channelBuffer) const { + if(channel >= numChannels) { + return false; + } + + size_t offset = getChannelIndex(dir, channel)*getBytesPerSample(); + + for(size_t frame = 0; frame < getNumFrames(); frame++, offset += getFrameSize()) { + std::copy(data.begin() + offset, data.end() + offset + getBytesPerSample(), std::back_inserter(channelBuffer)); + } + + return true; + } + + bool fillChannelStream(A2BDirection dir, uint8_t channel, std::unique_ptr& channelStream) const { + if(channel >= numChannels) { + return false; + } + + size_t offset = getChannelIndex(dir, channel)*getBytesPerSample(); + + for(size_t frame = 0; frame < getNumFrames(); frame++, offset += getFrameSize()) { + channelStream->write((const char*)(data.data() + offset), getBytesPerSample()); + } + + return true; + } + + void fill(A2BPCMSample sample) { + uint8_t* buf = data.data(); + + if(channelSize16) { + uint16_t sample16bit = sample & 0xFF; + uint16_t* samps = (uint16_t*)buf; + std::fill(samps, samps + data.size()/2, sample16bit); } else { - upstream[channel].push_back(std::move(sample)); + A2BPCMSample* samps = (A2BPCMSample*)buf; + sample = sample << 8; + std::fill(samps, samps + data.size()/4, sample); } - totalSamples++; } - const A2BPCMSample* getSamples(A2BDirection dir, uint8_t channel) const { - if(channel >= getNumChannels()) { - return nullptr; + bool fillFrame(A2BPCMSample sample, size_t frame) { + if(frame >= getNumFrames()) { + return false; } - if(dir == A2BDirection::DownStream) { - return downstream[channel].data(); + uint8_t* buf = data.data(); + size_t start = 2 * numChannels * frame; + size_t end = 2 * numChannels * (frame+1); + + if(channelSize16) { + uint16_t sample16bit = sample & 0xFF; + uint16_t* samps = (uint16_t*)buf; + std::fill(samps+start, samps + end, sample16bit); } - return upstream[channel].data(); + else { + A2BPCMSample* samps = (A2BPCMSample*)buf; + sample = sample << 8; + std::fill(samps+start, samps + end, sample); + } + return true; + } + + template + bool setAudioBuffer(Iterator begin, Iterator end, A2BDirection dir, uint8_t channel, uint32_t frame) { + size_t offset = getChannelIndex(dir, channel)*getBytesPerSample() + frame * getFrameSize(); + size_t dist = (size_t)(std::distance(begin, end)); + + if(dist > (data.size() - offset)) { + return false; + } + + std::copy(begin, end, data.begin() + offset); + return true; + } + + template + bool setAudioBuffer(Iterator begin, Iterator end) { + return setAudioBuffer(begin, end, A2BMessage::A2BDirection::Downstream, 0, 0); } std::optional getSample(A2BDirection dir, uint8_t channel, uint32_t frame) const { - const A2BPCMSample* samples = getSamples(dir, channel); - auto numSamplesInChannel = getNumSamplesInChannel(dir, channel); - if( - samples == nullptr || - frame >= numSamplesInChannel.value_or(0) + channel >= numChannels || + frame >= getNumFrames() ) { return std::nullopt; } - return samples[frame]; + A2BPCMSample sample = 0; + size_t offset = getChannelIndex(dir, channel)*getBytesPerSample() + frame * getFrameSize(); + + std::copy(data.begin() + offset, data.begin() + offset + getBytesPerSample(), (uint8_t*)&sample); + if(channelSize16) { + sample = sample >> 8; + } + + return sample; } - std::optional getNumSamplesInChannel(A2BDirection dir, uint8_t channel) const { - if(channel >= getNumChannels()) { + std::optional getSample(size_t sampleNum) const { + if(sampleNum >= getNumSamples()) { return std::nullopt; } - if(dir == A2BDirection::DownStream) { - return downstream[channel].size(); + + A2BPCMSample sample = 0; + size_t offset = sampleNum*getBytesPerSample(); + + std::copy(data.begin() + offset, data.begin() + offset + getBytesPerSample(), (uint8_t*)&sample); + + if(channelSize16) { + sample = sample >> 8; } - return upstream[channel].size(); + return sample; } - const std::vector& getDownstream() const { - return downstream; + bool setSample(A2BDirection dir, uint8_t channel, uint32_t frame, A2BPCMSample sample) { + if( + channel >= numChannels || + frame >= getNumFrames() + ) { + return false; + } + + size_t offset = getChannelIndex(dir, channel)*getBytesPerSample() + frame * getFrameSize(); + + if(!channelSize16) { + sample = sample << 8; + } + uint8_t* sampToBytes = (uint8_t*)&sample; + std::copy(sampToBytes,sampToBytes+getBytesPerSample(), data.begin() + offset); + + return true; } - const std::vector& getUpstream() const { - return upstream; + + bool setSample(uint8_t icsChannel, uint32_t frame, A2BPCMSample sample) { + if( + icsChannel >= (2*numChannels) || + frame >= getNumFrames() + ) { + return false; + } + + size_t offset = ((size_t)icsChannel)*getBytesPerSample() + frame * getFrameSize(); + + if(!channelSize16) { + sample = sample << 8; + } + uint8_t* sampToBytes = (uint8_t*)&sample; + std::copy(sampToBytes,sampToBytes+getBytesPerSample(), data.begin() + offset); + + return true; + } + + FrameView operator[](size_t index) { + if(index >= getNumFrames()) { + EventManager::GetInstance().add(APIEvent(APIEvent::Type::ParameterOutOfRange, APIEvent::Severity::Error)); + return FrameView(nullptr, 0, 0); + } + + return FrameView(data.data() + index*getFrameSize(), numChannels, getBytesPerSample()); } size_t getNumSamples() const { - return totalSamples; + return data.size()/((size_t)getBytesPerSample()); } uint8_t getNumChannels() const { - return static_cast(downstream.size()); + return numChannels; } uint8_t getBitDepth() const { - return bDepth; + return channelSize16 ? 16 : 24; } uint8_t getBytesPerSample() const { - return bps; + return channelSize16 ? 2 : 4; } - // Equals operation for testing. - bool operator==(const A2BMessage& rhs) const { - return channelSize16 == rhs.channelSize16 && - monitor == rhs.monitor && - txmsg == rhs.txmsg && - errIndicator == rhs.errIndicator && - syncFrame == rhs.syncFrame && - rfu2 == rhs.rfu2 && - downstream == rhs.downstream && - upstream == rhs.upstream && - totalSamples == rhs.totalSamples && - bDepth == rhs.bDepth && - bps == rhs.bps; + bool isTxMsg() const { + return txmsg; } - bool channelSize16; - bool monitor; - bool txmsg; - bool errIndicator; - bool syncFrame; - uint16_t rfu2; + void setTxMsgBit(bool bit) { + txmsg = bit; + } + + bool isMonitorMsg() const { + return monitor; + } + + void setMonitorBit(bool bit) { + monitor = bit; + } + + bool isErrIndicator() const { + return errIndicator; + } + + void setErrIndicatorBit(bool bit) { + errIndicator = bit; + } + + bool isSyncFrame() const { + return syncFrame; + } + + void setSyncFrameBit(bool bit) { + syncFrame = bit; + } + + uint16_t getRFU2() const { + return rfu2; + } + + void setRFU2(uint16_t newRfu2) { + rfu2 = newRfu2; + } + + size_t getFrameSize() const { + return 2*((size_t)numChannels) * ((size_t)getBytesPerSample()); + } + + size_t getNumFrames() const { + return data.size() / getFrameSize(); + } + + size_t getAudioBufferSize() const { + return data.size(); + } + + const uint8_t* getAudioBuffer() const { + return data.data(); + } + + + static constexpr uint32_t maxSize = 2048; private: - std::vector downstream; - std::vector upstream; - size_t totalSamples = 0; - - uint8_t bDepth; - uint8_t bps; + + uint8_t numChannels = 0; + bool channelSize16 = false; + bool monitor = false; + bool txmsg = false; + bool errIndicator = false; + bool syncFrame = false; + uint16_t rfu2 = 0; + + size_t roundNextMultiple(size_t x, size_t y) const { + if(y==0) { + return 0; + } + else if(x%y == 0) { + return x; + } + + return x + y - (x%y); + } + + size_t getChannelIndex(A2BDirection dir, uint8_t channel) const { + size_t channelIndex = 2 * ((size_t)channel); + + if(dir == A2BDirection::Upstream) { + channelIndex++; + } + + return channelIndex; + } }; } diff --git a/include/icsneo/communication/message/callback/streamoutput/a2bdecoder.h b/include/icsneo/communication/message/callback/streamoutput/a2bdecoder.h new file mode 100644 index 0000000..451542d --- /dev/null +++ b/include/icsneo/communication/message/callback/streamoutput/a2bdecoder.h @@ -0,0 +1,85 @@ +#ifndef __A2BDECODER_H_ +#define __A2BDECODER_H_ + +#ifdef __cplusplus + +#include "icsneo/communication/message/callback/streamoutput/streamoutput.h" +#include "icsneo/communication/message/a2bmessage.h" +#include "icsneo/device/device.h" + +namespace icsneo { + +typedef uint8_t Channel; + +class A2BAudioChannelMap { +public: + + A2BAudioChannelMap(uint8_t tdm); + + void set(Channel outChannel, A2BMessage::A2BDirection dir, Channel inChannel); + void setAll(Channel inChannel); + + Channel get(Channel outChannel, A2BMessage::A2BDirection dir) const; + + size_t size() const; + + uint8_t getTDM() const; + Channel& operator[](size_t idx); + + operator const std::vector&() const; + + + +private: + + size_t getChannelIndex(Channel channel, A2BMessage::A2BDirection dir) const; + + std::vector rawMap; +}; + + + +class A2BDecoder { +public: + + A2BDecoder( + std::unique_ptr&& streamOut, + bool chSize16, + const A2BAudioChannelMap& chMap + ); + + A2BDecoder( + const char* filename, + bool chSize16, + const A2BAudioChannelMap& chMap + ); + + operator bool() const; + + std::shared_ptr decode(); + + bool outputAll(std::shared_ptr &device); + + std::unique_ptr stream; + private: + + void initializeFromHeader(); + + uint8_t tdm; + uint8_t audioBytesPerSample; + uint8_t channelsInWave; + bool channelSize16; + A2BAudioChannelMap channelMap; + + std::vector frame; + std::vector frameWave; + + bool initialized = false; + +}; + +} + +#endif // __cplusplus + +#endif diff --git a/include/icsneo/communication/message/callback/streamoutput/a2bwavoutput.h b/include/icsneo/communication/message/callback/streamoutput/a2bwavoutput.h index c078570..111524e 100644 --- a/include/icsneo/communication/message/callback/streamoutput/a2bwavoutput.h +++ b/include/icsneo/communication/message/callback/streamoutput/a2bwavoutput.h @@ -5,17 +5,16 @@ #include "icsneo/communication/message/callback/streamoutput/streamoutput.h" #include "icsneo/communication/message/a2bmessage.h" -#include -#include +#include "icsneo/device/device.h" namespace icsneo { class A2BWAVOutput : public StreamOutput { public: - A2BWAVOutput(const char* filename, uint32_t sampleRate = A2BPCM_SAMPLERATE_44100) + A2BWAVOutput(const char* filename, uint32_t sampleRate = 44100) : StreamOutput(filename), wavSampleRate(sampleRate) {} - A2BWAVOutput(std::unique_ptr&& os, uint32_t sampleRate = A2BPCM_SAMPLERATE_44100) + A2BWAVOutput(std::unique_ptr&& os, uint32_t sampleRate = 44100) : StreamOutput(std::move(os)), wavSampleRate(sampleRate) {} void writeHeader(const std::shared_ptr& firstMsg) const; @@ -32,12 +31,11 @@ public: protected: - bool writeSamples(const std::shared_ptr& msg, A2BMessage::A2BDirection dir) const; - uint32_t wavSampleRate; mutable uint32_t streamStartPos; mutable bool firstMessageFlag = true; mutable bool closed = false; + }; } diff --git a/include/icsneo/device/tree/rada2b/rada2bsettings.h b/include/icsneo/device/tree/rada2b/rada2bsettings.h index 725d1e9..bbc7716 100644 --- a/include/icsneo/device/tree/rada2b/rada2bsettings.h +++ b/include/icsneo/device/tree/rada2b/rada2bsettings.h @@ -3,6 +3,7 @@ #include #include "icsneo/device/idevicesettings.h" +#include "icsneo/communication/message/a2bmessage.h" #ifdef __cplusplus @@ -11,6 +12,9 @@ namespace icsneo { #endif #pragma pack(push, 2) + + + typedef struct { uint8_t tdmMode; @@ -61,6 +65,34 @@ typedef struct { class RADA2BSettings : public IDeviceSettings { public: + + enum class NodeType : uint8_t { + Monitor = 0, + Master = 1, + Subnode = 2 + }; + + enum class TDMMode : uint8_t { + TDM2 = 0, + TDM4 = 1, + TDM8 = 2, + TDM12 = 3, + TDM16 = 4, + TDM20 = 5, + TDM24 = 6, + TDM32 = 7, + }; + + enum class ChannelSize : uint8_t { + chSize16 = 0, + chSize32 = 1 + }; + + enum class RADA2BDevice : uint8_t { + Monitor, + Node + }; + RADA2BSettings(std::shared_ptr com) : IDeviceSettings(com, sizeof(rada2b_settings_t)) {} const CAN_SETTINGS* getCANSettingsFor(Network net) const override { auto cfg = getStructurePointer(); @@ -88,6 +120,108 @@ public: return nullptr; } } + + TDMMode getTDMMode(RADA2BDevice device) const { + auto cfg = getStructurePointer(); + auto &deviceSettings = device == RADA2BDevice::Monitor ? cfg->a2b_monitor : cfg->a2b_node; + + return (TDMMode)(deviceSettings.tdmMode); + } + + uint8_t getTDMModeInt(RADA2BDevice device) const { + return tdmModeToChannelNum(getTDMMode(device)); + } + + ChannelSize getChannelSize(RADA2BDevice device) const { + auto cfg = getStructurePointer(); + auto &deviceSettings = device == RADA2BDevice::Monitor ? cfg->a2b_monitor : cfg->a2b_node; + + return (ChannelSize)(~(deviceSettings.flags & a2bSettingsFlag16bit)); + } + + uint8_t getChannelOffset(RADA2BDevice device, A2BMessage::A2BDirection dir) const { + auto cfg = getStructurePointer(); + auto &deviceSettings = device == RADA2BDevice::Monitor ? cfg->a2b_monitor : cfg->a2b_node; + + if(dir == A2BMessage::A2BDirection::Upstream) { + return deviceSettings.upstreamChannelOffset; + } + + return deviceSettings.downstreamChannelOffset; + } + + NodeType getNodeType(RADA2BDevice device) const { + auto cfg = getStructurePointer(); + auto &deviceSettings = device == RADA2BDevice::Monitor ? cfg->a2b_monitor : cfg->a2b_node; + + return (NodeType)(deviceSettings.nodeType); + } + + void setNodeType(RADA2BDevice device, NodeType newType) { + auto cfg = getMutableStructurePointer(); + auto &deviceSettings = device == RADA2BDevice::Monitor ? cfg->a2b_monitor : cfg->a2b_node; + + deviceSettings.nodeType = static_cast(newType); + } + + void setTDMMode(RADA2BDevice device, TDMMode newMode) { + auto cfg = getMutableStructurePointer(); + auto &deviceSettings = device == RADA2BDevice::Monitor ? cfg->a2b_monitor : cfg->a2b_node; + + deviceSettings.tdmMode = static_cast(newMode); + } + + void setChannelOffset(RADA2BDevice device, A2BMessage::A2BDirection dir, uint8_t newOffset) { + auto cfg = getMutableStructurePointer(); + auto &deviceSettings = device == RADA2BDevice::Monitor ? cfg->a2b_monitor : cfg->a2b_node; + + if(dir == A2BMessage::A2BDirection::Upstream) { + deviceSettings.upstreamChannelOffset = newOffset; + } + else { + deviceSettings.downstreamChannelOffset = newOffset; + } + } + + void setChannelSize(RADA2BDevice device, ChannelSize newChannelSize) { + auto cfg = getMutableStructurePointer(); + auto &deviceSettings = device == RADA2BDevice::Monitor ? cfg->a2b_monitor : cfg->a2b_node; + + if(newChannelSize == ChannelSize::chSize16) { + deviceSettings.flags |= a2bSettingsFlag16bit; + } + else { + deviceSettings.flags &= ~a2bSettingsFlag16bit; + } + } + + static const uint8_t tdmModeToChannelNum(TDMMode mode) { + + switch(mode) { + case TDMMode::TDM2: + return 4; + case TDMMode::TDM4: + return 8; + case TDMMode::TDM8: + return 16; + case TDMMode::TDM12: + return 24; + case TDMMode::TDM16: + return 32; + case TDMMode::TDM20: + return 40; + case TDMMode::TDM24: + return 48; + case TDMMode::TDM32: + return 64; + default: + break; + } + + return 0; + } + + static constexpr uint8_t a2bSettingsFlag16bit = 0x01; }; } diff --git a/test/a2bencoderdecodertest.cpp b/test/a2bencoderdecodertest.cpp index a3ad52e..65be429 100644 --- a/test/a2bencoderdecodertest.cpp +++ b/test/a2bencoderdecodertest.cpp @@ -1,6 +1,7 @@ #include "icsneo/icsneocpp.h" #include "icsneo/communication/encoder.h" #include "icsneo/communication/packet/a2bpacket.h" +#include "icsneo/communication/message/a2bmessage.h" #include "icsneo/communication/packetizer.h" #include "icsneo/api/eventmanager.h" #include "gtest/gtest.h" @@ -11,193 +12,14 @@ using namespace icsneo; class A2BEncoderDecoderTest : public ::testing::Test { protected: void SetUp() override { - report = [](APIEvent::Type, APIEvent::Severity) { // Unless caught by the test, the packetizer should not throw errors EXPECT_TRUE(false); }; - packetizer.emplace([this](APIEvent::Type t, APIEvent::Severity s) { - report(t, s); - }); - packetEncoder.emplace([this](APIEvent::Type t, APIEvent::Severity s) { - report(t, s); - }); - packetDecoder.emplace([this](APIEvent::Type t, APIEvent::Severity s) { - report(t, s); - }); - A2BPCMSample initialSample = 10 << 8; - constructTest( - msg1, - msg1Encoded, - {1,1,0,0,0,0}, - 1, - true, - false, - false, - false, - false, - 0, - 3, - initialSample - ); - - initialSample = (29 << 16) | (10 << 8); - constructTest( - msg2, - msg2Encoded, - {1,0,9,0,0,0}, - 1, - false, - true, - false, - false, - true, - 0, - 1, - initialSample - ); - - initialSample = (29 << 16) | (10 << 8); - constructTest( - msg3, - msg3Encoded, - {2,0,6,0,0xAA,0xFF}, - 2, - false, - false, - true, - true, - false, - 0xFFAA, - 3, - initialSample - ); - - constructTest( - msg4, - msg4Encoded, - {4,0,6,0,0xAA,0xFF}, - 4, - false, - false, - true, - true, - false, - 0xFFAA, - 0, - initialSample - ); - } - - void constructTest( - std::shared_ptr& testMsg, - std::vector& testMsgEncoded, - std::array encodedHeader, - uint8_t numChannels, - bool channelSize16, - bool monitor, - bool txmsg, - bool errIndicator, - bool syncFrame, - uint16_t rfu2, - uint32_t numFrames, - A2BPCMSample initialSample - ) - { - - testMsg = std::make_shared( - (uint8_t)(channelSize16 ? 16 : 24), - (uint8_t)(channelSize16 ? 2 : 4), - numChannels - ); - - auto addSample = [&]( - uint8_t channel, - A2BMessage::A2BDirection dir - ) { - testMsg->addSample(std::move(initialSample), dir, channel); - - for(size_t i = 0; i < static_cast(testMsg->getBytesPerSample()); i++) { - testMsgEncoded.push_back(static_cast(initialSample >> (i * 8))); - } - - initialSample++; - }; - - testMsg->network = Network(Network::NetID::A2B1); - testMsg->channelSize16 = channelSize16; - testMsg->monitor = monitor; - testMsg->txmsg = txmsg; - testMsg->errIndicator = errIndicator; - testMsg->syncFrame = syncFrame; - testMsg->rfu2 = rfu2; - - testMsgEncoded.reserve( - HardwareA2BPacket::coreMiniMessageHeaderSize + numFrames * static_cast(testMsg->getBytesPerSample()) - ); - - testMsgEncoded.insert( - testMsgEncoded.end(), - encodedHeader.begin(), - encodedHeader.end() - ); // Insert header - appendCoreMiniHeaderOffset(testMsgEncoded); - - for(unsigned int frame = 0; frame < numFrames; frame++) { - for(uint8_t channel = 0; channel < testMsg->getNumChannels(); channel++) { - - addSample(channel, A2BMessage::A2BDirection::DownStream); - addSample(channel, A2BMessage::A2BDirection::UpStream); - - } - } - } - - void appendCoreMiniHeaderOffset(std::vector& buf) { - for( - size_t i = 0; - i < (HardwareA2BPacket::coreMiniMessageHeaderSize - HardwareA2BPacket::a2bHeaderSize); - i++ - ) { - buf.push_back(0); - } - } - - void decrementEncodedMessageSize(std::vector& bytestream) { - if(bytestream.size() < 6) - return; - - uint16_t size = (bytestream[3] << 8) | bytestream[2]; - - if(size == 0) - return; - - size--; - - bytestream[2] = (uint8_t)size; - bytestream[3] = (uint8_t)(size >> 8); - } - - void testMessage(std::shared_ptr& msg, std::vector& msgEncoded) { - std::vector bytestream; - std::vector rawPacketBytes; - std::shared_ptr decodeMsg; - std::shared_ptr decodeA2BMsg; - - EXPECT_TRUE(packetEncoder->encode(*packetizer, bytestream, msg)); - rawPacketBytes = std::vector(bytestream.begin() + 6, bytestream.end()); - - EXPECT_EQ(rawPacketBytes, msgEncoded); - decrementEncodedMessageSize(bytestream); - - EXPECT_TRUE(packetizer->input(bytestream)); - auto packets = packetizer->output(); - if(packets.empty()) { EXPECT_TRUE(false);} - EXPECT_TRUE(packetDecoder->decode(decodeMsg, packets.back())); - decodeA2BMsg = std::static_pointer_cast(decodeMsg); - - EXPECT_EQ(*msg, *decodeA2BMsg); + packetizer.emplace([this](APIEvent::Type t, APIEvent::Severity s) { report(t, s); }); + packetEncoder.emplace([this](APIEvent::Type t, APIEvent::Severity s) { report(t, s); }); + packetDecoder.emplace([this](APIEvent::Type t, APIEvent::Severity s) { report(t, s); }); } device_eventhandler_t report; @@ -205,31 +27,59 @@ protected: std::optional packetizer; std::optional packetDecoder; - std::shared_ptr msg1; - std::vector msg1Encoded; + std::vector testBytes = + {0xaa, 0x0c, 0x15, 0x00, 0x0b, 0x02, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x03, 0x02, 0x00, 0x00, + 0x08, 0x04, 0x00, 0x00}; - std::shared_ptr msg2; - std::vector msg2Encoded; - - std::shared_ptr msg3; - std::vector msg3Encoded; - - std::shared_ptr msg4; - std::vector msg4Encoded; + std::vector recvBytes = + {0xaa, 0x00, 0x2a, 0x00, 0x0a, 0x02, 0x02, 0x01, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x02, 0x00, 0x00, 0x08, 0x04, + 0x00, 0x00}; }; -TEST_F(A2BEncoderDecoderTest, ChannelSize16SingleChannel) { - testMessage(msg1, msg1Encoded); +TEST_F(A2BEncoderDecoderTest, PacketEncoderTest) +{ + std::vector bytestream; + auto messagePtr = std::make_shared((uint8_t)2, true, 8); + messagePtr->network = icsneo::Network::NetID::A2B2; + A2BMessage& message = *messagePtr.get(); + + message[0][0] = (0x02 << 8) | (0x03); + message[0][2] = (0x04 << 8) | (0x08); + + packetEncoder->encode(*packetizer, bytestream, messagePtr); + EXPECT_EQ(bytestream, testBytes); } -TEST_F(A2BEncoderDecoderTest, ChannelSize24MultiChannel) { - testMessage(msg2, msg2Encoded); -} +TEST_F(A2BEncoderDecoderTest, PacketDecoderTest) +{ + std::shared_ptr decodeMsg; + std::shared_ptr message = std::make_shared((uint8_t)2, true, 8); -TEST_F(A2BEncoderDecoderTest, ChannelSize24MultiChannelMultiFrame) { - testMessage(msg3, msg3Encoded); -} + message->network = icsneo::Network::NetID::A2B1; + message->setTxMsgBit(false); + message->setMonitorBit(true); -TEST_F(A2BEncoderDecoderTest, NoFrames) { - testMessage(msg4, msg4Encoded); + EXPECT_TRUE(message->setSample(0, 0, (0x02 << 8) | (0x03))); + EXPECT_TRUE(message->setSample(2, 0, (0x04 << 8) | (0x08))); + + EXPECT_TRUE(packetizer->input(recvBytes)); + auto packets = packetizer->output(); + if(packets.empty()) { + EXPECT_TRUE(false); + } + EXPECT_TRUE(packetDecoder->decode(decodeMsg, packets.back())); + auto testMessage = std::dynamic_pointer_cast(decodeMsg); + EXPECT_EQ(message->network, testMessage->network); + EXPECT_EQ(message->data, testMessage->data); + EXPECT_EQ(message->getNumChannels(), testMessage->getNumChannels()); + EXPECT_EQ(message->isMonitorMsg(), testMessage->isMonitorMsg()); + EXPECT_EQ(message->isTxMsg(), testMessage->isTxMsg()); + EXPECT_EQ(message->isErrIndicator(), testMessage->isErrIndicator()); + EXPECT_EQ(message->isSyncFrame(), testMessage->isSyncFrame()); + EXPECT_EQ(message->getRFU2(), testMessage->getRFU2()); }