FFMpeg SDK 開發手冊(2)

helloxchen發表於2010-11-04

:轉載時請以超連結形式標明文章原始出處和作者資訊及本宣告
http://leezen.blogbus.com/logs/16084488.html

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 framen");

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);

[@more@]

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/24790158/viewspace-1040904/,如需轉載,請註明出處,否則將追究法律責任。

相關文章