📽️

[WIP] MPEG2TSの規格を/パケットを読んでみる

2022/12/23に公開

こちらはACCESS Advent calendarの22日目の記事となります。

業務でMPEG2TS周りの問題を追う機会があり、自分の理解を整理するために記事を書いています。専門からやや離れた分野のため瑕疵があったら存分にマサカリを投げてぜひ指摘してください。助かります。

はじめに

想定読者

  • MPEG2TSについて名前は聞いたことがあるが触ったことはない。
  • 業務で触ることになったけど、いいとっかかりがない。
  • 規格を読むのに慣れていない。

この記事でやりたいこと

この記事では、公開されている規格/ソフトウェアの範囲でMPEG2TSについて解説していきます。
読む上で定義を淡々と追うより、むしろ規格が実現したいことに着目しながら何がこういう理由で必要だから……という構造で読み進められるような書き方を目指します。

この記事でやらないこと

各メディア(H262, H.264, H265など)ごとのお話は省略します。

MPEG2TSの概要の理解

ここでは H.222 / ISO/IEC 13818-1を参照してみながら読み進めていきます(すべては取り上げません。特にDecode/Encode周りの話は省略します)。

規格のモチベーション

映像を放送波みたいな1つのストリームだけしかないものに載せて送るときの方法を規格にしたものがMPEG2TSです。

放送波っていうものは基本的に連続的に垂れ流すモノで、頭からデータが読めるmp4などとは違ったデータ構造の要求が出てきます。具体的には、

  • どうやって別の種類のデータを1つのストリームに乗せるか
  • どうやってタイミングを合わせて再生するか
  • そもそもどんな種類が入っているのかをどう伝えるのか
  • ...

みたいな感じで色々考えておかないといけないことがあるわけです。

他にも放送波では番組表だったり、dボタンみたいな「HTMLを放送波に乗せて見れる!」みたいな機能もありますし、他にも受信時にデータが欠損してた!みたいなこともあり得ます。そういったものに対応できる規格としてMPEG2TSは策定されました。

で、まず全体像を理解してもらうため、送信する側と受信する側について簡単なポンチ絵[1]を描きました。

送信する側のポンチ絵

生の映像データをTrasnportStreamに変換する

送信する側では、生の動画/音声データをTransport Streamに束ねます。まず、生のデータはそのまま送るにはあまりにも大きいため、Encoderによってエンコードされます(エンコードの方式はいろいろです)。エンコードしたデータはElementary Stream(ES)というデータとなり、これを可変長のPacketにまとめてPacketized Elementary Stream(PES)となります。動画/映像のPESを一つのストリームまとめ、さらに番組情報やESの内訳を受信側に教えるための情報をMultiplexer (MUXER) に与えます。MultiplexerはPESをさらにTransport packetというものに詰め込む、クロックなどの情報を付与する、定期的に番組情報やESの内訳を吐き出すなどいろいろなことをして、これらの情報を一つのTransport Streamにまとめます。

受け取る側のポンチ絵

TransportStreamを生の映像データに戻す

受信する側では、受け取ったTransport Streamから、番組情報やESの内訳が書いてある情報をまず見つけ出します。再生したい動画/音声をデフォルトで選択したりユーザなどから選択されるのに合わせて、適切なDecoderを用意してデコードします。デコードした動画/音声をタイミングを合わせて表示すれば動画が再生できます。この一つのストリームから動画/音声のデータに分ける部分をDemultiplexer(DEMUXER)といいます。


と、いうのが大まかな流れになってきます。以降これらの工程にでてきたPESを一つのTransport streamにまとめていくMUXER、そしてそれを元に戻すDEMUXERがやるべきことに着目して見ていきます。

もう少しMPEG2TSを詳しくみてみる

ということで、MUXER/DEMUXERの視点でPESおよびTransport streamについて見ていきましょう。

PES (Packetized elementaly stream)

PESはpacketized elementaly streamの略で、直訳してしまえば「パケット化された基本ストリーム」ということになります。PESは動画/音声のデータをエンコードしたelementary streamをパケット化したものです。PESは固定長のHeaderと可変長のPayloadから構成されるPacketとなります。

動画/音声のデータを放送波では送りたいわけでした。動画/音声それぞれのPES(あるいはそれ以外のPES)をMultiplexしたもの、それがTS (transport stream)となります[2]。このコネコネっていう部分が先程の、

  • どうやって別の種類のデータを1つのストリームに乗せるか
  • どうやってタイミングを合わせて再生するか
  • そもそもどんな種類が入っているのかをどう伝えるのか
  • ...

などを解消する部分になっていきます。


では、PESをどのようにして1つのストリームにするのでしょうか。やるべきこととしては、

  • PESをTransport Packetsに格納して送信する
  • タイミングを合わせる情報を定期的に付加する
  • 動画/音声がどのようなものなのかを伝えるためのデータを用意して定期的に送信する

の主に3つです。このことを頭に入れた状態で次のTransport Packetの説明に移ります。

Transport Packet

Transport packetは固定長のHeader/任意のAdaptation Field/そしてPayloadから構成された、188 bytes[3]の固定長なパケットです。

TSはこのTrasnport packetの連なりで、Transport packet以外の構成要素が含まれません。全てのPESはこのTransport packetに格納されます(多くの場合はフラグメンテーションされて格納される)。

ここではTransport Packetの構成要素を簡単にだけ説明します。Transport Packetに関する詳細はH222の2.4.3.2およびH.222のAnnex F.1.1を参照してください。[4]

Transport packetのHeaderは、固定長32 bitのフィールドです。

ここから先はある程度規格をそのままなぞって書いていくのですが、読み進めていく上での重要度(not equal 現実世界での重要度)を一応★で表記するので、参考にして(最悪読み飛ばして)ください。

Header: Sync Byte ★★

Sync Byteは8 bitのフィールドで、必ず0x47 (= 0100 0111) という値が入ります。

Sync ByteはTransport Streamの性質上、連続したデータのうち「どこからパケットが始まるのか」を区別するため必要なものです。テレビのような放送波を受信する側の目線で見ると、テレビを起動してから必ず放送波の途中からデータを読み始めるため、その頭出しをできるものが必要なわけです。

そのためSync Byteと一致するような値を規則的に含まれる領域(例えば、後述するPID)で使うことは避けるべきとなっています(SHOULD)。

Header: Transport error indicator ★

Transport error indicatorは1bitのフラグで、このTransport packetの中に修正不可能なエラーがあることを示します。エラーがある場合に1、なければ0がセットされます。

おそらく、何度もmultiplexされることを想定して、エラーがある時にどこかで修正できるかも、みたいな理由で用意されているフラグ?かと思います。

メディアプレイヤ目線であれば、修正不可能なエラーがあることがわかるのでそのまま破棄することができます。[5]

Header: Payload unit start indicator ★★

Payload unit start indicatorは1bitのフラグで、payloadに格納されているデータの始まりかどうかを表現するフラグです。[6]

Transport packetのペイロードがPESの場合、PESのパケットの一番最初が入っている場合は1、そうでなければ0が格納されます。また、transport stream section data(後述)を格納するときでもほとんど同様の取り扱いになっています。[7]

Header: PID ★★★

PID(Packet identifier)は13 bitのフィールドで、Transport Packetの中に何が入っているのかを示します。より強くいうと、Transport Packetに中に入っているPESの種類と一対一対応するようになっています。また、このTransport Packetにはフラグメント化したPESが入っています。

PIDは、0x0000から0x000Fおよび0x1FFFが特別な意味があるPID、それ以外の0x0010から0x1FFEが動画/音声/その他のデータがPayloadに入っている汎用的なPIDになります。

以下はH.222のTable 2.3から作成したPIDの持つ意味合いの一覧です。

PIDの値 対応するデータ
0x0000 Program assosiation table (PAT)
0x0001 Conditional access table (CAT)
0x0002 Transport stream description table (TSDT)
0x0003 IPMP control information table
0x0004 Adaptive streaming information Payload syntax is defined in 5.10.3.3.5 of ISO/IEC 23009-1. と書いてあってまだ見てない。。
0x0005...0x000F 予約済 他の規格で拡張されたりする
0x0010...0x1FFE 色々 動画/音声/字幕のPESやPMT[8](Program map table; 後述しますが、ストリーム内にどのようなPESが含まれているのかを伝えるためのテーブルです)など[9]
0x1FFF Null Packet 空っぽなパケット。パディングとして使われます

さて、このPIDはDEMUXER目線ではとても重要です。
「PIDはPESの種類に対応している」と書いてあるとおり、動画のPES/主音声のPES/副音声のPES、それぞれに固有のPIDが割り振られています。そのためPIDはDEMUXERがどのDecoderにデータを渡すのかを判断するために使うとても重要なフィールドです。

Header: Transport scrambling control ★

Transport scrambling controlは、2bitのフィールドで、Transport packetのPayloadのスクランブルのモードがどうなっているのかを示します。

スクランブルの方式についてはこの規格では定義せず、ユーザ定義となっており、'00'がセットされている場合はスクランブルされていないモードでパケットを送っていることがわかります。

Header: Adaptation field control ★★

Adaptation Field Controlは、2bitのフィールドで、このTransport packetがAdaptation field / Payloadを持つ/持たないを示します。

Adaptation Field Controlの持つ値とそれの持つ意味は以下の通りです。

対応するモード
00 予約済み;もし受け取ったとしてもデコーダは破棄する (SHALL)
01 Adaptation field: なし、Payload: あり
10 Adaptation field: あり、Payload: なし
11 Adaptation field: あり、Payload: あり

とりあえず1X側がAdaptation field, X1側がPayloadのあることを示している、と思えば大丈夫です。

また、Null packetのAdaptation field controlの値は01となります。

Header: Continuity counter ★★

Continuity counterは、4bitのフィールドで、同じPIDの、Payloadの異なるPacketを送るごとにインクリメントした値が入るフィールドです。インクリメントして1111となった後には0000にリセットされます。

Transport packetでは、エラーが発生することを想定して同じPayloadをもつパケットを2回送信できます。[10]このような重複送信をする場合はこのContinuity counterをインクリメントせずに送信します。また、Payloadを持たないパケットを送信するときも、同様にインクリメントせずに送信します。

また、Null packetのAdaptation field controlの値は未定義となります。

Adaptation field

Adaptation fieldはTransport packetのHeaderに続いて配置される可変長のフィールドです。先に話した通りAdaptation field controlが10あるいは11の場合にAdaptation fieldが存在します(01の場合は、固定長のHeaderのあとにPayloadが続きます)。

Adaptation field length

Adaptation field lengthは8 bitのAdaptation fieldの長さを示すフィールドです。この「長さ」は、adaptation field lengthを格納するのに必要な1 byte分の長さを除いた長さ(つまり、「引く1した」長さ) です。8 bitなので原理的には0-255までの値が入ります。が、そもそもTransport packetの長さは188 bytes、そしてそのうち固定長のHeaderが32 bit (= 4 byte) 、そしてこのAdaptation field lengthが1 byte占有するため、実際には有効な値というのは制約されます。

具体的には以下のようになります。

  • Payloadがない場合 (adaptation_field_control=10) 、Adaptation field lengthは183 bytesとなります。
  • Payloadがある場合 (adaptation_field_control=11) 、Adaptation field lengthは0--182となります(Payloadは1 byte)。

Adaptation field lengthは、Adaptation fieldに必要最低限な長さを指定する必要はありません。この特性を使うことで、Payloadの長さを調整することができます。実際分割されたPESの長さが不十分な場合に、Adaptation fieldをつかったStuffingを利用することができます。

Adaptation field lengthが0でない場合、以下のフィールドを読み進めていきます。
残りのフィールドの構成は以下のようになります。

  • 8個のFlag
    • 3つのIndicator
    • 5つのadaptation field内のOptionalなフィールドの存在を伝えるフラグ
  • 先のFlagのうち5つのFlagに応じて存在する、5つの固定長/可変長なフィールド
  • Adaptation field lengthに達するまでフィールドを埋めるStuffing

Discontinuity indicator

Discontinuity indicatorは、Transport packetが不連続であることを示すためのフラグです。

Discontinuity indicatorが1であることは、このTransport packetは一つ前のtransport packetと不連続であることを意味します。
また、Adaptation fieldが存在しないとき、あるいはDiscontinuity indicatorが0であることは、このTransport packetと一つ前のTransport packetは連続であることを意味します。

この不連続/連続というのには、Clockの不連続とContinuous counterの不連続という2つの側面があります。

TODO..

Random access indicator

Elementary stream priority indicator

PCR flagと対応するフィールド

OPCR flagと対応するフィールド

Splicing point flagと対応するフィールド

Transport private data flagと対応するフィールド

他にもある規格

MPEG2TSは未定義な部分を残しており、また他の規格で色々な拡張がされています(DVB/ATSC/ARIB...)。

MPEG2TSのファイルを実際に見てみる

さて、人によっては「このMPEG2TSがメディアプレイヤで再生できないんだよなあ。。」みたいな問題にHitすると思います。そんなあなたに必要なのがファイルの中身を覗く能力です。

ここでは、とりあえず基礎的なことを理解するために、フリーで転がっているMPEG2TSのデータを見ていきます。お手元でやってみたい方もいると思うので、インストールしている人が多いと思われるWiresharkを使って試してみましょう。

TDB...

脚注
  1. Zennはsvg埋め込めないのね。。orz ↩︎

  2. また、先に触れた通りPSもPESをデータとして含みます。 ↩︎

  3. MPEG2TSにおいてbyteは8 bitだと思います(きっちりと文は追ってないですが、Sync byteは8 bitとか1 byteとか書いていたのでそれはそう)。 ↩︎

  4. H.222 (2018) のAnnex F.1.1 の図説はわかりやすいのですが、188 bytesの範囲が2つのHeaderを含むような表記となっていて、これは誤りなので注意してください。 ↩︎

  5. Exoplayerの場合 このような形で破棄します。 ↩︎

  6. 後述するNull packetでは必ず0が格納されます。 ↩︎

  7. pointer field周りの取り扱いが異なる。詳しくはH.222の2.4.4.2のあたりを見るとわかるが、section dataがどこから始まるのかを示すための、pointer fieldというものが1の時はpayloadの冒頭にくるが、0の時には入らない。といってもsection dataなんて基本短いので、あまり複数のpacketにまたがるケースを見ることもないと思います。 ↩︎

  8. PMTはしばしば規格中にてMPTという誤植があります。 ↩︎

  9. とはいえ実際にはSync byteを避けるべき(SHOULD)なわけですから、0x47を部分的に含むPIDは避けるわけです。そのため、実際に使用可能な領域はもう少し少ないはずです(が、それでも十分でしょう。たぶん)。 ↩︎

  10. adaptation fieldのPCR(後述するかもしれない)の値は別途適切に設定しなくてはならない。 ↩︎

Discussion