最近有个需求,需要接入语音识别SDK,与某公司达成了购买意向,并在本地部署了服务器,因此需要进一步测试下准确率。对方给的SDK是java版,带有一个示例语音,通过模拟推流,调用其实时语音识别api进行识别。
为了进一步测试准确率,需要自己多录几段音频进行测试,参考网上的资料实现了录音并存储wav文件,下面直接上代码。
1.调用方式
运行代码开始录音,命令行输入s,回车结束录音并保存文件。
public static void main(String[] args) {
RecordWav recordWav = new RecordWav();
try {
recordWav.save("D:\\record.wav");
} catch (Exception e) {
e.printStackTrace();
}
}
2.RecordWav.java
package com.ilei.record.recorddemo;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Scanner;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.TargetDataLine;
/**
* 思路:采用java官方API——TargetDataLine,从声卡中采集音频数据达到录音效果,采集的数据为PCM裸流需要转为wav格式的话参照——PCM转WAV
* 。
*
* @author Administrator
*
*/
public class RecordWav {
boolean isStop = false;
private String wavPath = "";
private String pcmPath = "";
// 采样率
private static float RATE = 8000f;// 44100f;
// 编码格式PCM
private static AudioFormat.Encoding ENCODING = AudioFormat.Encoding.PCM_SIGNED;
// 帧大小 16
private static int SAMPLE_SIZE = 16;
// 是否大端
private static boolean BIG_ENDIAN = false;// true
// 通道数
private static int CHANNELS = 1;
public void save(String path) throws Exception {
if (path == null || path.isEmpty()) {
System.out.println("save path is null");
return;
}
this.wavPath = path;
this.pcmPath = path.replace(".wav", ".pcm");
// 创建指定文件
File file = new File(pcmPath);
if (file.isDirectory()) {
if (!file.exists()) {
file.mkdirs();
}
file.createNewFile();
}
// 设置格式
AudioFormat audioFormat = new AudioFormat(ENCODING, RATE, SAMPLE_SIZE, CHANNELS, (SAMPLE_SIZE / 8) * CHANNELS,
RATE, BIG_ENDIAN);
// 获取线路
TargetDataLine targetDataLine = AudioSystem.getTargetDataLine(audioFormat);
targetDataLine.open();
targetDataLine.start();
/**
* targetDataLine.read() 从数据线的输入缓冲区读取音频数据,该方法会阻塞,当数据先关闭之后就不会阻塞了
*/
Thread thread = new Thread() {
OutputStream os = new FileOutputStream(file);
byte[] b = new byte[256];
@Override
public void run() {
while ((targetDataLine.read(b, 0, b.length)) > 0) {// 从声卡中采集数据
try {
os.write(b);
} catch (IOException e) {
e.printStackTrace();
}
if (isStop) {
isStop = false;
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
RecordWav.convertAudioFiles(pcmPath, wavPath);
break;
}
}
}
};
thread.start();
//监听按键
Thread thread2=new Thread() {
@Override
public void run() {
Scanner in = new Scanner(System.in);
if(in.next().equals("s")) {
isStop = true;
}
in.close();
}
};
thread2.start();
}
/**
*
* @param pcm
* pcm指定pcm文件位置,wav指定输出的wav文件存放位置
* @throws Exception
*/
public static void convertAudioFiles(String pcm, String wav) {
//获取PCM文件大小
File file=new File(pcm);
int pcmSize =(int) file.length();
//定义wav文件头
//填入参数,比特率等等。这里用的是16位单声道 8000 hz
WaveHeader header = new WaveHeader(pcmSize);
//长度字段 = 内容的大小(PCMSize) + 头部字段的大小(不包括前面4字节的标识符RIFF以及fileLength本身的4字节)
header.fileLength = pcmSize + (44 - 8);
header.FmtHdrLeth = 16;
header.BitsPerSample = 16;
header.Channels = 1;
header.FormatTag = 0x0001;
header.SamplesPerSec = 8000;
header.BlockAlign = (short)(header.Channels * header.BitsPerSample / 8);
header.AvgBytesPerSec = header.BlockAlign * header.SamplesPerSec;
header.DataHdrLeth = pcmSize;
FileOutputStream fs = null;
FileInputStream fiss = null;
try {
//获取wav文件头字节数组
byte[] h = header.getHeader();
assert h.length == 44; //WAV标准,头部应该是44字节
//将文件头写入文件
fs = new FileOutputStream(wav);
fs.write(h);
//将pcm文件写到文件头后面
fiss = new FileInputStream(pcm);
byte[] bb = new byte[10];
int len = -1;
while((len = fiss.read(bb))>0) {
fs.write(bb, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fs != null) {
try {
fs.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fiss != null) {
try {
fiss.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
/**
* WavHeader辅助类。用于生成头部信息。
* @author Administrator
*
*/
class WaveHeader {
/**wav文件头:RIFF区块
* 名称 偏移地址 字节数 端序 内容
* ID 0x00 4Byte 大端 'RIFF' (0x52494646)
Size 0x04 4Byte 小端 fileSize - 8
Type 0x08 4Byte 大端 'WAVE'(0x57415645)
解析:
以'RIFF'为标识
Size是整个文件的长度减去ID和Size的长度
Type是WAVE表示后面需要两个子块:Format区块和Data区块
*/
/**
* FORMAT区块:
* 名称 偏移地址 字节数 端序 内容
ID 0x00 4Byte 大端 'fmt ' (0x666D7420)
Size 0x04 4Byte 小端 16
AudioFormat 0x08 2Byte 小端 音频格式
NumChannels 0x0A 2Byte 小端 声道数
SampleRate 0x0C 4Byte 小端 采样率
ByteRate 0x10 4Byte 小端 每秒数据字节数
BlockAlign 0x14 2Byte 小端 数据块对齐
BitsPerSample 0x16 2Byte 小端 采样位数
解析:
以'fmt '为标识
Size表示该区块数据的长度(不包含ID和Size的长度)
AudioFormat表示Data区块存储的音频数据的格式,PCM音频数据的值为1
NumChannels表示音频数据的声道数,1:单声道,2:双声道
SampleRate表示音频数据的采样率
ByteRate每秒数据字节数 = SampleRate * NumChannels * BitsPerSample / 8
BlockAlign每个采样所需的字节数 = NumChannels * BitsPerSample / 8
BitsPerSample每个采样存储的bit数,8:8bit,16:16bit,32:32bit
*/
/**
* DATA区块
*
* 名称 偏移地址 字节数 端序 内容
ID 0x00 4Byte 大端 'data' (0x64617461)
Size 0x04 4Byte 小端 N
Data 0x08 NByte 小端 音频数据
解析:
以'data'为标识
Size表示音频数据的长度,N = ByteRate * seconds
Data音频数据
*/
public final char fileID[] = {'R', 'I', 'F', 'F'};
public int fileLength;
public short FormatTag;
public short Channels;
public int SamplesPerSec;
public int AvgBytesPerSec;
public short BlockAlign;
public short BitsPerSample;
public char DataHdrID[] = {'d','a','t','a'};
public int DataHdrLeth;
public char wavTag[] = {'W', 'A', 'V', 'E'};;
public char FmtHdrID[] = {'f', 'm', 't', ' '};
public int FmtHdrLeth;
public WaveHeader() {}//无参构造方法
/**
*
* @param a
*/
public WaveHeader(int a) {
}
public byte[] getHeader() throws IOException {
//创建一个输出流,用于将各个字节数组写入缓存中,缓存区会自动增长。然后可以将整个输出流转换为完整的字节数组,关闭该流不会有任何效果。
ByteArrayOutputStream bos = new ByteArrayOutputStream();
writeChar(bos, fileID);
writeInt(bos, fileLength);
writeChar(bos, wavTag);
writeChar(bos, FmtHdrID);
writeInt(bos,FmtHdrLeth);
writeShort(bos,FormatTag);
writeShort(bos,Channels);
writeInt(bos,SamplesPerSec);
writeInt(bos,AvgBytesPerSec);
writeShort(bos,BlockAlign);
writeShort(bos,BitsPerSample);
writeChar(bos,DataHdrID);
writeInt(bos,DataHdrLeth);
bos.flush();
byte[] r = bos.toByteArray();
bos.close();
return r;
}
private void writeShort(ByteArrayOutputStream bos, int s) throws IOException {
byte[] mybyte = new byte[2];
mybyte[1] =(byte)( (s << 16) >> 24 );//存放高位
mybyte[0] =(byte)( (s << 24) >> 24 );//存放低位
bos.write(mybyte);
}
private void writeInt(ByteArrayOutputStream bos, int n) throws IOException {
byte[] buf = new byte[4];
buf[3] =(byte)( n >> 24 );
buf[2] =(byte)( (n << 8) >> 24 );
buf[1] =(byte)( (n << 16) >> 24 );
buf[0] =(byte)( (n << 24) >> 24 );
bos.write(buf);
}
private void writeChar(ByteArrayOutputStream bos, char[] id) {
for (int i=0; i<id.length; i++) {
char c = id[i];
bos.write(c);
}
}
}
参考:https://blog.csdn.net/weixin_43710268/article/details/106976584