ffmpeg 过程分析

  • 简介

FFmpeg是一个集录制、转换、音/视频编码解码功能为一体的完整的开源解决方案。FFmpeg的开发是基于Linux操作系统,但是可以在大多数操作系统中编译和使用。FFmpeg支持MPEG、DivX、MPEG4、AC3、DV、FLV等40多种编码,AVI、MPEG、OGG、Matroska、ASF等90多种解码.

TCPMP, VLC,MPlayer等开源播放器都用到了FFmpeg。

FFmpeg主目录下主要有libavcodec、libavformat和libavutil等子目录。其中libavcodec用于存放各个encode/decode模块,libavformat用于存放muxer/demuxer模块,libavutil用于存放内存操作等常用模块。

以flash movie的flv文件格式为例, muxer/demuxer的flvenc.c和flvdec.c文件在libavformat目录下,encode/decode的mpegvideo.c和h263de.c在libavcodec目录下。

muxer/demuxer与encoder/decoder定义与初始化

muxer/demuxer和encoder/decoder在FFmpeg中的实现代码里,有许多相同的地方,而二者最大的差别是muxer和demuxer分别是不同的结构AVOutputFormat与AVInputFormat,而encoder和decoder都是用的AVCodec结构。

muxer/demuxer和encoder/decoder在FFmpeg中相同的地方有:

l二者都是在main()开始的av_register_all()函数内初始化的。

l二者都是以链表的形式保存在全局变量中的。

muxer/demuxer是分别保存在全局变量AVOutputFormat *first_oformat与AVInputFormat *first_iformat中的。

encoder/decoder都是保存在全局变量AVCodec *first_avcodec中的。

l二者都用函数指针的方式作为开放的公共接口。

demuxer开放的接口有:

int (*read_probe)(AVProbeData *);

int(*read_header)(struct AVFormatContext *, AVFormatParameters *ap);

int(*read_packet)(struct AVFormatContext *, AVPacket *pkt);

int(*read_close)(struct AVFormatContext *);

int(*read_seek)(struct AVFormatContext *, int stream_index, int64_t timestamp, intflags);

muxer开放的接口有:

int (*write_header)(struct AVFormatContext *);

int (*write_packet)(struct AVFormatContext *, AVPacket *pkt);

int (*write_trailer)(struct AVFormatContext *);

encoder/decoder的接口都是一样的,只不过二者分别只实现encoder和decoder函数:

int(*init)(AVCodecContext *);

int (*encode)(AVCodecContext*, uint8_t *buf, int buf_size, void *data);

int (*close)(AVCodecContext*);

int (*decode)(AVCodecContext*, void *outdata, int *outdata_size, uint8_t *buf, int buf_size);

仍以flv文件为例来说明muxer/demuxer的初始化。

在libavformat\allformats.c文件的av_register_all(void)函数中,通过执行

REGISTER_MUXDEMUX(FLV,flv);

将支持flv 格式的flv_muxerflv_demuxer变量分别注册到全局变量first_oformatfirst_iformat链表的最后位置。

其中flv_muxer在libavformat\flvenc.c中定义如下:

AVOutputFormat flv_muxer = {

"flv",

"flv format",

"video/x-flv",

"flv",

sizeof(FLVContext),

#ifdefCONFIG_LIBMP3LAME

CODEC_ID_MP3,

#else //CONFIG_LIBMP3LAME

CODEC_ID_NONE,

CODEC_ID_FLV1,

flv_write_header,

flv_write_packet,

flv_write_trailer,

.codec_tag= (const AVCodecTag*[]){flv_video_codec_ids, flv_audio_codec_ids, 0},

}

AVOutputFormat结构的定义如下:

typedefstruct AVOutputFormat {

const char *name;

const char *long_name;

const char *mime_type;

const char *extensions; /**< comma separated filename extensions */

/** size of private data so that it can be allocated in the wrapper */

int priv_data_size;

/* output support */

enum CodecID audio_codec; /**< default audio codec */

enum CodecID video_codec; /**< default video codec */

int (*write_header)(struct AVFormatContext *);

int (*write_packet)(struct AVFormatContext *, AVPacket *pkt);

int (*write_trailer)(struct AVFormatContext *);

/** can use flags: AVFMT_NOFILE, AVFMT_NEEDNUMBER, AVFMT_GLOBALHEADER */

int flags;

/** currently only used to set pixel format if not YUV420P */

int (*set_parameters)(struct AVFormatContext *, AVFormatParameters *);

int (*interleave_packet)(struct AVFormatContext *, AVPacket *out, AVPacket *in,int flush);

/**

* list of supported codec_id-codec_tag pairs, ordered by "better choicefirst"

* the arrays are all CODEC_ID_NONE terminated

*/

const struct AVCodecTag **codec_tag;

/* private fields */

struct AVOutputFormat *next;

} AVOutputFormat;

AVOutputFormat结构的定义可知,flv_muxer变量初始化的第一、第二个成员分别为该muxer的名称与长名称,第三、第四个成员为所对应MIMIE Type和后缀名,第五个成员是所对应的私有结构的大小,第六、第七个成员为所对应的音频编码和视频编码类型ID,接下来就是三个重要的接口函数,该muxer的功能也就是通过调用这三个接口实现的。

flv_demuxer在libavformat\flvdec.c中定义如下, 与flv_muxer类似,在这儿主要也是设置了5个接口函数,其中flv_probe接口用途是测试传入的数据段是否是符合当前文件格式,这个接口在匹配当前demuxer的时候会用到。

AVInputFormatflv_demuxer = {

"flv",

"flv format",

0,

flv_probe,

flv_read_header,

flv_read_packet,

flv_read_close,

flv_read_seek,

.extensions = "flv",

.value = CODEC_ID_FLV1,

};

在上述av_register_all(void)函数中通过执行libavcodec\allcodecs.c文件里的avcodec_register_all(void)函数来初始化全部的encoder/decoder。

因为不是每种编码方式都支持encode和decode,所以有以下三种注册方式:

#defineREGISTER_ENCODER(X,x) \

if(ENABLE_##X##_ENCODER)register_avcodec(&x##_encoder)

#defineREGISTER_DECODER(X,x) \

if(ENABLE_##X##_DECODER)register_avcodec(&x##_decoder)

#defineREGISTER_ENCDEC(X,x)

REGISTER_ENCODER(X,x);REGISTER_DECODER(X,x)

如支持flv的flv_encoderflv_decoder变量就分别是在libavcodec\mpegvideo.c和libavcodec\h263de.c中创建的。

当前muxer/demuxer的匹配

在FFmpeg的文件转换过程中,首先要做的就是根据传入文件和传出文件的后缀名匹配合适的demuxer和muxer。

匹配上的demuxer和muxer都保存在如下所示,定义在ffmpeg.c里的全局变量file_iformatfile_oformat中:

staticAVInputFormat *file_iformat;

staticAVOutputFormat *file_oformat;

1.demuxer匹配

在libavformat\utils.c中的static AVInputFormat*av_probe_input_format2(AVProbeData *pd, int is_opened, int *score_max)函数用途是根据传入的probe data数据,依次调用每个demuxer的read_probe接口,来进行该demuxer是否和传入的文件内容匹配的判断。其调用顺序如下:

voidparse_options(int argc, char **argv, const OptionDef *options)

static voidopt_input_file(const char *filename)

static voidopt_input_file(const char *filename)

int av_open_input_file(…… )

AVInputFormat *av_probe_input_format(AVProbeData *pd, int is_opened)

staticAVInputFormat *av_probe_input_format2(……)

opt_input_file函数是在保存在const OptionDef options[]数组中,用于void parse_options(int argc, char**argv, const OptionDef *options)中解析argv里的“-i” 参数,也就是输入文件名时调用的。

2.muxer匹配

与demuxer的匹配不同,muxer的匹配是调用guess_format函数,根据main( ) 函数的argv里的输出文件后缀名来进行的。

voidparse_options(int argc, char **argv, const OptionDef *options)

voidparse_arg_file(const char *filename)

static voidopt_output_file(const char *filename)

AVOutputFormat*guess_format(const char *short_name, const char *filename,

const char *mime_type)

当前encoder/decoder的匹配

main( )函数中除了解析传入参数并初始化demuxer与muxer的parse_options()函数以外,其他的功能都是在av_encode( )函数里完成的。

在libavcodec\utils.c中有如下二个函数。

AVCodec *avcodec_find_encoder(enum CodecID id)

AVCodec *avcodec_find_decoder(enum CodecID id)

他们的功能就是根据传入的CodecID,找到匹配的encoder和decoder。

av_encode( )函数的开头,首先初始化各个AVInputStreamAVOutputStream,然后分别调用上述二个函数,并将匹配上的encoder与decoder分别保存在AVInputStream->AVStream*st->AVCodecContext *codec->struct AVCodec *codecAVOutputStream->AVStream*st->AVCodecContext *codec->struct AVCodec *codec变量中。

其他主要数据结构

1.AVFormatContext

AVFormatContext是FFMpeg格式转换过程中实现输入和输出功能、保存相关数据的主要结构。每一个输入和输出文件,都在如下定义的指针数组全局变量中有对应的实体。

staticAVFormatContext *output_files[MAX_FILES];

staticAVFormatContext *input_files[MAX_FILES];

对于输入和输出,因为共用的是同一个结构体,所以需要分别对该结构中如下定义的iformatoformat成员赋值。

struct AVInputFormat *iformat;

struct AVOutputFormat *oformat;

对一个AVFormatContext来说,二个成员不能同时有值,即一个AVFormatContext不能同时含有demuxer和muxer。

main( )函数开头的parse_options( )函数中找到了匹配的muxer和demuxer之后,根据传入的argv参数,初始化每个输入和输出的AVFormatContext结构,并保存在相应的output_filesinput_files指针数组中。

av_encode( )函数中,output_filesinput_files是作为函数参数传入后,在其他地方就没有用到了。

2.AVCodecContext

保存AVCodec指针和与codec相关的数据,如video的width、height,audio的sample rate等。AVCodecContext中的codec_type,codec_id二个变量对于encoder/decoder的匹配来说,最为重要。

enum CodecType codec_type; /* seeCODEC_TYPE_xxx */

enumCodecID codec_id; /* see CODEC_ID_xxx */

如上所示,codec_type保存的是CODEC_TYPE_VIDEOCODEC_TYPE_AUDIO等媒体类型,

codec_id保存的是CODEC_ID_FLV1CODEC_ID_VP6F等编码方式。

以支持flv格式为例,在前述的av_open_input_file(……) 函数中,匹配到正确的AVInputFormat demuxer后,通过av_open_input_stream( )函数中调用AVInputFormatread_header接口来执行flvdec.c中的flv_read_header()函数。在flv_read_header()函数内,根据文件头中的数据,创建相应的视频或音频AVStream,并设置AVStreamAVCodecContext的正确的codec_type值。codec_id值是在解码过程中flv_read_packet( )函数执行时根据每一个packet头中的数据来设置的。

3.AVStream

AVStream结构保存与数据流相关的编解码器,数据段等信息。比较重要的有如下二个成员:

AVCodecContext*codec; /**< codec context */

void*priv_data;

其中codec指针保存的就是上节所述的encoder或decoder结构。priv_data指针保存的是和具体编解码流相关的数据,如下代码所示,在ASF的解码过程中,priv_data保存的就是ASFStream结构的数据。

AVStream *st;

ASFStream*asf_st;

… …

st->priv_data= asf_st;

4.AVInputStream/AVOutputStream

根据输入和输出流的不同,前述的AVStream结构都是封装在AVInputStreamAVOutputStream结构中,在av_encode( )函数中使用。

AVInputStream中还保存的有与时间有关的信息。

AVOutputStream中还保存有与音视频同步等相关的信息。

5.AVPacket

AVPacket结构定义如下,其是用于保存读取的packet数据。

typedefstruct AVPacket {

int64_t pts;///< presentation time stamp in time_base units

int64_t dts;///< decompression time stamp in time_base units

uint8_t *data;

int size;

int stream_index;

int flags;

int duration; ///

void(*destruct)(struct AVPacket *);

void*priv;

int64_t pos; ///

} AVPacket;

av_encode( )函数中,调用AVInputFormat(*read_packet)(structAVFormatContext *, AVPacket *pkt);接口,读取输入文件的一帧数据保存在当前输入AVFormatContextAVPacket成员中。

av_encode函数主要流程

av_encode( )函数是FFMpeg中最重要的函数,编解码和输出等大部分功能都在此函数内完成,因此有必要详细描述一下这个函数的主要流程。

1.input streamsinitializing

2.outputstreams initializing

3.encoders anddecoders initializing

4.set meta datainformation from input file if required.

5.write outputfiles header

6.loop ofhandling each frame

a.read frame frominput file:

b.decode framedata

c.encode newframe data

d.write newframe to output file

7.write outputfiles trailer

8.close eachencoder and decoder

·IV. AVCodecContext

此结构在Ffmpeg SDK中的注释是:main external api structure其重要性可见一斑。而且在avcodec它的定义处,对其每个

成员变量,都给出了十分详细的介绍。应该说AVCodecContext的初始化是Codec使用中最重要的一环。虽然在前面的

AVStream中已经有所提及,但是这里还是要在说一遍。AVCodecContext作为Avstream的一个成员结构,必须要在Avstream初

始化後(30)再对其初始化(AVStream的初始化用到AVFormatContex)。虽然成员变量比较多,但是这里只说一下在

output_example.c中用到了,其他的请查阅avcodec.h文件中介绍。

// static AVStream*add_video_stream(AVFormatContext *oc, int codec_id)

AVCodecContext *c;

st = av_new_stream(oc, 0);

c = st->codec;

c->codec_id = codec_id;

c->codec_type = CODEC_TYPE_VIDEO;

c->bit_rate = 400000; // 400kbits/s

c->width = 352;

c->height = 288; // CIF

// 帧率做分母,秒做分子,那么time_base也就是一帧所用时间。(时间基!)

c->time_base.den =STREAM_FRAME_RATE;

c->time_base.num = 1;

c->gop_size =12;

// here define:

// #define STREAM_PIX_FMT PIX_FMT_YUV420P

// pixel format, see PIX_FMT_xxx

// -encoding: set by user.

// -decoding: set by lavc.

c->pix_fmt = STREAM_PIX_FMT;

除了以上列出了的。还有诸如指定运动估计算法的: me_method。量化参数、最大b帧数:max_b_frames。码率控制的参数、

差错掩盖error_concealment、模式判断模式:mb_decision (这个参数蛮有意思的,可以看看avcodec.h 1566行)、

Lagrange multipler参数:lmin & lmax 和 宏块级Lagrange multipler参数:mb_lmin & mb_lmax、constant

quantization parameter rate controlmethod: cqp等。

值得一提的是在AVCodecContext中有两个成员数据结构:AVCodec、AVFrame。AVCodec记录了所要使用的Codec信息并且含有

5个函数:init、encoder、close、decode、flush来完成编解码工作(参见avcode.h 2072行)。AVFrame中主要是包含了编

码後的帧信息,包括本帧是否是key frame、*data[4]定义的Y、Cb和Cr信息等,随后详细介绍。

初始化後,可以说AVCodecContext在(8)&(10)中大显身手。先在(8)open_video()中初始化AVCodec *codec以及AVFrame*

picture:

// AVCodecContext *c;

codec = avcodec_find_encoder(c->codec_id);

……

picture =alloc_picture(PIX_FMT_YUV420P, c->width, c->height);

後在writer_video_frame(AVFormatContext *oc, AVStream *st)中作为一个编解码器的主要参数被利用:

AVCodecContext *c;

c = st->codec;

……

out_size = avcodec_encode_video(c,video_outbuf, video_outbuf_size, picture);

V.AVCodec

结构AVCodec中成员变量和成员函数比较少,但是很重要。他包含了CodecID,也就是用哪个Codec、

像素格式信息。还有前面提到过的5个函数(init、encode、close、decoder、flush)。顺便提一下,虽然在参考代码

output_example.c中的编码函数用的是avcodec_encode_video(),我怀疑在其中就是调用了AVCodec的encode函数,他们

传递的参数和返回值都是一致的,当然还没有得到确认,有兴趣可以看看ffmpeg源代码。在参考代码中,AVCodec的初始化

後的使用都是依附于AVCodecContex,前者是后者的成员。在AVCodecContext初始化後(add_video_stream()),AVCodec也

就能很好的初始化了:

//初始化

codec =avcodec_find_encoder(c->codec_id); (33)

//打开Codec

avcodec_open(c, codec) (34)

VI. AVFrame

AVFrame是个很有意思的结构,它本身是这样定义的:

typedef struct AVFrame {

FF_COMMON_FRAME

}AVFrame;

其中,FF_COMMON_FRAME是以一个宏出现的。由于在编解码过程中AVFrame中的数据是要经常存取的。为了加速,要采取这样

的代码手段。

AVFrame是作为一个描述“原始图像”(也就是YUV或是RGB…还有其他的吗?)的结构,他的头两个成员数据,uint8_t

*data[4],int linesize[4],第一个存放的是Y、Cb、Cr(yuv格式),linesize是啥?由这两个数据还能提取处另外一个

数据结构:

typedef struct AVPicture {

uint8_t *data[4];

int linesize[4]; // number of bytesper line

}AVPicture ;

此外,AVFrame还含有其他一些成员数据,比如。是否key_frame、已编码图像书coded_picture_number、是否作为参考帧

reference、宏块类型 *mb_type等等(avcodec.h 446行)。

AVFrame的初始化并没有他结构上看上去的那么简单。由于AVFrame还有一个承载图像数据的任务(data[4])因此,对他分

配内存应该要小心完成。output_example.c中提供了alloc_picute()来完成这项工作。参考代码中定义了两个全局变量:

AVFrame *picture,*tmp_picture。(如果使用yuv420格式的那么只用到前一个数据picture就行了,将图像信息放入

picture中。如果是其他格式,那么先要将yuv420格式初始化后放到tmp_picture中在转到需求格式放入picture中。)在

open_video()打开编解码器后初始化AVFrame:

picture =alloc_picture(c->pix_fmt, c->width, c->height);

tmp_picture =alloc_picture(PIX_FMT_YUV420P, c->width, c->height);

static AVFrame *alloc_picture(intpix_fmt, int width, int height){

AVFrame *picture;

uint8_t *picture_buf; // think aboutwhy use uint8_t? a byte!

picture = avcodec_alloc_frame();(35)

if(!picture)

return NULL;

size = avpicture_get_size(pix_fmt,width, height); (36)

picture_buf = av_malloc(size); (37)

if(!picture_buf){

av_free(picture); (38)

return NULL;

}

avpicture_fill ( (AVPicture*)picture, picture_buf, pix_fmt, width, height); (39)

return picture;

}

从以上代码可以看出,完成对一个AVFrame的初始化(其实也就是内存分配),基本上是有这样一个固定模式的。至于(35)

(39)分别完成了那些工作,以及为什么有这样两步,还没有搞清楚,需要看原代码。我的猜测是(35)对AVFrame做了基本的

内存分配,保留了对可以提取出AVPicture的前两个数据的内存分配到(39)来完成。

说到这里,我们观察到在(39)中有一个(AVPicture *)picture,AVPicture这个结构也很有用。基本上他的大小也就是要在

网络上传输的包大小,我们在后面可以看到AVPacket跟AVPicture有密切的关系。

VII.AVPicture

AVPicture在参考代码中没有自己本身的申明和初始化过程。出现了的两次都是作为强制类型转换由AVFrame中提取出来的:

// open_video() 中

avpicture_fill((AVPicture *)picture,picture_buf, pix_fmt, width, height); (40)

//write_video_frame 中

// AVPacket pkt;

if(oc->oformat->flags &AVFMT_RAWPICTURE){

……

pkt.size = sizeof(AVPicture); (41)

}

在(40)中,实际上是对AVFrame的data[4]、linesize[4]分配内存。由于这两个数据大小如何分配确实需要有pix_fmt、

width、height来确定。如果输出文件格式就是RAW 图片(如YUV和RGB),AVPacket作为将编码后数据写入文件的基本数据

单元,他的单元大小、数据都是由AVPacket来的。

总结起来就是,AVPicture的存在有以下原因,AVPicture将Picture的概念从Frame中提取出来,就只由Picture(图片)本

身的信息,亮度、色度和行大小。而Frame还有如是否是key frame之类的信息。这样的类似“分级”是整个概念更加清晰。

VIII.AVPacket

AVPacket的存在是作为写入文件的基本单元而存在的。我们可能会认为直接把编码后的比特流写入文件不就可以了,为什么

还要麻烦设置一个AVPacket结构。在我看来这样的编码设置是十分有必要的,特别是在做视频实时传输,同步、边界问题可

以通过AVPacket来解决。AVPacket的成员数据有两个时间戳、数据data(通常是编码后数据)、大小size等等(参见

avformat.h 48行)。讲AVPacket的用法就不得不提到编解码函数,因为AVPacket的好些信息只有在编解码后才能的知。在

参考代码中(ouput_example.c 从362到394行),做的一个判断分支。如果输出文件格式是RAW图像(即YUV或RGB)那么就

没有编码函数,直接写入文件(因为程序本身生成一个YUV文件),这里的代码虽然在此看来没什么价值,但是如果是解码

函数解出yuv文件(或rgb)那么基本的写文件操作就是这样的:

if(oc->oformat->flags &AVFMT_RAWPICTURE) {

AVPacket pkt; // 这里没有用指针!

av_init_packet(&pkt);

pkt.flags |= PKT_FLAG_KEY // rawpicture 中,每帧都是key frame?

pkt.stream_index = st->index;

pkt.data = (uint8_t *)picture;

pkt.size = sizeof(AVPicture);

ret = av_write_frame(oc, &pkt);

}

输出非raw picture,编码后:

else{

// video_outbuf &video_outbuf_size在open_video() 中初始化

out_size = avcodec_encode_video(c,video_outbuf, video_outbuf_size, picture); (42)

if(out_size > 0){

AVPacket pkt;

av_init_packet(&pkt); (43)

pkt.pts=av_rescale_q(c->coded_frame->pts, c->time_base, st->time_base);(44)

if(c->coded_frame->key_frame)

pkt.flags |= PKT_FLAG_KEY;

pkt.stream_index= st->index;

pkt.data= video_outbuf;

pkt.size= out_size;

/* write the compressed frame in themedia file */

ret = av_write_frame(oc, &pkt);(45)

} else {

ret = 0;

}

if (ret != 0) {

fprintf(stderr, "Error whilewriting video frame\n");

exit(1);

}

其中video_outbuf和video_outbuf_size在open_video()里的初始化是这样的:

video_outbuf = NULL;

// 输出不是raw picture,而确实用到编码codec

if( !(oc->oformat->flags &AVFMT_RAWPICTURE)){

video_outbuf_size = 200000;

video_outbuf =av_malloc(video_outbuf_size);

}

(43)是AVPacket结构的初始化函数。(44)比较难理解,而且为什么会有这样的一些时间戳我也没有搞明白。其他的AVPacket

成员数据的赋值比较容易理解,要注意的是video_outbuf和video_outbuf_size的初始化问题,由于在参考代码中初始化和

使用不在同一函数中,所以比较容易忽视。(45)是写文件函数,AVFormatContext* oc中含有文件名等信息,返回值ret因该

是一共写了多少数据信息,如果返回0则说明写失败。(42)和(45)作为比较重要的SDK函数,后面还会介绍的。.

IX. Conclusion

以上分析了FFMpeg中比较重要的数据结构。下面的这个生成关系理一下思路:(->表示 派生出)

AVFormatContext->AVStream->AVCodecContext->AVCodec

|

AVOutputFormat or AVInputFormat

AVFrame->AVPicture….>AVPacket

二.FFMpeg 中的函数:

在前一部分的分析中我们已经看到FFMpeg SDK提供了许多初始化函数和编码函数。我们要做的就是对主要数据结构正确的初

始化,以及正确使用相应的编解码函数以及读写(I/O)操作函数。作为一个整体化的代码SDK,FFMpeg有一些他自己的标准

化使用过程。比如函数av_register_all(); 就是一个最开始就该调用的“注册函数”,他初始化了libavcodec,“注册”

了所有的的codec和视频文件格式(format)。下面,我沿着参考代码(ouput_example.c)的脉络,介绍一下相关函数。

/******************************************************************

main()

******************************************************************/

1. av_register_all ();

usage: initialize ibavcoded, andregister all codecs and formats

每个使用FFMpeg SDK的工程都必须调用的函数。进行codec和format的注册,然后才能使用。声明在allformats.c中,都是

宏有兴趣看看。

2. AVOutputFormat guess_format(constchar *short_name, const char *filename, const char *mime_type)

usage: 通过文件后缀名,猜测文件格式,其实也就是要判断使用什么编码器(or解码器)。

AVOutputFormat *fmt;

fmt = guess_format(NULL, filename,NULL);

3. AVFormatContext*av_alloc_format_context(void)

usage: allocate the output mediacontext.实际是初始化AVFormatContext的成员数据AVClass:

AVFormatContext *ic;

ic->av_class =&av_format_context_class;

//where

// format_to_name, options arepointer to function

static const AVClassav_format_context_class = {“AVFormatContext”, format_to_name, options};

4. static AVStream*add_video_stream(AVFormatContext *ox, int codec_id);

AVStream *video_st;

video_st = add_video_stream(oc,fmt->video_codec);

5. intav_set_parameters(AVFormatContext *s, AVFormatParameters *ap)

usage: set the output parameters(must be done even if no parameters).

AVFormatContext *oc;

// if failed, return integer smallerthan zero

av_set_parameters(oc, NULL);

6. void dump_format(AVFormatContext*ic, int index, const char *url, int is_output);

usage: 这一步会用有效的信息把 AVFormatContext 的流域(streams field)填满。作为一个可调试的诊断,我们会将这

些信息全盘输出到标准错误输出中,不过你在一个应用程序的产品中并不用这么做:

dump_format(oc, 0, filename, 1); // 也就是指明AVFormatContext中的事AVOutputFormat,还是 //AVInputFormat

7. static voidopen_video(AVFormatContext *oc, AVStream *st)

open_video(oc, video_st);

8. intav_write_header(AVFormatContext *s)

usage: allocate the stream privatedata and writer the stream header to an output media file. param s media

file

handle, return 0 if OK, AVERROR_xxxif error.

write the stream header, if any

av_write_header(oc);

9. static voidwrite_video_frame(AVFormatContext *oc, AVStream *st)

write_video_frame(oc, video_st);

10. static voidclose_video(AVFormatContext *oc, AVStream *st)

// close each codec

close_video(oc, video_st);

11. intav_write_trailer(AVFormatContext *s)

usage: write the trailer, if any.Write the stream trailer to an output media file and free the file private

data.

av_write_trailer(oc);

12. void av_freep(void *arg)

usage: free the streams. Freesmemory and sets the pointer to NULL. arg pointer to the pointer which should

be freed .

av_freep(&oc->streams[i]->codec);

av_freeep(&oc->streams[s]);

13. int url_fclose(ByteIOContext*s);

usage: close the output file

url_fclose(&oc->pb);

·14. void av_free(void *ptr)

usage: free the stream. Free memorywhich has been allocated with av_malloc(z)() or av_realloc().

av_free(oc);

/******************************************************************

******************************************************************

add_video_stream()

AVCodecContext *c

AVStream *st

******************************************************************/

******************************************************************

1.AVStream*av_new_stream(AVFormatContext *s, int id)

usage: add a new stream to a mediafile. s: media file handle, id: file format dependent stream id

st = av_new_stream(oc, 0);

/******************************************************************

******************************************************************

open_video()

AVCodecContext *c

AVCodec *codec

AVFrame *picture, *tmp_picture

uint8_t *video_output

int frame_count, video_outbuf_size;

******************************************************************

******************************************************************/

1 AVCodec *avcodec_find_encoder(enumCodecID id)

usage: find the codec of encoder byCodecID. 在前面main中的guess_format()就已经开始为此准备了。

codec =avcodec_find_encoder(c->codec_id);

2 int avcodec_open(AVCodecContext*avctx, AVCodec *codec);

usage: opens / inits the AVCodecContext.打开失败的话,返回值小于零。

avcodec_open(c, codec);

3 void *av_malloc(unsigned in size);

usage: you can redefine av_mallocand av_free in your project to use your memory allocator. You do not need

to suppress this file because thelinker will do it automatically

Memory allocation of size byte withalignment suitable for all memory accesses (including vectors if

available on the CPU). av_malloc(0)must return a non NULL pointer.

video_outbuf_size = 200000;

video_outbuf =avmalloc(video_outbuf_size);

4 static AVFrame *alloc_picture(intpix_fmt, int width, int height)

picture =alloc_picture(c->pix_fmt, c->width, c->height);

/******************************************************************

******************************************************************

******************************************************************

alloc_picture()

AVFrame *picture

uint8_t *picture_buf

int size

******************************************************************

******************************************************************/

******************************************************************

1. avcodec_alloc_frame()

usage: initialize AVFrame* picture

picture = avcodec_alloc_frame()

2. int avpicture_get_size(intpix_fmt, int width, int height)

usage: 根据像素格式和视频分辨率获得picture存储大小。

size = avpicture_get_size(pix_fmt,width, height);

picture_buf = av_malloc(size)

3. int avpicture_fill(AVPicture*picture, uint8_t *ptr, int pix_fmt, int width, int height)

usage: Picture field are filled with‘ptr’ addresses, also return size。用ptr中的内容根据文件格式(YUV…)

和分辨率填充picture。这里由于是在初始化阶段,所以填充的可能全是零。

avpicture_fill((AVPicture*)picture,picture_buf, pix_fmt, width, height);

/******************************************************************

******************************************************************

write_video_frame()

int out_size, ret;

AVCodecContext *c;

static struct SwsContext*img_convert_ctx

******************************************************************

******************************************************************/

1 struct SwsContext*sws_getContext(int srcW, ……)

usage: 转变raw picture格式的获取context函数,比如下面的代码就是将其他格式的(如yuv422)转为yuv420,就要将

context 保存在img_convert_ctx中,然后再利用后面的介绍函数做转化。

img_convert_ctx =sws_getContext(c->width, c->height,

PIX_FMT_YUV420P,

c->width, c->height,

c->pix_fmt,

sws_flags, NULL, NULL, NULL);

2 int sws_scale(struct SwsContext*ctx, uing8_t* src[], int srcStride[], int srcSliceY, int srcSliceH,

uint8_t* dst[], int dstStride[]);

usage: 根据SwsContext保存的目标文件context将src(source)转为dst(destination)。

sws_scale(img_convert_ctx,tmp_picture->data, tmp_picture->linesize, 0, c->height,picture->data, picture-

>linesize);

4. intavcodec_encode_video(AVCodecContext *avctx, uint8_t *buf, int buf_size, constAVFrame *pict);

usage: 根据AVCodecContext将pict编码到buf中,buf_size是buf的大小。

out_size = avcodec_encode_video(c,video_outbuf, video_outbuf_size, picture);

5 static inline voidav_init_packet(AVPacket *pkt)

usage: initialize optional fields ofa packet.初始化AVPacket。

AVPacket pkt;

av_init_packet(&pkt)

6 int64_t av_rescale_q(int64_t a,AVRational bq, AVRational cq)

usage: 校准时间基(maybe)

pkt.pts =av_rescale_q(c->coded_frame->pts, c->time_base, st->time_base);

7 int av_write_frame(AVFormatContext*s, AVPacket *pkt)

usage: write a packet to an outputmedia file . pkt: the packet, which contains the stream_index,

buf/buf_size, dts/pts, …if errorreturn<0, if OK =0, if end of stream wanted =1

ret = av_write_frame(oc, &pke);

/******************************************************************

******************************************************************

static voidclose_video(AVFormatContext *oc, AVStream *st)

{

avcodec_close(st->codec);

av_free(picture->data[0]);

av_free(picture);

if (tmp_picture) {

av_free(tmp_picture->data[0]);

av_free(tmp_picture);

}

av_free(video_outbuf);

}

/******************************************************************

******************************************************************

讲初始化过的,特别是分配过内存的,都要释放。

注:标定红色的函数为需要编程人员自己按照实际应用情况编写的,蓝色的是FFMpegSDK中定义的函数。我们会分析函数的

作用和调用方法。

由于时间关系,写得很乱。希望没有给你带来太大困扰,这份不成熟的文档能对你有一点点地帮助。chinavideo版上有很多

大牛,我想做抛砖引玉者,希望以后能有更好的文档出现。我也是ffmpeg的初学者,如果大家有什么问题也希望能在版上一

起讨论。我的email:[email protected]


你可能感兴趣的:(ffmpeg 过程分析)