symphonia_core/formats/mod.rs
1// Symphonia
2// Copyright (c) 2019-2026 The Project Symphonia Developers.
3//
4// This Source Code Form is subject to the terms of the Mozilla Public
5// License, v. 2.0. If a copy of the MPL was not distributed with this
6// file, You can obtain one at https://mozilla.org/MPL/2.0/.
7
8//! The `format` module provides the traits and support structures necessary to implement media
9//! demuxers.
10
11use std::fmt;
12
13use crate::codecs::{CodecParameters, audio, subtitle, video};
14use crate::common::FourCc;
15use crate::errors::Result;
16use crate::io::MediaSourceStream;
17use crate::meta::{ChapterGroup, Metadata, MetadataLog};
18use crate::packet::Packet;
19use crate::units::{Duration, Time, TimeBase, Timestamp};
20
21use bitflags::bitflags;
22
23pub mod prelude {
24 //! The `formats` module prelude for format reader implementers.
25
26 pub use crate::meta::{Chapter, ChapterGroup, ChapterGroupItem};
27 pub use crate::packet::{Packet, PacketBuilder};
28 pub use crate::units::{Duration, TimeBase, Timestamp};
29
30 pub use super::{
31 Attachment, FileAttachment, FormatId, FormatInfo, FormatOptions, FormatReader, MediaInfo,
32 SeekMode, SeekTo, SeekedTo, Track, VendorDataAttachment,
33 };
34}
35
36pub mod probe;
37
38/// A `FormatId` is a unique identifier used to identify a specific container format.
39#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
40pub struct FormatId(u32);
41
42impl FormatId {
43 /// Create a new format ID from a FourCC.
44 pub const fn new(cc: FourCc) -> FormatId {
45 // A FourCc always only contains ASCII characters. Therefore, the upper bits are always 0.
46 Self(0x8000_0000 | u32::from_be_bytes(cc.get()))
47 }
48}
49
50impl From<FourCc> for FormatId {
51 fn from(value: FourCc) -> Self {
52 FormatId::new(value)
53 }
54}
55
56impl fmt::Display for FormatId {
57 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
58 write!(f, "{:#x}", self.0)
59 }
60}
61
62/// Null container format
63pub const FORMAT_ID_NULL: FormatId = FormatId(0x0);
64
65/// Basic information about a container format.
66#[derive(Copy, Clone)]
67pub struct FormatInfo {
68 /// The `FormatId` identifier.
69 pub format: FormatId,
70 /// A short ASCII-only string identifying the format.
71 pub short_name: &'static str,
72 /// A longer, more descriptive, string identifying the format.
73 pub long_name: &'static str,
74}
75
76/// `SeekTo` specifies a position to seek to.
77pub enum SeekTo {
78 /// Seek to a `Time` in regular time units (i.e., seconds).
79 Time {
80 /// The `Time` to seek to.
81 time: Time,
82 /// If `Some`, specifies which track's timestamp should be returned after the seek. If
83 /// `None`, then the default track's timestamp is returned. If the container does not have
84 /// a default track, then the first track's timestamp is returned.
85 track_id: Option<u32>,
86 },
87 /// Seek to a track's `Timestamp` in that track's timebase units.
88 Timestamp {
89 /// The `Timestamp` to seek to.
90 ts: Timestamp,
91 /// Specifies which track `ts` is relative to.
92 track_id: u32,
93 },
94}
95
96/// `SeekedTo` is the result of a seek.
97#[derive(Copy, Clone, Debug)]
98pub struct SeekedTo {
99 /// The track the seek was relative to.
100 pub track_id: u32,
101 /// The `TimeStamp` required for the requested seek.
102 pub required_ts: Timestamp,
103 /// The `TimeStamp` that was seeked to.
104 pub actual_ts: Timestamp,
105}
106
107/// `SeekMode` selects the precision of a seek.
108#[derive(Copy, Clone, Debug, Eq, PartialEq)]
109pub enum SeekMode {
110 /// Coarse seek mode is a best-effort attempt to seek to the requested position. The actual
111 /// position seeked to may be before or after the requested position. Coarse seeking is an
112 /// optional performance enhancement. If a `FormatReader` does not support this mode an
113 /// accurate seek will be performed instead.
114 Coarse,
115 /// Accurate (aka sample-accurate) seek mode will be always seek to a position before the
116 /// requested position.
117 Accurate,
118}
119
120/// `FormatOptions` is a common set of options that all demuxers use.
121#[non_exhaustive]
122#[derive(Clone, Debug)]
123pub struct FormatOptions {
124 /// If a `FormatReader` requires a seek index, but the container does not provide one, build the
125 /// seek index during instantiation instead of building it progressively.
126 ///
127 /// Default: `false`.
128 pub prebuild_seek_index: bool,
129 /// If a seek index needs to be built, this value determines the period, in milliseconds, at
130 /// which a new entry is added to the seek index. For example, if set to 500 ms, then two
131 /// entries are added to the seek index for every 1 second of media.
132 ///
133 /// Default: `1000`.
134 ///
135 /// Note: This is a CPU vs. memory trade-off. A high value will increase the amount of IO
136 /// required during a seek, whereas a low value will require more memory. The default chosen is
137 /// a good compromise for casual playback of music, podcasts, movies, etc. However, for
138 /// highly-interactive applications, this value should be decreased.
139 pub seek_index_fill_period_ms: u16,
140 /// External, supplementary, data related to the media container read before the start of the
141 /// container, or provided through some other side-channel.
142 pub external_data: ExternalFormatData,
143}
144
145/// `ExternalFormatData` contains supplementary data related to the media container that was read
146/// before the start of the container, or provided through some other side-channel.
147#[derive(Clone, Debug, Default)]
148pub struct ExternalFormatData {
149 /// Optional metadata.
150 ///
151 /// When provided, the `FormatReader` will take the metadata revisions in this log and use them
152 /// as them as the first metdata revisions for the container.
153 pub metadata: Option<MetadataLog>,
154 /// Optional chapter information.
155 pub chapters: Option<ChapterGroup>,
156}
157
158impl Default for FormatOptions {
159 fn default() -> Self {
160 FormatOptions {
161 prebuild_seek_index: false,
162 seek_index_fill_period_ms: 1000,
163 external_data: Default::default(),
164 }
165 }
166}
167
168impl FormatOptions {
169 /// If a `FormatReader` requires a seek index, but the container does not provide one, build the
170 /// seek index during instantiation instead of building it progressively.
171 ///
172 /// Default: `false`.
173 pub fn prebuild_seek_index(mut self, prebuild: bool) -> Self {
174 self.prebuild_seek_index = prebuild;
175 self
176 }
177
178 /// If a seek index needs to be built, this value determines the period, in milliseconds, at
179 /// which a new entry is added to the seek index. For example, if set to 500 ms, then two
180 /// entries are added to the seek index for every 1 second of media.
181 ///
182 /// Default: `1000`.
183 ///
184 /// Note: This is a CPU vs. memory trade-off. A high value will increase the amount of IO
185 /// required during a seek, whereas a low value will require more memory. The default chosen is
186 /// a good compromise for casual playback of music, podcasts, movies, etc. However, for
187 /// highly-interactive applications, this value should be decreased.
188 pub fn seek_index_fill_period_ms(mut self, period: u16) -> Self {
189 self.seek_index_fill_period_ms = period;
190 self
191 }
192}
193
194bitflags! {
195 /// Flags indicating certain attributes about a track.
196 #[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
197 pub struct TrackFlags: u32 {
198 /// The track is the default track for its track type.
199 const DEFAULT = 1 << 0;
200 /// The track should be played even if user or player settings normally wouldn't call for
201 /// it.
202 ///
203 /// For example, the forced flag may be set on an English subtitle track so that it is
204 /// always played even if the audio language is also English.
205 const FORCED = 1 << 1;
206 /// The track is in the original language.
207 const ORIGINAL_LANGUAGE = 1 << 2;
208 /// The track contains commentary.
209 const COMMENTARY = 1 << 3;
210 /// The track is suitable for the hearing impaired.
211 const HEARING_IMPAIRED = 1 << 4;
212 /// The track is suitable for the visually impaired.
213 const VISUALLY_IMPAIRED = 1 << 5;
214 /// The track contains text descriptions of visual content.
215 const TEXT_DESCRIPTIONS = 1 << 6;
216 }
217}
218
219/// The track type.
220#[non_exhaustive]
221#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
222pub enum TrackType {
223 /// An audio track.
224 Audio,
225 /// A video track.
226 Video,
227 /// A subtitle track.
228 Subtitle,
229}
230
231/// A `Track` is an independently coded media bitstream. A media format may contain multiple tracks
232/// in one container. Each of those tracks are represented by one `Track`.
233#[derive(Clone, Debug)]
234pub struct Track {
235 /// A unique identifier for the track.
236 ///
237 /// For most formats this is usually the zero-based index of the track, however, some more
238 /// complex formats set this differently.
239 pub id: u32,
240 /// The codec parameters for the track.
241 ///
242 /// If `None`, the format reader was unable to determine the codec parameters and the track will
243 /// be unplayable.
244 pub codec_params: Option<CodecParameters>,
245 /// The language of the track. May be unknown or not set.
246 pub language: Option<String>,
247 /// The timebase of the track.
248 ///
249 /// The timebase is the length of time in seconds of a single tick of a timestamp or duration.
250 /// It can be used to convert any timestamp or duration related to the track into seconds.
251 pub time_base: Option<TimeBase>,
252 /// The length of the track in number of audio, video, or subtitle frames.
253 ///
254 /// This count excludes any delay or padding frames. In other words, it is the number of
255 /// playable (non-discarded) frames.
256 ///
257 /// Generally, when presenting a track's duration, the `duration` field should be used instead.
258 /// This is because a track's timebase may not always be the reciprocal of the sample or frame
259 /// rate, resulting in slight differences between the duration as stated in the container
260 /// (in timebase units) vs. when calculated from the number of playable frames.
261 pub num_frames: Option<u64>,
262 /// The duration of the track in timebase units.
263 ///
264 /// If a timebase is available, this field can be used to calculate the total duration of the
265 /// track in seconds by using [`TimeBase::calc_time`] and passing the duration as the argument.
266 pub duration: Option<Duration>,
267 /// The timestamp of the first frame.
268 pub start_ts: Timestamp,
269 /// The number of leading frames inserted by the encoder that should be skipped during playback.
270 pub delay: Option<u32>,
271 /// The number of trailing frames inserted by the encoder for padding that should be skipped
272 /// during playback.
273 pub padding: Option<u32>,
274 /// Flags indicating track attributes.
275 pub flags: TrackFlags,
276}
277
278impl Track {
279 /// Instantiate a new track with a given ID.
280 pub fn new(id: u32) -> Self {
281 Track {
282 id,
283 codec_params: None,
284 language: None,
285 time_base: None,
286 num_frames: None,
287 duration: None,
288 start_ts: Timestamp::new(0),
289 delay: None,
290 padding: None,
291 flags: TrackFlags::empty(),
292 }
293 }
294
295 /// Provide the codec parameters.
296 ///
297 /// Note: If the codec parameters contains a non-zero sample or frame rate that, a default
298 /// timebase will be derived.
299 pub fn with_codec_params(&mut self, codec_params: CodecParameters) -> &mut Self {
300 // Derive a timebase from the sample/frame rate if one is not already set.
301 if self.time_base.is_none() {
302 self.time_base = match &codec_params {
303 CodecParameters::Audio(params) => {
304 params.sample_rate.and_then(TimeBase::try_from_recip)
305 }
306 _ => None,
307 };
308 }
309
310 self.codec_params = Some(codec_params);
311 self
312 }
313
314 /// Provide the track language.
315 pub fn with_language(&mut self, language: &str) -> &mut Self {
316 self.language = Some(language.to_string());
317 self
318 }
319
320 /// Provide the `TimeBase`.
321 pub fn with_time_base(&mut self, time_base: TimeBase) -> &mut Self {
322 self.time_base = Some(time_base);
323 self
324 }
325
326 /// Provide the total number of frames.
327 pub fn with_num_frames(&mut self, num_frames: u64) -> &mut Self {
328 self.num_frames = Some(num_frames);
329 self
330 }
331
332 /// Provide the duration in timebase units.
333 pub fn with_duration(&mut self, duration: Duration) -> &mut Self {
334 self.duration = Some(duration);
335 self
336 }
337
338 /// Provide the timestamp of the first frame.
339 pub fn with_start_ts(&mut self, start_ts: Timestamp) -> &mut Self {
340 self.start_ts = start_ts;
341 self
342 }
343
344 /// Provide the number of delay frames.
345 pub fn with_delay(&mut self, delay: u32) -> &mut Self {
346 self.delay = Some(delay);
347 self
348 }
349
350 /// Provide the number of padding frames.
351 pub fn with_padding(&mut self, padding: u32) -> &mut Self {
352 self.padding = Some(padding);
353 self
354 }
355
356 /// Append provided track flags.
357 pub fn with_flags(&mut self, flags: TrackFlags) -> &mut Self {
358 self.flags |= flags;
359 self
360 }
361
362 /// Get the track type.
363 ///
364 /// Determining the track type requires knowing the codec parameters. If codec parameters is
365 /// `None`, then this function will also return `None`.
366 pub fn track_type(&self) -> Option<TrackType> {
367 match self.codec_params {
368 Some(CodecParameters::Audio(_)) => Some(TrackType::Audio),
369 Some(CodecParameters::Video(_)) => Some(TrackType::Video),
370 Some(CodecParameters::Subtitle(_)) => Some(TrackType::Subtitle),
371 None => None,
372 }
373 }
374}
375
376/// An attachment is additional data that is carried along with the container format.
377pub enum Attachment {
378 /// A file.
379 File(FileAttachment),
380 /// Application or vendor-specific data.
381 VendorData(VendorDataAttachment),
382}
383
384/// A file attachment.
385pub struct FileAttachment {
386 /// The file name.
387 pub name: String,
388 /// An optional description of the file.
389 pub description: Option<String>,
390 /// An optional media-type describing the file data.
391 pub media_type: Option<String>,
392 /// The file data.
393 pub data: Box<[u8]>,
394}
395
396/// Application or vendor-specific proprietary binary data attachment.
397#[derive(Clone, Debug)]
398pub struct VendorDataAttachment {
399 /// A text representation of the vendor's application identifier.
400 pub ident: String,
401 /// The vendor data.
402 pub data: Box<[u8]>,
403}
404
405/// Information about a piece of media as a whole.
406#[non_exhaustive]
407#[derive(Copy, Clone, Debug, Default)]
408pub struct MediaInfo {
409 /// The timebase of the media.
410 ///
411 /// The timebase is the length of time in seconds of a single tick of a timestamp or duration.
412 /// It can be used to convert any timestamp or duration related to the media into seconds.
413 ///
414 /// # For Implementations
415 ///
416 /// If a container writes an explicit timebase for the media as a whole, then this field should
417 /// be populated using that timebase. Otherwise, this field should make sense for the duration
418 /// and start timestamp fields (i.e., use the timebase of the track they're derived from).
419 pub time_base: Option<TimeBase>,
420 /// The duration of the media in timebase units.
421 ///
422 /// If a timebase is available, this field can be used to calculate the total duration of the
423 /// media in seconds by using [`TimeBase::calc_time`] and passing the duration as the argument.
424 ///
425 /// # For Implementations
426 ///
427 /// If a container writes an explicit duration for the media as a whole, then this field should
428 /// be populated using that value. Otherwise, for single track media, this field should be
429 /// duration of the sole track. For media with multiple tracks, but an explicit media duration
430 /// is not provided, this should usually be equal to the duration of the longest track.
431 pub duration: Option<Duration>,
432 /// The timestamp of the first frame in timebase units.
433 ///
434 /// If a timebase is available, this field can be used to calculate the start time of the
435 /// media in seconds by using [`TimeBase::calc_time`] and passing the timestamp as the argument.
436 ///
437 /// # For Implementations
438 ///
439 /// If a container writes an explicit start timestamp for the media as a whole, then this field
440 /// should be populated using that value. Otherwise, for single track media, this field should
441 /// be the start timestamp of the sole track. For media with multiple tracks, but an explicit
442 /// start timestamp is not provided, this should usually be equal to the start timestamp of the
443 /// track that has the earliest start timestamp.
444 pub start_ts: Timestamp,
445}
446
447impl MediaInfo {
448 /// For media that contains a single track, populates and returns `MediaInfo` from that track.
449 ///
450 /// # For Implementations
451 ///
452 /// This function only populates the timebase, duration, and start timestamp. Other fields
453 /// are defaulted and must be populated manually.
454 pub fn from_track(track: &Track) -> Self {
455 MediaInfo { time_base: track.time_base, duration: track.duration, start_ts: track.start_ts }
456 }
457
458 /// For media that contains multiple tracks, populates and returns `MediaInfo` using the
459 /// documented recommendations.
460 ///
461 /// If `tracks` is an empty slice, returns a default media information.
462 ///
463 /// # For Implementations
464 ///
465 /// This function only populates the timebase, duration, and start timestamp. Other fields
466 /// are defaulted and must be populated manually.
467 pub fn from_tracks(tracks: &[Track]) -> Self {
468 match tracks {
469 // No tracks, return defaulted media information.
470 [] => Default::default(),
471 // Single track, use information from the sole track.
472 [track] => Self::from_track(track),
473 // Multiple tracks.
474 tracks => {
475 let mut media_info: MediaInfo = Default::default();
476
477 // Earliest start timestamp.
478 if let Some(track) = tracks.iter().min_by_key(|t| t.start_ts) {
479 media_info.start_ts = track.start_ts;
480 }
481
482 // Longest duration (only considering tracks with a timebase and duration).
483 if let Some(track) = tracks
484 .iter()
485 .filter_map(|t| {
486 let tb = t.time_base?;
487 let dur = t.duration?;
488
489 // Calculate the duration of the track in seconds. Saturate here because if
490 // the track is too long then skipping this track to pick a shorter one that
491 // does not saturate is more wrong.
492 let dur_as_ts =
493 dur.timestamp_from(Timestamp::ZERO).unwrap_or(Timestamp::MAX);
494
495 let dur_time = tb.calc_time_saturating(dur_as_ts);
496
497 Some((dur_time, t))
498 })
499 .max_by_key(|(dur_time, _)| *dur_time)
500 .map(|(_, t)| t)
501 {
502 media_info.time_base = track.time_base;
503 media_info.duration = track.duration;
504 }
505
506 media_info
507 }
508 }
509 }
510
511 pub fn new() -> Self {
512 Default::default()
513 }
514
515 /// Provide the `TimeBase`.
516 pub fn with_time_base(&mut self, time_base: TimeBase) -> &mut Self {
517 self.time_base = Some(time_base);
518 self
519 }
520
521 /// Provide the duration in timebase units.
522 pub fn with_duration(&mut self, duration: Duration) -> &mut Self {
523 self.duration = Some(duration);
524 self
525 }
526
527 /// Provide the timestamp of the first frame.
528 pub fn with_start_ts(&mut self, start_ts: Timestamp) -> &mut Self {
529 self.start_ts = start_ts;
530 self
531 }
532}
533
534/// A `FormatReader` is a media container demuxer. It provides methods to read a media container
535/// and iterate over the codec bitstream packets of all encapsulated tracks. Additionally, it
536/// provides methods to access any metadata, chapters, or attachments.
537///
538/// Most, if not all, media containers contain metadata, then a number of packetized, and
539/// interleaved codec bitstreams. These bitstreams are usually referred to as tracks. Generally,
540/// the encapsulated bitstreams are independently encoded using some codec. The allowed codecs for a
541/// container are defined in the specification of the container format.
542///
543/// While demuxing, packets are read one-by-one and may be discarded or decoded at the choice of
544/// the caller. The contents of a packet is undefined: it may be a frame of video, a millisecond
545/// of audio, or a subtitle, but a packet will never contain data from two different bitstreams.
546/// Therefore the caller can be selective in what tracks(s) should be decoded and consumed.
547///
548/// `FormatReader` provides an Iterator-like interface over packets for easy consumption and
549/// filtering. Seeking will invalidate the state of any `Decoder` processing packets from the
550/// `FormatReader` and should be reset after a successful seek operation.
551pub trait FormatReader: Send + Sync {
552 /// Get basic information about the container format.
553 fn format_info(&self) -> &FormatInfo;
554
555 /// Get information about the media as a whole.
556 fn media_info(&self) -> &MediaInfo;
557
558 /// Get a list of all attachments.
559 ///
560 /// # For Implementations
561 ///
562 /// The default implementation returns an empty slice.
563 fn attachments(&self) -> &[Attachment] {
564 &[]
565 }
566
567 /// Get media chapters, if available.
568 ///
569 /// # For Implementations
570 ///
571 /// The default implementation returns `None`.
572 fn chapters(&self) -> Option<&ChapterGroup> {
573 None
574 }
575
576 /// Gets the metadata revision log.
577 fn metadata(&mut self) -> Metadata<'_>;
578
579 /// Seek, as precisely as possible depending on the mode, to the `Time` or track `TimeStamp`
580 /// requested. Returns the requested and actual `TimeStamps` seeked to, as well as the `Track`.
581 ///
582 /// After a seek, all `Decoder`s consuming packets from this reader should be reset.
583 ///
584 /// Note: The `FormatReader` by itself cannot seek to an exact audio frame, it is only capable
585 /// of seeking to the nearest `Packet`. Therefore, to seek to an exact frame, a `Decoder` must
586 /// decode packets until the requested position is reached. When using the accurate `SeekMode`,
587 /// the seeked position will always be at or before the requested position. If the coarse
588 /// `SeekMode` is used, then the seek position may be after the requested position. Coarse
589 /// seeking is an optional performance enhancement a reader may implement, therefore, a coarse
590 /// seek may sometimes be an accurate seek.
591 fn seek(&mut self, mode: SeekMode, to: SeekTo) -> Result<SeekedTo>;
592
593 /// Gets a list of tracks in the container.
594 fn tracks(&self) -> &[Track];
595
596 /// Get the first track of a certain track type.
597 fn first_track(&self, track_type: TrackType) -> Option<&Track> {
598 // Find the first track matching the desired track type.
599 self.tracks().iter().find(|track| matches_track_type(track, track_type))
600 }
601
602 /// Get the first track of a certain track type with a known (non-null) codec.
603 fn first_track_known_codec(&self, track_type: TrackType) -> Option<&Track> {
604 // Find the first track matching the desired track type with a known codec.
605 self.tracks().iter().find(|track| match &track.codec_params {
606 Some(CodecParameters::Audio(params)) if track_type == TrackType::Audio => {
607 params.codec != audio::CODEC_ID_NULL_AUDIO
608 }
609 Some(CodecParameters::Video(params)) if track_type == TrackType::Video => {
610 params.codec != video::CODEC_ID_NULL_VIDEO
611 }
612 Some(CodecParameters::Subtitle(params)) if track_type == TrackType::Subtitle => {
613 params.codec != subtitle::CODEC_ID_NULL_SUBTITLE
614 }
615 _ => false,
616 })
617 }
618
619 /// Get the default track of a certain track type.
620 ///
621 /// # For Implementations
622 ///
623 /// The default implementation of this function will return the first track of the desired track
624 /// type with the default flag set, or if there is no track with the default flag set, the first
625 /// track of the desired track type with a non-null codec ID. If no tracks are present then
626 /// `None` is returned.
627 ///
628 /// Most format reader implementations should not override the default implementation and
629 /// instead set the default track flag appropriately.
630 fn default_track(&self, track_type: TrackType) -> Option<&Track> {
631 // Find a track with the default flag set that matches the desired track type.
632 self.tracks()
633 .iter()
634 .filter(|track| track.flags.contains(TrackFlags::DEFAULT))
635 .find(|track| matches_track_type(track, track_type))
636 .or_else(|| self.first_track_known_codec(track_type))
637 }
638
639 /// Reader the next packet from the container.
640 ///
641 /// If `Ok(None)` is returned, the media has ended and no more packets will be produced until
642 /// the reader is seeked to a new position.
643 ///
644 /// If `Err(ResetRequired)` is returned, then the track list must be re-examined and all
645 /// `Decoder`s re-created. All other errors are unrecoverable.
646 fn next_packet(&mut self) -> Result<Option<Packet>>;
647
648 /// Consumes the `FormatReader` and returns the underlying media source stream
649 fn into_inner<'s>(self: Box<Self>) -> MediaSourceStream<'s>
650 where
651 Self: 's;
652}
653
654/// Returns true, if `track` is of the specific track type.
655fn matches_track_type(track: &Track, track_type: TrackType) -> bool {
656 match track.codec_params {
657 Some(CodecParameters::Audio(_)) if track_type == TrackType::Audio => true,
658 Some(CodecParameters::Video(_)) if track_type == TrackType::Video => true,
659 Some(CodecParameters::Subtitle(_)) if track_type == TrackType::Subtitle => true,
660 _ => false,
661 }
662}
663
664pub mod util {
665 //! Helper utilities for implementing `FormatReader`s.
666
667 use crate::units::Timestamp;
668
669 /// A `SeekPoint` is a mapping between a sample or frame number to byte offset within a media
670 /// stream.
671 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
672 pub struct SeekPoint {
673 /// The frame or sample timestamp of the `SeekPoint`.
674 pub frame_ts: Timestamp,
675 /// The byte offset of the `SeekPoint`s timestamp relative to a format-specific location.
676 pub byte_offset: u64,
677 /// The number of frames the `SeekPoint` covers.
678 pub n_frames: u32,
679 }
680
681 impl SeekPoint {
682 fn new(frame_ts: Timestamp, byte_offset: u64, n_frames: u32) -> Self {
683 SeekPoint { frame_ts, byte_offset, n_frames }
684 }
685 }
686
687 /// A `SeekIndex` stores `SeekPoint`s (generally a sample or frame number to byte offset) within
688 /// a media stream and provides methods to efficiently search for the nearest `SeekPoint`(s)
689 /// given a timestamp.
690 ///
691 /// A `SeekIndex` does not require complete coverage of the entire media stream. However, the
692 /// better the coverage, the smaller the manual search range the `SeekIndex` will return.
693 #[derive(Default)]
694 pub struct SeekIndex {
695 points: Vec<SeekPoint>,
696 }
697
698 /// `SeekSearchResult` is the return value for a search on a `SeekIndex`. It returns a range of
699 /// `SeekPoint`s a `FormatReader` should search to find the desired timestamp. Ranges are
700 /// lower-bound inclusive, and upper-bound exclusive.
701 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
702 pub enum SeekSearchResult {
703 /// The `SeekIndex` is empty so the desired timestamp could not be found. The entire stream
704 /// should be searched for the desired timestamp.
705 Stream,
706 /// The desired timestamp can be found before, the `SeekPoint`. The stream should be
707 /// searched for the desired timestamp from the start of the stream up-to, but not
708 /// including, the `SeekPoint`.
709 Upper(SeekPoint),
710 /// The desired timestamp can be found at, or after, the `SeekPoint`. The stream should be
711 /// searched for the desired timestamp starting at the provided `SeekPoint` up-to the end of
712 /// the stream.
713 Lower(SeekPoint),
714 /// The desired timestamp can be found within the range. The stream should be searched for
715 /// the desired starting at the first `SeekPoint` up-to, but not-including, the second
716 /// `SeekPoint`.
717 Range(SeekPoint, SeekPoint),
718 }
719
720 impl SeekIndex {
721 /// Create an empty `SeekIndex`
722 pub fn new() -> SeekIndex {
723 SeekIndex { points: Vec::new() }
724 }
725
726 /// Insert a `SeekPoint` into the index.
727 pub fn insert(&mut self, ts: Timestamp, byte_offset: u64, n_frames: u32) {
728 // Create the seek point.
729 let seek_point = SeekPoint::new(ts, byte_offset, n_frames);
730
731 // Get the timestamp of the last entry in the index.
732 let (last_ts, last_offset) =
733 self.points.last().map_or((Timestamp::MIN, 0), |p| (p.frame_ts, p.byte_offset));
734
735 // If the seek point has a timestamp greater-than and byte offset greater-than or equal to
736 // the last entry in the index, then simply append it to the index.
737 if ts > last_ts && byte_offset >= last_offset {
738 self.points.push(seek_point)
739 }
740 else if ts < last_ts {
741 // If the seek point has a timestamp less-than the last entry in the index, then the
742 // insertion point must be found. This case should rarely occur.
743 let i = self
744 .points
745 .partition_point(|p| ts > p.frame_ts && byte_offset >= p.byte_offset);
746
747 // Insert if the point found or if the points are empty
748 if i < self.points.len() || i == 0 {
749 self.points.insert(i, seek_point);
750 }
751 }
752 }
753
754 /// Search the index to find a bounded range of bytes wherein the specified frame timestamp
755 /// will be contained. If the index is empty, this function simply returns a result
756 /// indicating the entire stream should be searched manually.
757 pub fn search(&self, frame_ts: Timestamp) -> SeekSearchResult {
758 // The index must contain atleast one SeekPoint to return a useful result.
759 if !self.points.is_empty() {
760 let mut lower = 0;
761 let mut upper = self.points.len() - 1;
762
763 // If the desired timestamp is less than the first SeekPoint within the index,
764 // indicate that the stream should be searched from the beginning.
765 if frame_ts < self.points[lower].frame_ts {
766 return SeekSearchResult::Upper(self.points[lower]);
767 }
768 // If the desired timestamp is greater than or equal to the last SeekPoint within
769 // the index, indicate that the stream should be searched from the last SeekPoint.
770 else if frame_ts >= self.points[upper].frame_ts {
771 return SeekSearchResult::Lower(self.points[upper]);
772 }
773
774 // Desired timestamp is between the lower and upper indicies. Perform a binary
775 // search to find a range of SeekPoints containing the desired timestamp. The binary
776 // search exits when either two adjacent SeekPoints or a single SeekPoint is found.
777 while upper - lower > 1 {
778 let mid = (lower + upper) / 2;
779 let mid_ts = self.points[mid].frame_ts;
780
781 if frame_ts < mid_ts {
782 upper = mid;
783 }
784 else {
785 lower = mid;
786 }
787 }
788
789 return SeekSearchResult::Range(self.points[lower], self.points[upper]);
790 }
791
792 // The index is empty, the stream must be searched manually.
793 SeekSearchResult::Stream
794 }
795 }
796
797 #[cfg(test)]
798 mod tests {
799 use crate::units::Timestamp;
800
801 use super::{SeekIndex, SeekPoint, SeekSearchResult};
802
803 #[test]
804 fn verify_seek_index_search() {
805 let mut index = SeekIndex::new();
806 // Normal index insert
807 index.insert(Timestamp::new(479232), 706812, 1152);
808 index.insert(Timestamp::new(959616), 1421536, 1152);
809 index.insert(Timestamp::new(1919232), 2833241, 1152);
810 index.insert(Timestamp::new(2399616), 3546987, 1152);
811 index.insert(Timestamp::new(2880000), 4259455, 1152);
812
813 // Search for point lower than the first entry
814 assert_eq!(
815 index.search(Timestamp::new(0)),
816 SeekSearchResult::Upper(SeekPoint::new(Timestamp::new(479232), 706812, 1152))
817 );
818
819 // Search for point higher than last entry
820 assert_eq!(
821 index.search(Timestamp::new(3000000)),
822 SeekSearchResult::Lower(SeekPoint::new(Timestamp::new(2880000), 4259455, 1152))
823 );
824
825 // Search for point that has equal timestamp with some index
826 assert_eq!(
827 index.search(Timestamp::new(959616)),
828 SeekSearchResult::Range(
829 SeekPoint::new(Timestamp::new(959616), 1421536, 1152),
830 SeekPoint::new(Timestamp::new(1919232), 2833241, 1152)
831 )
832 );
833
834 // Index insert out of order
835 index.insert(Timestamp::new(1440000), 2132419, 1152);
836 index.insert(Timestamp::new(-78000), 20, 1152);
837 index.insert(Timestamp::new(-50000), 45000, 1152);
838
839 // Search for a negative timestamp.
840 assert_eq!(
841 index.search(Timestamp::MIN),
842 SeekSearchResult::Upper(SeekPoint::new(Timestamp::new(-78000), 20, 1152))
843 );
844
845 assert_eq!(
846 index.search(Timestamp::new(-69000)),
847 SeekSearchResult::Range(
848 SeekPoint::new(Timestamp::new(-78000), 20, 1152),
849 SeekPoint::new(Timestamp::new(-50000), 45000, 1152)
850 )
851 );
852
853 // Search for 0 again.
854 assert_eq!(
855 index.search(Timestamp::new(0)),
856 SeekSearchResult::Range(
857 SeekPoint::new(Timestamp::new(-50000), 45000, 1152),
858 SeekPoint::new(Timestamp::new(479232), 706812, 1152)
859 )
860 );
861
862 // Search for point that have out of order index when inserting
863 assert_eq!(
864 index.search(Timestamp::new(1000000)),
865 SeekSearchResult::Range(
866 SeekPoint::new(Timestamp::new(959616), 1421536, 1152),
867 SeekPoint::new(Timestamp::new(1440000), 2132419, 1152)
868 )
869 );
870
871 // Index insert with byte_offset less than last entry
872 index.insert(Timestamp::new(3359232), 0, 0);
873
874 // Search for ignored point because byte_offset less than last entry
875 assert_eq!(
876 index.search(Timestamp::new(3359232)),
877 SeekSearchResult::Lower(SeekPoint::new(Timestamp::new(2880000), 4259455, 1152))
878 );
879 }
880 }
881}
882
883/// IDs for well-known container formats.
884pub mod well_known {
885 use super::FormatId;
886
887 /// Waveform Audio File Format
888 pub const FORMAT_ID_WAVE: FormatId = FormatId(0x100);
889 /// Audio Interchange File Format
890 pub const FORMAT_ID_AIFF: FormatId = FormatId(0x101);
891 /// Audio Video Interleave
892 pub const FORMAT_ID_AVI: FormatId = FormatId(0x102);
893 /// Core Audio Format
894 pub const FORMAT_ID_CAF: FormatId = FormatId(0x103);
895 /// MPEG Audio Layer 1 Native
896 pub const FORMAT_ID_MP1: FormatId = FormatId(0x104);
897 /// MPEG Audio Layer 2 Native
898 pub const FORMAT_ID_MP2: FormatId = FormatId(0x105);
899 /// MPEG Audio Layer 3 Native
900 pub const FORMAT_ID_MP3: FormatId = FormatId(0x106);
901 /// Audio Data Transport Stream
902 pub const FORMAT_ID_ADTS: FormatId = FormatId(0x107);
903 /// Ogg
904 pub const FORMAT_ID_OGG: FormatId = FormatId(0x108);
905 /// Free Lossless Audio Codec Native
906 pub const FORMAT_ID_FLAC: FormatId = FormatId(0x109);
907 /// WavPack
908 pub const FORMAT_ID_WAVPACK: FormatId = FormatId(0x10a);
909 /// ISO Base Media File Format
910 pub const FORMAT_ID_ISOMP4: FormatId = FormatId(0x10b);
911 /// Matroska/WebM
912 pub const FORMAT_ID_MKV: FormatId = FormatId(0x10c);
913 /// Flash Video
914 pub const FORMAT_ID_FLV: FormatId = FormatId(0x10d);
915}