Skip to content

Commit d9d12c5

Browse files
committed
Speed up Graph push/pull per-frame paths
Cache a one-byte filter kind on FilterContext (source / video sink / audio sink / other) at wrap time, so push/pull no longer reconvert filter.name (a C string) to a Python str and do tuple-membership tests on every frame. Cache the buffer/abuffer source contexts on Graph so push/vpush index a list attribute directly instead of looking them up in _context_by_type each call. Pure internal change, no API or behavior difference. On a trivial buffer->buffersink graph the push+pull round trip drops from ~2.0 to ~1.25 us/frame.
1 parent 0fbedb2 commit d9d12c5

4 files changed

Lines changed: 33 additions & 13 deletions

File tree

av/filter/context.pxd

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,13 @@ from av.filter.graph cimport Graph
55

66

77
cdef class FilterContext:
8-
98
cdef lib.AVFilterContext *ptr
109
cdef readonly object _graph
1110
cdef readonly Filter filter
12-
1311
cdef object _inputs
1412
cdef object _outputs
15-
1613
cdef bint inited
14+
cdef unsigned char _kind
1715

1816

1917
cdef FilterContext wrap_filter_context(Graph graph, Filter filter, lib.AVFilterContext *ptr)

av/filter/context.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@
1212

1313
_cinit_sentinel = cython.declare(object, object())
1414

15+
_KIND_OTHER = cython.declare(cython.uchar, 0)
16+
_KIND_SOURCE = cython.declare(cython.uchar, 1) # buffer / abuffer
17+
_KIND_VIDEO_SINK = cython.declare(cython.uchar, 2) # buffersink
18+
_KIND_AUDIO_SINK = cython.declare(cython.uchar, 3) # abuffersink
19+
1520

1621
@cython.cfunc
1722
def wrap_filter_context(
@@ -21,6 +26,16 @@ def wrap_filter_context(
2126
self._graph = weakref.ref(graph)
2227
self.filter = filter
2328
self.ptr = ptr
29+
30+
name: str = filter.name
31+
if name == "buffer" or name == "abuffer":
32+
self._kind = _KIND_SOURCE
33+
elif name == "buffersink":
34+
self._kind = _KIND_VIDEO_SINK
35+
elif name == "abuffersink":
36+
self._kind = _KIND_AUDIO_SINK
37+
else:
38+
self._kind = _KIND_OTHER
2439
return self
2540

2641

@@ -108,7 +123,7 @@ def push(self, frame: Frame | None):
108123
res = lib.av_buffersrc_write_frame(self.ptr, cython.NULL)
109124
err_check(res)
110125
return
111-
elif self.filter.name in ("abuffer", "buffer"):
126+
elif self._kind == _KIND_SOURCE:
112127
with cython.nogil:
113128
res = lib.av_buffersrc_write_frame(self.ptr, frame.ptr)
114129
err_check(res)
@@ -126,9 +141,9 @@ def push(self, frame: Frame | None):
126141
def pull(self):
127142
frame: Frame
128143
res: cython.int
129-
if self.filter.name == "buffersink":
144+
if self._kind == _KIND_VIDEO_SINK:
130145
frame = alloc_video_frame()
131-
elif self.filter.name == "abuffersink":
146+
elif self._kind == _KIND_AUDIO_SINK:
132147
frame = alloc_audio_frame()
133148
else:
134149
# Delegate to the output.

av/filter/graph.pxd

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,5 @@ cdef class Graph:
2020
cdef int _nb_filters_seen
2121
cdef dict[long, FilterContext] _context_by_ptr
2222
cdef dict[str, list[FilterContext]] _context_by_type
23+
cdef list[FilterContext] _video_sources
24+
cdef list[FilterContext] _audio_sources

av/filter/graph.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ def __cinit__(self):
2222
self._nb_filters_seen = 0
2323
self._context_by_ptr = {}
2424
self._context_by_type = {}
25+
self._video_sources = []
26+
self._audio_sources = []
2527

2628
def __dealloc__(self):
2729
if self.ptr:
@@ -108,8 +110,13 @@ def add(self, filter, args=None, **kwargs):
108110

109111
@cython.cfunc
110112
def _register_context(self, ctx: FilterContext):
113+
name: str = ctx.filter.ptr.name
111114
self._context_by_ptr[cython.cast(cython.long, ctx.ptr)] = ctx
112-
self._context_by_type.setdefault(ctx.filter.ptr.name, []).append(ctx)
115+
self._context_by_type.setdefault(name, []).append(ctx)
116+
if name == "buffer":
117+
self._video_sources.append(ctx)
118+
elif name == "abuffer":
119+
self._audio_sources.append(ctx)
113120

114121
@cython.cfunc
115122
def _auto_register(self):
@@ -234,13 +241,11 @@ def set_audio_frame_size(self, frame_size):
234241

235242
def push(self, frame, at: cython.int = -1):
236243
if frame is None:
237-
contexts = self._get_context_by_type("buffer") + self._get_context_by_type(
238-
"abuffer"
239-
)
244+
contexts = self._video_sources + self._audio_sources
240245
elif isinstance(frame, VideoFrame):
241-
contexts = self._get_context_by_type("buffer")
246+
contexts = self._video_sources
242247
elif isinstance(frame, AudioFrame):
243-
contexts = self._get_context_by_type("abuffer")
248+
contexts = self._audio_sources
244249
else:
245250
raise ValueError(
246251
f"can only AudioFrame, VideoFrame or None; got {type(frame)}"
@@ -259,7 +264,7 @@ def push(self, frame, at: cython.int = -1):
259264

260265
def vpush(self, frame: VideoFrame | None, at: cython.int = -1):
261266
"""Like `push`, but only for VideoFrames."""
262-
contexts = self._get_context_by_type("buffer")
267+
contexts = self._video_sources
263268
if at >= 0:
264269
if at >= len(contexts):
265270
raise IndexError(

0 commit comments

Comments
 (0)