Android視音頻開發初探【一】(clang編譯FFmpeg+fdk-aac+x264+openssl)

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就可以了。

目錄圖
編譯的時候我們可以選擇將所有庫編譯成.a靜態庫,然后把他們合并成一個.so動態庫,但是這樣的話最后的體積會比較大,我這里選擇的是折衷的方式,將x264、fdkaac、openssl編譯成靜態庫,然后動態編譯FFmpeg生成多個so庫。下面來編寫編譯腳本,這需要一點點shell知識。

定義公共變量

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

編譯完以后輸出如下

image

編譯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的時候你可能會遇到一些問題,比如下面這樣

fdkaac-error
這是因為fdk-aac已經作為android的一部分被構建了,所以直接引入了android才有的log庫打印一些日志,我們可以到/libSBRdec/src/llp_tran.cpp源碼中把log相關代碼刪掉,有幾個地方需要刪。本方法來自issue區作者的回答

tag1

編譯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

image

我們到FFmpeg源碼目錄/ffbuild/config.log看一下編譯日志
image

FFmpeg會去調用openssl的一個函數來檢查是否正確加入了openssl庫,然而日志顯示這個函數未定義。
這是因為openssl自1.1.0版本以后將此函數改為了OPENSSL_init_ssl。所以我們需要手動到FFmpeg的configure文件里面做一些改動,直接到configure中搜索SSL_library_init添加下面三行。
image

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。

image
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,825評論 6 546
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,814評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,980評論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,064評論 1 319
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,779評論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,109評論 1 330
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,099評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,287評論 0 291
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,799評論 1 338
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,515評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,750評論 1 375
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,221評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,933評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,327評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,667評論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,492評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,703評論 2 380

推薦閱讀更多精彩內容