Java audio Clip cannot be closed when using Linux

2020-07-23 03:30发布

问题:

I'm trying to play a sound in a Java application but the application never actually terminates because the PulseAudio Eventloop thread is left running even after trying to close the Clip instance used for playing the sound:

import java.io.File;
import java.io.IOException;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.LineEvent;
import javax.sound.sampled.LineListener;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.UnsupportedAudioFileException;

final class AudioClipTest {

    public static void main(String[] args) throws UnsupportedAudioFileException, IOException, LineUnavailableException {
        try (AudioInputStream instream = AudioSystem.getAudioInputStream(new File("resources/test-sound.wav"))){
            final AudioFormat format = instream.getFormat();
            final Clip clip = (Clip) AudioSystem.getLine(new DataLine.Info(Clip.class, format));
            clip.addLineListener(new LineListener() {
                @Override
                public void update(final LineEvent event) {
                    if (event.getType() == LineEvent.Type.STOP) {
                        System.out.println("Finished playing audio.");
                        event.getLine().close();
                        // FIXME: This line is never reached on Linux with
                        // PulseAudio
                        System.out.println("Closed audio data line.");
                    }
                }
            });
            clip.open(instream);
            clip.start();
        }
    }

}

On my Linux machine, the above main method plays the relevant sound, prints

Finished playing audio.

and then hangs, while on a Windows machine it prints

Finished playing audio.

Closed audio data line.

and exits properly.

The actual Clip implementation used on the Linux machine is org.classpath.icedtea.pulseaudio.PulseAudioClip whereas on Windows it is com.sun.media.sound.DirectAudioDevice.DirectClip; What is going on here? — What is PulseAudioClip.close() doing which isn't returning?


Distribution: Linux Mint 18.1; Kernel: 4.4.0-92-generic x86_64; OpenJDK: 8u131-b11-2ubuntu1.16.04.3; PulseAudio: 1:8.0-ubuntu3.3

Distribution: Windows 10 Pro; Version: 1703; OS Build: 15063.540 x64; Oracle JDK: 1.8.0_144-b01; DirectX: version 12

回答1:

One possible way to work around this issue is to use the Alsa (userspace) API for playing audio using Java. The alsa userspace API will redirect to PulseAudio (or another API/mixer) so you should still get correct audio.

Edit $JAVA_HOME/jre/lib/sound.properties and comment out everything related to org.classpath.icedtea.pulseaudio and add this:

javax.sound.sampled.Clip=com.sun.media.sound.DirectAudioDeviceProvider
javax.sound.sampled.Port=com.sun.media.sound.PortMixerProvider
javax.sound.sampled.SourceDataLine=com.sun.media.sound.DirectAudioDeviceProvider
javax.sound.sampled.TargetDataLine=com.sun.media.sound.DirectAudioDeviceProvider

Also, please file a bug against your distribution's openjdk package. The PulseAudio/Java bindings have been unsupported for years now and should be removed.