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
8 changes: 8 additions & 0 deletions .github/workflows/interop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,14 @@ jobs:
env:
MOQ_TEST_RUNS: ${{ runner.temp }}/moq-test-runs

# The same video beside four audio PIDs in a multiplex faster than its Rx: each slot
# has to interleave the video with the rest, or its transport buffer overflows.
- name: TS multiplex above the video's Rx
run: nix develop --command just test ts --headroom
shell: bash -leo pipefail {0}
env:
MOQ_TEST_RUNS: ${{ runner.temp }}/moq-test-runs

# The same round-trip over an open-GOP clip: every leading picture after a
# recovery point must come back, in decode order.
- name: TS open GOP
Expand Down
1 change: 1 addition & 0 deletions quest/m1/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ bench all require its crate.
- [Encoder colour](/quest/m1/color-model.md) - every moq-video encode path signals the colour its output actually has, or refuses instead of mislabelling
- [T-STD TS export](/quest/m1/tstd/README.md) - `moq export ts` is a proper remux that passes the T-STD buffer model, starting with a fixed `--delay`
- [Multi-packet PMT](/quest/m1/ts-pmt-split.md) - `export ts` splits a PMT longer than one packet instead of exiting before its first packet
- [ATSC AC-3](/quest/m1/ts-atsc-ac3.md) - `export ts` carries 48 kHz ATSC AC-3 at every A/52 rate up to 640 kb/s instead of aborting from 384 kb/s up
- [TS damage log](/quest/m1/ts-damage-log.md) - a burst of damaged TS units logs a first warning and a periodic summary per PID, not one warning per unit
- [TS adaptation field length](/quest/m1/ts-adaptation-only.md) - a TS adaptation field with the wrong length for its packet (183 adaptation-only, at most 182 with a payload) is refused as damage
- [Rust non-continuous signal](/quest/m1/rust-continuous.md) - the Rust container consumer reports a frame after a subscribe or discontinuity as non-continuous, like JS, for the tune-in and warmup trims
Expand Down
52 changes: 52 additions & 0 deletions quest/m1/ts-atsc-ac3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# [M] TS export carries 48 kHz ATSC AC-3 at every A/52 rate

## Goal

`moq export ts` carries 48 kHz AC-3 as ATSC (`stream_type` 0x81, with the
2,592-byte main buffer of ATSC A/53 Part 5) at every A/52 rate up to 640 kb/s
without missing a decode deadline, and the output passes `compliance.py`'s strict
`tstd` check. Today every rate from 384 kb/s up aborts the export, and 384 and
448 kb/s 5.1 is ATSC's standard main audio. The carriage stays ATSC: DVB AC-3
(5,696 B) already fits every rate, and E-AC-3 has
[its own quest](/quest/m2/ts-eac3.md). 48 kHz is the only rate ATSC carries.
A 44.1 or 32 kHz frame that fits the buffer goes out like any other, and one
that outgrows it on its own (2,786 and 3,840 B at 640 kb/s) fails the export
with an error naming both sizes, rather than the misleading "raise the delay or
the multiplex rate" or a switch to DVB carriage.

## Plan

Decided (2026-10-10), planned from #5207, whose multi-PID `--headroom` arm had
to drop its AC-3 to 192 kb/s. `Schedule::admit`
(`rs/moq-mux/src/container/ts/schedule.rs`) keeps a unit's bytes in its
decoder buffer until the 25 ms slot after its due slot is past, not until it
decodes. AC-3 frames are 32 ms apart, so two frames' due slots can be one
apart, and the second must then fit beside the whole first: at 384 kb/s the
two hold 3,312 B against 2,592. The T-STD (ISO 13818-1 2.4.2.3) frees B at the
decode instant, and a 448 kb/s frame takes about 7 ms at Rx = 2 Mb/s, leaving
some 25 ms of its 32 ms spacing. The B overflow (122 %) graded on that run's
partial output is the same coarse timing, not a separate bug.

- Free each unit's bytes at its decode instant, for every PID with a decoder
buffer (AC-3, AAC, MP2, E-AC-3, and video's EB), so the schedule follows the
standard everywhere rather than special-casing AC-3. In the slot where a
buffer frees up, the packets admitted against the freed room go after that
instant: `Slot::layout` (from #5207) gains an earliest position per PID
beside its round-robin. Chosen over admitting by sub-slots (the same idea,
coarser) and over shrinking the 25 ms PCR grid, which would cost each PID
about a tenth of its Rx to the per-slot slack, 2.5 times the clock packets,
and every PCR-timing expectation.
- A unit bigger than its PID's decoder buffer can never be admitted, so the
export fails it when it is pushed, naming the unit's size and the buffer's,
rather than waiting for its deadline to pass. Once every source has ended,
the tail still goes out late, as today. Chosen over switching such a track
to DVB carriage, which would silently change what an ATSC receiver is handed.
- [Send-ahead](/quest/m1/tstd/send-ahead.md) bounds a unit's reach by what its
decoder buffer holds and changes the same code; whichever lands second
rebases onto the other.
- Test: schedule unit tests at 384, 448, 512, and 640 kb/s frame sizes against
the 2,592 B buffer, each frame finishing by its deadline, with the existing
decoder-buffer tests moved to the exact decode instant, and a 32 kHz
448 kb/s frame (2,688 B) failing with both sizes named. Raise the
`--headroom` arm's AC-3 (`test/ts/run.sh`) from 192 to 640 kb/s, the worst
case, so CI grades it under strict `tstd` on every run.
1 change: 0 additions & 1 deletion quest/m1/tstd/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ nightly.

- [Send-ahead within the delay](/quest/m1/tstd/send-ahead.md) - total lag is `--delay`, send-ahead included, with a 1 s default
- [Mux-rate hold](/quest/m1/tstd/mux-rate-hold.md) - import publishes its catalog once the mux rate is measured, so export is constant-rate from the start
- [Slot layout](/quest/m1/tstd/slot-layout.md) - each slot interleaves its PIDs evenly, so video stays within its transport buffer when the multiplex runs above its Rx

## Related

Expand Down
36 changes: 0 additions & 36 deletions quest/m1/tstd/slot-layout.md

This file was deleted.

7 changes: 4 additions & 3 deletions rs/moq-mux/src/container/ts/export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1754,6 +1754,7 @@ impl<E: catalog::Catalog> Export<E> {

self.schedule.set_buffer(0, Buffer::SYSTEM);
self.schedule.set_buffer(pmt_pid, Buffer::SYSTEM);
self.schedule.set_clock(pcr_pid);
for track in self.tracks.values() {
self.liveness.register(track.pid);
}
Expand Down Expand Up @@ -2024,12 +2025,12 @@ impl<E: catalog::Catalog> Export<E> {
};
let (pcr_pid, pmt_pid) = (psi.pcr_pid, psi.pmt_pid);
while let Some(slot) = self.schedule.next(known)? {
let mut payload = pcr_packet(pcr_pid, slot.pcr)?;
let mut clock = pcr_packet(pcr_pid, slot.pcr)?;
let discontinuity = std::mem::take(&mut self.pcr_discontinuity);
if discontinuity {
payload[5] |= 0x80;
clock[5] |= 0x80;
}
payload.extend_from_slice(&slot.layout(pmt_pid, &NULL_PACKET));
let mut payload = slot.layout(&clock, pmt_pid, &NULL_PACKET);
self.number(&mut payload);
let frame = Frame {
timestamp: slot_stamp(slot.index)?,
Expand Down
Loading
Loading