x265 是一个免费的软件库和应用程序,用于将视频流编码为 H.265/MPEG-H HEVC 压缩格式,并在 GNU GPL 条款下发布。
FFmpeg 为了支持 H.265 编、解码可以集成 x265 编译,在编译 FFmpeg 之前需要先编译 x265,但并不是所有的版本都能直接使用,比如笔者同时编译 Android、Window 和 Linux 三个平台时,使用 Android NDK r21e 会遇到很多报错,符号缺失,无法编译 .S 文件都是可能遇到的问题。
一、x265 编译
下面的编译统一都使用 ubuntu 18.04 版本进行。编译目录结构如下:
其中 i686 & x86_64 和平台相关,这里是 window 平台示例。 Android 上 arm 平台是 armeabi-v7a & arm64-v8a。x265 目录内放置 x265 的源码。
编译的时候 cd x265 工程
& ./build_x265.sh
即可。然后同级的 build 目录会输出相应的编译产物。
1. 交叉编译 Android 使用的 x265
前置条件使用 android-ndk-r21e linux 版本。编译脚本内容如下:
build_x265.sh
rm -rf $(pwd)/x265/build/android
rm -rf $(pwd)/build
mkdir x265/build/android
cp -rf build_script/* x265/build/android
export NDK_ROOT=/home/snake/Android/android-ndk-r21e # 换成自己的实际 NDK 路径
export ANDROID_API_VERSION=21
export NUMBER_OF_CORES=4
export OUTPUT_PREFIX=$(pwd)/build
pushd x265/build/android/armeabi-v7a
bash build.sh
popd
pushd x265/build/android/arm64-v8a
bash build.sh
popd
build_script/armeabi-v7a/build.sh
cmake ../../../source \
-DCMAKE_SYSTEM_NAME=Android \
-DCMAKE_SYSTEM_VERSION=${ANDROID_API_VERSION} \
-DCMAKE_ANDROID_ARCH_ABI=armeabi-v7a \
-DCMAKE_ANDROID_NDK=${NDK_ROOT} \
-DCMAKE_ANDROID_STL_TYPE=c++_static \
-DNEON_ANDROID=1
sed -i 's/-lpthread/-pthread/g' CMakeFiles/cli.dir/link.txt
sed -i 's/-lpthread/-pthread/g' CMakeFiles/x265-shared.dir/link.txt
sed -i 's/-lpthread/-pthread/g' CMakeFiles/x265-static.dir/link.txt
make -j${NUMBER_OF_CORES}
make DESTDIR=${OUTPUT_PREFIX}/armeabi-v7a install
build_script/arm64-v8a/build.sh
cmake ../../../source \
-DCMAKE_SYSTEM_NAME=Android \
-DCMAKE_SYSTEM_VERSION=${ANDROID_API_VERSION} \
-DCMAKE_ANDROID_ARCH_ABI=arm64-v8a \
-DCMAKE_ANDROID_NDK=${NDK_ROOT} \
-DCMAKE_ANDROID_STL_TYPE=c++_static \
-DNEON_ANDROID=1
sed -i 's/-lpthread/-pthread/g' CMakeFiles/cli.dir/link.txt
sed -i 's/-lpthread/-pthread/g' CMakeFiles/x265-shared.dir/link.txt
sed -i 's/-lpthread/-pthread/g' CMakeFiles/x265-static.dir/link.txt
make -j${NUMBER_OF_CORES}
make DESTDIR=${OUTPUT_PREFIX}/arm64-v8a install
同时为了支持 NEON,需要修改 x265 的源码可参考:https://github.com/kimsan0622/libx265-android/commit/cda6b5eac8f293b7f8264e98b722fd4fb252b072
首先编译 x265 Release_3.5 版本,armeabi-v7a 编译的时候会出现符号缺失的问题(ftello、fseeko…):
fseeko 和 ftello
- fseeko
int fseeko(FILE *stream, off_t offset, int whence);
- ftell
off_t ftello(FILE *stream);
fseeko 和 ftello 两个函数的存在,就是为了解决 fseek 和 ftell 这两个函数中文件 2G 大小限制的历史遗留问题。NDK r21e 中无法导出(ANDROID_API_VERSION=21)armeabi-v7a 内的 fseeko、ftello 符号。
arm64-v8a 编译则会报 .S 文件里的各种符号错误,因此 r21e ndk 是无法正常编译 x265 Release_3.5 版本的。
经过实验,2.6 版本的 x265 是可以正常编译的! 下面的 Window & Linux 版本编译同样使用 x265 2.6 版本。
2. 交叉编译 Windows 使用的 x265
编译环境搭建可以使用 mingw-w64-build:https://github.com/Zeranoe/mingw-w64-build
build_x265.sh
rm -rf $(pwd)/x265/build/windows
rm -rf $(pwd)/build
mkdir $(pwd)/x265/build/windows
cp -rf build_script/* x265/build/windows
export TOOLCHAIN_ROOT=/home/snake/.zeranoe/mingw-w64 # 换成自己的实际 mingw-w64 路径
export NUMBER_OF_CORES=4
export OUTPUT_PREFIX=$(pwd)/build
pushd x265/build/windows/i686
bash build.sh
popd
pushd x265/build/windows/x86_64
bash build.sh
popd
build_script/i686/build.sh
cmake ../../../source \
-DCMAKE_SYSTEM_NAME=Windows \
-DCMAKE_C_COMPILER=${TOOLCHAIN_ROOT}/i686/bin/i686-w64-mingw32-gcc \
-DCMAKE_CXX_COMPILER=${TOOLCHAIN_ROOT}/i686/bin/i686-w64-mingw32-g++ \
-DCMAKE_RANLIB=${TOOLCHAIN_ROOT}/i686/bin/i686-w64-mingw32-ranlib \
-DCMAKE_STRIP=${TOOLCHAIN_ROOT}/i686/bin/i686-w64-mingw32-strip \
-DCMAKE_AR=${TOOLCHAIN_ROOT}/i686/bin/i686-w64-mingw32-ar \
-DCMAKE_LD=${TOOLCHAIN_ROOT}/i686/bin/i686-w64-mingw32-ld \
-DCMAKE_NM=${TOOLCHAIN_ROOT}/i686/bin/i686-w64-mingw32-nm \
-DCMAKE_RC_COMPILER=${TOOLCHAIN_ROOT}/i686/bin/i686-w64-mingw32-windres
make -j${NUMBER_OF_CORES}
make DESTDIR=${OUTPUT_PREFIX}/i686 install
build_script/x86_64/build.sh
cmake ../../../source \
-DCMAKE_SYSTEM_NAME=Windows \
-DCMAKE_C_COMPILER=${TOOLCHAIN_ROOT}/x86_64/bin/x86_64-w64-mingw32-gcc \
-DCMAKE_CXX_COMPILER=${TOOLCHAIN_ROOT}/x86_64/bin/x86_64-w64-mingw32-g++ \
-DCMAKE_RANLIB=${TOOLCHAIN_ROOT}/x86_64/bin/x86_64-w64-mingw32-ranlib \
-DCMAKE_STRIP=${TOOLCHAIN_ROOT}/x86_64/bin/x86_64-w64-mingw32-strip \
-DCMAKE_AR=${TOOLCHAIN_ROOT}/x86_64/bin/x86_64-w64-mingw32-ar \
-DCMAKE_LD=${TOOLCHAIN_ROOT}/x86_64/bin/x86_64-w64-mingw32-ld \
-DCMAKE_NM=${TOOLCHAIN_ROOT}/x86_64/bin/x86_64-w64-mingw32-nm \
-DCMAKE_RC_COMPILER=${TOOLCHAIN_ROOT}/x86_64/bin/x86_64-w64-mingw32-windres
make -j${NUMBER_OF_CORES}
make DESTDIR=${OUTPUT_PREFIX}/x86_64 install
3. 编译 Linux 使用的 x265
此处只编译 x86_64 版本。
build_x265.sh
rm -rf $(pwd)/x265/build/linux-out
rm -rf $(pwd)/build
mkdir $(pwd)/x265/build/linux-out
cp -rf build_script/* x265/build/linux-out
export TOOLCHAIN_ROOT=/usr/bin
export NUMBER_OF_CORES=4
export OUTPUT_PREFIX=$(pwd)/build
pushd x265/build/linux-out/x86_64
bash build.sh
popd
build_script/x86_64/build.sh
cmake ../../../source \
-DCMAKE_SYSTEM_NAME=Linux \
-DCMAKE_C_COMPILER=${TOOLCHAIN_ROOT}/x86_64-linux-gnu-gcc \
-DCMAKE_CXX_COMPILER=${TOOLCHAIN_ROOT}/x86_64-linux-gnu-g++ \
-DCMAKE_RANLIB=${TOOLCHAIN_ROOT}/x86_64-linux-gnu-ranlib \
-DCMAKE_STRIP=${TOOLCHAIN_ROOT}/x86_64-linux-gnu-strip \
-DCMAKE_AR=${TOOLCHAIN_ROOT}/x86_64-linux-gnu-ar \
-DCMAKE_LD=${TOOLCHAIN_ROOT}/x86_64-linux-gnu-ld \
-DCMAKE_NM=${TOOLCHAIN_ROOT}/x86_64-linux-gnu-gcc-nm
make -j${NUMBER_OF_CORES}
make DESTDIR=${OUTPUT_PREFIX}/x86_64 install
二、FFmpeg 集成 x265 编译
下面是 Linux FFmpeg 编译脚本修改点,增加以下差异点即可。
+ # x265的目录
+ X265_INCLUDE=${ROOT_SOURCE}/x265_git_2.6/build/x86_64/usr/local/include
+ X265_LIB=${ROOT_SOURCE}/x265_git_2.6/build/x86_64/usr/local/lib
+ X265_LIB_PKG_CONFIG=${ROOT_SOURCE}/x265_git_2.6/build/x86_64/usr/local/lib/pkgconfig
+ export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:$X264_LIB/pkgconfig:$FDK_AAC_LIB/pkgconfig:$X265_LIB_PKG_CONFIG
echo PKG-CONFIG-PATH=$PKG_CONFIG_PATH
./configure \
......
+ --extra-cflags="-I${X264_INCLUDE} -I${FDK_AAC_INCLUDE} -I${X265_INCLUDE} -I/usr/include" \
+ --extra-ldflags="-L${X264_LIB} -L${FDK_AAC_LIB} -L${X265_LIB}" \
......
+ --enable-libx265 \
+ --enable-encoder=libx265 \
+ --enable-muxer=hevc \
+ --enable-decoder=hevc \
+ --enable-demuxer=hevc \
+ --enable-parser=hevc \
三、H.265 解码
FFmpeg 解码 H.265 码流整体并不难,重点需要注意下,avcodec_find_decoder(…) 入参传入 AV_CODEC_ID_HEVC,然后 avcodec_send_packet 送入 packet 需要拼装 VPS + SPS + PPS + IDR 后一起送入。