Skip to content

Commit 684e31d

Browse files
fix(wasapi): shift i24 to/from top bits (#1309)
Co-authored-by: Roderick van Domburg <roderick@vandomburg.net>
1 parent dd196b6 commit 684e31d

2 files changed

Lines changed: 49 additions & 5 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
8585
- **WASAPI**: Capture streams no longer report a spurious xrun on the first buffer after starting.
8686
- **WASAPI**: Output streams no longer reject formats that the built-in resampler can convert.
8787
- **WASAPI**: Device enumeration no longer panics if the COM enumerator fails to initialize.
88+
- **WASAPI**: Fix 24-bit samples not getting shifted from/to MSB.
8889
- **WebAudio**: Fix stale audio output when a data callback wrote a partial buffer.
8990
- **WebAudio**: Fix unsound `Send + Sync` on `Stream` when compiled with `+atomics`.
9091
- **WebAudio**: Fix `Host::is_available()` always returning `true`, even in non-window contexts.

src/host/wasapi/stream.rs

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::{
22
mem,
33
ops::ControlFlow,
4-
ptr,
4+
ptr, slice,
55
sync::{
66
Arc,
77
atomic::{AtomicBool, AtomicU64, Ordering},
@@ -665,6 +665,15 @@ fn run_input(
665665
emit_error(error_callback, err);
666666
}
667667

668+
let stream = &run_ctxt.stream;
669+
let scratch_len = if stream.sample_format == SampleFormat::I24 {
670+
stream.max_frames_in_buffer as usize * stream.bytes_per_frame as usize / size_of::<i32>()
671+
} else {
672+
// The scratch buffer won't be used in this case.
673+
0 // Vec::with_capacity(0) does not allocate.
674+
};
675+
let mut scratch_buffer = vec![0; scratch_len].into_boxed_slice();
676+
668677
loop {
669678
match process_commands_and_await_signal(&mut run_ctxt, error_callback) {
670679
ControlFlow::Break(()) => break,
@@ -675,7 +684,12 @@ fn run_input(
675684
AudioClientFlow::Capture { ref capture_client } => capture_client.clone(),
676685
_ => unreachable!(),
677686
};
678-
if let Err(err) = process_input(&run_ctxt.stream, capture_client, data_callback) {
687+
if let Err(err) = process_input(
688+
&run_ctxt.stream,
689+
capture_client,
690+
data_callback,
691+
&mut scratch_buffer,
692+
) {
679693
emit_error(error_callback, err);
680694
break;
681695
}
@@ -815,6 +829,7 @@ fn process_input(
815829
stream: &StreamInner,
816830
capture_client: Audio::IAudioCaptureClient,
817831
data_callback: &mut dyn FnMut(&Data, &CallbackInfo),
832+
scratch_buffer: &mut [i32],
818833
) -> Result<(), Error> {
819834
unsafe {
820835
// Get the available data in the shared buffer.
@@ -850,10 +865,26 @@ fn process_input(
850865
&& flags & Audio::AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY.0 as u32 != 0;
851866

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

854-
let data = buffer as *mut ();
855-
let len = frames_available as usize * stream.bytes_per_frame as usize
856-
/ stream.sample_format.sample_size();
882+
dst.as_mut_ptr().cast()
883+
} else {
884+
buffer.cast()
885+
};
886+
887+
let len = byte_count / stream.sample_format.sample_size();
857888
let data = Data::from_parts(data, len, stream.sample_format);
858889

859890
if !stream.draining.load(Ordering::Relaxed) {
@@ -921,6 +952,18 @@ fn process_output(
921952
xrun: false,
922953
},
923954
);
955+
956+
if stream.sample_format == SampleFormat::I24 {
957+
// WASAPI stores i24 in the upper bits
958+
#[expect(
959+
clippy::cast_ptr_alignment,
960+
reason = "WASAPI guarantees the buffer to be aligned to a frame boundary"
961+
)]
962+
let buffer_slice_i32 = slice::from_raw_parts_mut(buffer.cast::<i32>(), len);
963+
for sample in buffer_slice_i32 {
964+
*sample <<= 8;
965+
}
966+
}
924967
}
925968

926969
render_client.ReleaseBuffer(frames_available, 0)?;

0 commit comments

Comments
 (0)