FFMpeg SDK 開發手冊(2)
:轉載時請以超連結形式標明文章原始出處和作者資訊及本宣告
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_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 framen");
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);
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/24790158/viewspace-1040904/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- MeterSphere開發者手冊
- [開發文件]bootstrap中文手冊boot
- Web 開發手冊——PHP 開發環境搭建WebPHP開發環境
- wxpython - 快速開發封裝手冊Python封裝
- 阿里Java開發手冊思考(三)阿里Java
- 阿里Java開發手冊思考(二)阿里Java
- 阿里Java開發手冊思考(一)阿里Java
- base業務框架開發手冊框架
- 阿里巴巴Java開發手冊阿里Java
- Web前端開發規範手冊Web前端
- TensorFlow開發者證書 中文手冊
- Java開發手冊精華總結Java
- MaxPHP(原Yao框架)完全開發手冊PHP框架
- 安卓開發開發規範手冊V1.0安卓
- 安卓開發開發規範手冊 V1.0安卓
- 阿里巴巴java開發手冊筆記阿里Java筆記
- Flutter開發者必備手冊 Flutter GoFlutterGo
- 阿里巴巴Java開發規範手冊阿里Java
- Qt 嵌入式圖形開發大全和QT開發手冊QT
- 開發者手冊之如何成為 OceanBase Contributor
- javacv教程文件手冊開發指南匯總篇Java
- 純乾貨:微服務開發手冊之GRPC微服務RPC
- Web 安全開發規範手冊 V1.0Web
- Web安全開發規範手冊V1.0Web
- 《阿里巴巴 Java開發手冊》讀後感阿里Java
- 瑞芯微RK3288_Android9.0 SDK版本說明手冊Android
- 研發環境手冊
- 影片SDK開發,多平臺SDK快速接入
- 阿里巴巴Android開發手冊V1.0.0隨手筆記阿里Android筆記
- 君正x1000軟體開發指南手冊
- 唯品會Java開發手冊》1.0.2版閱讀Java
- F5 api介面開發實戰手冊(二)API
- 《碼出高效:Java開發手冊》背後的故事Java
- 阿里巴巴Java開發手冊閱讀筆記阿里Java筆記
- FFmpeg開發筆記(三十五)Windows環境給FFmpeg整合libsrt筆記Windows
- FFmpeg開發筆記(八)Linux交叉編譯Android的FFmpeg庫筆記Linux編譯Android
- Android 基於ffmpeg開發簡易播放器 - ffmpeg解封裝Android播放器封裝
- 四、Clion搭建FFmpeg開發環境開發環境
- 微信公眾號開發 2 : weixin-JS-sdk 的使用JS