#3 は、C/Nの壁に阻まれて終わった。FECは雑音床を超えられず、誤り訂正が効かない ── 較正でそれを証明したところまでだった。今回はその壁を物理で殴って越え、そこから RS復号・エネルギー逆拡散まで一気に解いて、ついに ── 自作の復調器で、実際のテレビが映った。IQサンプルから、フルスクラッチのRustコードだけで、生の地デジ電波が動く映像になるまでの最終回。
壁を殴る ── 付属アンテナから、壁のTV端子へ
#3までは付属のロッドアンテナ。床から+11dBしか出ず、EVMは98%。ここが全ての元凶だった。そこで 壁のTVアンテナ端子に、F型→SMAの変換をかませて直結した。信号が強いので、逆にゲインは最大49.6ではなく30に下げる(強すぎるとADCが飽和して崩れる)。録り直して同じ処理を流すと、数字が一変した:
付属アンテナ 壁アンテナ
SPコヒーレンス 0.84 → 1.000
EVM 98% → 16.3%
FEC一致率 0.894 → 1.00000 ← 誤り0でロック
Viterbiが完璧にロックした。#3の較正で「雑音床0.928を超えない」と突き止めた、まさにその壁を越えた瞬間だ。ビットはもう1つも間違っていない。あとは、この綺麗なビット列をTSパケットに組み直すだけ ── のはずだった。
TSパケットの影 ── 0x47は出た、でも中身が読めない
Viterbiの出力をバイトに詰め、Forneyのバイトデインターリーブをかけると、MPEG-TSの同期バイト 0x47 が、204バイトごとに命中率100%で立った。TSパケットの骨格は確かに出た。ところが、パケットの中身(ペイロード)をリードソロモン(204,188)にかけると、1ブロックも符号語にならない。ビットは完璧なのに、だ。
ここで危うく「同期は出た」で満足しかけた。だが罠がある:Forneyインターリーバは同期バイトを遅延0の枝に通すので、0x47は何もしなくても204周期で生き残る。つまり 0x47 が並んでも、ペイロードが正しく組み直せている保証にはならない。合成データで自分のインタ↔デインタが逆変換になることは確認済み。なのに実データが通らない。丸一日、ここで唸った。
二つの取り違え ── 順序と、周期
詰まったら参照実装(gr-isdbt)のブロック接続を睨む。すると受信の並びが byte-deint → エネルギー逆拡散 → RS になっている。DVBは「スクランブル→RS」だが、ISDB-Tは順序が逆で、RSの後に全体をスクランブルしている。だから受信側はRSより先に逆拡散しないと符号語に戻らない。僕はRSを先にかけていた。順序を直すと ── まだ13%。惜しい、でも足りない。
13%の内訳を直接ダンプして眺めた。すると成功ブロックが8個だけ連続して、あとは崩れている。これは「PRBSのリセット周期が、僕の思っている8ブロックではない」というサインだ。周期を8に決め打ちしていたのを、広く総当たりにした。そして周期を64付近にした瞬間 ── パターンが ...........######################## と延々と成功に変わった。決め手は、成功ブロックのPIDを表示したこと:
成功ブロックのPID:
0x1fff ← ヌルパケット(TSの定番パディング)
0x0151 0x0152 0x0150 ← 1セグのサービス(映像・音声・PCR)
0x0001 0x0010 0x0024 ← PSI/SI(番組情報テーブル)
RS復号成功率: 99.7%(3271/3282 パケット)
0x1fff と 0x0151 が並んだ瞬間、鳥肌が立った。これはまぎれもない、本物のMPEG-TSだ。ランダムなノイズからは絶対に出ない、放送の構造そのもの。
そして、テレビが映った
出来上がった .ts を ffprobe に食わせる:
Input: MPEG-TS (MPEG-2 Transport Stream), 3 streams
Stream #0: Audio: aac, 48000 Hz, stereo
Stream #1: Video: h264, 320x180 ← ワンセグ解像度そのもの
Stream #2: (データ/字幕)
H.264の映像 320×180 と、AACの音声。ワンセグの規格そのものだ。最後に ffmpeg で1フレーム抜き出す。IQサンプルから、②同期→③等化→④TMCC・デインターリーブ→⑤Viterbi+RS→⑥逆拡散、その全部を自分で書いたコードだけを通り抜けてきた、生の地デジ電波の、その画がこれ:

映った。本当に、テレビが映った。 u8のIQバイト列でしかなかったものが、OFDMのシンボルになり、QPSKの点になり、ビットになり、パケットになり、H.264になって、人の顔と、時計と、テロップになった。全部、この手で書いたRustのコードの中で起きたことだ。
完成 ── ①から⑥まで
- ① RF入力(rtl_sdr 生IQ)… ✅
- ② OFDM同期(CP自己相関)… ✅
- ③ チャネル等化(スキャッタードパイロット)… ✅ QPSK
- ④ TMCC復号+周波数/時間/ビットデインターリーブ+QPSKデマップ … ✅
- ⑤ FEC(Viterbi 2/3 + RS(204,188))… ✅
- ⑥ エネルギー逆拡散 → MPEG-TS出力 … ✅ H.264 320×180 が再生できた
振り返ると、この最終回で効いたのは全部「数字を疑い、直接見る」だった。「0x47が出た」で止まらず、ペイロードをRSで検算する。「13%」で諦めず、成功パターンを可視化して周期のズレに気づく。PIDを表示した瞬間に勝ちが見えた。#1の「ロックした≠正しくロックした」から始まったこの姿勢が、最後まで通用した。
ワンセグは無スクランブル。だから復調に成功すれば、こうして本当に映る。GNU Radioに頼らない、現代のRust実装 ── 調べた限り誰もやっていなかったこの一台が、いま実電波で動いている。次は WASM+WebUSB でブラウザに載せて、「URLを開くとテレビが映る」ところまで持っていきたい。……が、それはまた別の話。まずは、映った。それで十分だ。
コメントを残す