跳至内容

文本转语音 API 集成:流式、批量、重试

发布时间
最近更新

收听收听本文

集成文本转语音 API 并不复杂……前提是先做出几项关键决定:选择哪种传输方式、如何选择模型和输出格式、如何流式传输、如何在不超出并发限制的前提下处理高请求量、如何通过缓存和重试避免为同一段音频重复付费,以及如何与其他服务商对比首字节时间。

为帮助你集成文本转语音 API,我们拆解了这些架构决策及其实现方法。本指南将帮助你集成 ElevenLabs 文本转语音 API 并实现扩展,其中的代码片段可直接用于生产环境。

如需全面了解本文提到的概念,请查看我们的音频流式传输详解延迟优化ElevenLabs 模型概览。 

摘要

  • ElevenLabs 文本转语音 API 只有一个端点,可通过 3 种方式访问:批量转换、HTTP 流和 stream-input WebSocket。
  • 通过 HTTP 时,每个进行中的请求都会占用并发额度;通过 WebSocket 时,只有活跃生成会被计入。
  • 将并行量控制在套餐限制以下,并缓存所有影响输出的参数哈希,避免同一文本重复计费。
  • 对 429 和 5xx 使用指数退避和完全抖动重试,在触及并发上限前主动降速。

集成文本转语音 API 的 3 种方式

文本转语音只有一个端点,但集成方式会影响延迟、复杂度和成本。 

同一个 POST /v1/text-to-speech/{voice_id} 调用可采用 3 种形式,分别适合略有不同的场景。以下是集成文本转语音 API 的 3 种方式:

  • 批量(convert)是最简单的集成方式:发送 1 个请求,即可获得 1 个音频响应。它的复杂度最低,但首段音频等待时间最长,因为必须先合成完整音频片段,才会返回任何字节。
  • HTTP 流式传输(stream)使用相同请求,但会分块返回响应:在路径后附加 /stream,调用 stream 方法,音频就会以分块响应返回。代码几乎相同,但感知延迟大幅降低。
  • WebSocket(stream-input)会保持持久连接:可逐步发送文本,并持续接收音频分块。它专为交互式智能体设计,也适用于在 LLM 生成 token 的过程中、句子尚未结束前,将输出转换为语音。

流式传输不会让模型更快生成音频,推理时间不变。它改变的是收到第一个分块的时间:完整音频尚未生成完便会发送首个分块。因此,尽管总工作量相同,用户感受到的等待时间更短。

批量、流式传输与 WebSocket 对比表

在这 3 种方式中做选择时,需要考虑多个因素。

快速参考:离线渲染选择批量转换;用户正在等待的已知文本选择 HTTP 流式传输;智能体和实时 LLM 转语音选择 WebSocket。 

下表按大规模部署时的重要维度,拆解了各项权衡。

Batch (convert)
Time-to-first-audio
Highest (wait for full clip)
Implementation complexity
Lowest
Text known up front?
Required
Streaming LLM output into TTS
Awkward
Concurrency cost
Each request counts fully
Best for
Offline rendering, audiobooks, caching
HTTP streaming
Time-to-first-audio
Low
Implementation complexity
Low
Text known up front?
Required
Streaming LLM output into TTS
Awkward
Concurrency cost
Each request counts fully
Best for
Web/app playback of known text
WebSocket (stream-input)
Time-to-first-audio
Lowest
Implementation complexity
Highest (connection lifecycle, framing)
Text known up front?
Not required - send incrementally
Streaming LLM output into TTS
Native fit
Concurrency cost
Only active generation counts
Best for
Voice agents, live LLM to speech

通过 HTTP 时,无论是批量还是流式传输,每个进行中的请求在整个持续期间都会占用套餐的并发额度。通过 WebSocket 时,只有模型主动生成音频的时间会被计入;已打开但空闲的 socket 几乎不占用资源。

对于级联式语音智能体,它会在整个对话期间保持连接,但只在智能体发言时生成音频,这一差异非常显著,也是构建智能体时使用 WebSocket 的主要原因。完整协议请参阅实时文本转语音 WebSocket 指南

选择模型和输出格式

TTS API 集成返回的音频由两项选择决定:模型决定质量和速度;输出格式决定容器、比特率和采样率。

从一开始就正确选择两者,可让延迟、电话兼容性等后续环节顺利衔接。

模型

我们提供多种文本转语音模型。它们并非从好到差的排名,每种模型都有不同取舍。

Best for
eleven_flash_v2_5
Real-time, agents, bulk throughput (~75ms model inference)
eleven_flash_v2
Real-time, English only (~75ms)
eleven_multilingual_v2
Highest stable fidelity, narration
eleven_v3
Most expressive, widest language range
Languages
eleven_flash_v2_5
32
eleven_flash_v2
English
eleven_multilingual_v2
29
eleven_v3
70+
Character limit
eleven_flash_v2_5
40,000
eleven_flash_v2
30,000
eleven_multilingual_v2
10,000
eleven_v3
5,000

说明:约 75ms 是具有代表性条件下的模型推理时间,不包括网络和应用延迟。输入更长或负载更高时,时间会增加。请始终从实际应用中测量,而非依赖基准测试数字。

Flash 模型体积更小,并使用更激进的近似方法缩短推理时间。Eleven v3 和 Multilingual v2 是更大的模型,会为每个字符投入更多处理时间,以生成更丰富的结果。没有任何设置能同时提供 Eleven v3 的质量和 Flash 的速度,因为这种质量来自额外计算。

对于实时或智能体场景,请使用 eleven_flash_v2_5;它是延迟最低的多语言选项。对于旁白、有声书或营销旁白配音,如需稳定的高保真效果,请使用 eleven_multilingual_v2;如需最强表现力和情感范围,请使用 eleven_v3。 

当发音很重要时,例如电话号码、日期或金额,请在文本发送至 API 前自行在应用中进行数字规范化。直接写出期望的读法。 

自行规范化可使不同模型的发音保持可预测,并避免依赖可能变更的模型默认设置。

输出格式

output_format 参数控制返回音频的容器、采样率和比特率。最常用的值包括:

Use case
mp3_44100_128
General playback, downloads, highest mp3 quality shown here
mp3_22050_32
Lower-bandwidth playback, smaller files
pcm_24000 / pcm_16000
Raw PCM for your own audio pipeline or further processing
ulaw_8000
Telephony - the format used with Twilio and similar systems
Languages
mp3_44100_128
32
mp3_22050_32
English
pcm_24000 / pcm_16000
29
ulaw_8000
70+
Character limit
mp3_44100_128
40,000
mp3_22050_32
30,000
pcm_24000 / pcm_16000
10,000
ulaw_8000
5,000

音色设置

以下设置控制生成语音的呈现方式:

  • Stability:控制稳定性与表现力的平衡。较低值会产生更多变化、更有表现力的语音;较高值则让表达更稳定、可预测。
  • SimilarityBoost:控制输出与参考音色的贴近程度。
  • Style:提高该值会强化音色的自然说话风格。
  • useSpeakerBoost:以少量延迟为代价,增强与原始说话者的相似度。
  • Speed:以默认值 1.0 为基准调整语速。

在这些设置中,Stability 通常最影响感知质量。较低值会带来更有表现力但一致性较低的输出;较高值则优先保证一致性和可预测性。

选择音色时,延迟最低的组合是 Flash 搭配即时语音克隆或默认音色;专业语音克隆的效果出色,但会增加每次生成的开销,需要纳入考量。

本指南中的示例音色 ID 为 JBFqnCBsd6RMkjVDRZzb(George)。

流式集成(HTTP 和 WebSocket)

本节介绍文本转语音 API 集成的实操核心,包括安装 SDK、打开流以及实时处理接收到的音频。HTTP 路径适用于大多数网页和应用播放;WebSocket 路径适用于智能体和实时 LLM 输出。

这两种方式均假定你已按以下方式初始化 ElevenLabs 客户端。

npm install @elevenlabs/elevenlabs-js
import { ElevenLabsClient } from "@elevenlabs/elevenlabs-js";

const elevenlabs = new ElevenLabsClient({
  apiKey: process.env.ELEVENLABS_API_KEY,
});

流式路径会打开一个流,并在分块到达时进行处理。voiceId 是第一个位置参数,后接使用 camelCase 键名(modelId、outputFormat、voiceSettings)的选项对象:

const stream = await elevenlabs.textToSpeech.stream("JBFqnCBsd6RMkjVDRZzb", {
  text,
  modelId: "eleven_flash_v2_5",
  outputFormat: "mp3_44100_128",
  voiceSettings: { stability: 0, similarityBoost: 1.0, style: 0, useSpeakerBoost: true, speed: 1.0 },
});

for await (const chunk of stream) {
  // chunk is a Buffer; feed it to the player as it arrives
}

对于 WebSocket 方式,请连接至 wss://api.elevenlabs.io/v1/text-to-speech/{voice_id}/stream-input,先发送一条包含音色设置和开头空格的消息,再在文本可用时发送文本消息,并读取 audio 字段包含 base64 编码分块的 JSON 帧。

高吞吐量的批处理与并发限制

高吞吐量集成受并发数限制,即同一时刻生成音频的请求数量。每个套餐都针对不同模型系列设有相应限制。 

每个套餐的并发限制不同:

  • 免费版:4 个并发 Flash 请求。
  • Starter:6 个并发 Flash 请求。
  • Creator:10 个并发 Flash 请求。
  • Pro:20 个并发 Flash 请求。
  • Scale 和商业版:30 个并发 Flash 请求;企业版限制可定制。

Multilingual v2 的限制约为上述数值的一半。

可通过有界池限制同时运行的请求数量:

// Set MAX_CONCURRENCY at or below your plan's Flash concurrency limit.
const MAX_CONCURRENCY = 8;

async function synthMany(texts: string[]): Promise<Buffer[]> {
  const results: Buffer[] = [];
  for (let i = 0; i < texts.length; i += MAX_CONCURRENCY) {
    const batch = texts.slice(i, i + MAX_CONCURRENCY);
    results.push(...(await Promise.all(batch.map(eachSingleRequest)))); // never more than MAX_CONCURRENCY in flight
  }
  return results;

将 MAX_CONCURRENCY 设为略低于套餐限制的值,而非刚好等于限制。预留的余量可以吸收使用同一密钥的其他流量,避免达到返回 429 的阈值。

字符限制与长文本拆分

每个模型都会限制单个请求可接受的字符数。任何长文本集成都必须拆分文本,再将音频拼接起来。 

各模型的单次请求字符限制如下:

  • Flash v2.5:单次请求最多接受 40,000 个字符。
  • Flash v2:单次请求最多接受 30,000 个字符。
  • Multilingual v2:单次请求最多接受 10,000 个字符。
  • Eleven v3:单次请求最多接受 5,000 个字符。

更长的文本必须拆分为多个请求。应尽量按句子边界拆分,以保留分块衔接处的韵律。

function splitText(text: string, maxChars: number): string[] {
  const sentences = text.trim().split(/(?<=[.!?])\s+/);
  const chunks: string[] = [];
  let current = "";
  for (let sentence of sentences) {
    if (current.length + sentence.length + 1 > maxChars) {
      if (current) chunks.push(current.trim());
      // A single sentence longer than the limit is hard-split.
      while (sentence.length > maxChars) {
        chunks.push(sentence.slice(0, maxChars));
        sentence = sentence.slice(maxChars);
      }
      current = sentence;
    } else {
      current = `${current} ${sentence}`.trim();
    }
  }
  if (current) chunks.push(current.trim());
  return chunks;
}

按顺序渲染各分块,再拼接音频。对于每段相互独立的长篇旁白,这两部分可直接组合:将 splitText 的输出传入上方的有界池,其余工作会自动处理。

缓存与幂等性

文本转语音输出具有足够的确定性,使用相同音色、模型和设置重新渲染同一文本会造成浪费。应以影响音频的输入参数哈希作为键缓存结果,该键也可在重试时充当幂等令牌。

具体实现如下。

import { createHash } from "node:crypto";

function cacheKey(text: string, voiceId: string, modelId: string,
                  outputFormat: string, settings: object): string {
  // Every parameter that changes the audio must be in the key.
  const payload = JSON.stringify({ text, voiceId, modelId, outputFormat, settings });
  return createHash("sha256").update(payload).digest("hex");
}

async function cachedSynth(text: string, voiceId: string, modelId: string,
                           outputFormat: string, settings: object): Promise<Buffer> {
  const key = cacheKey(text, voiceId, modelId, outputFormat, settings);
  const cached = await cacheGet(key);          // e.g. read from disk or S3
  if (cached) return cached;

  const audio = await elevenlabs.textToSpeech.convert(voiceId, { text, modelId, outputFormat });
  await cachePut(key, audio);                   // store the bytes under the key
  return audio;
}

实现这一点的规则是:所有会改变音频的参数都必须包含在键中,包括 outputFormat 和音色设置。正确实现后,同一个键还能充当幂等令牌。当客户端重试已成功的请求时,直接返回缓存的字节,而非再次生成。

错误处理与速率限制(429)

生产环境客户端需要采用退避和抖动重试,并根据状态码采取不同处理方式,因为有些失败值得重试,有些则不值得。 

下表列出每种状态对应的正确操作,本节还会说明为何 429 是软限制,而不是不可逾越的硬墙。

Meaning
401
Authentication failed
422
Invalid request
429
Concurrency exceeded
5xx
Transient server error
Action
401
Do not retry. Check the xi-api-key header and key validity.
422
Do not retry. Fix the payload (bad voice id, unsupported format, text over limit).
429
Retry with exponential backoff and jitter.
5xx
Retry with backoff.
Character limit
401
40,000
422
30,000
429
10,000
5xx
5,000

429 并非不可逾越的硬墙,了解其机制会有所帮助。超出并发限制时,请求会先按优先级排队,通常会增加约 50ms 的等待时间。只有之后仍超出容量,才会收到 429。 

响应中还包含 current-concurrent-requests 和 maximum-concurrent-requests 标头,用于显示实时余量。可读取这些标头,在触及限制前主动降速。

const RETRYABLE = new Set([429, 500, 502, 503, 504]);

async function synthWithRetry(text: string, voiceId: string, maxRetries = 5): Promise<Buffer> {
  let delay = 500; // ms, base for exponential backoff
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      return await elevenlabs.textToSpeech.convert(voiceId, {
        text, modelId: "eleven_flash_v2_5", outputFormat: "mp3_44100_128",
      });
    } catch (err: any) {
      const status = err.statusCode;
      // 401/422 and exhausted retries are not recoverable here.
      if (!RETRYABLE.has(status) || attempt === maxRetries) throw err;
      // Exponential backoff with full jitter.
      await new Promise((r) => setTimeout(r, Math.random() * delay));
      delay = Math.min(delay * 2, 8000);
    }
  }
  throw new Error("unreachable");
}

如果需要更多余量,而不是改进重试行为,请升级套餐。企业版客户可通过客户经理申请提高限制。

延迟与首字节时间基准测试

延迟取决于所在区域、输入内容和当前负载,因此唯一值得依赖的延迟数据,是从实际环境中测得的数据。 

本节提供 Flash 流式端点的首字节时间(TTFB)测试方法,其结构支持将同一测试工具指向其他服务商,在相同条件下进行比较。

请将其视为测试方法,而非已发布的结果。单次运行不能保证任何结果。 

对文本转语音 API 集成进行延迟基准测试时,以下注意事项很重要:

  • 纳入网络往返时间:TTFB 取决于地理位置和服务商最近的集群,因此应从服务器通常运行的位置进行测试。
  • 舍弃预热运行:冷连接的首次请求较慢,可能导致数据失真。
  • 固定输入:输入长度、音色、模型和负载都会影响结果,因此在不同服务商之间应保持一致。
  • 报告分布情况:每次运行的数值都会变化,因此应发布中位数和 p95,而非单一数值。

考虑以上因素后,就可以开始进行基准测试。

const TEXT = "This is a fixed benchmark sentence used for every provider.";

async function measureElevenLabs(): Promise<number> {
  const start = performance.now();
  const res = await fetch(
    "https://api.elevenlabs.io/v1/text-to-speech/JBFqnCBsd6RMkjVDRZzb/stream?output_format=mp3_44100_128",
    {
      method: "POST",
      headers: { "xi-api-key": process.env.ELEVENLABS_API_KEY!, "Content-Type": "application/json" },
      body: JSON.stringify({ text: TEXT, model_id: "eleven_flash_v2_5" }),
    },
  );
  for await (const _ of res.body!) {
    return performance.now() - start; // first chunk received
  }
  throw new Error("no audio returned");
}

如需与其他服务商比较,编写一个结构相同的函数。然后使用一个小型运行器驱动两者:舍弃 1 次预热调用,以间隔时间采集约 20 个样本,避免相互冲突,并以毫秒为单位报告中位数和 p95。

公平比较的关键在于控制变量。 

从同一台机器和同一网络测试两家服务商。理想情况下,应使用实际部署区域内的服务器,而非住宅宽带上的笔记本电脑。使用相同的输入文本,并保持音频简短,让模型推理而非生成长度主导结果。应基于多次运行报告中位数和 p95,因为单次测量只是噪声。 

请注意,公网 TTFB 包含 20-200ms 的网络往返时间,与模型无关。我们在北美、欧洲和东南亚设有集群,并会路由至最近的集群,因此应相应地将测试客户端部署在附近,否则测试的大部分其实是与数据中心的距离。

文本转语音 API 集成要点

生产环境的文本转语音 API 集成,取决于几个影响重大的决策。

做好以下几点,其余环节便会顺利衔接:

  • 按场景选择模型:交互式场景使用 Flash v2.5;对于延迟要求较低的离线渲染,使用 Multilingual v2 或 Eleven v3 等高保真模型。
  • 只要用户在等待,就使用流式传输:已知文本使用 HTTP 流式传输,智能体使用 WebSocket,避免空闲时间占用并发额度。
  • 将并行量限制在套餐额度内:将并发请求数设为略低于套餐限制,并基于所有影响输出的参数哈希进行缓存,避免同一音频重复计费。
  • 对 429 和 5xx 使用指数退避与完全抖动重试:遇到 429 和 5xx 时,使用完全抖动进行退避,并关注并发标头以了解距离限制还有多远。
  • 按句子边界拆分长文本:在各模型的字符限制内按句子边界拆分,以保留韵律

如需深入了解,请查看流式传输操作指南音频流式传输概念身份验证供客户端使用的一次性令牌

使用 ElevenAPI 构建文本转语音集成

读完本指南后,你已掌握生产环境文本转语音 API 集成所需的全部模式。无论是流式传输、批处理、缓存、重试,还是基准测试,现在都可以投入实际使用。 

立即了解文本转语音 API,或注册,今天就使用ElevenAPI完成首次调用。

文本转语音 API 集成常见问题

相关内容

用高质量 AI 音频创作