298 lines
7.5 KiB
Markdown
298 lines
7.5 KiB
Markdown
# 实时会议 WebSocket 对接文档
|
||
|
||
本文档用于客户侧接入实时会议识别服务,包含接口地址、消息格式、返回示例和重连约定。
|
||
|
||
## 1. 接口地址
|
||
|
||
- 推荐地址:`/ws/v1/asr`
|
||
- 显式地址:`/ws/v1/asr/qwen`
|
||
|
||
说明:
|
||
- 当前协议为 JSON 控制消息 + 音频二进制流。
|
||
- 返回结构按实时会议场景组织,核心消息类型为:`voice_id`、`start`、`sentences`、`end`、`error`。
|
||
|
||
## 2. 交互流程
|
||
|
||
客户端调用顺序:
|
||
|
||
1. 建立 WebSocket 连接
|
||
2. 发送 `start` 文本消息
|
||
3. 持续发送音频二进制数据
|
||
4. 持续接收服务端返回的 `voice_id`、`start`、`sentences`
|
||
5. 识别结束时发送 `stop`
|
||
6. 接收最终 `end`
|
||
|
||
## 3. start 请求
|
||
|
||
### 3.1 请求示例
|
||
|
||
```json
|
||
{
|
||
"type": "start",
|
||
"payload": {
|
||
"format": "pcm",
|
||
"sample_rate": 16000,
|
||
"session_id": "meeting-abc-123",
|
||
"language": null,
|
||
"context": "",
|
||
"enable_inverse_text_normalization": true,
|
||
"silence_duration_ms": 800,
|
||
"min_partial_sec": 0.3,
|
||
"pre_roll_ms": 240,
|
||
"max_sentence_count": 8,
|
||
"partial_holdback_chars": 2,
|
||
"unfixed_token_num": 3,
|
||
"enable_native_partial_stream": true,
|
||
"enable_speaker": true,
|
||
"match_speaker_registry": true,
|
||
"speaker_threshold": 0.6,
|
||
"enable_realtime_vad_split": true,
|
||
"enable_realtime_longform": false,
|
||
"force_stable_segment_sec": 6,
|
||
"force_stable_min_chars": 24,
|
||
"max_segment_sec": 12
|
||
}
|
||
}
|
||
```
|
||
|
||
### 3.2 主要参数说明
|
||
|
||
| 字段 | 类型 | 默认值 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| `format` | string | `pcm` | 建议传 `pcm` |
|
||
| `sample_rate` | number | `16000` | 音频采样率 |
|
||
| `session_id` | string | 可不传 | 会话标识;断线重连时传同一个值可恢复同一会议会话 |
|
||
| `language` | string/null | `null` | 留空表示自动识别 |
|
||
| `context` | string | `""` | 业务上下文提示,如会议主题、术语 |
|
||
| `enable_inverse_text_normalization` | boolean | `true` | 是否启用数字归一化 |
|
||
| `silence_duration_ms` | number | `800` | 静音多久触发收段 |
|
||
| `min_partial_sec` | number | 后端默认 | 最短 partial 窗口 |
|
||
| `pre_roll_ms` | number | `240` | 句首预读时长 |
|
||
| `max_sentence_count` | number | `8` | 单段最多句数 |
|
||
| `partial_holdback_chars` | number | 后端默认 | partial 尾部保留字数,减少尾字抖动 |
|
||
| `unfixed_token_num` | number | 后端默认 | native partial 模式下回滚 token 数 |
|
||
| `enable_native_partial_stream` | boolean | `false` | 是否启用原生流式 partial |
|
||
| `enable_speaker` | boolean | `true` | 是否开启说话人分离 |
|
||
| `match_speaker_registry` | boolean | `false` | 是否匹配已注册声纹库 |
|
||
| `speaker_threshold` | number/null | `null` | 本次声纹匹配阈值 |
|
||
| `enable_realtime_vad_split` | boolean | `false` | 是否启用实时 VAD 拆段 |
|
||
| `enable_realtime_longform` | boolean | `false` | 是否启用长段重转写 |
|
||
| `force_stable_segment_sec` | number | 后端默认 | 超时后软提交阈值 |
|
||
| `force_stable_min_chars` | number | 后端默认 | 软提交最少字数 |
|
||
| `max_segment_sec` | number | 后端默认 | 单段最长秒数 |
|
||
|
||
说明:
|
||
- `enable_speaker=true` 时,服务端会先返回文本,再异步补充 speaker 归属。
|
||
- `match_speaker_registry=true` 时,若匹配到声纹库,会返回实名 speaker 信息。
|
||
|
||
## 4. 音频发送
|
||
|
||
`start` 成功后,客户端持续发送音频二进制帧。
|
||
|
||
推荐格式:
|
||
|
||
- 单声道 PCM
|
||
- 16k 采样率
|
||
- 小块连续发送
|
||
|
||
示例:
|
||
|
||
```js
|
||
ws.send(pcmChunkArrayBuffer);
|
||
```
|
||
|
||
## 5. stop 请求
|
||
|
||
```json
|
||
{
|
||
"type": "stop"
|
||
}
|
||
```
|
||
|
||
## 6. 服务端返回
|
||
|
||
## 6.1 `voice_id`
|
||
|
||
连接建立并收到 `start` 后,服务端先返回:
|
||
|
||
```json
|
||
{
|
||
"type": "voice_id",
|
||
"voice_id": "41ff1926",
|
||
"session_id": "meeting-abc-123"
|
||
}
|
||
```
|
||
|
||
说明:
|
||
- `voice_id` 为本次实时识别标识。
|
||
- `session_id` 为本次会话标识;若未传,服务端会返回自动生成值。
|
||
|
||
## 6.2 `start`
|
||
|
||
服务端确认开始识别:
|
||
|
||
```json
|
||
{
|
||
"type": "start",
|
||
"session_id": "meeting-abc-123"
|
||
}
|
||
```
|
||
|
||
## 6.3 `sentences`
|
||
|
||
识别过程中会持续返回 `sentences`。
|
||
|
||
重要说明:
|
||
|
||
- `sentence_type=0` / `slice_type=1` 表示实时 partial,仅用于实时展示。
|
||
- `sentence_type=1` / `slice_type=2` 表示该段最终定稿,建议作为最终入库依据。
|
||
- 说话人可能异步补写,不保证和首个 final 同时返回。
|
||
|
||
### partial 返回示例
|
||
|
||
```json
|
||
{
|
||
"type": "sentences",
|
||
"code": 0,
|
||
"voice_id": "41ff1926",
|
||
"final": 0,
|
||
"result": {
|
||
"slice_type": 1,
|
||
"index": 3,
|
||
"voice_text_str": "今天这个会议主要讨论预算。"
|
||
},
|
||
"sentences": [
|
||
{
|
||
"sentence_id": 3,
|
||
"sentence_type": 0,
|
||
"speaker_id": -1,
|
||
"start_time": 12000,
|
||
"end_time": 15600,
|
||
"sentence": "今天这个会议主要讨论预算。"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
### final 返回示例
|
||
|
||
```json
|
||
{
|
||
"type": "sentences",
|
||
"code": 0,
|
||
"voice_id": "41ff1926",
|
||
"final": 0,
|
||
"result": {
|
||
"slice_type": 2,
|
||
"index": 3,
|
||
"voice_text_str": "今天这个会议主要讨论预算。"
|
||
},
|
||
"sentences": [
|
||
{
|
||
"sentence_id": 3,
|
||
"sentence_type": 1,
|
||
"speaker_id": -1,
|
||
"start_time": 12000,
|
||
"end_time": 15880,
|
||
"sentence": "今天这个会议主要讨论预算。",
|
||
"speaker_name": "",
|
||
"user_id": null
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
### speaker 回写示例
|
||
|
||
如果 speaker 后续识别完成,服务端会再次返回相同 `sentence_id` 的消息,只更新 speaker 信息:
|
||
|
||
```json
|
||
{
|
||
"type": "sentences",
|
||
"code": 0,
|
||
"voice_id": "41ff1926",
|
||
"final": 0,
|
||
"result": {
|
||
"slice_type": 2,
|
||
"index": 3,
|
||
"voice_text_str": "今天这个会议主要讨论预算。"
|
||
},
|
||
"sentences": [
|
||
{
|
||
"sentence_id": 3,
|
||
"sentence_type": 1,
|
||
"speaker_id": 19,
|
||
"start_time": 12000,
|
||
"end_time": 15880,
|
||
"sentence": "今天这个会议主要讨论预算。",
|
||
"speaker_name": "Alan Paine",
|
||
"user_id": "3"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
接入建议:
|
||
|
||
- 客户端按 `sentence_id` 更新句子,不要把 speaker 回写当成新句子追加。
|
||
- `speaker_id=-1` 代表当前 speaker 暂未确认,不代表识别失败。
|
||
|
||
## 6.4 `end`
|
||
|
||
识别结束后返回:
|
||
|
||
```json
|
||
{
|
||
"type": "end",
|
||
"code": 0,
|
||
"message": "",
|
||
"voice_id": "41ff1926",
|
||
"session_id": "meeting-abc-123",
|
||
"final": 1,
|
||
"result": {
|
||
"slice_type": 2,
|
||
"index": 5,
|
||
"voice_text_str": "完整会议文本"
|
||
},
|
||
"sentences": []
|
||
}
|
||
```
|
||
|
||
## 6.5 `error`
|
||
|
||
异常时返回:
|
||
|
||
```json
|
||
{
|
||
"type": "error",
|
||
"code": "INVALID_STATE",
|
||
"message": "请先发送 start",
|
||
"voice_id": "41ff1926"
|
||
}
|
||
```
|
||
|
||
## 7. 断线重连
|
||
|
||
支持基于 `session_id` 的会话恢复。
|
||
|
||
约定如下:
|
||
|
||
1. 首次连接时,客户端可以自行生成 `session_id` 并放入 `start.payload.session_id`
|
||
2. 若客户端未传,服务端会返回自动生成的 `session_id`
|
||
3. 断线重连时,客户端使用同一个 `session_id` 再次发送 `start`
|
||
4. 若服务端会话仍在保活期内,则恢复同一会议上下文
|
||
|
||
建议:
|
||
|
||
- 客户端在会议生命周期内保持 `session_id` 稳定
|
||
- 网络抖动或页面刷新后,优先使用上一次会话的 `session_id` 重连
|
||
|
||
## 8. 客户端接入建议
|
||
|
||
- 实时展示可消费 `sentence_type=0` 的 partial
|
||
- 业务落库建议只以 `sentence_type=1` 的 final 为准
|
||
- 若后续收到相同 `sentence_id` 的 final 更新,应执行更新而不是新增
|
||
- 若需要最佳实时体验,建议开启 `enable_native_partial_stream=true`
|
||
- 若希望 speaker 识别更完整,建议开启 `enable_speaker=true`
|
||
|