OpenCVのvideoioモジュールでソースビルドのFFmpegを使う

この記事は[OpenCV Advent Calendar 2022](https://qiita.com/advent-calendar/2022/opencv)の11日目の記事です。

## 1. はじめに

OpenCVのvideoioモジュールはいくつかのbackendを用意しています。このbackendの一つに[FFmpeg](https://ffmpeg.org/)があります。<https://docs.opencv.org/4.6.0/d0/da7/videoio_overview.html>の「The FFmpeg library」にも

> OpenCV can use the FFmpeg library (<http://ffmpeg.org/>) as backend to record, convert and stream audio and video.

とあります。また、

> FFmpeg is licensed under the GNU Lesser General Public License (LGPL) version 2.1 or later. See `OPENCV_SOURCE_CODE/3rdparty/ffmpeg/readme.txt` and <http://ffmpeg.org/legal.html> for details and licensing information

という説明もあり、OpenCVを自作ソフトウェア、プロダクトに組み込むケースなどでこの点を気にする人が居るかもしれません。それ以外にも

- システムにインストールされているFFmpegでは有効化されていない機能を使いたい
- aptでインストールできるFFmpegよりも新しいバージョンのFFmpegを使いたい
- ハードウェアアクセラレーションを有効にしたFFmpegを使いたい
  - 参考：<https://trac.ffmpeg.org/wiki/HWAccelIntro>
- ライブラリのサイズをコンパクトにしたい
  - 参考：<https://zenn.dev/tetsu_koba/articles/129ac2c6415515>
- openh264を使うようにしたFFmpegを使いたい
  - 参考：<https://tech-blog.re-arc-lab.jp/posts/210911_ffmpeg-openh264/>

など、OpenCVのvideoioモジュールで使用するFFmpegをカスタムしたい状況があるかもしれません。この記事ではOpenCVのvideoioモジュールでソースビルドのFFmpegを使う方法を紹介します。

## 2. OpenCV公式ドキュメントを読む

前述の説明で

> See `OPENCV_SOURCE_CODE/3rdparty/ffmpeg/readme.txt`

とあったので<https://github.com/opencv/opencv/blob/4.6.0/3rdparty/ffmpeg/readme.txt>を読んでみましょう。ここではプラットフォーム別に掻い摘んで読んでいきます。

### 2.1 Linux and other Unix flavors

> On Linux and other Unix flavors OpenCV uses default or user-built ffmpeg/libav libraries.
> If user builds ffmpeg/libav from source and wants OpenCV to stay BSD library, not GPL/LGPL,
> he/she should use --enabled-shared configure flag and make sure that no GPL components are
> enabled (some notable examples are x264 (H264 encoder) and libac3 (Dolby AC3 audio codec)).
> See <https://www.ffmpeg.org/legal.html> for details.

- Linux and other Unix flavorsだとシステムにインストールされているFFmpeg、libavライブラリを使用する
- ソースからビルドしたFFmpeg、libavライブラリを使用する場合、FFmpegのビルド設定で非GPL/非LGPLにすることができる

> If you want to play very safe and do not want to use FFMPEG at all, regardless of whether it's installed on
> your system or not, configure and build OpenCV using CMake with WITH_FFMPEG=OFF flag. OpenCV will then use
> AVFoundation (OSX), GStreamer (Linux) or other available backends supported by opencv_videoio module.

- OpenCVのvideoioモジュールでFFmpegを使用したくない場合、`WITH_FFMPEG=OFF`を指定してビルドし、他のサポートされたbackendを使用するようにすることができる

という旨が書かれています。

### 2.2 Windows

> On Windows OpenCV uses pre-built ffmpeg binaries, built with proper flags (without GPL components) and
> wrapped with simple, stable OpenCV-compatible API.
> The binaries are opencv_videoio_ffmpeg.dll (version for 32-bit Windows) and
> opencv_videoio_ffmpeg_64.dll (version for 64-bit Windows).

- Windows版のOpenCVはpre-builtのFFmpegバイナリを使用する
  - 32-bit Windows：`opencv_videoio_ffmpeg.dll`
  - 64-bit Windows：`opencv_videoio_ffmpeg_64.dll`
- 「without GPL components」のビルド設定でビルドされている

という記載があります。pre-builtのバイナリ、ソースはopencv_3rdpartyで管理されています。

- <https://github.com/opencv/opencv_3rdparty/tree/ffmpeg/4.x_20220912>
- <https://github.com/opencv/opencv_3rdparty/tree/ffmpeg/4.x_20220912_src>

> The pre-built opencv_videoio_ffmpeg*.dll is:
>  
> - LGPL library, not BSD libraries.
> - Loaded at runtime by opencv_videoio module.
> If it succeeds, ffmpeg can be used to decode/encode videos;
> otherwise, other API is used.

- pre-builtの`opencv_videoio_ffmpeg*.dll`のライセンスはLGPL
- opencv_videoioモジュールはAPI実行時のこのDLLをロードし、ロードが成功したらFFmpegを使用する

という記載があります。ライブラリをビルドするためのスクリプトである<https://github.com/opencv/opencv_3rdparty/blob/ffmpeg/4.x_20220912/ffmpeg/make_mingw.sh>を読むと、配布されているライブラリのFFMPEGビルドオプションで`--enable-gpl`が指定されていません。<https://www.ffmpeg.org/legal.html>に以下の記載があります。

- FFmpeg is licensed under the GNU Lesser General Public License (LGPL) version 2.1 or later.
- Compile FFmpeg **without** "--enable-gpl" and **without** "--enable-nonfree".

上記のことから、pre-builtの`opencv_videoio_ffmpeg*.dll`はLGPLバイナリとして頒布するために`--enable-gpl`、`--enable-nonfree`、いずれも指定せずにビルドされていることがわかります。

## 3. OpenCV videoioモジュールにソースビルドしたFFmpegを組み込む

それではOpenCV videoioモジュールにソースビルドしたFFmpegを組み込む方法を紹介します。以降はUbuntu 22.04上で行った手順となります。

### 3.1 FFmpegインストール

<https://mikoto2000.blogspot.com/2021/10/ubuntu-1804-h264-ffmpeg.html>を参考にしてFFmpegをインストールします。Ubuntuへの公式のインストール手順は<https://trac.ffmpeg.org/wiki/CompilationGuide/Ubuntu>にあります。

#### 3.1.1 依存パッケージのインストール

今回ビルドするFFmpegに必要な依存パッケージをインストールします。ビルド設定によってインストールに必要なパッケージは変わるので留意ください。

```shell
sudo apt-get -y install \
  autoconf \
  automake \
  build-essential \
  cmake \
  git-core \
  libass-dev \
  libfreetype6-dev \
  libgnutls28-dev \
  libmp3lame-dev \
  libsdl2-dev \
  libtool \
  libunistring-dev \
  libva-dev \
  libvdpau-dev \
  libvorbis-dev \
  libx264-dev \
  libxcb-shm0-dev \
  libxcb-xfixes0-dev \
  libxcb1-dev \
  libaom-dev \
  meson \
  nasm \
  ninja-build \
  pkg-config \
  texinfo \
  wget \
  yasm \
  zlib1g-dev
```

#### 3.1.2 FFmpegのインストール

以下のコマンドを実行して、FFmpeg [n5.0.2](https://github.com/FFmpeg/FFmpeg/releases/tag/n5.0.2)をインストールします。

```shell
git clone https://github.com/FFmpeg/FFmpeg.git
cd FFmpeg
git checkout n5.0.2

./configure \
  --enable-shared \
  --enable-gpl \
  --enable-libx264 \
  --enable-libaom \
  --enable-nonfree

make -j8
sudo make install
sudo ldconfig
```

FFmpegインストール先はデフォルトの`/usr/local`にしています。また、ここではGPLとなることは気にせず、さらにH.264、AV1を使えるようにしたオプションにしています（実際に使うときのオプションは用途に応じて各自でカスタムしてください）。

#### 3.1.3 FFmpeg動作確認

FFmpegの各コンポーネントバージョンを確認します。

```shell
$ ffmpeg -version
ffmpeg version n5.0.2 Copyright (c) 2000-2022 the FFmpeg developers
built with gcc 11 (Ubuntu 11.3.0-1ubuntu1~22.04)
configuration: --enable-shared --enable-gpl --enable-libx264 --enable-libaom --enable-nonfree
libavutil      57. 17.100 / 57. 17.100
libavcodec     59. 18.100 / 59. 18.100
libavformat    59. 16.100 / 59. 16.100
libavdevice    59.  4.100 / 59.  4.100
libavfilter     8. 24.100 /  8. 24.100
libswscale      6.  4.100 /  6.  4.100
libswresample   4.  3.100 /  4.  3.100
libpostproc    56.  3.100 / 56.  3.100
```

また、

```shell
$ ffmpeg -codecs
```

でH.264、AV1が含まれていることを確認しました。

### 3.2 OpenCVビルド

以下のコマンドを実行してOpenCVをビルドします。ここでは`-D OPENCV_FFMPEG_USE_FIND_PACKAGE=ON`としています（「3.4 OPENCV_FFMPEG_USE_FIND_PACKAGE」参照）。

```shell
git clone https://github.com/opencv/opencv.git
cd opencv
git checkout 4.6.0
mkdir build
cd build
cmake -D CMAKE_BUILD_TYPE=Release -D WITH_FFMPEG=ON -D OPENCV_FFMPEG_USE_FIND_PACKAGE=ON -D BUILD_TESTS=OFF -D BUILD_PERF_TESTS=OFF -D BUILD_opencv_calib3d=OFF -D BUILD_opencv_dnn=OFF -D BUILD_opencv_features2d=OFF -D BUILD_opencv_flann=OFF -D BUILD_opencv_gapi=OFF -D BUILD_opencv_ml=OFF -D BUILD_opencv_objdetect=OFF -D BUILD_opencv_photo=OFF -D BUILD_opencv_stitching=OFF -D BUILD_opencv_video=OFF ..
make -j8
sudo make install
sudo ldconfig
```

OpenCVインストール後、`libopencv_videoio.so`が今回ソースビルドしたFFmpegをリンクしているのか確認します。

```shell
$ ldd /usr/local/lib/libopencv_videoio.so  
	linux-vdso.so.1 (0x00007ffce2995000)
	libopencv_imgcodecs.so.406 => /usr/local/lib/libopencv_imgcodecs.so.406 (0x00007efe0dcf3000)
	libopencv_imgproc.so.406 => /usr/local/lib/libopencv_imgproc.so.406 (0x00007efe0c168000)
	libopencv_core.so.406 => /usr/local/lib/libopencv_core.so.406 (0x00007efe0b16f000)
	libavcodec.so.59 => /usr/local/lib/libavcodec.so.59 (0x00007efe09e2d000)
	libavformat.so.59 => /usr/local/lib/libavformat.so.59 (0x00007efe09bc8000)
	libavutil.so.57 => /usr/local/lib/libavutil.so.57 (0x00007efe09a1a000)
	libswscale.so.6 => /usr/local/lib/libswscale.so.6 (0x00007efe09985000)
	libstdc++.so.6 => /lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007efe09753000)
	<中略>
```

`/usr/local/lib`以下にある今回ソースビルドしたFFmpeg（libavcodec、libavformatなど）をリンクしていることがわかります。

### 3.3 動作確認

以下のスクリプトを実行して動画ファイルのキャプチャができることを確認しました。  
（テストデータは<https://github.com/opencv/opencv/blob/4.6.0/samples/data/vtest.avi>を使用）

```python
import cv2

def main():
    capture = cv2.VideoCapture("vtest.avi")
    if not capture.isOpened():
        raise IOError("can't open capture!")

    while True:
        result, image = capture.read()
        if result is False:
            cv2.waitKey(0)
            break

        cv2.imshow("image", image)
        key = cv2.waitKey(10)
        if key == ord('q'):
            break

    cv2.destroyAllWindows()


if __name__ == '__main__':
    main()
```

また、OpenCVのAV1テストデータ<https://github.com/opencv/opencv_extra/blob/4.6.0/testdata/highgui/video/sample_322x242_15frames.yuv420p.libaom-av1.mp4>も再生できることを確認しました。

### 3.4 OPENCV_FFMPEG_USE_FIND_PACKAGEとは

「3.2 OpenCVビルド」でも少し出てきましたが、`OPENCV_FFMPEG_USE_FIND_PACKAGE`は、CMakeのfind_packageを使ってFFmpegを探索するCMakeオプションです。<https://github.com/opencv/opencv/pull/11636>で追加されています。

## 4. Appendix：FFmpegインストールパスのカスタム

### 4.1 FFmpegのインストール

「3.1.2 FFmpegのインストール」ではFFmpegのインストールパスをデフォルトの`/usr/local`としていましたが、`./configure`の`--prefix=`でFFmpegのインストールパスをカスタムすることができます。  
<https://github.com/FFmpeg/FFmpeg/blob/n5.0.2/configure#L83>

以降、FFmpegのインストールパスを`$HOME/dev/ffmpeg`にしたものとして説明します。

### 4.2 パス設定

FFmpegのインストール後、以下のパスを設定します。`PKG_CONFIG_PATH`の設定はOpenCVビルド時に必要になります。

```shell
export PATH="$HOME/dev/ffmpeg/bin:$PATH"
export LD_LIBRARY_PATH="$HOME/dev/ffmpeg/lib:$LD_LIBRARY_PATH"
export PKG_CONFIG_PATH="$HOME/dev/ffmpeg/lib/pkgconfig:$PKG_CONFIG_PATH"
```

### 4.3 OpenCVビルド

「3.2 OpenCVビルド」の手順と同様です。OpenCVインストール後、lddコマンドで依存関係を確認します。`/home/opencv/dev/ffmpeg/lib`以下にあるライブラリをリンクしていることがわかります。

```
$ ldd /usr/local/lib/libopencv_videoio.so  
	linux-vdso.so.1 (0x00007fff7d959000)
	libopencv_imgcodecs.so.406 => /usr/local/lib/libopencv_imgcodecs.so.406 (0x00007f83e490a000)
	libopencv_imgproc.so.406 => /usr/local/lib/libopencv_imgproc.so.406 (0x00007f83e2d7f000)
	libopencv_core.so.406 => /usr/local/lib/libopencv_core.so.406 (0x00007f83e1d86000)
	libavcodec.so.59 => /home/opencv/dev/ffmpeg/lib/libavcodec.so.59 (0x00007f83e0a44000)
	libavformat.so.59 => /home/opencv/dev/ffmpeg/lib/libavformat.so.59 (0x00007f83e07dd000)
	libavutil.so.57 => /home/opencv/dev/ffmpeg/lib/libavutil.so.57 (0x00007f83e0631000)
	libswscale.so.6 => /home/opencv/dev/ffmpeg/lib/libswscale.so.6 (0x00007f83e059c000)
	libstdc++.so.6 => /lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f83e036a000)
	<中略>
```

## 5. 動作確認環境

- Ubuntu 22.04
- OpenCV 4.6.0
- FFmpeg [n5.0.2](https://github.com/FFmpeg/FFmpeg/releases/tag/n5.0.2) 

## 6. おわりに

この記事ではソースビルドしたFFmpegをOpenCVのvideoioモジュールで使用する方法を紹介しました。明日の[OpenCV Advent Calendar 2022](https://qiita.com/advent-calendar/2022/opencv)は[fukushima1981](https://qiita.com/fukushima1981)さんの「facebookのキャリブレーションパターン検出器の精度はOpenCV実装よりも高い」です。

## 参考URL

- <https://trac.ffmpeg.org/wiki/CompilationGuide/Ubuntu>
- <https://mikoto2000.blogspot.com/2021/10/ubuntu-1804-h264-ffmpeg.html>
- <https://tech-blog.re-arc-lab.jp/posts/210911_ffmpeg-openh264/>
- <https://zenn.dev/tetsu_koba/articles/129ac2c6415515>