H.264/AVC JM Reference Software について
JM は、H.264 の規格を作っていく際に並行して作成していったソフトウェアです。
エンコーダとデコーダから成り、ソースコードは完全にオープンになっています。
このページでは、この参照ソフトウェアを読み解くにあたって、必要な情報をまとめています。
導入
- 入手: http://iphome.hhi.de/suehring/tml/
- C言語で記述されている。
- Visual Studio 2012 までのソリューションファイルが用意されている。
- Visual Studio 2015 以降では、下記の修正が必要。
- lcommon/inc/win32.h の "# define inline _inline" を「#if (_MSC_VER < 1900)」で無効化する。Visual Studio 2015 以降、xkeycheck.h によって inline などの予約語を #define するとエラー "The C++ Standard Library forbids macroizing keywords. Enable warning C4005 to find the forbidden macro. " になる。
- lcommon/inc/win32.h の INT64_MIN 定義を「#if (_MSC_VER < 1600)」で無効化する。stdint.h で定義されている。
- ソースコードの文字コードを UTF-8 (BOM付き)に変更する。日本語 OS で Visual Studio を使用した際には、コンパイラはソースコードを Shift_JIS として解釈し、コメント中の「Guy Côté」が原因でコンパイル時に警告を受ける。オリジナルは UTF-8 (BOMなし) と思われる。
各プロジェクトについて
- ldecod プロジェクトは、デコーダーアプリ。
- lencod プロジェクトは、エンコーダーアプリ。
- rtp_loss プロジェクトは、RTPでパケットロスしたことをシミュレートするアプリ。入力したファイル(RTPパケットヘッダーつき)をランダムにパケロスさせた結果を出力ファイルに書き込む。
ldecod
H.264 要素ストリーム(Annex.B) を読むソフトウェア。
仕様
- 入力は H.264 要素ストリームファイル(Annex. B)
- 出力は YUV ファイル。画面に各フレームの情報。
- 出力結果をバイナリ比較する機能あり。
- 4:2:0、4:2:2、4:4:4 をサポートする。
使用方法
- 入出力ファイルを引数でコントロール可能。
- 入力ファイル名は -p InputFile="hoge.264" と指定する。
デコード処理の大まかな構造
- decoder_test.c に main() 関数。
- Configure() → OpenDecoder() → { DecodeOneFrame() + WriteOneFrame() } × N → FinitDecoder() → CloseDecoder() が主な流れ。
- 上記の関数は ldecod.c に定義あり。
- DecodeOneFrame() で、1フレームずつデコードを行う。基本的に decode_one_frame() を呼び出しているだけ。
- WriteOneFrame() で、デコード結果を書き出す。
- decode_one_frame() は image.c に定義あり。
- decode_one_frame() では、最初に1つのアクセスユニットを切り出す処理を行い、次にそのアクセスユニットのデコード処理を行う。
- アクセスユニット切り出し処理の流れは次の通り。
- read_new_slice() でスライスデータを読み取る。
- アクセスユニットのデコード処理の流れは次の通り。
- スライス数分 { init_slice() + decode_slice() } を繰り返す。
- デコードが終わったら exit_picture() で後始末を行う。
- 各ピクチャのタイプやYUV形式などを標準出力に書き込んでいるのも exit_picture() 関数。
- image.c 以下の呼び出し階層は次のようになる。
- image.c → nalu.c → annexb.c
- image.c → cabac.c
- image.c → macroblock.c → mb_prediction.c → intra_***.c
雑多
- defines.h と global.h はほとんどのソースでインクルードされるヘッダーファイル。
- 各フレームの情報表示が image.c の exit_picture() 関数で行われる。
- ldecod.c にグローバル変数 DecoderParams *p_Dec; あり。これが各種ソースで参照されるため、デコーダーの再利用には注意が必要。OpenDecoder() で初期化され、CloseDecoder() を呼び出すまで使用される。この変数は global.h にて外部変数宣言されており、ほとんどのソースが global.h をインクルードしているためにどこでも使用可能になっている。
ファイルリード
- annexb.c でファイルをオープンし、512*1024 バイトのバッファにリードする。
- バッファからは1バイトずつ getfbyte() で読みだしていて、バッファの残りデータ量を ANNEXB_t::bytesinbuffer で管理している。
- バッファの残量が 0 になったときに getfbyte() を呼び出すと、getChunk() が自動で呼ばれ、バッファが再度満たされる。
DPB (Decode Picture Buffer) の操作
- mbuffer.c で行っている。
- store_picture_in_dpb() でデコード済みピクチャをバッファのフレーム・ストアに詰める。
- バンピング処理、store_picture_in_dpb() 内で output_one_frame_from_dpb() を経由して、remove_frame_from_dpb() が呼び出されて実施される。(バンピング処理:ピクチャをバッファのフレーム・ストアに詰める際、古いピクチャーを追い出し、空きフレーム・ストアを確保する処理)
アクセスユニット
- アクセスユニット境界の見つけ方は、規格書の「7.4.1.2.3 Order of NAL units and coded pictures and association to access units」に定義あり。
- image.c の decode_one_frame() が1アクセスユニットを読み出す処理。
- 「while (current_header != SOP && current_header != EOS)」によって、次のアクセスユニット境界まで読んでいる。
- image.c の is_new_picture() は「7.4.1.2.4 Detection of the first VCL NAL unit of a primary coded picture」に相当する。
PPS/SPSのアクティブ化
- 規格書の「7.4.1.2.1 」に定義あり。
- parset.c の activate_sps() や activate_pps() が該当。
- VideoParameters 構造体のメンバ SeqParSet 配列に 32 個の SPS を保持している。
- VideoParameters 構造体のメンバ PicParSet 配列に 256 個の PPS を保持している。
- スライス層をデコードする際には、VideoParameters 構造体のメンバ activate_pps と activate_sps を使用する。それぞれ前述の配列の要素を指すポインタである。