feat: audio - add concurrent mixing, output switching, and device-aware UI
This commit is contained in:
1 parent
d99dab45e5
commit
86de3400a9
25 files changed
+688
-209
No files matched your search
+13
-72
@@ -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();
|
||||
|
||||
@@ -12,4 +12,4 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#define MONTAUK_BUILD_NUMBER 17
|
||||
#define MONTAUK_BUILD_NUMBER 20
|
||||
@@ -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;
|
||||
|
||||
Reference in new issue
Block a user