# min-version: 0.40.0 # Backport of upstream mpv commit 09900a0ff016c24409a4e6f95012260ed1107d7e # ("demux/packet_pool: incrementally free old packets on pop"), PR #17889. # Adapted for v0.41.0's structure: upstream's diff assumes a # demux_packet_unref(dp) helper that doesn't exist yet in v0.41.0 (pop() # still does the equivalent work inline) - the GC logic itself # (POOL_HOT_RESERVE/POOL_GC_BATCH/pool_gc_detach, and its call site # relative to the lock) is unchanged from upstream, only the unrelated # pre-existing inline unref code around it is left as-is rather than # refactored to match upstream's later renaming. --- a/demux/packet_pool.c 2026-07-10 16:49:12.550481098 +0000 +++ b/demux/packet_pool.c 2026-07-10 16:49:33.924027863 +0000 @@ -104,6 +104,39 @@ #endif } +// Number of most-recently-pushed packets kept at the head of the freelist for +// fast reuse. Pop walks at most this many cold-cache nodes per call, so we +// keep it small. +#define POOL_HOT_RESERVE 16 + +// Maximum number of packets freed per pop, starting after the hot reserve. +// Acts as a self-balancing GC. After a large push, for example on cache +// reset, the pool will shrink across multiple subsequent pops. This value +// has to be reasonably higher than POOL_HOT_RESERVE to make the clearing +// effective, but not too high to avoid cost of big GC batches on each pop. +#define POOL_GC_BATCH 64 + +// Detach up to POOL_GC_BATCH packets from the pool, starting after the +// first POOL_HOT_RESERVE entries. Must be called locked. +static inline struct demux_packet *pool_gc_detach(struct demux_packet_pool *pool) +{ + struct demux_packet *cat_reserve = pool->packets; + for (int i = 1; cat_reserve && i < POOL_HOT_RESERVE; i++) + cat_reserve = cat_reserve->next; + + if (!cat_reserve || !cat_reserve->next) + return NULL; + + struct demux_packet *gc = cat_reserve->next; + struct demux_packet *cut_rest = gc; + for (int i = 1; cut_rest->next && i < POOL_GC_BATCH; i++) + cut_rest = cut_rest->next; + + cat_reserve->next = cut_rest->next; + cut_rest->next = NULL; + return gc; +} + struct demux_packet *demux_packet_pool_pop(struct demux_packet_pool *pool) { mp_mutex_lock(&pool->lock); @@ -112,6 +145,7 @@ pool->packets = dp->next; dp->next = NULL; } + struct demux_packet *gc = pool_gc_detach(pool); mp_mutex_unlock(&pool->lock); // Clear the packet from possible external references. This is done in the @@ -125,6 +159,16 @@ av_packet_unref(dp->avpacket); ta_free_children(dp); } + // yauiclient/NextPVR: GC is done unlocked, this means packets could be + // iterated again elsewhere in the meantime, but that's fine with + // POOL_GC_BATCH elements only - matches upstream mpv commit 09900a0 + // ("demux/packet_pool: incrementally free old packets on pop"). + // Without this, the pool only ever released packets on shutdown or an + // explicit runtime cache-size reduction, so it grew monotonically + // across a session - every file switch / channel change added packets + // that were never freed again. free_demux_packets() already exists in + // this file (used elsewhere), no new helper needed for it. + free_demux_packets(gc); return dp; }