FFmpeg 基本数据结构 AVFrame分析
1、AVFrame 结构分析
FFmpeg 中的 AVFrame 是一个核心数据结构,用于表示未压缩的(原始)音频或视频数据。它是处理解码后的数据(输入到编码器、滤镜或渲染器之前)和编码前的数据的基石。理解 AVFrame 对于使用 FFmpeg 进行音视频处理(如转码、滤镜应用、分析、渲染)至关重要。
具体代码分析如下所示:
typedef struct AVFrame {uint8_t *data[AV_NUM_DATA_POINTERS]; // 指向图像数据的指针数组int linesize[AV_NUM_DATA_POINTERS]; // 图像数据每行字节数数组uint8_t **extended_data; // 指向扩展数据指针的指针int width, height; // 图像宽度和高度int nb_samples; // 音频采样数int format; // 像素格式enum AVPictureType pict_type; // 图像类型AVRational sample_aspect_ratio; // 采样比例int64_t pts; // 显示时间戳int64_t pkt_dts; // 包解码时间戳AVRational time_base; // 时间基准int quality; // 质量void *opaque; // 透明数据int repeat_pict; // 重复图像int sample_rate; // 采样率AVBufferRef *buf[AV_NUM_DATA_POINTERS]; // 指向缓冲区的指针数组AVBufferRef **extended_buf; // 指向扩展缓冲区指针的指针int nb_extended_buf; // 扩展缓冲区数量AVFrameSideData **side_data; // 指向侧数据指针的指针int nb_side_data; // 侧数据数量int flags; // 标志enum AVColorRange color_range; // 颜色范围enum AVColorPrimaries color_primaries; // 颜色原点enum AVColorTransferCharacteristic color_trc; // 颜色传输特性enum AVColorSpace colorspace; // 颜色空间enum AVChromaLocation chroma_location; // 色度位置int64_t best_effort_timestamp; // 最佳努力时间戳AVDictionary *metadata; // 元数据int decode_error_flags; // 解码错误标志int pkt_size; // 包大小AVBufferRef *hw_frames_ctx; // 硬件帧上下文AVBufferRef *opaque_ref; // 不透明引用size_t crop_top; // 上裁剪尺寸size_t crop_bottom; // 下裁剪尺寸size_t crop_left; // 左裁剪尺寸size_t crop_right; // 右裁剪尺寸AVBufferRef *private_ref; // 私有引用AVChannelLayout ch_layout; // 通道布局int64_t duration; // 持续时间
} AVFrame;
各个成员的含义如下:
- data: 一个指向图像数据的指针数组,用于存储图像的每个通道的数据。
- linesize: 一个整数数组,用于存储图像数据每行字节数。
- extended_data: 一个指向扩展数据指针的指针,用于存储图像数据的扩展指针。
- width, height: 两个整数,分别表示图像的宽度和高度。
- nb_samples: 一个整数,表示音频采样的样本数。
- format: 一个整数,表示像素格式。
- pict_type: 一个枚举类型,表示图像类型。
- sample_aspect_ratio: 一个AVRational结构体,表示采样比例。
- pts, pkt_dts: 两个整数,分别表示显示时间戳和解码时间戳。
- time_base: 一个AVRational结构体,表示时间基准。
- quality: 一个整数,表示质量。
- opaque: 一个指向透明数据的指针。
- repeat_pict: 一个整数,表示重复图像。
- sample_rate: 一个整数,表示采样率。
- buf: 一个指向缓冲区的指针数组,用于存储图像数据的缓冲区。
- extended_buf: 一个指向扩展缓冲区指针的指针,用于存储图像数据的扩展缓冲区。
- nb_extended_buf: 一个整数,表示扩展缓冲区数量。
- side_data: 一个指向侧数据指针的指针,用于存储图像数据的侧数据。
- nb_side_data: 一个整数,表示侧数据数量。
- flags: 一个整数,表示标志。
- color_range: 一个枚举类型,表示颜色范围。
-color_primaries: 一个枚举类型。
2、AVFrame 内部基础信息
AVFrame 中包含基本信息,其描述Audio 和Video 信息,表示帧是不是关键帧等flag。在解码和编码过程中,其重要作用。
- format: 整数,表示帧数据的格式。
- 对于视频:这对应于 AVPixelFormat 枚举(例如 AV_PIX_FMT_YUV420P, AV_PIX_FMT_RGB24, AV_PIX_FMT_NV12)。它定义了像素如何排列、存储(平面还是打包)、颜色空间、位深等。
- 对于音频:这对应于 AVSampleFormat 枚举(例如 AV_SAMPLE_FMT_FLTP - 32位浮点平面, AV_SAMPLE_FMT_S16P - 16位有符号整数平面)。它定义了样本的位深和存储方式(打包或平面)。
- width, height: 整数,表示视频帧的宽度和高度(以像素为单位)。仅对视频帧有意义。
- sample_rate: 整数,表示音频数据的采样率(每秒样本数,如 44100, 48000)。仅对音频帧有意义。
- channels: 整数,表示音频通道的数量(如 1-单声道, 2-立体声)。注意:更推荐使用 channel_layout。
- channel_layout: uint64_t,表示音频通道的布局(如 AV_CH_LAYOUT_STEREO, AV_CH_LAYOUT_5POINT1)。这是一个位掩码,精确指定了每个通道的位置(左、右、中置、LFE等)。比 channels 提供更精确的信息。
- nb_samples: 整数,表示此音频帧中包含的每个通道的样本数。仅对音频帧有意义。对于视频,通常一帧就是一幅完整的图像(但 H.264 的场编码等是特例)。
- key_frame: 整数,标志位(1 或 0),指示该视频帧是否是关键帧(I帧)。关键帧可以独立解码,不依赖于其他帧。
- pict_type: enum AVPictureType,表示视频帧的类型(I帧, P帧, B帧等)。例如 AV_PICTURE_TYPE_I, AV_PICTURE_TYPE_P, AV_PICTURE_TYPE_B。
3、AVFrame 内部数据存储
- data: 指向指针数组的指针 (uint8_t **data)。这是存储实际媒体数据(像素或样本)的缓冲区数组。
- 视频 (Planar Formats - 最常见如YUV420P):
- data[0] - 指向 Y (亮度) 分量平面。
- data[1] - 指向 U (Cb) 分量平面。
- data[2] - 指向 V (Cr) 分量平面。
- (对于其他平面格式,如RGB24 planar,或 NV12,分量索引和数量会不同)
- 视频 (Packed Formats - 较少见如RGB24 packed):
- data[0] - 指向单个缓冲区,其中 R, G, B 值按像素交错存储(如 R0, G0, B0, R1, G1, B1, …)。
- 音频 (Planar Formats - 最常见):
- data[0] - 指向通道 0 的样本数据。
- data[1] - 指向通道 1 的样本数据。
- … data[channels-1] - 指向最后一个通道的样本数据。每个通道的数据是连续的。
- 音频 (Packed Formats):
- data[0] - 指向单个缓冲区,其中所有通道的样本按样本点交错存储(如 S0_C0, S0_C1, S1_C0, S1_C1, …)。
- 视频 (Planar Formats - 最常见如YUV420P):
- linesize: 整数数组 (int linesize[AV_NUM_DATA_POINTERS])。也称为 stride 或 pitch。
- 对于视频:linesize[i] 表示 data[i] 指向的分量平面中一行数据的字节数。这个值通常大于或等于基于 width 和像素格式计算出的最小字节数。原因包括:
- 内存对齐要求(如16字节对齐)。
- 缓冲区由硬件或特定库分配。
- 图像是另一个更大图像的一部分(子区域)。
- 对于音频:linesize[i] 表示 data[i] 指向的单个通道的音频数据缓冲区的总字节数(即 通道i的大小 = nb_samples * 每个样本的字节数)。在平面音频中,这通常是 nb_samples * av_get_bytes_per_sample(sample_fmt)。对于打包音频,linesize[0] 包含所有通道交错数据的总大小。
- 对于视频:linesize[i] 表示 data[i] 指向的分量平面中一行数据的字节数。这个值通常大于或等于基于 width 和像素格式计算出的最小字节数。原因包括:
4、AVFrame 内部元数据与时间信息
- pts: 显示时间戳。表示何时应该向用户显示/呈现此帧。时间基由流 (AVStream->time_base) 或编解码器上下文 (AVCodecContext->time_base) 定义。AV_NOPTS_VALUE 表示未定义。至关重要 用于音视频同步。
- dts: 解码时间戳。表示何时应该将此帧送入解码器进行解码。主要用于视频编码/解码的比特流排序(特别是存在 B 帧时)。通常 dts <= pts。对于未压缩数据或没有 B 帧的情况,dts 可能未设置或等于 pts。
- duration: 帧的持续时间。表示此帧在显示时应持续多久(使用与 pts 相同的时间基)。
- quality: 整数,编码器内部使用的质量指示(值越大质量越好,值越小质量越差)。对解码输出意义不大。
- opaque: 指向用户私有数据的指针。FFmpeg 本身不使用,供应用程序在帧生命周期内关联自定义数据。
- crop_top, crop_bottom, crop_left, crop_right: 裁剪信息(较少直接使用)。指示在显示或处理前应从帧的边缘裁剪多少像素。
- metadata: 字典 (AVDictionary *)。可以存储与帧相关的任意键值对元数据(例如来自编码器设置、滤镜标记)。
5、AVFrame 内部内存管理与引用计数
- buf: 指向 AVBufferRef 指针数组的指针 (AVBufferRef **buf)。这是现代 FFmpeg 中管理 data 缓冲区内存的推荐方式。它实现了引用计数。
- 每个 data[i] 缓冲区通常由一个对应的 buf[i] 管理(对于平面格式)。对于打包格式,可能只有一个 buf[0] 管理整个 data[0]。
- AVBufferRef 封装了实际的数据缓冲区 (AVBuffer) 和引用计数。
- 引用计数原理:
- 当创建一个新的 AVFrame(如通过 av_frame_alloc())或显式引用一个现有帧(av_frame_ref())时,引用计数会增加。
- 当你不再需要一个 AVFrame 时,调用 av_frame_unref()。这会减少引用计数。
- 当引用计数变为 0 时,FFmpeg 会自动释放 data 缓冲区以及 AVFrame 结构体本身(如果它是通过 av_frame_alloc() 分配的)。
- 为什么重要? 避免了手动内存管理错误(内存泄漏、野指针、重复释放)。允许多个部分(解码器、滤镜、你的代码)安全地共享同一帧数据,而无需深度拷贝。始终优先使用基于 buf/ref 的 API (av_frame_ref(), av_frame_move_ref()) 来复制帧,而不是直接拷贝结构体或 data 指针。
- extended_data: 指向指针数组的指针 (uint8_t **extended_data)。通常等于 data。在音频帧的通道数超过 AV_NUM_DATA_POINTERS (通常为8) 时,extended_data 会指向一个单独分配的、包含所有通道指针的数组,而 data 可能只包含前几个通道或为 NULL。最佳实践:处理音频时总是使用 extended_data 来访问样本数据,以确保兼容高通道数。
视频数据(以 YUV420P 为例):这是一种 Planar(平面)格式,Y、U、V 分量分别存储在 data[0]、data[1]、data[2] 三个独立的平面中。
- data[0] 存储所有 Y(亮度)分量数据,大小为 width * height。
- data[1] 存储所有 U(色度)分量数据,大小为 (width/2) * (height/2)。
- data[2] 存储所有 V(色度)分量数据,大小为 (width/2) * (height/2)。
- linesize[0] 是 Y 平面一行的字节数,由于对齐可能 >= width。
- linesize[1] 是 U 平面一行的字节数,可能 >= width/2。
- linesize[2] 是 V 平面一行的字节数,可能 >= width/2。
音频数据(以 Planar Float 为例):每个声道的数据存储在独立的平面。例如,立体声(双声道):
- data[0] 指向左声道所有采样点数据。
- data[1] 指向右声道所有采样点数据。
- linesize[0] 和 linesize[1] 分别表示每个声道数据缓冲区的大小。
6、AVFrame 相关的重要API 函数
- av_frame_alloc(): 分配一个新的空 AVFrame。必须使用此函数分配。
- av_frame_free(AVFrame **frame): 释放 AVFrame 及其所有关联的缓冲区(减少引用计数,必要时释放)。
- av_frame_unref(AVFrame *frame): 取消对 frame 所引用的所有缓冲区的引用,并将 frame 重置为空状态(不释放 frame 结构本身)。
- av_frame_ref(AVFrame *dst, const AVFrame *src): 设置 dst 为对 src 中数据的新引用。增加引用计数。dst 需要已分配。
- av_frame_move_ref(AVFrame *dst, AVFrame *src): 将引用从 src 移动到 dst。src 在调用后被置空。高效且避免引用计数变化。
- av_frame_get_buffer(AVFrame *frame, int align): 根据 frame 的格式、宽度、高度(视频)或通道数、样本数(音频)为 frame 分配数据缓冲区。设置 data 和 linesize。align 指定缓冲区对齐要求(通常为 0 或 1 让 FFmpeg 决定,或 32 用于 SIMD)。
- av_frame_make_writable(AVFrame *frame): 确保 frame 是可写的。如果引用计数 > 1(即存在其他引用),它会创建并复制一份数据的副本。在修改 data 内容之前调用此函数非常重要!
- av_frame_copy(AVFrame *dst, const AVFrame *src): 将 src 的内容(数据和元数据)复制到 dst。dst 需要已分配并有足够大小的缓冲区。比 ref 更重,因为它涉及内存拷贝。
- av_frame_copy_props(AVFrame *dst, const AVFrame *src): 仅复制 src 的属性(pts, dts, duration, format, width/height, sample_rate 等)到 dst。不复制实际数据。
7、AVFrame 使用总结与重要提示
- 核心作用: AVFrame 是原始音视频数据的载体。
- data 和 linesize: 访问实际像素/样本的关键。理解格式(format)对于正确解释 data 至关重要。linesize 不等于 width * bytes_per_pixel 是常见情况!
- format: 必须始终检查以知晓如何处理 data。
- pts/dts/duration: 同步和处理流程的核心。
- 引用计数 (buf): 现代 FFmpeg 内存管理的基石。理解 av_frame_ref(), av_frame_unref(), av_frame_move_ref(), av_frame_make_writable() 是避免内存错误的关键。永远不要简单地对 AVFrame 结构体进行 memcpy。
- 分配/释放: 总是使用 av_frame_alloc() 和 av_frame_free()(或正确使用引用计数)。
- 音频优先使用 extended_data。
- 修改前调用 av_frame_make_writable(): 防止意外修改共享缓冲区。
