Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ v18.1.0 (Unreleased)

Features:

- Add ``CodecContext.supported_options`` to discover generic and codec-specific options by :gh-user:`WyattBlue` (:issue:`2032`).
- Add ``Packet.rescale_ts()`` to rescale packet PTS, DTS, and duration to a new ``AVRational`` time base by :gh-user:`WyattBlue`.
- Support reusing the thread's current CUDA context via a ``current_ctx`` flag on ``CudaContext`` and ``VideoFrame.from_dlpack``, for interop with libraries like PyTorch that initialize CUDA first by :gh-user:`Yozer` (:pr:`2339`).
- ``VideoFrame.from_dlpack`` no longer requires restating ``primary_ctx``/``current_ctx`` when passing an explicit ``cuda_context``; the flags are only validated when explicitly given by :gh-user:`WyattBlue`.
Expand Down
1 change: 1 addition & 0 deletions av/codec/context.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ cdef class CodecContext:
# Wraps both versions of the transcode API, returning lists.
cpdef encode(self, Frame frame=?)
cpdef decode(self, Packet packet=?)
cdef _decode(self, Packet packet)
cpdef flush_buffers(self)

# Used by hardware-accelerated decode.
Expand Down
170 changes: 168 additions & 2 deletions av/codec/context.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from enum import Flag, IntEnum
from dataclasses import dataclass
from enum import Flag, IntEnum, IntFlag

import cython
from cython.cimports import libav as lib
Expand All @@ -10,7 +11,7 @@
from cython.cimports.av.utils import avrational_to_fraction, to_avrational
from cython.cimports.libc.errno import EAGAIN
from cython.cimports.libc.stdint import uint8_t
from cython.cimports.libc.string import memcpy
from cython.cimports.libc.string import memcpy, strcmp

from av.error import InvalidDataError

Expand Down Expand Up @@ -90,6 +91,142 @@ class Flags2(IntEnum):
ro_flush_noop = lib.AV_CODEC_FLAG2_RO_FLUSH_NOOP


class OptionType(IntEnum):
FLAGS = lib.AV_OPT_TYPE_FLAGS
INT = lib.AV_OPT_TYPE_INT
INT64 = lib.AV_OPT_TYPE_INT64
DOUBLE = lib.AV_OPT_TYPE_DOUBLE
FLOAT = lib.AV_OPT_TYPE_FLOAT
STRING = lib.AV_OPT_TYPE_STRING
RATIONAL = lib.AV_OPT_TYPE_RATIONAL
BINARY = lib.AV_OPT_TYPE_BINARY
DICT = lib.AV_OPT_TYPE_DICT
UINT64 = lib.AV_OPT_TYPE_UINT64
CONST = lib.AV_OPT_TYPE_CONST
IMAGE_SIZE = lib.AV_OPT_TYPE_IMAGE_SIZE
PIXEL_FMT = lib.AV_OPT_TYPE_PIXEL_FMT
SAMPLE_FMT = lib.AV_OPT_TYPE_SAMPLE_FMT
VIDEO_RATE = lib.AV_OPT_TYPE_VIDEO_RATE
DURATION = lib.AV_OPT_TYPE_DURATION
COLOR = lib.AV_OPT_TYPE_COLOR
CHANNEL_LAYOUT = lib.AV_OPT_TYPE_CHLAYOUT
BOOL = lib.AV_OPT_TYPE_BOOL
UINT = lib.AV_OPT_TYPE_UINT


class OptionFlags(IntFlag):
ENCODING_PARAM = lib.AV_OPT_FLAG_ENCODING_PARAM
DECODING_PARAM = lib.AV_OPT_FLAG_DECODING_PARAM
AUDIO_PARAM = lib.AV_OPT_FLAG_AUDIO_PARAM
VIDEO_PARAM = lib.AV_OPT_FLAG_VIDEO_PARAM
SUBTITLE_PARAM = lib.AV_OPT_FLAG_SUBTITLE_PARAM
EXPORT = lib.AV_OPT_FLAG_EXPORT
READONLY = lib.AV_OPT_FLAG_READONLY
BITSTREAM_FILTER_PARAM = lib.AV_OPT_FLAG_BSF_PARAM
RUNTIME_PARAM = lib.AV_OPT_FLAG_RUNTIME_PARAM
FILTERING_PARAM = lib.AV_OPT_FLAG_FILTERING_PARAM
DEPRECATED = lib.AV_OPT_FLAG_DEPRECATED
CHILD_CONSTS = lib.AV_OPT_FLAG_CHILD_CONSTS


@dataclass(frozen=True, slots=True)
class CodecOptionChoice:
"""A named value accepted by a codec option."""

name: str
help: str


@dataclass(frozen=True, slots=True)
class CodecOption:
"""Description of a generic or codec-specific option."""

name: str
help: str
type: OptionType | int
is_array: bool
default: str | None
min: float
max: float
flags: OptionFlags
choices: tuple[CodecOptionChoice, ...]


@dataclass(frozen=True, slots=True)
class CodecOptionSet:
"""Generic and codec-specific options supported by a codec context."""

generic: tuple[CodecOption, ...]
private: tuple[CodecOption, ...]


@cython.cfunc
def _get_option_default(
obj: cython.p_void, name: cython.pointer[cython.const[cython.char]]
):
value: cython.pointer[uint8_t] = cython.NULL
if lib.av_opt_get(obj, name, 0, cython.address(value)) < 0:
return None
try:
return cython.cast(cython.p_char, value) if value != cython.NULL else None
finally:
lib.av_free(value)


@cython.cfunc
def _get_supported_options(obj: cython.p_void):
options: list = []
ptr: cython.pointer[cython.const[lib.AVOption]] = lib.av_opt_next(obj, cython.NULL)
choice_ptr: cython.pointer[cython.const[lib.AVOption]]
option_type: object

while ptr != cython.NULL:
if ptr.type != lib.AV_OPT_TYPE_CONST:
choices: list = []
if ptr.unit != cython.NULL:
choice_ptr = lib.av_opt_next(obj, cython.NULL)
while choice_ptr != cython.NULL:
if (
choice_ptr.type == lib.AV_OPT_TYPE_CONST
and choice_ptr.unit != cython.NULL
and strcmp(choice_ptr.unit, ptr.unit) == 0
):
choices.append(
CodecOptionChoice(
choice_ptr.name,
choice_ptr.help
if choice_ptr.help != cython.NULL
else "",
)
)
choice_ptr = lib.av_opt_next(obj, choice_ptr)

raw_type = cython.cast(cython.int, ptr.type)
is_array = bool(raw_type & lib.AV_OPT_TYPE_FLAG_ARRAY)
raw_type &= ~lib.AV_OPT_TYPE_FLAG_ARRAY
try:
option_type = OptionType(raw_type)
except ValueError:
option_type = raw_type

options.append(
CodecOption(
ptr.name,
ptr.help if ptr.help != cython.NULL else "",
option_type,
is_array,
_get_option_default(obj, ptr.name),
ptr.min,
ptr.max,
OptionFlags(ptr.flags),
tuple(choices),
)
)
ptr = lib.av_opt_next(obj, ptr)

return tuple(options)


@cython.cclass
class CodecContext:
@staticmethod
Expand All @@ -108,6 +245,31 @@ def __cinit__(self, sentinel=None, *args, **kwargs):
self.stream_index = -1 # This is set by the container immediately.
self.is_open = False

@property
def supported_options(self):
"""Options supported by this codec context.

``generic`` contains options provided by :ffmpeg:`AVCodecContext`, while
``private`` contains options provided by the selected codec. Values are
descriptors only; set options through :attr:`options`.
"""
ctx: cython.pointer[lib.AVCodecContext] = lib.avcodec_alloc_context3(
self.codec.ptr
)
child: cython.p_void
private: list = []
if ctx == cython.NULL:
raise MemoryError("Cannot allocate codec context")
try:
generic = _get_supported_options(ctx)
child = lib.av_opt_child_next(ctx, cython.NULL)
while child != cython.NULL:
private.extend(_get_supported_options(child))
child = lib.av_opt_child_next(ctx, child)
return CodecOptionSet(generic, tuple(private))
finally:
lib.avcodec_free_context(cython.address(ctx))

@cython.cfunc
def _init(
self,
Expand Down Expand Up @@ -555,6 +717,10 @@ def decode(self, packet: Packet | None = None):
from multiple threads, give each thread its own :class:`CodecContext`.

"""
return self._decode(packet)

@cython.cfunc
def _decode(self, packet: Packet | None):
if not self.codec.ptr:
raise ValueError("cannot decode unknown codec")

Expand Down
63 changes: 62 additions & 1 deletion av/codec/context.pyi
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from enum import Flag, IntEnum
from dataclasses import dataclass
from enum import Flag, IntEnum, IntFlag
from fractions import Fraction
from typing import ClassVar, Literal, cast, overload

Expand Down Expand Up @@ -52,10 +53,70 @@ class Flags2(IntEnum):
skip_manual = cast(int, ...)
ro_flush_noop = cast(int, ...)

class OptionType(IntEnum):
FLAGS = cast(int, ...)
INT = cast(int, ...)
INT64 = cast(int, ...)
DOUBLE = cast(int, ...)
FLOAT = cast(int, ...)
STRING = cast(int, ...)
RATIONAL = cast(int, ...)
BINARY = cast(int, ...)
DICT = cast(int, ...)
UINT64 = cast(int, ...)
CONST = cast(int, ...)
IMAGE_SIZE = cast(int, ...)
PIXEL_FMT = cast(int, ...)
SAMPLE_FMT = cast(int, ...)
VIDEO_RATE = cast(int, ...)
DURATION = cast(int, ...)
COLOR = cast(int, ...)
CHANNEL_LAYOUT = cast(int, ...)
BOOL = cast(int, ...)
UINT = cast(int, ...)

class OptionFlags(IntFlag):
ENCODING_PARAM = cast(int, ...)
DECODING_PARAM = cast(int, ...)
AUDIO_PARAM = cast(int, ...)
VIDEO_PARAM = cast(int, ...)
SUBTITLE_PARAM = cast(int, ...)
EXPORT = cast(int, ...)
READONLY = cast(int, ...)
BITSTREAM_FILTER_PARAM = cast(int, ...)
RUNTIME_PARAM = cast(int, ...)
FILTERING_PARAM = cast(int, ...)
DEPRECATED = cast(int, ...)
CHILD_CONSTS = cast(int, ...)

@dataclass(frozen=True, slots=True)
class CodecOptionChoice:
name: str
help: str

@dataclass(frozen=True, slots=True)
class CodecOption:
name: str
help: str
type: OptionType | int
is_array: bool
default: str | None
min: float
max: float
flags: OptionFlags
choices: tuple[CodecOptionChoice, ...]

@dataclass(frozen=True, slots=True)
class CodecOptionSet:
generic: tuple[CodecOption, ...]
private: tuple[CodecOption, ...]

class CodecContext:
name: str
type: Literal["video", "audio", "data", "subtitle", "attachment"]
options: dict[str, str]
@property
def supported_options(self) -> CodecOptionSet: ...
profile: str | None
level: int
@property
Expand Down
2 changes: 1 addition & 1 deletion av/subtitles/codeccontext.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ from av.packet cimport Packet

cdef class SubtitleCodecContext(CodecContext):
cdef bint subtitle_header_set
cpdef decode(self, Packet packet=?)
cdef _decode(self, Packet packet)
cpdef decode2(self, Packet packet)
4 changes: 2 additions & 2 deletions av/subtitles/codeccontext.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,8 @@ def encode_subtitle(self, subtitle: SubtitleSet) -> Packet:

return packet

@cython.ccall
def decode(self, packet: Packet | None = None):
@cython.cfunc
def _decode(self, packet: Packet | None):
"""Decode a subtitle packet, returning a list of :class:`.Subtitle` objects
if a subtitle was decoded, or an empty list otherwise."""
if not self.codec.ptr:
Expand Down
8 changes: 7 additions & 1 deletion docs/api/codec.rst
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,13 @@ Contexts

.. autoattribute:: CodecContext.codec
.. autoattribute:: CodecContext.options
.. autoattribute:: CodecContext.supported_options

.. autoclass:: CodecOptionSet
.. autoclass:: CodecOption
.. autoclass:: CodecOptionChoice
.. autoclass:: OptionType
.. autoclass:: OptionFlags

.. automethod:: CodecContext.create
.. automethod:: CodecContext.open
Expand Down Expand Up @@ -161,4 +168,3 @@ frames passed to ``encode`` are uploaded to the device automatically::
See ``examples/basics/hw_decode.py`` for a complete example, including
recommended device types per platform.


12 changes: 12 additions & 0 deletions include/avutil.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,8 @@ cdef extern from "libavutil/opt.h" nogil:
AV_OPT_TYPE_COLOR
AV_OPT_TYPE_CHLAYOUT
AV_OPT_TYPE_BOOL
AV_OPT_TYPE_UINT
AV_OPT_TYPE_FLAG_ARRAY

cdef union AVOption_default_val:
int64_t i64
Expand All @@ -320,7 +322,11 @@ cdef extern from "libavutil/opt.h" nogil:
AV_OPT_FLAG_SUBTITLE_PARAM
AV_OPT_FLAG_EXPORT
AV_OPT_FLAG_READONLY
AV_OPT_FLAG_BSF_PARAM
AV_OPT_FLAG_RUNTIME_PARAM
AV_OPT_FLAG_FILTERING_PARAM
AV_OPT_FLAG_DEPRECATED
AV_OPT_FLAG_CHILD_CONSTS

cdef struct AVOption:
const char *name
Expand All @@ -333,6 +339,12 @@ cdef extern from "libavutil/opt.h" nogil:
int flags
const char *unit

cdef const AVOption *av_opt_next(const void *obj, const AVOption *prev)
cdef void *av_opt_child_next(void *obj, void *prev)
cdef int av_opt_get(
void *obj, const char *name, int search_flags, uint8_t **out_val
)

cdef extern from "libavutil/pixdesc.h" nogil:
# See: http://ffmpeg.org/doxygen/trunk/structAVComponentDescriptor.html
cdef struct AVComponentDescriptor:
Expand Down
8 changes: 0 additions & 8 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,12 +141,6 @@ def parse_cflags(raw_flags):

IMPORT_NAME = "av"

# Newer compilers treat incompatible pointer types as an error by default;
# downgrade it back to a warning so FFmpeg API churn doesn't break the build.
extra_compile_args = []
if platform.system() != "Windows":
extra_compile_args.append("-Wno-error=incompatible-pointer-types")

loudnorm_extension = Extension(
f"{IMPORT_NAME}.filter.loudnorm",
sources=[
Expand All @@ -157,7 +151,6 @@ def parse_cflags(raw_flags):
libraries=extension_extra["libraries"],
library_dirs=extension_extra["library_dirs"],
define_macros=define_macros,
extra_compile_args=extra_compile_args,
py_limited_api=py_limited_api,
)

Expand Down Expand Up @@ -204,7 +197,6 @@ def parse_cflags(raw_flags):
library_dirs=extension_extra["library_dirs"],
sources=[pyx_path],
define_macros=define_macros,
extra_compile_args=extra_compile_args,
py_limited_api=py_limited_api,
),
compiler_directives=compiler_directives,
Expand Down
Loading
Loading