Repository navigation
Memory
Most of the memory is allocated once, in the decoder's begin(). It grows in proportion to the sample rate, and it's dominated by the receive buffers. Sending needs almost no memory, except with OFDM and ggwave. Olivia and CSS work at a fixed internal rate (8000 Hz, twice the bandwidth), so their memory hardly depends on the sample rate.
| Where | What | When |
|---|---|---|
| Inside the object (fixed size) | Output buffer of 128 × int16_t, Goertzel state, Packets buffers, FSK expect_* strings |
When the object is created |
Heap (audio_tools::Vector) |
Decoder sample buffers, OFDM and ggwave sine/cosine tables, OFDM frame, ggwave DFT history and candidates | Once, in begin(), which reallocates when the configuration changes |
| Heap (growing) |
ByteQueue (received bytes in the ...Stream classes) |
Grows up to about rx_buffer_size (default 256), then keeps its capacity |
| Stack | OFDM decoding: about 1.5 KB of local arrays. CSS encoding: about 1.5 KB. FX.25 and IL2P decoding, Reed-Solomon corrections and packet decoding: a few hundred bytes | Per call. The receive task of the ...StreamRTOS classes has a default stack of 8 KB (see setTaskConfig()) |
PacketStream uses no heap at all. Its size is fixed by the template parameters (888 bytes with PacketStream<64, 16>).
The heap allocated by begin(), measured on the desktop. The buffers hold floats, so they are the same size on the ESP32 and RP2040.
| Modem type | Mode | Receiving, 8 kHz | Receiving, 48 kHz | Sending, 48 kHz |
|---|---|---|---|---|
| FSK |
1200, 300
|
2.6 KB | 16 KB | – |
| FSK |
rtty (45.45 baud) |
12.7 KB | 76 KB | – |
| MFSK |
mfsk4 / mfsk16 / mfsk64
|
0.9 / 4.2 / 15 KB | 3.5 / 15 / 57 KB | – |
| PSK |
psk31 / psk500
|
3 / 0.2 KB | 18 / 1.2 KB | – |
| OFDM |
ofdm16 / ofdm32 / ofdm64
|
3.6 / 7.2 / 14 KB | 20 / 40 / 81 KB | 3.7 / 6.7 / 13 KB |
| AX.25 | aprs |
0.1 KB | 0.6 KB | – |
| ggwave |
ggwave-fast / ggwave-dt-fast
|
– (needs ≥ 24 / 12 kHz; 24 kHz: 23 / 12.5 KB) | 27 / 16.5 KB | 8 KB |
| PSK |
qpsk31 / 8psk125
|
3 / 0.4 KB | 18 / 2.3 KB | – |
| AX.25 |
fx25, il2p
|
0.1 KB | 0.6 KB | – |
| Olivia |
olivia-8-250 / olivia-32-1000 / olivia-64-2000
|
35 / 36 / 38 KB | 32 / 36 / 38 KB | – |
| Olivia | contestia-8-250 |
20 KB | 20 KB | – |
| CSS |
css5 / css7 / css10
|
2.6 / 11 / 112 KB | 3 / 11 / 115 KB | – |
| DTMF | dtmf |
– | – | – |
| Morse |
morse-5 / morse (20 wpm) / morse-40
|
7.5 / 1.9 / 1 KB | 45 / 11 / 5.6 KB | – |
A dash means less than 100 bytes.
The objects themselves are small: 0.5 to 1.4 KB for an encoder or decoder (the AX.25 decoder with the FX.25 and IL2P buffers 2.4 KB, the Olivia and CSS decoders 2.9 KB, mostly the filter table of the sample rate conversion). A complete stream with an encoder, a decoder and a 256-byte copy buffer takes 1.4 to 3 KB. These sizes were measured on a 64-bit desktop, so they are a bit smaller on a 32-bit microcontroller.
Why some modes need more memory than others:
- Slow FSK modes keep about 2 characters' worth of samples. At 45 baud this is about 0.4 s of audio.
-
MFSK keeps the samples which are needed to find the preamble. This grows with the symbol length, and
mfsk64has long symbols. - OFDM keeps about 4 symbols plus 2 FFT windows. The FFT size is the sample rate divided by the carrier spacing.
- Olivia keeps the soft bits of the last block for each of the 34 time slices and frequency offsets and the synchronizer state for each of the 128 timings and 17 frequency offsets (about 30 KB together), independent of the sample rate.
-
CSS keeps 4 symbols of complex samples at twice the bandwidth and an FFT of 2 × 2^SF points: the memory doubles with each step of the spreading factor,
css10needs 112 KB. - ggwave keeps the DFT of 96 bins (DT: 33) for the last 24 blocks of a quarter frame, the sine table of a frame and 9 candidate decoders. Only the sine table grows with the sample rate. The encoder also keeps a sine table of a frame.
Use a lower sample rate to save memory, because all buffers scale linearly with it. 8 to 22.05 kHz is enough for all modes except ggwave, because the highest tone is about 3 kHz. ggwave needs 24 kHz (DT: 12 kHz, ultrasound: 48 kHz).
TinyModem allocates its buffers with audio_tools::Vector, so arduino-audio-tools decides where they go:
-
ESP32:
VectorusesDefaultAllocator, which triesps_malloc()first and falls back tomalloc(). If PSRAM is enabled in the board settings, the decoder buffers, the OFDM tables and the receive queue are placed in PSRAM automatically. Without PSRAM they are placed in internal RAM. -
RP2040 and RP2350:
Vectorusesnew[], so the buffers always stay in internal RAM, even on an RP2350 with PSRAM. The RP2040's 264 KB are enough for all modes, evenofdm64at 48 kHz. - The objects themselves are placed wherever you declare them, normally as globals in internal RAM.
- PSRAM is slower than internal RAM. The decoders read their buffers in tight loops (Goertzel filters, the OFDM DFT, the MFSK clock search), and with PSRAM these accesses go through the cache. If OFDM or MFSK cannot keep up in real time on an ESP32 with PSRAM, use a lower sample rate or disable PSRAM.
-
ESP32 without PSRAM: a single allocation of 76 to 81 KB (
rttyorofdm64at 48 kHz) can fail when the heap is fragmented. Use 22.05 kHz or less.css10needs PSRAM or a lower spreading factor. -
Call
begin()early, for example insetup(), while the heap is still unfragmented. A laterbegin()with a different configuration allocates the buffers again.