共计 3871 个字符,预计需要花费 10 分钟才能阅读完成。
背景与痛点
语音识别在实时交互场景(如在线会议、语音助手)中,延迟是核心痛点。传统方案采用 HTTP 轮询,存在两个致命问题:

- 高延迟 :客户端需要不断询问服务端是否有新结果,平均延迟在 1 - 3 秒
- 资源浪费 :无结果时频繁发起请求,浪费服务器带宽和计算资源
我曾在一个客服系统中用轮询方案实现语音识别,当并发用户超过 50 时,服务器 CPU 直接飙到 90%。直到改用 WebSocket,延迟降到 200ms 内,资源消耗降低 60%。
技术选型
实时通信常见方案对比:
| 技术 | 延迟 | 双向通信 | 浏览器兼容性 |
|---|---|---|---|
| WebSocket | 100-300ms | ✅ | IE10+ |
| SSE | 500ms+ | ❌(仅服务端推) | 除 IE 外主流 |
| 长轮询 | 1s+ | ❌ | 全部 |
WebSocket 胜出原因 :
1. 全双工通信,适合语音这种持续流式数据
2. 只需一次 HTTP 握手,后续直接基于 TCP 传输
3. 现代浏览器原生支持,API 简洁
核心实现
1. WebSocket 服务端搭建(Node.js 示例)
const WebSocket = require('ws');
const speech = require('@google-cloud/speech');
// 创建语音识别客户端
const client = new speech.SpeechClient();
const wss = new WebSocket.Server({port: 8080});
wss.on('connection', (ws) => {
// 配置语音识别参数
const request = {
config: {
encoding: 'WEBM_OPUS',
sampleRateHertz: 16000,
languageCode: 'zh-CN',
},
interimResults: true // 获取中间识别结果
};
// 创建识别流
const recognizeStream = client
.streamingRecognize(request)
.on('data', data => {
// 将识别结果实时发送给客户端
ws.send(JSON.stringify({text: data.results[0].alternatives[0].transcript,
isFinal: data.results[0].isFinal
}));
});
// 接收客户端发来的音频数据
ws.on('message', (audioChunk) => {recognizeStream.write(audioChunk);
});
ws.on('close', () => {recognizeStream.destroy();
});
});
2. 前端音频采集与传输
关键点在于使用 MediaRecorder API 获取音频流,并分片传输:
// 获取用户麦克风权限
navigator.mediaDevices.getUserMedia({audio: true})
.then(stream => {
const mediaRecorder = new MediaRecorder(stream, {
mimeType: 'audio/webm',
audioBitsPerSecond: 16000
});
const socket = new WebSocket('ws://your-server:8080');
// 每收集到 100ms 数据就发送一次
mediaRecorder.ondataavailable = (e) => {if (socket.readyState === WebSocket.OPEN) {socket.send(e.data);
}
};
// 启动录音,每 100ms 触发一次 ondataavailable
mediaRecorder.start(100);
});
3. 实时可视化实现
结合 Waveform 和动态文字展示:
<div class="voice-container">
<!-- 声波动画 -->
<canvas id="waveform" width="600" height="100"></canvas>
<!-- 识别结果展示 -->
<div id="transcript" class="transcript-box"></div>
</div>
<script>
const canvas = document.getElementById('waveform');
const ctx = canvas.getContext('2d');
// 接收 WebSocket 识别结果
socket.onmessage = (event) => {const result = JSON.parse(event.data);
// 更新文字转录
document.getElementById('transcript').innerText = result.text;
// 绘制声波(模拟)drawWaveform(result.text.length * 2);
};
function drawWaveform(amplitude) {ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
for(let x = 0; x < canvas.width; x++) {const y = amplitude * Math.sin(x * 0.1) + canvas.height/2;
ctx.lineTo(x, y);
}
ctx.strokeStyle = '#4CAF50';
ctx.stroke();}
</script>
性能优化
音频编码选择
| 编码格式 | 带宽需求 | 延迟 | 适用场景 |
|---|---|---|---|
| PCM | 高 | 低 | 专业音频处理 |
| OPUS | 低 | 中 | 实时通信(推荐) |
| MP3 | 中 | 高 | 不推荐 |
实测数据 :
– OPUS 16kHz 采样率:延迟 220ms,带宽约 12kbps
– PCM 16kHz 采样率:延迟 180ms,带宽约 256kbps
WebSocket 连接管理
必须实现心跳机制防止连接被运营商 NAT 超时断开:
// 服务端心跳
setInterval(() => {wss.clients.forEach((client) => {if (client.isAlive === false)
return client.terminate();
client.isAlive = false;
client.ping();});
}, 30000);
// 客户端响应 pong
socket.on('pong', () => {socket.isAlive = true;});
生产环境部署
Nginx 反向代理配置
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 443 ssl;
server_name your-domain.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
location /ws {
proxy_pass http://localhost:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $host;
}
}
必须实现的断线重连
let reconnectAttempts = 0;
function connect() {const socket = new WebSocket('wss://your-domain.com/ws');
socket.onclose = () => {const delay = Math.min(++reconnectAttempts, 5) * 1000;
setTimeout(connect, delay);
};
socket.onopen = () => {reconnectAttempts = 0;};
}
扩展:支持多人识别
架构升级方案:
flowchart TD
A[Client 1] -->|WS| B[Load Balancer]
C[Client 2] -->|WS| B
B --> D[WS Server 1]
B --> E[WS Server 2]
D & E --> F[Redis Pub/Sub]
F --> G[ASR Service]
关键改进点:
1. 使用 Redis 广播所有音频片段
2. 每个语音识别会话分配唯一 sessionId
3. 结果通过 sessionId 路由回对应客户端
实测数据对比
测试环境:AWS t3.medium 实例,10 并发用户
| 方案 | 平均延迟 | CPU 使用率 | 内存占用 |
|---|---|---|---|
| HTTP 轮询 | 1200ms | 85% | 1.2GB |
| WebSocket | 210ms | 32% | 480MB |
示例代码库
完整可运行项目已开源:
https://github.com/example/realtime-asr-demo
包含:
– 带身份验证的 WS 服务
– 自适应音频编码
– 移动端兼容实现
– 压力测试脚本
踩坑总结
- 浏览器录音格式 :Chrome 的 MediaRecorder 默认输出 opus 编码的 webm,而 Safari 输出 AAC,需要前端统一转码
- ASR 流式限制 :Google Speech-to-Text 每个识别流最长 5 分钟,需要定时重建
- 内存泄漏 :务必在 WS 关闭时清理语音识别流,否则会导致服务内存持续增长
- 移动端网络抖动 :在弱网环境下需要增加音频缓存,避免因网络波动导致识别中断
这套方案已在我们产品的视频会议模块稳定运行 9 个月,日均处理语音时长超过 3000 小时。开发过程中最大的启示是: 实时系统的优化永远要关注端到端全链路 ,从麦克风采集到网络传输再到 ASR 引擎,每个环节都可能成为瓶颈。
