diff --git a/CHANGELOG.rst b/CHANGELOG.rst index ae641c115..258a57d30 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -51,6 +51,7 @@ Fixes: - ``FilterContext.link_to`` raises ``ValueError`` when linking filters from different graphs instead of aborting the process. - Filters added or linked after ``Graph.configure()`` are now configured on the next push or pull instead of segfaulting. - ``SubtitleCodecContext.decode(None)`` flushes the decoder instead of raising ``RuntimeError``, so delayed decoders such as EIA-608 closed captions return their final subtitle. +- ``AudioFrame.planes`` no longer counts one plane too many for 8-channel planar audio (e.g. ``fltp`` with ``7.1``), which made ``planes``, ``to_ndarray()``, and ``from_ndarray()`` raise ``ValueError``, and no longer reads past the end of ``extended_data`` for more channels. By :gh-user:`ChandraChaan` in (:pr:`2502`). v19.0.2 ------- diff --git a/av/audio/frame.py b/av/audio/frame.py index e4ae05a43..db4575e45 100644 --- a/av/audio/frame.py +++ b/av/audio/frame.py @@ -148,8 +148,9 @@ def planes(self): :type: tuple """ + nb_planes: cython.int = self.layout.nb_channels if self.format.is_planar else 1 plane_count: cython.int = 0 - while self.ptr.extended_data[plane_count]: + while plane_count < nb_planes and self.ptr.extended_data[plane_count]: plane_count += 1 return tuple([AudioPlane(self, i) for i in range(plane_count)]) diff --git a/tests/test_audioframe.py b/tests/test_audioframe.py index c364fb24e..80c06de0e 100644 --- a/tests/test_audioframe.py +++ b/tests/test_audioframe.py @@ -45,6 +45,17 @@ def test_manual_fltp_stereo_constructor() -> None: assert frame.samples == 160 +def test_manual_fltp_7_1_constructor() -> None: + # With 8 planes, extended_data points at the frame's data[8] array, which + # is not NULL-terminated: the next field is linesize[0]. + frame = AudioFrame(format="fltp", layout="7.1", samples=160) + assert len(frame.planes) == 8 + + array = np.arange(8 * 160, dtype="f4").reshape(8, 160) + frame = AudioFrame.from_ndarray(array, format="fltp", layout="7.1") + assertNdarraysEqual(frame.to_ndarray(), array) + + def test_manual_s16_mono_constructor() -> None: frame = AudioFrame(format="s16", layout="mono", samples=160) assert frame.format.name == "s16"