This is an automated email from the git hooks/post-receive script. Git pushed a commit to branch master in repository ffmpeg.
commit dd941af8ac9c97017b335832714457fe9dd7f6be Author: Michael Niedermayer <[email protected]> AuthorDate: Wed Jul 8 16:14:48 2026 +0200 Commit: michaelni <[email protected]> CommitDate: Sat Jul 11 21:53:00 2026 +0000 avcodec/bsf/dts2pts: evict unconsumable nodes from the poc tree Each pending packet consumes up to poc_diff tree nodes when it is output, so the packets in the FIFO can consume at most nb_pending nodes in total. Frames whose tree lookup misses on output leave their nodes behind, and damaged or crafted streams can make that happen indefinitely, growing the tree without limit. Track the node count and insertion order, keep the leftovers of up to MAX_DAMAGED_FRAMES frames and beyond that evict the nodes unconsumed the longest. Timestamps of valid frames are unaffected unless more frames than that are damaged. No eviction is done at EOF, where nodes are deliberately kept to regenerate timestamps from. Of all h264/hevc conformance samples only MR3_TANDBERG_B.264 triggers evictions, and no sample changes output. Fixes: unbounded memory growth with damaged streams Co-Authored-By: Fable-5 --- libavcodec/bsf/dts2pts.c | 51 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 48 insertions(+), 3 deletions(-) diff --git a/libavcodec/bsf/dts2pts.c b/libavcodec/bsf/dts2pts.c index b0b6b0ec23..03e7c39983 100644 --- a/libavcodec/bsf/dts2pts.c +++ b/libavcodec/bsf/dts2pts.c @@ -41,11 +41,17 @@ #include "libavcodec/h264_ps.h" #include "libavcodec/hevc/ps.h" +// Damaged frames leave their up to 2 timestamp nodes behind unconsumed. +// This many damaged frames are tolerated before the oldest leftovers are +// evicted; no timestamp of a valid frame is lost below this. +#define MAX_DAMAGED_FRAMES 32 + typedef struct DTS2PTSNode { int64_t dts; int64_t duration; int poc; int gop; + int64_t serial; // insertion order, evicting the stalest node first struct DTS2PTSNode *next; // valid only during same-gop re-keying } DTS2PTSNode; @@ -90,6 +96,9 @@ typedef struct DTS2PTSContext { DTS2PTSHEVCContext hevc; } u; + int nb_nodes; + int nb_pending; + int64_t serial; int nb_frame; int gop; int eof; @@ -129,11 +138,21 @@ static int free_node(void *opaque, void *elem) return 0; } +static int find_stalest(void *opaque, void *elem) +{ + DTS2PTSNode **stalest = opaque; + DTS2PTSNode *node = elem; + if (!*stalest || node->serial < (*stalest)->serial) + *stalest = node; + return 0; +} + // Shared functions static int alloc_and_insert_node(AVBSFContext *ctx, int64_t ts, int64_t duration, int poc, int poc_diff, int gop) { DTS2PTSContext *s = ctx->priv_data; + for (int i = 0; i < poc_diff; i++) { struct AVTreeNode *node = av_tree_node_alloc(); DTS2PTSNode *poc_node, *ret; @@ -146,13 +165,14 @@ static int alloc_and_insert_node(AVBSFContext *ctx, int64_t ts, int64_t duration } if (i && ts != AV_NOPTS_VALUE) ts += duration / poc_diff; - *poc_node = (DTS2PTSNode) { ts, duration, poc++, gop }; + *poc_node = (DTS2PTSNode) { ts, duration, poc++, gop, s->serial++ }; ret = av_tree_insert(&s->root, poc_node, cmp_insert, &node); if (ret && ret != poc_node) { *ret = *poc_node; av_refstruct_unref(&poc_node); av_free(node); - } + } else + s->nb_nodes++; } return 0; } @@ -230,6 +250,7 @@ static int h264_queue_frame(AVBSFContext *ctx, AVPacket *pkt, int poc, int *queu frame = (DTS2PTSFrame) { pkt, poc, poc_diff, s->gop }; ret = av_fifo_write(s->fifo, &frame, 1); av_assert2(ret >= 0); + s->nb_pending += poc_diff; *queued = 1; return 0; @@ -477,6 +498,7 @@ static int hevc_queue_frame(AVBSFContext *ctx, AVPacket *pkt, int poc, bool *que *r = *node; av_refstruct_unref(&node); av_free(tnode); + s->nb_nodes--; } } } @@ -498,6 +520,7 @@ static int hevc_queue_frame(AVBSFContext *ctx, AVPacket *pkt, int poc, bool *que ret = av_fifo_write(s->fifo, &frame, 1); if (ret < 0) return ret; + s->nb_pending += frame.poc_diff; *queued = true; @@ -665,6 +688,7 @@ static int dts2pts_filter(AVBSFContext *ctx, AVPacket *out) // Fetch a packet from the FIFO ret = av_fifo_read(s->fifo, &frame, 1); av_assert2(ret >= 0); + s->nb_pending -= frame.poc_diff; av_packet_move_ref(out, frame.pkt); av_packet_free(&frame.pkt); @@ -691,6 +715,7 @@ static int dts2pts_filter(AVBSFContext *ctx, AVPacket *out) av_tree_insert(&s->root, poc_node, cmp_insert, &node); av_refstruct_unref(&poc_node); av_free(node); + s->nb_nodes--; poc_node = av_tree_find(s->root, &dup, cmp_find, NULL); } } @@ -715,6 +740,24 @@ static int dts2pts_filter(AVBSFContext *ctx, AVPacket *out) av_log(ctx, AV_LOG_WARNING, "No timestamp for POC %d in tree\n", frame.poc); } else av_log(ctx, AV_LOG_WARNING, "No timestamp for POC %d in tree\n", frame.poc); + + // The pending packets consume nb_pending nodes; frames whose lookup above + // missed leave nodes behind which nothing consumes anymore. Keep the + // leftovers of up to MAX_DAMAGED_FRAMES frames, then evict the nodes + // unconsumed the longest. + // At EOF nodes are deliberately kept to regenerate timestamps from. + while (!s->eof && s->nb_nodes > s->nb_pending + 2 * MAX_DAMAGED_FRAMES) { + DTS2PTSNode *stale = NULL; + struct AVTreeNode *tnode = NULL; + av_tree_enumerate(s->root, &stale, NULL, find_stalest); + av_log(ctx, AV_LOG_WARNING, "Evicting unconsumed POC %d, GOP %d\n", + stale->poc, stale->gop); + av_tree_insert(&s->root, stale, cmp_insert, &tnode); + av_refstruct_unref(&stale); + av_free(tnode); + s->nb_nodes--; + } + av_log(ctx, AV_LOG_DEBUG, "Returning frame for POC %d, GOP %d, dts %"PRId64", pts %"PRId64"\n", frame.poc, frame.gop, out->dts, out->pts); @@ -736,7 +779,9 @@ static void dts2pts_flush(AVBSFContext *ctx) av_tree_enumerate(s->root, NULL, NULL, free_node); av_tree_destroy(s->root); - s->root = NULL; + s->root = NULL; + s->nb_nodes = 0; + s->nb_pending = 0; ff_cbs_fragment_reset(&s->au); if (s->cbc) _______________________________________________ ffmpeg-cvslog mailing list -- [email protected] To unsubscribe send an email to [email protected]
