feat: 自动重启音频应用使配置生效/恢复
This commit is contained in:
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|
||||||
// 强制退出
|
// 强制退出
|
||||||
|
|||||||
@@ -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)]
|
||||||
|
|||||||
Reference in New Issue
Block a user