ESP @Wiki

P-X2641

最終更新:

匿名ユーザー

- view
メンバー限定 登録/ログイン

How to use libx264


for all developers and lovers...
現在ページが縦に長くなってしまっているので、後程分割いたします。

libx264概要


open


x264コーデックは、オープンする際に、画像の大きさやFPS、さらにはクオリティの設定等を保持した構造体をパラメータとして渡し、オープンする必要がある。これらの情報のうち、最低限必要なものは、縦と横の大きさである。流れをまとめると以下のようになる。

  1. パラメータの構造体を得る
  2. サイズ、クオリティの設定を行う
  3. コーデックをオープンする

パラメータの構造体を得る


x264_param_t *p = (x264_param_t *)malloc(sizeof(x264_param_t)); // malloc
x264_param_default(p); // 構造体を初期化する

サイズ、クオリティの設定を行う


p->i_width  = 720;
p->i_height = 480;

コーデックをオープンする


x264_t *hEncoder = x264_encoder_open(p);

hEncoderが実際にオープンされた、コーデックへの参照を保持する。

encode


エンコードを行う際は、入力としてYUVフォーマット画像データを入力する必要がある。現在は、YUV 4:2:0 Planarと呼ばれる形式を用いる。よって、まずデコードされた画像データをこのYUVプランナーにコピーを行う。

これでエンコードを行う準備が整ったので、コーデックのエンコードファンクションを呼び出す。通常の感覚では、これで終わりであると思うが、x264の場合、nal encodeという処理もこの後に続けて行ってあげる必要がある。

  1. 入力画像をコピーする
  2. x264_encoder_encodeをコール
  3. x264_nal_encodeをコール

入力画像をコピーする


入力画像をコピーする対象は、 x264_picture_t構造体のメンバーである、x264_image_t構造体である。基本的にポインタで操作されるので、picture_t用のメモリ領域を確保する必要があり、x264からその関数が提供される。

x264_picture_alloc(&picIn, X264_CSP_I420, width, height);

これで、picInに領域が確保される。この例のように、プランナーと、横幅、縦幅を指定してあげればよい。

次に、実際のYUV420のコピーであるが、picIn->img.plane[0]にY、picIn->img.plane[1]にU、picIn->img.plane[2]にVをコピーする。plane変数は2重ポインタなので気をつけたい。

実例として以下のようにコピーする、

memcpy(picIn->img.plane[0], yuv->data[0], height * width);       // Y
memcpy(picIn->img.plane[1], yuv->data[1], height * width / 2); // U
memcpy(picIn->img.plane[2], yuv->data[2], height * width / 2); // V

これで、入力用のデータ準備が完了した。

x264_encoder_encodeをコール


bytesWritten = x264_encoder_encode(hEncoder,
                                     &nal,
                                     &nalCount,
                                     &picIn,
                                     &picOut);

hEncoderとpicInについては説明済み。

簡単なところとして、まずpicOutを説明。この変数には、出力される画像のslice type、つまりIとかBとかPといった情報が記録される。このpicOutでは、特にmallocなどの作業は必要ない。

次にnal。ぶっちゃけ不明。きっとnal_encodeを行う際に必要な情報が保持されるんだろうと思う。これは、普通にmallocをして領域を確保する必要がある。

最後に、nalCount。エンコード結果この値が0より大きい場合(大抵の場合そうなる)に、nal_encodeを行う必要がある。

x264_nal_encodeをコール


この処理が、libx264を利用する際のキモになる。解説が難しく、ソース読んで、って言いたくなる。

基本はこう
for (int i = 0; i < nalCount; i++) {

    bytesWritten = x264_nal_encode(nalEncodeBuffer +totalBytesWritten,
                                   &nalEncodeBufferSize,
                                   1, 
                                   &nal[i]);

  totalBytesWritten += bytesWritten;

}

つまり、先ほど得られたnalCountの回数だけ、x264_nal_encode()を呼び出す。第1引数nalEncodedBufferが最終的なエンコード結果のデータである。私の場合、バッファを使い回すので、書き込むごとにオフセットさせている。第2引数nalEncodeBufferSizeは結構適当でOK。

第3引数は、MP4でアウトプットする場合、必ず1である必要がある。通常b_annexebと呼ばれる。とにかく1である。

最後の引数nalは、先ほどのx264_encoder_encode()で設定された、あれである。

次に全体像
totalBytesWritten = 0;
for (int i = 0; i < nalCount; i++) {

  bytesWritten = x264_nal_encode(...); //省略

  int type = nalEncodeBuffer[totalBytesWritten + 4] & 0x1F;

  if (type == 1 || type == 5 || type == 6) {
     nalEncodeBuffer[totalBytesWritten + 0] = ((bytesWritten - 4) >> 24) & 0xFF;
     nalEncodeBuffer[totalBytesWritten + 1] = ((bytesWritten - 4) >> 16) & 0xFF;
     nalEncodeBuffer[totalBytesWritten + 2] = ((bytesWritten - 4) >>  8) & 0xFF;
     nalEncodeBuffer[totalBytesWritten + 3] = ((bytesWritten - 4) >>  0) & 0xFF;
  }

  totalBytesWritten += bytesWritten;

}

  • このシークエンス、int type,,,,以下はこの通りにコーディングして下さい。こんなこと言っていいのかわかりませんが、(bytesWritte - 4) >> 24,,,のよくわからない処理をすっ飛ばした場合、"Quicktimeで再生した場合、コンピュータをクラッシュさせてしまう"ムービーが出来てしまいます。

これで、libx264を用いたエンコードに関する処理の説明は終了である。デコード、コピー、エンコード、ナルエンコードを繰り返すことにより、映像のエンコードが実現される。

mp4 header


エンコードの結果得られたデータを、MP4コンテナに詰め込むためには、事前にヘッダ情報を書き込む必要がある。この、ヘッダ情報もやはり、x264から提供される関数で得ることができるが、その内容を切り出すには、自力でルーチンを組む必要がある。またヘッダ情報は、SPSとPPSと呼ばれる2種類のものが必要となる。

  1. x264_encoder_headers()からヘッダ情報を得る
  2. SPS・PPS情報を得る

x264_encoder_headers()からヘッダ情報を得る


まず、どのタイミングでヘッダ情報を得ることができるようになるのかという疑問があるだろう。答えは、コーデックをオープンしていれば得ることができる。通常この処理はオープンの直後に行うことが多い。

_hEncoder = x264_encoder_open(param);
x264_encoder_headers(_hEncoder, &nal, &nalCount);

この引数、nalとnalCountは直前で説明した変数である。このヘッダ関数を呼び出すと、主にnalにSPSとPPSの情報が書き込まれる。そこで、このnalからSPSとPPSを抽出するのだ。

SPS・PPS情報を得る


このヘッダ抽出は規格に基づいた処理であるので、以下のソースに掲げる通りである。

// SPS header
int spsLen; u_int8_t* sps;

spsLen = 1 + nal[1].i_payload;
sps = (u_int8_t *)malloc(spsLen);
sps[0] = 0x67;

memcpy(&sps[1], nal[1].p_payload, spsLen-1);

// PPS header
int ppsLen; u_int8_t* pps;

ppsLen = 1 + nal[2].i_payload;
pps = (u_int8_t *)malloc(ppsLen);
pps[0] = 0x68;

memcpy(&pps[1], nal[2].p_payload, ppsLen-1);

こうして得られた情報を、libmp4v2へ渡せば良い。ただ、エンコードしている最中にもSPSやPPSの情報が得られることがあるが、通常この最初の1回のみヘッダ情報を渡せば良い。

quality setting


2-pass


まとめのコード