Skip to content

Commit 22d244f

Browse files
fix: discard denormals, prevent slow processing from freezing UI
- Refs #30 Signed-off-by: Maika Namuo <httpworldview@gmail.com>
1 parent b072893 commit 22d244f

1 file changed

Lines changed: 32 additions & 17 deletions

File tree

‎src/infra/pipewire/transport.rs‎

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33

44
use super::{MAX_CAPTURE_CHANNELS, MAX_CAPTURE_SAMPLE_RATE};
55
use crate::dsp::{AudioFormat, ChannelPosition};
6-
use crate::util::{audio::DEFAULT_SAMPLE_RATE, unpoison};
6+
use crate::util::{
7+
audio::{DEFAULT_SAMPLE_RATE, flush_denormal_f32},
8+
unpoison,
9+
};
710
use rtrb::{Consumer, Producer, PushError, RingBuffer};
811
use std::hash::{Hash, Hasher};
912
use std::io::Read;
@@ -197,14 +200,24 @@ pub(super) fn pcm_chunk(
197200
Some([start..start + first, 0..len - first])
198201
}
199202

203+
// Producer IIR tails can settle on subnormals, which *MIGHT* stall
204+
// f32 DSP on CPUs that handle subnormals in microcode (e.g. Intel).
205+
fn sanitize_pcm(mut sample: f32) -> f32 {
206+
if !sample.is_finite() {
207+
return 0.0;
208+
}
209+
flush_denormal_f32(&mut sample);
210+
sample
211+
}
212+
200213
fn pcm_has_signal(bytes: &[u8], chunk: &[Range<usize>; 2]) -> bool {
201214
let mut source = Read::chain(&bytes[chunk[0].clone()], &bytes[chunk[1].clone()]);
202215
let mut raw = [0; 4];
203216
std::iter::from_fn(|| {
204217
source.read_exact(&mut raw).ok()?;
205218
Some(f32::from_ne_bytes(raw))
206219
})
207-
.any(|sample| sample != 0.0 && sample.is_finite())
220+
.any(|sample| sanitize_pcm(sample) != 0.0)
208221
}
209222

210223
struct Packet {
@@ -332,11 +345,8 @@ impl CaptureWriter {
332345
.read_exact(bytemuck::cast_slice_mut(samples))
333346
.expect("valid PCM bounds");
334347
for sample in samples {
335-
if sample.is_finite() {
336-
signal |= *sample != 0.0;
337-
} else {
338-
*sample = 0.0;
339-
}
348+
*sample = sanitize_pcm(*sample);
349+
signal |= *sample != 0.0;
340350
}
341351
});
342352
if accepted {
@@ -611,7 +621,10 @@ impl AudioReader {
611621
return;
612622
}
613623

614-
while let Ok(packet) = self.consumer.pop() {
624+
for _ in 0..self.consumer.slots() {
625+
let Ok(packet) = self.consumer.pop() else {
626+
break;
627+
};
615628
self.accept(packet, &mut consume);
616629
if self.scratch.len() >= PCM_FLUSH_SAMPLES {
617630
self.flush(&mut consume);
@@ -917,15 +930,15 @@ mod tests {
917930
}
918931

919932
#[test]
920-
fn pcm_copy_handles_wrapping_and_sanitizes_non_finite_samples() {
933+
fn pcm_copy_handles_wrapping_and_sanitizes_non_finite_and_tiny_samples() {
921934
let (mut writer, mut reader, _) = mono(4, 1_000);
922-
let mut bytes = bytemuck::cast_slice(&[1.0_f32, f32::NAN, 2.0]).to_vec();
935+
let mut bytes = bytemuck::cast_slice(&[1.0_f32, f32::NAN, 2.0, 1.0e-40, -1.0e-30]).to_vec();
923936
bytes.rotate_right(3);
924937
let chunk = pcm_chunk(bytes.len(), 3, bytes.len() as u32, size_of::<f32>()).unwrap();
925-
writer.push_pcm(&bytes, &chunk, 3);
938+
writer.push_pcm(&bytes, &chunk, 5);
926939
assert!(writer.flush_pending());
927940
let samples = reader.consumer.pop().unwrap().samples.unwrap();
928-
assert_eq!(&samples[..3], &[1.0, 0.0, 2.0]);
941+
assert_eq!(&samples[..5], &[1.0, 0.0, 2.0, 0.0, 0.0]);
929942
}
930943

931944
#[test]
@@ -958,11 +971,13 @@ mod tests {
958971
assert!(reader.sleep_until_signal());
959972

960973
writer.push_silence(BLOCK_FRAMES as u64);
961-
let zero = 0.0_f32.to_ne_bytes();
962-
let chunk = pcm_chunk(zero.len(), 0, zero.len() as u32, zero.len()).unwrap();
963-
writer.push_pcm(&zero, &chunk, 1);
964-
assert!(!reader.is_active());
965-
assert!(audio_wake(&wake).try_recv().is_err());
974+
for quiet in [0.0_f32, 1.0e-40] {
975+
let quiet = quiet.to_ne_bytes();
976+
let chunk = pcm_chunk(quiet.len(), 0, quiet.len() as u32, quiet.len()).unwrap();
977+
writer.push_pcm(&quiet, &chunk, 1);
978+
assert!(!reader.is_active());
979+
assert!(audio_wake(&wake).try_recv().is_err());
980+
}
966981

967982
let mut signal = 0.25_f32.to_ne_bytes();
968983
signal.rotate_right(1);

0 commit comments

Comments
 (0)