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
|
set(SRC_FILES
|
||||||
communication/message/flexray/control/flexraycontrolmessage.cpp
|
communication/message/flexray/control/flexraycontrolmessage.cpp
|
||||||
communication/message/callback/streamoutput/a2bwavoutput.cpp
|
communication/message/callback/streamoutput/a2bwavoutput.cpp
|
||||||
|
communication/message/callback/streamoutput/a2bdecoder.cpp
|
||||||
communication/message/neomessage.cpp
|
communication/message/neomessage.cpp
|
||||||
communication/message/ethphymessage.cpp
|
communication/message/ethphymessage.cpp
|
||||||
communication/message/linmessage.cpp
|
communication/message/linmessage.cpp
|
||||||
|
|
|
||||||
|
|
@ -140,6 +140,30 @@ bool Decoder::decode(std::shared_ptr<Message>& result, const std::shared_ptr<Pac
|
||||||
|
|
||||||
return true;
|
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: {
|
case Network::Type::Internal: {
|
||||||
switch(packet->network.getNetID()) {
|
switch(packet->network.getNetID()) {
|
||||||
case Network::NetID::Reset_Status: {
|
case Network::NetID::Reset_Status: {
|
||||||
|
|
@ -352,30 +376,6 @@ bool Decoder::decode(std::shared_ptr<Message>& result, const std::shared_ptr<Pac
|
||||||
}
|
}
|
||||||
break;
|
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
|
// For the moment other types of messages will automatically be decoded as raw messages
|
||||||
|
|
|
||||||
|
|
@ -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/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 {
|
void A2BWAVOutput::writeHeader(const std::shared_ptr<A2BMessage>& firstMsg) const {
|
||||||
|
|
||||||
WaveFileHeader header = WaveFileHeader(2 * firstMsg->getNumChannels(), wavSampleRate, firstMsg->getBitDepth());
|
WaveFileHeader header = WaveFileHeader(2 * firstMsg->getNumChannels(), wavSampleRate, firstMsg->getBitDepth());
|
||||||
header.write(stream);
|
header.write(stream);
|
||||||
streamStartPos = static_cast<uint32_t>(stream->tellp());
|
streamStartPos = static_cast<uint32_t>(stream->tellp());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool A2BWAVOutput::callIfMatch(const std::shared_ptr<Message>& message) const {
|
bool A2BWAVOutput::callIfMatch(const std::shared_ptr<Message>& message) const {
|
||||||
if(closed)
|
|
||||||
{
|
if(closed) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(message->type != Message::Type::Frame)
|
if(message->type != Message::Type::Frame) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const auto& frame = std::static_pointer_cast<Frame>(message);
|
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;
|
firstMessageFlag = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!writeSamples(a2bmsg, A2BMessage::A2BDirection::DownStream)) {
|
// Might need to readd this block of code later if sample alignment fix is necessary
|
||||||
close();
|
/*
|
||||||
|
std::streamsize bps = (std::streamsize)a2bmsg->getBytesPerSample();
|
||||||
return false;
|
for(size_t i=0; i<a2bmsg->getNumSamples(); i++) {
|
||||||
|
A2BPCMSample samp = *(a2bmsg->getSample(i));
|
||||||
|
write((void*)&samp, bps);
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
if(!writeSamples(a2bmsg, A2BMessage::A2BDirection::UpStream)) {
|
write((void*)a2bmsg->getAudioBuffer(), a2bmsg->getAudioBufferSize());
|
||||||
close();
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void A2BWAVOutput::close() const
|
void A2BWAVOutput::close() const {
|
||||||
{
|
|
||||||
if(closed) {
|
if(closed) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -65,36 +66,4 @@ void A2BWAVOutput::close() const
|
||||||
closed = true;
|
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 "icsneo/communication/packet/a2bpacket.h"
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace icsneo {
|
namespace icsneo {
|
||||||
|
|
||||||
|
|
@ -15,147 +15,48 @@ std::shared_ptr<Message> HardwareA2BPacket::DecodeToMessage(const std::vector<ui
|
||||||
return nullptr;
|
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();
|
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;
|
std::shared_ptr<A2BMessage> msg = std::make_shared<A2BMessage>(
|
||||||
uint8_t numChannels = data->header.channelNum;
|
(uint8_t)data->header.channelNum,
|
||||||
uint8_t bitDepth = data->header.channelSize16 ? A2BPCM_L16 : A2BPCM_L24;
|
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(
|
msg->setMonitorBit(data->header.monitor);
|
||||||
getSampleFromBytes(bytesPerChannel, bytes + bytesPerChannel),
|
msg->setTxMsgBit(data->header.txmsg);
|
||||||
A2BMessage::A2BDirection::UpStream,
|
msg->setErrIndicatorBit(data->header.errIndicator);
|
||||||
channel
|
msg->setSyncFrameBit(data->header.syncFrame);
|
||||||
);
|
msg->setRFU2(data->header.rfu2);
|
||||||
|
msg->setAudioBuffer(bytestream.begin() + coreMiniMessageHeaderSize, bytestream.end());
|
||||||
}
|
|
||||||
|
|
||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HardwareA2BPacket::EncodeFromMessage(const A2BMessage& message, std::vector<uint8_t>& bytestream, const device_eventhandler_t& report) {
|
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) {
|
if(message.getBytesPerSample() != 2 && message.getBytesPerSample() != 4) {
|
||||||
report(APIEvent::Type::MessageFormattingError, APIEvent::Severity::Error);
|
report(APIEvent::Type::MessageFormattingError, APIEvent::Severity::Error);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t sampleBytes = message.getNumSamples() * static_cast<size_t>(message.getBytesPerSample());
|
size_t sampleBytes = message.getAudioBufferSize();
|
||||||
size_t totalSize = coreMiniMessageHeaderSize + sampleBytes;
|
size_t totalSize = a2btxMessageHeaderSize + sampleBytes;
|
||||||
|
|
||||||
if(totalSize > a2bMessageMaxLength) {
|
bytestream.resize(totalSize, 0);
|
||||||
report(APIEvent::Type::MessageMaxLengthExceeded, APIEvent::Severity::Error);
|
uint32_t offset = 0;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bytestream.reserve(totalSize);
|
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);
|
||||||
|
|
||||||
bytestream.push_back(message.getNumChannels());
|
std::copy(message.data.begin(), message.data.end(), bytestream.begin() + offset);
|
||||||
bytestream.push_back(message.channelSize16 ? 1 : 0);
|
|
||||||
|
|
||||||
uint8_t a2b2Bits = 0;
|
|
||||||
|
|
||||||
if(message.monitor) {
|
|
||||||
a2b2Bits = a2b2Bits | 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
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++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
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``
|
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``
|
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,
|
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
|
||||||
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`` the corresponding channel in the wave file would be
|
||||||
variable ``IS_UPSTREAM`` which is ``0`` when downstream and ``1`` when upstream and desired a channel ``CHANNEL_NUM`` in either downstream or upstream the
|
``2*CHANNEL_NUM + IS_UPSTREAM``.
|
||||||
channel ``IS_UPSTREAM * NUM_CHANNELS + CHANNEL_NUM`` would correspond to the channel in the outputted wave file.
|
|
||||||
|
|
||||||
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
|
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
|
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``.
|
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 <iostream>
|
||||||
#include <iomanip>
|
#include <fstream>
|
||||||
#include <thread>
|
#include <icsneo/icsneocpp.h>
|
||||||
#include <chrono>
|
#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::vector<uint8_t> sineWave;
|
||||||
std::cout << "Start example\n";
|
sineWave.reserve(sineWaveSamples.size() + sizeof(header));
|
||||||
auto devices = icsneo::FindAllDevices();
|
|
||||||
|
|
||||||
std::shared_ptr<icsneo::Device> rada2b;
|
sineWave.insert(sineWave.begin(), (uint8_t*)&header, (uint8_t*)(&header) + sizeof(header));
|
||||||
for(auto& device : devices) {
|
std::copy(sineWaveSamples.begin(), sineWaveSamples.end(), std::back_inserter(sineWave));
|
||||||
if(device->getSerial() == rada2bSerial) {
|
|
||||||
rada2b = device;
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
rada2b->open();
|
decoder.stream->clear();
|
||||||
rada2b->goOnline();
|
decoder.stream->seekg(0, std::ios::beg);
|
||||||
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
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example 1: RX
|
||||||
|
void example1(std::shared_ptr<icsneo::Device>& rada2b) {
|
||||||
|
std::cout << "Receiving 5 seconds of audio data..." << std::endl;
|
||||||
|
|
||||||
|
// Add WAV output message callback
|
||||||
|
// Saves samples to "out.wav"
|
||||||
|
auto handler = rada2b->addMessageCallback(std::make_shared<icsneo::A2BWAVOutput>("out.wav", 48000));
|
||||||
|
|
||||||
|
// Sleep this thread for 5 seconds, message callback still runs
|
||||||
|
std::this_thread::sleep_for(std::chrono::seconds(5));
|
||||||
|
|
||||||
|
// Remove callback
|
||||||
|
rada2b->removeMessageCallback(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 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;
|
||||||
}
|
}
|
||||||
}));
|
|
||||||
|
|
||||||
std::this_thread::sleep_for(std::chrono::seconds(5)); // captures 5 seconds of A2B data.
|
// Get current tdm mode
|
||||||
|
tdm = rada2bSettings->getTDMModeInt(icsneo::RADA2BSettings::RADA2BDevice::Node);
|
||||||
|
|
||||||
rada2b->removeMessageCallback(handler1);
|
std::cout << "Current tdm mode: " << tdm << std::endl;
|
||||||
rada2b->removeMessageCallback(handler2);
|
// Set node type to master node.
|
||||||
|
rada2bSettings->setNodeType(icsneo::RADA2BSettings::RADA2BDevice::Node, icsneo::RADA2BSettings::NodeType::Master);
|
||||||
|
|
||||||
std::cout << "End A2B example\n";
|
// 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->goOffline();
|
||||||
rada2b->close();
|
rada2b->close();
|
||||||
|
|
||||||
return 0;
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
@ -4,140 +4,436 @@
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
|
|
||||||
#include "icsneo/communication/message/message.h"
|
#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 {
|
namespace icsneo {
|
||||||
|
|
||||||
typedef uint32_t A2BPCMSample;
|
typedef uint32_t A2BPCMSample;
|
||||||
typedef std::vector<A2BPCMSample> ChannelBuffer;
|
|
||||||
|
|
||||||
class A2BMessage : public Frame {
|
class A2BMessage : public Frame {
|
||||||
public:
|
private:
|
||||||
enum class A2BDirection : uint8_t {
|
class FrameView {
|
||||||
DownStream = 0,
|
private:
|
||||||
UpStream = 1
|
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;
|
||||||
};
|
};
|
||||||
|
|
||||||
A2BMessage() = delete;
|
public:
|
||||||
|
FrameView(uint8_t* vPtr, uint8_t nChannels, uint8_t bps) : viewPtr(vPtr), tdm(nChannels), bytesPerSample(bps) {}
|
||||||
|
|
||||||
A2BMessage(uint8_t bitDepth, uint8_t bytesPerSample, uint8_t numChannels) :
|
SampleView operator[](size_t index) {
|
||||||
bDepth(bitDepth),
|
|
||||||
bps(bytesPerSample)
|
if(index >= ((size_t)tdm) * 2) {
|
||||||
{
|
EventManager::GetInstance().add(APIEvent(APIEvent::Type::ParameterOutOfRange, APIEvent::Severity::Error));
|
||||||
downstream.resize(numChannels);
|
return SampleView(nullptr, 0, 0);
|
||||||
upstream.resize(numChannels);
|
}
|
||||||
|
return SampleView(viewPtr, bytesPerSample, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
void addSample(A2BPCMSample&& sample, A2BDirection dir, uint8_t channel) {
|
FrameView& operator=(const std::vector<A2BPCMSample>& samples) {
|
||||||
if(dir == A2BDirection::DownStream) {
|
if(!viewPtr) {
|
||||||
downstream[channel].push_back(std::move(sample));
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
enum class A2BDirection : uint8_t {
|
||||||
|
Downstream = 0,
|
||||||
|
Upstream = 1
|
||||||
|
};
|
||||||
|
|
||||||
|
A2BMessage(uint8_t nChannels, bool chSize16, size_t size) :
|
||||||
|
numChannels(nChannels),
|
||||||
|
channelSize16(chSize16)
|
||||||
|
{
|
||||||
|
data.resize(std::min(roundNextMultiple(size, getFrameSize()),(size_t)maxSize), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
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 {
|
bool fillFrame(A2BPCMSample sample, size_t frame) {
|
||||||
if(channel >= getNumChannels()) {
|
if(frame >= getNumFrames()) {
|
||||||
return nullptr;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(dir == A2BDirection::DownStream) {
|
uint8_t* buf = data.data();
|
||||||
return downstream[channel].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 {
|
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(
|
if(
|
||||||
samples == nullptr ||
|
channel >= numChannels ||
|
||||||
frame >= numSamplesInChannel.value_or(0)
|
frame >= getNumFrames()
|
||||||
) {
|
) {
|
||||||
return std::nullopt;
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<size_t> getNumSamplesInChannel(A2BDirection dir, uint8_t channel) const {
|
return sample;
|
||||||
if(channel >= getNumChannels()) {
|
}
|
||||||
|
|
||||||
|
std::optional<A2BPCMSample> getSample(size_t sampleNum) const {
|
||||||
|
if(sampleNum >= getNumSamples()) {
|
||||||
return std::nullopt;
|
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 {
|
bool setSample(A2BDirection dir, uint8_t channel, uint32_t frame, A2BPCMSample sample) {
|
||||||
return downstream;
|
if(
|
||||||
|
channel >= numChannels ||
|
||||||
|
frame >= getNumFrames()
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<ChannelBuffer>& getUpstream() const {
|
size_t offset = getChannelIndex(dir, channel)*getBytesPerSample() + frame * getFrameSize();
|
||||||
return upstream;
|
|
||||||
|
if(!channelSize16) {
|
||||||
|
sample = sample << 8;
|
||||||
|
}
|
||||||
|
uint8_t* sampToBytes = (uint8_t*)&sample;
|
||||||
|
std::copy(sampToBytes,sampToBytes+getBytesPerSample(), data.begin() + offset);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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 {
|
size_t getNumSamples() const {
|
||||||
return totalSamples;
|
return data.size()/((size_t)getBytesPerSample());
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t getNumChannels() const {
|
uint8_t getNumChannels() const {
|
||||||
return static_cast<uint8_t>(downstream.size());
|
return numChannels;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t getBitDepth() const {
|
uint8_t getBitDepth() const {
|
||||||
return bDepth;
|
return channelSize16 ? 16 : 24;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t getBytesPerSample() const {
|
uint8_t getBytesPerSample() const {
|
||||||
return bps;
|
return channelSize16 ? 2 : 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Equals operation for testing.
|
bool isTxMsg() const {
|
||||||
bool operator==(const A2BMessage& rhs) const {
|
return txmsg;
|
||||||
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 channelSize16;
|
void setTxMsgBit(bool bit) {
|
||||||
bool monitor;
|
txmsg = bit;
|
||||||
bool txmsg;
|
}
|
||||||
bool errIndicator;
|
|
||||||
bool syncFrame;
|
bool isMonitorMsg() const {
|
||||||
uint16_t rfu2;
|
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:
|
private:
|
||||||
std::vector<ChannelBuffer> downstream;
|
|
||||||
std::vector<ChannelBuffer> upstream;
|
|
||||||
size_t totalSamples = 0;
|
|
||||||
|
|
||||||
uint8_t bDepth;
|
uint8_t numChannels = 0;
|
||||||
uint8_t bps;
|
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/callback/streamoutput/streamoutput.h"
|
||||||
#include "icsneo/communication/message/a2bmessage.h"
|
#include "icsneo/communication/message/a2bmessage.h"
|
||||||
#include <memory>
|
#include "icsneo/device/device.h"
|
||||||
#include <functional>
|
|
||||||
|
|
||||||
namespace icsneo {
|
namespace icsneo {
|
||||||
|
|
||||||
class A2BWAVOutput : public StreamOutput {
|
class A2BWAVOutput : public StreamOutput {
|
||||||
public:
|
public:
|
||||||
A2BWAVOutput(const char* filename, uint32_t sampleRate = A2BPCM_SAMPLERATE_44100)
|
A2BWAVOutput(const char* filename, uint32_t sampleRate = 44100)
|
||||||
: StreamOutput(filename), wavSampleRate(sampleRate) {}
|
: 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) {}
|
: StreamOutput(std::move(os)), wavSampleRate(sampleRate) {}
|
||||||
|
|
||||||
void writeHeader(const std::shared_ptr<A2BMessage>& firstMsg) const;
|
void writeHeader(const std::shared_ptr<A2BMessage>& firstMsg) const;
|
||||||
|
|
@ -32,12 +31,11 @@ public:
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
bool writeSamples(const std::shared_ptr<A2BMessage>& msg, A2BMessage::A2BDirection dir) const;
|
|
||||||
|
|
||||||
uint32_t wavSampleRate;
|
uint32_t wavSampleRate;
|
||||||
mutable uint32_t streamStartPos;
|
mutable uint32_t streamStartPos;
|
||||||
mutable bool firstMessageFlag = true;
|
mutable bool firstMessageFlag = true;
|
||||||
mutable bool closed = false;
|
mutable bool closed = false;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include "icsneo/device/idevicesettings.h"
|
#include "icsneo/device/idevicesettings.h"
|
||||||
|
#include "icsneo/communication/message/a2bmessage.h"
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
|
|
||||||
|
|
@ -11,6 +12,9 @@ namespace icsneo {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#pragma pack(push, 2)
|
#pragma pack(push, 2)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
uint8_t tdmMode;
|
uint8_t tdmMode;
|
||||||
|
|
@ -61,6 +65,34 @@ typedef struct {
|
||||||
|
|
||||||
class RADA2BSettings : public IDeviceSettings {
|
class RADA2BSettings : public IDeviceSettings {
|
||||||
public:
|
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)) {}
|
RADA2BSettings(std::shared_ptr<Communication> com) : IDeviceSettings(com, sizeof(rada2b_settings_t)) {}
|
||||||
const CAN_SETTINGS* getCANSettingsFor(Network net) const override {
|
const CAN_SETTINGS* getCANSettingsFor(Network net) const override {
|
||||||
auto cfg = getStructurePointer<rada2b_settings_t>();
|
auto cfg = getStructurePointer<rada2b_settings_t>();
|
||||||
|
|
@ -88,6 +120,108 @@ public:
|
||||||
return nullptr;
|
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/icsneocpp.h"
|
||||||
#include "icsneo/communication/encoder.h"
|
#include "icsneo/communication/encoder.h"
|
||||||
#include "icsneo/communication/packet/a2bpacket.h"
|
#include "icsneo/communication/packet/a2bpacket.h"
|
||||||
|
#include "icsneo/communication/message/a2bmessage.h"
|
||||||
#include "icsneo/communication/packetizer.h"
|
#include "icsneo/communication/packetizer.h"
|
||||||
#include "icsneo/api/eventmanager.h"
|
#include "icsneo/api/eventmanager.h"
|
||||||
#include "gtest/gtest.h"
|
#include "gtest/gtest.h"
|
||||||
|
|
@ -11,193 +12,14 @@ using namespace icsneo;
|
||||||
class A2BEncoderDecoderTest : public ::testing::Test {
|
class A2BEncoderDecoderTest : public ::testing::Test {
|
||||||
protected:
|
protected:
|
||||||
void SetUp() override {
|
void SetUp() override {
|
||||||
|
|
||||||
report = [](APIEvent::Type, APIEvent::Severity) {
|
report = [](APIEvent::Type, APIEvent::Severity) {
|
||||||
// Unless caught by the test, the packetizer should not throw errors
|
// Unless caught by the test, the packetizer should not throw errors
|
||||||
EXPECT_TRUE(false);
|
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;
|
packetizer.emplace([this](APIEvent::Type t, APIEvent::Severity s) { report(t, s); });
|
||||||
constructTest(
|
packetEncoder.emplace([this](APIEvent::Type t, APIEvent::Severity s) { report(t, s); });
|
||||||
msg1,
|
packetDecoder.emplace([this](APIEvent::Type t, APIEvent::Severity s) { report(t, s); });
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
device_eventhandler_t report;
|
device_eventhandler_t report;
|
||||||
|
|
@ -205,31 +27,59 @@ protected:
|
||||||
std::optional<Packetizer> packetizer;
|
std::optional<Packetizer> packetizer;
|
||||||
std::optional<Decoder> packetDecoder;
|
std::optional<Decoder> packetDecoder;
|
||||||
|
|
||||||
std::shared_ptr<A2BMessage> msg1;
|
std::vector<uint8_t> testBytes =
|
||||||
std::vector<uint8_t> msg1Encoded;
|
{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> recvBytes =
|
||||||
std::vector<uint8_t> msg2Encoded;
|
{0xaa, 0x00, 0x2a, 0x00, 0x0a, 0x02, 0x02, 0x01,
|
||||||
|
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
std::shared_ptr<A2BMessage> msg3;
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
std::vector<uint8_t> msg3Encoded;
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x03, 0x02, 0x00, 0x00, 0x08, 0x04,
|
||||||
std::shared_ptr<A2BMessage> msg4;
|
0x00, 0x00};
|
||||||
std::vector<uint8_t> msg4Encoded;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
TEST_F(A2BEncoderDecoderTest, ChannelSize16SingleChannel) {
|
TEST_F(A2BEncoderDecoderTest, PacketEncoderTest)
|
||||||
testMessage(msg1, msg1Encoded);
|
{
|
||||||
|
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) {
|
TEST_F(A2BEncoderDecoderTest, PacketDecoderTest)
|
||||||
testMessage(msg2, msg2Encoded);
|
{
|
||||||
}
|
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) {
|
message->network = icsneo::Network::NetID::A2B1;
|
||||||
testMessage(msg3, msg3Encoded);
|
message->setTxMsgBit(false);
|
||||||
}
|
message->setMonitorBit(true);
|
||||||
|
|
||||||
TEST_F(A2BEncoderDecoderTest, NoFrames) {
|
EXPECT_TRUE(message->setSample(0, 0, (0x02 << 8) | (0x03)));
|
||||||
testMessage(msg4, msg4Encoded);
|
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