JAVA程序员自救之路——开源工具VOSK助建私有化ASR

内容分享3周前发布
0 3 0

目前许多数字化项目都在向智能化转型,许多企业都想在AI方面方向上靠拢。但是各大云厂商的AI api成本下不来,尤其是一些项目的特殊环境必须私有化,导致工作开展不下去。所以我们需要一些开源的工具将成本打下去。本篇文章主要是介绍语音识别ASR的一个强劲的开源利器-VOSK。

Vosk是言语识别工具包。Vosk的优势有:

  1. 支持二十+种语言 – 中文,英语,印度英语,德语,法语,西班牙语,葡萄牙语,俄语,土耳其语,越南语,意大利语,荷兰人,加泰罗尼亚语,阿拉伯, 希腊语, 波斯语, 菲律宾语,乌克兰语, 哈萨克语, 瑞典语, 日语, 世界语, 印地语, 捷克语, 波兰语, 乌兹别克语, 韩国语, 塔吉克语
  2. 移动设备上脱机工作-Raspberry Pi,Android,iOS
  3. 使用简单的 pip3 install vosk 安装
  4. 每种语言的手提式模型只有是50Mb, 但还有更大的服务器模型可用
  5. 提供流媒体API,以提供最佳用户体验(与流行的语音识别python包不同)
  6. 还有用于不同编程语言的包装器-java / csharp / javascript等
  7. 可以快速重新配置词汇以实现最佳准确性
  8. 支持说话人识别

亲测了一下,适用于移动设备的小模型识别速度超级快,识别率稍微低点,但是也能用。而标准模型的识别准确率还是比较高的,能识别绝大部分文字。

VOSK由C++编写的,但是提供了多种语言的api,其中包括Java。下面我们看一下java怎么来接入这个工具:

一 引入pom

        <!-- 语音识别 -->
        <dependency>
            <groupId>net.java.dev.jna</groupId>
            <artifactId>jna</artifactId>
            <version>5.13.0</version>
        </dependency>
        <dependency>
            <groupId>com.alphacephei</groupId>
            <artifactId>vosk</artifactId>
            <version>0.3.45</version>
        </dependency>
        <!-- JAVE2(Java音频视频编码器)库是ffmpeg项目上的Java包装器。 -->
        <dependency>
            <groupId>ws.schild</groupId>
            <artifactId>jave-core</artifactId>
            <version>3.1.1</version>
        </dependency>

这里注意我们用到了一个jave,这个是一个音频的解码工具,我们用来做识别前的前置音频处理列如格式转换,切分。底层是调用ffmpeg。所以在使用他之前,我们需要安装ffmpeg。

Download FFmpeg

JAVA程序员自救之路——开源工具VOSK助建私有化ASR

需要注意的是,windows下我们要配置环境变量。

然后,我们就可以写代码了。核心代码如下:

1 加载模型,我们在VOSK的官网上把模型down下来。VOSK Models

JAVA程序员自救之路——开源工具VOSK助建私有化ASR

这里我们选择标准的中文模型:vosk-model-cn-0.22。

    private VoskModel() {
        String modelStr = "D:\workspace\study\study-backend\vosk-model-cn-0.22";
        try {
            voskModel = new Model(modelStr);
            LibVosk.setLogLevel(LogLevel.INFO);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

接着,我们将拿到的音频进行一下切分转码等处理。当然,如果你的音频不长,不切分也是可以的。

        MultimediaObject multimediaObject = new MultimediaObject(new File(wavFilePath),processLocator);
        MultimediaInfo info = multimediaObject.getInfo();
        // 时长/毫秒
        long duration = info.getDuration();
        AudioInfo audio = info.getAudio();
        // 通道数
        int channels = audio.getChannels();
        // 秒
        long offset = 0;
        long forNum = (duration / 1000) / cutDuration;
        if (duration % (cutDuration * 1000) > 0) {
            forNum = forNum + 1;
        }
        UUID uuid = UUID.randomUUID();
        // 大文件切割为固定时长的小文件
        List<String> strings = new ArrayList<>();
        for (int i = 0; i < forNum; i++) {
            String target = "D:\vosktemp\" + uuid + "\" + i + ".wav";
            Float offsetF = Float.valueOf(String.valueOf(offset));
            Float cutDurationF = Float.valueOf(String.valueOf(cutDuration));
            Jave2Util.cut(wavFilePath, target, offsetF, cutDurationF);
            offset = offset + cutDuration;
            strings.add(target);
        }

然后我们将切分的音频,逐一识别:

        StringBuilder result = new StringBuilder();
        Model voskModel = VoskModel.getInstance().getVoskModel();
        // 采样率为音频采样率的声道倍数
        log.info("====加载完成,开始分析====");
        try (
                Recognizer recognizer = new Recognizer(voskModel, 16000 * channels);
                InputStream ais = new FileInputStream(f)
        ) {
            int nbytes;
            byte[] b = new byte[4096];
            while ((nbytes = ais.read(b)) >= 0) {
                if (recognizer.acceptWaveForm(b, nbytes)) {
                    // 返回语音识别结果
                    result.append(getResult(recognizer.getResult()));
                    log.info("识别结果:{}", recognizer.getResult());
                } else {
                    // 返回语音识别结果
                    // log.info("识别结果:{}", recognizer.getPartialResult());
                }
            }
            // 返回语音识别结果。你一般在流的最后调用它来获得音频的最后部分。它刷新功能管道,以便处理所有剩余的音频块。
            result.append(getResult(recognizer.getFinalResult()));
            log.info("识别结果:{}", result.toString());
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result.toString();

最后将所有识别内容汇总,就得到了最终的识别结果。

效果是这样的:

JAVA程序员自救之路——开源工具VOSK助建私有化ASR

基本上是可以的。但是这个还需要进一步的处理,由于VOSK识别出来的文字是分词输出的,而且没有标点。所以,我们需要进一步的处理。

  1. 去空格
  2. 将文字给大模型,写一个prompt
        ChatResponse chatResponse =chatClient.prompt().system("你是一个语言专家,请将以下对话内容添加标点符号。并且分析说话人。按说话人:内容。输出一个json列表。")
                .user(question)
                .call().chatResponse();
        String response =  Optional.ofNullable(chatResponse)
                .map(ChatResponse::getResult)
                .map(Generation::getOutput)
                .map(AbstractMessage::getText)
                .orElse("");
        return response;

生成的结果就基本符合我们的要求了。结果如下:

JAVA程序员自救之路——开源工具VOSK助建私有化ASR

© 版权声明

相关文章

3 条评论

您必须登录才能参与评论!
立即登录