CUDA
ffmpeg
GPU
NVIDIA
NVENC

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

こんにちは、やまけんです

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

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

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

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

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

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

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

必須環境

・NVIDIAのGPUが載っているUbuntu 18.04 マシン
→自宅サーバに載せるのであれば拡張電源の不要なGeForce GTX 1050あたりのカードが良さげ。
しかし、同時にエンコードできる数が2つまでに制限されるためQuadroやTeslaだと並行して大量にエンコードできます。

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

準備

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
こちらを進めれば良いです。

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

# ネットワークインストーラーをDL
wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu1804/x86_64/cuda-repo-ubuntu1804_10.0.130-1_amd64.deb

# ネットワークインストーラをインストール
sudo dpkg -i cuda-repo-ubuntu1804_10.0.130-1_amd64.deb

# GPGキーをインポート
sudo apt-key adv --fetch-keys http://developer.download.nvidia.com/compute/cuda/repos/ubuntu1804/x86_64/7fa2af80.pub

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

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

# パスを通す
export PATH=/usr/local/cuda-10.0/bin${PATH:+:${PATH}}
export LD_LIBRARY_PATH=/usr/local/cuda-10.0/lib64\
                         ${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}

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

test@test:~$ nvidia-smi
Thu Dec 20 16:19:59 2018       
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 410.79       Driver Version: 410.79       CUDA Version: 10.0     |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|===============================+======================+======================|
|   0  GeForce GTX 105...  Off  | 00000000:01:00.0 Off |                  N/A |
| 40%   24C    P0    N/A /  75W |      0MiB /  4040MiB |      0%      Default |
+-------------------------------+----------------------+----------------------+

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

FFmpegのインストール

Compile FFmpeg for Ubuntu, Debian, or Mint
https://trac.ffmpeg.org/wiki/CompilationGuide/Ubuntu
こちらをベースに全てソースコードで入れていきます。
※libaom(AV1 video encoder/decoder)についてはlibaomenc.cのコンパイルが不安定なため除外しています。必要な人は上記Compile FFmpeg for Ubuntu, Debian, or Mintに従ってインストールしてみてください。

また、FFmpeg4以降でnvencを使用する場合はnv-codec-headers が必要です。
https://github.com/FFmpeg/nv-codec-headers

#FFmpegコンパイルに必要なものを導入
sudo apt-get update -qq && sudo apt-get -y install \
  autoconf \
  automake \
  build-essential \
  cmake \
  git-core \
  libass-dev \
  libfreetype6-dev \
  libsdl2-dev \
  libtool \
  libva-dev \
  libvdpau-dev \
  libvorbis-dev \
  libxcb1-dev \
  libxcb-shm0-dev \
  libxcb-xfixes0-dev \
  pkg-config \
  texinfo \
  wget \
  zlib1g-dev

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

#NASM インストール
cd ~/ffmpeg_sources && \
wget https://www.nasm.us/pub/nasm/releasebuilds/2.13.03/nasm-2.13.03.tar.bz2 && \
tar xjvf nasm-2.13.03.tar.bz2 && \
cd nasm-2.13.03 && \
./autogen.sh && \
PATH="$HOME/bin:$PATH" ./configure --prefix="$HOME/ffmpeg_build" --bindir="$HOME/bin" && \
make && \
make install

#Yasm インストール
cd ~/ffmpeg_sources && \
wget -O yasm-1.3.0.tar.gz https://www.tortall.net/projects/yasm/releases/yasm-1.3.0.tar.gz && \
tar xzvf yasm-1.3.0.tar.gz && \
cd yasm-1.3.0 && \
./configure --prefix="$HOME/ffmpeg_build" --bindir="$HOME/bin" && \
make && \
make install

#libx264 インストール
cd ~/ffmpeg_sources && \
git -C x264 pull 2> /dev/null || git clone --depth 1 https://git.videolan.org/git/x264 && \
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 mercurial libnuma-dev && \
cd ~/ffmpeg_sources && \
if cd x265 2> /dev/null; then hg pull && hg update; else hg clone https://bitbucket.org/multicoreware/x265; fi && \
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

#libmp3lame インストール
cd ~/ffmpeg_sources && \
wget -O lame-3.100.tar.gz https://downloads.sourceforge.net/project/lame/lame/3.100/lame-3.100.tar.gz && \
tar xzvf lame-3.100.tar.gz && \
cd lame-3.100 && \
PATH="$HOME/bin:$PATH" ./configure --prefix="$HOME/ffmpeg_build" --bindir="$HOME/bin" --disable-shared --enable-nasm && \
PATH="$HOME/bin:$PATH" 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

#Nvidias 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 && \
PATH="$HOME/bin:$PATH" make && \
make install PREFIX="$HOME/ffmpeg_build"

#FFmpeg インストール
cd ~/ffmpeg_sources && \
wget -O ffmpeg-snapshot.tar.bz2 https://ffmpeg.org/releases/ffmpeg-snapshot.tar.bz2 && \
tar xjvf ffmpeg-snapshot.tar.bz2 && \
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" \
  --extra-libs="-lpthread -lm" \
  --bindir="$HOME/bin" \
  --enable-gpl \
  --enable-libass \
  --enable-libfdk-aac \
  --enable-libfreetype \
  --enable-libmp3lame \
  --enable-libopus \
  --enable-libvorbis \
  --enable-libvpx \
  --enable-libx264 \
  --enable-libx265 \
  --enable-static \
  --enable-cuda \
  --enable-cuvid \
  --enable-nvenc \
  --enable-libnpp \
  --extra-cflags=-I/usr/local/cuda/include \
  --extra-ldflags=-L/usr/local/cuda/lib64 \
  --enable-nonfree && \
PATH="$HOME/bin:$PATH" make && \
make install && \
hash -r

パスを通したり
source ~/.profile
echo "MANPATH_MAP $HOME/bin $HOME/ffmpeg_build/share/man" >> ~/.manpath

実際に使ってみる

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

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

ffmpeg -hwaccel cuvid -c:v mpeg2_cuvid -deint adaptive -drop_second_field 1 -i input.mp2ts -c:v hevc_nvenc -vf scale_npp=-1:720 out.mp4

解説です。

-hwaccel cuvid

HWエンコードでcuvidを明示

-c:v mpeg2_cuvid

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

-deint adaptive -drop_second_field 1

インターレス解除します。-deintだけだとフレーム数が2倍に増えるだけなので-drop_second_field 1 で増えたフレームを間引いています。

-i input.mp2ts

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

-c:v hevc_nvenc

H.265でNVEncを使います

-vf scale_npp=-1:720

720Pになるよう縮小

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では、同時にエンコードできるストリームは2本に制限されてますので、同時に複数のHWエンコードが必要(ストリーミング環境など)はQuadroやTeslaが必要です。