Skip to main content

zencodec_media/
track.rs

1//! Tracks: what a container says about each stream.
2//!
3//! Codec identity ([`CodecId`]) is separate from the container: AV1 lives
4//! in IVF, WebM, MP4 and AVIF alike, and each container crate keeps its own
5//! mapping from fourccs and codec ids to `CodecId`. [`TrackInfo`] has
6//! private fields and a validating builder so new fields can be added
7//! without breaking anyone.
8
9use alloc::boxed::Box;
10use alloc::string::String;
11use alloc::sync::Arc;
12use core::fmt;
13
14use zencodec::time::{FrameDuration, TimeBase};
15use zenpixels::Cicp;
16
17/// What a track carries.
18#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
19#[non_exhaustive]
20pub enum TrackKind {
21    /// Pictures.
22    Video,
23    /// Sound.
24    Audio,
25    /// Timed text.
26    Subtitle,
27    /// Anything else (timed metadata, attachments).
28    Data,
29}
30
31/// A codec identifier read from a file that no [`CodecId`] variant names: a
32/// fourcc, a Matroska `CodecID` string. Owned, so it can hold whatever the
33/// file says.
34#[derive(Clone, Debug, PartialEq, Eq, Hash)]
35pub struct CodecTag(Box<str>);
36
37impl CodecTag {
38    /// A tag holding `tag`.
39    pub fn new(tag: &str) -> Self {
40        Self(String::from(tag).into_boxed_str())
41    }
42
43    /// The tag text.
44    pub fn as_str(&self) -> &str {
45        &self.0
46    }
47}
48
49/// A codec, independent of the container that carries it.
50#[derive(Clone, Debug, PartialEq, Eq, Hash)]
51#[non_exhaustive]
52pub enum CodecId {
53    /// AV1.
54    Av1,
55    /// VP9.
56    Vp9,
57    /// VP8.
58    Vp8,
59    /// H.264 / AVC.
60    H264,
61    /// H.265 / HEVC.
62    Hevc,
63    /// AAC.
64    Aac,
65    /// Opus.
66    Opus,
67    /// FLAC.
68    Flac,
69    /// Uncompressed PCM.
70    Pcm,
71    /// A codec the container named but this crate does not.
72    Other(CodecTag),
73}
74
75/// What a container declares about a video track.
76#[derive(Clone, Copy, Debug, PartialEq, Eq)]
77#[non_exhaustive]
78pub struct VideoTrackInfo {
79    /// Coded width in pixels.
80    pub coded_width: u32,
81    /// Coded height in pixels.
82    pub coded_height: u32,
83    /// Intended display size, when the container declares one.
84    pub display: Option<(u32, u32)>,
85    /// The container's colour claim (MP4 `colr`, WebM `Colour`). The
86    /// bitstream's claim travels on each frame; the two are kept apart.
87    pub color: Option<Cicp>,
88}
89
90impl VideoTrackInfo {
91    /// A track of `coded_width` x `coded_height` pictures.
92    pub const fn new(coded_width: u32, coded_height: u32) -> Self {
93        Self {
94            coded_width,
95            coded_height,
96            display: None,
97            color: None,
98        }
99    }
100
101    /// The declared display size.
102    pub const fn with_display(mut self, width: u32, height: u32) -> Self {
103        self.display = Some((width, height));
104        self
105    }
106
107    /// The container's colour claim.
108    pub const fn with_color(mut self, color: Cicp) -> Self {
109        self.color = Some(color);
110        self
111    }
112}
113
114/// Audio channels: a count, and the layout when the container gives one.
115#[derive(Clone, Copy, Debug, PartialEq, Eq)]
116#[non_exhaustive]
117pub struct ChannelLayout {
118    /// Number of channels.
119    pub count: u16,
120    /// A channel mask (WAVE / ITU order) when the container gives one.
121    pub mask: Option<u64>,
122}
123
124impl ChannelLayout {
125    /// `count` channels with no declared layout.
126    pub const fn new(count: u16) -> Self {
127        Self { count, mask: None }
128    }
129
130    /// The declared channel mask.
131    pub const fn with_mask(mut self, mask: u64) -> Self {
132        self.mask = Some(mask);
133        self
134    }
135}
136
137/// What a container declares about an audio track.
138#[derive(Clone, Copy, Debug, PartialEq, Eq)]
139#[non_exhaustive]
140pub struct AudioTrackInfo {
141    /// Samples per second per channel.
142    pub sample_rate: u32,
143    /// The channels.
144    pub channels: ChannelLayout,
145}
146
147impl AudioTrackInfo {
148    /// A track at `sample_rate` with `channels`.
149    pub const fn new(sample_rate: u32, channels: ChannelLayout) -> Self {
150        Self {
151            sample_rate,
152            channels,
153        }
154    }
155}
156
157/// One track of a container: its codec, clock, configuration and what the
158/// container declares about it. Built with [`TrackInfo::video`],
159/// [`TrackInfo::audio`] or [`TrackInfo::other`].
160///
161/// Delays and edit offsets are carried untouched, so a stream copy preserves
162/// them. They are applied exactly once, by the decoder adapter configured
163/// from this track; the session never trims.
164#[derive(Clone, Debug)]
165pub struct TrackInfo {
166    index: u32,
167    kind: TrackKind,
168    codec: CodecId,
169    time_base: TimeBase,
170    codec_private: Option<Arc<[u8]>>,
171    config_epoch: u32,
172    video: Option<VideoTrackInfo>,
173    audio: Option<AudioTrackInfo>,
174    codec_delay: Option<FrameDuration>,
175    seek_preroll: Option<FrameDuration>,
176    edit_delay_ticks: Option<i64>,
177    declared_packets: Option<u64>,
178    declared_duration_ticks: Option<u64>,
179}
180
181/// An invalid track description.
182#[derive(Clone, Copy, Debug, PartialEq, Eq)]
183#[non_exhaustive]
184pub enum TrackError {
185    /// A video track with zero width or height.
186    ZeroDimension,
187    /// An audio track with a zero sample rate.
188    ZeroSampleRate,
189    /// An audio track with no channels.
190    NoChannels,
191}
192
193impl fmt::Display for TrackError {
194    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
195        f.write_str(match self {
196            Self::ZeroDimension => "video track has a zero dimension",
197            Self::ZeroSampleRate => "audio track has a zero sample rate",
198            Self::NoChannels => "audio track has no channels",
199        })
200    }
201}
202
203impl core::error::Error for TrackError {}
204
205/// Builds a [`TrackInfo`]; [`build`](Self::build) validates it.
206#[derive(Clone, Debug)]
207pub struct TrackInfoBuilder(TrackInfo);
208
209impl TrackInfo {
210    fn base(index: u32, kind: TrackKind, codec: CodecId, time_base: TimeBase) -> TrackInfo {
211        TrackInfo {
212            index,
213            kind,
214            codec,
215            time_base,
216            codec_private: None,
217            config_epoch: 0,
218            video: None,
219            audio: None,
220            codec_delay: None,
221            seek_preroll: None,
222            edit_delay_ticks: None,
223            declared_packets: None,
224            declared_duration_ticks: None,
225        }
226    }
227
228    /// A video track.
229    pub fn video(
230        index: u32,
231        codec: CodecId,
232        time_base: TimeBase,
233        video: VideoTrackInfo,
234    ) -> TrackInfoBuilder {
235        let mut t = Self::base(index, TrackKind::Video, codec, time_base);
236        t.video = Some(video);
237        TrackInfoBuilder(t)
238    }
239
240    /// An audio track.
241    pub fn audio(
242        index: u32,
243        codec: CodecId,
244        time_base: TimeBase,
245        audio: AudioTrackInfo,
246    ) -> TrackInfoBuilder {
247        let mut t = Self::base(index, TrackKind::Audio, codec, time_base);
248        t.audio = Some(audio);
249        TrackInfoBuilder(t)
250    }
251
252    /// A subtitle or data track.
253    pub fn other(
254        index: u32,
255        kind: TrackKind,
256        codec: CodecId,
257        time_base: TimeBase,
258    ) -> TrackInfoBuilder {
259        TrackInfoBuilder(Self::base(index, kind, codec, time_base))
260    }
261
262    /// The track's index in its container's track list.
263    pub fn index(&self) -> u32 {
264        self.index
265    }
266
267    /// What the track carries.
268    pub fn kind(&self) -> TrackKind {
269        self.kind
270    }
271
272    /// The codec.
273    pub fn codec(&self) -> &CodecId {
274        &self.codec
275    }
276
277    /// The clock its timestamps use.
278    pub fn time_base(&self) -> TimeBase {
279        self.time_base
280    }
281
282    /// Codec configuration bytes from the container (`av1C`, `avcC`,
283    /// `OpusHead`), as stored.
284    pub fn codec_private(&self) -> Option<&Arc<[u8]>> {
285        self.codec_private.as_ref()
286    }
287
288    /// The configuration epoch the current `codec_private` belongs to.
289    pub fn config_epoch(&self) -> u32 {
290        self.config_epoch
291    }
292
293    /// Video declarations, for a video track.
294    pub fn video_info(&self) -> Option<&VideoTrackInfo> {
295        self.video.as_ref()
296    }
297
298    /// Audio declarations, for an audio track.
299    pub fn audio_info(&self) -> Option<&AudioTrackInfo> {
300        self.audio.as_ref()
301    }
302
303    /// Decoder delay the container records (Opus pre-skip, AAC priming).
304    pub fn codec_delay(&self) -> Option<FrameDuration> {
305        self.codec_delay
306    }
307
308    /// How much must be decoded before a seek target to output it
309    /// correctly (Opus in WebM: 80 ms).
310    pub fn seek_preroll(&self) -> Option<FrameDuration> {
311        self.seek_preroll
312    }
313
314    /// The edit-list offset of the first presentation, in track ticks.
315    pub fn edit_delay_ticks(&self) -> Option<i64> {
316        self.edit_delay_ticks
317    }
318
319    /// How many packets the container says the track has.
320    pub fn declared_packets(&self) -> Option<u64> {
321        self.declared_packets
322    }
323
324    /// The track length the container declares, in track ticks.
325    pub fn declared_duration_ticks(&self) -> Option<u64> {
326        self.declared_duration_ticks
327    }
328}
329
330impl TrackInfoBuilder {
331    /// Codec configuration bytes.
332    pub fn with_codec_private(mut self, bytes: Arc<[u8]>) -> Self {
333        self.0.codec_private = Some(bytes);
334        self
335    }
336
337    /// The configuration epoch.
338    pub fn with_config_epoch(mut self, epoch: u32) -> Self {
339        self.0.config_epoch = epoch;
340        self
341    }
342
343    /// Decoder delay.
344    pub fn with_codec_delay(mut self, d: FrameDuration) -> Self {
345        self.0.codec_delay = Some(d);
346        self
347    }
348
349    /// Seek pre-roll.
350    pub fn with_seek_preroll(mut self, d: FrameDuration) -> Self {
351        self.0.seek_preroll = Some(d);
352        self
353    }
354
355    /// Edit-list offset in track ticks.
356    pub fn with_edit_delay_ticks(mut self, ticks: i64) -> Self {
357        self.0.edit_delay_ticks = Some(ticks);
358        self
359    }
360
361    /// Declared packet count.
362    pub fn with_declared_packets(mut self, n: u64) -> Self {
363        self.0.declared_packets = Some(n);
364        self
365    }
366
367    /// Declared length in track ticks.
368    pub fn with_declared_duration_ticks(mut self, n: u64) -> Self {
369        self.0.declared_duration_ticks = Some(n);
370        self
371    }
372
373    /// Validate and finish.
374    ///
375    /// # Errors
376    ///
377    /// A [`TrackError`] for a zero video dimension, a zero sample rate, or
378    /// no audio channels.
379    pub fn build(self) -> Result<TrackInfo, TrackError> {
380        let t = self.0;
381        if let Some(v) = &t.video
382            && (v.coded_width == 0 || v.coded_height == 0)
383        {
384            return Err(TrackError::ZeroDimension);
385        }
386        if let Some(a) = &t.audio {
387            if a.sample_rate == 0 {
388                return Err(TrackError::ZeroSampleRate);
389            }
390            if a.channels.count == 0 {
391                return Err(TrackError::NoChannels);
392            }
393        }
394        Ok(t)
395    }
396}