chore: 优化 alsa 配置
This commit is contained in:
+2
-1
@@ -33,9 +33,10 @@ upload_to_xiaoai stereo 192.168.31.235 # right
|
|||||||
# /data/stereo master right
|
# /data/stereo master right
|
||||||
|
|
||||||
|
|
||||||
# ubus call mediaplayer player_play_url '{"url":"/tmp/test.wav","type":1}'
|
# ubus call mediaplayer player_play_url '{"url":"/data/test.wav","type":1}'
|
||||||
|
|
||||||
# /etc/init.d/mediaplayer restart >/dev/null 2>&1
|
# /etc/init.d/mediaplayer restart >/dev/null 2>&1
|
||||||
|
# /etc/init.d/bluetooth restart >/dev/null 2>&1
|
||||||
# /etc/init.d/miio restart >/dev/null 2>&1
|
# /etc/init.d/miio restart >/dev/null 2>&1
|
||||||
# /etc/init.d/mico_aivs_lab restart >/dev/null 2>&1
|
# /etc/init.d/mico_aivs_lab restart >/dev/null 2>&1
|
||||||
# /etc/init.d/miplay restart >/dev/null 2>&1
|
# /etc/init.d/miplay restart >/dev/null 2>&1
|
||||||
@@ -50,9 +50,19 @@ fn setup_pcm(pcm: &PCM, sample_rate: u32, channels: u16) -> Result<()> {
|
|||||||
hwp.set_format(Format::s16())?;
|
hwp.set_format(Format::s16())?;
|
||||||
hwp.set_rate(sample_rate, alsa::ValueOr::Nearest)?;
|
hwp.set_rate(sample_rate, alsa::ValueOr::Nearest)?;
|
||||||
hwp.set_channels(channels as u32)?;
|
hwp.set_channels(channels as u32)?;
|
||||||
|
|
||||||
|
// 设置较大的缓冲区以减少由于调度抖动导致的断音
|
||||||
|
// 48000Hz * 0.2s = 9600 samples
|
||||||
|
let buffer_size = (sample_rate as f64 * 0.2) as u32;
|
||||||
|
let period_size = buffer_size / 4;
|
||||||
|
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")?;
|
pcm.hw_params(&hwp).context("Failed to set HwParams")?;
|
||||||
|
|
||||||
let swp = pcm.sw_params_current()?;
|
let swp = pcm.sw_params_current()?;
|
||||||
|
// 设置 start_threshold 为 buffer_size 的一半,确保缓冲区有足够数据再开始播放
|
||||||
|
swp.set_start_threshold(buffer_size as alsa::pcm::Frames / 2)?;
|
||||||
pcm.sw_params(&swp)?;
|
pcm.sw_params(&swp)?;
|
||||||
pcm.prepare()?;
|
pcm.prepare()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ impl AlsaRedirector {
|
|||||||
// 重命名原有的 default 逻辑,插入拦截器
|
// 重命名原有的 default 逻辑,插入拦截器
|
||||||
let mut new_conf = original_conf.replace("pcm.!default", "pcm.original_default");
|
let mut new_conf = original_conf.replace("pcm.!default", "pcm.original_default");
|
||||||
new_conf.push_str(&format!(
|
new_conf.push_str(&format!(
|
||||||
"\npcm.!default {{ type plug slave.pcm \"stereo_interceptor\" }}\n\
|
"\npcm.!default {{ type plug slave {{ pcm \"stereo_interceptor\" format S16_LE rate 48000 channels 2 }} }}\n\
|
||||||
pcm.stereo_interceptor {{ type file slave.pcm \"null\" file \"{}\" format \"raw\" }}\n",
|
pcm.stereo_interceptor {{ type file slave.pcm \"null\" file \"{}\" format \"raw\" }}\n",
|
||||||
FIFO_PATH
|
FIFO_PATH
|
||||||
));
|
));
|
||||||
|
|
||||||
|
|||||||
@@ -82,6 +82,9 @@ pub async fn run_master(master_role: ChannelRole) -> Result<()> {
|
|||||||
let mut player: Option<AudioPlayer> = None;
|
let mut player: Option<AudioPlayer> = None;
|
||||||
|
|
||||||
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 left_pcm = vec![0i16; config.frame_size];
|
||||||
|
let mut right_pcm = vec![0i16; config.frame_size];
|
||||||
let mut opus_out = vec![0u8; 1500];
|
let mut opus_out = vec![0u8; 1500];
|
||||||
let mut seq = 0u32;
|
let mut seq = 0u32;
|
||||||
|
|
||||||
@@ -120,10 +123,13 @@ pub async fn run_master(master_role: ChannelRole) -> Result<()> {
|
|||||||
break; // FIFO 关闭,重新打开
|
break; // FIFO 关闭,重新打开
|
||||||
}
|
}
|
||||||
|
|
||||||
let active_slaves = slaves.lock().await.clone();
|
let active_slaves = {
|
||||||
|
let s = slaves.lock().await;
|
||||||
|
if s.is_empty() { None } else { Some(s.clone()) }
|
||||||
|
};
|
||||||
|
|
||||||
// 检查是否需要切换播放器模式
|
// 检查是否需要切换播放器模式
|
||||||
let target_channels = if active_slaves.is_empty() { 2 } else { 1 };
|
let target_channels = if active_slaves.is_none() { 2 } else { 1 };
|
||||||
if player.is_none() || current_player_channels != target_channels {
|
if player.is_none() || current_player_channels != target_channels {
|
||||||
println!(
|
println!(
|
||||||
"🔄 切换播放模式: {}",
|
"🔄 切换播放模式: {}",
|
||||||
@@ -148,37 +154,21 @@ pub async fn run_master(master_role: ChannelRole) -> Result<()> {
|
|||||||
stream_start_seq = seq;
|
stream_start_seq = seq;
|
||||||
}
|
}
|
||||||
|
|
||||||
if active_slaves.is_empty() {
|
// 计算该帧应当播放的基准时间(相对于流开始)
|
||||||
// 情况 1: 没有从节点,本地立体声播放
|
let target_ts = stream_start_ts
|
||||||
let mut pcm = Vec::with_capacity(config.frame_size * 2);
|
+ ((seq - stream_start_seq) as u128 * frame_duration_us)
|
||||||
for i in 0..config.frame_size {
|
+ delay_us;
|
||||||
let l = i16::from_le_bytes([raw_buf[i * 4], raw_buf[i * 4 + 1]]);
|
|
||||||
let r = i16::from_le_bytes([raw_buf[i * 4 + 2], raw_buf[i * 4 + 3]]);
|
|
||||||
pcm.push(l);
|
|
||||||
pcm.push(r);
|
|
||||||
}
|
|
||||||
if let Some(p) = &player {
|
|
||||||
p.write(&pcm)?;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 情况 2: 有从节点,主从同步
|
|
||||||
let mut left_pcm = Vec::with_capacity(config.frame_size);
|
|
||||||
let mut right_pcm = Vec::with_capacity(config.frame_size);
|
|
||||||
|
|
||||||
|
if let Some(slaves_list) = active_slaves {
|
||||||
|
// 情况 1: 有从节点,主从同步
|
||||||
for i in 0..config.frame_size {
|
for i in 0..config.frame_size {
|
||||||
let l = i16::from_le_bytes([raw_buf[i * 4], raw_buf[i * 4 + 1]]);
|
left_pcm[i] = i16::from_le_bytes([raw_buf[i * 4], raw_buf[i * 4 + 1]]);
|
||||||
let r = i16::from_le_bytes([raw_buf[i * 4 + 2], raw_buf[i * 4 + 3]]);
|
right_pcm[i] = i16::from_le_bytes([raw_buf[i * 4 + 2], raw_buf[i * 4 + 3]]);
|
||||||
left_pcm.push(l);
|
|
||||||
right_pcm.push(r);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let target_ts = stream_start_ts
|
|
||||||
+ ((seq - stream_start_seq) as u128 * frame_duration_us)
|
|
||||||
+ delay_us;
|
|
||||||
|
|
||||||
// 1. 检查各声道是否有从节点需要
|
// 1. 检查各声道是否有从节点需要
|
||||||
let needs_left = active_slaves.iter().any(|s| s.role == ChannelRole::Left);
|
let needs_left = slaves_list.iter().any(|s| s.role == ChannelRole::Left);
|
||||||
let needs_right = active_slaves.iter().any(|s| s.role == ChannelRole::Right);
|
let needs_right = slaves_list.iter().any(|s| s.role == ChannelRole::Right);
|
||||||
|
|
||||||
// 2. 编码需要的声道
|
// 2. 编码需要的声道
|
||||||
let mut left_bytes = None;
|
let mut left_bytes = None;
|
||||||
@@ -205,7 +195,7 @@ pub async fn run_master(master_role: ChannelRole) -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 3. 发送给对应的从节点
|
// 3. 发送给对应的从节点
|
||||||
for slave in &active_slaves {
|
for slave in &slaves_list {
|
||||||
let bytes = match slave.role {
|
let bytes = match slave.role {
|
||||||
ChannelRole::Left => left_bytes.as_ref(),
|
ChannelRole::Left => left_bytes.as_ref(),
|
||||||
ChannelRole::Right => right_bytes.as_ref(),
|
ChannelRole::Right => right_bytes.as_ref(),
|
||||||
@@ -215,7 +205,7 @@ pub async fn run_master(master_role: ChannelRole) -> Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. 本地回放同步
|
// 4. 本地回放
|
||||||
let master_pcm = match master_role {
|
let master_pcm = match master_role {
|
||||||
ChannelRole::Left => &left_pcm,
|
ChannelRole::Left => &left_pcm,
|
||||||
ChannelRole::Right => &right_pcm,
|
ChannelRole::Right => &right_pcm,
|
||||||
@@ -231,6 +221,16 @@ pub async fn run_master(master_role: ChannelRole) -> Result<()> {
|
|||||||
if let Some(p) = &player {
|
if let Some(p) = &player {
|
||||||
p.write(master_pcm)?;
|
p.write(master_pcm)?;
|
||||||
}
|
}
|
||||||
|
} 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]]);
|
||||||
|
}
|
||||||
|
if let Some(p) = &player {
|
||||||
|
p.write(&pcm_out)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
seq += 1;
|
seq += 1;
|
||||||
|
|||||||
Reference in New Issue
Block a user