From 412c781efd293f9c40d7f08c0b839e689b55c786 Mon Sep 17 00:00:00 2001 From: Georgi Gerganov Date: Thu, 21 Jul 2022 20:06:56 +0300 Subject: [PATCH] ggwave-from-file : add example to decode messages from a WAV file --- README-tmpl.md | 1 + README.md | 1 + examples/CMakeLists.txt | 1 + examples/{ggwave-to-file => }/dr_wav.h | 0 examples/ggwave-cli/main.cpp | 16 +-- examples/ggwave-from-file/CMakeLists.txt | 15 +++ examples/ggwave-from-file/README.md | 39 +++++++ examples/ggwave-from-file/main.cpp | 130 +++++++++++++++++++++ examples/ggwave-to-file/README.md | 35 ++++-- examples/ggwave-to-file/ggwave-to-file.php | 1 + examples/ggwave-to-file/ggwave-to-file.py | 5 +- examples/ggwave-to-file/main.cpp | 19 +-- 12 files changed, 233 insertions(+), 30 deletions(-) rename examples/{ggwave-to-file => }/dr_wav.h (100%) create mode 100644 examples/ggwave-from-file/CMakeLists.txt create mode 100644 examples/ggwave-from-file/README.md create mode 100644 examples/ggwave-from-file/main.cpp diff --git a/README-tmpl.md b/README-tmpl.md index e5516a7..f7f5251 100644 --- a/README-tmpl.md +++ b/README-tmpl.md @@ -106,6 +106,7 @@ The [examples](https://github.com/ggerganov/ggwave/blob/master/examples/) folder | [ggwave-cli](https://github.com/ggerganov/ggwave/blob/master/examples/ggwave-cli) | Command line tool for sending/receiving data through sound | SDL | | [ggwave-wasm](https://github.com/ggerganov/ggwave/blob/master/examples/ggwave-wasm) | WebAssembly module for web applications | SDL | | [ggwave-to-file](https://github.com/ggerganov/ggwave/blob/master/examples/ggwave-to-file) | Output a generated waveform to an uncompressed WAV file | - | +| [ggwave-from-file](https://github.com/ggerganov/ggwave/blob/master/examples/ggwave-from-file) | Decode a waveform from an uncompressed WAV file | - | | [waver](https://github.com/ggerganov/ggwave/blob/master/examples/waver) | GUI application for sending/receiving data through sound | SDL | | [ggwave-py](https://github.com/ggerganov/ggwave/blob/master/examples/ggwave-py) | Python examples | PortAudio | | [ggwave-js](https://github.com/ggerganov/ggwave/blob/master/examples/ggwave-js) | Javascript example | Web Audio API | diff --git a/README.md b/README.md index edf7cfc..ed45e99 100644 --- a/README.md +++ b/README.md @@ -106,6 +106,7 @@ The [examples](https://github.com/ggerganov/ggwave/blob/master/examples/) folder | [ggwave-cli](https://github.com/ggerganov/ggwave/blob/master/examples/ggwave-cli) | Command line tool for sending/receiving data through sound | SDL | | [ggwave-wasm](https://github.com/ggerganov/ggwave/blob/master/examples/ggwave-wasm) | WebAssembly module for web applications | SDL | | [ggwave-to-file](https://github.com/ggerganov/ggwave/blob/master/examples/ggwave-to-file) | Output a generated waveform to an uncompressed WAV file | - | +| [ggwave-from-file](https://github.com/ggerganov/ggwave/blob/master/examples/ggwave-from-file) | Decode a waveform from an uncompressed WAV file | - | | [waver](https://github.com/ggerganov/ggwave/blob/master/examples/waver) | GUI application for sending/receiving data through sound | SDL | | [ggwave-py](https://github.com/ggerganov/ggwave/blob/master/examples/ggwave-py) | Python examples | PortAudio | | [ggwave-js](https://github.com/ggerganov/ggwave/blob/master/examples/ggwave-js) | Javascript example | Web Audio API | diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 8360768..28edcc8 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -86,6 +86,7 @@ if (EMSCRIPTEN) add_subdirectory(buttons) else() add_subdirectory(ggwave-to-file) + add_subdirectory(ggwave-from-file) add_subdirectory(arduino-rx) add_subdirectory(arduino-tx) diff --git a/examples/ggwave-to-file/dr_wav.h b/examples/dr_wav.h similarity index 100% rename from examples/ggwave-to-file/dr_wav.h rename to examples/dr_wav.h diff --git a/examples/ggwave-cli/main.cpp b/examples/ggwave-cli/main.cpp index c44a442..072c56f 100644 --- a/examples/ggwave-cli/main.cpp +++ b/examples/ggwave-cli/main.cpp @@ -18,17 +18,17 @@ int main(int argc, char** argv) { printf(" -pN - select playback device N\n"); printf(" -tN - transmission protocol\n"); printf(" -lN - fixed payload length of size N, N in [1, %d]\n", GGWave::kMaxLengthFixed); - printf(" -s - use Direct Sequence Spread (DSS)\n"); + printf(" -d - use Direct Sequence Spread (DSS)\n"); printf(" -v - print generated tones on resend\n"); printf("\n"); - const auto argm = parseCmdArguments(argc, argv); - const int captureId = argm.count("c") == 0 ? 0 : std::stoi(argm.at("c")); - const int playbackId = argm.count("p") == 0 ? 0 : std::stoi(argm.at("p")); - const int txProtocolId = argm.count("t") == 0 ? 1 : std::stoi(argm.at("t")); - const int payloadLength = argm.count("l") == 0 ? -1 : std::stoi(argm.at("l")); - const bool useDSS = argm.count("s") > 0; - const bool printTones = argm.count("v") > 0; + const auto argm = parseCmdArguments(argc, argv); + const int captureId = argm.count("c") == 0 ? 0 : std::stoi(argm.at("c")); + const int playbackId = argm.count("p") == 0 ? 0 : std::stoi(argm.at("p")); + const int txProtocolId = argm.count("t") == 0 ? 1 : std::stoi(argm.at("t")); + const int payloadLength = argm.count("l") == 0 ? -1 : std::stoi(argm.at("l")); + const bool useDSS = argm.count("d") > 0; + const bool printTones = argm.count("v") > 0; if (GGWave_init(playbackId, captureId, payloadLength, 0.0f, useDSS) == false) { fprintf(stderr, "Failed to initialize GGWave\n"); diff --git a/examples/ggwave-from-file/CMakeLists.txt b/examples/ggwave-from-file/CMakeLists.txt new file mode 100644 index 0000000..347b0aa --- /dev/null +++ b/examples/ggwave-from-file/CMakeLists.txt @@ -0,0 +1,15 @@ +set(TARGET ggwave-from-file) + +add_executable(${TARGET} main.cpp) + +target_include_directories(${TARGET} PRIVATE + .. + ) + +target_link_libraries(${TARGET} PRIVATE + ggwave + ggwave-common + ${CMAKE_THREAD_LIBS_INIT} + ) + +install(TARGETS ${TARGET} RUNTIME DESTINATION bin) diff --git a/examples/ggwave-from-file/README.md b/examples/ggwave-from-file/README.md new file mode 100644 index 0000000..fed4340 --- /dev/null +++ b/examples/ggwave-from-file/README.md @@ -0,0 +1,39 @@ +## ggwave-from-file + +Decode GGWave messages from an input WAV file + +``` +Usage: ./bin/ggwave-from-file [-lN] [-d] + -lN - fixed payload length of size N, N in [1, 64] + -d - use Direct Sequence Spread (DSS) +``` + +### Examples + +- Basic usage with auto-detection of frequency and speed: + + ```bash + echo "Hello world" | ./bin/ggwave-to-file > example.wav + ./bin/ggwave-from-file example.wav + + Usage: ./bin/ggwave-from-file audio.wav [-lN] [-d] + -lN - fixed payload length of size N, N in [1, 64] + -d - use Direct Sequence Spread (DSS) + + [+] Number of channels: 1 + [+] Sample rate: 48000 + [+] Bits per sample: 16 + [+] Total samples: 69632 + [+] Decoding .. + + [+] Decoded message with length 11: 'Hello world' + + [+] Done + ``` + +- Decoding fixed-length payload with DSS enabled: + + ```bash + echo "Hello world" | ./bin/ggwave-to-file -l16 -d > example.wav + ./bin/ggwave-from-file example.wav -l16 -d + ``` diff --git a/examples/ggwave-from-file/main.cpp b/examples/ggwave-from-file/main.cpp new file mode 100644 index 0000000..f85db80 --- /dev/null +++ b/examples/ggwave-from-file/main.cpp @@ -0,0 +1,130 @@ +#include "ggwave/ggwave.h" + +#define DR_WAV_IMPLEMENTATION +#include "dr_wav.h" + +#include "ggwave-common.h" + +#include +#include +#include + +int main(int argc, char** argv) { + fprintf(stderr, "Usage: %s audio.wav [-lN] [-d]\n", argv[0]); + fprintf(stderr, " -lN - fixed payload length of size N, N in [1, %d]\n", GGWave::kMaxLengthFixed); + fprintf(stderr, " -d - use Direct Sequence Spread (DSS)\n"); + fprintf(stderr, "\n"); + + if (argc < 2) { + return -1; + } + + const auto argm = parseCmdArguments(argc, argv); + + if (argm.count("h") > 0) { + return 0; + } + + const int payloadLength = argm.count("l") == 0 ? -1 : std::stoi(argm.at("l")); + const bool useDSS = argm.count("d") > 0; + + drwav wav; + if (!drwav_init_file(&wav, argv[1], nullptr)) { + fprintf(stderr, "Failed to open WAV file\n"); + return -4; + } + + if (wav.channels != 1) { + fprintf(stderr, "Only mono WAV files are supported\n"); + return -5; + } + + // Read WAV samples into a buffer + // Add 3 seconds of silence at the end + const size_t samplesSilence = 3*wav.sampleRate; + const size_t samplesCount = wav.totalPCMFrameCount; + const size_t samplesSize = wav.bitsPerSample/8; + size_t samplesTotal = samplesCount + samplesSilence; + std::vector samples(samplesTotal*samplesSize*wav.channels, 0); + + printf("[+] Number of channels: %d\n", wav.channels); + printf("[+] Sample rate: %d\n", wav.sampleRate); + printf("[+] Bits per sample: %d\n", wav.bitsPerSample); + printf("[+] Total samples: %zu\n", samplesCount); + + printf("[+] Decoding .. \n\n"); + + GGWave::Parameters parameters = GGWave::getDefaultParameters(); + + parameters.payloadLength = payloadLength; + parameters.sampleRateInp = wav.sampleRate; + parameters.operatingMode = GGWAVE_OPERATING_MODE_RX; + if (useDSS) parameters.operatingMode |= GGWAVE_OPERATING_MODE_USE_DSS; + + switch (wav.bitsPerSample) { + case 16: + drwav_read_pcm_frames_s16(&wav, samplesCount, reinterpret_cast(samples.data())); + + if (wav.channels > 1) { + for (size_t i = 0; i < samplesCount; ++i) { + int16_t sample = 0; + for (size_t j = 0; j < wav.channels; ++j) { + sample += reinterpret_cast(samples.data())[i*wav.channels + j]; + } + reinterpret_cast(samples.data())[i] = sample / wav.channels; + } + } + + parameters.sampleFormatInp = GGWAVE_SAMPLE_FORMAT_I16; + + break; + case 32: + drwav_read_pcm_frames_f32(&wav, samplesCount, reinterpret_cast(samples.data())); + + if (wav.channels > 1) { + for (size_t i = 0; i < samplesCount; ++i) { + float sample = 0.0f; + for (size_t j = 0; j < wav.channels; ++j) { + sample += reinterpret_cast(samples.data())[i*wav.channels + j]; + } + reinterpret_cast(samples.data())[i] = sample / wav.channels; + } + } + + parameters.sampleFormatInp = GGWAVE_SAMPLE_FORMAT_F32; + + break; + default: + fprintf(stderr, "Unsupported WAV format\n"); + return -6; + } + + GGWave ggWave(parameters); + ggWave.setLogFile(nullptr); + + GGWave::TxRxData data; + auto ptr = samples.data(); + while ((int) samplesTotal >= parameters.samplesPerFrame) { + if (ggWave.decode(ptr, parameters.samplesPerFrame*samplesSize*wav.channels) == false) { + fprintf(stderr, "Failed to decode the waveform in the WAV file\n"); + return -7; + } + + ptr += parameters.samplesPerFrame*samplesSize*wav.channels; + samplesTotal -= parameters.samplesPerFrame; + + const int n = ggWave.rxTakeData(data); + if (n > 0) { + printf("[+] Decoded message with length %d: '", n); + for (auto i = 0; i < n; ++i) { + printf("%c", data[i]); + } + printf("'\n"); + } + + } + + printf("\n[+] Done\n"); + + return 0; +} diff --git a/examples/ggwave-to-file/README.md b/examples/ggwave-to-file/README.md index 020989e..4f2ed01 100644 --- a/examples/ggwave-to-file/README.md +++ b/examples/ggwave-to-file/README.md @@ -3,22 +3,26 @@ Output a generated waveform to an uncompressed WAV file. ``` -Usage: ./bin/ggwave-to-file [-vN] [-sN] [-pN] [-lN] +Usage: ./bin/ggwave-to-file [-vN] [-sN] [-pN] [-lN] [-d] -vN - output volume, N in (0, 100], (default: 50) -sN - output sample rate, N in [6000, 96000], (default: 48000) -pN - select the transmission protocol id (default: 1) -lN - fixed payload length of size N, N in [1, 16] + -d - use Direct Sequence Spread (DSS) Available protocols: - 0 - Normal - 1 - Fast - 2 - Fastest - 3 - [U] Normal - 4 - [U] Fast - 5 - [U] Fastest - 6 - [DT] Normal - 7 - [DT] Fast - 8 - [DT] Fastest + 0 - Normal + 1 - Fast + 2 - Fastest + 3 - [U] Normal + 4 - [U] Fast + 5 - [U] Fastest + 6 - [DT] Normal + 7 - [DT] Fast + 8 - [DT] Fastest + 9 - [MT] Normal + 10 - [MT] Fast + 11 - [MT] Fastest ``` ### Examples @@ -47,6 +51,12 @@ Usage: ./bin/ggwave-to-file [-vN] [-sN] [-pN] [-lN] echo "Hello world!" | ./bin/ggwave-to-file -l12 > example.wav ``` +- Use DSS when encoding the text + + ```bash + echo "aaaaaaaa" | ./bin/ggwave-to-file -l8 -d > example.wav + ``` + - Play the generated waveform directly through the speakers ```bash @@ -89,13 +99,13 @@ from typing import Dict, Union import requests import wave - def ggwave(message: str, file: str, protocolId: int = 1, sampleRate: float = 48000, volume: int = 50, - payloadLength: int = -1) -> None: + payloadLength: int = -1, + useDSS: int = 0) -> None: url = 'https://ggwave-to-file.ggerganov.com/' @@ -105,6 +115,7 @@ def ggwave(message: str, 's': sampleRate, # output sample rate 'v': volume, # output volume 'l': payloadLength, # if positive - use fixed-length encoding + 'd': useDSS, # if positive - use DSS } response = requests.get(url, params=params) diff --git a/examples/ggwave-to-file/ggwave-to-file.php b/examples/ggwave-to-file/ggwave-to-file.php index b109e03..505f918 100644 --- a/examples/ggwave-to-file/ggwave-to-file.php +++ b/examples/ggwave-to-file/ggwave-to-file.php @@ -6,6 +6,7 @@ if (isset($_GET['s'])) { $cmd .= " -s".intval($_GET['s']); } if (isset($_GET['v'])) { $cmd .= " -v".intval($_GET['v']); } if (isset($_GET['p'])) { $cmd .= " -p".intval($_GET['p']); } if (isset($_GET['l'])) { $cmd .= " -l".intval($_GET['l']); } +if (isset($_GET['d'])) { if (intval($_GET['d']) > 0) $cmd .= " -d"; } $descriptorspec = array( 0 => array("pipe", "r"), diff --git a/examples/ggwave-to-file/ggwave-to-file.py b/examples/ggwave-to-file/ggwave-to-file.py index 1c05445..9199374 100644 --- a/examples/ggwave-to-file/ggwave-to-file.py +++ b/examples/ggwave-to-file/ggwave-to-file.py @@ -2,13 +2,13 @@ from typing import Dict, Union import requests import wave - def ggwave(message: str, file: str, protocolId: int = 1, sampleRate: float = 48000, volume: int = 50, - payloadLength: int = -1) -> None: + payloadLength: int = -1, + useDSS: int = 0) -> None: url = 'https://ggwave-to-file.ggerganov.com/' @@ -18,6 +18,7 @@ def ggwave(message: str, 's': sampleRate, # output sample rate 'v': volume, # output volume 'l': payloadLength, # if positive - use fixed-length encoding + 'd': useDSS, # if positive - use DSS } response = requests.get(url, params=params) diff --git a/examples/ggwave-to-file/main.cpp b/examples/ggwave-to-file/main.cpp index bb6b621..4ebc62d 100644 --- a/examples/ggwave-to-file/main.cpp +++ b/examples/ggwave-to-file/main.cpp @@ -10,18 +10,21 @@ #include int main(int argc, char** argv) { - fprintf(stderr, "Usage: %s [-vN] [-sN] [-pN] [-lN]\n", argv[0]); + fprintf(stderr, "Usage: %s [-vN] [-sN] [-pN] [-lN] [-d]\n", argv[0]); fprintf(stderr, " -vN - output volume, N in (0, 100], (default: 50)\n"); - fprintf(stderr, " -SN - output sample rate, N in [%d, %d], (default: %d)\n", (int) GGWave::kSampleRateMin, (int) GGWave::kSampleRateMax, (int) GGWave::kDefaultSampleRate); + fprintf(stderr, " -sN - output sample rate, N in [%d, %d], (default: %d)\n", (int) GGWave::kSampleRateMin, (int) GGWave::kSampleRateMax, (int) GGWave::kDefaultSampleRate); fprintf(stderr, " -pN - select the transmission protocol id (default: 1)\n"); fprintf(stderr, " -lN - fixed payload length of size N, N in [1, %d]\n", GGWave::kMaxLengthFixed); - fprintf(stderr, " -s - use Direct Sequence Spread (DSS)\n"); + fprintf(stderr, " -d - use Direct Sequence Spread (DSS)\n"); fprintf(stderr, "\n"); fprintf(stderr, " Available protocols:\n"); const auto & protocols = GGWave::Protocols::kDefault(); for (int i = 0; i < (int) protocols.size(); ++i) { const auto & protocol = protocols[i]; + if (protocol.enabled == false) { + continue; + } fprintf(stderr, " %d - %s\n", i, protocol.name); } fprintf(stderr, "\n"); @@ -36,11 +39,11 @@ int main(int argc, char** argv) { return 0; } - const int volume = argm.count("v") == 0 ? 50 : std::stoi(argm.at("v")); - const float sampleRateOut = argm.count("S") == 0 ? GGWave::kDefaultSampleRate : std::stof(argm.at("S")); - const int protocolId = argm.count("p") == 0 ? 1 : std::stoi(argm.at("p")); - const int payloadLength = argm.count("l") == 0 ? -1 : std::stoi(argm.at("l")); - const bool useDSS = argm.count("s") > 0; + const int volume = argm.count("v") == 0 ? 50 : std::stoi(argm.at("v")); + const float sampleRateOut = argm.count("s") == 0 ? GGWave::kDefaultSampleRate : std::stof(argm.at("s")); + const int protocolId = argm.count("p") == 0 ? 1 : std::stoi(argm.at("p")); + const int payloadLength = argm.count("l") == 0 ? -1 : std::stoi(argm.at("l")); + const bool useDSS = argm.count("d") > 0; if (volume <= 0 || volume > 100) { fprintf(stderr, "Invalid volume\n");