Skip to content

Commit a965bda

Browse files
committed
added packet side-data handler
1 parent 5f33896 commit a965bda

File tree

4 files changed

+316
-1
lines changed

4 files changed

+316
-1
lines changed

av/packet.pxd

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ from av.buffer cimport Buffer
44
from av.bytesource cimport ByteSource
55
from av.stream cimport Stream
66

7+
from cython.cimports.libc.stdint import uint8_t
8+
9+
cdef class PacketSideData:
10+
cdef uint8_t *data
11+
cdef size_t size
12+
cdef lib.AVPacketSideDataType dtype
713

814
cdef class Packet(Buffer):
915
cdef lib.AVPacket* ptr
@@ -13,3 +19,7 @@ cdef class Packet(Buffer):
1319
cdef ByteSource source
1420
cdef size_t _buffer_size(self)
1521
cdef void* _buffer_ptr(self)
22+
23+
cdef PacketSideData _packet_side_data_from_packet(
24+
lib.AVPacket* packet, lib.AVPacketSideDataType data_type
25+
)

av/packet.py

Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,203 @@
44
from cython.cimports.av.error import err_check
55
from cython.cimports.av.opaque import opaque_container
66
from cython.cimports.av.utils import avrational_to_fraction, to_avrational
7+
from cython.cimports.av.error import err_check
8+
from cython.cimports.libc.string import memcpy
9+
10+
from typing import Literal, get_args, Iterator
11+
12+
# check https://github.com/FFmpeg/FFmpeg/blob/master/libavcodec/packet.h#L41 for
13+
# new additions in the future FFmpeg releases
14+
# note: the order must follow that of the AVPacketSideDataType enum def
15+
PacketSideDataTypeLiteral = Literal[
16+
"palette",
17+
"new_extradata",
18+
"param_change",
19+
"h263_mb_info",
20+
"replay_gain",
21+
"display_matrix",
22+
"stereo_3d",
23+
"audio_service_type",
24+
"quality_stats",
25+
"fallback_track",
26+
"cpb_properties",
27+
"skip_samples",
28+
"jp_dual_mono",
29+
"strings_metadata",
30+
"subtitle_position",
31+
"matroska_block_additional",
32+
"webvtt_identifier",
33+
"webvtt_settings",
34+
"metadata_update",
35+
"mpegts_stream_id",
36+
"mastering_display_metadata",
37+
"spherical",
38+
"content_light_level",
39+
"a53_cc",
40+
"encryption_init_info",
41+
"encryption_info",
42+
"afd",
43+
"prft",
44+
"icc_profile",
45+
"dovi_conf",
46+
"s12m_timecode",
47+
"dynamic_hdr10_plus",
48+
"iamf_mix_gain_param",
49+
"iamf_info_param",
50+
"iamf_recon_gain_info_param",
51+
"ambient_viewing_environment",
52+
"frame_cropping",
53+
"lcevc",
54+
"3d_reference_displays",
55+
"rtcp_sr",
56+
]
57+
58+
59+
def _packet_side_data_type_to_literal(
60+
data_type: lib.AVPacketSideDataType,
61+
) -> PacketSideDataTypeLiteral:
62+
63+
return get_args(PacketSideDataTypeLiteral)[cython.cast(int, data_type)]
64+
65+
66+
def _packet_side_data_type_from_literal(
67+
data_type: PacketSideDataTypeLiteral,
68+
) -> lib.AVPacketSideDataType:
69+
70+
i = cython.declare(
71+
cython.int, get_args(PacketSideDataTypeLiteral).index(data_type)
72+
)
73+
74+
return cython.cast(lib.AVPacketSideDataType, i)
75+
76+
77+
def packet_side_data_type_desc(data_type: PacketSideDataTypeLiteral) -> str | None:
78+
"""FFmpeg description of packet side data type"""
79+
dtype = _packet_side_data_type_from_literal(data_type)
80+
res = lib.av_packet_side_data_name(dtype)
81+
return res if res != cython.NULL else None
82+
83+
84+
@cython.cclass
85+
class PacketSideData:
86+
87+
@staticmethod
88+
def from_packet(
89+
packet: "Packet", data_type: PacketSideDataTypeLiteral
90+
) -> "PacketSideData":
91+
"""create new PacketSideData by copying an existing packet's side data
92+
93+
:param packet: Source packet
94+
:type packet: :class:`~av.packet.Packet`
95+
:param data_type: side data type
96+
:return: newly created copy of the side data if the side data of the
97+
requested type is found in the packet, else an empty object
98+
:rtype: :class:`~av.packet.PacketSideData`
99+
"""
100+
101+
dtype = _packet_side_data_type_from_literal(data_type)
102+
return _packet_side_data_from_packet(packet.ptr, dtype)
103+
104+
def __cinit__(self, dtype: lib.AVPacketSideDataType, size: cython.size_t):
105+
self.dtype = dtype
106+
with cython.nogil:
107+
if size:
108+
self.data = cython.cast(cython.p_uchar, lib.av_malloc(size))
109+
if self.data == cython.NULL:
110+
raise MemoryError("Failed to allocate memory")
111+
else:
112+
self.data = cython.NULL
113+
self.size = size
114+
115+
def __dealloc__(self):
116+
with cython.nogil:
117+
lib.av_freep(cython.address(self.data))
118+
119+
def to_packet(self, packet: "Packet", move: cython.bint = False):
120+
"""copy or move side data to the specified packet
121+
122+
:param packet: Target packet
123+
:type packet: :class:`~av.packet.Packet`
124+
:param move: True to move the data from this object to the packet,
125+
defaults to False.
126+
:type move: bool
127+
"""
128+
if self.size == 0:
129+
# nothing to add, should clear existing side_data in packet?
130+
return
131+
132+
data = self.data
133+
134+
with cython.nogil:
135+
if not move:
136+
data = cython.cast(cython.p_uchar, lib.av_malloc(self.size))
137+
if data == cython.NULL:
138+
raise MemoryError("Failed to allocate memory")
139+
memcpy(data, self.data, self.size)
140+
141+
res = lib.av_packet_add_side_data(packet.ptr, self.dtype, data, self.size)
142+
err_check(res)
143+
144+
if move:
145+
self.data = cython.NULL
146+
self.size = 0
147+
148+
@property
149+
def data_type(self) -> str:
150+
"""
151+
The type of this packet side data.
152+
153+
:type: str
154+
"""
155+
return _packet_side_data_type_to_literal(self.dtype)
156+
157+
@property
158+
def data_desc(self) -> str:
159+
"""
160+
The description of this packet side data type.
161+
162+
:type: str
163+
"""
164+
165+
return lib.av_packet_side_data_name(self.dtype)
166+
167+
@property
168+
def data_size(self) -> int:
169+
"""
170+
The size in bytes of this packet side data.
171+
172+
:type: int
173+
"""
174+
return self.size
175+
176+
def __bool__(self) -> bool:
177+
"""
178+
True if this object holds side data.
179+
180+
:type: bool
181+
"""
182+
return self.data != cython.NULL
183+
184+
185+
@cython.cfunc
186+
def _packet_side_data_from_packet(
187+
packet: cython.pointer[lib.AVPacket], data_type: lib.AVPacketSideDataType
188+
) -> PacketSideData:
189+
190+
with cython.nogil:
191+
c_ptr = lib.av_packet_side_data_get(
192+
packet.side_data, packet.side_data_elems, data_type
193+
)
194+
195+
found = cython.declare(cython.bint, c_ptr != cython.NULL)
196+
197+
sdata = PacketSideData(data_type, c_ptr.size if found else 0)
198+
199+
with cython.nogil:
200+
if found:
201+
memcpy(sdata.data, c_ptr.data, c_ptr.size)
202+
203+
return sdata
7204

8205

9206
@cython.cclass
@@ -235,3 +432,49 @@ def opaque(self, v):
235432
if v is None:
236433
return
237434
self.ptr.opaque_ref = opaque_container.add(v)
435+
436+
def has_side_data(self, data_type: str) -> bool:
437+
"""True if this packet has the specified side data
438+
439+
:param data_type: side data type
440+
:type data_type: str
441+
"""
442+
443+
dtype = _packet_side_data_type_from_literal(data_type)
444+
return (
445+
lib.av_packet_side_data_get(
446+
self.ptr.side_data, self.ptr.side_data_elems, dtype
447+
)
448+
!= cython.NULL
449+
)
450+
451+
def get_side_data(self, data_type: str) -> PacketSideData:
452+
"""get a copy of the side data
453+
454+
:param data_type: side data type (:method:`~av.packet.PacketSideData.side_data_types` for the full list of options)
455+
:type data_type: str
456+
:return: newly created copy of the side data if the side data of the
457+
requested type is found in the packet, else an empty object
458+
:rtype: :class:`~av.packet.PacketSideData`
459+
"""
460+
return PacketSideData.from_packet(self, data_type)
461+
462+
def set_side_data(self, side_data: PacketSideData, move: bool = False):
463+
"""copy or move side data to this packet
464+
465+
:param side_data: Source packet side data
466+
:type side_data: :class:`~av.packet.PacketSideData`
467+
:param move: True to move the data from `side_data` object,
468+
defaults to False.
469+
:type move: bool
470+
"""
471+
side_data.to_packet(self, move)
472+
473+
def iter_side_data(self) -> Iterator[PacketSideData]:
474+
"""iterate over side data of this packet.
475+
476+
:yield: :class:`~av.packet.PacketSideData` object
477+
"""
478+
479+
for i in range(self.ptr.side_data_elems):
480+
yield _packet_side_data_from_packet(self.ptr, self.ptr.side_data[i].type)

include/libavcodec/avcodec.pxd

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from libc.stdint cimport int8_t, int64_t, uint16_t, uint32_t
1+
from libc.stdint cimport int8_t, int64_t, uint16_t, uint32_t, uint8_t
22

33
cdef extern from "libavcodec/codec.h":
44
struct AVCodecTag:
@@ -17,6 +17,17 @@ cdef extern from "libavcodec/packet.h" nogil:
1717
int free_opaque
1818
)
1919

20+
const AVPacketSideData *av_packet_side_data_get(const AVPacketSideData *sd,
21+
int nb_sd,
22+
AVPacketSideDataType type)
23+
24+
uint8_t* av_packet_get_side_data(const AVPacket *pkt, AVPacketSideDataType type,
25+
size_t *size)
26+
27+
int av_packet_add_side_data(AVPacket *pkt, AVPacketSideDataType type,
28+
uint8_t *data, size_t size)
29+
30+
const char *av_packet_side_data_name(AVPacketSideDataType type)
2031

2132
cdef extern from "libavutil/channel_layout.h":
2233
ctypedef enum AVChannelOrder:
@@ -469,6 +480,8 @@ cdef extern from "libavcodec/avcodec.h" nogil:
469480
int size
470481
int stream_index
471482
int flags
483+
AVPacketSideData *side_data
484+
int side_data_elems
472485
int duration
473486
int64_t pos
474487
void *opaque

tests/test_packet.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
from .common import fate_suite
44

5+
from typing import get_args
6+
57

68
class TestProperties:
79
def test_is_keyframe(self) -> None:
@@ -48,3 +50,50 @@ def test_set_duration(self) -> None:
4850
packet.duration += 10
4951

5052
assert packet.duration == old_duration + 10
53+
54+
55+
class TestPacketSideData:
56+
57+
def test_data_types(self):
58+
dtypes = get_args(av.packet.PacketSideDataTypeLiteral)
59+
ffmpeg_ver = [int(v) for v in av.ffmpeg_version_info.split(".", 2)[:2]]
60+
for dtype in dtypes:
61+
av_enum = av.packet._packet_side_data_type_from_literal(dtype)
62+
assert dtype == av.packet._packet_side_data_type_to_literal(av_enum)
63+
assert av.packet.packet_side_data_type_desc(dtype) is not None
64+
65+
if (ffmpeg_ver[0] < 8 and dtype == "lcevc") or (
66+
ffmpeg_ver[0] < 9 and dtype == "rtcp_sr"
67+
):
68+
break
69+
70+
def test_iter(self):
71+
with av.open(fate_suite("h264/extradata-reload-multi-stsd.mov")) as container:
72+
for pkt in container.demux():
73+
for sdata in pkt.iter_side_data():
74+
assert pkt.dts == 2 and sdata.data_type == "new_extradata"
75+
76+
def test_palette(self):
77+
78+
with av.open(fate_suite("h264/extradata-reload-multi-stsd.mov")) as container:
79+
80+
iterpackets = container.demux()
81+
pkt = next(pkt for pkt in iterpackets if pkt.has_side_data("new_extradata"))
82+
83+
sdata = pkt.get_side_data("new_extradata")
84+
assert sdata.data_type == "new_extradata"
85+
assert bool(sdata)
86+
assert sdata.data_size > 0
87+
assert sdata.data_desc == "New Extradata"
88+
89+
nxt = next(iterpackets) # has no palette
90+
91+
assert not nxt.has_side_data("new_extradata")
92+
93+
sdata1 = nxt.get_side_data("new_extradata")
94+
assert sdata1.data_type == "new_extradata"
95+
assert not bool(sdata1)
96+
assert sdata1.data_size == 0
97+
98+
nxt.set_side_data(sdata, move=True)
99+
assert not bool(sdata)

0 commit comments

Comments
 (0)