Lzh on GitHub

Text-To-Speech (TTS) API

Spring AI 提供了统一的文本转语音(TTS)API,通过 TextToSpeechModel 和 StreamingTextToSpeechModel 接口实现。这使您可以编写可移植的代码,适用于不同的 TTS 提供商。

Spring AI 提供了统一的文本转语音(TTS)API,通过 TextToSpeechModelStreamingTextToSpeechModel 接口实现。这使您可以编写可移植的代码,适用于不同的 TTS 提供商。

支持的提供商

通用接口

所有文本转语音(TTS)提供商都实现以下共享接口:

文本转语音模型(TextToSpeechModel)

TextToSpeechModel 接口提供了将文本转换为语音的方法:

public interface TextToSpeechModel extends Model<TextToSpeechPrompt, TextToSpeechResponse>, StreamingTextToSpeechModel {

    /**
     * 使用默认选项将文本转换为语音。
     */
    default byte[] call(String text) {
        // 默认实现
    }

    /**
     * 使用自定义选项将文本转换为语音。
     */
    TextToSpeechResponse call(TextToSpeechPrompt prompt);

    /**
     * 返回该模型的默认选项。
     */
    default TextToSpeechOptions getDefaultOptions() {
        // 默认实现
    }
}

流式文本转语音模型(StreamingTextToSpeechModel)

StreamingTextToSpeechModel 接口提供了用于 实时流式输出音频 的方法:

@FunctionalInterface
public interface StreamingTextToSpeechModel extends StreamingModel<TextToSpeechPrompt, TextToSpeechResponse> {

    /**
     * 以流的形式返回文本转语音结果,并包含元数据。
     */
    Flux<TextToSpeechResponse> stream(TextToSpeechPrompt prompt);

    /**
     * 针对给定文本,以流的形式输出音频字节数据。
     */
    default Flux<byte[]> stream(String text) {
        // 默认实现
    }
}

文本转语音提示(TextToSpeechPrompt)

TextToSpeechPrompt 类用于封装 输入文本 以及 相关配置选项

TextToSpeechPrompt prompt = new TextToSpeechPrompt(
    "你好,这是一个文本转语音的示例。",
    options
);

其中,TextToSpeechPrompt 将待转换的文本与对应的语音合成参数(options)一起打包,作为一次文本转语音请求传递给模型。

文本转语音响应(TextToSpeechResponse)

TextToSpeechResponse 类包含 生成的音频数据 以及 相关的元数据信息

TextToSpeechResponse response = model.call(prompt);
byte[] audioBytes = response.getResult().getOutput();
TextToSpeechResponseMetadata metadata = response.getMetadata();

其中:

  • audioBytes:模型生成的语音音频字节数据
  • metadata:本次文本转语音请求的元数据(例如模型信息、处理状态等)

编写与提供商无关的代码

共享的 TTS 接口带来的一个关键优势是:无需修改代码即可适配不同的文本转语音(TTS)服务提供商

实际使用的提供商(如 OpenAI、ElevenLabs 等)由 Spring Boot 的配置决定,因此你可以 仅通过配置切换 TTS 提供商,而无需更改任何应用代码

基本服务示例

共享接口使你可以编写 适用于任何 TTS 提供商的通用代码

@Service
public class NarrationService {

    private final TextToSpeechModel textToSpeechModel;

    public NarrationService(TextToSpeechModel textToSpeechModel) {
        this.textToSpeechModel = textToSpeechModel;
    }

    public byte[] narrate(String text) {
        // 适用于任何 TTS 提供商
        return textToSpeechModel.call(text);
    }

    public byte[] narrateWithOptions(String text, TextToSpeechOptions options) {
        TextToSpeechPrompt prompt = new TextToSpeechPrompt(text, options);
        TextToSpeechResponse response = textToSpeechModel.call(prompt);
        return response.getResult().getOutput();
    }
}

该服务可以 无缝地与 OpenAI、ElevenLabs 或其他任何 TTS 提供商协同工作,具体使用哪一种实现完全由 Spring Boot 的配置决定,无需修改应用代码。

高级示例:多提供商支持

你可以构建 同时支持多个 TTS 提供商的应用程序

@Service
public class MultiProviderNarrationService {

    private final Map<String, TextToSpeechModel> providers;

    public MultiProviderNarrationService(List<TextToSpeechModel> models) {
        // Spring 会注入所有可用的 TextToSpeechModel Bean
        this.providers = models.stream()
            .collect(Collectors.toMap(
                model -> model.getClass().getSimpleName(),
                model -> model
            ));
    }

    public byte[] narrateWithProvider(String text, String providerName) {
        TextToSpeechModel model = providers.get(providerName);
        if (model == null) {
            throw new IllegalArgumentException("未知的提供商: " + providerName);
        }
        return model.call(text);
    }

    public Set<String> getAvailableProviders() {
        return providers.keySet();
    }
}

通过这种方式,你的应用可以在 同一运行时环境中同时集成并使用多个 TTS 提供商,并根据需要动态选择具体的语音合成服务。

流式音频示例

共享接口同样支持 流式(Streaming)处理,可用于 实时音频生成

@Service
public class StreamingNarrationService {

    private final TextToSpeechModel textToSpeechModel;

    public StreamingNarrationService(TextToSpeechModel textToSpeechModel) {
        this.textToSpeechModel = textToSpeechModel;
    }

    public Flux<byte[]> streamNarration(String text) {
        // TextToSpeechModel 继承了 StreamingTextToSpeechModel
        return textToSpeechModel.stream(text);
    }

    public Flux<TextToSpeechResponse> streamWithMetadata(String text, TextToSpeechOptions options) {
        TextToSpeechPrompt prompt = new TextToSpeechPrompt(text, options);
        return textToSpeechModel.stream(prompt);
    }
}

通过流式接口,你可以在文本尚未完全转换完成时就开始接收并播放音频,非常适合 实时播报、对话式语音输出 等场景。

REST 控制器示例

构建一个 与具体提供商无关(provider-agnostic)的 TTS REST API 示例:

@RestController
@RequestMapping("/api/tts")
public class TextToSpeechController {

    private final TextToSpeechModel textToSpeechModel;

    public TextToSpeechController(TextToSpeechModel textToSpeechModel) {
        this.textToSpeechModel = textToSpeechModel;
    }

    @PostMapping(value = "/synthesize", produces = "audio/mpeg")
    public ResponseEntity<byte[]> synthesize(@RequestBody SynthesisRequest request) {
        byte[] audio = textToSpeechModel.call(request.text());
        return ResponseEntity.ok()
            .contentType(MediaType.parseMediaType("audio/mpeg"))
            .header("Content-Disposition", "attachment; filename=\"speech.mp3\"")
            .body(audio);
    }

    @GetMapping(value = "/stream", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
    public Flux<byte[]> streamSynthesis(@RequestParam String text) {
        return textToSpeechModel.stream(text);
    }

    record SynthesisRequest(String text) {}
}

该 REST API 的特点是:

  • 与具体 TTS 提供商解耦:无需关心使用的是 OpenAI、ElevenLabs 还是其他提供商
  • 统一接口调用:通过 TextToSpeechModel 即可完成语音合成
  • 支持两种模式
    • /synthesize:一次性生成并返回音频文件
    • /stream:以流的方式实时返回音频数据

具体使用哪一个 TTS 提供商,完全由 Spring Boot 配置 决定,无需修改任何业务代码。

基于配置的提供商选择

通过 Spring Profile 或配置属性 在不同 TTS 提供商之间进行切换:

# application-openai.yml
spring:
  ai:
    model:
      audio:
        speech: openai
    openai:
      api-key: ${OPENAI_API_KEY}
      audio:
        speech:
          options:
            model: gpt-4o-mini-tts
            voice: alloy

# application-elevenlabs.yml
spring:
  ai:
    model:
      audio:
        speech: elevenlabs
    elevenlabs:
      api-key: ${ELEVENLABS_API_KEY}
      tts:
        options:
          model-id: eleven_turbo_v2_5
          voice-id: your_voice_id

然后通过激活对应的 Profile 来选择所需的提供商:

# 使用 OpenAI
java -jar app.jar --spring.profiles.active=openai

# 使用 ElevenLabs
java -jar app.jar --spring.profiles.active=elevenlabs

这种方式使你可以在 不修改任何代码 的情况下,仅通过配置就灵活切换不同的 TTS 提供商,既方便开发调试,也有利于在不同环境中部署。

使用可移植选项

为了实现最大程度的可移植性,仅使用通用的 TextToSpeechOptions 接口方法:

@Service
public class PortableNarrationService {

    private final TextToSpeechModel textToSpeechModel;

    public PortableNarrationService(TextToSpeechModel textToSpeechModel) {
        this.textToSpeechModel = textToSpeechModel;
    }

    public byte[] createPortableNarration(String text) {
        // 使用提供商的默认选项以实现最大可移植性
        TextToSpeechOptions defaultOptions = textToSpeechModel.getDefaultOptions();
        TextToSpeechPrompt prompt = new TextToSpeechPrompt(text, defaultOptions);
        TextToSpeechResponse response = textToSpeechModel.call(prompt);
        return response.getResult().getOutput();
    }
}

这种做法可以确保你的代码在不同 TTS 提供商之间无缝迁移,无需依赖任何提供商特定的选项或功能。

使用提供商特定功能

当你需要使用提供商特定功能时,仍然可以在保持可移植代码的同时使用它们:

@Service
public class FlexibleNarrationService {

    private final TextToSpeechModel textToSpeechModel;

    public FlexibleNarrationService(TextToSpeechModel textToSpeechModel) {
        this.textToSpeechModel = textToSpeechModel;
    }

    public byte[] narrate(String text, TextToSpeechOptions baseOptions) {
        TextToSpeechOptions options = baseOptions;

        // 如果可用,则应用提供商特定优化
        if (textToSpeechModel instanceof OpenAiAudioSpeechModel) {
            options = OpenAiAudioSpeechOptions.builder()
                .from(baseOptions)
                .model("gpt-4o-tts")  // OpenAI特定:使用高质量模型
                .speed(1.0)
                .build();
        } else if (textToSpeechModel instanceof ElevenLabsTextToSpeechModel) {
            // 这里可以添加 ElevenLabs 特定的选项
        }

        TextToSpeechPrompt prompt = new TextToSpeechPrompt(text, options);
        TextToSpeechResponse response = textToSpeechModel.call(prompt);
        return response.getResult().getOutput();
    }
}

这种方式让你在大多数情况下使用通用选项保持可移植性,同时在特定提供商上启用高级功能或优化。

可移植代码最佳实践

  1. 依赖接口:始终注入 TextToSpeechModel,而不是具体实现类。
  2. 使用通用选项:尽量使用 TextToSpeechOptions 接口的方法,以实现最大的可移植性。
  3. 优雅处理元数据:不同提供商返回的元数据可能不同,应以通用方式处理。
  4. 使用多提供商测试:确保你的代码至少在两个 TTS 提供商下可正常运行。
  5. 记录提供商假设:如果你的代码依赖于特定提供商的行为,应明确记录这些假设。

提供商特定功能

虽然共享接口提供了可移植性,但每个提供商也通过特定的选项类提供专有功能(例如 OpenAiAudioSpeechOptionsElevenLabsSpeechOptions)。这些类在实现 TextToSpeechOptions 接口的同时,增加了针对各自提供商的特定功能。