You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何借助FFmpeg API精准定位到媒体的指定时间戳?

解决FFmpeg音频精准时间戳定位问题

核心问题分析

你的代码存在两个致命错误,导致seek失败或陷入循环:

  1. 时间单位不匹配:seek_ts被赋值为微秒级的seek_abs(seek_pos*AV_TIME_BASE),但curr_s是通过packet->pts * av_q2d(time_base)计算出的秒级值,两者量级差1e6,导致curr_s < seek_ts永远成立,陷入无限循环。
  2. seek处理时机错误:你先读取了packet再处理seek请求,此时读取的packet是seek前的旧数据,即便执行seek后continue,后续循环中错误的单位比较依然会卡住流程。

修复方案

针对音频(MP3)的特性,结合FFmpeg 4.4.4的API,按以下步骤修改:

1. 统一时间单位

将所有时间戳统一为微秒(和AV_TIME_BASE对齐),避免单位混乱:

// 计算packet的微秒级时间戳
int64_t curr_us = packet->pts * av_q2d(pfmtctx->streams[stream]->time_base) * AV_TIME_BASE;

2. 调整seek处理顺序

在av_read_frame之前处理seek请求,确保读取的第一个packet就是seek后的新数据:

void decode_func(...)
{
    while (1) {
        // 先处理seek请求,再读取packet
        if (seek_req) {
            int64_t seek_abs = seek_pos * AV_TIME_BASE;
            if (seek_abs < 0) seek_abs = 0;
            
            printf("Seek to %lld us\n", seek_abs);
            // 指定音频流索引,提高seek精度
            int ret = avformat_seek_file(pfmtctx, stream, INT64_MIN, seek_abs, INT64_MAX, AVSEEK_FLAG_ANY);
            if (ret >= 0) {
                avcodec_flush_buffers(pcodectx);
                seek_ts = seek_abs; // 微秒级目标时间
                seek_req = 0;
                // 清空当前packet(如果之前有残留)
                av_packet_unref(packet);
                continue;
            } else {
                printf("Failed to seek to %lld\n", seek_abs);
                seek_req = 0;
                seek_ts = 0;
            }
        }

        if (av_read_frame(pfmtctx, packet) < 0) {
            avcodec_flush_buffers(pcodectx);
            printf("Got end of media, breaking\n");
            break;
        }

        // 丢弃目标时间之前的数据包(统一微秒单位)
        if (seek_ts > 0) {
            int64_t curr_us = packet->pts * av_q2d(pfmtctx->streams[stream]->time_base) * AV_TIME_BASE;
            // 注意:MP3的pts可能为AV_NOPTS_VALUE,需要额外处理
            if (packet->pts != AV_NOPTS_VALUE && curr_us < seek_ts) {
                av_packet_unref(packet);
                continue;
            } else {
                seek_ts = 0;
            }
        }

        // 解码逻辑
        int res = avcodec_send_packet(pcodectx, packet);
        while (res >= 0) {
            res = avcodec_receive_frame(pcodectx, pFrame);
            if (res == AVERROR(EAGAIN)) break;
            if (res == AVERROR_EOF) break;
            if (res >= 0) {
                /// 处理解码后的音频帧
            }
        }
        av_frame_unref(pFrame);
        av_packet_unref(packet);
    }
}

3. 针对MP3的额外优化

  • 移除AVSEEK_FLAG_BACKWARD:MP3的每个帧都可独立解码,使用AVSEEK_FLAG_ANY即可直接定位到目标时间附近的帧,无需回退到前一个关键帧(音频无严格意义的关键帧)。
  • 处理AV_NOPTS_VALUE:部分MP3文件的packet可能没有pts,此时需要用dts或跳过时间戳检查,直接进入解码流程。
  • 清空packet:执行seek后,务必清空当前的packet对象,避免残留旧数据干扰后续读取。

关键注意事项

  • 音频的time_base通常是1/sample_rate,所以转换时要确保使用对应流的time_base,不要混淆格式上下文和编解码器上下文的time_base。
  • avcodec_flush_buffers必须在seek后调用,清空编解码器的缓存,避免输出seek前的残留帧。

内容的提问来源于stack exchange,提问作者wangt13

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 09:05:09