【Netflixでも使ってる!】 映像品質評価ライブラリVMAFを使ったエンコードパラメータの決定方法の紹介

こんにちは、テリーです。Macbook Proの新商品は買いましたか? M1Pro/MaxのベンチマークがYouTubeにたくさん紹介されていますが、ほとんどが「動画を編集したときの操作感」「動画を出力してみたときの時間」という比較ですね。動画編集を扱わないユースケースでは、体感できるほどの差が出ないのかもしれません。

今回は動画のエンコードの話です。撮影した生のカメラ映像をファイルに出力または配信する場合、生のデータでは大きすぎるので、何かしらのコーデックを使ってファイルサイズを小さくします。「画質とフレームレートは極力よい状態を維持したい。しかしファイルサイズは小さくしたい。電波のよくない環境でも見れるようにしたい」というトレードオフがあります。画質をよくすればファイルサイズは大きくなり、画質を犠牲にすれば地下でも見れる人が増えます。多くの人が「このくらいでいいんじゃないか?」という経験や勘で決めているか、もしくは編集ソフトのデフォルト値をそのまま使っているようです。

では、その設定値(エンコードパラメータと言います)の最適解はどのように求めるのでしょうか? 世界最大級の映像配信会社Netflixが、自社で使用しているビデオ品質評価ライブラリVMAFをオープンソースで公開しています。Netflix規模の配信会社になると、配信データ量を1%減らすだけ莫大なコスト削減になるでしょう。本記事では、VMAFの使い方を紹介します。

動作環境

本記事は以下のバージョンを用いて動作を確認しています。

  • MacBook Pro (16-inch, 2019)
  • macOS Big Sur 11.6.1
  • VMAF 2.3.0
  • ffmpeg 4.4.1

インストール

VMAFはYUV形式での比較となるため、他の形式の動画を読み込ませるために、一般的にはffmpegを通して使用します。ffmpegのソースコードにすでに組み込まれています。本記事ではmacOS用のビルド済バイナリを使用しますが、独自でビルドすることも可能です。

ターミナルを開いて下記を実行してください。

mkdir ffmpeg
cd ffmpeg
wget https://evermeet.cx/ffmpeg/ffmpeg-4.4.1.zip
unzip ffmpeg-4.4.1.zip
cp ffmpeg /usr/local/bin
ffmpeg -version

下図のように表示されます。

比較用モデルデータの入手

VMAFを使用するためには、「モデル」と呼ばれる設定ファイルを/usr/local/share/modelというフォルダに設置します。

wget https://github.com/Netflix/vmaf/archive/refs/heads/master.zip
unzip master.zip 'vmaf-master/model/*'
cp -r vmaf-master/model /usr/local/share/

比較用動画の入手

本記事ではBigBuckBunnyという動画をサンプルとして使用します。読者ご自身で手配された動画データでも構いません。ffmpegがデコードに対応している形式ならば使用できます。
短時間で検証できるように、ダウンロードした動画を30秒間だけ部分的にカットし、base.mp4という名前にします。

wget http://distribution.bbb3d.renderfarming.net/video/mp4/bbb_sunflower_1080p_30fps_normal.mp4
ffmpeg -ss 15 -t 30 -i bbb_sunflower_1080p_30fps_normal.mp4 -vcodec copy -an base.mp4

比較の実行

下記のコマンドを実行してください。1つ目のffmpegコマンドは、比較用に画質を落としたファイルencoded.mp4を生成しています。2つ目のffmpegコマンドでencoded.mp4とbase.mp4の比較を行います。

ffmpeg -i base.mp4 -b:v 3000k -an -t 30 -y encoded.mp4

ffmpeg -i encoded.mp4 -i base.mp4 \
-lavfi "[0:v]settb=AVTB,setpts=PTS-STARTPTS[aa]; \
[1:v]settb=AVTB,setpts=PTS-STARTPTS[bb]; \
[aa][bb]libvmaf=log_fmt=xml:log_path=/dev/stdout: \
model_path=/usr/local/share/model/vmaf_v0.6.1.json: \
n_threads=16:shortest=1:repeatlast=0" -an -f null -t 29 -

下図のようにXMLが出力されます。

一番最後の行に出力された「VMAF score: 92.515588」という値が、2つの動画を比較したときのスコアです。コマンドで2つ目に指定した動画base.mp4を基準として、1つ目に指定した動画が2つ目に指定した動画に近ければ近いほど100点、まったく違っていれば0点になります。フレームごとに複数の指標(メトリック)で数値を算出し、指標値を元にVMAFスコアを求めます。全フレームを通して、最小値・最大値・平均値を計算し、最後に全フレームのVMAFの平均値をこの2つの動画の品質スコアとしています。

ターゲットスコアの設定

エンコードパラメータの調整においては、はじめにこの品質スコアの値をどれぐらいにするかを決め(ターゲットスコアといいます)、ターゲットスコアに近い値になるようにエンコードパラメータを調整していきます。

ターゲットスコアを定める(例えば80)ことによって、コンテンツやコーデックに関わらず映像品質が同等になります。計算されたスコアがターゲットスコアを上回っている場合はビットレートを下げて帯域を節約することができます。ターゲットスコアを下回っている場合はビットレートを上げるか、フレームレートや解像度を下げることを検討する必要があります。

ターゲットスコアの決め方は各社の自社基準ですが、一度決めると、作業スタッフ個人の感性に関わらず、コンテンツをすべて目視チェックすることなく品質を均質に保つことができます。

このあとは、解像度やターゲットを変えた動画を生成し、スコアがどのように変化するかを見ていきます。

解像度が異なる動画の比較

解像度が異なる2つの動画を比較するときは、拡大してから比較することになるため、計算量が多くなります。また、拡大処理のためにデータ本来の画質から若干変わってしまいます。
下記のコマンドを実行して、もともとの解像度である1080pの動画に加えて、小さい解像度の動画を3種類(720p,540p,360p)出力します。次にそれぞれのVMAFを計算します。コマンドの中の「scale=1920:1080」という値はbase.mp4の解像度です。「-t 29」という値は「先頭から29秒で止める」ことを意味しています。

ffmpeg -i base.mp4 -s 1920x1080 -b:v 3000k -an -t 30 -y 1080p.mp4
ffmpeg -i base.mp4 -s 1280x720 -b:v 3000k -an -t 30 -y 720p.mp4
ffmpeg -i base.mp4 -s 960x540 -b:v 3000k -an -t 30 -y 540p.mp4
ffmpeg -i base.mp4 -s 640x360 -b:v 3000k -an -t 30 -y 360p.mp4

ffmpeg -i 1080p.mp4 -i base.mp4 \
-lavfi "[0:v]scale=1920:1080:flags=bicubic,settb=AVTB,setpts=PTS-STARTPTS[aa]; \
[1:v]settb=AVTB,setpts=PTS-STARTPTS[bb];[aa][bb]libvmaf=log_fmt=xml: \
log_path=/dev/stdout:model_path=/usr/local/share/model/vmaf_v0.6.1.json: \
n_threads=16:shortest=1:repeatlast=0" -an -f null -t 29 -

ffmpeg -i 720p.mp4 -i base.mp4 \
-lavfi "[0:v]scale=1920:1080:flags=bicubic,settb=AVTB,setpts=PTS-STARTPTS[aa]; \
[1:v]settb=AVTB,setpts=PTS-STARTPTS[bb];[aa][bb]libvmaf=log_fmt=xml: \
log_path=/dev/stdout:model_path=/usr/local/share/model/vmaf_v0.6.1.json: \
n_threads=16:shortest=1:repeatlast=0" -an -f null -t 29 -

ffmpeg -i 540p.mp4 -i base.mp4 \
-lavfi "[0:v]scale=1920:1080:flags=bicubic,settb=AVTB,setpts=PTS-STARTPTS[aa]; \
[1:v]settb=AVTB,setpts=PTS-STARTPTS[bb];[aa][bb]libvmaf=log_fmt=xml: \
log_path=/dev/stdout:model_path=/usr/local/share/model/vmaf_v0.6.1.json: \
n_threads=16:shortest=1:repeatlast=0" -an -f null -t 29 -

ffmpeg -i 360p.mp4 -i base.mp4 \
-lavfi "[0:v]scale=1920:1080:flags=bicubic,settb=AVTB,setpts=PTS-STARTPTS[aa]; \
[1:v]settb=AVTB,setpts=PTS-STARTPTS[bb];[aa][bb]libvmaf=log_fmt=xml: \
log_path=/dev/stdout:model_path=/usr/local/share/model/vmaf_v0.6.1.json: \
n_threads=16:shortest=1:repeatlast=0" -an -f null -t 29 -

解像度(縦) 1080 720 540 480
VMAFスコア 92.515588 87.753861 80.765040 67.759832

解像度を小さくするほど、スコアが下がることが確認できます。

コーデックが異なる動画の比較

次は解像度を変えずに、コーデックを別のものにしてみましょう。はじめに7種類のコーデックごとに動画を作成し、次にオリジナルの動画との比較を行います。

ffmpeg -i base.mp4 -vcodec libx264 -b:v 3000k -an -t 30 -y x264.mp4
ffmpeg -i base.mp4 -vcodec libx265 -b:v 3000k -an -t 30 -y x265.mp4
ffmpeg -i base.mp4 -vcodec h264_videotoolbox -b:v 3000k -an -t 30 -y h264_vt.mp4
ffmpeg -i base.mp4 -vcodec hevc_videotoolbox -b:v 3000k -an -t 30 -y h265_vt.mp4
ffmpeg -i base.mp4 -vcodec vp8 -b:v 3000k -an -t 30 -y vp8.webm
ffmpeg -i base.mp4 -vcodec vp9 -b:v 3000k -an -t 30 -y vp9.webm
ffmpeg -i base.mp4 -vcodec libaom-av1 -b:v 3000k -cpu-used 5 -an -t 30 -y av1.mp4

ffmpeg -i x264.mp4 -i base.mp4 \
-lavfi "[0:v]settb=AVTB,setpts=PTS-STARTPTS[aa]; \
[1:v]settb=AVTB,setpts=PTS-STARTPTS[bb]; \
[aa][bb]libvmaf=log_fmt=xml:log_path=/dev/stdout: \
model_path=/usr/local/share/model/vmaf_v0.6.1.json: \
n_threads=16:shortest=1:repeatlast=0" -an -f null -t 29 -

ffmpeg -i x265.mp4 -i base.mp4 \
-lavfi "[0:v]settb=AVTB,setpts=PTS-STARTPTS[aa]; \
[1:v]settb=AVTB,setpts=PTS-STARTPTS[bb]; \
[aa][bb]libvmaf=log_fmt=xml:log_path=/dev/stdout: \
model_path=/usr/local/share/model/vmaf_v0.6.1.json: \
n_threads=16:shortest=1:repeatlast=0" -an -f null -t 29 -

ffmpeg -i h264_vt.mp4 -i base.mp4 \
-lavfi "[0:v]settb=AVTB,setpts=PTS-STARTPTS[aa]; \
[1:v]settb=AVTB,setpts=PTS-STARTPTS[bb]; \
[aa][bb]libvmaf=log_fmt=xml:log_path=/dev/stdout: \
model_path=/usr/local/share/model/vmaf_v0.6.1.json: \
n_threads=16:shortest=1:repeatlast=0" -an -f null -t 29 -

ffmpeg -i h265_vt.mp4 -i base.mp4 \
-lavfi "[0:v]settb=AVTB,setpts=PTS-STARTPTS[aa]; \
[1:v]settb=AVTB,setpts=PTS-STARTPTS[bb]; \
[aa][bb]libvmaf=log_fmt=xml:log_path=/dev/stdout: \
model_path=/usr/local/share/model/vmaf_v0.6.1.json: \
n_threads=16:shortest=1:repeatlast=0" -an -f null -t 29 -

ffmpeg -i vp8.webm -i base.mp4 \
-lavfi "[0:v]settb=AVTB,setpts=PTS-STARTPTS[aa]; \
[1:v]settb=AVTB,setpts=PTS-STARTPTS[bb]; \
[aa][bb]libvmaf=log_fmt=xml:log_path=/dev/stdout: \
model_path=/usr/local/share/model/vmaf_v0.6.1.json: \
n_threads=16:shortest=1:repeatlast=0" -an -f null -t 29 -

ffmpeg -i vp9.webm -i base.mp4 \
-lavfi "[0:v]settb=AVTB,setpts=PTS-STARTPTS[aa]; \
[1:v]settb=AVTB,setpts=PTS-STARTPTS[bb]; \
[aa][bb]libvmaf=log_fmt=xml:log_path=/dev/stdout: \
model_path=/usr/local/share/model/vmaf_v0.6.1.json: \
n_threads=16:shortest=1:repeatlast=0" -an -f null -t 29 -

ffmpeg -i av1.mp4 -i base.mp4 \
-lavfi "[0:v]settb=AVTB,setpts=PTS-STARTPTS[aa]; \
[1:v]settb=AVTB,setpts=PTS-STARTPTS[bb]; \
[aa][bb]libvmaf=log_fmt=xml:log_path=/dev/stdout: \
model_path=/usr/local/share/model/vmaf_v0.6.1.json: \
n_threads=16:shortest=1:repeatlast=0" -an -f null -t 29 -
コーデック x264 x265 H.264 VideoToolBox H.265 VideoToolBox VP8 VP9 AV1
VMAFスコア 92.515588 93.742381 78.886235 75.399565 80.472284 87.360544 96.499582

コーデックの選択によってスコアに優劣がはっきりとわかります。ターゲットスコアを80とするならば、VP9やAV1ではもっとビットレートを下げる余地があると言えます。

まとめ

このように、コーデック、解像度、フレームレート、平均ビットレート、最大ビットレート、キーフレームインターバル、プロファイル、その他コーデック固有のオプションなど、パラメータ1つを順に変えていき、ターゲットスコアに近づくようにすることで最適なパラメータを見つけることができます。また、想定と異なる異常なスコアが出た場合には、パラメータが間違っている可能性も検出できます。MacでもWindowsでも気軽に試せるのでぜひご自身の環境で挑戦してみてください。

有料サービスのご紹介

WebRTCを利用した会員制ライブ配信サービスを短期間で導入したい方、大規模配信のためのサーバ構築・インフラ運用を専門家に任せたい方は「ImageFlux Live Streaming」をご検討下さい。WebRTC SFUという技術を用いて、配信映像をサーバに中継させる仕組みです。1対多の会員制映像配信に特に強みがあり、初期の利用コストが低価格に抑えられます。