fix: audio stack bug fixes

This commit is contained in:
2026-05-19 18:36:22 +02:00
parent 79284083ca
commit fa3a6d30f1
3 changed files with 107 additions and 41 deletions
+8
View File
@@ -13,6 +13,7 @@
#include <Hal/Apic/IoApic.hpp> #include <Hal/Apic/IoApic.hpp>
#include <Terminal/Terminal.hpp> #include <Terminal/Terminal.hpp>
#include <CppLib/Stream.hpp> #include <CppLib/Stream.hpp>
#include <CppLib/Spinlock.hpp>
#include <Libraries/Memory.hpp> #include <Libraries/Memory.hpp>
namespace Drivers::Audio::IntelHda { namespace Drivers::Audio::IntelHda {
@@ -24,6 +25,7 @@ namespace Drivers::Audio::IntelHda {
// ========================================================================= // =========================================================================
static bool g_initialized = false; static bool g_initialized = false;
static kcp::Spinlock g_codecLock;
static volatile uint8_t* g_mmioBase = nullptr; static volatile uint8_t* g_mmioBase = nullptr;
static uint8_t g_bus, g_dev, g_func; static uint8_t g_bus, g_dev, g_func;
@@ -199,16 +201,20 @@ namespace Drivers::Audio::IntelHda {
// Send a verb and wait for the response // Send a verb and wait for the response
static uint32_t CodecCommand(uint8_t codec, uint8_t nid, uint32_t verb) { static uint32_t CodecCommand(uint8_t codec, uint8_t nid, uint32_t verb) {
g_codecLock.Acquire();
uint32_t fullVerb = ((uint32_t)codec << 28) | ((uint32_t)nid << 20) | verb; uint32_t fullVerb = ((uint32_t)codec << 28) | ((uint32_t)nid << 20) | verb;
SendVerb(fullVerb); SendVerb(fullVerb);
uint32_t response = 0; uint32_t response = 0;
if (!ReadResponse(&response, nullptr)) { if (!ReadResponse(&response, nullptr)) {
g_codecLock.Release();
KernelLogStream(WARNING, "HDA") << "Verb timeout: codec=" << base::dec KernelLogStream(WARNING, "HDA") << "Verb timeout: codec=" << base::dec
<< (uint64_t)codec << " nid=" << (uint64_t)nid << (uint64_t)codec << " nid=" << (uint64_t)nid
<< " verb=" << base::hex << (uint64_t)verb; << " verb=" << base::hex << (uint64_t)verb;
return 0; return 0;
} }
g_codecLock.Release();
return response; return response;
} }
@@ -652,6 +658,7 @@ namespace Drivers::Audio::IntelHda {
// never runs and unsolicited responses (jack events) pile up unread. // never runs and unsolicited responses (jack events) pile up unread.
// This consumes them and sets g_jackEventPending if any are found. // This consumes them and sets g_jackEventPending if any are found.
static void DrainUnsolicitedResponses() { static void DrainUnsolicitedResponses() {
g_codecLock.Acquire();
for (;;) { for (;;) {
uint16_t wp = Read16(REG_RIRBWP) & 0xFF; uint16_t wp = Read16(REG_RIRBWP) & 0xFF;
if (g_rirbReadPtr == wp) if (g_rirbReadPtr == wp)
@@ -666,6 +673,7 @@ namespace Drivers::Audio::IntelHda {
g_rirbReadPtr = next; g_rirbReadPtr = next;
g_jackEventPending = true; g_jackEventPending = true;
} }
g_codecLock.Release();
} }
// Poll jack state; call when an unsolicited response fires. // Poll jack state; call when an unsolicited response fires.
+83 -37
View File
@@ -69,6 +69,7 @@ namespace Drivers::Audio::Mixer {
static int g_masterVolume = 80; static int g_masterVolume = 80;
static bool g_masterMute = false; static bool g_masterMute = false;
static int g_activeCount = 0; static int g_activeCount = 0;
static uint64_t g_masterHwSeq = 0;
// Monotonically increasing serial. Bumped (and waiters woken) on every // Monotonically increasing serial. Bumped (and waiters woken) on every
// mutation of mixer state. Clients use it to detect changes without // mutation of mixer state. Clients use it to detect changes without
@@ -113,11 +114,34 @@ namespace Drivers::Audio::Mixer {
if (!IntelHda::IsInitialized()) return false; if (!IntelHda::IsInitialized()) return false;
g_hdaHandle = IntelHda::Open(MIX_RATE, MIX_CHANNELS, MIX_BITS); g_hdaHandle = IntelHda::Open(MIX_RATE, MIX_CHANNELS, MIX_BITS);
if (g_hdaHandle < 0) return false; if (g_hdaHandle < 0) return false;
IntelHda::Control(g_hdaHandle, IntelHda::AUDIO_CTL_SET_VOLUME, g_masterMute ? 0 : g_masterVolume); // 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);
g_hdaOpened = true; g_hdaOpened = true;
return true; return true;
} }
static void SyncHdaMasterMute() {
for (;;) {
g_lock.Acquire();
bool hdaOpen = g_hdaOpened;
int handle = g_hdaHandle;
bool muted = g_masterMute;
uint64_t seq = g_masterHwSeq;
g_lock.Release();
if (!hdaOpen) return;
IntelHda::Control(handle, IntelHda::AUDIO_CTL_SET_VOLUME,
muted ? 0 : 100);
g_lock.Acquire();
bool done = (seq == g_masterHwSeq);
g_lock.Release();
if (done) return;
}
}
// Convert one chunk of raw input bytes from a stream into int16 stereo at // Convert one chunk of raw input bytes from a stream into int16 stereo at
// the stream's native rate, then push into its ring. Caller holds g_lock. // the stream's native rate, then push into its ring. Caller holds g_lock.
// Returns the number of *input bytes* successfully ingested. // Returns the number of *input bytes* successfully ingested.
@@ -157,9 +181,28 @@ namespace Drivers::Audio::Mixer {
// ========================================================================= // =========================================================================
// //
// Compute the number of frames we can safely write to the HDA DMA buffer // Compute the number of frames we can safely write to the HDA DMA buffer
// (free space in the ring, minus a small guard), then for each active // (free space in the ring, minus a small guard), then cap that to the
// stream resample and mix it into the scratch buffer. Finally hand the // amount of stream data actually available. Muted and zero-volume streams
// result to IntelHda::Write(). // still advance their read cursors so they cannot build up stale audio.
// Finally hand the result to IntelHda::Write().
static uint32_t ProducibleFrames(const VirtualStream& s, uint32_t maxFrames) {
if (!s.active || s.paused) return 0;
uint32_t available = s.writeFrame - s.readFrame;
if (available < 2) return 0;
uint64_t pos = s.posQ32;
uint64_t step = s.stepQ32;
uint32_t frames = 0;
while (frames < maxFrames) {
uint64_t intPart = pos >> 32;
if (intPart + 1 >= available) break;
frames++;
pos += step;
}
return frames;
}
static void Pump() { static void Pump() {
if (!g_hdaOpened) return; if (!g_hdaOpened) return;
@@ -175,11 +218,27 @@ namespace Drivers::Audio::Mixer {
if (frames == 0) return; if (frames == 0) return;
if (frames > MAX_PUMP_FRAMES) frames = MAX_PUMP_FRAMES; if (frames > MAX_PUMP_FRAMES) frames = MAX_PUMP_FRAMES;
// Always write to HDA, even when no streams are active, so the uint32_t streamFrames = 0;
// hardware ring stays filled with silence instead of looping the bool hasUnpaused = false;
// last mixed audio. Without this, closing the last app would leave bool hasAudible = false;
// its tail playing on repeat. for (int i = 0; i < MAX_STREAMS; i++) {
if (g_activeCount == 0) { VirtualStream& s = g_streams[i];
if (!s.active || s.paused) continue;
hasUnpaused = true;
if (!s.muted && s.volume > 0) hasAudible = true;
uint32_t n = ProducibleFrames(s, frames);
if (n > streamFrames) streamFrames = n;
}
// Write silence intentionally when there is no stream data to mix:
// either the mixer is idle, every stream is paused/muted/silent, or an
// audible producer has genuinely under-run. When data exists, write no
// more than the longest available stream window so we don't append a
// large zero tail after a small real-time write.
bool silenceOnly = (streamFrames == 0);
if (!silenceOnly) {
frames = streamFrames;
} else if (g_activeCount == 0 || !hasAudible || !hasUnpaused) {
memset(g_outScratch, 0, frames * 2 * sizeof(int16_t)); memset(g_outScratch, 0, frames * 2 * sizeof(int16_t));
IntelHda::Write(g_hdaHandle, (const uint8_t*)g_outScratch, frames * 4); IntelHda::Write(g_hdaHandle, (const uint8_t*)g_outScratch, frames * 4);
return; return;
@@ -190,17 +249,18 @@ namespace Drivers::Audio::Mixer {
for (int i = 0; i < MAX_STREAMS; i++) { for (int i = 0; i < MAX_STREAMS; i++) {
VirtualStream& s = g_streams[i]; VirtualStream& s = g_streams[i];
if (!s.active || s.paused || s.muted || s.volume == 0) continue; if (!s.active || s.paused) continue;
// posQ32 is the fractional read cursor; integer part is the index // posQ32 is the fractional read cursor; integer part is the index
// of the *next* input frame to consume. // of the *next* input frame to consume.
uint64_t pos = s.posQ32; uint64_t pos = s.posQ32;
uint64_t step = s.stepQ32; uint64_t step = s.stepQ32;
uint32_t available = s.writeFrame - s.readFrame; uint32_t available = s.writeFrame - s.readFrame;
if (available == 0) continue; if (available < 2) continue;
// Per-stream gain in Q15 (post-master mix). // Per-stream gain in Q15 (post-master mix).
int32_t gain = (int32_t)s.volume; // 0..100 int32_t gain = (int32_t)s.volume; // 0..100
bool emit = !s.muted && gain > 0;
for (uint32_t f = 0; f < frames; f++) { for (uint32_t f = 0; f < frames; f++) {
uint64_t intPart = pos >> 32; uint64_t intPart = pos >> 32;
@@ -221,12 +281,14 @@ namespace Drivers::Audio::Mixer {
int32_t l = (l0 * inv + l1 * frac) >> 16; int32_t l = (l0 * inv + l1 * frac) >> 16;
int32_t r = (r0 * inv + r1 * frac) >> 16; int32_t r = (r0 * inv + r1 * frac) >> 16;
// Apply per-stream volume. if (emit) {
l = (l * gain) / 100; // Apply per-stream volume.
r = (r * gain) / 100; l = (l * gain) / 100;
r = (r * gain) / 100;
g_mixScratch[f * 2 + 0] += l; g_mixScratch[f * 2 + 0] += l;
g_mixScratch[f * 2 + 1] += r; g_mixScratch[f * 2 + 1] += r;
}
pos += step; pos += step;
} }
@@ -456,25 +518,12 @@ namespace Drivers::Audio::Mixer {
if (percent < 0) percent = 0; if (percent < 0) percent = 0;
if (percent > 100) percent = 100; if (percent > 100) percent = 100;
// Take the mixer lock just long enough to update software state and
// capture a snapshot for the HW write. The IntelHda codec command
// path busy-waits on the RIRB for a few hundred microseconds, so
// doing it inside the lock would freeze interrupts (BCIS, scheduler,
// input) for the whole duration of every drag tick — visible as
// slider lag.
g_lock.Acquire(); g_lock.Acquire();
bool changed = (g_masterVolume != percent); bool changed = (g_masterVolume != percent);
g_masterVolume = percent; g_masterVolume = percent;
int curVol = g_masterVolume;
bool curMute = g_masterMute;
bool hdaOpen = g_hdaOpened;
if (changed) BumpSerialLocked(); if (changed) BumpSerialLocked();
g_lock.Release(); g_lock.Release();
if (changed && hdaOpen) {
IntelHda::Control(g_hdaHandle, IntelHda::AUDIO_CTL_SET_VOLUME,
curMute ? 0 : curVol);
}
if (changed) Sched::WakeObjectWaiters((void*)&g_serial); if (changed) Sched::WakeObjectWaiters((void*)&g_serial);
} }
@@ -486,16 +535,13 @@ namespace Drivers::Audio::Mixer {
g_lock.Acquire(); g_lock.Acquire();
bool changed = (g_masterMute != muted); bool changed = (g_masterMute != muted);
g_masterMute = muted; g_masterMute = muted;
int curVol = g_masterVolume; if (changed) {
bool curMute = g_masterMute; g_masterHwSeq++;
bool hdaOpen = g_hdaOpened; BumpSerialLocked();
if (changed) BumpSerialLocked(); }
g_lock.Release(); g_lock.Release();
if (changed && hdaOpen) { if (changed) SyncHdaMasterMute();
IntelHda::Control(g_hdaHandle, IntelHda::AUDIO_CTL_SET_VOLUME,
curMute ? 0 : curVol);
}
if (changed) Sched::WakeObjectWaiters((void*)&g_serial); if (changed) Sched::WakeObjectWaiters((void*)&g_serial);
} }
+16 -4
View File
@@ -399,7 +399,10 @@ void gui::desktop_handle_mouse(DesktopState* ds) {
int v = ((mx - slider_abs_x) * 100) / (200 - 32); int v = ((mx - slider_abs_x) * 100) / (200 - 32);
if (v < 0) v = 0; if (v < 0) v = 0;
if (v > 100) v = 100; if (v > 100) v = 100;
ds->vol_muted = false; if (ds->vol_muted) {
ds->vol_muted = false;
montauk::audio_set_master_mute(false);
}
ds->vol_level = v; ds->vol_level = v;
montauk::audio_set_master_volume(v); montauk::audio_set_master_volume(v);
return; return;
@@ -420,7 +423,10 @@ void gui::desktop_handle_mouse(DesktopState* ds) {
int v = ((mx - slider_abs_x) * 100) / slider_w; int v = ((mx - slider_abs_x) * 100) / slider_w;
if (v < 0) v = 0; if (v < 0) v = 0;
if (v > 100) v = 100; if (v > 100) v = 100;
ds->vol_muted = false; if (ds->vol_muted) {
ds->vol_muted = false;
montauk::audio_set_master_mute(false);
}
ds->vol_level = v; ds->vol_level = v;
montauk::audio_set_master_volume(v); montauk::audio_set_master_volume(v);
ds->vol_dragging = true; ds->vol_dragging = true;
@@ -437,7 +443,10 @@ void gui::desktop_handle_mouse(DesktopState* ds) {
if (my >= btn_y && my < btn_y + btn_h) { if (my >= btn_y && my < btn_y + btn_h) {
// [-] // [-]
if (mx >= bx && mx < bx + minus_w) { if (mx >= bx && mx < bx + minus_w) {
ds->vol_muted = false; if (ds->vol_muted) {
ds->vol_muted = false;
montauk::audio_set_master_mute(false);
}
int v = ds->vol_level - 5; int v = ds->vol_level - 5;
if (v < 0) v = 0; if (v < 0) v = 0;
ds->vol_level = v; ds->vol_level = v;
@@ -447,7 +456,10 @@ void gui::desktop_handle_mouse(DesktopState* ds) {
bx += minus_w + gap; bx += minus_w + gap;
// [+] // [+]
if (mx >= bx && mx < bx + plus_w) { if (mx >= bx && mx < bx + plus_w) {
ds->vol_muted = false; if (ds->vol_muted) {
ds->vol_muted = false;
montauk::audio_set_master_mute(false);
}
int v = ds->vol_level + 5; int v = ds->vol_level + 5;
if (v > 100) v = 100; if (v > 100) v = 100;
ds->vol_level = v; ds->vol_level = v;