こんにちは、SaaS 事業部の髙橋と申します。
最近、動画のエンコード処理を高速化するためにハードウェアエンコードについて調査する機会がありましたので、FFmpeg と NVIDIA Video Codec SDK の組み合わせでビルドしてハードウェアエンコードを試してみることにしました。
ハードウェアエンコードとは
ざっくりと説明しますとハードウェアエンコードとは CPU によってエンコードするのではなく、画像処理に特化した GPU によってエンコードすることを指します。多くの GPU には動画の専用処理が実装されており、それらを呼び出すことで動画を高速に処理できます。
GPU に実装された動画の専用処理は主に以下のようなものがあります。
NVENC / NVDEC
GeForce シリーズや Quadro シリーズ、Tesla シリーズなど NVIDIA 製の GPU で使用できるエンコーダ(NVENC)およびデコーダ(NVDEC)です。
どの GPU が NVENC / NVDEC に対応しているかは以下で確認できます。
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 公式サイトからドライバーを探してダウンロードを行います。
今回は以下の条件で検索します。
製品シリーズ: 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 を探します。私の環境では最新版をインストールするとエンコードのときにエラーが発生しました。
今回はドライバーのバージョンが 510.85.02 でしたので、CUDA Toolkit 11.6.2 をダウンロードします。過去の CUDA Toolkit は以下からダウンロードできます。
ダウンロードページの選択肢は以下を選択します。
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 を使用したハードウェアエンコードを実行するまでの方法と、ハードウェアエンコードとソフトウェアエンコードの実行結果の比較についてを記載しました。
結果としましてはハードウェアエンコードの方がソフトウェアエンコードよりも画質の劣化はあるが、非常に高速でエンコードができるという結果となりました。