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