mirror of
https://github.com/ggerganov/ggwave.git
synced 2026-02-06 08:37:59 +08:00
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:
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
264
src/ggwave.cpp
264
src/ggwave.cpp
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user