用户指南 API 参考

实时语音转文字 (STT)

使用 WebSockets 将实时流式音频转写为文本

WSS wss://moknah.io/api/v1/stt/ws/

将原始音频流式传输至我们的服务器,并获取双向实时转写结果。服务器会返回中间结果(用户说话时的实时文本)以及最终结果(包含词级时间戳的完整句子)。

💳 预付费计费器 (83 点数/分钟)

由于流媒体传输具有实时性和不可预测性,我们采用预付费计费模式。连接建立时,我们会立即预扣 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 的问题或疑问,请联系我们: api@moknah.io.