本文主要介绍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帧的状态 (allen_young_yang) |