fix: make BT A2DP output exclusive to one process

The A2DP output is a single unmixed PCM stream. A second process
opening audio while a stream was active would reconfigure the SBC
encoder and media clock under the owner and interleave both apps' PCM
into one ring, garbling playback (e.g. launching DOOM destabilized
Music). Add ClaimOutput/ReleaseOutput pid ownership: the first opener
gets the BT sink, later openers fall back to the HDA mixer, and only
the owner can tear the stream down. The scheduler releases ownership
on process exit so a killed app cannot leak the claim.

Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
danielandClaude Fable 5 committed 2026-07-10 12:41:17 +02:00
1 parent 9519e174c0
commit 928fe0bbed
5 files changed
+75 -14

No files matched your search

+26
View File
@@ -134,6 +134,11 @@ namespace Drivers::USB::Bluetooth::A2dp {
// Volume
static int g_volume = 80;
// Exclusive owner (pid) of the A2DP audio output, -1 = free. See
// ClaimOutput/ReleaseOutput in the header: the output is one unmixed
// stream, so a second process sharing the handle would corrupt it.
static std::atomic<int> g_outputOwnerPid{-1};
// AVDTP response tracking
static volatile bool g_avdtpResponseReady = false;
static uint8_t g_avdtpResponseBuf[128] = {};
@@ -1606,4 +1611,25 @@ namespace Drivers::USB::Bluetooth::A2dp {
g_volume = percent;
}
// =========================================================================
// Output ownership (one process at a time; see header)
// =========================================================================
bool ClaimOutput(int pid) {
if (pid < 0) return false;
int expected = -1;
return g_outputOwnerPid.compare_exchange_strong(expected, pid,
std::memory_order_acq_rel);
}
void ReleaseOutput(int pid) {
if (pid < 0) return;
if (g_outputOwnerPid.load(std::memory_order_acquire) != pid) return;
// Stop (suspend + flush queued PCM) BEFORE freeing ownership, so a
// concurrent Open cannot configure the stream while it is being
// torn down.
StopStream(true);
g_outputOwnerPid.store(-1, std::memory_order_release);
}
}
+15
View File
@@ -59,6 +59,21 @@ namespace Drivers::USB::Bluetooth::A2dp {
// of bytes accepted (0 = ring full, retry later).
int WriteAudio(const uint8_t* pcmData, uint32_t pcmLen);
// The A2DP output is a single unmixed PCM stream, so at most one process
// may own the Bluetooth audio handle at a time. A second opener sharing
// it would reconfigure the SBC encoder and media clock under the first
// stream and interleave its raw PCM into the same ring (audible garble
// and dropouts), and its close would suspend the owner's stream.
//
// ClaimOutput returns true if `pid` now owns the output; false if it is
// already owned (the caller should fall back to the HDA mixer).
// ReleaseOutput stops the stream (dropping queued PCM) and frees the
// output when `pid` is the current owner; no-op otherwise. The
// scheduler also calls it on process exit so a killed app cannot leak
// ownership.
bool ClaimOutput(int pid);
void ReleaseOutput(int pid);
// Encode + send queued PCM, paced to the audio clock and gated on ACL TX
// readiness. Called from the idle-loop event pump and from WriteAudio;
// self-serializing, cheap no-op when not streaming.