From 2edaf522cdfaebecf594b1c74e9462d0fa1dd22a Mon Sep 17 00:00:00 2001 From: baiqing Date: Thu, 7 May 2026 16:33:12 +0800 Subject: [PATCH] =?UTF-8?q?fix(recorder):=20=E6=98=BE=E5=BC=8F=20pause=20c?= =?UTF-8?q?pal=20Stream=20=E5=86=8D=20drop=EF=BC=8C=E9=87=8A=E6=94=BE=20ma?= =?UTF-8?q?cOS=20=E9=BA=A6=E5=85=8B=E9=A3=8E=E5=8D=A0=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 用户报告:按热键结束录音后麦克风没有被释放(橙色 mic 指示灯不灭、 process_callback 持续打 cb# 日志直到进程退出)。 根因:cpal 0.15 的 Stream::drop 在 macOS coreaudio backend 上不会同步 调 AudioOutputUnitStop,只走 dispose。AudioUnit 的 render callback 仍被 系统调用,process_callback 以 ~5 ms / 帧的速率继续推到 consumer 和 level_handler;macOS 一直认为我们在用 mic,橙点不灭,其它 app 无法独占。 实测日志: T=01.786 hotkey 按下 → end_session 启动 T=02.699 audio-mute released by dictation (rec.stop() 已返回 = 音频 线程已 join 完成 = drop(stream) 已执行) T=02.720 cb#750 ← 仍在跑 T=03.110 cb#800 … T=09.510 cb#2000 ← 直到用户杀进程才停 修复:在 drop(stream) 之前显式 stream.pause()。 StreamTrait::pause 在 cpal coreaudio backend 里直接调 AudioOutputUnitStop, 同步终止 callback;之后 drop 处理 dispose / 资源释放。pause 失败时仅打 warn,不阻塞 drop 路径。 同时在 drop 之后加一行 "cpal Stream dropped (mic released)" 日志, 让以后排查这类问题能直接看到 drop 是否真的执行。 --- openless-all/app/src-tauri/src/recorder.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/openless-all/app/src-tauri/src/recorder.rs b/openless-all/app/src-tauri/src/recorder.rs index 35b44788..e2f6aba1 100644 --- a/openless-all/app/src-tauri/src/recorder.rs +++ b/openless-all/app/src-tauri/src/recorder.rs @@ -234,8 +234,18 @@ fn run_audio_thread( thread::sleep(std::time::Duration::from_millis(50)); } - // Stream 在 drop 时自动停止。 + // 显式 pause 再 drop。 + // 实测 cpal 0.15 在 macOS coreaudio 上单纯 drop(Stream) 不会同步调用 + // AudioOutputUnitStop,AudioUnit 的 render callback 会继续被系统调, + // process_callback 仍然以 ~5 ms / 帧的速率打 cb# 日志,macOS 一直认为 + // 我们在用 mic(橙点不灭)。pause() 走的是 StreamTrait::pause —— 在 + // coreaudio backend 里直接 AudioOutputUnitStop,同步终止 callback。 + // 之后 drop 处理 dispose / 资源释放。pause 失败时仅 warn,不阻塞 drop。 + if let Err(err) = stream.pause() { + log::warn!("[recorder] cpal Stream pause before drop failed: {err}"); + } drop(stream); + log::info!("[recorder] cpal Stream dropped (mic released)"); // 等待 watchdog 线程退出 if let Some(handle) = watchdog_handle {