Skip to main content

zvidlib_container/
mp4_demux.rs

1//! Bounded, read-only ISO BMFF/MP4 probing and sample indexing.
2
3use crate::audio::{AudioEdit, AudioTrackTiming, EncodedAudioSample};
4use crate::codec::{EncodedVideoSample, SampleDependency, TrackKind};
5use crate::io::ByteSource;
6use crate::media::{Codec, VideoDimensions};
7use crate::mp4::{CoverArt, CoverArtFormat};
8use crate::opus::{OPUS_SAMPLE_RATE, OpusHead};
9use crate::timeline::FrameIndex;
10use crate::vorbis::VorbisConfig;
11use crate::{Error, ErrorKind, Limits, Result};
12use std::collections::{BTreeMap, BTreeSet};
13
14const HEADER_SIZE: usize = 8;
15
16/// Resource limits specific to untrusted MP4 structure.
17#[derive(Clone, Debug, Eq, PartialEq)]
18pub struct Mp4DemuxerOptions {
19    pub limits: Limits,
20    pub max_box_bytes: u64,
21    pub max_boxes: u32,
22    pub max_box_depth: u8,
23    pub max_samples_per_track: u32,
24}
25
26impl Default for Mp4DemuxerOptions {
27    fn default() -> Self {
28        Self {
29            limits: Limits::default(),
30            max_box_bytes: 64 * 1024 * 1024,
31            max_boxes: 100_000,
32            max_box_depth: 16,
33            max_samples_per_track: 10_000_000,
34        }
35    }
36}
37
38/// A movie-to-media timeline edit. A media time of `-1` is an empty edit.
39#[derive(Clone, Copy, Debug, Eq, PartialEq)]
40pub struct EditMapping {
41    pub segment_duration: u64,
42    pub media_time: i64,
43    pub media_rate_integer: i16,
44    pub media_rate_fraction: i16,
45}
46
47/// One encoded sample in decode order.
48#[derive(Clone, Debug, Eq, PartialEq)]
49pub struct Mp4Sample {
50    pub offset: u64,
51    pub size: u32,
52    pub dts: u64,
53    pub pts: i64,
54    pub duration: u32,
55    pub dependency: SampleDependency,
56    pub is_sync: bool,
57}
58
59/// Read-only metadata and indexes for one MP4 track.
60#[derive(Clone, Debug, Eq, PartialEq)]
61pub struct Mp4Track {
62    pub id: u32,
63    pub kind: TrackKind,
64    pub codec: Codec,
65    pub timescale: u32,
66    pub duration: u64,
67    pub dimensions: Option<VideoDimensions>,
68    pub channels: Option<u16>,
69    pub sample_rate: Option<u32>,
70    /// Complete codec configuration box, including its header.
71    pub decoder_config: Vec<u8>,
72    pub edits: Vec<EditMapping>,
73    /// Samples in decode order.
74    pub samples: Vec<Mp4Sample>,
75    /// Decode-order indexes of the presentation frames, sorted by PTS, then
76    /// DTS, then decode index. A decode-only sample, such as a hidden VP8
77    /// frame stored as a WebM block of its own, is decoded on the way to the
78    /// frames after it but is never presented, so it is not listed.
79    pub presentation_order: Vec<usize>,
80}
81
82impl Mp4Track {
83    /// Returns a sample by its zero-based presentation index.
84    pub fn presentation_sample(&self, index: usize) -> Option<&Mp4Sample> {
85        self.presentation_order
86            .get(index)
87            .and_then(|&decode_index| self.samples.get(decode_index))
88    }
89
90    /// Reads one decode-order sample without changing caller-visible source state.
91    pub async fn read_sample_into<S: ByteSource + ?Sized>(
92        &self,
93        source: &S,
94        decode_index: usize,
95        destination: &mut [u8],
96    ) -> Result<()> {
97        let sample = self
98            .samples
99            .get(decode_index)
100            .ok_or_else(|| invalid("MP4 sample index is out of range"))?;
101        if destination.len() != sample.size as usize {
102            return Err(invalid("sample destination size does not match the index"));
103        }
104        read_exact(source, sample.offset, destination).await
105    }
106
107    /// Reads every decode-order sample and returns owned
108    /// [`EncodedVideoSample`] values keyed by presentation order, ready for
109    /// a [`crate::codec::VideoDecoderFactory`] backend.
110    ///
111    /// The zero-based position of each sample within
112    /// [`Self::presentation_order`] becomes its `presentation_index`, and
113    /// `is_sync` becomes `random_access`. A decode-only sample, which
114    /// `presentation_order` does not list, takes an identity past the last
115    /// presentation frame, in decode order, so it never collides with a frame
116    /// a caller asks for. Total decoded sample bytes are bounded by
117    /// `limits.max_allocation_bytes`.
118    pub async fn to_encoded_video_samples<S: ByteSource + ?Sized>(
119        &self,
120        source: &S,
121        limits: &Limits,
122    ) -> Result<Vec<EncodedVideoSample>> {
123        if self.kind != TrackKind::Video {
124            return Err(unsupported("encoded video samples require a video track"));
125        }
126        let mut presentation_index_by_decode = vec![None; self.samples.len()];
127        for (presentation_index, &decode_index) in self.presentation_order.iter().enumerate() {
128            let presentation_index = u64::try_from(presentation_index)
129                .map_err(|_| limit("presentation index overflow"))?;
130            *presentation_index_by_decode
131                .get_mut(decode_index)
132                .ok_or_else(|| malformed("presentation order references a missing sample"))? =
133                Some(presentation_index);
134        }
135        let mut next_decode_only = self.presentation_order.len() as u64;
136        let presentation_index_by_decode: Vec<u64> = presentation_index_by_decode
137            .into_iter()
138            .map(|index| {
139                index.unwrap_or_else(|| {
140                    next_decode_only += 1;
141                    next_decode_only - 1
142                })
143            })
144            .collect();
145
146        let mut total_bytes = 0_u64;
147        let mut samples = Vec::with_capacity(self.samples.len());
148        for (decode_index, sample) in self.samples.iter().enumerate() {
149            total_bytes = total_bytes
150                .checked_add(u64::from(sample.size))
151                .ok_or_else(|| limit("encoded sample allocation overflow"))?;
152            if total_bytes > limits.max_allocation_bytes {
153                return Err(limit(
154                    "encoded video samples exceed the configured allocation limit",
155                ));
156            }
157            let mut data = vec![0_u8; sample.size as usize];
158            self.read_sample_into(source, decode_index, &mut data)
159                .await?;
160            samples.push(EncodedVideoSample {
161                presentation_index: FrameIndex(presentation_index_by_decode[decode_index]),
162                random_access: sample.is_sync,
163                data,
164            });
165        }
166        Ok(samples)
167    }
168
169    /// Parses the AAC AudioSpecificConfig carried by this track's `esds` box.
170    pub fn aac_config(&self) -> Result<AacTrackConfig> {
171        if self.kind != TrackKind::Audio || self.codec != Codec::Aac {
172            return Err(unsupported("AAC configuration requires an AAC audio track"));
173        }
174        let sample_rate = self
175            .sample_rate
176            .ok_or_else(|| malformed("AAC sample rate is missing"))?;
177        let channels = self
178            .channels
179            .ok_or_else(|| malformed("AAC channel count is missing"))?;
180        parse_aac_config(&self.decoder_config, sample_rate, channels)
181    }
182
183    /// Parses the Opus identification header carried by this track's `dOps`
184    /// box.
185    pub fn opus_config(&self) -> Result<OpusHead> {
186        if self.kind != TrackKind::Audio || self.codec != Codec::Opus {
187            return Err(unsupported(
188                "Opus configuration requires an Opus audio track",
189            ));
190        }
191        let head = OpusHead::from_dops(&self.decoder_config)?;
192        if self.channels != Some(u16::from(head.channels)) {
193            return Err(malformed(
194                "Opus dOps channel count disagrees with the sample entry",
195            ));
196        }
197        Ok(head)
198    }
199
200    /// Parses the three Vorbis header packets a WebM Vorbis track carries as
201    /// its `decoder_config` (see [`crate::WebmDemuxer`]); Vorbis has no MP4
202    /// sample entry.
203    pub fn vorbis_config(&self) -> Result<VorbisConfig> {
204        if self.kind != TrackKind::Audio || self.codec != Codec::Vorbis {
205            return Err(unsupported(
206                "Vorbis configuration requires a Vorbis audio track",
207            ));
208        }
209        VorbisConfig::from_codec_private(&self.decoder_config)
210    }
211
212    /// The sample rate this audio track decodes at: the AAC
213    /// `AudioSpecificConfig`'s or the Vorbis identification header's, or 48
214    /// kHz for Opus, which always decodes at that rate whatever its input was.
215    pub fn audio_sample_rate(&self) -> Result<u32> {
216        match self.codec {
217            Codec::Aac => Ok(self.aac_config()?.sample_rate),
218            Codec::Opus => {
219                self.opus_config()?;
220                Ok(OPUS_SAMPLE_RATE)
221            }
222            Codec::Vorbis => Ok(self.vorbis_config()?.sample_rate),
223            _ => Err(unsupported(
224                "audio packets require an AAC, Opus or Vorbis track",
225            )),
226        }
227    }
228
229    /// Reads every indexed audio packet - AAC access units, Opus or Vorbis
230    /// packets - from its validated byte range.
231    ///
232    /// Packet intervals use the decoded PCM sample clock and remain contiguous
233    /// even when the MP4 track timescale differs from the decoded sample rate.
234    pub async fn to_encoded_audio_samples<S: ByteSource + ?Sized>(
235        &self,
236        source: &S,
237        limits: &Limits,
238    ) -> Result<Vec<EncodedAudioSample>> {
239        let sample_rate = self.audio_sample_rate()?;
240        // A Vorbis packet's length depends on the one before it, so the
241        // intervals are assigned once every packet has been read.
242        let vorbis = if self.codec == Codec::Vorbis {
243            Some(self.vorbis_config()?)
244        } else {
245            None
246        };
247        let mut total_bytes = 0_u64;
248        let mut decoded_start = 0_u64;
249        let mut track_ticks = 0_u64;
250        let mut packets = Vec::with_capacity(self.samples.len());
251        for (decode_index, sample) in self.samples.iter().enumerate() {
252            total_bytes = total_bytes
253                .checked_add(u64::from(sample.size))
254                .ok_or_else(|| limit("encoded audio allocation overflow"))?;
255            if total_bytes > limits.max_allocation_bytes {
256                return Err(limit(
257                    "encoded audio samples exceed the configured allocation limit",
258                ));
259            }
260            let mut data = vec![0_u8; sample.size as usize];
261            self.read_sample_into(source, decode_index, &mut data)
262                .await?;
263            if vorbis.is_some() {
264                packets.push(EncodedAudioSample {
265                    decoded_range: crate::SampleRange::new(0, 0)?,
266                    data,
267                });
268                continue;
269            }
270            let decoded_end = if self.codec == Codec::Opus {
271                // An Opus packet's own table of contents says how long it
272                // decodes. Muxers shorten the last sample's duration to trim
273                // the stream's end - FFmpeg does - so the sample table cannot.
274                decoded_start
275                    .checked_add(u64::from(crate::opus::opus_packet_samples(&data)?))
276                    .ok_or_else(|| limit("audio track timing overflow"))?
277            } else {
278                track_ticks = track_ticks
279                    .checked_add(u64::from(sample.duration))
280                    .ok_or_else(|| limit("audio track timing overflow"))?;
281                scale_time(track_ticks, self.timescale, sample_rate)?
282            };
283            if decoded_end <= decoded_start {
284                return Err(malformed("audio packet has an empty decoded interval"));
285            }
286            packets.push(EncodedAudioSample {
287                decoded_range: crate::SampleRange::new(decoded_start, decoded_end)?,
288                data,
289            });
290            decoded_start = decoded_end;
291        }
292        if packets.is_empty() {
293            return Err(malformed("audio track contains no samples"));
294        }
295        if let Some(vorbis) = vorbis {
296            return vorbis.encoded_samples(packets.into_iter().map(|packet| packet.data).collect());
297        }
298        Ok(packets)
299    }
300
301    /// Converts MP4 edit-list timing to the presentation sample clock used by
302    /// [`crate::AudioSampleReader`], including decoder priming and end padding.
303    ///
304    /// An Opus track without an edit list still has its `dOps` pre-skip
305    /// trimmed as priming, and ends where its sample table does: the packets
306    /// themselves can decode past that end, which is how a muxer that writes
307    /// no edit list trims the stream.
308    pub fn audio_timing(&self, movie_timescale: u32) -> Result<AudioTrackTiming> {
309        let sample_rate = self.audio_sample_rate()?;
310        let decoded_length = scale_time(self.duration, self.timescale, sample_rate)?;
311        if self.edits.is_empty() {
312            if self.codec != Codec::Opus {
313                return Ok(AudioTrackTiming::default());
314            }
315            let priming = self.opus_config()?.pre_skip;
316            let length = decoded_length
317                .checked_sub(u64::from(priming))
318                .filter(|&length| length > 0)
319                .ok_or_else(|| malformed("Opus pre-skip covers the whole track"))?;
320            return Ok(AudioTrackTiming {
321                priming: u32::from(priming),
322                padding: 0,
323                track_offset: 0,
324                edits: vec![AudioEdit {
325                    presentation: crate::SampleRange::new(0, length)?,
326                    media_start: Some(u64::from(priming)),
327                }],
328            });
329        }
330        let mut presentation_start = 0_u64;
331        let mut edits = Vec::with_capacity(self.edits.len());
332        let mut first_media = None;
333        let mut last_media_end = 0_u64;
334        for edit in &self.edits {
335            if edit.media_rate_integer != 1 || edit.media_rate_fraction != 0 {
336                return Err(unsupported(
337                    "audio edit rates other than 1.0 are unsupported",
338                ));
339            }
340            let length = scale_time(edit.segment_duration, movie_timescale, sample_rate)?;
341            if length == 0 {
342                return Err(malformed("audio edit has an empty presentation interval"));
343            }
344            let presentation_end = presentation_start
345                .checked_add(length)
346                .ok_or_else(|| limit("audio edit presentation overflow"))?;
347            let media_start = if edit.media_time == -1 {
348                None
349            } else {
350                if edit.media_time < 0 {
351                    return Err(malformed("audio edit has an invalid negative media time"));
352                }
353                let start = scale_time(edit.media_time as u64, self.timescale, sample_rate)?;
354                let end = start
355                    .checked_add(length)
356                    .ok_or_else(|| limit("audio edit media range overflow"))?;
357                first_media = Some(first_media.map_or(start, |value: u64| value.min(start)));
358                last_media_end = last_media_end.max(end);
359                Some(start)
360            };
361            edits.push(AudioEdit {
362                presentation: crate::SampleRange::new(presentation_start, presentation_end)?,
363                media_start,
364            });
365            presentation_start = presentation_end;
366        }
367        let priming = u32::try_from(first_media.unwrap_or(0))
368            .map_err(|_| limit("audio priming exceeds the supported range"))?;
369        let padding = u32::try_from(decoded_length.saturating_sub(last_media_end))
370            .map_err(|_| limit("audio padding exceeds the supported range"))?;
371        Ok(AudioTrackTiming {
372            priming,
373            padding,
374            track_offset: 0,
375            edits,
376        })
377    }
378}
379
380/// Decoder configuration extracted from an AAC `mp4a` / `esds` description.
381#[derive(Clone, Debug, Eq, PartialEq)]
382pub struct AacTrackConfig {
383    pub audio_object_type: u8,
384    pub sample_rate: u32,
385    pub channels: u16,
386    pub audio_specific_config: Vec<u8>,
387}
388
389/// Parsed MP4 movie metadata and indexes.
390#[derive(Clone, Debug, Eq, PartialEq)]
391pub struct Mp4Demuxer {
392    pub movie_timescale: u32,
393    pub tracks: Vec<Mp4Track>,
394    /// The first JPEG or PNG picture in iTunes-style
395    /// `moov/udta/meta/ilst/covr` metadata, if the file has one.
396    pub cover_art: Option<CoverArt>,
397}
398
399/// Bounded format detection. At most 32 bytes are requested.
400pub async fn probe_mp4<S: ByteSource + ?Sized>(source: &S) -> Result<bool> {
401    if source
402        .len()
403        .is_some_and(|length| length < HEADER_SIZE as u64)
404    {
405        return Ok(false);
406    }
407    let mut bytes = [0_u8; HEADER_SIZE];
408    let mut read = 0;
409    while read < bytes.len() {
410        let count = source.read_at(read as u64, &mut bytes[read..]).await?;
411        if count == 0 {
412            return Ok(false);
413        }
414        read += count;
415    }
416    let size = u32::from_be_bytes(bytes[..4].try_into().expect("fixed slice"));
417    if size != 0 && size != 1 && size < 8 {
418        return Ok(false);
419    }
420    Ok(&bytes[4..8] == b"ftyp" || &bytes[4..8] == b"moov")
421}
422
423impl Mp4Demuxer {
424    /// Incrementally scans top-level boxes and reads only bounded metadata boxes.
425    pub async fn open<S: ByteSource + ?Sized>(
426        source: &S,
427        options: Mp4DemuxerOptions,
428    ) -> Result<Self> {
429        validate_options(&options)?;
430        let source_len = source
431            .len()
432            .ok_or_else(|| unsupported("MP4 indexing requires a source length"))?;
433        let mut cursor = 0_u64;
434        let mut budget = Budget::new(&options);
435        let mut movie = None;
436        let mut fragments = Vec::new();
437        let mut media_ranges = Vec::new();
438        while cursor < source_len {
439            let header = read_header(source, cursor, source_len, &mut budget).await?;
440            match &header.kind {
441                b"moov" => {
442                    if movie.is_some() {
443                        return Err(malformed("multiple movie boxes are not supported"));
444                    }
445                    let bytes = read_payload(source, header, &options).await?;
446                    movie = Some(parse_moov(&bytes, &mut budget, &options)?);
447                }
448                b"moof" => {
449                    let bytes = read_payload(source, header, &options).await?;
450                    ensure_allocation(
451                        fragments.len() + 1,
452                        std::mem::size_of::<Fragment>(),
453                        &options,
454                        "movie fragments",
455                    )?;
456                    fragments.push(parse_moof(&bytes, header.start, &mut budget, &options)?);
457                }
458                b"mdat" => {
459                    ensure_allocation(
460                        media_ranges.len() + 1,
461                        std::mem::size_of::<(u64, u64)>(),
462                        &options,
463                        "media data ranges",
464                    )?;
465                    media_ranges.push((header.payload_start, header.end));
466                }
467                _ => {}
468            }
469            cursor = header.end;
470        }
471        let ParsedMovie {
472            mut demuxer,
473            fragment_defaults,
474        } = movie.ok_or_else(|| malformed("MP4 does not contain a movie box"))?;
475        append_fragments(&mut demuxer, fragments, &fragment_defaults, &options)?;
476        for track in &mut demuxer.tracks {
477            track
478                .samples
479                .sort_by_key(|sample| (sample.dts, sample.offset));
480            validate_samples(track, source_len, &media_ranges)?;
481            for sample in &track.samples {
482                track.duration = track.duration.max(
483                    sample
484                        .dts
485                        .checked_add(u64::from(sample.duration))
486                        .ok_or_else(|| limit("track duration overflow"))?,
487                );
488            }
489            ensure_allocation(
490                track.samples.len(),
491                std::mem::size_of::<usize>(),
492                &options,
493                "presentation index",
494            )?;
495            track.presentation_order = (0..track.samples.len()).collect();
496            track.presentation_order.sort_by_key(|&index| {
497                let sample = &track.samples[index];
498                (sample.pts, sample.dts, index)
499            });
500        }
501        Ok(demuxer)
502    }
503
504    pub fn track(&self, id: u32) -> Option<&Mp4Track> {
505        self.tracks.iter().find(|track| track.id == id)
506    }
507}
508
509#[derive(Clone, Copy)]
510struct Header {
511    start: u64,
512    payload_start: u64,
513    end: u64,
514    kind: [u8; 4],
515}
516
517struct Budget {
518    boxes: u32,
519    max_boxes: u32,
520}
521
522impl Budget {
523    fn new(options: &Mp4DemuxerOptions) -> Self {
524        Self {
525            boxes: 0,
526            max_boxes: options.max_boxes,
527        }
528    }
529
530    fn box_seen(&mut self) -> Result<()> {
531        self.boxes = self
532            .boxes
533            .checked_add(1)
534            .ok_or_else(|| limit("MP4 box count overflow"))?;
535        if self.boxes > self.max_boxes {
536            return Err(limit("MP4 box count limit exceeded"));
537        }
538        Ok(())
539    }
540}
541
542async fn read_header<S: ByteSource + ?Sized>(
543    source: &S,
544    start: u64,
545    parent_end: u64,
546    budget: &mut Budget,
547) -> Result<Header> {
548    budget.box_seen()?;
549    let remaining = parent_end
550        .checked_sub(start)
551        .ok_or_else(|| malformed("box offset exceeds parent"))?;
552    if remaining < 8 {
553        return Err(malformed("truncated MP4 box header"));
554    }
555    let mut basic = [0_u8; 16];
556    read_exact(source, start, &mut basic[..8]).await?;
557    let size32 = u32::from_be_bytes(basic[..4].try_into().expect("fixed slice"));
558    let kind = basic[4..8].try_into().expect("fixed slice");
559    let (size, header_size) = match size32 {
560        0 => (remaining, 8_u64),
561        1 => {
562            if remaining < 16 {
563                return Err(malformed("truncated extended MP4 box header"));
564            }
565            read_exact(source, start + 8, &mut basic[8..16]).await?;
566            (
567                u64::from_be_bytes(basic[8..16].try_into().expect("fixed slice")),
568                16,
569            )
570        }
571        value => (u64::from(value), 8),
572    };
573    if size < header_size {
574        return Err(malformed("MP4 box is smaller than its header"));
575    }
576    let end = start
577        .checked_add(size)
578        .ok_or_else(|| malformed("MP4 box end overflow"))?;
579    if end > parent_end {
580        return Err(malformed("MP4 box exceeds its parent or source"));
581    }
582    Ok(Header {
583        start,
584        payload_start: start + header_size,
585        end,
586        kind,
587    })
588}
589
590async fn read_exact<S: ByteSource + ?Sized>(
591    source: &S,
592    mut offset: u64,
593    mut output: &mut [u8],
594) -> Result<()> {
595    while !output.is_empty() {
596        let read = source.read_at(offset, output).await?;
597        if read == 0 {
598            return Err(malformed("unexpected end of MP4 source"));
599        }
600        offset = offset
601            .checked_add(read as u64)
602            .ok_or_else(|| malformed("read offset overflow"))?;
603        output = &mut output[read..];
604    }
605    Ok(())
606}
607
608async fn read_payload<S: ByteSource + ?Sized>(
609    source: &S,
610    header: Header,
611    options: &Mp4DemuxerOptions,
612) -> Result<Vec<u8>> {
613    let length = header.end - header.payload_start;
614    if length > options.max_box_bytes || length > options.limits.max_allocation_bytes {
615        return Err(limit("MP4 metadata box allocation limit exceeded"));
616    }
617    let mut bytes = vec![
618        0;
619        usize::try_from(length)
620            .map_err(|_| limit("MP4 box is too large for this platform"))?
621    ];
622    read_exact(source, header.payload_start, &mut bytes).await?;
623    Ok(bytes)
624}
625
626#[derive(Default)]
627struct TrackBuilder {
628    id: Option<u32>,
629    kind: Option<TrackKind>,
630    codec: Option<Codec>,
631    timescale: Option<u32>,
632    duration: u64,
633    dimensions: Option<VideoDimensions>,
634    channels: Option<u16>,
635    sample_rate: Option<u32>,
636    decoder_config: Vec<u8>,
637    edits: Vec<EditMapping>,
638    stts: Vec<(u32, u32)>,
639    ctts: Vec<(u32, i64)>,
640    stsc: Vec<(u32, u32, u32)>,
641    sizes: Vec<u32>,
642    chunks: Vec<u64>,
643    sync: Option<BTreeSet<u32>>,
644    dependencies: Vec<SampleDependency>,
645    samples: Vec<Mp4Sample>,
646    ignored: bool,
647}
648
649#[derive(Default)]
650struct MovieBuilder {
651    timescale: Option<u32>,
652    tracks: Vec<TrackBuilder>,
653    trex: BTreeMap<u32, Trex>,
654}
655
656struct ParsedMovie {
657    demuxer: Mp4Demuxer,
658    fragment_defaults: BTreeMap<u32, Trex>,
659}
660
661fn parse_moov(
662    bytes: &[u8],
663    budget: &mut Budget,
664    options: &Mp4DemuxerOptions,
665) -> Result<ParsedMovie> {
666    let mut builder = MovieBuilder::default();
667    let mut cover_art = None;
668    for child in children(bytes, 1, budget, options)? {
669        match &child.kind {
670            b"mvhd" => builder.timescale = Some(parse_mvhd(child.payload)?),
671            b"trak" => builder
672                .tracks
673                .push(parse_trak(child.payload, budget, options)?),
674            b"mvex" => parse_mvex(child.payload, &mut builder.trex, budget, options)?,
675            b"udta" if cover_art.is_none() => {
676                // User data is optional metadata that files in the wild often
677                // lay out loosely, so a malformed `udta` never fails the open;
678                // only exceeding a resource limit does.
679                cover_art = match parse_udta_cover_art(child.payload, budget, options) {
680                    Ok(cover_art) => cover_art,
681                    Err(error) if error.kind() == ErrorKind::ResourceLimit => return Err(error),
682                    Err(_) => None,
683                };
684            }
685            _ => {}
686        }
687    }
688    if builder.tracks.len() > usize::from(options.limits.max_tracks) {
689        return Err(limit("MP4 track count limit exceeded"));
690    }
691    let movie_timescale = builder
692        .timescale
693        .ok_or_else(|| malformed("movie header is missing"))?;
694    if movie_timescale == 0 {
695        return Err(malformed("movie timescale is zero"));
696    }
697    let mut ids = BTreeSet::new();
698    let mut tracks = Vec::with_capacity(builder.tracks.len());
699    for track in builder.tracks {
700        let id = track
701            .id
702            .ok_or_else(|| malformed("track header is missing"))?;
703        if id == 0 || !ids.insert(id) {
704            return Err(malformed("MP4 track IDs must be unique and nonzero"));
705        }
706        if track.ignored {
707            continue;
708        }
709        tracks.push(finalize_track(track, options)?);
710    }
711    Ok(ParsedMovie {
712        demuxer: Mp4Demuxer {
713            movie_timescale,
714            tracks,
715            cover_art,
716        },
717        fragment_defaults: builder.trex,
718    })
719}
720
721/// Finds the first JPEG or PNG `data` box under `udta/meta/ilst/covr`.
722fn parse_udta_cover_art(
723    bytes: &[u8],
724    budget: &mut Budget,
725    options: &Mp4DemuxerOptions,
726) -> Result<Option<CoverArt>> {
727    for meta in children(bytes, 2, budget, options)? {
728        if &meta.kind != b"meta" {
729            continue;
730        }
731        // ISO BMFF makes `meta` a full box; QuickTime writes it as a plain
732        // container, recognizable by `hdlr` starting right after the header.
733        let items = if meta.payload.get(4..8) == Some(b"hdlr") {
734            meta.payload
735        } else {
736            full(meta.payload)?.1
737        };
738        for ilst in children(items, 3, budget, options)? {
739            if &ilst.kind != b"ilst" {
740                continue;
741            }
742            for covr in children(ilst.payload, 4, budget, options)? {
743                if &covr.kind != b"covr" {
744                    continue;
745                }
746                for data in children(covr.payload, 5, budget, options)? {
747                    if &data.kind != b"data" {
748                        continue;
749                    }
750                    let Some(format) = CoverArtFormat::from_data_type(be_u32(data.payload, 0)?)
751                    else {
752                        continue;
753                    };
754                    let image = slice(data.payload, 8, data.payload.len().saturating_sub(8))?;
755                    if image.is_empty() {
756                        continue;
757                    }
758                    ensure_allocation(image.len(), 1, options, "cover art")?;
759                    return Ok(Some(CoverArt {
760                        format,
761                        data: image.to_vec(),
762                    }));
763                }
764            }
765        }
766    }
767    Ok(None)
768}
769
770fn parse_mvhd(payload: &[u8]) -> Result<u32> {
771    let (version, body) = full(payload)?;
772    let offset = match version {
773        0 => 8,
774        1 => 16,
775        _ => return Err(malformed("unsupported mvhd version")),
776    };
777    be_u32(body, offset)
778}
779
780fn parse_trak(
781    bytes: &[u8],
782    budget: &mut Budget,
783    options: &Mp4DemuxerOptions,
784) -> Result<TrackBuilder> {
785    let mut track = TrackBuilder::default();
786    for child in children(bytes, 2, budget, options)? {
787        match &child.kind {
788            b"tkhd" => parse_tkhd(child.payload, &mut track, options)?,
789            b"edts" => parse_edts(child.payload, &mut track, budget, options)?,
790            b"mdia" => parse_mdia(child.payload, &mut track, budget, options)?,
791            _ => {}
792        }
793    }
794    Ok(track)
795}
796
797fn parse_tkhd(payload: &[u8], track: &mut TrackBuilder, options: &Mp4DemuxerOptions) -> Result<()> {
798    let (version, body) = full(payload)?;
799    let (id_offset, width_offset) = match version {
800        0 => (8, 72),
801        1 => (16, 84),
802        _ => return Err(malformed("unsupported tkhd version")),
803    };
804    track.id = Some(be_u32(body, id_offset)?);
805    let width = be_u32(body, width_offset)? >> 16;
806    let height = be_u32(body, width_offset + 4)? >> 16;
807    if width != 0 || height != 0 {
808        track.dimensions = Some(VideoDimensions::new(width, height, &options.limits)?);
809    }
810    Ok(())
811}
812
813fn parse_edts(
814    bytes: &[u8],
815    track: &mut TrackBuilder,
816    budget: &mut Budget,
817    options: &Mp4DemuxerOptions,
818) -> Result<()> {
819    for child in children(bytes, 3, budget, options)? {
820        if &child.kind != b"elst" {
821            continue;
822        }
823        let (version, body) = full(child.payload)?;
824        let count = count(body, 0, options.max_samples_per_track, "edit")?;
825        ensure_allocation(
826            count,
827            std::mem::size_of::<EditMapping>(),
828            options,
829            "edit list",
830        )?;
831        let entry_size = if version == 1 {
832            20
833        } else if version == 0 {
834            12
835        } else {
836            return Err(malformed("unsupported elst version"));
837        };
838        require(
839            body,
840            4,
841            count
842                .checked_mul(entry_size)
843                .ok_or_else(|| malformed("edit table overflow"))?,
844        )?;
845        for index in 0..count {
846            let at = 4 + index * entry_size;
847            let (segment_duration, media_time, rate_at) = if version == 1 {
848                (be_u64(body, at)?, be_i64(body, at + 8)?, at + 16)
849            } else {
850                (
851                    u64::from(be_u32(body, at)?),
852                    i64::from(be_i32(body, at + 4)?),
853                    at + 8,
854                )
855            };
856            track.edits.push(EditMapping {
857                segment_duration,
858                media_time,
859                media_rate_integer: be_i16(body, rate_at)?,
860                media_rate_fraction: be_i16(body, rate_at + 2)?,
861            });
862        }
863    }
864    Ok(())
865}
866
867fn parse_mdia(
868    bytes: &[u8],
869    track: &mut TrackBuilder,
870    budget: &mut Budget,
871    options: &Mp4DemuxerOptions,
872) -> Result<()> {
873    for child in children(bytes, 3, budget, options)? {
874        match &child.kind {
875            b"mdhd" => parse_mdhd(child.payload, track)?,
876            b"hdlr" => parse_hdlr(child.payload, track)?,
877            b"minf" => parse_minf(child.payload, track, budget, options)?,
878            _ => {}
879        }
880    }
881    Ok(())
882}
883
884fn parse_mdhd(payload: &[u8], track: &mut TrackBuilder) -> Result<()> {
885    let (version, body) = full(payload)?;
886    let (timescale_at, duration_at) = match version {
887        0 => (8, 12),
888        1 => (16, 20),
889        _ => return Err(malformed("unsupported mdhd version")),
890    };
891    let timescale = be_u32(body, timescale_at)?;
892    if timescale == 0 {
893        return Err(malformed("track timescale is zero"));
894    }
895    track.timescale = Some(timescale);
896    track.duration = if version == 0 {
897        u64::from(be_u32(body, duration_at)?)
898    } else {
899        be_u64(body, duration_at)?
900    };
901    Ok(())
902}
903
904fn parse_hdlr(payload: &[u8], track: &mut TrackBuilder) -> Result<()> {
905    let (_, body) = full(payload)?;
906    track.kind = match slice(body, 4, 4)? {
907        b"vide" => Some(TrackKind::Video),
908        b"soun" => Some(TrackKind::Audio),
909        _ => {
910            track.ignored = true;
911            None
912        }
913    };
914    Ok(())
915}
916
917fn parse_minf(
918    bytes: &[u8],
919    track: &mut TrackBuilder,
920    budget: &mut Budget,
921    options: &Mp4DemuxerOptions,
922) -> Result<()> {
923    if track.ignored {
924        return Ok(());
925    }
926    for child in children(bytes, 4, budget, options)? {
927        if &child.kind == b"stbl" {
928            parse_stbl(child.payload, track, budget, options)?;
929        }
930    }
931    Ok(())
932}
933
934fn parse_stbl(
935    bytes: &[u8],
936    track: &mut TrackBuilder,
937    budget: &mut Budget,
938    options: &Mp4DemuxerOptions,
939) -> Result<()> {
940    for child in children(bytes, 5, budget, options)? {
941        match &child.kind {
942            b"stsd" => parse_stsd(child.payload, track, budget, options)?,
943            b"stts" => track.stts = parse_u32_pairs(child.payload, options, "stts")?,
944            b"ctts" => track.ctts = parse_ctts(child.payload, options)?,
945            b"stsc" => track.stsc = parse_u32_triples(child.payload, options)?,
946            b"stsz" => track.sizes = parse_stsz(child.payload, options)?,
947            b"stz2" => track.sizes = parse_stz2(child.payload, options)?,
948            b"stco" => track.chunks = parse_offsets(child.payload, options, false)?,
949            b"co64" => track.chunks = parse_offsets(child.payload, options, true)?,
950            b"stss" => track.sync = Some(parse_sync(child.payload, options)?),
951            b"sdtp" => track.dependencies = parse_sdtp(child.payload, options)?,
952            _ => {}
953        }
954    }
955    Ok(())
956}
957
958fn parse_stsd(
959    payload: &[u8],
960    track: &mut TrackBuilder,
961    budget: &mut Budget,
962    options: &Mp4DemuxerOptions,
963) -> Result<()> {
964    if track.ignored {
965        return Ok(());
966    }
967    let (_, body) = full(payload)?;
968    let entries = count(body, 0, 16, "sample description")?;
969    if entries != 1 {
970        return Err(unsupported(
971            "exactly one MP4 sample description is supported",
972        ));
973    }
974    let entry = one_child(&body[4..], 6, budget, options)?;
975    let (codec, prefix, kind) = match &entry.kind {
976        b"avc1" | b"avc3" => (Codec::H264, 78, TrackKind::Video),
977        b"hvc1" | b"hev1" => (Codec::Hevc, 78, TrackKind::Video),
978        b"av01" => (Codec::Av1, 78, TrackKind::Video),
979        b"vp09" => (Codec::Vp9, 78, TrackKind::Video),
980        b"mp4a" => (Codec::Aac, 28, TrackKind::Audio),
981        b"Opus" => (Codec::Opus, 28, TrackKind::Audio),
982        _ => return Err(unsupported("unsupported MP4 sample entry")),
983    };
984    if let Some(handler) = track.kind
985        && handler != kind
986    {
987        return Err(malformed("sample entry and track handler disagree"));
988    }
989    if kind == TrackKind::Video {
990        let width = u32::from(be_u16(entry.payload, 24)?);
991        let height = u32::from(be_u16(entry.payload, 26)?);
992        track.dimensions = Some(VideoDimensions::new(width, height, &options.limits)?);
993    } else {
994        track.channels = Some(be_u16(entry.payload, 16)?);
995        let fixed_rate = be_u32(entry.payload, 24)?;
996        if fixed_rate & 0xffff != 0 {
997            return Err(unsupported("fractional audio sample rates are unsupported"));
998        }
999        track.sample_rate = Some(fixed_rate >> 16);
1000    }
1001    let config_kind = match codec {
1002        Codec::H264 => b"avcC",
1003        Codec::Hevc => b"hvcC",
1004        Codec::Av1 => b"av1C",
1005        Codec::Vp9 => b"vpcC",
1006        Codec::Aac => b"esds",
1007        Codec::Opus => b"dOps",
1008        Codec::UncompressedVideo | Codec::Vp8 => {
1009            return Err(unsupported(
1010                "uncompressed MP4 sample entries are not supported",
1011            ));
1012        }
1013        Codec::Vorbis => return Err(unsupported("Vorbis has no MP4 sample entry")),
1014        _ => return Err(unsupported("unsupported MP4 sample entry codec")),
1015    };
1016    let nested = children(&entry.payload[prefix..], 7, budget, options)?;
1017    let config = nested
1018        .into_iter()
1019        .find(|child| &child.kind == config_kind)
1020        .ok_or_else(|| malformed("codec configuration box is missing"))?;
1021    track.decoder_config = config.whole.to_vec();
1022    track.codec = Some(codec);
1023    Ok(())
1024}
1025
1026fn parse_u32_pairs(
1027    payload: &[u8],
1028    options: &Mp4DemuxerOptions,
1029    label: &str,
1030) -> Result<Vec<(u32, u32)>> {
1031    let (_, body) = full(payload)?;
1032    let count = count(body, 0, options.max_samples_per_track, label)?;
1033    ensure_allocation(count, std::mem::size_of::<(u32, u32)>(), options, label)?;
1034    require(body, 4, checked_product(count, 8, label)?)?;
1035    (0..count)
1036        .map(|i| Ok((be_u32(body, 4 + i * 8)?, be_u32(body, 8 + i * 8)?)))
1037        .collect()
1038}
1039
1040fn parse_ctts(payload: &[u8], options: &Mp4DemuxerOptions) -> Result<Vec<(u32, i64)>> {
1041    let (version, body) = full(payload)?;
1042    let count = count(body, 0, options.max_samples_per_track, "ctts")?;
1043    ensure_allocation(count, std::mem::size_of::<(u32, i64)>(), options, "ctts")?;
1044    require(body, 4, checked_product(count, 8, "ctts")?)?;
1045    (0..count)
1046        .map(|i| {
1047            let raw = be_u32(body, 8 + i * 8)?;
1048            Ok((
1049                be_u32(body, 4 + i * 8)?,
1050                if version == 0 {
1051                    i64::from(raw)
1052                } else if version == 1 {
1053                    i64::from(raw as i32)
1054                } else {
1055                    return Err(malformed("unsupported ctts version"));
1056                },
1057            ))
1058        })
1059        .collect()
1060}
1061
1062fn parse_u32_triples(payload: &[u8], options: &Mp4DemuxerOptions) -> Result<Vec<(u32, u32, u32)>> {
1063    let (_, body) = full(payload)?;
1064    let count = count(body, 0, options.max_samples_per_track, "stsc")?;
1065    ensure_allocation(
1066        count,
1067        std::mem::size_of::<(u32, u32, u32)>(),
1068        options,
1069        "stsc",
1070    )?;
1071    require(body, 4, checked_product(count, 12, "stsc")?)?;
1072    (0..count)
1073        .map(|i| {
1074            Ok((
1075                be_u32(body, 4 + i * 12)?,
1076                be_u32(body, 8 + i * 12)?,
1077                be_u32(body, 12 + i * 12)?,
1078            ))
1079        })
1080        .collect()
1081}
1082
1083fn parse_stsz(payload: &[u8], options: &Mp4DemuxerOptions) -> Result<Vec<u32>> {
1084    let (_, body) = full(payload)?;
1085    let fixed = be_u32(body, 0)?;
1086    let count = count(body, 4, options.max_samples_per_track, "sample")?;
1087    ensure_allocation(count, std::mem::size_of::<u32>(), options, "sample sizes")?;
1088    if fixed != 0 {
1089        return Ok(vec![fixed; count]);
1090    }
1091    require(body, 8, checked_product(count, 4, "sample sizes")?)?;
1092    (0..count).map(|i| be_u32(body, 8 + i * 4)).collect()
1093}
1094
1095fn parse_stz2(payload: &[u8], options: &Mp4DemuxerOptions) -> Result<Vec<u32>> {
1096    let (_, body) = full(payload)?;
1097    let field_size = slice(body, 3, 1)?[0];
1098    let count = count(body, 4, options.max_samples_per_track, "sample")?;
1099    ensure_allocation(count, std::mem::size_of::<u32>(), options, "sample sizes")?;
1100    let encoded_bytes = match field_size {
1101        4 => count.div_ceil(2),
1102        8 => count,
1103        16 => checked_product(count, 2, "compact sample sizes")?,
1104        _ => return Err(malformed("unsupported compact sample size width")),
1105    };
1106    let encoded = slice(body, 8, encoded_bytes)?;
1107    let mut sizes = Vec::with_capacity(count);
1108    for index in 0..count {
1109        sizes.push(match field_size {
1110            4 => {
1111                let byte = encoded[index / 2];
1112                u32::from(if index % 2 == 0 {
1113                    byte >> 4
1114                } else {
1115                    byte & 0x0f
1116                })
1117            }
1118            8 => u32::from(encoded[index]),
1119            16 => u32::from(u16::from_be_bytes(
1120                encoded[index * 2..index * 2 + 2]
1121                    .try_into()
1122                    .expect("validated compact sample size"),
1123            )),
1124            _ => unreachable!("validated compact sample size width"),
1125        });
1126    }
1127    Ok(sizes)
1128}
1129
1130fn parse_offsets(payload: &[u8], options: &Mp4DemuxerOptions, wide: bool) -> Result<Vec<u64>> {
1131    let (_, body) = full(payload)?;
1132    let count = count(body, 0, options.max_samples_per_track, "chunk")?;
1133    ensure_allocation(count, std::mem::size_of::<u64>(), options, "chunk offsets")?;
1134    let width = if wide { 8 } else { 4 };
1135    require(body, 4, checked_product(count, width, "chunk offsets")?)?;
1136    (0..count)
1137        .map(|i| {
1138            if wide {
1139                be_u64(body, 4 + i * 8)
1140            } else {
1141                be_u32(body, 4 + i * 4).map(u64::from)
1142            }
1143        })
1144        .collect()
1145}
1146
1147fn parse_sync(payload: &[u8], options: &Mp4DemuxerOptions) -> Result<BTreeSet<u32>> {
1148    let (_, body) = full(payload)?;
1149    let count = count(body, 0, options.max_samples_per_track, "sync sample")?;
1150    ensure_allocation(count, std::mem::size_of::<u32>(), options, "sync samples")?;
1151    require(body, 4, checked_product(count, 4, "sync samples")?)?;
1152    (0..count).map(|i| be_u32(body, 4 + i * 4)).collect()
1153}
1154
1155fn parse_sdtp(payload: &[u8], options: &Mp4DemuxerOptions) -> Result<Vec<SampleDependency>> {
1156    let (_, body) = full(payload)?;
1157    if body.len() > options.max_samples_per_track as usize {
1158        return Err(limit("sample dependency count limit exceeded"));
1159    }
1160    ensure_allocation(
1161        body.len(),
1162        std::mem::size_of::<SampleDependency>(),
1163        options,
1164        "sample dependencies",
1165    )?;
1166    Ok(body
1167        .iter()
1168        .map(|&v| SampleDependency {
1169            is_leading: v >> 6,
1170            depends_on: (v >> 4) & 3,
1171            is_depended_on: (v >> 2) & 3,
1172            has_redundancy: v & 3,
1173        })
1174        .collect())
1175}
1176
1177fn finalize_track(mut b: TrackBuilder, options: &Mp4DemuxerOptions) -> Result<Mp4Track> {
1178    let id = b.id.ok_or_else(|| malformed("track ID is missing"))?;
1179    let kind = b
1180        .kind
1181        .ok_or_else(|| malformed("track handler is missing"))?;
1182    let codec = b
1183        .codec
1184        .ok_or_else(|| malformed("sample description is missing"))?;
1185    let timescale = b
1186        .timescale
1187        .ok_or_else(|| malformed("media header is missing"))?;
1188    if b.sizes.len() > options.max_samples_per_track as usize {
1189        return Err(limit("sample count limit exceeded"));
1190    }
1191    ensure_allocation(
1192        b.sizes.len(),
1193        std::mem::size_of::<Mp4Sample>(),
1194        options,
1195        "sample index",
1196    )?;
1197    ensure_allocation(
1198        b.sizes.len(),
1199        std::mem::size_of::<i64>(),
1200        options,
1201        "expanded timing table",
1202    )?;
1203    if b.sync.as_ref().is_some_and(|sync| {
1204        sync.iter()
1205            .any(|&index| index == 0 || index as usize > b.sizes.len())
1206    }) {
1207        return Err(malformed("sync sample index is out of range"));
1208    }
1209    if !b.dependencies.is_empty() && b.dependencies.len() != b.sizes.len() {
1210        return Err(malformed("sample dependency count does not match samples"));
1211    }
1212    if !b.sizes.is_empty() {
1213        let durations = expand_runs(&b.stts, b.sizes.len(), "decode timing")?;
1214        let offsets = if b.ctts.is_empty() {
1215            vec![0; b.sizes.len()]
1216        } else {
1217            expand_signed_runs(&b.ctts, b.sizes.len(), "composition timing")?
1218        };
1219        let sample_offsets = expand_chunks(&b.stsc, &b.chunks, &b.sizes)?;
1220        let mut dts = 0_u64;
1221        for i in 0..b.sizes.len() {
1222            let duration = durations[i];
1223            if duration == 0 {
1224                return Err(malformed("sample duration is zero"));
1225            }
1226            let pts = i64::try_from(dts)
1227                .map_err(|_| limit("decode timestamp exceeds i64"))?
1228                .checked_add(offsets[i])
1229                .ok_or_else(|| malformed("presentation timestamp overflow"))?;
1230            let number = u32::try_from(i + 1).map_err(|_| limit("sample index overflow"))?;
1231            let is_sync = b.sync.as_ref().is_none_or(|s| s.contains(&number));
1232            let dependency = b.dependencies.get(i).copied().unwrap_or(if is_sync {
1233                SampleDependency::INDEPENDENT
1234            } else {
1235                SampleDependency::DEPENDENT
1236            });
1237            b.samples.push(Mp4Sample {
1238                offset: sample_offsets[i],
1239                size: b.sizes[i],
1240                dts,
1241                pts,
1242                duration,
1243                dependency,
1244                is_sync,
1245            });
1246            dts = dts
1247                .checked_add(u64::from(duration))
1248                .ok_or_else(|| limit("decode timestamp overflow"))?;
1249        }
1250    }
1251    Ok(Mp4Track {
1252        id,
1253        kind,
1254        codec,
1255        timescale,
1256        duration: b.duration,
1257        dimensions: b.dimensions,
1258        channels: b.channels,
1259        sample_rate: b.sample_rate,
1260        decoder_config: b.decoder_config,
1261        edits: b.edits,
1262        samples: b.samples,
1263        presentation_order: Vec::new(),
1264    })
1265}
1266
1267fn expand_runs(runs: &[(u32, u32)], expected: usize, label: &str) -> Result<Vec<u32>> {
1268    let mut out = Vec::with_capacity(expected);
1269    for &(count, value) in runs {
1270        let count = usize::try_from(count).map_err(|_| limit(format!("{label} count overflow")))?;
1271        if out.len().checked_add(count).is_none_or(|n| n > expected) {
1272            return Err(malformed(format!("{label} count exceeds samples")));
1273        }
1274        out.extend(std::iter::repeat_n(value, count));
1275    }
1276    if out.len() != expected {
1277        return Err(malformed(format!("{label} count does not match samples")));
1278    }
1279    Ok(out)
1280}
1281fn expand_signed_runs(runs: &[(u32, i64)], expected: usize, label: &str) -> Result<Vec<i64>> {
1282    let mut out = Vec::with_capacity(expected);
1283    for &(count, value) in runs {
1284        let count = count as usize;
1285        if out.len().checked_add(count).is_none_or(|n| n > expected) {
1286            return Err(malformed(format!("{label} count exceeds samples")));
1287        }
1288        out.extend(std::iter::repeat_n(value, count));
1289    }
1290    if out.len() != expected {
1291        return Err(malformed(format!("{label} count does not match samples")));
1292    }
1293    Ok(out)
1294}
1295
1296fn expand_chunks(stsc: &[(u32, u32, u32)], chunks: &[u64], sizes: &[u32]) -> Result<Vec<u64>> {
1297    if sizes.is_empty() {
1298        return Ok(Vec::new());
1299    }
1300    if stsc.is_empty() || chunks.is_empty() {
1301        return Err(malformed("sample-to-chunk or chunk offsets are missing"));
1302    }
1303    if stsc[0].0 != 1 {
1304        return Err(malformed("first stsc entry must start at chunk one"));
1305    }
1306    if stsc.windows(2).any(|entries| entries[0].0 >= entries[1].0) {
1307        return Err(malformed("stsc first-chunk values are not increasing"));
1308    }
1309    if stsc.iter().any(|entry| entry.1 == 0 || entry.2 != 1) {
1310        return Err(malformed("invalid or unsupported stsc entry"));
1311    }
1312    let mut out = Vec::with_capacity(sizes.len());
1313    let mut sample = 0;
1314    for (ci, &chunk_offset) in chunks.iter().enumerate() {
1315        let chunk_number = u32::try_from(ci + 1).map_err(|_| limit("chunk index overflow"))?;
1316        let entry = stsc
1317            .iter()
1318            .rev()
1319            .find(|e| e.0 <= chunk_number)
1320            .ok_or_else(|| malformed("chunk has no stsc mapping"))?;
1321        let mut offset = chunk_offset;
1322        for _ in 0..entry.1 {
1323            if sample >= sizes.len() {
1324                return Err(malformed("chunks contain more samples than stsz"));
1325            }
1326            out.push(offset);
1327            offset = offset
1328                .checked_add(u64::from(sizes[sample]))
1329                .ok_or_else(|| malformed("sample offset overflow"))?;
1330            sample += 1;
1331        }
1332    }
1333    if sample != sizes.len() {
1334        return Err(malformed("chunks contain fewer samples than stsz"));
1335    }
1336    Ok(out)
1337}
1338
1339#[derive(Clone, Copy, Default)]
1340struct Trex {
1341    duration: u32,
1342    size: u32,
1343    flags: u32,
1344}
1345fn parse_mvex(
1346    bytes: &[u8],
1347    trex: &mut BTreeMap<u32, Trex>,
1348    budget: &mut Budget,
1349    options: &Mp4DemuxerOptions,
1350) -> Result<()> {
1351    for child in children(bytes, 2, budget, options)? {
1352        if &child.kind == b"trex" {
1353            let (_, body) = full(child.payload)?;
1354            let id = be_u32(body, 0)?;
1355            trex.insert(
1356                id,
1357                Trex {
1358                    duration: be_u32(body, 8)?,
1359                    size: be_u32(body, 12)?,
1360                    flags: be_u32(body, 16)?,
1361                },
1362            );
1363        }
1364    }
1365    Ok(())
1366}
1367
1368struct Fragment {
1369    moof_start: u64,
1370    trafs: Vec<Traf>,
1371}
1372#[derive(Default)]
1373struct Traf {
1374    track_id: u32,
1375    base: u64,
1376    decode_time: u64,
1377    default_duration: u32,
1378    default_size: u32,
1379    default_flags: u32,
1380    runs: Vec<Run>,
1381}
1382struct Run {
1383    data_offset: Option<i32>,
1384    first_flags: Option<u32>,
1385    samples: Vec<FragSample>,
1386}
1387#[derive(Default)]
1388struct FragSample {
1389    duration: Option<u32>,
1390    size: Option<u32>,
1391    flags: Option<u32>,
1392    cto: i64,
1393}
1394
1395fn parse_moof(
1396    bytes: &[u8],
1397    start: u64,
1398    budget: &mut Budget,
1399    options: &Mp4DemuxerOptions,
1400) -> Result<Fragment> {
1401    let mut trafs = Vec::new();
1402    for child in children(bytes, 1, budget, options)? {
1403        if &child.kind == b"traf" {
1404            trafs.push(parse_traf(child.payload, start, budget, options)?);
1405        }
1406    }
1407    Ok(Fragment {
1408        moof_start: start,
1409        trafs,
1410    })
1411}
1412fn parse_traf(
1413    bytes: &[u8],
1414    moof_start: u64,
1415    budget: &mut Budget,
1416    options: &Mp4DemuxerOptions,
1417) -> Result<Traf> {
1418    let mut traf = Traf {
1419        base: moof_start,
1420        ..Traf::default()
1421    };
1422    for child in children(bytes, 2, budget, options)? {
1423        match &child.kind {
1424            b"tfhd" => parse_tfhd(child.payload, &mut traf, moof_start)?,
1425            b"tfdt" => parse_tfdt(child.payload, &mut traf)?,
1426            b"trun" => traf.runs.push(parse_trun(child.payload, options)?),
1427            _ => {}
1428        }
1429    }
1430    if traf.track_id == 0 {
1431        return Err(malformed("fragment track ID is missing"));
1432    }
1433    Ok(traf)
1434}
1435fn parse_tfhd(payload: &[u8], traf: &mut Traf, moof_start: u64) -> Result<()> {
1436    let (_, flags, body) = full_flags(payload)?;
1437    let mut at = 0;
1438    traf.track_id = be_u32(body, at)?;
1439    at += 4;
1440    if flags & 1 != 0 {
1441        traf.base = be_u64(body, at)?;
1442        at += 8;
1443    } else if flags & 0x020000 != 0 {
1444        traf.base = moof_start;
1445    }
1446    if flags & 2 != 0 {
1447        at += 4;
1448    }
1449    if flags & 8 != 0 {
1450        traf.default_duration = be_u32(body, at)?;
1451        at += 4;
1452    }
1453    if flags & 0x10 != 0 {
1454        traf.default_size = be_u32(body, at)?;
1455        at += 4;
1456    }
1457    if flags & 0x20 != 0 {
1458        traf.default_flags = be_u32(body, at)?;
1459    }
1460    Ok(())
1461}
1462fn parse_tfdt(payload: &[u8], traf: &mut Traf) -> Result<()> {
1463    let (version, body) = full(payload)?;
1464    traf.decode_time = if version == 1 {
1465        be_u64(body, 0)?
1466    } else if version == 0 {
1467        u64::from(be_u32(body, 0)?)
1468    } else {
1469        return Err(malformed("unsupported tfdt version"));
1470    };
1471    Ok(())
1472}
1473fn parse_trun(payload: &[u8], options: &Mp4DemuxerOptions) -> Result<Run> {
1474    let (version, flags, body) = full_flags(payload)?;
1475    let count = count(body, 0, options.max_samples_per_track, "fragment sample")?;
1476    ensure_allocation(
1477        count,
1478        std::mem::size_of::<FragSample>(),
1479        options,
1480        "fragment samples",
1481    )?;
1482    let mut at = 4;
1483    let data_offset = if flags & 1 != 0 {
1484        let v = be_i32(body, at)?;
1485        at += 4;
1486        Some(v)
1487    } else {
1488        None
1489    };
1490    let first_flags = if flags & 4 != 0 {
1491        let v = be_u32(body, at)?;
1492        at += 4;
1493        Some(v)
1494    } else {
1495        None
1496    };
1497    let mut samples = Vec::with_capacity(count);
1498    for _ in 0..count {
1499        let mut s = FragSample::default();
1500        if flags & 0x100 != 0 {
1501            s.duration = Some(be_u32(body, at)?);
1502            at += 4;
1503        }
1504        if flags & 0x200 != 0 {
1505            s.size = Some(be_u32(body, at)?);
1506            at += 4;
1507        }
1508        if flags & 0x400 != 0 {
1509            s.flags = Some(be_u32(body, at)?);
1510            at += 4;
1511        }
1512        if flags & 0x800 != 0 {
1513            let raw = be_u32(body, at)?;
1514            at += 4;
1515            s.cto = if version == 0 {
1516                i64::from(raw)
1517            } else if version == 1 {
1518                i64::from(raw as i32)
1519            } else {
1520                return Err(malformed("unsupported trun version"));
1521            };
1522        }
1523        samples.push(s);
1524    }
1525    Ok(Run {
1526        data_offset,
1527        first_flags,
1528        samples,
1529    })
1530}
1531
1532fn append_fragments(
1533    movie: &mut Mp4Demuxer,
1534    fragments: Vec<Fragment>,
1535    trex: &BTreeMap<u32, Trex>,
1536    options: &Mp4DemuxerOptions,
1537) -> Result<()> {
1538    for fragment in fragments {
1539        for traf in fragment.trafs {
1540            let track = movie
1541                .tracks
1542                .iter_mut()
1543                .find(|t| t.id == traf.track_id)
1544                .ok_or_else(|| malformed("fragment references an unknown track"))?;
1545            let defaults = trex.get(&traf.track_id).copied().unwrap_or_default();
1546            let mut dts = traf.decode_time;
1547            let mut next_data = None;
1548            for run in traf.runs {
1549                let mut offset = if let Some(relative) = run.data_offset {
1550                    add_signed(traf.base, relative)?
1551                } else {
1552                    next_data.unwrap_or(fragment.moof_start)
1553                };
1554                for (i, s) in run.samples.into_iter().enumerate() {
1555                    if track.samples.len() >= options.max_samples_per_track as usize {
1556                        return Err(limit("fragment sample count limit exceeded"));
1557                    }
1558                    ensure_allocation(
1559                        track.samples.len() + 1,
1560                        std::mem::size_of::<Mp4Sample>(),
1561                        options,
1562                        "sample index",
1563                    )?;
1564                    let duration = s
1565                        .duration
1566                        .or(nonzero(traf.default_duration))
1567                        .or(nonzero(defaults.duration))
1568                        .ok_or_else(|| malformed("fragment sample duration is missing"))?;
1569                    let size = s
1570                        .size
1571                        .or(nonzero(traf.default_size))
1572                        .or(nonzero(defaults.size))
1573                        .ok_or_else(|| malformed("fragment sample size is missing"))?;
1574                    let flags = s
1575                        .flags
1576                        .or(if i == 0 { run.first_flags } else { None })
1577                        .unwrap_or(if traf.default_flags != 0 {
1578                            traf.default_flags
1579                        } else {
1580                            defaults.flags
1581                        });
1582                    let is_sync = flags & 0x0001_0000 == 0;
1583                    let dependency = dependency_from_flags(flags, is_sync);
1584                    let pts = i64::try_from(dts)
1585                        .map_err(|_| limit("fragment DTS exceeds i64"))?
1586                        .checked_add(s.cto)
1587                        .ok_or_else(|| malformed("fragment PTS overflow"))?;
1588                    track.samples.push(Mp4Sample {
1589                        offset,
1590                        size,
1591                        dts,
1592                        pts,
1593                        duration,
1594                        dependency,
1595                        is_sync,
1596                    });
1597                    offset = offset
1598                        .checked_add(u64::from(size))
1599                        .ok_or_else(|| malformed("fragment data offset overflow"))?;
1600                    dts = dts
1601                        .checked_add(u64::from(duration))
1602                        .ok_or_else(|| malformed("fragment DTS overflow"))?;
1603                }
1604                next_data = Some(offset);
1605            }
1606        }
1607    }
1608    Ok(())
1609}
1610fn dependency_from_flags(flags: u32, is_sync: bool) -> SampleDependency {
1611    let value = ((flags >> 20) & 0xff) as u8;
1612    let parsed = SampleDependency {
1613        is_leading: value >> 6,
1614        depends_on: (value >> 4) & 3,
1615        is_depended_on: (value >> 2) & 3,
1616        has_redundancy: value & 3,
1617    };
1618    if value == 0 {
1619        if is_sync {
1620            SampleDependency::INDEPENDENT
1621        } else {
1622            SampleDependency::DEPENDENT
1623        }
1624    } else {
1625        parsed
1626    }
1627}
1628fn nonzero(v: u32) -> Option<u32> {
1629    (v != 0).then_some(v)
1630}
1631fn add_signed(base: u64, delta: i32) -> Result<u64> {
1632    if delta >= 0 {
1633        base.checked_add(delta as u64)
1634    } else {
1635        base.checked_sub(u64::from(delta.unsigned_abs()))
1636    }
1637    .ok_or_else(|| malformed("fragment data offset overflow"))
1638}
1639
1640fn validate_samples(track: &Mp4Track, source_len: u64, media_ranges: &[(u64, u64)]) -> Result<()> {
1641    for sample in &track.samples {
1642        if sample.size == 0 {
1643            return Err(malformed("sample size is zero"));
1644        }
1645        let end = sample
1646            .offset
1647            .checked_add(u64::from(sample.size))
1648            .ok_or_else(|| malformed("sample byte range overflow"))?;
1649        if end > source_len {
1650            return Err(malformed("sample byte range exceeds source"));
1651        }
1652        if !media_ranges
1653            .iter()
1654            .any(|&(start, range_end)| sample.offset >= start && end <= range_end)
1655        {
1656            return Err(malformed("sample byte range is outside media data"));
1657        }
1658    }
1659    Ok(())
1660}
1661
1662struct Child<'a> {
1663    kind: [u8; 4],
1664    payload: &'a [u8],
1665    whole: &'a [u8],
1666}
1667fn children<'a>(
1668    bytes: &'a [u8],
1669    depth: u8,
1670    budget: &mut Budget,
1671    options: &Mp4DemuxerOptions,
1672) -> Result<Vec<Child<'a>>> {
1673    if depth > options.max_box_depth {
1674        return Err(limit("MP4 box nesting limit exceeded"));
1675    }
1676    let mut out = Vec::new();
1677    let mut at = 0;
1678    while at < bytes.len() {
1679        budget.box_seen()?;
1680        if bytes.len() - at < 8 {
1681            return Err(malformed("truncated child box header"));
1682        }
1683        let size32 = be_u32(bytes, at)?;
1684        let (kind, header, size) = if size32 == 1 {
1685            (
1686                slice(bytes, at + 4, 4)?.try_into().expect("fixed"),
1687                16,
1688                usize::try_from(be_u64(bytes, at + 8)?)
1689                    .map_err(|_| limit("extended child box too large"))?,
1690            )
1691        } else {
1692            (
1693                slice(bytes, at + 4, 4)?.try_into().expect("fixed"),
1694                8,
1695                if size32 == 0 {
1696                    bytes.len() - at
1697                } else {
1698                    size32 as usize
1699                },
1700            )
1701        };
1702        if size < header {
1703            return Err(malformed("child box is smaller than its header"));
1704        }
1705        let end = at
1706            .checked_add(size)
1707            .ok_or_else(|| malformed("child box size overflow"))?;
1708        if end > bytes.len() {
1709            return Err(malformed("child box exceeds its parent"));
1710        }
1711        ensure_allocation(
1712            out.len() + 1,
1713            std::mem::size_of::<Child<'_>>(),
1714            options,
1715            "child boxes",
1716        )?;
1717        out.push(Child {
1718            kind,
1719            payload: &bytes[at + header..end],
1720            whole: &bytes[at..end],
1721        });
1722        at = end;
1723    }
1724    Ok(out)
1725}
1726fn one_child<'a>(
1727    bytes: &'a [u8],
1728    depth: u8,
1729    budget: &mut Budget,
1730    options: &Mp4DemuxerOptions,
1731) -> Result<Child<'a>> {
1732    let mut values = children(bytes, depth, budget, options)?;
1733    if values.len() != 1 {
1734        return Err(malformed("sample description entry count is inconsistent"));
1735    }
1736    Ok(values.remove(0))
1737}
1738fn full(payload: &[u8]) -> Result<(u8, &[u8])> {
1739    let (version, _, body) = full_flags(payload)?;
1740    Ok((version, body))
1741}
1742fn full_flags(payload: &[u8]) -> Result<(u8, u32, &[u8])> {
1743    require(payload, 0, 4)?;
1744    Ok((
1745        payload[0],
1746        u32::from_be_bytes([0, payload[1], payload[2], payload[3]]),
1747        &payload[4..],
1748    ))
1749}
1750fn count(bytes: &[u8], at: usize, max: u32, label: &str) -> Result<usize> {
1751    let value = be_u32(bytes, at)?;
1752    if value > max {
1753        return Err(limit(format!("{label} count limit exceeded")));
1754    }
1755    Ok(value as usize)
1756}
1757fn checked_product(count: usize, width: usize, label: &str) -> Result<usize> {
1758    count
1759        .checked_mul(width)
1760        .ok_or_else(|| malformed(format!("{label} byte count overflow")))
1761}
1762fn ensure_allocation(
1763    count: usize,
1764    element_size: usize,
1765    options: &Mp4DemuxerOptions,
1766    label: &str,
1767) -> Result<()> {
1768    let bytes = checked_product(count, element_size, label)?;
1769    if bytes as u128 > u128::from(options.limits.max_allocation_bytes) {
1770        return Err(limit(format!("{label} allocation limit exceeded")));
1771    }
1772    Ok(())
1773}
1774fn require(bytes: &[u8], at: usize, len: usize) -> Result<()> {
1775    let end = at
1776        .checked_add(len)
1777        .ok_or_else(|| malformed("box field offset overflow"))?;
1778    if end > bytes.len() {
1779        return Err(malformed("truncated MP4 box fields"));
1780    }
1781    Ok(())
1782}
1783fn slice(bytes: &[u8], at: usize, len: usize) -> Result<&[u8]> {
1784    require(bytes, at, len)?;
1785    Ok(&bytes[at..at + len])
1786}
1787
1788fn scale_time(value: u64, source_timescale: u32, destination_rate: u32) -> Result<u64> {
1789    if source_timescale == 0 || destination_rate == 0 {
1790        return Err(malformed("media timescale and sample rate must be nonzero"));
1791    }
1792    let numerator = u128::from(value)
1793        .checked_mul(u128::from(destination_rate))
1794        .ok_or_else(|| limit("media time scaling overflow"))?;
1795    let rounded = numerator
1796        .checked_add(u128::from(source_timescale) / 2)
1797        .ok_or_else(|| limit("media time rounding overflow"))?
1798        / u128::from(source_timescale);
1799    u64::try_from(rounded).map_err(|_| limit("scaled media time cannot be represented"))
1800}
1801
1802fn parse_aac_config(esds: &[u8], entry_rate: u32, entry_channels: u16) -> Result<AacTrackConfig> {
1803    if esds.len() < 13 || &esds[4..8] != b"esds" {
1804        return Err(malformed("AAC decoder configuration is not an esds box"));
1805    }
1806    let payload = &esds[12..];
1807    let mut unsupported_type = None;
1808    for at in 0..payload.len() {
1809        if payload[at] != 0x05 {
1810            continue;
1811        }
1812        let Ok((length, header)) = descriptor_length(&payload[at + 1..]) else {
1813            continue;
1814        };
1815        let start = at + 1 + header;
1816        let Some(end) = start.checked_add(length) else {
1817            continue;
1818        };
1819        if end > payload.len() || length < 2 {
1820            continue;
1821        }
1822        let asc = &payload[start..end];
1823        let Ok((object_type, sample_rate, channel_config)) = parse_audio_specific_config(asc)
1824        else {
1825            continue;
1826        };
1827        if object_type != 2 {
1828            unsupported_type = Some(object_type);
1829            continue;
1830        }
1831        if sample_rate != entry_rate {
1832            return Err(malformed(
1833                "AAC AudioSpecificConfig sample rate disagrees with mp4a",
1834            ));
1835        }
1836        if u16::from(channel_config) != entry_channels {
1837            return Err(malformed(
1838                "AAC AudioSpecificConfig channel count disagrees with mp4a",
1839            ));
1840        }
1841        return Ok(AacTrackConfig {
1842            audio_object_type: object_type,
1843            sample_rate,
1844            channels: entry_channels,
1845            audio_specific_config: asc.to_vec(),
1846        });
1847    }
1848    if let Some(object_type) = unsupported_type {
1849        Err(unsupported(format!(
1850            "AAC audio object type {object_type} is unsupported; AAC-LC is required"
1851        )))
1852    } else {
1853        Err(malformed(
1854            "AAC esds does not contain a valid DecoderSpecificInfo descriptor",
1855        ))
1856    }
1857}
1858
1859fn descriptor_length(bytes: &[u8]) -> Result<(usize, usize)> {
1860    let mut length = 0_usize;
1861    for (index, &byte) in bytes.iter().take(4).enumerate() {
1862        length = length
1863            .checked_shl(7)
1864            .and_then(|value| value.checked_add(usize::from(byte & 0x7f)))
1865            .ok_or_else(|| malformed("AAC descriptor length overflow"))?;
1866        if byte & 0x80 == 0 {
1867            return Ok((length, index + 1));
1868        }
1869    }
1870    Err(malformed("AAC descriptor has an invalid length"))
1871}
1872
1873fn parse_audio_specific_config(bytes: &[u8]) -> Result<(u8, u32, u8)> {
1874    let mut bits = AscBits::new(bytes);
1875    let mut object_type = bits.read(5)? as u8;
1876    if object_type == 31 {
1877        object_type = 32_u8
1878            .checked_add(bits.read(6)? as u8)
1879            .ok_or_else(|| malformed("AAC audio object type overflow"))?;
1880    }
1881    let frequency_index = bits.read(4)? as usize;
1882    const FREQUENCIES: [u32; 13] = [
1883        96_000, 88_200, 64_000, 48_000, 44_100, 32_000, 24_000, 22_050, 16_000, 12_000, 11_025,
1884        8_000, 7_350,
1885    ];
1886    let sample_rate = if frequency_index == 15 {
1887        bits.read(24)?
1888    } else {
1889        *FREQUENCIES
1890            .get(frequency_index)
1891            .ok_or_else(|| malformed("AAC AudioSpecificConfig has an invalid sample rate"))?
1892    };
1893    let channel_config = bits.read(4)? as u8;
1894    if channel_config == 0 || channel_config > 7 {
1895        return Err(unsupported(
1896            "AAC program-config-element channel layouts are unsupported",
1897        ));
1898    }
1899    Ok((object_type, sample_rate, channel_config))
1900}
1901
1902struct AscBits<'a> {
1903    bytes: &'a [u8],
1904    position: usize,
1905}
1906
1907impl<'a> AscBits<'a> {
1908    fn new(bytes: &'a [u8]) -> Self {
1909        Self { bytes, position: 0 }
1910    }
1911
1912    fn read(&mut self, count: usize) -> Result<u32> {
1913        if count > 32
1914            || self
1915                .position
1916                .checked_add(count)
1917                .is_none_or(|end| end > self.bytes.len() * 8)
1918        {
1919            return Err(malformed("truncated AAC AudioSpecificConfig"));
1920        }
1921        let mut value = 0_u32;
1922        for _ in 0..count {
1923            value = (value << 1)
1924                | u32::from((self.bytes[self.position / 8] >> (7 - self.position % 8)) & 1);
1925            self.position += 1;
1926        }
1927        Ok(value)
1928    }
1929}
1930fn be_u16(b: &[u8], a: usize) -> Result<u16> {
1931    Ok(u16::from_be_bytes(
1932        slice(b, a, 2)?.try_into().expect("fixed"),
1933    ))
1934}
1935fn be_i16(b: &[u8], a: usize) -> Result<i16> {
1936    Ok(i16::from_be_bytes(
1937        slice(b, a, 2)?.try_into().expect("fixed"),
1938    ))
1939}
1940fn be_u32(b: &[u8], a: usize) -> Result<u32> {
1941    Ok(u32::from_be_bytes(
1942        slice(b, a, 4)?.try_into().expect("fixed"),
1943    ))
1944}
1945fn be_i32(b: &[u8], a: usize) -> Result<i32> {
1946    Ok(i32::from_be_bytes(
1947        slice(b, a, 4)?.try_into().expect("fixed"),
1948    ))
1949}
1950fn be_u64(b: &[u8], a: usize) -> Result<u64> {
1951    Ok(u64::from_be_bytes(
1952        slice(b, a, 8)?.try_into().expect("fixed"),
1953    ))
1954}
1955fn be_i64(b: &[u8], a: usize) -> Result<i64> {
1956    Ok(i64::from_be_bytes(
1957        slice(b, a, 8)?.try_into().expect("fixed"),
1958    ))
1959}
1960fn validate_options(o: &Mp4DemuxerOptions) -> Result<()> {
1961    if o.max_box_bytes < 8
1962        || o.max_boxes == 0
1963        || o.max_box_depth == 0
1964        || o.max_samples_per_track == 0
1965    {
1966        return Err(invalid("MP4 parser limits must be nonzero"));
1967    }
1968    Ok(())
1969}
1970fn invalid(m: impl Into<String>) -> Error {
1971    Error::new(ErrorKind::InvalidInput, m)
1972}
1973fn malformed(m: impl Into<String>) -> Error {
1974    Error::new(ErrorKind::MalformedMedia, m)
1975}
1976fn limit(m: impl Into<String>) -> Error {
1977    Error::new(ErrorKind::ResourceLimit, m)
1978}
1979fn unsupported(m: impl Into<String>) -> Error {
1980    Error::new(ErrorKind::Unsupported, m)
1981}
1982
1983#[cfg(test)]
1984mod tests {
1985    use super::*;
1986    use crate::io::{IoFuture, MemorySource};
1987    use std::cell::Cell;
1988    use std::future::Future;
1989    use std::pin::Pin;
1990    use std::task::{Context, Poll, Waker};
1991
1992    fn block_on<T>(future: impl Future<Output = T>) -> T {
1993        let mut future = Box::pin(future);
1994        let waker = Waker::noop();
1995        let mut context = Context::from_waker(waker);
1996        loop {
1997            match Pin::new(&mut future).poll(&mut context) {
1998                Poll::Ready(value) => return value,
1999                Poll::Pending => std::thread::yield_now(),
2000            }
2001        }
2002    }
2003
2004    fn boxed(kind: &[u8; 4], payload: impl AsRef<[u8]>) -> Vec<u8> {
2005        let payload = payload.as_ref();
2006        let mut bytes = u32::try_from(payload.len() + 8)
2007            .unwrap()
2008            .to_be_bytes()
2009            .to_vec();
2010        bytes.extend_from_slice(kind);
2011        bytes.extend_from_slice(payload);
2012        bytes
2013    }
2014
2015    fn full_box(kind: &[u8; 4], version: u8, flags: u32, body: &[u8]) -> Vec<u8> {
2016        let mut payload = vec![version];
2017        payload.extend_from_slice(&flags.to_be_bytes()[1..]);
2018        payload.extend_from_slice(body);
2019        boxed(kind, payload)
2020    }
2021
2022    fn container(kind: &[u8; 4], children: &[Vec<u8>]) -> Vec<u8> {
2023        boxed(kind, children.concat())
2024    }
2025
2026    fn fragmented_fixture() -> Vec<u8> {
2027        let mut mvhd = vec![0; 16];
2028        mvhd[8..12].copy_from_slice(&1_000_u32.to_be_bytes());
2029
2030        let mut tkhd = vec![0; 80];
2031        tkhd[8..12].copy_from_slice(&1_u32.to_be_bytes());
2032        tkhd[72..76].copy_from_slice(&(2_u32 << 16).to_be_bytes());
2033        tkhd[76..80].copy_from_slice(&(2_u32 << 16).to_be_bytes());
2034
2035        let mut mdhd = vec![0; 20];
2036        mdhd[8..12].copy_from_slice(&1_000_u32.to_be_bytes());
2037        let mut hdlr = vec![0; 8];
2038        hdlr[4..8].copy_from_slice(b"vide");
2039
2040        let mut sample_entry = vec![0; 78];
2041        sample_entry[6..8].copy_from_slice(&1_u16.to_be_bytes());
2042        sample_entry[24..26].copy_from_slice(&2_u16.to_be_bytes());
2043        sample_entry[26..28].copy_from_slice(&2_u16.to_be_bytes());
2044        sample_entry.extend_from_slice(&boxed(b"av1C", [0x81, 0, 0, 0]));
2045        let sample_entry = boxed(b"av01", sample_entry);
2046        let mut stsd_body = 1_u32.to_be_bytes().to_vec();
2047        stsd_body.extend_from_slice(&sample_entry);
2048        let stbl = container(
2049            b"stbl",
2050            &[
2051                full_box(b"stsd", 0, 0, &stsd_body),
2052                full_box(b"stts", 0, 0, &0_u32.to_be_bytes()),
2053                full_box(b"stsc", 0, 0, &0_u32.to_be_bytes()),
2054                full_box(b"stsz", 0, 0, &[0; 8]),
2055                full_box(b"stco", 0, 0, &0_u32.to_be_bytes()),
2056            ],
2057        );
2058        let minf = container(b"minf", &[stbl]);
2059        let mdia = container(
2060            b"mdia",
2061            &[
2062                full_box(b"mdhd", 0, 0, &mdhd),
2063                full_box(b"hdlr", 0, 0, &hdlr),
2064                minf,
2065            ],
2066        );
2067        let trak = container(b"trak", &[full_box(b"tkhd", 0, 7, &tkhd), mdia]);
2068
2069        let mut trex = Vec::new();
2070        trex.extend_from_slice(&1_u32.to_be_bytes());
2071        trex.extend_from_slice(&1_u32.to_be_bytes());
2072        trex.extend_from_slice(&1_000_u32.to_be_bytes());
2073        trex.extend_from_slice(&4_u32.to_be_bytes());
2074        trex.extend_from_slice(&0_u32.to_be_bytes());
2075        let mvex = container(b"mvex", &[full_box(b"trex", 0, 0, &trex)]);
2076        let moov = container(b"moov", &[full_box(b"mvhd", 0, 0, &mvhd), trak, mvex]);
2077
2078        let tfhd = full_box(b"tfhd", 0, 0x020000, &1_u32.to_be_bytes());
2079        let tfdt = full_box(b"tfdt", 1, 0, &0_u64.to_be_bytes());
2080        let make_moof = |data_offset: i32| {
2081            let mut trun = 2_u32.to_be_bytes().to_vec();
2082            trun.extend_from_slice(&data_offset.to_be_bytes());
2083            trun.extend_from_slice(&1_000_i32.to_be_bytes());
2084            trun.extend_from_slice(&(-1_000_i32).to_be_bytes());
2085            let traf = container(
2086                b"traf",
2087                &[
2088                    tfhd.clone(),
2089                    tfdt.clone(),
2090                    full_box(b"trun", 1, 0x801, &trun),
2091                ],
2092            );
2093            container(b"moof", &[traf])
2094        };
2095        let provisional = make_moof(0);
2096        let moof = make_moof(i32::try_from(provisional.len() + 8).unwrap());
2097        let mdat = boxed(b"mdat", b"AAAABBBB");
2098        [boxed(b"ftyp", b"isom\0\0\0\0isom"), moov, moof, mdat].concat()
2099    }
2100
2101    struct PartialSource {
2102        inner: MemorySource,
2103        largest_request: Cell<usize>,
2104    }
2105
2106    impl ByteSource for PartialSource {
2107        fn len(&self) -> Option<u64> {
2108            self.inner.len()
2109        }
2110
2111        fn read_at<'a>(&'a self, offset: u64, destination: &'a mut [u8]) -> IoFuture<'a, usize> {
2112            self.largest_request
2113                .set(self.largest_request.get().max(destination.len()));
2114            let limited = destination.len().min(3);
2115            self.inner.read_at(offset, &mut destination[..limited])
2116        }
2117    }
2118
2119    #[test]
2120    fn fragmented_mp4_expands_decode_and_presentation_indexes_incrementally() {
2121        block_on(async {
2122            let source = PartialSource {
2123                inner: MemorySource::new(fragmented_fixture()),
2124                largest_request: Cell::new(0),
2125            };
2126            assert!(probe_mp4(&source).await.unwrap());
2127            let movie = Mp4Demuxer::open(&source, Mp4DemuxerOptions::default())
2128                .await
2129                .unwrap();
2130            let track = movie.track(1).unwrap();
2131            assert_eq!(track.codec, Codec::Av1);
2132            assert_eq!(track.samples.len(), 2);
2133            assert_eq!(track.samples[0].dts, 0);
2134            assert_eq!(track.samples[0].pts, 1_000);
2135            assert_eq!(track.samples[1].dts, 1_000);
2136            assert_eq!(track.samples[1].pts, 0);
2137            assert_eq!(track.presentation_order, vec![1, 0]);
2138            assert_eq!(track.samples[1].offset, track.samples[0].offset + 4);
2139            assert_eq!(track.presentation_sample(0), Some(&track.samples[1]));
2140            let mut encoded = [0; 4];
2141            track
2142                .read_sample_into(&source, 1, &mut encoded)
2143                .await
2144                .unwrap();
2145            assert_eq!(&encoded, b"BBBB");
2146            assert!(source.largest_request.get() < source.inner.as_slice().len());
2147        });
2148    }
2149
2150    #[test]
2151    fn malformed_sizes_and_limits_fail_without_panicking() {
2152        block_on(async {
2153            let valid = fragmented_fixture();
2154            for length in 0..valid.len() {
2155                let _result = Mp4Demuxer::open(
2156                    &MemorySource::new(valid[..length].to_vec()),
2157                    Mp4DemuxerOptions::default(),
2158                )
2159                .await;
2160            }
2161            for declared in 0_u32..8 {
2162                let mut bytes = valid.clone();
2163                bytes[..4].copy_from_slice(&declared.to_be_bytes());
2164                let result =
2165                    Mp4Demuxer::open(&MemorySource::new(bytes), Mp4DemuxerOptions::default()).await;
2166                assert!(result.is_err(), "top-level size {declared} was accepted");
2167            }
2168
2169            let options = Mp4DemuxerOptions {
2170                max_box_bytes: 32,
2171                ..Mp4DemuxerOptions::default()
2172            };
2173            let error = Mp4Demuxer::open(&MemorySource::new(valid), options)
2174                .await
2175                .unwrap_err();
2176            assert_eq!(error.kind(), ErrorKind::ResourceLimit);
2177        });
2178    }
2179
2180    #[test]
2181    fn compact_sample_sizes_expand_at_every_supported_width() {
2182        let options = Mp4DemuxerOptions::default();
2183        for (width, count, encoded, expected) in [
2184            (4_u8, 3_u32, vec![0x12, 0x30], vec![1, 2, 3]),
2185            (8, 3, vec![1, 2, 255], vec![1, 2, 255]),
2186            (16, 2, vec![0, 1, 1, 0], vec![1, 256]),
2187        ] {
2188            let mut body = vec![0; 4];
2189            body[3] = width;
2190            body.extend_from_slice(&count.to_be_bytes());
2191            body.extend_from_slice(&encoded);
2192            assert_eq!(
2193                parse_stz2(&full_box(b"stz2", 0, 0, &body)[8..], &options).unwrap(),
2194                expected
2195            );
2196        }
2197    }
2198
2199    #[test]
2200    fn to_encoded_video_samples_reads_the_bundled_hevc_sample_in_presentation_order() {
2201        let bytes = std::fs::read(concat!(
2202            env!("CARGO_MANIFEST_DIR"),
2203            "/../../examples/media/BigBuckBunny.mp4"
2204        ))
2205        .expect("bundled sample video is checked into the repository");
2206        let source = MemorySource::new(bytes);
2207        let demuxer = block_on(Mp4Demuxer::open(&source, Mp4DemuxerOptions::default())).unwrap();
2208        let track = demuxer
2209            .tracks
2210            .iter()
2211            .find(|track| track.kind == TrackKind::Video)
2212            .expect("sample video has a video track");
2213        assert_eq!(track.codec, Codec::Hevc);
2214        assert!(!track.decoder_config.is_empty());
2215
2216        let samples =
2217            block_on(track.to_encoded_video_samples(&source, &Limits::default())).unwrap();
2218        assert_eq!(samples.len(), track.samples.len());
2219        assert!(
2220            samples[0].random_access,
2221            "decode must start at a sync sample"
2222        );
2223        let mut seen: Vec<u64> = samples.iter().map(|s| s.presentation_index.0).collect();
2224        seen.sort_unstable();
2225        seen.dedup();
2226        assert_eq!(
2227            seen.len(),
2228            samples.len(),
2229            "presentation indexes must be unique"
2230        );
2231        assert!(samples.iter().all(|sample| !sample.data.is_empty()));
2232
2233        let derived =
2234            crate::codec_config::derive_codec_string(track.codec, &track.decoder_config).unwrap();
2235        assert!(derived.codec_string.starts_with("hev1."));
2236    }
2237
2238    /// An `avcC` record as WebCodecs/mediabunny writes it: High profile,
2239    /// level 3.1, 4-byte NAL lengths, one SPS and one PPS.
2240    fn avcc_box() -> Vec<u8> {
2241        let sps = [
2242            0x67, 0x64, 0x00, 0x1f, 0xac, 0xd9, 0x40, 0x50, 0x05, 0xbb, 0x01, 0x10,
2243        ];
2244        let pps = [0x68, 0xeb, 0xe3, 0xcb, 0x22, 0xc0];
2245        let mut record = vec![1, 0x64, 0x00, 0x1f, 0xff, 0xe1];
2246        record.extend_from_slice(&u16::try_from(sps.len()).unwrap().to_be_bytes());
2247        record.extend_from_slice(&sps);
2248        record.push(1);
2249        record.extend_from_slice(&u16::try_from(pps.len()).unwrap().to_be_bytes());
2250        record.extend_from_slice(&pps);
2251        boxed(b"avcC", record)
2252    }
2253
2254    fn mux_h264(samples: &[crate::EncodedSample]) -> Vec<u8> {
2255        use crate::io::MemorySink;
2256        use crate::mp4::{Mp4Muxer, Mp4TrackConfig, Mp4TrackFormat};
2257        block_on(async {
2258            let mut muxer = Mp4Muxer::new(
2259                MemorySink::new(),
2260                vec![Mp4TrackConfig {
2261                    encoder: crate::EncoderConfig {
2262                        codec: Codec::H264,
2263                        timescale: 30_000,
2264                        decoder_config: avcc_box(),
2265                    },
2266                    format: Mp4TrackFormat::Video(VideoDimensions {
2267                        width: 640,
2268                        height: 360,
2269                    }),
2270                }],
2271                16,
2272            )
2273            .await
2274            .unwrap();
2275            for sample in samples {
2276                muxer.write_sample(0, sample.clone()).await.unwrap();
2277            }
2278            muxer.finish().await.unwrap().into_inner()
2279        })
2280    }
2281
2282    #[test]
2283    fn h264_avc1_track_round_trips_through_the_muxer_and_demuxer() {
2284        let samples: Vec<crate::EncodedSample> = (0..4_u8)
2285            .map(|index| crate::EncodedSample {
2286                data: vec![index + 1; 20 + usize::from(index)],
2287                dts: i64::from(index) * 1_001,
2288                pts: i64::from(index) * 1_001,
2289                duration: 1_001,
2290                is_sync: index == 0,
2291                dependency: if index == 0 {
2292                    SampleDependency::INDEPENDENT
2293                } else {
2294                    SampleDependency::DEPENDENT
2295                },
2296            })
2297            .collect();
2298        let bytes = mux_h264(&samples);
2299        assert!(
2300            bytes.windows(4).any(|window| window == b"avc1"),
2301            "the muxer must write an avc1 sample entry"
2302        );
2303
2304        let source = MemorySource::new(bytes.clone());
2305        let demuxer = block_on(Mp4Demuxer::open(&source, Mp4DemuxerOptions::default())).unwrap();
2306        assert_eq!(demuxer.tracks.len(), 1);
2307        let track = &demuxer.tracks[0];
2308        assert_eq!(track.kind, TrackKind::Video);
2309        assert_eq!(track.codec, Codec::H264);
2310        assert_eq!(track.decoder_config, avcc_box());
2311        assert_eq!(track.timescale, 30_000);
2312        assert_eq!(
2313            track.dimensions,
2314            Some(VideoDimensions {
2315                width: 640,
2316                height: 360
2317            })
2318        );
2319        assert_eq!(track.samples.len(), samples.len());
2320        for (read, written) in track.samples.iter().zip(&samples) {
2321            assert_eq!(read.dts, u64::try_from(written.dts).unwrap());
2322            assert_eq!(read.pts, written.pts);
2323            assert_eq!(read.duration, written.duration);
2324            assert_eq!(read.is_sync, written.is_sync);
2325        }
2326        let read = block_on(track.to_encoded_video_samples(&source, &Limits::default())).unwrap();
2327        let data: Vec<_> = read.iter().map(|sample| sample.data.clone()).collect();
2328        let expected: Vec<_> = samples.iter().map(|sample| sample.data.clone()).collect();
2329        assert_eq!(data, expected);
2330
2331        // H.264 is passthrough only: there is no WebCodecs codec string for it.
2332        let error = crate::codec_config::derive_codec_string(track.codec, &track.decoder_config)
2333            .unwrap_err();
2334        assert_eq!(error.kind(), ErrorKind::Unsupported);
2335
2336        // `avc3` carries the same `avcC` and demuxes the same way.
2337        let at = bytes
2338            .windows(4)
2339            .position(|window| window == b"avc1")
2340            .unwrap();
2341        let mut avc3 = bytes;
2342        avc3[at..at + 4].copy_from_slice(b"avc3");
2343        let demuxer = block_on(Mp4Demuxer::open(
2344            &MemorySource::new(avc3),
2345            Mp4DemuxerOptions::default(),
2346        ))
2347        .unwrap();
2348        assert_eq!(demuxer.tracks[0].codec, Codec::H264);
2349        assert_eq!(demuxer.tracks[0].decoder_config, avcc_box());
2350    }
2351
2352    #[test]
2353    fn vp9_vp09_track_round_trips_through_the_muxer_and_demuxer() {
2354        use crate::io::MemorySink;
2355        use crate::mp4::{Mp4Muxer, Mp4TrackConfig, Mp4TrackFormat};
2356        // vpcC version 1: profile 0, level 3.0, 8-bit colocated 4:2:0.
2357        let vpcc = full_box(b"vpcC", 1, 0, &[0, 30, 0x82, 1, 1, 1, 0, 0]);
2358        let samples: Vec<crate::EncodedSample> = (0..3_u8)
2359            .map(|index| crate::EncodedSample {
2360                data: vec![index + 1; 10],
2361                dts: i64::from(index) * 512,
2362                pts: i64::from(index) * 512,
2363                duration: 512,
2364                is_sync: index == 0,
2365                dependency: SampleDependency::INDEPENDENT,
2366            })
2367            .collect();
2368        let bytes = block_on(async {
2369            let mut muxer = Mp4Muxer::new(
2370                MemorySink::new(),
2371                vec![Mp4TrackConfig {
2372                    encoder: crate::EncoderConfig {
2373                        codec: Codec::Vp9,
2374                        timescale: 15_360,
2375                        decoder_config: vpcc.clone(),
2376                    },
2377                    format: Mp4TrackFormat::Video(VideoDimensions {
2378                        width: 320,
2379                        height: 180,
2380                    }),
2381                }],
2382                16,
2383            )
2384            .await
2385            .unwrap();
2386            for sample in &samples {
2387                muxer.write_sample(0, sample.clone()).await.unwrap();
2388            }
2389            muxer.finish().await.unwrap().into_inner()
2390        });
2391        assert!(bytes.windows(4).any(|window| window == b"vp09"));
2392
2393        let source = MemorySource::new(bytes);
2394        let demuxer = block_on(Mp4Demuxer::open(&source, Mp4DemuxerOptions::default())).unwrap();
2395        let track = &demuxer.tracks[0];
2396        assert_eq!(track.codec, Codec::Vp9);
2397        assert_eq!(track.decoder_config, vpcc);
2398        assert_eq!(
2399            track.dimensions,
2400            Some(VideoDimensions {
2401                width: 320,
2402                height: 180
2403            })
2404        );
2405        let read = block_on(track.to_encoded_video_samples(&source, &Limits::default())).unwrap();
2406        assert_eq!(read.len(), 3);
2407        assert!(read[0].random_access && !read[1].random_access);
2408        assert_eq!(read[2].data, samples[2].data);
2409
2410        let derived =
2411            crate::codec_config::derive_codec_string(track.codec, &track.decoder_config).unwrap();
2412        assert_eq!(derived.codec_string, "vp09.00.30.08");
2413        assert_eq!(derived.profile, crate::CodecProfile::Vp9Profile0);
2414    }
2415
2416    #[test]
2417    fn libvpx_vp9_mp4_exposes_its_vpcc_configuration() {
2418        let source = MemorySource::new(
2419            include_bytes!("../../zvidlib-vp9-decoder/tests/fixtures/vp9_bbb_256x144.mp4").to_vec(),
2420        );
2421        let demuxer = block_on(Mp4Demuxer::open(&source, Mp4DemuxerOptions::default())).unwrap();
2422        let track = &demuxer.tracks[0];
2423        assert_eq!(track.codec, Codec::Vp9);
2424        let config = crate::Vp9CodecConfig::parse(&track.decoder_config).unwrap();
2425        assert_eq!((config.profile, config.bit_depth), (0, 8));
2426        assert!(config.is_420());
2427        assert_eq!(track.samples.len(), 48);
2428        assert_eq!(
2429            track.samples.iter().filter(|sample| sample.is_sync).count(),
2430            2
2431        );
2432    }
2433
2434    #[test]
2435    fn h264_track_needs_an_avcc_decoder_config() {
2436        use crate::io::MemorySink;
2437        use crate::mp4::{Mp4Muxer, Mp4TrackConfig, Mp4TrackFormat};
2438        let error = match block_on(Mp4Muxer::new(
2439            MemorySink::new(),
2440            vec![Mp4TrackConfig {
2441                encoder: crate::EncoderConfig {
2442                    codec: Codec::H264,
2443                    timescale: 30_000,
2444                    decoder_config: boxed(b"hvcC", [1, 2, 3, 4]),
2445                },
2446                format: Mp4TrackFormat::Video(VideoDimensions {
2447                    width: 640,
2448                    height: 360,
2449                }),
2450            }],
2451            16,
2452        )) {
2453            Ok(_) => panic!("an H.264 track with an hvcC box must be rejected"),
2454            Err(error) => error,
2455        };
2456        assert_eq!(error.kind(), ErrorKind::InvalidInput);
2457    }
2458
2459    #[test]
2460    fn bundled_aac_track_exposes_indexed_packets_configuration_and_gapless_timing() {
2461        block_on(async {
2462            let source = MemorySource::new(
2463                include_bytes!("../../../examples/media/BigBuckBunny.mp4").to_vec(),
2464            );
2465            let movie = Mp4Demuxer::open(&source, Mp4DemuxerOptions::default())
2466                .await
2467                .unwrap();
2468            let track = movie
2469                .tracks
2470                .iter()
2471                .find(|track| track.kind == TrackKind::Audio)
2472                .unwrap();
2473            let config = track.aac_config().unwrap();
2474            assert_eq!(config.audio_object_type, 2);
2475            assert_eq!(config.sample_rate, 48_000);
2476            assert_eq!(config.channels, 2);
2477            assert_eq!(config.audio_specific_config, [0x11, 0x90, 0x56, 0xe5, 0x00]);
2478
2479            let packets = track
2480                .to_encoded_audio_samples(&source, &Limits::default())
2481                .await
2482                .unwrap();
2483            assert_eq!(packets.len(), 1_501);
2484            assert_eq!(
2485                packets[0].decoded_range,
2486                crate::SampleRange::new(0, 1_024).unwrap()
2487            );
2488            assert_eq!(
2489                packets[1].decoded_range,
2490                crate::SampleRange::new(1_024, 2_048).unwrap()
2491            );
2492            assert_eq!(packets.last().unwrap().decoded_range.end, 1_537_024);
2493            assert_eq!(packets[0].data.len(), track.samples[0].size as usize);
2494
2495            let timing = track.audio_timing(movie.movie_timescale).unwrap();
2496            assert_eq!(timing.priming, 1_024);
2497            assert_eq!(timing.padding, 0);
2498            assert_eq!(timing.edits.len(), 1);
2499            assert_eq!(
2500                timing.edits[0].presentation,
2501                crate::SampleRange::new(0, 1_536_000).unwrap()
2502            );
2503            assert_eq!(timing.edits[0].media_start, Some(1_024));
2504        });
2505    }
2506}