A2B: Add initial WAV streaming support

This commit is contained in:
Yasser Yassine
2022-10-19 18:44:05 -04:00
parent 7f22286838
commit 7b2544864b
21 changed files with 461 additions and 61 deletions
@@ -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
{
const A2BPCMSample *samples = getSamples(dir, channel);
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