Java-аудио не воспроизводит wav-файл в Linux

У меня проблемы с использованием аудио Java в Linux. Это OpenJDK 8 на Ubuntu 14.04. В следующем примере происходит сбой с файлом .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

Я действительно получаю совпадение (используя format.matches()), но по-прежнему получаю исключение "Неверный формат". Возможно, потому что в совпадающем формате написано «Неизвестная частота дискретизации», а затем, когда я пытаюсь открыть клип, он обнаруживает, что на самом деле он не поддерживает 44100 Гц?


person Grodriguez    schedule 13.09.2017    source источник
comment
Вам нужен интерфейс Clip или SourceDataLine будет работать? Я понимаю, что ваш пример здесь, вероятно, просто минимальное воспроизведение проблемы, так что вам нужно искать или зацикливаться?   -  person Dave    schedule 20.09.2017


Ответы (3)


Если вы можете использовать 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), пока не найдете совместимое.

Ни один из них не является особенно элегантным. Во-первых, это неправильное использование исключений. Второй просто немного запутан, хотя кажется более правильным.

person Dave    schedule 20.09.2017
comment
К сожалению, мне нужно использовать Clip. Ре. ваши предложения: Первый обходной путь нецелесообразен, так как место, где создается клип (getClip), не связано с местом, где мне нужно вызвать Clip.open. Второе казалось многообещающим, но даже если я действительно получаю совпадение, позже я все равно получаю исключение. Смотрите обновление на мой вопрос. Похоже, совпадения недостаточно, чтобы гарантировать совместимость. Любые другие идеи? - person Grodriguez; 20.09.2017
comment
Вполне вероятно, что getClip делает что-то похожее на второе предложение за кулисами. Я должен буду подумать об этом, но я не знаю лучшего способа спонтанно. - person Dave; 21.09.2017
comment
Я попытался немного поискать и наткнулся на этот SO-ответ, который предполагает, что привязки PulseAudio Java больше не поддерживаются. Я недостаточно знаю, чтобы подтвердить или опровергнуть это, но, возможно, вы можете настроить среду выполнения Java, чтобы избежать PulseAudio. - person Dave; 21.09.2017
comment
ОК, похоже, в PulseAudio есть ошибка (см. мой ответ). Я голосую за ваш ответ и награждаю вас наградой, так как вы действительно поставили меня на правильный путь. Однако я отмечаю свой ответ как принятый, поскольку он описывает всю историю и, следовательно, с большей вероятностью поможет другим, у которых может возникнуть такая же проблема в будущем. - person Grodriguez; 22.09.2017
comment
Está muy великодушно. Я рад, что помог вам найти убедительный ответ, даже если я не смог предоставить реальное решение. Спасибо, что поделились отчетом об ошибке с обходными путями. Это хорошо знать. - person Dave; 22.09.2017

У меня тоже 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();
    }
}

Как убедиться, что выбран совместимый микшер?

Другие случаи - использовать по умолчанию по умолчанию или перехватывать исключение и использовать по умолчанию при отработке отказа.

person egorlitvinenko    schedule 20.09.2017
comment
Спасибо и +1. Похоже, это только половина дела — вторая половина — это ошибка в PulseAudio :-/ - person Grodriguez; 22.09.2017

Похоже, здесь есть две отдельные проблемы.

Во-первых, полагаться на 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 действительно может обрабатывать этот формат. Это описано в следующих отчетах об ошибках (второй содержит обходные пути):

person Grodriguez    schedule 22.09.2017