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]