diff --git a/playback/src/audio_backend/alsa.rs b/playback/src/audio_backend/alsa.rs index bd2b4bf5c..54abef823 100644 --- a/playback/src/audio_backend/alsa.rs +++ b/playback/src/audio_backend/alsa.rs @@ -4,7 +4,7 @@ use crate::convert::Converter; use crate::decoder::AudioPacket; use crate::{NUM_CHANNELS, SAMPLE_RATE}; use alsa::device_name::HintIter; -use alsa::pcm::{Access, Format, Frames, HwParams, PCM}; +use alsa::pcm::{Access, Format, Frames, HwParams, PCM, State}; use alsa::{Direction, ValueOr}; use std::process::exit; use thiserror::Error; @@ -442,7 +442,17 @@ impl Sink for AlsaSink { let pcm = self.pcm.take().ok_or(AlsaError::NotConnected)?; - pcm.drain().map_err(AlsaError::DrainFailure)?; + // Only drain if the PCM is in Running state. After a write error, + // try_recover() leaves the PCM in Prepared state, and calling drain() + // on a Prepared-state USB PCM can deadlock the kernel driver + // (confirmed on Raspberry Pi 3 with the dwc_otg USB controller). + // In that case, dropping the PCM is sufficient to release resources. + let state = pcm.state(); + if state == State::Running { + pcm.drain().map_err(AlsaError::DrainFailure)?; + } else { + debug!("PCM not in Running state ({state:?}), skipping drain and dropping PCM"); + } } Ok(())