关键帧索引需遍历avpacket并检查flags中av_pkt_flag_key标志,结合pts、dts和pos构建,且必须确保avformat_find_stream_info()已调用、pkt.pos≠-1、pts有效,并注意gop动态变化及idr帧校验。

关键帧索引不是直接“获取”的,而是靠解析 AVPacket 的 flags
FFmpeg 本身不提供“关键帧数组”或“索引表”这类现成结构。所谓关键帧索引,本质是遍历解复用后的每个 AVPacket,检查其 flags 字段是否含 AV_PKT_FLAG_KEY。很多新手误以为调用某个函数就能返回所有关键帧位置,结果翻遍文档找不到——因为根本不存在这样的 API。
注意:AVPacket.flags 只在解复用(av_read_frame())后有效;若跳过解复用直接送包给解码器(比如用 avcodec_send_packet()),该标志可能未被设置或已被清空。
- 必须先调用
avformat_find_stream_info(),否则部分格式(如某些 MP4)的 keyframe 标志可能延迟出现或缺失 - 对封装格式敏感:MKV/FLV 通常可靠;而某些 MP4(尤其 QuickTime 风格)可能把关键帧信息藏在
moov的stssbox 中,此时AVPacket.flags仍可用,但需确保 demuxer 正确解析了该 box - 不要依赖
AVStream.codecpar->video_delay或帧率估算位置——关键帧分布是非均匀的,尤其在场景切换或编码器动态 GOP 控制下
如何安全提取时间戳和文件偏移作为“索引”
真正实用的关键帧索引,至少包含三项:显示时间(pts)、解码时间(dts)、以及在文件中的字节偏移(pos)。其中 pos 最容易被忽略,但它才是实现快速跳转的核心。
AVPacket.pos 在大多数格式中有效(如 AVI、MP4、MKV),但部分流式协议(如 HLS 的 TS 分片)会返回 -1;此时只能退而求其次,用 pts + time_base 计算逻辑时间点,并配合 av_seek_frame() 定位。
- 记录前一个关键帧的
pos时,务必确认pkt.pos != -1,否则后续 seek 会失败 -
pts和dts是整数,单位为AVStream.time_base,不是秒;转换成秒要乘以av_q2d(stream->time_base) - 如果只存
pts,跳转时要用av_seek_frame(fmt_ctx, stream_idx, pts, AVSEEK_FLAG_BACKWARD),而非AVSEEK_FLAG_ANY—— 后者可能定位到非关键帧,导致解码花屏
避免踩坑:GOP 结构变化导致索引失效
视频流可能在运行中动态改变 GOP 模式(比如从固定 I-frame 间隔切换为场景自适应),导致你预建的索引在播放中途突然不准。这不是代码 bug,而是编码器行为。
典型现象:用 FFmpeg 提取的索引跳转到第 10s,画面卡顿或绿屏,但手动拖动播放器却能正常显示——说明索引指向了非 IDR 帧,或该帧依赖后续 P/B 帧才能解码。
- IDR 帧才真正可独立解码;普通 I 帧(如 H.264 中非 IDR 的 I slice)仍可能依赖前面帧,
AV_PKT_FLAG_KEY无法区分二者 - 安全做法:对每个标记为 key 的
AVPacket,再检查pkt.flags & AV_PKT_FLAG_DISPOSABLE是否为 0(表示非 disposable),并结合AVCodecContext.has_b_frames判断 GOP 是否含 B 帧 - 若需高可靠性索引(如视频编辑),建议用
avformat_seek_file()+ 解码循环验证,直到拿到第一个完整AVFrame再记录位置
简单示例:只存关键帧的 pts 和 pos
std::vector<:pair int64_t>> keyframes; // pts, pos
AVPacket pkt;
while (av_read_frame(fmt_ctx, &pkt) >= 0) {
if (pkt.stream_index == video_stream_idx && (pkt.flags & AV_PKT_FLAG_KEY)) {
if (pkt.pos != -1) {
keyframes.emplace_back(pkt.pts, pkt.pos);
}
}
av_packet_unref(&pkt);
}
</:pair>
这段代码够用,但别直接拿去生产环境:它没检查 pts 是否有效(AV_NOPTS_VALUE)、没处理多流时间基差异、也没做异常退出保护。真要落地,得在每次 av_read_frame() 后加 if (pkt.pts == AV_NOPTS_VALUE) continue;,且初始化时确保 video_stream_idx 已通过 av_find_best_stream() 确认。
最常被跳过的细节:没有调用 avformat_flush_inputs(fmt_ctx) 就释放上下文,可能导致最后几个包丢失,从而漏掉末尾关键帧。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











