【轉載】FFMpeg SDK 開發手冊
FFMpeg 中比較重要的函式以及資料結構如下:
1. 資料結構:
(1) AVFormatContext
(2) AVOutputFormat
(3) AVInputFormat
(4) AVCodecContext
(5) AVCodec
(6) AVFrame
(7) AVPacket
(8) AVPicture
(9) AVStream
2. 初始化函式:
(1) av_register_all()
(2) avcodec_open()
(3) avcodec_close()
(4) av_open_input_file()
(5) av_find_input_format()
(6) av_find_stream_info()
(7) av_close_input_file()
3. 音視訊編解碼函式:
(1) avcodec_find_decoder()
(2) avcodec_alloc_frame()
(3) avpicture_get_size()
(4) avpicture_fill()
(5) img_convert()
(6) avcodec_alloc_context()
(7) avcodec_decode_video()
(8) av_free_packet()
(9) av_free()
4. 檔案操作:
(1) avnew_steam()
(2) av_read_frame()
(3) av_write_frame()
(4) dump_format()
5. 其他函式:
(1) avpicture_deinterlace()
(2) ImgReSampleContext()
以下就根據,以上資料結構及函式在ffmpeg測試程式碼output_example.c中出現的前後順進行分析。在此之前還是先談一下
ffmpeg的編譯問題。在linux下的編譯比較簡單,這裡不多說了。在windows下的編譯可以參考以下網頁:
http://bbs.chinavideo.org/viewthread.php?tid=1897&extra=page%3D1
值得一提的是,在使用編譯後的sdk進行測試時(用到ffmpeg目錄下的output_example.c)編譯過程中可能會有以下兩個問
題:
1. Output_example.c用到了snprintf.h這個標頭檔案。然而這個標頭檔案在win下和linux下有所不同。具體在win下
可以用以下方法解決:
http://www.ijs.si/software/snprintf/
2. 如果使用vc6,或是vc6的命令列進行編譯,inline可能不認。錯誤會出現在common.h檔案中,可以在common.h中加入
#ifdef _MSC_VAR
#define inline __inline
#endif
交待完畢進入正題。
一.FFMpeg 中的資料結構:
I. AVFormatContext
一般在使用ffmpeg sdk的程式碼中AVFormatContext是一個貫穿始終的資料結構,很多函式都要用到它作為引數。FFmpeg程式碼
中對這個資料結構的註釋是:format I/O context
此結構包含了一個視訊流的格式內容。其中存有了AVInputFormat(or AVOutputFormat同一時間AVFormatContext內只能存
在其中一個),和AVStream、AVPacket這幾個重要的資料結構以及一些其他的相關資訊,比如title,author,copyright等。
還有一些可能在編解碼中會用到的資訊,諸如:duration, file_size, bit_rate等。參考avformat.h標頭檔案。
Useage:
宣告:
AVFormatContext *oc; (1)
初始化: 由於AVFormatConext結構包含許多資訊因此初始化過程是分步完成,而且有些變數如果沒有值可用,也可不初始
化。但是由於一般宣告都是用指標因此一個分配記憶體過程不可少:
oc = av_alloc_format_context(); (2)
結構中的AVInputFormat*(或AVOutputFormat*)是一定要初始化的,基本上這是編譯碼要使用什麼codec的依據所在:
oc->oformat = fmt; or oc->iformat = fmt; (3)
其中AVOutputFormat* fmt或AVInputFormat* fmt。(AVInputFormat and AVOutputFormat的初始化在後面介紹。隨後在參
考程式碼output_example.c中有一行:
snprintf(oc-filename, sizeof(oc->filename), “%s”, filename); (4)
還不是十分清楚有什麼作用,估計是先要在輸出檔案中寫一些頭資訊。
在完成以上步驟後,(初始化完畢AVInputFormat*(或AVOutputFormat*)以及AVFormatContext)接下來就是要利用oc初始
化本節開始講到的AVFormatContext中的第二個重要結構。AVStream(假設已經有了宣告AVStream *video_st。參考程式碼中
用了一個函式來完成初始化,當然也可以在主函式中做,傳遞進函式的引數是oc 和fmt->video_codec(這個在下一節介紹
(29)):
vdeo_st = add_video_stream(oc, fmt->video_codec); (5)
此函式會在後面講到AVStream結構時分析。
AVFormatContext最後的一個設定工作是:
if( av_set_paramters(oc,NULL) < 0){ (6)
//handle error;
}
dump_format(oc, 0, filename, 1); (7)
作用就是看看先前的初始化過程中設定的引數是否符合規範,否則將報錯。
上面講的都是初始化的過程,包括AVFormatContext本身的和利用AVFormatContext初始化其他資料結構的。接下來要講講整
個的編解碼過程。我想先將ouput_example.c中main函式內的編解碼函式框架描述一下。這樣比較清晰,而且編碼者為了結
構清晰,在寫ouput_example.c的過程中也基本上在main函式中只保持AVFormatContext和AVStream兩個資料結構
(AVOutputFormat其實也在但是包含在AVFormatContext中了)。
// open video codec and allocate the necessary encode buffers
if(video_st)
open_video(oc, video_st); (8)
// write the stream header, if any
av_write_header(oc); (9)
// encode and decode process
for(; ;){
write_video_frame(oc, video_st); (10)
// break condition…here
}
//close codec
if(video_st)
close_video(oc, video_st); (11)
//write the trailer , if any
av_write_trailer(oc); (12)
// free the streams
for(i=0; i<oc->b_streams; i++){
av_freep(&oc->streams[i]->codec); (13)
av_freep(&oc->streams[i]); (14)
}
//close the ouput file
if(!(fmt->flags & AVFMT_NOFILE)){
url_fclose(&oc->pb); (15)
}
av_free(oc); (16)
通過以上的一串程式碼,就可以清晰地看出AVFormatContex* oc和AVStream* video_st是在使用ffmpeg SDK開發時貫穿始終的
兩個資料結構。以下,簡要介紹一下三個標為紅色的函式,他們是參考程式碼output_example.c開發者自行定義的函式。這樣
可以使整個程式碼結構清晰,當然你在使用ffmpeg SDK時也可以在主函式中完成對應的功能。在後面我們會專門針對這三個函
數做分析。
1. open_video(oc, video_st);
此函式主要是對視訊編碼器(或解碼器)的初始化過程。初始化的資料結構為AVCodec* codec和AVCodecContext* c包括用
到了的SDK函式有:
c = st->codec;
codec = avcodec_find_encoder(c->codec_id); //編碼時,找編碼器 (17)
codec = avcodec_find_decoder(c->codec_id); //解碼時,找解碼器 (18)
AVCodecContex是結構AVStream中的一個數據結構,因此在AVStream初始化後(5)直接復值給c。
// internal open video codec
avcodec_open(c,codec); (19)
// allocate video stream buffer
// AVFrame *picture
// uint8_t *video_outbuf
video_outbuf_size=200000;
video_outbuf = av_maloc(video_outbuf_size); (20)
// allocate video frame buffer
picture = alloc_picture(c->pix_fmt, c->width, c->height); (21)
上述三步比較容易理解,開啟視訊編解碼codec、分配輸出流快取大小、分配每一幀影象快取大小。其中AVFrame也是ffmpeg
中主要資料結構之一。這一步(8)是對編解碼器的初始化過程。
2. write_video_frame(AVFormatContext *oc, AVStream *st)
這個函式中做了真正的編解碼工作,其中的函式比較複雜先列出來慢慢分析。
用到的資料結構有AVCodecContext *c, SwsContext *img_convert_ctx。其中SwsContext是用來變換影象格式的。比如
yuv422變到yuv420等,當然也用到函式,見下面列表。
fill_yuv_image(tmp_picture, frame_count, c->width, c->height); (22)
sws_scale(img_convert_ctx, tmp_picture->, tmp_picture->linesize,
0, c->height, picture->data, picture->linesize); (23)
img_convert_ctx = sws_getContxt(c->width, c->height, PIX_FMT_YUV420P, (24)
c->width, c->heigth, c->pix_fmt, sws_flags, NULL, NULL, NULL);
由於參考程式碼中做的是一個編碼。因此,它總是要求編碼器輸入的是yuv檔案,而且是yuv420格式的。就會有了以上一些處
理過程。接下來呼叫編碼器編碼,資料規則化(打包)用到AVPacket,這也是ffmpeg中一個比較不好理解的地方。
out_size = avcodec_encode_video(c, video_outbuf, video_outbuf_size, picture); (25)
AVPacket pkt;
av_init_packet(&pkt); (26)
//……handle pkt process, we will analyze later
ret = av_write_frame(oc, &pkt); (27)
有encode就一定會有decode。而且ffmpeg專為解碼而生,但是為什麼在參考程式碼中只用了encoder呢?個人猜想是因為
encode只是用yuv420來編碼,這樣的yuv420生成比較容易,要是用到解碼的化,還要在程式碼中附帶一個其他格式的音視訊文
件。在原始碼libavcodec資料夾中有一個apiexample.c的參考程式碼,其中就做了編解碼。有空的化我會分析一下。
3. close_video(AVFormatContext *oc, AVStream *st)
avcodec_close(st->codec);
av_free(picture->data[0]);
av_free(picture);
av_free(video_outbuf);
比較容易理解,不多說了。
以上一大段雖然名為介紹AVFormatContext。但基本上把ouput_example.c的視訊編碼部分的框架走了一遍,其一是想說明結
構AVFormatContext的重要性,另一方面也是希望對使用FFMpeg SDK開發者有一個大致的框架。
其實,真正的一些編碼函式,記憶體分配函式在SDK中都已經封裝好了,只要搞清楚結構就能用了。而開發者要做的就是一些
初始化的過程,基本上就是針對資料結構1的初始化。
II. AVOutputFormat
雖然簡單(初始化)但是十分重要,他是編解碼器將要使用哪個codec的“指示”。在其成員資料中最重要的就是關於視訊
codec的了:enum CodecID video_codec;
AVOutputFormat *fmt;
fmt = guess_format(NULL, filename, NULL); (28)
根據filename來判斷檔案格式,同時也初始化了用什麼編碼器。當然,如果是用AVInputFormat *fmt的化,就是fix用什麼
解碼器。(指定輸出序列->fix編碼器,指定輸入序列->fix解碼器?)
III. AVStream
AVStream作為繼AVFormatContext後第二個貫穿始終的結構是有其理由的。他的成員資料中有AVCodecContext這基本的上是
對所使用的Video Codec的引數進行設定的(包括bit rate、解析度等重要資訊)。同時作為“Stream”,它包含了“流”
這個概念中的一些資料,比如:幀率(r_frame_rate)、基本時間計量單位(time_base)、(需要編解碼的)首幀位置
(start_time)、持續時間(duration)、幀數(nb_frames)以及一些ip資訊。當然後面的這些資訊中有些不是必須要初
始化的,但是AVCodecContex是一定要初始化的,而且就是作為初始化AVStream最重要的一個部分。我們在前面就談到了
AVStream的初始化函式(5),現在來看看他是怎麼做的:
// declaration
AVStream *video_st;
video_st = add_video_stream(oc, fmt->video_codec);
static AVStream *add_video_stream(AVFormatContex *oc, int codec_id){ (29)
AVCodecContext *c; // member of AVStream, which will be initialized here
AVStream *st; // temporary data, will be returned
st = av_new_stream(oc, 0); (30)
c = st->codec;
// 以下基本是針對c的初始化過程。包括位元率、解析度、GOP大小等。
……
// 以下的兩行需要注意一下,特別是使用MP4的
if(!strcmp(oc->oformat->name, “mp4”) || !strcmp(oc->oformat->name, “mov”) || !strcmp(oc->oformat->name,
“3gp”))
c->flags |= CODEC_FLAG_GLOBAL_HEADER;
// 將st傳給video_st;
return st;
}
以上程式碼中,有幾點需要注意的。一個是(30)和c = st->codec是一定要做的,當然這是程式設計中最基本的問題,(30)是將st
這個AVSteam繫結到AVFormatContext* oc上。後面的c = st->codec是將c繫結到st的AVCodecContext上。其二是對c的初始
化過程中,ouput_example.c裡做的是一些基本的配置,當然作為使用者的你還希望對codec加入其他的一些編解碼的條件。
可以參考avcodec.h裡關於AVCodecContext結構的介紹,註釋比較詳細的。
關於AVStream的使用在前面介紹AVFormatContext時已有所涉及,在主函式中三個編解碼函式中(8)、(10)和(11)中。觀察相
關的程式碼,可以發現主要還是將AVStream中的AVCodecContext提取出來,再從中提取出AVCodec結構如在(8)中:
// open_video(oc, video_st);
// AVFormatContext *oc, AVStream *st
AVCodec *codec;
AVCodecContext *c;
c = st->codec;
codec = avcodec_find_encoder(c->codec_id); (31)
// open the codec
avcodec_open(c, codec); (32)
同樣,我們可以看到在(10)(write_video_frame())中AVFrame也是做為傳遞AVCodecContext結構的載體而存在。(11)
(close_video())比較簡單,不熬述。
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);
14. void av_free(void *ptr)
usage: free the stream. Free memory which 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 media file. 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(enum CodecID id)
usage: find the codec of encoder by CodecID. 在前面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_malloc and av_free in your project to use your memory allocator. You do not need
to suppress this file because the linker will do it automatically
Memory allocation of size byte with alignment 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(int pix_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(int pix_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. int avcodec_encode_video(AVCodecContext *avctx, uint8_t *buf, int buf_size, const AVFrame *pict);
usage: 根據AVCodecContext將pict編碼到buf中,buf_size是buf的大小。
out_size = avcodec_encode_video(c, video_outbuf, video_outbuf_size, picture);
5 static inline void av_init_packet(AVPacket *pkt)
usage: initialize optional fields of a 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 output media file . pkt: the packet, which contains the stream_index,
buf/buf_size, dts/pts, …if error return<0, if OK =0, if end of stream wanted =1
ret = av_write_frame(oc, &pke);
/******************************************************************
******************************************************************
static void close_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);
}
/******************************************************************
******************************************************************
講初始化過的,特別是分配過記憶體的,都要釋放。
注:標定紅色的函式為需要程式設計人員自己按照實際應用情況編寫的,藍色的是FFMpeg SDK中定義的函式。我們會分析函式的
作用和呼叫方法。
由於時間關係,寫得很亂。希望沒有給你帶來太大困擾,這份不成熟的文件能對你有一點點地幫助。chinavideo版上有很多