Skip to content

Commit 82dc79d

Browse files
committed
Make audio / video planes property lazy
This avoids reference loops which make it hard for the garbage collector to free AudioFrame and VideoFrame instances.
1 parent 9bda7d8 commit 82dc79d

File tree

4 files changed

+31
-45
lines changed

4 files changed

+31
-45
lines changed

av/audio/frame.pyx

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,15 +81,12 @@ cdef class AudioFrame(Frame):
8181
align
8282
))
8383

84-
self._init_planes(AudioPlane)
85-
8684
def __dealloc__(self):
8785
lib.av_freep(&self._buffer)
8886

8987
cdef _init_user_attributes(self):
9088
self.layout = get_audio_layout(0, self.ptr.channel_layout)
9189
self.format = get_audio_format(<lib.AVSampleFormat>self.ptr.format)
92-
self._init_planes(AudioPlane)
9390

9491
def __repr__(self):
9592
return '<av.%s %d, pts=%s, %d samples at %dHz, %s, %s at 0x%x>' % (
@@ -131,6 +128,19 @@ cdef class AudioFrame(Frame):
131128
plane.update(array[i, :])
132129
return frame
133130

131+
@property
132+
def planes(self):
133+
"""
134+
A tuple of :class:`~av.audio.plane.AudioPlane`.
135+
136+
:type: tuple
137+
"""
138+
cdef int plane_count = 0
139+
while self.ptr.extended_data[plane_count]:
140+
plane_count += 1
141+
142+
return tuple([AudioPlane(self, i) for i in range(plane_count)])
143+
134144
property samples:
135145
"""
136146
Number of audio samples (per channel).

av/frame.pxd

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,6 @@ cdef class Frame(object):
1313

1414
cdef readonly int index
1515

16-
cdef readonly tuple planes
17-
"""
18-
A tuple of :class:`~av.audio.plane.AudioPlane` or :class:`~av.video.plane.VideoPlane` objects.
19-
20-
:type: tuple
21-
"""
22-
23-
cdef _init_planes(self, cls=?)
24-
cdef int _max_plane_count(self)
25-
2616
cdef _copy_internal_attributes(self, Frame source, bint data_layout=?)
2717

2818
cdef _init_user_attributes(self)

av/frame.pyx

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,3 @@
1-
from libc.limits cimport INT_MAX
2-
3-
from cpython cimport Py_INCREF, PyTuple_New, PyTuple_SET_ITEM
4-
5-
from av.plane cimport Plane
61
from av.utils cimport avrational_to_fraction, to_avrational
72

83
from fractions import Fraction
@@ -33,29 +28,6 @@ cdef class Frame(object):
3328
self.pts,
3429
)
3530

36-
cdef _init_planes(self, cls=Plane):
37-
38-
# We need to detect which planes actually exist, but also contrain
39-
# ourselves to the maximum plane count (as determined only by VideoFrames
40-
# so far), in case the library implementation does not set the last
41-
# plane to NULL.
42-
cdef int max_plane_count = self._max_plane_count()
43-
cdef int plane_count = 0
44-
while plane_count < max_plane_count and self.ptr.extended_data[plane_count]:
45-
plane_count += 1
46-
47-
self.planes = PyTuple_New(plane_count)
48-
for i in range(plane_count):
49-
# We are constructing this tuple manually, but since Cython does
50-
# not understand reference stealing we must manually Py_INCREF
51-
# so that when Cython Py_DECREFs it doesn't release our object.
52-
plane = cls(self, i)
53-
Py_INCREF(plane)
54-
PyTuple_SET_ITEM(self.planes, i, plane)
55-
56-
cdef int _max_plane_count(self):
57-
return INT_MAX
58-
5931
cdef _copy_internal_attributes(self, Frame source, bint data_layout=True):
6032
"""Mimic another frame."""
6133
self.index = source.index

av/video/frame.pyx

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -106,12 +106,8 @@ cdef class VideoFrame(Frame):
106106

107107
self._init_user_attributes()
108108

109-
cdef int _max_plane_count(self):
110-
return self.format.ptr.nb_components
111-
112109
cdef _init_user_attributes(self):
113110
self.format = get_video_format(<lib.AVPixelFormat>self.ptr.format, self.ptr.width, self.ptr.height)
114-
self._init_planes(VideoPlane)
115111

116112
def __dealloc__(self):
117113
# The `self._buffer` member is only set if *we* allocated the buffer in `_init`,
@@ -274,6 +270,24 @@ cdef class VideoFrame(Frame):
274270

275271
return frame
276272

273+
@property
274+
def planes(self):
275+
"""
276+
A tuple of :class:`~av.video.plane.VideoPlane` objects.
277+
278+
:type: tuple
279+
"""
280+
# We need to detect which planes actually exist, but also contrain
281+
# ourselves to the maximum plane count (as determined only by VideoFrames
282+
# so far), in case the library implementation does not set the last
283+
# plane to NULL.
284+
cdef int max_plane_count = self.format.ptr.nb_components
285+
cdef int plane_count = 0
286+
while plane_count < max_plane_count and self.ptr.extended_data[plane_count]:
287+
plane_count += 1
288+
289+
return tuple([VideoPlane(self, i) for i in range(plane_count)])
290+
277291
property width:
278292
"""Width of the image, in pixels."""
279293
def __get__(self): return self.ptr.width

0 commit comments

Comments
 (0)