fix(recorder): 显式 pause cpal Stream 再 drop,释放 macOS 麦克风占用

用户报告:按热键结束录音后麦克风没有被释放(橙色 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 是否真的执行。
This commit is contained in:
baiqing committed 2026-05-07 16:33:12 +08:00
1 parent 43f95a0888
commit 2edaf522cd
1 file changed
+11 -1
+11 -1
View File
@@ -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 {