Introduction

掲題の通り。

開発メモ その315 FFMPEG で libopenh264 を Windows で使う開発メモ その316 FFMPEG で libopenh264 を Windows で使う その2開発メモ その475 カスタムビルドした FFMPEG を OpenCV にリンクできない にて libopenh264 を使ってライセンス的に問題なく FFmpeg 及び OpenCV から H264 を扱うことを記事にしてきたが…

「再生方法」について一切記載していなかった。
エンコード方法は正しいが、肝心のデコード方法がない。

そして、普通にデコードするだけではライセンス違反だと思われる。

Why?

結論からいうと、 FFmpeg にはデフォルトで h264 をデコードする libavcodec が存在するからである。
何も指定しないと、libopenh264 は使われない。
当然、バックエンドに FFmpeg を使うようにビルドした OpenCV でも同様。

なので

等々、見たところすべてが libavcodec で再生しており、せっかく用意した libopenh264 を使っていない状態になる。
草生える。

Cisco の libopenh264 を同梱しているので悪意はないとは思うが…それを判断するのは裁判所なので (無慈悲)

根拠

ちゃんと証拠がある。
ffmpeg をビルドすると生成される ffmpeg という実行ファイルがあるのだが、これを使って libopenh264 を指定した時とそうでないときの出力が違うからである。
言うまでもなく OpenCV も同様。

FFmpeg

INTER-STREAM®サポートページ にある bun33s.mp4 という H264 動画で試験してみる。
ffmpeg のオプションで入力する動画を意味する -i より前に -c:v <codec name> を指定することでデコーダを指定できる。
-i より後ろだとエンコーダになる。

デコーダ指定無し
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
$ ./ffmpeg -i bun33s.mp4 -map 0:v:0 -f null -
ffmpeg version n8.1 Copyright (c) 2000-2026 the FFmpeg developers
built with gcc 11 (Ubuntu 11.4.0-1ubuntu1~22.04.3)
configuration: --enable-optimizations --disable-debug --enable-shared --disable-static --disable-logging --fatal-warnings --enable-pic --disable-doc --disable-htmlpages --disable-manpages --disable-podpages --disable-txtpages --disable-gpl --enable-libopenh264 --prefix=/data/work/oss/Demo/Multimedia/FFmpeg/install/linux/ffmpeg/n8.1/dynamic/Release
libavutil 60. 26.100 / 60. 26.100
libavcodec 62. 28.100 / 62. 28.100
libavformat 62. 12.100 / 62. 12.100
libavdevice 62. 3.100 / 62. 3.100
libavfilter 11. 14.100 / 11. 14.100
libswscale 9. 5.100 / 9. 5.100
libswresample 6. 3.100 / 6. 3.100
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'bun33s.mp4':
Metadata:
major_brand : isom
minor_version : 512
compatible_brands: isomiso2avc1mp41
encoder : Lavf56.40.100
Duration: 00:00:33.04, start: 0.000000, bitrate: 943 kb/s
Stream #0:0[0x1](eng): Video: h264 (Constrained Baseline) (avc1 / 0x31637661), yuv420p(tv, smpte170m/smpte170m/bt709, progressive), 480x270, 824 kb/s, 25 fps, 25 tbr, 19200 tbn (default)
Metadata:
handler_name : VideoHandler
Stream #0:1[0x2](eng): Audio: aac (LC) (mp4a / 0x6134706D), 44100 Hz, stereo, fltp, 127 kb/s (default)
Metadata:
handler_name : SoundHandler
Stream mapping:
Stream #0:0 -> #0:0 (h264 (native) -> wrapped_avframe (native))
Press [q] to stop, [?] for help
Output #0, null, to 'pipe:':
Metadata:
major_brand : isom
minor_version : 512
compatible_brands: isomiso2avc1mp41
encoder : Lavf62.12.100
Stream #0:0(eng): Video: wrapped_avframe, yuv420p(tv, smpte170m/smpte170m/bt709, progressive), 480x270, q=2-31, 200 kb/s, 25 fps, 25 tbn (default)
Metadata:
encoder : Lavc62.28.100 wrapped_avframe
handler_name : VideoHandler
[out#0/null @ 0x55ac86625140] video:336KiB audio:0KiB subtitle:0KiB other streams:0KiB global headers:0KiB muxing overhead: unknown
frame= 812 fps=0.0 q=-0.0 Lsize=N/A time=00:00:32.48 bitrate=N/A speed=68.3x elapsed=0:00:00.47
デコーダ指定あり
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
$ ./ffmpeg -c:v libopenh264 -i bun33s.mp4 -map 0:v:0 -f null -
ffmpeg version n8.1 Copyright (c) 2000-2026 the FFmpeg developers
built with gcc 11 (Ubuntu 11.4.0-1ubuntu1~22.04.3)
configuration: --enable-optimizations --disable-debug --enable-shared --disable-static --disable-logging --fatal-warnings --enable-pic --disable-doc --disable-htmlpages --disable-manpages --disable-podpages --disable-txtpages --disable-gpl --enable-libopenh264 --prefix=/data/work/oss/Demo/Multimedia/FFmpeg/install/linux/ffmpeg/n8.1/dynamic/Release
libavutil 60. 26.100 / 60. 26.100
libavcodec 62. 28.100 / 62. 28.100
libavformat 62. 12.100 / 62. 12.100
libavdevice 62. 3.100 / 62. 3.100
libavfilter 11. 14.100 / 11. 14.100
libswscale 9. 5.100 / 9. 5.100
libswresample 6. 3.100 / 6. 3.100
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'bun33s.mp4':
Metadata:
major_brand : isom
minor_version : 512
compatible_brands: isomiso2avc1mp41
encoder : Lavf56.40.100
Duration: 00:00:33.04, start: 0.000000, bitrate: 943 kb/s
Stream #0:0[0x1](eng): Video: h264 (Constrained Baseline) (avc1 / 0x31637661), yuv420p(tv, smpte170m/smpte170m/bt709, progressive), 480x270, 824 kb/s, 25 fps, 25 tbr, 19200 tbn (default)
Metadata:
handler_name : VideoHandler
Stream #0:1[0x2](eng): Audio: aac (LC) (mp4a / 0x6134706D), 44100 Hz, stereo, fltp, 127 kb/s (default)
Metadata:
handler_name : SoundHandler
Stream mapping:
Stream #0:0 -> #0:0 (h264 (libopenh264) -> wrapped_avframe (native))
Press [q] to stop, [?] for help
Output #0, null, to 'pipe:':
Metadata:
major_brand : isom
minor_version : 512
compatible_brands: isomiso2avc1mp41
encoder : Lavf62.12.100
Stream #0:0(eng): Video: wrapped_avframe, yuv420p(tv, smpte170m/smpte170m/bt709, progressive), 480x270, q=2-31, 200 kb/s, 25 fps, 25 tbn (default)
Metadata:
encoder : Lavc62.28.100 wrapped_avframe
handler_name : VideoHandler
[out#0/null @ 0x60ca03b34200] video:336KiB audio:0KiB subtitle:0KiB other streams:0KiB global headers:0KiB muxing overhead: unknown
frame= 812 fps=0.0 q=-0.0 Lsize=N/A time=00:00:32.48 bitrate=N/A speed=73.1x elapsed=0:00:00.44

違いは下記。

1
2
3
  Stream mapping:
- Stream #0:0 -> #0:0 (h264 (native) -> wrapped_avframe (native))
+ Stream #0:0 -> #0:0 (h264 (libopenh264) -> wrapped_avframe (native))

デコーダの指定の有無で明確に違うことがわかる。

OpenCV

こっちがややこしい。
まず cv::VideoCapture にはバックエンドを指定するオプションはあるが、デコーダを指定するオプションがない。
では、どうするかというと下記の環境変数を使う。

  • OPENCV_FFMPEG_CAPTURE_OPTIONS
  • OPENCV_FFMPEG_DEBUG
  • OPENCV_LOG_LEVEL

きちんとドキュメントがある。

OpenCV environment variables reference#videoio

上記を使うと、FFmpeg に対して使用するデコーダを指定し、かつデコード中の処理内容を標準出力に吐き出せる。

重要なのは OPENCV_FFMPEG_CAPTURE_OPTIONS。こいつでデコーダを指定する。
指定方法は上記のドキュメントにある通り

Note: extra FFmpeg options should be pased in form key;value|key;value|key;value, for example hwaccel;cuvid|video_codec;h264_cuvid|vsync;0 or vcodec;x264|vprofile;high|vlevel;4.0

とあるので video_codec;libopenh264 と指定すればよい。

cv::VideoCapture を使ったサンプルコードは下記。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>

#include <opencv2/opencv.hpp>

int32_t main(int32_t argc, const char** argv)
{
const auto inputVideoPath = argv[1];
size_t count = 0;
while (true)
{
cv::Mat frame;
if (!videoCapture.read(frame))
{
std::cout << "[Info] Video has finished." << std::endl;
break;
}

count++;
}

std::cout << "[Info] Total frame count: " << count << std::endl;

return 0;
}
環境変数指定無し
1
2
3
4
5
6
7
$ OPENCV_LOG_LEVEL=INFO OPENCV_FFMPEG_DEBUG=1 ./Demo ./bun33s.mp4
[ INFO:0@0.000] global videoio_registry.cpp:257 VideoBackendRegistry VIDEOIO: Enabled backends(9, sorted by priority): FFMPEG(1000); FFMPEG(990); GSTREAMER(980); INTEL_MFX(970); V4L2(960); FFMPEG(950); CV_IMAGES(940); CV_MJPEG(930); UEYE(920)
[OPENCV:FFMPEG:40] Reinit context to 480x272, pix_fmt: yuv420p
[OPENCV:FFMPEG:40] Reinit context to 480x272, pix_fmt: yuv420p
[Info] Video has finished.
[Info] Total frame count: 812
[OPENCV:FFMPEG:40] Statistics: 3952029 bytes read, 2 seeks
環境変数指定あり
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
$ OPENCV_FFMPEG_CAPTURE_OPTIONS="video_codec;libopenh264" OPENCV_LOG_LEVEL=INFO OPENCV_FFMPEG_DEBUG=1 ./Demo ./bun33s.mp4
[ INFO:0@0.000] global videoio_registry.cpp:257 VideoBackendRegistry VIDEOIO: Enabled backends(9, sorted by priority): FFMPEG(1000); FFMPEG(990); GSTREAMER(980); INTEL_MFX(970); V4L2(960); FFMPEG(950); CV_IMAGES(940); CV_MJPEG(930); UEYE(920)
[OPENCV:FFMPEG:40] Reinit context to 480x272, pix_fmt: yuv420p
[OPENCV:FFMPEG:40] [OpenH264] this = 0x0x650a204cddb0, Info:CWelsDecoder::SetOption():DECODER_OPTION_TRACE_CALLBACK callback = 0x7d4f7110c7f0.
[OPENCV:FFMPEG:40] [OpenH264] this = 0x0x650a204cddb0, Info:CWelsDecoder::init_decoder(), openh264 codec version = d12e51a, ParseOnly = 0
[OPENCV:FFMPEG:40] [OpenH264] this = 0x0x650a204cddb0, Info:CWelsDecoder::init_decoder(), openh264 codec version = d12e51a, ParseOnly = 0
[OPENCV:FFMPEG:40] [OpenH264] this = 0x0x650a204cddb0, Info:eVideoType: 1
[OPENCV:FFMPEG:40] [OpenH264] this = 0x0x650a204cddb0, Info:WelsRequestMem(): memory alloc size = 480 * 272, ref list size = 4
[OPENCV:FFMPEG:40] [OpenH264] this = 0x0x650a204cddb0, Info:SyncPictureResolutionExt(), overall memory usage: 5972906 bytes
[OPENCV:FFMPEG:40] [OpenH264] this = 0x0x650a204cddb0, Info:DecodeFrameConstruction(): will output first frame of new sequence, 480 x 272, crop_left:0, crop_right:0, crop_top:0, crop_bottom:1, ignored error packet:0.
[OPENCV:FFMPEG:40] [OpenH264] this = 0x0x650a204cddb0, Info:DecodeFrameConstruction(): will output first frame of new sequence, 480 x 272, crop_left:0, crop_right:0, crop_top:0, crop_bottom:1, ignored error packet:0.
[OPENCV:FFMPEG:40] [OpenH264] this = 0x0x650a204cddb0, Info:DecodeFrameConstruction(): will output first frame of new sequence, 480 x 272, crop_left:0, crop_right:0, crop_top:0, crop_bottom:1, ignored error packet:0.
[OPENCV:FFMPEG:40] [OpenH264] this = 0x0x650a204cddb0, Info:DecodeFrameConstruction(): will output first frame of new sequence, 480 x 272, crop_left:0, crop_right:0, crop_top:0, crop_bottom:1, ignored error packet:0.
[OPENCV:FFMPEG:40] [OpenH264] this = 0x0x650a204cddb0, Info:DecodeFrameConstruction(): will output first frame of new sequence, 480 x 272, crop_left:0, crop_right:0, crop_top:0, crop_bottom:1, ignored error packet:0.
[OPENCV:FFMPEG:40] [OpenH264] this = 0x0x650a204cddb0, Info:DecodeFrameConstruction(): will output first frame of new sequence, 480 x 272, crop_left:0, crop_right:0, crop_top:0, crop_bottom:1, ignored error packet:0.
[OPENCV:FFMPEG:40] [OpenH264] this = 0x0x650a204cddb0, Info:DecodeFrameConstruction(): will output first frame of new sequence, 480 x 272, crop_left:0, crop_right:0, crop_top:0, crop_bottom:1, ignored error packet:0.
[OPENCV:FFMPEG:40] [OpenH264] this = 0x0x650a204cddb0, Info:DecodeFrameConstruction(): will output first frame of new sequence, 480 x 272, crop_left:0, crop_right:0, crop_top:0, crop_bottom:1, ignored error packet:0.
[OPENCV:FFMPEG:40] [OpenH264] this = 0x0x650a204cddb0, Info:DecodeFrameConstruction(): will output first frame of new sequence, 480 x 272, crop_left:0, crop_right:0, crop_top:0, crop_bottom:1, ignored error packet:0.
[OPENCV:FFMPEG:40] [OpenH264] this = 0x0x650a204cddb0, Info:DecodeFrameConstruction(): will output first frame of new sequence, 480 x 272, crop_left:0, crop_right:0, crop_top:0, crop_bottom:1, ignored error packet:0.
[OPENCV:FFMPEG:40] [OpenH264] this = 0x0x650a204cddb0, Info:DecodeFrameConstruction(): will output first frame of new sequence, 480 x 272, crop_left:0, crop_right:0, crop_top:0, crop_bottom:1, ignored error packet:0.
[OPENCV:FFMPEG:40] [OpenH264] this = 0x0x650a204cddb0, Info:DecodeFrameConstruction(): will output first frame of new sequence, 480 x 272, crop_left:0, crop_right:0, crop_top:0, crop_bottom:1, ignored error packet:0.
[OPENCV:FFMPEG:40] [OpenH264] this = 0x0x650a204cddb0, Info:DecodeFrameConstruction(): will output first frame of new sequence, 480 x 272, crop_left:0, crop_right:0, crop_top:0, crop_bottom:1, ignored error packet:0.
[OPENCV:FFMPEG:40] [OpenH264] this = 0x0x650a204cddb0, Info:DecodeFrameConstruction(): will output first frame of new sequence, 480 x 272, crop_left:0, crop_right:0, crop_top:0, crop_bottom:1, ignored error packet:0.
[OPENCV:FFMPEG:40] [OpenH264] this = 0x0x650a204cddb0, Info:DecodeFrameConstruction(): will output first frame of new sequence, 480 x 272, crop_left:0, crop_right:0, crop_top:0, crop_bottom:1, ignored error packet:0.
[OPENCV:FFMPEG:40] [OpenH264] this = 0x0x650a204cddb0, Info:DecodeFrameConstruction(): will output first frame of new sequence, 480 x 272, crop_left:0, crop_right:0, crop_top:0, crop_bottom:1, ignored error packet:0.
[OPENCV:FFMPEG:40] [OpenH264] this = 0x0x650a204cddb0, Info:DecodeFrameConstruction(): will output first frame of new sequence, 480 x 272, crop_left:0, crop_right:0, crop_top:0, crop_bottom:1, ignored error packet:0.
[OPENCV:FFMPEG:40] [OpenH264] this = 0x0x650a204cddb0, Info:DecodeFrameConstruction(): will output first frame of new sequence, 480 x 272, crop_left:0, crop_right:0, crop_top:0, crop_bottom:1, ignored error packet:0.
[Info] Video has finished.
[Info] Total frame count: 812
[OPENCV:FFMPEG:40] [OpenH264] this = 0x0x650a204cddb0, Info:CWelsDecoder::~CWelsDecoder()
[OPENCV:FFMPEG:40] [OpenH264] this = 0x0x650a204cddb0, Info:CWelsDecoder::UninitDecoderCtx(), openh264 codec version = d12e51a.
[OPENCV:FFMPEG:40] [OpenH264] this = 0x0x650a204cddb0, Info:CWelsDecoder::UninitDecoder(), verify memory usage (0 bytes) after free..
[OPENCV:FFMPEG:40] Statistics: 3952029 bytes read, 2 seeks

比較するまでもないが、デコーダを指定すると、openh264 を使っていることがよくわかる。
openh264 codec version = d12e51a. なんて https://github.com/cisco/openh264/blob/v2.6.0/codec/decoder/plus/src/welsDecoderExt.cpp#L350 そのままである。

画質

念のため比較した。
ffmpeg でデコードし静止画に分割したファイルを liopenh264 指定あり/なしについて WinMerge で比較したが差はなかったで一安心。

まとめ

今すぐソースコードを確認しよう (戒め)