# 实时会议 WebSocket 调用说明 本文档说明当前项目实时会议识别接口的调用方式、入参与返回格式。 ## 1. 接口地址 - 推荐地址:`/ws/v1/asr` - 显式地址:`/ws/v1/asr/qwen` 说明: - `/ws/v1/asr/funasr` 已废弃,不再使用。 - 当前返回结构按腾讯实时识别风格组织,核心消息类型为:`voice_id`、`start`、`sentences`、`end`、`error`。 ## 2. 交互流程 客户端调用顺序: 1. 建立 WebSocket 连接 2. 发送 `start` 消息 3. 持续发送音频二进制数据 4. 接收服务端返回的 `voice_id`、`start`、`sentences` 5. 发送 `stop` 6. 接收最终 `end` ## 3. start 消息 客户端先发送文本消息: ```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 } } ``` ## 4. start 参数说明 ### 必填/常用参数 | 字段 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | | `format` | string | `pcm` | 当前建议传 `pcm` | | `sample_rate` | number | `16000` | 采样率 | | `session_id` | string | 自动生成/可不传 | 会话标识;断线重连时传同一个值可在保活期内恢复同一会议会话 | | `language` | string/null | `null` | 识别语言,留空表示自动判断 | | `context` | string | `""` | 上下文提示,如会议主题、术语 | | `enable_inverse_text_normalization` | boolean | `true` | 是否启用数字归一化 | ### partial / 切段相关 | 字段 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | | `silence_duration_ms` | number | `800` | 静音多久触发收段 | | `min_partial_sec` | number | `0.9`(后端默认) | 最短 partial 窗口 | | `pre_roll_ms` | number | `240` | 句首预读毫秒数 | | `max_sentence_count` | number | `8` | 一段内最多允许的句子数 | | `partial_holdback_chars` | number | 后端默认 | partial 尾部保留字数,减少尾字抖动 | | `unfixed_token_num` | number | `5`(后端默认) | native partial 模式下回滚 token 数 | | `enable_native_partial_stream` | boolean | `false` | 是否启用底层原生流式 partial | | `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 | 后端默认 | 单段最长秒数 | ### speaker 相关 | 字段 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | | `enable_speaker` | boolean | `true` | 是否开启说话人分离 | | `match_speaker_registry` | boolean | `false` | 是否匹配已注册声纹库 | | `speaker_threshold` | number/null | `null` | 本次声纹匹配阈值 | 说明: - `enable_speaker=true` 时,服务端会先返回文本,再异步补充 speaker 归属。 - `match_speaker_registry=true` 时,若匹配到声纹库,会返回实名 speaker 信息。 ## 5. 音频数据发送 `start` 成功后,客户端持续发送音频二进制帧。 当前推荐: - 单声道 PCM - 16k 采样率 - 小块持续发送 示例: ```js ws.send(pcmChunkArrayBuffer); ``` ## 6. stop 消息 识别结束后发送: ```json { "type": "stop" } ``` ## 7. 服务端返回消息 ## 7.1 `voice_id` 连接启动后首先返回: ```json { "type": "voice_id", "voice_id": "41ff1926", "session_id": "meeting-abc-123" } ``` ## 7.2 `start` 服务端确认开始识别: ```json { "type": "start", "session_id": "meeting-abc-123" } ``` 说明: - 若客户端在 `start.payload.session_id` 中传入固定值,服务端会优先复用该值。 - 若客户端未传 `session_id`,服务端仍按当前连接生成 `voice_id`,并同步作为 `session_id` 返回。 - 在 `REALTIME_SESSION_RESUME_TTL_SEC` 保活时间内,客户端断线后使用相同 `session_id` 再次发送 `start`,服务端会恢复原会话上下文。 ## 7.3 `sentences` 识别过程中会持续返回 `sentences`。 重要说明: - `sentence_type=0` / `slice_type=1` 表示实时 partial,仅用于实时展示。 - `sentence_type=1` / `slice_type=2` 表示该段最终定稿,建议作为最终业务入库依据。 - 若开启 `enable_native_partial_stream=true`,partial 刷新会更快,但中间文本可能更活。 - 若希望少存数据,建议不要存 partial,只对 `sentence_type=1` 做落库或 upsert。 ### 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": "今天这个会议主要讨论预算。" } ] } ``` 字段说明: | 字段 | 说明 | | --- | --- | | `slice_type=1` | partial | | `sentence_type=0` | partial 句子 | | `speaker_id=-1` | 当前还未确认 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": -1, "start_time": 12000, "end_time": 15880, "sentence": "今天这个会议主要讨论预算。", "speaker_name": "", "user_id": null } ] } ``` 字段说明: | 字段 | 说明 | | --- | --- | | `slice_type=2` | 该段文本已定稿 | | `sentence_type=1` | final 句子 | | `speaker_name` | 项目扩展字段 | | `user_id` | 项目扩展字段 | ### speaker 回写返回 如果 speaker 后续识别完成,服务端会再次返回相同 `sentence_id` 的 `sentences` 消息,只更新 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" } ] } ``` 说明: - speaker 是异步补归属,不保证和文本首个 final 同时返回。 - 前端应使用 `sentence_id` 做幂等更新,而不是简单追加。 ## 7.3.1 partial 与 final 的使用建议 推荐使用方式: - 前端实时显示:使用 `sentence_type=0` - 最终文本入库:使用 `sentence_type=1` - 说话人归属更新:继续按相同 `sentence_id` 更新已有 final 记录 推荐入库字段: - `sentence_id` - `start_time` - `end_time` - `sentence` - `speaker_id` - `speaker_name` - `user_id` 推荐入库策略: 1. 收到 `sentences` 后,仅处理 `sentence_type=1` 2. 使用 `sentence_id` 作为同一连接内的幂等更新键 3. 若后续相同 `sentence_id` 再次返回,通常表示 speaker 归属补写,直接更新原记录 4. 收到 `end` 后,可将本次会话的 final 结果视为最终完成结果 ## 7.4 `end` 客户端发送 `stop` 后,服务端返回最终结束消息: ```json { "type": "end", "code": 0, "message": "", "voice_id": "41ff1926", "final": 1, "result": { "slice_type": 2, "index": 3, "voice_text_str": "完整识别文本" }, "sentences": [ { "sentence_id": 0, "sentence_type": 1, "speaker_id": 19, "start_time": 0, "end_time": 3200, "sentence": "第一句", "speaker_name": "Alan Paine", "user_id": "3" } ] } ``` ## 7.5 `error` 异常时返回: ```json { "type": "error", "code": -1, "message": "错误信息", "voice_id": "41ff1926" } ``` ## 8. 腾讯风格字段兼容说明 当前实时会议返回格式按腾讯实时 speaker demo 风格组织,核心字段保持一致: - `type` - `code` - `voice_id` - `final` - `result.slice_type` - `result.index` - `result.voice_text_str` - `sentences[].sentence_id` - `sentences[].sentence_type` - `sentences[].speaker_id` - `sentences[].start_time` - `sentences[].end_time` - `sentences[].sentence` 在此基础上,项目额外补充: - `sentences[].speaker_name` - `sentences[].user_id` ## 9. JavaScript 调用示例 ```js const ws = new WebSocket("ws://127.0.0.1:8000/ws/v1/asr"); ws.onopen = () => { ws.send(JSON.stringify({ type: "start", payload: { format: "pcm", sample_rate: 16000, 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 } })); }; ws.onmessage = (event) => { const payload = JSON.parse(event.data); console.log("ws message:", payload); }; function sendPcmChunk(arrayBuffer) { ws.send(arrayBuffer); } function stopRecognition() { ws.send(JSON.stringify({ type: "stop" })); } ``` ## 10. 对接注意事项 - 前端要按 `sentence_id` 更新句子,不要把 speaker 回写当成新句子追加。 - `speaker_id=-1` 代表 speaker 暂未确认,不代表识别失败。 - 默认启用 `enable_native_partial_stream`;partial 刷新更快,但中间文本可能会修订。 - 最终展示建议以 `sentence_type=1` 的句子为准。 - 若需要排查前端是否真的传了某个参数,建议把 `start payload` 直接打印到页面日志。