From 360d5dddaa8135f98aacaded0928c542caa073af Mon Sep 17 00:00:00 2001 From: evgeny Date: Fri, 16 Oct 2026 17:08:55 +0200 Subject: [PATCH] Expose WASAPI and Linux audio buffer glitches to device owners --- lib/miniaudio.h | 16 ++++++++++++++-- tools/chatgui/transport/miniaudio_impl.c | 18 +++++++++++------- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/lib/miniaudio.h b/lib/miniaudio.h index 6ac3795f..bbc84668 100644 --- a/lib/miniaudio.h +++ b/lib/miniaudio.h @@ -6780,7 +6780,9 @@ typedef enum ma_device_notification_type_rerouted, ma_device_notification_type_interruption_began, ma_device_notification_type_interruption_ended, - ma_device_notification_type_unlocked + ma_device_notification_type_unlocked, + ma_device_notification_type_capture_overrun, /* uTun: подтверждённый backend разрыв входного PCM. */ + ma_device_notification_type_playback_underrun /* uTun: backend исчерпал буфер воспроизведения. */ } ma_device_notification_type; typedef struct @@ -20216,7 +20218,13 @@ static void ma_device__on_notification(ma_device_notification notification) { MA_ASSERT(notification.pDevice != NULL); - MA_DIAGNOSTIC_EVENT(notification.pDevice, AUDIO_DIAG_NOTIFY, -1, notification.type, ma_device_get_state(notification.pDevice)); + if (notification.type == ma_device_notification_type_capture_overrun) { + MA_DIAGNOSTIC_EVENT(notification.pDevice, AUDIO_DIAG_BACKEND_OVERFLOW, AUDIO_DIAG_READ, 1, 0); + } else if (notification.type == ma_device_notification_type_playback_underrun) { + MA_DIAGNOSTIC_EVENT(notification.pDevice, AUDIO_DIAG_BACKEND_UNDERRUN, AUDIO_DIAG_WRITE, 1, 0); + } else { + MA_DIAGNOSTIC_EVENT(notification.pDevice, AUDIO_DIAG_NOTIFY, -1, notification.type, ma_device_get_state(notification.pDevice)); + } if (notification.pDevice->onNotification != NULL) { notification.pDevice->onNotification(¬ification); @@ -24824,6 +24832,7 @@ static ma_result ma_device_read__wasapi(ma_device* pDevice, void* pFrames, ma_ui /* Overrun detection. */ if ((flags & MA_AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY) != 0) { + ma_device__on_notification(ma_device_notification_init(pDevice, ma_device_notification_type_capture_overrun)); /* Glitched. Probably due to an overrun. */ /* @@ -29900,6 +29909,7 @@ static ma_result ma_device_read__alsa(ma_device* pDevice, void* pFramesOut, ma_u continue; /* Try again. */ } else if (resultALSA == -EPIPE) { ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "EPIPE (read)\n"); + ma_device__on_notification(ma_device_notification_init(pDevice, ma_device_notification_type_capture_overrun)); /* Overrun. Recover and try again. If this fails we need to return an error. */ resultALSA = ((ma_snd_pcm_recover_proc)pDevice->pContext->alsa.snd_pcm_recover)((ma_snd_pcm_t*)pDevice->alsa.pPCMCapture, resultALSA, MA_TRUE); @@ -29955,6 +29965,7 @@ static ma_result ma_device_write__alsa(ma_device* pDevice, const void* pFrames, continue; /* Try again. */ } else if (resultALSA == -EPIPE) { ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "EPIPE (write)\n"); + ma_device__on_notification(ma_device_notification_init(pDevice, ma_device_notification_type_playback_underrun)); /* Underrun. Recover and try again. If this fails we need to return an error. */ resultALSA = ((ma_snd_pcm_recover_proc)pDevice->pContext->alsa.snd_pcm_recover)((ma_snd_pcm_t*)pDevice->alsa.pPCMPlayback, resultALSA, MA_TRUE); /* MA_TRUE=silent (don't print anything on error). */ @@ -31839,6 +31850,7 @@ static void ma_device_on_read__pulse(ma_pa_stream* pStream, size_t byteCount, vo ma_uint8 silence[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; ma_uint64 remaining = framesMapped; ma_uint32 capacity = sizeof(silence) / bpf; + ma_device__on_notification(ma_device_notification_init(pDevice, ma_device_notification_type_capture_overrun)); ma_silence_pcm_frames(silence, capacity, pDevice->capture.internalFormat, pDevice->capture.internalChannels); while (remaining > 0 && ma_device_get_state(pDevice) == ma_device_state_started) { ma_uint32 n = (ma_uint32)ma_min(remaining, capacity); diff --git a/tools/chatgui/transport/miniaudio_impl.c b/tools/chatgui/transport/miniaudio_impl.c index 451b6745..43bc4a34 100644 --- a/tools/chatgui/transport/miniaudio_impl.c +++ b/tools/chatgui/transport/miniaudio_impl.c @@ -20,11 +20,13 @@ static void audio_capture_hole(uint64_t offset); #ifdef MA_SUPPORT_PULSEAUDIO static void audio_diag_pulse_underflow(ma_pa_stream* stream, void* userdata) { (void)stream; - audio_diag_event(userdata, AUDIO_DIAG_BACKEND_UNDERRUN, AUDIO_DIAG_WRITE, 1, 0); + ma_device__on_notification(ma_device_notification_init(userdata, ma_device_notification_type_playback_underrun)); } static void audio_diag_pulse_overflow(ma_pa_stream* stream, void* userdata) { - (void)stream; - audio_diag_event(userdata, AUDIO_DIAG_BACKEND_OVERFLOW, AUDIO_DIAG_WRITE, 1, 0); + ma_device* device = userdata; + if (stream == device->pulse.pStreamCapture) + ma_device__on_notification(ma_device_notification_init(device, ma_device_notification_type_capture_overrun)); + else audio_diag_event(userdata, AUDIO_DIAG_BACKEND_OVERFLOW, AUDIO_DIAG_WRITE, 1, 0); } static void audio_diag_pulse_started(ma_pa_stream* stream, void* userdata) { (void)stream; @@ -67,7 +69,6 @@ static void audio_diag_pulse_init(void* device) { if (dev->pContext->backend != ma_backend_pulseaudio) return; audio_diag_pulse_buffer(dev, 1); audio_diag_pulse_buffer(dev, 0); - if (!dev->pulse.pStreamPlayback) return; #ifndef MA_NO_RUNTIME_LINKING underflow = (ma_pa_stream_set_suspended_callback_proc)dlsym(dev->pContext->pulse.pulseSO, "pa_stream_set_underflow_callback"); overflow = (ma_pa_stream_set_suspended_callback_proc)dlsym(dev->pContext->pulse.pulseSO, "pa_stream_set_overflow_callback"); @@ -81,9 +82,12 @@ static void audio_diag_pulse_init(void* device) { audio_diag_log(ma_device_get_log(dev), MA_LOG_LEVEL_WARNING, "PulseAudio xrun/started callbacks unavailable"); return; } - underflow((ma_pa_stream*)dev->pulse.pStreamPlayback, audio_diag_pulse_underflow, dev); - overflow((ma_pa_stream*)dev->pulse.pStreamPlayback, audio_diag_pulse_overflow, dev); - started((ma_pa_stream*)dev->pulse.pStreamPlayback, audio_diag_pulse_started, dev); + if (dev->pulse.pStreamCapture) overflow((ma_pa_stream*)dev->pulse.pStreamCapture, audio_diag_pulse_overflow, dev); + if (dev->pulse.pStreamPlayback) { + underflow((ma_pa_stream*)dev->pulse.pStreamPlayback, audio_diag_pulse_underflow, dev); + overflow((ma_pa_stream*)dev->pulse.pStreamPlayback, audio_diag_pulse_overflow, dev); + started((ma_pa_stream*)dev->pulse.pStreamPlayback, audio_diag_pulse_started, dev); + } audio_diag_log(ma_device_get_log(dev), MA_LOG_LEVEL_INFO, "PulseAudio xrun/started observers registered"); #else (void)device;