Skip to content

Commit

Permalink
Clean up hwaccel
Browse files Browse the repository at this point in the history
  • Loading branch information
WyattBlue committed Dec 17, 2024
1 parent 6fa996c commit d5121a1
Show file tree
Hide file tree
Showing 7 changed files with 104 additions and 111 deletions.
6 changes: 4 additions & 2 deletions av/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,11 @@ def main() -> None:
print(f"{libname:<13} {version[0]:3d}.{version[1]:3d}.{version[2]:3d}")

if args.hwdevices:
from av.codec.hwaccel import dump_hwdevices
from av.codec.hwaccel import hwdevices_available

dump_hwdevices()
print("Hardware device types:")
for x in hwdevices_available():
print(" ", x)

if args.hwconfigs:
from av.codec.codec import dump_hwconfigs
Expand Down
41 changes: 23 additions & 18 deletions av/codec/codec.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,8 @@ cdef class Codec:
raise RuntimeError("%s is both encoder and decoder.")

def __repr__(self):
return f"<av.{self.__class__.__name__}({self.name!r}, {self.mode!r})>"
mode = "w" if self.is_encoder else "r"
return f"<av.{self.__class__.__name__} {self.name} {mode=}>"

def create(self, kind = None):
"""Create a :class:`.CodecContext` for this codec.
Expand Down Expand Up @@ -315,16 +316,18 @@ codec_descriptor = wrap_avclass(lib.avcodec_get_class())
def dump_codecs():
"""Print information about available codecs."""

print('''Codecs:
D.... = Decoding supported
.E... = Encoding supported
..V.. = Video codec
..A.. = Audio codec
..S.. = Subtitle codec
...I. = Intra frame-only codec
....L = Lossless compression
.....H = Hardware decoding supported
------''')
print(
"""Codecs:
D..... = Decoding supported
.E.... = Encoding supported
..V... = Video codec
..A... = Audio codec
..S... = Subtitle codec
...I.. = Intra frame-only codec
....L. = Lossy compression
.....S = Lossless compression
------"""
)

for name in sorted(codecs_available):
try:
Expand All @@ -342,14 +345,14 @@ def dump_codecs():

try:
print(
" %s%s%s%s%s%s %-18s %s"
" %s%s%s%s%s%s %-18s %s"
% (
".D"[bool(d_codec)],
".E"[bool(e_codec)],
codec.type[0].upper(),
".I"[codec.intra_only],
".L"[codec.lossless],
".H"[bool((d_codec or codec).hardware_configs)],
".L"[codec.lossy],
".S"[codec.lossless],
codec.name,
codec.long_name,
)
Expand All @@ -358,15 +361,17 @@ def dump_codecs():
print(f"...... {codec.name:<18} ERROR: {e}")

def dump_hwconfigs():
print('Hardware configs:')
print("Hardware configs:")
for name in sorted(codecs_available):
try:
codec = Codec(name, 'r')
codec = Codec(name, "r")
except ValueError:
continue

configs = codec.hardware_configs
if not configs:
continue
print(' ', codec.name)

print(" ", codec.name)
for config in configs:
print(' ', config)
print(" ", config)
4 changes: 3 additions & 1 deletion av/codec/context.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,9 @@ class CodecContext:
def open(self, strict: bool = True) -> None: ...
@staticmethod
def create(
codec: str | Codec, mode: Literal["r", "w"] | None = None
codec: str | Codec,
mode: Literal["r", "w"] | None = None,
hwaccel: HWAccel | None = None,
) -> CodecContext: ...
def parse(
self, raw_input: bytes | bytearray | memoryview | None = None
Expand Down
58 changes: 30 additions & 28 deletions av/codec/hwaccel.pyi
Original file line number Diff line number Diff line change
@@ -1,46 +1,48 @@
from enum import IntEnum
from typing import Sequence

from av.codec.codec import Codec
from av.video.format import VideoFormat

class HWDeviceType(IntEnum):
NONE = int
VDPAU = int
CUDA = int
VAAPI = int
DXVA2 = int
QSV = int
VIDEOTOOLBOX = int
D3D11VA = int
DRM = int
OPENCL = int
MEDIACODEC = int
VULKAN = int
D3D12VA = int
none: int
vdpau: int
cuda: int
vaapi: int
dxva2: int
qsv: int
videotoolbox: int
d3d11va: int
drm: int
opencl: int
mediacodec: int
vulkan: int
d3d12va: int

class HWConfig(object):
def __init__(self, sentinel): ...
def __repr__(self): ...
class HWConfigMethod(IntEnum):
none: int
hw_device_ctx: int
hw_frame_ctx: int
internal: int
ad_hoc: int

class HWConfig:
@property
def device_type(self): ...
def device_type(self) -> HWDeviceType: ...
@property
def format(self): ...
def format(self) -> VideoFormat: ...
@property
def methods(self): ...
def methods(self) -> HWConfigMethod: ...
@property
def is_supported(self): ...
def is_supported(self) -> bool: ...

class HWAccel:
def __init__(
self,
device_type: str | HWDeviceType,
device: str | None = None,
allow_software_fallback: bool = True,
options=None,
**kwargs
): ...
allow_software_fallback: bool = False,
options: dict[str, object] | None = None,
) -> None: ...
def create(self, codec: Codec): ...

hwdevices_available: Sequence[str]

def dump_hwdevices() -> None: ...
def hwdevices_available() -> list[str]: ...
94 changes: 40 additions & 54 deletions av/codec/hwaccel.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -14,26 +14,26 @@ from av.dictionary import Dictionary


class HWDeviceType(IntEnum):
NONE = lib.AV_HWDEVICE_TYPE_NONE
VDPAU = lib.AV_HWDEVICE_TYPE_VDPAU
CUDA = lib.AV_HWDEVICE_TYPE_CUDA
VAAPI = lib.AV_HWDEVICE_TYPE_VAAPI
DXVA2 = lib.AV_HWDEVICE_TYPE_DXVA2
QSV = lib.AV_HWDEVICE_TYPE_QSV
VIDEOTOOLBOX = lib.AV_HWDEVICE_TYPE_VIDEOTOOLBOX
D3D11VA = lib.AV_HWDEVICE_TYPE_D3D11VA
DRM = lib.AV_HWDEVICE_TYPE_DRM
OPENCL = lib.AV_HWDEVICE_TYPE_OPENCL
MEDIACODEC = lib.AV_HWDEVICE_TYPE_MEDIACODEC
VULKAN = lib.AV_HWDEVICE_TYPE_VULKAN
D3D12VA = lib.AV_HWDEVICE_TYPE_D3D12VA
none = lib.AV_HWDEVICE_TYPE_NONE
vdpau = lib.AV_HWDEVICE_TYPE_VDPAU
cuda = lib.AV_HWDEVICE_TYPE_CUDA
vaapi = lib.AV_HWDEVICE_TYPE_VAAPI
dxva2 = lib.AV_HWDEVICE_TYPE_DXVA2
qsv = lib.AV_HWDEVICE_TYPE_QSV
videotoolbox = lib.AV_HWDEVICE_TYPE_VIDEOTOOLBOX
d3d11va = lib.AV_HWDEVICE_TYPE_D3D11VA
drm = lib.AV_HWDEVICE_TYPE_DRM
opencl = lib.AV_HWDEVICE_TYPE_OPENCL
mediacodec = lib.AV_HWDEVICE_TYPE_MEDIACODEC
vulkan = lib.AV_HWDEVICE_TYPE_VULKAN
d3d12va = lib.AV_HWDEVICE_TYPE_D3D12VA

class HWConfigMethod(IntEnum):
NONE = 0
HW_DEVICE_CTX = lib.AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX # This is the only one we support.
HW_FRAME_CTX = lib.AV_CODEC_HW_CONFIG_METHOD_HW_FRAMES_CTX
INTERNAL = lib.AV_CODEC_HW_CONFIG_METHOD_INTERNAL
AD_HOC = lib.AV_CODEC_HW_CONFIG_METHOD_AD_HOC
none = 0
hw_device_ctx = lib.AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX # This is the only one we support.
hw_frame_ctx = lib.AV_CODEC_HW_CONFIG_METHOD_HW_FRAMES_CTX
internal = lib.AV_CODEC_HW_CONFIG_METHOD_INTERNAL
ad_hoc = lib.AV_CODEC_HW_CONFIG_METHOD_AD_HOC


cdef object _cinit_sentinel = object()
Expand Down Expand Up @@ -82,30 +82,22 @@ cdef class HWConfig:
def is_supported(self):
return bool(self.ptr.methods & lib.AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX)

hwdevices_available = []

cdef lib.AVHWDeviceType x = lib.AV_HWDEVICE_TYPE_NONE
while True:
x = lib.av_hwdevice_iterate_types(x)
if x == lib.AV_HWDEVICE_TYPE_NONE:
break
hwdevices_available.append(lib.av_hwdevice_get_type_name(HWDeviceType(x)))
cpdef hwdevices_available():
result = []

def dump_hwdevices():
print("Hardware device types:")
for x in hwdevices_available:
print(" ", x)
cdef lib.AVHWDeviceType x = lib.AV_HWDEVICE_TYPE_NONE
while True:
x = lib.av_hwdevice_iterate_types(x)
if x == lib.AV_HWDEVICE_TYPE_NONE:
break
result.append(lib.av_hwdevice_get_type_name(HWDeviceType(x)))

return result


cdef class HWAccel:
def __init__(
self,
device_type: str | HWDeviceType,
device: str | None = None,
allow_software_fallback: bool = True,
options=None,
**kwargs,
):
def __init__(self, device_type, device=None, allow_software_fallback=True, options=None):
if isinstance(device_type, HWDeviceType):
self._device_type = device_type
elif isinstance(device_type, str):
Expand All @@ -115,25 +107,22 @@ cdef class HWAccel:

self._device = device
self.allow_software_fallback = allow_software_fallback
self.options = {} if not options else dict(options)

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

def create(self, Codec codec):
def create(self, Codec codec not None):
return HWAccelContext(
device_type=HWDeviceType(self._device_type),
device=self._device,
options=self.options,
codec=codec,
allow_software_fallback=self.allow_software_fallback,
HWDeviceType(self._device_type),
self._device,
self.options,
self.allow_software_fallback,
codec,
)

cdef class HWAccelContext(HWAccel):
def __init__(self, device_type, device, options, codec, allow_software_fallback, **kwargs):
super().__init__(device_type, device, options, **kwargs)
def __init__(self, device_type, device, allow_software_fallback, options, codec):
super().__init__(device_type, device, options)
if not codec:
raise ValueError("codec is required")
raise ValueError("`codec` is required")
self.codec = codec
cdef HWConfig config

Expand All @@ -144,7 +133,7 @@ cdef class HWAccelContext(HWAccel):
continue
break
else:
raise NotImplementedError(f"no supported hardware config for {codec}")
raise NotImplementedError(f"No supported hardware config for {codec}")

self.config = config
cdef char *c_device = NULL
Expand All @@ -162,6 +151,3 @@ cdef class HWAccelContext(HWAccel):
def __dealloc__(self):
if self.ptr:
lib.av_buffer_unref(&self.ptr)

def create(self, *args, **kwargs):
raise ValueError("cannot call HWAccelContext.create")
1 change: 0 additions & 1 deletion av/container/core.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ class Container:
options: dict[str, str]
container_options: dict[str, str]
stream_options: list[dict[str, str]]
hwaccel: HWAccel
streams: StreamContainer
metadata: dict[str, str]
open_timeout: Real | None
Expand Down
11 changes: 4 additions & 7 deletions tests/test_decode.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,30 +204,27 @@ def test_side_data(self) -> None:
assert frame.rotation == -90

def test_hardware_decode(self) -> None:
hwdevices_available = av.codec.hwaccel.hwdevices_available()
if "HWACCEL_DEVICE_TYPE" not in os.environ:
pytest.skip(
"Set the HWACCEL_DEVICE_TYPE to run this test. "
f"Options are {' '.join(av.codec.hwaccel.hwdevices_available)}"
f"Options are {' '.join(hwdevices_available)}"
)

HWACCEL_DEVICE_TYPE = os.environ["HWACCEL_DEVICE_TYPE"]

assert (
HWACCEL_DEVICE_TYPE in av.codec.hwaccel.hwdevices_available
HWACCEL_DEVICE_TYPE in hwdevices_available
), f"{HWACCEL_DEVICE_TYPE} not available"

test_video_path = "tests/assets/black.mp4"
make_h264_test_video(test_video_path)

# Test decode.
hwaccel = av.codec.hwaccel.HWAccel(
device_type=HWACCEL_DEVICE_TYPE, allow_software_fallback=False
)

container = av.open(test_video_path, hwaccel=hwaccel)
video_stream = next(s for s in container.streams if s.type == "video")

assert video_stream is container.streams.video[0]
video_stream = container.streams.video[0]
assert video_stream.codec_context.is_hwaccel

frame_count = 0
Expand Down

0 comments on commit d5121a1

Please sign in to comment.