feat(audio): populate MicActivity timeline from the loopback writer (FR-SPK)
This commit is contained in:
1 parent
fee18ebc59
commit
39294adcbc
1 file changed
+25
-5
@@ -287,9 +287,6 @@ impl MicActivity {
|
||||
}
|
||||
|
||||
/// The loopback writer publishes its rate so frame positions map to windows.
|
||||
// ponytail: wired into capture_loop + stop in the next Phase 3 slice; the
|
||||
// struct + span logic land first, tested, so the wiring is mechanical.
|
||||
#[allow(dead_code)]
|
||||
fn set_rate(&self, rate: u32) {
|
||||
self.rate.store(rate, Ordering::Relaxed);
|
||||
}
|
||||
@@ -297,7 +294,6 @@ impl MicActivity {
|
||||
/// Flag the `audio.wav` frames `[start_frame, start_frame + mic.len())` as
|
||||
/// active when the pulled mic chunk is speech-level. Silence is left `false`
|
||||
/// (the default), so gaps between utterances don't become "You".
|
||||
#[allow(dead_code)]
|
||||
fn mark(&self, start_frame: u64, mic: &[f32]) {
|
||||
let rate = self.rate.load(Ordering::Relaxed) as u64;
|
||||
if rate == 0 || mic.is_empty() {
|
||||
@@ -379,6 +375,7 @@ impl WasapiCapture {
|
||||
event_sink: EventSink,
|
||||
bridge: Option<Arc<MicBridge>>,
|
||||
voice_sample: Option<Arc<VoiceSample>>,
|
||||
mic_activity: Option<Arc<MicActivity>>,
|
||||
emit_level: bool,
|
||||
) -> Result<CaptureHandle, AudioError> {
|
||||
let running = Arc::new(AtomicBool::new(true));
|
||||
@@ -401,6 +398,7 @@ impl WasapiCapture {
|
||||
&paused_th,
|
||||
bridge.as_ref(),
|
||||
voice_sample.as_ref(),
|
||||
mic_activity.as_ref(),
|
||||
emit_level,
|
||||
)
|
||||
})
|
||||
@@ -423,6 +421,7 @@ impl WasapiCapture {
|
||||
frame_sink: FrameSink,
|
||||
event_sink: EventSink,
|
||||
bridge: Arc<MicBridge>,
|
||||
mic_activity: Option<Arc<MicActivity>>,
|
||||
) -> Result<CaptureHandle, AudioError> {
|
||||
self.start_capture(
|
||||
"wa-audio-capture",
|
||||
@@ -433,6 +432,7 @@ impl WasapiCapture {
|
||||
event_sink,
|
||||
Some(bridge),
|
||||
None,
|
||||
mic_activity,
|
||||
true,
|
||||
)
|
||||
}
|
||||
@@ -458,6 +458,7 @@ impl WasapiCapture {
|
||||
event_sink,
|
||||
Some(bridge),
|
||||
voice_sample,
|
||||
None,
|
||||
true,
|
||||
)
|
||||
}
|
||||
@@ -481,6 +482,7 @@ impl AudioCapture for WasapiCapture {
|
||||
event_sink,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
true,
|
||||
)
|
||||
}
|
||||
@@ -500,6 +502,7 @@ impl AudioCapture for WasapiCapture {
|
||||
event_sink,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
true,
|
||||
)
|
||||
}
|
||||
@@ -671,6 +674,7 @@ fn capture_loop(
|
||||
paused: &AtomicBool,
|
||||
bridge: Option<&Arc<MicBridge>>,
|
||||
voice_sample: Option<&Arc<VoiceSample>>,
|
||||
mic_activity: Option<&Arc<MicActivity>>,
|
||||
emit_level: bool,
|
||||
) -> Result<CaptureSummary, AudioError> {
|
||||
wasapi::initialize_mta()
|
||||
@@ -682,12 +686,16 @@ fn capture_loop(
|
||||
// UI can overlay them — but only the loopback stream writes a WAV.
|
||||
let mut session = open_capture_session(&direction, device_id)?;
|
||||
// Loopback publishes its rate so the mic knows what to resample to before
|
||||
// pushing into the shared bridge (mic-into-recording, FR-CAP-7).
|
||||
// pushing into the shared bridge (mic-into-recording, FR-CAP-7), and so the
|
||||
// Phase 3 mic-activity timeline maps frame positions to windows.
|
||||
if is_loopback {
|
||||
if let Some(b) = bridge {
|
||||
b.rate
|
||||
.store(session.format.get_samplespersec(), Ordering::Relaxed);
|
||||
}
|
||||
if let Some(ma) = mic_activity {
|
||||
ma.set_rate(session.format.get_samplespersec());
|
||||
}
|
||||
}
|
||||
let spec = wav_spec_for(&session.format)?;
|
||||
let mut writer = match wav_path {
|
||||
@@ -746,6 +754,9 @@ fn capture_loop(
|
||||
b.rate
|
||||
.store(new_session.format.get_samplespersec(), Ordering::Relaxed);
|
||||
}
|
||||
if let Some(ma) = mic_activity {
|
||||
ma.set_rate(new_session.format.get_samplespersec());
|
||||
}
|
||||
}
|
||||
session = new_session;
|
||||
let _ = event_sink.try_send(CaptureEvent::DeviceChanged {
|
||||
@@ -770,7 +781,11 @@ fn capture_loop(
|
||||
* (session.format.get_bitspersample() as usize / 8);
|
||||
let silence = vec![0u8; gap_frames * bpf];
|
||||
let mic = b.pull(gap_frames);
|
||||
let before = frames_written;
|
||||
frames_written += write_wav_bytes(w, &silence, &session.format, &mic)?;
|
||||
if let Some(ma) = mic_activity {
|
||||
ma.mark(before, &mic);
|
||||
}
|
||||
last_write = Instant::now();
|
||||
}
|
||||
}
|
||||
@@ -793,7 +808,11 @@ fn capture_loop(
|
||||
Some(b) if is_loopback => b.pull(frame_count(&bytes, &session.format)),
|
||||
_ => Vec::new(),
|
||||
};
|
||||
let before = frames_written;
|
||||
frames_written += write_wav_bytes(w, &bytes, &session.format, &mic)?;
|
||||
if let Some(ma) = mic_activity {
|
||||
ma.mark(before, &mic);
|
||||
}
|
||||
}
|
||||
let mono = decode_mono_f32(&bytes, &session.format)?;
|
||||
|
||||
@@ -1603,6 +1622,7 @@ mod tests {
|
||||
loop_tx,
|
||||
ev_tx,
|
||||
bridge.clone(),
|
||||
None,
|
||||
) {
|
||||
Ok(h) => h,
|
||||
Err(e) => {
|
||||
|
||||
Reference in new issue
Block a user