feat: 自动重启音频应用使配置生效/恢复

This commit is contained in:
Del Wang
2025-12-31 22:53:33 +08:00
committed by Del
parent cdb4fcff85
commit 2ff214c873
3 changed files with 54 additions and 67 deletions
+14 -6
View File
@@ -38,13 +38,14 @@ impl AlsaRedirector {
if !status.success() { if !status.success() {
return Err(anyhow::anyhow!("挂载 asound.conf 失败")); return Err(anyhow::anyhow!("挂载 asound.conf 失败"));
} }
Self::restart_applications();
} }
// 创建 FIFO 管道 // 创建 FIFO 管道
let _ = Command::new("mkfifo").arg(FIFO_PATH).status(); let _ = Command::new("mkfifo").arg(FIFO_PATH).status();
let _ = Command::new("chmod").arg("666").arg(FIFO_PATH).status(); let _ = Command::new("chmod").arg("666").arg(FIFO_PATH).status();
// println!("ALSA 输出已重定向至 {}", FIFO_PATH);
Ok(Self) Ok(Self)
} }
@@ -55,16 +56,23 @@ impl AlsaRedirector {
.status(); .status();
let _ = fs::remove_file(TEMP_ASOUND_CONF); let _ = fs::remove_file(TEMP_ASOUND_CONF);
let _ = fs::remove_file(FIFO_PATH); let _ = fs::remove_file(FIFO_PATH);
// println!("ALSA 配置已恢复。"); Self::restart_applications();
} }
pub fn fifo_path() -> &'static str { pub fn fifo_path() -> &'static str {
FIFO_PATH FIFO_PATH
} }
}
impl Drop for AlsaRedirector { pub fn restart_applications() {
fn drop(&mut self) { // 重启媒体播放器
Self::cleanup(); 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();
} }
} }
+40 -59
View File
@@ -28,6 +28,7 @@ pub async fn run_master(master_role: ChannelRole) -> Result<()> {
println!("--- 主节点模式 ({}) ---", master_role.to_string()); println!("--- 主节点模式 ({}) ---", master_role.to_string());
// 0. 设置 ALSA 重定向 // 0. 设置 ALSA 重定向
println!("🔥 启动中,请稍等...");
let _alsa_guard = AlsaRedirector::new()?; let _alsa_guard = AlsaRedirector::new()?;
// 1. 设置网络 (UDP + TCP) // 1. 设置网络 (UDP + TCP)
@@ -78,8 +79,11 @@ pub async fn run_master(master_role: ChannelRole) -> Result<()> {
vbr: true, vbr: true,
..AudioConfig::music() ..AudioConfig::music()
}; };
let mut current_player_channels = 0; let player = AudioPlayer::new(&AudioConfig {
let mut player: Option<AudioPlayer> = None; channels: 2,
playback_device: "plug:original_default".into(),
..config.clone()
})?;
let mut raw_buf = vec![0u8; config.frame_size * 2 * 2]; let mut raw_buf = vec![0u8; config.frame_size * 2 * 2];
let mut pcm_out = vec![0i16; config.frame_size * 2]; let mut pcm_out = vec![0i16; config.frame_size * 2];
@@ -105,8 +109,10 @@ pub async fn run_master(master_role: ChannelRole) -> Result<()> {
// 打开 FIFO // 打开 FIFO
let mut fifo = match tokio::fs::File::open(AlsaRedirector::fifo_path()).await { let mut fifo = match tokio::fs::File::open(AlsaRedirector::fifo_path()).await {
Ok(f) => f, Ok(f) => f,
Err(e) => { Err(_) => {
eprintln!("❌ 无法打开 FIFO: {:?}, 重试...", e); if shutdown_flag_clone.load(Ordering::Relaxed) {
break;
}
tokio::time::sleep(Duration::from_secs(1)).await; tokio::time::sleep(Duration::from_secs(1)).await;
continue; continue;
} }
@@ -131,26 +137,6 @@ pub async fn run_master(master_role: ChannelRole) -> Result<()> {
if s.is_empty() { None } else { Some(s.clone()) } if s.is_empty() { None } else { Some(s.clone()) }
}; };
// 检查是否需要切换播放器模式
let target_channels = if active_slaves.is_none() { 2 } else { 1 };
if player.is_none() || current_player_channels != target_channels {
println!(
"🔄 切换播放模式: {}",
if target_channels == 2 {
"本地立体声"
} else {
"主从同步 (单声道)"
}
);
let playback_config = AudioConfig {
channels: target_channels,
playback_device: "plug:original_default".into(),
..config.clone()
};
player = Some(AudioPlayer::new(&playback_config)?);
current_player_channels = target_channels;
}
let now = now_us(); let now = now_us();
if stream_start_ts == 0 { if stream_start_ts == 0 {
stream_start_ts = now; stream_start_ts = now;
@@ -162,12 +148,14 @@ pub async fn run_master(master_role: ChannelRole) -> Result<()> {
+ ((seq - stream_start_seq) as u128 * frame_duration_us) + ((seq - stream_start_seq) as u128 * frame_duration_us)
+ delay_us; + delay_us;
// 提取 PCM 数据
for i in 0..config.frame_size {
left_pcm[i] = i16::from_le_bytes([raw_buf[i * 4], raw_buf[i * 4 + 1]]);
right_pcm[i] = i16::from_le_bytes([raw_buf[i * 4 + 2], raw_buf[i * 4 + 3]]);
}
if let Some(slaves_list) = active_slaves { if let Some(slaves_list) = active_slaves {
// 情况 1: 有从节点,主从同步 // 情况 1: 有从节点,进行网络传输,并本地构造静音声道回放
for i in 0..config.frame_size {
left_pcm[i] = i16::from_le_bytes([raw_buf[i * 4], raw_buf[i * 4 + 1]]);
right_pcm[i] = i16::from_le_bytes([raw_buf[i * 4 + 2], raw_buf[i * 4 + 3]]);
}
// 1. 检查各声道是否有从节点需要 // 1. 检查各声道是否有从节点需要
let needs_left = slaves_list.iter().any(|s| s.role == ChannelRole::Left); let needs_left = slaves_list.iter().any(|s| s.role == ChannelRole::Left);
@@ -208,12 +196,21 @@ pub async fn run_master(master_role: ChannelRole) -> Result<()> {
} }
} }
// 4. 本地回放 // 4. 将非本节点的声道置为静音
let master_pcm = match master_role { for i in 0..config.frame_size {
ChannelRole::Left => &left_pcm, match master_role {
ChannelRole::Right => &right_pcm, ChannelRole::Left => {
}; pcm_out[i * 2] = left_pcm[i];
pcm_out[i * 2 + 1] = 0;
}
ChannelRole::Right => {
pcm_out[i * 2] = 0;
pcm_out[i * 2 + 1] = right_pcm[i];
}
}
}
// 5. 等待播放
let now = now_us(); let now = now_us();
if now < target_ts { if now < target_ts {
let wait = target_ts - now; let wait = target_ts - now;
@@ -221,28 +218,18 @@ pub async fn run_master(master_role: ChannelRole) -> Result<()> {
tokio::time::sleep(Duration::from_micros(wait as u64)).await; tokio::time::sleep(Duration::from_micros(wait as u64)).await;
} }
} }
if let Some(p) = &player {
if let Err(_) = p.write(master_pcm) {
// 如果写入失败且正在退出,直接跳出循环
if shutdown_flag_clone.load(Ordering::Relaxed) {
break;
}
}
}
} else { } else {
// 情况 2: 没有从节点,本地立体声播放 // 情况 2: 没有从节点,本地立体声播放
for i in 0..config.frame_size { for i in 0..config.frame_size {
pcm_out[i * 2] = i16::from_le_bytes([raw_buf[i * 4], raw_buf[i * 4 + 1]]); pcm_out[i * 2] = left_pcm[i];
pcm_out[i * 2 + 1] = pcm_out[i * 2 + 1] = right_pcm[i];
i16::from_le_bytes([raw_buf[i * 4 + 2], raw_buf[i * 4 + 3]]);
} }
if let Some(p) = &player { }
if let Err(_) = p.write(&pcm_out) {
// 如果写入失败且正在退出,直接跳出循环 // 统一写入播放器 (始终是立体声)
if shutdown_flag_clone.load(Ordering::Relaxed) { if let Err(_) = player.write(&pcm_out) {
break; if shutdown_flag_clone.load(Ordering::Relaxed) {
} break;
}
} }
} }
@@ -251,11 +238,6 @@ pub async fn run_master(master_role: ChannelRole) -> Result<()> {
// 重置流计时 // 重置流计时
stream_start_ts = 0; stream_start_ts = 0;
// 如果是因为退出信号而跳出内层循环,也要跳出外层循环
if shutdown_flag_clone.load(Ordering::Relaxed) {
break;
}
} }
Ok::<(), anyhow::Error>(()) Ok::<(), anyhow::Error>(())
@@ -270,12 +252,11 @@ pub async fn run_master(master_role: ChannelRole) -> Result<()> {
_ = shutdown_signal() => { _ = shutdown_signal() => {
// 设置退出标志,通知音频循环停止 // 设置退出标志,通知音频循环停止
shutdown_flag.store(true, Ordering::Relaxed); shutdown_flag.store(true, Ordering::Relaxed);
// 等待一小段时间让音频循环有机会退出
tokio::time::sleep(Duration::from_millis(100)).await;
}, },
} }
// 显式清理 // 显式清理
println!("👋 正在退出...");
AlsaRedirector::cleanup(); AlsaRedirector::cleanup();
// 强制退出 // 强制退出
-2
View File
@@ -35,8 +35,6 @@ pub enum ControlPacket {
server_ts: u128, server_ts: u128,
seq: u32, seq: u32,
}, },
// 音量同步
Volume(u8), // 音量 0-100
} }
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]