Dockerを使ってNVENCを有効にしたFFmpegをWSL2にインストールする

# 目的
　環境を汚さず最小構成で、Nvidia GPUを使用したハードウェアエンコードが可能なFFmpegを手に入れます。

# 環境
- OS: Ubuntu 22.04 on WSL2
- GPU: Geforce RTX 4090
- GPUドライバ: NVIDIA Studioドライバー 566.14

# 結論
　次の内容の`Dockerfile`を作成して
```Dockerfile
FROM nvidia/cuda:12.6.2-devel-ubuntu24.04
# Select and pull from "https://catalog.ngc.nvidia.com/orgs/nvidia/containers/cuda".
# `base` does NOT include `nvcc` that is required to compile ffmpeg with nvcodec.

# The first one is necessary not to stop at installing git with time zone selection.
ENV DEBIAN_FRONTEND=noninteractive
ENV NVIDIA_VISIBLE_DEVICES all
ENV NVIDIA_DRIVER_CAPABILITIES video,compute,utility

# NVIDIA have not set these fundamental environment variables which most programs refer to
# while they selves are requiring in "https://docs.nvidia.com/cuda/cuda-installation-guide-linux/index.html#mandatory-actions".
# An error about missing (not found) `nvcc` can raise without these setup.
ENV PATH="/usr/local/cuda-12.6/bin${PATH:+:${PATH}}"
ENV PKG_CONFIG_PATH="/usr/local/lib/pkgconfig${PKG_CONFIG_PATH:+:${PKG_CONFIG_PATH}}"
ENV LD_LIBRARY_PATH="/usr/local/cuda-12.6/lib64${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}"


# See "https://docs.nvidia.com/video-technologies/video-codec-sdk/12.0/ffmpeg-with-nvidia-gpu/index.html#compiling-for-linux".
# git, nasm and pkg-config are also required to install ffmpeg, however they are not included in above instruction.
# So they are added in following command.
RUN apt-get update \
    && apt-get install -y build-essential yasm cmake libtool libc6 libc6-dev unzip wget libnuma1 libnuma-dev git nasm pkg-config

# Check what version of Video Codec SDK is available with README in "https://github.com/FFmpeg/nv-codec-headers".
# Match the SDK and cuda.
# Ref) https://docs.nvidia.com/video-technologies/video-codec-sdk/12.2/read-me/index.html#system-requirements
RUN mkdir ffmpeg-nvcodec && cd ffmpeg-nvcodec \
    && git clone https://git.videolan.org/git/ffmpeg/nv-codec-headers.git \
    && cd nv-codec-headers && make && make install

RUN git clone https://git.ffmpeg.org/ffmpeg.git ffmpeg/ && cd ffmpeg \
    && wget https://ffmpeg.org/releases/ffmpeg-7.1.tar.xz \
    && tar -xf ffmpeg-7.1.tar.xz \
    && rm ffmpeg-7.1.tar.xz \
    && cd ffmpeg-7.1 \
    && ./configure \
        --enable-nonfree --extra-cflags=-I/usr/local/cuda/include \
        --extra-ldflags=-L/usr/local/cuda/lib64 \
        --disable-static \
        --enable-shared \
        --enable-cuda-nvcc \
        --enable-libnpp \
    && make -j $(nproc) \
    && make install

RUN echo 'export PATH=$PATH' >> ~/.bashrc \
    && echo 'export PKG_CONFIG_PATH=$PKG_CONFIG_PATH' >> ~/.bashrc \
    && echo 'export LD_LIBRARY_PATH=$LD_LIBRARY_PATH' >> ~/.bashrc \
    && mkdir workspace
```
ビルドする
```
sudo docker pull nvidia/cuda:12.6.2-devel-ubuntu24.04
sudo docker build -t ffmpeg:12.6.2-devel-ubuntu24.04 .
sudo docker run --gpus all -it --rm -v $(pwd):/workspace ffmpeg:12.6.2-devel-ubuntu24.04
```

# 手順
## 事前準備
　WSL2にインストールした`Docker`でGPUを使用可能になっていることが必要です。
そこまでの方法は[以前](https://qiita.com/Yukiho_P/items/af9f57aff5d8bb206b27#windows%E3%81%ABubuntu%E3%82%92%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB-wsl2)に紹介しています。
NVIDIA Container Toolkitのインストールまで完了して下さい。

## 使用可能なCUDAのバージョン確認
　ffnvcodecが対応しているCUDAを調べます。これは2段階で行います。
まず[配布ページ](https://github.com/FFmpeg/nv-codec-headers)のREADMEでffnvcodecが対応しているVideo Codec SDKのバージョンを調べます。
　続いてそのSDKのページを探して対応しているCUDAのバージョンを確認します。
例えばSDK v12.2であればhttps://docs.nvidia.com/video-technologies/video-codec-sdk/12.2/read-me/index.html#system-requirements にCUDA 11.0以上と記載されています。

## CUDAインストール済みDockerイメージの入手
　使えるバージョンのCUDAがインストールされたイメージを[NVIDIA NGC](https://catalog.ngc.nvidia.com/orgs/nvidia/containers/cuda)で探します。
注意点としてタグに`base`とあるイメージはNVENCを有効にするために必要な`nvcc`がインストールされていません。このため`devel`とあるイメージから好みのものを選択して下さい。この記事では`12.6.2-devel-ubuntu24.04`を選択します。
```WSL2
sudo docker pull nvidia/cuda:12.6.2-devel-ubuntu24.04
```
## FFmpegのバージョンを選択
[リリースされているもの](https://git.ffmpeg.org/gitweb/ffmpeg.git/heads)から選んでください。この記事では7.1を選びます。

## Dockerfileの作成
### 冒頭のコードの編集
　FFmpegをインストールしたDockerイメージを作るために`Dockerfile`を準備します。
 [結論](#結論)に記載したコードを`Dockerfile`に書き込み、次のように修正して下さい。
 
 0. 1行目を入手したCUDAインストール済みDockerイメージに合わせる
 0. CUDAのバージョン(例 12.6)に合わせて`ENV PATH="/usr/local/cuda-12.6/bin${PATH:+:${PATH}}"`を書き換える
 0. 同じく`ENV LD_LIBRARY_PATH="/usr/local/cuda-12.6/lib64${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}"`を書き換える
 0. 3つ目の`RUN`のFFmpegのバージョン4箇所を書き換える

### 解説
　[CUDAのインストールガイドで必須とされている](https://docs.nvidia.com/cuda/cuda-installation-guide-linux/index.html#mandatory-actions)にも関わらず、NVIDIAが提供しているDockerイメージでは環境変数が設定されていません。このまま放置すると`nvcc`が見つからないというエラーが出たり、肝心のNVENCが含まれないFFmpegが出来上がったりします。そのため`ENV`で設定しています。ただし、イメージから起動したコンテナ中には持ち込まれないため最後の`RUN`を行っています。
　また`ENV DEBIAN_FRONTEND=noninteractive`がない場合には、最初の`RUN`でライブラリのインストール中にタイムゾーンを選ばされて処理が止まります。これは(普通はインストール済みのため)[NVIDIAの説明](https://docs.nvidia.com/video-technologies/video-codec-sdk/12.0/ffmpeg-with-nvidia-gpu/index.html#compiling-for-linux)にない`git`を追加したことで起こります。

 ## NVENC有効なFFmpegをインストールしたDockerイメージの作成
 　作成した`Dockerfile`があるディレクトリで次を実行します。基にしたイメージが分かる方が管理しやすいため、タグは基のイメージと同じにします。ただの好みなので変更しても問題ありません。
```
sudo docker build -t ffmpeg:12.6.2-devel-ubuntu24.04 .
```
　あとはDockerコンテナを起動すればNVENC可能なFFmpegが使えます。
```
sudo docker run --gpus all -it --rm -v $(pwd):/workspace ffmpeg:12.6.2-devel-ubuntu24.04
```
オプションの意味は次の通りです。
- `--gpus all`: 起動したコンテナでGPUを使用するために必須
- `-it`: インタラクティブシェルに入る
- `--rm`: 同じイメージ由来のコンテナが起動済みの場合は古いコンテナを削除
- `-v $(pwd):/workspace`: カレントディレクトリをコンテナ中の`/workspace`に接続

# 結言
　パスの設定を忘れずに。