在Java中处理音频数据并保存到本地文件是一项常见的需求。你可以使用Java标准库中的javax.sound.sampled
包来完成这项任务。下面是一个详细的指南,介绍如何录制音频并将其保存为WAV文件。
1. 准备工作
首先,确保你的开发环境中已经包含了Java标准库。javax.sound.sampled
是Java自带的,因此你不需要额外安装其他库。
2. WAV音频文件格式
WAV(Waveform Audio File Format)音频文件格式,是一种无压缩的音频文件格式,它使用PCM(Pulse Code Modulation)编码,将模拟音频信号转换为数字音频信号。它是Windows操作系统中默认的音频文件格式,通常用于存储高质量的音频数据,比如音乐、录音等。
WAV文件的格式:
WAV文件包含了音频数据的采样率、比特率、声道数等信息,并且可以包含额外的标签信息。
WAV文件的特点是无损压缩,即不会失去任何音频质量。它可以支持多种音频编码方式,比如PCM编码和ADPCM编码等。由于WAV文件不进行任何压缩,所以它的文件大小相对较大,这也是WAV文件的一个缺点。除了音频数据外,WAV文件还可以包含元数据,比如歌曲的歌手、专辑名称、发行日期等信息。这些元数据可以帮助用户更好地管理和查找自己的音乐文件。
pcm和wav的区别:
- PCM和WAV都是数字音频的编码格式,但是它们之间有一些区别:
- PCM是一种音频信号的编码方式,它将模拟音频信号转换成数字音频信号。而WAV是一种容器格式,它可以将不同的编码格式的音频数据储存起来,比如PCM、MP3等。
- PCM是无损的编码格式,它将原始音频信号转换为数值型数据储存。而WAV可以支持无损和有损的编码格式。
- PCM文件只储存音频数据,没有元数据。而WAV文件可以包含音频数据、元数据、文本等。
- WAV是微软公司开发的一种标准文件格式,所以WAV文件通常在Windows系统上使用较为广泛。而PCM是编码方式,不属于某一种文件格式。
- WAV文件可以包含多种采样率和位深度的音频数据,而PCM只能够描述一种采样率和位深度的音频数据。
3. 录制音频并保存为WAV文件
在Java中,我们可以使用javax.sound.sampled包提供的API来录制音频。以下是一个完整的示例,演示了如何录制音频并将其保存为WAV文件。
3.1代码示例
import javax.sound.sampled.*;
import java.io.File;
import java.io.IOException;
public class AudioRecorder {
// 设置录音的格式
private static final AudioFormat FORMAT = new AudioFormat(
AudioFormat.Encoding.PCM_SIGNED, // 编码格式
16000, // 采样率
16, // 量化位数
1, // 声道数
2, // 每个样本的字节数
16000, // 每秒的采样数
true // 是否是大端序
);
public static void main(String[] args) {
File audioFile = new File("recorded.wav");
try {
// 创建并启动录音线程
AudioInputStream audioInputStream = getAudioInput();
// 将录音数据写入文件
AudioSystem.write(audioInputStream, AudioFileFormat.Type.WAVE, audioFile);
System.out.println("录音完成,文件保存为: " + audioFile.getAbsolutePath());
} catch (IOException | LineUnavailableException e) {
e.printStackTrace();
}
}
// 录制音频的方法
private static AudioInputStream getAudioInput() throws LineUnavailableException {
// 获取音频线(即音频输入源)
TargetDataLine line = AudioSystem.getTargetDataLine(FORMAT);
line.open(FORMAT);
line.start();
// 创建录音的缓冲区和音频输入流
byte[] buffer = new byte[4096];
AudioInputStream audioInputStream = new AudioInputStream(line);
// 启动录音线程
Thread recordingThread = new Thread(() -> {
try {
System.out.println("开始录音...");
while (true) {
int bytesRead = line.read(buffer, 0, buffer.length);
if (bytesRead == -1) break;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
line.stop();
line.close();
}
});
recordingThread.start();
// 录音持续时间
try {
Thread.sleep(5000); // 录制5秒
} catch (InterruptedException e) {
e.printStackTrace();
}
return audioInputStream;
}
}
3.2 代码解释
-
设置录音格式:
AudioFormat
类定义了音频数据的格式,包括编码格式、采样率、量化位数、声道数等。 -
录制音频:
- 使用
AudioSystem.getTargetDataLine(FORMAT)
获取一个TargetDataLine
对象,它是用来从麦克风或其他音频输入设备读取数据的。 - 启动
TargetDataLine
并将其封装在AudioInputStream
中。 - 创建一个录音线程,通过读取
TargetDataLine
的数据来录制音频。
- 使用
-
保存音频:
使用AudioSystem.write()
方法将录制的音频数据保存到WAV文件中。
3.4. 注意事项
- 录音时间:在示例中,录音时间设置为5秒。你可以根据需要调整录音时间。
- 异常处理:示例中简单地打印了异常信息。在实际应用中,可能需要更详细的错误处理。
- 采样率和格式:你可以根据实际需求调整音频格式(如采样率、位深度等)。
4. 录制音频代码实例
下面是一个示例代码,用于录制一段WAV音频并保存到本地文件。
import javax.sound.sampled.*;
/**
*
* @author 赵海洋
* @date 2020-8-16
* */
public class AudioRecorder {
private static final int SAMPLE_SIZE = 16;
private static final int CHANNELS = 1;
private static final int SAMPLE_RATE = 44100;
public void startRecord(String filename) {
try {
AudioFormat format = new AudioFormat(SAMPLE_RATE, SAMPLE_SIZE, CHANNELS, true, true);
DataLine.Info info = new DataLine.Info(TargetDataLine.class, format);
// 不支持当前格式
if (!AudioSystem.isLineSupported(info)) {
return;
}
TargetDataLine line = (TargetDataLine) AudioSystem.getLine(info);
line.open(format);
line.start();
AudioInputStream ais = new AudioInputStream(line);
AudioSystem.write(ais, AudioFileFormat.Type.WAVE, new File(filename));
line.stop();
line.close();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
AudioRecorder recorder = new AudioRecorder();
recorder.startRecord("hello.wav");
}
}
AudioSystem类的isLineSupported()方法来检查当前系统是否支持指定的音频格式。如果不支持,我们可以根据需要选择其他的音频格式。
5. 把音频流保存到本地
在实际应用中特别是语音系统,由于很多语音识别和合成服务都是调用第三方服务,通常提供API或流的形式,这样在实际开发过程中,需要将流保存到本机存储起来。
特别注意:音频流大部分是裸流,没有头部信息,如果保存需要自己手工添加
Java代码示例,演示了如何向一个没有WAV头的裸音频文件中添加WAV头。在这个示例中,我们假设裸音频文件的采样率为24000,声道数为2,每个样本大小为16位。
5.1 纯java代码编写
import java.io.*;
/**
*
* @author 赵海洋
* @date 2020-8-16
* */
public class FileAddWavHeader {
public static void addWavHeader(String inputFilePath, String outputFilePath) {
try {
File inputFile = new File(inputFilePath);
File outputFile = new File(outputFilePath);
int audioDataLength = (int) inputFile.length();
int totalDataLength = audioDataLength + 36;
long mySubChunk1Size = 16;
int myBitsPerSample= 16;
long mySampleRate = 24000;
int myChannel = 1;
int byteRate = mySampleRate * myChannel * myBitsPerSample/8;
int myBlockAlign = (int) (myChannel * myBitsPerSample/8);
int mySubChunk1Size =16;
int myFormat = 1;
OutputStream os = new FileOutputStream(outputFile);
DataOutputStream dos = new DataOutputStream(os);
dos.writeBytes("RIFF");//RIFF
dos.writeInt(Integer.reverseBytes(totalDataLength));//how big is the rest of this file?
dos.writeBytes("WAVE");//格式
dos.writeBytes("fmt ");//fmt
dos.writeInt(Integer.reverseBytes(mySubChunk1Size));//size of this chunk
dos.writeShort(Short.reverseBytes((short)myFormat));//what is the audio format? 1 for PCM = Pulse Code Modulation
dos.writeShort(Short.reverseBytes((short)myChannel));//mono or stereo? 1 or 2
dos.writeInt(Integer.reverseBytes(mySampleRate));//samples per second (numbers per second)
dos.writeInt(Integer.reverseBytes(byteRate));//bytes per second
dos.writeShort(Short.reverseBytes((short)myBlockAlign));//# of bytes in one sample, for all channels
dos.writeShort(Short.reverseBytes((short) myBitsPerSample));// how many bits in a sample(number)? usually 16 or 24
dos.writeBytes("data");
dos.writeInt(Integer.reverseBytes(audioDataLength));// how big is this data chunk
FileInputStream fis = new FileInputStream(inputFile);
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
dos.write(buffer, 0, bytesRead);
}
fis.close();
dos.close();
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
addWavHeader("E:\\file.wav", "E:\\out.wav");
}
}
5.2 纯java sound代码实现
import javax.sound.sampled.*;
import java.io.ByteArrayInputStream;
import java.io.File;
/**
*
* @author 赵海洋
* @date 2020-8-16
* */
public class AudioFormate {
byte[] bt = null;
public AudioFormate(byte[] buffer){
this.bt = buffer;
}
public void saveFile(){
// 设置音频格式和采样率等参数:8000,11025,16000,22050,44100 采样率
//44100:44.1kHz
float sampleRate =24000;
// 8,16 每个样本中的位数
int sampleSizeInBits = 16;
// 1,2 信道数(单声道为 1,立体声为 2,等等)
int channels = 1;
boolean signed = true;
// true,false 指示是以 big-endian 顺序还是以 little-endian 顺序存储音频数据。
boolean bigEndian = false;
AudioFormat audioFormat = new AudioFormat(sampleRate, sampleSizeInBits, channels, signed, bigEndian);
try {
DataLine.Info info = new DataLine.Info(TargetDataLine.class, audioFormat);
TargetDataLine line = (TargetDataLine) AudioSystem.getLine(info);
line.open(audioFormat);
line.start();
AudioSystem.write(new AudioInputStream(new ByteArrayInputStream(bt),audioFormat,bt.length/ audioFormat.getFrameSize()),
AudioFileFormat.Type.WAVE,
new File("D://ss.wav"));
line.stop();
line.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}