A2B: Add initial WAV streaming support
parent
7f22286838
commit
7b2544864b
|
|
@ -171,6 +171,7 @@ endforeach()
|
|||
|
||||
set(SRC_FILES
|
||||
communication/message/flexray/control/flexraycontrolmessage.cpp
|
||||
communication/message/callback/streamoutput/a2bwavoutput.cpp
|
||||
communication/message/neomessage.cpp
|
||||
communication/message/ethphymessage.cpp
|
||||
communication/packet/flexraypacket.cpp
|
||||
|
|
|
|||
|
|
@ -251,7 +251,7 @@ int icsneo_addMessageCallback(const neodevice_t* device, void (*callback)(neomes
|
|||
return -1;
|
||||
|
||||
return device->device->addMessageCallback(
|
||||
MessageCallback(
|
||||
std::make_shared<MessageCallback>(
|
||||
[=](std::shared_ptr<icsneo::Message> msg) {
|
||||
return callback(CreateNeoMessage(msg));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -193,7 +193,7 @@ std::shared_ptr<LogicalDiskInfoMessage> Communication::getLogicalDiskInfoSync(st
|
|||
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);
|
||||
messageCallbacks.insert(std::make_pair(messageCallbackIDCounter, cb));
|
||||
return messageCallbackIDCounter++;
|
||||
|
|
@ -217,7 +217,7 @@ std::shared_ptr<Message> Communication::waitForMessageSync(std::function<bool(vo
|
|||
std::shared_ptr<Message> returnedMessage;
|
||||
|
||||
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);
|
||||
returnedMessage = message;
|
||||
|
|
@ -251,7 +251,7 @@ void Communication::dispatchMessage(const std::shared_ptr<Message>& msg) {
|
|||
EventManager::GetInstance().cancelErrorDowngradingOnCurrentThread();
|
||||
for(auto& cb : messageCallbacks) {
|
||||
if(!closing) { // We might have closed while reading or processing
|
||||
cb.second.callIfMatch(msg);
|
||||
cb.second->callIfMatch(msg);
|
||||
}
|
||||
}
|
||||
if(downgrade)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -4,8 +4,7 @@
|
|||
|
||||
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;
|
||||
|
||||
|
|
@ -17,9 +16,8 @@ std::shared_ptr<Message> HardwareA2BPacket::DecodeToMessage(const std::vector<ui
|
|||
auto getSampleFromBytes = [](uint8_t bytesPerSample, const uint8_t *bytes) {
|
||||
A2BPCMSample result = 0;
|
||||
|
||||
for(auto i = 0; i < bytesPerSample; i++)
|
||||
{
|
||||
result |= bytes[i] << (i * 8);
|
||||
for(auto i = 0; i < bytesPerSample; i++) {
|
||||
result |= static_cast<uint32_t>(bytes[i]) << (i * 8);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
|
@ -42,8 +40,7 @@ std::shared_ptr<Message> HardwareA2BPacket::DecodeToMessage(const std::vector<ui
|
|||
|
||||
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(
|
||||
getSampleFromBytes(bytesPerChannel, bytes),
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ bool Device::enableMessagePolling() {
|
|||
report(APIEvent::Type::DeviceCurrentlyPolling, APIEvent::Severity::Error);
|
||||
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);
|
||||
enforcePollingMessageLimit();
|
||||
}));
|
||||
|
|
@ -222,7 +222,7 @@ bool Device::open(OpenFlags flags, OpenStatusHandler handler) {
|
|||
|
||||
MessageFilter filter;
|
||||
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);
|
||||
}));
|
||||
|
||||
|
|
@ -232,7 +232,7 @@ bool Device::open(OpenFlags flags, OpenStatusHandler handler) {
|
|||
MessageFilter filter;
|
||||
filter.includeInternalInAny = true;
|
||||
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;
|
||||
}));
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ std::optional<uint64_t> PlasionDiskReadDriver::readLogicalDiskAligned(Communicat
|
|||
uint32_t copied = 0;
|
||||
bool error = false;
|
||||
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);
|
||||
|
||||
const auto sdmsg = std::dynamic_pointer_cast<NeoReadMemorySDMessage>(msg);
|
||||
|
|
@ -45,6 +45,7 @@ std::optional<uint64_t> PlasionDiskReadDriver::readLogicalDiskAligned(Communicat
|
|||
if(copied == amount) {
|
||||
lk.unlock();
|
||||
cv.notify_all();
|
||||
|
||||
}
|
||||
}, NeoMemorySDRead));
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
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.
|
||||
|
||||
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``
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
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_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
|
||||
# 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)
|
||||
endif()
|
||||
|
||||
if(LIBICSNEO_BUILD_CPP_A2B_EXAMPLE)
|
||||
add_subdirectory(cpp/a2b)
|
||||
endif()
|
||||
|
||||
# if(LIBICSNEO_BUILD_CSHARP_INTERACTIVE_EXAMPLE)
|
||||
# add_subdirectory(csharp)
|
||||
# endif()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -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™
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -614,7 +614,7 @@ int main() {
|
|||
switch(selection) {
|
||||
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);
|
||||
}));
|
||||
|
||||
|
|
|
|||
|
|
@ -141,7 +141,7 @@ int main() {
|
|||
// We can also register a handler
|
||||
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
|
||||
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) {
|
||||
case icsneo::Message::Type::Frame: {
|
||||
// A message of type Frame is guaranteed to be a Frame, so we can static cast safely
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ public:
|
|||
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));
|
||||
|
||||
int addMessageCallback(const MessageCallback& cb);
|
||||
int addMessageCallback(const std::shared_ptr<MessageCallback>& cb);
|
||||
bool removeMessageCallback(int id);
|
||||
std::shared_ptr<Message> waitForMessageSync(
|
||||
const std::shared_ptr<MessageFilter>& f = {},
|
||||
|
|
@ -83,7 +83,7 @@ public:
|
|||
protected:
|
||||
static int messageCallbackIDCounter;
|
||||
std::mutex messageCallbacksLock;
|
||||
std::map<int, MessageCallback> messageCallbacks;
|
||||
std::map<int, std::shared_ptr<MessageCallback>> messageCallbacks;
|
||||
std::atomic<bool> closing{false};
|
||||
std::atomic<bool> redirectingRead{false};
|
||||
std::function<void(std::vector<uint8_t>&&)> redirectionFn;
|
||||
|
|
|
|||
|
|
@ -5,26 +5,26 @@
|
|||
|
||||
#include "icsneo/communication/message/message.h"
|
||||
|
||||
|
||||
#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 std::vector<A2BPCMSample> ChannelBuffer;
|
||||
|
||||
class A2BMessage : public Frame
|
||||
{
|
||||
class A2BMessage : public Frame {
|
||||
public:
|
||||
enum class A2BDirection : uint8_t
|
||||
{
|
||||
enum class A2BDirection : uint8_t {
|
||||
DownStream = 0,
|
||||
UpStream = 1
|
||||
};
|
||||
|
|
@ -40,86 +40,78 @@ public:
|
|||
upstream.resize(numChannels);
|
||||
}
|
||||
|
||||
void addSample(A2BPCMSample &&sample, A2BDirection dir, uint8_t channel)
|
||||
{
|
||||
if(dir == A2BDirection::DownStream)
|
||||
{
|
||||
void addSample(A2BPCMSample &&sample, A2BDirection dir, uint8_t channel) {
|
||||
if(dir == A2BDirection::DownStream) {
|
||||
downstream[channel].push_back(std::move(sample));
|
||||
}
|
||||
else
|
||||
{
|
||||
else {
|
||||
upstream[channel].push_back(std::move(sample));
|
||||
}
|
||||
totalSamples++;
|
||||
}
|
||||
|
||||
const A2BPCMSample *getSamples(A2BDirection dir, uint8_t channel) const
|
||||
{
|
||||
if(channel >= getNumChannels())
|
||||
{
|
||||
const A2BPCMSample* getSamples(A2BDirection dir, uint8_t channel) const {
|
||||
if(channel >= getNumChannels()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if(dir == A2BDirection::DownStream)
|
||||
{
|
||||
if(dir == A2BDirection::DownStream) {
|
||||
return downstream[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);
|
||||
auto numSamplesInChannel = getNumSamplesInChannel(dir, channel);
|
||||
|
||||
if(
|
||||
samples == nullptr ||
|
||||
sampleIndex >= numSamplesInChannel.value_or(0)
|
||||
)
|
||||
{
|
||||
) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return samples[sampleIndex];
|
||||
}
|
||||
|
||||
std::optional<std::size_t> getNumSamplesInChannel(A2BDirection dir, uint8_t channel) const
|
||||
{
|
||||
if(channel >= getNumChannels())
|
||||
{
|
||||
std::optional<size_t> getNumSamplesInChannel(A2BDirection dir, uint8_t channel) const {
|
||||
if(channel >= getNumChannels()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if(dir == A2BDirection::DownStream)
|
||||
{
|
||||
if(dir == A2BDirection::DownStream) {
|
||||
return downstream[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;
|
||||
}
|
||||
|
||||
uint8_t getNumChannels() const
|
||||
{
|
||||
uint8_t getNumChannels() const {
|
||||
return static_cast<uint8_t>(downstream.size());
|
||||
}
|
||||
|
||||
uint8_t getBitDepth() const
|
||||
{
|
||||
uint8_t getBitDepth() const {
|
||||
return mBitDepth;
|
||||
}
|
||||
|
||||
uint8_t getBytesPerSample() const
|
||||
{
|
||||
uint8_t getBytesPerSample() const {
|
||||
return mBytesPerSample;
|
||||
}
|
||||
|
||||
bool isMonitor() const
|
||||
{
|
||||
bool isMonitor() const {
|
||||
return mMonitor;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -19,8 +19,7 @@ struct HardwareA2BPacket {
|
|||
|
||||
static std::shared_ptr<Message> DecodeToMessage(const std::vector<uint8_t>& bytestream);
|
||||
|
||||
struct
|
||||
{
|
||||
struct {
|
||||
// CxA2B
|
||||
icscm_bitfield channelNum : 8;
|
||||
icscm_bitfield channelSize16 : 1;
|
||||
|
|
|
|||
|
|
@ -153,7 +153,7 @@ public:
|
|||
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 transmit(std::shared_ptr<Frame> frame);
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@
|
|||
#include "icsneo/communication/message/i2cmessage.h"
|
||||
#include "icsneo/communication/message/a2bmessage.h"
|
||||
|
||||
#include "icsneo/communication/message/callback/streamoutput/a2bwavoutput.h"
|
||||
|
||||
namespace icsneo {
|
||||
|
||||
std::vector<std::shared_ptr<Device>> FindAllDevices();
|
||||
|
|
|
|||
Loading…
Reference in New Issue