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; } }
核心问题分析
时间戳单位不匹配
av_seek_frame的第三个参数要求是视频流时间基(stream_tb)下的PTS值,但代码中直接将requestedTimestamp作为target_pts传入,若requestedTimestamp是其他时间单位(比如毫秒、微秒),会导致seek到错误位置,最终解码从第0帧开始。Seek标志位不足
仅使用AVSEEK_FLAG_BACKWARD只能定位到目标PTS之前的最近关键帧,缺少AVSEEK_FLAG_FRAME时,无法精准定位到目标帧附近,导致后续解码需要跳过大量无效帧,甚至出现逻辑判断错误。帧PTS处理不严谨
代码直接使用frame->pts,但部分帧的pts可能为AV_NOPTS_VALUE(无有效时间戳),此时会导致判断逻辑失效,无法正确跳过目标PTS之前的帧。冗余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
相关产品推荐
相关产品推荐

