I want to playback short beep sounds (WAV files) on success and error of a GUI triggered action.
I came across javax.sound.sampled.Clip, which seemed to work. Here is the basic code I use:
clip.stop();
clip.setFramePosition(0);
clip.start();
This is executed after a button click triggers a database action. On success and error two different preloaded Clips are played.
But on the production machine (an old PC running Kubuntu 10.4) after some time (around 400+ executions or 2-4 hours) the clip refuses to 开发者_JS百科play. The stop method takes around 3 seconds to terminate and the following start action does not play any sound. Every following invocation of the code fails then without throwing exception or any other feedback. The only thing, that fixes this, is restarting the whole application.
My questions are: Is there any workaround for this? Does anyone else have the same problem? Or is there another framework I can use to play at least two different sounds (the Toolkit.beep() can only play one sound).
Don't be afraid to just recreate objects, the overhead is low. Instead of resetting the clips, try just creating new ones. You could cache the files, that would be a useful optimization. Reusing the clip objects is not.
Or you could try an alternative implementation [that is not restricted].
This is the top result in Google for 'java play wav files':
http://www.anyexample.com/programming/java/java_play_wav_sound_file.xml
It simplifies things down to a single call:
new AePlayWave("test.wav").start();
Just add this class to your codebase:
import java.io.File;
import java.io.IOException;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.FloatControl;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;
import javax.sound.sampled.UnsupportedAudioFileException;
public class AePlayWave extends Thread {
private String filename;
private Position curPosition;
private final int EXTERNAL_BUFFER_SIZE = 524288; // 128Kb
enum Position {
LEFT, RIGHT, NORMAL
};
public AePlayWave(String wavfile) {
filename = wavfile;
curPosition = Position.NORMAL;
}
public AePlayWave(String wavfile, Position p) {
filename = wavfile;
curPosition = p;
}
public void run() {
File soundFile = new File(filename);
if (!soundFile.exists()) {
System.err.println("Wave file not found: " + filename);
return;
}
AudioInputStream audioInputStream = null;
try {
audioInputStream = AudioSystem.getAudioInputStream(soundFile);
} catch (UnsupportedAudioFileException e1) {
e1.printStackTrace();
return;
} catch (IOException e1) {
e1.printStackTrace();
return;
}
AudioFormat format = audioInputStream.getFormat();
SourceDataLine auline = null;
DataLine.Info info = new DataLine.Info(SourceDataLine.class, format);
try {
auline = (SourceDataLine) AudioSystem.getLine(info);
auline.open(format);
} catch (LineUnavailableException e) {
e.printStackTrace();
return;
} catch (Exception e) {
e.printStackTrace();
return;
}
if (auline.isControlSupported(FloatControl.Type.PAN)) {
FloatControl pan = (FloatControl) auline
.getControl(FloatControl.Type.PAN);
if (curPosition == Position.RIGHT)
pan.setValue(1.0f);
else if (curPosition == Position.LEFT)
pan.setValue(-1.0f);
}
auline.start();
int nBytesRead = 0;
byte[] abData = new byte[EXTERNAL_BUFFER_SIZE];
try {
while (nBytesRead != -1) {
nBytesRead = audioInputStream.read(abData, 0, abData.length);
if (nBytesRead >= 0)
auline.write(abData, 0, nBytesRead);
}
} catch (IOException e) {
e.printStackTrace();
return;
} finally {
auline.drain();
auline.close();
}
}
}
So how i fixed it:
I basically followed the hint from Charles Goodwin, but i changed the AePlayWave class to implement runnable and use it with a thread pool (to avoid overhead from starting new Threads all the time). Also, i use URLs and not Files to use resources from within the packed JAR file. The AePlayWave Object is created when setup is done (the files are chosen) or settings change. There is an instance for every sound I want the application to play. The listener methods for the events then trigger the pool to run the specific AePlayWave instance for that events sound. The rest is basically the same.
There are only two inconvenient issues:
1.) on weak machines, the ending of a WAV is not always played. When sounds are very short (like 100ms beeps), this might lead to no sound being played at all! Thats why i added 500 ms of silence to the end of each sound i'd like to play. It's a workaround, but it helps and for now it seems to be the best and most stable approach.
2.) If more than one sound is played (because of very quick repetition) the sounds overlap and you hear a change in tune and volume. This is ok in my case but might be annoying for other uses.
It is already running on the productive system. If any errors are reported to me, i will edit this post to keep you up to date.
Now here is the (basically reduced) sourcecode:
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;
import javax.sound.sampled.UnsupportedAudioFileException;
public class AudibleListener implements SomeListener {
private Runnable successRunner;
private Runnable failRunner;
ExecutorService pool = Executors.newCachedThreadPool();
/**
* Call this after initialization and after every change in your config at runtime.
*/
public void reloadSettings() {
// put your configuration here
this.successRunner = new WavePlayer(this.getClass().getResource("success.wav"));
this.failRunner = new WavePlayer(this.getClass().getResource("fail.wav"));
}
/**
* Call this to savely shutdown the thread pool.
*/
public void shutdown() {
this.pool.shutdown();
}
/**
* Listener method called on success.
*/
public void eventSuccess() {
this.pool.execute(this.successRunner);
}
/**
* Listener method called on fail.
*/
public void eventFailed() {
this.pool.execute(this.failRunner);
}
private class WavePlayer implements Runnable {
private final int EXTERNAL_BUFFER_SIZE = 524288; // 128Kb
private URL soundFile;
public WavePlayer(URL soundFile) {
this.soundFile = soundFile;
}
@Override
public void run() {
try {
// check if the URL is still accessible!
this.soundFile.openConnection().connect();
this.soundFile.openStream().close();
} catch (Exception e) {
return;
}
AudioInputStream audioInputStream = null;
try {
audioInputStream = AudioSystem
.getAudioInputStream(this.soundFile);
} catch (UnsupportedAudioFileException e) {
return;
} catch (IOException e) {
return;
}
AudioFormat format = audioInputStream.getFormat();
SourceDataLine auline = null;
DataLine.Info info = new DataLine.Info(SourceDataLine.class, format);
try {
auline = (SourceDataLine) AudioSystem.getLine(info);
auline.open(format);
} catch (LineUnavailableException e) {
return;
} catch (Exception e) {
return;
}
auline.start();
int nBytesRead = 0;
byte[] abData = new byte[this.EXTERNAL_BUFFER_SIZE];
try {
while (nBytesRead != -1) {
nBytesRead = audioInputStream
.read(abData, 0, abData.length);
if (nBytesRead >= 0) {
auline.write(abData, 0, nBytesRead);
}
}
} catch (IOException e) {
return;
} finally {
auline.drain();
auline.close();
}
}
}
}
Cheers and thanks so far for all the help!
P.
Update:
This is now running for the last 72 hours without any errors! Looks like we made it!
精彩评论