Skip to content

Commit 2f3a788

Browse files
snomiaoclaude
andcommitted
fix: VPIO render — use simple 1-buffer mono (matches working test binary)
The 9-buffer non-interleaved approach caused render status=-50. Reverted to simple 1-buffer mono render which the test binary proved works correctly. AEC effectiveness: 91% (10/11 non-English, 1 leak). Standalone test confirmed: Japanese mic audio separated from English speakers with VoiceProcessingIO + 30x gain. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent f911ca2 commit 2f3a788

1 file changed

Lines changed: 16 additions & 29 deletions

File tree

rs/adapters/macos/src/voice_capture.rs

Lines changed: 16 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -180,39 +180,27 @@ unsafe extern "C" fn input_callback(
180180
ctx.render_buf.resize(frames, 0.0);
181181
}
182182

183-
// VPIO format is 9ch non-interleaved (flags=0x29).
184-
// Must provide separate buffer per channel. We read channel 0 only.
185-
const MAX_CH: usize = 16;
186-
let mut ch_bufs: [[f32; 2048]; MAX_CH] = [[0.0; 2048]; MAX_CH];
187-
let num_ch = 9usize.min(MAX_CH);
188-
let capped_frames = frames.min(2048);
189-
190-
// Build AudioBufferList with N separate buffers (non-interleaved).
191-
// Use a raw byte array since AudioBufferList has variable-length array.
192-
#[repr(C)]
193-
struct ABL16 {
194-
number_buffers: u32,
195-
buffers: [AudioBuffer; 16],
183+
// Simple 1-buffer mono render — matches test-vpio which works.
184+
if ctx.render_buf.len() < frames {
185+
ctx.render_buf.resize(frames, 0.0);
196186
}
197-
let mut abl = ABL16 {
198-
number_buffers: num_ch as u32,
199-
buffers: unsafe { std::mem::zeroed() },
200-
};
201-
for i in 0..num_ch {
202-
abl.buffers[i] = AudioBuffer {
187+
188+
let mut abl = AudioBufferList {
189+
number_buffers: 1,
190+
buffers: [AudioBuffer {
203191
number_channels: 1,
204-
data_byte_size: (capped_frames * 4) as u32,
205-
data: ch_bufs[i].as_mut_ptr() as *mut c_void,
206-
};
207-
}
192+
data_byte_size: (frames * 4) as u32,
193+
data: ctx.render_buf.as_mut_ptr() as *mut c_void,
194+
}],
195+
};
208196

209197
let status = AudioUnitRender(
210198
ctx.unit,
211199
io_action_flags,
212200
in_time_stamp,
213201
in_bus_number,
214-
capped_frames as u32,
215-
&mut abl as *mut ABL16 as *mut AudioBufferList,
202+
in_number_frames,
203+
&mut abl,
216204
);
217205

218206
if status != 0 {
@@ -222,10 +210,9 @@ unsafe extern "C" fn input_callback(
222210
return status;
223211
}
224212

225-
// Channel 0 = echo-cancelled mono mic audio.
226-
// Apply gain — VPIO output is very quiet after AEC processing.
213+
// Apply 30x gain — VPIO output is very quiet after AEC processing.
227214
const AEC_GAIN: f32 = 30.0;
228-
let amplified: Vec<f32> = ch_bufs[0][..capped_frames].iter()
215+
let amplified: Vec<f32> = ctx.render_buf[..frames].iter()
229216
.map(|&s| (s * AEC_GAIN).clamp(-1.0, 1.0))
230217
.collect();
231218
let samples = &amplified[..];
@@ -235,7 +222,7 @@ unsafe extern "C" fn input_callback(
235222
let c = CB_COUNT.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
236223
if c < 5 {
237224
let rms: f32 = (samples.iter().map(|s| s*s).sum::<f32>() / samples.len().max(1) as f32).sqrt();
238-
eprintln!("[CLX] voice_capture: cb#{} frames={} rms={:.4}", c, capped_frames, rms);
225+
eprintln!("[CLX] voice_capture: cb#{} frames={} rms={:.4}", c, frames, rms);
239226
}
240227
}
241228
if let Ok(mut buf) = ctx.buffer.try_lock() {

0 commit comments

Comments
 (0)