技術

ffmpeg 2.8のh264_qsvがSandy Bridgeで動作しない問題の回避策

いきなり余談

最近Monster X3AというHDMIキャプチャボードを買いまして、ここ数年ずっと愛用してるHDDレコーダーDVR-W1の録画データをH264で圧縮しつつPCにどんどこ取り込んでおります。
レコーダーの空き容量がここ最近ずっと逼迫した状態だったのでPCに取り込むことで解決を図る、という考えです。
(録画よりも何となくAmazonビデオやHuluやDアニメストアなどの動画サイトのほうを優先して見てしまうから溜まってしまうんですよね。)

わざわざキャプチャするのではなく、レコーダーのHDDをもっと大きいものに換装するほうが楽ですが、新しいHDDを買うのは4TB以上のもののGB単価がもっと下がってから、と何となく考えているので今回この方法はパス。
4TBもだいぶ下がってきましたね~。
でもまだ3TBがGB単価的に一番オトクなんですよね。
3という数字嫌いではないですがPC関係ではあまり使いたくない感があります。
(棚に置いてあるPhenom X3の箱が目に入りました)

それと、HDDはいくつか持っていますがHDMIキャプチャボードは持ってなかったので、一度使ってみようという思いもありました。

で、録画をPCに取り込むとその分レコーダー側の空き容量を増やすことができ、同時にH264で圧縮することでPC側の容量消費も抑えられます。
最終的に1280×720 29.97fps 約2Mbpsで保存するようにしてます。
その場合レコーダーの録画データ(1440×1080のMPEG2 TSを暗号化したもの?)が約4分の1に圧縮できるようです。

追記
今はCQPのI:25, P:27, B:30でエンコードしています。
ですので元の映像の複雑度次第で最終的なサイズは大きく変わります。
アニメを中心にいろいろエンコードしてみた感じでは10分の1~3分の1くらいになるようです。

HDMIキャプチャによる取り込みなので、等速再生・等速録画するしかなく、例えば30分の録画の取り込みに最低30分はかかります。
これ自体は仕組み上どうしようもありません。
ただ、できるだけ取り込み時の手間を減らすため、OpenCVを使ってレコーダーに表示される番組名や再生時間を自動認識するプログラムをC#で作成し使用しています。
このプログラムによって、レコーダーのリモコンの再生ボタンを押すだけで自動的にキャプチャを開始し、再生が終われば自動でキャプチャを停止し、認識した番組名から動画のファイル名を自動的に決定する、ということが可能で、取り込みの作業がとても楽になりました。

といいつつ、実のところ番組名認識のための判定器(kNN)の学習は随時行うようにしているため、未知の文字が出現する度に人間が教えてあげなきゃいけません。
しかしDVR-W1のメニューはシンプルかつ等幅の文字を使っているため、認識の精度を上げやすく、また現在学習データがだいぶ溜まってきたので、人の手によって学習させる手間はだいぶ減ってきています。

本題

録画の取り込みにはffmpegを使用しました。
X3Aに入力される映像と音声をDirectShowフィルタ(X3Aのドライバインストールで使用可能になる)経由で取り込み、それをそのままリアルタイムでh264_qsvを使用してm4vで保存するという感じです。

ffmpegのQSV対応バイナリは公式には配布されていないため、誰かがビルドしたバイナリを使うか、QSVが使えるよう自前でビルドする必要があります。

そこで以下のページを参考に自前でビルドしました。
FFmpeg2.8のQSV(H264)を拡張してみる

これはWindows 8.1 64bit & Haswell (Celeron G1820)の環境で問題なく動作していました。

ちなみにffmpegの実行は以下のようなオプションにて行っていました。

ffmpeg -rtbufsize 256MB ^
-f dshow -s 1920x1080 ^
-r 29.97 -vsync cfr ^
-video_pin_name 0 ^
-audio_pin_name 1 ^
-i video="SKNET MonsterX3 HD Capture (Path 0)"^
:audio="SKNET MonsterX3 HD Capture (Path 0)" ^
-vcodec h264_qsv ^
-s 1280x720 -b:v 2000k ^
-threads 2 output.m4v

これをWindows 10 64bit & Sandy Bridge (Core i3 2100T)の環境に移行したところ、”Error initializing an internal MFX session”というエラーが発生するようになり、QSVによるエンコードが全く行えなくなってしまいました。

当然最初は環境を疑いました。
しかしHandBrakeでは問題なくQSVでエンコード出来ました。

そこでffmpegのソースを見てみたところ、QSVのどの機能を有効にするかどうかを単にmfx_dispatchライブラリに定義されている定数を元にコンパイル時に決定しているようでした。

QSVと言っても使える機能の詳細はIntel HD Graphicsの世代 ≒ CPUの世代によって変わります。
例えばHaswell以降の世代はlook ahead機能が使えるとかSkylake世代からHEVC(H265)に対応するとか。

なので、本来QSVのどの機能を有効にするかどうかは実行時に動的に決定すべき類のもののはずです。
ですが、ffmpegはコンパイル時にしかも実際のハードウェアとは全く関係ない定数によって決定してるため、実際には使えない機能を使おうとして失敗する、ということが起きるようです。

そこでその定数をSandy Bridgeに合うよう(具体的にはQSV(MFX)バージョン 1.4)に変更し、再コンパイルすることでとりあえずQSVでエンコード出来るようにはなりましたが、出力のDTSやPTSがおかしな値になりごく一部のプレーヤーを除いてまともに再生出来ない動画ファイルが出来上がってしまいます。
(同時にエンコード中にNon-monotonous DTS ~の警告が山ほど出ます。)

これも修正できないかソースをちょっと読んでみたのですが、簡単には対応できなさそうでした。

ソースを見た感じ、ffmpegのQSV対応は今のところかなり「とりあえず」感がつよいです。
Haswell以降(かつWindows 7以降)なら多分普通に使えるとは思います。

追記
違う見方をすれば、ffmpegはQSV(mfx_dispatchやIntel Media SDK)に対してある種の想定をしたうえで、その想定にそって正しく実装されているようにも見えます。
その想定が筋が通ったものなのか(だとするとmfx_dispatchもしくはIntel Media SDKがおかしいということになります)は正直私には分かりません。

さらに追記
ffmpeg 2.8ではなく3.0の話ですが、こちらの記事(素晴らしい!)
FFmpeg3.0のQSV(H264)を拡張してみる
によると、look aheadがデフォルトでオンになってるせいで、look ahead非対応CPUによるQSVが動作しないらしいです。
そして”-look_ahead 0″というオプションを追加するだけでこの問題は回避できるようです。
なんかスッと納得出来ましたw
なるほど!という感じです。
ただこの方法が2.8でも通用するのかは、ちょっと手元に確認できる環境が残ってない(QSV非対応3.0に移行してしまった)ので確認出来てません。
もし2.8でも通用するとすれば、この記事まるごと無意味になりますね(笑)
ただリンク先にも書いてありますが、QSVにはffmpegよりもオプションが分かりやすく細かく設定できるQSVEncのほうを私もおすすめします。

回避策

ffmpegによるQSVエンコードについては諦めて別途QSVEncCを使うようにしました。

QSVEncCはDirectShowフィルタを直接入力することはできませんが、ffmpeg経由でパイプを使いデータを流してやることで、QSVEncCによるリアルタイムH264エンコードができるようになります。

具体的なコマンドは以下の様な感じです。

ffmpeg -rtbufsize 256MB ^
-f dshow -s 1920x1080 ^
-r 29.97 -vsync cfr ^
-video_pin_name 0 ^
-audio_pin_name 1 ^
-i video="SKNET MonsterX3 HD Capture (Path 0)"^
:audio="SKNET MonsterX3 HD Capture (Path 0)" ^
-acodec aac output.aac ^
-s 1280x720 -r 29.97 -pix_fmt yuv420p -f yuv4mpegpipe - | ^
QSVencC --y4m --quality best -i - -o output.mp4

muxer -i output.mp4?fps=2997/100 -i output.aac -o output.m4v

リアルタイムエンコード終了後に別途L-SMASHのmuxer.exeを使用してmuxしており、この分追加の時間がかかります。
30分の番組の場合数秒で処理が終わるためまぁよいかなと。

また、別途muxするため音ズレの心配がありましたが、30分の取り込みでいくつかテストしたところ特に音ズレが気になる場面はありませんでした。
これはキャプチャに専用のPCを使ってるからかもしれません。
他の処理の影響でエンコードが間に合わなくなってフレームがドロップしてしまったときにどうなるかは不明です。

実はこのままだと延々とキャプチャが続いてしまうので、再生時間が過ぎたタイミングでffmpegをkillする必要があります。
この辺りは自作のプログラムで自動的にやるようにしました。

ffmpegのオプションでdurationを指定すればkillする必要はないのですが、再生時間も文字認識で判定しているため誤った再生時間をdurationに指定してしまった場合、キャプチャ中に後から変更する方法は多分ないのでこのような方式にしています。
まぁ再生時間の判定は0-9の文字しか出現しないためほぼ100%の精度なのですが一応念のため。

ffmpegだけでQSVエンコードしてた時よりファイルサイズが少し小さくなりつつ格段に画質が上がったのは嬉しい誤算でした。
特に動きのある場面の画質の向上が顕著です。
上の例ではQSVEncCのオプションで品質をbestにしてますが、デフォルトのbalanceでもそうでした。(もちろんbestのほうがより良いです。)
QSVEncCすごい…

追記
ファイルサイスが小さくなったのはたまたまでした。
今回の場合ffmpegではVBRを、QSVEncCではデフォルトのCQPを使ってることになります。
つまりQSVEncCではビットレートを指定してるわけではないため、出力されるファイルのサイズは映像ソースによって大きく変わります。
(VBRでもそういう傾向は当然ありますがCQPほど影響は大きくないようです。)
私が試した限りでは、止め画が多めのアニメなど圧縮が効きやすいソースではVBRの場合と比べファイルサイズが小さくなり、動きの多い実写映像などではVBRの場合より2倍くらい大きくなりました。

追記
今はQSVEncCのCQPのデフォルトの品質値より少し下げてI:25, P:27, B:30で運用しています。
これだとそこそこの品質でおおむねVBR 2Mbpsの場合よりファイルサイズを小さくできるようです。
(主にアニメで確認)

動きのある場面の例。左: ffmpeg, 右: QSVEncC(best)
ffmpeg_qsvencc_diff

追記
これもffmpegとQSVEncCの比較というよりは、QSVのVBR 2MbpsとCQP I:24 P:26 B:27(QSVEncCのデフォルト)の比較という方が適切でしょう。

ffmpegでも色々オプションを指定すれば同じ画質にできるのかもしれません。
ただ、ffmpegのオプション結構謎いので…
それにソースを見た今となっては、オプションを指定してもちゃんと動いてくれるか疑わしい…

コメントを残す

メールアドレスが公開されることはありません。



※画像をクリックして別の画像を表示