diff --git a/CHANGELOG.md b/CHANGELOG.md index a84f09c6..8eb98a56 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ New features: - Add `debug` feature for extra logging. - Improve validation of filters. - Setting to enable retry on reads from Alsa capture devices. +- Avoid blocking reads on Alsa capture devices (helps avoiding driver bugs for some devices). Bugfixes: - Don't block playback for CoreAudio/Wasapi if there is no data in time. diff --git a/Cargo.toml b/Cargo.toml index 4e0aba58..75d16351 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "camilladsp" -version = "0.5.0" +version = "0.5.0-beta5" authors = ["Henrik Enquist "] description = "A flexible tool for processing audio" diff --git a/src/alsadevice.rs b/src/alsadevice.rs index 6906467e..0335c24e 100644 --- a/src/alsadevice.rs +++ b/src/alsadevice.rs @@ -131,17 +131,58 @@ fn play_buffer( Ok(()) } -/// Play a buffer. +/// Capture a buffer. fn capture_buffer( buffer: &mut [u8], pcmdevice: &alsa::PCM, io: &alsa::pcm::IO, retry: bool, + samplerate: usize, + frames_to_read: usize, ) -> Res { let capture_state = pcmdevice.state(); if capture_state == State::XRun { - warn!("prepare capture"); + warn!("Prepare capture device"); pcmdevice.prepare()?; + } else if capture_state != State::Running { + debug!("Starting capture"); + pcmdevice.start()?; + } + let available = pcmdevice.avail(); + match available { + Ok(frames) => { + if (frames as usize) < frames_to_read { + trace!( + "Not enough frames available: {}, need: {}, waiting...", + frames, + frames_to_read + ); + // Let's wait for more frames, with 10% plus 1 ms of margin + thread::sleep(Duration::from_millis( + (1 + (1100 * (frames_to_read - frames as usize)) / samplerate) as u64, + )); + if (pcmdevice.avail().unwrap_or(0) as usize) < frames_to_read { + // Still not enough, + warn!("Capture timed out, will try again"); + return Ok(CaptureResult::RecoverableError); + } + } + } + Err(err) => { + if retry { + warn!("Capture failed while querying for available frames, error: {}, will try again.", err); + thread::sleep(Duration::from_millis( + (1000 * frames_to_read as u64) / samplerate as u64, + )); + return Ok(CaptureResult::RecoverableError); + } else { + warn!( + "Capture failed while querying for available frames, error: {}", + err + ); + return Err(Box::new(err)); + } + } } let _frames = match io.readi(buffer) { Ok(frames) => frames, @@ -151,7 +192,7 @@ fn capture_buffer( warn!("Capture failed with error: {}, will try again.", err); return Ok(CaptureResult::RecoverableError); } else { - warn!("Capture failed, error: {}", err); + warn!("Capture failed with error: {}", err); return Err(Box::new(err)); } } @@ -397,6 +438,8 @@ fn capture_loop_bytes( pcmdevice, &io, params.retry_on_error, + params.capture_samplerate, + capture_bytes / (params.channels * params.store_bytes_per_sample), ); match capture_res { Ok(CaptureResult::Normal) => {