Skip to content
Open
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 @@ -79,6 +79,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **WASAPI**: Output streams now start with real audio immediately instead of undefined content in the render buffer.
- **WASAPI**: A stream paused immediately after starting no longer plays silence before real audio on resume.
- **WASAPI**: Fix `I64` and `F64` incorrectly reported as supported output formats.
- **WASAPI**: A paused stream no longer wakes its thread every engine period while another client keeps the device running.

## [0.18.2] - 2026-08-16

Expand Down
47 changes: 32 additions & 15 deletions src/host/wasapi/stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -241,8 +241,11 @@ struct RunContext {
// Streams that have been created in this event loop.
stream: StreamInner,

// Handles corresponding to the `event` field of each element of `voices`. Must always be in
// sync with `voices`, except that the first element is always `pending_scheduled_event`.
// The handles the run loop waits on, in this order: `pending_scheduled_event` (commands),
// the default-device change event (only for streams that track the default device), and
// the stream's audio event last, so that a paused stream can wait on all but the last.
// When several are signalled, the wait reports the lowest index; an auto-reset event it
// does not report stays signalled for the next wait.
handles: Vec<Foundation::HANDLE>,

commands: Receiver<Command>,
Expand Down Expand Up @@ -347,10 +350,11 @@ impl Stream {
debug_assert_ne!(qpc_frequency, 0, "QueryPerformanceFrequency returned zero");
}

let mut handles = vec![pending_scheduled_event, stream_inner.event];
let mut handles = vec![pending_scheduled_event];
if let Some(ref monitor) = default_device_monitor {
handles.push(monitor.event);
}
handles.push(stream_inner.event);

let pending_device_changed = default_device_monitor
.as_ref()
Expand Down Expand Up @@ -423,10 +427,11 @@ impl Stream {
debug_assert_ne!(qpc_frequency, 0, "QueryPerformanceFrequency returned zero");
}

let mut handles = vec![pending_scheduled_event, stream_inner.event];
let mut handles = vec![pending_scheduled_event];
if let Some(ref monitor) = default_device_monitor {
handles.push(monitor.event);
}
handles.push(stream_inner.event);

let pending_device_changed = default_device_monitor
.as_ref()
Expand Down Expand Up @@ -849,27 +854,39 @@ fn process_commands_and_await_signal(
}
}

// Wait for any of the handles to be signalled.
let handle_idx = match wait_for_handle_signal(&run_context.handles) {
// Wait for any of the handles to be signalled. A stopped client's audio
// event can still be signalled every engine period while other streams
// keep the endpoint running, which would wake this thread about 100
// times a second for nothing. While paused, leave out the audio event,
// which is the last handle.
let audio_idx = run_context.handles.len() - 1;
let handles = if run_context.stream.playback_state == PlaybackState::Playing {
&run_context.handles[..]
} else {
&run_context.handles[..audio_idx]
};
let handle_idx = match wait_for_handle_signal(handles) {
Ok(idx) => idx,
Err(err) => {
emit_error(error_callback, err);
return ControlFlow::Break(());
}
};

// Handle layout: 0 = pending_scheduled_event (commands), 1 = WASAPI audio event,
// 2+ = default-device change event (only present for default-device streams).
// Handle layout: see `RunContext::handles`.
// Continue(true) = audio event fired, proceed to process audio this iteration.
// Continue(false) = command or device-change event, loop around and wait again.
if handle_idx >= 2 {
emit_error(
error_callback,
default_device_change_error(run_context.default_device_flow),
);
return ControlFlow::Continue(false);
match handle_idx {
0 => ControlFlow::Continue(false),
idx if idx == audio_idx => ControlFlow::Continue(true),
_ => {
emit_error(
error_callback,
default_device_change_error(run_context.default_device_flow),
);
ControlFlow::Continue(false)
}
}
ControlFlow::Continue(handle_idx != 0)
}

// The loop for processing pending input data.
Expand Down
Loading