chore: 测试播放和录音
This commit is contained in:
@@ -1,115 +0,0 @@
|
||||
#![cfg(target_os = "linux")]
|
||||
|
||||
use crate::app::client::handlers;
|
||||
use crate::app::client::session::ClientSession;
|
||||
use crate::audio::config::AudioConfig;
|
||||
use crate::net::discovery::Discovery;
|
||||
use crate::net::network::{AudioSocket, ClientNetwork};
|
||||
use crate::net::protocol::{ControlPacket, DeviceInfo, RpcResult};
|
||||
use anyhow::{Context, Result};
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::broadcast;
|
||||
|
||||
pub struct Client {
|
||||
info: DeviceInfo,
|
||||
session: Arc<tokio::sync::Mutex<Option<Arc<ClientSession>>>>,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
pub fn new(model: &str, mac: &str, version: u32) -> Self {
|
||||
Self {
|
||||
info: DeviceInfo {
|
||||
model: model.to_string(),
|
||||
mac: mac.to_string(),
|
||||
version,
|
||||
},
|
||||
session: Arc::new(tokio::sync::Mutex::new(None)),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run(self: Arc<Self>) -> Result<()> {
|
||||
println!("正在寻找服务端...");
|
||||
let (server_ip, server_port) = Discovery::discover_server().await?;
|
||||
let server_addr = SocketAddr::new(server_ip, server_port);
|
||||
println!("发现服务端: {}", server_addr);
|
||||
|
||||
let network = ClientNetwork::connect(server_addr).await?;
|
||||
let mut control = network.into_control();
|
||||
|
||||
// 握手
|
||||
control
|
||||
.send_packet(&ControlPacket::ClientIdentify {
|
||||
info: self.info.clone(),
|
||||
})
|
||||
.await?;
|
||||
|
||||
match control.recv_packet().await? {
|
||||
ControlPacket::IdentifyOk => println!("认证成功"),
|
||||
p => return Err(anyhow::anyhow!("认证失败: {:?}", p)),
|
||||
}
|
||||
|
||||
let (reader, writer) = control.split();
|
||||
let session = Arc::new(ClientSession::new(self.info.clone(), writer));
|
||||
*self.session.lock().await = Some(session.clone());
|
||||
|
||||
let (stop_tx, _) = broadcast::channel::<()>(1);
|
||||
let audio_socket = Arc::new(AudioSocket::bind().await?);
|
||||
|
||||
// 启动测试 RPC 调用的任务
|
||||
let client_clone = self.clone();
|
||||
tokio::spawn(async move {
|
||||
tokio::time::sleep(std::time::Duration::from_secs(2)).await;
|
||||
println!("测试向服务端发起 RPC: status");
|
||||
match client_clone.call_server("status", vec![]).await {
|
||||
Ok(res) => println!("收到服务端响应: {:?}", res),
|
||||
Err(e) => eprintln!("向服务端发起 RPC 失败: {}", e),
|
||||
}
|
||||
});
|
||||
|
||||
let mut reader = reader;
|
||||
loop {
|
||||
let packet = reader.recv_packet().await?;
|
||||
let self_clone = self.clone();
|
||||
let session_clone = session.clone();
|
||||
let audio_socket = audio_socket.clone();
|
||||
let stop_tx = stop_tx.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = handlers::handle_packet(
|
||||
self_clone,
|
||||
session_clone,
|
||||
packet,
|
||||
audio_socket,
|
||||
stop_tx,
|
||||
server_addr,
|
||||
)
|
||||
.await
|
||||
{
|
||||
eprintln!("处理控制包出错: {}", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn call_server(&self, method: &str, args: Vec<String>) -> Result<RpcResult> {
|
||||
let session = self
|
||||
.session
|
||||
.lock()
|
||||
.await
|
||||
.clone()
|
||||
.context("Session not established")?;
|
||||
let (id, rx) = session.rpc.alloc_id();
|
||||
session
|
||||
.writer
|
||||
.lock()
|
||||
.await
|
||||
.send_packet(&ControlPacket::RpcRequest {
|
||||
id,
|
||||
method: method.to_string(),
|
||||
args,
|
||||
})
|
||||
.await?;
|
||||
Ok(rx.await?)
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
#![cfg(target_os = "linux")]
|
||||
|
||||
use crate::app::client::core::Client;
|
||||
use anyhow::Result;
|
||||
|
||||
pub async fn run_client() -> Result<()> {
|
||||
// 模拟从系统获取信息
|
||||
let model = "XiaoAi-V2-Simulated";
|
||||
let mac = "00:11:22:33:44:55";
|
||||
let version = 1;
|
||||
|
||||
let client = std::sync::Arc::new(Client::new(model, mac, version));
|
||||
client.run().await
|
||||
}
|
||||
@@ -1,202 +0,0 @@
|
||||
#![cfg(target_os = "linux")]
|
||||
|
||||
use crate::app::client::core::Client;
|
||||
use crate::app::client::session::ClientSession;
|
||||
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, ControlPacket, RpcResult};
|
||||
use anyhow::Result;
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::broadcast;
|
||||
|
||||
pub async fn handle_packet(
|
||||
client: Arc<Client>,
|
||||
session: Arc<ClientSession>,
|
||||
packet: ControlPacket,
|
||||
audio_socket: Arc<AudioSocket>,
|
||||
stop_tx: broadcast::Sender<()>,
|
||||
server_addr: SocketAddr,
|
||||
) -> Result<()> {
|
||||
match packet {
|
||||
ControlPacket::RpcResponse { id, result } => {
|
||||
session.rpc.fulfill(id, result);
|
||||
}
|
||||
ControlPacket::RpcRequest { id, method, args } => {
|
||||
println!("收到 RPC 请求: {} {:?}", method, args);
|
||||
let result = handle_rpc(&method, args).await;
|
||||
let response = ControlPacket::RpcResponse { id, result };
|
||||
session.writer.lock().await.send_packet(&response).await?;
|
||||
}
|
||||
ControlPacket::StartRecording { config } => {
|
||||
// ...
|
||||
println!("开始录音: {:?}", config);
|
||||
let mut stop_rx = stop_tx.subscribe();
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = handle_recording(config, audio_socket, server_addr, stop_rx).await {
|
||||
eprintln!("录音出错: {}", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
ControlPacket::StopRecording => {
|
||||
println!("停止录音");
|
||||
let _ = stop_tx.send(());
|
||||
}
|
||||
ControlPacket::StartPlayback { config } => {
|
||||
println!("开始播放: {:?}", config);
|
||||
let mut stop_rx = stop_tx.subscribe();
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = handle_playback(config, audio_socket, stop_rx).await {
|
||||
eprintln!("播放出错: {}", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
ControlPacket::StopPlayback => {
|
||||
println!("停止播放");
|
||||
let _ = stop_tx.send(());
|
||||
}
|
||||
ControlPacket::Ping => {
|
||||
let _ = session
|
||||
.writer
|
||||
.lock()
|
||||
.await
|
||||
.send_packet(&ControlPacket::Pong)
|
||||
.await;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn handle_rpc(method: &str, args: Vec<String>) -> RpcResult {
|
||||
match method {
|
||||
"shell" => handle_shell(args).await,
|
||||
_ => RpcResult {
|
||||
stdout: "".to_string(),
|
||||
stderr: format!("Unknown method: {}", method),
|
||||
code: -1,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_shell(args: Vec<String>) -> RpcResult {
|
||||
if args.is_empty() {
|
||||
return RpcResult {
|
||||
stdout: "".to_string(),
|
||||
stderr: "Missing command argument".to_string(),
|
||||
code: -1,
|
||||
};
|
||||
}
|
||||
let cmd = &args[0];
|
||||
println!("Executing Shell: {}", cmd);
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
use std::process::Command;
|
||||
let output = Command::new("sh").arg("-c").arg(cmd).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 {
|
||||
stdout: "".to_string(),
|
||||
stderr: e.to_string(),
|
||||
code: -1,
|
||||
},
|
||||
}
|
||||
}
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
{
|
||||
RpcResult {
|
||||
stdout: format!("Mock execution of {} successful", cmd),
|
||||
stderr: "".to_string(),
|
||||
code: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_recording(
|
||||
config: AudioConfig,
|
||||
socket: Arc<AudioSocket>,
|
||||
server_addr: SocketAddr,
|
||||
mut stop_rx: broadcast::Receiver<()>,
|
||||
) -> Result<()> {
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
let recorder = AudioRecorder::new(&config)?;
|
||||
let mut codec = OpusCodec::new(&config)?;
|
||||
let mut pcm_buf = vec![0i16; config.frame_size];
|
||||
let mut opus_buf = vec![0u8; 4096];
|
||||
|
||||
loop {
|
||||
if stop_rx.try_recv().is_ok() {
|
||||
break;
|
||||
}
|
||||
let n = recorder.read(&mut pcm_buf)?;
|
||||
if n > 0 {
|
||||
let opus_len = codec.encode(&pcm_buf[..n], &mut opus_buf)?;
|
||||
let packet = AudioPacket {
|
||||
data: opus_buf[..opus_len].to_vec(),
|
||||
};
|
||||
socket.send_packet(&packet, server_addr).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
{
|
||||
println!("当前系统不支持 ALSA 录音,模拟发送音频数据...");
|
||||
loop {
|
||||
if stop_rx.try_recv().is_ok() {
|
||||
break;
|
||||
}
|
||||
tokio::time::sleep(std::time::Duration::from_millis(20)).await;
|
||||
let packet = AudioPacket {
|
||||
data: vec![0u8; 10],
|
||||
};
|
||||
socket.send_packet(&packet, server_addr).await?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_playback(
|
||||
config: AudioConfig,
|
||||
socket: Arc<AudioSocket>,
|
||||
mut stop_rx: broadcast::Receiver<()>,
|
||||
) -> Result<()> {
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
let player = AudioPlayer::new(&config)?;
|
||||
let mut codec = OpusCodec::new(&config)?;
|
||||
let mut pcm_buf = vec![0i16; config.frame_size];
|
||||
let mut udp_buf = vec![0u8; 4096];
|
||||
|
||||
loop {
|
||||
if stop_rx.try_recv().is_ok() {
|
||||
break;
|
||||
}
|
||||
let (packet, _) = socket.recv_packet(&mut udp_buf).await?;
|
||||
let pcm_len = codec.decode(&packet.data, &mut pcm_buf)?;
|
||||
player.write(&pcm_buf[..pcm_len])?;
|
||||
}
|
||||
}
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
{
|
||||
println!("当前系统不支持 ALSA 播放,模拟接收音频数据...");
|
||||
let mut udp_buf = vec![0u8; 4096];
|
||||
loop {
|
||||
tokio::select! {
|
||||
_ = stop_rx.recv() => break,
|
||||
res = socket.recv_packet(&mut udp_buf) => {
|
||||
let _ = res?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,4 +1,234 @@
|
||||
pub mod core;
|
||||
pub mod entry;
|
||||
pub mod handlers;
|
||||
pub mod session;
|
||||
#![cfg(target_os = "linux")]
|
||||
|
||||
use crate::audio::codec::OpusCodec;
|
||||
use crate::audio::player::AudioPlayer;
|
||||
use crate::audio::recorder::AudioRecorder;
|
||||
use crate::net::discovery::Discovery;
|
||||
use crate::net::network::{AudioSocket, Connection};
|
||||
use crate::net::protocol::{AudioPacket, ControlPacket, DeviceInfo, RpcResult};
|
||||
use crate::net::rpc::RpcManager;
|
||||
use anyhow::Result;
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::{Mutex, broadcast};
|
||||
|
||||
pub struct Client {
|
||||
info: DeviceInfo,
|
||||
conn: Mutex<Option<Arc<Connection>>>,
|
||||
rpc: Arc<RpcManager>,
|
||||
server_audio_addr: Mutex<Option<SocketAddr>>,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
info: DeviceInfo::current(),
|
||||
conn: Mutex::new(None),
|
||||
rpc: Arc::new(RpcManager::new()),
|
||||
server_audio_addr: Mutex::new(None),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run(self: Arc<Self>) -> Result<()> {
|
||||
let (ip, tcp_port, udp_port) = Discovery::listen().await?;
|
||||
let addr = SocketAddr::new(ip, tcp_port);
|
||||
let audio_addr = SocketAddr::new(ip, udp_port);
|
||||
println!("Found server at {}, audio at {}", addr, audio_addr);
|
||||
*self.server_audio_addr.lock().await = Some(audio_addr);
|
||||
|
||||
println!("Connecting to TCP server at {}...", addr);
|
||||
let stream = tokio::net::TcpStream::connect(addr).await?;
|
||||
let conn = Arc::new(Connection::new(stream)?);
|
||||
println!("TCP connected, sending identification...");
|
||||
|
||||
let audio = Arc::new(AudioSocket::bind().await?);
|
||||
conn.send(&ControlPacket::ClientIdentify {
|
||||
info: self.info.clone(),
|
||||
udp_port: audio.port(),
|
||||
})
|
||||
.await?;
|
||||
match conn.recv().await? {
|
||||
ControlPacket::IdentifyOk => println!("Connected to server"),
|
||||
p => return Err(anyhow::anyhow!("Handshake failed: {:?}", p)),
|
||||
}
|
||||
|
||||
*self.conn.lock().await = Some(conn.clone());
|
||||
let (stop_tx, _) = broadcast::channel(1);
|
||||
|
||||
loop {
|
||||
let packet = conn.recv().await?;
|
||||
let this = self.clone();
|
||||
let audio = audio.clone();
|
||||
let stop_tx = stop_tx.clone();
|
||||
let audio_addr = self.server_audio_addr.lock().await.unwrap();
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = this.handle_packet(packet, audio, stop_tx, audio_addr).await {
|
||||
eprintln!("Handle packet error: {}", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_packet(
|
||||
&self,
|
||||
packet: ControlPacket,
|
||||
audio: Arc<AudioSocket>,
|
||||
stop_tx: broadcast::Sender<()>,
|
||||
server_addr: SocketAddr,
|
||||
) -> Result<()> {
|
||||
match packet {
|
||||
ControlPacket::RpcRequest { id, method, args } => {
|
||||
let result = self.handle_rpc(&method, args).await;
|
||||
if let Some(conn) = self.conn.lock().await.as_ref() {
|
||||
conn.send(&ControlPacket::RpcResponse { id, result })
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
ControlPacket::RpcResponse { id, result } => self.rpc.resolve(id, result),
|
||||
ControlPacket::StartRecording { config } => {
|
||||
let mut stop_rx = stop_tx.subscribe();
|
||||
tokio::spawn(async move {
|
||||
let (pcm_tx, mut pcm_rx) = tokio::sync::mpsc::channel::<Vec<i16>>(20);
|
||||
|
||||
// 录音线程:使用 std::thread 处理阻塞的 ALSA 调用
|
||||
let config_clone = config.clone();
|
||||
std::thread::spawn(move || {
|
||||
let recorder = match AudioRecorder::new(&config_clone) {
|
||||
Ok(r) => r,
|
||||
Err(e) => {
|
||||
eprintln!("Failed to start recorder: {}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
loop {
|
||||
let mut pcm = vec![0i16; config_clone.frame_size];
|
||||
match recorder.read(&mut pcm) {
|
||||
Ok(n) => {
|
||||
if pcm_tx.blocking_send(pcm[..n].to_vec()).is_err() {
|
||||
break; // Receiver dropped, stop recording
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Recorder read error: {}", e);
|
||||
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! {
|
||||
_ = stop_rx.recv() => break,
|
||||
Some(pcm_data) = pcm_rx.recv() => {
|
||||
let mut opus = vec![0u8; 4096];
|
||||
if let Ok(len) = codec.encode(&pcm_data, &mut opus) {
|
||||
let _ = audio.send(&AudioPacket { data: opus[..len].to_vec() }, server_addr).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
println!("Recording stopped.");
|
||||
});
|
||||
}
|
||||
ControlPacket::StartPlayback { config } => {
|
||||
let mut stop_rx = stop_tx.subscribe();
|
||||
tokio::spawn(async move {
|
||||
let (pcm_tx, mut pcm_rx) = tokio::sync::mpsc::channel::<Vec<i16>>(20);
|
||||
|
||||
// 播放线程:使用 std::thread 处理阻塞的 ALSA 调用
|
||||
let config_clone = config.clone();
|
||||
std::thread::spawn(move || {
|
||||
let player = match AudioPlayer::new(&config_clone) {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
eprintln!("Failed to start player: {}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
while let Some(pcm_data) = pcm_rx.blocking_recv() {
|
||||
let _ = player.write(&pcm_data);
|
||||
}
|
||||
});
|
||||
|
||||
let mut codec = match OpusCodec::new(&config) {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
eprintln!("Failed to init opus codec: {}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let mut buf = vec![0u8; 4096];
|
||||
println!("Playback started with jitter buffer...");
|
||||
loop {
|
||||
tokio::select! {
|
||||
_ = stop_rx.recv() => break,
|
||||
res = audio.recv(&mut buf) => {
|
||||
match res {
|
||||
Ok((packet, _)) => {
|
||||
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;
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Audio recv error: {}", e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
println!("Playback stopped.");
|
||||
});
|
||||
}
|
||||
ControlPacket::StopRecording | ControlPacket::StopPlayback => {
|
||||
let _ = stop_tx.send(());
|
||||
}
|
||||
ControlPacket::Ping => {
|
||||
if let Some(conn) = self.conn.lock().await.as_ref() {
|
||||
conn.send(&ControlPacket::Pong).await?;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
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()
|
||||
},
|
||||
}
|
||||
}
|
||||
_ => RpcResult {
|
||||
stderr: "Unsupported method".to_string(),
|
||||
code: -1,
|
||||
..Default::default()
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
use crate::net::network::ControlWriter;
|
||||
use crate::net::protocol::DeviceInfo;
|
||||
use crate::net::rpc::RpcManager;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct ClientSession {
|
||||
pub info: DeviceInfo,
|
||||
pub writer: Arc<tokio::sync::Mutex<ControlWriter>>,
|
||||
pub rpc: Arc<RpcManager>,
|
||||
}
|
||||
|
||||
impl ClientSession {
|
||||
pub fn new(info: DeviceInfo, writer: ControlWriter) -> Self {
|
||||
Self {
|
||||
info,
|
||||
writer: Arc::new(tokio::sync::Mutex::new(writer)),
|
||||
rpc: Arc::new(RpcManager::new()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,252 +0,0 @@
|
||||
use crate::app::server::handlers;
|
||||
use crate::app::server::session::ServerSession;
|
||||
use crate::audio::codec::OpusCodec;
|
||||
use crate::audio::config::AudioConfig;
|
||||
use crate::audio::wav::{WavReader, WavWriter};
|
||||
use crate::net::discovery::Discovery;
|
||||
use crate::net::network::{AudioSocket, ControlConnection, ServerNetwork};
|
||||
use crate::net::protocol::{AudioPacket, ControlPacket, RpcResult};
|
||||
use anyhow::{Context, Result};
|
||||
use parking_lot::Mutex;
|
||||
use std::collections::HashMap;
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct Server {
|
||||
sessions: Arc<Mutex<HashMap<SocketAddr, Arc<ServerSession>>>>,
|
||||
audio_socket: Arc<AudioSocket>,
|
||||
}
|
||||
|
||||
impl Server {
|
||||
pub async fn new() -> Result<Self> {
|
||||
Ok(Self {
|
||||
sessions: Arc::new(Mutex::new(HashMap::new())),
|
||||
audio_socket: Arc::new(AudioSocket::bind().await?),
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn run(self: Arc<Self>, port: u16) -> Result<()> {
|
||||
let network = ServerNetwork::setup(port).await?;
|
||||
println!("服务端启动在: {}", network.local_addr()?);
|
||||
|
||||
// 启动广播
|
||||
Discovery::start_broadcast(port).await?;
|
||||
|
||||
loop {
|
||||
let (control, addr) = network.accept().await?;
|
||||
let server = self.clone();
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = server.handle_connection(control, addr).await {
|
||||
eprintln!("连接 {} 出错: {}", addr, e);
|
||||
}
|
||||
server.sessions.lock().remove(&addr);
|
||||
println!("客户端 {} 断开连接", addr);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_connection(
|
||||
&self,
|
||||
mut control: ControlConnection,
|
||||
addr: SocketAddr,
|
||||
) -> Result<()> {
|
||||
println!("新客户端连接: {}", addr);
|
||||
|
||||
// 握手认证
|
||||
let info = match control.recv_packet().await? {
|
||||
ControlPacket::ClientIdentify { info } => info,
|
||||
p => return Err(anyhow::anyhow!("预期的握手包,收到: {:?}", p)),
|
||||
};
|
||||
|
||||
println!(
|
||||
"客户端识别: {} ({}) v{}",
|
||||
info.model, info.mac, info.version
|
||||
);
|
||||
control.send_packet(&ControlPacket::IdentifyOk).await?;
|
||||
|
||||
let (mut reader, writer) = control.split();
|
||||
let session = Arc::new(ServerSession::new(info, writer, addr));
|
||||
|
||||
self.sessions.lock().insert(addr, session.clone());
|
||||
|
||||
// 处理控制消息循环
|
||||
loop {
|
||||
let packet = reader.recv_packet().await?;
|
||||
let session = session.clone();
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = handlers::handle_packet(session, packet).await {
|
||||
eprintln!("处理来自 {} 的包时出错: {}", addr, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 暴露给外部调用的方法
|
||||
pub async fn call_shell(&self, addr: SocketAddr, cmd: &str) -> Result<RpcResult> {
|
||||
let session = self
|
||||
.sessions
|
||||
.lock()
|
||||
.get(&addr)
|
||||
.cloned()
|
||||
.context("未找到 Session")?;
|
||||
let (id, rx) = session.rpc.alloc_id();
|
||||
session
|
||||
.writer
|
||||
.lock()
|
||||
.await
|
||||
.send_packet(&ControlPacket::RpcRequest {
|
||||
id,
|
||||
method: "shell".to_string(),
|
||||
args: vec![cmd.to_string()],
|
||||
})
|
||||
.await?;
|
||||
Ok(rx.await?)
|
||||
}
|
||||
|
||||
pub async fn start_recording(&self, addr: SocketAddr, config: AudioConfig) -> Result<()> {
|
||||
let session = self
|
||||
.sessions
|
||||
.lock()
|
||||
.get(&addr)
|
||||
.cloned()
|
||||
.context("未找到 Session")?;
|
||||
|
||||
// 发送开始录音指令
|
||||
session
|
||||
.writer
|
||||
.lock()
|
||||
.await
|
||||
.send_packet(&ControlPacket::StartRecording {
|
||||
config: config.clone(),
|
||||
})
|
||||
.await?;
|
||||
|
||||
let audio_socket = self.audio_socket.clone();
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = save_audio_to_wav(audio_socket, config, "temp/recorded.wav").await {
|
||||
eprintln!("保存录音失败: {}", e);
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn stop_recording(&self, addr: SocketAddr) -> Result<()> {
|
||||
let session = self
|
||||
.sessions
|
||||
.lock()
|
||||
.get(&addr)
|
||||
.cloned()
|
||||
.context("未找到 Session")?;
|
||||
session
|
||||
.writer
|
||||
.lock()
|
||||
.await
|
||||
.send_packet(&ControlPacket::StopRecording)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn start_playback(&self, addr: SocketAddr, config: AudioConfig) -> Result<()> {
|
||||
let session = self
|
||||
.sessions
|
||||
.lock()
|
||||
.get(&addr)
|
||||
.cloned()
|
||||
.context("未找到 Session")?;
|
||||
|
||||
session
|
||||
.writer
|
||||
.lock()
|
||||
.await
|
||||
.send_packet(&ControlPacket::StartPlayback {
|
||||
config: config.clone(),
|
||||
})
|
||||
.await?;
|
||||
|
||||
let audio_socket = self.audio_socket.clone();
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = stream_wav_to_client(audio_socket, addr, config, "temp/test.wav").await
|
||||
{
|
||||
eprintln!("推流失败: {}", e);
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn stop_playback(&self, addr: SocketAddr) -> Result<()> {
|
||||
let session = self
|
||||
.sessions
|
||||
.lock()
|
||||
.get(&addr)
|
||||
.cloned()
|
||||
.context("未找到 Session")?;
|
||||
session
|
||||
.writer
|
||||
.lock()
|
||||
.await
|
||||
.send_packet(&ControlPacket::StopPlayback)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_sessions(&self) -> Vec<SocketAddr> {
|
||||
self.sessions.lock().keys().cloned().collect()
|
||||
}
|
||||
}
|
||||
|
||||
async fn save_audio_to_wav(
|
||||
socket: Arc<AudioSocket>,
|
||||
config: AudioConfig,
|
||||
path: &str,
|
||||
) -> Result<()> {
|
||||
std::fs::create_dir_all("temp")?;
|
||||
let mut writer = WavWriter::create(path, config.sample_rate, config.channels)?;
|
||||
let mut codec = OpusCodec::new(&config)?;
|
||||
let mut pcm_buf = vec![0i16; config.frame_size];
|
||||
let mut udp_buf = vec![0u8; 4096];
|
||||
|
||||
println!("正在录制到 {}...", path);
|
||||
|
||||
for _ in 0..100 {
|
||||
let (packet, _) = socket.recv_packet(&mut udp_buf).await?;
|
||||
let pcm_len = codec.decode(&packet.data, &mut pcm_buf)?;
|
||||
writer.write_samples(&pcm_buf[..pcm_len])?;
|
||||
}
|
||||
|
||||
writer.finalize()?;
|
||||
println!("录制完成: {}", path);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn stream_wav_to_client(
|
||||
socket: Arc<AudioSocket>,
|
||||
target: SocketAddr,
|
||||
config: AudioConfig,
|
||||
path: &str,
|
||||
) -> Result<()> {
|
||||
let mut reader = WavReader::open(path)?;
|
||||
let mut codec = OpusCodec::new(&config)?;
|
||||
let mut pcm_buf = vec![0i16; config.frame_size];
|
||||
let mut opus_buf = vec![0u8; 4096];
|
||||
|
||||
println!("正在从 {} 推流...", path);
|
||||
|
||||
loop {
|
||||
let n = reader.read_samples(&mut pcm_buf)?;
|
||||
if n == 0 {
|
||||
break;
|
||||
}
|
||||
let opus_len = codec.encode(&pcm_buf[..n], &mut opus_buf)?;
|
||||
let packet = AudioPacket {
|
||||
data: opus_buf[..opus_len].to_vec(),
|
||||
};
|
||||
socket.send_packet(&packet, target).await?;
|
||||
|
||||
tokio::time::sleep(std::time::Duration::from_millis(20)).await;
|
||||
}
|
||||
|
||||
println!("推流结束");
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
use crate::app::server::core::Server;
|
||||
use crate::audio::config::AudioConfig;
|
||||
use anyhow::Result;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub async fn run_server() -> Result<()> {
|
||||
let server = Arc::new(Server::new().await?);
|
||||
let s = server.clone();
|
||||
|
||||
// 运行服务器
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = s.run(53531).await {
|
||||
eprintln!("Server error: {}", e);
|
||||
}
|
||||
});
|
||||
|
||||
// 等待一个客户端连接并进行演示
|
||||
println!("等待客户端连接以进行功能演示...");
|
||||
loop {
|
||||
let sessions = server.get_sessions();
|
||||
if !sessions.is_empty() {
|
||||
let addr = sessions[0];
|
||||
println!("开始对 {} 进行功能测试...", addr);
|
||||
|
||||
// 1. 测试 RPC
|
||||
println!("测试 RPC: echo hello");
|
||||
let res = server.call_shell(addr, "echo hello").await?;
|
||||
println!("RPC 结果: {:?}", res);
|
||||
|
||||
// 2. 测试录音
|
||||
println!("测试录制 2 秒音频...");
|
||||
server.start_recording(addr, AudioConfig::voice()).await?;
|
||||
tokio::time::sleep(std::time::Duration::from_secs(2)).await;
|
||||
server.stop_recording(addr).await?;
|
||||
println!("录制结束,请检查 temp/recorded.wav");
|
||||
|
||||
// 3. 测试播放 (如果 temp/test.wav 存在)
|
||||
if std::path::Path::new("temp/test.wav").exists() {
|
||||
println!("测试播放 temp/test.wav...");
|
||||
server.start_playback(addr, AudioConfig::voice()).await?;
|
||||
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
|
||||
server.stop_playback(addr).await?;
|
||||
println!("播放结束");
|
||||
} else {
|
||||
println!("跳过播放测试 (temp/test.wav 不存在)");
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||
}
|
||||
|
||||
// 保持运行
|
||||
loop {
|
||||
tokio::time::sleep(std::time::Duration::from_secs(3600)).await;
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
use crate::app::server::session::ServerSession;
|
||||
use crate::net::protocol::{ControlPacket, RpcResult};
|
||||
use anyhow::Result;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub async fn handle_packet(session: Arc<ServerSession>, packet: ControlPacket) -> Result<()> {
|
||||
match packet {
|
||||
ControlPacket::RpcResponse { id, result } => {
|
||||
session.rpc.fulfill(id, result);
|
||||
}
|
||||
ControlPacket::RpcRequest { id, method, args } => {
|
||||
println!(
|
||||
"收到来自客户端 {} 的 RPC 请求: {} {:?}",
|
||||
session.addr, method, args
|
||||
);
|
||||
let result = handle_server_rpc(&method, args).await;
|
||||
session
|
||||
.writer
|
||||
.lock()
|
||||
.await
|
||||
.send_packet(&ControlPacket::RpcResponse { id, result })
|
||||
.await?;
|
||||
}
|
||||
ControlPacket::Ping => {
|
||||
session
|
||||
.writer
|
||||
.lock()
|
||||
.await
|
||||
.send_packet(&ControlPacket::Pong)
|
||||
.await?;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_server_rpc(method: &str, _args: Vec<String>) -> RpcResult {
|
||||
match method {
|
||||
"status" => RpcResult {
|
||||
stdout: "Server is running normally".to_string(),
|
||||
stderr: "".to_string(),
|
||||
code: 0,
|
||||
},
|
||||
_ => RpcResult {
|
||||
stdout: "".to_string(),
|
||||
stderr: format!("Server does not support method: {}", method),
|
||||
code: -1,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,224 @@
|
||||
pub mod core;
|
||||
pub mod entry;
|
||||
pub mod handlers;
|
||||
pub mod session;
|
||||
use crate::audio::codec::OpusCodec;
|
||||
use crate::audio::config::AudioConfig;
|
||||
use crate::audio::wav::{WavReader, WavWriter};
|
||||
use crate::net::discovery::Discovery;
|
||||
use crate::net::network::{AudioSocket, Connection};
|
||||
use crate::net::protocol::{AudioPacket, ControlPacket, DeviceInfo, RpcResult};
|
||||
use crate::net::rpc::RpcManager;
|
||||
use anyhow::{Context, Result};
|
||||
use std::collections::HashMap;
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
pub struct Session {
|
||||
pub info: DeviceInfo,
|
||||
pub conn: Arc<Connection>,
|
||||
pub rpc: Arc<RpcManager>,
|
||||
pub audio_addr: SocketAddr,
|
||||
}
|
||||
|
||||
pub struct Server {
|
||||
sessions: Arc<Mutex<HashMap<SocketAddr, Arc<Session>>>>,
|
||||
audio: Arc<AudioSocket>,
|
||||
}
|
||||
|
||||
impl Server {
|
||||
pub async fn new() -> Result<Self> {
|
||||
Ok(Self {
|
||||
sessions: Arc::new(Mutex::new(HashMap::new())),
|
||||
audio: Arc::new(AudioSocket::bind().await?),
|
||||
})
|
||||
}
|
||||
|
||||
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);
|
||||
Discovery::broadcast(port, self.audio.port()).await?;
|
||||
|
||||
loop {
|
||||
let (stream, addr) = listener.accept().await?;
|
||||
let server = self.clone();
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = server.handle_connection(stream, addr).await {
|
||||
eprintln!("Session {} error: {}", addr, e);
|
||||
}
|
||||
server.sessions.lock().await.remove(&addr);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_connection(
|
||||
&self,
|
||||
stream: tokio::net::TcpStream,
|
||||
addr: SocketAddr,
|
||||
) -> Result<()> {
|
||||
println!("New TCP connection from {}", addr);
|
||||
let conn = Arc::new(Connection::new(stream)?);
|
||||
|
||||
let (info, client_udp_port) = match conn.recv().await? {
|
||||
ControlPacket::ClientIdentify { info, udp_port } => (info, udp_port),
|
||||
p => {
|
||||
println!("Expected Identify from {}, got {:?}", addr, p);
|
||||
return Err(anyhow::anyhow!("Expected Identify, got {:?}", p));
|
||||
}
|
||||
};
|
||||
|
||||
let audio_addr = SocketAddr::new(addr.ip(), client_udp_port);
|
||||
println!(
|
||||
"Client identified: {} ({}) version {}, audio at {}",
|
||||
info.model, addr, info.version, audio_addr
|
||||
);
|
||||
conn.send(&ControlPacket::IdentifyOk).await?;
|
||||
|
||||
let session = Arc::new(Session {
|
||||
info,
|
||||
conn: conn.clone(),
|
||||
rpc: Arc::new(RpcManager::new()),
|
||||
audio_addr,
|
||||
});
|
||||
|
||||
self.sessions.lock().await.insert(addr, session.clone());
|
||||
|
||||
loop {
|
||||
let packet = conn.recv().await?;
|
||||
let session = session.clone();
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = handle_packet(session, packet).await {
|
||||
eprintln!("Handle packet error: {}", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_clients(&self) -> Vec<SocketAddr> {
|
||||
self.sessions.lock().await.keys().cloned().collect()
|
||||
}
|
||||
|
||||
pub async fn call(
|
||||
&self,
|
||||
addr: SocketAddr,
|
||||
method: &str,
|
||||
args: Vec<String>,
|
||||
) -> Result<RpcResult> {
|
||||
let session = self
|
||||
.sessions
|
||||
.lock()
|
||||
.await
|
||||
.get(&addr)
|
||||
.cloned()
|
||||
.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?)
|
||||
}
|
||||
|
||||
pub async fn start_record(&self, addr: SocketAddr, config: AudioConfig) -> Result<()> {
|
||||
let session = self
|
||||
.sessions
|
||||
.lock()
|
||||
.await
|
||||
.get(&addr)
|
||||
.cloned()
|
||||
.context("Session not found")?;
|
||||
session
|
||||
.conn
|
||||
.send(&ControlPacket::StartRecording {
|
||||
config: config.clone(),
|
||||
})
|
||||
.await?;
|
||||
let audio = self.audio.clone();
|
||||
tokio::spawn(async move {
|
||||
let mut writer =
|
||||
WavWriter::create("temp/recorded.wav", config.sample_rate, config.channels)
|
||||
.unwrap();
|
||||
let mut codec = OpusCodec::new(&config).unwrap();
|
||||
let mut pcm = vec![0i16; config.frame_size];
|
||||
let mut buf = vec![0u8; 4096];
|
||||
for _ in 0..500 {
|
||||
// Record ~10s
|
||||
if let Ok((packet, _)) = audio.recv(&mut buf).await {
|
||||
if let Ok(n) = codec.decode(&packet.data, &mut pcm) {
|
||||
writer.write_samples(&pcm[..n]).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
writer.finalize().unwrap();
|
||||
println!("Recording saved to temp/recorded.wav");
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn start_play(&self, addr: SocketAddr) -> Result<()> {
|
||||
let session = self
|
||||
.sessions
|
||||
.lock()
|
||||
.await
|
||||
.get(&addr)
|
||||
.cloned()
|
||||
.context("Session not found")?;
|
||||
let reader = WavReader::open("temp/test.wav")?;
|
||||
|
||||
let opus_rate = if reader.sample_rate > 24000 {
|
||||
48000
|
||||
} else {
|
||||
16000
|
||||
};
|
||||
|
||||
let config = AudioConfig {
|
||||
sample_rate: opus_rate,
|
||||
channels: reader.channels,
|
||||
frame_size: (opus_rate / 50) as usize, // 20ms
|
||||
..AudioConfig::music_48k()
|
||||
};
|
||||
|
||||
session
|
||||
.conn
|
||||
.send(&ControlPacket::StartPlayback {
|
||||
config: config.clone(),
|
||||
})
|
||||
.await?;
|
||||
let audio = self.audio.clone();
|
||||
let target_addr = session.audio_addr;
|
||||
tokio::spawn(async move {
|
||||
let mut reader = reader;
|
||||
let mut codec = OpusCodec::new(&config).unwrap();
|
||||
let mut pcm = vec![0i16; config.frame_size];
|
||||
let mut opus = vec![0u8; 4096];
|
||||
while let Ok(n) = reader.read_samples(&mut pcm) {
|
||||
if n == 0 {
|
||||
break;
|
||||
}
|
||||
if let Ok(len) = codec.encode(&pcm[..n], &mut opus) {
|
||||
let _ = audio
|
||||
.send(
|
||||
&AudioPacket {
|
||||
data: opus[..len].to_vec(),
|
||||
},
|
||||
target_addr,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
tokio::time::sleep(std::time::Duration::from_millis(20)).await;
|
||||
}
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_packet(session: Arc<Session>, packet: ControlPacket) -> Result<()> {
|
||||
match packet {
|
||||
ControlPacket::RpcResponse { id, result } => session.rpc.resolve(id, result),
|
||||
ControlPacket::Ping => session.conn.send(&ControlPacket::Pong).await?,
|
||||
_ => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
use crate::net::network::ControlWriter;
|
||||
use crate::net::protocol::DeviceInfo;
|
||||
use crate::net::rpc::RpcManager;
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct ServerSession {
|
||||
pub info: DeviceInfo,
|
||||
pub writer: Arc<tokio::sync::Mutex<ControlWriter>>,
|
||||
pub addr: SocketAddr,
|
||||
pub rpc: Arc<RpcManager>,
|
||||
}
|
||||
|
||||
impl ServerSession {
|
||||
pub fn new(info: DeviceInfo, writer: ControlWriter, addr: SocketAddr) -> Self {
|
||||
Self {
|
||||
info,
|
||||
writer: Arc::new(tokio::sync::Mutex::new(writer)),
|
||||
addr,
|
||||
rpc: Arc::new(RpcManager::new()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,62 +9,67 @@ pub struct OpusCodec {
|
||||
|
||||
impl OpusCodec {
|
||||
pub fn new(config: &AudioConfig) -> Result<Self> {
|
||||
// Opus 仅支持这些采样率,强制进行转换以防万一
|
||||
let opus_rate = match config.sample_rate {
|
||||
8000 => 8000,
|
||||
12000 => 12000,
|
||||
16000 => 16000,
|
||||
24000 => 24000,
|
||||
48000 => 48000,
|
||||
_ => {
|
||||
let fallback = if config.sample_rate < 24000 {
|
||||
16000
|
||||
} else {
|
||||
48000
|
||||
};
|
||||
println!(
|
||||
"Warning: Opus does not support {}Hz, falling back to {}Hz",
|
||||
config.sample_rate, fallback
|
||||
);
|
||||
fallback
|
||||
}
|
||||
};
|
||||
|
||||
let channels = match config.channels {
|
||||
1 => Channels::Mono,
|
||||
2 => Channels::Stereo,
|
||||
_ => return Err(anyhow::anyhow!("Invalid channels: {}", config.channels)),
|
||||
_ => return Err(anyhow::anyhow!("Unsupported channels: {}", config.channels)),
|
||||
};
|
||||
|
||||
let mode = match config.audio_scene {
|
||||
AudioScene::Music => Application::Audio,
|
||||
AudioScene::Voice => Application::Voip,
|
||||
};
|
||||
let bitrate = match config.bitrate {
|
||||
-1 => Bitrate::Max,
|
||||
0 => Bitrate::Auto,
|
||||
_ => Bitrate::Bits(config.bitrate),
|
||||
|
||||
let mut encoder =
|
||||
Encoder::new(opus_rate, channels, mode).context("Opus encoder init failed")?;
|
||||
|
||||
let bitrate = if config.bitrate <= 0 {
|
||||
Bitrate::Auto
|
||||
} else {
|
||||
Bitrate::Bits(config.bitrate)
|
||||
};
|
||||
|
||||
let mut encoder = Encoder::new(config.sample_rate, channels, mode)
|
||||
.context("Failed to create Opus encoder")?;
|
||||
|
||||
encoder.set_bitrate(bitrate)?;
|
||||
if config.vbr {
|
||||
encoder.set_vbr(true)?;
|
||||
}
|
||||
encoder.set_vbr(config.vbr)?;
|
||||
if config.fec {
|
||||
encoder.set_inband_fec(true)?; // 内联前向纠错
|
||||
encoder.set_packet_loss_perc(20)?; // 预期丢包率20%
|
||||
encoder.set_inband_fec(true)?;
|
||||
encoder.set_packet_loss_perc(10)?;
|
||||
}
|
||||
|
||||
let decoder =
|
||||
Decoder::new(config.sample_rate, channels).context("Failed to create Opus decoder")?;
|
||||
Decoder::new(config.sample_rate, channels).context("Opus decoder init failed")?;
|
||||
|
||||
Ok(Self { encoder, decoder })
|
||||
}
|
||||
|
||||
pub fn encode(&mut self, pcm: &[i16], out: &mut [u8]) -> Result<usize> {
|
||||
self.encoder
|
||||
.encode(pcm, out)
|
||||
.context("Opus encoding failed")
|
||||
self.encoder.encode(pcm, out).context("Opus encode failed")
|
||||
}
|
||||
|
||||
pub fn decode(&mut self, opus: &[u8], out: &mut [i16]) -> Result<usize> {
|
||||
self.decoder
|
||||
.decode(opus, out, false)
|
||||
.context("Opus decoding failed")
|
||||
}
|
||||
|
||||
/// 前向纠错(FEC)
|
||||
pub fn decode_fec(&mut self, opus: &[u8], out: &mut [i16]) -> Result<usize> {
|
||||
self.decoder
|
||||
.decode(opus, out, true)
|
||||
.context("Opus FEC decoding failed")
|
||||
}
|
||||
|
||||
/// 丢包补偿(PLC)
|
||||
pub fn decode_loss(&mut self, out: &mut [i16]) -> Result<usize> {
|
||||
self.decoder
|
||||
.decode(&[], out, false)
|
||||
.context("Opus PLC (decode_loss) failed")
|
||||
.context("Opus decode failed")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,56 +8,49 @@ pub enum AudioScene {
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct AudioConfig {
|
||||
// ALSA 设备参数,用于录音和播放
|
||||
pub capture_device: String,
|
||||
pub playback_device: String,
|
||||
pub sample_rate: u32,
|
||||
pub channels: u16,
|
||||
pub frame_size: usize, // 帧大小,单位为采样点
|
||||
|
||||
// Opus 编解码参数,用于音频传输
|
||||
pub frame_size: usize,
|
||||
pub audio_scene: AudioScene,
|
||||
pub bitrate: i32,
|
||||
pub vbr: bool, // 是否启用 VBR(动态比特率)
|
||||
pub fec: bool, // 是否启用 FEC(内联前向纠错)
|
||||
pub vbr: bool,
|
||||
pub fec: bool,
|
||||
}
|
||||
|
||||
impl AudioConfig {
|
||||
pub fn music() -> Self {
|
||||
pub fn voice_16k() -> Self {
|
||||
Self {
|
||||
audio_scene: AudioScene::Music,
|
||||
sample_rate: 48_000, // 48kHz
|
||||
channels: 2,
|
||||
frame_size: 960, // 20ms at 48kHz
|
||||
bitrate: 320_000, // 320 kbps
|
||||
..Default::default()
|
||||
capture_device: "plug:Capture".to_string(),
|
||||
playback_device: "plug:default".to_string(),
|
||||
sample_rate: 16_000,
|
||||
channels: 1,
|
||||
frame_size: 320, // 20ms
|
||||
audio_scene: AudioScene::Voice,
|
||||
bitrate: 32_000,
|
||||
vbr: true,
|
||||
fec: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn voice() -> Self {
|
||||
pub fn music_48k() -> Self {
|
||||
Self {
|
||||
audio_scene: AudioScene::Voice,
|
||||
sample_rate: 16_000, // 16kHz
|
||||
channels: 1,
|
||||
frame_size: 320, // 20ms at 16kHz
|
||||
bitrate: 32_000, // 32 kbps
|
||||
..Default::default()
|
||||
capture_device: "plug:Capture".to_string(),
|
||||
playback_device: "plug:default".to_string(),
|
||||
sample_rate: 48_000,
|
||||
channels: 2,
|
||||
frame_size: 960, // 20ms
|
||||
audio_scene: AudioScene::Music,
|
||||
bitrate: 128_000,
|
||||
vbr: true,
|
||||
fec: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for AudioConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
audio_scene: AudioScene::Voice,
|
||||
capture_device: "plug:Capture".to_string(),
|
||||
playback_device: "default".to_string(),
|
||||
sample_rate: 16_000,
|
||||
channels: 1,
|
||||
frame_size: 320, // 20ms at 16kHz
|
||||
bitrate: 32_000,
|
||||
vbr: false,
|
||||
fec: false,
|
||||
}
|
||||
Self::voice_16k()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,61 +11,38 @@ pub struct AudioPlayer {
|
||||
|
||||
impl AudioPlayer {
|
||||
pub fn new(config: &AudioConfig) -> Result<Self> {
|
||||
let pcm = PCM::new(&config.playback_device, Direction::Playback, false)
|
||||
.context("Failed to open playback PCM device")?;
|
||||
let pcm = PCM::new(&config.playback_device, Direction::Playback, false)?;
|
||||
{
|
||||
let hwp = HwParams::any(&pcm)?;
|
||||
hwp.set_access(Access::RWInterleaved)?;
|
||||
hwp.set_format(Format::s16())?;
|
||||
hwp.set_rate_near(config.sample_rate, alsa::ValueOr::Nearest)?;
|
||||
hwp.set_channels_near(config.channels as u32)?;
|
||||
|
||||
setup_pcm(&pcm, config.sample_rate, config.channels)?;
|
||||
// 100ms buffer to prevent underruns
|
||||
let buffer_size = (config.sample_rate as f64 * 0.1) as u32;
|
||||
hwp.set_buffer_size_near(buffer_size as alsa::pcm::Frames)?;
|
||||
|
||||
pcm.hw_params(&hwp)?;
|
||||
}
|
||||
pcm.prepare()?;
|
||||
Ok(Self { pcm })
|
||||
}
|
||||
|
||||
pub fn write(&self, buffer: &[i16]) -> Result<usize> {
|
||||
let res = self.pcm.io_i16()?.writei(buffer);
|
||||
|
||||
pub fn write(&self, buf: &[i16]) -> Result<usize> {
|
||||
let res = self.pcm.io_i16()?.writei(buf);
|
||||
match res {
|
||||
Ok(written) => Ok(written),
|
||||
Err(e) => {
|
||||
// Buffer Underrun,即播放缓冲区的数据被耗尽,导致音频流中断
|
||||
if e.errno() == 32 {
|
||||
// 恢复音频流状态
|
||||
self.pcm.prepare()?;
|
||||
// 重新获取 IO 对象并尝试写入数据
|
||||
self.pcm
|
||||
.io_i16()?
|
||||
.writei(buffer)
|
||||
.context("Failed to write to playback device after recovery")
|
||||
} else {
|
||||
Err(e).context("Failed to write to playback device")
|
||||
}
|
||||
Ok(n) => Ok(n),
|
||||
Err(e) if e.errno() == 32 => {
|
||||
println!("ALSA write underrun, preparing PCM");
|
||||
// Broken pipe (underrun)
|
||||
self.pcm.prepare()?;
|
||||
self.pcm
|
||||
.io_i16()?
|
||||
.writei(buf)
|
||||
.context("ALSA write retry failed")
|
||||
}
|
||||
Err(e) => Err(e.into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn prepare(&self) -> Result<()> {
|
||||
self.pcm.prepare().context("Failed to prepare PCM")
|
||||
}
|
||||
}
|
||||
|
||||
fn setup_pcm(pcm: &PCM, sample_rate: u32, channels: u16) -> Result<()> {
|
||||
let hwp = HwParams::any(pcm).context("Failed to get HwParams")?;
|
||||
hwp.set_access(Access::RWInterleaved)?;
|
||||
hwp.set_format(Format::s16())?;
|
||||
hwp.set_rate(sample_rate, alsa::ValueOr::Nearest)?;
|
||||
hwp.set_channels(channels as u32)?;
|
||||
|
||||
// 设置较大的缓冲区以减少由于调度抖动和设备重初始化导致的断音/卡顿
|
||||
// 使用 100ms 缓冲区,既能防止 underrun,又不会引入过大延迟
|
||||
let buffer_size = (sample_rate as f64 * 0.1) as u32; // 100ms 缓冲
|
||||
let period_size = buffer_size / 4; // 25ms 周期
|
||||
hwp.set_buffer_size_near(buffer_size as alsa::pcm::Frames)?;
|
||||
hwp.set_period_size_near(period_size as alsa::pcm::Frames, alsa::ValueOr::Nearest)?;
|
||||
|
||||
pcm.hw_params(&hwp).context("Failed to set HwParams")?;
|
||||
|
||||
let swp = pcm.sw_params_current()?;
|
||||
// 设置 start_threshold,当缓冲区有 1 个 period 数据时就开始播放
|
||||
// 这样可以快速启动,同时保持足够的缓冲余量
|
||||
swp.set_start_threshold(period_size as alsa::pcm::Frames)?;
|
||||
pcm.sw_params(&swp)?;
|
||||
pcm.prepare()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -11,31 +11,32 @@ pub struct AudioRecorder {
|
||||
|
||||
impl AudioRecorder {
|
||||
pub fn new(config: &AudioConfig) -> Result<Self> {
|
||||
let pcm = PCM::new(&config.capture_device, Direction::Capture, false)
|
||||
.context("Failed to open capture PCM device")?;
|
||||
|
||||
setup_pcm(&pcm, config.sample_rate, config.channels)?;
|
||||
let pcm = PCM::new(&config.capture_device, Direction::Capture, false)?;
|
||||
{
|
||||
let hwp = HwParams::any(&pcm)?;
|
||||
hwp.set_access(Access::RWInterleaved)?;
|
||||
hwp.set_format(Format::s16())?;
|
||||
hwp.set_rate_near(config.sample_rate, alsa::ValueOr::Nearest)?;
|
||||
hwp.set_channels_near(config.channels as u32)?;
|
||||
pcm.hw_params(&hwp)?;
|
||||
}
|
||||
pcm.prepare()?;
|
||||
Ok(Self { pcm })
|
||||
}
|
||||
|
||||
pub fn read(&self, buffer: &mut [i16]) -> Result<usize> {
|
||||
self.pcm
|
||||
.io_i16()?
|
||||
.readi(buffer)
|
||||
.context("Failed to read from capture device")
|
||||
pub fn read(&self, buf: &mut [i16]) -> Result<usize> {
|
||||
match self.pcm.io_i16()?.readi(buf) {
|
||||
Ok(n) => Ok(n),
|
||||
Err(e) if e.errno() == 32 => {
|
||||
// 32 = Broken pipe (Overrun)
|
||||
println!("ALSA recording overrun, recovering...");
|
||||
self.pcm.prepare()?;
|
||||
self.pcm
|
||||
.io_i16()?
|
||||
.readi(buf)
|
||||
.context("ALSA read retry failed")
|
||||
}
|
||||
Err(e) => Err(e.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn setup_pcm(pcm: &PCM, sample_rate: u32, channels: u16) -> Result<()> {
|
||||
let hwp = HwParams::any(pcm).context("Failed to get HwParams")?;
|
||||
hwp.set_access(Access::RWInterleaved)?;
|
||||
hwp.set_format(Format::s16())?;
|
||||
hwp.set_rate(sample_rate, alsa::ValueOr::Nearest)?;
|
||||
hwp.set_channels(channels as u32)?;
|
||||
pcm.hw_params(&hwp).context("Failed to set HwParams")?;
|
||||
|
||||
let swp = pcm.sw_params_current()?;
|
||||
pcm.sw_params(&swp)?;
|
||||
pcm.prepare()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
use anyhow::Result;
|
||||
use xiao::app::client::Client;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
#[cfg(feature = "app")]
|
||||
{
|
||||
xiao::app::client::entry::run_client().await?;
|
||||
}
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
let client = Arc::new(Client::new());
|
||||
client.run().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,10 +1,47 @@
|
||||
use anyhow::Result;
|
||||
use std::sync::Arc;
|
||||
use xiao::app::server::Server;
|
||||
use xiao::audio::config::AudioConfig;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
#[cfg(feature = "app")]
|
||||
{
|
||||
xiao::app::server::entry::run_server().await?;
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
let server = Arc::new(Server::new().await?);
|
||||
let s = server.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
s.run(8080).await.unwrap();
|
||||
});
|
||||
|
||||
println!("Server is running. Waiting for a client to connect...");
|
||||
|
||||
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).await?;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
println!("Tests completed. Press Ctrl+C to exit.");
|
||||
tokio::signal::ctrl_c().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
pub mod app;
|
||||
pub mod audio;
|
||||
pub mod net;
|
||||
pub mod utils;
|
||||
|
||||
#[cfg(feature = "app")]
|
||||
pub mod app;
|
||||
|
||||
@@ -5,18 +5,21 @@ use std::time::Duration;
|
||||
use tokio::net::UdpSocket;
|
||||
|
||||
pub const DISCOVERY_PORT: u16 = 53530;
|
||||
const DISCOVERY_MAGIC: &[u8] = b"XIAO_DISCOVERY_V2";
|
||||
|
||||
/// 服务发现模块,用于主从节点的自动发现
|
||||
pub struct Discovery;
|
||||
|
||||
impl Discovery {
|
||||
/// 服务端:启动广播,告知客户端自己的 TCP 端口
|
||||
pub async fn start_broadcast(tcp_port: u16) -> Result<()> {
|
||||
pub async fn broadcast(tcp_port: u16, udp_port: u16) -> Result<()> {
|
||||
let socket = UdpSocket::bind("0.0.0.0:0").await?;
|
||||
socket.set_broadcast(true)?;
|
||||
|
||||
let target: SocketAddr = format!("255.255.255.255:{}", DISCOVERY_PORT).parse()?;
|
||||
let msg = postcard::to_allocvec(&ControlPacket::ServerHello { tcp_port })?;
|
||||
|
||||
let mut msg = DISCOVERY_MAGIC.to_vec();
|
||||
msg.extend(postcard::to_allocvec(&ControlPacket::ServerHello {
|
||||
tcp_port,
|
||||
udp_port,
|
||||
})?);
|
||||
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
@@ -24,21 +27,23 @@ impl Discovery {
|
||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 客户端:监听广播,发现服务端的 IP 和 TCP 端口
|
||||
pub async fn discover_server() -> Result<(IpAddr, u16)> {
|
||||
pub async fn listen() -> Result<(IpAddr, u16, u16)> {
|
||||
let socket = UdpSocket::bind(format!("0.0.0.0:{}", DISCOVERY_PORT)).await?;
|
||||
let mut buf = [0u8; 1024];
|
||||
|
||||
loop {
|
||||
let (len, addr) = socket.recv_from(&mut buf).await?;
|
||||
if let Ok(ControlPacket::ServerHello { tcp_port }) =
|
||||
postcard::from_bytes::<ControlPacket>(&buf[..len])
|
||||
{
|
||||
return Ok((addr.ip(), tcp_port));
|
||||
let data = &buf[..len];
|
||||
|
||||
if data.starts_with(DISCOVERY_MAGIC) {
|
||||
let packet_data = &data[DISCOVERY_MAGIC.len()..];
|
||||
if let Ok(ControlPacket::ServerHello { tcp_port, udp_port }) =
|
||||
postcard::from_bytes(packet_data)
|
||||
{
|
||||
return Ok((addr.ip(), tcp_port, udp_port));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,61 @@
|
||||
use crate::net::protocol::{AudioPacket, ControlPacket};
|
||||
use anyhow::{Context, Result};
|
||||
use anyhow::Result;
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
use tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf};
|
||||
use tokio::net::{TcpListener, TcpStream, UdpSocket};
|
||||
use tokio::net::{TcpStream, UdpSocket};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
/// UDP 音频传输
|
||||
// ... (rest of AudioSocket is same)
|
||||
pub struct NetConfig {
|
||||
pub tcp_port: u16,
|
||||
pub udp_port: u16,
|
||||
}
|
||||
|
||||
/// A unified control connection over TCP
|
||||
pub struct Connection {
|
||||
reader: Mutex<tokio::net::tcp::OwnedReadHalf>,
|
||||
writer: Mutex<tokio::net::tcp::OwnedWriteHalf>,
|
||||
peer_addr: SocketAddr,
|
||||
}
|
||||
|
||||
impl Connection {
|
||||
pub fn new(stream: TcpStream) -> Result<Self> {
|
||||
let peer_addr = stream.peer_addr()?;
|
||||
let (r, w) = stream.into_split();
|
||||
Ok(Self {
|
||||
reader: Mutex::new(r),
|
||||
writer: Mutex::new(w),
|
||||
peer_addr,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn send(&self, packet: &ControlPacket) -> Result<()> {
|
||||
let bytes = postcard::to_allocvec(packet)?;
|
||||
let mut writer = self.writer.lock().await;
|
||||
writer.write_u32(bytes.len() as u32).await?;
|
||||
writer.write_all(&bytes).await?;
|
||||
writer.flush().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn recv(&self) -> Result<ControlPacket> {
|
||||
let mut reader = self.reader.lock().await;
|
||||
let len = reader.read_u32().await? as usize;
|
||||
if len > 1024 * 1024 {
|
||||
return Err(anyhow::anyhow!("Packet too large: {}", len));
|
||||
}
|
||||
let mut buf = vec![0u8; len];
|
||||
reader.read_exact(&mut buf).await?;
|
||||
let packet = postcard::from_bytes(&buf)?;
|
||||
Ok(packet)
|
||||
}
|
||||
|
||||
pub fn peer_addr(&self) -> SocketAddr {
|
||||
self.peer_addr
|
||||
}
|
||||
}
|
||||
|
||||
/// UDP Socket for audio transmission
|
||||
pub struct AudioSocket {
|
||||
socket: Arc<UdpSocket>,
|
||||
}
|
||||
@@ -20,140 +68,19 @@ impl AudioSocket {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn local_port(&self) -> Result<u16> {
|
||||
Ok(self.socket.local_addr()?.port())
|
||||
pub fn port(&self) -> u16 {
|
||||
self.socket.local_addr().unwrap().port()
|
||||
}
|
||||
|
||||
pub async fn send_packet(&self, packet: &AudioPacket, target: SocketAddr) -> Result<()> {
|
||||
pub async fn send(&self, packet: &AudioPacket, target: SocketAddr) -> Result<()> {
|
||||
let bytes = postcard::to_allocvec(packet)?;
|
||||
self.socket.send_to(&bytes, target).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn recv_packet(&self, buf: &mut [u8]) -> Result<(AudioPacket, SocketAddr)> {
|
||||
pub async fn recv(&self, buf: &mut [u8]) -> Result<(AudioPacket, SocketAddr)> {
|
||||
let (len, addr) = self.socket.recv_from(buf).await?;
|
||||
let packet = postcard::from_bytes(&buf[..len])?;
|
||||
Ok((packet, addr))
|
||||
}
|
||||
|
||||
pub fn clone_inner(&self) -> Arc<UdpSocket> {
|
||||
self.socket.clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// TCP 控制连接读取端
|
||||
pub struct ControlReader {
|
||||
reader: OwnedReadHalf,
|
||||
}
|
||||
|
||||
impl ControlReader {
|
||||
pub async fn recv_packet(&mut self) -> Result<ControlPacket> {
|
||||
let len = self.reader.read_u32().await? as usize;
|
||||
if len > 10 * 1024 * 1024 {
|
||||
return Err(anyhow::anyhow!("Packet too large: {}", len));
|
||||
}
|
||||
let mut buf = vec![0u8; len];
|
||||
self.reader.read_exact(&mut buf).await?;
|
||||
let packet = postcard::from_bytes(&buf)?;
|
||||
Ok(packet)
|
||||
}
|
||||
}
|
||||
|
||||
/// TCP 控制连接写入端
|
||||
pub struct ControlWriter {
|
||||
writer: OwnedWriteHalf,
|
||||
}
|
||||
|
||||
impl ControlWriter {
|
||||
pub async fn send_packet(&mut self, packet: &ControlPacket) -> Result<()> {
|
||||
let bytes = postcard::to_allocvec(packet)?;
|
||||
let len = bytes.len() as u32;
|
||||
self.writer.write_u32(len).await?;
|
||||
self.writer.write_all(&bytes).await?;
|
||||
self.writer.flush().await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// TCP 控制连接
|
||||
pub struct ControlConnection {
|
||||
stream: TcpStream,
|
||||
}
|
||||
|
||||
impl ControlConnection {
|
||||
pub fn new(stream: TcpStream) -> Self {
|
||||
Self { stream }
|
||||
}
|
||||
|
||||
pub async fn send_packet(&mut self, packet: &ControlPacket) -> Result<()> {
|
||||
let bytes = postcard::to_allocvec(packet)?;
|
||||
let len = bytes.len() as u32;
|
||||
self.stream.write_u32(len).await?;
|
||||
self.stream.write_all(&bytes).await?;
|
||||
self.stream.flush().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn recv_packet(&mut self) -> Result<ControlPacket> {
|
||||
let len = self.stream.read_u32().await? as usize;
|
||||
if len > 10 * 1024 * 1024 {
|
||||
return Err(anyhow::anyhow!("Packet too large: {}", len));
|
||||
}
|
||||
let mut buf = vec![0u8; len];
|
||||
self.stream.read_exact(&mut buf).await?;
|
||||
let packet = postcard::from_bytes(&buf)?;
|
||||
Ok(packet)
|
||||
}
|
||||
|
||||
pub fn split(self) -> (ControlReader, ControlWriter) {
|
||||
let (r, w) = self.stream.into_split();
|
||||
(ControlReader { reader: r }, ControlWriter { writer: w })
|
||||
}
|
||||
|
||||
pub fn peer_addr(&self) -> Result<SocketAddr> {
|
||||
self.stream.peer_addr().context("Failed to get peer addr")
|
||||
}
|
||||
}
|
||||
|
||||
/// 服务端网络管理器
|
||||
pub struct ServerNetwork {
|
||||
listener: TcpListener,
|
||||
}
|
||||
|
||||
impl ServerNetwork {
|
||||
pub async fn setup(port: u16) -> Result<Self> {
|
||||
let listener = TcpListener::bind(format!("0.0.0.0:{}", port)).await?;
|
||||
Ok(Self { listener })
|
||||
}
|
||||
|
||||
pub async fn accept(&self) -> Result<(ControlConnection, SocketAddr)> {
|
||||
let (stream, addr) = self.listener.accept().await?;
|
||||
Ok((ControlConnection::new(stream), addr))
|
||||
}
|
||||
|
||||
pub fn local_addr(&self) -> Result<SocketAddr> {
|
||||
self.listener
|
||||
.local_addr()
|
||||
.context("Failed to get local addr")
|
||||
}
|
||||
}
|
||||
|
||||
/// 客户端网络管理器
|
||||
pub struct ClientNetwork {
|
||||
control: ControlConnection,
|
||||
}
|
||||
|
||||
impl ClientNetwork {
|
||||
pub async fn connect(server_addr: SocketAddr) -> Result<Self> {
|
||||
let stream = TcpStream::connect(server_addr)
|
||||
.await
|
||||
.context(format!("无法连接到服务端 TCP 地址: {}", server_addr))?;
|
||||
Ok(Self {
|
||||
control: ControlConnection::new(stream),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn into_control(self) -> ControlConnection {
|
||||
self.control
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,19 +8,31 @@ pub struct DeviceInfo {
|
||||
pub version: u32,
|
||||
}
|
||||
|
||||
impl DeviceInfo {
|
||||
pub fn current() -> Self {
|
||||
Self {
|
||||
model: "Open-XiaoAi-V2".to_string(),
|
||||
mac: "00:00:00:00:00:00".to_string(), // TODO: Get actual MAC
|
||||
version: 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub enum ControlPacket {
|
||||
// 服务发现
|
||||
// Discovery
|
||||
ServerHello {
|
||||
tcp_port: u16,
|
||||
udp_port: u16,
|
||||
},
|
||||
// 握手与认证
|
||||
// Handshake
|
||||
ClientIdentify {
|
||||
info: DeviceInfo,
|
||||
udp_port: u16,
|
||||
},
|
||||
IdentifyOk,
|
||||
|
||||
// 音频控制
|
||||
// Audio Control
|
||||
StartRecording {
|
||||
config: AudioConfig,
|
||||
},
|
||||
@@ -41,12 +53,12 @@ pub enum ControlPacket {
|
||||
result: RpcResult,
|
||||
},
|
||||
|
||||
// 心跳
|
||||
// Heartbeat
|
||||
Ping,
|
||||
Pong,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
|
||||
pub struct RpcResult {
|
||||
pub stdout: String,
|
||||
pub stderr: String,
|
||||
@@ -55,5 +67,5 @@ pub struct RpcResult {
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct AudioPacket {
|
||||
pub data: Vec<u8>, // Opus 编码数据
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
|
||||
@@ -17,14 +17,14 @@ impl RpcManager {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn alloc_id(&self) -> (u32, oneshot::Receiver<RpcResult>) {
|
||||
pub fn register(&self) -> (u32, oneshot::Receiver<RpcResult>) {
|
||||
let id = self.next_id.fetch_add(1, Ordering::SeqCst);
|
||||
let (tx, rx) = oneshot::channel();
|
||||
self.pending.lock().insert(id, tx);
|
||||
(id, rx)
|
||||
}
|
||||
|
||||
pub fn fulfill(&self, id: u32, result: RpcResult) {
|
||||
pub fn resolve(&self, id: u32, result: RpcResult) {
|
||||
if let Some(tx) = self.pending.lock().remove(&id) {
|
||||
let _ = tx.send(result);
|
||||
}
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
#![cfg(target_os = "linux")]
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use std::fs;
|
||||
use std::process::Command;
|
||||
|
||||
const FIFO_PATH: &str = "/tmp/xiao_out.fifo";
|
||||
const REAL_ASOUND_CONF: &str = "/etc/asound.conf";
|
||||
const TEMP_ASOUND_CONF: &str = "/tmp/asound.xiao.conf";
|
||||
|
||||
/// ALSA 音频重定向器,用于拦截系统音频输出到 FIFO 管道
|
||||
pub struct AlsaRedirector;
|
||||
|
||||
impl AlsaRedirector {
|
||||
pub fn new() -> Result<Self> {
|
||||
Self::cleanup(); // 确保环境干净
|
||||
|
||||
let original_conf = fs::read_to_string(REAL_ASOUND_CONF).unwrap_or_default();
|
||||
|
||||
if !original_conf.contains("pcm.original_default") {
|
||||
// 重命名原有的 default 逻辑,插入拦截器
|
||||
let mut new_conf = original_conf.replace("pcm.!default", "pcm.original_default");
|
||||
new_conf.push_str(&format!(
|
||||
"\npcm.!default {{ type plug slave {{ pcm \"xiao_interceptor\" format S16_LE rate 48000 channels 2 }} }}\n\
|
||||
pcm.xiao_interceptor {{ type file slave.pcm \"null\" file \"{}\" format \"raw\" }}\n",
|
||||
FIFO_PATH
|
||||
));
|
||||
|
||||
fs::write(TEMP_ASOUND_CONF, new_conf)?;
|
||||
|
||||
// 挂载覆盖 /etc/asound.conf
|
||||
let status = Command::new("mount")
|
||||
.arg("--bind")
|
||||
.arg(TEMP_ASOUND_CONF)
|
||||
.arg(REAL_ASOUND_CONF)
|
||||
.status()
|
||||
.context("执行 mount 命令失败")?;
|
||||
|
||||
if !status.success() {
|
||||
return Err(anyhow::anyhow!("挂载 asound.conf 失败"));
|
||||
}
|
||||
|
||||
Self::restart_applications();
|
||||
}
|
||||
|
||||
// 创建 FIFO 管道
|
||||
let _ = Command::new("mkfifo").arg(FIFO_PATH).status();
|
||||
let _ = Command::new("chmod").arg("666").arg(FIFO_PATH).status();
|
||||
|
||||
Ok(Self)
|
||||
}
|
||||
|
||||
pub fn cleanup() {
|
||||
let _ = Command::new("sh")
|
||||
.arg("-c")
|
||||
.arg(format!("umount -l {} >/dev/null 2>&1", REAL_ASOUND_CONF))
|
||||
.status();
|
||||
let _ = fs::remove_file(TEMP_ASOUND_CONF);
|
||||
let _ = fs::remove_file(FIFO_PATH);
|
||||
Self::restart_applications();
|
||||
}
|
||||
|
||||
pub fn fifo_path() -> &'static str {
|
||||
FIFO_PATH
|
||||
}
|
||||
|
||||
pub fn restart_applications() {
|
||||
// 重启媒体播放器
|
||||
let _ = Command::new("sh")
|
||||
.arg("-c")
|
||||
.arg("/etc/init.d/mediaplayer restart >/dev/null 2>&1")
|
||||
.status();
|
||||
// 重启蓝牙
|
||||
let _ = Command::new("sh")
|
||||
.arg("-c")
|
||||
.arg("/etc/init.d/bluetooth restart >/dev/null 2>&1")
|
||||
.status();
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for AlsaRedirector {
|
||||
fn drop(&mut self) {
|
||||
Self::cleanup();
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
pub mod alsa;
|
||||
pub mod sync;
|
||||
@@ -1,224 +0,0 @@
|
||||
use std::collections::VecDeque;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
/// 获取当前微秒级时间戳
|
||||
pub fn now_us() -> u128 {
|
||||
SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.expect("时间倒流")
|
||||
.as_micros()
|
||||
}
|
||||
|
||||
/// 时钟同步管理器,用于计算主从节点间的时钟偏移
|
||||
/// 采用改进的 NTP 算法 + Kalman 滤波思想
|
||||
pub struct ClockSync {
|
||||
/// 偏移量样本窗口
|
||||
offsets: VecDeque<OffsetSample>,
|
||||
/// 当前估计的时钟偏移 (server_time - client_time)
|
||||
pub current_offset: i128,
|
||||
/// RTT 样本窗口
|
||||
rtts: VecDeque<i128>,
|
||||
/// 当前估计的最小 RTT
|
||||
min_rtt: i128,
|
||||
/// 窗口大小
|
||||
window_size: usize,
|
||||
/// 时钟漂移率 (ppm: parts per million)
|
||||
/// 正值表示从节点时钟比主节点快
|
||||
drift_rate: f64,
|
||||
/// 上次更新时间
|
||||
last_update_time: u128,
|
||||
/// 漂移率估计窗口
|
||||
drift_samples: VecDeque<DriftSample>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct OffsetSample {
|
||||
offset: i128,
|
||||
rtt: i128,
|
||||
timestamp: u128,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct DriftSample {
|
||||
offset: i128,
|
||||
timestamp: u128,
|
||||
}
|
||||
|
||||
impl ClockSync {
|
||||
pub fn new(window_size: usize) -> Self {
|
||||
Self {
|
||||
offsets: VecDeque::with_capacity(window_size),
|
||||
current_offset: 0,
|
||||
rtts: VecDeque::with_capacity(window_size),
|
||||
min_rtt: i128::MAX,
|
||||
window_size,
|
||||
drift_rate: 0.0,
|
||||
last_update_time: now_us(),
|
||||
drift_samples: VecDeque::with_capacity(60), // 保留 60 秒的样本
|
||||
}
|
||||
}
|
||||
|
||||
/// 更新时钟偏移估计 (NTP 算法)
|
||||
///
|
||||
/// NTP 时间戳标记:
|
||||
/// t1 = client_send_ts : 客户端发送 Ping 的时间
|
||||
/// t2 = server_ts : 服务器接收 Ping 的时间
|
||||
/// t3 = server_ts : 服务器发送 Pong 的时间 (假设处理时间忽略不计)
|
||||
/// t4 = client_recv_ts : 客户端接收 Pong 的时间
|
||||
///
|
||||
/// RTT = (t4 - t1) - (t3 - t2) = (t4 - t1) (因为 t3 = t2)
|
||||
/// Offset = ((t2 - t1) + (t3 - t4)) / 2 = ((t2 - t1) + (t2 - t4)) / 2
|
||||
/// = t2 - (t1 + t4) / 2
|
||||
pub fn update(&mut self, client_send_ts: u128, server_ts: u128, client_recv_ts: u128) {
|
||||
let t1 = client_send_ts as i128;
|
||||
let t2 = server_ts as i128;
|
||||
let t4 = client_recv_ts as i128;
|
||||
|
||||
let rtt = t4 - t1;
|
||||
|
||||
// 过滤异常 RTT (局域网内 > 100ms 视为异常)
|
||||
if rtt < 0 || rtt > 100_000 {
|
||||
return;
|
||||
}
|
||||
|
||||
// 计算时钟偏移: offset = server_time - client_time
|
||||
// offset = t2 - (t1 + t4) / 2
|
||||
let offset = t2 - (t1 + t4) / 2;
|
||||
|
||||
// 更新 RTT 窗口
|
||||
self.rtts.push_back(rtt);
|
||||
if self.rtts.len() > self.window_size {
|
||||
self.rtts.pop_front();
|
||||
}
|
||||
self.min_rtt = *self.rtts.iter().min().unwrap_or(&rtt);
|
||||
|
||||
// 更新偏移量窗口
|
||||
let sample = OffsetSample {
|
||||
offset,
|
||||
rtt,
|
||||
timestamp: client_recv_ts,
|
||||
};
|
||||
self.offsets.push_back(sample);
|
||||
if self.offsets.len() > self.window_size {
|
||||
self.offsets.pop_front();
|
||||
}
|
||||
|
||||
// 偏移量估计: 使用低 RTT 样本的中位数
|
||||
// 原理: RTT 较小的样本受网络抖动影响小,时间测量更准确
|
||||
let mut low_rtt_offsets: Vec<i128> = self
|
||||
.offsets
|
||||
.iter()
|
||||
.filter(|s| s.rtt <= self.min_rtt + 5000) // 5ms 容差
|
||||
.map(|s| s.offset)
|
||||
.collect();
|
||||
|
||||
if !low_rtt_offsets.is_empty() {
|
||||
low_rtt_offsets.sort_unstable();
|
||||
let new_offset = low_rtt_offsets[low_rtt_offsets.len() / 2];
|
||||
|
||||
// 漂移率估计
|
||||
self.estimate_drift(new_offset, client_recv_ts);
|
||||
|
||||
// 平滑更新偏移量 (避免突变)
|
||||
let alpha = 0.3; // 低通滤波系数
|
||||
self.current_offset =
|
||||
(alpha * new_offset as f64 + (1.0 - alpha) * self.current_offset as f64) as i128;
|
||||
}
|
||||
|
||||
self.last_update_time = client_recv_ts;
|
||||
}
|
||||
|
||||
/// 估计时钟漂移率
|
||||
/// 时钟漂移率 = d(offset) / dt
|
||||
fn estimate_drift(&mut self, offset: i128, timestamp: u128) {
|
||||
self.drift_samples.push_back(DriftSample { offset, timestamp });
|
||||
if self.drift_samples.len() > 60 {
|
||||
self.drift_samples.pop_front();
|
||||
}
|
||||
|
||||
// 至少需要 10 秒的数据才能估计漂移
|
||||
if self.drift_samples.len() < 10 {
|
||||
return;
|
||||
}
|
||||
|
||||
// 使用线性回归估计漂移率
|
||||
let first = self.drift_samples.front().unwrap();
|
||||
let last = self.drift_samples.back().unwrap();
|
||||
|
||||
let dt = (last.timestamp - first.timestamp) as f64;
|
||||
let d_offset = (last.offset - first.offset) as f64;
|
||||
|
||||
if dt > 10_000_000.0 {
|
||||
// 超过 10 秒
|
||||
// drift_rate 单位: 微秒/秒 = ppm
|
||||
let new_drift = d_offset / (dt / 1_000_000.0);
|
||||
|
||||
// 平滑更新漂移率
|
||||
let beta = 0.1;
|
||||
self.drift_rate = beta * new_drift + (1.0 - beta) * self.drift_rate;
|
||||
}
|
||||
}
|
||||
|
||||
/// 将本地时间转换为服务器(主节点)时间
|
||||
/// 考虑时钟漂移补偿
|
||||
pub fn to_server_time(&self, client_time: u128) -> u128 {
|
||||
let base_server_time = (client_time as i128 + self.current_offset) as u128;
|
||||
|
||||
// 漂移补偿: 根据距离上次同步的时间,补偿时钟漂移
|
||||
let elapsed_since_update = client_time.saturating_sub(self.last_update_time) as f64;
|
||||
let drift_correction = (self.drift_rate * elapsed_since_update / 1_000_000.0) as i128;
|
||||
|
||||
(base_server_time as i128 + drift_correction) as u128
|
||||
}
|
||||
|
||||
/// 将服务器(主节点)时间转换为本地时间
|
||||
pub fn to_client_time(&self, server_time: u128) -> u128 {
|
||||
// 简化版本,不考虑漂移补偿 (播放时主要用 to_server_time)
|
||||
(server_time as i128 - self.current_offset) as u128
|
||||
}
|
||||
|
||||
/// 获取当前估计的 RTT (微秒)
|
||||
pub fn get_rtt(&self) -> i128 {
|
||||
self.min_rtt
|
||||
}
|
||||
|
||||
/// 获取当前时钟漂移率 (ppm)
|
||||
pub fn get_drift_rate(&self) -> f64 {
|
||||
self.drift_rate
|
||||
}
|
||||
|
||||
/// 获取同步质量评估 (0-100, 越高越好)
|
||||
pub fn get_sync_quality(&self) -> u8 {
|
||||
if self.offsets.is_empty() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 基于 RTT 稳定性和偏移量方差评估
|
||||
let rtt_variance = self.calculate_variance(&self.rtts.iter().copied().collect::<Vec<_>>());
|
||||
let offset_variance = self.calculate_variance(
|
||||
&self.offsets.iter().map(|s| s.offset).collect::<Vec<_>>(),
|
||||
);
|
||||
|
||||
// RTT 越稳定,方差越小,质量越高
|
||||
let rtt_score = ((100_000.0 - rtt_variance.min(100_000.0)) / 100_000.0 * 50.0) as u8;
|
||||
let offset_score = ((50_000.0 - offset_variance.min(50_000.0)) / 50_000.0 * 50.0) as u8;
|
||||
|
||||
rtt_score + offset_score
|
||||
}
|
||||
|
||||
fn calculate_variance(&self, samples: &[i128]) -> f64 {
|
||||
if samples.is_empty() {
|
||||
return 0.0;
|
||||
}
|
||||
let mean = samples.iter().sum::<i128>() as f64 / samples.len() as f64;
|
||||
let variance = samples
|
||||
.iter()
|
||||
.map(|&x| {
|
||||
let diff = x as f64 - mean;
|
||||
diff * diff
|
||||
})
|
||||
.sum::<f64>()
|
||||
/ samples.len() as f64;
|
||||
variance.sqrt()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user