Как получить сиквенциальный номер currecnt AVFrame после av_seek_frame?

#c #ffmpeg

Вопрос:

Я новичок в декодере и 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(amp;m_pAVFormatCtx, "", nullptr, nullptr) != 0)
    {
        printf("FFmpegDecoder::InitFFmpeg: error in avformat_open_inputn");
        return false;
    }

    if (avformat_find_stream_info(m_pAVFormatCtx, nullptr) < 0)
    {
        printf("FFmpegDecoder::InitFFmpeg: error in avformat_find_stream_infon");
        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 streamn");
        return false;
    }

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

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

    if (avcodec_parameters_to_context(m_pAVCodecCtx, m_pAVStream->codecpar) < 0)
    {
        printf("FFmpegDecoder::InitFFmpeg: error in avcodec_parameters_to_contextn");
        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(amp;m_pBufferRefForHw, hwDevType, NULL, NULL, 0) < 0)
        {
            printf("FFmpegDecoder::InitFFmpeg: error in av_hwdevice_ctx_createn");
            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_open2n");
        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_buffern");
        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_getContextn");
        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("----- BATCHn");

                    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("EXITn");
            break;
        }
    }
}
 

UPDATE

Init logic

 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(amp;m_pAVFormatCtx, "", nullptr, nullptr) != 0)
    {
        printf("FFmpegDecoder::InitFFmpeg: error in avformat_open_inputn");
        return false;
    }

    if (avformat_find_stream_info(m_pAVFormatCtx, nullptr) < 0)
    {
        printf("FFmpegDecoder::InitFFmpeg: error in avformat_find_stream_infon");
        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 streamn");
        return false;
    }

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

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

    if (avcodec_parameters_to_context(m_pAVCodecCtx, m_pAVStream->codecpar) < 0)
    {
        printf("FFmpegDecoder::InitFFmpeg: error in avcodec_parameters_to_contextn");
        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(amp;m_pBufferRefForHw, hwDevType, NULL, NULL, 0) < 0)
        {
            printf("FFmpegDecoder::InitFFmpeg: error in av_hwdevice_ctx_createn");
            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_open2n");
        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_buffern");
        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_getContextn");
        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("EXIT1n");
                break;
            }
        }

        if (number_of_errors == MAX_ERROR_NUM)
        {
            printf("EXIT2n");
            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);
}
 

Ответ №1:

Вы стремитесь «точно» соответствовать определенному фрейму… таким образом, вы ищете нужный кадр с обратным флагом, чтобы убедиться, что вы получите кадр или предыдущий. Если случай является предыдущим, вы декодируете его до тех пор, пока не получите фактический запрошенный кадр.

Есть два важных шага, которые, как я вижу, вы упускаете:-

  1. После каждого av_seek_frame (demux) и перед следующим avcodec_send_packet (декодированием) вам необходимо очистить декодер с помощью avcodec_flush_buffers
  2. После каждого пакета avcodec_send_packet (декодирования) вам необходимо получать все кадры (может быть более одного) с помощью avcodec_receive_frame, например:-
 while (avcodec_receive_frame(...) == 0) { process frame here }
 

Решение 1 (Без поиска — Небольшие шаги или для неизвестных кадров в секунду и обеспечивает точность)

Давайте начнем с шага = 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 (С Улучшенной производительностью)

 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]
 

Примечание: Для 2-го решения все еще есть место для лучшей обработки небольших шагов (кадры, которые находятся в одних и тех же ключевых кадрах). Вы можете проверить это, сохранив ключевой кадр последнего поиска и сравнив его с ключевым кадром текущего поиска. Если это так, вы могли бы избежать промывки кодека и повторного поиска в позиции предыдущего кадра с ЛЮБЫМ флагом, чтобы быть точным поиском (чтобы продолжить декодер с того места, где он был). Однако временная метка нового кадра должна быть больше, чем у предыдущего.

Комментарии:

1. Не могу увидеть весь ваш код, чтобы помочь вам больше. Вы включаете только один поток (видеопоток), чтобы убедиться, что вы не получаете пакеты из других потоков (например, аудио) ? Проверьте также мой код демуксера здесь

2. Вам необходимо убедиться, что вы также выделяете/освобождаете или не загружаете свои пакеты/кадры

3. Нет, вам все равно это нужно, чтобы правильно подать декодер, чтобы он мог декодировать следующий кадр. Я постараюсь создать некоторый код на c# для вашего вопроса. Правильно ли работает FrameToPts, можете ли вы также опубликовать его?

4. Хорошо, у нас здесь есть некоторые проблемы. Вы не можете доверять номеру display_picture_number, он не будет установлен всегда. Вы не можете доверять, что частота кадров в секунду может быть динамичной, возможно, в качестве обходного пути вы использовали бы среднюю частоту кадров. И еще один важный вопрос … Вы хотите, чтобы шаг был статическим числом и небольшим?

5. @AlekseyTimoshchenko после нашего подробного обсуждения я добавил 2-е решение для повышения производительности. Я также буду использовать его для своей библиотеки форзацев для быстрого шага вперед/назад 😉