豆包实时语音接口
通过 WebSocket 调用豆包实时语音,包含连接鉴权、音频上传、流式回复和 Python 示例
豆包实时语音接口通过 WebSocket 收发 JSON 事件,可用于语音对话、语音识别结果接收及指定文本朗读。调用时使用 AiCore 平台的 API 令牌和 doubao-realtime 模型。
开始前,请在您使用的平台控制台创建 API 令牌,确认账号和令牌具有 doubao-realtime 的使用权限,并获取平台提供的接口地址。
连接与鉴权
{REALTIME_BASE_URL}/v1/realtime?model=doubao-realtimeHTTP GET 升级为 WebSocket 后,使用 JSON 文本帧进行双向通信。
REALTIME_BASE_URL 表示您使用的 WebSocket 基础地址,请根据平台提供的接口域名填写:
- 使用
wss://协议,地址格式为wss://<接口域名>;如接口地址包含端口,也需保留。 - 基础地址不包含
/v1、/realtime或查询参数,完整路径由示例代码拼接。 - 如果平台提供的是 HTTPS 接口地址,将协议
https://改为wss://,并移除末尾已有的/v1。
文中的 <接口域名> 是占位符,运行前需替换为实际接口域名。接口地址变化时,只需更新基础地址配置。
| 参数名 | 位置 | 必填 | 描述 |
|---|---|---|---|
| model | Query | 是 | 填写 doubao-realtime;账号所在分组和令牌需有该模型权限 |
| Authorization | Header | 是 | Bearer YOUR_API_KEY,使用平台签发的 sk- 开头令牌 |
每条 WebSocket 文本消息对应一个完整 JSON 对象,通过顶层 type 区分事件。音频以 Base64 字符串放入 JSON,不直接发送二进制音频帧,也不使用 SSE。
本接口兼容部分 OpenAI Realtime WebSocket 事件。会话启动、文本输入和工具调用仍有豆包特有约定,具体见下方“兼容范围”。本文不提供 WebRTC 或临时令牌接口。
创建会话
连接后首先发送 session.update 创建会话,再等待 session.created。同一连接中再次发送 session.update 可更新配置,成功后返回 session.updated。
{
"type": "session.update",
"event_id": "evt_session_1",
"session": {
"audio": {
"input": {
"format": { "type": "audio/pcm", "rate": 24000 }
},
"output": {
"format": { "type": "audio/pcm", "rate": 24000 },
"voice": "zh_female_vv_jupiter_bigtts"
}
}
},
"extension": {
"asr": {},
"tts": { "audio_config": { "format": "pcm_s16le" } },
"dialog": {}
}
}会话参数
| 参数名 | 类型 | 必填 | 描述 |
|---|---|---|---|
| type | string | 是 | 使用 session.update |
| event_id | string | 否 | 客户端事件标识,建议每条消息使用不同值以便排查问题 |
| session | object | 是 | 会话配置对象 |
| session.instructions | string | 否 | 对话指令;实际支持范围以豆包模型为准 |
| session.audio.input.format | object | 否 | 示例使用 audio/pcm、24000 Hz;上传音频应与此配置一致 |
| session.audio.output.format | object | 否 | 示例请求 audio/pcm、24000 Hz 输出 |
| session.audio.output.voice | string | 是 | 豆包音色 ID;示例采用官方文档中的 zh_female_vv_jupiter_bigtts,不要直接填 OpenAI 的音色名 |
| session.tools | array | 否 | 工具声明,使用 type、name、description、parameters 的函数定义结构 |
| extension | object | 否 | 豆包扩展配置,包含 asr、tts、dialog,按官方文档填写 |
模型通过连接 URL 中的 model=doubao-realtime 指定,不需要在 session 中填写模型版本。示例通过 extension.tts.audio_config.format=pcm_s16le 明确请求 24000 Hz、单声道、16 位有符号小端 PCM 输出。pcm 可返回 Float32 音频,不能直接按 PCM16 播放。extension 也可以放在 session.extension 中,两种位置均支持。
会话创建响应示例:
{
"type": "session.created",
"event_id": "event_1",
"session": { "id": "your-session-id" }
}session.created 可能只包含 session.id,客户端应保存自己发送的音频配置,不要依赖服务端返回完整的 OpenAI session 对象。
发送音频与文本
音频输入
推荐客户端上传 24000 Hz、单声道、16 位有符号小端 PCM,按 20 ms 一包发送,每包原始数据为 960 字节,并将会话输入采样率设置为 24000 Hz。
{
"type": "input_audio_buffer.append",
"event_id": "evt_audio_1",
"audio": "BASE64_ENCODED_PCM"
}audio 只包含原始 PCM 数据的 Base64,不包含 WAV 文件头或 data:audio/...;base64, 前缀。MP3、WAV 文件不能直接整体放入该字段;浏览器 MediaRecorder 常见的 WebM/Opus 输出也不是此示例的输入格式。
发送录音文件时同样需要按照音频时长控制发送速度,20 ms 音频间隔约 20 ms 发送,不能一次性把整段录音高速发送给服务端。有效语音发送完毕后,可以强制判停:
{ "type": "input_audio_buffer.commit", "event_id": "evt_commit_1" }input_audio_buffer.committed 只表示输入已提交,不代表回复完成。全静音数据不保证触发回复;收到 response.done 后再结束本轮处理。
麦克风暂停与恢复
停止上传音频时发送静音事件,恢复上传前发送取消静音事件。仅停止发送音频可能触发会话超时,WebSocket Ping 不能代替这些应用层事件。
{ "type": "input_audio_mute.commit" }{ "type": "input_audio_unmute.commit" }指定文本朗读
打招呼或朗读固定文本时,使用豆包扩展事件,将文本直接放在 speech_text_buffer.commit 的 text 字段:
{
"type": "speech_text_buffer.commit",
"event_id": "evt_greeting_1",
"text": "你好,这是豆包实时语音接口测试。"
}需要分段提交待合成文本时,可依次发送 speech_text_buffer.replacement.append(包含 text)和 speech_text_buffer.replacement.commit。这些事件用于指定朗读内容,不是向模型发起任意文本问答。
接收事件与结束会话
| 客户端收到的事件 | 含义 | 处理方式 |
|---|---|---|
| session.created / session.updated | 会话创建或更新确认 | 保存会话 ID,收到确认后继续发送 |
| input_audio_buffer.committed | 输入音频已提交 | 继续等待识别和回复事件 |
| conversation.item.input_audio_transcription.delta / completed | 输入语音转录 | 根据实际事件字段处理增量或最终文本 |
| response.output_text.delta | 模型回复文本 | 按顺序处理 delta,兼容保留的豆包字段 |
| response.audio.started | 音频输出开始 | 准备接收音频 |
| response.audio.delta | 音频分片 | Base64 解码 delta,按到达顺序送入播放器或写入文件 |
| response.audio.done | 音频输出结束 | 完成音频处理,继续等待 response.done |
| response.done | 本轮交互结束 | 完成本轮处理,可继续下一轮或关闭会话 |
| response.canceled | 打断确认 | 根据应用需要清理待播放音频 |
| session.closed | 会话关闭确认 | 随后关闭 WebSocket |
| error | 请求或服务端错误 | 读取 error.type、error.code、error.message |
本接口返回的音频事件使用 response.audio.* 命名,音频分片位于 response.audio.delta 的 delta 字段。使用示例配置时请求的是 24000 Hz、单声道、16 位有符号小端 PCM,数据不带文件容器头;请根据实际输出编码播放,不要把文件扩展名直接改为 .mp3。
打断回复可以发送 {"type":"response.cancel"},同时由客户端停止播放已经缓冲的音频。
结束时先等本轮 response.done,再发送:
{ "type": "session.close", "event_id": "evt_close_1" }收到 session.closed 后再断开连接。response.audio.done 仅表示音频输出结束,请继续等待 response.done,完成本轮处理后再关闭会话。
Python 调用示例
下面的示例创建会话、朗读一段文字、保存原始音频,并在本轮交互结束后正常关闭会话。使用 Python 3.11 或更新版本:
python3 -m pip install "websockets>=14,<16"
export REALTIME_BASE_URL="wss://<接口域名>"
export XTY_API_KEY="sk-your-platform-api-key"保存为 doubao_realtime.py,运行 python3 doubao_realtime.py:
import asyncio
import base64
import json
import os
from pathlib import Path
from websockets.asyncio.client import connect
async def receive_until(ws, expected_type, audio_file):
# 限制整个阶段的等待时间,避免持续收到其他事件时无限等待。
async with asyncio.timeout(60):
while True:
event = json.loads(await ws.recv())
event_type = event.get("type")
if event_type == "error":
raise RuntimeError(json.dumps(event.get("error"), ensure_ascii=False))
if event_type == "response.audio.delta":
audio_file.write(base64.b64decode(event["delta"], validate=True))
else:
print("event:", event_type)
if event_type == expected_type:
return event
async def main():
base_url = os.environ["REALTIME_BASE_URL"].rstrip("/")
url = f"{base_url}/v1/realtime?model=doubao-realtime"
headers = {"Authorization": f"Bearer {os.environ['XTY_API_KEY']}"}
output = Path("doubao-output.pcm")
async with connect(
url, additional_headers=headers, open_timeout=15,
close_timeout=5, max_size=32 * 1024 * 1024,
) as ws:
with output.open("wb") as audio_file:
await ws.send(json.dumps({
"type": "session.update",
"session": {
"audio": {
"input": {"format": {"type": "audio/pcm", "rate": 24000}},
"output": {
"format": {"type": "audio/pcm", "rate": 24000},
"voice": "zh_female_vv_jupiter_bigtts",
},
}
},
"extension": {
"asr": {},
"tts": {"audio_config": {"format": "pcm_s16le"}},
"dialog": {},
},
}))
await receive_until(ws, "session.created", audio_file)
# 本示例不上传麦克风音频,使用静音事件通知服务端。
await ws.send(json.dumps({"type": "input_audio_mute.commit"}))
await ws.send(json.dumps({
"type": "speech_text_buffer.commit",
"text": "你好,这是豆包实时语音接口测试。",
}, ensure_ascii=False))
await receive_until(ws, "response.done", audio_file)
await ws.send(json.dumps({"type": "session.close"}))
await receive_until(ws, "session.closed", audio_file)
print("音频已保存:", output.resolve())
asyncio.run(main())该示例验证固定文本的语音输出。接入麦克风时,需要按前文格式上传 input_audio_buffer.append,并在独立接收任务中持续处理下行事件,避免等录音全部上传后才开始接收音频。
兼容范围
| 能力或参数 | 当前行为 |
|---|---|
| WebSocket JSON 文本帧、Base64 音频 | 支持 |
| session.update | 首次用于创建会话,后续用于更新会话 |
| input_audio_buffer.append / commit | 支持 PCM 音频上传与强制判停 |
| response.create | 当前被忽略;豆包通过音频判停或指定文本事件触发回复 |
| conversation.item.create 发起用户文本问答 | 当前不支持普通 OpenAI 文本轮次,会返回错误;指定文本朗读使用 speech_text_buffer.commit |
| OpenAI SDK 的完整事件对象 | 不保证完整兼容;部分事件保留豆包字段,建议按本文直接处理 JSON |
| 工具调用 | 下行 response.function_call_arguments.done 可能包含豆包 items 数组,需逐项读取 call_id、name 和 arguments,不能直接假设为 OpenAI 的单次调用结构 |
| temperature、turn_detection、modalities 等 | 当前接口不支持通过这些会话参数控制行为 |
| 音频格式 | 本文覆盖 PCM 路径;其他编码和采样率不能直接套用此示例 |
| WebRTC / 临时会话令牌 | 当前接口未提供 |