H.263からSparkへのトランスコーディング(上)


こんにちは、稲垣@CEREVOです。

Sorenson Sparkという動画コーデックをご存じでしょうか。FLV1という名前の方が通りがいいかも知れません。Adobe Flash Playerにエンコーダとデコーダが両方とも登載されている唯一の動画コーデックだったので (v11からはH.264のエンコーダも登載され、唯一ではなくなったようです)、名前は知っているという人も多いでしょう。ところが世界中に普及しているSparkがFlash関連以外の分野で普及しているかというと、あまり見かけないのではないかと思います。むしろ類似の、というか本家の技術であるH.263の方が広く使われています (要出典)。
つまり、我々はSparkをエンコードしたいのに手元にはH.263のハードウェアエンコーダしかない、ソフトウェアエンコードはCPUパワーが足りなくてできない、という状況にあったわけです。H.263からSparkへのビットストリーム高速変換は可能なのでしょうか。開発の流れを紹介します。

H.263とSparkの文法の違い

さて、H.263とSparkはどのように違うのでしょうか? 実はほとんど同じで、誤解を恐れずに一言で言うと一部のヘッダが違うだけです。まあ誤解なんですけどね。それは追い追い明らかになります。実際、最初は簡単にビットストリームを書き換えるだけで変換できるはずだと思って進めました。ですから、まずはビットストリームの文法を確認してみましょう。

H.263は以下のような階層構造を持っています:

  • Picture
  • Group of Block (GOB)
  • Macro Block (MB)
  • Block

PictureはバイトアラインされたPictureヘッダで始まりますので、スキャンしていくと比較的簡単に見つかります。H.263とSparkで互換性はありません。Pictureの中には一つ以上のGOBが入っています。

GOBもバイトアラインされたGOBヘッダで始まります。これもやはりスキャンして容易に見つけることができます。ただしピクチャの最初のGOBはGOBヘッダを持ちません。内容がPictureヘッダと重複するからです。GOBはMBのかたまりを何行か含んでおり、H.263では一つのPictureを複数のGOBに分けているのに対して、SparkではPicture全体が大きな一つのGOBになっています。前述のとおり最初のGOBにはGOBヘッダは付きませんから、SparkにはGOBヘッダが出現しません。実際のところ、GOBというのは動画の符号化だけを考えたら必要ないものだと思うのですが、H.263はハードウェアでの並列処理を意識しているのだと思います。Sparkはシングルスレッドで処理する前提で符号化効率を向上させようとしているのでしょう。

MBもMBヘッダで始まります。H.263とSparkで文法は共通です。MBは画像の最小単位で、輝度要素のBlockを4個、色差要素のBlockをそれぞれ1個ずつ含みます。いわゆる4:2:0サンプリングです。ただし変化のないBlockはビットストリームから省略されたりするので、そこはヘッダをちゃんと読む必要があります。またInter Picture (H.263では、MPEGで言うところのI PictureとP PictureをそれぞれIntra Picture、Inter Pictureと言います) においては、MVD (Motion Vector Data) を含むこともあります。

Blockにはヘッダはありません。必要なことはMBヘッダに書かれています。H.263とSparkで文法は共通です。DC係数 (あったりなかったりしますが) と、量子化したDCT係数で構成されます。DCT係数はジグザグスキャンしたものを0のラン長とパルスの高さで符号化してあります。最後のビットは0のときもあるし、1のときもあるので、どこがBlockの終端なのかはパッと見では分かりません。

以上のように、下層の文法は共通で、上の方のヘッダだけが違ったりなかったりするわけです。

どうやって変換しようか?

文法を眺めてみると、Pictureヘッダを変換し、GOBヘッダを捨て、MBをコピーすれば変換できそうな気がしますが、コピーもそう簡単ではありません。

H.263では、DCT係数をBlockごとに一定の分母、Quantizerで除算することで量子化しています。Quantizerの値はPictureヘッダとGOBヘッダで直接指定されますが、MBでは±2の範囲で変化させることしかできません。それで、例えば最初のGOBの最後のMBはQuantizer=4で量子化されているのに、次のGOBの最初のMBはQuantizer=7で量子化されているというようなH.263ストリームは、そのままではSparkに変換できないのです。GOBヘッダを単に捨てたら画像は滅茶苦茶になるでしょうし、真面目に変換するなら再量子化して後のPictureもエンコードし直さなければなりません。そんなもの、最初からSparkのソフトウェアエンコーダを使った方がマシというものです。それで、GOB境界でQuantizerが大きく変化しないようにH.263エンコーダを設定できなければ、変換はできません。エンコーダによって違うと思いますが、例えば画質指定 (Quantizer指定) のVBRモードなどがあれば確実に変換できるでしょう。

次に変換が簡単なプログラムでできるかどうか、特にビットストリームの単純なコピーで済むのかどうかです。PictureヘッダとGOBヘッダはバイトアラインで始まるので、見つけるのは簡単です。しかし、GOBの終端はどうでしょうか。GOBの終端はすなわちBlockの終端ですが、それがどうなっているかは、真面目にパースしなければ分かりません。GOBの終端から次のGOBヘッダまでは0のビットでパディングされているのですが、Blockの最後のビットが1か0かは分からないので、どこまでがBlockで、どこからがパディングなのかはパースしないと分からないのです。ちなみにこの問題はH.264では最後のビットを1にすることで解決されているようです。

文法を検討することで、トランスコードするための条件が二つ分かりました:

  • H.263エンコーダを設定して、QuantizerがGOB境界で大きく変化しないようにする
  • トランスコーダはH.263のビットストリームを真面目にパースして各GOBの終端を把握しなければならない。手抜きはできない

文法だけのトランスコード

文法は把握できたので、あとはH.263のパーザ (パース結果はデバッグ用にプリントしてすぐに捨ててしまうのですが) を書き、ヘッダを書き換えてMBをコピーする処理を追加すれば、ビットストリームトランスコーダは完成します。ひたすら手を動かすのみです。簡単です。

しかし、こうしてトランスコードしたストリームは、実際にはまともに見られる動画になりませんでした。Intra Picture はちゃんと再生できるのに、Inter Picture は正しく再生できなかったのです。変換したストリームを適当にaviやflvに入れてmplayerで再生するとエラーメッセージもなく再生することができ、文法上の誤りはないようでした。それなのにInter Pictureはなぜか崩れてしまう。

実は重要なことを見落としていたのですが、それは何でしょうか? (続く) → 後編