1use 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#[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#[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#[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#[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 pub decoder_config: Vec<u8>,
72 pub edits: Vec<EditMapping>,
73 pub samples: Vec<Mp4Sample>,
75 pub presentation_order: Vec<usize>,
80}
81
82impl Mp4Track {
83 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 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 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 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 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 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 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 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 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 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 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#[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#[derive(Clone, Debug, Eq, PartialEq)]
391pub struct Mp4Demuxer {
392 pub movie_timescale: u32,
393 pub tracks: Vec<Mp4Track>,
394 pub cover_art: Option<CoverArt>,
397}
398
399pub 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 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 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
721fn 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 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 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 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 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 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}