diff --git a/CMakeLists.txt b/CMakeLists.txt index 0c04a52..f0d0e52 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/api/icsneoc/icsneoc.cpp b/api/icsneoc/icsneoc.cpp index 35eceaf..fb1a727 100644 --- a/api/icsneoc/icsneoc.cpp +++ b/api/icsneoc/icsneoc.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( [=](std::shared_ptr msg) { return callback(CreateNeoMessage(msg)); } diff --git a/communication/communication.cpp b/communication/communication.cpp index 4e67c11..95bf441 100644 --- a/communication/communication.cpp +++ b/communication/communication.cpp @@ -193,7 +193,7 @@ std::shared_ptr Communication::getLogicalDiskInfoSync(st return std::dynamic_pointer_cast(msg); } -int Communication::addMessageCallback(const MessageCallback& cb) { +int Communication::addMessageCallback(const std::shared_ptr& cb) { std::lock_guard lk(messageCallbacksLock); messageCallbacks.insert(std::make_pair(messageCallbackIDCounter, cb)); return messageCallbackIDCounter++; @@ -217,7 +217,7 @@ std::shared_ptr Communication::waitForMessageSync(std::function returnedMessage; std::unique_lock 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) { + int cb = addMessageCallback(std::make_shared([&m, &returnedMessage, &cv](std::shared_ptr message) { { std::lock_guard lk(m); returnedMessage = message; @@ -251,7 +251,7 @@ void Communication::dispatchMessage(const std::shared_ptr& 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) diff --git a/communication/message/callback/streamoutput/a2bwavoutput.cpp b/communication/message/callback/streamoutput/a2bwavoutput.cpp new file mode 100644 index 0000000..5736ab8 --- /dev/null +++ b/communication/message/callback/streamoutput/a2bwavoutput.cpp @@ -0,0 +1,100 @@ +#include "icsneo/communication/message/callback/streamoutput/a2bwavoutput.h" + +namespace icsneo +{ + +void A2BWAVOutput::writeHeader(const std::shared_ptr& firstMsg) const { + + WaveFileHeader header = WaveFileHeader(2 * firstMsg->getNumChannels(), wavSampleRate, firstMsg->getBitDepth()); + header.write(stream); + streamStartPos = static_cast(stream->tellp()); +} + +bool A2BWAVOutput::callIfMatch(const std::shared_ptr& message) const { + if(closed) + { + return false; + } + + if(message->type != Message::Type::Frame) + return false; + + const auto& frame = std::static_pointer_cast(message); + + if(frame->network.getType() != Network::Type::A2B) + return false; + + const auto& a2bmsg = std::static_pointer_cast(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(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& 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; +} + +} \ No newline at end of file diff --git a/communication/packet/a2bpacket.cpp b/communication/packet/a2bpacket.cpp index 916724c..2a28146 100644 --- a/communication/packet/a2bpacket.cpp +++ b/communication/packet/a2bpacket.cpp @@ -4,8 +4,7 @@ using namespace icsneo; -std::shared_ptr HardwareA2BPacket::DecodeToMessage(const std::vector &bytestream) -{ +std::shared_ptr HardwareA2BPacket::DecodeToMessage(const std::vector &bytestream) { constexpr uint8_t coreMiniMessageHeaderSize = 28; @@ -17,9 +16,8 @@ std::shared_ptr HardwareA2BPacket::DecodeToMessage(const std::vector(bytes[i]) << (i * 8); } return result; @@ -42,8 +40,7 @@ std::shared_ptr HardwareA2BPacket::DecodeToMessage(const std::vector(bytesPerChannel), bytes += 2 * bytesPerChannel, channel = (channel + 1) % numChannels) - { + for(uint32_t i = 0; i < totalPackedLength; i += 2 * static_cast(bytesPerChannel), bytes += 2 * bytesPerChannel, channel = (channel + 1) % numChannels) { msg->addSample( getSampleFromBytes(bytesPerChannel, bytes), diff --git a/device/device.cpp b/device/device.cpp index e2683b9..5940444 100644 --- a/device/device.cpp +++ b/device/device.cpp @@ -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) { + messagePollingCallbackID = com->addMessageCallback(std::make_shared([this](std::shared_ptr 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) { + internalHandlerCallbackID = com->addMessageCallback(std::make_shared(filter, [this](std::shared_ptr message) { handleInternalMessage(message); })); @@ -232,7 +232,7 @@ bool Device::open(OpenFlags flags, OpenStatusHandler handler) { MessageFilter filter; filter.includeInternalInAny = true; std::atomic receivedMessage{false}; - auto messageReceivedCallbackID = com->addMessageCallback(MessageCallback(filter, [&receivedMessage](std::shared_ptr message) { + auto messageReceivedCallbackID = com->addMessageCallback(std::make_shared(filter, [&receivedMessage](std::shared_ptr message) { receivedMessage = true; })); diff --git a/disk/plasiondiskreaddriver.cpp b/disk/plasiondiskreaddriver.cpp index 7bd4328..2018b21 100644 --- a/disk/plasiondiskreaddriver.cpp +++ b/disk/plasiondiskreaddriver.cpp @@ -29,7 +29,7 @@ std::optional PlasionDiskReadDriver::readLogicalDiskAligned(Communicat uint32_t copied = 0; bool error = false; std::unique_lock lk(m); - auto cb = com.addMessageCallback(MessageCallback([&](std::shared_ptr msg) { + auto cb = com.addMessageCallback(std::make_shared([&](std::shared_ptr msg) { std::unique_lock lk(m); const auto sdmsg = std::dynamic_pointer_cast(msg); @@ -45,6 +45,7 @@ std::optional PlasionDiskReadDriver::readLogicalDiskAligned(Communicat if(copied == amount) { lk.unlock(); cv.notify_all(); + } }, NeoMemorySDRead)); diff --git a/docs/Usage.rst b/docs/Usage.rst index 608b3b1..0af74f0 100644 --- a/docs/Usage.rst +++ b/docs/Usage.rst @@ -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`` diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 81d29b3..f65d96e 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -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() diff --git a/examples/cpp/a2b/CMakeLists.txt b/examples/cpp/a2b/CMakeLists.txt new file mode 100644 index 0000000..dca64b2 --- /dev/null +++ b/examples/cpp/a2b/CMakeLists.txt @@ -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) \ No newline at end of file diff --git a/examples/cpp/a2b/README.md b/examples/cpp/a2b/README.md new file mode 100644 index 0000000..2003d4d --- /dev/null +++ b/examples/cpp/a2b/README.md @@ -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™ diff --git a/examples/cpp/a2b/src/a2b.cpp b/examples/cpp/a2b/src/a2b.cpp new file mode 100644 index 0000000..ec99f09 --- /dev/null +++ b/examples/cpp/a2b/src/a2b.cpp @@ -0,0 +1,52 @@ +#include +#include +#include +#include + +#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 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("examples/cpp/a2b/src/out.wav")); // Starts writing A2B PCM data to out.wav + + auto handler2 = rada2b->addMessageCallback( + std::make_shared( + [](std::shared_ptr message) { + std::cout << "Got in callback " << std::endl; + if(message->type == icsneo::Message::Type::Frame) { + std::shared_ptr frame = std::static_pointer_cast(message); + + if(frame->network.getType() == icsneo::Network::Type::A2B) { + std::shared_ptr msg = std::static_pointer_cast(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; +} \ No newline at end of file diff --git a/examples/cpp/interactive/src/InteractiveExample.cpp b/examples/cpp/interactive/src/InteractiveExample.cpp index 8efc7cf..4ee446b 100644 --- a/examples/cpp/interactive/src/InteractiveExample.cpp +++ b/examples/cpp/interactive/src/InteractiveExample.cpp @@ -614,7 +614,7 @@ int main() { switch(selection) { case '1': { - int callbackID = selectedDevice->addMessageCallback(icsneo::MessageCallback([](std::shared_ptr msg){ + int callbackID = selectedDevice->addMessageCallback(std::make_shared([](std::shared_ptr msg){ printMessage(msg); })); diff --git a/examples/cpp/simple/src/SimpleExample.cpp b/examples/cpp/simple/src/SimpleExample.cpp index 588864a..960cf50 100644 --- a/examples/cpp/simple/src/SimpleExample.cpp +++ b/examples/cpp/simple/src/SimpleExample.cpp @@ -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 message) { + auto handler = device->addMessageCallback(std::make_shared([](std::shared_ptr 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 diff --git a/include/icsneo/communication/communication.h b/include/icsneo/communication/communication.h index 782a93a..d4df8dc 100644 --- a/include/icsneo/communication/communication.h +++ b/include/icsneo/communication/communication.h @@ -59,7 +59,7 @@ public: std::optional< std::vector< std::optional > > getVersionsSync(std::chrono::milliseconds timeout = std::chrono::milliseconds(50)); std::shared_ptr getLogicalDiskInfoSync(std::chrono::milliseconds timeout = std::chrono::milliseconds(50)); - int addMessageCallback(const MessageCallback& cb); + int addMessageCallback(const std::shared_ptr& cb); bool removeMessageCallback(int id); std::shared_ptr waitForMessageSync( const std::shared_ptr& f = {}, @@ -83,7 +83,7 @@ public: protected: static int messageCallbackIDCounter; std::mutex messageCallbacksLock; - std::map messageCallbacks; + std::map> messageCallbacks; std::atomic closing{false}; std::atomic redirectingRead{false}; std::function&&)> redirectionFn; diff --git a/include/icsneo/communication/message/a2bmessage.h b/include/icsneo/communication/message/a2bmessage.h index 5082990..11c6749 100644 --- a/include/icsneo/communication/message/a2bmessage.h +++ b/include/icsneo/communication/message/a2bmessage.h @@ -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 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 getSample(A2BDirection dir, uint8_t channel, uint32_t sampleIndex) const - { - const A2BPCMSample *samples = getSamples(dir, channel); + std::optional 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 getNumSamplesInChannel(A2BDirection dir, uint8_t channel) const - { - if(channel >= getNumChannels()) - { + std::optional 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& getDownstream() const { + return downstream; + } + + const std::vector& getUpstream() const { + return upstream; + } + + size_t getNumSamples() const { return totalSamples; } - uint8_t getNumChannels() const - { + uint8_t getNumChannels() const { return static_cast(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; } diff --git a/include/icsneo/communication/message/callback/streamoutput/a2bwavoutput.h b/include/icsneo/communication/message/callback/streamoutput/a2bwavoutput.h new file mode 100644 index 0000000..e7820c5 --- /dev/null +++ b/include/icsneo/communication/message/callback/streamoutput/a2bwavoutput.h @@ -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 +#include + +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&& os, uint32_t sampleRate = A2BPCM_SAMPLERATE_44100) + : wavSampleRate(sampleRate), StreamOutput(std::move(os)) {} + + void writeHeader(const std::shared_ptr& firstMsg) const; + + bool callIfMatch(const std::shared_ptr& message) const override; + + void close() const; + + ~A2BWAVOutput() { + if(!closed) { + close(); + } + } + +protected: + + bool writeSamples(const std::shared_ptr& msg, A2BMessage::A2BDirection dir) const; + + uint32_t wavSampleRate; + mutable uint32_t streamStartPos; + mutable bool firstMessageFlag = true; + mutable bool closed = false; +}; + +} + +#endif // __cplusplus + +#endif diff --git a/include/icsneo/communication/message/callback/streamoutput/streamoutput.h b/include/icsneo/communication/message/callback/streamoutput/streamoutput.h new file mode 100644 index 0000000..751f323 --- /dev/null +++ b/include/icsneo/communication/message/callback/streamoutput/streamoutput.h @@ -0,0 +1,112 @@ +#ifndef __STREAMOUTPUT_H_ +#define __STREAMOUTPUT_H_ + +#ifdef __cplusplus + +#include "icsneo/communication/message/callback/messagecallback.h" +#include +#include +#include +#include + +#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& stream) { + + stream->write(reinterpret_cast(&chunkId), 4); + stream->write(reinterpret_cast(&chunkSize), 4); + stream->write(reinterpret_cast(&format), 4); + stream->write(reinterpret_cast(&subchunk1Id), 4); + stream->write(reinterpret_cast(&subchunk1Size), 4); + stream->write(reinterpret_cast(&audioFormat), 2); + stream->write(reinterpret_cast(&numChannels), 2); + stream->write(reinterpret_cast(&sampleRate), 4); + stream->write(reinterpret_cast(&byteRate), 4); + stream->write(reinterpret_cast(&blockAlign), 2); + stream->write(reinterpret_cast(&bitsPerSample), 2); + stream->write(reinterpret_cast(&subchunk2Id), 4); + stream->write(reinterpret_cast(&subchunk2Size), 4); + + } +}; + +class StreamOutput : public MessageCallback { +public: + StreamOutput(std::unique_ptr&& os, fn_messageCallback cb, std::shared_ptr f) + : stream(std::move(os)), MessageCallback(cb, f) {} + + StreamOutput(const char* filename, fn_messageCallback cb, std::shared_ptr f) + : MessageCallback(cb, f) { + stream = std::make_unique(filename, std::ios::binary); + } + + StreamOutput(const char* filename) : MessageCallback([](std::shared_ptr msg) {}) { + stream = std::make_unique(filename, std::ios::binary); + } + + StreamOutput(std::unique_ptr&& os) : stream(std::move(os)), MessageCallback([](std::shared_ptr msg) {}) {} + + virtual ~StreamOutput() {} + +protected: + std::unique_ptr stream; + + void write(void* msg, std::streamsize size) const { + stream->write(reinterpret_cast(msg), size); + } +}; + +} + +#endif // __cplusplus + +#endif diff --git a/include/icsneo/communication/packet/a2bpacket.h b/include/icsneo/communication/packet/a2bpacket.h index da90bb3..b27afdd 100644 --- a/include/icsneo/communication/packet/a2bpacket.h +++ b/include/icsneo/communication/packet/a2bpacket.h @@ -19,8 +19,7 @@ struct HardwareA2BPacket { static std::shared_ptr DecodeToMessage(const std::vector& bytestream); - struct - { + struct { // CxA2B icscm_bitfield channelNum : 8; icscm_bitfield channelSize16 : 1; diff --git a/include/icsneo/device/device.h b/include/icsneo/device/device.h index c7f8c98..5479ece 100644 --- a/include/icsneo/device/device.h +++ b/include/icsneo/device/device.h @@ -153,7 +153,7 @@ public: enforcePollingMessageLimit(); } - int addMessageCallback(const MessageCallback& cb) { return com->addMessageCallback(cb); } + int addMessageCallback(const std::shared_ptr& cb) { return com->addMessageCallback(cb); } bool removeMessageCallback(int id) { return com->removeMessageCallback(id); } bool transmit(std::shared_ptr frame); diff --git a/include/icsneo/icsneocpp.h b/include/icsneo/icsneocpp.h index 7961a0f..d62305d 100644 --- a/include/icsneo/icsneocpp.h +++ b/include/icsneo/icsneocpp.h @@ -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> FindAllDevices();