酢ろぐ!

カレーが嫌いなスマートフォンアプリプログラマのブログ。

iOS/macOSでFFmpegを使ってh264エンコードすると画質が悪くなる問題 (h264_videotoolbox vs libx264)

f:id:ch3cooh393:20220306131429p:plain

iOSアプリにtanersener/ffmpeg-kitを組み込んで、iPhoneで録画した動画をffmpegを使ってh264エンコードするとどの動画にもノイズが入る問題が発生した。macOS上で同じ動画をエンコードした場合にはノイズが乗らない。なぜノイズの有無が発生してしまうのか? この差異を認識するのに随分時間をかけてしまった。

結論としてはffmpegで使われるエンコーダーが異なるのが原因である。iOSアプリ上ではApple社が実装したコーデックの h264_videotoolbox が使われ、macOS上では libx264 が使われる。h264_videotoolboxはハードウェアエンコードをおこなうのでエンコード時間は短くて済むが画質が悪くなってしまう。

h264 といったエイリアスを使うのではなく、厳密な名称である h264_videotoolboxlibx264 を指定すれば、macOS上でも同等の現象を再現できる。

画質が悪いことに気づいた経緯

以下のパラメータを使って動画のエンコードをした。テスト動画を用いて複数のパラメータを使ってエンコードして、サイズと画質のバランスが良いパラメータを選んだ。

ffmpeg -i original.mov -c:v h264 -b:v 1000k output_h264.mp4

しかしiOS上で動画をエンコードすると下図のようにブロックノイズが乗ってしまう。期待する画質が得られないことがわかった。

f:id:ch3cooh393:20220306120811p:plain
左:macOSでエンコードした動画(libx264が使われている)。右:iOSでエンコードした動画(h264_videotoolboxが使われている)。

当初は同じh264エンコーダーを指定しているのに何故画質に違いが出るのかわからなかった。

-c:v h264 はエイリアスであり内部的には異なるコーデックが使われていた

ffmpegに詳しい方にとっては当たり前のことなのかもしれないが、-c:v h264 はエイリアスであり内部的には異なるコーデックが使われていた。これは数日調査してわかった。

homebrew経由でインストールした ffmpeg で利用可能なコーデックを調べると以下の結果が得られる。

$ffmpeg -codecs

 DEV.LS h264                 H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10 (encoders: libx264 libx264rgb h264_videotoolbox )

iOSアプリ上で ffmpeg -codecs を実行してみたところ以下の結果が得られた。iOSアプリ上では libx264 を使うことができない*1

 DEV.LS h264                  (encoders: h264_videotoolbox )

つまり -c:v h264 はエイリアスであり、h264を指定するとmacOSでは libx264 のエンコーダーが使われ、iOSでは h264_videotoolbox のエンコーダーが使われることがわかった。

エンコードパラメータとして h264 を使うのではなく、より厳密な h264_videotoolboxlibx264 を指定していればもっと早く画質が悪い原因が特定できていたと思う。

h264_videotoolbox を使って高画質動画にするためにはビットレートを上げる

iOSでは h264_videotoolbox を使わないといけないが、libx264 の感覚でビットレートを指定すると低画質になってしまう。h264_videotoolboxエンコーダーを使って高画質の動画にするにはどうすればよいか? 答えはStack Overflowに書かれていた。

SOのAnswerによると以下の通りである。ここでは libx265 と書かれているがこのまま libx264 と読み変えられる。

多くのハードウェアアクセラレーションエンコーダと同様に、hevc_videotoolbox は libx265 ほど効率的ではありません。そのため、libx265と同等の品質を実現するには、かなり高いビットレートを与えなければならないかもしれません。これは、H.264からHEVC/H.265に再エンコードする目的を失うかもしれません

h264_videotoolbox を使う場合には libx264 より高いビットレートを指定しなければいけないことがわかった。どこまでビットレートを上げれば同等くらいの画質になるのか検証した。ここでの「同等くらいの画質」はさくさん基準によるため、各位には動画の性質によって適切な求めてもらいたい。そして良さげなビットレート設定を教えてもらえると嬉しい。

libx264(1000k) vs h264_videotoolbox(5000k)

h264_videotoolboxでの指定ビットレートを1000kから5000kに変更した。パラメータは下記のように指定している。

ffmpeg -i original.mov -c:v h264_videotoolbox -b:v 5000k output_videotool_5000k.mp4

h264_videotoolboxでもビットレートを -b:v 5000k まであげることで、libx264の動画よりも高画質にすることができた。しかしlibx264の動画サイズは 17.7MB に対して、h264_videotoolboxの動画サイズは 78.9MB とかなり大きくなってしまった。

f:id:ch3cooh393:20220306123934p:plain
左:libx264を使って-b:v 1000k指定した動画。右:h264_videotoolboxを使って-b:v 5000k指定した動画。

libx264(1000k) vs h264_videotoolbox(2000k)

h264_videotoolboxでの指定ビットレートを1000kから2000kに変更した。パラメータは下記のように指定している。

ffmpeg -i original.mov -c:v h264_videotoolbox -b:v 2000k output_videotool_2000k.mp4

h264_videotoolboxの指定ビットレートを -b:v 2000k にあげることで、libx264の動画と同等の画質にすることができた。libx264の動画サイズは 17.7MB に対して、h264_videotoolboxの動画サイズは 32.9MB と2倍程度に抑えることができている。

f:id:ch3cooh393:20220306124406p:plain
左:libx264を使って-b:v 1000k指定した動画。右:h264_videotoolboxを使って-b:v 2000k指定した動画。

結論

以上のことからffmpegで使われるエンコーダーが異なるのが原因であることがわかった。

iOSアプリ上ではApple社が実装したコーデックの h264_videotoolbox が使われ、macOS上では libx264 が使われる。h264_videotoolboxはハードウェアエンコードをおこなうのでエンコード時間は短くて済むが画質が悪くなってしまう。macOSでも-c:v h264_videotoolboxと指定すればハードウェアエンコーダーを使うことができる。

M1 MacBook Proを使っていてもlibx264を使うと等倍でしかエンコードできない。これは10分の動画をエンコードするのに10分かかってしまうことを意味している。対して h264_videotoolbox は6倍でエンコードできる。これは10分の動画を約1.6分でエンコードできることを意味している。

ユーザーを待たせることができるのであれば*2libx264一択で良いだろう。画質面・ファイルサイズ面で優れている。もしユーザーを待たせることができないのであれば h264_videotoolbox を使い、指定ビットレートを上げてファイルサイズを犠牲にして画質を上げる必要がある。

*1:厳密的には異なるが本記事での紹介は省く

*2:そしてライセンス的な問題がなければ