FFmpeg 基本API av_read_frame函数内部调用流程分析
1、av_read_frame函数定义
av_read_frame 是 FFmpeg 库中的一个函数,用于从 AVFormatContext 中读取一个 AVPacket。它负责从解复用器(demuxer)获取原始编码数据包(AVPacket)。
int av_read_frame(AVFormatContext *s, AVPacket *pkt);
参数说明:
- s: 一个指向 AVFormatContext 结构体的指针,用于存储 AVFormatContext 对象。
- pkt: 一个指向 AVPacket 结构体的指针,用于存储读取到的 AVPacket。
返回值: - 如果读取成功,则返回 0;如果读取失败,则返回一个负值。在调用 av_read_frame 函数之后,可以通过 AVPacket 结构体中的成员来获取读取到的音视频数据,例如 data、size 和 pts 等。
注意:在使用 FFmpeg 进行音视频处理时,通常需要先调用 avformat_open_input 函数打开输入 URL,然后调用 avformat_find_stream_info 函数获取流信息,最后调用 av_read_frame 函数读取音视频数据。
大概流程如下所示:
2、av_read_frame内部调用逻辑
2.1 函数入口与初始化
FFFormatContext *const si = ffformatcontext(s);
const int genpts = s->flags & AVFMT_FLAG_GENPTS; // 是否生成PTS标志
int eof = 0;
int ret;
/*
关键变量:genpts:控制是否自动生成缺失的PTS(演示时间戳)si->packet_buffer:内部数据包缓存队列
*/
2.2 非GENPTS模式(简单读取)
if (!genpts) {ret = si->packet_buffer.head ? avpriv_packet_list_get(&si->packet_buffer, pkt) // 从缓存取包: read_frame_internal(s, pkt); // 直接读取新包if (ret < 0) return ret;goto return_packet; // 跳转到后处理
}
/*
逻辑流程:优先从缓存队列取包(packet_buffer)缓存为空则调用底层read_frame_internal()直接进入后处理阶段 */
2.3 GENPTS模式(复杂时间戳处理)
for (;;) {PacketListEntry *pktl = si->packet_buffer.head;if (pktl) {AVPacket *next_pkt = &pktl->pkt;// 时间戳修复逻辑...}...
}
1. 时间戳修复核心算法
if (next_pkt->dts != AV_NOPTS_VALUE) {int wrap_bits = s->streams[...]->pts_wrap_bits;while (pktl && next_pkt->pts == AV_NOPTS_VALUE) {if (pktl->pkt.stream_index == ... && av_compare_mod(...) < 0) {if (av_compare_mod(...)) { // 非B帧next_pkt->pts = pktl->pkt.dts; // 用后续包DTS修复当前PTS}}pktl = pktl->next;}// EOF特殊处理if (eof && next_pkt->pts == AV_NOPTS_VALUE) {next_pkt->pts = last_dts + next_pkt->duration; // 用时长推算PTS}
}
/*
修复策略:
前向探测:遍历后续包寻找参考DTS
非B帧优先:优先使用I/P帧的DTS
EOF启发式:文件末尾用最后DTS+时长推算
*/2. 出队条件判断
st = s->streams[next_pkt->stream_index];
if (!(next_pkt->pts == AV_NOPTS_VALUE && st->discard < AVDISCARD_ALL &&next_pkt->dts != AV_NOPTS_VALUE && !eof))
{ret = avpriv_packet_list_get(...); // 满足条件出队goto return_packet;
}
/*
出队条件(满足任一):
PTS有效
流设置为丢弃所有包
DTS无效
未到文件末尾
*/3. 读取新包流程
ret = read_frame_internal(s, pkt); // 底层读包
if (ret < 0) {if (pktl && ret != AVERROR(EAGAIN)) {eof = 1; // 标记EOF但继续处理缓存continue;}return ret; // 真实错误退出
}
// 新包加入缓存
ret = avpriv_packet_list_put(&si->packet_buffer, pkt, NULL, 0);
/*
EOF处理:标记eof但继续处理缓存包
缓存管理:新包加入队列尾部
*/
2.4 后处理阶段 (return_packet)
// 关键帧索引处理
if ((s->iformat->flags & AVFMT_GENERIC_INDEX) && pkt->flags & AV_PKT_FLAG_KEY)
{ff_reduce_index(s, st->index); // 索引优化av_add_index_entry(st, pkt->pos, pkt->dts, ...); // 添加索引
}// 相对时间戳转换
if (is_relative(pkt->dts)) pkt->dts -= RELATIVE_TS_BASE;
if (is_relative(pkt->pts)) pkt->pts -= RELATIVE_TS_BASE;
/*
关键操作:
索引构建:为关键帧创建 seek 索引
时间戳转换:将相对时间戳转为绝对时间戳
*/