【问题标题】:Why does Java Sound behave differently when I run from a .jar?为什么当我从 .jar 运行时 Java Sound 的行为会有所不同?
【发布时间】:2012-08-28 10:31:18
【问题描述】:

下面的 play 方法来自一个类,该类在实例化时将 .wav 文件读入名为 data 的字节数组中,并将声音格式存储在名为 formatAudioFormat 对象中。

我有一个程序从java.util.Timer 调用play。当我进入包含所有相关 .class 文件的文件夹并使用命令 java MainClass 运行程序时,一切都按预期工作。但是,当我将所有 .class 文件放入可执行 .jar 并使用命令 java -jar MyProgram.jar 运行程序时,使用 play 方法播放的声音会在 50 到 150 毫秒后被切断。

public void play() throws LineUnavailableException {
    final Clip clip = (Clip)AudioSystem.getLine(new DataLine.Info(Clip.class, format));
    clip.open(format, data, 0, data.length);
    new Thread() {
        public void run() {
            clip.start();
            try {
                Thread.sleep(300); // all sounds are less than 300 ms long
            } catch (InterruptedException ex) { /* i know, i know... */ }
            clip.close();
        }
    }.start();
}

几个cmets:

  • 我已尝试将 play 方法中的睡眠时间增加到 1000 毫秒,但行为没有任何变化。

  • 使用System.nanoTimeThread.sleep 计时,可确认线程睡眠的时间与预期的时间一样长。

  • 由于要播放的声音文件是预加载到内存中的,我认为从 .jar 中提取声音资源的行为不会导致问题。

  • 我尝试使用内存池大小选项-Xms2m-Xmx64m(分别)从 jar 的内部和外部运行程序,但行为没有变化。

我在 Ubuntu 11.04 上运行 OpenJDK Java 6。知道发生了什么吗?

【问题讨论】:

  • 如需尽快获得更好的帮助,请发帖 SSCCE。这可能是byte[] 的加载问题。
  • @AndrewThompson:你完全正确!将 .wav 文件读入字节数组的方法中存在错误。当我使用 java MainClass 运行程序时,输入流能够在单个读取调用中读取 .wav 文件,因此该错误并不明显。当我从 .jar 运行程序时,需要多次读取调用,并且出现了错误。非常感谢你的帮助!如果您发表评论作为答案,我很乐意接受。
  • @AndrewThompson:谢谢!很抱歉,我花了这么长时间才接受您的回答......

标签: java multithreading javasound


【解决方案1】:

我不知道这是否与问题直接相关,但是每次播放该剪辑时都实例化一个新剪辑有点弄巧成拙。 Clip 的重点是能够启动它而无需先加载它。根据您当前的设置,您的 Clip 在完全加载之前甚至不会开始播放。 (当您包含实例化步骤时,SourceDataLines 开始播放比 Clips 更快,因为它们不会在开始之前等待所有数据加载。)

因此,作为解决此问题的第一步,我建议在调用播放之前实例化各种剪辑。我相信如果你做对了,并且剪辑开始在它自己的线程中,它可以被允许运行它的过程而不需要弄乱 Thread.sleep。您只需在下一次开始之前将剪辑重新定位回其起始帧。 (可以在开始前的 play 调用中完成。

一旦您对 Clip 的使用变得更加传统,可能会更容易判断是否还有其他事情发生。

我也不清楚为什么将源 .wav 文件作为中间步骤加载到字节数组中。不妨将它们直接加载到 Clips 中。考虑到 PCM 数据的优势,无论哪种方式,它们都将占用几乎相同数量的空间,并且在您调用它们时准备好使用。

禁用错误消息也是弄巧成拙。 Java 可能试图给你一个非常好的诊断,也许不是在这个特定的睡眠呼叫中(我知道,我知道),但如果你经常这样做,谁知道呢。 :)

【讨论】:

  • 每次播放声音时我都会实例化一个新剪辑,因为我可能需要同时播放相同声音的两个副本。有关详细信息,请参阅此答案:(stackoverflow.com/a/10000439/1644283)。
  • 我已经通过写入标准输出检查了在这个特定的睡眠调用期间没有抛出异常。 catch 块是静默的,因为调用 sleep 方法的方式使得很难将消息发送回我的程序的错误处理系统。 (该程序是一个 GUI 应用程序,因此标准输出的输出很可能会在正常使用期间被丢弃。)
  • 然后将两个剪辑预加载到内存中!您的播放例程应该只需要启动它们,而不是加载它们,否则,您的表现肯定会比使用 SourceDataLines 时更差。啊!刚看到trashgod的回复。解决此问题的第一步是遵循他的模式。他用代码显示了我在说什么。
【解决方案2】:

放大@Anrew 和@Phil 的答案,下面有一个获取Clip 并异步播放的大纲。有一个完整的例子here

AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(...);
Clip clip = AudioSystem.getClip();
clip.open(audioInputStream);
...
// Play the sound in a separate thread.
private void playSound() {
    Runnable soundPlayer = new Runnable() {

        @Override
        public void run() {
            try {
                clip.setMicrosecondPosition(0);
                clip.start();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    };
    new Thread(soundPlayer).start();
}

【讨论】:

  • 感谢您写出并引用高效剪辑使用的好例子! +1
  • 是的。我喜欢我的评论(现在升级为答案),但现在我更喜欢这个答案。 @Phil 也出现在第一个详细答案中。 OP - 您应该仔细考虑选择“接受”答案的方式。
  • trashgod 和@PhilFreihofner --- 感谢您的详细回答,希望其他人会觉得它们有用,但我不能接受它们,因为它们没有帮助我解决我的问题。我正在使用问题中描述的复杂方法,因为你们描述的传统方法对我不起作用。也许如果我更好地理解 Java Sound 和多线程,我就能看到我做错了什么,但不幸的是,这对我来说不是优先事项——尤其是现在我有工作代码。
  • @Vectornaut:无需解释;安德鲁的回答是肯定的。我只是不想抢占菲尔的回答。很高兴你把事情解决了。供以后参考,请注意此example 如何加载并在initial thread 上播放Clip,而对话框在 EDT 上运行。
  • 我是来帮助的,学习东西,而不是玩系统来积累积分。很高兴能得到 Andrew 的赞美,垃圾神,因为我很欣赏你们的专业知识以及我不会让人们误入歧途的保证(如果我的回答有误,也感谢批评)。
【解决方案3】:

这很可能是byte[] 的加载问题。

【讨论】:

  • 我不得不认为byte[] 是多余的;更多here.
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-03-30
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多