Skip to main content

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}