Newbie: Channel clarification

Hi,

I'm getting started with learning rust and are doing a project recording audio.

I've got a bit of a weird problem, when writing the recorded audio to a wav file in the portaudio callback it sounds fine. But when trying to send it through a channel it becomes all crackly.

In short

   let callback = move |portaudio::InputStreamCallbackArgs { buffer, .. }| {
        for &sample in buffer.iter() {
            wav_writer.write_sample(sample).ok();
        }

works fine while

While

    let callback = move |portaudio::InputStreamCallbackArgs { buffer, .. }| {
        sender.send(buffer).unwrap();
        portaudio::Continue
    };

And then later

    while start.elapsed().as_secs().lt(time_to_wait) {
        let recv_data = receiver.recv().unwrap();
        for sample in recv_data.iter() {
            wav_writer.write_sample(*sample).ok();
        }
    }

produces crackling audio in the wav-file.

Am I some how misusing the channel causing the format of the received data to change? Or is this some wonkyness related to portaudio?

Edit: The source for this: rust-recorder/main.rs at master · forslund/rust-recorder · GitHub

Thanks

  • Åke

What's the wav_writer? Doesn't it record the high precision timestamp when the .write_sample() is called?

The wav_writer is a hound::WavWriter (hound::WavWriter - Rust) I don't think it writes any timestamps.

Another possible case would be that the .write_sample() returns error which is ignored due to the .ok();. Does it panic if you replace them with .unwrap();?

This is just a guess, but if the channel send takes too long for some reason, portaudio might be missing some samples while it waits for the send call to complete. If you're not compiling with --release, that might help.

I haven't looked at the code, but you probably want to be using a bounded channel so that send never tries to allocate anything.

Thanks @Hyeonu and @2e71828 for your suggestions.

I did change the ok to unwrap() without any panics.

I also added a reference write in the PA callback to see if there was any starvation of PA causing issues but the reference file sounded fine. I've just started to compare the resulting files and they match for ~560 bytes then they diverge...

Can the thing I send in the channel be written to by the callback again for some reason? Is there a way to enforce a copy when passing the buffer as an argument to the channel?

I dug into the portaudio bindings a little bit, and they seem a little suspicious: The callback argument is InputCallbackArgs<'static, I>, which means that the buffer is &'static [I]. In Rust terms, that means the data in the buffer is supposed to remain allocated and unchanged for the remainder of the program, assuming that I isn't some kind of Cell. (I haven't managed to track down where the I parameter is defined).

If true, that's a memory leak every time the callback is triggered. It seems more likely that portaudio isn't honoring Rust's non-aliasing guarantee for references, and is changing the buffer contents after the callback returns. That means the reading side of the channel, which is operating outside of the callback, might be getting into a data race with the audio driver that the compiler isn't expecting.


Edit: From your code, it looks like I = i16.

1 Like

Thanks, that sounds likely...perhaps it can be resolved with copying the data to a new buffer in the callback and sending that across the channel?

let callback = move |portaudio::InputStreamCallbackArgs { buffer, frames, .. }|
{
        let mut _buf = vec![0i16; frames];
        let mut index = 0usize;
        for sample in buffer.iter() {
            _buf[index] = *sample;
            index += 1;
            wav_writer.write_sample(*sample).unwrap(); // Create write to reference wav
        }
        sender.send(_buf).unwrap();
        portaudio::Continue
    };

This seems to work but is rather ugly, I'll see if I can improve it somewhat :slight_smile:

Thanks for all the help and pointers.

1 Like

sender.send(buffer.to_vec()).unwrap(); should also works.

https://doc.rust-lang.org/stable/std/primitive.slice.html#method.to_vec

2 Likes

Somebody on the rust-audio discord suggested using ringbuf for this instead of std::mpsc. It should be able to avoid any allocations inside the callback, which can cause delays that mess up the recording.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.