Sign inSign up

blackwellj/dsc-decoder

By blackwellj

Updated about 2 months ago

VHF DSC decoder for Marine VHF radio. ITU-R M.493-13 compliant

Image
Integration & delivery
0

4.4K

blackwellj/dsc-decoder repository overview

About

This container supersedes the old RTL-SDR-based dsc-decoder. It now operates as a file-based decoder — pass it WAV/MP3 audio recordings and it returns decoded DSC messages as JSON. No RTL-SDR, no privileged mode, no USB devices required.

VHF DSC Decoder

Decode Marine VHF DSC (Digital Selective Calling) audio into JSON. 100% ITU-R M.493-13 compliant.

Quick Start

Pull and decode a file:

docker pull blackwellj/dsc-decoder:latest

# Decode a WAV file
docker run --rm -v /path/to/audio:/input:ro blackwellj/dsc-decoder /input/recording.wav --pretty

# Decode an MP3 file
docker run --rm -v /path/to/audio:/input:ro blackwellj/dsc-decoder /input/recording.mp3 --pretty

Run as an HTTP server

docker run -d --name dsc-decoder -p 8080:8080 blackwellj/dsc-decoder --serve --host 0.0.0.0 --port 8080

# Send audio via POST
curl -X POST "http://localhost:8080/decode?filename=audio.wav" \
  --data-binary @recording.wav \
  -H "Content-Type: application/octet-stream"

Returns JSON with decoded calls, or {"ok": false} if nothing found.

Supported input

  • WAV (native, any sample rate)
  • MP3 (auto-converted via ffmpeg)
  • Any format ffmpeg supports

Files shorter than 0.3 seconds or longer than 5 seconds are rejected immediately to save CPU.

How it works

The decoder uses two paths and picks whichever works:

Path 1 — Bit-level (standard)
  1. Bandpass filter (800–2800 Hz) to remove noise outside the DSC tone range
  2. Frequency offset search (±100 Hz) to handle PPM drift on SDRs
  3. 20-bit dot pattern matched filter to find where the signal starts
  4. I/Q quadrature correlation with Gardner clock recovery for each bit
  5. 10-bit symbol decoding — tolerates both MSB-first and LSB-first check-bit conventions
  6. RX-first phasing alignment to find the start of message data
  7. Frame detection with ECC validation
Path 2 — Symbol-level (weak signals)

Activates automatically when Path 1 finds nothing. Gives ~10 dB more sensitivity:

  1. Wide frequency search: ±800 Hz (handles large drift)
  2. Wider bandpass: 600–2800 Hz
  3. Pre-computes 128 reference waveforms — one for every valid DSC codeword
  4. Correlates the raw audio against all 128 references at once using phase-invariant quadrature
  5. Picks the best-matching codeword per 10-bit window — no intermediate bit decisions

Output format

{
  "ok": true,
  "calls": [
    {
      "format": {"symbol": 112, "name": "distress"},
      "self_id": "002320011",
      "ecc": {"received": 6, "calculated": 6, "ok": true},
      "confidence": {"score": 240, "invalid_symbols": 0},
      "message": {
        "kind": "distress_call",
        "nature": {"symbol": 7, "name": "undesignated distress"},
        "position": {
          "valid": true,
          "latitude": 50.3333,
          "longitude": -1.5
        },
        "utc": {"valid": true, "hour": 12, "minute": 30},
        "subsequent_communication": {"symbol": 100, "name": "F3E/G3E all modes TP"}
      }
    }
  ]
}
Call types
FormatSymbolDescriptionLength
Distress112Emergency call with nature, position, UTC25 symbols
Individual120Point-to-point with address MMSI23 symbols
All ships116Broadcast to all vesselsVariable
Group114Call to a group of vessels23 symbols
Geographical area102Call to vessels in an areaVariable
Automatic service123Automated data exchangeVariable
Fields explained
  • format: The type of DSC call
  • category: Routine, safety, urgency, or distress
  • self_id: The MMSI of the vessel that transmitted (9 digits)
  • address: Target MMSI (for individual calls)
  • ecc: Error Check Character — validates the message integrity
  • confidence.score: Higher = more reliable decode (typically 200-255)
  • message.kind: Machine-readable call type
  • message.position: Latitude/longitude if transmitted (all hemispheres supported)
  • demodulator: Shows which decode path was used (standard or symbol_level)

Compliance

FeatureStatus
ITU-R M.493-13 symbol encoding✅ Compliant
10-bit DSC symbols (0–127)✅ Compliant
DX/RX time diversity✅ Compliant
RX-first phasing (14 symbols)✅ Compliant
ECC validation✅ Compliant
Distress call (25 symbols)✅ Compliant
Individual call (23 symbols)✅ Compliant
All ships call✅ Compliant
Position (all hemispheres)✅ Compliant
UTC time✅ Compliant
Distress nature codes (0–31)✅ Compliant
Telecommand tables (100–127)✅ Compliant
MP3 input (ffmpeg)✅ Supported
Symbol-level correlation (+10 dB)✅ Supported
Soft-decision Viterbi✅ Supported

Performance

Tested against 1,000+ real off-air recordings from UK coastal VHF stations. The fixed decoder achieves approximately 13% decode rate on the full backlog (vs 0.1% from the original decoder). On stronger signals (within range of the receiving station), it decodes routinely.

Known issues

  • Very weak signals (below ~3 dB SNR in the DSC tone band) still don't decode. The symbol-level path helps but has limits.
  • 16 kHz source audio limits frequency resolution. 48 kHz native gives better results.
  • RTL-SDR PPM drift can shift tones by 200-500 Hz. The symbol-level path handles this; the bit-level path may need the bandpass widening manually.

Source code

https://github.com/blackwellj/coastalhub/tree/feat/tunnel-reliability-v2/apps/vhf-dsc-decoder

Tag summary

Content type

Image

Digest

sha256:95acd87d5

Size

271 MB

Last updated

about 2 months ago

docker pull blackwellj/dsc-decoder