feat: audio stack performance improvements
This commit is contained in:
@@ -1074,6 +1074,22 @@ namespace Drivers::Audio::IntelHda {
|
|||||||
KernelLogStream(OK, "HDA") << "Stream closed";
|
KernelLogStream(OK, "HDA") << "Stream closed";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint32_t GetWriteSpace(int handle) {
|
||||||
|
if (handle != 0 || !g_stream.Active) return 0;
|
||||||
|
|
||||||
|
uint32_t hwPos = g_dmaPos[g_stream.StreamIndex * 2];
|
||||||
|
uint32_t writePos = g_stream.WritePos;
|
||||||
|
uint32_t available;
|
||||||
|
if (writePos >= hwPos) {
|
||||||
|
available = TOTAL_BUFFER_SIZE - (writePos - hwPos);
|
||||||
|
} else {
|
||||||
|
available = hwPos - writePos;
|
||||||
|
}
|
||||||
|
if (available > 64) available -= 64;
|
||||||
|
else available = 0;
|
||||||
|
return available;
|
||||||
|
}
|
||||||
|
|
||||||
int Write(int handle, const uint8_t* data, uint32_t size) {
|
int Write(int handle, const uint8_t* data, uint32_t size) {
|
||||||
if (handle != 0 || !g_stream.Active || !data || size == 0)
|
if (handle != 0 || !g_stream.Active || !data || size == 0)
|
||||||
return -1;
|
return -1;
|
||||||
|
|||||||
@@ -274,6 +274,13 @@ namespace Drivers::Audio::IntelHda {
|
|||||||
// Returns number of bytes written (may be less than requested if buffer full).
|
// Returns number of bytes written (may be less than requested if buffer full).
|
||||||
int Write(int handle, const uint8_t* data, uint32_t size);
|
int Write(int handle, const uint8_t* data, uint32_t size);
|
||||||
|
|
||||||
|
// Free bytes in the DMA ring that a caller may write right now.
|
||||||
|
// Returns 0 if the stream is not active. The mixer uses this to size
|
||||||
|
// each pump so it never produces more than HDA can accept (otherwise
|
||||||
|
// surplus mixed data would be silently dropped and the stream readers
|
||||||
|
// would still advance, scrambling playback).
|
||||||
|
uint32_t GetWriteSpace(int handle);
|
||||||
|
|
||||||
// Control commands
|
// Control commands
|
||||||
constexpr int AUDIO_CTL_SET_VOLUME = 0; // value: 0-100
|
constexpr int AUDIO_CTL_SET_VOLUME = 0; // value: 0-100
|
||||||
constexpr int AUDIO_CTL_GET_VOLUME = 1;
|
constexpr int AUDIO_CTL_GET_VOLUME = 1;
|
||||||
|
|||||||
@@ -164,9 +164,16 @@ namespace Drivers::Audio::Mixer {
|
|||||||
static void Pump() {
|
static void Pump() {
|
||||||
if (!g_hdaOpened) return;
|
if (!g_hdaOpened) return;
|
||||||
|
|
||||||
// Ask IntelHda how much room there is. The driver clamps writes to free
|
// Produce only as many frames as the HDA DMA ring has room for right
|
||||||
// space, so we feed it a generous buffer and let it consume what it can.
|
// now. Producing more would mean the surplus is silently dropped by
|
||||||
uint32_t frames = MAX_PUMP_FRAMES;
|
// IntelHda::Write while the per-stream resamplers had already
|
||||||
|
// advanced — that's what scrambles speech into a sequence of
|
||||||
|
// unrelated chunks. Clamp to MAX_PUMP_FRAMES so the scratch buffers
|
||||||
|
// are bounded.
|
||||||
|
uint32_t freeBytes = IntelHda::GetWriteSpace(g_hdaHandle);
|
||||||
|
uint32_t frames = freeBytes / 4;
|
||||||
|
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
|
// Always write to HDA, even when no streams are active, so the
|
||||||
// hardware ring stays filled with silence instead of looping the
|
// hardware ring stays filled with silence instead of looping the
|
||||||
@@ -448,15 +455,26 @@ namespace Drivers::Audio::Mixer {
|
|||||||
void SetMasterVolume(int percent) {
|
void SetMasterVolume(int percent) {
|
||||||
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;
|
||||||
if (g_hdaOpened) {
|
int curVol = g_masterVolume;
|
||||||
IntelHda::Control(g_hdaHandle, IntelHda::AUDIO_CTL_SET_VOLUME,
|
bool curMute = g_masterMute;
|
||||||
g_masterMute ? 0 : g_masterVolume);
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -468,12 +486,16 @@ 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;
|
||||||
if (g_hdaOpened) {
|
int curVol = g_masterVolume;
|
||||||
IntelHda::Control(g_hdaHandle, IntelHda::AUDIO_CTL_SET_VOLUME,
|
bool curMute = g_masterMute;
|
||||||
g_masterMute ? 0 : g_masterVolume);
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -180,6 +180,7 @@ static void refresh_master() {
|
|||||||
static void apply_master_volume(int v) {
|
static void apply_master_volume(int v) {
|
||||||
if (v < 0) v = 0;
|
if (v < 0) v = 0;
|
||||||
if (v > 100) v = 100;
|
if (v > 100) v = 100;
|
||||||
|
if (v == g_master_vol) return; // No change → skip syscall + HW codec hit.
|
||||||
g_master_vol = v;
|
g_master_vol = v;
|
||||||
montauk::audio_set_master_volume(v);
|
montauk::audio_set_master_volume(v);
|
||||||
}
|
}
|
||||||
@@ -198,6 +199,7 @@ static void apply_stream_volume(int idx, int v) {
|
|||||||
if (idx < 0 || idx >= g_stream_count) return;
|
if (idx < 0 || idx >= g_stream_count) return;
|
||||||
if (v < 0) v = 0;
|
if (v < 0) v = 0;
|
||||||
if (v > 100) v = 100;
|
if (v > 100) v = 100;
|
||||||
|
if ((uint8_t)v == g_streams[idx].volume) return;
|
||||||
g_streams[idx].volume = (uint8_t)v;
|
g_streams[idx].volume = (uint8_t)v;
|
||||||
montauk::audio_set_volume(g_streams[idx].handle, v);
|
montauk::audio_set_volume(g_streams[idx].handle, v);
|
||||||
}
|
}
|
||||||
@@ -433,13 +435,16 @@ extern "C" void _start() {
|
|||||||
|
|
||||||
// Event-driven sync: the kernel bumps a mixer serial on every state
|
// Event-driven sync: the kernel bumps a mixer serial on every state
|
||||||
// change. Reading it is one cheap syscall; we only re-snapshot the
|
// change. Reading it is one cheap syscall; we only re-snapshot the
|
||||||
// full mixer state when the serial has actually moved. The master
|
// full mixer state when the serial has actually moved. Only redraw
|
||||||
// value is left alone while the user is dragging the master slider
|
// when the snapshot differs from what we already show, otherwise
|
||||||
// so a mid-drag wakeup can't snap the knob to the kernel value.
|
// self-driven bumps (e.g. the user dragging our own slider) would
|
||||||
|
// cause a redraw on every drag tick.
|
||||||
uint64_t now_serial = montauk::audio_wait(g_mixer_serial, 0);
|
uint64_t now_serial = montauk::audio_wait(g_mixer_serial, 0);
|
||||||
if (now_serial != g_mixer_serial) {
|
if (now_serial != g_mixer_serial) {
|
||||||
g_mixer_serial = now_serial;
|
g_mixer_serial = now_serial;
|
||||||
|
|
||||||
|
// Master: only refresh when we're not dragging master ourselves,
|
||||||
|
// otherwise a wake mid-drag could snap our knob to the kernel.
|
||||||
if (g_drag_target != -1) {
|
if (g_drag_target != -1) {
|
||||||
int prev_vol = g_master_vol;
|
int prev_vol = g_master_vol;
|
||||||
bool prev_muted = g_master_muted;
|
bool prev_muted = g_master_muted;
|
||||||
@@ -448,8 +453,29 @@ extern "C" void _start() {
|
|||||||
redraw = true;
|
redraw = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Per-stream: diff before redrawing so dragging the master slider
|
||||||
|
// (which bumps the serial) doesn't trigger redundant repaints.
|
||||||
|
int prev_count = g_stream_count;
|
||||||
|
uint8_t prev_vols[8] = {0};
|
||||||
|
uint8_t prev_mutes[8] = {0};
|
||||||
|
int prev_handles[8] = {0};
|
||||||
|
for (int i = 0; i < prev_count && i < 8; i++) {
|
||||||
|
prev_vols[i] = g_streams[i].volume;
|
||||||
|
prev_mutes[i] = g_streams[i].muted;
|
||||||
|
prev_handles[i] = g_streams[i].handle;
|
||||||
|
}
|
||||||
refresh_streams();
|
refresh_streams();
|
||||||
redraw = true;
|
if (g_stream_count != prev_count) {
|
||||||
|
redraw = true;
|
||||||
|
} else {
|
||||||
|
for (int i = 0; i < g_stream_count; i++) {
|
||||||
|
if (g_streams[i].handle != prev_handles[i] ||
|
||||||
|
g_streams[i].volume != prev_vols[i] ||
|
||||||
|
g_streams[i].muted != prev_mutes[i]) {
|
||||||
|
redraw = true; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (r < 0) break;
|
if (r < 0) break;
|
||||||
|
|||||||
Reference in New Issue
Block a user