π°π· νκ΅ μ΅μ΄ Β· π μΈκ³ μ΅μ΄ β μμ Rust λ‘ λ§λ λ©λͺ¨λ¦¬ μμ DAB Mode I OFDM λμ½λ / T-DMB μμ κΈ°.
YTNγλ΄μ€νΉλ³΄γλ₯Ό dab-rs κ° λ¨λ μΌλ‘ λμ½λν ν μ₯λ©΄ β Airspy Mini μ 3 MSPS
int16raw I/Q β OFDM λκΈ°/CFO/FFT β FIC β MSC β 16-μ¬λ‘― Forney μκ°-λμΈν°λ¦¬λΈ β EEP λνν¬μ²/λΉν°λΉ β PRBS β RS(204,188) β MPEG-TS β H.264, λ§μ§λ§ κ·Έλ¦Όλ§ ffmpeg μ λ§‘κ²Όλ€. fudge factor λ μκ³ magic number λ μλ€. λͺ¨λ μμλeti-stuffλλ DAB/DVB νμ€μμ μ λνλ€. μ½κ°μ λ§€ν¬λ‘λΈλ‘ μν°ν©νΈλ μ΄ μΊ‘μ²μ SNR μμ t=8 μ μ νκ³λ₯Ό μ΄κ³Όν RS(204,188) λΈλ‘ 6.9% μ μ νν μΌμΉνλ€.
| νλͺ© | νν© |
|---|---|
| νκ΅ μ΅μ΄ | μμ Rust λ‘ μμ±λ νκ΅ν T-DMB μμ κΈ°. νκ΅ μ±λ(K8B, YTN DMB)μμ raw I/Q β MPEG-TS β H.264 κΉμ§ end-to-end κ²μ¦ μλ£. |
| μΈκ³ μ΅μ΄ | Rust λ‘ μμ±λ λ©λͺ¨λ¦¬ μμ DAB Mode I OFDM λμ½λ. WebAssembly λ°°ν¬λ₯Ό μ μ‘°μ€ β unsafe 0μ€, C/C++ μμ‘΄μ± μμ. |
| κ²μ¦ | C++ eti-stuff μ byte λ¨μλ‘ λλ±. fudge factor 0κ°, magic number 0κ°. λͺ¨λ μμλ spec λλ eti-stuff μ μΆμ κ°λ₯. |
| λ‘λλ§΅ | π WASM μΉλ²μ β λΈλΌμ°μ μμ λμνλ μΈκ³ μ΅μ΄μ μΉ DMB μμ κΈ°. λ³λ μ±/νλ¬κ·ΈμΈ μμ΄ URL λ§μΌλ‘ λΌμ΄λΈ DMB μμ². |
60μ΄ λΌμ΄λΈ μΊ‘μ² β MP4:
# 1) Airspy Mini λ‘ K8B(YTN DMB) 60μ΄ μΊ‘μ²
airspy_rx -f 183.008 -a 3000000 -t 2 -l 14 -m 15 -v 12 -b 0 \
-n 180000000 -r /tmp/live.iq
# 2) FIC: μμλΈ + μλΉμ€ λͺ©λ‘ νμΈ
dab fic-iq /tmp/live.iq
# β fib_ok=7424/7524 (98.7%), EId=0xE040, label="YTN DMB", 5 services
# 3) MSC: SubCh 1 (mYTN λΉλμ€) β MPEG-TS
dab msc-iq --scid 1 --ts /tmp/live.ts /tmp/live.iq
# β ts_packets=12872, rs_corrected=7240 (98.7%)
# 4) (μ ν) SL ν€λ strip + SPS prepend β MP4
python3 tools/extract_sl_h264_v2.py /tmp/live.ts --video-pid 0x113 \
--out /tmp/live.h264
cat sps_pps.bin /tmp/live.h264 > /tmp/live_sps.h264
ffmpeg -r 30 -i /tmp/live_sps.h264 -c copy /tmp/live.mp4κ²°κ³Ό: 320Γ240 H.264 Baseline, 1784 frames @ 30 FPS, 60μ΄ μμ.
Week 3e + Week 4 μλ£ β raw I/Q λΆν° MP4 κΉμ§ fudge-free end-to-end λμ.
| ν¬λ μ΄νΈ | μν | μν |
|---|---|---|
dab-ofdm |
Mode I OFDM 볡쑰기 (ν΅μ¬ κΈ°μ¬) | β fudge-free 90%+ (sim5, sim4, k8b_v4) |
dab-iq |
νμΌ I/Q reader (Cs8/Cs16Le/Cf32Le, JSON sidecar) | β done; libairspy μ§κ²° streaming μ μΆν |
dab-fic |
FIB CRC-16, FIG 0/xΒ·1/x β Ensemble | β done β byte-identical vs eti-stuff |
dab-viterbi |
Rate-1/4 K=7 punctured λΉν°λΉ + EEP/FIC depuncture | β done |
dab-descramble |
μλμ§ λΆμ° PRBS (xβΉ + xβ΅ + 1) | β done |
dab-msc |
MSC sub-channel byte μΆμΆ | β done |
dab-eti |
ETI(NI, G.703) frame parser (ETSI EN 300 799) | β done |
dab-fec |
T-DMB outer FEC: sync-aligned Forney + RS(204,188) | β done |
dab-cli |
dab fic-iq + dab msc-iq CLI λ°μ΄λ리 |
β done |
| μΊ‘μ² | κ²½λ‘ | FIB CRC | RS(204,188) | MP4 frames |
|---|---|---|---|---|
| 60s live capture | cs16le β LinearResampler β μ 체 chain | 98.7% (7424/7524) | 98.7% (7240/7332) | 1784 @ 30 FPS |
k8b_v4.iq (20s) |
cs16le β LinearResampler | 87.4% (2193/2508) | 92.8% (3383/3646) | 475 @ 23.75 FPS |
sim5_resampled.cf32 (12s) |
cf32 bypass | 90.4% (1704/1884) | 96.8% (1381/1426) | β |
μν₯μ ꡬμΆ: κ° λ¨κ³λ λ€μ λ¨κ³κ° μμ΄κΈ° μ μ reference μ κ²μ¦λλ€.
Oracle μ C++ eti-stuff (byte-identical λͺ©ν) μ Python airspy-mini-dmb.
- Week 1 β Outer FEC + ETI plumbing.
dab-eti(ETI(NI) frame parser),dab-msc(sub-channel extraction),dab-fec(sync-aligned Forney deinterleaver + RS(204,188)). Python reference μ byte-identical. - Week 2 β Inner FEC.
dab-viterbi(rate-1/4 K=7 λΉν°λΉ + EEP depuncturing),dab-descramble(energy-dispersal PRBS).eti-stuffverbatim ν¬ν . - Week 3a-d β
dab-iq+ OFDM stages 1-7. 3MSPS β 2.048 MSPS resampler, null detect, CP autocorrelation sync, NCO + integer CFO, FFT, differential reference, Ο/4-DQPSK demap. - Week 3e β Fudge-free FIC chain. 25κ° μ¬λΌμ΄μ€ μ μ§λ¨ β integer-CFO λ₯Ό
rotate_spectrum λμ μκ°-μμ NCO λ‘ ν΅ν© (
eti-stuffμ μ νν λμΌν ꡬ쑰). λͺ¨λ magic number μ κ±°. K8B μμ FIB CRC 87-90%, EId 0xE040, 5 services μλ³.eti-stufflinear-interp resampler μ΄μ (+2.5 dB SNR). - Week 4 β MSC sub-channel decode. 75 data symbol demap, 16-μ¬λ‘― Forney μκ° λμΈν°λ¦¬λ² (eti-generator.cpp:207 verbatim), EepProtection + KoreanTDmbOuterFec wiring. K8B SubCh 1 β MPEG-TS β H.264 β MP4 (320Γ240, 1784 frames, 30 FPS). λΌμ΄λΈ 60μ΄ μΊ‘μ² κ²μ¦ μλ£.
- Week 5 β libairspy FFI + streaming. νμΌ I/O λμ Airspy Mini μ§κ²°. Manual gain L14/M15/V12 default (μ€μΈ‘ -G 0 λλΉ 20Γ RS κ°μ ). μꡬ ring buffer κΈ°λ° streaming pipeline (νμ¬λ batch).
- Week 6 β Performance margin. SNR threshold νλ, lock μκ° μ΅μ ν,
criterionλ²€μΉλ§ν¬ (throughput, latency, memory).
- Week 7 β WASM μΉλ²μ (β
μΈκ³ μ΅μ΄).
wasm-packλΉλ + λΈλΌμ°μ SDR μ°κ²° (WebUSB λ‘ Airspy Mini μ§κ²°, λλ 미리 λ Ήνλ .iq νμΌ μ λ‘λ).- λΌμ΄λΈ λ°λͺ¨ νμ΄μ§: μ¬μ©μκ° URL λ§ μ΄λ©΄ μ±/νλ¬κ·ΈμΈ μμ΄ λΈλΌμ°μ μμ DMB μμ² κ°λ₯
- λͺ¨λ°μΌ PWA: μλλ‘μ΄λ/iOS Chrome μμ WebUSB μ§μ μ SDR dongle μ°κ²° κ°λ₯ (νκ΅ K-block 6 μ±λ μ€μΊ β μ±λ μ ν β λΌμ΄λΈ μμ)
- νμ /κ΅μ‘ μλ£ νμ©: spec.html β μ€μκ° OFDM constellation/FFT bin μκ°ν (signal-processing κ°μμ©)
- Week 8 β λ Όλ¬Έ. SoftwareX / JOSS / IEEE BMSB νκΉ β "dab-rs: A Memory-Safe Software-Defined DAB Mode I Demodulator in Rust with WebAssembly Deployment."
| νλΌλ―Έν° | κ° |
|---|---|
| λ΄λΆ sample rate | 2.048 MSPS |
| Useful symbol | 2048 samples (1 ms) |
| Guard interval | 504 samples |
| Sub-carriers | 1536 (β768..+768, DC null) |
| Modulation | Ο/4-DQPSK (differential) |
| Inner FEC | rate-1/4 conv, K=7, polys (0o133, 0o171, 0o145, 0o133) |
| Outer FEC (T-DMB) | RS(204,188) DVB params + Forney TI (N=12, M=17) |
ν¬ν κ³Όμ μμ λ§μ£ΌμΉ λ―ΈμΈν ꡬν λν μΌλ€. λ€μ κΈ°μ¬μ(λλ λ€μ λ Όλ¬Έ reviewer)κ° λ―Έλ¦¬ μμλ¬μΌ ν μ¬νλ€.
eti-stuff λ coarseCorrector + fineCorrector λ₯Ό νλμ μκ°μμ NCO
λ‘ ν¨κ» mix νλ€ (getSamples(coarseCorrector + fineCorrector)). μ μ-CFO
Ξ΄ κ° λ§€ μ¬λ³Όλ§λ€ NCO μμ μ§ν 2Ο·δ·T_s/T_u β 1.55 rad/sym (Ξ΄=+1) λ₯Ό
λΆμ¬νλλ°, dab-rs μ μ΄κΈ° ꡬνμ μ μ-CFO λ₯Ό rotate_spectrum μΌλ‘
post-FFT μ²λ¦¬ν΄μ κ·Έ per-symbol μμμ΄ λΉ μ‘λ€. μνμ μΌλ‘ κ°μ 보μ΄μ§λ§,
Ο/4-DQPSK μ°¨λ 볡쑰 λ¨κ³μμ constellation μ΄ 1.55 rad νμ λ μ±λ‘ λμ°©
β λΉνΈ λ§€ν wrong.
fix: λ°μ΄ν° μ¬λ³Όμ cfo_hz + δ·1000 μ NCO μ ν΅ν©ν΄ mix, rotate μλ΅.
PRS λ§ rotate (Ξ΄ λ₯Ό μΈ‘μ νλ €λ©΄ PRS FFT κ° λ¨Όμ νμνκΈ° λλ¬Έ). κ²°κ³Ό: sim5
FIB CRC 4% β 90.4%, fudge-free.
dab-ofdm μ μ΄κΈ° resampler λ polyphase FIR (Blackman-sinc) μ΄μλλ°
νκ³ SNR(11 dB K8B) μμ decoding μ λ²½ μλ. eti-stuff μ airspy-handler.cpp:157-162
linear-interp resampler λ₯Ό verbatim μ΄μ (LinearResampler struct).
κ°μ μ
λ ₯μμ band_ratio +2.5 dB, k8b_v4.iq cs16le FIB 0/2496 β 2193/2508.
eti-stuff eti-generator.cpp:207:
const int16_t interleaveMap[] = {0,8,4,12,2,10,6,14,1,9,5,13,3,11,7,15};λΉνΈ μμΉ i & 0xF λ§λ€ 16-μ¬λ‘― ring buffer μ μλ‘ λ€λ₯Έ μ¬λ‘―μμ μ½μ΄μ
λμΈν°λ¦¬λΈνλ€. CIF 1κ°λΉ 55296 λΉνΈ, 18κ° OFDM μ¬λ³Ό β 1 CIF, 1 DAB frame
= 4 CIFs. 15-CIF warmup ν μΆλ ₯μ΄ valid.
κ°μ indoor K8B μν
λ μ
μ
μμ 17-config gain sweep κ²°κ³Ό: hardware AGC
(-G 0) λ small-signal regime μμ under-amplify νλ€ (firmware κ° mixer
λ working level λ‘ μ‘μ§λ§ VGA λ default 10 μΌλ‘ κ³ μ β ~20 dB headroom
λ―Έμ¬μ©). Manual LNA 14 / Mixer 15 / VGA 12 κ° 25μ΄λΉ 4619-4852 RS
corrected blocks λ₯Ό μ°μΆ, -G 0 μ 241 λλΉ 20Γ κ°μ .
fibquality 100 ν¬ν. Live air-receive μ manual L14/M15/V12 κΈ°λ³Έ κΆμ₯.
DAB null symbol μ 96ms frame λ§λ€ ~0 envelope μ΄μ΄μΌ νλλ°, K8B λ
single-frequency network νκ²½μμ 5κ° μ‘μ ν(λ¨μ°/κ΄μ
μ°/μ©λ¬Έμ°/κ΄κ΅μ°/μ΄μ μ°)
μ΄ μκ°μ°¨λ‘ λμ°©ν΄ null κ°κ²©μ΄ λ€λ₯Έ μ‘μ νμ μ νΈλ‘ μ±μμ§λ€. min/Β΅ β
0.72-0.78 (κ΅κ³Όμ κ° 0.1-0.3 μ λ€λ¦). κ³ μ threshold λ λͺ¨λ null μ λμΉ¨.
NullDetector λ adaptive threshold p1 + 0.30Β·(p99 β p1) λ₯Ό smoothed
envelope μ μ μ©ν΄ ~7 dB SNR μμλ 96ms cadence ν볡.
eti-stuff μ viterbi-handler.cpp μ£Όμμ +255 β bit 0 μ΄λΌ μ νμμ§λ§
trellis metric update λ₯Ό μΆμ νλ©΄ +255 β bit 1 κ° μ€μ λμ. dab-rs
λ verbatim ν¬ν
μ΄λΌ μ€μ λμμ κ·Έλλ‘ λ°λΌκ°λ©°, λ°λΌμ OFDM λλ§€νΌλ
κ°μ + β 1 convention μΌλ‘ soft bit λ₯Ό emit ν΄μΌ νλ€.
Airspy Mini μ native USB μΆλ ₯μ 12-bit ADC μνμ 16-bit μμ packed
νλ€ (zero-extended Complex<i16> κ° μλ). libairspy κ° transparent
νκ² unpack ν΄μ Complex<f32> λλ Complex<i16> λ‘ λ
ΈμΆνλ€. λ°λΌμ
dab-iq-airspy (Week 5) λ pure-Rust libusb λμ bindgen FFI λ‘ libairspy
λ₯Ό νΈμΆνλ€. μ§μ libusb κ²½λ‘λ unpacking + κ²μΈ stage μ μ΄ (LNA/Mixer/VGA)
- bias-tee ν κΈ λͺ¨λ μλ‘ derive ν΄μΌ νλ―λ‘ λΆλ΄μ΄ ν¬λ€.
Rust stable (1.83+). macOS κΈ°μ€:
brew install fftw libsndfile libsamplerate pkg-config libusb airspy
cargo build --workspace --release
cargo test --workspace --release# λΌμ΄λΈ μΊ‘μ²
airspy_rx -f 183.008 -a 3000000 -t 2 -l 14 -m 15 -v 12 -n 180000000 -r live.iq
# FIC (μμλΈ + μλΉμ€ λͺ©λ‘)
dab fic-iq live.iq
# MSC (νΉμ sub-channel β MPEG-TS)
dab msc-iq --scid 1 --ts mYTN.ts live.iq
# λ€λ₯Έ format/rate
dab fic-iq --format cf32le --rate 2048000 pre_resampled.cf32airspy-mini-dmb repo (Git LFS) μ reference captures (k8b_v4.iq +
k8b_v4.eti + sim5_resampled.cf32 λ±) μ‘΄μ¬. λ‘컬 κ²½λ‘ μ§μ ν ν΅ν© ν
μ€νΈ:
export DAB_RS_K8B_V4_IQ=/path/to/k8b_v4.iq
cargo test -p dab-cli --test k8b_v4_fic_iq -- --include-ignoredκΈ°λ μμΉλ tests/golden/ μ κΈ°λ‘λμ΄ μλ€.
dab-rs λ production-quality SDR μννΈμ¨μ΄μΈ λμμ νλΆ/λνμ ν΅μ Β·λ°©μ‘Β·
μ νΈμ²λ¦¬ κ°μμ© κ΅μ¬λ‘ μ€κ³λλ€. κ° λ¨κ³κ° isolated crate + unit tests +
diagnostic dump λ‘ λΆλ¦¬λμ΄, λ€μ μμ°μ΄ λͺ¨λ κ°λ₯νλ€:
- OFDM λκΈ° μκ°ν:
docs/diag/cp_autocorr_manual.pyκ° CP μκΈ°μκ΄ metric μ plot ν΄ fine timing + fractional CFO μΆμ μ λμ μ리 λ ΈμΆ - FFT bin alignment:
docs/diag/fft_by_pos.pyκ° sync_pos κΈ°λ°μΌλ‘ dab-rs FFT μ eti-stuff FFT λ₯Ό sample-aligned λΉκ΅ (|cc|=0.9994 λλ¬ κ²μ¦) - μκ° λμΈν°λ¦¬λ² μμ λΆμ:
docs/diag/sync_pos_compare.pyκ° PRS alignment μ Β±T_s oscillation μ dump - outer FEC visualization:
crates/dab-viterbi/examples/encode_dump.rsverify_expected_soft.rsκ° Python β Rust convolutional encode μ bit-identity νμΈ
κ° λ°κ²¬(Discovered subtlety #1-#7) μ reference μμ μΈ‘μ β κ°μ€ β μ νν
μμ cycle λ‘ λμΆλλ€ (Week 3e μ 25 μ¬λΌμ΄μ€ = docs/diag/ μ μ§λ¨ μΈνλΌ).
μ½λ: MIT. Reference captures λ λ³λ link, redistribute νμ§ μμ (CC-BY-4.0).
μ΄ νλ‘μ νΈλ₯Ό νμ μλ£μμ μΈμ©νλ€λ©΄:
@software{dab_rs_2026,
author = {{zobithecat}},
title = {{dab-rs: A Memory-Safe Software-Defined DAB Mode I Demodulator
in Rust with WebAssembly Deployment}},
year = {2026},
url = {https://github.com/zobithecat/dab-rs},
note = {The first pure-Rust DAB OFDM decoder and the first
T-DMB receiver implementation in Rust.}
}π°π· νκ΅ μ΅μ΄ Β· π μΈκ³ μ΅μ΄ β Made with β€οΈ and cargo build.
