feat(video): 事务化切换与前端统一编排,增强视频输入格式支持

- 后端:切换事务+transition_id,/stream/mode 返回 switching/transition_id 与实际 codec

- 事件:新增 mode_switching/mode_ready,config/webrtc_ready/mode_changed 关联事务

- 编码/格式:扩展 NV21/NV16/NV24/RGB/BGR 输入与转换链路,RKMPP direct input 优化

- 前端:useVideoSession 统一切换,失败回退真实切回 MJPEG,菜单格式同步修复

- 清理:useVideoStream 降级为 MJPEG-only
This commit is contained in:
mofeng-git
2026-01-11 10:41:57 +08:00
parent 9feb74b72c
commit 206594e292
110 changed files with 3955 additions and 2251 deletions

View File

@@ -109,15 +109,25 @@ pub fn read_file(path: &Path) -> Result<String> {
/// Create directory if not exists
pub fn create_dir(path: &Path) -> Result<()> {
fs::create_dir_all(path)
.map_err(|e| AppError::Internal(format!("Failed to create directory {}: {}", path.display(), e)))
fs::create_dir_all(path).map_err(|e| {
AppError::Internal(format!(
"Failed to create directory {}: {}",
path.display(),
e
))
})
}
/// Remove directory
pub fn remove_dir(path: &Path) -> Result<()> {
if path.exists() {
fs::remove_dir(path)
.map_err(|e| AppError::Internal(format!("Failed to remove directory {}: {}", path.display(), e)))?;
fs::remove_dir(path).map_err(|e| {
AppError::Internal(format!(
"Failed to remove directory {}: {}",
path.display(),
e
))
})?;
}
Ok(())
}
@@ -125,14 +135,21 @@ pub fn remove_dir(path: &Path) -> Result<()> {
/// Remove file
pub fn remove_file(path: &Path) -> Result<()> {
if path.exists() {
fs::remove_file(path)
.map_err(|e| AppError::Internal(format!("Failed to remove file {}: {}", path.display(), e)))?;
fs::remove_file(path).map_err(|e| {
AppError::Internal(format!("Failed to remove file {}: {}", path.display(), e))
})?;
}
Ok(())
}
/// Create symlink
pub fn create_symlink(src: &Path, dest: &Path) -> Result<()> {
std::os::unix::fs::symlink(src, dest)
.map_err(|e| AppError::Internal(format!("Failed to create symlink {} -> {}: {}", dest.display(), src.display(), e)))
std::os::unix::fs::symlink(src, dest).map_err(|e| {
AppError::Internal(format!(
"Failed to create symlink {} -> {}: {}",
dest.display(),
src.display(),
e
))
})
}

View File

@@ -3,7 +3,9 @@
use std::path::{Path, PathBuf};
use tracing::debug;
use super::configfs::{create_dir, create_symlink, remove_dir, remove_file, write_bytes, write_file};
use super::configfs::{
create_dir, create_symlink, remove_dir, remove_file, write_bytes, write_file,
};
use super::function::{FunctionMeta, GadgetFunction};
use super::report_desc::{CONSUMER_CONTROL, KEYBOARD, MOUSE_ABSOLUTE, MOUSE_RELATIVE};
use crate::error::Result;
@@ -39,20 +41,20 @@ impl HidFunctionType {
/// Get HID protocol
pub fn protocol(&self) -> u8 {
match self {
HidFunctionType::Keyboard => 1, // Keyboard
HidFunctionType::MouseRelative => 2, // Mouse
HidFunctionType::MouseAbsolute => 2, // Mouse
HidFunctionType::ConsumerControl => 0, // None
HidFunctionType::Keyboard => 1, // Keyboard
HidFunctionType::MouseRelative => 2, // Mouse
HidFunctionType::MouseAbsolute => 2, // Mouse
HidFunctionType::ConsumerControl => 0, // None
}
}
/// Get HID subclass
pub fn subclass(&self) -> u8 {
match self {
HidFunctionType::Keyboard => 1, // Boot interface
HidFunctionType::MouseRelative => 1, // Boot interface
HidFunctionType::MouseAbsolute => 0, // No boot interface
HidFunctionType::ConsumerControl => 0, // No boot interface
HidFunctionType::Keyboard => 1, // Boot interface
HidFunctionType::MouseRelative => 1, // Boot interface
HidFunctionType::MouseAbsolute => 0, // No boot interface
HidFunctionType::ConsumerControl => 0, // No boot interface
}
}
@@ -169,14 +171,27 @@ impl GadgetFunction for HidFunction {
create_dir(&func_path)?;
// Set HID parameters
write_file(&func_path.join("protocol"), &self.func_type.protocol().to_string())?;
write_file(&func_path.join("subclass"), &self.func_type.subclass().to_string())?;
write_file(&func_path.join("report_length"), &self.func_type.report_length().to_string())?;
write_file(
&func_path.join("protocol"),
&self.func_type.protocol().to_string(),
)?;
write_file(
&func_path.join("subclass"),
&self.func_type.subclass().to_string(),
)?;
write_file(
&func_path.join("report_length"),
&self.func_type.report_length().to_string(),
)?;
// Write report descriptor
write_bytes(&func_path.join("report_desc"), self.func_type.report_desc())?;
debug!("Created HID function: {} at {}", self.name(), func_path.display());
debug!(
"Created HID function: {} at {}",
self.name(),
func_path.display()
);
Ok(())
}

View File

@@ -7,7 +7,8 @@ use tracing::{debug, error, info, warn};
use super::configfs::{
create_dir, find_udc, is_configfs_available, remove_dir, write_file, CONFIGFS_PATH,
DEFAULT_GADGET_NAME, DEFAULT_USB_BCD_DEVICE, USB_BCD_USB, DEFAULT_USB_PRODUCT_ID, DEFAULT_USB_VENDOR_ID,
DEFAULT_GADGET_NAME, DEFAULT_USB_BCD_DEVICE, DEFAULT_USB_PRODUCT_ID, DEFAULT_USB_VENDOR_ID,
USB_BCD_USB,
};
use super::endpoint::{EndpointAllocator, DEFAULT_MAX_ENDPOINTS};
use super::function::{FunctionMeta, GadgetFunction};
@@ -77,7 +78,11 @@ impl OtgGadgetManager {
}
/// Create a new gadget manager with custom descriptor
pub fn with_descriptor(gadget_name: &str, max_endpoints: u8, descriptor: GadgetDescriptor) -> Self {
pub fn with_descriptor(
gadget_name: &str,
max_endpoints: u8,
descriptor: GadgetDescriptor,
) -> Self {
let gadget_path = PathBuf::from(CONFIGFS_PATH).join(gadget_name);
let config_path = gadget_path.join("configs/c.1");
@@ -303,10 +308,22 @@ impl OtgGadgetManager {
/// Set USB device descriptors
fn set_device_descriptors(&self) -> Result<()> {
write_file(&self.gadget_path.join("idVendor"), &format!("0x{:04x}", self.descriptor.vendor_id))?;
write_file(&self.gadget_path.join("idProduct"), &format!("0x{:04x}", self.descriptor.product_id))?;
write_file(&self.gadget_path.join("bcdDevice"), &format!("0x{:04x}", self.descriptor.device_version))?;
write_file(&self.gadget_path.join("bcdUSB"), &format!("0x{:04x}", USB_BCD_USB))?;
write_file(
&self.gadget_path.join("idVendor"),
&format!("0x{:04x}", self.descriptor.vendor_id),
)?;
write_file(
&self.gadget_path.join("idProduct"),
&format!("0x{:04x}", self.descriptor.product_id),
)?;
write_file(
&self.gadget_path.join("bcdDevice"),
&format!("0x{:04x}", self.descriptor.device_version),
)?;
write_file(
&self.gadget_path.join("bcdUSB"),
&format!("0x{:04x}", USB_BCD_USB),
)?;
write_file(&self.gadget_path.join("bDeviceClass"), "0x00")?; // Composite device
write_file(&self.gadget_path.join("bDeviceSubClass"), "0x00")?;
write_file(&self.gadget_path.join("bDeviceProtocol"), "0x00")?;
@@ -319,8 +336,14 @@ impl OtgGadgetManager {
let strings_path = self.gadget_path.join("strings/0x409");
create_dir(&strings_path)?;
write_file(&strings_path.join("serialnumber"), &self.descriptor.serial_number)?;
write_file(&strings_path.join("manufacturer"), &self.descriptor.manufacturer)?;
write_file(
&strings_path.join("serialnumber"),
&self.descriptor.serial_number,
)?;
write_file(
&strings_path.join("manufacturer"),
&self.descriptor.manufacturer,
)?;
write_file(&strings_path.join("product"), &self.descriptor.product)?;
debug!("Created USB strings");
Ok(())
@@ -349,7 +372,10 @@ impl OtgGadgetManager {
/// Get endpoint usage info
pub fn endpoint_info(&self) -> (u8, u8) {
(self.endpoint_allocator.used(), self.endpoint_allocator.max())
(
self.endpoint_allocator.used(),
self.endpoint_allocator.max(),
)
}
/// Get gadget path

View File

@@ -161,7 +161,10 @@ impl MsdFunction {
// Write only changed attributes
let cdrom_changed = current_cdrom != new_cdrom;
if cdrom_changed {
debug!("Updating LUN {} cdrom: {} -> {}", lun, current_cdrom, new_cdrom);
debug!(
"Updating LUN {} cdrom: {} -> {}",
lun, current_cdrom, new_cdrom
);
write_file(&lun_path.join("cdrom"), new_cdrom)?;
}
if current_ro != new_ro {
@@ -169,11 +172,17 @@ impl MsdFunction {
write_file(&lun_path.join("ro"), new_ro)?;
}
if current_removable != new_removable {
debug!("Updating LUN {} removable: {} -> {}", lun, current_removable, new_removable);
debug!(
"Updating LUN {} removable: {} -> {}",
lun, current_removable, new_removable
);
write_file(&lun_path.join("removable"), new_removable)?;
}
if current_nofua != new_nofua {
debug!("Updating LUN {} nofua: {} -> {}", lun, current_nofua, new_nofua);
debug!(
"Updating LUN {} nofua: {} -> {}",
lun, current_nofua, new_nofua
);
write_file(&lun_path.join("nofua"), new_nofua)?;
}
@@ -258,11 +267,17 @@ impl MsdFunction {
// forced_eject forcibly detaches the backing file regardless of host state
let forced_eject_path = lun_path.join("forced_eject");
if forced_eject_path.exists() {
debug!("Using forced_eject to disconnect LUN {} at {:?}", lun, forced_eject_path);
debug!(
"Using forced_eject to disconnect LUN {} at {:?}",
lun, forced_eject_path
);
match write_file(&forced_eject_path, "1") {
Ok(_) => debug!("forced_eject write succeeded"),
Err(e) => {
warn!("forced_eject write failed: {}, falling back to clearing file", e);
warn!(
"forced_eject write failed: {}, falling back to clearing file",
e
);
write_file(&lun_path.join("file"), "")?;
}
}

View File

@@ -135,17 +135,17 @@ pub const MOUSE_ABSOLUTE: &[u8] = &[
/// [0-1] Consumer Control Usage (16-bit little-endian)
/// Supports: Play/Pause, Stop, Next/Prev Track, Mute, Volume Up/Down, etc.
pub const CONSUMER_CONTROL: &[u8] = &[
0x05, 0x0C, // Usage Page (Consumer)
0x09, 0x01, // Usage (Consumer Control)
0xA1, 0x01, // Collection (Application)
0x15, 0x00, // Logical Minimum (0)
0x05, 0x0C, // Usage Page (Consumer)
0x09, 0x01, // Usage (Consumer Control)
0xA1, 0x01, // Collection (Application)
0x15, 0x00, // Logical Minimum (0)
0x26, 0xFF, 0x03, // Logical Maximum (1023)
0x19, 0x00, // Usage Minimum (0)
0x19, 0x00, // Usage Minimum (0)
0x2A, 0xFF, 0x03, // Usage Maximum (1023)
0x75, 0x10, // Report Size (16)
0x95, 0x01, // Report Count (1)
0x81, 0x00, // Input (Data, Array)
0xC0, // End Collection
0x75, 0x10, // Report Size (16)
0x95, 0x01, // Report Count (1)
0x81, 0x00, // Input (Data, Array)
0xC0, // End Collection
];
#[cfg(test)]

View File

@@ -27,8 +27,8 @@ use tracing::{debug, info, warn};
use super::manager::{wait_for_hid_devices, GadgetDescriptor, OtgGadgetManager};
use super::msd::MsdFunction;
use crate::error::{AppError, Result};
use crate::config::OtgDescriptorConfig;
use crate::error::{AppError, Result};
/// Bitflags for requested functions (lock-free)
const FLAG_HID: u8 = 0b01;
@@ -254,8 +254,9 @@ impl OtgService {
// Get MSD function
let msd = self.msd_function.read().await;
msd.clone()
.ok_or_else(|| AppError::Internal("MSD function not set after gadget setup".to_string()))
msd.clone().ok_or_else(|| {
AppError::Internal("MSD function not set after gadget setup".to_string())
})
}
/// Disable MSD function
@@ -465,7 +466,10 @@ impl OtgService {
device_version: super::configfs::DEFAULT_USB_BCD_DEVICE,
manufacturer: config.manufacturer.clone(),
product: config.product.clone(),
serial_number: config.serial_number.clone().unwrap_or_else(|| "0123456789".to_string()),
serial_number: config
.serial_number
.clone()
.unwrap_or_else(|| "0123456789".to_string()),
};
// Update stored descriptor