ggwave-from-file : add example to decode messages from a WAV file

This commit is contained in:
Georgi Gerganov
2022-07-21 20:06:56 +03:00
parent b0f5c9d4c4
commit 412c781efd
12 changed files with 233 additions and 30 deletions

View File

@@ -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)

View File

@@ -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");

View File

@@ -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)

View File

@@ -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
```

View File

@@ -0,0 +1,130 @@
#include "ggwave/ggwave.h"
#define DR_WAV_IMPLEMENTATION
#include "dr_wav.h"
#include "ggwave-common.h"
#include <cstdio>
#include <cstring>
#include <iostream>
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<uint8_t> 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<int16_t*>(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<int16_t*>(samples.data())[i*wav.channels + j];
}
reinterpret_cast<int16_t*>(samples.data())[i] = sample / wav.channels;
}
}
parameters.sampleFormatInp = GGWAVE_SAMPLE_FORMAT_I16;
break;
case 32:
drwav_read_pcm_frames_f32(&wav, samplesCount, reinterpret_cast<float*>(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<float*>(samples.data())[i*wav.channels + j];
}
reinterpret_cast<float*>(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;
}

View File

@@ -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)

View File

@@ -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"),

View File

@@ -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)

View File

@@ -10,18 +10,21 @@
#include <iostream>
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");