Java多线程声音崩溃

Java多线程声音崩溃,java,multithreading,crash,javasound,Java,Multithreading,Crash,Javasound,我正在用Java开发一个游戏项目,在这个项目中,我需要能够同时实时播放多个声音。我已经实现了一个声音系统,可以生成新的线程来播放SourceDataLine声音 这个游戏在我的电脑上运行得很好,但对我的一个测试人员来说,音响系统会以随机的间隔不断地向他袭来。我们都在Windows7上运行同一版本的java(jre 1.6.029),经过几轮测试和谷歌搜索,我一直无法理解为什么它对我来说总是很好,但却在他身上崩溃了 下面是一个SSCCE,它演示了我的代码中出现问题的部分。重要的是SoundPlay

我正在用Java开发一个游戏项目,在这个项目中,我需要能够同时实时播放多个声音。我已经实现了一个声音系统,可以生成新的线程来播放SourceDataLine声音

这个游戏在我的电脑上运行得很好,但对我的一个测试人员来说,音响系统会以随机的间隔不断地向他袭来。我们都在Windows7上运行同一版本的java(jre 1.6.029),经过几轮测试和谷歌搜索,我一直无法理解为什么它对我来说总是很好,但却在他身上崩溃了

下面是一个SSCCE,它演示了我的代码中出现问题的部分。重要的是SoundPlayer、Sound和SoundThread类。您需要在保存SSCCE的同一目录中包含两个名为shortSound.wav和longerSound.wav的声音文件

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.sound.sampled.*;
import java.util.HashMap;
import java.util.Collection;
import java.io.*;
import java.net.URL;
import java.util.LinkedList;

public class SoundSSCCE extends JFrame
{

    private JPanel screenP;
    private JPanel bgFraming;

    /**
    *   Constructor
    *   Preconditions: None.
    *   Postconditions: The window for the SSCCE is created.
    **/

    public SoundSSCCE()
    {
        super("Sound problem SSCCE");
        this.setSize(200,100);

        // instantiate main window panel

        screenP = new SSCCEPanel(this);
        this.add(screenP);

        // finishing touches on Game window

        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setVisible(true);

        System.out.println("Game Window successfully created!!!");
    }


    public static void main(String[] args)
    {
        SoundSSCCE gui = new SoundSSCCE();      
    }
}




/**
*   SSCCEPanel is the JPanel that manages the example's timer, painting, and logic. 
**/

class SSCCEPanel extends JPanel
{
    public Frame parentFrame;
    private Timer timer;
    public int logicLoops;
    public double prevFPS;
    boolean timerReady;

    // The SoundPlayer object is used by the example to play the sounds.

    public SoundPlayer soundPlayer;

    public SSCCEPanel(Frame parent)
    {
        super(true);
        parentFrame = parent;
        this.setFocusable(true);

        Toolkit.getDefaultToolkit().sync();
        logicLoops = 0;

        soundPlayer = new SoundPlayer();

        TimerListener timerListener = new TimerListener();
        prevFPS = 0;
        timerReady = true;
        timer = new Timer(0,timerListener);
        this.setFPS(60);
        timer.start();
    }

    /** 
    *   setFPS()
    *   Preconditions: fps is a quantity of frames per second
    *   Postconditions: Sets the timer's refresh rate so that it 
    *       fires fps times per second.
    **/

    public void setFPS(int fps)
    {
        int mspf = (int) (1000.0 /fps + 0.5);
        timer.setDelay(mspf);
    }


    /**
    *   This is the JPanel's timer listener. It runs the example's logic and repaint
    *   methods each time it gets a timer signal.
    **/

    private class TimerListener implements ActionListener
    {
        long startTime = System.currentTimeMillis();
        long lastTime = this.startTime;
        int ticks = 0;

        public void actionPerformed(ActionEvent e)
        {
            Object source = e.getSource();
            if(source == timer)
            {
                // perform a loop through the game's logic and repaint.

                synchronized(this)
                {
                    if(timerReady)
                    {
                        timerReady = false;
                        runSSCCELogic();
                        repaint();
                        timerReady = true;
                    }
                }

                // Logic for Frames per Second counter

                this.ticks++;

                long currentTime = System.currentTimeMillis();

                if(currentTime - startTime >= 500) 
                {
                    prevFPS =  1000.0 * ticks/(1.0*currentTime - startTime);
                    System.out.println(prevFPS);
                    startTime = currentTime;
                    ticks = 0;
                }

                lastTime = currentTime;
            }
        }
    }


    /**
    *   repaints the SSCCE.
    *   This just shows the current FPS and the number of sounds currently playing.
    **/

    public void paintComponent(Graphics g)
    {
            super.paintComponent(g);

            Graphics2D g2D = (Graphics2D) g;
            double roundedFPS = Math.round(prevFPS*10)/10.0;

            g2D.setColor(new Color(0x000000));
            g2D.drawString("FPS: " + roundedFPS, 20,20);
            g2D.drawString("Sounds playing: " + soundPlayer.soundsPlaying(), 20,50);
            g.dispose();
    }

    /**
    *   runSSCCEELogic()
    *   This is where the run-time logic for the SSCCE example is. 
    *   All it will do is play sounds at regular narrowly-spaced intervals.
    *   The sounds are stored in the same directory as the SSCCE. Substitute any .wav
    *   file here in place of shortSound.wav and longerSound.wav.
    **/

    public void runSSCCELogic()
    {
        if(logicLoops % 4 == 0)
        {
            soundPlayer.play("shortSound.wav");
        }
        if(logicLoops% 40 == 0)
        {
            soundPlayer.play("longerSound.wav");
        }
        logicLoops++;
    }


}



/**
*   My game uses makes method calls to the SoundPlayer object whenever it
*   needs to play any sounds. This separates the main game code from the lower-level 
*   mucking-about with Sound object code. It also makes it easier for me to make changes to 
*   my Sound class without having to make changes any lines in my game where I play sounds.
**/


class SoundPlayer
{
    private HashMap<String,Sound> sounds;
    private double volume;
    private int soundsPlaying;

    public SoundPlayer()
    {
        sounds = new HashMap<String,Sound>();
        volume = 1.0;
        soundsPlaying = 0;
    }


    /**
    *   playSound(String path)
    *   creates and plays a Sound specified by path.
    *   Preconditions: path is the file path for the sound.
    *   Postconditions: The sound is loaded and begins playing. 
    *       This method returns a reference to that sound.
    **/

    public Sound play(String path)
    {       
        Sound newSound;

        if(volume == 0)
            return null;

        if(sounds.containsKey(path))
        {
            newSound = sounds.get(path);
        }
        else
        {
            newSound = new Sound(path);
            sounds.put(path,newSound);
        }

        newSound.play(volume);

        return newSound;
    }

    /**
    *   load(String path)
    *   preloads a sound to be play instances of at a later time
    *   Preconditions: path is the file path for the sound.
    *   Postconditions: The sound is loaded. This method returns 
    *       a reference to that sound.
    **/

    public Sound load(String path)
    {
        Sound newSound;

        if(sounds.containsKey(path))
        {
            newSound = sounds.get(path);
        }
        else
        {
            newSound = new Sound(path);
            sounds.put(path,newSound);
        }
        return newSound;
    }

    public int soundsPlaying()
    {
        int count = 0;

        Collection<Sound> soundsIterable = sounds.values();
        for(Sound sound : soundsIterable)
        {
            count += sound.instances;
        }
        return count;
    }

    public int soundsLoaded()
    {
        return sounds.size();
    }


    public void setVolume(double vol)
    {
        this.volume = vol;
    }

}


/**
*   Sound objects store the path to a given sound file and spawn new threads
*   to play instances of their sound when the SoundPlayer tells them to play.
**/

class Sound
{
    public String path;
    protected int instances;
    protected URL soundURL;

    public Sound(String name)
    {
        try
        {
            soundURL =  getClass().getClassLoader().getResource(name);
            instances = 0;
            path = name;
        }
        catch(Exception ex)
        {
            System.out.println("An error occured while loading the sound file: " + name);
            System.out.println(ex.getMessage());
            ex.printStackTrace();
        }
    }

    /**
    *   play(double volume)
    *   Preconditions: volume is between 0.0 and 1.0 inclusive, where 1.0 is 
    *       at full volume and 0.0 is silent.
    *   Postconditions: The Sound spawns a new thread to play an instance
    *       of itself.
    **/

    public void play(double volume)
    {
        try
        {
            SoundThread clip = new SoundThread(this);
            synchronized(this)
            {
                // increment the count of its instances. The SoundThread
                //  will automatically decrement the sound instance when it finishes. 

                instances++;
            }
            clip.setVolume(volume);
            clip.start();

        }
        catch(Exception e)
        {
            System.out.println("Sound error: Error playing sound");
            System.out.println(e.getMessage());
            e.printStackTrace();
        }
    }
}


/**
*   SoundThread is a thread that Sound objects spawn to play instances 
*   of their sounds. This supports multiple sounds being played simultaneously. 
**/

class SoundThread extends Thread
{
    public SourceDataLine clip;
    public AudioInputStream stream;
    private int bufferSize = 50;
    private Sound parent;

    public SoundThread(Sound parentSound)
    {
        try
        {
            parent = parentSound;

            // obtains input stream from AudioSystem to read from the file.

            stream = AudioSystem.getAudioInputStream(parentSound.soundURL); 
            AudioFormat format = stream.getFormat();

            // obtains the sound file's line

            DataLine.Info info = new DataLine.Info(SourceDataLine.class, format); 

            // loads the line into the clip

            clip = (SourceDataLine) AudioSystem.getLine(info); 

            // opens the clip onto the stream

            clip.open(format); 
            System.out.println("Sound buffer size: " + clip.getBufferSize());
        }
        catch(Exception e)
        {
            System.out.println("error playing sound");
            System.out.println(e.getMessage());
            e.printStackTrace();
        }
    }


    public void run()
    {
        try
        {
            // I start my sourceDataLine and begin reading data 
            // from the stream into its buffer.

            clip.start();
            int bytesRead = 0;
            byte[] soundData = new byte[bufferSize];

            // read data from the stream into the sourceDataLine until there
            // is no more data to read

            while(bytesRead != -1)
            {
                bytesRead = stream.read(soundData,0,bufferSize);
                if(bytesRead >= 0)
                {
                    clip.write(soundData, 0, bytesRead);
                }
                else
                {
                    // Here I drain and close the line and its stream when
                    //  the sound is finished playing (it has read all the bytes from its stream).
                    // My tester's log suggests that SourceDataLine.drain() is where it
                    // is crashing.

                    clip.drain();
                    clip.close();
                    stream.close();
                }
            }

            // decrement the count of the Sound's instances.

            synchronized(parent)
            {
                parent.instances--;
            }
        }
        catch(Exception e)
        {
            System.out.println(e.getMessage());
            e.printStackTrace();
        }
    }



    public void setVolume(double vol)
    {
        try
        {
            FloatControl volCtrl = (FloatControl) clip.getControl(FloatControl.Type.MASTER_GAIN);
            if(vol > 1.0)
                vol = 1.0;
            if(vol < 0.0)
                vol = 0.0;

            // tricky, confusing sound math stuff.

            float dB = (float) (Math.log(vol) / Math.log(10.0) * 20.0);
            if(dB > volCtrl.getMaximum())
                dB = volCtrl.getMaximum();
            if(dB < volCtrl.getMinimum())
                dB = volCtrl.getMinimum();
            volCtrl.setValue(dB);
        }
        catch(Exception e)
        {
            System.out.println("set volume failed");
            System.out.println(e.getMessage());
            e.printStackTrace();
        }
    }
}
日志文件表明,当声音结束并调用SourceDataLine.drain()以清除声音缓冲区中的任何剩余数据时,崩溃发生在声音线程期间。我不确定我做错了什么导致我的测试人员的jre在游戏中途崩溃


你的声音/线程大师的任何见解都会很有帮助。

冒着没有直接回答你的问题而被指责的风险,我想提出一个建议。可以创建一个自定义“剪辑”对象,允许多次播放和以不同速度播放。如果ClipTrack(我的名字)有多个游标,它们的输出可以相加并作为单个音频值发送出去

我在这里演示如何使用这种方法:

该站点上有指向Java代码的链接,发布在,欢迎您使用。但是您可能需要经常编辑源代码。我知道我从一开始就把它贴出来了。我只是一名中级Java程序员,正在自学声音。有很多事情应该做得不同(对象将数据“交给”我编写的混音器,而不是直接播放它——但是您可以重写它,如果可以设计一种完全无阻塞的管理游标的方法,那么使用CopyOnWriteArrayList也可能不是最佳的)

但是,至少可以在那里看到基本思想。:)

这种方法的潜在好处包括减少线程数。此外,您的内存中一次不会有多个原始音频数据副本(而不是在所有剪辑中制作多个副本)


我发现使用javax.sound Clips非常烦人,我很高兴不再与它们纠缠。

这是一个不同的答案,我不打算修改之前关于编写自定义剪辑的建议(这是一种有效但非常不同的方法)

顺便说一句:我对SourceDataLine使用变量名“clip”感到困惑。真的应该重构那种东西

嗯。查看您的代码,您省略了我在应用程序中包括的三个步骤,在应用程序中,我回放了重叠的SDL

line.drain();
line.stop();
line.close();
line = null;

aiStream.close();
aiStream = null;
其中“line”是源数据线,aiStream是AudioInputStream


我认为将这些设置为null可能是关键步骤。不确定省略的stop()步骤有多重要。我不能确切地告诉你为什么需要这样做,但这在我的应用程序中起了作用。

这个问题仍然没有解决。总之,问题可能是发生在机器上的固有问题,在本机Java中可能不值得进一步研究

可能太晚了,无法为您提供任何帮助,但以防其他人感兴趣-

SoundThread::run
中,您正在检查
bytesRead
,但如果
stream.read
返回
0
(可能不应该),那么您将陷入无限循环


这可能取决于正在播放的声音的精确长度,并可能解释为什么只有一个人看到问题。

在SoundThread中捕获了一些异常,但没有登录。你确定这不是问题所在吗?为了更快地获得更好的帮助,请发布一条消息。安德鲁:刚刚为它创建了一个SSCCE。编辑我的OP以提供它。Jivings:我更改了那些try-catch块以打印异常消息。我正在等待我的测试人员查看异常(如果有)会说什么。Jivings:在我更改代码后,我的测试人员没有收到任何异常消息。它只是崩溃了,没有抛出异常。你好,菲尔。最初,我使用剪辑播放声音,但测试我的游戏的人中约有一半的人在剪辑方面有问题,导致游戏延迟。如果我找不到其他工作,我会试试你的ClipTrack课程。在花时间学习全新的类包之前,我想知道是什么导致我当前的sound threads代码崩溃。我会让我的测试人员尝试一下。这并没有解决问题:(我的测试仪说它仍在崩溃。谢谢你让我知道。很抱歉它没有帮助。
line.drain();
line.stop();
line.close();
line = null;

aiStream.close();
aiStream = null;