用Swing播放音频 - 我的代码线程安全吗?

时间:2018-04-26 17:54:08

标签: java swing audio thread-safety javasound

我正在做的简化版本(当计时器到期时触发蜂鸣器声音),但这足以证明我的设计。

开始播放后,Swing永远不需要再次触摸音频片段。我已经能够确认此代码确实回放了声音并且没有阻止事件发送线程,但我想确保没有其他线程安全问题,我在不知不觉中违反了。谢谢!

import java.io.IOException;
import java.net.URL;

import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.LineEvent;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.UnsupportedAudioFileException;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;

public class TriggeringSoundTest extends JFrame
{
    private static final long serialVersionUID = -4573437469199690850L;

    private boolean soundPlaying = false;

    public TriggeringSoundTest()
    {
        JButton button = new JButton("Play Sound");
        button.addActionListener(e -> new Thread(() -> playBuzzer()).start());
        getContentPane().add(button);
    }

    private void playBuzzer()
    {
        URL url = getClass().getResource("resources/buzzer.wav");
        try (AudioInputStream stream = AudioSystem.getAudioInputStream(url); 
                Clip clip = AudioSystem.getClip())
        {
            clip.open(stream);
            clip.addLineListener(e -> {
                if (e.getType() == LineEvent.Type.STOP)
                    soundPlaying = false;
            });
            soundPlaying = true;
            clip.start();
            while (soundPlaying)
            {
                try
                {
                    Thread.sleep(1000);
                }
                catch (InterruptedException exp)
                {
                    // TODO Auto-generated catch block
                    exp.printStackTrace();
                }
            }
        }
        catch (UnsupportedAudioFileException exp)
        {
            // TODO Auto-generated catch block
            exp.printStackTrace();
        }
        catch (IOException exp)
        {
            // TODO Auto-generated catch block
            exp.printStackTrace();
        }
        catch (LineUnavailableException exp)
        {
            // TODO Auto-generated catch block
            exp.printStackTrace();
        }
    }

    private static void createAndShowGUI()
    {
        JFrame frame = new TriggeringSoundTest();
        frame.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
        frame.pack();
        frame.setVisible(true);
    }

    public static void main(String[] args)
    {
        SwingUtilities.invokeLater(() -> createAndShowGUI());
    }
}

1 个答案:

答案 0 :(得分:1)

我已经从使用Clips的所有内容中删除了一些内容,因为它们带来了许多恼人的方面。

但是,正如评论中所提到的那样,您应该在程序开始时一次初始化(打开)每个Clip一次。然后,每次需要时重新启动Clip。 API将显示将Clip重置为其起始帧并重播的确切命令。

管理线程是令人厌烦的。如果我理解正确,Clip会启动一个守护程序线程来实现音频输出。父线程终止时,守护程序线程会死亡。因此,您可以通过放入 Thread.sleep()命令和LineListener来保持线程在SFX的持续时间内保持活动状态,因此它的守护进程不会被扼杀父线程终止。

我担心,如果你使用这种方法,调用线程(按下按钮启动的代码)在你的" sleep"期间无法做任何其他事情。期。如果按钮代码只是执行播放,那么你应该没问题。但是如果你以后决定用相同的按钮按下(比如蜂鸣器动画)还有什么东西可以触发怎么办?可能有必要添加另一层复杂功能,例如将剪辑包装到另一个线程中,该线程的唯一责任是剪辑播放。然后按下按钮就可以启动这个包装器并执行其他操作,而不受Thread.sleep()解决方案的限制。

现在,应该可以避免这一切!我们假设你做了一个Clip名为的蜂鸣器,并在程序的早期打开它,将其保存在一个实例变量中(如Andrew所建议)。然后,让我们说你从一个无限期保持活着的线程中调用 buzzer.start(),例如经典的游戏循环。我想在这里,只要游戏循环线程继续存在,Clip start()就会启动它的守护程序线程并进行游戏。通过这种方式,您可以省去睡眠和听线。但是,如果没有实际编写示例并亲自尝试,我并不是百分之百确定。

(按钮线程如何告诉游戏循环线程播放声音?也许按钮线程设置一个标志,游戏循环线程检查该标志作为其正常"更新"的一部分。嗯。也许我真的需要在提供它之前尝试编码。)

作为替代方案,请随意检查代码,并可能使用AudioCue。它与加载和播放中的Clip类似,但在其下方使用SourceDataLine代替Clip。如果您查看代码,您将看到SourceDataLine在其自己的线程中维护(它作为正在打开的AudioCue的一部分保持活动状态)。这消除了很多复杂情况。

这种方法带来的一个很好的优势是它可以提供并发播放 - 例如,如果你想在第一个蜂鸣器仍在播放时启动第二个蜂鸣器实例。 AudioCue还允许您执行诸如以不同速度播放蜂鸣器之类的操作,这可能有助于将SFX充当听起来像多个提示。我尽力提供文档和示例。该许可证允许您剪切和粘贴代码,以满足您的特定需求。