Support for various sample formats (#11)

* wip : support for various sample formats

* finalize support for various sample formats

* adding more tests

* update python bindings

* add "string" header
This commit is contained in:
Georgi Gerganov
2021-01-23 11:45:20 +02:00
committed by GitHub
parent 440a87807e
commit a64106783f
9 changed files with 473 additions and 136 deletions

View File

@@ -164,12 +164,6 @@ sudo snap connect waver:audio-record :audio-record
brew install ggerganov/ggerganov/waver
```
## Todo
- [ ] Improve library interface
- [ ] Support for non-float32 input and non-int16 output
- [x] Mobile app examples
[changelog]: ./CHANGELOG.md
[changelog-badge]: https://img.shields.io/badge/changelog-ggwave%20v0.1-dummy
[license]: ./LICENSE

View File

@@ -1,6 +1,10 @@
cdef extern from "ggwave.h" nogil:
ctypedef enum ggwave_SampleFormat:
GGWAVE_SAMPLE_FORMAT_UNDEFINED,
GGWAVE_SAMPLE_FORMAT_U8,
GGWAVE_SAMPLE_FORMAT_I8,
GGWAVE_SAMPLE_FORMAT_U16,
GGWAVE_SAMPLE_FORMAT_I16,
GGWAVE_SAMPLE_FORMAT_F32
@@ -16,12 +20,12 @@ cdef extern from "ggwave.h" nogil:
int sampleRateIn
int sampleRateOut
int samplesPerFrame
ggwave_SampleFormat formatIn
ggwave_SampleFormat formatOut
ggwave_SampleFormat sampleFormatIn
ggwave_SampleFormat sampleFormatOut
ctypedef int ggwave_Instance
ggwave_Parameters ggwave_defaultParameters();
ggwave_Parameters ggwave_getDefaultParameters();
ggwave_Instance ggwave_init(const ggwave_Parameters parameters);

View File

@@ -6,12 +6,12 @@ import struct
cimport cggwave
def defaultParameters():
return cggwave.ggwave_defaultParameters()
def getDefaultParameters():
return cggwave.ggwave_getDefaultParameters()
def init(parameters = None):
if (parameters is None):
parameters = defaultParameters()
parameters = getDefaultParameters()
return cggwave.ggwave_init(parameters)
@@ -33,7 +33,7 @@ def encode(payload, txProtocolId = 1, volume = 10, instance = None):
own = False
if (instance is None):
own = True
instance = init(defaultParameters())
instance = init(getDefaultParameters())
n = cggwave.ggwave_encode(instance, cdata, len(data_bytes), txProtocolId, volume, coutput)

View File

@@ -188,48 +188,37 @@ bool GGWave_init(
}
}
int sampleSizeBytesIn = 4;
int sampleSizeBytesOut = 2;
GGWave::SampleFormat sampleFormatIn = GGWAVE_SAMPLE_FORMAT_UNDEFINED;
GGWave::SampleFormat sampleFormatOut = GGWAVE_SAMPLE_FORMAT_UNDEFINED;
switch (g_obtainedSpecIn.format) {
case AUDIO_U8:
case AUDIO_S8:
sampleSizeBytesIn = 1;
break;
case AUDIO_U16SYS:
case AUDIO_S16SYS:
sampleSizeBytesIn = 2;
break;
case AUDIO_S32SYS:
case AUDIO_F32SYS:
sampleSizeBytesIn = 4;
break;
case AUDIO_U8: sampleFormatIn = GGWAVE_SAMPLE_FORMAT_U8; break;
case AUDIO_S8: sampleFormatIn = GGWAVE_SAMPLE_FORMAT_I8; break;
case AUDIO_U16SYS: sampleFormatIn = GGWAVE_SAMPLE_FORMAT_U16; break;
case AUDIO_S16SYS: sampleFormatIn = GGWAVE_SAMPLE_FORMAT_I16; break;
case AUDIO_S32SYS: sampleFormatIn = GGWAVE_SAMPLE_FORMAT_F32; break;
case AUDIO_F32SYS: sampleFormatIn = GGWAVE_SAMPLE_FORMAT_F32; break;
}
switch (g_obtainedSpecOut.format) {
case AUDIO_U8:
case AUDIO_S8:
sampleSizeBytesOut = 1;
break;
case AUDIO_U16SYS:
case AUDIO_S16SYS:
sampleSizeBytesOut = 2;
break;
case AUDIO_S32SYS:
case AUDIO_F32SYS:
sampleSizeBytesOut = 4;
case AUDIO_U8: sampleFormatOut = GGWAVE_SAMPLE_FORMAT_U8; break;
case AUDIO_S8: sampleFormatOut = GGWAVE_SAMPLE_FORMAT_I8; break;
case AUDIO_U16SYS: sampleFormatOut = GGWAVE_SAMPLE_FORMAT_U16; break;
case AUDIO_S16SYS: sampleFormatOut = GGWAVE_SAMPLE_FORMAT_I16; break;
case AUDIO_S32SYS: sampleFormatOut = GGWAVE_SAMPLE_FORMAT_F32; break;
case AUDIO_F32SYS: sampleFormatOut = GGWAVE_SAMPLE_FORMAT_F32; break;
break;
}
if (reinit) {
if (g_ggWave) delete g_ggWave;
g_ggWave = new GGWave(
g_ggWave = new GGWave({
g_obtainedSpecIn.freq,
g_obtainedSpecOut.freq,
GGWave::kDefaultSamplesPerFrame,
sampleSizeBytesIn,
sampleSizeBytesOut);
sampleFormatIn,
sampleFormatOut});
}
return true;
@@ -246,7 +235,7 @@ bool GGWave_mainLoop() {
SDL_QueueAudio(g_devIdOut, data, nBytes);
};
static GGWave::CBDequeueAudio CBDequeueAudio = [&](void * data, uint32_t nMaxBytes) {
static GGWave::CBDequeueAudio cbDequeueAudio = [&](void * data, uint32_t nMaxBytes) {
return SDL_DequeueAudio(g_devIdIn, data, nMaxBytes);
};
@@ -259,7 +248,7 @@ bool GGWave_mainLoop() {
if ((int) SDL_GetQueuedAudioSize(g_devIdOut) < g_ggWave->getSamplesPerFrame()*g_ggWave->getSampleSizeBytesOut()) {
SDL_PauseAudioDevice(g_devIdIn, SDL_FALSE);
if (::getTime_ms(tLastNoData, tNow) > 500.0f) {
g_ggWave->decode(CBDequeueAudio);
g_ggWave->decode(cbDequeueAudio);
if ((int) SDL_GetQueuedAudioSize(g_devIdIn) > 32*g_ggWave->getSamplesPerFrame()*g_ggWave->getSampleSizeBytesIn()) {
SDL_ClearQueuedAudio(g_devIdIn);
}

View File

@@ -70,7 +70,7 @@ int main(int argc, char** argv) {
fprintf(stderr, "Generating waveform for message '%s' ...\n", message.c_str());
GGWave ggWave(GGWave::kBaseSampleRate, sampleRateOut, 1024, 4, 2);
GGWave ggWave({ GGWave::kBaseSampleRate, sampleRateOut, 1024, GGWAVE_SAMPLE_FORMAT_F32, GGWAVE_SAMPLE_FORMAT_I16 });
ggWave.init(message.size(), message.data(), ggWave.getTxProtocol(protocolId), volume);
std::vector<char> bufferPCM;

View File

@@ -181,7 +181,7 @@ struct State {
Message message;
GGWave::SpectrumData spectrum;
GGWave::AmplitudeData16 txAmplitudeData;
GGWave::AmplitudeDataI16 txAmplitudeData;
GGWaveStats stats;
};
@@ -563,7 +563,7 @@ void updateCore() {
g_buffer.stateCore.flags.newSpectrum = true;
}
if (g_ggWave->takeTxAmplitudeData16(g_buffer.stateCore.txAmplitudeData)) {
if (g_ggWave->takeTxAmplitudeDataI16(g_buffer.stateCore.txAmplitudeData)) {
g_buffer.stateCore.update = true;
g_buffer.stateCore.flags.newTxAmplitudeData = true;
}
@@ -696,7 +696,7 @@ void renderMain() {
static GGWaveStats statsCurrent;
static GGWave::SpectrumData spectrumCurrent;
static GGWave::AmplitudeData16 txAmplitudeDataCurrent;
static GGWave::AmplitudeDataI16 txAmplitudeDataCurrent;
static std::vector<Message> messageHistory;
if (stateCurrent.update) {

View File

@@ -25,6 +25,10 @@ extern "C" {
// Data format of the audio samples
typedef enum {
GGWAVE_SAMPLE_FORMAT_UNDEFINED,
GGWAVE_SAMPLE_FORMAT_U8,
GGWAVE_SAMPLE_FORMAT_I8,
GGWAVE_SAMPLE_FORMAT_U16,
GGWAVE_SAMPLE_FORMAT_I16,
GGWAVE_SAMPLE_FORMAT_F32,
} ggwave_SampleFormat;
@@ -41,11 +45,11 @@ extern "C" {
// GGWave instance parameters
typedef struct {
int sampleRateIn; // capture sample rate
int sampleRateOut; // playback sample rate
int samplesPerFrame; // number of samples per audio frame
ggwave_SampleFormat formatIn; // format of the captured audio samples
ggwave_SampleFormat formatOut; // format of the playback audio samples
int sampleRateIn; // capture sample rate
int sampleRateOut; // playback sample rate
int samplesPerFrame; // number of samples per audio frame
ggwave_SampleFormat sampleFormatIn; // format of the captured audio samples
ggwave_SampleFormat sampleFormatOut; // format of the playback audio samples
} ggwave_Parameters;
// GGWave instances are identified with an integer and are stored
@@ -54,7 +58,7 @@ extern "C" {
typedef int ggwave_Instance;
// Helper method to get default instance parameters
GGWAVE_API ggwave_Parameters ggwave_defaultParameters(void);
GGWAVE_API ggwave_Parameters ggwave_getDefaultParameters(void);
// Create a new GGWave instance with the specified parameters
GGWAVE_API ggwave_Instance ggwave_init(const ggwave_Parameters parameters);
@@ -120,6 +124,7 @@ extern "C" {
#include <functional>
#include <vector>
#include <map>
#include <string>
class GGWave {
public:
@@ -133,7 +138,9 @@ public:
static constexpr auto kMaxSpectrumHistory = 4;
static constexpr auto kMaxRecordedFrames = 1024;
using TxProtocolId = ggwave_TxProtocolId;
using Parameters = ggwave_Parameters;
using SampleFormat = ggwave_SampleFormat;
using TxProtocolId = ggwave_TxProtocolId;
struct TxProtocol {
const char * name; // string identifier of the protocol
@@ -160,26 +167,24 @@ public:
return kTxProtocols;
}
using AmplitudeData = std::vector<float>;
using AmplitudeData16 = std::vector<int16_t>;
using SpectrumData = std::vector<float>;
using RecordedData = std::vector<float>;
using TxRxData = std::vector<std::uint8_t>;
using AmplitudeData = std::vector<float>;
using AmplitudeDataI16 = std::vector<int16_t>;
using SpectrumData = std::vector<float>;
using RecordedData = std::vector<float>;
using TxRxData = std::vector<std::uint8_t>;
using CBEnqueueAudio = std::function<void(const void * data, uint32_t nBytes)>;
using CBDequeueAudio = std::function<uint32_t(void * data, uint32_t nMaxBytes)>;
GGWave(
int sampleRateIn,
int sampleRateOut,
int samplesPerFrame,
int sampleSizeBytesIn,
int sampleSizeBytesOut);
GGWave(const Parameters & parameters);
~GGWave();
static const Parameters & getDefaultParameters();
bool init(const std::string & text, const int volume = kDefaultVolume);
bool init(const std::string & text, const TxProtocol & txProtocol, const int volume = kDefaultVolume);
bool init(int dataSize, const char * dataBuffer, const int volume = kDefaultVolume);
bool init(int dataSize, const char * dataBuffer, const TxProtocol & aProtocol, const int volume = kDefaultVolume);
bool init(int dataSize, const char * dataBuffer, const TxProtocol & txProtocol, const int volume = kDefaultVolume);
bool encode(const CBEnqueueAudio & cbEnqueueAudio);
void decode(const CBDequeueAudio & cbDequeueAudio);
@@ -209,7 +214,7 @@ public:
const TxProtocolId & getRxProtocolId() const { return m_rxProtocolId; }
int takeRxData(TxRxData & dst);
int takeTxAmplitudeData16(AmplitudeData16 & dst);
int takeTxAmplitudeDataI16(AmplitudeDataI16 & dst);
bool takeSpectrum(SpectrumData & dst);
private:
@@ -226,6 +231,8 @@ private:
const float m_isamplesPerFrame;
const int m_sampleSizeBytesIn;
const int m_sampleSizeBytesOut;
const SampleFormat m_sampleFormatIn;
const SampleFormat m_sampleFormatOut;
const float m_hzPerSample;
const float m_ihzPerSample;
@@ -249,6 +256,7 @@ private:
int m_framesLeftToRecord;
int m_framesToAnalyze;
int m_framesToRecord;
int m_samplesNeeded;
std::vector<float> m_fftIn; // real
std::vector<float> m_fftOut; // complex
@@ -256,6 +264,7 @@ private:
bool m_hasNewSpectrum;
SpectrumData m_sampleSpectrum;
AmplitudeData m_sampleAmplitude;
TxRxData m_sampleAmplitudeTmp;
bool m_hasNewRxData;
int m_lastRxDataLength;
@@ -282,8 +291,9 @@ private:
TxProtocol m_txProtocol;
AmplitudeData m_outputBlock;
AmplitudeData16 m_outputBlock16;
AmplitudeData16 m_txAmplitudeData16;
TxRxData m_outputBlockTmp;
AmplitudeDataI16 m_outputBlockI16;
AmplitudeDataI16 m_txAmplitudeDataI16;
};
#endif

View File

@@ -3,8 +3,9 @@
#include "reed-solomon/rs.hpp"
#include <chrono>
#include <cmath>
#include <algorithm>
#include <random>
//#include <random>
#include <stdexcept>
#include <map>
@@ -17,27 +18,20 @@ std::map<ggwave_Instance, GGWave *> g_instances;
}
extern "C"
ggwave_Parameters ggwave_defaultParameters(void) {
ggwave_Parameters result {
GGWave::kBaseSampleRate,
GGWave::kBaseSampleRate,
GGWave::kDefaultSamplesPerFrame,
GGWAVE_SAMPLE_FORMAT_F32,
GGWAVE_SAMPLE_FORMAT_I16
};
return result;
ggwave_Parameters ggwave_getDefaultParameters(void) {
return GGWave::getDefaultParameters();
}
extern "C"
ggwave_Instance ggwave_init(const ggwave_Parameters parameters) {
static ggwave_Instance curId = 0;
g_instances[curId] = new GGWave(
g_instances[curId] = new GGWave({
parameters.sampleRateIn,
parameters.sampleRateOut,
parameters.samplesPerFrame,
4, // todo : hardcoded sample sizes
2);
GGWAVE_SAMPLE_FORMAT_F32,
GGWAVE_SAMPLE_FORMAT_I16});
return curId++;
}
@@ -74,8 +68,7 @@ int ggwave_encode(
char * p = (char *) data;
std::copy(p, p + nBytes, outputBuffer);
// todo : tmp assume int16
nSamples = nBytes/2;
nSamples = nBytes/ggWave->getSampleSizeBytesOut();
};
if (ggWave->encode(cbEnqueueAudio) == false) {
@@ -242,21 +235,45 @@ int getECCBytesForLength(int len) {
return std::max(4, 2*(len/5));
}
int bytesForSampleFormat(GGWave::SampleFormat sampleFormat) {
switch (sampleFormat) {
case GGWAVE_SAMPLE_FORMAT_UNDEFINED: return 0; break;
case GGWAVE_SAMPLE_FORMAT_U8: return sizeof(uint8_t); break;
case GGWAVE_SAMPLE_FORMAT_I8: return sizeof(int8_t); break;
case GGWAVE_SAMPLE_FORMAT_U16: return sizeof(uint16_t); break;
case GGWAVE_SAMPLE_FORMAT_I16: return sizeof(int16_t); break;
case GGWAVE_SAMPLE_FORMAT_F32: return sizeof(float); break;
};
fprintf(stderr, "Invalid sample format: %d\n", (int) sampleFormat);
return 0;
}
GGWave::GGWave(
int sampleRateIn,
int sampleRateOut,
int samplesPerFrame,
int sampleSizeBytesIn,
int sampleSizeBytesOut) :
m_sampleRateIn(sampleRateIn),
m_sampleRateOut(sampleRateOut),
m_samplesPerFrame(samplesPerFrame),
}
const GGWave::Parameters & GGWave::getDefaultParameters() {
static ggwave_Parameters result {
GGWave::kBaseSampleRate,
GGWave::kBaseSampleRate,
GGWave::kDefaultSamplesPerFrame,
GGWAVE_SAMPLE_FORMAT_F32,
GGWAVE_SAMPLE_FORMAT_I16
};
return result;
}
GGWave::GGWave(const Parameters & parameters) :
m_sampleRateIn(parameters.sampleRateIn),
m_sampleRateOut(parameters.sampleRateOut),
m_samplesPerFrame(parameters.samplesPerFrame),
m_isamplesPerFrame(1.0f/m_samplesPerFrame),
m_sampleSizeBytesIn(sampleSizeBytesIn),
m_sampleSizeBytesOut(sampleSizeBytesOut),
m_hzPerSample(m_sampleRateIn/samplesPerFrame),
m_sampleSizeBytesIn(bytesForSampleFormat(parameters.sampleFormatIn)),
m_sampleSizeBytesOut(bytesForSampleFormat(parameters.sampleFormatOut)),
m_sampleFormatIn(parameters.sampleFormatIn),
m_sampleFormatOut(parameters.sampleFormatOut),
m_hzPerSample(m_sampleRateIn/parameters.samplesPerFrame),
m_ihzPerSample(1.0f/m_hzPerSample),
m_freqDelta_bin(1),
m_freqDelta_hz(2*m_hzPerSample),
@@ -264,11 +281,13 @@ GGWave::GGWave(
m_nMarkerFrames(16),
m_nPostMarkerFrames(0),
m_encodedDataOffset(3),
m_samplesNeeded(m_samplesPerFrame),
m_fftIn(kMaxSamplesPerFrame),
m_fftOut(2*kMaxSamplesPerFrame),
m_hasNewSpectrum(false),
m_sampleSpectrum(kMaxSamplesPerFrame),
m_sampleAmplitude(kMaxSamplesPerFrame),
m_sampleAmplitudeTmp(kMaxSamplesPerFrame*m_sampleSizeBytesIn),
m_hasNewRxData(false),
m_lastRxDataLength(0),
m_rxData(kMaxDataSize),
@@ -278,23 +297,40 @@ GGWave::GGWave(
m_txData(kMaxDataSize),
m_txDataEncoded(kMaxDataSize),
m_outputBlock(kMaxSamplesPerFrame),
m_outputBlock16(kMaxRecordedFrames*kMaxSamplesPerFrame)
{
if (samplesPerFrame > kMaxSamplesPerFrame) {
m_outputBlockTmp(kMaxRecordedFrames*kMaxSamplesPerFrame*m_sampleSizeBytesOut),
m_outputBlockI16(kMaxRecordedFrames*kMaxSamplesPerFrame) {
if (m_sampleSizeBytesIn == 0) {
throw std::runtime_error("Invalid or unsupported capture sample format");
}
if (m_sampleSizeBytesOut == 0) {
throw std::runtime_error("Invalid or unsupported playback sample format");
}
if (parameters.samplesPerFrame > kMaxSamplesPerFrame) {
throw std::runtime_error("Invalid samples per frame");
}
init(0, "", getDefaultTxProtocol(), 0);
init("", getDefaultTxProtocol(), 0);
}
GGWave::~GGWave() {
}
bool GGWave::init(const std::string & text, const int volume) {
return init(text.size(), text.data(), getDefaultTxProtocol(), volume);
}
bool GGWave::init(const std::string & text, const TxProtocol & txProtocol, const int volume) {
return init(text.size(), text.data(), txProtocol, volume);
}
bool GGWave::init(int dataSize, const char * dataBuffer, const int volume) {
return init(dataSize, dataBuffer, getDefaultTxProtocol(), volume);
}
bool GGWave::init(int dataSize, const char * dataBuffer, const TxProtocol & aProtocol, const int volume) {
bool GGWave::init(int dataSize, const char * dataBuffer, const TxProtocol & txProtocol, const int volume) {
if (dataSize < 0) {
fprintf(stderr, "Negative data size: %d\n", dataSize);
return false;
@@ -310,7 +346,7 @@ bool GGWave::init(int dataSize, const char * dataBuffer, const TxProtocol & aPro
return false;
}
m_txProtocol = aProtocol;
m_txProtocol = txProtocol;
m_txDataLength = dataSize;
m_sendVolume = ((double)(volume))/100.0f;
@@ -372,10 +408,10 @@ bool GGWave::encode(const CBEnqueueAudio & cbEnqueueAudio) {
}
// note : what is the purpose of this shuffle ? I forgot .. :(
std::random_device rd;
std::mt19937 g(rd());
//std::random_device rd;
//std::mt19937 g(rd());
std::shuffle(phaseOffsets.begin(), phaseOffsets.end(), g);
//std::shuffle(phaseOffsets.begin(), phaseOffsets.end(), g);
std::vector<bool> dataBits(kMaxDataBits);
@@ -507,19 +543,75 @@ bool GGWave::encode(const CBEnqueueAudio & cbEnqueueAudio) {
m_outputBlock[i] *= scale;
}
// todo : support for non-int16 output
uint32_t offset = frameId*samplesPerFrameOut;
// default output is in 16-bit signed int so we always compute it
for (int i = 0; i < samplesPerFrameOut; ++i) {
m_outputBlock16[frameId*samplesPerFrameOut + i] = std::round(32000.0*m_outputBlock[i]);
m_outputBlockI16[offset + i] = 32768*m_outputBlock[i];
}
// convert from 32-bit float
switch (m_sampleFormatOut) {
case GGWAVE_SAMPLE_FORMAT_UNDEFINED: break;
case GGWAVE_SAMPLE_FORMAT_U8:
{
auto p = reinterpret_cast<uint8_t *>(m_outputBlockTmp.data());
for (int i = 0; i < samplesPerFrameOut; ++i) {
p[offset + i] = 128*(m_outputBlock[i] + 1.0f);
}
} break;
case GGWAVE_SAMPLE_FORMAT_I8:
{
auto p = reinterpret_cast<uint8_t *>(m_outputBlockTmp.data());
for (int i = 0; i < samplesPerFrameOut; ++i) {
p[offset + i] = 128*m_outputBlock[i];
}
} break;
case GGWAVE_SAMPLE_FORMAT_U16:
{
auto p = reinterpret_cast<uint16_t *>(m_outputBlockTmp.data());
for (int i = 0; i < samplesPerFrameOut; ++i) {
p[offset + i] = 32768*(m_outputBlock[i] + 1.0f);
}
} break;
case GGWAVE_SAMPLE_FORMAT_I16:
{
// skip because we already have the data in m_outputBlockI16
//auto p = reinterpret_cast<uint16_t *>(m_outputBlockTmp.data());
//for (int i = 0; i < samplesPerFrameOut; ++i) {
// p[offset + i] = 32768*m_outputBlock[i];
//}
} break;
case GGWAVE_SAMPLE_FORMAT_F32:
{
auto p = reinterpret_cast<float *>(m_outputBlockTmp.data());
for (int i = 0; i < samplesPerFrameOut; ++i) {
p[offset + i] = m_outputBlock[i];
}
} break;
}
++frameId;
}
cbEnqueueAudio(m_outputBlock16.data(), frameId*samplesPerFrameOut*m_sampleSizeBytesOut);
switch (m_sampleFormatOut) {
case GGWAVE_SAMPLE_FORMAT_UNDEFINED: break;
case GGWAVE_SAMPLE_FORMAT_I16:
{
cbEnqueueAudio(m_outputBlockI16.data(), frameId*samplesPerFrameOut*m_sampleSizeBytesOut);
} break;
case GGWAVE_SAMPLE_FORMAT_U8:
case GGWAVE_SAMPLE_FORMAT_I8:
case GGWAVE_SAMPLE_FORMAT_U16:
case GGWAVE_SAMPLE_FORMAT_F32:
{
cbEnqueueAudio(m_outputBlockTmp.data(), frameId*samplesPerFrameOut*m_sampleSizeBytesOut);
} break;
}
m_txAmplitudeData16.resize(frameId*samplesPerFrameOut);
m_txAmplitudeDataI16.resize(frameId*samplesPerFrameOut);
for (int i = 0; i < frameId*samplesPerFrameOut; ++i) {
m_txAmplitudeData16[i] = m_outputBlock16[i];
m_txAmplitudeDataI16[i] = m_outputBlockI16[i];
}
return true;
@@ -528,11 +620,84 @@ bool GGWave::encode(const CBEnqueueAudio & cbEnqueueAudio) {
void GGWave::decode(const CBDequeueAudio & cbDequeueAudio) {
while (m_hasNewTxData == false) {
// read capture data
//
// todo : support for non-float input
auto nBytesRecorded = cbDequeueAudio(m_sampleAmplitude.data(), m_samplesPerFrame*m_sampleSizeBytesIn);
uint32_t nBytesNeeded = m_samplesNeeded*m_sampleSizeBytesIn;
uint32_t nBytesRecorded = 0;
uint32_t offset = m_samplesPerFrame - m_samplesNeeded;
if (nBytesRecorded != 0) {
switch (m_sampleFormatIn) {
case GGWAVE_SAMPLE_FORMAT_UNDEFINED: break;
case GGWAVE_SAMPLE_FORMAT_U8:
case GGWAVE_SAMPLE_FORMAT_I8:
case GGWAVE_SAMPLE_FORMAT_U16:
case GGWAVE_SAMPLE_FORMAT_I16:
{
nBytesRecorded = cbDequeueAudio(m_sampleAmplitudeTmp.data() + offset, nBytesNeeded);
} break;
case GGWAVE_SAMPLE_FORMAT_F32:
{
nBytesRecorded = cbDequeueAudio(m_sampleAmplitude.data() + offset, nBytesNeeded);
} break;
}
if (nBytesRecorded % m_sampleSizeBytesIn != 0) {
fprintf(stderr, "Failure during capture - provided bytes (%d) are not multiple of sample size (%d)\n",
nBytesRecorded, m_sampleSizeBytesIn);
m_samplesNeeded = m_samplesPerFrame;
break;
}
if (nBytesRecorded > nBytesNeeded) {
fprintf(stderr, "Failure during capture - more samples were provided (%d) than requested (%d)\n",
nBytesRecorded/m_sampleSizeBytesIn, nBytesNeeded/m_sampleSizeBytesIn);
m_samplesNeeded = m_samplesPerFrame;
break;
}
// convert to 32-bit float
switch (m_sampleFormatIn) {
case GGWAVE_SAMPLE_FORMAT_UNDEFINED: break;
case GGWAVE_SAMPLE_FORMAT_U8:
{
constexpr float scale = 1.0f/128;
int nSamplesRecorded = nBytesRecorded/m_sampleSizeBytesIn;
auto p = reinterpret_cast<uint8_t *>(m_sampleAmplitudeTmp.data());
for (int i = 0; i < nSamplesRecorded; ++i) {
m_sampleAmplitude[offset + i] = float(int16_t(*(p + offset + i)) - 128)*scale;
}
} break;
case GGWAVE_SAMPLE_FORMAT_I8:
{
constexpr float scale = 1.0f/128;
int nSamplesRecorded = nBytesRecorded/m_sampleSizeBytesIn;
auto p = reinterpret_cast<int8_t *>(m_sampleAmplitudeTmp.data());
for (int i = 0; i < nSamplesRecorded; ++i) {
m_sampleAmplitude[offset + i] = float(*(p + offset + i))*scale;
}
} break;
case GGWAVE_SAMPLE_FORMAT_U16:
{
constexpr float scale = 1.0f/32768;
int nSamplesRecorded = nBytesRecorded/m_sampleSizeBytesIn;
auto p = reinterpret_cast<uint16_t *>(m_sampleAmplitudeTmp.data());
for (int i = 0; i < nSamplesRecorded; ++i) {
m_sampleAmplitude[offset + i] = float(int32_t(*(p + offset + i)) - 32768)*scale;
}
} break;
case GGWAVE_SAMPLE_FORMAT_I16:
{
constexpr float scale = 1.0f/32768;
int nSamplesRecorded = nBytesRecorded/m_sampleSizeBytesIn;
auto p = reinterpret_cast<int16_t *>(m_sampleAmplitudeTmp.data());
for (int i = 0; i < nSamplesRecorded; ++i) {
m_sampleAmplitude[offset + i] = float(*(p + offset + i))*scale;
}
} break;
case GGWAVE_SAMPLE_FORMAT_F32: break;
}
// we have enough bytes to do analysis
if (nBytesRecorded == nBytesNeeded) {
m_samplesNeeded = m_samplesPerFrame;
m_sampleAmplitudeHistory[m_historyId] = m_sampleAmplitude;
if (++m_historyId >= kMaxSpectrumHistory) {
@@ -784,6 +949,7 @@ void GGWave::decode(const CBDequeueAudio & cbDequeueAudio) {
}
}
} else {
m_samplesNeeded -= nBytesRecorded/m_sampleSizeBytesIn;
break;
}
}
@@ -802,11 +968,11 @@ int GGWave::takeRxData(TxRxData & dst) {
return res;
}
int GGWave::takeTxAmplitudeData16(AmplitudeData16 & dst) {
if (m_txAmplitudeData16.size() == 0) return 0;
int GGWave::takeTxAmplitudeDataI16(AmplitudeDataI16 & dst) {
if (m_txAmplitudeDataI16.size() == 0) return 0;
int res = (int) m_txAmplitudeData16.size();
dst = std::move(m_txAmplitudeData16);
int res = (int) m_txAmplitudeDataI16.size();
dst = std::move(m_txAmplitudeDataI16);
return res;
}

View File

@@ -1,6 +1,11 @@
#include "ggwave/ggwave.h"
#include <limits>
#include <string>
#include <typeinfo>
#include <typeindex>
#include <vector>
#include <set>
#define CHECK(cond) \
if (!(cond)) { \
@@ -11,29 +16,198 @@
#define CHECK_T(cond) CHECK(cond)
#define CHECK_F(cond) CHECK(!(cond))
const std::map<std::type_index, float> kSampleScale = {
{ typeid(uint8_t), std::numeric_limits<uint8_t>::max() },
{ typeid(int8_t), std::numeric_limits<int8_t>::max() },
{ typeid(uint16_t), std::numeric_limits<uint16_t>::max() },
{ typeid(int16_t), std::numeric_limits<int16_t>::max() },
{ typeid(float), 1.0f },
};
const std::map<std::type_index, float> kSampleOffset = {
{ typeid(uint8_t), 0.5f*std::numeric_limits<uint8_t>::max() },
{ typeid(int8_t), 0.0f },
{ typeid(uint16_t), 0.5f*std::numeric_limits<uint16_t>::max() },
{ typeid(int16_t), 0.0f },
{ typeid(float), 0.0f },
};
const std::set<GGWave::SampleFormat> kFormats = {
GGWAVE_SAMPLE_FORMAT_U8,
GGWAVE_SAMPLE_FORMAT_I8,
GGWAVE_SAMPLE_FORMAT_U16,
GGWAVE_SAMPLE_FORMAT_I16,
GGWAVE_SAMPLE_FORMAT_F32,
};
template <typename T>
GGWave::CBEnqueueAudio getCBEnqueueAudio(uint32_t & nSamples, std::vector<T> & buffer) {
return [&nSamples, &buffer](const void * data, uint32_t nBytes) {
nSamples = nBytes/sizeof(T);
CHECK(nSamples*sizeof(T) == nBytes);
buffer.resize(nSamples);
std::copy((char *) data, (char *) data + nBytes, (char *) buffer.data());
};
}
template <typename T>
GGWave::CBDequeueAudio getCBDequeueAudio(uint32_t & nSamples, std::vector<T> & buffer) {
return [&nSamples, &buffer](void * data, uint32_t nMaxBytes) {
uint32_t nCopied = std::min((uint32_t) (nSamples*sizeof(T)), nMaxBytes);
const char * p = (char *) (buffer.data() + buffer.size() - nSamples);
std::copy(p, p + nCopied, (char *) data);
nSamples -= nCopied/sizeof(T);
return nCopied;
};
}
template <typename S, typename D>
void convert(const std::vector<S> & src, std::vector<D> & dst) {
int n = src.size();
dst.resize(n);
for (int i = 0; i < n; ++i) {
dst[i] = ((float(src[i]) - kSampleOffset.at(typeid(S)))/kSampleScale.at(typeid(S)))*kSampleScale.at(typeid(D)) + kSampleOffset.at(typeid(D));
}
}
int main() {
GGWave instance(48000, 48000, 1024, 4, 2);
std::vector<uint8_t> bufferU8;
std::vector<int8_t> bufferI8;
std::vector<uint16_t> bufferU16;
std::vector<int16_t> bufferI16;
std::vector<float> bufferF32;
std::string payload = "hello";
auto convertHelper = [&](GGWave::SampleFormat formatOut, GGWave::SampleFormat formatIn) {
switch (formatOut) {
case GGWAVE_SAMPLE_FORMAT_UNDEFINED: CHECK(false); break;
case GGWAVE_SAMPLE_FORMAT_U8:
{
switch (formatIn) {
case GGWAVE_SAMPLE_FORMAT_UNDEFINED: CHECK(false); break;
case GGWAVE_SAMPLE_FORMAT_U8: break;
case GGWAVE_SAMPLE_FORMAT_I8: convert(bufferU8, bufferI8); break;
case GGWAVE_SAMPLE_FORMAT_U16: convert(bufferU8, bufferU16); break;
case GGWAVE_SAMPLE_FORMAT_I16: convert(bufferU8, bufferI16); break;
case GGWAVE_SAMPLE_FORMAT_F32: convert(bufferU8, bufferF32); break;
};
} break;
case GGWAVE_SAMPLE_FORMAT_I8:
{
switch (formatIn) {
case GGWAVE_SAMPLE_FORMAT_UNDEFINED: CHECK(false); break;
case GGWAVE_SAMPLE_FORMAT_U8: convert(bufferI8, bufferU8); break;
case GGWAVE_SAMPLE_FORMAT_I8: break;
case GGWAVE_SAMPLE_FORMAT_U16: convert(bufferI8, bufferU16); break;
case GGWAVE_SAMPLE_FORMAT_I16: convert(bufferI8, bufferI16); break;
case GGWAVE_SAMPLE_FORMAT_F32: convert(bufferI8, bufferF32); break;
};
} break;
case GGWAVE_SAMPLE_FORMAT_U16:
{
switch (formatIn) {
case GGWAVE_SAMPLE_FORMAT_UNDEFINED: CHECK(false); break;
case GGWAVE_SAMPLE_FORMAT_U8: convert(bufferU16, bufferU8); break;
case GGWAVE_SAMPLE_FORMAT_I8: convert(bufferU16, bufferI8); break;
case GGWAVE_SAMPLE_FORMAT_U16: break;
case GGWAVE_SAMPLE_FORMAT_I16: convert(bufferU16, bufferI16); break;
case GGWAVE_SAMPLE_FORMAT_F32: convert(bufferU16, bufferF32); break;
};
} break;
case GGWAVE_SAMPLE_FORMAT_I16:
{
switch (formatIn) {
case GGWAVE_SAMPLE_FORMAT_UNDEFINED: CHECK(false); break;
case GGWAVE_SAMPLE_FORMAT_U8: convert(bufferI16, bufferU8); break;
case GGWAVE_SAMPLE_FORMAT_I8: convert(bufferI16, bufferI8); break;
case GGWAVE_SAMPLE_FORMAT_U16: convert(bufferI16, bufferU16); break;
case GGWAVE_SAMPLE_FORMAT_I16: break;
case GGWAVE_SAMPLE_FORMAT_F32: convert(bufferI16, bufferF32); break;
};
} break;
case GGWAVE_SAMPLE_FORMAT_F32:
{
switch (formatIn) {
case GGWAVE_SAMPLE_FORMAT_UNDEFINED: CHECK(false); break;
case GGWAVE_SAMPLE_FORMAT_U8: convert(bufferF32, bufferU8); break;
case GGWAVE_SAMPLE_FORMAT_I8: convert(bufferF32, bufferI8); break;
case GGWAVE_SAMPLE_FORMAT_U16: convert(bufferF32, bufferU16); break;
case GGWAVE_SAMPLE_FORMAT_I16: convert(bufferF32, bufferI16); break;
case GGWAVE_SAMPLE_FORMAT_F32: break;
};
} break;
};
CHECK(instance.init(payload.size(), payload.c_str()));
};
// data
CHECK_F(instance.init(-1, "asd"));
CHECK_T(instance.init(0, nullptr));
CHECK_T(instance.init(0, "asd"));
CHECK_T(instance.init(1, "asd"));
CHECK_T(instance.init(2, "asd"));
CHECK_T(instance.init(3, "asd"));
uint32_t nSamples = 0;
// volume
CHECK_F(instance.init(payload.size(), payload.c_str(), -1));
CHECK_T(instance.init(payload.size(), payload.c_str(), 0));
CHECK_T(instance.init(payload.size(), payload.c_str(), 50));
CHECK_T(instance.init(payload.size(), payload.c_str(), 100));
CHECK_F(instance.init(payload.size(), payload.c_str(), 101));
const std::map<GGWave::SampleFormat, GGWave::CBEnqueueAudio> kCBEnqueueAudio = {
{ GGWAVE_SAMPLE_FORMAT_U8, getCBEnqueueAudio(nSamples, bufferU8) },
{ GGWAVE_SAMPLE_FORMAT_I8, getCBEnqueueAudio(nSamples, bufferI8) },
{ GGWAVE_SAMPLE_FORMAT_U16, getCBEnqueueAudio(nSamples, bufferU16) },
{ GGWAVE_SAMPLE_FORMAT_I16, getCBEnqueueAudio(nSamples, bufferI16) },
{ GGWAVE_SAMPLE_FORMAT_F32, getCBEnqueueAudio(nSamples, bufferF32) },
};
// todo ..
const std::map<GGWave::SampleFormat, GGWave::CBDequeueAudio> kCBDequeueAudio = {
{ GGWAVE_SAMPLE_FORMAT_U8, getCBDequeueAudio(nSamples, bufferU8) },
{ GGWAVE_SAMPLE_FORMAT_I8, getCBDequeueAudio(nSamples, bufferI8) },
{ GGWAVE_SAMPLE_FORMAT_U16, getCBDequeueAudio(nSamples, bufferU16) },
{ GGWAVE_SAMPLE_FORMAT_I16, getCBDequeueAudio(nSamples, bufferI16) },
{ GGWAVE_SAMPLE_FORMAT_F32, getCBDequeueAudio(nSamples, bufferF32) },
};
{
GGWave instance(GGWave::getDefaultParameters());
std::string payload = "hello";
CHECK(instance.init(payload));
// data
CHECK_F(instance.init(-1, "asd"));
CHECK_T(instance.init(0, nullptr));
CHECK_T(instance.init(0, "asd"));
CHECK_T(instance.init(1, "asd"));
CHECK_T(instance.init(2, "asd"));
CHECK_T(instance.init(3, "asd"));
// volume
CHECK_F(instance.init(payload.size(), payload.c_str(), -1));
CHECK_T(instance.init(payload.size(), payload.c_str(), 0));
CHECK_T(instance.init(payload.size(), payload.c_str(), 50));
CHECK_T(instance.init(payload.size(), payload.c_str(), 100));
CHECK_F(instance.init(payload.size(), payload.c_str(), 101));
}
for (const auto & txProtocol : GGWave::getTxProtocols()) {
for (const auto & formatOut : kFormats) {
for (const auto & formatIn : kFormats) {
printf("Testing: protocol = %s, in = %d, out = %d\n", txProtocol.second.name, formatIn, formatOut);
auto parameters = GGWave::getDefaultParameters();
parameters.sampleFormatIn = formatIn;
parameters.sampleFormatOut = formatOut;
GGWave instance(parameters);
std::string payload = "test message xxxxxxxxxxxx";
instance.init(payload, txProtocol.second, 25);
instance.encode(kCBEnqueueAudio.at(formatOut));
convertHelper(formatOut, formatIn);
instance.decode(kCBDequeueAudio.at(formatIn));
{
GGWave::TxRxData result;
CHECK(instance.takeRxData(result) == (int) payload.size());
for (int i = 0; i < (int) payload.size(); ++i) {
CHECK(payload[i] == result[i]);
}
}
}
}
}
return 0;
}