# Störender Lag zwischen 2 Clips



## PattXterminator (5. Dez 2009)

Hallo

Ich suche zur Zeit verzweifelt nach einer Möglichkeit zu Prüfen, ob ein Clip fast zu Ende ist... Ich habe vor, einen kleines Intro Clip abzuspielen, und direkt im Anschluss einen anderen. Wenn ich das mit dem LineEvent probiere, sieht das bis jetzt so aus:


```
public void m_clip_update(LineEvent evt)
  {
    if(m_clip.isRunnging()==false)
    {
      b_clip.loop(b_clip.LOOP_CONTINUOUSLY);
      m_clip.close();
    }
  }
```

(m_clip ist das Intro, b_clip das eigentliche Stück)
Dabei kommt das Problem auf, dass zwischen dem Intro und des Stücks ein durchaus hörbarer Lag entsteht, weil ich ja eben erst nachdem der Clip zu Ende ist prüfen kann, ob er auch wirklich zu Ende ist, und dann erst den neuen starten kann. Gibt es da irgendeine Möglichkeit das zu umgehen?

Vielen Dank für eure Antworten im Vorraus

MfG
PattXterminator


----------



## hdi (5. Dez 2009)

Also ich kann jetzt zur API von Sounds usw nix sagen, da ich damit noch nicht gearbeitet habe. Aber du erzählst irgendwas von LineEvents... Du nutzt das Event doch gar nicht :bahnhof:


----------



## Spacerat (5. Dez 2009)

Man kann doch mit der Differenz aus [c]getMircosecondLength()[/c] und [c]getMicrosecondPosition()[/c] feststellen, wann der Clip beendet werden soll. Behält man diesen Wert im Auge, kann man rechtzeitig drauf reagieren und den 2. Clip starten. Das garantiert aber immer noch nicht, das der Clip dann auch nahtlos startet. Will man das ganze nahtlos haben, müsstest du dir sozusagen einen eigenen Clip-Mixer entwickeln, welcher eine SourceDataLine verwendet. Werden vom ersten Clip dann -1 (Clip-Ende) gelesen, kann diese gleich mit den Daten des 2. Clips gefüllt werden. Für den Fall, das du mir grad' nicht folgen kannst, versuch' ich mal ein Beispiel zusammen zu frickeln.


----------



## PattXterminator (6. Dez 2009)

Hallo,
danke für die Antworten erstmal



hdi hat gesagt.:


> Aber du erzählst irgendwas von LineEvents... Du nutzt das Event doch gar nicht :bahnhof:



Doch, das tue ich. Das LineEvent triggert, wenn der Linienstatus geändert wird. Wenn der Clip zu Ende ist, ist das dann genau der Fall. Und dann gucke ich, ob der Clip mit isRunning()==false auch wirklich zu Ende ist.



Spacerat hat gesagt.:


> Man kann doch mit der Differenz aus [c]getMircosecondLength()[/c] und [c]getMicrosecondPosition()[/c] feststellen, wann der Clip beendet werden soll. Behält man diesen Wert im Auge, kann man rechtzeitig drauf reagieren und den 2. Clip starten.



Ja, aber das Problem an der Sache ist, dass ich dabei die Line permanent überwachen muss. Mein Programm sieht bisher so aus:


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


public class ClipPlayer
{


  // Anfang Attribute
  private Clip m_clip;
  private Clip b_clip;
  // Ende Attribute


  public ClipPlayer(String musik_intro, String musik_theme)
  {
    File clipFileIntro = new File(musik_intro);
    AudioInputStream audioInputStreamIntro = null;
    try
    {
      audioInputStreamIntro = AudioSystem.getAudioInputStream(clipFileIntro);
    }
    catch (Exception e)
    {
      e.printStackTrace();
    }
    AudioFormat formatIntro = audioInputStreamIntro.getFormat();
    DataLine.Info infoIntro = new DataLine.Info(Clip.class, formatIntro);
    try
    {
      m_clip = (Clip)AudioSystem.getLine(infoIntro);
      m_clip.open(audioInputStreamIntro);
    }
    catch (LineUnavailableException e)
    {
      e.printStackTrace();
    }
    catch (IOException e)
    {
      e.printStackTrace();
    }

    File clipFileTheme = new File(musik_theme);
    AudioInputStream audioInputStreamTheme = null;
    try
    {
      audioInputStreamTheme = AudioSystem.getAudioInputStream(clipFileTheme);
    }
    catch (Exception e)
    {
      e.printStackTrace();
    }
    AudioFormat formatTheme = audioInputStreamTheme.getFormat();
    DataLine.Info infoTheme = new DataLine.Info(Clip.class, formatTheme);
    try
    {
      b_clip = (Clip)AudioSystem.getLine(infoTheme);
      b_clip.open(audioInputStreamTheme);
    }
    catch (LineUnavailableException e)
    {
      e.printStackTrace();
    }
    catch (IOException e)
    {
      e.printStackTrace();
    }

    
    m_clip.addLineListener(new LineListener() {
      public void update(LineEvent evt) {
        m_clip_update(evt);
      }
    });

    System.out.println(m_clip.getFrameLength()+"");
    m_clip.start();
  }

  // Anfang Methoden

  public void stoppe()
  {
    m_clip.stop();
    m_clip.close();
    b_clip.stop();
    b_clip.close();
  }
  
  public void volume(double pVolume)
  {
    FloatControl gainControlTheme = (FloatControl) m_clip.getControl(FloatControl.Type.MASTER_GAIN);
    float dB = (float)(Math.log(pVolume)/Math.log(10.0)*20.0);
    gainControlTheme.setValue(dB);
    FloatControl gainControlIntro = (FloatControl) b_clip.getControl(FloatControl.Type.MASTER_GAIN);
    gainControlIntro.setValue(dB);
  }
  
  public void m_clip_update(LineEvent evt)
  {
    if(m_clip.isRunning()==false)
    {
      b_clip.loop(b_clip.LOOP_CONTINUOUSLY);
      m_clip.close();
    }
  }
  // Ende Methoden
}
```

Ich erledige die ganze Arbeit schon im Constructor, damit ich dann in meiner GUI ein Objekt pro Stück erzeuge. Wenn ich da jetzt einmal [c]if(m_clip.getMircosecondLength()-m_clip.getMicrosecondPosition()==100)[/c] reinschreibe, überprüft er das einmal wenn ich das Objekt erzeuge, das bringt dann auch nix mehr....




Spacerat hat gesagt.:


> Das garantiert aber immer noch nicht, das der Clip dann auch nahtlos startet. Will man das ganze nahtlos haben, müsstest du dir sozusagen einen eigenen Clip-Mixer entwickeln, welcher eine SourceDataLine verwendet. Werden vom ersten Clip dann -1 (Clip-Ende) gelesen, kann diese gleich mit den Daten des 2. Clips gefüllt werden. Für den Fall, das du mir grad' nicht folgen kannst, versuch' ich mal ein Beispiel zusammen zu frickeln.



Hmm ich hab jetzt noch nicht ganz so viel Ahnung von Sound in Java, also ich kann dir nicht wirklich folgen... ^^

Danke für weitere Antworten schonmal im Vorraus ^^
MfG,
PattXterminator


----------



## Spacerat (6. Dez 2009)

PattXterminator hat gesagt.:


> Hmm ich hab jetzt noch nicht ganz so viel Ahnung von Sound in Java, also ich kann dir nicht wirklich folgen...


Jepp... das ist unverkennbar... sorry...
Der Clip bietet dir über die Events nicht die Möglichkeit, die sich dahinter verborgene DataLine "am Leben" zu halten, d.H. er bietet dir nicht die Möglichkeit, die Line weiter zu befüllen, wenn Clip-Ende signalisiert wurde. Deswegen ist er für deine Ansprüche völlig ungeeignet. Deswegen (und jetzt bitte nicht erschrecken... ) hier mal 'ne Version, womit man das Ende von Clip1 abwartet und anschliessend Clip2 in die selbe Line looped.
	
	
	
	





```
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;
import javax.sound.sampled.UnsupportedAudioFileException;

public class ClipStreamer
{
	private final AudioInputStream clip1, clip2;
	private Thread runner;
	private final byte[] buffer;
	private final SourceDataLine line;

	public static void main(String[] args)
	throws UnsupportedAudioFileException, IOException, LineUnavailableException
	{
		if(args == null || args.length < 2) {
			args = new String[] {"chimes.wav", "win.wav",};
		}
		new ClipStreamer(args[0], args[1]).start();
	}

	private ClipStreamer(String clip1, String clip2)
	throws UnsupportedAudioFileException, IOException, LineUnavailableException
	{
		BufferedInputStream c1 = new BufferedInputStream(new FileInputStream(new File(clip1)));
		BufferedInputStream c2 = new BufferedInputStream(new FileInputStream(new File(clip2)));
		this.clip1 = AudioSystem.getAudioInputStream(c1);
		this.clip2 = AudioSystem.getAudioInputStream(c2);
		AudioFormat af = this.clip1.getFormat();
		buffer = new byte[32768];
		line = AudioSystem.getSourceDataLine(af);
		line.open(af, buffer.length);
	}

	public synchronized void start()
	{
		if(runner == null) {
			try {
				clip2.mark(clip2.available());
			} catch(IOException e) {
				e.printStackTrace();
				return;
			}
			runner = new Thread()
			{
				@Override
				public void run()
				{
					AudioInputStream source = clip1;
					int bytesRead;
					while(!isInterrupted()) {
						try {
							bytesRead = source.read(buffer, 0, buffer.length);
							if(bytesRead <= 0) {
								if(source != clip2) {
									source = clip2;
								} else {
									source.reset();
									source.mark(source.available());
								}
							} else {
								line.write(buffer, 0, bytesRead);
							}
						} catch (IOException ioe) {
							interrupt();
						}
					}
					line.flush();
					line.stop();
					runner = null;
				}
			};
			line.start();
			runner.start();
		}
	}

	public synchronized void stop()
	{
		if(runner != null) {
			runner.interrupt();
			while(runner != null) {
				// wait
			}
		}
	}
}
```
Naja... sieht schlimmer aus als es ist. Das Beispiel geht im übrigen von zwei Dateien gleichen Formats aus (also nicht nur wav, sondern auch 22kHz, 16Bit, Mono).


----------



## PattXterminator (6. Dez 2009)

Hallo,

ist nicht so schlimm, bin ja selber noch am Lernen ^^ Hab das jetzt mal ausprobiert und funktioniert an sich ganz super. Nur wenn ich jetzt aus meiner GUI die main Methode mit passendem String Array aufrufe, verlangt er immernoch eine UnsupportedAudioFileException...

MfG
PattXterminator


----------



## Spacerat (6. Dez 2009)

Ja OK... Dann mal was "schmutziges"... Einfach mal die ganzen geworfenen Exceptions in einer einzigen zusammenfassen... also aus [c]throws UnsupportedAudioFileException, IOException, LineUnavailableException[/c] schlicht [c]throws Exception[/c] machen. Das ist dann die einzige die abgefangen werden muss. Ansonsten musst du alle der Reihe nach abfangen. D.h., wenn du dem Wunsch der IDE entsprochen hast und die [c]UnsupportedAudioFileException[/c] abgefangen hast, meckert diese nämlich die beiden anderen auch noch an.


----------



## PattXterminator (6. Dez 2009)

Hallo,

jetzt meckert er rum, dass er die normale Exception abgefangen haben will... Bei mir sieht das grade so aus, vielleicht mache ich ja was grundlegendes falsch >_<

[JAVA=18]
private ClipStreamer player;
[/code]

[JAVA=990]
String[] so = {"C:/Anime/Out Of ControlBegin.wav", "C:/Anime/Out Of ControlTest.wav"};
player.main(so);
[/code]

Und die Fehlermeldung:
cGUI.java:991:18: unreported exception java.lang.Exception; must be caught or declared to be thrown
player.main(so);
(Die Fehlermeldung verweist auf die öffnende Klammer)

MfG
PattXtermiantor


----------



## Spacerat (6. Dez 2009)

ja, nee... is Klar... die musst du abfangen oder weiter reichen. Ab Zeile 990 müsste das etwa so aussehen:[JAVA=990]String[] so = {"C:/Anime/Out Of ControlBegin.wav", "C:/Anime/Out Of ControlTest.wav"};
try {
  player.main(so);
} catch(Exception e) {
  e.printStackTrace();
  // wenn im Fehlerfall das Programm beendet werden soll...
  System.exit(-1);
}[/code]


----------



## PattXterminator (6. Dez 2009)

Hallo,

ah achso, jetzt funktioniert alles ^^ Tschuldigung, dass ich so noobish bin... :'D
Und die letzte Frage: Darf ich deinen Code für mein Projekt verwenden?

EDIT:
Es ist für nichtkommerzielle Zwecke und nur für den Privatgebrauch

MfG
PattXterminator


----------



## Spacerat (6. Dez 2009)

Natürlich... Sag' ich mal... Hab' die Forenregeln zwar jetzt nicht soo unbedingt im Kopf, aber irgendwo geht daraus hervor, dass man auf eigene, hier veröffentlichte Codeschnipsel keinerlei Rechte (ausgenommen dem Urheberrecht vllt.) mehr hat. Aber nochmal was anderes. Ich würde die Klasse nicht über dessen [c]main()[/c] instanzieren. Ich würde vielmehr den Kostruktor [c]public[/c] machen und mit [c]new[/c] instanzieren. Dann hat man nämlich seine Instanz und kann darauf nach belieben [c]start()[/c] und [c]stop()[/c] aufrufen.


----------



## PattXterminator (6. Dez 2009)

Hallo,

okay super danke, hast mir echt weitergeholfen ^^

MfG
PattXterminator


----------

