diff --git a/include/ggwave/ggwave.h b/include/ggwave/ggwave.h index e7a6b4c..966e9b9 100644 --- a/include/ggwave/ggwave.h +++ b/include/ggwave/ggwave.h @@ -181,15 +181,23 @@ public: static const Parameters & getDefaultParameters(); + // set Tx data to encode 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 & txProtocol, const int volume = kDefaultVolume); + // expected waveform size of the encoded Tx data uint32_t encodeSize_bytes() const; uint32_t encodeSize_samples() const; + // encode Tx data into an audio waveform + // + // returns false if the encoding fails + // bool encode(const CBWaveformOut & cbWaveformOut); + + // decode an audio waveform void decode(const CBWaveformInp & cbWaveformInp); const bool & hasTxData() const { return m_hasNewTxData; } diff --git a/src/ggwave.cpp b/src/ggwave.cpp index 799a860..614bf15 100644 --- a/src/ggwave.cpp +++ b/src/ggwave.cpp @@ -313,6 +313,11 @@ GGWave::GGWave(const Parameters & parameters) : throw std::runtime_error("Invalid samples per frame"); } + if (m_sampleRateInp < m_sampleRateOut) { + fprintf(stderr, "Error: capture sample rate (%d Hz) must be >= playback sample rate (%d Hz)\n", (int) m_sampleRateInp, (int) m_sampleRateOut); + throw std::runtime_error("Invalid capture/playback sample rate"); + } + init("", getDefaultTxProtocol(), 0); } @@ -399,6 +404,7 @@ uint32_t GGWave::encodeSize_samples() const { return 0; } + int samplesPerFrameOut = (m_sampleRateOut/m_sampleRateInp)*m_samplesPerFrame; int nECCBytesPerTx = getECCBytesForLength(m_txDataLength); int sendDataLength = m_txDataLength + m_encodedDataOffset; int totalBytes = sendDataLength + nECCBytesPerTx; @@ -406,15 +412,12 @@ uint32_t GGWave::encodeSize_samples() const { return ( m_nMarkerFrames + m_nPostMarkerFrames + totalDataFrames + m_nMarkerFrames - )*m_samplesPerFrame; + )*samplesPerFrameOut; } bool GGWave::encode(const CBWaveformOut & cbWaveformOut) { int samplesPerFrameOut = (m_sampleRateOut/m_sampleRateInp)*m_samplesPerFrame; - if (m_sampleRateOut > m_sampleRateInp) { - fprintf(stderr, "Error: capture sample rate (%d Hz) must be <= playback sample rate (%d Hz)\n", (int) m_sampleRateInp, (int) m_sampleRateOut); - return false; - } + if (m_sampleRateOut != m_sampleRateInp) { fprintf(stderr, "Resampling from %d Hz to %d Hz\n", (int) m_sampleRateInp, (int) m_sampleRateOut); } diff --git a/tests/test-ggwave.cpp b/tests/test-ggwave.cpp index b860d26..a360590 100644 --- a/tests/test-ggwave.cpp +++ b/tests/test-ggwave.cpp @@ -181,6 +181,42 @@ int main() { CHECK_F(instance.init(payload.size(), payload.c_str(), 101)); } + // capture / playback at different sample rates + { + auto parameters = GGWave::getDefaultParameters(); + + std::string payload = "hello"; + + // encode + { + parameters.sampleRateInp = 48000; + parameters.sampleRateOut = 12000; + GGWave instanceOut(parameters); + + instanceOut.init(payload, 25); + auto expectedSize = instanceOut.encodeSize_samples(); + instanceOut.encode(kCBWaveformOut.at(parameters.sampleFormatOut)); + printf("Expected = %d, actual = %d\n", expectedSize, nSamples); + CHECK(expectedSize == nSamples); + convertHelper(parameters.sampleFormatOut, parameters.sampleFormatInp); + } + + // decode + { + parameters.samplesPerFrame *= float(parameters.sampleRateOut)/parameters.sampleRateInp; + parameters.sampleRateInp = parameters.sampleRateOut; + GGWave instanceInp(parameters); + + instanceInp.decode(kCBWaveformInp.at(parameters.sampleFormatInp)); + + GGWave::TxRxData result; + CHECK(instanceInp.takeRxData(result) == (int) payload.size()); + for (int i = 0; i < (int) payload.size(); ++i) { + CHECK(payload[i] == result[i]); + } + } + } + for (const auto & txProtocol : GGWave::getTxProtocols()) { for (const auto & formatOut : kFormats) { for (const auto & formatInp : kFormats) { @@ -201,12 +237,10 @@ int main() { convertHelper(formatOut, formatInp); instance.decode(kCBWaveformInp.at(formatInp)); - { - GGWave::TxRxData result; - CHECK(instance.takeRxData(result) == (int) payload.size()); - for (int i = 0; i < (int) payload.size(); ++i) { - CHECK(payload[i] == result[i]); - } + GGWave::TxRxData result; + CHECK(instance.takeRxData(result) == (int) payload.size()); + for (int i = 0; i < (int) payload.size(); ++i) { + CHECK(payload[i] == result[i]); } } }