当前位置: 首页 > news >正文

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();
    }

}

简单调用成功,流式调用失败。

在这里插入图片描述

– 求知若饥,虚心若愚。

相关文章:

  • Linux的进程信号 -- 信号产生,信号保存,信号捕捉,硬件中断,内核态和用户态,可重入函数,volatile,SIGCHLD
  • 反序列化漏洞
  • STM32实现智能温控系统(暖手宝):PID 算法 + DS18B20+OLED 显示,[学习 PID 优质项目]
  • 卷积神经网络 - AlexNet各层详解
  • 蓝桥杯第十届 数列求值
  • c++进阶--哈希表的实现
  • element-plus中el-empty空盒子组件和Collapse 折叠面板组件的使用
  • 第十七章:Future Directions_《C++ Templates》notes
  • java 线程创建Executors 和 ThreadPoolExecutor 和 CompletableFuture 三者 区别
  • 数据库查询练习
  • ASP.NET Web API + VUE3 整合阿里云OSS,后端API生成预签名上传Url,前端VUE进行上传
  • 蓝桥杯第 十一天 国赛 2020 第 2题 扩散
  • CVE-2021-45232未授权接口练习笔记
  • conda环境下解决gitk乱码模糊
  • Postman使用02、断点、fiddler弱网测试
  • Java 基于微信小程序的开放实验室预约管理系统
  • 从单机到集群:Elasticsearch集群搭建指南
  • HTML5 初探:新特性与本地存储的魔法
  • IP-guard与Ping32哪个加密更强?两款加密软件的安全架构解析
  • 专访中兴通讯蒋军:AI数字人驱动企业培训,“内容生产”与“用户体验”双重提升
  • 网站首页seo怎么做/网络营销公司哪家可靠
  • 建站之星至尊版/2022最新热点事件及点评
  • 网站建设群/app拉新推广代理
  • 学平面设计网站/seo工具不包括
  • 福清市住房和城乡建设局网站/小程序推广方案
  • 学前教育网站建设/广州seo推广服务