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
1 change: 0 additions & 1 deletion quest/m2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,6 @@ lite-07; bitrate enforcement is not part of this release.
- [iOS capture](/quest/m2/mobile-capture-ios.md) - Rust captures the camera and screen on iOS, setting the catalog rotation
- [Android capture](/quest/m2/mobile-capture-android.md) - Rust captures through NDK/JNI on Android, reusing the existing codecs and setting the catalog rotation
- [Time stretch](/quest/m2/watch-audio-time-stretch.md) - js/watch: the audio ring converges by time-stretching instead of skipping or going silent
- [Audio join budget](/quest/m2/audio-join-budget.md) - `moq-audio` joining at the latest group loses no group a publisher bursts before its budget widens
- [Latest readers use Range::live()](/quest/m2/readers-range-live.md) - after #6278, readers start at the live edge without waiting on `largest()`
- [Media QA audio cases](/quest/m2/media-qa-audio.md) - AAC, audio codec/rate/channel changes, rendition switches, output mute/volume, and a native publisher's A/V sync are graded like the video cases
- [Audio outlives video](/quest/m2/audio-bufferbloat-av.md) - under bufferbloat audio stays within budget while video may skip, rendered frames stay in sync, and native audio rows are enforced
Expand Down
24 changes: 0 additions & 24 deletions quest/m2/audio-join-budget.md

This file was deleted.

4 changes: 0 additions & 4 deletions quest/m2/readers-range-live.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,3 @@ Public API: none expected. Wire: none.
## Required

- [JS ranges](/quest/m1/subscribe-ranges/js.md) - `@moq/room` chat needs relative ranges in `@moq/net`

## Related

- [Audio join budget](/quest/m2/audio-join-budget.md) - moq-audio's join at the latest group
68 changes: 68 additions & 0 deletions rs/moq-audio/src/decode/consumer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -945,6 +945,74 @@ mod tests {
assert_eq!(frame.timestamp.as_micros(), 80_000, "the backlog was replayed");
}

/// A latest join with a warmup loses no group the publisher bursts before its first
/// frame decodes. A join that put only the warmup on the wire, widening it once the
/// first packet was read, let the publisher skip every group of such a burst a warmup
/// behind its newest, a gap no later update recovers.
#[tokio::test(start_paused = true)]
async fn latest_loses_no_group_the_publisher_bursts_before_its_first_frame() {
let publisher = origin(1);
let broadcast = publisher.create_broadcast("bcast").unwrap();
let track = broadcast
.create_track("audio", hang::container::track_info(hang::catalog::PRIORITY.audio))
.unwrap();
broadcast.announce(Default::default()).unwrap();
let mut producer = moq_mux::container::Producer::new(
track,
moq_mux::catalog::hang::Container::Legacy(moq_mux::container::Kind::Audio),
);
// One 20 ms packet per group.
let mut write = |packet: u64| {
producer
.write(moq_mux::container::Frame {
timestamp: Timestamp::from_micros(packet * 20_000).unwrap(),
duration: None,
payload: vec![0; 960 * size_of::<f32>()].into(),
keyframe: true,
})
.unwrap();
producer.cut(None).unwrap();
};
write(0);

let subscriber = origin(2);
let _link = link(&publisher, &subscriber).await;
let consumer = subscriber.consume();
let remote = consumer.follow("bcast", None).unwrap().available().await.unwrap();

let mut catalog = hang::catalog::AudioConfig::new(hang::catalog::AudioCodec::Pcm, 48_000, 1);
catalog.warmup = Some(Duration::from_millis(80));
let mut consumer = Consumer::new(
&remote,
&catalog,
"audio",
Options {
start: Start::Latest,
max_delay: Duration::from_secs(1),
..Options::new()
},
)
.await
.unwrap();

// Written in one pass, before the publisher's session runs again.
for packet in 1..=20 {
write(packet);
}
producer.finish().unwrap();

let mut stamps = Vec::new();
while let Some(read) = consumer.read().await.unwrap() {
match read {
Read::Frame(frame) => stamps.push(frame.timestamp.as_micros()),
Read::Gap { timestamp, duration } => panic!("a gap of {duration:?} after {timestamp:?}"),
Read::End { .. } => {}
}
}
let expected: Vec<u128> = (4..=20).map(|packet| packet * 20_000).collect();
assert_eq!(stamps, expected, "the burst reached the decoder whole");
}

#[tokio::test(start_paused = true)]
async fn warmup_trims_the_join_without_trimming_groups_or_declared_returns() {
let (stamps, frames) = warmup_output(true).await;
Expand Down
Loading