Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions doc/lib/rs/moq-mux.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,26 @@ real idle gap. fMP4 passthrough rewrites each fragment's `tfdt` to match. Use
it for a live feed with its own zero; publish verbatim only when the catalog's
clock (`Config::with_clock`) already names the source's zero.

An application running its own demuxer gets the same mapping from
`clock::Anchor`: one per source, plus one `clock::Lane` per track. A single
`SourceMap` per track would let tracks drift apart by their first-PTS
difference, and one `SourceMap` shared across tracks reads their interleaving
as a reset. Each lane detects its own restarts, and the anchor moves once for
all of them. Call `Lane::restart()` before a frame when the demuxer sees a
discontinuity out of band.

```rust
use moq_mux::clock;

let mut anchor = clock::Anchor::new(catalog.clock());
let mut video = clock::Lane::default();
let mut klv = clock::Lane::default();

// Both tracks keep their source spacing on the broadcast clock.
let video_ts = anchor.translate(&mut video, video_pts)?;
let klv_ts = anchor.translate(&mut klv, klv_pts)?;
```

```bash
cargo add moq-mux
```
Expand Down
1 change: 0 additions & 1 deletion quest/m1/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,6 @@ QUIC studies there on that rule.
- [Data consumer timestamps](/quest/m1/data-consumer-timestamps.md) - json and binary consumers return each value's timestamp, in Rust and every binding
- [Nested data configs](/quest/m1/data-config-nesting.md) - docs nest `BinaryConfig`/`JsonConfig` in an application section instead of flattening it
- [Omit empty catalog sections](/quest/m1/catalog-omit-empty.md) - a Rust catalog with no video or audio leaves those keys out, as JS does
- [Public clock anchor](/quest/m1/clock-anchor-public.md) - an application demuxer shares one source offset across its tracks through `Anchor` and `Lane`
- [Delete SourceMap](/quest/m1/source-map-removal.md) - on dev, `Anchor` with its `Lane`s is the one way onto the broadcast clock
- [Draft-20 FETCH](/quest/m1/ietf-fetch-location.md) - a draft-20+ FETCH within one group is served from its LOCATION_FILTER, as older drafts are
- [Fetch without SUBSCRIBE](/quest/m1/ietf-fetch-only.md) - a relay fetches from an IETF upstream without subscribing, finished tracks included, with End of Track always reported
Expand Down
29 changes: 0 additions & 29 deletions quest/m1/clock-anchor-public.md

This file was deleted.

4 changes: 0 additions & 4 deletions quest/m1/source-map-removal.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,3 @@ with its `Lane`s is the one way to map a source onto the broadcast clock.
it. A single-track source uses `Anchor` with one `Lane`. Migrate the
`SourceMap` tests in `rs/moq-mux/src/clock.rs` onto `Anchor`, and update
`doc/lib/rs/moq-mux.md`.

## Required

- [Public clock anchor](/quest/m1/clock-anchor-public.md) - `Anchor` and `Lane` must be public first
39 changes: 31 additions & 8 deletions rs/moq-mux/src/clock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
//! A source with its own zero (a file, a restarted encoder) is translated onto the mapping with
//! [`SourceMap`]: the first frame anchors onto the live edge, and a reset re-anchors forward,
//! preserving the real idle gap measured on this monotonic clock.
//!
//! A source that muxes several tracks (an MPEG-TS program, an fMP4 file) shares one [`Anchor`]
//! across them, with one [`Lane`] per track, so every track keeps the same offset.

use std::time::{Duration, Instant, SystemTime};

Expand Down Expand Up @@ -240,7 +243,21 @@ impl SourceMap {
/// video step back further than [`SourceMap::MAX_REORDER`], which would read as a reset. So
/// each track keeps its own [`Lane`] that detects its own backwards steps, and a restart any
/// lane detects moves the anchor once; the other lanes adopt that mapping when they restart too.
pub(crate) struct Anchor {
///
/// ```
/// use moq_mux::{Clock, clock};
///
/// let mut anchor = clock::Anchor::new(Clock::new());
/// let (mut video, mut klv) = (clock::Lane::default(), clock::Lane::default());
///
/// // Both tracks of the program keep the 800ms between their source timestamps.
/// let v = anchor.translate(&mut video, moq_net::Timestamp::from_micros(10_800_000)?)?;
/// let k = anchor.translate(&mut klv, moq_net::Timestamp::from_micros(10_000_000)?)?;
/// assert_eq!(v.as_micros() - k.as_micros(), 800_000);
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
#[derive(Debug)]
pub struct Anchor {
clock: Clock,
/// Broadcast micros minus source micros for the current generation; `None` until the first
/// frame anchors it.
Expand All @@ -256,8 +273,8 @@ pub(crate) struct Anchor {
}

/// One track's position on its source's [`Anchor`].
#[derive(Default)]
pub(crate) struct Lane {
#[derive(Debug, Default)]
pub struct Lane {
/// The offset this lane translates with, in micros; `None` until it adopts one.
offset: Option<i128>,
generation: u64,
Expand All @@ -270,13 +287,14 @@ pub(crate) struct Lane {

impl Lane {
/// The next frame starts a new source timeline, however its PTS compares to the last.
pub(crate) fn restart(&mut self) {
pub fn restart(&mut self) {
self.restart = true;
}
}

impl Anchor {
pub(crate) fn new(clock: Clock) -> Self {
/// A source mapping onto `clock`, unanchored until the first frame on any lane.
pub fn new(clock: Clock) -> Self {
Self {
clock,
offset: None,
Expand All @@ -288,12 +306,15 @@ impl Anchor {
}

/// Translate one of `lane`'s timestamps, sampling the arrival time.
pub(crate) fn translate(&mut self, lane: &mut Lane, pts: moq_net::Timestamp) -> crate::Result<moq_net::Timestamp> {
pub fn translate(&mut self, lane: &mut Lane, pts: moq_net::Timestamp) -> crate::Result<moq_net::Timestamp> {
self.translate_at(lane, pts, self.clock.now().value())
}

/// Translate one of `lane`'s timestamps, arriving at monotonic `now` micros.
pub(crate) fn translate_at(
///
/// The deterministic core behind [`translate`](Self::translate): `now` is what
/// [`Clock::now`] would read, pinned for synthetic sources and tests.
pub fn translate_at(
&mut self,
lane: &mut Lane,
pts: moq_net::Timestamp,
Expand Down Expand Up @@ -366,7 +387,9 @@ impl Anchor {
}

/// Record that the broadcast has published up to `end`, e.g. a fragment's last sample end.
pub(crate) fn extend(&mut self, end: moq_net::Timestamp) {
///
/// A restart then lands after `end` even when no frame's start reached it.
pub fn extend(&mut self, end: moq_net::Timestamp) {
self.extend_micros(end.as_micros());
}

Expand Down
4 changes: 3 additions & 1 deletion rs/moq-mux/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
//! raw bitstream to a broadcast.
//! - [`catalog`] publishes and subscribes to the broadcast catalog,
//! the JSON manifest listing every track and how to decode it.
//! - [`clock`](mod@clock) holds the broadcast [`Clock`] and the translators
//! that move a source's own timestamps onto it.
//! - [`import`](mod@import) is the front door for callers who only have
//! a format string. It picks the right concrete importer for you.
//! - [`select`] picks which renditions of a broadcast to keep, on either
Expand All @@ -28,7 +30,7 @@

pub mod binary;
pub mod catalog;
mod clock;
pub mod clock;
pub mod codec;
pub mod container;
mod error;
Expand Down
Loading