跳转到内容

模型能力

文本转语音

通过一次 API 调用将文本转换为语音音频。该 API 支持丰富的表达声音集、用于精细控制的内联语音标签,以及从高保真 MP3 到电话优化的 μ-law 等多种输出格式。

快速开始

通过一次 API 调用生成语音:

bash
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.mp3
python
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! 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")
javascript
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`);
swift
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")

响应体包含原始音频字节。直接保存到文件或传输到音频播放器。

尝试 Playground →

实时语音演示

获取 API 密钥

请求体

参数类型必需描述
textstring要转换为语音的文本。最大 15,000 个字符。支持 语音标签
voice_idstring用于合成的声音。默认为 eve。参见 声音
languagestringBCP-47 语言代码(例如 enzhpt-BR)或 auto 用于自动语言检测。参见 支持的语言
output_formatobject输出格式配置。默认为 24 kHz/128 kbps 的 MP3。参见 输出格式
speednumber语音速度倍数。1.0 为正常速度。低于 1.0 的值会减慢语音,高于 1.0 的值会加速语音。范围:0.71.5。默认为 1.0
optimize_streaming_latencyinteger流式合成的延迟优化级别。0(默认):无优化 — 最佳音频质量。1:减少第一块大小以降低首次音频时间,在块边界处有轻微的质量折衷。2:进一步减少第一块大小以实现最低首次音频时间,在块边界处有更明显的质量折衷。
text_normalizationboolean在合成前启用文本规范化。当为 true 时,模型会将书面形式文本(例如数字、缩写、符号)规范化为口语形式后再生成音频。默认为 false
with_timestampsboolean在音频的同时返回字符级时间戳。当为 true 时,响应是一个包含 base64 编码音频和每个字符开始/结束时间的 JSON 封装。增加了合成后对齐过程的延迟。默认为 false。参见 字符级时间戳

包含所有选项的示例

json
{
  "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 是不区分大小写的 — eveEveEVE 都可以工作。在 Playground 中预览所有声音 →

自定义声音

使用 自定义声音 API 从简短的参考片段中克隆任何声音,或在 控制台 中免费创建一个。要在控制台中找到您的自定义声音 ID,点击声音卡片上的三个点菜单并选择复制声音 ID。然后将其作为 voice_id 传递:

bash
# 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.mp3
python
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! 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)
javascript
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()));

您还可以使用 文本转语音 - 列出声音 端点以编程方式列出声音:

bash
curl -s https://api.x.ai/v1/tts/voices \
  -H "Authorization: Bearer $XAI_API_KEY"
python
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']}")
javascript
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}`));
swift
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 进行自动语言检测,或明确指定语言代码以获得一致的结果。

语言代码验证是不区分大小写的 — enENEn 都可以工作。

语言语言代码
自动检测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> — 包装文本部分以更改其朗读方式(例如耳语、唱歌)

内联标签

在应该出现表达的位置插入这些标签。点击任何标签收听示例:

类别标签
停顿
笑声和哭泣
口部声音
呼吸

包装标签

包装文本以更改朗读风格。使用开始标签和匹配的结束标签。点击任何标签收听示例:

类别标签
音量和强度
音高和速度
声音风格

示例

bash
# 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.mp3
python
import 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)
javascript
// 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",
  }),
});
swift
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最适用
mp3audio/mpeg通用 - 广泛兼容,良好压缩
wavaudio/wav无损音频 - 编辑,后期制作
pcmaudio/pcm原始音频 - 实时处理管道
mulawaudio/basic电话(G.711 μ-law)
alawaudio/alaw电话(G.711 A-law)

采样率

速率描述
8000窄带 - 电话
16000宽带 - 语音识别
22050标准 - 平衡质量
24000高质量 - 默认,推荐用于大多数用例
44100CD 质量 - 媒体制作
48000专业级 - 录音室级音频

比特率(仅限 MP3)

速率质量
32000低 - 最小文件大小
64000中 - 适合语音
96000标准 - 平衡
128000高 - 默认,推荐
192000最高 - 最高保真度

示例:高保真 MP3

json
{
  "text": "Crystal clear audio at maximum quality.",
  "voice_id": "rex",
  "language": "en",
  "output_format": {
    "codec": "mp3",
    "sample_rate": 44100,
    "bit_rate": 192000
  }
}

示例:电话(μ-law)

json
{
  "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 主体返回,而不是原始字节:

bash
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.json
python
import 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")
javascript
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`);
swift
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))

响应结构

json
{
  "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]
    ]
  }
}
字段类型描述
audiostring请求的编解码器中的 base64 编码音频。解码并播放,如正常响应。
content_typestring解码音频的 MIME 类型(例如 audio/mpegaudio/wav)。
durationnumber总音频持续时间(秒)。
audio_timestamps.graph_charsstring[]每个输入字符,按顺序,包括空格、标点符号和语音标签。
audio_timestamps.graph_timesnumber[][]并行的 [start, end] 对数组,单位为秒。

graph_charsgraph_times 通过索引对齐,它们逐位置匹配。因此 graph_chars[i] 是在时间间隔 graph_times[i] 内发音的字符。对于 "Hello world."

text
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 调用并减少延迟。
  • 根据用例匹配格式。 对于电话使用 mulawalaw 在 8 kHz;网络使用 mp3 在 24 kHz;后期制作使用 wav 在 44.1+ kHz。
  • 遵守并发会话限制。 流式 WebSocket 端点允许每个团队最多 50 个并发会话。对于高吞吐量服务,连接池或排队请求以保持在此限制内。

浏览器播放

要在浏览器中播放 TTS 音频,通过您的后端代理请求并使用 Web Audio API 或 <audio> 元素:

javascript
// 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.durationInfinity,这破坏了搜索栏和时间显示。改用 AudioContext.decodeAudioData()

javascript
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 之前:

javascript
// 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 这样的容器格式。使用 mp3wav 进行浏览器播放。如果您在服务器端处理原始格式(例如,传输到电话),从字节数估计持续时间:

javascript
// 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。对于下载,延迟撤销以便浏览器完成保存文件:

javascript
// 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服务器错误使用指数退避重试

退避重试

python
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")
javascript
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");
}
swift
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(参见 [声音](

本文档为 docs.x.ai 全站中文翻译,由 AI 自动翻译生成。代码示例请以原文为准。