从 JAR 应用程序播放音频文件在 Linux 上不起作用

Playing audio files from a JAR-application does not work on Linux

我的 Java 申请需要一些帮助。它的用途是阅读某个网站,所以我需要连续播放很多音频文件。 JAR 是使用 Java 8 编译的。我用 Windows 11 和 Java 16.0.1 测试了我的应用程序,一切正常。然后我使用了最新的 Ubuntu Linux 和 Java 11.0.13 以及 Java 8:它播放了一些音频,但不是每个文件。

我写了一个测试 class,结果是,无论我以何种顺序播放音频,都只播放前(正是!)62 个文件。每个下一个文件(甚至是最初成功播放的文件)都会产生我的代码在这个位置抛出的异常:

if (mixerSelected != null) {
    audioClip0 = AudioSystem.getClip(mixerSelected);
} else {
    throw new IllegalArgumentException("File is not compatible: '" + audioFilePath + "'.");
}

我确保每个音频文件都是 .WAV

我的应用程序构建为 JAR 文件,包括资源目录中的音频文件。

这是我的代码:

public class PlayAudio {

    /**
     * plays an audio file
     *
     * @param audioFilePath String: path to the audio file
     * @param speed double: speed applied to the audios
     */
    public boolean singleFile(String audioFilePath, double speed) {

        //audioFilePath = "audio" + File.separator + audioFilePath;
        audioFilePath = "audio" + "/" + audioFilePath;

        AudioInputStream audioStream0;

        //create new file using path to the audio
        try {
            //load files from resources folder as stream
            ClassLoader classLoader = getClass().getClassLoader();
            InputStream inputStream = classLoader.getResourceAsStream(audioFilePath);
            InputStream bufferedInputStream = new BufferedInputStream(inputStream);

            if (bufferedInputStream == null) {
                throw new IllegalArgumentException("File not found: '" + audioFilePath + "'.");
            } else {
                //create new AudioStream
                audioStream0 = AudioSystem.getAudioInputStream(bufferedInputStream);
            }
        } catch (IllegalArgumentException e) {
            //handle
            return false;
        } catch (IOException e) {
            //handle
            return false;
        } catch (UnsupportedAudioFileException e) {
            //handle
            return false;
        }

        try {
            //create new AudioFormat
            AudioFormat audioFormat0 = audioStream0.getFormat();

            //create new Info
            DataLine.Info info0 = new DataLine.Info(Clip.class, audioFormat0);

            //initialize new Mixer
            Mixer.Info mixerSelected = null;

            for (Mixer.Info mixerInfo : AudioSystem.getMixerInfo()) {
                Mixer mixer = AudioSystem.getMixer(mixerInfo);

                if (mixer.isLineSupported(info0)) {
                    mixerSelected = mixerInfo;
                    break;
                }
            }

            //create new Clip
            Clip audioClip0;

            if (mixerSelected != null) {
                audioClip0 = AudioSystem.getClip(mixerSelected);
            } else {
                //THIS EXCEPTION GETS THROWN!!!
                throw new IllegalArgumentException("File is not compatible: '" + audioFilePath + "'.");
            }

            //open created Clips via created AudioStream
            audioClip0.open(audioStream0);

            //start the play of audio file
            audioClip0.start();

            //wait until play completed
            double waitTime = (double)((((double)audioClip0.getMicrosecondLength()/1000.0)/speed + 50.0) * 0.8);
            Thread.sleep((long)waitTime);

            return true;

        //handle exceptions
        } catch (LineUnavailableException e) {
            //handle
            return false;
        } catch (IOException e) {
            //handle
            return false;
        } catch (InterruptedException e) {
            //handle
            return false;
        } catch (IllegalArgumentException e) {
            //THIS EXCEPTION GETS THROWN!!!
            //handle invalid audio clips
            System.out.println(e);
            e.printStackTrace();
            return false;
        }
    }



    /**
     * plays multiple audio files in the order they are stored in an ArrayList
     *
     * @param fileNames ArrayList<String>: list with filenames of audio files to play
     * @param speaker String: speaker to use for playing the audios (can be 'm' or 'w')
     * @param speed double: speed applied to the audios
     * @return boolean: true if playing audios completed successfully, otherwise false
     */
    public static boolean multiFiles(ArrayList<String> fileNames, String speaker, double speed) {

        PlayAudio player = new PlayAudio();

        //play every audio file in the array of file names
        for (int i = 0; (i < fileNames.toArray().length); i ++) {
            //generate file names
            String fullFileName = speaker + "_" + fileNames.toArray()[i];

            //play audio
            player.singleFile(fullFileName, speed);
        }

        return true;
    }
}

我已经尝试了什么?

  1. 我在另一台运行 Ubuntu Linux 的计算机上试过。
  2. 每次播放新音频时,我都会创建一个 PlayAudio() 的新实例。
  3. 我在每个音频后使用 audioClip0.stop();
  4. 我将每个音频后的睡眠毫秒数增加到音频长度加 1 秒。
  5. 我重建了项目……将近 1k 次。

如何重现错误?

我只需要播放超过 62 个音频文件 运行 我的 JAR 文件在 Linux Ubuntu.

你能帮我什么忙?

我不知道如何处理这个问题。用 Linux 播放 .WAV 文件有什么问题? 有解决这个问题的通用方法吗? (我不允许使用除 OracleJDK 和 OpenJDK 之外的任何库。)

#1 建议来自 Mark Rotteveel。 AudioInputStream class 需要关闭。这通常会给人们带来惊喜,因为 Java 以管理垃圾收集而闻名。但是对于 AudioInputStream 有资源需要释放。 API 没有充分指出这一点,恕我直言,但是可以从 AudioInputStream.close() 方法的描述中推断出处理的需要:

Closes this audio input stream and releases any system resources associated with the stream.

#2 建议来自 Andrew Thompson 和 Hendrik 可能比直接解决方案更有帮助,但它仍然是一个非常好的主意,而且我认为所有额外的低效率似乎是合理的,不需要的基础设施(ClassLoaderInputStreamBufferedInputStream)可能是造成此问题的原因。但是我对底层代码的理解真的不够好,无法知道它的相关性。

不过,我认为你可以做得更好。不要使用 Clip。您当前使用 Clip 违背了其设计理念。 Clips 适用于要保存在内存中并多次播放的持续时间较短的声音,而不是每次播放前反复重新加载的文件。这种使用(加载和播放)的正确 class 是 SourceDataLine.

可以在 javasound wiki 中找到使用 SourceDataLine 播放的示例。此示例还说明了如何使用 URL 来获取必要的 AudioInputStream。我会在这里逐字引用它。

public class ExampleSourceDataLine {

    public static void main(String[] args) throws Exception {
        
        // Name string uses relative addressing, assumes the resource is  
        // located in "audio" child folder of folder holding this class.
        URL url = ExampleSourceDataLine.class.getResource("audio/371535__robinhood76__06934-distant-ship-horn.wav");
        // The wav file named above was obtained from https://freesound.org/people/Robinhood76/sounds/371535/ 
        // and matches the audioFormat.
        AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(url);
        
        AudioFormat audioFormat = new AudioFormat(
                Encoding.PCM_SIGNED, 44100, 16, 2, 4, 44100, false);
        Info info = new DataLine.Info(SourceDataLine.class, audioFormat);
        SourceDataLine sourceDataLine = (SourceDataLine)AudioSystem.getLine(info);
        sourceDataLine.open(audioFormat);
        
        int bytesRead = 0;
        byte[] buffer = new byte[1024];
        sourceDataLine.start();
        while((bytesRead = audioInputStream.read(buffer)) != -1)
        {
            // It is possible at this point manipulate the data in buffer[].
            // The write operation blocks while the system plays the sound.
            sourceDataLine.write(buffer, 0, bytesRead);                                 
        }   
        sourceDataLine.drain();
        // release resources
        sourceDataLine.close();
        audioInputStream.close();
    }
}

您必须进行一些编辑,因为示例是通过 main 方法设置为 运行。此外,您将使用您的音频格式,并且音频文件的名称及其文件夹位置必须与您在 getResource() 方法中使用的参数中指定的相对或绝对位置相匹配。此外,buffer 数组的大小可能是首选。 (我经常用8192)

但最重要的是,请注意在此示例中,我们关闭了 SourceDataLineAudioInputStream。使用 try-with-resources 的替代建议很好,也会释放资源。

如果更改上述内容以适应您的程序有困难,我相信如果您向我们展示您的尝试,我们可以帮助实现它。

应用@Phil Freihofner 的答案后,这对我有用:

/**
 * plays an audio file
 *
 * @param audioFilePath String: path to the audio file
 * @param speed double: speed applied to the audios
 */
public boolean singleFile(String audioFilePath) {

    //get class
    ClassLoader classLoader = getClass().getClassLoader();

    //use try-with-resources
    //load files from resources folder as stream
    try (
            AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(
            new BufferedInputStream(
                    Objects.requireNonNull(classLoader.getResourceAsStream(audioFilePath))))
    ) {

        if (audioInputStream == null) {
            throw new IllegalArgumentException("File not found: '" + audioFilePath + "'.");
        }

        //create new AudioFormat
        AudioFormat audioFormat = audioInputStream.getFormat();

        //create new Info
        DataLine.Info info = new DataLine.Info(SourceDataLine.class, audioFormat);

        //create new SourceDataLine and open it
        SourceDataLine sourceDataLine = (SourceDataLine)AudioSystem.getLine(info);
        sourceDataLine.open(audioFormat);

        //start the play of the audio file
        int bytesRead;
        byte[] buffer = new byte[8192];
        sourceDataLine.start();

        while ((bytesRead = audioInputStream.read(buffer)) != -1) {
            sourceDataLine.write(buffer, 0, bytesRead);
        }

        sourceDataLine.drain();
        sourceDataLine.close();
        audioInputStream.close();

        //return true, because play finished
        return true;
    } catch (Exception e) {
        //ignore exceptions
        return false;
    }
}

感谢大家为我的解决方案做出贡献。