mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-28 13:32:23 +00:00
fix(voice): handle U16 sample format in the fallback capture path (#4537)
This commit is contained in:
@@ -368,8 +368,7 @@ fn record_on_thread(
|
||||
.build_input_stream(
|
||||
&stream_config,
|
||||
move |data: &[i16], _: &cpal::InputCallbackInfo| {
|
||||
let floats: Vec<f32> =
|
||||
data.iter().map(|&s| s as f32 / 32768.0).collect();
|
||||
let floats = i16_to_f32(data);
|
||||
let mono = to_mono(&floats, source_channels);
|
||||
update_peak_rms(&rms_tracker, &mono);
|
||||
let gated = gate.lock().process(&mono);
|
||||
@@ -389,10 +388,7 @@ fn record_on_thread(
|
||||
.build_input_stream(
|
||||
&stream_config,
|
||||
move |data: &[u16], _: &cpal::InputCallbackInfo| {
|
||||
let floats: Vec<f32> = data
|
||||
.iter()
|
||||
.map(|&s| (s as f32 - 32768.0) / 32768.0)
|
||||
.collect();
|
||||
let floats = u16_to_f32(data);
|
||||
let mono = to_mono(&floats, source_channels);
|
||||
update_peak_rms(&rms_tracker, &mono);
|
||||
let gated = gate.lock().process(&mono);
|
||||
@@ -446,8 +442,7 @@ fn record_on_thread(
|
||||
.build_input_stream(
|
||||
&sc,
|
||||
move |data: &[i16], _: &cpal::InputCallbackInfo| {
|
||||
let floats: Vec<f32> =
|
||||
data.iter().map(|&s| s as f32 / 32768.0).collect();
|
||||
let floats = i16_to_f32(data);
|
||||
let mono = to_mono(&floats, ch);
|
||||
update_peak_rms(&rt, &mono);
|
||||
let gated = gate.lock().process(&mono);
|
||||
@@ -459,7 +454,23 @@ fn record_on_thread(
|
||||
None,
|
||||
)
|
||||
.map_err(|e| format!("fallback i16 stream failed: {e}")),
|
||||
_ => Err(format!("unsupported fallback format: {fmt:?}")),
|
||||
SampleFormat::U16 => device
|
||||
.build_input_stream(
|
||||
&sc,
|
||||
move |data: &[u16], _: &cpal::InputCallbackInfo| {
|
||||
let floats = u16_to_f32(data);
|
||||
let mono = to_mono(&floats, ch);
|
||||
update_peak_rms(&rt, &mono);
|
||||
let gated = gate.lock().process(&mono);
|
||||
if !gated.is_empty() {
|
||||
sw.lock().extend_from_slice(&gated);
|
||||
}
|
||||
},
|
||||
|err| error!("{LOG_PREFIX} audio stream error: {err}"),
|
||||
None,
|
||||
)
|
||||
.map_err(|e| format!("fallback u16 stream failed: {e}")),
|
||||
other => Err(format!("unsupported fallback format: {other:?}")),
|
||||
};
|
||||
match fallback_stream {
|
||||
Ok(s) => (s, sr, ch),
|
||||
@@ -519,6 +530,22 @@ pub fn list_input_devices() -> Result<Vec<String>, String> {
|
||||
Ok(names)
|
||||
}
|
||||
|
||||
/// Convert interleaved signed `i16` PCM samples to normalised `f32` in
|
||||
/// `[-1.0, 1.0)` (`s / 32768`). Extracted so the preferred and fallback stream
|
||||
/// paths share one conversion instead of duplicating it inline.
|
||||
pub(crate) fn i16_to_f32(data: &[i16]) -> Vec<f32> {
|
||||
data.iter().map(|&s| s as f32 / 32768.0).collect()
|
||||
}
|
||||
|
||||
/// Convert interleaved unsigned `u16` PCM samples (mid-scale 32768) to
|
||||
/// normalised `f32` in `[-1.0, 1.0)` (`(s - 32768) / 32768`). Shared by the
|
||||
/// preferred and fallback stream paths.
|
||||
pub(crate) fn u16_to_f32(data: &[u16]) -> Vec<f32> {
|
||||
data.iter()
|
||||
.map(|&s| (s as f32 - 32768.0) / 32768.0)
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Convert interleaved multi-channel samples to mono by averaging channels.
|
||||
pub(crate) fn to_mono(samples: &[f32], channels: usize) -> Vec<f32> {
|
||||
if channels <= 1 {
|
||||
|
||||
@@ -1,6 +1,25 @@
|
||||
use super::*;
|
||||
use cpal::{SampleFormat, SampleRate, SupportedBufferSize, SupportedStreamConfigRange};
|
||||
|
||||
#[test]
|
||||
fn i16_to_f32_normalises_to_unit_range() {
|
||||
assert_eq!(i16_to_f32(&[0]), vec![0.0]);
|
||||
assert_eq!(i16_to_f32(&[16384]), vec![0.5]);
|
||||
assert_eq!(i16_to_f32(&[-32768]), vec![-1.0]);
|
||||
// Interleaved frames are converted element-wise, order preserved.
|
||||
assert_eq!(i16_to_f32(&[0, 16384, -16384]), vec![0.0, 0.5, -0.5]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn u16_to_f32_centers_on_midscale() {
|
||||
// Unsigned PCM is offset-binary: 32768 is silence (0.0), the endpoints map
|
||||
// to the extremes. This is the conversion the fallback path was missing.
|
||||
assert_eq!(u16_to_f32(&[32768]), vec![0.0]);
|
||||
assert_eq!(u16_to_f32(&[49152]), vec![0.5]);
|
||||
assert_eq!(u16_to_f32(&[0]), vec![-1.0]);
|
||||
assert_eq!(u16_to_f32(&[32768, 49152, 16384]), vec![0.0, 0.5, -0.5]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_mono_passthrough_single_channel() {
|
||||
let input = vec![0.1, 0.2, 0.3];
|
||||
|
||||
Reference in New Issue
Block a user