Skip to main content

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}