Skip to main content

zencodec_media/
container.rs

1//! Containers.
2//!
3//! A [`Demuxer`] is sans-I/O and speaks the image decoders' dialogue: the
4//! driver offers an [`Input`], the demuxer takes a prefix and either yields a
5//! [`DemuxEvent`] or names what it [`requested`](Demuxer::requested) next.
6//! [`DemuxStage`] makes any demuxer a [`zencodec_io::ByteStepper`], so every
7//! `zencodec-io` cursor (slice, `ReadAt` with coalesced ranges, `Read`,
8//! async) drives it; a container that needs its index first (an MP4 with
9//! `moov` at the end) asks for that range and declares
10//! [`AccessPattern::RandomAccess`].
11//!
12//! A [`Muxer`] takes packets and lets the caller pull its bytes through
13//! `pending_output` / `consume_output` / `take_output`, as a streaming image
14//! encoder does. [`Muxer::begin`] says, through [`MuxTiming`], whether bytes
15//! stream and whether `finish` patches earlier bytes (sizes, offsets); a
16//! caller whose destination cannot seek back passes
17//! [`MuxHint::forward_only`] and gets a layout without patches, or a refusal
18//! at `begin`.
19//!
20//! Borrowed Pulse arguments are optional. `None` adds no per-call control;
21//! retained job controls and structural limits still apply. Forwarding adapters
22//! pass the original option through to the implementation.
23
24use alloc::boxed::Box;
25use alloc::vec::Vec;
26
27pub use zencodec::decode::{ByteRange, Input, InputRequest};
28/// Deprecated byte-input request name; use [`InputRequest`].
29#[deprecated(note = "use InputRequest")]
30pub type Request = InputRequest;
31pub use zencodec::source::AccessPattern;
32
33use crate::packet::{Packet, RandomAccess};
34use crate::session::{ErasedError, SessionError, UnsupportedMedia};
35use crate::track::TrackInfo;
36use zencodec::time::Timestamp;
37use zencodec_io::{ByteStepper, Step, Stepped};
38
39#[derive(Debug)]
40#[non_exhaustive]
41/// What one demuxer step produced.
42pub enum DemuxEvent {
43    /// Satisfy one of `requested()` and step again.
44    Need,
45    /// [`Demuxer::tracks`] is populated.
46    Headers,
47    /// One packet; [`Packet::track`] says whose.
48    Packet(Packet),
49    /// A track's codec configuration changed; packets that follow carry the
50    /// new epoch. Decoders get `configure` before the next packet.
51    ConfigChanged {
52        /// The track.
53        track: u32,
54        /// The new epoch.
55        epoch: u32,
56    },
57    /// A pending `seek` is positioned; packets resume there.
58    Seeked(SeekResult),
59    /// No more packets.
60    Done,
61}
62
63/// The result of one [`Demuxer::step`].
64#[derive(Debug)]
65pub struct DemuxProgress {
66    /// Bytes taken from the front of the input.
67    pub consumed: usize,
68    /// What the step produced.
69    pub event: DemuxEvent,
70}
71
72impl DemuxProgress {
73    /// A step that took `consumed` bytes and produced `event`.
74    pub const fn new(consumed: usize, event: DemuxEvent) -> Self {
75        Self { consumed, event }
76    }
77
78    /// A step that took `consumed` bytes and needs more.
79    pub const fn need(consumed: usize) -> Self {
80        Self::new(consumed, DemuxEvent::Need)
81    }
82}
83
84#[derive(Clone, Copy, Debug, PartialEq, Eq)]
85#[non_exhaustive]
86/// Which presentation a seek selects relative to the target time.
87pub enum SeekMode {
88    /// The nearest; on a tie, the earlier.
89    Nearest,
90    /// The last at or before the target.
91    AtOrBefore,
92    /// The first at or after the target.
93    AtOrAfter,
94}
95
96#[derive(Clone, Copy, Debug, PartialEq, Eq)]
97#[non_exhaustive]
98/// Which packets a seek may land on.
99pub enum SeekScope {
100    /// Any packet; the caller decodes from an earlier random-access point
101    /// and discards up to it.
102    Any,
103    /// Only packets the container marks as random-access points.
104    RandomAccessPoints,
105}
106
107/// Where to seek.
108#[derive(Clone, Copy, Debug)]
109#[non_exhaustive]
110pub struct SeekTarget {
111    /// The track whose timeline is meant.
112    pub track: u32,
113    /// The target instant.
114    pub time: Timestamp,
115    /// How to pick around it.
116    pub mode: SeekMode,
117    /// Which packets qualify.
118    pub scope: SeekScope,
119}
120
121impl SeekTarget {
122    /// A seek on `track` to `time`.
123    pub const fn new(track: u32, time: Timestamp, mode: SeekMode, scope: SeekScope) -> Self {
124        Self {
125            track,
126            time,
127            mode,
128            scope,
129        }
130    }
131}
132
133/// What a seek actually landed on: the real timestamp and ordinal, never the
134/// request echoed back, plus the container's random-access evidence.
135#[derive(Clone, Copy, Debug, PartialEq, Eq)]
136#[non_exhaustive]
137pub struct SeekResult {
138    /// The presentation timestamp of the packet the demuxer resumes at.
139    pub pts: Timestamp,
140    /// Its ordinal.
141    pub ordinal: u64,
142    /// The container's random-access evidence there.
143    pub random_access: RandomAccess,
144    /// The configuration epoch in force there.
145    pub config_epoch: u32,
146}
147
148impl SeekResult {
149    /// A landing position.
150    pub const fn new(
151        pts: Timestamp,
152        ordinal: u64,
153        random_access: RandomAccess,
154        config_epoch: u32,
155    ) -> Self {
156        Self {
157            pts,
158            ordinal,
159            random_access,
160            config_epoch,
161        }
162    }
163}
164
165#[derive(Clone, Copy, Debug, PartialEq, Eq)]
166#[non_exhaustive]
167/// What `seek` did.
168pub enum SeekStatus {
169    /// Positioned at once.
170    Positioned(SeekResult),
171    /// The container needs bytes (an index scan, a cue read): keep stepping;
172    /// `DemuxEvent::Seeked` reports the position.
173    Scanning,
174}
175
176/// A sans-I/O container reader. See the [module docs](self).
177pub trait Demuxer {
178    /// The implementation's error (malformed or truncated input).
179    type Error: core::error::Error + Send + Sync + 'static;
180
181    /// Take a prefix of `input` and report what happened.
182    fn step(
183        &mut self,
184        input: Input<'_>,
185        pulse: Option<&dyn zencodec::Pulse>,
186    ) -> Result<DemuxProgress, SessionError<Self::Error>>;
187
188    /// What the demuxer needs next, after [`DemuxEvent::Need`]. Never empty
189    /// then.
190    fn requested(&self) -> &[InputRequest];
191
192    /// The tracks; empty before [`DemuxEvent::Headers`].
193    fn tracks(&self) -> &[TrackInfo];
194
195    /// How the demuxer reads its input. Default: front to back.
196    fn access_pattern(&self) -> AccessPattern {
197        AccessPattern::Sequential
198    }
199    /// Reposition. Default: unsupported.
200    fn seek(&mut self, target: &SeekTarget) -> Result<SeekStatus, SessionError<Self::Error>> {
201        let _ = target;
202        Err(SessionError::Unsupported(UnsupportedMedia::Seek))
203    }
204}
205
206/// When a muxer's bytes arrive and whether it must patch earlier bytes at
207/// `finish` (the writer-capability split: seekable fix-ups versus
208/// forward-only emission).
209#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
210pub struct MuxTiming {
211    streams: bool,
212    patches_at_finish: bool,
213}
214
215impl MuxTiming {
216    /// Every byte comes from `finish`.
217    pub const AT_FINISH: Self = Self {
218        streams: false,
219        patches_at_finish: false,
220    };
221
222    /// Bytes stream through `pending_output`; nothing is patched.
223    pub const fn streaming() -> Self {
224        Self {
225            streams: true,
226            patches_at_finish: false,
227        }
228    }
229    /// Bytes stream, and `finish` patches some earlier ones.
230    pub const fn streaming_with_patches() -> Self {
231        Self {
232            streams: true,
233            patches_at_finish: true,
234        }
235    }
236    /// Whether bytes arrive before `finish`.
237    pub const fn streams(&self) -> bool {
238        self.streams
239    }
240
241    /// Whether `finish` overwrites earlier bytes.
242    pub const fn patches_at_finish(&self) -> bool {
243        self.patches_at_finish
244    }
245}
246
247#[derive(Clone, Copy, Debug, Default)]
248#[non_exhaustive]
249/// What the caller tells a muxer about its destination.
250pub struct MuxHint {
251    /// The destination cannot seek back: a muxer that would patch must
252    /// choose a forward-only layout or refuse at `begin`.
253    pub forward_only: bool,
254}
255
256impl MuxHint {
257    /// A destination that cannot seek back (a socket, a pipe).
258    pub const fn forward_only() -> Self {
259        Self { forward_only: true }
260    }
261}
262
263/// Shared completion and patch types; retained paths for media implementations.
264pub use zencodec::output_patch::{OutputFinish as MuxFinish, Patch, PatchError};
265
266/// A container writer whose bytes the caller pulls. See the
267/// [module docs](self).
268pub trait Muxer {
269    /// The implementation's error.
270    type Error: core::error::Error + Send + Sync + 'static;
271
272    /// Declare the tracks and learn how bytes will arrive. A second `begin`
273    /// is a [`ProtocolViolation::BeginTwice`](crate::session::ProtocolViolation::BeginTwice).
274    fn begin(
275        &mut self,
276        tracks: &[TrackInfo],
277        hint: MuxHint,
278    ) -> Result<MuxTiming, SessionError<Self::Error>>;
279
280    /// Prepare a layout that preserves supplied PTS, DTS and explicit sample
281    /// durations exactly, or refuse without making output available.
282    ///
283    /// Unlike a capability query, success binds this muxer instance. Later
284    /// packets that cannot be represented must fail before their payload or
285    /// timing affects pending output. Missing timestamps/durations may be
286    /// refused; values must not be guessed, rounded or rebased to make a packet
287    /// fit. A missing DTS may use its PTS as decode time when the format requires
288    /// one. This does not certify metadata, packet additions or workspace bounds.
289    ///
290    /// Preparation cannot prove facts about future packets. A driver reports
291    /// already committed output on a subsequent failure and must not retry a
292    /// different layout. The default refuses: calling legacy `begin` alone is
293    /// not evidence of this stronger contract.
294    fn begin_exact_timing(
295        &mut self,
296        tracks: &[TrackInfo],
297        hint: MuxHint,
298    ) -> Result<MuxTiming, SessionError<Self::Error>> {
299        let _ = (tracks, hint);
300        Err(SessionError::Unsupported(UnsupportedMedia::ExactTiming))
301    }
302
303    /// Write one packet. Takes the packet so its payload can be reused
304    /// without a copy.
305    fn write_packet(
306        &mut self,
307        packet: Packet,
308        pulse: Option<&dyn zencodec::Pulse>,
309    ) -> Result<(), SessionError<Self::Error>>;
310
311    /// Bytes ready to send, not yet consumed.
312    fn pending_output(&self) -> &[u8];
313
314    /// Mark the first `n` pending bytes as sent (clamped).
315    fn consume_output(&mut self, n: usize);
316
317    /// Append the pending bytes to `into` and mark them sent.
318    fn take_output(&mut self, into: &mut Vec<u8>);
319
320    /// Finish: the bytes not yet consumed, and any patches.
321    fn finish(
322        self,
323        pulse: Option<&dyn zencodec::Pulse>,
324    ) -> Result<MuxFinish, SessionError<Self::Error>>
325    where
326        Self: Sized;
327}
328
329/// Object-safe muxer (`finish` by box), the `DynAnimationFrameEncoder` pattern.
330pub trait DynMuxer {
331    /// See [`Muxer::begin_exact_timing`]. Default refuses without calling `begin`.
332    fn begin_exact_timing(
333        &mut self,
334        tracks: &[TrackInfo],
335        hint: MuxHint,
336    ) -> Result<MuxTiming, SessionError<ErasedError>> {
337        let _ = (tracks, hint);
338        Err(SessionError::Unsupported(UnsupportedMedia::ExactTiming))
339    }
340    /// See [`Muxer::begin`].
341    fn begin(
342        &mut self,
343        tracks: &[TrackInfo],
344        hint: MuxHint,
345    ) -> Result<MuxTiming, SessionError<ErasedError>>;
346    /// See [`Muxer::write_packet`].
347    fn write_packet(
348        &mut self,
349        packet: Packet,
350        pulse: Option<&dyn zencodec::Pulse>,
351    ) -> Result<(), SessionError<ErasedError>>;
352    /// See [`Muxer::pending_output`].
353    fn pending_output(&self) -> &[u8];
354    /// See [`Muxer::consume_output`].
355    fn consume_output(&mut self, n: usize);
356    /// See [`Muxer::take_output`].
357    fn take_output(&mut self, into: &mut Vec<u8>);
358    /// See [`Muxer::finish`].
359    fn finish(
360        self: Box<Self>,
361        pulse: Option<&dyn zencodec::Pulse>,
362    ) -> Result<MuxFinish, SessionError<ErasedError>>;
363}
364
365/// Shim erasing a muxer's error type (the `DynAnimationFrameEncoder` pattern:
366/// a wrapper, not a blanket impl, so method names never collide).
367pub struct MuxerShim<M>(pub M);
368
369impl<M: Muxer> DynMuxer for MuxerShim<M> {
370    fn begin_exact_timing(
371        &mut self,
372        tracks: &[TrackInfo],
373        hint: MuxHint,
374    ) -> Result<MuxTiming, SessionError<ErasedError>> {
375        self.0
376            .begin_exact_timing(tracks, hint)
377            .map_err(|e| e.map_codec(|c| ErasedError::new(Box::new(c))))
378    }
379    fn begin(
380        &mut self,
381        tracks: &[TrackInfo],
382        hint: MuxHint,
383    ) -> Result<MuxTiming, SessionError<ErasedError>> {
384        self.0
385            .begin(tracks, hint)
386            .map_err(|e| e.map_codec(|c| ErasedError::new(Box::new(c))))
387    }
388    fn write_packet(
389        &mut self,
390        packet: Packet,
391        pulse: Option<&dyn zencodec::Pulse>,
392    ) -> Result<(), SessionError<ErasedError>> {
393        self.0
394            .write_packet(packet, pulse)
395            .map_err(|e| e.map_codec(|c| ErasedError::new(Box::new(c))))
396    }
397    fn pending_output(&self) -> &[u8] {
398        self.0.pending_output()
399    }
400    fn consume_output(&mut self, n: usize) {
401        self.0.consume_output(n)
402    }
403    fn take_output(&mut self, into: &mut Vec<u8>) {
404        self.0.take_output(into)
405    }
406    fn finish(
407        self: Box<Self>,
408        pulse: Option<&dyn zencodec::Pulse>,
409    ) -> Result<MuxFinish, SessionError<ErasedError>> {
410        self.0
411            .finish(pulse)
412            .map_err(|e| e.map_codec(|c| ErasedError::new(Box::new(c))))
413    }
414}
415
416/// Erase a muxer's error type.
417pub fn erase_muxer<M: Muxer + Send + 'static>(m: M) -> Box<dyn DynMuxer + Send> {
418    Box::new(MuxerShim(m))
419}
420
421/// Apply a finish to an in-memory file: append the tail, then patch.
422///
423/// # Errors
424///
425/// See [`MuxFinish::apply_to`].
426#[deprecated(
427    note = "use finish.apply_to(file); use finish.validate(emitted_bytes) before external writes"
428)]
429pub fn apply_finish(file: &mut Vec<u8>, finish: MuxFinish) -> Result<(), PatchError> {
430    finish.apply_to(file)
431}
432
433/// A [`Demuxer`] as a [`ByteStepper`], so every `zencodec-io` cursor drives
434/// it: `Need` maps to [`Step::Need`], `Done` to [`Step::Finished`], and
435/// every other event is an output.
436#[derive(Debug)]
437pub struct DemuxStage<'a, D: ?Sized>(pub &'a mut D);
438
439impl<D: Demuxer + ?Sized> ByteStepper for DemuxStage<'_, D> {
440    type Output = DemuxEvent;
441    type Error = SessionError<D::Error>;
442
443    fn step(
444        &mut self,
445        input: Input<'_>,
446        pulse: Option<&dyn zencodec::Pulse>,
447    ) -> Result<Stepped<DemuxEvent>, Self::Error> {
448        let p = self.0.step(input, pulse)?;
449        let step = match p.event {
450            DemuxEvent::Need => Step::Need,
451            DemuxEvent::Done => Step::Finished(DemuxEvent::Done),
452            other => Step::Output(other),
453        };
454        Ok(Stepped {
455            consumed: p.consumed,
456            step,
457        })
458    }
459
460    fn requested(&self) -> &[InputRequest] {
461        self.0.requested()
462    }
463}