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 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!
Related
So my application should play the WAV file every time I click on the panel. But the problem right now is, it waits for the first one to finish before it plays the second one. I want to be able to have them play simultaneously.
The reason I put Thread.sleep(500) is because if I don't, then it won't play the sound at all :(
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.FloatControl;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.UnsupportedAudioFileException;
import javax.swing.JFrame;
#SuppressWarnings("serial")
public class SoundEffectPlayer extends JFrame {
/*
* Jframe stuff
*/
public SoundEffectPlayer() {
this.setSize(400, 400);
this.setTitle("Mouse Clicker");
this.addMouseListener(new Clicker());
this.setVisible(true);
}
private class Clicker extends MouseAdapter {
public void mouseClicked(MouseEvent e) {
try {
playSound(1);
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
}
/*
* Directory of your sound files
* format is WAV
*/
private static final String DIRECTORY = "file:///C:/Users/Jessica/Desktop/audio/effects/sound 1.wav";
/*
* The volume for sound effects
*/
public static float soundEffectsVolume = 0.00f;
/*
* Loads the sound effect files from cache
* into the soundEffects array.
*/
public void playSound(int ID) throws InterruptedException {
try {
System.out.println("playing");
Clip clip;
URL url = new URL(DIRECTORY);
AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(url);
clip = AudioSystem.getClip();
clip.open(audioInputStream);
clip.setFramePosition(0);
FloatControl gainControl = (FloatControl) clip.getControl(FloatControl.Type.MASTER_GAIN);
gainControl.setValue(soundEffectsVolume);
clip.start();
System.out.println("played");
Thread.sleep(3000);
System.out.println("closing");
} catch (MalformedURLException e) {
System.out.println("Sound effect not found: "+ID);
e.printStackTrace();
return;
} catch (UnsupportedAudioFileException e) {
System.out.println("Unsupported format for sound: "+ID);
return;
} catch (LineUnavailableException e) {
e.printStackTrace();
return;
} catch (IOException e) {
e.printStackTrace();
return;
}
}
public static void main(String[] args) throws InterruptedException {
new SoundEffectPlayer();
}
}
Update: Okay so I got them to play simeutaneously, but I want to make the the thread close when the Clip is done playing, instead of making the thread wait 500ms
How can I do that?
I have always run multiple sounds like this. I don't spawn a new thread as I guess javaSound already runs clips in an another threads. Main "game loop" may continue doing its own stuff. App may register listeners for callbacks or use getters to see what clips are doing.
Sometimes if we are to make multimedia or game application its easier to just use getters. Running gameloop 30-60fps gives enough granularity for most cases and we have a total control of what happens and when. This little testapp playbacks two wav files, first is run once, second is started after 3sec, second loops.
// java -cp ./classes SoundTest1 clip1=sound1.wav clip2=sound2.wav
import java.util.*;
import java.io.*;
import java.net.URL;
import javax.sound.sampled.*;
public class SoundTest1 {
public Clip play(String filename, boolean autostart, float gain) throws Exception {
AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(new File(filename));
Clip clip = AudioSystem.getClip();
clip.open(audioInputStream);
clip.setFramePosition(0);
// values have min/max values, for now don't check for outOfBounds values
FloatControl gainControl = (FloatControl)clip.getControl(FloatControl.Type.MASTER_GAIN);
gainControl.setValue(gain);
if(autostart) clip.start();
return clip;
}
public static void main(String[] args) throws Exception {
Map<String,String> params = parseParams(args);
SoundTest1 test1 = new SoundTest1();
Clip clip1 = test1.play(params.get("clip1"), true, -5.0f);
Clip clip2 = test1.play(params.get("clip2"), false, 5.0f);
final long duration=Long.MAX_VALUE;
final int interval=500;
float clip2IncGain=0.4f;
for(long ts=0; ts<duration; ts+=interval) {
System.out.println(String.format("clip1=%d/%d(%.2f), clip2=%d/%d(%.2f)"
,clip1.getFramePosition(), clip1.getFrameLength()
,((FloatControl)clip1.getControl(FloatControl.Type.MASTER_GAIN)).getValue()
,clip2.getFramePosition(), clip2.getFrameLength()
,((FloatControl)clip2.getControl(FloatControl.Type.MASTER_GAIN)).getValue()
));
if (ts>6000 && !clip2.isRunning()) {
clip2.setFramePosition(0);
clip2.start();
}
if (!clip1.isRunning()) {
clip1.close();
}
if(ts % 2000 == 0) {
// values have min/max values, for now don't check for outOfBounds values
FloatControl gainControl = (FloatControl)clip2.getControl(FloatControl.Type.MASTER_GAIN);
float oldVal=gainControl.getValue();
clip2IncGain = oldVal>=5.5 ? clip2IncGain*-1
: oldVal<=-5.5 ? clip2IncGain*-1
: clip2IncGain;
gainControl.setValue(oldVal+clip2IncGain);
}
Thread.sleep(interval);
}
}
private static Map<String,String> parseParams(String[] args) {
Map<String,String> params = new HashMap<String,String>();
for(String arg : args) {
int delim = arg.indexOf('=');
if (delim<0) params.put("", arg.trim());
else if (delim==0) params.put("", arg.substring(1).trim());
else params.put(arg.substring(0, delim).trim(), arg.substring(delim+1).trim() );
}
return params;
}
}
See JavaSound documentation for more information.
Try checking the source code of this open source soundboard program: DBoard.
You are specifically interested in using the MediaPlayer class. You can call it using
(new Thread(new MediaPlayer(PATHTOFILE)).start();
Problem; Only hearing one sound clip when executed. After one sound has played the other doesn't play & neither can play at the same time.
Result; To be able to play 2 sounds at the same time.
Code:
import java.io.*;
import javax.sound.sampled.*;
public class ThreadPlay extends Thread {
private String filename; // The name of the file to play
private boolean finished; // A flag showing that the thread has finished
private ThreadPlay(String fname) {
filename = fname;
finished = false;
}
public static void main(String[] args) {
ThreadPlay s1 = new ThreadPlay("soundClip1.wav");
ThreadPlay s2 = new ThreadPlay("soundClip2.wav");
s1.start();
s2.start();
while (!s1.finished || !s2.finished);
System.exit(0); // Java Sound bug fix...
}
public void run() {
try {
File file = new File(filename);
AudioInputStream stream = AudioSystem.getAudioInputStream(file);
AudioFormat format = stream.getFormat();
DataLine.Info info = new DataLine.Info(Clip.class, format);
Clip clip = (Clip)AudioSystem.getLine(info);
clip.open(stream);
clip.start();
Thread.sleep(100);
while (clip.isRunning()) { Thread.sleep(100); }
clip.close();
}
catch (Exception e) { }
finished = true;
}
}
Audio Lines:
AudioSystem.getMixerInfo() results in:
[Ljavax.sound.sampled.Mixer$Info;#52cc8049
Array length: 7
Contents:
PulseAudio Mixer, version 0.02
default [default], version 4.4.0-66-generic
PCH [plughw:0,0], version 4.4.0-66-generic
PCH [plughw:0,1], version 4.4.0-66-generic
PCH [plughw:0,3], version 4.4.0-66-generic
PCH [plughw:0,7], version 4.4.0-66-generic
Port PCH [hw:0], version 4.4.0-66-generic
For each mixer, AudioSystem.getMixer(AudioSystem.getMixerInfo()[x])
Results:
org.classpath.icedtea.pulseaudio.PulseAudioMixer#685f4c2e
com.sun.media.sound.DirectAudioDevice#7a07c5b4
com.sun.media.sound.DirectAudioDevice#5ce65a89
com.sun.media.sound.DirectAudioDevice#1de0aca6
com.sun.media.sound.DirectAudioDevice#443b7951
com.sun.media.sound.DirectAudioDevice#45283ce2
com.sun.media.sound.PortMixer#4d76f3f8
Imports:
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
Run method:
public void run() {
try {
AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(this.getClass().getResource("NameOfFile.wav"));
Clip clip = AudioSystem.getClip();
clip.open(audioInputStream);
clip.start();
clip.loop(Clip.LOOP_CONTINUOUSLY); // There are several different amounts of time you can loop it, so you can change this if you want, or you can just use clip.stop() whenever you want.
} catch (Exception ex) {
ex.printStackTrace();
}
}
If you use this and piece of code over multiple threads, it should work. If I am correct in assuming that you are initiating this piece of code twice, once for each thread, then this should work. I hope that this helps.
Fixed:
public void run() {
try {
File file = new File(filename);
AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(file);
DataLine.Info info = new DataLine.Info(Clip.class, audioInputStream.getFormat());
Clip clip = (Clip)AudioSystem.getLine(info);
clip.open(audioInputStream);
clip.start();
clip.loop(Clip.LOOP_CONTINUOUSLY); // There are several different amounts of time you can loop it, so you can change this if you want, or you can just use clip.stop() whenever you want.
} catch (Exception ex) {
ex.printStackTrace();
}
finished = true;
}
Also, if you're running Ubuntu like myself you need to remove OpenJDK/OpenJRE and set SunJDK/SunJRE as Default.
This battleship game is the first multi-threaded application I've tried to write and it worked perfectly up until I added the multi-threading,which is only used for sound effects. It is all in one single class, the AudioManager.
I'm pretty sure I just lack experience and or/understanding regarding concurrency, even though I've read the java tutorials etc. I think I just need a little help to get it to click.
Anyway the game runs fine until enough sounds have been played that it runs out of memory and gives me this error:
Exception in thread "AWT-EventQueue-0" java.lang.OutOfMemoryError: unable to create new native thread
I was creating a new thread for each sound effect to play on because I didn't want the gui to wait for the sound to finish, and because sounds are often played very close to each other and I didn't want them conflicting on the same thread if they overlapped. The problem, I think, is that I'm not sure how to close each thread after the sound is played without stalling the main thread.
Here is the class with all the sound code. The sounds are played using the setSound() method, which sets the sound to be played and then starts a new thread with the SoundPlayer inner class for the Runnable. Thanks in advance:
import java.io.IOException;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;
import javax.sound.sampled.UnsupportedAudioFileException;
public class AudioManager {
private static Thread backgroundThread = new Thread();
private static int loopCounter = 2;
private static Clip clip;
private static String[] backgroundFiles = {
"/40_Avalon.wav","/13_Glatisant.wav",
"/31_Lying_In_Deceit.wav","/43_Return_to_Base.wav"};
private static String[] files = {
"/bigboom.wav","/Robot_blip.wav",
"/battleStations.WAV","/beep1.wav",
"/button-47.wav","/button-35.wav",
"/beep-23.wav","/Sonar_pings.wav",
"/button-21.wav","/SONAR.WAV"};
private static AudioInputStream currentBackgroundMusic;
private static AudioInputStream currentSound;
private static boolean backgroundOn = false;
private static boolean canStart = true;
private static AudioInputStream loadSound(int s){
AudioInputStream stream = null;
try {
stream = AudioSystem.getAudioInputStream(AudioManager.class.getClass().getResource(files[s]));
} catch (UnsupportedAudioFileException | IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return stream;
}
private static AudioInputStream loadBackground(int s){
AudioInputStream stream = null;
try {
stream = AudioSystem.getAudioInputStream(AudioManager.class.getClass().getResource(backgroundFiles[s]));
} catch (UnsupportedAudioFileException | IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return stream;
}
public static void setSound(int s){
currentSound = loadSound(s);
Thread thread = new Thread(new SoundPlayer());
thread.start();
}
private static void continueMusic(){
setBackgroundMusic(loopCounter);
loopCounter++;
if(loopCounter > 3) loopCounter = 0;
}
public static void playSound(){
try {
clip = AudioSystem.getClip();
clip.open(currentSound);
} catch (LineUnavailableException | IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
clip.start();
}
public static void setBackgroundMusic(int s){
if (backgroundOn) {
backgroundOn = false;
canStart = false;
try {
backgroundThread.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
currentBackgroundMusic = loadBackground(s);
backgroundThread = new Thread(new MusicPlayer());
backgroundOn = true;
backgroundThread.start();
canStart = true;
}
private static void playSound2(AudioInputStream audio) {
AudioFormat audioFormat = audio.getFormat();
SourceDataLine line = null;
DataLine.Info info = new DataLine.Info(SourceDataLine.class,audioFormat);
try{
line = (SourceDataLine) AudioSystem.getLine(info);
line.open(audioFormat);
}
catch (Exception e)
{
e.printStackTrace();
}
line.start();
int nBytesRead = 0;
byte[] abData = new byte[128000];
while (nBytesRead != -1 && backgroundOn)
{
try{
nBytesRead = audio.read(abData, 0, abData.length);
} catch (IOException e){
e.printStackTrace();
}
if (nBytesRead == -1) break;
line.write(abData, 0, nBytesRead);
}
line.drain();
line.stop();
line.close();
line = null;
backgroundOn = false;
}
private static class MusicPlayer implements Runnable{
#Override
public void run() {
playSound2(currentBackgroundMusic);
}
}
private static class SoundPlayer implements Runnable{
#Override
public void run() {
playSound();
}
}
public static void loopMusic(){
Thread loop = new Thread(new Runnable(){
#Override
public void run() {
while(true){
if(backgroundThread.isAlive()){
try {
backgroundThread.join(0);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} else if (canStart){
continueMusic();
}
}
}});
loop.start();
}
public static void reset(){
loopCounter = 2;
}
}
First of all, thank you to everyone who posted answers. You all helped a lot and the solution was a combination of your answers. I've decided to post my own answer with the solution I came up with for the benefit of others who may have the same issue.
It turns out, I was indeed creating too many threads and the OS only lets Java have a certain amount of memory space. So I fixed that by using an ExecutorService.
However, I was still having the same problem, even though I wasn't explicitly creating lots of new threads. Why? because I was creating new Clips to play sounds.
I think the Clips are somehow creating threads to play sounds on, so they can play without locking up the program or GUI (which I didn't understand before). So, to solve the problem once and for all, and also to allow my game to play the exact same sound rapidly in succession without clipping or having to wait for the previous sound to finish, I got rid of the executor and created ten Clips for each sound and that's all.
When a sound is played, it increments an index so that the next time that sound is played, it will actually use a different clip (but loaded with the same exact sound) and it prepares the next clip to play too.
My game not longer creates excessive threads or clips and runs great! The updated code is below, along with a couple of tests that I used to find out what was going on:
import java.io.IOException;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.LineEvent;
import javax.sound.sampled.LineListener;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;
import javax.sound.sampled.UnsupportedAudioFileException;
public class AudioManager {
private static ExecutorService backgroundPool = Executors.newFixedThreadPool(1);
private static Future<?> backgroundStatus;
private static int loopCounter = 2;
private static String[] backgroundFiles = {
"/40_Avalon.wav","/13_Glatisant.wav",
"/31_Lying_In_Deceit.wav","/43_Return_to_Base.wav"};
private static String[] files = {
"/bigboom.wav","/Robot_blip.wav",
"/battleStations.WAV","/beep1.wav",
"/button-47.wav","/button-35.wav",
"/beep-23.wav","/Sonar_pings.wav",
"/button-21.wav","/SONAR.WAV"};
private static AudioInputStream currentBackgroundMusic;
private static boolean backgroundOn = false;
private static boolean canStart = true;
private static int[] clipIndex = new int[10];
private static Clip[][] clips = new Clip[10][10];
private static void initializeClips(int sound){
clipIndex[sound] = 0;
for (int i = 0 ; i < 10 ; i++)
try {
clips[sound][i] = AudioSystem.getClip();
clips[sound][i].open(loadSound(sound));
clips[sound][i].addLineListener(new LineListener(){
#Override
public void update(LineEvent event) {
if(event.getType() == javax.sound.sampled.LineEvent.Type.STOP){
clips[sound][clipIndex[sound]].setFramePosition(0);
}
}});
} catch (LineUnavailableException | IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private static AudioInputStream loadSound(int s){
AudioInputStream stream = null;
try {
stream = AudioSystem.getAudioInputStream(AudioManager.class.getClass().getResource(files[s]));
} catch (UnsupportedAudioFileException | IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return stream;
}
private static AudioInputStream loadBackground(int s){
AudioInputStream stream = null;
try {
stream = AudioSystem.getAudioInputStream(AudioManager.class.getClass().getResource(backgroundFiles[s]));
} catch (UnsupportedAudioFileException | IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return stream;
}
public static void setSound(int s){
if(clips[s] == null){
initializeClips(s);
}
playSound(s);
}
private static void continueMusic(){
setBackgroundMusic(loopCounter);
loopCounter++;
if(loopCounter > 3) loopCounter = 0;
}
public static void playSound(int sound){
if(clips[sound][0] == null){
initializeClips(sound);
}
clips[sound][clipIndex[sound]].start();
clipIndex[sound]++;
if(clipIndex[sound] == 10){
clipIndex[sound] = 0;
}
clips[sound][clipIndex[sound]].drain();
clips[sound][clipIndex[sound]].flush();
clips[sound][clipIndex[sound]].setFramePosition(0);
}
public static void setBackgroundMusic(int s){
canStart = false;
if (backgroundOn) {
backgroundOn = false;
}
currentBackgroundMusic = loadBackground(s);
backgroundStatus = backgroundPool.submit(new MusicPlayer());
canStart = true;
}
private static void playSound2(AudioInputStream audio) {
backgroundOn = true;
AudioFormat audioFormat = audio.getFormat();
SourceDataLine line = null;
DataLine.Info info = new DataLine.Info(SourceDataLine.class,audioFormat);
try{
line = (SourceDataLine) AudioSystem.getLine(info);
line.open(audioFormat);
}
catch (Exception e)
{
e.printStackTrace();
}
line.start();
int nBytesRead = 0;
byte[] abData = new byte[128000];
while (nBytesRead != -1 && backgroundOn){
try{
nBytesRead = audio.read(abData, 0, abData.length);
} catch (IOException e){
e.printStackTrace();
}
if (nBytesRead == -1) break;
line.write(abData, 0, nBytesRead);
}
line.drain();
line.stop();
line.close();
line = null;
backgroundOn = false;
}
private static class MusicPlayer implements Runnable{
#Override
public void run() {
playSound2(currentBackgroundMusic);
}
}
public static void loopMusic(){
Thread loop = new Thread(new Runnable(){
#Override
public void run() {
while(true){
if(backgroundStatus.isDone() && canStart){
continueMusic();
}
}
}});
loop.start();
}
public static void reset(){
loopCounter = 2;
}
}
The following is a test that will tell you how many threads your operating system lets the JVM create. Once you get the error, just look at the last number that was printed to the console.
public class Test1 {
static long count = 0L;
public static void main(String[] args) {
while(true){
count ++;
System.out.println(count);
new Thread(new Runnable(){
#Override
public void run() {
try {
Thread.sleep(60000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}}).start();
}
}
}
And the following is a test that does the same thing, except by creating clips and opening resources. Notice that the clips themselves don't require a thread, but once you open them they do. You should get the same number (or close) before the error with each test. Of course, you will have to provide your own sound file to run the second one.
import java.io.IOException;
import java.util.ArrayList;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.UnsupportedAudioFileException;
import audio.AudioManager;
public class Test2 {
static long count = 0L;
static ArrayList<Clip> clips = new ArrayList<>();
public static void main(String[] args) {
while(true){
count ++;
System.out.println(count);
try {
Clip clip1 = AudioSystem.getClip();
clip1.open(loadSound());
clips.add(clip1);
} catch (LineUnavailableException | IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public static AudioInputStream loadSound(){
AudioInputStream stream = null;
try {
stream = AudioSystem.getAudioInputStream(AudioManager.class.getClass().getResource("/bigboom.wav"));
} catch (UnsupportedAudioFileException | IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return stream;
}
}
"Clip.start() method returns immediately, and the system playbacks the sound file in a background thread." (from this question which discusses how to play sounds after each other).
Since the threads you create effectively run "make clip object and start it", they actually do hardly anything. Even the I/O operations (opening the stream) are done at forehand (in the main GUI thread).
Your assumption that the GUI has to wait for a clip to finish does not appear to be valid. And I doubt they can be conflicting on the same thread if they overlap. Can you confirm the GUI is more responsive with multi-threading? My guess is that it is actually less responsive since creating and starting new threads is not cheap.
Well, as you said, your problem is that your threads are still either running or they stopped but the memory they used is not being released by java's garbage collector (GC).
A thread stops when their run() method returns (finishes) or throws an exception. If that happens, and there is NO REFERENCES to that thread anywhere in your code, it will be deleted by the GC eventually (if your program needs some memory, for example).
So, first, check that those threads you create aren't in an nasty infinite loop like the one below:
public void run() {
while(true){
//...
//Some code
//...
}
}
and once you are sure they are ending propertly after playing the sound, make sure they're not being referenced anywhere else in your code (a part of your program still maintaining a pointer to that object).
One last note. Consider checking the Thread Pool Pattern for this kind of things. I prefer it over just creating a new thread for each new task. It can be less messy and more efficient.
You need to get rid of all of these static variables. This code is not thread safe, as you are effectively trying to use all of the static variables as a shared state.
I would recommend that you start passing state around your runnable objects. If they need to communicate with each other, use the built in concurrency utilities that come with Java. If that is not sufficient, use synchronization and mutate object state.
After a cursory look at your code I can tell that your code will suffer from memory visibility issues. You could try to fix this by making your static variables volatile, but I doubt that it will be sufficient. It would make much cleaner code if you encapsulate the state within individual objects.
Before you do anything further, I would step back and try to spend 10-15 minutes and come up with an overall design. What objects will you have, what responsibilities will each one have and what state will they have (immutable/mutable). How will those objects synchronize with each other (if they run in different threads?
Your code is not thread safe.
Wait, let me get to that later. Your OOME is being caused by too many threads being created. Your current experience with concurrency is just reading online tutorials, right? Those only cover how to synchronize and share, and not how threads work.
Threads have a SIGNIFICANT setup and teardown overhead, and what you're doing is called unbounded thread creation, meaning you just create more and more threads until the system can't support it anymore.
First off, you can fix your problem using thread pooling, or more specifically, an ExecutorService to execute tasks concurrently. A cached thread pool is all you need.
Secondly, you have a ton of shared state fields. You can fix this by making an immutable wrapper for a single state snapshot every operation on an AtomicReference (or something of the like), or you can synchronize access to your fields.
Third, please get rid of all of your static fields and methods. I don't see it as appropriate in this case, although there's not enough code to validate my concern.
In my swing application i am using a class to play a sound when the mouse is clicked. The problem i have is when i call the class the sound is played onetime and when suddenly another button is clicked it doesn't play the sound. I tried giving a delay in my code but stilll id doesn't work as i expected. Is it something to do with threads ?? I am not good at threads so please tell me how to do that. The code i am using is as below,
package utilities;
import java.applet.AudioClip;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.logging.Level;
import java.util.logging.Logger;
import sun.audio.AudioData;
import sun.audio.AudioPlayer;
import sun.audio.AudioStream;
import sun.audio.ContinuousAudioDataStream;
public class Tone {`
File wavFile = new File("sounds/Windows Default.wav");
URL urlClick = Tone.class.getResource("/sounds/WindowsDefault.wav");
AudioClip sound;
public void sound() {
AudioStream as = null;
try {
InputStream in = this.getClass().getResourceAsStream("/sounds/WindowsDefault.wav");
as = new AudioStream(in);
AudioData data = as.getData();
// Create ContinuousAudioDataStream.
ContinuousAudioDataStream cas = new ContinuousAudioDataStream (data);
//System.out.println(as.getLength());
AudioPlayer.player.start(cas);
//System.out.println(urlClick);
//sound = Applet.newAudioClip(urlClick);
//this.wait(1000);
for(int i =0;i<100000;i++){
double k = Math.pow(i, 5);
if(i==99999){
AudioPlayer.player.stop(cas);
return;
}
}
// sound.play();
String relativeDirectory = System.getProperty("user.dir");
System.out.println(relativeDirectory);
} catch (IOException ex) {
Logger.getLogger(Tone.class.getName()).log(Level.SEVERE, null, ex);
} finally {
try {
as.close();
} catch (IOException ex) {
Logger.getLogger(Tone.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
public static void main(String[] args) {
Tone tone = new Tone();
tone.sound();
System.out.println("done");
}
}
--EDIT--
The reason why i used the continuousaudiostream is because the sound was not happening at all when i use this aleast hear some sound. Even when i used AudioStream the problem was there..
1) Better not use hidden classes/api - as you already have read in the link you got in your other question :-)
2) stop/close an already running sound before starting/re-open again, in public api something like:
Clip clip;
private void doPlay(final String url) {
try {
stopPlay();
AudioInputStream inputStream = AudioSystem
.getAudioInputStream(getClass().getResourceAsStream(url));
clip = AudioSystem.getClip();
clip.open(inputStream);
clip.start();
} catch (Exception e) {
stopPlay();
System.err.println(e.getMessage());
}
}
private void stopPlay() {
if (clip != null) {
clip.stop();
clip.close();
clip = null;
}
}
(Note: completely closed for illustration only, refine the logic to load once and then stop/start again)
It looks like the ContinuousAudioDataStream only resets after the end of the file was reached, so it could be that the second button click happened before this occurred. Maybe try resetting the stream
I want to be able to play sound files in my program. Where should I look?
I wrote the following code that works fine. But I think it only works with .wav format.
public static synchronized void playSound(final String url) {
new Thread(new Runnable() {
// The wrapper thread is unnecessary, unless it blocks on the
// Clip finishing; see comments.
public void run() {
try {
Clip clip = AudioSystem.getClip();
AudioInputStream inputStream = AudioSystem.getAudioInputStream(
Main.class.getResourceAsStream("/path/to/sounds/" + url));
clip.open(inputStream);
clip.start();
} catch (Exception e) {
System.err.println(e.getMessage());
}
}
}).start();
}
A bad example:
import sun.audio.*; //import the sun.audio package
import java.io.*;
//** add this into your application code as appropriate
// Open an input stream to the audio file.
InputStream in = new FileInputStream(Filename);
// Create an AudioStream object from the input stream.
AudioStream as = new AudioStream(in);
// Use the static class member "player" from class AudioPlayer to play
// clip.
AudioPlayer.player.start(as);
// Similarly, to stop the audio.
AudioPlayer.player.stop(as);
I didn't want to have so many lines of code just to play a simple damn sound. This can work if you have the JavaFX package (already included in my jdk 8).
private static void playSound(String sound){
// cl is the ClassLoader for the current class, ie. CurrentClass.class.getClassLoader();
URL file = cl.getResource(sound);
final Media media = new Media(file.toString());
final MediaPlayer mediaPlayer = new MediaPlayer(media);
mediaPlayer.play();
}
Notice : You need to initialize JavaFX. A quick way to do that, is to call the constructor of JFXPanel() once in your app :
static{
JFXPanel fxPanel = new JFXPanel();
}
For whatever reason, the top answer by wchargin was giving me a null pointer error when I was calling this.getClass().getResourceAsStream().
What worked for me was the following:
void playSound(String soundFile) {
File f = new File("./" + soundFile);
AudioInputStream audioIn = AudioSystem.getAudioInputStream(f.toURI().toURL());
Clip clip = AudioSystem.getClip();
clip.open(audioIn);
clip.start();
}
And I would play the sound with:
playSound("sounds/effects/sheep1.wav");
sounds/effects/sheep1.wav was located in the base directory of my project in Eclipse (so not inside the src folder).
For playing sound in java, you can refer to the following code.
import java.io.*;
import java.net.URL;
import javax.sound.sampled.*;
import javax.swing.*;
// To play sound using Clip, the process need to be alive.
// Hence, we use a Swing application.
public class SoundClipTest extends JFrame {
public SoundClipTest() {
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setTitle("Test Sound Clip");
this.setSize(300, 200);
this.setVisible(true);
try {
// Open an audio input stream.
URL url = this.getClass().getClassLoader().getResource("gameover.wav");
AudioInputStream audioIn = AudioSystem.getAudioInputStream(url);
// Get a sound clip resource.
Clip clip = AudioSystem.getClip();
// Open audio clip and load samples from the audio input stream.
clip.open(audioIn);
clip.start();
} catch (UnsupportedAudioFileException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (LineUnavailableException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
new SoundClipTest();
}
}
I created a game framework sometime ago to work on Android and Desktop, the desktop part that handle sound maybe can be used as inspiration to what you need.
https://github.com/hamilton-lima/jaga/blob/master/jaga%20desktop/src-desktop/com/athanazio/jaga/desktop/sound/Sound.java
Here is the code for reference.
package com.athanazio.jaga.desktop.sound;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
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 Sound {
AudioInputStream in;
AudioFormat decodedFormat;
AudioInputStream din;
AudioFormat baseFormat;
SourceDataLine line;
private boolean loop;
private BufferedInputStream stream;
// private ByteArrayInputStream stream;
/**
* recreate the stream
*
*/
public void reset() {
try {
stream.reset();
in = AudioSystem.getAudioInputStream(stream);
din = AudioSystem.getAudioInputStream(decodedFormat, in);
line = getLine(decodedFormat);
} catch (Exception e) {
e.printStackTrace();
}
}
public void close() {
try {
line.close();
din.close();
in.close();
} catch (IOException e) {
}
}
Sound(String filename, boolean loop) {
this(filename);
this.loop = loop;
}
Sound(String filename) {
this.loop = false;
try {
InputStream raw = Object.class.getResourceAsStream(filename);
stream = new BufferedInputStream(raw);
// ByteArrayOutputStream out = new ByteArrayOutputStream();
// byte[] buffer = new byte[1024];
// int read = raw.read(buffer);
// while( read > 0 ) {
// out.write(buffer, 0, read);
// read = raw.read(buffer);
// }
// stream = new ByteArrayInputStream(out.toByteArray());
in = AudioSystem.getAudioInputStream(stream);
din = null;
if (in != null) {
baseFormat = in.getFormat();
decodedFormat = new AudioFormat(
AudioFormat.Encoding.PCM_SIGNED, baseFormat
.getSampleRate(), 16, baseFormat.getChannels(),
baseFormat.getChannels() * 2, baseFormat
.getSampleRate(), false);
din = AudioSystem.getAudioInputStream(decodedFormat, in);
line = getLine(decodedFormat);
}
} catch (UnsupportedAudioFileException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (LineUnavailableException e) {
e.printStackTrace();
}
}
private SourceDataLine getLine(AudioFormat audioFormat)
throws LineUnavailableException {
SourceDataLine res = null;
DataLine.Info info = new DataLine.Info(SourceDataLine.class,
audioFormat);
res = (SourceDataLine) AudioSystem.getLine(info);
res.open(audioFormat);
return res;
}
public void play() {
try {
boolean firstTime = true;
while (firstTime || loop) {
firstTime = false;
byte[] data = new byte[4096];
if (line != null) {
line.start();
int nBytesRead = 0;
while (nBytesRead != -1) {
nBytesRead = din.read(data, 0, data.length);
if (nBytesRead != -1)
line.write(data, 0, nBytesRead);
}
line.drain();
line.stop();
line.close();
reset();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
It works for me. Simple variant
public void makeSound(){
File lol = new File("somesound.wav");
try{
Clip clip = AudioSystem.getClip();
clip.open(AudioSystem.getAudioInputStream(lol));
clip.start();
} catch (Exception e){
e.printStackTrace();
}
}
There is an alternative to importing the sound files which works in both applets and applications: convert the audio files into .java files and simply use them in your code.
I have developed a tool which makes this process a lot easier. It simplifies the Java Sound API quite a bit.
http://stephengware.com/projects/soundtoclass/
I'm surprised nobody suggested using Applet. Use Applet. You'll have to supply the beep audio file as a wav file, but it works. I tried this on Ubuntu:
package javaapplication2;
import java.applet.Applet;
import java.applet.AudioClip;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
public class JavaApplication2 {
public static void main(String[] args) throws MalformedURLException {
File file = new File("/path/to/your/sounds/beep3.wav");
URL url = null;
if (file.canRead()) {url = file.toURI().toURL();}
System.out.println(url);
AudioClip clip = Applet.newAudioClip(url);
clip.play();
System.out.println("should've played by now");
}
}
//beep3.wav was available from: http://www.pacdv.com/sounds/interface_sound_effects/beep-3.wav
import java.net.URL;
import java.net.MalformedURLException;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.Clip;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.UnsupportedAudioFileException;
import java.io.IOException;
import java.io.File;
public class SoundClipTest{
//plays the sound
public static void playSound(final String path){
try{
final File audioFile=new File(path);
AudioInputStream audioIn=AudioSystem.getAudioInputStream(audioFile);
Clip clip=AudioSystem.getClip();
clip.open(audioIn);
clip.start();
long duration=getDurationInSec(audioIn);
//System.out.println(duration);
//We need to delay it otherwise function will return
//duration is in seconds we are converting it to milliseconds
Thread.sleep(duration*1000);
}catch(LineUnavailableException | UnsupportedAudioFileException | MalformedURLException | InterruptedException exception){
exception.printStackTrace();
}
catch(IOException ioException){
ioException.printStackTrace();
}
}
//Gives duration in seconds for audio files
public static long getDurationInSec(final AudioInputStream audioIn){
final AudioFormat format=audioIn.getFormat();
double frameRate=format.getFrameRate();
return (long)(audioIn.getFrameLength()/frameRate);
}
////////main//////
public static void main(String $[]){
//SoundClipTest test=new SoundClipTest();
SoundClipTest.playSound("/home/dev/Downloads/mixkit-sad-game-over-trombone-471.wav");
}
}
This thread is rather old but I have determined an option that could prove useful.
Instead of using the Java AudioStream library you could use an external program like Windows Media Player or VLC and run it with a console command through Java.
String command = "\"C:/Program Files (x86)/Windows Media Player/wmplayer.exe\" \"C:/song.mp3\"";
try {
Process p = Runtime.getRuntime().exec(command);
catch (IOException e) {
e.printStackTrace();
}
This will also create a separate process that can be controlled it the program.
p.destroy();
Of course this will take longer to execute than using an internal library but there may be programs that can start up faster and possibly without a GUI given certain console commands.
If time is not of the essence then this is useful.
I faced many issues to play mp3 file format
so converted it to .wav using some online converter
and then used below code (it was easier instead of mp3 supporting)
try
{
Clip clip = AudioSystem.getClip();
clip.open(AudioSystem.getAudioInputStream(GuiUtils.class.getResource("/sounds/success.wav")));
clip.start();
}
catch (Exception e)
{
LogUtils.logError(e);
}