macOS における FFmpeg の source code からのビルドについての纏め

FFmpeg を source code からビルドする必要性は一般的には極めて低い。FFmpeg はひろく普及している OSS の一つであり、 様々な OS に向けたバイナリファイルが用意されており、一般にはビルドすべき機会がない為である。 また FFmpeg のビルドは必要性よりもその困難性により避けられているようにも思われる。 この困難性は FFmpeg が各種の特許問題を回避するために複雑なライブラリ構成をとっている事とも無関係とは思えない。 以上のような状況を踏まえてなお FFmpeg をビルドすべき最大の理由は FFmpeg の豊富なメディアライブラリの API を利用したい場合である。 ここでは Xcode toolchains をインストールしている前提で FFmpeg の API を macOS で利用するためにどのような手順をとったか纏めたものである。 Homebrew を利用している場合は以下で示したライブラリを全て必要とはしていない点に留意していただきたい。

ビルド環境の用意と使い方について

多くのライブラリは Makefile と configure を備えており、
./configure --prefix=/usr/local
make -j 4
sudo make install
とする事で --prefix に指定したディレクトリにライブラリをインストールする事ができる。make -j は並列度のオプションである。 ところが一部のライブラリは automake/autoconf や CMake、meson/ninja によるビルドを前提としたものが存在する。 そこでライブラリを円滑に導入する為のいくつかの注意点について簡単に纏めておく。 実際の導入は FFmpeg をビルドする際に自動化したスクリプトで処理する事にするのでここでは省略する。

automake/autoconf

automake/autoconf によりビルドする環境の特徴は source code 中に Makefile.am や configure.ac が存在するという点である。 このビルド環境では Makefile や configure を自動的に生成することを目的としており、一般的には次のように行う。

autoreconf --install
automake
./configure --prefix=/usr/local
make -j 4
sudo make install

このようにして Makefile や configure を作成した上で ./configure や make を実行する事になる。 automake/autoconf を導入する際には、libtool を必要とするので予めインストールが必要である。

CMake

CMake によりビルドする環境の特徴は source code 中に CMakeLists.txt が存在する点である。 LLVM/Clang などのプロジェクトでも用いられているが、Makefile や configure が用意されている場合も多い。 CMake 環境の特徴の一つは configure は実行せず make を呼び出す点で、一般的には次のように行う。

cmake -DCMAKE_INSTALL_PREFIX=/usr/local
make -j 4
sudo make install

D で始まる独特のオプションを伴う事が一般的であり、vid.stab ライブラリで示した様に CMakeLists.txt に 独自に定義されたオプションによって ./configure で行われていた多彩なカスタマイズを可能としている。インストールにあたって特別必要な外部ライブラリはない。 また CMake においては Out-of-Tree build と呼ばれる CMakeLists.txt と異なるディレクトリで cmake/make の実行が要請されることも多く、 例えば CMakeLists.txt ファイルから見て ../build ディレクトリでビルドしたい場合は cmake ../build/ -DCMAKE_INSTALL_PREFIX=/usr/local などと指定する。

meson/ninja

meson/ninja は比較的新しい Python3 を基盤としたビルド環境であり、その特徴は source code 中に meson.build が存在し、 なによりも CMake においても用いられた make に変わって ninja を呼び出す点である。一般的には次のように行う。

meson --prefix=/usr/local . ../build/
		ninja
		ninja install

meson においては Out-of-Tree build が標準的であり、上記の例は meson.build ファイルから見て ../build ディレクトリでビルドしたい場合のものである。 macOS では Python2.x しか標準でインストールされていないため、一般には Python3 を用意するところから行なわねばならない。 その際に Python3 は zlib がなければ zlib を必要とする標準ライブラリをインストールしてくれないため、明示的に zlib を示しておかねばならない。 故に zlib, Python3, setuptools, meson, re2c, ninja の順にインストールせねばならない。 setuptools は meson をビルドする際に必要となる Python3 で非常に一般的な拡張ライブラリであり、re2c は ninja が必要としている。 setuptools, re2c は python3 ./setup.py install とするだけでインストールが完了する。インストール先は当然ながら Python3 をインストールした --prefix 環境である。

FFmpeg のビルド

次の様なシェルスクリプトを実行することで FFmpeg のビルドを半自動的に行い、ヘッダファイルの抽出が可能となる。
#!/bin/sh

SDK_DIR="ffmpeg-20190529-d903c09-macos64-shared"
INSTALL_DIR="include"
SOURCE_CODE_DIR="ffmpeg-02333fe"
BUILD_DIR="build"
THREAD_NUM=8
OPTINONAL_CONFIG=""
CURRENT_DIR=`pwd`
export SDK_DIR
CONFIG=`ruby <<-EOF
	dir = ENV["SDK_DIR"]
	options = []
	if File.exists?("#{dir}/README.txt")
		fileHandle = File.open("#{dir}/README.txt", "r")
		isConfig = false
		while ! fileHandle.eof?
			string = fileHandle.readline
			if isConfig
				if string[0, "  --".length] == "  --"
					options.push(string[2, string.length - 4])
				end
				break if string[0, "Libraries:".length] == "Libraries:"
			else
				isConfig = true if string[0, "Configuration:".length] == "Configuration:"
			end
		end
	end
	print options.join(" ")
EOF`

TEMPORARY_INSTALL_DIR=/tmp
while [ -e ${TEMPORARY_INSTALL_DIR} ]
do
	TEMPORARY_INSTALL_DIR=/tmp/`cat /dev/urandom | base64 | fold -w 10 | head -n 1`
done
mkdir ${TEMPORARY_INSTALL_DIR}
PATH=${TEMPORARY_INSTALL_DIR}/bin:${PATH}
export LDFLAGS=-L${TEMPORARY_INSTALL_DIR}/lib
export CFLAGS=-I${TEMPORARY_INSTALL_DIR}/include
export PKG_CONFIG_PATH=${TEMPORARY_INSTALL_DIR}/lib/pkgconfig

function CreateSDK ()
{
	LIBS_LIST=(avcodec avdevice avfilter avformat avutil postproc swresample swscale)

	if [ ! -e ${SDK_DIR}/${INSTALL_DIR} ]; then
		mkdir ${SDK_DIR}/${INSTALL_DIR}
	fi

	for libName in "${LIBS_LIST[@]}"
	do
		mv ${TEMPORARY_INSTALL_DIR}/include/lib$libName ${SDK_DIR}/${INSTALL_DIR}/lib$libName
	done
}

function ConfigFFmpeg ()
{
	cd "${SOURCE_CODE_DIR}"
	./configure ${CONFIG} --prefix=${TEMPORARY_INSTALL_DIR} ${OPTINONAL_CONFIG} #--extra-ldflags=-L${TEMPORARY_INSTALL_DIR}/lib --extra-cflags=-I/TEMPORARY_INSTALL_DIR}/include
	make -j ${THREAD_NUM}
	make install
	cd "${CURRENT_DIR}"
}

function ProjectExtend ()
{
	fileName=$1
	fileExt=$2
	fileExtender=$3
	optExtender=$4

	mkdir extended
	if [ -z "${optExtender}" ]; then
		${fileExtender} ${fileName}.${fileExt} -C extended
	else
		${optExtender} ${fileName}.${fileExt} | ${fileExtender} -C extended
	fi
	if [ -e extended/${fileName} ]; then
		mv extended/${fileName} ${fileName}
		rm -r extended
	else
		mv extended ${fileName}
	fi
}

function ProjectBuild ()
{
	fileName=$1
	envOpt=$5
	buildOpt=$6
	makeType="make"

	cd "${BUILD_DIR}"

	ProjectExtend "$1" "$2" "$3" "$4"

	cd "${fileName}"
	if [ -n "${envOpt}" ]; then
		eval ${envOpt}
	fi

	if [ ! -e configure ]; then
		if [ -e Makefile.am ]; then
			autoreconf --install
			automake
		fi
	fi

	if [ -e configure ]; then
		./configure --prefix=${TEMPORARY_INSTALL_DIR} ${buildOpt}
	elif [ -e CMakeLists.txt ]; then
		cmake -DCMAKE_INSTALL_PREFIX=${TEMPORARY_INSTALL_DIR} ${buildOpt}
	elif [ -e setup.py ]; then
		python3 ./setup.py install
		makeType=""
	else
		makeType=""
	fi

	if [ -n "${makeType}" ]; then
		make -j ${THREAD_NUM}
		make install
	fi
	cd ../
	rm -r "${fileName}"
	cd "${CURRENT_DIR}"
}

function ProjectTreeBuild ()
{
	fileName=$1
	envOpt=$5
	buildOpt=$6
	makeType="make"

	cd "${BUILD_DIR}"

	ProjectExtend "$1" "$2" "$3" "$4"

	mkdir work
	cd work
	if [ -n "${envOpt}" ]; then
		eval ${envOpt}
	fi

	if [ -e ../${fileName}/configure ]; then
		../${fileName}/configure --prefix=${TEMPORARY_INSTALL_DIR} ${buildOpt}
	elif [ -e ../${fileName}/CMakeLists.txt ]; then
		cmake ../${fileName}/ -DCMAKE_INSTALL_PREFIX=${TEMPORARY_INSTALL_DIR} ${buildOpt}
	elif [ -e ../${fileName}/meson.build ]; then
		meson --prefix=${TEMPORARY_INSTALL_DIR} ../${fileName}/ .
		ninja
		ninja install
		makeType=""
	fi
	if [ -n "${makeType}" ]; then
		make -j ${THREAD_NUM}
		make install
	fi
	cd ../
	rm -r work
	rm -r "${fileName}"
	cd "${CURRENT_DIR}"
}

function CreateLibrary ()
{
	ProjectBuild "pkg-config-0.29.2" "tar.gz" "tar -xzvf" "" "" "--with-internal-glib"
	ProjectBuild "libtool-2.4.6" "tar.xz" "tar xfv -" "xz -dckv"
	ProjectBuild "automake-1.16.1" "tar.xz" "tar xfv -" "xz -dckv"
	ProjectBuild "autoconf-2.69" "tar.xz" "tar xfv -" "xz -dckv"
	ProjectBuild "cmake-3.14.4" "tar.gz" "tar -xzvf"
	ProjectBuild "zlib-1.2.11" "tar.gz" "tar -xzvf"
	ProjectBuild "Python-3.7.3" "tar.xz" "tar xfv -" "xz -dckv"
	ProjectBuild "setuptools-41.0.1" "zip" "echo" "" "cd ../; rm -r setuptools-41.0.1; unzip setuptools-41.0.1.zip; cd setuptools-41.0.1"
	ProjectBuild "meson-0.50.1" "tar.gz" "tar -xzvf"
	ProjectBuild "re2c-1.1.1" "tar.gz" "tar -xzvf"
	ProjectBuild "ninja-master" "zip" "echo" "" "cd ../; rm -r ninja-master; unzip ninja-master.zip; cd ninja-master; python3 configure.py --bootstrap; mv ninja ${TEMPORARY_INSTALL_DIR}/bin/ninja"
	ProjectBuild "gmp-6.1.2" "tar.bz2" "tar -xjvf"
	ProjectBuild "nasm-2.14.02" "tar.gz" "tar -xzvf"
	ProjectBuild "yasm-1.3.0" "tar.gz" "tar -xzvf"
	ProjectBuild "nettle-3.4.1" "tar.gz" "tar -xzvf"
	ProjectBuild "libtasn1-4.9" "tar.gz" "tar -xzvf" "" "CFLAGS=\"${CFLAGS} -Wno-sign-compare\"" ""
	ProjectBuild "gnutls-3.6.8" "tar.xz" "tar xfv -" "xz -dckv" "" "--with-included-unistring --with-included-libtasn1 --without-p11-kit"
	ProjectTreeBuild "aom-1.0.0" "tar.gz" "tar -xzvf"
	ProjectBuild "freetype-2.10.0" "tar.bz2" "tar -xjvf"
	ProjectBuild "fribidi-1.0.5" "tar.bz2" "tar -xjvf"
	ProjectBuild "libass-0.14.0" "tar.xz" "tar xfv -" "xz -dckv"
	ProjectBuild "libxml2-2.9.7" "tar.gz" "tar -xzvf"
	ProjectBuild "fontconfig-2.13.1" "tar.bz2" "tar -xjvf"
	ProjectBuild "libbluray-1.1.1" "tar.bz2" "tar -xjvf" "" "" "--disable-bdjava-jar"
	ProjectTreeBuild "dav1d-master" "tar.bz2" "tar -xjvf"
	ProjectBuild "lame-3.99.5" "tar.gz" "tar -xzvf" # https://forums.slimdevices.com/showthread.php?108552-Installation-of-LAME-under-MacOS-10-13-2-(High-Sierra)-for-use-with-Squeezebox
	ProjectBuild "CUnit-2.1-2" "tar.bz2" "tar -xjvf" # https://qiita.com/from_chc/items/db771bef1e83fc00783a
	ProjectTreeBuild "libmysofa-master" "zip" "echo" "" "cd ../; rm -r libmysofa-master; unzip libmysofa-master.zip; cd libmysofa-master"
	ProjectBuild "opencore-amr-0.1.5" "tar.gz" "tar -xzvf"
	ProjectBuild "openjpeg-2.3.1" "tar.gz" "tar -xzvf"
	ProjectBuild "opus-1.3.1" "tar.gz" "tar -xzvf"
	ProjectBuild "shine-master" "zip" "echo" "" "cd ../; rm -r shine-master; unzip shine-master.zip; cd shine-master"
	ProjectBuild "snappy-master" "zip" "echo" "" "cd ../; rm -r snappy-master; unzip snappy-master.zip; cd snappy-master"
	ProjectBuild "soxr-0.1.3-Source" "tar.xz" "tar xfv -" "xz -dckv"
	ProjectBuild "speex-1.2.0" "tar.gz" "tar -xzvf"
	ProjectBuild "libogg-1.3.3" "tar.xz" "tar xfv -" "xz -dckv"
	ProjectBuild "libvorbis-1.3.6" "tar.xz" "tar -xjvf"
	ProjectBuild "libtheora-1.1.1" "tar.bz2" "tar -xjvf"
	ProjectBuild "twolame-0.3.13" "tar.gz" "tar -xzvf"
	ProjectBuild "vid.stab-master" "zip" "echo" "" "cd ../; rm -r vid.stab-master; unzip vid.stab-master.zip; cd vid.stab-master" "-DUSE_OMP=NO"
	ProjectBuild "vo-amrwbenc-master" "zip" "echo" "" "cd ../; rm -r vo-amrwbenc-master; unzip vo-amrwbenc-master.zip; cd vo-amrwbenc-master"
	ProjectBuild "libvpx-refs_heads_master" "tar.gz" "tar -xzvf"
	ProjectBuild "wavpack-5.1.0" "tar.bz2" "tar -xjvf"
	ProjectBuild "libwebp-master" "zip" "echo" "" "cd ../; rm -r libwebp-master; unzip libwebp-master.zip; cd libwebp-master"
	ProjectBuild "x264-snapshot-20190530-2245" "tar.bz2" "tar -xjvf" "" "" "--enable-shared"
	ProjectBuild "x265_3.0" "tar.gz" "tar -xzvf" "" "cd source"
	ProjectBuild "xvidcore" "tar.gz" "tar -xzvf" "" "cd build/generic"
	ProjectBuild "zimg-release-2.8" "tar.gz" "tar -xzvf"
	ProjectBuild "SDL2-2.0.9" "tar.gz" "tar -xzvf"
}

CreateLibrary
ConfigFFmpeg
CreateSDK

rm -r ${TEMPORARY_INSTALL_DIR}
このスクリプトは慣習的に sh を指定した宣言になっているが bash 固有の機能を呼び出している点に注意する。基本的には次にようなディレクトリ構成を前提としている。
./script.sh
./SDK_DIR/
./SOURCE_CODE_DIR/
./BUILD_DIR/
./INSTALL_DIR/

スクリプト定数について

SDK_DIR とは Zeranoe FFmpeg で配布されている、動的な共有ライブラリ形式でビルドされた FFmpeg を展開したディレクトリである。 FFmpeg をビルドする際に README.txt を読み取る事で配布されたバイナリと同じコンパイルオプションになる様にするためのもので、そのオプションは CONFIG に格納されている。 Ruby をヒアドキュメントで実行しているが README.txt が CRLF で改行されていることを前提にしたコードとなっている。 なお OPTINONAL_CONFIG は FFmpeg をビルドする際に追加で加えておきたいオプションを指定する定数である。

SOURCE_CODE_DIR はビルドしたい FFmpeg のソースコードを展開したディレクトリである。Zeranoe FFmpeg で配布物毎にリポジトリのスナップショット URL が示されているのでそれを参考に作成する。

BUILD_DIR は FFmpeg をビルドする際に使用するライブラリの圧縮ファイルを設置するディレクトリである。 ビルドする際に使用するライブラリのバージョンは function CreateLibrary で圧縮ファイルの名前として指定しているので、適宜書き換える必要性がある。 またこの圧縮ファイル名は展開された際に生成されるディレクトリ名と一致している必要性がある。 殆どの場合は tar.gz, tar.bz2 を前提としたコードとなっているが、tar.xz を導入する場合はパイプで流し込んで処理する。 この xz を利用する為に XZ Utils を予めインストールする必要性がある。 zip の場合は展開作業を行うことなく環境変数定義用に用意した function ProjectBuild の第五引数で展開する。

INSTALL_DIR は FFmpeg をビルド後にヘッダを抽出して格納するディレクトリである。一部のヘッダでビルド時のみ参照されると思われる config.h が参照されているのでコメントアウトすべき点に注意したい。 因みに Xcode で利用する際に .tbd ファイルが必要な場合は Xcode toolchains の ld が含まれているディレクトリに存在する tapi に対して tapi stubify libavcodec.58.dylib などとすると作成できる。

THREAD_NUM は make する際に指定する並列度であり、TEMPORARY_INSTALL_DIR はスクリプトを実行すると自動的に決定される作業用のディレクトリである。 TEMPORARY_INSTALL_DIR はこのスクリプトがライブラリをインストールする先であり、作業終了後には自動的に削除されるディレクトリである。

ProjectBuild/ProjectTreeBuild はライブラリの圧縮ファイルからライブラリを TEMPORARY_INSTALL_DIR へインストールするルーチンである。 引数は、ファイル名、拡張子、展開コマンド文字列、パイプを伴う展開コマンド文字列、ビルド前実行スクリプト文字列、ビルドオプション、以上の六つとなっている。 ビルドオプションは configure か cmake に対して渡される。

一部のライブラリについて

ninja は python3 configure.py --bootstrap を実行した上で生成された ninja ファイルをコピーして使用する。

nasm は FFmpeg で必要とされているが macOS のインストールされているものでは古い場合があるので FFmpeg に対して明示的に与えられる様に、PATH 変数の書き換えは TEMPORARY_INSTALL_DIR の bin を優先する。

libtasn1 は 4.9 の場合に -Wno-sign-compare を与えなければ整数比較の箇所で Clang がエラーを出してビルドできない問題がある。 最新版では meson が使われているが gnutls では内蔵されているので絶対に同じバージョンでなければならないわけではない。

libbruray は 1.1.1 の場合には bdjava オプションを指定しないことでビルドを通す様にする。

lame は最新版には ./configure が存在しないなどの問題があるので古いバージョンを導入する。

vid.stab はスレッド並列を実現する OpenMP を標準で前提としているが Clang は標準では対応していないので CMakeLists.txt に記載された DUSE_OMP に NO を与えることで無効化する。 Mac OS X 10.5 頃の Xcode で gcc を採用していた際は OpenMP に対応していたが、Apple は GCD を用いたスレッド並列を推奨していて、OpenMP が使えない事情がある。 LLVM/Clang も最近は公式のリソースをビルドすれば Intel の OpenMP ライブラリを組み込むことができる様になってきたが、今尚その手続きは複雑なものである。

x264 は標準で共有ライブラリが作られないので明治的にビルドオプションを指定する。 x265 や xvidcore では make するディレクトリまで移動する必要性があるので、ビルド前実行スクリプトで cd を指定する。

技術考󠄁 > macOS における FFmpeg の source code からの build についての纏め
Copyright© R01[2019]. All Rights Reserved.