用java做一套离线且免费的智能语音系统,ASR+LLM+TTS

2,710 阅读4分钟

其实调用第三方接口完成一个智能语音系统是非常简单的,像阿里、科大讯飞、微软都有相关接口,直接根据官方文档集成就可以,但想要离线的就要麻烦一点了,主要是想不花钱,现在人工智能基本是python的天下,不得不感慨,再不学python感觉自己要被淘汰了。

言归正传,首先说一下标题中的ASR+LLM+TTS,ASR就是语音识别,LLM就是大语言模型,TTS就是文字转语音,要想把这几个功能做好对电脑性能要求还是蛮高的,本次方案是一个新的尝试也是减少对性能的消耗

1.先看效果

image.png 生成的音频效果放百度网盘 通过网盘分享的文件:result.wav 链接: pan.baidu.com/s/19ImtqunH… 提取码: hm67 听完之后是不是感觉效果很好,但是。。。后面再说吧

2.如何做

2.1ASR功能

添加依赖


 <!-- 获取音频信息 -->
  <dependency>
     <groupId>org</groupId>
     <artifactId>jaudiotagger</artifactId>
     <version>2.0.3</version>
  </dependency>

<!-- 语音识别 -->
<dependency>
    <groupId>net.java.dev.jna</groupId>
    <artifactId>jna</artifactId>
    <version>5.7.0</version>
</dependency>
<dependency>
    <groupId>com.alphacephei</groupId>
    <artifactId>vosk</artifactId>
    <version>0.3.32</version>
</dependency>


代码实现,需要提前下载模型,去vosk官网下载:VOSK Models (alphacephei.com)中文模型一个大的一个小的,小的识别速度快准确率低,大的识别速度慢准确率高

image.png
提前预加载模型,提升识别速度

private static final Model model = loadModel();

private static Model loadModel() {
  try {
      String path=System.getProperty("user.dir");
      return new Model(path+"\vosk-model-small-cn-0.22");
  } catch (Exception e) {
      throw new RuntimeException("Failed to load model", e);
  }
}

语音转文字方法实现

public  String voiceToText(String filePath) {
    File file = new File(filePath);

    LibVosk.setLogLevel(LogLevel.DEBUG);
    String msg = null;
    int sampleRate = 0;
    RandomAccessFile rdf = null;
    /**
     * "r": 以只读方式打开。调用结果对象的任何 write 方法都将导致抛出 IOException。
     * "rw": 打开以便读取和写入。
     * "rws": 打开以便读取和写入。相对于 "rw","rws" 还要求对“文件的内容”或“元数据”的每个更新都同步写入到基础存储设备。
     * "rwd" : 打开以便读取和写入,相对于 "rw","rwd" 还要求对“文件的内容”的每个更新都同步写入到基础存储设备。
     */
    try {
        rdf = new RandomAccessFile(file, "r");
        sampleRate=toInt(read(rdf));
        System.out.println(file.getName() + " SampleRate:" + sampleRate);  // 采样率、音频采样级别 8000 = 8KHz
        rdf.close();
    } catch (FileNotFoundException e) {
        throw new RuntimeException(e);
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
    try (
        InputStream ais = AudioSystem.getAudioInputStream(file);
        Recognizer recognizer = new Recognizer(model, 16000)) {
        int bytes;
        byte[] b = new byte[1024];
        while ((bytes = ais.read(b)) >= 0) {
            recognizer.acceptWaveForm(b, bytes);
        }
        String result=recognizer.getResult();
        JSONObject jsonObject = JSONObject.parseObject(result);
         msg=jsonObject.getString("text");
} catch (UnsupportedAudioFileException e) {
        throw new RuntimeException(e);
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
    return msg;
}

2.2LLM问答功能

这个就要请出神奇的羊驼ollama(链接:Ollama),下载即用非常简单,可以运行大部分主流大语言模型,在官网models选择要加载的模型在控制台运行对应的命令即可

image.png 添加依赖

<dependency>
    <groupId>io.springboot.ai</groupId>
    <artifactId>spring-ai-ollama-spring-boot-starter</artifactId>
    <version>1.0.3</version>
</dependency>

加入配置

spring:
  ai:
    ollama:
      base-url: http://10.3.0.178:11434 //接口地址 默认端口11434
      chat:
        options:
          model: qwen2 //模型名称
        enabled: true

代码实现,引入OllamaChatClient,然后调用call方法

@Resource
private OllamaChatClient ollamaChatClient;
//msg为提问的信息
String ask=ollamaChatClient.call(msg);

2.3TTS功能

添加依赖

<dependency>
    <groupId>com.hynnet</groupId>
    <artifactId>jacob</artifactId>
    <version>1.18</version>
</dependency>

代码实现

public boolean localTextToSpeech(String text, int volume, int speed,String outPath) {
    try {
        // 调用dll朗读方法
        ActiveXComponent ax = new ActiveXComponent("Sapi.SpVoice");
        // 音量 0 - 100
        ax.setProperty("Volume", new Variant(volume));
        // 语音朗读速度 -10 到 +10
        ax.setProperty("Rate", new Variant(speed));
        // 输入的语言内容
        Dispatch dispatch = ax.getObject();
        // 本地执行朗读
//            Dispatch.call(dispatch, "Speak", new Variant(text));

        //开始生成语音文件,构建文件流
        ax = new ActiveXComponent("Sapi.SpFileStream");
        Dispatch sfFileStream = ax.getObject();
        //设置文件生成格式
        ax = new ActiveXComponent("Sapi.SpAudioFormat");
        Dispatch fileFormat = ax.getObject();
        // 设置音频流格式
        Dispatch.put(fileFormat, "Type", new Variant(22));
        // 设置文件输出流格式
        Dispatch.putRef(sfFileStream, "Format", fileFormat);
        // 调用输出文件流打开方法,创建一个音频文件
        Dispatch.call(sfFileStream, "Open", new Variant(outPath), new Variant(3), new Variant(true));
        // 设置声音对应输出流为输出文件对象
        Dispatch.putRef(dispatch, "AudioOutputStream", sfFileStream);
        // 设置音量
        Dispatch.put(dispatch, "Volume", new Variant(volume));
        // 设置速度
        Dispatch.put(dispatch, "Rate", new Variant(speed));
        // 执行朗读
        Dispatch.call(dispatch, "Speak", new Variant(text));
        // 关闭输出文件
        Dispatch.call(sfFileStream, "Close");
        Dispatch.putRef(dispatch, "AudioOutputStream", null);

        // 关闭资源
        sfFileStream.safeRelease();
        fileFormat.safeRelease();
        // 关闭朗读的操作
        dispatch.safeRelease();
        ax.safeRelease();
        return true;
    } catch (Exception e) {
        e.printStackTrace();
        return false;
    }
}

到此为止功能就全部实现了,在不联网的情况下也可以免费使用,但这个TTS生成出来的语音太机器了,所以在上面的示例中我说但是,因为机器音实在太难受了,所以用了另一个方案,但这个方案需要联网,暂时看是不需要收费(后续使用发现是有接口调用次数限制) ,这个大家就看原作者介绍吧(ikfly/java-tts: java-tts 文本转语音 (github.com)

最终实现效果来看还是能接受的吧,整个过程速度还比较快,python的语音相关项目也看了很多,如果部署一个python的TTS服务,效果可能还会好点,但是我试过的速度都有点慢啊,还是容我再继续研究一下吧