我在 Linux 上使用 Java 音频时遇到问题。这是 Ubuntu 14.04 上的 OpenJDK 8。以下示例因 .wav 文件来自此链接而失败:
import java.net.URL;
import javax.sound.sampled.*;
public class PlaySound {
public void play() throws Exception
{
// List all mixers and default mixer
System.out.println("All mixers:");
for (Mixer.Info m : AudioSystem.getMixerInfo())
{
System.out.println(" " + m);
}
System.out.println("Default mixer: " + AudioSystem.getMixer(null).getMixerInfo());
URL url = getClass().getResource("drop.wav");
Clip clip;
AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(url);
clip = AudioSystem.getClip();
System.out.println("Clip format: " + clip.getFormat());
clip.open(audioInputStream);
clip.start();
do { Thread.sleep(100); } while (clip.isRunning());
}
public static void main(String [] args) throws Exception {
(new PlaySound()).play();
}
}
这是结果:
All mixers:
PulseAudio Mixer, version 0.02
default [default], version 4.4.0-31-generic
Intel [plughw:0,0], version 4.4.0-31-generic
Intel [plughw:0,2], version 4.4.0-31-generic
NVidia [plughw:1,3], version 4.4.0-31-generic
NVidia [plughw:1,7], version 4.4.0-31-generic
NVidia [plughw:1,8], version 4.4.0-31-generic
NVidia [plughw:1,9], version 4.4.0-31-generic
Port Intel [hw:0], version 4.4.0-31-generic
Port NVidia [hw:1], version 4.4.0-31-generic
Default mixer: default [default], version 4.4.0-31-generic
Clip format: PCM_SIGNED unknown sample rate, 16 bit, stereo, 4 bytes/frame, big-endian
Exception in thread "main" java.lang.IllegalArgumentException: Invalid format
at org.classpath.icedtea.pulseaudio.PulseAudioDataLine.createStream(PulseAudioDataLine.java:142)
at org.classpath.icedtea.pulseaudio.PulseAudioDataLine.open(PulseAudioDataLine.java:99)
at org.classpath.icedtea.pulseaudio.PulseAudioDataLine.open(PulseAudioDataLine.java:283)
at org.classpath.icedtea.pulseaudio.PulseAudioClip.open(PulseAudioClip.java:402)
at org.classpath.icedtea.pulseaudio.PulseAudioClip.open(PulseAudioClip.java:453)
at PlaySound.play(PlaySound.java:22)
at PlaySound.main(PlaySound.java:29)
显然问题在于正在选择 PulseAudio 混音器,并且由于某种原因它无法播放 .wav 文件。
如果我将
AudioSystem.getClip()
调用替换为 AudioSystem.getClip(null)
(它选择默认混音器),那么它就可以工作。
如何确保选择兼容的混音器?
更新: 按照@Dave 的建议循环访问可用的混音器,直到找到具有“兼容”格式的混音器,我看到以下内容:
目标格式(来自
AudioInputStream.getFormat()
)是:
PCM_SIGNED 44100.0 Hz, 16 bit, stereo, 4 bytes/frame, little-endian
我循环遍历所有混音器、每个混音器的源行以及每个源行支持的格式,并获得以下匹配:
Mixer: PulseAudio Mixer, version 0.02
Source line: interface SourceDataLine supporting 42 audio formats, and buffers of 0 to 1000000 bytes
Format matches: PCM_SIGNED unknown sample rate, 16 bit, stereo, 4 bytes/frame, little-endian
我do得到了匹配(使用
format.matches()
)但我仍然得到“无效格式”异常。也许是因为匹配的格式显示“未知采样率”,然后当我尝试打开剪辑时,发现它实际上并不支持 44100 Hz ?
SourceDataLine
而不是 Clip
,那么您应该能够执行以下操作:
URL url = getClass().getResource("drop.wav");
SourceDataLine sourceLine;
AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(url);
sourceLine = AudioSystem.getSourceDataLine(audioInputStream.getFormat());
System.out.println("Source format: " + sourceLine.getFormat());
sourceLine.open(audioInputStream);
sourceLine.start();
do { Thread.sleep(100); } while (sourceLine.isRunning());
(请注意,这在我这边尚未经过测试。)
如果您打算查找或循环,则只需要
Clip
。
如果您确实需要查找或循环的能力,一个(丑陋的)方法将首先调用
AudioSystem.getClip(null)
以确保您选择默认的 Mixer
。 AudioSystem.getClip()
的语义(正如您所注意到的)不是特别可靠。将所有调用 Clip.open
的尝试包装在 try/catch 中。如果打开剪辑无法使用默认混音器,则循环遍历除默认混音器之外的可用 Mixer.Info
对象,并对每个对象调用 getClip(mixerInfo)
,直到有一个可以工作。
另一种方法是循环
Mixer.Info
返回的 AudioSystem.getMixerInfo()
对象。为每个对象调用 AudioSystem.getMixer(mixerInfo)
以获取 Mixer
实例。循环遍历 Line.Info
返回的 Mixer.getSourceLineInfo()
对象。对于其中每一个,如果它是 DataLine.Info
的实例,则对其进行转换并调用 DataLine.Info.getFormats()
来获取支持的 AudioFormat
对象。将它们与 AudioInputStream.getFormat()
返回的内容进行比较(使用 matches
),直到找到兼容的。
这些都不是特别优雅。第一个是异常的错误使用。第二个有点复杂,尽管它看起来更正确。
我也在 Ubuntu 14.04 上,但有不同的混音器,它工作正常。 我认为您可以指定您的生产线所需的具体参数:
import javax.sound.sampled.*;
import java.net.URL;
public class PlaySound {
public void play() throws Exception {
// List all mixers and default mixer
System.out.println("All mixers:");
for (Mixer.Info m : AudioSystem.getMixerInfo()) {
System.out.println(" " + m);
}
System.out.println("Default mixer: " + AudioSystem.getMixer(null).getMixerInfo());
URL url = getClass().getResource("drop.wav");
Clip clip;
AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(url);
// clip = AudioSystem.getClip();
try {
AudioFormat format = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED,
44100,
16, 2, 4,
AudioSystem.NOT_SPECIFIED, true);
DataLine.Info info = new DataLine.Info(Clip.class, format);
clip = (Clip) AudioSystem.getLine(info);
} catch (LineUnavailableException e) {
System.out.println("matching line is not available due to resource restrictions");
return;
} catch (SecurityException ee) {
System.out.println("if a matching line is not available due to security restrictions");
return;
} catch (IllegalArgumentException eee) {
System.out.println("if the system does not support at least one line matching the specified Line.Info object " +
"through any installed mixer");
return;
}
System.out.println("Clip format: " + clip.getFormat());
clip.open(audioInputStream);
clip.start();
do {
Thread.sleep(100);
} while (clip.isRunning());
}
public static void main(String[] args) throws Exception {
(new PlaySound()).play();
}
}
如何确保选择兼容的混音器?
另一种情况 - 默认情况下使用默认值,或者捕获异常并在故障转移时使用默认值。
看起来这里涉及两个单独的问题。
首先,依赖
AudioSystem.getClip()
并不是一个好主意,因为基本上不能保证剪辑能够处理 wav 文件的特定格式。相反,应使用以下方法之一:
按照@Dave的建议:循环访问可用的混音器并查询是否支持目标格式:
URL url = getClass().getResource("drop.wav");
AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(url);
AudioFormat format = audioInputStream.getFormat();
DataLine.Info lineInfo = new DataLine.Info(Clip.class, format);
Mixer.Info selectedMixer = null;
for (Mixer.Info mixerInfo : AudioSystem.getMixerInfo()) {
Mixer mixer = AudioSystem.getMixer(mixerInfo);
if (mixer.isLineSupported(lineInfo)) {
selectedMixer = mixerInfo;
break;
}
}
if (selectedMixer != null) {
Clip clip = AudioSystem.getClip(selectedMixer);
[...]
}
或者,按照@egorlitvinenko的建议,使用
AudioSystem.getLine(DataLine.Info)
获得具有所需功能的线路。上述两种方法“应该”有效。
除此之外,PulseAudio 还有一个额外的问题,即存在一个错误,可能会导致“无效格式”异常 即使 PulseAudio 混音器实际上可以处理该格式。以下错误报告对此进行了描述(第二个包含解决方法):
您启动剪辑,也关闭剪辑。否则它在 Linux 上只能播放一次。
clappingSoundButton.addActionListener(event -> {
if (Settings.isSoundOn())
{
try
{
Clip clip = AudioSystem.getClip();
clip.open(ApplicationSound.getClappingSound());
FloatControl volume = (FloatControl) clip
.getControl(FloatControl.Type.MASTER_GAIN);
volume.setValue(Settings.getVolume());
clip.start();
do {
Thread.sleep(100);
} while (clip.isRunning());
clip.close();
}
catch (LineUnavailableException | IOException e)
{
// nothing
}
catch (InterruptedException e)
{
// nothing
}
}
});
这对我在 Windows 和 Linux 上都有效。