フルスクラッチでコーデック

mpegデコーダを作っています。

上が読み込んだIフレーム、下がIフレームを切り張りして差分を加算して復号したPフレーム。


1:単純に動き補償でIフレームのブロックを切り張りした結果。


2:差分の符号化(イントラ含)

1+2で上記Pフレームが復元されます。Bフレームはまだ未対応です。

汚ソース&バグを含みますがmpgを読んでIフレームとPフレームを描画するコードを置きます。
http://g0307.hp.infoseek.co.jp/090720_mpg1dec.zip
VC2008等でコンパイルできます。読み込むファイルはmain.cppをいじってください。

DirectXのmediaSDKを使えば簡単に動画内の画像を読み込めるのですが
I,P,Bフレーム・マクロブロック単位のローレベル処理をするとなると
こういったデコーダが必要になるかと思います。

mpegの大体の処理内容は知っていたのですが、実際に読み込み処理を作るとなると
どうすればいいのかと思って作ってみましたが、思った以上に苦戦しています。

参考として
http://home.catv.ne.jp/dd/pub/book/mpeg.htmlのサイトと、
JPEGMPEG完全理解(http://www.coronasha.co.jp/np/detail.do?goods_id=2075
という本を購入しましたが、実際に実装するとなると不明な点が多かったため
The Berkeley MPEG Playerというデコーダのソースと出力結果と突き合わせで
作りました。
The Berkeley MPEG Player:
http://bmrc.berkeley.edu/frame/research/mpeg/mpeg_play.html

以下、実装するにあたってポイントや上記本やサイトでは分からない点を書きます。

■基本:ビット単位のデータの読み込み
mpegのデータはバイト単位ではなくビット単位で読み込む必要があります。
ファイルから動画データをunsigned char型でバッファにためておいて、

data = get_bits(n);

という感じでバッファからnビットのデータを取り出す関数を作る必要があります。
get_bitsはデータを読み込んでポインタを進めますが、データを読み込むだけの
関数show_bits(n)も必要になります。

mpge_playでVCLを高速に復号するテクニックとして、show_bitsで多めに
データを読み込み、その値をルックアップテーブルからひぱって来て
読み込んだ符号長をポインタに加算(flush_bit)する方式がとられています。

■パケット分解
上記サイト・本ではパケットレイヤの話がありません。動画データはパケット単位で
実体が入っていますので、ファイルからバッファを読み込んだあと
パケットの中身だけを取り出して、バッファにため込む必要があります。
そうしないと、パケットレイヤのヘッダの0x000001E0などが動画データに
混じって、おかしなことになります。
パケットレイヤの構成ですが、スターとコード0x000001E0〜0x000001EEの
後にパケットサイズ16bitの後に、stuffering(ゴミ?)として0xffが
並んでいるので飛ばします。16bitのデータの後、8bit,40bit,80bit
のいずれかのデータが来ます。その後、ようやく動画本体のデータが来ますので
ここからバッファにコピーしていきます。

■マクロブロックレイヤの終わりについて
スライスレイヤにマクロブロックが並んでいますが、スライスレイヤに
マクロブロック数が記述されているわけではないためマクロブロックを
読み込むごとに、マクロブロックの終了判定が必要になります。
判定方法は、バッファからデータを読み込んでスタートコード0x000001が
あるかどうかになります。これもshow_bits関数が役立ちます。

■動き補償(MC)データについて
こちらの実装の問題かもしれませんが、上記サイト・本のVCLではうまく動きませんでした。
mpeg_playの11bit(サイズ2048)のMC変換テーブルを持ってくると正常に動きました。
あと、MCデータから実際の動きベクトルの値への変換法が意味不明すぎたのですが
これもmpeg_playのComputeVector関数をそのまま引っ張ってきました。

■IDCTについて
上記サイトは式がおそらく違っていて正しくは

C_n = { 1/√2 (n==0)
1 (n!=0)
です。
あと、イントラブロックでDC成分は差分符号化で復号しますが、上記式に代入するときは
DC成分を128倍する必要があります。

■MBAについて
MBAの値の分だけブロックをスキップしますが、スキップされるブロックは現在の
動きベクトル値を使います。ちなみに、スキップした後にデコードした画像データを
書き出します。

■まだよく分からない点
動き補償で画像から飛び出したデータの補完法、動きベクトル、DC成分のリセットの
タイミング。


実装にあたって困った点は上記のような感じです。mpeg自体はそんなに難しくない
アルゴリズムなんですが、実装にあたって面倒くさいことが多すぎます。
資料もあまりないですし。これからコーデックや動画像処理を作る人に
役立てば幸いです。