Skip to content

Commit 6c324ce

Browse files
WyattBlueclaude
andcommitted
Fix memory growth when remuxing with add_stream_from_template
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 <noreply@anthropic.com>
1 parent 1c7beae commit 6c324ce

2 files changed

Lines changed: 22 additions & 5 deletions

File tree

av/codec/context.pxd

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ cdef class CodecContext:
1414
# Whether AVCodecContext.extradata should be de-allocated upon destruction.
1515
cdef bint extradata_set
1616

17+
# Set to True when the context was initialized from a stream template via
18+
# add_stream_from_template(). When True, start_encoding() will skip calling
19+
# avcodec_open2() for encoder contexts (encoder opened lazily by encode()).
20+
cdef readonly bint _template_initialized
21+
1722
# Used as a signal that this is within a stream, and also for us to access that
1823
# stream. This is set "manually" by the stream after constructing this object.
1924
cdef int stream_index

av/container/output.py

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,9 @@ def add_stream_from_template(
193193

194194
# Construct the user-land stream
195195
py_codec_context: CodecContext = wrap_codec_context(ctx, codec, None)
196+
# Mark this context as template-initialized so start_encoding() skips
197+
# opening encoder contexts that aren't needed for remux workflows.
198+
py_codec_context._template_initialized = True
196199
py_stream: Stream = wrap_stream(self, stream, py_codec_context)
197200
self.streams.add_stream(py_stream)
198201

@@ -368,12 +371,21 @@ def start_encoding(self):
368371
if not ctx.is_open:
369372
for k, v in self.options.items():
370373
ctx.options.setdefault(k, v)
371-
ctx.open()
372374

373-
# Track option consumption.
374-
for k in self.options:
375-
if k not in ctx.options:
376-
used_options.add(k)
375+
if ctx._template_initialized:
376+
# This context was created from a stream template
377+
# (add_stream_from_template). For pure remux workflows the
378+
# codec is never used, so skip avcodec_open2 here to avoid
379+
# unnecessary memory allocation. The codec will be opened
380+
# lazily by encode() or decode() if actually needed.
381+
pass
382+
else:
383+
ctx.open()
384+
385+
# Track option consumption.
386+
for k in self.options:
387+
if k not in ctx.options:
388+
used_options.add(k)
377389

378390
stream._finalize_for_output()
379391

0 commit comments

Comments
 (0)