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