# soundlösung zu langsam für spiele



## jared (27. Okt 2005)

hallo! 
ich spiele sound - kurz gefasst - so ab: 

```
SourceDataLine line=null;
        AudioFormat audioFormat=new AudioFormat((float)8000.0,16,2,true,false);
        DataLine.Info info=new DataLine.Info(SourceDataLine.class,audioFormat);
        try{
            line = (SourceDataLine) AudioSystem.getLine(info);
            line.open(audioFormat);
        }catch (LineUnavailableException e){}
        ////
        line.write(buffer,0,buffersize);
```

(hoffe, es ist alles wichtige drin)
das ist leider zu langsam für ein spiel. 
es vergeht ca. 1/2 sekunde vom befehl bis man den sound hört. 

wie kann man das schneller machen??

gez:jared


----------



## Bert Brenner (27. Okt 2005)

Ist jetzt nur geraten, aber warum initialisierst du die SourceDataLine nicht früher, so das du noch line#write zur gegebenen zeit benutzen musst?


----------



## Soulfly (28. Okt 2005)

Mach ich zwar ungern, aber ich hab hier die WaveFile Klasse von meiner 2D-engine. Es gibt auch noch Ogg und Midi.
Damit solltest du keine Probleme mehr haben.


```
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.Clip;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.FloatControl;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.UnsupportedAudioFileException;



/**
 * Diese erstellt aus einer Wav-File ein abspielbares Objekt. Und sollte wenn möglich nur für Sounds verwendet werden
 * 
 * @author Soulfly alias Ingo Müller
 */
public class WaveFile implements Runnable
{
	/** Vereinfacht die Steuerung für einen unendlichen Sound(Musik) */
	static final boolean	LOOP	= true;

	/** Vereinfacht die Steuerung für einenSound */
	static final boolean	PLAY	= false;

	/** Die Lautstärke vom abgespielten Sound */
	private float			gain;

	/** zeigt, ob loop aktiviert ist */
	private boolean			loop;

	/** Name der Sound-Datei */
	private String			name;

	/** Links-Rechts-Modifier für die Sound-Datei */
	private float			pan;

	/** zum Steuern des loop-Sounds */
	private boolean			shouldplay;

	/** SoundThread */
	private Thread			t;

	/**
	 * Kontruktor
	 * 
	 * @param n
	 *            Sound-Datei
	 * @param g
	 *            Gain-Lautstärke
	 */
	public WaveFile(String n, float g)
	{
		pan = 0f;
		gain = g;
		name = n;
	}

	/**
	 * Abspiel-Thread der WaveFile
	 */
	public void run()
	{
		if (loop)
		{
			shouldplay = true;
			loop();
		}
		else
		{
			play();
		}
	}

	/**
	 * Startet den Sound einmal oder im Loop
	 * 
	 * @param LOOP
	 *            Loop oder nicht
	 */
	public void start(boolean LOOP)
	{
		loop = LOOP;

		t = new Thread(this);
		t.start();
	}

	/**
	 * Stop den unendlichen Sound
	 */
	public void stop()
	{
		shouldplay = false;
	}

	/**
	 * Spielt den Sound oder die Musik unendlich ab bis der Sound gestoppt
	 */
	private void loop()
	{
		while (shouldplay)
		{
			try
			{
				System.out.println("loop start");
				AudioInputStream ais;

				Clip clip;

				AudioFormat format;

				ais = AudioSystem.getAudioInputStream(new File(WaveFile.getResource(name).getFile()));
				format = ais.getFormat();

				if ((format.getEncoding() == AudioFormat.Encoding.ULAW) || (format.getEncoding() == AudioFormat.Encoding.ALAW))
				{
					AudioFormat tmp = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, format.getSampleSizeInBits(), format
							.getSampleSizeInBits() * 2, format.getChannels(), format.getFrameSize() * 2, format.getFrameRate(),
														true);
					ais = AudioSystem.getAudioInputStream(tmp, ais);
					format = tmp;
				}

				DataLine.Info info = new DataLine.Info(Clip.class, format, ((int) ais.getFrameLength() * format.getFrameSize()));

				clip = (Clip) AudioSystem.getLine(info);
				clip.open(ais);

				FloatControl panControl = (FloatControl) clip.getControl(FloatControl.Type.MASTER_GAIN);
				panControl.setValue(pan);

				FloatControl gainControl = (FloatControl) clip.getControl(FloatControl.Type.MASTER_GAIN);
				gainControl.setValue(gain);

				clip.start();

				while (true)
				{
					try
					{
						Thread.sleep(100);
					}
					catch (Exception e)
					{
					}

					if (!clip.isRunning())
						break;
				}
				clip.stop();
				clip.close();

			}
			catch (LineUnavailableException e1)
			{
				e1.printStackTrace();
			}
			catch (IOException e1)
			{
				e1.printStackTrace();
			}
			catch (UnsupportedAudioFileException e)
			{
				e.printStackTrace();
			}
		}
	}

	/**
	 * Spielt den Sound einmal ab
	 */
	private void play()
	{
		try
		{
			AudioInputStream ais;

			Clip clip;

			AudioFormat format;

			ais = AudioSystem.getAudioInputStream(new File(WaveFile.getResource(name).getFile()));
			format = ais.getFormat();

			if ((format.getEncoding() == AudioFormat.Encoding.ULAW) || (format.getEncoding() == AudioFormat.Encoding.ALAW))
			{
				AudioFormat tmp = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, format.getSampleSizeInBits(), format
						.getSampleSizeInBits() * 2, format.getChannels(), format.getFrameSize() * 2, format.getFrameRate(), true);
				ais = AudioSystem.getAudioInputStream(tmp, ais);
				format = tmp;
			}

			DataLine.Info info = new DataLine.Info(Clip.class, format, ((int) ais.getFrameLength() * format.getFrameSize()));

			clip = (Clip) AudioSystem.getLine(info);
			clip.open(ais);

			FloatControl panControl = (FloatControl) clip.getControl(FloatControl.Type.MASTER_GAIN);
			panControl.setValue(pan);

			FloatControl gainControl = (FloatControl) clip.getControl(FloatControl.Type.MASTER_GAIN);
			gainControl.setValue(gain);

			clip.start();

			while (true)
			{
				try
				{
					Thread.sleep(100);
				}
				catch (Exception e)
				{
				}

				if (!clip.isRunning())
					break;
			}
			clip.stop();
			clip.close();

		}
		catch (LineUnavailableException e1)
		{
			e1.printStackTrace();
		}
		catch (IOException e1)
		{
			e1.printStackTrace();
		}
		catch (UnsupportedAudioFileException e)
		{
			e.printStackTrace();
		}
	}
        
        /**
	 * Einfacher statische URL Loader für Jar-Archive und Ordner
	 * 
	 * @param filename
	 *            Datei die geladen werden soll
	 * @return Passende URL zur Datei
	 */
	public final static URL getResource(final String filename)
	{
		// Try to load resource from jar
		URL url = ClassLoader.getSystemResource(filename);
		// If not found in jar, then load from disk
		if (url == null)
		{
			try
			{
				url = new URL("file", "localhost", filename);
			}
			catch (Exception urlException)
			{
			} // ignore
		}
		return url;
	}
}
```


----------



## jared (28. Okt 2005)

[/quote]Ist jetzt nur geraten, aber warum initialisierst du die SourceDataLine nicht früher, so das du noch line#write zur gegebenen zeit benutzen musst?





> mach ich natürlich :wink: . was ich da geschrieben hab ist gekürzt, damit man es schnell versteht.


----------



## jared (28. Okt 2005)

ups, schlecht gequotet   
naja. weisst ja, wiess gemeint war...


----------



## jared (28. Okt 2005)

danke für die klasse. 

bei mir meldet der aber fehler: 
zeile 90: 
"the constructor Thread(WavFile) is undefined"

wie kann ich das fixen?


----------



## Soulfly (28. Okt 2005)

Wenn da wirklich "WavFile" steht, dann benenn mal deine Datei um in WaveFile.java.
Sind da noch andere Fehleranzeigen.
Ich hab die nämlich halbdirekt aus meiner Engine rauskopiert. Sorry dafür.

MfG
Soulfly


----------



## jared (29. Okt 2005)

die anderen fehler konnte ich alle fixen, 
indem ich imoprted habe. 
jetzt ist da nur noch der mit dem thread. 
naja, ich versuch mal, das programm zu verstehen, 
dann kann ich direkt teile bei mir einbauen. 
mfg, jared


----------



## EgonOlsen (29. Okt 2005)

Soulfly hat gesagt.:
			
		

> Mach ich zwar ungern, aber ich hab hier die WaveFile Klasse von meiner 2D-engine. Es gibt auch noch Ogg und Midi.
> Damit solltest du keine Probleme mehr haben.


Warum lädst du den Sound für jedes Abspielen neu? Spielst du es deswegen in einem Thread, damit die ständige Initialisierung keinen Schluckauf im Spiel verursacht? Es macht meiner Ansicht nach viel mehr Sinn, den Sound im <init> zu laden und dann nur noch abzuspielen. Und dafür braucht man keine Threads. Sich bei Java darauf zu verlassen, das ein Thread auch rechtzeitig drankommt ist eine sehr wackelige Angelegenheit.
In diesem Thread findest du ein Beispiel, wie ich das meine (der untere Code (StrippedSound)): www.java-forum.org/de/viewtopic.php?t=17095


----------



## Soulfly (29. Okt 2005)

Hi 
ich weiß wieder woran es. Ich vorher über eine allgemeine Schnittstelle für Soundklassen, mit dieser kommuniziert
und in der Schnittstelle war auch das Interface Runnable.
Implementiere diese Schnittstelle für die Klasse und alles funktioniert.
Ich ändere das oben auch, damit nicht jeder, der das will immer diesen Fehler hat.

Mfg
sOUFLLY


----------



## jared (29. Okt 2005)

ja. 
ich hab jetzt eine klasse draus gemacht: 


```
package soul2d.engine.audio;

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

import javax.sound.sampled.*;

public class WavFileCore extends Thread{
	
	float pan=0;
	float gain=1;
	String name;
	boolean ended=false;
	
	public WavFileCore(String filename){
		super();
		name=filename;
	}
	
	public void run(){
		play();
	}
	
	private void play() 
	{ 
	      try 
	      { 
	         AudioInputStream ais; 

	         Clip clip; 

	         AudioFormat format; 

	         ais = AudioSystem.getAudioInputStream(new File(WaveFile.getResource(name).getFile())); 
	         format = ais.getFormat(); 

	         if ((format.getEncoding() == AudioFormat.Encoding.ULAW) || (format.getEncoding() == AudioFormat.Encoding.ALAW)) 
	         { 
	            AudioFormat tmp = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, format.getSampleSizeInBits(), format 
	                  .getSampleSizeInBits() * 2, format.getChannels(), format.getFrameSize() * 2, format.getFrameRate(), true); 
	            ais = AudioSystem.getAudioInputStream(tmp, ais); 
	            format = tmp; 
	         } 

	         DataLine.Info info = new DataLine.Info(Clip.class, format, ((int) ais.getFrameLength() * format.getFrameSize())); 

	         clip = (Clip) AudioSystem.getLine(info); 
	         clip.open(ais); 

	         FloatControl panControl = (FloatControl) clip.getControl(FloatControl.Type.MASTER_GAIN); 
	         panControl.setValue(pan); 

	         FloatControl gainControl = (FloatControl) clip.getControl(FloatControl.Type.MASTER_GAIN); 
	         gainControl.setValue(gain); 

	         clip.start(); 

	         while (true) 
	         { 
	            try 
	            { 
	               Thread.sleep(100); 
	            } 
	            catch (Exception e) 
	            { 
	            } 

	            if (!clip.isRunning()) 
	               break; 
	         } 
	         clip.stop(); 
	         clip.close(); 

	      } 
	      catch (LineUnavailableException e1) 
	      { 
	         e1.printStackTrace(); 
	      } 
	      catch (IOException e1) 
	      { 
	         e1.printStackTrace(); 
	      } 
	      catch (UnsupportedAudioFileException e) 
	      { 
	         e.printStackTrace(); 
	      } 
	      ended=true;
   } 
}
```

dann habe ich den clip durch eine SourceDataLine ersetzt: 


```
package soul2d.engine.audio;

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

import javax.sound.sampled.*;

public class LineCore extends Thread{

	float pan=0;
	float gain=1;
	String name;
	boolean ended=false;
	
	public LineCore(String filename){
		super();
		name=filename;
	}
	
	public void run(){
		play();
	}
	
	private void play() 
	{ 
	      try 
	      { 
	         AudioInputStream ais; 

	     	SourceDataLine line;

	         AudioFormat format; 

	         File soundfile=new File(WaveFile.getResource(name).getFile());
	         ais = AudioSystem.getAudioInputStream(soundfile); 
	         format = ais.getFormat(); 

	         /*if ((format.getEncoding() == AudioFormat.Encoding.ULAW) || (format.getEncoding() == AudioFormat.Encoding.ALAW)) 
	         { 
	            AudioFormat tmp = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, format.getSampleSizeInBits(), format 
	                  .getSampleSizeInBits() * 2, format.getChannels(), format.getFrameSize() * 2, format.getFrameRate(), true); 
	            ais = AudioSystem.getAudioInputStream(tmp, ais); 
	            format = tmp; 
	         } */

	         DataLine.Info info = new DataLine.Info(SourceDataLine.class, format); 

	         line = (SourceDataLine)AudioSystem.getLine(info); 
	         line.open(format); 

	         FloatControl panControl = (FloatControl) line.getControl(FloatControl.Type.MASTER_GAIN); 
	         panControl.setValue(pan); 

	         FloatControl gainControl = (FloatControl) line.getControl(FloatControl.Type.MASTER_GAIN); 
	         gainControl.setValue(gain); 

	         line.start(); 

     		byte[] abData=new byte[1024];
    		int nRead=0;
	         for(int i=0;i<soundfile.length();i++)
	         { 
	            try 
	            { 
       				nRead=ais.read(abData,0,abData.length);
        			if(nRead>=0){
        				line.write(abData,0,nRead);
        			}
	            } 
	            catch (Exception e) 
	            { 
	            } 
	         } 
	         line.stop(); 
	         line.close(); 

	      } 
	      catch (LineUnavailableException e1) 
	      { 
	    	  System.out.println("gaytnich");
	         e1.printStackTrace(); 
	      } 
	      catch (IOException e1) 
	      { 
	    	  System.out.println("gaytnich");
	         e1.printStackTrace(); 
	      } 
	      catch (UnsupportedAudioFileException e) 
	      { 
	    	  System.out.println("gaytnich");
	         e.printStackTrace(); 
	      } 
	      ended=true;
   } 
}
```

das klappt, vom knopfdrücken bis man den sound hört vergeht fast keine zeit. 

jetzt versteh ich aber nicht warum es bei meinem anderen programm so lange dauert. 
(das andere programm hat eine andere funktion) 

ich poste es mal. (hoffentlich ist es verständlich, ich werde ein paar erklärungen einfügen):


```
package byteplay;

import java.io.*;
import javax.sound.sampled.*;

public class BytePlay416BitStereo extends Thread{
	public byte[] playByte; //eine reihe von bytes
                //, die als sound interpretiert immer wieder abgespielt werden. 
                //in soundplay fügt man in dieses array ein anderes array ein...
	public AudioFormat audioFormat=new AudioFormat((float)8000.0,16,2,true,false);
	SourceDataLine line;
	public boolean stopit=false;//von aussen kann man stopit true setzen, dann stoppt der thread.
	public int at=0;//welchen teil des arrays gerade auf die SourceDataLine geschrieben wird
	int buffersize;//s.u.

	public static byte[] filezubyte(File soundfile){

                       //konvertiert eine wav-file (16bit,stereo,48000khz) in ein bytearray
                       //wird z.B. benutzt, wenn man einen wavsound von aussen abspielen will (siehe playSound())

		byte[] returnbyte=new byte[(int)soundfile.length()];
		AudioInputStream audioInputStream=null;
		try{
			audioInputStream=AudioSystem.getAudioInputStream(soundfile);
		}catch (Exception e){}
		try{
			audioInputStream.read(returnbyte,0,returnbyte.length);
		}catch (IOException e){}
		return returnbyte;
	}
	
	public void playSound(byte[] sound,double volL,double volR){//sollte 16bit,stereo,samplerate=48000 sein

                                //diese methode wird von aussen benutzt, um ein bytearray in 'playbyte' einzufügen
                                //hier wird eigentlihc nichts abgespielt

		boolean links=true;
		boolean erstes=true;
		for(int i=0;i<sound.length;i++){
			int einsetzbei=at+i;
		                 //bei 'at', also dem teil, der gerade in die line geschrieben wird wird das bytearray eingefügt
			if(einsetzbei>=playByte.length)einsetzbei-=playByte.length;
			if(links){playByte[einsetzbei]+=(byte)(volL*sound[i]);}
			else{playByte[einsetzbei]+=(byte)(volR*sound[i]);}
			if(!erstes)links=!links;
			erstes=!erstes;
		}
	}
	
	public BytePlay416BitStereo(int maxeinspielsoundlänge,int buffersize) {
		super();
		this.buffersize=buffersize;
		playByte=new byte[maxeinspielsoundlänge];
		line=null;
		DataLine.Info info=new DataLine.Info(SourceDataLine.class,audioFormat);
		try{
			line = (SourceDataLine) AudioSystem.getLine(info);
			line.open(audioFormat);
		}catch (LineUnavailableException e){}
	}
	
	public void run(){

		//'byteplay' wird immer wieder und in abschnitte (buffer) unterteilt auf die line geschrieben (line.write)

		line.start();
		byte[] buffer=new byte[buffersize];
		while(!stopit){
			for(int i=0;i<buffersize;i++){
				buffer[i]=playByte[at+i];
				playByte[at+i]=0;
			}
			line.write(buffer,0,buffersize);
			at+=buffersize;
			if(at>playByte.length-buffersize)at=0;
		}
		System.out.println("stopped!");
		line.close();
	}
}
```

noch ein beispiel, wie manns benutzt: 


```
int volL=1;//lautstärke der linken spur 100%=1.0
int volR=1;//abspiel-lautstärke der rechten spur
BytePlay416BitStereo bp=new BytePlay416BitStereo(9999999,64);
bp.playSound(filezubyte(new File(meinsound.wav)),volL,volR);
```

so. hat einer eine ahnung, warum BytePlay416BitStereo so langsam reagiert?
oder fragen zu der klasse?...


----------



## Guest (29. Okt 2005)

moment, das beispiel ist falsch. 
es muss lauten: 

```
double volL=1.0;
double volR=1.0;
BytePlay416BitStereo bp=new BytePlay416BitStereo((9999999,64);
bp.start();
bp.playSound(filezubyte(new File(meinsound.wav)),volL,volR);
```


----------



## Simon Griese (Gast) (30. Okt 2005)

Mich überrascht dass das überhaupt funktioniert. 
Sehr umständliche Methode!


----------



## thomas.g (31. Okt 2005)

hi,

ich hätte eine einfachere Lösung, allerdings muss man Java Media Framework installieren.

Wenn ich irgendwelche Soundbasierte Applicationen machen verwende ich immer JMF, weil man da so ziemlich alles abspielen kann und es leicht zu implementieren ist.

mfg, thomas


----------



## 0xdeadbeef (31. Okt 2005)

Hatte das in leicht geänderter Form schon mal gepostet, aber da war da hatte ich noch mehr Workarounds eingebaut, die inzwischen unnötig sind, weil die Probleme in der JVM lagen. Also nochmal meine (simple) Soundklasse:


```
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
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.Line;
import javax.sound.sampled.Mixer;

public class Sound {
	
	public Sound() throws Exception {
		canPlay = false;
		String fName="";
		soundBuffer = new byte[NUMBER][];
		format = new AudioFormat[NUMBER];
		
		try {
			URLClassLoader urlLoader = (URLClassLoader) this.getClass().getClassLoader();
			for (int i = 0; i<NUMBER; i++) {
				fName = "sound/sound_"+Integer.toString(i)+".wav";
				URL fileLoc = urlLoader.findResource(fName);
				if (fileLoc == null)
					throw new Exception();
				AudioInputStream f = AudioSystem.getAudioInputStream(fileLoc); 
				format[i] = f.getFormat();
				soundBuffer[i] = new byte[(int)f.getFrameLength()*format[i].getFrameSize()];
				f.read(soundBuffer[i]);
				f.close();
				
			}
		} catch (Exception ex) {
			throw new Exception("Error loading sound file "+ fName);
		}
		
		// get all available mixers
		Mixer.Info[] mixInfo = AudioSystem.getMixerInfo();
		ArrayList mix = new ArrayList();
		for (int i=0; i<mixInfo.length; i++) {
			Mixer mixer = AudioSystem.getMixer(mixInfo[i]);
			Line.Info info = new Line.Info(Clip.class);
			int num = mixer.getMaxLines(info);
			if (num != 0)
				mix.add(mixer);			
		} 		
		mixers = new Mixer[mix.size()];
		mixers = (Mixer[])mix.toArray(mixers);		
	}
	
	public String[] getMixers() {
		if (mixers==null)
			return null;
		String s[] = new String[mixers.length];
		for (int i=0; i<mixers.length; i++)
			s[i] = mixers[i].getMixerInfo().getName();
		return s;
	}
	
	public void setMixer(int i) {
		if (i > mixers.length)
			i = 0;
		mixerIdx = i;
	}
	
	public void play(int num) {
		int retryCount = 0;
		DataLine.Info info = new DataLine.Info(Clip.class, format[num]);
		try {
			Clip c = (Clip)mixers[mixerIdx].getLine(info);
			c.open(format[num],soundBuffer[num],0,soundBuffer[num].length);			
			c.start();
			break;
		} catch (Exception ex) {}
	}
		
	byte soundBuffer[][];
	AudioFormat format[];
	int   mixerIdx;
	Mixer mixers[];
	boolean canPlay;
	
	final static int NEEDED_LINES = 2;
	final static int NUMBER = 24;
}
```

Der Konstruktor lädt "sound\sound_0.wav" bis "sound\sound_23.wav" in ein zweidimensionales Byte-Array. Könnte man "generischer" programmieren, aber ich brauche genau diese Anzahl und werde nie mehr brauchen, also dürfte das die kürzeste/performanteste Lösung sein. Kann man ja aber einfach in ArrayList usw. umbauen.
Das getrennte Abspeichern des AudioFormats ist eigentlich auch ein Workaround gewesen: könnte man wegoptimieren, wenn die Soundfiles alle ähnlich/gleich sind. Ansonsten könnte man das noch etwas auf Geschwindigkeit optimieren, wenn man die DataLine.Info in einem Array speichert. Tut aber auch so.
Die Auswahl des Mixers ist eine Komfortfunktion, damit man per Menü einen Mixer einstellen kann. Prinzipiell könnte man auch eine Line auch per AudioSystem.getLine() anfordern, dann bekommt man allerdings immer die hardwarebeschleunigte Variante, die u.U. Probleme machen kann.
Allerdings scheinen in der 1.5_05 die Abspielprobleme kurzer Clips behoben zu sein, also könnte man wohl wieder auf diese einfache Version gehen.


----------



## maxf (1. Nov 2005)

Ich würde die Klasse LemmException so deklerieren:

```
class LemmException extends Exception{
	
	public LemmException() {
		super();
	}
	
	public LemmException(String s) {
		super(s);
	}
}
```


----------



## 0xdeadbeef (1. Nov 2005)

Sollte gar nicht mehr vorkommen, hatte ich beim Rausschneiden aus meinem kleinen Spielprojekt (wer anhand der Exception errät, um was es geht, kriegt einen Gummipunkt) lediglich vergessen.
Habe jetzt alle LemmExceptions in Exception umbenannt. Für eine saubere Lösung sollte man sich natürlich eine eigene SoundException schreiben. Aber ich wollte das Beispiel nicht unnötig kompliziert machen.


----------

