Skip to content

Loopback not working on macOS <= 14.6 #1030

@Super1Windcloud

Description

@Super1Windcloud

The capture example captures the output stream normally on Windows, but not on macOS, and there won't be any error messages

I know that is a latest PR ,can you fix it ? thank you very much

use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
use cpal::Sample;
use dasp::sample::ToSample;
use hound::WavWriter;
use rubato::{FftFixedIn, Resampler};
use std::fs::File;
use std::io::BufWriter;
use std::sync::{Arc, Mutex};

pub struct RecordParams {
    pub device: String,
    pub duration: u64,
}

fn select_output_config() -> Result<cpal::SupportedStreamConfig, String> {
    let device = cpal::default_host()
        .default_output_device()
        .ok_or("没有可用的输出设备")?;

    let supported_configs = device
        .supported_output_configs()
        .map_err(|_| "无法获取输出设备配置".to_string())?;

    let desired_sample_rate = cpal::SampleRate(16000);

    for range in supported_configs {
        if range.min_sample_rate() <= desired_sample_rate
            && range.max_sample_rate() >= desired_sample_rate
        {
            let selected = range.with_sample_rate(desired_sample_rate);
            println!("选择输出设备配置:{:?}", selected);
            return Ok(selected);
        }
    }

    let fallback = device
        .default_output_config()
        .map_err(|_| "没有可用的输出配置".to_string())?;

    println!("使用默认输出配置:{:?}", fallback);
    Ok(fallback)
}

pub fn record_audio(params: RecordParams) -> Result<(), String> {
    let host = cpal::default_host();

    let device = match params.device.as_str() {
        "default" => host.default_output_device(),
        "default_input" => host.default_input_device(),
        name => host
            .output_devices()
            .unwrap()
            .find(|x| x.name().map(|y| y == name).unwrap_or(false)),
    }
    .ok_or_else(|| "无法找到输出设备".to_string())?;
    let config = select_output_config().unwrap();

    const PATH_F32_STEREO: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/recorded_f32_stereo.wav");
    const PATH_F32_MONO: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/recorded_f32_mono.wav");
    const PATH_I16_STEREO: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/recorded_i16_stereo.wav");
    const PATH_I16_MONO: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/recorded_i16_mono.wav");
    const PATH_I16_STEREO_RESAMPLE: &str = concat!(
        env!("CARGO_MANIFEST_DIR"),
        "/recorded_i16_stereo_resample.wav"
    );
    const PATH_I16_MONO_RESAMPLE: &str = concat!(
        env!("CARGO_MANIFEST_DIR"),
        "/recorded_i16_mono_resample.wav"
    );

    
    let spec_f32_stereo = wav_spec_from_config(&config);
    let spec_f32_mono = hound::WavSpec {
        channels: 1,
        sample_rate: spec_f32_stereo.sample_rate,
        bits_per_sample: 32,
        sample_format: hound::SampleFormat::Float,
    };
    let spec_i16_stereo = hound::WavSpec {
        channels: spec_f32_stereo.channels,
        sample_rate: spec_f32_stereo.sample_rate,
        bits_per_sample: 16,
        sample_format: hound::SampleFormat::Int,
    };
    let spec_i16_mono = hound::WavSpec {
        channels: 1,
        sample_rate: spec_f32_stereo.sample_rate,
        bits_per_sample: 16,
        sample_format: hound::SampleFormat::Int,
    };

    let spec_i16_stereo_resample = hound::WavSpec {
        channels: spec_i16_stereo.channels,
        sample_rate: 16000,
        bits_per_sample: 16,
        sample_format: hound::SampleFormat::Int,
    };

    let spec_i16_mono_resample = hound::WavSpec {
        channels: 1,
        sample_rate: 16000,
        bits_per_sample: 16,
        sample_format: hound::SampleFormat::Int,
    };

 
    let f32_stereo = Arc::new(Mutex::new(Some(
        hound::WavWriter::create(PATH_F32_STEREO, spec_f32_stereo)
            .map_err(|e| format!("创建文件失败: {e}"))?,
    )));
    let f32_mono = Arc::new(Mutex::new(Some(
        hound::WavWriter::create(PATH_F32_MONO, spec_f32_mono)
            .map_err(|e| format!("创建文件失败: {e}"))?,
    )));
    let i16_stereo = Arc::new(Mutex::new(Some(
        hound::WavWriter::create(PATH_I16_STEREO, spec_i16_stereo)
            .map_err(|e| format!("创建文件失败: {e}"))?,
    )));
    let i16_mono = Arc::new(Mutex::new(Some(
        hound::WavWriter::create(PATH_I16_MONO, spec_i16_mono)
            .map_err(|e| format!("创建文件失败: {e}"))?,
    )));
    let i16_resample_stereo = Arc::new(Mutex::new(Some(
        hound::WavWriter::create(PATH_I16_STEREO_RESAMPLE, spec_i16_stereo_resample)
            .map_err(|e| format!("创建文件失败: {e}"))?,
    )));
    let i16_resample_mono = Arc::new(Mutex::new(Some(
        hound::WavWriter::create(PATH_I16_MONO_RESAMPLE, spec_i16_mono_resample)
            .map_err(|e| format!("创建文件失败: {e}"))?,
    )));

    let err_fn = |err| eprintln!("🎧 音频流错误: {err}");

    let stream = match config.sample_format() {
        cpal::SampleFormat::F32 => device.build_input_stream(
            &config.into(),
            {
                let f32_stereo = f32_stereo.clone();
                let f32_mono = f32_mono.clone();
                let i16_stereo = i16_stereo.clone();
                let i16_mono = i16_mono.clone();
                let i16_resample_stereo = i16_resample_stereo.clone();
                let i16_resample_mono = i16_resample_mono.clone();

                move |data: &[f32], _: &_| {
                    write_all_formats(
                        data,
                        &f32_stereo,
                        &f32_mono,
                        &i16_stereo,
                        &i16_mono,
                        &i16_resample_stereo,
                        &i16_resample_mono,
                    );
                }
            },
            err_fn,
            None,
        ),
        cpal::SampleFormat::I16 => device.build_input_stream(
            &config.into(),
            {
                let f32_stereo = f32_stereo.clone();
                let f32_mono = f32_mono.clone();
                let i16_stereo = i16_stereo.clone();
                let i16_mono = i16_mono.clone();

                move |data: &[i16], _: &_| {
                    write_all_formats(
                        data,
                        &f32_stereo,
                        &f32_mono,
                        &i16_stereo,
                        &i16_mono,
                        &i16_resample_stereo,
                        &i16_resample_mono,
                    );
                }
            },
            err_fn,
            None,
        ),
        other => return Err(format!("暂不支持的采样格式: {other:?}")),
    }
    .map_err(|e| format!("创建音频输出流失败: {e}"))?;

    println!("▶️ 开始录制 {:#?} 秒...", params.duration);
    stream.play().unwrap();

    std::thread::sleep(std::time::Duration::from_secs(params.duration));
    drop(stream);

    finalize_all(&[&f32_stereo, &f32_mono, &i16_stereo, &i16_mono]);

  
    Ok(())
}

fn wav_spec_from_config(config: &cpal::SupportedStreamConfig) -> hound::WavSpec {
    hound::WavSpec {
        channels: config.channels() as _,
        sample_rate: config.sample_rate().0 as _,
        bits_per_sample: (config.sample_format().sample_size() * 8) as _,
        sample_format: if config.sample_format().is_float() {
            hound::SampleFormat::Float
        } else {
            hound::SampleFormat::Int
        },
    }
}

type WavWriterHandle = Arc<Mutex<Option<WavWriter<BufWriter<File>>>>>;

 
fn write_all_formats<T>(
    input: &[T],
    f32_stereo: &WavWriterHandle,
    f32_mono: &WavWriterHandle,
    i16_stereo: &WavWriterHandle,
    i16_mono: &WavWriterHandle,
    i16_stereo_resample: &Arc<Mutex<Option<WavWriter<BufWriter<File>>>>>,
    i16_mono_resample: &Arc<Mutex<Option<WavWriter<BufWriter<File>>>>>,
) where
    T: Sample + ToSample<f32> + ToSample<i16>,
{
    // 转换
    let stereo_f32: Vec<f32> = input.iter().map(|s| s.to_sample::<f32>()).collect();
    let stereo_i16: Vec<i16> = input.iter().map(|s| s.to_sample::<i16>()).collect();
    let mono_f32 = stereo_to_mono_f32(&stereo_f32);
    let mono_i16 = stereo_to_mono_i16(&stereo_i16);
    let input_rate = 44100;
    let output_rate = 16000;
    let resampled_stereo_f32 = resample_audio(&stereo_f32, input_rate, output_rate, 2);
    let resampled_mono_f32 = resample_audio(&mono_f32, input_rate, output_rate, 1);

    let resampled_stereo_i16: Vec<i16> = resampled_stereo_f32
        .iter()
        .map(|s| (*s * i16::MAX as f32) as i16)
        .collect();
    let resampled_mono_i16: Vec<i16> = resampled_mono_f32
        .iter()
        .map(|s| (*s * i16::MAX as f32) as i16)
        .collect();

    write_samples(&stereo_f32, f32_stereo);
    write_samples(&mono_f32, f32_mono);
    write_samples(&stereo_i16, i16_stereo);
    write_samples(&mono_i16, i16_mono);
    write_samples(&resampled_stereo_i16, i16_stereo_resample);
    write_samples(&resampled_mono_i16, i16_mono_resample);
}

fn resample_audio(
    input: &[f32],
    input_rate: usize,
    output_rate: usize,
    channels: usize,
) -> Vec<f32> {
    if input_rate == output_rate {
        return input.to_vec();
    }
 
    let mut separated: Vec<Vec<f32>> = vec![Vec::new(); channels];
    for frame in input.chunks(channels) {
        for (ch, &sample) in frame.iter().enumerate() {
            separated[ch].push(sample);
        }
    }

    let mut resampler = FftFixedIn::<f32>::new(
        input_rate,
        output_rate,
        separated[0].len(),
        separated[0].len(),
        channels,
    )
    .unwrap();

    let output = resampler.process(&separated, None).unwrap();

 
    let mut interleaved = Vec::with_capacity(output[0].len() * channels);
    for i in 0..output[0].len() {
        for ch in 0..channels {
            interleaved.push(output[ch][i]);
        }
    }

    interleaved
}

 
fn write_samples<T>(data: &[T], writer: &WavWriterHandle)
where
    T: hound::Sample + Copy,
{
    if let Ok(mut guard) = writer.try_lock() {
        if let Some(w) = guard.as_mut() {
            for &s in data {
                w.write_sample(s).ok();
            }
        }
    }
}

fn stereo_to_mono_f32(samples: &[f32]) -> Vec<f32> {
    if samples.len() < 2 {
        return samples.to_vec();
    }
    samples
        .chunks_exact(2)
        .map(|c| (c[0] + c[1]) / 2.0)
        .collect()
}

fn stereo_to_mono_i16(samples: &[i16]) -> Vec<i16> {
    if samples.len() < 2 {
        return samples.to_vec();
    }
    samples
        .chunks_exact(2)
        .map(|c| ((c[0] as i32 + c[1] as i32) / 2) as i16)
        .collect()
}

fn finalize_all(writers: &[&WavWriterHandle]) {
    for w in writers {
        if let Ok(mut guard) = w.lock() {
            if let Some(w) = guard.take() {
                let _ = w.finalize();
            }
        }
    }
}

fn main() {
    let params = RecordParams {
        device: "default".into(),
        duration: 10,
    };

    if let Err(e) = record_audio(params) {
        eprintln!("❌ 错误: {e}");
    }
}

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions