NVENCを使ったHWEncode環境  (Ubuntu版)

これは さくらインターネットAdvent Calendar 2018 15日目の記事です。(公開遅くなりました)

こんにちは、さくらインターネットのやまけんです

さくらのアドベントカレンダーなのにあまりにも関係性が低いような内容ですが、さくらの高火力GPUサーバ環境でも試せる内容なので、今回は記事にさせていただきました。

GPUサーバというと、機械学習などのイメージが大きいですが、本来のグラフィック演算ユニットとしての使い方としてハードウェアエンコードのプラットフォームとして、使ってみようというのが今回のテーマです。

ちょうどH.265も再生環境が整ってきたかと思いますので、MPEG2やH.264からのエンコードを高速に並列処理できる環境として、GPUサーバを利用します。

このテーマに向いている人

・多量のMPEG2やH.264動画を所有していて、H.265に変換したい人
　（画質にそこまでこだわりはなく、ストレージの容量逼迫してるからさっさと片付けたい！など）

・何かしらのMPEG2-TSを録画する環境があり、録画した後にH.265へのトランスコードをしたいなと思ってる人
　（多分このような用途の人には一番おすすめな気がする）

・動画配信などでリアルタイムトランスコードが必要な人
　（画質より速度が重要視される分野）

**2022年12月26日注記**
CUDA12以降nvccの -gencode=arch がcompute_50以降じゃないと動かないため、ffmpegコンパイル時に   --nvccflags="-gencode arch=compute_52,code=sm_52 -O2" というオプションで指定しています。
そもそも本来自分のGPUの世代と同様のものを選ぶのが正しいですが、nvenc自体のコンパイルの場合GPU最適化がそこまで必要ないので汎用的な設定になっていたと思われる。

ちなみにNVIDIA T400の場合は
--nvccflags="-gencode arch=compute_75,code=sm_75 -O2"
になります。

正しいGPUを選ぶ際の参考(Your GPU Compute Capabilityを参照)
https://developer.nvidia.com/cuda-gpus

```
nvcc warning : The 'compute_35', 'compute_37', 'compute_50', 'sm_35', 'sm_37' and 'sm_50' architectures are deprecated, and may be removed in a future release
```

```
test@test:~$ nvcc -V
nvcc: NVIDIA (R) Cuda compiler driver
Copyright (c) 2005-2025 NVIDIA Corporation
Built on Wed_Jan_15_19:20:09_PST_2025
Cuda compilation tools, release 12.8, V12.8.61
Build cuda_12.8.r12.8/compiler.35404655_0

test@test:~$ nvcc --list-gpu-arch 
compute_50
compute_52
compute_53
compute_60
compute_61
compute_62
compute_70
compute_72
compute_75
compute_80
compute_86
compute_87
compute_89
compute_90
compute_100
compute_101
compute_120
```

# 必須環境

・NVIDIAのGPUが載っている Ubuntu 18.04 / 20.04 / 22.04 /24.04 x86_64 マシン
→自宅サーバに載せるのであれば、2025/01/10現在だと NVIDIA A400でNVENCが良さげ
1スロットLowProfile対応のグラボで消費電力50Wと1Uサーバに余裕で搭載できるサイズ。
ドスパラで32,448円で買えます。
https://www.dospara.co.jp/SBR1808/IC507416.html
Amazonはこちら
https://www.amazon.co.jp/dp/B0D3PLCK85/

他にもコスパ良いGPUありますが、エンコードだけだったら、メモリも少なくても問題ないですし、CUDAコアもそんな必要ないです。

あと、A400やGeforce系コンシューマーGPUは同時にエンコードできる数が8つに制限されます。
他の旧Quadro系やデータセンター向けTeslaだと並行して大量にエンコードできます。（とはいえ1チップに複数ストリーム入れると処理時間は増えていくのである程度調整しましょう）

私の好みでUbuntuにしていますが、CentでもCUDA入れてffmpegコンパイルできれば同じようになるかと。

ちなみに古いUbuntuのバージョンだと最新のドライバが入らないので、NVIDIA Video Codec SDKのバージョンを落とせば対応可能だと思いますが、検証はしていません。

GPU世代によってH.265のエンコードができないやav1のエンコードができない等があります。詳しくは
Video Encode and Decode GPU Support Matrixにて
https://developer.nvidia.com/video-encode-and-decode-gpu-support-matrix-new

# 準備
## CUDAのインストール
NVIDIA CUDA Installation Guide for Linux に従って入れます
https://docs.nvidia.com/cuda/cuda-installation-guide-linux/index.html

今回はパッケージで入れたいと思いますので、
2. Pre-installation Actions
3. Package Manager Installation
7. Post-installation Actions
こちらを進めれば良いです。

例(Ubuntu24.04でCUDA12.6の場合)

```
# Kernel Headerを入れる
sudo apt-get install linux-headers-$(uname -r)

# 古いapt-keyを削除する
sudo apt-key del 7fa2af80

# cuda-keyring パッケージをDL
wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu$(lsb_release -rs | tr -d '.')/$(arch)/cuda-keyring_1.1-1_all.deb

# cuda-keyring パッケージをインストール
sudo dpkg -i cuda-keyring_1.1-1_all.deb

# リポジトリ更新
sudo apt-get update

# GPUドライバインストール
sudo apt-get -y install nvidia-open
※Pascal以前のGPUの場合は　sudo apt-get install -y cuda-drivers

# CUDAインストール
sudo apt-get -y install cuda-toolkit

# パスを通す
echo 'export PATH=/usr/local/cuda/bin${PATH:+:${PATH}}' >> ~/.bashrc
echo 'export LD_LIBRARY_PATH=/usr/local/cuda/lib64${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}' >> ~/.bashrc

source ~/.bashrc
```

サーバなどで、GUIを使っておらず、CLI利用の場合、ドライバ類のインストールと共にX関連も入れるので、必要に応じてCLI起動するように設定しましょう。（必須ではありません。Tesla使ってたらそもそも不要）

```
# 現在の設定を確認
test@test:~$ systemctl get-default
graphical.target
```
graphical.targetと帰ってくると、GUI起動になってるので、CLI(sysvinitでいうrunlevel3)に設定変更

```
# multi-user.target へ変更
test@test:~$ sudo systemctl set-default multi-user.target
Created symlink /etc/systemd/system/default.target → /lib/systemd/system/multi-user.target.
```
これで再起動後よりCLIになります。

インストールが完了したら再起動し、 nvidia-smi にてドライバインストールも含め完了しているか確認しましょう。



```
test@test:~$ nvidia-smi
Tue Feb 11 04:19:08 2025       
+-----------------------------------------------------------------------------------------+
| NVIDIA-SMI 570.86.15              Driver Version: 570.86.15      CUDA Version: 12.8     |
|-----------------------------------------+------------------------+----------------------+
| 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  NVIDIA T400                    Off |   00000000:02:00.0 Off |                  N/A |
| 38%   45C    P8             N/A /   31W |       1MiB /   2048MiB |      0%      Default |
|                                         |                        |                  N/A |
+-----------------------------------------+------------------------+----------------------+
                                                                                         
+-----------------------------------------------------------------------------------------+
| Processes:                                                                              |
|  GPU   GI   CI              PID   Type   Process name                        GPU Memory |
|        ID   ID                                                               Usage      |
|=========================================================================================|
|  No running processes found                                                             |
+-----------------------------------------------------------------------------------------+
```

## FFmpegのインストール
Compile FFmpeg for Ubuntu, Debian, or Mint
https://trac.ffmpeg.org/wiki/CompilationGuide/Ubuntu
こちらをベースに全てソースコードで入れていきます。

個人的にhapエンコードを使いたいので libsnappy-dev をインストールの上
`  --enable-libsnappy` を追記しています。
またTLS対応のために libssl-dev をインストールの上
`  --enable-openssl` を追記しています。
Netflixが開発しているVMAFを使って動画評価したいため、libvmafをインストールの上
`  --enable-libvmaf` を追記しています。

また、FFmpeg4以降でnvencを使用する場合は nv-codec-headers が必要です。
これらはNVIDIA Video Codec SDKの一部を使用しています。
https://developer.nvidia.com/ffmpeg
https://github.com/FFmpeg/nv-codec-headers

ちなみに
https://developer.nvidia.com/blog/nvidia-ffmpeg-transcoding-guide/
において、コンパイル時は
`./configure --enable-cuda --enable-cuvid --enable-nvenc --enable-nonfree --enable-libnpp --extra-cflags=-I/usr/local/cuda/include  --extra-ldflags=-L/usr/local/cuda/lib64`
という記載がありますが、 `./configure --help | grep cuda` で調べればわかるとおり、現在は

```
  --enable-cuda-nvcc       enable Nvidia CUDA compiler [no]
```

が正しいため、`--enable-cuda` ではなく `--enable-cuda-nvcc` に変更しています。

なお、インストールされるドライババージョンによって、NVIDIA Video Codec SDKのバージョン依存が発生します。
https://github.com/FFmpeg/nv-codec-headers のREADMEにて対応バージョンを確認ください。

本ドキュメントは基本的に最新のドライバをインストールしてある前提にあります。

必ず `nvidia-smi` にてインストールされているドライババーションを確認してください。

古いグラボなどで最新ドライバが入らない場合、
https://github.com/FFmpeg/nv-codec-headers の ブランチを切り替えると、過去のSDKでの必要ドライババーションの確認が確認ができます。

ドライバが古い場合は、NVIDIA codec API のインストール時の
`git -C nv-codec-headers pull 2> /dev/null || git clone https://git.videolan.org/git/ffmpeg/nv-codec-headers -b sdk/9.0 && \`
のように -b sdk/9.0 でブランチを指定してインストールしてみてください。

```
# FFmpegコンパイルに必要なものを導入
sudo apt-get update -qq && sudo apt-get -y install \
  autoconf \
  automake \
  build-essential \
  cmake \
  git-core \
  libass-dev \
  libfreetype6-dev \
  libunistring-dev \
  libmp3lame-dev \
  libssl-dev \
  libtool \
  libvorbis-dev \
  libsnappy-dev \
  meson \
  ninja-build \
  pkg-config \
  texinfo \
  wget \
  yasm \
  nasm \
  zlib1g-dev

# 作業ディレクトリ作成
mkdir -p ~/ffmpeg_sources ~/bin

# libx264 インストール
cd ~/ffmpeg_sources && \
git -C x264 pull 2> /dev/null || git clone --depth 1 https://code.videolan.org/videolan/x264.git && \
cd x264 && \
PATH="$HOME/bin:$PATH" PKG_CONFIG_PATH="$HOME/ffmpeg_build/lib/pkgconfig" ./configure --prefix="$HOME/ffmpeg_build" --bindir="$HOME/bin" --enable-static --enable-pic && \
PATH="$HOME/bin:$PATH" make && \
make install

# libx265 インストール
sudo apt-get install libnuma-dev && \
cd ~/ffmpeg_sources && \
wget -O x265.tar.bz2 https://bitbucket.org/multicoreware/x265_git/get/master.tar.bz2 && \
mkdir -p x265 && tar xjvf x265.tar.bz2 -C x265 --strip-components 1 && \
cd x265/build/linux && \
PATH="$HOME/bin:$PATH" cmake -G "Unix Makefiles" -DCMAKE_INSTALL_PREFIX="$HOME/ffmpeg_build" -DENABLE_SHARED=off ../../source && \
PATH="$HOME/bin:$PATH" make && \
make install

# libvpx インストール
cd ~/ffmpeg_sources && \
git -C libvpx pull 2> /dev/null || git clone --depth 1 https://chromium.googlesource.com/webm/libvpx.git && \
cd libvpx && \
PATH="$HOME/bin:$PATH" ./configure --prefix="$HOME/ffmpeg_build" --disable-examples --disable-unit-tests --enable-vp9-highbitdepth --as=yasm && \
PATH="$HOME/bin:$PATH" make && \
make install

# libfdk-aac インストール
cd ~/ffmpeg_sources && \
git -C fdk-aac pull 2> /dev/null || git clone --depth 1 https://github.com/mstorsjo/fdk-aac && \
cd fdk-aac && \
autoreconf -fiv && \
./configure --prefix="$HOME/ffmpeg_build" --disable-shared && \
make && \
make install

# libopus インストール
cd ~/ffmpeg_sources && \
git -C opus pull 2> /dev/null || git clone --depth 1 https://github.com/xiph/opus.git && \
cd opus && \
./autogen.sh && \
./configure --prefix="$HOME/ffmpeg_build" --disable-shared && \
make && \
make install

# libaom インストール
cd ~/ffmpeg_sources && \
git -C aom pull 2> /dev/null || git clone --depth 1 https://aomedia.googlesource.com/aom && \
mkdir -p aom_build && \
cd aom_build && \
PATH="$HOME/bin:$PATH" cmake -G "Unix Makefiles" -DCMAKE_INSTALL_PREFIX="$HOME/ffmpeg_build" -DENABLE_TESTS=OFF -DENABLE_NASM=on ../aom && \
PATH="$HOME/bin:$PATH" make && \
make install

# libsvtav1 インストール
cd ~/ffmpeg_sources && \
git -C SVT-AV1 pull 2> /dev/null || git clone https://gitlab.com/AOMediaCodec/SVT-AV1.git && \
mkdir -p SVT-AV1/build && \
cd SVT-AV1/build && \
PATH="$HOME/bin:$PATH" cmake -G "Unix Makefiles" -DCMAKE_INSTALL_PREFIX="$HOME/ffmpeg_build" -DCMAKE_BUILD_TYPE=Release -DBUILD_DEC=OFF -DBUILD_SHARED_LIBS=OFF .. && \
PATH="$HOME/bin:$PATH" make && \
make install

# libdav1d インストール
cd ~/ffmpeg_sources && \
git -C dav1d pull 2> /dev/null || git clone --depth 1 https://code.videolan.org/videolan/dav1d.git && \
mkdir -p dav1d/build && \
cd dav1d/build && \
meson setup -Denable_tools=false -Denable_tests=false --default-library=static .. --prefix "$HOME/ffmpeg_build" --libdir="$HOME/ffmpeg_build/lib" && \
ninja && \
ninja install

# libvmaf インストール
cd ~/ffmpeg_sources && \
wget https://github.com/Netflix/vmaf/archive/v3.0.0.tar.gz && \
tar xvf v3.0.0.tar.gz && \
mkdir -p vmaf-3.0.0/libvmaf/build &&\
cd vmaf-3.0.0/libvmaf/build && \
meson setup -Denable_tests=false -Denable_docs=false --buildtype=release --default-library=static .. --prefix "$HOME/ffmpeg_build" --libdir="$HOME/ffmpeg_build/lib" && \
ninja && \
ninja install

# NVIDIA codec API インストール
cd ~/ffmpeg_sources && \
git -C nv-codec-headers pull 2> /dev/null || git clone https://git.videolan.org/git/ffmpeg/nv-codec-headers && \
cd nv-codec-headers && \
make && \
make install PREFIX="$HOME/ffmpeg_build"

# FFmpeg インストール
cd ~/ffmpeg_sources && \
git -C ffmpeg pull 2> /dev/null || git clone --depth 1 https://git.ffmpeg.org/ffmpeg.git && \
cd ffmpeg && \
PATH="$HOME/bin:$PATH" PKG_CONFIG_PATH="$HOME/ffmpeg_build/lib/pkgconfig" ./configure \
  --prefix="$HOME/ffmpeg_build" \
  --pkg-config-flags="--static" \
  --extra-cflags="-I$HOME/ffmpeg_build/include" \
  --extra-ldflags="-L$HOME/ffmpeg_build/lib" \
  --enable-cuda-nvcc \
  --nvccflags="-gencode arch=compute_75,code=sm_75 -O2" \
  --enable-cuvid \
  --enable-nvenc \
  --enable-libnpp \
  --extra-cflags="-I/usr/local/cuda/include" \
  --extra-ldflags="-L/usr/local/cuda/lib64" \
  --extra-libs="-lpthread -lm" \
  --ld="g++" \
  --bindir="$HOME/bin" \
  --enable-openssl \
  --enable-gpl \
  --enable-libaom \
  --enable-libass \
  --enable-libfdk-aac \
  --enable-libfreetype \
  --enable-libmp3lame \
  --enable-libopus \
  --enable-libsvtav1 \
  --enable-libdav1d \
  --enable-libvorbis \
  --enable-libsnappy \
  --enable-libvpx \
  --enable-libx264 \
  --enable-libx265 \
  --enable-libvmaf \
  --enable-nonfree && \
PATH="$HOME/bin:$PATH" make && \
make install && \
hash -r

パスを通したり
source ~/.profile
echo "MANPATH_MAP $HOME/bin $HOME/ffmpeg_build/share/man" >> ~/.manpath
```
## 動作確認
実際にインストールが成功したか、下記のコマンドでnvencエンコード関連オプションが使えるか確認します。

```
for i in buildconf hwaccels decoders filters encoders; do echo $i:; ffmpeg -hide_banner -${i} | egrep -i "npp|cuvid|nvenc|cuda|nvdec"; done
```

結果はこのようになると思います。

```
test@test:~$ for i in buildconf hwaccels decoders filters encoders; do echo $i:; ffmpeg -hide_banner -${i} | egrep -i "npp|cuvid|nvenc|cuda|nvdec"; done
buildconf:
    --enable-cuda-nvcc
    --enable-cuvid
    --enable-nvenc
    --enable-libnpp
    --extra-cflags=-I/usr/local/cuda-12.2/include
    --extra-ldflags=-L/usr/local/cuda-12.2/lib64
hwaccels:
cuda
decoders:
 V..... av1_cuvid            Nvidia CUVID AV1 decoder (codec av1)
 V..... h264_cuvid           Nvidia CUVID H264 decoder (codec h264)
 V..... hevc_cuvid           Nvidia CUVID HEVC decoder (codec hevc)
 V..... mjpeg_cuvid          Nvidia CUVID MJPEG decoder (codec mjpeg)
 V..... mpeg1_cuvid          Nvidia CUVID MPEG1VIDEO decoder (codec mpeg1video)
 V..... mpeg2_cuvid          Nvidia CUVID MPEG2VIDEO decoder (codec mpeg2video)
 V..... mpeg4_cuvid          Nvidia CUVID MPEG4 decoder (codec mpeg4)
 V..... vc1_cuvid            Nvidia CUVID VC1 decoder (codec vc1)
 V..... vp8_cuvid            Nvidia CUVID VP8 decoder (codec vp8)
 V..... vp9_cuvid            Nvidia CUVID VP9 decoder (codec vp9)
filters:
 ... bilateral_cuda    V->V       GPU accelerated bilateral filter
 T.. bwdif_cuda        V->V       Deinterlace CUDA frames
 ... chromakey_cuda    V->V       GPU accelerated chromakey filter
 ... colorspace_cuda   V->V       CUDA accelerated video color converter
 ... hwupload_cuda     V->V       Upload a system memory frame to a CUDA device.
 ... overlay_cuda      VV->V      Overlay one video on top of another using CUDA
 ... scale_cuda        V->V       GPU accelerated video resizer
 ... scale_npp         V->V       NVIDIA Performance Primitives video scaling and format conversion
 ... scale2ref_npp     VV->VV     NVIDIA Performance Primitives video scaling and format conversion to the given reference.
 ... sharpen_npp       V->V       NVIDIA Performance Primitives video sharpening filter.
 ... thumbnail_cuda    V->V       Select the most representative frame in a given sequence of consecutive frames.
 ... transpose_npp     V->V       NVIDIA Performance Primitives video transpose
 T.. yadif_cuda        V->V       Deinterlace CUDA frames
encoders:
 V....D av1_nvenc            NVIDIA NVENC av1 encoder (codec av1)
 V....D h264_nvenc           NVIDIA NVENC H.264 encoder (codec h264)
 V....D hevc_nvenc           NVIDIA NVENC hevc encoder (codec hevc)
test@test:~$

```

きちんとビルド時のオプションが有効なこと、デコーダ、フィルター、エンコーダにnvenc関連が入っているのがわかります。

表示が出ない場合、今回コンパイルしたffmpegではないパッケージでインストールしたffmpegなどの結果になっている可能性があります。 `which ffmpeg` にてffmpegのパスを確認し、今回コンパイルしたffmpegで間違い無いかを確認してください。

# 実際に使ってみる

では実際に使ってみましょう。

MPEG2からH.265へ720pでエンコード

```
ffmpeg -y -hwaccel cuda -hwaccel_output_format cuda -c:v mpeg2_cuvid -i "input.mp2ts" -vf "yadif_cuda,scale_cuda=-2:720:interp_algo=lanczos" -c:v hevc_nvenc -preset slow -tag:v hvc1 -f mp4 -aspect 16:9 out.mp4
```
解説です。

> -hwaccel cuda

HWエンコードでcudaを明示

> -hwaccel_output_format cuda

GPUメモリ上にデコードしたデータを展開することによってメインメモリとの間のやりとりを減らして処理を高速化させます。

> -c:v mpeg2_cuvid

MPEG2のハードウェアデコードを明示

> -i input.mp2ts

変換元ファイルを指定します。

> vf "yadif_cuda,scale_cuda=-2:720:interp_algo=lanczos"

まず yadif_cuda でHWにてインターレス解除。その後720Pになるよう縮小。
通常はscale=と使うかと思いますが、scale_cudaでnvidiaのHWスケーリングを使うので高速になります。

> -c:v hevc_nvenc

H.265でNVEncを使います

> -tag:v hvc1

Apple製品で扱えるH.265は hev1ではなくhvc1の方なので、タグで明示します

> -f mp4

出力ファイルフォーマットがをmp4であると明示します

> -aspect 16:9 

16:9を指定

> out.mp4

出力先ファイル名を指定


詳しいオプションは
Using FFmpeg with NVIDIA GPU Hardware Acceleration
https://developer.nvidia.com/designworks/dl/Using_FFmpeg_with_NVIDIA_GPU_Hardware_Acceleration-pdf
にて。

## 注意
Geforce などのコンシュマーGPUでは、同時にエンコードできるストリームは3本に制限されてますので、同時に複数のHWエンコードが必要な場合（ストリーミング環境など）はQuadroやTeslaが必要です。