语音系统
概述
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
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 模型。
模型回退:
- 优先使用
whisper-large-v3 - 遇到 429(限速)或 5xx(服务端错误)时自动回退到
whisper-large-v3-turbo - 其他错误(4xx)直接抛出,不重试
API 密钥解析(优先级从高到低):
- 显式传入的
apiKey参数 friend.json中的groqApiKey配置- 环境变量
GROQ_API_KEY ~/.claude/settings.json中的env.groqApiKey
核心流程:
connectGroqStream()返回VoiceStreamConnection接口- 接收 PCM 音频块并缓冲
finalize()时将 PCM 转换为 WAV(pcmToWav(),16-bit 单声道,16000 Hz)- 通过
FileAPI 上传 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
连接生命周期:
connectVoiceStream()建立 WebSocket 连接send(audioChunk)发送二进制音频帧finalize()发送CloseStream,等待服务端返回TranscriptEndpointFinalizeSource枚举标识解析路径: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
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.pyPython 脚本 - 依赖
.venv/bin/python或系统 Python - 返回标准化的
TTSResult接口
实现路径三(Friend 模块):
文件:src/friend/tts.ts(edgeTts())
- 使用
node-edge-ttsnpm 包(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
流程:
- POST 请求获取合成音频的 URL
- 从 URL 下载音频数据
- 保存为 WAV 临时文件
- 支持 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(按住说话)的实现:
- 开始录音:用户按下语音快捷键
- 音频采集:底层 cpal 或回退方案开始采集 16kHz 单声道 PCM 音频
- 音频传输:音频块通过 Voice Stream WebSocket 实时发送
- 释放停止:用户松开快捷键,发送 CloseStream
- 等待转写:接收服务端返回的 TranscriptText 和 TranscriptEndpoint
- 提交文本:转写文本进入对话输入流
当使用本地 Whisper 时,流程类似但使用子进程通信而非 WebSocket。
提供商注册与选择
文件:src/services/voice/providers.ts
系统通过 TranscriptionProvider 和 TTSProvider 接口实现多提供商支持。每个提供商有自己的名称(name 属性)和实现逻辑。选择策略在调用方(如 useVoice hook)中决定,根据可用性和用户配置选择合适的提供商。