mediadecode_ffmpeg/ticket.rs
1//! The owned codec ticket — one stream's `AVCodecParameters`, mirrored
2//! into plain Rust and rebuilt on demand.
3//!
4//! # Why the mirror exists
5//!
6//! [`ffmpeg_next::codec::Parameters`] is a `*mut AVCodecParameters`
7//! behind a `Send`-but-not-`Sync` wrapper. A track row that stores one
8//! is `!Sync`, an `Arc` of that row is `!Send`, and every consumer that
9//! shares a track table across tasks stops compiling — for a struct
10//! FFmpeg documents as a plain *descriptor* with no thread affinity at
11//! all. The auto-trait is missing, not the safety.
12//!
13//! The crate already answers that class one way: it mirrors what a
14//! consumer needs into owned Rust —
15//! [`TrackParams`](mediadecode::demuxer::TrackParams) mirrors the
16//! common seats, [`SideDataEntry`] mirrors a frame's metadata,
17//! [`FfmpegBytes`] mirrors its pixels. [`CodecTicket`] walks that road
18//! its last mile: **every** seat of an `AVCodecParameters`, held as
19//! owned bytes and plain integers, with `Sync` arriving by
20//! construction rather than by an `unsafe impl` over FFI.
21//!
22//! # The two halves
23//!
24//! * [`CodecTicket::mirror`] reads a live `AVCodecParameters` into the
25//! ticket. It is the only place that reads one.
26//! * [`CodecTicket::rebuild`] allocates a fresh `AVCodecParameters` and
27//! writes every seat back. It is the only place in this crate's
28//! track-row road that allocates one, and what it hands back is what
29//! `avcodec_parameters_to_context` is fed — unchanged from before the
30//! mirror existed.
31//!
32//! The pair is proved in `tests/codec_ticket_parity.rs`: for every
33//! stream of every fixture the corpus can mint, the rebuilt struct is
34//! compared with the original **field by field**, including
35//! `extradata` bytes, the `AV_INPUT_BUFFER_PADDING_SIZE` zeroes past
36//! their end, every `coded_side_data` entry's type id and payload, and
37//! the channel layout down to a custom map's per-channel names. The
38//! shapes no container will hand over — a custom map, an unnamed
39//! side-data kind, every scalar set off its default — are built by
40//! hand in the same file, and a decoder is opened through a rebuilt
41//! ticket and made to produce a frame.
42//!
43//! # The reading discipline, inherited
44//!
45//! Not one bindgen enum is materialised out of FFmpeg memory. Every
46//! open C enum seat — the media type, the codec id, the field order,
47//! the five colour seats, the alpha mode, the channel order, a custom
48//! channel's id, a side-data type id — travels as **the raw 32-bit
49//! pattern it is on the wire**, read and written through the same
50//! `i32` cast, so a value this build's bindings cannot name is still a
51//! value this ticket carries. That is the same rule
52//! [`crate::extras::bounded_clone_parameters`] is written to, for the
53//! same reason: forming a typed reference to a struct whose enum field
54//! holds an unnamed discriminant is undefined behaviour before a
55//! single field is read.
56//!
57//! [`SideDataEntry`]: crate::extras::SideDataEntry
58//! [`FfmpegBytes`]: crate::FfmpegBytes
59
60use core::ptr::{addr_of, addr_of_mut, copy_nonoverlapping, read_unaligned, write_unaligned};
61
62use ffmpeg_next::{
63 codec::Parameters,
64 ffi::{
65 AV_INPUT_BUFFER_PADDING_SIZE, AVChannelCustom, AVChannelOrder, AVCodecParameters,
66 AVPacketSideData, AVPacketSideDataType, av_mallocz,
67 },
68};
69
70use crate::{
71 FfmpegBytes,
72 demuxer::{
73 DemuxError, ParametersAlloc, ParametersChannelMap, ParametersCopy, ParametersMissing,
74 ParametersOpaque, ParametersTooLarge,
75 },
76 extras::{ExtradataPolicy, SideDataEntry, measure_parameters},
77};
78
79/// A verbatim `AVRational` seat.
80///
81/// Its own type rather than a [`mediatime::Timebase`] because the two
82/// seats it carries — `sample_aspect_ratio` and `framerate` — are not
83/// timebases and are not always valid ratios: FFmpeg spells "unknown"
84/// as a zero numerator (`sample_aspect_ratio`) or as `0/1`
85/// (`framerate`), and a mirror that normalised either would fail its
86/// own parity test. Nothing here reduces, validates or interprets;
87/// the numbers cross unchanged.
88#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
89pub struct Ratio {
90 num: i32,
91 den: i32,
92}
93
94impl Ratio {
95 /// Constructs a `Ratio` from a numerator and denominator, verbatim.
96 #[cfg_attr(not(tarpaulin), inline(always))]
97 pub const fn new(num: i32, den: i32) -> Self {
98 Self { num, den }
99 }
100 /// The numerator.
101 #[cfg_attr(not(tarpaulin), inline(always))]
102 pub const fn num(&self) -> i32 {
103 self.num
104 }
105 /// The denominator.
106 #[cfg_attr(not(tarpaulin), inline(always))]
107 pub const fn den(&self) -> i32 {
108 self.den
109 }
110}
111
112/// The Dolby Vision decoder configuration record's two routing seats —
113/// the profile number and the base-layer signal-compatibility id —
114/// read from the container's `dvcC`/`dvvC`/`dwvC` box when present.
115///
116/// **Numbers, not interpretation.** This crate does not map `profile`
117/// onto a named Dolby Vision profile (5, 7, 8.1, …) or `compatibility_id`
118/// onto "HDR10-compatible" / "SDR-compatible" / etc. — those tables are
119/// Dolby's own and change independently of this crate's release cycle;
120/// the consumer that already routes base-layer-vs-refuse on this value
121/// (per the sealed ground this type answers to) owns that table. What
122/// crosses here is exactly what the box declared, unchanged.
123///
124/// `Copy`: two bytes, nothing owned.
125#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
126pub struct DolbyVisionConfig {
127 profile: u8,
128 compatibility_id: u8,
129}
130
131impl DolbyVisionConfig {
132 /// Constructs a `DolbyVisionConfig` from its two routing numbers.
133 #[cfg_attr(not(tarpaulin), inline(always))]
134 pub const fn new(profile: u8, compatibility_id: u8) -> Self {
135 Self {
136 profile,
137 compatibility_id,
138 }
139 }
140 /// The Dolby Vision profile number (`dv_profile`), verbatim.
141 #[cfg_attr(not(tarpaulin), inline(always))]
142 pub const fn profile(&self) -> u8 {
143 self.profile
144 }
145 /// The base-layer signal-compatibility id (`dv_bl_signal_
146 /// compatibility_id`), verbatim — what the consumer this record was
147 /// sealed for routes base-layer-vs-refuse on.
148 #[cfg_attr(not(tarpaulin), inline(always))]
149 pub const fn compatibility_id(&self) -> u8 {
150 self.compatibility_id
151 }
152}
153
154/// Byte offset of `dv_profile` in FFmpeg's in-process
155/// `AVDOVIDecoderConfigurationRecord` (`libavutil/dovi_meta.h`): two
156/// leading version bytes, then the profile.
157const DOVI_CONFIG_PROFILE_OFFSET: usize = 2;
158/// Byte offset of `dv_bl_signal_compatibility_id`: version (2) +
159/// profile (1) + level (1) + three one-byte presence flags (3).
160const DOVI_CONFIG_COMPATIBILITY_ID_OFFSET: usize = 7;
161/// Minimum payload length [`parse_dolby_vision_config`] needs — enough
162/// to read the compatibility id, the later of the two seats. The full
163/// struct FFmpeg n9.0 allocates is nine bytes (a ninth,
164/// `dv_md_compression`, follows); this function reads neither that
165/// byte nor relies on the struct's total size, which its own header
166/// documents as **not** part of the public ABI.
167const DOVI_CONFIG_MIN_BYTES: usize = DOVI_CONFIG_COMPATIBILITY_ID_OFFSET + 1;
168
169/// Parses an `AV_PKT_DATA_DOVI_CONF` payload — a byte-for-byte copy of
170/// FFmpeg's `AVDOVIDecoderConfigurationRecord` (`dv_version_major,
171/// dv_version_minor, dv_profile, dv_level, rpu_present_flag,
172/// el_present_flag, bl_present_flag, dv_bl_signal_compatibility_id,
173/// [dv_md_compression]`, every seat one byte) — into the two routing
174/// numbers. `None` when the payload is shorter than
175/// [`DOVI_CONFIG_MIN_BYTES`] — a version-skew or corrupt entry.
176fn parse_dolby_vision_config(bytes: &[u8]) -> Option<DolbyVisionConfig> {
177 if bytes.len() < DOVI_CONFIG_MIN_BYTES {
178 return None;
179 }
180 Some(DolbyVisionConfig::new(
181 bytes[DOVI_CONFIG_PROFILE_OFFSET],
182 bytes[DOVI_CONFIG_COMPATIBILITY_ID_OFFSET],
183 ))
184}
185
186/// One entry of a custom channel map — the `AV_CHANNEL_ORDER_CUSTOM`
187/// arm of [`ChannelLayoutTicket`].
188///
189/// `name` is FFmpeg's inline `char[16]`, carried as the sixteen bytes
190/// it is. It is a NUL-padded label, not a Rust string: FFmpeg's own
191/// contract is "may be filled with a 0-terminated string … otherwise
192/// it must be zeroed", so the bytes cross verbatim and any decoding
193/// into text is the consumer's choice, not the mirror's.
194#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
195pub struct CustomChannel {
196 id: i32,
197 name: [u8; 16],
198}
199
200impl CustomChannel {
201 /// Constructs a `CustomChannel` from a raw `AVChannel` id and the
202 /// sixteen name bytes.
203 #[cfg_attr(not(tarpaulin), inline(always))]
204 pub const fn new(id: i32, name: [u8; 16]) -> Self {
205 Self { id, name }
206 }
207 /// The raw `AVChannel` id. Negative values are real: `AV_CHAN_NONE`
208 /// is `-1`.
209 #[cfg_attr(not(tarpaulin), inline(always))]
210 pub const fn id(&self) -> i32 {
211 self.id
212 }
213 /// The sixteen name bytes, NUL-padded, exactly as FFmpeg holds them.
214 #[cfg_attr(not(tarpaulin), inline(always))]
215 pub const fn name_bytes(&self) -> &[u8; 16] {
216 &self.name
217 }
218}
219
220/// The owned mirror of an `AVChannelLayout`.
221///
222/// The union is discriminated by `order`, and this type keeps that
223/// discrimination honest: `mask` is read and written **only** for the
224/// orders whose union arm is the bitmask, and `map` **only** for
225/// `AV_CHANNEL_ORDER_CUSTOM`, whose arm is a pointer. Reading the
226/// pointer arm as a mask would put a raw address in an owned mirror,
227/// which is the whole thing this type exists to stop.
228#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
229pub struct ChannelLayoutTicket {
230 order: i32,
231 channels: i32,
232 mask: u64,
233 map: Vec<CustomChannel>,
234}
235
236impl ChannelLayoutTicket {
237 /// The raw `AVChannelOrder`.
238 #[cfg_attr(not(tarpaulin), inline(always))]
239 pub const fn order(&self) -> i32 {
240 self.order
241 }
242 /// `nb_channels`, verbatim.
243 #[cfg_attr(not(tarpaulin), inline(always))]
244 pub const fn channels(&self) -> i32 {
245 self.channels
246 }
247 /// The channel bitmask, meaningful for every order but
248 /// `AV_CHANNEL_ORDER_CUSTOM`, where it reads zero and [`Self::map`]
249 /// carries the layout instead.
250 #[cfg_attr(not(tarpaulin), inline(always))]
251 pub const fn mask(&self) -> u64 {
252 self.mask
253 }
254 /// The custom channel map — empty for every order but
255 /// `AV_CHANNEL_ORDER_CUSTOM`.
256 #[cfg_attr(not(tarpaulin), inline(always))]
257 pub fn map(&self) -> &[CustomChannel] {
258 self.map.as_slice()
259 }
260
261 // `is_custom` used to live here, and its removal is the point rather
262 // than tidying. Every caller asked it "is this custom?" and treated
263 // `false` as "mask-backed" — which is how an unspecified layout's
264 // undefined union came to be read into `mask`. The question has three
265 // answers, not two, and
266 // [`LayoutArm`](crate::channel_layout::LayoutArm) is the only thing
267 // that answers it now.
268}
269
270/// Every seat of one stream's `AVCodecParameters`, owned.
271///
272/// # The roster
273///
274/// All thirty-two fields FFmpeg n9.0 declares, in that struct's own
275/// order. Nothing is elided as "video only" or "audio only":
276/// `avcodec_parameters_to_context` reads a different subset per medium
277/// but the *file* carries whatever it carries, and a mirror that kept
278/// only one medium's subset would lose a seat the moment a container
279/// declared something unusual.
280///
281/// They land in three kinds. Three seats own heap and become owned
282/// Rust: `extradata`, `coded_side_data`, and `ch_layout`'s custom map.
283/// Two are lengths of those — `extradata_size` and
284/// `nb_coded_side_data` — and are **not stored**: a carrier already
285/// knows its own length, and a second copy of it is a second thing to
286/// keep in agreement. The remaining twenty-seven are scalars, held as
287/// the integers (or, for the two `AVRational` seats, the [`Ratio`])
288/// they are.
289///
290/// # `Send + Sync`, by construction
291///
292/// Every field is an integer, an [`FfmpegBytes`](crate::FfmpegBytes)
293/// (an `Arc<[u8]>`), or a `Vec` of those. There is no raw pointer, so
294/// there is no `unsafe impl` and no safety argument to get wrong —
295/// which is exactly the point of the road this type is on. See
296/// `tests::the_ticket_is_send_and_sync`.
297///
298/// # What it does not carry, and why that is a refusal rather than a
299/// loss
300///
301/// `AVChannelLayout::opaque` and `AVChannelCustom::opaque` are
302/// documented as "private data of the user": raw pointers, set by
303/// nobody but the caller who owns them, and unreadable to a mirror
304/// that must outlive the pointer's owner. libavformat never sets
305/// either, so no demuxed stream reaches this type carrying one. If one
306/// ever did, [`CodecTicket::mirror`] refuses with
307/// [`DemuxError::ParametersOpaque`] rather than dropping it in
308/// silence — the same fail-closed answer
309/// [`measure_parameters`](crate::extras) gives a channel order it has
310/// never heard of.
311#[derive(Clone)]
312pub struct CodecTicket {
313 /// The `AVStream.index` this mirror was taken at.
314 ///
315 /// Carried for one reason: so [`Self::rebuild`]'s errors can name
316 /// the stream they are about. A rebuild that reports `ENOMEM`
317 /// without saying which track it was opening is a log line nobody
318 /// can act on, and the mirror is the last place that knows.
319 stream_index: usize,
320 codec_type: i32,
321 codec_id: i32,
322 codec_tag: u32,
323 extradata: FfmpegBytes,
324 coded_side_data: Vec<SideDataEntry>,
325 format: i32,
326 bit_rate: i64,
327 bits_per_coded_sample: i32,
328 bits_per_raw_sample: i32,
329 profile: i32,
330 level: i32,
331 width: i32,
332 height: i32,
333 sample_aspect_ratio: Ratio,
334 framerate: Ratio,
335 field_order: i32,
336 color_range: i32,
337 color_primaries: i32,
338 color_trc: i32,
339 color_space: i32,
340 chroma_location: i32,
341 video_delay: i32,
342 ch_layout: ChannelLayoutTicket,
343 sample_rate: i32,
344 block_align: i32,
345 frame_size: i32,
346 initial_padding: i32,
347 trailing_padding: i32,
348 seek_preroll: i32,
349 alpha_mode: i32,
350 /// What [`Self::rebuild`] will ask FFmpeg's allocator for — see
351 /// [`Self::footprint_bytes`].
352 footprint_bytes: usize,
353}
354
355impl CodecTicket {
356 /// Mirrors a live set of codec parameters into an owned ticket.
357 ///
358 /// `budget` is the ceiling the mirror's heap seats must fit under,
359 /// measured before a byte is copied — the same admission
360 /// [`crate::extras::bounded_clone_parameters`] performs and the same
361 /// number `admit_streams` charges against the session's total. A set
362 /// of parameters over the ceiling is refused with
363 /// [`DemuxError::ParametersTooLarge`], never truncated.
364 ///
365 /// Fails with [`DemuxError::ParametersMissing`] when `source` is
366 /// null-backed — `Parameters::new()` and `Parameters::default()` are
367 /// safe constructors over an unchecked `avcodec_parameters_alloc`,
368 /// so a caller can hold one without ever having been told.
369 pub fn mirror(
370 source: &Parameters,
371 stream_index: usize,
372 budget: usize,
373 ) -> Result<Self, DemuxError> {
374 Self::mirror_with(source, stream_index, budget, ExtradataPolicy::Copy)
375 }
376
377 /// [`Self::mirror`], with the `extradata` policy named.
378 pub(crate) fn mirror_with(
379 source: &Parameters,
380 stream_index: usize,
381 budget: usize,
382 extradata_policy: ExtradataPolicy,
383 ) -> Result<Self, DemuxError> {
384 // SAFETY: reading the pointer without dereferencing it — which is
385 // what the null check exists for.
386 let par = unsafe { source.as_ptr() };
387 if par.is_null() {
388 return Err(DemuxError::ParametersMissing(ParametersMissing::new(
389 stream_index,
390 )));
391 }
392 // SAFETY: `par` is a live `AVCodecParameters` owned by `source`
393 // for the duration of this call.
394 unsafe { Self::from_raw(par, stream_index, budget, extradata_policy) }
395 }
396
397 /// [`Self::mirror`] over a raw pointer.
398 ///
399 /// # Safety
400 ///
401 /// `par` must be a non-null, live `*const AVCodecParameters` for the
402 /// duration of this call.
403 pub(crate) unsafe fn from_raw(
404 par: *const AVCodecParameters,
405 stream_index: usize,
406 budget: usize,
407 extradata_policy: ExtradataPolicy,
408 ) -> Result<Self, DemuxError> {
409 let too_large = |bytes: usize| {
410 DemuxError::ParametersTooLarge(ParametersTooLarge::new(stream_index, bytes, budget))
411 };
412
413 // **Structure first, before the measurement and long before the
414 // copies.** The demux road already ran this during admission, but
415 // this constructor is reachable on its own, and it used to copy
416 // `extradata` and every coded-side-data payload — the two
417 // attacker-sized seats — and only then reach
418 // [`channel_layout_of`], which is where a malformed layout was
419 // discovered. A deterministic refusal derivable from the struct's
420 // own declared fields has no business arriving after two payload
421 // copies, whichever caller asked for the mirror.
422 //
423 // It costs a pointer walk and allocates nothing. See
424 // [`validate_channel_layout`], which is the whole of what can be
425 // refused here from structure alone.
426 //
427 // SAFETY: this function's own contract, forwarded — `par` is a
428 // non-null, live `*const AVCodecParameters`.
429 unsafe { validate_channel_layout(par, stream_index) }?;
430
431 // Measured before a byte is copied, exactly as the bounded clone
432 // does it: the footprint enumerates the same three heap seats this
433 // mirror is about to read, and refusing here is what keeps an
434 // attacker-sized `extradata` or ICC profile from being copied into
435 // Rust memory just to be refused afterwards.
436 //
437 // SAFETY: `par` is live per this function's contract; the
438 // measurement allocates nothing and dereferences only what it
439 // counts.
440 let footprint = unsafe { measure_parameters(par) }.ok_or_else(|| too_large(usize::MAX))?;
441 let footprint_bytes = match extradata_policy {
442 ExtradataPolicy::Copy => footprint.total(),
443 ExtradataPolicy::Omit => footprint.total_without_extradata(),
444 }
445 .ok_or_else(|| too_large(usize::MAX))?;
446 if footprint_bytes > budget {
447 return Err(too_large(footprint_bytes));
448 }
449
450 // SAFETY: `par` is live; every read below either takes a scalar
451 // field by value or reaches one through `addr_of!`, and no enum
452 // field is read as anything but the `i32` pattern it is on the
453 // wire. See the module docs for why that distinction is
454 // load-bearing rather than stylistic.
455 let ticket = unsafe {
456 Self {
457 stream_index,
458 codec_type: read_unaligned(addr_of!((*par).codec_type).cast::<i32>()),
459 codec_id: read_unaligned(addr_of!((*par).codec_id).cast::<i32>()),
460 codec_tag: (*par).codec_tag,
461 extradata: extradata_of(par, extradata_policy, stream_index)?,
462 coded_side_data: side_data_of(par, stream_index)?,
463 format: (*par).format,
464 bit_rate: (*par).bit_rate,
465 bits_per_coded_sample: (*par).bits_per_coded_sample,
466 bits_per_raw_sample: (*par).bits_per_raw_sample,
467 profile: (*par).profile,
468 level: (*par).level,
469 width: (*par).width,
470 height: (*par).height,
471 sample_aspect_ratio: Ratio::new(
472 (*par).sample_aspect_ratio.num,
473 (*par).sample_aspect_ratio.den,
474 ),
475 framerate: Ratio::new((*par).framerate.num, (*par).framerate.den),
476 field_order: read_unaligned(addr_of!((*par).field_order).cast::<i32>()),
477 color_range: read_unaligned(addr_of!((*par).color_range).cast::<i32>()),
478 color_primaries: read_unaligned(addr_of!((*par).color_primaries).cast::<i32>()),
479 color_trc: read_unaligned(addr_of!((*par).color_trc).cast::<i32>()),
480 color_space: read_unaligned(addr_of!((*par).color_space).cast::<i32>()),
481 chroma_location: read_unaligned(addr_of!((*par).chroma_location).cast::<i32>()),
482 video_delay: (*par).video_delay,
483 ch_layout: channel_layout_of(par, stream_index)?,
484 sample_rate: (*par).sample_rate,
485 block_align: (*par).block_align,
486 frame_size: (*par).frame_size,
487 initial_padding: (*par).initial_padding,
488 trailing_padding: (*par).trailing_padding,
489 seek_preroll: (*par).seek_preroll,
490 alpha_mode: read_unaligned(addr_of!((*par).alpha_mode).cast::<i32>()),
491 footprint_bytes,
492 }
493 };
494 Ok(ticket)
495 }
496
497 /// Rebuilds a live `AVCodecParameters` from the ticket.
498 ///
499 /// **The one ffmpeg-native allocation on the track row's road**, and
500 /// the handoff a decoder is opened from:
501 ///
502 /// ```ignore
503 /// FfmpegAudioStreamDecoder::open(
504 /// track.extra().clone_parameters()?,
505 /// track.timebase(),
506 /// limits,
507 /// )
508 /// ```
509 ///
510 /// Every seat that can hold a non-default value is written, so the
511 /// result depends on the ticket rather than on what
512 /// `avcodec_parameters_alloc` happened to leave behind. Stated
513 /// exactly, because the difference is load-bearing:
514 ///
515 /// * The **twenty-seven scalars** are written unconditionally. Those
516 /// are the seats `avcodec_parameters_alloc` gives non-zero defaults
517 /// to — `format` is `-1`, `profile` and `level` are
518 /// `AV_PROFILE_UNKNOWN` / `AV_LEVEL_UNKNOWN`, both rationals are
519 /// `0/1`, and so on — so leaving any of them would let a default
520 /// masquerade as the file's own value.
521 /// * The **four descriptor seats** — `extradata` and
522 /// `extradata_size`, `coded_side_data` and `nb_coded_side_data` —
523 /// are written only when the ticket has something to put there. On
524 /// the empty path they keep the allocator's zero, and that is
525 /// correct rather than an omission: `codec_parameters_reset`
526 /// `memset`s the whole struct to zero and then assigns non-zero
527 /// defaults to a named list that contains none of these four. A
528 /// null pointer with a zero length is exactly what "no extradata"
529 /// and "no side data" mean, and it is what the source had.
530 /// * `ch_layout` is always written — order, channel count, and either
531 /// the mask or the map.
532 ///
533 /// That is what makes field-by-field parity with the original
534 /// provable rather than hopeful, and
535 /// `tests/codec_ticket_parity.rs::every_scalar_seat_is_written_back`
536 /// is the assertion that a seat quietly relying on a default cannot
537 /// pass.
538 ///
539 /// Fallible because allocation is: `ParametersAlloc` when the struct
540 /// itself cannot be allocated, `ParametersCopy` carrying `ENOMEM`
541 /// when one of the heap seats cannot. Nothing here consults a
542 /// budget — the bytes are already resident and were admitted at
543 /// [`Self::mirror`]; what this allocates is exactly
544 /// [`Self::footprint_bytes`].
545 pub fn rebuild(&self) -> Result<Parameters, DemuxError> {
546 let stream_index = self.stream_index;
547 let mut out = Parameters::new();
548 // SAFETY: reading the pointer the constructor stored without
549 // dereferencing it — `Parameters::new` does not check
550 // `avcodec_parameters_alloc` and hands back a null on failure.
551 let dst = unsafe { out.as_mut_ptr() };
552 if dst.is_null() {
553 return Err(DemuxError::ParametersAlloc(ParametersAlloc::new(
554 stream_index,
555 )));
556 }
557
558 // SAFETY: `dst` is a live, freshly allocated `AVCodecParameters`
559 // whose heap seats are still null. Every write below is a scalar
560 // store, or an `addr_of_mut!` store of the same 32-bit pattern the
561 // mirror read, into a struct nothing else holds a reference to.
562 unsafe {
563 write_unaligned(
564 addr_of_mut!((*dst).codec_type).cast::<i32>(),
565 self.codec_type,
566 );
567 write_unaligned(addr_of_mut!((*dst).codec_id).cast::<i32>(), self.codec_id);
568 (*dst).codec_tag = self.codec_tag;
569 (*dst).format = self.format;
570 (*dst).bit_rate = self.bit_rate;
571 (*dst).bits_per_coded_sample = self.bits_per_coded_sample;
572 (*dst).bits_per_raw_sample = self.bits_per_raw_sample;
573 (*dst).profile = self.profile;
574 (*dst).level = self.level;
575 (*dst).width = self.width;
576 (*dst).height = self.height;
577 (*dst).sample_aspect_ratio.num = self.sample_aspect_ratio.num();
578 (*dst).sample_aspect_ratio.den = self.sample_aspect_ratio.den();
579 (*dst).framerate.num = self.framerate.num();
580 (*dst).framerate.den = self.framerate.den();
581 write_unaligned(
582 addr_of_mut!((*dst).field_order).cast::<i32>(),
583 self.field_order,
584 );
585 write_unaligned(
586 addr_of_mut!((*dst).color_range).cast::<i32>(),
587 self.color_range,
588 );
589 write_unaligned(
590 addr_of_mut!((*dst).color_primaries).cast::<i32>(),
591 self.color_primaries,
592 );
593 write_unaligned(addr_of_mut!((*dst).color_trc).cast::<i32>(), self.color_trc);
594 write_unaligned(
595 addr_of_mut!((*dst).color_space).cast::<i32>(),
596 self.color_space,
597 );
598 write_unaligned(
599 addr_of_mut!((*dst).chroma_location).cast::<i32>(),
600 self.chroma_location,
601 );
602 (*dst).video_delay = self.video_delay;
603 (*dst).sample_rate = self.sample_rate;
604 (*dst).block_align = self.block_align;
605 (*dst).frame_size = self.frame_size;
606 (*dst).initial_padding = self.initial_padding;
607 (*dst).trailing_padding = self.trailing_padding;
608 (*dst).seek_preroll = self.seek_preroll;
609 write_unaligned(
610 addr_of_mut!((*dst).alpha_mode).cast::<i32>(),
611 self.alpha_mode,
612 );
613 }
614
615 // The three heap seats, each allocated from FFmpeg's allocator so
616 // `avcodec_parameters_free` releases them with the struct — the
617 // same allocator discipline `bounded_clone_parameters` uses, and
618 // the same one `avcodec_parameters_copy` would have used.
619 //
620 // SAFETY: `dst` is live and its heap seats are null; each
621 // allocation below is checked, and each is attached to `dst`
622 // before the next one is attempted, so a failure part way leaves
623 // `out`'s own destructor a well-formed struct to free.
624 unsafe {
625 write_extradata(dst, self.extradata.as_slice(), stream_index)?;
626 write_side_data(dst, &self.coded_side_data, stream_index)?;
627 write_channel_layout(dst, &self.ch_layout, stream_index)?;
628 }
629
630 Ok(out)
631 }
632
633 /// What [`Self::rebuild`] asks FFmpeg's allocator for: `extradata`
634 /// with the `AV_INPUT_BUFFER_PADDING_SIZE` decoders read past the
635 /// end into, the `coded_side_data` descriptor array and every
636 /// entry's payload, and a custom channel map.
637 ///
638 /// The number the session admitted this stream at, and the number
639 /// `DemuxLimits::max_codec_parameter_bytes` was judged against — so
640 /// a row that opened is a row whose every rebuild fits the ceiling
641 /// it opened under.
642 ///
643 /// Not the ticket's own residency: the owned mirror holds the
644 /// payload without FFmpeg's trailing padding, and shares its buffers
645 /// by refcount.
646 #[cfg_attr(not(tarpaulin), inline(always))]
647 pub const fn footprint_bytes(&self) -> usize {
648 self.footprint_bytes
649 }
650
651 /// The `AVStream.index` this mirror was taken at — what
652 /// [`Self::rebuild`]'s errors name.
653 #[cfg_attr(not(tarpaulin), inline(always))]
654 pub const fn stream_index(&self) -> usize {
655 self.stream_index
656 }
657 /// The raw `AVMediaType`.
658 #[cfg_attr(not(tarpaulin), inline(always))]
659 pub const fn codec_type(&self) -> i32 {
660 self.codec_type
661 }
662 /// The raw `AVCodecID`.
663 #[cfg_attr(not(tarpaulin), inline(always))]
664 pub const fn codec_id(&self) -> i32 {
665 self.codec_id
666 }
667 /// The codec tag — the AVI FOURCC, when the container carries one.
668 #[cfg_attr(not(tarpaulin), inline(always))]
669 pub const fn codec_tag(&self) -> u32 {
670 self.codec_tag
671 }
672 /// The decoder-initialisation bytes — SPS/PPS for H.264, the
673 /// `AudioSpecificConfig` for AAC, a font's payload for an
674 /// attachment. Empty when the stream carries none, or when the row
675 /// was built on the attachment road that leaves them to the carrier.
676 #[cfg_attr(not(tarpaulin), inline(always))]
677 pub fn extradata(&self) -> &[u8] {
678 self.extradata.as_slice()
679 }
680 /// The extradata's carrier, for a consumer that wants the bytes
681 /// without copying them again.
682 #[cfg_attr(not(tarpaulin), inline(always))]
683 pub const fn extradata_ref(&self) -> &FfmpegBytes {
684 &self.extradata
685 }
686 /// Stream-level side data — where a MOV `prof` atom's ICC profile
687 /// arrives, among others.
688 #[cfg_attr(not(tarpaulin), inline(always))]
689 pub fn coded_side_data(&self) -> &[SideDataEntry] {
690 self.coded_side_data.as_slice()
691 }
692 /// The Dolby Vision configuration record — profile number and base-
693 /// layer compatibility id — from the container's `dvcC` / `dvvC` /
694 /// `dwvC` box, when the stream carries one.
695 ///
696 /// `None` when [`Self::coded_side_data`] holds no
697 /// `AV_PKT_DATA_DOVI_CONF` entry (an ordinary, non-Dolby-Vision
698 /// stream — the overwhelming majority) or the entry's payload is too
699 /// short to hold both seats. Absent configuration answers absent,
700 /// same as every other seat this crate exposes as an `Option`.
701 ///
702 /// This is the **configuration-record** half of Dolby Vision — the
703 /// two numbers a consumer routes base-layer-vs-refuse on before a
704 /// single frame decodes. The **per-frame** half — the RPU buffer
705 /// (`AV_FRAME_DATA_DOVI_RPU_BUFFER`) and parsed dynamic metadata
706 /// (`AV_FRAME_DATA_DOVI_METADATA`), plus HDR10+ dynamic metadata
707 /// (`AV_FRAME_DATA_DYNAMIC_HDR_PLUS`) — is not exposed by this crate
708 /// yet: [mediadecode#54](https://github.com/findit-studio/mediadecode/issues/54).
709 #[cfg_attr(not(tarpaulin), inline(always))]
710 pub fn dolby_vision_config(&self) -> Option<DolbyVisionConfig> {
711 let kind = AVPacketSideDataType::AV_PKT_DATA_DOVI_CONF as i32;
712 self
713 .coded_side_data
714 .iter()
715 .find(|entry| entry.kind() == kind)
716 .and_then(|entry| parse_dolby_vision_config(entry.data()))
717 }
718 /// The pixel format (video) or sample format (audio), as the raw
719 /// integer both enums share this seat as.
720 #[cfg_attr(not(tarpaulin), inline(always))]
721 pub const fn format(&self) -> i32 {
722 self.format
723 }
724 /// Average bitrate in bits per second.
725 #[cfg_attr(not(tarpaulin), inline(always))]
726 pub const fn bit_rate(&self) -> i64 {
727 self.bit_rate
728 }
729 /// Bits per sample in the coded bitstream.
730 #[cfg_attr(not(tarpaulin), inline(always))]
731 pub const fn bits_per_coded_sample(&self) -> i32 {
732 self.bits_per_coded_sample
733 }
734 /// Valid bits in each output sample.
735 #[cfg_attr(not(tarpaulin), inline(always))]
736 pub const fn bits_per_raw_sample(&self) -> i32 {
737 self.bits_per_raw_sample
738 }
739 /// The codec profile.
740 #[cfg_attr(not(tarpaulin), inline(always))]
741 pub const fn profile(&self) -> i32 {
742 self.profile
743 }
744 /// The codec level.
745 #[cfg_attr(not(tarpaulin), inline(always))]
746 pub const fn level(&self) -> i32 {
747 self.level
748 }
749 /// Frame width in pixels — video, and the subtitle canvas.
750 #[cfg_attr(not(tarpaulin), inline(always))]
751 pub const fn width(&self) -> i32 {
752 self.width
753 }
754 /// Frame height in pixels — video, and the subtitle canvas.
755 #[cfg_attr(not(tarpaulin), inline(always))]
756 pub const fn height(&self) -> i32 {
757 self.height
758 }
759 /// The sample aspect ratio. A zero numerator means unknown.
760 #[cfg_attr(not(tarpaulin), inline(always))]
761 pub const fn sample_aspect_ratio(&self) -> Ratio {
762 self.sample_aspect_ratio
763 }
764 /// The codec-level frame rate. `0/1` when frames differ in duration
765 /// or the value is not known.
766 #[cfg_attr(not(tarpaulin), inline(always))]
767 pub const fn framerate(&self) -> Ratio {
768 self.framerate
769 }
770 /// The raw `AVFieldOrder`.
771 #[cfg_attr(not(tarpaulin), inline(always))]
772 pub const fn field_order(&self) -> i32 {
773 self.field_order
774 }
775 /// The raw `AVColorRange`.
776 #[cfg_attr(not(tarpaulin), inline(always))]
777 pub const fn color_range(&self) -> i32 {
778 self.color_range
779 }
780 /// The raw `AVColorPrimaries`.
781 #[cfg_attr(not(tarpaulin), inline(always))]
782 pub const fn color_primaries(&self) -> i32 {
783 self.color_primaries
784 }
785 /// The raw `AVColorTransferCharacteristic`.
786 #[cfg_attr(not(tarpaulin), inline(always))]
787 pub const fn color_trc(&self) -> i32 {
788 self.color_trc
789 }
790 /// The raw `AVColorSpace`.
791 #[cfg_attr(not(tarpaulin), inline(always))]
792 pub const fn color_space(&self) -> i32 {
793 self.color_space
794 }
795 /// The raw `AVChromaLocation`.
796 #[cfg_attr(not(tarpaulin), inline(always))]
797 pub const fn chroma_location(&self) -> i32 {
798 self.chroma_location
799 }
800 /// Number of delayed frames — the decoder's `has_b_frames`.
801 #[cfg_attr(not(tarpaulin), inline(always))]
802 pub const fn video_delay(&self) -> i32 {
803 self.video_delay
804 }
805 /// The channel layout.
806 #[cfg_attr(not(tarpaulin), inline(always))]
807 pub const fn ch_layout(&self) -> &ChannelLayoutTicket {
808 &self.ch_layout
809 }
810 /// Audio samples per second.
811 #[cfg_attr(not(tarpaulin), inline(always))]
812 pub const fn sample_rate(&self) -> i32 {
813 self.sample_rate
814 }
815 /// Bytes per coded audio frame — `nBlockAlign` in `WAVEFORMATEX`.
816 #[cfg_attr(not(tarpaulin), inline(always))]
817 pub const fn block_align(&self) -> i32 {
818 self.block_align
819 }
820 /// Audio frame size, when the format fixes one.
821 #[cfg_attr(not(tarpaulin), inline(always))]
822 pub const fn frame_size(&self) -> i32 {
823 self.frame_size
824 }
825 /// Leading padding samples the encoder inserted.
826 #[cfg_attr(not(tarpaulin), inline(always))]
827 pub const fn initial_padding(&self) -> i32 {
828 self.initial_padding
829 }
830 /// Trailing padding samples the encoder appended.
831 #[cfg_attr(not(tarpaulin), inline(always))]
832 pub const fn trailing_padding(&self) -> i32 {
833 self.trailing_padding
834 }
835 /// Samples to skip after a discontinuity.
836 #[cfg_attr(not(tarpaulin), inline(always))]
837 pub const fn seek_preroll(&self) -> i32 {
838 self.seek_preroll
839 }
840 /// The raw `AVAlphaMode` — how an alpha channel relates to the
841 /// colour values, and the last field `AVCodecParameters` declares.
842 ///
843 /// New in FFmpeg n9.0, and the seat this mirror's first draft
844 /// dropped: video-only, left at its zero by every fixture the corpus
845 /// can mint, and therefore reading back identically whether it is
846 /// mirrored or forgotten. The parity comparator names every field
847 /// for exactly that reason.
848 #[cfg_attr(not(tarpaulin), inline(always))]
849 pub const fn alpha_mode(&self) -> i32 {
850 self.alpha_mode
851 }
852}
853
854impl core::fmt::Debug for CodecTicket {
855 /// Sizes rather than payloads. An `extradata` blob and an ICC
856 /// profile are both megabyte-scale and neither is readable; what a
857 /// reader of a log wants is the stream's identity and whether the
858 /// heap seats are populated.
859 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
860 f.debug_struct("CodecTicket")
861 .field(
862 "medium",
863 &crate::boundary::media_kind_from_raw(self.codec_type),
864 )
865 .field("codec_id", &self.codec_id)
866 .field("codec_tag", &format_args!("{:#010x}", self.codec_tag))
867 .field("format", &self.format)
868 .field("width", &self.width)
869 .field("height", &self.height)
870 .field("sample_rate", &self.sample_rate)
871 .field("channels", &self.ch_layout.channels)
872 .field("extradata_len", &self.extradata.len())
873 .field("coded_side_data", &self.coded_side_data.len())
874 .field("footprint_bytes", &self.footprint_bytes)
875 .finish_non_exhaustive()
876 }
877}
878
879// ---------------------------------------------------------------------------
880// Reading the three heap seats.
881// ---------------------------------------------------------------------------
882
883/// Copies `extradata` into an owned carrier — the payload only.
884///
885/// FFmpeg's `AV_INPUT_BUFFER_PADDING_SIZE` trailing zeroes are an
886/// allocation contract, not content: decoders read past the end of the
887/// buffer and the padding is what makes that defined. Carrying them in
888/// the mirror would store zeroes an owned `Arc<[u8]>` needs no reader
889/// to be safe past; [`write_extradata`] mints them again on the way
890/// back out, which is where they mean something.
891///
892/// # Safety
893///
894/// `par` must be a live `*const AVCodecParameters`.
895unsafe fn extradata_of(
896 par: *const AVCodecParameters,
897 policy: ExtradataPolicy,
898 stream_index: usize,
899) -> Result<FfmpegBytes, DemuxError> {
900 if matches!(policy, ExtradataPolicy::Omit) {
901 return Ok(FfmpegBytes::empty());
902 }
903 // SAFETY: `par` is live per the contract; both fields are a pointer
904 // and an integer.
905 let (ptr, size) = unsafe { ((*par).extradata, (*par).extradata_size) };
906 let Ok(len) = usize::try_from(size) else {
907 return Ok(FfmpegBytes::empty());
908 };
909 if ptr.is_null() || len == 0 {
910 return Ok(FfmpegBytes::empty());
911 }
912 // SAFETY: libavformat guarantees `extradata` is readable for
913 // `extradata_size` bytes while the parameters live, and the slice is
914 // consumed before this function returns.
915 // **Fallibly**, because the size is the container's: the footprint
916 // above admitted it against the caller's ceiling, and an admitted
917 // size that then aborts the process is a budget that did not do its
918 // job.
919 FfmpegBytes::try_copy_from_slice(unsafe { core::slice::from_raw_parts(ptr, len) })
920 .ok_or_else(|| DemuxError::ParametersAlloc(ParametersAlloc::new(stream_index)))
921}
922
923/// Copies every `coded_side_data` entry into owned entries.
924///
925/// # Safety
926///
927/// `par` must be a live `*const AVCodecParameters`.
928unsafe fn side_data_of(
929 par: *const AVCodecParameters,
930 stream_index: usize,
931) -> Result<Vec<SideDataEntry>, DemuxError> {
932 // SAFETY: `par` is live per the contract.
933 let (array, count) = unsafe { ((*par).coded_side_data, (*par).nb_coded_side_data) };
934 let alloc = || DemuxError::ParametersAlloc(ParametersAlloc::new(stream_index));
935 let Ok(count) = usize::try_from(count) else {
936 return Ok(Vec::new());
937 };
938 if array.is_null() || count == 0 {
939 return Ok(Vec::new());
940 }
941 // The descriptor table, reserved fallibly: `nb_coded_side_data` is
942 // the container's number too.
943 let mut entries = Vec::new();
944 entries.try_reserve_exact(count).map_err(|_| alloc())?;
945 for index in 0..count {
946 // **Never `&*entry`.** `AVPacketSideData::type` is an open C enum
947 // and an ABI-compatible FFmpeg newer than these bindings emits
948 // kinds absent from the generated Rust enum; forming a typed
949 // reference asserts every field inhabits its declared type, which
950 // is undefined behaviour before a single field is read.
951 //
952 // SAFETY: the array is valid for `nb_coded_side_data` contiguous
953 // entries per FFmpeg's contract, `index` is below that count, and
954 // `addr_of!` computes a field address without forming a reference
955 // to the struct containing it.
956 let (kind, data, size) = unsafe {
957 let entry = array.add(index);
958 (
959 read_unaligned(addr_of!((*entry).type_).cast::<i32>()),
960 read_unaligned(addr_of!((*entry).data)),
961 read_unaligned(addr_of!((*entry).size)),
962 )
963 };
964 let payload = if data.is_null() || size == 0 {
965 FfmpegBytes::empty()
966 } else {
967 // SAFETY: the descriptor declares `size` readable bytes at
968 // `data`, and the slice is consumed before the loop advances.
969 FfmpegBytes::try_copy_from_slice(unsafe { core::slice::from_raw_parts(data, size) })
970 .ok_or_else(alloc)?
971 };
972 entries.push(SideDataEntry::new(kind, payload));
973 }
974 Ok(entries)
975}
976
977/// **Every refusal a channel layout can earn from the container's own
978/// declared facts — and not one byte allocated deciding them.**
979///
980/// Split out of [`channel_layout_of`] so the judging can happen where
981/// the paying has not: `admit_streams` calls this for every stream
982/// before the track table reserves a row, so a malformed layout on the
983/// *last* stream refuses the open before the *first* stream's ticket is
984/// copied. `channel_layout_of` calls it again as its own first
985/// statement, which is what lets the build below carry no refusals at
986/// all and makes the two passes one rule rather than two.
987///
988/// Judged here, in this order:
989///
990/// - a non-null `ch_layout.opaque`, which this crate has no way to
991/// mirror and will not silently drop;
992/// - every structural fault of a `CUSTOM` map —a negative or zero
993/// channel count, a null `u.map`, or a sixteen-byte name with no NUL
994/// inside it — through
995/// [`custom_map_fault`](crate::channel_layout::custom_map_fault),
996/// which is the single place that rule is written and which the
997/// describe road applies too. `av_channel_layout_copy` (which
998/// `avcodec_parameters_to_context` moves this field through)
999/// allocates `nb_channels` entries and `memcpy`s from `src->u.map`
1000/// with no null check of its own, and `av_channel_layout_describe`
1001/// hands each fixed `name` array to a `%s` conversion; carrying
1002/// either across would be a faithful round trip of a crash;
1003/// - a non-null `opaque` on any entry of that map, which only this road
1004/// cares about because only this road mirrors it.
1005///
1006/// Refusing the incomplete map here is also what lets
1007/// [`write_channel_layout`] rely on `map.len() == nb_channels` for a
1008/// custom order.
1009///
1010/// # Safety
1011///
1012/// `par` must be a live `*const AVCodecParameters`, and for a `CUSTOM`
1013/// order with a non-null `u.map` that map must hold `nb_channels` live
1014/// `AVChannelCustom` entries — FFmpeg's own contract for a layout it
1015/// filled, which is the only kind this crate hands in.
1016pub(crate) unsafe fn validate_channel_layout(
1017 par: *const AVCodecParameters,
1018 stream_index: usize,
1019) -> Result<(), DemuxError> {
1020 // SAFETY: `ch_layout` is embedded by value; `addr_of!` reaches each
1021 // field without forming a reference to the layout, and `order` has
1022 // the layout of a `c_int`.
1023 let (order, channels, opaque) = unsafe {
1024 (
1025 read_unaligned(addr_of!((*par).ch_layout.order).cast::<i32>()),
1026 (*par).ch_layout.nb_channels,
1027 (*par).ch_layout.opaque,
1028 )
1029 };
1030 if !opaque.is_null() {
1031 return Err(DemuxError::ParametersOpaque(ParametersOpaque::new(
1032 stream_index,
1033 None,
1034 )));
1035 }
1036 // **The layout's whole structure, through the one function that
1037 // decides it — every order, not only `CUSTOM`.**
1038 //
1039 // The custom map's rules (null map, non-positive count, a name with
1040 // no NUL) and the other orders' (a count outside FFmpeg's arithmetic
1041 // range, a `NATIVE` mask that disagrees with its count, an
1042 // `AMBISONIC` layout whose channels form no ambisonic order) are one
1043 // preflight, and the describe road, the decoder and the resampler
1044 // apply exactly this one — so the pass that admits and the road that
1045 // materialises cannot come to different answers about a layout.
1046 //
1047 // An earlier round stopped here for every order but `CUSTOM`, on the
1048 // argument that a `uint64_t` mask cannot be malformed. The mask is
1049 // not the only field.
1050 //
1051 // SAFETY: this function's own contract, forwarded: `ch_layout` is
1052 // embedded in a live `AVCodecParameters`, `order` was read as the
1053 // `c_int` it is, and for a custom order its map holds `nb_channels`
1054 // entries.
1055 unsafe { crate::channel_layout::layout_preflight(addr_of!((*par).ch_layout)) }
1056 .map_err(|fault| crate::demuxer::layout_fault_to_demux(stream_index, fault))?;
1057
1058 if order != AVChannelOrder::AV_CHANNEL_ORDER_CUSTOM as i32 {
1059 return Ok(());
1060 }
1061 // The map is sound to walk now; what remains is the one thing the
1062 // describe road has no opinion about, because it never mirrors it.
1063 //
1064 // SAFETY: the order names the `map` arm, and the preflight above
1065 // proved it non-null with a positive count.
1066 let map = unsafe { (*par).ch_layout.u.map };
1067 let count = channels.max(0) as usize;
1068 for index in 0..count {
1069 // Field pointers, never `&AVChannelCustom`: `id` is an open enum
1070 // with the same hazard as a side-data type id.
1071 //
1072 // SAFETY: the contract above makes the map `count` entries long,
1073 // `index` is below that count, and the read goes through
1074 // `addr_of!`.
1075 let opaque = unsafe { read_unaligned(addr_of!((*map.add(index)).opaque)) };
1076 if !opaque.is_null() {
1077 return Err(DemuxError::ParametersOpaque(ParametersOpaque::new(
1078 stream_index,
1079 Some(index),
1080 )));
1081 }
1082 }
1083 Ok(())
1084}
1085
1086/// Mirrors the embedded `AVChannelLayout`.
1087///
1088/// Carries no refusals of its own beyond an allocator's: everything
1089/// this layout can be refused for is decided by
1090/// [`validate_channel_layout`], which runs first here and has already
1091/// run during admission.
1092///
1093/// # Safety
1094///
1095/// `par` must be a live `*const AVCodecParameters`, with
1096/// [`validate_channel_layout`]'s contract for a custom map.
1097unsafe fn channel_layout_of(
1098 par: *const AVCodecParameters,
1099 stream_index: usize,
1100) -> Result<ChannelLayoutTicket, DemuxError> {
1101 // SAFETY: the caller's contract, forwarded unchanged.
1102 unsafe { validate_channel_layout(par, stream_index) }?;
1103
1104 // SAFETY: as in the validation above — field reads through
1105 // `addr_of!`, with `order` read as the `c_int` it is.
1106 let (order, channels) = unsafe {
1107 (
1108 read_unaligned(addr_of!((*par).ch_layout.order).cast::<i32>()),
1109 (*par).ch_layout.nb_channels,
1110 )
1111 };
1112
1113 let mut layout = ChannelLayoutTicket {
1114 order,
1115 channels,
1116 mask: 0,
1117 map: Vec::new(),
1118 };
1119 match crate::channel_layout::LayoutArm::of(order) {
1120 // SAFETY: `NATIVE` and `AMBISONIC` are the two orders whose
1121 // contract defines `u.mask`, and this arm names exactly them.
1122 crate::channel_layout::LayoutArm::Mask => {
1123 layout.mask = unsafe { (*par).ch_layout.u.mask };
1124 return Ok(layout);
1125 }
1126 // **`UNSPEC`, and any order this build has never heard of: the
1127 // union is not read at all.** FFmpeg's own header declares it
1128 // undefined for an unspecified layout, and this mirror used to
1129 // treat "not custom" as "mask-backed" — importing whatever bytes
1130 // happened to sit there into a field that `mask()` hands out and
1131 // that the derived `Debug`, `PartialEq` and `Hash` then expose and
1132 // compare. The stored zero is a value this crate chose, not one it
1133 // read.
1134 crate::channel_layout::LayoutArm::Undefined => return Ok(layout),
1135 crate::channel_layout::LayoutArm::Map => {}
1136 }
1137
1138 // SAFETY: the order names the `map` arm, and the validation above
1139 // proved it non-null with a positive count.
1140 let map = unsafe { (*par).ch_layout.u.map };
1141 let count = channels.max(0) as usize;
1142 layout
1143 .map
1144 .try_reserve_exact(count)
1145 .map_err(|_| DemuxError::ParametersAlloc(ParametersAlloc::new(stream_index)))?;
1146 for index in 0..count {
1147 // SAFETY: FFmpeg's contract makes the map `count` entries long,
1148 // `index` is below that count, and every read goes through
1149 // `addr_of!`.
1150 let (id, name) = unsafe {
1151 let entry = map.add(index);
1152 (
1153 read_unaligned(addr_of!((*entry).id).cast::<i32>()),
1154 read_unaligned(addr_of!((*entry).name).cast::<[u8; 16]>()),
1155 )
1156 };
1157 layout.map.push(CustomChannel::new(id, name));
1158 }
1159 Ok(layout)
1160}
1161
1162// ---------------------------------------------------------------------------
1163// Writing the three heap seats.
1164// ---------------------------------------------------------------------------
1165
1166/// The `ENOMEM` a heap seat's allocation reports when it fails.
1167fn seat_alloc_failed(stream_index: usize) -> DemuxError {
1168 DemuxError::ParametersCopy(ParametersCopy::new(
1169 stream_index,
1170 ffmpeg_next::Error::Other {
1171 errno: libc::ENOMEM,
1172 },
1173 ))
1174}
1175
1176/// Allocates `extradata` and its padding, and copies the payload in.
1177///
1178/// # Safety
1179///
1180/// `dst` must be a live `*mut AVCodecParameters` whose `extradata` is
1181/// null.
1182unsafe fn write_extradata(
1183 dst: *mut AVCodecParameters,
1184 payload: &[u8],
1185 stream_index: usize,
1186) -> Result<(), DemuxError> {
1187 if payload.is_empty() {
1188 return Ok(());
1189 }
1190 let size = i32::try_from(payload.len()).map_err(|_| seat_alloc_failed(stream_index))?;
1191 let padded = payload
1192 .len()
1193 .checked_add(AV_INPUT_BUFFER_PADDING_SIZE as usize)
1194 .ok_or_else(|| seat_alloc_failed(stream_index))?;
1195 // SAFETY: `av_mallocz` returns zeroed memory or null; the copy
1196 // writes exactly `payload.len()` bytes into an allocation that is
1197 // `AV_INPUT_BUFFER_PADDING_SIZE` longer, leaving the padding zero —
1198 // which is the contract decoders read past the end under.
1199 unsafe {
1200 let buffer = av_mallocz(padded).cast::<u8>();
1201 if buffer.is_null() {
1202 return Err(seat_alloc_failed(stream_index));
1203 }
1204 copy_nonoverlapping(payload.as_ptr(), buffer, payload.len());
1205 (*dst).extradata = buffer;
1206 (*dst).extradata_size = size;
1207 }
1208 Ok(())
1209}
1210
1211/// Allocates the `coded_side_data` descriptor array and each payload.
1212///
1213/// # Safety
1214///
1215/// `dst` must be a live `*mut AVCodecParameters` whose
1216/// `coded_side_data` is null.
1217unsafe fn write_side_data(
1218 dst: *mut AVCodecParameters,
1219 entries: &[SideDataEntry],
1220 stream_index: usize,
1221) -> Result<(), DemuxError> {
1222 if entries.is_empty() {
1223 return Ok(());
1224 }
1225 let count = i32::try_from(entries.len()).map_err(|_| seat_alloc_failed(stream_index))?;
1226 let bytes = entries
1227 .len()
1228 .checked_mul(core::mem::size_of::<AVPacketSideData>())
1229 .ok_or_else(|| seat_alloc_failed(stream_index))?;
1230
1231 // SAFETY: `dst` is live with a null `coded_side_data`. The array is
1232 // attached before any payload is filled in, so a failure part way
1233 // leaves the destructor a well-formed array to walk: the entries it
1234 // has not reached are zeroed, and freeing a null payload is a no-op.
1235 unsafe {
1236 let array = av_mallocz(bytes).cast::<AVPacketSideData>();
1237 if array.is_null() {
1238 return Err(seat_alloc_failed(stream_index));
1239 }
1240 (*dst).coded_side_data = array;
1241 (*dst).nb_coded_side_data = count;
1242
1243 for (index, entry) in entries.iter().enumerate() {
1244 let into = array.add(index);
1245 // The type id travels as the raw bits it is on the wire, for the
1246 // reason the read did: a kind these bindings cannot name is
1247 // still a kind the file carries and a decoder may want.
1248 write_unaligned(addr_of_mut!((*into).type_).cast::<i32>(), entry.kind());
1249 let payload = entry.data();
1250 if payload.is_empty() {
1251 continue;
1252 }
1253 let buffer = av_mallocz(payload.len()).cast::<u8>();
1254 if buffer.is_null() {
1255 return Err(seat_alloc_failed(stream_index));
1256 }
1257 copy_nonoverlapping(payload.as_ptr(), buffer, payload.len());
1258 write_unaligned(addr_of_mut!((*into).data), buffer);
1259 write_unaligned(addr_of_mut!((*into).size), payload.len());
1260 }
1261 }
1262 Ok(())
1263}
1264
1265/// Writes the channel layout, allocating a custom map when the order
1266/// names one.
1267///
1268/// Written field by field rather than through
1269/// `av_channel_layout_copy`, because the source it would copy from is
1270/// the thing this road has abolished: there is no live
1271/// `AVChannelLayout` to copy, only owned Rust. The one allocation is
1272/// the custom map, which [`CodecTicket::mirror`] already measured and
1273/// admitted.
1274///
1275/// # Safety
1276///
1277/// `dst` must be a live `*mut AVCodecParameters` whose `ch_layout` is
1278/// the zeroed (`AV_CHANNEL_ORDER_UNSPEC`) state
1279/// `avcodec_parameters_alloc` leaves, owning no map.
1280unsafe fn write_channel_layout(
1281 dst: *mut AVCodecParameters,
1282 layout: &ChannelLayoutTicket,
1283 stream_index: usize,
1284) -> Result<(), DemuxError> {
1285 // SAFETY: `dst` is live and its layout owns nothing yet; `order` is
1286 // written as the same 32-bit pattern the mirror read.
1287 unsafe {
1288 write_unaligned(
1289 addr_of_mut!((*dst).ch_layout.order).cast::<i32>(),
1290 layout.order(),
1291 );
1292 (*dst).ch_layout.opaque = core::ptr::null_mut();
1293 }
1294
1295 match crate::channel_layout::LayoutArm::of(layout.order()) {
1296 // SAFETY: the order names the `mask` arm.
1297 crate::channel_layout::LayoutArm::Mask => {
1298 unsafe {
1299 (*dst).ch_layout.nb_channels = layout.channels();
1300 (*dst).ch_layout.u.mask = layout.mask();
1301 }
1302 return Ok(());
1303 }
1304 // **The union is left as `avcodec_parameters_alloc` zeroed it.**
1305 // An order that defines no arm gets no arm written: handing
1306 // libavcodec a mask for an unspecified layout would be inventing a
1307 // fact about a field FFmpeg says means nothing there, and the
1308 // mirror does not read one either.
1309 crate::channel_layout::LayoutArm::Undefined => {
1310 // SAFETY: `dst` is live and the count is a plain `int` field.
1311 unsafe {
1312 (*dst).ch_layout.nb_channels = layout.channels();
1313 }
1314 return Ok(());
1315 }
1316 crate::channel_layout::LayoutArm::Map => {}
1317 }
1318
1319 // **`nb_channels` comes from the map, not from the stored field.**
1320 // The two are equal — [`channel_layout_of`] refuses a custom layout
1321 // it cannot map in full, and the fields are private, so no other
1322 // value can exist. Writing the count from the array anyway is what
1323 // makes that structural rather than remembered: the layout handed to
1324 // libavcodec can never declare more channels than the array it points
1325 // at, which is precisely the shape `av_channel_layout_copy` would
1326 // `memcpy` past the end of.
1327 debug_assert_eq!(
1328 layout.map().len(),
1329 layout.channels().max(0) as usize,
1330 "a custom layout's map length is its channel count",
1331 );
1332 let count = layout.map().len();
1333 if count == 0 {
1334 // Unreachable through `mirror`, and fail-closed if a future
1335 // constructor ever makes it reachable.
1336 return Err(DemuxError::ParametersChannelMap(ParametersChannelMap::new(
1337 stream_index,
1338 layout.channels(),
1339 )));
1340 }
1341
1342 let declared = i32::try_from(count).map_err(|_| seat_alloc_failed(stream_index))?;
1343 let bytes = count
1344 .checked_mul(core::mem::size_of::<AVChannelCustom>())
1345 .ok_or_else(|| seat_alloc_failed(stream_index))?;
1346 // SAFETY: `av_mallocz` returns zeroed memory or null. The map and the
1347 // count it describes are attached together, before the entries are
1348 // filled in, so a later failure leaves a well-formed (zeroed) map for
1349 // the destructor to free, and every write goes through
1350 // `addr_of_mut!` rather than a typed reference.
1351 unsafe {
1352 let map = av_mallocz(bytes).cast::<AVChannelCustom>();
1353 if map.is_null() {
1354 return Err(seat_alloc_failed(stream_index));
1355 }
1356 (*dst).ch_layout.u.map = map;
1357 (*dst).ch_layout.nb_channels = declared;
1358 for (index, channel) in layout.map().iter().enumerate() {
1359 let into = map.add(index);
1360 write_unaligned(addr_of_mut!((*into).id).cast::<i32>(), channel.id());
1361 write_unaligned(
1362 addr_of_mut!((*into).name).cast::<[u8; 16]>(),
1363 *channel.name_bytes(),
1364 );
1365 write_unaligned(addr_of_mut!((*into).opaque), core::ptr::null_mut());
1366 }
1367 }
1368 Ok(())
1369}
1370
1371#[cfg(test)]
1372mod tests;