Java实现录音(wav)

最近有个需求,需要接入语音识别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

# java  wav 

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×