PLAY DEVELOPERS BLOG

HuluやTVerなどの日本最大級の動画配信を支える株式会社PLAYが運営するテックブログです。

ハードウェアエンコード (NVENC) を試してみた

こんにちは、SaaS 事業部の髙橋と申します。

最近、動画のエンコード処理を高速化するためにハードウェアエンコードについて調査する機会がありましたので、FFmpeg と NVIDIA Video Codec SDK の組み合わせでビルドしてハードウェアエンコードを試してみることにしました。

ハードウェアエンコードとは

ざっくりと説明しますとハードウェアエンコードとは CPU によってエンコードするのではなく、画像処理に特化した GPU によってエンコードすることを指します。多くの GPU には動画の専用処理が実装されており、それらを呼び出すことで動画を高速に処理できます。

GPU に実装された動画の専用処理は主に以下のようなものがあります。

NVENC / NVDEC

GeForce シリーズや Quadro シリーズ、Tesla シリーズなど NVIDIA 製の GPU で使用できるエンコーダ(NVENC)およびデコーダ(NVDEC)です。

どの GPU が NVENC / NVDEC に対応しているかは以下で確認できます。

developer.nvidia.com

Intel Quick Sync Video

Intel HD Graphics シリーズなど Intel 製の GPU で使用できます。

プロセッサー内部に GPU を内蔵している必要があります。特に Intel Core i シリーズで型番の末尾に F が付いているプロセッサー(例えば i5-9400F など)では GPU を内蔵していないので使用できません。

Video Coding Engine / Video Core Next

Radeon シリーズなど AMD 製の GPU で使用できます。

VideoToolbox

macOS で使用できます。GPU については Intel 製でも Apple 製でも動作します。

環境構築

今回、ハードウェアには NVIDIA 製のグラフィックボードを搭載した AWS の EC2 インスタンス(g4dn.xlarge)を使用し、エンコードを行うためのソフトウェアには FFmpeg を使用します。FFmpeg でハードウェアエンコードを行うためには NVIDIA Video Codec SDK をリンクしてビルドする必要があります。ここではインスタンス起動後からの環境構築を解説します。

後ほど CUDA のインストールを行いますが、かなりのディスク容量を使用しますので、 EC2 インスタンスのディスク容量(EBS)は 32GB 以上に設定することを推奨します。

NVIDIA のドライバーをインストール

インストーラーのダウンロード

NVIDIA 公式サイトからドライバーを探してダウンロードを行います。

www.nvidia.co.jp

今回は以下の条件で検索します。

製品シリーズ:  T-Series
製品ファミリー: Tesla T4
オペレーティングシステム: Linux 64-bit

「ダウンロードの同意」ボタンを右クリックしリンク先のアドレスをコピーします。

EC2 インスタンスにログインし、以下のコマンドを実行してダウンロードします。

$ wget 'コピーしたリンク先のアドレス'

必要なパッケージのインストール

以下のコマンドを実行してパッケージをインストールします。

$ sudo yum install gcc make kernel-devel-$(uname -r)

インストーラーの実行

今回は NVIDIA-Linux-x86_64-510.85.02.run というファイル名でダウンロードされました。ファイル名はダウンロードされたものに置き換えて以下のコマンドを実行してインストールします。

$ chmod u+x ./NVIDIA-Linux-x86_64-510.85.02.run
$ sudo CC=/usr/bin/gcc10-cc ./NVIDIA-Linux-x86_64-510.85.02.run

Install NVIDIA's 32-bit compatibility libraries? は不要なため No を選択します。

しばらく待つとインストールが完了します。インストールに成功していれば、 nvidia-smi を実行することで以下のように GPU の情報が表示されます。

$ nvidia-smi
Fri Nov 11 12:06:10 2022
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 510.85.02    Driver Version: 510.85.02    CUDA Version: 11.6     |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|                               |                      |               MIG M. |
|===============================+======================+======================|
|   0  Tesla T4            Off  | 00000000:00:1E.0 Off |                    0 |
| N/A   54C    P0    29W /  70W |      0MiB / 15360MiB |     10%      Default |
|                               |                      |                  N/A |
+-------------------------------+----------------------+----------------------+

+-----------------------------------------------------------------------------+
| Processes:                                                                  |
|  GPU   GI   CI        PID   Type   Process name                  GPU Memory |
|        ID   ID                                                   Usage      |
|=============================================================================|
|  No running processes found                                                 |
+-----------------------------------------------------------------------------+

CUDA Toolkit のインストール

NVIDIA Video Codec SDK を使用するためには CUDA Toolkit が必要なため、インストールします。

インストーラーのダウンロード

以下を参考にインストールしたドライバーのバージョンに対応した CUDA Toolkit を探します。私の環境では最新版をインストールするとエンコードのときにエラーが発生しました。

docs.nvidia.com

今回はドライバーのバージョンが 510.85.02 でしたので、CUDA Toolkit 11.6.2 をダウンロードします。過去の CUDA Toolkit は以下からダウンロードできます。

developer.nvidia.com

ダウンロードページの選択肢は以下を選択します。

Operating System: Linux
Architecture: x86_64
Distribution: RHEL
Version: 7
Installer Type: runfile (local)

表示されたコマンドを実行してダウンロードします。

$ wget https://developer.download.nvidia.com/compute/cuda/11.6.2/local_installers/cuda_11.6.2_510.47.03_linux.run

インストーラーの実行

以下のコマンドを実行してインストールします。

$ sudo ./cuda_11.6.2_510.47.03_linux.run --silent --override --toolkit --no-opengl-libs
$ export PATH="/usr/local/cuda/bin:${PATH}"
$ sudo ldconfig

しばらく待つとインストールが完了します。インストールに成功していれば、 nvcc -V を実行することで以下のように CUDA のバージョンなどが表示されます。

$ nvcc -V
nvcc: NVIDIA (R) Cuda compiler driver
Copyright (c) 2005-2022 NVIDIA Corporation
Built on Tue_Mar__8_18:18:20_PST_2022
Cuda compilation tools, release 11.6, V11.6.124
Build cuda_11.6.r11.6/compiler.31057947_0

FFmpeg のビルド

必要なパッケージのインストール

以下のコマンドを実行してパッケージをインストールします。

$ sudo yum install autoconf automake bzip2 bzip2-devel cmake freetype-devel gcc gcc-c++ git libtool make pkgconfig zlib-devel nasm

ディレクトリの作成

FFmpeg に必要なライブラリや実行ファイルを置くためのディレクトリを作成します。ディレクトリを作成することで管理者権限(sudo)でコマンドを実行せずにビルドできます。

$ cd ~
$ mkdir bin ffmpeg_build ffmpeg_sources

FFmpeg に必要なソフトウェアをビルド

ハードウェアエンコード(NVENC)を行うために必要な nv-codec-headers(これが NVIDIA Video Codec SDK となります)を以下のコマンドを実行してビルドします。

$ cd ~/ffmpeg_sources/
$ wget https://github.com/FFmpeg/nv-codec-headers/archive/refs/tags/n11.1.5.2.tar.gz
$ tar xf n11.1.5.2.tar.gz
$ cd nv-codec-headers-n11.1.5.2/
$ make PREFIX=$HOME/ffmpeg_build
$ make install PREFIX=$HOME/ffmpeg_build

ハードウェアエンコードとの比較のために今回はソフトウェアエンコーダの x264 もビルドします。以下のコマンドを実行してビルドします。

$ cd ~/ffmpeg_sources
$ git clone --branch stable --depth 1 https://code.videolan.org/videolan/x264.git
$ cd x264
$ PKG_CONFIG_PATH="$HOME/ffmpeg_build/lib/pkgconfig" ./configure --prefix="$HOME/ffmpeg_build" --bindir="$HOME/bin" --enable-static
$ make
$ make install

FFmpeg をビルド

今回は FFmpeg 5.1.2 をビルドします。以下のコマンドを実行してビルドします。

$ cd ~/ffmpeg_sources
$ wget 'https://ffmpeg.org/releases/ffmpeg-5.1.2.tar.gz'
$ cd ffmpeg-5.1.2
$ CFLAGS="-O3 -I/usr/local/cuda/include" LDFLAGS="-L/usr/local/cuda/lib64" PKG_CONFIG_PATH="$HOME/ffmpeg_build/lib/pkgconfig" ./configure --prefix="$HOME/ffmpeg_build" --pkg-config-flags="--static" --bindir="$HOME/bin" --enable-nonfree --enable-gpl --enable-libx264 --enable-cuda-nvcc --enable-libnpp --disable-debug --disable-doc --enable-static
$ make -j 8
$ make install

インストールに成功していれば、 ffmpeg を実行することで以下のように FFmpeg の情報が表示されます。

$ ffmpeg
ffmpeg version 5.1.2 Copyright (c) 2000-2022 the FFmpeg developers
  built with gcc 7 (GCC)
  configuration: --prefix=/home/ec2-user/ffmpeg_build --pkg-config-flags=--static --bindir=/home/ec2-user/bin --enable-nonfree --enable-gpl --enable-libx264 --enable-cuda-nvcc --enable-libnpp --disable-debug --disable-doc --enable-static
  libavutil      57. 28.100 / 57. 28.100
  libavcodec     59. 37.100 / 59. 37.100
  libavformat    59. 27.100 / 59. 27.100
  libavdevice    59.  7.100 / 59.  7.100
  libavfilter     8. 44.100 /  8. 44.100
  libswscale      6.  7.100 /  6.  7.100
  libswresample   4.  7.100 /  4.  7.100
  libpostproc    56.  6.100 / 56.  6.100
Hyper fast Audio and Video encoder
usage: ffmpeg [options] [[infile options] -i infile]... {[outfile options] outfile}...

Use -h to get full help or, even better, run 'man ffmpeg'

実行

今回はハードウェアエンコードでは NVENC、ソフトウェアエンコードでは x264 を使用してビデオコーデックが H.264 の動画をエンコードします。 エンコード元の動画には Big Buck Bunny の動画を使用させていただきました。ファイル名が bbb_sunflower_1080p_30fps_normal.mp4 のもので解像度がフル HD、フレームレートが 30fps、尺が 10 分 34 秒の動画となっています。

なお NVENC のエンコード時には処理を最適化するため NVDEC によるハードウェアデコードも行います。

実際のコマンド

FFmpeg を実行してエンコード行います。音声はどちらも同じ条件なので -an で出力しないよう設定しています。ビットレートは元の動画が 3Mbps ほどであったため、2Mbps に設定しています。time コマンドは実行時間を測るため使用しています。なお今回は画質に関する設定は何も指定していないため、設定次第では速度や画質の結果が変わる可能性があることを付け加えておきます。

# NVENC でエンコード
$ time ffmpeg -an -i bbb_sunflower_1080p_30fps_normal.mp4 -c:v h264_nvenc -b:v 2M -y output_nvenc_only.mp4

# NVDEC でデコードして NVENC でエンコード
$ time ffmpeg -hwaccel cuda -hwaccel_output_format cuda -extra_hw_frames 7 -an -i bbb_sunflower_1080p_30fps_normal.mp4 -c:v h264_nvenc -b:v 2M -y output_nvenc.mp4

# x264 でエンコード
$ time ffmpeg -an -i bbb_sunflower_1080p_30fps_normal.mp4 -c:v libx264 -b:v 2M -y output_x264.mp4

実行結果

NVENC と x264 でそれぞれエンコードした結果を速度と画質の2つの観点で比較します。

速度

EC2 インスタンス g4dn.xlarge のスペックは以下の通りです。

  • vCPU 数:4
  • CPU コア数:2
  • CPU コアごとのスレッド数:2

コマンドの実行時に time コマンドを使用していたため、エンコード完了時に実行時間が表示されます。real は実際にかかった時間、user はプログラムが CPU を使用した時間(マルチコアですと実際にかかった時間よりも長い結果になることもあります)、sys はシステムが CPU を使用した時間を意味します。

NVENC
real    0m59.948s
user    2m58.691s
sys     0m4.073s

NVDEC + NVENC
real    0m51.757s
user    0m3.193s
sys     0m1.323s

x264
real    11m18.164s
user    44m2.401s
sys     0m10.296s

今回は NVENC の方が x264 よりも 11 倍ほどエンコードが速いという結果になりました。NVDEC も使用すると 13 倍まで処理が高速になり、また user についても少ない時間になっていて、いかに CPU を使用していないかが分かります。

画質

ここでは NVENC と x264 でそれぞれエンコードした結果の画質を比較します。実際の画像を目視しての評価と SSIM という画質指標による機械的な評価を行います。

実際の画像

以下のように FFmpeg を使用することで特定の再生時間を PNG 形式で保存できます。

$ ffmpeg -ss 28 -i output_nvenc.mp4 -frames:v 1 screenshot_28sec_output_nvenc.png

0:28 地点

動きが少ない場面。画質にはあまり差がないです。

4:37 地点

うさぎが縄跳びをして一部の動きが多い場面。画質にはあまり差がないです。

7:17 地点

ムササビが飛行することで画面がスクロールし、地面からは棘が生えていて全体的に動きが多い場面。x264 の方が細部が少しくっきりとしていて画質が良く感じられます。

SSIM

画質評価指標の 1 つに SSIM というものがあります。この SSIM を使用することでエンコード後の動画がエンコード前からどれほど劣化したかを数値で示すことができます。All の結果が 1 に近づくほどエンコード前からの劣化が少ないことを意味します。

以下のように FFmpeg を使用することで SSIM を計算できます。

$ ffmpeg -i output_nvenc.mp4 -i bbb_sunflower_1080p_30fps_normal.mp4 -filter_complex ssim -f null -
[Parsed_ssim_0 @ 0x31bd300] SSIM Y:0.979095 (16.797416) U:0.980021 (16.994276) V:0.983456 (17.813630) All:0.979976 (16.984478)

$ ffmpeg -i output_x264.mp4 -i bbb_sunflower_1080p_30fps_normal.mp4 -filter_complex ssim -f null -
[Parsed_ssim_0 @ 0x3077bc0] SSIM Y:0.984780 (18.175857) U:0.988053 (19.227455) V:0.989667 (19.857916) All:0.986140 (18.582399)

今回は x264 の方がエンコード前からの劣化が少ないという結果になりました。

終わりに

今回は NVIDIA の GPU を使用したハードウェアエンコードを実行するまでの方法と、ハードウェアエンコードとソフトウェアエンコードの実行結果の比較についてを記載しました。

結果としましてはハードウェアエンコードの方がソフトウェアエンコードよりも画質の劣化はあるが、非常に高速でエンコードができるという結果となりました。

参考