mpeg2ts の解析(2): PSI テーブルのパース、PAT, PMT を例に

公開: 2012年09月16日17時, 最終更新: 2012年09月16日18時

前回で、Transport Stream から一塊の PSI セクションを抜き出せたので、次はパースをします。

PAT (Program Association Table) は PID が固定ですし、構造も単純なのでまずはこれから考えます。

program_association_section() {
    table_id                           8  uimsbf
    section_syntax_indicator           1  bslbf
    '0'                                1  bslbf
    reserved                           2  bslbf
    section_length                    12  uimsbf
    transport_stream_id               16  uimsbf
    reserved                           2  bslbf
    version_number                     5  uimsbf
    current_next_indicator             1  bslbf
    section_number                     8  uimsbf
    last_section_number                8  uimsbf
    for (i = 0; i < N; i++) {
        program_number                16  uimsbf
        reserved                       3  bslbf
        if (program_number == '0') {
            network_PID               13  uimsbf
        } else {
            program_map_PID           13  uimsbf
        }
    }
    CRC_32                            32  rpchof
}

payload の中身が PAT であるパケットの例です

47 60 00 1B 00 00 B0 1D 7E 87 D9 00 00 00 00 E0
10 5C 38 E1 01 5C 39 E1 02 5D B8 FF C8 5D B9 FF
C9 90 3F 0A 85 FF FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF FF FF

意味合いごとに改行を加えると以下のようになります:

47 60 00 1B               # ヘッダ4バイト
00                        # pointer_field
00 B0 1D 7E 87 D9 00 00   # 固定長部分 (table_id 〜 last_section_number)
                          # ここから可変長部分。長さは section_length = 0x1D = 29 バイトから固定部分 5 バイトと CRC_32 の 4 バイトを引いた20バイト
00 00 E0 10               # 1つ目のループ。 program_number が 0 なので、 network_id = 0x010
5C 38 E1 01               # 2つ目。 program_number = 0x5C38, program_map_PID = 0x101
5C 39 E1 02               # 3つ目。 program_number = 0x5C39, program_map_PID = 0x102
5D B8 FF C8               # 4つ目。 program_number = 0x5DB8, program_map_PID = 0x1FC8
5D B9 FF C9               # 5つ目。 program_number = 0x5DB9, program_map_PID = 0x1FC9
                          # 20バイトパースしたのでここで可変長部分終了
90 3F 0A 85               # CRC_32
                          # 余った部分は 0xFF で埋められる
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF

Program Association Table は、プログラム番号ごとに PMT の PID を指定しています。別途 Service Description Table を見ると分かるのですが、program_number 0x5C38 は TOKYO MX1、 0x5C39 は TOKYO MX2、 0x5DB8 は MXワンセグ1、 0x5D89 は MXワンセグ2 です。だいたい必要になるのは PAT のループで最初に出てくる program_number なのですが、場合によってはそうではないこともあるようで、一律最初の組み合わせで処理をしたために不具合が起きたみたいな例が昔のフリー実装ではあったようです。

recpt1 で channel 20 を録画した場合は program_number 0x5C38 の PMT を参照すべきであるというのは固定情報なので、設定ファイルを作るときはひとまとめにしておいたほうがいいでしょう。

次に PMT (Program Map Table) です。動画、音声、その他もろもろの PID が何番なのかを指定するテーブルです。PMT はそれほど情報量が多くならないので、たいてい1パケットに収まっていますが、下の例では2パケットにまたがっています。

47 61 01 1E 00 02 B0 C7 5C 38 D1 00 00 E1 00 F0
09 09 04 00 05 E0 31 C1 01 84 02 E1 21 F0 06 52
01 00 C8 01 53 0F E1 12 F0 03 52 01 10 0D E7 40
F0 0F 52 01 40 FD 0A 00 0C 33 3F 00 03 00 03 FF
BF 0D E7 50 F0 0A 52 01 50 FD 05 00 0C 1F FF BF
0D E7 51 F0 0A 52 01 51 FD 05 00 0C 1F FF BF 0D
E7 52 F0 0A 52 01 52 FD 05 00 0C 1F FF BF 0D E9
60 F0 0A 52 01 60 FD 05 00 0C 1F FF BF 0D E9 61
F0 0A 52 01 61 FD 05 00 0C 1F FF BF 0D E7 5E F0
10 52 01 5E 09 04 00 05 FF FF FD 05 00 0C 1F FF
BF 0D E7 5F F0 10 52 01 5F 09 04 00 05 FF FF FD
05 00 0C 1F FF BF 0D E9 6E F0 10 52

47 21 01 1F 01 6E 09 04 00 05 FF FF FD 05 00 0C
1F FF BF 32 23 3E 76 FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF FF FF

まず、一連の PMT がこの2つのパケットにまたがっていることをヘッダから確認しましょう。 一つ目のパケットは payload_unit_start_indicator ( 2 バイト目の 2 ビット目) が 1 で、確かにこのパケットから payload が開始されます。一方2パケット目の payload_unit_start_indicator は 0 です。

また、 PID ( 2 バイト目の下 5 ビットと 3 バイト目) は両方とも 0x101 で、上で調べた program_id = 0x5C38 のものです。 continuity_counter ( 4 バイト目の下 4 ビット) は上が 0x0E 、下が 0x0F でパケットロスはありません。 adaptation_field_control ( 4 バイト目の上から 3 〜 4 ビット) はともに 0x01 で、 adaptation_field はありません。ゆえに payload_unit_start_indicator が 1 である上のパケットの 5 バイト目は pointer_field で、その値は 0 なので 6 バイト目から payload が始まります。

PMT のデータ構造は下に示す通りで、 payload の 2 バイト目下 4 ビットと 3 バイト目が section_length です。0xC7 = 199 バイトで、この値は section_length から先に何バイトがあるかを示しています。ヘッダ 4 バイト、 poiner_field 1 バイト、 payload のうち section_length の前にあるバイトと section_length 自体の合計が 3バイト、あわせて 8 バイトをすでに使っているので、 199 バイトのうちこのパケット内が 180 バイト、次のパケットに 19 バイトです。次のパケットもやはりヘッダ 4 バイトから始まるので、あわせて 23 バイトになり、 24 バイト目からは余った部分です。例に出したパケットは確かに 2 パケット目の 24 バイト目以降が 0xFF になっており、以上の議論通りであることがわかります。

TS_program_map_section(){
    table_id                  8  uimsbf
    section_syntax_indicator  1  bslbf
    ‘0’                       1  bslbf
    reserved                  2  bslbf
    section_length           12  uimsbf
    program_number           16  uimsbf
    reserved                  2  bslbf
    version_number            5  uimsbf
    current_next_indicator    1  bslbf
    section_number            8  uimsbf
    last_section_number       8  uimsbf
    reserved                  3  bslbf
    PCR_PID                  13  uimsbf
    reserved                  4  bslbf
    program_info_length      12 uimsbf
    for (i=0;i<N;i++){
        descriptor()
    }
    for (i=0;i<N1;i++){
        stream_type           8  uimsbf
        reserved              3  bslbf
        elementary_PID       13  uimsbf
        reserved              4  bslbf
        ES_info_length       12  uimsbf
        for(i=0;i<N2;i++){
            descriptor()
        }
    }
    CRC_32                   32  rpchof
}

意味合いごとに改行と記号を加えると以下のようになります。

47 61 01 1E                            # ヘッダ4バイト
00                                     # pointer_field
02 B0 C7 5C 38 D1 00 00 E1 00 F0 09    # 固定長部分 (table_id 〜 program_info_length)
                                       # program_info_length が 0x09 なので、後続 9 バイトは descriptor()
[09 04 00 05 E0 31, C1 01 84]          # 記述子はすべて 1 バイト目が descriptor_tag 、 2 バイト目が descriptor_length なので
                                       # descriptor() の中にどんな記述子が何バイト出てくるかは、そこを見れば分かる。
                                       # この例では tag 0x09 の 4 バイトの記述子と、 tag 0xC1 の 1 バイトの記述子が存在する。
                                       # descriptor() のあとは可変部分。長さは section_length 0xC7 = 199 から program_info_length 0x09 = 9 と固定長部分 9 と CRC_32 の 4 バイトを引いた 177 バイト
                                       # stream_type の値が何に対応しているのかは ISO 13818-1 2.4.4.10 節表 2-29 を参照
02 E1 21 F0 06 [52 01 00, C8 01 53]    # 1つ目のループ。 stream_type = 0x02(動画), elementary_PID = 0x121, 記述子長 6 バイト
0F E1 12 F0 03 [52 01 10]              # 2つ目のループ。 stream_type = 0x0F(音声), elementary_PID = 0x112, 記述子長 3 バイト
                                       # ここから下は stream_type = 0x0D で、 ISO/IEC 13818-6 type D なるデータなのらしい。よく調べてない
0D E7 40 F0 0F [52 01 40, FD 0A 00 0C 33 3F 00 03 00 03 FF BF]
0D E7 50 F0 0A [52 01 50, FD 05 00 0C 1F FF BF] 
0D E7 51 F0 0A [52 01 51, FD 05 00 0C 1F FF BF] 
0D E7 52 F0 0A [52 01 52, FD 05 00 0C 1F FF BF] 
0D E9 60 F0 0A [52 01 60, FD 05 00 0C 1F FF BF] 
0D E9 61 F0 0A [52 01 61, FD 05 00 0C 1F FF BF] 
0D E7 5E F0 10 [52 01 5E, 09 04 00 05 FF FF, FD 05 00 0C 1F FF BF] 
0D E7 5F F0 10 [52 01 5F, 09 04 00 05 FF FF, FD 05 00 0C 1F FF BF] 
0D E9 6E F0 10 [52                     # ここで1つ目のパケットは終了

47 21 01 1F                            # 2つ目のパケットのヘッダ
                   01 6E, 09 04 00 05 FF FF, FD 05 00 0C 1F FF BF]
                                       # 177バイトパースしたのでここで可変長部分終了
32 23 3E 76                            # CRC_32
                                       # 余った部分は 0xFF で埋められる
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF

ということで、動画の PID が 0x121 で、音声の PID が 0x112 ということが分かりました。動画については、記述子リストの 2 つ目が 0xC8 で、これはビデオデコードコントロール記述子 (ARIB-STD-B10-2 6.2.30) です。第 3 バイトの上から 3 〜 6 ビット目が video_encode_format で、0x53 は 2 進数で 0b01010011 なのだから video_decode_format は 0b0100 で、 ARIB-STD-B10 第2部 表6-60 よりこの動画は 480i (SD) です。 TOKYO MX では録画したい番組が HD なのに一つ前の番組が SD なので扱いに困るということがありますが、この値を追いかければ SD を捨てて HD だけ残すということも可能です。

音声の方は、このデータだけではステレオかモノラルか分かりません。以前、録画したい番組がステレオなのに一つ前の番組がモノラルでやはり扱いに困るということがありました。その問題については別の解法が必要です。 (EIT を見れば番組内の音声がステレオかモノラルかは分かるので、番組切り替えのタイミングをうまく掴んで切り離すとか、音声バイナリじたいを調べてモノラルからステレオに変わったところで切り離すとかいった手があります)