mirror of
https://github.com/ggerganov/ggwave.git
synced 2026-02-07 01:11:22 +08:00
322 lines
15 KiB
C++
322 lines
15 KiB
C++
#include "ggwave/ggwave.h"
|
|
|
|
#include <cstring>
|
|
#include <limits>
|
|
#include <string>
|
|
#include <typeinfo>
|
|
#include <typeindex>
|
|
#include <vector>
|
|
#include <set>
|
|
#include <cstdint>
|
|
#include <map>
|
|
|
|
constexpr float iRandMax = 1.0f/float(RAND_MAX);
|
|
float frand() { return float(rand()%RAND_MAX)*iRandMax; }
|
|
|
|
#define CHECK(cond) \
|
|
if (!(cond)) { \
|
|
fprintf(stderr, "[%s:%d] Check failed: %s\n", __FILE__, __LINE__, #cond); \
|
|
exit(1); \
|
|
}
|
|
|
|
#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 S, typename D>
|
|
void convert(std::vector<uint8_t> & src) {
|
|
const int n = src.size()/sizeof(S);
|
|
std::vector<D> dst(n);
|
|
S v;
|
|
for (int i = 0; i < n; ++i) {
|
|
std::memcpy(&v, &src[i*sizeof(S)], sizeof(S));
|
|
dst[i] = ((float(v) - kSampleOffset.at(typeid(S)))/kSampleScale.at(typeid(S)))*kSampleScale.at(typeid(D)) + kSampleOffset.at(typeid(D));
|
|
}
|
|
|
|
src.resize(n*sizeof(D));
|
|
std::memcpy(&src[0], &dst[0], n*sizeof(D));
|
|
}
|
|
|
|
int main(int argc, char ** argv) {
|
|
bool full = false;
|
|
if (argc > 1) {
|
|
if (strcmp(argv[1], "--full") == 0) {
|
|
full = true;
|
|
}
|
|
}
|
|
|
|
std::vector<uint8_t> buffer;
|
|
|
|
auto convertHelper = [&](GGWave::SampleFormat formatOut, GGWave::SampleFormat formatInp) {
|
|
switch (formatOut) {
|
|
case GGWAVE_SAMPLE_FORMAT_UNDEFINED: CHECK(false); break;
|
|
case GGWAVE_SAMPLE_FORMAT_U8:
|
|
{
|
|
switch (formatInp) {
|
|
case GGWAVE_SAMPLE_FORMAT_UNDEFINED: CHECK(false); break;
|
|
case GGWAVE_SAMPLE_FORMAT_U8: break;
|
|
case GGWAVE_SAMPLE_FORMAT_I8: convert<uint8_t, int8_t> (buffer); break;
|
|
case GGWAVE_SAMPLE_FORMAT_U16: convert<uint8_t, uint16_t>(buffer); break;
|
|
case GGWAVE_SAMPLE_FORMAT_I16: convert<uint8_t, int16_t> (buffer); break;
|
|
case GGWAVE_SAMPLE_FORMAT_F32: convert<uint8_t, float> (buffer); break;
|
|
};
|
|
} break;
|
|
case GGWAVE_SAMPLE_FORMAT_I8:
|
|
{
|
|
switch (formatInp) {
|
|
case GGWAVE_SAMPLE_FORMAT_UNDEFINED: CHECK(false); break;
|
|
case GGWAVE_SAMPLE_FORMAT_U8: convert<int8_t, uint8_t> (buffer); break;
|
|
case GGWAVE_SAMPLE_FORMAT_I8: break;
|
|
case GGWAVE_SAMPLE_FORMAT_U16: convert<int8_t, uint16_t>(buffer); break;
|
|
case GGWAVE_SAMPLE_FORMAT_I16: convert<int8_t, int16_t> (buffer); break;
|
|
case GGWAVE_SAMPLE_FORMAT_F32: convert<int8_t, float> (buffer); break;
|
|
};
|
|
} break;
|
|
case GGWAVE_SAMPLE_FORMAT_U16:
|
|
{
|
|
switch (formatInp) {
|
|
case GGWAVE_SAMPLE_FORMAT_UNDEFINED: CHECK(false); break;
|
|
case GGWAVE_SAMPLE_FORMAT_U8: convert<uint16_t, uint8_t>(buffer); break;
|
|
case GGWAVE_SAMPLE_FORMAT_I8: convert<uint16_t, int8_t> (buffer); break;
|
|
case GGWAVE_SAMPLE_FORMAT_U16: break;
|
|
case GGWAVE_SAMPLE_FORMAT_I16: convert<uint16_t, int16_t>(buffer); break;
|
|
case GGWAVE_SAMPLE_FORMAT_F32: convert<uint16_t, float> (buffer); break;
|
|
};
|
|
} break;
|
|
case GGWAVE_SAMPLE_FORMAT_I16:
|
|
{
|
|
switch (formatInp) {
|
|
case GGWAVE_SAMPLE_FORMAT_UNDEFINED: CHECK(false); break;
|
|
case GGWAVE_SAMPLE_FORMAT_U8: convert<int16_t, uint8_t> (buffer); break;
|
|
case GGWAVE_SAMPLE_FORMAT_I8: convert<int16_t, int8_t> (buffer); break;
|
|
case GGWAVE_SAMPLE_FORMAT_U16: convert<int16_t, uint16_t>(buffer); break;
|
|
case GGWAVE_SAMPLE_FORMAT_I16: break;
|
|
case GGWAVE_SAMPLE_FORMAT_F32: convert<int16_t, float> (buffer); break;
|
|
};
|
|
} break;
|
|
case GGWAVE_SAMPLE_FORMAT_F32:
|
|
{
|
|
switch (formatInp) {
|
|
case GGWAVE_SAMPLE_FORMAT_UNDEFINED: CHECK(false); break;
|
|
case GGWAVE_SAMPLE_FORMAT_U8: convert<float, uint8_t> (buffer); break;
|
|
case GGWAVE_SAMPLE_FORMAT_I8: convert<float, int8_t> (buffer); break;
|
|
case GGWAVE_SAMPLE_FORMAT_U16: convert<float, uint16_t>(buffer); break;
|
|
case GGWAVE_SAMPLE_FORMAT_I16: convert<float, int16_t> (buffer); break;
|
|
case GGWAVE_SAMPLE_FORMAT_F32: break;
|
|
};
|
|
} break;
|
|
};
|
|
};
|
|
|
|
auto addNoiseHelper = [&](float level, GGWave::SampleFormat format) {
|
|
switch (format) {
|
|
case GGWAVE_SAMPLE_FORMAT_UNDEFINED: CHECK(false); break;
|
|
case GGWAVE_SAMPLE_FORMAT_U8:
|
|
{
|
|
const int n = buffer.size()/sizeof(uint8_t);
|
|
auto p = (uint8_t *) buffer.data();
|
|
for (int i = 0; i < n; ++i) {
|
|
p[i] = std::max(0.0f, std::min(255.0f, (float) p[i] + (frand() - 0.5f)*(level*256)));
|
|
}
|
|
} break;
|
|
case GGWAVE_SAMPLE_FORMAT_I8:
|
|
{
|
|
const int n = buffer.size()/sizeof(int8_t);
|
|
auto p = (int8_t *) buffer.data();
|
|
for (int i = 0; i < n; ++i) {
|
|
p[i] = std::max(-128.0f, std::min(127.0f, (float) p[i] + (frand() - 0.5f)*(level*256)));
|
|
}
|
|
} break;
|
|
case GGWAVE_SAMPLE_FORMAT_U16:
|
|
{
|
|
const int n = buffer.size()/sizeof(uint16_t);
|
|
auto p = (uint16_t *) buffer.data();
|
|
for (int i = 0; i < n; ++i) {
|
|
p[i] = std::max(0.0f, std::min(65535.0f, (float) p[i] + (frand() - 0.5f)*(level*65536)));
|
|
}
|
|
} break;
|
|
case GGWAVE_SAMPLE_FORMAT_I16:
|
|
{
|
|
const int n = buffer.size()/sizeof(int16_t);
|
|
auto p = (int16_t *) buffer.data();
|
|
for (int i = 0; i < n; ++i) {
|
|
p[i] = std::max(-32768.0f, std::min(32767.0f, (float) p[i] + (frand() - 0.5f)*(level*65536)));
|
|
}
|
|
} break;
|
|
case GGWAVE_SAMPLE_FORMAT_F32:
|
|
{
|
|
const int n = buffer.size()/sizeof(float);
|
|
auto p = (float *) buffer.data();
|
|
for (int i = 0; i < n; ++i) {
|
|
p[i] = std::max(-1.0f, std::min(1.0f, p[i] + (frand() - 0.5f)*(level)));
|
|
}
|
|
} break;
|
|
};
|
|
};
|
|
|
|
{
|
|
GGWave instance(GGWave::getDefaultParameters());
|
|
|
|
std::string payload = "hello";
|
|
|
|
CHECK(instance.init(payload.c_str(), GGWAVE_PROTOCOL_AUDIBLE_FAST));
|
|
|
|
// data
|
|
CHECK_F(instance.init(-1, "asd", GGWAVE_PROTOCOL_AUDIBLE_FAST));
|
|
CHECK_T(instance.init(0, nullptr, GGWAVE_PROTOCOL_AUDIBLE_FAST));
|
|
CHECK_T(instance.init(0, "asd", GGWAVE_PROTOCOL_AUDIBLE_FAST));
|
|
CHECK_T(instance.init(1, "asd", GGWAVE_PROTOCOL_AUDIBLE_FAST));
|
|
CHECK_T(instance.init(2, "asd", GGWAVE_PROTOCOL_AUDIBLE_FAST));
|
|
CHECK_T(instance.init(3, "asd", GGWAVE_PROTOCOL_AUDIBLE_FAST));
|
|
|
|
// volume
|
|
CHECK_F(instance.init(payload.size(), payload.c_str(), GGWAVE_PROTOCOL_AUDIBLE_FAST, -1));
|
|
CHECK_T(instance.init(payload.size(), payload.c_str(), GGWAVE_PROTOCOL_AUDIBLE_FAST, 0));
|
|
CHECK_T(instance.init(payload.size(), payload.c_str(), GGWAVE_PROTOCOL_AUDIBLE_FAST, 50));
|
|
CHECK_T(instance.init(payload.size(), payload.c_str(), GGWAVE_PROTOCOL_AUDIBLE_FAST, 100));
|
|
CHECK_F(instance.init(payload.size(), payload.c_str(), GGWAVE_PROTOCOL_AUDIBLE_FAST, 101));
|
|
}
|
|
|
|
// playback / capture at different sample rates
|
|
for (int srInp = GGWave::kDefaultSampleRate/6; srInp <= 2*GGWave::kDefaultSampleRate; srInp += 1371) {
|
|
printf("Testing: sample rate = %d\n", srInp);
|
|
|
|
auto parameters = GGWave::getDefaultParameters();
|
|
parameters.soundMarkerThreshold = 3.0f;
|
|
|
|
const std::string payload = "hello123";
|
|
|
|
// encode
|
|
{
|
|
parameters.sampleRateOut = srInp;
|
|
GGWave instanceOut(parameters);
|
|
|
|
instanceOut.init(payload.c_str(), GGWAVE_PROTOCOL_DT_FASTEST, 25);
|
|
const auto expectedSize = instanceOut.encodeSize_bytes();
|
|
const auto nBytes = instanceOut.encode();
|
|
printf("Expected = %d, actual = %d\n", expectedSize, nBytes);
|
|
CHECK(expectedSize >= nBytes);
|
|
{ auto p = (const uint8_t *)(instanceOut.txWaveform()); buffer.resize(nBytes); memcpy(buffer.data(), p, nBytes); }
|
|
addNoiseHelper(0.01, parameters.sampleFormatOut); // add some artificial noise
|
|
convertHelper(parameters.sampleFormatOut, parameters.sampleFormatInp);
|
|
}
|
|
|
|
// decode
|
|
{
|
|
parameters.sampleRateInp = srInp;
|
|
GGWave instanceInp(parameters);
|
|
instanceInp.rxProtocols().only(GGWAVE_PROTOCOL_DT_FASTEST);
|
|
|
|
instanceInp.decode(buffer.data(), buffer.size());
|
|
|
|
GGWave::TxRxData result;
|
|
CHECK(instanceInp.rxTakeData(result) == (int) payload.size());
|
|
for (int i = 0; i < (int) payload.size(); ++i) {
|
|
CHECK(payload[i] == result[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
const std::string payload = "a0Z5kR2g";
|
|
|
|
// encode / decode using different sample formats and Tx protocols
|
|
for (const auto & formatOut : kFormats) {
|
|
for (const auto & formatInp : kFormats) {
|
|
if (full == false) {
|
|
if (formatOut != GGWAVE_SAMPLE_FORMAT_I16) continue;
|
|
if (formatInp != GGWAVE_SAMPLE_FORMAT_F32) continue;
|
|
}
|
|
for (int protocolId = 0; protocolId < GGWAVE_PROTOCOL_COUNT; ++protocolId) {
|
|
const auto & protocol = GGWave::Protocols::kDefault()[protocolId];
|
|
if (protocol.enabled == false) continue;
|
|
printf("Testing: protocol = %s, in = %d, out = %d\n", protocol.name, formatInp, formatOut);
|
|
|
|
for (int length = 1; length <= (int) payload.size(); ++length) {
|
|
// mono-tone protocols with variable length are not supported
|
|
if (protocol.extra == 2) {
|
|
break;
|
|
}
|
|
|
|
// variable payload length
|
|
{
|
|
auto parameters = GGWave::getDefaultParameters();
|
|
parameters.sampleFormatInp = formatInp;
|
|
parameters.sampleFormatOut = formatOut;
|
|
if (rand() % 2 == 0) parameters.operatingMode |= GGWAVE_OPERATING_MODE_USE_DSS;
|
|
GGWave instance(parameters);
|
|
instance.rxProtocols().only(GGWave::ProtocolId(protocolId));
|
|
|
|
instance.init(length, payload.data(), GGWave::ProtocolId(protocolId), 25);
|
|
const auto expectedSize = instance.encodeSize_bytes();
|
|
const auto nBytes = instance.encode();
|
|
printf("Expected = %d, actual = %d\n", expectedSize, nBytes);
|
|
CHECK(expectedSize == nBytes);
|
|
{ auto p = (const uint8_t *)(instance.txWaveform()); buffer.resize(nBytes); memcpy(buffer.data(), p, nBytes); }
|
|
convertHelper(formatOut, formatInp);
|
|
instance.decode(buffer.data(), buffer.size());
|
|
|
|
GGWave::TxRxData result;
|
|
CHECK(instance.rxTakeData(result) == length);
|
|
for (int i = 0; i < length; ++i) {
|
|
CHECK(payload[i] == result[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int length = 1; length <= (int) payload.size(); ++length) {
|
|
// fixed payload length
|
|
{
|
|
auto parameters = GGWave::getDefaultParameters();
|
|
parameters.payloadLength = length;
|
|
parameters.sampleFormatInp = formatInp;
|
|
parameters.sampleFormatOut = formatOut;
|
|
if (rand() % 2 == 0) parameters.operatingMode |= GGWAVE_OPERATING_MODE_USE_DSS;
|
|
GGWave instance(parameters);
|
|
instance.rxProtocols().only(GGWave::ProtocolId(protocolId));
|
|
|
|
instance.init(length, payload.data(), GGWave::ProtocolId(protocolId), 10);
|
|
const auto expectedSize = instance.encodeSize_bytes();
|
|
const auto nBytes = instance.encode();
|
|
printf("Expected = %d, actual = %d\n", expectedSize, nBytes);
|
|
CHECK(expectedSize == nBytes);
|
|
{ auto p = (const uint8_t *)(instance.txWaveform()); buffer.resize(nBytes); memcpy(buffer.data(), p, nBytes); }
|
|
convertHelper(formatOut, formatInp);
|
|
instance.decode(buffer.data(), buffer.size());
|
|
|
|
GGWave::TxRxData result;
|
|
CHECK(instance.rxTakeData(result) == length);
|
|
for (int i = 0; i < length; ++i) {
|
|
CHECK(payload[i] == result[i]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|