当前位置:网站首页>FFMPEG关键结构体——AVFrame

FFMPEG关键结构体——AVFrame

2022-07-05 23:56:00 陈小帅hh

一、AVFrame结构体

AVFrame结构体一般用于存储原始数据(即非压缩数据,例如对视频来说是YUV,RGB,对音频来说是PCM),此外还包含了一些相关的信息。

比如说,解码的时候存储了宏块类型表,QP表,运动矢量表等数据。编码的时候也存储了相关的数据。因此在使用FFMPEG进行码流分析的时候,AVFrame是一个很重要的结构体。

AVFramet通常在解码时包含较多的码流参数,编码时主要用于承载图像数据或者音频采样数据。结构体的定义位于libavutil/frame.h,这里介绍解码情况下的主要变量
在这里插入图片描述

1.变量介绍

uint8_t *data[AV_NUM_DATA_POINTERS];
(1)图像数据:
对于packed格式的数据(例如RGB24),会存到data[0]里面。
对于planar格式的数据(例如YUV420P),则会分开成data[0],data[1],data[2]…(YUV420P中data[0]存Y,data[1]存U,data[2]存V)
(2)音频数据:
采样数据PCM, 保存方式同图像数据。 对于对于planar格式的音频数据通道数超过8时,其余通道数据存放于extended_data中。

int linesize[AV_NUM_DATA_POINTERS];
行字节的跨度,相当于stride。对于data[i]区域中的一行像素占用的字节数,对于RGB24理论是{wh3, 0, …}; 对于yuv420p,理论是{w, w/2, w/2, 0, …}。但ffmpeg内存会填充对齐,实际行字节数会大于等于理论值。
enum AVPictureType pict_type;
帧数据类型,部分摘取如下
在这里插入图片描述

二、常用函数介绍

1、av_frame_alloc()
申请AVFrame结构体空间,同时会对申请的结构体初始化。注意哦,这个函数只是创建AVFrame结构的空间,AVFrame中的uint8_t *data[AV_NUM_DATA_POINTERS]空间此时NULL,不会创建的。

2、av_frame_free()
释放AVFrame的结构体空间。这个函数就有点意思了。因为他不仅仅释放结构体空间,还涉及到AVFrame中的uint8_t *data[AV_NUM_DATA_POINTERS];字段的释放问题。,如果AVFrame中的uint8_t *data[AV_NUM_DATA_POINTERS]中的引用==1,则释放data的空间。

3、int av_frame_ref(AVFrame *dst, const AVFrame *src)
对已有AVFrame的引用,这个引用做了两个动作:1、将src属性内容复制到dst,2、对AVFrame中的uint8_t *data[AV_NUM_DATA_POINTERS]字段引用计数+1。

4、void av_frame_unref(AVFrame *frame)
对frame释放引用,做了两个动作:1、将frame的各个属性初始化,2、如果AVFrame中的uint8_t *data[AV_NUM_DATA_POINTERS]中的引用==1,则释放data的空间。当然,如果data的引用计数>1则由别的frame去检测释放。

5、av_frame_get_buffer()
这个函数是建立AVFrame中的uint8_t *data[AV_NUM_DATA_POINTERS]内存空间,使用这个函数之前frame结构中的format、width、height:必须赋值,要不然函数怎么知道创建多少字节的空间呢!

三、常规解码流程使用

3个步骤:
①AVFrame *pFrame = av_frame_alloc()分配一个AVFrame对象,缓冲区data[]未分配。
②使用调用av_receive_frame解码,会对pFrame分配data[]缓冲区并保存解码数据;每一次使用后,必须需要使用av_frame_unref释放缓冲区,否则重复解码会造成内存泄露。
③需要使用av_frame_free 释放整个对象。

AVFrame *pFrame = av_frame_alloc();  // [1]
while(){
    av_receive_frame(ctx, pFrame);// process
	av_frame_unref(pFrame);         // [2]
} 
av_frame_free(pFrame);              // [3]

四、图像处理

前面提到,ffmpeg内部编码器对于图像处理部分为了方便优化处理,通常创建的缓冲区比原始图像大,实际有效数据部分只是缓冲区的一部分。这一种优化方案直接反应在linesize上,使用8或16或32字节对齐,取决于平台。

对于分辨率为638*272的视频解码后yuv420p的缓冲区 linsesize为{640,320,320,…},内存布局结构如下图
在这里插入图片描述
图中,其中w = 640 , h = 320。可以看到,在三个通道中每一行数据间进行了数据填充,Y区域的行字节数不等于638,U/V区域的行字节数也不等于319。注意观察data[2],data[1],data[0]之间的差值。

如果我们需要yuv三个分量无填充的 yuv420p数据,可以手动分配大小为wh3/2的内存,再从解码后AVFrame的data[]中拷贝出来。

1、分配缓冲区内存

①方法一:使用malloc原生的内存管理方式,

uint8_t yuv_buf = malloc(w*h*3/2);

②方法二:fmpeg内存分配函数

// yuv420p对齐处理 变量
AVFrame *frame_yuv = av_frame_alloc();
// 分配缓冲区,接收转换后yuv420p的1字节对齐数据,分辨率不改变
av_image_alloc(frame_yuv->data, frame_yuv->linesize,
               video_decoder_ctx->width, video_decoder_ctx->height, AV_PIX_FMT_YUV420P, 1);
// 对于编码,需要AVFrame有对应的参数
frame_yuv->width = video_decoder_ctx->width;
frame_yuv->height = video_decoder_ctx->height;
frame_yuv->format = AV_PIX_FMT_YUV420P;

③方法三:原生指针加ffmpeg内存分配函数

uint8_t *yuvbuf;
int linesize[4];
av_image_alloc(&yuvbuf, linesize, video_decoder_ctx->width, video_decoder_ctx->height, 
                   AV_PIX_FMT_YUV420P, 1);

④方法四:使用av_frame_get_buffer函数(注意检查内存是否连续)

AVFrame *frame_yuv = av_frame_alloc();
frame_yuv->width = video_decoder_ctx->width;
frame_yuv->height = video_decoder_ctx->height;
frame_yuv->format = AV_PIX_FMT_YUV420P;
// 使用一下函数必须先指定frame的 音频/视频 参数
av_frame_get_buffer(frame_yuv, 1);  // align = 0, 由系统选择最优对齐方式

注意,av_frame_get_buffer(frame_yuv, 1); 保证y,u,v分量数据区域是1字节对齐的、连续的,但是y,u,v三个数据区不是1字节对齐、也不是连续的。例如,手动分配100*100尺寸的yuv420的数据分量部分

AVFrame *frame_yuv = av_frame_alloc();
frame_yuv->width = 100;
frame_yuv->height = 100;
frame_yuv->format = AV_PIX_FMT_YUV420P;
av_frame_get_buffer(frame_yuv, 1);

结果如下图,三个分量数据区指针从小到大为 u < y < v,指针之间的间隔不等于wh,也不等于wh/2。

注意:后两种方式一种是使用裸指针,一种借助AVFrame,虽然访问管理缓冲区的方式不同,但是都用到了av_image_alloc,都需要传递缓冲区指针和接收linesize的数组。

原网站

版权声明
本文为[陈小帅hh]所创,转载请带上原文链接,感谢
https://blog.csdn.net/PPPPPPPKD/article/details/125586279