• C++
  • Assembler
  • H.264
  • 01 Nov 2015

    Pretty Pull Request

    ちょっと前に OpenH264 というエンコーダに小さなプルリクを出した時のメモが以下のように今も残ってた。

    この Mac のデフォルトのメモ帳は要素を増やすとそれなりに動作が緩慢になっていくので消そうと思ったけど ただ消すのも忍びないので書き残しておくことにした。

    メモでは色々調べていたけど、作業的には 小さなもの だった。

    ことの発端としてはこういうサイト を見ながら、小さなSIMDプログラムを書く機会があったが、本当に小さくてすぐ終わってしまったので 他にも何かしらで書けないかと考えた時についでにOSSにプルリク投げてみるか、という機運が高まったことから始まった。 最終的にSIMDコード書けてないわけだけど…。

    さらに、SIMDは intrinsics で書きたかったのだけれど、 SIMDはどこもアセンブラで、メモを見返してみるとアセンブラについて調べた部分がいくらかあった。

    書いてて思い出してきたが AVX2の開発も受け付けてるという記述があり、書こうと思ったら そもそもAVX2がプログラム内で有効であるか検出する機構がないので、とりあえずそれを書いたという流れだったと思う。

    コミットログを眺めてみたけどぱっと見AVX2コードは入ってなさそう。今はあんまりやる気がない..。

    もう書くことがないので、あとはつらつらメモの残りを羅列することにする。 プロジェクトを何も知らない人が読んでも意味がわからず、メモ同士の関連性も低く 本当に有用にするには補助的な文章が必要だろうけど面倒くさいのでしない。

    あと、ここに書いてあることの正しさは保証してませんので予めご了承。

    OpenH264の解析

    ISVCEncoder

    空っぽのクラス。抽象クラス。 具象クラスは CWelsH264SVCEncoder になる。メインのでかいクラス。 WelsCreateSVCEncoder() は encoder/plus/src/welsEncoderExt.cpp にある。

    エントリーポイント

    codec/console/enc/src/welsenc.cpp にコンソール版の main() がある。

    EncodeFrame

    EncodeFrameInternalを呼び出す。入力がI420じゃない場合は中断。

    EncodeFrameInternal
    WelsEncoderEncodeExt

    メインな関数。色々呼び出す。分かったものだけ記述。

    DecideFrameType

    フレームタイプを決定する。x264にも似た名前の関数があるけど、x264と違ってここから探索が発生することはなさそう

    Intra予測の関数
    MEはどこ/MEはどこから発火する?

    svc_motion_estimate.cpp:WelsMotionEstimateSearch[Static|Scrolled]

    encoder_ext において pFuncList->pfMotionSearch に代入されている。 代入後 core/src/svc_base_layer_md で利用されている。

    WelsMotionEstimateSearch は内部で pFuncList->pfCalculateSatd を呼び出す

    WelsMdP16x16 はどこから呼ばれる
    WelsMdInterMb と WelsMdInterMbEnhancelayer の違い

    アセンブラ

    読む上で気をつけたこと。

    データの処理単位

    セミコロンの前の文字はよく命令の suffix についている。

    メモってあった頻出命令

    mov[d|q|dqa|dqu|sxd]

    lea

    アドレス計算命令。フラグレジスタが変更されない。アドレス自体の演算に利用。

    pxor

    ビット単位の xor を実行。オペランドには 64bit同士、128bit同士を指定することができる。

    psub[b|w|d]

    オペランドに64bit同士、128bit同士をとれる。 suffix がそれぞれ byte, word, double-word を指すので 8bit/16bit/32bit スロットでの演算となる。

    pmax[s|u][|b|w|d]

    レジスタを2つとり、スロット毎に比較して最大値を格納する。 値を評価する場合に符号あり・なしとスロットの大きさで命令が分かれる。

    psadbw

    符号なし8bit整数で絶対値誤差の合計値を計算して格納レジスタの下位16bitに書き込む。 オペランドに64bit/128bitレジスタ同士を取る。

    shufps

    名前的にはシャッフルを押すほどのシャッフルでもないけど確かにシャッフル可能な命令。 オペランドを3つとり(うちSIMDレジスタが2つ)、スロットを32bitで評価してそれぞれの SIMDレジスタから2つのスロットを格納先へ書き込む。

    pmaddubsw

    Multiply-ADD Unsigned Byte, Signed Word

    乗算はオペランドレジスタを符号なし8bit整数スロットで評価し、 次のオペランドレジスタは符号あり8bitで評価。 乗算の結果を2つずつ和にして16bitスロットとして格納先レジスタへ書き込む。

    movehl_ps

    各 hi-64bit を2つのSIMDレジスタからとって merge。

    YASM/NASM

    用いられているアセンブラ。

    codec/common/x86/inc_asm.asm に共通マクロがある。

    わかったものだけ。

    AVX2の検出

    AVXの検出

    OpenH264のアセンブラ関数

    あんまり調べてない。

    WelsSampleSadFourWxH

    4つ分計算する。この4つ分は上下左右の1pixelだけずれたものを計算している。

    WelsSampleSad4x4_mmx

    ナイーブに実装されている。mmxなのでレジスタ長が64bitなので8bit*8で2line分まで入るので よしなにロードして2回 psadbw するだけ。

    WelsSampleSad16x16_sse2

    128bitSAD命令で 4x16 を4回呼ぶ構成。

    場所

    codec/common/x86/satd_sad.asm