语音交互实战:TTS 与 STT 的 Spring AI 封装
前面我们已经让 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 的实现类分别是 OpenAiAudioSpeechModel 和 OpenAiAudioTranscriptionModel,它们都已经包含在 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 中
SpeechModel和AudioTranscriptionModel的统一调用方式。 - 用极简的代码实现了文字转语音播放,以及音频文件转写文字。
- 学会了处理音频返回和文件上传的细节。
动手建议
在你的项目中加入一个“语音播报”功能:当 AI 完成一段长回复后,自动调用 TTS 接口生成音频,给用户“听”的选项。然后把日志系统里的错误信息自动转成语音播报给运维人员——这只是趣味玩法,但能让你快速体验语音交互的潜力。
下一步预告
语音能力目前还只是 OpenAI 一家。作为国产生态的深度使用者,你一定也想看看阿里云百炼平台能提供怎样的多模态能力。下一篇,我们将进入 “百炼多模态全家桶”,用 Spring AI Alibaba 一站式搞定图像生成、语音合成、语音识别,甚至视频生成。一套 API,多模态全通。
下一篇《百炼多模态全家桶:图像、语音、视频一站式搞定》见。
本系列博客基于 Spring AI 1.1.6 版本编写。语音模型的可用性和定价请参考 OpenAI 官方文档。如果遇到 TTS 中文效果不理想的情况,可以考虑在下一篇中迁移至阿里云百炼平台的语音服务。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)