Spring AI Alibaba AudioModel使用
一、AudioModel简介
1、AudioModel
当前,Spring AI Alibaba 支持以下两种通义语音模型的适配,分别是:
- 文本生成语音 SpeechModel,对应于 OpenAI 的 Text-To-Speech (TTS) API
- 录音文件生成文字 DashScopeAudioTranscriptionModel,对应于 OpenAI 的 Transcription API
二、AudioModel使用
Spring AI Alibaba对话模型(Chat Model):https://java2ai.com/docs/1.0.0-M5.1/tutorials/chat-model/
Spring AI Alibaba 支持Model 抽象与通义系列模型的适配,并通过 spring-ai-alibaba-starter AutoConfiguration 自动初始化了默认实例,因此我们可以在应用程序中直接注入 ChatModel、ImageModel 等 bean,当然在需要的时候也可以自定义 Model 实例。
1、文本生成语音
在普通 Controller Bean 中注入 SpeechSynthesisModel 实例,实现“文本生成语音”功能:
- 简单|流式调用
- 自定义 LLMs 参数调用
编写 Controller接口:
@Slf4j
@RestController
@RequestMapping("/dashscope/audio-tts")
public class TTSController {
private static final String DEFAULT_TEXT = "你好!我是通义千问,阿里巴巴集团旗下的超大规模语言模型。";
private final SpeechSynthesisModel speechSynthesisModel;
/**
* 使用如下的方式自动注入 SpeechSynthesisModel
*
* @param speechSynthesisModel
*/
public TTSController(SpeechSynthesisModel speechSynthesisModel) {
this.speechSynthesisModel = speechSynthesisModel;
}
/**
* 简单调用
*/
@GetMapping("/simple/tts")
public String simpleTTS(String userInputText) throws IOException {
if (StringUtils.isBlank(userInputText)) {
userInputText = DEFAULT_TEXT;
}
SpeechSynthesisResponse speechSynthesisResponse = speechSynthesisModel.call(
new SpeechSynthesisPrompt(userInputText)
);
ByteBuffer byteBuffer = speechSynthesisResponse.getResult().getOutput().getAudio();
byte[] bytes = byteBuffer.array();
// 保存到文件
String outputFilePath = "D:\\TempFiles\\audio\\simple_tts_output.mp3";
File file = new File(outputFilePath);
try (FileOutputStream fos = new FileOutputStream(file)) {
fos.write(bytes);
} catch (IOException e) {
log.error("调用 simpleTTS 保存到文件异常 --> userInputPrompt ={}, e", userInputText, e);
throw new IOException(e.getMessage());
}
return "SUCCESS";
}
/**
* 流式调用
*/
@GetMapping("/stream/tts")
public String streamTTS(HttpServletResponse response, String userInputText) {
if (StringUtils.isBlank(userInputText)) {
userInputText = DEFAULT_TEXT;
}
// 指定参数
DashScopeSpeechSynthesisOptions speechSynthesisOptions = DashScopeSpeechSynthesisOptions.builder()
.withModel("cosyvoice-v1")
.withResponseFormat(DashScopeSpeechSynthesisApi.ResponseFormat.MP3)
.withVoice("longhua")
//.withSpeed(1.0) // 语速,取值范围:0.5~2.0,默认值为 1.0
.build();
Flux<SpeechSynthesisResponse> speechSynthesisResponseFlux = speechSynthesisModel.stream(
new SpeechSynthesisPrompt(userInputText, speechSynthesisOptions)
);
/**
* 为什么使用 CountDownLatch?
* 异步处理:Flux 是响应式编程的一部分,处理数据是异步的。这意味着数据的处理不会阻塞主线程。
* 确保完成:CountDownLatch 用于等待所有异步操作完成。它允许主线程等待,直到所有数据处理完成后再继续执行
*/
// 创建一个 CountDownLatch,初始计数为 1。
CountDownLatch latch = new CountDownLatch(1);
String outputFilePath = "D:\\TempFiles\\audio\\stream_tts_output.mp3";
File file = new File(outputFilePath);
try (FileOutputStream fos = new FileOutputStream(file)) {
// 订阅 Flux<SpeechSynthesisResponse>
speechSynthesisResponseFlux
.doFinally( // 在 Flux 所有数据处理完成后调用 countDown()
signal -> latch.countDown()
)
.subscribe(synthesisResponse -> {
// 处理每个 SpeechSynthesisResponse
ByteBuffer byteBuffer = synthesisResponse.getResult().getOutput().getAudio();
byte[] bytes = new byte[byteBuffer.remaining()];
byteBuffer.get(bytes);
try {
fos.write(bytes);
} catch (IOException e) {
log.error("调用 streamTTS 将字节数据写入文件异常 --> e", e);
throw new RuntimeException(e);
}
}, error -> { // 处理错误
log.error(" streamTTS 调用异常 --> error", error);
latch.countDown(); // 确保在发生错误时也减少计数器
});
//
// 主线程在这里等待所有异步操作完成,直到 CountDownLatch 的计数变为 0,即所有 SpeechSynthesisResponse 处理完成。
latch.await();
} catch (IOException | InterruptedException e) {
log.error("调用 streamTTS 保存到文件异常 --> userInputText ={}, e", userInputText, e);
throw new RuntimeException(e);
}
return "SUCCESS";
}
}
2、录音文件生成文字
在普通 Controller Bean 中注入 SpeechSynthesisModel 实例,实现“录音文件生成文字”功能:
- 简单|流式调用
- 自定义 LLMs 参数调用
注意:
不同模型对音频格式,采样率,实时|离线处理,时长控制,语言等音频文件的处理不同,针对音频文件选择正确的处理模型,否则会因为不符合模型的要求(如 WAV 格式传递给 PCM 模型)导致报错。
下面语音模型区别
- sensevoice-v1:
- 这是一个较早期的语音识别模型。
- 可能适用于特定场景或语言,但功能和性能可能不如后续版本强大。
- 对于一些复杂的音频文件(如高噪声、低质量录音),可能会出现识别错误。
- paraformer-realtime-v2:
- 这是一个实时语音转文字模型,支持流式处理。
- 适用于需要实时返回结果的场景(如直播字幕、实时会议记录)。
- 性能优化较好,能够快速响应并生成结果。
- paraformer-v2:
- 这是一个非实时的语音转文字模型,通常用于离线处理。
- 适合对较长音频文件进行高精度转写。
- 相比实时模型,可能需要更多的时间来处理音频。
编写 Controller接口:
@Slf4j
@RestController
@RequestMapping("/dashscope/audio-stt")
public class STTController {
private static final String DEFAULT_MODEL_1 = "sensevoice-v1";
private static final String DEFAULT_MODEL_2 = "paraformer-realtime-v2";
private static final String DEFAULT_MODEL_3 = "paraformer-v2";
private static final String AUDIO_RESOURCES_URL = "https://dashscope.oss-cn-beijing.aliyuncs.com/samples/audio/paraformer/hello_world_female2.wav";
private final AudioTranscriptionModel transcriptionModel;
/**
* 使用如下的方式自动注入 AudioTranscriptionModel
*
* @param transcriptionModel
*/
public STTController(AudioTranscriptionModel transcriptionModel) {
this.transcriptionModel = transcriptionModel;
}
/**
* 简单调用
*/
@GetMapping("/simple/stt")
public String stt() throws MalformedURLException {
// 音频资源
UrlResource audioResource = new UrlResource(AUDIO_RESOURCES_URL);
// 指定参数
DashScopeAudioTranscriptionOptions transcriptionOptions = DashScopeAudioTranscriptionOptions.builder()
.withModel(DEFAULT_MODEL_1)
.build();
AudioTranscriptionResponse response = transcriptionModel.call(
new AudioTranscriptionPrompt(audioResource, transcriptionOptions)
);
// <|Speech|>hello world,这里是阿里巴巴语音实验室。<|/Speech|><|NEUTRAL|>
return response.getResult().getOutput();
}
/**
* 流式调用
*
* @return
*/
@GetMapping("/stream/stt")
public String streamSTT() throws FileNotFoundException, MalformedURLException {
CountDownLatch latch = new CountDownLatch(1);
StringBuilder stringBuilder = new StringBuilder();
// 音频资源
String filePath = "D:\\TempFiles\\audio\\count.pcm";
FileSystemResource audioResource = new FileSystemResource(new File(filePath));
// 指定参数
DashScopeAudioTranscriptionOptions transcriptionOptions = DashScopeAudioTranscriptionOptions.builder()
.withModel(DEFAULT_MODEL_2)
.withFormat(DashScopeAudioTranscriptionOptions.AudioFormat.PCM)
// 设置音频采样率。16kHz/s是常见的语音处理采样率。
.withSampleRate(16000)
// 不启用话语不流畅移除功能。不流畅内容通常指说话中的停顿、重复等现象。
.withDisfluencyRemovalEnabled(false)
.build();
Flux<AudioTranscriptionResponse> response = transcriptionModel.stream(
new AudioTranscriptionPrompt(audioResource, transcriptionOptions)
);
// 订阅 Flux<AudioTranscriptionResponse>
response.doFinally(
signal -> latch.countDown()
).subscribe(
resp -> stringBuilder.append(resp.getResult().getOutput())
);
try {
latch.await();
} catch (InterruptedException e) {
log.error("调用 streamSTT 异常 --> e", e);
}
return stringBuilder.toString();
}
}
简单调用成功,流式调用失败。
– 求知若饥,虚心若愚。