1、概述
与FFmpeg初识,大约是在六年前。那时公司要求做这么一个功能:运营编辑人员通过管理后台上传本地视频文件或粘贴其他视频网站的详情页地址,获取到视频后,通过FFmpeg来转码为各种清晰度的视频。那时刚毕业不久,只略懂PHP和JAVA,音视频的知识更是一穷二白,只能通过JAVA调用系统命令行的方式使用FFmpeg,最终因为性能等问题,项目流产了。再次相遇是两年前,为提升ExoPlayer播放器的兼容性,为其添加视频软解功能。这次打开FFmpeg的姿势稍微帅一点,会使用FFmpeg API来做转码。恍恍惚惚这么多年,一直都是FFmpeg的看客,只在门口晃悠。
近来想稍微系统地学习一番FFmpeg,发现国内关于FFmpeg的相关资料甚少。可参考的有:
- 官方文档:这里能了解到最新FFmpeg动态
- 雷神的博客:相信国内大多数音视频开发者或多或少看过他的博文,运行过他的代码。雷神已故,甚是可惜。FFmpeg迭代很快,其中不少API使用已经过时,希望有更多的人延续雷神的分享精神。
- 《FFmpeg从入门到精通》:本书侧重于命令行的使用,API甚少。作者应该也是大神,只是过分惜墨,导致文章内容平铺。
没有经过严格正确性验证的代码或文章,都是耍流氓。我要开始耍流氓了:
FFmpeg:A complete, cross-platform solution to record, convert and stream audio and video。稍微展开就是:
- 它是功能完备的,性能优越的,支持跨平台的音视频处理框架。
- record:音视频录制,更多地指将音视频保存到本地文件
- convert:转换,包括多媒体文件格式转换,音视频编码转换,视频缩放和色彩格式转换,音频处理等
- stream:流化音视频,更多指协议层的文件流,多媒体流等
下面是FFmpeg框架的分层模型:
协议层:该层处理流媒体协议的数据解析与封装,包括http,rtmp,rtsp,file等
容器层:该层处理多媒体容器的解析和封装,包括mp4,flv,mkv等
编解码层:该层负责音视频编解码,包括h264,h265,mp3,aac等
原始数据层:该层负责原始音视频数据的处理,如视频像素格式转换,缩放,裁剪,过滤,音频重采样,过滤等,处理对象是pcm,yuv,rgb等原始数据。
设备层:负责音视频播放及采集
看到这个是否联想到 TCP/IP协议族的网络分层模型,从顶到底分别是应用层,传输层,网络层,链路层,物理层。但两者的设计有个很大的不同点。网络分层模型中,普通应用开发者只能在应用层做文章;而FFmpeg分层模型相对自由灵活,每层都提供了相对解耦的类库:
- livavformat 作用于协议层和容器层,依赖于libavcodec。
- libavcodec 作用于编解码层。
- ibswscale,libswresample,libavfilter作用于原始数据层。
- libavdevice 作用于设备层。
- livavutil 是基础公共模块,上面各个类库都会依赖于它。
每个模块的详细说明,可查看官方文档的 Libraries Documentation ,其中的常用的模块是livavutil,libavcodec及libavformat。常规的多媒体开发(不一定基于FFmpeg)都能映射到上面的FFmpeg分层模型中:如
- 单看左边,一路下来就是播放器的实现流程。
- 单看右边,一路上去就是直播主播端的推流流程。
- 若是不涉及改变编码的文件格式转换,只在容器层就可实现。
- 若只想给已有的播放器,添加更多的解码格式,只需使用到其中的编解码层。
2、编译
使用FFmpeg API的方式开发,编译源码是必然的。首先从官网下载最新的稳定版本(当前是4.1),切记不要使用主干的最新代码。原因有二:其一,主干代码经常更新,有不稳定的可能,其二,使用固定版本,方便交流和定位问题。
FFmpeg编译是我们首先需要面对的难题,基本分为下面几种情况:
- 通过编译FFmpeg源码的方式,安装FFmpeg。
- 将FFmpeg移植到移动平台(Android和iOS)的交叉编译。
- 将第三方类库集成到FFmpeg,如集成x264,fdk-aac。
- 本机编译相关类库,及在本机环境使用FFmpeg API。
经常能看到描述FFmpeg编译的文章,其下会有不少读者抱怨,我完全按你的编译配置及编译脚本执行,就是编译失败,或者执行失败。喂虾米!大体原因不外:
- 编译环境是否匹配,包括操作系统,预安装的编译工具及特定平台的编译配置。可参考官方编译说明。
- FFmpeg版本是否一致。不同版本支持的配置项也有可能不一样。
- 需求是否一样。FFmpeg是支持裁剪编译的。作者的编译选项可能未包含你的需要的功能。
接下来,来看一个我在mac平台上编译FFmpeg 4.1的脚本,主要用于在mac上调试运行FFmpeg的开发示例。
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)。执行如下终端命令:
chmod a+x ./build_pc.sh //使build_pc.sh为可执行文件
./build_pc.sh //执行构建脚本
稍等片刻,顺利的话,便可在源码根目录下看到输出文件out,其下包括头文件目录include,类库文件目录lib及示例文件目录share。仔细观察,可发现其中也就四条命令:
- configure 编译裁剪配置
- make 执行编译
- make install 执行安装,就是将相关编译好的程序,类库以及头文件示例代码拷贝到
—prefix
指定的目录(上例为out目录) - make clean 清理编译过程产生的临时文件
FFmpeg是个庞大的类库,最好根据我们的需求,进行编译选项配置。执行如下命令,可查看到所有配置项。
./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
通常使用“禁大开小”的配置策略。如下是常见的禁止配置:
--disable-doc // 禁止输出文档
--disable-programs // 禁止编译执行程序 ffmpeg ffprobe ffplay
--disable-everything // 禁止所有的encoder,decoder,muxer,demuxer,parser,bsf,protocol及filter
--disable-avdevice // 禁止相关模块 这些模块在上面的分层模型提及
--disable-postproc
--disable-avfilter
根据实际的需求,再开启相关配置:
--enable-decoder=h264
--enable-decoder=aac
--enable-demuxer=mov
--enable-demuxer=flv
--enable-protocol=file
默认是只编译静态库,可通过 --enable-shared
开启动态库的编译。默认会将编译输出到 /usr/local目录,可配置--prefix=输出目录
来修改。Toolchain options通用用于交叉编译,可参考ExoPlayer集成FFmpeg。
在执行编译脚本时,最开始的输出日志是最关键的,可以看出是否符合你的预期:
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编译的基本脉络。若你能一次编译成功,且能在项目中成功运行的,那你可以去买彩票了。通常或多或少有平台相关的问题,先查找官方编译说明,未果再google一下。
3、小试牛刀
了解了FFmpeg的概貌及编译后,接下来就可使用相关的API。官方API文档,及官方示例是学习FFmpeg API的最佳资料。以此为基点,慢慢张开,若有疑问,可查看源码,亦可从网上需求答案。可用下面示例验证我们编译的类库包括哪些组件:
// 输出版本信息,编译配置等
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;
// 获取所有协议
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语言编写,其API是一些核心函数和关键结构体的集合。组织方式相对松散,新手容易迷路。再细看,发现其是基于对象(结构体)及上下文的方式来贯穿多媒体处理的会话周期。在官方示例中能发现各个应用场景的使用基本套路。
后续文章以分享这些示例的学习笔记为主,与君共勉。。