changeset 107:8ddfb8d274c7

2008-09-03 Omair Majid <omajid@redhat.com> * src/java/org/classpath/icedtea/pulseaudio/PulseAudioClip.java Moved controls to PulseAudioDataLine. (PulseAudioClip): Removed lineListeners and controls - They are in a superclass of PulseAudioClip. (getBufferSize): Moved method to PulseAudioDataLine. (getControl): Moved method to PulseAudioLine. (getControls): Likewise. (getFormat): Moved method to PulseAudioDataLine. (getLevel): Likewise. (getLineInfo): Likewise. (isControlSupported): Moved to PulseAudioLine. (isOpen): Likewise. (isRunning): Moved to PulseAudioDataLine. (removeLineListeners): Moved to PulseAudioLine. * src/java/org/classpath/icedtea/pulseaudio/PulseAudioClip.java (open): Now sets isEngagedInIo when playback starts or stops. (close): Calls super.close and throws an exception if interrupted. (start): Removed comments. (stop): Likewise. (getBufferSize): New function. Moved from derived class to this class. (getLineInfo): Likewise. (getFormat): Likewise. (getLevel): Likewise. * src/java/org/classpath/icedtea/pulseaudio/PulseAudioLine.java Added controls. (close): New function. Moved from derived class to this class. (getControl): Likewise. (getControls): Likewise. (isControlSupported): Likewise. (open): Likewise. * src/java/org/classpath/icedtea/pulseaudio/PulseAudioMixer.java (getSupportedFormats): Removed redundant comments. (getLine): Removed unused variables. (addTargetLine): Renamed from addTargetDataLine. (removeTargetLine): Renamed from removeTargetDataLine. * src/java/org/classpath/icedtea/pulseaudio/PulseAduioPort.java Removed controls (open): Use the controls List from the parent class. (getControl): Moved to superclass. (getControls): Likewise. (isControlSupported): Likewise. * src/java/org/classpath/icedtea/pulseaudio/PulseAudioSourceDataLine.java Removed controls array. Uses the one from parent class. (open): Fixed to use the controls List. (write): Added an extra check. (getBufferSize): Moved to parent class. (getFormat): Likewise. (getLevel): Likewise. (getControl): Likewise. (getControls): Likewise. (getLineInfo): Likewise. (isControlSupported): Likewise. * src/java/org/classpath/icedate/pulseaudio/PulseAudioTargetDataLine.java (PulseAudioTargetDataLine): Use lineListeners from parent class. (close): Fixed function name to call in Mixer. (open): Likewise. (read): Added an extra check. (getBufferSize): Moved to parent class. (getFormat): Likewise. (getLevel): Likewise. (getControl): Likewise. (getControls): Likewise. (getLIneInfo): Likewise. (isControlSupported): Likewise. * src/java/org/classpath/icedtea/pulseaudio/PulseAudioVolumeControl.java Formatting fixes. * src/java/org/classpath/icedtea/pulseaudio/Stream.java (underflowCallback): Removed debug output. * unittests/org/classpath/icedtea/pulseaudio/PulseAudioClipTest.java (testOpenEvent): New test. Checks the OPEN event from PulseAudioClip (testCloseEvent): New test. Check the CLOSE event from PulseAudioClip (testPlayTwoClips): Removed redundant try/catch block. (testSupportedControls): New test. Checks that PulseAudioClip supports at least two controls. (testMixerKnowsAboutOpenClips): New test. Checks that open clips are added to the mixer's list of open source lines. * unittests/org/classpath/icedtea/pulseaudio/PulseAudioMixerRawTest.java Fixed formatting. * unittests/org/classpath/icedtea/pulseaudio/PulseAudioMixerTest.java (testOpeneingAgain): Fixed test to detect the IllegalStateException thrown. * unittests/org/classpath/icedtea/pulseaudio/PulseAudioSourcePortTest.java (tearDown): New function. Close the mixer at the end of the test. * unittests/org/classpath/icedtea/pulseaudio/PulseSourceDataLineTest.java (testMixerKnowsAoubtOpenLines): Modified the test to check that the lines are actually the same.
author Omair Majid <omajid@redhat.com>
date Wed, 03 Sep 2008 15:11:46 -0400
parents b191c8f8cb2d
children 56be77eae7fe
files src/java/org/classpath/icedtea/pulseaudio/PulseAudioClip.java src/java/org/classpath/icedtea/pulseaudio/PulseAudioDataLine.java src/java/org/classpath/icedtea/pulseaudio/PulseAudioLine.java src/java/org/classpath/icedtea/pulseaudio/PulseAudioMixer.java src/java/org/classpath/icedtea/pulseaudio/PulseAudioPort.java src/java/org/classpath/icedtea/pulseaudio/PulseAudioSourceDataLine.java src/java/org/classpath/icedtea/pulseaudio/PulseAudioTargetDataLine.java src/java/org/classpath/icedtea/pulseaudio/PulseAudioVolumeControl.java src/java/org/classpath/icedtea/pulseaudio/Stream.java unittests/org/classpath/icedtea/pulseaudio/PulseAudioClipTest.java unittests/org/classpath/icedtea/pulseaudio/PulseAudioMixerRawTest.java unittests/org/classpath/icedtea/pulseaudio/PulseAudioMixerTest.java unittests/org/classpath/icedtea/pulseaudio/PulseAudioSourcePortTest.java unittests/org/classpath/icedtea/pulseaudio/PulseAudioTargetDataLineTest.java unittests/org/classpath/icedtea/pulseaudio/PulseAudioTargetPortTest.java unittests/org/classpath/icedtea/pulseaudio/PulseSourceDataLineTest.java
diffstat 16 files changed, 359 insertions(+), 410 deletions(-) [+]
line wrap: on
line diff
--- a/src/java/org/classpath/icedtea/pulseaudio/PulseAudioClip.java	Wed Sep 03 10:47:27 2008 -0400
+++ b/src/java/org/classpath/icedtea/pulseaudio/PulseAudioClip.java	Wed Sep 03 15:11:46 2008 -0400
@@ -38,21 +38,13 @@
 package org.classpath.icedtea.pulseaudio;
 
 import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
 import java.util.concurrent.Semaphore;
 
 import javax.sound.sampled.AudioFormat;
 import javax.sound.sampled.AudioInputStream;
 import javax.sound.sampled.AudioSystem;
-import javax.sound.sampled.BooleanControl;
 import javax.sound.sampled.Clip;
-import javax.sound.sampled.Control;
-import javax.sound.sampled.DataLine;
-import javax.sound.sampled.FloatControl;
-import javax.sound.sampled.LineListener;
 import javax.sound.sampled.LineUnavailableException;
-import javax.sound.sampled.Control.Type;
 
 import org.classpath.icedtea.pulseaudio.Stream.WriteListener;
 
@@ -73,8 +65,6 @@
 
 	public static final String DEFAULT_CLIP_NAME = "Clip";
 
-	private List<Control> controls = null;
-
 	private Object clipLock = new Object();
 	private boolean clipThreadStarted;
 	private int loopsLeft;
@@ -176,12 +166,11 @@
 			AudioFormat defaultFormat) {
 		supportedFormats = formats;
 		this.eventLoop = eventLoop;
-		this.lineListeners = new ArrayList<LineListener>();
 		this.defaultFormat = defaultFormat;
 		this.currentFormat = defaultFormat;
+		this.volume = PulseAudioVolumeControl.MAX_VOLUME;
+
 		clipThread = new ClipThread();
-		this.volume = PulseAudioVolumeControl.MAX_VOLUME;
-		controls = new ArrayList<Control>();
 
 	}
 
@@ -241,46 +230,6 @@
 	}
 
 	@Override
-	public int getBufferSize() {
-		if (!isOpen) {
-			return DEFAULT_BUFFER_SIZE;
-		}
-		return bufferSize;
-	}
-
-	@Override
-	public Control getControl(Type control) {
-		if (isOpen) {
-			if (control.getClass() == BooleanControl.Type.MUTE.getClass()) {
-				return controls.get(1);
-			}
-
-			if (control.getClass() == FloatControl.Type.VOLUME.getClass()) {
-				return controls.get(0);
-			}
-		}
-		throw new IllegalArgumentException(control.toString()
-				+ " not supported");
-	}
-
-	@Override
-	public Control[] getControls() {
-		if (!isOpen) {
-			return new Control[] {};
-		}
-
-		return (Control[]) controls.toArray(new Control[0]);
-	}
-
-	@Override
-	public AudioFormat getFormat() {
-		if (!isOpen) {
-			return defaultFormat;
-		}
-		return currentFormat;
-	}
-
-	@Override
 	public int getFrameLength() {
 		return frameCount;
 	}
@@ -291,18 +240,6 @@
 	}
 
 	@Override
-	public float getLevel() {
-		return AudioSystem.NOT_SPECIFIED;
-	}
-
-	@Override
-	public javax.sound.sampled.Line.Info getLineInfo() {
-		return new DataLine.Info(this.getClass(), supportedFormats,
-				StreamBufferAttributes.MIN_VALUE,
-				StreamBufferAttributes.MAX_VALUE);
-	}
-
-	@Override
 	public long getLongFramePosition() {
 		synchronized (clipLock) {
 			return framesSinceOpen;
@@ -327,22 +264,6 @@
 	}
 
 	@Override
-	public boolean isControlSupported(Type control) {
-		return false;
-	}
-
-	@Override
-	public boolean isOpen() {
-		return isOpen;
-	}
-
-	@Override
-	public boolean isRunning() {
-		// really confused about what this is supposed to do
-		return isActive();
-	}
-
-	@Override
 	public void loop(int count) {
 
 		System.out.println("Loop " + count + " called");
@@ -429,11 +350,6 @@
 	}
 
 	@Override
-	public void removeLineListener(LineListener listener) {
-		lineListeners.remove(listener);
-	}
-
-	@Override
 	public void setFramePosition(int frames) {
 
 		if (frames > frameCount) {
--- a/src/java/org/classpath/icedtea/pulseaudio/PulseAudioDataLine.java	Wed Sep 03 10:47:27 2008 -0400
+++ b/src/java/org/classpath/icedtea/pulseaudio/PulseAudioDataLine.java	Wed Sep 03 15:11:46 2008 -0400
@@ -53,9 +53,6 @@
 
 	protected String streamName = "Java Stream";
 
-	// true between open() and close(). ie represents when a line has acquire
-	// resources
-
 	// true between start() and stop()
 	protected boolean isStarted = false;
 
@@ -76,9 +73,6 @@
 
 	public void open(AudioFormat format, int bufferSize)
 			throws LineUnavailableException {
-		if (isOpen) {
-			throw new IllegalStateException("Line is already open");
-		}
 
 		for (AudioFormat myFormat : supportedFormats) {
 			if (format.matches(myFormat)) {
@@ -90,7 +84,7 @@
 
 				}
 				currentFormat = format;
-				isOpen = true;
+				super.open();
 			}
 		}
 		// no matches found
@@ -122,6 +116,7 @@
 		Stream.UnderflowListener stoppedListener = new Stream.UnderflowListener() {
 			@Override
 			public void update() {
+				isEngagedInIo = false;
 				fireLineEvent(new LineEvent(PulseAudioDataLine.this,
 						LineEvent.Type.STOP, AudioSystem.NOT_SPECIFIED));
 			}
@@ -131,6 +126,7 @@
 		Stream.PlaybackStartedListener startedListener = new Stream.PlaybackStartedListener() {
 			@Override
 			public void update() {
+				isEngagedInIo = true;
 				fireLineEvent(new LineEvent(PulseAudioDataLine.this,
 						LineEvent.Type.START, AudioSystem.NOT_SPECIFIED));
 			}
@@ -170,51 +166,31 @@
 	}
 
 	public void close() {
-		// FIXME what should be done here
-		assert (isOpen);
+
+		super.close();
 
 		synchronized (eventLoop.threadLock) {
-			// drain();
+			drain();
 			stream.disconnect();
 		}
 
 		try {
 			semaphore.acquire();
 		} catch (InterruptedException e) {
-			// throw new LineUnavailableException("unable to prepare
-			// stream");
+			throw new RuntimeException("unable to prepare stream");
 		}
 
-		isOpen = false;
-
 	}
 
 	public void start() {
-		// if (isPaused) {
-		// synchronized (eventLoop.threadLock) {
-		// stream.cork(false);
-		// }
-		// isPaused = false;
-		// }
-
 		isStarted = true;
-
-		/*
-		 * for(LineListener l :listeners) { l.update(new LineEvent(this,
-		 * LineEvent.Type.START, 0)); }
-		 */
-
 	}
 
 	public void stop() {
-		// synchronized (eventLoop.threadLock) {
-		// stream.cork(true);
-		// }
-		// isPaused = true;
+		isStarted = false;
+	}
 
-		isStarted = false;
-
-	}
+	// A BIG FIXME !
 
 	/*
 	 * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4791152 :
@@ -245,4 +221,30 @@
 		return stream;
 	}
 
+	@Override
+	public int getBufferSize() {
+		if (!isOpen) {
+			return DEFAULT_BUFFER_SIZE;
+		}
+		return bufferSize;
+	}
+
+	public javax.sound.sampled.Line.Info getLineInfo() {
+		return new DataLine.Info(this.getClass(), supportedFormats,
+				StreamBufferAttributes.MIN_VALUE,
+				StreamBufferAttributes.MAX_VALUE);
+	}
+
+	@Override
+	public AudioFormat getFormat() {
+		if (!isOpen) {
+			return defaultFormat;
+		}
+		return currentFormat;
+	}
+
+	public float getLevel() {
+		return AudioSystem.NOT_SPECIFIED;
+	}
+
 }
--- a/src/java/org/classpath/icedtea/pulseaudio/PulseAudioLine.java	Wed Sep 03 10:47:27 2008 -0400
+++ b/src/java/org/classpath/icedtea/pulseaudio/PulseAudioLine.java	Wed Sep 03 15:11:46 2008 -0400
@@ -40,20 +40,33 @@
 import java.util.ArrayList;
 import java.util.List;
 
+import javax.sound.sampled.Control;
+import javax.sound.sampled.Line;
 import javax.sound.sampled.LineEvent;
 import javax.sound.sampled.LineListener;
+import javax.sound.sampled.LineUnavailableException;
+import javax.sound.sampled.Control.Type;
 
-abstract class PulseAudioLine {
+abstract class PulseAudioLine implements Line {
 
 	protected List<LineListener> lineListeners = new ArrayList<LineListener>();
+	protected List<Control> controls = new ArrayList<Control>();
+
+	// true between open() and close(). ie represents when a line has acquire
+	// resources
 	protected boolean isOpen = false;
 
 	public void addLineListener(LineListener listener) {
 		this.lineListeners.add(listener);
 	}
 
-	public void removeLineListener(LineListener listener) {
-		this.lineListeners.remove(listener);
+	@Override
+	public void close() {
+		if (!isOpen) {
+			throw new IllegalStateException("Line is not open");
+		}
+
+		isOpen = false;
 	}
 
 	protected void fireLineEvent(LineEvent e) {
@@ -62,8 +75,51 @@
 		}
 	}
 
+	@Override
+	public Control getControl(Type control) {
+		if (isOpen) {
+			for (Control aControl : controls) {
+				if (aControl.getType() == control) {
+					return aControl;
+				}
+			}
+		}
+		throw new IllegalArgumentException(control.toString()
+				+ " not supported");
+	}
+
+	@Override
+	public Control[] getControls() {
+		if (!isOpen) {
+			return new Control[] {};
+		}
+
+		return (Control[]) controls.toArray(new Control[0]);
+	}
+
+	public boolean isControlSupported(Type control) {
+		for (Control myControl : controls) {
+			if (myControl.getType().getClass() == control.getClass()) {
+				return true;
+			}
+		}
+		return false;
+	}
+
 	public boolean isOpen() {
 		return isOpen;
 	}
 
+	@Override
+	public void open() throws LineUnavailableException {
+		if (isOpen) {
+			throw new IllegalStateException("Line is already open");
+		}
+		isOpen = true;
+	}
+
+	public void removeLineListener(LineListener listener) {
+		lineListeners.remove(listener);
+	}
+
 }
--- a/src/java/org/classpath/icedtea/pulseaudio/PulseAudioMixer.java	Wed Sep 03 10:47:27 2008 -0400
+++ b/src/java/org/classpath/icedtea/pulseaudio/PulseAudioMixer.java	Wed Sep 03 15:11:46 2008 -0400
@@ -119,15 +119,17 @@
 
 		Map<String, Object> properties;
 
-		int[] channelSizes = new int[] { 1, 2, 5 };
+		/*
+		 * frameSize = sample size (in bytes, not bits) x # of channels ^ From
+		 * PulseAudio's sources
+		 * http://git.0pointer.de/?p=pulseaudio.git;a=blob;f=src/pulse/sample.c;h=93da2465f4301e27af4976e82737c3a048124a68;hb=82ea8dde8abc51165a781c69bc3b38034d62d969#l63
+		 */
+
+		int[] channelSizes = new int[] { 1, 2, 5, 6, 8 };
 		for (int channelSize : channelSizes) {
 			properties = new HashMap<String, Object>();
 			properties.put(PULSEAUDIO_FORMAT_KEY, "PA_SAMPLE_U8");
 
-			// frameSize = sample size (in bytes, not bits) x # of channels
-			// ^ that's from PulseAudio sources, so it will pretty much break
-			// as soon as they change something
-			// FIXME ^
 			int sampleSize = 8; // in bits
 			AudioFormat PA_SAMPLE_U8 = new AudioFormat(Encoding.PCM_UNSIGNED, // encoding
 					AudioSystem.NOT_SPECIFIED, // sample rate
@@ -145,11 +147,6 @@
 			properties = new HashMap<String, Object>();
 			properties.put(PULSEAUDIO_FORMAT_KEY, "PA_SAMPLE_ALAW");
 
-			// frameSize = sample size (in bytes, not bits) x # of channels
-			// ^ that's from PulseAudio sources, so it will pretty much break
-			// as soon as they change something
-			// FIXME ^
-
 			int sampleSize = 8;
 			final AudioFormat PA_SAMPLE_ALAW = new AudioFormat(Encoding.ALAW, // encoding
 					AudioSystem.NOT_SPECIFIED, // sample rate
@@ -167,11 +164,6 @@
 			properties = new HashMap<String, Object>();
 			properties.put(PULSEAUDIO_FORMAT_KEY, "PA_SAMPLE_ULAW");
 
-			// frameSize = sample size (in bytes, not bits) x # of channels
-			// ^ that's from PulseAudio sources, so it will pretty much break
-			// as soon as they change something
-			// FIXME ^
-
 			int sampleSize = 8;
 			final AudioFormat PA_SAMPLE_ULAW = new AudioFormat(Encoding.ULAW, // encoding
 					AudioSystem.NOT_SPECIFIED, // sample rate
@@ -189,11 +181,6 @@
 			properties = new HashMap<String, Object>();
 			properties.put(PULSEAUDIO_FORMAT_KEY, "PA_SAMPLE_S16BE");
 
-			// frameSize = sample size (in bytes, not bits) x # of channels
-			// ^ that's from PulseAudio sources, so it will pretty much break
-			// as soon as they change something
-			// FIXME ^
-
 			int sampleSize = 16;
 			final AudioFormat PA_SAMPLE_S16BE = new AudioFormat(
 					Encoding.PCM_SIGNED, // encoding
@@ -212,11 +199,6 @@
 			properties = new HashMap<String, Object>();
 			properties.put(PULSEAUDIO_FORMAT_KEY, "PA_SAMPLE_S16LE");
 
-			// frameSize = sample size (in bytes, not bits) x # of channels
-			// ^ that's from PulseAudio sources, so it will pretty much break
-			// as soon as they change something
-			// FIXME ^
-
 			int sampleSize = 16;
 			final AudioFormat A_SAMPLE_S16LE = new AudioFormat(
 					Encoding.PCM_SIGNED, // encoding
@@ -235,11 +217,6 @@
 			properties = new HashMap<String, Object>();
 			properties.put(PULSEAUDIO_FORMAT_KEY, "PA_SAMPLE_S32BE");
 
-			// frameSize = sample size (in bytes, not bits) x # of channels
-			// ^ that's from PulseAudio sources, so it will pretty much break
-			// as soon as they change something
-			// FIXME ^
-
 			int sampleSize = 32;
 			final AudioFormat PA_SAMPLE_S32BE = new AudioFormat(
 					Encoding.PCM_SIGNED, // encoding
@@ -258,11 +235,6 @@
 			properties = new HashMap<String, Object>();
 			properties.put(PULSEAUDIO_FORMAT_KEY, "PA_SAMPLE_S32LE");
 
-			// frameSize = sample size (in bytes, not bits) x # of channels
-			// ^ that's from PulseAudio sources, so it will pretty much break
-			// as soon as they change something
-			// FIXME ^
-
 			int sampleSize = 32;
 			final AudioFormat PA_SAMPLE_S32LE = new AudioFormat(
 					Encoding.PCM_SIGNED, // encoding
@@ -284,7 +256,7 @@
 	public Line getLine(javax.sound.sampled.Line.Info info)
 			throws LineUnavailableException {
 
-		if (!this.isOpen) {
+		if (!isOpen) {
 			throw new LineUnavailableException();
 		}
 
@@ -331,12 +303,8 @@
 			return new PulseAudioClip(eventLoop, formats, defaultFormat);
 		}
 
-		String portName;
-		boolean isSource;
-
 		if (Port.Info.class.isInstance(info)) {
 			Port.Info portInfo = (Port.Info) info;
-			portName = portInfo.getName();
 			if (portInfo.isSource()) {
 				return new PulseAudioSourcePort(portInfo.getName(), eventLoop);
 			} else {
@@ -344,7 +312,7 @@
 			}
 		}
 
-		throw new IllegalArgumentException();
+		throw new IllegalArgumentException("No matching lines found");
 
 	}
 
@@ -519,7 +487,7 @@
 
 	@Override
 	public boolean isOpen() {
-		return this.isOpen;
+		return isOpen;
 	}
 
 	@Override
@@ -568,8 +536,9 @@
 
 	synchronized public void openRemote(String appName, String host,
 			Integer port) throws UnknownHostException, LineUnavailableException {
-		if (this.isOpen) {
-			return;
+
+		if (isOpen) {
+			throw new IllegalStateException("Mixer is already open");
 		}
 
 		InetAddress addr = InetAddress.getAllByName(host)[0];
@@ -719,11 +688,11 @@
 		sourceLines.remove(line);
 	}
 
-	void addTargetDataLine(PulseAudioLine line) {
+	void addTargetLine(PulseAudioLine line) {
 		targetLines.add(line);
 	}
 
-	void removeTargetDataLine(PulseAudioLine line) {
+	void removeTargetLine(PulseAudioLine line) {
 		targetLines.remove(line);
 	}
 
--- a/src/java/org/classpath/icedtea/pulseaudio/PulseAudioPort.java	Wed Sep 03 10:47:27 2008 -0400
+++ b/src/java/org/classpath/icedtea/pulseaudio/PulseAudioPort.java	Wed Sep 03 15:11:46 2008 -0400
@@ -38,24 +38,28 @@
 package org.classpath.icedtea.pulseaudio;
 
 import javax.sound.sampled.AudioSystem;
-import javax.sound.sampled.Control;
-import javax.sound.sampled.FloatControl;
 import javax.sound.sampled.LineEvent;
-import javax.sound.sampled.LineListener;
 import javax.sound.sampled.LineUnavailableException;
 import javax.sound.sampled.Port;
-import javax.sound.sampled.Control.Type;
 
 public abstract class PulseAudioPort extends PulseAudioLine implements Port,
 		PulseAudioPlaybackLine {
 
 	private String name;
+
+	/*
+	 * Variable used in native code
+	 */
+	@SuppressWarnings("unused")
 	private long contextPointer;
+	@SuppressWarnings("unused")
+	private int channels;
+
 	private EventLoop eventLoop;
-	private int channels;
+
 	private float volume;
 	private boolean muted;
-	private Control[] controls = null;
+
 	private PulseAudioMuteControl muteControl;
 	private PulseAudioVolumeControl volumeControl;
 
@@ -65,11 +69,10 @@
 		this.eventLoop = eventLoop;
 		updateVolumeInfo();
 
-		controls = new Control[2];
 		volumeControl = new PulseAudioVolumeControl(this, eventLoop);
-		controls[0] = volumeControl;
+		controls.add(volumeControl);
 		muteControl = new PulseAudioMuteControl(this, volumeControl);
-		controls[1] = muteControl;
+		controls.add(muteControl);
 		isOpen = true;
 
 		System.out.println("Opened Target Port " + name);
@@ -119,41 +122,9 @@
 				AudioSystem.NOT_SPECIFIED));
 	}
 
-	public Control getControl(Type control) {
-		if (!isOpen) {
-			throw new IllegalArgumentException(
-					"Controls only supported when line is open");
-		}
-
-		for (int i = 0; i < controls.length; i++) {
-			if (controls[i].getType().getClass() == control.getClass()) {
-				return controls[i];
-			}
-		}
-		throw new IllegalArgumentException("Unsupported control type");
-	}
-
-	public Control[] getControls() {
-		if (isOpen) {
-			return controls;
-		} else {
-			return new Control[] {};
-		}
-
-	}
-
 	@Override
 	public abstract javax.sound.sampled.Line.Info getLineInfo();
 
-	public boolean isControlSupported(Type control) {
-		for (Control myControl : controls) {
-			if (myControl.getType().getClass() == control.getClass()) {
-				return true;
-			}
-		}
-		return false;
-	}
-
 	@Override
 	public void open() throws LineUnavailableException {
 		native_setVolume(volume);
--- a/src/java/org/classpath/icedtea/pulseaudio/PulseAudioSourceDataLine.java	Wed Sep 03 10:47:27 2008 -0400
+++ b/src/java/org/classpath/icedtea/pulseaudio/PulseAudioSourceDataLine.java	Wed Sep 03 15:11:46 2008 -0400
@@ -40,20 +40,15 @@
 import java.util.ArrayList;
 
 import javax.sound.sampled.AudioFormat;
-import javax.sound.sampled.AudioSystem;
-import javax.sound.sampled.Control;
-import javax.sound.sampled.DataLine;
 import javax.sound.sampled.LineListener;
 import javax.sound.sampled.LineUnavailableException;
 import javax.sound.sampled.SourceDataLine;
-import javax.sound.sampled.Control.Type;
 
 import org.classpath.icedtea.pulseaudio.Stream.WriteListener;
 
 public class PulseAudioSourceDataLine extends PulseAudioDataLine implements
 		SourceDataLine, PulseAudioPlaybackLine {
 
-	private Control[] controls = null;
 	private PulseAudioMuteControl muteControl;
 	private PulseAudioVolumeControl volumeControl;
 	private boolean muted;
@@ -76,11 +71,11 @@
 			throws LineUnavailableException {
 
 		super.open(format, bufferSize);
-		controls = new Control[2];
+
 		volumeControl = new PulseAudioVolumeControl(this, eventLoop);
-		controls[0] = volumeControl;
+		controls.add(volumeControl);
 		muteControl = new PulseAudioMuteControl(this, volumeControl);
-		controls[1] = muteControl;
+		controls.add(muteControl);
 
 		PulseAudioMixer parentMixer = PulseAudioMixer.getInstance();
 		parentMixer.addSourceLine(this);
@@ -120,6 +115,10 @@
 	@Override
 	public int write(byte[] data, int offset, int length) {
 
+		if (!isOpen) {
+			throw new IllegalStateException("must call open() before write()");
+		}
+
 		if (!isStarted) {
 			throw new IllegalStateException("must call start() before write()");
 		}
@@ -207,28 +206,10 @@
 		}
 	};
 
-	public int getBufferSize() {
-		if (!isOpen) {
-			return DEFAULT_BUFFER_SIZE;
-		}
-		return bufferSize;
-	}
-
-	public AudioFormat getFormat() {
-		if (!isOpen) {
-			return defaultFormat;
-		}
-		return currentFormat;
-	}
-
 	public int getFramePosition() {
 		return (int) currentFramePosition;
 	}
 
-	public float getLevel() {
-		return AudioSystem.NOT_SPECIFIED;
-	}
-
 	public long getLongFramePosition() {
 		return currentFramePosition;
 	}
@@ -241,44 +222,6 @@
 		return microseconds;
 	}
 
-	public Control getControl(Type control) {
-		if (!isOpen) {
-			throw new IllegalArgumentException(
-					"Controls only supported when line is open");
-		}
-
-		for (int i = 0; i < controls.length; i++) {
-			if (controls[i].getType().getClass() == control.getClass()) {
-				return controls[i];
-			}
-		}
-		throw new IllegalArgumentException("Unsupported control type");
-	}
-
-	public Control[] getControls() {
-		if (isOpen) {
-			return controls;
-		} else {
-			return new Control[] {};
-		}
-
-	}
-
-	public javax.sound.sampled.Line.Info getLineInfo() {
-		return new DataLine.Info(this.getClass(), supportedFormats,
-				StreamBufferAttributes.MIN_VALUE,
-				StreamBufferAttributes.MAX_VALUE);
-	}
-
-	public boolean isControlSupported(Type control) {
-		for (Control myControl : controls) {
-			if (myControl.getType().getClass() == control.getClass()) {
-				return true;
-			}
-		}
-		return false;
-	}
-
 	@Override
 	public void drain() {
 		Operation operation;
--- a/src/java/org/classpath/icedtea/pulseaudio/PulseAudioTargetDataLine.java	Wed Sep 03 10:47:27 2008 -0400
+++ b/src/java/org/classpath/icedtea/pulseaudio/PulseAudioTargetDataLine.java	Wed Sep 03 15:11:46 2008 -0400
@@ -37,16 +37,9 @@
 
 package org.classpath.icedtea.pulseaudio;
 
-import java.util.ArrayList;
-
 import javax.sound.sampled.AudioFormat;
-import javax.sound.sampled.AudioSystem;
-import javax.sound.sampled.Control;
-import javax.sound.sampled.DataLine;
-import javax.sound.sampled.LineListener;
 import javax.sound.sampled.LineUnavailableException;
 import javax.sound.sampled.TargetDataLine;
-import javax.sound.sampled.Control.Type;
 
 public class PulseAudioTargetDataLine extends PulseAudioDataLine implements
 		TargetDataLine {
@@ -60,7 +53,6 @@
 			AudioFormat defaultFormat) {
 		supportedFormats = formats;
 		this.eventLoop = eventLoop;
-		this.lineListeners = new ArrayList<LineListener>();
 		this.defaultFormat = defaultFormat;
 		this.currentFormat = defaultFormat;
 
@@ -69,7 +61,7 @@
 	@Override
 	public void close() {
 		PulseAudioMixer parentMixer = PulseAudioMixer.getInstance();
-		parentMixer.removeTargetDataLine(this);
+		parentMixer.removeTargetLine(this);
 
 		super.close();
 	}
@@ -80,7 +72,7 @@
 		super.open(format, bufferSize);
 
 		PulseAudioMixer parentMixer = PulseAudioMixer.getInstance();
-		parentMixer.addTargetDataLine(this);
+		parentMixer.addTargetLine(this);
 	}
 
 	protected void connectLine(int bufferSize) {
@@ -93,8 +85,13 @@
 
 	@Override
 	public int read(byte[] data, int offset, int length) {
+
+		if (!isOpen) {
+			throw new IllegalStateException("must call open() before read()");
+		}
+
 		if (!isStarted) {
-			throw new IllegalStateException("must call open before read()");
+			throw new IllegalStateException("must call start() before read()");
 		}
 
 		int frameSize = currentFormat.getFrameSize();
@@ -145,14 +142,12 @@
 
 	@Override
 	public void drain() {
-		// TODO Auto-generated method stub
-
+		// FIXME how do we drain a target data line?
 	}
 
 	@Override
 	public void flush() {
 		// TODO Auto-generated method stub
-
 	}
 
 	public int available() {
@@ -161,25 +156,10 @@
 		}
 	}
 
-	public int getBufferSize() {
-		return bufferSize;
-	}
-
-	public AudioFormat getFormat() {
-		if (!isOpen) {
-			return defaultFormat;
-		}
-		return currentFormat;
-	}
-
 	public int getFramePosition() {
 		return (int) currentFramePosition;
 	}
 
-	public float getLevel() {
-		return AudioSystem.NOT_SPECIFIED;
-	}
-
 	public long getLongFramePosition() {
 		return currentFramePosition;
 	}
@@ -188,23 +168,4 @@
 		return (long) (currentFramePosition / currentFormat.getFrameRate());
 	}
 
-	public Control getControl(Type control) {
-		throw new IllegalArgumentException(
-				"PulseAudioTargetDataLine does not support any controls");
-	}
-
-	public Control[] getControls() {
-		return new Control[] {};
-	}
-
-	public javax.sound.sampled.Line.Info getLineInfo() {
-		return new DataLine.Info(this.getClass(), supportedFormats,
-				StreamBufferAttributes.MIN_VALUE,
-				StreamBufferAttributes.MAX_VALUE);
-	}
-
-	public boolean isControlSupported(Type control) {
-		return false;
-	}
-
 }
\ No newline at end of file
--- a/src/java/org/classpath/icedtea/pulseaudio/PulseAudioVolumeControl.java	Wed Sep 03 10:47:27 2008 -0400
+++ b/src/java/org/classpath/icedtea/pulseaudio/PulseAudioVolumeControl.java	Wed Sep 03 15:11:46 2008 -0400
@@ -46,14 +46,15 @@
 	public static final int MAX_VOLUME = 65536;
 	public static final int MIN_VOLUME = 0;
 
-	protected PulseAudioVolumeControl(PulseAudioPlaybackLine line, EventLoop eventLoop) {
-		super(FloatControl.Type.VOLUME, MIN_VOLUME, MAX_VOLUME, 1, -1, line.getVolume(), 
-				"pulseaudio units", "Volume Off",
+	protected PulseAudioVolumeControl(PulseAudioPlaybackLine line,
+			EventLoop eventLoop) {
+		super(FloatControl.Type.VOLUME, MIN_VOLUME, MAX_VOLUME, 1, -1, line
+				.getVolume(), "pulseaudio units", "Volume Off",
 				"Default Volume", "Full Volume");
 		this.line = line;
 		this.eventLoop = eventLoop;
 	}
-	
+
 	@SuppressWarnings("unused")
 	private long streamPointer;
 
@@ -65,8 +66,7 @@
 			String library = new java.io.File(".").getCanonicalPath()
 					+ java.io.File.separatorChar
 					+ System.mapLibraryName("pulse-java");
-			System.out.println(PulseAudioVolumeControl.class
-					.getCanonicalName()
+			System.out.println(PulseAudioVolumeControl.class.getCanonicalName()
 					+ ": " + library);
 			System.load(library);
 		} catch (IOException e) {
@@ -75,12 +75,11 @@
 	}
 
 	public synchronized void setValue(float newValue) {
-		if (newValue > MAX_VOLUME
-				|| newValue < MIN_VOLUME) {
+		if (newValue > MAX_VOLUME || newValue < MIN_VOLUME) {
 			throw new IllegalArgumentException("invalid value");
 		}
-		
-		if(!line.isOpen()) {
+
+		if (!line.isOpen()) {
 			return;
 		}
 
@@ -106,7 +105,4 @@
 		return line.getVolume();
 	}
 
-	;
 }
-
-
--- a/src/java/org/classpath/icedtea/pulseaudio/Stream.java	Wed Sep 03 10:47:27 2008 -0400
+++ b/src/java/org/classpath/icedtea/pulseaudio/Stream.java	Wed Sep 03 15:11:46 2008 -0400
@@ -559,7 +559,6 @@
 
 	@SuppressWarnings("unused")
 	private void underflowCallback() {
-		System.out.println("DEBUG: underflowCallback called");
 		synchronized (underflowListeners) {
 			for (UnderflowListener listener : underflowListeners) {
 				listener.update();
--- a/unittests/org/classpath/icedtea/pulseaudio/PulseAudioClipTest.java	Wed Sep 03 10:47:27 2008 -0400
+++ b/unittests/org/classpath/icedtea/pulseaudio/PulseAudioClipTest.java	Wed Sep 03 15:11:46 2008 -0400
@@ -39,13 +39,15 @@
 
 import java.io.File;
 import java.io.IOException;
-import java.util.concurrent.Semaphore;
 
 import javax.sound.sampled.AudioFormat;
 import javax.sound.sampled.AudioInputStream;
 import javax.sound.sampled.AudioSystem;
 import javax.sound.sampled.Clip;
+import javax.sound.sampled.Control;
 import javax.sound.sampled.Line;
+import javax.sound.sampled.LineEvent;
+import javax.sound.sampled.LineListener;
 import javax.sound.sampled.LineUnavailableException;
 import javax.sound.sampled.Mixer;
 import javax.sound.sampled.UnsupportedAudioFileException;
@@ -158,6 +160,81 @@
 
 	}
 
+	int opened = 0;
+
+	@Test
+	public void testOpenEvent() throws LineUnavailableException,
+			UnsupportedAudioFileException, IOException {
+		Clip clip = (Clip) mixer.getLine(new Line.Info(Clip.class));
+		File soundFile = new File("testsounds/startup.wav");
+		AudioInputStream audioInputStream = AudioSystem
+				.getAudioInputStream(soundFile);
+
+		opened = 0;
+
+		LineListener openListener = new LineListener() {
+
+			@Override
+			public void update(LineEvent event) {
+				if (event.getType() == LineEvent.Type.OPEN) {
+					opened++;
+				}
+			}
+
+		};
+
+		clip.addLineListener(openListener);
+		clip.open(audioInputStream);
+		clip.close();
+		clip.removeLineListener(openListener);
+
+		Assert.assertEquals(1, opened);
+
+	}
+
+	int closed = 0;
+
+	@Test
+	public void testCloseEvent() throws LineUnavailableException,
+			UnsupportedAudioFileException, IOException {
+
+		System.out.println("This tests the CLOSE event");
+
+		Clip clip = (Clip) mixer.getLine(new Line.Info(Clip.class));
+		File soundFile = new File("testsounds/startup.wav");
+		AudioInputStream audioInputStream = AudioSystem
+				.getAudioInputStream(soundFile);
+
+		closed = 0;
+
+		LineListener closeListener = new LineListener() {
+
+			@Override
+			public void update(LineEvent event) {
+				if (event.getType() == LineEvent.Type.CLOSE) {
+					closed++;
+				}
+			}
+
+		};
+
+		clip.addLineListener(closeListener);
+		clip.open(audioInputStream);
+		clip.close();
+
+		try {
+			// wait a little while before removing the listener
+			// to ensure it is called before we remove it
+			Thread.sleep(100);
+		} catch (InterruptedException e) {
+			e.printStackTrace();
+		}
+		clip.removeLineListener(closeListener);
+
+		Assert.assertEquals(1, closed);
+
+	}
+
 	@Test
 	public void testLoop0Clip() throws LineUnavailableException, IOException,
 			UnsupportedAudioFileException {
@@ -194,35 +271,64 @@
 	 */
 
 	@Test
-	public void testPlayTwoClips() {
-		try {
-			Clip clip1 = (Clip) mixer.getLine(new Line.Info(Clip.class));
-			File soundFile1 = new File("testsounds/startup.wav");
-			AudioInputStream audioInputStream1 = AudioSystem
-					.getAudioInputStream(soundFile1);
-			clip1.open(audioInputStream1);
+	public void testPlayTwoClips() throws LineUnavailableException,
+			UnsupportedAudioFileException, IOException {
+		Clip clip1 = (Clip) mixer.getLine(new Line.Info(Clip.class));
+		File soundFile1 = new File("testsounds/startup.wav");
+		AudioInputStream audioInputStream1 = AudioSystem
+				.getAudioInputStream(soundFile1);
+		clip1.open(audioInputStream1);
 
-			Clip clip2 = (Clip) mixer.getLine(new Line.Info(Clip.class));
-			File soundFile2 = new File("testsounds/logout.wav");
-			AudioInputStream audioInputStream2 = AudioSystem
-					.getAudioInputStream(soundFile2);
-			clip2.open(audioInputStream2);
+		Clip clip2 = (Clip) mixer.getLine(new Line.Info(Clip.class));
+		File soundFile2 = new File("testsounds/logout.wav");
+		AudioInputStream audioInputStream2 = AudioSystem
+				.getAudioInputStream(soundFile2);
+		clip2.open(audioInputStream2);
+
+		clip1.start();
+		clip2.start();
+
+		clip1.drain();
+		clip2.drain();
+
+		clip1.close();
+		clip2.close();
+
+	}
 
-			Semaphore done = new Semaphore(0);
+	@Test
+	public void testSupportedControls() throws LineUnavailableException,
+			UnsupportedAudioFileException, IOException {
+		Clip clip = (Clip) mixer.getLine(new Line.Info(Clip.class));
+		File soundFile1 = new File("testsounds/startup.wav");
+		AudioInputStream audioInputStream1 = AudioSystem
+				.getAudioInputStream(soundFile1);
+		clip.open(audioInputStream1);
+
+		Control[] controls = clip.getControls();
+		Assert.assertNotNull(controls);
+		Assert.assertTrue(controls.length >= 2);
+		for (Control control : controls) {
+			Assert.assertNotNull(control);
+		}
 
-			clip1.start();
-			clip2.start();
-			
-			clip1.drain();
-			clip2.drain();
-			
-			clip1.close();
-			clip2.close();
-			
+		clip.close();
+	}
 
-		} catch (Exception e) {
-			e.printStackTrace();
-		}
+	@Test
+	public void testMixerKnowsAboutOpenClips() throws LineUnavailableException,
+			UnsupportedAudioFileException, IOException {
+		Clip clip = (Clip) mixer.getLine(new Line.Info(Clip.class));
+		File soundFile1 = new File("testsounds/startup.wav");
+		AudioInputStream audioInputStream1 = AudioSystem
+				.getAudioInputStream(soundFile1);
+
+		Assert.assertEquals(0, mixer.getSourceLines().length);
+		clip.open(audioInputStream1);
+		Assert.assertEquals(1, mixer.getSourceLines().length);
+		Assert.assertEquals(clip, mixer.getSourceLines()[0]);
+		clip.close();
+		Assert.assertEquals(0, mixer.getSourceLines().length);
 
 	}
 
--- a/unittests/org/classpath/icedtea/pulseaudio/PulseAudioMixerRawTest.java	Wed Sep 03 10:47:27 2008 -0400
+++ b/unittests/org/classpath/icedtea/pulseaudio/PulseAudioMixerRawTest.java	Wed Sep 03 15:11:46 2008 -0400
@@ -33,7 +33,7 @@
 this exception to your version of the library, but you are not
 obligated to do so.  If you do not wish to do so, delete this
 exception statement from your version.
-*/
+ */
 
 package org.classpath.icedtea.pulseaudio;
 
@@ -47,24 +47,22 @@
 public class PulseAudioMixerRawTest {
 
 	Mixer mixer = null;
-	
+
 	@Before
 	public void setUp() {
 		mixer = PulseAudioMixer.getInstance();
 	}
-	
-	
+
 	@Test
 	public void testOpen() throws LineUnavailableException {
 		mixer.open();
-		
+		mixer.close();
+
 	}
-	
-	
-	
+
 	@After
 	public void tearDown() {
-		
+
 	}
-	
+
 }
--- a/unittests/org/classpath/icedtea/pulseaudio/PulseAudioMixerTest.java	Wed Sep 03 10:47:27 2008 -0400
+++ b/unittests/org/classpath/icedtea/pulseaudio/PulseAudioMixerTest.java	Wed Sep 03 15:11:46 2008 -0400
@@ -244,9 +244,8 @@
 		selectedMixer.getLine(Port.Info.MICROPHONE);
 	}
 
-	@Test
-	public void testOpeningAgain() throws LineUnavailableException,
-			UnsupportedOperationException {
+	@Test(expected = IllegalStateException.class)
+	public void testOpeningAgain() throws LineUnavailableException {
 		selectedMixer.open();
 		selectedMixer.open();
 	}
--- a/unittests/org/classpath/icedtea/pulseaudio/PulseAudioSourcePortTest.java	Wed Sep 03 10:47:27 2008 -0400
+++ b/unittests/org/classpath/icedtea/pulseaudio/PulseAudioSourcePortTest.java	Wed Sep 03 15:11:46 2008 -0400
@@ -1,26 +1,31 @@
 package org.classpath.icedtea.pulseaudio;
 
-import javax.sound.sampled.*;
+import javax.sound.sampled.BooleanControl;
+import javax.sound.sampled.FloatControl;
+import javax.sound.sampled.Line;
+import javax.sound.sampled.LineUnavailableException;
+import javax.sound.sampled.Mixer;
+import javax.sound.sampled.Port;
+
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 
 public class PulseAudioSourcePortTest {
-	
+
 	Mixer mixer;
 
-
-	
 	@Before
 	public void setUp() throws Exception {
 		mixer = PulseAudioMixer.getInstance();
 		mixer.open();
 
 	}
-	
+
 	@Test
-	public void testClose() throws LineUnavailableException{
+	public void testClose() throws LineUnavailableException {
 		Line.Info[] lineInfos = mixer.getSourceLineInfo();
-		for(Line.Info info : lineInfos) {
+		for (Line.Info info : lineInfos) {
 			if (info.getLineClass() == Port.class) {
 				System.out.println(info.toString());
 				Port port = (Port) mixer.getLine(info);
@@ -28,22 +33,29 @@
 			}
 		}
 	}
-	
-	
+
 	@Test
-	public void testControls() throws LineUnavailableException{
+	public void testControls() throws LineUnavailableException {
 		Line.Info[] lineInfos = mixer.getSourceLineInfo();
-		for(Line.Info info : lineInfos) {
+		for (Line.Info info : lineInfos) {
 			if (info.getLineClass() == Port.class) {
 				System.out.println(info.toString());
 				Port port = (Port) mixer.getLine(info);
-				FloatControl volumeControl = (FloatControl) port.getControl(FloatControl.Type.VOLUME);
+				FloatControl volumeControl = (FloatControl) port
+						.getControl(FloatControl.Type.VOLUME);
 				volumeControl.setValue(60000);
-				BooleanControl muteControl = (BooleanControl) port.getControl(BooleanControl.Type.MUTE);
+				BooleanControl muteControl = (BooleanControl) port
+						.getControl(BooleanControl.Type.MUTE);
 				muteControl.setValue(true);
 				muteControl.setValue(false);
 			}
 		}
 	}
 
+	@After
+	public void tearDown() {
+		mixer.close();
+
+	}
+
 }
--- a/unittests/org/classpath/icedtea/pulseaudio/PulseAudioTargetDataLineTest.java	Wed Sep 03 10:47:27 2008 -0400
+++ b/unittests/org/classpath/icedtea/pulseaudio/PulseAudioTargetDataLineTest.java	Wed Sep 03 15:11:46 2008 -0400
@@ -43,6 +43,7 @@
 		mixer = AudioSystem.getMixer(wantedMixerInfo);
 		Assert.assertNotNull(mixer);
 		mixer.open();
+		targetDataLine = null;
 
 	}
 
@@ -127,25 +128,36 @@
 		targetDataLine.removeLineListener(closeListener);
 
 	}
-	
-	
+
 	@Test
 	public void testMixerKnowsAboutOpenLines() throws LineUnavailableException {
 		targetDataLine = (TargetDataLine) mixer.getLine(new Line.Info(
 				TargetDataLine.class));
-		
+
 		Assert.assertEquals(0, mixer.getTargetLines().length);
 		targetDataLine.open();
 		Assert.assertEquals(1, mixer.getTargetLines().length);
 		targetDataLine.close();
 		Assert.assertEquals(0, mixer.getTargetLines().length);
-		
+
 	}
-	
+
 	@Test
 	public void testAllTargetLinesClosed() {
 		Assert.assertEquals(0, mixer.getTargetLines().length);
-		
+
+	}
+
+	@Test
+	public void testTargetLineHasNoControls() throws LineUnavailableException {
+		targetDataLine = (TargetDataLine) mixer.getLine(new Line.Info(
+				TargetDataLine.class));
+
+		targetDataLine.open();
+
+		Assert.assertEquals(0, targetDataLine.getControls().length);
+
+		targetDataLine.close();
 	}
 
 	@After
--- a/unittests/org/classpath/icedtea/pulseaudio/PulseAudioTargetPortTest.java	Wed Sep 03 10:47:27 2008 -0400
+++ b/unittests/org/classpath/icedtea/pulseaudio/PulseAudioTargetPortTest.java	Wed Sep 03 15:11:46 2008 -0400
@@ -1,26 +1,31 @@
 package org.classpath.icedtea.pulseaudio;
 
-import javax.sound.sampled.*;
+import javax.sound.sampled.BooleanControl;
+import javax.sound.sampled.FloatControl;
+import javax.sound.sampled.Line;
+import javax.sound.sampled.LineUnavailableException;
+import javax.sound.sampled.Mixer;
+import javax.sound.sampled.Port;
+
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 
 public class PulseAudioTargetPortTest {
-	
+
 	Mixer mixer;
 
-
-	
 	@Before
 	public void setUp() throws Exception {
 		mixer = PulseAudioMixer.getInstance();
 		mixer.open();
 
 	}
-	
+
 	@Test
-	public void testClose() throws LineUnavailableException{
+	public void testClose() throws LineUnavailableException {
 		Line.Info[] lineInfos = mixer.getTargetLineInfo();
-		for(Line.Info info : lineInfos) {
+		for (Line.Info info : lineInfos) {
 			if (info.getLineClass() == Port.class) {
 				System.out.println(info.toString());
 				Port port = (Port) mixer.getLine(info);
@@ -28,22 +33,28 @@
 			}
 		}
 	}
-	
-	
+
 	@Test
-	public void testControls() throws LineUnavailableException{
+	public void testControls() throws LineUnavailableException {
 		Line.Info[] lineInfos = mixer.getTargetLineInfo();
-		for(Line.Info info : lineInfos) {
+		for (Line.Info info : lineInfos) {
 			if (info.getLineClass() == Port.class) {
 				System.out.println(info.toString());
 				Port port = (Port) mixer.getLine(info);
-				FloatControl volumeControl = (FloatControl) port.getControl(FloatControl.Type.VOLUME);
+				FloatControl volumeControl = (FloatControl) port
+						.getControl(FloatControl.Type.VOLUME);
 				volumeControl.setValue(60000);
-				BooleanControl muteControl = (BooleanControl) port.getControl(BooleanControl.Type.MUTE);
+				BooleanControl muteControl = (BooleanControl) port
+						.getControl(BooleanControl.Type.MUTE);
 				muteControl.setValue(true);
 				muteControl.setValue(false);
 			}
 		}
 	}
 
+	@After
+	public void tearDown() {
+		mixer.close();
+	}
+
 }
--- a/unittests/org/classpath/icedtea/pulseaudio/PulseSourceDataLineTest.java	Wed Sep 03 10:47:27 2008 -0400
+++ b/unittests/org/classpath/icedtea/pulseaudio/PulseSourceDataLineTest.java	Wed Sep 03 15:11:46 2008 -0400
@@ -301,8 +301,6 @@
 	public void testVolumeAndMute() throws Exception {
 
 		Mixer selectedMixer = mixer;
-
-		selectedMixer.open();
 		SourceDataLine line = (SourceDataLine) selectedMixer
 				.getLine(new Line.Info(SourceDataLine.class));
 
@@ -346,7 +344,6 @@
 
 		Mixer selectedMixer = mixer;
 
-		selectedMixer.open();
 		SourceDataLine line = (SourceDataLine) selectedMixer
 				.getLine(new Line.Info(SourceDataLine.class));
 
@@ -543,6 +540,7 @@
 		Assert.assertEquals(0, mixer.getSourceLines().length);
 		sourceDataLine.open();
 		Assert.assertEquals(1, mixer.getSourceLines().length);
+		Assert.assertEquals(sourceDataLine, mixer.getSourceLines()[0]);
 		sourceDataLine.close();
 		Assert.assertEquals(0, mixer.getSourceLines().length);