A2B: Add A2B Tx streaming support
A2B: Add A2BDecoder for streaming wave to A2B device RADA2B: Add functions to configure settingspull/56/head
parent
539cfa511b
commit
ddee1254a0
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -140,6 +140,30 @@ bool Decoder::decode(std::shared_ptr<Message>& result, const std::shared_ptr<Pac
|
|||
|
||||
return true;
|
||||
}
|
||||
case Network::Type::A2B: {
|
||||
result = HardwareA2BPacket::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
|
||||
}
|
||||
|
||||
A2BMessage& msg = *static_cast<A2BMessage*>(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<LINMessage*>(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<Message>& result, const std::shared_ptr<Pac
|
|||
}
|
||||
break;
|
||||
}
|
||||
case Network::Type::A2B: {
|
||||
result = HardwareA2BPacket::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
|
||||
}
|
||||
|
||||
A2BMessage& msg = *static_cast<A2BMessage*>(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<LINMessage*>(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<RawMessage>(packet->network, packet->data);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,156 @@
|
|||
#include "icsneo/communication/message/callback/streamoutput/a2bdecoder.h"
|
||||
#include <chrono>
|
||||
#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<Channel>&() const {
|
||||
return rawMap;
|
||||
}
|
||||
|
||||
A2BDecoder::A2BDecoder(
|
||||
std::unique_ptr<std::istream>&& 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<std::ifstream>(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<A2BMessage> A2BDecoder::decode() {
|
||||
if(!*(this)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto a2bMessagePtr = std::make_shared<icsneo::A2BMessage>(
|
||||
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>& 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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<A2BMessage>& firstMsg) const {
|
||||
|
||||
WaveFileHeader header = WaveFileHeader(2 * firstMsg->getNumChannels(), wavSampleRate, firstMsg->getBitDepth());
|
||||
header.write(stream);
|
||||
streamStartPos = static_cast<uint32_t>(stream->tellp());
|
||||
|
||||
}
|
||||
|
||||
bool A2BWAVOutput::callIfMatch(const std::shared_ptr<Message>& 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<Frame>(message);
|
||||
|
||||
|
|
@ -31,23 +34,21 @@ bool A2BWAVOutput::callIfMatch(const std::shared_ptr<Message>& 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; i<a2bmsg->getNumSamples(); 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<A2BMessage>& 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
#include "icsneo/communication/packet/a2bpacket.h"
|
||||
#include <cstring>
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace icsneo {
|
||||
|
||||
|
|
@ -14,148 +14,49 @@ std::shared_ptr<Message> HardwareA2BPacket::DecodeToMessage(const std::vector<ui
|
|||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto getSampleFromBytes = [](uint8_t bytesPerSample, const uint8_t *bytes) {
|
||||
A2BPCMSample result = 0;
|
||||
|
||||
for(auto i = 0; i < bytesPerSample; i++) {
|
||||
result |= static_cast<uint32_t>(bytes[i]) << (i * 8);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
|
||||
const HardwareA2BPacket *data = (const HardwareA2BPacket*)bytestream.data();
|
||||
|
||||
uint32_t totalPackedLength = static_cast<uint32_t>(bytestream.size()) - static_cast<uint32_t>(coreMiniMessageHeaderSize); // First 28 bytes are message header.
|
||||
size_t totalPackedLength = static_cast<size_t>(bytestream.size()) - static_cast<size_t>(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<A2BMessage> msg = std::make_shared<A2BMessage>(
|
||||
(uint8_t)data->header.channelNum,
|
||||
data->header.channelSize16,
|
||||
totalPackedLength
|
||||
);
|
||||
|
||||
std::shared_ptr<A2BMessage> msg = std::make_shared<A2BMessage>(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<uint32_t>(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<uint8_t>& 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<size_t>(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<uint8_t>(message.rfu2));
|
||||
bytestream.push_back(static_cast<uint8_t>(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<uint32_t>(message.getBytesPerSample()); i++) {
|
||||
bytestream.push_back(static_cast<uint8_t>((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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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``
|
||||
|
|
|
|||
|
|
@ -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 <iostream>
|
||||
#include <iomanip>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
#include <fstream>
|
||||
#include <icsneo/icsneocpp.h>
|
||||
#include <icsneo/device/tree/rada2b/rada2bsettings.h>
|
||||
#include <icsneo/communication/message/callback/streamoutput/a2bdecoder.h>
|
||||
#include <string>
|
||||
|
||||
#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<uint8_t> 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<uint8_t> sineWave;
|
||||
sineWave.reserve(sineWaveSamples.size() + sizeof(header));
|
||||
|
||||
std::shared_ptr<icsneo::Device> 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<icsneo::Device>& 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<std::istringstream>(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<icsneo::A2BWAVOutput>("examples/cpp/a2b/src/out.wav")); // Starts writing A2B PCM data to out.wav
|
||||
// Example 1: RX
|
||||
void example1(std::shared_ptr<icsneo::Device>& rada2b) {
|
||||
std::cout << "Receiving 5 seconds of audio data..." << std::endl;
|
||||
|
||||
auto handler2 = rada2b->addMessageCallback(
|
||||
std::make_shared<icsneo::MessageCallback>(
|
||||
[](std::shared_ptr<icsneo::Message> message) {
|
||||
std::cout << "Got in callback " << std::endl;
|
||||
if(message->type == icsneo::Message::Type::Frame) {
|
||||
std::shared_ptr<icsneo::Frame> frame = std::static_pointer_cast<icsneo::Frame>(message);
|
||||
// Add WAV output message callback
|
||||
// Saves samples to "out.wav"
|
||||
auto handler = rada2b->addMessageCallback(std::make_shared<icsneo::A2BWAVOutput>("out.wav", 48000));
|
||||
|
||||
if(frame->network.getType() == icsneo::Network::Type::A2B) {
|
||||
std::shared_ptr<icsneo::A2BMessage> msg = std::static_pointer_cast<icsneo::A2BMessage>(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<icsneo::Device>& 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<std::string> 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<icsneo::Device> 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;
|
||||
}
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,140 +4,436 @@
|
|||
#ifdef __cplusplus
|
||||
|
||||
#include "icsneo/communication/message/message.h"
|
||||
#include "icsneo/api/eventmanager.h"
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
|
||||
|
||||
#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<A2BPCMSample> 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<A2BPCMSample>& 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<A2BPCMSample>& 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<A2BPCMSample>& 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<uint8_t>& 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<std::ostream>& 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<typename Iterator>
|
||||
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<typename Iterator>
|
||||
bool setAudioBuffer(Iterator begin, Iterator end) {
|
||||
return setAudioBuffer(begin, end, A2BMessage::A2BDirection::Downstream, 0, 0);
|
||||
}
|
||||
|
||||
std::optional<A2BPCMSample> 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<size_t> getNumSamplesInChannel(A2BDirection dir, uint8_t channel) const {
|
||||
if(channel >= getNumChannels()) {
|
||||
std::optional<A2BPCMSample> 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<ChannelBuffer>& 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<ChannelBuffer>& 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<uint8_t>(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<ChannelBuffer> downstream;
|
||||
std::vector<ChannelBuffer> 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;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Channel>&() const;
|
||||
|
||||
|
||||
|
||||
private:
|
||||
|
||||
size_t getChannelIndex(Channel channel, A2BMessage::A2BDirection dir) const;
|
||||
|
||||
std::vector<Channel> rawMap;
|
||||
};
|
||||
|
||||
|
||||
|
||||
class A2BDecoder {
|
||||
public:
|
||||
|
||||
A2BDecoder(
|
||||
std::unique_ptr<std::istream>&& streamOut,
|
||||
bool chSize16,
|
||||
const A2BAudioChannelMap& chMap
|
||||
);
|
||||
|
||||
A2BDecoder(
|
||||
const char* filename,
|
||||
bool chSize16,
|
||||
const A2BAudioChannelMap& chMap
|
||||
);
|
||||
|
||||
operator bool() const;
|
||||
|
||||
std::shared_ptr<A2BMessage> decode();
|
||||
|
||||
bool outputAll(std::shared_ptr<Device> &device);
|
||||
|
||||
std::unique_ptr<std::istream> stream;
|
||||
private:
|
||||
|
||||
void initializeFromHeader();
|
||||
|
||||
uint8_t tdm;
|
||||
uint8_t audioBytesPerSample;
|
||||
uint8_t channelsInWave;
|
||||
bool channelSize16;
|
||||
A2BAudioChannelMap channelMap;
|
||||
|
||||
std::vector<uint8_t> frame;
|
||||
std::vector<uint8_t> frameWave;
|
||||
|
||||
bool initialized = false;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // __cplusplus
|
||||
|
||||
#endif
|
||||
|
|
@ -5,17 +5,16 @@
|
|||
|
||||
#include "icsneo/communication/message/callback/streamoutput/streamoutput.h"
|
||||
#include "icsneo/communication/message/a2bmessage.h"
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
#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<std::ostream>&& os, uint32_t sampleRate = A2BPCM_SAMPLERATE_44100)
|
||||
A2BWAVOutput(std::unique_ptr<std::ostream>&& os, uint32_t sampleRate = 44100)
|
||||
: StreamOutput(std::move(os)), wavSampleRate(sampleRate) {}
|
||||
|
||||
void writeHeader(const std::shared_ptr<A2BMessage>& firstMsg) const;
|
||||
|
|
@ -32,12 +31,11 @@ public:
|
|||
|
||||
protected:
|
||||
|
||||
bool writeSamples(const std::shared_ptr<A2BMessage>& msg, A2BMessage::A2BDirection dir) const;
|
||||
|
||||
uint32_t wavSampleRate;
|
||||
mutable uint32_t streamStartPos;
|
||||
mutable bool firstMessageFlag = true;
|
||||
mutable bool closed = false;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include <stdint.h>
|
||||
#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<Communication> com) : IDeviceSettings(com, sizeof(rada2b_settings_t)) {}
|
||||
const CAN_SETTINGS* getCANSettingsFor(Network net) const override {
|
||||
auto cfg = getStructurePointer<rada2b_settings_t>();
|
||||
|
|
@ -88,6 +120,108 @@ public:
|
|||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
TDMMode getTDMMode(RADA2BDevice device) const {
|
||||
auto cfg = getStructurePointer<rada2b_settings_t>();
|
||||
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<rada2b_settings_t>();
|
||||
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<rada2b_settings_t>();
|
||||
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<rada2b_settings_t>();
|
||||
auto &deviceSettings = device == RADA2BDevice::Monitor ? cfg->a2b_monitor : cfg->a2b_node;
|
||||
|
||||
return (NodeType)(deviceSettings.nodeType);
|
||||
}
|
||||
|
||||
void setNodeType(RADA2BDevice device, NodeType newType) {
|
||||
auto cfg = getMutableStructurePointer<rada2b_settings_t>();
|
||||
auto &deviceSettings = device == RADA2BDevice::Monitor ? cfg->a2b_monitor : cfg->a2b_node;
|
||||
|
||||
deviceSettings.nodeType = static_cast<uint8_t>(newType);
|
||||
}
|
||||
|
||||
void setTDMMode(RADA2BDevice device, TDMMode newMode) {
|
||||
auto cfg = getMutableStructurePointer<rada2b_settings_t>();
|
||||
auto &deviceSettings = device == RADA2BDevice::Monitor ? cfg->a2b_monitor : cfg->a2b_node;
|
||||
|
||||
deviceSettings.tdmMode = static_cast<uint8_t>(newMode);
|
||||
}
|
||||
|
||||
void setChannelOffset(RADA2BDevice device, A2BMessage::A2BDirection dir, uint8_t newOffset) {
|
||||
auto cfg = getMutableStructurePointer<rada2b_settings_t>();
|
||||
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<rada2b_settings_t>();
|
||||
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;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<A2BMessage>& testMsg,
|
||||
std::vector<uint8_t>& testMsgEncoded,
|
||||
std::array<uint8_t, 6> 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<A2BMessage>(
|
||||
(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<size_t>(testMsg->getBytesPerSample()); i++) {
|
||||
testMsgEncoded.push_back(static_cast<uint8_t>(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<size_t>(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<uint8_t>& buf) {
|
||||
for(
|
||||
size_t i = 0;
|
||||
i < (HardwareA2BPacket::coreMiniMessageHeaderSize - HardwareA2BPacket::a2bHeaderSize);
|
||||
i++
|
||||
) {
|
||||
buf.push_back(0);
|
||||
}
|
||||
}
|
||||
|
||||
void decrementEncodedMessageSize(std::vector<uint8_t>& 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<A2BMessage>& msg, std::vector<uint8_t>& msgEncoded) {
|
||||
std::vector<uint8_t> bytestream;
|
||||
std::vector<uint8_t> rawPacketBytes;
|
||||
std::shared_ptr<Message> decodeMsg;
|
||||
std::shared_ptr<A2BMessage> decodeA2BMsg;
|
||||
|
||||
EXPECT_TRUE(packetEncoder->encode(*packetizer, bytestream, msg));
|
||||
rawPacketBytes = std::vector<uint8_t>(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<A2BMessage>(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> packetizer;
|
||||
std::optional<Decoder> packetDecoder;
|
||||
|
||||
std::shared_ptr<A2BMessage> msg1;
|
||||
std::vector<uint8_t> msg1Encoded;
|
||||
std::vector<uint8_t> testBytes =
|
||||
{0xaa, 0x0c, 0x15, 0x00, 0x0b, 0x02, 0x00, 0x00,
|
||||
0x08, 0x00, 0x00, 0x00, 0x03, 0x02, 0x00, 0x00,
|
||||
0x08, 0x04, 0x00, 0x00};
|
||||
|
||||
std::shared_ptr<A2BMessage> msg2;
|
||||
std::vector<uint8_t> msg2Encoded;
|
||||
|
||||
std::shared_ptr<A2BMessage> msg3;
|
||||
std::vector<uint8_t> msg3Encoded;
|
||||
|
||||
std::shared_ptr<A2BMessage> msg4;
|
||||
std::vector<uint8_t> msg4Encoded;
|
||||
std::vector<uint8_t> 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<uint8_t> bytestream;
|
||||
auto messagePtr = std::make_shared<icsneo::A2BMessage>((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<icsneo::Message> decodeMsg;
|
||||
std::shared_ptr<icsneo::A2BMessage> message = std::make_shared<icsneo::A2BMessage>((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<icsneo::A2BMessage>(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());
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue