feat: audio - add concurrent mixing, output switching, and device-aware UI

This commit is contained in:
2026-07-29 20:03:35 +01:00
parent d99dab45e5
commit 86de3400a9
25 changed files with 688 additions and 209 deletions
+13 -72
View File
@@ -19,52 +19,19 @@
namespace montauk::abi {
// Audio handle convention:
// 0x00 - 0x07 : Mixer virtual streams (one per opened audio handle)
// 0x100 : Bluetooth A2DP audio output (bypasses mixer for now)
static constexpr int AUDIO_HANDLE_BT = 0x100;
static int64_t Sys_AudioOpen(uint32_t sampleRate, uint8_t channels, uint8_t bitsPerSample) {
auto* proc = Sched::GetCurrentProcessPtr();
int pid = proc ? proc->pid : -1;
const char* name = proc ? proc->name : "?";
// Auto-switch: when a Bluetooth A2DP sink is connected and its stream is
// set up (StartSource left it Configured/Open), route audio to the
// headphones -- like a phone does when you plug in BT. Falls back to
// the built-in speakers (HDA) when no BT sink is ready.
if (Drivers::USB::Bluetooth::IsInitialized()) {
auto state = Drivers::USB::Bluetooth::A2dp::GetState();
if (state == Drivers::USB::Bluetooth::A2dp::State::Open ||
state == Drivers::USB::Bluetooth::A2dp::State::Streaming ||
state == Drivers::USB::Bluetooth::A2dp::State::Configured) {
// The BT output is a single unmixed stream, so only the first
// opener gets it. Configuring it again while another stream
// owns it would reset the SBC encoder and media clock under
// that stream and interleave both apps' PCM into one ring
// (garbled playback, dropouts persisting until the owner
// reopens). Later openers fall through to the HDA mixer.
if (Drivers::USB::Bluetooth::A2dp::ClaimOutput(pid)) {
Drivers::USB::Bluetooth::A2dp::ConfigureStream(sampleRate, channels, bitsPerSample);
if (Drivers::USB::Bluetooth::A2dp::StartStream()) {
return AUDIO_HANDLE_BT;
}
// Stream would not start (sink unresponsive / state desync):
// returning the BT handle anyway would make every write fail
// with the app stuck reporting "playing" at 0:00. Fall through
// to the speakers instead.
Drivers::USB::Bluetooth::A2dp::ReleaseOutput(pid);
Kt::KernelLogStream(Kt::WARNING, "Audio")
<< "BT A2DP stream failed to start; falling back to HDA";
}
}
}
// HDA-backed mixer is the default output. The mixer keeps the HDA
// hardware stream open across virtual streams, so multiple apps can
// play simultaneously.
if (Drivers::Audio::IntelHda::IsInitialized()) {
// Every application gets a mixer stream regardless of the selected
// device. The mixer performs one shared resample/mix pass and routes it
// to HDA or A2DP, allowing concurrent Bluetooth playback and live
// switching without invalidating application handles.
if (Drivers::Audio::Mixer::IsOutputAvailable(
Drivers::Audio::Mixer::Output::Hda) ||
Drivers::Audio::Mixer::IsOutputAvailable(
Drivers::Audio::Mixer::Output::Bluetooth)) {
return (int64_t)Drivers::Audio::Mixer::Open(sampleRate, channels,
bitsPerSample, pid, name);
}
@@ -73,46 +40,20 @@ namespace montauk::abi {
}
static int64_t Sys_AudioClose(int handle) {
if (handle == AUDIO_HANDLE_BT) {
// Stops the stream (dropping the queued tail) only when the caller
// owns the BT output, so a stale handle held by another process
// cannot tear down the owner's stream.
auto* proc = Sched::GetCurrentProcessPtr();
Drivers::USB::Bluetooth::A2dp::ReleaseOutput(proc ? proc->pid : -1);
return 0;
}
Drivers::Audio::Mixer::Close(handle);
return 0;
}
static int64_t Sys_AudioWrite(int handle, const uint8_t* data, uint32_t size) {
if (handle == AUDIO_HANDLE_BT) {
return (int64_t)Drivers::USB::Bluetooth::A2dp::WriteAudio(data, size);
}
return (int64_t)Drivers::Audio::Mixer::Write(handle, data, size);
}
static int64_t Sys_AudioCtl(int handle, int cmd, int value) {
if (handle == AUDIO_HANDLE_BT) {
switch (cmd) {
case AUDIO_CTL_SET_VOLUME:
Drivers::USB::Bluetooth::A2dp::SetVolume(value);
return 0;
case AUDIO_CTL_GET_VOLUME:
return Drivers::USB::Bluetooth::A2dp::GetVolume();
case AUDIO_CTL_PAUSE:
// Pause keeps the queued PCM so resume continues gaplessly.
if (value) Drivers::USB::Bluetooth::A2dp::StopStream(false);
else Drivers::USB::Bluetooth::A2dp::StartStream();
return 0;
case AUDIO_CTL_GET_OUTPUT:
return 1; // Bluetooth
default:
return -1;
}
}
if (cmd == AUDIO_CTL_GET_OUTPUT) return 0; // HDA
if (cmd == AUDIO_CTL_GET_OUTPUT)
return (int)Drivers::Audio::Mixer::GetOutput();
if (cmd == AUDIO_CTL_SET_OUTPUT)
return Drivers::Audio::Mixer::SetOutput(
(Drivers::Audio::Mixer::Output)value);
if (cmd == AUDIO_CTL_BT_STATUS) {
if (!Drivers::USB::Bluetooth::IsInitialized()) return 0;
return (int64_t)Drivers::USB::Bluetooth::A2dp::GetState();
+1 -1
View File
@@ -12,4 +12,4 @@
#pragma once
#define MONTAUK_BUILD_NUMBER 17
#define MONTAUK_BUILD_NUMBER 20
+2
View File
@@ -174,6 +174,8 @@ namespace montauk::abi {
static constexpr int AUDIO_CTL_GET_MUTE = 10;
static constexpr int AUDIO_CTL_SET_MASTER_MUTE = 11;
static constexpr int AUDIO_CTL_GET_MASTER_MUTE = 12;
static constexpr int AUDIO_OUTPUT_HDA = 0;
static constexpr int AUDIO_OUTPUT_BLUETOOTH = 1;
/* Bluetooth.hpp */
static constexpr uint64_t SYS_BTSCAN = 84;
+58 -14
View File
@@ -26,6 +26,10 @@ namespace Drivers::Audio::IntelHda {
static bool g_initialized = false;
static kcp::Spinlock g_codecLock;
// Serializes stream lifecycle, DMA write-pointer updates, and IRQ-side
// stream inspection. The IRQ releases it before calling the mixer to keep
// the lock order consistently Mixer -> HDA stream.
static kcp::Spinlock g_streamLock;
static volatile uint8_t* g_mmioBase = nullptr;
static uint8_t g_bus, g_dev, g_func;
@@ -852,6 +856,7 @@ namespace Drivers::Audio::IntelHda {
bool bufferCompleted = false;
// Handle stream interrupts (bits 0-29 correspond to stream descriptors)
g_streamLock.Acquire();
if (g_stream.Active) {
uint8_t si = g_stream.StreamIndex;
if (intsts & (1u << si)) {
@@ -860,6 +865,7 @@ namespace Drivers::Audio::IntelHda {
WriteSD8(si, SD_STS, sts);
}
}
g_streamLock.Release();
// Handle RIRB interrupt (controller interrupt enable bit 30)
// Do NOT advance g_rirbReadPtr here — ReadResponse() owns it.
@@ -1028,11 +1034,16 @@ namespace Drivers::Audio::IntelHda {
int Open(uint32_t sampleRate, uint8_t channels, uint8_t bitsPerSample) {
if (!g_initialized) return -1;
if (g_stream.Active) return -1; // Only one stream at a time
if (channels < 1 || channels > 8) return -1;
if (bitsPerSample != 8 && bitsPerSample != 16 && bitsPerSample != 20
&& bitsPerSample != 24 && bitsPerSample != 32) return -1;
g_streamLock.Acquire();
if (g_stream.Active) {
g_streamLock.Release();
return -1; // Only one physical stream at a time
}
// Output stream index = numInputStreams (first output stream)
uint8_t streamIndex = g_numInputStreams;
uint8_t streamTag = 1;
@@ -1044,7 +1055,10 @@ namespace Drivers::Audio::IntelHda {
ConfigureOutputPath(fmt, streamTag);
// Set up the output stream DMA
if (!SetupOutputStream(streamIndex, fmt)) return -1;
if (!SetupOutputStream(streamIndex, fmt)) {
g_streamLock.Release();
return -1;
}
// Zero the DMA buffer
memset(g_dmaBuffer, 0, TOTAL_BUFFER_SIZE);
@@ -1065,11 +1079,17 @@ namespace Drivers::Audio::IntelHda {
<< (uint64_t)sampleRate << "Hz " << (uint64_t)bitsPerSample << "-bit "
<< (uint64_t)channels << "ch";
g_streamLock.Release();
return 0; // Handle 0
}
void Close(int handle) {
if (handle != 0 || !g_stream.Active) return;
if (handle != 0) return;
g_streamLock.Acquire();
if (!g_stream.Active) {
g_streamLock.Release();
return;
}
StopStream(g_stream.StreamIndex);
@@ -1080,10 +1100,16 @@ namespace Drivers::Audio::IntelHda {
g_stream.Active = false;
KernelLogStream(OK, "HDA") << "Stream closed";
g_streamLock.Release();
}
uint32_t GetWriteSpace(int handle) {
if (handle != 0 || !g_stream.Active) return 0;
if (handle != 0) return 0;
g_streamLock.Acquire();
if (!g_stream.Active) {
g_streamLock.Release();
return 0;
}
uint32_t hwPos = g_dmaPos[g_stream.StreamIndex * 2];
uint32_t writePos = g_stream.WritePos;
@@ -1095,12 +1121,18 @@ namespace Drivers::Audio::IntelHda {
}
if (available > 64) available -= 64;
else available = 0;
g_streamLock.Release();
return available;
}
int Write(int handle, const uint8_t* data, uint32_t size) {
if (handle != 0 || !g_stream.Active || !data || size == 0)
if (handle != 0 || !data || size == 0)
return -1;
g_streamLock.Acquire();
if (!g_stream.Active) {
g_streamLock.Release();
return -1;
}
// Drain unsolicited responses from the RIRB — during playback no
// CodecCommands are sent, so ReadResponse() never runs and jack
@@ -1135,7 +1167,10 @@ namespace Drivers::Audio::IntelHda {
else available = 0;
if (size > available) size = available;
if (size == 0) return 0;
if (size == 0) {
g_streamLock.Release();
return 0;
}
// Write data to DMA buffer (handle wrap-around)
uint32_t firstChunk = TOTAL_BUFFER_SIZE - writePos;
@@ -1148,36 +1183,45 @@ namespace Drivers::Audio::IntelHda {
g_stream.WritePos = (writePos + size) % TOTAL_BUFFER_SIZE;
g_streamLock.Release();
return (int)size;
}
int Control(int handle, int cmd, int value) {
if (handle != 0) return -1;
g_streamLock.Acquire();
int result = -1;
switch (cmd) {
case AUDIO_CTL_SET_VOLUME:
if (!g_initialized) { g_volume = value; return 0; }
if (!g_initialized) { g_volume = value; result = 0; break; }
SetOutputVolume(value);
return 0;
result = 0;
break;
case AUDIO_CTL_GET_VOLUME:
return g_volume;
result = g_volume;
break;
case AUDIO_CTL_GET_POS:
if (!g_stream.Active) return 0;
return (int)g_dmaPos[g_stream.StreamIndex * 2];
result = !g_stream.Active ? 0 :
(int)g_dmaPos[g_stream.StreamIndex * 2];
break;
case AUDIO_CTL_PAUSE:
if (!g_stream.Active) return -1;
if (!g_stream.Active) break;
if (value)
StopStream(g_stream.StreamIndex);
else
StartStream(g_stream.StreamIndex);
return 0;
result = 0;
break;
default:
return -1;
break;
}
g_streamLock.Release();
return result;
}
};
+277 -36
View File
@@ -7,6 +7,8 @@
#include "Mixer.hpp"
#include "IntelHda.hpp"
#include <Drivers/USB/Bluetooth/Bluetooth.hpp>
#include <Drivers/USB/Bluetooth/A2dp.hpp>
#include <Memory/PageFrameAllocator.hpp>
#include <Memory/HHDM.hpp>
#include <Sched/Scheduler.hpp>
@@ -14,6 +16,7 @@
#include <CppLib/Stream.hpp>
#include <CppLib/Spinlock.hpp>
#include <Libraries/Memory.hpp>
#include <atomic>
namespace Drivers::Audio::Mixer {
@@ -34,9 +37,12 @@ namespace Drivers::Audio::Mixer {
// Cap one pump cycle to keep loop bounded under heavy write bursts.
static constexpr uint32_t MAX_PUMP_FRAMES = 4096; // ~85 ms at 48 kHz
static_assert((MAX_STREAMS & (MAX_STREAMS - 1)) == 0,
"handle slot mask requires a power-of-two stream count");
struct VirtualStream {
bool active;
int handle;
int ownerPid;
char name[64];
@@ -66,20 +72,24 @@ namespace Drivers::Audio::Mixer {
static VirtualStream g_streams[MAX_STREAMS] = {};
static bool g_hdaOpened = false;
static int g_hdaHandle = -1;
static int g_masterVolume = 80;
static bool g_masterMute = false;
static bool g_btOpened = false;
static Output g_output = Output::Hda;
static std::atomic<bool> g_switchingOutput{false};
static std::atomic<int> g_masterVolume{80};
static std::atomic<bool> g_masterMute{false};
static int g_activeCount = 0;
static uint32_t g_nextGeneration = 1;
static uint64_t g_masterHwSeq = 0;
// Monotonically increasing serial. Bumped (and waiters woken) on every
// mutation of mixer state. Clients use it to detect changes without
// re-reading the whole snapshot. The address of the serial doubles as the
// wait-object passed to BlockOnObject / WakeObjectWaiters.
static volatile uint64_t g_serial = 0;
static std::atomic<uint64_t> g_serial{0};
// Caller must hold g_lock.
static void BumpSerialLocked() {
g_serial++;
g_serial.fetch_add(1, std::memory_order_release);
}
// Scratch mix buffer (int32 stereo, to avoid clipping during accumulation).
@@ -104,11 +114,19 @@ namespace Drivers::Audio::Mixer {
return (int16_t*)p;
}
static int SlotFromHandle(int handle) {
if (handle < 0) return -1;
return handle & (MAX_STREAMS - 1);
}
static void FreeRing(int16_t* ring) {
if (!ring) return;
Memory::g_pfa->ReallocConsecutive(ring, 0);
}
// Caller holds g_lock. HDA setup is non-blocking and serialized by the
// mixer; Bluetooth setup is performed outside g_lock by SetOutput because
// AVDTP commands may wait for the peer.
static bool EnsureHdaOpen() {
if (g_hdaOpened) return true;
if (!IntelHda::IsInitialized()) return false;
@@ -116,17 +134,26 @@ namespace Drivers::Audio::Mixer {
if (g_hdaHandle < 0) return false;
// Master volume is applied in software during mixdown. Keep the codec
// amp at unity and use it only as an immediate hard-mute gate.
IntelHda::Control(g_hdaHandle, IntelHda::AUDIO_CTL_SET_VOLUME, g_masterMute ? 0 : 100);
IntelHda::Control(g_hdaHandle, IntelHda::AUDIO_CTL_SET_VOLUME,
g_masterMute.load(std::memory_order_acquire) ? 0 : 100);
g_hdaOpened = true;
return true;
}
static bool BluetoothReady() {
if (!Drivers::USB::Bluetooth::IsInitialized()) return false;
auto state = Drivers::USB::Bluetooth::A2dp::GetState();
return state == Drivers::USB::Bluetooth::A2dp::State::Configured ||
state == Drivers::USB::Bluetooth::A2dp::State::Open ||
state == Drivers::USB::Bluetooth::A2dp::State::Streaming;
}
static void SyncHdaMasterMute() {
for (;;) {
g_lock.Acquire();
bool hdaOpen = g_hdaOpened;
int handle = g_hdaHandle;
bool muted = g_masterMute;
bool muted = g_masterMute.load(std::memory_order_acquire);
uint64_t seq = g_masterHwSeq;
g_lock.Release();
@@ -204,8 +231,25 @@ namespace Drivers::Audio::Mixer {
return frames;
}
static uint32_t BackendWriteSpace() {
if (g_output == Output::Bluetooth) {
return g_btOpened
? Drivers::USB::Bluetooth::A2dp::GetWriteSpace() : 0;
}
return g_hdaOpened ? IntelHda::GetWriteSpace(g_hdaHandle) : 0;
}
static int BackendWrite(const uint8_t* data, uint32_t size) {
if (g_output == Output::Bluetooth) {
return g_btOpened
? Drivers::USB::Bluetooth::A2dp::WriteAudio(data, size) : -1;
}
return g_hdaOpened ? IntelHda::Write(g_hdaHandle, data, size) : -1;
}
static void Pump() {
if (!g_hdaOpened) return;
if ((g_output == Output::Hda && !g_hdaOpened) ||
(g_output == Output::Bluetooth && !g_btOpened)) return;
// Produce only as many frames as the HDA DMA ring has room for right
// now. Producing more would mean the surplus is silently dropped by
@@ -213,7 +257,7 @@ namespace Drivers::Audio::Mixer {
// advanced — that's what scrambles speech into a sequence of
// unrelated chunks. Clamp to MAX_PUMP_FRAMES so the scratch buffers
// are bounded.
uint32_t freeBytes = IntelHda::GetWriteSpace(g_hdaHandle);
uint32_t freeBytes = BackendWriteSpace();
uint32_t frames = freeBytes / 4;
if (frames == 0) return;
if (frames > MAX_PUMP_FRAMES) frames = MAX_PUMP_FRAMES;
@@ -240,7 +284,7 @@ namespace Drivers::Audio::Mixer {
frames = streamFrames;
} else if (g_activeCount == 0 || !hasAudible || !hasUnpaused) {
memset(g_outScratch, 0, frames * 2 * sizeof(int16_t));
IntelHda::Write(g_hdaHandle, (const uint8_t*)g_outScratch, frames * 4);
BackendWrite((const uint8_t*)g_outScratch, frames * 4);
return;
}
@@ -304,7 +348,10 @@ namespace Drivers::Audio::Mixer {
}
// Saturate, apply master volume + mute, and emit s16 stereo.
int32_t masterGain = g_masterMute ? 0 : g_masterVolume; // 0..100
bool masterMuted = g_masterMute.load(std::memory_order_acquire);
int32_t masterGain = masterMuted
? 0
: g_masterVolume.load(std::memory_order_acquire);
for (uint32_t f = 0; f < frames; f++) {
int32_t l = (g_mixScratch[f * 2 + 0] * masterGain) / 100;
int32_t r = (g_mixScratch[f * 2 + 1] * masterGain) / 100;
@@ -314,7 +361,7 @@ namespace Drivers::Audio::Mixer {
// Hand off to HDA. IntelHda::Write returns the number of bytes
// actually accepted (limited by free space in the DMA ring).
IntelHda::Write(g_hdaHandle, (const uint8_t*)g_outScratch, frames * 4);
BackendWrite((const uint8_t*)g_outScratch, frames * 4);
}
// =========================================================================
@@ -334,10 +381,48 @@ namespace Drivers::Audio::Mixer {
int16_t* ring = AllocRing();
if (!ring) return -1;
retry_after_switch:
g_lock.Acquire();
if (!EnsureHdaOpen()) {
if (g_switchingOutput.load(std::memory_order_acquire)) {
g_lock.Release();
Sched::BlockOnObject((void*)&g_switchingOutput, 1000);
goto retry_after_switch;
}
// The selected backend is opened lazily. A remembered Bluetooth
// selection whose sink is no longer ready falls back to HDA.
bool wakeSwitchWaiters = false;
if (g_output == Output::Bluetooth && !BluetoothReady())
g_output = Output::Hda;
if (g_output == Output::Hda && !IntelHda::IsInitialized() &&
BluetoothReady())
g_output = Output::Bluetooth;
if (g_output == Output::Bluetooth && !g_btOpened && BluetoothReady()) {
g_switchingOutput = true;
g_lock.Release();
bool btOk = Drivers::USB::Bluetooth::A2dp::ConfigureStream(
MIX_RATE, MIX_CHANNELS, MIX_BITS) &&
Drivers::USB::Bluetooth::A2dp::StartStream();
g_lock.Acquire();
g_btOpened = btOk;
if (!btOk) g_output = Output::Hda;
if (btOk)
Drivers::USB::Bluetooth::A2dp::SetMuted(
g_masterMute.load(std::memory_order_acquire));
g_switchingOutput = false;
wakeSwitchWaiters = true;
}
if (g_output == Output::Hda && !EnsureHdaOpen()) {
g_lock.Release();
if (wakeSwitchWaiters)
Sched::WakeObjectWaiters((void*)&g_switchingOutput);
FreeRing(ring);
return -1;
}
if (g_output == Output::Bluetooth && !g_btOpened) {
g_lock.Release();
if (wakeSwitchWaiters)
Sched::WakeObjectWaiters((void*)&g_switchingOutput);
FreeRing(ring);
return -1;
}
@@ -348,12 +433,18 @@ namespace Drivers::Audio::Mixer {
}
if (slot < 0) {
g_lock.Release();
if (wakeSwitchWaiters)
Sched::WakeObjectWaiters((void*)&g_switchingOutput);
FreeRing(ring);
return -1;
}
VirtualStream& s = g_streams[slot];
s.active = true;
uint32_t generation = g_nextGeneration;
g_nextGeneration = (g_nextGeneration == 0x0FFFFFFFu)
? 1 : g_nextGeneration + 1;
s.handle = (int)((generation << 3) | (uint32_t)slot);
s.ownerPid = ownerPid;
int n = 0;
if (ownerName) {
@@ -377,14 +468,17 @@ namespace Drivers::Audio::Mixer {
BumpSerialLocked();
g_lock.Release();
if (wakeSwitchWaiters)
Sched::WakeObjectWaiters((void*)&g_switchingOutput);
Sched::WakeObjectWaiters((void*)&g_serial);
return slot;
return s.handle;
}
void Close(int handle) {
if (handle < 0 || handle >= MAX_STREAMS) return;
int slot = SlotFromHandle(handle);
if (slot < 0 || slot >= MAX_STREAMS) return;
g_lock.Acquire();
VirtualStream& s = g_streams[handle];
VirtualStream& s = g_streams[slot];
bool changed = false;
// Pull the ring pointer out of the slot before freeing it. Once
// s.active=false and s.ring=nullptr are visible under the lock no
@@ -393,10 +487,11 @@ namespace Drivers::Audio::Mixer {
// spinning on g_lock while ReallocConsecutive walks the free list.
int16_t* ringToFree = nullptr;
bool lastStream = false;
if (s.active) {
if (s.active && s.handle == handle) {
ringToFree = s.ring;
s.ring = nullptr;
s.active = false;
s.handle = -1;
s.ownerPid = 0;
s.name[0] = '\0';
if (g_activeCount > 0) g_activeCount--;
@@ -408,29 +503,36 @@ namespace Drivers::Audio::Mixer {
// the HDA DMA ring stops looping its last 32 KiB of samples. The
// next Open() reopens the HDA stream via EnsureHdaOpen().
bool closeHda = lastStream && g_hdaOpened;
bool closeBt = lastStream && g_btOpened;
int hdaHandle = g_hdaHandle;
if (closeHda) {
g_hdaOpened = false;
g_hdaHandle = -1;
}
if (closeBt) g_btOpened = false;
g_lock.Release();
if (closeHda) IntelHda::Close(hdaHandle);
if (closeBt) Drivers::USB::Bluetooth::A2dp::StopStream(true);
if (ringToFree) FreeRing(ringToFree);
if (changed) Sched::WakeObjectWaiters((void*)&g_serial);
}
int Write(int handle, const uint8_t* data, uint32_t size) {
if (handle < 0 || handle >= MAX_STREAMS || !data || size == 0) return -1;
int slot = SlotFromHandle(handle);
if (slot < 0 || slot >= MAX_STREAMS || !data || size == 0) return -1;
g_lock.Acquire();
VirtualStream& s = g_streams[handle];
if (!s.active) { g_lock.Release(); return -1; }
VirtualStream& s = g_streams[slot];
if (!s.active || s.handle != handle) { g_lock.Release(); return -1; }
uint32_t written = ConvertAndPush(s, data, size);
// Run a pump cycle so the HDA buffer stays fed.
Pump();
bool serviceBluetooth = g_output == Output::Bluetooth;
g_lock.Release();
if (serviceBluetooth)
Drivers::USB::Bluetooth::A2dp::ServiceMedia();
return (int)written;
}
@@ -449,11 +551,12 @@ namespace Drivers::Audio::Mixer {
return GetMasterMute() ? 1 : 0;
}
if (handle < 0 || handle >= MAX_STREAMS) return -1;
int slot = SlotFromHandle(handle);
if (slot < 0 || slot >= MAX_STREAMS) return -1;
g_lock.Acquire();
VirtualStream& s = g_streams[handle];
if (!s.active) { g_lock.Release(); return -1; }
VirtualStream& s = g_streams[slot];
if (!s.active || s.handle != handle) { g_lock.Release(); return -1; }
int rv = -1;
bool changed = false;
@@ -498,7 +601,7 @@ namespace Drivers::Audio::Mixer {
for (int i = 0; i < MAX_STREAMS && count < maxCount; i++) {
VirtualStream& s = g_streams[i];
if (!s.active) continue;
buf[count].handle = i;
buf[count].handle = s.handle;
buf[count].ownerPid = s.ownerPid;
int j = 0;
for (; j < 63 && s.name[j]; j++) buf[count].name[j] = s.name[j];
@@ -527,6 +630,7 @@ namespace Drivers::Audio::Mixer {
ringsToFree[ringCount++] = s.ring;
s.ring = nullptr;
s.active = false;
s.handle = -1;
s.ownerPid = 0;
s.name[0] = '\0';
if (g_activeCount > 0) g_activeCount--;
@@ -534,14 +638,17 @@ namespace Drivers::Audio::Mixer {
}
}
bool closeHda = changed && (g_activeCount == 0) && g_hdaOpened;
bool closeBt = changed && (g_activeCount == 0) && g_btOpened;
int hdaHandle = g_hdaHandle;
if (closeHda) {
g_hdaOpened = false;
g_hdaHandle = -1;
}
if (closeBt) g_btOpened = false;
if (changed) BumpSerialLocked();
g_lock.Release();
if (closeHda) IntelHda::Close(hdaHandle);
if (closeBt) Drivers::USB::Bluetooth::A2dp::StopStream(true);
for (int i = 0; i < ringCount; i++) FreeRing(ringsToFree[i]);
if (changed) Sched::WakeObjectWaiters((void*)&g_serial);
}
@@ -551,8 +658,9 @@ namespace Drivers::Audio::Mixer {
if (percent > 100) percent = 100;
g_lock.Acquire();
bool changed = (g_masterVolume != percent);
g_masterVolume = percent;
bool changed =
g_masterVolume.load(std::memory_order_relaxed) != percent;
g_masterVolume.store(percent, std::memory_order_release);
if (changed) BumpSerialLocked();
g_lock.Release();
@@ -560,13 +668,13 @@ namespace Drivers::Audio::Mixer {
}
int GetMasterVolume() {
return g_masterVolume;
return g_masterVolume.load(std::memory_order_acquire);
}
void SetMasterMute(bool muted) {
g_lock.Acquire();
bool changed = (g_masterMute != muted);
g_masterMute = muted;
bool changed = g_masterMute.load(std::memory_order_relaxed) != muted;
g_masterMute.store(muted, std::memory_order_release);
if (changed) {
g_masterHwSeq++;
BumpSerialLocked();
@@ -574,11 +682,142 @@ namespace Drivers::Audio::Mixer {
g_lock.Release();
if (changed) SyncHdaMasterMute();
if (changed)
Drivers::USB::Bluetooth::A2dp::SetMuted(muted);
if (changed) Sched::WakeObjectWaiters((void*)&g_serial);
}
bool GetMasterMute() {
return g_masterMute;
return g_masterMute.load(std::memory_order_acquire);
}
int SetOutput(Output output) {
if (output != Output::Hda && output != Output::Bluetooth) return -1;
g_lock.Acquire();
if (g_output == output &&
!g_switchingOutput.load(std::memory_order_acquire)) {
bool ready = g_activeCount == 0 ||
(output == Output::Hda ? g_hdaOpened : g_btOpened);
if (ready) {
g_lock.Release();
return 0;
}
}
if (g_switchingOutput.load(std::memory_order_acquire)) {
g_lock.Release();
return -1;
}
g_switchingOutput = true;
bool needBackend = g_activeCount != 0;
g_lock.Release();
bool prepared = true;
int newHdaHandle = -1;
if (needBackend && output == Output::Hda) {
if (!IntelHda::IsInitialized()) {
prepared = false;
} else {
newHdaHandle = IntelHda::Open(MIX_RATE, MIX_CHANNELS, MIX_BITS);
prepared = newHdaHandle >= 0;
if (prepared) {
IntelHda::Control(
newHdaHandle, IntelHda::AUDIO_CTL_SET_VOLUME,
g_masterMute.load(std::memory_order_acquire) ? 0 : 100);
}
}
} else if (needBackend && output == Output::Bluetooth) {
prepared = BluetoothReady() &&
Drivers::USB::Bluetooth::A2dp::ConfigureStream(
MIX_RATE, MIX_CHANNELS, MIX_BITS) &&
Drivers::USB::Bluetooth::A2dp::StartStream();
if (prepared)
Drivers::USB::Bluetooth::A2dp::SetMuted(
g_masterMute.load(std::memory_order_acquire));
} else if (output == Output::Hda) {
prepared = IntelHda::IsInitialized();
} else {
prepared = BluetoothReady();
}
g_lock.Acquire();
if (!prepared) {
g_switchingOutput = false;
g_lock.Release();
Sched::WakeObjectWaiters((void*)&g_switchingOutput);
return -1;
}
Output oldOutput = g_output;
bool closeHda = oldOutput == Output::Hda && g_hdaOpened;
bool closeBt = oldOutput == Output::Bluetooth && g_btOpened;
int oldHdaHandle = g_hdaHandle;
g_output = output;
if (output == Output::Hda && needBackend) {
g_hdaHandle = newHdaHandle;
g_hdaOpened = true;
}
if (output == Output::Bluetooth && needBackend) g_btOpened = true;
if (closeHda) {
g_hdaOpened = false;
if (output != Output::Hda) g_hdaHandle = -1;
}
if (closeBt) g_btOpened = false;
// Prime the new device immediately from data already queued by
// applications, avoiding a full hardware-buffer interval of silence
// after a switch.
Pump();
g_switchingOutput = false;
BumpSerialLocked();
g_lock.Release();
Sched::WakeObjectWaiters((void*)&g_switchingOutput);
if (closeHda) IntelHda::Close(oldHdaHandle);
if (closeBt) Drivers::USB::Bluetooth::A2dp::StopStream(true);
Sched::WakeObjectWaiters((void*)&g_serial);
return 0;
}
Output GetOutput() {
g_lock.Acquire();
Output output = g_output;
g_lock.Release();
return output;
}
bool IsOutputAvailable(Output output) {
if (output == Output::Hda) return IntelHda::IsInitialized();
if (output == Output::Bluetooth) return BluetoothReady();
return false;
}
void OnBluetoothStateChanged() {
bool wake = false;
bool stopBt = false;
g_lock.Acquire();
if (g_output == Output::Bluetooth && !BluetoothReady()) {
stopBt = g_btOpened;
g_btOpened = false;
if (g_activeCount == 0 || EnsureHdaOpen()) {
g_output = Output::Hda;
if (g_hdaOpened) Pump();
}
BumpSerialLocked();
wake = true;
} else {
BumpSerialLocked();
wake = true;
}
g_lock.Release();
if (stopBt) Drivers::USB::Bluetooth::A2dp::StopStream(true);
if (wake) Sched::WakeObjectWaiters((void*)&g_serial);
}
void OnBluetoothWritable() {
g_lock.Acquire();
if (g_output == Output::Bluetooth && g_btOpened) Pump();
g_lock.Release();
}
void OnHdaBufferComplete() {
@@ -586,12 +825,12 @@ namespace Drivers::Audio::Mixer {
// HDA register access are both serialized through g_lock (which
// disables interrupts on acquire), so this is safe to call from IRQ.
g_lock.Acquire();
Pump();
if (g_output == Output::Hda) Pump();
g_lock.Release();
}
uint64_t GetSerial() {
return g_serial;
return g_serial.load(std::memory_order_acquire);
}
// BlockOnObjectIf callback: returns true (i.e. "do block") only if the
@@ -600,18 +839,20 @@ namespace Drivers::Audio::Mixer {
// read of g_serial and the scheduler dropping the process to Blocked.
struct WaitCtx { uint64_t expected; };
static bool WaitShouldBlock(void* ctx) {
return g_serial == ((WaitCtx*)ctx)->expected;
return g_serial.load(std::memory_order_acquire) ==
((WaitCtx*)ctx)->expected;
}
uint64_t Wait(uint64_t prevSerial, uint64_t timeoutMs) {
// Fast path: state already moved on, no need to enter the scheduler.
if (g_serial != prevSerial) return g_serial;
if (timeoutMs == 0) return g_serial;
uint64_t serial = g_serial.load(std::memory_order_acquire);
if (serial != prevSerial) return serial;
if (timeoutMs == 0) return serial;
WaitCtx ctx{prevSerial};
Sched::BlockOnObjectIf((void*)&g_serial, timeoutMs,
WaitShouldBlock, &ctx);
return g_serial;
return g_serial.load(std::memory_order_acquire);
}
};
+22 -3
View File
@@ -11,8 +11,8 @@
namespace Drivers::Audio::Mixer {
// Maximum simultaneous virtual streams. Each open audio handle owned by a
// process consumes one slot. Slot index doubles as the user-visible handle.
// Maximum simultaneous virtual streams. Handles include a generation, so
// a stale handle cannot affect a different stream after slot reuse.
constexpr int MAX_STREAMS = 8;
// Fixed hardware mix format. Streams opened at other rates / channel
@@ -21,7 +21,13 @@ namespace Drivers::Audio::Mixer {
constexpr uint8_t MIX_CHANNELS = 2;
constexpr uint8_t MIX_BITS = 16;
// Lazy-init: opens the underlying HDA stream on first virtual Open().
enum class Output : int {
Hda = 0,
Bluetooth = 1
};
// Opens a virtual stream. All streams are mixed together before being
// handed to the selected HDA or Bluetooth backend.
int Open(uint32_t sampleRate, uint8_t channels, uint8_t bitsPerSample,
int ownerPid, const char* ownerName);
void Close(int handle);
@@ -41,6 +47,19 @@ namespace Drivers::Audio::Mixer {
void SetMasterMute(bool muted);
bool GetMasterMute();
// Global output routing. SetOutput prepares the new backend before making
// it visible, so existing virtual streams survive a device switch.
int SetOutput(Output output);
Output GetOutput();
bool IsOutputAvailable(Output output);
// Bluetooth link state changed. Wake userspace and fall back to HDA if the
// selected sink disappeared.
void OnBluetoothStateChanged();
// Called by the Bluetooth service loop after it frees PCM queue space.
void OnBluetoothWritable();
// Called from the HDA BCIS interrupt: a buffer segment finished playing,
// refill the HW ring so audio doesn't loop stale data.
void OnHdaBufferComplete();
+96 -49
View File
@@ -65,9 +65,9 @@ namespace Drivers::USB::Bluetooth::A2dp {
// State
// =========================================================================
static State g_state = State::Idle;
static std::atomic<State> g_state{State::Idle};
static uint16_t g_sigCid = 0; // L2CAP CID for AVDTP signaling
static uint16_t g_mediaCid = 0; // L2CAP CID for AVDTP media transport
static std::atomic<uint16_t> g_mediaCid{0}; // L2CAP CID for AVDTP media transport
static uint8_t g_txLabel = 1;
static uint8_t g_remoteSeid = 0; // Remote stream endpoint ID
static uint8_t g_localSeid = 1; // Our local SEID
@@ -92,7 +92,8 @@ namespace Drivers::USB::Bluetooth::A2dp {
// SBC encoder
static Sbc::SbcEncoder g_sbcEncoder = {};
static bool g_sbcInitialized = false;
static std::atomic<bool> g_sbcInitialized{false};
static std::atomic<bool> g_routeChanged{false};
// SBC capability negotiation. An A2DP source must SetConfiguration with a
// subset of what the sink advertised in GetCapabilities -- asserting a fixed
@@ -120,6 +121,7 @@ namespace Drivers::USB::Bluetooth::A2dp {
static std::atomic<uint32_t> g_ringHead{0}; // producer: WriteAudio
static std::atomic<uint32_t> g_ringTail{0}; // consumer: PumpMedia
static std::atomic<bool> g_pumpActive{false}; // single pumper at a time
static std::atomic<bool> g_serviceActive{false}; // serialize USB event reap too
static uint32_t g_pcmRate = 48000;
static uint64_t g_clockBase = 0; // ms timestamp of the media clock zero
static uint64_t g_sentSamples = 0; // per-channel samples sent since reset
@@ -132,12 +134,8 @@ 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};
static std::atomic<bool> g_muted{false};
static std::atomic<int> g_requestedVolume{-1};
// AVDTP response tracking
static volatile bool g_avdtpResponseReady = false;
@@ -1313,6 +1311,7 @@ namespace Drivers::USB::Bluetooth::A2dp {
// with no kernel log output at all).
case AVDTP_CLOSE: {
g_state = State::Idle;
g_routeChanged.store(true, std::memory_order_release);
SendAvdtpResponse(txLabel, AVDTP_CLOSE, nullptr, 0);
KernelLogStream(WARNING, "BT-A2DP") << "Remote CLOSED stream";
break;
@@ -1327,6 +1326,7 @@ namespace Drivers::USB::Bluetooth::A2dp {
case AVDTP_ABORT: {
g_state = State::Idle;
g_routeChanged.store(true, std::memory_order_release);
SendAvdtpResponse(txLabel, AVDTP_ABORT, nullptr, 0);
KernelLogStream(WARNING, "BT-A2DP") << "Remote ABORTED stream";
break;
@@ -1353,6 +1353,13 @@ namespace Drivers::USB::Bluetooth::A2dp {
// =========================================================================
bool ConfigureStream(uint32_t sampleRate, uint8_t channels, uint8_t bitsPerSample) {
// Encoder configuration and PumpMedia both mutate the SBC encoder.
// Device switching normally configures an Open stream, but explicitly
// exclude a pumper that was already in flight.
bool expected = false;
if (!g_pumpActive.compare_exchange_strong(expected, true,
std::memory_order_acquire))
return false;
Sbc::Init(&g_sbcEncoder, sampleRate, channels, bitsPerSample);
// Override with the SBC parameters actually negotiated in
// SetConfiguration so the encoded frame headers match what the sink
@@ -1373,6 +1380,7 @@ namespace Drivers::USB::Bluetooth::A2dp {
<< (uint64_t)sampleRate << "Hz " << (uint64_t)bitsPerSample << "-bit "
<< (uint64_t)channels << "ch";
g_pumpActive.store(false, std::memory_order_release);
return true;
}
@@ -1380,19 +1388,40 @@ namespace Drivers::USB::Bluetooth::A2dp {
// StartStream / StopStream
// =========================================================================
static bool AcquireMediaService() {
for (int spin = 0; spin < 100000; spin++) {
bool expected = false;
if (g_serviceActive.compare_exchange_weak(expected, true,
std::memory_order_acquire))
return true;
asm volatile("pause" ::: "memory");
}
return false;
}
bool StartStream() {
if (!AcquireMediaService()) return false;
bool result = false;
if (g_state == State::Open || g_state == State::Configured) {
if (g_state == State::Configured) {
if (!AvdtpOpen()) return false;
if (!AvdtpOpen()) {
g_serviceActive.store(false, std::memory_order_release);
return false;
}
}
if (!AvdtpStart()) return false;
ResetMediaClock();
return true;
if (AvdtpStart()) {
ResetMediaClock();
result = true;
}
} else {
result = (g_state == State::Streaming);
}
return (g_state == State::Streaming);
g_serviceActive.store(false, std::memory_order_release);
return result;
}
bool StopStream(bool flushQueued) {
if (!AcquireMediaService()) return false;
if (g_state == State::Streaming) {
uint8_t payload[1] = {(uint8_t)(g_remoteSeid << 2)};
SendAvdtpCommand(AVDTP_SUSPEND, payload, 1);
@@ -1406,6 +1435,7 @@ namespace Drivers::USB::Bluetooth::A2dp {
g_ringTail.store(g_ringHead.load(std::memory_order_relaxed),
std::memory_order_release);
}
g_serviceActive.store(false, std::memory_order_release);
return true;
}
@@ -1521,9 +1551,8 @@ namespace Drivers::USB::Bluetooth::A2dp {
bytesPerFrame - firstPart);
g_ringTail.store(tail + bytesPerFrame, std::memory_order_release);
uint32_t numSamples = samplesPerFrame * g_sbcEncoder.Channels;
for (uint32_t i = 0; i < numSamples; i++) {
framePcm[i] = (int16_t)(((int32_t)framePcm[i] * g_volume) / 100);
if (g_muted.load(std::memory_order_acquire)) {
memset(framePcm, 0, bytesPerFrame);
}
frameLen = Sbc::Encode(&g_sbcEncoder, framePcm, &mediaPkt[off]);
@@ -1556,10 +1585,17 @@ namespace Drivers::USB::Bluetooth::A2dp {
static uint32_t rejCount = 0;
rejCount++;
if (rejCount <= 2 || (rejCount & 0x3FF) == 0) {
bool sbcInitialized =
g_sbcInitialized.load(std::memory_order_acquire);
State state = g_state.load(std::memory_order_acquire);
uint16_t mediaCid =
g_mediaCid.load(std::memory_order_acquire);
KernelLogStream(WARNING, "BT-A2DP") << "WriteAudio rejected #"
<< (uint64_t)rejCount << ": sbc=" << (uint64_t)(g_sbcInitialized ? 1 : 0)
<< " state=" << (uint64_t)(int)g_state
<< " mediaCid=" << base::hex << (uint64_t)g_mediaCid << base::dec;
<< (uint64_t)rejCount << ": sbc="
<< (uint64_t)(sbcInitialized ? 1 : 0)
<< " state=" << (uint64_t)(int)state
<< " mediaCid=" << base::hex << (uint64_t)mediaCid
<< base::dec;
}
return -1;
}
@@ -1579,14 +1615,39 @@ namespace Drivers::USB::Bluetooth::A2dp {
memcpy(&g_pcmRing[0], pcmData + firstPart, n - firstPart);
g_ringHead.store(head + n, std::memory_order_release);
// Reap events (NOCP credits, inbound traffic) and feed the link from
// syscall context too, so streaming keeps moving even when no core
// is idle.
// Event processing and SBC encoding deliberately happen in
// ServiceMedia(), after the mixer releases its lock.
return (int)n;
}
void ServiceMedia() {
if (!AcquireMediaService()) return;
Xhci::PollEvents();
Hci::DrainEvents();
PumpMedia();
g_serviceActive.store(false, std::memory_order_release);
}
return (int)n;
uint32_t GetWriteSpace() {
if (!g_sbcInitialized || g_state != State::Streaming || g_mediaCid == 0)
return 0;
uint32_t head = g_ringHead.load(std::memory_order_relaxed);
uint32_t tail = g_ringTail.load(std::memory_order_acquire);
return (PCM_RING_SIZE - (head - tail)) & ~3u;
}
void OnDisconnected(uint16_t aclHandle) {
if (aclHandle != L2cap::GetAclHandle()) return;
g_state.store(State::Idle, std::memory_order_release);
g_mediaCid.store(0, std::memory_order_release);
g_sbcInitialized.store(false, std::memory_order_release);
g_ringTail.store(g_ringHead.load(std::memory_order_relaxed),
std::memory_order_release);
g_routeChanged.store(true, std::memory_order_release);
}
bool ConsumeRouteChange() {
return g_routeChanged.exchange(false, std::memory_order_acq_rel);
}
// =========================================================================
@@ -1594,42 +1655,28 @@ namespace Drivers::USB::Bluetooth::A2dp {
// =========================================================================
State GetState() {
return g_state;
return g_state.load(std::memory_order_acquire);
}
bool IsStreaming() {
return (g_state == State::Streaming);
return g_state.load(std::memory_order_acquire) == State::Streaming;
}
int GetVolume() {
return g_volume;
}
void SetVolume(int percent) {
void RequestMasterVolume(int percent) {
if (percent < 0) percent = 0;
if (percent > 100) percent = 100;
g_volume = percent;
g_requestedVolume.store(percent, std::memory_order_release);
}
// =========================================================================
// 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 SetMuted(bool muted) {
g_muted.store(muted, std::memory_order_release);
}
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);
bool ConsumeVolumeRequest(int* percent) {
int value = g_requestedVolume.exchange(-1, std::memory_order_acq_rel);
if (value < 0) return false;
if (percent) *percent = value;
return true;
}
}
+18 -18
View File
@@ -59,36 +59,36 @@ 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);
// Free bytes in the PCM queue, aligned to complete stereo frames.
uint32_t GetWriteSpace();
// Tear down local media state after the ACL link disappears. The actual
// mixer notification is deferred out of the nested HCI receive path.
void OnDisconnected(uint16_t aclHandle);
bool ConsumeRouteChange();
// 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.
void PumpMedia();
// Reap controller events and pump media from process context. Kept
// separate from WriteAudio so the mixer never holds its spinlock across
// USB event processing or SBC encoding.
void ServiceMedia();
// Get current state
State GetState();
// Check if currently streaming
bool IsStreaming();
// Get volume (0-100)
int GetVolume();
// Queue a headset AVRCP absolute-volume request for the system mixer.
void RequestMasterVolume(int percent);
void SetMuted(bool muted);
// Set volume (0-100)
void SetVolume(int percent);
// AVRCP receive runs nested inside the transport event pump. Defer its
// master-volume request until the top-level Bluetooth service context.
bool ConsumeVolumeRequest(int* percent);
}
+3 -2
View File
@@ -7,6 +7,7 @@
#include "Avrcp.hpp"
#include "A2dp.hpp"
#include "L2cap.hpp"
#include <Drivers/Audio/Mixer.hpp>
#include <Terminal/Terminal.hpp>
#include <CppLib/Stream.hpp>
#include <Libraries/Memory.hpp>
@@ -183,7 +184,7 @@ namespace Drivers::USB::Bluetooth::Avrcp {
// follow-up on actual change is a later feature.)
if (p[0] == EVT_VOLUME_CHANGED) {
uint8_t rp[2] = {EVT_VOLUME_CHANGED,
(uint8_t)((A2dp::GetVolume() * 127) / 100)};
(uint8_t)((Drivers::Audio::Mixer::GetMasterVolume() * 127) / 100)};
SendVendorRsp(localCid, transaction, AVC_RSP_INTERIM,
pdu, rp, sizeof(rp));
} else if (p[0] == EVT_PLAYBACK_STATUS) {
@@ -203,7 +204,7 @@ namespace Drivers::USB::Bluetooth::Avrcp {
}
} else if (pdu == PDU_SET_ABS_VOLUME && ctype == AVC_CTYPE_CONTROL && plen >= 1) {
uint8_t vol = p[0] & 0x7F;
A2dp::SetVolume(((int)vol * 100) / 127);
A2dp::RequestMasterVolume(((int)vol * 100) / 127);
SendVendorRsp(localCid, transaction, AVC_RSP_ACCEPTED,
pdu, &vol, 1);
KernelLogStream(INFO, "BT-AVRCP") << "absolute volume -> "
@@ -8,6 +8,7 @@
#include "Hci.hpp"
#include "A2dp.hpp"
#include "IntelFirmware.hpp"
#include <Drivers/Audio/Mixer.hpp>
#include <Drivers/USB/Xhci.hpp>
#include <Drivers/USB/UsbDevice.hpp>
#include <Fs/Vfs.hpp>
@@ -419,10 +420,14 @@ namespace Drivers::USB::Bluetooth {
void ServiceEvents() {
if (!g_initialized) return;
if (Xhci::InPollContext()) return; // never nest under PollEvents
Xhci::PollEvents();
Hci::DrainEvents();
Hci::ProcessPendingCommands();
A2dp::PumpMedia(); // feed queued media to the link (no-op when idle)
A2dp::ServiceMedia(); // reap events and feed queued media
Drivers::Audio::Mixer::OnBluetoothWritable();
int requestedVolume;
if (A2dp::ConsumeVolumeRequest(&requestedVolume))
Drivers::Audio::Mixer::SetMasterVolume(requestedVolume);
if (A2dp::ConsumeRouteChange())
Drivers::Audio::Mixer::OnBluetoothStateChanged();
}
// =========================================================================
@@ -605,6 +610,7 @@ namespace Drivers::USB::Bluetooth {
for (int k = 0; k < 200; k++) asm volatile("pause" ::: "memory");
}
A2dp::StartSource();
Drivers::Audio::Mixer::OnBluetoothStateChanged();
}
// Persist any new link key now (process context), even if
// the link later dropped, so the disk write never stalls
+2
View File
@@ -6,6 +6,7 @@
#include "Hci.hpp"
#include "L2cap.hpp"
#include "A2dp.hpp"
#include <atomic>
#include <Fs/Vfs.hpp>
#include <Drivers/USB/Xhci.hpp>
@@ -795,6 +796,7 @@ namespace Drivers::USB::Bluetooth::Hci {
KernelLogStream(INFO, "BT-HCI") << "Disconnection: handle="
<< (uint64_t)handle << " reason=" << (uint64_t)reason;
A2dp::OnDisconnected(handle);
for (int i = 0; i < MAX_CONNECTIONS; i++) {
if (g_connections[i].Active && g_connections[i].Handle == handle) {
-6
View File
@@ -24,7 +24,6 @@
#include <Api/Heap.hpp>
#include <Api/LibSyscall.hpp>
#include <Drivers/Audio/Mixer.hpp>
#include <Drivers/USB/Bluetooth/A2dp.hpp>
#include <Drivers/Graphics/IntelGPU.hpp>
#include <Ipc/Ipc.hpp>
@@ -1241,11 +1240,6 @@ namespace Sched {
// and its ring buffer don't leak when an app forgets to audio_close.
Drivers::Audio::Mixer::CleanupProcess(exitingPid);
// Release the Bluetooth A2DP output if this process owned it, so a
// killed app cannot leave the output claimed forever (no-op when the
// process was not the owner).
Drivers::USB::Bluetooth::A2dp::ReleaseOutput(exitingPid);
// Restore scanout to buffer 0 if the exiting process owned page
// flips, so the next fullscreen client and the kernel terminal are
// never stranded on the invisible buffer (no-op for non-owners).