From 2755da010d3951a313172aa87bc9ef9dc6b5c960 Mon Sep 17 00:00:00 2001 From: WyattBlue Date: Tue, 10 Mar 2026 00:28:21 -0400 Subject: [PATCH] Fix memory growth when remuxing with add_stream_from_template MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit start_encoding() was calling avcodec_open2() on codec contexts created by add_stream_from_template(), fully initializing the codec (e.g. libx264 allocates x264_t, thread pools, reference frames) even when the stream is only used for remuxing and never calls encode()/decode(). After freeing, the C heap retains this memory, causing RSS to grow with each output segment. Add a _template_initialized flag to CodecContext, set by add_stream_from_template(). In start_encoding(), contexts with this flag skip avcodec_open2() — the codec opens lazily via encode() or decode() if actually needed. Fixes #2135 Co-Authored-By: Claude Sonnet 4.6 --- av/codec/context.pxd | 4 ++++ av/container/output.py | 13 ++++++++----- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/av/codec/context.pxd b/av/codec/context.pxd index 3d87023d7..08d24761e 100644 --- a/av/codec/context.pxd +++ b/av/codec/context.pxd @@ -14,6 +14,10 @@ cdef class CodecContext: # Whether AVCodecContext.extradata should be de-allocated upon destruction. cdef bint extradata_set + # True when created via add_stream_from_template(); start_encoding() skips + # avcodec_open2() and lets encode()/decode() open the codec lazily if needed. + cdef readonly bint _template_initialized + # Used as a signal that this is within a stream, and also for us to access that # stream. This is set "manually" by the stream after constructing this object. cdef int stream_index diff --git a/av/container/output.py b/av/container/output.py index b36cf307f..b5bafc660 100644 --- a/av/container/output.py +++ b/av/container/output.py @@ -193,6 +193,7 @@ def add_stream_from_template( # Construct the user-land stream py_codec_context: CodecContext = wrap_codec_context(ctx, codec, None) + py_codec_context._template_initialized = True py_stream: Stream = wrap_stream(self, stream, py_codec_context) self.streams.add_stream(py_stream) @@ -368,12 +369,14 @@ def start_encoding(self): if not ctx.is_open: for k, v in self.options.items(): ctx.options.setdefault(k, v) - ctx.open() - # Track option consumption. - for k in self.options: - if k not in ctx.options: - used_options.add(k) + if not ctx._template_initialized: + ctx.open() + + # Track option consumption. + for k in self.options: + if k not in ctx.options: + used_options.add(k) stream._finalize_for_output()