ffmpeg源码学习

2021-07-05 19:58:17 浏览数 (1)

1. 整体介绍

1.1整体架构

1.2 8个常用库

• AVUtil:核心工具库,下面的许多其他模块都会依赖该库做一些基本的音视频处理操作。

AVFormat:文件格式和协议库,该模块是最重要的模块之一,封装了Protocol层和Demuxer、Muxer层,使得协议和格式对于开发者来说是透明的。

AVCodec:编解码库,封装了Codec层,但是有一些Codec是具备自己的License的,FFmpeg是不会默认添加像libx264、FDK-AAC等库的,但是FFmpeg就像一个平台一样,可以将其他的第三方的Codec以插件的方式添加进来,然后为开发者提供统一的接口。

• AVFilter:音视频滤镜库,该模块提供了包括音频特效和视频特效的处理,在使用FFmpeg的API进行编解码的过程中,直接使用该模块为音视频数据做特效处理是非常方便同时也非常高效的一种方式。

AVDevice:输入输出设备库,比如,需要编译出播放声音或者视频的工具ffplay,就需要确保该模块是打开的,同时也需要SDL的预先编译,因为该设备模块播放声音与播放视频使用的都是SDL库。

SwrRessample:该模块可用于音频重采样,可以对数字音频进行声道数、数据格式、采样率等多种基本信息的转换。

SWScale:该模块是将图像进行格式转换的模块,比如,可以将YUV的数据转换为RGB的数据,缩放尺寸由1280*720变为800*480。

PostProc:该模块可用于进行后期处理,当我们使用AVFilter的时候需要打开该模块的开关,因为Filter中会使用到该模块的一些基础函数

1.3解封装

封装格式相关API

◼ avformat_alloc_context();负责申请一个AVFormatContext结构的内存,并进行简单初始化

◼ avformat_free_context();释放该结构里的所有东西以及该结构本身

◼ avformat_close_input();关闭解复用器。关闭后就不再需要使用avformat_free_context 进行释放。

◼ avformat_open_input();打开输入视频文件

◼ avformat_find_stream_info():获取音视频文件信息

◼ av_read_frame(); 读取音视频包

◼ avformat_seek_file(); 定位文件

◼ av_seek_frame():定位文件

区分不同的码流

◼ AVMEDIA_TYPE_VIDEO视频流

video_index = av_find_best_stream(ic, AVMEDIA_TYPE_VIDEO, -1,-1, NULL, 0)

◼ AVMEDIA_TYPE_AUDIO音频流

audio_index = av_find_best_stream(ic, AVMEDIA_TYPE_AUDIO, -1,-1, NULL, 0)

AVPacket 里面也有一个index的字段

avformat_open_input和avformat_find_stream_info分别用于打开一个流和分析流信息。

在初始信息不足的情况下(比如FLV和H264文件),avformat_find_stream_info接口需要在内部调用read_frame_internal接口读取流数据(音视频帧),然后再分析后,设置核心数据结构AVFormatContext。

由于需要读取数据包,avformat_find_stream_info接口会带来很大的延迟

1.4编码

关键函数说明:

avcodec_find_encoder:根据指定的AVCodecID查找注册的编码器。

avcodec_alloc_context3:为AVCodecContext分配内存。

avcodec_open2:打开编码器。

avcodec_send_frame:将AVFrame⾮压缩数据给编码器。

avcodec_receive_packet:获取到编码后的AVPacket数据,收到的packet需要⾃⼰释放内存。

av_frame_get_buffer: 为⾳频或视频帧分配新的buffer。在调⽤这个函数之前,必须在AVFame上设

置好以下属性:format(视频为像素格式,⾳频为样本格式)、nb_samples(样本个数,针对⾳频)、

channel_layout(通道类型,针对⾳频)、width/height(宽⾼,针对视频)。

av_frame_make_writable:确保AVFrame是可写的,使⽤av_frame_make_writable()的问题是,在

最坏的情况下,它会在您使⽤encode再次更改整个输⼊frame之前复制它. 如果frame不可写,

av_frame_make_writable()将分配新的缓冲区,并复制这个输⼊input frame数据,避免和编码器需

要缓存该帧时造成冲突。

av_samples_fill_arrays 填充⾳频帧

对于 flush encoder的操作:

编码器通常的冲洗⽅法:调⽤⼀次 avcodec_send_frame(NULL)(返回成功),然后不停调⽤avcodec_receive_packet() 返回 AVERROR_EOF 这⼀次是没有有效数据的,仅仅获取到⼀

个结束标志

从本地读取YUV数据编码为h264格式的数据,然后再存⼊到本地,编码后的数据有带startcode。

与FFmpeg 示例⾳频编码的流程基本⼀致。

函数说明:

avcodec_find_encoder_by_name:根据指定的编码器名称查找注册的编码器。

avcodec_alloc_context3:为AVCodecContext分配内存。

avcodec_open2:打开编解码器。

avcodec_send_frame:将AVFrame⾮压缩数据给编码器。。

avcodec_receive_packet:获取到编码后的AVPacket数据。

av_frame_get_buffer: 为⾳频或视频数据分配新的buffer。在调⽤这个函数之前,必须在AVFame上设

置好以下属性:format(视频为像素格式,⾳频为样本格式)、nb_samples(样本个数,针对⾳频)、

channel_layout(通道类型,针对⾳频)、width/height(宽⾼,针对视频)。

av_frame_make_writable:确保AVFrame是可写的,尽可能避免数据的复制。

如果AVFrame不是是可写的,将分配新的buffer和复制数据。

av_image_fill_arrays: 存储⼀帧像素数据存储到AVFrame对应的data buffer。

编码出来的h264数据可以直接使⽤ffplay播放,也可以使⽤VLC

1.5解码

Ffmpeg中id相同的编解码器 用链表存储,avcodec_find_decoder()查找到链表第一个就返回了,所以一般用avcodec_find_decoder_by_name()查找??

解码器相关

• avcodec_alloc_context3(): 分配解码器上下文

• avcodec_find_decoder():根据ID查找解码器

• avcodec_find_decoder_by_name():根据解码器名字

• avcodec_open2(): 打开编解码器

• avcodec_send_packet(): 发送编码数据包

• avcodec_receive_frame(): 接收解码后数据

• avcodec_free_context():释放解码器上下文,包含了avcodec_close()

• avcodec_close():关闭解码器

1.6内存模型

AVBuffer 的引用计数atomic_uint refcount

2.ffmpeg库源码解析

2.1 I/O模块

注意:这篇转载的文章比较早,写得很清晰,但是新版的ffmpeg的很多数据结构的名字已经改了。因此只能作参考。(例如ByteIOContext已经改名为AVIOContext)

1概述

ffmpeg项目的数据IO部分主要是在libavformat库中实现,某些对于内存的操作部分在libavutil库中。数据IO是基于文件格式(Format)以及文件传输协议(Protocol)的,与具体的编解码标准无关。 ffmpeg工程转码时数据IO层次关系如图所示:

对于上面的数据IO流程,具体可以用下面的例子来说明,我们从一个http服务器获取音视频数据,格式是flv的,需要通过转码后变成avi格式,然后通过udp协议进行发布。

其过程就如下所示:

1、读入http协议数据流,根据http协议获取真正的文件数据(去除无关报文信息);

2、根据flv格式对数据进行解封装;

3、读取帧进行转码操作;

4、按照目标格式avi进行封装;

5、通过udp协议发送出去。

2相关数据结构介绍

在libavformat库中与数据IO相关的数据结构主要有URLProtocol、URLContext、ByteIOContext、AVFormatContext等,各结构之间的关系如图所示。

1、URLProtocol结构

表示广义的输入文件,该结构体提供了很多的功能函数,每一种广义的输入文件(如:file、pipe、tcp、rtp等等)对应着一个URLProtocol结构,在av_register_all()中将该结构体初始化为一个链表,表头为avio.c里的URLProtocol *first_protocol = NULL;保存所有支持的输入文件协议,该结构体的定义如下:

typedef struct URLProtocol

{

const char *name;

int (*url_open)(URLContext *h, const char *url, int flags);

int (*url_read)(URLContext *h, unsigned char *buf, int size);

int (*url_write)(URLContext *h, const unsigned char *buf, int size);

int64_t (*url_seek)(URLContext *h, int64_t pos, int whence);

int (*url_close)(URLContext *h); struct URLProtocol *next;

int (*url_read_pause)(URLContext *h, int pause);

int64_t (*url_read_seek)(URLContext *h, int stream_index,int64_t timestamp, int flags);

int (*url_get_file_handle)(URLContext *h);

int priv_data_size;

const AVClass *priv_data_class;

int flags;

int (*url_check)(URLContext *h, int mask);

} URLProtocol;

注意到,URLProtocol是一个链表结构,这是为了协议的统一管理,ffmpeg项目中将所有的用到的协议都存放在一个全局变量first_protocol中,协议的注册是在av_register_all中完成的,新添加单个协议可以调用av_register_protocol2函数实现。而协议的注册就是将具体的协议对象添加至first_protocol链表的末尾。 URLProtocol在各个具体的文件协议中有一个具体的实例,如在file协议中定义为:

URLProtocol ff_file_protocol = {

name = "file",

url_open = file_open,

url_read = file_read,

url_write = file_write,

url_seek = file_seek,

url_close = file_close,

url_get_file_handle = file_get_handle,

url_check = file_check

};

2、URLContext结构

URLContext提供了与当前打开的具体的文件协议(URL)相关数据的描述,在该结构中定义了指定当前URL(即filename项)所要用到的具体的URLProtocol,即:提供了一个在URLprotocol链表中找到具体项的依据,此外还有一些其它的标志性的信息,如flags, is_streamed等。它可以看成某一种协议的载体。其结构定义如下:

typedef struct URLContext

{

const AVClass *av_class; ///< information for av_log(). Set by url_open().

struct URLProtocol *prot;

int flags;

int is_streamed; //< true if streamed (no seek possible), default = false * int max_packet_size;

void *priv_data;

char *filename; //< specified URL

int is_connected;

} URLContext;

那么ffmpeg依据什么信息初始化URLContext?然后又是如何初始化URLContext的呢?在打开一个URL时,全局函数ffurl_open会根据filename的前缀信息来确定URL所使用的具体协议,并为该协议分配好资源,再调用ffurl_connect函数打开具体协议,即调用协议的url_open,调用关系如下: int av_open_input_file(AVFormatContext **ic_ptr, const char *filename, AVInputFormat *fmt, int buf_size, AVFormatParameters *ap) int avformat_open_input(AVFormatContext **ps, const char *filename, AVInputFormat *fmt, AVDictionary **options) static int init_input(AVFormatContext *s, const char *filename) int avio_open(AVIOContext **s, const char *filename, int flags) int ffurl_open(URLContext **puc, const char *filename, int flags) int ffurl_alloc(URLContext **puc, const char *filename, int flags) static int url_alloc_for_protocol (URLContext **puc, struct URLProtocol *up, const char *filename, int flags) 浅蓝色部分的函数完成了URLContext函数的初始化,URLContext使ffmpeg外所暴露的接口是统一的,而不是对于不同的协议用不同的函数,这也是面向对象思维的体现。在此结构中还有一个值得说的是priv_data项,这是结构的一个可扩展项,具体协议可以根据需要添加相应的结构,将指针保存在这就行。 3、AVIOContext结构 AVIOContext(即:ByteIOContext)是由URLProtocol和URLContext结构扩展而来,也是ffmpeg提供给用户的接口,它将以上两种不带缓冲的读取文件抽象为带缓冲的读取和写入,为用户提供带缓冲的读取和写入操作。数据结构定义如下:

typedef struct {

unsigned char *buffer; /**< Start of the buffer. */

int buffer_size; /**< Maximum buffer size */

unsigned char *buf_ptr; /**< Current position in the buffer */

unsigned char *buf_end;

void *opaque; /关联URLContext

int (*read_packet)(void *opaque,uint8_t *buf,int buf_size);

int (*write_packet)(void *opaque,uint8_t *buf,int buf_size);

int64_t (*seek)(void *opaque,int64_t offset,int whence);

int64_t pos;

int must_flush;

int eof_reached; /**< true if eof reached */

int write_flag; /**< true if open for writing */

int max_packet_size;

unsigned long checksum;

unsigned char *checksum_ptr;

unsigned long (*update_checksum)(unsigned long checksum,const uint8_t *buf,unsigned int size);

int error;

int (*read_pause)(void *opaque,int pause)

int64_t (*read_seek)(void *opaque,int stream_index,int64_t timestamp,int flags);

int seekable;

} AVIOContext;

结构简单的为用户提供读写容易实现的四个操作,read_packet  write_packet  read_pause  read_seek,极大的方便了文件的读取,四个函数在加了缓冲机制后被中转到,URLContext指向的实际的文件协议读写函数中。 下面给出0.8版本中是如何将AVIOContext的读写操作中转到实际文件中的。 在avio_open()函数中调用了ffio_fdopen()函数完成了对AVIOContex的初始化,其调用过程如下: int avio_open(AVIOContext **s, const char *filename, int flags) ffio_fdopen(s, h); //h是URLContext指针 ffio_init_context(*s, buffer, buffer_size, h->flags & AVIO_FLAG_WRITE, h, (void*)ffurl_read,(void*)ffurl_write,(void*)ffurl_seek) 蓝色部分的函数调用完成了对AVIOContext的初始化,在初始化的过程中,将AVIOContext的read_packet、write_packet、seek分别初始化为:ffurl_read ffurl_write ffurl_seek,而这三个函数又将具体的读写操作中转为:h->prot->url_read、h->prot->url_write、h->prot->url_seek,另外两个变量初始化时也被相应的中转,如下: (*s)->read_pause = (int (*)(void *, int))h->prot->url_read_pause; (*s)->read_seek = (int64_t (*)(void *, int, int64_t, int))h->prot->url_read_seek; 所以,可以简要的描述为:AVIOContext的接口口是加了缓冲后的URLProtocol的函数接口。 在aviobuf.c中定义了一系列关于ByteIOContext这个结构体的函数,如下 put_xxx系列:

put_xxx系列:

void put_byte(ByteIOContext *s, int b);

void put_buffer(ByteIOContext *s, const unsigned char *buf, int size);

void put_le64(ByteIOContext *s, uint64_t val);

void put_be64(ByteIOContext *s, uint64_t val);

void put_le32(ByteIOContext *s, unsigned int val);

void put_be32(ByteIOContext *s, unsigned int val);

void put_le24(ByteIOContext *s, unsigned int val);

void put_be24(ByteIOContext *s, unsigned int val);

void put_le16(ByteIOContext *s, unsigned int val);

void put_be16(ByteIOContext *s, unsigned int val);

void put_tag(ByteIOContext *s, const char *tag);

get_xxx系列:

int get_buffer(ByteIOContext *s, unsigned char *buf, int size);

int get_partial_buffer(ByteIOContext *s, unsigned char *buf, int size);

int get_byte(ByteIOContext *s);

unsigned int get_le24(ByteIOContext *s);

unsigned int get_le32(ByteIOContext *s);

uint64_t get_le64(ByteIOContext *s);

unsigned int get_le16(ByteIOContext *s);

char *get_strz(ByteIOContext *s, char *buf, int maxlen);

unsigned int get_be16(ByteIOContext *s);

unsigned int get_be24(ByteIOContext *s);

unsigned int get_be32(ByteIOContext *s);

uint64_t get_be64(ByteIOContext *s);

这些put_xxx及get_xxx函数是用于从缓冲区buffer中写入或者读取若干个字节,对于读写整型数据,分别实现了大端和小端字节序的版本。而缓冲区buffer中的数据又是从何而来呢,有一个fill_buffer的函数,在fill_buffer函数中调用了ByteIOContext结构的read_packet接口。在调用put_xxx函数时,并没有直接进行真正写入操作,而是先缓存起来,直到缓存达到最大限制或调用flush_buffer函数对缓冲区进行刷新,才使用write_packet函数进行写入操作。

2.2重要结构体

https://blog.csdn.net/leixiaohua1020/article/details/11693997

FFMPEG中结构体很多。最关键的结构体可以分成以下几类:

a)        解协议(http,rtsp,rtmp,mms)

AVIOContext,URLProtocol,URLContext主要存储视音频使用的协议的类型以及状态。URLProtocol存储输入视音频使用的封装格式。每种协议都对应一个URLProtocol结构。(注意:FFMPEG中文件也被当做一种协议“file”)

b)        解封装(flv,avi,rmvb,mp4)

AVFormatContext主要存储视音频封装格式中包含的信息;AVInputFormat存储输入视音频使用的封装格式。每种视音频封装格式都对应一个AVInputFormat 结构。

c)        解码(h264,mpeg2,aac,mp3)

每个AVStream存储一个视频/音频流的相关数据;每个AVStream对应一个AVCodecContext,存储该视频/音频流使用解码方式的相关数据;每个AVCodecContext中对应一个AVCodec,包含该视频/音频对应的解码器。每种解码器都对应一个AVCodec结构。

d) 存数据

视频的话,每个结构一般是存一帧;音频可能有好几帧

解码前数据:AVPacket

解码后数据:AVFrame

他们之间的对应关系如下所示:

1)AVFrame

AVFrame是包含码流参数较多的结构体。本文将会详细分析一下该结构体里主要变量的含义和作用。

首先看一下结构体的定义(位于avcodec.h) 现在好像是在 frame.h中了?

/**该结构描述了解码的(原始)音频或视频数据。 *必须使用av_frame_alloc()(旧接口:avcodec_alloc_frame(),新接口:av_frame_alloc())分配AVFrame。请注意,这只是 *分配AVFrame本身,必须通过其他方式(见下文)管理数据的缓冲区; *必须使用av_frame_free()释放AVFrame。 * * AVFrame通常分配一次,然后多次重复使用以保存 *不同的数据(例如,单个AVFrame用于保存从一个解码器接收的帧) *av_frame_unref()再次重复使用前将释放由其持有的任何引用框架并将其重置为之前的原始清洁状态

*一个AVFrame所描述的数据通常是通过参考AVBuffer API计算。内部的buffer引用存储在AVFrame.buf /AVFrame.extended_buf。 *AVFrame将用于引用计数,当至少一个引用被set时,如果AVFrame.buf[0] != NULL, 每个单个数据至少包含一个AVFrame.buf */AVFrame.extended_buf.可能会有一个缓冲的数据,或一个单独的缓冲对每个plane, 或介于两者之间的任何东西。 *sizeof(AVFrame)不是一个public的API,因此新的成员将被添加到末尾。同样字段标记为只访问av_opt_ptr()可以重新排序 */

VFrame结构体一般用于存储原始数据(即非压缩数据,例如对视频来说是YUV,RGB,对音频来说是PCM),此外还包含了一些相关的信息。比如说,解码的时候存储了宏块类型表,QP表,运动矢量表等数据。编码的时候也存储了相关的数据。因此在使用FFMPEG进行码流分析的时候,AVFrame是一个很重要的结构体。

下面看几个主要变量的作用(在这里考虑解码的情况):

uint8_t *data[AV_NUM_DATA_POINTERS]:解码后原始数据(对视频来说是YUV,RGB,对音频来说是PCM)

int linesize[AV_NUM_DATA_POINTERS]:data中“一行”数据的大小。注意:未必等于图像的宽,一般大于图像的宽。

int width, height:视频帧宽和高(1920x1080,1280x720...)

int nb_samples:音频的一个AVFrame中可能包含多个音频帧,在此标记包含了几个

int format:解码后原始数据类型(YUV420,YUV422,RGB24...)

int key_frame:是否是关键帧

enum AVPictureType pict_type:帧类型(I,B,P...)

AVRational sample_aspect_ratio:宽高比(16:9,4:3...)

int64_t pts:显示时间戳

int coded_picture_number:编码帧序号

int display_picture_number:显示帧序号

int8_t *qscale_table:QP表

uint8_t *mbskip_table:跳过宏块表

int16_t (*motion_val[2])[2]:运动矢量表

uint32_t *mb_type:宏块类型表

short *dct_coeff:DCT系数,这个没有提取过

int8_t *ref_index[2]:运动估计参考帧列表(貌似H.264这种比较新的标准才会涉及到多参考帧)

int interlaced_frame:是否是隔行扫描

uint8_t motion_subsample_log2:一个宏块中的运动矢量采样个数,取log的

其他的变量不再一一列举,源代码中都有详细的说明。在这里重点分析一下几个需要一定的理解的变量:

1.data[]

对于packed格式的数据(例如RGB24),会存到data[0]里面。

对于planar格式的数据(例如YUV420P),则会分开成data[0],data[1],data[2]...(YUV420P中data[0]存Y,data[1]存U,data[2]存V)

具体参见:FFMPEG 实现 YUV,RGB各种图像原始数据之间的转换(swscale)

2.pict_type

包含以下类型:

enum AVPictureType {

    AV_PICTURE_TYPE_NONE = 0, ///< Undefined

    AV_PICTURE_TYPE_I,     ///< Intra

    AV_PICTURE_TYPE_P,     ///< Predicted

    AV_PICTURE_TYPE_B,     ///< Bi-dir predicted

    AV_PICTURE_TYPE_S,     ///< S(GMC)-VOP MPEG4

    AV_PICTURE_TYPE_SI,    ///< Switching Intra

    AV_PICTURE_TYPE_SP,    ///< Switching Predicted

    AV_PICTURE_TYPE_BI,    ///< BI type

};

3.sample_aspect_ratio

宽高比是一个分数,FFMPEG中用AVRational表达分数:

/**

 * rational number numerator/denominator

 */

typedef struct AVRational{

    int num; ///< numerator

    int den; ///< denominator

} AVRational;

4.qscale_table

QP表指向一块内存,里面存储的是每个宏块的QP值。宏块的标号是从左往右,一行一行的来的。每个宏块对应1个QP。

qscale_table[0]就是第1行第1列宏块的QP值;qscale_table[1]就是第1行第2列宏块的QP值;qscale_table[2]就是第1行第3列宏块的QP值。以此类推...

宏块的个数用下式计算:

注:宏块大小是16x16的。

每行宏块数:

int mb_stride = pCodecCtx->width/16 1

宏块的总数:

int mb_sum = ((pCodecCtx->height 15)>>4)*(pCodecCtx->width/16 1)

5.motion_subsample_log2

1个运动矢量所能代表的画面大小(用宽或者高表示,单位是像素),注意,这里取了log2。

代码注释中给出以下数据:

4->16x16, 3->8x8, 2-> 4x4, 1-> 2x2

即1个运动矢量代表16x16的画面的时候,该值取4;1个运动矢量代表8x8的画面的时候,该值取3...以此类推

6.motion_val

运动矢量表存储了一帧视频中的所有运动矢量。

该值的存储方式比较特别:

int16_t (*motion_val[2])[2];

为了弄清楚该值究竟是怎么存的,花了我好一阵子功夫...

注释中给了一段代码:

int mv_sample_log2= 4 - motion_subsample_log2;

int mb_width= (width 15)>>4;

int mv_stride= (mb_width << mv_sample_log2) 1;

motion_val[direction][x y*mv_stride][0->mv_x, 1->mv_y];

大概知道了该数据的结构:

1.首先分为两个列表L0和L1

2.每个列表(L0或L1)存储了一系列的MV(每个MV对应一个画面,大小由motion_subsample_log2决定)

3.每个MV分为横坐标和纵坐标(x,y)

注意,在FFMPEG中MV和MB在存储的结构上是没有什么关联的,第1个MV是屏幕上左上角画面的MV(画面的大小取决于motion_subsample_log2),第2个MV是屏幕上第1行第2列的画面的MV,以此类推。因此在一个宏块(16x16)的运动矢量很有可能如下图所示(line代表一行运动矢量的个数):

//例如8x8划分的运动矢量与宏块的关系:

//-------------------------

//|          |            |

//|mv[x]     |mv[x 1]     |

//-------------------------

//|          |           |

//|mv[x line]|mv[x line 1]|

//-------------------------

7.mb_type

宏块类型表存储了一帧视频中的所有宏块的类型。其存储方式和QP表差不多。只不过其是uint32类型的,而QP表是uint8类型的。每个宏块对应一个宏块类型变量。

宏块类型如下定义所示:

//The following defines may change, don't expect compatibility if you use them.

#define MB_TYPE_INTRA4x4   0x0001

#define MB_TYPE_INTRA16x16 0x0002 //FIXME H.264-specific

#define MB_TYPE_INTRA_PCM  0x0004 //FIXME H.264-specific

#define MB_TYPE_16x16      0x0008

#define MB_TYPE_16x8       0x0010

#define MB_TYPE_8x16       0x0020

#define MB_TYPE_8x8        0x0040

#define MB_TYPE_INTERLACED 0x0080

#define MB_TYPE_DIRECT2    0x0100 //FIXME

#define MB_TYPE_ACPRED     0x0200

#define MB_TYPE_GMC        0x0400

#define MB_TYPE_SKIP       0x0800

#define MB_TYPE_P0L0       0x1000

#define MB_TYPE_P1L0       0x2000

#define MB_TYPE_P0L1       0x4000

#define MB_TYPE_P1L1       0x8000

#define MB_TYPE_L0         (MB_TYPE_P0L0 | MB_TYPE_P1L0)

#define MB_TYPE_L1         (MB_TYPE_P0L1 | MB_TYPE_P1L1)

#define MB_TYPE_L0L1       (MB_TYPE_L0   | MB_TYPE_L1)

#define MB_TYPE_QUANT      0x00010000

#define MB_TYPE_CBP        0x00020000

//Note bits 24-31 are reserved for codec specific use (h264 ref0, mpeg1 0mv, ...)

一个宏块如果包含上述定义中的一种或两种类型,则其对应的宏块变量的对应位会被置1。 注:一个宏块可以包含好几种类型,但是有些类型是不能重复包含的,比如说一个宏块不可能既是16x16又是8x8。

8.ref_index

2) AVPacket

在此不再详述,其中AVPacket是存储压缩编码数据相关信息的结构体。本文将会详细分析一下该结构体里重要变量的含义和作用。

首先看一下结构体的定义(位于avcodec.h文件中):现在是packet.h?

在AVPacket结构体中,重要的变量有以下几个:

uint8_t *data:压缩编码的数据。

例如对于H.264来说。1个AVPacket的data通常对应一个NAL。

注意:在这里只是对应,而不是一模一样。

因此在使用FFMPEG进行视音频处理的时候,常常可以将得到的AVPacket的data数据直接写成文件,从而得到视音频的码流文件。

int size:data的大小

int64_t pts:显示时间戳

int64_t dts:解码时间戳

int stream_index:标识该AVPacket所属的视频/音频流。

这个结构体虽然比较简单,但是非常的常用。

3)AVStream

视频文件中每个视频(音频)流对应一个该结构体。

AVStream是存储每一个视频/音频流信息的结构体。本文将会分析一下该结构体里重要变量的含义和作用。

首先看一下结构体的定义(位于avformat.h文件中):

AVStream重要的变量如下所示:

int index:标识该视频/音频流

AVCodecContext *codec:指向该视频/音频流的AVCodecContext(它们是一一对应的关系)

AVRational time_base:时基。通过该值可以把PTS,DTS转化为真正的时间。FFMPEG其他结构体中也有这个字段,但是根据我的经验,只有AVStream中的time_base是可用的。PTS*time_base=真正的时间

int64_t duration:该视频/音频流长度

AVDictionary *metadata:元数据信息

AVRational avg_frame_rate:帧率(注:对视频来说,这个挺重要的)

AVPacket attached_pic:附带的图片。比如说一些MP3,AAC音频文件附带的专辑封面。

该结构体其他字段的作用目前还有待于探索。

4)AVIOContext

FFMPEG管理输入输出数据的结构体。本文将会详细分析一下该结构体里每个变量的含义和作用。

首先看一下结构体的定义(位于avio.h):

AVIOContext中有以下几个变量比较重要:

unsigned char *buffer:缓存开始位置

int buffer_size:缓存大小(默认32768)

unsigned char *buf_ptr:当前指针读取到的位置

unsigned char *buf_end:缓存结束的位置

void *opaque:URLContext结构体

在解码的情况下,buffer用于存储ffmpeg读入的数据。例如打开一个视频文件的时候,先把数据从硬盘读入buffer,然后在送给解码器用于解码。

其中opaque指向了URLContext。注意,这个结构体并不在FFMPEG提供的头文件中,而是在FFMPEG的源代码中。从FFMPEG源代码中翻出的定义如下所示:

typedef struct URLContext {

const AVClass *av_class; ///< information for av_log(). Set by url_open().

struct URLProtocol *prot;

int flags;

int is_streamed;  /**< true if streamed (no seek possible), default = false */

int max_packet_size;  /**< if non zero, the stream is packetized with this max packet size */

void *priv_data;

char *filename; /**< specified URL */

int is_connected;

AVIOInterruptCB interrupt_callback;

} URLContext;

URLContext结构体中还有一个结构体URLProtocol。注:每种协议(rtp,rtmp,file等)对应一个URLProtocol。这个结构体也不在FFMPEG提供的头文件中。从FFMPEG源代码中翻出其的定义:

typedef struct URLProtocol {

const char *name;

int (*url_open)(URLContext *h, const char *url, int flags);

int (*url_read)(URLContext *h, unsigned char *buf, int size);

int (*url_write)(URLContext *h, const unsigned char *buf, int size);

int64_t (*url_seek)(URLContext *h, int64_t pos, int whence);

int (*url_close)(URLContext *h);

struct URLProtocol *next;

int (*url_read_pause)(URLContext *h, int pause);

int64_t (*url_read_seek)(URLContext *h, int stream_index,int64_t timestamp, int flags);

int (*url_get_file_handle)(URLContext *h);

int priv_data_size;

const AVClass *priv_data_class;

int flags;

int (*url_check)(URLContext *h, int mask);

} URLProtocol;

在这个结构体中,除了一些回调函数接口之外,有一个变量const char *name,该变量存储了协议的名称。每一种输入协议都对应这样一个结构体。

比如说,文件协议中代码如下(file.c):

URLProtocol ff_file_protocol = {

    .name                = "file",

    .url_open            = file_open,

    .url_read            = file_read,

    .url_write           = file_write,

    .url_seek            = file_seek,

    .url_close           = file_close,

    .url_get_file_handle = file_get_handle,

    .url_check           = file_check,

};

libRTMP中代码如下(libRTMP.c):

URLProtocol ff_rtmp_protocol = {

    .name                = "rtmp",

    .url_open            = rtmp_open,

    .url_read            = rtmp_read,

    .url_write           = rtmp_write,

    .url_close           = rtmp_close,

    .url_read_pause      = rtmp_read_pause,

    .url_read_seek       = rtmp_read_seek,

    .url_get_file_handle = rtmp_get_file_handle,

    .priv_data_size      = sizeof(RTMP),

    .flags               = URL_PROTOCOL_FLAG_NETWORK,

};

udp协议代码如下(udp.c):

URLProtocol ff_udp_protocol = {

.name                = "udp",

.url_open            = udp_open,

    .url_read            = udp_read,

    .url_write           = udp_write,

    .url_close           = udp_close,

    .url_get_file_handle = udp_get_file_handle,

    .priv_data_size      = sizeof(UDPContext),

    .flags               = URL_PROTOCOL_FLAG_NETWORK,

};

等号右边的函数是完成具体读写功能的函数。可以看一下file协议的几个函数(其实就是读文件,写文件这样的操作)(file.c):

5)AVCodec

每种视频(音频)编解码器(例如H.264解码器)对应一个该结构体

是存储编解码器信息的结构体。本文将会详细分析一下该结构体里每个变量的含义和作用。

首先看一下结构体的定义(位于avcodec.h文件中):

下面说一下最主要的几个变量:

const char *name:编解码器的名字,比较短

const char *long_name:编解码器的名字,全称,比较长

enum AVMediaType type:指明了类型,是视频,音频,还是字幕

enum AVCodecID id:ID,不重复

const AVRational *supported_framerates:支持的帧率(仅视频)

const enum AVPixelFormat *pix_fmts:支持的像素格式(仅视频)

const int *supported_samplerates:支持的采样率(仅音频)

const enum AVSampleFormat *sample_fmts:支持的采样格式(仅音频)

const uint64_t *channel_layouts:支持的声道数(仅音频)

int priv_data_size:私有数据的大小

详细介绍几个变量:

1.enum AVMediaType type

AVMediaType定义如下:

enum AVMediaType {

    AVMEDIA_TYPE_UNKNOWN = -1,  ///< Usually treated as AVMEDIA_TYPE_DATA

AVMEDIA_TYPE_VIDEO,

AVMEDIA_TYPE_AUDIO,

    AVMEDIA_TYPE_DATA,          ///< Opaque data information usually continuous

    AVMEDIA_TYPE_SUBTITLE,

    AVMEDIA_TYPE_ATTACHMENT,    ///< Opaque data information usually sparse

    AVMEDIA_TYPE_NB

}; 2.enum AVCodecID id

AVCodecID定义如下:

enum AVCodecID {

AV_CODEC_ID_NONE,

    /* video codecs */

AV_CODEC_ID_MPEG1VIDEO,

AV_CODEC_ID_MPEG2VIDEO, ///< preferred ID for MPEG-1/2 video decoding

    AV_CODEC_ID_MPEG2VIDEO_XVMC,

    AV_CODEC_ID_H261,

    AV_CODEC_ID_H263,

    AV_CODEC_ID_RV10,

    AV_CODEC_ID_RV20,

AV_CODEC_ID_MJPEG,

AV_CODEC_ID_MJPEGB,

    AV_CODEC_ID_LJPEG,

    AV_CODEC_ID_SP5X,

    AV_CODEC_ID_JPEGLS,

    AV_CODEC_ID_MPEG4,

    AV_CODEC_ID_RAWVIDEO,

    AV_CODEC_ID_MSMPEG4V1,

    AV_CODEC_ID_MSMPEG4V2,

    AV_CODEC_ID_MSMPEG4V3,

    AV_CODEC_ID_WMV1,

    AV_CODEC_ID_WMV2,

    AV_CODEC_ID_H263P,

    AV_CODEC_ID_H263I,

    AV_CODEC_ID_FLV1,

    AV_CODEC_ID_SVQ1,

    AV_CODEC_ID_SVQ3,

    AV_CODEC_ID_DVVIDEO,

    AV_CODEC_ID_HUFFYUV,

    AV_CODEC_ID_CYUV,

    AV_CODEC_ID_H264,

    ...(代码太长,略)

}

3.const enum AVPixelFormat *pix_fmts

AVPixelFormat定义如下:

enum AVPixelFormat {

    AV_PIX_FMT_NONE = -1,

    AV_PIX_FMT_YUV420P,   ///< planar YUV 4:2:0, 12bpp, (1 Cr & Cb sample per 2x2 Y samples)

    AV_PIX_FMT_YUYV422,   ///< packed YUV 4:2:2, 16bpp, Y0 Cb Y1 Cr

    AV_PIX_FMT_RGB24,     ///< packed RGB 8:8:8, 24bpp, RGBRGB...

    AV_PIX_FMT_BGR24,     ///< packed RGB 8:8:8, 24bpp, BGRBGR...

    AV_PIX_FMT_YUV422P,   ///< planar YUV 4:2:2, 16bpp, (1 Cr & Cb sample per 2x1 Y samples)

    AV_PIX_FMT_YUV444P,   ///< planar YUV 4:4:4, 24bpp, (1 Cr & Cb sample per 1x1 Y samples)

    AV_PIX_FMT_YUV410P,   ///< planar YUV 4:1:0,  9bpp, (1 Cr & Cb sample per 4x4 Y samples)

    AV_PIX_FMT_YUV411P,   ///< planar YUV 4:1:1, 12bpp, (1 Cr & Cb sample per 4x1 Y samples)

    AV_PIX_FMT_GRAY8,     ///<        Y        ,  8bpp

    AV_PIX_FMT_MONOWHITE, ///<        Y        ,  1bpp, 0 is white, 1 is black, in each byte pixels are ordered from the msb to the lsb

    AV_PIX_FMT_MONOBLACK, ///<        Y        ,  1bpp, 0 is black, 1 is white, in each byte pixels are ordered from the msb to the lsb

    AV_PIX_FMT_PAL8,      ///< 8 bit with PIX_FMT_RGB32 palette

    AV_PIX_FMT_YUVJ420P,  ///< planar YUV 4:2:0, 12bpp, full scale (JPEG), deprecated in favor of PIX_FMT_YUV420P and setting color_range

    AV_PIX_FMT_YUVJ422P,  ///< planar YUV 4:2:2, 16bpp, full scale (JPEG), deprecated in favor of PIX_FMT_YUV422P and setting color_range

    AV_PIX_FMT_YUVJ444P,  ///< planar YUV 4:4:4, 24bpp, full scale (JPEG), deprecated in favor of PIX_FMT_YUV444P and setting color_range

    AV_PIX_FMT_XVMC_MPEG2_MC,///< XVideo Motion Acceleration via common packet passing

    AV_PIX_FMT_XVMC_MPEG2_IDCT,

...(代码太长,略)

}

4.const enum AVSampleFormat *sample_fmts

enum AVSampleFormat {

    AV_SAMPLE_FMT_NONE = -1,

    AV_SAMPLE_FMT_U8,          ///< unsigned 8 bits

    AV_SAMPLE_FMT_S16,         ///< signed 16 bits

    AV_SAMPLE_FMT_S32,         ///< signed 32 bits

    AV_SAMPLE_FMT_FLT,         ///< float

    AV_SAMPLE_FMT_DBL,         ///< double

AV_SAMPLE_FMT_U8P,         ///< unsigned 8 bits, planar

AV_SAMPLE_FMT_S16P,        ///< signed 16 bits, planar

AV_SAMPLE_FMT_S32P,        ///< signed 32 bits, planar

AV_SAMPLE_FMT_FLTP,        ///< float, planar

    AV_SAMPLE_FMT_DBLP,        ///< double, planar

    AV_SAMPLE_FMT_NB           ///< Number of sample formats. DO NOT USE if linking dynamically

};

每一个编解码器对应一个该结构体,查看一下ffmpeg的源代码,我们可以看一下H.264解码器的结构体如下所示(h264.c):

AVCodec ff_h264_decoder = {

    .name           = "h264",

    .type           = AVMEDIA_TYPE_VIDEO,

.id             = CODEC_ID_H264,

.priv_data_size = sizeof(H264Context),

    .init           = ff_h264_decode_init,

    .close          = ff_h264_decode_end,

    .decode         = decode_frame,

    .capabilities   = /*CODEC_CAP_DRAW_HORIZ_BAND |*/ CODEC_CAP_DR1 | CODEC_CAP_DELAY |

                      CODEC_CAP_SLICE_THREADS | CODEC_CAP_FRAME_THREADS,

    .flush= flush_dpb,

    .long_name = NULL_IF_CONFIG_SMALL("H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10"),

    .init_thread_copy      = ONLY_IF_THREADS_ENABLED(decode_init_thread_copy),

    .update_thread_context = ONLY_IF_THREADS_ENABLED(decode_update_thread_context),

    .profiles = NULL_IF_CONFIG_SMALL(profiles),

    .priv_class     = &h264_class,

};

JPEG2000解码器结构体(j2kdec.c)

AVCodec ff_jpeg2000_decoder = {

    .name           = "j2k",

    .type           = AVMEDIA_TYPE_VIDEO,

    .id             = CODEC_ID_JPEG2000,

    .priv_data_size = sizeof(J2kDecoderContext),

.init           = j2kdec_init,

.close          = decode_end,

    .decode         = decode_frame,

    .capabilities = CODEC_CAP_EXPERIMENTAL,

    .long_name = NULL_IF_CONFIG_SMALL("JPEG 2000"),

    .pix_fmts =

        (const enum PixelFormat[]) {PIX_FMT_GRAY8, PIX_FMT_RGB24, PIX_FMT_NONE}

};

下面简单介绍一下遍历ffmpeg中的解码器信息的方法(这些解码器以一个链表的形式存储):

1.注册所有编解码器:av_register_all();

2.声明一个AVCodec类型的指针,比如说AVCodec* first_c;

3.调用av_codec_next()函数,即可获得指向链表下一个解码器的指针,循环往复可以获得所有解码器的信息。注意,如果想要获得指向第一个解码器的指针,则需要将该函数的参数设置为NULL。

6)AVCodecContext

编解码器上下文结构体,保存了视频(音频)编解码相关信息。

是包含变量较多的结构体(感觉差不多是变量最多的结构体)。本文将会大概分析一下该结构体里每个变量的含义和作用。因为如果每个变量都分析的话,工作量太大,实在来不及。

首先看一下结构体的定义(位于avcodec.h):

在这里需要注意:AVCodecContext中很多的参数是编码的时候使用的,而不是解码的时候使用的。

其实这些参数都比较容易理解。就不多费篇幅了。在这里看一下以下几个参数:

1.codec_type

编解码器类型有以下几种:

enum AVMediaType {

    AVMEDIA_TYPE_UNKNOWN = -1,  ///< Usually treated as AVMEDIA_TYPE_DATA

    AVMEDIA_TYPE_VIDEO,

    AVMEDIA_TYPE_AUDIO,

    AVMEDIA_TYPE_DATA,          ///< Opaque data information usually continuous

    AVMEDIA_TYPE_SUBTITLE,

    AVMEDIA_TYPE_ATTACHMENT,    ///< Opaque data information usually sparse

    AVMEDIA_TYPE_NB

};

2.sample_fmt

在FFMPEG中音频采样格式有以下几种:

enum AVSampleFormat {

    AV_SAMPLE_FMT_NONE = -1,

    AV_SAMPLE_FMT_U8,          ///< unsigned 8 bits

    AV_SAMPLE_FMT_S16,         ///< signed 16 bits

    AV_SAMPLE_FMT_S32,         ///< signed 32 bits

    AV_SAMPLE_FMT_FLT,         ///< float

    AV_SAMPLE_FMT_DBL,         ///< double

    AV_SAMPLE_FMT_U8P,         ///< unsigned 8 bits, planar

    AV_SAMPLE_FMT_S16P,        ///< signed 16 bits, planar

    AV_SAMPLE_FMT_S32P,        ///< signed 32 bits, planar

    AV_SAMPLE_FMT_FLTP,        ///< float, planar

    AV_SAMPLE_FMT_DBLP,        ///< double, planar

    AV_SAMPLE_FMT_NB           ///< Number of sample formats. DO NOT USE if linking dynamically

};

3.profile

在FFMPEG中型有以下几种,可以看出AAC,MPEG2,H.264,VC-1,MPEG4都有型的概念。

#define FF_PROFILE_UNKNOWN -99

#define FF_PROFILE_RESERVED -100

#define FF_PROFILE_AAC_MAIN 0

#define FF_PROFILE_AAC_LOW  1

#define FF_PROFILE_AAC_SSR  2

#define FF_PROFILE_AAC_LTP  3

#define FF_PROFILE_AAC_HE   4

#define FF_PROFILE_AAC_HE_V2 28

#define FF_PROFILE_AAC_LD   22

#define FF_PROFILE_AAC_ELD  38

#define FF_PROFILE_DTS         20

#define FF_PROFILE_DTS_ES      30

#define FF_PROFILE_DTS_96_24   40

#define FF_PROFILE_DTS_HD_HRA  50

#define FF_PROFILE_DTS_HD_MA   60

#define FF_PROFILE_MPEG2_422    0

#define FF_PROFILE_MPEG2_HIGH   1

#define FF_PROFILE_MPEG2_SS     2

#define FF_PROFILE_MPEG2_SNR_SCALABLE  3

#define FF_PROFILE_MPEG2_MAIN   4

#define FF_PROFILE_MPEG2_SIMPLE 5

#define FF_PROFILE_H264_CONSTRAINED  (1<<9)  // 8 1; constraint_set1_flag

#define FF_PROFILE_H264_INTRA        (1<<11) // 8 3; constraint_set3_flag

#define FF_PROFILE_H264_BASELINE             66

#define FF_PROFILE_H264_CONSTRAINED_BASELINE (66|FF_PROFILE_H264_CONSTRAINED)

#define FF_PROFILE_H264_MAIN                 77

#define FF_PROFILE_H264_EXTENDED             88

#define FF_PROFILE_H264_HIGH                 100

#define FF_PROFILE_H264_HIGH_10              110

#define FF_PROFILE_H264_HIGH_10_INTRA        (110|FF_PROFILE_H264_INTRA)

#define FF_PROFILE_H264_HIGH_422             122

#define FF_PROFILE_H264_HIGH_422_INTRA       (122|FF_PROFILE_H264_INTRA)

#define FF_PROFILE_H264_HIGH_444             144

#define FF_PROFILE_H264_HIGH_444_PREDICTIVE  244

#define FF_PROFILE_H264_HIGH_444_INTRA       (244|FF_PROFILE_H264_INTRA)

#define FF_PROFILE_H264_CAVLC_444            44

#define FF_PROFILE_VC1_SIMPLE   0

#define FF_PROFILE_VC1_MAIN     1

#define FF_PROFILE_VC1_COMPLEX  2

#define FF_PROFILE_VC1_ADVANCED 3

#define FF_PROFILE_MPEG4_SIMPLE                     0

#define FF_PROFILE_MPEG4_SIMPLE_SCALABLE            1

#define FF_PROFILE_MPEG4_CORE                       2

#define FF_PROFILE_MPEG4_MAIN                       3

#define FF_PROFILE_MPEG4_N_BIT                      4

#define FF_PROFILE_MPEG4_SCALABLE_TEXTURE           5

#define FF_PROFILE_MPEG4_SIMPLE_FACE_ANIMATION      6

#define FF_PROFILE_MPEG4_BASIC_ANIMATED_TEXTURE     7

#define FF_PROFILE_MPEG4_HYBRID                     8

#define FF_PROFILE_MPEG4_ADVANCED_REAL_TIME         9

#define FF_PROFILE_MPEG4_CORE_SCALABLE             10

#define FF_PROFILE_MPEG4_ADVANCED_CODING           11

#define FF_PROFILE_MPEG4_ADVANCED_CORE             12

#define FF_PROFILE_MPEG4_ADVANCED_SCALABLE_TEXTURE 13

#define FF_PROFILE_MPEG4_SIMPLE_STUDIO             14

#define FF_PROFILE_MPEG4_ADVANCED_SIMPLE           15

7)AVFormatContext

封装格式上下文结构体,也是统领全局的结构体,保存了视频文件封装格式相关信息。

是包含码流参数较多的结构体。本文将会详细分析一下该结构体里每个变量的含义和作用。

首先看一下结构体的定义(位于avformat.h):

在使用FFMPEG进行开发的时候,AVFormatContext是一个贯穿始终的数据结构,很多函数都要用到它作为参数。它是FFMPEG解封装(flv,mp4,rmvb,avi)功能的结构体。下面看几个主要变量的作用(在这里考虑解码的情况):

struct AVInputFormat *iformat:输入数据的封装格式

AVIOContext *pb:输入数据的缓存

unsigned int nb_streams:视音频流的个数

AVStream **streams:视音频流

char filename[1024]:文件名

int64_t duration:时长(单位:微秒us,转换为秒需要除以1000000)

int bit_rate:比特率(单位bps,转换为kbps需要除以1000)

AVDictionary *metadata:元数据

视频的时长可以转换成HH:MM:SS的形式,示例代码如下:

AVFormatContext *pFormatCtx;

CString timelong;

...

//duration是以微秒为单位

//转换成hh:mm:ss形式

int tns, thh, tmm, tss;

tns  = (pFormatCtx->duration)/1000000;

thh  = tns / 3600;

tmm  = (tns % 3600) / 60;

tss  = (tns % 60);

timelong.Format("d:d:d",thh,tmm,tss);

视频的原数据(metadata)信息可以通过AVDictionary获取。元数据存储在AVDictionaryEntry结构体中,如下所示

typedef struct AVDictionaryEntry {

    char *key;

    char *value;

} AVDictionaryEntry;

每一条元数据分为key和value两个属性。

在ffmpeg中通过av_dict_get()函数获得视频的原数据。

下列代码显示了获取元数据并存入meta字符串变量的过程,注意每一条key和value之间有一个"t:",value之后有一个"rn"

//MetaData------------------------------------------------------------

//从AVDictionary获得

//需要用到AVDictionaryEntry对象

//CString author,copyright,description;

CString meta=NULL,key,value;

AVDictionaryEntry *m = NULL;

//不用一个一个找出来

/* m=av_dict_get(pFormatCtx->metadata,"author",m,0);

author.Format("作者:%s",m->value);

m=av_dict_get(pFormatCtx->metadata,"copyright",m,0);

copyright.Format("版权:%s",m->value);

m=av_dict_get(pFormatCtx->metadata,"description",m,0);

description.Format("描述:%s",m->value);

*/

//使用循环读出

//(需要读取的数据,字段名称,前一条字段(循环时使用),参数)

while(m=av_dict_get(pFormatCtx->metadata,"",m,AV_DICT_IGNORE_SUFFIX)){

key.Format(m->key);

value.Format(m->value);

meta =key "t:" value "rn" ;

8)AVInputFormat demuxer

每种封装格式(例如FLV, MKV, MP4, AVI)对应一个该结构体。

9)AVOutputFormat muxer

10)AVOption

AVOption是用来设置FFmpeg中变量的值的结构体。可能说到这个作用有的人会奇怪:设置系统中变量的值,直接使用等于号“=”就可以,为什么还要专门定义一个结构体呢?其实AVOption的特点就在于它赋值时候的灵活性。AVOption可以使用字符串为任何类型的变量赋值。传统意义上,如果变量类型为int,则需要使用整数来赋值;如果变量为double,则需要使用小数来赋值;如果变量类型为char *,才需要使用字符串来赋值。而AVOption将这些赋值“归一化”了,统一使用字符串赋值。例如给int型变量qp设定值为20,通过AVOption需要传递进去一个内容为“20”的字符串。 此外,AVOption中变量的名称也使用字符串来表示。结合上面提到的使用字符串赋值的特性,我们可以发现使用AVOption之后,传递两个字符串(一个是变量的名称,一个是变量的值)就可以改变系统中变量的值。 上文提到的这种方法的意义在哪里?我个人感觉对于直接使用C语言进行开发的人来说,作用不是很明显:完全可以使用等于号“=”就可以进行各种变量的赋值。但是对于从外部系统中调用FFmpeg的人来说,作用就很大了:从外部系统中只可以传递字符串给内部系统。比如说对于直接调用ffmpeg.exe的人来说,他们是无法修改FFmpeg内部各个变量的数值的,这种情况下只能通过输入“名称”和“值”这样的字符串,通过AVOption改变FFmpeg内部变量的值。由此可见,使用AVOption可以使FFmpeg更加适应多种多样的外部系统。

突然想到了JavaEE开发中也有这种类似的机制。互联网上只可以传输字符串,即是没有方法传输整形、浮点型这种的数据。而Java系统中却包含整形、浮点型等各种数据类型。因此开发JSP中的Servlet的时候经常需要将整数字符串手工转化成一个整型的变量。使用最多的一个函数就是Integer.parseInt()方法。例如下面代码可以将字符串“123”转化成整数123。

int a=Integer.parseInt("123");

而在使用JavaEE中的Struts2进行开发的时候,就不需要进行手动转换处理了。Struts2中包含了类似AVOption的这种数据类型自动转换机制,可以将互联网上收到的字符串“名称”和“值”的组合自动赋值给相应名称的变量。 由此发现了一个结论:编程语言之间真的是相通的! 现在回到AVOption。其实除了可以对FFmpeg常用结构体AVFormatContext,AVCodecContext等进行赋值之外,还可以对它们的私有数据priv_data进行赋值。这个字段里通常存储了各种编码器特有的结构体。而这些结构体的定义在FFmpeg的SDK中是找不到的。例如使用libx264进行编码的时候,通过AVCodecContext的priv_data字段可以对X264Context结构体中的变量进行赋值,设置preset,profile等。使用libx265进行编码的时候,通过AVCodecContext的priv_data字段可以对libx265Context结构体中的变量进行赋值,设置preset,tune等。

下面简单解释一下AVOption的几个成员变量:

name:名称。

help:简短的帮助。

offset:选项相对结构体首部地址的偏移量(这个很重要)。

type:选项的类型。

default_val:选项的默认值。

min:选项的最小值。

max:选项的最大值。

flags:一些标记。

unit:该选项所属的逻辑单元,可以为空。

其中,default_val是一个union类型的变量,可以根据选项数据类型的不同,取int,double,char*,AVRational(表示分数)几种类型。type是一个AVOptionType类型的变量。AVOptionType是一个枚举类型,定义如下。

enum AVOptionType{

AV_OPT_TYPE_FLAGS,

AV_OPT_TYPE_INT,

AV_OPT_TYPE_INT64,

AV_OPT_TYPE_DOUBLE,

AV_OPT_TYPE_FLOAT,

AV_OPT_TYPE_STRING,

AV_OPT_TYPE_RATIONAL,

AV_OPT_TYPE_BINARY,  ///< offset must point to a pointer immediately followed by an int for the length

AV_OPT_TYPE_DICT,

AV_OPT_TYPE_CONST = 128,

AV_OPT_TYPE_IMAGE_SIZE = MKBETAG('S','I','Z','E'), ///< offset must point to two consecutive integers

AV_OPT_TYPE_PIXEL_FMT  = MKBETAG('P','F','M','T'),

AV_OPT_TYPE_SAMPLE_FMT = MKBETAG('S','F','M','T'),

AV_OPT_TYPE_VIDEO_RATE = MKBETAG('V','R','A','T'), ///< offset must point to AVRational

AV_OPT_TYPE_DURATION   = MKBETAG('D','U','R',' '),

AV_OPT_TYPE_COLOR      = MKBETAG('C','O','L','R'),

AV_OPT_TYPE_CHANNEL_LAYOUT = MKBETAG('C','H','L','A'),

#if FF_API_OLD_AVOPTIONS

FF_OPT_TYPE_FLAGS = 0,

FF_OPT_TYPE_INT,

FF_OPT_TYPE_INT64,

FF_OPT_TYPE_DOUBLE,

FF_OPT_TYPE_FLOAT,

FF_OPT_TYPE_STRING,

FF_OPT_TYPE_RATIONAL,

FF_OPT_TYPE_BINARY,  ///< offset must point to a pointer immediately followed by an int for the length

FF_OPT_TYPE_CONST=128,

#endif

};

AVOption有关的API

AVOption常用的API可以分成两类:用于设置参数的API和用于读取参数的API。其中最有代表性的用于设置参数的API就是av_opt_set();而最有代表性的用于读取参数的API就是av_opt_get()。除了记录以上两个函数之外,本文再记录一个在FFmpeg的结构体初始化代码中最常用的用于设置默认值的函数av_opt_set_defaults()。

av_opt_set()

通过AVOption设置参数最常用的函数就是av_opt_set()了。该函数通过字符串的方式(传入的参数是变量名称的字符串和变量值的字符串)设置一个AVOption的值。此外,还包含了它的一系列“兄弟”函数av_opt_set_XXX(),其中“XXX”代表了int,double这些数据类型。使用这些函数的时候,可以指定int,double这些类型的变量(而不是字符串)作为输入,设定相应的AVOption的值。

av_opt_find2() / av_opt_find()

av_opt_find2()本身也是一个API函数,用于查找AVOption。它的声明位于libavutilopt.h中,如下所示。

av_opt_get()

av_opt_get()用于获取一个AVOption变量的值。需要注意的是,不论是何种类型的变量,通过av_opt_get()取出来的值都是字符串类型的。此外,还包含了它的一系列“兄弟”函数av_opt_get_XXX()(其中“XXX”代表了int,double这些数据类型)。通过这些“兄弟”函数可以直接取出int,double类型的数值。av_opt_get()的声明如下所示。

11)AVClass

AVClass最主要的作用就是给结构体(例如AVFormatContext等)增加AVOption功能的支持。换句话说AVClass就是AVOption和目标结构体之间的“桥梁”。AVClass要求必须声明为目标结构体的第一个变量。

AVClass中有一个option数组用于存储目标结构体的所有的AVOption。举个例子,AVFormatContext结构体,AVClass和AVOption之间的关系如下图所示。

图中AVFormatContext结构体的第一个变量为AVClass类型的指针av_class,它在AVFormatContext结构体初始化的时候,被赋值指向了全局静态变量av_format_context_class结构体(定义位于libavformatoptions.c)。而AVClass类型的av_format_context_class结构体中的option变量指向了全局静态数组avformat_options(定义位于libavformatoptions_table.h)。

AVClass中存储了AVOption类型的数组option,用于存储选项信息。AVClass有一个特点就是它必须位于其支持的结构体的第一个位置。例如,AVFormatContext和AVCodecContext都支持AVClass,观察它们结构体的定义可以发现他们结构体的第一个变量都是AVClass。

下面简单解释一下AVClass的几个已经理解的成员变量:

class_name:AVClass名称。 item_name:函数,获取与AVClass相关联的结构体实例的名称。 option:AVOption类型的数组(最重要)。 version:完成该AVClass的时候的LIBAVUTIL_VERSION。 category:AVClass的类型,是一个类型为AVClassCategory的枚举型变量。

其中AVClassCategory定义如下。

typedef enum {

    AV_CLASS_CATEGORY_NA = 0,

    AV_CLASS_CATEGORY_INPUT,

    AV_CLASS_CATEGORY_OUTPUT,

    AV_CLASS_CATEGORY_MUXER,

    AV_CLASS_CATEGORY_DEMUXER,

    AV_CLASS_CATEGORY_ENCODER,

    AV_CLASS_CATEGORY_DECODER,

    AV_CLASS_CATEGORY_FILTER,

    AV_CLASS_CATEGORY_BITSTREAM_FILTER,

    AV_CLASS_CATEGORY_SWSCALER,

    AV_CLASS_CATEGORY_SWRESAMPLER,

    AV_CLASS_CATEGORY_DEVICE_VIDEO_OUTPUT = 40,

    AV_CLASS_CATEGORY_DEVICE_VIDEO_INPUT,

    AV_CLASS_CATEGORY_DEVICE_AUDIO_OUTPUT,

    AV_CLASS_CATEGORY_DEVICE_AUDIO_INPUT,

    AV_CLASS_CATEGORY_DEVICE_OUTPUT,

    AV_CLASS_CATEGORY_DEVICE_INPUT,

    AV_CLASS_CATEGORY_NB, ///< not part of ABI/API

2.3编码核心api

2.4解码核心api

2.5重要api解读

1) avformat_find_stream_info()

该函数可以读取一部分视音频数据并且获得一些相关的信息。avformat_find_stream_info()的声明位于libavformatavformat.h

简单解释一下它的参数的含义:

ic:输入的AVFormatContext。 options:额外的选项,目前没有深入研究过。

函数正常执行后返回值大于等于0。

代码比较长,难以全部分析,在这里只能简单记录一下它的要点。该函数主要用于给每个媒体流(音频/视频)的AVStream结构体赋值。我们大致浏览一下这个函数的代码,会发现它其实已经实现了解码器的查找,解码器的打开,视音频帧的读取,视音频帧的解码等工作。换句话说,该函数实际上已经“走通”的解码的整个流程。下面看一下除了成员变量赋值之外,该函数的几个关键流程。

1.查找解码器:find_decoder() 2.打开解码器:avcodec_open2() 3.读取完整的一帧压缩编码的数据:read_frame_internal() 注:av_read_frame()内部实际上就是调用的read_frame_internal()。 4.解码一些压缩编码数据:try_decode_frame()

下面选择上述流程中几个关键函数的代码简单看一下。

2) av_read_frame()

作用是读取码流中的音频若干帧或者视频一帧。例如,解码视频的时候,每解码一个视频帧,需要先调用 av_read_frame()获得一帧视频的压缩数据,然后才能对该数据进行解码(例如H.264中一帧压缩数据通常对应一个NAL)。

对该函数源代码的分析是很久之前做的了,现在翻出来,用博客记录一下。

上代码之前,先参考了其他人对av_read_frame()的解释,在此做一个参考:

通过av_read_packet(***),读取一个包,需要说明的是此函数必须是包含整数帧的,不存在半帧的情况,以ts流为例,是读取一个完整的PES包(一个完整pes包包含若干视频或音频es包),读取完毕后,通过av_parser_parse2(***)分析出视频一帧(或音频若干帧),返回,下次进入循环的时候,如果上次的数据没有完全取完,则st = s->cur_st;不会是NULL,即再此进入av_parser_parse2(***)流程,而不是下面的av_read_packet(**)流程,这样就保证了,如果读取一次包含了N帧视频数据(以视频为例),则调用av_read_frame(***)N次都不会去读数据,而是返回第一次读取的数据,直到全部解析完毕。

参考:

https://blog.csdn.net/leixiaohua1020/article/details/25430425

https://blog.csdn.net/leixiaohua1020/article/details/38868499

https://blog.csdn.net/leixiaohua1020/article/details/44268323

https://blog.csdn.net/leixiaohua1020/article/details/44279329

https://blog.csdn.net/leixiaohua1020/article/details/14214859

https://blog.csdn.net/leixiaohua1020/article/details/14215833

https://blog.csdn.net/leixiaohua1020/article/details/14215755

https://blog.csdn.net/leixiaohua1020/article/details/14214577

https://blog.csdn.net/leixiaohua1020/article/details/84489473

https://blog.csdn.net/leixiaohua1020/article/details/38868499

https://blog.csdn.net/leixiaohua1020/article/details/44220151

https://blog.csdn.net/leixiaohua1020/article/details/44226355

https://blog.csdn.net/leixiaohua1020/article/details/25430425

https://blog.csdn.net/leixiaohua1020/article/details/14215821

0 人点赞