feat: 自动重启音频应用使配置生效/恢复
This commit is contained in:
@@ -38,13 +38,14 @@ impl AlsaRedirector {
|
||||
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();
|
||||
|
||||
// println!("ALSA 输出已重定向至 {}", FIFO_PATH);
|
||||
Ok(Self)
|
||||
}
|
||||
|
||||
@@ -55,16 +56,23 @@ impl AlsaRedirector {
|
||||
.status();
|
||||
let _ = fs::remove_file(TEMP_ASOUND_CONF);
|
||||
let _ = fs::remove_file(FIFO_PATH);
|
||||
// println!("ALSA 配置已恢复。");
|
||||
Self::restart_applications();
|
||||
}
|
||||
|
||||
pub fn fifo_path() -> &'static str {
|
||||
FIFO_PATH
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for AlsaRedirector {
|
||||
fn drop(&mut self) {
|
||||
Self::cleanup();
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ pub async fn run_master(master_role: ChannelRole) -> Result<()> {
|
||||
println!("--- 主节点模式 ({}) ---", master_role.to_string());
|
||||
|
||||
// 0. 设置 ALSA 重定向
|
||||
println!("🔥 启动中,请稍等...");
|
||||
let _alsa_guard = AlsaRedirector::new()?;
|
||||
|
||||
// 1. 设置网络 (UDP + TCP)
|
||||
@@ -78,8 +79,11 @@ pub async fn run_master(master_role: ChannelRole) -> Result<()> {
|
||||
vbr: true,
|
||||
..AudioConfig::music()
|
||||
};
|
||||
let mut current_player_channels = 0;
|
||||
let mut player: Option<AudioPlayer> = None;
|
||||
let player = AudioPlayer::new(&AudioConfig {
|
||||
channels: 2,
|
||||
playback_device: "plug:original_default".into(),
|
||||
..config.clone()
|
||||
})?;
|
||||
|
||||
let mut raw_buf = vec![0u8; config.frame_size * 2 * 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
|
||||
let mut fifo = match tokio::fs::File::open(AlsaRedirector::fifo_path()).await {
|
||||
Ok(f) => f,
|
||||
Err(e) => {
|
||||
eprintln!("❌ 无法打开 FIFO: {:?}, 重试...", e);
|
||||
Err(_) => {
|
||||
if shutdown_flag_clone.load(Ordering::Relaxed) {
|
||||
break;
|
||||
}
|
||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||
continue;
|
||||
}
|
||||
@@ -131,26 +137,6 @@ pub async fn run_master(master_role: ChannelRole) -> Result<()> {
|
||||
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();
|
||||
if stream_start_ts == 0 {
|
||||
stream_start_ts = now;
|
||||
@@ -162,13 +148,15 @@ pub async fn run_master(master_role: ChannelRole) -> Result<()> {
|
||||
+ ((seq - stream_start_seq) as u128 * frame_duration_us)
|
||||
+ delay_us;
|
||||
|
||||
if let Some(slaves_list) = active_slaves {
|
||||
// 情况 1: 有从节点,主从同步
|
||||
// 提取 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 {
|
||||
// 情况 1: 有从节点,进行网络传输,并本地构造静音声道回放
|
||||
|
||||
// 1. 检查各声道是否有从节点需要
|
||||
let needs_left = slaves_list.iter().any(|s| s.role == ChannelRole::Left);
|
||||
let needs_right = slaves_list.iter().any(|s| s.role == ChannelRole::Right);
|
||||
@@ -208,12 +196,21 @@ pub async fn run_master(master_role: ChannelRole) -> Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 本地回放
|
||||
let master_pcm = match master_role {
|
||||
ChannelRole::Left => &left_pcm,
|
||||
ChannelRole::Right => &right_pcm,
|
||||
};
|
||||
// 4. 将非本节点的声道置为静音
|
||||
for i in 0..config.frame_size {
|
||||
match master_role {
|
||||
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();
|
||||
if now < target_ts {
|
||||
let wait = target_ts - now;
|
||||
@@ -221,41 +218,26 @@ pub async fn run_master(master_role: ChannelRole) -> Result<()> {
|
||||
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 {
|
||||
// 情况 2: 没有从节点,本地立体声播放
|
||||
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 + 1] =
|
||||
i16::from_le_bytes([raw_buf[i * 4 + 2], raw_buf[i * 4 + 3]]);
|
||||
pcm_out[i * 2] = left_pcm[i];
|
||||
pcm_out[i * 2 + 1] = right_pcm[i];
|
||||
}
|
||||
if let Some(p) = &player {
|
||||
if let Err(_) = p.write(&pcm_out) {
|
||||
// 如果写入失败且正在退出,直接跳出循环
|
||||
}
|
||||
|
||||
// 统一写入播放器 (始终是立体声)
|
||||
if let Err(_) = player.write(&pcm_out) {
|
||||
if shutdown_flag_clone.load(Ordering::Relaxed) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
seq += 1;
|
||||
}
|
||||
|
||||
// 重置流计时
|
||||
stream_start_ts = 0;
|
||||
|
||||
// 如果是因为退出信号而跳出内层循环,也要跳出外层循环
|
||||
if shutdown_flag_clone.load(Ordering::Relaxed) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok::<(), anyhow::Error>(())
|
||||
@@ -270,12 +252,11 @@ pub async fn run_master(master_role: ChannelRole) -> Result<()> {
|
||||
_ = shutdown_signal() => {
|
||||
// 设置退出标志,通知音频循环停止
|
||||
shutdown_flag.store(true, Ordering::Relaxed);
|
||||
// 等待一小段时间让音频循环有机会退出
|
||||
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||
},
|
||||
}
|
||||
|
||||
// 显式清理
|
||||
println!("👋 正在退出...");
|
||||
AlsaRedirector::cleanup();
|
||||
|
||||
// 强制退出
|
||||
|
||||
@@ -35,8 +35,6 @@ pub enum ControlPacket {
|
||||
server_ts: u128,
|
||||
seq: u32,
|
||||
},
|
||||
// 音量同步
|
||||
Volume(u8), // 音量 0-100
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
|
||||
Reference in New Issue
Block a user