# 第一阶段 · 模块一 · 第九节:Voice Mode 语音模式
Claude Code 如何实现语音输入?Voice Mode 的工作原理是什么?STT(语音转文字)是如何实现的?如何处理实时语音流?
Claude Code 全局架构
┌─────────────────────────────────────────────────────────────────────┐
│ 服务层(services/) │
│ │
│ Voice Mode ← 本节 │
│ ├── 音频捕获 │
│ ├── 静音检测 │
│ ├── 流式 STT │
│ └── TTS 合成 │
└─────────────────────────────────────────────────────────────────────┘
源码位置:`src/services/voice.ts`
Voice Mode 是 Claude Code 的语音输入功能,允许用户通过语音与 Claude 交互,支持实时语音对话。
┌─────────────────────────────────────────────────────────────────────┐
│ Voice Mode 工作流程 │
│ │
│ 麦克风 ──► 录音 ──► STT ──► 文字 ──► Claude │
│ ▲ │
│ 语音输入 │
└─────────────────────────────────────────────────────────────────────┘
源码位置:`src/services/voice.ts`
// Voice Mode 模块
voice.ts // 主语音服务
voiceStreamSTT.ts // 流式语音转文字
voiceKeyterms.ts // 语音关键词
voiceModeEnabled.ts // 启用检查
┌─────────────────────────────────────────────────────────────────────┐
│ Voice Mode 功能 │
│ │
│ 1. 实时语音输入 │
│ - 麦克风录音 │
│ - 流式 STT 识别 │
│ │
│ 2. 静音自动停止 │
│ - 检测静音自动结束录音 │
│ - 无需手动停止 │
│ │
│ 3. 关键词触发 │
│ - 支持语音关键词唤醒 │
│ │
│ 4. TTS 语音输出 │
│ - Claude 回复转为语音 │
└─────────────────────────────────────────────────────────────────────┘
问 1:Voice Mode 和打字输入有什么区别?
// 打字输入:
// - 需要手动输入
// - 适合精确表达
// - 速度受限
// Voice Mode:
// - 语音直接输入
// - 适合快速表达
// - 可结合手势控制
源码位置:`src/services/voice.ts`
// 音频常量
const RECORDING_SAMPLE_RATE = 16000 // 16kHz - 语音识别最佳采样率
const RECORDING_CHANNELS = 1 // 单声道
// 录音格式
// - WAV (未压缩) - 本地处理
// - Opus (压缩) - 用于传输
平台支持:
┌─────────────────────────────────────────────────────────────────────┐
│ 1. macOS/Linux/Windows │
│ 使用原生音频 API │
│ - CoreAudio (macOS) │
│ - ALSA/PulseAudio (Linux) │
│ - WASAPI (Windows) │
│ │
│ 2. Linux 备选方案 │
│ - SoX (rec) │
│ - arecord (ALSA) │
│ │
│ 3. Android (Termux) │
│ - 使用 Termux API │
└─────────────────────────────────────────────────────────────────────┘
// 录音流程
async function startRecording(): Promise<void> {
// 1. 请求麦克风权限
// 2. 打开音频流
// 3. 开始录音(边录边传)
// 4. 检测静音
// 5. 静音超时自动停止
}
问 1:为什么采样率是 16kHz?
// 选择 16kHz 的原因:
// 1. 语音识别最佳范围
// - 人类语音频率范围 300Hz-3400Hz
// - 16kHz 采样可以完整覆盖
// 2. 带宽考虑
// - 16kHz 比 44.1kHz 带宽更低
// - 适合网络传输
// 3. 计算效率
// - 16kHz 数据量适中
// - 实时处理可行
源码位置:`src/services/voice.ts`
// SoX 静音检测参数
const SILENCE_DURATION_SECS = '2.0' // 2秒静音后停止录音
const SILENCE_THRESHOLD = '3%' // 3% 以下判定为静音
// 静音检测原理:
// 1. 实时监测音频电平
// - 计算音频信号的均方根 (RMS)
// - 判断是否低于阈值
// 2. 静音判定
// - 电平 < 阈值 * 持续时间 > SILENCE_DURATION_SECS
// - 则判定为静音
// 3. 自动停止
// - 触发静音条件后自动停止录音
// - 发送已录制的音频数据
// VAD (Voice Activity Detection)
// 用于区分:
// - 语音信号
// - 背景噪音
// - 静音
// 算法:
// 1. 短时能量检测
// 2. 过零率检测
// 3. 频谱特征分析
问 1:如何避免误触发静音检测?
// 方法:
// 1. 调整阈值
// - 根据环境噪音调整
// - 安静环境 2-3%
// - 嘈杂环境 5-10%
// 2. 调整持续时间
// - 说话间隔短 → 缩短持续时间
// - 说话间隔长 → 延长持续时间
// 3. 使用 VAD
// - 更准确的语音检测
// - 减少误判
源码位置:`src/services/voiceStreamSTT.ts`
Claude Code 使用 Anthropic voice_stream WebSocket API 实现实时语音识别。
// WebSocket 连接建立
const VOICE_STREAM_PATH = '/api/ws/speech_to_text/voice_stream'
async function connect(): Promise<VoiceStreamConnection> {
// 使用 OAuth 凭证连接 Anthropic WebSocket
const ws = new WebSocket(
`wss://api.anthropic.com${VOICE_STREAM_PATH}`,
{
headers: {
Authorization: `Bearer ${oauthToken}`,
'User-Agent': getUserAgent()
}
}
)
}
// 协议使用 JSON 控制消息 + 二进制音频帧
// 控制消息类型
type ControlMessage =
| { type: 'KeepAlive' } // 心跳保活
| { type: 'CloseStream' } // 关闭流
| { type: 'TranscriptText'; data: string } // 转写文本
| { type: 'TranscriptEndpoint' } // 转写端点
| { type: 'TranscriptError'; error_code?: string }
// 发送音频
connection.send(audioChunk: Buffer)
// 结束转写
const source = await connection.finalize()
// source: 'post_closestream_endpoint' | 'no_data_timeout' | 'safety_timeout'
// 心跳保活间隔
const KEEPALIVE_INTERVAL_MS = 8_000 // 8 秒
// 发送心跳
setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send('{"type":"KeepAlive"}')
}
}, KEEPALIVE_INTERVAL_MS)
// finalize() 等待超时配置
export const FINALIZE_TIMEOUTS_MS = {
safety: 5_000, // 最长等待 5 秒
noData: 1_500, // 无数据后 1.5 秒确认
}
// noData: 服务器在 CloseStream 后没有更多 TranscriptText
// safety: 最终安全超时
问 1:流式 STT 和完整 STT 有什么区别?
// 完整 STT:
// - 录音完整后提交
// - 延迟 = 录音时间 + 处理时间
// 流式 STT(voice_stream):
// - WebSocket 实时流式识别
// - 边录边识别,延迟极低
// - 使用 Anthropic OAuth 认证
// - 支持 binary 音频帧传输
// TTS (Text-to-Speech) 配置
interface TTSConfig {
engine: 'elevenlabs' | 'openai' | 'native'
voice: string
speed: number // 0.5 - 2.0
}
// TTS 流程
async function speak(text: string): Promise<void> {
// 1. 文本预处理
// - 去除 Markdown 格式
// - 处理特殊字符
// 2. 调用 TTS API
const audio = await ttsService.synthesize(text, config)
// 3. 播放音频
await audioPlayer.play(audio)
}
// 语音控制
// 1. 播放/暂停
// 2. 语速调节
// 3. 音量控制
// 4. 停止播放
答案:
// 适用场景:
// 1. 快速输入
// - 不方便打字时
// - 移动场景
// 2. 手部忙碌
// - 驾驶时
// - 烹饪时
// 3. 口语表达更自然
// - 描述代码逻辑
// - 口头 Code Review
// 不适用场景:
// 1. 精确代码输入
// - 变量名拼写
// - 标点符号
// 2. 嘈杂环境
// - 会议中
// - 公共场所
答案:
// 优化方法:
// 1. 音频质量
// - 使用好的麦克风
// - 减少背景噪音
// 2. 语言模型
// - 使用专门的代码词汇模型
// - 微调识别模型
// 3. 上下文理解
// - 结合对话上下文
// - 纠错机制
// 4. 用户反馈
// - 错误时及时纠正
// - 个性化学习
答案:
// 心跳机制的作用:
// 1. 检测连接状态
// - 及时发现断开的连接
// - 避免在死连接上继续发送数据
// 2. 保持连接活跃
// - 防止 NAT 超时断开
// - 防火墙可能会断开空闲连接
// 3. 节省资源
// - 可以快速回收死连接
// - 避免资源浪费
// 实现示例:
const HEARTBEAT_INTERVAL = 8000; // 8秒
setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: 'KeepAlive' }));
}
}, HEARTBEAT_INTERVAL);
答案:
// 错误处理策略:
// 1. 超时处理
// - 设置最大等待时间
// - 超时后提示用户重试
// 2. 重试机制
// - 识别失败后自动重试
// - 限制重试次数避免死循环
// 3. 错误恢复
// - 返回错误码让用户了解原因
// - 提供手动输入作为备选
// 4. 日志记录
// - 记录错误便于排查
// - 分析常见错误模式
| 资源 | 说明 |
| `src/services/voice.ts` | 语音服务主模块 |
| `src/services/voiceStreamSTT.ts` | 流式 STT 实现 |
| 流式语音处理 | 实时语音识别 |
下一节我们将进入 Cost Tracker 成本追踪:
- Token 计数
- 费用计算
- 使用报告