Fix audio on windows (#3)
Build Mumble Web 2 / linux_build (push) Successful in 1m42s
Build Mumble Web 2 / windows_build (push) Successful in 2m43s

Reviewed-on: #3

Automatically choose supported profile
Play stream once created

Co-authored-by: Sam Sartor <me@samsartor.com>
Co-committed-by: Sam Sartor <me@samsartor.com>
This commit was merged in pull request #3.
This commit is contained in:
2025-12-05 05:34:07 +00:00
committed by Sam Sartor
parent 55412f5778
commit 5df7b0e082
4 changed files with 41 additions and 21 deletions
+1 -1
View File
@@ -130,4 +130,4 @@ desktop = [
"dasp_ring_buffer", "dasp_ring_buffer",
"rfd/xdg-portal", "rfd/xdg-portal",
"rfd/tokio", "rfd/tokio",
] ]
+3 -3
View File
@@ -97,13 +97,13 @@ impl AudioProcessor {
} }
impl AudioProcessor { impl AudioProcessor {
pub fn process(&mut self, audio: &[f32], output: &mut Vec<f32>) { pub fn process(&mut self, audio: &[f32], channels: usize, output: &mut Vec<f32>) {
let mut include_raw = true; let mut include_raw = true;
if self.denoise { if self.denoise {
with_denoising_model(&self.spawn, |df| { with_denoising_model(&self.spawn, |df| {
include_raw = false; include_raw = false;
self.buffer.extend_from_slice(audio); self.buffer.extend(audio.iter().step_by(channels).copied());
output.reserve(audio.len()); output.reserve(audio.len());
let hop = df.hop_size; let hop = df.hop_size;
@@ -130,7 +130,7 @@ impl AudioProcessor {
} }
if include_raw { if include_raw {
output.extend_from_slice(audio); output.extend(audio.iter().step_by(channels).copied());
} }
} }
} }
+36 -16
View File
@@ -1,7 +1,7 @@
use crate::app::Command; use crate::app::Command;
use crate::effects::{AudioProcessor, AudioProcessorSender}; use crate::effects::{AudioProcessor, AudioProcessorSender};
use color_eyre::eyre::{eyre, Context, Error}; use color_eyre::eyre::{eyre, Context, Error};
use cpal::traits::{DeviceTrait, HostTrait}; use cpal::traits::{DeviceTrait, HostTrait, StreamTrait as _};
use dioxus::hooks::UnboundedReceiver; use dioxus::hooks::UnboundedReceiver;
use futures::io::{AsyncRead, AsyncWrite}; use futures::io::{AsyncRead, AsyncWrite};
use mumble_protocol::control::ClientControlCodec; use mumble_protocol::control::ClientControlCodec;
@@ -64,10 +64,34 @@ impl AudioSystem {
self.processors.store(Some(processor)) self.processors.store(Some(processor))
} }
fn choose_config(&self, configs: impl Iterator<Item=cpal::SupportedStreamConfigRange>) -> Result<cpal::StreamConfig, Error> {
let mut supported_configs: Vec<_> = configs
.filter_map(|cfg| cfg.try_with_sample_rate(cpal::SampleRate(SAMPLE_RATE)))
.filter(|cfg| cfg.sample_format() == cpal::SampleFormat::I16)
.map(|cfg| {
cpal::StreamConfig {
buffer_size: cpal::BufferSize::Fixed(match *cfg.buffer_size() {
cpal::SupportedBufferSize::Range { min, max } => 480.clamp(min, max),
cpal::SupportedBufferSize::Unknown => 480,
}),
..cfg.config()
}
})
.collect();
supported_configs.sort_by(|a, b| {
let cpal::BufferSize::Fixed(a_buf) = a.buffer_size else { unreachable!() };
let cpal::BufferSize::Fixed(b_buf) = b.buffer_size else { unreachable!() };
Ord::cmp(&a.channels, &b.channels).then(Ord::cmp(&a_buf, &b_buf))
});
supported_configs.get(0).cloned().ok_or(eyre!("no supported stream configs"))
}
pub fn start_recording( pub fn start_recording(
&mut self, &mut self,
mut each: impl FnMut(Vec<u8>) + Send + 'static, mut each: impl FnMut(Vec<u8>) + Send + 'static,
) -> Result<(), Error> { ) -> Result<(), Error> {
let config = self.choose_config(self.input.supported_input_configs()?)?;
info!("creating recording on {:?} with {:#?}", self.input.name()?, config);
let mut encoder = let mut encoder =
opus::Encoder::new(SAMPLE_RATE, opus::Channels::Mono, opus::Application::Voip)?; opus::Encoder::new(SAMPLE_RATE, opus::Channels::Mono, opus::Application::Voip)?;
let mut current_processor = AudioProcessor::new_plain(); let mut current_processor = AudioProcessor::new_plain();
@@ -78,7 +102,7 @@ impl AudioSystem {
if let Some(new_processor) = processors.take() { if let Some(new_processor) = processors.take() {
current_processor = new_processor; current_processor = new_processor;
} }
current_processor.process(frame, &mut output_buffer); current_processor.process(frame, config.channels as usize, &mut output_buffer);
if output_buffer.len() < PACKET_SAMPLES as usize { if output_buffer.len() < PACKET_SAMPLES as usize {
return; return;
} }
@@ -95,16 +119,13 @@ impl AudioSystem {
}; };
match self.input.build_input_stream( match self.input.build_input_stream(
&cpal::StreamConfig { &config,
channels: 1,
sample_rate: cpal::SampleRate(SAMPLE_RATE),
buffer_size: cpal::BufferSize::Fixed(PACKET_SAMPLES),
},
data_callback, data_callback,
error_callback, error_callback,
None, None,
) { ) {
Ok(stream) => { Ok(stream) => {
stream.play()?;
self.recording_stream = Some(stream); self.recording_stream = Some(stream);
Ok(()) Ok(())
} }
@@ -116,6 +137,8 @@ impl AudioSystem {
} }
pub fn create_player(&mut self) -> Result<AudioPlayer, Error> { pub fn create_player(&mut self) -> Result<AudioPlayer, Error> {
let config = self.choose_config(self.input.supported_input_configs()?)?;
info!("creating player on {:?} with {:#?}", self.output.name().ok(), &config);
let buffer = Arc::new(Mutex::new(dasp_ring_buffer::Bounded::from_raw_parts( let buffer = Arc::new(Mutex::new(dasp_ring_buffer::Bounded::from_raw_parts(
0, 0,
0, 0,
@@ -128,20 +151,16 @@ impl AudioSystem {
let stream = { let stream = {
let buffer = buffer.clone(); let buffer = buffer.clone();
self.output.build_output_stream( self.output.build_output_stream(
&cpal::StreamConfig { &config,
channels: 1, move |frame, _info| {
sample_rate: cpal::SampleRate(SAMPLE_RATE),
buffer_size: cpal::BufferSize::Fixed(480), // 10ms playback delay
},
move |frame, info| {
let mut buffer = buffer.lock().unwrap(); let mut buffer = buffer.lock().unwrap();
for x in frame.iter_mut() { for x in frame.chunks_mut(config.channels as usize) {
match buffer.pop() { match buffer.pop() {
Some(y) => { Some(y) => {
*x = y; x.fill(y);
} }
None => { None => {
*x = 0; x.fill(0);
} }
} }
} }
@@ -150,6 +169,7 @@ impl AudioSystem {
None, None,
)? )?
}; };
stream.play()?;
Ok(AudioPlayer { Ok(AudioPlayer {
decoder, decoder,
stream, stream,
+1 -1
View File
@@ -209,7 +209,7 @@ fn process_audio(frame: &JsValue, processor: &mut AudioProcessor) {
}; };
let input = samples.to_vec(); let input = samples.to_vec();
let mut output = Vec::with_capacity(input.len()); let mut output = Vec::with_capacity(input.len());
processor.process(&input, &mut output); processor.process(&input, 1, &mut output);
samples.copy_from(&output); samples.copy_from(&output);
} }