File size: 9,980 Bytes
eeeb2b6
 
 
 
96f34e3
eeeb2b6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
# 语音系统

## 概述

Codev 的语音系统支持**语音转文字(STT, Speech-to-Text)****文字转语音(TTS, Text-to-Speech)** 功能。语音录制使用底层音频捕获库(cpal),含有 SoX 和 arecord 回退方案。语音识别支持多个提供商,包括云端 API 和本地模型;语音合成同样支持多个引擎,涵盖 Edge TTS 和 DashScope TTS。

> 注意:部分功能受构建特性门控(feature gate)限制,仅在 ant-internal 构建中可用。

---

## 语音录制(Voice Recording)

核心文件:`src/services/voice.ts`

录音模块使用分层策略:

| 层级 | 方案 | 适用平台 |
|------|------|----------|
| 原生 | `audio-capture-napi`(cpal) | macOS, Linux, Windows |
| 回退 1 | SoX `rec` | Linux (有 SoX) |
| 回退 2 | `arecord` (ALSA) | Linux (有 ALSA) |

关键特征:

- **懒加载原生模块**`loadAudioNapi()` 在首次按键时异步加载 `audio-capture-napi`,避免启动时阻塞(dlopen 可能耗时 1-8 秒)。
- **静音检测**:使用 SoX 或 arecord 时启用静音检测(阈值 3%,持续时间 2 秒),自动结束录音。
- **录音常量**:采样率 16000 Hz,单声道,16-bit PCM。
- **依赖检查**`checkVoiceDependencies()` 探测可用性,显示每个方案的可用状态。

---

## STT 提供商(Speech-to-Text)

### 抽象接口

文件:`src/services/voice/providers.ts`

```typescript
interface TranscriptionProvider {
  name: string
  transcribe(wavPath: string, language?: string): Promise<TranscriptionResult>
}
```

所有 STT 提供商实现此接口,`LocalWhisperSTT``DoubaoSTTProvider` 是内置实现。

---

### 1. Groq Whisper(云端)

文件:`src/services/voice/groqSTT.ts`

通过官方 `groq-sdk` npm 包调用 Groq LPU API,使用 Whisper 模型。

**模型回退**1. 优先使用 `whisper-large-v3`
2. 遇到 429(限速)或 5xx(服务端错误)时自动回退到 `whisper-large-v3-turbo`
3. 其他错误(4xx)直接抛出,不重试

**API 密钥解析**(优先级从高到低):
1. 显式传入的 `apiKey` 参数
2. `friend.json` 中的 `groqApiKey` 配置
3. 环境变量 `GROQ_API_KEY`
4. `~/.claude/settings.json` 中的 `env.groqApiKey`

**核心流程**- `connectGroqStream()` 返回 `VoiceStreamConnection` 接口
- 接收 PCM 音频块并缓冲
- `finalize()` 时将 PCM 转换为 WAV(`pcmToWav()`,16-bit 单声道,16000 Hz)
- 通过 `File` API 上传 WAV 到 Groq

---

### 2. Local Whisper(本地模型)

文件:`src/services/voice/whisperSTT.ts`

基于 Python `openai-whisper` 的本地部署方案。

**架构**- 启动一个长期运行的 Python 子进程(`whisper_server.py`- 通过 stdin/stdout 的 JSON 行协议通信
- 支持预加载模型(`preloadWhisperModel()`**通信协议**- `{"type":"load","model":"small"}` —— 加载模型
- `{"type":"transcribe","wav":"/path/to/audio.wav","language":"en"}` —— 转写
- 服务端以 `{"type":"result","text":"...","language":"..."}``{"type":"error","message":"..."}` 响应

**进程管理**- 进程崩溃后自动重启
- 30 秒超时保护
- 临时文件自动清理

**可用性检查**:使用 Python `importlib.util.find_spec("whisper")` 探测 whisper 模块是否可导入,避免加载 PyTorch 的耗时。

---

### 3. Anthropic Voice Stream(WebSocket)

文件:`src/services/voiceStreamSTT.ts`

通过 Anthropic 的 voice_stream WebSocket 端点传输语音,仅在 ant-internal 构建中可用(由 `feature('VOICE_MODE')` 门控)。

**WebSocket 协议**- 端点:`wss://api.anthropic.com/api/ws/speech_to_text/voice_stream`
- 认证:OAuth Bearer Token(与 Claude Code 共享凭证)
- 消息类型:
  - `KeepAlive` —— 每 8 秒发送保持连接
  - `CloseStream` —— 结束流
  - 服务端推送 `TranscriptText``TranscriptEndpoint``TranscriptError`

**连接生命周期**1. `connectVoiceStream()` 建立 WebSocket 连接
2. `send(audioChunk)` 发送二进制音频帧
3. `finalize()` 发送 `CloseStream`,等待服务端返回 `TranscriptEndpoint`
4. `FinalizeSource` 枚举标识解析路径:`post_closestream_endpoint``no_data_timeout``safety_timeout``ws_close``ws_already_closed`

**Deepgram Nova 3 门控**:通过 GrowthBook 特性标记 `deepgram_nova_3_gate` 控制是否使用 Deepgram Nova 3 模型。

**Voice Keyterms**:通过查询参数传递关键词列表,提高领域术语的识别准确率。

---

### 4. Doubao(豆包 STT)

文件:`src/services/voice/doubaoSTT.ts`

此文件是一个自动生成的存根(stub),对应 ant-internal 的 `feature()` 门控模块。外部构建中所有代码路径在 DCE(死代码消除)后不会实际执行。

存根使用 JavaScript `Proxy` 将任何属性访问、函数调用、构造操作映射到无操作(noop)处理器。导出 `connectDoubaoStream``normalizeLanguageForSTT` 等函数作为占位符。

`DoubaoSTTProvider`(在 `providers.ts` 中)封装了此存根的调用逻辑,通过动态导入(`import('./doubaoSTT.js')`)在运行时解析。

---

## TTS 提供商(Text-to-Speech)

### 抽象接口

文件:`src/services/voice/providers.ts`

```typescript
interface TTSProvider {
  name: string
  synthesize(text: string): Promise<SynthesisResult>
}
```

`EdgeTTSProvider``CommandTTSProvider` 是内置实现。

---

### 1. Edge TTS(微软神经网络语音)

文件:`src/services/voice/providers.ts`(类 `EdgeTTSProvider`**实现路径一(Provider 接口)**- 直接调用 `edge-tts` 命令行工具
- 使用 Node.js `child_process.spawn` 执行子进程
- 通过 `--voice``--text``--write-media` 参数控制输出
- 默认语音:`en-US-AriaNeural`

**实现路径二(独立函数)**:
文件:`src/services/voice/edgeTTS.ts``speakWithEdgeTTS()`- 调用 `scripts/speak.py` Python 脚本
- 依赖 `.venv/bin/python` 或系统 Python
- 返回标准化的 `TTSResult` 接口

**实现路径三(Friend 模块)**:
文件:`src/friend/tts.ts``edgeTts()`- 使用 `node-edge-tts` npm 包(Node.js 原生实现,无需 Python)
- 默认语音:`zh-CN-XiaoxiaoNeural`(中文语音)
- 输出为 MP3 文件

**播放功能**`src/services/voice/edgeTTS.ts` `playAudioFile()`):
| 平台 | 播放器 | 说明 |
|------|--------|------|
| macOS | `afplay` | 原生 |
| Linux | `ffplay` | 先 `pkill` 已有进程,再启动新进程 |
| Windows | `start` | 系统默认播放器 |

---

### 2. Qwen DashScope TTS(通义千问语音合成)

文件:`src/friend/tts.ts``qwenTts()`)

调用阿里云 DashScope API 进行语音合成。

**API 信息**- 国内端点:`https://dashscope.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation`
- 国际端点:`https://dashscope-intl.aliyuncs.com/...`
- 默认模型:`qwen3-tts-flash`
- 默认语音:`Cherry`
- 认证:Bearer Token(`apiKey`**参数**- `voice`:语音角色(如 Cherry, Polly 等)
- `model`:模型版本
- `language`:语言(`zh``en`),决定端点选择和 `language_type`

**流程**1. POST 请求获取合成音频的 URL
2. 从 URL 下载音频数据
3. 保存为 WAV 临时文件
4. 支持 30 秒超时保护

**音频文件注册**`registerAudioFile()` 将文件路径注册到内存映射,5 分钟后自动过期,用于跨模块引用。

---

### 3. Command TTS(命令模板回退)

文件:`src/services/voice/providers.ts`(类 `CommandTTSProvider`)

通用 shell 命令 TTS,支持模板占位符:
- `{input}` / `{input_path}` —— 输入文本文件路径
- `{output_path}` —— 输出音频文件路径

适用于调用任意外部 TTS 命令行工具。

---

## Voice Stream 协议

文件:`src/services/voiceStreamSTT.ts`

Voice Stream 是 Anthropic 的 WebSocket 协议,用于实时语音识别。

### 消息类型

| 方向 | 类型 | 说明 |
|------|------|------|
| 客户端 → 服务端 | `KeepAlive` | 心跳,每 8 秒 |
| 客户端 → 服务端 | `CloseStream` | 结束音频流 |
| 客户端 → 服务端 | 二进制帧 | PCM 音频数据 |
| 服务端 → 客户端 | `TranscriptText` | 转写文本片段 |
| 服务端 → 客户端 | `TranscriptEndpoint` | 转写结束标记 |
| 服务端 → 客户端 | `TranscriptError` | 错误信息 |

### FinalizeSource 枚举

`finalize()` 方法的解析路径:

| 值 | 说明 |
|----|------|
| `post_closestream_endpoint` | 正常流程:发送 CloseStream 后收到 TranscriptEndpoint |
| `no_data_timeout` | 发送 CloseStream 后 1.5 秒无响应 |
| `safety_timeout` | WebSocket 挂起超过 5 秒 |
| `ws_close` | WebSocket 连接关闭 |
| `ws_already_closed` | 已关闭的连接被重复调用 |

### Keyterms(关键词)

通过 WebSocket 查询参数 `keyterms` 传递关键词列表,格式为逗号分隔的 URL 编码值。关键词可提高模型对特定术语的识别准确率。

---

## Voice Mode(语音模式)

语音模式是 Push-to-Talk(按住说话)的实现:

1. **开始录音**:用户按下语音快捷键
2. **音频采集**:底层 cpal 或回退方案开始采集 16kHz 单声道 PCM 音频
3. **音频传输**:音频块通过 Voice Stream WebSocket 实时发送
4. **释放停止**:用户松开快捷键,发送 CloseStream
5. **等待转写**:接收服务端返回的 TranscriptText 和 TranscriptEndpoint
6. **提交文本**:转写文本进入对话输入流

当使用本地 Whisper 时,流程类似但使用子进程通信而非 WebSocket。

---

## 提供商注册与选择

文件:`src/services/voice/providers.ts`

系统通过 `TranscriptionProvider``TTSProvider` 接口实现多提供商支持。每个提供商有自己的名称(`name` 属性)和实现逻辑。选择策略在调用方(如 `useVoice` hook)中决定,根据可用性和用户配置选择合适的提供商。