Search code examples
javalinuxaudiopulseaudio

Java audio fails to play wav file in Linux


I am having trouble using Java audio on Linux. This is OpenJDK 8 on Ubuntu 14.04. The following sample fails with the .wav file 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();
    }
}

This is the result:

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)

Apparently the problem is that the PulseAudio mixer is being selected, and for some reason it cannot play the .wav file.

If I replace the AudioSystem.getClip() call with AudioSystem.getClip(null), which selects the default mixer, then it works.

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


Update: Following the suggestion from @Dave to loop through the available mixers until I find one that has a "compatible" format, I see the following:

Target format (from AudioInputStream.getFormat()) is:

PCM_SIGNED 44100.0 Hz, 16 bit, stereo, 4 bytes/frame, little-endian

I loop through all mixers, source lines for each mixer, and supported formats for each source line, and get the following match:

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

I do get a match (using format.matches()) yet I still get the "Invalid format" exception. Perhaps because the format that matched says "Unknown sample rate" and then when I try to open the clip, it finds that it does not actually support 44100 Hz ?


Solution

  • Looks like there are two separate issues involved here.

    First, relying on AudioSystem.getClip() is not a good idea as basically there's no guarantee that the clip will be able to handle the specific format of the wav file. Instead, one of the following approaches should be used:

    • As suggested by @Dave: Loop through the available mixers and query if the target format is supported:

      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); 
          [...]
      }
      
    • Or, as suggested by @egorlitvinenko, use AudioSystem.getLine(DataLine.Info) to get a line with the desired capabilities.

    Both of the above approaches "should" work.

    On top of that, there is an additional problem with PulseAudio, which is that there is a bug which may result in an "invalid format" exception even though the PulseAudio mixer can actually handle the format. This is described in the following bug reports (the second one contains workarounds):