ffmpegをwindows向けにビルドした方法

ffmpegをwindows向けにビルドした際にそこそこ苦戦したので知見をまとめます。

- windows: Windows 10 Pro, x64
- ffmpeg: `n3.2.2`
- msys2: `msys2-runtime 2.6.0-1`
- visual studio: `Microsoft Visual Studio Community 2015 Version 14.0.25431.01 Update 3`

# アプローチ

[公式サイト](https://www.ffmpeg.org/platform.html#Windows)や[公式wiki](https://trac.ffmpeg.org/wiki/CompilationGuide)にも書いてあるとおり、大きくいくつかのアプローチがあります。今回は、[MSYS2/MinGW](https://msys2.github.io/)環境でのビルドと、Visual Studio 2015 Community版でのビルドの両方に成功したので、それぞれ説明します。なおどちらの方法でもポータブルなバイナリ(DLL)が作成できます。

# 共通のMSYS2環境の構築

Visual Studioでのビルドとは言っても、cmake製のビルドシステムのようにVisual Studioで開くプロジェクトファイルを生成するわけではありません。Visual Studioに同梱されているコンパイラやリンカなどのツールキットを使ってconfigure/makeベースのビルドをします。そのため、 このconfigure/make自体を動かすためにMSYS2シェルが必要になります。なので、いずれにせよまずはMSYS2をインストールします。

ちなみに、cygwin環境でのビルドもできるそうなのですが、msysはwindowsネイティブなバイナリを作ることを主としているのに対して、cygwinはcygwin用のバイナリを作るのが標準の世界です。なので、cygwinからwindowsへのクロスコンパイルということになるのですが、これは無駄に問題がややこしくなっていると思ったのでやめました。

MSYS2は公式サイトからインストーラーを取ってきます。作業環境が64bit windowsなので64bit版を選びました。推奨どおり `C:\msys64` にインストールしました。なお、msys2自体はその中でさらに3つの世界に分かれていて、mingw32, mingw64, msys2の3つの環境があります。これはこのmsysシェルの中でのツールキットや、リンクするライブラリバイナリなどがまるっと切り替わります。今回はターゲットが32bitなのと、ffmpegのドキュメントどおりに、mingw32環境を使用します。

msys/mingw32シェルは `C:\msys64\mingw32.exe` から起動できます。インストール後初回は、まず全パッケージのアップデートを行います。

```bash
pacman -Syuu
```

そして下記のツールをインストールします。

```bash
pacman -S make
pacman -S diffutils
pacman -S yasm
pacman -S mingw-w64-i686-gcc
```

gccについてはターゲットが32bitなのでi686を指定します。このホストのところがw64である理由はよくわかっていないのですが、mingw32環境であっても64bitで動いているということなのでしょうか？

また先程msys2/mingw32シェルを起動する方法を説明しましたが、もう一つ、windowsシェルの `cmd` からmsys2を開く方法があります。これが後々の手順で必要になるのですが、このときに元々のwindowsシェルのパス変数を引き継いでmsys2シェルを起動するように設定します。 `C:\msys64\msys2_shell.cmd` をテキストエディタで開いて冒頭部の下記コメントをコメントインします。

```
rem To export full current PATH from environment into MSYS2 use '-use-full-path' parameter
rem or uncomment next line
set MSYS2_PATH_TYPE=inherit
```

以降は方式別の手順になります。

# Visual Studio

まずyasmをインストールします。[公式サイト](http://yasm.tortall.net/)からバイナリを落としてきてwindows側のパスを通します。仕組みから言ってmsys2の方で入れたyasmだけあれば大丈夫な気がしますが自分は念のため入れました。

次がポイントです。Visual Studioはクロスコンパイルするためにホスト環境とターゲット環境の組み合わせに対してツールキットを切り替える仕組みになっています。これは、それぞれの組み合わせに応じたコンパイラなどの実行ファイルが分けて用意されていて、どれを使うのかをパス変数にどのフォルダを登録するかによって切り替える仕組みになっています。そして、そのような適切なパスを通すために、 `C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC` に `vcvarsall.bat` というスクリプトが用意されています。これを windows側のシェルの `cmd` から叩くことでパスが通ります。また、わざわざ `vcvarsall.bat` を叩かなくても、自分の環境では専用のショートカットがインストールされていました。 `スタート > Visual Studio 2015 > VS2015 x86 Native Tools Command Prompt` といったものです。自分の環境ではホストが32bitと64bitの2種類、ターゲットが32bit、64bit、ARM(!!)の3種類の組み合わせで6つのショートカットがありました。今回のターゲットは32bit(x86)です。作業環境は64bitのWindowsですが、これに引っ張られてターゲット設定が影響を受けると嫌なので、ツールキットも32bitを使うことにしました。先述の `VS2015 x86 Native Tools Command Prompt` をクリックして起動します。起動したら下記コマンドでちゃんとパスが通っていることを確かめられます。

```
set PATH
```

さて、次にこのvisual studioのツールキットが通った環境から、MSYS2シェルを起動します。それには `C:\msys64\msys2_shell.cmd -mingw32` を実行します。オプションの `-mingw32` によって msys2/mingw32の環境になります。起動したらパスがちゃんと引き継げているか確認します。

```bash
echo $PATH
```

もしここでパスが引き継げていない場合、先述したpathのinherit設定を見直してください。

次にパスの設定を修正します。Visual Studioが用意する `link` というコマンドがあるのですが、msysの方も `/usr/bin/link` というコマンドがあります。引き継いだパスよりmsysのパスのほうが優先設定されるので、このままだとmsysのlinkが使われるのですが、これを前者のVisual Studioのlinkを使う必要があるので、こちらが選択されるようにVCのパスを前方に持ってきます。

まず今パスが通っているVCのパスを取得します。

```bash
$ which -a cl
/c/Program Files (x86)/Microsoft Visual Studio 14.0/VC/BIN/cl
```

この `cl` の入っているフォルダがbinパスなので、下記のようにしてパスの前方に持ってきます。

```bash
export PATH="/c/Program Files (x86)/Microsoft Visual Studio 14.0/VC/BIN:$PATH"
```

こうして、下記出力でVCパスが先に出てくればOKです。

```bash
$ which -a link
/c/Program Files (x86)/Microsoft Visual Studio 14.0/VC/BIN/link
/usr/bin/link
/bin/link
/c/Program Files (x86)/Microsoft Visual Studio 14.0/VC/BIN/link
```

もともとのパスのも後ろに残っているので2回出てますがまあ問題ないです。

いよいよビルドです。作業ディレクトリにffmpegがチェックアウトされているとして、下記手順でconfigureします。

```bash
mkdir out
cd out
../ffmpeg/configure --toolchain=msvc --prefix=./ --enable-shared
```

重要なのは `--toolchain=msvc` を指定することです。今回はDLLでほしいので `--enable-shared` も指定します。それ以外は普通のffmpegのconfigureなので目的に応じて適宜指定してください。configureが成功したら、 `config.mak` が生成されます。これを `Makefile` が読み込んでビルドする仕組みです。

しかしこのままだと問題が生じます。最後にリンクするときに、 `GetDesktopWindow` のシンボルが見つからないというエラーが出てしまいました。 コマンドラインを確認すると、この関数が入っている `user32.lib` がリンク対象に入っていないようなので、それを追加します。

`config.mak` の中から下記のような行を探します。

```
EXTRALIBS= secur32.lib psapi.lib advapi32.lib shell32.lib
```

これに下記のように末尾に `user32.lib` を追記します。

```
EXTRALIBS= secur32.lib psapi.lib advapi32.lib shell32.lib user32.lib
```

修正したらあとはmakeをします。

```bash
make
make install
```

うまくいけば `bin/avcodec.lib`, `bin/avcodec-57.dll` 等が生成されます。 

# MSYS2/MinGW32

こちらの方式では、コンパイラなどのツールキットとしてgccとその仲間を使います。

msys2/mingw32シェルを開きます。作業ディレクトリにffmpegがチェックアウトされているとして、下記手順でconfigureします。

```bash
mkdir out
cd out
../ffmpeg/configure --enable-shared
```

特に特別なことはありません。自動的にmsys2/mingw32環境のツールキットが選択されます。ここではDLLがほしいので `--enable-shared` を指定しています。その他のオプションは普通のffmpegのconfigureなので目的に応じて適宜指定してください。configureが成功したら `config.mak` が生成されます。これを `Makefile` が読み込んでビルドする仕組みです。

しかしこのままだと問題が生じます。ビルドすること自体はできるのですが、最終的に、 `libgcc_s_seh-1.dll`, `libwinpthread-1.dll` という2つの謎のDLLへの動的依存が残ったDLLが生成されてしまいます。これだとmingw32環境専用のバイナリになってしまっていて、通常のwindows環境で起動できません。これを解決するために、 `config.mak` にこの2つのライブラリを静的リンクさせるように修正を行います。

下記のような行を探します。

```bash
EXTRALIBS= -lsecur32 -lm -lbz2 -lz -lpsapi -ladvapi32 -lshell32
```

ここに下記のように `-static-libgcc -Wl,-Bstatic -lpthread -Wl,-Bdynamic` を挿入します。

```bash
EXTRALIBS=-static-libgcc -Wl,-Bstatic -lpthread -Wl,-Bdynamic -lsecur32 -lm -lbz2 -lz -lpsapi -ladvapi32 -lshell32
```

これはgccのCRTとpthreadのライブラリを静的リンクして、それ以降は動的リンクするように指定しています。

修正したらあとはmakeをします。

```bash
make
make install
```

うまくいけば `bin/avcodec.lib`, `bin/avcodec-57.dll` 等が生成されます。 

# 生成物の確認

`C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\bin` に `dumpbin` というツールが入っていて、これを使うとDLLの依存関係などが確認できます。

下記で依存先DLLが確認できます。

```bash
dumpbin -dependents bin/avcodec-57.dll
```

下記で依存先DLLから取り込むシンボルが確認できます。

```bash
dumpbin -imports bin/avcodec-57.dll
```

下記でエクスポートしているシンボルが確認できます。

```bash
dumpbin -exports bin/avcodec-57.dll
```

上記コマンドで調べると、vs製とgcc製で少し違いが出ます。
gccの方は `msvcrt.dll` への依存があり、vs製のものはそれがありません。gccの方はどうやらlibcが静的リンクされるが、libc自体が `msvcrt.dll` に動的依存しています。ここらへんのVisual Studio標準DLLは [公式サイト](https://msdn.microsoft.com/en-us/library/abx4dbyh.aspx?f=255&MSPPError=-2147217396) に説明があります。また、[msys2のサイトにも説明があります。](https://sourceforge.net/p/mingw-w64/wiki2/The%20case%20against%20msvcrt.dll/) それによると結構昔からあるやつで、Windowsに同梱されているそうです。なので、ポータビリティとしては問題なさそうです。vs製のほうはCRT部分は静的リンクされているようです。コマンドラインでは特に指定されていませんが、指定されていない場合 `/MT` 扱いになるようです。VCのCRTのオプションは[ここに情報があります。](https://msdn.microsoft.com/en-us/library/2kzt1wy3.aspx)