TSパケットからはじめる(2) - PAT/PMT
PESとPSIを判断する
いくつかのPIDは固定番号のものがあり、これらの固定は全てPSIとなります。固定番号の1つにPAT(PID=0x00)と呼ばれるものがありPAT(Program Association Table)はPSIとなります。
PATからPMT(Program Map Table)をPIDを取得できます。PMTからPES一覧を取得することができます。このPMTに含まれないPIDはPSIとなります。
PAT(Program Association Table)取得
PAT構造は以下のようになり、複数パケットに分割されて送信されることはありません(見たことない)。今回は試しにPATをデコードしてみたいと思います。
![](https://assets.st-note.com/img/1643350588455-P7ahHoS3Ht.png?width=800)
PAT構造から取得したい情報は2つです。1つ目はselection_length、2つ目はprogram_map_PID(PMTのPID)です。selection_lengthは先頭から2バイト目の下位4ビットと3バイト目を組みわせた12ビットとなります。
下記はPATを含むTSパケットとなります。このパケットはアダプテーションフィールドは含めておらずペイロードのみ含まれています。また、Start payload unit indicatorは1です。
0000: 47 60 00 16 00 00 B0 1D 7F E0 C5 00 00 00 00 E0
0010: 10 04 00 E1 F0 04 01 E3 F0 05 80 FF C8 FF F0 FC
0020: F0 11 56 51 72 FF FF FF FF FF FF FF FF FF FF FF
0030: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
0040: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
0050: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
0060: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
0070: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
0080: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
0090: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
00A0: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
00B0: FF FF FF FF FF FF FF FF FF FF FF FF
先頭4バイトはTSヘッダとなりますのでそれ以降で見ていきます。
00 00 B0 1D 7F E0 C5 00 …
PATはPSIとなるので先頭の00はポインターフィールドとなります。なのでPAT構造体に当てはめると、2バイト目の00はTableId、selection_lengthは3バイト目の0xB0の下位4ビットは0と0x1Dを組み合わせると0x1Dとなります。
selection_lengthはアドレスで表すと0x0008 ~ 0x0024までを意味します。FFで埋まっているのは全てダミーデータということですね。
TsPATというクラスを生成してペイロードを受け取ったらデコードする処理となります。
public class TsPAT
{
public Dictionary<int, int> program_pid { get; private set; }
public int Network_PID { get; private set; }
public TsPAT(byte[] rows)
{
using (MemoryStream ms = new MemoryStream(rows))
{
this.program_pid = new Dictionary<int, int>();
int tableid = ms.ReadByte();
int selection_length = (ms.ReadByte() & 0x0f) << 8 | ms.ReadByte();
ms.Position += 5; // 使わないので読み飛ばし
selection_length -= 9; // 4バイトのCRCを含めているので9
while (selection_length != 0)
{
int program_number = (ms.ReadByte() << 8) | ms.ReadByte();
int pmt_pid = (ms.ReadByte() & 0x1f) << 8 | ms.ReadByte();
if (program_number == 0x00) { this.Network_PID = pmt_pid; }
else { this.program_pid.Add(program_number, pmt_pid); }
selection_length -= 4;
}
}
}
}
![](https://assets.st-note.com/img/1643362667011-PIjty4rrJS.png?width=800)
この結果から何が分かるかというとNetwork_PIDは0x10となりますが、これは固定PIDのNITというものに相当します。固定なので改めて取得する情報ではありません。
program_pidの方です。0x400に関するPIDは0x01f0、0x401に関するPIDは0x3f0という感じになります。この0x400は「NNK総合1」、0x401は「NNK総合2」、0x580は「NNKワンセグ1」というチャンネルを意味しています。
0x400はNNK総合1(こんなチャンネル名ありませんが)でそのPESリストは0x01f0を見ればわかるよ!という感じです。
PMT(Program Map Table)取得
PATからプログラム番号のPIDを取得しました(0x400はPID=0x01f0)。このPIDこそがPMTに相当します。
![](https://assets.st-note.com/img/1643364465565-dA0KpcRu40.png?width=800)
取得したい情報は後半のstream_typeとelemantary_PIDをPMTから情報取得できます。
![](https://assets.st-note.com/img/1643364613456-K6RZGSK23P.png?width=800)
左側はPID名、右側がストリームタイプとなります。ストリームタイプは0x02はVideo、0x0fはAudio、0x06は文字データ(字幕等)で0x0dはとりあえず無視という形になります。
PAT → program_number = 0x400とPID = 0x01f0を取得
0x01f0のPMT → PID = 0x100は動画(0x02)、PID=0x110は音声(0x0f)
最後にTsPATと同じようにTsPMTというクラスを作成しました。
public class TsPMT
{
public Dictionary<int, int> program_map { get; private set; }
public TsPMT(byte[] rows)
{
this.program_map = new Dictionary<int, int>();
using (MemoryStream ms = new MemoryStream(rows))
{
int tableid = ms.ReadByte();
int section_length = (ms.ReadByte() & 0x0f) << 8 | ms.ReadByte();
ms.Position += 0x7; // 使わないのでスキップ
int program_length = (ms.ReadByte() & 0x0f) << 8 | ms.ReadByte();
ms.Position += program_length;
section_length -= (program_length + 0x09 + 4); //ヘッダとCRC32分等々
// PIDがどのStreamTypeかを紐づけ
while (section_length != 0)
{
int stream_type = ms.ReadByte();
int element_pid = (ms.ReadByte() & 0x1f) << 8 | ms.ReadByte();
int es_length = (ms.ReadByte() & 0x0f) << 8 | ms.ReadByte();
ms.Position += es_length;
section_length -= (5 + es_length);
this.program_map.Add(element_pid, stream_type);
}
}
}
}
この記事が気に入ったらサポートをしてみませんか?