From ac568c7bbfeb0f43050880fec999a9ad4410ae9c Mon Sep 17 00:00:00 2001 From: Alex P Date: Fri, 21 Nov 2025 16:47:34 +0200 Subject: [PATCH] Fix HDMI hotplug crash by releasing mutex during blocking ALSA I/O When HDMI is unplugged during active audio capture, the blocking snd_pcm_readi() call was holding the mutex, preventing clean shutdown. This caused snd_pcm_drop() to race with the blocking read, leading to undefined behavior and crashes. Solution mirrors PiKVM's approach: - Release mutex before snd_pcm_readi()/snd_pcm_writei() - Reacquire mutex after I/O completes - Verify handle and stop flag before proceeding This allows snd_pcm_drop() to immediately abort pending I/O when the device is closed, ensuring clean shutdown during HDMI hotplug events. --- audio.go | 4 ++-- internal/audio/c/audio.c | 20 +++++++++++++++++--- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/audio.go b/audio.go index fbdff99a..ce88f0d4 100644 --- a/audio.go +++ b/audio.go @@ -30,9 +30,9 @@ var ( func getAlsaDevice(source string) string { if source == "hdmi" { - return "hw:0,0" // TC358743 HDMI audio + return "hw:0,0" // TC358743 HDMI audio } - return "hw:1,0" // USB Audio Gadget + return "hw:1,0" // USB Audio Gadget } func initAudio() { diff --git a/internal/audio/c/audio.c b/internal/audio/c/audio.c index 0e74d80e..abd286bb 100644 --- a/internal/audio/c/audio.c +++ b/internal/audio/c/audio.c @@ -673,10 +673,16 @@ retry_read: snd_pcm_t *handle = pcm_capture_handle; - // Read from hardware at hardware sample rate + // Release mutex before blocking I/O to allow clean shutdown + pthread_mutex_unlock(&capture_mutex); + + // Read from hardware at hardware sample rate (blocking call, no mutex held) pcm_rc = snd_pcm_readi(handle, pcm_hw_buffer, hardware_frame_size); - if (handle != pcm_capture_handle) { + // Reacquire mutex and verify device wasn't closed during read + pthread_mutex_lock(&capture_mutex); + + if (handle != pcm_capture_handle || atomic_load(&capture_stop_requested)) { pthread_mutex_unlock(&capture_mutex); return -1; } @@ -884,9 +890,17 @@ retry_write: } snd_pcm_t *handle = pcm_playback_handle; + + // Release mutex before blocking I/O to allow clean shutdown + pthread_mutex_unlock(&playback_mutex); + + // Write to hardware (blocking call, no mutex held) pcm_rc = snd_pcm_writei(handle, pcm_buffer, pcm_frames); - if (handle != pcm_playback_handle) { + // Reacquire mutex and verify device wasn't closed during write + pthread_mutex_lock(&playback_mutex); + + if (handle != pcm_playback_handle || atomic_load(&playback_stop_requested)) { pthread_mutex_unlock(&playback_mutex); return -1; }