1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
//! RAII owner for an `AVPacket`.
//!
//! [`Packet`] allocates a packet and frees it exactly once on drop, replacing
//! the manual `av_packet_alloc` + `av_packet_free` pair. Ownership is unique;
//! [`try_clone`](Packet::try_clone) makes a ref-counted copy (`av_packet_ref`).
//! Scalar fields ([`stream_index`](Packet::stream_index) / [`pts`](Packet::pts))
//! are read through the typed accessors below.
use std::ptr::NonNull;
use crate::{
AVPacket, AVPacketSideDataType, AVRational, AvError, av_packet_alloc as ffi_av_packet_alloc,
av_packet_free as ffi_av_packet_free, av_packet_new_side_data as ffi_av_packet_new_side_data,
av_packet_ref as ffi_av_packet_ref, av_packet_rescale_ts as ffi_av_packet_rescale_ts,
av_packet_unref as ffi_av_packet_unref,
};
/// An owned `AVPacket`.
///
/// The packet is freed exactly once on drop. This is guaranteed by construction:
/// the value owns a [`NonNull`] and is neither `Copy` nor `Clone`, so it drops
/// exactly once and cannot be duplicated (a ref-counted copy is made explicitly
/// via [`try_clone`](Self::try_clone)).
#[derive(Debug)]
pub struct Packet {
ptr: NonNull<AVPacket>,
}
impl Packet {
/// Allocates a new, empty packet.
///
/// # Errors
///
/// Returns an [`AvError`] if allocation fails.
pub fn new() -> Result<Self, AvError> {
// SAFETY: `av_packet_alloc` takes no arguments and returns a fresh packet or null.
let ptr = unsafe { ffi_av_packet_alloc() };
NonNull::new(ptr)
.ok_or_else(|| AvError::new(crate::error_codes::ENOMEM))
.map(|ptr| Self { ptr })
}
/// Returns the packet pointer for read-only use.
///
/// Crate-internal: the safe codec / format APIs consume the owned [`Packet`],
/// so no public signature exposes this raw pointer.
#[must_use]
pub(crate) const fn as_ptr(&self) -> *const AVPacket {
self.ptr.as_ptr()
}
/// Returns the packet pointer for mutation and FFI calls.
///
/// Crate-internal: see [`as_ptr`](Self::as_ptr).
#[must_use]
pub(crate) fn as_mut_ptr(&mut self) -> *mut AVPacket {
self.ptr.as_ptr()
}
/// Returns the index of the stream this packet belongs to.
#[must_use]
pub fn stream_index(&self) -> std::os::raw::c_int {
// SAFETY: `self.ptr` is a valid owned packet; `stream_index` is a plain field.
unsafe { (*self.ptr.as_ptr()).stream_index }
}
/// Returns the presentation timestamp (in the stream's time base).
#[must_use]
pub fn pts(&self) -> i64 {
// SAFETY: `self.ptr` is a valid owned packet; `pts` is a plain field.
unsafe { (*self.ptr.as_ptr()).pts }
}
/// Returns the decompression timestamp (in the stream's time base).
#[must_use]
pub fn dts(&self) -> i64 {
// SAFETY: `self.ptr` is a valid owned packet; `dts` is a plain field.
unsafe { (*self.ptr.as_ptr()).dts }
}
/// Returns the packet's duration (in the stream's time base).
#[must_use]
pub fn duration(&self) -> i64 {
// SAFETY: `self.ptr` is a valid owned packet; `duration` is a plain field.
unsafe { (*self.ptr.as_ptr()).duration }
}
/// Returns the packet's payload size in bytes.
#[must_use]
pub fn size(&self) -> std::os::raw::c_int {
// SAFETY: `self.ptr` is a valid owned packet; `size` is a plain field.
unsafe { (*self.ptr.as_ptr()).size }
}
/// Returns the packet's flags (a bitmask of `AV_PKT_FLAG_*`).
#[must_use]
pub fn flags(&self) -> std::os::raw::c_int {
// SAFETY: `self.ptr` is a valid owned packet; `flags` is a plain field.
unsafe { (*self.ptr.as_ptr()).flags }
}
/// Sets the index of the stream this packet belongs to.
pub fn set_stream_index(&mut self, stream_index: std::os::raw::c_int) {
// SAFETY: `self.ptr` is a valid owned packet; `stream_index` is a plain field.
unsafe { (*self.ptr.as_ptr()).stream_index = stream_index };
}
/// Sets the presentation timestamp (in the stream's time base).
pub fn set_pts(&mut self, pts: i64) {
// SAFETY: `self.ptr` is a valid owned packet; `pts` is a plain field.
unsafe { (*self.ptr.as_ptr()).pts = pts };
}
/// Sets the decompression timestamp (in the stream's time base).
pub fn set_dts(&mut self, dts: i64) {
// SAFETY: `self.ptr` is a valid owned packet; `dts` is a plain field.
unsafe { (*self.ptr.as_ptr()).dts = dts };
}
/// Sets the packet's duration (in the stream's time base).
pub fn set_duration(&mut self, duration: i64) {
// SAFETY: `self.ptr` is a valid owned packet; `duration` is a plain field.
unsafe { (*self.ptr.as_ptr()).duration = duration };
}
/// Allocates `size` bytes of side data of the given `kind` on this packet
/// and returns a mutable slice over it, or `None` on allocation failure.
///
/// The side data is owned by the packet and freed with it. The caller
/// writes the payload (e.g. an HDR metadata struct) into the returned slice.
pub fn new_side_data(&mut self, kind: AVPacketSideDataType, size: usize) -> Option<&mut [u8]> {
// SAFETY: `self.ptr` is a valid owned packet; `av_packet_new_side_data`
// allocates `size` bytes (or returns null on OOM) attached to it.
let ptr = unsafe { ffi_av_packet_new_side_data(self.ptr.as_ptr(), kind, size) };
if ptr.is_null() {
None
} else {
// SAFETY: `ptr` is non-null and points to `size` writable bytes owned
// by the packet's side-data buffer.
Some(unsafe { std::slice::from_raw_parts_mut(ptr, size) })
}
}
/// Rescales the packet's `pts` / `dts` / `duration` from `src_tb` to `dst_tb`.
pub fn rescale_ts(&mut self, src_tb: AVRational, dst_tb: AVRational) {
// SAFETY: `self.ptr` is a valid owned packet; the time bases are plain
// POD values.
unsafe { ffi_av_packet_rescale_ts(self.ptr.as_ptr(), src_tb, dst_tb) };
}
/// Unreferences the packet's buffer, returning it to a blank state.
pub fn unref(&mut self) {
// SAFETY: `self.ptr` is a valid owned packet.
unsafe { ffi_av_packet_unref(self.ptr.as_ptr()) };
}
/// Makes a ref-counted copy of this packet (`av_packet_ref`), sharing the
/// underlying buffer rather than deep-copying.
///
/// # Errors
///
/// Returns an [`AvError`] if the copy cannot be allocated / referenced.
pub fn try_clone(&self) -> Result<Self, AvError> {
let dst = Self::new()?;
// SAFETY: `dst` is a fresh blank packet and `self` is a valid packet;
// `av_packet_ref` ref-counts `self`'s buffer into `dst`.
let ret = unsafe { ffi_av_packet_ref(dst.ptr.as_ptr(), self.ptr.as_ptr()) };
if ret < 0 {
Err(AvError::new(ret))
} else {
Ok(dst)
}
}
}
impl Drop for Packet {
fn drop(&mut self) {
// SAFETY: we uniquely own the packet (NonNull, not Copy/Clone), so this runs
// exactly once. `av_packet_free` frees it and writes null into our
// local copy of the pointer, which is then discarded.
unsafe {
let mut raw = self.ptr.as_ptr();
ffi_av_packet_free(&mut raw);
}
}
}
// SAFETY: an `AVPacket` is not safe for concurrent access, but moving ownership
// between threads is sound because Rust's ownership model guarantees
// exclusive access.
unsafe impl Send for Packet {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_should_allocate_and_drop_cleanly() {
let packet = Packet::new().expect("packet allocation should succeed");
assert!(!packet.as_ptr().is_null());
// Dropping `packet` frees it exactly once (no panic / double free).
}
#[test]
fn try_clone_should_produce_an_independent_owner() {
let packet = Packet::new().expect("packet allocation should succeed");
let clone = packet.try_clone().expect("ref-count clone should succeed");
assert!(!clone.as_ptr().is_null());
// Both `packet` and `clone` drop independently (ref-counted), no double free.
}
#[test]
fn scalar_setters_should_round_trip() {
let mut packet = Packet::new().expect("packet allocation should succeed");
packet.set_stream_index(3);
packet.set_pts(1_000);
packet.set_dts(900);
packet.set_duration(512);
assert_eq!(packet.stream_index(), 3);
assert_eq!(packet.pts(), 1_000);
assert_eq!(packet.dts(), 900);
assert_eq!(packet.duration(), 512);
}
#[test]
fn size_and_flags_should_read_defaults() {
let packet = Packet::new().expect("packet allocation should succeed");
// A fresh packet carries no payload and no flags; the accessors read
// those plain fields (there is no public setter for either).
assert_eq!(packet.size(), 0);
assert_eq!(packet.flags(), 0);
}
#[test]
fn new_side_data_should_return_a_writable_slice() {
let mut packet = Packet::new().expect("packet allocation should succeed");
let buf = packet
.new_side_data(
crate::AVPacketSideDataType_AV_PKT_DATA_CONTENT_LIGHT_LEVEL,
8,
)
.expect("side data allocation should succeed");
assert_eq!(buf.len(), 8);
// The returned slice is writable and owned by the packet (freed on drop).
buf.copy_from_slice(&[1, 2, 3, 4, 5, 6, 7, 8]);
}
#[test]
fn rescale_ts_should_scale_pts_and_dts() {
let mut packet = Packet::new().expect("packet allocation should succeed");
packet.set_pts(100);
packet.set_dts(100);
// 1/1000 -> 1/2000 doubles the timestamps.
packet.rescale_ts(
AVRational { num: 1, den: 1000 },
AVRational { num: 1, den: 2000 },
);
assert_eq!(packet.pts(), 200);
assert_eq!(packet.dts(), 200);
}
}