1喳坠、概述
與FFmpeg初識(shí)杈曲,大約是在六年前丈钙。那時(shí)公司要求做這么一個(gè)功能:運(yùn)營(yíng)編輯人員通過(guò)管理后臺(tái)上傳本地視頻文件或粘貼其他視頻網(wǎng)站的詳情頁(yè)地址非驮,獲取到視頻后,通過(guò)FFmpeg來(lái)轉(zhuǎn)碼為各種清晰度的視頻雏赦。那時(shí)剛畢業(yè)不久劫笙,只略懂PHP和JAVA芙扎,音視頻的知識(shí)更是一窮二白,只能通過(guò)JAVA調(diào)用系統(tǒng)命令行的方式使用FFmpeg填大,最終因?yàn)樾阅艿葐?wèn)題戒洼,項(xiàng)目流產(chǎn)了。再次相遇是兩年前允华,為提升ExoPlayer播放器的兼容性圈浇,為其添加視頻軟解功能。這次打開(kāi)FFmpeg的姿勢(shì)稍微帥一點(diǎn)靴寂,會(huì)使用FFmpeg API來(lái)做轉(zhuǎn)碼磷蜀。恍恍惚惚這么多年榨汤,一直都是FFmpeg的看客蠕搜,只在門(mén)口晃悠。
近來(lái)想稍微系統(tǒng)地學(xué)習(xí)一番F(xiàn)Fmpeg收壕,發(fā)現(xiàn)國(guó)內(nèi)關(guān)于FFmpeg的相關(guān)資料甚少妓灌。可參考的有:
- 官方文檔:這里能了解到最新FFmpeg動(dòng)態(tài)
- 雷神的博客:相信國(guó)內(nèi)大多數(shù)音視頻開(kāi)發(fā)者或多或少看過(guò)他的博文蜜宪,運(yùn)行過(guò)他的代碼虫埂。雷神已故,甚是可惜圃验。FFmpeg迭代很快掉伏,其中不少API使用已經(jīng)過(guò)時(shí),希望有更多的人延續(xù)雷神的分享精神澳窑。
- 《FFmpeg從入門(mén)到精通》:本書(shū)側(cè)重于命令行的使用斧散,API甚少。作者應(yīng)該也是大神摊聋,只是過(guò)分惜墨鸡捐,導(dǎo)致文章內(nèi)容平鋪。
沒(méi)有經(jīng)過(guò)嚴(yán)格正確性驗(yàn)證的代碼或文章麻裁,都是耍流氓箍镜。我要開(kāi)始耍流氓了:
FFmpeg:A complete, cross-platform solution to record, convert and stream audio and video。稍微展開(kāi)就是:
- 它是功能完備的煎源,性能優(yōu)越的色迂,支持跨平臺(tái)的音視頻處理框架。
- record:音視頻錄制手销,更多地指將音視頻保存到本地文件
- convert:轉(zhuǎn)換歇僧,包括多媒體文件格式轉(zhuǎn)換,音視頻編碼轉(zhuǎn)換锋拖,視頻縮放和色彩格式轉(zhuǎn)換诈悍,音頻處理等
- stream:流化音視頻埂淮,更多指協(xié)議層的文件流,多媒體流等
下面是FFmpeg框架的分層模型:
協(xié)議層:該層處理流媒體協(xié)議的數(shù)據(jù)解析與封裝写隶,包括http,rtmp讲仰,rtsp慕趴,file等
容器層:該層處理多媒體容器的解析和封裝,包括mp4鄙陡,flv冕房,mkv等
編解碼層:該層負(fù)責(zé)音視頻編解碼,包括h264趁矾,h265耙册,mp3,aac等
原始數(shù)據(jù)層:該層負(fù)責(zé)原始音視頻數(shù)據(jù)的處理毫捣,如視頻像素格式轉(zhuǎn)換详拙,縮放,裁剪蔓同,過(guò)濾饶辙,音頻重采樣,過(guò)濾等斑粱,處理對(duì)象是pcm弃揽,yuv,rgb等原始數(shù)據(jù)则北。
設(shè)備層:負(fù)責(zé)音視頻播放及采集
看到這個(gè)是否聯(lián)想到 TCP/IP協(xié)議族的網(wǎng)絡(luò)分層模型矿微,從頂?shù)降追謩e是應(yīng)用層,傳輸層尚揣,網(wǎng)絡(luò)層涌矢,鏈路層,物理層惑艇。但兩者的設(shè)計(jì)有個(gè)很大的不同點(diǎn)蒿辙。網(wǎng)絡(luò)分層模型中,普通應(yīng)用開(kāi)發(fā)者只能在應(yīng)用層做文章滨巴;而FFmpeg分層模型相對(duì)自由靈活思灌,每層都提供了相對(duì)解耦的類庫(kù):
- livavformat 作用于協(xié)議層和容器層,依賴于libavcodec恭取。
- libavcodec 作用于編解碼層泰偿。
- ibswscale,libswresample蜈垮,libavfilter作用于原始數(shù)據(jù)層耗跛。
- libavdevice 作用于設(shè)備層裕照。
- livavutil 是基礎(chǔ)公共模塊,上面各個(gè)類庫(kù)都會(huì)依賴于它调塌。
每個(gè)模塊的詳細(xì)說(shuō)明晋南,可查看官方文檔的 Libraries Documentation ,其中的常用的模塊是livavutil羔砾,libavcodec及l(fā)ibavformat负间。常規(guī)的多媒體開(kāi)發(fā)(不一定基于FFmpeg)都能映射到上面的FFmpeg分層模型中:如
- 單看左邊,一路下來(lái)就是播放器的實(shí)現(xiàn)流程姜凄。
- 單看右邊政溃,一路上去就是直播主播端的推流流程。
- 若是不涉及改變編碼的文件格式轉(zhuǎn)換态秧,只在容器層就可實(shí)現(xiàn)董虱。
- 若只想給已有的播放器,添加更多的解碼格式申鱼,只需使用到其中的編解碼層愤诱。
2、編譯
使用FFmpeg API的方式開(kāi)發(fā)润讥,編譯源碼是必然的转锈。首先從官網(wǎng)下載最新的穩(wěn)定版本(當(dāng)前是4.1),切記不要使用主干的最新代碼楚殿。原因有二:其一撮慨,主干代碼經(jīng)常更新,有不穩(wěn)定的可能脆粥,其二砌溺,使用固定版本,方便交流和定位問(wèn)題变隔。
FFmpeg編譯是我們首先需要面對(duì)的難題规伐,基本分為下面幾種情況:
- 通過(guò)編譯FFmpeg源碼的方式,安裝FFmpeg匣缘。
- 將FFmpeg移植到移動(dòng)平臺(tái)(Android和iOS)的交叉編譯猖闪。
- 將第三方類庫(kù)集成到FFmpeg,如集成x264肌厨,fdk-aac培慌。
- 本機(jī)編譯相關(guān)類庫(kù),及在本機(jī)環(huán)境使用FFmpeg API柑爸。
經(jīng)常能看到描述FFmpeg編譯的文章吵护,其下會(huì)有不少讀者抱怨,我完全按你的編譯配置及編譯腳本執(zhí)行,就是編譯失敗馅而,或者執(zhí)行失敗祥诽。喂蝦米!大體原因不外:
- 編譯環(huán)境是否匹配瓮恭,包括操作系統(tǒng)雄坪,預(yù)安裝的編譯工具及特定平臺(tái)的編譯配置⊥捅模可參考官方編譯說(shuō)明诸衔。
- FFmpeg版本是否一致。不同版本支持的配置項(xiàng)也有可能不一樣颇玷。
- 需求是否一樣。FFmpeg是支持裁剪編譯的就缆。作者的編譯選項(xiàng)可能未包含你的需要的功能帖渠。
接下來(lái),來(lái)看一個(gè)我在mac平臺(tái)上編譯FFmpeg 4.1的腳本竭宰,主要用于在mac上調(diào)試運(yùn)行FFmpeg的開(kāi)發(fā)示例空郊。
COMMON_OPTIONS=" \
--disable-doc \
--disable-programs \
--disable-everything \
--disable-avdevice \
--disable-postproc \
--disable-avfilter \
--disable-symver \
--disable-avresample \
--disable-audiotoolbox \
--disable-videotoolbox \
--disable-appkit \
--disable-bzlib \
--disable-iconv \
--disable-securetransport \
--disable-avfoundation \
--disable-coreimage \
--disable-sdl2 \
--disable-zlib \
--enable-decoder=h264 \
--enable-decoder=aac \
--enable-demuxer=mov \
--enable-demuxer=flv \
--enable-demuxer=rtsp \
--enable-demuxer=mp3 \
--enable-demuxer=h264 \
--enable-demuxer=aac \
--enable-muxer=mp4 \
--enable-muxer=flv \
--enable-muxer=h264 \
--enable-muxer=adts \
--enable-muxer=mp3 \
--enable-protocol=rtmp \
--enable-protocol=file \
--enable-bsf=aac_adtstoasc \
--enable-bsf=h264_mp4toannexb \
--enable-bsf=hevc_mp4toannexb \
" && \
./configure \
--prefix='out' \
${COMMON_OPTIONS} \
&& \
make -j4 && make install && make clean
將其放置于FFmpeg源碼根目錄下(比如命名為build_pc.sh)。執(zhí)行如下終端命令:
chmod a+x ./build_pc.sh //使build_pc.sh為可執(zhí)行文件
./build_pc.sh //執(zhí)行構(gòu)建腳本
稍等片刻切揭,順利的話狞甚,便可在源碼根目錄下看到輸出文件out,其下包括頭文件目錄include廓旬,類庫(kù)文件目錄lib及示例文件目錄share哼审。仔細(xì)觀察,可發(fā)現(xiàn)其中也就四條命令:
- configure 編譯裁剪配置
- make 執(zhí)行編譯
- make install 執(zhí)行安裝孕豹,就是將相關(guān)編譯好的程序涩盾,類庫(kù)以及頭文件示例代碼拷貝到
—prefix
指定的目錄(上例為out目錄) - make clean 清理編譯過(guò)程產(chǎn)生的臨時(shí)文件
FFmpeg是個(gè)龐大的類庫(kù),最好根據(jù)我們的需求励背,進(jìn)行編譯選項(xiàng)配置春霍。執(zhí)行如下命令,可查看到所有配置項(xiàng)叶眉。
./configure --help
其中常用的配置如下:
Help options:
--list-decoders show all available decoders
--list-encoders show all available encoders
--list-hwaccels show all available hardware accelerators
--list-demuxers show all available demuxers
--list-muxers show all available muxers
--list-parsers show all available parsers
--list-protocols show all available protocols
--list-bsfs show all available bitstream filters
--list-indevs show all available input devices
--list-outdevs show all available output devices
--list-filters show all available filters
Standard options:
--prefix=PREFIX install in PREFIX [/usr/local]
Configuration options:
--disable-static do not build static libraries [no]
--enable-shared build shared libraries [no]
Program options:
--disable-programs do not build command line programs
--disable-ffmpeg disable ffmpeg build
--disable-ffplay disable ffplay build
--disable-ffprobe disable ffprobe build
Documentation options:
--disable-doc do not build documentation
Component options:
--disable-avdevice disable libavdevice build
--disable-avcodec disable libavcodec build
--disable-avformat disable libavformat build
--disable-swresample disable libswresample build
--disable-swscale disable libswscale build
--disable-postproc disable libpostproc build
--disable-avfilter disable libavfilter build
--enable-avresample enable libavresample build (deprecated) [no]
Individual component options:
--disable-everything disable all components listed below
--enable-encoder=NAME enable encoder NAME
--enable-decoder=NAME enable decoder NAME
--enable-muxer=NAME enable muxer NAME
--enable-demuxer=NAME enable demuxer NAME
--enable-parser=NAME enable parser NAME
--enable-bsf=NAME enable bitstream filter NAME
--enable-protocol=NAME enable protocol NAME
--enable-filter=NAME enable filter NAME
External library support:
--enable-libfdk-aac enable AAC de/encoding via libfdk-aac [no]
--enable-libmp3lame enable MP3 encoding via libmp3lame [no]
--enable-libx264 enable H.264 encoding via x264 [no]
--enable-libx265 enable HEVC encoding via x265 [no]
Toolchain options:
--arch=ARCH select architecture []
--cpu=CPU select the minimum required CPU (affects
instruction selection, may crash on older CPUs)
--cross-prefix=PREFIX use PREFIX for compilation tools []
--sysroot=PATH root of cross-build tree
--sysinclude=PATH location of cross-build system headers
--target-os=OS compiler targets OS []
Optimization options (experts only):
--disable-asm disable all assembly optimizations
--disable-neon disable NEON optimizations
通常使用“禁大開(kāi)小”的配置策略址儒。如下是常見(jiàn)的禁止配置:
--disable-doc // 禁止輸出文檔
--disable-programs // 禁止編譯執(zhí)行程序 ffmpeg ffprobe ffplay
--disable-everything // 禁止所有的encoder,decoder衅疙,muxer莲趣,demuxer,parser炼蛤,bsf妖爷,protocol及filter
--disable-avdevice // 禁止相關(guān)模塊 這些模塊在上面的分層模型提及
--disable-postproc
--disable-avfilter
根據(jù)實(shí)際的需求,再開(kāi)啟相關(guān)配置:
--enable-decoder=h264
--enable-decoder=aac
--enable-demuxer=mov
--enable-demuxer=flv
--enable-protocol=file
默認(rèn)是只編譯靜態(tài)庫(kù),可通過(guò) --enable-shared
開(kāi)啟動(dòng)態(tài)庫(kù)的編譯絮识。默認(rèn)會(huì)將編譯輸出到 /usr/local目錄绿聘,可配置--prefix=輸出目錄
來(lái)修改。Toolchain options通用用于交叉編譯次舌,可參考ExoPlayer集成FFmpeg熄攘。
在執(zhí)行編譯腳本時(shí),最開(kāi)始的輸出日志是最關(guān)鍵的彼念,可以看出是否符合你的預(yù)期:
install prefix out
source path .
C compiler gcc
C library
ARCH x86 (generic)
...
static yes
shared no
...
External libraries:
External libraries providing hardware acceleration:
Libraries:
avcodec avformat avutil swresample swscale
Programs:
Enabled decoders:
aac h264
Enabled encoders:
Enabled hwaccels:
Enabled parsers:
mpegaudio
Enabled demuxers:
aac flv mov mpegts rtsp
asf h264 mp3 rm
Enabled muxers:
adts flv h264 mov mp3 mp4
Enabled protocols:
file http rtmp rtp tcp udp
Enabled filters:
Enabled bsfs:
aac_adtstoasc h264_mp4toannexb hevc_mp4toannexb null
Enabled indevs:
Enabled outdevs:
以上就是FFmpeg編譯的基本脈絡(luò)挪圾。若你能一次編譯成功,且能在項(xiàng)目中成功運(yùn)行的逐沙,那你可以去買彩票了哲思。通常或多或少有平臺(tái)相關(guān)的問(wèn)題吩案,先查找官方編譯說(shuō)明棚赔,未果再google一下。
3徘郭、小試牛刀
了解了FFmpeg的概貌及編譯后靠益,接下來(lái)就可使用相關(guān)的API。官方API文檔残揉,及官方示例是學(xué)習(xí)FFmpeg API的最佳資料胧后。以此為基點(diǎn),慢慢張開(kāi)抱环,若有疑問(wèn)壳快,可查看源碼,亦可從網(wǎng)上需求答案镇草∩髁辏可用下面示例驗(yàn)證我們編譯的類庫(kù)包括哪些組件:
// 輸出版本信息加酵,編譯配置等
std::cout << "version:" << av_version_info() << " avformat:" << avformat_version() << " avcodec:"
<< avcodec_version() << " avutil:" << avutil_version() << std::endl;
std::cout << "license:" << avformat_license() << std::endl;
std::cout << "configuration:" << avformat_configuration() << std::endl;
void *opaque = NULL;
// 獲取所有協(xié)議
std::cout << "=== input protocols ===" << std::endl;
const char *protocol = avio_enum_protocols(&opaque, 0);
while (protocol!=NULL){
std::cout << protocol << std::endl;
protocol = avio_enum_protocols(&opaque, 0);
}
std::cout << "=== output protocols ===" << std::endl;
protocol = avio_enum_protocols(&opaque, 1);
while (protocol!=NULL){
std::cout << protocol << std::endl;
protocol = avio_enum_protocols(&opaque, 1);
}
// 獲取所有的解封裝器
std::cout << "=== demuxer ===" << std::endl;
const AVInputFormat *inputFormat = av_demuxer_iterate(&opaque);
while (inputFormat != NULL) {
std::cout << inputFormat->name << std::endl;
inputFormat = av_demuxer_iterate(&opaque);
}
// 獲取所有封裝器
std::cout << "=== muxer ===" << std::endl;
opaque = NULL;
const AVOutputFormat *outputFormat = av_muxer_iterate(&opaque);
while (outputFormat != NULL) {
std::cout << outputFormat->name << std::endl;
outputFormat = av_muxer_iterate(&opaque);
}
// 獲取所有編碼器
std::cout << "=== encoder ===" << std::endl;
opaque = NULL;
const AVCodec *avCodec = av_codec_iterate(&opaque);
while (avCodec != NULL) {
if(av_codec_is_encoder(avCodec)){
std::cout << avCodec->name << std::endl;
}
avCodec = av_codec_iterate(&opaque);
}
// 獲取所有編碼器
std::cout << "=== decoder ===" << std::endl;
opaque = NULL;
avCodec = av_codec_iterate(&opaque);
while (avCodec != NULL) {
if(av_codec_is_decoder(avCodec)){
std::cout << avCodec->name << std::endl;
}
avCodec = av_codec_iterate(&opaque);
}
// 獲取所有bsf
std::cout << "=== bsf ===" << std::endl;
opaque = NULL;
const AVBitStreamFilter *bsf = av_bsf_iterate(&opaque);
while (bsf != NULL) {
std::cout << bsf->name << std::endl;
bsf = av_bsf_iterate(&opaque);
}
FFmpeg使用C語(yǔ)言編寫(xiě),其API是一些核心函數(shù)和關(guān)鍵結(jié)構(gòu)體的集合。組織方式相對(duì)松散刽辙,新手容易迷路信柿。再細(xì)看精盅,發(fā)現(xiàn)其是基于對(duì)象(結(jié)構(gòu)體)及上下文的方式來(lái)貫穿多媒體處理的會(huì)話周期胳施。在官方示例中能發(fā)現(xiàn)各個(gè)應(yīng)用場(chǎng)景的使用基本套路。
后續(xù)文章以分享這些示例的學(xué)習(xí)筆記為主羽嫡,與君共勉本姥。。