226 lines
7.6 KiB
Markdown
226 lines
7.6 KiB
Markdown
# 火山引擎大模型语音流式接入 2026-05-08
|
||
|
||
## 背景
|
||
|
||
本次接入火山引擎豆包语音能力,覆盖两类运行态语音链路:
|
||
|
||
1. 大模型流式语音识别 ASR,使用 WebSocket 双向流式优化模式。
|
||
2. 大模型语音合成 TTS,使用实时交互场景的 WebSocket 双向流式接口,并提供一次性文本输入的 HTTP SSE 单向流式接口。
|
||
|
||
语音能力属于外部副作用,按 server-rs DDD 分层落在 `platform-speech`;`api-server` 只负责平台账号鉴权、环境配置校验、协议代理和错误映射。前端不得直接持有火山引擎密钥。
|
||
|
||
## 官方文档依据
|
||
|
||
- ASR:`https://www.volcengine.com/docs/6561/1354869?lang=zh`
|
||
- TTS WebSocket 双向流式:`https://www.volcengine.com/docs/6561/1329505?lang=zh`
|
||
- TTS WebSocket 单向流式:`https://www.volcengine.com/docs/6561/1719100?lang=zh`
|
||
- TTS HTTP Chunked / SSE 单向流式:`https://www.volcengine.com/docs/6561/1598757?lang=zh`
|
||
|
||
## 环境变量
|
||
|
||
真实值只能放在本地未提交的 `.env.local` / `.env.secrets.local` 或生产服务器环境文件,禁止提交到仓库。
|
||
|
||
```text
|
||
VOLCENGINE_SPEECH_API_KEY=
|
||
VOLCENGINE_SPEECH_APP_ID=
|
||
VOLCENGINE_SPEECH_ACCESS_KEY=
|
||
VOLCENGINE_SPEECH_ASR_RESOURCE_ID=volc.seedasr.sauc.concurrent
|
||
VOLCENGINE_SPEECH_TTS_RESOURCE_ID=seed-tts-2.0
|
||
VOLCENGINE_SPEECH_REQUEST_TIMEOUT_MS=180000
|
||
VOLCENGINE_SPEECH_ASR_WS_URL=wss://openspeech.bytedance.com/api/v3/sauc/bigmodel_async
|
||
VOLCENGINE_SPEECH_TTS_BIDIRECTION_WS_URL=wss://openspeech.bytedance.com/api/v3/tts/bidirection
|
||
VOLCENGINE_SPEECH_TTS_SSE_URL=https://openspeech.bytedance.com/api/v3/tts/unidirectional/sse
|
||
```
|
||
|
||
配置规则:
|
||
|
||
1. 优先使用新版控制台 `VOLCENGINE_SPEECH_API_KEY`,上游请求头写 `X-Api-Key`。
|
||
2. 若只配置旧版控制台信息,则使用 `VOLCENGINE_SPEECH_APP_ID` 和 `VOLCENGINE_SPEECH_ACCESS_KEY`。
|
||
3. ASR 默认资源 ID 选 ASR 2.0 并发版;如账号是小时版,部署时改成 `volc.seedasr.sauc.duration`。
|
||
4. TTS 默认资源 ID 选 `seed-tts-2.0`;旧音色或 1.0 计费资源由部署环境覆盖。
|
||
|
||
## ASR 协议边界
|
||
|
||
客户端连接:
|
||
|
||
```text
|
||
GET /api/speech/volcengine/asr/stream
|
||
Authorization: Bearer <Genarrative JWT>
|
||
```
|
||
|
||
浏览器与 `api-server` 使用 WebSocket 二进制帧透传:
|
||
|
||
1. 首包必须是 JSON 文本,表示 ASR full client request 的业务参数。
|
||
2. 后续二进制帧是音频分片。
|
||
3. 浏览器发送文本帧 `{"type":"finish"}` 时,后端把最后一个空音频包按负包发送给火山。
|
||
4. 后端把火山 full server response 解析成 JSON 文本帧发回浏览器。
|
||
|
||
ASR 上游连接:
|
||
|
||
```text
|
||
wss://openspeech.bytedance.com/api/v3/sauc/bigmodel_async
|
||
X-Api-Key: <VOLCENGINE_SPEECH_API_KEY>
|
||
X-Api-Resource-Id: <VOLCENGINE_SPEECH_ASR_RESOURCE_ID>
|
||
X-Api-Request-Id: <uuid>
|
||
X-Api-Sequence: -1
|
||
```
|
||
|
||
ASR 二进制协议:
|
||
|
||
1. 4 字节 header,大端整数。
|
||
2. full client request:message type `0b0001`,JSON 序列化,gzip 压缩。
|
||
3. audio only request:message type `0b0010`,raw payload,gzip 压缩。
|
||
4. 最后一包音频使用 flags `0b0010`。
|
||
5. full server response:message type `0b1001`,payload 为 gzip JSON。
|
||
6. error response:message type `0b1111`,payload 为错误 JSON 或 UTF-8 文本。
|
||
|
||
首包参数由前端传入,但后端会兜底:
|
||
|
||
```json
|
||
{
|
||
"user": { "uid": "current-user-id" },
|
||
"audio": {
|
||
"format": "pcm",
|
||
"codec": "raw",
|
||
"rate": 16000,
|
||
"bits": 16,
|
||
"channel": 1
|
||
},
|
||
"request": {
|
||
"model_name": "bigmodel",
|
||
"enable_itn": true,
|
||
"enable_punc": true,
|
||
"show_utterances": true,
|
||
"result_type": "full"
|
||
}
|
||
}
|
||
```
|
||
|
||
## TTS 协议边界
|
||
|
||
### WebSocket 双向流式
|
||
|
||
客户端连接:
|
||
|
||
```text
|
||
GET /api/speech/volcengine/tts/bidirection
|
||
Authorization: Bearer <Genarrative JWT>
|
||
```
|
||
|
||
浏览器向后端发送 JSON 文本帧:
|
||
|
||
```json
|
||
{ "type": "start_connection" }
|
||
{ "type": "start_session", "sessionId": "...", "payload": { "user": {}, "req_params": {} } }
|
||
{ "type": "task_request", "sessionId": "...", "payload": { "req_params": { "text": "..." } } }
|
||
{ "type": "finish_session", "sessionId": "..." }
|
||
{ "type": "finish_connection" }
|
||
```
|
||
|
||
后端转成火山 WebSocket V3 二进制帧,并把上游返回帧统一解析成 JSON 文本或音频二进制帧回传浏览器。
|
||
|
||
TTS 双向上游连接:
|
||
|
||
```text
|
||
wss://openspeech.bytedance.com/api/v3/tts/bidirection
|
||
X-Api-Key: <VOLCENGINE_SPEECH_API_KEY>
|
||
X-Api-Resource-Id: <VOLCENGINE_SPEECH_TTS_RESOURCE_ID>
|
||
X-Api-Connect-Id: <uuid>
|
||
```
|
||
|
||
V3 事件帧:
|
||
|
||
1. Full-client request + event number 用于 `StartConnection`、`StartSession`、`TaskRequest`、`FinishSession`、`FinishConnection`。
|
||
2. Full-server response + event number 用于 `ConnectionStarted`、`SessionStarted`、`SessionFinished` 等状态事件。
|
||
3. Audio-only response + event number 用于返回音频二进制。
|
||
4. 错误帧必须转成结构化 JSON 错误,不把上游密钥或完整请求头写入日志。
|
||
|
||
### HTTP SSE 单向流式
|
||
|
||
客户端请求:
|
||
|
||
```text
|
||
POST /api/speech/volcengine/tts/sse
|
||
Authorization: Bearer <Genarrative JWT>
|
||
Content-Type: application/json
|
||
Accept: text/event-stream
|
||
```
|
||
|
||
请求体:
|
||
|
||
```json
|
||
{
|
||
"text": "你好,欢迎来到百梦。",
|
||
"speaker": "zh_female_cancan_mars_bigtts",
|
||
"audioParams": {
|
||
"format": "mp3",
|
||
"sampleRate": 24000
|
||
}
|
||
}
|
||
```
|
||
|
||
后端转换为火山 HTTP SSE 请求体:
|
||
|
||
```json
|
||
{
|
||
"user": { "uid": "current-user-id" },
|
||
"req_params": {
|
||
"text": "...",
|
||
"speaker": "...",
|
||
"audio_params": {
|
||
"format": "mp3",
|
||
"sample_rate": 24000
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
上游 SSE 的常见事件:
|
||
|
||
1. `352`:TTSResponse,`data` 为 base64 音频片段。
|
||
2. `351`:TTSSentenceEnd,`sentence` 为字幕或时间戳数据。
|
||
3. `152`:SessionFinish,合成完成,可含 `usage.text_words`。
|
||
4. `153`:SessionFailed,合成失败。
|
||
|
||
后端保持 SSE 形态透传,但会补齐平台 `requestId` 与上游 `X-Tt-Logid` 作为排障信息。
|
||
|
||
## api-server 路由
|
||
|
||
| 方法 | 路由 | 说明 |
|
||
|---|---|---|
|
||
| `GET` | `/api/speech/volcengine/config` | 返回前端可见的默认资源和推荐音频参数,不返回密钥 |
|
||
| `GET` | `/api/speech/volcengine/asr/stream` | ASR WebSocket 双向流式代理 |
|
||
| `GET` | `/api/speech/volcengine/tts/bidirection` | TTS WebSocket 双向流式代理 |
|
||
| `POST` | `/api/speech/volcengine/tts/sse` | TTS HTTP SSE 单向流式代理 |
|
||
|
||
所有路由必须走 `require_bearer_auth`。
|
||
|
||
## 验收
|
||
|
||
代码级验收:
|
||
|
||
```bash
|
||
cargo fmt --manifest-path server-rs/Cargo.toml --all --check
|
||
cargo test --manifest-path server-rs/Cargo.toml -p platform-speech
|
||
cargo test --manifest-path server-rs/Cargo.toml -p api-server volcengine_speech
|
||
cargo check --manifest-path server-rs/Cargo.toml -p api-server
|
||
npm run check:encoding
|
||
```
|
||
|
||
联调验收:
|
||
|
||
1. 启动 `npm run api-server`。
|
||
2. 检查 `/healthz` 返回 200。
|
||
3. 未登录访问语音路由返回 401。
|
||
4. 已登录后 `/api/speech/volcengine/config` 不返回任何密钥字段。
|
||
5. ASR WebSocket 发送首包和 200ms PCM 分片后能收到识别 JSON。
|
||
6. TTS SSE 能收到 `352` 音频事件与最终 `152` 完成事件。
|
||
7. TTS 双向 WebSocket 能复用连接完成至少一个 session。
|
||
|
||
## 注意事项
|
||
|
||
1. 不把 `VOLCENGINE_ACCESS_KEY_ID`、`VOLCENGINE_SECRET_ACCESS_KEY`、API Key、Access Token 或完整 Authorization 写入文档、日志、测试快照或前端状态。
|
||
2. 中文语音默认使用 16k 单声道 PCM ASR;TTS 默认使用 24k mp3,运行时可按玩法需要改为 pcm。
|
||
3. 火山返回的 `X-Tt-Logid` 是排障关键信息,应记录 logid,但不能记录密钥。
|
||
4. 语音流式能力是平台副作用,不涉及 SpacetimeDB 表结构变更,本次无需修改 `migration.rs`。
|