1. ホーム
  2. c++

[解決済み] av_seek_frame の後に現在の AVFrame の連番を取得するには?

2022-01-31 21:50:01

質問

私はデコーダーとFFmpegの初心者です。つまり、あるファイルがあり、フレーム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へのポインタを移動します。例えば、ビデオファイルには0、15、30...のように15個ずつキーフレームがあるとします(もちろん、別の番号でもかまいません)。つまり、フレーム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);
}

解決方法は?

特定のフレームに「正確に」シークする...そのため、逆方向のフラグを使用して、そのフレームまたは前のフレームを確実に取得するために、希望するフレームにシークします。前のフレームである場合は、実際に要求されたフレームを取得するまでデコードします。

あなたが見落としている重要なステップが2つあります。

  1. 各 av_seek_frame (demux) の後、次の avcodec_send_packet (decode) の前に、avcodec_flush_buffers を使用してデコーダをフラッシュする必要があります。

  2. 各 avcodec_send_packet (デコード) の後、avcodec_receive_frame で全てのフレーム (複数可) を受信する必要があります 例:-)

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

解決策1 (シークなし - 小ステップまたは未知の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 (シークあり - 性能向上)

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番目の解決策については、小さなステップ(両方とも同じキーフレーム内にあるフレーム)をより良く処理するためのスペースが残っています。最後のシークのキーフレームを保存し、現在のシークのキーフレームと比較することでそれを検証することができます。この場合、コーデックのフラッシュを回避し、ANYフラグを使用して前のフレームの位置で再シークを行い、正確なシークを行います(デコーダを元の位置から続行します)。ただし、新しいフレームのタイムスタンプは、前のフレームより大きくなければなりません。