PR #24634 opened by timblechmann
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24634
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24634.patch

mediacodec_receive_frame()'s feed loop retries forever with no sleep
when the input port is full and the output port keeps returning
EAGAIN. ff_mediacodec_dec_receive()'s dequeue timeout collapses to 0
while output_buffer_count is 0, so a decoder that never produces its
first frame spins the calling thread at 100% CPU indefinitely
(reachable via avcodec_send_packet(), which calls
mediacodec_receive_frame() internally for receive_frame-only
decoders).

Reproducer (hangs before this patch, fails after 5s with it):
    ffmpeg -an -c:v h264_mediacodec -i input.mp4 -c:v rawvideo -f null -

Observed against Android emulator's c2.goldfish.h264.decoder on
ordinary H.264 content that decodes fine on real hardware.

Bound the stall to MEDIACODEC_NO_PROGRESS_TIMEOUT_US (5s), then log
and return AVERROR_EXTERNAL instead of hanging forever.



>From 4ebf1505b198a537c2d7056c9fe26f41796f2372 Mon Sep 17 00:00:00 2001
From: Tim Blechmann <[email protected]>
Date: Tue, 22 Sep 2026 17:37:18 +0800
Subject: [PATCH] avcodec/mediacodecdec: bound the input/output stall busy-loop

mediacodec_receive_frame()'s feed loop retries forever with no sleep
when the input port is full and the output port keeps returning
EAGAIN. ff_mediacodec_dec_receive()'s dequeue timeout collapses to 0
while output_buffer_count is 0, so a decoder that never produces its
first frame spins the calling thread at 100% CPU indefinitely
(reachable via avcodec_send_packet(), which calls
mediacodec_receive_frame() internally for receive_frame-only
decoders).

Reproducer (hangs before this patch, fails after 5s with it):
    ffmpeg -an -c:v h264_mediacodec -i input.mp4 -c:v rawvideo -f null -

Observed against Android emulator's c2.goldfish.h264.decoder on
ordinary H.264 content that decodes fine on real hardware.

Bound the stall to MEDIACODEC_NO_PROGRESS_TIMEOUT_US (5s), then log
and return AVERROR_EXTERNAL instead of hanging forever.
---
 libavcodec/mediacodecdec.c | 27 ++++++++++++++++++++++++++-
 1 file changed, 26 insertions(+), 1 deletion(-)

diff --git a/libavcodec/mediacodecdec.c b/libavcodec/mediacodecdec.c
index bc176d8070..9cf3f732cd 100644
--- a/libavcodec/mediacodecdec.c
+++ b/libavcodec/mediacodecdec.c
@@ -32,6 +32,7 @@
 #include "libavutil/intreadwrite.h"
 #include "libavutil/pixfmt.h"
 #include "libavutil/internal.h"
+#include "libavutil/time.h"
 
 #include "avcodec.h"
 #include "codec_internal.h"
@@ -486,11 +487,21 @@ done:
     return ret;
 }
 
+/* If the input port stays full and the output port keeps saying "try again"
+ * at the same time, neither side can make progress on its own: this is only
+ * expected to last a handful of iterations while the decoder is warming up.
+ * A codec that never recovers from this state (observed with a stalled
+ * software decoder) turns the loop below into an unbounded busy spin with no
+ * sleep between iterations, hanging the calling thread forever. Bound the
+ * total time spent in that specific state instead of looping unconditionally. 
*/
+#define MEDIACODEC_NO_PROGRESS_TIMEOUT_US (5 * 1000000)
+
 static int mediacodec_receive_frame(AVCodecContext *avctx, AVFrame *frame)
 {
     MediaCodecH264DecContext *s = avctx->priv_data;
     int ret;
     ssize_t index;
+    int64_t no_progress_since = 0;
 
     /* In delay_flush mode, wait until the user has released or rendered
        all retained frames. */
@@ -519,11 +530,25 @@ static int mediacodec_receive_frame(AVCodecContext 
*avctx, AVFrame *frame)
                  * return EAGAIN, which violate the design.
                  */
                 if (ff_AMediaCodec_infoTryAgainLater(s->ctx->codec, index) &&
-                    ret == AVERROR(EAGAIN))
+                    ret == AVERROR(EAGAIN)) {
+                    int64_t now = av_gettime_relative();
+                    if (!no_progress_since)
+                        no_progress_since = now;
+                    if (now - no_progress_since > 
MEDIACODEC_NO_PROGRESS_TIMEOUT_US) {
+                        av_log(avctx, AV_LOG_ERROR,
+                               "MediaCodec %p made no progress for over %.1fs: 
"
+                               "input buffer pool stayed full while no output "
+                               "was produced. Treating the decoder as 
stalled.\n",
+                               s->ctx->codec,
+                               MEDIACODEC_NO_PROGRESS_TIMEOUT_US / 1000000.0);
+                        return AVERROR_EXTERNAL;
+                    }
                     continue;
+                }
                 return ret;
             }
             s->ctx->current_input_buffer = index;
+            no_progress_since = 0;
         }
 
         /* try to flush any buffered packet data */
-- 
2.52.0

_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to