本文主要介紹ffmpeg解碼器內部管理Video Buffer的原理和過程,ffmpeg的Videobuffer為內部管理,其流程大致為:注冊處理函數->幀級釋放->幀級申請->清空。
1?注冊get_buffer()和release_buffer()
FFAPI_InitCodec()
avcodec_alloc_context()
avcodec_alloc_context2()
avcodec_get_context_default2(AVCodecContext *s,...){
......
s->get_buffer = avcodec_default_get_buffer;
s->release_buffer = avcodec_default_release_buffer;
......
}
2幀級的內存申請和釋放調用
圖1幀級內存申請和釋放的函數調用
2.1 FFAPI函數調用libavcodec相應的codec(WMV3對應的Codec是VC1)函數進行解碼,過程中調用內部buffer處理函數。其中buffer管理被統一封裝到Mpegvideo接口中(包括的codec有H.261, H.263, H.264, mpeg12, rv10,rv34, svq1和VC1)
FFAPI_Decode()
???????avcodec_decode_video2()
??????????????avctx->codec->decode()//初始化過程中注冊codec,wmv3的解碼函數是
??????????????vc1_decode_frame(){
???????decode_vc1_header;
???????MPV_frame_start();?????????????????????????????????????//2.2.2
???????vc1_decode_blocks();
???????MPV_frame_end();?????????????????????????????????????//2.2.3
}
2.2 MPV_frame_start()//通過調用get_buffer()申請當前幀的video buffer。
MPV_frame_start()
???????//首先調用release_buffer()釋放非參考幀的video buffer
???????for(i=0; i<MAX_PICTURE_COUNT; i++)
if(s->picture[i].data[0] && !s->picture[i].reference)
free_frame_buffer(s, &s->picture[i]); //調用s->avctx->get_buffer(),回調avcodec_default_release_buffer()
?
???????ff_alloc_picture()
??????????????alloc_frame_buffer()
?????????????????????s->avctx->get_buffer()??????//回調avcodec_default_get_buffer()
2.3MPV_frame_end()??????????????????????????????????????????//完成視頻加邊等操作
?
3幀級的內存申請和釋放處理方法
3.1內部buffer數據結構
–???typedef struct InternalBuffer{
–???????int last_pic_num;??????????????
–???????uint8_t *base[4];?????????????
–???????uint8_t *data[4];?????????????
–???????int linesize[4];???????????
–???????int width, height;????????????
–???????enum PixelFormat pix_fmt;???
–???}InternalBuffer;
–???typedef struct AVCodecContext {
–??????????……
–???int internal_buffer_count;?//記錄當前內部buffer的個數,get_buffer和release_buffer時均需要對其進行維護。
–???void *internal_buffer;//初始化為數組InternalBuffer [INTERNAL_BUFFER_SIZE]
–???……
–???}?AVCodecContext;
Codec通過維護internal_buffer_count和internal_buffer實現高效的內存管理。
3.2參考幀管理相關數據結構
–???typedef??struct Picture{
–???????uint8_t *data[4];
–???????int linesize[4];
–???????uint8_t *base[4];
–???????int reference;
–???????……
–???}?Picture;
–???typedef??struct MpegEncContext{
–???????……
–???????Picture* picture;???//初始化為數組Picture[INTERNAL_BUFFER_SIZE]
–???????Picture* last_picture_ptr;??????//指向前一幀
–???????Picture* next_picture_ptr;;????//雙向預測時,指向后一幀
–???????Picture* current_picture_ptr;//指向當前幀
–???……
–???}?MpegEncContext;?
3.3申請和釋放原理
圖2?內存申請和釋放原理
(1)初始化時將internal_buffer全部清零
(2)釋放buffer時,將釋放的buffer與最后一個有效buffer交換,而不是用av_free()釋放內存。
avcodec_default_release_buffer(AVCodecContext *s, AVFrame *pic){
s->internal_buffer_count--;
????last = &((InternalBuffer*)s->internal_buffer)[s->internal_buffer_count];
????//將last buffer和要釋放的buffer交換,使last buffer變成無效buffer,在下次get_buffer時能被申請到。
FFSWAP(InternalBuffer, *buf, *last);
????for(i=0; i<4; i++){
????????pic->data[i]=NULL;
????}
}
(3)申請buffer時,檢查internal_buffer[internal_buffer_count]的基址是否非空,若非空則直接使用internal_buffer[internal_buffer_count];若空,使用av_malloc()函數進行申請。
這樣處理的好處是避免了頻繁的調用malloc()和free(),從而提升了效率。
avcodec_default_get_buffer(AVCodecContext *s, AVFrame *pic){
???????……
???????buf= &((InternalBuffer*)s->internal_buffer)[s->internal_buffer_count];
???????get_size_info(size[]);
???????buf->base[0, 1, 2] = av_malloc(size[0, 1, 2]);
???????buf->data[0, 1, 2] = buf->base[0, 1, 2] + padding_offset[0, 1, 2];
???????……
}
(4)決定輸出幀是在每幀解碼后,根據當前幀的類型和參考信息決定輸出幀。
if (s->pict_type == FF_B_TYPE || s->low_delay) {
*pict= *(AVFrame*)s->current_picture_ptr;
} else if (s->last_picture_ptr != NULL) {
*pict= *(AVFrame*)s->last_picture_ptr;
}
3.4舉例——假設解碼IPBPB的非H.264碼流。
(1)初始化后的狀態如所示,IBC為ctx->internal_buffer_count,CurPtr為s->current_picture_ptr,LastPtr為s->last_picture_ptr,NextPtr為s->next_picture_ptr。
gpAVPicture指針為輸出圖像的指針。
圖3?初始化狀態
(2)解碼第一個I幀,過程中不會不調用release_buffer(),get_buffer()得到picture[0]?,此時不輸出任何圖像。
圖4解碼第一個I幀后的狀態
(3)解碼第一個P幀,過程中不調用release_buffer(),get_buffer()得到picture[1]?,輸出picture[0]。
圖5解碼第一個P幀后的狀態
(4)解碼第一個B幀,過程中不調用release_buffer(),get_buffer()得到picture[2]?,輸出picture[2]。
圖6解碼第一個B幀后的狀態
(5)解碼第二個P幀,調用release_buffer(&picture[2]),再調用get_buffer(),得到picture[2],?輸出picture[1]。
圖7解碼第二個P幀的狀態