AVFrame介绍

AVFrame 是 FFmpeg 库中用于存储音视频数据帧的核心结构体。它在处理视频解码、编码、滤镜以及其他音视频处理任务时非常重要。AVFrame 可以表示原始的未压缩音频或视频数据。

  1. AVFrame 的作用:
  • 视频帧:在处理视频时,AVFrame 用于存储解码后的原始视频帧,每个帧包括像素数据、分辨率、格式、时间戳等信息。
  • 音频帧:在处理音频时,AVFrame 用于存储解码后的原始音频数据,包括音频样本、通道布局、采样率等信息。
  1. AVFrame 的结构:

AVFrame 是一个复杂的结构体,包含了大量与音视频数据相关的字段。以下是一些主要字段的介绍:

  • 数据和缓冲区相关:

    AV_NUM_DATA_POINTERS是 FFmpeg 库中的一个宏定义,它定义了用于指向数据平面的指针数组的大小。一般被定义为 8,这意味着数据指针数组 data[AV_NUM_DATA_POINTERS] 可以存储多达 8 个指向不同数据平面的指针。

    • uint8_t *data[AV_NUM_DATA_POINTERS]:指向存储数据的指针数组。对于视频,data[0] 通常指向 Y 平面data[1]data[2] 分别指向 U 和 V 平面。对于音频,这些指针指向音频样本数据。
    • int linesize[AV_NUM_DATA_POINTERS]:表示每行图像数据(对于视频)或每个音频通道的字节数。对于视频帧,这决定了每行像素数据在内存中的跨度。
    • AVBufferRef *buf[AV_NUM_DATA_POINTERS]:指向 AVBufferRef 的引用,管理 data 指针的引用计数和生命周期。
  • 视频相关:

    • int width:视频帧的宽度(以像素为单位)。
    • int height:视频帧的高度(以像素为单位)。
    • enum AVPixelFormat format:视频帧的像素格式,如 AV_PIX_FMT_YUV420PAV_PIX_FMT_RGB24 等。
  • 音频相关:

    • int nb_samples:音频帧中包含的样本数。
    • int channels:音频帧的通道数。
    • enum AVSampleFormat format:音频帧的采样格式,如 AV_SAMPLE_FMT_S16(16位有符号整数)或 AV_SAMPLE_FMT_FLTP(浮点数,平面格式)等。
    • uint64_t channel_layout:音频通道布局,例如 AV_CH_LAYOUT_STEREO 表示立体声。
  • 时间相关:

    • int64_t pts:表示这个帧的展示时间戳(Presentation Timestamp),通常用来同步音视频。
    • int64_t pkt_dts:表示解码时间戳(Decoding Timestamp)。
  • 引用计数和缓冲区管理:

    • int *extended_data:与 data 指针类似,但用于扩展通道数的情况,尤其在处理音频数据时。
    • int refcounted:指示该帧是否使用引用计数。如果为 1,则帧的数据会通过引用计数机制进行管理,多个 AVFrame 可以共享同一块数据。
  1. 常用的 AVFrame 函数:
  • av_frame_alloc():分配一个新的 AVFrame 对象,并初始化为默认值。
  • av_frame_free(AVFrame **frame):释放 AVFrame 对象及其关联的资源。调用后,frame 指针将被置为 NULL
  • av_frame_get_buffer(AVFrame *frame, int align):为 AVFrame 的数据缓冲区分配内存。align 参数指定内存的对齐方式。
  • av_frame_unref(AVFrame *frame):减少 AVFrame 缓冲区的引用计数,并将 AVFrame 重置为空。这个函数不会释放 AVFrame 对象本身,只是释放其数据缓冲区。
  • av_frame_ref(AVFrame *dst, const AVFrame *src):使 dst AVFrame 成为 src AVFrame 的引用,即共享相同的缓冲区。
  1. AVFrame 的应用场景:
  • 视频解码:从解码器获得原始视频帧后,可以使用 AVFrame 存储并进一步处理或显示。
  • 音频解码:类似地,解码后的音频样本数据也可以存储在 AVFrame 中。
  • 过滤:在使用 FFmpeg 过滤器链时,AVFrame 作为输入和输出数据结构传递和处理。
  • 视频编码:将处理后的帧再次封装或编码时,AVFrame 也是主要的数据结构。
  1. AVFrame 的生命周期管理:
  • 分配:首先,通过 av_frame_alloc() 函数分配一个 AVFrame 对象。
  • 初始化和填充数据:可以通过 av_frame_get_buffer() 为其数据分配缓冲区,或者通过解码器等接口直接获得填充了数据的 AVFrame
  • 使用AVFrame 进行各种处理,比如滤镜、编码、显示等。
  • 释放:使用完毕后,通过 av_frame_unref()av_frame_free() 释放 AVFrame 及其数据。

总结:

AVFrame 是 FFmpeg 中非常重要的数据结构,广泛用于音视频处理的各个阶段。通过引用计数管理缓冲区,AVFrame 提供了高效、安全的内存管理机制,是 FFmpeg 处理多媒体数据的基石。

常用的 AVFrame 函数说明

av_frame_get_buffer(AVFrame *frame, int align)

align 参数。常见的对齐值有 1、16、32、64 等。默认情况下,FFmpeg 一般使用 32 字节对齐,因为这在大多数平台上是一个较好的平衡选择。

如果设置的时候,不是 1、16、32、64 ,系统会自动向上取整为最近的 16 字节对齐。比如align为12,系统会自动调整为16

查看align的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void check_alignment(AVFrame *frame, int align) {
for (int i = 0; i < AV_NUM_DATA_POINTERS; i++) {
if (frame->data[i]) {
std::cout << "Frame data[" << i << "] address: " << (uintptr_t)frame->data[i] << std::endl;
std::cout << "Alignment check (should be 0 if correctly aligned): "
<< ((uintptr_t)frame->data[i] % align) << std::endl;
}
}
}

int main()
{
...
auto re = av_frame_get_buffer(frame, 12);

check_alignment(frame, 12);
std::cout << "======" << std::endl;
check_alignment(frame, 16);
...
}

align为12的时候,执行main函数中的代码,会得到

image-20240812142347462

说明:与12有偏差,但是与16没有偏差

代码样例:

引用计数

运行结果:

image-20240811165246723
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#include "AVFrame_test.h"

int main(int argc, char* argv[])
{
std::cout << "first ffmepg" << std::endl;
std::cout << avcodec_configuration() << std::endl;
std::cout << std::endl;

// 创建frame对象
auto frame1 = av_frame_alloc();

// 图像参数
frame1->width = 400;
frame1->height = 300;
frame1->format = AV_PIX_FMT_ARGB;

// 分配空间 16字节对齐,默认填0就是32字节
int re = av_frame_get_buffer(frame1, 16);
if (re != 0)
{
char buf[1024] = { 0 };
av_strerror(re, buf, sizeof(buf));
std::cout << buf << std::endl;
}

std::cout << "frame1->linesize[0]=" << frame1->linesize[0] << std::endl;
/*
由于是ARGB格式
linesize[0] = width × 每个像素的字节数
linesize[0] = width x 4
*/
if(frame1->buf[0])
{
std::cout << "frame1 ref count = " <<
av_buffer_get_ref_count(frame1->buf[0]) << // 线程安全
std::endl;
std::cout << std::endl;
}
std::cout << "=======" << std::endl;




// 创建一个新的 AVFrame 对象 frame2,并使其成为 frame1 的引用。
// frame2不是复制frame1的数据,而是共享底层数据
auto frame2 = av_frame_alloc();
av_frame_ref(frame2, frame1);

std::cout << "av_frame_ref(frame2, frame1)" << std::endl;
std::cout << "frame1 ref count = " <<
av_buffer_get_ref_count(frame1->buf[0]) << // 线程安全
std::endl;
std::cout << std::endl;

std::cout << "frame2 ref count = " <<
av_buffer_get_ref_count(frame2->buf[0]) << // 线程安全
std::endl;
std::cout << std::endl;




// 引用计数-1 并将buf清零
av_frame_unref(frame2);
std::cout << "av_frame_unref(frame2)" << std::endl;
std::cout << "frame1 ref count = " <<
av_buffer_get_ref_count(frame1->buf[0]) << // 线程安全
std::endl;
std::cout << std::endl;

// 引用计数为1 直接删除buf空间 引用计数变为0 且无法访问
av_frame_unref(frame1);

// 释放frame对象空间,并且buf引用计数减一
// buf已经为空 只删除frame对象空间
av_frame_free(&frame1);
av_frame_free(&frame2);

return 0;
}

工程运用

详情看 XSDL