FFmpeg入门:概述与编译

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框架的分层模型:

FFmpeg入门:概述与编译_第1张图片
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编译是我们首先需要面对的难题,基本分为下面几种情况:

  1. 通过编译FFmpeg源码的方式,安装FFmpeg。
  2. 将FFmpeg移植到移动平台(Android和iOS)的交叉编译。
  3. 将第三方类库集成到FFmpeg,如集成x264,fdk-aac。
  4. 本机编译相关类库,及在本机环境使用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是一些核心函数和关键结构体的集合。组织方式相对松散,新手容易迷路。再细看,发现其是基于对象(结构体)及上下文的方式来贯穿多媒体处理的会话周期。在官方示例中能发现各个应用场景的使用基本套路。

后续文章以分享这些示例的学习笔记为主,与君共勉。。

你可能感兴趣的:(FFmpeg入门:概述与编译)