---
title: "实时语音转写(WebSocket 流式)"
method: GET
path: "/api/v1/users/tasks/speech-to-text-stream"
tags: ["【用户】任务管理"]
---

# 实时语音转写(WebSocket 流式)

`GET /api/v1/users/tasks/speech-to-text-stream`

通过 WebSocket 上传实时音频流并实时返回识别结果。
后端使用豆包流式语音识别 2.0 (bigmodel_async)。完整协议见 docs/speech-to-text-stream.md。

## 帧类型约定
| 方向 | 帧类型 | 用途 |
|---|---|---|
| C → S | Text(JSON) | 控制消息:start / stop |
| C → S | Binary | 音频字节流,服务端封装为豆包帧透传 |
| S → C | Text(JSON) | 所有事件(ready / partial / final / done / error) |

## 客户端 → 服务端

### 1) start (第一帧必须是它)
```json
{
"type": "start",
"format": "pcm",
"disfluency": false
}
```
- `format` 可选,默认 `pcm`。支持 `pcm` / `wav` / `ogg` / `mp3`,单声道、16-bit、采样率固定 16000Hz
- `pcm` / `wav` 内部音频流必须是 `pcm_s16le`;`ogg` 必须为 `opus` 编码;`mp3` 由远端解码
- `disfluency` 可选,默认 `false`。`true` 时启用语义顺滑(过滤"嗯/啊"等口头禅、语义重复词)
- 服务端校验通过 → 与豆包建立 WSS → 收到首个响应后向客户端下发 `ready` 事件(带 `logid`)
- **客户端必须在收到 `ready` 之后才能发 Binary 音频帧**
- 以下能力默认开启:中间结果(双向流式天然有)、标点预测、ITN(中文数字转阿拉伯数字)

### 2) Binary 音频帧
`ready` 之后,客户端持续发 Binary 帧。**建议每帧 200ms**(豆包推荐值,过碎会影响性能)。

### 3) stop (主动结束)
```json
{ "type": "stop" }
```
服务端收到后向豆包发送"最后一包"标志,等待豆包最终响应后下发 `done` 事件并关闭 WS。客户端直接 close WS 亦可。

## 服务端 → 客户端

所有事件统一外层结构:
```json
{ "type": "<event_type>", "timestamp": 1733299200000, ... }
```

### ready — 远端已就绪
```json
{ "type": "ready", "logid": "202407261553070FACFE6D19421815D605", "timestamp": 1733299200000 }
```
仅推送一次。客户端收到后开始发 Binary 音频帧。`logid` 是豆包返回的 `X-Tt-Logid`,排障必备,
建议前端在 session 期间一直打印它,跟后续 error 事件可以关联。

### partial — 中间结果(实时滚动,会反复推送)
```json
{ "type": "partial", "index": 1, "text": "今天天气真", "timestamp": 1733299201500 }
```
- 同一 `index` 的 `partial` 会反复推送,`text` 通常逐渐变长
- **客户端必须用 `text` 覆盖该句显示内容,不要追加**

### final — 一句话定稿
```json
{ "type": "final", "index": 1, "text": "今天天气真不错。", "timestamp": 1733299202800 }
```
- 该句识别完成、内容固化,后续不会再变;客户端用 `text` 覆盖 `index` 对应位置
- `final` 之后可能立刻有下一句的 `partial`(`index+1`)

### done — 整个 session 结束
```json
{ "type": "done", "logid": "...", "timestamp": 1733299210000 }
```
- 服务端已收到豆包最后一包响应、释放资源,即将关闭 WS;`done` 之后不会再有任何事件

### error — 错误
豆包远端错误:
```json
{
"type": "error",
"logid": "202407261553070FACFE6D19421815D605",
"error": {
"code": 45000001,
"message": "请求参数无效",
"request_id": "67ee89ba-7050-4c04-a3d7-ac61a63499b3",
"logid": "202407261553070FACFE6D19421815D605"
},
"timestamp": 1733299205000
}
```
本服务前置校验错误(远端连接前):`code=0`,在 `message` 描述原因:
```json
{
"type": "error",
"error": { "code": 0, "message": "first message must be a 'start' control message" },
"timestamp": 1733299205000
}
```

## 豆包常见错误码速查
| code | 含义 | 典型原因 |
|---|---|---|
| `45000001` | 请求参数无效 | 缺字段 / 字段值无效 / 重复请求 |
| `45000002` | 空音频 | 录音未采集到声音 |
| `45000081` | 等包超时 | 前端没在期限内连续发送音频帧 |
| `45000151` | 音频格式不正确 | `format` 与实际音频不匹配 |
| `55000031` | 服务器繁忙 | 退避重试 |
| `550xxxxx` | 服务内部错误 | 直接重试,持续失败时带 `logid` 联系运维 |

## 时序示例
```
C → S: WS upgrade (带鉴权)
C → S: {"type":"start","format":"pcm"}
S → C: {"type":"ready","logid":"..."}
C → S: <binary 200ms> <binary 200ms> ...
S → C: {"type":"partial","index":1,"text":"今天"}
S → C: {"type":"partial","index":1,"text":"今天天气真"}
S → C: {"type":"final","index":1,"text":"今天天气真不错。"}
C → S: {"type":"stop"}
S → C: {"type":"done","logid":"..."}
WS close
```

## 与 POST /speech-to-text 的差异
- POST /speech-to-text:整段录音 → SSE 单段结果,适合短语音 ≤60s
- 本接口:WS 双向实时流,支持长语音、句级 final、可被打断,适合 Web/移动端边说边显示

## Request body

- DomainSpeechStreamStartReq
  - `disfluency` boolean — 是否启用语义顺滑(过滤"嗯/啊"等口头禅、语义重复词),默认 false
  - `format` 'pcm' | 'wav' | 'ogg' | 'mp3' — 音频容器格式,单声道、16-bit、采样率固定 16000Hz。 pcm / wav 内部音频流必须是 pcm_s16le;ogg 必须为 opus 编码;mp3 由服务端解码。
  - `type` string, required — 消息类型,固定为 "start"

## Other responses

- `101` — WebSocket 升级成功;此后通过 WS 帧通信,事件结构见上方说明
- `401` — 未授权
- `500` — 服务器内部错误(ASR 服务未配置等)

---

[API](https://skmtc.net/chaitin/apis/untitled-api.md) · [All operations](https://skmtc.net/chaitin/apis/untitled-api/llms.txt) · [OpenAPI document](https://skmtc-service-staging.skmtc.workers.dev/v1/apis/chaitin/untitled-api/revisions/5a5929cb17f2/schema)
