Java如何播放/录制音频?

前言:今天早上突发奇想想要玩一玩Java自带的声音API,闲着无聊学了一下午搞明白了它的用法(没有视频光看文字果然学习效率低下)

VoiceMonitor 的GitHub仓库

本来其实想做一个实时翻译的功能,用Aliyun的API,但是阿里云的API好像要付费,虽然GitHub上有做好的DEMO了,还挺好,自带字幕在屏幕底部,但是没有买套餐包,所以没法用,太难了。
然后闲着无聊想要搞一个耳机返听麦克风功能,听到自己的环境音,所以Bing了各种问题,敲敲打打缝缝补补搞出来一个DEMO,想要测试的时候发现,我电脑麦克风坏了你能信,插耳机或者笔记本自带的音响都没法用,佛了,虽然做出来了,逻辑上觉得还可以,应该没啥问题,但是估计跑起来有问题吧。有空了再去修一修。


在这里立下一个介绍(浅谈)javax.sound相关包的适用方法教程博文的Flag,有空一定!
——2020 05-17 17:23
时隔几天,我又回来了。开始更新。我可真是鸽子?
——2020 05-23 08:13


如何通过Java原生API录制音频?

要录制音频之前,我们要先认识一下AudioFormat 这个类,

AudioFormat (Java Platform SE 8 )

AudioFormat类适用于多种常见的声音文件编码技术,包括脉码调制(PCM),多律法编码和法律编码。 这些编码技术是预定义的,但服务提供商可以创建新的编码类型。 特定格式使用的编码由其encoding字段命名。

这个类是用来定义音频文件的格式的,比如说:采样频率,双声道,位数等。接下来列出来一个比较简单的写好的getAudioFormat()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
	public static AudioFormat getAudioFormat(){
//下面注释部分是另外一种音频格式,两者都可以
AudioFormat.Encoding encoding = AudioFormat.Encoding.
PCM_SIGNED ;
float rate = 8000f;//采样频率
int sampleSize = 16;//表示每个具有此格式的声音样本中的位数
String signedString = "signed";
boolean bigEndian = true;
int channels = 1;//单声道
return new AudioFormat(encoding, rate, sampleSize, channels,
(sampleSize / 8) * channels, rate, bigEndian);//返回一个创建好的AudioFormat类
// //采样率是每秒播放和录制的样本数
// float sampleRate = 16000.0F;
// // 采样率8000,11025,16000,22050,44100
// //sampleSizeInBits表示每个具有此格式的声音样本中的位数
// int sampleSizeInBits = 16;
// // 8,16
// int channels = 1;
// // 单声道为1,立体声为2
// boolean signed = true;
// // true,false
// boolean bigEndian = true;
// // true,false
// return new AudioFormat(sampleRate, sampleSizeInBits, channels, signed,bigEndian);
}
public static InputStream streamTran(ByteArrayOutputStream baos) {//输出流转换成输入流工具
return new ByteArrayInputStream(baos.toByteArray());
}

有了AudioFormat之后,我们就可以根据我们设置好的声音参数来采集声音了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public static ByteArrayOutputStream out = new ByteArrayOutputStream();//写出流
public void record() {
try {
AudioFormat format = AudioUtils.getAudioFormat();//获取需要采样声音的参数信息
DataLine.Info info = new DataLine.Info(TargetDataLine.class,format); // format is an AudioFormat object
TargetDataLine line = (TargetDataLine)(AudioSystem.getLine(info));//获取与指定的Line.Info对象中的描述匹配的行。
//通俗点就是获取一下话筒Line
line.open(format);//以指定的格式打开行,使该行获取任何所需的系统资源,并可以运行。
/*
*****这里的DataLine等操作,可以直接CV拷贝,这些都是固定格式。*****
*/

// Assume that the TargetDataLine, line, has already
// been obtained and opened.
int numBytesRead;
byte[] data = new byte[line.getBufferSize() / 5];//设置缓冲区大小
// Begin audio capture.
line.start();//开始记录声音
//允许线路从事数据I / O。 如果在已经运行的行上调用该方法,该方法什么也不做。 除非缓冲区中的数据已被刷新,
//否则线路将从线路停止时未处理的第一帧开始恢复I / O。 当音频捕获或播放开始时,会生成一个START事件。
// Here, stopped is a global boolean set by another thread.
for(;;) {//这里设置成为死循环,方便一直记录,不停歇。
// Read the next chunk of data from the TargetDataLine.
numBytesRead = line.read(data, 0, data.length);//读取字节到字节数组
// Save this chunk of data.
out.write(data, 0, numBytesRead);//写出流
}
} catch (LineUnavailableException e) {
e.printStackTrace();
}
}

什么是DataLine.Info ?DataLine.Info (Java Platform SE 8 )

除了从其超类继承的类信息, DataLine.Info还提供了特定于数据行的附加信息。 此信息包括: 数据线支持的音频格式
其内部缓冲区的最小和最大大小
因为一个Line.Info知道类其描述了线,一个DataLine.Info对象可以描述DataLine子接口,如SourceDataLineTargetDataLineClip 。 可以查询的任何这些类型的线混合器,传递的适当实例DataLine.Info作为参数的方法,如Mixer.getLine(Line.Info)

什么是TargetDataLine?TargetDataLine (Java Platform SE 8 )
目标数据线是可DataLine读取音频数据的类型DataLine 。 最常见的示例是从音频捕获设备获取其数据的数据线。 (该设备实现为写入目标数据线的混音器。)
目标数据线可以通过调用具有适当的DataLine.Info对象的MixergetLine方法从混合器获得。

写好这写代码之后,就可以在out流中读取到捕捉的音频字节了!

如何播放音频?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
	private InputStream is = null;
public void play() {
is = (ByteArrayInputStream) AudioUtils.streamTran(RecordVoice.out);
SourceDataLine sdl = null;
try {
DataLine.Info info = new DataLine.Info(SourceDataLine.class, AudioUtils.getAudioFormat());
sdl = (SourceDataLine) AudioSystem.getLine(info);//获取音频输出的Line
sdl.open(AudioUtils.getAudioFormat());
sdl.start();
byte[] buffer = new byte[1024];//缓存数组
int temp = 0;
while(true) {
temp = is.read(buffer);//读取字节
//(temp = inputStream.read(buffer))>=0
try {
sdl.write(buffer, 0, temp);//写出播放到音响的Line里。
}catch(Exception e1) {updateIs();continue;}//如果流里没有东西了,就手动更新一下
//输出声音
updateIs();
//每一次都重新获取一下声音流
}
} catch (Exception e) {
e.printStackTrace();
}finally {
sdl.drain();
sdl.close();
}
}

什么是SourceDataLine ?SourceDataLine (Java Platform SE 8 )
源数据线是可以写入数据的数据线。 它作为混音器的源头。 应用程序将音频字节写入源数据线,该数据线处理字节的缓冲并将其传递到混频器。 混合器可以将样品与来自其他来源的样品混合,然后将混合物输送到目标,例如输出端口(其可以表示声卡上的音频输出设备)。
可以通过调用具有适当的DataLine.Info对象的MixergetLine方法从混合器获得源数据线。

这样,我们就简单的实现了一个声音的录制/播放功能。