QSV 対応 Intel CPU で H.264 のハードウェアエンコード on Linux

# はじめに
H.264 エンコードもすっかり普及して久しい感のある今日この頃。
画質ではソフトウェアエンコードに及ばないものの、速度や負荷の面で有利なハードウェアエンコードは用途によってはとても有用です。
今回は、バージョン 3.1 で VAAPI によるエンコードに対応した [FFmpeg](https://ffmpeg.org/index.html#pr3.1) を使って QSV 対応 CPU による H.264 のハードウェアエンコードを試します。

## Intel QSV
[Intel QSV (Quick Sync Video)](http://www.intel.co.jp/content/www/jp/ja/architecture-and-technology/quick-sync-video/quick-sync-video-general.html) は、最近 (Sandy Bridge 以降) の Intel の CPU に搭載されているハードウェアエンコーダ/デコーダです。
CPU の世代によっては MPEG2 や H.264 だけでなく H.265 や VP8 のエンコード/デコードにも対応しています。
QSV を利用するには対応したアプリケーションが必要です。

## Intel Media Server Studio
Linux 上で QSV を利用するには [Intel Media Server Studio](https://software.intel.com/en-us/intel-media-server-studio) (以下 MSS) をインストールし、QSV 対応アプリ (FFmpeg 等) から利用するという方法があるようです。[^1] [^2] [^3] [^4]
MSS に含まれている Intel Media SDK を使用することで QSV を利用できるようになるのですが、MSS は対応する CPU の世代が限定されていたり[^5]、特定のディストリビューション・カーネルバージョンが推奨されていたり[^6]、カーネルのビルドが必要だったり[^7]、と気軽に利用するには少しハードルが高い感じがします。

[^1]: [LinuxでIntel QSVを使ったH.264ハードウェアエンコード（CentOS7＋ffmpeg） | Kung Noi Blog](http://www.goodnai.com/blog/2015/11/24/linux%E3%81%A7intel-qsv%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%9Fh-264%E3%83%8F%E3%83%BC%E3%83%89%E3%82%A6%E3%82%A7%E3%82%A2%E3%82%A8%E3%83%B3%E3%82%B3%E3%83%BC%E3%83%89%EF%BC%88centos7%EF%BC%8Bffmpeg/) (MSS 2015r6 使用例)
[^2]: [クリスマスなのでLinuxでQSVエンコードする - 犬アイコンのみっきー](https://mzyy94.com/blog/2015/12/21/linux-qsv-encode/)  (MSS 2015r6 使用例)
[^3]: [Linux (CentOS 7.1) と FFmpeg 3.0.1 で QSV (Quick Sync Video) | hirooka.pro](https://hirooka.pro/?p=8305) (MSS 2016 使用例)
[^4]: [Linux (CentOS 7.2 1511) と FFmpeg で QSV (Quick Sync Video) | hirooka.pro](https://hirooka.pro/?p=8562) (MSS 2017 使用例)
[^5]: MSS 2017 は Broadwell/Skylake のみに対応
[^6]: MSS 2017 では CentOS 7.2 が推奨されている
[^7]: MSS 2017 ではカーネルのビルドは不要になった模様[^4]

## VAAPI
[VAAPI (Video Acceleration API)](https://01.org/linuxgraphics/community/vaapi) とは、Linux 上で動画のエンコード/デコードにハードウェアアクセラレーションを利用するためのオープンソースな API で、Intel の [Graphics for Linux*](https://01.org/linuxgraphics) というプロジェクトの一環として開発されています。
クローズドソースな Intel Media SDK を使用する場合に比べてパフォーマンスは劣るようですが[^8]、MSS のように CPU の世代やディストリビューションに縛られることなく QSV が利用できるので、手軽に試してみるには良さそうです。

[^8]: [Comparison of performance between SDK and only VAAPI based Application](https://software.intel.com/en-us/forums/intel-media-sdk/topic/595283)

# インストール
さっそく自宅サーバとして稼働中の PRIMERGY TX1310 M1 で VAAPI を試してみました。
スペックは以下の通りです

CPU : Xeon E3-1246v3 (Haswell Refresh / Intel HD Graphics P4600)
RAM : 32GB (DDR3-1600 non-ECC)
Chipset : Intel C226
OS : CentOS 7.2

## VAAPI / Intel ドライバ
CentOS で VAAPI を利用するためには `libva` と `libva-intel-driver` が必要です。
(Ubuntu では `libva1` と `i965-va-driver` がそれらしい?)
今回は該当パッケージが含まれている [Simone Caronni さんのリポジトリ](http://negativo17.org/handbrake/) を利用します。
野良リポジトリの利用が気になる場合は [VAAPI の公式](https://www.freedesktop.org/wiki/Software/vaapi/) からソースをダウンロードしてビルドするのが良いでしょう。

まず `epel` リポジトリを追加します。
ついでに `yum-config-manager` を使うために `yum-utils` も入れておきます。

```
$ sudo yum install epel-release yum-utils
```

Caronni さんの `epel-handbrake` リポジトリを追加します。

```
$ sudo yum-config-manager --add-repo=http://negativo17.org/repos/epel-handbrake.repo
```

追加したリポジトリをデフォルトでは不使用にしておきます。

```
$ sudo yum-config-manager --disable epel epel-HandBrake
```

必要なパッケージをインストールします。
`libva-devel` は後ほど ffmpeg のビルド時に必要となります。
`libva-utils` に含まれる `vainfo` は VAAPI の状況を確認するのに便利です。

```
$ sudo yum --enablerepo=epel,epel-HandBrake libva libva-devel libva-intel-driver libva-utils
```

VAAPI を使用できるように、自ユーザを `video` グループに追加します。
( `/etc/udev/rules.d/` に適当なルールを作成して `/dev/dri/renderD128` のパーミッションを 666 等にしても良いと思います)

```
$ sudo usermod -aG video ユーザ名
```

ログインし直して `id` コマンド等で `video` グループに属していることを確認したら `vainfo` コマンドで VAAPI が利用できることを確認します。

```
$ vainfo
error: can't connect to X server!
libva info: VA-API version 0.38.1
libva info: va_getDriverName() returns 0
libva info: Trying to open /usr/lib64/dri/i965_drv_video.so
libva info: Found init function __vaDriverInit_0_38
libva info: va_openDriver() returns 0
vainfo: VA-API version: 0.38 (libva 1.6.2)
vainfo: Driver version: Intel i965 driver for Intel(R) Haswell Server - 1.6.2
vainfo: Supported profile and entrypoints
      VAProfileMPEG2Simple            : VAEntrypointVLD
      VAProfileMPEG2Simple            : VAEntrypointEncSlice
      VAProfileMPEG2Main              : VAEntrypointVLD
      VAProfileMPEG2Main              : VAEntrypointEncSlice
      VAProfileH264ConstrainedBaseline: VAEntrypointVLD
      VAProfileH264ConstrainedBaseline: VAEntrypointEncSlice
      VAProfileH264Main               : VAEntrypointVLD
      VAProfileH264Main               : VAEntrypointEncSlice
      VAProfileH264High               : VAEntrypointVLD
      VAProfileH264High               : VAEntrypointEncSlice
      VAProfileH264MultiviewHigh      : VAEntrypointVLD
      VAProfileH264MultiviewHigh      : VAEntrypointEncSlice
      VAProfileH264StereoHigh         : VAEntrypointVLD
      VAProfileH264StereoHigh         : VAEntrypointEncSlice
      VAProfileVC1Simple              : VAEntrypointVLD
      VAProfileVC1Main                : VAEntrypointVLD
      VAProfileVC1Advanced            : VAEntrypointVLD
      VAProfileNone                   : VAEntrypointVideoProc
      VAProfileJPEGBaseline           : VAEntrypointVLD
      VAProfileH264MultiviewHigh      : VAEntrypointVLD
      VAProfileH264MultiviewHigh      : VAEntrypointEncSlice
      VAProfileH264StereoHigh         : VAEntrypointVLD
      VAProfileH264StereoHigh         : VAEntrypointEncSlice
```

利用可能なプロファイル一覧が出てくれば成功です。
X を起動していないので X と接続できない旨のエラーが出ていますが特に問題ありません。
ここで `VAEntrypointVLD` の項目はハードウェアデコード可能、`VAEntrypointEncSlice` の項目はハードウェアエンコード可能です。

これで VAAPI を利用する準備が整いました。

## FFmpeg
前項で利用した `epel-HandBrake` リポジトリからも `ffmpeg` は入手できるのですが、現時点でバージョンが 2.8.8 と少し古いため、以下の公式手順に従って最新版の FFmpeg をビルドします。
[https://trac.ffmpeg.org/wiki/CompilationGuide/Centos](https://trac.ffmpeg.org/wiki/CompilationGuide/Centos)
この手順ではホームディレクトリ以下に必要なライブラリを展開してビルドするので OS 環境に影響を与えません。

`libva-devel` が正しくインストールされていれば自動的に VAAPI に対応した `ffmpeg` がビルドされますが、念のため `ffmpeg` の `configure` を実行した際の出力の中で

- `External libraries providing hardware acceleration:` の項目に `vaapi` が含まれていること
- `Enabled hwaccels:` の項目に `h264_vaapi` 等の利用したいコーデックが含まれていること

を確認しておきましょう。

ビルドに成功したら改めて VAAPI への対応を確認します。

```
$ ~/bin/ffmpeg -hwaccels 2>/dev/null
Hardware acceleration methods:
vaapi

$ ~/bin/ffmpeg -encoders 2>/dev/null |fgrep vaapi
 V..... h264_vaapi           H.264/AVC (VAAPI) (codec h264)
 V..... hevc_vaapi           H.265/HEVC (VAAPI) (codec hevc)
 V..... mjpeg_vaapi          MJPEG (VAAPI) (codec mjpeg)
```

# エンコード
それでは実際にエンコードしてみましょう。

今回はアニメ 1 話 30 分の TS ファイルをエンコードしてみます。
`ffmpeg` 実行時はオプションの順番にも気をつけましょう。

```
$ ~/bin/ffmpeg -vaapi_device /dev/dri/renderD128 \
> -hwaccel vaapi -hwaccel_output_format vaapi \
> -i anime.ts \
> -vf 'format=nv12|vaapi,hwupload,scale_vaapi=w=1280:h=720' \
> -c:v h264_vaapi -profile 100 -level 40 -qp 23 -aspect 16:9 \
> -c:a copy \
> anime.mp4
(略)
frame=53848 fps=343 q=-0.0 Lsize=  711827kB time=00:29:56.66 bitrate=3245.6kbits/s dup=24 drop=0 speed=11.4x
$ 
```

2分38秒でエンコードが完了しました！速いですね！
`top` で確認すると `ffmpeg` プロセスのCPU 使用率は終始 12% 程度でした。

||オリジナル|エンコード後|
|:-:|:-:|:-:|
|長さ|29:56|29:56 (ノーカット)|
|ファイルサイズ|2,030 MB|695 MB|
|解像度|1440x1080|1280x720|
|フレームレート|29.97 (60i)|29.97 (30p)|
|映像コーデック|MPEG2|H.264|
|音声コーデック|AAC|AAC (無変換)|

## オプション
`ffmpeg` で VAAPI を使用するときのオプションは以下のような感じです。

<dl>
<dt>-vaapi_device</dt>
<dd>使用する VAAPI デバイスを指定します。QSV 対応 CPU を利用する場合は <code>/dev/dri/renderD128</code> の模様。</dd>
<dt>-hwaccel</dt>
<dd>デコードに使用するハードウェア機能を指定します。今回はデコードも QSV を利用したため <code>vaapi</code> を指定しました。デコードをソフトウェアで行う場合はこのオプションは不要です。</dd>
<dt>-hwaccel_output_format</dt>
<dd><code>-hwaccel</code> と一緒に使用し、ハードウェアデコード後の形式を指定します。デコードもエンコードも VAAPI で行う場合、ここで <code>vaapi</code> を指定するとデコード結果が直接エンコーダに渡され、処理がより高速になります。ただしエンコードの前に <code>ffmpeg</code> のデインターレース等のソフトウェアフィルタを利用したい場合、デコード結果を一旦デコーダから <code>ffmepg</code> 側に渡す必要があるので <code>yuv420p</code> 等を指定します。</dd>
<dt>-i</dt>
<dd>入力ファイルを指定します。ハードウェアデコードを使用する場合はこれより先に上記のオプションを記述しておきます。</dd>
<dt>-vf</dt>
<dd>フィルタを指定します。複数のフィルタは <code>,</code> で区切って記述します。VAAPI を利用する場合は基本的に <code>format=nv12|vaapi,hwupload</code> を指定しておけば大丈夫だと思います。今回は <code>scale_vaapi</code> フィルタを加えてリサイズにもハードウェアを使用してみました。</dd>
<dt>-c:v</dt>
<dd>映像コーデックを指定します。<code>h264_vaapi</code> を指定すると VAAPI を使用して H.264 のエンコードが行われます。</dd>
<dt>-profile</dt>
<dd>H.264 のプロファイルです。デフォルト値は 100 (High) です。Baseline は 66、Main は 77 を指定するようです。</dd>
<dt>-level</dt>
<dd>H.264 のレベルです。実際のレベルを 10 倍した値を指定します (Level 4.1 なら 41)。解像度・フレームレートや再生デバイスによって選択しましょう。デフォルト値は 51 (Level 5.1) です。今回は Level 4.0 としました。</dd>
<dt>-qp</dt>
<dd>P フレームの品質を指定します。1 (最良) ~ 51 (最悪) で設定します。デフォルトは 20 です。数値が小さいほど高品質・高ビットレートになり、大きいほど低品質・低ビットレートになります。<code>ffmpeg</code> で VAAPI を使用する場合、デフォルトではこの値を元にした CQP エンコードとなるようです。</dd>
<dt>-b</dt>
<dd>ビットレートを指定します。このオプションでビットレートが指定された場合 <code>-qp</code> オプションは無視され、CBR エンコードとなるようです。今回は CQP でのエンコードを試したためこのオプションは指定しませんでした。</dd>
<dt>-aspect</dt>
<dd>今回はエンコード前後で PAR が異なるため DAR を明示的に指定しています。</dd>
</dl>

その他にもいくつか VAAPI 関連のオプションがありますが、詳細は以下が参考になります。
[https://wiki.libav.org/Hardware/vaapi](https://wiki.libav.org/Hardware/vaapi)
[https://gist.github.com/Brainiarc7/95c9338a737aa36d9bb2931bed379219](https://gist.github.com/Brainiarc7/95c9338a737aa36d9bb2931bed379219)

## 比較
`-qp` オプションの値をいくつか変えてエンコードしてみました。
またソフトウェアエンコード (x264) とも比較してみました。
x264 で使用したオプションは以下です。

```
$ ~/bin/ffmpeg -i anime.ts \
> -codec:v libx264 -profile:v high -level 4.0 -preset medium -qp XX -s 1280x720 \
> -c:a copy \
> anime_x264_qpXX.mp4
```

x264 では qp は 0~69 だったため、((VAAPI の qp) * 69 / 51) を指定してみました。
ついでに x264 では qp よりも crf 指定の方が一般的と思われるため crf 23 も比較してみました。
オプションは上記の `-qp XX` を `-crf 23` に変えて実行しました。

|No|形式|ファイルサイズ|エンコード時間|
|:-:|:-:|--:|--:|
|0|オリジナル|2,030 MB|-|
|1|VAAPI / qp 20|1,027 MB|2:39|
|2|VAAPI / qp 23|695 MB|2:38|
|3|VAAPI / qp 26|501 MB|2:38|
|4|x264 / qp 27|321 MB|7:21|
|5|x264 / qp 31|219 MB|6:52|
|6|x264 / qp 35|161 MB|6:18|
|7|x264 / crf 23|349 MB|8:22|

結果は上記の通りです。

<dl>
<dt>画質</dt>
<dd>1,4,7 ではブロックノイズが目立つこともなくまずまずの感じでした。2,5 になると動きのある部分で少しノイズが目につき出し、3,6 ではさらにノイズがのり一部で輪郭もぼやけています。また VAAPI では他の QSV 検証記事などでも書かれているように、グラデーション表現でノイズが出やすい気がしました。</dd>
<dt>速度</dt>
<dd>VAAPI 使用時が速いです。さすがハードウェアエンコードというところですね。VAAPI では qp 値はエンコード速度にあまり影響しないようです。対して x264 では qp 値に比例して速度が上がっています。Xeon をぶん回したので x264 でもそれなりの速度が出ました。</dd>
<dt>負荷</dt>
<dd>VAAPI では <code>ffmpeg</code> プロセスのCPU 使用率は 12% 程度でした。一方 x264 では 700% 前後とほぼフル稼働、ファンの回転数も上がって非エコな感じでした。自宅サーバはエンコード専用マシンではないのでエンコード時に高負荷になり他プロセスに影響が出るのは避けたいところです。</dd>
<dt>圧縮率</dt>
<dd>こちらはさすが x264、VAAPI の 2 倍以上の圧縮率となっています。ディスクの節約が最優先の場合は x264 一択ですね。</dd>
</dl>

これらを踏まえてどれを選択するかは完全に個人の感覚次第ですが、録画して見終わったら消すようなものであれば、画質・速度・負荷・圧縮率を考えると VAAPI / qp 23 でも十分ありかなと個人的には思います。

# おわりに
ハードウェアエンコードは保存用途には向きませんが、見たら消す予定の ts ファイルを小さくしておき HDD を節約する用途としては有用ではないかと思います。
今回は 3.5GHz 4C/8T の Xeon を使用したため x264 と比べてそこまで大きな時間差はありませんでしたが、動作クロックの低い Pentium や Celeron の場合はさらに大きな差がつくでしょう。

現状 `ffmpeg` から VAAPI を利用する場合、QSV のデインターレース・デノイズ・シャープネス等の機能は使えないようですが、`gstreamer` の VAAPI プラグインではこれらも利用できるようなので VAAPI を極めたい方は `gstreamer` も試してみると良いと思います。

また FFmpeg 3.1 では Raspberry Pi に載っている H.264 エンコーダにも対応したようなので、これも試してみたいところです。

今さらですが、動画についても FFmpeg についてもそれほど詳しいわけではないので間違いがあったらすみません…。
とはいえ少しでも参考になればうれしいです。

期待作が目白押しの秋アニメにむけて、万全の環境で臨みましょう！

# 脚注