#![cfg(target_arch = "wasm32")]
use mediadecode::{
Timebase, Timestamp,
future::local::AudioStreamDecoder,
packet::{AudioPacket, PacketFlags},
};
use mediadecode_webcodecs::{
AudioDecodeError, AudioPacketExtra, WebCodecsAudioStreamDecoder, WebCodecsBuffer,
empty_audio_frame,
};
use wasm_bindgen_test::{wasm_bindgen_test, wasm_bindgen_test_configure};
wasm_bindgen_test_configure!(run_in_browser);
const SAMPLE_RATE: u32 = 16_000;
const CHANNELS: u8 = 1;
const SAMPLES_PER_CHUNK: usize = 1024;
fn silent_pcm_s16_packet(
pts_samples: i64,
time_base: Timebase,
) -> AudioPacket<AudioPacketExtra, WebCodecsBuffer> {
let body = vec![0u8; SAMPLES_PER_CHUNK * 2 * CHANNELS as usize];
AudioPacket::new(
WebCodecsBuffer::from_bytes(body),
AudioPacketExtra::new(true),
)
.with_flags(PacketFlags::KEY)
.with_pts(Some(Timestamp::new(pts_samples, time_base)))
}
#[wasm_bindgen_test]
async fn flush_isolates_pre_and_post_flush_streams() {
let time_base = Timebase::new(
1,
core::num::NonZeroU32::new(SAMPLE_RATE).expect("non-zero rate"),
);
let mut decoder = WebCodecsAudioStreamDecoder::open_with_codec_string(
"pcm-s16",
None,
SAMPLE_RATE,
CHANNELS,
time_base,
)
.expect("open pcm-s16 decoder");
let mut frame = empty_audio_frame();
let pre_flush_ptses: [i64; 4] = [0, 1024, 2048, 3072];
for &pts in &pre_flush_ptses {
decoder
.send_packet(&silent_pcm_s16_packet(pts, time_base))
.await
.expect("pre-flush send_packet");
loop {
match decoder.receive_frame(&mut frame).await {
Ok(()) => {
let observed = frame.pts().expect("pre-flush frame pts is Some").pts();
assert!(
pre_flush_ptses.contains(&observed),
"pre-flush frame pts {observed} not in {pre_flush_ptses:?}"
);
}
Err(AudioDecodeError::NoFrameReady) => break,
Err(e) => panic!("pre-flush receive_frame: {e:?}"),
}
}
}
decoder.flush().await.expect("flush succeeded");
let post_flush_ptses: [i64; 4] = [1_000_000, 1_001_024, 1_002_048, 1_003_072];
let mut post_flush_frames_observed: u32 = 0;
for &pts in &post_flush_ptses {
decoder
.send_packet(&silent_pcm_s16_packet(pts, time_base))
.await
.expect("post-flush send_packet");
loop {
match decoder.receive_frame(&mut frame).await {
Ok(()) => {
let observed = frame.pts().expect("post-flush frame pts is Some").pts();
assert!(
post_flush_ptses.contains(&observed),
"post-flush frame pts {observed} bled in from outside the \
post-flush namespace ({post_flush_ptses:?}); the cross-flush \
record-stealing race appears to have re-emerged",
);
post_flush_frames_observed = post_flush_frames_observed.saturating_add(1);
}
Err(AudioDecodeError::NoFrameReady) => break,
Err(e) => panic!("post-flush receive_frame: {e:?}"),
}
}
}
decoder.send_eof().await.expect("post-flush send_eof");
loop {
match decoder.receive_frame(&mut frame).await {
Ok(()) => {
let observed = frame.pts().expect("eof-drain frame pts").pts();
assert!(
post_flush_ptses.contains(&observed),
"eof-drain frame pts {observed} outside post-flush set {post_flush_ptses:?}"
);
post_flush_frames_observed = post_flush_frames_observed.saturating_add(1);
}
Err(AudioDecodeError::Eof) => break,
Err(AudioDecodeError::NoFrameReady) => continue,
Err(e) => panic!("eof-drain receive_frame: {e:?}"),
}
}
assert_eq!(
post_flush_frames_observed,
post_flush_ptses.len() as u32,
"post-flush frame count drift: expected {} frames carrying the four \
post-flush ptses, observed {post_flush_frames_observed}",
post_flush_ptses.len(),
);
}