[fix](trx-server): re-enumerate audio device on each stream recovery cycle
After ALSA POLLERR the existing cpal Device handle can be stale, causing repeated stream build failures with no path to recovery. Re-acquire host and device inside the outer recreation loop so each attempt gets a fresh handle. Device-not-found is now a warn+retry rather than a fatal error, allowing recovery from transient USB audio device disappearances. Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+70
-30
@@ -259,21 +259,6 @@ fn run_capture(
|
||||
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
|
||||
use std::sync::mpsc::{RecvTimeoutError, TryRecvError as StdTryRecvError};
|
||||
|
||||
let host = cpal::default_host();
|
||||
let device = if let Some(ref name) = device_name {
|
||||
host.input_devices()?
|
||||
.find(|d| d.name().map(|n| n == *name).unwrap_or(false))
|
||||
.ok_or_else(|| format!("audio input device '{}' not found", name))?
|
||||
} else {
|
||||
host.default_input_device()
|
||||
.ok_or("no default audio input device")?
|
||||
};
|
||||
|
||||
info!(
|
||||
"Audio capture: using device '{}'",
|
||||
device.name().unwrap_or_else(|_| "unknown".into())
|
||||
);
|
||||
|
||||
let config = cpal::StreamConfig {
|
||||
channels,
|
||||
sample_rate: cpal::SampleRate(sample_rate),
|
||||
@@ -304,6 +289,41 @@ fn run_capture(
|
||||
let mut capturing = false;
|
||||
|
||||
loop {
|
||||
// Re-enumerate the device on every recovery cycle: after POLLERR the
|
||||
// existing device handle can be stale (especially for USB audio).
|
||||
let host = cpal::default_host();
|
||||
let device = if let Some(ref name) = device_name {
|
||||
match host.input_devices() {
|
||||
Ok(mut devs) => {
|
||||
match devs.find(|d| d.name().map(|n| n == *name).unwrap_or(false)) {
|
||||
Some(d) => d,
|
||||
None => {
|
||||
warn!("Audio capture: device '{}' not found, retrying", name);
|
||||
std::thread::sleep(AUDIO_STREAM_RECOVERY_DELAY);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("Audio capture: failed to enumerate devices, retrying: {}", e);
|
||||
std::thread::sleep(AUDIO_STREAM_RECOVERY_DELAY);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match host.default_input_device() {
|
||||
Some(d) => d,
|
||||
None => {
|
||||
warn!("Audio capture: no default input device, retrying");
|
||||
std::thread::sleep(AUDIO_STREAM_RECOVERY_DELAY);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
};
|
||||
info!(
|
||||
"Audio capture: using device '{}'",
|
||||
device.name().unwrap_or_else(|_| "unknown".into())
|
||||
);
|
||||
let (sample_tx, sample_rx) = std::sync::mpsc::sync_channel::<Vec<f32>>(64);
|
||||
let (stream_err_tx, stream_err_rx) = std::sync::mpsc::sync_channel::<()>(1);
|
||||
let stream_failed = Arc::new(AtomicBool::new(false));
|
||||
@@ -443,21 +463,6 @@ fn run_playback(
|
||||
use std::sync::mpsc::TryRecvError as StdTryRecvError;
|
||||
use tokio::sync::mpsc::error::TryRecvError as TokioTryRecvError;
|
||||
|
||||
let host = cpal::default_host();
|
||||
let device = if let Some(ref name) = device_name {
|
||||
host.output_devices()?
|
||||
.find(|d| d.name().map(|n| n == *name).unwrap_or(false))
|
||||
.ok_or_else(|| format!("audio output device '{}' not found", name))?
|
||||
} else {
|
||||
host.default_output_device()
|
||||
.ok_or("no default audio output device")?
|
||||
};
|
||||
|
||||
info!(
|
||||
"Audio playback: using device '{}'",
|
||||
device.name().unwrap_or_else(|_| "unknown".into())
|
||||
);
|
||||
|
||||
let config = cpal::StreamConfig {
|
||||
channels,
|
||||
sample_rate: cpal::SampleRate(sample_rate),
|
||||
@@ -489,6 +494,41 @@ fn run_playback(
|
||||
let mut channel_closed = false;
|
||||
|
||||
loop {
|
||||
// Re-enumerate the device on every recovery cycle: after POLLERR the
|
||||
// existing device handle can be stale (especially for USB audio).
|
||||
let host = cpal::default_host();
|
||||
let device = if let Some(ref name) = device_name {
|
||||
match host.output_devices() {
|
||||
Ok(mut devs) => {
|
||||
match devs.find(|d| d.name().map(|n| n == *name).unwrap_or(false)) {
|
||||
Some(d) => d,
|
||||
None => {
|
||||
warn!("Audio playback: device '{}' not found, retrying", name);
|
||||
std::thread::sleep(AUDIO_STREAM_RECOVERY_DELAY);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("Audio playback: failed to enumerate devices, retrying: {}", e);
|
||||
std::thread::sleep(AUDIO_STREAM_RECOVERY_DELAY);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match host.default_output_device() {
|
||||
Some(d) => d,
|
||||
None => {
|
||||
warn!("Audio playback: no default output device, retrying");
|
||||
std::thread::sleep(AUDIO_STREAM_RECOVERY_DELAY);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
};
|
||||
info!(
|
||||
"Audio playback: using device '{}'",
|
||||
device.name().unwrap_or_else(|_| "unknown".into())
|
||||
);
|
||||
let (stream_err_tx, stream_err_rx) = std::sync::mpsc::sync_channel::<()>(1);
|
||||
let stream_failed = Arc::new(AtomicBool::new(false));
|
||||
let stream = match device.build_output_stream(
|
||||
|
||||
Reference in New Issue
Block a user