FFmpeg — avcodec_receive_frame получает не все кадры и теряет кадры перед отправкой кадров

#ffmpeg

#ffmpeg

Вопрос:

vcodec_receive_frame Функция не получила остальные кадры. Я проверил, что всего в видео было 132 кадра, и он получил только 125 кадров, потеряв 7 кадров в конце видео. Как я могу вернуть потерянные кадры обратно?

Но произошло что-то странное. Как вы можете видеть результат моей MyDecode::receiveFrame() функции. Код внутри блока if (ret != 0){} выполняется первым, но потерянные кадры находятся в конце видео. Так как же они могли появиться первыми? Что вызвало это?

MyDecode.cpp

 AVFrame* MyDecode::receiveFrame()
{
    mux.lock();
    if (!codecCtx) {
        mux.unlock();
        return 0;
    }
    AVFrame* frame = av_frame_alloc();
    int ret = avcodec_receive_frame(codecCtx, frame);
    mux.unlock();
    if (ret != 0)
    {
        static int lost_frames = 1;
        std::cout << "Lost frames: " << lost_frames << std::endl;
        lost_frames  = 1;
        av_frame_free(amp;frame);
        return nullptr;
    }
    std::cout << "Received frames: " << received_frame_num << std::endl;
    received_frame_num  = 1;
    return frame;
}

bool MyDecode::sendPacket(AVPacket* packet)
{
    if (!packet || !packet->data || packet->size == 0)
        return false;
    mux.lock();
    if (!codecCtx) {
        mux.unlock();
        return false;
    }
    int ret = avcodec_send_packet(codecCtx, packet);
    mux.unlock();
    av_packet_free(amp;packet);
    if (ret != 0) {
        return false;
    }
    return true;
}
  

Вывод на консоль

 Total frames: 132
Lost frames: 1
Lost frames: 2
Lost frames: 3
Lost frames: 4
Lost frames: 5
Lost frames: 6
Lost frames: 7
Received frames: 1
Received frames: 2
Received frames: 3
................
Received frames: 125
  

Обновить:

MyDemux.cpp

 AVPacket* MyDemux::readFrame()
{
    mux.lock();
    if (!formatCtx) {
        std::cout << "formaetCtx is null" << std::endl;
        mux.unlock();
        return nullptr;
    }
    AVPacket* packet = av_packet_alloc();
    if (!packet) {
        std::cout << "packet is null" << std::endl;
        mux.unlock();
        return nullptr;
    }

    int ret = av_read_frame(formatCtx, packet);
    if (ret != 0) {
        while (true) {
            av_read_frame(formatCtx, nullptr);
        }
        mux.unlock();
        av_packet_free(amp;packet);
        av_packet_unref(packet);
        return nullptr;
    }
    media_type = packet->stream_index;
    mux.unlock();
    return packet;
}
  

main.cpp

 while (true) {
            AVPacket* pkt = demux.readFrame();
            if (demux.get_media_type() == 0) {
                AVFrame* frame = video_decode.receiveFrame();
                videoWidget->paintFrame(frame);
            }
            else if (demux.get_media_type() == 1) {
            }
            if (!pkt) {
                std::cout << "to break" << std::endl;
                break;
            }
        }
  

Ответ №1:

Вы должны отправить нулевые pkts в декодер, чтобы удалить все ожидающие кадры.

Из avcodec.h

Ситуации с завершением потока. Это требует «промывки» (иначе говоря, слива) кодека, поскольку кодек может буферизировать несколько кадров или пакетов внутри для повышения производительности или по необходимости (рассмотрим B-кадры).
Это обрабатывается следующим образом:
— Вместо допустимых входных данных отправьте NULL функциям avcodec_send_packet() (декодирование) или avcodec_send_frame () (кодирование). Это приведет к переходу в режим слива.
— Вызовите avcodec_receive_frame() (декодирование) или avcodec_receive_packet() (кодирование) в цикле, пока не будет возвращен AVERROR_EOF. Функции не вернут AVERROR (EAGAIN), если только вы не забыли перейти в режим слива. — Прежде чем декодирование можно будет возобновить снова, кодек должен быть сброшен с помощью avcodec_flush_buffers().

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

1. Я пытался. Но у меня нет идеи, как это сделать конкретно, например, где я должен разместить avcodec_send_frame(codecCtx, NULL) .

2. Как только вы получите EOF от входных данных, пропустите эту проверку: if (!packet || !packet->data || packet->size == 0) и отправляйте NULL до тех пор, пока не получите никаких кадров.

3. int ret = avcodec_receive_frame(codecCtx, frame); if (ret == AVERROR_EOF) {} вот так? не работает