mirror of
https://github.com/intrepidcs/libicsneo.git
synced 2026-08-05 01:18:36 +02:00
Device: Refactor A2B APIs
* Removes features in `A2BMessage` class to support API for reading 16, 24, and 32 bit samples from A2B channels * Re-organizes WAV receiving and transmitting code and API * Creates API for mapping message channels to WAV channels and vice versa for transmitting and receiving * Fixes `icsneo::Network::NetID::ExtendedData` VnetID bug for `icsneo::ExtendedDataMessage` decoding * Creates RAD-A2B sequence chart example * Fixes coremini uploading for certain devices in EEPROM by introducing `icsneo::Device::supportsEraseMemory`
This commit is contained in:
committed by
Kyle Schwarz
parent
06f6861130
commit
cb22e622b3
@@ -5,435 +5,104 @@
|
||||
|
||||
#include "icsneo/communication/message/message.h"
|
||||
#include "icsneo/api/eventmanager.h"
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
|
||||
#include <unordered_map>
|
||||
#include "icsneo/communication/message/callback/streamoutput/streamoutput.h"
|
||||
|
||||
namespace icsneo {
|
||||
|
||||
typedef uint32_t A2BPCMSample;
|
||||
using PCMSample = int32_t;
|
||||
enum class PCMType : uint8_t {
|
||||
L16,
|
||||
L24,
|
||||
L32
|
||||
};
|
||||
|
||||
using ChannelMap = std::unordered_map<uint8_t, uint8_t>;
|
||||
|
||||
class A2BMessage : public Frame {
|
||||
private:
|
||||
class FrameView {
|
||||
private:
|
||||
class SampleView {
|
||||
public:
|
||||
SampleView(uint8_t* vPtr, uint8_t bps, size_t ind) :
|
||||
index(ind), viewPtr(vPtr), bytesPerSample(bps) {}
|
||||
public:
|
||||
static constexpr size_t maxAudioBufferSize = 2048;
|
||||
|
||||
operator A2BPCMSample() const {
|
||||
if(!viewPtr) {
|
||||
return 0;
|
||||
}
|
||||
A2BPCMSample sample = 0;
|
||||
|
||||
std::copy(viewPtr+index*bytesPerSample, viewPtr+(index+1)*bytesPerSample, (uint8_t*)&sample);
|
||||
if(bytesPerSample == 4) {
|
||||
sample = sample >> 8;
|
||||
}
|
||||
|
||||
return sample;
|
||||
}
|
||||
|
||||
SampleView& operator=(A2BPCMSample sample) {
|
||||
if(!viewPtr) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
if(bytesPerSample == 4) {
|
||||
sample = sample << 8;
|
||||
}
|
||||
std::copy((uint8_t*)&sample, (uint8_t*)&sample + bytesPerSample, viewPtr + index*bytesPerSample);
|
||||
return *this;
|
||||
}
|
||||
|
||||
SampleView(const SampleView&) = delete;
|
||||
SampleView& operator=(const SampleView&) = delete;
|
||||
|
||||
private:
|
||||
|
||||
size_t index;
|
||||
uint8_t* viewPtr;
|
||||
uint8_t bytesPerSample;
|
||||
};
|
||||
|
||||
public:
|
||||
FrameView(uint8_t* vPtr, uint8_t nChannels, uint8_t bps) : viewPtr(vPtr), tdm(nChannels), bytesPerSample(bps) {}
|
||||
|
||||
SampleView operator[](size_t index) {
|
||||
|
||||
if(index >= ((size_t)tdm) * 2) {
|
||||
EventManager::GetInstance().add(APIEvent(APIEvent::Type::ParameterOutOfRange, APIEvent::Severity::Error));
|
||||
return SampleView(nullptr, 0, 0);
|
||||
}
|
||||
return SampleView(viewPtr, bytesPerSample, index);
|
||||
}
|
||||
|
||||
FrameView& operator=(const std::vector<A2BPCMSample>& samples) {
|
||||
if(!viewPtr) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
if(samples.size() != (size_t)(tdm)*2) {
|
||||
EventManager::GetInstance().add(APIEvent(APIEvent::Type::BufferInsufficient, APIEvent::Severity::Error));
|
||||
return *this;
|
||||
}
|
||||
|
||||
for(size_t icsChannel = 0; icsChannel < ((size_t)(tdm) * 2); icsChannel++) {
|
||||
operator[](icsChannel) = samples[icsChannel];
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
FrameView(const FrameView&) = delete;
|
||||
FrameView& operator=(const FrameView&) = delete;
|
||||
private:
|
||||
uint8_t* viewPtr;
|
||||
uint8_t tdm;
|
||||
uint8_t bytesPerSample;
|
||||
enum class TDMMode : uint8_t {
|
||||
TDM2 = 0,
|
||||
TDM4 = 1,
|
||||
TDM8 = 2,
|
||||
TDM12 = 3,
|
||||
TDM16 = 4,
|
||||
TDM20 = 5,
|
||||
TDM24 = 6,
|
||||
TDM32 = 7,
|
||||
};
|
||||
|
||||
public:
|
||||
enum class A2BDirection : uint8_t {
|
||||
static uint8_t tdmToChannelNum(TDMMode tdm);
|
||||
|
||||
enum class Direction : uint8_t {
|
||||
Downstream = 0,
|
||||
Upstream = 1
|
||||
};
|
||||
|
||||
A2BMessage(uint8_t nChannels, bool chSize16, size_t size) :
|
||||
numChannels(nChannels),
|
||||
channelSize16(chSize16)
|
||||
{
|
||||
data.resize(std::min(roundNextMultiple(size, getFrameSize()),(size_t)maxSize), 0);
|
||||
}
|
||||
|
||||
bool allocateSpace(size_t numSpaceToAdd) {
|
||||
size_t spaceToAdd = roundNextMultiple(numSpaceToAdd, getFrameSize());
|
||||
|
||||
if(spaceToAdd + data.size() > maxSize) {
|
||||
return false;
|
||||
}
|
||||
|
||||
data.resize(data.size() + numSpaceToAdd, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool addFrame(const std::vector<A2BPCMSample>& frame) {
|
||||
if(frame.size() != ((size_t)numChannels)*2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t oldSize = data.size();
|
||||
|
||||
if(!allocateSpace(getFrameSize())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto it = data.begin() + oldSize;
|
||||
size_t offset = 0;
|
||||
for(A2BPCMSample sample: frame) {
|
||||
if(!channelSize16) {
|
||||
sample = sample << 8;
|
||||
}
|
||||
std::copy((uint8_t*)&sample, (uint8_t*)&sample + getBytesPerSample(), it + offset);
|
||||
offset+=getBytesPerSample();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool setFrame(const std::vector<A2BPCMSample>& frame, size_t frameNum) {
|
||||
if(frame.size() != ((size_t)numChannels)*2 || frameNum >= getNumFrames()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto it = data.begin() + frameNum*getFrameSize();
|
||||
size_t offset = 0;
|
||||
for(A2BPCMSample sample: frame) {
|
||||
if(!channelSize16) {
|
||||
sample = sample << 8;
|
||||
}
|
||||
std::copy((uint8_t*)&sample, (uint8_t*)&sample + getBytesPerSample(), it + offset);
|
||||
offset+=getBytesPerSample();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool fillChannelAudioBuffer(A2BDirection dir, uint8_t channel, std::vector<uint8_t>& channelBuffer) const {
|
||||
if(channel >= numChannels) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t offset = getChannelIndex(dir, channel)*getBytesPerSample();
|
||||
|
||||
for(size_t frame = 0; frame < getNumFrames(); frame++, offset += getFrameSize()) {
|
||||
std::copy(data.begin() + offset, data.end() + offset + getBytesPerSample(), std::back_inserter(channelBuffer));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool fillChannelStream(A2BDirection dir, uint8_t channel, std::unique_ptr<std::ostream>& channelStream) const {
|
||||
if(channel >= numChannels) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t offset = getChannelIndex(dir, channel)*getBytesPerSample();
|
||||
|
||||
for(size_t frame = 0; frame < getNumFrames(); frame++, offset += getFrameSize()) {
|
||||
channelStream->write((const char*)(data.data() + offset), getBytesPerSample());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void fill(A2BPCMSample sample) {
|
||||
uint8_t* buf = data.data();
|
||||
|
||||
if(channelSize16) {
|
||||
uint16_t sample16bit = sample & 0xFF;
|
||||
uint16_t* samps = (uint16_t*)buf;
|
||||
std::fill(samps, samps + data.size()/2, sample16bit);
|
||||
}
|
||||
else {
|
||||
A2BPCMSample* samps = (A2BPCMSample*)buf;
|
||||
sample = sample << 8;
|
||||
std::fill(samps, samps + data.size()/4, sample);
|
||||
}
|
||||
}
|
||||
|
||||
bool fillFrame(A2BPCMSample sample, size_t frame) {
|
||||
if(frame >= getNumFrames()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t* buf = data.data();
|
||||
size_t start = 2 * numChannels * frame;
|
||||
size_t end = 2 * numChannels * (frame+1);
|
||||
|
||||
if(channelSize16) {
|
||||
uint16_t sample16bit = sample & 0xFF;
|
||||
uint16_t* samps = (uint16_t*)buf;
|
||||
std::fill(samps+start, samps + end, sample16bit);
|
||||
}
|
||||
else {
|
||||
A2BPCMSample* samps = (A2BPCMSample*)buf;
|
||||
sample = sample << 8;
|
||||
std::fill(samps+start, samps + end, sample);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename Iterator>
|
||||
bool setAudioBuffer(Iterator begin, Iterator end, A2BDirection dir, uint8_t channel, uint32_t frame) {
|
||||
size_t offset = getChannelIndex(dir, channel)*getBytesPerSample() + frame * getFrameSize();
|
||||
size_t dist = (size_t)(std::distance(begin, end));
|
||||
|
||||
if(dist > (data.size() - offset)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::copy(begin, end, data.begin() + offset);
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename Iterator>
|
||||
bool setAudioBuffer(Iterator begin, Iterator end) {
|
||||
return setAudioBuffer(begin, end, A2BMessage::A2BDirection::Downstream, 0, 0);
|
||||
}
|
||||
|
||||
std::optional<A2BPCMSample> getSample(A2BDirection dir, uint8_t channel, uint32_t frame) const {
|
||||
if(
|
||||
channel >= numChannels ||
|
||||
frame >= getNumFrames()
|
||||
) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
A2BPCMSample sample = 0;
|
||||
size_t offset = getChannelIndex(dir, channel)*getBytesPerSample() + frame * getFrameSize();
|
||||
|
||||
std::copy(data.begin() + offset, data.begin() + offset + getBytesPerSample(), (uint8_t*)&sample);
|
||||
if(channelSize16) {
|
||||
sample = sample >> 8;
|
||||
}
|
||||
|
||||
return sample;
|
||||
}
|
||||
|
||||
std::optional<A2BPCMSample> getSample(size_t sampleNum) const {
|
||||
if(sampleNum >= getNumSamples()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
|
||||
A2BPCMSample sample = 0;
|
||||
size_t offset = sampleNum*getBytesPerSample();
|
||||
|
||||
std::copy(data.begin() + offset, data.begin() + offset + getBytesPerSample(), (uint8_t*)&sample);
|
||||
|
||||
if(channelSize16) {
|
||||
sample = sample >> 8;
|
||||
}
|
||||
|
||||
return sample;
|
||||
}
|
||||
|
||||
bool setSample(A2BDirection dir, uint8_t channel, uint32_t frame, A2BPCMSample sample) {
|
||||
if(
|
||||
channel >= numChannels ||
|
||||
frame >= getNumFrames()
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t offset = getChannelIndex(dir, channel)*getBytesPerSample() + frame * getFrameSize();
|
||||
|
||||
if(!channelSize16) {
|
||||
sample = sample << 8;
|
||||
}
|
||||
uint8_t* sampToBytes = (uint8_t*)&sample;
|
||||
std::copy(sampToBytes,sampToBytes+getBytesPerSample(), data.begin() + offset);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool setSample(uint8_t icsChannel, uint32_t frame, A2BPCMSample sample) {
|
||||
if(
|
||||
icsChannel >= (2*numChannels) ||
|
||||
frame >= getNumFrames()
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t offset = ((size_t)icsChannel)*getBytesPerSample() + frame * getFrameSize();
|
||||
|
||||
if(!channelSize16) {
|
||||
sample = sample << 8;
|
||||
}
|
||||
uint8_t* sampToBytes = (uint8_t*)&sample;
|
||||
std::copy(sampToBytes,sampToBytes+getBytesPerSample(), data.begin() + offset);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
FrameView operator[](size_t index) {
|
||||
if(index >= getNumFrames()) {
|
||||
EventManager::GetInstance().add(APIEvent(APIEvent::Type::ParameterOutOfRange, APIEvent::Severity::Error));
|
||||
return FrameView(nullptr, 0, 0);
|
||||
}
|
||||
|
||||
return FrameView(data.data() + index*getFrameSize(), numChannels, getBytesPerSample());
|
||||
}
|
||||
|
||||
size_t getNumSamples() const {
|
||||
return data.size()/((size_t)getBytesPerSample());
|
||||
}
|
||||
|
||||
uint8_t getNumChannels() const {
|
||||
return numChannels;
|
||||
}
|
||||
|
||||
uint8_t getBitDepth() const {
|
||||
return channelSize16 ? 16 : 24;
|
||||
}
|
||||
|
||||
uint8_t getBytesPerSample() const {
|
||||
return channelSize16 ? 2 : 4;
|
||||
}
|
||||
|
||||
bool isTxMsg() const {
|
||||
return txmsg;
|
||||
}
|
||||
|
||||
void setTxMsgBit(bool bit) {
|
||||
txmsg = bit;
|
||||
}
|
||||
|
||||
bool isMonitorMsg() const {
|
||||
return monitor;
|
||||
}
|
||||
|
||||
void setMonitorBit(bool bit) {
|
||||
monitor = bit;
|
||||
}
|
||||
|
||||
bool isErrIndicator() const {
|
||||
return errIndicator;
|
||||
}
|
||||
|
||||
void setErrIndicatorBit(bool bit) {
|
||||
errIndicator = bit;
|
||||
}
|
||||
|
||||
bool isSyncFrame() const {
|
||||
return syncFrame;
|
||||
}
|
||||
|
||||
void setSyncFrameBit(bool bit) {
|
||||
syncFrame = bit;
|
||||
}
|
||||
|
||||
uint16_t getRFU2() const {
|
||||
return rfu2;
|
||||
}
|
||||
|
||||
void setRFU2(uint16_t newRfu2) {
|
||||
rfu2 = newRfu2;
|
||||
}
|
||||
|
||||
size_t getFrameSize() const {
|
||||
return 2*((size_t)numChannels) * ((size_t)getBytesPerSample());
|
||||
}
|
||||
|
||||
size_t getNumFrames() const {
|
||||
return data.size() / getFrameSize();
|
||||
}
|
||||
|
||||
size_t getAudioBufferSize() const {
|
||||
return data.size();
|
||||
}
|
||||
|
||||
const uint8_t* getAudioBuffer() const {
|
||||
return data.data();
|
||||
}
|
||||
|
||||
|
||||
static constexpr uint32_t maxSize = 2048;
|
||||
private:
|
||||
|
||||
uint16_t rfu2 = 0;
|
||||
uint8_t numChannels = 0;
|
||||
bool channelSize16 = false;
|
||||
bool monitor = false;
|
||||
bool txmsg = false;
|
||||
bool errIndicator = false;
|
||||
bool syncFrame = false;
|
||||
uint16_t rfu2 = 0;
|
||||
|
||||
size_t roundNextMultiple(size_t x, size_t y) const {
|
||||
if(y==0) {
|
||||
return 0;
|
||||
}
|
||||
else if(x%y == 0) {
|
||||
return x;
|
||||
}
|
||||
A2BMessage() = default;
|
||||
/**
|
||||
* Creates a new A2BMessage
|
||||
*
|
||||
* @param numFrames The number of audio frames to hold in the message audio buffer
|
||||
* @param tdm The TDM mode to transmit this message to, note this variable determines the number of channels
|
||||
* @param chSize16 True if the message channel sizes are 16 bit, false for 32 bit.
|
||||
*/
|
||||
A2BMessage(size_t numFrames, TDMMode tdm, bool chSize16);
|
||||
|
||||
return x + y - (x%y);
|
||||
}
|
||||
/**
|
||||
* Creates a new A2BMessage with the maximum number of possible frames
|
||||
*
|
||||
* @param tdm The TDM mode to transmit this message to, note this variable determines the number of channels
|
||||
* @param chSize16 True if the message channel sizes are 16 bit, false for 32 bit.
|
||||
*/
|
||||
A2BMessage(TDMMode tdm, bool chSize16);
|
||||
|
||||
size_t getChannelIndex(A2BDirection dir, uint8_t channel) const {
|
||||
size_t channelIndex = 2 * ((size_t)channel);
|
||||
|
||||
if(dir == A2BDirection::Upstream) {
|
||||
channelIndex++;
|
||||
}
|
||||
/**
|
||||
* Loads A2BMessage audio buffer from a IWAVStream object representing a WAV data-stream
|
||||
*
|
||||
* @param wavStream The WAV data-stream the audio buffer with
|
||||
* @param channelMap A map which maps a message channel to a wav channel. See docs for A2B message channel format
|
||||
* @returns true on successful load, false otherwise
|
||||
*/
|
||||
bool loadAudioBuffer(IWAVStream& wavStream, const ChannelMap& channelMap);
|
||||
|
||||
return channelIndex;
|
||||
}
|
||||
/**
|
||||
* Get a PCM sample from the audio buffer. If the desired pcmType is larger than the channel size,
|
||||
* the output will be a PCM sample which is scaled up.
|
||||
*
|
||||
* @param dir The direction of the A2B stream
|
||||
* @param channel The desired channel to read a PCM sample from
|
||||
* @param frame The desired frame to read a PCM sample from
|
||||
* @param pcmType The interpretted bit depth of the audio buffer sample
|
||||
*/
|
||||
PCMSample getChannelSample(Direction dir, uint8_t channel, size_t frame, PCMType pcmType) const;
|
||||
|
||||
/**
|
||||
* Write a PCM sample to the audio buffer
|
||||
*
|
||||
* @param dir The direction of the A2B stream
|
||||
* @param channel The desired channel to write a PCM sample to
|
||||
* @param frame The desired frame to write a PCM sample to
|
||||
* @param sampleToSet The PCM sample which will be written to the buffer
|
||||
* @param pcmType The interpretted bit depth of the sample to write
|
||||
*/
|
||||
void setChannelSample(Direction dir, uint8_t channel, size_t frame, PCMSample sampleToSet, PCMType pcmType);
|
||||
|
||||
size_t getFrameSize() const;
|
||||
size_t getSampleOffset(Direction dir, uint8_t channel, size_t frame) const;
|
||||
uint8_t getBytesPerChannel() const;
|
||||
size_t getNumFrames() const;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
#ifndef __A2BDECODER_H_
|
||||
#define __A2BDECODER_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
#include "icsneo/communication/message/callback/streamoutput/streamoutput.h"
|
||||
#include "icsneo/communication/message/a2bmessage.h"
|
||||
#include "icsneo/device/device.h"
|
||||
|
||||
namespace icsneo {
|
||||
|
||||
typedef uint8_t Channel;
|
||||
|
||||
class A2BAudioChannelMap {
|
||||
public:
|
||||
|
||||
A2BAudioChannelMap(uint8_t tdm);
|
||||
|
||||
void set(Channel outChannel, A2BMessage::A2BDirection dir, Channel inChannel);
|
||||
void setAll(Channel inChannel);
|
||||
|
||||
Channel get(Channel outChannel, A2BMessage::A2BDirection dir) const;
|
||||
|
||||
size_t size() const;
|
||||
|
||||
uint8_t getTDM() const;
|
||||
Channel& operator[](size_t idx);
|
||||
|
||||
operator const std::vector<Channel>&() const;
|
||||
|
||||
|
||||
|
||||
private:
|
||||
|
||||
size_t getChannelIndex(Channel channel, A2BMessage::A2BDirection dir) const;
|
||||
|
||||
std::vector<Channel> rawMap;
|
||||
};
|
||||
|
||||
|
||||
|
||||
class A2BDecoder {
|
||||
public:
|
||||
|
||||
A2BDecoder(
|
||||
std::unique_ptr<std::istream>&& streamOut,
|
||||
bool chSize16,
|
||||
const A2BAudioChannelMap& chMap
|
||||
);
|
||||
|
||||
A2BDecoder(
|
||||
const char* filename,
|
||||
bool chSize16,
|
||||
const A2BAudioChannelMap& chMap
|
||||
);
|
||||
|
||||
operator bool() const;
|
||||
|
||||
std::shared_ptr<A2BMessage> decode();
|
||||
|
||||
bool outputAll(std::shared_ptr<Device> &device);
|
||||
|
||||
std::unique_ptr<std::istream> stream;
|
||||
private:
|
||||
|
||||
void initializeFromHeader();
|
||||
|
||||
uint8_t tdm;
|
||||
uint8_t audioBytesPerSample;
|
||||
uint8_t channelsInWave;
|
||||
bool channelSize16;
|
||||
A2BAudioChannelMap channelMap;
|
||||
|
||||
std::vector<uint8_t> frame;
|
||||
std::vector<uint8_t> frameWave;
|
||||
|
||||
bool initialized = false;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // __cplusplus
|
||||
|
||||
#endif
|
||||
@@ -9,33 +9,73 @@
|
||||
|
||||
namespace icsneo {
|
||||
|
||||
/**
|
||||
* A message callback which injests A2BMessage PCM data and formats it into a WAV file
|
||||
*/
|
||||
class A2BWAVOutput : public StreamOutput {
|
||||
public:
|
||||
A2BWAVOutput(const char* filename, uint32_t sampleRate = 44100)
|
||||
: StreamOutput(filename), wavSampleRate(sampleRate) {}
|
||||
static constexpr size_t wavBufferSize = 1024 * 32;
|
||||
|
||||
A2BWAVOutput(std::unique_ptr<std::ostream>&& os, uint32_t sampleRate = 44100)
|
||||
: StreamOutput(std::move(os)), wavSampleRate(sampleRate) {}
|
||||
/**
|
||||
* Creates a new A2BWAVOutput object
|
||||
*
|
||||
* @param filename Name of desired output WAV file
|
||||
* @param channelMap A map which maps a channel in the output WAV file to a channel in received messages. See docs for specific channel format in messages
|
||||
* @param bitDepth The size of the samples in the WAV file.
|
||||
* @param numWAVChannels The number of channels in the output WAV file
|
||||
* @param sampleRate The output WAV file sample rate
|
||||
*/
|
||||
A2BWAVOutput(
|
||||
const char* filename,
|
||||
const ChannelMap& channelMap,
|
||||
PCMType bitDepth,
|
||||
size_t numWAVChannels,
|
||||
uint32_t sampleRate = 48000
|
||||
);
|
||||
|
||||
void writeHeader(const std::shared_ptr<A2BMessage>& firstMsg) const;
|
||||
/**
|
||||
* Creates a new A2BWAVOutput object
|
||||
*
|
||||
* @param os A std::ostream object which represents this WAV file
|
||||
* @param channelMap A map which maps a channel in the output WAV file to a channel in received messages. See docs for specific channel format in messages
|
||||
* @param bitDepth The size of the samples in the WAV file.
|
||||
* @param numWAVChannels The number of channels in the output WAV file
|
||||
* @param sampleRate The output WAV file sample rate
|
||||
*/
|
||||
A2BWAVOutput(
|
||||
std::ostream& os,
|
||||
const ChannelMap& channelMap,
|
||||
PCMType bitDepth,
|
||||
size_t numWAVChannels,
|
||||
uint32_t sampleRate = 48000
|
||||
);
|
||||
|
||||
bool callIfMatch(const std::shared_ptr<Message>& message) const override;
|
||||
|
||||
void close() const;
|
||||
|
||||
~A2BWAVOutput() override {
|
||||
if(!closed) {
|
||||
close();
|
||||
}
|
||||
}
|
||||
~A2BWAVOutput() override;
|
||||
|
||||
protected:
|
||||
void close() const;
|
||||
bool initialize();
|
||||
|
||||
uint32_t wavSampleRate;
|
||||
/**
|
||||
* Write and clear the current stored audio buffer
|
||||
*/
|
||||
bool writeCurrentBuffer() const;
|
||||
|
||||
mutable std::vector<uint8_t> wavBuffer; // A buffer which is used to cache PCM data to write to disk later
|
||||
mutable size_t wavBufferOffset = 0; // Current offset in the above buffer, gets incremented as data is read into buffer
|
||||
uint32_t wavSampleRate; // The output WAV sample rate
|
||||
|
||||
size_t bytesPerSampleWAV; // The number of bytes per sample in the output WAV file
|
||||
size_t numChannelsWAV; // The number of channels in the output WAV file
|
||||
|
||||
ChannelMap chMap; // A map which maps a WAV channel to a A2BMessage channel
|
||||
size_t maxMessageChannel; // The highest message channel in the above channel map, this variable is used for error checking
|
||||
|
||||
bool initialized = false;
|
||||
mutable uint32_t streamStartPos;
|
||||
mutable bool firstMessageFlag = true;
|
||||
mutable bool closed = false;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -14,33 +14,34 @@
|
||||
|
||||
namespace icsneo {
|
||||
|
||||
struct WaveFileHeader {
|
||||
#pragma pack(push, 1)
|
||||
struct WAVHeader {
|
||||
|
||||
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
|
||||
static constexpr uint32_t WAV_CHUNK_ID = 0x46464952; // "RIFF"
|
||||
static constexpr uint32_t WAV_FORMAT = 0x45564157; // "WAV"
|
||||
static constexpr uint32_t WAV_SUBCHUNK1_ID = 0x20746d66; // "fmt "
|
||||
static constexpr uint32_t WAV_SUBCHUNK2_ID = 0x61746164; // "data"
|
||||
static constexpr uint16_t WAV_SUBCHUNK1_SIZE = 16;
|
||||
static constexpr uint16_t WAV_AUDIO_FORMAT_PCM = 1;
|
||||
static constexpr uint32_t WAV_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
|
||||
uint32_t chunkId = WAV_CHUNK_ID; // "RIFF"
|
||||
uint32_t chunkSize = WAV_DEFAULT_SIZE; // number of bytes to follow
|
||||
uint32_t format = WAV_FORMAT; // "WAV"
|
||||
uint32_t subchunk1Id = WAV_SUBCHUNK1_ID; // "fmt "
|
||||
uint32_t subchunk1Size = WAV_SUBCHUNK1_SIZE; // number of bytes in *this* subchunk (always 16)
|
||||
uint16_t audioFormat = WAV_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
|
||||
uint32_t subchunk2Id = WAV_SUBCHUNK2_ID; // "data"
|
||||
uint32_t subchunk2Size = WAV_DEFAULT_SIZE; // number of bytes to follow
|
||||
|
||||
WaveFileHeader() = default;
|
||||
WAVHeader() = default;
|
||||
|
||||
WaveFileHeader(uint16_t nChannels, uint32_t sRate, uint16_t bps, uint32_t nSamples = 0) {
|
||||
WAVHeader(uint16_t nChannels, uint32_t sRate, uint16_t bps, uint32_t nSamples = 0) {
|
||||
setHeader(nChannels, sRate, bps, nSamples);
|
||||
}
|
||||
|
||||
@@ -59,48 +60,82 @@ struct WaveFileHeader {
|
||||
subchunk2Size = numSamples * numChannels * (bitsPerSample / 8);
|
||||
chunkSize = subchunk2Size + 36;
|
||||
}
|
||||
};
|
||||
|
||||
void write(const std::unique_ptr<std::ostream>& stream) {
|
||||
#pragma pack(pop)
|
||||
|
||||
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 IWAVStream {
|
||||
private:
|
||||
std::unique_ptr<std::istream, std::function<void(std::istream*)>> stream;
|
||||
bool initialized = false;
|
||||
public:
|
||||
WAVHeader header;
|
||||
|
||||
IWAVStream(std::istream& WAVInput)
|
||||
: stream(&WAVInput, [](std::istream*){}) {
|
||||
|
||||
if(initialize()) {
|
||||
initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
IWAVStream(const char* filename)
|
||||
: stream(new std::ifstream(filename, std::ios::in | std::ios::binary), std::default_delete<std::istream>()) {
|
||||
|
||||
if(initialize()) {
|
||||
initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool initialize() {
|
||||
return !(!stream->read(reinterpret_cast<char*>(&header), sizeof(WAVHeader)));
|
||||
}
|
||||
|
||||
operator bool() const {
|
||||
return initialized && stream && stream->good();
|
||||
}
|
||||
|
||||
bool read(char* into, std::streamsize num) {
|
||||
return !(!stream->read(into, num));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set stream immediately after WAV header
|
||||
*/
|
||||
void reset() {
|
||||
if(!(*this)) {
|
||||
return;
|
||||
}
|
||||
|
||||
stream->clear();
|
||||
stream->seekg(sizeof(icsneo::WAVHeader), std::ios::beg);
|
||||
}
|
||||
};
|
||||
|
||||
class StreamOutput : public MessageCallback {
|
||||
public:
|
||||
StreamOutput(std::unique_ptr<std::ostream>&& os, fn_messageCallback cb, std::shared_ptr<MessageFilter> f)
|
||||
: MessageCallback(cb, f), stream(std::move(os)) {}
|
||||
StreamOutput(std::ostream& os, fn_messageCallback cb, std::shared_ptr<MessageFilter> f)
|
||||
: MessageCallback(cb, f), stream(&os, [](std::ostream*){}) {}
|
||||
|
||||
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);
|
||||
}
|
||||
:
|
||||
MessageCallback(cb, f),
|
||||
stream(
|
||||
new std::ofstream(filename, std::ios::binary),
|
||||
std::default_delete<std::ostream>()
|
||||
) {}
|
||||
|
||||
StreamOutput(const char* filename) : MessageCallback([](std::shared_ptr<Message> msg) {}) {
|
||||
stream = std::make_unique<std::ofstream>(filename, std::ios::binary);
|
||||
}
|
||||
StreamOutput(const char* filename) :
|
||||
MessageCallback([](std::shared_ptr<Message> msg) {}),
|
||||
stream(
|
||||
new std::ofstream(filename, std::ios::binary),
|
||||
std::default_delete<std::ostream>()
|
||||
) {}
|
||||
|
||||
StreamOutput(std::unique_ptr<std::ostream>&& os) : MessageCallback([](std::shared_ptr<Message> msg) {}), stream(std::move(os)) {}
|
||||
StreamOutput(std::ostream& os) : MessageCallback([](std::shared_ptr<Message> msg) {}), stream(&os, [](std::ostream*){}) {}
|
||||
|
||||
protected:
|
||||
std::unique_ptr<std::ostream> stream;
|
||||
|
||||
void write(void* msg, std::streamsize size) const {
|
||||
stream->write(reinterpret_cast<const char*>(msg), size);
|
||||
}
|
||||
std::unique_ptr<std::ostream, std::function<void(std::ostream*)>> stream;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
namespace icsneo {
|
||||
|
||||
class ExtendedDataMessage : public RawMessage {
|
||||
class ExtendedDataMessage : public Frame {
|
||||
public:
|
||||
#pragma pack(push, 2)
|
||||
struct ExtendedDataHeader {
|
||||
@@ -23,7 +23,7 @@ public:
|
||||
static constexpr size_t MaxExtendedDataBufferSize = 2048;
|
||||
const ExtendedDataHeader header;
|
||||
|
||||
ExtendedDataMessage(ExtendedDataHeader params) : RawMessage(Message::Type::RawMessage, Network::NetID::ExtendedData), header{params} {}
|
||||
ExtendedDataMessage(ExtendedDataHeader params) : header{params} {}
|
||||
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user