> ## Documentation Index
> Fetch the complete documentation index at: https://docs.aihubmix.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 实时语音转录

> 通过 WebSocket 建立持久连接，边说边出字，实现低延迟的流式语音转文本输出

## 介绍

实时语音转录通过 WebSocket（一种在客户端与服务端之间保持长连接、可双向推送数据的协议）建立持久连接，将持续输入的音频流**边接收、边转写、边返回**，适合对延迟敏感的语音场景。

它与文件转录 [STT](https://docs.aihubmix.com/cn/api/STT.md) 的区别：

| 维度 | 文件转录（STT） | 实时转录（本页） |
| - | - | - |
| 协议 | HTTP，一次请求返回完整结果 | WebSocket，持续推送增量结果 |
| 输入 | 完整音频文件（≤25MB） | 连续音频流（PCM 分片） |
| 延迟 | 等整段处理完 | 说话过程中即返回文字 |
| 适用 | 录音转写、字幕生成 | 会议实时字幕、语音助手、直播听写 |

**可用模型：**

* **gpt-live-transcribe**：流式转录模型，支持多语言，随音频输入实时输出转写文本。

> [!WARNING]
>
> **本接口面向服务端集成，浏览器无法直连。** 出于安全考虑，网关会校验并拒绝带 `Origin` 头的连接、拒绝 `openai-insecure-api-key` 子协议，密钥只接受标准 `Authorization` 头。浏览器发起的 WebSocket 会自动附带 `Origin` 头，因此会被拒绝。若需在前端做实时转录，请在你自己的服务端建立到网关的连接、再把结果转发给前端。

## 快速开始

### 连接端点

```text theme={null}
wss://aihubmix.com/v1/realtime?intent=transcription&model=gpt-live-transcribe
```

* `intent=transcription`：**必填**，声明这是一条转录会话。
* `model=gpt-live-transcribe`：**必填**，模型在连接时由 URL 参数定死，会话中不可再更改（见下方约束）。

### 鉴权

握手时通过标准 HTTP 头传入密钥：

```text theme={null}
Authorization: Bearer $AIHUBMIX_API_KEY
```

### 音频格式要求

当前仅支持一种输入格式，发送前请将音频转换为：

* **编码**：PCM16（16 位有符号整数，小端序）
* **采样率**：24000 Hz
* **声道**：单声道（mono）

即 `audio/pcm@24000`。发送非该格式（如 G.711/µ-law）会被拒绝并关闭会话。

> [!NOTE]
>
> **转录会话不支持语音活动检测（turn\_detection / VAD）**，必须显式设为 `null`。若省略或传非 `null` 值，模型推理厂商会以 `invalid_value` 拒绝转录。网关会对转发的配置强制把 `turn_detection` 置为 `null`，但仍建议你在客户端主动设为 `null` 以保持行为清晰。

## 会话配置（session.update）

连接建立后，客户端先发送一帧 `session.update` 配置转录参数。若你不发送，网关会用授权模型注入一份默认配置兜底，但推荐显式配置。

```json theme={null}
{
  "type": "session.update",
  "session": {
    "type": "transcription",
    "audio": {
      "input": {
        "format": { "type": "audio/pcm", "rate": 24000 },
        "transcription": {
          "model": "gpt-live-transcribe",
          "languages": ["en", "zh"],
          "prompt": "Meeting recording with product names and abbreviations.",
          "keywords": ["AiHubMix", "gpt-live-transcribe"],
          "delay": "low"
        },
        "turn_detection": null,
        "noise_reduction": { "type": "near_field" }
      }
    }
  }
}
```

### 配置参数

**session.type** `string` *required*

会话类型，转录场景固定为 `transcription`。

**session.audio.input.format** `object` *required*

输入音频格式，固定为 `{ "type": "audio/pcm", "rate": 24000 }`。

**session.audio.input.transcription.model** `string` *required*

转录模型。必须与连接 URL 的 `model` 一致（`gpt-live-transcribe`）。传入其它模型视为越权，会话将被以 `1008` 关闭。

**session.audio.input.transcription.languages** `string[]`

预期语言列表，数组形式（如 `["en", "zh"]`）。`gpt-live-transcribe` 用**复数** `languages`，可一次声明多种语言；指定语言能提升准确性并降低延迟。取值字典见下方 [语言代码](#language-codes)。

**session.audio.input.transcription.language** `string`

单数写法，单个 ISO-639-1 代码（如 `"en"`）。与 `languages` **二选一，不要同时传**（同时传会被以 `invalid_value` 拒绝）。官方对 `gpt-live-transcribe` 推荐用复数 `languages`；单数 `language` 网关也接受，便于从旧代码迁移。

**session.audio.input.transcription.prompt** `string`

自由文本提示，描述录音场景（如「客服通话」「含医学术语的问诊」），帮助模型贴合语域。实测服务端会在 `session.updated` 中原样回显，已生效。

**session.audio.input.transcription.keywords** `string[]`

字面提示词数组，用于产品名、缩写、专有名词等易错词（如 `["AiHubMix", "gpt-live-transcribe"]`）。它是**提示**而非强制输出；每个词单独一项，避免包含 `<`、`>`、换行符。实测已回显生效。

**session.audio.input.transcription.delay** `string`

延迟 / 准确率档位，可选值 `minimal`、`low`、`medium`、`high`、`xhigh`，档位越高越准但延迟越大。**注意**：网关接受该字段（不报错），但实测未在 `session.updated` 中回显，生效性以官方文档为准、暂未由回显确认。

**session.audio.input.turn\_detection** `null` *required*

语音活动检测。转录会话必须为 `null`。

**session.audio.input.noise\_reduction** `object`

可选的降噪配置，如 `{ "type": "near_field" }`（近场，适合麦克风贴近说话人）或 `{ "type": "far_field" }`（远场）。

### 语言代码（language codes）

`languages` / `language` 的取值遵循以下格式，**大小写敏感、必须是下方支持的形式**，传入不支持或格式错误的代码会被 realtime API 拒绝：

| 类别 | 示例 | 说明 |
| - | - | - |
| ISO 639-1（两位） | `en`、`zh`、`es`、`fr`、`ja` | 最常用，一门语言一个两位码 |
| 部分 ISO 639-3（三位） | `eng`、`spa`、`yue`、`cmn` | 用于区分方言，如 `yue`=粤语、`cmn`=普通话 |
| 地区化中文 | `zh-cn`、`zh-tw`、`zh-hk` | 语言 + 地区，细分简繁与港台用词 |

> [!TIP]
>
> 用 `languages` 复数时，把最可能出现的语言排在前面。多语混说场景（如中英夹杂）可写 `["zh", "en"]`；单一语言直接写 `["en"]` 即可，比不指定更准更快。

## 发送音频

将 PCM16 音频切成小片（如每 100ms 一片），base64 编码后通过 `input_audio_buffer.append` 事件持续发送：

```json theme={null}
{
  "type": "input_audio_buffer.append",
  "audio": "<base64 编码的 PCM16 音频片段>"
}
```

由于转录会话不启用 VAD（语音活动检测），服务端不会自动判断一段话何时结束。发完一段音频后，需**手动**发一帧 `input_audio_buffer.commit` 标记该段结束，触发转录收尾并返回 `completed` 结果：

```json theme={null}
{ "type": "input_audio_buffer.commit" }
```

## 接收转录结果

服务端会持续推送事件，关键事件类型：

**session.created / session.updated** `event`

会话创建、配置更新的确认。

**conversation.item.input\_audio\_transcription.delta** `event`

转录**增量**结果，字段 `delta` 是本次新增的文字片段。边说边返回，适合实时上屏。

**conversation.item.input\_audio\_transcription.completed** `event`

一段语音转录**完成**，字段 `transcript` 是该段的完整文本。

**error** `event`

错误事件，包含错误码与说明。

## 完整示例

下面给出两种写法，任选其一：

* **OpenAI 官方 SDK（推荐）**：无需手写 WebSocket，把 `websocket_base_url`（SDK 的 WebSocket 基址参数）指到网关即可复用官方库。
* **原生 websockets**：不装 SDK，直接按协议收发帧，依赖最少、便于排查。

> [!NOTE]
>
> **为什么官方 demo 不传模型名，我们却要传？** OpenAI 的转录 intent 把模型放在 `session.update` 的 `transcription.model` 里，连接 URL 只带 `?intent=transcription`。AiHubMix 网关不同：模型名**必须**出现在握手 URL（`?model=gpt-live-transcribe`），因为网关在 WebSocket 握手那一刻就要用它**选择模型推理商、鉴权、预留额度**，而 `session.update` 是握手完成之后才到、来不及。所以用 SDK 时要给 `connect()` 显式传 `model`（SDK 会把它拼进 URL query）；缺了它网关在**握手期就回 `400 missing_model_parameter`**，`connect()` 直接抛异常、连接根本建不起来，压根进不到 `session.update` 那一步。会话内 `transcription.model` 仍需与 URL 一致。

```python Python (OpenAI SDK) theme={null}
# 依赖: pip install "openai[realtime]"
import asyncio
import base64
from openai import AsyncOpenAI

# 复用官方 SDK 只需三处适配:
#   1) websocket_base_url 指向 AiHubMix 网关(而非 OpenAI 默认地址)
#   2) connect() 显式传 model,见上方 Note,网关握手必需
#   3) extra_query 带上 intent=transcription
client = AsyncOpenAI(
    api_key="sk-***",  # 替换为你的 AiHubMix API 密钥
    websocket_base_url="wss://aihubmix.com/v1",
)

async def main():
    async with client.realtime.connect(
        model="gpt-live-transcribe",           # 必传: 落进握手 URL,网关据此选路+计费
        extra_query={"intent": "transcription"},
    ) as conn:
        # 1) 配置转录会话
        await conn.session.update(session={
            "type": "transcription",
            "audio": {"input": {
                "format": {"type": "audio/pcm", "rate": 24000},
                "transcription": {
                    "model": "gpt-live-transcribe",
                    "languages": ["en", "zh"],
                    "prompt": "Meeting recording with product names and abbreviations.",
                    "keywords": ["AiHubMix", "gpt-live-transcribe"],
                },
                "turn_detection": None,
                "noise_reduction": {"type": "near_field"},
            }},
        })

        # 2) 读取本地 PCM16 / 24kHz / 单声道 裸音频,分块发送
        async def send_audio():
            with open("audio_pcm16_24k.raw", "rb") as f:
                pcm = f.read()
            chunk = 24000 * 2 // 10  # 100ms = 采样率 × 2字节 ÷ 10
            for i in range(0, len(pcm), chunk):
                await conn.input_audio_buffer.append(
                    audio=base64.b64encode(pcm[i:i + chunk]).decode()
                )
                await asyncio.sleep(0.1)  # 模拟实时节奏
            # 无 VAD,音频发完后手动 commit 触发转录收尾
            await conn.input_audio_buffer.commit()

        asyncio.create_task(send_audio())

        # 3) 接收转录结果
        async for evt in conn:
            etype = getattr(evt, "type", "")
            if etype.endswith("transcription.delta"):
                print(getattr(evt, "delta", ""), end="", flush=True)
            elif etype.endswith("transcription.completed"):
                print("\n[完成]", getattr(evt, "transcript", ""))
                break  # 拿到完整结果即可退出
            elif etype == "error":
                print("\n[错误]", evt.to_dict())
                break

asyncio.run(main())
```

```python Python (websockets) theme={null}
import asyncio
import base64
import json
import websockets

API_KEY = "sk-***"  # 替换为你的 AiHubMix API 密钥
URL = (
    "wss://aihubmix.com/v1/realtime"
    "?intent=transcription&model=gpt-live-transcribe"
)

async def main():
    # websockets >= 13 用 additional_headers；旧版本用 extra_headers
    async with websockets.connect(
        URL, additional_headers={"Authorization": f"Bearer {API_KEY}"}
    ) as ws:
        # 1) 配置转录会话
        await ws.send(json.dumps({
            "type": "session.update",
            "session": {
                "type": "transcription",
                "audio": {"input": {
                    "format": {"type": "audio/pcm", "rate": 24000},
                    "transcription": {"model": "gpt-live-transcribe", "language": "en"},
                    "turn_detection": None,
                    "noise_reduction": {"type": "near_field"},
                }},
            },
        }))

        # 2) 读取本地 PCM16 / 24kHz / 单声道 裸音频，分块发送
        async def send_audio():
            with open("audio_pcm16_24k.raw", "rb") as f:
                pcm = f.read()
            chunk = 24000 * 2 // 10  # 100ms = 采样率 × 2字节 ÷ 10
            for i in range(0, len(pcm), chunk):
                await ws.send(json.dumps({
                    "type": "input_audio_buffer.append",
                    "audio": base64.b64encode(pcm[i:i + chunk]).decode(),
                }))
                await asyncio.sleep(0.1)  # 模拟实时节奏
            # 无 VAD，音频发完后手动 commit 触发转录收尾
            await ws.send(json.dumps({"type": "input_audio_buffer.commit"}))

        asyncio.create_task(send_audio())

        # 3) 接收转录结果
        async for msg in ws:
            evt = json.loads(msg)
            etype = evt.get("type", "")
            if etype.endswith("transcription.delta"):
                print(evt.get("delta", ""), end="", flush=True)
            elif etype.endswith("transcription.completed"):
                print("\n[完成]", evt.get("transcript", ""))
                break  # 拿到完整结果即可退出
            elif etype == "error":
                print("\n[错误]", evt.get("error"))
                break

asyncio.run(main())
```

```bash 连接测试 (wscat) theme={null}
# 用 wscat 快速验证连通性与鉴权（需先 npm i -g wscat）
wscat -c "wss://aihubmix.com/v1/realtime?intent=transcription&model=gpt-live-transcribe" \
  -H "Authorization: Bearer $AIHUBMIX_API_KEY"

# 连接成功后，粘贴一帧 session.update 即可开始（音频需自行 base64 后用 input_audio_buffer.append 发送）
```

> [!TIP]
>
> 把任意音频转成本接口要求的裸 PCM 格式，可用 ffmpeg：
>
> ```bash theme={null}
> ffmpeg -i input.mp3 -f s16le -acodec pcm_s16le -ac 1 -ar 24000 audio_pcm16_24k.raw
> ```

## 运行效果（线上实测）

以下为上述示例在 `aihubmix.com` 线上环境（模型 `gpt-live-transcribe`）的真实运行结果。配置了 `languages: ["en", "zh"]` + `prompt` + `keywords` + `delay: "low"` + `noise_reduction: { "type": "near_field" }`：

```text theme={null}
# 1) 服务端回显确认（session.updated）: languages / prompt / keywords / noise_reduction 均原样回显
session.updated  session.audio.input.transcription = {
                   "model": "gpt-live-transcribe",
                   "language": null,
                   "languages": ["en", "zh"],
                   "keywords": ["AiHubMix", "gpt-live-transcribe"],
                   "prompt": "Product demo recording, contains the brand name AiHubMix."
                 }
                 noise_reduction = { "type": "near_field" }   turn_detection = null
                 # 注意：请求里传的 delay 字段未出现在回显中

# 2) 音频（PCM16 / 24kHz / 单声道）分块发送并 commit 后，转录逐字增量返回（delta），末尾给出完整文本（completed）
Hello, this is a real-time transcription test for AIHubMix. The weather is really nice today
[完成] Hello, this is a real-time transcription test for AIHubMix. The weather is really nice today
```

> [!NOTE]
>
> 实测中 `languages`、`prompt`、`keywords`、`noise_reduction` 均被服务端在 `session.updated` 中原样回显，说明配置已实际生效（并非仅接受不处理）。`delay` 字段网关接受但不回显，生效性以官方文档为准；`language`（单数）与 `languages`（复数）二者只能传其一。

## 计费说明

* **单价**：`gpt-live-transcribe` 按 **\$0.017 / 分钟** 计费（以[模型详情页](https://aihubmix.com/model/gpt-live-transcribe)实时挂牌价为准）。
* 按**转录的音频时长**计费：以实际转发到转录模型的音频秒数为准，向上取整到整秒。例如转录 90 秒音频，计费 `90 ÷ 60 × $0.017 = $0.0255`。
* 计费不受网络往返或空闲等待影响，只对真正送入转录的音频计时。
* **边用边结算**：这是一条长连接，费用**在会话过程中分段实时扣除**，无需等会话结束再一次性结算。建立会话时会先按约 1 分钟用量做一次**额度预留**（仅作准入校验，不是真实扣费）；会话中每 20 秒滚动续期一次预留，实际费用按真实转发秒数分段扣除，会话结束后释放剩余预留。**因此账户可用余额至少要够约 1 分钟的用量，会话才能建立。**
* 在 [用量与账单](https://aihubmix.com) 的消费明细里，每条实时转录记录的**备注**会标注「每分钟单价」与「本次实收秒数」，方便逐条核对。

![gpt-live-transcribe 实时转录在用量与账单 Activity 里的计费记录，备注显示按 7 秒音频、每分钟 \$0.017 计费](https://mintcdn.com/aihubmix/NHavMnNP2PBQnyvP/public/cn/realtime-transcription-billing.png?fit=max\&auto=format\&n=NHavMnNP2PBQnyvP\&q=85\&s=ab7d655e49bfe8b29a0f63d9b9a4ad1c)

用量与账单 Activity 中的一条 gpt-live-transcribe 实时转录计费记录，备注标注按 7 秒音频 × $0.017 / 分钟计费，实收 $0.001982，与上文口径一致。

## 限制与约束

1. **单会话时长**：一条 WebSocket 连接最长 **62 分钟**，到时服务端主动关闭；需要更长请分段重连。
2. **空闲断连**：客户端与模型双方都没有活动持续 **5 分钟**时，服务端关闭会话（关闭码 `1008`，原因 `idle_timeout`）。任意一方有活动都会重置计时。
3. **余额不足**：分两种情况：**建立会话时**可用余额不足以覆盖约 1 分钟的预留额度，握手直接被拒（HTTP 403），会话不会建立；**会话进行中**若余额耗尽（每 20 秒的续期校验或分段扣费后复查发现），已建立的连接会被立即关闭。
4. **仅服务端**：不支持浏览器直连（校验 `Origin` 头），请在服务端集成。
5. **模型锁定**：模型在连接 URL 定死，会话中通过 `session.update` 改模型会被拒绝并关闭会话。
6. **格式锁定**：仅支持 `audio/pcm@24000` 单声道，其它格式会被拒绝。

## 常见错误

| 场景 | 关闭码 / 状态 | 说明 |
| - | - | - |
| 服务未开启 | HTTP 403 `realtime_disabled` | 实时转录未对该环境开放 |
| 携带 `Origin` 头 / 浏览器直连 | 握手被拒 | 改用服务端连接 |
| 会话中改模型 | `1008` `model_override_forbidden` | 模型只能在连接 URL 指定 |
| 音频格式非 PCM | `1008` `audio_format_unsupported` | 转成 `audio/pcm@24000` |
| 建连时余额不足 | HTTP 403 `insufficient_user_quota` | 余额不足以预留约 1 分钟用量，充值后重试 |
| 会话中余额耗尽 | 连接被关闭 | 充值后重连 |
| 达到最长时长 | `1000` `session_duration_limit` | 已达 62 分钟上限，重新连线后继续 |
| 双方空闲 5 分钟 | `1008` `idle_timeout` | 任一方有活动即重置计时 |

***

更新时间：2026-09-16


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.