分类: 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_lmax、constant
quantization parameter rate control method: 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 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 *)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 // 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_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 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_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, and register all codecs and formats
每个使用FFMpeg SDK的工程都必须调用的函数。进行codec和format的注册,然后才能使用。声明在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);