feat: 支持多播放源并发混音
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
use anyhow::Result;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use tokio::io::AsyncReadExt;
|
||||
use tokio::net::{UnixListener, UnixStream};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
pub const MIXER_SOCKET_PATH: &str = "/tmp/audio_mixer.sock";
|
||||
|
||||
/// 混音器服务,负责接收来自多个 Injector 的音频流并进行混音
|
||||
pub struct Mixer {
|
||||
clients: Arc<Mutex<Vec<UnixStream>>>,
|
||||
}
|
||||
|
||||
impl Mixer {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
clients: Arc::new(Mutex::new(Vec::new())),
|
||||
}
|
||||
}
|
||||
|
||||
/// 启动 Unix Socket 监听服务
|
||||
pub async fn start(&self) -> Result<()> {
|
||||
if Path::new(MIXER_SOCKET_PATH).exists() {
|
||||
let _ = fs::remove_file(MIXER_SOCKET_PATH);
|
||||
}
|
||||
|
||||
let listener = UnixListener::bind(MIXER_SOCKET_PATH)?;
|
||||
// 允许任何用户写入,确保 ALSA 进程有权连接
|
||||
let _ = std::process::Command::new("chmod")
|
||||
.arg("666")
|
||||
.arg(MIXER_SOCKET_PATH)
|
||||
.status();
|
||||
|
||||
let clients = self.clients.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
match listener.accept().await {
|
||||
Ok((stream, _)) => {
|
||||
let mut c = clients.lock().await;
|
||||
c.push(stream);
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("❌ Mixer accept error: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 获取当前连接的客户端数量
|
||||
pub async fn client_count(&self) -> usize {
|
||||
self.clients.lock().await.len()
|
||||
}
|
||||
|
||||
/// 读取并混合一帧音频数据
|
||||
/// frame_size: 每声道的采样点数
|
||||
/// channels: 声道数
|
||||
pub async fn read_mixed_frame(&self, frame_size: usize, channels: usize) -> Vec<i16> {
|
||||
let total_samples = frame_size * channels;
|
||||
let bytes_per_frame = total_samples * 2;
|
||||
let mut raw_buf = vec![0u8; bytes_per_frame];
|
||||
|
||||
loop {
|
||||
let mut clients = self.clients.lock().await;
|
||||
if clients.is_empty() {
|
||||
drop(clients);
|
||||
// 没有客户端时,稍微等待,避免空转
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(1)).await;
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut mixed_frame = vec![0i32; total_samples];
|
||||
let mut active_clients = Vec::new();
|
||||
let mut read_any = false;
|
||||
|
||||
// 遍历所有客户端,各读取一帧
|
||||
for mut client in clients.drain(..) {
|
||||
// 注意:这里 read_exact 是异步的,会按顺序等待每个客户端的数据
|
||||
// 在主从同步场景下,所有客户端通常来自本地 ALSA,速率是同步的
|
||||
match client.read_exact(&mut raw_buf).await {
|
||||
Ok(_) => {
|
||||
for i in 0..total_samples {
|
||||
let sample =
|
||||
i16::from_le_bytes([raw_buf[i * 2], raw_buf[i * 2 + 1]]) as i32;
|
||||
mixed_frame[i] += sample;
|
||||
}
|
||||
active_clients.push(client);
|
||||
read_any = true;
|
||||
}
|
||||
Err(_) => {
|
||||
// 客户端断开连接
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*clients = active_clients;
|
||||
|
||||
if read_any {
|
||||
// 饱和截断 (Clipping/Saturating) 将 i32 转回 i16
|
||||
return mixed_frame
|
||||
.into_iter()
|
||||
.map(|x| {
|
||||
if x > 32767 {
|
||||
32767
|
||||
} else if x < -32768 {
|
||||
-32768
|
||||
} else {
|
||||
x as i16
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
} else {
|
||||
// 如果所有客户端都断开了,继续等待新连接
|
||||
drop(clients);
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(1)).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user