diff --git a/av/__init__.py b/av/__init__.py index 7713215b7cfe4536632473521f34591264e0b7f3..870cdf15999a52a3e5184c27df9a76ebc49a408b 100644 --- a/av/__init__.py +++ b/av/__init__.py @@ -19,6 +19,7 @@ from av.audio.layout import AudioLayout from av.audio.resampler import AudioResampler from av.codec.codec import Codec, codecs_available from av.codec.context import CodecContext +from av.codec.hwaccel import HWConfig from av.container import open from av.format import ContainerFormat, formats_available from av.packet import Packet diff --git a/av/__main__.py b/av/__main__.py index 09ace3c4bba8c114d96e50af9383de0c878605cd..6f11d3d8ce375d6ccdee2538d64f4641775d6dc0 100644 --- a/av/__main__.py +++ b/av/__main__.py @@ -7,6 +7,8 @@ def main(): parser = argparse.ArgumentParser() parser.add_argument('--codecs', action='store_true') + parser.add_argument('--hwdevices', action='store_true') + parser.add_argument('--hwconfigs', action='store_true') parser.add_argument('--version', action='store_true') args = parser.parse_args() @@ -39,6 +41,14 @@ def main(): from av.codec.codec import dump_codecs dump_codecs() + if args.hwdevices: + from av.codec.hwaccel import dump_hwdevices + dump_hwdevices() + + if args.hwconfigs: + from av.codec.codec import dump_hwconfigs + dump_hwconfigs() + if __name__ == '__main__': main() diff --git a/av/audio/codeccontext.pyx b/av/audio/codeccontext.pyx index b66064f1230b1eb2a9a7f5a5d918e4fce69f14ae..7d5439d0bd479b3e8808dd45e083e76f21070c87 100644 --- a/av/audio/codeccontext.pyx +++ b/av/audio/codeccontext.pyx @@ -3,6 +3,7 @@ cimport libav as lib from av.audio.format cimport AudioFormat, get_audio_format from av.audio.frame cimport AudioFrame, alloc_audio_frame from av.audio.layout cimport AudioLayout, get_audio_layout +from av.codec.hwaccel cimport HWAccel from av.error cimport err_check from av.frame cimport Frame from av.packet cimport Packet @@ -10,8 +11,8 @@ from av.packet cimport Packet cdef class AudioCodecContext(CodecContext): - cdef _init(self, lib.AVCodecContext *ptr, const lib.AVCodec *codec): - CodecContext._init(self, ptr, codec) + cdef _init(self, lib.AVCodecContext *ptr, const lib.AVCodec *codec, HWAccel hwaccel): + CodecContext._init(self, ptr, codec, hwaccel) # Sometimes there isn't a layout set, but there are a number of # channels. Assume it is the default layout. diff --git a/av/codec/codec.pxd b/av/codec/codec.pxd index 173f0ef182497e359b35f8ebfba0cf41dd84edac..1713180afafde556e4ffb5baef439bbc7cb5f879 100644 --- a/av/codec/codec.pxd +++ b/av/codec/codec.pxd @@ -7,6 +7,8 @@ cdef class Codec(object): cdef const lib.AVCodecDescriptor *desc cdef readonly bint is_encoder + cdef tuple _hardware_configs + cdef _init(self, name=?) diff --git a/av/codec/codec.pyx b/av/codec/codec.pyx index ed9762d3014885a7e16218d0b979e13ce9cfc5b1..7846a234528941c7ce36d37e3d2e28a1345825cc 100644 --- a/av/codec/codec.pyx +++ b/av/codec/codec.pyx @@ -1,4 +1,7 @@ +from __future__ import print_function + from av.audio.format cimport get_audio_format +from av.codec.hwaccel cimport wrap_hwconfig from av.descriptor cimport wrap_avclass from av.utils cimport avrational_to_fraction, flag_in_bitfield from av.video.format cimport get_video_format @@ -86,14 +89,21 @@ cdef class Codec(object): if self.is_encoder and lib.av_codec_is_decoder(self.ptr): raise RuntimeError('%s is both encoder and decoder.') - def create(self): + def __repr__(self): + return f'<av.{self.__class__.__name__}({self.name!r}, {self.mode!r})>' + + def create(self, *args, **kwargs): from .context import CodecContext - return CodecContext.create(self) + return CodecContext.create(self, *args, **kwargs) property is_decoder: def __get__(self): return not self.is_encoder + @property + def mode(self): + return 'w' if self.is_encoder else 'r' + property descriptor: def __get__(self): return wrap_avclass(self.ptr.priv_class) @@ -184,6 +194,26 @@ cdef class Codec(object): i += 1 return ret + @property + def hardware_configs(self): + + if self._hardware_configs: + return self._hardware_configs + + ret = [] + cdef int i = 0 + cdef lib.AVCodecHWConfig *ptr + while True: + ptr = lib.avcodec_get_hw_config(self.ptr, i) + if not ptr: + break + ret.append(wrap_hwconfig(ptr)) + i += 1 + + ret = tuple(ret) + self._hardware_configs = ret + return ret + # Capabilities. property draw_horiz_band: def __get__(self): return flag_in_bitfield(self.ptr.capabilities, lib.CODEC_CAP_DRAW_HORIZ_BAND) @@ -266,16 +296,16 @@ codec_descriptor = wrap_avclass(lib.avcodec_get_class()) def dump_codecs(): """Print information about availible codecs.""" - 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 - ------''' + 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 + ------''') for name in sorted(codecs_available): @@ -292,13 +322,33 @@ def dump_codecs(): # TODO: Assert these always have the same properties. codec = e_codec or d_codec - print ' %s%s%s%s%s%s %-18s %s' % ( + print(' %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], - '.S'[codec.lossless], + '.L'[codec.lossless], + '.H'[bool((d_codec or codec).hardware_configs)], codec.name, codec.long_name - ) + )) + + +def dump_hwconfigs(): + + print('Hardware configs:') + + for name in sorted(codecs_available): + + try: + codec = Codec(name, 'r') + except ValueError: + continue + + configs = codec.hardware_configs + if not configs: + continue + + print(' ', codec.name) + for config in configs: + print(' ', config) diff --git a/av/codec/context.pxd b/av/codec/context.pxd index 446cef8e9dcc2cf3cac9af7b4350d801e3eb56bf..7163fe73c3948936f93608fc213a7ccfdc0ea66e 100644 --- a/av/codec/context.pxd +++ b/av/codec/context.pxd @@ -2,10 +2,11 @@ from libc.stdint cimport int64_t cimport libav as lib +from av.bytesource cimport ByteSource from av.codec.codec cimport Codec +from av.codec.hwaccel cimport HWAccel, HWAccelContext from av.frame cimport Frame from av.packet cimport Packet -from av.bytesource cimport ByteSource cdef class CodecContext(object): @@ -25,10 +26,12 @@ cdef class CodecContext(object): # To hold a reference to passed extradata. cdef ByteSource extradata_source - cdef _init(self, lib.AVCodecContext *ptr, const lib.AVCodec *codec) + cdef _init(self, lib.AVCodecContext *ptr, const lib.AVCodec *codec, HWAccel hwaccel) cdef readonly Codec codec + cdef readonly HWAccelContext hwaccel + cdef public dict options # Public API. @@ -59,10 +62,12 @@ cdef class CodecContext(object): cdef _send_packet_and_recv(self, Packet packet) cdef _recv_frame(self) + cdef _transfer_hwframe(self, Frame frame) + # Implemented by children for the generic send/recv API, so we have the # correct subclass of Frame. cdef Frame _next_frame cdef Frame _alloc_next_frame(self) -cdef CodecContext wrap_codec_context(lib.AVCodecContext*, const lib.AVCodec*, bint allocated) +cdef CodecContext wrap_codec_context(lib.AVCodecContext*, const lib.AVCodec*, bint allocated, HWAccel hwaccel) diff --git a/av/codec/context.pyx b/av/codec/context.pyx index d983829ee7ca2386895f6cb184d29200c9f41efb..7a527254985de1687bdbc7cc55a23d84e00b6acf 100644 --- a/av/codec/context.pyx +++ b/av/codec/context.pyx @@ -7,6 +7,7 @@ cimport libav as lib from av.bytesource cimport ByteSource, bytesource from av.codec.codec cimport Codec, wrap_codec +from av.codec.hwaccel cimport HWAccel from av.dictionary cimport _Dictionary from av.dictionary import Dictionary from av.enums cimport define_enum @@ -18,7 +19,7 @@ from av.utils cimport avdict_to_dict, avrational_to_fraction, to_avrational cdef object _cinit_sentinel = object() -cdef CodecContext wrap_codec_context(lib.AVCodecContext *c_ctx, const lib.AVCodec *c_codec, bint allocated): +cdef CodecContext wrap_codec_context(lib.AVCodecContext *c_ctx, const lib.AVCodec *c_codec, bint allocated, HWAccel hwaccel): """Build an av.CodecContext for an existing AVCodecContext.""" cdef CodecContext py_ctx @@ -37,7 +38,7 @@ cdef CodecContext wrap_codec_context(lib.AVCodecContext *c_ctx, const lib.AVCode py_ctx = CodecContext(_cinit_sentinel) py_ctx.allocated = allocated - py_ctx._init(c_ctx, c_codec) + py_ctx._init(c_ctx, c_codec, hwaccel) return py_ctx @@ -62,10 +63,10 @@ SkipType = define_enum('SkipType', ( cdef class CodecContext(object): @staticmethod - def create(codec, mode=None): + def create(codec, mode=None, hwaccel=None): cdef Codec cy_codec = codec if isinstance(codec, Codec) else Codec(codec, mode) cdef lib.AVCodecContext *c_ctx = lib.avcodec_alloc_context3(cy_codec.ptr) - return wrap_codec_context(c_ctx, cy_codec.ptr, True) + return wrap_codec_context(c_ctx, cy_codec.ptr, True, hwaccel) def __cinit__(self, sentinel=None, *args, **kwargs): if sentinel is not _cinit_sentinel: @@ -74,7 +75,7 @@ cdef class CodecContext(object): self.options = {} self.stream_index = -1 # This is set by the container immediately. - cdef _init(self, lib.AVCodecContext *ptr, const lib.AVCodec *codec): + cdef _init(self, lib.AVCodecContext *ptr, const lib.AVCodec *codec, HWAccel hwaccel): self.ptr = ptr if self.ptr.codec and codec and self.ptr.codec != codec: @@ -301,10 +302,15 @@ cdef class CodecContext(object): return err_check(res) + frame = self._transfer_hwframe(frame) + if not res: self._next_frame = None return frame + cdef _transfer_hwframe(self, Frame frame): + return frame + cdef _recv_packet(self): cdef Packet packet = Packet() diff --git a/av/codec/hwaccel.pxd b/av/codec/hwaccel.pxd new file mode 100644 index 0000000000000000000000000000000000000000..d0c874b3a4580e974e901b9df05bab9ced2bec92 --- /dev/null +++ b/av/codec/hwaccel.pxd @@ -0,0 +1,33 @@ + +cimport libav as lib + +from av.codec.codec cimport Codec + + +cdef class HWConfig(object): + + cdef object __weakref__ + + cdef lib.AVCodecHWConfig *ptr + + cdef void _init(self, lib.AVCodecHWConfig *ptr) + + +cdef HWConfig wrap_hwconfig(lib.AVCodecHWConfig *ptr) + + +cdef class HWAccel(object): + + #cdef lib.AVHWAccel *ptr + + cdef str _device_type + cdef str _device + cdef public dict options + +cdef class HWAccelContext(HWAccel): + + cdef readonly Codec codec + cdef readonly HWConfig config + + cdef lib.AVBufferRef *ptr + diff --git a/av/codec/hwaccel.pyx b/av/codec/hwaccel.pyx new file mode 100644 index 0000000000000000000000000000000000000000..206ee746591dada8826bf60dd753e960b5f34021 --- /dev/null +++ b/av/codec/hwaccel.pyx @@ -0,0 +1,176 @@ +from __future__ import print_function + +import weakref + +cimport libav as lib + +from av.codec.codec cimport Codec +from av.dictionary cimport _Dictionary +from av.enums cimport define_enum +from av.error cimport err_check +from av.video.format cimport get_video_format + +from av.dictionary import Dictionary + + +HWDeviceType = define_enum('HWDeviceType', ( + # ('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), +)) + + +HWConfigMethod = define_enum('HWConfigMethod', ( + ('NONE', 0), + ('HW_DEVICE_CTX', lib.AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX), # This is the only one we support. + ('HW_FRAMES_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), +), is_flags=True, allow_multi_flags=True) + + +cdef object _cinit_sentinel = object() +cdef object _singletons = weakref.WeakValueDictionary() + +cdef HWConfig wrap_hwconfig(lib.AVCodecHWConfig *ptr): + try: + return _singletons[<int>ptr] + except KeyError: + pass + cdef HWConfig config = HWConfig(_cinit_sentinel) + config._init(ptr) + _singletons[<int>ptr] = config + return config + + +cdef class HWConfig(object): + + def __init__(self, sentinel): + if sentinel is not _cinit_sentinel: + raise RuntimeError('Cannot instantiate CodecContext') + + cdef void _init(self, lib.AVCodecHWConfig *ptr): + self.ptr = ptr + + def __repr__(self): + return ( + f'<av.{self.__class__.__name__} ' + f'device={self.device_type} ' + f'format={self.format.name if self.format else None} ' + f'is_supported={self.is_supported} ' + f'at 0x{<int>self.ptr:x}>' + ) + + @property + def device_type(self): + return HWDeviceType.get(self.ptr.device_type) + + @property + def format(self): + return get_video_format(self.ptr.pix_fmt, 0, 0) + + @property + def methods(self): + return HWConfigMethod.get(self.ptr.methods) + + @property + def is_supported(self): + return bool(self.ptr.methods & lib.AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX) + + +hwdevices_availible = set() + +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_availible.add(HWDeviceType.get(x)) + + +def dump_hwdevices(): + print('Hardware devices:') + for x in hwdevices_availible: + print(' ', x) + + +cdef class HWAccel(object): + + @classmethod + def adapt(cls, input_): + if input_ is True: + return cls() + if isinstance(input_, cls): + return input_ + if isinstance(input_, (str, HWDeviceType)): + return cls(input_) + if isinstance(input_, (list, tuple)): + return cls(*input_) + if isinstance(input_, dict): + return cls(**input_) + raise TypeError(f"can't adapt to HWAccel; {input_!r}") + + def __init__(self, device_type=None, device=None, options=None, **kwargs): + + self._device_type = HWDeviceType(device_type) if device_type else None + self._device = device + + 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): + return HWAccelContext(self._device_type, self._device, self.options, codec) + + +cdef class HWAccelContext(HWAccel): + + def __init__(self, device_type=None, device=None, options=None, codec=None, **kwargs): + super().__init__(device_type, device, options, **kwargs) + + if not codec: + raise ValueError("codec is required") + self.codec = codec + + cdef HWConfig config + for config in codec.hardware_configs: + + if not (config.ptr.methods & lib.AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX): + continue + + if self._device_type and config.device_type != self._device_type: + continue + + break + + else: + raise ValueError(f"no supported hardware config for {codec}") + + self.config = config + + cdef char *c_device = NULL + if self._device: + device_bytes = self._device.encode() + c_device = device_bytes + + cdef _Dictionary c_options = Dictionary(self.options) + + err_check(lib.av_hwdevice_ctx_create(&self.ptr, config.ptr.device_type, c_device, c_options.ptr, 0)) + + def __dealloc__(self): + if self.ptr: + lib.av_buffer_unref(&self.ptr) + + def create(self, *args, **kwargs): + raise ValueError("cannot call HWAccelContext.create") + + + diff --git a/av/container/core.pxd b/av/container/core.pxd index 01a9dfa0d0673748199fa0afb90c68e51c6c1bf1..0cb234de6c0b1b6845502139f5043a4d4e32e6f0 100644 --- a/av/container/core.pxd +++ b/av/container/core.pxd @@ -1,5 +1,6 @@ cimport libav as lib +from av.codec.hwaccel cimport HWAccel from av.container.streams cimport StreamContainer from av.dictionary cimport _Dictionary from av.format cimport ContainerFormat @@ -41,6 +42,8 @@ cdef class Container(object): cdef readonly dict container_options cdef readonly list stream_options + cdef HWAccel hwaccel + cdef readonly StreamContainer streams cdef readonly dict metadata diff --git a/av/container/core.pyx b/av/container/core.pyx index 65b20d8b0e73f2b4242767de45ac8353e5570f2b..422392981f59ecf9bb85f8e5c4859ccd3d60ca89 100755 --- a/av/container/core.pyx +++ b/av/container/core.pyx @@ -7,6 +7,7 @@ import time cimport libav as lib +from av.codec.hwaccel cimport HWAccel from av.container.core cimport timeout_info from av.container.input cimport InputContainer from av.container.output cimport OutputContainer @@ -64,7 +65,7 @@ cdef class Container(object): def __cinit__(self, sentinel, file_, format_name, options, container_options, stream_options, metadata_encoding, metadata_errors, - buffer_size, open_timeout, read_timeout): + buffer_size, open_timeout, read_timeout, hwaccel): if sentinel is not _cinit_sentinel: raise RuntimeError('cannot construct base Container') @@ -91,6 +92,8 @@ cdef class Container(object): self.open_timeout = open_timeout self.read_timeout = read_timeout + self.hwaccel = hwaccel + if format_name is not None: self.format = ContainerFormat(format_name) @@ -238,7 +241,7 @@ cdef class Container(object): def open(file, mode=None, format=None, options=None, container_options=None, stream_options=None, metadata_encoding=None, metadata_errors='strict', - buffer_size=32768, timeout=None): + buffer_size=32768, timeout=None, hwaccel=None): """open(file, mode='r', format=None, options=None, metadata_encoding=None, metadata_errors='strict') Main entrypoint to opening files/streams. @@ -259,6 +262,8 @@ def open(file, mode=None, format=None, options=None, :param timeout: How many seconds to wait for data before giving up, as a float, or a :ref:`(open timeout, read timeout) <timeouts>` tuple. :type timeout: float or tuple + :param dict hwaccel: The desired device parameters to use for hardware acceleration + including device_type_name (e.x. cuda) and optional device (e.x. '/dev/dri/renderD128'). For devices (via ``libavdevice``), pass the name of the device to ``format``, e.g.:: @@ -282,12 +287,16 @@ def open(file, mode=None, format=None, options=None, open_timeout = timeout read_timeout = timeout + if hwaccel is not None: + hwaccel = HWAccel.adapt(hwaccel) + if mode.startswith('r'): return InputContainer( _cinit_sentinel, file, format, options, container_options, stream_options, metadata_encoding, metadata_errors, - buffer_size, open_timeout, read_timeout + buffer_size, open_timeout, read_timeout, + hwaccel, ) if mode.startswith('w'): if stream_options: @@ -296,6 +305,7 @@ def open(file, mode=None, format=None, options=None, _cinit_sentinel, file, format, options, container_options, stream_options, metadata_encoding, metadata_errors, - buffer_size, open_timeout, read_timeout + buffer_size, open_timeout, read_timeout, + hwaccel, ) raise ValueError("mode must be 'r' or 'w'; got %r" % mode) diff --git a/av/stream.pyx b/av/stream.pyx index 3a1f13579074d610577ed59413c7bcacd9351256..cb051b5394284c7a1f66a5434feb4bdfc781e6b6 100644 --- a/av/stream.pyx +++ b/av/stream.pyx @@ -85,7 +85,7 @@ cdef class Stream(object): else: self._codec = self._codec_context.codec - self.codec_context = wrap_codec_context(self._codec_context, self._codec, False) + self.codec_context = wrap_codec_context(self._codec_context, self._codec, False, container.hwaccel) self.codec_context.stream_index = stream.index def __repr__(self): diff --git a/av/video/codeccontext.pxd b/av/video/codeccontext.pxd index 9693caa9bb91e111c0f0e1f44ec485665648a7b2..31e7ff187bed28e92d39a50b89cbfe2bdbd3fea0 100644 --- a/av/video/codeccontext.pxd +++ b/av/video/codeccontext.pxd @@ -1,4 +1,6 @@ +cimport libav as lib + from av.codec.context cimport CodecContext from av.video.format cimport VideoFormat from av.video.frame cimport VideoFrame diff --git a/av/video/codeccontext.pyx b/av/video/codeccontext.pyx index 162413302e93df050a5e99c306ff9fa6d2cf038c..caa9e2b5586b566d42e92122f367631be89d6ec3 100644 --- a/av/video/codeccontext.pyx +++ b/av/video/codeccontext.pyx @@ -3,6 +3,7 @@ from libc.stdint cimport int64_t cimport libav as lib from av.codec.context cimport CodecContext +from av.codec.hwaccel cimport HWAccel, HWConfig from av.frame cimport Frame from av.packet cimport Packet from av.utils cimport avrational_to_fraction, to_avrational @@ -12,14 +13,31 @@ from av.video.frame cimport VideoFrame, alloc_video_frame from av.video.reformatter cimport VideoReformatter +cdef lib.AVPixelFormat _get_hw_format(lib.AVCodecContext *ctx, lib.AVPixelFormat *pix_fmts): + i = 0 + while pix_fmts[i] != -1: + if pix_fmts[i] == ctx.pix_fmt: + return pix_fmts[i] + i += 1 + + return lib.AV_PIX_FMT_NONE + + cdef class VideoCodecContext(CodecContext): def __cinit__(self, *args, **kwargs): self.last_w = 0 self.last_h = 0 - cdef _init(self, lib.AVCodecContext *ptr, const lib.AVCodec *codec): - CodecContext._init(self, ptr, codec) # TODO: Can this be `super`? + cdef _init(self, lib.AVCodecContext *ptr, const lib.AVCodec *codec, HWAccel hwaccel): + CodecContext._init(self, ptr, codec, hwaccel) # TODO: Can this be `super`? + + if hwaccel is not None: + self.hwaccel = hwaccel.create(self.codec) + self.ptr.hw_device_ctx = lib.av_buffer_ref(self.hwaccel.ptr) + self.ptr.pix_fmt = self.hwaccel.config.ptr.pix_fmt + self.ptr.get_format = _get_hw_format + self._build_format() self.encoded_frame_count = 0 @@ -68,6 +86,24 @@ cdef class VideoCodecContext(CodecContext): cdef VideoFrame vframe = frame vframe._init_user_attributes() + cdef _transfer_hwframe(self, Frame frame): + cdef Frame frame_sw + + # TODO: What is up with the format check?! + # retrieve data from GPU to CPU + if self.hwaccel is not None and frame.ptr.format == self.hwaccel.config.ptr.pix_fmt: + frame_sw = self._alloc_next_frame() + + err_check(lib.av_hwframe_transfer_data(frame_sw.ptr, frame.ptr, 0)) + + # TODO: Is there anything else to transfer?! + frame_sw.pts = frame.pts + + return frame_sw + + else: + return frame + cdef _build_format(self): self._format = get_video_format(<lib.AVPixelFormat>self.ptr.pix_fmt, self.ptr.width, self.ptr.height) diff --git a/include/libav.pxd b/include/libav.pxd index 5598ce143e5e1cacaa4854a5743bb844a17db4f0..6da830d5c30eade14641a6fa59f88299097930fe 100644 --- a/include/libav.pxd +++ b/include/libav.pxd @@ -12,8 +12,12 @@ include "libavutil/dict.pxd" include "libavutil/error.pxd" include "libavutil/frame.pxd" include "libavutil/samplefmt.pxd" +include "libavutil/buffer.pxd" +include "libavutil/hwcontext.pxd" include "libavcodec/avcodec.pxd" +include "libavcodec/hwaccel.pxd" + include "libavdevice/avdevice.pxd" include "libavformat/avformat.pxd" include "libswresample/swresample.pxd" diff --git a/include/libavcodec/avcodec.pxd b/include/libavcodec/avcodec.pxd index 99cb27e5cfc4445c845a43b2556db7399f2be098..7f99a764d924e0b28cc162dbd18baba752940700 100644 --- a/include/libavcodec/avcodec.pxd +++ b/include/libavcodec/avcodec.pxd @@ -176,6 +176,11 @@ cdef extern from "libavcodec/avcodec.pyav.h" nogil: int get_buffer(AVCodecContext *ctx, AVFrame *frame) void release_buffer(AVCodecContext *ctx, AVFrame *frame) + # Hardware acceleration + AVHWAccel *hwaccel + AVBufferRef *hw_device_ctx + AVPixelFormat (*get_format)(AVCodecContext *s, const AVPixelFormat * fmt) + # User Data void *opaque diff --git a/include/libavcodec/hwaccel.pxd b/include/libavcodec/hwaccel.pxd new file mode 100644 index 0000000000000000000000000000000000000000..ab1290c7b50a483adea4c37e5234279f13818fbd --- /dev/null +++ b/include/libavcodec/hwaccel.pxd @@ -0,0 +1,24 @@ +cdef extern from "libavcodec/avcodec.h" nogil: + + cdef enum: + AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX, + AV_CODEC_HW_CONFIG_METHOD_HW_FRAMES_CTX, + AV_CODEC_HW_CONFIG_METHOD_INTERNAL, + AV_CODEC_HW_CONFIG_METHOD_AD_HOC, + + cdef struct AVCodecHWConfig: + AVPixelFormat pix_fmt + int methods + AVHWDeviceType device_type + + cdef const AVCodecHWConfig* avcodec_get_hw_config(const AVCodec *codec, int index) + + cdef enum: + AV_HWACCEL_CODEC_CAP_EXPERIMENTAL + + cdef struct AVHWAccel: + char *name + AVMediaType type + AVCodecID id + AVPixelFormat pix_fmt + int capabilities diff --git a/include/libavutil/buffer.pxd b/include/libavutil/buffer.pxd new file mode 100644 index 0000000000000000000000000000000000000000..5445b563f8505576061d241bf9ea92ff7c8aaa43 --- /dev/null +++ b/include/libavutil/buffer.pxd @@ -0,0 +1,21 @@ +from libc.stdint cimport intptr_t, uint8_t + + +cdef extern from "libavutil/buffer.h" nogil: + + cdef struct AVBuffer: + uint8_t *data + int size + intptr_t refcount + void (*free)(void *opaque, uint8_t *data) + void *opaque + int flags + + cdef struct AVBufferRef: + AVBuffer *buffer + uint8_t *data + int size + + + cdef AVBufferRef* av_buffer_ref(AVBufferRef *buf) + cdef void av_buffer_unref(AVBufferRef **buf) diff --git a/include/libavutil/hwcontext.pxd b/include/libavutil/hwcontext.pxd new file mode 100644 index 0000000000000000000000000000000000000000..fb76f6dfd71fdce7f9489bb68ab20e9219ce7708 --- /dev/null +++ b/include/libavutil/hwcontext.pxd @@ -0,0 +1,24 @@ +cdef extern from "libavutil/hwcontext.h" nogil: + + enum AVHWDeviceType: + AV_HWDEVICE_TYPE_NONE + AV_HWDEVICE_TYPE_VDPAU + AV_HWDEVICE_TYPE_CUDA + AV_HWDEVICE_TYPE_VAAPI + AV_HWDEVICE_TYPE_DXVA2 + AV_HWDEVICE_TYPE_QSV + AV_HWDEVICE_TYPE_VIDEOTOOLBOX + AV_HWDEVICE_TYPE_D3D11VA + AV_HWDEVICE_TYPE_DRM + AV_HWDEVICE_TYPE_OPENCL + AV_HWDEVICE_TYPE_MEDIACODEC + + cdef AVHWDeviceType av_hwdevice_iterate_types(AVHWDeviceType prev) + + cdef int av_hwdevice_ctx_create(AVBufferRef **device_ctx, AVHWDeviceType type, const char *device, AVDictionary *opts, int flags) + + cdef AVHWDeviceType av_hwdevice_find_type_by_name(const char *name) + cdef const char *av_hwdevice_get_type_name(AVHWDeviceType type) + + cdef int av_hwframe_transfer_data(AVFrame *dst, const AVFrame *src, int flags) + diff --git a/scratchpad/decode.py b/scratchpad/decode.py index 0dfbf2df931feb5a0a9b7c5d685a9852ae8b5acf..398a542be801621a16c56ce2ce542937dd9134fc 100644 --- a/scratchpad/decode.py +++ b/scratchpad/decode.py @@ -27,6 +27,7 @@ arg_parser.add_argument('-a', '--audio', action='store_true') arg_parser.add_argument('-v', '--video', action='store_true') arg_parser.add_argument('-s', '--subs', action='store_true') arg_parser.add_argument('-d', '--data', action='store_true') +arg_parser.add_argument('-H', '--hwaccel', action='store_true') arg_parser.add_argument('--dump-packets', action='store_true') arg_parser.add_argument('--dump-planes', action='store_true') arg_parser.add_argument('-p', '--play', action='store_true') @@ -39,7 +40,7 @@ args = arg_parser.parse_args() proc = None options = dict(x.split('=') for x in args.option) -container = open(args.path, format=args.format, options=options) +container = open(args.path, format=args.format, options=options, hwaccel=args.hwaccel or None) print('container:', container) print('\tformat:', container.format) diff --git a/scripts/build-deps b/scripts/build-deps index a33ce197d255630c608ce3b663cc62b670d54413..bc650169584a14891482e6792ed0d1f41320913c 100755 --- a/scripts/build-deps +++ b/scripts/build-deps @@ -25,6 +25,32 @@ fi mkdir -p "$PYAV_LIBRARY_ROOT" mkdir -p "$PYAV_LIBRARY_PREFIX" + +# Nvidia build +CONFFLAGS_NVIDIA="" +if [[ -e /usr/local/cuda ]]; then + # Get Nvidia headers for ffmpeg + cd $PYAV_LIBRARY_ROOT + if [[ ! -e "$PYAV_LIBRARY_ROOT/nv-codec-headers" ]]; then + git clone https://github.com/FFmpeg/nv-codec-headers.git + cd nv-codec-headers + make -j4 + make PREFIX="$PYAV_LIBRARY_PREFIX" install + fi + + PKG_CONFIG_PATH="/usr/local/lib/pkgconfig:$PKG_CONFIG_PATH" + CONFFLAGS_NVIDIA="--enable-cuda \ + --enable-cuvid \ + --enable-nvenc \ + --enable-nonfree \ + --enable-libnpp \ + --extra-cflags=-I/usr/local/cuda/include \ + --extra-ldflags=-L/usr/local/cuda/lib64" +else + echo "WARNING: Did not find cuda libraries in /usr/local/cuda..." + echo " Building without hardware acceleration support" +fi + cd "$PYAV_LIBRARY_ROOT" @@ -52,6 +78,7 @@ fi --enable-debug=3 \ --enable-gpl \ --enable-libx264 \ + $CONFFLAGS_NVIDIA \ $CONFFLAGS \ --prefix="$PYAV_LIBRARY_PREFIX" \ || exit 2