use crate::support::{self, RUN_TIME};
use std::collections::VecDeque;
use wasapi::*;
use windows::Win32::Foundation::E_NOINTERFACE;
use windows::Win32::Media::Audio::{AUDCLNT_E_ALREADY_INITIALIZED, AUDCLNT_E_NOT_STOPPED};
fn shared(device: &Device) -> support::Opened {
support::open(
device,
&device.get_direction(),
ShareMode::Shared,
TimingMode::Polling,
)
.expect("a shared mode client at the mix format did not initialise")
}
#[test]
fn initialize_twice_is_refused() {
let Some(fx) = support::fixture() else { return };
let mut opened = shared(&fx.cable.render);
let (def_period, _min) = opened.client.get_device_period().unwrap();
let again = opened
.client
.initialize_client(
&opened.format,
&Direction::Render,
&StreamMode::PollingShared {
autoconvert: false,
buffer_duration_hns: def_period,
},
)
.expect_err("a second initialize_client succeeded");
assert!(
support::is_hresult(&again, AUDCLNT_E_ALREADY_INITIALIZED),
"a second initialize_client gave {again}, expected AUDCLNT_E_ALREADY_INITIALIZED"
);
}
#[test]
fn direction_mismatches_are_refused() {
let Some(fx) = support::fixture() else { return };
let mut client = fx.cable.render.get_iaudioclient().unwrap();
let format = client.get_mixformat().unwrap();
assert!(matches!(
client.initialize_client(
&format,
&Direction::Capture,
&StreamMode::EventsExclusive {
period_hns: 100_000
},
),
Err(WasapiError::LoopbackWithExclusiveMode)
));
let mut client = fx.cable.capture.get_iaudioclient().unwrap();
let format = client.get_mixformat().unwrap();
for mode in [
StreamMode::PollingShared {
autoconvert: false,
buffer_duration_hns: 100_000,
},
StreamMode::EventsExclusive {
period_hns: 100_000,
},
] {
assert!(matches!(
client.initialize_client(&format, &Direction::Render, &mode),
Err(WasapiError::RenderToCaptureDevice)
));
}
}
#[test]
fn write_errors() {
let Some(fx) = support::fixture() else { return };
let opened = shared(&fx.cable.render);
let blockalign = opened.format.get_blockalign() as usize;
let render = opened.client.get_audiorenderclient().unwrap();
for (frames, bytes) in [(4usize, 3 * blockalign), (4, 5 * blockalign)] {
let error = render
.write_to_device(frames, &vec![0u8; bytes], None)
.expect_err("a wrong length write succeeded");
assert!(
matches!(
error,
WasapiError::DataLengthMismatch { received, expected }
if received == bytes && expected == frames * blockalign
),
"a {bytes} byte write of {frames} frames gave {error}"
);
}
let mut short: VecDeque<u8> = VecDeque::from(vec![0u8; 2 * blockalign]);
let error = render
.write_to_device_from_deque(4, &mut short, None)
.expect_err("a short deque write succeeded");
assert!(
matches!(error, WasapiError::DataLengthTooShort { .. }),
"a short deque write gave {error}"
);
assert_eq!(short.len(), 2 * blockalign, "the deque was consumed anyway");
assert!(render.write_to_device(0, &[], None).is_ok());
let buffer_size = opened.client.get_buffer_size().unwrap() as usize;
let too_many = buffer_size + 1;
assert!(
render
.write_to_device(too_many, &vec![0u8; too_many * blockalign], None)
.is_err(),
"a write larger than the buffer succeeded"
);
let space = opened.client.get_available_space_in_frames().unwrap() as usize;
assert_eq!(space, buffer_size);
render
.write_to_device(space, &vec![0u8; space * blockalign], None)
.expect("a correct write after an oversized one failed");
}
#[test]
fn deque_variants_move_whole_frames() {
let Some(fx) = support::fixture() else { return };
let opened = shared(&fx.cable.render);
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;
let mut queue: VecDeque<u8> = VecDeque::from(vec![0u8; (frames + 32) * blockalign]);
render
.write_to_device_from_deque(frames, &mut queue, None)
.expect("a deque write failed");
assert_eq!(
queue.len(),
32 * blockalign,
"a {frames} frame deque write consumed the wrong number of bytes"
);
drop(opened);
let opened = shared(&fx.cable.capture);
let blockalign = opened.format.get_blockalign() as usize;
let capture = opened.client.get_audiocaptureclient().unwrap();
let mut queue: VecDeque<u8> = VecDeque::new();
opened.client.start_stream().unwrap();
for _ in 0..40 {
std::thread::sleep(std::time::Duration::from_millis(5));
capture
.read_from_device_to_deque(&mut queue)
.expect("a deque read failed");
}
opened.client.stop_stream().unwrap();
assert!(!queue.is_empty(), "a deque read collected nothing");
assert_eq!(
queue.len() % blockalign,
0,
"{} bytes is not a whole number of {blockalign} byte frames",
queue.len()
);
}
#[test]
fn reset_requires_a_stopped_stream() {
let Some(fx) = support::fixture() else { return };
let opened = shared(&fx.cable.render);
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();
let running = opened
.client
.reset_stream()
.expect_err("reset_stream succeeded on a running stream");
assert!(
support::is_hresult(&running, AUDCLNT_E_NOT_STOPPED),
"reset_stream on a running stream gave {running}, expected AUDCLNT_E_NOT_STOPPED"
);
opened.client.stop_stream().unwrap();
opened
.client
.reset_stream()
.expect("reset_stream after stop failed");
assert_eq!(
opened.client.get_available_space_in_frames().unwrap(),
opened.client.get_buffer_size().unwrap(),
"the buffer was not empty after a reset"
);
}
#[test]
fn aec_reports_cleanly_on_a_device_without_it() {
let Some(fx) = support::fixture() else { return };
let opened = shared(&fx.cable.capture);
let supported = opened
.client
.is_aec_supported()
.expect("is_aec_supported returned an error instead of false");
println!("the cable reports AEC support: {supported}");
if !supported {
match opened.client.get_aec_control() {
Ok(_) => panic!("get_aec_control succeeded on a device without AEC"),
Err(error) => assert!(
support::is_hresult(&error, E_NOINTERFACE),
"get_aec_control gave {error}, expected E_NOINTERFACE"
),
}
}
}
#[test]
fn loopback_capture_mechanics() {
let Some(fx) = support::fixture() else { return };
let mut client = fx.cable.render.get_iaudioclient().unwrap();
let format = client.get_mixformat().unwrap();
let (def_period, _min) = client.get_device_period().unwrap();
client
.initialize_client(
&format,
&Direction::Capture,
&StreamMode::PollingShared {
autoconvert: false,
buffer_duration_hns: def_period,
},
)
.expect("a loopback client on a render endpoint did not initialise");
assert_eq!(client.get_direction(), Direction::Capture);
client
.get_audiocaptureclient()
.expect("a loopback client has no capture client");
let run = support::capture_for(&client, &format, RUN_TIME).expect("the capture loop failed");
println!(
"loopback delivered {} frames in {} packets at {:.0} Hz",
run.frames,
run.packets.len(),
run.rate()
);
assert!(run.frames > 0, "a loopback capture delivered no frames");
}
#[test]
fn process_loopback_reports_its_limitations() {
let Some(_fx) = support::fixture() else {
return;
};
let mut client = match AudioClient::new_application_loopback_client(std::process::id(), false) {
Ok(client) => client,
Err(e) => {
support::skip(&format!("process loopback capture is not available: {e}"));
return;
}
};
let format = WaveFormat::new(32, 32, &SampleType::Float, 48000, 2, None);
let broken: [(&str, bool); 7] = [
("get_mixformat", client.get_mixformat().is_err()),
(
"is_supported",
client.is_supported(&format, &ShareMode::Shared).is_err(),
),
(
"is_supported_exclusive_with_quirks",
client.is_supported_exclusive_with_quirks(&format).is_err(),
),
("get_device_period", client.get_device_period().is_err()),
(
"calculate_aligned_period_near",
client
.calculate_aligned_period_near(100_000, Some(128), &format)
.is_err(),
),
("get_current_padding", client.get_current_padding().is_err()),
(
"get_available_space_in_frames",
client.get_available_space_in_frames().is_err(),
),
];
for (name, failed) in broken {
assert!(
failed,
"{name} worked, but the documentation says it does not"
);
}
client
.initialize_client(
&format,
&Direction::Capture,
&StreamMode::EventsShared {
autoconvert: true,
buffer_duration_hns: 0,
},
)
.expect("a process loopback client did not initialise");
assert_eq!(client.get_sharemode(), Some(ShareMode::Shared));
assert_eq!(client.get_timing_mode(), Some(TimingMode::Events));
assert!(client.get_audiocaptureclient().is_ok());
}