Playing game music in Java - How to stop the stream of data?
I have this music class for a game I'm making in an Introduction Course in high school. You can see the source where I got the code from.
I originally was using a Clip
, but found out that had pretty small buffer sizes, and couldn't play long songs well (at least not on our Macs at school). The new code works well, from what I understand it is getting "chunks" of the song, playing them, then getting more chunks. Problem is, when the music changes the songs will sometimes overlap, and when I exit the game, a terrible sound plays (at least on the Macs) probably caused because the stream of data is being cut before the game closes.
Is there anyway to fix this other than delaying the program a few seconds each time? (Note: I don't want to use external .jar's or libraries, I'd like to keep it to strictly the JRE)
package mahaffeyfinalproject;
import java.io.File;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInp开发者_如何转开发utStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.SourceDataLine;
public class Music implements Runnable{
//SOURCE: http://www.ntu.edu.sg/home/ehchua/programming/java/J8c_PlayingSound.html
//Note: I've modified it slightly
private String location;
private boolean play;
public void Music(){
}
public void playMusic(String loc) {
location = loc;
play = true;
try{
Thread t = new Thread(this);
t.start();
}catch(Exception e){
System.err.println("Could not start music thread");
}
}
public void run(){
SourceDataLine soundLine = null;
int BUFFER_SIZE = 64*1024;
try {
File soundFile = new File(location);
AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(soundFile);
AudioFormat audioFormat = audioInputStream.getFormat();
DataLine.Info info = new DataLine.Info(SourceDataLine.class, audioFormat);
soundLine = (SourceDataLine) AudioSystem.getLine(info);
soundLine.open(audioFormat);
soundLine.start();
int nBytesRead = 0;
byte[] sampledData = new byte[BUFFER_SIZE];
while (nBytesRead != -1 && play == true) {
nBytesRead = audioInputStream.read(sampledData, 0, sampledData.length);
if (nBytesRead >= 0) {
soundLine.write(sampledData, 0, nBytesRead);
}
}
} catch (Exception e) {
System.err.println("Could not start music!");
}
soundLine.drain();
soundLine.close();
}
public void stop(){
play = false;
}
}
I'm not sure about the horrible sound you are getting on close, but I think I have an idea of how to solve the overlap problem you mentioned. It looks like your usage of the Sound API is fine, but I suspect you may be having a concurrency issue. I noticed that you start off a new thread for each sound you play. This is a good idea, but you need to be careful if you don't want your songs to mix. My theory is that your code looks something like this:
Music songA = new Music();
Music songB = new Music();
//Start playing song A
songA.playMusic("path");
//Some stuff happens with your game...
//Some time later you want to change to song B
songA.stop();
songB.playMusic("other path");
At first blush this looks fine, after all you were careful to stop songA before starting songB, right? Unfortunately, when it comes to concurrency few things are as simple as we might like them to be. Let's look at a small bit of your code, specifically the while
loop responsible for feeding data into the audio stream...
//Inside run(), this is the interesting bit at the end
while(nBytesRead != -1 && play == true){
//Feed the audio stream while there is still data
}
stop()
sets play
to false, causing the while loop to exit, after finishing the current iteration. If I'm not mistaken, each iteration of your while
loop is sending 64k of data into the audio stream. 64k is a fair amount of audio data, and depending on the audio properties will go on for some time. What you really want to do is signal the thread to quit (i.e. set play to false), and then wait for the thread to finish. I believe this will fix the overlap problem, and if you're not killing your threads properly this issue may also be causing the god-awful racket on quit. I humbly suggest the following change to your Music class (with no guarantee of correctness)...
public class Music implements Runnable{
private Thread t; //Make the thread object a class field
//Just about everything is the same as it was before...
public void stop(){
play = false;
try{
t.join()
}catch(InterruptedException e){
//Don't do anything, it doesn't matter
}
}
}
Hope this helps, and for the love of all that is good and pure don't use my code as-is, it came off the top of my head in about 2 minutes. I recommend The Java Tutorials' series on threading.
精彩评论