Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ Fixes:
- ``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`).
- ``AudioFifo.write()`` now raises ``ValueError`` for a frame whose channel layout differs from the first frame's, instead of reading the wrong number of channels and scrambling or over-reading the samples. By :gh-user:`ChandraChaan` in (:pr:`2505`).
- A failed ``CodecContext.open()`` no longer clears the selected codec, so ``codec``, ``name``, ``is_encoder``, and ``is_decoder`` keep working and the context can be reused after fixing the offending parameters.

v19.0.2
-------
Expand Down
21 changes: 18 additions & 3 deletions av/codec/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,16 @@ def is_open(self):

@cython.ccall
def open(self, strict: cython.bint = True):
"""open(strict=True)

Open the codec, readying the context to encode or decode.

The selected codec is preserved if opening fails, so the context stays
inspectable and can be retried after fixing the offending parameters.
FFmpeg's failed-open cleanup still resets other fields, though (notably
``layout`` drops to 0 channels), so a retry must re-apply the parameters
that were set before the failed attempt.
"""
if lib.avcodec_is_open(self.ptr):
if strict:
raise ValueError("CodecContext is already open.")
Expand All @@ -433,10 +443,15 @@ def open(self, strict: cython.bint = True):

self._setup_encode_hwframes()

err_check(
lib.avcodec_open2(self.ptr, self.ptr.codec, cython.address(options.ptr)),
f'avcodec_open2("{self.ptr.codec.name}", {self.options})',
# AVCodec structs are static globals, so holding the pointer across the
# call is safe.
codec: cython.pointer[cython.const[lib.AVCodec]] = self.ptr.codec
ret: cython.int = lib.avcodec_open2(
self.ptr, codec, cython.address(options.ptr)
)
if ret < 0:
self.ptr.codec = codec
err_check(ret, f'avcodec_open2("{codec.name}", {self.options})')
self.options = dict(options)

def __dealloc__(self):
Expand Down
24 changes: 24 additions & 0 deletions tests/test_codec_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,30 @@ def test_parse_assigns_packet_timing(self) -> None:
# so at least some packets should carry it through.
assert any(p.pos is not None for p in packets)

def test_failed_open_preserves_codec(self) -> None:
# A failed avcodec_open2() runs FFmpeg's cleanup, which clears
# avctx->codec. open() must restore it so the context stays usable.
ctx = av.AudioCodecContext.create("aac", "w")
ctx.format = av.AudioFormat("s16") # AAC wants fltp, so open() fails.
ctx.layout = "stereo"
ctx.sample_rate = 44100

with pytest.raises(av.error.FFmpegError):
ctx.open()

# The selected codec is still known after the failure.
assert ctx.codec is not None
assert ctx.codec.name == "aac"
assert ctx.is_encoder and not ctx.is_decoder
assert not ctx.is_open

# Fixing the offending parameters and retrying works on the same context.
ctx.format = av.AudioFormat("fltp")
ctx.layout = "stereo"
ctx.sample_rate = 44100
ctx.open()
assert ctx.is_open


class TestEncoding(TestCase):
def test_encoding_png(self) -> None:
Expand Down
Loading