Java 音频无法播放 Linux 中的 wav 文件

Java audio fails to play wav file in Linux

我在 Linux 上使用 Java 音频时遇到问题。这是 Ubuntu 14.04 上的 OpenJDK 8。以下示例因 .wav 文件 from this link:

而失败
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(null) 替换 AudioSystem.getClip() 调用,那么它就可以工作。

如何确保选择了兼容的调音台?


更新: 按照@Dave 的建议循环遍历可用的混音器,直到找到一个具有 "compatible" 格式的混音器,我看到以下内容:

目标格式(来自 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

匹配(使用format.matches())但我仍然得到"Invalid format"异常。可能是因为匹配的格式说 "Unknown sample rate" 然后当我尝试打开剪辑时,它发现它实际上不支持 44100 Hz ?

如果你可以使用 SourceDataLine in your use case instead of 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) 以确保您选择默认的 MixerAudioSystem.getClip() 的语义(正如您所注意到的)不是特别可靠。将所有调用 Clip.open 的尝试包装在 try/catch 中。如果使用默认混音器打开剪辑不起作用,则循环遍历不包括默认混音器的可用 Mixer.Info 对象并调用每个对象 getClip(mixerInfo) 直到一个有效。

另一种方法是遍历 AudioSystem.getMixerInfo() 返回的 Mixer.Info 个对象。为每个调用 AudioSystem.getMixer(mixerInfo) 以获取 Mixer 实例。循环遍历 Mixer.getSourceLineInfo() 返回的 Line.Info 个对象。对于其中的每一个,如果它是 DataLine.Info 的实例,则对其进行转换并调用 DataLine.Info.getFormats() 以获取受支持的 AudioFormat 对象。将这些与 AudioInputStream.getFormat() returns(使用 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();
    }
}

How can I make sure that a compatible mixer is selected ?

另一种情况 - 默认使用默认值,或者捕获异常并在故障转移时使用默认值。

这里似乎涉及两个不同的问题。

首先,依靠 AudioSystem.getClip() 不是一个好主意,因为基本上不能保证剪辑能够处理特定格式的 wav 文件。相反,应该使用以下方法之一:

  • :遍历可用的混音器并查询是否支持目标格式:

    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); 
        [...]
    }
    
  • 或者,,使用 AudioSystem.getLine(DataLine.Info) 获取具有所需功能的行。

以上两种方法 "should" 都有效。

除此之外,PulseAudio 还存在一个额外的问题,即存在一个可能导致 "invalid format" 异常 的错误,即使 PulseAudio 混音器实际上可以处理格式。这在以下错误报告中进行了描述(第二个包含解决方法):