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}