From 80430058f5eefb55147218a85225adeb6b616f4d Mon Sep 17 00:00:00 2001 From: Carl Hetherington Date: Tue, 15 Feb 2022 22:47:48 +0100 Subject: [PATCH] Try to handle EAGAIN from avcodec_send_packet() properly. The docs say on EAGAIN we should call avcodec_receive_frame() and then re-send the same packet again. This should do that. This is a fix for errors trigged by the accompanying test. --- src/lib/ffmpeg_decoder.cc | 38 +++++++++++++++++++++--------------- src/lib/ffmpeg_examiner.cc | 33 +++++++++++++++++-------------- test/ffmpeg_examiner_test.cc | 7 +++++++ 3 files changed, 47 insertions(+), 31 deletions(-) diff --git a/src/lib/ffmpeg_decoder.cc b/src/lib/ffmpeg_decoder.cc index b2a1bbbe0..d49878217 100644 --- a/src/lib/ffmpeg_decoder.cc +++ b/src/lib/ffmpeg_decoder.cc @@ -533,24 +533,30 @@ FFmpegDecoder::decode_and_process_video_packet (AVPacket* packet) auto context = video_codec_context(); - int r = avcodec_send_packet (context, packet); - if (r < 0) { - LOG_WARNING("avcodec_send_packet returned %1 for a video packet", r); - } - - while (true) { - r = avcodec_receive_frame (context, _video_frame); - if (r == AVERROR(EAGAIN) || r == AVERROR_EOF || (r < 0 && !packet)) { - /* More input is required, no more frames are coming, or we are flushing and there was - * some error which we just want to ignore. - */ - return false; - } else if (r < 0) { - throw DecodeError (N_("avcodec_receive_frame"), N_("FFmpeg::decode_and_process_video_packet"), r); + bool pending = false; + do { + int r = avcodec_send_packet (context, packet); + if (r < 0) { + LOG_WARNING("avcodec_send_packet returned %1 for a video packet", r); } - process_video_frame (); - } + /* EAGAIN means we should call avcodec_receive_frame and then re-send the same packet */ + pending = r == AVERROR(EAGAIN); + + while (true) { + r = avcodec_receive_frame (context, _video_frame); + if (r == AVERROR(EAGAIN) || r == AVERROR_EOF || (r < 0 && !packet)) { + /* More input is required, no more frames are coming, or we are flushing and there was + * some error which we just want to ignore. + */ + return false; + } else if (r < 0) { + throw DecodeError (N_("avcodec_receive_frame"), N_("FFmpeg::decode_and_process_video_packet"), r); + } + + process_video_frame (); + } + } while (pending); return true; } diff --git a/src/lib/ffmpeg_examiner.cc b/src/lib/ffmpeg_examiner.cc index 3c6d185f4..fab999006 100644 --- a/src/lib/ffmpeg_examiner.cc +++ b/src/lib/ffmpeg_examiner.cc @@ -221,22 +221,25 @@ FFmpegExaminer::video_packet (AVCodecContext* context, string& temporal_referenc return false; } - int r = avcodec_send_packet (context, packet); - if (r < 0 && !(r == AVERROR_EOF && !packet)) { - /* We could cope with AVERROR(EAGAIN) and re-send the packet but I think it should never happen. - * AVERROR_EOF can happen during flush if we've already sent a flush packet. - */ - throw DecodeError (N_("avcodec_send_packet"), N_("FFmpegExaminer::video_packet"), r); - } + bool pending = false; + do { + int r = avcodec_send_packet (context, packet); + if (r < 0) { + LOG_WARNING("avcodec_send_packet returned %1 for a video packet", r); + } - r = avcodec_receive_frame (context, _video_frame); - if (r == AVERROR(EAGAIN)) { - /* More input is required */ - return true; - } else if (r == AVERROR_EOF) { - /* No more output is coming */ - return false; - } + /* EAGAIN means we should call avcodec_receive_frame and then re-send the same packet */ + pending = r == AVERROR(EAGAIN); + + r = avcodec_receive_frame (context, _video_frame); + if (r == AVERROR(EAGAIN)) { + /* More input is required */ + return true; + } else if (r == AVERROR_EOF) { + /* No more output is coming */ + return false; + } + } while (pending); if (!_first_video) { _first_video = frame_time (_video_frame, _format_context->streams[_video_stream.get()]); diff --git a/test/ffmpeg_examiner_test.cc b/test/ffmpeg_examiner_test.cc index c460830fb..2c244f541 100644 --- a/test/ffmpeg_examiner_test.cc +++ b/test/ffmpeg_examiner_test.cc @@ -76,3 +76,10 @@ BOOST_AUTO_TEST_CASE (ffmpeg_examiner_vob_test) auto examiner = make_shared(content); } + +/** Check that another file can be examined without error */ +BOOST_AUTO_TEST_CASE (ffmpeg_examiner_mkv_test) +{ + auto content = make_shared(TestPaths::private_data() / "sample.mkv"); + auto examiner = make_shared(content); +} -- 2.30.2