Skip to content

Second round of correctness fixes on multiple backends - #1335

Open
roderickvd wants to merge 13 commits into
masterfrom
fix/backend-robustness-round2
Open

Second round of correctness fixes on multiple backends#1335
roderickvd wants to merge 13 commits into
masterfrom
fix/backend-robustness-round2

Conversation

@roderickvd

Copy link
Copy Markdown
Member

This is the result from a second pass comparing cpal against CamillaDSP's backends for correctness and robustness
gaps (first one landed in 0.18.0). Their backends moved on since (next5), so I went through it again.

In full transparency of licensing: CamillaDSP is MPL-2.0, cpal is Apache 2.0. That should be OK: MPL's copyleft covers licensed source files but not ideas or bugs found while reading them. Nothing was copied, rather, every fix was reimplemented against cpal's conventions. @HEnquist let me know if you have any objections.

@LastExceed would you verify the ASIO and WASAPI changes? You'll note that in WASAPI I made some small changes to pave the way for exclusive mode later.

@roderickvd roderickvd added the bug label Aug 22, 2026
@LastExceed

Copy link
Copy Markdown
Contributor

sure, but might have to wait until monday

@HEnquist

Copy link
Copy Markdown
Contributor

I think it's great if we can share knowledge, absolutely no objections from me!
And I have learned a lot about CoreAudio by looking at cpal and coreaudio-rs, so really nice to be able to give something back :)

@roderickvd

Copy link
Copy Markdown
Member Author

sure, but might have to wait until monday

No worries, let me know when it's done, many of these things we can still backport to stable-0.18 before releasing 0.18.3.

@LastExceed LastExceed left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Static review only so far, haven't tested anything yet. Also can't speak on the changes to non-windows backends, as I am not familiar with them

Comment thread src/host/asio/stream.rs
Comment on lines +1282 to +1284
// ASIOStart() plays buffer half 1 immediately, before the first bufferSwitch can fill it;
// half 0 is covered by that first callback.
fn prefill_output_silence(driver: &sys::Driver, asio_streams: &Mutex<sys::AsioStreams>) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this use the data callback instead of generating silence? Also I think this isn't actually necessary at all:

ASIO 2.3 specification, secion II.5:

The audio streaming starts after the ASIOStart() call. Prior to starting the hardware streaming the driver will issue one or more bufferSwitch() or bufferSwitchTimeInfo() callbacks to fill its first output buffer(s). Since the hardware did not provide any input data yet the input channels' buffers should be filled with silence by the driver.

To me this clearly states that pre-filling is the driver's responsibility, not the host's. In fact, if the initial callback invocations happen within ASIOCreateBuffers() (on the calling thread), then the silence ends up overwriting actual stream data.

@roderickvd roderickvd Aug 24, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What I based it on is https://github.com/dechamps/ASIOUtil/blob/master/BUFFERS.md which also states this and then goes on with two more pieces of the spec and conludes that:

the host should fill buffer 1 prior to calling ASIOStart() ...

It does not seem filled via bufferSwitch callbacks.

Is this synthesis incorrect? I thought that this source was pretty authoritative but it'd be great if you could verify.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is an awesome resource, and I have a thing or two to say about it (will create an issue there). But it doesn't actually seem to contain the conclusion you quoted, Ctrl + F "it is not filled" yields 0 results. Is this paraphrased?

My interpretation is still that the driver should invoke the callback right after buffer creation to populate the buffers, but it is indeed a bit ambiguous

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed the quote for what was my deduction.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left a thorough comment there, I recommend you read it too. Bottom like is that the spec contradicts itself, but it can be somewhat shoehorned into what I said earlier

Comment thread CHANGELOG.md
- **PipeWire**: Fix capture reading from the wrong offset in the buffer on some devices.
- **WASAPI**: Device enumeration no longer panics if the COM enumerator fails to initialize.
- **WASAPI**: Output streams now start with silence instead of undefined content in the render buffer.
- **WASAPI**: Fix `I64` and `F64` incorrectly reported as supported output formats.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have a source for the claim that this was incorrect? I can't find anything that forbids WASAPI devices from using these formats

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't, the MSDN docs are woefully quiet about what's supported and what's not. I believe that 64-bit isn't a thing in WASAPI at all. As proof of the contrary I've not found any implementations of it either. Could you test on a Windows box?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Scratch that, I just realized that we're specifically talking about formats supported by AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM, not by the device itself. Your commit is correct, i64 and f64 are not supported (IAudioClient::Initialize() returns E_INVALIDARG). See also #1343

Comment thread src/host/wasapi/stream.rs
Comment on lines +587 to +590
let frames = get_available_frames(&run_context.stream)?;
if frames > 0 {
write_silence(render_client, &run_context.stream, frames)?;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here too, shouldn't we use the data callback instead of generating silence?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, not sure if I got this right. As matter of fact, CamillaDSP doesn't write silence at all, but moves start_stream() to after the first pass of the fill loop. This proposal of mine writes silence because process_commands() has no access to the data callback, at the cost of one period of silence at the start. That's smaller than the real fix would be, mirroring what CamillaDSP did. What do you think?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess it is fine for now, not worth refactoring everything just for this. But probably worth creating an issue for future reference, I could imagine similar situations existing on the other backends as well (I haven't checked)

Comment thread src/host/wasapi/device.rs
Comment on lines 189 to 195
// Given the audio client and format, returns whether the audio engine supports it natively in
// shared mode without format conversion.
// the given share mode without format conversion.
pub unsafe fn is_format_supported(
client: &Audio::IAudioClient,
waveformatex_ptr: *const Audio::WAVEFORMATEX,
share_mode: Audio::AUDCLNT_SHAREMODE,
) -> Result<bool, Error> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd generally discourage implementing things before we need them (YAGNI principle). In this case it is probably fine, but in general you never know how requirements might change, or when a planned feature ends up getting scrapped entirely, potentially resulting in code that ends up never being used

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Native DSD support on WASAPI is one of the v0.19 design goals, so it was easy to rig up.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Err I meant exclusive mode.

Comment thread src/host/asio/stream.rs
Comment on lines 225 to +228

// There is 0% chance of lock contention the host only locks when recreating streams.
let stream_lock = asio_streams.lock().unwrap();
let Ok(stream_lock) = asio_streams.lock() else {
return;
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The commit message says "do not block", but .lock() is a blocking function, no?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, I should have written "do not panic".

@LastExceed

Copy link
Copy Markdown
Contributor

ran a couple examples on hardware via ASIO and WASAPI, no problems to report

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants