Skip to main content
gamedev.net
🔒 Locked 🎮 Unity

[java] Multi threading question

Started by TheSpotlessPane 12 replies 2.8k views
Original Post
TheSpotlessPane
TheSpotlessPane
So I've been trying to get a simple game going in Java. I've got an animated title screen and I finally got the music to play. (I'm using java sound because I want to have that lower level functionality for later in the project...especially being able to control where in the file playback will start.) The problem is that if I play the sound file, the music plays until the file is completely fed into my SourceDataLine object, and nothing else happens. It's like the code reader (whatever that is) is stuck in this part of the program and can't do anything else until the music has played all the way through. What this means is none of the other title screen features get taken care of. The graphics don't even get loaded (since this music part happens before any of that other stuff). But, of course, when the music is finished, and the file has been played all the way through, the code continues to do the other stuff in the program...so, the title screen pops up at the end of the song.

I've attached the code that handles the music playing part. I've set it up to where in main, I set up a "Scene_Title" object, and inside this class, the track object (as well as other objects specific to the title screen like sprites etc...). The Scene_Title object gets continuously updated from main, but none of this really matters right now since it just gets stuck in the Track class until the file is finished. I was looking around, and saw a topic that talked about multi threading, is this what I would need to do so that the compiler can focus on other things while it plays out the track? And how do you tell the compiler to multi thread something? Thanks for any feed back.

Also, I've tried removing the while loop, and just loading all the bit data in one go. I was reading something that said you could do this, and it would simply remember your request and load in pieces of it at a time, but I still had the same problem when I did this.


import java.io.File;
import java.io.IOException;

import javax.sound.sampled.*;

public class Track {
public Track(String name) throws IOException {
//Get a mixer
Mixer.Info[] mixerinfo = AudioSystem.getMixerInfo();
Mixer mixer = AudioSystem.getMixer(mixerinfo[0]);

//Get the file
String separator = System.getProperty("file.separator");
String rootdir = System.getProperty("user.dir");
String trackPath = rootdir + separator + "resources" + separator + "music" +
separator + name;

AudioFileFormat format;
try {
format = AudioSystem.getAudioFileFormat(new File(trackPath));

//get a stream for my file
AudioInputStream stream = AudioSystem.getAudioInputStream(new File(trackPath));

SourceDataLine line;

DataLine.Info info2 = new DataLine.Info(SourceDataLine.class, format.getFormat());
//DataLine.Info info = new DataLine.Info(Clip.class, format.getFormat());
try {
line = (SourceDataLine) mixer.getLine(info2);
line.open();

// get the total length of the sound file in bytes
long length = format.getByteLength();
int bufferSize = line.getBufferSize();
long total = 0;
boolean stopped = false;

line.start();
int nBytesRead = 0;

byte[] bytes = new byte[bufferSize];

try {
while (nBytesRead != -1){
nBytesRead = stream.read(bytes, 0, bytes.length);
if (nBytesRead >= 0){
line.write(bytes, 0, nBytesRead);
}
}
} catch (IOException e){
e.printStackTrace();
return;
} finally {
line.drain();
line.close();
}

} catch (LineUnavailableException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} catch (UnsupportedAudioFileException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

}
}
EricTheRed
EricTheRed
Yes, you do want to run the audio in another thread. Java is always multi-threaded, but you still need to tell it when to create a new thread. Try this code:

import java.io.File;
import java.io.IOException;

import javax.sound.sampled.*;

public class Track implements Runnable {
private String name;
public Track(String name) {
this.name = name;
}

public void run() throws IOException {
//Get a mixer
Mixer.Info[] mixerinfo = AudioSystem.getMixerInfo();
Mixer mixer = AudioSystem.getMixer(mixerinfo[0]);

//Get the file
String separator = System.getProperty("file.separator");
String rootdir = System.getProperty("user.dir");
String trackPath = rootdir + separator + "resources" + separator + "music" +
separator + name;

AudioFileFormat format;
try {
format = AudioSystem.getAudioFileFormat(new File(trackPath));

//get a stream for my file
AudioInputStream stream = AudioSystem.getAudioInputStream(new File(trackPath));

SourceDataLine line;

DataLine.Info info2 = new DataLine.Info(SourceDataLine.class, format.getFormat());
//DataLine.Info info = new DataLine.Info(Clip.class, format.getFormat());
try {
line = (SourceDataLine) mixer.getLine(info2);
line.open();

// get the total length of the sound file in bytes
long length = format.getByteLength();
int bufferSize = line.getBufferSize();
long total = 0;
boolean stopped = false;

line.start();
int nBytesRead = 0;

byte[] bytes = new byte[bufferSize];

try {
while (nBytesRead != -1){
nBytesRead = stream.read(bytes, 0, bytes.length);
if (nBytesRead >= 0){
line.write(bytes, 0, nBytesRead);
}
}
} catch (IOException e){
e.printStackTrace();
return;
} finally {
line.drain();
line.close();
}

} catch (LineUnavailableException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} catch (UnsupportedAudioFileException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

}
}


Now use this:

Track track = new Track("song.wav");
new Thread(track).run;


That should make your song play in a separate thread. You should read into how threading works some more because it sounds like you don't fully understand how it is handled in Java (http://download.oracle.com/javase/tutorial/essential/concurrency/). Note: I just wrote this code in the post so I have no idea if it will compile.
TheSpotlessPane
TheSpotlessPane
Thanks a lot for the quick response. You are correct, I'm fairly new to this stuff and still have a ways to go before I'll really feel in control, so thank you for the link, I'm definitely going to check that out. Also, I tried having the Track class implement Runnable like you suggested, and set it up the way you set it up, but it's acting like I can't throw an exception in the run() method. I get an error in Eclipse that says "Exception IOException is not compatible with throws clause in Runnable.run()", but then if I remove the throws statement, of course it tells me "Unhandled exception type IOException." Do you somehow need to set up run() to where no exceptions occur? Or is there a pretty quick and easy fix for this? Thanks again for your feed back.

Here's the new code exactly as is...(pretty much identical to what you wrote)


import java.io.File;
import java.io.IOException;

import javax.sound.sampled.*;

public class Track implements Runnable {
private String name;
public Track(String name) throws IOException {
this.name = name;
}

@Override
public void run() throws IOException{
//Get a mixer
Mixer.Info[] mixerinfo = AudioSystem.getMixerInfo();
Mixer mixer = AudioSystem.getMixer(mixerinfo[0]);

//Get the file
String separator = System.getProperty("file.separator");
String rootdir = System.getProperty("user.dir");
String trackPath = rootdir + separator + "resources" + separator + "music" +
separator + name;

AudioFileFormat format;
try {
format = AudioSystem.getAudioFileFormat(new File(trackPath));

//get a stream for my file
AudioInputStream stream = AudioSystem.getAudioInputStream(new File(trackPath));

SourceDataLine line;

DataLine.Info info2 = new DataLine.Info(SourceDataLine.class, format.getFormat());
//DataLine.Info info = new DataLine.Info(Clip.class, format.getFormat());
try {
line = (SourceDataLine) mixer.getLine(info2);
line.open();

// get the total length of the sound file in bytes
long length = format.getByteLength();
int bufferSize = line.getBufferSize();
long total = 0;
boolean stopped = false;

line.start();
int nBytesRead = 0;

byte[] bytes = new byte[bufferSize];

try {
while (nBytesRead != -1){
nBytesRead = stream.read(bytes, 0, bytes.length);
if (nBytesRead >= 0){
line.write(bytes, 0, nBytesRead);
}
}
} catch (IOException e){
e.printStackTrace();
return;
} finally {
line.drain();
line.close();
}

} catch (LineUnavailableException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} catch (UnsupportedAudioFileException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

}

}
EricTheRed
EricTheRed
Ok, think about it this way, what should a thread do if it has an exception? Should it stop running, should it propagate the exception to the thread that created it, should it just silently do nothing? This is why you can't throw an exception out of run. Java doesn't know what to do with it. Instead wrap everything in the run in a try clause. The behavior you are looking for is probably something like just don't play the file and print an error, which would then be easy to set up. You could also move the file loading portion to the constructor and just stick the play code in the run method.
WavyVirus
WavyVirus
If the main thread really needs to know when an exception occurs, trap the exception in a try/catch block in your Runnable and notify the main thread. The Runnable could contain a reference to an object passed in by the main thread, calling a method on that object to register the fact that an exception occurred. Or the main thread could keep a reference to the runnable, which would expose some method for checking its state. Read up a bit on thread-safety if you're not familiar with the term - Eric's link is definitely worth reading.

If playing a sound fails, most likely you'll just want to log the error, have the audio thread die and the main thread continue on - the exception never has to leave the catch block.

TheSpotlessPane
TheSpotlessPane
Cool, I got the run method working and everything, but it still seems to be getting stuck in the section of code that handles the music playing. I'm gonna visit that link and see if it gives me any useful insights.

To WavyVirus: ha, I have to admit you went a little over my head. I think the main problem here is that I'm a bit out of my depth. But thanks to both of you for trying to help. Hopefully I'll be a little more fit to tackle this problem after reading through the info on that link.
WavyVirus
WavyVirus
Try: new Thread( new Track("foo.bar") ).start();
EricTheRed
EricTheRed

Try: new Thread( new Track("foo.bar") ).start();


That's right, my mistake. new Thread(track).run() would just call track.run(). new Thead(track).start() creates a new thread then calls track.run() in that new thread.
TheSpotlessPane
TheSpotlessPane
It works! woo! I was just calling track.run() before. Thanks again.
WavyVirus
WavyVirus

[quote name='WavyVirus' timestamp='1300405070' post='4787229']
Try: new Thread( new Track("foo.bar") ).start();

That's right, my mistake. new Thread(track).run() would just call track.run(). new Thead(track).start() creates a new thread then calls track.run() in that new thread.[/quote]Yeah - I only noticed because I made the same mistake at work the other day and it took me a good couple of minutes to notice the problem - "Thread.run()" doesn't immediately jump out as being wrong when you're scanning through your code...
TheSpotlessPane
TheSpotlessPane
While we're on the subject. The way I have that class set up, I would be making a new thread every time I wanted a song to play (or even a sound effect for that matter), is this ok for a game where you might have two background tracks playing (ambient noises and music) as well as all of the sound effects? That would mean I would have created at least 3 extra threads. Seems like it would be the only way given the way I've done it. Is it no big deal for the computer to have that many separate threads running? Or is it generally bad practice to have this many threads executing at once?

EDIT: also, I would be obtaining the mixer and opening up a new line every time I wanted to play a new sound. I'm assuming this isn't good. Maybe I should have a super class that obtains the mixer and line, and sub class "Track" that gets the file and plays it through the one line that any other sounds are also being played through? I guess I'm just wondering if I have to worry about running out of lines if I'm opening a new one every time I want to play a sound. Are you able to play multiple tracks through a given line at the same time? Like if I had the line declared in the super class, and a new thread for each subclass that fed sound into that line...actually now that I'm thinking about it, I probably can't do this, since I would be feeding bit data into the line from two different threads...that's probably not good...

Also, I've been reading through the concurrency link, but it doesn't seem to directly mention anything about how to "close" a thread (at least not yet). Does the thread automatically close once it reaches the end of its task? Or could I be sucking up CPU power by creating threads and not disposing of them somehow when I no longer need them?
WavyVirus
WavyVirus
I would be making a new thread every time I wanted a song to play (or even a sound effect for that matter), is this ok for a game where you might have two background tracks playing (ambient noises and music) as well as all of the sound effects?[/quote]
This shouldn't be a problem - generally you don't want to create a thread unless you need to, but if it makes sense to do it then go ahead. Having a handful of threads running shouldn't be an issue (and in fact when using Java there are probably several other threads running in the background already for housekeeping tasks). Start to rethink things if you're creating a "large" number of threads (there's no black-and-white rule, but you probably don't need hundreds of threads running in a game, for example).

also, I would be obtaining the mixer and opening up a new line every time I wanted to play a new sound. I'm assuming this isn't good. Maybe I should have a super class that obtains the mixer and line, and sub class "Track" that gets the file and plays it through the one line that any other sounds are also being played through?[/quote]
I don't think it makes sense for "Track" to be a sub-class of a "mixer-and-line-obtainer" class - generally there should be an "is-a" relationship between subclass and superclass (i.e. Track "is a" MixerAndLineObtainer, which doesn't make much sense - it's more like Track "plays on a" Mixer or maybe Mixer "plays several" Tracks).

Perhaps it makes more sense in this situation to pass in a Mixer object to the track when you create it, which it will grab a line from and begin playing.

You could go the other way and have your own sound-playing class containing a Mixer, which has a method like SoundPlayer.play(fileName). When this is called it grabs a line from its internal Mixer object, creates a Track object (passing the Line and audio file into its constructor) and kicks off a thread to play the Track. This sound-playing class could then decide what to do if there are no lines free, or have more complicated behaviour such as having a line reserved for playing background music.

I guess I'm just wondering if I have to worry about running out of lines if I'm opening a new one every time I want to play a sound. Are you able to play multiple tracks through a given line at the same time?[/quote]
You probably do have to think about this... I haven't played with audio in Java myself, but you almost certainly have only one sound per line. The Mixer class has a getMaxLines() method to find out how many sounds can play simultaneously, and its getLine() method throws a "LineUnavailableException" if there are no lines free. You'll have to deal with this situation somehow (perhaps just failing to play the sound, perhaps retrying until there is a line free, perhaps killing an old sound which is playing and taking over its line).

Also, I've been reading through the concurrency link, but it doesn't seem to directly mention anything about how to "close" a thread (at least not yet). Does the thread automatically close once it reaches the end of its task? Or could I be sucking up CPU power by creating threads and not disposing of them somehow when I no longer need them?[/quote]
The thread closes when it reaches the end of its run() method. Of course the Thread object will still exist if you have a variable referring to it, it just won't represent an actual running thread any more - you can call isAlive() on a Thread object to see if it is still running.

To stop a thread, call the interrupt() method of the Thread object from your main thread - then, within the run() method of the Track thread itself, have something like if( Thread.interrupted() ) { return; } which is called at regular intervals (e.g. in your data-streaming loop somewhere). Notice we're not really terminating the Track thread from the main thread, we're just notifying it that we would like it to close when it is next convenient.
WavyVirus
WavyVirus

Very useful info, thanks for the thorough response.


No problem.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.