【SpringAIAlibaba新手村系列】(10)Text to Voice 文本转语音技术

张开发
2026/4/17 21:39:18 15 分钟阅读

分享文章

【SpringAIAlibaba新手村系列】(10)Text to Voice 文本转语音技术
第十章 Text to Voice 文本转语音技术版本标注Spring AI:1.1.2Spring AI Alibaba:1.1.2.2章节定位本章聚焦 TTS 基础调用也就是“把文字转成语音”。在更完整的语音应用里它通常会进一步组合成STT - Agent - TTS的语音交互链路。s01 s02 s03 s04 s05 s06 s07 s08 s09 [ s10 ] s11 s12 s13 s14 s15 s16 s17 s18文字一旦开口说话, 应用场景立刻就扩展了-- TTS 连接的是文本理解和真实交互。一、什么是 Text to Voice1.1 概念科普Text to Voice / Text to SpeechTTS说白了就是把一段文字直接变成可播放的语音。我自己学到这一章时最直观的感受是前面的聊天接口终于开始“开口说话”了整个应用一下子就从文本工具变成了语音交互应用。应用场景听书听新闻语音播报通知辅助视障人士智能客服语音有声内容创作1.2 阿里云 CosyVoice本章最终跑通时我采用的是阿里云的CosyVoice v3 Flash模型。它的几个特点很适合拿来做学习和演示中文发音自然流畅首包延迟低适合实时语音输出支持流式返回音频数据二、TextToSpeech 相关核心类2.1 DashScopeAudioSpeechModel在Spring AI Alibaba 1.1.2.2里我最后采用的是下面这个语音模型实现类com.alibaba.cloud.ai.dashscope.audio.tts.DashScopeAudioSpeechModel它在这一章里可以简单理解成“真正负责把文字送去合成语音”的那个对象。它做的事情主要有三件接收TextToSpeechPrompt调用 DashScope 的 TTS 服务以流式方式返回音频数据这里要特别注意当前版本下像cosyvoice-v3-flash这类模型通常应通过stream()使用而不是call()。2.2 TextToSpeechPromptTextToSpeechPrompt就是一次语音合成请求本身。你可以把它理解成“这次我要读什么内容、用什么参数去读”的打包对象。// 创建语音合成请求 TextToSpeechPrompt prompt new TextToSpeechPrompt( 你好我是AI助手, // 要转换的文字 options // 语音选项 );2.3 DashScopeAudioSpeechOptionsDashScopeAudioSpeechOptions则是这次语音生成的参数区。模型、音色、输出格式、采样率基本都放在这里配置。// 构建语音选项 DashScopeAudioSpeechOptions options DashScopeAudioSpeechOptions.builder() .model(cosyvoice-v3-flash) // 语音模型 .voice(longanyang) // 音色选择 .format(mp3) // 输出格式 .sampleRate(22050) // 采样率 .textType(PlainText) // 文本类型 .build();三、项目代码详解3.1 依赖配置说明3.1.1 当前章节采用的类路径这一章最后我固定采用的 TTS 实现类路径是com.alibaba.cloud.ai.dashscope.audio.tts也就是说控制器代码中的核心类来自import com.alibaba.cloud.ai.dashscope.audio.tts.DashScopeAudioSpeechModel; import com.alibaba.cloud.ai.dashscope.audio.tts.DashScopeAudioSpeechOptions;我这里最终就按这套来写因为它和当前1.1.2.2的实际运行结果是对上的。3.1.2 本章所需依赖本章最终能正常跑通依赖上我保留的是下面这两个dependency groupIdcom.alibaba.cloud.ai/groupId artifactIdspring-ai-alibaba-starter-dashscope/artifactId /dependency dependency groupIdcom.alibaba.cloud.ai/groupId artifactIdspring-ai-alibaba-dashscope/artifactId /dependency可以把它们理解成一层“自动装配”和一层“具体实现”spring-ai-alibaba-starter-dashscope提供 Spring Boot 自动配置能力spring-ai-alibaba-dashscope提供更底层的 DashScope 具体实现类包括语音合成相关实现3.1.3 推荐依赖写法如果父工程已经通过 BOM 管理版本子模块里这样写就够了dependency groupIdcom.alibaba.cloud.ai/groupId artifactIdspring-ai-alibaba-starter-dashscope/artifactId /dependency dependency groupIdcom.alibaba.cloud.ai/groupId artifactIdspring-ai-alibaba-dashscope/artifactId /dependency如果你的模块和我一样偶尔会遇到 BOM 没有稳定接管版本的问题那就直接显式把版本写出来dependency groupIdcom.alibaba.cloud.ai/groupId artifactIdspring-ai-alibaba-starter-dashscope/artifactId version1.1.2.2/version /dependency dependency groupIdcom.alibaba.cloud.ai/groupId artifactIdspring-ai-alibaba-dashscope/artifactId version1.1.2.2/version /dependency3.1.4 一个更稳的理解方式这一章我最后没有再去兜圈子追求“最抽象的写法”而是直接使用 DashScope 的具体实现类DashScopeAudioSpeechModelDashScopeAudioSpeechOptions原因很现实TTS 这块在不同版本里的 API 变化比普通对话接口更快直接用具体实现类反而更容易把模型、音色、格式和流式输出这些细节对齐。3.2 控制器代码package com.atguigu.study.controller; import com.alibaba.cloud.ai.dashscope.audio.tts.DashScopeAudioSpeechModel; import com.alibaba.cloud.ai.dashscope.audio.tts.DashScopeAudioSpeechOptions; import com.alibaba.cloud.ai.dashscope.spec.DashScopeModel; import org.springframework.ai.audio.tts.TextToSpeechPrompt; import org.springframework.ai.audio.tts.TextToSpeechResponse; import jakarta.annotation.Resource; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import reactor.core.publisher.Flux; import java.io.FileOutputStream; import java.util.List; import java.util.UUID; /** * 文本转语音控制器 * 展示如何将文字转换为语音MP3格式 */ RestController public class Text2VoiceController { Resource(name dashScopeSpeechSynthesisModel) private DashScopeAudioSpeechModel speechModel; public static final String BAILIAN_VOICE_MODEL DashScopeModel.AudioModel.COSYVOICE_V3_FLASH.getValue(); public static final String BAILIAN_VOICE_TIMBER longanyang; /** * 文本转语音 * * 接口http://localhost:8010/t2v/voice?msg温馨提醒支付宝到账100元请注意查收 * * param msg 要转成语音的文字 * return 生成的语音文件路径 */ GetMapping(/t2v/voice) public String voice(RequestParam(name msg, defaultValue 温馨提醒支付宝到账100元请注意查收) String msg) { String filePath System.getProperty(java.io.tmpdir) UUID.randomUUID() .mp3; DashScopeAudioSpeechOptions options DashScopeAudioSpeechOptions.builder() .model(BAILIAN_VOICE_MODEL) .voice(BAILIAN_VOICE_TIMBER) .format(mp3) .sampleRate(22050) .textType(PlainText) .build(); TextToSpeechPrompt prompt new TextToSpeechPrompt(msg, options); byte[] audioBytes collectStreamBytes(speechModel.stream(prompt)); if (audioBytes null || audioBytes.length 0) { throw new IllegalStateException(TTS generated no audio data); } try (FileOutputStream fileOutputStream new FileOutputStream(filePath)) { fileOutputStream.write(audioBytes); } catch (Exception e) { throw new RuntimeException(Failed to write audio file, e); } return filePath; } private byte[] collectStreamBytes(FluxTextToSpeechResponse stream) { Listbyte[] chunks stream .filter(r - r ! null r.getResult() ! null r.getResult().getOutput() ! null) .map(r - r.getResult().getOutput()) .collectList() .block(); if (chunks null || chunks.isEmpty()) { return new byte[0]; } int total chunks.stream().mapToInt(b - b.length).sum(); byte[] result new byte[total]; int offset 0; for (byte[] chunk : chunks) { System.arraycopy(chunk, 0, result, offset, chunk.length); offset chunk.length; } return result; } }3.3 为什么要这样写这一章真正绕人的地方不在于“怎么把字节写进文件”而在于当前版本的语音模型应该怎么调用。我最后跑通以后结论其实很明确cosyvoice-v3-flash这类模型更适合走stream()不适合再按传统同步call()的思路去写。所以这个实现真正要抓住的是三个点显式指定模型和音色确保模型版本和音色是匹配的组合显式指定输出格式通过.format(mp3)告诉服务端返回 MP3 音频流手动拼接音频块stream()返回的是多个音频分片需要把所有byte[]顺序拼接后才能写成完整文件这几个参数里最容易忽略但又最关键的是.sampleRate(22050)用来约定输出采样率.textType(PlainText)用来明确当前输入是普通文本而不是其他格式collectStreamBytes(...)的作用就是把多个流式分片还原成完整音频字节数组所以最后这段代码的思路就变成了不是等服务端一次性把完整文件塞回来而是先收集流式返回的音频块再在本地把它们拼成完整文件。四、音色选择与参数调整4.1 可用音色列表实际写代码时音色最好不要随便猜。我这里先列几个常见音色够做学习和实验用了音色名称音色描述适用场景longanyang龙阳标准男声xiaoyuan小圆满清亮女声yaying雅音温柔女声zhishengtts致远标准男声4.2 参数调优// 调整语速 .withSpeed(0.8) // 0.5-2.0越小越慢越大越快 // 调整音量 .withVolume(1.2) // 0.1-10.0默认1.0 // 调整音调 .withPitch(2.0) // -12.0到12.0正值偏高负值偏低五、音频播放与后续处理5.1 在前端播放后端把文件生成出来后前端最直接的做法就是用 HTML5 的audio标签播放!-- 直接播放 -- audio controls source srchttp://localhost:8010/audio/xxx.mp3 typeaudio/mpeg /audio !-- 或者用 JavaScript -- script new Audio(http://localhost:8010/audio/xxx.mp3).play(); /script5.2 生成播放链接如果需要提供 HTTP 访问可以配置静态资源或文件服务# application.yml spring: web: resources: static-locations: file:d:/,classpath:/static/然后在控制器里返回一个可访问的 URL而不是磁盘绝对路径。六、本章小结6.1 核心概念概念说明DashScopeAudioSpeechModel语音合成的核心模型TextToSpeechPrompt语音合成的请求对象CosyVoice阿里云语音合成模型Flux流式返回的语音分片byte[]最终拼接后的完整音频数据6.2 使用流程1. 准备要转换的文字 2. 创建语音选项模型、音色、语速等 3. 生成语音合成请求 4. 调用 speechModel.stream(prompt) 获取流式语音分片 5. 拼接多个 byte[] 分片为完整音频 6. 将完整音频写入文件6.3 注意事项生成的音频文件最好放到临时目录、对象存储或统一文件服务里管理不建议长期直接落在本地磁盘根目录音色不要只看名字最好和当前模型版本一起确认如果后面发现文件能生成但播放器打不开优先先检查输出格式、采样率和流式拼接逻辑如果后面继续往语音 Agent 方向扩展这一章的 TTS 能力基本就可以直接作为最后的“发声出口”。本章重点掌握DashScopeAudioSpeechModel.stream()的使用方法理解流式音频分片如何拼接为完整文件能够正确配置模型、音色、输出格式和采样率下章剧透s11学会了文字生成语音后下一章我们将学习 Embedding向量化——让 AI 理解文本的数学表示这也是 RAG 技术的核心基础TIP从 TTS 到 Voice Agent本章解决的是文本转语音这一小段链路。但真实的语音交互应用通常还会继续向前补一段语音输入 - 语音识别(STT) - Agent 推理 - 文本转语音(TTS)你可以把本章学到的DashScopeAudioSpeechModel直接看成这条语音链路中的最后一环。编辑者Flittly更新时间2026年4月

更多文章