demo地址https://github.com/ColorfulHorse/learnFFmpeg, 包含編譯腳本
本文主要參考https://github.com/byhook/ffmpeg4android以及雷霄驊博客
下一篇Android視音頻開發初探【二】(簡單的相機推流器)
一些概念
什么是音視頻開發?
簡單點說分為兩個方面,一方面是播放視頻的時候,要經歷:解協議(網絡視頻,rtmp,rtsp)-> 解封裝(flv, mp4) -> 解碼(h.264,h.265) -> 得到rgb/yuv原始數據,音頻得到pcm原數據,然后分別進行繪制和播放;
另一方面是錄制視頻的時候,流程剛好反過來:采集原數據 -> 原始數據編碼 -> 音視頻封裝到一起 -> 協議封裝傳輸(網絡視頻)。
關于各種格式和協議介紹更詳細的知識可以看雷霄驊的這篇博客 視音頻編解碼技術零基礎學習方法
為什么要進行編解碼?
歸根結底還是因為原數據太大,必須進行壓縮。就拿我們熟悉的rgb565(一個像素2byte)來說,1080x1920 30fps的視頻一秒鐘需要1080x1920x2x30 = 118MB,這種量級是完全無法接受的,所以必然要進行壓縮;對應圖片有圖片壓縮算法,而視頻由于動態連續性可以有更高壓縮比的方法,這就是視頻編碼了。
比如h.264編碼格式,就定義了I幀P幀B幀,I幀可以解析成一個完整的圖像;P幀需要根據它之前的幀來補充自己的信息,本身存的信息比較少;B幀則需要通過前后幀來補充自己。關于H.264更詳細一點的知識可以看這篇博客http://www.lxweimin.com/p/1b3f8187b271
FFmpeg能做什么?
FFmpeg是一個純c編寫的開源庫,提供了對音視頻進行編解碼、變換、采樣、格式封裝、濾鏡后處理的一系列接口,由于統一了上層接口,開發者能更容易基于它開發兼容多平臺的應用。視頻編碼標準迄今為止已經發展了好幾代,每一個標準都有若干編解碼器的實現,比如x264是最好的h.264編碼器,把它接入到FFmpeg以后我們無需再關心x264的api,只需要通過FFmpeg提供的統一編碼接口就可以使用它。
另外,編解碼分為軟編解碼和硬編解碼兩種,軟對應cpu,硬對應gpu,硬編解碼雖然速度更快,但是并非所有設備都支持;軟編解碼效率較低占用資源也比較大,但是勝在兼容性,綜合來說軟硬結合比較合理。android中提供了硬編解碼的api,java層對應MediaCodec類,使用FFmpeg添加mediacodec支持也可以在native層調用它,不過目前僅限于解碼,還不支持編碼。
編譯FFmpeg
上面說了這么多,我們來開始第一步,編譯FFmpeg。我的編譯環境是虛擬機Ubuntu 20.04 LTS 桌面版,
ndkr21,ffmpeg4.2.3,x264最新版,fdk-aac2.0.1(音頻編碼),openssl1.1.1(用于添加https支持),對應的源碼到官網去下載就好,需要注意的是ndk要下載linux版本。高版本的ndk已經移除了gcc,所以我們用clang來編譯,同時arm架構只需要適配armv7和armv8就可以了。
定義公共變量
config.sh 用于初始化一些公用變量
#NDK路徑
export ANDROID_NDK_ROOT=/home/lyj/dev/android-ndk-r21
export AOSP_API="21"
#cpu架構
if [ "$#" -lt 1 ]; then
THE_ARCH=armv7
else
THE_ARCH=$(tr [A-Z] [a-z] <<< "$1")
fi
#根據不同架構配置變量
case "$THE_ARCH" in
armv7a|armeabi-v7a)
TOOLNAME_BASE="arm-linux-androideabi"
COMPILER_BASE="armv7a-linux-androideabi"
AOSP_ABI="armeabi-v7a"
AOSP_ARCH="armeabi-v7a"
OPENSSL_ARCH="android-arm"
HOST="arm-linux-androideabi"
FF_EXTRA_CFLAGS="-DANDROID -Wall -fPIC"
FF_CFLAGS="-DANDROID -Wall -fPIC"
;;
armv8|armv8a|aarch64|arm64|arm64-v8a)
TOOLNAME_BASE="aarch64-linux-android"
COMPILER_BASE="aarch64-linux-android"
AOSP_ABI="arm64-v8a"
AOSP_ARCH="arm64"
OPENSSL_ARCH="android-arm64"
HOST="aarch64-linux-android"
FF_EXTRA_CFLAGS="-DANDROID -Wall -fPIC"
FF_CFLAGS="-DANDROID -Wall -fPIC"
;;
x86)
TOOLNAME_BASE="i686-linux-android"
COMPILER_BASE="i686-linux-android"
AOSP_ABI="x86"
AOSP_ARCH="x86"
OPENSSL_ARCH="android-x86"
HOST="i686-linux-android"
FF_EXTRA_CFLAGS="-DANDROID -Wall -fPIC"
FF_CFLAGS="-DANDROID -Wall -fPIC"
;;
x86_64|x64)
TOOLNAME_BASE="x86_64-linux-android"
COMPILER_BASE="x86_64-linux-android"
AOSP_ABI="x86_64"
AOSP_ARCH="x86_64"
OPENSSL_ARCH="android-x86_64"
HOST="x86_64-linux-android"
FF_EXTRA_CFLAGS="-DANDROID -Wall -fPIC"
FF_CFLAGS="-DANDROID -Wall -fPIC"
;;
*)
echo "ERROR: Unknown architecture $1"
[ "$0" = "$BASH_SOURCE" ] && exit 1 || return 1
;;
esac
# 工具鏈
TOOLCHAIN=$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/linux-x86_64
SYS_ROOT=$TOOLCHAIN/sysroot
# 交叉編譯路徑
CROSS_PREFIX=$TOOLCHAIN/bin/$TOOLNAME_BASE-
# 編譯器
CC=$TOOLCHAIN/bin/$COMPILER_BASE$AOSP_API-clang
CXX=$TOOLCHAIN/bin/$COMPILER_BASE$AOSP_API-clang++
echo "TOOLNAME_BASE="$TOOLNAME_BASE
echo "COMPILER_BASE="$COMPILER_BASE
echo "AOSP_ABI="$AOSP_ABI
echo "AOSP_ARCH="$AOSP_ARCH
echo "HOST="$HOST
編譯x264
build_x264.sh
#!/bin/bash
ARCH=$1
# 導入配置文件
source config.sh $ARCH
# 輸出路徑
LIBS_DIR=$(cd `dirname $0`; pwd)/libs/libx264
echo "LIBS_DIR="$LIBS_DIR
# x264源碼路徑
cd x264
export CC=$CC
export CXX=$CXX
export CXXFLAGS=$FF_EXTRA_CFLAGS
export CFLAGS=$FF_CFLAGS
export AR="${CROSS_PREFIX}ar"
export LD="${CROSS_PREFIX}ld"
export AS="${CROSS_PREFIX}as"
export NM="${CROSS_COMPILE}nm"
export STRIP="${CROSS_COMPILE}strip"
export RANLIB="${CROSS_COMPILE}ranlib"
PREFIX=$LIBS_DIR/$AOSP_ABI
./configure --prefix=$PREFIX \
--enable-static \
--enable-pic \
--disable-cli \
--disable-asm \
--host=$HOST \
--cross-prefix=$CROSS_PREFIX \
--sysroot=$SYS_ROOT \
--extra-cflags="$FF_CFLAGS" \
--extra-ldflags=""
make clean
make -j2
make install
cd ..
命令行sudo bash buid_x264.sh armv7a 編譯對應平臺,同時我們也可以寫一個shell一次性編譯所有平臺,其他的腳本也可以參照這個做法
build_x264_all.sh
for arch in armeabi-v7a arm64-v8a x86 x86_64
do
bash build_x264.sh $arch
done
編譯完以后輸出如下
編譯fdk-aac
build_fdkaac.sh
#!/bin/bash
ARCH=$1
source config.sh $ARCH
LIBS_DIR=$(cd `dirname $0`; pwd)/libs/libfdk-aac
cd fdk-aac-2.0.1
PREFIX=$LIBS_DIR/$AOSP_ABI
echo "PREFIX="$PREFIX
export CC="$CC"
export CXX="$CXX"
export CFLAGS="$FF_CFLAGS"
export CXXFLAGS="$FF_EXTRA_CFLAGS"
# x86架構源碼中使用了math庫所以必須鏈接
export LDFLAGS="-lm"
export AR="${CROSS_PREFIX}ar"
export LD="${CROSS_PREFIX}ld"
export AS="${CROSS_PREFIX}as"
./configure \
--prefix=$PREFIX \
--target=android \
--with-sysroot=$SYS_ROOT \
--enable-static \
--disable-shared \
--host=$HOST
make clean
make -j2
make install
cd ..
編譯fdkaac的時候你可能會遇到一些問題,比如下面這樣
編譯openssl
openssl腳本: build_openssl.sh
#!/bin/bash
ARCH=$1
source config.sh $ARCH
LIBS_DIR=$(cd `dirname $0`; pwd)/libs/openssl
PREFIX=$LIBS_DIR/$AOSP_ABI
echo "PREFIX"=$PREFIX
cd openssl
export ANDROID_NDK_HOME=$ANDROID_NDK_ROOT
export PATH=$TOOLCHAIN/bin:$PATH
export CC="$CC"
export CXX="$CXX"
export AR="${CROSS_PREFIX}ar"
export LD="${CROSS_PREFIX}ld"
export AS="${CROSS_PREFIX}as"
export NM="${CROSS_COMPILE}nm"
./Configure $OPENSSL_ARCH \
-D__ANDROID_API__=$AOSP_API \
--prefix=$PREFIX \
no-shared \
no-engine \
no-dtls \
no-hw
make clean
make -j2
make install
cd ..
編譯FFmpeg
源碼包里面有一個configure文件,我們可以./configure --help來查看編譯配置,也可以去看文檔
FFmpeg wiki
下面的腳本這么多配置是為了對庫進行按需裁剪,可以根據自己的實際情況改動
注意編譯的時候要將 \ 后面的注釋全部去掉,不然無法解析
build_ffmpeg.sh
#!/bin/bash
# 編譯ffmpeg,鏈接x264和fdkaac
ARCH=$1
source config.sh $ARCH
NOW_DIR=$(cd `dirname $0`; pwd)
LIBS_DIR=$NOW_DIR/libs
# 源碼目錄,自行更改
cd ffmpeg-4.2.3
# 輸出路徑
PREFIX=$LIBS_DIR/ffmpeg/$AOSP_ABI
# 頭文件目錄
FDK_INCLUDE=$LIBS_DIR/libfdk-aac/$AOSP_ABI/include
# 庫文件目錄
FDK_LIB=$LIBS_DIR/libfdk-aac/$AOSP_ABI/lib
X264_INCLUDE=$LIBS_DIR/libx264/$AOSP_ABI/include
X264_LIB=$LIBS_DIR/libx264/$AOSP_ABI/lib
OPENSSL_INCLUDE=$LIBS_DIR/openssl/$AOSP_ABI/include
OPENSSL_LIB=$LIBS_DIR/openssl/$AOSP_ABI/lib
./configure \
--target-os=android \
--prefix=$PREFIX \
--enable-cross-compile \
--disable-runtime-cpudetect \
--disable-asm \
--arch=$AOSP_ARCH \
--cc=$CC \
--cxx=$CXX \
--cross-prefix=$CROSS_PREFIX \
# 鏈接頭文件路徑
--extra-cflags="-I$X264_INCLUDE -I$FDK_INCLUDE -I$OPENSSL_INCLUDE $FF_CFLAGS" \
--extra-cxxflags="$FF_EXTRA_CFLAGS" \
# 鏈接庫文件路徑
--extra-ldflags="-L$X264_LIB -L$FDK_LIB -L$OPENSSL_LIB" \
--extra-libs=-lm \
--sysroot=$SYS_ROOT \
--disable-static \
--enable-shared \
--enable-jni \
--enable-mediacodec \
--enable-pthreads \
--enable-pic \
--disable-iconv \
--enable-libx264 \
--enable-libfdk_aac \
--enable-openssl \
--enable-gpl \
--enable-nonfree \
# 編復用器,用于格式封裝
--disable-muxers \
--enable-muxer=mov \
--enable-muxer=mp4 \
--enable-muxer=h264 \
--enable-muxer=avi \
--enable-muxer=flv \
--enable-muxer=hls \
--enable-muxer=rtp \
--enable-muxer=rtsp \
# 解復用器
--disable-demuxers \
--enable-demuxer=mov \
--enable-demuxer=h264 \
--enable-demuxer=avi \
--enable-demuxer=flv \
--enable-demuxer=hls \
--enable-demuxer=rtp \
--enable-demuxer=rtsp \
# 編碼器
--disable-encoders \
--enable-encoder=aac \
--enable-encoder=libfdk_aac \
--enable-encoder=libx264 \
--enable-encoder=mpeg4 \
--enable-encoder=mjpeg \
--enable-encoder=png \
# 解碼器
--disable-decoders \
--enable-decoder=aac \
--enable-decoder=aac_latm \
--enable-decoder=libfdk_aac \
--enable-decoder=h264 \
--enable-decoder=h264_mediacodec \
--enable-decoder=mpeg4 \
--enable-decoder=mjpeg \
--enable-decoder=png \
#解析器
--disable-parsers \
--enable-parser=aac \
--enable-parser=aac_latm \
--enable-parser=h264 \
--enable-parser=mjpeg \
--enable-parser=png \
# 傳輸協議
--disable-protocols \
--enable-protocol=file \
--enable-protocol=crypto \
--enable-protocol=http \
--enable-protocol=https \
--enable-protocol=tls \
--enable-protocol=tcp \
--enable-protocol=udp \
--enable-protocol=rtp \
--enable-protocol=rtmp \
--enable-protocol=rtmps \
--enable-protocol=hls \
# 其他
--enable-zlib \
--enable-small \
--enable-postproc \
--disable-outdevs \
--disable-indevs \
--disable-ffprobe \
--disable-ffplay \
--disable-ffmpeg \
--disable-debug \
--disable-symver
make clean
make -j2
make install
cd ..
如果加入了openssl也許你會碰到下圖的情況,提示openssl not found
我們到FFmpeg源碼目錄/ffbuild/config.log看一下編譯日志
FFmpeg會去調用openssl的一個函數來檢查是否正確加入了openssl庫,然而日志顯示這個函數未定義。
這是因為openssl自1.1.0版本以后將此函數改為了
OPENSSL_init_ssl
。所以我們需要手動到FFmpeg的configure文件里面做一些改動,直接到configure中搜索SSL_library_init
添加下面三行。check_lib openssl openssl/ssl.h OPENSSL_init_ssl -lssl -lcrypto ||
check_lib openssl openssl/ssl.h OPENSSL_init_ssl -lssl32 -leay32 ||
check_lib openssl openssl/ssl.h OPENSSL_init_ssl -lssl -lcrypto -lws2_32 -lgdi32 ||
之后再重新編譯就沒有問題了,之后將FFmpeg輸出目錄中各個平臺的頭文件和庫文件拷貝到項目中就可以了,下一篇博客我們來學習如何使用FFmpeg。