[本地语音降噪]:完成本地语音链路和噪音过滤,包含降噪模型、本地ASR和实时字幕稳定策略

This commit is contained in:
mkbk
2026-06-17 22:55:33 +08:00
parent a77a172412
commit 8c75fc5baf
22 changed files with 803 additions and 64 deletions
@@ -31,12 +31,14 @@ run-live
-> SoundDeviceAudioTransport
-> SherpaOnnxKeywordWakeWordProvider(local models/wake)
-> AcknowledgeStage(local "我在")
-> CaptureStage(primary speaker endpoint by default)
-> CloudAsrSttProvider or SherpaOnnxSttProvider
-> CaptureStage(raw mic frames)
-> AudioPreprocessStage(local GTCRN denoise, capture only)
-> PrimarySpeakerEndpoint/VAD(denoised frames)
-> SherpaOnnxSttProvider(local CTC partial/final by default)
-> DialogStage(session memory)
-> ConversationContext
-> OpenAICompatibleLlmProvider
-> CloudTtsProvider or MacSayTtsProvider
-> MacSayTtsProvider
-> speaker playback
```
@@ -51,11 +53,12 @@ run-live
7. On wake hit, reset wake stream and VAD recorder.
8. Record user utterance with VAD. The default provider is `hybrid`: project-local `sherpa-onnx` VAD remains the primary detector, and an energy threshold fallback prevents low microphone gain from being treated as no speech.
9. When `OWNER_ENDPOINT_MODE=primary_speaker`, build a temporary per-turn speaker profile from the first valid user speech frames and end the capture when the primary speaker is absent for `OWNER_SPEAKER_ABSENT_MS`.
10. Transcribe user utterance.
11. Emit transcript event to terminal.
12. Append user text and call LLM.
13. Synthesize/play reply.
14. Append assistant reply and return to standby.
10. When `OWNER_NOISE_FILTER_ENABLED=1`, pass formal user utterance frames through the local GTCRN denoiser before VAD, realtime STT, and final STT segment assembly.
11. Transcribe user utterance with the configured STT provider. The default is local CTC STT; cloud STT remains an explicit compatibility option.
12. Emit transcript event to terminal.
13. Append final user text and call LLM.
14. Synthesize/play reply with local TTS by default.
15. Append assistant reply and return to standby.
## Interfaces
@@ -88,6 +91,29 @@ class PipelineEventBus:
Required event types are `pipeline_started`, `wake_listening`, `wake_detected`, `ack_started`, `capture_started`, `speech_started`, `speech_ended`, `stt_started`, `transcript_final`, `llm_started`, `tts_started`, `playback_finished`, `standby_resumed`, and `stage_error`.
### `AudioPreprocessor`
```python
class AudioPreprocessor:
def load(self) -> None: ...
def reset(self) -> None: ...
def process_frame(self, frame: AudioFrame) -> AudioFrame: ...
def flush(self) -> list[AudioFrame]: ...
```
`NoopAudioPreprocessor` preserves tests and disabled configurations. `SherpaOnnxDenoiserPreprocessor` uses `sherpa_onnx.OnlineSpeechDenoiser` with an `OfflineSpeechDenoiserGtcrnModelConfig` pointing to `models/denoise/gtcrn_simple.onnx`. It converts int16 PCM to float32, calls `run(samples, sample_rate)`, converts returned `DenoisedAudio.samples` back to int16 PCM, and adds diagnostic metadata without storing audio.
The first version applies preprocessing only in capture. Wake listening remains raw by default because wake KWS models can be sensitive to spectral changes introduced by denoising. `OWNER_WAKE_DENOISE_ENABLED=1` reserves an explicit future switch for wake preprocessing.
### Local CTC STT
`SherpaOnnxSttProvider` reads `providers.stt.type` from `models/manifest.json`:
1. `sherpa-onnx-streaming-transducer`: existing `tokens`/`encoder`/`decoder`/`joiner` files and `OnlineRecognizer.from_transducer`.
2. `sherpa-onnx-streaming-zipformer2-ctc`: new default `tokens`/`model` files and `OnlineRecognizer.from_zipformer2_ctc`.
Realtime partial and final transcript use the same local recognizer when `OWNER_SPEECH_PROVIDER=local`. Partial output is filtered before emitting `transcript_partial`: texts with fewer than two meaningful characters, duplicate texts, and short regressions from the last displayed text are ignored. The final transcript remains the only user text appended to `ConversationContext`.
### `TurnController`
```python
@@ -136,7 +162,11 @@ models/
vad/
silero_vad.onnx
stt/
sherpa-onnx-streaming-zipformer-zh-14M-2023-02-23/
sherpa-onnx-streaming-zipformer-ctc-zh-int8-2025-06-30/
tokens.txt
model.int8.onnx
denoise/
gtcrn_simple.onnx
```
## Error Handling
@@ -152,6 +182,10 @@ models/
9. Playback drain misconfiguration: negative `OWNER_POST_PLAYBACK_DRAIN_MS` remains invalid; non-zero values are treated as explicit user tuning rather than default behavior.
10. Speaker profile threshold misconfiguration: non-positive `OWNER_SPEAKER_PROFILE_MIN_MS` fails config validation.
11. Realtime STT failure: startup model缺失按 `model-check` 暴露;capture 中 partial session 失败不得把 partial 文本写入上下文。
12. Denoiser missing model: `model-check` reports `denoise/gtcrn_simple.onnx` and live startup fails before microphone listening.
13. Denoiser runtime failure: the current turn emits `stage_error` and recovers to standby without invoking final STT or LLM.
14. CTC manifest mismatch: missing `model` or `tokens` files report `STT_MODEL_MISSING`; transducer manifests remain supported for existing local setups.
15. Partial text noise: single-character or transient partial output is ignored rather than displayed as terminal feedback.
## Testing Strategy
@@ -168,6 +202,11 @@ models/
11. Transport batching test validates that queued SoundDevice frames are returned together.
12. Partial transcript event test validates that realtime text appears after speech start and before final transcript.
13. Context isolation test validates partial transcript does not enter LLM messages.
14. Denoiser manifest/model-check test validates the required GTCRN file is present.
15. Fake denoiser test validates VAD, partial STT, and final STT receive denoised frames from the same processed stream.
16. Local speech provider test validates `OWNER_SPEECH_PROVIDER=local` does not construct cloud ASR/TTS providers.
17. Partial filter test validates single-character and short transient results are not emitted.
18. CTC STT loading test validates manifest type selects `from_zipformer2_ctc` and does not require transducer encoder/decoder/joiner files.
## Migration
@@ -179,3 +218,11 @@ python3.11 scripts/download_speech_models.py --dir models
```
Existing `.env` remains valid because new wake keys have defaults.
For the local voice-chain revision, users should also ensure:
```dotenv
OWNER_SPEECH_PROVIDER=local
OWNER_NOISE_FILTER_ENABLED=1
OWNER_WAKE_DENOISE_ENABLED=0
OWNER_NOISE_FILTER_PROVIDER=sherpa_onnx_gtcrn
```
@@ -52,6 +52,10 @@
10. 实时消费问题:`SoundDeviceAudioTransport.read_frames()` 每次只返回一个队列帧,在音频回调批量积压时会增加 pipeline 对真实麦克风流的追帧成本。
11. 画像门槛问题:`PrimarySpeakerVadRecorder._profile_ready()` 把主说话人画像就绪阈值绑定到 `OWNER_VAD_MIN_DURATION_MS`,默认至少等待 250 ms 后主说话人端点才参与结束判断,短句用户会被迫等普通 VAD 静音或重复说话。
12. 实时转写问题:当前终端只在整段录音结束并完成 final STT 后显示“转写结果”,用户说话期间看不到任何文字反馈,无法判断系统是否已经听到并识别当前句子。
13. 实时字幕质量问题:真人日志中 `实时转写:家``实时转写:家确` 来自旧本地 14M streaming STT partial,最终云端 ASR 虽然较准,但用户看到的实时反馈会被单字噪声和短暂跳变污染。
14. 语音链路一致性问题:当前 `OWNER_SPEECH_PROVIDER=cloud` 时 final STT/TTS 走云端,而 partial 走本地模型;同一轮对话里 partial 和 final 来自不同模型,容易出现“实时字幕和最终转写明显冲突”的体验。
15. 降噪缺失问题:正式问题录音直接把原始麦克风帧送入 VAD、partial STT 和 final STT,背景噪声会同时影响端点、实时字幕和最终识别。
16. 本地模型落后问题:默认 STT 仍是 2023 年 14M 小模型,适合最小验收但不适合作为默认实时字幕质量基线;应升级为 sherpa-onnx 官方 2025 中文 CTC int8 模型。
## 详细需求
@@ -78,6 +82,15 @@
19. 录音期间 SHALL 支持 partial transcript 事件;当本地 streaming STT 产生新的中间文本时,终端 SHALL 立即显示 `实时转写:<文本>`
20. partial transcript SHALL 只作为用户可见反馈,不得直接写入对话上下文;LLM 输入仍以最终 `transcript_final` 文本为准。
21.`OWNER_SPEECH_PROVIDER=cloud` 时,partial transcript SHALL 使用本地 streaming STT,避免对云端 ASR 进行高频请求。
22. 下一版默认语音链路 SHALL 改为“除 LLM 外全本地”:wake、VAD、STT、partial transcript、TTS、噪音过滤均在本机执行;LLM 仍走配置中的云端 OpenAI-compatible endpoint。
23. `.env.example` 和代码默认 SHALL 将 `OWNER_SPEECH_PROVIDER` 设为 `local`,从而默认 final STT 使用本地 sherpa-onnx 模型,TTS 使用 macOS 本地 `say/afplay` provider。
24. 模型 manifest SHALL 默认使用 sherpa-onnx 官方 2025 中文 Zipformer2 CTC int8 模型,关键文件为 `tokens.txt``model.int8.onnx`,不再以旧 14M transducer 作为默认 STT 模型。
25. 模型 manifest SHALL 新增 GTCRN/sherpa-onnx speech denoiser 模型 `denoise/gtcrn_simple.onnx``download_speech_models.py``model-check` 必须把它列为 required file。
26. Pipeline SHALL 新增 `AudioPreprocessStage`。默认 `OWNER_NOISE_FILTER_ENABLED=1` 时,唤醒后的正式问题采集必须先对音频帧降噪,再把同一份降噪后帧送入 VAD、实时 partial STT 和 final STT segment。
27. wake 阶段默认 SHALL 继续使用原始音频帧,避免 denoiser 改变 KWS 特征;仅当用户显式设置 `OWNER_WAKE_DENOISE_ENABLED=1` 时才允许对 wake 帧预处理。
28. 降噪 provider 失败 SHALL 作为结构化可恢复错误进入 `stage_error -> recovering -> standby`,不得把未经标记的半处理音频写入对话上下文。
29. partial transcript SHALL 增加稳定过滤:不得显示单个中文/英文有效字符,不得重复显示同一文本,不得把极短的瞬态跳变作为终端实时字幕输出。
30. final transcript SHALL 是唯一进入 LLM 的用户文本;降噪帧、partial 文本、denoiser metadata 和 ASR raw metadata 均不得进入 `ConversationContext`
### 非功能需求
@@ -88,6 +101,9 @@
5. 测试性:自动化测试必须能注入 fake wake provider,不依赖真实麦克风或真实 KWS 模型。
6. 架构可观测性:所有用户可见状态必须来自 pipeline event bus,终端 reporter 和后续 GUI 只消费事件,不直接嵌入 stage 逻辑。
7. 端点性能:默认配置下,主说话人音色消失后 300 ms 左右应结束采集,并进入 STT;最大录音时长仍作为兜底。
8. 实时字幕质量:本地 partial 默认不显示 1 个有效字符以内的文本,避免 `家``嗯``啊` 这类背景噪声触发可见字幕;最终字幕仍由 final STT 决定。
9. 语音隐私:除 LLM 请求文本外,正式问题原始音频、降噪音频、VAD 特征和临时音色画像均不得上传云端、不得落盘。
10. 降噪性能:GTCRN online denoiser 只能在 capture 阶段运行,目标是不明显拖慢端点;如果 denoiser 不可用,启动/模型检查必须明确失败,而不是静默回退为无降噪。
### 边缘案例
@@ -100,6 +116,11 @@
7. 播放回声误触发:播放期间仍遵循既有音频反馈抑制要求,不能把 TTS 当作新 wake。
8. 背景噪声拖尾:用户停止说话后若仍有非主说话人或噪声,主说话人端点必须允许结束录音。
9. 音色画像不足:如果开头音频太短或能量不足,采集阶段必须回退到普通 VAD 静音端点,不能卡死。
10. 降噪模型缺失:`model-check` 必须报告 `denoise/gtcrn_simple.onnx` 缺失;`run-live` 启动时不得进入“看似可用但未降噪”的状态。
11. 降噪运行时异常:如果 GTCRN provider 在某帧处理失败,当前 turn 必须结构化报错并恢复待机;不得把可能损坏的片段送入 STT 或 LLM。
12. partial 单字误识别:本地 streaming STT 输出 `家``确``a` 等单字时,终端不得显示为 `实时转写`
13. partial 瞬态跳变:本地 streaming STT 从“你是谁”短暂跳到“加”再回到“你是谁”时,不得显示中间极短跳变。
14. CTC 模型缺旧 transducer 文件:当 manifest 类型为 `sherpa-onnx-streaming-zipformer2-ctc` 时,不应再要求 encoder/decoder/joiner 文件存在。
### 输入输出规格
@@ -120,6 +141,10 @@
13. `OWNER_POST_PLAYBACK_DRAIN_MS=0`:ACK 或 TTS 播放完成后只 flush 已积压输入,不额外读取并丢弃新音频。
14. `OWNER_SPEAKER_PROFILE_MIN_MS=120`:主说话人画像参与端点判断的最低有效语音长度。
15. `OWNER_REALTIME_TRANSCRIPT_ENABLED=1`:启用录音期间本地 streaming STT 中间结果显示。
16. `OWNER_SPEECH_PROVIDER=local`:默认本地 STT/TTS,云端只保留 LLM。
17. `OWNER_NOISE_FILTER_ENABLED=1`:正式问题阶段默认启用本地降噪。
18. `OWNER_WAKE_DENOISE_ENABLED=0`wake 阶段默认不启用降噪。
19. `OWNER_NOISE_FILTER_PROVIDER=sherpa_onnx_gtcrn`:第一版本地降噪 provider。
终端输出:
@@ -147,12 +172,15 @@ SoundDeviceAudioTransport
-> LocalWakeWordProvider(sherpa-onnx KeywordSpotter, models/wake, keywords.txt)
-> wake_hit
-> AcknowledgeStage("我在")
-> CaptureStage(primary speaker endpoint, user utterance only)
-> SttProvider(cloud or local, configured by OWNER_SPEECH_PROVIDER)
-> CaptureStage(raw user utterance only)
-> AudioPreprocessStage(sherpa-onnx GTCRN denoise, capture only)
-> PrimarySpeakerEndpoint + VAD(denoised frames)
-> RealtimeSttProvider(local CTC, denoised frames, stable partial filter)
-> SttProvider(local CTC by default, denoised final segment)
-> PipelineEvent(transcript_final)
-> ConversationContext(temporary process history)
-> Cloud LLM
-> TTS
-> Local TTS(MacSayTtsProvider)
-> Speaker
-> standby
```
@@ -168,6 +196,9 @@ SoundDeviceAudioTransport
7. Runtime 将 `user_text` 追加到临时上下文并调用 LLM。
8. TTS 播放后追加 assistant 历史并恢复待机。
9. 所有 stage 同步发出 pipeline events;终端 reporter 只把事件映射为中文文案。
10. 正式问题 capture 收到原始麦克风帧后,先调用 `AudioPreprocessor.process_frame(frame)` 得到降噪帧。
11. VAD、主说话人端点、partial streaming STT 和 final STT segment builder 必须使用同一份降噪帧,保证用户看到的 realtime partial 和最终转写来自一致音频来源。
12. wake listening 默认使用原始帧;后续仅在 `OWNER_WAKE_DENOISE_ENABLED=1` 时把同一预处理接口接到 wake 阶段。
### 接口定义
@@ -192,6 +223,28 @@ TurnController.run_turn(turn_id: int) -> TurnResult
VoiceAssistantPipeline.run(once: bool = False, max_turns: int | None = None) -> RuntimeSummary
```
```text
AudioPreprocessor.load() -> None
AudioPreprocessor.reset() -> None
AudioPreprocessor.process_frame(frame: AudioFrame) -> AudioFrame
AudioPreprocessor.flush() -> list[AudioFrame]
```
```text
SherpaOnnxDenoiserPreprocessor(
models_dir: Path,
provider: "sherpa_onnx_gtcrn",
enabled: bool,
sherpa_module: object | None = None,
)
```
```text
SherpaOnnxSttProvider.load()
manifest type "sherpa-onnx-streaming-transducer" -> OnlineRecognizer.from_transducer(...)
manifest type "sherpa-onnx-streaming-zipformer2-ctc" -> OnlineRecognizer.from_zipformer2_ctc(tokens, model, ...)
```
```text
SherpaOnnxKeywordWakeWordProvider(
models_dir: Path,
@@ -228,6 +281,9 @@ standby
7. 批量读帧:真实 SoundDevice 输入在拿到首帧后立即 drain 当前队列中所有可用帧并返回给 pipeline,使 wake、capture 和 VAD 能在同一个循环内处理积压帧。
8. 快速主说话人结束:画像就绪最低语音长度由 `OWNER_SPEAKER_PROFILE_MIN_MS` 控制,默认 120 ms;一旦画像就绪,主说话人缺席计时达到 `OWNER_SPEAKER_ABSENT_MS` 即结束,不再叠加 `OWNER_VAD_MIN_DURATION_MS`
9. 实时转写显示:CaptureStage 在 `speech_started` 后把已录入的帧同时送入本地 streaming STT session;每当 partial 文本变化时发出 `transcript_partial`,终端显示 `实时转写:<文本>`;最终段落仍交给 configured STT provider 生成 `transcript_final`
10. 捕获阶段降噪:`AudioPreprocessStage` 将 int16 PCM 转为 float32,调用 `sherpa_onnx.OnlineSpeechDenoiser.run(samples, sample_rate)`,把返回的 `DenoisedAudio.samples` 转回 int16 PCM,并在 metadata 中加入 `denoised=True``noise_filter_provider=sherpa_onnx_gtcrn`
11. CTC STT 加载:manifest `stt.type=sherpa-onnx-streaming-zipformer2-ctc` 时,只校验 `tokens``model`,调用 `OnlineRecognizer.from_zipformer2_ctc`;旧 manifest 仍兼容 transducer 路径。
12. partial 稳定过滤:实时字幕会先去除空白和标点,计算有效字符数;有效字符数小于 2 的文本直接忽略;若新文本比上一次已显示文本短且不构成稳定前缀推进,也忽略,避免把瞬时噪声显示给用户。
### 数据库/状态管理变更
@@ -246,6 +302,9 @@ standby
2. 新增 KWS 模型约 15 MB,下载到 ignored `models/wake/`
3. 不新增 Python 运行依赖。
4. `model-check` 变严格:缺 wake 模型会失败。
5. 默认 STT 模型升级为 2025 中文 CTC int8,模型体积和加载时间高于旧 14M 小模型,但换来更稳定的本地 partial/final 一致性。
6. 新增 GTCRN denoiser 单文件模型,下载到 ignored `models/denoise/gtcrn_simple.onnx`
7. 继续使用已安装的 `sherpa-onnx` 包;不新增 Python 依赖,不接入云 ASR/TTS 作为默认路径。
## 风险与权衡
@@ -253,7 +312,7 @@ standby
| --- | --- | --- | --- |
| KWS 关键词拼音格式不匹配模型 | 中 | 高 | 默认写入 sherpa 示例格式;保留 keywords 文件可编辑;测试下载后用真实模型加载;README 标明关键词文件位置 |
| KWS 模型误唤醒或漏唤醒 | 中 | 中 | 暴露 threshold/score 配置;保留状态输出;后续可替换 KWS Provider |
| 唤醒应答期间用户抢说被缓冲清理吞掉 | 中 | 高 | 终端提示顺序改为“唤醒命中 -> 应答中 -> 请说出问题 -> 录音中”,用户只在应答完成后收到提问提示;默认播放后排水从 250 ms 降为 50 ms |
| 唤醒应答期间用户抢说被缓冲清理吞掉 | 中 | 高 | 终端提示顺序改为“唤醒命中 -> 应答中 -> 请说出问题 -> 录音中”,用户只在应答完成后收到提问提示;默认 `OWNER_POST_PLAYBACK_DRAIN_MS=0`,不再额外读取并丢弃播放后的新音频 |
| 本地 VAD 对真实麦克风音量过保守 | 中 | 高 | 默认 VAD provider 改为 `hybrid`,本地模型判断和能量阈值兜底任一命中即认为有语音;保留 `local``energy` 可配置回退 |
| 能量兜底让录音无法及时结束 | 高 | 高 | 将能量兜底限制为“开始录音辅助”,结束录音优先使用主说话人音色消失和本地 VAD 静音 |
| ACK 后额外丢弃音频截断首句 | 高 | 高 | 默认 `OWNER_POST_PLAYBACK_DRAIN_MS=0`;播放结束后只 flush 已积压输入;新增首句保留回归测试 |
@@ -268,6 +327,11 @@ standby
| 终端转写不是逐字流式 | 中 | 低 | 第一版至少在 LLM 前即时显示最终 STT 文本;后续可接入本地 streaming STT partial |
| 模型下载网络失败 | 中 | 中 | 下载脚本保留重试;`model-check` 给明确缺失文件 |
| 误提交模型或 key | 低 | 高 | `.gitignore`、security-check、提交前 `git status --short` |
| 降噪模型 API 与当前 sherpa-onnx 版本不一致 | 中 | 高 | 用本机 `sherpa_onnx 1.13.3` introspection 确认 `OnlineSpeechDenoiser` 构造;新增 fake module 单测;model-check 尝试加载 provider |
| GTCRN 对 wake 特征造成误伤 | 中 | 中 | wake 默认不走降噪,仅正式问题阶段降噪;保留 `OWNER_WAKE_DENOISE_ENABLED=1` 作为显式实验开关 |
| CTC 模型比旧 14M 模型更大导致下载慢 | 中 | 中 | 仍放 `models/` 并跳过已存在文件;README 明确首次下载较久;下载脚本保留重试 |
| partial 过滤过严导致实时字幕少显示 | 中 | 低 | 过滤只影响用户可见 partial,不影响 final STT 和 LLM;后续可配置更细阈值 |
| 降噪失败后用户无法继续本轮 | 低 | 中 | 当前 turn 结构化失败并恢复待机,避免错误音频进入 LLM;下一轮可继续唤醒 |
## 任务分解
@@ -322,6 +386,17 @@ standby
- [ ] 7.5 更新 README、`.env.example`、本地 `.env` 非密钥配置;前置条件:7.2 至 7.4 完成;验收标准:运行说明匹配新 pipeline;测试要点:`--show-config` 不泄露 key;优先级:P0;预计:30 分钟。
- [ ] 7.6 验证并提交“Pipeline 文档验收”模块;前置条件:7.1 至 7.5 完成;验收标准:compileall、unittest、security-check、model-check、device-check、OpenSpec strict 全通过;优先级:P0;预计:30 分钟。
### 10. 本地语音链路、高质量实时转写与噪音过滤
- [ ] 10.1 更新 OpenSpec 描述本地语音链路和降噪阶段;前置条件:真人日志确认旧本地 14M partial 会产生 `家/家确` 等噪声误识别;验收标准:proposal/design/spec/tasks 明确 wake/VAD/STT/partial/TTS/denoise 全本地、LLM 云端、正式问题阶段默认降噪;测试要点:OpenSpec strict;优先级:P0;预计:45 分钟。
- [ ] 10.2 升级 speech model manifest 和下载脚本;前置条件:确认 sherpa-onnx 官方 2025 中文 CTC int8 模型和 GTCRN denoiser URL;验收标准:`models/manifest.json` 默认 STT 指向 CTC `model.int8.onnx`,新增 `denoise/gtcrn_simple.onnx` required file;测试要点:manifest、required files、下载脚本单测/真实下载;优先级:P0;预计:60 分钟。
- [ ] 10.3 实现 `AudioPreprocessStage`;前置条件:10.2 完成;验收标准:新增可注入的 no-op 和 sherpa-onnx GTCRN online denoiser provider,正式问题帧在进入 VAD、partial STT、final STT 前共用同一份降噪后音频;测试要点:fake denoiser 验证 capture/STT 收到降噪后帧;优先级:P0;预计:60 分钟。
- [ ] 10.4 扩展本地 STT Provider 支持 CTC 模型和 partial 稳定过滤;前置条件:10.2 完成;验收标准:`SherpaOnnxSttProvider` 可按 manifest 加载 transducer 或 zipformer2 CTCpartial 不显示单字噪声和短暂跳变;测试要点:fake sherpa CTC、single-char partial filter、transient partial filter;优先级:P0;预计:60 分钟。
- [ ] 10.5 修改 runtime 默认本地语音链路;前置条件:10.3 至 10.4 完成;验收标准:默认 `OWNER_SPEECH_PROVIDER=local`TTS 使用 `MacSayTtsProvider`,云端只用于 LLM`--show-config` 显示噪音过滤配置且不泄露 key;测试要点:build runtime provider 类型、cloud ASR/TTS 不被调用;优先级:P0;预计:45 分钟。
- [ ] 10.6 更新 README、`.env.example` 和本地 `.env` 非密钥配置;前置条件:10.5 完成;验收标准:中文运行说明包含本地语音默认值、降噪开关、模型下载、model-check、run-live 验收;测试要点:命令可复制,`.env` 不进入提交;优先级:P0;预计:35 分钟。
- [ ] 10.7 下载/校验新本地模型并执行门禁;前置条件:10.2 至 10.6 完成;验收标准:下载脚本、compileall、unittest、security-check、model-check、device-check、OpenSpec strict、git diff check 通过或记录真实设备失败原因;测试要点:输出不泄露 key,模型文件不进 Git;优先级:P0;预计:60 分钟。
- [ ] 10.8 提交“本地语音降噪”模块;前置条件:10.7 门禁通过;验收标准:中文 commit 信息为 `[本地语音降噪]:完成本地语音链路和噪音过滤,包含降噪模型、本地ASR和实时字幕稳定策略`,提交后除本地 `.env` 非提交修改外无未提交源码/文档中间状态;优先级:P0;预计:10 分钟。
## Spec Deltas
### 新增能力
@@ -340,6 +415,7 @@ standby
8. `Primary speaker endpointing`:新增本轮临时主说话人音色消失结束录音要求。
9. `Low latency capture and first utterance preservation`:新增 ACK 后不额外丢弃正式问题、批量读帧、独立画像就绪阈值和快速主说话人端点要求。
10. `Realtime partial transcript output`:新增录音期间 partial transcript 事件、终端显示和上下文隔离要求。
11. `Local voice chain and noise filtering`:新增本地 STT/TTS 默认、GTCRN 降噪、CTC 模型、partial 稳定过滤和音频不上传要求。
### 删除项
@@ -359,6 +435,7 @@ standby
6. M6Stage 化 pipeline、事件总线、TurnController、主说话人端点和文档验收分模块提交。
7. M7:低延迟端点与首句保留修正完成后提交,保留真人 `run-live` 验收任务,不在用户确认前归档。
8. M8:录音期间实时转写显示完成后提交,保留真人 `run-live` 验收任务,不在用户确认前归档。
9. M9:本地语音链路、高质量实时字幕和正式问题降噪完成后提交,继续保留真人 `run-live` 验收任务,不在用户确认前归档。
估时:
@@ -64,6 +64,47 @@ The live runtime SHALL display recognized user utterance text in the terminal bo
- **WHEN** STT returns empty text, punctuation-only text, or an invalid transcript
- **THEN** the runtime SHALL NOT emit a misleading transcript as valid user input and SHALL recover to standby without invoking the LLM
### Requirement: Local voice chain and noise filtering
The live runtime SHALL run wake, VAD, STT, realtime transcript, TTS, and capture-stage noise filtering locally by default, while continuing to send only the final user text and session history to the configured cloud LLM.
#### Scenario: Default speech provider is local
- **WHEN** the user starts `run-live` without overriding `OWNER_SPEECH_PROVIDER`
- **THEN** the runtime SHALL use local sherpa-onnx STT and local macOS TTS
- **AND** it SHALL NOT call cloud ASR or cloud TTS providers during the turn
#### Scenario: Capture audio is denoised before downstream stages
- **WHEN** `OWNER_NOISE_FILTER_ENABLED=1` and the user speaks after wake acknowledgement
- **THEN** the capture stage SHALL pass microphone frames through the configured local denoiser before VAD analysis, realtime partial STT, and final STT segment assembly
- **AND** all three downstream consumers SHALL observe the same denoised frame content for a given captured frame
#### Scenario: Wake remains raw by default
- **WHEN** `OWNER_WAKE_DENOISE_ENABLED` is unset or `0`
- **THEN** wake word detection SHALL receive raw microphone frames rather than denoised frames
#### Scenario: Denoiser model is missing
- **WHEN** `models/denoise/gtcrn_simple.onnx` is absent
- **THEN** `model-check` SHALL fail with a structured missing model error
- **AND** `run-live` SHALL NOT enter live microphone listening as if noise filtering were available
#### Scenario: Denoiser runtime fails
- **WHEN** the local denoiser raises an error while processing a formal user utterance frame
- **THEN** the current turn SHALL emit a stage error and recover to standby
- **AND** the runtime SHALL NOT invoke final STT, LLM, or TTS for that failed turn
#### Scenario: Local ASR uses the 2025 CTC model by default
- **WHEN** the default manifest is written by `download_speech_models.py`
- **THEN** the STT provider SHALL be declared as `sherpa-onnx-streaming-zipformer2-ctc`
- **AND** required STT files SHALL be `tokens.txt` and `model.int8.onnx`, not transducer encoder/decoder/joiner files
#### Scenario: Realtime partial filters noise
- **WHEN** local streaming STT emits a single meaningful character or a short transient regression
- **THEN** the terminal reporter SHALL NOT display that text as `实时转写`
- **AND** final STT SHALL remain responsible for the text sent to the LLM
#### Scenario: Audio privacy is preserved
- **WHEN** local noise filtering and local STT run during capture
- **THEN** raw PCM, denoised PCM, VAD features, partial transcript metadata, and temporary speaker profile data SHALL remain process-local and SHALL NOT be sent to the cloud LLM
### Requirement: Wake acknowledgement before recording
The live runtime SHALL provide an audible local acknowledgement after local wake detection and before it starts recording the user's formal question.
@@ -120,14 +161,14 @@ The system SHALL provide project-local speech model preparation and diagnostics
#### Scenario: Models are downloaded
- **WHEN** the user runs `python3.11 scripts/download_speech_models.py --dir models`
- **THEN** the script SHALL create or update a project-local model directory with the files required by the configured wake, VAD, and STT providers
- **THEN** the script SHALL create or update a project-local model directory with the files required by the configured wake, VAD, STT, and denoiser providers
#### Scenario: Model check succeeds
- **WHEN** required wake, VAD, STT dependencies and model files are available
- **WHEN** required wake, VAD, STT, denoiser dependencies and model files are available
- **THEN** `PYTHONPATH=src python3.11 -m owner_voice_pet model-check` SHALL exit successfully and report the model directory and checked providers
#### Scenario: Model check fails
- **WHEN** `sherpa-onnx` is unavailable, a wake model file is missing, a VAD/STT model file is missing, or a model cannot be loaded
- **WHEN** `sherpa-onnx` is unavailable, a wake model file is missing, a VAD/STT/denoiser model file is missing, or a model cannot be loaded
- **THEN** `model-check` SHALL fail with a structured model error and SHALL NOT start live microphone listening
### Requirement: Live repeat voice runtime
@@ -70,3 +70,14 @@
- [x] 9.3 实现本地 streaming STT partial provider;前置条件:本地 STT 模型已由 `model-check` 覆盖;验收标准:`SherpaOnnxSttProvider` 支持 streaming sessioncloud final ASR 模式下仍使用本地 streaming STT 做 partial;测试要点:fake session 与 metadata partial 单测;优先级:P0;预计:60 分钟。
- [x] 9.4 接入 `VoiceAssistantPipeline` 捕获循环;前置条件:9.2 至 9.3 完成;验收标准:用户说话期间持续 feed 已开始录音的帧,partial 在 `speech_started` 后、`transcript_final` 前输出;测试要点:live runtime partial 顺序测试;优先级:P0;预计:45 分钟。
- [x] 9.5 补充配置、README、门禁并提交;前置条件:9.1 至 9.4 完成;验收标准:新增 `OWNER_REALTIME_TRANSCRIPT_ENABLED=1`compileall、unittest、security-check、model-check、device-check、OpenSpec strict 通过后中文 commit;优先级:P0;预计:45 分钟。
## 10. 本地语音链路、高质量实时转写与噪音过滤
- [x] 10.1 更新 OpenSpec 描述本地语音链路和降噪阶段;前置条件:真人日志确认旧本地 14M partial 会产生 `家/家确` 等噪声误识别;验收标准:proposal/design/spec/tasks 明确 wake/VAD/STT/partial/TTS/denoise 全本地、LLM 云端、正式问题阶段默认降噪;测试要点:OpenSpec strict;优先级:P0;预计:45 分钟。
- [x] 10.2 升级 speech model manifest 和下载脚本;前置条件:确认 sherpa-onnx 官方 2025 中文 CTC int8 模型和 GTCRN denoiser URL;验收标准:`models/manifest.json` 默认 STT 指向 CTC `model.int8.onnx`,新增 `denoise/gtcrn_simple.onnx` required file;测试要点:manifest、required files、下载脚本单测/真实下载;优先级:P0;预计:60 分钟。
- [x] 10.3 实现 `AudioPreprocessStage`;前置条件:10.2 完成;验收标准:新增可注入的 no-op 和 sherpa-onnx GTCRN online denoiser provider,正式问题帧在进入 VAD、partial STT、final STT 前共用同一份降噪后音频;测试要点:fake denoiser 验证 capture/STT 收到降噪后帧;优先级:P0;预计:60 分钟。
- [x] 10.4 扩展本地 STT Provider 支持 CTC 模型和 partial 稳定过滤;前置条件:10.2 完成;验收标准:`SherpaOnnxSttProvider` 可按 manifest 加载 transducer 或 zipformer2 CTCpartial 不显示单字噪声和短暂跳变;测试要点:fake sherpa CTC、single-char partial filter、transient partial filter;优先级:P0;预计:60 分钟。
- [x] 10.5 修改 runtime 默认本地语音链路;前置条件:10.3 至 10.4 完成;验收标准:默认 `OWNER_SPEECH_PROVIDER=local`TTS 使用 `MacSayTtsProvider`,云端只用于 LLM`--show-config` 显示噪音过滤配置且不泄露 key;测试要点:build runtime provider 类型、cloud ASR/TTS 不被调用;优先级:P0;预计:45 分钟。
- [x] 10.6 更新 README、`.env.example` 和本地 `.env` 非密钥配置;前置条件:10.5 完成;验收标准:中文运行说明包含本地语音默认值、降噪开关、模型下载、model-check、run-live 验收;测试要点:命令可复制,`.env` 不进入提交;优先级:P0;预计:35 分钟。
- [x] 10.7 下载/校验新本地模型并执行门禁;前置条件:10.2 至 10.6 完成;验收标准:下载脚本、compileall、unittest、security-check、model-check、device-check、OpenSpec strict、git diff check 通过或记录真实设备失败原因;测试要点:输出不泄露 key,模型文件不进 Git;优先级:P0;预计:60 分钟。
- [x] 10.8 提交“本地语音降噪”模块;前置条件:10.7 门禁通过;验收标准:中文 commit 信息为 `[本地语音降噪]:完成本地语音链路和噪音过滤,包含降噪模型、本地ASR和实时字幕稳定策略`,提交后除本地 `.env` 非提交修改外无未提交源码/文档中间状态;优先级:P0;预计:10 分钟。