mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-10-28 08:20:49 +08:00
feat: 完善 OTG UAC 音频支持
This commit is contained in:
@@ -28,7 +28,7 @@ pub use msd::{MsdFunction, MsdLunConfig};
|
||||
#[cfg(unix)]
|
||||
pub use network::NetworkFunction;
|
||||
#[cfg(unix)]
|
||||
pub use service::{HidDevicePaths, OtgNetworkStatus, OtgRuntimeHealth, OtgService, UacConfig};
|
||||
pub use service::{HidDevicePaths, OtgNetworkStatus, OtgRuntimeHealth, OtgService};
|
||||
#[cfg(unix)]
|
||||
pub use uac::UacFunction;
|
||||
|
||||
|
||||
@@ -9,39 +9,10 @@ use super::manager::{wait_for_hid_devices, GadgetDescriptor, OtgGadgetManager};
|
||||
use super::msd::MsdFunction;
|
||||
use crate::config::{
|
||||
HidBackend, HidConfig, MsdConfig, OtgDescriptorConfig, OtgHidFunctions, OtgNetworkConfig,
|
||||
UacConfig,
|
||||
};
|
||||
use crate::error::{AppError, Result};
|
||||
|
||||
/// Configuration for the USB Audio Class (UAC) gadget function.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct UacConfig {
|
||||
/// Enable the virtual USB microphone.
|
||||
pub enabled: bool,
|
||||
/// Sample rate in Hz (e.g. 48000).
|
||||
pub sample_rate: u32,
|
||||
/// Number of channels (1=mono, 2=stereo).
|
||||
pub channels: u8,
|
||||
}
|
||||
|
||||
impl UacConfig {
|
||||
pub fn validate(&self) -> Result<()> {
|
||||
if self.sample_rate != 0 && (self.sample_rate < 8000 || self.sample_rate > 384000) {
|
||||
return Err(AppError::BadRequest(format!(
|
||||
"UAC sample rate {} out of range (8000-384000)",
|
||||
self.sample_rate
|
||||
)));
|
||||
}
|
||||
if self.channels != 0 && self.channels > 8 {
|
||||
return Err(AppError::BadRequest(format!(
|
||||
"UAC channel count {} out of range (1-8)",
|
||||
self.channels
|
||||
)));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct HidDevicePaths {
|
||||
pub keyboard: Option<PathBuf>,
|
||||
@@ -92,7 +63,7 @@ pub(crate) struct OtgDesiredState {
|
||||
pub msd_enabled: bool,
|
||||
pub msd_lun_capacity: u8,
|
||||
pub network: OtgNetworkConfig,
|
||||
pub uac_enabled: bool,
|
||||
pub uac: UacConfig,
|
||||
}
|
||||
|
||||
impl Default for OtgDesiredState {
|
||||
@@ -105,7 +76,7 @@ impl Default for OtgDesiredState {
|
||||
msd_enabled: false,
|
||||
msd_lun_capacity: 1,
|
||||
network: OtgNetworkConfig::default(),
|
||||
uac_enabled: false,
|
||||
uac: UacConfig::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -127,8 +98,7 @@ impl OtgDesiredState {
|
||||
};
|
||||
|
||||
hid.validate_otg_functions()?;
|
||||
let needs_udc =
|
||||
hid_functions.is_some() || msd.enabled || network.enabled || uac.enabled;
|
||||
let needs_udc = hid_functions.is_some() || msd.enabled || network.enabled || uac.enabled;
|
||||
let udc = if needs_udc {
|
||||
hid.otg_udc
|
||||
.as_ref()
|
||||
@@ -146,7 +116,11 @@ impl OtgDesiredState {
|
||||
msd_enabled: msd.enabled,
|
||||
msd_lun_capacity: 1,
|
||||
network: network.clone(),
|
||||
uac_enabled: uac.enabled,
|
||||
uac: if uac.enabled {
|
||||
uac.clone()
|
||||
} else {
|
||||
UacConfig::default()
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -169,7 +143,7 @@ struct OtgServiceState {
|
||||
pub msd_enabled: bool,
|
||||
pub msd_lun_capacity: u8,
|
||||
pub network: OtgNetworkConfig,
|
||||
pub uac_enabled: bool,
|
||||
pub uac: UacConfig,
|
||||
pub configured_udc: Option<String>,
|
||||
pub hid_paths: Option<HidDevicePaths>,
|
||||
pub hid_functions: Option<OtgHidFunctions>,
|
||||
@@ -187,7 +161,7 @@ impl Default for OtgServiceState {
|
||||
msd_enabled: false,
|
||||
msd_lun_capacity: 1,
|
||||
network: OtgNetworkConfig::default(),
|
||||
uac_enabled: false,
|
||||
uac: UacConfig::default(),
|
||||
configured_udc: None,
|
||||
hid_paths: None,
|
||||
hid_functions: None,
|
||||
@@ -349,7 +323,7 @@ impl OtgService {
|
||||
desired.hid_enabled(),
|
||||
desired.msd_enabled,
|
||||
desired.network_enabled(),
|
||||
desired.uac_enabled,
|
||||
desired.uac.enabled,
|
||||
desired.udc
|
||||
);
|
||||
|
||||
@@ -361,7 +335,7 @@ impl OtgService {
|
||||
&& state.msd_enabled == desired.msd_enabled
|
||||
&& state.msd_lun_capacity == desired.msd_lun_capacity
|
||||
&& state.network == desired.network
|
||||
&& state.uac_enabled == desired.uac_enabled
|
||||
&& state.uac == desired.uac
|
||||
&& state.configured_udc == desired.udc
|
||||
&& state.hid_functions == desired.hid_functions
|
||||
&& state.keyboard_leds_enabled == desired.keyboard_leds
|
||||
@@ -401,7 +375,7 @@ impl OtgService {
|
||||
state.msd_enabled = false;
|
||||
state.msd_lun_capacity = 1;
|
||||
state.network = OtgNetworkConfig::default();
|
||||
state.uac_enabled = false;
|
||||
state.uac = UacConfig::default();
|
||||
state.configured_udc = None;
|
||||
state.hid_paths = None;
|
||||
state.hid_functions = None;
|
||||
@@ -410,7 +384,11 @@ impl OtgService {
|
||||
state.error = None;
|
||||
}
|
||||
|
||||
if !desired.hid_enabled() && !desired.msd_enabled && !desired.network_enabled() {
|
||||
if !desired.hid_enabled()
|
||||
&& !desired.msd_enabled
|
||||
&& !desired.network_enabled()
|
||||
&& !desired.uac.enabled
|
||||
{
|
||||
info!("OTG desired state is empty, gadget removed");
|
||||
return Ok(());
|
||||
}
|
||||
@@ -440,12 +418,12 @@ impl OtgService {
|
||||
// lower hardware endpoint number. DWC3 seems to have
|
||||
// trouble with isochronous transfers on higher-numbered
|
||||
// endpoints when they follow interrupt endpoints.
|
||||
let _uac_func = if desired.uac_enabled {
|
||||
let sample_rate: u32 = 48000;
|
||||
let channels: u8 = 2;
|
||||
Some(manager.add_uac(sample_rate, channels).map_err(|e| {
|
||||
AppError::Internal(format!("Failed to add UAC function: {e}"))
|
||||
})?)
|
||||
let _uac_func = if desired.uac.enabled {
|
||||
Some(
|
||||
manager
|
||||
.add_uac(desired.uac.sample_rate, desired.uac.channels)
|
||||
.map_err(|e| AppError::Internal(format!("Failed to add UAC function: {e}")))?,
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
@@ -594,7 +572,7 @@ impl OtgService {
|
||||
state.msd_enabled = desired.msd_enabled;
|
||||
state.msd_lun_capacity = desired.msd_lun_capacity;
|
||||
state.network = desired.network.clone();
|
||||
state.uac_enabled = desired.uac_enabled;
|
||||
state.uac = desired.uac.clone();
|
||||
state.configured_udc = Some(udc);
|
||||
state.hid_paths = hid_paths;
|
||||
state.hid_functions = desired.hid_functions;
|
||||
@@ -732,7 +710,8 @@ mod tests {
|
||||
..OtgNetworkConfig::default()
|
||||
};
|
||||
|
||||
let desired = OtgDesiredState::from_config(&hid, &msd, &network).unwrap();
|
||||
let desired =
|
||||
OtgDesiredState::from_config(&hid, &msd, &network, &UacConfig::default()).unwrap();
|
||||
|
||||
assert_eq!(desired.udc.as_deref(), Some("c9040000.usb"));
|
||||
assert_eq!(desired.hid_functions, Some(OtgHidFunctions::full()));
|
||||
|
||||
@@ -6,7 +6,7 @@ use super::configfs::{create_dir, create_symlink, remove_dir, write_file};
|
||||
use super::function::GadgetFunction;
|
||||
use crate::error::{AppError, Result};
|
||||
|
||||
/// USB Audio Class 2.0 (UAC1) gadget function.
|
||||
/// USB Audio Class 1.0 gadget function.
|
||||
///
|
||||
/// Creates a virtual USB microphone that the USB host sees as a standard
|
||||
/// USB audio input device. Audio written to the PCM playback device on the
|
||||
@@ -60,13 +60,18 @@ impl GadgetFunction for UacFunction {
|
||||
let chmask: u32 = (1u32 << self.channels) - 1;
|
||||
write_file(&func_path.join("p_chmask"), &chmask.to_string())?;
|
||||
write_file(&func_path.join("p_srate"), &self.sample_rate.to_string())?;
|
||||
write_file(&func_path.join("p_ssize"), "2")?; // 16-bit S16LE
|
||||
// 16-bit S16LE.
|
||||
write_file(&func_path.join("p_ssize"), "2")?;
|
||||
// One decibel per step. The kernel default is 1/256 dB, which creates
|
||||
// 25,600 control values and triggers a UAC volume-range warning.
|
||||
write_file(&func_path.join("p_volume_res"), "256")?;
|
||||
// UAC1 does not need p_hs_bint — Windows has native built-in
|
||||
// UAC1 drivers and handles isochronous streaming automatically.
|
||||
|
||||
// Only enable playback direction (gadget → host = mic).
|
||||
// Disabling capture saves one isochronous endpoint.
|
||||
write_file(&func_path.join("c_chmask"), "0")?;
|
||||
write_file(&func_path.join("c_volume_present"), "0")?;
|
||||
|
||||
// req_number=4: explicitly allocate 4 USB requests for the
|
||||
// isochronous endpoint. Default (0 = auto) may not be enough
|
||||
|
||||
Reference in New Issue
Block a user