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

Мне нужно сделать такую логику которая будет читать фреймы с каким то шагом (допустим 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 то есть если keyframes в файле каждые 15 фреймов, то в случае когда я делаю av_seek_frame на 20й фрейм то как результат получаю фрейм 15.

Я вижу, что AVFrame есть coded_picture_number который может быть полезным в моем случае я попробовал добавить эти значения в вектор но вижу, что эти значения не имеют смысла

enter image description here

Я ожидал увидеть 0, 15, 30, 45...

Если бы coded_picture_number возвращало бы верное значение (допустим 15), то можно было бы предположить, что это IFrame индекс и пропустить еще 4 фрейма и вот пожалуйста 20й фрейм бери пользуйся)

В общем есть ощущение, что я упускаю тут, что то важное. Кто работал с таким подскажите куда смотреть?

ПРАВКА

Init логика

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;
        }
    }
}

Ответы (1 шт):

Автор решения: Aleksey Timoshchenko

В итоге вот так получилось решить

void GetFrame(int index) // Zero-based frame index
{
    int ret;
    double avgFrameDuration = 10000000 / av_q2d(m_pAVFormatCtx->streams[m_streamIdx]->avg_frame_rate);
    double m_streamTimebase = av_q2d(m_pAVStream->time_base) * 1000.0 * 10000.0; // Convert timebase to ticks so we can easily convert stream's timestamps to ticks;
    long startTime = m_pAVStream->start_time != AV_NOPTS_VALUE ? (long)(m_pAVStream->start_time * m_streamTimebase) : 0; // We will need this when we seek (adding it to seek timestamp);
    long frameTimestamp = (long)(index * avgFrameDuration); // Calculation of FrameX timestamp (based on fps/avgFrameDuration)
    printf("Searching for : %ld\n", 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

                                                                                            
        ret = avcodec_send_packet(m_pAVCodecCtx, m_pAVPkt); // Send Packet for decoding
        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(m_pAVCodecCtx, m_pAVFrame);
            if (ret != 0) { av_frame_unref(m_pAVFrame); break; }

            // Get frame pts (prefer best_effort_timestamp)
            long curPts = (long)(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
            printf("Skip pts: %ld\n", curPts);
            if ((long)(curPts * m_streamTimebase) / 10000 < frameTimestamp / 10000) { av_frame_unref(m_pAVFrame); continue; }

            //needed frame here to process
            printf("Found pts: %ld\n", curPts);
            av_frame_unref(m_pAVFrame);
            return;
        }
    }
}
→ Ссылка