A2B: Add initial WAV streaming support

add-device-sharing
Yasser Yassine 2022-10-19 18:44:05 -04:00
parent 7f22286838
commit 7b2544864b
21 changed files with 461 additions and 61 deletions

View File

@ -171,6 +171,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/neomessage.cpp communication/message/neomessage.cpp
communication/message/ethphymessage.cpp communication/message/ethphymessage.cpp
communication/packet/flexraypacket.cpp communication/packet/flexraypacket.cpp

View File

@ -251,7 +251,7 @@ int icsneo_addMessageCallback(const neodevice_t* device, void (*callback)(neomes
return -1; return -1;
return device->device->addMessageCallback( return device->device->addMessageCallback(
MessageCallback( std::make_shared<MessageCallback>(
[=](std::shared_ptr<icsneo::Message> msg) { [=](std::shared_ptr<icsneo::Message> msg) {
return callback(CreateNeoMessage(msg)); return callback(CreateNeoMessage(msg));
} }

View File

@ -193,7 +193,7 @@ std::shared_ptr<LogicalDiskInfoMessage> Communication::getLogicalDiskInfoSync(st
return std::dynamic_pointer_cast<LogicalDiskInfoMessage>(msg); return std::dynamic_pointer_cast<LogicalDiskInfoMessage>(msg);
} }
int Communication::addMessageCallback(const MessageCallback& cb) { int Communication::addMessageCallback(const std::shared_ptr<MessageCallback>& cb) {
std::lock_guard<std::mutex> lk(messageCallbacksLock); std::lock_guard<std::mutex> lk(messageCallbacksLock);
messageCallbacks.insert(std::make_pair(messageCallbackIDCounter, cb)); messageCallbacks.insert(std::make_pair(messageCallbackIDCounter, cb));
return messageCallbackIDCounter++; return messageCallbackIDCounter++;
@ -217,7 +217,7 @@ std::shared_ptr<Message> Communication::waitForMessageSync(std::function<bool(vo
std::shared_ptr<Message> returnedMessage; std::shared_ptr<Message> returnedMessage;
std::unique_lock<std::mutex> lk(m); // Don't let the callback fire until we're waiting for it std::unique_lock<std::mutex> lk(m); // Don't let the callback fire until we're waiting for it
int cb = addMessageCallback(MessageCallback([&m, &returnedMessage, &cv](std::shared_ptr<Message> message) { int cb = addMessageCallback(std::make_shared<MessageCallback>([&m, &returnedMessage, &cv](std::shared_ptr<Message> message) {
{ {
std::lock_guard<std::mutex> lk(m); std::lock_guard<std::mutex> lk(m);
returnedMessage = message; returnedMessage = message;
@ -251,7 +251,7 @@ void Communication::dispatchMessage(const std::shared_ptr<Message>& msg) {
EventManager::GetInstance().cancelErrorDowngradingOnCurrentThread(); EventManager::GetInstance().cancelErrorDowngradingOnCurrentThread();
for(auto& cb : messageCallbacks) { for(auto& cb : messageCallbacks) {
if(!closing) { // We might have closed while reading or processing if(!closing) { // We might have closed while reading or processing
cb.second.callIfMatch(msg); cb.second->callIfMatch(msg);
} }
} }
if(downgrade) if(downgrade)

View File

@ -0,0 +1,100 @@
#include "icsneo/communication/message/callback/streamoutput/a2bwavoutput.h"
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)
{
return false;
}
if(message->type != Message::Type::Frame)
return false;
const auto& frame = std::static_pointer_cast<Frame>(message);
if(frame->network.getType() != Network::Type::A2B)
return false;
const auto& a2bmsg = std::static_pointer_cast<A2BMessage>(frame);
if(firstMessageFlag) {
writeHeader(a2bmsg);
firstMessageFlag = false;
}
if(!writeSamples(a2bmsg, A2BMessage::A2BDirection::DownStream)) {
close();
return false;
}
if(!writeSamples(a2bmsg, A2BMessage::A2BDirection::UpStream)) {
close();
return false;
}
return true;
}
void A2BWAVOutput::close() const
{
if(closed) {
return;
}
uint32_t streamEndPos = static_cast<uint32_t>(stream->tellp());
uint32_t subChunk2Size = streamEndPos - streamStartPos;
uint32_t chunkSize = streamEndPos - 8;
stream->seekp(streamStartPos - 4);
write((void*)&subChunk2Size, 4);
stream->seekp(4, std::ios::beg);
write((void*)&chunkSize, 4);
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 sampleIndex = 0;
uint8_t bitDepth = msg->getBitDepth();
while(true) {
auto sample = msg->getSample(dir, channel, sampleIndex);
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) {
sampleIndex++;
}
}
return true;
}
}

View File

@ -4,8 +4,7 @@
using namespace icsneo; using namespace icsneo;
std::shared_ptr<Message> HardwareA2BPacket::DecodeToMessage(const std::vector<uint8_t> &bytestream) std::shared_ptr<Message> HardwareA2BPacket::DecodeToMessage(const std::vector<uint8_t> &bytestream) {
{
constexpr uint8_t coreMiniMessageHeaderSize = 28; constexpr uint8_t coreMiniMessageHeaderSize = 28;
@ -17,9 +16,8 @@ std::shared_ptr<Message> HardwareA2BPacket::DecodeToMessage(const std::vector<ui
auto getSampleFromBytes = [](uint8_t bytesPerSample, const uint8_t *bytes) { auto getSampleFromBytes = [](uint8_t bytesPerSample, const uint8_t *bytes) {
A2BPCMSample result = 0; A2BPCMSample result = 0;
for(auto i = 0; i < bytesPerSample; i++) for(auto i = 0; i < bytesPerSample; i++) {
{ result |= static_cast<uint32_t>(bytes[i]) << (i * 8);
result |= bytes[i] << (i * 8);
} }
return result; return result;
@ -42,8 +40,7 @@ std::shared_ptr<Message> HardwareA2BPacket::DecodeToMessage(const std::vector<ui
uint8_t channel = 0; 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) for(uint32_t i = 0; i < totalPackedLength; i += 2 * static_cast<uint32_t>(bytesPerChannel), bytes += 2 * bytesPerChannel, channel = (channel + 1) % numChannels) {
{
msg->addSample( msg->addSample(
getSampleFromBytes(bytesPerChannel, bytes), getSampleFromBytes(bytesPerChannel, bytes),

View File

@ -95,7 +95,7 @@ bool Device::enableMessagePolling() {
report(APIEvent::Type::DeviceCurrentlyPolling, APIEvent::Severity::Error); report(APIEvent::Type::DeviceCurrentlyPolling, APIEvent::Severity::Error);
return false; return false;
} }
messagePollingCallbackID = com->addMessageCallback(MessageCallback([this](std::shared_ptr<Message> message) { messagePollingCallbackID = com->addMessageCallback(std::make_shared<MessageCallback>([this](std::shared_ptr<Message> message) {
pollingContainer.enqueue(message); pollingContainer.enqueue(message);
enforcePollingMessageLimit(); enforcePollingMessageLimit();
})); }));
@ -222,7 +222,7 @@ bool Device::open(OpenFlags flags, OpenStatusHandler handler) {
MessageFilter filter; MessageFilter filter;
filter.includeInternalInAny = true; filter.includeInternalInAny = true;
internalHandlerCallbackID = com->addMessageCallback(MessageCallback(filter, [this](std::shared_ptr<Message> message) { internalHandlerCallbackID = com->addMessageCallback(std::make_shared<MessageCallback>(filter, [this](std::shared_ptr<Message> message) {
handleInternalMessage(message); handleInternalMessage(message);
})); }));
@ -232,7 +232,7 @@ bool Device::open(OpenFlags flags, OpenStatusHandler handler) {
MessageFilter filter; MessageFilter filter;
filter.includeInternalInAny = true; filter.includeInternalInAny = true;
std::atomic<bool> receivedMessage{false}; std::atomic<bool> receivedMessage{false};
auto messageReceivedCallbackID = com->addMessageCallback(MessageCallback(filter, [&receivedMessage](std::shared_ptr<Message> message) { auto messageReceivedCallbackID = com->addMessageCallback(std::make_shared<MessageCallback>(filter, [&receivedMessage](std::shared_ptr<Message> message) {
receivedMessage = true; receivedMessage = true;
})); }));

View File

@ -29,7 +29,7 @@ std::optional<uint64_t> PlasionDiskReadDriver::readLogicalDiskAligned(Communicat
uint32_t copied = 0; uint32_t copied = 0;
bool error = false; bool error = false;
std::unique_lock<std::mutex> lk(m); std::unique_lock<std::mutex> lk(m);
auto cb = com.addMessageCallback(MessageCallback([&](std::shared_ptr<Message> msg) { auto cb = com.addMessageCallback(std::make_shared<MessageCallback>([&](std::shared_ptr<Message> msg) {
std::unique_lock<std::mutex> lk(m); std::unique_lock<std::mutex> lk(m);
const auto sdmsg = std::dynamic_pointer_cast<NeoReadMemorySDMessage>(msg); const auto sdmsg = std::dynamic_pointer_cast<NeoReadMemorySDMessage>(msg);
@ -45,6 +45,7 @@ std::optional<uint64_t> PlasionDiskReadDriver::readLogicalDiskAligned(Communicat
if(copied == amount) { if(copied == amount) {
lk.unlock(); lk.unlock();
cv.notify_all(); cv.notify_all();
} }
}, NeoMemorySDRead)); }, NeoMemorySDRead));

View File

@ -100,3 +100,19 @@ Write Blocking Status
The write blocking status of the device determines the behavior of attempting to transmit to the device (likely via sending messages) with a large backlog of messages. The write blocking status of the device determines the behavior of attempting to transmit to the device (likely via sending messages) with a large backlog of messages.
If write blocking is enabled, then the transmitting thread will wait for the entire buffer to be transmitted. If write blocking is enabled, then the transmitting thread will wait for the entire buffer to be transmitted.
If write blocking is disabled, then the attempt to transmit will simply fail and an error will be logged on the calling thread. If write blocking is disabled, then the attempt to transmit will simply fail and an error will be logged on the calling thread.
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.
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
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``

View File

@ -1,6 +1,8 @@
option(LIBICSNEO_BUILD_C_INTERACTIVE_EXAMPLE "Build the command-line interactive C example." ON) option(LIBICSNEO_BUILD_C_INTERACTIVE_EXAMPLE "Build the command-line interactive C example." ON)
option(LIBICSNEO_BUILD_CPP_SIMPLE_EXAMPLE "Build the simple C++ example." ON) option(LIBICSNEO_BUILD_CPP_SIMPLE_EXAMPLE "Build the simple C++ example." ON)
option(LIBICSNEO_BUILD_CPP_INTERACTIVE_EXAMPLE "Build the command-line interactive C++ example." ON) option(LIBICSNEO_BUILD_CPP_INTERACTIVE_EXAMPLE "Build the command-line interactive C++ example." ON)
option(LIBICSNEO_BUILD_CPP_A2B_EXAMPLE "Build the A2B example." ON)
# Disabled until we properly build these in-tree # Disabled until we properly build these in-tree
# option(LIBICSNEO_BUILD_CSHARP_INTERACTIVE_EXAMPLE "Build the command-line interactive C# example." OFF) # option(LIBICSNEO_BUILD_CSHARP_INTERACTIVE_EXAMPLE "Build the command-line interactive C# example." OFF)
@ -18,6 +20,10 @@ if(LIBICSNEO_BUILD_CPP_INTERACTIVE_EXAMPLE)
add_subdirectory(cpp/interactive) add_subdirectory(cpp/interactive)
endif() endif()
if(LIBICSNEO_BUILD_CPP_A2B_EXAMPLE)
add_subdirectory(cpp/a2b)
endif()
# if(LIBICSNEO_BUILD_CSHARP_INTERACTIVE_EXAMPLE) # if(LIBICSNEO_BUILD_CSHARP_INTERACTIVE_EXAMPLE)
# add_subdirectory(csharp) # add_subdirectory(csharp)
# endif() # endif()

View File

@ -0,0 +1,27 @@
cmake_minimum_required(VERSION 3.2)
project(libicsneocpp-a2b VERSION 0.2.0)
set(CMAKE_CXX_STANDARD_REQUIRED 11)
include(GNUInstallDirs)
# Add an include directory like so if desired
#include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include)
# Enable Warnings
if(MSVC)
# Force to always compile with W4
if(CMAKE_CXX_FLAGS MATCHES "/W[0-4]")
string(REGEX REPLACE "/W[0-4]" "/W4" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
else()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4")
endif()
else() #if(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-switch -Wno-unknown-pragmas")
endif()
# Add libicsneo, usually a git submodule within your project works well
#add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../third-party/libicsneo ${CMAKE_CURRENT_BINARY_DIR}/third-party/libicsneo)
add_executable(libicsneocpp-a2b src/a2b.cpp)
target_link_libraries(libicsneocpp-a2b icsneocpp)

View File

@ -0,0 +1,48 @@
# libicsneo C++ Example
This is an example console application which uses libicsneo to connect to an Intrepid Control Systems hardware device. It has both interactive and simple examples for sending and receiving CAN & CAN FD traffic.
## Building
This example shows how to use the C++ version of libicsneo with CMake. It will build libicsneo along with your project.
First, you need to clone the repository onto your local machine. Run:
```shell
git clone https://github.com/intrepidcs/libicsneo-examples --recursive
```
Alternatively, if you cloned without the `--recursive flag`, you must enter the `libicsneo-examples` folder and run the following:
```shell
git submodule update --recursive --init
```
If you haven't done this, `third-party/libicsneo` will be empty and you won't be able to build!
### Windows using Visual Studio 2017+
1. Launch Visual Studio and open the `libicsneo-examples` folder.
2. Choose `File->Open->CMake...`
3. Navigate to the `libicsneocpp-example` folder and select the `CMakeLists.txt` there.
4. Visual Studio will process the CMake project.
5. Choose the dropdown attached to the green play button (labelled "select startup item...") in the toolbar.
6. Select `libicsneocpp-simple-example.exe`
7. Press the green play button to compile and run the example.
### Ubuntu 18.04 LTS
1. Install dependencies with `sudo apt update` then `sudo apt install build-essential cmake libusb-1.0-0-dev libpcap0.8-dev`
2. Change directories to your `libicsneo-examples/libicsneocpp-example` folder and create a build directory by running `mkdir -p build`
3. Enter the build directory with `cd build`
4. Run `cmake ..` to generate your Makefile.
* Hint! Running `cmake -DCMAKE_BUILD_TYPE=Debug ..` will generate the proper scripts to build debug, and `cmake -DCMAKE_BUILD_TYPE=Release ..` will generate the proper scripts to build with all optimizations on.
5. Run `make libicsneocpp-interactive-example` to build.
* Hint! Speed up your build by using multiple processors! Use `make libicsneocpp-interactive-example -j#` where `#` is the number of cores/threads your system has plus one. For instance, on a standard 8 thread Intel i7, you might use `-j9` for an ~8x speedup.
6. Now run `sudo ./libicsneocpp-interactive-example` to run the example.
* Hint! In order to run without sudo, you will need to set up the udev rules. Copy `libicsneo-examples/third-party/libicsneo/99-intrepidcs.rules` to `/etc/udev/rules.d`, then run `udevadm control --reload-rules && udevadm trigger` afterwards. While the program will still run without setting up these rules, it will fail to open any devices.
7. If you wish to run the simple example instead, replace any instances of "interactive" with "simple" in steps 5 and 6.
### macOS
Instructions coming soon&trade;

View File

@ -0,0 +1,52 @@
#include <iostream>
#include <iomanip>
#include <thread>
#include <chrono>
#include "icsneo/icsneocpp.h"
const std::string rada2bSerial = "Your RADA2B serial number.";
int main() {
std::cout << "Start example\n";
auto devices = icsneo::FindAllDevices();
std::shared_ptr<icsneo::Device> rada2b;
for(auto& device : devices) {
if(device->getSerial() == rada2bSerial) {
rada2b = device;
}
}
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
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;
}
}
}));
std::this_thread::sleep_for(std::chrono::seconds(5)); // captures 5 seconds of A2B data.
rada2b->removeMessageCallback(handler1);
rada2b->removeMessageCallback(handler2);
std::cout << "End A2B example\n";
rada2b->goOffline();
rada2b->close();
return 0;
}

View File

@ -614,7 +614,7 @@ int main() {
switch(selection) { switch(selection) {
case '1': case '1':
{ {
int callbackID = selectedDevice->addMessageCallback(icsneo::MessageCallback([](std::shared_ptr<icsneo::Message> msg){ int callbackID = selectedDevice->addMessageCallback(std::make_shared<icsneo::MessageCallback>([](std::shared_ptr<icsneo::Message> msg){
printMessage(msg); printMessage(msg);
})); }));

View File

@ -141,7 +141,7 @@ int main() {
// We can also register a handler // We can also register a handler
std::cout << "\tStreaming messages in for 3 seconds... " << std::endl; std::cout << "\tStreaming messages in for 3 seconds... " << std::endl;
// MessageCallbacks are powerful, and can filter on things like ArbID for you. See the documentation // MessageCallbacks are powerful, and can filter on things like ArbID for you. See the documentation
auto handler = device->addMessageCallback(icsneo::MessageCallback([](std::shared_ptr<icsneo::Message> message) { auto handler = device->addMessageCallback(std::make_shared<icsneo::MessageCallback>([](std::shared_ptr<icsneo::Message> message) {
switch(message->type) { switch(message->type) {
case icsneo::Message::Type::Frame: { case icsneo::Message::Type::Frame: {
// A message of type Frame is guaranteed to be a Frame, so we can static cast safely // A message of type Frame is guaranteed to be a Frame, so we can static cast safely

View File

@ -59,7 +59,7 @@ public:
std::optional< std::vector< std::optional<DeviceAppVersion> > > getVersionsSync(std::chrono::milliseconds timeout = std::chrono::milliseconds(50)); std::optional< std::vector< std::optional<DeviceAppVersion> > > getVersionsSync(std::chrono::milliseconds timeout = std::chrono::milliseconds(50));
std::shared_ptr<LogicalDiskInfoMessage> getLogicalDiskInfoSync(std::chrono::milliseconds timeout = std::chrono::milliseconds(50)); std::shared_ptr<LogicalDiskInfoMessage> getLogicalDiskInfoSync(std::chrono::milliseconds timeout = std::chrono::milliseconds(50));
int addMessageCallback(const MessageCallback& cb); int addMessageCallback(const std::shared_ptr<MessageCallback>& cb);
bool removeMessageCallback(int id); bool removeMessageCallback(int id);
std::shared_ptr<Message> waitForMessageSync( std::shared_ptr<Message> waitForMessageSync(
const std::shared_ptr<MessageFilter>& f = {}, const std::shared_ptr<MessageFilter>& f = {},
@ -83,7 +83,7 @@ public:
protected: protected:
static int messageCallbackIDCounter; static int messageCallbackIDCounter;
std::mutex messageCallbacksLock; std::mutex messageCallbacksLock;
std::map<int, MessageCallback> messageCallbacks; std::map<int, std::shared_ptr<MessageCallback>> messageCallbacks;
std::atomic<bool> closing{false}; std::atomic<bool> closing{false};
std::atomic<bool> redirectingRead{false}; std::atomic<bool> redirectingRead{false};
std::function<void(std::vector<uint8_t>&&)> redirectionFn; std::function<void(std::vector<uint8_t>&&)> redirectionFn;

View File

@ -5,26 +5,26 @@
#include "icsneo/communication/message/message.h" #include "icsneo/communication/message/message.h"
#define A2BMESSAGE_UPSTREAM 1 #define A2BMESSAGE_UPSTREAM 1
#define A2BMESSAGE_DOWNSTREAM 0 #define A2BMESSAGE_DOWNSTREAM 0
#define A2BPCM_SAMPLE_SIZE 4
#define A2BPCM_L16 16 #define A2BPCM_L16 16
#define A2BPCM_L24 24 #define A2BPCM_L24 24
#define A2BPCM_SAMPLERATE_44100 44100 #define A2BPCM_SAMPLERATE_44100 44100
#define A2BPCM_SAMPLERATE_48000 48000 #define A2BPCM_SAMPLERATE_48000 48000
namespace icsneo namespace icsneo {
{
typedef uint32_t A2BPCMSample; typedef uint32_t A2BPCMSample;
typedef std::vector<A2BPCMSample> ChannelBuffer; typedef std::vector<A2BPCMSample> ChannelBuffer;
class A2BMessage : public Frame class A2BMessage : public Frame {
{
public: public:
enum class A2BDirection : uint8_t enum class A2BDirection : uint8_t {
{
DownStream = 0, DownStream = 0,
UpStream = 1 UpStream = 1
}; };
@ -40,86 +40,78 @@ public:
upstream.resize(numChannels); upstream.resize(numChannels);
} }
void addSample(A2BPCMSample &&sample, A2BDirection dir, uint8_t channel) void addSample(A2BPCMSample &&sample, A2BDirection dir, uint8_t channel) {
{ if(dir == A2BDirection::DownStream) {
if(dir == A2BDirection::DownStream)
{
downstream[channel].push_back(std::move(sample)); downstream[channel].push_back(std::move(sample));
} }
else else {
{
upstream[channel].push_back(std::move(sample)); upstream[channel].push_back(std::move(sample));
} }
totalSamples++; totalSamples++;
} }
const A2BPCMSample *getSamples(A2BDirection dir, uint8_t channel) const const A2BPCMSample* getSamples(A2BDirection dir, uint8_t channel) const {
{ if(channel >= getNumChannels()) {
if(channel >= getNumChannels())
{
return nullptr; return nullptr;
} }
if(dir == A2BDirection::DownStream) if(dir == A2BDirection::DownStream) {
{
return downstream[channel].data(); return downstream[channel].data();
} }
return upstream[channel].data(); return upstream[channel].data();
} }
std::optional<A2BPCMSample> getSample(A2BDirection dir, uint8_t channel, uint32_t sampleIndex) const std::optional<A2BPCMSample> getSample(A2BDirection dir, uint8_t channel, uint32_t sampleIndex) const {
{ const A2BPCMSample* samples = getSamples(dir, channel);
const A2BPCMSample *samples = getSamples(dir, channel);
auto numSamplesInChannel = getNumSamplesInChannel(dir, channel); auto numSamplesInChannel = getNumSamplesInChannel(dir, channel);
if( if(
samples == nullptr || samples == nullptr ||
sampleIndex >= numSamplesInChannel.value_or(0) sampleIndex >= numSamplesInChannel.value_or(0)
) ) {
{
return std::nullopt; return std::nullopt;
} }
return samples[sampleIndex]; return samples[sampleIndex];
} }
std::optional<std::size_t> getNumSamplesInChannel(A2BDirection dir, uint8_t channel) const std::optional<size_t> getNumSamplesInChannel(A2BDirection dir, uint8_t channel) const {
{ if(channel >= getNumChannels()) {
if(channel >= getNumChannels())
{
return std::nullopt; return std::nullopt;
} }
if(dir == A2BDirection::DownStream) if(dir == A2BDirection::DownStream) {
{
return downstream[channel].size(); return downstream[channel].size();
} }
return upstream[channel].size(); return upstream[channel].size();
} }
size_t getNumSamples() const const std::vector<ChannelBuffer>& getDownstream() const {
{ return downstream;
}
const std::vector<ChannelBuffer>& getUpstream() const {
return upstream;
}
size_t getNumSamples() const {
return totalSamples; return totalSamples;
} }
uint8_t getNumChannels() const uint8_t getNumChannels() const {
{
return static_cast<uint8_t>(downstream.size()); return static_cast<uint8_t>(downstream.size());
} }
uint8_t getBitDepth() const uint8_t getBitDepth() const {
{
return mBitDepth; return mBitDepth;
} }
uint8_t getBytesPerSample() const uint8_t getBytesPerSample() const {
{
return mBytesPerSample; return mBytesPerSample;
} }
bool isMonitor() const bool isMonitor() const {
{
return mMonitor; return mMonitor;
} }

View File

@ -0,0 +1,47 @@
#ifndef __A2BWAVOUTPUT_H_
#define __A2BWAVOUTPUT_H_
#ifdef __cplusplus
#include "icsneo/communication/message/callback/streamoutput/streamoutput.h"
#include "icsneo/communication/message/a2bmessage.h"
#include <memory>
#include <functional>
namespace icsneo {
class A2BWAVOutput : public StreamOutput {
public:
A2BWAVOutput(const char* filename, uint32_t sampleRate = A2BPCM_SAMPLERATE_44100)
: wavSampleRate(sampleRate), StreamOutput(filename) {}
A2BWAVOutput(std::unique_ptr<std::ostream>&& os, uint32_t sampleRate = A2BPCM_SAMPLERATE_44100)
: wavSampleRate(sampleRate), StreamOutput(std::move(os)) {}
void writeHeader(const std::shared_ptr<A2BMessage>& firstMsg) const;
bool callIfMatch(const std::shared_ptr<Message>& message) const override;
void close() const;
~A2BWAVOutput() {
if(!closed) {
close();
}
}
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;
};
}
#endif // __cplusplus
#endif

View File

@ -0,0 +1,112 @@
#ifndef __STREAMOUTPUT_H_
#define __STREAMOUTPUT_H_
#ifdef __cplusplus
#include "icsneo/communication/message/callback/messagecallback.h"
#include <memory>
#include <functional>
#include <iostream>
#include <fstream>
#define WAV_SAMPLE_RATE_44100 44100
#define WAV_SAMPLE_RATE_48000 48000
namespace icsneo {
struct WaveFileHeader {
static constexpr uint32_t WAVE_CHUNK_ID = 0x46464952; // "RIFF"
static constexpr uint32_t WAVE_FORMAT = 0x45564157; // "WAVE"
static constexpr uint32_t WAVE_SUBCHUNK1_ID = 0x20746d66; // "fmt "
static constexpr uint32_t WAVE_SUBCHUNK2_ID = 0x61746164; // "data"
static constexpr uint16_t WAVE_SUBCHUNK1_SIZE = 16;
static constexpr uint16_t WAVE_AUDIO_FORMAT_PCM = 1;
static constexpr uint32_t WAVE_DEFAULT_SIZE = 0; // Default size for streamed wav
uint32_t chunkId = WAVE_CHUNK_ID; // "RIFF"
uint32_t chunkSize = WAVE_DEFAULT_SIZE; // number of bytes to follow
uint32_t format = WAVE_FORMAT; // "WAVE"
uint32_t subchunk1Id = WAVE_SUBCHUNK1_ID; // "fmt "
uint32_t subchunk1Size = WAVE_SUBCHUNK1_SIZE; // number of bytes in *this* subchunk (always 16)
uint16_t audioFormat = WAVE_AUDIO_FORMAT_PCM; // 1 for PCM
uint16_t numChannels; // number of channels
uint32_t sampleRate; // sample rate in Hz
uint32_t byteRate; // bytes per second of audio: sampleRate * numChannels * (bitsPerSample / 8)
uint16_t blockAlign; // alignment of each block in bytes: numChannels * (bitsPerSample / 8)
uint16_t bitsPerSample; // number of bits in each sample
uint32_t subchunk2Id = WAVE_SUBCHUNK2_ID; // "data"
uint32_t subchunk2Size = WAVE_DEFAULT_SIZE; // number of bytes to follow
WaveFileHeader() = default;
WaveFileHeader(uint16_t nChannels, uint32_t sRate, uint16_t bps, uint32_t nSamples = 0) {
setHeader(nChannels, sRate, bps, nSamples);
}
void setHeader(uint16_t newNumChannels, uint32_t newSampleRate, uint16_t newBitsPerSample, uint32_t numSamples = 0) {
numChannels = newNumChannels;
sampleRate = newSampleRate;
bitsPerSample = newBitsPerSample;
blockAlign = numChannels * (bitsPerSample / 8);
byteRate = sampleRate * numChannels * (bitsPerSample / 8);
if(numSamples != 0) {
setNumSamples(numSamples);
}
}
void setNumSamples(uint32_t numSamples) {
subchunk2Size = numSamples * numChannels * (bitsPerSample / 8);
chunkSize = subchunk2Size + 36;
}
void write(const std::unique_ptr<std::ostream>& stream) {
stream->write(reinterpret_cast<const char*>(&chunkId), 4);
stream->write(reinterpret_cast<const char*>(&chunkSize), 4);
stream->write(reinterpret_cast<const char*>(&format), 4);
stream->write(reinterpret_cast<const char*>(&subchunk1Id), 4);
stream->write(reinterpret_cast<const char*>(&subchunk1Size), 4);
stream->write(reinterpret_cast<const char*>(&audioFormat), 2);
stream->write(reinterpret_cast<const char*>(&numChannels), 2);
stream->write(reinterpret_cast<const char*>(&sampleRate), 4);
stream->write(reinterpret_cast<const char*>(&byteRate), 4);
stream->write(reinterpret_cast<const char*>(&blockAlign), 2);
stream->write(reinterpret_cast<const char*>(&bitsPerSample), 2);
stream->write(reinterpret_cast<const char*>(&subchunk2Id), 4);
stream->write(reinterpret_cast<const char*>(&subchunk2Size), 4);
}
};
class StreamOutput : public MessageCallback {
public:
StreamOutput(std::unique_ptr<std::ostream>&& os, fn_messageCallback cb, std::shared_ptr<MessageFilter> f)
: stream(std::move(os)), MessageCallback(cb, f) {}
StreamOutput(const char* filename, fn_messageCallback cb, std::shared_ptr<MessageFilter> f)
: MessageCallback(cb, f) {
stream = std::make_unique<std::ofstream>(filename, std::ios::binary);
}
StreamOutput(const char* filename) : MessageCallback([](std::shared_ptr<Message> msg) {}) {
stream = std::make_unique<std::ofstream>(filename, std::ios::binary);
}
StreamOutput(std::unique_ptr<std::ostream>&& os) : stream(std::move(os)), MessageCallback([](std::shared_ptr<Message> msg) {}) {}
virtual ~StreamOutput() {}
protected:
std::unique_ptr<std::ostream> stream;
void write(void* msg, std::streamsize size) const {
stream->write(reinterpret_cast<const char*>(msg), size);
}
};
}
#endif // __cplusplus
#endif

View File

@ -19,8 +19,7 @@ struct HardwareA2BPacket {
static std::shared_ptr<Message> DecodeToMessage(const std::vector<uint8_t>& bytestream); static std::shared_ptr<Message> DecodeToMessage(const std::vector<uint8_t>& bytestream);
struct struct {
{
// CxA2B // CxA2B
icscm_bitfield channelNum : 8; icscm_bitfield channelNum : 8;
icscm_bitfield channelSize16 : 1; icscm_bitfield channelSize16 : 1;

View File

@ -153,7 +153,7 @@ public:
enforcePollingMessageLimit(); enforcePollingMessageLimit();
} }
int addMessageCallback(const MessageCallback& cb) { return com->addMessageCallback(cb); } int addMessageCallback(const std::shared_ptr<MessageCallback>& cb) { return com->addMessageCallback(cb); }
bool removeMessageCallback(int id) { return com->removeMessageCallback(id); } bool removeMessageCallback(int id) { return com->removeMessageCallback(id); }
bool transmit(std::shared_ptr<Frame> frame); bool transmit(std::shared_ptr<Frame> frame);

View File

@ -19,6 +19,8 @@
#include "icsneo/communication/message/i2cmessage.h" #include "icsneo/communication/message/i2cmessage.h"
#include "icsneo/communication/message/a2bmessage.h" #include "icsneo/communication/message/a2bmessage.h"
#include "icsneo/communication/message/callback/streamoutput/a2bwavoutput.h"
namespace icsneo { namespace icsneo {
std::vector<std::shared_ptr<Device>> FindAllDevices(); std::vector<std::shared_ptr<Device>> FindAllDevices();