FFmpeg从入门到出家(FFmpeg简析)

 FFmpeg简析

FFmpeg从无到有,发展至今,功能日益强大,代码也越来越多,很多初学者都被其众多的源文件、庞大的结构体和复杂的算法打消了继续学习的念头。本章节将从总体对FFmpeg进行简单的解析,教您如何阅读FFmpeg源码。

2.1 总体说明

FFmpeg包含如下类库:

libavformat - 用于各种音视频封装格式的生成和解析,包括获取解码所需信息、读取音视频数据等功能。各种流媒体协议代码(如rtmpproto.c等)以及音视频格式的(解)复用代码(如flvdec.c、flvenc.c等)都位于该目录下。

libavcodec - 音视频各种格式的编解码。各种格式的编解码代码(如aacenc.c、aacdec.c等)都位于该目录下。

libavutil - 包含一些公共的工具函数的使用库,包括算数运算,字符操作等。

libswscale - 提供原始视频的比例缩放、色彩映射转换、图像颜色空间或格式转换的功能。

libswresample - 提供音频重采样,采样格式转换和混合等功能。

libavfilter - 各种音视频滤波器。

libpostproc - 用于后期效果处理,如图像的去块效应等。

libavdevice - 用于硬件的音视频采集、加速和显示。

如果您之前没有阅读FFmpeg代码的经验,建议优先阅读libavformat、libavcodec以及libavutil下面的代码,它们提供了音视频开发的最基本功能,应用范围也是最广的。

2.2 常用结构

FFmpeg里面最常用的数据结构,按功能可大致分为以下几类(以下代码行数,以branch: origin/release/3.4为准):

1. 封装格式

◦AVFormatContext - 描述了媒体文件的构成及基本信息,是统领全局的基本结构体,贯穿程序始终,很多函数都要用它作为参数;

◦AVInputFormat - 解复用器对象,每种作为输入的封装格式(例如FLV、MP4、TS等)对应一个该结构体,如libavformat/flvdec.c的ff_flv_demuxer;

◦AVOutputFormat - 复用器对象,每种作为输出的封装格式(例如FLV, MP4、TS等)对应一个该结构体,如libavformat/flvenc.c的ff_flv_muxer;

◦AVStream - 用于描述一个视频/音频流的相关数据信息。

2.编解码

◦AVCodecContext - 描述编解码器上下文的数据结构,包含了众多编解码器需要的参数信息;

◦AVCodec - 编解码器对象,每种编解码格式(例如H.264、AAC等)对应一个该结构体,如libavcodec/aacdec.c的ff_aac_decoder。每个AVCodecContext中含有一个AVCodec;

◦AVCodecParameters - 编解码参数,每个AVStream中都含有一个AVCodecParameters,用来存放当前流的编解码参数。

3. 网络协议

◦AVIOContext - 管理输入输出数据的结构体;

◦URLProtocol - 描述了音视频数据传输所使用的协议,每种传输协议(例如HTTP、RTMP)等,都会对应一个URLProtocol结构,如libavformat/http.c中的ff_http_protocol;

◦URLContext - 封装了协议对象及协议操作对象。

[if !supportLists]4. [endif]数据存放

◦AVPacket - 存放编码后、解码前的压缩数据,即ES数据;

◦AVFrame - 存放编码前、解码后的原始数据,如YUV格式的视频数据或PCM格式的音频数据等;

上述结构体的关系图如下所示(箭头表示派生出):

FFmpeg从入门到出家(FFmpeg简析)_第1张图片
图2. FFmpeg结构体关系图

2.3 代码结构

下面这段代码完成了读取媒体文件中音视频数据的基本功能,本节以此为例,分析FFmpeg内部代码的调用逻辑。

char *url = "http://192.168.1.105/test.flv";

AVPacket pkt;

int ret = 0;

//注册复用器、编码器等

av_register_all();

avformat_network_init();

//打开文件

AVFormatContext *fmtCtx = avformat_alloc_context();

ret = avformat_open_input(&fmtCtx, url, NULL, NULL);

ret = avformat_find_stream_info(fmtCtx, NULL);

//读取音视频数据

while(ret >= 0)

{

  ret = av_read_frame(s, &pkt);

}

2.3.1 注册

av_register_all函数的作用是注册一系列的(解)复用器、编/解码器等。它在所有基于FFmpeg的应用程序中几乎都是第一个被调用的,只有调用了该函数,才能使用复用器、编码器等。

static void register_all(void)

{

avcodec_register_all();


    /* (de)muxers */

    ……

    REGISTER_MUXDEMUX(FLV,              flv);

    ……

}

REGISTER_MUXDEMUX实际上调用的是av_register_input_format和av_register_output_format,通过这两个方法,将(解)复用器分别添加到了全局变量first_iformat与first_oformat链表的最后位置。

编/解码其注册过程相同,此处不再赘述。

2.3.2 文件打开

FFmpeg读取媒体数据的过程始于avformat_open_input,该方法中完成了媒体文件的打开和格式探测的功能。但FFmpeg是如何找到正确的流媒体协议和解复用器呢?可以看到avformat_open_input方法中调用了init_input函数,在这里面完成了查找流媒体协议和解复用器的工作。

static intinit_input(AVFormatContext *s, const char *filename,

AVDictionary **options)

{

int ret;

    ……

    if ((ret = s->io_open(s, &s->pb, filename, AVIO_FLAG_READ | s->avio_flags, options)) < 0)

        return ret;


    if (s->iformat)

        return 0;

    return av_probe_input_buffer2(s->pb, &s->iformat, filename,

                                 s, 0, s->format_probesize);

}

[if !supportLists]1. [endif]s->io_open实际上调用的就是io_open_default,它最终调用到url_find_protocol方法。

static conststructURLProtocol *url_find_protocol(const char *filename)

{

constURLProtocol **protocols;

    ……

    protocols = ffurl_get_protocols(NULL, NULL);

    if (!protocols)

        return NULL;

    for (i = 0; protocols[i]; i++) {

constURLProtocol *up = protocols[i];

        if (!strcmp(proto_str, up->name)) {

av_freep(&protocols);

            return up;

        }

        if (up->flags & URL_PROTOCOL_FLAG_NESTED_SCHEME &&

!strcmp(proto_nested, up->name)) {

av_freep(&protocols);

            return up;

        }

    }

av_freep(&protocols);


    return NULL;

}

ffurl_get_protocols可以得到当前编译的FFmpeg支持的所有流媒体协议,通过url的scheme和protocol->name相比较,得到正确的protocol。例如本例中URLProtocol最终指向了libavformat/http.c中的ff_http_protocol。

[if !supportLists]1. [endif]av_probe_input_buffer2最终调用到av_probe_input_format3,该方法遍历所有的解复用器,即first_iformat链表中的所有节点,调用它们的read_probe()函数计算匹配得分,函数最终返回计算找到的最匹配的解复用器。本例中AVInputFormat最终指向了libavformat/flvdec.c中的ff_flv_demuxer。

2.3.3 数据读取

av_read_frame作用是读取媒体数据中的每个音视频帧,该方法中最关键的地方就是调用了AVInputFormat的read_packet()方法。AVInputFormat的read_packet()是一个函数指针,指向当前的AVInputFormat的读取数据的函数。在本例中,AVInputFormat为ff_flv_demuxer,也就是说read_packet最终指向了flv_read_packet。

你可能感兴趣的:(FFmpeg从入门到出家(FFmpeg简析))