前面我们已经让 AI 学会了对话、推理,甚至能画图。今天,我们要解锁一种更自然的交互方式——语音。让应用能“说”出回复,也能“听懂”你的话。

文本转语音(Text-to-Speech,TTS)和语音转文字(Speech-to-Text,STT)是构建多模态 AI 应用的两块基石。它们让 AI 不再只是屏幕上冷冰冰的文字,而是可以播报消息、处理电话录音、朗读文章。Spring AI 对语音模型也做了统一封装,今天我们就用 OpenAI 的语音服务,一次性实现这两种能力。

一、痛点场景:为什么需要语音交互?

场景一:语音播报

你做了一个智能助手应用,用户在开车或做饭时不便看屏幕,想让 AI 把查到的天气、新闻读出来。这就需要把文字转换成自然流畅的语音。

场景二:语音输入

用户不想打字,直接说话:“明天早上8点提醒我开会”。应用需要把语音转成文字,再交给聊天模型处理。这就是 STT 的典型用例。

场景三:电话客服分析

公司有大量客服通话录音,想自动分析客户的投诉内容。你需要批量把这些录音转成文本,然后做情感分析或意图识别。

如果自己去对接 OpenAI 的 Audio API 或者云厂商的语音服务,又要处理鉴权、请求封装、流式响应、文件格式转换……Spring AI 把这一切都简化成了类似 ChatClient 的调用风格。你只需要关注输入和输出,框架帮你处理中间所有脏活。

二、核心概念快览

2.1 语音模型接口

Spring AI 提供了统一的语音模型抽象,目前包含两个核心接口:

  • SpeechModel:负责文本转语音(TTS)。调用 call(String text) 返回语音数据。
  • AudioTranscriptionModel:负责语音转文字(STT)。接收音频文件,返回转写后的文本。

OpenAI 的实现类分别是 OpenAiAudioSpeechModelOpenAiAudioTranscriptionModel,它们都已经包含在 spring-ai-starter-model-openai 中。

2.2 SpeechPrompt 和 SpeechResponse

  • SpeechPrompt:TTS 的输入对象,封装要转换的文本以及语音选项(如声音类型、语速、音频格式)。
  • SpeechResponse:TTS 的输出对象,里面包含一个 byte[] 类型的音频数据,可以直接写回客户端或保存成文件。

2.3 语音转写的输入输出

STT 的输入是一个音频资源(文件路径或 InputStream),加上可选的转写选项(如语言)。输出是一个字符串,即转写结果。Spring AI 把输入抽象为 org.springframework.core.io.Resource,你可以传文件、字节数组或输入流。

2.4 工作流程

TTS 流程

用户输入文本
    ↓
构建 SpeechPrompt(text, options)
    ↓
调用 speechModel.call(speechPrompt)
    ↓
返回 SpeechResponse(byte[] 音频数据)
    ↓
接口以 audio/mpeg 形式返回给前端

STT 流程

用户上传音频文件
    ↓
将 MultipartFile 转为 Resource
    ↓
调用 transcriptionModel.call(resource, options)
    ↓
返回转写后的文字

三、环境准备

3.1 API Key

使用 OpenAI 的语音模型需要有效的 API Key,且必须拥有语音服务的调用权限。获取地址仍是 platform.openai.com/api-keys。语音模型按字符(TTS)或按分钟(STT)计费,成本很低,适合学习测试。请设置环境变量:

export OPENAI_API_KEY=sk-xxx

3.2 Maven 依赖

继续使用 spring-ai-starter-model-openai,它已经内置了 TTS 和 STT 所需的全部依赖,无需额外引入。

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-model-openai</artifactId>
</dependency>

3.3 application.yml 配置

spring:
  ai:
    openai:
      api-key: ${OPENAI_API_KEY}
      # 语音模型配置
      audio:
        speech:
          options:
            model: tts-1          # TTS 模型,可选 tts-1-hd(更高质量)
            voice: alloy          # 声音类型:alloy, echo, fable, onyx, nova, shimmer
            speed: 1.0            # 语速,0.25 ~ 4.0
            response-format: mp3  # 输出音频格式,默认 mp3
        transcription:
          options:
            model: whisper-1      # STT 模型,目前只有 whisper-1
            language: zh          # 音频语言,如 en, zh,留空则自动检测
            response-format: json # 返回格式

声音类型说明

  • alloy:中性、温和(推荐默认使用)
  • echo:深沉、有共鸣
  • fable:高音调、有表现力
  • onyx:深沉、稳重
  • nova:温暖、亲切
  • shimmer:清晰、明亮

你可以让用户自行选择喜欢的声音。

四、代码实战

4.1 创建 SpeechService

新建 SpeechService.java,封装 TTS 和 STT 两种能力:

package com.example.springaihelloworld.service;

import org.springframework.ai.openai.OpenAiAudioSpeechModel;
import org.springframework.ai.openai.OpenAiAudioTranscriptionModel;
import org.springframework.ai.openai.OpenAiAudioSpeechOptions;
import org.springframework.ai.openai.OpenAiAudioTranscriptionOptions;
import org.springframework.ai.audio.speech.SpeechPrompt;
import org.springframework.ai.audio.speech.SpeechResponse;
import org.springframework.core.io.InputStreamResource;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.io.InputStream;

@Service
public class SpeechService {

    private final OpenAiAudioSpeechModel speechModel;
    private final OpenAiAudioTranscriptionModel transcriptionModel;

    public SpeechService(OpenAiAudioSpeechModel speechModel,
                         OpenAiAudioTranscriptionModel transcriptionModel) {
        this.speechModel = speechModel;
        this.transcriptionModel = transcriptionModel;
    }

    /**
     * 文本转语音,返回音频字节数组
     * @param text 要转换的文本
     * @return 音频数据(MP3 byte[])
     */
    public byte[] textToSpeech(String text) {
        SpeechResponse response = speechModel.call(
                new SpeechPrompt(text, OpenAiAudioSpeechOptions.builder()
                        .withModel("tts-1")      // 或 tts-1-hd
                        .withVoice("alloy")
                        .withSpeed(1.0f)
                        .withResponseFormat("mp3")
                        .build()));

        return response.getResult().getOutput(); // byte[] 形式的音频数据
    }

    /**
     * 语音转文字
     * @param audioFile 用户上传的音频文件
     * @return 转写后的文本
     */
    public String speechToText(MultipartFile audioFile) throws IOException {
        // 将 MultipartFile 转为 Resource
        Resource audioResource = new InputStreamResource(audioFile.getInputStream());

        // 调用转写模型,这里不额外设置 options,使用配置文件中的默认值
        String transcript = transcriptionModel.call(
                audioResource,
                OpenAiAudioTranscriptionOptions.builder()
                        .withLanguage("zh")         // 可省略,自动检测
                        .withResponseFormat("json")
                        .build());

        return transcript;
    }
}

关键点解读

  • textToSpeech 返回的是音频原始字节数组,后续可以写入 OutputStream 或直接返回给浏览器。
  • speechToText 接收 MultipartFile,通过 Spring 的 InputStreamResource 适配后传入模型。
  • 在 TTS 中,如果不指定 Options,框架会使用 application.yml 中的默认配置,这里为了演示灵活性显式设置了。

4.2 创建 SpeechController

新建 SpeechController.java,提供两个 REST 端点:

package com.example.springaihelloworld.controller;

import com.example.springaihelloworld.service.SpeechService;
import org.springframework.http.ContentDisposition;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;

@RestController
public class SpeechController {

    private final SpeechService speechService;

    public SpeechController(SpeechService speechService) {
        this.speechService = speechService;
    }

    /**
     * 文本转语音接口
     * GET /speech?text=你好,欢迎使用Spring AI
     * 返回 MP3 音频文件,浏览器可直接播放
     */
    @GetMapping("/speech")
    public ResponseEntity<byte[]> textToSpeech(@RequestParam String text) {
        byte[] audioData = speechService.textToSpeech(text);

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.parseMediaType("audio/mpeg"));
        headers.setContentDisposition(ContentDisposition
                .inline()   // inline 表示浏览器直接播放,attachment 表示下载
                .filename("speech.mp3")
                .build());
        headers.setContentLength(audioData.length);

        return ResponseEntity.ok()
                .headers(headers)
                .body(audioData);
    }

    /**
     * 语音转文字接口
     * POST /transcribe (multipart/form-data)
     * 上传音频文件,返回转写文本
     */
    @PostMapping(value = "/transcribe", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    public String speechToText(@RequestParam("file") MultipartFile file) throws IOException {
        return speechService.speechToText(file);
    }
}

4.3 前端测试页面(可选)

为了直观测试,可以在 src/main/resources/static 下放一个简单 HTML 页面 speech-test.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>语音测试</title>
</head>
<body>
    <h2>TTS:文字转语音</h2>
    <input id="textInput" type="text" value="你好,我是Spring AI语音助手" />
    <button onclick="playSpeech()">播放语音</button>
    <audio id="audioPlayer" controls></audio>

    <h2>STT:语音转文字</h2>
    <input id="fileInput" type="file" accept="audio/*" />
    <button onclick="transcribe()">开始转写</button>
    <p id="transcript"></p>

    <script>
        function playSpeech() {
            const text = document.getElementById('textInput').value;
            document.getElementById('audioPlayer').src = '/speech?text=' + encodeURIComponent(text);
        }

        async function transcribe() {
            const file = document.getElementById('fileInput').files[0];
            if (!file) return;
            const formData = new FormData();
            formData.append('file', file);
            const resp = await fetch('/transcribe', { method: 'POST', body: formData });
            const text = await resp.text();
            document.getElementById('transcript').textContent = '转写结果:' + text;
        }
    </script>
</body>
</html>

五、运行与演示

5.1 启动应用

确保 OPENAI_API_KEY 环境变量已设置,启动 Spring Boot。

5.2 测试 TTS(文本转语音)

直接在浏览器输入:

http://localhost:8080/speech?text=你好,欢迎学习Spring AI语音交互

浏览器会直接播放 MP3 音频,你会听到一个自然的人声朗读出这句话。试试换成不同的 voice 参数(可以通过接口扩展),感受声音风格的变化。

也可以在 HTML 页面中测试。

5.3 测试 STT(语音转文字)

使用 curl 上传一个音频文件(推荐使用 .mp3.wav 格式,时长不超过 25MB):

curl -X POST -F "file=@/path/to/your/audio.mp3" http://localhost:8080/transcribe

返回类似:

你好,欢迎学习Spring AI语音交互

你也可以用手机录一段话,传到电脑上测试。

六、常见问题与避坑提示

问题一:TTS 中文发音不自然

OpenAI 的 TTS 目前主要针对英文优化,中文发音虽然可用,但在部分多音字或节奏上可能略显生硬。如果追求极致中文语音体验,可以考虑阿里云百炼平台的语音合成服务(下一篇会讲到)。

问题二:STT 模型仅支持 whisper-1

OpenAI 的语音转写模型目前固定为 whisper-1,不能选择其他模型。whisper-1 支持多种语言,但准确率在某些方言或嘈杂环境下会下降。如果文件背景噪音大,建议先用音频处理工具降噪。

问题三:上传音频文件大小限制

Spring Boot 默认文件上传大小限制为 1MB,而语音文件很容易超过这个限制。需要在 application.yml 中调整:

spring:
  servlet:
    multipart:
      max-file-size: 30MB
      max-request-size: 30MB

OpenAI 官方限制音频文件不超过 25MB,所以设置 30MB 足够。

问题四:流式 TTS 支持

上述代码是一次性生成全部音频再返回。OpenAI API 本身支持流式 TTS(stream 参数),但 Spring AI 1.1.6 的语音模型尚未封装流式 API。如果你有实时播报的需求,可以暂时用普通方式生成音频后通过 Transfer-Encoding: chunked 分段发送,或者关注 Spring AI 新版本的更新。

问题五:Base URL 问题

如果你通过代理访问 OpenAI(比如使用国内中转服务),同样可以在 application.yml 中配置 base-url,覆盖语音服务的请求地址:

spring:
  ai:
    openai:
      audio:
        speech:
          base-url: https://your-proxy.com/v1
        transcription:
          base-url: https://your-proxy.com/v1

如果没有特殊网络环境,保持默认即可。

七、小结与下一步预告

本篇回顾

  • 理解了 TTS 和 STT 的实际应用场景。
  • 掌握了 Spring AI 中 SpeechModelAudioTranscriptionModel 的统一调用方式。
  • 用极简的代码实现了文字转语音播放,以及音频文件转写文字。
  • 学会了处理音频返回和文件上传的细节。

动手建议

在你的项目中加入一个“语音播报”功能:当 AI 完成一段长回复后,自动调用 TTS 接口生成音频,给用户“听”的选项。然后把日志系统里的错误信息自动转成语音播报给运维人员——这只是趣味玩法,但能让你快速体验语音交互的潜力。

下一步预告

语音能力目前还只是 OpenAI 一家。作为国产生态的深度使用者,你一定也想看看阿里云百炼平台能提供怎样的多模态能力。下一篇,我们将进入 “百炼多模态全家桶”,用 Spring AI Alibaba 一站式搞定图像生成、语音合成、语音识别,甚至视频生成。一套 API,多模态全通。

下一篇《百炼多模态全家桶:图像、语音、视频一站式搞定》见。


本系列博客基于 Spring AI 1.1.6 版本编写。语音模型的可用性和定价请参考 OpenAI 官方文档。如果遇到 TTS 中文效果不理想的情况,可以考虑在下一篇中迁移至阿里云百炼平台的语音服务。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐