剖析AVFrame

AVFrame是FFmpeg中非常重要的数据结构,其封装了解码后的媒体数据。在FFmpeg之中,有几个比较重要的音视频概念:

pixel_format:

表示像素格式,图像像素在内存中的排列格式。一种像素格式包含有:色彩空间、采样方式、存储模式、位深等信息;

bit_depth:

表示位深,指每个分量(Y、U、V、R、G、B 等)单个采样点所占的位宽度;

plane:

表示存储图像中一个或多个分量的一片内存区域:

当处于planar 存储模式中,至少有一个分量占用单独的一个 plane,planar 存储模式至少有两个 plane,具体到 yuv420p 格式有 Y、U、V 三个 plane,nv12 格式有 Y、UV 两个 plane,gbrap 格式有 G、B、R、A 四个 plane。

当处于packed 存储模式中,因为所有分量的像素是交织存放的,所以 packed 存储模式只有一个 plane;

slice:

表示图像中一片连续的行,必须是连续的,顺序由顶部到底部或由底部到顶部;

stride/pitch:

一行图像中某个分量(如亮度分量或色度分量)所占的字节数, 也就是一个 plane 中一行数据的宽度。有对齐要求,计算公式如下:

stride = width * 分量数 * 单样本位宽度 / 水平子采样因子 / 8

其中,图像宽度(width)表示图像宽度是多少个像素,分量数指当前 plane 包含多少个分量(如 rgb24 格式一个 plane 有 R、G、B 三个分量),单位本位宽度指某分量的一个样本在考虑对齐后在内存中占用的实际位数(例如位深 8 占 8 位宽,位深 10 实际占 16 位宽,对齐值与平台相关),水平子采样因子指在水平方向上每多少个像素采样出一个色度样本(亮度样本不进行下采样,所以采样因子总是 1)

AVFrame

AVFrame 中存储的是解码后的原始数据(raw)。在解码中,AVFrame 是解码器的输出;在编码中,AVFrame 是编码器的输入。

必须使用av_frame_alloc进行初始化,该函数仅会对AVFrame进行初始化而不会分配任何缓存空间。相反,需要通过av_frame_free进行释放销毁。大部分实际使用中,AVFrame在同一属性下只会初始化一次,然后不断变更缓存内容,保存不同内容。av_frame_unref用来释放内部保留的所有引用,并使其于再次被引用前重置为“干净”状态。

 _______              ______________
|       |            |              |
| input |  demuxer   | encoded data |   decoder
| file  | ---------> | packets      | -----+
|_______|            |______________|      |
                                           v
                                       _________
                                      |         |
                                      | decoded |
                                      | frames  |
                                      |_________|
 ________             ______________       |
|        |           |              |      |
| output | <-------- | encoded data | <----+
| file   |   muxer   | packets      |   encoder
|________|           |______________|
    

剖析重要成员

data、linesize、extended_data

其中,data 存储原始帧数据,未编码的原始图像或音频数据,作为解码器的输出或编码器的输入。data是一个指针数组,元素指向视频图像的某一plane或音频中某声道的某一plane。无论是视频还是音频,都有 PacketPlanar 两种存储方式。

typedef struct AVFrame {
    /**
     * 存储原始帧数据,未编码的原始图像或音频数据,作为解码器的输出或编码器的输入
     * data是一个指针数组,元素指向视频图像的某一plane或音频中某声道的某一plane
     * 视频:
     *   Packet: Y,U,V交织存储在一个plane中,如YUVYUV...data[0]指向这个plane
     *   Planar: Y,U,V存储在三个plane中,data[0]指向Y-plane,data[1]指向U-plane,data[2]指向V-plane
     * 音频:
     *   Packet: L,R交织存储在一个plane中,形如LRLRLR...data[0]指向这个plane
     *   Planar: L,R存储在两个plane中,data[0]指向L-plane,data[1]指向R-plane
     */
    uint8_t *data[AV_NUM_DATA_POINTERS];
    /**
     * linesize是一个数组
     * 视频:
     *   每个元素是一个图像plane中一行图像的大小(字节数),注意有对齐要求linesize对应stride值
     *   packed: 只有一个plane,linesize[0]表示一行图像所占的存储空间大小     
     *   planar: 有多个plane,每个plane的linesize[i]表示一行图像在当前plane中所占的存储空间大小
     * 音频:
     *   每个元素是一个音频plane的大小(字节数)
     *   packed: 多声道音频只有一个plane,linesize[0]表示所有数据的存储空间大小
     *   planar: 有多个plane,但只使用一个linesize[0],每个plane大小都是linesize[0]
     * 事实上,linesize可能因性能考虑而填充一些额外的数据,故可能比实际对应的音视频数据要大
     */
    int linesize[AV_NUM_DATA_POINTERS];
    /**
     * 在一个正常的AVFrame中,data与extended_data通常都会被设置
     * 但是对于一个plannar且有多个通道且data无法装下所有通道的数据的时候,extended_data必须被使用,用来存储多出来的通道的数据的指针
     */
    uint8_t **extended_data;
	// ...
} AVFrame;

怎么应用呢?

当我们读取数据时,按width与height来读取吗?如下面:

fwrite(ofrm->data[0], 1, ofrm->width * ofrm->height, outfile)

显然是错误的,因为这样的写法,没有考虑到“字节对齐”的问题,比如宽是1280,但实际上该图片的切片宽可能是1200,只不过在内存区的总大小是1280,正确的写法应该是:

// 读取方式
// |___________|___|___________|___|___________|___| : 内存区域
// |___________| : linesize
// |_______________| : width
// Y分量
for (int j = 0; j < ofrm->height; j++) {
    fwrite(ofrm->data[0]+j*ofrm->linesize[0], 1, ofrm->width, outfile);
}
// U分量
for (int j = 0; j < ofrm->height / 2; j++) {
    fwrite(ofrm->data[1]+j*ofrm->linesize[1], 1, ofrm->width/2, outfile);
}
// V分量
for (int j = 0; j < ofrm->height / 2; j++) {
    fwrite(ofrm->data[2]+j*ofrm->linesize[2], 1, ofrm->width/2, outfile);
}

那么,我们如何判断这个数据是 packed 还是 planar 的呢?答案是通过ofrm->format来判断,对音频而言类型是 AVSampleFormat,对视频而言类型是 AVPixelFormat

width、height、sample_aspect_ratio

视频的宽与高,以及宽高比

typedef struct AVFrame {
    // 宽与高
    int width, height;
    // 视频帧的宽高比
    AVRational sample_aspect_ratio;
    // ...
}

key_frame、pict_type

关键帧标识以及帧类型,都是比较好理解的。

typedef struct AVFrame {
    // ...
    // 是否关键帧? 当值==1时,表示该视频帧为关键帧
    int key_frame;
    // 视频帧类型:I,B,P....
    enum AVPictureType pict_type;
    // ...
}

nb_samples、sample_rate、channels

音频声道采样数

typedef struct AVFrame {
    // ...
    // 音频每个声道内帧包含采样数
    int nb_samples;
    // 音频采样率
    int sample_rate;
    // 音频声道数
    int channels;
}

channel_layout

typedef struct AVFrame {
    // ...
    // 音频声道布局,每bit代表一个特定的声道
    uint64_t channel_layout;
}

// Layout
#define AV_CH_FRONT_LEFT        0x00000001
#define AV_CH_FRONT_RIGHT       0x00000002
#define AV_CH_FRONT_CENTER      0x00000004
#define AV_CH_LOW_FREQUENCY     0x00000008
// ...
#define AV_CH_LAYOUT_MONO       (AV_CH_FRONT_CENTER)
#define AV_CH_LAYOUT_STEREO     (AV_CH_FRONT_LEFT|AV_CH_FRONT_RIGHT)
#define AV_CH_LAYOUT_2POINT1    (AV_CH_LAYOUT_STEREO|AV_CH_LOW_FREQUENCY)

format

帧格式,如果是未知格式或未设置,则值为-1;

AVSampleFormat 名称里带P的表示 Packet 内存模式,而不带P的,则表示 Planar 内存模式;

typedef struct AVFrame {
    /** 
     * 对于视频帧,此值对应于: enum AVPixelFormat
     * 对于音频帧,此值对应于: enum AVSampleFormat
     */
    int format;
	// ...
}

enum AVPixelFormat {
    AV_PIX_FMT_NONE = -1,
    AV_PIX_FMT_YUV420P,   ///< planar YUV 4:2:0, 12bpp
    AV_PIX_FMT_YUYV422,   ///< packed YUV 4:2:2, 16bpp, Y0 Cb Y1 Cr
    AV_PIX_FMT_RGB24,     ///< packed RGB 8:8:8, 24bpp, RGBRGB...
    AV_PIX_FMT_BGR24,     ///< packed RGB 8:8:8, 24bpp, BGRBGR...
    // ...
}

enum AVSampleFormat {
    AV_SAMPLE_FMT_NONE = -1,
    AV_SAMPLE_FMT_U8,          ///< unsigned 8 bits
    AV_SAMPLE_FMT_S16,         ///< signed 16 bits
    AV_SAMPLE_FMT_S32,         ///< signed 32 bits
    AV_SAMPLE_FMT_FLT,         ///< float
    AV_SAMPLE_FMT_DBL,         ///< double

    AV_SAMPLE_FMT_U8P,         ///< unsigned 8 bits, planar
    AV_SAMPLE_FMT_S16P,        ///< signed 16 bits, planar
    AV_SAMPLE_FMT_S32P,        ///< signed 32 bits, planar
    AV_SAMPLE_FMT_FLTP,        ///< float, planar
    AV_SAMPLE_FMT_DBLP,        ///< double, planar
    AV_SAMPLE_FMT_S64,         ///< signed 64 bits
    AV_SAMPLE_FMT_S64P,        ///< signed 64 bits, planar

    AV_SAMPLE_FMT_NB
};

pts、pkt_dts

typedef struct AVFrame {
    // 帧显示时间戳,单位是:time_base
    int64_t pts;
    // 此frame对应的packet中的解码时间戳,是从对应packet中拷贝DTS得到此值
    // 如果对应的packet中只有dts而未设置pts,则此值也是此frame的pts
	int64_t pkt_dts;
}

buf、extended_buf

对于视频来说,buf[] 包含所有 AVBufferRef 指针。对于具有多于 AV_NUM_DATA_POINTERS 个声道的 planar 音频来说,可能 buf[] 存不下所有的 AVBbufferRef 指针,多出的 AVBufferRef 指针存储在 extended_buf 数组中。

typedef struct AVFrame {
    /**
     * 此帧的数据可以由AVBufferRef管理,AVBufferRef提供AVBuffer引用机制,
     * 这里涉及到缓冲区引用计数概念....
     * AVBuffer: 是FFmpeg中很常用的一种缓冲区,缓冲区使用“引用计数”机制
     * AVBufferRef: 则对AVBuffer缓冲区提供了一层封装,作引用计数处理,实现安全机制
     * 用户不应直接访问AVBuffer,应通过AVBufferRef来访问AVBuffer,以保证安全。
     */
    AVBufferRef *buf[AV_NUM_DATA_POINTERS];
    // 扩展缓冲区
    AVBufferRef **extended_buf;
	// ...
}

pkt_pos、pkt_duration、pkt_size

typedef struct AVFrame {
    // ...
    // 记录最后一个扔进解码器的packet在输入文件中的位置偏移量
    int pkt_size;
	// 对应packet的时长,单位是AVStream->time_base
    int64_t pkt_duration;
    // 对应packet的大小
    int pkt_size;
}

剖析重要函数接口

av_frame_alloc()、av_frame_free()

构造一个 frame,对象各成员被设为默认值,此函数只分配 AVFrame 对象本身,而不分配 AVFrame 中的数据缓冲区。

/**
 * 构造AVFrame,默认配置,不分配缓冲区
 */
AVFrame *av_frame_alloc(void);
/**
 * 释放AVFrame,并且也会释放所有缓冲区,例如extended_data
 * 如果正被引用计数,则解除引用
 */
void av_frame_free(AVFrame **frame);

av_frame_ref()、av_frame_unref()

为 src 中的数据建立一个新的引用。将 src 中帧的各属性拷到 dst 中,并且为 src 中每个 AVBufferRef 创建一个新的引用。如果 src 未使用引用计数,则 dst 中会分配新的数据缓冲区,将将 src 中缓冲区的数据拷贝到 dst 中的缓冲区。

/**
 * 新的AVFrame从源AVFrame对象中引用,引用计数+1
 */
int av_frame_ref(AVFrame *dst, const AVFrame *src);
/**
 * 解除一个引用,并让引用计数-1
 */
void av_frame_unref(AVFrame *frame);

av_frame_clone()

创建一个新的 frame,新的 frame 和 src 使用同一数据缓冲区,缓冲区管理使用引用计数机制。函数相当于 av_frame_alloc() + av_frame_ref()。

/**
 * 创建新的缓冲区并添加引用
 */
AVFrame *av_frame_clone(const AVFrame *src);

av_frame_get_buffer()

为音频或视频数据分配新的缓冲区。调用本函数前,帧中的如下成员必须先设置好:

  • format:视频像素格式或音频采样格式
  • width、height:视频画面和宽和高
  • nb_samples、channel_layout:音频单个声道中的采样点数目和声道布局

本函数会填充 AVFrame.dataAVFrame.linesizeAVFrame.buf 数组,如果有需要,还会分配和填充 AVFrame.extended_dataAVFrame.extended_buf。对于 planar 格式,会为每个 plane 分配一个缓冲区。

// 根据AVFrame属性分配缓冲区
int av_frame_get_buffer(AVFrame *frame, int align);

av_frame_copy

将 src 中的帧数据拷贝到 dst 中。本函数并不会有任何分配缓冲区的动作,调用此函数前 dst 必须已经使用了和 src 同样的参数完成了初始化。

注意其和 av_frame_clone 的区别。本函数只拷贝帧中的数据缓冲区的内容(data/extended_data 数组中的内容),而不涉及帧中任何其他的属性,类似于 memcpy

// 拷贝缓存区
int av_frame_copy(AVFrame *dst, const AVFrame *src);

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
06-02
AVFrameFFmpeg 中表示视频或音频帧的结构体,它包含了一帧的所有信息,如像素数据、采样数据、时间戳、宽高等等。在 FFmpeg 中,解码后的数据一般都会被存储在 AVFrame 中,然后再进行后续的处理。 AVFrame 的定义如下: ``` typedef struct AVFrame { /** * Pointer to the picture/channel planes. * This might be different from the first allocated byte */ uint8_t *data[AV_NUM_DATA_POINTERS]; /** * Size, in bytes, of the data for each plane. */ int linesize[AV_NUM_DATA_POINTERS]; /** * pointers to the samples in uint8_t ** * planar audio only */ uint8_t **extended_data; /** * Width and height of the video frame */ int width, height; /** * Format of the video frame */ enum AVPixelFormat format; /** * Sample rate of the audio frame */ int sample_rate; /** * Number of audio samples (per channel) described by this frame */ int nb_samples; /** * Channel layout of the audio frame */ uint64_t channel_layout; /** * Presentation timestamp in timebase units (time when frame should be shown to user). */ int64_t pts; /** * PTS copied from the AVPacket that was decoded to produce this frame */ int64_t pkt_pts; /** * Duration of this frame in timebase units (0 if unknown or undefined). */ int64_t duration; /** * Flags indicating which frame properties are present */ int flags; /** * A Boolean value indicating whether the frame is a key frame (1) or not (0). */ int key_frame; /** * A pointer to the next frame in the linked list. */ struct AVFrame *next; /** * The number of elements in the extended_data array. */ int8_t *extended_data_size; /** * Metadata for the frame. */ AVDictionary *metadata; } AVFrame; ``` AVFrame 中最重要的是 data 和 linesize 这两个成员,它们表示每个像素或采样数据的地址和大小,可以通过它们来访问帧中的数据。同时,AVFrame 中还包含了一些其他的信息,如帧的宽高、格式、时间戳等等。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

毕加索解锁

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值