音频
这页能做什么
你可以把音频文件转成文字,或通过 WebSocket 持续发送 PCM 音频做实时转写。
前置条件
- 已有 LokoSpace API Key,请求头使用
Authorization: Bearer <你的 LokoSpace API Key> - 账号积分足够支付本次识别
- 文件转写模型出现在
GET /audio/models的返回里 - 实时语音模型还必须满足
supports_realtime: true,并且实时入口已开通 - 文件转写单个文件不超过 25MB;实时语音只接受 PCM 16-bit
- 实时语音是单独开通的能力,未开通时握手直接返回 404
可用模型
curl https://sheverai.com/8811c8/api/llm/v1/audio/models \
-H "Authorization: Bearer $LOKO_KEY"
{
"object": "list",
"data": [
{
"id": "<音频模型 ID>",
"object": "model",
"display_name": "示例音频模型",
"capabilities": ["audio"],
"protocol": "<协议标识>",
"supports_realtime": false,
"billing": {
"mode": "per_second",
"credits_per_second": "<每秒积分>",
"min_seconds": 1
}
}
]
}
| 字段 | 类型 | 说明 |
|---|---|---|
object | string | 固定为 list |
data | array | 可用音频模型 |
data[].id | string | 请求体里的 model |
data[].object | string | 固定为 model |
data[].display_name | string | 展示名称 |
data[].capabilities | array | 模型能力标签 |
data[].protocol | string | 服务端音频协议标识。判断能否实时,直接看 supports_realtime |
data[].supports_realtime | boolean | 是否可用于实时语音 |
data[].billing.mode | string | 固定为 per_second |
data[].billing.credits_per_second | number | 每秒积分 |
data[].billing.min_seconds | integer | 最短计费秒数 |
文件转写和实时语音是两个入口。文件转写通常使用 supports_realtime 为 false 的模型;只有 supports_realtime 为 true 的模型才适合实时入口。
文件转写
请求
上传本地文件:
curl https://sheverai.com/8811c8/api/llm/v1/audio/transcriptions \
-H "Authorization: Bearer $LOKO_KEY" \
-F "file=@speech.mp3" \
-F "language=zh" \
-F "enable_itn=true"
也可以传远程地址:
curl https://sheverai.com/8811c8/api/llm/v1/audio/transcriptions \
-H "Authorization: Bearer $LOKO_KEY" \
-H "Content-Type: application/json" \
-d '{
"audio_url": "https://cdn.example.com/speech.mp3",
"language": "zh"
}'
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
file | binary | 否 | 表单文件,最大 25MB。与 audio_url、audio_base64 三选一 |
audio_url | string | 否 | http(s) 或 oss:// 地址 |
audio_base64 | string | 否 | base64 音频,也可以带 data URL 前缀 |
mime_type | string | 否 | 音频 MIME,默认 audio/wav |
model | string | 否 | 音频模型 ID,不传时用服务端默认模型 |
language | string | 否 | 指定识别语言,如 zh |
enable_itn | boolean | 否 | 是否做数字、日期等文本规整,默认 false |
Idempotency-Key | string | 否 | 请求头。建议传,相同键重试返回已结算结果 |
file、audio_url、audio_base64 必须提供一个。
返回
{
"text": "今天下午三点开会。",
"language": "zh",
"duration": 12,
"usage": { "seconds": 12 },
"lokospace_billing": {
"requestId": "<请求 ID>",
"creditsUsed": "<本次扣除积分>",
"balanceAfter": "<扣除后余额>"
}
}
| 字段 | 类型 | 说明 |
|---|---|---|
text | string | 识别文本 |
language | string | null | 识别语言,可能为空 |
duration | integer | 识别音频的秒数,已经按向上取整处理;实际扣费还受 min_seconds 约束 |
usage | object | 上游模型服务返回的用量对象,字段随模型而定 |
lokospace_billing.requestId | string | 本次请求 ID |
lokospace_billing.creditsUsed | number | 本次实际扣除积分 |
lokospace_billing.balanceAfter | number | null | 扣除后余额 |
lokospace_billing.duplicate | boolean | 命中相同幂等键的重放时为 true,首次调用不返回 |
成功响应带 x-lokospace-request-id 和 x-lokospace-credits-used 两个头。
实时语音
地址与鉴权
wss://sheverai.com/8811c8/api/llm/v1/audio/realtime?model=<实时模型 ID>
model 可以省略,省略时使用服务端默认实时模型。建议显式传入 GET /audio/models 中 supports_realtime: true 的 id。
| 方式 | 写法 | 适用 |
|---|---|---|
| 请求头 | Authorization: Bearer <你的 LokoSpace API Key> | 服务端、桌面端,优先使用 |
| 查询参数 | ?token=<你的 LokoSpace API Key> | 不能设置请求头的客户端。URL 可能进入日志 |
| 首帧 | {"type":"auth","token":"<你的 LokoSpace API Key>"} | 浏览器。首帧必须在 5 秒内发出 |
查询参数鉴权与 model 同时使用时,写成 ?model=<实时模型 ID>&token=<你的 LokoSpace API Key>。
用首帧鉴权时,首帧必须是 JSON;二进制首帧会被拒绝。首帧之后、鉴权完成前的消息会短暂缓存,最多 16 条,鉴权通过后按顺序转发。
事件
客户端发给服务端:
| 事件 | 作用 |
|---|---|
session.update | 配置音频格式、采样率、声道。开始发音频前发一次 |
input_audio_buffer.append | 发送一帧 PCM,audio 是 base64。可以连续发送 |
input_audio_buffer.commit | 结束当前语音段,触发识别 |
{
"type": "session.update",
"session": {
"input_audio_format": "pcm",
"sample_rate": 16000,
"input_audio_channels": 1
}
}
网关会原样转发 session.update,并按 input_audio_format、sample_rate、input_audio_channels 识别计费口径。字段名要与所选模型的实时协议一致。
input_audio_format 支持 pcm(16-bit PCM)、pcm16、pcm_s16le 等写法。采样率 8000 到 192000,声道 1 到 8。音频开始发送后不能改格式。
{"type":"input_audio_buffer.append","audio":"<base64 PCM>"}
{"type":"input_audio_buffer.commit"}
服务端回给客户端的事件原样转发。连接级事件有 session.created、session.updated,错误事件是 error。识别完成事件通常为 conversation.item.input_audio_transcription.completed,读取其中的 transcript;不同模型也可能返回增量事件,按 type 处理,以实际返回为准。
{
"type": "error",
"error": {
"type": "insufficient_credits",
"code": "INSUFFICIENT_CREDITS",
"message": "积分余额不足,实时语音会话已结束"
}
}
调用示例
先安装 ws,再把音频转成 16 kHz、16-bit、单声道 PCM。
const fs = require('fs');
const WebSocket = require('ws');
const key = process.env.LOKO_KEY;
const model = process.env.LOKO_REALTIME_MODEL;
const url = 'wss://sheverai.com/8811c8/api/llm/v1/audio/realtime?model='
+ encodeURIComponent(model);
const ws = new WebSocket(url, {
headers: { Authorization: 'Bearer ' + key }
});
ws.on('open', () => {
ws.send(JSON.stringify({
type: 'session.update',
session: {
input_audio_format: 'pcm',
sample_rate: 16000,
input_audio_channels: 1
}
}));
const pcm = fs.readFileSync('speech.pcm'); // 16 kHz, 16-bit, mono
const chunk = 3200; // 100 ms
let offset = 0;
const timer = setInterval(() => {
if (offset >= pcm.length) {
clearInterval(timer);
ws.send(JSON.stringify({ type: 'input_audio_buffer.commit' }));
return;
}
ws.send(JSON.stringify({
type: 'input_audio_buffer.append',
audio: pcm.subarray(offset, offset + chunk).toString('base64')
}));
offset += chunk;
}, 100);
});
ws.on('message', (raw) => {
let event;
try { event = JSON.parse(raw.toString()); } catch (_) { return; }
if (event.type === 'error') console.error(event.error);
const text = event.transcript
|| event.item?.content?.[0]?.transcript
|| event.delta;
if (text) console.log(text);
});
ws.on('close', (code, reason) => {
console.log('closed', code, reason.toString());
});
计费说明
文件转写按 per_second 计费,是后结算:识别成功后才扣费,失败不扣费。计费秒数 = ceil(max(上游返回的音频秒数, min_seconds)),且至少 1 秒;总价 = 每秒积分 × 计费秒数。调用前会按文件头估算时长做余额预检,估算失败时按 60 秒;预检不扣费,余额不足直接返回 402,不调用上游。成功结算后,相同 Idempotency-Key 重试返回原结果,不重复扣费。
实时语音也按 per_second 计费,按实际转发给上游的 PCM 字节换算秒数。默认每满 10 秒预扣一次,结束时按实际秒数多退少补;0 秒会话不扣费。余额不足时服务端发 error 并以 4402 关闭,不会继续转发音频。
1 元 = 100 积分。单价从 model_pricing 读取,文档不写死具体价格,以 GET /models 返回为准;文件转写和实时语音的每秒单价也可以在 GET /audio/models 的 billing 里看到。
错误与常见问题
| 状态 | 含义 | 处理 |
|---|---|---|
| 400 | 缺少音频、URL 非法、base64 为空、实时音频格式不支持 | 按返回的 message 修正 |
| 401 | 密钥缺失、无效、已撤销或已过期 | 重新创建或更换密钥 |
| 402 | 积分不足。实时会话内对应关闭码 4402 | 充值后重试 |
| 404 | 模型不存在或未启用;实时入口未开通也返回 404 | 重新拉取 /audio/models,确认实时能力已开通 |
| 413 | AUDIO_TOO_LARGE:base64 音频超过 25MB | 压缩或切分后重试 |
| 429 | 上游模型服务并发或频率受限;实时入口对应并发会话数上限 | 退避后重试 |
| 502 | 上游模型服务失败、超时,或没有识别出文本 | 稍后重试 |
| 503 | 没有配置按秒定价,或渠道凭证不可用 | 联系管理员检查模型定价与渠道 |
实时语音关闭码:
| 关闭码 | 含义 |
|---|---|
| 4401 | 鉴权失败或首帧超时 |
| 4402 | 积分不足 |
| 4408 | 会话达到最长时长 |
| 4429 | 同账号并发会话数已达上限 |
| 1011 | 上游连接异常或实时会话错误 |
常见问题:
- 文件转写支持哪些格式?服务端会从 WAV、MP3、MP4 的文件头估算时长,具体能识别哪些编码由模型决定。估算不到时按 60 秒做余额预检。
- 实时为什么只支持 PCM?服务端按 PCM 字节数换算计费秒数,压缩格式无法准确换算,会返回
AUDIO_REALTIME_FORMAT_UNSUPPORTED。 - 实时中途能改采样率吗?不能。开始发送音频后改格式会返回错误并结束会话。
- 实时最长多久?默认单会话 30 分钟,同一账号默认 2 路并发。
- 断线会重复扣费吗?不会按整段预估扣费,只按已经转发给上游的音频结算,差额退回。