mediadecode_ffmpeg/buffer.rs
1//! `FfmpegBuffer` — owned, refcounted handle to an `AVBufferRef`.
2//!
3//! Both `AVPacket.buf` and `AVFrame.buf[i]` are FFmpeg's refcounted
4//! buffers. This crate's adapter exposes them through a `Bytes`-like
5//! type that implements `AsRef<[u8]>` so the buffer can be used as the
6//! `B` parameter on `mediadecode::Packet<A, B>` / `Frame<A, B>` without
7//! copying. Cloning bumps the refcount; dropping releases one
8//! reference and lets FFmpeg free the memory when the last reference
9//! goes away.
10
11use core::{fmt, slice};
12
13use derive_more::{IsVariant, TryUnwrap, Unwrap};
14use ffmpeg_next::ffi::{AVBufferRef, av_buffer_ref, av_buffer_unref};
15
16/// Owned, refcounted handle to a contiguous byte range inside an
17/// `AVBufferRef`.
18///
19/// Holds one reference to the underlying `AVBufferRef`. The `view`
20/// (offset + length) carves out a sub-region of the buffer's data —
21/// useful when an `AVFrame` packs multiple planes into a single
22/// allocation (e.g. NV12 with `data[1] == data[0] + Y_size`). Each
23/// plane gets its own `FfmpegBuffer` view at a different offset,
24/// every view bumps the refcount, and dropping one doesn't free the
25/// underlying buffer until the last view goes away.
26///
27/// `Clone` shares the same view (offset + length unchanged). `Drop`
28/// releases one reference via `av_buffer_unref`.
29pub struct FfmpegBuffer {
30 inner: *mut AVBufferRef,
31 /// Offset from `inner.data` where this view starts.
32 offset: usize,
33 /// Byte length of this view. Always `<= inner.size - offset`.
34 len: usize,
35}
36
37// SAFETY: `AVBufferRef`'s refcount is atomically managed by FFmpeg, so
38// transferring ownership of an `FfmpegBuffer` across threads is sound —
39// `Drop` (which is the only operation that mutates the refcount) calls
40// `av_buffer_unref` which uses atomic decrement.
41//
42// We deliberately do **not** implement `Sync`. Decoder-output buffers
43// from FFmpeg are immutable in practice, but the underlying
44// `AVBufferRef.data` is reachable through `as_av_buffer_ref` and
45// nothing in this type's contract prevents a caller from passing the
46// pointer to an FFmpeg API that mutates the bytes — concurrent reads
47// from another thread would then race. `Send`-only is the conservative
48// stance.
49unsafe impl Send for FfmpegBuffer {}
50
51impl FfmpegBuffer {
52 /// Constructs an `FfmpegBuffer` by **incrementing** the refcount of
53 /// an existing `AVBufferRef`. The view covers the buffer's full
54 /// `size` (offset 0). The caller's `*mut AVBufferRef` is unchanged —
55 /// it still owns its own reference and must be released independently.
56 ///
57 /// Returns `None` if `buf` is null or `av_buffer_ref` fails (out of
58 /// memory).
59 ///
60 /// # Safety
61 ///
62 /// `buf` must either be null or point to a live `AVBufferRef` for
63 /// the duration of this call.
64 #[inline]
65 pub unsafe fn from_ref(buf: *mut AVBufferRef) -> Option<Self> {
66 if buf.is_null() {
67 return None;
68 }
69 // SAFETY: caller upholds liveness; av_buffer_ref handles atomicity.
70 let new_ref = unsafe { av_buffer_ref(buf) };
71 if new_ref.is_null() {
72 return None;
73 }
74 let len = unsafe { (*new_ref).size as usize };
75 Some(Self {
76 inner: new_ref,
77 offset: 0,
78 len,
79 })
80 }
81
82 /// Constructs an `FfmpegBuffer` view over a sub-region of an existing
83 /// `AVBufferRef`. The refcount is incremented; the view runs from
84 /// `offset` for `len` bytes inside `(*buf).data`.
85 ///
86 /// Returns `None` if `buf` is null, `av_buffer_ref` fails, or
87 /// `offset + len > (*buf).size`.
88 ///
89 /// # Safety
90 ///
91 /// `buf` must either be null or point to a live `AVBufferRef` for
92 /// the duration of this call.
93 #[inline]
94 pub unsafe fn from_ref_view(buf: *mut AVBufferRef, offset: usize, len: usize) -> Option<Self> {
95 if buf.is_null() {
96 return None;
97 }
98 let buf_size = unsafe { (*buf).size };
99 let end = offset.checked_add(len)?;
100 if end > buf_size {
101 return None;
102 }
103 let new_ref = unsafe { av_buffer_ref(buf) };
104 if new_ref.is_null() {
105 return None;
106 }
107 Some(Self {
108 inner: new_ref,
109 offset,
110 len,
111 })
112 }
113
114 /// Allocates a 1-byte refcounted `AVBufferRef` and exposes a
115 /// zero-length view over it. Useful as a placeholder when
116 /// constructing an "empty" `mediadecode::VideoFrame` /
117 /// `AudioFrame` to pass to a decoder's `receive_frame` — the
118 /// decoder overwrites the planes on success, but the slot needs a
119 /// non-null buffer to satisfy the array shape.
120 ///
121 /// # Panics
122 ///
123 /// Panics if FFmpeg fails to allocate (out-of-memory). Allocations
124 /// of one byte never realistically fail; this matches the
125 /// behaviour of `Clone` on a populated `FfmpegBuffer`. Callers who
126 /// need to recover from OOM should use [`Self::try_empty`].
127 #[inline]
128 pub fn empty() -> Self {
129 Self::try_empty().expect("FfmpegBuffer::empty: av_buffer_alloc returned null (OOM)")
130 }
131
132 /// Fallible counterpart to [`Self::empty`]. Returns `None` if the
133 /// 1-byte `av_buffer_alloc` fails (out-of-memory). Use this when
134 /// you'd rather propagate an error than panic.
135 #[inline]
136 pub fn try_empty() -> Option<Self> {
137 use ffmpeg_next::ffi::av_buffer_alloc;
138 let raw = unsafe { av_buffer_alloc(1) };
139 if raw.is_null() {
140 return None;
141 }
142 // SAFETY: `raw` is non-null and freshly allocated; we transfer
143 // its single reference to the new `FfmpegBuffer`.
144 let mut buf = unsafe { Self::take(raw) }?;
145 buf.len = 0;
146 Some(buf)
147 }
148
149 /// Borrows the refcounted payload of an `ffmpeg::Packet` as an
150 /// `FfmpegBuffer` view. The packet's `AVBufferRef` is shared via
151 /// refcount bump — no copy. The view spans exactly
152 /// `(*packet.as_ptr()).data .. data + size` (the *payload*) — not
153 /// the entire underlying allocation: `AVPacket.buf` can be larger
154 /// than the payload (encoder padding, oversized buffers, sub-range
155 /// references after `av_packet_split_side_data`), so exposing the
156 /// whole AVBufferRef would corrupt downstream consumers that
157 /// trust the buffer to be just the compressed bytes.
158 ///
159 /// `Ok(None)` means the packet carries no payload at all — an empty
160 /// packet, which some demuxers emit as a marker. That is a fact about
161 /// the packet, and it is kept apart from [`PacketBufferError`], which
162 /// is a failure to take a payload that *is* there: conflating the two
163 /// makes an out-of-memory look like an empty marker and drops real
164 /// compressed bytes without a word. Callers needing universal
165 /// coverage of stack- or arena-allocated AVPackets can fall back to
166 /// [`Self::copy_from_slice`] over `packet.data()`.
167 #[inline]
168 pub fn from_packet(packet: &ffmpeg_next::Packet) -> Result<Option<Self>, PacketBufferError> {
169 use ffmpeg_next::packet::Ref;
170 // SAFETY: `packet` keeps the AVPacket live for the duration of
171 // this call, which is all `payload_of` requires.
172 unsafe { payload_of(packet.as_ptr()) }
173 }
174
175 /// Borrows one of an `ffmpeg::Frame`'s plane buffers
176 /// (`AVFrame.buf[plane_idx]`) as an `FfmpegBuffer` view. The view
177 /// covers the underlying `AVBufferRef`'s full size; for
178 /// per-plane subviews into a multi-plane shared allocation see
179 /// [`crate::convert::video_frame_from`].
180 ///
181 /// Returns `None` when `plane_idx >= 8` or the plane has no
182 /// buffer attached.
183 #[inline]
184 pub fn from_frame_plane(frame: &ffmpeg_next::Frame, plane_idx: usize) -> Option<Self> {
185 if plane_idx >= 8 {
186 return None;
187 }
188 // SAFETY: `frame` keeps the AVFrame live for the duration of
189 // this call; `buf[]` is a public fixed-size array on AVFrame.
190 let buf_ptr = unsafe { (*frame.as_ptr()).buf[plane_idx] };
191 unsafe { Self::from_ref(buf_ptr) }
192 }
193
194 /// Allocates a fresh refcounted `AVBufferRef` and copies `bytes` into
195 /// it. Returns `None` if the FFmpeg allocation fails.
196 ///
197 /// Useful for adapting non-refcounted FFmpeg payloads (e.g. subtitle
198 /// `AVSubtitleRect.text` / `.ass` / `.data[0]`) into the refcounted
199 /// `FfmpegBuffer` shape the rest of the crate carries.
200 #[inline]
201 pub fn copy_from_slice(bytes: &[u8]) -> Option<Self> {
202 use ffmpeg_next::ffi::av_buffer_alloc;
203 let len = bytes.len();
204 // av_buffer_alloc(0) is allowed on most platforms but isn't
205 // portable; force a 1-byte allocation in that case so the resulting
206 // buffer is non-null.
207 let alloc_size = len.max(1);
208 let raw = unsafe { av_buffer_alloc(alloc_size as _) };
209 if raw.is_null() {
210 return None;
211 }
212 if len > 0 {
213 // SAFETY: raw is non-null and freshly allocated with `alloc_size >= len`
214 // bytes; the source slice is valid for `len` reads.
215 unsafe {
216 core::ptr::copy_nonoverlapping(bytes.as_ptr(), (*raw).data, len);
217 }
218 }
219 Some(Self {
220 inner: raw,
221 offset: 0,
222 len,
223 })
224 }
225
226 /// Takes ownership of an existing `AVBufferRef` without bumping the
227 /// refcount. The view covers the buffer's full size. Use this when
228 /// the caller's reference will be dropped (e.g. transferring out of
229 /// an `AVPacket`/`AVFrame`).
230 ///
231 /// Returns `None` if `buf` is null.
232 ///
233 /// # Safety
234 ///
235 /// `buf` must be either null or a live `AVBufferRef` whose reference
236 /// the caller is willing to give up. After a successful call, the
237 /// caller MUST NOT call `av_buffer_unref` on the same pointer.
238 #[inline]
239 pub unsafe fn take(buf: *mut AVBufferRef) -> Option<Self> {
240 if buf.is_null() {
241 return None;
242 }
243 let len = unsafe { (*buf).size };
244 Some(Self {
245 inner: buf,
246 offset: 0,
247 len,
248 })
249 }
250
251 /// Number of bytes visible through this view.
252 #[inline]
253 pub fn len(&self) -> usize {
254 self.len
255 }
256
257 /// True when the view is zero bytes long.
258 #[inline]
259 pub fn is_empty(&self) -> bool {
260 self.len == 0
261 }
262
263 /// Raw pointer to the start of this view. Valid for [`Self::len`]
264 /// bytes for the lifetime of `self`. Returns a dangling-but-aligned
265 /// pointer when the view is empty (parallel to `core::ptr::NonNull::dangling`)
266 /// — the caller must respect [`Self::len`] before any read.
267 #[inline]
268 pub fn as_ptr(&self) -> *const u8 {
269 // SAFETY: inner is non-null per constructor invariant. We guard
270 // against null `data` (possible when the underlying AVBufferRef
271 // was created with size 0) before doing pointer arithmetic, since
272 // `null.add(offset)` is UB for offset > 0 even before any deref.
273 unsafe {
274 let data = (*self.inner).data;
275 if data.is_null() {
276 // Safe sentinel for empty/dataless buffers. The caller must
277 // gate any read on `len() == 0`.
278 return core::ptr::NonNull::<u8>::dangling().as_ptr();
279 }
280 (data as *const u8).add(self.offset)
281 }
282 }
283
284 /// Underlying `*const AVBufferRef`. Useful when handing the buffer
285 /// back to an FFmpeg API that expects a borrowed pointer (do **not**
286 /// call `av_buffer_unref` on the result — `self` still owns the ref).
287 /// The returned pointer references the **whole** buffer, not just
288 /// this view's sub-region.
289 ///
290 /// This intentionally returns `*const`, not `*mut`. FFmpeg APIs that
291 /// mutate via the buffer (e.g. `av_buffer_make_writable`) should be
292 /// reached through the unsafe constructors which transfer ownership;
293 /// shared `&self` access must not allow aliased writes.
294 #[inline]
295 pub fn as_av_buffer_ref(&self) -> *const AVBufferRef {
296 self.inner as *const _
297 }
298
299 /// Byte offset of this view's start within the underlying buffer.
300 #[inline]
301 pub fn offset(&self) -> usize {
302 self.offset
303 }
304
305 /// Narrows this view to at most `len` bytes, keeping its start.
306 ///
307 /// Shrink-only and allocation-free: the refcount is untouched and the
308 /// end of the view can only move inward, so the constructor's
309 /// `offset + len <= size` invariant survives by construction. This is
310 /// what lets a view be taken before its final length is known — the
311 /// resampler acquires its output planes' references *before* `swr`
312 /// runs and trims them to what it produced afterwards, so no
313 /// allocation and no failure is left on the far side of the
314 /// conversion.
315 ///
316 /// `resampler`'s only caller — gated with it, or this is dead code
317 /// whenever the `resample` feature is off.
318 #[cfg(feature = "resample")]
319 #[inline]
320 pub(crate) fn shrink_to(&mut self, len: usize) {
321 self.len = self.len.min(len);
322 }
323
324 /// Fallible counterpart to [`Clone::clone`]. Returns `None` if
325 /// `av_buffer_ref` fails (out-of-memory) instead of panicking.
326 /// Use this in OOM-recoverable paths; the `Clone` impl panics on
327 /// the same failure to match Rust's standard `Clone` contract.
328 #[inline]
329 pub fn try_clone(&self) -> Option<Self> {
330 // SAFETY: inner is non-null per invariant; av_buffer_ref
331 // atomically bumps the refcount and returns null on OOM only.
332 let new_ref = unsafe { av_buffer_ref(self.inner) };
333 if new_ref.is_null() {
334 return None;
335 }
336 Some(Self {
337 inner: new_ref,
338 offset: self.offset,
339 len: self.len,
340 })
341 }
342}
343
344/// Payload for [`PacketBufferError::Refcount`].
345#[derive(Copy, Clone, Debug, PartialEq, Eq, thiserror::Error)]
346#[error("out of memory referencing a {len}-byte packet payload")]
347pub struct Refcount {
348 len: usize,
349}
350
351impl Refcount {
352 /// Constructs a `Refcount` payload.
353 #[cfg_attr(not(tarpaulin), inline(always))]
354 pub const fn new(len: usize) -> Self {
355 Self { len }
356 }
357 /// The payload's length in bytes.
358 #[cfg_attr(not(tarpaulin), inline(always))]
359 pub const fn len(&self) -> usize {
360 self.len
361 }
362 /// `true` when the payload is zero bytes long.
363 #[cfg_attr(not(tarpaulin), inline(always))]
364 pub const fn is_empty(&self) -> bool {
365 self.len == 0
366 }
367}
368
369/// Payload for [`PacketBufferError::Bounds`].
370///
371/// The payload does not lie inside the packet's own buffer.
372/// `AVPacket` guarantees it does; a packet that says otherwise is
373/// malformed, and wrapping it would hand out a view over memory the
374/// buffer does not own.
375#[derive(Copy, Clone, Debug, PartialEq, Eq, thiserror::Error)]
376#[error("a {len}-byte payload at offset {offset} does not lie inside a {size}-byte buffer")]
377pub struct Bounds {
378 offset: usize,
379 len: usize,
380 size: usize,
381}
382
383impl Bounds {
384 /// Constructs a `Bounds` payload.
385 #[cfg_attr(not(tarpaulin), inline(always))]
386 pub const fn new(offset: usize, len: usize, size: usize) -> Self {
387 Self { offset, len, size }
388 }
389 /// Where the payload starts inside the buffer.
390 #[cfg_attr(not(tarpaulin), inline(always))]
391 pub const fn offset(&self) -> usize {
392 self.offset
393 }
394 /// The payload's length in bytes.
395 #[cfg_attr(not(tarpaulin), inline(always))]
396 pub const fn len(&self) -> usize {
397 self.len
398 }
399 /// `true` when the payload is zero bytes long.
400 #[cfg_attr(not(tarpaulin), inline(always))]
401 pub const fn is_empty(&self) -> bool {
402 self.len == 0
403 }
404 /// The buffer's own length in bytes.
405 #[cfg_attr(not(tarpaulin), inline(always))]
406 pub const fn size(&self) -> usize {
407 self.size
408 }
409}
410
411/// Payload for [`PacketBufferError::SideDataEntries`].
412///
413/// A packet declares more side-data entries than this crate will
414/// walk, or a negative count.
415///
416/// The cap bounds the work a crafted packet can demand *before* it is
417/// refused. It cannot trip on anything FFmpeg's own packet API
418/// produces: both `av_packet_new_side_data` and
419/// `av_packet_add_side_data` replace an entry of the same type, so a
420/// packet carries at most one entry per named type — forty-three in
421/// this build, and the cap tracks that number if it ever grows past
422/// the floor.
423#[derive(Copy, Clone, Debug, PartialEq, Eq, thiserror::Error)]
424#[error("a packet declaring {count} side-data entries cannot be carried (limit {cap})")]
425pub struct SideDataEntries {
426 count: i32,
427 cap: usize,
428}
429
430impl SideDataEntries {
431 /// Constructs a `SideDataEntries` payload.
432 #[cfg_attr(not(tarpaulin), inline(always))]
433 pub const fn new(count: i32, cap: usize) -> Self {
434 Self { count, cap }
435 }
436 /// The count the packet declared.
437 #[cfg_attr(not(tarpaulin), inline(always))]
438 pub const fn count(&self) -> i32 {
439 self.count
440 }
441 /// The most entries this crate will walk.
442 #[cfg_attr(not(tarpaulin), inline(always))]
443 pub const fn cap(&self) -> usize {
444 self.cap
445 }
446}
447
448/// Payload for [`PacketBufferError::SideDataArray`].
449///
450/// A packet declares side-data entries and carries no array to read
451/// them from.
452///
453/// Malformed, and named rather than read as "no side data": a null
454/// array with a positive count is the same silent loss as a truncated
455/// copy, reached through the pointer instead of the cap.
456#[derive(Copy, Clone, Debug, PartialEq, Eq, thiserror::Error)]
457#[error("a packet declaring {count} side-data entries carries no array")]
458pub struct SideDataArray {
459 count: i32,
460}
461
462impl SideDataArray {
463 /// Constructs a `SideDataArray` payload.
464 #[cfg_attr(not(tarpaulin), inline(always))]
465 pub const fn new(count: i32) -> Self {
466 Self { count }
467 }
468 /// The count the packet declared.
469 #[cfg_attr(not(tarpaulin), inline(always))]
470 pub const fn count(&self) -> i32 {
471 self.count
472 }
473}
474
475/// Payload for [`PacketBufferError::SideDataPayload`].
476#[derive(Copy, Clone, Debug, PartialEq, Eq, thiserror::Error)]
477#[error("side-data entry {index} declares {size} bytes and carries no data")]
478pub struct SideDataPayload {
479 index: usize,
480 size: usize,
481}
482
483impl SideDataPayload {
484 /// Constructs a `SideDataPayload` payload.
485 #[cfg_attr(not(tarpaulin), inline(always))]
486 pub const fn new(index: usize, size: usize) -> Self {
487 Self { index, size }
488 }
489 /// The entry's position in the packet's array.
490 #[cfg_attr(not(tarpaulin), inline(always))]
491 pub const fn index(&self) -> usize {
492 self.index
493 }
494 /// The length the entry declared.
495 #[cfg_attr(not(tarpaulin), inline(always))]
496 pub const fn size(&self) -> usize {
497 self.size
498 }
499}
500
501/// Payload for [`PacketBufferError::SideDataBytes`].
502///
503/// A packet's side data is larger than this crate will copy.
504#[derive(Copy, Clone, Debug, PartialEq, Eq, thiserror::Error)]
505#[error("{bytes} bytes of side data cannot be carried (limit {cap})")]
506pub struct SideDataBytes {
507 bytes: usize,
508 cap: usize,
509}
510
511impl SideDataBytes {
512 /// Constructs a `SideDataBytes` payload.
513 #[cfg_attr(not(tarpaulin), inline(always))]
514 pub const fn new(bytes: usize, cap: usize) -> Self {
515 Self { bytes, cap }
516 }
517 /// The total the packet's entries reached.
518 #[cfg_attr(not(tarpaulin), inline(always))]
519 pub const fn bytes(&self) -> usize {
520 self.bytes
521 }
522 /// The most bytes this crate will copy.
523 #[cfg_attr(not(tarpaulin), inline(always))]
524 pub const fn cap(&self) -> usize {
525 self.cap
526 }
527}
528
529/// Payload for [`PacketBufferError::UnrepresentableFlags`].
530///
531/// A packet carries flag bits the portable vocabulary cannot hold.
532///
533/// `mediadecode`'s `PacketFlags` is a `u8` bit set, and every packet
534/// flag FFmpeg names today lives in that byte — so this cannot fire
535/// against this build. It exists so that the day one does not, the
536/// packet is refused by name instead of arriving with a bit quietly
537/// missing: the same rule the rest of this boundary keeps.
538#[derive(Copy, Clone, Debug, PartialEq, Eq, thiserror::Error)]
539#[error("packet flags {raw:#x} do not fit the portable flag set")]
540pub struct UnrepresentableFlags {
541 raw: i32,
542}
543
544impl UnrepresentableFlags {
545 /// Constructs an `UnrepresentableFlags` payload.
546 #[cfg_attr(not(tarpaulin), inline(always))]
547 pub const fn new(raw: i32) -> Self {
548 Self { raw }
549 }
550 /// `AVPacket.flags` as FFmpeg wrote it.
551 #[cfg_attr(not(tarpaulin), inline(always))]
552 pub const fn raw(&self) -> i32 {
553 self.raw
554 }
555}
556
557/// Payload for [`PacketBufferError::SideDataAlloc`].
558///
559/// Out of memory copying a side-data entry.
560#[derive(Copy, Clone, Debug, PartialEq, Eq, thiserror::Error)]
561#[error("out of memory copying {size} bytes of side data")]
562pub struct SideDataAlloc {
563 size: usize,
564}
565
566impl SideDataAlloc {
567 /// Constructs a `SideDataAlloc` payload.
568 #[cfg_attr(not(tarpaulin), inline(always))]
569 pub const fn new(size: usize) -> Self {
570 Self { size }
571 }
572 /// The entry's length in bytes.
573 #[cfg_attr(not(tarpaulin), inline(always))]
574 pub const fn size(&self) -> usize {
575 self.size
576 }
577}
578
579/// Why a packet could not be carried across the boundary — its payload,
580/// or the side data that comes with it.
581///
582/// Every arm means the bytes are real and this crate could not carry
583/// them — never that there were none. "No payload" is `Ok(None)` from
584/// [`FfmpegBuffer::from_packet`], and keeping the two apart is the
585/// whole point of the type: a demuxer that reads a refcount failure as
586/// an empty marker drops a video packet under memory pressure and
587/// carries on as though the file said so. The side-data arms exist for
588/// the same reason one tier along — a packet whose side data cannot be
589/// carried whole is refused, never delivered with some of it.
590#[derive(Copy, Clone, Debug, PartialEq, Eq, thiserror::Error, IsVariant, Unwrap, TryUnwrap)]
591#[unwrap(ref, ref_mut)]
592#[try_unwrap(ref, ref_mut)]
593pub enum PacketBufferError {
594 /// `av_buffer_ref` returned null — out of memory taking a second
595 /// reference to a payload that is there.
596 #[error(transparent)]
597 Refcount(#[from] Refcount),
598
599 /// The payload does not lie inside the packet's own buffer.
600 #[error(transparent)]
601 Bounds(#[from] Bounds),
602
603 /// A packet declares more side-data entries than this crate will
604 /// walk, or a negative count.
605 #[error(transparent)]
606 SideDataEntries(#[from] SideDataEntries),
607
608 /// A packet declares side-data entries and carries no array to read
609 /// them from.
610 #[error(transparent)]
611 SideDataArray(#[from] SideDataArray),
612
613 /// A side-data entry declares bytes it does not carry.
614 #[error(transparent)]
615 SideDataPayload(#[from] SideDataPayload),
616
617 /// A packet's side data is larger than this crate will copy.
618 #[error(transparent)]
619 SideDataBytes(#[from] SideDataBytes),
620
621 /// A packet carries flag bits the portable vocabulary cannot hold.
622 #[error(transparent)]
623 UnrepresentableFlags(#[from] UnrepresentableFlags),
624
625 /// Out of memory copying a side-data entry.
626 #[error(transparent)]
627 SideDataAlloc(#[from] SideDataAlloc),
628}
629
630/// The refcounted payload of a raw `AVPacket`.
631///
632/// Shared by [`FfmpegBuffer::from_packet`] and the demuxer's capture of
633/// `AVStream.attached_pic`, which is an `AVPacket` embedded in the
634/// stream by value and so cannot be reached through the safe wrapper.
635/// One implementation, so the empty-versus-failed distinction cannot
636/// drift between them.
637///
638/// # Safety
639///
640/// `pkt` must be a live `*const AVPacket` for the duration of this
641/// call.
642pub(crate) unsafe fn payload_of(
643 pkt: *const ffmpeg_next::ffi::AVPacket,
644) -> Result<Option<FfmpegBuffer>, PacketBufferError> {
645 // SAFETY: `pkt` is live per the contract above; `.buf`, `.data` and
646 // `.size` are public fields on `AVPacket`, and `buf` may be null
647 // (stack-allocated packets).
648 let buf_ptr = unsafe { (*pkt).buf };
649 let data_ptr = unsafe { (*pkt).data };
650 let size_raw = unsafe { (*pkt).size };
651 if buf_ptr.is_null() || data_ptr.is_null() || size_raw <= 0 {
652 return Ok(None);
653 }
654 let len = size_raw as usize;
655 // SAFETY: `buf_ptr` is a live `AVBufferRef` owned by the packet.
656 let buf_data = unsafe { (*buf_ptr).data };
657 let size = unsafe { (*buf_ptr).size };
658 if buf_data.is_null() {
659 return Err(PacketBufferError::Bounds(Bounds::new(0, len, size)));
660 }
661 // `AVPacket` guarantees `data` lies within
662 // `buf->data .. buf->data + buf->size`. The bounds are checked here
663 // rather than left to `from_ref_view` so that a malformed packet and
664 // a failed `av_buffer_ref` do not come back as the same `None`.
665 let offset = (data_ptr as usize).wrapping_sub(buf_data as usize);
666 match offset.checked_add(len) {
667 Some(end) if end <= size => {}
668 _ => {
669 return Err(PacketBufferError::Bounds(Bounds::new(offset, len, size)));
670 }
671 }
672 // SAFETY: `buf_ptr` is a live `AVBufferRef`, and the view was just
673 // proved to lie inside it.
674 unsafe { FfmpegBuffer::from_ref_view(buf_ptr, offset, len) }
675 .map(Some)
676 .ok_or(PacketBufferError::Refcount(Refcount::new(len)))
677}
678
679impl Clone for FfmpegBuffer {
680 /// Refcounts the underlying `AVBufferRef`. **Panics** on OOM (see
681 /// [`Self::try_clone`] for the fallible variant).
682 fn clone(&self) -> Self {
683 self
684 .try_clone()
685 .expect("FfmpegBuffer::clone: av_buffer_ref returned null (OOM)")
686 }
687}
688
689impl Drop for FfmpegBuffer {
690 fn drop(&mut self) {
691 // SAFETY: inner is a live AVBufferRef per invariant. `av_buffer_unref`
692 // takes `**mut AVBufferRef` and zeroes the pointer; we don't read
693 // self.inner after this.
694 unsafe { av_buffer_unref(&mut self.inner) };
695 }
696}
697
698impl AsRef<[u8]> for FfmpegBuffer {
699 #[inline]
700 fn as_ref(&self) -> &[u8] {
701 // SAFETY:
702 // - `inner` is non-null (constructor invariant).
703 // - The data pointer is non-null and valid for the underlying
704 // buffer's `size` bytes per FFmpeg's contract.
705 // - `offset + len <= buffer size` is established at construction
706 // (and preserved by Clone), so the view stays in-bounds.
707 // - The buffer is immutable for the lifetime we hold the refcount.
708 unsafe {
709 let data = (*self.inner).data as *const u8;
710 if data.is_null() || self.len == 0 {
711 return &[];
712 }
713 // `offset + len <= buffer size` was established at construction
714 // (and preserved by Clone), so the resulting pointer + length
715 // stays inside the AVBufferRef's allocation.
716 slice::from_raw_parts(data.add(self.offset), self.len)
717 }
718 }
719}
720
721impl fmt::Debug for FfmpegBuffer {
722 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
723 f.debug_struct("FfmpegBuffer")
724 .field("len", &self.len())
725 .finish()
726 }
727}
728
729#[cfg(test)]
730mod tests {
731 use super::*;
732 use ffmpeg_next::ffi::av_buffer_alloc;
733
734 /// Allocate a fresh AVBufferRef of `size` bytes, fill it with `fill`,
735 /// and wrap it in our type via `take` (taking ownership of the
736 /// caller's reference).
737 fn make_buffer(size: usize, fill: u8) -> FfmpegBuffer {
738 let raw = unsafe { av_buffer_alloc(size as _) };
739 assert!(!raw.is_null(), "av_buffer_alloc failed");
740 unsafe {
741 let data = (*raw).data;
742 core::ptr::write_bytes(data, fill, size);
743 }
744 unsafe { FfmpegBuffer::take(raw) }.expect("non-null take")
745 }
746
747 #[test]
748 fn null_take_returns_none() {
749 assert!(unsafe { FfmpegBuffer::take(core::ptr::null_mut()) }.is_none());
750 }
751
752 #[test]
753 fn null_from_ref_returns_none() {
754 assert!(unsafe { FfmpegBuffer::from_ref(core::ptr::null_mut()) }.is_none());
755 }
756
757 #[test]
758 fn allocated_buffer_round_trips_bytes() {
759 let buf = make_buffer(16, 0xAB);
760 assert_eq!(buf.len(), 16);
761 assert!(!buf.is_empty());
762 let slice = buf.as_ref();
763 assert_eq!(slice.len(), 16);
764 assert!(slice.iter().all(|&b| b == 0xAB));
765 }
766
767 #[test]
768 fn clone_bumps_refcount_and_keeps_data_alive() {
769 let original = make_buffer(8, 0x5A);
770 let cloned = original.clone();
771 // Both references see the same bytes.
772 assert_eq!(original.as_ref(), cloned.as_ref());
773 assert_eq!(original.as_ptr(), cloned.as_ptr());
774 // Drop one — the other must still be valid.
775 drop(original);
776 assert_eq!(cloned.len(), 8);
777 assert!(cloned.as_ref().iter().all(|&b| b == 0x5A));
778 }
779
780 #[test]
781 fn debug_shows_length() {
782 let buf = make_buffer(42, 0);
783 let s = format!("{buf:?}");
784 assert!(s.contains("len: 42"), "got {s}");
785 }
786
787 #[test]
788 fn from_ref_view_carves_out_subregion() {
789 // 24-byte buffer: bytes 0..8 = 0xAA, 8..16 = 0xBB, 16..24 = 0xCC.
790 let raw = unsafe { av_buffer_alloc(24) };
791 assert!(!raw.is_null());
792 unsafe {
793 let data = (*raw).data;
794 core::ptr::write_bytes(data, 0xAA, 8);
795 core::ptr::write_bytes(data.add(8), 0xBB, 8);
796 core::ptr::write_bytes(data.add(16), 0xCC, 8);
797 }
798
799 // Three independent views, each with its own refcount.
800 let view_a = unsafe { FfmpegBuffer::from_ref_view(raw, 0, 8) }.expect("view_a");
801 let view_b = unsafe { FfmpegBuffer::from_ref_view(raw, 8, 8) }.expect("view_b");
802 let view_c = unsafe { FfmpegBuffer::from_ref_view(raw, 16, 8) }.expect("view_c");
803 assert!(view_a.as_ref().iter().all(|&b| b == 0xAA));
804 assert!(view_b.as_ref().iter().all(|&b| b == 0xBB));
805 assert!(view_c.as_ref().iter().all(|&b| b == 0xCC));
806 assert_eq!(view_a.offset(), 0);
807 assert_eq!(view_b.offset(), 8);
808 assert_eq!(view_c.offset(), 16);
809 assert_eq!(view_a.len(), 8);
810
811 // Drop the original; the views still keep the buffer alive.
812 unsafe { av_buffer_unref(&mut { raw }) };
813 let _ = (view_a, view_b, view_c);
814 }
815
816 #[test]
817 fn from_ref_view_rejects_out_of_bounds() {
818 let raw = unsafe { av_buffer_alloc(16) };
819 assert!(!raw.is_null());
820 // Past the end:
821 assert!(unsafe { FfmpegBuffer::from_ref_view(raw, 10, 8) }.is_none());
822 // Overflow protection (offset + len overflows usize):
823 assert!(unsafe { FfmpegBuffer::from_ref_view(raw, usize::MAX, 1) }.is_none());
824 unsafe { av_buffer_unref(&mut { raw }) };
825 }
826
827 #[test]
828 fn empty_buffer_returns_empty_slice() {
829 // av_buffer_alloc(0) is valid in FFmpeg; some platforms return a
830 // non-null buf with data == null and size == 0. Either way, our
831 // as_ref must return an empty slice without dereferencing data.
832 let raw = unsafe { av_buffer_alloc(0) };
833 if raw.is_null() {
834 // Some allocators refuse 0; skip the test in that case.
835 return;
836 }
837 let buf = unsafe { FfmpegBuffer::take(raw) }.expect("non-null take");
838 assert_eq!(buf.len(), 0);
839 assert!(buf.is_empty());
840 assert_eq!(buf.as_ref(), &[] as &[u8]);
841 }
842}