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();
}
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?
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.
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.
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.