Skip to content

Commit 2c63608

Browse files
authored
Implement hardware decoding
This implements hardware decoding continuing from the work of @rvillalba-novetta and @mikeboers with cleanup work by @WyattBlue.
1 parent 6eaf701 commit 2c63608

29 files changed

Lines changed: 649 additions & 23 deletions

CHANGELOG.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,16 @@ We are operating with `semantic versioning <https://semver.org>`_.
1717
are merged into the "default" branch.
1818

1919

20+
v14.1.0 (Unreleased)
21+
--------------------
22+
23+
Features
24+
25+
- Add hardware decoding by :gh-user:`matthewlai` and :gh-user:`WyattBlue` in (:pr:`1685`).
26+
27+
2028
v14.0.1
29+
-------
2130

2231
Fixes:
2332

av/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from av.bitstream import BitStreamFilterContext, bitstream_filters_available
1818
from av.codec.codec import Codec, codecs_available
1919
from av.codec.context import CodecContext
20+
from av.codec.hwaccel import HWConfig
2021
from av.container import open
2122
from av.format import ContainerFormat, formats_available
2223
from av.packet import Packet

av/__main__.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
def main() -> None:
77
parser = argparse.ArgumentParser()
88
parser.add_argument("--codecs", action="store_true")
9+
parser.add_argument("--hwdevices", action="store_true")
10+
parser.add_argument("--hwconfigs", action="store_true")
911
parser.add_argument("--version", action="store_true")
1012
args = parser.parse_args()
1113

@@ -30,6 +32,18 @@ def main() -> None:
3032
version = config["version"]
3133
print(f"{libname:<13} {version[0]:3d}.{version[1]:3d}.{version[2]:3d}")
3234

35+
if args.hwdevices:
36+
from av.codec.hwaccel import hwdevices_available
37+
38+
print("Hardware device types:")
39+
for x in hwdevices_available():
40+
print(" ", x)
41+
42+
if args.hwconfigs:
43+
from av.codec.codec import dump_hwconfigs
44+
45+
dump_hwconfigs()
46+
3347
if args.codecs:
3448
from av.codec.codec import dump_codecs
3549

av/about.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "14.0.1"
1+
__version__ = "14.1.0"

av/audio/codeccontext.pyx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@ 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.frame cimport Frame
78
from av.packet cimport Packet
89

910

1011
cdef class AudioCodecContext(CodecContext):
11-
cdef _init(self, lib.AVCodecContext *ptr, const lib.AVCodec *codec):
12-
CodecContext._init(self, ptr, codec)
12+
cdef _init(self, lib.AVCodecContext *ptr, const lib.AVCodec *codec, HWAccel hwaccel):
13+
CodecContext._init(self, ptr, codec, hwaccel)
1314

1415
cdef _prepare_frames_for_encode(self, Frame input_frame):
1516

av/codec/codec.pxd

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ cdef class Codec:
77
cdef const lib.AVCodecDescriptor *desc
88
cdef readonly bint is_encoder
99

10+
cdef tuple _hardware_configs
11+
1012
cdef _init(self, name=?)
1113

1214

av/codec/codec.pyi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,3 +108,4 @@ class codec_descriptor:
108108
codecs_available: set[str]
109109

110110
def dump_codecs() -> None: ...
111+
def dump_hwconfigs() -> None: ...

av/codec/codec.pyx

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from av.audio.format cimport get_audio_format
2+
from av.codec.hwaccel cimport wrap_hwconfig
23
from av.descriptor cimport wrap_avclass
34
from av.utils cimport avrational_to_fraction
45
from av.video.format cimport get_video_format
@@ -117,6 +118,10 @@ cdef class Codec:
117118
if self.is_encoder and lib.av_codec_is_decoder(self.ptr):
118119
raise RuntimeError("%s is both encoder and decoder.")
119120

121+
def __repr__(self):
122+
mode = "w" if self.is_encoder else "r"
123+
return f"<av.{self.__class__.__name__} {self.name} {mode=}>"
124+
120125
def create(self, kind = None):
121126
"""Create a :class:`.CodecContext` for this codec.
122127
@@ -203,6 +208,23 @@ cdef class Codec:
203208
i += 1
204209
return ret
205210

211+
@property
212+
def hardware_configs(self):
213+
if self._hardware_configs:
214+
return self._hardware_configs
215+
ret = []
216+
cdef int i = 0
217+
cdef lib.AVCodecHWConfig *ptr
218+
while True:
219+
ptr = lib.avcodec_get_hw_config(self.ptr, i)
220+
if not ptr:
221+
break
222+
ret.append(wrap_hwconfig(ptr))
223+
i += 1
224+
ret = tuple(ret)
225+
self._hardware_configs = ret
226+
return ret
227+
206228
@property
207229
def properties(self):
208230
return self.desc.props
@@ -337,3 +359,19 @@ def dump_codecs():
337359
)
338360
except Exception as e:
339361
print(f"...... {codec.name:<18} ERROR: {e}")
362+
363+
def dump_hwconfigs():
364+
print("Hardware configs:")
365+
for name in sorted(codecs_available):
366+
try:
367+
codec = Codec(name, "r")
368+
except ValueError:
369+
continue
370+
371+
configs = codec.hardware_configs
372+
if not configs:
373+
continue
374+
375+
print(" ", codec.name)
376+
for config in configs:
377+
print(" ", config)

av/codec/context.pxd

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ from libc.stdint cimport int64_t
33

44
from av.bytesource cimport ByteSource
55
from av.codec.codec cimport Codec
6+
from av.codec.hwaccel cimport HWAccel
67
from av.frame cimport Frame
78
from av.packet cimport Packet
89

@@ -18,11 +19,12 @@ cdef class CodecContext:
1819
cdef int stream_index
1920

2021
cdef lib.AVCodecParserContext *parser
21-
cdef _init(self, lib.AVCodecContext *ptr, const lib.AVCodec *codec)
22+
cdef _init(self, lib.AVCodecContext *ptr, const lib.AVCodec *codec, HWAccel hwaccel)
2223

2324
# Public API.
2425
cdef readonly bint is_open
2526
cdef readonly Codec codec
27+
cdef readonly HWAccel hwaccel
2628
cdef public dict options
2729
cpdef open(self, bint strict=?)
2830

@@ -31,6 +33,9 @@ cdef class CodecContext:
3133
cpdef decode(self, Packet packet=?)
3234
cpdef flush_buffers(self)
3335

36+
# Used by hardware-accelerated decode.
37+
cdef HWAccel hwaccel_ctx
38+
3439
# Used by both transcode APIs to setup user-land objects.
3540
# TODO: Remove the `Packet` from `_setup_decoded_frame` (because flushing packets
3641
# are bogus). It should take all info it needs from the context and/or stream.
@@ -49,10 +54,11 @@ cdef class CodecContext:
4954
cdef _send_packet_and_recv(self, Packet packet)
5055
cdef _recv_frame(self)
5156

57+
cdef _transfer_hwframe(self, Frame frame)
58+
5259
# Implemented by children for the generic send/recv API, so we have the
5360
# correct subclass of Frame.
5461
cdef Frame _next_frame
5562
cdef Frame _alloc_next_frame(self)
5663

57-
58-
cdef CodecContext wrap_codec_context(lib.AVCodecContext*, const lib.AVCodec*)
64+
cdef CodecContext wrap_codec_context(lib.AVCodecContext*, const lib.AVCodec*, HWAccel hwaccel)

av/codec/context.pyi

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ from typing import ClassVar, Literal
55
from av.packet import Packet
66

77
from .codec import Codec
8+
from .hwaccel import HWAccel
89

910
class ThreadType(Flag):
1011
NONE: ClassVar[ThreadType]
@@ -83,10 +84,14 @@ class CodecContext:
8384
def delay(self) -> bool: ...
8485
@property
8586
def extradata_size(self) -> int: ...
87+
@property
88+
def is_hwaccel(self) -> bool: ...
8689
def open(self, strict: bool = True) -> None: ...
8790
@staticmethod
8891
def create(
89-
codec: str | Codec, mode: Literal["r", "w"] | None = None
92+
codec: str | Codec,
93+
mode: Literal["r", "w"] | None = None,
94+
hwaccel: HWAccel | None = None,
9095
) -> CodecContext: ...
9196
def parse(
9297
self, raw_input: bytes | bytearray | memoryview | None = None

0 commit comments

Comments
 (0)