Java离线版语音识别-语音转文字

  • 1.项目前言
  • 2.Vosk介绍
  • 3.项目开发
    • 3.1 项目准备
    • 3.2 model 准备
    • 3.3 测试音频准备
    • 3.4 代码实现
  • 4.效果演示
    • 4.1 界面效果
    • 4.2 单个文件语音识别
      • 4.2.1 轻量模型
      • 4.2.2 通用模型
      • 4.2.3 两者对比
    • 4.3 多个语音文件识别
      • 4.3.1 轻量模型
      • 4.3.2 通用模型
      • 4.3.3 两者对比
  • 5.项目总结

系统:Win10
Java:1.8.0_333
IDEA:2020.3.4
Gitee:https://gitee.com/lijinjiang01/SpeechRecognition

1.项目前言

最近在做一个鬼畜视频的时候,需要处理大量语音文件,全部都是 wav 格式的,然后我想把这些语音转成文字,不过这些语音有几千条,这时候我就想能不能用 Java 实现。
不过现在主流的语音识别像百度。讯飞好像都不支持 Java 离线版,在查找一些资料后,我准备使用 Vosk

2.Vosk介绍

Vosk 官网:https://alphacephei.com/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 的原因,我想大概因为他们是 Apache-2.0 开源项目吧,而且他们还提供了中文模型,这省了很多事不是么

3.项目开发

3.1 项目准备

这里的项目准备只做一个 wav 语音识别,能够供自己使用就行了
首先,我们需要新建一个 Maven Java 项目,然后导入相关的依赖

<!-- 获取音频信息 -->
<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 相关依赖,我还导入了 jaudiotagger 这个获取音频信息的依赖,因为等会我们需要自动获取音频的采样率(SampleRate),有兴趣的小伙伴可以看一下我另一篇文章:Java获取Wav文件的采样率SampleRate

那么为什么我需要获取音频的采样率呢?这里我们看下 Vosk 官方给的示例代码:
https://github.com/alphacep/vosk-api/blob/master/java/demo/src/main/java/org/vosk/demo/DecoderDemo.java

package org.vosk.demo;import java.io.FileInputStream;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.UnsupportedAudioFileException;import org.vosk.LogLevel;
import org.vosk.Recognizer;
import org.vosk.LibVosk;
import org.vosk.Model;public class DecoderDemo {public static void main(String[] argv) throws IOException, UnsupportedAudioFileException {LibVosk.setLogLevel(LogLevel.DEBUG);try (Model model = new Model("model");InputStream ais = AudioSystem.getAudioInputStream(new BufferedInputStream(new FileInputStream("../../python/example/test.wav")));Recognizer recognizer = new Recognizer(model, 16000)) {int nbytes;byte[] b = new byte[4096];while ((nbytes = ais.read(b)) >= 0) {if (recognizer.acceptWaveForm(b, nbytes)) {System.out.println(recognizer.getResult());} else {System.out.println(recognizer.getPartialResult());}}System.out.println(recognizer.getFinalResult());}}
}

这个示例代码里有两个重要点:

  1. model:也就是 new Model(“model”) 这里,这里需要我们指定模型位置
  2. sampleRate:也就是 new Recognizer(model, 16000) 这里,他这里的示例代码写死了 sampleRate 为 16000 Hz,不过每个音频的采样率不可能都一样,我需要识别的音频采样率基本都是 44100 Hz,所以这里我们需要将他改为自动识别

3.2 model 准备

我们需要实现离线语音识别,那么就得将模型下载到本地电脑。下载地址为官网的 Models 模块:https://alphacephei.com/vosk/models
我们直接找到 Chinese 分类,这里有 2 个模型,上面较小的 40 多M的是轻量级模型,适用于手机等移动设备;下面 1 个多G的适用于服务器的,很明显模型越大识别语音正确率越高

这里我们两个都下载,等会对比下正确率和速率,下载下来是两个压缩包,直接解压到 D 盘,等会选择路径方便(怎么方便怎么来)。

解压之后如下

3.3 测试音频准备

音频下载地址:https://download.csdn.net/download/qq_35132089/86723883
测试音频已经上传到 CSDN 的资源库,设置下载积分为0,有兴趣的小伙伴可以下载测试玩玩
这里一共准备了 8 段音频,共 62 个字

01.wav: 保家卫国
02.wav: 这个世界需要希望
03.wav: 我们的勇气绝对不能动摇
04.wav: 德玛西亚
05.wav: 正义要靠法律要么靠武力
06.wav: 为了那些不能作战的人而战
07.wav: 勇往直前
08.wav: 生命不息战斗不止

3.4 代码实现

捋清楚思路,接下来实现就比较简单了,我这里写一个 Swing 的项目,准备到时候选择 wav 文件直接语音识别,或者选择一个文件夹,解析该目录下所有的 wav 音频文件
关键代码:

import com.lijinjiang.beautyeye.ch3_button.BEButtonUI;
import org.jaudiotagger.audio.AudioFile;
import org.jaudiotagger.audio.wav.WavFileReader;
import org.vosk.Model;
import org.vosk.Recognizer;
import javax.sound.sampled.AudioSystem;
import javax.swing.*;
import javax.swing.filechooser.FileNameExtensionFilter;
import javax.swing.filechooser.FileSystemView;
import java.awt.*;
import java.io.*;public class MainFrame {private JFrame mainFrame; // 主界面private final JPanel contentPanel = new JPanel(null); // 内容面板private String modelPath; // 模型位置private File chooseFile; // 选择的文件夹或文件private JTextField pathField; // 模型位置文本框private JTextField fileField; // 文件路径文本框private JTextArea displayArea; // 展示区域private JLabel timeLabel; // 显示耗时标签public MainFrame() {modelPath = System.getProperty("user.dir") + "/src/main/resources/vosk-model-small-cn-0.22"; // 初始化模型System.out.println(modelPath);createFrame();}/*** 创建主窗口*/private void createFrame() {mainFrame = new JFrame();mainFrame.setTitle("语音识别");createOperatePanel();createDisplayPane();createTimeLabel();mainFrame.add(contentPanel);mainFrame.setSize(new Dimension(800, 600));mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);mainFrame.setLocationRelativeTo(null);mainFrame.setVisible(true);}/*** 创建操作面板*/private void createOperatePanel() {JButton pathBtn = new JButton("选择模型");pathBtn.setLocation(10, 10);pathBtn.setSize(new Dimension(80, 36));pathBtn.setFocusable(false); // 不绘制焦点pathBtn.addActionListener(e -> showChoosePathDialog());pathField = new JTextField();pathField.setEditable(false);pathField.setLocation(100, 10);pathField.setSize(new Dimension(250, 36));JButton fileBtn = new JButton("选择文件");fileBtn.setFocusable(false); // 不绘制焦点fileBtn.addActionListener(e -> showChooseFileDialog());fileBtn.setLocation(360, 10);fileBtn.setSize(new Dimension(80, 36));fileField = new JTextField();fileField.setEditable(false);fileField.setLocation(450, 10);fileField.setSize(new Dimension(250, 36));// 开始执行按钮JButton startBtn = new JButton("执行");startBtn.addActionListener(e -> execute());startBtn.setUI(new BEButtonUI().setNormalColor(BEButtonUI.NormalColor.green));startBtn.setFocusable(false); // 不绘制焦点startBtn.setLocation(710, 10);startBtn.setSize(new Dimension(70, 36));contentPanel.add(pathBtn);contentPanel.add(pathField);contentPanel.add(fileBtn);contentPanel.add(fileField);contentPanel.add(startBtn);}/*** 创建展示面板*/private void createDisplayPane() {JScrollPane scrollPane = new JScrollPane();displayArea = new JTextArea();scrollPane.setViewportView(displayArea);displayArea.setEditable(false);displayArea.setBorder(null);scrollPane.setSize(new Dimension(775, 480));scrollPane.setLocation(8, 56);contentPanel.add(scrollPane);}private void createTimeLabel() {timeLabel = new JLabel();timeLabel.setHorizontalAlignment(SwingConstants.RIGHT); // 文本靠右对齐timeLabel.setSize(new Dimension(100, 36));timeLabel.setLocation(680, 530);contentPanel.add(timeLabel);}/*** 选择模型位置*/private void showChoosePathDialog() {JFileChooser fileChooser = new JFileChooser(); // 初始化一个文件选择器String pathValue = pathField.getText().trim();if (pathValue.length() == 0) {FileSystemView fsv = fileChooser.getFileSystemView(); // 获取文件系统网关fileChooser.setCurrentDirectory(fsv.getHomeDirectory()); // 设置桌面为当前文件路径} else {// 设置上一次选择路径为当前文件路径File file = new File(pathValue);File parentFile = file.getParentFile();if (parentFile == null) {fileChooser.setCurrentDirectory(file);} else {fileChooser.setCurrentDirectory(parentFile);}}fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); // 可选文件夹和文件fileChooser.setMultiSelectionEnabled(false); // 设置可多选int result = fileChooser.showOpenDialog(mainFrame);if (result == JFileChooser.APPROVE_OPTION) {File file = fileChooser.getSelectedFile();modelPath = file.getAbsolutePath();pathField.setText(modelPath); // 将选择的文件路径写入到文本框}}/*** 选择需要转换成文字的文件夹或者文件* 文件夹:表示该目录下一层所有 wav 都需要转成文字* 文件:表示只需要将该文件转换成文字即可*/private void showChooseFileDialog() {JFileChooser fileChooser = new JFileChooser(); // 初始化一个文件选择器String fileValue = fileField.getText().trim();if (fileValue.length() == 0) {FileSystemView fsv = fileChooser.getFileSystemView();fileChooser.setCurrentDirectory(fsv.getHomeDirectory()); // 设置桌面为当前文件路径} else {// 设置上一次选择路径为当前文件路径File file = new File(fileValue);File parentFile = file.getParentFile();if (parentFile == null) {fileChooser.setCurrentDirectory(file);} else {fileChooser.setCurrentDirectory(parentFile);}}fileChooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES); // 可选文件夹和文件fileChooser.setMultiSelectionEnabled(false); // 设置可多选fileChooser.removeChoosableFileFilter(fileChooser.getAcceptAllFileFilter()); // 不显示所有文件的下拉选fileChooser.addChoosableFileFilter(new FileNameExtensionFilter("wav", "wav"));int result = fileChooser.showOpenDialog(mainFrame);if (result == JFileChooser.APPROVE_OPTION) {chooseFile = fileChooser.getSelectedFile();fileField.setText(chooseFile.getAbsolutePath()); // 将选择的文件路径写入到文本框}}/*** 开始执行操作*/private void execute() {displayArea.setText(""); // 执行后清空显示面板if (modelPath == null || 0 == modelPath.length()) {JOptionPane.showMessageDialog(mainFrame, "模型位置不能为空", "错误", JOptionPane.ERROR_MESSAGE);return;}if (chooseFile == null) {JOptionPane.showMessageDialog(mainFrame, "未选择文件夹或者音频文件", "错误", JOptionPane.ERROR_MESSAGE);return;}long startTime = System.currentTimeMillis();// 用于测试进度条的线程Thread thread = new Thread() {public void run() {if (chooseFile.isDirectory()) { // 如果是文件夹,则遍历里面每个文件File[] files = chooseFile.listFiles(pathname -> pathname.getName().endsWith(".wav"));if (files != null) {for (File childFile : files) processFile(childFile);}} else {processFile(chooseFile);}}};//显示进度条测试对话框ProgressBar.show((Frame) null, thread, "语音正在识别中,请稍后...", "执行结束", "取消");// 否则直接处理该文件long endTime = System.currentTimeMillis();String msg = "耗时:" + (endTime - startTime) + "ms";timeLabel.setText(msg);}/*** 处理文件:语音转文字*/private void processFile(File file) {try (Model model = new Model(modelPath);//该段是模型地址InputStream ais = AudioSystem.getAudioInputStream(new BufferedInputStream(new FileInputStream(file))); //该段是要转的语言文件,仅支持wavRecognizer recognizer = new Recognizer(model, getSampleRate(file))) { //该段中12000是语言频率(Hz),需要大于8000,可以自行调整int bytes;byte[] b = new byte[4096];while ((bytes = ais.read(b)) >= 0) {recognizer.acceptWaveForm(b, bytes);}displayArea.append(file.getName() + " ");displayArea.append(recognizer.getFinalResult() + System.lineSeparator());} catch (Exception e) {JOptionPane.showMessageDialog(mainFrame, e.getMessage(), "错误", JOptionPane.ERROR_MESSAGE);e.printStackTrace();}}/*** 获取音频文件的采样率*/private Float getSampleRate(File file) throws Exception {WavFileReader fileReader = new WavFileReader();AudioFile audioFile = fileReader.read(file);String sampleRate = audioFile.getAudioHeader().getSampleRate();return Float.parseFloat(sampleRate);}
}

4.效果演示

4.1 界面效果

界面效果就是如下图所示

  1. 点击选择模型,就可以指定模型文件夹路径
  2. 点击选择文件,就可以指定需要识别的语音或文件夹
  3. 最后点击执行即可开始语音识别
  4. 识别成功,会将对应音频文件名和识别的文字写在下面的文本域中
  5. 最后还会将使用时间显示在界面右下角

4.2 单个文件语音识别

4.2.1 轻量模型

这里选择模型选择轻量模型,文件只识别第一个文件

4.2.2 通用模型

这里模型替换为了通用模型,语音文件不变,然后执行

4.2.3 两者对比

单个文件语音识别 轻量模型 通用模型
正确率 100% 100%
消耗时间 2021 ms 21093 ms

这里我们可以发现同一个文件,轻量模型只用了 1/10 的时间就识别完成,且成功率均为 100%,不过这也存在这段语言比较简单的原因

4.3 多个语音文件识别

4.3.1 轻量模型

这里的模型还是选择轻量模型,语音文件选择整个语音文件夹

4.3.2 通用模型

这里模型替换为了通用模型,语音文件不变,然后执行;因为这里耗费时间太长了,GIF 做了抽帧处理

4.3.3 两者对比

多个文件语音识别 轻量模型 通用模型
正确率 71.43% 84.12%
消耗时间 14332 ms 176040 ms

这里我们可以发现同一个文件,轻量模型只用了 8% 的时间就识别完成,不过这里的正确率只有 71% 左右,通用模型的正确率却有 84% 左右,当然因为其中存在一些专有名词如德玛西亚,两次都没有识别出来

5.项目总结

综合比较下来我们基本可以得出结论:如果对正确率要求没那么高的情况下,轻量模型完全符合我们的要求;而且通用模型的时间消耗确实太大了,可能需要一些方法来减少时间的消耗。

【项目管理】Java离线版语音识别-语音转文字相关推荐

  1. 纯java离线版语音转文字

    前言 当前主流的语音识别大厂有科大讯飞.百度.谷歌等,但在他们官网中发现,支持java离线版的并不多,科大讯飞离线包仅基于安卓,而百度官方并没有离线版的,所以在资源查找中筛选出VOSK.CMU Sph ...

  2. 语音识别|语音转文字识别|在线语音识别

    什么是语音识别? 语音识别就是通过录音形式转化成文字,现在的语音识别技术可支持的语言有:普通话.粤语.四川话和英语. 语音识别的分类都有哪些? 语音识别分为在线识别.离线命令词和唤醒词 在线识别:即联 ...

  3. uniapp使用百度语音识别语音转文字

    HBuilderX已支持讯飞语音识别和百度语音识别,这里讲下百度语音接入 一:首先是百度语音识别申请 这个步骤暂且省略,可以直接移步百度语音api页面自行申请,主要是为了获取语音权限以及AppID.A ...

  4. 开源(离线)中文语音识别ASR(语音转文本)工具整理

    开源(离线)中文语音识别ASR(语音转文本)工具整理 目录 文章目录 目录 @[toc] open ai 的开源工具:whisper whisper介绍 引用 ASRT语音识别项目 ASRT介绍 引用 ...

  5. 工具 转_好用的语音转文字工具,总有一款适合你!

    昨天在评论区有朋友问有没有好用的语音转文字工具,这当然有了,这种办公必备的需求这么大,怎么会没有相应的软件呢!今天就给大家分别推荐几款我觉得好用的,包括在线版.PC版和手机版的语音转文字工具,一起来看 ...

  6. 放置江湖html5游戏,放置江湖单机离线版

    放置江湖单机离线版是一款文字放置类的角色扮演手游,这里有着非常丰富的武学门派,每个门派都有着不一样的特点,武功自然也不同. 放置江湖单机离线版简介: 时光飞逝,日月如梭,江湖如戏,人生如歌.伴随着彼此 ...

  7. 【Buzz】离线语音转文字、实时语音识别

    Buzz是基于 OpenAI Whisper的离线语音转文字(字幕),实时语音识别工具. 功能 实时语音转文字.实时翻译(需麦克风权限) 导入音频.视频文件(mp3.wav.m4a.ogg.mp4.w ...

  8. 讯飞语音识别之语音转文字------java

    最近项目要用到语音识别的功能,网上找别人写的都不行,二话不说直接去讯飞官网注册下载了一个sdk,但是个人觉得sdk中并不是很明确的能运行实现功能,于是自己参考下载的sdk中的代码做了一个改动和封装.添 ...

  9. python离线录音转文字_python3实现语音转文字(语音识别)和文字转语音(语音合成)...

    话不多说,直接上代码运行截图 1.语音合成 -------> 执行: 结果: 输入要转换的内容,程序直接帮你把转换好的mp3文件输出(因为下一步–语音识别–需要.pcm格式的文件,程序自动执行格 ...

最新文章

  1. RIPng 原理和实践
  2. 能打开java文件_用java打开一个本地文件
  3. 使用ffmpeg实现转码样例(代码实现)
  4. python+pywinauto之PC端自动化一
  5. NO。58 新浪微博顶部新评论提示层效果——position:fixed
  6. 基于排队理论的云计算模型的MATLAB仿真
  7. 安装alien,DEB与RPM互换
  8. ubuntu要更新18.04了,lei了lei了~~~
  9. matlab函数冒号代表的意思,MATLAB中冒号运算符的含义
  10. MySql5.6版修改用户登录密码
  11. Asp.net使用JQuery实现评论的无刷新分页及分段延迟加载效果
  12. 阶段3 2.Spring_01.Spring框架简介_02.今日课程内容介绍
  13. java环境变量配置不成功,已经解决
  14. 21天学通C语言-学习笔记(7)
  15. hsqldb mysql_安装HSQLDB
  16. 那时候的漫画【怀旧贴】
  17. 美国人眼中的大数据法律问题
  18. Elasticsearch 7.2.0 搜索时报 all shards failed 错误
  19. 解决虚拟机不能连接外网问题
  20. 大流行时代的三大社会技术影响

热门文章

  1. 一份触目惊心的性犯罪方案
  2. 重积分 | 【拓展】格林公式、高斯公式、斯托克斯公式之间的联系
  3. Win + D 和 Win + M的区别
  4. TPS、QPS与并发
  5. atoi和strtol的用法
  6. 为什么智能家居总是被说没卵用?
  7. javadoc所有参数及解释
  8. 解决方案 NComputing L系列、X系列、U系列
  9. C++ STL 迭代器方法 之 advance与prev 方法 浅析
  10. 2019 CCPC秦皇岛 J 题 MUV LUV EXTRA【KMP 求最小循环节】