Commit 6edbe0d5af for ffmpeg
commit 6edbe0d5af28c31f97a7a9ae4aee94a8b9d102ce
Author: Jun Zhao <barryjzhao@tencent.com>
Date: Thu Nov 12 07:33:32 2026 +0800
avformat/mpegts: avoid ~131 KiB stack frames in PAT/PMT callbacks
5a75d905cb24 added stream_groups[MAX_STREAMS_PER_PROGRAM] to struct
Program, making it 134688 bytes on 64-bit targets. Two callbacks keep a
whole struct Program on the stack: FFSWAP() in pat_cb() creates a
by-value temporary, and pmt_cb() has a local struct Program old_program.
GCC -fstack-usage reports 134800 bytes for pat_cb() and 136368 bytes for
pmt_cb().
The ffmpeg CLI demuxes each input in a thread created by task_start()
with default attributes; musl's default thread stack is 131072 bytes.
Both callbacks overflow it as soon as a PAT or PMT is parsed in that
thread, so ffmpeg dies with SIGSEGV on every MPEG-TS input. This is not
musl specific: any thread with a stack below ~140 KiB is affected.
Swap the two programs member by member in a helper whose largest
temporary is one struct StreamGroup (1040 bytes). A plain assignment is
not equivalent: pat_cb() compacts the active programs while ts->nb_prg
still holds the old value, so all old entries must remain findable.
pmt_cb() only needs the old program's stream list, which
find_matching_stream() reads through nb_streams and streams[]. Save just
the active entries in a new struct ProgramStreams (1028 bytes) before
clear_program(prg) and the PMT loop overwrite prg->streams[] in place.
Measured with -fstack-usage on arm64: pat_cb() 134848 -> 1200 bytes,
pmt_cb() 136400 -> 2752 bytes, both now below configure's
-Wstack-usage=122880 threshold.
Fixes: https://code.ffmpeg.org/FFmpeg/FFmpeg/issues/25937
Signed-off-by: Jun Zhao <barryjzhao@tencent.com>
diff --git a/libavformat/mpegts.c b/libavformat/mpegts.c
index ae4d177083..c200fb4f45 100644
--- a/libavformat/mpegts.c
+++ b/libavformat/mpegts.c
@@ -129,6 +129,7 @@ struct StreamGroup {
AVStream *streams[MAX_STREAMS_PER_PROGRAM];
};
+/* Keep program_swap() in sync when adding fields. */
struct Program {
unsigned int id; // program id/service id
unsigned int nb_pids;
@@ -142,6 +143,13 @@ struct Program {
int pmt_found;
};
+/* Subset of struct Program needed by find_matching_stream(). Keeping this
+ * small avoids a ~131 KiB stack frame in pmt_cb(). */
+struct ProgramStreams {
+ unsigned int nb_streams;
+ struct Stream streams[MAX_STREAMS_PER_PROGRAM];
+};
+
struct MpegTSContext {
const AVClass *class;
/* user data */
@@ -332,6 +340,31 @@ static void clear_program(struct Program *p)
p->pmt_found = 0;
}
+/* Swap two programs without a struct Program-sized stack temporary. */
+static void program_swap(struct Program *a, struct Program *b)
+{
+ int n;
+
+ FFSWAP(unsigned int, a->id, b->id);
+
+ n = FFMAX(a->nb_pids, b->nb_pids);
+ FFSWAP(unsigned int, a->nb_pids, b->nb_pids);
+ for (int i = 0; i < n; i++)
+ FFSWAP(unsigned int, a->pids[i], b->pids[i]);
+
+ n = FFMAX(a->nb_streams, b->nb_streams);
+ FFSWAP(unsigned int, a->nb_streams, b->nb_streams);
+ for (int i = 0; i < n; i++)
+ FFSWAP(struct Stream, a->streams[i], b->streams[i]);
+
+ n = FFMAX(a->nb_stream_groups, b->nb_stream_groups);
+ FFSWAP(unsigned int, a->nb_stream_groups, b->nb_stream_groups);
+ for (int i = 0; i < n; i++)
+ FFSWAP(struct StreamGroup, a->stream_groups[i], b->stream_groups[i]);
+
+ FFSWAP(int, a->pmt_found, b->pmt_found);
+}
+
static void clear_programs(MpegTSContext *ts)
{
av_freep(&ts->prg);
@@ -2545,7 +2578,8 @@ int ff_parse_mpeg2_descriptor(AVFormatContext *fc, AVStream *st, int stream_type
}
static AVStream *find_matching_stream(MpegTSContext *ts, int pid, unsigned int programid,
- int stream_identifier, int pmt_stream_idx, struct Program *p)
+ int stream_identifier, int pmt_stream_idx,
+ const struct ProgramStreams *p)
{
AVFormatContext *s = ts->stream;
AVStream *found = NULL;
@@ -2741,7 +2775,7 @@ static void pmt_cb(MpegTSFilter *filter, const uint8_t *section, int section_len
{
MpegTSContext *ts = filter->u.section_filter.opaque;
MpegTSSectionFilter *tssf = &filter->u.section_filter;
- struct Program old_program;
+ struct ProgramStreams old_program;
SectionHeader h1, *h = &h1;
PESContext *pes;
AVStream *st;
@@ -2777,15 +2811,15 @@ static void pmt_cb(MpegTSFilter *filter, const uint8_t *section, int section_len
return;
prg = get_program(ts, h->id);
- if (prg)
- old_program = *prg;
- else
- clear_program(&old_program);
-
if (ts->skip_unknown_pmt && !prg)
return;
if (prg && prg->nb_pids && prg->pids[0] != ts->current_pid)
return;
+ old_program.nb_streams = prg ? prg->nb_streams : 0;
+ if (old_program.nb_streams)
+ memcpy(old_program.streams, prg->streams,
+ old_program.nb_streams * sizeof(*old_program.streams));
+
if (!ts->skip_clear)
clear_avprogram(ts, h->id);
clear_program(prg);
@@ -3033,7 +3067,7 @@ static void pat_cb(MpegTSFilter *filter, const uint8_t *section, int section_len
clear_program(prg);
add_pid_to_program(prg, pmt_pid);
if (prg_idx > nb_prg)
- FFSWAP(struct Program, ts->prg[nb_prg], ts->prg[prg_idx]);
+ program_swap(&ts->prg[nb_prg], &ts->prg[prg_idx]);
if (prg_idx >= nb_prg)
nb_prg++;
} else