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

FFmpeg av_seek_frame()调用成功但解码始终从起始帧开始的问题

FFmpeg视频播放器seek到指定PTS后仍从第0帧解码的问题修复

我用C++结合FFmpeg开发视频播放器,需要定位到指定PTS并从该位置解码,但调用av_seek_frame()成功后,解码始终从第0帧而非目标帧启动,相关decodeLoop函数代码如下:

void decodeLoop() {
      while (!stopThread) {

          std::unique_lock<std::mutex> lock(mtx);
          cv.wait(lock, [this] { return decoding || stopThread; });

          if (stopThread) break;

          int64_t reqTimestamp = requestedTimestamp.load();

          // **Skip redundant seeks**
          if (reqTimestamp == lastRequestedTimestamp.load()) {
              continue;  // Avoid unnecessary seeks
          }

          int64_t requestedTS = requestedTimestamp.load();
          AVRational stream_tb = fmt_ctx->streams[video_stream_index]->time_base;
          int64_t target_pts = reqTimestamp;
          target_pts = FFMAX(target_pts, 0); // Ensure it's not negative

          int seek_flags = AVSEEK_FLAG_BACKWARD;

          if (av_seek_frame(fmt_ctx, video_stream_index, target_pts, seek_flags) >= 0) {
              avcodec_flush_buffers(codec_ctx);  // Clear old frames from the decoder
              avformat_flush(fmt_ctx);          // **Flush demuxer**
              qDebug() << "Seek successful to PTS:" << target_pts;
       //      avfilter_graph_free(&filter_graph);  // Reset filter graph
          }
          else {
              qDebug() << "Seeking failed!";
              decoding = false;
              continue;
          }

          lock.unlock();

          // Keep decoding frames until we reach (or slightly exceed) requestedTimestamp
          bool frameDecoded = false;
          while (av_read_frame(fmt_ctx, pkt) >= 0) {
              if (pkt->stream_index == video_stream_index) {
                  if (avcodec_send_packet(codec_ctx, pkt) == 0) {
                      while (avcodec_receive_frame(codec_ctx, frame) == 0) {
                          int64_t frame_pts = frame->pts;

                          // Skip frames before reaching target PTS
                          if (frame_pts < requestedTimestamp.load()) {

                              qDebug() << "SKIPPING FRAMES " << frame_pts;
                              av_frame_unref(frame);  // Free unused frame memory
                              continue;  // Discard early frames
                          }

                          qDebug() << "DECODING THE SEEKED FRAMES " << frame_pts;

                          if (frame_pts >= requestedTimestamp.load()) {
                             
                                  // Accept frame only if it's close enough to the target PTS
                                  current_pts.store(frame_pts);
                                  lastRequestedTimestamp.store(requestedTimestamp.load());
                                  convertHWFrameToImage(frame);
                                  emit frameDecodedSignal(outputImage);
                                  frameDecoded = true;
                                  break;  // Stop decoding once we reach the desired frame
                             
                          }
                      }
                  }
              }
              av_packet_unref(pkt);

              if (frameDecoded) break;
          }

          decoding = false;
      }
  }

核心问题分析

  1. 时间戳单位不匹配
    av_seek_frame的第三个参数要求是视频流时间基(stream_tb)下的PTS值,但代码中直接将requestedTimestamp作为target_pts传入,若requestedTimestamp是其他时间单位(比如毫秒、微秒),会导致seek到错误位置,最终解码从第0帧开始。

  2. Seek标志位不足
    仅使用AVSEEK_FLAG_BACKWARD只能定位到目标PTS之前的最近关键帧,缺少AVSEEK_FLAG_FRAME时,无法精准定位到目标帧附近,导致后续解码需要跳过大量无效帧,甚至出现逻辑判断错误。

  3. 帧PTS处理不严谨
    代码直接使用frame->pts,但部分帧的pts可能为AV_NOPTS_VALUE(无有效时间戳),此时会导致判断逻辑失效,无法正确跳过目标PTS之前的帧。

  4. 冗余seek判断时机错误
    lastRequestedTimestamp在找到目标帧后才更新,若seek后解码过程中出现异常,会导致重复进入seek逻辑,干扰正常解码流程。


修复后的代码

void decodeLoop() {
    while (!stopThread) {
        std::unique_lock<std::mutex> lock(mtx);
        cv.wait(lock, [this] { return decoding || stopThread; });

        if (stopThread) break;

        int64_t reqTimestamp = requestedTimestamp.load();

        // 跳过重复seek
        if (reqTimestamp == lastRequestedTimestamp.load()) {
            decoding = false;
            continue;
        }

        AVRational stream_tb = fmt_ctx->streams[video_stream_index]->time_base;
        // 关键:将请求的时间戳转换为视频流时间基下的PTS
        // 假设reqTimestamp是毫秒单位,若为其他单位需调整转换基准
        int64_t target_pts = av_rescale_q(reqTimestamp, av_make_q(1, 1000), stream_tb);
        target_pts = FFMAX(target_pts, 0);

        // 使用更精准的seek标志位:找目标PTS之前的最近关键帧,同时允许按帧定位
        int seek_flags = AVSEEK_FLAG_BACKWARD | AVSEEK_FLAG_FRAME;

        if (av_seek_frame(fmt_ctx, video_stream_index, target_pts, seek_flags) >= 0) {
            avcodec_flush_buffers(codec_ctx); // 清空解码器缓存
            avformat_flush(fmt_ctx);          // 清空解复用器缓存
            qDebug() << "Seek successful to stream TB PTS:" << target_pts;
            // 立即更新lastRequestedTimestamp,避免重复seek
            lastRequestedTimestamp.store(reqTimestamp);
        } else {
            qDebug() << "Seeking failed!";
            decoding = false;
            continue;
        }

        lock.unlock();

        bool frameDecoded = false;
        while (av_read_frame(fmt_ctx, pkt) >= 0) {
            if (pkt->stream_index == video_stream_index) {
                if (avcodec_send_packet(codec_ctx, pkt) == 0) {
                    while (avcodec_receive_frame(codec_ctx, frame) == 0) {
                        // 处理无有效PTS的情况,使用best_effort_timestamp替代
                        int64_t frame_pts = frame->pts != AV_NOPTS_VALUE 
                                            ? frame->pts 
                                            : frame->best_effort_timestamp;
                        // 将帧PTS转换为与reqTimestamp相同的时间单位(这里是毫秒)
                        int64_t frame_pts_ms = av_rescale_q(frame_pts, stream_tb, av_make_q(1, 1000));

                        // 跳过目标时间戳之前的帧
                        if (frame_pts_ms < reqTimestamp) {
                            qDebug() << "SKIPPING FRAMES (ms):" << frame_pts_ms;
                            av_frame_unref(frame);
                            continue;
                        }

                        qDebug() << "DECODING TARGET FRAME (ms):" << frame_pts_ms;
                        current_pts.store(frame_pts);
                        convertHWFrameToImage(frame);
                        emit frameDecodedSignal(outputImage);
                        frameDecoded = true;
                        av_frame_unref(frame);
                        break;
                    }
                }
            }
            av_packet_unref(pkt);

            if (frameDecoded) break;
        }

        decoding = false;
    }
}

关键修复点说明

  • 时间戳统一转换:将请求的时间戳(假设为毫秒)与视频流时间基双向转换,确保av_seek_frame的参数和帧PTS判断使用统一单位。
  • 优化seek标志位:添加AVSEEK_FLAG_FRAME让seek操作更精准地定位到帧级别,避免偏移过大。
  • 处理无效PTS:当帧的pts无效时,使用best_effort_timestamp作为 fallback,保证时间戳判断逻辑正常运行。
  • 提前更新seek标记:在seek成功后立即更新lastRequestedTimestamp,避免重复触发不必要的seek操作。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 15:16:12