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をデコードしてみたいと思います。

PAT構造

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;
            }
        }
    }
}
TSファイルのPATデータ例

この結果から何が分かるかというと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に相当します。

PMTデータ構造

取得したい情報は後半のstream_typeとelemantary_PIDをPMTから情報取得できます。

PMT出力例

左側はPID名、右側がストリームタイプとなります。ストリームタイプは0x02はVideo、0x0fはAudio、0x06は文字データ(字幕等)で0x0dはとりあえず無視という形になります。

PAT → program_number = 0x400PID = 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);
            }
        }
    }
}


この記事が気に入ったらサポートをしてみませんか?