简体   繁体   English

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

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

I need some help with my Java application.我的 Java 应用程序需要一些帮助。 Its purpose is to read a certain website, so I need to play many audio files in a row.它的目的是阅读某个网站,所以我需要连续播放很多音频文件。 The JAR is compiled using Java 8. I tested my application with Windows 11 and Java 16.0.1, everything works fine. The JAR is compiled using Java 8. I tested my application with Windows 11 and Java 16.0.1, everything works fine. Then I used the latest Ubuntu Linux and Java 11.0.13 as well as Java 8: It plays some audio, but not every file. Then I used the latest Ubuntu Linux and Java 11.0.13 as well as Java 8: It plays some audio, but not every file.

I wrote a test class and the result was, that - no matter in which order I play the audio - only the first (exactly.) 62 files are played, Every next file (even the ones: that were successfully played at first) produces the exception my code throws at this position:我写了一个测试 class ,结果是——无论我以哪种顺序播放音频——只播放第一个(确切地说。)62个文件,每个下一个文件(甚至是那些:最初成功播放的文件)都会产生我的代码在这个位置抛出的异常:

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

I ensured that every audio file is .WAV with我确保每个音频文件都是.WAV

  • 8k sample rate, 8k 采样率,
  • 16k Bytes per second in average,平均每秒 16k 字节,
  • 16 Bits, and 16 位,和
  • pcm_s16le codec. pcm_s16le 编解码器。

My application is built as JAR-file including my audio files in the resources directory.我的应用程序构建为 JAR 文件,包括资源目录中的音频文件。

This is my code:这是我的代码:

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;
    }
}

What did I already try?我已经尝试了什么?

  1. I tried it on another computer that runs Ubuntu Linux as well.我也在另一台运行 Ubuntu Linux 的计算机上进行了尝试。
  2. I created a new instance of PlayAudio() everytime a new audio is played.每次播放新音频时,我都会创建一个新的PlayAudio()实例。
  3. I used audioClip0.stop();我用audioClip0.stop(); after every audio.在每个音频之后。
  4. I increased the milliseconds of sleep after every audio to length of the audio plus 1 second.我将每个音频后的睡眠毫秒数增加到音频长度加 1 秒。
  5. I rebuilt the projects... nearly 1k times.我重建了项目......近 1k 次。

How can I reproduce the error?如何重现错误?

I simply need to play more than 62 audio files running my JAR-file under Linux Ubuntu.我只需要在 Linux Ubuntu 下播放运行我的 JAR 文件的 62 个以上的音频文件。

How can you help me?你怎么能帮助我?

I don't know how to handle this issue.我不知道如何处理这个问题。 What is the problem playing .WAV -files with Linux?使用 Linux 播放.WAV文件有什么问题? Is there a common way to fix this?有没有一种通用的方法来解决这个问题? (I am not allowed to use any library except OracleJDK and OpenJDK.) (我不允许使用除 OracleJDK 和 OpenJDK 之外的任何库。)

The #1 suggestion is by Mark Rotteveel. #1 的建议是由 Mark Rotteveel 提出的。 The AudioInputStream class needs closing. AudioInputStream class 需要关闭。 This is often a surprise for people, because Java is well known for managing garbage collection.这常常让人们感到意外,因为 Java 以管理垃圾收集而闻名。 But for AudioInputStream there are resources that need to be released.但是对于AudioInputStream有需要释放的资源。 The API doesn't do an adequate job of pointing this out, imho, but the need for handling can be inferred from the description for the AudioInputStream.close() method: API 没有充分指出这一点,恕我直言,但可以从AudioInputStream.close()方法的描述中推断出需要处理:

Closes this audio input stream and releases any system resources associated with the stream.关闭此音频输入 stream 并释放与 stream 关联的所有系统资源。

The #2 suggestion is from both Andrew Thompson and Hendrik may be more a helpful hint than a direct solution, but it is still a very good idea, and it seems plausible to me that the inefficiency of all the additional, unneeded infrastructure ( ClassLoader , InputStream , BufferedInputStream ) might be contributing to the issue.来自 Andrew Thompson 和 Hendrik 的 #2 建议可能比直接解决方案更有帮助,但这仍然是一个非常好的主意,而且在我看来,所有额外的、不需要的基础设施( ClassLoaderInputStreamBufferedInputStream )可能会导致问题。 But I really don't have a good enough understanding of the underlying code to know how pertinent that is.但我对底层代码的理解真的不够好,无法知道它的相关性。

However, I think you can do even better.但是,我认为你可以做得更好。 Don't use Clip .不要使用Clip You current use of Clip goes against the concept of its design.您当前对Clip的使用违背了它的设计理念。 Clips are meant for short duration sounds that are to be held in memory and played multiple times, not files that are repeatedly reloaded before each playback. Clips适用于要保存在 memory 中并播放多次的短时声音,而不是每次播放前重复重新加载的文件。 The proper class for this sort of use (load and play) is the SourceDataLine .适合这种用途(加载和播放)的 class 是SourceDataLine

An example of playback using a SourceDataLine can be found in the javasound wiki.可以在javasound wiki 中找到使用SourceDataLine播放的示例。 This example also illustrates the use of URL for obtaining the necessary AudioInputStream .此示例还说明了使用URL获取必要的AudioInputStream I will quote it here verbatim.我将在这里逐字引用。

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();
    }
}

You will have to do some editing, as the example was set up to run via a main method.您将不得不进行一些编辑,因为该示例被设置为通过main方法运行。 Also, you'll be using your audio format, and that the names of the audio files with their folder locations will have to match the relative or absolute location specified in the argument you use in getResource() method.此外,您将使用您的音频格式,并且音频文件的名称及其文件夹位置必须与您在getResource()方法中使用的参数中指定的相对或绝对位置相匹配。 Also, a larger size for the buffer array might be preferred.此外,更大的buffer数组可能是首选。 (I often use 8192). (我经常用8192)。

But most importantly, notice that in this example, we close both the SourceDataLine and the AudioInputStream .但最重要的是,请注意,在此示例中,我们关闭了SourceDataLineAudioInputStream The alternate suggestion to use try-with-resources is a good one and will also release the resources.使用 try-with-resources 的替代建议是一个很好的建议,并且也会释放资源。

If there are difficulties altering the above to fit into your program, I'm sure if you show us what you try, we can help with making it work.如果在更改上述内容以适应您的程序时遇到困难,我相信如果您向我们展示您的尝试,我们可以帮助使其工作。

After applying the answer from @Phil Freihofner this worked for me:在应用@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;
    }
}

Thak you all for contributing to my solution.谢谢大家为我的解决方案做出贡献。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM