Skip to content

Commit 10bea3b

Browse files
committed
Fixed HWAccel so we don't share contexts between streams
1 parent a4854a3 commit 10bea3b

File tree

5 files changed

+50
-25
lines changed

5 files changed

+50
-25
lines changed

av/codec/hwaccel.pyx

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import copy
12
import weakref
23
from enum import IntEnum
34

@@ -7,6 +8,7 @@ from av.codec.codec cimport Codec
78
from av.dictionary cimport _Dictionary
89
from av.error cimport err_check
910
from av.video.format cimport get_video_format
11+
1012
from av.dictionary import Dictionary
1113

1214

@@ -94,34 +96,32 @@ cpdef hwdevices_available():
9496

9597

9698
cdef class HWAccel:
97-
def __init__(self, device_type, device=None, codec=None, allow_software_fallback=True, options=None):
99+
def __init__(self, device_type, device=None, allow_software_fallback=True, options=None):
98100
if isinstance(device_type, HWDeviceType):
99101
self._device_type = device_type
100102
elif isinstance(device_type, str):
101103
self._device_type = int(lib.av_hwdevice_find_type_by_name(device_type))
104+
elif isinstance(device_type, int):
105+
self._device_type = device_type
102106
else:
103107
raise ValueError("Unknown type for device_type")
104108

105109
self._device = device
106110
self.allow_software_fallback = allow_software_fallback
107111
self.options = {} if not options else dict(options)
108112
self.ptr = NULL
109-
self.codec = codec
110113
self.config = None
111114

112-
if codec:
113-
self._initialize_hw_context()
114-
115-
def _initialize_hw_context(self):
115+
def _initialize_hw_context(self, Codec codec not None):
116116
cdef HWConfig config
117-
for config in self.codec.hardware_configs:
117+
for config in codec.hardware_configs:
118118
if not (config.ptr.methods & lib.AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX):
119119
continue
120120
if self._device_type and config.device_type != self._device_type:
121121
continue
122122
break
123123
else:
124-
raise NotImplementedError(f"No supported hardware config for {self.codec}")
124+
raise NotImplementedError(f"No supported hardware config for {codec}")
125125

126126
self.config = config
127127

@@ -142,9 +142,14 @@ cdef class HWAccel:
142142
if self.ptr:
143143
raise RuntimeError("Hardware context already initialized")
144144

145-
self.codec = codec
146-
self._initialize_hw_context()
147-
return self
145+
ret = HWAccel(
146+
device_type=self._device_type,
147+
device=self._device,
148+
allow_software_fallback=self.allow_software_fallback,
149+
options=self.options
150+
)
151+
ret._initialize_hw_context(codec)
152+
return ret
148153

149154
def __dealloc__(self):
150155
if self.ptr:

av/container/input.pyx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ cdef class InputContainer(Container):
6868
lib.av_dict_free(&c_options[i])
6969
free(c_options)
7070

71+
at_least_one_accelerated_context = False
72+
7173
self.streams = StreamContainer()
7274
for i in range(self.ptr.nb_streams):
7375
stream = self.ptr.streams[i]
@@ -78,11 +80,16 @@ cdef class InputContainer(Container):
7880
err_check(lib.avcodec_parameters_to_context(codec_context, stream.codecpar))
7981
codec_context.pkt_timebase = stream.time_base
8082
py_codec_context = wrap_codec_context(codec_context, codec, self.hwaccel)
83+
if py_codec_context.is_hwaccel:
84+
at_least_one_accelerated_context = True
8185
else:
8286
# no decoder is available
8387
py_codec_context = None
8488
self.streams.add_stream(wrap_stream(self, stream, py_codec_context))
8589

90+
if self.hwaccel and not self.hwaccel.allow_software_fallback and not at_least_one_accelerated_context:
91+
raise RuntimeError("Hardware accelerated decode requested but no stream is compatible")
92+
8693
self.metadata = avdict_to_dict(self.ptr.metadata, self.metadata_encoding, self.metadata_errors)
8794

8895
def __dealloc__(self):

av/video/codeccontext.pyx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ cdef class VideoCodecContext(CodecContext):
5454
# stream with it, so we shouldn't abort even if we find a stream that can't
5555
# be HW decoded.
5656
# If the user wants to make sure hwaccel is actually used, they can check with the
57-
# is_hardware_accelerated() function on each stream's codec context.
57+
# is_hwaccel() function on each stream's codec context.
5858
self.hwaccel_ctx = None
5959

6060
self._build_format()

examples/basics/hw_decode.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,14 @@
3030
)
3131

3232
if HW_DEVICE is None:
33-
av.codec.hwaccel.dump_hwdevices()
34-
print("Please set HW_DEVICE.")
33+
print(
34+
f"Please set HW_DEVICE. Options are: {str(av.codec.hwaccel.hwdevices_available())}"
35+
)
3536
exit()
3637

37-
assert HW_DEVICE in av.codec.hwaccel.hwdevices_available, f"{HW_DEVICE} not available."
38+
assert (
39+
HW_DEVICE in av.codec.hwaccel.hwdevices_available()
40+
), f"{HW_DEVICE} not available."
3841

3942
print("Decoding in software (auto threading)...")
4043

tests/test_decode.py

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,16 @@
88

99
import av
1010

11+
# This import is needed to make the test_decoded_time_base test pass when we run only this test file.
12+
# Not sure why.
13+
from av.subtitles import subtitle
14+
1115
from .common import TestCase, fate_suite
1216

1317

1418
@functools.cache
1519
def make_h264_test_video(path: str) -> None:
16-
"""Generates a black H264 test video for testing hardware decoding."""
20+
"""Generates a black H264 test video with two streams for testing hardware decoding."""
1721

1822
# We generate a file here that's designed to be as compatible as possible with hardware
1923
# encoders. Hardware encoders are sometimes very picky and the errors we get are often
@@ -23,21 +27,27 @@ def make_h264_test_video(path: str) -> None:
2327
# 8-bit yuv420p.
2428
pathlib.Path(path).parent.mkdir(parents=True, exist_ok=True)
2529
output_container = av.open(path, "w")
26-
stream = output_container.add_stream("libx264", rate=24)
27-
assert isinstance(stream, av.VideoStream)
28-
stream.width = 1280
29-
stream.height = 720
30-
stream.pix_fmt = "yuv420p"
30+
31+
streams = []
32+
for _ in range(2):
33+
stream = output_container.add_stream("libx264", rate=24)
34+
assert isinstance(stream, av.VideoStream)
35+
stream.width = 1280
36+
stream.height = 720
37+
stream.pix_fmt = "yuv420p"
38+
streams.append(stream)
3139

3240
for _ in range(24):
3341
frame = av.VideoFrame.from_ndarray(
3442
np.zeros((720, 1280, 3), dtype=np.uint8), format="rgb24"
3543
)
36-
for packet in stream.encode(frame):
37-
output_container.mux(packet)
44+
for stream in streams:
45+
for packet in stream.encode(frame):
46+
output_container.mux(packet)
3847

39-
for packet in stream.encode():
40-
output_container.mux(packet)
48+
for stream in streams:
49+
for packet in stream.encode():
50+
output_container.mux(packet)
4151

4252
output_container.close()
4353

0 commit comments

Comments
 (0)