feat: 支持 MJPEG 解码与 MSD 目录配置

- FFmpeg/hwcodec 增加 RKMPP MJPEG 解码与 RAM FFI,ARM 构建启用对应解码器
  - 共享视频管线新增 MJPEG 解码路径(RKMPP/TurboJPEG),优化 WebRTC 发送与 MJPEG 去重
  - MSD 配置改为 msd_dir 并自动创建子目录,接口与前端设置同步更新
  - 更新包依赖与版本号
This commit is contained in:
mofeng-git
2026-01-11 16:32:37 +08:00
parent 0f52168e75
commit 01e01430da
30 changed files with 1185 additions and 260 deletions

View File

@@ -220,11 +220,8 @@ pub async fn apply_hid_config(
// Get MSD config from store
let config = state.config.get();
let msd = crate::msd::MsdController::new(
state.otg_service.clone(),
&config.msd.images_path,
&config.msd.drive_path,
);
let msd =
crate::msd::MsdController::new(state.otg_service.clone(), config.msd.msd_dir_path());
if let Err(e) = msd.init().await {
tracing::warn!("Failed to auto-initialize MSD for OTG: {}", e);
@@ -253,51 +250,73 @@ pub async fn apply_msd_config(
// Check if MSD enabled state changed
let old_msd_enabled = old_config.enabled;
let new_msd_enabled = new_config.enabled;
let msd_dir_changed = old_config.msd_dir != new_config.msd_dir;
tracing::info!(
"MSD enabled: old={}, new={}",
old_msd_enabled,
new_msd_enabled
);
if msd_dir_changed {
tracing::info!("MSD directory changed: {}", new_config.msd_dir);
}
if old_msd_enabled != new_msd_enabled {
if new_msd_enabled {
// MSD was disabled, now enabled - need to initialize
tracing::info!("MSD enabled in config, initializing...");
// Ensure MSD directories exist (msd/images, msd/ventoy)
let msd_dir = new_config.msd_dir_path();
if let Err(e) = std::fs::create_dir_all(msd_dir.join("images")) {
tracing::warn!("Failed to create MSD images directory: {}", e);
}
if let Err(e) = std::fs::create_dir_all(msd_dir.join("ventoy")) {
tracing::warn!("Failed to create MSD ventoy directory: {}", e);
}
let msd = crate::msd::MsdController::new(
state.otg_service.clone(),
&new_config.images_path,
&new_config.drive_path,
);
msd.init()
.await
.map_err(|e| AppError::Config(format!("MSD initialization failed: {}", e)))?;
// Set event bus
let events = state.events.clone();
msd.set_event_bus(events).await;
// Store the initialized controller
*state.msd.write().await = Some(msd);
tracing::info!("MSD initialized successfully");
} else {
// MSD was enabled, now disabled - shutdown
tracing::info!("MSD disabled in config, shutting down...");
if let Some(msd) = state.msd.write().await.as_mut() {
if let Err(e) = msd.shutdown().await {
tracing::warn!("MSD shutdown failed: {}", e);
}
}
*state.msd.write().await = None;
tracing::info!("MSD shutdown complete");
}
} else {
let needs_reload = old_msd_enabled != new_msd_enabled || msd_dir_changed;
if !needs_reload {
tracing::info!(
"MSD enabled state unchanged ({}), no reload needed",
"MSD enabled state unchanged ({}) and directory unchanged, no reload needed",
new_msd_enabled
);
return Ok(());
}
if new_msd_enabled {
tracing::info!("(Re)initializing MSD...");
// Shutdown existing controller if present
let mut msd_guard = state.msd.write().await;
if let Some(msd) = msd_guard.as_mut() {
if let Err(e) = msd.shutdown().await {
tracing::warn!("MSD shutdown failed: {}", e);
}
}
*msd_guard = None;
drop(msd_guard);
let msd =
crate::msd::MsdController::new(state.otg_service.clone(), new_config.msd_dir_path());
msd.init()
.await
.map_err(|e| AppError::Config(format!("MSD initialization failed: {}", e)))?;
// Set event bus
let events = state.events.clone();
msd.set_event_bus(events).await;
// Store the initialized controller
*state.msd.write().await = Some(msd);
tracing::info!("MSD initialized successfully");
} else {
// MSD disabled - shutdown
tracing::info!("MSD disabled in config, shutting down...");
let mut msd_guard = state.msd.write().await;
if let Some(msd) = msd_guard.as_mut() {
if let Err(e) = msd.shutdown().await {
tracing::warn!("MSD shutdown failed: {}", e);
}
}
*msd_guard = None;
tracing::info!("MSD shutdown complete");
}
Ok(())

View File

@@ -3,6 +3,7 @@ use crate::error::AppError;
use crate::rustdesk::config::RustDeskConfig;
use crate::video::encoder::BitratePreset;
use serde::Deserialize;
use std::path::Path;
use typeshare::typeshare;
// ===== Video Config =====
@@ -305,16 +306,20 @@ impl HidConfigUpdate {
#[derive(Debug, Deserialize)]
pub struct MsdConfigUpdate {
pub enabled: Option<bool>,
pub images_path: Option<String>,
pub drive_path: Option<String>,
pub virtual_drive_size_mb: Option<u32>,
pub msd_dir: Option<String>,
}
impl MsdConfigUpdate {
pub fn validate(&self) -> crate::error::Result<()> {
if let Some(size) = self.virtual_drive_size_mb {
if !(1..=10240).contains(&size) {
return Err(AppError::BadRequest("Drive size must be 1-10240 MB".into()));
if let Some(ref dir) = self.msd_dir {
let trimmed = dir.trim();
if trimmed.is_empty() {
return Err(AppError::BadRequest("MSD directory cannot be empty".into()));
}
if !Path::new(trimmed).is_absolute() {
return Err(AppError::BadRequest(
"MSD directory must be an absolute path".into(),
));
}
}
Ok(())
@@ -324,14 +329,8 @@ impl MsdConfigUpdate {
if let Some(enabled) = self.enabled {
config.enabled = enabled;
}
if let Some(ref path) = self.images_path {
config.images_path = path.clone();
}
if let Some(ref path) = self.drive_path {
config.drive_path = path.clone();
}
if let Some(size) = self.virtual_drive_size_mb {
config.virtual_drive_size_mb = size;
if let Some(ref dir) = self.msd_dir {
config.msd_dir = dir.trim().to_string();
}
}
}

View File

@@ -90,12 +90,13 @@ pub struct CapabilityInfo {
pub async fn system_info(State(state): State<Arc<AppState>>) -> Json<SystemInfo> {
let config = state.config.get();
// Get disk space information for MSD images directory
// Get disk space information for MSD base directory
let disk_space = {
if let Some(ref msd_controller) = *state.msd.read().await {
get_disk_space(msd_controller.images_path()).ok()
} else {
let msd_dir = config.msd.msd_dir_path();
if msd_dir.as_os_str().is_empty() {
None
} else {
get_disk_space(&msd_dir).ok()
}
};
@@ -933,66 +934,85 @@ pub async fn update_config(
}
}
// MSD config processing - reload if enabled state changed
// MSD config processing - reload if enabled state or directory changed
if has_msd {
tracing::info!("MSD config sent, checking if reload needed...");
tracing::debug!("Old MSD config: {:?}", old_config.msd);
tracing::debug!("New MSD config: {:?}", new_config.msd);
// Check if MSD enabled state changed
let old_msd_enabled = old_config.msd.enabled;
let new_msd_enabled = new_config.msd.enabled;
let msd_dir_changed = old_config.msd.msd_dir != new_config.msd.msd_dir;
tracing::info!(
"MSD enabled: old={}, new={}",
old_msd_enabled,
new_msd_enabled
);
if msd_dir_changed {
tracing::info!("MSD directory changed: {}", new_config.msd.msd_dir);
}
if old_msd_enabled != new_msd_enabled {
if new_msd_enabled {
// MSD was disabled, now enabled - need to initialize
tracing::info!("MSD enabled in config, initializing...");
// Ensure MSD directories exist (msd/images, msd/ventoy)
let msd_dir = new_config.msd.msd_dir_path();
if let Err(e) = std::fs::create_dir_all(msd_dir.join("images")) {
tracing::warn!("Failed to create MSD images directory: {}", e);
}
if let Err(e) = std::fs::create_dir_all(msd_dir.join("ventoy")) {
tracing::warn!("Failed to create MSD ventoy directory: {}", e);
}
let msd = crate::msd::MsdController::new(
state.otg_service.clone(),
&new_config.msd.images_path,
&new_config.msd.drive_path,
);
if let Err(e) = msd.init().await {
tracing::error!("MSD initialization failed: {}", e);
// Rollback config on failure
state.config.set((*old_config).clone()).await?;
return Ok(Json(LoginResponse {
success: false,
message: Some(format!("MSD initialization failed: {}", e)),
}));
}
// Set event bus
let events = state.events.clone();
msd.set_event_bus(events).await;
// Store the initialized controller
*state.msd.write().await = Some(msd);
tracing::info!("MSD initialized successfully");
} else {
// MSD was enabled, now disabled - shutdown
tracing::info!("MSD disabled in config, shutting down...");
if let Some(msd) = state.msd.write().await.as_mut() {
if let Err(e) = msd.shutdown().await {
tracing::warn!("MSD shutdown failed: {}", e);
}
}
*state.msd.write().await = None;
tracing::info!("MSD shutdown complete");
}
} else {
let needs_reload = old_msd_enabled != new_msd_enabled || msd_dir_changed;
if !needs_reload {
tracing::info!(
"MSD enabled state unchanged ({}), no reload needed",
"MSD enabled state unchanged ({}) and directory unchanged, no reload needed",
new_msd_enabled
);
} else if new_msd_enabled {
tracing::info!("(Re)initializing MSD...");
// Shutdown existing controller if present
let mut msd_guard = state.msd.write().await;
if let Some(msd) = msd_guard.as_mut() {
if let Err(e) = msd.shutdown().await {
tracing::warn!("MSD shutdown failed: {}", e);
}
}
*msd_guard = None;
drop(msd_guard);
let msd = crate::msd::MsdController::new(
state.otg_service.clone(),
new_config.msd.msd_dir_path(),
);
if let Err(e) = msd.init().await {
tracing::error!("MSD initialization failed: {}", e);
// Rollback config on failure
state.config.set((*old_config).clone()).await?;
return Ok(Json(LoginResponse {
success: false,
message: Some(format!("MSD initialization failed: {}", e)),
}));
}
// Set event bus
let events = state.events.clone();
msd.set_event_bus(events).await;
// Store the initialized controller
*state.msd.write().await = Some(msd);
tracing::info!("MSD initialized successfully");
} else {
tracing::info!("MSD disabled in config, shutting down...");
let mut msd_guard = state.msd.write().await;
if let Some(msd) = msd_guard.as_mut() {
if let Err(e) = msd.shutdown().await {
tracing::warn!("MSD shutdown failed: {}", e);
}
}
*msd_guard = None;
tracing::info!("MSD shutdown complete");
}
}
@@ -2069,7 +2089,7 @@ pub async fn msd_status(State(state): State<Arc<AppState>>) -> Result<Json<MsdSt
/// List all available images
pub async fn msd_images_list(State(state): State<Arc<AppState>>) -> Result<Json<Vec<ImageInfo>>> {
let config = state.config.get();
let images_path = std::path::PathBuf::from(&config.msd.images_path);
let images_path = config.msd.images_dir();
let manager = ImageManager::new(images_path);
let images = manager.list()?;
@@ -2082,7 +2102,7 @@ pub async fn msd_image_upload(
mut multipart: Multipart,
) -> Result<Json<ImageInfo>> {
let config = state.config.get();
let images_path = std::path::PathBuf::from(&config.msd.images_path);
let images_path = config.msd.images_dir();
let manager = ImageManager::new(images_path);
while let Some(field) = multipart
@@ -2115,7 +2135,7 @@ pub async fn msd_image_get(
AxumPath(id): AxumPath<String>,
) -> Result<Json<ImageInfo>> {
let config = state.config.get();
let images_path = std::path::PathBuf::from(&config.msd.images_path);
let images_path = config.msd.images_dir();
let manager = ImageManager::new(images_path);
let image = manager.get(&id)?;
@@ -2128,7 +2148,7 @@ pub async fn msd_image_delete(
AxumPath(id): AxumPath<String>,
) -> Result<Json<LoginResponse>> {
let config = state.config.get();
let images_path = std::path::PathBuf::from(&config.msd.images_path);
let images_path = config.msd.images_dir();
let manager = ImageManager::new(images_path);
manager.delete(&id)?;
@@ -2194,7 +2214,7 @@ pub async fn msd_connect(
})?;
// Get image info from ImageManager
let images_path = std::path::PathBuf::from(&config.msd.images_path);
let images_path = config.msd.images_dir();
let manager = ImageManager::new(images_path);
let image = manager.get(&image_id)?;
@@ -2240,7 +2260,7 @@ pub async fn msd_disconnect(State(state): State<Arc<AppState>>) -> Result<Json<L
/// Get drive info
pub async fn msd_drive_info(State(state): State<Arc<AppState>>) -> Result<Json<DriveInfo>> {
let config = state.config.get();
let drive_path = std::path::PathBuf::from(&config.msd.drive_path);
let drive_path = config.msd.drive_path();
let drive = VentoyDrive::new(drive_path);
if !drive.exists() {
@@ -2257,7 +2277,7 @@ pub async fn msd_drive_init(
Json(req): Json<DriveInitRequest>,
) -> Result<Json<DriveInfo>> {
let config = state.config.get();
let drive_path = std::path::PathBuf::from(&config.msd.drive_path);
let drive_path = config.msd.drive_path();
let drive = VentoyDrive::new(drive_path);
let info = drive.init(req.size_mb).await?;
@@ -2281,7 +2301,7 @@ pub async fn msd_drive_delete(State(state): State<Arc<AppState>>) -> Result<Json
drop(msd_guard);
// Delete the drive file
let drive_path = std::path::PathBuf::from(&config.msd.drive_path);
let drive_path = config.msd.drive_path();
if drive_path.exists() {
std::fs::remove_file(&drive_path)
.map_err(|e| AppError::Internal(format!("Failed to delete drive file: {}", e)))?;
@@ -2299,7 +2319,7 @@ pub async fn msd_drive_files(
Query(params): Query<HashMap<String, String>>,
) -> Result<Json<Vec<DriveFile>>> {
let config = state.config.get();
let drive_path = std::path::PathBuf::from(&config.msd.drive_path);
let drive_path = config.msd.drive_path();
let drive = VentoyDrive::new(drive_path);
let dir_path = params.get("path").map(|s| s.as_str()).unwrap_or("/");
@@ -2314,7 +2334,7 @@ pub async fn msd_drive_upload(
mut multipart: Multipart,
) -> Result<Json<LoginResponse>> {
let config = state.config.get();
let drive_path = std::path::PathBuf::from(&config.msd.drive_path);
let drive_path = config.msd.drive_path();
let drive = VentoyDrive::new(drive_path);
let target_dir = params.get("path").map(|s| s.as_str()).unwrap_or("/");
@@ -2359,7 +2379,7 @@ pub async fn msd_drive_download(
AxumPath(file_path): AxumPath<String>,
) -> Result<Response> {
let config = state.config.get();
let drive_path = std::path::PathBuf::from(&config.msd.drive_path);
let drive_path = config.msd.drive_path();
let drive = VentoyDrive::new(drive_path);
// Get file stream (returns file size and channel receiver)
@@ -2393,7 +2413,7 @@ pub async fn msd_drive_file_delete(
AxumPath(file_path): AxumPath<String>,
) -> Result<Json<LoginResponse>> {
let config = state.config.get();
let drive_path = std::path::PathBuf::from(&config.msd.drive_path);
let drive_path = config.msd.drive_path();
let drive = VentoyDrive::new(drive_path);
drive.delete(&file_path).await?;
@@ -2410,7 +2430,7 @@ pub async fn msd_drive_mkdir(
AxumPath(dir_path): AxumPath<String>,
) -> Result<Json<LoginResponse>> {
let config = state.config.get();
let drive_path = std::path::PathBuf::from(&config.msd.drive_path);
let drive_path = config.msd.drive_path();
let drive = VentoyDrive::new(drive_path);
drive.mkdir(&dir_path).await?;