Reputation: 1
I am developing a 2D game in Java and sometimes I have problems with sound. At the moment, I am not using sound in many parts except in music, when walking, when attacking and when enemies take damage.
I leave you the code of the sound class and the error I have:
public class SoundThread extends Thread {
private String filename;
private Clip clip;
private float volumeScale;
private boolean isPlaying;
public SoundThread(String filename) {
try {
this.filename = filename;
clip = AudioSystem.getClip();
}
catch (LineUnavailableException ex) {
Logger.getLogger(SoundThread.class.getName()).log(Level.SEVERE, null, ex);
}
}
@Override
public void run() {
isPlaying = true;
try {
InputStream in = getClass().getResourceAsStream("/sonidos/" + filename + ".wav");
if (in != null) {
AudioInputStream ais = AudioSystem.getAudioInputStream(new BufferedInputStream(in));
clip = AudioSystem.getClip();
clip.open(ais);
clip.start();
while (clip.isActive()) {
Thread.sleep(80);
}
}
else {
System.err.println("No se pudo cargar el recurso de sonido: " + filename);
}
}
catch (UnsupportedAudioFileException | IOException | LineUnavailableException | InterruptedException e) {
e.printStackTrace();
} finally {
isPlaying = false;
}
}
public void reproducir(float volumen) {
if (!isPlaying && clip != null && !clip.isActive()) {
new Thread(() -> {
try {
InputStream in = getClass().getResourceAsStream("/sonidos/" + filename + ".wav");
if (in != null) {
AudioInputStream ais = AudioSystem.getAudioInputStream(new BufferedInputStream(in));
clip = AudioSystem.getClip();
clip.open(ais);
ajustarVolumen(clip, volumen);
clip.start();
}
else {
System.err.println("No se pudo cargar el recurso de sonido: " + filename);
}
}
catch (UnsupportedAudioFileException | IOException | LineUnavailableException e) {
e.printStackTrace();
}
}).start();
}
}
public void repetir(float volumen) {
if (!isPlaying && clip != null && !clip.isActive()) {
new Thread(() -> {
try {
InputStream in = getClass().getResourceAsStream("/sonidos/" + filename + ".wav");
if (in != null) {
AudioInputStream ais = AudioSystem.getAudioInputStream(new BufferedInputStream(in));
clip = AudioSystem.getClip();
clip.open(ais);
ajustarVolumen(clip, volumen);
clip.start();
clip.loop(Clip.LOOP_CONTINUOUSLY);
}
else {
System.err.println("No se pudo cargar el recurso de sonido: " + filename);
}
}
catch (UnsupportedAudioFileException | IOException | LineUnavailableException e) {
e.printStackTrace();
}
}).start();
}
}
private void ajustarVolumen(Clip clip, float volumen) {
if (clip != null)
FloatControl gainControl = (FloatControl) clip.getControl(FloatControl.Type.MASTER_GAIN);
float range = gainControl.getMaximum() - gainControl.getMinimum();
float gain = (range * volumen) + gainControl.getMinimum();
gainControl.setValue(gain);
}
}
public long getDuracion() {
return clip.getMicrosecondLength();
}
public void detener() {
if (clip != null && clip.isRunning()) {
clip.stop();
isPlaying = false;
}
}
public void cambiarArchivo(String nuevoArchivo) {
detener();
this.filename = nuevoArchivo;
}
}
Error:
Exception in thread "Thread-40" java.lang.IllegalArgumentException: Unsupported control type: Master Gain
at java.desktop/com.sun.media.sound.AbstractLine.getControl(AbstractLine.java:149)
at principal.sonido.SoundThread.ajustarVolumen(SoundThread.java:116)
at principal.sonido.SoundThread.lambda$reproducir$0(SoundThread.java:68)
at java.base/java.lang.Thread.run(Thread.java:842)
I have tried adding exceptions or looking for another library, but I don't know which one would help better. I also don't know why so many threads accumulate. As the error mentions, it is thread 40 and I don't think I am using the class in so many places.
Upvotes: 0
Views: 61
Reputation: 7910
Check if AudioCue will make your life easier. It is a library I wrote, available via maven.
AudioCue allows concurrent playback of cues, as well as real time volume, pitch and panning, all without requiring FloatControl
objects which are not always supported.
For basic playback using Java's Clip
or SourceDataLine
, I recommend checking out the information wiki provided by stackoverflow for the javasound.
@VGR made several excellent suggestions. I also tend to just make a SoundHandler class to hold the audio commands, and do not extend anything. A couple additional tips I'd like to mention:
(1) If you have a clear reason to wait to read the audio file until just before playing it, it's better to use SourceDataLine
. With a Clip
, the sound will not commence playing until after the entire file has been loaded to memory. With the SourceDataLine
playback starts as soon as a buffer's worth of data has come through the AudioInputStream
. The usual way Clip
is deployed is to load the file into memory during an initialization phase, and hold it there until it is actually time to play the cue.
I prefer loading my AudioInputStream
using a URL
rather than via an InputStream
. With a URL
, one can use a resource that is within a jar, and there is no need to deal with BufferedInputStream
. The stackoverflow wiki for javasound that I linked earlier has an example. Also, there are fewer exception-triggering conditions when a URL is the argument given to AudioSystem.getAudioInputStream()
than with InputStream
which can throw an error if the input stream does not support "mark/reset" capabilities. You can see this in the API for this method when you compare the different overloads.
Upvotes: 0
Reputation: 44414
It appears you are asking two questions:
The stack trace shows that the IllegalArgumentException is occurring when the ajustarVolumen method calls clip.getControl(FloatControl.Type.MASTER_GAIN)
. The getControl method of Clip is inherited from Line class, and its documentation says:
Throws:
IllegalArgumentException
- if a control of the specified type is not supported
You need to check whether the Clip supports MASTER_GAIN before trying to use it:
if (clip.isControlSupported(FloatControl.Type.MASTER_GAIN)) {
FloatControl gainControl = (FloatControl) clip.getControl(FloatControl.Type.MASTER_GAIN);
float range = gainControl.getMaximum() - gainControl.getMinimum();
float gain = (range * volumen) + gainControl.getMinimum();
gainControl.setValue(gain);
} else if (clip.isControlSupported(FloatControl.Type.AUX_SEND)) {
FloatControl gainControl = (FloatControl) clip.getControl(FloatControl.Type.AUX_SEND);
float range = gainControl.getMaximum() - gainControl.getMinimum();
float gain = (range * volumen) + gainControl.getMinimum();
gainControl.setValue(gain);
} else if (clip.isControlSupported(FloatControl.Type.VOLUME)) {
FloatControl gainControl = (FloatControl) clip.getControl(FloatControl.Type.VOLUME);
float range = gainControl.getMaximum() - gainControl.getMinimum();
float gain = (range * volumen) + gainControl.getMinimum();
gainControl.setValue(gain);
}
(You may want to move those repeated lines into a private method, to make the above logic easier to read.)
As for your thread problem, you are creating far more Threads and Clips than you need to.
You need to declare isPlaying as volatile
, so that when one thread changes that field’s value, other threads will immediately be able to see that value. If you don’t, other threads may not know when the value changes. See this short example in the Java Language Specification.
You are currently creating a Clip in the constructor which never gets used. Every method then creates its own Clip object. I would remove all assignments to clip
except for the one in the constructor. In order to reuse a single Clip object, the code must call the Clip’s close()
method, which brings us to this…
Currently, you are using a polling loop to determine when the Clip has finished playing. Instead, you should use a LineListener for that:
public SoundThread(String filename) {
try {
this.filename = filename;
clip = AudioSystem.getClip();
clip.addLineListener(event -> {
if (event.getType().equals(LineEvent.Type.STOP)) {
clip.close();
isPlaying = false;
}
});
}
catch (LineUnavailableException ex) {
Logger.getLogger(SoundThread.class.getName()).log(Level.SEVERE, null, ex);
}
}
This allows your method to return quickly, and also lets you close the Clip so it can be reused for subsequent playbacks.
SoundThread has multiple functions (run, reproducir, repetir), so it should not extend Thread. The class should be named something like Sound or SoundPlayer and should not extend any other class. Since the class can only perform one function at a time, it should not create new Threads for every call; instead, it should one maintain just one Thread for all of its functions. The easiest way to do this is with a single threaded ExecutorService:
private final ExecutorService queue = Executors.newSingleThreadExecutor();
You can then use that to perform your various functions:
public void play() {
isPlaying = true;
queue.submit(() -> {
try {
InputStream in = getClass().getResourceAsStream("/sonidos/" + filename + ".wav");
if (in != null) {
AudioInputStream ais = AudioSystem.getAudioInputStream(new BufferedInputStream(in));
clip.open(ais);
clip.start();
}
else {
System.err.println("No se pudo cargar el recurso de sonido: " + filename);
}
}
catch (UnsupportedAudioFileException | IOException | LineUnavailableException | InterruptedException e) {
e.printStackTrace();
isPlaying = false;
}
});
}
public void reproducir(float volumen) {
if (!isPlaying && clip != null && !clip.isActive()) {
queue.submit(() -> {
try {
InputStream in = getClass().getResourceAsStream("/sonidos/" + filename + ".wav");
if (in != null) {
AudioInputStream ais = AudioSystem.getAudioInputStream(new BufferedInputStream(in));
clip.open(ais);
ajustarVolumen(clip, volumen);
clip.start();
}
else {
System.err.println("No se pudo cargar el recurso de sonido: " + filename);
}
}
catch (UnsupportedAudioFileException | IOException | LineUnavailableException e) {
e.printStackTrace();
}
});
}
}
public void repetir(float volumen) {
if (!isPlaying && clip != null && !clip.isActive()) {
queue.submit(() -> {
try {
InputStream in = getClass().getResourceAsStream("/sonidos/" + filename + ".wav");
if (in != null) {
AudioInputStream ais = AudioSystem.getAudioInputStream(new BufferedInputStream(in));
clip.open(ais);
ajustarVolumen(clip, volumen);
clip.start();
clip.loop(Clip.LOOP_CONTINUOUSLY);
}
else {
System.err.println("No se pudo cargar el recurso de sonido: " + filename);
}
}
catch (UnsupportedAudioFileException | IOException | LineUnavailableException e) {
e.printStackTrace();
}
});
}
}
Upvotes: 1