One of the most intersting things I've made. A completely lossless stream of audio over SimTX. Using FEC and OFDM.
  • C 90%
  • Shell 7.4%
  • Makefile 2.6%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-10-15 16:21:14 +03:00
src first commit 2026-10-15 16:21:14 +03:00
test first commit 2026-10-15 16:21:14 +03:00
.gitignore first commit 2026-10-15 16:21:14 +03:00
AGENTS.md first commit 2026-10-15 16:21:14 +03:00
decode.sh first commit 2026-10-15 16:21:14 +03:00
DESIGN.md first commit 2026-10-15 16:21:14 +03:00
LICENSE first commit 2026-10-15 16:21:14 +03:00
Makefile first commit 2026-10-15 16:21:14 +03:00
README.md first commit 2026-10-15 16:21:14 +03:00
receive-stream.sh first commit 2026-10-15 16:21:14 +03:00
start-stream.sh first commit 2026-10-15 16:21:14 +03:00
test.stxpreset first commit 2026-10-15 16:21:14 +03:00

400IQ (LARO): Lossless Audio Radio OFDM

Bit-exact, live-streamable audio broadcast over OFDM radio.

Overview

400IQ (Lossless Audio Radio OFDM, or LARO) streams real-time, bit-exact audio over Software Defined Radio (SDR) channels.

The system is designed for lossy, unidirectional radio links:

  • Lossless reconstruction: Reconstructs the original 16-bit PCM samples on reception when SNR is at or above 19 dB.
  • Independent frames: Every frame decodes on its own without inter-frame prediction. A receiver tuning in mid-broadcast decodes on the next received frame.
  • Continuous sample-clock pacing: Direct TCP streaming paces output to the SDR sample rate, transmitting idle zero symbols during pauses to prevent transmitter buffer underruns.
  • RTL-TCP frontend: A C client connects directly to remote or local RTL-TCP servers, handles frequency and gain commands, and converts incoming 8-bit I/Q samples to 32-bit floating-point complex samples through a lookup table.
  • Jitter buffer: The receiver uses packet loss concealment and frontier re-anchoring to absorb packet jitter and recover from dropouts.

Architecture

flowchart TD
    subgraph TX ["Transmitter Pipeline (start-stream.sh)"]
        AudioIn["Audio Input<br/>(PulseAudio / PipeWire parec)"] --> LaroTX["build/laro_tx<br/>LPC + Rice Encoder"]
        LaroTX -->|"LARO Frames<br/>(CRC-8 + CRC-32)"| OFDM_TX["build/ofdm_tx<br/>OFDM Modulator & Pacer"]
        OFDM_TX -->|"complex64 TCP (127.0.0.1:5000)<br/>Clock Pacing"| SimTX["SimTX / SDR Transmitter<br/>(RF 40.0 MHz)"]
    end

    SimTX ==>|"RF Channel<br/>AWGN, Multipath, Fading"| RTL_TCP["RTL-TCP Server<br/>(argon.simtx.net:1240)"]

    subgraph RX ["Receiver Pipeline (decode.sh / receive-stream.sh)"]
        RTL_TCP -->|"uint8 interleaved IQ"| RTL_RX["build/rtl_tcp_rx<br/>RTL-TCP Client & LUT Converter"]
        RTL_RX -->|"complex64 float32 IQ"| OFDM_RX["build/ofdm_rx<br/>liquid-dsp ofdmflexframesync"]
        OFDM_RX -->|"Demodulated LARO Frames"| LaroRX["build/laro_rx --live<br/>Jitter Buffer + PLC + LPC Decoder"]
        LaroRX -->|"Signed 16-bit PCM"| AudioOut["PulseAudio / PipeWire Playout<br/>(paplay) or PCM File"]
    end

    classDef tx fill:#1e293b,stroke:#38bdf8,stroke-width:2px,color:#f8fafc;
    classDef rf fill:#312e81,stroke:#818cf8,stroke-width:2px,color:#f8fafc;
    classDef rx fill:#064e3b,stroke:#34d399,stroke-width:2px,color:#f8fafc;
    class AudioIn,LaroTX,OFDM_TX tx;
    class SimTX,RTL_TCP rf;
    class RTL_RX,OFDM_RX,LaroRX,AudioOut rx;

Operating Presets

Preset Audio Format Block Size Subcarriers (M) Cyclic Prefix (CP) IQ Sample Rate (Fs) Bandwidth Profile
--stereo (default) 44.1 kHz Stereo 960 1024 128 1,200,000 S/s ~1.17 MHz Full fidelity CD stereo
--simtx-200k 22.05 kHz Mono 960 128 16 200,000 S/s ~185 kHz FM-fidelity audio
--simtx-124k 16.0 kHz Mono 960 128 16 124,000 S/s ~118 kHz Low uplink bandwidth (3.97 Mbps wire rate)

Building and Testing

Prerequisites

  • GCC supporting C11, GNU Make, glibc, and libm.
  • PulseAudio or PipeWire utilities (parec, paplay, pactl) for live audio.
  • No system radio libraries needed: make fetches and builds vendor/liquid-dsp automatically.

Compilation

# Build all binaries
make

# Run offline unit tests
make test

Test Suite

make test runs three offline test programs:

  1. loopback_test: Verifies that the LPC compressor, Rice bit-packer, and framer round-trip bit-exact PCM audio across sine waves, noise bursts, DC offsets, and corrupted headers.
  2. channel_test: Sweeps simulated bit error rates (10^{-6} to 10^{-3}) to confirm that frame CRC checks reject corrupted packets without silent data corruption.
  3. resample_test: Validates clock drift absorption under \pm 500 ppm frequency skew.

Quick Start

1. Transmit Audio

Stream local audio into SimTX:

# Transmit stereo audio (44.1 kHz, Fs=1.2 MS/s) to SimTX on TCP port 5000:
./start-stream.sh --simtx

# Transmit 16 kHz mono (Fs=124 kS/s):
./start-stream.sh --simtx --simtx-124k

# Transmit over UDP to a local port:
./start-stream.sh --rate 44100 --channels 2

2. Receive and Decode

Receive and decode the radio signal:

# Decode off-air from argon.simtx.net:1240 at 40 MHz and play through default sink:
./decode.sh

# Record decoded PCM audio directly to a file:
./decode.sh --out capture.pcm

# Run the receiver with custom frequency and gain:
./receive-stream.sh --simtx --freq 40000000 --gain 15.0

System Modules

1. Audio Codec (src/codec.c, src/codec.h)

  • LPC Order-4 Predictor: Uses a Levinson-Durbin auto-regressive model to predict each sample from preceding samples within the current block.
  • Golomb-Rice Coding: Adapts the Rice parameter k to the residual variance for compact bit-packing.
  • PCM Fallback: If high-entropy input (such as white noise) causes the compressed residual to exceed the original size, the encoder outputs uncompressed signed 16-bit samples (flag = 0x01).

2. Frame Format (src/framer.c, src/framer.h)

  • Header Structure:
    [8-byte Header]
      - 0x00..0x01: Magic 0x4C 0x51 ('LQ')
      - 0x02:       Stream Generation ID
      - 0x03:       Flags (0x80 = Stream Header, 0x01 = Raw Fallback)
      - 0x04..0x05: Sequence Number (uint16 big-endian)
      - 0x06:       Payload Length (uint8)
      - 0x07:       Header CRC-8
    [Payload: N bytes]
    [4-byte Payload CRC-32 (IEEE 802.3)]
    
  • Stream Headers: Transmitted roughly once per second. They broadcast audio parameters (sample rate, channels, block size), allowing new listeners to synchronize quickly.

3. OFDM Physical Layer (src/ofdm_tx.c, src/ofdm_rx.c)

  • Modulation: Uses liquid-dsp's ofdmflexframe generator and synchronizer with configurable FFT sizes (M=128 to 1024) and cyclic prefix lengths (CP).
  • Constellations: Supports 64-QAM (default), 16-QAM, QPSK, and BPSK.
  • Clock Pacing: ofdm_tx paces output using microsecond timers (--fs RATE). Between audio bursts, it sends zero symbols to keep transmitter buffers from draining.

4. RTL-TCP SDR Receiver (src/rtl_tcp_rx.c)

  • Network Ingest: Connects to RTL-TCP servers (such as argon.simtx.net:1240) over TCP.
  • Tuner Control: Issues 5-byte command packets to set frequency (0x01), sample rate (0x02), tuner gain (0x04), and AGC state (0x08).
  • Float Conversion: Converts incoming unsigned 8-bit I/Q sample pairs to float32 values in [-1.0, +1.0] using a 256-entry lookup table.

5. Jitter Buffer (src/laro_rx.c, src/jitbuf.c)

  • Ring Buffer: Reorders frames that arrive out of order and smooths network jitter.
  • Loss Concealment: If an audio frame is lost or fails its CRC check, the buffer repeats the previous block to keep playback continuous.
  • Frontier Re-anchoring: Recovers from multi-second signal dropouts by advancing directly to the newest sequence number.

Repository Layout

400iq/
├── Makefile             # C11 build rules (gcc and make)
├── decode.sh            # Default 40 MHz off-air decoder
├── receive-stream.sh    # Receive pipeline script (RTL-TCP, UDP, file)
├── start-stream.sh      # Transmit pipeline script (parec to SimTX)
├── test.stxpreset       # SimTX RF and antenna configuration preset
├── DESIGN.md            # Detailed format specification and link budget
├── HANDOFF.md           # Dated project status and change log
├── AGENTS.md            # Development instructions and project rules
├── src/
│   ├── codec.c / .h     # LPC-4 and Golomb-Rice lossless audio codec
│   ├── framer.c / .h    # Frame structure, CRC-8, and CRC-32
│   ├── jitbuf.c / .h    # Receiver ring buffer and loss concealment
│   ├── laro_tx.c        # PCM ingest and frame pack frontend
│   ├── laro_rx.c        # Frame deframer and audio playout frontend
│   ├── ofdm_tx.c        # OFDM modulation, symbol pacing, and TCP streaming
│   ├── ofdm_rx.c        # OFDM frame synchronizer and payload extraction
│   └── rtl_tcp_rx.c     # Standalone RTL-TCP client and converter
├── test/
│   ├── loopback_test.c  # In-memory bit-exact codec verification
│   ├── channel_test.c   # Simulated BER channel sweep and CRC test
│   ├── channel_sim.c    # Software RF channel simulator (AWGN and fading)
│   └── resample_test.c  # Clock skew adaptation tests
└── vendor/
    └── liquid-dsp/      # Physical-layer DSP library (auto-fetched)

AWGN channel simulation results using build/channel_sim (64-QAM, block 960):

SNR Sweep (64-QAM, Block 960, Fs = 1.2 MS/s):
-----------------------------------------------------------------------------
  SNR >= 19.0 dB : 101/101 frames OK  (100% bit-exact, 0 concealed)
  SNR = 18.5 dB  : 100/101 frames OK  (1 concealed)
  SNR = 18.0 dB  :  98/101 frames OK  (3 concealed)
  SNR = 17.0 dB  :  73/101 frames OK  (Waterfall knee)
  SNR <= 15.0 dB :  Muted; headers only
-----------------------------------------------------------------------------