動き補償の SSE2 実装

フレーム間圧縮を使用するビデオデコーダーにおいて、動き補償 (Motion Compensation) 処理は、 負荷の高いプロセスである。

Theora では、16 x 16, 8 x 8 ブロックの動き補償が行われるため、16 x 16 ブロックでは、 SSE 128 bit レジスターを、8 x 8 ブロックでは、MMX 64 bit レジスターを使用し、 ブロックの1ライン分を一括処理することで、効率を向上させる。

Pentinum 4 以降の x86 Intel プロセッサーでは、64 bit のミスアラインロード (MOVQ) には、 特にペナルティーがない。128 bit のアライメントされていないロード (MOVDQU) には、 ペナルティーが発生する。

NOMV (ゼロベクトル) での 16 x 16 ブロック動き補償では、128 bit のアライメント されたロード (MOVDQA) を使用して、ペナルティーを避けることができる。 0 でないベクトルでは、MOVDQU を使用するが、動き補償ではエンコーダーの動き検索の場合と違い、 一過性のロードであるため、アライメントロードへ最適化する効果は薄いかもしれない。

Half-Pixel Predictor

Theora では、全画素精度の動きベクトルに加えて、半画素精度を動きベクトルを使用する。

この際、ピクセル (画素) の予想値に近傍の2つの画素の平均値を使用する。

P = (A + B) >> 1

SSE が導入された際、PAVGB という平均値を求める命令が追加されたが、この命令では、

P = (A + B + 1) >> 1

という処理が行われるため、規格と相違が生じる。

そこで、切り上げが行われた場合に、補正を行うことで、規格に相当する処理を実現させる。

切り上げが行われる条件は、A, B の最下位ビットのどちらか1つが、1 である場合である。 その時に切り上げられた 1 を減算することで、補正を行う。実際の演算は、以下の通りである。

AVG = (A + B + 1) >> 1
P = AVG - ((A ^ B) & 1)

この場合のステップ数は、4である。

PAVGB 命令を使用しない場合、通常であれば 16 bit へ拡張を行って処理することになるが、 ビット演算のトリックを使用することで、8 bit 処理で平均値を求めることもできる。

説明のためまず 2bit での例を考えると、

A1,A0 は、A の各ビット (0 or 1)
B1,B0 は、B の各ビット (0 or 1)

P = ((2 * A1 + A0) + (2 * B1 + B0)) >> 1
P = ((2 * A1 + A0) + (2 * B1 + B0)) / 2
P = (A1 + B1) + (A0 + B0) / 2
P = (A1 + B1) * (2**0) + (A0 + B0) * (2**(-1))

** はべき乗

(A0 + B0) * (2**(-1)) の項を考えると、A0, B0 共に 1 のとき 1 となる。これは、AND 演算に相当する。

(A1 + B1) * (2**0) の項を考えると、A1, B1 共に 1 のとき 2 となる。A1, B1 のどちらかが 1 の とき (XOR) 1 となる。つまり、

P1 = (A1 & B1) << 1) + (A1 ^ B1)
P1 = ((A1 & B1) << (1 + 0)) + ((A1 ^ B1) << (1 - 1))

である。

ここで、一般項を考え n ビットの場合を考えると、

Pn = ((An & Bn) << (n + 0)) + ((An ^ Bn) << (n - 1))

と考えることができる。n ビットの演算では、全ての項の和をとることになるので、 結果として、

P = (A & B) + ((A ^ B) >> 1)

と計算することができる。

この場合のステップ数は、4であるが、PackedByte の右シフト命令が存在しないため、 実際にはマスク演算を組み合わせる必要があるので、ステップ数は5となる。

P = (A & B) + (((A ^ B) & 0xfe) >> 1)