mpeg2ts の解析(1): packet から PSI を取得

初出
更新

Transport Stream ファイルからパケットを効率的にイテレートする Python 実装を前回考えたので、今回からはイテレートしたパケットから必要なデータを抜き出す方法を考えていきます。

Transport Stream の各パケットの内容は PES (Packetized Elementary Stream) と PSI (Program Specific Information) の2種類に分別できます。 PES には動画や音声などのデータ、 PSI には様々なメタデータが入っています。中身がどちらにせよパケットレイヤのデータ構造は同じで、 ISO-13818-1 の2.4.3.2節表2-2にある通りです。

transport_packet(){
    sync_byte                            8  bslbf
    transport_error_indicator            1  bslbf
    payload_unit_start_indicator         1  bslbf
    transport_priority                   1  bslbf
    PID                                 13  uimsbf
    transport_scrambling_control         2  bslbf
    adaptation_field_control             2  bslbf
    continuity_counter                   4  uimsbf
    if(adaptation_field_control = = '10' || adaptation_field_control = = '11'){
        adaptation_field()
    }
    if(adaptation_field_control = = '01' || adaptation_field_control = = '11') {
        for (i = 0; i < N; i++){
            data_byte                    8  bslbf
        }
    }
}

この表の言わんとしていることは以下のとおりです

PES や PSI の実際のデータは data_byte の中です。複数のパケットに別れて送出されることがあり、先頭のパケットかどうかは payload_unit_start_indicator の値で判断します。 1 なら先頭パケット、 0 なら後続パケットです。先頭パケットの data_byte の先頭3バイト (packet_start_code_prefix) が 0x000001 なら PES、そうでないなら PSI です。より詳しく書くと、 PSI の先頭パケットの data_byte は pointer_field から始まります。この pointer_field の値は、一つ前の PSI パケットで伝送しきれなかった残りのバイナリがこの後何バイトあるかを表しています。pointer_field の後はデータ本体 (payload) で、 table_id が8ビット、そのあと必ず上4ビットが 0b1011 であるバイトから始まるので、pointer_field と table_id が 0 でも次の3バイト目は必ず異なることになり PES と PSI の区別が付くようになっています。

PID は data_byte がどういうデータかを表すものですが、一律に動画の PID が何番などとは決まっていません。動画や音声の PES の PID は PMT (Program Map Table) で指定され、その PMT の PID も決まっていないので PAT (Program Association Table) をパースして調べる必要があります。 PAT の PID は 0x00 であると ISO13818-1 2.4.3.3節表2-3で決められています。

ということで、TS ストリームの中から望みの PSI を取り出すには、少なくとも以下の操作が必要です

  1. PID の値が望みの PSI のものかを調べる
  2. payload_unit_start_indicator を見て先頭パケットを探す
  3. adaptation_field_control と pointer_field を見て data_byte が何バイト目から始まるか調べる
  4. 次に payload_unit_start_indicator が 1 になるまで payload をバッファしておいて、次の先頭パケットの pointer_field 分を取り込んで返す

PES の場合は pointer_field がないので 3番目の処理が不要です。

実装例は packet.py の payload 関数や tables 関数あたりを参考にしてみてください。

は、取得できた PSI データをパースします。

Chisa Youzaka; cc by 4.0;