# SpeechRecognition
**Repository Path**: hcxxch/SpeechRecognition
## Basic Information
- **Project Name**: SpeechRecognition
- **Description**: Vosk语音识别项目
- **Primary Language**: Java
- **License**: Apache-2.0
- **Default Branch**: master
- **Homepage**: https://lijinjiang.blog.csdn.net/article/details/127069618
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 14
- **Created**: 2024-06-28
- **Last Updated**: 2024-06-28
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# SpeechRecognition



> System : Windows 10
>
> Intellij IDEA : Ultimate 2020.3.4
>
> Java : 1.8.0_333
>
> CSDN : [https://lijinjiang.blog.csdn.net/article/details/127069618](https://lijinjiang.blog.csdn.net/article/details/127069618)
## 1.项目介绍
该项目是最近在做一个鬼畜视频的时候,需要处理大量语音文件,全部都是 wav 格式的,然后我想把这些语音转成文字,不过这些语音有几千条,然后我就用 Java 做了这个语音识别的项目
## 2.Vosk介绍
Vosk 官网:[**https://alphacephei.com/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 代码实现
捋清楚思路,接下来实现就比较简单了,我这里写一个 Swing 的项目,准备到时候选择 wav 文件直接语音识别,或者选择一个文件夹,解析该目录下所有的 wav 音频文件
关键代码:
```java
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))); //该段是要转的语言文件,仅支持wav
Recognizer 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.项目总结
综合比较下来我们基本可以得出结论:如果对正确率要求没那么高的情况下,轻量模型完全符合我们的要求;而且通用模型的时间消耗确实太大了,可能需要一些方法来减少时间的消耗。