Skip to main content

moq_mux/container/
mod.rs

1//! Container formats.
2//!
3//! A container decides how a media frame is laid out inside a moq-lite
4//! frame: framing overhead, whether multiple samples can share one moq
5//! frame, and whether the same encoding doubles as a file format on disk.
6//!
7//! Each submodule implements one format. The wire-level ones implement
8//! the [`Container`] trait, so [`Producer<C>`] and [`Consumer<C>`] can
9//! be generic over the choice. The catalog announces a container per
10//! track; [`catalog::hang::Container`](crate::catalog::hang::Container)
11//! dispatches the right implementation at runtime.
12
13use std::task::Poll;
14
15use bytes::Bytes;
16
17mod consumer;
18pub(crate) mod jitter;
19mod producer;
20mod source;
21
22pub mod flv;
23pub mod fmp4;
24pub mod legacy;
25pub mod loc;
26pub mod mkv;
27pub mod ts;
28
29pub use consumer::Consumer;
30pub use producer::Producer;
31pub(crate) use source::ExportSource;
32
33/// Microsecond presentation timestamp, the canonical timebase for media frames in moq-mux on `main`.
34pub type Timestamp = moq_net::Timescale<1_000_000>;
35
36/// A decoded media frame: timestamp, payload bytes, keyframe flag.
37///
38/// `payload` is the raw codec bitstream that gets handed to the decoder.
39/// The exact shape depends on the codec (Annex B for H.264/H.265, OBU for
40/// AV1, and so on).
41#[derive(Clone, Debug)]
42pub struct Frame {
43	/// Presentation timestamp.
44	///
45	/// Each container picks its own native scale: fmp4 uses the source
46	/// `mdhd.timescale`, mkv uses nanoseconds, legacy is fixed at microseconds.
47	/// LOC defaults to microseconds but a decoded frame keeps whatever per-frame
48	/// timescale the wire carried, so an exporter can re-emit without forcing
49	/// micros. Frames within a track must be in *decode* order, not display
50	/// order. B-frames may have non-monotonic presentation timestamps.
51	pub timestamp: Timestamp,
52
53	/// How long this frame occupies the presentation timeline, in the frame's
54	/// own scale, when the container reports it.
55	///
56	/// CMAF carries a per-sample duration (trun sample-duration); containers
57	/// that don't (Legacy, LOC) leave this `None`. The [`Consumer`] adds it to
58	/// `timestamp` to learn how far a group has presented, so it can advance to
59	/// a newer group as soon as the gap is covered instead of waiting out the
60	/// latency budget.
61	pub duration: Option<Timestamp>,
62
63	/// Encoded codec payload.
64	pub payload: Bytes,
65
66	/// Whether this frame is a keyframe.
67	///
68	/// Containers that carry the bit on the wire (CMAF reads it from
69	/// trun sample-flags) should set it; containers that don't (Legacy,
70	/// LOC) leave it `false`. The wrapping [`Consumer`] still asserts
71	/// "first frame in a group is a keyframe" as a fallback, so the
72	/// Legacy/LOC case lands correctly without anyone having to know.
73	pub keyframe: bool,
74}
75
76/// A non-keyframe frame arrived with no open group.
77///
78/// A track must open with a keyframe (and so must the frame after
79/// [`finish_group`](Producer::finish_group) / [`seek`](Producer::seek)).
80/// [`Producer::write`] returns this so a caller joining mid-stream can skip
81/// frames until the first keyframe instead of treating it as fatal.
82#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
83#[error("missing keyframe: a group must open on a keyframe")]
84pub struct MissingKeyframe;
85
86/// Encode and decode media frames over a moq-lite group.
87///
88/// Implementors decide how many [`Frame`]s map onto one moq-lite frame:
89/// Legacy and LOC write one media frame per moq-lite frame; CMAF can
90/// pack many samples into a single moof+mdat fragment.
91pub trait Container {
92	/// Container-specific error. Must be convertible from [`moq_net::Error`]
93	/// (so IO errors propagate) and [`MissingKeyframe`] (so the producer can
94	/// reject a group that doesn't open on a keyframe).
95	type Error: std::error::Error + Send + Sync + Unpin + From<moq_net::Error> + From<MissingKeyframe>;
96
97	/// Encode one or more frames into a single moq-lite frame appended to `group`.
98	fn write(&self, group: &mut moq_net::GroupProducer, frames: &[Frame]) -> Result<(), Self::Error>;
99
100	/// Poll the next moq-lite frame from `group` and decode it. Returns
101	/// [`Read::Done`] when the group has ended, [`Read::Frame`] for the common
102	/// one-frame-per-moq-frame case (Legacy, LOC), or [`Read::Fragment`] when a
103	/// single moq frame decodes into several media frames (a CMAF moof+mdat).
104	fn poll_read(&self, group: &mut moq_net::GroupConsumer, waiter: &kio::Waiter) -> Poll<Result<Read, Self::Error>>;
105
106	/// Async wrapper around [`Self::poll_read`].
107	fn read(&self, group: &mut moq_net::GroupConsumer) -> impl std::future::Future<Output = Result<Read, Self::Error>>
108	where
109		Self: Sync,
110	{
111		async { kio::wait(|waiter| self.poll_read(group, waiter)).await }
112	}
113}
114
115/// The outcome of one [`Container::poll_read`].
116///
117/// Splitting the single-frame case ([`Frame`](Read::Frame)) from the multi-frame
118/// case ([`Fragment`](Read::Fragment)) lets the common one-frame-per-moq-frame
119/// containers (Legacy, LOC) decode without allocating a `Vec` per frame.
120#[derive(Debug)]
121#[non_exhaustive]
122pub enum Read {
123	/// The group has ended; there are no more frames.
124	Done,
125	/// A single decoded media frame.
126	Frame(Frame),
127	/// One moq frame decoded into several media frames, e.g. every sample in a
128	/// CMAF moof+mdat fragment.
129	Fragment(Vec<Frame>),
130}
131
132impl Read {
133	/// The decoded frames as a slice, so callers can iterate without matching the
134	/// variant: empty for [`Done`](Read::Done), one element for [`Frame`](Read::Frame),
135	/// or the whole batch for [`Fragment`](Read::Fragment).
136	pub fn frames(&self) -> &[Frame] {
137		match self {
138			Read::Done => &[],
139			Read::Frame(frame) => std::slice::from_ref(frame),
140			Read::Fragment(frames) => frames,
141		}
142	}
143}
144
145impl<'a> IntoIterator for &'a Read {
146	type Item = &'a Frame;
147	type IntoIter = std::slice::Iter<'a, Frame>;
148
149	fn into_iter(self) -> Self::IntoIter {
150		self.frames().iter()
151	}
152}