Skip to main content

mediadecode_ffmpeg/
view.rs

1//! `FfmpegBuffer` — the **view** lane's carrier.
2//!
3//! A refcounted handle onto an `AVBufferRef` that FFmpeg already owns.
4//! Cloning bumps the refcount; dropping releases one reference; the
5//! bytes are never copied. This is the carrier 0.8 shipped, resurrected
6//! by ruling as the second of two first-class lanes — see
7//! [the carrier lanes][lanes] for what each one is for.
8//!
9//! # What the amputation round taught this type
10//!
11//! The view lane is not 0.8 restored unchanged. Every lesson from the
12//! 0.9 review loop that lands on *this* type has been re-applied:
13//!
14//! * **The extent is proved before a view exists.** 0.8 formed a view
15//!   over a packet's claimed range and let a malformed `size` hand out
16//!   a slice nobody had checked. The proof now runs first, in
17//!   [`crate::buffer::payload_of`], which both lanes share — so the
18//!   view lane cannot regress it independently.
19//! * **`AV_PKT_FLAG_TRUSTED` is refused on both legs**, and for a
20//!   sharper reason here than on the owned lane: a payload that is a
21//!   structure of pointers is uncarriable when copied, and *equally*
22//!   uncarriable when viewed. Sharing the allocation does not make its
23//!   pointers own what they name.
24//! * **Budgets judge sizes, not copies.** A view costs no bytes, but a
25//!   ceiling on frame and packet size is a ceiling on what a caller
26//!   will be handed and asked to hold — so every seat that fires on
27//!   the owned lane fires identically here.
28//! * **Every constructor that takes an extent is crate-private**, and
29//!   the one that takes row geometry checks it rather than asserting
30//!   it. An invariant that lives in the caller is an invariant the
31//!   caller has to be inside this crate to be trusted with.
32//! * **A shared buffer never reaches a caller as something mutable.**
33//!   The zero-copy send exists, but it is scoped to a decoder
34//!   submission inside this crate; a packet a caller *holds* owns its
35//!   bytes. See [the boundary's `with_ffmpeg_video_packet`][scoped].
36//! * **What may be read past a carrier is recorded, not inferred.**
37//!   Trailing capacity is not padding — see [`Origin`].
38//!
39//! [scoped]: crate::boundary
40//!
41//! [lanes]: mediadecode::adapter#the-two-carrier-lanes
42
43use core::{fmt, slice};
44
45use ffmpeg_next::ffi::{AVBufferRef, av_buffer_ref, av_buffer_unref};
46
47/// Where a carrier's bytes came from, and therefore what may be assumed
48/// about the bytes **after** them.
49///
50/// The send leg needs one specific guarantee: libavcodec reads
51/// `AV_INPUT_BUFFER_PADDING_SIZE` bytes past a packet's payload, and
52/// libavformat allocates exactly that much zeroed slack behind every
53/// packet it produces. Trailing *capacity* is not that guarantee — a
54/// video plane has more pixels after it, a resampler's output frame has
55/// more samples, and a bitstream reader running off the end of a packet
56/// into either would eat them as though they were bitstream.
57///
58/// So provenance is recorded where it is known — at capture — rather
59/// than inferred later from a size comparison that cannot tell padding
60/// from a neighbour.
61#[derive(Debug, Clone, Copy, PartialEq, Eq)]
62pub(crate) enum Origin {
63  /// A packet payload captured out of an `AVPacket`'s own buffer, whose
64  /// trailing padding is libavformat's contract.
65  PacketPayload,
66  /// Anything else: a frame plane, a resampler's output, a copy this
67  /// crate allocated. Nothing may be assumed about what follows.
68  Foreign,
69}
70
71/// Refcounted view onto a contiguous byte range inside an
72/// `AVBufferRef`.
73///
74/// Holds one reference to the buffer. The view (offset + length) carves
75/// out a sub-region, which is what lets several planes of one
76/// allocation — NV12's `data[1] == data[0] + y_size` — each be their
77/// own carrier at their own offset, every one bumping the same
78/// refcount.
79///
80/// # Lifetime, stated plainly
81///
82/// This carrier keeps FFmpeg's buffer alive, which is the point and
83/// also the catch: a frame held is a **pool slot held**. A decoder
84/// whose pool is exhausted blocks or fails, so a consumer that parks
85/// view frames in a queue is a consumer that stalls its own decoder.
86/// Read in place, drop, decode on. See the contract for the tradeoff
87/// table and for why graph traffic belongs on the owned lane.
88pub struct FfmpegBuffer {
89  /// The reference this carrier owns, or null for the empty carrier.
90  ///
91  /// Null is the *only* shape with no buffer behind it, and it exists so
92  /// that a placeholder plane slot costs nothing and cannot fail: an
93  /// empty carrier that allocated would put an out-of-memory road under
94  /// `[Plane; 8]`, which is a lot of failure to buy a zero-length span.
95  /// `len == 0` whenever this is null, and every read consults `len`
96  /// first.
97  inner: *mut AVBufferRef,
98  /// Offset from `inner.data` where this view starts.
99  offset: usize,
100  /// Byte length of this view. Always `<= inner.size - offset`.
101  len: usize,
102  /// What may be assumed about the bytes after this view. See
103  /// [`Origin`].
104  origin: Origin,
105}
106
107// SAFETY: `AVBufferRef`'s refcount is managed atomically by FFmpeg, and
108// `Drop` — the only operation that mutates it — goes through
109// `av_buffer_unref`. Moving a carrier between threads is therefore
110// sound.
111//
112// `Sync` is deliberately **not** implemented, and this is a contract
113// decision rather than an oversight. The bytes behind the view belong
114// to FFmpeg, which is entitled to hand the same allocation to an API
115// that writes through it; nothing in this type's contract forbids a
116// caller from doing exactly that via `as_av_buffer_ref`. Shared access
117// from two threads would then race. The owned lane is `Sync` because
118// its bytes are nobody's but ours.
119unsafe impl Send for FfmpegBuffer {}
120
121impl FfmpegBuffer {
122  /// Takes a view over `len` bytes at `offset` inside `buf`,
123  /// incrementing its refcount.
124  ///
125  /// The caller keeps its own reference and must release it
126  /// independently.
127  ///
128  /// `None` when `buf` is null, when the view would run past the
129  /// buffer's `size`, or when `av_buffer_ref` fails.
130  ///
131  /// **Crate-private on purpose.** Every argument here is an invariant
132  /// the unsafe layer trusts, and a caller outside this crate has no
133  /// way to establish them. The lanes are reached through the carrier
134  /// seam, whose operations are equally unreachable from outside; what
135  /// a consumer gets is the finished carrier.
136  ///
137  /// # Safety
138  ///
139  /// `buf` must be null or a live `AVBufferRef` for the duration of
140  /// this call.
141  pub(crate) unsafe fn view_of(
142    buf: *mut AVBufferRef,
143    offset: usize,
144    len: usize,
145    origin: Origin,
146  ) -> Option<Self> {
147    if buf.is_null() {
148      return None;
149    }
150    // SAFETY: `buf` is live per the contract; `size` is a public field.
151    let size = unsafe { (*buf).size };
152    // The extent proof, kept here as well as at the call sites: this is
153    // the constructor, and a constructor that trusts its arguments is
154    // one bad caller away from a view over somebody else's memory.
155    if offset.checked_add(len)? > size {
156      return None;
157    }
158    // SAFETY: as above; `av_buffer_ref` is atomic and returns null only
159    // on allocation failure.
160    let new_ref = unsafe { av_buffer_ref(buf) };
161    if new_ref.is_null() {
162      return None;
163    }
164    Some(Self {
165      inner: new_ref,
166      offset,
167      len,
168      origin,
169    })
170  }
171
172  /// What may be assumed about the bytes after this view.
173  #[inline]
174  pub(crate) const fn origin(&self) -> Origin {
175    self.origin
176  }
177
178  /// Allocates a fresh refcounted buffer and copies `bytes` into it.
179  ///
180  /// **The view lane copies here, and it has to.** Not every byte
181  /// FFmpeg hands over lives in an `AVBufferRef`: subtitle rect text,
182  /// `AVFrameSideData` payloads and a palette plane are plain
183  /// allocations with no refcount to share. A carrier over them has to
184  /// own something, so it owns a copy — and the lane stays honest by
185  /// saying so rather than pretending the whole road is zero-copy.
186  ///
187  /// `None` when the allocation fails.
188  pub fn copy_from_slice(bytes: &[u8]) -> Option<Self> {
189    use ffmpeg_next::ffi::av_buffer_alloc;
190    let len = bytes.len();
191    if len == 0 {
192      // `av_buffer_alloc(0)` is not portable, and there is nothing to
193      // hold: the empty carrier is the answer.
194      return Some(Self::empty());
195    }
196    // SAFETY: a plain allocation, checked for null before any write.
197    let raw = unsafe { av_buffer_alloc(len as _) };
198    if raw.is_null() {
199      return None;
200    }
201    // SAFETY: `raw` is a fresh allocation of `len` bytes and `bytes` is
202    // valid for `len` reads; the two cannot overlap.
203    unsafe { core::ptr::copy_nonoverlapping(bytes.as_ptr(), (*raw).data, len) };
204    Some(Self {
205      inner: raw,
206      offset: 0,
207      len,
208      // A copy this crate allocated: exactly `len` bytes, nothing
209      // behind them, and so never shareable into a decoder.
210      origin: Origin::Foreign,
211    })
212  }
213
214  /// Allocates a refcounted buffer and gathers `rows` runs of
215  /// `row_bytes` into it, tightly.
216  ///
217  /// The padded-plane road, which this lane copies like the other one —
218  /// see [`FfmpegCarrier::from_rows`](crate::FfmpegCarrier::from_rows)
219  /// for why sharing a padded span is not available to anybody.
220  ///
221  /// `None` when the allocation fails, or when a row is not exactly
222  /// `row_bytes` wide.
223  ///
224  /// **The row length is checked, not asserted.** It was a
225  /// `debug_assert` once, which meant a release build copied
226  /// `row_bytes` out of whatever slice the closure returned — an
227  /// out-of-bounds read whose only guard vanished under `--release`.
228  /// A length that arrives from a caller is an input, and an input is
229  /// checked.
230  pub(crate) fn from_rows<'a>(
231    rows: usize,
232    row_bytes: usize,
233    mut row: impl FnMut(usize) -> &'a [u8],
234  ) -> Option<Self> {
235    use ffmpeg_next::ffi::av_buffer_alloc;
236
237    let len = rows.checked_mul(row_bytes)?;
238    if len == 0 {
239      return Some(Self::empty());
240    }
241    // SAFETY: a plain allocation, checked for null before any write.
242    let raw = unsafe { av_buffer_alloc(len as _) };
243    if raw.is_null() {
244      return None;
245    }
246    // The allocation is owned from here on, so a refusal below releases
247    // it instead of leaking it. `Self` is that guard: it is a complete
248    // carrier the moment it exists, and dropping it unrefs the buffer.
249    let out = Self {
250      inner: raw,
251      offset: 0,
252      len,
253      origin: Origin::Foreign,
254    };
255    for index in 0..rows {
256      let src = row(index);
257      if src.len() != row_bytes {
258        // `out` drops here and releases the allocation.
259        return None;
260      }
261      // SAFETY: `raw` holds `rows * row_bytes` bytes; this write lands
262      // at `index * row_bytes` for `row_bytes`, inside it. `src` was
263      // just checked to be exactly that wide, and is a distinct
264      // allocation.
265      unsafe {
266        core::ptr::copy_nonoverlapping(src.as_ptr(), (*raw).data.add(index * row_bytes), row_bytes);
267      }
268    }
269    Some(out)
270  }
271
272  /// The empty carrier: no buffer, no bytes, no allocation.
273  ///
274  /// Infallible and `const`, which is the point — it is what an
275  /// unpopulated plane slot holds, and eight of them per frame is not a
276  /// place to put an allocator.
277  #[must_use]
278  pub const fn empty() -> Self {
279    Self {
280      inner: core::ptr::null_mut(),
281      offset: 0,
282      len: 0,
283      origin: Origin::Foreign,
284    }
285  }
286
287  /// Narrows this view to its first `len` bytes.
288  ///
289  /// Only ever shrinks: a length past the current one is clamped, since
290  /// growing would extend the span past what the constructor proved.
291  /// The buffer, the reference and the offset are untouched — this
292  /// moves no bytes and cannot fail, which is what lets a producer take
293  /// its reference **before** a conversion runs and settle the exact
294  /// length after it.
295  /// Narrowing **clears provenance**: whatever now sits between the new
296  /// end and the old one is the carrier's own former contents, not the
297  /// zeroed slack a decoder is entitled to read.
298  pub(crate) const fn shrink_to(&mut self, len: usize) {
299    if len < self.len {
300      self.len = len;
301      self.origin = Origin::Foreign;
302    }
303  }
304
305  /// Bytes visible through this view.
306  #[inline]
307  pub const fn len(&self) -> usize {
308    self.len
309  }
310
311  /// Whether the view is zero bytes long.
312  #[inline]
313  pub const fn is_empty(&self) -> bool {
314    self.len == 0
315  }
316
317  /// Byte offset of this view's start inside the underlying buffer.
318  #[inline]
319  pub const fn offset(&self) -> usize {
320    self.offset
321  }
322
323  /// Start of the view. Valid for [`Self::len`] bytes while `self`
324  /// lives.
325  ///
326  /// A dangling-but-aligned pointer when the view is empty, so a caller
327  /// must consult `len` before reading — the same contract
328  /// `NonNull::dangling` keeps.
329  pub fn as_ptr(&self) -> *const u8 {
330    if self.inner.is_null() {
331      return core::ptr::NonNull::<u8>::dangling().as_ptr();
332    }
333    // SAFETY: `inner` is non-null on this road. `data` can still be
334    // null for a zero-sized buffer, and `null.add(n)` is undefined even
335    // before a read, so the null case answers with the sentinel.
336    unsafe {
337      let data = (*self.inner).data;
338      if data.is_null() {
339        return core::ptr::NonNull::<u8>::dangling().as_ptr();
340      }
341      (data as *const u8).add(self.offset)
342    }
343  }
344
345  /// The underlying `AVBufferRef`, borrowed — null for the empty
346  /// carrier.
347  ///
348  /// Points at the **whole** buffer, not this view's sub-region, and is
349  /// `*const` on purpose: a shared borrow must not become an aliased
350  /// write. Do not `av_buffer_unref` it — `self` still owns that
351  /// reference.
352  #[inline]
353  pub const fn as_av_buffer_ref(&self) -> *const AVBufferRef {
354    self.inner.cast_const()
355  }
356
357  /// Whether two carriers view the same underlying allocation.
358  ///
359  /// The proof a clone shared rather than copied, and the proof two
360  /// planes of one frame really do share one buffer.
361  ///
362  /// Compares the `AVBuffer` behind the reference, not the reference
363  /// itself: `av_buffer_ref` mints a **new** `AVBufferRef` around the
364  /// same shared object, so two carriers that genuinely share have
365  /// different `AVBufferRef` pointers and the same `buffer`.
366  pub fn ptr_eq(&self, other: &Self) -> bool {
367    if self.inner.is_null() || other.inner.is_null() {
368      // Two empty carriers share the same nothing; an empty one shares
369      // nothing with a real buffer.
370      return self.inner == other.inner;
371    }
372    // SAFETY: both `inner` pointers are non-null on this road; `buffer`
373    // is a public field naming the shared allocation.
374    unsafe { (*self.inner).buffer == (*other.inner).buffer }
375  }
376
377  /// Fallible [`Clone::clone`]: `None` on allocation failure instead of
378  /// a panic.
379  pub fn try_clone(&self) -> Option<Self> {
380    if self.inner.is_null() {
381      return Some(Self::empty());
382    }
383    // SAFETY: `inner` is non-null on this road; `av_buffer_ref` is
384    // atomic and returns null only on allocation failure.
385    let new_ref = unsafe { av_buffer_ref(self.inner) };
386    if new_ref.is_null() {
387      return None;
388    }
389    Some(Self {
390      inner: new_ref,
391      offset: self.offset,
392      len: self.len,
393      // Provenance is a property of the bytes, not of the handle: a
394      // clone views the same range of the same allocation.
395      origin: self.origin,
396    })
397  }
398}
399
400impl Clone for FfmpegBuffer {
401  /// One refcount bump. **Panics** on allocation failure; see
402  /// [`FfmpegBuffer::try_clone`] for the fallible road.
403  fn clone(&self) -> Self {
404    self
405      .try_clone()
406      .expect("FfmpegBuffer::clone: av_buffer_ref returned null (OOM)")
407  }
408}
409
410impl Drop for FfmpegBuffer {
411  fn drop(&mut self) {
412    if self.inner.is_null() {
413      return;
414    }
415    // SAFETY: `inner` is non-null on this road and this carrier owns
416    // exactly one reference to it, released exactly once here.
417    unsafe { av_buffer_unref(&mut self.inner) };
418  }
419}
420
421impl AsRef<[u8]> for FfmpegBuffer {
422  fn as_ref(&self) -> &[u8] {
423    if self.len == 0 {
424      return &[];
425    }
426    // SAFETY: the constructors prove `offset + len <= size` against the
427    // buffer's own extent, so the range lies inside an allocation this
428    // carrier holds a reference to and therefore keeps alive.
429    unsafe { slice::from_raw_parts(self.as_ptr(), self.len) }
430  }
431}
432
433impl fmt::Debug for FfmpegBuffer {
434  /// Shape, not contents: a carrier can be megabytes, and a `Debug`
435  /// that prints them is a `Debug` nobody can use.
436  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
437    f.debug_struct("FfmpegBuffer")
438      .field("offset", &self.offset)
439      .field("len", &self.len)
440      .finish_non_exhaustive()
441  }
442}
443
444#[cfg(test)]
445mod tests;