refactor: 重构 Open-XiaoAI Client V2

This commit is contained in:
Del Wang
2026-01-04 11:24:37 +08:00
parent 052ce06355
commit cce3dd630a
17 changed files with 3408 additions and 774 deletions
+200 -1
View File
@@ -1,6 +1,205 @@
# Open-XiaoAI Client V2
> 开发中,敬请期待
> 开发中,敬请期待...
实时音频流推送服务,支持多客户端连接、音频录制/播放、RPC 远程调用和实时事件推送。
## 架构
```
┌─────────────────────────────────────────────────────────────────────────┐
│ Server │
│ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ SessionManager │ │ AudioBus │ │ EventBus │ │
│ │ │ │ (Pub/Sub) │ │ (Broadcast) │ │
│ │ ├─ Session 1 │ │ │ │ │ │
│ │ ├─ Session 2 │◄──►│ ├─ Receiver │ │ ├─ Server → │ │
│ │ └─ Session N │ │ └─ Broadcaster │ │ └─ Client → │ │
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ Command Handler │ │
│ │ ├─ Shell (执行远程命令) │ │
│ │ ├─ GetInfo (获取设备信息) │ │
│ │ ├─ SetVolume (设置音量) │ │
│ │ ├─ File (文件操作) │ │
│ │ └─ System (系统控制) │ │
│ └─────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
│ TCP │ UDP
▼ ▼
┌─────────────────────────────────────────────────────────────────────────┐
│ Client │
│ │
│ ┌─────────────────┐ ┌─────────────────────────────────────────┐ │
│ │ Session │ │ Audio Pipelines │ │
│ │ │ │ │ │
│ │ ├─ Connection │ │ ┌───────────┐ ┌───────────────┐ │ │
│ │ ├─ RPC Manager │ │ │ Record │ │ Playback │ │ │
│ │ └─ Pipelines │◄──►│ │ Pipeline │ │ Pipeline │ │ │
│ └─────────────────┘ │ │ │ │ │ │ │
│ │ │ Mic→Opus │ │ Opus→Speaker │ │ │
│ │ │ →UDP │ │ ←UDP │ │ │
│ │ └───────────┘ └───────────────┘ │ │
│ └─────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
```
## 项目结构
```
src/
├── lib.rs
├── app/
│ ├── server/ # 服务端
│ │ ├── mod.rs # Server 主体
│ │ ├── audio_bus.rs # 音频总线 (发布-订阅)
│ │ ├── session.rs # 会话管理
│ │ └── stream.rs # 音频流 (录音/播放/转发)
│ │
│ └── client/ # 客户端
│ ├── mod.rs # Client 主体
│ ├── session.rs # 会话管理
│ └── pipeline.rs # 音频管道
├── audio/ # 音频处理
│ ├── codec.rs # Opus 编解码
│ ├── config.rs # 音频配置
│ ├── player.rs # ALSA 播放
│ ├── recorder.rs # ALSA 录音
│ └── wav.rs # WAV 文件读写
├── net/ # 网络层
│ ├── command.rs # RPC 命令类型
│ ├── discovery.rs # 服务发现
│ ├── event.rs # 实时事件系统
│ ├── network.rs # TCP/UDP 连接
│ ├── protocol.rs # 通信协议
│ └── rpc.rs # RPC 管理
└── bin/
├── client.rs # 客户端 demo
└── server.rs # 服务端 demo
```
## 功能特性
### RPC 命令系统
支持多种类型的远程调用:
| 命令 | 描述 | 请求 | 响应 |
| ----------- | --------------- | --------------------------------- | ------------------------------------------- |
| `Shell` | 执行 Shell 命令 | command, cwd, env, timeout | stdout, stderr, exit_code |
| `GetInfo` | 获取设备信息 | - | model, serial, version, uptime, audio_state |
| `SetVolume` | 设置音量 | volume (0-100) | previous, current |
| `File` | 文件操作 | Read/Write/Delete/List/Stat | data/entries/stat |
| `System` | 系统控制 | Reboot/Shutdown/GetLoad/GetMemory | Accepted/Load/Memory |
| `Ping` | 延迟测量 | timestamp | timestamp, server_time |
### 实时事件系统
**服务端事件 (Server → Client):**
- `AudioStatusChanged` - 音频状态变化
- `ClientJoined/Left` - 客户端加入/离开
- `Notification` - 通知消息
- `RecordingComplete` - 录音完成
- `PlaybackComplete` - 播放完成
**客户端事件 (Client → Server):**
- `StatusUpdate` - 状态更新 (CPU/内存/温度)
- `AudioLevel` - 音频电平
- `KeyPress` - 按键事件
- `Alert` - 警告/错误
### 音频功能
- **录音**: 从客户端麦克风录制,服务端保存为 WAV
- **播放**: 服务端推送音频文件到客户端播放
- **编码**: Opus 编解码,支持 16kHz/48kHz
- **传输**: UDP 低延迟传输
## 使用方法
### 启动服务端
```bash
cargo run --bin server --release
```
### 启动客户端 (Linux)
```bash
cargo run --bin client --release
```
### 交叉编译 (ARM)
```bash
make build-arm
```
## 配置
通过环境变量配置认证:
```bash
export XIAO_SERVER_AUTH="your-server-secret"
export XIAO_CLIENT_AUTH="your-client-secret"
```
## 示例代码
### 服务端
```rust
use xiao::app::server::Server;
use xiao::net::command::Command;
let server = Arc::new(Server::new().await?);
// 启动服务器
tokio::spawn(async move {
server.run(8080).await.unwrap();
});
// 执行远程命令
let result = server.shell(client_addr, "uname -a").await?;
println!("Output: {}", result.stdout);
// 开始录音
server.start_record(client_addr, AudioConfig::voice_16k()).await?;
// 广播事件
server.broadcast_event(ServerEvent::Notification {
level: NotificationLevel::Info,
title: "Notice".to_string(),
message: "Hello everyone!".to_string(),
}).await;
```
### 客户端
```rust
use xiao::app::client::{Client, ClientConfig};
let client = Arc::new(Client::new(ClientConfig::default()));
// 订阅服务端事件
let mut events = client.subscribe_events();
tokio::spawn(async move {
while let Ok(event) = events.recv().await {
println!("Event: {:?}", event);
}
});
// 运行客户端
client.run().await?;
```
## License
@@ -1,151 +0,0 @@
use crate::audio::codec::OpusCodec;
use crate::audio::config::AudioConfig;
use crate::audio::player::AudioPlayer;
use crate::audio::recorder::AudioRecorder;
use crate::net::network::AudioSocket;
use crate::net::protocol::AudioPacket;
use std::net::SocketAddr;
use std::sync::Arc;
use tokio::sync::{RwLock, mpsc};
use tokio_util::sync::CancellationToken;
pub struct ClientAudioManager {
audio_socket: Arc<AudioSocket>,
audio_addr: SocketAddr,
session_cancel: CancellationToken,
record_cancel: RwLock<Option<CancellationToken>>,
play_cancel: RwLock<Option<CancellationToken>>,
}
impl ClientAudioManager {
pub fn new(
audio_socket: Arc<AudioSocket>,
audio_addr: SocketAddr,
session_cancel: CancellationToken,
) -> Self {
Self {
audio_socket,
audio_addr,
session_cancel,
record_cancel: RwLock::new(None),
play_cancel: RwLock::new(None),
}
}
pub async fn stop_recorder(&self) {
let mut cancel_guard = self.record_cancel.write().await;
if let Some(token) = cancel_guard.take() {
token.cancel();
}
}
pub async fn stop_player(&self) {
let mut cancel_guard = self.play_cancel.write().await;
if let Some(token) = cancel_guard.take() {
token.cancel();
}
}
pub async fn start_recording(&self, config: AudioConfig) {
self.stop_recorder().await;
let token = self.session_cancel.child_token();
*self.record_cancel.write().await = Some(token.clone());
let audio_socket = self.audio_socket.clone();
let audio_addr = self.audio_addr;
tokio::spawn(async move {
let (pcm_tx, mut pcm_rx) = mpsc::channel::<Vec<i16>>(32);
let conf = config.clone();
// 录音线程 (ALSA 阻塞)
std::thread::spawn(move || {
let recorder = match AudioRecorder::new(&conf) {
Ok(r) => r,
Err(e) => {
eprintln!("Failed to start recorder: {}", e);
return;
}
};
let mut buf = vec![0i16; conf.frame_size];
while let Ok(n) = recorder.read(&mut buf) {
if pcm_tx.blocking_send(buf[..n].to_vec()).is_err() {
break;
}
}
});
let mut codec = match OpusCodec::new(&config) {
Ok(c) => c,
Err(e) => {
eprintln!("Failed to init opus codec: {}", e);
return;
}
};
println!("Recording started...");
loop {
tokio::select! {
_ = token.cancelled() => break,
Some(pcm) = pcm_rx.recv() => {
let mut out = vec![0u8; 4096];
if let Ok(len) = codec.encode(&pcm, &mut out) {
let _ = audio_socket.send(&AudioPacket { data: out[..len].to_vec() }, audio_addr).await;
}
}
}
}
println!("Recording stopped.");
});
}
pub async fn start_playback(&self, config: AudioConfig) {
self.stop_player().await;
let token = self.session_cancel.child_token();
*self.play_cancel.write().await = Some(token.clone());
let audio_socket = self.audio_socket.clone();
tokio::spawn(async move {
let (pcm_tx, mut pcm_rx) = mpsc::channel::<Vec<i16>>(32);
let conf = config.clone();
// 播放线程 (ALSA 阻塞)
std::thread::spawn(move || {
let player = match AudioPlayer::new(&conf) {
Ok(p) => p,
Err(e) => {
eprintln!("Failed to start player: {}", e);
return;
}
};
while let Some(pcm) = pcm_rx.blocking_recv() {
let _ = player.write(&pcm);
}
});
let mut codec = match OpusCodec::new(&config) {
Ok(c) => c,
Err(e) => {
eprintln!("Failed to init opus codec: {}", e);
return;
}
};
let mut udp_buf = vec![0u8; 4096];
println!("Playback started...");
loop {
tokio::select! {
_ = token.cancelled() => break,
res = audio_socket.recv(&mut udp_buf) => {
if let Ok((packet, _)) = res {
let mut pcm = vec![0i16; config.frame_size];
if let Ok(n) = codec.decode(&packet.data, &mut pcm) {
let _ = pcm_tx.send(pcm[..n].to_vec()).await;
}
}
}
}
}
println!("Playback stopped.");
});
}
}
+342 -155
View File
@@ -1,166 +1,227 @@
//! # Client 模块
//!
//! 音频客户端,支持:
//! - 服务发现和自动连接
//! - 音频录制和播放
//! - RPC 远程调用
//! - 实时事件处理
//!
//! ## 架构
//!
//! ```text
//! ┌─────────────────────────────────────┐
//! │ Client │
//! │ │
//! Server ◀──TCP─────┼──▶ Session │
//! │ ├─ Connection │
//! │ ├─ RPC Manager │
//! │ └─ Active Pipelines │
//! │ │
//! Server ◀──UDP─────┼──▶ Audio Pipelines │
//! │ ├─ RecordPipeline │
//! │ └─ PlaybackPipeline │
//! │ │
//! Events ◀──────────┼──▶ Event Handlers │
//! └─────────────────────────────────────┘
//! ```
#![cfg(target_os = "linux")]
mod audio_manager;
mod pipeline;
mod session;
pub use pipeline::{PipelineHandle, PlaybackPipeline, RecordPipeline};
pub use session::Session;
use crate::net::command::{
AudioState, Command, CommandError, CommandResult, DeviceInfo, SetVolumeResponse, ShellRequest,
ShellResponse,
};
use crate::net::discovery::Discovery;
use crate::net::event::{ClientEvent, NotificationLevel, ServerEvent};
use crate::net::network::{AudioSocket, Connection};
use crate::net::protocol::{ClientInfo, ControlPacket, RpcResult};
use crate::net::rpc::RpcManager;
use crate::net::protocol::{ClientInfo, ControlPacket};
use anyhow::{Result, anyhow};
use audio_manager::ClientAudioManager;
use session::handshake;
use std::net::SocketAddr;
use std::sync::Arc;
use tokio::sync::{RwLock};
use tokio::sync::{RwLock, broadcast};
use tokio_util::sync::CancellationToken;
struct ActiveSession {
conn: Arc<Connection>,
audio_manager: ClientAudioManager,
session_cancel: CancellationToken,
/// 客户端配置
pub struct ClientConfig {
/// 客户端型号
pub model: String,
/// 序列号
pub serial_number: String,
/// 心跳间隔(秒)
pub heartbeat_interval: u64,
/// 连接超时(秒)
pub timeout: u64,
}
impl Default for ClientConfig {
fn default() -> Self {
Self {
model: "Open-XiaoAi-V2".to_string(),
serial_number: "00:00:00:00:00:00".to_string(),
heartbeat_interval: 10,
timeout: 60,
}
}
}
/// 音频客户端
pub struct Client {
session: RwLock<Option<Arc<ActiveSession>>>,
rpc: Arc<RpcManager>,
/// 配置
config: ClientConfig,
/// 当前活动会话
session: RwLock<Option<Arc<Session>>>,
/// 全局取消令牌
cancel: CancellationToken,
/// 服务端事件广播
server_events: broadcast::Sender<ServerEvent>,
/// 客户端启动时间
started_at: std::time::Instant,
}
impl Client {
pub fn new() -> Self {
/// 创建新客户端
pub fn new(config: ClientConfig) -> Self {
let (server_events, _) = broadcast::channel(64);
Self {
config,
session: RwLock::new(None),
rpc: Arc::new(RpcManager::new()),
cancel: CancellationToken::new(),
server_events,
started_at: std::time::Instant::now(),
}
}
/// 使用默认配置创建客户端
pub fn with_defaults() -> Self {
Self::new(ClientConfig::default())
}
/// 订阅服务端事件
pub fn subscribe_events(&self) -> broadcast::Receiver<ServerEvent> {
self.server_events.subscribe()
}
/// 运行客户端(自动发现服务器并连接)
pub async fn run(self: Arc<Self>) -> Result<()> {
loop {
println!("Searching for server...");
let (ip, tcp_port) = match Discovery::listen().await {
Ok(res) => res,
Err(e) => {
eprintln!("Discovery listen error: {}", e);
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
continue;
tokio::select! {
_ = self.cancel.cancelled() => {
println!("[Client] Shutting down...");
break;
}
};
let addr = SocketAddr::new(ip, tcp_port);
println!("Found server at {}", addr);
match tokio::net::TcpStream::connect(addr).await {
Err(e) => eprintln!("Failed to connect to {}: {}", addr, e),
Ok(stream) => {
println!("Connected to TCP server at {}", addr);
if let Err(e) = self.clone().handle_session(stream, addr).await {
eprintln!("Session error: {:?}", e);
result = self.discover_and_connect() => {
if let Err(e) = result {
eprintln!("[Client] Connection error: {}", e);
}
// 连接断开后清理并重试
self.cleanup().await;
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
}
}
self.cleanup().await;
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
}
Ok(())
}
async fn cleanup(&self) {
let mut session_guard = self.session.write().await;
if let Some(s) = session_guard.take() {
s.session_cancel.cancel();
}
/// 发现服务器并连接
async fn discover_and_connect(&self) -> Result<()> {
println!("[Client] Searching for server...");
let (ip, tcp_port) = Discovery::listen().await?;
let server_addr = SocketAddr::new(ip, tcp_port);
println!("[Client] Found server at {}", server_addr);
let stream = tokio::net::TcpStream::connect(server_addr).await?;
println!("[Client] Connected to {}", server_addr);
self.handle_session(stream, server_addr).await
}
/// 处理会话
async fn handle_session(
self: Arc<Self>,
&self,
stream: tokio::net::TcpStream,
addr: SocketAddr,
server_addr: SocketAddr,
) -> Result<()> {
let conn = Arc::new(Connection::new(stream)?);
let audio_socket = Arc::new(AudioSocket::bind().await?);
// --- 握手 (Handshake) ---
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());
conn.send(&ControlPacket::ClientHello {
auth: server_auth,
version: version.clone(),
udp_port: audio_socket.port(),
// todo client info
info: ClientInfo {
model: "Open-XiaoAi-V2".to_string(),
serial_number: "00:00:00:00:00:00".to_string(),
},
})
.await?;
let server_udp_port = match conn.recv().await? {
ControlPacket::ServerHello {
version: v,
udp_port,
auth,
} => {
if v != version {
return Err(anyhow!("Server version mismatch: {} != {}", v, version));
}
if auth != client_auth {
return Err(anyhow!("Invalid server auth"));
}
udp_port
}
_ => return Err(anyhow!("Handshake failed")),
// 执行握手
let client_info = ClientInfo {
model: self.config.model.clone(),
serial_number: self.config.serial_number.clone(),
};
let audio_addr = SocketAddr::new(addr.ip(), server_udp_port);
let handshake_result =
handshake(&conn, audio_socket.port(), server_addr, client_info).await?;
println!(
"Handshake successful with {}, audio at {}",
addr, audio_addr
"[Client] Handshake OK, server audio at {}",
handshake_result.server_audio_addr
);
// --- 初始化 Session ---
let session_cancel = CancellationToken::new();
let session = Arc::new(ActiveSession {
conn: conn.clone(),
audio_manager: ClientAudioManager::new(
audio_socket,
audio_addr,
session_cancel.clone(),
),
session_cancel,
});
// 创建会话
let session_cancel = self.cancel.child_token();
let session = Arc::new(Session::new(
conn.clone(),
audio_socket,
handshake_result.server_audio_addr,
session_cancel.clone(),
));
// 存储会话
*self.session.write().await = Some(session.clone());
// 心跳
let hb_session = session.clone();
// 启动心跳
self.spawn_heartbeat(session.clone());
// 运行消息循环
self.message_loop(session).await
}
/// 启动心跳任务
fn spawn_heartbeat(&self, session: Arc<Session>) {
let interval = std::time::Duration::from_secs(self.config.heartbeat_interval);
tokio::spawn(async move {
let mut interval = tokio::time::interval(std::time::Duration::from_secs(10));
let mut ticker = tokio::time::interval(interval);
loop {
tokio::select! {
_ = hb_session.session_cancel.cancelled() => break,
_ = interval.tick() => {
if hb_session.conn.send(&ControlPacket::Ping).await.is_err() { break; }
_ = session.cancel.cancelled() => break,
_ = ticker.tick() => {
if session.send(&ControlPacket::Ping).await.is_err() {
break;
}
}
}
}
});
}
/// 消息主循环
async fn message_loop(&self, session: Arc<Session>) -> Result<()> {
let timeout = std::time::Duration::from_secs(self.config.timeout);
// 消息主循环
loop {
tokio::select! {
_ = session.session_cancel.cancelled() => break,
res = tokio::time::timeout(std::time::Duration::from_secs(60), conn.recv()) => {
match res {
_ = session.cancel.cancelled() => break,
result = tokio::time::timeout(timeout, session.recv()) => {
match result {
Ok(Ok(packet)) => {
if let Err(e) = self.process_packet(packet, &session).await {
eprintln!("Process packet error: {}", e);
}
self.handle_packet(&session, packet).await?;
}
Ok(Err(e)) => {
return Err(anyhow!("Connection receive error: {}", e));
return Err(anyhow!("Connection error: {}", e));
}
Err(_) => {
return Err(anyhow!("Connection timeout"));
}
Err(_) => return Err(anyhow!("Connection timeout")),
}
}
}
@@ -169,87 +230,213 @@ impl Client {
Ok(())
}
// todo 考虑有些操作比较耗时,需要非阻塞处理
async fn process_packet(
&self,
packet: ControlPacket,
session: &Arc<ActiveSession>,
) -> Result<()> {
/// 处理控制包
async fn handle_packet(&self, session: &Arc<Session>, packet: ControlPacket) -> Result<()> {
match packet {
ControlPacket::Ping => {
session.conn.send(&ControlPacket::Pong).await?;
session.send(&ControlPacket::Pong).await?;
}
ControlPacket::Pong => {}
ControlPacket::RpcResponse { id, result } => {
self.rpc.resolve(id, result);
session.resolve_rpc(id, result);
}
ControlPacket::RpcRequest { id, method, args } => {
let result = self.handle_rpc(&method, args).await;
ControlPacket::RpcRequest { id, command } => {
let result = self.handle_command(session, command).await;
session
.conn
.send(&ControlPacket::RpcResponse { id, result })
.await?;
}
ControlPacket::StartRecording { config } => {
session.audio_manager.start_recording(config).await;
ControlPacket::ServerEvent(event) => {
self.handle_server_event(session, event).await;
}
ControlPacket::StartPlayback { config } => {
session.audio_manager.start_playback(config).await;
ControlPacket::StartRecording { config } => {
println!("[Client] Starting recording...");
let handle = RecordPipeline::spawn(
config,
session.audio_socket.clone(),
session.server_audio_addr,
session.cancel.clone(),
);
session.start_recording(handle);
}
ControlPacket::StopRecording => {
session.audio_manager.stop_recorder().await;
println!("[Client] Stopping recording...");
session.stop_recording();
}
ControlPacket::StartPlayback { config } => {
println!("[Client] Starting playback...");
let handle = PlaybackPipeline::spawn(
config,
session.audio_socket.clone(),
session.cancel.clone(),
);
session.start_playback(handle);
}
ControlPacket::StopPlayback => {
session.audio_manager.stop_player().await;
println!("[Client] Stopping playback...");
session.stop_playback();
}
_ => {}
}
Ok(())
}
async fn handle_rpc(&self, method: &str, args: Vec<String>) -> RpcResult {
match method {
"shell" if !args.is_empty() => {
let output = std::process::Command::new("sh")
.arg("-c")
.arg(&args[0])
.output();
match output {
Ok(out) => RpcResult {
stdout: String::from_utf8_lossy(&out.stdout).to_string(),
stderr: String::from_utf8_lossy(&out.stderr).to_string(),
code: out.status.code().unwrap_or(0),
},
Err(e) => RpcResult {
stderr: e.to_string(),
code: -1,
..Default::default()
},
/// 处理 RPC 命令
async fn handle_command(&self, session: &Arc<Session>, command: Command) -> CommandResult {
match command {
Command::Shell(req) => self.handle_shell(req),
Command::GetInfo => self.handle_get_info(session),
Command::Ping { timestamp } => {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_millis() as u64;
CommandResult::Pong {
timestamp,
server_time: now,
}
}
_ => RpcResult {
stderr: "Unsupported method".to_string(),
code: -1,
..Default::default()
},
Command::SetVolume(req) => {
let prev = session.set_volume(req.volume);
CommandResult::Volume(SetVolumeResponse {
previous: prev,
current: session.volume(),
})
}
_ => CommandResult::Error(CommandError::not_implemented()),
}
}
pub async fn call(&self, method: &str, args: Vec<String>) -> Result<RpcResult> {
let (id, rx) = self.rpc.register();
/// 处理 Shell 命令
fn handle_shell(&self, req: ShellRequest) -> CommandResult {
let mut cmd = std::process::Command::new("sh");
cmd.arg("-c").arg(&req.command);
if let Some(cwd) = &req.cwd {
cmd.current_dir(cwd);
}
if let Some(env) = &req.env {
for (k, v) in env {
cmd.env(k, v);
}
}
match cmd.output() {
Ok(output) => CommandResult::Shell(ShellResponse {
stdout: String::from_utf8_lossy(&output.stdout).to_string(),
stderr: String::from_utf8_lossy(&output.stderr).to_string(),
exit_code: output.status.code().unwrap_or(-1),
}),
Err(e) => CommandResult::Error(CommandError::internal(e.to_string())),
}
}
/// 处理获取设备信息
fn handle_get_info(&self, session: &Arc<Session>) -> CommandResult {
CommandResult::Info(DeviceInfo {
model: self.config.model.clone(),
serial_number: self.config.serial_number.clone(),
version: env!("CARGO_PKG_VERSION").to_string(),
uptime_secs: session.uptime_secs(),
audio_state: AudioState {
is_recording: session.is_recording(),
is_playing: session.is_playing(),
volume: session.volume(),
},
})
}
/// 处理服务端事件
async fn handle_server_event(&self, _session: &Arc<Session>, event: ServerEvent) {
// 广播给订阅者
let _ = self.server_events.send(event.clone());
// 本地处理
match &event {
ServerEvent::Notification {
level,
title,
message,
} => {
println!("[Event] [{:?}] {}: {}", level, title, message);
}
ServerEvent::AudioStatusChanged {
is_recording,
is_playing,
} => {
println!(
"[Event] Audio status: recording={}, playing={}",
is_recording, is_playing
);
}
ServerEvent::ClientJoined { addr, model } => {
println!("[Event] Client joined: {} ({})", model, addr);
}
ServerEvent::ClientLeft { addr, model } => {
println!("[Event] Client left: {} ({})", model, addr);
}
_ => {}
}
}
/// 清理资源
async fn cleanup(&self) {
let mut session_guard = self.session.write().await;
if let Some(session) = session_guard.take() {
session.cleanup();
println!("[Client] Session cleaned up");
}
}
// ==================== 公开 API ====================
/// 执行 RPC 命令
pub async fn execute(&self, command: Command) -> Result<CommandResult> {
let session_guard = self.session.read().await;
if let Some(session) = session_guard.as_ref() {
session
.conn
.send(&ControlPacket::RpcRequest {
id,
method: method.to_string(),
args,
})
.await?;
Ok(rx.await?)
session.execute(command).await
} else {
Err(anyhow!("Not connected"))
}
}
/// 执行 Shell 命令
pub async fn shell(&self, cmd: &str) -> Result<ShellResponse> {
let result = self.execute(Command::shell(cmd)).await?;
match result {
CommandResult::Shell(resp) => Ok(resp),
CommandResult::Error(e) => Err(anyhow!("{}", e)),
_ => Err(anyhow!("Unexpected response type")),
}
}
/// 发送客户端事件
pub async fn send_event(&self, event: ClientEvent) -> Result<()> {
let session_guard = self.session.read().await;
if let Some(session) = session_guard.as_ref() {
session.send(&ControlPacket::ClientEvent(event)).await
} else {
Err(anyhow!("Not connected"))
}
}
/// 发送警告事件
pub async fn send_alert(&self, level: NotificationLevel, message: &str) -> Result<()> {
self.send_event(ClientEvent::Alert {
level,
message: message.to_string(),
})
.await
}
/// 检查是否已连接
pub async fn is_connected(&self) -> bool {
self.session.read().await.is_some()
}
/// 关闭客户端
pub fn shutdown(&self) {
self.cancel.cancel();
}
}
@@ -0,0 +1,276 @@
//! # Audio Pipeline - 音频管道抽象
//!
//! 提供录音和播放的管道化处理。
//!
//! ## 设计
//!
//! ```text
//! RecordPipeline:
//! ┌──────────┐ ┌────────────┐ ┌────────────┐
//! │ ALSA Mic │────▶│ Opus Encode│────▶│ UDP Send │
//! │ (thread) │ │ (async) │ │ (async) │
//! └──────────┘ └────────────┘ └────────────┘
//!
//! PlaybackPipeline:
//! ┌────────────┐ ┌────────────┐ ┌────────────┐
//! │ UDP Recv │────▶│ Opus Decode│────▶│ ALSA Play │
//! │ (async) │ │ (async) │ │ (thread) │
//! └────────────┘ └────────────┘ └────────────┘
//! ```
use crate::audio::codec::OpusCodec;
use crate::audio::config::AudioConfig;
use crate::audio::player::AudioPlayer;
use crate::audio::recorder::AudioRecorder;
use crate::net::network::AudioSocket;
use crate::net::protocol::AudioPacket;
use std::net::SocketAddr;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use tokio::sync::mpsc;
use tokio_util::sync::CancellationToken;
/// 管道句柄 - 用于控制正在运行的音频管道
pub struct PipelineHandle {
cancel: CancellationToken,
/// 用于通知阻塞线程停止
stop_flag: Arc<AtomicBool>,
}
impl PipelineHandle {
fn new(cancel: CancellationToken, stop_flag: Arc<AtomicBool>) -> Self {
Self { cancel, stop_flag }
}
/// 停止管道
pub fn stop(&self) {
self.stop_flag.store(true, Ordering::SeqCst);
self.cancel.cancel();
}
/// 检查是否已停止
pub fn is_stopped(&self) -> bool {
self.cancel.is_cancelled()
}
}
impl Drop for PipelineHandle {
fn drop(&mut self) {
self.stop();
}
}
/// 录音管道
/// 从麦克风捕获 -> Opus 编码 -> UDP 发送到服务器
pub struct RecordPipeline;
impl RecordPipeline {
/// 启动录音管道
///
/// # Arguments
/// * `config` - 音频配置
/// * `socket` - UDP socket
/// * `target` - 目标服务器地址
/// * `parent_cancel` - 父级取消令牌
pub fn spawn(
config: AudioConfig,
socket: Arc<AudioSocket>,
target: SocketAddr,
parent_cancel: CancellationToken,
) -> PipelineHandle {
let cancel = parent_cancel.child_token();
let stop_flag = Arc::new(AtomicBool::new(false));
let handle = PipelineHandle::new(cancel.clone(), stop_flag.clone());
let token = cancel.clone();
tokio::spawn(async move {
if let Err(e) = Self::run(config, socket, target, token, stop_flag).await {
eprintln!("[RecordPipeline] Error: {}", e);
}
});
handle
}
async fn run(
config: AudioConfig,
socket: Arc<AudioSocket>,
target: SocketAddr,
cancel: CancellationToken,
stop_flag: Arc<AtomicBool>,
) -> anyhow::Result<()> {
// 创建 PCM 数据通道
let (pcm_tx, mut pcm_rx) = mpsc::channel::<Vec<i16>>(32);
// 启动 ALSA 录音线程(阻塞 I/O)
let recorder_config = config.clone();
let recorder_stop = stop_flag.clone();
std::thread::spawn(move || {
let recorder = match AudioRecorder::new(&recorder_config) {
Ok(r) => r,
Err(e) => {
eprintln!("[RecordPipeline] Failed to create recorder: {}", e);
return;
}
};
let mut buf = vec![0i16; recorder_config.frame_size];
// 使用 stop_flag 来优雅退出
while !recorder_stop.load(Ordering::SeqCst) {
match recorder.read(&mut buf) {
Ok(n) if n > 0 => {
if pcm_tx.blocking_send(buf[..n].to_vec()).is_err() {
break;
}
}
Ok(_) => {
// 空读取,继续
}
Err(e) => {
eprintln!("[RecordPipeline] Read error: {}", e);
break;
}
}
}
});
// 创建 Opus 编码器
let mut codec = OpusCodec::new(&config)?;
let mut opus_buf = vec![0u8; 4096];
println!("[RecordPipeline] Started -> {}", target);
// 主循环:从录音线程接收 PCM,编码后发送
loop {
tokio::select! {
_ = cancel.cancelled() => break,
pcm = pcm_rx.recv() => {
match pcm {
Some(samples) => {
if let Ok(len) = codec.encode(&samples, &mut opus_buf) {
let packet = AudioPacket {
data: opus_buf[..len].to_vec(),
};
let _ = socket.send(&packet, target).await;
}
}
None => {
// 录音线程已退出
break;
}
}
}
}
}
println!("[RecordPipeline] Stopped");
Ok(())
}
}
/// 播放管道
/// 从 UDP 接收 -> Opus 解码 -> 扬声器播放
pub struct PlaybackPipeline;
impl PlaybackPipeline {
/// 启动播放管道
///
/// # Arguments
/// * `config` - 音频配置
/// * `socket` - UDP socket
/// * `parent_cancel` - 父级取消令牌
pub fn spawn(
config: AudioConfig,
socket: Arc<AudioSocket>,
parent_cancel: CancellationToken,
) -> PipelineHandle {
let cancel = parent_cancel.child_token();
let stop_flag = Arc::new(AtomicBool::new(false));
let handle = PipelineHandle::new(cancel.clone(), stop_flag.clone());
let token = cancel.clone();
tokio::spawn(async move {
if let Err(e) = Self::run(config, socket, token, stop_flag).await {
eprintln!("[PlaybackPipeline] Error: {}", e);
}
});
handle
}
async fn run(
config: AudioConfig,
socket: Arc<AudioSocket>,
cancel: CancellationToken,
stop_flag: Arc<AtomicBool>,
) -> anyhow::Result<()> {
// 创建 PCM 数据通道
let (pcm_tx, pcm_rx) = mpsc::channel::<Vec<i16>>(32);
// 启动 ALSA 播放线程(阻塞 I/O)
let player_config = config.clone();
let player_stop = stop_flag.clone();
std::thread::spawn(move || {
let player = match AudioPlayer::new(&player_config) {
Ok(p) => p,
Err(e) => {
eprintln!("[PlaybackPipeline] Failed to create player: {}", e);
return;
}
};
// 使用 blocking_recv 在线程中接收
let mut rx = pcm_rx;
while !player_stop.load(Ordering::SeqCst) {
match rx.blocking_recv() {
Some(samples) => {
if let Err(e) = player.write(&samples) {
eprintln!("[PlaybackPipeline] Write error: {}", e);
break;
}
}
None => {
// 通道关闭
break;
}
}
}
});
// 创建 Opus 解码器
let mut codec = OpusCodec::new(&config)?;
let mut pcm_buf = vec![0i16; config.frame_size];
let mut udp_buf = vec![0u8; 4096];
println!("[PlaybackPipeline] Started");
// 主循环:从 UDP 接收,解码后发送给播放线程
loop {
tokio::select! {
_ = cancel.cancelled() => break,
result = socket.recv(&mut udp_buf) => {
match result {
Ok((packet, _src)) => {
if let Ok(n) = codec.decode(&packet.data, &mut pcm_buf) {
// 使用 try_send 避免阻塞
let _ = pcm_tx.try_send(pcm_buf[..n].to_vec());
}
}
Err(e) => {
eprintln!("[PlaybackPipeline] Recv error: {}", e);
}
}
}
}
}
println!("[PlaybackPipeline] Stopped");
Ok(())
}
}
@@ -0,0 +1,264 @@
//! # Client Session - 客户端会话管理
//!
//! 轻量级的会话结构,负责:
//! - TCP 控制连接
//! - RPC 管理
//! - 会话生命周期
//! - 活动音频流追踪
use crate::net::command::{Command, CommandResult};
use crate::net::network::{AudioSocket, Connection};
use crate::net::protocol::{ClientInfo, ControlPacket};
use crate::net::rpc::RpcManager;
use anyhow::{Context, Result, anyhow};
use std::net::SocketAddr;
use std::sync::Arc;
use tokio_util::sync::CancellationToken;
use super::pipeline::PipelineHandle;
/// 活动管道追踪
pub struct ActivePipelines {
pub recorder: Option<PipelineHandle>,
pub player: Option<PipelineHandle>,
}
impl Default for ActivePipelines {
fn default() -> Self {
Self {
recorder: None,
player: None,
}
}
}
impl ActivePipelines {
pub fn stop_all(&mut self) {
if let Some(h) = self.recorder.take() {
h.stop();
}
if let Some(h) = self.player.take() {
h.stop();
}
}
pub fn start_recording(&mut self, handle: PipelineHandle) {
if let Some(h) = self.recorder.take() {
h.stop();
}
self.recorder = Some(handle);
}
pub fn stop_recording(&mut self) {
if let Some(h) = self.recorder.take() {
h.stop();
}
}
pub fn start_playback(&mut self, handle: PipelineHandle) {
if let Some(h) = self.player.take() {
h.stop();
}
self.player = Some(handle);
}
pub fn stop_playback(&mut self) {
if let Some(h) = self.player.take() {
h.stop();
}
}
pub fn is_recording(&self) -> bool {
self.recorder.is_some()
}
pub fn is_playing(&self) -> bool {
self.player.is_some()
}
}
/// 客户端会话
pub struct Session {
/// TCP 控制连接
pub conn: Arc<Connection>,
/// UDP 音频 socket
pub audio_socket: Arc<AudioSocket>,
/// 服务器音频地址
pub server_audio_addr: SocketAddr,
/// RPC 管理器
pub rpc: Arc<RpcManager>,
/// 会话取消令牌
pub cancel: CancellationToken,
/// 活动管道
pipelines: parking_lot::Mutex<ActivePipelines>,
/// 会话创建时间
created_at: std::time::Instant,
/// 当前音量
volume: parking_lot::Mutex<u8>,
}
impl Session {
/// 创建新会话
pub fn new(
conn: Arc<Connection>,
audio_socket: Arc<AudioSocket>,
server_audio_addr: SocketAddr,
cancel: CancellationToken,
) -> Self {
Self {
conn,
audio_socket,
server_audio_addr,
rpc: Arc::new(RpcManager::new()),
cancel,
pipelines: parking_lot::Mutex::new(ActivePipelines::default()),
created_at: std::time::Instant::now(),
volume: parking_lot::Mutex::new(100),
}
}
/// 检查会话是否仍然有效
pub fn is_alive(&self) -> bool {
!self.cancel.is_cancelled()
}
/// 获取会话运行时间
pub fn uptime_secs(&self) -> u64 {
self.created_at.elapsed().as_secs()
}
/// 发送控制包
pub async fn send(&self, packet: &ControlPacket) -> Result<()> {
self.conn.send(packet).await
}
/// 接收控制包
pub async fn recv(&self) -> Result<ControlPacket> {
self.conn.recv().await
}
/// 发起 RPC 调用(新版)
pub async fn execute(&self, command: Command) -> Result<CommandResult> {
let (id, rx) = self.rpc.register();
self.conn
.send(&ControlPacket::RpcRequest { id, command })
.await?;
rx.await.context("RPC channel closed")
}
/// 处理 RPC 响应
pub fn resolve_rpc(&self, id: u32, result: CommandResult) {
self.rpc.resolve(id, result);
}
/// 开始录音管道
pub fn start_recording(&self, handle: PipelineHandle) {
self.pipelines.lock().start_recording(handle);
}
/// 停止录音管道
pub fn stop_recording(&self) {
self.pipelines.lock().stop_recording();
}
/// 开始播放管道
pub fn start_playback(&self, handle: PipelineHandle) {
self.pipelines.lock().start_playback(handle);
}
/// 停止播放管道
pub fn stop_playback(&self) {
self.pipelines.lock().stop_playback();
}
/// 检查是否正在录音
pub fn is_recording(&self) -> bool {
self.pipelines.lock().is_recording()
}
/// 检查是否正在播放
pub fn is_playing(&self) -> bool {
self.pipelines.lock().is_playing()
}
/// 获取当前音量
pub fn volume(&self) -> u8 {
*self.volume.lock()
}
/// 设置音量
pub fn set_volume(&self, vol: u8) -> u8 {
let mut v = self.volume.lock();
let prev = *v;
*v = vol.min(100);
prev
}
/// 清理所有资源
pub fn cleanup(&self) {
self.cancel.cancel();
self.pipelines.lock().stop_all();
}
}
impl Drop for Session {
fn drop(&mut self) {
self.cleanup();
}
}
/// 握手结果
pub struct HandshakeResult {
pub server_audio_addr: SocketAddr,
}
/// 执行客户端握手
pub async fn handshake(
conn: &Connection,
audio_port: u16,
server_addr: SocketAddr,
client_info: ClientInfo,
) -> Result<HandshakeResult> {
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,
})
.await?;
// 等待 ServerHello
let server_udp_port = match conn.recv().await? {
ControlPacket::ServerHello {
version: v,
udp_port,
auth,
} => {
if v != version {
return Err(anyhow!("Server version mismatch: {} != {}", v, version));
}
if auth != client_auth {
return Err(anyhow!("Invalid server auth"));
}
udp_port
}
_ => return Err(anyhow!("Expected ServerHello")),
};
let server_audio_addr = SocketAddr::new(server_addr.ip(), server_udp_port);
Ok(HandshakeResult { server_audio_addr })
}
@@ -0,0 +1,250 @@
//! # AudioBus - 音频总线
//!
//! 核心的音频路由模块,采用发布-订阅模式处理多客户端音频流。
//!
//! ## 设计理念
//!
//! ```text
//! ┌──────────────────────────────────────────┐
//! │ AudioBus │
//! │ │
//! Client A ──UDP──▶│ ┌─────────┐ ┌──────────────────┐ │
//! │ │ Ingress │──────▶ BroadcastChannel │ │──▶ Client B, C...
//! Client B ──UDP──▶│ │ Router │ └──────────────────┘ │
//! │ └─────────┘ │
//! │ │ │
//! │ ▼ │
//! │ ┌─────────────┐ │
//! │ │ Recorder │──▶ WAV File │
//! │ └─────────────┘ │
//! │ │
//! │ ┌─────────────┐ │
//! │ │ Playback │◀── WAV/MP3 File │──▶ Client X
//! │ └─────────────┘ │
//! └──────────────────────────────────────────┘
//! ```
//!
//! ## 核心概念
//!
//! - **Subscriber**: 订阅者,可以是客户端或录音器
//! - **Publisher**: 发布者,可以是客户端麦克风或文件播放器
//! - **Channel**: 频道,用于隔离不同的音频流组
use crate::net::network::AudioSocket;
use crate::net::protocol::AudioPacket;
use dashmap::DashMap;
use std::net::SocketAddr;
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
use tokio::sync::broadcast;
/// 订阅者 ID
pub type SubscriberId = u64;
/// 音频包及其来源
#[derive(Clone, Debug)]
pub struct AudioFrame {
/// 音频数据包
pub packet: AudioPacket,
/// 发送者地址(如果来自网络)
pub source: Option<SocketAddr>,
/// 时间戳(单调递增)
pub timestamp: u64,
}
/// 订阅者信息
#[derive(Clone)]
pub struct Subscriber {
pub id: SubscriberId,
/// UDP 目标地址
pub addr: SocketAddr,
/// 是否过滤自己的音频(防止回声)
pub filter_self: bool,
}
/// 音频总线 - 负责音频流的路由和分发
pub struct AudioBus {
/// UDP socket 用于收发音频
socket: Arc<AudioSocket>,
/// 广播通道 - 发布订阅模式的核心
/// 所有接收到的音频都会广播到这个 channel
broadcast_tx: broadcast::Sender<AudioFrame>,
/// 订阅者注册表
/// key: SocketAddr (UDP 地址)
/// value: Subscriber
subscribers: DashMap<SocketAddr, Subscriber>,
/// 地址到订阅者 ID 的反向映射
addr_to_id: DashMap<SocketAddr, SubscriberId>,
/// ID 生成器
next_id: AtomicU64,
/// 全局时间戳
timestamp: AtomicU64,
}
impl AudioBus {
/// 创建新的音频总线
pub async fn new() -> anyhow::Result<Self> {
let socket = Arc::new(AudioSocket::bind().await?);
// 广播 channel 容量,设置较大以容纳多个订阅者
let (broadcast_tx, _) = broadcast::channel(256);
Ok(Self {
socket,
broadcast_tx,
subscribers: DashMap::new(),
addr_to_id: DashMap::new(),
next_id: AtomicU64::new(1),
timestamp: AtomicU64::new(0),
})
}
/// 获取 UDP 端口
pub fn port(&self) -> u16 {
self.socket.port()
}
/// 获取 socket 引用(用于外部发送)
pub fn socket(&self) -> Arc<AudioSocket> {
self.socket.clone()
}
/// 注册一个订阅者
pub fn register(&self, addr: SocketAddr, filter_self: bool) -> SubscriberId {
let id = self.next_id.fetch_add(1, Ordering::SeqCst);
let subscriber = Subscriber {
id,
addr,
filter_self,
};
self.subscribers.insert(addr, subscriber);
self.addr_to_id.insert(addr, id);
println!("[AudioBus] Subscriber {id} registered at {addr}");
id
}
/// 注销订阅者
pub fn unregister(&self, addr: &SocketAddr) {
if let Some((_, sub)) = self.subscribers.remove(addr) {
self.addr_to_id.remove(addr);
println!("[AudioBus] Subscriber {} unregistered", sub.id);
}
}
/// 订阅广播频道,返回一个 Receiver
pub fn subscribe(&self) -> broadcast::Receiver<AudioFrame> {
self.broadcast_tx.subscribe()
}
/// 发布音频帧到总线
pub fn publish(&self, packet: AudioPacket, source: Option<SocketAddr>) {
let ts = self.timestamp.fetch_add(1, Ordering::SeqCst);
let frame = AudioFrame {
packet,
source,
timestamp: ts,
};
// 忽略没有订阅者的情况
let _ = self.broadcast_tx.send(frame);
}
/// 广播音频帧到所有订阅者(除了发送者自己)
pub async fn broadcast(&self, frame: &AudioFrame) {
for entry in self.subscribers.iter() {
let sub = entry.value();
// 如果启用了自过滤,跳过发送者自己
if sub.filter_self {
if let Some(src) = &frame.source {
if src == &sub.addr {
continue;
}
}
}
// 发送音频包
if let Err(e) = self.socket.send(&frame.packet, sub.addr).await {
eprintln!("[AudioBus] Failed to send to {}: {}", sub.addr, e);
}
}
}
/// 发送音频包到指定地址
pub async fn send_to(&self, packet: &AudioPacket, addr: SocketAddr) -> anyhow::Result<()> {
self.socket.send(packet, addr).await
}
/// 启动 UDP 接收循环
/// 这是一个独立的任务,负责:
/// 1. 接收 UDP 音频包
/// 2. 发布到广播频道
pub async fn run_receiver(&self) {
let mut buf = vec![0u8; 4096];
loop {
match self.socket.recv(&mut buf).await {
Ok((packet, src_addr)) => {
// 只处理已注册的发送者
if self.subscribers.contains_key(&src_addr) {
self.publish(packet, Some(src_addr));
}
}
Err(e) => {
eprintln!("[AudioBus] Recv error: {}", e);
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
}
}
}
}
/// 启动广播分发循环
/// 订阅广播频道并将音频转发给所有订阅者
pub async fn run_broadcaster(self: Arc<Self>) {
let mut rx = self.subscribe();
loop {
match rx.recv().await {
Ok(frame) => {
self.broadcast(&frame).await;
}
Err(broadcast::error::RecvError::Lagged(n)) => {
eprintln!("[AudioBus] Broadcaster lagged {} frames", n);
}
Err(broadcast::error::RecvError::Closed) => {
break;
}
}
}
}
/// 获取当前订阅者数量
pub fn subscriber_count(&self) -> usize {
self.subscribers.len()
}
/// 检查地址是否已注册
pub fn is_registered(&self, addr: &SocketAddr) -> bool {
self.subscribers.contains_key(addr)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_audio_bus_basic() {
let bus = AudioBus::new().await.unwrap();
let addr: SocketAddr = "127.0.0.1:12345".parse().unwrap();
let id = bus.register(addr, true);
assert!(bus.is_registered(&addr));
assert_eq!(bus.subscriber_count(), 1);
bus.unregister(&addr);
assert!(!bus.is_registered(&addr));
assert_eq!(bus.subscriber_count(), 0);
}
}
@@ -1,208 +0,0 @@
use crate::audio::codec::OpusCodec;
use crate::audio::config::AudioConfig;
use crate::audio::wav::{WavReader, WavWriter};
use crate::net::network::AudioSocket;
use crate::net::protocol::AudioPacket;
use anyhow::Result;
use parking_lot::Mutex;
use std::net::SocketAddr;
use std::sync::Arc;
use tokio::sync::mpsc;
use tokio_util::sync::CancellationToken;
use tokio_util::task::TaskTracker;
pub enum RecorderCommand {
Start {
config: AudioConfig,
filename: String,
},
Stop,
}
pub struct ServerAudioManager {
session_cancel: CancellationToken,
record_cancel: Mutex<Option<CancellationToken>>,
play_cancel: Mutex<Option<CancellationToken>>,
audio_tx: mpsc::Sender<AudioPacket>,
recorder_tx: mpsc::Sender<RecorderCommand>,
tracker: TaskTracker,
}
impl ServerAudioManager {
pub fn new(
session_cancel: CancellationToken,
tracker: TaskTracker,
) -> (
Self,
mpsc::Receiver<AudioPacket>,
mpsc::Receiver<RecorderCommand>,
) {
let (audio_tx, audio_rx) = mpsc::channel(1024);
let (recorder_tx, recorder_rx) = mpsc::channel(64);
let manager = Self {
session_cancel,
record_cancel: Mutex::new(None),
play_cancel: Mutex::new(None),
audio_tx,
recorder_tx,
tracker,
};
(manager, audio_rx, recorder_rx)
}
pub fn audio_tx(&self) -> mpsc::Sender<AudioPacket> {
self.audio_tx.clone()
}
pub async fn start_recording(&self, config: AudioConfig, filename: String) -> Result<()> {
// 仅停止之前的录音任务
{
let mut guard = self.record_cancel.lock();
if let Some(token) = guard.take() {
token.cancel();
}
*guard = Some(self.session_cancel.child_token());
}
self.recorder_tx
.send(RecorderCommand::Start { config, filename })
.await?;
Ok(())
}
pub async fn stop_recording(&self) -> Result<()> {
if let Some(token) = self.record_cancel.lock().take() {
token.cancel();
}
let _ = self.recorder_tx.send(RecorderCommand::Stop).await;
Ok(())
}
pub async fn start_playback(
&self,
config: AudioConfig,
reader: WavReader,
audio_socket: Arc<AudioSocket>,
target_addr: SocketAddr,
) -> Result<()> {
let token = {
let mut guard = self.play_cancel.lock();
if let Some(token) = guard.take() {
token.cancel();
}
let token = self.session_cancel.child_token();
*guard = Some(token.clone());
token
};
let session_cancel = self.session_cancel.clone();
self.tracker.spawn(async move {
let mut reader = reader;
let mut codec = match OpusCodec::new(&config) {
Ok(c) => c,
Err(e) => {
eprintln!("Failed to create opus codec: {}", e);
return;
}
};
let mut pcm = vec![0i16; config.frame_size];
let mut opus = vec![0u8; 4096];
let mut interval = tokio::time::interval(std::time::Duration::from_millis(20));
loop {
tokio::select! {
_ = token.cancelled() => break,
_ = session_cancel.cancelled() => break,
_ = interval.tick() => {
match reader.read_samples(&mut pcm) {
Ok(0) => break,
Ok(n) => {
if let Ok(len) = codec.encode(&pcm[..n], &mut opus) {
let _ = audio_socket
.send(
&AudioPacket {
data: opus[..len].to_vec(),
},
target_addr,
)
.await;
}
}
Err(e) => {
eprintln!("Failed to read samples: {}", e);
break;
}
}
}
}
}
});
Ok(())
}
pub fn stop_playback(&self) {
if let Some(token) = self.play_cancel.lock().take() {
token.cancel();
}
}
pub fn spawn_audio_processor(
&self,
mut audio_rx: mpsc::Receiver<AudioPacket>,
mut recorder_rx: mpsc::Receiver<RecorderCommand>,
) {
let session_cancel = self.session_cancel.clone();
self.tracker.spawn(async move {
let mut active_recorder: Option<(WavWriter, OpusCodec, usize)> = None;
loop {
tokio::select! {
_ = session_cancel.cancelled() => break,
cmd = recorder_rx.recv() => {
match cmd {
Some(RecorderCommand::Start { config, filename }) => {
if let Some((writer, _, _)) = active_recorder.take() {
let _ = writer.finalize();
}
match WavWriter::create(&filename, config.sample_rate, config.channels) {
Ok(writer) => {
match OpusCodec::new(&config) {
Ok(codec) => active_recorder = Some((writer, codec, config.frame_size)),
Err(e) => eprintln!("Failed to create opus codec: {}", e),
}
}
Err(e) => eprintln!("Failed to create wav writer: {}", e),
}
}
Some(RecorderCommand::Stop) => {
if let Some((writer, _, _)) = active_recorder.take() {
let _ = writer.finalize();
}
}
None => break,
}
}
packet = audio_rx.recv() => {
match packet {
Some(packet) => {
if let Some((writer, codec, frame_size)) = &mut active_recorder {
let mut pcm = vec![0i16; *frame_size];
if let Ok(n) = codec.decode(&packet.data, &mut pcm) {
let _ = writer.write_samples(&pcm[..n]);
}
}
}
None => break,
}
}
}
}
if let Some((writer, _, _)) = active_recorder.take() {
let _ = writer.finalize();
}
});
}
}
+400 -188
View File
@@ -1,109 +1,228 @@
mod audio_manager;
//! # Server 模块
//!
//! 实时音频流服务器,支持:
//! - 多客户端连接管理
//! - 音频流发布-订阅
//! - 录音和播放
//! - RPC 远程调用
//! - 实时事件推送
//!
//! ## 架构
//!
//! ```text
//! ┌─────────────────────────────────────────────┐
//! │ Server │
//! │ │
//! Client 1 ──TCP────────┼──▶ SessionManager │
//! Client 2 ──TCP────────┼──▶ ├─ Session 1 │
//! Client N ──TCP────────┼──▶ ├─ Session 2 │
//! │ └─ Session N │
//! │ │ │
//! ┌────────────────┼────────────┴────────────────────────┐ │
//! │ │ │ │
//! ▼ │ ▼ │
//! ┌─────────┐ │ ┌─────────────────────────────┐ │
//! │Command │ │ │ AudioBus │ │
//! │Handler │ │ │ │ │
//! └─────────┘ │ │ ┌─────────┐ ┌──────────┐ │ │
//! │ │ │ │Receiver │ │Broadcaster│ │ │
//! ▼ │ │ │ Loop │─▶│ Loop │──┼───┼──▶ All Clients
//! ┌─────────┐ │ │ └─────────┘ └──────────┘ │ │
//! │ Event │ │ └─────────────────────────────┘ │
//! │ Bus │───────────┼─────────────────────────────────────────────┼──▶ Events
//! └─────────┘ │ │
//! └─────────────────────────────────────────────┘
//! ```
mod audio_bus;
mod session;
mod stream;
pub use audio_bus::{AudioBus, AudioFrame};
pub use session::{Session, SessionManager};
pub use stream::{FilePlaybackStream, RecorderStream, StreamHandle};
use crate::audio::config::AudioConfig;
use crate::audio::wav::WavReader;
use crate::net::command::{
AudioState, Command, CommandError, CommandResult, DeviceInfo, ShellResponse,
};
use crate::net::discovery::Discovery;
use crate::net::network::{AudioSocket, Connection};
use crate::net::protocol::{ClientInfo, ControlPacket, RpcResult};
use crate::net::rpc::RpcManager;
use crate::net::event::{ClientEvent, ServerEvent, ServerEventBus};
use crate::net::network::Connection;
use crate::net::protocol::ControlPacket;
use anyhow::{Context, Result, anyhow};
use audio_manager::ServerAudioManager;
use dashmap::DashMap;
use std::net::SocketAddr;
use std::sync::Arc;
use tokio_util::sync::CancellationToken;
use tokio_util::task::TaskTracker;
pub struct Session {
pub info: ClientInfo,
pub conn: Arc<Connection>,
pub rpc: Arc<RpcManager>,
pub tcp_addr: SocketAddr,
pub audio_addr: SocketAddr,
pub session_cancel: CancellationToken,
pub audio_manager: ServerAudioManager,
pub tracker: TaskTracker,
}
/// 实时音频服务器
pub struct Server {
sessions: DashMap<SocketAddr, Arc<Session>>,
udp_to_tcp: DashMap<SocketAddr, SocketAddr>,
audio: Arc<AudioSocket>,
/// 会话管理器
sessions: Arc<SessionManager>,
/// 音频总线
audio_bus: Arc<AudioBus>,
/// 服务端事件总线
event_bus: Arc<ServerEventBus>,
/// 服务器取消令牌
cancel: CancellationToken,
/// 服务器启动时间
started_at: std::time::Instant,
}
impl Server {
/// 创建新服务器
pub async fn new() -> Result<Self> {
let audio_bus = Arc::new(AudioBus::new().await?);
Ok(Self {
sessions: DashMap::new(),
udp_to_tcp: DashMap::new(),
audio: Arc::new(AudioSocket::bind().await?),
sessions: Arc::new(SessionManager::new()),
audio_bus,
event_bus: Arc::new(ServerEventBus::default()),
cancel: CancellationToken::new(),
started_at: std::time::Instant::now(),
})
}
/// 获取事件总线(用于外部订阅)
pub fn event_bus(&self) -> Arc<ServerEventBus> {
self.event_bus.clone()
}
/// 启动服务器
pub async fn run(self: Arc<Self>, port: u16) -> Result<()> {
let listener = tokio::net::TcpListener::bind(format!("0.0.0.0:{}", port)).await?;
let addr = listener.local_addr()?;
println!("Server listening on TCP: {}", addr);
println!("[Server] Listening on TCP: {}", addr);
println!("[Server] Audio UDP port: {}", self.audio_bus.port());
// 广播服务发现
Discovery::broadcast(port).await?;
// Audio Dispatcher
let this = self.clone();
// 启动音频总线接收循环
let bus = self.audio_bus.clone();
tokio::spawn(async move {
let mut buf = vec![0u8; 4096];
loop {
match this.audio.recv(&mut buf).await {
Ok((packet, src_addr)) => {
if let Some(tcp_addr) = this.udp_to_tcp.get(&src_addr) {
if let Some(session) = this.sessions.get(tcp_addr.value()) {
// 使用 try_send 避免某一个客户端阻塞导致全局音频延迟
let _ = session.audio_manager.audio_tx().try_send(packet);
}
bus.run_receiver().await;
});
// 启动音频总线广播循环
let bus = self.audio_bus.clone();
tokio::spawn(async move {
bus.run_broadcaster().await;
});
// TCP 连接接受循环
loop {
tokio::select! {
_ = self.cancel.cancelled() => {
println!("[Server] Shutting down...");
break;
}
result = listener.accept() => {
match result {
Ok((stream, addr)) => {
let server = self.clone();
tokio::spawn(async move {
if let Err(e) = server.handle_connection(stream, addr).await {
eprintln!("[Server] Connection {} error: {}", addr, e);
}
});
}
Err(e) => {
eprintln!("[Server] Accept error: {}", e);
}
}
Err(e) => {
eprintln!("Audio socket recv error: {}", e);
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
}
}
}
});
loop {
let (stream, addr) = listener.accept().await?;
let server = self.clone();
tokio::spawn(async move {
if let Err(e) = server.clone().handle_session(stream, addr).await {
eprintln!("Session {} error: {}", addr, e);
}
server.remove_session(&addr).await;
});
}
Ok(())
}
async fn remove_session(&self, tcp_addr: &SocketAddr) {
if let Some((_, session)) = self.sessions.remove(tcp_addr) {
session.session_cancel.cancel();
session.tracker.close();
self.udp_to_tcp.remove(&session.audio_addr);
println!("Session {} ({}) closed", tcp_addr, session.info.model);
}
}
async fn handle_session(
/// 处理新连接
async fn handle_connection(
self: Arc<Self>,
stream: tokio::net::TcpStream,
addr: SocketAddr,
) -> Result<()> {
println!("New TCP connection from {}", addr);
println!("[Server] New connection from {}", addr);
let conn = Arc::new(Connection::new(stream)?);
// --- 握手 (Handshake) ---
// --- 握手 ---
let (info, audio_addr) = self.handshake(&conn, addr).await?;
println!(
"[Server] Client identified: {} ({}) audio: {}",
info.model, info.serial_number, audio_addr
);
// --- 创建 Session ---
let session_cancel = self.cancel.child_token();
let session = Arc::new(Session::new(
info.clone(),
conn.clone(),
addr,
audio_addr,
session_cancel.clone(),
));
// 注册到 SessionManager 和 AudioBus
self.sessions.register(session.clone());
self.audio_bus.register(audio_addr, true);
// 发布客户端加入事件
self.event_bus.publish(ServerEvent::ClientJoined {
addr: addr.to_string(),
model: info.model.clone(),
});
// 广播给其他客户端
self.sessions
.broadcast_except(
&ControlPacket::ServerEvent(ServerEvent::ClientJoined {
addr: addr.to_string(),
model: info.model.clone(),
}),
&addr,
)
.await;
// --- 主循环 ---
let result = self.session_loop(session.clone()).await;
// --- 清理 ---
self.audio_bus.unregister(&audio_addr);
self.sessions.unregister(&addr);
// 发布客户端离开事件
self.event_bus.publish(ServerEvent::ClientLeft {
addr: addr.to_string(),
model: info.model.clone(),
});
// 广播给其他客户端
self.sessions
.broadcast(&ControlPacket::ServerEvent(ServerEvent::ClientLeft {
addr: addr.to_string(),
model: info.model,
}))
.await;
result
}
/// 握手流程
async fn handshake(
&self,
conn: &Arc<Connection>,
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 {
auth,
@@ -112,71 +231,45 @@ impl Server {
info,
} => {
if v != version {
return Err(anyhow!("Client version mismatch: {} != {}", v, version));
return Err(anyhow!("Version mismatch: {} != {}", v, version));
}
if auth != server_auth {
return Err(anyhow!("Invalid client auth"));
}
(info, udp_port)
}
_ => return Err(anyhow!("Handshake failed")),
_ => return Err(anyhow!("Expected ClientHello")),
};
// 发送服务器 Hello
conn.send(&ControlPacket::ServerHello {
auth: client_auth,
version: env!("CARGO_PKG_VERSION").to_string(),
udp_port: self.audio.port(),
version,
udp_port: self.audio_bus.port(),
})
.await?;
let audio_addr = SocketAddr::new(addr.ip(), client_audio_port);
println!(
"Client identified: {} ({}), audio at {}",
info.model, info.serial_number, audio_addr
);
Ok((info, audio_addr))
}
// --- 初始化 Session ---
let tracker = TaskTracker::new();
let session_cancel = CancellationToken::new();
let (audio_manager, audio_rx, recorder_rx) =
ServerAudioManager::new(session_cancel.clone(), tracker.clone());
/// Session 消息循环
async fn session_loop(&self, session: Arc<Session>) -> Result<()> {
let timeout = std::time::Duration::from_secs(60);
let session = Arc::new(Session {
info,
conn: conn.clone(),
rpc: Arc::new(RpcManager::new()),
tcp_addr: addr,
audio_addr,
session_cancel,
audio_manager,
tracker: tracker.clone(),
});
session
.audio_manager
.spawn_audio_processor(audio_rx, recorder_rx);
self.sessions.insert(addr, session.clone());
self.udp_to_tcp.insert(audio_addr, addr);
// 消息主循环
loop {
tokio::select! {
_ = session.session_cancel.cancelled() => break,
res = tokio::time::timeout(std::time::Duration::from_secs(60), conn.recv()) => {
match res {
_ = session.cancel.cancelled() => break,
result = tokio::time::timeout(timeout, session.recv()) => {
match result {
Ok(Ok(packet)) => {
if let Err(e) = self.process_packet(session.clone(), packet).await {
eprintln!("Process packet error: {}", e);
}
self.handle_packet(&session, packet).await?;
}
Ok(Err(e)) => {
eprintln!("Session {} connection error: {}", addr, e);
break;
return Err(anyhow!("Connection error: {}", e));
}
Err(_) => {
eprintln!("Session {} timeout", addr);
break;
return Err(anyhow!("Connection timeout"));
}
}
}
@@ -186,119 +279,209 @@ impl Server {
Ok(())
}
pub async fn get_clients(&self) -> Vec<SocketAddr> {
self.sessions.iter().map(|r| *r.key()).collect()
}
// todo 考虑有些操作比较耗时,需要非阻塞处理
async fn process_packet(&self, session: Arc<Session>, packet: ControlPacket) -> Result<()> {
/// 处理控制包
async fn handle_packet(&self, session: &Arc<Session>, packet: ControlPacket) -> Result<()> {
match packet {
ControlPacket::Ping => {
session.conn.send(&ControlPacket::Pong).await?;
session.send(&ControlPacket::Pong).await?;
}
ControlPacket::Pong => {}
ControlPacket::RpcResponse { id, result } => {
session.rpc.resolve(id, result);
session.resolve_rpc(id, result);
}
ControlPacket::RpcRequest { id, method, args } => {
let result = self.handle_rpc(&session, &method, args).await;
ControlPacket::RpcRequest { id, command } => {
let result = self.handle_command(session, command).await;
session
.conn
.send(&ControlPacket::RpcResponse { id, result })
.await?;
}
ControlPacket::ClientEvent(event) => {
self.handle_client_event(session, event).await;
}
_ => {}
}
Ok(())
}
async fn handle_rpc(
&self,
_session: &Arc<Session>,
method: &str,
args: Vec<String>,
) -> RpcResult {
match method {
"hello" => RpcResult {
stdout: format!("Hello from server! Args: {:?}", args),
..Default::default()
},
_ => RpcResult {
stderr: format!("Unknown method: {}", method),
code: -1,
..Default::default()
},
/// 处理 RPC 命令
async fn handle_command(&self, session: &Arc<Session>, command: Command) -> CommandResult {
match command {
Command::Shell(req) => {
// 转发给客户端执行
match session.execute(Command::Shell(req)).await {
Ok(result) => result,
Err(e) => CommandResult::Error(CommandError::internal(e.to_string())),
}
}
Command::GetInfo => {
// 返回服务器信息
CommandResult::Info(DeviceInfo {
model: "XiaoAi-Server".to_string(),
serial_number: "SERVER-001".to_string(),
version: env!("CARGO_PKG_VERSION").to_string(),
uptime_secs: self.started_at.elapsed().as_secs(),
audio_state: AudioState {
is_recording: session.is_recording(),
is_playing: session.is_playing(),
volume: 100,
},
})
}
Command::Ping { timestamp } => {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_millis() as u64;
CommandResult::Pong {
timestamp,
server_time: now,
}
}
Command::SetVolume(_) => {
// 转发给客户端
match session.execute(command).await {
Ok(result) => result,
Err(e) => CommandResult::Error(CommandError::internal(e.to_string())),
}
}
_ => CommandResult::Error(CommandError::not_implemented()),
}
}
pub async fn call(
&self,
addr: SocketAddr,
method: &str,
args: Vec<String>,
) -> Result<RpcResult> {
let session = self
.sessions
.get(&addr)
.map(|r| r.value().clone())
.context("Session not found")?;
let (id, rx) = session.rpc.register();
session
.conn
.send(&ControlPacket::RpcRequest {
id,
method: method.to_string(),
args,
})
.await?;
Ok(rx.await?)
/// 处理客户端事件
async fn handle_client_event(&self, session: &Arc<Session>, event: ClientEvent) {
match &event {
ClientEvent::Alert { level, message } => {
println!(
"[Event] Alert from {}: [{:?}] {}",
session.tcp_addr, level, message
);
}
ClientEvent::AudioLevel {
level_db,
is_silent,
} => {
println!(
"[Event] Audio level from {}: {:.1}dB (silent: {})",
session.tcp_addr, level_db, is_silent
);
}
_ => {}
}
// 可以在这里将客户端事件转发给其他订阅者
}
// ==================== 公开 API ====================
/// 获取所有已连接的客户端地址
pub async fn get_clients(&self) -> Vec<SocketAddr> {
self.sessions.all_addrs()
}
/// 获取客户端数量
pub fn client_count(&self) -> usize {
self.sessions.count()
}
/// 获取服务器运行时间
pub fn uptime_secs(&self) -> u64 {
self.started_at.elapsed().as_secs()
}
/// 向客户端发起 RPC 调用(新版)
pub async fn execute(&self, addr: SocketAddr, command: Command) -> Result<CommandResult> {
let session = self.sessions.get(&addr).context("Session not found")?;
session.execute(command).await
}
/// 执行 Shell 命令
pub async fn shell(&self, addr: SocketAddr, cmd: &str) -> Result<ShellResponse> {
let result = self.execute(addr, Command::shell(cmd)).await?;
match result {
CommandResult::Shell(resp) => Ok(resp),
CommandResult::Error(e) => Err(anyhow!("{}", e)),
_ => Err(anyhow!("Unexpected response type")),
}
}
/// 推送事件给所有客户端
pub async fn broadcast_event(&self, event: ServerEvent) {
self.event_bus.publish(event.clone());
self.sessions
.broadcast(&ControlPacket::ServerEvent(event))
.await;
}
/// 推送事件给指定客户端
pub async fn send_event(&self, addr: SocketAddr, event: ServerEvent) -> Result<()> {
let session = self.sessions.get(&addr).context("Session not found")?;
self.event_bus.publish_to(addr, event.clone());
session.send(&ControlPacket::ServerEvent(event)).await
}
/// 开始录音
pub async fn start_record(&self, addr: SocketAddr, config: AudioConfig) -> Result<()> {
let session = self
.sessions
.get(&addr)
.map(|r| r.value().clone())
.context("Session not found")?;
let session = self.sessions.get(&addr).context("Session not found")?;
let filename = format!(
"temp/recorded_{}.wav",
session.info.serial_number.replace(":", "")
);
// 通知 Audio Manager 开始录音
session
.audio_manager
.start_recording(config.clone(), filename)
.await?;
// 创建录音流,订阅音频总线
let handle = RecorderStream::spawn(
config.clone(),
filename.clone(),
self.audio_bus.subscribe(),
Some(session.audio_addr),
session.cancel.clone(),
);
session.start_recording(handle, config.clone());
// 通知客户端开始发送音频
session
.conn
.send(&ControlPacket::StartRecording { config })
.await?;
// 发布事件
session
.send(&ControlPacket::ServerEvent(
ServerEvent::AudioStatusChanged {
is_recording: true,
is_playing: session.is_playing(),
},
))
.await?;
println!("[Server] Recording started for {} -> {}", addr, filename);
Ok(())
}
/// 停止录音
pub async fn stop_record(&self, addr: SocketAddr) -> Result<()> {
let session = self
.sessions
.get(&addr)
.map(|r| r.value().clone())
.context("Session not found")?;
let session = self.sessions.get(&addr).context("Session not found")?;
session.stop_recording();
session.send(&ControlPacket::StopRecording).await?;
// 停止录音任务
session.audio_manager.stop_recording().await?;
session.conn.send(&ControlPacket::StopRecording).await?;
// 发布事件
session
.send(&ControlPacket::ServerEvent(
ServerEvent::AudioStatusChanged {
is_recording: false,
is_playing: session.is_playing(),
},
))
.await?;
println!("[Server] Recording stopped for {}", addr);
Ok(())
}
/// 开始播放
pub async fn start_play(&self, addr: SocketAddr, file_path: &str) -> Result<()> {
let session = self
.sessions
.get(&addr)
.map(|r| r.value().clone())
.context("Session not found")?;
let session = self.sessions.get(&addr).context("Session not found")?;
let reader = WavReader::open(file_path)?;
let opus_rate = if reader.sample_rate > 24000 {
@@ -314,31 +497,60 @@ impl Server {
..AudioConfig::music_48k()
};
// 通知客户端准备接收音频
session
.conn
.send(&ControlPacket::StartPlayback {
config: config.clone(),
})
.await?;
// 创建播放流
let handle = FilePlaybackStream::spawn(
config,
reader,
self.audio_bus.socket(),
session.audio_addr,
session.cancel.clone(),
);
session.start_playback(handle);
// 发布事件
session
.audio_manager
.start_playback(config, reader, self.audio.clone(), session.audio_addr)
.send(&ControlPacket::ServerEvent(
ServerEvent::AudioStatusChanged {
is_recording: session.is_recording(),
is_playing: true,
},
))
.await?;
println!("[Server] Playback started for {} from {}", addr, file_path);
Ok(())
}
/// 停止播放
pub async fn stop_play(&self, addr: SocketAddr) -> Result<()> {
let session = self
.sessions
.get(&addr)
.map(|r| r.value().clone())
.context("Session not found")?;
let session = self.sessions.get(&addr).context("Session not found")?;
session.stop_playback();
session.send(&ControlPacket::StopPlayback).await?;
// 停止播放任务
session.audio_manager.stop_playback();
session.conn.send(&ControlPacket::StopPlayback).await?;
// 发布事件
session
.send(&ControlPacket::ServerEvent(
ServerEvent::AudioStatusChanged {
is_recording: session.is_recording(),
is_playing: false,
},
))
.await?;
println!("[Server] Playback stopped for {}", addr);
Ok(())
}
/// 关闭服务器
pub fn shutdown(&self) {
self.cancel.cancel();
}
}
@@ -0,0 +1,326 @@
//! # Session - 客户端会话管理
//!
//! 轻量级的会话结构,专注于:
//! - TCP 控制连接
//! - RPC 管理
//! - 会话生命周期
//!
//! 音频流的实际处理由 AudioBus 和 Stream 模块负责。
use crate::audio::config::AudioConfig;
use crate::net::command::{Command, CommandResult};
use crate::net::network::Connection;
use crate::net::protocol::{ClientInfo, ControlPacket};
use crate::net::rpc::RpcManager;
use anyhow::{Context, Result};
use std::net::SocketAddr;
use std::sync::Arc;
use tokio_util::sync::CancellationToken;
use super::stream::StreamHandle;
/// 活动流追踪
/// 用于追踪当前 session 的活动音频流
pub struct ActiveStreams {
/// 当前录音流句柄
pub recorder: Option<StreamHandle>,
/// 当前播放流句柄
pub playback: Option<StreamHandle>,
}
impl Default for ActiveStreams {
fn default() -> Self {
Self {
recorder: None,
playback: None,
}
}
}
impl ActiveStreams {
/// 停止所有活动流
pub fn stop_all(&mut self) {
if let Some(h) = self.recorder.take() {
h.stop();
}
if let Some(h) = self.playback.take() {
h.stop();
}
}
/// 开始录音(停止之前的录音)
pub fn start_recording(&mut self, handle: StreamHandle) {
if let Some(h) = self.recorder.take() {
h.stop();
}
self.recorder = Some(handle);
}
/// 停止录音
pub fn stop_recording(&mut self) {
if let Some(h) = self.recorder.take() {
h.stop();
}
}
/// 开始播放(停止之前的播放)
pub fn start_playback(&mut self, handle: StreamHandle) {
if let Some(h) = self.playback.take() {
h.stop();
}
self.playback = Some(handle);
}
/// 停止播放
pub fn stop_playback(&mut self) {
if let Some(h) = self.playback.take() {
h.stop();
}
}
/// 检查是否正在录音
pub fn is_recording(&self) -> bool {
self.recorder.is_some()
}
/// 检查是否正在播放
pub fn is_playing(&self) -> bool {
self.playback.is_some()
}
}
/// 客户端会话
pub struct Session {
/// 客户端信息
pub info: ClientInfo,
/// TCP 控制连接
pub conn: Arc<Connection>,
/// RPC 管理器
pub rpc: Arc<RpcManager>,
/// TCP 地址(用作会话 ID
pub tcp_addr: SocketAddr,
/// UDP 音频地址
pub audio_addr: SocketAddr,
/// 会话取消令牌
pub cancel: CancellationToken,
/// 活动流
streams: parking_lot::Mutex<ActiveStreams>,
/// 当前录音配置(如果正在录音)
recording_config: parking_lot::Mutex<Option<AudioConfig>>,
/// 会话创建时间
created_at: std::time::Instant,
}
impl Session {
/// 创建新会话
pub fn new(
info: ClientInfo,
conn: Arc<Connection>,
tcp_addr: SocketAddr,
audio_addr: SocketAddr,
cancel: CancellationToken,
) -> Self {
Self {
info,
conn,
rpc: Arc::new(RpcManager::new()),
tcp_addr,
audio_addr,
cancel,
streams: parking_lot::Mutex::new(ActiveStreams::default()),
recording_config: parking_lot::Mutex::new(None),
created_at: std::time::Instant::now(),
}
}
/// 获取会话 ID(使用 TCP 地址)
pub fn id(&self) -> SocketAddr {
self.tcp_addr
}
/// 检查会话是否仍然有效
pub fn is_alive(&self) -> bool {
!self.cancel.is_cancelled()
}
/// 获取会话运行时间(秒)
pub fn uptime_secs(&self) -> u64 {
self.created_at.elapsed().as_secs()
}
/// 发送控制包
pub async fn send(&self, packet: &ControlPacket) -> Result<()> {
self.conn.send(packet).await
}
/// 接收控制包
pub async fn recv(&self) -> Result<ControlPacket> {
self.conn.recv().await
}
/// 执行 RPC 调用(新版,使用 Command
pub async fn execute(&self, command: Command) -> Result<CommandResult> {
let (id, rx) = self.rpc.register();
self.conn
.send(&ControlPacket::RpcRequest { id, command })
.await?;
rx.await.context("RPC channel closed")
}
/// 处理 RPC 响应
pub fn resolve_rpc(&self, id: u32, result: CommandResult) {
self.rpc.resolve(id, result);
}
/// 开始录音流
pub fn start_recording(&self, handle: StreamHandle, config: AudioConfig) {
self.streams.lock().start_recording(handle);
*self.recording_config.lock() = Some(config);
}
/// 停止录音流
pub fn stop_recording(&self) {
self.streams.lock().stop_recording();
*self.recording_config.lock() = None;
}
/// 开始播放流
pub fn start_playback(&self, handle: StreamHandle) {
self.streams.lock().start_playback(handle);
}
/// 停止播放流
pub fn stop_playback(&self) {
self.streams.lock().stop_playback();
}
/// 检查是否正在录音
pub fn is_recording(&self) -> bool {
self.streams.lock().is_recording()
}
/// 检查是否正在播放
pub fn is_playing(&self) -> bool {
self.streams.lock().is_playing()
}
/// 清理所有资源
pub fn cleanup(&self) {
self.cancel.cancel();
self.streams.lock().stop_all();
}
/// 获取当前录音配置
pub fn recording_config(&self) -> Option<AudioConfig> {
self.recording_config.lock().clone()
}
}
impl Drop for Session {
fn drop(&mut self) {
self.cleanup();
}
}
/// 会话管理器
/// 负责管理所有客户端会话
pub struct SessionManager {
sessions: dashmap::DashMap<SocketAddr, Arc<Session>>,
/// 从 UDP 地址到 TCP 地址的映射
udp_to_tcp: dashmap::DashMap<SocketAddr, SocketAddr>,
}
impl SessionManager {
pub fn new() -> Self {
Self {
sessions: dashmap::DashMap::new(),
udp_to_tcp: dashmap::DashMap::new(),
}
}
/// 注册新会话
pub fn register(&self, session: Arc<Session>) {
let tcp_addr = session.tcp_addr;
let audio_addr = session.audio_addr;
self.udp_to_tcp.insert(audio_addr, tcp_addr);
self.sessions.insert(tcp_addr, session);
println!(
"[SessionManager] Registered: {} (audio: {})",
tcp_addr, audio_addr
);
}
/// 注销会话
pub fn unregister(&self, tcp_addr: &SocketAddr) -> Option<Arc<Session>> {
if let Some((_, session)) = self.sessions.remove(tcp_addr) {
self.udp_to_tcp.remove(&session.audio_addr);
session.cleanup();
println!(
"[SessionManager] Unregistered: {} ({})",
tcp_addr, session.info.model
);
Some(session)
} else {
None
}
}
/// 通过 TCP 地址获取会话
pub fn get(&self, tcp_addr: &SocketAddr) -> Option<Arc<Session>> {
self.sessions.get(tcp_addr).map(|r| r.value().clone())
}
/// 通过 UDP 地址获取会话
pub fn get_by_udp(&self, udp_addr: &SocketAddr) -> Option<Arc<Session>> {
self.udp_to_tcp
.get(udp_addr)
.and_then(|tcp_addr| self.get(tcp_addr.value()))
}
/// 获取所有会话地址
pub fn all_addrs(&self) -> Vec<SocketAddr> {
self.sessions.iter().map(|r| *r.key()).collect()
}
/// 获取所有会话
pub fn all_sessions(&self) -> Vec<Arc<Session>> {
self.sessions.iter().map(|r| r.value().clone()).collect()
}
/// 获取会话数量
pub fn count(&self) -> usize {
self.sessions.len()
}
/// 广播控制包到所有会话
pub async fn broadcast(&self, packet: &ControlPacket) {
for entry in self.sessions.iter() {
let _ = entry.value().send(packet).await;
}
}
/// 广播控制包到所有会话(除了指定的)
pub async fn broadcast_except(&self, packet: &ControlPacket, except: &SocketAddr) {
for entry in self.sessions.iter() {
if entry.key() != except {
let _ = entry.value().send(packet).await;
}
}
}
}
impl Default for SessionManager {
fn default() -> Self {
Self::new()
}
}
+302
View File
@@ -0,0 +1,302 @@
//! # AudioStream - 音频流抽象
//!
//! 提供统一的音频流处理接口,支持多种输入源和输出目标。
//!
//! ## 设计
//!
//! ```text
//! ┌─────────────────────────────────────────────────────────────┐
//! │ Stream Types │
//! │ │
//! │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
//! │ │ FileSource │ │ BusSource │ │ NetworkSink │ │
//! │ │ (WAV Reader) │ │ (From Bus) │ │ (To Client) │ │
//! │ └──────────────┘ └──────────────┘ └──────────────┘ │
//! │ │
//! │ ┌──────────────┐ ┌──────────────┐ │
//! │ │ FileSink │ │ BusSink │ │
//! │ │ (WAV Writer) │ │ (To Bus) │ │
//! │ └──────────────┘ └──────────────┘ │
//! └─────────────────────────────────────────────────────────────┘
//! ```
use crate::audio::codec::OpusCodec;
use crate::audio::config::AudioConfig;
use crate::audio::wav::{WavReader, WavWriter};
use crate::net::network::AudioSocket;
use crate::net::protocol::AudioPacket;
use std::net::SocketAddr;
use std::sync::Arc;
use tokio::sync::broadcast;
use tokio_util::sync::CancellationToken;
use super::audio_bus::AudioFrame;
/// 音频流任务句柄
/// 用于控制正在运行的音频流任务
pub struct StreamHandle {
cancel: CancellationToken,
}
impl StreamHandle {
pub fn new(cancel: CancellationToken) -> Self {
Self { cancel }
}
/// 停止流
pub fn stop(&self) {
self.cancel.cancel();
}
/// 检查是否已停止
pub fn is_stopped(&self) -> bool {
self.cancel.is_cancelled()
}
}
impl Drop for StreamHandle {
fn drop(&mut self) {
self.cancel.cancel();
}
}
/// 文件播放流 - 从 WAV 文件读取并发送到指定客户端
pub struct FilePlaybackStream;
impl FilePlaybackStream {
/// 启动文件播放流
///
/// # Arguments
/// * `config` - 音频配置(用于 Opus 编码)
/// * `reader` - WAV 文件读取器
/// * `socket` - UDP socket
/// * `target` - 目标客户端地址
/// * `parent_cancel` - 父级取消令牌(用于 session 级别取消)
pub fn spawn(
config: AudioConfig,
reader: WavReader,
socket: Arc<AudioSocket>,
target: SocketAddr,
parent_cancel: CancellationToken,
) -> StreamHandle {
let cancel = parent_cancel.child_token();
let token = cancel.clone();
tokio::spawn(async move {
if let Err(e) = Self::run(config, reader, socket, target, token).await {
eprintln!("[FilePlayback] Error: {}", e);
}
});
StreamHandle::new(cancel)
}
async fn run(
config: AudioConfig,
mut reader: WavReader,
socket: Arc<AudioSocket>,
target: SocketAddr,
cancel: CancellationToken,
) -> anyhow::Result<()> {
let mut codec = OpusCodec::new(&config)?;
let mut pcm = vec![0i16; config.frame_size];
let mut opus_buf = vec![0u8; 4096];
let frame_duration = std::time::Duration::from_millis(20);
let mut interval = tokio::time::interval(frame_duration);
println!("[FilePlayback] Started -> {}", target);
loop {
tokio::select! {
_ = cancel.cancelled() => break,
_ = interval.tick() => {
match reader.read_samples(&mut pcm) {
Ok(0) => {
println!("[FilePlayback] EOF reached");
break;
}
Ok(n) => {
if let Ok(len) = codec.encode(&pcm[..n], &mut opus_buf) {
let packet = AudioPacket {
data: opus_buf[..len].to_vec(),
};
let _ = socket.send(&packet, target).await;
}
}
Err(e) => {
eprintln!("[FilePlayback] Read error: {}", e);
break;
}
}
}
}
}
println!("[FilePlayback] Stopped");
Ok(())
}
}
/// 录音流 - 从总线订阅音频并写入 WAV 文件
pub struct RecorderStream;
impl RecorderStream {
/// 启动录音流
///
/// # Arguments
/// * `config` - 音频配置
/// * `filename` - 输出文件路径
/// * `bus_rx` - 音频总线接收器
/// * `source_filter` - 仅录制来自此地址的音频(None 表示全部录制)
/// * `parent_cancel` - 父级取消令牌
pub fn spawn(
config: AudioConfig,
filename: String,
bus_rx: broadcast::Receiver<AudioFrame>,
source_filter: Option<SocketAddr>,
parent_cancel: CancellationToken,
) -> StreamHandle {
let cancel = parent_cancel.child_token();
let token = cancel.clone();
tokio::spawn(async move {
if let Err(e) = Self::run(config, filename, bus_rx, source_filter, token).await {
eprintln!("[Recorder] Error: {}", e);
}
});
StreamHandle::new(cancel)
}
async fn run(
config: AudioConfig,
filename: String,
mut bus_rx: broadcast::Receiver<AudioFrame>,
source_filter: Option<SocketAddr>,
cancel: CancellationToken,
) -> anyhow::Result<()> {
let mut writer = WavWriter::create(&filename, config.sample_rate, config.channels)?;
let mut codec = OpusCodec::new(&config)?;
let mut pcm = vec![0i16; config.frame_size];
println!(
"[Recorder] Started -> {} (filter: {:?})",
filename, source_filter
);
loop {
tokio::select! {
_ = cancel.cancelled() => break,
result = bus_rx.recv() => {
match result {
Ok(frame) => {
// 应用源过滤
if let Some(filter_addr) = &source_filter {
if frame.source.as_ref() != Some(filter_addr) {
continue;
}
}
// 解码并写入
if let Ok(n) = codec.decode(&frame.packet.data, &mut pcm) {
let _ = writer.write_samples(&pcm[..n]);
}
}
Err(broadcast::error::RecvError::Lagged(n)) => {
eprintln!("[Recorder] Lagged {} frames", n);
}
Err(broadcast::error::RecvError::Closed) => {
break;
}
}
}
}
}
// 确保正确关闭文件
writer.finalize()?;
println!("[Recorder] Stopped, file saved: {}", filename);
Ok(())
}
}
/// 音频转发流 - 从总线订阅并转发到指定客户端
/// 用于实现"监听"功能或者服务端音频源推送
pub struct ForwardStream;
impl ForwardStream {
/// 启动转发流
///
/// # Arguments
/// * `socket` - UDP socket
/// * `target` - 目标地址
/// * `bus_rx` - 音频总线接收器
/// * `source_filter` - 源过滤(可选)
/// * `parent_cancel` - 父级取消令牌
pub fn spawn(
socket: Arc<AudioSocket>,
target: SocketAddr,
bus_rx: broadcast::Receiver<AudioFrame>,
source_filter: Option<SocketAddr>,
parent_cancel: CancellationToken,
) -> StreamHandle {
let cancel = parent_cancel.child_token();
let token = cancel.clone();
tokio::spawn(async move {
if let Err(e) = Self::run(socket, target, bus_rx, source_filter, token).await {
eprintln!("[Forward] Error: {}", e);
}
});
StreamHandle::new(cancel)
}
async fn run(
socket: Arc<AudioSocket>,
target: SocketAddr,
mut bus_rx: broadcast::Receiver<AudioFrame>,
source_filter: Option<SocketAddr>,
cancel: CancellationToken,
) -> anyhow::Result<()> {
println!(
"[Forward] Started -> {} (filter: {:?})",
target, source_filter
);
loop {
tokio::select! {
_ = cancel.cancelled() => break,
result = bus_rx.recv() => {
match result {
Ok(frame) => {
// 应用源过滤
if let Some(filter_addr) = &source_filter {
if frame.source.as_ref() != Some(filter_addr) {
continue;
}
}
// 不转发给自己
if frame.source.as_ref() == Some(&target) {
continue;
}
let _ = socket.send(&frame.packet, target).await;
}
Err(broadcast::error::RecvError::Lagged(n)) => {
eprintln!("[Forward] Lagged {} frames", n);
}
Err(broadcast::error::RecvError::Closed) => {
break;
}
}
}
}
}
println!("[Forward] Stopped");
Ok(())
}
}
+97 -10
View File
@@ -1,7 +1,19 @@
//! # Client Demo
//!
//! 演示客户端的主要功能:
//! - 自动服务发现
//! - 响应 RPC 调用
//! - 音频录制和播放
//! - 事件处理
#[cfg(target_os = "linux")]
use std::sync::Arc;
#[cfg(target_os = "linux")]
use xiao::app::client::Client;
use xiao::app::client::{Client, ClientConfig};
#[cfg(target_os = "linux")]
use xiao::net::command::Command;
#[cfg(target_os = "linux")]
use xiao::net::event::NotificationLevel;
#[cfg(not(target_os = "linux"))]
fn main() {
@@ -11,23 +23,98 @@ fn main() {
#[cfg(target_os = "linux")]
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let client = Arc::new(Client::new());
let c = client.clone();
println!("╔═══════════════════════════════════════════════════════╗");
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 client = Arc::new(Client::new(config));
// 启动事件监听器
let event_client = client.clone();
tokio::spawn(async move {
if let Err(e) = c.run().await {
let mut rx = event_client.subscribe_events();
while let Ok(event) = rx.recv().await {
println!("📨 [ServerEvent] {:?}", event);
}
});
// 启动客户端主循环
let run_client = client.clone();
tokio::spawn(async move {
if let Err(e) = run_client.run().await {
eprintln!("Client error: {}", e);
}
});
// Wait for connection
tokio::time::sleep(std::time::Duration::from_secs(3)).await;
println!("Client is running, searching for server...\n");
println!("Testing RPC call to server...");
match client.call("hello", vec!["world".to_string()]).await {
Ok(res) => println!("Server RPC response: {}", res.stdout),
Err(e) => eprintln!("Server RPC call failed: {}", e),
// 等待连接
loop {
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
if client.is_connected().await {
break;
}
}
println!("\n═══════════════════════════════════════════════════════");
println!("Connected to server!");
println!("Running client-side tests...\n");
// 1. 测试向服务器发送 Ping
println!("1️⃣ Testing Ping to server...");
match client.execute(Command::ping()).await {
Ok(result) => println!(" ✅ Pong received: {:?}", result),
Err(e) => println!(" ❌ Ping failed: {}", e),
}
// 2. 测试获取服务器信息
println!("\n2️⃣ Testing GetInfo from server...");
match client.execute(Command::GetInfo).await {
Ok(result) => println!(" ✅ Server info: {:?}", result),
Err(e) => println!(" ❌ GetInfo failed: {}", e),
}
// 3. 发送客户端事件
println!("\n3️⃣ Sending alert event to server...");
match client
.send_alert(NotificationLevel::Info, "Client started successfully!")
.await
{
Ok(_) => println!(" ✅ Alert sent"),
Err(e) => println!(" ❌ Failed to send alert: {}", e),
}
println!("\n═══════════════════════════════════════════════════════");
println!("✅ Client tests completed!");
println!("\nClient is now ready to receive commands from server.");
println!("Press Ctrl+C to exit.\n");
// 保持运行,等待服务器命令
tokio::signal::ctrl_c().await?;
println!("\nShutting down client...");
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()
}
+122 -25
View File
@@ -1,47 +1,144 @@
//! # Server Demo
//!
//! 演示服务端的主要功能:
//! - 多客户端管理
//! - RPC 调用
//! - 音频录制和播放
//! - 事件广播
use std::sync::Arc;
use xiao::app::server::Server;
use xiao::audio::config::AudioConfig;
use xiao::net::command::Command;
use xiao::net::event::{NotificationLevel, ServerEvent};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
println!("╔═══════════════════════════════════════════════════════╗");
println!("║ XiaoAi Audio Server v{}", env!("CARGO_PKG_VERSION"));
println!("╚═══════════════════════════════════════════════════════╝");
println!();
let server = Arc::new(Server::new().await?);
let s = server.clone();
// 启动服务器
tokio::spawn(async move {
s.run(8080).await.unwrap();
if let Err(e) = s.run(8080).await {
eprintln!("Server error: {}", e);
}
});
println!("Server is running. Waiting for a client to connect...");
// 启动事件监听器
let event_server = server.clone();
tokio::spawn(async move {
let mut rx = event_server.event_bus().subscribe();
while let Some((addr, event)) = rx.recv().await {
match event {
ServerEvent::ClientJoined { model, .. } => {
println!("📱 [Event] Client joined: {} ({:?})", model, addr);
}
ServerEvent::ClientLeft { model, .. } => {
println!("📴 [Event] Client left: {} ({:?})", model, addr);
}
_ => {}
}
}
});
println!("Server is running on port 8080");
println!("Waiting for clients to connect...\n");
// 主循环:等待客户端并执行测试
loop {
tokio::time::sleep(std::time::Duration::from_secs(2)).await;
let clients = server.get_clients().await;
if !clients.is_empty() {
let addr = clients[0];
println!("Client connected: {}. Starting tests...", addr);
println!("1. Testing Shell RPC...");
let res = server
.call(addr, "shell", vec!["echo 'Hello from Mars!'".to_string()])
.await?;
println!(
"RPC Result: stdout={}, code={}",
res.stdout.trim(),
res.code
);
println!("2. Testing Audio Recording (10s)...");
server.start_record(addr, AudioConfig::voice_16k()).await?;
tokio::time::sleep(std::time::Duration::from_secs(12)).await;
println!("3. Testing Audio Playback (from temp/test.wav)...");
server.start_play(addr, "temp/test.wav").await?;
break;
if clients.is_empty() {
continue;
}
let addr = clients[0];
println!("\n═══════════════════════════════════════════════════════");
println!("Client connected: {}", addr);
println!("Running demo tests...\n");
// 1. 测试 Ping
println!("1️⃣ Testing Ping...");
match server.execute(addr, Command::ping()).await {
Ok(result) => println!(" ✅ Ping result: {:?}", result),
Err(e) => println!(" ❌ Ping failed: {}", e),
}
// 2. 测试获取设备信息
println!("\n2️⃣ Testing GetInfo...");
match server.execute(addr, Command::GetInfo).await {
Ok(result) => println!(" ✅ Device info: {:?}", result),
Err(e) => println!(" ❌ GetInfo failed: {}", e),
}
// 3. 测试 Shell 命令
println!("\n3️⃣ Testing Shell RPC...");
match server.shell(addr, "echo 'Hello from XiaoAi!'").await {
Ok(resp) => {
println!(" ✅ stdout: {}", resp.stdout.trim());
println!(" ✅ exit_code: {}", resp.exit_code);
}
Err(e) => println!(" ❌ Shell failed: {}", e),
}
// 4. 测试事件广播
println!("\n4️⃣ Broadcasting notification event...");
server
.broadcast_event(ServerEvent::Notification {
level: NotificationLevel::Info,
title: "Test".to_string(),
message: "This is a test notification from server".to_string(),
})
.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() {
match server.start_play(addr, "temp/test.wav").await {
Ok(_) => {
println!(" ▶️ Playback started...");
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
server.stop_play(addr).await?;
println!(" ⏹️ Playback stopped");
}
Err(e) => println!(" ❌ Playback failed: {}", e),
}
} else {
println!(" ⚠️ No test file found at temp/test.wav, skipping...");
}
println!("\n═══════════════════════════════════════════════════════");
println!("✅ All tests completed!");
println!("\nServer status:");
println!(" • Connected clients: {}", server.client_count());
println!(" • Uptime: {} seconds", server.uptime_secs());
println!("\nPress Ctrl+C to exit.");
break;
}
println!("Tests completed. Press Ctrl+C to exit.");
// 等待退出信号
tokio::signal::ctrl_c().await?;
println!("\nShutting down server...");
server.shutdown();
Ok(())
}
+324
View File
@@ -0,0 +1,324 @@
//! # Command - RPC 命令类型系统
//!
//! 支持多种类型的命令,每种命令有独立的请求和响应结构。
//!
//! ## 设计
//!
//! ```text
//! ┌─────────────────────────────────────────────────────────────┐
//! │ Command Types │
//! │ │
//! │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
//! │ │ Shell │ │ GetInfo │ │ SetVolume │ ... │
//! │ │ cmd → out │ │ () → Info │ │ vol → () │ │
//! │ └──────────────┘ └──────────────┘ └──────────────┘ │
//! │ │
//! │ ┌──────────────────┐ │
//! │ │ RpcRequest │ │
//! │ │ id + Command │ │
//! │ └────────┬─────────┘ │
//! │ │ │
//! │ ▼ │
//! │ ┌──────────────────┐ │
//! │ │ RpcResponse │ │
//! │ │ id + Result │ │
//! │ └──────────────────┘ │
//! └─────────────────────────────────────────────────────────────┘
//! ```
use serde::{Deserialize, Serialize};
// ==================== 命令请求类型 ====================
/// Shell 命令请求
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ShellRequest {
/// 要执行的命令
pub command: String,
/// 工作目录(可选)
pub cwd: Option<String>,
/// 环境变量(可选)
pub env: Option<Vec<(String, String)>>,
/// 超时秒数(可选)
pub timeout_secs: Option<u32>,
}
/// Shell 命令响应
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct ShellResponse {
pub stdout: String,
pub stderr: String,
pub exit_code: i32,
}
/// 获取设备信息请求
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct GetInfoRequest;
/// 设备信息响应
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct DeviceInfo {
pub model: String,
pub serial_number: String,
pub version: String,
pub uptime_secs: u64,
pub audio_state: AudioState,
}
/// 音频状态
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct AudioState {
pub is_recording: bool,
pub is_playing: bool,
pub volume: u8,
}
/// 设置音量请求
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct SetVolumeRequest {
pub volume: u8, // 0-100
}
/// 设置音量响应
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct SetVolumeResponse {
pub previous: u8,
pub current: u8,
}
/// 文件操作请求
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum FileRequest {
/// 读取文件
Read { path: String },
/// 写入文件
Write { path: String, data: Vec<u8> },
/// 删除文件
Delete { path: String },
/// 列出目录
List { path: String },
/// 获取文件信息
Stat { path: String },
}
/// 文件操作响应
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum FileResponse {
/// 读取结果
Data(Vec<u8>),
/// 写入成功
Written { bytes: usize },
/// 删除成功
Deleted,
/// 目录列表
Entries(Vec<FileEntry>),
/// 文件信息
Stat(FileStat),
}
/// 文件条目
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct FileEntry {
pub name: String,
pub is_dir: bool,
pub size: u64,
}
/// 文件状态
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct FileStat {
pub size: u64,
pub is_dir: bool,
pub modified: u64,
}
/// 系统控制请求
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum SystemRequest {
/// 重启
Reboot,
/// 关机
Shutdown,
/// 获取系统负载
GetLoad,
/// 获取内存使用
GetMemory,
}
/// 系统控制响应
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum SystemResponse {
/// 操作已接受
Accepted,
/// 系统负载
Load { one: f32, five: f32, fifteen: f32 },
/// 内存使用
Memory { total: u64, used: u64, free: u64 },
}
/// 自定义命令请求(用于扩展)
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct CustomRequest {
pub name: String,
pub payload: Vec<u8>,
}
/// 自定义命令响应
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct CustomResponse {
pub payload: Vec<u8>,
}
// ==================== 统一命令枚举 ====================
/// RPC 命令 - 统一的请求类型
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum Command {
/// 执行 Shell 命令
Shell(ShellRequest),
/// 获取设备信息
GetInfo,
/// 设置音量
SetVolume(SetVolumeRequest),
/// 文件操作
File(FileRequest),
/// 系统控制
System(SystemRequest),
/// 自定义命令
Custom(CustomRequest),
/// Ping(用于测量延迟)
Ping { timestamp: u64 },
}
/// RPC 结果 - 统一的响应类型
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum CommandResult {
/// Shell 命令结果
Shell(ShellResponse),
/// 设备信息
Info(DeviceInfo),
/// 音量设置结果
Volume(SetVolumeResponse),
/// 文件操作结果
File(FileResponse),
/// 系统控制结果
System(SystemResponse),
/// 自定义命令结果
Custom(CustomResponse),
/// Pong 响应
Pong { timestamp: u64, server_time: u64 },
/// 错误
Error(CommandError),
}
/// 命令错误
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct CommandError {
pub code: i32,
pub message: String,
}
impl CommandError {
pub fn new(code: i32, message: impl Into<String>) -> Self {
Self {
code,
message: message.into(),
}
}
pub fn not_found(msg: impl Into<String>) -> Self {
Self::new(-1, msg)
}
pub fn invalid_args(msg: impl Into<String>) -> Self {
Self::new(-2, msg)
}
pub fn permission_denied(msg: impl Into<String>) -> Self {
Self::new(-3, msg)
}
pub fn internal(msg: impl Into<String>) -> Self {
Self::new(-500, msg)
}
pub fn timeout(msg: impl Into<String>) -> Self {
Self::new(-408, msg)
}
pub fn not_implemented() -> Self {
Self::new(-501, "Not implemented")
}
}
impl std::fmt::Display for CommandError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "[{}] {}", self.code, self.message)
}
}
impl std::error::Error for CommandError {}
// ==================== 便捷构造方法 ====================
impl Command {
/// 创建 Shell 命令
pub fn shell(cmd: impl Into<String>) -> Self {
Self::Shell(ShellRequest {
command: cmd.into(),
cwd: None,
env: None,
timeout_secs: None,
})
}
/// 创建带超时的 Shell 命令
pub fn shell_with_timeout(cmd: impl Into<String>, timeout: u32) -> Self {
Self::Shell(ShellRequest {
command: cmd.into(),
cwd: None,
env: None,
timeout_secs: Some(timeout),
})
}
/// 创建 Ping 命令
pub fn ping() -> Self {
Self::Ping {
timestamp: std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_millis() as u64,
}
}
}
impl CommandResult {
/// 创建成功的 Shell 响应
pub fn shell_ok(stdout: String) -> Self {
Self::Shell(ShellResponse {
stdout,
stderr: String::new(),
exit_code: 0,
})
}
/// 创建错误响应
pub fn error(err: CommandError) -> Self {
Self::Error(err)
}
/// 检查是否是错误
pub fn is_error(&self) -> bool {
matches!(self, Self::Error(_))
}
/// 获取错误(如果有)
pub fn as_error(&self) -> Option<&CommandError> {
match self {
Self::Error(e) => Some(e),
_ => None,
}
}
}
+286
View File
@@ -0,0 +1,286 @@
//! # Event - 实时事件系统
//!
//! 支持双向的实时事件推送,包括:
//! - 服务端事件(推送给客户端)
//! - 客户端事件(推送给服务端)
//! - 事件订阅和过滤
//!
//! ## 设计
//!
//! ```text
//! ┌─────────────────────────────────────────────────────────────┐
//! │ Event System │
//! │ │
//! │ Server Events: │
//! │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
//! │ │ AudioStatus │ │ ClientJoined │ │ Message │ ... │
//! │ └──────────────┘ └──────────────┘ └──────────────┘ │
//! │ │
//! │ Client Events: │
//! │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
//! │ │ StatusUpdate │ │ Metrics │ │ Alert │ ... │
//! │ └──────────────┘ └──────────────┘ └──────────────┘ │
//! │ │
//! │ ┌─────────────────────┐ │
//! │ │ EventBus │ │
//! │ │ broadcast channel │ │
//! │ └─────────────────────┘ │
//! └─────────────────────────────────────────────────────────────┘
//! ```
use serde::{Deserialize, Serialize};
use std::net::SocketAddr;
use std::sync::Arc;
use tokio::sync::broadcast;
// ==================== 事件类型 ====================
/// 服务端事件(Server → Client
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum ServerEvent {
/// 音频状态变化
AudioStatusChanged {
is_recording: bool,
is_playing: bool,
},
/// 客户端加入(广播给其他客户端)
ClientJoined {
addr: String,
model: String,
},
/// 客户端离开
ClientLeft {
addr: String,
model: String,
},
/// 服务器消息/通知
Notification {
level: NotificationLevel,
title: String,
message: String,
},
/// 录音完成
RecordingComplete {
filename: String,
duration_secs: f32,
size_bytes: u64,
},
/// 播放完成
PlaybackComplete {
filename: String,
},
/// 服务器状态更新
ServerStatus {
connected_clients: u32,
uptime_secs: u64,
},
/// 自定义事件
Custom {
name: String,
payload: Vec<u8>,
},
}
/// 客户端事件(Client → Server
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum ClientEvent {
/// 状态更新
StatusUpdate {
cpu_usage: f32,
memory_usage: f32,
temperature: Option<f32>,
},
/// 音频电平
AudioLevel {
level_db: f32,
is_silent: bool,
},
/// 按键事件
KeyPress {
key: String,
action: KeyAction,
},
/// 警告/错误
Alert {
level: NotificationLevel,
message: String,
},
/// 自定义事件
Custom {
name: String,
payload: Vec<u8>,
},
}
/// 通知级别
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
pub enum NotificationLevel {
Debug,
Info,
Warning,
Error,
}
/// 按键动作
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
pub enum KeyAction {
Press,
Release,
LongPress,
}
// ==================== 事件总线 ====================
/// 事件订阅者信息
pub struct EventSubscription<E> {
pub receiver: broadcast::Receiver<E>,
}
impl<E: Clone> EventSubscription<E> {
/// 接收下一个事件
pub async fn recv(&mut self) -> Option<E> {
match self.receiver.recv().await {
Ok(event) => Some(event),
Err(broadcast::error::RecvError::Lagged(_)) => {
// 跳过丢失的事件,继续接收
Box::pin(self.recv()).await
}
Err(broadcast::error::RecvError::Closed) => None,
}
}
}
/// 服务端事件总线
pub struct ServerEventBus {
tx: broadcast::Sender<(Option<SocketAddr>, ServerEvent)>,
}
impl ServerEventBus {
pub fn new(capacity: usize) -> Self {
let (tx, _) = broadcast::channel(capacity);
Self { tx }
}
/// 发布事件(广播给所有客户端)
pub fn publish(&self, event: ServerEvent) {
let _ = self.tx.send((None, event));
}
/// 发布事件给指定客户端
pub fn publish_to(&self, addr: SocketAddr, event: ServerEvent) {
let _ = self.tx.send((Some(addr), event));
}
/// 订阅事件
pub fn subscribe(&self) -> EventSubscription<(Option<SocketAddr>, ServerEvent)> {
EventSubscription {
receiver: self.tx.subscribe(),
}
}
}
impl Default for ServerEventBus {
fn default() -> Self {
Self::new(256)
}
}
/// 客户端事件总线
pub struct ClientEventBus {
tx: broadcast::Sender<(SocketAddr, ClientEvent)>,
}
impl ClientEventBus {
pub fn new(capacity: usize) -> Self {
let (tx, _) = broadcast::channel(capacity);
Self { tx }
}
/// 发布客户端事件
pub fn publish(&self, client_addr: SocketAddr, event: ClientEvent) {
let _ = self.tx.send((client_addr, event));
}
/// 订阅事件
pub fn subscribe(&self) -> EventSubscription<(SocketAddr, ClientEvent)> {
EventSubscription {
receiver: self.tx.subscribe(),
}
}
}
impl Default for ClientEventBus {
fn default() -> Self {
Self::new(256)
}
}
// ==================== 事件过滤器 ====================
/// 服务端事件过滤器
pub trait ServerEventFilter: Send + Sync {
fn should_process(&self, event: &ServerEvent) -> bool;
}
/// 接受所有事件
pub struct AcceptAll;
impl ServerEventFilter for AcceptAll {
fn should_process(&self, _: &ServerEvent) -> bool {
true
}
}
/// 只接受通知事件
pub struct NotificationsOnly;
impl ServerEventFilter for NotificationsOnly {
fn should_process(&self, event: &ServerEvent) -> bool {
matches!(event, ServerEvent::Notification { .. })
}
}
/// 只接受音频相关事件
pub struct AudioEventsOnly;
impl ServerEventFilter for AudioEventsOnly {
fn should_process(&self, event: &ServerEvent) -> bool {
matches!(
event,
ServerEvent::AudioStatusChanged { .. }
| ServerEvent::RecordingComplete { .. }
| ServerEvent::PlaybackComplete { .. }
)
}
}
// ==================== 事件处理器 ====================
/// 事件处理器 trait
#[allow(async_fn_in_trait)]
pub trait EventHandler<E>: Send + Sync {
async fn handle(&self, event: E);
}
/// 运行事件处理循环
pub async fn run_event_loop<E, H>(mut subscription: EventSubscription<E>, handler: Arc<H>)
where
E: Clone + Send + 'static,
H: EventHandler<E> + 'static,
{
while let Some(event) = subscription.recv().await {
handler.handle(event).await;
}
}
+12
View File
@@ -1,4 +1,16 @@
//! # Net 模块
//!
//! 网络通信相关模块:
//! - `command` - RPC 命令类型系统
//! - `discovery` - 服务发现
//! - `event` - 实时事件系统
//! - `network` - 底层网络连接
//! - `protocol` - 通信协议定义
//! - `rpc` - RPC 调用管理
pub mod command;
pub mod discovery;
pub mod event;
pub mod network;
pub mod protocol;
pub mod rpc;
+65 -29
View File
@@ -1,59 +1,90 @@
//! # Protocol - 通信协议定义
//!
//! 定义 Client 和 Server 之间的所有通信协议。
use crate::audio::config::AudioConfig;
use crate::net::command::{Command, CommandResult};
use crate::net::event::{ClientEvent, ServerEvent};
use serde::{Deserialize, Serialize};
// ==================== 基础类型 ====================
/// 客户端信息
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ClientInfo {
pub model: String,
pub serial_number: String,
}
// ==================== 控制包 ====================
/// 控制包 - TCP 通道传输的所有消息类型
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum ControlPacket {
// Discovery
Discovery {
protocol: String,
port: u16,
},
// ========== 服务发现 ==========
/// 服务发现广播
Discovery { protocol: String, port: u16 },
// Handshake
// ========== 握手 ==========
/// 服务端握手
ServerHello {
auth: String,
version: String,
udp_port: u16, // for audio
udp_port: u16,
},
/// 客户端握手
ClientHello {
auth: String,
version: String,
udp_port: u16, // for audio
udp_port: u16,
info: ClientInfo,
},
// Heartbeat
// ========== 心跳 ==========
Ping,
Pong,
// RPC
RpcRequest {
id: u32,
method: String,
args: Vec<String>,
},
RpcResponse {
id: u32,
result: RpcResult,
},
// ========== RPC ==========
/// RPC 请求(新版,使用 Command 类型)
RpcRequest { id: u32, command: Command },
/// RPC 响应(新版,使用 CommandResult 类型)
RpcResponse { id: u32, result: CommandResult },
// Audio Control
StartRecording {
config: AudioConfig,
},
// ========== 事件 ==========
/// 服务端事件推送
ServerEvent(ServerEvent),
/// 客户端事件推送
ClientEvent(ClientEvent),
// ========== 音频控制 ==========
/// 开始录音
StartRecording { config: AudioConfig },
/// 停止录音
StopRecording,
StartPlayback {
config: AudioConfig,
},
/// 开始播放
StartPlayback { config: AudioConfig },
/// 停止播放
StopPlayback,
// ========== 订阅管理 ==========
/// 订阅事件类型
Subscribe { event_types: Vec<String> },
/// 取消订阅
Unsubscribe { event_types: Vec<String> },
}
// ==================== 音频包 ====================
/// 音频数据包 - UDP 通道传输
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct AudioPacket {
/// Opus 编码的音频数据
pub data: Vec<u8>,
}
// ==================== 兼容性:旧版 RPC 结果 ====================
/// 旧版 RPC 结果(保持向后兼容)
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct RpcResult {
pub stdout: String,
@@ -61,7 +92,12 @@ pub struct RpcResult {
pub code: i32,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct AudioPacket {
pub data: Vec<u8>,
impl From<crate::net::command::ShellResponse> for RpcResult {
fn from(resp: crate::net::command::ShellResponse) -> Self {
Self {
stdout: resp.stdout,
stderr: resp.stderr,
code: resp.exit_code,
}
}
}
+142 -7
View File
@@ -1,32 +1,167 @@
use crate::net::protocol::RpcResult;
//! # RPC Manager - RPC 调用管理
//!
//! 管理 RPC 请求的生命周期,包括:
//! - 请求 ID 生成
//! - 请求/响应匹配
//! - 超时处理
use crate::net::command::CommandResult;
use parking_lot::Mutex;
use std::collections::HashMap;
use std::sync::atomic::{AtomicU32, Ordering};
use std::time::Duration;
use tokio::sync::oneshot;
/// RPC 调用错误
#[derive(Debug)]
pub enum RpcError {
/// 超时
Timeout,
/// 通道关闭
Cancelled,
/// 未连接
NotConnected,
}
impl std::fmt::Display for RpcError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
RpcError::Timeout => write!(f, "RPC timeout"),
RpcError::Cancelled => write!(f, "RPC cancelled"),
RpcError::NotConnected => write!(f, "Not connected"),
}
}
}
impl std::error::Error for RpcError {}
/// 等待中的 RPC 请求
struct PendingRequest {
tx: oneshot::Sender<CommandResult>,
created_at: std::time::Instant,
}
/// RPC 管理器
pub struct RpcManager {
next_id: AtomicU32,
pending: Mutex<HashMap<u32, oneshot::Sender<RpcResult>>>,
pending: Mutex<HashMap<u32, PendingRequest>>,
default_timeout: Duration,
}
impl RpcManager {
/// 创建新的 RPC 管理器
pub fn new() -> Self {
Self::with_timeout(Duration::from_secs(30))
}
/// 创建带自定义超时的 RPC 管理器
pub fn with_timeout(timeout: Duration) -> Self {
Self {
next_id: AtomicU32::new(1),
pending: Mutex::new(HashMap::new()),
default_timeout: timeout,
}
}
pub fn register(&self) -> (u32, oneshot::Receiver<RpcResult>) {
/// 注册一个新的 RPC 请求
///
/// 返回 (请求ID, 响应接收器)
pub fn register(&self) -> (u32, oneshot::Receiver<CommandResult>) {
let id = self.next_id.fetch_add(1, Ordering::SeqCst);
let (tx, rx) = oneshot::channel();
self.pending.lock().insert(id, tx);
self.pending.lock().insert(
id,
PendingRequest {
tx,
created_at: std::time::Instant::now(),
},
);
(id, rx)
}
pub fn resolve(&self, id: u32, result: RpcResult) {
if let Some(tx) = self.pending.lock().remove(&id) {
let _ = tx.send(result);
/// 解析 RPC 响应
pub fn resolve(&self, id: u32, result: CommandResult) {
if let Some(req) = self.pending.lock().remove(&id) {
let _ = req.tx.send(result);
}
}
/// 取消指定的 RPC 请求
pub fn cancel(&self, id: u32) {
self.pending.lock().remove(&id);
}
/// 获取待处理请求数量
pub fn pending_count(&self) -> usize {
self.pending.lock().len()
}
/// 清理超时的请求
pub fn cleanup_expired(&self) {
let mut pending = self.pending.lock();
let now = std::time::Instant::now();
pending.retain(|_, req| {
if now.duration_since(req.created_at) > self.default_timeout {
false
} else {
true
}
});
}
/// 获取默认超时时间
pub fn default_timeout(&self) -> Duration {
self.default_timeout
}
}
impl Default for RpcManager {
fn default() -> Self {
Self::new()
}
}
/// RPC 调用辅助函数
pub async fn call_with_timeout<F, Fut>(
timeout: Duration,
register_fn: F,
) -> Result<CommandResult, RpcError>
where
F: FnOnce() -> Fut,
Fut: std::future::Future<Output = Result<oneshot::Receiver<CommandResult>, RpcError>>,
{
let rx = register_fn().await?;
match tokio::time::timeout(timeout, rx).await {
Ok(Ok(result)) => Ok(result),
Ok(Err(_)) => Err(RpcError::Cancelled),
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(_)));
}
}