Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Help on convert a stream from 48khz 32bit 1ch (from cpal) to 16khz 16bit 1ch #12

Open
jBernavaPrah opened this issue Mar 27, 2024 · 1 comment
Labels
question Further information is requested
Milestone

Comments

@jBernavaPrah
Copy link

jBernavaPrah commented Mar 27, 2024

Hi!

Thanks for your crate.
I'm not so sure if this will resolve my headache, but I will try anyway :)

I have a stream of Vec that is generated by the microphone read by the cpal crate, which is a 48000hz 32bit mono channel and I need to convert it into a 16000 16bit mono channel Vec.

I tried to understand where and how to put the code from this crate but without success.

Could you help me to achieve the goal?

Let me know if you need anything else.

Thanks a lot!
Ps: I'm still new to rust so if something is a little odd to you, it's sure my fault.

Here is my minimal example:
This was taken by this example

///! Records a WAV file (roughly 3 seconds long) using the default input device and config.
//!
//! The input data is recorded to "$CARGO_MANIFEST_DIR/recorded.wav".

use std::f32;
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
use cpal::{ Sample};
use std::fs::File;
use std::io::BufWriter;
use std::sync::{Arc, Mutex};

fn main() -> Result<(), anyhow::Error> {

    let host = cpal::default_host();
    let device = host.default_input_device().unwrap();
    println!("Input device: {}", device.name()?);

    let config = device
        .default_input_config()
        .expect("Failed to get default input config");
    println!("Default input config: {:?}", config);

    // The WAV file we're recording to.
    const PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/recorded.wav");
    
    let writer = hound::WavWriter::create(PATH, hound::WavSpec {
        channels: config.channels() as _,
        // here I put the specific sample rate to the one that I'm interested.
        sample_rate: 16000,
        // here I put the specific sample rate to the one that I'm interested.
        bits_per_sample: 16,
        sample_format: hound::SampleFormat::Int,
    })?;
    let writer = Arc::new(Mutex::new(Some(writer)));

    // A flag to indicate that recording is in progress.
    println!("Begin recording...");

    // Run the input stream on a separate thread.
    let writer_2 = writer.clone();

    let err_fn = move |err| {
        eprintln!("an error occurred on stream: {}", err);
    };

    let stream = match config.sample_format() {
        // here my hardware only supports f32
        cpal::SampleFormat::F32 => device.build_input_stream(
            &config.into(),
            move |data, _: &_| write_input_data(data, &writer_2),
            err_fn,
            None,
        )?,
        sample_format => {
            return Err(anyhow::Error::msg(format!(
                "Unsupported sample format '{sample_format}'"
            )));
        }
    };

    stream.play()?;

    // Let recording go for roughly three seconds.
    std::thread::sleep(std::time::Duration::from_secs(5));
    drop(stream);
    writer.lock().unwrap().take().unwrap().finalize()?;
    println!("Recording {} complete!", PATH);
    Ok(())
}

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

fn write_input_data(input: &[f32], writer: &WavWriterHandle)

{

    if let Ok(mut guard) = writer.try_lock() {
        if let Some(writer) = guard.as_mut() {

            // todo: Where put the conversion?

            for &sample in input.iter() {
                let sample: f32 = f32::from_sample(sample);
                writer.write_sample(sample).ok();
            }
        }
    }
}
@AldaronLau AldaronLau added the question Further information is requested label Mar 30, 2024
@AldaronLau
Copy link
Member

@jBernavaPrah thanks for opening an issue! If I understand right, you want to resample in realtime? I don't have an example for that, which I should probably fix.

Resampling after the fact for a 3 second audio in going to be simpler:

let writer: WavWriterHandle = todo!();
let audio: Vec<f32> = todo!();
let audio = Audio::<Ch32, 1>::with_f32_buffer(48_000, audio.as_slice());
let audio = Audio::<Ch16, 1>::with_audio(16_000, &audio);

for sample in audio.as_f32_slice().iter().cloned() {
    writer.write_sample(sample).ok();
}

For realtime conversion (something like this):

pub struct MyOutput {
    writer: WavWriterHandle,
}

impl fon::Sink<fon::chan::Ch16, 1> for MyOutput {
    fn sample_rate(&self) -> NonZeroU32 {
         NonZeroU32::try_from(16_000).unwrap()
    }

    fn len(&self) -> usize {
         // 3 seconds times sample rate
         3 * 16_000
    }

    fn sink_with(&mut self, iter: &mut dyn Iterator<Item = fon::Frame<fon::chan::Ch16, 1>>) {
        for sample in iter {
            self.writer.write_sample(sample).ok();
        }
    }
}

fn write_input_data(
    input: &[f32],
    sink_to: &mut SinkTo<fon::chan::Ch16, fon::chan::Ch16, MyOutput, 1, 1>.
    stream: &mut fon::Stream<1>,
) {
    let audio = Audio::with_f32_buffer(48_000, input.to_vec());
    stream.pipe(audio, sink_to);
}

let sink: SinkTo<fon::chan::Ch16, fon::chan::Ch32, MyOutput, 1, 1> = fon::SinkTo::new(MyOutput { writer });

Sorry, I don't have time to test it right now, but should be a start at least. I'll let you know when I make a more complete example later.

@AldaronLau AldaronLau added this to the Release 1.0 milestone Oct 14, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants