fix: audio stack bug fixes
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user