feat(audio): WASAPI exclusive-mode direct stream (Windows)

The render thread and the handle. COM objects are !Send, so one thread owns
every one of them for the life of the stream and this struct is a bounded
channel plus atomics - the same back-pressure snd_pcm_writei gives the ALSA
path for free.

Opens by ENDPOINT ID, never by friendly name: Windows gives several
endpoints the same name, which is what hid this DAC from the picker until
249adc77b. Walks the format ladder, pre-aligns the period to 128 bytes,
keeps the documented NOT_ALIGNED recovery for the devices that still need
it, joins MMCSS Pro Audio, and refuses S_FALSE loudly - a "closest match" is
by definition not the format we asked for.

Verified against the owner's Cambridge Audio USB Audio 2.0 with the new
example, and it matches the spike exactly:

  rate 96000, S24In32 (24/32), period 30000 hns, buffer 288 frames,
  realigned false, timing Events, mode DirectHardware,
  960000 frames, 0 underruns, 0 event timeouts

Three faults were found on the way, and all three were in the MEASUREMENT
rather than the audio - worth recording because each looked like a hardware
problem:

  - 339 "underruns" were event timeouts counted in the same counter. Split
    them; they are different faults and were hiding each other.
  - 337 event timeouts came from a 2x-period deadline. At a 3 ms period that
    expired on 10% of wakeups because Windows scheduling is not that
    punctual. The deadline exists to stop the thread hanging on a dead
    device, not to detect glitches, and the spike waited 1000 ms and saw
    none in ten minutes. It is generous now.
  - The last 2 were a drain's trailing silence. A drain emitting silence is
    the drain working.

The stub for every other platform mirrors alsa_direct's: constructors refuse,
teardown succeeds, so a caller written against this compiles and unwinds
everywhere. `cargo tree` shows exactly one windows v0.62.2 - no second
windows tree entered the graph.

cc
This commit is contained in:
vicrodh committed 2026-08-27 09:10:37 -06:00
1 parent cc3a11f254
commit be3f423bc4
4 files changed
+897

No files matched your search

+12
View File
@@ -64,3 +64,15 @@ jack = "0.13"
# CoreAudio for device probing, sample rate switching, and (future) hog mode / exclusive mode
coreaudio-rs = "0.14.1"
objc2-core-audio = "0.3.2"
[target.'cfg(windows)'.dependencies]
# WASAPI exclusive mode (phase B). HEnquist's crate, the one CamillaDSP
# uses. `windows` is pinned to the 0.62 line cpal already resolves, so
# this adds no second windows tree to the graph.
wasapi = "0.24"
windows = { version = "0.62", features = [
"Win32_Foundation",
"Win32_Media_Audio",
"Win32_System_Com",
"Win32_System_Threading",
] }