如何获取av_seek_frame后的当前Frame序号?

How to get currecnt AVFrame siquential number after av_seek_frame?

我是解码器和 FFmpeg 的新手。我需要的是实现可以通过一些步骤(例如:20)读取帧的逻辑,换句话说,我有一个文件,我需要读取帧 0、20、40、60...

我做的是


AVFrame * m_pAVFrame = nullptr;
int firstFrameIdx = 0;

while(true)
{

if(firstFrameIdx > 0)
{
int64_t seekTarget = FrameToPts(m_pAVStream, firstFrameIdx);
nRet = av_seek_frame(m_pAVFormatCtx, m_streamIdx, seekTarget, AVSEEK_FLAG_FRAME);
}

nRet = av_read_frame(m_pAVFormatCtx, m_pAVPkt);
ret = avcodec_send_packet(m_pAVCodecCtx, m_pAVPkt);
ret = avcodec_receive_frame(m_pAVCodecCtx, m_pAVFrame);

firstFrameIdx+=20;
}

但问题是 av_seek_frame 将指针移动到 Iframe,假设视频文件有每个 15 个关键帧(当然它可以是不同的数字),例如 0、15、30 ... 所以这意味着如果我试图寻找第 20 帧,我实际上会到达第 15 帧。

我看到 AVFrame 有一个 属性 coded_picture_number 可能对我的情况有用,我试图将这些返回值放入向量中,但我发现这些值无关紧要

我希望看到的是 0, 15, 30, 45...

例如,如果我可以进行搜索然后得到一个框架来询问订单号(例如:15),那么我可以理解 Iframe 是 15,为了达到第 20 帧,我需要读取并跳过 5 帧,结果,我到达了第 20 帧,但是正如你在上面看到的那样,在搜索之后我询问了订单号并得到了奇怪的值,比如 0, 2, 1, 3... 与它无关......

感觉漏掉了一些基础知识,有人可以解释一下如何进行查找逻辑并找到正确的框架吗?

更新

初始化逻辑

bool FFmpegDecoder::Init(unsigned char const * pData, int dataSize, int reqId, bool bUseHWAccel, FFmpegDecoderCallback * pCB)
{
    Deinit();

    // From memory:
    if (pData == nullptr || dataSize == 0)
    {
        printf("FFmpegDecoder::Init FAILED: neither filename nor memory data were given !\n");
        return false;
    }
    m_pIoCtx = std::make_shared<AVIOContextMem>(pData, dataSize);

    if (m_pIoCtx->IsValid() == false)
    {
        printf("FFmpegDecoder::Init FAILED: m_pIoCtx is nullptr !\n");
        return false;
    }

    m_reqId = reqId;
    m_bUseHWAccel = bUseHWAccel;
    m_pCB = pCB;
    m_pData = pData;
    m_dataSize = dataSize;

    m_bRequestedAbort = false;

    m_pAVPkt = av_packet_alloc();
    av_init_packet(m_pAVPkt);

    m_pAVFrame = av_frame_alloc();
    m_pAVFrameRGB = av_frame_alloc();

    if (m_bUseHWAccel)
    {
        m_pSwAVFrameForHw = av_frame_alloc();
    }

    m_pAVFormatCtx = avformat_alloc_context();
    m_pIoCtx->initAVFormatContext(m_pAVFormatCtx);

    if (avformat_open_input(&m_pAVFormatCtx, "", nullptr, nullptr) != 0)
    {
        printf("FFmpegDecoder::InitFFmpeg: error in avformat_open_input\n");
        return false;
    }

    if (avformat_find_stream_info(m_pAVFormatCtx, nullptr) < 0)
    {
        printf("FFmpegDecoder::InitFFmpeg: error in avformat_find_stream_info\n");
        return false;
    }


    //av_dump_format(ctx_format, 0, "", false);
    for (int i = 0; i < (int)m_pAVFormatCtx->nb_streams; i++)
    {
        if (m_pAVFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
        {
            m_streamIdx = i;
            m_pAVStream = m_pAVFormatCtx->streams[i];
            break;
        }
    }
    if (m_pAVStream == nullptr)
    {
        printf("FFmpegDecoder::InitFFmpeg: failed to find video stream\n");
        return false;
    }

    m_pAVCodec = avcodec_find_decoder(m_pAVStream->codecpar->codec_id);
    if (!m_pAVCodec)
    {
        printf("FFmpegDecoder::InitFFmpeg: error in avcodec_find_decoder\n");
        return false;
    }

    m_pAVCodecCtx = avcodec_alloc_context3(m_pAVCodec);
    if (!m_pAVCodecCtx)
    {
        printf("FFmpegDecoder::InitFFmpeg: error in avcodec_alloc_context3\n");
        return false;
    }

    if (avcodec_parameters_to_context(m_pAVCodecCtx, m_pAVStream->codecpar) < 0)
    {
        printf("FFmpegDecoder::InitFFmpeg: error in avcodec_parameters_to_context\n");
        return false;
    }

    if (m_bUseHWAccel)
    {
        AVHWDeviceType hwDevType = AV_HWDEVICE_TYPE_DXVA2;
        g_hwPixFormat = find_fmt_by_hw_type(hwDevType);
        m_pAVCodecCtx->get_format = get_hw_format;
        av_opt_set_int(m_pAVCodecCtx, "refcounted_frames", 1, 0);
        if (av_hwdevice_ctx_create(&m_pBufferRefForHw, hwDevType, NULL, NULL, 0) < 0)
        {
            printf("FFmpegDecoder::InitFFmpeg: error in av_hwdevice_ctx_create\n");
            return false;
        }
        m_pAVCodecCtx->hw_device_ctx = av_buffer_ref(m_pBufferRefForHw);
    }

    if (avcodec_open2(m_pAVCodecCtx, m_pAVCodec, nullptr) < 0)
    {
        printf("FFmpegDecoder::InitFFmpeg: error in avcodec_open2\n");
        return false;
    }

    m_pAVFrameRGB->format = AV_PIX_FMT_BGR24;
    m_pAVFrameRGB->width = m_pAVCodecCtx->width;
    m_pAVFrameRGB->height = m_pAVCodecCtx->height;
    if (av_frame_get_buffer(m_pAVFrameRGB, 32) != 0)
    {
        printf("FFmpegDecoder::InitFFmpeg: error in av_frame_get_buffer\n");
        return false;
    }

    m_streamRotationDegrees = GetAVStreamRotation(m_pAVStream);
    m_estimatedFramesCount = 0;
    assert(m_pAVFormatCtx->nb_streams > 0);
    if (m_pAVFormatCtx->nb_streams > 0)
    {
        m_estimatedFramesCount = m_pAVFormatCtx->streams[0]->nb_frames;
    }

    //InitConvertColorSpace
    // Init converter from YUV420p to BGR:
    if (m_bUseHWAccel)
    {
        m_pSwsCtxConvertImg = sws_getContext(m_pAVCodecCtx->width, m_pAVCodecCtx->height, AV_PIX_FMT_NV12, m_pAVCodecCtx->width, m_pAVCodecCtx->height, AV_PIX_FMT_RGB24, SWS_FAST_BILINEAR, NULL, NULL, NULL);
    }
    else
    {
        m_pSwsCtxConvertImg = sws_getContext(m_pAVCodecCtx->width, m_pAVCodecCtx->height, m_pAVCodecCtx->pix_fmt, m_pAVCodecCtx->width, m_pAVCodecCtx->height, AV_PIX_FMT_RGB24, SWS_FAST_BILINEAR, NULL, NULL, NULL);
    }
    if (!m_pSwsCtxConvertImg)
    {
        printf("FFmpegDecoder::InitFFmpeg: error in sws_getContext\n");
        return false;
    }

    m_bInitOK = true;
    return true;
}

具有上次更改的解码逻辑

void FFmpegDecoder::DecodeWithStep(int step)
{
    step = 20;
    int currentFramePos = 0;
    int number_of_errors = 0;
    const int MAX_ERROR_NUM = 10;

    while (true)
    {
        if (step > 0)
        {
            int seekPos = currentFramePos + step;
            int64_t seekTarget = FrameToPts(m_pAVStream, seekPos);

            if (av_seek_frame(m_pAVFormatCtx, m_streamIdx, seekTarget, AVSEEK_FLAG_FRAME) < 0)
            {
                number_of_errors++;
            }
            else
            {
                currentFramePos = seekPos;
                m_is_seeked = true;
            }
        }

        if (av_read_frame(m_pAVFormatCtx, m_pAVPkt) == 0)
        {
            if (m_pAVPkt->stream_index == m_streamIdx) //to make sure that I dont get packets from other streams
            {
                if (m_is_seeked)
                {
                    avcodec_flush_buffers(m_pAVCodecCtx);
                    m_is_seeked = false;
                }

                if (avcodec_send_packet(m_pAVCodecCtx, m_pAVPkt) == 0)
                {
                    printf("----- BATCH\n");

                    while (avcodec_receive_frame(m_pAVCodecCtx, m_pAVFrame) == 0)
                    {
                        ProcessFrame(m_pAVFrame);
                        av_frame_unref(m_pAVFrame);
                        currentFramePos++;
                        printf("----- cur position (%d) \n", currentFramePos);
                    }
                }

                av_packet_unref(m_pAVPkt);
            }
        }
        else
        {
            number_of_errors++;
        }

        if (number_of_errors == MAX_ERROR_NUM)
        {
            printf("EXIT\n");
            break;
        }
    }
}

更新

初始化逻辑

bool FFmpegDecoder::Init(unsigned char const * pData, int dataSize, int reqId, bool bUseHWAccel, FFmpegDecoderCallback * pCB)
{
    Deinit();

    // From memory:
    if (pData == nullptr || dataSize == 0)
    {
        printf("FFmpegDecoder::Init FAILED: neither filename nor memory data were given !\n");
        return false;
    }
    m_pIoCtx = std::make_shared<AVIOContextMem>(pData, dataSize);

    if (m_pIoCtx->IsValid() == false)
    {
        printf("FFmpegDecoder::Init FAILED: m_pIoCtx is nullptr !\n");
        return false;
    }

    m_reqId = reqId;
    m_bUseHWAccel = bUseHWAccel;
    m_pCB = pCB;
    m_pData = pData;
    m_dataSize = dataSize;

    m_bRequestedAbort = false;

    m_pAVPkt = av_packet_alloc();
    av_init_packet(m_pAVPkt);

    m_pAVFrame = av_frame_alloc();
    m_pAVFrameRGB = av_frame_alloc();

    if (m_bUseHWAccel)
    {
        m_pSwAVFrameForHw = av_frame_alloc();
    }

    m_pAVFormatCtx = avformat_alloc_context();
    m_pIoCtx->initAVFormatContext(m_pAVFormatCtx);

    if (avformat_open_input(&m_pAVFormatCtx, "", nullptr, nullptr) != 0)
    {
        printf("FFmpegDecoder::InitFFmpeg: error in avformat_open_input\n");
        return false;
    }

    if (avformat_find_stream_info(m_pAVFormatCtx, nullptr) < 0)
    {
        printf("FFmpegDecoder::InitFFmpeg: error in avformat_find_stream_info\n");
        return false;
    }


    //av_dump_format(ctx_format, 0, "", false);
    for (int i = 0; i < (int)m_pAVFormatCtx->nb_streams; i++)
    {
        if (m_pAVFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
        {
            m_streamIdx = i;
            m_pAVStream = m_pAVFormatCtx->streams[i];
            break;
        }
    }
    if (m_pAVStream == nullptr)
    {
        printf("FFmpegDecoder::InitFFmpeg: failed to find video stream\n");
        return false;
    }

    m_pAVCodec = avcodec_find_decoder(m_pAVStream->codecpar->codec_id);
    if (!m_pAVCodec)
    {
        printf("FFmpegDecoder::InitFFmpeg: error in avcodec_find_decoder\n");
        return false;
    }

    m_pAVCodecCtx = avcodec_alloc_context3(m_pAVCodec);
    if (!m_pAVCodecCtx)
    {
        printf("FFmpegDecoder::InitFFmpeg: error in avcodec_alloc_context3\n");
        return false;
    }

    if (avcodec_parameters_to_context(m_pAVCodecCtx, m_pAVStream->codecpar) < 0)
    {
        printf("FFmpegDecoder::InitFFmpeg: error in avcodec_parameters_to_context\n");
        return false;
    }

    if (m_bUseHWAccel)
    {
        AVHWDeviceType hwDevType = AV_HWDEVICE_TYPE_DXVA2;
        g_hwPixFormat = find_fmt_by_hw_type(hwDevType);
        m_pAVCodecCtx->get_format = get_hw_format;
        av_opt_set_int(m_pAVCodecCtx, "refcounted_frames", 1, 0);
        if (av_hwdevice_ctx_create(&m_pBufferRefForHw, hwDevType, NULL, NULL, 0) < 0)
        {
            printf("FFmpegDecoder::InitFFmpeg: error in av_hwdevice_ctx_create\n");
            return false;
        }
        m_pAVCodecCtx->hw_device_ctx = av_buffer_ref(m_pBufferRefForHw);
    }

    if (avcodec_open2(m_pAVCodecCtx, m_pAVCodec, nullptr) < 0)
    {
        printf("FFmpegDecoder::InitFFmpeg: error in avcodec_open2\n");
        return false;
    }

    m_pAVFrameRGB->format = AV_PIX_FMT_BGR24;
    m_pAVFrameRGB->width = m_pAVCodecCtx->width;
    m_pAVFrameRGB->height = m_pAVCodecCtx->height;
    if (av_frame_get_buffer(m_pAVFrameRGB, 32) != 0)
    {
        printf("FFmpegDecoder::InitFFmpeg: error in av_frame_get_buffer\n");
        return false;
    }

    m_streamRotationDegrees = GetAVStreamRotation(m_pAVStream);
    m_estimatedFramesCount = 0;
    assert(m_pAVFormatCtx->nb_streams > 0);
    if (m_pAVFormatCtx->nb_streams > 0)
    {
        m_estimatedFramesCount = m_pAVFormatCtx->streams[0]->nb_frames;
    }

    //InitConvertColorSpace
    // Init converter from YUV420p to BGR:
    if (m_bUseHWAccel)
    {
        m_pSwsCtxConvertImg = sws_getContext(m_pAVCodecCtx->width, m_pAVCodecCtx->height, AV_PIX_FMT_NV12, m_pAVCodecCtx->width, m_pAVCodecCtx->height, AV_PIX_FMT_RGB24, SWS_FAST_BILINEAR, NULL, NULL, NULL);
    }
    else
    {
        m_pSwsCtxConvertImg = sws_getContext(m_pAVCodecCtx->width, m_pAVCodecCtx->height, m_pAVCodecCtx->pix_fmt, m_pAVCodecCtx->width, m_pAVCodecCtx->height, AV_PIX_FMT_RGB24, SWS_FAST_BILINEAR, NULL, NULL, NULL);
    }
    if (!m_pSwsCtxConvertImg)
    {
        printf("FFmpegDecoder::InitFFmpeg: error in sws_getContext\n");
        return false;
    }

    m_bInitOK = true;
    return true;
}
void FFmpegDecoder::DecodeWithStep(int step)
{
    step = 20;
    int currentFramePos = 0;
    int number_of_errors = 0;
    const int MAX_ERROR_NUM = 10;
    int seekPos = 0;

    while (true)
    {
        if (step > 1)
        {
            seekPos = currentFramePos + step;
            int64_t seekTarget = FrameToPts(m_pAVStream, seekPos);

            if (av_seek_frame(m_pAVFormatCtx, m_streamIdx, seekTarget, AVSEEK_FLAG_FRAME) < 0)
            {
                number_of_errors++;
            }
            else
            {
                m_is_seeked = true;
            }
        }

        while (true)
        {
            if (av_read_frame(m_pAVFormatCtx, m_pAVPkt) == 0)
            {
                if (m_pAVPkt->stream_index == m_streamIdx) //to make sure that I dont get packets from other streams
                {
                    if (m_is_seeked)
                    {
                        avcodec_flush_buffers(m_pAVCodecCtx);
                        m_is_seeked = false;
                    }

                    if (avcodec_send_packet(m_pAVCodecCtx, m_pAVPkt) == 0)
                    {
                        int ret = 0;
                        while (ret >= 0)
                        {
                            ret = avcodec_receive_frame(m_pAVCodecCtx, m_pAVFrame);

                            if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
                            {
                                av_frame_unref(m_pAVFrame);
                                break;
                            }

                            currentFramePos = m_pAVFrame->display_picture_number; //In order to get position of currect frame (seek move poiter to the key frame)

                            if (currentFramePos < seekPos) //Some frames need to be skiped in order to reach needed frame
                            {
                                printf("----- SKIP : cur position (%d) \n", currentFramePos);
                                av_frame_unref(m_pAVFrame);
                                continue;
                            }

                            ProcessFrame(m_pAVFrame); //needed frame was processed
                            av_frame_unref(m_pAVFrame);
                            printf("----- cur position (%d) \n", currentFramePos);
                            break;
                        }
                    }

                    av_packet_unref(m_pAVPkt);
                }
                else
                {
                    av_packet_unref(m_pAVPkt); //we got a frame from the wrong stream
                }
            }
            else
            {
                number_of_errors++;
            }

            if (number_of_errors == MAX_ERROR_NUM)
            {
                printf("EXIT1\n");
                break;
            }
        }

        if (number_of_errors == MAX_ERROR_NUM)
        {
            printf("EXIT2\n");
            break;
        }
    }
}
int64_t FrameToPts(AVStream* pavStream, int frame)
{
    return (int64_t(frame) * pavStream->r_frame_rate.den *  pavStream->time_base.den) /
        (int64_t(pavStream->r_frame_rate.num) * pavStream->time_base.num);
}

您寻找 'accurate' 到一个特定的帧...所以您使用向后标志寻找您想要的帧,以确保您获得该帧或前一帧。如果案例是前一个案例,您将解码直到获得实际请求的帧。

我可以看出您遗漏了两个重要步骤:-

  1. 在每个 av_seek_frame(解复用器)之后和下一个 avcodec_send_packet(解码)之前,您需要使用 avcodec_flush_buffers

    刷新解码器
  2. 在每个 avcodec_send_packet(解码)之后,您需要使用 avcodec_receive_frame 接收所有帧(可以不止一个),例如:-

while (avcodec_receive_frame(...) == 0) { process frame here }

解决方案 1(不使用 Seek - 小步骤或未知 fps 并确保准确性)

让我们从步骤 = 1 开始,进行简单的多路分解和我们得到的结果

public void Aleksey()
{
    AVPacket* m_pAVPkt  = av_packet_alloc();
    AVFrame*  m_pAVFrame= av_frame_alloc();
    int ret;
    int step = 1;
    int curFrameNumber = 0;

    while (true)
    {
        ret = av_read_frame(demuxer.FormatContext, m_pAVPkt);
        if (m_pAVPkt->stream_index != 0) { av_packet_unref(m_pAVPkt); continue; }

        ret = avcodec_send_packet(codecCtx, m_pAVPkt);
        av_packet_unref(m_pAVPkt);

        while (true)
        {
            ret = avcodec_receive_frame(codecCtx, m_pAVFrame);
            if (ret != 0) { av_frame_unref(m_pAVFrame); break; }

            curFrameNumber++;

            if (curFrameNumber % step == 0)
                Console.WriteLine($"[pts: {m_pAVFrame->pts}] [time: {Utils.TicksToTime((long)(m_pAVFrame->pts * demuxer.VideoStreams[0].Timebase))}] [displaynumber: {m_pAVFrame->display_picture_number}] [codednumber: {m_pAVFrame->coded_picture_number}]");

            av_frame_unref(m_pAVFrame);
        }
    }
}

输出(我们看到正确的 pts/cur 帧时间,但我们没有得到 display_picture_number/coded_picture_number 的帧号)。似乎您可以为此使用 m_pAVCodecCtx->frame_number 但我敢打赌它会在每个 av_seek_frame 和 avcodec_flush_buffers 上重置.

[pts: 0] [time: 00:00:00:000] [displaynumber: 0] [codednumber: 0] [framenumber: 1]
[pts: 42] [time: 00:00:00:042] [displaynumber: 0] [codednumber: 3] [framenumber: 2]
[pts: 83] [time: 00:00:00:083] [displaynumber: 0] [codednumber: 2] [framenumber: 3]
[pts: 125] [time: 00:00:00:125] [displaynumber: 0] [codednumber: 4] [framenumber: 4]
[pts: 167] [time: 00:00:00:167] [displaynumber: 0] [codednumber: 1] [framenumber: 5]
[pts: 209] [time: 00:00:00:209] [displaynumber: 0] [codednumber: 7] [framenumber: 6]
[pts: 250] [time: 00:00:00:250] [displaynumber: 0] [codednumber: 6] [framenumber: 7]
[pts: 292] [time: 00:00:00:292] [displaynumber: 0] [codednumber: 8] [framenumber: 8]
[pts: 334] [time: 00:00:00:334] [displaynumber: 0] [codednumber: 5] [framenumber: 9]
[pts: 375] [time: 00:00:00:375] [displaynumber: 0] [codednumber: 11] [framenumber: 10]

此代码可确保您精确执行 X 步帧,但它根本不使用对性能不利的搜索。如果你的搜索步骤很小,那没关系,但如果你想使用大步骤,那么我们可以使用包含的搜索。

解决方案 2(使用 Seek - 更好的性能)

AVFormatContext*m_pAVFormatCtx;
AVCodecContext* m_pAVCodecCtx;
AVStream*       pavStream;
AVPacket*       m_pAVPkt;
AVFrame*        m_pAVFrame;
int             m_streamIdx;
long            startTime;
double          avgFrameDuration;
double          m_streamTimebase;

public void Prepare()
{
    // Using your variable names (mapped with mine)
    m_pAVFormatCtx  = demuxer.FormatContext;
    m_pAVCodecCtx   = codecCtx;
    pavStream       = demuxer.VideoStreams[0].AVStream;
    m_streamIdx     = pavStream->index;

    m_streamTimebase= av_q2d(pavStream->time_base) * 1000.0 * 10000.0; // Convert timebase to ticks so we can easily convert stream's timestamps to ticks
    startTime       = pavStream->start_time != AV_NOPTS_VALUE ? (long)(pavStream->start_time * m_streamTimebase) : 0; // We will need this when we seek (adding it to seek timestamp)
    avgFrameDuration= 10000000 / av_q2d(pavStream->avg_frame_rate); // eg. 1 sec / 25 fps = 400.000 ticks (40ms)

    // Prepare packet/frame for demux/decode
    m_pAVPkt        = av_packet_alloc();
    m_pAVFrame      = av_frame_alloc();
}

public void GetFrame(int index) // Zero-based frame index
{
    int ret;
    long frameTimestamp = (long) (index * avgFrameDuration); // Calculation of FrameX timestamp (based on fps/avgFrameDuration)
    Console.WriteLine($"Searching for {Utils.TicksToTime(frameTimestamp)}");

    // Seeking at frameTimestamp or previous I/Key frame and flushing codec
    ret = av_seek_frame(m_pAVFormatCtx, -1, (startTime + frameTimestamp) / 10, AVSEEK_FLAG_FRAME | AVSEEK_FLAG_BACKWARD);
    avcodec_flush_buffers(m_pAVCodecCtx);
    if (ret < 0) return; // handle seek error

    while (true)
    {
        // Demux Packet
        ret = av_read_frame(m_pAVFormatCtx, m_pAVPkt);
        if (ret != 0) break; // handle EOF/error
        if (m_pAVPkt->stream_index != m_streamIdx) { av_packet_unref(m_pAVPkt); continue; } // Exclude other streams

        // Send Packet for decoding
        ret = avcodec_send_packet(codecCtx, m_pAVPkt);
        av_packet_unref(m_pAVPkt);
        if (ret != 0) break; // handle EOF/error

        while (true)
        {
            // Receive all available frames for the decoder
            ret = avcodec_receive_frame(codecCtx, m_pAVFrame);
            if (ret != 0) { av_frame_unref(m_pAVFrame); break; }

            // Get frame pts (prefer best_effort_timestamp)
            long curPts = m_pAVFrame->best_effort_timestamp == AV_NOPTS_VALUE ? m_pAVFrame->pts : m_pAVFrame->best_effort_timestamp;
            if (curPts == AV_NOPTS_VALUE) { { av_frame_unref(m_pAVFrame); continue; } }

            // Skip frames before our actual requested frame
            Console.WriteLine($"[Skip] [pts: {curPts}] [time: {Utils.TicksToTime((long)(curPts * m_streamTimebase))}]");
            if ((long)(curPts * m_streamTimebase) / 10000 < frameTimestamp / 10000) { av_frame_unref(m_pAVFrame); continue; }

            Console.WriteLine($"[Found] [pts: {curPts}] [time: {Utils.TicksToTime((long)(curPts * m_streamTimebase))}]");
            av_frame_unref(m_pAVFrame);
            return;
        }
    }
}

使用以下代码进行测试

Prepare();
GetFrame(100);
GetFrame(200);
GetFrame(300);
GetFrame(100);

给出输出

Searching for 00:00:04:129
[Skip] [pts: 4004] [time: 00:00:04:004]
[Skip] [pts: 4046] [time: 00:00:04:046]
[Skip] [pts: 4087] [time: 00:00:04:087]
[Skip] [pts: 4129] [time: 00:00:04:129]
[Found] [pts: 4129] [time: 00:00:04:129]
Searching for 00:00:08:299
[Skip] [pts: 8008] [time: 00:00:08:008]
[Skip] [pts: 8050] [time: 00:00:08:050]
[Skip] [pts: 8091] [time: 00:00:08:091]
[Skip] [pts: 8133] [time: 00:00:08:133]
[Skip] [pts: 8175] [time: 00:00:08:175]
[Skip] [pts: 8217] [time: 00:00:08:217]
[Skip] [pts: 8258] [time: 00:00:08:258]
[Skip] [pts: 8300] [time: 00:00:08:300]
[Found] [pts: 8300] [time: 00:00:08:300]
Searching for 00:00:12:470
[Skip] [pts: 12012] [time: 00:00:12:012]
[Skip] [pts: 12054] [time: 00:00:12:054]
[Skip] [pts: 12095] [time: 00:00:12:095]
[Skip] [pts: 12137] [time: 00:00:12:137]
[Skip] [pts: 12179] [time: 00:00:12:179]
[Skip] [pts: 12221] [time: 00:00:12:221]
[Skip] [pts: 12262] [time: 00:00:12:262]
[Skip] [pts: 12304] [time: 00:00:12:304]
[Skip] [pts: 12346] [time: 00:00:12:346]
[Skip] [pts: 12387] [time: 00:00:12:387]
[Skip] [pts: 12429] [time: 00:00:12:429]
[Skip] [pts: 12471] [time: 00:00:12:471]
[Found] [pts: 12471] [time: 00:00:12:471]
Searching for 00:00:04:129
[Skip] [pts: 4004] [time: 00:00:04:004]
[Skip] [pts: 4046] [time: 00:00:04:046]
[Skip] [pts: 4087] [time: 00:00:04:087]
[Skip] [pts: 4129] [time: 00:00:04:129]
[Found] [pts: 4129] [time: 00:00:04:129]

注意:对于第二个解决方案,仍有 space 可以更好地处理小步骤(都在同一关键帧内的帧)。您可以通过存储上次搜索的关键帧并将其与当前搜索的关键帧进行比较来验证这一点。如果是这种情况,您可以避免刷新编解码器并使用 ANY 标志重新搜索前一帧的位置以进行精确搜索(以便从原来的位置继续解码器)。不过,新帧的时间戳必须大于前一帧。