实时转写
通过 Muse Voice Transcribe WebSocket 持续发送 PCM 音频并接收实时转写事件。
Muse Voice Transcribe 实时接口通过持久 WebSocket 接收裸 PCM 音频,并持续返回进度、语音轮次和转写事件。该接口只生成文本,不生成语音回复。
wss://api.agtcloud.cn/v1/asr/realtime认证与连接
WebSocket 建立后,应在 10 秒内发送首个 JSON 文本帧。Bearer API Key 位于 authorization.accessToken,不是 WebSocket 握手 Header。
| 项目 | 值 |
|---|---|
| 连接地址 | wss://api.agtcloud.cn/v1/asr/realtime |
| 鉴权位置 | 首个 JSON 文本帧的 authorization.accessToken |
| 模型 | muse-voice-transcribe-1.0 |
| 可选查询参数 | sessionId,仅透传会话标识,不表示支持断线恢复或历史会话查询 |
首帧不包含 type,最大为 64 KiB。收到包含 sessionId 的成功响应后,再开始发送音频;WebSocket 升级成功不代表首帧鉴权已经通过。
会话流程
- 建立 WebSocket 连接。
- 发送包含鉴权、模型和音频配置的首帧。
- 等待服务端返回
sessionId。 - 按实时速率发送裸 PCM 二进制帧,同时持续接收事件。
- 音频结束后发送
{"type":"endStream"}。 - 继续接收尾部转写和关闭消息。
speechComplete 只表示一个语音轮次完成,不表示整个会话结束。发送 endStream 后也应继续读取服务端事件,直到连接关闭。
首帧参数
| 字段 | 类型 | 说明 |
|---|---|---|
authorization.accessToken | string | 必填,格式为 Bearer your-api-key |
model | string | 必填,使用 muse-voice-transcribe-1.0 |
audioEncoding | string | 必填,实时音频使用 PCM_16KHZ 或 PCM_24KHZ |
mode | string | 可选:PUSH_TO_TALK、ENDPOINTING、DIARIZATION |
partialMode | string | 可选:CUMULATIVE 返回累计文本,DELTA 返回新增文本 |
keywords | string[] | 可选,提示术语或专名 |
languageBias | string[] | 可选,提示预期语言 |
emitAudioProgress | boolean | 可选,是否返回音频处理进度事件 |
zdrOverride | boolean | 可选的数据处理选项;是否生效取决于当前服务和账户权限 |
音频输入
音频必须是不含 WAV 文件头的 PCM16 little-endian、单声道数据,并与首帧声明的 16 kHz 或 24 kHz 采样率一致。每个音频帧不超过 1 MiB,字节长度必须为偶数。
建议以 100 ms 为一个音频块并按实时速率发送:24 kHz 对应 4800 字节,16 kHz 对应 3200 字节。静音期间如需维持连续时间线,也应发送相同格式的 PCM 静音数据。
# 生成 24 kHz、单声道、PCM16 裸音频
ffmpeg -i input.mp3 -ac 1 -ar 24000 -c:a pcm_s16le -f s16le audio.pcmPython 示例
安装 WebSocket 客户端并准备好 audio.pcm 后运行示例。发送和接收在不同线程中进行,避免音频上传阻塞服务端事件处理。
python -m pip install websocket-clientimport json
import sys
import threading
import websocket
url = "wss://api.agtcloud.cn/v1/asr/realtime"
stop = threading.Event()
sender = None
ws = websocket.create_connection(url, timeout=40, enable_multithread=True)
def send_audio():
try:
with open(sys.argv[1], "rb") as audio:
while not stop.is_set():
chunk = audio.read(4800)
if not chunk:
break
if len(chunk) % 2:
raise ValueError("PCM 字节数必须为偶数")
ws.send_binary(chunk)
if stop.wait(len(chunk) / 48000):
return
if not stop.is_set():
silence = bytes(4800)
for _ in range(10):
ws.send_binary(silence)
if stop.wait(0.1):
return
ws.send(json.dumps({"type": "endStream"}))
except Exception as exc:
print("发送中断:", exc)
stop.set()
ws.close()
try:
ws.send(json.dumps({
"authorization": {"accessToken": "Bearer your-api-key"},
"model": "muse-voice-transcribe-1.0",
"audioEncoding": "PCM_24KHZ",
"mode": "ENDPOINTING",
"partialMode": "CUMULATIVE",
"languageBias": ["Mandarin Chinese"],
"emitAudioProgress": True
}))
ack = json.loads(ws.recv())
if ack.get("type") == "error" or not ack.get("sessionId"):
raise RuntimeError("连接初始化失败: " + json.dumps(ack, ensure_ascii=False))
print("sessionId:", ack["sessionId"])
sender = threading.Thread(target=send_audio, daemon=True)
sender.start()
while not stop.is_set():
message = ws.recv()
if not message:
break
event = json.loads(message)
print(json.dumps(event, ensure_ascii=False))
if event.get("type") == "error":
break
finally:
stop.set()
ws.close()
if sender is not None:
sender.join(timeout=2)事件 Reference
audioProcessedMs 表示累计处理进度,不应把多个事件的值相加。使用 CUMULATIVE 时,新的部分文本替换上一份累计结果;使用 DELTA 时,按增量追加文本。
audioProgress
返回当前累计处理的音频进度。
| 字段 | 类型 | 必填 | 说明 | 示例值 |
|---|---|---|---|---|
type | 字符串 | 是 | 固定为 audioProgress。 | audioProgress |
audioProcessedMs | 整数 | 是 | 累计处理的音频时长,单位为毫秒。 | 1600 |
{
"type": "audioProgress",
"audioProcessedMs": 1600
}speechComplete.transcript 包含当前语音轮次的完整转写结果。会话收尾时,final: true 的 transcript 事件可能不包含文本;客户端不应使用空字符串覆盖已经保存的累计文本或 speechComplete.transcript。
会话限制与错误处理
会话最长约 60 分钟。连续 15 秒没有客户端消息可能触发输入收尾;网络或服务端状态也可能提前结束连接。客户端应设置总时长、读取超时和有限重试策略。
| 关闭码 | 含义 | 处理建议 |
|---|---|---|
1008 | 策略或协议错误 | 检查首帧结构、鉴权、参数以及后续控制消息 |
1011 | 服务端或上游中断 | 保存 sessionId、错误事件和关闭原因后再决定是否重试 |
1013 | 服务暂时不可用或限流 | 使用退避策略进行有限重试 |
计费
实时转写按已处理音频时长计费,具体价格以当前站点的模型广场为准。进度事件中的毫秒值用于表示处理时间线,不应作为独立账单逐条累加。