PTSとDTSの生成
更新日: 2013年03月17日

DTSはDecode TimeStamp(復号時刻)の略であり、PTSはPresentation TimeStamp(表示時刻)の略である。 QuickTimeのMOVやISO Base Media (ISO/IEC 14496-12)ではCTSというものが定義されており、 Composition TimeStamp(合成時刻)の略で厳密にはPTSとは異なる概念であるが、ここではCTSもPTSと同じものとして扱うことにする。 また「フレーム」と「ピクチャ」は区別して使用することにする。 フレームは、大まかに次の3種に分けられる。 1: 1つのプログレッシヴなピクチャからなるフレーム (フレーム符号プログレッシヴ・ピクチャ) 2: 1つインターレースなピクチャからなるフレーム (フレーム符号インターレース・ピクチャ) 3: フィールド毎に分けられた2つのプログレッシヴなピクチャからなるフレーム (フィールド符号ピクチャ) ここでは「ピクチャ」をDTS及びPTSが定義される最小のアクセス単位ということとして扱おう。 DTSは必ず符号順で見て前のピクチャのものより大きいものとする。 (コンテナの時間軸の解像度によってどうしても重複してしまうケースがあるが、ここではそれを考慮しない。) また、復号から表示に至るレイテンシ(遅延)は一切考えないものとする。 ||[- 1. MPEG-1/2 Video 及び VC-1/WMV3 におけるDTSからのPTSの生成。 -]|| 映像CODECではエンコードされた際に、既に各ピクチャの表示されるべき順番が決定されている。 よって各ピクチャが採れるPTSはそのDTSによって制限される。 ここではピクチャタイプからDTSからPTSを生成する。 先ず、ピクチャタイプに関してであるが、 MPEG-1/2 Videoには3種類のピクチャタイプが定義されている。 Iピクチャ: それ単一で復号できるピクチャ。ピクチャ内符号(イントラ符号)のみで構成される。他のピクチャとの差分からは生成されない。 Pピクチャ: PTSがより小さいピクチャからの差分による符号(インター符号或いはノンイントラ符号)と、イントラ符号から構成される。 Bピクチャ: PTSがより小さいピクチャとより大きいピクチャからのインター符号と、イントラ符号から構成され、他ピクチャから参照されない。 PTSがより大きいピクチャを必要とするので、このBピクチャが存在すると、符号順/復号順と表示順の並び順が全体で異なることになる。 連続したBピクチャの表示順は必ず符号順/復号順と同じであり、復号と同時に表示される。 最後に復号したIピクチャまたはPピクチャのことを纏めてアンカー・ピクチャと言う。 非Bピクチャを復号することになったならば、最後に復号したアンカー・ピクチャを表示する。 VC-1及びWMV3では更にスキップト・ピクチャとBIピクチャが定義されている。 スキップト・ピクチャ: 直前に復号されるピクチャと全く同じものを表示することを示すPピクチャ。 ここではPピクチャとして含めることにする。 BIピクチャ: インター符号を行わないで、イントラ符号のみからなるBピクチャ。 これはその方が圧縮率が改善するケースがあるからである。 ここではBピクチャとして含めることにする。 Bピクチャが存在しない場合は、符号順/復号順と表示順は同じであり、すなわちPTS == DTSが成り立つ。(復号と同時に表示する) つまり、このようなケースにおいてはDTSをそのままPTSにすれば良いのである。 では、Bピクチャが存在するケースは、どうするのだろうか。 符号順/復号順と表示順は全体から見て異なることになり、PTS == DTSが常に成り立たなくなる。 ここで、 A.「連続したBピクチャの表示順は必ず符号順/復号順と同じである。」 B.「非Bピクチャを復号することになったならば、最後に復号したアンカー・ピクチャを表示する。」 というルールを使うことによってそのPTSを決定出来る。 これは、MPEG-2ならば ITU-T H.262 02/2002 の 6.1.1.11 Frame re-ordering において定義されている。 VC-1ならばSMPTE 421M-2006 5.4 Frame Orderingにおいて定義されている。 今、次のような例を考える。(X[-]はフレームが存在しないことを示す。) 復号順 I[1]P[2]P[3]B[4]B[5]P[6]X[-] DTS 0 1 2 3 4 5 - Bフレームが存在するので、フレームを表示する際には並び替えないとならない。 先ず、I[1]であるが、P[2]が次に来るので、ルールB.よりI[1]が最初(1番目)に表示される。 P[2]はP[3]が次に来るのでルールB.によりP[2]が次に2番目に表示される。 P[3]であるが、次がB[4]なのですぐには表示されない。次のアンカー・フレームはP[6]である。 B[4]及びB[5]はルールA.によりそれぞれ3番目、4番目に表示される。 P[6]の所に来てようやくP[3]が表示される。P[3]は5番目である。 P[6]の次にフレームは無いので、P[6]が6番目に表示される。 このようにして、 復号順 I[1]P[2]P[3]B[4]B[5]P[6]X[-] DTS 0 1 2 3 4 5 - 表示順 X[-]I[1]P[2]B[4]B[5]P[3]P[6] PTS - 1 2 3 4 5 6 となる。 PTSは、PTS >= DTSが絶対条件であるから+1してある。 このことから、Bフレームが存在する場合は、デコーダにフレームを入力してから出力までに、1フレーム分遅延が生じることが分かる。 フレーム数が2以上でのアルゴリズムとしては int Bフレーム連続数 = 0; for( int i = 1; i <= 総フレーム数; i++ ) { if( フレーム[i].タイプ == B ) { /* Bフレームは必ず符号化された順番で出力/表示されなければならない. */ フレーム[i].PTS = フレーム[i].DTS; ++Bフレーム連続数; } else { /* 現在のフレームのDTSをアンカー・フレームのPTSに与える. */ if( i - 1 > Bフレーム連続数 ) フレーム[i - 1 - Bフレーム連続数].PTS = フレーム[i].DTS; Bフレーム連続数 = 0; } } フレーム[総フレーム数 - Bフレーム連続数].PTS = フレーム[総フレーム数].DTS + α; /* αは正でなければならない. */ となる。 αはケースバイケースである。 ||[- 2. PTSからのDTSの生成。 -]|| ピクチャ出力遅延数が分かっており、PTSが予め決まっていれば、それから有効なDTSを生成することができる。 Bピクチャのようなピクチャによって復号順と表示順が異なるケースでなければPTSをそのままDTSに代入すれば良いが、 では、そのようなケースでないのであればどうしたら良いだろうか。 以下は L-SMASH の timelineeditor 及び libx264 において実装したアルゴリズムである。 このアルゴリズムのアイディアの原形はtc2mp4であるが、エンコーダにも利用できるように改良を加えたものである。 int 直前表示順PTS[最大ピクチャ出力遅延数]; int ピクチャ出力遅延数 = 0; int 出力ピクチャ遅延時間 = 0; ピクチャ型 入力ピクチャ[]; ピクチャ型 出力ピクチャ[]; for( int i = 0; ; i++ ) { 入力ピクチャ[i] = i < 総ピクチャ数 ? 入力ピクチャ取得() : NULL; /* 入力ピクチャはPTS == DTSとする. */ 出力ピクチャ[i] = 出力ピクチャ取得( 入力ピクチャ[i] ); /* 出力ピクチャはPTSを対応する入力ピクチャからコピーしている. */ if( 出力ピクチャ[i] == NULL ) { ++ピクチャ出力遅延数; continue; } else if( 出力ピクチャ遅延時間 == 0 ) 出力ピクチャ遅延時間 = 入力ピクチャ[ピクチャ出力遅延数].PTS - 入力ピクチャ[0].PTS; else if( 入力ピクチャ[i] == NULL && 出力ピクチャ[i] == NULL ) break; int j = i - ピクチャ出力遅延数; 出力ピクチャ[j].DTS = j <= ピクチャ出力遅延数 ? 入力ピクチャ[j].PTS; : 直前表示順PTS[ (j - ピクチャ出力遅延数) % ピクチャ出力遅延数 ]; 出力ピクチャ[j].DTS -= 出力ピクチャ遅延時間; 直前表示順PTS[ j % ピクチャ出力遅延数 ] = 入力ピクチャ[j].PTS + 出力ピクチャ遅延時間; } このアルゴリズムは、不正なPTSが紛れ込んでない限り、 PTS >= DTS 且つ DTSの単調増加の原則を厳守して生成できる優れたものである。 なお、ここでは便宜的に出力ピクチャのDTSの方を出力ピクチャ遅延時間だけ差し引いているが、 代わりに出力ピクチャのPTSの方に出力ピクチャ遅延時間を足しても良い。これは実装に依存する。 さて、このアルゴリズムが PTS >= DTS 且つ DTSの単調増加を満たすことの証明を行おう。 ここでは、入力ピクチャのPTSが狭義単調増加することを前提にする。 {{- Proof -}} [i] j <= ピクチャ出力遅延数 と、[ii] j > ピクチャ出力遅延数 の場合に分けて考えよう。 また、[iii]で[i]から[ii]の場合に遷移する際にも DTS が単調増加するかも考える。 [i] j <= ピクチャ出力遅延数 の場合では、 出力ピクチャ[j].DTS = 入力ピクチャ[j].PTS - 出力ピクチャ遅延時間; であるが、 出力ピクチャ遅延時間 = 入力ピクチャ[ピクチャ出力遅延数].PTS - 入力ピクチャ[0].PTS; より、これを代入して、 出力ピクチャ[j].DTS = 入力ピクチャ[j].PTS - (入力ピクチャ[ピクチャ出力遅延数].PTS - 入力ピクチャ[0].PTS); 出力ピクチャ[j].DTS = 入力ピクチャ[0].PTS - (入力ピクチャ[ピクチャ出力遅延数].PTS - 入力ピクチャ[j].PTS); /* 式(f.A) */ である。 また、入力ピクチャ[j].PTS が j に対して狭義単調増加であることより、 入力ピクチャ[ピクチャ出力遅延数].PTS >= 入力ピクチャ[j].PTS だから、 出力ピクチャ[j].DTS <= 入力ピクチャ[0].PTS が成り立つ。 出力ピクチャのPTSは入力ピクチャのPTSから一切弄っていないので、 出力ピクチャ[j].PTS >= 入力ピクチャ[0].PTS 故に、 出力ピクチャ[j].PTS >= 出力ピクチャ[j].DTS が成り立つ。 出力ピクチャのDTSが、単調増加するのは 出力ピクチャ[j].DTS = 入力ピクチャ[j].PTS - 出力ピクチャ遅延時間; より、入力ピクチャ[j].PTS が j に対して狭義単調増加であることから明らかである。 [ii] j > ピクチャ出力遅延数 の場合では、 出力ピクチャのDTSは、ピクチャ出力遅延数 だけ過去の 直前表示順PTS から 出力ピクチャ遅延時間 だけ差し引いたものを採用している。 出力ピクチャ[j].DTS == A[j] - 出力ピクチャ遅延時間 A[j] == 入力ピクチャ[j - ピクチャ出力遅延数].PTS + 出力ピクチャ遅延時間 すなわち、 出力ピクチャ[j].DTS == 入力ピクチャ[j - ピクチャ出力遅延数].PTS /* 式(f.B) */ である。 入力ピクチャ[j].PTS は j に対して狭義単調増加するから、明らかに 入力ピクチャ[j].PTS >= 入力ピクチャ[j - ピクチャ出力遅延数].PTS であり、 出力ピクチャ[j].PTS >= 出力ピクチャ[j].DTS が成り立つ。 出力ピクチャのDTSが、単調増加するのは 入力ピクチャ[j].PTS が j に対して狭義単調増加であることから明らかである。 [iii] 式(f.A)より、 出力ピクチャ[ピクチャ出力遅延数].DTS == 入力ピクチャ[0].PTS 式(f.B)より、 出力ピクチャ[ピクチャ出力遅延数 + 1].DTS == 入力ピクチャ[1].PTS 入力ピクチャ[j].PTS が j に対して狭義単調増加であることから、 出力ピクチャ[ピクチャ出力遅延数 + 1].DTS > 出力ピクチャ[ピクチャ出力遅延数].DTS 故に、[i]から[ii]の場合に遷移する際にも DTS が単調増加する。 [i]、[ii]、[iii]より、このアルゴリズムは PTS >= DTS 且つ DTSの単調増加 を満たす。 Q.E.D.