# 声通兼容实时评测 路径与参数帧沿用声通命名,可开启朗读进度。 来源:https://open.shengzhiai.com/sdk/v2/api/ws-compat/ `WSS wss://open.shengzhiai.com/{coreType}` ## 帧序列,沙箱实录 ```text 0 ms 客户端 GET /sent.eval.cn?appKey=…&nonce=…×tamp=…&signature=… 27 ms 服务端 {"event":"connected","coreType":"sent.eval.cn"} 27 ms 客户端 {"refText":"今天天气很好","language":"zh-CN","realtime_feedback":true,"idempotencyKey":"49db5f38…"} 477 ms 服务端 {"event":"started","coreType":"sent.eval.cn"} 477 ms 客户端 {"cmd":"ping"} 477 ms 客户端 二进制帧 96 个,每个 640 字节,共 61440 字节 915 ms 服务端 {"event":"pong","ts":1791483761096} 1429 ms 服务端 {"eof":0,"result":{"bytes":16000}} 1933 ms 服务端 {"eof":0,"result":{"bytes":32000}} 2472 ms 服务端 {"eof":0,"result":{"bytes":48000}} 2509 ms 客户端 {"cmd":"end"} 6268 ms 服务端 {"recordId":"eval_a07810a40779","result":{"overall":94.6,"pronunciation":100,"fluency":96,"integrity":100,"tone":83,"rhythm":78,"speed":225,"duration":"1.920","warning":[],"words":[{"word":"今","scores":{"overall":78}},{"word":"天","scores":{"overall":95}},{"word":"天","scores":{"overall":85}},{"word":"气","scores":{"overall":76}},{"word":"很","scores":{"overall":94}},{"word":"好","scores":{"overall":73}}]},"eof":1} ``` ## 示例代码 Node.js,直连协议: ```js // 声通兼容实时评测,直连协议。Node.js 18 及以上,依赖 npm install ws import crypto from 'node:crypto'; import { readFile } from 'node:fs/promises'; import WebSocket from 'ws'; const idempotencyKey = crypto.randomBytes(16).toString('hex'); const query = { appKey: process.env.YUGU_APP_KEY, nonce: crypto.randomBytes(16).toString('hex'), timestamp: String(Math.floor(Date.now() / 1000)), }; const payload = Object.keys(query).sort().map((k) => `${k}=${query[k]}`).join('&'); const signature = crypto.createHmac('sha256', process.env.YUGU_SECRET_KEY).update(payload, 'utf8').digest('base64'); const ws = new WebSocket('wss://open.shengzhiai.com/sent.eval.cn?' + new URLSearchParams({ ...query, signature })); const wav = await readFile('audio.wav'); const pcm = wav.subarray(wav.indexOf('data') + 8); ws.on('message', (data, isBinary) => { if (isBinary) return; const msg = JSON.parse(data.toString()); if (msg.event === 'connected') { // 参数帧一轮只发一次,幂等键放在参数帧里 ws.send(JSON.stringify({ refText: '今天天气很好', language: 'zh-CN', realtime_feedback: true, idempotencyKey })); } else if (msg.event === 'started') { for (let i = 0; i < pcm.length; i += 640) ws.send(pcm.subarray(i, i + 640)); ws.send(JSON.stringify({ cmd: 'end' })); } else if (msg.eof === 0) { console.log('进度', msg.result.bytes); } else if (msg.eof === 1) { console.log('recordId', msg.recordId, '总分', msg.result.overall); ws.close(); } else if (msg.event === 'error') { console.error('错误', msg.code, msg.message); ws.close(); } }); ``` ## 握手参数 query 参数与[原生实时评测](https://open.shengzhiai.com/sdk/v2/api/ws-evaluate/#握手参数)相同,路径中的 coreType 取值同[声通兼容整段评测](https://open.shengzhiai.com/sdk/v2/api/evaluate-compat/#路径参数)。 ## 会话流程 `wss://open.shengzhiai.com/{coreType}`,coreType 取值同[声通兼容整段评测](https://open.shengzhiai.com/sdk/v2/api/evaluate-compat/#路径参数)。 1. 服务端发 `{"event": "connected", "coreType": "sent.eval.cn"}`。 2. 客户端发参数帧 `{"refText": "北京你好", "language": "zh-CN", "realtime_feedback": true, "idempotencyKey": "..."}`,也接受 `text` 代替 `refText`。不要朗读进度时不带 `realtime_feedback`。服务端回 `{"event": "started", "coreType": "sent.eval.cn"}`。参数帧会清空这一轮已收的音频,一轮只发一次。 3. 客户端发音频,规则同[原生实时评测](https://open.shengzhiai.com/sdk/v2/api/ws-evaluate/)。`realtime_feedback` 为 true 时服务端在收音过程中下发朗读进度帧,见[朗读进度](https://open.shengzhiai.com/sdk/v2/api/ws-progress/)。 4. 客户端发 `{"cmd": "end"}` 或 `{"end": true}`,服务端回 `{"recordId": ..., "eof": 1, "result": {...}}`,重放结果带 `"replayed": true`。 参数帧带 `"timing": 1` 时终评帧另有 `timing` 对象,见[声通兼容整段评测](https://open.shengzhiai.com/sdk/v2/api/evaluate-compat/#分段耗时)。握手可协商 `permessage-deflate`,服务端下发的帧随之压缩,段落题终评帧可小到原来的十分之一左右。 ## 客户端消息 | 帧字段 | 类型 | 必填 | 说明 | |---|---|---|---| | 参数帧 | 文本帧 | 是 | `{"refText": ..., "language": ..., "realtime_feedback": true, "idempotencyKey": ...}`,也接受 `text` 代替 `refText`,一轮只发一次 | | 二进制帧 | 音频 | 是 | 规则同原生实时评测 | | `{"cmd":"end"}` | 文本帧 | 是 | 结束帧,也接受 `{"end": true}` | | `{"cmd":"ping"}` | 文本帧 | 否 | 应用层心跳 | ## 服务端消息 | 帧字段 | 类型 | 必填 | 说明 | |---|---|---|---| | `{"event":"connected"}` | 文本帧 | 是 | 连接建立,带 `coreType` | | `{"event":"started"}` | 文本帧 | 是 | 参数帧已受理 | | `{"eof":0}` | 文本帧 | 否 | 进度帧,`realtime_feedback` 为 true 时下发,`result.bytes` 为已收字节数,另带逐字的朗读进度,见[朗读进度](https://open.shengzhiai.com/sdk/v2/api/ws-progress/),不含评分 | | `{"eof":1}` | 文本帧 | 是 | 终评,带 `recordId` 与 `result`,重放时带 `"replayed": true` | | `{"event":"error"}` | 文本帧 | 否 | 错误,带 `message`,原因是服务端业务错误时另带 `code` | | `{"event":"pong"}` | 文本帧 | 否 | 心跳回复 |