模型能力
文本转语音
通过一次 API 调用将文本转换为语音音频。该 API 支持丰富的表达声音集、用于精细控制的内联语音标签,以及从高保真 MP3 到电话优化的 μ-law 等多种输出格式。
快速开始
通过一次 API 调用生成语音:
curl -X POST https://api.x.ai/v1/tts \
-H "Authorization: Bearer $XAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"text": "Hello! Welcome to the xAI Text to Speech API.",
"voice_id": "eve",
"language": "en"
}' \
--output hello.mp3import os
import requests
response = requests.post(
"https://api.x.ai/v1/tts",
headers={
"Authorization": f"Bearer {os.environ['XAI_API_KEY']}",
"Content-Type": "application/json",
},
json={
"text": "Hello! Welcome to the xAI Text to Speech API.",
"voice_id": "eve",
"language": "en",
},
)
response.raise_for_status()
with open("hello.mp3", "wb") as f:
f.write(response.content)
print(f"Saved {len(response.content):,} bytes to hello.mp3")import fs from "fs";
const response = await fetch("https://api.x.ai/v1/tts", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.XAI_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
text: "Hello! Welcome to the xAI Text to Speech API.",
voice_id: "eve",
language: "en",
}),
});
if (!response.ok) throw new Error(`TTS error ${response.status}`);
const buffer = Buffer.from(await response.arrayBuffer());
fs.writeFileSync("hello.mp3", buffer);
console.log(`Saved ${buffer.length.toLocaleString()} bytes to hello.mp3`);import Foundation
let apiKey = ProcessInfo.processInfo.environment["XAI_API_KEY"]!
let url = URL(string: "https://api.x.ai/v1/tts")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = try JSONSerialization.data(withJSONObject: [
"text": "Hello! Welcome to the xAI Text to Speech API.",
"voice_id": "eve",
"language": "en",
])
let (data, _) = try await URLSession.shared.data(for: request)
let fileURL = URL(fileURLWithPath: "hello.mp3")
try data.write(to: fileURL)
print("Saved \(data.count) bytes to hello.mp3")响应体包含原始音频字节。直接保存到文件或传输到音频播放器。
请求体
| 参数 | 类型 | 必需 | 描述 |
|---|---|---|---|
text | string | ✓ | 要转换为语音的文本。最大 15,000 个字符。支持 语音标签。 |
voice_id | string | 用于合成的声音。默认为 eve。参见 声音。 | |
language | string | ✓ | BCP-47 语言代码(例如 en、zh、pt-BR)或 auto 用于自动语言检测。参见 支持的语言。 |
output_format | object | 输出格式配置。默认为 24 kHz/128 kbps 的 MP3。参见 输出格式。 | |
speed | number | 语音速度倍数。1.0 为正常速度。低于 1.0 的值会减慢语音,高于 1.0 的值会加速语音。范围:0.7 到 1.5。默认为 1.0。 | |
optimize_streaming_latency | integer | 流式合成的延迟优化级别。0(默认):无优化 — 最佳音频质量。1:减少第一块大小以降低首次音频时间,在块边界处有轻微的质量折衷。2:进一步减少第一块大小以实现最低首次音频时间,在块边界处有更明显的质量折衷。 | |
text_normalization | boolean | 在合成前启用文本规范化。当为 true 时,模型会将书面形式文本(例如数字、缩写、符号)规范化为口语形式后再生成音频。默认为 false。 | |
with_timestamps | boolean | 在音频的同时返回字符级时间戳。当为 true 时,响应是一个包含 base64 编码音频和每个字符开始/结束时间的 JSON 封装。增加了合成后对齐过程的延迟。默认为 false。参见 字符级时间戳。 |
包含所有选项的示例
{
"text": "Hello! This is a high-fidelity text to speech example.",
"voice_id": "ara",
"language": "en",
"output_format": {
"codec": "mp3",
"sample_rate": 44100,
"bit_rate": 192000
},
"speed": 1.2
}声音
每种声音都有独特的个性。收听示例并选择最适合您用例的声音(eve 是默认声音):
声音 ID 是不区分大小写的 — eve、Eve 和 EVE 都可以工作。在 Playground 中预览所有声音 →
自定义声音
使用 自定义声音 API 从简短的参考片段中克隆任何声音,或在 控制台 中免费创建一个。要在控制台中找到您的自定义声音 ID,点击声音卡片上的三个点菜单并选择复制声音 ID。然后将其作为 voice_id 传递:
# Replace YOUR_VOICE_ID with your custom voice ID from the console
# or the GET /v1/custom-voices endpoint.
curl -X POST https://api.x.ai/v1/tts \
-H "Authorization: Bearer $XAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"text": "Hello! This is my custom voice.",
"voice_id": "YOUR_VOICE_ID",
"language": "en"
}' \
--output hello.mp3import os
import requests
response = requests.post(
"https://api.x.ai/v1/tts",
headers={
"Authorization": f"Bearer {os.environ['XAI_API_KEY']}",
"Content-Type": "application/json",
},
json={
"text": "Hello! This is my custom voice.",
"voice_id": "YOUR_VOICE_ID", # replace with your custom voice ID
"language": "en",
},
)
response.raise_for_status()
with open("hello.mp3", "wb") as f:
f.write(response.content)import fs from "fs";
const response = await fetch("https://api.x.ai/v1/tts", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.XAI_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
text: "Hello! This is my custom voice.",
voice_id: "YOUR_VOICE_ID", // replace with your custom voice ID
language: "en",
}),
});
if (!response.ok) throw new Error(`TTS error ${response.status}: ${await response.text()}`);
fs.writeFileSync("hello.mp3", Buffer.from(await response.arrayBuffer()));您还可以使用 文本转语音 - 列出声音 端点以编程方式列出声音:
curl -s https://api.x.ai/v1/tts/voices \
-H "Authorization: Bearer $XAI_API_KEY"import os
import requests
response = requests.get(
"https://api.x.ai/v1/tts/voices",
headers={"Authorization": f"Bearer {os.environ['XAI_API_KEY']}"},
)
for voice in response.json()["voices"]:
print(f"{voice['voice_id']:5s} {voice['name']}")const response = await fetch("https://api.x.ai/v1/tts/voices", {
headers: { Authorization: `Bearer ${process.env.XAI_API_KEY}` },
});
const { voices } = await response.json();
voices.forEach((v) => console.log(`${v.voice_id} ${v.name}`));import Foundation
let apiKey = ProcessInfo.processInfo.environment["XAI_API_KEY"]!
let url = URL(string: "https://api.x.ai/v1/tts/voices")!
var request = URLRequest(url: url)
request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
let (data, _) = try await URLSession.shared.data(for: request)
let json = try JSONSerialization.jsonObject(with: data) as! [String: Any]
let voices = json["voices"] as! [[String: Any]]
for voice in voices {
print("\(voice["voice_id"]!) \(voice["name"]!)")
}支持的语言
TTS API 通过 BCP-47 语言代码支持 20 种语言。使用 auto 进行自动语言检测,或明确指定语言代码以获得一致的结果。
语言代码验证是不区分大小写的 — en、EN 和 En 都可以工作。
| 语言 | 语言代码 |
|---|---|
| 自动检测 | auto |
| 英语 | en |
| 阿拉伯语(埃及) | ar-EG |
| 阿拉伯语(沙特阿拉伯) | ar-SA |
| 阿拉伯语(阿联酋) | ar-AE |
| 孟加拉语 | bn |
| 中文(简体) | zh |
| 法语 | fr |
| 德语 | de |
| 印地语 | hi |
| 印尼语 | id |
| 意大利语 | it |
| 日语 | ja |
| 韩语 | ko |
| 葡萄牙语(巴西) | pt-BR |
| 葡萄牙语(葡萄牙) | pt-PT |
| 俄语 | ru |
| 西班牙语(墨西哥) | es-MX |
| 西班牙语(西班牙) | es-ES |
| 土耳其语 | tr |
| 越南语 | vi |
该模型还能够生成上述列表之外的其他语言的语音,但准确度各不相同。
语音标签
示例: 所以我走进来,[pause] 它就在那里。[laugh] 我真的不敢相信!<whisper>这整段时间都是个秘密。</whisper> 很酷,对吧?
在您的文本中添加内联语音标签以实现富有表现力的朗读。有两种类型的标签:
- 内联标签
[tag]— 放置在文本中的特定位置以产生语音表达(例如笑声或停顿) - 包装标签
<tag>text</tag>— 包装文本部分以更改其朗读方式(例如耳语、唱歌)
内联标签
在应该出现表达的位置插入这些标签。点击任何标签收听示例:
| 类别 | 标签 |
|---|---|
| 停顿 | |
| 笑声和哭泣 | |
| 口部声音 | |
| 呼吸 |
包装标签
包装文本以更改朗读风格。使用开始标签和匹配的结束标签。点击任何标签收听示例:
| 类别 | 标签 |
|---|---|
| 音量和强度 | |
| 音高和速度 | |
| 声音风格 |
示例
# Inline tags
curl -X POST https://api.x.ai/v1/tts \
-H "Authorization: Bearer $XAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"text": "So I walked in and [pause] there it was. [laugh] I honestly could not believe it!",
"voice_id": "eve",
"language": "en"
}' \
--output expressive.mp3
# Wrapping tags
curl -X POST https://api.x.ai/v1/tts \
-H "Authorization: Bearer $XAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"text": "I need to tell you something. <whisper>It is a secret.</whisper> Pretty cool, right?",
"voice_id": "eve",
"language": "en"
}' \
--output whisper.mp3import os
import requests
# Inline tags
response = requests.post(
"https://api.x.ai/v1/tts",
headers={
"Authorization": f"Bearer {os.environ['XAI_API_KEY']}",
"Content-Type": "application/json",
},
json={
"text": "So I walked in and [pause] there it was. [laugh] I honestly could not believe it!",
"voice_id": "eve",
"language": "en",
},
)
response.raise_for_status()
with open("expressive.mp3", "wb") as f:
f.write(response.content)
# Wrapping tags
response = requests.post(
"https://api.x.ai/v1/tts",
headers={
"Authorization": f"Bearer {os.environ['XAI_API_KEY']}",
"Content-Type": "application/json",
},
json={
"text": "I need to tell you something. <whisper>It is a secret.</whisper> Pretty cool, right?",
"voice_id": "eve",
"language": "en",
},
)
response.raise_for_status()
with open("whisper.mp3", "wb") as f:
f.write(response.content)// Inline tags
const response = await fetch("https://api.x.ai/v1/tts", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.XAI_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
text: "So I walked in and [pause] there it was. [laugh] I honestly could not believe it!",
voice_id: "eve",
language: "en",
}),
});
// Wrapping tags
const whisperResponse = await fetch("https://api.x.ai/v1/tts", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.XAI_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
text: "I need to tell you something. <whisper>It is a secret.</whisper> Pretty cool, right?",
voice_id: "eve",
language: "en",
}),
});import Foundation
let apiKey = ProcessInfo.processInfo.environment["XAI_API_KEY"]!
let url = URL(string: "https://api.x.ai/v1/tts")!
// Inline tags
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = try JSONSerialization.data(withJSONObject: [
"text": "So I walked in and [pause] there it was. [laugh] I honestly could not believe it!",
"voice_id": "eve",
"language": "en",
])
let (data, _) = try await URLSession.shared.data(for: request)
try data.write(to: URL(fileURLWithPath: "expressive.mp3"))
// Wrapping tags
request.httpBody = try JSONSerialization.data(withJSONObject: [
"text": "I need to tell you something. <whisper>It is a secret.</whisper> Pretty cool, right?",
"voice_id": "eve",
"language": "en",
])
let (whisperData, _) = try await URLSession.shared.data(for: request)
try whisperData.write(to: URL(fileURLWithPath: "whisper.mp3"))语音标签提示:
- 在对话中表达自然出现的位置放置内联标签
- 将标签与标点符号结合使用 —
"Really? [laugh] That's incredible!"产生的结果比堆叠标签更自然 - 使用
[pause]或[long-pause]添加戏剧性停顿或让一个想法落地 - 包装标签在完整短语周围效果最好 —
<whisper>It is a secret.</whisper>读起来比包装单个词更自然 - 组合风格以达到效果 —
<slow><soft>Goodnight, sleep well.</soft></slow>
输出格式
使用 output_format 对象控制音频编解码器、采样率和比特率。省略时,默认为 24 kHz/128 kbps 的 MP3。
编解码器
| 编解码器 | Content-Type | 最适用 |
|---|---|---|
mp3 | audio/mpeg | 通用 - 广泛兼容,良好压缩 |
wav | audio/wav | 无损音频 - 编辑,后期制作 |
pcm | audio/pcm | 原始音频 - 实时处理管道 |
mulaw | audio/basic | 电话(G.711 μ-law) |
alaw | audio/alaw | 电话(G.711 A-law) |
采样率
| 速率 | 描述 |
|---|---|
8000 | 窄带 - 电话 |
16000 | 宽带 - 语音识别 |
22050 | 标准 - 平衡质量 |
24000 | 高质量 - 默认,推荐用于大多数用例 |
44100 | CD 质量 - 媒体制作 |
48000 | 专业级 - 录音室级音频 |
比特率(仅限 MP3)
| 速率 | 质量 |
|---|---|
32000 | 低 - 最小文件大小 |
64000 | 中 - 适合语音 |
96000 | 标准 - 平衡 |
128000 | 高 - 默认,推荐 |
192000 | 最高 - 最高保真度 |
示例:高保真 MP3
{
"text": "Crystal clear audio at maximum quality.",
"voice_id": "rex",
"language": "en",
"output_format": {
"codec": "mp3",
"sample_rate": 44100,
"bit_rate": 192000
}
}示例:电话(μ-law)
{
"text": "Hello, thank you for calling. How can I help you today?",
"voice_id": "ara",
"language": "en",
"output_format": {
"codec": "mulaw",
"sample_rate": 8000
}
}字符级时间戳
将 with_timestamps 设置为 true 以接收每个字符的开始和结束时间戳。适合同步字幕、卡拉 OK 高亮、实时口型同步或其他时间对齐的应用。
响应然后从原始音频字节更改为一个 JSON 封装(Content-Type: application/json),其中包含 base64 编码的音频和字符时间戳。
请求时间戳
向正常请求添加标志。音频作为 JSON 主体返回,而不是原始字节:
curl -X POST https://api.x.ai/v1/tts \
-H "Authorization: Bearer $XAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"text": "Hello world.",
"voice_id": "eve",
"language": "en",
"with_timestamps": true
}' \
--output response.jsonimport base64
import os
import requests
response = requests.post(
"https://api.x.ai/v1/tts",
headers={
"Authorization": f"Bearer {os.environ['XAI_API_KEY']}",
"Content-Type": "application/json",
},
json={
"text": "Hello world.",
"voice_id": "eve",
"language": "en",
"with_timestamps": True,
},
)
response.raise_for_status()
payload = response.json()
# The audio is base64-encoded — decode it exactly like a normal response
with open("hello.mp3", "wb") as f:
f.write(base64.b64decode(payload["audio"]))
ts = payload["audio_timestamps"]
for char, (start, end) in zip(ts["graph_chars"], ts["graph_times"]):
print(f"{char!r:>5} {start:.2f}s – {end:.2f}s")
print(f"duration: {payload['duration']:.2f}s")import fs from "fs";
const response = await fetch("https://api.x.ai/v1/tts", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.XAI_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
text: "Hello world.",
voice_id: "eve",
language: "en",
with_timestamps: true,
}),
});
if (!response.ok) throw new Error(`TTS error ${response.status}`);
const payload = await response.json();
// The audio is base64-encoded — decode it exactly like a normal response
fs.writeFileSync("hello.mp3", Buffer.from(payload.audio, "base64"));
const { graph_chars, graph_times } = payload.audio_timestamps;
graph_chars.forEach((char, i) => {
const [start, end] = graph_times[i];
console.log(`${JSON.stringify(char).padStart(5)} ${start.toFixed(2)}s – ${end.toFixed(2)}s`);
});
console.log(`duration: ${payload.duration.toFixed(2)}s`);import Foundation
// snake_case JSON keys map to camelCase via the decoder strategy below
struct TimedTts: Decodable {
let audio: String
let contentType: String
let duration: Double
let audioTimestamps: AudioTimestamps?
struct AudioTimestamps: Decodable {
let graphChars: [String]
let graphTimes: [[Double]]
}
}
let apiKey = ProcessInfo.processInfo.environment["XAI_API_KEY"]!
var request = URLRequest(url: URL(string: "https://api.x.ai/v1/tts")!)
request.httpMethod = "POST"
request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = try JSONSerialization.data(withJSONObject: [
"text": "Hello world.",
"voice_id": "eve",
"language": "en",
"with_timestamps": true,
])
let (data, _) = try await URLSession.shared.data(for: request)
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let payload = try decoder.decode(TimedTts.self, from: data)
// The audio is base64-encoded — decode it exactly like a normal response
try Data(base64Encoded: payload.audio)!.write(to: URL(fileURLWithPath: "hello.mp3"))
if let ts = payload.audioTimestamps {
for (char, time) in zip(ts.graphChars, ts.graphTimes) {
print(String(format: "%5@ %.2fs – %.2fs", char as NSString, time[0], time[1]))
}
}
print(String(format: "duration: %.2fs", payload.duration))响应结构
{
"audio": "<base64-encoded audio in the requested codec>",
"content_type": "audio/mpeg",
"duration": 0.92,
"audio_timestamps": {
"graph_chars": ["H", "e", "l", "l", "o", " ", "w", "o", "r", "l", "d", "."],
"graph_times": [
[0.00, 0.06],
[0.06, 0.12],
[0.12, 0.18],
[0.18, 0.24],
[0.24, 0.34],
[0.34, 0.40],
[0.40, 0.48],
[0.48, 0.54],
[0.54, 0.62],
[0.62, 0.68],
[0.68, 0.78],
[0.78, 0.92]
]
}
}| 字段 | 类型 | 描述 |
|---|---|---|
audio | string | 请求的编解码器中的 base64 编码音频。解码并播放,如正常响应。 |
content_type | string | 解码音频的 MIME 类型(例如 audio/mpeg、audio/wav)。 |
duration | number | 总音频持续时间(秒)。 |
audio_timestamps.graph_chars | string[] | 每个输入字符,按顺序,包括空格、标点符号和语音标签。 |
audio_timestamps.graph_times | number[][] | 并行的 [start, end] 对数组,单位为秒。 |
graph_chars 和 graph_times 通过索引对齐,它们逐位置匹配。因此 graph_chars[i] 是在时间间隔 graph_times[i] 内发音的字符。对于 "Hello world.":
char: H e l l o ␣ w o r l d .
start: 0.00 0.06 0.12 0.18 0.24 0.34 0.40 0.48 0.54 0.62 0.68 0.78
└──────────── "Hello" ───────────┘ └──────────── "world." ──────────┘
0s ─────────────────────────────────────────────────────────────────────▶ 0.92s特殊字符
graph_chars 逐个镜像您的输入,包括空格、标点符号和语音标签。当一个书写标记被读作多个词时,其时间分配给第一个字符。其余字符在该相同时间范围内插值。
这主要在启用 text_normalization 时发生,它将符号和数字扩展为单词。启用规范化后,$5 被读作"five dollars",但仍然是两个字符:$ 获得"five dollars"的整个时间跨度,而 5 在其中获得插值时间。因此,始终按顺序遍历 graph_chars,而不是按索引切片输入文本。
最佳实践
从 TTS API 获取最高质量输出的提示。
编写有效文本
- 使用自然标点符号。 逗号、句号和问号引导节奏和语调。
"Wait, really?"比"Wait really"听起来更自然。 - 添加情感上下文。 感叹号和问号影响朗读方式 —
"That's amazing!"听起来热情,而"That's amazing."则平淡。 - 将长内容分段。 段落分隔创建自然停顿,并帮助模型在较长的文本中保持一致的质量。
- 保持单次请求在 15,000 个字符以下。 对于更长的内容,使用 双向 WebSocket 端点,它没有文本长度限制,或按逻辑分段(按段落或句子)并连接音频输出。
与 AI 编码助手集成
Cloud Console Playground 包含现成的代理指令,您可以复制并粘贴到 Cursor、GitHub Copilot 或 Windsurf 等工具中。这些指令已预先配置了您当前的声音和格式设置 - 打开 Playground,调整您的设置,然后复制提示以获取为您的编码代理量身定制的集成指南。
生产环境优化
- 在服务器端代理请求。 切勿在客户端代码中暴露您的 API 密钥。通过您的后端路由 TTS 请求。
- 缓存生成的音频。 如果重复请求相同的文本,缓存音频字节以节省 API 调用并减少延迟。
- 根据用例匹配格式。 对于电话使用
mulaw或alaw在 8 kHz;网络使用mp3在 24 kHz;后期制作使用wav在 44.1+ kHz。 - 遵守并发会话限制。 流式 WebSocket 端点允许每个团队最多 50 个并发会话。对于高吞吐量服务,连接池或排队请求以保持在此限制内。
浏览器播放
要在浏览器中播放 TTS 音频,通过您的后端代理请求并使用 Web Audio API 或 <audio> 元素:
// Client-side: fetch from your backend proxy, then play
async function speakText(text, voiceId = "eve") {
const response = await fetch("/api/tts", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ text, voice_id: voiceId }),
});
if (!response.ok) throw new Error("TTS request failed");
const blob = await response.blob();
const url = URL.createObjectURL(blob);
const audio = new Audio(url);
audio.addEventListener("ended", () => URL.revokeObjectURL(url));
await audio.play();
}
// Usage
await speakText("Hello from the browser!");WARNING
切勿直接从浏览器调用 TTS API - 这会暴露您的 API 密钥。始终通过您的后端代理。
浏览器注意事项
Safari 在 blob URL 上为 audio.duration 返回 Infinity。 loadedmetadata 事件触发但 audio.duration 是 Infinity,这破坏了搜索栏和时间显示。改用 AudioContext.decodeAudioData():
async function getAudioDuration(arrayBuffer) {
const AudioCtx = window.AudioContext || window.webkitAudioContext;
const ctx = new AudioCtx();
// Clone the buffer - decodeAudioData detaches the original
const decoded = await ctx.decodeAudioData(arrayBuffer.slice(0));
const durationMs = Math.round(decoded.duration * 1000);
await ctx.close();
return durationMs;
}AudioContext 必须在 Safari 的用户手势期间创建。 Safari 永久挂起在点击/点击处理程序之外创建的 AudioContext,无法恢复。Chrome 更宽松。始终在按钮的点击处理程序中创建或恢复上下文,在任何 await 之前:
// Create the AudioContext once, in a click handler
let audioCtx;
button.addEventListener("click", async () => {
// This MUST happen synchronously in the click handler for Safari
if (!audioCtx) audioCtx = new AudioContext();
if (audioCtx.state === "suspended") await audioCtx.resume();
// Now it's safe to fetch and play audio asynchronously
const response = await fetch("/api/tts", { /* ... */ });
const arrayBuffer = await response.arrayBuffer();
const decoded = await audioCtx.decodeAudioData(arrayBuffer);
const source = audioCtx.createBufferSource();
source.buffer = decoded;
source.connect(audioCtx.destination);
source.start();
});原始编解码器(pcm、mulaw、alaw)在浏览器中无法播放。 AudioContext.decodeAudioData() 和 <audio> 元素仅支持像 MP3 和 WAV 这样的容器格式。使用 mp3 或 wav 进行浏览器播放。如果您在服务器端处理原始格式(例如,传输到电话),从字节数估计持续时间:
// PCM = 16-bit LE (2 bytes/sample), mulaw/alaw = 8-bit (1 byte/sample)
const bytesPerSample = codec === "pcm" ? 2 : 1;
const durationMs = Math.round((byteLength / bytesPerSample / sampleRate) * 1000);撤销 blob URL 以避免内存泄漏。 每个 URL.createObjectURL() 调用都会分配内存,该内存会一直持续到被明确释放。在播放结束时撤销 URL。对于下载,延迟撤销以便浏览器完成保存文件:
// Playback: revoke when done
const url = URL.createObjectURL(blob);
const audio = new Audio(url);
audio.addEventListener("ended", () => URL.revokeObjectURL(url));
// Downloads: delay revocation
const downloadUrl = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = downloadUrl;
a.download = "speech.mp3";
a.click();
setTimeout(() => URL.revokeObjectURL(downloadUrl), 10_000);错误处理
| 状态 | 含义 | 操作 |
|---|---|---|
200 | 成功 | 响应体中的音频字节 |
400 | 错误请求 | 检查:文本非空,少于 15,000 个字符;编解码器和采样率有效 |
401 | 未授权 | API 密钥缺失或无效 |
404 | 未找到 | 未知的 voice_id — 通过 GET /v1/tts/voices(内置)或 GET /v1/custom-voices(自定义)验证 |
429 | 速率限制 | 退避并使用指数延迟重试 |
503 | 服务不可用 | TTS 服务暂时不可用 - 重试 |
500 | 服务器错误 | 使用指数退避重试 |
退避重试
import os
import time
import requests
def generate_speech(text, language="en", voice_id="eve", max_retries=3):
for attempt in range(max_retries):
response = requests.post(
"https://api.x.ai/v1/tts",
headers={
"Authorization": f"Bearer {os.environ['XAI_API_KEY']}",
"Content-Type": "application/json",
},
json={"text": text, "language": language, "voice_id": voice_id},
)
if response.ok:
return response.content
if response.status_code in (429, 500, 503):
wait = 2 ** attempt
time.sleep(wait)
continue
response.raise_for_status() # Non-retryable error
raise RuntimeError("Max retries exceeded")async function generateSpeech(text, language = "en", voiceId = "eve", maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
const response = await fetch("https://api.x.ai/v1/tts", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.XAI_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ text, language, voice_id: voiceId }),
});
if (response.ok) return Buffer.from(await response.arrayBuffer());
if ([429, 500, 503].includes(response.status)) {
await new Promise((r) => setTimeout(r, 2 ** attempt * 1000));
continue;
}
throw new Error(`TTS error ${response.status}: ${await response.text()}`);
}
throw new Error("Max retries exceeded");
}import Foundation
func generateSpeech(text: String, language: String = "en", voiceId: String = "eve", maxRetries: Int = 3) async throws -> Data {
let apiKey = ProcessInfo.processInfo.environment["XAI_API_KEY"]!
let url = URL(string: "https://api.x.ai/v1/tts")!
for attempt in 0..<maxRetries {
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = try JSONSerialization.data(withJSONObject: [
"text": text, "language": language, "voice_id": voiceId,
])
let (data, response) = try await URLSession.shared.data(for: request)
let status = (response as! HTTPURLResponse).statusCode
if status == 200 { return data }
if [429, 500, 503].contains(status) {
try await Task.sleep(nanoseconds: UInt64(pow(2.0, Double(attempt))) * 1_000_000_000)
continue
}
throw URLError(.badServerResponse)
}
throw URLError(.timedOut)
}限制
单次/服务器流式端点和双向 WebSocket 端点有不同的限制:
单次和服务器流式 (POST /v1/tts) | 双向 WebSocket (wss://api.x.ai/v1/tts) | |
|---|---|---|
| 最大文本长度 | 每次请求 15,000 个字符 | 无限制 — 单个 text.delta 消息限制为 15,000 个字符 |
| 请求超时 | 15 分钟 | 无超时(连接保持打开) |
| 并发会话 | — | 每个团队 50 个 |
对于超过 15,000 个字符的内容,使用 双向 WebSocket 端点,它没有文本长度限制。
流式 TTS (WebSocket)
对于实时音频生成,打开到流式 TTS 端点的 WebSocket 连接。文本作为增量流式输入,音频作为 base64 编码的块流式返回 — 适合交互式应用程序,您希望音频在完整文本可用之前就开始播放。
端点: wss://api.x.ai/v1/tts
NOTE
切勿在客户端代码中暴露您的 API 密钥。 始终通过您的后端代理 WebSocket 连接。
连接
使用查询参数打开 WebSocket 连接以配置语言、声音和音频格式:
GET /v1/tts?language=en&voice=eve&codec=mp3&sample_rate=24000&bit_rate=128000
Upgrade: websocket
Authorization: Bearer $XAI_API_KEY| 参数 | 必需 | 默认 | 接受的值 |
|---|---|---|---|
voice | 任何内置声音 ID(参见 [声音]( |