feat: 增加设备丢失自恢复机制

增加音频设备丢失自恢复机制,完善视频设备丢失自恢复机制

降级部分日志级别,GOSTC key打印脱敏

代码格式化
This commit is contained in:
mofeng-git
2026-05-02 10:54:31 +08:00
parent 52754c862b
commit 12a3f1c947
16 changed files with 929 additions and 60 deletions

View File

@@ -13,7 +13,8 @@ use tracing::{debug, error, info, trace, warn};
use super::csi_bridge;
use super::device::{
enumerate_devices, find_best_device, parse_bridge_kind, VideoDevice, VideoDeviceInfo,
enumerate_devices, find_best_device, parse_bridge_kind, select_recovery_device, VideoDevice,
VideoDeviceInfo, VideoDeviceRecoveryHint,
};
use super::format::{PixelFormat, Resolution};
use super::frame::{FrameBuffer, FrameBufferPool, VideoFrame};
@@ -366,7 +367,7 @@ impl Streamer {
// IMPORTANT: Disconnect all MJPEG clients FIRST before stopping capture
// This prevents race conditions where clients try to reconnect and reopen the device
info!("Disconnecting all MJPEG clients before config change...");
debug!("Disconnecting all MJPEG clients before config change...");
self.mjpeg_handler.disconnect_all_clients();
// Give clients time to receive the disconnect signal and close their connections
@@ -392,7 +393,7 @@ impl Streamer {
*self.state.write().await = StreamerState::Ready;
// Publish "config applied" event
info!(
debug!(
"Publishing StreamConfigApplied event: {}x{} {:?} @ {}fps",
resolution.width, resolution.height, format, fps
);
@@ -408,7 +409,7 @@ impl Streamer {
// Note: We don't auto-start here anymore.
// The stream will be started when MJPEG client connects (handlers.rs:790)
// This avoids race conditions between config change and client reconnection.
info!("Config applied, stream will start when client connects");
debug!("Config applied, stream will start when client connects");
Ok(())
}
@@ -1305,6 +1306,7 @@ impl Streamer {
{
let mut cfg = self.config.write().await;
cfg.device_path = Some(device_info.path.clone());
cfg.format = format;
cfg.resolution = resolution;
}
@@ -1392,6 +1394,20 @@ impl Streamer {
.await
.clone()
.unwrap_or_else(|| "Device lost".to_string());
let recovery_hint = self
.current_device
.read()
.await
.as_ref()
.map(VideoDeviceRecoveryHint::from)
.unwrap_or_else(|| VideoDeviceRecoveryHint {
path: PathBuf::from(&device),
name: String::new(),
driver: String::new(),
bus_info: String::new(),
card: String::new(),
is_capture_card: true,
});
// Store error info
*self.last_lost_device.write().await = Some(device.clone());
@@ -1409,7 +1425,7 @@ impl Streamer {
// Start recovery task
let streamer = Arc::clone(self);
tokio::spawn(async move {
let device_path = device.clone();
let original_device_path = device.clone();
loop {
let attempt = streamer
@@ -1433,13 +1449,13 @@ impl Streamer {
if attempt == 1 || attempt.is_multiple_of(5) {
streamer
.publish_event(SystemEvent::StreamReconnecting {
device: device_path.clone(),
device: original_device_path.clone(),
attempt,
})
.await;
info!(
"Attempting to recover video device {} (attempt {})",
device_path, attempt
original_device_path, attempt
);
}
@@ -1450,9 +1466,28 @@ impl Streamer {
};
tokio::time::sleep(wait).await;
// Check if device file exists
let device_exists = std::path::Path::new(&device_path).exists();
if !device_exists {
let devices = match enumerate_devices() {
Ok(devices) => devices,
Err(e) => {
debug!("Failed to enumerate devices during recovery: {}", e);
continue;
}
};
let Some(device) = select_recovery_device(&devices, &recovery_hint) else {
debug!("No matching video device present yet for recovery");
continue;
};
let device_path = device.path.display().to_string();
if device_path != original_device_path {
info!(
"Recovered video device path changed: {} -> {}",
original_device_path, device_path
);
}
if !std::path::Path::new(&device_path).exists() {
debug!("Device {} not present yet", device_path);
continue;
}