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#[cfg(test)]
22pub(crate) mod test_util;
23
24pub mod flv;
25pub mod fmp4;
26pub mod legacy;
27pub mod loc;
28pub mod mkv;
29pub mod ts;
30
31pub use consumer::Consumer;
32pub use producer::Producer;
33pub(crate) use source::ExportSource;
34
35/// A decoded media frame: timestamp, payload bytes, keyframe flag.
36///
37/// `payload` is the raw codec bitstream that gets handed to the decoder.
38/// The exact shape depends on the codec (Annex B for H.264/H.265, OBU for
39/// AV1, and so on).
40#[derive(Clone, Debug)]
41pub struct Frame {
42 /// Presentation timestamp.
43 ///
44 /// Each container picks its own native scale: fmp4 uses the source
45 /// `mdhd.timescale`, mkv uses nanoseconds, legacy is fixed at microseconds.
46 /// LOC defaults to microseconds but a decoded frame keeps whatever per-frame
47 /// timescale the wire carried, so an exporter can re-emit without forcing
48 /// micros. Frames within a track must be in *decode* order, not display
49 /// order. B-frames may have non-monotonic presentation timestamps.
50 pub timestamp: moq_net::Timestamp,
51
52 /// Sample duration in the frame's own scale, when the container reports it.
53 ///
54 /// CMAF carries a per-sample duration (trun sample-duration); containers
55 /// that don't (Legacy, LOC) leave this `None`. The [`Consumer`] adds it to
56 /// `timestamp` to learn how far a group has presented, so it can advance to
57 /// a newer group as soon as the gap is covered instead of waiting out the
58 /// latency budget.
59 pub duration: Option<moq_net::Timestamp>,
60
61 /// Encoded codec payload.
62 pub payload: Bytes,
63
64 /// Whether this frame is a keyframe.
65 ///
66 /// Containers that carry the bit on the wire (CMAF reads it from
67 /// trun sample-flags) should set it; containers that don't (Legacy,
68 /// LOC) leave it `false`. The wrapping [`Consumer`] still asserts
69 /// "first frame in a group is a keyframe" as a fallback, so the
70 /// Legacy/LOC case lands correctly without anyone having to know.
71 pub keyframe: bool,
72}
73
74/// A non-keyframe frame arrived with no open group.
75///
76/// A track must open with a keyframe (and so must the frame after
77/// [`cut`](Producer::cut) / [`seek`](Producer::seek)).
78/// [`Producer::write`] returns this so a caller joining mid-stream can skip
79/// frames until the first keyframe instead of treating it as fatal.
80#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
81#[error("missing keyframe: a group must open on a keyframe")]
82pub struct MissingKeyframe;
83
84/// Encode and decode media frames over a moq-lite group.
85///
86/// Implementors decide how many [`Frame`]s map onto one moq-lite frame:
87/// Legacy and LOC write one media frame per moq-lite frame; CMAF can
88/// pack many samples into a single moof+mdat fragment.
89pub trait Container {
90 /// Container-specific error. Must be convertible from [`moq_net::Error`]
91 /// (so IO errors propagate) and [`MissingKeyframe`] (so the producer can
92 /// reject a group that doesn't open on a keyframe).
93 type Error: std::error::Error + Send + Sync + Unpin + From<moq_net::Error> + From<MissingKeyframe>;
94
95 /// Encode one or more frames into a single moq-lite frame appended to `group`.
96 fn write(&self, group: &mut moq_net::group::Producer, frames: &[Frame]) -> Result<(), Self::Error>;
97
98 /// Poll the next moq-lite frame from `group` and decode it into media
99 /// frames. A single call may produce multiple media frames (e.g. all samples
100 /// in a CMAF fragment).
101 ///
102 /// Only `Ok(None)` signals the end of the group. `Ok(Some(batch))` may carry
103 /// an empty `batch`: a wire frame was consumed but decoded to no media frames
104 /// (e.g. a CMAF fragment with zero samples). That is not end-of-group; poll
105 /// again for the next batch. Callers accumulating frames must not treat an
106 /// empty batch as completion.
107 fn poll_read(
108 &self,
109 group: &mut moq_net::group::Consumer,
110 waiter: &kio::Waiter,
111 ) -> Poll<Result<Option<Vec<Frame>>, Self::Error>>;
112
113 /// Async wrapper around [`Self::poll_read`]. Carries the same contract: only
114 /// `Ok(None)` ends the group, and `Ok(Some(batch))` may hand back an empty
115 /// `batch` (poll again for more), so a caller loop must key completion off
116 /// `None`, not an empty batch.
117 fn read(
118 &self,
119 group: &mut moq_net::group::Consumer,
120 ) -> impl std::future::Future<Output = Result<Option<Vec<Frame>>, Self::Error>>
121 where
122 Self: Sync,
123 {
124 async { kio::wait(|waiter| self.poll_read(group, waiter)).await }
125 }
126}