ffmpeg学习 结构体分析AVFrame

1、AVFrame结构体介绍

AVFrame结构体一般用于存储原始数据(即非压缩数据,例如对视频来说是YUV,RGB,对音频来说是PCM),此外还包含了一些相关的信息。比如说,解码的时候存储了宏块类型表,QP表,运动矢量表等数据。编码的时候也存储了相关的数据。因此在使用FFMPEG进行码流分析的时候,AVFrame是一个很重要的结构体。

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

uint8_t *data[AV_NUM_DATA_POINTERS]:解码后原始数据(对视频来说是YUV,RGB,对音频来说是PCM)
​
int linesize[AV_NUM_DATA_POINTERS]:data中“一行”数据的大小。注意:未必等于图像的宽,一般大于图像的宽。
​
int width, height:视频帧宽和高(1920x1080,1280x720...)
​
int nb_samples:音频的一个AVFrame中可能包含多个音频帧,在此标记包含了几个
​
int format:解码后原始数据类型(YUV420,YUV422,RGB24...)
​
int key_frame:是否是关键帧
​
enum AVPictureType pict_type:帧类型(I,B,P...)
​
AVRational sample_aspect_ratio:宽高比(16:9,4:3...)
​
int64_t pts:显示时间戳
​
int coded_picture_number:编码帧序号
​
int display_picture_number:显示帧序号

(1) data[]

  • 图像数据 对于packed格式的数据(例如RGB24),会存到data[0]里面。 对于planar格式的数据(例如YUV420P),则会分开成data[0],data[1],data[2]…(YUV420P中data[0]存Y,data[1]存U,data[2]存V)

  • 音频数据 采样数据PCM, 保存方式同图像数据。 对于对于planar格式的音频数据通道数超过8时,其余通道数据存放于extended_data中。

(2) linesize[]

行字节的跨度,相当于stride。对于data[i]区域中的一行像素占用的字节数,对于RGB24理论是{wh3, 0, …}; 对于yuv420p,理论是{w, w/2, w/2, 0, …}。但ffmpeg内存会填充对齐,实际行字节数会大于等于理论值。

(3) pict_type

帧数据类型,部分摘取如下

enum AVPictureType {
    AV_PICTURE_TYPE_NONE = 0, ///< Undefined
    AV_PICTURE_TYPE_I,     ///< Intra
    AV_PICTURE_TYPE_P,     ///< Predicted
    AV_PICTURE_TYPE_B,     ///< Bi-dir predicted
    AV_PICTURE_TYPE_S,     ///< S(GMC)-VOP MPEG4
    AV_PICTURE_TYPE_SI,    ///< Switching Intra
    AV_PICTURE_TYPE_SP,    ///< Switching Predicted
    AV_PICTURE_TYPE_BI,    ///< BI type
    ...

(4) sample_aspect_ratio

宽高比,sar,用分数AVRational表示

2、使用方式

常用的函数有

AVFrame *av_frame_alloc(void);  // 分配一个数据帧结构
​
AVFrame *av_frame_clone(const AVFrame *src); // 完整的克隆数据帧结构, 包括其内部数据
​
void av_frame_free(AVFrame **frame);  // 释放数据帧结构及其内部数据
​
int av_frame_ref(AVFrame *dst, const AVFrame *src);  // 增加引用计数
​
void av_frame_unref(AVFrame *frame);  // 减少引用计数 
​
​
int av_frame_get_buffer(AVFrame *frame, int align); // 分配缓冲区

2.1 常规解码流程使用

常规解码使用时,会用到其中3个

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

首先,AVFrame *pFrame = av_frame_alloc()分配一个AVFrame对象,缓冲区data[]未分配。

之后,使用调用av_receive_frame解码,会对pFrame分配data[]缓冲区并保存解码数据;每一次使用后,必须需要使用av_frame_unref释放缓冲区,否则重复解码会造成内存泄露。

最后,需要使用av_frame_free 释放整个对象。

2.2 图像处理

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

对于分辨率为638*272的视频解码后yuv420p的缓冲区 linsesize为{640,320,320,…},内存布局结构如下图

图中,其中w = 640 , h = 320。可以看到,在三个通道中每一行数据间进行了数据填充,Y区域的行字节数不等于638U/V区域的行字节数也不等于319。注意观察data[2],data[1],data[0]之间的差值。

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

2.2.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,  由系统选择最优对齐方式

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

注意,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。

函数av_image_alloc定义

/**
 * Allocate an image with size w and h and pixel format pix_fmt, and
 * fill pointers and linesizes accordingly.
 * The allocated image buffer has to be freed by using
 * av_freep(&pointers[0]).
 *
 * @param align the value to use for buffer size alignment
 * @return the size in bytes required for the image buffer, a negative
 * error code in case of failure
 */
int av_image_alloc(uint8_t *pointers[4], int linesizes[4],
                   int w, int h, enum AVPixelFormat pix_fmt, int align);

函数根据用户输入的 图像分辨率 w,h 像素格式 pix_fmt,以及对齐字节数 等参数,按照linesize分配需要的内存。使用完后,需要av_freep释放内存,如果内存对象是AVFrame则调用av_frame_free即可。

uint8_t *yuvbuf; 经过av_image_alloc分配内存后的释放方法为 av_freep(&(&yuvbuf)[0])。

使用av_image_alloc分配1字节对齐的yuv420p缓冲区内存后,三个分量对应的内存区域是连续的,无填充的。以AVFrame为例,对于638*272的数据缓冲区分配情况如下

linesize不再是{640,320,320},而是{638,319,319}。并且 data[2]-data[1] = 1/4*w*h, data[1]-data[0]=w*h,也就是说,尽管YUV三个分别存放于三个指针指向的内存区域,但这三块内存实际是连续的。从data[0]连续读取w*h*3/2个字节,就是完整的yuv数据

2.2.2 获取无填充1字节对齐的yuv420p数据

最直接也低效的方式是使用3层for循环拷贝数据,对应原生内存管理的方式。

for(int i = 0; i < frame->height; i++)
    memcpy(yuvbuf + i* frame-> width,    frame->data[0] + i* frame->linesize[0], frame->width   );
for(int i = 0; i < frame->height / 2; i++)
    memcpy(yuvbuf + i* frame->width / 2, frame->data[1] + i* frame->linesize[1], frame->width / 2);
for(int i = 0; i < frame->height / 2; i++)
    memcpy(yuvbuf + i* frame->width / 2, frame->data[2] + i* frame->linesize[2], frame->width / 2);

这里提供另外两个方法av_image_copy和sws_scale。

(1) av_image_copy

针对上一节两种内存分配的对象不同,使用方式如下

av_image_copy(frame_yuv->data, frame_yuv->linesize,
               const uint8_t **)frame->data, frame->linesize,
               (AVPixelFormat)frame->format, frame->height, frame->width);
​
​
av_image_copy(&yuvbuf, linesize,
               (const uint8_t **)frame->data, frame->linesize,
                (AVPixelFormat)frame->format, frame->height, frame->width);

(2) sws_scale

// SwsContext上下文,用于sws_scale调用
SwsContext *sws_ctx = 
    sws_getContext(video_decoder_ctx->width, video_decoder_ctx->height, video_decoder_ctx->pix_fmt, // 输入格式
                   frame_yuv->width, frame_yuv->height, AV_PIX_FMT_YUV420P,                         // 输出格式
                   SWS_BILINEAR, NULL, NULL, NULL);                                                 // 变换处理

同理,针对上一节两种内存分配的对象不同,使用方式如下

sws_scale(sws_ctx,
          frame->data, frame->linesize, 0, frame->height,
          frame_yuv->data, frame_yuv->linesize);
​
sws_scale(sws_ctx,
          frame->data, frame->linesize, 0, frame->height,
          &yuvbuf, linesize);

20210524 特殊编码器输入要求 某些硬件编码器处于性能考虑,对yuv420p的内存布局对齐方式有要求。这种情况下,不能使用使用1字节对齐分配数据(例如h264_omx使用opencv转yuv420p的数据直接编码导致有重影)。使用16/32补齐进行尝试。 分配方式可以参考 ffmpeg学习 结构体分析AVFrame

原文 ffmpeg学习 结构体分析AVFrame - DoubleLi - 博客园

★文末名片可以免费领取音视频开发学习资料,内容包括(FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,srs)以及音视频学习路线图等等。

见下方!↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

 

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值