文章目录
- Spring AI Alibaba 语音模型实战(TTS + STT)
- 版本
- 一、依赖引入
- 二、yml 配置
- 三、代码案例:TTS(文字 → 音频)
- 四、代码案例:STT(音频 → 文字)
- 1. 公网 URL + 同步 `call()`(录音文件识别)
- 2. 本地上传:用 `stream()`
Spring AI Alibaba 语音模型实战(TTS + STT)
用 Spring AI Alibaba 接入百炼:文字转语音(TTS)与语音转文字(STT)。
版本
- Spring Boot:
3.4.5 - spring-ai-alibaba-starter-dashscope:
1.1.2.1(修复call()只返回部分音频的问题) - Java:
17
Spring-ai更新速度快,请以官方为准
可参考网址:
- Spring-ai官网
- Spring-ai阿里巴巴
- 阿里百炼模型广场
- 开发者指南(API/SDK)
一、依赖引入
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>com.alibaba.cloud.ai</groupId><artifactId>spring-ai-alibaba-starter-dashscope</artifactId><!-- 1.1.2.1:TTS call() 能拿完整音频 --><version>1.1.2.1</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>dashscope-sdk-java</artifactId><version>2.22.18</version></dependency>二、yml 配置
spring:application:name:spring-audio-demoai:dashscope:api-key:${DASHSCOPE_API_KEY}audio:synthesis:options:# 默认音色配置;接口里也可按需覆盖 model / voicemodel:cosyvoice-v1voice:longhua说明:CosyVoice 的model 与 voice 必须成套。例如代码里用cosyvoice-v3-flash时,音色要用longdaiyu_v3这类 v3 音色,否则可能 418。
三、代码案例:TTS(文字 → 音频)
@RestController@RequestMapping("/ai/tts")publicclassTTSAudioController{/** 相对运行目录的 audio/;IDE 从父工程启动时可能落在 spring-ai-second/audio */privatestaticfinalPathAUDIO_DIR=Paths.get("audio");privatestaticfinalStringDEFAULT_TEXT="哟,我来的不巧了,早知她来,我就不来了.";/** 注入 DashScope 语音合成模型(Bean 名:dashScopeSpeechSynthesisModel) */privatefinalTextToSpeechModeltextToSpeechModel;publicTTSAudioController(@Qualifier("dashScopeSpeechSynthesisModel")TextToSpeechModeltextToSpeechModel){this.textToSpeechModel=textToSpeechModel;}/** * GET /ai/tts/speak?text=你好 * 流程:拼 Options → Prompt → call() → 落盘 → 返回 mp3 字节 */@GetMapping(value="/speak",produces="audio/mpeg")publicbyte[]speak(@RequestParam(defaultValue=DEFAULT_TEXT)Stringtext){// 1)合成参数:模型、音色、格式、采样率等DashScopeAudioSpeechOptionsoptions=DashScopeAudioSpeechOptions.builder().model("cosyvoice-v3-flash")// 合成模型.voice("longdaiyu_v3")// 必须与模型版本匹配.responseFormat(DashScopeAudioSpeechApi.ResponseFormat.MP3).sampleRate(22050)// 采样率.volume(50)// 音量 0~100.speed(1.0)// 语速.build();// 2)文本 + 参数 → PromptTextToSpeechPromptprompt=newTextToSpeechPrompt(text,options);// 3)同步合成,拿到音频字节TextToSpeechResponseresponse=textToSpeechModel.call(prompt);byte[]audio=response.getResult().getOutput();// 4)先存本地,再读回(学习「生成 → 落盘 → 再读」)Stringfilename="tts-"+UUID.randomUUID()+".mp3";saveToFolder(audio,filename);returnreadFromFolder(filename);}privatevoidsaveToFolder(byte[]data,Stringfilename){try{Files.createDirectories(AUDIO_DIR);Files.write(AUDIO_DIR.resolve(filename),data);}catch(IOExceptione){e.printStackTrace();}}privatebyte[]readFromFolder(Stringfilename){try{returnFiles.readAllBytes(AUDIO_DIR.resolve(filename));}catch(IOExceptione){e.printStackTrace();returnnewbyte[0];}}}浏览器直接访问/ai/tts/speak,即可播放返回的 MP3。
四、代码案例:STT(音频 → 文字)
1. 公网 URL + 同步call()(录音文件识别)
@RestController@RequestMapping("/ai/stt")publicclassController{/** * call() 走「录音文件识别」:云端自己下载音频,所以必须是公网 URL。 * 本地文件 / MultipartFile 对云端不可见,要用 stream()(见下方 upload)。 */privatestaticfinalStringDEMO_AUDIO_URL="https://dashscope.oss-cn-beijing.aliyuncs.com/samples/audio/paraformer/hello_world_female2.wav";privatefinalDashScopeAudioTranscriptionModeltranscriptionModel;publicController(DashScopeAudioTranscriptionModeltranscriptionModel){this.transcriptionModel=transcriptionModel;}/** * GET /ai/stt/basic * 流程:Options → UrlResource → Prompt → call() → 文本 */@GetMapping(value="/basic",produces="text/html;charset=UTF-8")publicStringbasic(@RequestParam(defaultValue=DEMO_AUDIO_URL)Stringurl)throwsMalformedURLException{DashScopeAudioTranscriptionOptionsoptions=DashScopeAudioTranscriptionOptions.builder()// 录音文件识别模型(非实时).model(DashScopeModel.AudioModel.PARAFORMER_V2.getValue()).languageHints(List.of("zh","en")).build();// 告诉框架:音频在这个网络地址UrlResourceaudioResource=newUrlResource(url);AudioTranscriptionPromptprompt=newAudioTranscriptionPrompt(audioResource,options);// 同步调用:提交任务并等待(内部会轮询;日志里出现 pending 属正常)AudioTranscriptionResponseresponse=transcriptionModel.call(prompt);Stringtext=response.getResult().getOutput();return"识别结果: "+text;}}2. 本地上传:用stream()
上传文件时云端拿不到你本机路径,应走实时流:
/** * POST /ai/stt/upload ,form-data 字段名 file * sampleRate 必须与文件真实采样率一致(本项目 TTS 的 mp3 常用 22050) */@PostMapping(value="/upload",produces="text/html;charset=UTF-8")publicStringupload(@RequestParam("file")MultipartFilefile,@RequestParam(defaultValue="22050")IntegersampleRate)throwsIOException{DashScopeAudioTranscriptionOptionsoptions=DashScopeAudioTranscriptionOptions.builder().model(DashScopeModel.AudioModel.PARAFORMER_REALTIME_V2.getValue()).sampleRate(sampleRate).format(DashScopeAudioTranscriptionApi.AudioFormat.MP3)// 按实际后缀选择.languageHints(List.of("zh","en")).build();ResourceaudioResource=newByteArrayResource(file.getBytes()){@OverridepublicStringgetFilename(){returnfile.getOriginalFilename();}};AudioTranscriptionPromptprompt=newAudioTranscriptionPrompt(audioResource,options);// 实时流每帧常是「整句修订版」,取最后一帧即可,不要把所有帧 join 起来Stringtext=transcriptionModel.stream(prompt).map(r->r.getResult()==null?"":r.getResult().getOutput()).filter(t->t!=null&&!t.isBlank()).last("").block();return"识别结果: "+text;}