Skip to content

Commit 90e334f

Browse files
committed
Move responsibility for setting up hardware to HWAccelContext.
We will not likely continue down this path, as `man ffmpeg` makes the point that CPU decoding is about the same speed as GPU decoding, and so is only really of use for playback. I don't think PyAVs target is such high performance playback, so we don't need to make the design concesions required for this branch. NOTE: This has not been tested to work. Two commits back is the original PR squashed into a single commit and is more likely to work, although if there is any hope of this being merged it will have to look more like this commit does. See (and further any discussion) #331 on GitHub.
1 parent b406383 commit 90e334f

File tree

11 files changed

+110
-89
lines changed

11 files changed

+110
-89
lines changed

av/audio/codeccontext.pyx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,16 @@ cimport libav as lib
33
from av.audio.format cimport AudioFormat, get_audio_format
44
from av.audio.frame cimport AudioFrame, alloc_audio_frame
55
from av.audio.layout cimport AudioLayout, get_audio_layout
6+
from av.codec.hwaccel cimport HWAccel
67
from av.error cimport err_check
78
from av.frame cimport Frame
89
from av.packet cimport Packet
910

1011

1112
cdef class AudioCodecContext(CodecContext):
1213

13-
cdef _init(self, lib.AVCodecContext *ptr, const lib.AVCodec *codec):
14-
CodecContext._init(self, ptr, codec)
14+
cdef _init(self, lib.AVCodecContext *ptr, const lib.AVCodec *codec, HWAccel hwaccel):
15+
CodecContext._init(self, ptr, codec, hwaccel)
1516

1617
# Sometimes there isn't a layout set, but there are a number of
1718
# channels. Assume it is the default layout.

av/codec/codec.pyx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,9 +92,9 @@ cdef class Codec(object):
9292
def __repr__(self):
9393
return f'<av.{self.__class__.__name__}({self.name!r}, {self.mode!r})>'
9494

95-
def create(self):
95+
def create(self, *args, **kwargs):
9696
from .context import CodecContext
97-
return CodecContext.create(self)
97+
return CodecContext.create(self, *args, **kwargs)
9898

9999
property is_decoder:
100100
def __get__(self):

av/codec/context.pxd

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@ from libc.stdint cimport int64_t
22

33
cimport libav as lib
44

5+
from av.bytesource cimport ByteSource
56
from av.codec.codec cimport Codec
7+
from av.codec.hwaccel cimport HWAccel, HWAccelContext
68
from av.frame cimport Frame
79
from av.packet cimport Packet
8-
from av.bytesource cimport ByteSource
910

1011

1112
cdef class CodecContext(object):
@@ -25,10 +26,12 @@ cdef class CodecContext(object):
2526
# To hold a reference to passed extradata.
2627
cdef ByteSource extradata_source
2728

28-
cdef _init(self, lib.AVCodecContext *ptr, const lib.AVCodec *codec)
29+
cdef _init(self, lib.AVCodecContext *ptr, const lib.AVCodec *codec, HWAccel hwaccel)
2930

3031
cdef readonly Codec codec
3132

33+
cdef readonly HWAccelContext hwaccel
34+
3235
cdef public dict options
3336

3437
# Public API.
@@ -67,4 +70,4 @@ cdef class CodecContext(object):
6770
cdef Frame _alloc_next_frame(self)
6871

6972

70-
cdef CodecContext wrap_codec_context(lib.AVCodecContext*, const lib.AVCodec*, bint allocated, dict hwaccel)
73+
cdef CodecContext wrap_codec_context(lib.AVCodecContext*, const lib.AVCodec*, bint allocated, HWAccel hwaccel)

av/codec/context.pyx

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ cimport libav as lib
77

88
from av.bytesource cimport ByteSource, bytesource
99
from av.codec.codec cimport Codec, wrap_codec
10+
from av.codec.hwaccel cimport HWAccel
1011
from av.dictionary cimport _Dictionary
1112
from av.dictionary import Dictionary
1213
from av.enums cimport define_enum
@@ -18,15 +19,15 @@ from av.utils cimport avdict_to_dict, avrational_to_fraction, to_avrational
1819
cdef object _cinit_sentinel = object()
1920

2021

21-
cdef CodecContext wrap_codec_context(lib.AVCodecContext *c_ctx, const lib.AVCodec *c_codec, bint allocated, dict hwaccel):
22+
cdef CodecContext wrap_codec_context(lib.AVCodecContext *c_ctx, const lib.AVCodec *c_codec, bint allocated, HWAccel hwaccel):
2223
"""Build an av.CodecContext for an existing AVCodecContext."""
2324

2425
cdef CodecContext py_ctx
2526

2627
# TODO: This.
2728
if c_ctx.codec_type == lib.AVMEDIA_TYPE_VIDEO:
2829
from av.video.codeccontext import VideoCodecContext
29-
py_ctx = VideoCodecContext(_cinit_sentinel, hwaccel=hwaccel)
30+
py_ctx = VideoCodecContext(_cinit_sentinel)
3031
elif c_ctx.codec_type == lib.AVMEDIA_TYPE_AUDIO:
3132
from av.audio.codeccontext import AudioCodecContext
3233
py_ctx = AudioCodecContext(_cinit_sentinel)
@@ -37,7 +38,7 @@ cdef CodecContext wrap_codec_context(lib.AVCodecContext *c_ctx, const lib.AVCode
3738
py_ctx = CodecContext(_cinit_sentinel)
3839

3940
py_ctx.allocated = allocated
40-
py_ctx._init(c_ctx, c_codec)
41+
py_ctx._init(c_ctx, c_codec, hwaccel)
4142

4243
return py_ctx
4344

@@ -62,10 +63,10 @@ SkipType = define_enum('SkipType', (
6263
cdef class CodecContext(object):
6364

6465
@staticmethod
65-
def create(codec, mode=None):
66+
def create(codec, mode=None, hwaccel=None):
6667
cdef Codec cy_codec = codec if isinstance(codec, Codec) else Codec(codec, mode)
6768
cdef lib.AVCodecContext *c_ctx = lib.avcodec_alloc_context3(cy_codec.ptr)
68-
return wrap_codec_context(c_ctx, cy_codec.ptr, True, None)
69+
return wrap_codec_context(c_ctx, cy_codec.ptr, True, hwaccel)
6970

7071
def __cinit__(self, sentinel=None, *args, **kwargs):
7172
if sentinel is not _cinit_sentinel:
@@ -74,7 +75,7 @@ cdef class CodecContext(object):
7475
self.options = {}
7576
self.stream_index = -1 # This is set by the container immediately.
7677

77-
cdef _init(self, lib.AVCodecContext *ptr, const lib.AVCodec *codec):
78+
cdef _init(self, lib.AVCodecContext *ptr, const lib.AVCodec *codec, HWAccel hwaccel):
7879

7980
self.ptr = ptr
8081
if self.ptr.codec and codec and self.ptr.codec != codec:

av/codec/hwaccel.pxd

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11

22
cimport libav as lib
33

4+
from av.codec.codec cimport Codec
5+
46

57
cdef class HWConfig(object):
68

@@ -16,8 +18,14 @@ cdef HWConfig wrap_hwconfig(lib.AVCodecHWConfig *ptr)
1618

1719
cdef class HWAccel(object):
1820

19-
cdef lib.AVHWAccel *ptr
20-
2121
cdef str _device_type
2222
cdef str _device
2323
cdef public dict options
24+
25+
26+
cdef class HWAccelContext(HWAccel):
27+
28+
cdef readonly Codec codec
29+
cdef readonly HWConfig config
30+
31+
cdef lib.AVBufferRef *ptr

av/codec/hwaccel.pyx

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,13 @@ import weakref
55
cimport libav as lib
66

77
from av.codec.codec cimport Codec
8+
from av.dictionary cimport _Dictionary
89
from av.enums cimport define_enum
10+
from av.error cimport err_check
911
from av.video.format cimport get_video_format
1012

13+
from av.dictionary import Dictionary
14+
1115

1216
HWDeviceType = define_enum('HWDeviceType', (
1317
# ('NONE', lib.AV_HWDEVICE_TYPE_NONE),
@@ -100,12 +104,70 @@ def dump_hwdevices():
100104

101105
cdef class HWAccel(object):
102106

107+
@classmethod
108+
def adapt(cls, input_):
109+
if input_ is True:
110+
return cls()
111+
if isinstance(input_, cls):
112+
return input_
113+
if isinstance(input_, (str, HWDeviceType)):
114+
return cls(input_)
115+
if isinstance(input_, (list, tuple)):
116+
return cls(*input_)
117+
if isinstance(input_, dict):
118+
return cls(**input_)
119+
raise TypeError(f"can't adapt to HWAccel; {input_!r}")
120+
103121
def __init__(self, device_type=None, device=None, options=None, **kwargs):
104122

105-
self._device_type = device_type
123+
self._device_type = HWDeviceType(device_type) if device_type else None
106124
self._device = device
107125

108126
if options and kwargs:
109127
raise ValueError("accepts only one of options arg or kwargs")
110128
self.options = dict(options or kwargs)
111129

130+
def create(self, Codec codec):
131+
return HWAccelContext(self._device_type, self._device, self.options, codec)
132+
133+
134+
cdef class HWAccelContext(HWAccel):
135+
136+
def __init__(self, device_type=None, device=None, options=None, codec=None, **kwargs):
137+
super().__init__(device_type, device, options, **kwargs)
138+
139+
if not codec:
140+
raise ValueError("codec is required")
141+
self.codec = codec
142+
143+
cdef HWConfig config
144+
for config in codec.hardware_configs:
145+
146+
if not (config.ptr.methods & lib.AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX):
147+
continue
148+
149+
if self._device_type and config.device_type != self._device_type:
150+
continue
151+
152+
break
153+
154+
else:
155+
raise ValueError(f"no supported hardware config for {codec}")
156+
157+
self.config = config
158+
159+
cdef char *c_device = NULL
160+
if self._device:
161+
device_bytes = self._device.encode()
162+
c_device = device_bytes
163+
164+
cdef _Dictionary c_options = Dictionary(self.options)
165+
166+
err_check(lib.av_hwdevice_ctx_create(&self.ptr, config.ptr.device_type, c_device, c_options.ptr, 0))
167+
168+
def __dealloc__(self):
169+
if self.ptr:
170+
lib.av_buffer_unref(&self.ptr)
171+
172+
def create(self, *args, **kwargs):
173+
raise ValueError("cannot call HWAccelContext.create")

av/container/core.pxd

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
cimport libav as lib
22

3+
from av.codec.hwaccel cimport HWAccel
34
from av.container.streams cimport StreamContainer
45
from av.dictionary cimport _Dictionary
56
from av.format cimport ContainerFormat
@@ -41,7 +42,7 @@ cdef class Container(object):
4142
cdef readonly dict container_options
4243
cdef readonly list stream_options
4344

44-
cdef dict hwaccel
45+
cdef HWAccel hwaccel
4546

4647
cdef readonly StreamContainer streams
4748
cdef readonly dict metadata

av/container/core.pyx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import time
77

88
cimport libav as lib
99

10+
from av.codec.hwaccel cimport HWAccel
1011
from av.container.core cimport timeout_info
1112
from av.container.input cimport InputContainer
1213
from av.container.output cimport OutputContainer
@@ -287,7 +288,7 @@ def open(file, mode=None, format=None, options=None,
287288
read_timeout = timeout
288289

289290
if hwaccel is not None:
290-
hwaccel = dict(hwaccel)
291+
hwaccel = HWAccel.adapt(hwaccel)
291292

292293
if mode.startswith('r'):
293294
return InputContainer(

av/video/codeccontext.pxd

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,3 @@ cdef class VideoCodecContext(CodecContext):
2121

2222
# For decoding.
2323
cdef VideoFrame next_frame
24-
25-
# For hardware acceleration
26-
cdef dict hwaccel
27-
cdef lib.AVPixelFormat hw_pix_fmt
28-
cdef lib.AVBufferRef* hw_device_ctx
29-
cdef bint _setup_hw_decoder(self, lib.AVCodec *codec)

av/video/codeccontext.pyx

Lines changed: 13 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ from libc.stdint cimport int64_t
33
cimport libav as lib
44

55
from av.codec.context cimport CodecContext
6+
from av.codec.hwaccel cimport HWAccel, HWConfig
67
from av.frame cimport Frame
78
from av.packet cimport Packet
89
from av.utils cimport avrational_to_fraction, to_avrational
@@ -28,66 +29,18 @@ cdef class VideoCodecContext(CodecContext):
2829
self.last_w = 0
2930
self.last_h = 0
3031

31-
self.hw_pix_fmt = lib.AV_PIX_FMT_NONE
32-
self.hw_device_ctx = NULL
33-
self.hwaccel = kwargs.get("hwaccel", None)
32+
cdef _init(self, lib.AVCodecContext *ptr, const lib.AVCodec *codec, HWAccel hwaccel):
33+
CodecContext._init(self, ptr, codec, hwaccel) # TODO: Can this be `super`?
3434

35-
cdef _init(self, lib.AVCodecContext *ptr, const lib.AVCodec *codec):
36-
CodecContext._init(self, ptr, codec) # TODO: Can this be `super`?
37-
38-
if self.hwaccel is not None:
39-
self._setup_hw_decoder(codec)
35+
if hwaccel is not None:
36+
self.hwaccel = hwaccel.create(self.codec)
37+
self.ptr.hw_device_ctx = lib.av_buffer_ref(self.hwaccel.ptr)
38+
self.ptr.pix_fmt = self.hwaccel.config.ptr.pix_fmt
39+
self.ptr.get_format = _get_hw_format
4040

4141
self._build_format()
4242
self.encoded_frame_count = 0
4343

44-
cdef bint _setup_hw_decoder(self, lib.AVCodec *codec):
45-
# Get device type
46-
device_type = lib.av_hwdevice_find_type_by_name(self.hwaccel["device_type_name"])
47-
if device_type == lib.AV_HWDEVICE_TYPE_NONE:
48-
raise ValueError("Device type {} is not supported.".format(self.hwaccel["device_type_name"]))
49-
50-
# Check that decoder is supported by this device
51-
i = 0
52-
while True:
53-
config = lib.avcodec_get_hw_config(codec, i)
54-
55-
# Exhausted list
56-
if not config:
57-
break
58-
59-
if config.methods & lib.AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX and config.device_type == device_type:
60-
self.hw_pix_fmt = config.pix_fmt
61-
break
62-
63-
i += 1
64-
65-
# Decoder is not supported by the desired device
66-
if self.hw_pix_fmt == lib.AV_PIX_FMT_NONE:
67-
return False
68-
69-
# Override the decoder context's get_format function
70-
self.ptr.pix_fmt = self.hw_pix_fmt
71-
self.ptr.get_format = _get_hw_format
72-
73-
# Create the hardware device context
74-
cdef char* device = NULL
75-
if "device" in self.hwaccel:
76-
device_bytes = self.hwaccel["device"].encode()
77-
device = device_bytes
78-
79-
err = lib.av_hwdevice_ctx_create(&self.hw_device_ctx, device_type, device, NULL, 0)
80-
if err < 0:
81-
raise RuntimeError("Failed to create specified HW device")
82-
83-
self.ptr.hw_device_ctx = lib.av_buffer_ref(self.hw_device_ctx)
84-
85-
return True
86-
87-
def __dealloc__(self):
88-
if self.hw_device_ctx:
89-
lib.av_buffer_unref(&self.hw_device_ctx)
90-
9144
cdef _set_default_time_base(self):
9245
self.ptr.time_base.num = self.ptr.framerate.den or 1
9346
self.ptr.time_base.den = self.ptr.framerate.num or lib.AV_TIME_BASE
@@ -136,14 +89,14 @@ cdef class VideoCodecContext(CodecContext):
13689
cdef _transfer_hwframe(self, Frame frame):
13790
cdef Frame frame_sw
13891

139-
if self.using_hwaccel and frame.ptr.format == self.hw_pix_fmt:
140-
# retrieve data from GPU to CPU
92+
# TODO: What is up with the format check?!
93+
# retrieve data from GPU to CPU
94+
if self.hwaccel is not None and frame.ptr.format == self.hwaccel.config.ptr.pix_fmt:
14195
frame_sw = self._alloc_next_frame()
14296

143-
ret = lib.av_hwframe_transfer_data(frame_sw.ptr, frame.ptr, 0)
144-
if (ret < 0):
145-
raise RuntimeError("Error transferring the data to system memory")
97+
err_check(lib.av_hwframe_transfer_data(frame_sw.ptr, frame.ptr, 0))
14698

99+
# TODO: Is there anything else to transfer?!
147100
frame_sw.pts = frame.pts
148101

149102
return frame_sw
@@ -245,7 +198,3 @@ cdef class VideoCodecContext(CodecContext):
245198
property coded_height:
246199
def __get__(self):
247200
return self.ptr.coded_height
248-
249-
property using_hwaccel:
250-
def __get__(self):
251-
return self.hw_device_ctx != NULL

0 commit comments

Comments
 (0)