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: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **WASAPI**: Capture streams no longer report a spurious xrun on the first buffer after starting.
- **WASAPI**: Output streams no longer reject formats that the built-in resampler can convert.
- **WASAPI**: Device enumeration no longer panics if the COM enumerator fails to initialize.
- **WASAPI**: Fix 24-bit samples not getting shifted from/to MSB.
- **WebAudio**: Fix stale audio output when a data callback wrote a partial buffer.
- **WebAudio**: Fix unsound `Send + Sync` on `Stream` when compiled with `+atomics`.
- **WebAudio**: Fix `Host::is_available()` always returning `true`, even in non-window contexts.
Expand Down
53 changes: 48 additions & 5 deletions src/host/wasapi/stream.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::{
mem,
ops::ControlFlow,
ptr,
ptr, slice,
sync::{
Arc,
atomic::{AtomicBool, AtomicU64, Ordering},
Expand Down Expand Up @@ -665,6 +665,15 @@ fn run_input(
emit_error(error_callback, err);
}

let stream = &run_ctxt.stream;
let scratch_len = if stream.sample_format == SampleFormat::I24 {
stream.max_frames_in_buffer as usize * stream.bytes_per_frame as usize / size_of::<i32>()
} else {
// The scratch buffer won't be used in this case.
0 // Vec::with_capacity(0) does not allocate.
};
let mut scratch_buffer = vec![0; scratch_len].into_boxed_slice();

loop {
match process_commands_and_await_signal(&mut run_ctxt, error_callback) {
ControlFlow::Break(()) => break,
Expand All @@ -675,7 +684,12 @@ fn run_input(
AudioClientFlow::Capture { ref capture_client } => capture_client.clone(),
_ => unreachable!(),
};
if let Err(err) = process_input(&run_ctxt.stream, capture_client, data_callback) {
if let Err(err) = process_input(
&run_ctxt.stream,
capture_client,
data_callback,
&mut scratch_buffer,
) {
emit_error(error_callback, err);
break;
}
Expand Down Expand Up @@ -815,6 +829,7 @@ fn process_input(
stream: &StreamInner,
capture_client: Audio::IAudioCaptureClient,
data_callback: &mut dyn FnMut(&Data, &CallbackInfo),
scratch_buffer: &mut [i32],
) -> Result<(), Error> {
unsafe {
// Get the available data in the shared buffer.
Expand Down Expand Up @@ -850,10 +865,26 @@ fn process_input(
&& flags & Audio::AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY.0 as u32 != 0;

debug_assert!(!buffer.is_null());
let byte_count = frames_available as usize * stream.bytes_per_frame as usize;
let data = if stream.sample_format == SampleFormat::I24 {
// WASAPI stores i24 in the upper bits
let source_data =
slice::from_raw_parts(buffer.cast(), byte_count / size_of::<i32>());
// use a scratch buffer since the capture buffer isn't meant to be written
let dst = &mut scratch_buffer[..source_data.len()];
dst.copy_from_slice(source_data);
for sample in dst.iter_mut() {
// On signed integers, >> is an arithmetic shift,
// which ensures the correct upper bits get shifted in
*sample >>= 8;
}

let data = buffer as *mut ();
let len = frames_available as usize * stream.bytes_per_frame as usize
/ stream.sample_format.sample_size();
dst.as_mut_ptr().cast()
} else {
buffer.cast()
};

let len = byte_count / stream.sample_format.sample_size();
let data = Data::from_parts(data, len, stream.sample_format);

if !stream.draining.load(Ordering::Relaxed) {
Expand Down Expand Up @@ -921,6 +952,18 @@ fn process_output(
xrun: false,
},
);

if stream.sample_format == SampleFormat::I24 {
// WASAPI stores i24 in the upper bits
#[expect(
clippy::cast_ptr_alignment,
reason = "WASAPI guarantees the buffer to be aligned to a frame boundary"
)]
let buffer_slice_i32 = slice::from_raw_parts_mut(buffer.cast::<i32>(), len);
for sample in buffer_slice_i32 {
*sample <<= 8;
}
}
}

render_client.ReleaseBuffer(frames_available, 0)?;
Expand Down
Loading