fm-rds-tx ist ein broadcast-konformer FM-Stereo-MPX-Encoder mit RDS für PlutoSDR/SoapySDR. Die DSP-Kette folgt dem Industriestandard (Omnia, Orban, Stereo Tool) mit Clip-Filter-Clip- Architektur und ITU-R BS.412 MPX Power Limiting.
┌─────────────────────────────────────────────────────────────────┐
│ AUDIO INPUT │
│ S16LE Stereo PCM via stdin (ffmpeg) oder interner Tongenerator │
└──────────────────────────┬──────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ STAGE 1: Pre-Emphasis + Band-Limiting (pro Kanal L/R) │
│ │
│ Audio × gain ──→ Pre-Emphasis (50µs EU / 75µs US) │
│ ──→ 15kHz LPF (8th-order Chebyshev Type I, 0.5dB Ripple) │
│ ──→ 19kHz Notch (Q=15, double-cascade) │
│ │
│ Frequenzantwort (verifiziert): │
│ 10kHz: +0.1dB (flat) 15kHz: -0.2dB │
│ 17kHz: -21dB 18.5kHz: -40dB │
│ 19kHz: -155dB (tot) 22kHz: -51dB │
└──────────────────────────┬──────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ STAGE 2: Drive + Kompression + Clip₁ │
│ │
│ × OutputDrive │
│ ──→ StereoLimiter (5ms Attack / 200ms Release, ceiling) │
│ ──→ HardClip₁ (ceiling) │
│ │
│ Der Limiter komprimiert die Dynamik (bringt Average hoch). │
│ Der Clip fängt Peaks die der Limiter's Attack verpasst. │
│ "Slow-to-fast Progression" — Broadcast-Standard. │
└──────────────────────────┬──────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ STAGE 3: Cleanup LPF + Clip₂ (Overshoot-Kompensator) │
│ │
│ ──→ 15kHz LPF (8th-order Chebyshev, identisch zu Stage 1) │
│ ──→ HardClip₂ (ceiling) │
│ │
│ Der zweite LPF-Pass entfernt Clip₁-Harmonische. │
│ Clip₂ fängt die LPF-Overshoots (IIR-Filter erzeugen diese). │
│ Doppelter LPF-Pass verdoppelt die Guard-Band-Dämpfung. │
└──────────────────────────┬──────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ STAGE 4: Stereo-Encode │
│ │
│ L/R ──→ Mono: (L+R)/2 (0–15kHz Baseband) │
│ ──→ Stereo: (L-R)/2 × sin(38kHz) (23–53kHz DSB-SC) │
│ ──→ Pilot: sin(19kHz) (phase-locked, kohärent) │
│ ──→ RDS Carrier: sin(57kHz) (3× Pilot-Phase, kohärent) │
└──────────────────────────┬──────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ STAGE 5: Composite Clip + Schutzfilter │
│ │
│ Audio-MPX (Mono + Stereo-Sub) │
│ ──→ HardClip₃ (ceiling) — finale Deviations-Kontrolle │
│ ──→ 19kHz Notch (Q=10, double) — Clip-Harmonische bei Pilot │
│ ──→ 57kHz Notch (Q=10, double) — Clip-Harmonische bei RDS │
│ │
│ Guard Bands (total, 2× LPF + Notches): │
│ 19kHz: >-80dB broadband, >-90dB exakt │
│ 57kHz: >-100dB │
│ (Omnia 11 Spezifikation: >80dB — wir sind on par) │
└──────────────────────────┬──────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ STAGE 6: BS.412 MPX Power Limiter (optional) │
│ │
│ ──→ × BS412 Gain │
│ │
│ Rolling 60-Sekunden RMS-Messung auf dem Audio-Composite. │
│ Langsamer Gain-Regler (2s Attack / 5s Release). │
│ Zieht Pilot+RDS-Power automatisch vom Budget ab. │
│ Pflicht in CH, DE, NL, FR für lizenzierte FM-Sender. │
│ ~5dB Lautheitsverlust bei 0 dBr Threshold. │
└──────────────────────────┬──────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ STAGE 7: Pilot + RDS Injection (fixe Amplitude) │
│ │
│ composite = audioMPX │
│ + pilotLevel × sin(19kHz) — IMMER 9% │
│ + rdsInjection × rdsWaveform — IMMER 4% │
│ │
│ Pilot und RDS werden NIE geclippt, NIE gefiltert, NIE vom │
│ BS.412-Limiter berührt. Konstante Amplitude, immer. │
│ │
│ Peak Composite = ceiling + pilotLevel + rdsInjection ≈ 113% │
│ (Standard-Broadcast-Praxis — Pilot/RDS werden von den meisten │
│ Regulierungsbehörden aus dem Modulationslimit ausgenommen) │
└──────────────────────────┬──────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ STAGE 8: FM-Modulation │
│ │
│ Split-Rate: Composite @ 228kHz ──→ FMUpsampler ──→ IQ @ 2.28MHz│
│ maxDeviation × mpxGain = effektive Deviation │
│ composite=1.0 → ±75kHz Deviation (bei mpxGain=1.0) │
│ │
│ Ausgabe: IQ-Samples (float32) an PlutoSDR via libiio │
└──────────────────────────────────────────────────────────────────┘
| Parameter | Typ | Default | Beschreibung |
|---|---|---|---|
audio.gain |
float | 1.0 | Eingangsverstärkung vor Pre-Emphasis. 1.0 = unity. |
audio.inputPath |
string | "” | WAV-Datei als Quelle (leer = stdin oder Tongenerator) |
Empfehlung: gain: 1.0. Pegel-Kontrolle über outputDrive.
| Parameter | Typ | Default | Bereich | Beschreibung |
|---|---|---|---|---|
outputDrive |
float | 0.5 | 0–10 | Eingangsverstärkung vor Limiter/Clip. Bestimmt wie aggressiv Limiter und nachfolgende Clip-Stufen arbeiten. |
limiterEnabled |
bool | true | — | Aktiviert nur die StereoLimiter-Stufe (5ms/200ms) im L/R-Pfad. Die Hard-Clip-Stufen bleiben aktiv. |
limiterCeiling |
float | 1.0 | 0–2 | Maximum-Amplitude für Audio L/R und Composite. 1.0 = ±75kHz. |
preEmphasisTauUS |
float | 50 | 0/50/75 | Pre-Emphasis Zeitkonstante. 50µs = Europa/CH, 75µs = USA, 0 = aus. |
outputDrive im Detail:
Der Drive bestimmt den Klangcharakter, nicht die Lautstärke (wenn BS.412 aktiv ist):
| Drive | Effekt | Einsatz |
|---|---|---|
| 1–2 | Wenig Kompression, dynamisch, sauber | Klassik, Jazz, Wortbeiträge |
| 3–4 | Moderate Kompression, ausgewogen | Empfohlen für die meisten Formate |
| 5–7 | Aggressive Kompression, dichter Sound | Pop/Rock-Formatradio |
| 8–10 | Maximale Dichte, hörbare Clip-Artefakte | Nicht empfohlen |
Empfehlung: outputDrive: 3.0 für sauberen, broadcast-fähigen Sound.
| Parameter | Typ | Default | Bereich | Beschreibung |
|---|---|---|---|---|
pilotLevel |
float | 0.09 | 0–0.2 | 19kHz Pilot-Amplitude. Direkte Prozentangabe von ±75kHz. 0.09 = 9% = ITU-Standard. |
rdsInjection |
float | 0.04 | 0–0.15 | RDS-Amplitude. Direkte Prozentangabe. 0.04 = 4%. Waveform ist unity-normalisiert. |
stereoEnabled |
bool | true | — | Stereo-Encode an/aus. Aus = nur Mono (L+R)/2, kein Pilot. |
Empfehlung: pilotLevel: 0.09, rdsInjection: 0.04. Nicht ändern ausser es gibt einen guten Grund.
Pilot und RDS sind unabhängig vom OutputDrive — sie bleiben immer bei der konfigurierten Amplitude, egal wie hart das Audio komprimiert wird.
| Parameter | Typ | Default | Beschreibung |
|---|---|---|---|
bs412Enabled |
bool | false | Aktiviert den BS.412 MPX Power Limiter. Pflicht in CH, DE, NL, FR. |
bs412ThresholdDBr |
float | 0 | Power-Limit in dBr. 0 = Standard. +3 = relaxiert. |
Was BS.412 macht: Begrenzt die durchschnittliche MPX-Leistung über ein rollendes 60-Sekunden-Fenster. Reduziert die Audio-Amplitude langsam wenn die Power den Threshold überschreitet. Pilot und RDS werden automatisch vom Power-Budget abgezogen.
Effekt auf den Sound:
Empfehlung: bs412Enabled: true, bs412ThresholdDBr: 0 für BAKOM-Compliance.
| Parameter | Typ | Default | Bereich | Beschreibung |
|---|---|---|---|---|
mpxGain |
float | 1.0 | 0.1–5 | Skaliert die FM-Deviation (nicht den Composite!). Kompensiert DAC/SDR-Hardware-Faktoren. |
maxDeviationHz |
float | 75000 | 0–150000 | Maximale FM-Deviation in Hz. 75000 = Standard. |
compositeRateHz |
int | 228000 | — | Interne DSP-Sample-Rate. 228000 = 12×19kHz (optimal für Pilot-Kohärenz). |
MpxTool-Kalibrierung:
mpxGain: 1.0 setzen (keine Skalierung)Empfehlung: mpxGain: 1.0, maxDeviationHz: 75000. Kalibrierung über MpxTool Ref Level.
| Parameter | Typ | Default | Beschreibung |
|---|---|---|---|
rds.enabled |
bool | true | RDS an/aus |
rds.pi |
string | “1234” | Programme Identification (4-stellig hex). Muss mit BAKOM-Zuteilung übereinstimmen. |
rds.ps |
string | “FMRTX” | Programme Service Name (max 8 Zeichen). Stationsname auf dem Display. |
rds.radioText |
string | "” | Radio Text (max 64 Zeichen). Scrolltext auf dem Display. |
rds.pty |
int | 0 | Programme Type. 0=undefined, 1=News, 3=Info, 10=Pop, 15=Other Music, etc. |
| Parameter | Typ | Default | Beschreibung |
|---|---|---|---|
backend.kind |
string | “file” | "pluto" für PlutoSDR, "soapy" für SoapySDR, "file" für Dateiausgabe |
backend.device |
string | "” | Device-String. PlutoSDR: "usb:" oder "ip:192.168.2.1" |
backend.deviceSampleRateHz |
float | 0 | SDR-Device-Rate. 2280000 = 10× compositeRate (optimal). |
{
"audio": {
"gain": 1.0
},
"rds": {
"enabled": true,
"pi": "BEEF",
"ps": "RADIO-ZH",
"radioText": "Ihr Zürcher Kurzradio",
"pty": 0
},
"fm": {
"frequencyMHz": 100.0,
"stereoEnabled": true,
"pilotLevel": 0.09,
"rdsInjection": 0.04,
"preEmphasisTauUS": 50,
"outputDrive": 3.0,
"limiterEnabled": true,
"limiterCeiling": 1.0,
"bs412Enabled": true,
"bs412ThresholdDBr": 0,
"mpxGain": 1.0,
"compositeRateHz": 228000,
"maxDeviationHz": 75000,
"fmModulationEnabled": true
},
"backend": {
"kind": "pluto",
"device": "usb:",
"deviceSampleRateHz": 2280000
},
"control": {
"listenAddress": "127.0.0.1:8088"
}
}
ffmpeg -i http://stream-url/stream -f s16le -ar 44100 -ac 2 - | fmrtx.exe --tx --tx-auto-start --audio-stdin --audio-rate 44100 --config config.json
Hinweis: Unter Windows cmd.exe verwenden, nicht PowerShell (korrumpiert die Binary-Pipe).
| Parameter | Messung | Soll |
|---|---|---|
| Pilot Level | 9.0% | 9% ✓ |
| RDS Injection | 3.4% | 4% (≈, BPSK-Mittelung) |
| MPX Peak | 105–110% | 100–113% ✓ |
| Guard Band 19kHz | >-80dB | >-80dB (Omnia 11: >80dB) ✓ |
| Audio Bandwidth | flat bis 15kHz | 15kHz ✓ |