Last active
November 14, 2025 07:00
-
-
Save ahkohd/35476b148532b41aeac068e6700b7020 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ... | |
| let host = cpal::default_host(); | |
| // Mic device (channel 0 / left) | |
| let mic_device = host.default_input_device().expect("no input device"); | |
| let mic_config = mic_device.default_input_config().expect("no mic config"); | |
| println!("[dual-source] Mic: {:?}", mic_device.name()); | |
| // System audio loopback device (channel 1 / right) | |
| let loopback_device = host.default_output_device().expect("no output device"); | |
| // Get config: use output config even if device doesn't support input (that's the trick for loopback) | |
| let loopback_config = if loopback_device.supported_input_configs().is_ok() | |
| && loopback_device | |
| .supported_input_configs() | |
| .unwrap() | |
| .next() | |
| .is_some() | |
| { | |
| println!( | |
| "[dual-source] System audio: {:?}", | |
| loopback_device.name() | |
| ); | |
| loopback_device | |
| .default_input_config() | |
| .expect("failed to get input config") | |
| } else { | |
| println!( | |
| "[dual-source] System audio: {:?}", | |
| loopback_device.name() | |
| ); | |
| loopback_device | |
| .default_output_config() | |
| .expect("failed to get output config") | |
| }; | |
| // .... | |
| // Calculate decimation ratio for loopback resampling | |
| let decimation_ratio = if loopback_sample_rate > mic_sample_rate { | |
| loopback_sample_rate / mic_sample_rate | |
| } else { | |
| 1 | |
| }; | |
| // Start mic stream - use native config | |
| let mic_buf_clone = mic_buffer.clone(); | |
| let mic_stream = match mic_config.sample_format() { | |
| SampleFormat::F32 => mic_device.build_input_stream( | |
| &mic_config.clone().into(), | |
| move |data: &[f32], _| { | |
| let mut buf = mic_buf_clone.lock().unwrap(); | |
| for &sample in data { | |
| buf.push((sample * 32767.0) as i16); | |
| } | |
| }, | |
| |err| eprintln!("[mic] error: {}", err), | |
| None, | |
| ), | |
| SampleFormat::I16 => mic_device.build_input_stream( | |
| &mic_config.clone().into(), | |
| move |data: &[i16], _| { | |
| let mut buf = mic_buf_clone.lock().unwrap(); | |
| buf.extend_from_slice(data); | |
| }, | |
| |err| eprintln!("[mic] error: {}", err), | |
| None, | |
| ), | |
| format => panic!("unsupported mic format: {:?}", format), | |
| } | |
| .expect("failed to build mic stream"); | |
| // ... | |
| let loopback_stream_result = match loopback_config.sample_format() { | |
| SampleFormat::F32 => { | |
| let decim = decimation_ratio as usize; | |
| loopback_device.build_input_stream( | |
| &loopback_config.clone().into(), | |
| move |data: &[f32], _| { | |
| let mut buf = sys_buf_clone.lock().unwrap(); | |
| // Stereo to mono + decimation (sample rate conversion) | |
| if loopback_channels == 2 { | |
| for (i, chunk) in data.chunks_exact(2).enumerate() { | |
| if i % decim == 0 { | |
| let mixed = (chunk[0] + chunk[1]) / 2.0; | |
| buf.push((mixed * 32767.0) as i16); | |
| } | |
| } | |
| } else { | |
| for (i, &sample) in data.iter().enumerate() { | |
| if i % decim == 0 { | |
| buf.push((sample * 32767.0) as i16); | |
| } | |
| } | |
| } | |
| }, | |
| |err| eprintln!("[loopback] error: {}", err), | |
| None, | |
| ) | |
| } | |
| SampleFormat::I16 => { | |
| let decim = decimation_ratio as usize; | |
| loopback_device.build_input_stream( | |
| &loopback_config.clone().into(), | |
| move |data: &[i16], _| { | |
| let mut buf = sys_buf_clone.lock().unwrap(); | |
| // Stereo to mono + decimation (sample rate conversion) | |
| if loopback_channels == 2 { | |
| for (i, chunk) in data.chunks_exact(2).enumerate() { | |
| if i % decim == 0 { | |
| let mixed = (chunk[0] as i32 + chunk[1] as i32) / 2; | |
| buf.push(mixed as i16); | |
| } | |
| } | |
| } else { | |
| for (i, &sample) in data.iter().enumerate() { | |
| if i % decim == 0 { | |
| buf.push(sample); | |
| } | |
| } | |
| } | |
| }, | |
| |err| eprintln!("[loopback] error: {}", err), | |
| None, | |
| ) | |
| } | |
| format => panic!("unsupported loopback format: {:?}", format), | |
| }; | |
| // ... |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| [dependencies] | |
| cpal = { git = "https://github.com/RustAudio/cpal.git", rev = "a8269d3c993f7d375d4655b53d3437429d4f6bd8" } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment