为什么这段代码无法播放声音文件?

3

代码

import javax.sound.sampled.*;
import java.io.*;

public class Tester {
static Thread th;


public static void main(String[] args) {
    startNewThread();   
   while( th.isAlive() == true) {
       System.out.println("sound thread is working");
   }
}

public static void startNewThread() {
   Runnable r = new Runnable() {
       public void run() {
           startPlaying();
       }
   };
   th =new Thread(r);
   th.start();
} 
public static void startPlaying() {
   try {            
        AudioInputStream ais = AudioSystem.getAudioInputStream(new File("d:/UnderTest/wavtester.wav"));
        Clip clip = AudioSystem.getClip();
        clip.open(ais);
        clip.loop(-1); // keep playing the sound                  
   } catch(Exception exc) {
       System.out.println(exc);
     }       
 }
}

这段代码输出“sound thread working”,但没有播放任何声音。在这段代码中,我启动了一个单独的线程来播放声音,程序不应该在声音线程完成任务之前终止。但程序在打印一系列“sound thread working”后终止了。
造成程序终止和声音未播放的原因是什么?

我是指修改您的其他问题。但是,我在那里发表的相同评论也适用于此处。链接源中的最后两个单行注释对您意味着什么? - Andrew Thompson
@ Andrew Thompson 这意味着程序只有在由 clip 生成的守护线程完成后才会终止。但我不知道它在播放3-4秒后为什么会结束。 - Suhail Gupta
@ Andrew Thompson JOptionPane 出现一会儿就自动消失了。我之前发布过这个问题,但没有得到有意义的答案。http://stackoverflow.com/questions/6594921/how-does-boolean-here-become-false-on-its-own - Suhail Gupta
JOptionPane 出现一会儿就自动消失了。”我能想到唯一的解释是“你的 Java 安装已经损坏了”。 - Andrew Thompson
@ Andrew Thompson 你所说的“is broken”是什么意思? - Suhail Gupta
@Andrew Thompson 这是播放 wav / 任何声音文件 的最佳方法吗? 我发布的答案中的代码 - Suhail Gupta
3个回答

6
问题在于 Clip 已经启动了一个后台的守护线程来播放音频文件。
因此,您的代码执行流程如下:
  • 主线程启动一个次要(无用)线程
  • 次要线程启动守护线程(将播放声音)
  • 与此同时,主线程在次要线程处于活动状态时继续打印一些东西
  • 当次要线程完成启动播放线程后,它将结束,因此次要线程将不再处于活动状态
  • 主线程将注意到次要线程不再活动并将结束
  • 由于播放线程是守护线程,JVM 将退出(因为剩下的线程都是守护线程)
最终结果就是您看到的:在次要线程正在启动播放线程时,主线程会输出一些文本,当播放线程开始播放时,JVM 就会崩溃。有时候您甚至可以听到耳机中的“咔哒声”(表示音频已经开始播放)然后 JVM 就退出了。
最简单的解决方法是让次要线程(即非守护线程)在播放声音时睡眠。
...
  clip.open(ais);
  clip.loop(-1);
  Thread.sleep(amountToSleep);
...

需要注意的一件重要事情:一年前,当我使用这个Java API时,我发现getMicrosecondLength()方法存在缺陷。我在Windows和Linux两个平台上进行编码,在一个平台上我可以得到正确的值,但在另一个平台上,同样的方法只返回毫秒级的长度!

我发现获取声音真实长度最可靠的方法是使用getFrameLength()方法,并从中计算长度。

我没有在这个笔记本电脑中找到我当时写的代码。我将在另一台电脑上检查,如果我找到它,我会发布一个完整的示例(在Windows上使用Sun JVM和在Linux上使用OpenJDK或Sun都可以可靠地工作)。


我得到了一个帧长度为504845,如何从中知道音轨长度? - Suhail Gupta
这是我不太确定的部分... 我需要检查我的代码才能确定。但你可以尝试一下 clip.getFormat().getFrameRate(),它应该会给你每秒钟的帧数(通常这个值会在8000到44100之间)。然后你可以通过将帧数(getFrameLength())除以帧速率来得到剪辑的长度(单位为秒)。 - Bruno Reis

0
while(clip.isRunning()){} 

更好


1
如果我通过 clip.stop() 停止/暂停了该行,而主线程完成了它的工作。它将返回 true,并将我从程序中抛出。我认为这个检查不适合这种情况。 - Suhail Gupta

0
import javax.sound.sampled.*;
import java.io.*;
import javax.swing.*;

public class Tester {
static Thread th;


public static void main(String[] args) throws Exception {
     Clip clip = AudioSystem.getClip();
     AudioInputStream ais = AudioSystem.getAudioInputStream(new File("d:/UnderTest/wavtester.wav"));
     clip.open(ais);
     clip.loop(0);
     // Calculate totalFrames
     long totalFrames = (long)(clip.getFrameLength() * clip.getFormat().getFrameRate());
     Thread.sleep( ( totalFrames* 1000 )); // pause the thread till the sound plays

     System.out.println(clip.getFrameLength());
     System.out.println(clip.getFormat().getFrameRate());           
 }
}

我会移除 try {} catch {} 块,只保留 Thread.sleep(...),因为在这么简单的代码中它是不必要的(你甚至已经将 main 方法声明为 throws Exception),而在生产代码中,这远非处理 InterruptedException 的正确方式。 - Bruno Reis

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接