文章目录
前言
FFmpeg 是一个非常强大的音视频处理库,广泛应用于多媒体流的处理与转换。在本篇文章中,我们将通过一个 C++/Qt 示例,展示如何使用 FFmpeg 打开视频文件,读取视频帧并提取时间戳。文章会重点介绍涉及到的 FFmpeg 函数和结构体,帮助你了解 FFmpeg 的基本用法。
主体
1. AVPacket
作用:
AVPacket
结构体用于存储一个编码后音视频数据包。每个音视频流的帧数据都会以包的形式存在,AVPacket
结构体就存储了这些信息,包括了数据包的时间戳、数据内容等。
相关字段:
pts
:该字段存储解码时间戳(以流的时间基准单位表示)。它用于标识数据包的时间点。dts
:解码时间戳,通常与 PTS 一致,但对于 B 帧可能不同。data
:存储实际的音视频数据(例如,视频帧或音频帧)。size
:data
中的数据大小。
作用场景:
在视频解码和播放时,每一帧的音视频数据会被封装成一个 AVPacket
对象。我们可以通过读取这些数据包来获取视频的每一帧,并通过时间戳等信息进行同步。
2. av_read_frame
函数原型:
c
int av_read_frame(AVFormatContext *s, AVPacket *pkt);
作用:
av_read_frame
函数从媒体文件中读取一帧数据,并将数据封装在 AVPacket
中。此函数对于读取视频或音频的流数据是至关重要的。它会从输入流中读取音视频数据包,直到所有数据都被处理完。
参数:
s
:指向AVFormatContext
的指针,表示已打开的媒体文件。pkt
:指向AVPacket
的指针,用于存储读取到的数据包。
返回值:
成功时返回 0,失败时返回负值。
相关结构体:
AVFormatContext
:表示已打开的媒体文件的上下文。AVPacket
:表示存储在数据包中的音视频帧数据。
3. av_packet_unref
函数原型:
c
void av_packet_unref(AVPacket *pkt);
作用:
av_packet_unref
用于释放 AVPacket
中的内存,清除数据包中的内容。每当我们使用完 AVPacket
后,都应该调用该函数进行内存清理,避免内存泄漏。
参数:
pkt
:指向AVPacket
结构体的指针,表示需要清理的AVPacket
。
返回值:
无
4. AVRational
作用:
AVRational
是 FFmpeg 中用于表示有理数的结构体。在视频处理中,AVRational
结构体经常用来表示时间戳与流时间基之间的关系。
相关字段:
num
:分子。den
:分母。
作用场景:
在 FFmpeg 中,许多与时间相关的计算(如帧率、时间基准等)都使用 AVRational
结构体来进行表示和计算。例如,视频的时间基(time_base
)就是一个 AVRational
,表示每个时间单位的长度。
5. r2d(AVRational r)
作用:
r2d
是一个辅助函数,将 AVRational
转换为 double
类型。它通过将 AVRational
的分子除以分母,返回一个浮动数值。
代码实现:
cpp
static double r2d(AVRational r)
{
return r.num == 0 || r.den == 0 ? 0. : (double)r.num / (double)r.den;
}
参数:
r
:AVRational
结构体,表示一个有理数。
返回值:
返回 AVRational
的值,转换为 double
类型。
代码分析
打开视频文件
cpp
av_register_all();
char *path = "video.mp4";
AVFormatContext *ic = NULL;
int re = avformat_open_input(&ic, path, 0, 0);
if (re != 0)
{
char buf[1024] = { 0 };
av_strerror(re, buf, sizeof(buf));
printf("open %s failed: %s\n", path, buf);
getchar();
return -1;
}
首先,我们调用 av_register_all()
来初始化 FFmpeg 库。接着,通过 avformat_open_input
打开视频文件。如果打开失败,程序会输出错误信息并退出。
读取视频帧
cpp
int totalSec = ic->duration / AV_TIME_BASE;
printf("file totalSec is %d-%d\n", totalSec / 60, totalSec % 60);
for (;;)
{
AVPacket pkt;
re = av_read_frame(ic, &pkt);
if (re != 0) break;
int pts = pkt.pts * r2d(ic->streams[pkt.stream_index]->time_base) * 1000;
printf("pts = %d\n", pts);
av_packet_unref(&pkt);
}
接下来,我们计算视频文件的总时长。通过 av_read_frame
函数,我们逐帧读取视频文件的数据包(AVPacket
)。每读取一帧,我们会计算并打印该帧的时间戳(PTS)。使用 r2d
函数将 AVRational
转换为 double
类型后,再乘以 1000 转换为毫秒。最后,调用 av_packet_unref
清除数据包。
关闭视频文件
cpp
avformat_close_input(&ic);
读取完所有帧后,我们调用 avformat_close_input
来关闭文件并释放相关资源。
总结
在本篇文章中,我们介绍了如何通过 FFmpeg 打开视频文件,读取视频数据包,并提取时间戳。关键的 FFmpeg 函数如 av_read_frame
、av_packet_unref
和结构体如 AVPacket
,AVFormatContext
在此过程中扮演了重要角色。通过这个示例,我们展示了如何结合 FFmpeg 和 Qt 进行音视频处理与播放,并且理解了 FFmpeg 内部的处理流程。这为进一步深入了解 FFmpeg 提供了良好的基础。