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;