Chienomi

動きの少ないゲームのキャプチャ(エロゲとか)のVP9エンコード

Live With Linux::practical

私自慢のmmfft9を用いて先日、放課後シンデレラ2というエロゲーのキャプチャをlibvpx-vp9を用いてエンコードしたのだが、かなりいまいちな結果になった。

絵が切り替わるときに結構荒れてしまうのと、目パチがうまく出ないという問題があるのだ。

前提として、VP9は画面が完全に切り替わると静止画として改めて切り直すので、一瞬だけビットレートが上がり、そこから動かなければすぐ下がる、という挙動を見せる。 この関係もあり、ゲームのキャプチャはHEVCでやるよりも、VP9のほうが荒れが少なく、結果的に低いビットレート(=小さいファイルサイズ)で良好な視覚品質を得られる。

しかし、エロゲーになると画面の大部分が変更されないため、極めて低いビットレートで差分を取る状態になり、結果「差分が落とされて」しまう。 HEVCではCRFを上げても「これ以上ビットレートが下がらない」という状態になりこそすれ、そうでない限りソースに関わらず割と似たようなビットレートになることが多い。対して、VP9はソース次第でかなり極端なビットレートまで落ちる。 これはアクションゲームのキャプチャでは良い結果を得られるが、エロゲーでは裏目に出てしまうのだ。

そこで、今回は実際にエロゲーのスクリーンを用いてlibvpx-vp9でどのような設定にすれば良いアーカイブが作れるかを検証する。 使用したタイトルはSugar*Styleで、純粋なアドベンチャーパート(立ち絵の部分)を30秒切り出した。 ソーススクリーンサイズは1282x758で、30FPSである。

CRFのみの結果

CRF値 ビットレート
20 890kb/s
24 793kb/s
28 674kb/s
36 499kb/s
42 394kb/s

この程度ならCRFを思い切り下げてしまえばいい、という結論になりそうだ。 まず、42だと激しく荒れる。36も立ち絵が切り替わるときなどは荒れてしまう。

libvpx-vp9はビットレートが「下げられるなら下げる」ので、ソースが単調なものであればCRF値を思い切り下げてもビットレートはあまり上がらない。

24で気にならないレベルになったが、24のときに一度輪郭線が点滅するという問題が生じたことがあった。 アドベンチャー形式のエロゲの場合、CRF値を20-24程度としたConstant Qualityにするのがよさそうだ。

「フラワーナイトガール」で試す

フラワーナイトガールはDMM/FANZAのブラウザゲーム(ソシャゲ)である。

画面としては、アドベンチャーパートとソシャゲでよくある画面類(育成画面とか)、そして戦闘パートに分けて考えることができる。 戦闘パートはそれなりに激しい動きのあるアニメーションとなっている。 このため、通しで撮影すると場面の起伏が激しい。CRF38のとき、戦闘パートは2000kb/s程度、アドベンチャーパートは500kb/s程度に落ち着く。

だが、動きのある戦闘パートは、そこまで低いCRF値を設定しなくても十分鑑賞に耐えるクオリティになる。

VP9はビットレートをそこまで拘束しないため、戦闘パートのビットレートのアタマを抑えると良さそうだ。

ffmpeg -i fkn.mkv -c:v libvpx-vp9 -c:a libopus -b:v 0 -crf 28 -b:a 128k fkn-vp9.webm

これで3931kb/sになった。ブラウザゲームでこのビットレートはかなり高い。 原神の4kでもこれくらいのビットレートである。

そこでCRF値をもっと上げたいところだが、満足できるくらいの値(38とか)にするとアドベンチャーパートは荒れが目立つ。 そこで、次のような設定を考えた。

ffmpeg -i fkn.mkv -c:v libvpx-vp9 -c:a libopus -b:v 2500k -crf 24 -b:a 128k fkn-vp9.webm

これで2400kb/sとなった。悪くない。

さらに、これで2passにしてみた。

ffmpeg -i fkn.mkv -c:v libvpx-vp9 -an -b:v 2500k -crf 24 -pass 1 -f null /dev/null
ffmpeg -i fkn.mkv -c:v libvpx-vp9 -c:a libopus -b:v 2500k -crf 24 -pass 2 -b:a 128k fkn-vp9.webm

2556kb/sとなった。ビットレートはあまり変わらないが、明らかにノイズが減ってきれいになった。 ただし、pass2のエンコード速度も1passのときの半分程度に落ちてしまい、かなり時間がかかった。

libvpx-vp9はConstrain Qualityで-b:vを指定したときに、その値を目指してエンコードするわけではなく、ビットを投入する余地に乏しいと考えれば(Q値が下がっても)低ビットレートで節約しようとする。 また、映像品質は静止画に近いアドベンチャーパートのほうが神経質になることから、CRF値はアドベンチャーパートで担保してほしい品質に基づいて決めれば良いということだ。 一方、アドベンチャーパートを基準にしたCRF値だと動きの激しい戦闘パートでは非常に高いビットレートになってしまう。 それは望ましくないので、動きの激しい場面においても許容できる上限値を-b:vで指定するのが良いわけだ。

値をどうやって決めるか

それがアドベンチャータイプのエロゲーであれば話は非常に単純で、「安心できるくらいに(過剰だと思う程度に)低いCRF値を設定すれば良い」である。

切り替えでの荒れを気にするなら2passにしたほうが良い。 唐突に小さな変化があるようなソースは、短いフレームでは適切なビットレート配分を見誤る可能性が高いからだ。 ただ、より多くのCPUパワーを必要とするから、そこにリソースをつぎ込むかどうかという問題は発生する。

このタイプの映像ではCRF値を下げてもビットレートは十分低いため、容量の節約を求めてリソースをつぎ込むよりは、単純にCRF値を下げて高品質にエンコードしてもらったほうが手間も少なく、十分な確実性がある。 20くらいのCRF値を設定すれば、1passでのエンコードであっても満足できる映像品質を得られるだろう。

だが、動きの激しいパートが混在する場合、手動で切り分けることなく一発でエンコードしようとするとなかなか難しい。 Constrain Qualityにするというのはひとつ良い案なのだが、アドベンチャーパートが重要な場合はCRF値はエロゲーと同等の基準にするとして、戦闘パートのビットレートを抑えるためのビットレート値はいくつにするかというのがちょっと難しい。

私の結論としては、「リハーサルが必要」である。

戦闘パートだけを30秒から1分程度切り出して、CRF値を変更しながら実際にエンコードしてみる。 そして、容量との妥協点を見つけたら、当該CRF値でエンコードした動画のビットレートを基準にしてビットレート値を決める。

このようなソースにおいては2passエンコードを行ったとき、品質(ビット)の配分がかなり最適化されるため、Contrain Qualityでの品質向上はかなり期待できる。 2passエンコードは「目標に対しより正確に近づける」というものだが、Contrain Qualityでは「ビットレートキャップを制約とした上で、指定された品質にできるだけ近づける」という処理となり、結果的に容量の配分がよくなり、映像が荒れにくくなる。

だが、そもそもソシャゲの戦闘パートのエンコードは結構重い部類に入る。 これはCPUパワーも必要で、容量もかさみやすいという意味で、ただでさえ時間がかかるのに、2passにすることで時間が増加する量も多い。 かなり悩みどころだ。

また、2passにすると戦闘パートに容量を割くためか、それともアドベンチャーパートでの最適化が進むためか、アドベンチャーパートのビットレートは下がる。 これが許容できない場合は、さらにCRF値を下げてもいいが、場合によっては(その2つの場合にフォーカスすれば十分なのであれば)-minrate-maxrateを指定するという方法もアリかもしれない。

Contrain Qualityはなかなか使える

これまであまり気にしてこなかった指定方法だが、単純にゲーム動画のエンコードという点でもContrain Qualityを検討する価値はある。

例えば格闘ゲームのGUILTY GEAR -STRIVE-はFHDでCRF値44でも大きいものは15Mb/s程度になってしまう。 原神が4kで5Mb/s程度だからかなり大きい。

しかし、格闘ゲームのキャプチャをアーカイブする上で重要なのは動きであって、絵の美麗さではない。 多少映像がガサついても構わないのだ。 だが、CRF値を上げても非常に大きなビットレートになる動画を、CRF値をさらに上げて対応するのはlibvpx-vp9では難しい。 libx265だとCRF値を上げるとなんとしてでも容量を抑え込んでくるが、libvpx-vp9はよほどのことがない限りそこまで下げないのだ。 それに、全体に渡って品質を思い切り下げて欲しいわけではない。

このようなケースでは、ほどほどのCRF値を設定しつつ、上限ビットレートを指定したほうが現実的に動作する。 これは、「このCRF値で期待した品質が存在するんだけど、その品質を目指すとしてもこれ以上に容量は割いてほしくない」という意図を達成できる。

また、これは単純にキャップとして動作するわけではなく、意図をある程度反映したような挙動になる。 このため、ビットレートを指定しない場合に非常に大きなビットレートになるソースで、それを下回るビットレートを指定した場合に、上限ビットレートに張り付くわけではなく、相対的に容量が大きくなるところが上限ビットレート付近になるような挙動を見せる。

例として、GGSTのキャプチャをCRF値40でエンコードしたところ、通常は11000-13500kb/s程度で推移するのに対し、-b:v 8500kを指定したところ6500-8500kb/s程度で推移した。

これも適切な値はリハーサルしないと出しづらいが、エンコードしてみたものをffprobeで確認して、「いや、そんな大きくならないだろ!?」という気持ちになったらやってみるといいかもしれない。 libvpx-vp9は割と人間の感覚としてそんなに容量を投入しなくていい動画にがんばって容量を使ってきれいに出そうとしたり、逆に静止画としての美しさが欲しいようなもので容量を節約しすぎて荒れたりといったことがあるので、ある程度試行錯誤は必要である。

これもあって、ソースに違いが大きいビデオカメラ撮影の動画のエンコードはあまり向いていない。 エンコードに時間がかかる上に、ソースに合わせた設定を出すための試行錯誤が動画ごとに必要になるからだ。 libx265のほうはざっくりしたCRF値で納得できる結果になる(動きが激しい動画とかは大きくもなるし荒れもする)ため、カメラ映像にはHEVCのほうが良さそうだ。

なお、-b:vを指定した場合に、これを下回るビットレートで品質が満たせる場合に影響はないのかというと、なくはない。 例えば、CRF値44で4936kb/sでエンコードされた原神のプレイ動画を、-b:v 7500kをつけてエンコードし場合、5300kb/sへと上昇した。

mmfft9はこれに対応

従来、CRF値を指定しないエンコードはできなかったが、bv, min, maxのいずれかが定義されている場合、デフォルトのCRF値をセットするのをやめた。 CRF値とビットレートの両方を指定したい場合は、明示的にcrfを指定する必要がある。

また、ff_optionsとして2pass: yesとした場合は、自動的に2passエンコーディングを行うように変更した。