PSIテーブルのパース、PAT, PMT を例に

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

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

# ヘッダ4バイト
47 60 00 1B
# pointer_field
00
# 固定長部分 (table_id 〜 last_section_number)
00 B0 1D 7E 87 D9 00 00

# ここから可変長部分。長さは section_length = 0x1D = 29バイトから
# 固定部分 5 バイトと CRC_32 の 4 バイトを引いた20バイト
# 1つ目のループ。 program_number が 0 なので、 network_id = 0x010
00 00 E0 10
# 2つ目のループ。 program_number = 0x5C38, program_map_PID = 0x101
5C 38 E1 01
# 3つ目のループ。 program_number = 0x5C39, program_map_PID = 0x102
5C 39 E1 02
# 4つ目のループ。 program_number = 0x5DB8, program_map_PID = 0x1FC8
5D B8 FF C8
# 5つ目のループ。 program_number = 0x5D89, program_map_PID = 0x1Fc9
5D B9 FF C9

# 20バイトパースしたのでここで可変長部分終了
# CRC_32
90 3F 0A 85

# 余った部分は 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
}

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

# ヘッダ4バイト
47 61 01 1E
# pointer_field
00
# 固定長部分 (table_idprogram_info_length)
02 B0 C7 5C 38 D1 00 00 E1 00 F0 09
# program_info_length  0x09 なので後続 9 バイトは descriptor()
# descriptor_tag = 0x09, descriptor_length = 0x04 
# descriptor_tag = 0xC1, descriptor_legnth = 0x01 の2つの descriptor がある
[09 04 00 05 E0 31, C1 01 84]

# 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 を参照

# 1つ目のループ stream_type = 0x02(動画), elementary_PID = 0x121, 記述子長 6 バイト
02 E1 21 F0 06 [52 01 00, C8 01 53]
# 2つ目のループ stream_type = 0x0F(音声), elementary_PID = 0x112, 記述子長 3 バイト
0F E1 12 F0 03 [52 01 10]
# ここから下は 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つ目のパケットは終了

# 2つ目のパケットのヘッダ
47 21 01 1F                            
                   01 6E, 09 04 00 05 FF FF, FD 05 00 0C 1F FF BF]

# 177バイトパースしたのでここで可変長部分終了
# CRC_32
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

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

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