Overview
Calling container.decode() / Stream.decode() on a video (or audio) stream whose codec_context is None crashes the entire process with a fatal memory-access fault (SIGSEGV or SIGBUS; EXC_BAD_ACCESS on macOS) instead of raising a catchable Python exception. codec_context is None when the container opens but cannot establish codec parameters — e.g. a bitstream truncated inside its first coded frame. Because the fault is a native memory access inside FFmpeg's multithreaded decoder, no try/except can recover it: one bad input takes down the whole interpreter.
Reproduce
import av, io
# any H.264/mp4 with faststart (moov up front), truncated inside the first frame
data = open("some_faststart_h264.mp4", "rb").read()
truncated = data[:406]
container = av.open(io.BytesIO(truncated))
stream = container.streams.video[0]
assert stream.codec_context is None # container couldn't build one
for _ in container.decode(stream): # <-- process dies here
pass
Exit status is 139 (SIGSEGV) or 138 (SIGBUS) depending on build; a try/except around the loop does not help — the process is gone before any exception is raised.
Environment
- PyAV 18.0.0 (also reproduced on a source build against the same FFmpeg)
- FFmpeg 8.1.2,
libavcodec 62.28.102
- macOS 15 / Apple Silicon (arm64), CPython 3.12 and 3.14
Root cause
The decode context PyAV creates for container streams uses FFmpeg's default threading (thread_count = 0 → auto; here 4 threads, thread_type = SLICE). FFmpeg 8.1's slice-multithreaded H.264 decoder performs an out-of-bounds / misaligned read when handed a stream truncated inside its first coded frame. Three observations isolate the threading as the trigger, on the same bytes:
| Path |
Result |
ffmpeg CLI (same libav 62.x) |
graceful — "Output file does not contain any stream", no crash |
PyAV, default auto-threaded container.decode() |
crash (SIGSEGV/SIGBUS) |
PyAV, thread_count = 1 / thread_type = "NONE" |
graceful — decodes 0 frames, no crash |
A fault report puts the faulting frame in VideoStream.decode → CodecContext.decode (avcodec_send_packet/avcodec_receive_frame).
It is a class of inputs, not one file: sweeping truncation offsets across several synthetic H.264/mp4 variants (varying duration, GOP, size), decoding each prefix in an isolated subprocess, ~3–4% of arbitrary truncation points crash under the default threading and 0% crash single-threaded. Every crashing cut has stream.codec_context is None.
Suggested fix
The underlying OOB read is arguably an FFmpeg bug in the slice-threaded H.264 decoder (worth reporting upstream too). On the PyAV side, VideoStream.decode / AudioStream.decode are thin passthroughs to self.codec_context.decode(packet) and should not proceed to a native decode when codec_context is None — they can raise a clear ValueError instead. This converts the process-killing crash into a catchable error and is not a perf change for valid streams (they have a codec context and are unaffected; default multithreaded decode is preserved).
Overview
Calling
container.decode()/Stream.decode()on a video (or audio) stream whosecodec_contextisNonecrashes the entire process with a fatal memory-access fault (SIGSEGVorSIGBUS;EXC_BAD_ACCESSon macOS) instead of raising a catchable Python exception.codec_contextisNonewhen the container opens but cannot establish codec parameters — e.g. a bitstream truncated inside its first coded frame. Because the fault is a native memory access inside FFmpeg's multithreaded decoder, notry/exceptcan recover it: one bad input takes down the whole interpreter.Reproduce
Exit status is
139(SIGSEGV) or138(SIGBUS) depending on build; atry/exceptaround the loop does not help — the process is gone before any exception is raised.Environment
libavcodec 62.28.102Root cause
The decode context PyAV creates for container streams uses FFmpeg's default threading (
thread_count = 0→ auto; here 4 threads,thread_type = SLICE). FFmpeg 8.1's slice-multithreaded H.264 decoder performs an out-of-bounds / misaligned read when handed a stream truncated inside its first coded frame. Three observations isolate the threading as the trigger, on the same bytes:ffmpegCLI (same libav 62.x)container.decode()thread_count = 1/thread_type = "NONE"A fault report puts the faulting frame in
VideoStream.decode→CodecContext.decode(avcodec_send_packet/avcodec_receive_frame).It is a class of inputs, not one file: sweeping truncation offsets across several synthetic H.264/mp4 variants (varying duration, GOP, size), decoding each prefix in an isolated subprocess, ~3–4% of arbitrary truncation points crash under the default threading and 0% crash single-threaded. Every crashing cut has
stream.codec_context is None.Suggested fix
The underlying OOB read is arguably an FFmpeg bug in the slice-threaded H.264 decoder (worth reporting upstream too). On the PyAV side,
VideoStream.decode/AudioStream.decodeare thin passthroughs toself.codec_context.decode(packet)and should not proceed to a native decode whencodec_context is None— they can raise a clearValueErrorinstead. This converts the process-killing crash into a catchable error and is not a perf change for valid streams (they have a codec context and are unaffected; default multithreaded decode is preserved).