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 <Terminal/Terminal.hpp>
#include <CppLib/Stream.hpp>
#include <CppLib/Spinlock.hpp>
#include <Libraries/Memory.hpp>
namespace Drivers::Audio::IntelHda {
@@ -24,6 +25,7 @@ namespace Drivers::Audio::IntelHda {
// =========================================================================
static bool g_initialized = false;
static kcp::Spinlock g_codecLock;
static volatile uint8_t* g_mmioBase = nullptr;
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
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;
SendVerb(fullVerb);
uint32_t response = 0;
if (!ReadResponse(&response, nullptr)) {
g_codecLock.Release();
KernelLogStream(WARNING, "HDA") << "Verb timeout: codec=" << base::dec
<< (uint64_t)codec << " nid=" << (uint64_t)nid
<< " verb=" << base::hex << (uint64_t)verb;
return 0;
}
g_codecLock.Release();
return response;
}
@@ -652,6 +658,7 @@ namespace Drivers::Audio::IntelHda {
// never runs and unsolicited responses (jack events) pile up unread.
// This consumes them and sets g_jackEventPending if any are found.
static void DrainUnsolicitedResponses() {
g_codecLock.Acquire();
for (;;) {
uint16_t wp = Read16(REG_RIRBWP) & 0xFF;
if (g_rirbReadPtr == wp)
@@ -666,6 +673,7 @@ namespace Drivers::Audio::IntelHda {
g_rirbReadPtr = next;
g_jackEventPending = true;
}
g_codecLock.Release();
}
// 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 bool g_masterMute = false;
static int g_activeCount = 0;
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
@@ -113,11 +114,34 @@ namespace Drivers::Audio::Mixer {
if (!IntelHda::IsInitialized()) return false;
g_hdaHandle = IntelHda::Open(MIX_RATE, MIX_CHANNELS, MIX_BITS);
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;
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
// the stream's native rate, then push into its ring. Caller holds g_lock.
// 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
// (free space in the ring, minus a small guard), then for each active
// stream resample and mix it into the scratch buffer. Finally hand the
// result to IntelHda::Write().
// (free space in the ring, minus a small guard), then cap that to the
// amount of stream data actually available. Muted and zero-volume streams
// 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() {
if (!g_hdaOpened) return;
@@ -175,11 +218,27 @@ namespace Drivers::Audio::Mixer {
if (frames == 0) return;
if (frames > MAX_PUMP_FRAMES) frames = MAX_PUMP_FRAMES;
// Always write to HDA, even when no streams are active, so the
// hardware ring stays filled with silence instead of looping the
// last mixed audio. Without this, closing the last app would leave
// its tail playing on repeat.
if (g_activeCount == 0) {
uint32_t streamFrames = 0;
bool hasUnpaused = false;
bool hasAudible = false;
for (int i = 0; i < MAX_STREAMS; i++) {
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));
IntelHda::Write(g_hdaHandle, (const uint8_t*)g_outScratch, frames * 4);
return;
@@ -190,17 +249,18 @@ namespace Drivers::Audio::Mixer {
for (int i = 0; i < MAX_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
// of the *next* input frame to consume.
uint64_t pos = s.posQ32;
uint64_t step = s.stepQ32;
uint32_t available = s.writeFrame - s.readFrame;
if (available == 0) continue;
if (available < 2) continue;
// Per-stream gain in Q15 (post-master mix).
int32_t gain = (int32_t)s.volume; // 0..100
bool emit = !s.muted && gain > 0;
for (uint32_t f = 0; f < frames; f++) {
uint64_t intPart = pos >> 32;
@@ -221,12 +281,14 @@ namespace Drivers::Audio::Mixer {
int32_t l = (l0 * inv + l1 * frac) >> 16;
int32_t r = (r0 * inv + r1 * frac) >> 16;
// Apply per-stream volume.
l = (l * gain) / 100;
r = (r * gain) / 100;
if (emit) {
// Apply per-stream volume.
l = (l * gain) / 100;
r = (r * gain) / 100;
g_mixScratch[f * 2 + 0] += l;
g_mixScratch[f * 2 + 1] += r;
g_mixScratch[f * 2 + 0] += l;
g_mixScratch[f * 2 + 1] += r;
}
pos += step;
}
@@ -456,25 +518,12 @@ namespace Drivers::Audio::Mixer {
if (percent < 0) percent = 0;
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();
bool changed = (g_masterVolume != percent);
g_masterVolume = percent;
int curVol = g_masterVolume;
bool curMute = g_masterMute;
bool hdaOpen = g_hdaOpened;
if (changed) BumpSerialLocked();
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);
}
@@ -486,16 +535,13 @@ namespace Drivers::Audio::Mixer {
g_lock.Acquire();
bool changed = (g_masterMute != muted);
g_masterMute = muted;
int curVol = g_masterVolume;
bool curMute = g_masterMute;
bool hdaOpen = g_hdaOpened;
if (changed) BumpSerialLocked();
if (changed) {
g_masterHwSeq++;
BumpSerialLocked();
}
g_lock.Release();
if (changed && hdaOpen) {
IntelHda::Control(g_hdaHandle, IntelHda::AUDIO_CTL_SET_VOLUME,
curMute ? 0 : curVol);
}
if (changed) SyncHdaMasterMute();
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);
if (v < 0) v = 0;
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;
montauk::audio_set_master_volume(v);
return;
@@ -420,7 +423,10 @@ void gui::desktop_handle_mouse(DesktopState* ds) {
int v = ((mx - slider_abs_x) * 100) / slider_w;
if (v < 0) v = 0;
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;
montauk::audio_set_master_volume(v);
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 (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;
if (v < 0) v = 0;
ds->vol_level = v;
@@ -447,7 +456,10 @@ void gui::desktop_handle_mouse(DesktopState* ds) {
bx += minus_w + gap;
// [+]
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;
if (v > 100) v = 100;
ds->vol_level = v;