Chinaunix首页 | 论坛 | 博客
  • 博客访问: 384568
  • 博文数量: 35
  • 博客积分: 2500
  • 博客等级: 少校
  • 技术积分: 797
  • 用 户 组: 普通用户
  • 注册时间: 2007-03-02 08:51
文章分类

全部博文(35)

文章存档

2011年(1)

2010年(3)

2009年(3)

2008年(28)

我的朋友

分类: WINDOWS

2008-08-18 23:42:44

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; // 400 kbits/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_lmaxconstant

quantization parameter rate control method: cqp等。

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

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

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

初始化後,可以说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);

VAVCodec

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

 

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

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

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

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

就能很好的初始化了:

//初始化

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

//打开Codec

avcodec_openc, 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],第一个存放的是YCbCryuv格式),linesize是啥?由这两个数据还能提取处另外一个

数据结构:

typedef struct AVPicture {

uint8_t *data[4];

int linesize[4]; // number of bytes per 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(int pix_fmt, int width, int height){

AVFrame *picture;

uint8_t *picture_buf; // think about why 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 *)pictureAVPicture这个结构也很有用。基本上他的大小也就是要在

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

 

VIIAVPicture

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)中,实际上是对AVFramedata[4]linesize[4]分配内存。由于这两个数据大小如何分配确实需要有pix_fmt

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

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

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

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

VIIIAVPacket

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

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

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

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

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

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

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

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

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

av_init_packet(&pkt);

pkt.flags |= PKT_FLAG_KEY // raw picture 中,每帧都是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_sizeopen_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 the media file */

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

} else {

ret = 0;

}

if (ret != 0) {

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

exit(1);

}

其中video_outbufvideo_outbuf_sizeopen_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_outbufvideo_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)操作函数。作为一个整体化的代码SDKFFMpeg有一些他自己的标准

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

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

 

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

main()

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

1. av_register_all ();

usage: initialize ibavcoded, and register all codecs and formats

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

宏有兴趣看看。

2. AVOutputFormat guess_format(const char *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 media context.实际是初始化AVFormatContext的成员数据AVClass

AVFormatContext *ic;

ic->av_class = &av_format_context_class;

//where

// format_to_name, options are pointer to function

static const AVClass av_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. int av_set_parameters(AVFormatContext *s, AVFormatParameters *ap)

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

AVFormatContext *oc;

// if failed, return integer smaller than 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 void open_video(AVFormatContext *oc, AVStream *st)

open_video(oc, video_st);

8. int av_write_header(AVFormatContext *s)

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

file

handle, return 0 if OK, AVERROR_xxx if error.

write the stream header, if any

av_write_header(oc);

9. static void write_video_frame(AVFormatContext *oc, AVStream *st)

write_video_frame(oc, video_st);

10. static void close_video(AVFormatContext *oc, AVStream *st)

// close each codec

close_video(oc, video_st);

11. int av_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. Frees memory 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);

阅读(4869) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~