From d88d63b15b21cd2480c3fd702ccbd121bd90e3fc Mon Sep 17 00:00:00 2001 From: Del Wang Date: Sun, 4 Jan 2026 15:06:55 +0800 Subject: [PATCH] chore: polish code --- packages/client-v2/src/app/client/mod.rs | 37 +++++----- packages/client-v2/src/app/client/session.rs | 25 +++---- packages/client-v2/src/app/server/mod.rs | 49 +++++++++---- packages/client-v2/src/audio/config.rs | 6 +- packages/client-v2/src/audio/player.rs | 14 ++-- packages/client-v2/src/bin/client.rs | 29 ++------ packages/client-v2/src/bin/server.rs | 35 +++++---- packages/client-v2/src/net/protocol.rs | 75 +++++++++----------- packages/client-v2/src/net/rpc.rs | 26 ------- 9 files changed, 137 insertions(+), 159 deletions(-) diff --git a/packages/client-v2/src/app/client/mod.rs b/packages/client-v2/src/app/client/mod.rs index bf7aaad..26c2485 100644 --- a/packages/client-v2/src/app/client/mod.rs +++ b/packages/client-v2/src/app/client/mod.rs @@ -40,7 +40,7 @@ use crate::net::command::{ use crate::net::discovery::Discovery; use crate::net::event::{ClientEvent, NotificationLevel, ServerEvent}; use crate::net::network::{AudioSocket, Connection}; -use crate::net::protocol::{ClientInfo, ControlPacket}; +use crate::net::protocol::ControlPacket; use anyhow::{Result, anyhow}; use session::handshake; use std::net::SocketAddr; @@ -49,24 +49,37 @@ use tokio::sync::{RwLock, broadcast}; use tokio_util::sync::CancellationToken; /// 客户端配置 +#[derive(Debug, Clone)] pub struct ClientConfig { - /// 客户端型号 - pub model: String, - /// 序列号 - pub serial_number: String, + /// 版本 + pub version: String, + /// 客户端认证 + pub client_auth: String, + /// 服务端认证 + pub server_auth: String, /// 心跳间隔(秒) pub heartbeat_interval: u64, /// 连接超时(秒) pub timeout: u64, + /// 客户端型号 + pub model: String, + /// 序列号 + pub serial_number: String, } impl Default for ClientConfig { fn default() -> Self { Self { - model: "Open-XiaoAi-V2".to_string(), - serial_number: "00:00:00:00:00:00".to_string(), + version: env!("CARGO_PKG_VERSION").to_string(), + server_auth: std::env::var("XIAO_SERVER_AUTH") + .unwrap_or_else(|_| "xiao-server".to_string()), + client_auth: std::env::var("XIAO_CLIENT_AUTH") + .unwrap_or_else(|_| "xiao-client".to_string()), heartbeat_interval: 10, timeout: 60, + // todo 获取设备信息 + model: "Open-XiaoAi-V2".to_string(), + serial_number: "00:00:00:00:00:00".to_string(), } } } @@ -81,8 +94,6 @@ pub struct Client { cancel: CancellationToken, /// 服务端事件广播 server_events: broadcast::Sender, - /// 客户端启动时间 - started_at: std::time::Instant, } impl Client { @@ -94,7 +105,6 @@ impl Client { session: RwLock::new(None), cancel: CancellationToken::new(), server_events, - started_at: std::time::Instant::now(), } } @@ -153,13 +163,8 @@ impl Client { let audio_socket = Arc::new(AudioSocket::bind().await?); // 执行握手 - let client_info = ClientInfo { - model: self.config.model.clone(), - serial_number: self.config.serial_number.clone(), - }; - let handshake_result = - handshake(&conn, audio_socket.port(), server_addr, client_info).await?; + handshake(&conn, audio_socket.port(), server_addr, &self.config).await?; println!( "[Client] Handshake OK, server audio at {}", diff --git a/packages/client-v2/src/app/client/session.rs b/packages/client-v2/src/app/client/session.rs index fe05247..7e9276a 100644 --- a/packages/client-v2/src/app/client/session.rs +++ b/packages/client-v2/src/app/client/session.rs @@ -15,7 +15,7 @@ use std::net::SocketAddr; use std::sync::Arc; use tokio_util::sync::CancellationToken; -use super::pipeline::PipelineHandle; +use super::{ClientConfig, pipeline::PipelineHandle}; /// 活动管道追踪 pub struct ActivePipelines { @@ -224,20 +224,17 @@ pub async fn handshake( conn: &Connection, audio_port: u16, server_addr: SocketAddr, - client_info: ClientInfo, + config: &ClientConfig, ) -> Result { - let version = env!("CARGO_PKG_VERSION").to_string(); - let server_auth = - std::env::var("XIAO_SERVER_AUTH").unwrap_or_else(|_| "xiao-server".to_string()); - let client_auth = - std::env::var("XIAO_CLIENT_AUTH").unwrap_or_else(|_| "xiao-client".to_string()); - // 发送 ClientHello conn.send(&ControlPacket::ClientHello { - auth: server_auth, - version: version.clone(), udp_port: audio_port, - info: client_info, + auth: config.server_auth.clone(), + version: config.version.clone(), + info: ClientInfo { + model: config.model.clone(), + serial_number: config.serial_number.clone(), + }, }) .await?; @@ -248,10 +245,10 @@ pub async fn handshake( udp_port, auth, } => { - if v != version { - return Err(anyhow!("Server version mismatch: {} != {}", v, version)); + if v != config.version { + return Err(anyhow!("Server version mismatch")); } - if auth != client_auth { + if auth != config.client_auth { return Err(anyhow!("Invalid server auth")); } udp_port diff --git a/packages/client-v2/src/app/server/mod.rs b/packages/client-v2/src/app/server/mod.rs index 69c4ea5..234379e 100644 --- a/packages/client-v2/src/app/server/mod.rs +++ b/packages/client-v2/src/app/server/mod.rs @@ -56,8 +56,36 @@ use std::net::SocketAddr; use std::sync::Arc; use tokio_util::sync::CancellationToken; +/// 服务端配置 +#[derive(Debug, Clone)] +pub struct ServerConfig { + /// 版本 + pub version: String, + /// 客户端认证 + pub client_auth: String, + /// 服务端认证 + pub server_auth: String, + /// 连接超时(秒) + pub timeout: u64, +} + +impl Default for ServerConfig { + fn default() -> Self { + Self { + version: env!("CARGO_PKG_VERSION").to_string(), + server_auth: std::env::var("XIAO_SERVER_AUTH") + .unwrap_or_else(|_| "xiao-server".to_string()), + client_auth: std::env::var("XIAO_CLIENT_AUTH") + .unwrap_or_else(|_| "xiao-client".to_string()), + timeout: 60, + } + } +} + /// 实时音频服务器 pub struct Server { + /// 配置 + config: ServerConfig, /// 会话管理器 sessions: Arc, /// 音频总线 @@ -72,10 +100,11 @@ pub struct Server { impl Server { /// 创建新服务器 - pub async fn new() -> Result { + pub async fn new(config: ServerConfig) -> Result { let audio_bus = Arc::new(AudioBus::new().await?); Ok(Self { + config, sessions: Arc::new(SessionManager::new()), audio_bus, event_bus: Arc::new(ServerEventBus::default()), @@ -216,12 +245,6 @@ impl Server { conn: &Arc, addr: SocketAddr, ) -> Result<(crate::net::protocol::ClientInfo, SocketAddr)> { - let version = env!("CARGO_PKG_VERSION").to_string(); - let server_auth = - std::env::var("XIAO_SERVER_AUTH").unwrap_or_else(|_| "xiao-server".to_string()); - let client_auth = - std::env::var("XIAO_CLIENT_AUTH").unwrap_or_else(|_| "xiao-client".to_string()); - // 等待客户端 Hello let (info, client_audio_port) = match conn.recv().await? { ControlPacket::ClientHello { @@ -230,10 +253,10 @@ impl Server { udp_port, info, } => { - if v != version { - return Err(anyhow!("Version mismatch: {} != {}", v, version)); + if v != self.config.version { + return Err(anyhow!("Client version mismatch")); } - if auth != server_auth { + if auth != self.config.server_auth { return Err(anyhow!("Invalid client auth")); } (info, udp_port) @@ -243,8 +266,8 @@ impl Server { // 发送服务器 Hello conn.send(&ControlPacket::ServerHello { - auth: client_auth, - version, + auth: self.config.client_auth.clone(), + version: self.config.version.clone(), udp_port: self.audio_bus.port(), }) .await?; @@ -255,7 +278,7 @@ impl Server { /// Session 消息循环 async fn session_loop(&self, session: Arc) -> Result<()> { - let timeout = std::time::Duration::from_secs(60); + let timeout = std::time::Duration::from_secs(self.config.timeout); loop { tokio::select! { diff --git a/packages/client-v2/src/audio/config.rs b/packages/client-v2/src/audio/config.rs index 4fedc0f..23f8b1b 100644 --- a/packages/client-v2/src/audio/config.rs +++ b/packages/client-v2/src/audio/config.rs @@ -30,7 +30,7 @@ impl AudioConfig { audio_scene: AudioScene::Voice, bitrate: 32_000, vbr: true, - fec: true, + fec: false, } } @@ -42,9 +42,9 @@ impl AudioConfig { channels: 2, frame_size: 960, // 20ms audio_scene: AudioScene::Music, - bitrate: 128_000, + bitrate: 320_000, vbr: true, - fec: true, + fec: false, } } } diff --git a/packages/client-v2/src/audio/player.rs b/packages/client-v2/src/audio/player.rs index 3aa9adb..70a8b43 100644 --- a/packages/client-v2/src/audio/player.rs +++ b/packages/client-v2/src/audio/player.rs @@ -11,21 +11,17 @@ pub struct AudioPlayer { impl AudioPlayer { pub fn new(config: &AudioConfig) -> Result { - let pcm = PCM::new(&config.playback_device, Direction::Playback, false)?; + let pcm = PCM::new(&config.playback_device, Direction::Playback, false) + .context("Failed to open playback PCM device")?; { - let hwp = HwParams::any(&pcm)?; + let hwp = HwParams::any(&pcm).context("Failed to get HwParams")?; hwp.set_access(Access::RWInterleaved)?; hwp.set_format(Format::s16())?; hwp.set_rate_near(config.sample_rate, alsa::ValueOr::Nearest)?; - hwp.set_channels_near(config.channels as u32)?; - - // 100ms buffer to prevent underruns - let buffer_size = (config.sample_rate as f64 * 0.1) as u32; - hwp.set_buffer_size_near(buffer_size as alsa::pcm::Frames)?; - + hwp.set_channels(config.channels as u32)?; pcm.hw_params(&hwp)?; } - pcm.prepare()?; + pcm.prepare().context("Failed to prepare PCM")?; Ok(Self { pcm }) } diff --git a/packages/client-v2/src/bin/client.rs b/packages/client-v2/src/bin/client.rs index c3bdbad..55927dd 100644 --- a/packages/client-v2/src/bin/client.rs +++ b/packages/client-v2/src/bin/client.rs @@ -24,18 +24,15 @@ fn main() { #[tokio::main] async fn main() -> anyhow::Result<()> { println!("╔═══════════════════════════════════════════════════════╗"); - println!("║ XiaoAi Audio Client v{} ║", env!("CARGO_PKG_VERSION")); + println!( + "║ XiaoAi Audio Client v{} ║", + env!("CARGO_PKG_VERSION") + ); println!("╚═══════════════════════════════════════════════════════╝"); println!(); - // 创建客户端(可以自定义配置) - let config = ClientConfig { - model: "Open-XiaoAi-Demo".to_string(), - serial_number: get_mac_address(), - heartbeat_interval: 10, - timeout: 60, - }; - + // 创建客户端 + let config = ClientConfig::default(); let client = Arc::new(Client::new(config)); // 启动事件监听器 @@ -104,17 +101,3 @@ async fn main() -> anyhow::Result<()> { client.shutdown(); Ok(()) } - -#[cfg(target_os = "linux")] -fn get_mac_address() -> String { - // 尝试获取 MAC 地址 - if let Ok(output) = std::process::Command::new("cat") - .arg("/sys/class/net/eth0/address") - .output() - { - if output.status.success() { - return String::from_utf8_lossy(&output.stdout).trim().to_string(); - } - } - "00:00:00:00:00:00".to_string() -} diff --git a/packages/client-v2/src/bin/server.rs b/packages/client-v2/src/bin/server.rs index 9fcd011..774ed52 100644 --- a/packages/client-v2/src/bin/server.rs +++ b/packages/client-v2/src/bin/server.rs @@ -7,7 +7,7 @@ //! - 事件广播 use std::sync::Arc; -use xiao::app::server::Server; +use xiao::app::server::{Server, ServerConfig}; use xiao::audio::config::AudioConfig; use xiao::net::command::Command; use xiao::net::event::{NotificationLevel, ServerEvent}; @@ -15,11 +15,16 @@ use xiao::net::event::{NotificationLevel, ServerEvent}; #[tokio::main] async fn main() -> anyhow::Result<()> { println!("╔═══════════════════════════════════════════════════════╗"); - println!("║ XiaoAi Audio Server v{} ║", env!("CARGO_PKG_VERSION")); + println!( + "║ XiaoAi Audio Server v{} ║", + env!("CARGO_PKG_VERSION") + ); println!("╚═══════════════════════════════════════════════════════╝"); println!(); - let server = Arc::new(Server::new().await?); + // 创建服务端 + let config = ServerConfig::default(); + let server = Arc::new(Server::new(config).await?); let s = server.clone(); // 启动服务器 @@ -98,18 +103,6 @@ async fn main() -> anyhow::Result<()> { .await; println!(" ✅ Event broadcasted"); - // 5. 测试音频录制 - println!("\n5️⃣ Testing Audio Recording (5 seconds)..."); - match server.start_record(addr, AudioConfig::voice_16k()).await { - Ok(_) => { - println!(" ⏺️ Recording started..."); - tokio::time::sleep(std::time::Duration::from_secs(5)).await; - server.stop_record(addr).await?; - println!(" ⏹️ Recording stopped"); - } - Err(e) => println!(" ❌ Recording failed: {}", e), - } - // 6. 测试音频播放(如果有测试文件) println!("\n6️⃣ Testing Audio Playback..."); if std::path::Path::new("temp/test.wav").exists() { @@ -126,6 +119,18 @@ async fn main() -> anyhow::Result<()> { println!(" ⚠️ No test file found at temp/test.wav, skipping..."); } + // 5. 测试音频录制 + println!("\n5️⃣ Testing Audio Recording (5 seconds)..."); + match server.start_record(addr, AudioConfig::voice_16k()).await { + Ok(_) => { + println!(" ⏺️ Recording started..."); + tokio::time::sleep(std::time::Duration::from_secs(5)).await; + server.stop_record(addr).await?; + println!(" ⏹️ Recording stopped"); + } + Err(e) => println!(" ❌ Recording failed: {}", e), + } + println!("\n═══════════════════════════════════════════════════════"); println!("✅ All tests completed!"); println!("\nServer status:"); diff --git a/packages/client-v2/src/net/protocol.rs b/packages/client-v2/src/net/protocol.rs index 81b4f67..e4f82df 100644 --- a/packages/client-v2/src/net/protocol.rs +++ b/packages/client-v2/src/net/protocol.rs @@ -16,6 +16,13 @@ pub struct ClientInfo { pub serial_number: String, } +/// 音频数据包 - UDP 通道传输 +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct AudioPacket { + /// Opus 编码的音频数据 + pub data: Vec, +} + // ==================== 控制包 ==================== /// 控制包 - TCP 通道传输的所有消息类型 @@ -23,7 +30,10 @@ pub struct ClientInfo { pub enum ControlPacket { // ========== 服务发现 ========== /// 服务发现广播 - Discovery { protocol: String, port: u16 }, + Discovery { + protocol: String, + port: u16, + }, // ========== 握手 ========== /// 服务端握手 @@ -46,9 +56,15 @@ pub enum ControlPacket { // ========== RPC ========== /// RPC 请求(新版,使用 Command 类型) - RpcRequest { id: u32, command: Command }, + RpcRequest { + id: u32, + command: Command, + }, /// RPC 响应(新版,使用 CommandResult 类型) - RpcResponse { id: u32, result: CommandResult }, + RpcResponse { + id: u32, + result: CommandResult, + }, // ========== 事件 ========== /// 服务端事件推送 @@ -56,48 +72,27 @@ pub enum ControlPacket { /// 客户端事件推送 ClientEvent(ClientEvent), + // ========== 订阅管理 ========== + /// 订阅事件类型 + Subscribe { + event_types: Vec, + }, + /// 取消订阅 + Unsubscribe { + event_types: Vec, + }, + // ========== 音频控制 ========== /// 开始录音 - StartRecording { config: AudioConfig }, + StartRecording { + config: AudioConfig, + }, /// 停止录音 StopRecording, /// 开始播放 - StartPlayback { config: AudioConfig }, + StartPlayback { + config: AudioConfig, + }, /// 停止播放 StopPlayback, - - // ========== 订阅管理 ========== - /// 订阅事件类型 - Subscribe { event_types: Vec }, - /// 取消订阅 - Unsubscribe { event_types: Vec }, -} - -// ==================== 音频包 ==================== - -/// 音频数据包 - UDP 通道传输 -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct AudioPacket { - /// Opus 编码的音频数据 - pub data: Vec, -} - -// ==================== 兼容性:旧版 RPC 结果 ==================== - -/// 旧版 RPC 结果(保持向后兼容) -#[derive(Serialize, Deserialize, Debug, Clone, Default)] -pub struct RpcResult { - pub stdout: String, - pub stderr: String, - pub code: i32, -} - -impl From for RpcResult { - fn from(resp: crate::net::command::ShellResponse) -> Self { - Self { - stdout: resp.stdout, - stderr: resp.stderr, - code: resp.exit_code, - } - } } diff --git a/packages/client-v2/src/net/rpc.rs b/packages/client-v2/src/net/rpc.rs index 9cc3ce8..0411be5 100644 --- a/packages/client-v2/src/net/rpc.rs +++ b/packages/client-v2/src/net/rpc.rs @@ -139,29 +139,3 @@ where Err(_) => Err(RpcError::Timeout), } } - -#[cfg(test)] -mod tests { - use super::*; - use crate::net::command::{CommandResult, ShellResponse}; - - #[tokio::test] - async fn test_rpc_manager() { - let mgr = RpcManager::new(); - - let (id, rx) = mgr.register(); - assert_eq!(mgr.pending_count(), 1); - - let result = CommandResult::Shell(ShellResponse { - stdout: "hello".to_string(), - stderr: String::new(), - exit_code: 0, - }); - - mgr.resolve(id, result.clone()); - assert_eq!(mgr.pending_count(), 0); - - let received = rx.await.unwrap(); - assert!(matches!(received, CommandResult::Shell(_))); - } -}