use crate::support::{self, RUN_TIME};
use std::time::{Duration, Instant};
use wasapi::*;
const MODES: [(ShareMode, TimingMode); 4] = [
(ShareMode::Shared, TimingMode::Events),
(ShareMode::Shared, TimingMode::Polling),
(ShareMode::Exclusive, TimingMode::Events),
(ShareMode::Exclusive, TimingMode::Polling),
];
#[test]
fn render_mechanics() {
let Some(fx) = support::fixture() else { return };
for (share, timing) in MODES {
let Some(opened) =
support::open_or_skip(&fx.cable.render, &Direction::Render, share, timing)
else {
continue;
};
let run = support::render_for(&opened.client, &opened.format, RUN_TIME)
.expect("the render loop failed");
support::assert_rate(&run, &opened.format, &format!("render {share} {timing:?}"));
assert_eq!(opened.client.get_sharemode(), Some(share));
assert_eq!(opened.client.get_timing_mode(), Some(timing));
}
}
#[test]
fn available_space_tracks_padding() {
let Some(fx) = support::fixture() else { return };
for (share, timing) in MODES {
let Some(opened) =
support::open_or_skip(&fx.cable.render, &Direction::Render, share, timing)
else {
continue;
};
let what = format!("render {share} {timing:?}");
let buffer_size = opened.client.get_buffer_size().unwrap();
let exclusive_events = share == ShareMode::Exclusive && timing == TimingMode::Events;
let padding = || opened.client.get_current_padding().unwrap();
let free = || opened.client.get_available_space_in_frames().unwrap();
assert_eq!(
padding(),
if exclusive_events { buffer_size } else { 0 },
"{what}: wrong padding for an empty buffer"
);
assert_eq!(
free(),
buffer_size,
"{what}: an empty buffer did not report itself as entirely free"
);
let blockalign = opened.format.get_blockalign() as usize;
let render = opened.client.get_audiorenderclient().unwrap();
let frames = free() as usize;
render
.write_to_device(frames, &vec![0u8; frames * blockalign], None)
.unwrap();
assert_eq!(
padding(),
buffer_size,
"{what}: a full buffer did not report itself as full"
);
assert_eq!(
free(),
if exclusive_events { buffer_size } else { 0 },
"{what}: wrong free space for a full buffer"
);
}
}
#[test]
fn capture_mechanics() {
let Some(fx) = support::fixture() else { return };
for (share, timing) in MODES {
let Some(opened) =
support::open_or_skip(&fx.cable.capture, &Direction::Capture, share, timing)
else {
continue;
};
let what = format!("capture {share} {timing:?}");
let run = support::capture_for(&opened.client, &opened.format, RUN_TIME)
.expect("the capture loop failed");
support::assert_rate(&run, &opened.format, &what);
assert_eq!(opened.client.get_sharemode(), Some(share));
assert_eq!(opened.client.get_timing_mode(), Some(timing));
assert!(
run.packets.len() > 1,
"{what}: only {} packets",
run.packets.len()
);
let (mut exact, mut gaps, mut flagged) = (0usize, 0usize, 0usize);
for pair in run.packets.windows(2) {
let (frames, before) = &pair[0];
let (_, after) = &pair[1];
let step = after.index.checked_sub(before.index).unwrap_or_else(|| {
panic!(
"{what}: position went backwards, {} then {}",
before.index, after.index
)
});
assert!(
step >= u64::from(*frames),
"{what}: a packet of {frames} frames only moved the position by {step}, \
so two packets overlap"
);
if step == u64::from(*frames) {
exact += 1;
} else {
gaps += 1;
}
if after.flags.data_discontinuity {
flagged += 1;
}
if !after.flags.timestamp_error && !before.flags.timestamp_error {
assert!(
after.timestamp >= before.timestamp,
"{what}: timestamp went backwards, {} then {}",
before.timestamp,
after.timestamp
);
}
}
let steps = exact + gaps;
println!(
"{what}: {} packets, {exact} of {steps} steps exact, {gaps} gaps, \
{flagged} flagged as a discontinuity",
run.packets.len()
);
assert!(
exact * 2 > steps,
"{what}: only {exact} of {steps} steps accounted for their frames exactly"
);
}
}
#[test]
fn an_uninitialised_client_reports_nothing() {
let Some(fx) = support::fixture() else { return };
let client = fx.cable.render.get_iaudioclient().unwrap();
assert_eq!(client.get_sharemode(), None);
assert_eq!(client.get_timing_mode(), None);
assert!(matches!(
client.get_available_space_in_frames(),
Err(WasapiError::ClientNotInit)
));
}
#[test]
fn packet_size_matches_the_read() {
let Some(fx) = support::fixture() else { return };
for share in [ShareMode::Shared, ShareMode::Exclusive] {
let Some(opened) = support::open_or_skip(
&fx.cable.capture,
&Direction::Capture,
share,
TimingMode::Polling,
) else {
continue;
};
let what = format!("capture {share} polling");
let capture = opened.client.get_audiocaptureclient().unwrap();
let blockalign = opened.format.get_blockalign() as usize;
let mut buffer = vec![0u8; opened.client.get_buffer_size().unwrap() as usize * blockalign];
opened.client.start_stream().unwrap();
let mut checked = 0;
for _ in 0..200 {
std::thread::sleep(Duration::from_millis(5));
match capture.get_next_packet_size().unwrap() {
None => {
assert_eq!(
share,
ShareMode::Exclusive,
"{what}: no packet size was announced in shared mode"
);
break;
}
Some(0) => continue,
Some(announced) => {
let (read, _info) = capture.read_from_device(&mut buffer).unwrap();
assert_eq!(
read, announced,
"{what}: {announced} frames were announced but {read} were read"
);
checked += 1;
if checked == 5 {
break;
}
}
}
}
opened.client.stop_stream().unwrap();
if share == ShareMode::Shared {
assert!(checked > 0, "{what}: no packet was ever announced");
}
println!("{what}: {checked} announced packet sizes matched the read");
}
}
#[test]
fn device_period_is_sane_and_accepted() {
let Some(fx) = support::fixture() else { return };
let client = fx.cable.render.get_iaudioclient().unwrap();
let (def_period, min_period) = client.get_device_period().unwrap();
assert!(min_period > 0, "the minimum period is {min_period}");
assert!(
def_period >= min_period,
"the default period {def_period} is below the minimum {min_period}"
);
let format = WaveFormat::new(16, 16, &SampleType::Int, 48000, 2, None);
let aligned = client
.calculate_aligned_period_near(min_period, Some(128), &format)
.unwrap();
assert!(
aligned >= min_period,
"the aligned period {aligned} is below the minimum {min_period}"
);
println!("periods: min {min_period}, default {def_period}, aligned {aligned}");
drop(client);
match support::open(
&fx.cable.render,
&Direction::Render,
ShareMode::Exclusive,
TimingMode::Events,
) {
Ok(opened) => assert!(opened.client.get_buffer_size().unwrap() > 0),
Err(e) => support::skip(&format!("no exclusive mode stream: {e}")),
}
}
#[test]
fn session_state_follows_the_stream() {
let Some(fx) = support::fixture() else { return };
let Some(opened) = support::open_or_skip(
&fx.cable.render,
&Direction::Render,
ShareMode::Shared,
TimingMode::Polling,
) else {
return;
};
let session = opened.client.get_audiosessioncontrol().unwrap();
let settles_on = |wanted: SessionState| {
let deadline = Instant::now() + Duration::from_secs(2);
while Instant::now() < deadline {
if session.get_state().unwrap() == wanted {
return true;
}
std::thread::sleep(Duration::from_millis(20));
}
false
};
assert_eq!(session.get_state().unwrap(), SessionState::Inactive);
let blockalign = opened.format.get_blockalign() as usize;
let render = opened.client.get_audiorenderclient().unwrap();
let frames = opened.client.get_available_space_in_frames().unwrap() as usize;
render
.write_to_device(frames, &vec![0u8; frames * blockalign], None)
.unwrap();
opened.client.start_stream().unwrap();
assert!(
settles_on(SessionState::Active),
"the session never went active"
);
opened.client.stop_stream().unwrap();
assert!(
settles_on(SessionState::Inactive),
"the session never went inactive again"
);
}