Try to handle EAGAIN from avcodec_send_packet() properly.
authorCarl Hetherington <cth@carlh.net>
Tue, 15 Feb 2022 21:47:48 +0000 (22:47 +0100)
committerCarl Hetherington <cth@carlh.net>
Thu, 17 Feb 2022 08:59:26 +0000 (09:59 +0100)
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
src/lib/ffmpeg_examiner.cc
test/ffmpeg_examiner_test.cc

index b2a1bbbe0eba06b00a3be8d1721f83dab6bfdab9..d49878217338fb40e29eab6892b62d44c788ef9d 100644 (file)
@@ -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;
 }
index 3c6d185f43f0991d3716ad31e12dfb7b5f9dfd20..fab9990060041602191380d47291d8a24b87cba7 100644 (file)
@@ -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()]);
index c460830fb09570a31048c808c71c4d7304f1e94e..2c244f54124b1c1e52219edd1e47587b24849f8d 100644 (file)
@@ -76,3 +76,10 @@ BOOST_AUTO_TEST_CASE (ffmpeg_examiner_vob_test)
        auto examiner = make_shared<FFmpegExaminer>(content);
 }
 
+
+/** Check that another file can be examined without error */
+BOOST_AUTO_TEST_CASE (ffmpeg_examiner_mkv_test)
+{
+       auto content = make_shared<FFmpegContent>(TestPaths::private_data() / "sample.mkv");
+       auto examiner = make_shared<FFmpegExaminer>(content);
+}