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

This commit is contained in:
daniel committed 2026-07-29 20:03:35 +01:00
1 parent d99dab45e5
commit 86de3400a9
25 files changed
+688 -209

No files matched your search

+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;