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]

Reply via email to