Skip to content

feat(moq-mux): count and log MPEG-TS audio resyncs - #3372

Merged
kixelated merged 6 commits into
mainfrom
quest/m0/2798-moq-import-ts-an-audio-resync-is-silent-no-log-no-counter
Sep 4, 2026
Merged

feat(moq-mux): count and log MPEG-TS audio resyncs#3372
kixelated merged 6 commits into
mainfrom
quest/m0/2798-moq-import-ts-an-audio-resync-is-silent-no-log-no-counter

Conversation

@kixelated

Copy link
Copy Markdown
Collaborator

Summary

  • Root cause: the recovery path zeroed its own evidence. Since fix(moq-mux): resync TS audio instead of aborting the broadcast #2751 the MPEG-TS importer
    recovers from a damaged MP2/AC-3/E-AC-3/AAC frame header by scanning to the next confirmed
    sync instead of ending the session, but Resync::recovered did nothing except reset
    discarded to 0. Neither call site (AacStream, LegacyStream) logged, and ts::Import
    had no stats surface at all, so a feed dropping audio looked exactly like a healthy one:
    the timeline just stepped 24 ms to 48 ms. Only the budget-exhausted give-up surfaced, as an
    error string.
  • Resync now carries cumulative counters and tracing::warn!s once per completed resync
    with the PID, the track suffix, the bytes discarded, and the running resync count.
  • New ts::Import::stats() returns a per-PID snapshot. moq <side> import ts logs the streams
    whose counters moved, per input chunk plus once more after the end-of-input drain, so a live
    feed reports as it degrades rather than only at exit.
  • The counters cover more than the scan path, per the issue: a frame published that nothing
    vouched for (a joined tail drained at end of stream, where no successor can ever confirm it)
    is a substitution rather than a gap, so it is counted separately as unconfirmed.
  • Deliberately not done: container::Producer::discontinuity() is still never called by
    the TS importer. A marker group per lost 24 ms frame changes downstream behaviour and is a
    separate decision.

Public API changes

Additive only, so this targets main. Nothing renamed, removed, or resignatured.

  • moq_mux::container::ts::Import::stats(&self) -> ts::Stats
  • moq_mux::container::ts::Stats - #[non_exhaustive], pub streams: BTreeMap<u16, StreamStats>, plus is_empty()
  • moq_mux::container::ts::StreamStats - #[non_exhaustive], pub track: &'static str, pub resyncs: u64, pub discarded: u64, pub unconfirmed: u64

How it is shaped and why:

  • A snapshot, not a handle. stats() reads counters the demuxer already keeps and clones
    them. No registry, no Arc, no interior mutability, and no callback: the caller polls at
    whatever cadence it wants and owns the reporting policy, which is what the "avoid callback
    parameters" rule asks for.
  • Two types rather than a bare BTreeMap<u16, ..> return. The map alone is the smaller
    surface today, but the TS layer also loses packet sync (Import::decode reacquires the
    188-byte stride), and that is an importer-wide counter with no PID to hang it on. Wrapping
    the map in a #[non_exhaustive] Stats leaves room to add it without a breaking change;
    returning the map directly would not.
  • #[non_exhaustive] on both is case 3 in rs/CLAUDE.md: these grow additive, defaultable
    counters. Both derive Default so an external caller still has a construction path, and
    PartialEq is what makes "log it when it changes" a one-liner.
  • Absent means healthy. A PID that has lost nothing is not in the map, so is_empty() is
    the whole-feed health check and change detection stays cheap.
  • Named by role: Stats is the snapshot, StreamStats is one elementary stream's share.
    Every field is a plain cumulative count, because the signal worth alarming on is the
    derivative, not the value.

Opus: Stats reports nothing for Opus PIDs, because on main today Opus does not go
through Resync at all (its framing is length-declared, not self-describing). The sibling
quest #2849 is routing Opus through Resync; when it lands, the one-line change is to move
Stream::Opus from the None arm of Stream::stats into the stream.resync.stats() arm
beside AAC and Legacy. Nothing else in this surface needs to change.

Test plan

New assertions on the existing fixtures in rs/moq-mux/src/container/ts/import.rs:

  • legacy_resyncs_past_damaged_header (the one-byte-damage fixture from fix(moq-mux): resync TS audio instead of aborting the broadcast #2751): exactly one
    resync on the MP2 PID, charged the damaged frame's 72 bytes.
  • aac_resyncs_past_damaged_header: the same for ADTS, charged 47 bytes.
  • legacy_drains_a_joined_frame_at_end_of_stream: unconfirmed: 1, no resync.
  • legacy_survives_a_looping_file_wrap (the real ac3.ts fixture, looped): the snapshot is
    empty. Worth calling out, because the issue expected this fixture to show an unconfirmed
    frame and it does not: the continuity counter catches that wrap a packet before the codec
    would ever see spliced bytes, so the tail is dropped rather than scanned past. The
    unconfirmed counter is exercised by the drain fixture above, which is the only path that
    publishes a frame nothing vouched for. The assertion is kept as a regression guard on that
    reasoning.

What was actually run, and what was not. This machine is running ~14 concurrent agent
builds and the shared target/ was garbage-collected mid-session, so a full
just check / just test did not finish in the time available. Scoped runs instead:

  • cargo test -p moq-mux --lib -- container::ts::import - 63 passed, 0 failed.
  • cargo check -p moq-cli - clean.
  • rustfmt --config-path rs/.rustfmt.toml over both changed files - clean.
  • cargo clippy -p moq-mux --all-targets -- -D warnings was started but had not finished.

CI is therefore the first full just check / just test run on this branch. Nothing here is
platform-gated or feature-gated, so there is no manual just rs macos / windows gap.

Cross-package sync

  • rs/moq-cli -> doc/bin/cli.md: skipped. No flag, argument, subcommand, or positional
    changed; the CLI only gained a log line, and that row covers the invocation surface.
  • No wire, catalog, container, or moq-ffi surface changed, so no drafts/, js/, or
    language-binding rows apply.

Quest

Completes and deletes quest/m0/2798-moq-import-ts-an-audio-resync-is-silent-no-log-no-counter.md
and its quest/m0/README.md entry.

Closes #2798

🤖 Generated with Claude Code

(Written by Claude Opus 5)

kixelated and others added 2 commits September 3, 2026 11:10
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The recovery path zeroed its own evidence: `Resync::recovered` reset the
discard count and neither call site logged, so a feed losing audio to a
damaged frame header looked identical to a healthy one.

`Resync` now keeps cumulative counters and warns once per completed resync
with the PID, track suffix, and bytes discarded. `ts::Import::stats()`
exposes them per elementary stream PID, and `moq import ts` logs the streams
whose counters moved.

Closes #2798

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

Next included review available in 4 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 4 included reviews currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Team

Run ID: 204a6fd9-48d9-4f49-be25-108ccff1a9dc

📥 Commits

Reviewing files that changed from the base of the PR and between f509968 and 30e97e3.

📒 Files selected for processing (2)
  • quest/m0/README.md
  • rs/moq-mux/src/container/ts/import.rs

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Team

Run ID: 45e252b9-1c82-47f7-9495-2544db9b1acc

📥 Commits

Reviewing files that changed from the base of the PR and between 921788e and f509968.

📒 Files selected for processing (4)
  • quest/m0/2798-moq-import-ts-an-audio-resync-is-silent-no-log-no-counter.md
  • quest/m0/README.md
  • rs/moq-cli/src/publish.rs
  • rs/moq-mux/src/container/ts/import.rs
💤 Files with no reviewable changes (2)
  • quest/m0/2798-moq-import-ts-an-audio-resync-is-silent-no-log-no-counter.md
  • quest/m0/README.md

Included review availability: Your plan provides up to 4 included reviews per hour; 1 remains after this review.


Walkthrough

The MPEG-TS importer now exposes per-PID audio resync statistics for AAC and legacy audio streams. It records resyncs, discarded bytes, and unconfirmed publications, including in-progress scans and EOF-drained frames. The CLI compares statistics during decoding and logs changed counters and final drain results. Tests cover damaged headers, loop wraps, active scans, and unconfirmed EOF publications. The completed quest entry was removed.

Merge Risk: ⚪ Minimal · up to f5099

MPEG-TS publishing now reports audio resync, discarded-byte, and unconfirmed-frame counters without changing recovery or downstream discontinuity behavior. The statistics flow and covered recovery cases indicate no current merge-blocking risk.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 76.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 25 functions across 2 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the primary change: counting and logging MPEG-TS audio resyncs.
Description check ✅ Passed The description directly explains the resync observability changes, API additions, CLI reporting, tests, and scope decisions.
Linked Issues check ✅ Passed The implementation satisfies issue #2798 by logging completed resyncs with PID and discarded-byte data, exposing cumulative per-PID counters, preserving recovery behavior, and adding diagnostic visibi…
Out of Scope Changes check ✅ Passed The changes remain within issue #2798. The quest tracking updates, importer statistics, CLI logging, and related tests support the stated observability objectives. No unrelated wire, catalog, binding,…
✨ Finishing Touches 💡 1
⚔️ Resolve merge conflicts 💡
  • Resolve merge conflict in branch quest/m0/2798-moq-import-ts-an-audio-resync-is-silent-no-log-no-counter
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch quest/m0/2798-moq-import-ts-an-audio-resync-is-silent-no-log-no-counter

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 3, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-04T18:57:47.752899Z 30e97e3 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4f344b3dc1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread rs/moq-mux/src/container/ts/import.rs Outdated
Comment thread rs/moq-mux/src/container/ts/import.rs

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f5099689a8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread rs/moq-mux/src/container/ts/import.rs Outdated
@kixelated
kixelated enabled auto-merge (squash) September 4, 2026 18:54
@kixelated
kixelated merged commit d554c75 into main Sep 4, 2026
2 checks passed
@kixelated
kixelated deleted the quest/m0/2798-moq-import-ts-an-audio-resync-is-silent-no-log-no-counter branch September 4, 2026 19:00
@moq-bot moq-bot Bot mentioned this pull request Sep 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

moq import ts: an audio resync is silent — no log, no counter, no downstream signal

1 participant