はじめに
地上デジタル放送のテレビ局が送出しているものは、突き詰めると「MPEG-TS ストリーム」と「その中に埋め込まれた PSI/SI テーブル」の2つに集約される。
であれば、同じ構造のストリームを自前で組み立てれば、市販のテレビ録画ソフトがそれを "本物のチャンネル" として認識するのではないか —— そう思い立ち、実際に試してみた。
結果として、OBS Studio の映像を ARIB STD-B10 準拠の PSI/SI 付き MPEG-TS に変換する Node.js サーバーを書き、Mirakurun(チューナーサーバー)に自作チャンネルとして認識させることに成功した。
番組表(EPG)、チャンネル名、チャンネルロゴまで、実際の地デジ放送と同じ仕組みで配信している。
なお、本記事では Mirakurun 自体の構築方法については扱わない。
Mirakurun がすでに動作している環境を前提とする。
【コラム】テレビの歴史
日本で最初にテレビ放送を開始したのは NHK(1953年2月1日)。
民放では 日本テレビ(1953年8月28日)が最古となる。
街頭テレビの前に人だかりができた時代の話だ。一方、日本で最も新しいテレビ局は BSJapanext(現・BS10)。
ジャパネットホールディングスの子会社であるジャパネットブロードキャスティングが2022年3月27日に開局した BS デジタル放送局で、2019年の総務省公募で認定を受けて開局に至った。
通販番組は全体の3割程度で、旅・ゴルフ・映画など多ジャンルの番組を放送している。1953年から2022年まで、約70年にわたって新しいテレビ局が生まれ続けてきた。
そして2026年、自宅の Node.js サーバーからまた1局——。
法令遵守について
本記事で紹介するシステムは、すべてローカルネットワーク内で完結しており、実際に電波を発射して公衆送信(放送)を行うものではありません。
日本において、電波を発射してテレビ放送を行うには、以下の法令上の要件を満たす必要があります。
-
電波法(昭和25年法律第131号)
無線局の開設には総務大臣の 免許 が必要であり、免許なく電波を発射することは違法です(電波法第4条)。違反した場合、1年以下の懲役又は100万円以下の罰金が科されます(電波法第110条)。 -
放送法(昭和25年法律第132号)
基幹放送(地上デジタル放送を含む)を行うには、総務大臣の 認定 を受けた基幹放送事業者である必要があります(放送法第93条)。 - (参考)総務省 不法無線局対策の取り組みについて:電波利用ポータル|不法無線局対策
本記事はあくまで MPEG-TS ストリームの技術的構造を学ぶための取り組みであり、生成したストリームは HTTP 経由でローカルの Mirakurun に送信しているに過ぎません。
電波法・放送法に基づく免許や認定を要する公衆送信は一切行っていません。
なお、「ローカルなエリア内で自主放送を流したい!」という要件の場合は、下記の記事のようなOFDM変調器を導入するのが手っ取り早い。
全体構成
Mirakurun のチューナーコマンドとして curl -sN http://<IP>:20772/stream を登録すると、Mirakurun は実際のテレビチューナーと同じようにこのストリームを読み取り、PAT/SDT/NIT を解析してサービス(チャンネル)を検出する。
左が放送用ディスプレイ(OBS に設定しているソース画面)、右が制御用ディスプレイ(OBS の制御画面と、番組送出の確認、Mirakurun の管理画面)という配置で運用している。
OBS から MPEG-TS を受け取る
OBS Studio の「配信」設定でカスタム RTMP サーバーを指定する。
broadcast-server は内部で ffmpeg を RTMP リスナーとして起動し、受け取った映像を MPEG-TS に変換する。
const proc = spawn("ffmpeg", [
"-listen",
"1",
"-i",
`rtmp://0.0.0.0:${port}/live`,
"-c",
"copy", // 再エンコードなし
"-f",
"mpegts",
"-mpegts_flags",
"resend_headers",
"-streamid",
"0:256", // 映像 PID = 0x0100
"-streamid",
"1:257", // 音声 PID = 0x0101
"-flush_packets",
"1",
"pipe:1",
]);
ポイントは -streamid オプション。
PMT で宣言する映像 PID (0x0100) / 音声 PID (0x0101) と、実際のストリームの PID を一致させないと、Mirakurun がサービスを認識しない。
PSI/SI テーブルの生成
地デジ放送の本質は映像データそのものではなく、ストリームに埋め込まれた制御情報テーブルにある。
テレビやレコーダーはこれらを読んで「何チャンネルか」「今何の番組をやっているか」を把握する。
PSI/SI の概要についてはWeblioの解説が参考になる。
また、これらの規格書は ARIB(一般社団法人 電波産業会)から無料でダウンロードできる。
PAT (Program Association Table)
ストリーム全体の目次。
「このストリームにはどのサービス(チャンネル)が含まれていて、それぞれの PMT はどの PID にあるか」を示す。
table_id: 0x00
transport_stream_id: 0x7FF0
| program_number | PMT PID |
|---|---|
| 0x0000 (NIT) | 0x0010 |
| 1024 (serviceId) | 0x1000 |
ARIB TR-B14 では PAT の送出間隔は最大 100ms と規定されている。
PMT (Program Map Table)
個々のサービスに含まれるエレメンタリーストリーム(映像・音声)の PID と種別を宣言する。
service_id: 1024
PCR_PID: 0x0100
| stream_type | ES PID | 内容 |
|---|---|---|
| 0x1B (H.264) | 0x0100 | 映像 |
| 0x0F (AAC) | 0x0101 | 音声 |
PMT が実際のストリーム PID と一致していないと、Mirakurun はサービスを登録しても映像を正しく取り出せない。
-streamid で PID を固定したのはこのため。
SDT (Service Description Table)
チャンネル名(サービス名)やサービス種別を格納する。
テレビのチャンネル一覧に表示される名前はここから来ている。
transport_stream_id: 0x7FF0
original_network_id: 0x7FF0
service_id: 1024
service_descriptor (0x48):
service_type: 0x01 (デジタルTVサービス)
service_provider_name: "自宅ネットワーク"
service_name: "ラビットTV"
logo_transmission_descriptor (0xCF):
logo_transmission_type: 1
logo_id: 1
logo_version: 1
download_data_id: 1
NIT (Network Information Table)
ネットワーク構成情報。
地上デジタル放送の場合、terrestrial_delivery_system_descriptor (0xFA) で物理チャンネルの周波数やガードインターバルなどの伝送パラメータを格納する。
自宅配信ではダミー値を使用。
EIT (Event Information Table)
番組表の本体。present/following(現在/次の番組、table_id 0x4E)と schedule(最大8日分、table_id 0x50〜)の2種類を送出する。
EIT present/following (0x4E):
section 0 (present): 現在放送中の番組
section 1 (following): 次の番組
各イベントエントリ:
event_id
start_time: MJD + BCD時刻
duration: BCD (HH:MM:SS)
short_event_descriptor (0x4D):
ISO_639_language_code: "jpn"
event_name: 番組タイトル
text: 番組説明
content_descriptor (0x54):
ジャンルコード (大分類/中分類)
時刻は MJD(修正ユリウス日)+ BCD 形式というフォーマットで格納される。
Unix タイムスタンプからの変換が必要になる。
// MJD 計算(ARIB STD-B10 付録C準拠)
const mjd =
Math.floor(365.25 * (yp + 4716)) +
Math.floor(30.6001 * (mp + 1)) +
day -
1524.5 -
2400000.5;
番組が登録されていない時間帯は「放送休止」というダミー番組を自動生成して EIT に載せている。
実際の地デジ局も、放送のない時間帯にはこのような扱いをしている。
TOT (Time Offset Table)
現在時刻を JST で送出する。
テレビの時計合わせに使われるテーブル。
送出間隔
各テーブルの送出間隔は ARIB TR-B14(地上デジタルテレビジョン放送運用規定)に定められている。
- PAT:100ms間隔
- PMT:100ms間隔
- SDT:2秒間隔
- NIT:10秒間隔
- EIT p/f:3秒間隔
- EIT schedule:30秒間隔
- TOT:30秒間隔
- CDT:2秒間隔
番組表の設定
EIT の構造は前述の通りだが、実際に番組を登録するにはどうするか。
broadcast-server は broadcast-config.json で番組情報を管理しており、programs 配列に番組を追加するだけで EIT に反映される。
{
"serviceName": "ラビットTV",
"serviceId": 1024,
"programs": [
{
"eventId": 1,
"title": "テスト放送!!",
"description": "テスト放送です。\nあいうえおAbcabc123",
"startAt": 1775257200000,
"duration": 9000000,
"genre": { "lv1": 15, "lv2": 15 }
}
]
}
| フィールド | 内容 |
|---|---|
eventId |
番組を一意に識別する ID(1〜65534) |
title |
番組タイトル(EIT の short_event_descriptor に格納) |
description |
番組の詳細説明(\n で改行可) |
startAt |
開始時刻の Unix タイムスタンプ(ミリ秒) |
duration |
番組の長さ(ミリ秒)。1時間なら 3600000
|
genre |
ARIB ジャンルコード。lv1 が大分類、lv2 が中分類(0〜15) |
JSON から EIT バイナリへの変換
これらの JSON フィールドは、psi.ts の buildEventEntry() 関数で ARIB STD-B10 準拠の EIT イベントエントリに変換される。各フィールドがどのように MPEG-TS のバイト列になるか、具体的に見ていく。
eventId
EIT イベントエントリの先頭 2 バイトにそのまま 16bit 整数として書き込まれる。
header.writeUInt16BE(program.eventId, 0);
startAt
Unix タイムスタンプ(ミリ秒)を MJD(修正ユリウス日)+ BCD 時刻の 5 バイトに変換する。
ARIB STD-B10 付録C で定義されたフォーマットで、上位 2 バイトが MJD、下位 3 バイトが時・分・秒の BCD。
// JST に変換してから MJD を算出
const jst = new Date(d.getTime() + 9 * 60 * 60 * 1000);
const mjd =
Math.floor(365.25 * (yp + 4716)) +
Math.floor(30.6001 * (mp + 1)) +
day -
1524.5 -
2400000.5;
buf.writeUInt16BE(Math.floor(mjd), 0);
buf[2] = (Math.floor(h / 10) << 4) | (h % 10); // BCD hours
buf[3] = (Math.floor(min / 10) << 4) | (min % 10);
buf[4] = (Math.floor(sec / 10) << 4) | (sec % 10);
たとえば 2026年4月3日 23:46 JST なら、MJD = 61304、BCD = 0x23 0x46 0x00 となり、計 5 バイト 0xEF78 234600 がイベントエントリに格納される。
duration
ミリ秒を時・分・秒に分解し、BCD 3 バイトにエンコードする。
6300000ms(1時間45分)なら 0x01 0x45 0x00。
const totalSec = Math.floor(ms / 1000);
const h = Math.floor(totalSec / 3600);
const m = Math.floor((totalSec % 3600) / 60);
const s = totalSec % 60;
buf[0] = (Math.floor(h / 10) << 4) | (h % 10);
buf[1] = (Math.floor(m / 10) << 4) | (m % 10);
buf[2] = (Math.floor(s / 10) << 4) | (s % 10);
title / description
ARIB STD-B24 の 8 単位符号にエンコードされ、short_event_descriptor(descriptor_tag 0x4D)に格納される。
言語コードは "jpn" (0x6A 0x70 0x6E) が付与される。
const shortEvtDesc = Buffer.alloc(2 + shortEvtBodyFixed.length);
shortEvtDesc[0] = 0x4d; // descriptor_tag: short_event_descriptor
shortEvtDesc[1] = shortEvtBodyFixed.length;
// shortEvtBodyFixed = [言語コード3バイト][タイトル長1バイト][タイトル][説明長1バイト][説明]
genre
content_descriptor(descriptor_tag 0x54)に格納される。
lv1 が上位 4 ビット、lv2 が下位 4 ビットに入る。
ARIB STD-B10 付録H にジャンルコードの一覧が定義されており、たとえば lv1: 0(ニュース)、lv1: 7(アニメ)などがある。lv1: 15, lv2: 15 は「その他」を意味する。
contentDesc[2] = (program.genre.lv1 << 4) | (program.genre.lv2 & 0x0f);
contentDesc[3] = 0xff; // user_nibble
EIT の送出タイミング
生成されたイベントエントリは、muxer が 2 種類の EIT セクションに分けて MPEG-TS ストリームに注入する。
-
EIT present/following(table_id
0x4E、3 秒間隔
現在時刻を元にprograms配列から「今放送中の番組」と「次の番組」を選び出し、section 0 / section 1 として送出する。 -
EIT schedule(table_id
0x50、30 秒間隔)
未来の番組を最大 8 日分まとめて送出する。
4096 バイトのセクションサイズ制限を超える場合は自動的に複数セクションに分割される。
番組情報が API 経由で更新されると、updatePrograms() が呼ばれ、EIT の version_number がインクリメントされると同時に EIT p/f と EIT schedule が即座に再送出される。
Mirakurun はバージョン番号の変化を検知して番組表を更新する。
設定ファイルを編集した後は、broadcast-server を再起動するか、REST API で反映できる。
# 設定ファイルごと反映
curl -X PUT http://localhost:20772/api/config \
-H "Content-Type: application/json" \
-d @broadcast-config.json
# 番組を個別に追加
curl -X POST http://localhost:20772/api/programs \
-H "Content-Type: application/json" \
-d '{"eventId":2,"title":"夜の番組","description":"説明","startAt":1775264400000,"duration":7200000}'
管理画面(http://localhost:20772/admin)からも番組の追加・削除ができる。
番組情報を更新すると、EIT の version_number がインクリメントされ、Mirakurun が変更を検知して番組表を更新する。
programs 配列にどの時間帯にも該当する番組がない場合、EIT の present(現在の番組)には「放送休止」というダミー番組が自動的に挿入される。
ARIB 文字コードとの戦い
PSI/SI 内の文字列(チャンネル名・番組名など)は、ARIB STD-B24 で規定された8単位符号でエンコードする必要がある。
最初は UTF-8 のバイト列をそのまま格納していたが、Mirakurun 側の aribts デコーダが JIS X 0208 として解釈するため、「マイチャンネル」が「ゃゃいゃゃぃゃこゃゃか」に化けるという現象が発生した。
aribts のデコーダの動作を追うと、以下のフローであることがわかった。
つまりエンコード側では、UTF-8 テキストを JIS X 0208 のバイト列に変換して格納する必要がある。
function encodeAribString(str: string): Buffer {
// UTF-8 → EUC-JP に変換
const eucBuf = iconv.encode(str, "euc-jp");
const out: number[] = [];
let inKanji = true; // デフォルト: G0=漢字
let i = 0;
while (i < eucBuf.length) {
const b = eucBuf[i];
if (b >= 0xa1 && b <= 0xfe && i + 1 < eucBuf.length) {
// 2バイト文字: EUC-JP の各バイトから 0x80 を引くと JIS コードになる
if (!inKanji) {
out.push(0x1b, 0x24, 0x42); // ESC $ B → G0=漢字に戻す
inKanji = true;
}
out.push(b & 0x7f, eucBuf[i + 1] & 0x7f);
i += 2;
} else {
// ASCII: G0 を ASCII に切り替え
if (b >= 0x21 && b <= 0x7e && inKanji) {
out.push(0x1b, 0x28, 0x4a); // ESC ( J → G0=ASCII
inKanji = false;
}
out.push(b);
i++;
}
}
return Buffer.from(out);
}
EUC-JP は JIS X 0208 の各バイトに 0x80 を足したエンコーディングなので、& 0x7F するだけで JIS コードに戻せる。漢字と ASCII の切り替えには ISO 2022 のエスケープシーケンスを使う。
チャンネルロゴの送出
地デジ放送では、テレビの番組表やチャンネルリストに表示されるチャンネルロゴも MPEG-TS ストリーム経由で配信されている。
CDT (Common Data Table, PID 0x0029) にロゴ画像を載せ、SDT の logo_transmission_descriptor (0xCF) でその存在を告知する。
ロゴのサイズ
ARIB TR-B14 表4-1 によると、地デジで送出されるロゴには6種類のサイズパターンがある。
| logo_type | 名称 | サイズ |
|---|---|---|
| 0x00 | SD4:3 スモール | 48×24 |
| 0x01 | SD16:9 スモール | 36×24 |
| 0x02 | HD スモール | 48×27 |
| 0x03 | SD4:3 ラージ | 72×36 |
| 0x04 | SD16:9 ラージ | 54×36 |
| 0x05 | HD ラージ | 64×36 |
今回は「HD ラージ」サイズ(64×36)で、オリジナルのアイコンを用意してみた。
ARIB CLUT パレット
ロゴ画像はフルカラー PNG ではなく、ARIB 固定 CLUT(Color Look-Up Table) にインデックス化されたパレット PNG として格納される。
@chinachu/aribts パッケージに含まれる logo_clut.js に 128 色のパレット定義があり、受信側はこのパレットを使って PNG を復元する。
送出側では、任意の画像をこのパレットに最近傍色で減色する。
function nearestClutIndex(r: number, g: number, b: number, a: number): number {
let best = 0;
let bestD = Infinity;
for (let i = 0; i < logoClut.length; i++) {
const [cr, cg, cb, ca] = logoClut[i];
const d = (r - cr) ** 2 + (g - cg) ** 2 + (b - cb) ** 2 + (a - ca) ** 2;
if (d < bestD) {
bestD = d;
best = i;
}
}
return best;
}
生成される PNG は特殊な構造をしている。
IHDR チャンクの直後に PLTE/tRNS チャンクが含まれない状態で CDT に格納され、受信側の TsLogo.decode が ARIB CLUT から PLTE/tRNS を挿入して完全な PNG に復元する。
- 送出側が格納する PNG
- PNG Signature → IHDR → IDAT → IEND
- (PLTE/tRNS なし)
- 受信側が復元する PNG
- PNG Signature → IHDR → PLTE → tRNS → IDAT → IEND
- (ARIB CLUT から PLTE/tRNS を生成して挿入)
Mirakurun への登録
Mirakurun は tuners.yml でチューナーを、channels.yml でチャンネルを管理している。
自作チャンネルを登録するには、この2つの設定ファイルにエントリを追加する。
# tuners.yml
- name: rabbit-tv-broadcast
types:
- SKY
command: curl -sN http://localhost:20772/stream
# channels.yml
- name: ラビットTV
type: SKY
channel: "100"
serviceId: 1024
ここで重要なのが type: SKY の指定。
Mirakurun は "GR" | "BS" | "CS" | "SKY" の4種類のチャンネルタイプをサポートしている。
当初 type: GR で登録したところ、Mirakurun がチャンネルスキャン時に実際の地デジチューナー(PX-S1UD)を使ってチャンネル50を受信しようとしてしまい、自作ストリームが読まれないという問題が発生した。
スカパー!チューナーを使っていない環境であれば SKY を指定することで、既存の GR チューナーとの競合を回避できる。
物理チャンネル番号について
channels.yml の channel フィールドには '100' を指定している。
日本の地上デジタル放送では物理チャンネル 13〜52(UHF 470〜710MHz、1チャンネルあたり6MHz幅)が使用されている。
かつては 53〜62ch も割り当てられていたが、2012年に700MHz帯の携帯電話への再編により廃止された。
100 は実在しない物理チャンネル番号だが、Mirakurun において channel はチューナーコマンドに渡される識別子に過ぎない。
rabbit-tv-broadcast のチューナーコマンドは curl -sN http://.../stream であり、channel の値は実際には使われない。
実在の物理チャンネル番号(13〜52)と被らない番号を使うことで、設定ファイルを見たときに自作チャンネルであることが一目でわかるようにしている。
参考:ボクにもわかる地上デジタル - チャンネル表 、 テレビ周波数チャンネル - Wikipedia
CRC32
PSI/SI テーブルの末尾には CRC32 チェックサムが付加される。
ここで使う CRC32 は一般的な CRC32 (zlib) とは多項式が異なる。
- 一般的な CRC32 (zlib, PNG):多項式
0xEDB88320(反転表現) - MPEG-2 PSI/SI 用 CRC32:多項式
0x04C11DB7(ISO 13818-1)
// MPEG-2 CRC32 — 通常の CRC32 とは多項式が異なる
for (let j = 0; j < 8; j++) {
if (crc & 0x80000000) {
crc = ((crc << 1) ^ 0x04c11db7) >>> 0;
} else {
crc = (crc << 1) >>> 0;
}
}
この違いに気づかず一般的な CRC32 を使うと、Mirakurun がテーブルを不正データとして破棄する。
TS パケットの構造
MPEG-TS は固定長 188 バイトのパケットで構成される。
各パケットの先頭4バイトがヘッダで、sync byte (0x47)、PID、continuity counter などが含まれる。
┌─────────┬───────────────────────────────────┐
│ 4 bytes │ 184 bytes payload │
│ header │ │
├─────────┼───────────────────────────────────┤
│ 47 │ PSI/SI セクション or 映像/音声 │
│ PID │ │
│ CC │ │
└─────────┴───────────────────────────────────┘
PSI/SI セクションが 184 バイトに収まらない場合は複数パケットに分割される。最初のパケットには pointer_field (0x00) が付き、payload_unit_start_indicator が 1 にセットされる。continuity_counter は PID ごとに 0〜15 で巡回する。
入力ストリームのフィルタリング
OBS → ffmpeg → broadcast-server と流れてくる MPEG-TS には、ffmpeg が生成した PAT/PMT などのテーブルが含まれている。
これをそのまま出力すると自前の PSI/SI と衝突するため、muxer で特定の PID を除去している。
// 入力ストリームの PAT/PMT/SDT/NIT/EIT/TOT は除去し、自前のものに差し替え
const pid = ((packet[1] & 0x1f) << 8) | packet[2];
if (
pid === PID_PAT ||
pid === PID_PMT ||
pid === PID_NIT ||
pid === PID_SDT ||
pid === PID_EIT ||
pid === PID_TOT ||
pid === PID_CDT ||
pid === PID_NULL
) {
continue; // 除去
}
映像 (0x0100) と音声 (0x0101) のパケットだけを通過させ、自前で生成した PSI/SI パケットを定期的に挿入する。
動作確認
Mirakurun を起動し、仮想チューナー(curl で HTTP ストリームを読むエントリ)が認識されていることを確認する。
チャンネルスキャンが完了すると、Mirakurun のログに以下のように表示される。
service (create) {
"id": 3275201024,
"serviceId": 1024,
"networkId": 32752,
"name": "ラビットTV",
"type": 1,
"logoId": 1,
...
"channel": { "type": "SKY", "channel": "50" }
}
また、Services 欄にチャンネルアイコンと設定した各項目の値が表示される。
Mirakurun の API にアクセスして、チャンネルや番組表が正しく認識されているか確認できる。
Mirakurun のデフォルトポートは 40772。
# チャンネル一覧(自作チャンネルが登録されているか確認)
curl http://localhost:40772/api/services
# 番組表(EIT が正しく受信されているか確認)
curl http://localhost:40772/api/programs?serviceId=1024
# チャンネルロゴ
curl http://localhost:40772/api/services/3275201024/logo
/api/services に自作チャンネルが表示され、/api/programs に番組表が載っていれば成功。
EPGStation などの録画管理ソフトからも通常の地デジチャンネルと同じように見える。
以下は、実際に番組映像が受信・再生できている様子の一例。
番組表も問題なく受信できていた。
ハマったポイント
1. curl のバッファリング
Mirakurun のチューナーコマンドで curl -s http://... とすると、curl がデフォルトで出力をバッファするため、PSI/SI テーブルが Mirakurun に届くのが大幅に遅れる。
-N (--no-buffer) を付けることで解決。
2. チューナータイプの競合
type: GR で登録すると、Mirakurun が実際の地デジチューナーを使って自作チャンネルのチャンネル番号を受信しようとする。
実チューナーと同じタイプにしてはいけない。
3. ARIB 文字コードの文字化け
UTF-8 をそのまま PSI/SI に埋め込むと、aribts のデコーダが JIS X 0208 として解釈して文字化けする。
UTF-8 → EUC-JP → JIS(0x80 bit strip)→ ISO 2022 エスケープシーケンス付きで格納する必要がある。
4. services.json / ロゴのキャッシュ
Mirakurun はサービス情報とチャンネルロゴを /usr/local/var/db/mirakurun/ 以下にキャッシュする。
設定変更後にキャッシュが残っていると古い情報が返り続けるため、services.json や {networkId}_{logoId}.png を削除して再起動する必要がある。
sudo mv /usr/local/var/db/mirakurun/services.json /usr/local/var/db/mirakurun/services_backup.json
sudo rm -rf /usr/local/var/db/mirakurun/logo-data/32752_1.png
curl -X PUT http://localhost:40772/api/restart
参考資料
- ARIB STD-B10 — デジタル放送に使用する番組配列情報の標準規格
- ARIB STD-B24 — データ符号化方式と文字符号化方式
- ARIB TR-B14 — 地上デジタルテレビジョン放送運用規定
- Mirakurun (GitHub) — 地デジチューナーサーバー
- @chinachu/aribts (npm) — ARIB TS パーサー / デコーダー
- TV規格: ISDB(ARIB)メモ|まくろぐ — ロゴタイプのサイズ一覧など
- TVTest ドキュメント — 放送チャンネルロゴの取得について
- ISO 13818-1 — MPEG-2 Systems(TS パケット構造、CRC32 多項式の定義)

