同声传译
简介
同声传译通过一个 WebSocket 持续接收语音,并在同一个连接上返回双语字幕和译文语音。麦克风音频以二进制帧上行;字幕是 JSON 事件,译文语音以二进制帧下行。
WebSocket 地址
接口调用连接 wss://api.anycap.io/v1/audio/ast/stream,在握手请求头里带 Authorization: Bearer <接入密钥>。公开调用不得带 Cookie 或 Origin。使用同一密钥调用 GET /v1/audio/ast/sessions 查看会话列表、GET /v1/audio/ast/sessions/{session_id} 读取会话详情。控制台页面连接 wss://<控制台 API 主机>/api/v1/capabilities/audio.ast/stream,使用会话 Cookie:浏览器 WebSocket 无法设置请求头,服务端只放行 allowlist 中的 Origin。
import WebSocket from "ws";import { readFileSync, writeFileSync, appendFileSync } from "node:fs"; const base = process.env.ANYCAP_API_BASE || "https://api.anycap.io";const headers = { Authorization: `Bearer ${process.env.ANYCAP_API_KEY}` };const response = await fetch(`${base}/v1/audio/ast/models`, { headers });if (!response.ok) throw new Error(`Discovery failed: ${response.status}`);const { items } = await response.json();const source = items.find((item) => item.resource_id === process.env.ANYCAP_AST_SOURCE_ID) || items[0];if (!source) throw new Error("No authorized interpretation source"); // Raw 16 kHz mono s16le PCM, without a WAV header. Install: npm install wsconst pcm = readFileSync(process.env.ANYCAP_PCM_FILE || "speech.pcm");const seconds = Math.min(60, source.max_session_seconds);if (!pcm.length || pcm.length % 2 || pcm.length > seconds * 32000) { throw new Error("PCM must contain whole samples within the session limit");}const url = new URL("/v1/audio/ast/stream", base);url.protocol = url.protocol === "https:" ? "wss:" : "ws:";url.search = new URLSearchParams({ resource_id: source.resource_id, mode: "s2s", source: "zh", target: "en", seconds: String(seconds), audio_format: "pcm", audio_rate: "24000",}).toString();const socket = new WebSocket(url, { headers }); // No Cookie or Origin.let timer;let terminal = false;writeFileSync("translated.pcm", Buffer.alloc(0));socket.on("message", (data, binary) => { if (binary) return appendFileSync("translated.pcm", data); const event = JSON.parse(data.toString()); if (event.type === "status" && !timer) { // Start audio only after status, then send at approximately real-time speed. let offset = 0; timer = setInterval(() => { if (offset >= pcm.length) { clearInterval(timer); socket.send(JSON.stringify({ type: "finish" })); return; } socket.send(pcm.subarray(offset, offset + 2560)); offset += 2560; }, 80); } if (event.type === "subtitle") console.log(event.channel, event.phase, event.text || ""); if (event.type === "done" || event.type === "error") { terminal = true; clearInterval(timer); console.log(event); socket.close(); }});socket.on("unexpected-response", (_request, response) => { console.error("Handshake rejected:", response.statusCode); response.setEncoding("utf8"); response.on("data", (body) => console.error(body));});socket.on("error", (error) => console.error(error.message));socket.on("close", () => { clearInterval(timer); if (!terminal) console.error("Connection ended without a terminal event; query the session before retrying.");});查询参数
resource_id
/v1/audio/ast/models 获取的已授权来源 UUID,必需。mode
s2s 同时返回译文语音和字幕;s2t 只返回字幕。source
target
speaker_id
zh_female_vv_uranus_bigtts 或 zh_male_jingqiangkanye_emo_mars_bigtts,仅 s2s;不传则复刻源说话人的音色。seconds
audio_format
pcm(默认):24 kHz 为 float32、16 kHz 为有符号 int16,均为单声道小端;ogg_opus 固定返回 48 kHz Ogg Opus。audio_rate
客户端发给服务端
- 二进制帧为 16 kHz 单声道 s16le 麦克风音频。建议每帧 80 ms(2,560 字节),上限 64 KB。
{"type":"finish"}表示说完了,服务端把剩余字幕和语音发完后再发done。{"type":"cancel"}立即结束本次会话。- 收到 status 后再发送音频。额度按上传 PCM 字节数计算,不是连接的墙钟时长;超出额度的帧会停止输入并返回 budget_exhausted 终态错误。
服务端发给客户端
文本帧是 JSON 事件;二进制帧是按 target_format 编码的译文语音,用 status 里的采样率解码。数值为 0 或布尔值为 false 时字段可能省略,按 0/false 处理。
| 事件 | 字段 | 说明 |
|---|---|---|
status | state、session_id、mode、max_seconds、reserved_credits、target_format、target_rate | 连接建立后的第一个事件,收到后再发送音频。reserved_credits 是本次预留积分;用 target_format 和 target_rate 解码音频。 |
subtitle | channel(source/translation)、phase(start/partial/final)、text、start_ms、end_ms、speaker_changed、detected_language | start 开始一句,partial 覆盖当前句文本,final 定稿并追加该行。 |
audio | phase(start/end)、start_ms、end_ms | 标记一句译文语音的起止;音频本身以二进制帧下发。 |
muted | muted_ms | 这段输入是静音或未被识别,没有产生译文。 |
done | audio_seconds、charged_credits,可选 usage | 终态事件,给出实际语音秒数与扣费;随后连接关闭。 |
error | code、message | 终态事件。以 code 为准;已经开始计费的会话不要静默重试。 |
语种约束
| 模式 | 源语言 | 目标语言 |
|---|---|---|
s2s,不传 speaker_id | zh、en、de、fr、es、id、ja、pt | zh、en、de、fr、es、id、ja、pt |
s2s,传 speaker_id | 下列 s2t 的二十种源语言及两个方言 | zh、en |
s2t | zh、en、de、fr、es、id、ja、pt、ko、tr、ms、nl、ro、pl、cs、ar、th、vi、ru、it,以及 yue-CN、sh-CN | 上述二十种语言,不含两个方言 |
源语言和目标语言同时传 zhen 时支持中英反转互译;其他组合要求中文或英文至少在一侧。方言源语言不算这一侧,所以 yue-CN 或 sh-CN 的目标语言必须是中文或英文。不支持的语种组合返回 invalid_input。
错误
按 token 定价的来源使用上报的 input_audio_tokens、output_text_tokens、output_audio_tokens 结算,数量以十进制字符串返回,可含小数;三类金额精确合计后仅向上取整一次到整积分。audio_seconds 只表示时长,不决定 token 费用。status.token_allowance(若有)是预留额度;用量缺失、异常或超过额度时返回待核对错误,不应重新提交该会话。原有按秒来源保持原结算方式。
WebSocket 建立前的拒绝是普通 HTTP 响应,正文为 {"error":{"code":...}};建立后的失败以 error 事件下发,code 可能为 budget_exhausted、provider_failed、transport_error、settlement_error、missing_usage、invalid_usage 或 usage_exceeds_limit。
| 错误码 | 状态码 | 说明 |
|---|---|---|
temporarily_unavailable | 503 | 实时连接未能建立。 |
price_unavailable | 503 | 没有可调用的已发布价格。 |
not_found | 404 | 来源不存在。 |
insufficient_credits | 402 | 余额不足以覆盖预留。 |
rate_limited | 429 | 并发会话过多。 |
capability_unavailable | 503 | 同传服务暂时不可用,请稍后重试。 |
forbidden | 403 | 密钥、账号、来源或 Origin 不被允许。 |
unauthorized | 401 | 密钥或控制台会话缺失或已过期。 |
invalid_input | 400 | 查询参数、来源或语种组合不被支持。 |