实时语音转文字 (STT)
使用 WebSockets 将实时流式音频转写为文本
将原始音频流式传输至我们的服务器,并获取双向实时转写结果。服务器会返回中间结果(用户说话时的实时文本)以及最终结果(包含词级时间戳的完整句子)。
由于流媒体传输具有实时性和不可预测性,我们采用预付费计费模式。连接建立时,我们会立即预扣 83 积分(用于支付最初 60 秒的费用);当您断开连接时,我们会精确计算您的实际使用时长(精确到秒),并将未使用的积分自动退还至您的账户!
身份验证与连接
您必须对 WebSocket 连接进行身份验证。具体方法取决于您的客户端环境:
- 前端浏览器: 标准浏览器 WebSocket 不支持自定义 HTTP 标头。您必须将 API 密钥作为查询参数(?api_key=YOUR_KEY)包含在内。
- 后端 / Python / Node.js: 您可以使用标准的 HTTP 标头 Authorization: Bearer YOUR_API_KEY。
| 查询参数 | 必填 | 类型 | 描述 |
|---|---|---|---|
| api_key | 条件 | 字符串 | 您的 Moknah API 密钥。(如果未通过 Authorization 标头传递,则此处为必填项。) |
| language | 可选 | 字符串 | 用于转写的语言代码。默认值为 ar-JO。支持的代码包括 ar-SA、ar-EG、en-US 等。 |
音频规格
请勿通过 WebSocket 发送压缩音频文件(如 .mp3、.m4a)或带有文件头的标准 .wav 文件,否则 AI 将输出随机噪声或空字符串。该 WebSocket 仅接受原始二进制 PCM 帧。
在通过套接字发送二进制帧之前,请确保您的音频流完全符合以下要求:
- 采样率: 16,000 Hz (16 kHz)
- 位深: 16位整数 (Int16)
- 频道: 1(单声道)
- 数据块大小: 请分小块发送(例如 100ms / 3200 字节)。超过 256KB 的数据块将被拒绝。
服务器响应(JSON 事件)
当您向服务器流式传输二进制音频时,服务器将异步回复 JSON 文本帧。
| 活动类型 | 有效载荷示例 |
|---|---|
|
连接成功 认证成功后立即发送。 |
{"status": "connected", "message": "准备流式传输音频……"} |
|
部分结果 连续发送。用于实时渲染打字效果的字幕。 |
{"type": "partial", "text": "欢迎来到该平台"} |
|
最终结果 在句子完成时发送。包含时间戳。 |
{
"type": "final",
"text": "欢迎来到 Makana 平台。",
"words": [
{"word": "مرحبا", "start_time_ms": 150, "duration_ms": 300},
{"word": "بك", "start_time_ms": 460, "duration_ms": 200}
]
}
|
结束直播: When the user stops speaking, send a JSON text frame: {"command": "stop"} to safely close the connection and calculate the final refund.
集成示例
# pip install websockets pyaudio
import asyncio
import websockets
import json
import pyaudio
import sys
API_KEY = "YOUR_API_KEY"
URL = "wss://moknah.io/api/v1/stt/ws/?language=ar-SA"
# 16kHz, 16-bit, Mono (100ms chunks)
CHUNK = 1600
async def stream_live_microphone(ws):
audio = pyaudio.PyAudio()
stream = audio.open(format=pyaudio.paInt16, channels=1, rate=16000, input=True, frames_per_buffer=CHUNK)
print("🎤 Microphone is LIVE. Start speaking!")
loop = asyncio.get_running_loop()
try:
while True:
data = await loop.run_in_executor(None, stream.read, CHUNK, False)
await ws.send(data)
await asyncio.sleep(0.001)
except asyncio.CancelledError:
await ws.send(json.dumps({"command": "stop"}))
finally:
stream.stop_stream()
stream.close()
audio.terminate()
async def receive_transcriptions(ws):
async for message in ws:
data = json.loads(message)
if data.get('type') == 'partial':
sys.stdout.write(f"\r⏳ {data['text']}")
sys.stdout.flush()
elif data.get('type') == 'final':
sys.stdout.write(f"\r✅ {data['text']}\n")
sys.stdout.flush()
async def main():
headers = {"Authorization": f"Bearer {API_KEY}"}
async with websockets.connect(URL, additional_headers=headers) as ws:
await asyncio.gather(
asyncio.create_task(stream_live_microphone(ws)),
asyncio.create_task(receive_transcriptions(ws))
)
asyncio.run(main())
// 1. Connect to WebSocket
const ws = new WebSocket('wss://moknah.io/api/v1/stt/ws/?api_key=YOUR_API_KEY&language=ar-SA');
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.type === 'partial') {
console.log("Live:", data.text);
} else if (data.type === 'final') {
console.log("Final:", data.text);
}
};
// 2. Capture Microphone Audio on Connection
ws.onopen = async () => {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
// Force 16kHz sample rate for the AudioContext
const audioCtx = new AudioContext({ sampleRate: 16000 });
const source = audioCtx.createMediaStreamSource(stream);
const processor = audioCtx.createScriptProcessor(4096, 1, 1);
processor.onaudioprocess = (e) => {
if (ws.readyState === WebSocket.OPEN) {
const float32Data = e.inputBuffer.getChannelData(0);
// Convert Browser Float32 to Int16 PCM (Required by Server)
const pcm16 = new Int16Array(float32Data.length);
for (let i = 0; i < float32Data.length; i++) {
pcm16[i] = Math.max(-1, Math.min(1, float32Data[i])) * 0x7FFF;
}
// Send binary PCM audio chunk to server
ws.send(pcm16.buffer);
}
};
source.connect(processor);
processor.connect(audioCtx.destination);
};
// 3. Handle graceful shutdown
function stopRecording() {
ws.send(JSON.stringify({ command: "stop" }));
}
断开连接与错误代码
如果连接被拒绝或中断,WebSocket 将使用以下特定代码之一关闭:
| 关闭代码 | 姓名 | 描述 |
|---|---|---|
| 1000 | NORMAL_CLOSURE |
流已成功结束。使用情况已记录,并已进行相应退款。 |
| 4001 | MISSING_API_KEY |
未提供 ?api_key= 参数,或者该参数无效。 |
| 4002 | UNAUTHORIZED |
API 密钥无效、IP 受限,或点数不足以启动/继续流媒体传输。 |
| 4009 | PAYLOAD_TOO_LARGE |
您发送的音频数据块超过了 256KB。请使用较小的缓冲区进行流式传输。 |
| 4290 | CONCURRENT_LIMIT |
您的账户存在过多的活跃 WebSocket 连接。限制为 1 个活跃数据流。 |
如有关于 API 的问题或疑问,请联系我们: api@moknah.io.