PR #24164 opened by AYOUB NABIL BOUBAGRAT (ayoubnabil)
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24164
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24164.patch

sparse subtitles could choke another decoder sharing the same demuxer
until its overflow queue filled. the resulting error was treated as eof,
silently truncating the stream.

propagate scheduler errors, ignore sparse subtitles for trailing dts,
and add regression tests. this fixes #24135.


>From 760ae081119ba1f52ae51c6bdcd44ab0ed3310fa Mon Sep 17 00:00:00 2001
From: Ayoub Nabil Boubagrat <[email protected]>
Date: Sat, 15 Aug 2026 17:56:49 +0200
Subject: [PATCH 1/2] fftools/ffmpeg_dec: propagate scheduler receive errors

Signed-off-by: Ayoub Nabil Boubagrat 
<[email protected]>
---
 fftools/ffmpeg_dec.c                |  8 ++++++++
 tests/fate/ffmpeg.mak               | 16 ++++++++++++++++
 tests/filtergraphs/decoder-overflow |  4 ++++
 3 files changed, 28 insertions(+)
 create mode 100644 tests/filtergraphs/decoder-overflow

diff --git a/fftools/ffmpeg_dec.c b/fftools/ffmpeg_dec.c
index 1498afa880..52c41b9137 100644
--- a/fftools/ffmpeg_dec.c
+++ b/fftools/ffmpeg_dec.c
@@ -922,6 +922,14 @@ static int decoder_thread(void *arg)
         int flush_buffers, have_data;
 
         input_status  = sch_dec_receive(dp->sch, dp->sch_idx, dt.pkt);
+        if (input_status < 0 && input_status != AVERROR_EOF) {
+            av_log(dp, AV_LOG_ERROR,
+                   "Error receiving a packet from the scheduler: %s\n",
+                   av_err2str(input_status));
+            ret = input_status;
+            goto finish;
+        }
+
         have_data     = input_status >= 0 &&
             (dt.pkt->buf || dt.pkt->side_data_elems ||
              (intptr_t)dt.pkt->opaque == PKT_OPAQUE_SUB_HEARTBEAT ||
diff --git a/tests/fate/ffmpeg.mak b/tests/fate/ffmpeg.mak
index ad8e5c775f..415465cabb 100644
--- a/tests/fate/ffmpeg.mak
+++ b/tests/fate/ffmpeg.mak
@@ -5,6 +5,22 @@ fate-ffmpeg-filter_complex: CMD = framecrc -filter_complex 
color=d=1:r=5 -fflags
 FATE_FFMPEG-$(call FILTERFRAMECRC, AEVALSRC ASETNSAMPLES ARESAMPLE, 
AC3_FIXED_ENCODER) += fate-ffmpeg-filter_complex_audio
 fate-ffmpeg-filter_complex_audio: CMD = framecrc -auto_conversion_filters 
-filter_complex "aevalsrc=0:d=0.1,asetnsamples=1537" -c ac3_fixed
 
+# decoder queue failures must not be treated as EOF
+FATE_FFMPEG-$(call ALLYES, FFMPEG LAVFI_INDEV TESTSRC2_FILTER SINE_FILTER \
+                           ASETNSAMPLES_FILTER TRIM_FILTER ATRIM_FILTER \
+                           SETPTS_FILTER ASETPTS_FILTER CONCAT_FILTER \
+                           ARESAMPLE_FILTER WRAPPED_AVFRAME_DECODER \
+                           PCM_S16LE_DECODER RAWVIDEO_ENCODER \
+                           PCM_S16LE_ENCODER FRAMECRC_MUXER PIPE_PROTOCOL) \
+                           += fate-ffmpeg-decoder-overflow-error
+fate-ffmpeg-decoder-overflow-error: tests/data/filtergraphs/decoder-overflow
+fate-ffmpeg-decoder-overflow-error: CMD = ! run $(FFMPEG) -nostdin -v error \
+    -f lavfi -i 
"testsrc2=s=16x16:r=1/6:d=6[out0];sine=r=48000:d=6,asetnsamples=n=1[out1]" \
+    -/filter_complex $(TARGET_PATH)/tests/data/filtergraphs/decoder-overflow \
+    -map "[v]" -map "[aout]" -c:v rawvideo -c:a pcm_s16le -f framecrc -
+fate-ffmpeg-decoder-overflow-error: CMP = grep
+fate-ffmpeg-decoder-overflow-error: REF = Error receiving a packet from the 
scheduler
+
 # Ticket 6375, use case of NoX
 FATE_SAMPLES_FFMPEG-$(call FRAMECRC, MOV, PNG ALAC, ARESAMPLE_FILTER) += 
fate-ffmpeg-attached_pics
 fate-ffmpeg-attached_pics: CMD = threads=2 framecrc -i 
$(TARGET_SAMPLES)/lossless-audio/inside.m4a -threads 1 -max_muxing_queue_size 
16 -af aresample
diff --git a/tests/filtergraphs/decoder-overflow 
b/tests/filtergraphs/decoder-overflow
new file mode 100644
index 0000000000..7d131d7f54
--- /dev/null
+++ b/tests/filtergraphs/decoder-overflow
@@ -0,0 +1,4 @@
+[0:v]trim=end=6,setpts=PTS-STARTPTS[v0];
+[0:a]atrim=end=6,asetpts=PTS-STARTPTS[a0];
+[v0][a0]concat=n=1:v=1:a=1[v][a];
+[a]asetnsamples=n=4096:p=0[aout]
-- 
2.52.0


>From 18de95aee6161a5d912d24d9891970e0f3bf5448 Mon Sep 17 00:00:00 2001
From: Ayoub Nabil Boubagrat <[email protected]>
Date: Sat, 15 Aug 2026 17:57:10 +0200
Subject: [PATCH 2/2] fftools/ffmpeg_sched: ignore sparse subtitles for
 trailing dts

Signed-off-by: Ayoub Nabil Boubagrat 
<[email protected]>
---
 fftools/ffmpeg_mux_init.c             |  3 +-
 fftools/ffmpeg_sched.c                | 25 ++++++++-
 fftools/ffmpeg_sched.h                |  3 +-
 tests/fate/ffmpeg.mak                 | 22 ++++++++
 tests/ref/fate/ffmpeg-sparse-subtitle | 81 +++++++++++++++++++++++++++
 tests/sparse-subtitle.srt             |  7 +++
 6 files changed, 136 insertions(+), 5 deletions(-)
 create mode 100644 tests/ref/fate/ffmpeg-sparse-subtitle
 create mode 100644 tests/sparse-subtitle.srt

diff --git a/fftools/ffmpeg_mux_init.c b/fftools/ffmpeg_mux_init.c
index f80b4427b4..d16a4e2689 100644
--- a/fftools/ffmpeg_mux_init.c
+++ b/fftools/ffmpeg_mux_init.c
@@ -1166,7 +1166,8 @@ static int ost_add(Muxer *mux, const OptionsContext *o, 
enum AVMediaType type,
         if (ret < 0)
             return ret;
 
-        ret = sch_add_mux_stream(mux->sch, mux->sch_idx);
+        ret = sch_add_mux_stream(mux->sch, mux->sch_idx,
+                                 type == AVMEDIA_TYPE_SUBTITLE);
         if (ret < 0)
             return ret;
 
diff --git a/fftools/ffmpeg_sched.c b/fftools/ffmpeg_sched.c
index 88b3bf3177..7e11455b18 100644
--- a/fftools/ffmpeg_sched.c
+++ b/fftools/ffmpeg_sched.c
@@ -195,6 +195,9 @@ typedef struct PreMuxQueue {
 typedef struct SchMuxStream {
     SchedulerNode       src;
 
+    // may have large gaps between packets
+    int                 sparse;
+
     unsigned           *sub_heartbeat_dst;
     unsigned         nb_sub_heartbeat_dst;
 
@@ -442,7 +445,9 @@ static void task_init(Scheduler *sch, SchTask *task, enum 
SchedulerNodeType type
 
 static int64_t trailing_dts(const Scheduler *sch)
 {
-    int64_t min_dts = INT64_MAX;
+    int64_t min_dts        = INT64_MAX;
+    int64_t min_sparse_dts = INT64_MAX;
+    int sparse_missing_dts = 0;
 
     for (unsigned i = 0; i < sch->nb_mux; i++) {
         const SchMux *mux = &sch->mux[i];
@@ -452,6 +457,13 @@ static int64_t trailing_dts(const Scheduler *sch)
 
             if (ms->source_finished)
                 continue;
+            if (ms->sparse) {
+                if (ms->last_dts == AV_NOPTS_VALUE)
+                    sparse_missing_dts = 1;
+                else
+                    min_sparse_dts = FFMIN(min_sparse_dts, ms->last_dts);
+                continue;
+            }
             if (ms->last_dts == AV_NOPTS_VALUE)
                 return AV_NOPTS_VALUE;
 
@@ -459,7 +471,12 @@ static int64_t trailing_dts(const Scheduler *sch)
         }
     }
 
-    return min_dts == INT64_MAX ? AV_NOPTS_VALUE : min_dts;
+    if (min_dts != INT64_MAX)
+        return min_dts;
+    if (sparse_missing_dts || min_sparse_dts == INT64_MAX)
+        return AV_NOPTS_VALUE;
+
+    return min_sparse_dts;
 }
 
 static int64_t progressing_dts(const Scheduler *sch, int count_finished)
@@ -691,7 +708,7 @@ int sch_add_mux(Scheduler *sch, SchThreadFunc func, int 
(*init)(void *),
     return idx;
 }
 
-int sch_add_mux_stream(Scheduler *sch, unsigned mux_idx)
+int sch_add_mux_stream(Scheduler *sch, unsigned mux_idx, int sparse)
 {
     SchMux       *mux;
     SchMuxStream *ms;
@@ -708,6 +725,8 @@ int sch_add_mux_stream(Scheduler *sch, unsigned mux_idx)
 
     ms = &mux->streams[stream_idx];
 
+    ms->sparse = sparse;
+
     ms->pre_mux_queue.fifo = av_fifo_alloc2(8, sizeof(AVPacket*), 0);
     if (!ms->pre_mux_queue.fifo)
         return AVERROR(ENOMEM);
diff --git a/fftools/ffmpeg_sched.h b/fftools/ffmpeg_sched.h
index 2cf3034437..2a371f9e71 100644
--- a/fftools/ffmpeg_sched.h
+++ b/fftools/ffmpeg_sched.h
@@ -265,11 +265,12 @@ int sch_add_mux(Scheduler *sch, SchThreadFunc func, int 
(*init)(void *),
  * Add a muxed stream for a previously added muxer.
  *
  * @param mux_idx index previously returned by sch_add_mux()
+ * @param sparse  whether the stream may have large gaps between packets
  *
  * @retval ">=0" Index of the newly-created muxed stream.
  * @retval "<0"  Error code.
  */
-int sch_add_mux_stream(Scheduler *sch, unsigned mux_idx);
+int sch_add_mux_stream(Scheduler *sch, unsigned mux_idx, int sparse);
 
 /**
  * Configure limits on packet buffering performed before the muxer task is
diff --git a/tests/fate/ffmpeg.mak b/tests/fate/ffmpeg.mak
index 415465cabb..f8ece17e9d 100644
--- a/tests/fate/ffmpeg.mak
+++ b/tests/fate/ffmpeg.mak
@@ -21,6 +21,28 @@ fate-ffmpeg-decoder-overflow-error: CMD = ! run $(FFMPEG) 
-nostdin -v error \
 fate-ffmpeg-decoder-overflow-error: CMP = grep
 fate-ffmpeg-decoder-overflow-error: REF = Error receiving a packet from the 
scheduler
 
+# mux the streams first so they share one demuxer, as in ticket #24135.
+# the sparse subtitle then keeps the audio decoder choked long enough for
+# 288000 one-sample packets to overflow its queue on 32-bit and 64-bit builds.
+tests/data/ffmpeg-sparse-subtitle.mkv: TAG = GEN
+tests/data/ffmpeg-sparse-subtitle.mkv: $(SRC_PATH)/tests/sparse-subtitle.srt
+tests/data/ffmpeg-sparse-subtitle.mkv: ffmpeg$(PROGSSUF)$(EXESUF) | tests/data
+       $(M)$(TARGET_EXEC) $(TARGET_PATH)/$< -nostdin -v error \
+           -f lavfi -i "sine=r=48000:d=6,asetnsamples=n=1" \
+           -f srt -i $(SRC_PATH)/tests/sparse-subtitle.srt \
+           -map 0:a -map 1:s -c:a pcm_s16le -c:s copy \
+           -fflags +bitexact -flags +bitexact -y $(TARGET_PATH)/$@
+
+FATE_FFMPEG-$(call ALLYES, FFMPEG LAVFI_INDEV SINE_FILTER ASETNSAMPLES_FILTER \
+                           ARESAMPLE_FILTER SRT_DEMUXER MATROSKA_MUXER \
+                           MATROSKA_DEMUXER PCM_S16LE_ENCODER 
PCM_S16LE_DECODER \
+                           FRAMECRC_MUXER PIPE_PROTOCOL FILE_PROTOCOL) \
+                           += fate-ffmpeg-sparse-subtitle
+fate-ffmpeg-sparse-subtitle: tests/data/ffmpeg-sparse-subtitle.mkv
+fate-ffmpeg-sparse-subtitle: CMD = framecrc -auto_conversion_filters \
+    -i $(TARGET_PATH)/tests/data/ffmpeg-sparse-subtitle.mkv \
+    -map 0:a -map 0:s -af asetnsamples=n=4096:p=0 -c:a pcm_s16le -c:s copy
+
 # Ticket 6375, use case of NoX
 FATE_SAMPLES_FFMPEG-$(call FRAMECRC, MOV, PNG ALAC, ARESAMPLE_FILTER) += 
fate-ffmpeg-attached_pics
 fate-ffmpeg-attached_pics: CMD = threads=2 framecrc -i 
$(TARGET_SAMPLES)/lossless-audio/inside.m4a -threads 1 -max_muxing_queue_size 
16 -af aresample
diff --git a/tests/ref/fate/ffmpeg-sparse-subtitle 
b/tests/ref/fate/ffmpeg-sparse-subtitle
new file mode 100644
index 0000000000..610ee131b7
--- /dev/null
+++ b/tests/ref/fate/ffmpeg-sparse-subtitle
@@ -0,0 +1,81 @@
+#tb 0: 1/48000
+#media_type 0: audio
+#codec_id 0: pcm_s16le
+#sample_rate 0: 48000
+#channel_layout_name 0: mono
+#tb 1: 1/1000
+#media_type 1: subtitle
+#codec_id 1: subrip
+0,          0,          0,     4096,     8192, 0xba4ccfdb
+1,          0,          0,       50,        5, 0x068a022e
+0,       4096,       4096,     4096,     8192, 0x0ea8f228
+0,       8192,       8192,     4096,     8192, 0xf215da72
+0,      12288,      12288,     4096,     8192, 0x2566eba1
+0,      16384,      16384,     4096,     8192, 0x9ac0e286
+0,      20480,      20480,     4096,     8192, 0xff28e21e
+0,      24576,      24576,     4096,     8192, 0x7f18e7a2
+0,      28672,      28672,     4096,     8192, 0xe2a9dd38
+0,      32768,      32768,     4096,     8192, 0x9466f14b
+0,      36864,      36864,     4096,     8192, 0x12edd449
+0,      40960,      40960,     4096,     8192, 0xb176f78e
+0,      45056,      45056,     4096,     8192, 0x49a7d481
+0,      49152,      49152,     4096,     8192, 0xfec9f060
+0,      53248,      53248,     4096,     8192, 0xc0f0da13
+0,      57344,      57344,     4096,     8192, 0x4455eb74
+0,      61440,      61440,     4096,     8192, 0xf0b5e1a8
+0,      65536,      65536,     4096,     8192, 0x0e4be63c
+0,      69632,      69632,     4096,     8192, 0xbc06e533
+0,      73728,      73728,     4096,     8192, 0x7cc6dc3c
+0,      77824,      77824,     4096,     8192, 0xe822f1db
+0,      81920,      81920,     4096,     8192, 0x8635d3d6
+0,      86016,      86016,     4096,     8192, 0x6572f839
+0,      90112,      90112,     4096,     8192, 0x27fad063
+0,      94208,      94208,     4096,     8192, 0x0196f2fc
+0,      98304,      98304,     4096,     8192, 0xa752dcff
+0,     102400,     102400,     4096,     8192, 0xfba6e81c
+0,     106496,     106496,     4096,     8192, 0x76e7e2f9
+0,     110592,     110592,     4096,     8192, 0x4034e306
+0,     114688,     114688,     4096,     8192, 0x42ccec39
+0,     118784,     118784,     4096,     8192, 0x74f4d8d3
+0,     122880,     122880,     4096,     8192, 0x4638f297
+0,     126976,     126976,     4096,     8192, 0x9cfed0b0
+0,     131072,     131072,     4096,     8192, 0x475cf7d1
+0,     135168,     135168,     4096,     8192, 0x5a9fd681
+0,     139264,     139264,     4096,     8192, 0x2cf5eddc
+0,     143360,     143360,     4096,     8192, 0x345bde69
+0,     147456,     147456,     4096,     8192, 0xebcce64f
+0,     151552,     151552,     4096,     8192, 0xd422e4e1
+0,     155648,     155648,     4096,     8192, 0x5fb4e30f
+0,     159744,     159744,     4096,     8192, 0x8ea5e90d
+0,     163840,     163840,     4096,     8192, 0xabd3dcb8
+0,     167936,     167936,     4096,     8192, 0x936ff1ba
+0,     172032,     172032,     4096,     8192, 0x3a02d0bc
+0,     176128,     176128,     4096,     8192, 0xe5bdfa3a
+0,     180224,     180224,     4096,     8192, 0x98e9d318
+0,     184320,     184320,     4096,     8192, 0xea12f0c1
+0,     188416,     188416,     4096,     8192, 0xffa8dd99
+0,     192512,     192512,     4096,     8192, 0x0dd6e4b2
+0,     196608,     196608,     4096,     8192, 0x7cd6e5b0
+0,     200704,     200704,     4096,     8192, 0x93f2e223
+0,     204800,     204800,     4096,     8192, 0xc901ebdf
+0,     208896,     208896,     4096,     8192, 0xf318d7b6
+0,     212992,     212992,     4096,     8192, 0xaf20f65e
+0,     217088,     217088,     4096,     8192, 0x418ecffe
+0,     221184,     221184,     4096,     8192, 0x8b54f77a
+0,     225280,     225280,     4096,     8192, 0x811bd61f
+0,     229376,     229376,     4096,     8192, 0x629aec8a
+0,     233472,     233472,     4096,     8192, 0xc50de3cf
+0,     237568,     237568,     4096,     8192, 0x506be304
+0,     241664,     241664,     4096,     8192, 0x799ee4b0
+0,     245760,     245760,     4096,     8192, 0x09aae1e0
+0,     249856,     249856,     4096,     8192, 0x36e9ec10
+0,     253952,     253952,     4096,     8192, 0x8166d890
+0,     258048,     258048,     4096,     8192, 0x6674f4aa
+0,     262144,     262144,     4096,     8192, 0x5223d05b
+0,     266240,     266240,     4096,     8192, 0xfc57f580
+0,     270336,     270336,     4096,     8192, 0x72d6d7dd
+0,     274432,     274432,     4096,     8192, 0xabaaecdf
+0,     278528,     278528,     4096,     8192, 0x3c1ddf5f
+0,     282624,     282624,     4096,     8192, 0x16b8e7ed
+1,       5900,       5900,       50,        3, 0x026f0137
+0,     286720,     286720,     1280,     2560, 0xf55202f4
diff --git a/tests/sparse-subtitle.srt b/tests/sparse-subtitle.srt
new file mode 100644
index 0000000000..af9eb112cd
--- /dev/null
+++ b/tests/sparse-subtitle.srt
@@ -0,0 +1,7 @@
+1
+00:00:00,000 --> 00:00:00,050
+start
+
+2
+00:00:05,900 --> 00:00:05,950
+end
-- 
2.52.0

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

Reply via email to