test/docs/realtime_meeting_websocket.md

10 KiB
Raw Blame History

实时会议 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 消息

客户端先发送文本消息:

{
  "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 采样率
  • 小块持续发送

示例:

ws.send(pcmChunkArrayBuffer);

6. stop 消息

识别结束后发送:

{
  "type": "stop"
}

7. 服务端返回消息

7.1 voice_id

连接启动后首先返回:

{
  "type": "voice_id",
  "voice_id": "41ff1926",
  "session_id": "meeting-abc-123"
}

7.2 start

服务端确认开始识别:

{
  "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 返回

{
  "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

定稿返回

{
  "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 归属:

{
  "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 后,服务端返回最终结束消息:

{
  "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

异常时返回:

{
  "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 调用示例

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 直接打印到页面日志。