changeset 1080:8b85ec866923

2008-10-10 Ioana Ivan <iivan@redhat.com> Omair Majid <omajid@redhat.com> * INSTALL: Updated to reflect requirements for pulse-java. * Makefile.am (distclean-local): Clean up pulse-java. (EXTRA_DIST): Add pulseaudio folder. (stamps/icedtea.stamp): Added dependency on stamps/pulse-java.stamp. Also copy generated .jar and .so files to the jre images. (stamps/icedtea-debug.stamp): Likewise. (stamps/pulse-java.stamp): New target. (stamps/pulse-java-jar.stamp): Likewise. (stamps/pulse-java-class.stamp): Likewise. (stamps/pulse-java-headers.stamp): Likewise. (clean-pulse-java): Likewise. * README: Added note for PulseAudio based mixer. * acinclude.m4 (FIND_PULSEAUDIO): Find the pulseaudio binary. * configure.ac: Check for pulseaudio server and header files being installed. * patches/icedtea-pulse-soundproperties.patch: Dont use pulse-java as the default Mixer. * pulseaudio/: Copied over sources from pulseaudio repository.
author Omair Majid <omajid@redhat.com>
date Fri, 10 Oct 2008 16:03:12 -0400
parents fa94fa7ac782
children e7b6e8e732e4
files ChangeLog INSTALL Makefile.am README acinclude.m4 configure.ac patches/icedtea-pulse-soundproperties.patch pulseaudio/AUTHORS pulseaudio/COPYING pulseaudio/README pulseaudio/TODO pulseaudio/src/java/META-INF/services/javax.sound.sampled.spi.MixerProvider pulseaudio/src/java/org/classpath/icedtea/pulseaudio/ContextEvent.java pulseaudio/src/java/org/classpath/icedtea/pulseaudio/ContextListener.java pulseaudio/src/java/org/classpath/icedtea/pulseaudio/EventLoop.java pulseaudio/src/java/org/classpath/icedtea/pulseaudio/Operation.java pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioClip.java pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioDataLine.java pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioLine.java pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioMixer.java pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioMixerInfo.java pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioMixerProvider.java pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioMuteControl.java pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioPlaybackLine.java pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioPort.java pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioSourceDataLine.java pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioSourcePort.java pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioTargetDataLine.java pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioTargetPort.java pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioVolumeControl.java pulseaudio/src/java/org/classpath/icedtea/pulseaudio/Stream.java pulseaudio/src/java/org/classpath/icedtea/pulseaudio/StreamBufferAttributes.java pulseaudio/src/java/org/classpath/icedtea/pulseaudio/StreamSampleSpecification.java pulseaudio/src/native/jni-common.c pulseaudio/src/native/jni-common.h pulseaudio/src/native/org_classpath_icedtea_pulseaudio_EventLoop.c pulseaudio/src/native/org_classpath_icedtea_pulseaudio_Operation.c pulseaudio/src/native/org_classpath_icedtea_pulseaudio_PulseAudioSourcePort.c pulseaudio/src/native/org_classpath_icedtea_pulseaudio_PulseAudioStreamVolumeControl.c pulseaudio/src/native/org_classpath_icedtea_pulseaudio_PulseAudioTargetPort.c pulseaudio/src/native/org_classpath_icedtea_pulseaudio_Stream.c pulseaudio/testsounds/README pulseaudio/testsounds/error.wav pulseaudio/testsounds/logout.wav pulseaudio/testsounds/startup.wav pulseaudio/unittests/org/classpath/icedtea/pulseaudio/OtherSoundProvidersAvailableTest.java pulseaudio/unittests/org/classpath/icedtea/pulseaudio/PulseAudioClipTest.java pulseaudio/unittests/org/classpath/icedtea/pulseaudio/PulseAudioEventLoopOverhead.java pulseaudio/unittests/org/classpath/icedtea/pulseaudio/PulseAudioMixerProviderTest.java pulseaudio/unittests/org/classpath/icedtea/pulseaudio/PulseAudioMixerRawTest.java pulseaudio/unittests/org/classpath/icedtea/pulseaudio/PulseAudioMixerTest.java pulseaudio/unittests/org/classpath/icedtea/pulseaudio/PulseAudioSourceDataLineRawTest.java pulseaudio/unittests/org/classpath/icedtea/pulseaudio/PulseAudioSourceDataLineTest.java pulseaudio/unittests/org/classpath/icedtea/pulseaudio/PulseAudioSourcePortTest.java pulseaudio/unittests/org/classpath/icedtea/pulseaudio/PulseAudioTargetDataLineTest.java pulseaudio/unittests/org/classpath/icedtea/pulseaudio/PulseAudioTargetPortTest.java
diffstat 55 files changed, 11603 insertions(+), 4 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Thu Oct 09 11:25:22 2008 -0400
+++ b/ChangeLog	Fri Oct 10 16:03:12 2008 -0400
@@ -1,3 +1,27 @@
+2008-10-10 Ioana Ivan <iivan@redhat.com>
+	    Omair Majid <omajid@redhat.com>
+
+	* INSTALL: Updated to reflect requirements for pulse-java.
+	* Makefile.am 
+	(distclean-local): Clean up pulse-java. 
+	(EXTRA_DIST): Add pulseaudio folder.
+	(stamps/icedtea.stamp): Added dependency on stamps/pulse-java.stamp. Also
+	copy generated .jar and .so files to the jre images.
+	(stamps/icedtea-debug.stamp): Likewise.
+	(stamps/pulse-java.stamp): New target.
+	(stamps/pulse-java-jar.stamp): Likewise.
+	(stamps/pulse-java-class.stamp): Likewise.
+	(stamps/pulse-java-headers.stamp): Likewise.
+	(clean-pulse-java): Likewise.
+	* README: Added note for PulseAudio based mixer.
+	* acinclude.m4 
+	(FIND_PULSEAUDIO): Find the pulseaudio binary.
+	* configure.ac: Check for pulseaudio server and header files being
+	installed.
+	* patches/icedtea-pulse-soundproperties.patch: Dont use pulse-java as the
+	default Mixer.
+	* pulseaudio/: Copied over sources from pulseaudio repository.
+
 2008-10-09  Gary Benson  <gbenson@redhat.com>
 	    Andrew Haley  <aph@redhat.com>
 
--- a/INSTALL	Thu Oct 09 11:25:22 2008 -0400
+++ b/INSTALL	Fri Oct 10 16:03:12 2008 -0400
@@ -32,6 +32,10 @@
 rhino
 netbeans (harness, platform8, apisupport1, java2, ide9) - for visualvm
 
+For builing the PulseAudio based mixer, you will need
+pulseaudio-libs-devel >= 0.9.11
+pulseaudio >= 0.9.11
+
 For building the zero-assembler port (see below), you will need libffi.
 
 See ./configure --help if you need to override the defaults.
--- a/Makefile.am	Thu Oct 09 11:25:22 2008 -0400
+++ b/Makefile.am	Fri Oct 10 16:03:12 2008 -0400
@@ -28,6 +28,19 @@
 endif
 endif
 
+if ENABLE_PULSE_JAVA
+# include the makefile in pulseaudio subdir
+PULSE_JAVA_DIR = pulseaudio
+PULSE_JAVA_NATIVE_SRCDIR = $(PULSE_JAVA_DIR)/src/native
+PULSE_JAVA_JAVA_SRCDIR = $(PULSE_JAVA_DIR)/src/java
+PULSE_JAVA_CLASS_DIR = $(PULSE_JAVA_DIR)/bin
+else 
+PULSE_JAVA_DIR =
+PULSE_JAVA_NATIVE_SRCDIR =
+PULSE_JAVA_JAVA_SRCDIR =
+PULSE_JAVA_CLASS_DIR =
+endif
+
 if WITH_VISUALVM
 VISUALVM_PATCH = patches/icedtea-visualvm.patch
 else
@@ -39,7 +52,7 @@
 
 all-local: icedtea-against-icedtea
 
-distclean-local: clean-copy clean-jtreg clean-jtreg-reports
+distclean-local: clean-copy clean-jtreg clean-jtreg-reports clean-pulse-java
 	rm -rf stamps
 	rm -f rt-source-files.txt \
 	  hotspot-tools-source-files.txt \
@@ -83,7 +96,7 @@
 	overlays extra jconsole.desktop policytool.desktop \
 	test/jtreg patches/icedtea-plugin.patch \
 	patches/icedtea-liveconnect.patch IcedTeaPlugin.cc \
-	HACKING patches/icedtea-visualvm.patch
+	HACKING patches/icedtea-visualvm.patch pulseaudio
 
 # The Binary plugs directory is called jdk1.7.0 for historical reasons. The
 # name is completely irrelevant; only contains the plugs to build IcedTea.
@@ -520,6 +533,11 @@
 	patches/icedtea-cacao.patch
 endif
 
+if ENABLE_PULSE_JAVA
+ICEDTEA_PATCHES += \
+	patches/icedtea-pulse-soundproperties.patch
+endif
+
 ICEDTEA_PATCHES += \
 	$(DISTRIBUTION_PATCHES)
 
@@ -923,7 +941,8 @@
 	stamps/hotspot-tools.stamp stamps/plugs.stamp \
 	stamps/ports.stamp stamps/patch.stamp stamps/overlay.stamp \
 	$(GCJWEBPLUGIN_TARGET) $(ICEDTEAPLUGIN_TARGET) \
-	extra-lib/about.jar stamps/cacao.stamp stamps/visualvm.stamp
+	extra-lib/about.jar stamps/cacao.stamp stamps/visualvm.stamp \
+	stamps/pulse-java.stamp
 	$(MAKE) \
 	  $(ICEDTEA_ENV) \
 	  -C openjdk/control/make/ \
@@ -941,6 +960,16 @@
 	  $(BUILD_OUTPUT_DIR)/j2re-image/lib/$(INSTALL_ARCH_DIR)
 endif
 endif
+if ENABLE_PULSE_JAVA
+	cp -pPRf libpulse-java.so \
+	  $(BUILD_OUTPUT_DIR)/j2sdk-image/jre/lib/$(INSTALL_ARCH_DIR)
+	cp -pPRf libpulse-java.so \
+	  $(BUILD_OUTPUT_DIR)/j2re-image/lib/$(INSTALL_ARCH_DIR)
+	cp -pPRf pulse-java.jar \
+	  $(BUILD_OUTPUT_DIR)/j2sdk-image/jre/lib/ext
+	cp -pPRf pulse-java.jar \
+	  $(BUILD_OUTPUT_DIR)/j2re-image/lib/ext
+endif
 if WITH_VISUALVM
 	mkdir -p $(BUILD_OUTPUT_DIR)/j2sdk-image/lib/visualvm/etc ; \
 	mkdir -p $(BUILD_OUTPUT_DIR)/j2sdk-image/lib/visualvm/visualvm ; \
@@ -975,7 +1004,8 @@
 	stamps/hotspot-tools.stamp stamps/plugs.stamp \
 	stamps/ports.stamp stamps/patch.stamp stamps/overlay.stamp \
 	$(GCJWEBPLUGIN_TARGET) $(ICEDTEAPLUGIN_TARGET) \
-	extra-lib/about.jar stamps/cacao.stamp
+	extra-lib/about.jar stamps/cacao.stamp \
+	stamps/pulse-java.stamp
 	$(MAKE) \
 	  $(ICEDTEA_ENV) \
 	  -C openjdk/control/make \
@@ -993,6 +1023,16 @@
 	  $(BUILD_OUTPUT_DIR)-debug/j2re-image/lib/$(INSTALL_ARCH_DIR)
 endif
 endif
+if ENABLE_PULSE_JAVA
+	cp -pPRf libpulse-java.so \
+	  $(BUILD_OUTPUT_DIR)-debug/j2sdk-image/jre/lib/$(INSTALL_ARCH_DIR)
+	cp -pPRf libpulse-java.so \
+	  $(BUILD_OUTPUT_DIR)-debug/j2re-image/lib/$(INSTALL_ARCH_DIR)
+	cp -pPRf pulse-java.jar \
+	  $(BUILD_OUTPUT_DIR)-debug/j2sdk-image/jre/lib/ext
+	cp -pPRf pulse-java.jar \
+	  $(BUILD_OUTPUT_DIR)-debug/j2re-image/lib/ext
+endif
 if WITH_VISUALVM
 	mkdir -p $(BUILD_OUTPUT_DIR)/j2sdk-image/lib/visualvm/etc ; \
 	mkdir -p $(BUILD_OUTPUT_DIR)/j2sdk-image/lib/visualvm/visualvm ; \
@@ -1375,6 +1415,92 @@
 endif
 endif
 
+
+# PulseAudio based mixer
+# (pulse-java)
+
+stamps/pulse-java.stamp: stamps/pulse-java-jar.stamp stamps/pulse-java-headers.stamp
+if ENABLE_PULSE_JAVA
+	$(CC) $(CFLAGS) -c $(PULSE_JAVA_NATIVE_SRCDIR)/jni-common.c
+	$(CC) $(LIBPULSE_CFLAGS) $(CFLAGS) -fpic -fPIC -c $(PULSE_JAVA_NATIVE_SRCDIR)/org_classpath_icedtea_pulseaudio_EventLoop.c
+	$(CC) $(LIBPULSE_CFLAGS) $(CFLAGS) -fpic -fPIC -c $(PULSE_JAVA_NATIVE_SRCDIR)/org_classpath_icedtea_pulseaudio_Operation.c
+	$(CC) $(LIBPULSE_CFLAGS) $(CFLAGS) -fpic -fPIC -c $(PULSE_JAVA_NATIVE_SRCDIR)/org_classpath_icedtea_pulseaudio_Stream.c
+	$(CC) $(LIBPULSE_CFLAGS) $(CFLAGS) -fpic -fPIC -c $(PULSE_JAVA_NATIVE_SRCDIR)/org_classpath_icedtea_pulseaudio_PulseAudioSourcePort.c 
+	$(CC) $(LIBPULSE_CFLAGS) $(CFLAGS) -fpic -fPIC -c $(PULSE_JAVA_NATIVE_SRCDIR)/org_classpath_icedtea_pulseaudio_PulseAudioTargetPort.c
+	$(CC) $(LDFLAGS) -shared $(LIBPULSE_LIBS) -o libpulse-java.so org_*pulseaudio*.o jni-common.o
+	mv org_classpath_icedtea_pulseaudio_*.o $(PULSE_JAVA_CLASS_DIR)
+endif
+	mkdir -p stamps
+	touch stamps/pulse-java.stamp
+
+stamps/pulse-java-jar.stamp: stamps/pulse-java-class.stamp
+if ENABLE_PULSE_JAVA
+	mkdir -p $(PULSE_JAVA_CLASS_DIR);
+	if ! test -d $(ICEDTEA_BOOT_DIR) ; \
+	then \
+	  $(JAR) cf pulse-java.jar -C $(PULSE_JAVA_CLASS_DIR) .; \
+	else \
+	  $(ICEDTEA_BOOT_DIR)/bin/jar cf pulse-java.jar -C $(PULSE_JAVA_CLASS_DIR) .; \
+	fi
+endif
+	mkdir -p stamps
+	touch stamps/pulse-java-jar.stamp
+	
+stamps/pulse-java-class.stamp: 
+if ENABLE_PULSE_JAVA
+	mkdir -p $(PULSE_JAVA_CLASS_DIR)
+	if ! test -d $(ICEDTEA_BOOT_DIR) ; \
+	then \
+		(cd $(PULSE_JAVA_JAVA_SRCDIR); \
+		 $(JAVAC) -d ../../../$(PULSE_JAVA_CLASS_DIR) \
+          	-bootclasspath  \
+	        '$(OPENJDK_SOURCEPATH_DIRS):$(abs_top_builddir)/generated' \
+          	org/classpath/icedtea/pulseaudio/*.java\
+          	) \
+        else \
+        	(cd $(PULSE_JAVA_JAVA_SRCDIR); \
+        	$(ICEDTEA_BOOT_DIR)/bin/javac -d ../../../$(PULSE_JAVA_CLASS_DIR)\
+        	-bootclasspath \
+	        '$(ICEDTEA_BOOT_DIR)/jre/lib/rt.jar' \
+        	org/classpath/icedtea/pulseaudio/*.java\
+        	) \
+        fi
+	cp  -r $(PULSE_JAVA_JAVA_SRCDIR)/META-INF $(PULSE_JAVA_CLASS_DIR)
+endif
+	mkdir -p stamps
+	touch stamps/pulse-java-class.stamp
+
+
+stamps/pulse-java-headers.stamp: stamps/pulse-java-class.stamp
+if ENABLE_PULSE_JAVA
+	if ! test -d $(ICEDTEA_BOOT_DIR) ; \
+	then \
+	  $(JAVAH) -d $(PULSE_JAVA_NATIVE_SRCDIR) -classpath $(PULSE_JAVA_CLASS_DIR) org.classpath.icedtea.pulseaudio.EventLoop ; \
+	  $(JAVAH) -d $(PULSE_JAVA_NATIVE_SRCDIR) -classpath $(PULSE_JAVA_CLASS_DIR) org.classpath.icedtea.pulseaudio.Stream ; \
+	  $(JAVAH) -d $(PULSE_JAVA_NATIVE_SRCDIR) -classpath $(PULSE_JAVA_CLASS_DIR) org.classpath.icedtea.pulseaudio.Operation; \
+	  $(JAVAH) -d $(PULSE_JAVA_NATIVE_SRCDIR) -classpath $(PULSE_JAVA_CLASS_DIR) org.classpath.icedtea.pulseaudio.PulseAudioSourcePort ; \
+	  $(JAVAH) -d $(PULSE_JAVA_NATIVE_SRCDIR) -classpath $(PULSE_JAVA_CLASS_DIR) org.classpath.icedtea.pulseaudio.PulseAudioTargetPort ; \
+	else \
+	  $(ICEDTEA_BOOT_DIR)/bin/javah -d $(PULSE_JAVA_NATIVE_SRCDIR) -classpath $(PULSE_JAVA_CLASS_DIR) org.classpath.icedtea.pulseaudio.EventLoop ; \
+	  $(ICEDTEA_BOOT_DIR)/bin/javah -d $(PULSE_JAVA_NATIVE_SRCDIR) -classpath $(PULSE_JAVA_CLASS_DIR) org.classpath.icedtea.pulseaudio.Stream ; \
+	  $(ICEDTEA_BOOT_DIR)/bin/javah -d $(PULSE_JAVA_NATIVE_SRCDIR) -classpath $(PULSE_JAVA_CLASS_DIR) org.classpath.icedtea.pulseaudio.Operation; \
+	  $(ICEDTEA_BOOT_DIR)/bin/javah -d $(PULSE_JAVA_NATIVE_SRCDIR) -classpath $(PULSE_JAVA_CLASS_DIR) org.classpath.icedtea.pulseaudio.PulseAudioSourcePort ; \
+	  $(ICEDTEA_BOOT_DIR)/bin/javah -d $(PULSE_JAVA_NATIVE_SRCDIR) -classpath $(PULSE_JAVA_CLASS_DIR) org.classpath.icedtea.pulseaudio.PulseAudioTargetPort ; \
+	fi
+endif
+	mkdir -p stamps
+	touch stamps/pulse-java-headers.stamp
+
+
+clean-pulse-java:
+	rm -rf $(PULSE_JAVA_CLASS_DIR)/*
+	rm -f $(PULSE_JAVA_NATIVE_SRCDIR)/org_*.h
+	rm -f stamps/pulse-java*.stamp
+	rm -f pulse-java.jar
+	rm -f libpulse-java.so
+
+# end of pulse-java
+
 # jtreg
 
 stamps/jtreg.stamp: stamps/icedtea.stamp
--- a/README	Thu Oct 09 11:25:22 2008 -0400
+++ b/README	Fri Oct 10 16:03:12 2008 -0400
@@ -159,3 +159,11 @@
 and --with-netbeans-profiler-src-zip which can be used to prevent 
 re-downloading of the source zips. --with-netbeans-home can be used to specify
 where the Netbeans tools are installed (default /usr/share/netbeans).
+
+
+
+PulseAudio Mixer
+================
+
+Passing --enable-pulse-java to configure will build the PulseAudio Mixer for 
+java. This allows java programs to use PulseAudio as the sound backend.
--- a/acinclude.m4	Thu Oct 09 11:25:22 2008 -0400
+++ b/acinclude.m4	Fri Oct 10 16:03:12 2008 -0400
@@ -665,6 +665,15 @@
   AC_SUBST(RHINO_JAR)
 ])
 
+AC_DEFUN([FIND_PULSEAUDIO],
+[
+  AC_PATH_PROG(PULSEAUDIO_BIN, "pulseaudio")
+  if test -z "${PULSEAUDIO_BIN}"; then
+    AC_MSG_ERROR("pulseaudio was not found.")
+  fi
+  AC_SUBST(PULSEAUDIO_BIN)
+])
+
 AC_DEFUN([ENABLE_OPTIMIZATIONS],
 [
   AC_MSG_CHECKING(whether to disable optimizations)
--- a/configure.ac	Thu Oct 09 11:25:22 2008 -0400
+++ b/configure.ac	Fri Oct 10 16:03:12 2008 -0400
@@ -126,6 +126,12 @@
               [enable_liveconnect="yes"], [enable_liveconnect="no"])
 AM_CONDITIONAL(ENABLE_LIVECONNECT, test "x${enable_liveconnect}" = "xyes")
 
+AC_ARG_ENABLE([pulse-java],
+              [AS_HELP_STRING([--enable-pulse-java],
+                              [Enable pulse-java - an audio mixer spi that uses PulseAudio])],
+              [enable_pulse_java="yes"], [enable_pulse_java="no"])
+AM_CONDITIONAL(ENABLE_PULSE_JAVA, test "x${enable_pulse_java}" = "xyes")
+
 AC_ARG_ENABLE([docs],
 	      [AS_HELP_STRING([--disable-docs],
 	      		      [Disable generation of documentation])],
@@ -247,6 +253,11 @@
 ENABLE_ZERO_BUILD
 SET_CORE_OR_SHARK_BUILD
 
+if test "x${enable_pulse_java}" = "xyes"
+then
+  FIND_PULSEAUDIO
+fi
+
 dnl pkgconfig cannot be used to finid these headers and libraries.
 AC_CHECK_HEADERS([cups/cups.h cups/ppd.h],[]
 	,[AC_MSG_ERROR("CUPS headers were not found -
@@ -354,6 +365,20 @@
 AC_SUBST(ALSA_CFLAGS)
 AC_SUBST(ALSA_LIBS)
 
+if test "x${enable_pulse_java}" = "xyes"
+then
+  dnl Check for pulseaudio libraries.
+  PKG_CHECK_MODULES(LIBPULSE,[libpulse >= 0.9.11],[LIBPULSE_FOUND=yes]
+    ,[LIBPULSE_FOUND=no])
+  if test "x${LIBPULSE_FOUND}" = xno
+  then
+    AC_MSG_ERROR([Could not find pulseaudio>=0.9.11 libraries - \
+    Try installing pulseaudio-libs-devel>=0.9.11.])
+  fi
+  AC_SUBST(LIBPULSE_CFLAGS)
+  AC_SUBST(LIBPULSE_LIBS)
+fi
+
 dnl Check for plugin support headers and libraries.
 dnl FIXME: use unstable
 if test "x${enable_liveconnect}" = "xyes"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/patches/icedtea-pulse-soundproperties.patch	Fri Oct 10 16:03:12 2008 -0400
@@ -0,0 +1,16 @@
+--- openjdk/jdk/src/share/lib/sound.properties	2008-08-28 04:15:18.000000000 -0400
++++ openjdk/jdk/src/share/lib/sound.properties	2008-10-03 16:59:21.000000000 -0400
+@@ -37,3 +37,13 @@
+ # Specify the default Receiver by provider and name:
+ # javax.sound.midi.Receiver=com.sun.media.sound.MidiProvider#SunMIDI1
+ #
++
++# javax.sound.sampled.Clip=org.classpath.icedtea.pulseaudio.PulseAudioMixerProvider
++# javax.sound.sampled.Port=org.classpath.icedtea.pulseaudio.PulseAudioMixerProvider
++# javax.sound.sampled.SourceDataLine=org.classpath.icedtea.pulseaudio.PulseAudioMixerProvider
++# javax.sound.sampled.TargetDataLine=org.classpath.icedtea.pulseaudio.PulseAudioMixerProvider
++
++javax.sound.sampled.Clip=com.sun.media.sound.DirectAudioDeviceProvider
++javax.sound.sampled.Port=com.sun.media.sound.PortMixerProvider
++javax.sound.sampled.SourceDataLine=com.sun.media.sound.DirectAudioDeviceProvider
++javax.sound.sampled.TargetDataLine=com.sun.media.sound.DirectAudioDeviceProvider
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pulseaudio/AUTHORS	Fri Oct 10 16:03:12 2008 -0400
@@ -0,0 +1,5 @@
+Authors
+
+Ioana Iivan (iivan@redhat.com)
+
+Omair Majid (omajid@redhat.com)
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pulseaudio/COPYING	Fri Oct 10 16:03:12 2008 -0400
@@ -0,0 +1,340 @@
+		    GNU GENERAL PUBLIC LICENSE
+		       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+     51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+			    Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+		    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+			    NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+		     END OF TERMS AND CONDITIONS
+
+	    How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year  name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Library General
+Public License instead of this License.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pulseaudio/README	Fri Oct 10 16:03:12 2008 -0400
@@ -0,0 +1,28 @@
+What is this? 
+An audio SPI implementation for java that uses PulseAudio as a mixer 
+(so as a bonus you get all of the ear candy that PulseAudio provides)
+
+How To Build?
+
+  autoreconf && ./configure && ant && make
+
+Note: Building requires PulseAudio 0.9.12 and java-devel etc..
+  
+How To Run the Tests?
+
+After building, do:
+  
+  ant test
+
+  (a few tests might fail; they test the networking capabilities of PulseAudio 
+
+Where does it come from?
+All of the code was written by the authors
+
+The sound files new.wav and logout.wav were taken from 
+http://websvn.kde.org/branches/KDE/4.0/kdeartwork/sounds/ (and renamed). 
+They are licensed by the copyright holders as GPLv2.
+
+The sound file error.wav is part of gnome-audio
+http://ftp.gnome.org/pub/gnome/sources/gnome-audio/2.22/gnome-audio-2.22.2.tar.bz2
+It is licensed under the LGPL by the copyright holders.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pulseaudio/src/java/META-INF/services/javax.sound.sampled.spi.MixerProvider	Fri Oct 10 16:03:12 2008 -0400
@@ -0,0 +1,3 @@
+# Providers of sound mixers
+
+org.classpath.icedtea.pulseaudio.PulseAudioMixerProvider
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pulseaudio/src/java/org/classpath/icedtea/pulseaudio/ContextEvent.java	Fri Oct 10 16:03:12 2008 -0400
@@ -0,0 +1,56 @@
+/* ContextEvent.java
+   Copyright (C) 2008 Red Hat, Inc.
+
+This file is part of IcedTea.
+
+IcedTea is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License as published by
+the Free Software Foundation, version 2.
+
+IcedTea is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with IcedTea; see the file COPYING.  If not, write to
+the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library.  Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module.  An independent module is a module which is not derived from
+or based on this library.  If you modify this library, you may extend
+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;
+
+public class ContextEvent {
+
+	public static enum Type {
+		UNCONNECTED, CONNECTING, AUTHORIZING, SETTING_NAME, READY, FAILED, TERMINATED
+	}
+
+	private Type type;
+
+	public ContextEvent(Type type) {
+		this.type = type;
+	}
+
+	public Type getType() {
+		return type;
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pulseaudio/src/java/org/classpath/icedtea/pulseaudio/ContextListener.java	Fri Oct 10 16:03:12 2008 -0400
@@ -0,0 +1,44 @@
+/* ContextListener.java
+   Copyright (C) 2008 Red Hat, Inc.
+
+This file is part of IcedTea.
+
+IcedTea is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License as published by
+the Free Software Foundation, version 2.
+
+IcedTea is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with IcedTea; see the file COPYING.  If not, write to
+the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library.  Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module.  An independent module is a module which is not derived from
+or based on this library.  If you modify this library, you may extend
+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;
+
+public interface ContextListener {
+
+	public void update(ContextEvent e);
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pulseaudio/src/java/org/classpath/icedtea/pulseaudio/EventLoop.java	Fri Oct 10 16:03:12 2008 -0400
@@ -0,0 +1,280 @@
+/* EventLoop.java
+   Copyright (C) 2008 Red Hat, Inc.
+
+This file is part of IcedTea.
+
+IcedTea is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License as published by
+the Free Software Foundation, version 2.
+
+IcedTea is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with IcedTea; see the file COPYING.  If not, write to
+the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library.  Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module.  An independent module is a module which is not derived from
+or based on this library.  If you modify this library, you may extend
+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;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Semaphore;
+
+import org.classpath.icedtea.pulseaudio.ContextEvent.Type;
+
+/*
+ * any methods that can obstruct the behaviour of pa_mainloop should run
+ * synchronized
+ * 
+ * 
+ */
+
+public class EventLoop implements Runnable {
+
+	/*
+	 * the threadLock object is the object used for synchronizing the
+	 * non-thread-safe operations of pulseaudio's c api
+	 * 
+	 */
+	public Object threadLock = new Object();
+
+	private static EventLoop instance = null;
+
+	private List<ContextListener> contextListeners;
+	// private List<SourceDataLine> lines;
+	private String name;
+	private String serverString;
+
+	private int status;
+	// private boolean eventLoopIsRunning = false;
+
+	public Semaphore finished = new Semaphore(0);
+
+	private List<String> targetPortNameList = new ArrayList<String>();
+	private List<String> sourcePortNameList = new ArrayList<String>();
+
+	/*
+	 * JNI stuff
+	 * 
+	 * Do not synchronize the individual functions, synchronize
+	 * block/method/lines around the call
+	 * 
+	 */
+
+	private native void native_setup(String appName, String server);
+
+	private native int native_iterate(int timeout);
+
+	private native void native_shutdown();
+
+	private native void native_set_sink_volume(byte[] streamPointer, int volume);
+
+	/*
+	 * These fields hold pointers
+	 * 
+	 * 
+	 */
+	@SuppressWarnings("unused")
+	private byte[] contextPointer;
+	@SuppressWarnings("unused")
+	private byte[] mainloopPointer;
+
+	/*
+	 * 
+	 */
+
+	static {
+		System.loadLibrary("pulse-java");
+	}
+
+	private EventLoop() {
+		contextListeners = new ArrayList<ContextListener>();
+
+	}
+
+	synchronized public static EventLoop getEventLoop() {
+		if (instance == null) {
+			instance = new EventLoop();
+		}
+		return instance;
+	}
+
+	public void setAppName(String name) {
+		this.name = name;
+	}
+
+	public void setServer(String serverString) {
+		this.serverString = serverString;
+	}
+
+	@Override
+	public void run() {
+		native_setup(this.name, this.serverString);
+
+		/*
+		 * Perhaps this loop should be written in C doing a Java to C call on
+		 * every iteration of the loop might be slow
+		 */
+		while (true) {
+			synchronized (threadLock) {
+				// timeout is in milliseconds
+				// timout = 0 means dont block
+				native_iterate(100);
+
+				if (Thread.interrupted()) {
+					native_shutdown();
+					// System.out.println(this.getClass().getName()
+					// + ": shutting down");
+
+					// clean up the listeners
+					synchronized (contextListeners) {
+						contextListeners.clear();
+					}
+
+					return;
+
+				}
+			}
+		}
+
+	}
+
+	public void addContextListener(ContextListener contextListener) {
+		synchronized (contextListeners) {
+			contextListeners.add(contextListener);
+		}
+	}
+
+	public void removeContextListener(ContextListener contextListener) {
+		synchronized (contextListeners) {
+			contextListeners.remove(contextListener);
+		}
+	}
+
+	public int getStatus() {
+		return this.status;
+	}
+
+	public void update(int status) {
+		synchronized (threadLock) {
+			// System.out.println(this.getClass().getName()
+			// + ".update() called! status = " + status);
+			this.status = status;
+			switch (status) {
+			case 0:
+				fireEvent(new ContextEvent(Type.UNCONNECTED));
+				break;
+			case 1:
+				fireEvent(new ContextEvent(Type.CONNECTING));
+				break;
+			case 2:
+				break;
+			case 3:
+				break;
+			case 4:
+				fireEvent(new ContextEvent(Type.READY));
+				break;
+			case 5:
+				fireEvent(new ContextEvent(Type.FAILED));
+				System.out.println("context failed");
+				break;
+			case 6:
+				fireEvent(new ContextEvent(Type.TERMINATED));
+				break;
+			default:
+
+			}
+		}
+	}
+
+	private void fireEvent(final ContextEvent e) {
+		// System.out.println(this.getClass().getName() + "firing event: "
+		// + e.getType().toString());
+
+		synchronized (contextListeners) {
+			// System.out.println(contextListeners.size());
+			for (ContextListener listener : contextListeners) {
+				listener.update(e);
+			}
+		}
+
+	}
+
+	public void setVolume(byte[] streamPointer, int volume) {
+		synchronized (threadLock) {
+			native_set_sink_volume(streamPointer, volume);
+		}
+	}
+
+	public byte[] getContextPointer() {
+		return contextPointer;
+	}
+
+	public byte[] getMainLoopPointer() {
+		return mainloopPointer;
+	}
+
+	private native byte[] nativeUpdateTargetPortNameList();
+
+	private native byte[] nativeUpdateSourcePortNameList();
+
+	protected synchronized List<String> updateTargetPortNameList() {
+		targetPortNameList = new ArrayList<String>();
+		Operation op;
+		synchronized (this.threadLock) {
+			op = new Operation(nativeUpdateTargetPortNameList());
+		}
+
+		op.waitForCompletion();
+
+		assert (op.getState() == Operation.State.Done);
+
+		op.releaseReference();
+		return targetPortNameList;
+	}
+
+	protected synchronized List<String> updateSourcePortNameList() {
+		sourcePortNameList = new ArrayList<String>();
+		Operation op;
+		synchronized (this.threadLock) {
+			op = new Operation(nativeUpdateSourcePortNameList());
+		}
+
+		op.waitForCompletion();
+
+		assert (op.getState() == Operation.State.Done);
+
+		op.releaseReference();
+		return sourcePortNameList;
+	}
+
+	public void source_callback(String name) {
+		sourcePortNameList.add(name);
+	}
+
+	public void sink_callback(String name) {
+		targetPortNameList.add(name);
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pulseaudio/src/java/org/classpath/icedtea/pulseaudio/Operation.java	Fri Oct 10 16:03:12 2008 -0400
@@ -0,0 +1,147 @@
+/* Operation.java
+   Copyright (C) 2008 Red Hat, Inc.
+
+This file is part of IcedTea.
+
+IcedTea is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License as published by
+the Free Software Foundation, version 2.
+
+IcedTea is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with IcedTea; see the file COPYING.  If not, write to
+the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library.  Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module.  An independent module is a module which is not derived from
+or based on this library.  If you modify this library, you may extend
+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;
+
+
+/*
+ * Encapsulates a pa_operation object
+ * 
+ * 
+ * This is really needed only so that we can deallocate the reference counted
+ * object
+ * 
+ * 
+ */
+
+public class Operation {
+
+	byte[] operationPointer;
+	EventLoop eventLoop;
+
+	public enum State {
+		Running, Done, Cancelled,
+	}
+
+	static {
+		System.loadLibrary("pulse-java");
+	}
+
+	private native void native_ref();
+
+	private native void native_unref();
+
+	private native int native_get_state();
+
+	public Operation(byte[] operationPointer) {
+		assert (operationPointer != null);
+		this.operationPointer = operationPointer;
+		this.eventLoop = EventLoop.getEventLoop();
+	}
+
+	@Override
+	protected void finalize() throws Throwable {
+		// might catch operations which havent been released
+		assert (operationPointer == null);
+		super.finalize();
+	}
+
+	public void addReference() {
+		assert (operationPointer != null);
+		synchronized (eventLoop.threadLock) {
+			native_ref();
+		}
+	}
+
+	public void releaseReference() {
+		assert (operationPointer != null);
+		synchronized (eventLoop.threadLock) {
+			native_unref();
+		}
+		operationPointer = null;
+	}
+
+	public boolean isNull() {
+		if (operationPointer == null) {
+			return true;
+		}
+		return false;
+	}
+
+	public State getState() {
+		assert (operationPointer != null);
+		int state;
+		synchronized (eventLoop.threadLock) {
+			state = native_get_state();
+		}
+		switch (state) {
+		case 0:
+			return State.Running;
+		case 1:
+			return State.Done;
+		case 2:
+			return State.Cancelled;
+		default:
+			throw new IllegalStateException("Invalid operation State");
+		}
+
+	}
+
+	public void waitForCompletion() {
+		assert (operationPointer != null);
+
+		boolean interrupted = false;
+		do {
+			synchronized (eventLoop.threadLock) {
+				if (getState() == Operation.State.Done) {
+					return;
+				}
+				try {
+					eventLoop.threadLock.wait();
+				} catch (InterruptedException e) {
+					// ingore the interrupt for now
+					interrupted = true;
+				}
+			}
+		} while (getState() != State.Done);
+
+		// let the caller know about the interrupt
+		if (interrupted) {
+			Thread.currentThread().interrupt();
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioClip.java	Fri Oct 10 16:03:12 2008 -0400
@@ -0,0 +1,568 @@
+/* PulseAudioClip.java
+   Copyright (C) 2008 Red Hat, Inc.
+
+This file is part of IcedTea.
+
+IcedTea is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License as published by
+the Free Software Foundation, version 2.
+
+IcedTea is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with IcedTea; see the file COPYING.  If not, write to
+the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library.  Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module.  An independent module is a module which is not derived from
+or based on this library.  If you modify this library, you may extend
+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;
+
+import java.io.IOException;
+
+import javax.sound.sampled.AudioFormat;
+import javax.sound.sampled.AudioInputStream;
+import javax.sound.sampled.AudioPermission;
+import javax.sound.sampled.AudioSystem;
+import javax.sound.sampled.Clip;
+import javax.sound.sampled.DataLine;
+import javax.sound.sampled.LineUnavailableException;
+
+import org.classpath.icedtea.pulseaudio.Stream.WriteListener;
+
+public class PulseAudioClip extends PulseAudioDataLine implements Clip,
+		PulseAudioPlaybackLine {
+
+	private byte[] data = null;
+
+	private boolean muted;
+	private float volume;
+
+	// these are frame indices. so counted from 0
+	// the current frame index
+	private int currentFrame = 0;
+
+	// total number of frames in this clip
+	private int frameCount = 0;
+
+	// the starting frame of the loop
+	private int startFrame = 0;
+	// the ending frame of the loop
+	private int endFrame = 0;
+
+	public static final String DEFAULT_CLIP_NAME = "Clip";
+
+	private Object clipLock = new Object();
+	private int loopsLeft = 0;
+
+	// private Semaphore clipSemaphore = new Semaphore(1);
+
+	private class ClipThread extends Thread {
+		@Override
+		public void run() {
+
+			/*
+			 * The while loop below only works with LOOP_CONTINUOUSLY because we
+			 * abuse the fact that loopsLeft's initial value is -1
+			 * (=LOOP_CONTINUOUSLY) and it keeps on going lower without hitting
+			 * 0. So do a sanity check
+			 */
+			if (Clip.LOOP_CONTINUOUSLY != -1) {
+				throw new UnsupportedOperationException(
+						"LOOP_CONTINUOUSLY has changed; things are going to break");
+			}
+
+			while (true) {
+				writeFrames(currentFrame, endFrame + 1);
+				if (Thread.interrupted()) {
+					// Thread.currentThread().interrupt();
+					// System.out.println("returned from interrupted
+					// writeFrames");
+					break;
+				}
+
+				// if loop(0) has been called from the mainThread,
+				// wait until loopsLeft has been set
+				if (loopsLeft == 0) {
+					// System.out.println("Reading to the end of the file");
+					// System.out.println("endFrame: " + endFrame);
+					writeFrames(endFrame, getFrameLength());
+					break;
+				} else {
+					synchronized (clipLock) {
+						currentFrame = startFrame;
+						if (loopsLeft != Integer.MIN_VALUE) {
+							loopsLeft--;
+						}
+					}
+				}
+
+			}
+
+			// drain
+			Operation operation;
+
+			synchronized (eventLoop.threadLock) {
+				operation = stream.drain();
+			}
+
+			operation.waitForCompletion();
+			operation.releaseReference();
+
+		}
+	}
+
+	private ClipThread clipThread;
+
+	private void writeFrames(int startingFrame, int lastFrame) {
+
+		WriteListener writeListener = new WriteListener() {
+			@Override
+			public void update() {
+				synchronized (eventLoop.threadLock) {
+					eventLoop.threadLock.notifyAll();
+				}
+			}
+		};
+
+		stream.addWriteListener(writeListener);
+
+		int remainingFrames = lastFrame - startingFrame - 1;
+		while (remainingFrames > 0) {
+			synchronized (eventLoop.threadLock) {
+				int availableSize;
+
+				do {
+					availableSize = stream.getWritableSize();
+					if (availableSize < 0) {
+						Thread.currentThread().interrupt();
+						stream.removeWriteListener(writeListener);
+						return;
+					}
+					if (availableSize == 0) {
+						try {
+							eventLoop.threadLock.wait();
+						} catch (InterruptedException e) {
+							// System.out
+							// .println("interrupted while waiting for
+							// getWritableSize");
+							// clean up and return
+							Thread.currentThread().interrupt();
+							stream.removeWriteListener(writeListener);
+							return;
+						}
+					}
+
+				} while (availableSize == 0);
+
+				int framesToWrite = Math.min(remainingFrames, availableSize
+						/ getFormat().getFrameSize());
+				stream.write(data, currentFrame * getFormat().getFrameSize(),
+						framesToWrite * getFormat().getFrameSize());
+				remainingFrames -= framesToWrite;
+				currentFrame += framesToWrite;
+				framesSinceOpen += framesToWrite;
+				if (Thread.interrupted()) {
+					Thread.currentThread().interrupt();
+					break;
+				}
+				// System.out.println("remaining frames" + remainingFrames);
+				// System.out.println("currentFrame: " + currentFrame);
+				// System.out.println("framesSinceOpen: " + framesSinceOpen);
+			}
+		}
+
+		stream.removeWriteListener(writeListener);
+	}
+
+	public PulseAudioClip(EventLoop eventLoop, AudioFormat[] formats,
+			AudioFormat defaultFormat) {
+		supportedFormats = formats;
+		this.eventLoop = eventLoop;
+		this.defaultFormat = defaultFormat;
+		this.currentFormat = defaultFormat;
+		this.volume = PulseAudioVolumeControl.MAX_VOLUME;
+
+		clipThread = new ClipThread();
+
+	}
+
+	protected void connectLine(int bufferSize, Stream masterStream)
+			throws LineUnavailableException {
+		StreamBufferAttributes bufferAttributes = new StreamBufferAttributes(
+				bufferSize, bufferSize / 2, bufferSize / 2, bufferSize / 2, 0);
+
+		if (masterStream != null) {
+			synchronized (eventLoop.threadLock) {
+				stream.connectForPlayback(Stream.DEFAULT_DEVICE,
+						bufferAttributes, masterStream.getStreamPointer());
+			}
+		} else {
+			synchronized (eventLoop.threadLock) {
+				stream.connectForPlayback(Stream.DEFAULT_DEVICE,
+						bufferAttributes, null);
+			}
+		}
+	}
+
+	@Override
+	public int available() {
+		return 0; // a clip always returns 0
+	}
+
+	@Override
+	public void close() {
+
+		/* check for permission to play audio */
+		AudioPermission perm = new AudioPermission("play", null);
+		perm.checkGuard(null);
+
+		if (!isOpen) {
+			throw new IllegalStateException("line already closed");
+		}
+
+		clipThread.interrupt();
+
+		try {
+			clipThread.join();
+		} catch (InterruptedException e) {
+			e.printStackTrace();
+		}
+
+		currentFrame = 0;
+		framesSinceOpen = 0;
+
+		PulseAudioMixer mixer = PulseAudioMixer.getInstance();
+		mixer.removeSourceLine(this);
+
+		super.close();
+
+	}
+
+	/*
+	 * 
+	 * drain() on a Clip should block until the entire clip has finished playing
+	 * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4732218
+	 * 
+	 * 
+	 * @see org.classpath.icedtea.pulseaudio.PulseAudioDataLine#drain()
+	 */
+	@Override
+	public void drain() {
+		if (!isOpen) {
+			throw new IllegalStateException("line not open");
+		}
+
+		while (clipThread != null && clipThread.isAlive()) {
+			try {
+				clipThread.join();
+			} catch (InterruptedException e) {
+				// ignore
+			}
+		}
+
+		Operation operation;
+
+		synchronized (eventLoop.threadLock) {
+			operation = stream.drain();
+		}
+
+		operation.waitForCompletion();
+		operation.releaseReference();
+
+	}
+
+	@Override
+	public void flush() {
+		if (!isOpen) {
+			throw new IllegalStateException("line not open");
+		}
+
+		Operation operation;
+		synchronized (eventLoop.threadLock) {
+			operation = stream.flush();
+			operation.waitForCompletion();
+		}
+		operation.releaseReference();
+
+	}
+
+	@Override
+	public int getFrameLength() {
+		if (!isOpen) {
+			return AudioSystem.NOT_SPECIFIED;
+		}
+
+		return frameCount;
+	}
+
+	@Override
+	public int getFramePosition() {
+		if (!isOpen) {
+			throw new IllegalStateException("Line not open");
+		}
+		synchronized (clipLock) {
+			return (int) framesSinceOpen;
+		}
+	}
+
+	@Override
+	public long getLongFramePosition() {
+		if (!isOpen) {
+			throw new IllegalStateException("Line not open");
+		}
+
+		synchronized (clipLock) {
+			return framesSinceOpen;
+		}
+	}
+
+	@Override
+	public long getMicrosecondLength() {
+		if (!isOpen) {
+			return AudioSystem.NOT_SPECIFIED;
+		}
+		synchronized (clipLock) {
+			return frameCount / currentFormat.getFrameSize();
+		}
+	}
+
+	@Override
+	public long getMicrosecondPosition() {
+		if (!isOpen) {
+			throw new IllegalStateException("Line not open");
+		}
+
+		synchronized (clipLock) {
+			return framesSinceOpen / currentFormat.getFrameSize();
+		}
+	}
+
+	@Override
+	public void loop(int count) {
+		if (!isOpen) {
+			throw new IllegalStateException("Line not open");
+		}
+
+		if (count < 0 && count != LOOP_CONTINUOUSLY) {
+			throw new IllegalArgumentException("invalid value for count:"
+					+ count);
+		}
+
+		if (clipThread.isAlive() && count != 0) {
+			// Do nothing; behavior not specified by the Java API
+			return;
+		}
+
+		super.start();
+
+		synchronized (clipLock) {
+			if (currentFrame > endFrame) {
+				loopsLeft = 0;
+			} else {
+				loopsLeft = count;
+			}
+		}
+		if (!clipThread.isAlive()) {
+			clipThread = new ClipThread();
+			clipThread.start();
+		}
+
+	}
+
+	@Override
+	public void open() throws LineUnavailableException {
+		throw new IllegalArgumentException("open() on a Clip is not allowed");
+	}
+
+	@Override
+	public void open(AudioFormat format, byte[] data, int offset, int bufferSize)
+			throws LineUnavailableException {
+
+		/* check for permission to play audio */
+		AudioPermission perm = new AudioPermission("play", null);
+		perm.checkGuard(null);
+
+		super.open(format);
+		this.data = new byte[bufferSize];
+		System.arraycopy(data, offset, this.data, 0, bufferSize);
+
+		frameCount = bufferSize / format.getFrameSize();
+		currentFrame = 0;
+		framesSinceOpen = 0;
+		startFrame = 0;
+		endFrame = frameCount - 1;
+		loopsLeft = 0;
+
+		PulseAudioVolumeControl volumeControl = new PulseAudioVolumeControl(
+				this, eventLoop);
+		PulseAudioMuteControl muteControl = new PulseAudioMuteControl(this,
+				volumeControl);
+		controls.add(volumeControl);
+		controls.add(muteControl);
+		volume = volumeControl.getValue();
+		muted = muteControl.getValue();
+
+		PulseAudioMixer mixer = PulseAudioMixer.getInstance();
+		mixer.addSourceLine(this);
+
+		isOpen = true;
+
+	}
+
+	public byte[] native_setVolume(float value) {
+		return stream.native_setVolume(value);
+	}
+
+	public boolean isMuted() {
+		return muted;
+	}
+
+	public void setMuted(boolean value) {
+		muted = value;
+	}
+
+	public float getVolume() {
+		return this.volume;
+	}
+
+	public void setVolume(float value) {
+		this.volume = value;
+
+	}
+
+	@Override
+	public void open(AudioInputStream stream) throws LineUnavailableException,
+			IOException {
+		byte[] buffer = new byte[(int) (stream.getFrameLength() * stream
+				.getFormat().getFrameSize())];
+		stream.read(buffer, 0, buffer.length);
+
+		open(stream.getFormat(), buffer, 0, buffer.length);
+
+	}
+
+	@Override
+	public void setFramePosition(int frames) {
+		if (!isOpen) {
+			throw new IllegalStateException("Line not open");
+		}
+
+		if (frames > frameCount) {
+			throw new IllegalArgumentException("incorreft frame value");
+		}
+
+		synchronized (clipLock) {
+			currentFrame = frames;
+		}
+
+	}
+
+	@Override
+	public void setLoopPoints(int start, int end) {
+		if (!isOpen) {
+			throw new IllegalStateException("Line not open");
+		}
+
+		if (end == -1) {
+			end = frameCount - 1;
+		}
+
+		if (end < start) {
+			throw new IllegalArgumentException(
+					"ending point must be greater than or equal to the starting point");
+		}
+
+		synchronized (clipLock) {
+			startFrame = start;
+			endFrame = end;
+		}
+
+	}
+
+	@Override
+	public void setMicrosecondPosition(long microseconds) {
+		if (!isOpen) {
+			throw new IllegalStateException("Line not open");
+		}
+
+		float frameIndex = microseconds * currentFormat.getFrameRate();
+		synchronized (clipLock) {
+			currentFrame = (int) frameIndex;
+		}
+
+	}
+
+	@Override
+	public void start() {
+		if (!isOpen) {
+			throw new IllegalStateException("Line not open");
+		}
+
+		if (isStarted) {
+			throw new IllegalStateException("already started");
+		}
+
+		super.start();
+
+		if (!clipThread.isAlive()) {
+			synchronized (clipLock) {
+				loopsLeft = 0;
+			}
+			clipThread = new ClipThread();
+			clipThread.start();
+		}
+
+	}
+
+	public void stop() {
+		if (!isOpen) {
+			throw new IllegalStateException("Line not open");
+		}
+
+		if (!isStarted) {
+			throw new IllegalStateException("not started, so cant stop");
+		}
+
+		if (clipThread.isAlive()) {
+			clipThread.interrupt();
+		}
+		try {
+			clipThread.join();
+		} catch (InterruptedException e) {
+			e.printStackTrace();
+		}
+		synchronized (clipLock) {
+			loopsLeft = 0;
+		}
+
+		super.stop();
+
+	}
+	
+	public javax.sound.sampled.Line.Info getLineInfo() {
+		return new DataLine.Info(Clip.class, supportedFormats,
+				StreamBufferAttributes.MIN_VALUE,
+				StreamBufferAttributes.MAX_VALUE);
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioDataLine.java	Fri Oct 10 16:03:12 2008 -0400
@@ -0,0 +1,471 @@
+/* PulseAudioClip.java
+   Copyright (C) 2008 Red Hat, Inc.
+
+This file is part of IcedTea.
+
+IcedTea is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License as published by
+the Free Software Foundation, version 2.
+
+IcedTea is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with IcedTea; see the file COPYING.  If not, write to
+the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library.  Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module.  An independent module is a module which is not derived from
+or based on this library.  If you modify this library, you may extend
+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;
+
+import java.util.concurrent.Semaphore;
+
+import javax.sound.sampled.AudioFormat;
+import javax.sound.sampled.AudioSystem;
+import javax.sound.sampled.DataLine;
+import javax.sound.sampled.LineEvent;
+import javax.sound.sampled.LineUnavailableException;
+
+import org.classpath.icedtea.pulseaudio.Stream.WriteListener;
+
+public abstract class PulseAudioDataLine extends PulseAudioLine implements
+		DataLine {
+
+	protected static final int DEFAULT_BUFFER_SIZE = StreamBufferAttributes.SANE_DEFAULT;
+	protected static final String PULSEAUDIO_FORMAT_KEY = "PulseAudioFormatKey";
+
+	protected String streamName = "Java Stream";
+
+	// true between start() and stop()
+	protected boolean isStarted = false;
+
+	// true between a started and an underflow callback
+	protected boolean dataWritten = false;
+
+	// true if a stream has been paused
+	// protected boolean isPaused = false;
+
+	protected AudioFormat[] supportedFormats = null;
+	protected AudioFormat currentFormat = null;
+	protected AudioFormat defaultFormat = null;
+	protected boolean sendEvents = true;
+
+	protected int bufferSize = 0;
+	// the total number of frames played since this line was opened
+	protected long framesSinceOpen = 0;
+
+	protected EventLoop eventLoop = null;
+	protected Semaphore semaphore = new Semaphore(0);
+	protected Stream stream;
+	boolean writeInterrupted = false;
+
+	protected void open(AudioFormat format, int bufferSize)
+			throws LineUnavailableException {
+
+		if (isOpen) {
+			throw new IllegalStateException("Line is already open");
+		}
+
+		createStream(format);
+		addStreamListeners();
+		connect(null, bufferSize);
+	}
+
+	private void createStream(AudioFormat format)
+			throws LineUnavailableException {
+
+		for (AudioFormat myFormat : supportedFormats) {
+			if (format.matches(myFormat)) {
+				/*
+				 * A few issues with format:
+				 * 
+				 * To match: SAME encoding: safe because its a java enum. SAME
+				 * number of channels: safe because myFormat has specific
+				 * values. SAME bits per sample (aka sampleSize) and bytes per
+				 * frame (aka frameSize): safe because myFormat has specific
+				 * values. SAME sample rate: _not_ safe because myFormat uses
+				 * AudioSystem.NOT_SPECIFIED. SAME frame rate: safe because we
+				 * _ignore_ it completely ;)
+				 * 
+				 * 
+				 */
+
+				float sampleRate = format.getSampleRate();
+				if (sampleRate == (float) AudioSystem.NOT_SPECIFIED) {
+					/* pick a random sample rate */
+					sampleRate = 44100.0f;
+				}
+
+				synchronized (eventLoop.threadLock) {
+					stream = new Stream(eventLoop.getContextPointer(),
+							streamName, Stream.Format.valueOf((String) myFormat
+									.getProperty(PULSEAUDIO_FORMAT_KEY)),
+							(int) sampleRate, myFormat.getChannels());
+
+				}
+				currentFormat = format;
+				isOpen = true;
+			}
+		}
+
+		if (!isOpen) {
+			throw new IllegalArgumentException("Invalid format");
+		}
+
+		// System.out.println("Stream " + stream + " created");
+
+	}
+
+	private void addStreamListeners() {
+		Stream.StateListener openCloseListener = new Stream.StateListener() {
+
+			@Override
+			public void update() {
+				synchronized (eventLoop.threadLock) {
+
+					/*
+					 * Note the order: first we notify all the listeners, and
+					 * then return. this means no race conditions when the
+					 * listeners are removed before they get called by close
+					 * 
+					 * eg:
+					 * 
+					 * line.close(); line.removeLineListener(listener)
+					 * 
+					 * the listener is guaranteed to have run
+					 * 
+					 */
+
+					if (stream.getState() == Stream.State.READY) {
+						if (sendEvents) {
+							fireLineEvent(new LineEvent(
+									PulseAudioDataLine.this,
+									LineEvent.Type.OPEN, framesSinceOpen));
+						}
+						semaphore.release();
+
+					} else if (stream.getState() == Stream.State.TERMINATED
+							|| stream.getState() == Stream.State.FAILED) {
+						if (sendEvents) {
+							fireLineEvent((new LineEvent(
+									PulseAudioDataLine.this,
+									LineEvent.Type.CLOSE, framesSinceOpen)));
+						}
+						synchronized (this) {
+							this.notifyAll();
+						}
+						semaphore.release();
+
+					}
+				}
+			}
+		};
+
+		stream.addStateListener(openCloseListener);
+
+		Stream.UnderflowListener stoppedListener = new Stream.UnderflowListener() {
+			@Override
+			public void update() {
+				dataWritten = false;
+
+				// always send a STOP event on an underflow (assumption:
+				// an underflow can't happen while the stream is corked)
+				fireLineEvent(new LineEvent(PulseAudioDataLine.this,
+						LineEvent.Type.STOP, framesSinceOpen));
+			}
+		};
+		stream.addUnderflowListener(stoppedListener);
+
+		Stream.PlaybackStartedListener startedListener = new Stream.PlaybackStartedListener() {
+			@Override
+			public void update() {
+				fireLineEvent(new LineEvent(PulseAudioDataLine.this,
+						LineEvent.Type.START, framesSinceOpen));
+
+				dataWritten = true;
+				synchronized (this) {
+					this.notifyAll();
+				}
+
+			}
+		};
+
+		stream.addPlaybackStartedListener(startedListener);
+
+		WriteListener writeNotifier = new WriteListener() {
+
+			@Override
+			public void update() {
+				synchronized (eventLoop.threadLock) {
+					eventLoop.threadLock.notifyAll();
+				}
+			}
+
+		};
+		stream.addWriteListener(writeNotifier);
+
+		Stream.CorkListener corkListener = new Stream.CorkListener() {
+
+			@Override
+			public void update() {
+				synchronized (eventLoop.threadLock) {
+					eventLoop.threadLock.notifyAll();
+				}
+			}
+
+		};
+		stream.addCorkListener(corkListener);
+	}
+
+	private void connect(Stream masterStream, int bufferSize)
+			throws LineUnavailableException {
+
+		try {
+			synchronized (eventLoop.threadLock) {
+				connectLine(bufferSize, masterStream);
+			}
+		} catch (LineUnavailableException e) {
+			// error connecting to the server!
+			// stream.removePlaybackStartedListener(startedListener);
+			// stream.removeUnderflowListener(stoppedListener);
+			// stream.removeStateListener(openCloseListener);
+			stream.free();
+			stream = null;
+			throw e;
+
+		}
+		this.bufferSize = bufferSize;
+		try {
+			semaphore.acquire();
+			synchronized (eventLoop.threadLock) {
+				if (stream.getState() != Stream.State.READY) {
+					System.out.println(stream.getState());
+					stream.disconnect();
+					stream.free();
+					throw new LineUnavailableException(
+							"unable to obtain a line");
+				}
+			}
+		} catch (InterruptedException e) {
+			throw new LineUnavailableException("unable to prepare stream");
+		}
+	}
+
+	protected void open(AudioFormat format) throws LineUnavailableException {
+		open(format, DEFAULT_BUFFER_SIZE);
+
+	}
+
+	public void open() throws LineUnavailableException {
+		assert (defaultFormat != null);
+
+		open(defaultFormat, DEFAULT_BUFFER_SIZE);
+	}
+
+	public void close() {
+
+		if (!isOpen) {
+			throw new IllegalStateException(
+					"Line must be open for close() to work");
+		}
+
+		synchronized (eventLoop.threadLock) {
+			stream.disconnect();
+		}
+
+		try {
+			semaphore.acquire();
+		} catch (InterruptedException e) {
+			throw new RuntimeException("unable to prepare stream");
+		}
+
+		synchronized (eventLoop.threadLock) {
+			stream.free();
+		}
+
+		super.close();
+
+		isStarted = false;
+	}
+
+	public void reconnectforSynchronization(Stream masterStream)
+			throws LineUnavailableException {
+		sendEvents = false;
+		drain();
+
+		synchronized (eventLoop.threadLock) {
+			stream.disconnect();
+		}
+		try {
+			semaphore.acquire();
+		} catch (InterruptedException e) {
+			throw new RuntimeException("unable to prepare stream");
+		}
+
+		createStream(getFormat());
+		addStreamListeners();
+		connect(masterStream, getBufferSize());
+
+		sendEvents = true;
+	}
+
+	public void start() {
+		if (!isOpen) {
+			throw new IllegalStateException(
+					"Line must be open()ed before it can be start()ed");
+		}
+
+		if (isStarted) {
+			return;
+
+		}
+
+		Operation op;
+		synchronized (eventLoop.threadLock) {
+			op = stream.unCork();
+		}
+
+		op.waitForCompletion();
+		op.releaseReference();
+		isStarted = true;
+
+	}
+
+	public synchronized void stop() {
+		if (!isOpen) {
+			throw new IllegalStateException(
+					"Line must be open()ed before it can be start()ed");
+
+		}
+		writeInterrupted = true;
+		if (!isStarted) {
+			return;
+		}
+
+		Operation op;
+		synchronized (eventLoop.threadLock) {
+			op = stream.cork();
+			// if there are no data on the line when stop was called,
+			// don't send a stop event
+			if (dataWritten && (isStarted)) {
+				fireLineEvent(new LineEvent(PulseAudioDataLine.this,
+						LineEvent.Type.STOP, framesSinceOpen));
+			}
+		}
+
+		op.waitForCompletion();
+		op.releaseReference();
+
+		isStarted = false;
+	}
+
+	/*
+	 * TODO
+	 * 
+	 * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4791152 :
+	 * 
+	 * a line is active in between calls to start() and stop(). In that sense,
+	 * active means that the line is ready to take or give data. Running is
+	 * tightly bound to data flow in the line. I.e. when you start a
+	 * SourceDataLine but never write data to it, the line should not be
+	 * running. This also means that a line should become not running on buffer
+	 * underrun/overflow.
+	 * 
+	 * 
+	 * HOWEVER, the javadocs say the opposite thing! (need help from the jck =
+	 * official spec)
+	 * 
+	 * 
+	 */
+
+	public boolean isActive() {
+		return isStarted;
+	}
+
+	public boolean isRunning() {
+		return isStarted && dataWritten;
+	}
+
+	protected abstract void connectLine(int bufferSize, Stream masterStream)
+			throws LineUnavailableException;
+
+	public abstract void drain();
+
+	public Stream getStream() {
+		if (!isOpen) {
+			throw new IllegalStateException("Line must be open");
+		}
+
+		return stream;
+	}
+
+	@Override
+	public int getBufferSize() {
+		if (!isOpen) {
+			return DEFAULT_BUFFER_SIZE;
+		}
+		return bufferSize;
+	}
+
+	@Override
+	public AudioFormat getFormat() {
+		if (!isOpen) {
+			return defaultFormat;
+		}
+		return currentFormat;
+	}
+
+	public float getLevel() {
+		return AudioSystem.NOT_SPECIFIED;
+	}
+
+	public void setName(String streamName) {
+		if (isOpen) {
+
+			Operation o;
+			synchronized (eventLoop.threadLock) {
+				o = stream.setName(streamName);
+			}
+			o.waitForCompletion();
+			o.releaseReference();
+
+		}
+
+		this.streamName = streamName;
+
+	}
+
+	public String getName() {
+		return streamName;
+	}
+
+	public int getBytesInBuffer() {
+		Operation o;
+		synchronized (eventLoop.threadLock) {
+			o = stream.updateTimingInfo();
+		}
+		o.waitForCompletion();
+		o.releaseReference();
+		return stream.bytesInBuffer();
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioLine.java	Fri Oct 10 16:03:12 2008 -0400
@@ -0,0 +1,118 @@
+/* PulseAudioClip.java
+   Copyright (C) 2008 Red Hat, Inc.
+
+This file is part of IcedTea.
+
+IcedTea is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License as published by
+the Free Software Foundation, version 2.
+
+IcedTea is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with IcedTea; see the file COPYING.  If not, write to
+the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library.  Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module.  An independent module is a module which is not derived from
+or based on this library.  If you modify this library, you may extend
+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;
+
+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.Control.Type;
+
+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);
+	}
+
+	@Override
+	public void close() {
+		if (!isOpen) {
+			throw new IllegalStateException("Line is not open");
+		}
+
+		lineListeners.clear();
+
+		isOpen = false;
+	}
+
+	protected void fireLineEvent(LineEvent e) {
+		for (LineListener lineListener : lineListeners) {
+			lineListener.update(e);
+		}
+	}
+
+	@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;
+	}
+
+	public void removeLineListener(LineListener listener) {
+		lineListeners.remove(listener);
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioMixer.java	Fri Oct 10 16:03:12 2008 -0400
@@ -0,0 +1,881 @@
+/* PulseAudioMixer.java
+   Copyright (C) 2008 Red Hat, Inc.
+
+This file is part of IcedTea.
+
+IcedTea is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License as published by
+the Free Software Foundation, version 2.
+
+IcedTea is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with IcedTea; see the file COPYING.  If not, write to
+the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library.  Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module.  An independent module is a module which is not derived from
+or based on this library.  If you modify this library, you may extend
+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;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Semaphore;
+
+import javax.sound.sampled.AudioFormat;
+import javax.sound.sampled.AudioInputStream;
+import javax.sound.sampled.AudioPermission;
+import javax.sound.sampled.AudioSystem;
+import javax.sound.sampled.Clip;
+import javax.sound.sampled.Control;
+import javax.sound.sampled.DataLine;
+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.Port;
+import javax.sound.sampled.SourceDataLine;
+import javax.sound.sampled.TargetDataLine;
+import javax.sound.sampled.UnsupportedAudioFileException;
+import javax.sound.sampled.AudioFormat.Encoding;
+import javax.sound.sampled.Control.Type;
+
+public class PulseAudioMixer implements javax.sound.sampled.Mixer {
+	// singleton
+
+	public EventLoop eventLoop;
+	public Thread eventLoopThread;
+
+	private List<Line.Info> sourceLineInfos = new ArrayList<Line.Info>();
+	private List<Line.Info> staticSourceLineInfos = new ArrayList<Line.Info>();
+
+	private List<Line.Info> targetLineInfos = new ArrayList<Line.Info>();
+	private List<Line.Info> staticTargetLineInfos = new ArrayList<Line.Info>();
+
+	private static PulseAudioMixer _instance = null;
+
+	private static final String DEFAULT_APP_NAME = "Java App";
+	private static final String PULSEAUDIO_FORMAT_KEY = "PulseAudioFormatKey";
+
+	private boolean isOpen = false;
+
+	private final List<PulseAudioLine> sourceLines = new ArrayList<PulseAudioLine>();
+	private final List<PulseAudioLine> targetLines = new ArrayList<PulseAudioLine>();
+
+	private final List<LineListener> lineListeners = new ArrayList<LineListener>();
+
+	private PulseAudioMixer() {
+		AudioFormat[] formats = getSupportedFormats();
+
+		staticSourceLineInfos.add(new DataLine.Info(SourceDataLine.class,
+				formats, StreamBufferAttributes.MIN_VALUE,
+				StreamBufferAttributes.MAX_VALUE));
+		staticSourceLineInfos.add(new DataLine.Info(Clip.class, formats,
+				StreamBufferAttributes.MIN_VALUE,
+				StreamBufferAttributes.MAX_VALUE));
+
+		staticTargetLineInfos.add(new DataLine.Info(TargetDataLine.class,
+				formats, StreamBufferAttributes.MIN_VALUE,
+				StreamBufferAttributes.MAX_VALUE));
+
+		refreshSourceAndTargetLines();
+
+	}
+
+	synchronized public static PulseAudioMixer getInstance() {
+		if (_instance == null) {
+			_instance = new PulseAudioMixer();
+		}
+		return _instance;
+	}
+
+	private AudioFormat[] getSupportedFormats() {
+
+		List<AudioFormat> supportedFormats = new ArrayList<AudioFormat>();
+
+		Map<String, Object> properties;
+
+		/*
+		 * 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
+		 */
+
+		/*
+		 * technically, PulseAudio supports up to 16 channels, but things get
+		 * interesting with channel maps
+		 * 
+		 * PA_CHANNEL_MAP_DEFAULT (=PA_CHANNEL_MAP_AIFF) supports 1,2,3,4,5 or 6
+		 * channels only
+		 * 
+		 */
+		int[] channelSizes = new int[] { 1, 2, 3, 4, 5, 6 };
+
+		for (int channelSize : channelSizes) {
+			properties = new HashMap<String, Object>();
+			properties.put(PULSEAUDIO_FORMAT_KEY, "PA_SAMPLE_ALAW");
+
+			int sampleSize = 8;
+			final AudioFormat PA_SAMPLE_ALAW = new AudioFormat(Encoding.ALAW, // encoding
+					AudioSystem.NOT_SPECIFIED, // sample rate
+					sampleSize, // sample size
+					channelSize, // channels
+					sampleSize / 8 * channelSize, // frame size
+					AudioSystem.NOT_SPECIFIED, // frame rate
+					false, // big endian?
+					properties);
+
+			supportedFormats.add(PA_SAMPLE_ALAW);
+		}
+
+		for (int channelSize : channelSizes) {
+			properties = new HashMap<String, Object>();
+			properties.put(PULSEAUDIO_FORMAT_KEY, "PA_SAMPLE_ULAW");
+
+			int sampleSize = 8;
+			final AudioFormat PA_SAMPLE_ULAW = new AudioFormat(Encoding.ULAW, // encoding
+					AudioSystem.NOT_SPECIFIED, // sample rate
+					sampleSize, // sample size
+					channelSize, // channels
+					sampleSize / 8 * channelSize, // frame size
+					AudioSystem.NOT_SPECIFIED, // frame rate
+					false, // big endian?
+					properties);
+
+			supportedFormats.add(PA_SAMPLE_ULAW);
+		}
+
+		for (int channelSize : channelSizes) {
+			properties = new HashMap<String, Object>();
+			properties.put(PULSEAUDIO_FORMAT_KEY, "PA_SAMPLE_S16BE");
+
+			int sampleSize = 16;
+			final AudioFormat PA_SAMPLE_S16BE = new AudioFormat(
+					Encoding.PCM_SIGNED, // encoding
+					AudioSystem.NOT_SPECIFIED, // sample rate
+					sampleSize, // sample size
+					channelSize, // channels
+					sampleSize / 8 * channelSize, // frame size
+					AudioSystem.NOT_SPECIFIED, // frame rate
+					true, // big endian?
+					properties);
+
+			supportedFormats.add(PA_SAMPLE_S16BE);
+		}
+
+		for (int channelSize : channelSizes) {
+			properties = new HashMap<String, Object>();
+			properties.put(PULSEAUDIO_FORMAT_KEY, "PA_SAMPLE_S16LE");
+
+			int sampleSize = 16;
+			final AudioFormat A_SAMPLE_S16LE = new AudioFormat(
+					Encoding.PCM_SIGNED, // encoding
+					AudioSystem.NOT_SPECIFIED, // sample rate
+					sampleSize, // sample size
+					channelSize, // channels
+					sampleSize / 8 * channelSize, // frame size
+					AudioSystem.NOT_SPECIFIED, // frame rate
+					false, // big endian?
+					properties);
+
+			supportedFormats.add(A_SAMPLE_S16LE);
+		}
+
+		for (int channelSize : channelSizes) {
+			properties = new HashMap<String, Object>();
+			properties.put(PULSEAUDIO_FORMAT_KEY, "PA_SAMPLE_S32BE");
+
+			int sampleSize = 32;
+			final AudioFormat PA_SAMPLE_S32BE = new AudioFormat(
+					Encoding.PCM_SIGNED, // encoding
+					AudioSystem.NOT_SPECIFIED, // sample rate
+					sampleSize, // sample size
+					channelSize, // channels
+					sampleSize / 8 * channelSize, // frame size
+					AudioSystem.NOT_SPECIFIED, // frame rate
+					true, // big endian?
+					properties);
+
+			supportedFormats.add(PA_SAMPLE_S32BE);
+		}
+
+		for (int channelSize : channelSizes) {
+			properties = new HashMap<String, Object>();
+			properties.put(PULSEAUDIO_FORMAT_KEY, "PA_SAMPLE_S32LE");
+
+			int sampleSize = 32;
+			final AudioFormat PA_SAMPLE_S32LE = new AudioFormat(
+					Encoding.PCM_SIGNED, // encoding
+					AudioSystem.NOT_SPECIFIED, // sample rate
+					sampleSize, // sample size
+					channelSize, // channels
+					sampleSize / 8 * channelSize, // frame size
+					AudioSystem.NOT_SPECIFIED, // frame rate
+					false, // big endian?
+					properties);
+
+			supportedFormats.add(PA_SAMPLE_S32LE);
+		}
+
+		for (int channelSize : channelSizes) {
+			properties = new HashMap<String, Object>();
+			properties.put(PULSEAUDIO_FORMAT_KEY, "PA_SAMPLE_U8");
+
+			int sampleSize = 8; // in bits
+			AudioFormat PA_SAMPLE_U8 = new AudioFormat(Encoding.PCM_UNSIGNED, // encoding
+					AudioSystem.NOT_SPECIFIED, // sample rate
+					sampleSize, // sample size
+					channelSize, // channels
+					sampleSize / 8 * channelSize, // frame size in bytes
+					AudioSystem.NOT_SPECIFIED, // frame rate
+					false, // big endian?
+					properties);
+
+			supportedFormats.add(PA_SAMPLE_U8);
+		}
+
+		return supportedFormats.toArray(new AudioFormat[0]);
+	}
+
+	@Override
+	public Line getLine(Line.Info info) throws LineUnavailableException {
+
+		if (!isLineSupported(info)) {
+			throw new IllegalArgumentException("Line unsupported: " + info);
+		}
+
+		if (!isOpen) {
+			throw new LineUnavailableException("The mixer isnt open");
+		}
+
+		AudioFormat[] formats = null;
+		AudioFormat defaultFormat = null;
+
+		if (DataLine.Info.class.isInstance(info)) {
+			ArrayList<AudioFormat> formatList = new ArrayList<AudioFormat>();
+			AudioFormat[] requestedFormats = ((DataLine.Info) info)
+					.getFormats();
+			for (int i = 0; i < requestedFormats.length; i++) {
+				AudioFormat f1 = requestedFormats[i];
+				for (AudioFormat f2 : getSupportedFormats()) {
+
+					if (f1.matches(f2)) {
+						formatList.add(f2);
+						defaultFormat = f1;
+					}
+				}
+			}
+			formats = formatList.toArray(new AudioFormat[0]);
+
+		} else {
+			formats = getSupportedFormats();
+			defaultFormat = new AudioFormat(Encoding.PCM_UNSIGNED, 44100, 8, 2,
+					2, AudioSystem.NOT_SPECIFIED, false);
+		}
+
+		if ((info.getLineClass() == SourceDataLine.class)) {
+			/* check for permission to play audio */
+			AudioPermission perm = new AudioPermission("play", null);
+			perm.checkGuard(null);
+
+			return new PulseAudioSourceDataLine(eventLoop, formats,
+					defaultFormat);
+		}
+
+		if ((info.getLineClass() == TargetDataLine.class)) {
+			/* check for permission to play audio */
+			AudioPermission perm = new AudioPermission("record", null);
+			perm.checkGuard(null);
+
+			return new PulseAudioTargetDataLine(eventLoop, formats,
+					defaultFormat);
+		}
+
+		if ((info.getLineClass() == Clip.class)) {
+			/* check for permission to play audio */
+			AudioPermission perm = new AudioPermission("play", null);
+			perm.checkGuard(null);
+
+			return new PulseAudioClip(eventLoop, formats, defaultFormat);
+		}
+
+		if (Port.Info.class.isInstance(info)) {
+			Port.Info portInfo = (Port.Info) info;
+			if (portInfo.isSource()) {
+				return new PulseAudioSourcePort(portInfo.getName(), eventLoop);
+			} else {
+				return new PulseAudioTargetPort(portInfo.getName(), eventLoop);
+			}
+		}
+
+		throw new IllegalArgumentException("No matching lines found");
+
+	}
+
+	@Override
+	public int getMaxLines(Line.Info info) {
+		/*
+		 * PulseAudio supports (theoretically) unlimited number of streams for
+		 * supported formats
+		 */
+		if (isLineSupported(info)) {
+			return AudioSystem.NOT_SPECIFIED;
+		}
+
+		return 0;
+	}
+
+	@Override
+	public Info getMixerInfo() {
+		return PulseAudioMixerInfo.getInfo();
+	}
+
+	public javax.sound.sampled.Line.Info[] getSourceLineInfo() {
+		return sourceLineInfos.toArray(new Line.Info[0]);
+	}
+
+	@Override
+	public javax.sound.sampled.Line.Info[] getSourceLineInfo(
+			javax.sound.sampled.Line.Info info) {
+		ArrayList<Line.Info> infos = new ArrayList<Line.Info>();
+
+		for (Line.Info supportedInfo : sourceLineInfos) {
+			if (info.matches(supportedInfo)) {
+				infos.add(supportedInfo);
+			}
+		}
+		return infos.toArray(new Line.Info[infos.size()]);
+	}
+
+	@Override
+	public Line[] getSourceLines() {
+
+		/* check for permmission to play audio */
+		AudioPermission perm = new AudioPermission("play", null);
+		perm.checkGuard(null);
+
+		return (Line[]) sourceLines.toArray(new Line[0]);
+
+	}
+
+	@Override
+	public javax.sound.sampled.Line.Info[] getTargetLineInfo() {
+		return targetLineInfos.toArray(new Line.Info[0]);
+	}
+
+	@Override
+	public javax.sound.sampled.Line.Info[] getTargetLineInfo(
+			javax.sound.sampled.Line.Info info) {
+		ArrayList<javax.sound.sampled.Line.Info> infos = new ArrayList<javax.sound.sampled.Line.Info>();
+
+		for (Line.Info supportedInfo : targetLineInfos) {
+			if (info.matches(supportedInfo)) {
+				infos.add(supportedInfo);
+			}
+		}
+		return infos.toArray(new Line.Info[infos.size()]);
+	}
+
+	@Override
+	public Line[] getTargetLines() {
+
+		/* check for permission to play audio */
+		AudioPermission perm = new AudioPermission("record", null);
+		perm.checkGuard(null);
+
+		return (Line[]) targetLines.toArray(new TargetDataLine[0]);
+	}
+
+	@Override
+	public boolean isLineSupported(javax.sound.sampled.Line.Info info) {
+		if (info != null) {
+			for (Line.Info myInfo : sourceLineInfos) {
+				if (info.matches(myInfo)) {
+					return true;
+				}
+			}
+
+			for (Line.Info myInfo : targetLineInfos) {
+				if (info.matches(myInfo)) {
+					return true;
+				}
+			}
+
+		}
+		return false;
+
+	}
+
+	@Override
+	public boolean isSynchronizationSupported(Line[] lines, boolean maintainSync) {
+
+		return false;
+	}
+
+	@Override
+	public void synchronize(Line[] lines, boolean maintainSync) {
+
+		throw new IllegalArgumentException(
+				"Mixer does not support synchronizing lines");
+
+		// Line masterStream = null;
+		// for (Line line : lines) {
+		// if (line.isOpen()) {
+		// masterStream = line;
+		// break;
+		// }
+		// }
+		// if (masterStream == null) {
+		// // for now, can't synchronize lines if none of them is open (no
+		// // stream pointer to pass)
+		// // will see what to do about this later
+		// throw new IllegalArgumentException();
+		// }
+		//
+		// try {
+		//
+		// for (Line line : lines) {
+		// if (line != masterStream) {
+		//
+		// ((PulseAudioDataLine) line)
+		// .reconnectforSynchronization(((PulseAudioDataLine) masterStream)
+		// .getStream());
+		//
+		// }
+		// }
+		// } catch (LineUnavailableException e) {
+		// // we couldn't reconnect, so tell the user we failed by throwing an
+		// // exception
+		// throw new IllegalArgumentException(e);
+		// }
+
+	}
+
+	@Override
+	public void unsynchronize(Line[] lines) {
+		// FIXME should be able to implement this
+		throw new IllegalArgumentException();
+	}
+
+	@Override
+	public void addLineListener(LineListener listener) {
+		lineListeners.add(listener);
+	}
+
+	@Override
+	synchronized public void close() {
+
+		/*
+		 * only allow the mixer to be controlled if either playback or recording
+		 * is allowed
+		 */
+
+		try {
+			/* check for permission to play audio */
+			AudioPermission perm = new AudioPermission("play", null);
+			perm.checkGuard(null);
+		} catch (SecurityException e) {
+			/* check for permission to record audio */
+			AudioPermission perm = new AudioPermission("record", null);
+			perm.checkGuard(null);
+		}
+
+		if (!this.isOpen) {
+			throw new IllegalStateException("Mixer is not open; cant close");
+		}
+
+		eventLoopThread.interrupt();
+
+		try {
+			eventLoopThread.join();
+		} catch (InterruptedException e) {
+			System.out.println(this.getClass().getName()
+					+ ": interrupted while waiting for eventloop to finish");
+		}
+
+		// System.out.println(this.getClass().getName() + ": closed");
+
+		isOpen = false;
+
+		if (sourceLines.size() > 0) {
+			System.out.println("DEBUG: some source lines have not been closed");
+			assert (sourceLines.size() < 0); // always fail
+		}
+		if (targetLines.size() > 0) {
+			System.out.println("DEBUG: some target lines have not been closed");
+			assert (targetLines.size() < 0); // always fail
+		}
+
+		synchronized (lineListeners) {
+			lineListeners.clear();
+		}
+
+		refreshSourceAndTargetLines();
+
+	}
+
+	@Override
+	public Control getControl(Type control) {
+		// mixer supports no controls
+		throw new IllegalArgumentException();
+	}
+
+	@Override
+	public Control[] getControls() {
+		// mixer supports no controls; return an array of length 0
+		return new Control[] {};
+	}
+
+	@Override
+	public javax.sound.sampled.Line.Info getLineInfo() {
+		// System.out.println("DEBUG: PulseAudioMixer.getLineInfo() called");
+		return new Line.Info(PulseAudioMixer.class);
+	}
+
+	@Override
+	public boolean isControlSupported(Type control) {
+		// mixer supports no controls
+		return false;
+	}
+
+	@Override
+	public boolean isOpen() {
+		return isOpen;
+	}
+
+	@Override
+	public void open() throws LineUnavailableException {
+		openLocal();
+
+	}
+
+	public void open(String appName, String host) throws UnknownHostException,
+			LineUnavailableException {
+		openRemote(appName, host);
+	}
+
+	public void openLocal() throws LineUnavailableException {
+		openLocal(DEFAULT_APP_NAME);
+	}
+
+	public void openLocal(String appName) throws LineUnavailableException {
+		try {
+			openRemote(appName, null);
+		} catch (UnknownHostException e) {
+			// not possible
+		}
+	}
+
+	public void openRemote(String appName, String host)
+			throws UnknownHostException, LineUnavailableException {
+		openRemote(appName, host, null);
+	}
+
+	synchronized public void openRemote(String appName, String host,
+			Integer port) throws UnknownHostException, LineUnavailableException {
+
+		/*
+		 * only allow the mixer to be controlled if either playback or recording
+		 * is allowed
+		 */
+
+		try {
+			/* check for permission to play audio */
+			AudioPermission perm = new AudioPermission("play", null);
+			perm.checkGuard(null);
+		} catch (SecurityException e) {
+			/* check for permission to record audio */
+			AudioPermission perm = new AudioPermission("record", null);
+			perm.checkGuard(null);
+		}
+
+		if (isOpen) {
+			throw new IllegalStateException("Mixer is already open");
+		}
+
+		InetAddress addr = InetAddress.getAllByName(host)[0];
+
+		if (port != null) {
+			host = addr.getHostAddress();
+			host = host + ":" + String.valueOf(port);
+		}
+
+		eventLoop = EventLoop.getEventLoop();
+		eventLoop.setAppName(appName);
+		eventLoop.setServer(host);
+
+		ContextListener generalEventListener = new ContextListener() {
+			@Override
+			public void update(ContextEvent e) {
+				if (e.getType() == ContextEvent.Type.READY) {
+					fireEvent(new LineEvent(PulseAudioMixer.this,
+							LineEvent.Type.OPEN, AudioSystem.NOT_SPECIFIED));
+				} else if (e.getType() == ContextEvent.Type.FAILED
+						|| e.getType() == ContextEvent.Type.TERMINATED) {
+					fireEvent(new LineEvent(PulseAudioMixer.this,
+							LineEvent.Type.CLOSE, AudioSystem.NOT_SPECIFIED));
+				}
+			}
+		};
+
+		eventLoop.addContextListener(generalEventListener);
+
+		final Semaphore ready = new Semaphore(0);
+
+		ContextListener initListener = new ContextListener() {
+
+			@Override
+			public void update(ContextEvent e) {
+				if (e.getType() == ContextEvent.Type.READY
+						|| e.getType() == ContextEvent.Type.FAILED
+						|| e.getType() == ContextEvent.Type.TERMINATED) {
+					ready.release();
+				}
+			}
+
+		};
+
+		eventLoop.addContextListener(initListener);
+
+		eventLoopThread = new Thread(eventLoop, "PulseAudio Eventloop Thread");
+
+		/*
+		 * Make the thread exit if by some weird error it is the only thread
+		 * running. The application should be able to exit if the main thread
+		 * doesn't or can't (perhaps an assert?) do a mixer.close().
+		 */
+		eventLoopThread.setDaemon(true);
+		eventLoopThread.start();
+
+		try {
+			// System.out.println("waiting...");
+			ready.acquire();
+			if (eventLoop.getStatus() != 4) {
+				/*
+				 * when exiting, wait for the thread to end otherwise we get one
+				 * thread that inits the singleton with new data and the old
+				 * thread then cleans up the singleton asserts fail all over the
+				 * place
+				 */
+				eventLoop.removeContextListener(initListener);
+				eventLoopThread.interrupt();
+				eventLoopThread.join();
+				throw new LineUnavailableException();
+			}
+			eventLoop.removeContextListener(initListener);
+			// System.out.println("got signal");
+		} catch (InterruptedException e) {
+			System.out
+					.println("PulseAudioMixer: got interrupted while waiting for the EventLoop to initialize");
+		}
+
+		// System.out.println(this.getClass().getName() + ": ready");
+
+		this.isOpen = true;
+
+		// sourceLineInfo and targetLineInfo need to be updated with
+		// port infos, which can only be obtained after EventLoop had started
+
+		refreshSourceAndTargetLines();
+
+		for (String portName : eventLoop.updateSourcePortNameList()) {
+			sourceLineInfos.add(new Port.Info(Port.class, portName, true));
+		}
+
+		for (String portName : eventLoop.updateTargetPortNameList()) {
+			targetLineInfos.add(new Port.Info(Port.class, portName, false));
+		}
+
+	}
+
+	@Override
+	public void removeLineListener(LineListener listener) {
+		lineListeners.remove(listener);
+	}
+
+	/*
+	 * Should this method be synchronized? I had a few reasons, but i forgot
+	 * them Pros: - Thread safety?
+	 * 
+	 * Cons: - eventListeners are run from other threads, if those then call
+	 * fireEvent while a method is waiting on a listener, this synchronized
+	 * block wont be entered: deadlock!
+	 * 
+	 */
+	private void fireEvent(final LineEvent e) {
+		synchronized (lineListeners) {
+			for (LineListener lineListener : lineListeners) {
+				lineListener.update(e);
+			}
+		}
+	}
+
+	public static void debug(String string) {
+		System.out.println("DEBUG: " + string);
+	}
+
+	public static void main(String[] args) throws Exception {
+		Mixer.Info mixerInfos[] = AudioSystem.getMixerInfo();
+		Mixer.Info selectedMixerInfo = null;
+		// int i = 0;
+		for (Mixer.Info info : mixerInfos) {
+			// System.out.println("Mixer Line " + i++ + ": " + info.getName() +
+			// " " + info.getDescription());
+			if (info.getName().contains("PulseAudio")) {
+				selectedMixerInfo = info;
+				System.out.println(selectedMixerInfo);
+			}
+		}
+
+		PulseAudioMixer mixer = (PulseAudioMixer) AudioSystem
+				.getMixer(selectedMixerInfo);
+
+		mixer.open();
+
+		String fileName1 = "testsounds/startup.wav";
+		PulseAudioSourceDataLine line1;
+		line1 = (PulseAudioSourceDataLine) mixer.getLine(new Line.Info(
+				SourceDataLine.class));
+		line1.setName("Line 1");
+
+		String fileName2 = "testsounds/logout.wav";
+		PulseAudioSourceDataLine line2;
+		line2 = (PulseAudioSourceDataLine) mixer.getLine(new Line.Info(
+				SourceDataLine.class));
+		line2.setName("Line 2");
+
+		ThreadWriter writer1 = mixer.new ThreadWriter(line1, fileName1);
+		ThreadWriter writer2 = mixer.new ThreadWriter(line2, fileName2);
+		// line2.start();
+		// line1.start();
+
+		Line[] lines = { line1, line2 };
+		mixer.synchronize(lines, true);
+
+		// line2.stop();
+
+		debug("PulseAudioMixer: " + line1.getName() + " and " + line2.getName()
+				+ " synchronized");
+		writer1.start();
+		writer2.start();
+
+		debug("PulseAudioMixer: writers started");
+		line2.start();
+		// line1.stop();
+		// line1.start();
+		debug("PulseAudioMixer: Started a line");
+
+		writer1.join();
+		writer2.join();
+
+		debug("PulseAudioMixer: both lines joined");
+
+		line2.close();
+		debug("PulseAudioMixer: " + line2.getName() + " closed");
+
+		line1.close();
+		debug("PulseAudioMixer: " + line1.getName() + " closed");
+
+		mixer.close();
+		debug("PulseAudioMixer: mixer closed");
+
+	}
+
+	public class ThreadWriter extends Thread {
+
+		private PulseAudioSourceDataLine line;
+		private AudioInputStream audioInputStream;
+
+		public ThreadWriter(PulseAudioSourceDataLine line, String fileName)
+				throws UnsupportedAudioFileException, IOException,
+				LineUnavailableException {
+			this.line = line;
+			File soundFile1 = new File(fileName);
+			audioInputStream = AudioSystem.getAudioInputStream(soundFile1);
+			AudioFormat audioFormat = audioInputStream.getFormat();
+			line.open(audioFormat);
+		}
+
+		@Override
+		public void run() {
+			debug("PulseAudioMixer: ThreadWriter: run(): entering");
+
+			// line.start();
+			// debug("PulseAudioMixer: " + line.getName() + " started");
+
+			byte[] abData = new byte[1000];
+			int bytesRead = 0;
+			try {
+				while (bytesRead >= 0) {
+					bytesRead = audioInputStream.read(abData, 0, abData.length);
+					if (bytesRead > 0) {
+						line.write(abData, 0, bytesRead);
+						// debug("PulseAudioMixer: wrote " + bytesRead + "data
+						// on "
+						// + line.getName());
+					}
+				}
+			} catch (IOException e) {
+				debug("PulseAudioMixer: ThreadWriter: run(): exception doing a read()");
+			}
+
+			debug("PulseAudioMixer: ThreadWriter: run(): leaving");
+
+		}
+	}
+
+	void addSourceLine(PulseAudioLine line) {
+		sourceLines.add(line);
+	}
+
+	void removeSourceLine(PulseAudioLine line) {
+		sourceLines.remove(line);
+	}
+
+	void addTargetLine(PulseAudioLine line) {
+		targetLines.add(line);
+	}
+
+	void removeTargetLine(PulseAudioLine line) {
+		targetLines.remove(line);
+	}
+
+	void refreshSourceAndTargetLines() {
+
+		sourceLineInfos.clear();
+		targetLineInfos.clear();
+
+		sourceLineInfos.addAll(staticSourceLineInfos);
+
+		targetLineInfos.addAll(staticTargetLineInfos);
+
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioMixerInfo.java	Fri Oct 10 16:03:12 2008 -0400
@@ -0,0 +1,62 @@
+/* PulseAudioMixerInfo.java
+   Copyright (C) 2008 Red Hat, Inc.
+
+This file is part of IcedTea.
+
+IcedTea is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License as published by
+the Free Software Foundation, version 2.
+
+IcedTea is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with IcedTea; see the file COPYING.  If not, write to
+the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library.  Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module.  An independent module is a module which is not derived from
+or based on this library.  If you modify this library, you may extend
+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;
+
+import javax.sound.sampled.Mixer;
+
+public class PulseAudioMixerInfo extends Mixer.Info {
+	// singleton
+
+	private static PulseAudioMixerInfo _instance = null;
+
+	protected PulseAudioMixerInfo(String name, String vendor,
+			String description, String version) {
+		super(name, vendor, description, version);
+	}
+
+	// the "getInstance()" method
+	synchronized public static PulseAudioMixerInfo getInfo() {
+		if (_instance == null) {
+			_instance = new PulseAudioMixerInfo("PulseAudio Mixer", "icedtea",
+					"the ear-candy mixer", "0.01");
+		}
+
+		return _instance;
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioMixerProvider.java	Fri Oct 10 16:03:12 2008 -0400
@@ -0,0 +1,63 @@
+/* PulseAudioMixerProvider.java
+   Copyright (C) 2008 Red Hat, Inc.
+
+This file is part of IcedTea.
+
+IcedTea is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License as published by
+the Free Software Foundation, version 2.
+
+IcedTea is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with IcedTea; see the file COPYING.  If not, write to
+the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library.  Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module.  An independent module is a module which is not derived from
+or based on this library.  If you modify this library, you may extend
+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;
+
+import javax.sound.sampled.Mixer;
+import javax.sound.sampled.Mixer.Info;
+
+public class PulseAudioMixerProvider extends
+		javax.sound.sampled.spi.MixerProvider {
+
+	@Override
+	public Mixer getMixer(Info info) {
+		// System.out.println("DEBUG: getMixer called");
+		if (info.equals(PulseAudioMixerInfo.getInfo())) {
+			return PulseAudioMixer.getInstance();
+		} else {
+			throw new IllegalArgumentException("Mixer type not supported");
+		}
+	}
+
+	@Override
+	public Info[] getMixerInfo() {
+		// System.out.println("DEBUG: get mixer info called");
+		Mixer.Info[] m = { PulseAudioMixerInfo.getInfo() };
+		return m;
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioMuteControl.java	Fri Oct 10 16:03:12 2008 -0400
@@ -0,0 +1,78 @@
+/* PulseAudioMuteControl.java
+   Copyright (C) 2008 Red Hat, Inc.
+
+This file is part of IcedTea.
+
+IcedTea is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License as published by
+the Free Software Foundation, version 2.
+
+IcedTea is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with IcedTea; see the file COPYING.  If not, write to
+the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library.  Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module.  An independent module is a module which is not derived from
+or based on this library.  If you modify this library, you may extend
+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;
+
+import javax.sound.sampled.BooleanControl;
+
+class PulseAudioMuteControl extends BooleanControl {
+	
+	private PulseAudioVolumeControl volumeControl;
+	private PulseAudioPlaybackLine line;
+
+	protected PulseAudioMuteControl(PulseAudioPlaybackLine line, PulseAudioVolumeControl volumeControl) {
+		super(BooleanControl.Type.MUTE, false, "Volume muted", "Volume on");
+		this.volumeControl = volumeControl;
+		this.line = line;
+	}
+	
+	
+
+
+
+		public synchronized void setValue(boolean value) {
+			if(!line.isOpen()) {
+				return;
+			}
+
+			if (value == true) {
+				line.setMuted(true);
+				volumeControl.setStreamVolume(0);
+			} else {
+				line.setMuted(false);
+				float newValue = volumeControl.getValue();
+				volumeControl.setStreamVolume(newValue);
+			}
+		}
+
+		public synchronized boolean getValue() {
+			return line.isMuted();
+		}
+
+	}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioPlaybackLine.java	Fri Oct 10 16:03:12 2008 -0400
@@ -0,0 +1,54 @@
+/* PulseAudioClip.java
+   Copyright (C) 2008 Red Hat, Inc.
+
+This file is part of IcedTea.
+
+IcedTea is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License as published by
+the Free Software Foundation, version 2.
+
+IcedTea is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with IcedTea; see the file COPYING.  If not, write to
+the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library.  Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module.  An independent module is a module which is not derived from
+or based on this library.  If you modify this library, you may extend
+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;
+
+interface PulseAudioPlaybackLine {
+
+	byte[] native_setVolume(float value);
+
+	boolean isMuted();
+
+	void setMuted(boolean mute);
+
+	float getVolume();
+
+	void setVolume(float volume);
+
+	boolean isOpen();
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioPort.java	Fri Oct 10 16:03:12 2008 -0400
@@ -0,0 +1,155 @@
+/* PulseAudioClip.java
+   Copyright (C) 2008 Red Hat, Inc.
+
+This file is part of IcedTea.
+
+IcedTea is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License as published by
+the Free Software Foundation, version 2.
+
+IcedTea is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with IcedTea; see the file COPYING.  If not, write to
+the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library.  Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module.  An independent module is a module which is not derived from
+or based on this library.  If you modify this library, you may extend
+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;
+
+import javax.sound.sampled.AudioSystem;
+import javax.sound.sampled.LineEvent;
+import javax.sound.sampled.Port;
+
+public abstract class PulseAudioPort extends PulseAudioLine implements Port,
+		PulseAudioPlaybackLine {
+
+	private String name;
+
+	/*
+	 * Variable used in native code
+	 */
+	@SuppressWarnings("unused")
+	private byte[] contextPointer;
+	@SuppressWarnings("unused")
+	private int channels;
+
+	private EventLoop eventLoop;
+
+	private float volume;
+	private boolean muted;
+
+	private PulseAudioMuteControl muteControl;
+	private PulseAudioVolumeControl volumeControl;
+
+	static {
+		System.loadLibrary("pulse-java");
+	}
+
+	public PulseAudioPort(String name, EventLoop eventLoop) {
+		this.name = name;
+		this.contextPointer = eventLoop.getContextPointer();
+		this.eventLoop = eventLoop;
+		updateVolumeInfo();
+
+		volumeControl = new PulseAudioVolumeControl(this, eventLoop);
+		controls.add(volumeControl);
+		muteControl = new PulseAudioMuteControl(this, volumeControl);
+		controls.add(muteControl);
+		isOpen = true;
+
+		/*
+		 * unlike other lines, Ports must either be open or close
+		 * 
+		 * close = no sound. open = sound
+		 * 
+		 */
+		// FIXME open();
+
+		// System.out.println("Opened Target Port " + name);
+	}
+
+	public abstract byte[] native_setVolume(float newValue);
+
+	public abstract byte[] native_updateVolumeInfo();
+
+	public boolean isMuted() {
+		return muted;
+	}
+
+	public void setMuted(boolean value) {
+		muted = value;
+	}
+
+	public float getVolume() {
+
+		// FIXME need to query system for volume
+		return this.volume;
+	}
+
+	public void setVolume(float value) {
+		this.volume = value;
+
+	}
+
+	public synchronized void updateVolumeInfo() {
+		Operation op;
+		synchronized (eventLoop.threadLock) {
+			op = new Operation(native_updateVolumeInfo());
+		}
+
+		op.waitForCompletion();
+		op.releaseReference();
+	}
+
+	public void update_channels_and_volume(int channels, float volume) {
+		this.channels = channels;
+		this.volume = volume;
+	}
+
+	@Override
+	public void close() {
+
+		native_setVolume((float) 0);
+		isOpen = false;
+		fireLineEvent(new LineEvent(this, LineEvent.Type.CLOSE,
+				AudioSystem.NOT_SPECIFIED));
+	}
+
+	@Override
+	public abstract javax.sound.sampled.Line.Info getLineInfo();
+
+	@Override
+	public void open() {
+
+		native_setVolume(volume);
+		isOpen = true;
+		fireLineEvent(new LineEvent(this, LineEvent.Type.OPEN,
+				AudioSystem.NOT_SPECIFIED));
+	}
+
+	public String getName() {
+		return this.name;
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioSourceDataLine.java	Fri Oct 10 16:03:12 2008 -0400
@@ -0,0 +1,340 @@
+/* PulseAudioSourceDataLine.java
+   Copyright (C) 2008 Red Hat, Inc.
+
+This file is part of IcedTea.
+
+IcedTea is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License as published by
+the Free Software Foundation, version 2.
+
+IcedTea is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with IcedTea; see the file COPYING.  If not, write to
+the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library.  Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module.  An independent module is a module which is not derived from
+or based on this library.  If you modify this library, you may extend
+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;
+
+import java.util.ArrayList;
+
+import javax.sound.sampled.AudioFormat;
+import javax.sound.sampled.DataLine;
+import javax.sound.sampled.AudioPermission;
+import javax.sound.sampled.LineListener;
+import javax.sound.sampled.LineUnavailableException;
+import javax.sound.sampled.SourceDataLine;
+
+public class PulseAudioSourceDataLine extends PulseAudioDataLine implements
+		SourceDataLine, PulseAudioPlaybackLine {
+
+	private PulseAudioMuteControl muteControl;
+	private PulseAudioVolumeControl volumeControl;
+	private boolean muted;
+	private float volume;
+
+	public PulseAudioSourceDataLine(EventLoop eventLoop, AudioFormat[] formats,
+			AudioFormat defaultFormat) {
+
+		this.supportedFormats = formats;
+		this.eventLoop = eventLoop;
+		this.lineListeners = new ArrayList<LineListener>();
+		this.defaultFormat = defaultFormat;
+		this.currentFormat = defaultFormat;
+		this.volume = PulseAudioVolumeControl.MAX_VOLUME;
+	}
+
+	@Override
+	synchronized public void open(AudioFormat format, int bufferSize)
+			throws LineUnavailableException {
+
+		/* check for permmission to play audio */
+		AudioPermission perm = new AudioPermission("play", null);
+		perm.checkGuard(null);
+
+		super.open(format, bufferSize);
+
+		volumeControl = new PulseAudioVolumeControl(this, eventLoop);
+		controls.add(volumeControl);
+		muteControl = new PulseAudioMuteControl(this, volumeControl);
+		controls.add(muteControl);
+
+		PulseAudioMixer parentMixer = PulseAudioMixer.getInstance();
+		parentMixer.addSourceLine(this);
+
+	}
+
+	@Override
+	public void open(AudioFormat format) throws LineUnavailableException {
+		open(format, DEFAULT_BUFFER_SIZE);
+	}
+
+	public byte[] native_setVolume(float value) {
+		synchronized (eventLoop.threadLock) {
+			return stream.native_setVolume(value);
+		}
+	}
+
+	public boolean isMuted() {
+		return muted;
+	}
+
+	public void setMuted(boolean value) {
+		muted = value;
+	}
+
+	public float getVolume() {
+		return this.volume;
+	}
+
+	synchronized public void setVolume(float value) {
+		this.volume = value;
+
+	}
+
+	protected void connectLine(int bufferSize, Stream masterStream)
+			throws LineUnavailableException {
+		StreamBufferAttributes bufferAttributes = new StreamBufferAttributes(
+
+		bufferSize, bufferSize / 4, bufferSize / 8,
+				((bufferSize / 10) > 100 ? bufferSize / 10 : 100), 0);
+
+		if (masterStream != null) {
+			synchronized (eventLoop.threadLock) {
+				stream.connectForPlayback(Stream.DEFAULT_DEVICE,
+						bufferAttributes, masterStream.getStreamPointer());
+			}
+		} else {
+			synchronized (eventLoop.threadLock) {
+				stream.connectForPlayback(Stream.DEFAULT_DEVICE,
+						bufferAttributes, null);
+			}
+		}
+	}
+
+	@Override
+	public int write(byte[] data, int offset, int length) {
+		// can't call write() without open()ing first, but can call write()
+		// without start()ing
+		synchronized (this) {
+			writeInterrupted = false;
+		}
+
+		if (!isOpen) {
+			throw new IllegalStateException("must call open() before write()");
+		}
+
+		int frameSize = currentFormat.getFrameSize();
+		if (length % frameSize != 0) {
+			throw new IllegalArgumentException(
+					"amount of data to write does not represent an integral number of frames");
+		}
+		if (length < 0) {
+			throw new IllegalArgumentException("length is negative");
+		}
+
+		if (length + offset > data.length) {
+			throw new ArrayIndexOutOfBoundsException(length + offset);
+		}
+
+		int position = offset;
+		int remainingLength = length;
+		int availableSize = 0;
+
+		int sizeWritten = 0;
+
+		boolean interrupted = false;
+
+		while (remainingLength != 0) {
+
+			synchronized (eventLoop.threadLock) {
+
+				do {
+					if (writeInterrupted) {
+						return sizeWritten;
+					}
+
+					if (availableSize == -1) {
+						return sizeWritten;
+					}
+					availableSize = stream.getWritableSize();
+
+					if (availableSize == 0) {
+						try {
+							eventLoop.threadLock.wait(100);
+						} catch (InterruptedException e) {
+							// ignore for now
+							interrupted = true;
+						}
+
+					}
+
+				} while (availableSize == 0);
+
+				if (availableSize > remainingLength) {
+					availableSize = remainingLength;
+				}
+
+				// only write entire frames, so round down avialableSize to
+				// a
+				// multiple of frameSize
+				availableSize = (availableSize / frameSize) * frameSize;
+
+				synchronized (this) {
+					if (writeInterrupted) {
+						return sizeWritten;
+					}
+					/* write a little bit of the buffer */
+					stream.write(data, position, availableSize);
+				}
+
+				sizeWritten += availableSize;
+				position += availableSize;
+				remainingLength -= availableSize;
+
+				framesSinceOpen += availableSize / frameSize;
+
+			}
+		}
+
+		// all the data should have been played by now
+		assert (sizeWritten == length);
+
+		if (interrupted) {
+			Thread.currentThread().interrupt();
+		}
+
+		return sizeWritten;
+	}
+
+	public int available() {
+		synchronized (eventLoop.threadLock) {
+			return stream.getWritableSize();
+		}
+	};
+
+	public int getFramePosition() {
+		return (int) framesSinceOpen;
+	}
+
+	public long getLongFramePosition() {
+		return framesSinceOpen;
+	}
+
+	public long getMicrosecondPosition() {
+
+		float frameRate = currentFormat.getFrameRate();
+		float time = framesSinceOpen / frameRate; // seconds
+		long microseconds = (long) (time * 1000);
+		return microseconds;
+	}
+
+	@Override
+	public void drain() {
+		if (!isOpen) {
+			throw new IllegalStateException(
+					"Line must be open before it can be drain()ed");
+
+		}
+
+		synchronized (this) {
+			writeInterrupted = true;
+		}
+
+		do {
+			synchronized (this) {
+				if (!isOpen) {
+					return;
+				}
+				if (getBytesInBuffer() == 0) {
+					return;
+				}
+				if (isStarted || !isOpen) {
+					break;
+				}
+				try {
+					this.wait(100);
+				} catch (InterruptedException e) {
+					return;
+				}
+			}
+		} while (!isStarted);
+
+		Operation operation;
+
+		synchronized (eventLoop.threadLock) {
+			operation = stream.drain();
+		}
+
+		operation.waitForCompletion();
+		operation.releaseReference();
+
+	}
+
+	@Override
+	public void flush() {
+		if (!isOpen) {
+			throw new IllegalStateException(
+					"Line must be open before it can be flush()ed");
+		}
+		synchronized (this) {
+			writeInterrupted = true;
+		}
+
+		Operation operation;
+		synchronized (eventLoop.threadLock) {
+			operation = stream.flush();
+		}
+
+		operation.waitForCompletion();
+		operation.releaseReference();
+
+	}
+
+	@Override
+	synchronized public void close() {
+
+		/* check for permmission to play audio */
+		AudioPermission perm = new AudioPermission("play", null);
+		perm.checkGuard(null);
+
+		if (!isOpen) {
+			throw new IllegalStateException("not open so cant close");
+		}
+
+		writeInterrupted = true;
+
+		PulseAudioMixer parent = PulseAudioMixer.getInstance();
+		parent.removeSourceLine(this);
+
+		super.close();
+	}
+	
+	public javax.sound.sampled.Line.Info getLineInfo() {
+		return new DataLine.Info(SourceDataLine.class, supportedFormats,
+				StreamBufferAttributes.MIN_VALUE,
+				StreamBufferAttributes.MAX_VALUE);
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioSourcePort.java	Fri Oct 10 16:03:12 2008 -0400
@@ -0,0 +1,94 @@
+/* PulseAudioClip.java
+   Copyright (C) 2008 Red Hat, Inc.
+
+This file is part of IcedTea.
+
+IcedTea is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License as published by
+the Free Software Foundation, version 2.
+
+IcedTea is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with IcedTea; see the file COPYING.  If not, write to
+the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library.  Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module.  An independent module is a module which is not derived from
+or based on this library.  If you modify this library, you may extend
+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;
+
+import javax.sound.sampled.AudioPermission;
+import javax.sound.sampled.Port;
+
+public class PulseAudioSourcePort extends PulseAudioPort {
+
+	/* aka mic */
+	
+	static {
+		System.loadLibrary("pulse-java");
+	}
+
+	public PulseAudioSourcePort(String name, EventLoop eventLoop) {
+		super(name, eventLoop);
+	}
+
+	public void open() {
+		
+		/* check for permission to record audio */
+		AudioPermission perm = new AudioPermission("record", null);
+		perm.checkGuard(null);
+		
+		super.open();
+		
+		PulseAudioMixer parent = PulseAudioMixer.getInstance();
+		parent.addSourceLine(this);
+	}
+	
+	public void close() {
+		
+		/* check for permission to record audio */
+		AudioPermission perm = new AudioPermission("record", null);
+		perm.checkGuard(null);
+		
+		if (!isOpen) {
+			throw new IllegalStateException("Port is not open; so cant close");
+		}
+		
+		PulseAudioMixer parent = PulseAudioMixer.getInstance();
+		parent.removeSourceLine(this);
+		
+		super.close();		
+	}
+	
+	public native byte[] native_setVolume(float newValue);
+
+	public synchronized native byte[] native_updateVolumeInfo();
+
+	@Override
+	public javax.sound.sampled.Line.Info getLineInfo() {
+		return new Port.Info(Port.class, getName(), false);
+	}
+	
+	
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioTargetDataLine.java	Fri Oct 10 16:03:12 2008 -0400
@@ -0,0 +1,373 @@
+/* PulseAudioTargetDataLine.java
+   Copyright (C) 2008 Red Hat, Inc.
+
+This file is part of IcedTea.
+
+IcedTea is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License as published by
+the Free Software Foundation, version 2.
+
+IcedTea is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with IcedTea; see the file COPYING.  If not, write to
+the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library.  Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module.  An independent module is a module which is not derived from
+or based on this library.  If you modify this library, you may extend
+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;
+
+import javax.sound.sampled.AudioFormat;
+import javax.sound.sampled.DataLine;
+import javax.sound.sampled.AudioPermission;
+import javax.sound.sampled.LineEvent;
+import javax.sound.sampled.LineUnavailableException;
+import javax.sound.sampled.TargetDataLine;
+
+public class PulseAudioTargetDataLine extends PulseAudioDataLine implements
+		TargetDataLine {
+
+	/*
+	 * This contains the data from the PulseAudio buffer that has since been
+	 * dropped. If 20 bytes of a fragment of size 200 are read, the other 180
+	 * are dumped in this
+	 */
+	byte[] fragmentBuffer;
+
+	/*
+	 * these are set to true only by the respective functions (flush(), drain())
+	 * set to false only by read()
+	 */
+	boolean flushed = false;
+	boolean drained = false;
+
+	public PulseAudioTargetDataLine(EventLoop eventLoop, AudioFormat[] formats,
+			AudioFormat defaultFormat) {
+		supportedFormats = formats;
+		this.eventLoop = eventLoop;
+		this.defaultFormat = defaultFormat;
+		this.currentFormat = defaultFormat;
+
+	}
+
+	@Override
+	synchronized public void close() {
+		/* check for permission to record audio */
+		AudioPermission perm = new AudioPermission("record", null);
+		perm.checkGuard(null);
+
+		if (!isOpen) {
+			throw new IllegalStateException(
+					"Line cant be closed if it isnt open");
+		}
+
+		PulseAudioMixer parentMixer = PulseAudioMixer.getInstance();
+		parentMixer.removeTargetLine(this);
+
+		super.close();
+	}
+
+	@Override
+	synchronized public void open(AudioFormat format, int bufferSize)
+			throws LineUnavailableException {
+		/* check for permission to record audio */
+		AudioPermission perm = new AudioPermission("record", null);
+		perm.checkGuard(null);
+
+		if (isOpen) {
+			throw new IllegalStateException("already open");
+		}
+		super.open(format, bufferSize);
+
+		/* initialize all the member variables */
+		framesSinceOpen = 0;
+		fragmentBuffer = null;
+		flushed = false;
+		drained = false;
+
+		/* add this open line to the mixer */
+		PulseAudioMixer parentMixer = PulseAudioMixer.getInstance();
+		parentMixer.addTargetLine(this);
+	}
+
+	@Override
+	synchronized public void open(AudioFormat format)
+			throws LineUnavailableException {
+		open(format, DEFAULT_BUFFER_SIZE);
+	}
+
+	protected void connectLine(int bufferSize, Stream masterStream)
+			throws LineUnavailableException {
+		int fragmentSize = bufferSize / 10 > 500 ? bufferSize / 10 : 500;
+		StreamBufferAttributes bufferAttributes = new StreamBufferAttributes(
+				bufferSize, 0, 0, 0, fragmentSize);
+		synchronized (eventLoop.threadLock) {
+			stream.connectForRecording(Stream.DEFAULT_DEVICE, bufferAttributes);
+		}
+	}
+
+	@Override
+	public int read(byte[] data, int offset, int length) {
+
+		/* check state and inputs */
+
+		if (!isOpen) {
+			throw new IllegalStateException("must call open() before read()");
+		}
+
+		int frameSize = currentFormat.getFrameSize();
+
+		if (length % frameSize != 0) {
+			throw new IllegalArgumentException(
+					"amount of data to read does not represent an integral number of frames");
+		}
+
+		if (length < 0) {
+			throw new IllegalArgumentException("length is negative");
+		}
+
+		if (length + offset > data.length) {
+			throw new ArrayIndexOutOfBoundsException("index: "
+					+ (length + offset) + " array size: " + data.length);
+		}
+
+		/* everything ok */
+
+		int position = offset;
+		int remainingLength = length;
+		int sizeRead = 0;
+
+		/* bytes read on each iteration of loop */
+		int bytesRead;
+
+		flushed = false;
+		drained = false;
+
+		/*
+		 * to read, we first take stuff from the fragmentBuffer
+		 */
+
+		/* on first read() of the line, fragmentBuffer is null */
+		if (fragmentBuffer != null) {
+			synchronized (this) {
+
+				boolean fragmentBufferSmaller = fragmentBuffer.length < length;
+				int smallerBufferLength = Math.min(fragmentBuffer.length,
+						length);
+				System.arraycopy(fragmentBuffer, 0, data, position,
+						smallerBufferLength);
+				framesSinceOpen += smallerBufferLength
+						/ currentFormat.getFrameSize();
+
+				if (!fragmentBufferSmaller) {
+					/*
+					 * if fragment was larger, then we already have all the data
+					 * we need. clean up the buffer before returning. Make a new
+					 * fragmentBuffer from the remaining bytes
+					 */
+					int remainingBytesInFragment = (fragmentBuffer.length - length);
+					byte[] newFragmentBuffer = new byte[remainingBytesInFragment];
+					System.arraycopy(fragmentBuffer, length, newFragmentBuffer,
+							0, newFragmentBuffer.length);
+					fragmentBuffer = newFragmentBuffer;
+					return length;
+				}
+
+				/* done with fragment buffer, remove it */
+				bytesRead = smallerBufferLength;
+				sizeRead += bytesRead;
+				position += bytesRead;
+				remainingLength -= bytesRead;
+				fragmentBuffer = null;
+
+			}
+		}
+
+		/*
+		 * if we need to read more data, then we read from PulseAudio's buffer
+		 */
+		while (remainingLength != 0) {
+			synchronized (this) {
+
+				if (!isOpen || !isStarted) {
+					return sizeRead;
+				}
+
+				if (flushed) {
+					flushed = false;
+					return sizeRead;
+				}
+
+				if (drained) {
+					drained = false;
+					return sizeRead;
+				}
+
+				byte[] currentFragment;
+				synchronized (eventLoop.threadLock) {
+
+					/* read a fragment, and drop it from the server */
+					currentFragment = stream.peek();
+
+					stream.drop();
+					if (currentFragment == null) {
+						System.out
+								.println("DEBUG: PulseAudioTargetDataLine:read(): error in stream.peek()");
+						continue;
+					}
+
+					bytesRead = Math.min(currentFragment.length,
+							remainingLength);
+
+					/*
+					 * we read more than we required, save the rest of the data
+					 * in the fragmentBuffer
+					 */
+					if (bytesRead < currentFragment.length) {
+						/* allocate a buffer to store unsaved data */
+						fragmentBuffer = new byte[currentFragment.length
+								- bytesRead];
+
+						/* copy over the unsaved data */
+						System.arraycopy(currentFragment, bytesRead,
+								fragmentBuffer, 0, currentFragment.length
+										- bytesRead);
+					}
+
+					System.arraycopy(currentFragment, 0, data, position,
+							bytesRead);
+
+					sizeRead += bytesRead;
+					position += bytesRead;
+					remainingLength -= bytesRead;
+					framesSinceOpen += bytesRead / currentFormat.getFrameSize();
+				}
+			}
+		}
+
+		// all the data should have been played by now
+		assert (sizeRead == length);
+
+		return sizeRead;
+
+	}
+
+	@Override
+	public void drain() {
+
+		if (!isOpen) {
+			throw new IllegalStateException("must call open() before drain()");
+		}
+
+		synchronized (this) {
+			drained = true;
+		}
+
+		// blocks when there is data on the line
+		// http://www.jsresources.org/faq_audio.html#stop_drain_tdl
+		while (true) {
+			synchronized (this) {
+				if (!isStarted || !isOpen) {
+					break;
+				}
+			}
+			try {
+				Thread.sleep(100);
+			} catch (InterruptedException e) {
+				// do nothing
+			}
+		}
+
+	}
+
+	@Override
+	public void flush() {
+		if (!isOpen) {
+			throw new IllegalStateException("Line must be open");
+		}
+
+		/* flush the buffer on pulseaudio's side */
+		Operation operation;
+		synchronized (eventLoop.threadLock) {
+			operation = stream.flush();
+		}
+		operation.waitForCompletion();
+		operation.releaseReference();
+
+		synchronized (this) {
+			flushed = true;
+			/* flush the partial fragment we stored */
+			fragmentBuffer = null;
+		}
+
+	}
+
+	public int available() {
+		if (!isOpen) {
+			throw new IllegalStateException("Line must be open");
+		}
+
+		synchronized (eventLoop.threadLock) {
+			return stream.getReableSize();
+		}
+	}
+
+	public int getFramePosition() {
+		return (int) framesSinceOpen;
+	}
+
+	public long getLongFramePosition() {
+		return framesSinceOpen;
+	}
+
+	public long getMicrosecondPosition() {
+		return (long) (framesSinceOpen / currentFormat.getFrameRate());
+	}
+
+	/*
+	 * A TargetData starts when we ask it to and continues playing until we ask
+	 * it to stop. There are no buffer underruns/overflows or anything so we
+	 * will just fire the LineEvents manually
+	 */
+
+	@Override
+	synchronized public void start() {
+		super.start();
+
+		fireLineEvent(new LineEvent(this, LineEvent.Type.START, framesSinceOpen));
+	}
+
+	@Override
+	synchronized public void stop() {
+		super.stop();
+
+		fireLineEvent(new LineEvent(this, LineEvent.Type.STOP, framesSinceOpen));
+	}
+	
+	public javax.sound.sampled.Line.Info getLineInfo() {
+		return new DataLine.Info(TargetDataLine.class, supportedFormats,
+				StreamBufferAttributes.MIN_VALUE,
+				StreamBufferAttributes.MAX_VALUE);
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioTargetPort.java	Fri Oct 10 16:03:12 2008 -0400
@@ -0,0 +1,94 @@
+/* PulseAudioClip.java
+   Copyright (C) 2008 Red Hat, Inc.
+
+This file is part of IcedTea.
+
+IcedTea is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License as published by
+the Free Software Foundation, version 2.
+
+IcedTea is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with IcedTea; see the file COPYING.  If not, write to
+the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library.  Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module.  An independent module is a module which is not derived from
+or based on this library.  If you modify this library, you may extend
+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;
+
+import javax.sound.sampled.AudioPermission;
+import javax.sound.sampled.Port;
+
+public class PulseAudioTargetPort extends PulseAudioPort {
+
+	/* aka speaker */
+
+	static {
+		System.loadLibrary("pulse-java");
+	}
+
+	public PulseAudioTargetPort(String name, EventLoop eventLoop) {
+
+		super(name, eventLoop);
+
+	}
+
+	public void open() {
+
+		/* check for permission to play audio */
+		AudioPermission perm = new AudioPermission("play", null);
+		perm.checkGuard(null);
+
+		super.open();
+
+		PulseAudioMixer parent = PulseAudioMixer.getInstance();
+		parent.addTargetLine(this);
+	}
+
+	public void close() {
+
+		/* check for permission to play audio */
+		AudioPermission perm = new AudioPermission("play", null);
+		perm.checkGuard(null);
+		
+		if (!isOpen) {
+			throw new IllegalStateException("not open, so cant close Port");
+		}
+		
+		PulseAudioMixer parent = PulseAudioMixer.getInstance();
+		parent.removeTargetLine(this);
+
+		super.close();
+	}
+
+	public native byte[] native_setVolume(float newValue);
+
+	public synchronized native byte[] native_updateVolumeInfo();
+
+	@Override
+	public javax.sound.sampled.Line.Info getLineInfo() {
+		return new Port.Info(Port.class, getName(), false);
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioVolumeControl.java	Fri Oct 10 16:03:12 2008 -0400
@@ -0,0 +1,90 @@
+/* PulseAudioVolumeControl.java
+   Copyright (C) 2008 Red Hat, Inc.
+
+This file is part of IcedTea.
+
+IcedTea is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License as published by
+the Free Software Foundation, version 2.
+
+IcedTea is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with IcedTea; see the file COPYING.  If not, write to
+the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library.  Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module.  An independent module is a module which is not derived from
+or based on this library.  If you modify this library, you may extend
+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;
+
+import javax.sound.sampled.FloatControl;
+
+class PulseAudioVolumeControl extends FloatControl {
+
+	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",
+				"Default Volume", "Full Volume");
+		this.line = line;
+		this.eventLoop = eventLoop;
+	}
+
+	private EventLoop eventLoop;
+	private PulseAudioPlaybackLine line;
+
+	public synchronized void setValue(float newValue) {
+		if (newValue > MAX_VOLUME || newValue < MIN_VOLUME) {
+			throw new IllegalArgumentException("invalid value");
+		}
+
+		if (!line.isOpen()) {
+			return;
+		}
+
+		if (!line.isMuted()) {
+			setStreamVolume(newValue);
+		}
+
+		line.setVolume(newValue);
+	}
+
+	protected synchronized void setStreamVolume(float newValue) {
+		Operation op;
+		synchronized (eventLoop.threadLock) {
+			op = new Operation(line.native_setVolume(newValue));
+		}
+
+		op.waitForCompletion();
+		op.releaseReference();
+
+	}
+
+	public synchronized float getValue() {
+		return line.getVolume();
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pulseaudio/src/java/org/classpath/icedtea/pulseaudio/Stream.java	Fri Oct 10 16:03:12 2008 -0400
@@ -0,0 +1,777 @@
+/* PulseAudioClip.java
+   Copyright (C) 2008 Red Hat, Inc.
+
+This file is part of IcedTea.
+
+IcedTea is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License as published by
+the Free Software Foundation, version 2.
+
+IcedTea is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with IcedTea; see the file COPYING.  If not, write to
+the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library.  Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module.  An independent module is a module which is not derived from
+or based on this library.  If you modify this library, you may extend
+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;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import javax.sound.sampled.LineUnavailableException;
+
+/**
+ * 
+ * This class encapsulates a pa_stream object and provides easier access to the
+ * native functions
+ * 
+ */
+public class Stream {
+
+	public interface StateListener {
+		public void update();
+	}
+
+	public interface CorkListener {
+		public void update();
+	}
+
+	public interface WriteListener {
+		public void update();
+	}
+
+	public interface ReadListener {
+		public void update();
+	}
+
+	public interface OverflowListener {
+		public void update();
+	}
+
+	public interface UnderflowListener {
+		public void update();
+	}
+
+	public interface PlaybackStartedListener {
+		public void update();
+	}
+
+	public interface LatencyUpdateListener {
+		public void update();
+	}
+
+	public interface MovedListener {
+		public void update();
+	}
+
+	public interface UpdateTimingInfoListener {
+		public void update();
+	}
+
+	public interface SuspendedListener {
+		public void update();
+	}
+
+	public static enum State {
+		UNCONNECTED, CREATING, READY, FAILED, TERMINATED,
+	}
+
+	public static enum Format {
+		PA_SAMPLE_U8, PA_SAMPLE_ULAW, PA_SAMPLE_ALAW, PA_SAMPLE_S16LE, PA_SAMPLE_S16BE, PA_SAMPLE_FLOAT32LE, PA_SAMPLE_FLOAT32BE, PA_SAMPLE_S32LE, PA_SAMPLE_S32BE
+	}
+
+	public static final String DEFAULT_DEVICE = null;
+
+	@SuppressWarnings("unused")
+	private byte[] streamPointer;
+
+	static {
+		System.loadLibrary("pulse-java");
+	}
+
+	private Format format;
+
+	private List<StateListener> stateListeners;
+	private List<WriteListener> writeListeners;
+	private List<ReadListener> readListeners;
+	private List<OverflowListener> overflowListeners;
+	private List<UnderflowListener> underflowListeners;
+	private List<PlaybackStartedListener> playbackStartedListeners;
+	private List<LatencyUpdateListener> latencyUpdateListeners;
+	private List<MovedListener> movedListeners;
+	private List<SuspendedListener> suspendedListeners;
+	private List<CorkListener> corkListeners;
+
+	private native void native_pa_stream_new(byte[] contextPointer,
+			String name, String format, int sampleRate, int channels);
+
+	private native void native_pa_stream_unref();
+
+	private native int native_pa_stream_get_state();
+
+	private native byte[] native_pa_stream_get_context();
+
+	private native int native_pa_stream_get_index();
+
+	private native int native_pa_stream_get_device_index();
+
+	private native String native_pa_stream_get_device_name();
+
+	private native int native_pa_stream_is_suspended();
+
+	private native int native_pa_stream_connect_playback(String name,
+			int bufferMaxLength, int bufferTargetLength,
+			int bufferPreBuffering, int bufferMinimumRequest,
+			int bufferFragmentSize, int flags, byte[] volumePointer,
+			byte[] sync_streamPointer);
+
+	private native int native_pa_stream_connect_record(String name,
+			int bufferMaxLength, int bufferTargetLength,
+			int bufferPreBuffering, int bufferMinimumRequest,
+			int bufferFragmentSize, int flags, byte[] volumePointer,
+			byte[] sync_streamPointer);
+
+	private native int native_pa_stream_disconnect();
+
+	private native int native_pa_stream_write(byte[] data, int offset,
+			int length);
+
+	private native byte[] native_pa_stream_peek();
+
+	private native int native_pa_stream_drop();
+
+	private native int native_pa_stream_writable_size();
+
+	private native int native_pa_stream_readable_size();
+
+	private native byte[] native_pa_stream_drain();
+
+	private native byte[] native_pa_stream_updateTimingInfo();
+
+	public native int bytesInBuffer();
+
+	/*
+	 * pa_operation pa_stream_update_timing_info (pa_stream *p,
+	 * pa_stream_success_cb_t cb, void *userdata) Request a timing info
+	 * structure update for a stream.
+	 */
+
+	private native int native_pa_stream_is_corked();
+
+	private native byte[] native_pa_stream_cork(int b);
+
+	private native byte[] native_pa_stream_flush();
+
+	/*
+	 * pa_operation pa_stream_prebuf (pa_stream *s, pa_stream_success_cb_t cb,
+	 * void *userdata) Reenable prebuffering as specified in the pa_buffer_attr
+	 * structure.
+	 */
+
+	private native byte[] native_pa_stream_trigger();
+
+	/* returns an operationPointer */
+	private native byte[] native_pa_stream_set_name(String name);
+
+	/* Return the current playback/recording time */
+	private native long native_pa_stream_get_time();
+
+	/* Return the total stream latency */
+	private native long native_pa_stream_get_latency();
+
+	/*
+	 * const pa_timing_info * pa_stream_get_timing_info (pa_stream *s) Return
+	 * the latest raw timing data structure.
+	 */
+
+	native StreamSampleSpecification native_pa_stream_get_sample_spec();
+
+	/*
+	 * const pa_channel_map * pa_stream_get_channel_map (pa_stream *s) Return a
+	 * pointer to the stream's channel map. const
+	 * 
+	 */
+	native StreamBufferAttributes native_pa_stream_get_buffer_attr();
+
+	native byte[] native_pa_stream_set_buffer_attr(StreamBufferAttributes info);
+
+	private native byte[] native_pa_stream_update_sample_rate(int rate);
+
+	public native byte[] native_setVolume(float newValue);
+
+	/*
+	 * pa_operation pa_stream_proplist_update (pa_stream *s, pa_update_mode_t
+	 * mode, pa_proplist *p, pa_stream_success_cb_t cb, void *userdata) Update
+	 * the property list of the sink input/source output of this stream, adding
+	 * new entries. pa_operation * pa_stream_proplist_remove (pa_stream *s,
+	 * const char *const keys[], pa_stream_success_cb_t cb, void *userdata)
+	 * Update the property list of the sink input/source output of this stream,
+	 * remove entries. int pa_stream_set_monitor_stream (pa_stream *s, uint32_t
+	 * sink_input_idx) For record streams connected to a monitor source: monitor
+	 * only a very specific sink input of the sink. uint32_t
+	 * pa_stream_get_monitor_stream (pa_stream *s) Return what has been set with
+	 * pa_stream_set_monitor_stream() ebfore.
+	 */
+
+	public Stream(byte[] contextPointer, String name, Format format,
+			int sampleRate, int channels) {
+		// System.out.println("format: " + format.toString());
+
+		stateListeners = new LinkedList<StateListener>();
+		writeListeners = new LinkedList<WriteListener>();
+		readListeners = new LinkedList<ReadListener>();
+		overflowListeners = new LinkedList<OverflowListener>();
+		underflowListeners = new LinkedList<UnderflowListener>();
+		playbackStartedListeners = new LinkedList<PlaybackStartedListener>();
+		latencyUpdateListeners = new LinkedList<LatencyUpdateListener>();
+		movedListeners = new LinkedList<MovedListener>();
+		suspendedListeners = new LinkedList<SuspendedListener>();
+		corkListeners = new LinkedList<CorkListener>();
+		this.format = format;
+
+		StreamSampleSpecification spec = new StreamSampleSpecification(format,
+				sampleRate, channels);
+
+		native_pa_stream_new(contextPointer, name, spec.getFormat().toString(),
+				spec.getRate(), spec.getChannels());
+	}
+
+	public void addStateListener(StateListener listener) {
+		synchronized (stateListeners) {
+			stateListeners.add(listener);
+		}
+	}
+
+	public void removeStateListener(StateListener listener) {
+		synchronized (stateListeners) {
+			stateListeners.remove(listener);
+		}
+
+	}
+
+	public void addWriteListener(WriteListener listener) {
+		synchronized (writeListeners) {
+			writeListeners.add(listener);
+		}
+	}
+
+	public void removeWriteListener(WriteListener listener) {
+		synchronized (writeListeners) {
+			writeListeners.remove(listener);
+		}
+	}
+
+	public void addReadListener(ReadListener listener) {
+		synchronized (readListeners) {
+			readListeners.add(listener);
+		}
+	}
+
+	public void removeReadListener(ReadListener listener) {
+		synchronized (readListeners) {
+			readListeners.remove(listener);
+		}
+	}
+
+	public void addOverflowListener(OverflowListener listener) {
+		synchronized (overflowListeners) {
+			overflowListeners.add(listener);
+		}
+	}
+
+	public void removeOverflowListener(OverflowListener listener) {
+		synchronized (overflowListeners) {
+			overflowListeners.remove(listener);
+		}
+	}
+
+	public void addUnderflowListener(UnderflowListener listener) {
+		synchronized (underflowListeners) {
+			underflowListeners.add(listener);
+		}
+	}
+
+	public void removeUnderflowListener(UnderflowListener listener) {
+		synchronized (underflowListeners) {
+			underflowListeners.remove(listener);
+		}
+	}
+
+	public void addCorkListener(CorkListener listener) {
+		synchronized (corkListeners) {
+			corkListeners.add(listener);
+		}
+	}
+
+	public void removeCorkListener(CorkListener listener) {
+		synchronized (corkListeners) {
+			corkListeners.remove(listener);
+		}
+	}
+
+	public void addPlaybackStartedListener(PlaybackStartedListener listener) {
+		synchronized (playbackStartedListeners) {
+			playbackStartedListeners.add(listener);
+		}
+	}
+
+	public void removePlaybackStartedListener(PlaybackStartedListener listener) {
+		synchronized (playbackStartedListeners) {
+			playbackStartedListeners.remove(listener);
+		}
+	}
+
+	public void addLatencyUpdateListener(LatencyUpdateListener listener) {
+		synchronized (latencyUpdateListeners) {
+			latencyUpdateListeners.add(listener);
+		}
+	}
+
+	public void removeLatencyUpdateListener(LatencyUpdateListener listener) {
+		synchronized (playbackStartedListeners) {
+			latencyUpdateListeners.remove(listener);
+		}
+	}
+
+	public void addMovedListener(MovedListener listener) {
+		synchronized (movedListeners) {
+			movedListeners.add(listener);
+		}
+	}
+
+	public void removeMovedListener(MovedListener listener) {
+		synchronized (movedListeners) {
+			movedListeners.remove(listener);
+		}
+	}
+
+	public void addSuspendedListener(SuspendedListener listener) {
+		synchronized (suspendedListeners) {
+			suspendedListeners.add(listener);
+		}
+	}
+
+	public void removeSuspendedListener(SuspendedListener listener) {
+		synchronized (suspendedListeners) {
+			suspendedListeners.remove(listener);
+		}
+	}
+		
+
+	public Stream.State getState() {
+		int state = native_pa_stream_get_state();
+		switch (state) {
+		case 0:
+			return State.UNCONNECTED;
+		case 1:
+			return State.CREATING;
+		case 2:
+			return State.READY;
+		case 3:
+			return State.FAILED;
+		case 4:
+			return State.TERMINATED;
+		default:
+			throw new IllegalStateException("invalid stream state");
+		}
+
+	}
+
+	public byte[] getContextPointer() {
+		return native_pa_stream_get_context();
+	}
+
+	public int getSinkInputIndex() {
+		return native_pa_stream_get_index();
+	}
+
+	/**
+	 * 
+	 * @return the index of the sink or source this stream is connected to in
+	 *         the server
+	 */
+	public int getDeviceIndex() {
+		return native_pa_stream_get_device_index();
+	}
+
+	/**
+	 * 
+	 * @return the name of the sink or source this stream is connected to in the
+	 *         server
+	 */
+	public String getDeviceName() {
+		return native_pa_stream_get_device_name();
+	}
+
+	/**
+	 * if the sink or source this stream is connected to has been suspended.
+	 * 
+	 * @return
+	 */
+	public boolean isSuspended() {
+		return (native_pa_stream_is_suspended() != 0);
+	}
+
+	/**
+	 * Connect the stream to a sink
+	 * 
+	 * @param deviceName
+	 *            the device to connect to. use
+	 *            <code>null</code for the default device
+	 * @throws LineUnavailableException
+	 */
+	public void connectForPlayback(String deviceName,
+			StreamBufferAttributes bufferAttributes, byte[] syncStreamPointer)
+			throws LineUnavailableException {
+
+		int returnValue = native_pa_stream_connect_playback(deviceName,
+				bufferAttributes.getMaxLength(), bufferAttributes
+						.getTargetLength(), bufferAttributes.getPreBuffering(),
+				bufferAttributes.getMinimumRequest(), bufferAttributes
+						.getFragmentSize(), 0, null, syncStreamPointer);
+		if (returnValue < 0) {
+			throw new LineUnavailableException(
+					"Unable To connect a line for playback");
+		}
+	}
+
+	/**
+	 * Connect the stream to a source.
+	 * 
+	 * @throws LineUnavailableException
+	 * 
+	 */
+	public void connectForRecording(String deviceName,
+			StreamBufferAttributes bufferAttributes)
+			throws LineUnavailableException {
+
+		int returnValue = native_pa_stream_connect_record(deviceName,
+				bufferAttributes.getMaxLength(), bufferAttributes
+						.getTargetLength(), bufferAttributes.getPreBuffering(),
+				bufferAttributes.getMinimumRequest(), bufferAttributes
+						.getFragmentSize(), 0, null, null);
+		if (returnValue < 0) {
+			throw new LineUnavailableException(
+					"Unable to connect line for recording");
+		}
+	}
+
+	/**
+	 * Disconnect a stream from a source/sink.
+	 */
+	public void disconnect() {
+		int returnValue = native_pa_stream_disconnect();
+		assert (returnValue == 0);
+	}
+
+	/**
+	 * Write data to the server
+	 * 
+	 * @param data
+	 * @param length
+	 * @return
+	 */
+	public int write(byte[] data, int offset, int length) {
+		return native_pa_stream_write(data, offset, length);
+	}
+
+	/**
+	 * Read the next fragment from the buffer (for recording).
+	 * 
+	 * 
+	 * @param data
+	 */
+	public byte[] peek() {
+		return native_pa_stream_peek();
+	}
+
+	/**
+	 * 
+	 * Remove the current fragment on record streams.
+	 */
+	public void drop() {
+		native_pa_stream_drop();
+	}
+
+	/**
+	 * Return the number of bytes that may be written using write().
+	 * 
+	 * @return
+	 */
+	public int getWritableSize() {
+		return native_pa_stream_writable_size();
+	}
+
+	/**
+	 * Return the number of bytes that may be read using peek().
+	 * 
+	 * @return
+	 */
+	public int getReableSize() {
+		return native_pa_stream_readable_size();
+	}
+
+	/**
+	 * Drain a playback stream
+	 * 
+	 * @return
+	 */
+	public Operation drain() {
+		Operation drainOperation = new Operation(native_pa_stream_drain());
+		return drainOperation;
+	}
+
+	public Operation updateTimingInfo() {
+		Operation updateOperation = new Operation(
+				native_pa_stream_updateTimingInfo());
+		return updateOperation;
+	}
+
+	/**
+	 * this function is called whenever the state changes
+	 */
+	@SuppressWarnings("unused")
+	private void stateCallback() {
+		synchronized (stateListeners) {
+			for (StateListener listener : stateListeners) {
+				listener.update();
+			}
+		}
+	}
+
+	@SuppressWarnings("unused")
+	private void writeCallback() {
+		synchronized (writeListeners) {
+			for (WriteListener listener : writeListeners) {
+				listener.update();
+			}
+		}
+	}
+
+	@SuppressWarnings("unused")
+	private void readCallback() {
+		synchronized (readListeners) {
+			for (ReadListener listener : readListeners) {
+				listener.update();
+			}
+		}
+	}
+
+	@SuppressWarnings("unused")
+	private void overflowCallback() {
+		System.out.println("overflowCallback called");
+		synchronized (overflowListeners) {
+			for (OverflowListener listener : overflowListeners) {
+				listener.update();
+			}
+		}
+	}
+
+	@SuppressWarnings("unused")
+	private void underflowCallback() {
+		synchronized (underflowListeners) {
+			for (UnderflowListener listener : underflowListeners) {
+				listener.update();
+			}
+		}
+	}
+
+	/**
+	 * callback function that is called when a the server starts playback after
+	 * an underrun or on initial startup
+	 */
+	@SuppressWarnings("unused")
+	private void playbackStartedCallback() {
+		synchronized (playbackStartedListeners) {
+			for (PlaybackStartedListener listener : playbackStartedListeners) {
+				listener.update();
+			}
+		}
+	}
+
+	/**
+	 * called whenever a latency information update happens
+	 */
+	@SuppressWarnings("unused")
+	private void latencyUpdateCallback() {
+		synchronized (latencyUpdateListeners) {
+			for (LatencyUpdateListener listener : latencyUpdateListeners) {
+				listener.update();
+			}
+		}
+	}
+
+	/**
+	 * whenever the stream is moved to a different sink/source
+	 */
+	@SuppressWarnings("unused")
+	private void movedCallback() {
+		synchronized (movedListeners) {
+			for (MovedListener listener : movedListeners) {
+				listener.update();
+			}
+		}
+	}
+
+	@SuppressWarnings("unused")
+	private void corkCallback() {
+		synchronized (corkListeners) {
+			for (CorkListener listener : corkListeners) {
+				listener.update();
+			}
+		}
+	}
+
+	/**
+	 * whenever the sink/source this stream is connected to is suspended or
+	 * resumed
+	 */
+	@SuppressWarnings("unused")
+	private void suspendedCallback() {
+		synchronized (suspendedListeners) {
+			for (SuspendedListener listener : suspendedListeners) {
+				listener.update();
+			}
+		}
+	}
+
+	public boolean isCorked() {
+		int corked = native_pa_stream_is_corked();
+		if (corked < 0) {
+			throw new IllegalStateException("Unable to determine state");
+		}
+		return corked == 0 ? false : true;
+	}
+
+	/**
+	 * Pause (or resume) playback of this stream temporarily.
+	 * 
+	 * @param cork
+	 * @return
+	 */
+	public Operation cork(boolean cork) {
+		int yes = cork ? 1 : 0;
+		Operation corkOperation = new Operation(native_pa_stream_cork(yes));
+		return corkOperation;
+	}
+
+	public Operation cork() {
+		return cork(true);
+	}
+
+	public Operation unCork() {
+		return cork(false);
+	}
+
+	/**
+	 * Flush the playback buffer of this stream.
+	 * 
+	 * @return
+	 */
+	public Operation flush() {
+		Operation flushOperation = new Operation(native_pa_stream_flush());
+		return flushOperation;
+	}
+
+	/*
+	 * Operation pa_stream_prebuf (pa_stream *s, pa_stream_success_cb_t cb, void
+	 * *userdata)
+	 * 
+	 * Reenable prebuffering as specified in the pa_buffer_attr structure.
+	 */
+
+	/**
+	 * Request immediate start of playback on this stream.
+	 */
+	public Operation triggerStart() {
+		Operation triggerOperation = new Operation(native_pa_stream_trigger());
+		return triggerOperation;
+	}
+
+	/**
+	 * set the stream's name
+	 * 
+	 * @param name
+	 * @return
+	 */
+	public Operation setName(String name) {
+		Operation setNameOperation = new Operation(
+				native_pa_stream_set_name(name));
+		return setNameOperation;
+	}
+
+	public long getTime() {
+		return native_pa_stream_get_time();
+	}
+
+	/**
+	 * @returns the total stream latency in microseconds
+	 */
+	public long getLatency() {
+		return native_pa_stream_get_latency();
+	}
+
+	/*
+	 * const pa_timing_info * pa_stream_get_timing_info (pa_stream *s) Return
+	 * the latest raw timing data structure.
+	 * 
+	 */
+
+	public Format getFormat() {
+		return format;
+	}
+
+	/*
+	 * const pa_channel_map * pa_stream_get_channel_map (pa_stream *s) Return a
+	 * pointer to the stream's channel map.
+	 */
+
+	public StreamBufferAttributes getBufferAttributes() {
+		return native_pa_stream_get_buffer_attr();
+	}
+
+	public Operation setBufferAtrributes(StreamBufferAttributes attr) {
+		return new Operation(native_pa_stream_set_buffer_attr(attr));
+	}
+
+	/**
+	 * Change the stream sampling rate during playback.
+	 * 
+	 */
+
+	Operation updateSampleRate(int rate) {
+		return new Operation(native_pa_stream_update_sample_rate(rate));
+
+	}
+
+	public byte[] getStreamPointer() {
+		return streamPointer;
+	}
+
+	public void free() {
+		native_pa_stream_unref();
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pulseaudio/src/java/org/classpath/icedtea/pulseaudio/StreamBufferAttributes.java	Fri Oct 10 16:03:12 2008 -0400
@@ -0,0 +1,84 @@
+/* PulseAudioClip.java
+   Copyright (C) 2008 Red Hat, Inc.
+
+This file is part of IcedTea.
+
+IcedTea is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License as published by
+the Free Software Foundation, version 2.
+
+IcedTea is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with IcedTea; see the file COPYING.  If not, write to
+the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library.  Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module.  An independent module is a module which is not derived from
+or based on this library.  If you modify this library, you may extend
+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;
+
+public class StreamBufferAttributes {
+
+	public static final int SANE_DEFAULT = 50000;
+
+	// set these to proper values
+	// integer.max_value will crash the program!
+	public static final int MAX_VALUE = 1000000;
+	public static final int MIN_VALUE = 0;
+
+	private int maxLength;
+	private int targetLength;
+	private int preBuffering;
+	private int minimumRequest;
+	private int fragmentSize;
+
+	public StreamBufferAttributes(int maxLength, int targetLength,
+			int preBuffering, int minimumRequest, int fragmentSize) {
+		this.maxLength = maxLength;
+		this.targetLength = targetLength;
+		this.preBuffering = preBuffering;
+		this.minimumRequest = minimumRequest;
+		this.fragmentSize = fragmentSize;
+	}
+
+	public int getMaxLength() {
+		return maxLength;
+	}
+
+	public int getTargetLength() {
+		return targetLength;
+	}
+
+	public int getPreBuffering() {
+		return preBuffering;
+	}
+
+	public int getMinimumRequest() {
+		return minimumRequest;
+	}
+
+	public int getFragmentSize() {
+		return fragmentSize;
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pulseaudio/src/java/org/classpath/icedtea/pulseaudio/StreamSampleSpecification.java	Fri Oct 10 16:03:12 2008 -0400
@@ -0,0 +1,70 @@
+/* PulseAudioClip.java
+   Copyright (C) 2008 Red Hat, Inc.
+
+This file is part of IcedTea.
+
+IcedTea is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License as published by
+the Free Software Foundation, version 2.
+
+IcedTea is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with IcedTea; see the file COPYING.  If not, write to
+the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library.  Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module.  An independent module is a module which is not derived from
+or based on this library.  If you modify this library, you may extend
+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;
+
+import org.classpath.icedtea.pulseaudio.Stream.Format;
+
+public class StreamSampleSpecification {
+	private Format format;
+	private int rate;
+	private int channels;
+
+	public StreamSampleSpecification(Format format, int rate, int channels) {
+		this.format = format;
+		this.rate = rate;
+		this.channels = channels;
+	}
+
+	public StreamSampleSpecification(String format, int rate, int channels) {
+		this.format = Format.valueOf(format);
+		this.rate = rate;
+		this.channels = channels;
+	}
+
+	public Format getFormat() {
+		return format;
+	}
+
+	public int getRate() {
+		return rate;
+	}
+
+	public int getChannels() {
+		return channels;
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pulseaudio/src/native/jni-common.c	Fri Oct 10 16:03:12 2008 -0400
@@ -0,0 +1,236 @@
+/* jni-common.c
+ Copyright (C) 2008 Red Hat, Inc.
+
+ This file is part of IcedTea.
+
+ IcedTea is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, version 2.
+
+ IcedTea is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with IcedTea; see the file COPYING.  If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301 USA.
+
+ Linking this library statically or dynamically with other modules is
+ making a combined work based on this library.  Thus, the terms and
+ conditions of the GNU General Public License cover the whole
+ combination.
+
+ As a special exception, the copyright holders of this library give you
+ permission to link this library with independent modules to produce an
+ executable, regardless of the license terms of these independent
+ modules, and to copy and distribute the resulting executable under
+ terms of your choice, provided that you also meet, for each linked
+ independent module, the terms and conditions of the license of that
+ module.  An independent module is a module which is not derived from
+ or based on this library.  If you modify this library, you may extend
+ 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.
+ */
+
+#include "jni-common.h"
+
+#include <assert.h>
+#include <string.h>
+
+/*
+ * Throw an exception by name
+ */
+void throwByName(JNIEnv* env, const char* name, const char* msg) {
+	jclass cls = (*env)->FindClass(env, name);
+	if (cls != NULL) {
+		(*env)->ThrowNew(env, cls, msg);
+		return;
+	}
+}
+
+jint getJavaIntField(JNIEnv* env, jobject obj, char* fieldName) {
+	jclass cls = (*env)->GetObjectClass(env, obj);
+	assert(cls);
+	jfieldID fid = (*env)->GetFieldID(env, cls, fieldName, "I");
+	assert(fid);
+	jint value = (*env)->GetIntField(env, obj, fid);
+	return value;
+}
+
+void setJavaIntField(JNIEnv *env, jobject obj, char *fieldName, jint value) {
+	jclass cls = (*env)->GetObjectClass(env, obj);
+	assert(cls);
+	jfieldID fid =(*env)->GetFieldID(env, cls, fieldName, "I");
+	assert(fid);
+	(*env)->SetIntField(env, obj, fid, value);
+}
+
+jlong getJavaLongField(JNIEnv* env, jobject obj, char* name) {
+	jclass cls = (*env)->GetObjectClass(env, obj);
+	assert(cls);
+	jfieldID fid = (*env)->GetFieldID(env, cls, name, "J");
+	assert(fid);
+	jint value = (*env)->GetLongField(env, obj, fid);
+	return value;
+
+}
+
+void setJavaLongField(JNIEnv* env, jobject obj, char* name, jlong value) {
+	jclass cls = (*env)->GetObjectClass(env, obj);
+	assert(cls);
+	jfieldID fid =(*env)->GetFieldID(env, cls, name, "J");
+	assert(fid);
+	(*env)->SetLongField(env, obj, fid, value);
+}
+
+jbyteArray getJavaByteArrayField(JNIEnv* env, jobject obj, char* name) {
+
+	jclass cls = (*env)->GetObjectClass(env, obj);
+	assert(cls);
+	jfieldID fid = (*env)->GetFieldID(env, cls, name, "[B");
+	assert(fid);
+	jbyteArray array = (*env)->GetObjectField(env, obj, fid);
+	assert(array);
+	return array;
+
+}
+
+void setJavaByteArrayField(JNIEnv* env, jobject obj, char* name,
+		jbyteArray array) {
+
+	jclass cls = (*env)->GetObjectClass(env, obj);
+	assert(cls);
+	jfieldID fid = (*env)->GetFieldID(env, cls, name, "[B");
+	assert(fid);
+
+	(*env)->SetObjectField(env, obj, fid, array);
+	return;
+}
+
+void callJavaVoidMethod(JNIEnv* env, jobject obj, const char* method_name) {
+
+	jclass cls = (*env)->GetObjectClass(env, obj);
+	if (cls == NULL) {
+		printf("unable to get class of object");
+		return;
+	}
+	jmethodID mid = (*env)->GetMethodID(env, cls, method_name, "()V");
+	if (mid == NULL) {
+		printf("unable to get method %s\n", method_name);
+		return;
+
+	}
+	(*env)->CallVoidMethod(env, obj, mid);
+
+	return;
+
+}
+
+jobject getLockObject(JNIEnv* env) {
+
+	const char* eventLoopClassName =
+			"org/classpath/icedtea/pulseaudio/EventLoop";
+
+	jclass eventLoopClass = (*env)->FindClass(env, eventLoopClassName);
+	assert(eventLoopClass);
+
+	const char* getEventLoopIDSignature =
+			"()Lorg/classpath/icedtea/pulseaudio/EventLoop;";
+	jmethodID getEventLoopID = (*env)->GetStaticMethodID(env, eventLoopClass, "getEventLoop",
+			getEventLoopIDSignature);
+	assert(getEventLoopID);
+
+	jobject eventLoop = (*env)->CallStaticObjectMethod(env, eventLoopClass, getEventLoopID);
+	assert(eventLoop);
+
+	jfieldID lockID = (*env)->GetFieldID(env, eventLoopClass, "threadLock",
+			"Ljava/lang/Object;");
+	assert(lockID);
+
+	jobject lockObject = (*env)->GetObjectField(env, eventLoop, lockID);
+	assert(lockObject);
+	return lockObject;
+
+}
+
+void notifyWaitingOperations(JNIEnv* env) {
+	jobject lockObject = getLockObject(env);
+
+	(*env)->MonitorEnter(env, lockObject);
+
+	jclass objectClass = (*env)->FindClass(env, "java/lang/Object");
+	assert(objectClass);
+	jmethodID notifyAllID = (*env)->GetMethodID(env, objectClass, "notifyAll", "()V");
+	assert(notifyAllID);
+
+	(*env)->CallObjectMethod(env, lockObject, notifyAllID);
+
+	(*env)->MonitorExit(env, lockObject);
+
+}
+
+void* getJavaPointer(JNIEnv* env, jobject obj, char* name) {
+
+	jbyteArray array = getJavaByteArrayField(env, obj, name);
+	assert(array);
+	void* value = convertJavaPointerToNative(env, array);
+	// allow returning NULL values
+	return value;
+}
+
+void setJavaPointer(JNIEnv* env, jobject obj, char* name, void* value) {
+
+	// allow NULL for value
+	jbyteArray array = convertNativePointerToJava(env, value);
+	assert(array);
+	setJavaByteArrayField(env, obj, name, array);
+	return;
+}
+
+void* convertJavaPointerToNative(JNIEnv* env, jbyteArray pointer) {
+	//	printf("convertJavaPointerToNative(): entering method\n");
+
+	void* returnPointer = NULL;
+
+	// this is not the pointer, but the container of the pointer
+	assert(pointer);
+
+	jsize len = (*env)->GetArrayLength(env, pointer);
+	assert(len);
+	assert(len == sizeof(returnPointer));
+
+	jbyte* data = (*env)->GetByteArrayElements(env, pointer, NULL);
+	if (data == NULL) {
+		return NULL; // oome;
+	}
+	memcpy(&returnPointer, data, sizeof(returnPointer));
+	(*env)->ReleaseByteArrayElements(env, pointer, data, 0);
+
+	//	printf("convertJavaPointerToNative(): leaving method\n");
+	return returnPointer;
+}
+
+jbyteArray convertNativePointerToJava(JNIEnv* env, void* pointer) {
+	//	printf("convertNativePointerToJava(): entering method\n");
+
+	jbyteArray array = (*env)->NewByteArray(env, sizeof(pointer));
+	if (array == NULL) {
+		return 0; // oome?
+	}
+
+	jbyte* data = (*env)->GetByteArrayElements(env, array, NULL);
+	if (data == NULL) {
+		return 0; // oome
+	}
+
+	memcpy(data, &pointer, sizeof(pointer));
+	(*env)->ReleaseByteArrayElements(env, array, data, 0);
+
+	//	printf("convertNativePointerToJava(): leaving method\n");
+
+	return array;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pulseaudio/src/native/jni-common.h	Fri Oct 10 16:03:12 2008 -0400
@@ -0,0 +1,90 @@
+/* jni-common.h
+ Copyright (C) 2008 Red Hat, Inc.
+
+ This file is part of IcedTea.
+
+ IcedTea is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, version 2.
+
+ IcedTea is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with IcedTea; see the file COPYING.  If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301 USA.
+
+ Linking this library statically or dynamically with other modules is
+ making a combined work based on this library.  Thus, the terms and
+ conditions of the GNU General Public License cover the whole
+ combination.
+
+ As a special exception, the copyright holders of this library give you
+ permission to link this library with independent modules to produce an
+ executable, regardless of the license terms of these independent
+ modules, and to copy and distribute the resulting executable under
+ terms of your choice, provided that you also meet, for each linked
+ independent module, the terms and conditions of the license of that
+ module.  An independent module is a module which is not derived from
+ or based on this library.  If you modify this library, you may extend
+ 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.
+ */
+
+#ifndef _JNI_COMMON_H
+#define _JNI_COMMON_H
+
+#include <jni.h>
+/*
+ * This file contains some commonly used functions 
+ * 
+ */
+
+typedef struct java_context_t {
+	JNIEnv* env;
+	jobject obj;
+} java_context_t;
+
+/* Exception Handling */
+
+void throwByName(JNIEnv* const env, const char* const name,
+		const char* const msg);
+
+#define ILLEGAL_ARGUMENT_EXCEPTION "java/lang/IllegalArgumentException"
+#define ILLEGAL_STATE_EXCEPTION "java/lang/IllegalStateException"
+
+/* Threading and Synchronization */
+
+jobject getLockObject(JNIEnv* env);
+void notifyWaitingOperations(JNIEnv* env);
+
+/* Storing and Loading Values */
+
+jint getJavaIntField(JNIEnv* env, jobject obj, char* fieldName);
+void setJavaIntField(JNIEnv* env, jobject obj, char* fieldName, jint value);
+
+jlong getJavaLongField(JNIEnv* env, jobject obj, char* name);
+void setJavaLongField(JNIEnv* env, jobject, char* name, jlong value);
+
+jbyteArray getJavaByteArrayField(JNIEnv* env, jobject obj, char* name);
+void setJavaByteArrayField(JNIEnv* env, jobject obj, char* name,
+		jbyteArray array);
+
+/* Pointers and Java */
+
+void* getJavaPointer(JNIEnv* env, jobject obj, char* name);
+void setJavaPointer(JNIEnv* env, jobject obj, char*name, void* pointer_value);
+
+void* convertJavaPointerToNative(JNIEnv* env, jbyteArray pointer);
+jbyteArray convertNativePointerToJava(JNIEnv* env, void* pointer);
+
+/* Calling Java Functions */
+
+void callJavaVoidMethod(JNIEnv* env, jobject obj, const char* method_name);
+
+#endif
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pulseaudio/src/native/org_classpath_icedtea_pulseaudio_EventLoop.c	Fri Oct 10 16:03:12 2008 -0400
@@ -0,0 +1,362 @@
+/* org_classpath_icedtea_pulseaudio_EventLoop.c
+ Copyright (C) 2008 Red Hat, Inc.
+
+ This file is part of IcedTea.
+
+ IcedTea is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, version 2.
+
+ IcedTea is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with IcedTea; see the file COPYING.  If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301 USA.
+
+ Linking this library statically or dynamically with other modules is
+ making a combined work based on this library.  Thus, the terms and
+ conditions of the GNU General Public License cover the whole
+ combination.
+
+ As a special exception, the copyright holders of this library give you
+ permission to link this library with independent modules to produce an
+ executable, regardless of the license terms of these independent
+ modules, and to copy and distribute the resulting executable under
+ terms of your choice, provided that you also meet, for each linked
+ independent module, the terms and conditions of the license of that
+ module.  An independent module is a module which is not derived from
+ or based on this library.  If you modify this library, you may extend
+ 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.
+ */
+
+#include <pulse/pulseaudio.h>
+
+#include "org_classpath_icedtea_pulseaudio_EventLoop.h"
+#include "jni-common.h"
+
+#include <poll.h>
+
+const int PA_ITERATE_BLOCK = 1;
+const int PA_ITERATE_NOBLOCK = 0;
+
+static java_context_t* java_context = NULL;
+
+JNIEnv* pulse_thread_env = NULL;
+
+void sink_list_success_cb(pa_context *context, const pa_sink_info *i, int eol,
+		void *userdata) {
+
+	if (eol == 0) {
+		jclass cls = (*pulse_thread_env)->GetObjectClass(pulse_thread_env,
+				java_context->obj);
+		assert(cls);
+		jstring name = (*pulse_thread_env)->NewStringUTF(pulse_thread_env, i->name);
+		assert(name);
+		jmethodID mid1 = (*pulse_thread_env)->GetMethodID(pulse_thread_env, cls,
+				"sink_callback", "(Ljava/lang/String;)V");
+		assert(mid1);
+		(*pulse_thread_env)->CallVoidMethod(pulse_thread_env,
+				java_context->obj, mid1, name) ;
+	} else {
+		assert(pulse_thread_env);
+		notifyWaitingOperations(pulse_thread_env);
+	}
+
+}
+
+void source_list_success_cb(pa_context *context, const pa_source_info *i,
+		int eol, void *userdata) {
+
+	if (eol == 0) {
+		jclass cls = (*pulse_thread_env)->GetObjectClass(pulse_thread_env,
+				java_context->obj);
+		assert(cls);
+		jstring name = (*pulse_thread_env)->NewStringUTF(pulse_thread_env, i->name);
+		assert(name);
+		jmethodID mid1 = (*pulse_thread_env)->GetMethodID(pulse_thread_env, cls,
+				"source_callback", "(Ljava/lang/String;)V");
+		assert(mid1);
+		(*pulse_thread_env)->CallVoidMethod(pulse_thread_env,
+				java_context->obj, mid1, name) ;
+	} else {
+		assert(pulse_thread_env);
+		notifyWaitingOperations(pulse_thread_env);
+	}
+
+}
+
+static void context_change_callback(pa_context* context, void* userdata) {
+	assert(context);
+	assert(userdata == NULL);
+
+	//java_context_t* java_context = (java_context_t*)userdata;
+	JNIEnv* env = java_context->env;
+	jobject obj = java_context->obj;
+
+	//	printf("context state changed to %d\n", pa_context_get_state(context));
+
+	/* Call the EventLoop.update method in java
+	 * to handle all java-side events
+	 */
+	jclass cls = (*env)->GetObjectClass(env, obj);
+	assert(cls);
+	jmethodID mid = (*env)->GetMethodID(env, cls, "update", "(I)V");
+	assert(mid);
+	(*env)->CallVoidMethod(env, obj, mid, pa_context_get_state(context));
+	return;
+
+}
+
+static int poll_function(struct pollfd *ufds, unsigned long nfds, int timeout,
+		void *userdata) {
+
+	JNIEnv* env = pulse_thread_env;
+	assert(env);
+	jobject lockObject = getLockObject(env);
+
+	(*env)->MonitorExit(env, lockObject);
+
+	int value = poll(ufds, nfds, timeout);
+
+	(*env)->MonitorEnter(env, lockObject);
+	return value;
+}
+
+/*
+ * Class:     org_classpath_icedtea_pulseaudio_EventLoop
+ * Method:    native_setup
+ * Signature: (Ljava/lang/String;Ljava/lang/String;)V
+ */
+JNIEXPORT void JNICALL Java_org_classpath_icedtea_pulseaudio_EventLoop_native_1setup
+(JNIEnv* env, jobject obj, jstring appName, jstring server) {
+
+	assert(appName != NULL);
+
+	//	printf("native_setup() called\n");
+	pa_mainloop *mainloop = pa_mainloop_new();
+	assert(mainloop != NULL);
+	pa_mainloop_api *mainloop_api = pa_mainloop_get_api(mainloop);
+	assert(mainloop != NULL);
+
+	pa_context *context = NULL;
+
+	const char* string_appName;
+	string_appName = (*env)->GetStringUTFChars(env, appName, NULL);
+	if (string_appName == NULL) {
+		return; /* a OutOfMemoryError thrown by vm */
+	}
+	//	printf("using appName : %s\n", string_appName);
+	context = pa_context_new(mainloop_api, string_appName);
+	assert(mainloop != NULL);
+	(*env)->ReleaseStringUTFChars(env, appName, string_appName);
+
+	obj = (*env)->NewGlobalRef(env, obj);
+
+	java_context = malloc(sizeof(java_context_t));
+	java_context->env = env;
+	pulse_thread_env = env;
+	java_context->obj = obj;
+
+	pa_context_set_state_callback(context, context_change_callback, NULL);
+
+	if (server != NULL) {
+		/* obtain the server from the caller */
+		const char* string_server = NULL;
+		string_server = (*env)->GetStringUTFChars(env, server, NULL);
+		if (string_server == NULL) {
+			/* error, so clean up */
+			(*env)->DeleteGlobalRef(env, java_context->obj);
+			pa_context_disconnect(context);
+			pa_mainloop_free(mainloop);
+			free(java_context);
+			return; /* OutOfMemoryError */
+		}
+		//		printf("About to connect to server: %s\n", string_server);
+		pa_context_connect(context, string_server, 0, NULL);
+		(*env)->ReleaseStringUTFChars(env, appName, string_server);
+	} else {
+		//		printf("using default server\n");
+		pa_context_connect(context, NULL, 0, NULL);
+	}
+
+	// set polling function
+	pa_mainloop_set_poll_func(mainloop, poll_function, NULL);
+
+	setJavaPointer(env, obj, "mainloopPointer", mainloop);
+	setJavaPointer(env, obj, "contextPointer", context);
+	//	printf("native_setup() returning\n");
+	return;
+
+}
+/*
+ * Class:     org_classpath_icedtea_pulseaudio_EventLoop
+ * Method:    native_iterate
+ * Signature: (I)I
+ */
+JNIEXPORT jint JNICALL Java_org_classpath_icedtea_pulseaudio_EventLoop_native_1iterate
+(JNIEnv* env, jobject obj, jint timeout) {
+
+	pa_mainloop* mainloop = (pa_mainloop*) getJavaPointer(env, obj, "mainloopPointer");
+	assert(mainloop);
+
+	int returnval;
+
+	returnval = pa_mainloop_prepare(mainloop, timeout);
+	if ( returnval < 0) {
+		return -1;
+	}
+	returnval = pa_mainloop_poll(mainloop);
+	if ( returnval < 0) {
+		return -1;
+	}
+	returnval = pa_mainloop_dispatch(mainloop);
+	if ( returnval < 0) {
+		return -1;
+	}
+	return returnval;
+
+}
+
+/*
+ * Class:     org_classpath_icedtea_pulseaudio_EventLoop
+ * Method:    nativeUpdateTargetPortNameList
+ * Signature: ()[B
+ */
+JNIEXPORT jbyteArray JNICALL Java_org_classpath_icedtea_pulseaudio_EventLoop_nativeUpdateTargetPortNameList
+(JNIEnv* env, jobject obj) {
+
+	pa_context* context = (pa_context*) getJavaPointer(env, obj, "contextPointer");
+	assert(context);
+	pa_operation *o = pa_context_get_sink_info_list(context, sink_list_success_cb, NULL);
+	assert(o);
+	return convertNativePointerToJava(env, o);
+}
+
+
+/*
+ * Class:     org_classpath_icedtea_pulseaudio_EventLoop
+ * Method:    nativeUpdateSourcePortNameList
+ * Signature: ()[B
+ */
+JNIEXPORT jbyteArray JNICALL Java_org_classpath_icedtea_pulseaudio_EventLoop_nativeUpdateSourcePortNameList
+(JNIEnv * env, jobject obj) {
+	pa_context* context = (pa_context*) getJavaPointer(env, obj, "contextPointer");
+	assert(context);
+	pa_operation *o = pa_context_get_source_info_list(context, source_list_success_cb, NULL);
+	assert(o);
+	return convertNativePointerToJava(env, o);
+}
+
+static void context_drain_complete_callback(pa_context* context, void* userdata) {
+	pa_context_disconnect(context);
+
+}
+
+/*
+ * Class:     org_classpath_icedtea_pulseaudio_EventLoop
+ * Method:    native_shutdown
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL Java_org_classpath_icedtea_pulseaudio_EventLoop_native_1shutdown
+(JNIEnv *env, jobject obj) {
+
+	//	printf("native_shutdown() starting\n");
+
+	pa_mainloop* mainloop = (pa_mainloop*) getJavaPointer(env, obj, "mainloopPointer");
+	assert(mainloop != NULL);
+
+	pa_context* context = (pa_context*) getJavaPointer(env, obj, "contextPointer");
+	assert(context != NULL);
+
+	pa_operation* o = pa_context_drain(context, context_drain_complete_callback, NULL);
+	if ( o == NULL) {
+		pa_context_disconnect(context);
+		pa_mainloop_free(mainloop);
+	} else {
+		pa_operation_unref(o);
+	}
+	
+	pa_context_unref(context);
+	(*env)->DeleteGlobalRef(env, java_context->obj);
+
+	free(java_context);
+	java_context = NULL;
+
+	setJavaPointer(env, obj, "mainloopPointer", NULL);
+	setJavaPointer(env, obj, "contextPointer", NULL);
+
+	//	printf("native_shutdown() returning\n");
+
+}
+
+static void sink_input_volume_change_complete(pa_context* contest, int success,
+		void* userdata) {
+	// userdata is the pointer to the int containing the new volume 
+
+	assert(userdata);
+	free(userdata);
+	assert(success);
+
+}
+
+static void sink_input_change_volume(pa_context* c,
+		const pa_sink_input_info* i, int eol, void* userdata) {
+	assert(c);
+
+	if (eol) {
+		return;
+	}
+
+	assert(i);
+	assert(userdata);
+	int volume = *((int*)userdata);
+
+	int j = 0;
+	//	printf("changing sink input volume\n");
+	pa_cvolume* new_volume = malloc(sizeof(pa_cvolume));
+
+	//	printf("allocated memory\n");
+	new_volume->channels = i->volume.channels;
+	//	printf("set the number of channels\n");
+	for (j = 0; j < new_volume->channels; j++) {
+		new_volume->values[j] = volume;
+	}
+
+	//	printf("calling set_sick_input_volume\n");
+	pa_operation* o = pa_context_set_sink_input_volume(c, i->index, new_volume,
+			sink_input_volume_change_complete, new_volume);
+	if (o != NULL) {
+		pa_operation_unref(o);
+	}
+
+	//	printf("done setup for changing volume\n");
+}
+
+
+/*
+ * Class:     org_classpath_icedtea_pulseaudio_EventLoop
+ * Method:    native_set_sink_volume
+ * Signature: ([BI)V
+ */
+JNIEXPORT void JNICALL Java_org_classpath_icedtea_pulseaudio_EventLoop_native_1set_1sink_1volume
+(JNIEnv* env, jobject obj, jbyteArray streamPointer, jint volume) {
+
+	pa_stream* stream = (pa_stream*) convertJavaPointerToNative(env, streamPointer);
+	assert(stream);
+	pa_context* context = (pa_context*) getJavaPointer(env, obj, "contextPointer");
+	assert(context);
+
+	int* new_volume = malloc(sizeof(int));
+	*new_volume = volume;
+
+	int stream_id = pa_stream_get_index(stream);
+	pa_context_get_sink_input_info(context, stream_id,sink_input_change_volume, new_volume);
+	return;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pulseaudio/src/native/org_classpath_icedtea_pulseaudio_Operation.c	Fri Oct 10 16:03:12 2008 -0400
@@ -0,0 +1,83 @@
+/* org_classpath_icedtea_pulseaudio_Operation.c
+ Copyright (C) 2008 Red Hat, Inc.
+
+ This file is part of IcedTea.
+
+ IcedTea is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, version 2.
+
+ IcedTea is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with IcedTea; see the file COPYING.  If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301 USA.
+
+ Linking this library statically or dynamically with other modules is
+ making a combined work based on this library.  Thus, the terms and
+ conditions of the GNU General Public License cover the whole
+ combination.
+
+ As a special exception, the copyright holders of this library give you
+ permission to link this library with independent modules to produce an
+ executable, regardless of the license terms of these independent
+ modules, and to copy and distribute the resulting executable under
+ terms of your choice, provided that you also meet, for each linked
+ independent module, the terms and conditions of the license of that
+ module.  An independent module is a module which is not derived from
+ or based on this library.  If you modify this library, you may extend
+ 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.
+ */
+
+#include "org_classpath_icedtea_pulseaudio_Operation.h"
+
+#include "jni-common.h"
+#include <pulse/pulseaudio.h>
+
+/*
+ * Class:     org_classpath_icedtea_pulseaudio_Operation
+ * Method:    native_ref
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL Java_org_classpath_icedtea_pulseaudio_Operation_native_1ref
+(JNIEnv* env, jobject obj) {
+
+	pa_operation* operation = (pa_operation*) getJavaPointer(env, obj, "operationPointer");
+	assert(operation);
+	pa_operation_ref(operation);
+
+}
+
+/*
+ * Class:     org_classpath_icedtea_pulseaudio_Operation
+ * Method:    native_unref
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL Java_org_classpath_icedtea_pulseaudio_Operation_native_1unref
+(JNIEnv* env, jobject obj) {
+
+	pa_operation* operation = (pa_operation*) getJavaPointer(env, obj, "operationPointer");
+	assert(operation);
+	pa_operation_unref(operation);
+
+}
+
+/*
+ * Class:     org_classpath_icedtea_pulseaudio_Operation
+ * Method:    native_get_state
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_org_classpath_icedtea_pulseaudio_Operation_native_1get_1state
+(JNIEnv *env, jobject obj) {
+
+	pa_operation* operation = (pa_operation*) getJavaPointer(env, obj, "operationPointer");
+	assert(operation);
+	int state = pa_operation_get_state(operation);
+	return state;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pulseaudio/src/native/org_classpath_icedtea_pulseaudio_PulseAudioSourcePort.c	Fri Oct 10 16:03:12 2008 -0400
@@ -0,0 +1,102 @@
+#include "org_classpath_icedtea_pulseaudio_PulseAudioSourcePort.h"
+#include "jni-common.h"
+#include <pulse/pulseaudio.h>
+#include <string.h>
+
+typedef struct java_context {
+	JNIEnv* env;
+	jobject obj;
+} java_context;
+
+extern JNIEnv* pulse_thread_env;
+
+void source_callback(pa_context *context, int success, void *userdata) {
+	assert(context);
+	assert(pulse_thread_env);
+	notifyWaitingOperations(pulse_thread_env);
+}
+
+void get_source_volume_callback(pa_context *context, const pa_source_info *i,
+		int eol, void *userdata) {
+	assert(context);
+	assert(pulse_thread_env);
+	
+	if (eol == 0) {
+		// printf("%s\n", i->name); 
+		jobject obj = (jobject) userdata;
+		assert(obj);
+		jclass cls = (*pulse_thread_env)->GetObjectClass(pulse_thread_env, obj);
+		assert(cls);
+		jmethodID mid1 = (*pulse_thread_env)->GetMethodID(pulse_thread_env, cls,
+				"update_channels_and_volume", "(IF)V");
+		assert(mid1);
+		(*pulse_thread_env)->CallVoidMethod(pulse_thread_env, obj, mid1,
+				(int) (i->volume).channels, (float) (i->volume).values[0]) ;
+	} else {
+		notifyWaitingOperations(pulse_thread_env);
+	}
+}
+
+/*
+ * Class:     org_classpath_icedtea_pulseaudio_PulseAudioSourcePort
+ * Method:    native_updateVolumeInfo
+ * Signature: ()[B
+ */
+JNIEXPORT jbyteArray JNICALL Java_org_classpath_icedtea_pulseaudio_PulseAudioSourcePort_native_1updateVolumeInfo
+(JNIEnv *env, jobject obj) {
+	jclass cls = (*env)->GetObjectClass(env, obj);
+	assert(cls);
+	jfieldID fid = (*env)->GetFieldID(env, cls, "name", "Ljava/lang/String;");
+	assert(fid);
+	jstring jstr = (*env)->GetObjectField(env, obj, fid);
+	assert(jstr);
+	const char *name = (*env)->GetStringUTFChars(env, jstr, NULL);
+	if (name == NULL) {
+		return NULL;    // oome
+	}
+	
+	pa_context* context = (pa_context*) getJavaPointer(env, obj, "contextPointer");
+	assert(context);
+	obj = (*env)->NewGlobalRef(env, obj);
+	pa_operation *o = pa_context_get_source_info_by_name (context, (char*) name, get_source_volume_callback, obj);
+	assert(o);
+	return convertNativePointerToJava(env, o);
+}
+
+/*
+ * Class:     org_classpath_icedtea_pulseaudio_PulseAudioSourcePort
+ * Method:    native_setVolume
+ * Signature: (F)[B
+ */
+JNIEXPORT jbyteArray JNICALL Java_org_classpath_icedtea_pulseaudio_PulseAudioSourcePort_native_1setVolume
+(JNIEnv *env, jobject obj, jfloat value) {
+	jclass cls = (*env)->GetObjectClass(env, obj);
+	assert(cls);
+	
+	jfieldID fid = (*env)->GetFieldID(env, cls, "name", "Ljava/lang/String;");
+	assert(fid);
+	
+	jstring jstr = (*env)->GetObjectField(env, obj, fid);
+	assert(jstr);
+	
+	const char *name = (*env)->GetStringUTFChars(env, jstr, NULL);
+	if (name == NULL) {
+		return NULL; 	// oome
+	}
+	
+	pa_context* context = (pa_context*) getJavaPointer(env, obj, "contextPointer");
+	assert(context);
+	
+	obj = (*env)->NewGlobalRef(env, obj);
+	fid = (*env)->GetFieldID(env, cls, "channels", "I");
+	assert(fid);
+	
+	jint channels = (*env)->GetIntField(env, obj, fid);
+	pa_cvolume cv;
+	
+	pa_operation *o = pa_context_set_source_volume_by_name (context, (char*) name,pa_cvolume_set(&cv, channels, value), source_callback, obj);
+	assert(o);
+	
+	return convertNativePointerToJava(env, o);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pulseaudio/src/native/org_classpath_icedtea_pulseaudio_PulseAudioStreamVolumeControl.c	Fri Oct 10 16:03:12 2008 -0400
@@ -0,0 +1,67 @@
+/* org_classpath_icedtea_pulseaudio_PulseAudioStreamVolumeControl.c
+ Copyright (C) 2008 Red Hat, Inc.
+
+ This file is part of IcedTea.
+
+ IcedTea is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, version 2.
+
+ IcedTea is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with IcedTea; see the file COPYING.  If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301 USA.
+
+ Linking this library statically or dynamically with other modules is
+ making a combined work based on this library.  Thus, the terms and
+ conditions of the GNU General Public License cover the whole
+ combination.
+
+ As a special exception, the copyright holders of this library give you
+ permission to link this library with independent modules to produce an
+ executable, regardless of the license terms of these independent
+ modules, and to copy and distribute the resulting executable under
+ terms of your choice, provided that you also meet, for each linked
+ independent module, the terms and conditions of the license of that
+ module.  An independent module is a module which is not derived from
+ or based on this library.  If you modify this library, you may extend
+ 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.
+ */
+
+#include <jni.h>
+#include <pulse/pulseaudio.h>
+
+#include "jni-common.h"
+#include "org_classpath_icedtea_pulseaudio_PulseAudioStreamVolumeControl.h"
+
+
+extern JNIEnv* pulse_thread_env;
+
+static void set_sink_input_volume_callback(pa_context* context, int success,
+		void* userdata) {
+
+	JNIEnv* env = pulse_thread_env;
+
+	notifyWaitingOperations(env);
+
+}
+
+JNIEXPORT jint JNICALL Java_org_classpath_icedtea_pulseaudio_PulseAudioStreamVolumeControl_native_1setValue
+(JNIEnv *env, jobject obj, jfloat new_volume) {
+	//	printf("IN NATIVE SET VOLUME\n");
+	pa_stream *stream = getJavaPointer(env, obj, "streamPointer");
+	//	printf("STREAM POINTER %d", (int) stream);
+	pa_context *context = pa_stream_get_context(stream);
+	int stream_id = pa_stream_get_index(stream);
+	int channels = pa_stream_get_sample_spec(stream)->channels;
+	pa_cvolume cv;
+	return (jint) pa_context_set_sink_input_volume(context, stream_id, pa_cvolume_set(&cv, channels, new_volume), set_sink_input_volume_callback, NULL);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pulseaudio/src/native/org_classpath_icedtea_pulseaudio_PulseAudioTargetPort.c	Fri Oct 10 16:03:12 2008 -0400
@@ -0,0 +1,107 @@
+#include "org_classpath_icedtea_pulseaudio_PulseAudioTargetPort.h"
+
+#include "jni-common.h"
+#include <pulse/pulseaudio.h>
+#include <string.h>
+
+typedef struct java_context {
+	JNIEnv* env;
+	jobject obj;
+} java_context;
+
+extern JNIEnv* pulse_thread_env;
+
+void sink_callback(pa_context *context, int success, void *userdata) {
+	notifyWaitingOperations(pulse_thread_env);
+}
+
+void get_sink_volume_callback(pa_context *context, const pa_sink_info *i,
+		int eol, void *userdata) {
+	assert(context);
+	assert(pulse_thread_env);
+
+	if (eol == 0) {
+		// printf("%s\n", i->name); 
+		jobject obj = (jobject) userdata;
+		assert(obj);
+		jclass cls = (*pulse_thread_env)->GetObjectClass(pulse_thread_env, obj);
+		assert(cls);
+		jmethodID mid1 = (*pulse_thread_env)->GetMethodID(pulse_thread_env, cls,
+				"update_channels_and_volume", "(IF)V");
+		assert(mid1);
+		(*pulse_thread_env)->CallVoidMethod(pulse_thread_env, obj, mid1,
+				(int) (i->volume).channels, (float) (i->volume).values[0]) ;
+	} else {
+		notifyWaitingOperations(pulse_thread_env);
+	}
+
+}
+
+
+/*
+ * Class:     org_classpath_icedtea_pulseaudio_PulseAudioTargetPort
+ * Method:    native_updateVolumeInfo
+ * Signature: ()[B
+ */
+JNIEXPORT jbyteArray JNICALL Java_org_classpath_icedtea_pulseaudio_PulseAudioTargetPort_native_1updateVolumeInfo
+(JNIEnv *env, jobject obj) {
+	jclass cls = (*env)->GetObjectClass(env, obj);
+	assert(cls);
+	
+	jfieldID fid = (*env)->GetFieldID(env, cls, "name", "Ljava/lang/String;");
+	assert(fid);
+	
+	jstring jstr = (*env)->GetObjectField(env, obj, fid);
+	assert(jstr);
+	
+	const char *name = (*env)->GetStringUTFChars(env, jstr, NULL);
+	if (name == NULL) {
+		return NULL;	// oome
+	}
+	
+	pa_context* context = (pa_context*) getJavaPointer(env, obj, "contextPointer");
+	assert(context);
+	
+	obj = (*env)->NewGlobalRef(env, obj);
+	
+	pa_operation *o = pa_context_get_sink_info_by_name (context, (char*) name, get_sink_volume_callback, obj);
+	assert(o);
+	
+	return convertNativePointerToJava(env, o);
+}
+
+/*
+ * Class:     org_classpath_icedtea_pulseaudio_PulseAudioTargetPort
+ * Method:    native_setVolume
+ * Signature: (F)[B
+ */
+JNIEXPORT jbyteArray JNICALL Java_org_classpath_icedtea_pulseaudio_PulseAudioTargetPort_native_1setVolume
+(JNIEnv *env, jobject obj, jfloat value) {
+	jclass cls = (*env)->GetObjectClass(env, obj);
+	assert(cls);
+	
+	jfieldID fid = (*env)->GetFieldID(env, cls, "name", "Ljava/lang/String;");
+	assert(fid);
+	
+	jstring jstr = (*env)->GetObjectField(env, obj, fid);
+	assert(jstr);
+	
+	const char *name = (*env)->GetStringUTFChars(env, jstr, NULL);
+	if (name == NULL) {
+		return NULL;	// return oome
+	}
+	
+	pa_context* context = (pa_context*) getJavaPointer(env, obj, "contextPointer");
+	assert(context);
+	
+	obj = (*env)->NewGlobalRef(env, obj);
+	fid = (*env)->GetFieldID(env, cls, "channels", "I");
+	assert(fid);
+	
+	jint channels = (*env)->GetIntField(env, obj, fid);
+	pa_cvolume cv;
+	pa_operation *o = pa_context_set_sink_volume_by_name (context, (char*) name,pa_cvolume_set(&cv, channels, value), sink_callback, obj);
+	assert(o);
+	
+	return convertNativePointerToJava(env, o);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pulseaudio/src/native/org_classpath_icedtea_pulseaudio_Stream.c	Fri Oct 10 16:03:12 2008 -0400
@@ -0,0 +1,1010 @@
+#include "org_classpath_icedtea_pulseaudio_Stream.h"
+
+#include "jni-common.h"
+#include <pulse/pulseaudio.h>
+#include <string.h>
+
+#define STREAM_POINTER "streamPointer"
+
+typedef struct java_context {
+	JNIEnv* env;
+	jobject obj;
+} java_context;
+
+extern JNIEnv* pulse_thread_env;
+
+static void set_sink_input_volume_callback(pa_context* context, int success,
+		void* userdata) {
+	notifyWaitingOperations(pulse_thread_env);
+
+}
+
+const char* getStringFromFormat(pa_sample_format_t format) {
+
+	const char* value;
+
+	if (format == PA_SAMPLE_U8) {
+		value = "PA_SAMPLE_U8";
+	} else if (format == PA_SAMPLE_ALAW) {
+		value = "PA_SAMPLE_ALAW";
+	} else if (format == PA_SAMPLE_ULAW) {
+		value = "PA_SAMPLE_ULAW";
+	} else if (format == PA_SAMPLE_S16BE) {
+		value = "PA_SAMPLE_S16BE";
+	} else if (format == PA_SAMPLE_S16LE) {
+		value = "PA_SAMPLE_S16LE";
+	} else if (format == PA_SAMPLE_S32BE) {
+		value = "PA_SAMPLE_S32BE";
+	} else if (format == PA_SAMPLE_S32LE) {
+		value = "PA_SAMPLE_S32LE";
+	} else {
+		value = "PA_SAMPLE_INVALID";
+	}
+
+	return value;
+}
+
+pa_sample_format_t getFormatFromString(const char* encoding) {
+
+	pa_sample_format_t format;
+
+	if (strcmp(encoding, "PA_SAMPLE_U8") == 0) {
+		format = PA_SAMPLE_U8;
+	} else if (strcmp(encoding, "PA_SAMPLE_ALAW") == 0) {
+		format = PA_SAMPLE_ALAW;
+	} else if (strcmp(encoding, "PA_SAMPLE_ULAW;") == 0) {
+		format = PA_SAMPLE_ULAW;
+	} else if (strcmp(encoding, "PA_SAMPLE_S16BE") == 0) {
+		format = PA_SAMPLE_S16BE;
+	} else if (strcmp(encoding, "PA_SAMPLE_S16LE") == 0) {
+		format = PA_SAMPLE_S16LE;
+	} else if (strcmp(encoding, "PA_SAMPLE_S32BE") == 0) {
+		format = PA_SAMPLE_S32BE;
+	} else if (strcmp(encoding, "PA_SAMPLE_S32LE") == 0) {
+		format = PA_SAMPLE_S32LE;
+	} else {
+		format = PA_SAMPLE_INVALID;
+	}
+
+	return format;
+}
+
+static void stream_state_callback(pa_stream* stream, void *userdata) {
+	//printf("stream_state_callback called\n");
+
+	java_context* context = userdata;
+	assert(stream);
+	assert(context);
+	assert(context->env);
+	assert(context->obj);
+
+	if (pa_stream_get_state(stream) == PA_STREAM_CREATING) {
+		callJavaVoidMethod(context->env, context->obj, "stateCallback");
+	} else {
+		callJavaVoidMethod(pulse_thread_env, context->obj, "stateCallback");
+	}
+
+}
+
+static void stream_write_callback(pa_stream *stream, size_t length,
+		void *userdata) {
+	//	printf("stream_write_callback called\n");
+
+	java_context* context = userdata;
+	assert(stream);
+	assert(context);
+	assert(context->env);
+	assert(context->obj);
+
+	if (pa_stream_get_state(stream) == PA_STREAM_CREATING) {
+		callJavaVoidMethod(context->env, context->obj, "writeCallback");
+	} else {
+		callJavaVoidMethod(pulse_thread_env, context->obj, "writeCallback");
+	}
+}
+
+static void stream_read_callback(pa_stream *stream, size_t length,
+		void *userdata) {
+	//	printf("stream_read_callback called\n");
+
+	java_context* context = userdata;
+	assert(stream);
+	assert(context);
+	assert(context->env);
+	assert(context->obj);
+
+	if (pa_stream_get_state(stream) == PA_STREAM_CREATING) {
+		callJavaVoidMethod(context->env, context->obj, "readCallback");
+	} else {
+		callJavaVoidMethod(pulse_thread_env, context->obj, "readCallback");
+	}
+
+}
+
+static void stream_overflow_callback(pa_stream *stream, void *userdata) {
+	//printf("stream_overflow_callback called\n");
+
+	java_context* context = userdata;
+	assert(stream);
+	assert(context);
+	assert(context->env);
+	assert(context->obj);
+
+	if (pa_stream_get_state(stream) == PA_STREAM_CREATING) {
+		callJavaVoidMethod(context->env, context->obj, "overflowCallback");
+	} else {
+		callJavaVoidMethod(pulse_thread_env, context->obj, "overflowCallback");
+	}
+}
+
+static void stream_underflow_callback(pa_stream *stream, void *userdata) {
+	//	printf("stream_underflow_callback called\n");
+
+	java_context* context = userdata;
+	assert(stream);
+	assert(context);
+	assert(context->env);
+	assert(context->obj);
+
+	if (pa_stream_get_state(stream) == PA_STREAM_CREATING) {
+		callJavaVoidMethod(context->env, context->obj, "underflowCallback");
+	} else {
+		callJavaVoidMethod(pulse_thread_env, context->obj, "underflowCallback");
+	}
+}
+
+
+static void update_timing_info_callback(pa_stream* stream, int success, void* userdata) {
+
+	assert(stream);
+	JNIEnv* env = pulse_thread_env;
+	assert(env);
+
+	notifyWaitingOperations(env);
+
+	if (success == 0) {
+		throwByName(env, ILLEGAL_STATE_EXCEPTION, "drain failed");
+	}
+
+}
+
+
+// requires pulseaudio 0.9.11 :(
+static void stream_started_callback(pa_stream *stream, void *userdata) {
+	// printf("stream_started_callback called\n");
+	java_context* context = userdata;
+	assert(stream);
+	assert(context);
+	assert(context->env);
+	assert(context->obj);
+
+	if (pa_stream_get_state(stream) == PA_STREAM_CREATING) {
+		callJavaVoidMethod(context->env, context->obj,
+				"playbackStartedCallback");
+	} else {
+		callJavaVoidMethod(pulse_thread_env, context->obj,
+				"playbackStartedCallback");
+	}
+
+}
+
+static void stream_latency_update_callback(pa_stream *stream, void *userdata) {
+	//	printf("stream_latency_update_callback called\n");
+
+	java_context* context = userdata;
+	assert(stream);
+	assert(context);
+	assert(context->env);
+	assert(context->obj);
+
+	if (pa_stream_get_state(stream) == PA_STREAM_CREATING) {
+		callJavaVoidMethod(context->env, context->obj, "latencyUpdateCallback");
+	} else {
+		callJavaVoidMethod(pulse_thread_env, context->obj,
+				"latencyUpdateCallback");
+	}
+}
+
+static void stream_moved_callback(pa_stream *stream, void *userdata) {
+	//	printf("stream_moved_callback called\n");
+
+	java_context* context = userdata;
+	assert(stream);
+	assert(context);
+	assert(context->env);
+	assert(context->obj);
+
+	if (pa_stream_get_state(stream) == PA_STREAM_CREATING) {
+		callJavaVoidMethod(context->env, context->obj, "movedCallback");
+	} else {
+		callJavaVoidMethod(pulse_thread_env, context->obj, "movedCallback");
+	}
+
+}
+
+static void stream_suspended_callback(pa_stream *stream, void *userdata) {
+	//	printf("stream_suspended_callback called\n");
+
+	java_context* context = userdata;
+	assert(stream);
+	assert(context);
+	assert(context->env);
+	assert(context->obj);
+
+	if (pa_stream_get_state(stream) == PA_STREAM_CREATING) {
+		callJavaVoidMethod(context->env, context->obj, "suspendedCallback");
+	} else {
+		callJavaVoidMethod(pulse_thread_env, context->obj, "suspendedCallback");
+	}
+
+}
+
+/*
+ * Class:     org_classpath_icedtea_pulseaudio_Stream
+ * Method:    native_pa_stream_new
+ * Signature: ([BLjava/lang/String;Ljava/lang/String;II)V
+ */
+JNIEXPORT void JNICALL Java_org_classpath_icedtea_pulseaudio_Stream_native_1pa_1stream_1new
+(JNIEnv* env, jobject obj, jbyteArray contextPointer, jstring nameString,
+		jstring encodingString, jint sampleRate, jint channels) {
+
+	//	printf("creating a new PulseAudio stream\n");
+
+	java_context* j_context = malloc(sizeof(java_context));
+	assert(j_context);
+	j_context->env = env;
+	j_context->obj = (*env)->NewGlobalRef(env, obj);
+
+	pa_context* context = convertJavaPointerToNative(env, contextPointer);
+	assert(context);
+
+	const char* name = NULL;
+	if (nameString) {
+		name = (*env)->GetStringUTFChars(env,nameString, NULL);
+		if (name == NULL) {
+			(*env)->DeleteGlobalRef(env, obj);
+			free(j_context);
+			return; // oome thrown
+		}
+	}
+
+	const char *encoding = (*env)->GetStringUTFChars(env, encodingString, NULL);
+	if( encoding == NULL) {
+		return; //oome thrown
+	}
+
+	pa_sample_spec sample_spec;
+
+	sample_spec.format = getFormatFromString(encoding);
+	sample_spec.rate = sampleRate;
+	sample_spec.channels = channels;
+
+	if ( !pa_sample_spec_valid(&sample_spec)) {
+		throwByName(env, "java/lang/IllegalArgumentException", "Invalid format");
+		(*env)->ReleaseStringUTFChars(env, encodingString, encoding);
+		if (name) {
+			(*env)->ReleaseStringUTFChars(env, nameString,name);
+		}
+		return;
+	}
+
+	pa_stream* stream = pa_stream_new(context, name, &sample_spec, NULL);
+	assert(stream);
+	if (name) {
+		(*env)->ReleaseStringUTFChars(env, nameString,name);
+	}
+
+	setJavaPointer(env, obj, "streamPointer", stream);
+
+	/*
+	 * 
+	 * The stream has been created; now setup the callbacks 
+	 * so we can do somethig about them
+	 * 
+	 */
+
+	pa_stream_set_state_callback (stream, stream_state_callback, j_context);
+	pa_stream_set_write_callback (stream, stream_write_callback, j_context);
+	pa_stream_set_read_callback (stream, stream_read_callback, j_context);
+	pa_stream_set_overflow_callback (stream, stream_overflow_callback, j_context);
+	pa_stream_set_underflow_callback (stream, stream_underflow_callback, j_context);
+	pa_stream_set_started_callback (stream, stream_started_callback, j_context);
+	pa_stream_set_latency_update_callback (stream, stream_latency_update_callback, j_context);
+	pa_stream_set_moved_callback (stream, stream_moved_callback, j_context);
+	pa_stream_set_suspended_callback (stream, stream_suspended_callback, j_context);
+
+}
+
+/*
+ * Class:     org_classpath_icedtea_pulseaudio_Stream
+ * Method:    native_pa_stream_unref
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL Java_org_classpath_icedtea_pulseaudio_Stream_native_1pa_1stream_1unref
+(JNIEnv* env, jobject obj) {
+	pa_stream* stream = getJavaPointer(env, obj, STREAM_POINTER);
+	assert(stream);
+	pa_stream_unref(stream);
+	setJavaPointer(env, obj, "streamPointer", NULL);
+}
+
+/*
+ * Class:     org_classpath_icedtea_pulseaudio_Stream
+ * Method:    native_pa_stream_get_state
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_org_classpath_icedtea_pulseaudio_Stream_native_1pa_1stream_1get_1state
+(JNIEnv* env, jobject obj) {
+	pa_stream* stream = (pa_stream*) getJavaPointer(env, obj, STREAM_POINTER);
+	assert(stream);
+	return pa_stream_get_state(stream);
+}
+
+/*
+ * Class:     org_classpath_icedtea_pulseaudio_Stream
+ * Method:    native_pa_stream_get_context
+ * Signature: ()[B
+ */
+JNIEXPORT jbyteArray JNICALL Java_org_classpath_icedtea_pulseaudio_Stream_native_1pa_1stream_1get_1context
+(JNIEnv* env, jobject obj) {
+
+	pa_stream* stream = (pa_stream*) getJavaPointer(env, obj, STREAM_POINTER);
+	assert(stream);
+	pa_context* context = pa_stream_get_context(stream);
+	assert(context);
+	return convertNativePointerToJava(env, context);
+}
+
+/*
+ * Class:     org_classpath_icedtea_pulseaudio_Stream
+ * Method:    native_pa_stream_get_index
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_org_classpath_icedtea_pulseaudio_Stream_native_1pa_1stream_1get_1index
+(JNIEnv* env, jobject obj) {
+	pa_stream* stream = (pa_stream*) getJavaPointer(env, obj, STREAM_POINTER);
+	assert(stream);
+	return pa_stream_get_index(stream);
+}
+
+/*
+ * Class:     org_classpath_icedtea_pulseaudio_Stream
+ * Method:    native_pa_stream_get_device_index
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_org_classpath_icedtea_pulseaudio_Stream_native_1pa_1stream_1get_1device_1index
+(JNIEnv* env, jobject obj) {
+	pa_stream* stream = (pa_stream*) getJavaPointer(env, obj, STREAM_POINTER);
+	assert(stream);
+	return pa_stream_get_device_index(stream);
+}
+
+/*
+ * Class:     org_classpath_icedtea_pulseaudio_Stream
+ * Method:    native_pa_stream_get_device_name
+ * Signature: ()Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_classpath_icedtea_pulseaudio_Stream_native_1pa_1stream_1get_1device_1name
+(JNIEnv* env, jobject obj) {
+	pa_stream* stream = (pa_stream*)getJavaPointer(env, obj, STREAM_POINTER);
+	assert(stream);
+	const char* name = pa_stream_get_device_name(stream);
+	assert(name);
+	return (*env)->NewStringUTF(env, name);
+}
+
+/*
+ * Class:     org_classpath_icedtea_pulseaudio_Stream
+ * Method:    native_pa_stream_is_suspended
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_org_classpath_icedtea_pulseaudio_Stream_native_1pa_1stream_1is_1suspended
+(JNIEnv* env, jobject obj) {
+	pa_stream* stream = (pa_stream*) getJavaPointer(env, obj, STREAM_POINTER);
+	assert(stream);
+	return pa_stream_is_suspended(stream);
+}
+
+/*
+ * Class:     org_classpath_icedtea_pulseaudio_Stream
+ * Method:    native_pa_stream_connect_playback
+ * Signature: (Ljava/lang/String;IIIIII[B[B)I
+ */
+JNIEXPORT jint JNICALL Java_org_classpath_icedtea_pulseaudio_Stream_native_1pa_1stream_1connect_1playback
+(JNIEnv* env, jobject obj, jstring device, jint bufferMaxLength,
+		jint bufferTargetLength, jint bufferPreBuffering,
+		jint bufferMinimumRequest, jint bufferFragmentSize, jint flags,
+		jbyteArray volumePointer, jbyteArray sync_streamPointer) {
+
+	pa_stream *sync_stream;
+	if(sync_streamPointer != NULL) {
+		sync_stream = convertJavaPointerToNative(env, sync_streamPointer);
+		printf("Master stream is %p\n", sync_stream);
+	} else {
+		sync_stream = NULL;
+	}
+
+	pa_stream* stream = (pa_stream*) getJavaPointer(env, obj, STREAM_POINTER);
+
+	pa_buffer_attr buffer_attr;
+
+	memset(&buffer_attr, 0, sizeof(buffer_attr));
+
+	buffer_attr.maxlength = (uint32_t) bufferMaxLength;
+	buffer_attr.tlength = (uint32_t) bufferTargetLength;
+	buffer_attr.prebuf = (uint32_t) bufferPreBuffering;
+	buffer_attr.minreq = (uint32_t) bufferMinimumRequest;
+
+	/*
+	 printf("buffer maxlength: %u\n", buffer_attr.maxlength);
+	 printf("buffer tlength: %u\n", buffer_attr.tlength);
+	 printf("buffer prebuf: %u\n", buffer_attr.prebuf);
+	 printf("buffer minreq: %u\n", buffer_attr.minreq);
+	 printf("buffer fragsize: %u\n", buffer_attr.fragsize);
+	 */
+
+	const char* dev = NULL;
+	if (device != NULL) {
+		dev = (*env)->GetStringUTFChars(env, device, NULL);
+		if (dev == NULL) {
+			return -1; // oome thrown
+		}
+	}
+	/* Set flags to 0 to fix problem with draining before calling start, might need to
+	 be changed back to PA_STREAM_START_CORKED in the future, if we'll be able to implement
+	 synchronization*/
+	int value = pa_stream_connect_playback(stream, dev, &buffer_attr, PA_STREAM_START_CORKED, NULL, sync_stream);
+
+	if (dev != NULL) {
+		(*env)->ReleaseStringUTFChars(env, device, dev);
+		dev = NULL;
+	}
+	return value;
+}
+
+/*
+ * Class:     org_classpath_icedtea_pulseaudio_Stream
+ * Method:    native_pa_stream_connect_record
+ * Signature: (Ljava/lang/String;IIIIII[B[B)I
+ */
+JNIEXPORT jint JNICALL Java_org_classpath_icedtea_pulseaudio_Stream_native_1pa_1stream_1connect_1record
+(JNIEnv* env, jobject obj, jstring device, jint bufferMaxLength,
+		jint bufferTargetLength, jint bufferPreBuffereing,
+		jint bufferMinimumRequest, jint bufferFragmentSize, jint flags,
+		jbyteArray volumePointer, jbyteArray sync_streamPointer) {
+
+	pa_stream* stream = (pa_stream*)getJavaPointer(env, obj, STREAM_POINTER);
+	assert(stream);
+
+	pa_buffer_attr buffer_attr;
+	memset(&buffer_attr, 0 , sizeof(buffer_attr));
+	buffer_attr.maxlength = (uint32_t) bufferMaxLength;
+	buffer_attr.fragsize = (uint32_t) bufferFragmentSize;
+
+	/*
+	 printf("buffer maxlength: %u\n", buffer_attr.maxlength);
+	 printf("buffer tlength: %u\n", buffer_attr.tlength);
+	 printf("buffer prebuf: %u\n", buffer_attr.prebuf);
+	 printf("buffer minreq: %u\n", buffer_attr.minreq);
+	 printf("buffer fragsize: %u\n", buffer_attr.fragsize);
+	 */
+
+	const char* dev = NULL;
+	if (device != NULL) {
+		dev = (*env)->GetStringUTFChars(env, device, NULL);
+		if (dev == NULL) {
+			return -1; // oome thrown
+		}
+	}
+
+	int value = pa_stream_connect_record(stream, dev, &buffer_attr, flags);
+
+	if (dev != NULL) {
+		(*env)->ReleaseStringUTFChars(env, device, dev);
+		dev = NULL;
+	}
+	return value;
+
+}
+
+/*
+ * Class:     org_classpath_icedtea_pulseaudio_Stream
+ * Method:    native_pa_stream_disconnect
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_org_classpath_icedtea_pulseaudio_Stream_native_1pa_1stream_1disconnect
+(JNIEnv* env, jobject obj) {
+	pa_stream* stream = (pa_stream*) getJavaPointer(env, obj, STREAM_POINTER);
+	assert(stream);
+	int return_value = pa_stream_disconnect(stream);
+
+	return return_value;
+}
+
+/*
+ * Class:     org_classpath_icedtea_pulseaudio_Stream
+ * Method:    native_pa_stream_write
+ * Signature: ([BI)I
+ */
+JNIEXPORT jint JNICALL Java_org_classpath_icedtea_pulseaudio_Stream_native_1pa_1stream_1write
+(JNIEnv* env, jobject obj, jbyteArray data, jint offset, jint data_length) {
+	pa_stream* stream = (pa_stream*)getJavaPointer(env, obj, STREAM_POINTER);
+	assert(stream);
+	jbyte* data_buffer = (*env)->GetByteArrayElements(env, data, NULL);
+	if (data_buffer == NULL) {
+		return -1; // oome thrown
+	}
+	jbyte* buffer_start = data_buffer + offset;
+	int value = pa_stream_write(stream, buffer_start, data_length, NULL, 0, PA_SEEK_RELATIVE);
+	(*env)->ReleaseByteArrayElements(env, data, data_buffer, 0);
+	return value;
+}
+
+/*
+ * Class:     org_classpath_icedtea_pulseaudio_Stream
+ * Method:    native_pa_stream_peek
+ * Signature: ()[B
+ */
+JNIEXPORT jbyteArray JNICALL Java_org_classpath_icedtea_pulseaudio_Stream_native_1pa_1stream_1peek
+(JNIEnv* env, jobject obj) {
+
+	pa_stream* stream = (pa_stream*)getJavaPointer(env, obj, STREAM_POINTER);
+	assert(stream);
+	const void* startLocation;
+	size_t count;
+
+	if ( pa_stream_peek(stream, &startLocation, &count) < 0 ) {
+		return NULL;
+	}
+
+	/* no data available */
+	if (startLocation == NULL) {
+		return NULL;
+	}
+
+	jsize length = count;
+	jbyteArray data = (*env)->NewByteArray(env, length);
+
+	if ( data == NULL) {
+		return NULL; // oome thrown
+	}
+
+	(*env)->SetByteArrayRegion(env, data, 0, count, startLocation);
+	return data;
+}
+
+/*
+ * Class:     org_classpath_icedtea_pulseaudio_Stream
+ * Method:    native_pa_stream_drop
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_org_classpath_icedtea_pulseaudio_Stream_native_1pa_1stream_1drop
+(JNIEnv* env, jobject obj) {
+	pa_stream* stream = (pa_stream*) getJavaPointer(env, obj, STREAM_POINTER);
+	assert(stream);
+	return pa_stream_drop(stream);
+}
+
+/*
+ * Class:     org_classpath_icedtea_pulseaudio_Stream
+ * Method:    native_pa_stream_writable_size
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_org_classpath_icedtea_pulseaudio_Stream_native_1pa_1stream_1writable_1size
+(JNIEnv* env, jobject obj) {
+	pa_stream* stream = (pa_stream*)getJavaPointer(env, obj, STREAM_POINTER);
+	if(!stream) {
+		return 0;
+	}
+	size_t size = pa_stream_writable_size(stream);
+	return size;
+
+}
+
+/*
+ * Class:     org_classpath_icedtea_pulseaudio_Stream
+ * Method:    native_pa_stream_readable_size
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_org_classpath_icedtea_pulseaudio_Stream_native_1pa_1stream_1readable_1size
+(JNIEnv* env, jobject obj) {
+	pa_stream* stream = (pa_stream*)getJavaPointer(env, obj, STREAM_POINTER);
+	assert(stream);
+	return pa_stream_readable_size(stream);
+}
+
+static void drain_callback(pa_stream* stream, int success, void* userdata) {
+
+	assert(stream);
+	JNIEnv* env = pulse_thread_env;
+	assert(env);
+
+	notifyWaitingOperations(env);
+
+	if (success == 0) {
+		throwByName(env, ILLEGAL_STATE_EXCEPTION, "drain failed");
+	}
+
+}
+
+/*
+ * Class:     org_classpath_icedtea_pulseaudio_Stream
+ * Method:    native_pa_stream_drain
+ * Signature: ()[B
+ */
+JNIEXPORT jbyteArray JNICALL Java_org_classpath_icedtea_pulseaudio_Stream_native_1pa_1stream_1drain
+(JNIEnv* env, jobject obj) {
+	pa_stream* stream = (pa_stream*)getJavaPointer(env, obj, STREAM_POINTER);
+	assert(stream);
+	pa_operation* operation = pa_stream_drain(stream, drain_callback, NULL);
+	assert(operation);
+	return convertNativePointerToJava(env, operation);
+}
+
+/*
+ * Class:     org_classpath_icedtea_pulseaudio_Stream
+ * Method:    native_pa_stream_is_corked
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_org_classpath_icedtea_pulseaudio_Stream_native_1pa_1stream_1is_1corked
+(JNIEnv* env, jobject obj) {
+	pa_stream* stream = (pa_stream*) getJavaPointer(env, obj, STREAM_POINTER);
+	assert(stream);
+	return pa_stream_is_corked(stream);
+}
+
+static void cork_callback(pa_stream* stream, int success, void* userdata) {
+
+	java_context* context = userdata;
+	assert(stream);
+	assert(context);
+	JNIEnv* env = pulse_thread_env;
+	assert(env);
+	notifyWaitingOperations(env);
+
+	if (success == 0) {
+		throwByName(env, ILLEGAL_STATE_EXCEPTION, "cork failed");
+	}
+
+}
+
+/*
+ * Class:     org_classpath_icedtea_pulseaudio_Stream
+ * Method:    native_pa_stream_cork
+ * Signature: (I)[B
+ */
+JNIEXPORT jbyteArray JNICALL Java_org_classpath_icedtea_pulseaudio_Stream_native_1pa_1stream_1cork
+(JNIEnv* env, jobject obj, jint yes) {
+	pa_stream* stream = (pa_stream*)getJavaPointer(env, obj, STREAM_POINTER);
+	assert(stream);
+	java_context* j_context = malloc(sizeof(java_context));
+	assert(j_context);
+	j_context->env = env;
+	j_context->obj = (*env)->NewGlobalRef(env, obj);
+	pa_operation* operation = pa_stream_cork(stream, yes, cork_callback, j_context);
+	assert(operation);
+	return convertNativePointerToJava(env, operation);
+}
+
+static void flush_callback(pa_stream* stream, int success, void* userdata) {
+	assert(stream);
+	JNIEnv* env = pulse_thread_env;
+	assert(env);
+	notifyWaitingOperations(env);
+
+	if (success == 0) {
+		throwByName(env, ILLEGAL_STATE_EXCEPTION, "flush failed");
+	}
+
+}
+
+/*
+ * Class:     org_classpath_icedtea_pulseaudio_Stream
+ * Method:    native_pa_stream_flush
+ * Signature: ()[B
+ */
+JNIEXPORT jbyteArray JNICALL Java_org_classpath_icedtea_pulseaudio_Stream_native_1pa_1stream_1flush
+(JNIEnv* env, jobject obj) {
+	pa_stream* stream = (pa_stream*) getJavaPointer(env, obj, STREAM_POINTER);
+	assert(stream);
+	pa_operation* operation = pa_stream_flush(stream, flush_callback, NULL);
+	assert(operation);
+	return convertNativePointerToJava(env, operation);
+}
+
+static void trigger_callback(pa_stream* stream, int success, void* userdata) {
+	assert(stream);
+	JNIEnv* env = pulse_thread_env;
+	assert(env);
+	notifyWaitingOperations(env);
+
+	if (success == 0) {
+		throwByName(env, ILLEGAL_STATE_EXCEPTION, "trigger failed");
+	}
+
+}
+
+/*
+ * Class:     org_classpath_icedtea_pulseaudio_Stream
+ * Method:    native_pa_stream_trigger
+ * Signature: ()[B
+ */
+JNIEXPORT jbyteArray JNICALL Java_org_classpath_icedtea_pulseaudio_Stream_native_1pa_1stream_1trigger
+(JNIEnv* env, jobject obj) {
+	pa_stream* stream = (pa_stream*)getJavaPointer(env, obj, STREAM_POINTER);
+	assert(stream);
+	pa_operation* operation = pa_stream_trigger(stream, trigger_callback, NULL);
+	assert(operation);
+	return convertNativePointerToJava(env, operation);
+}
+
+static void set_name_callback(pa_stream* stream, int success, void* userdata) {
+	assert(stream);
+	JNIEnv* env = pulse_thread_env;
+	notifyWaitingOperations(env);
+
+	if (success == 0) {
+		throwByName(env, ILLEGAL_STATE_EXCEPTION, "set_name failed");
+	}
+}
+
+/*
+ * Class:     org_classpath_icedtea_pulseaudio_Stream
+ * Method:    native_pa_stream_set_name
+ * Signature: (Ljava/lang/String;)[B
+ */
+JNIEXPORT jbyteArray JNICALL Java_org_classpath_icedtea_pulseaudio_Stream_native_1pa_1stream_1set_1name
+(JNIEnv* env, jobject obj, jstring newName) {
+	pa_stream* stream = (pa_stream*)getJavaPointer(env, obj, STREAM_POINTER);
+	assert(stream);
+
+	const char* name;
+	name = (*env)->GetStringUTFChars(env, newName, NULL);
+	if (name == NULL) {
+		return 0; // OutOfMemoryError already thrown
+	}
+
+	pa_operation* operation = pa_stream_set_name(stream, name, set_name_callback, NULL);
+	assert(operation);
+	(*env)->ReleaseStringUTFChars(env, newName, name);
+
+	return convertNativePointerToJava(env, operation);
+}
+
+/*
+ * Class:     org_classpath_icedtea_pulseaudio_Stream
+ * Method:    native_pa_stream_get_time
+ * Signature: ()J
+ */
+JNIEXPORT jlong JNICALL Java_org_classpath_icedtea_pulseaudio_Stream_native_1pa_1stream_1get_1time
+(JNIEnv* env, jobject obj) {
+	pa_stream* stream = (pa_stream*)getJavaPointer(env, obj, STREAM_POINTER);
+	assert(stream);
+
+	pa_usec_t time = 0;
+	int result = pa_stream_get_time (stream,&time);
+	assert(result == 0);
+
+	return time;
+
+}
+
+/*
+ * Class:     org_classpath_icedtea_pulseaudio_Stream
+ * Method:    native_pa_stream_get_latency
+ * Signature: ()J
+ */
+JNIEXPORT jlong JNICALL Java_org_classpath_icedtea_pulseaudio_Stream_native_1pa_1stream_1get_1latency
+(JNIEnv* env, jobject obj) {
+	pa_stream* stream = (pa_stream*)getJavaPointer(env, obj, STREAM_POINTER);
+	assert(stream);
+	pa_usec_t returnValue = 0;
+	int negative = 0;
+	int result = pa_stream_get_latency ( stream, &returnValue, &negative);
+	assert(result == 0);
+	assert(negative == 0);
+	return returnValue;
+}
+
+/*
+ * Class:     org_classpath_icedtea_pulseaudio_Stream
+ * Method:    native_pa_stream_get_sample_spec
+ * Signature: ()Lorg/classpath/icedtea/pulseaudio/StreamSampleSpecification;
+ */
+JNIEXPORT jobject JNICALL Java_org_classpath_icedtea_pulseaudio_Stream_native_1pa_1stream_1get_1sample_1spec
+(JNIEnv* env, jobject obj) {
+	pa_stream* stream = (pa_stream*)getJavaPointer(env, obj, STREAM_POINTER);
+	assert(stream);
+
+	const pa_sample_spec* sample_spec = pa_stream_get_sample_spec(stream);
+	assert(sample_spec);
+
+	char* name = "Lorg/classpath/icedtea/pulseaudio/StreamSampleSpecification;";
+	jclass cls = (*env)->FindClass(env, name);
+	assert(cls);
+	jmethodID constructor_mid = (*env)->GetMethodID(env, cls, "<init>", "V");
+	assert(constructor_mid);
+
+	const char* formatString = getStringFromFormat(sample_spec->format);
+	assert(formatString);
+	int rate = sample_spec->rate;
+	int channels = sample_spec->channels;
+
+	jstring format = (*env)->NewStringUTF(env, formatString);
+	if ( format == NULL) {
+		return NULL; // oome
+	}
+	jobject return_object = (*env)->NewObject(env, cls, constructor_mid, format, rate, channels);
+
+	return return_object;
+}
+
+/*
+ * Class:     org_classpath_icedtea_pulseaudio_Stream
+ * Method:    native_pa_stream_get_buffer_attr
+ * Signature: ()Lorg/classpath/icedtea/pulseaudio/StreamBufferAttributes;
+ */
+JNIEXPORT jobject JNICALL Java_org_classpath_icedtea_pulseaudio_Stream_native_1pa_1stream_1get_1buffer_1attr
+(JNIEnv* env, jobject obj) {
+
+	//	printf("in native_pa_stream_get_buffer_attributes");
+
+	pa_stream* stream = (pa_stream*)getJavaPointer(env, obj, STREAM_POINTER);
+	assert(stream);
+	const pa_buffer_attr* buffer = pa_stream_get_buffer_attr(stream);
+	assert(buffer);
+
+	const char* class_name = "Lorg/classpath/icedtea/pulseaudio/StreamBufferAttributes;";
+	jclass cls = (*env)->FindClass(env, class_name);
+	assert(cls);
+	jmethodID constructor_mid = (*env)->GetMethodID(env, cls, "<init>", "(IIIII)V");
+	assert(constructor_mid);
+	jint maxLength = buffer->maxlength;
+	jint targetLength = buffer->tlength;
+	jint preBuffering = buffer->prebuf;
+	jint minimumRequest = buffer->minreq;
+	jint fragmentSize = buffer->fragsize;
+
+	jobject return_object = (*env)->NewObject(env, cls, constructor_mid, maxLength, targetLength,
+			preBuffering, minimumRequest, fragmentSize);
+
+	return return_object;
+}
+
+static void set_buffer_attr_callback(pa_stream* stream, int success,
+		void* userdata) {
+
+	assert(stream);
+	JNIEnv* env = pulse_thread_env;
+	assert(env);
+	notifyWaitingOperations(env);
+
+	if (success == 0) {
+		throwByName(env, ILLEGAL_STATE_EXCEPTION, "set_buffer_attr failed");
+	}
+}
+
+/*
+ * Class:     org_classpath_icedtea_pulseaudio_Stream
+ * Method:    native_pa_stream_set_buffer_attr
+ * Signature: (Lorg/classpath/icedtea/pulseaudio/StreamBufferAttributes;)[B
+ */
+JNIEXPORT jbyteArray JNICALL Java_org_classpath_icedtea_pulseaudio_Stream_native_1pa_1stream_1set_1buffer_1attr
+(JNIEnv* env, jobject obj, jobject bufferAttributeObject) {
+
+	pa_stream* stream = (pa_stream*)getJavaPointer(env, obj, STREAM_POINTER);
+	assert(stream);
+
+	jclass cls = (*env)->GetObjectClass(env, bufferAttributeObject);
+	assert(cls);
+
+	pa_buffer_attr buffer;
+
+	jmethodID getMaxLengthID = (*env)->GetMethodID(env,cls,"getMaxLength","()I");
+	assert(getMaxLengthID);
+	buffer.maxlength = (uint32_t) (*env)->CallIntMethod(env, bufferAttributeObject, getMaxLengthID);
+
+	jmethodID getTargetLengthID = (*env)->GetMethodID(env,cls,"getTargetLength","()I");
+	assert(getTargetLengthID);
+	buffer.tlength = (uint32_t) (*env)->CallIntMethod(env, bufferAttributeObject, getTargetLengthID);
+
+	jmethodID getPreBufferingID = (*env)->GetMethodID(env,cls,"getPreBuffering","()I");
+	assert(getPreBufferingID);
+	buffer.prebuf = (uint32_t) (*env)->CallIntMethod(env, bufferAttributeObject, getPreBufferingID);
+
+	jmethodID getMinimumRequestID = (*env)->GetMethodID(env, cls, "getMinimumRequest", "()I");
+	assert(getMinimumRequestID);
+	buffer.minreq = (uint32_t) (*env)->CallIntMethod(env, bufferAttributeObject, getMinimumRequestID );
+
+	jmethodID getFragmentSizeID = (*env)->GetMethodID(env,cls,"getFragmentSize","()I");
+	assert(getFragmentSizeID);
+	buffer.fragsize = (uint32_t) (*env)->CallIntMethod(env, bufferAttributeObject, getFragmentSizeID );
+
+	/*
+	 const pa_buffer_attr* old_buffer = pa_stream_get_buffer_attr(stream);
+
+	 printf("old buffer values: %u %u %u %u %u\n", old_buffer->maxlength, old_buffer->tlength, old_buffer->prebuf, old_buffer->minreq, old_buffer->fragsize);
+
+	 printf("want these values: %u %u %u %u %u\n", buffer.maxlength, buffer.tlength, buffer.prebuf, buffer.minreq, buffer.fragsize);
+	 */
+
+	pa_operation* operation = pa_stream_set_buffer_attr(stream, &buffer, set_buffer_attr_callback, NULL);
+
+	assert(operation);
+	return convertNativePointerToJava(env,operation);
+}
+
+static void update_sample_rate_callback(pa_stream* stream, int success,
+		void* userdata) {
+	assert(stream);
+	JNIEnv* env = pulse_thread_env;
+	assert(env);
+	notifyWaitingOperations(env);
+
+	if (success == 0) {
+		throwByName(env, ILLEGAL_STATE_EXCEPTION, "update_sampl_rate failed");
+	}
+
+}
+/*
+ * Class:     org_classpath_icedtea_pulseaudio_Stream
+ * Method:    native_pa_stream_update_sample_rate
+ * Signature: (I)[B
+ */
+JNIEXPORT jbyteArray JNICALL Java_org_classpath_icedtea_pulseaudio_Stream_native_1pa_1stream_1update_1sample_1rate
+(JNIEnv* env, jobject obj, jint newRate) {
+
+	uint32_t rate = (uint32_t) newRate;
+
+	pa_stream* stream = (pa_stream*)getJavaPointer(env, obj, STREAM_POINTER);
+	assert(stream);
+	pa_operation* operation = pa_stream_update_sample_rate(stream,rate, update_sample_rate_callback, NULL);
+	assert(operation);
+	return convertNativePointerToJava(env, operation);
+
+}
+
+/*
+ * Class:     org_classpath_icedtea_pulseaudio_Stream
+ * Method:    native_setVolume
+ * Signature: (F)[B
+ */
+JNIEXPORT jbyteArray JNICALL Java_org_classpath_icedtea_pulseaudio_Stream_native_1setVolume
+(JNIEnv *env, jobject obj, jfloat new_volume) {
+	
+	pa_stream *stream = getJavaPointer(env, obj, STREAM_POINTER);
+	assert(stream);
+	pa_context *context = pa_stream_get_context(stream);
+	assert(context);
+
+	int stream_id = pa_stream_get_index(stream);
+	int channels = pa_stream_get_sample_spec(stream)->channels;
+	pa_cvolume cv;
+
+	pa_operation* o = pa_context_set_sink_input_volume(context, stream_id, pa_cvolume_set(&cv, channels, new_volume), set_sink_input_volume_callback, NULL);
+	assert(o);
+
+	return convertNativePointerToJava(env, o);
+
+}
+
+JNIEXPORT jint JNICALL Java_org_classpath_icedtea_pulseaudio_Stream_bytesInBuffer(JNIEnv *env, jobject obj) {
+	pa_stream *stream = getJavaPointer(env, obj, STREAM_POINTER);
+	assert(stream);
+	const pa_timing_info *timing_info = pa_stream_get_timing_info(stream);
+	int write_index = timing_info->write_index;
+	int read_index = timing_info->read_index;
+	return write_index - read_index;
+}
+
+JNIEXPORT jbyteArray JNICALL Java_org_classpath_icedtea_pulseaudio_Stream_native_1pa_1stream_1updateTimingInfo(JNIEnv* env, jobject obj) {
+	pa_stream *stream = getJavaPointer(env, obj, STREAM_POINTER);
+	assert(stream);
+	pa_operation* o = pa_stream_update_timing_info(stream, update_timing_info_callback, NULL); 
+	assert(o);
+	return convertNativePointerToJava(env, o);
+
+}
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pulseaudio/testsounds/README	Fri Oct 10 16:03:12 2008 -0400
@@ -0,0 +1,4 @@
+The sound files (startup.wav and logout.wav) were taken from the KDE Project 
+(www.kde.org). A copy of them can be obtained from 
+http://websvn.kde.org/branches/KDE/4.0/kdeartwork/sounds/ . They are licensed 
+by the copyright holders as GPLv2.
\ No newline at end of file
Binary file pulseaudio/testsounds/error.wav has changed
Binary file pulseaudio/testsounds/logout.wav has changed
Binary file pulseaudio/testsounds/startup.wav has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pulseaudio/unittests/org/classpath/icedtea/pulseaudio/OtherSoundProvidersAvailableTest.java	Fri Oct 10 16:03:12 2008 -0400
@@ -0,0 +1,114 @@
+/* OtherSoundProvidersAvailableTest.java
+   Copyright (C) 2008 Red Hat, Inc.
+
+This file is part of IcedTea.
+
+IcedTea is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License as published by
+the Free Software Foundation, version 2.
+
+IcedTea is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with IcedTea; see the file COPYING.  If not, write to
+the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library.  Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module.  An independent module is a module which is not derived from
+or based on this library.  If you modify this library, you may extend
+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;
+
+import javax.sound.sampled.AudioSystem;
+import javax.sound.sampled.Line;
+import javax.sound.sampled.LineUnavailableException;
+import javax.sound.sampled.Mixer;
+import javax.sound.sampled.SourceDataLine;
+
+import org.junit.Test;
+
+public class OtherSoundProvidersAvailableTest {
+
+	@Test
+	public void testOtherSoundProviders() {
+		System.out.println("This tests if alsa mixers are still available");
+
+		Mixer.Info mixerInfos[] = AudioSystem.getMixerInfo();
+		Mixer.Info selectedMixerInfo = null;
+		Mixer mixer;
+
+		boolean selected = false;
+		int i = 0;
+		System.out.println("Available Mixers:");
+		// use 0,0 or the default
+		for (Mixer.Info info : mixerInfos) {
+			System.out.println("Mixer Line " + i++ + ": " + info.getName()
+					+ " " + info.getDescription());
+			if (info.getName().contains("0,0") && !selected) {
+				System.out.println("^ selecting as the mixer to use");
+				selectedMixerInfo = info;
+				selected = true;
+			}
+		}
+
+		if (selectedMixerInfo != null) {
+			System.out.println(selectedMixerInfo.toString());
+		}
+
+		System.out.print("Selected mixer is of class: ");
+
+		mixer = AudioSystem.getMixer(selectedMixerInfo);
+		System.out.println(mixer.getClass().toString());
+		try {
+			Line.Info sourceDataLineInfo = null;
+
+			mixer.open(); // initialize the mixer
+
+			Line.Info allLineInfo[] = mixer.getSourceLineInfo();
+			System.out.println("Source lines supported by mixer: ");
+			int j = 0;
+			for (Line.Info lineInfo : allLineInfo) {
+				System.out.println("Source Line " + j++ + ": "
+						+ lineInfo.getLineClass());
+				if (lineInfo.toString().contains("SourceDataLine")) {
+					sourceDataLineInfo = lineInfo;
+				}
+
+			}
+
+			if (sourceDataLineInfo == null) {
+				System.out.println("Mixer supports no SourceDataLines");
+			} else {
+				SourceDataLine sourceDataLine = (SourceDataLine) mixer
+						.getLine(sourceDataLineInfo);
+
+				sourceDataLine.open();
+				// sourceDataLine.write('a', 0, 2);
+				sourceDataLine.close();
+			}
+		} catch (LineUnavailableException e) {
+			System.out.println("Line unavailable");
+		} finally {
+			mixer.close();
+		}
+
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pulseaudio/unittests/org/classpath/icedtea/pulseaudio/PulseAudioClipTest.java	Fri Oct 10 16:03:12 2008 -0400
@@ -0,0 +1,623 @@
+/* PulseAudioClipTest.java
+   Copyright (C) 2008 Red Hat, Inc.
+
+This file is part of IcedTea.
+
+IcedTea is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License as published by
+the Free Software Foundation, version 2.
+
+IcedTea is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with IcedTea; see the file COPYING.  If not, write to
+the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library.  Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module.  An independent module is a module which is not derived from
+or based on this library.  If you modify this library, you may extend
+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;
+
+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.Control;
+import javax.sound.sampled.DataLine;
+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;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class PulseAudioClipTest {
+
+	Mixer mixer;
+	AudioFormat aSupportedFormat = new AudioFormat(
+			AudioFormat.Encoding.PCM_UNSIGNED, 44100f, 8, 1, 1, 44100f, true);
+
+	int started = 0;
+	int stopped = 0;
+	int opened = 0;
+	int closed = 0;
+
+	@Before
+	public void setUp() throws LineUnavailableException {
+		Mixer.Info wantedMixerInfo = null;
+		Mixer.Info[] mixerInfos = AudioSystem.getMixerInfo();
+		for (Mixer.Info mixerInfo : mixerInfos) {
+			if (mixerInfo.getName().contains("PulseAudio")) {
+				wantedMixerInfo = mixerInfo;
+				break;
+			}
+		}
+		assert (wantedMixerInfo != null);
+		mixer = AudioSystem.getMixer(wantedMixerInfo);
+		mixer.open();
+
+		started = 0;
+		stopped = 0;
+		opened = 0;
+		closed = 0;
+	}
+
+	@Test
+	public void testObtainingAClip() throws LineUnavailableException {
+		System.out
+				.println("This tests if a clip can be obtained from the mixer");
+		Clip clip = (Clip) mixer.getLine(new Line.Info(Clip.class));
+		Assert.assertNotNull(clip);
+	}
+
+	@Test(expected = IllegalArgumentException.class)
+	public void testClipOpenWrongUse() throws LineUnavailableException {
+		System.out.println("This test checks ");
+		Clip clip = (Clip) mixer.getLine(new Line.Info(Clip.class));
+		clip.open();
+	}
+
+	@Test
+	public void testClipOpens() 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);
+		clip.open(audioInputStream);
+		clip.close();
+	}
+
+	@Test
+	public void testLoop4Times() throws LineUnavailableException, IOException,
+			UnsupportedAudioFileException {
+		System.out
+				.println("This tests loop(4) on the Clip. "
+						+ "You should hear a certain part of the clip play back 5 time");
+
+		Clip clip = (Clip) mixer.getLine(new Line.Info(Clip.class));
+		File soundFile = new File("testsounds/startup.wav");
+		AudioInputStream audioInputStream = AudioSystem
+				.getAudioInputStream(soundFile);
+		clip.open(audioInputStream);
+
+		clip.setLoopPoints((int) (clip.getFrameLength() / 4), (int) (clip
+				.getFrameLength() / 2));
+		clip.loop(4);
+
+		clip.drain();
+
+		clip.stop();
+
+		clip.close();
+	}
+	
+	
+	@Test
+	public void testLoopContinuously() throws LineUnavailableException,
+			IOException, UnsupportedAudioFileException, InterruptedException {
+		System.out
+				.println("This tests loop(LOOP_CONTINUOUSLY) on the Clip");
+		final Clip clip = (Clip) mixer.getLine(new Line.Info(Clip.class));
+		File soundFile = new File("testsounds/error.wav");
+		AudioInputStream audioInputStream = AudioSystem
+				.getAudioInputStream(soundFile);
+		clip.open(audioInputStream);
+
+		clip.setLoopPoints((int) (clip.getFrameLength() / 4), (int) (clip
+				.getFrameLength() / 2));
+		clip.loop(Clip.LOOP_CONTINUOUSLY);
+
+		Runnable blocker = new Runnable() {
+
+			@Override
+			public void run() {
+				clip.drain();
+			}
+
+		};
+
+		Thread th = new Thread(blocker);
+		th.start();
+		th.join(10000);
+
+		if (!th.isAlive()) {
+			clip.close();
+			Assert.fail("LOOP_CONTINUOUSLY doesnt seem to work");
+		}
+		
+		clip.stop();
+		th.join(500);
+		if ( th.isAlive()) {
+			clip.close();
+			Assert.fail("stopping LOOP_CONTINUOSLY failed");
+		}
+		
+		clip.close();
+	}
+
+	@Test
+	public void testIsActiveAndIsOpen() 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);
+
+		Assert.assertFalse(clip.isActive());
+		Assert.assertFalse(clip.isOpen());
+		clip.open(audioInputStream);
+		Assert.assertTrue(clip.isOpen());
+		Assert.assertFalse(clip.isActive());
+		clip.start();
+		Assert.assertTrue(clip.isOpen());
+		Assert.assertTrue(clip.isActive());
+		clip.stop();
+		Assert.assertTrue(clip.isOpen());
+		Assert.assertFalse(clip.isActive());
+		clip.close();
+		Assert.assertFalse(clip.isOpen());
+		Assert.assertFalse(clip.isActive());
+
+	}
+
+	@Test
+	public void testOpenEvent() throws LineUnavailableException,
+			UnsupportedAudioFileException, IOException {
+		System.out
+				.println("This tests the OPEN event. You should see an OPEN on the next line");
+		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) {
+					System.out.println("OPEN");
+					opened++;
+				}
+			}
+
+		};
+
+		clip.addLineListener(openListener);
+		clip.open(audioInputStream);
+		clip.close();
+		clip.removeLineListener(openListener);
+
+		Assert.assertEquals(1, opened);
+
+	}
+
+	@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) {
+					System.out.println("CLOSE");
+					closed++;
+				}
+			}
+
+		};
+
+		clip.addLineListener(closeListener);
+		clip.open(audioInputStream);
+		clip.close();
+
+		clip.removeLineListener(closeListener);
+
+		Assert.assertEquals(1, closed);
+
+	}
+
+	@Test
+	public void testStartedStopped() throws LineUnavailableException,
+			UnsupportedAudioFileException, IOException, InterruptedException {
+
+		File soundFile = new File("testsounds/startup.wav");
+		AudioInputStream audioInputStream = AudioSystem
+				.getAudioInputStream(soundFile);
+		AudioFormat audioFormat = audioInputStream.getFormat();
+
+		Clip clip;
+		clip = (Clip) mixer.getLine(new DataLine.Info(Clip.class, audioFormat));
+		Assert.assertNotNull(clip);
+
+		clip.open(audioInputStream);
+
+		LineListener startStopListener = new LineListener() {
+
+			@Override
+			public void update(LineEvent event) {
+				if (event.getType() == LineEvent.Type.START) {
+					System.out.println("START");
+					started++;
+					Assert.assertEquals(1, started);
+				}
+
+				if (event.getType() == LineEvent.Type.STOP) {
+					System.out.println("STOP");
+					stopped++;
+					Assert.assertEquals(1, stopped);
+				}
+			}
+
+		};
+
+		clip.addLineListener(startStopListener);
+
+		clip.start();
+		clip.drain();
+		clip.stop();
+		clip.close();
+
+		Assert.assertEquals(1, started);
+		Assert.assertEquals(1, stopped);
+
+	}
+
+	@Test(timeout = 1000)
+	public void testDrainWithoutStart() throws UnsupportedAudioFileException,
+			IOException, LineUnavailableException {
+
+		File soundFile = new File("testsounds/startup.wav");
+		AudioInputStream audioInputStream = AudioSystem
+				.getAudioInputStream(soundFile);
+		AudioFormat audioFormat = audioInputStream.getFormat();
+
+		Clip clip;
+		clip = (Clip) mixer.getLine(new DataLine.Info(Clip.class, audioFormat));
+		Assert.assertNotNull(clip);
+
+		clip.open(audioInputStream);
+		clip.drain();
+		clip.close();
+
+	}
+
+	@Test
+	public void testDrainBlocksWhilePlaying()
+			throws UnsupportedAudioFileException, IOException,
+			LineUnavailableException {
+
+		String fileName = "testsounds/startup.wav";
+		File soundFile = new File(fileName);
+		AudioInputStream audioInputStream = AudioSystem
+				.getAudioInputStream(soundFile);
+		AudioFormat audioFormat = audioInputStream.getFormat();
+
+		Clip clip;
+		clip = (Clip) mixer.getLine(new DataLine.Info(Clip.class, audioFormat));
+		Assert.assertNotNull(clip);
+
+		long startTime = System.currentTimeMillis();
+
+		clip.open(audioInputStream);
+		clip.start();
+		clip.drain();
+		clip.stop();
+		clip.close();
+
+		long endTime = System.currentTimeMillis();
+
+		Assert.assertTrue(endTime - startTime > 3000);
+		System.out.println("Playback of " + fileName + " completed in "
+				+ (endTime - startTime) + " milliseconds");
+	}
+
+	@Test
+	public void testLoop0InterruptsPlayback() throws LineUnavailableException,
+			IOException, UnsupportedAudioFileException {
+		Clip clip = (Clip) mixer.getLine(new Line.Info(Clip.class));
+		File soundFile = new File("testsounds/startup.wav");
+		AudioInputStream audioInputStream = AudioSystem
+				.getAudioInputStream(soundFile);
+		clip.open(audioInputStream);
+
+		clip.setLoopPoints((int) (clip.getFrameLength() / 4), (int) (clip
+				.getFrameLength() / 2));
+		clip.loop(4);
+		try {
+			Thread.sleep(2000);
+		} catch (InterruptedException e) {
+			e.printStackTrace();
+		}
+		clip.loop(0);
+		clip.close();
+	}
+
+	@Test(expected = IllegalArgumentException.class)
+	public void testOpenWrongUse() throws LineUnavailableException {
+		Clip clip = (Clip) mixer.getLine(new Line.Info(Clip.class));
+		clip.open();
+
+	}
+
+	/*
+	 * 
+	 * modified version of the sample code at
+	 * http://icedtea.classpath.org/bugzilla/show_bug.cgi?id=173
+	 * 
+	 */
+
+	@Test
+	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);
+
+		clip1.start();
+		clip2.start();
+
+		clip1.drain();
+		clip2.drain();
+
+		clip1.close();
+		clip2.close();
+
+		Assert.assertFalse(clip1.isOpen());
+		Assert.assertFalse(clip2.isOpen());
+
+	}
+
+	@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);
+		}
+
+		clip.close();
+	}
+
+	@Test
+	public void testFramePositionBeforeClose() throws LineUnavailableException,
+			UnsupportedAudioFileException, IOException {
+		System.out
+				.println("This tests if the Clip provides the correct frame position");
+		Clip clip = (Clip) mixer.getLine(new Line.Info(Clip.class));
+		String fileName = "testsounds/logout.wav";
+		File soundFile1 = new File(fileName);
+		AudioInputStream audioInputStream1 = AudioSystem
+				.getAudioInputStream(soundFile1);
+		clip.open(audioInputStream1);
+
+		clip.start();
+
+		clip.drain();
+
+		long pos = clip.getFramePosition();
+
+		clip.close();
+
+		long expected = 136703;
+		long granularity = 5;
+		System.out.println("Frames in " + fileName + ": " + expected);
+		System.out.println("Frame position in clip :" + pos);
+		Assert.assertTrue("Expected: " + expected + " got " + pos, Math
+				.abs(expected - pos) < granularity);
+
+	}
+
+	@Test
+	public void testFramePositionWithStartStop()
+			throws LineUnavailableException, UnsupportedAudioFileException,
+			IOException, InterruptedException {
+
+		System.out
+				.println("This tests if the Clip provides the correct frame position");
+		Clip clip = (Clip) mixer.getLine(new Line.Info(Clip.class));
+		String fileName = "testsounds/logout.wav";
+		File soundFile1 = new File(fileName);
+		AudioInputStream audioInputStream1 = AudioSystem
+				.getAudioInputStream(soundFile1);
+		clip.open(audioInputStream1);
+
+		clip.start();
+
+		Thread.sleep(500);
+		clip.stop();
+		Thread.sleep(5000);
+		clip.start();
+
+		clip.drain();
+
+		long pos = clip.getFramePosition();
+
+		clip.close();
+
+		long expected = 136703;
+		long granularity = 5;
+		System.out.println("Frames in " + fileName + ": " + expected);
+		System.out.println("Frame position in clip :" + pos);
+		Assert.assertTrue("Expected: " + expected + " got " + pos, Math
+				.abs(expected - pos) < granularity);
+
+	}
+
+	@Test
+	public void testFramePositionWithLoop() throws LineUnavailableException,
+			UnsupportedAudioFileException, IOException, InterruptedException {
+		System.out.println("This tests if the Clip provides the correct frame "
+				+ "position with a bit of looping in the clip");
+		Clip clip = (Clip) mixer.getLine(new Line.Info(Clip.class));
+		String fileName = "testsounds/logout.wav";
+		File soundFile1 = new File(fileName);
+		AudioInputStream audioInputStream1 = AudioSystem
+				.getAudioInputStream(soundFile1);
+		clip.open(audioInputStream1);
+
+		clip.setLoopPoints(0, -1);
+		clip.loop(1);
+		Thread.sleep(500);
+		clip.stop();
+		Thread.sleep(2000);
+		clip.start();
+		Thread.sleep(100);
+		clip.stop();
+		Thread.sleep(4000);
+		clip.start();
+		Thread.sleep(100);
+		clip.stop();
+		Thread.sleep(2000);
+		clip.start();
+		Thread.sleep(100);
+		clip.stop();
+		Thread.sleep(3000);
+		clip.start();
+
+		clip.drain();
+
+		long pos = clip.getFramePosition();
+
+		clip.close();
+
+		long expected = 136703 * 1;
+		long granularity = 5;
+		System.out.println("Frames in " + fileName + ": " + expected);
+		System.out.println("Frame position in clip :" + pos);
+		Assert.assertTrue("Expected: " + expected + " got " + pos, Math
+				.abs(expected - pos) < granularity);
+
+	}
+
+	@Test
+	public void testFramePositionAfterLooping()
+			throws LineUnavailableException, UnsupportedAudioFileException,
+			IOException {
+		System.out
+				.println("This tests if the Clip provides the correct frame position");
+		Clip clip = (Clip) mixer.getLine(new Line.Info(Clip.class));
+		String fileName = "testsounds/logout.wav";
+		File soundFile1 = new File(fileName);
+		AudioInputStream audioInputStream1 = AudioSystem
+				.getAudioInputStream(soundFile1);
+		clip.open(audioInputStream1);
+
+		clip.setLoopPoints(0, -1);
+		clip.loop(1);
+
+		clip.drain();
+
+		long pos = clip.getFramePosition();
+
+		clip.close();
+
+		long expected = 136703 * 2;
+		long granularity = 5;
+		System.out.println("Frames in " + fileName + ": " + expected);
+		System.out.println("Frame position in clip :" + pos);
+		Assert.assertTrue("Expected: " + expected + " got " + pos, Math
+				.abs(expected - pos) < granularity);
+
+	}
+
+	@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);
+
+	}
+
+	@After
+	public void tearDown() {
+		mixer.close();
+
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pulseaudio/unittests/org/classpath/icedtea/pulseaudio/PulseAudioEventLoopOverhead.java	Fri Oct 10 16:03:12 2008 -0400
@@ -0,0 +1,100 @@
+/* PulseAudioEventLoopOverhead.java
+   Copyright (C) 2008 Red Hat, Inc.
+
+This file is part of IcedTea.
+
+IcedTea is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License as published by
+the Free Software Foundation, version 2.
+
+IcedTea is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with IcedTea; see the file COPYING.  If not, write to
+the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library.  Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module.  An independent module is a module which is not derived from
+or based on this library.  If you modify this library, you may extend
+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;
+
+import static org.junit.Assert.assertNotNull;
+
+import javax.sound.sampled.AudioSystem;
+import javax.sound.sampled.LineUnavailableException;
+import javax.sound.sampled.Mixer;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class PulseAudioEventLoopOverhead {
+
+	Mixer mixer;
+
+	@Before
+	public void setUp() {
+		Mixer.Info mixerInfos[] = AudioSystem.getMixerInfo();
+		Mixer.Info selectedMixerInfo = null;
+		// int i = 0;
+		for (Mixer.Info info : mixerInfos) {
+			// System.out.println("Mixer Line " + i++ + ": " + info.getName() +
+			// " " + info.getDescription());
+			if (info.getName().contains("PulseAudio")) {
+				selectedMixerInfo = info;
+			}
+		}
+		assertNotNull(selectedMixerInfo);
+		mixer = AudioSystem.getMixer(selectedMixerInfo);
+		assertNotNull(mixer);
+		if (mixer.isOpen()) {
+			mixer.close();
+		}
+
+	}
+
+	@Test
+	@Ignore
+	public void testLongWait() throws LineUnavailableException {
+		mixer.open();
+		try {
+			/*
+			 * While this test is running, the java procces shouldnt be hogging
+			 * the cpu at all
+			 */
+
+			Thread.sleep(100000);
+		} catch (InterruptedException e) {
+			System.out.println("thread interrupted");
+		}
+
+	}
+
+	@After
+	public void tearDown() {
+		if (mixer.isOpen()) {
+			mixer.close();
+		}
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pulseaudio/unittests/org/classpath/icedtea/pulseaudio/PulseAudioMixerProviderTest.java	Fri Oct 10 16:03:12 2008 -0400
@@ -0,0 +1,118 @@
+/* PulseAudioMixerProviderTest.java
+   Copyright (C) 2008 Red Hat, Inc.
+
+This file is part of IcedTea.
+
+IcedTea is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License as published by
+the Free Software Foundation, version 2.
+
+IcedTea is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with IcedTea; see the file COPYING.  If not, write to
+the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library.  Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module.  An independent module is a module which is not derived from
+or based on this library.  If you modify this library, you may extend
+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;
+
+import static org.junit.Assert.assertNotNull;
+
+import javax.sound.sampled.AudioFormat;
+import javax.sound.sampled.AudioSystem;
+import javax.sound.sampled.Line;
+import javax.sound.sampled.LineUnavailableException;
+import javax.sound.sampled.Mixer;
+import javax.sound.sampled.SourceDataLine;
+
+import org.junit.Test;
+
+public class PulseAudioMixerProviderTest {
+
+	AudioFormat aSupportedFormat = new AudioFormat(
+			AudioFormat.Encoding.PCM_UNSIGNED, 44100f, 8, 1, 1, 44100f, true);
+
+	@Test
+	public void testMixerProvider() throws LineUnavailableException {
+
+		System.out
+				.println("This test checks that the PulseAudio mixer exists and is usable");
+
+		Mixer selectedMixer = null;
+		Mixer.Info selectedMixerInfo = null;
+
+		Mixer.Info mixerInfos[] = AudioSystem.getMixerInfo();
+
+		int i = 0;
+		for (Mixer.Info info : mixerInfos) {
+			System.out.println("Mixer Line " + i++ + ": " + info.getName()
+					+ " " + info.getDescription());
+			if (info.getName().contains("PulseAudio")) {
+				System.out.println("              ^ found PulseAudio Mixer!");
+				selectedMixerInfo = info;
+			}
+		}
+
+		assertNotNull(selectedMixerInfo);
+
+		System.out.println("Getting information from selected mixer:");
+		System.out.println("Name: " + selectedMixerInfo.getName());
+		System.out.println("Version: " + selectedMixerInfo.getVersion());
+
+		selectedMixer = AudioSystem.getMixer(selectedMixerInfo);
+		assertNotNull(selectedMixer);
+		System.out.println("Implemented in class: "
+				+ selectedMixer.getClass().toString());
+
+		selectedMixer.open(); // initialize the mixer
+
+		Line.Info sourceDataLineInfo = null;
+		Line.Info allLineInfo[] = selectedMixer.getSourceLineInfo();
+		System.out.println("Source lines supported by mixer: ");
+		int j = 0;
+		for (Line.Info lineInfo : allLineInfo) {
+			System.out.println("Source Line " + j++ + ": "
+					+ lineInfo.getLineClass());
+			if (lineInfo.toString().contains("SourceDataLine")) {
+				sourceDataLineInfo = lineInfo;
+			}
+
+		}
+
+		if (sourceDataLineInfo == null) {
+			System.out.println("Mixer supports no SourceDataLines");
+		} else {
+			SourceDataLine sourceDataLine = (SourceDataLine) selectedMixer
+					.getLine(sourceDataLineInfo);
+
+			sourceDataLine.open(aSupportedFormat);
+			// sourceDataLine.write('a', 0, 2);
+			sourceDataLine.close();
+		}
+
+		selectedMixer.close();
+
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pulseaudio/unittests/org/classpath/icedtea/pulseaudio/PulseAudioMixerRawTest.java	Fri Oct 10 16:03:12 2008 -0400
@@ -0,0 +1,120 @@
+/* PulseAudioMixerRawTest.java
+   Copyright (C) 2008 Red Hat, Inc.
+
+This file is part of IcedTea.
+
+IcedTea is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License as published by
+the Free Software Foundation, version 2.
+
+IcedTea is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with IcedTea; see the file COPYING.  If not, write to
+the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library.  Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module.  An independent module is a module which is not derived from
+or based on this library.  If you modify this library, you may extend
+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;
+
+import java.net.UnknownHostException;
+
+import javax.sound.sampled.LineUnavailableException;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class PulseAudioMixerRawTest {
+
+	PulseAudioMixer mixer = null;
+
+	@Before
+	public void setUp() {
+		mixer = PulseAudioMixer.getInstance();
+		if (mixer.isOpen()) {
+			mixer.close();
+		}
+	}
+
+	@Test
+	public void testLocalOpen() throws LineUnavailableException {
+		System.out.println("This test tries to open to the local system");
+		mixer.openLocal();
+	}
+
+	@Test
+	public void testLocalOpenAppName() throws LineUnavailableException {
+		System.out
+				.println("This test tries to connect to the local system while using an application name");
+		mixer.openLocal("JunitTest");
+
+	}
+
+	@Test(expected = LineUnavailableException.class)
+	public void testRemoteOpenWithInvalidPort() throws UnknownHostException,
+			LineUnavailableException {
+		System.out
+				.println("this test tries to connect to an invalid remote system");
+		mixer.openRemote("JUnitTest", "128.0.0.1", 10);
+
+	}
+
+	/*
+	 * This test assumes a computer named 'town' is in the network with
+	 * pulseaudio listening on port 4173
+	 */
+	@Test
+	public void testRemoteOpenWithValidPort() throws UnknownHostException,
+			LineUnavailableException {
+		System.out.println("This test tries to connect a valid remote system");
+		mixer.openRemote("JUnitTest", "town", 4713);
+		mixer.close();
+	}
+
+	/*
+	 * This test assumes a computer named 'town' is in the network with
+	 * pulseaudio listening
+	 */
+	@Test
+	public void testRemoteOpen() throws UnknownHostException,
+			LineUnavailableException {
+		mixer.openRemote("JUnitTest", "town");
+		mixer.close();
+	}
+
+	@Test(expected = LineUnavailableException.class)
+	public void testInvalidRemoteOpen() throws UnknownHostException,
+			LineUnavailableException {
+		mixer.openRemote("JUnitTest", "127.0.0.1");
+		mixer.close();
+	}
+
+	@After
+	public void tearDown() {
+		if (mixer.isOpen()) {
+			mixer.close();
+		}
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pulseaudio/unittests/org/classpath/icedtea/pulseaudio/PulseAudioMixerTest.java	Fri Oct 10 16:03:12 2008 -0400
@@ -0,0 +1,434 @@
+/* PulseAudioMixerTest.java
+   Copyright (C) 2008 Red Hat, Inc.
+
+This file is part of IcedTea.
+
+IcedTea is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License as published by
+the Free Software Foundation, version 2.
+
+IcedTea is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with IcedTea; see the file COPYING.  If not, write to
+the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library.  Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module.  An independent module is a module which is not derived from
+or based on this library.  If you modify this library, you may extend
+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;
+
+import javax.sound.sampled.AudioFormat;
+import javax.sound.sampled.AudioSystem;
+import javax.sound.sampled.DataLine;
+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.Port;
+import javax.sound.sampled.SourceDataLine;
+import javax.sound.sampled.TargetDataLine;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class PulseAudioMixerTest {
+
+	Mixer selectedMixer;
+
+	AudioFormat aSupportedFormat = new AudioFormat(
+			AudioFormat.Encoding.PCM_UNSIGNED, 44100f, 8, 1, 1, 44100f, true);
+	AudioFormat aNotSupportedFormat = new AudioFormat(
+			AudioFormat.Encoding.ULAW, 44100, 32, 10, 10, 44100f, true);
+
+	@Before
+	public void setUp() throws Exception {
+		Mixer.Info mixerInfos[] = AudioSystem.getMixerInfo();
+		Mixer.Info selectedMixerInfo = null;
+		// int i = 0;
+		for (Mixer.Info info : mixerInfos) {
+			// System.out.println("Mixer Line " + i++ + ": " + info.getName() +
+			// " " + info.getDescription());
+			if (info.getName().contains("PulseAudio")) {
+				selectedMixerInfo = info;
+			}
+		}
+		Assert.assertNotNull(selectedMixerInfo);
+		selectedMixer = AudioSystem.getMixer(selectedMixerInfo);
+		Assert.assertNotNull(selectedMixer);
+		if (selectedMixer.isOpen()) {
+			selectedMixer.close();
+		}
+
+	}
+
+	@Test
+	public void testOpenClose() throws LineUnavailableException {
+		selectedMixer.open();
+		selectedMixer.close();
+
+	}
+
+	@Test
+	public void testSourceLinesExist() throws LineUnavailableException {
+		System.out.println("This tests that source lines exist");
+		selectedMixer.open();
+		Line.Info allLineInfo[] = selectedMixer.getSourceLineInfo();
+		Assert.assertNotNull(allLineInfo);
+		Assert.assertTrue(allLineInfo.length > 0);
+
+		boolean foundSourceDataLine = false;
+		boolean foundClip = false;
+		boolean foundPort = false;
+
+		int j = 0;
+		for (Line.Info lineInfo : allLineInfo) {
+			System.out.println("Source Line " + j++ + ": "
+					+ lineInfo.getLineClass());
+			if (lineInfo.getLineClass().toString().contains("SourceDataLine")) {
+				foundSourceDataLine = true;
+			} else if (lineInfo.getLineClass().toString().contains("Clip")) {
+				foundClip = true;
+			} else if (lineInfo.getLineClass().toString().contains("Port")) {
+				foundPort = true;
+			} else {
+				Assert.assertFalse("Found a new type of Line", true);
+			}
+			Line sourceLine = (Line) selectedMixer.getLine(lineInfo);
+			Assert.assertNotNull(sourceLine);
+
+		}
+
+		Assert.assertTrue("Couldnt find a SourceDataLine", foundSourceDataLine);
+		Assert.assertTrue("Couldnt find a Clip", foundClip);
+		Assert.assertTrue("Couldnt find a Port", foundPort);
+
+	}
+
+	@Test
+	public void testTargetLinesExist() throws LineUnavailableException {
+		System.out.println("This tests if target Lines exist");
+		selectedMixer.open();
+		Line.Info allLineInfo[] = selectedMixer.getTargetLineInfo();
+		Assert.assertNotNull(allLineInfo);
+		Assert.assertTrue(allLineInfo.length > 0);
+
+		boolean foundTargetDataLine = false;
+		boolean foundPort = false;
+
+		int j = 0;
+		for (Line.Info lineInfo : allLineInfo) {
+			System.out.println("Target Line " + j++ + ": "
+					+ lineInfo.getLineClass());
+			if (lineInfo.getLineClass().toString().contains("TargetDataLine")) {
+				foundTargetDataLine = true;
+			} else if (lineInfo.getLineClass().toString().contains("Port")) {
+				foundPort = true;
+			} else {
+				Assert.assertTrue("Found invalid type of target line", true);
+			}
+			Line targetLine = (Line) selectedMixer.getLine(lineInfo);
+			Assert.assertNotNull(targetLine);
+
+		}
+
+		Assert.assertTrue("Couldnt find a TargetDataLine", foundTargetDataLine);
+		Assert.assertTrue("Couldnt find a target Port", foundPort);
+
+	}
+
+	@Ignore
+	@Test
+	public void testHeadphonePortExists() throws LineUnavailableException {
+		selectedMixer.open();
+		selectedMixer.getLine(Port.Info.HEADPHONE);
+	}
+
+	@Ignore
+	@Test
+	public void testSpeakerPortExists() throws LineUnavailableException {
+		selectedMixer.open();
+		selectedMixer.getLine(Port.Info.SPEAKER);
+	}
+
+	@Ignore
+	@Test
+	public void testLineInPortExists() throws LineUnavailableException {
+		selectedMixer.open();
+		selectedMixer.getLine(Port.Info.LINE_IN);
+	}
+
+	@Ignore
+	@Test
+	public void testCdPortExists() throws LineUnavailableException {
+		selectedMixer.open();
+		selectedMixer.getLine(Port.Info.COMPACT_DISC);
+	}
+
+	@Ignore
+	@Test
+	public void testLineOutPortExists() throws LineUnavailableException {
+		selectedMixer.open();
+		selectedMixer.getLine(Port.Info.LINE_OUT);
+	}
+
+	@Ignore
+	@Test
+	public void testMicrophonePortExists() throws LineUnavailableException {
+		selectedMixer.open();
+		selectedMixer.getLine(Port.Info.MICROPHONE);
+	}
+
+	@Test
+	public void testSaneNumberOfPorts() throws LineUnavailableException {
+		System.out
+				.println("This test checks that a sane number of ports are detected");
+		selectedMixer.open();
+		Line.Info[] lineInfos = selectedMixer.getSourceLineInfo();
+		Assert.assertNotNull(lineInfos);
+
+		int ports = 0;
+		for (Line.Info info : lineInfos) {
+			if (info instanceof Port.Info) {
+				ports++;
+			}
+		}
+		Assert.assertTrue("Too few Source ports", ports > 0);
+		Assert.assertTrue("Too many Source ports... this looks wrong",
+				ports < 5);
+
+		lineInfos = selectedMixer.getTargetLineInfo();
+		ports = 0;
+		for (Line.Info info : lineInfos) {
+			if (info instanceof Port.Info) {
+				ports++;
+			}
+		}
+		Assert.assertTrue("Too few Target ports", ports > 0);
+		Assert.assertTrue("Too many Target ports... this looks wrong",
+				ports < 5);
+
+	}
+
+	@Test
+	public void testGetTargetPortInfo() throws LineUnavailableException {
+		System.out.println("This test checks target ports");
+		selectedMixer.open();
+		Line.Info[] lineInfos = selectedMixer.getTargetLineInfo();
+		int i = 0;
+		for (Line.Info info : lineInfos) {
+			if (info instanceof Port.Info) {
+				Port.Info portInfo = (Port.Info) info;
+				Assert.assertTrue(portInfo.isSource() == false);
+				Assert.assertTrue(portInfo.getLineClass() == Port.class);
+				System.out.println("Port " + ++i + ": " + portInfo.getName()
+						+ " - " + portInfo.getLineClass());
+			}
+		}
+
+	}
+
+	@Test
+	public void testGetSourcePortInfo() throws LineUnavailableException {
+		System.out.println("This test checks source ports");
+		selectedMixer.open();
+		Line.Info[] lineInfos = selectedMixer.getSourceLineInfo();
+		int i = 0;
+		for (Line.Info info : lineInfos) {
+			if (info instanceof Port.Info) {
+				Port.Info portInfo = (Port.Info) info;
+				Assert.assertTrue(portInfo.isSource() == true);
+				Assert.assertTrue(portInfo.getLineClass() == Port.class);
+				System.out.println("Port " + ++i + ": " + portInfo.getName()
+						+ " - " + portInfo.getLineClass());
+			}
+		}
+
+	}
+
+	@Test(expected = IllegalStateException.class)
+	public void testOpeningAgain() throws LineUnavailableException {
+		selectedMixer.open();
+		selectedMixer.open();
+	}
+
+	@Test(expected = IllegalStateException.class)
+	public void testClosingAgain() throws LineUnavailableException,
+			IllegalStateException {
+		selectedMixer.close();
+		selectedMixer.close();
+	}
+
+	@Test
+	public void testSourceLinesOpenAndClose() throws LineUnavailableException {
+		System.out.println("This test checks if source lines open and close");
+		selectedMixer.open();
+
+		Line.Info allLineInfo[] = selectedMixer.getSourceLineInfo();
+		for (Line.Info lineInfo : allLineInfo) {
+			try {
+				Line sourceLine = selectedMixer.getLine(lineInfo);
+				sourceLine.open();
+				sourceLine.close();
+			} catch (IllegalArgumentException e) {
+				// ignore this
+			}
+		}
+	}
+
+	@Test
+	public void testTargetLinesOpenAndClose() throws LineUnavailableException {
+		System.out.println("This test checks if source lines open and close");
+		selectedMixer.open();
+
+		Line.Info allLineInfo[] = selectedMixer.getTargetLineInfo();
+		for (Line.Info lineInfo : allLineInfo) {
+			try {
+				TargetDataLine targetLine = (TargetDataLine) selectedMixer
+						.getLine(lineInfo);
+				Assert.assertNotNull(targetLine);
+				targetLine.open(aSupportedFormat);
+				targetLine.close();
+			} catch (ClassCastException cce) {
+				Port targetLine = (Port) selectedMixer.getLine(lineInfo);
+				Assert.assertNotNull(targetLine);
+				targetLine.open();
+				targetLine.close();
+			}
+
+		}
+	}
+
+	@Test
+	public void testOpenEvent() throws LineUnavailableException {
+		LineListener listener = new LineListener() {
+			private int called = 0;
+
+			@Override
+			public void update(LineEvent event) {
+				assert (event.getType() == LineEvent.Type.OPEN);
+				called++;
+				// assert listener is called exactly once
+				Assert.assertEquals(1, called);
+			}
+		};
+
+		selectedMixer.addLineListener(listener);
+		selectedMixer.open();
+		selectedMixer.removeLineListener(listener);
+		selectedMixer.close();
+
+	}
+
+	@Test
+	public void testCloseEvent() throws LineUnavailableException {
+		LineListener listener = new LineListener() {
+			private int count = 0;
+
+			@Override
+			public void update(LineEvent event) {
+				Assert.assertTrue(event.getType() == LineEvent.Type.CLOSE);
+				count++;
+				// assert listener is called exactly once
+				Assert.assertEquals(1, count);
+
+			}
+		};
+
+		selectedMixer.open();
+		selectedMixer.addLineListener(listener);
+		selectedMixer.close();
+		selectedMixer.removeLineListener(listener);
+
+	}
+
+	@Test
+	public void testLineSupportedWorksWithoutOpeningMixer() {
+
+		Assert.assertFalse(selectedMixer.isOpen());
+
+		Assert.assertFalse(selectedMixer.isLineSupported(new DataLine.Info(
+				SourceDataLine.class, aNotSupportedFormat)));
+
+		Assert.assertTrue(selectedMixer.isLineSupported(new DataLine.Info(
+				SourceDataLine.class, aSupportedFormat)));
+
+	}
+
+	@Test
+	public void testSynchronizationNotSupported()
+			throws LineUnavailableException {
+		selectedMixer.open();
+
+		SourceDataLine line1 = (SourceDataLine) selectedMixer
+				.getLine(new Line.Info(SourceDataLine.class));
+		SourceDataLine line2 = (SourceDataLine) selectedMixer
+				.getLine(new Line.Info(SourceDataLine.class));
+		Line[] lines = { line1, line2 };
+
+		Assert.assertFalse(selectedMixer
+				.isSynchronizationSupported(lines, true));
+
+		Assert.assertFalse(selectedMixer.isSynchronizationSupported(lines,
+				false));
+
+		try {
+			selectedMixer.synchronize(lines, true);
+			Assert.fail("mixer shouldnt be able to synchronize lines");
+		} catch (IllegalArgumentException e) {
+
+		}
+
+		try {
+			selectedMixer.synchronize(lines, false);
+			Assert.fail("mixer shouldnt be able to synchronize lines");
+		} catch (IllegalArgumentException e) {
+
+		}
+
+		selectedMixer.close();
+
+	}
+
+	@Ignore
+	@Test
+	public void testLongWait() throws LineUnavailableException {
+		selectedMixer.open();
+		try {
+			Thread.sleep(1000);
+		} catch (InterruptedException e) {
+			System.out.println("thread interrupted");
+		}
+
+	}
+
+	@After
+	public void tearDown() throws Exception {
+		if (selectedMixer.isOpen())
+			selectedMixer.close();
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pulseaudio/unittests/org/classpath/icedtea/pulseaudio/PulseAudioSourceDataLineRawTest.java	Fri Oct 10 16:03:12 2008 -0400
@@ -0,0 +1,305 @@
+/* PulseAudioSourceDataLineRawTest.java
+   Copyright (C) 2008 Red Hat, Inc.
+
+This file is part of IcedTea.
+
+IcedTea is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License as published by
+the Free Software Foundation, version 2.
+
+IcedTea is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with IcedTea; see the file COPYING.  If not, write to
+the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library.  Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module.  An independent module is a module which is not derived from
+or based on this library.  If you modify this library, you may extend
+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;
+
+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.BooleanControl;
+import javax.sound.sampled.DataLine;
+import javax.sound.sampled.FloatControl;
+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.SourceDataLine;
+import javax.sound.sampled.UnsupportedAudioFileException;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class PulseAudioSourceDataLineRawTest {
+
+	PulseAudioMixer mixer = null;
+
+	int started = 0;
+	int stopped = 0;
+
+	AudioFormat aSupportedFormat = new AudioFormat(
+			AudioFormat.Encoding.PCM_UNSIGNED, 44100f, 8, 1, 1, 44100f, true);
+
+	class ThreadWriter extends Thread {
+		PulseAudioSourceDataLine line;
+		AudioInputStream stream;
+
+		public ThreadWriter(AudioInputStream stream,
+				PulseAudioSourceDataLine line) throws LineUnavailableException {
+
+			this.line = line;
+			this.stream = stream;
+
+			if (line.isOpen()) {
+				line.close();
+			}
+
+		}
+
+		@Override
+		public void run() {
+			try {
+				AudioFormat audioFormat = stream.getFormat();
+
+				line.open(audioFormat);
+
+				byte[] abData = new byte[1000];
+				int bytesRead = 0;
+
+				line.start();
+
+				while (bytesRead >= 0) {
+					bytesRead = stream.read(abData, 0, abData.length);
+					// System.out.println("read data");
+					if (bytesRead > 0) {
+						// System.out.println("about to write data");
+						line.write(abData, 0, bytesRead);
+						// System.out.println("wrote data");
+					}
+				}
+
+				line.drain();
+				line.close();
+
+			} catch (LineUnavailableException e) {
+				e.printStackTrace();
+			} catch (IOException e) {
+				e.printStackTrace();
+			}
+		}
+	}
+
+	@Before
+	public void setUp() throws LineUnavailableException {
+
+		mixer = PulseAudioMixer.getInstance();
+		if (mixer.isOpen()) {
+			mixer.close();
+		}
+
+		mixer.open();
+
+		started = 0;
+		stopped = 0;
+
+	}
+
+	@Test
+	public void testStartAndStopEventsOnCork()
+			throws UnsupportedAudioFileException, IOException,
+			LineUnavailableException, InterruptedException {
+
+		System.out
+				.println("This test checks if START and STOP notifications appear on corking");
+
+		File soundFile = new File("testsounds/startup.wav");
+		AudioInputStream audioInputStream = AudioSystem
+				.getAudioInputStream(soundFile);
+		AudioFormat audioFormat = audioInputStream.getFormat();
+
+		PulseAudioSourceDataLine line;
+		line = (PulseAudioSourceDataLine) mixer.getLine(new DataLine.Info(
+				SourceDataLine.class, audioFormat));
+		Assert.assertNotNull(line);
+
+		LineListener startStopListener = new LineListener() {
+
+			@Override
+			public void update(LineEvent event) {
+				if (event.getType() == LineEvent.Type.START) {
+					System.out.println("START");
+					started++;
+				}
+
+				if (event.getType() == LineEvent.Type.STOP) {
+					System.out.println("STOP");
+					stopped++;
+				}
+			}
+
+		};
+
+		line.addLineListener(startStopListener);
+		System.out.println("Launching threadWriter");
+		ThreadWriter writer = new ThreadWriter(audioInputStream, line);
+		writer.start();
+		// System.out.println("started");
+
+		Thread.sleep(1000);
+
+		// CORK
+		line.stop();
+
+		Thread.sleep(1000);
+
+		// UNCORK
+		line.start();
+
+		Thread.sleep(1000);
+
+		// System.out.println("waiting for thread to finish");
+		writer.join();
+
+		Assert.assertEquals(2, started);
+		Assert.assertEquals(2, stopped);
+
+	}
+
+	@Test
+	public void testVolumeAndMute() throws Exception {
+
+		Mixer selectedMixer = mixer;
+		SourceDataLine line = (SourceDataLine) selectedMixer
+				.getLine(new Line.Info(SourceDataLine.class));
+
+		File soundFile = new File(new java.io.File(".").getCanonicalPath()
+				+ "/testsounds/logout.wav");
+		AudioInputStream audioInputStream = AudioSystem
+				.getAudioInputStream(soundFile);
+		AudioFormat audioFormat = audioInputStream.getFormat();
+
+		line.open(audioFormat);
+		line.start();
+		PulseAudioVolumeControl volume = (PulseAudioVolumeControl) line
+				.getControl(FloatControl.Type.VOLUME);
+		PulseAudioMuteControl mute = (PulseAudioMuteControl) line
+				.getControl(BooleanControl.Type.MUTE);
+
+		mute.setValue(true);
+		volume.setValue(PulseAudioVolumeControl.MAX_VOLUME);
+
+		mute.setValue(false);
+
+		byte[] abData = new byte[1000];
+		int bytesRead = 0;
+
+		while (bytesRead >= 0) {
+			bytesRead = audioInputStream.read(abData, 0, abData.length);
+			if (bytesRead > 0) {
+				line.write(abData, 0, bytesRead);
+			}
+		}
+
+		line.drain();
+		line.close();
+		selectedMixer.close();
+
+	}
+
+	@Test
+	public void testSettingStreamName() throws LineUnavailableException,
+			UnsupportedAudioFileException, IOException {
+		File soundFile = new File("testsounds/logout.wav");
+		AudioInputStream audioInputStream = AudioSystem
+				.getAudioInputStream(soundFile);
+		AudioFormat audioFormat = audioInputStream.getFormat();
+
+		PulseAudioSourceDataLine line;
+		line = (PulseAudioSourceDataLine) mixer.getLine(new DataLine.Info(
+				SourceDataLine.class, audioFormat));
+
+		String name = "Knights Who Say ... Oh my god, i am so sorry, i didnt mean it...";
+		line.setName(name);
+
+		line.open(audioFormat);
+		line.start();
+
+		byte[] abData = new byte[1000];
+		int bytesRead = 0;
+
+		while (bytesRead >= 0) {
+			bytesRead = audioInputStream.read(abData, 0, abData.length);
+			if (bytesRead > 0) {
+				line.write(abData, 0, bytesRead);
+			}
+		}
+
+		Assert.assertTrue(line.getName() == name);
+		/*
+		 * FIXME test that PulseAudio also knows this correctly using
+		 * introspection
+		 */
+
+		line.drain();
+		line.stop();
+		line.close();
+
+	}
+
+	@Test
+	public void messWithStreams() throws LineUnavailableException {
+		System.out
+				.println("This test tries to unCork a stream which hasnt been corked");
+
+		PulseAudioSourceDataLine line = (PulseAudioSourceDataLine) mixer
+				.getLine(new DataLine.Info(SourceDataLine.class,
+						aSupportedFormat, 1000));
+
+		line.open();
+		line.start();
+		Stream s = line.getStream();
+		Operation o;
+		synchronized (EventLoop.getEventLoop().threadLock) {
+			o = s.unCork();
+		}
+		o.waitForCompletion();
+		o.releaseReference();
+		line.stop();
+		line.close();
+	}
+
+	@After
+	public void tearDown() {
+
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pulseaudio/unittests/org/classpath/icedtea/pulseaudio/PulseAudioSourceDataLineTest.java	Fri Oct 10 16:03:12 2008 -0400
@@ -0,0 +1,1345 @@
+/* PulseSourceDataLineTest.java
+   Copyright (C) 2008 Red Hat, Inc.
+
+This file is part of IcedTea.
+
+IcedTea is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License as published by
+the Free Software Foundation, version 2.
+
+IcedTea is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with IcedTea; see the file COPYING.  If not, write to
+the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library.  Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module.  An independent module is a module which is not derived from
+or based on this library.  If you modify this library, you may extend
+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;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.UnknownHostException;
+
+import javax.sound.sampled.AudioFormat;
+import javax.sound.sampled.AudioInputStream;
+import javax.sound.sampled.AudioSystem;
+import javax.sound.sampled.BooleanControl;
+import javax.sound.sampled.Control;
+import javax.sound.sampled.DataLine;
+import javax.sound.sampled.FloatControl;
+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.SourceDataLine;
+import javax.sound.sampled.UnsupportedAudioFileException;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class PulseAudioSourceDataLineTest {
+	Mixer mixer;
+	SourceDataLine sourceDataLine;
+	private int listenerCalled = 0;
+
+	int started = 0;
+	int stopped = 0;
+	int opened = 0;
+	int closed = 0;
+
+	AudioFormat aSupportedFormat = new AudioFormat(
+			AudioFormat.Encoding.PCM_UNSIGNED, 44100f, 8, 1, 1, 44100f, true);
+
+	class ThreadWriter extends Thread {
+		SourceDataLine line;
+		AudioInputStream stream;
+
+		public ThreadWriter(AudioInputStream stream, SourceDataLine line)
+				throws LineUnavailableException {
+
+			this.line = line;
+			this.stream = stream;
+
+			if (line.isOpen()) {
+				line.close();
+			}
+
+		}
+
+		@Override
+		public void run() {
+			try {
+				AudioFormat audioFormat = stream.getFormat();
+
+				line.open(audioFormat);
+
+				byte[] abData = new byte[1000];
+				int bytesRead = 0;
+
+				line.start();
+
+				while (bytesRead >= 0) {
+					bytesRead = stream.read(abData, 0, abData.length);
+					// System.out.println("read data");
+					if (bytesRead > 0) {
+						// System.out.println("about to write data");
+						line.write(abData, 0, bytesRead);
+						// System.out.println("wrote data");
+					}
+				}
+
+				line.drain();
+				line.close();
+
+			} catch (LineUnavailableException e) {
+				e.printStackTrace();
+			} catch (IOException e) {
+				e.printStackTrace();
+			}
+		}
+	}
+
+	@Before
+	public void setUp() throws Exception {
+		Mixer.Info mixerInfos[] = AudioSystem.getMixerInfo();
+		Mixer.Info selectedMixerInfo = null;
+		// int i = 0;
+		for (Mixer.Info info : mixerInfos) {
+			// System.out.println("Mixer Line " + i++ + ": " + info.getName() +
+			// " " + info.getDescription());
+			if (info.getName().contains("PulseAudio")) {
+				selectedMixerInfo = info;
+			}
+		}
+		Assert.assertNotNull(selectedMixerInfo);
+		mixer = AudioSystem.getMixer(selectedMixerInfo);
+		Assert.assertNotNull(mixer);
+		if (mixer.isOpen()) {
+			mixer.close();
+		}
+
+		mixer.open();
+
+		sourceDataLine = null;
+
+		started = 0;
+		stopped = 0;
+		opened = 0;
+		closed = 0;
+		listenerCalled = 0;
+	}
+
+	@Test
+	public void testOpenAndClose() throws LineUnavailableException {
+		sourceDataLine = (SourceDataLine) mixer.getLine(new Line.Info(
+				SourceDataLine.class));
+
+		sourceDataLine.open();
+		Assert.assertTrue(sourceDataLine.isOpen());
+
+		sourceDataLine.close();
+		Assert.assertFalse(sourceDataLine.isOpen());
+
+	}
+
+	@Test
+	public void testIsActiveAndIsOpen() throws LineUnavailableException {
+
+		sourceDataLine = (SourceDataLine) mixer.getLine(new DataLine.Info(
+				SourceDataLine.class, aSupportedFormat, 1000));
+
+		Assert.assertFalse(sourceDataLine.isActive());
+		Assert.assertFalse(sourceDataLine.isOpen());
+		sourceDataLine.open();
+		Assert.assertTrue(sourceDataLine.isOpen());
+		Assert.assertFalse(sourceDataLine.isActive());
+		sourceDataLine.start();
+		Assert.assertTrue(sourceDataLine.isOpen());
+		Assert.assertTrue(sourceDataLine.isActive());
+		sourceDataLine.stop();
+		Assert.assertTrue(sourceDataLine.isOpen());
+		Assert.assertFalse(sourceDataLine.isActive());
+		sourceDataLine.close();
+		Assert.assertFalse(sourceDataLine.isOpen());
+		Assert.assertFalse(sourceDataLine.isActive());
+
+	}
+
+	@Test
+	public void testPlay() throws LineUnavailableException,
+			UnsupportedAudioFileException, IOException {
+		System.out.println("This test plays a file");
+
+		File soundFile = new File("testsounds/startup.wav");
+		AudioInputStream audioInputStream = AudioSystem
+				.getAudioInputStream(soundFile);
+		AudioFormat audioFormat = audioInputStream.getFormat();
+
+		sourceDataLine = (SourceDataLine) mixer.getLine(new DataLine.Info(
+				SourceDataLine.class, audioFormat));
+		Assert.assertNotNull(sourceDataLine);
+
+		sourceDataLine.open(audioFormat);
+		System.out.println("opened");
+		sourceDataLine.start();
+		System.out.println("started");
+		byte[] abData = new byte[1000];
+		int bytesRead = 0;
+
+		while (bytesRead >= 0) {
+			bytesRead = audioInputStream.read(abData, 0, abData.length);
+			if (bytesRead > 0) {
+				sourceDataLine.write(abData, 0, bytesRead);
+			}
+		}
+		System.out.println("done");
+
+		sourceDataLine.drain();
+		System.out.println("drained");
+		sourceDataLine.stop();
+		sourceDataLine.close();
+		System.out.println("closed");
+
+	}
+
+	@Test
+	public void testWriteWithoutStart() throws UnsupportedAudioFileException,
+			IOException, LineUnavailableException, InterruptedException {
+
+		System.out
+				.println("This test doesnt play a file; you shouldnt hear anything");
+
+		File soundFile = new File("testsounds/startup.wav");
+		final AudioInputStream audioInputStream = AudioSystem
+				.getAudioInputStream(soundFile);
+		final AudioFormat audioFormat = audioInputStream.getFormat();
+
+		sourceDataLine = (SourceDataLine) mixer.getLine(new DataLine.Info(
+				SourceDataLine.class, audioFormat));
+		Assert.assertNotNull(sourceDataLine);
+
+		Thread writer = new Thread() {
+			@Override
+			public void run() {
+				try {
+					sourceDataLine.open(audioFormat);
+					byte[] abData = new byte[1000];
+					int bytesRead = 0;
+					int total = 0;
+
+					while (bytesRead >= 0 && total < 50) {
+						bytesRead = audioInputStream.read(abData, 0,
+								abData.length);
+						if (bytesRead > 0) {
+							sourceDataLine.write(abData, 0, bytesRead);
+						}
+						total++;
+					}
+				} catch (LineUnavailableException e) {
+					Assert.fail();
+				} catch (IOException e) {
+					Assert.fail();
+				}
+			}
+
+		};
+
+		writer.start();
+
+		Thread.sleep(100);
+
+		writer.join(1000);
+
+		/* assert that the writer is still waiting in write */
+		Assert.assertTrue(writer.isAlive());
+
+	}
+
+	@Test
+	public void testWriteAndClose() throws UnsupportedAudioFileException,
+			IOException, LineUnavailableException, InterruptedException {
+		System.out.println("This test tires to close the line during a write");
+
+		File soundFile = new File("testsounds/startup.wav");
+		final AudioInputStream audioInputStream = AudioSystem
+				.getAudioInputStream(soundFile);
+		AudioFormat audioFormat = audioInputStream.getFormat();
+
+		sourceDataLine = (SourceDataLine) mixer.getLine(new DataLine.Info(
+				SourceDataLine.class, audioFormat));
+		Assert.assertNotNull(sourceDataLine);
+
+		sourceDataLine.open(audioFormat);
+		sourceDataLine.start();
+
+		Thread writer = new Thread() {
+
+			@Override
+			public void run() {
+				try {
+					final byte[] abData = new byte[10000000];
+
+					int bytesRead = 0;
+					while (bytesRead >= 0) {
+						bytesRead = audioInputStream.read(abData, 0,
+								abData.length);
+						if (bytesRead > 0) {
+							sourceDataLine.write(abData, 0, bytesRead);
+						}
+					}
+				} catch (UnknownHostException e) {
+					e.printStackTrace();
+				} catch (IOException e) {
+					e.printStackTrace();
+				}
+			}
+
+		};
+
+		writer.start();
+		Thread.sleep(100);
+
+		sourceDataLine.close();
+
+		writer.join(500);
+		Assert.assertFalse(writer.isAlive());
+
+	}
+
+	@Test
+	public void testWriteAndStop() throws UnsupportedAudioFileException,
+			IOException, LineUnavailableException, InterruptedException {
+		System.out.println("This test tires to stop the line during a write");
+
+		File soundFile = new File("testsounds/startup.wav");
+		final AudioInputStream audioInputStream = AudioSystem
+				.getAudioInputStream(soundFile);
+		AudioFormat audioFormat = audioInputStream.getFormat();
+
+		sourceDataLine = (SourceDataLine) mixer.getLine(new DataLine.Info(
+				SourceDataLine.class, audioFormat));
+		Assert.assertNotNull(sourceDataLine);
+
+		sourceDataLine.open(audioFormat);
+		sourceDataLine.start();
+
+		Thread writer = new Thread() {
+
+			@Override
+			public void run() {
+				try {
+					final byte[] abData = new byte[10000000];
+
+					int bytesRead = 0;
+					while (bytesRead >= 0) {
+						bytesRead = audioInputStream.read(abData, 0,
+								abData.length);
+						if (bytesRead > 0) {
+							sourceDataLine.write(abData, 0, bytesRead);
+						}
+					}
+				} catch (UnknownHostException e) {
+					e.printStackTrace();
+				} catch (IOException e) {
+					e.printStackTrace();
+				}
+			}
+
+		};
+
+		writer.start();
+
+		Thread.sleep(500);
+
+		sourceDataLine.stop();
+
+		writer.join(500);
+		Assert.assertFalse(writer.isAlive());
+
+		sourceDataLine.close();
+
+	}
+
+	@Test
+	public void testWriteAndFlush() throws UnsupportedAudioFileException,
+			IOException, LineUnavailableException, InterruptedException {
+
+		System.out.println("This test tries to flush a line during a write");
+
+		File soundFile = new File("testsounds/startup.wav");
+		final AudioInputStream audioInputStream = AudioSystem
+				.getAudioInputStream(soundFile);
+		AudioFormat audioFormat = audioInputStream.getFormat();
+
+		sourceDataLine = (SourceDataLine) mixer.getLine(new DataLine.Info(
+				SourceDataLine.class, audioFormat));
+		Assert.assertNotNull(sourceDataLine);
+
+		sourceDataLine.open(audioFormat);
+		sourceDataLine.start();
+
+		Thread writer = new Thread() {
+
+			@Override
+			public void run() {
+				try {
+					final byte[] abData = new byte[10000000];
+
+					int bytesRead = 0;
+					while (bytesRead >= 0) {
+						bytesRead = audioInputStream.read(abData, 0,
+								abData.length);
+						if (bytesRead > 0) {
+							sourceDataLine.write(abData, 0, bytesRead);
+						}
+					}
+				} catch (UnknownHostException e) {
+					e.printStackTrace();
+				} catch (IOException e) {
+					e.printStackTrace();
+				}
+			}
+
+		};
+
+		writer.start();
+
+		Thread.sleep(100);
+
+		sourceDataLine.flush();
+
+		writer.join(500);
+		Assert.assertFalse(writer.isAlive());
+
+		sourceDataLine.stop();
+		sourceDataLine.close();
+	}
+
+	@Test
+	public void testStartedStopped() throws LineUnavailableException,
+			UnsupportedAudioFileException, IOException {
+
+		System.out
+				.println("This test check START/STOP events. You should see 1 START and 1 STOP event");
+
+		File soundFile = new File("testsounds/startup.wav");
+		AudioInputStream audioInputStream = AudioSystem
+				.getAudioInputStream(soundFile);
+		AudioFormat audioFormat = audioInputStream.getFormat();
+
+		sourceDataLine = (SourceDataLine) mixer.getLine(new DataLine.Info(
+				SourceDataLine.class, audioFormat));
+		Assert.assertNotNull(sourceDataLine);
+
+		sourceDataLine.open(audioFormat);
+
+		LineListener startStopListener = new LineListener() {
+
+			@Override
+			public void update(LineEvent event) {
+				if (event.getType() == LineEvent.Type.START) {
+					System.out.println("START");
+					started++;
+					Assert.assertEquals(1, started);
+				}
+
+				if (event.getType() == LineEvent.Type.STOP) {
+					System.out.println("STOP");
+					stopped++;
+					Assert.assertEquals(1, stopped);
+				}
+			}
+
+		};
+
+		sourceDataLine.addLineListener(startStopListener);
+
+		byte[] abData = new byte[1000];
+		int bytesRead = 0;
+
+		sourceDataLine.start();
+		while (bytesRead >= 0) {
+			bytesRead = audioInputStream.read(abData, 0, abData.length);
+			if (bytesRead > 0) {
+				sourceDataLine.write(abData, 0, bytesRead);
+			}
+		}
+		sourceDataLine.drain();
+
+		sourceDataLine.stop();
+		sourceDataLine.close();
+
+		Assert.assertEquals(1, started);
+		Assert.assertEquals(1, stopped);
+
+	}
+
+	@Test
+	public void test2StartAndStopEvents() throws UnsupportedAudioFileException,
+			IOException, LineUnavailableException, InterruptedException {
+
+		System.out
+				.println("This test checks if START and STOP notifications appear on corking");
+
+		File soundFile = new File("testsounds/startup.wav");
+		AudioInputStream audioInputStream = AudioSystem
+				.getAudioInputStream(soundFile);
+		AudioFormat audioFormat = audioInputStream.getFormat();
+
+		sourceDataLine = (SourceDataLine) mixer.getLine(new DataLine.Info(
+				SourceDataLine.class, audioFormat));
+		Assert.assertNotNull(sourceDataLine);
+
+		LineListener startStopListener = new LineListener() {
+
+			@Override
+			public void update(LineEvent event) {
+				if (event.getType() == LineEvent.Type.START) {
+					System.out.println("START");
+					started++;
+				}
+
+				if (event.getType() == LineEvent.Type.STOP) {
+					System.out.println("STOP");
+					stopped++;
+				}
+			}
+
+		};
+
+		sourceDataLine.addLineListener(startStopListener);
+		System.out.println("Launching threadWriter");
+		ThreadWriter writer = new ThreadWriter(audioInputStream, sourceDataLine);
+		writer.start();
+		// System.out.println("started");
+
+		Thread.sleep(1000);
+
+		sourceDataLine.stop();
+
+		System.out.println("corked");
+
+		Thread.sleep(5000);
+
+		// UNCORK
+
+		sourceDataLine.start();
+
+		Thread.sleep(2000);
+
+		// System.out.println("waiting for thread to finish");
+		writer.join();
+
+		Assert.assertEquals(2, started);
+		Assert.assertEquals(2, stopped);
+
+	}
+
+	@Test
+	public void test3StartAndStopEvents() throws UnsupportedAudioFileException,
+			IOException, LineUnavailableException, InterruptedException {
+
+		System.out
+				.println("This test checks if START and STOP notifications appear on corking");
+
+		File soundFile = new File("testsounds/startup.wav");
+		AudioInputStream audioInputStream = AudioSystem
+				.getAudioInputStream(soundFile);
+		AudioFormat audioFormat = audioInputStream.getFormat();
+
+		sourceDataLine = (SourceDataLine) mixer.getLine(new DataLine.Info(
+				SourceDataLine.class, audioFormat));
+		Assert.assertNotNull(sourceDataLine);
+
+		LineListener startStopListener = new LineListener() {
+
+			@Override
+			public void update(LineEvent event) {
+				if (event.getType() == LineEvent.Type.START) {
+					System.out.println("START");
+					started++;
+				}
+
+				if (event.getType() == LineEvent.Type.STOP) {
+					System.out.println("STOP");
+					stopped++;
+				}
+			}
+
+		};
+
+		sourceDataLine.addLineListener(startStopListener);
+		System.out.println("Launching threadWriter");
+		ThreadWriter writer = new ThreadWriter(audioInputStream, sourceDataLine);
+		writer.start();
+		// System.out.println("started");
+
+		Thread.sleep(1000);
+
+		sourceDataLine.stop();
+
+		Thread.sleep(1000);
+
+		sourceDataLine.start();
+
+		Thread.sleep(1000);
+
+		sourceDataLine.stop();
+
+		Thread.sleep(1000);
+
+		sourceDataLine.start();
+
+		Thread.sleep(1000);
+
+		// System.out.println("waiting for thread to finish");
+		writer.join();
+
+		Assert.assertEquals(3, started);
+		Assert.assertEquals(3, stopped);
+
+	}
+
+	@Test(expected = IllegalStateException.class)
+	public void testStartOnClosedLine() throws LineUnavailableException {
+		sourceDataLine = (SourceDataLine) mixer.getLine(new Line.Info(
+				SourceDataLine.class));
+		Assert.assertNotNull(sourceDataLine);
+
+		sourceDataLine.start();
+
+	}
+
+	@Test(expected = IllegalStateException.class)
+	public void testStopOnClosedLine() throws LineUnavailableException {
+		sourceDataLine = (SourceDataLine) mixer.getLine(new Line.Info(
+				SourceDataLine.class));
+		Assert.assertNotNull(sourceDataLine);
+
+		sourceDataLine.stop();
+	}
+
+	@Test(expected = IllegalArgumentException.class)
+	public void testPlayLessThanFrameSize() throws LineUnavailableException,
+			UnsupportedAudioFileException, IOException {
+		File soundFile = new File("testsounds/startup.wav");
+		AudioInputStream audioInputStream = AudioSystem
+				.getAudioInputStream(soundFile);
+		AudioFormat audioFormat = audioInputStream.getFormat();
+		// the audio file must have an even number of channels
+		Assert.assertTrue(audioFormat.getChannels() % 2 == 0);
+
+		sourceDataLine = (SourceDataLine) mixer.getLine(new DataLine.Info(
+				SourceDataLine.class, audioFormat));
+
+		byte[] data = new byte[1];
+		data[0] = (byte) 'a';
+
+		sourceDataLine.open();
+		sourceDataLine.start();
+		try {
+			sourceDataLine.write(data, 0, 1);
+		} finally {
+			sourceDataLine.stop();
+			sourceDataLine.close();
+		}
+	}
+
+	@Test
+	public void testOpenFormat() throws LineUnavailableException,
+			UnsupportedAudioFileException, IOException {
+		/*
+		 * This test makes sure that the default format of a line using open()
+		 * is the same format that was passed to the mixer's getLine() function
+		 * to get the line in the first place
+		 */
+
+		File soundFile = new File("testsounds/startup.wav");
+		AudioInputStream audioInputStream = AudioSystem
+				.getAudioInputStream(soundFile);
+		AudioFormat audioFormat = audioInputStream.getFormat();
+
+		sourceDataLine = (SourceDataLine) mixer.getLine(new DataLine.Info(
+				SourceDataLine.class, audioFormat));
+		Assert.assertNotNull(sourceDataLine);
+		sourceDataLine.open();
+		Assert.assertTrue(sourceDataLine.getFormat().matches(audioFormat));
+		sourceDataLine.close();
+	}
+
+	@Test
+	public void testFindLineWithFormat() throws LineUnavailableException {
+		System.out
+				.println("This test tries to find a line with a valid format");
+
+		sourceDataLine = (SourceDataLine) mixer.getLine(new DataLine.Info(
+				SourceDataLine.class, aSupportedFormat));
+		sourceDataLine.open();
+		System.out.println(sourceDataLine.getFormat());
+		sourceDataLine.close();
+
+	}
+
+	@Test(expected = IllegalArgumentException.class)
+	public void testFindLineWithWrongFormat() throws LineUnavailableException {
+		System.out
+				.println("This test tries to acquire a line with incorrect format spec");
+		sourceDataLine = (SourceDataLine) mixer.getLine(new DataLine.Info(
+				SourceDataLine.class, new AudioFormat(
+						AudioFormat.Encoding.PCM_UNSIGNED, 44100, 10000, 1, 13,
+						10, true)));
+		sourceDataLine.open();
+		sourceDataLine.close();
+	}
+
+	@Test
+	public void testFindControl() throws LineUnavailableException {
+		sourceDataLine = (SourceDataLine) mixer.getLine(new Line.Info(
+				SourceDataLine.class));
+		sourceDataLine.open();
+		Control[] controls = sourceDataLine.getControls();
+		Assert.assertNotNull(controls);
+		Assert.assertTrue(sourceDataLine.getControls().length > 0);
+		for (Control control : controls) {
+			Assert.assertNotNull(control);
+		}
+		sourceDataLine.close();
+	}
+
+	@Test
+	public void testSupportedControls() throws LineUnavailableException {
+		sourceDataLine = (SourceDataLine) mixer.getLine(new Line.Info(
+				SourceDataLine.class));
+		sourceDataLine.open();
+		Assert.assertTrue(sourceDataLine
+				.isControlSupported(FloatControl.Type.VOLUME));
+		Assert.assertTrue(sourceDataLine
+				.isControlSupported(BooleanControl.Type.MUTE));
+		sourceDataLine.close();
+	}
+
+	@Test
+	public void testVolumeAndMute() throws Exception {
+
+		Mixer selectedMixer = mixer;
+		sourceDataLine = (SourceDataLine) selectedMixer.getLine(new Line.Info(
+				SourceDataLine.class));
+
+		File soundFile = new File(new java.io.File(".").getCanonicalPath()
+				+ "/testsounds/logout.wav");
+		AudioInputStream audioInputStream = AudioSystem
+				.getAudioInputStream(soundFile);
+		AudioFormat audioFormat = audioInputStream.getFormat();
+
+		sourceDataLine.open(audioFormat);
+		sourceDataLine.start();
+		FloatControl volume = (FloatControl) sourceDataLine
+				.getControl(FloatControl.Type.VOLUME);
+		BooleanControl mute = (BooleanControl) sourceDataLine
+				.getControl(BooleanControl.Type.MUTE);
+
+		mute.setValue(true);
+		volume.setValue(volume.getMaximum());
+
+		mute.setValue(false);
+
+		byte[] abData = new byte[1000];
+		int bytesRead = 0;
+
+		while (bytesRead >= 0) {
+			bytesRead = audioInputStream.read(abData, 0, abData.length);
+			if (bytesRead > 0) {
+				sourceDataLine.write(abData, 0, bytesRead);
+			}
+		}
+
+		sourceDataLine.drain();
+		sourceDataLine.close();
+		selectedMixer.close();
+
+	}
+
+	@Test
+	public void testVolumeChanging() throws LineUnavailableException,
+			IOException, UnsupportedAudioFileException {
+
+		Mixer selectedMixer = mixer;
+
+		sourceDataLine = (SourceDataLine) selectedMixer.getLine(new Line.Info(
+				SourceDataLine.class));
+
+		File soundFile = new File(new java.io.File(".").getCanonicalPath()
+				+ "/testsounds/logout.wav");
+		AudioInputStream audioInputStream = AudioSystem
+				.getAudioInputStream(soundFile);
+		AudioFormat audioFormat = audioInputStream.getFormat();
+
+		sourceDataLine.open(audioFormat);
+		sourceDataLine.start();
+		FloatControl volume = (FloatControl) sourceDataLine
+				.getControl(FloatControl.Type.VOLUME);
+
+		volume.setValue(volume.getMinimum());
+
+		byte[] abData = new byte[1000];
+		int bytesRead = 0;
+
+		while (bytesRead >= 0) {
+			bytesRead = audioInputStream.read(abData, 0, abData.length);
+			if (bytesRead > 0) {
+				sourceDataLine.write(abData, 0, bytesRead);
+				volume.setValue(volume.getValue() + 100);
+			}
+		}
+
+		sourceDataLine.drain();
+		sourceDataLine.close();
+		selectedMixer.close();
+
+	}
+
+	@Test
+	public void testOpenEvent() throws LineUnavailableException {
+
+		listenerCalled = 0;
+		LineListener openListener = new LineListener() {
+			public void update(LineEvent event) {
+				Assert.assertTrue(event.getType() == LineEvent.Type.OPEN);
+				PulseAudioSourceDataLineTest.this.listenerCalled++;
+			}
+		};
+
+		sourceDataLine = (SourceDataLine) mixer.getLine(new Line.Info(
+				SourceDataLine.class));
+		sourceDataLine.addLineListener(openListener);
+		sourceDataLine.open();
+		sourceDataLine.removeLineListener(openListener);
+		sourceDataLine.close();
+		Assert.assertEquals(1, listenerCalled);
+		listenerCalled = 0;
+	}
+
+	@Test
+	public void testCloseEvent() throws LineUnavailableException {
+		listenerCalled = 0;
+		LineListener closeListener = new LineListener() {
+			public void update(LineEvent event) {
+				Assert.assertTrue(event.getType() == LineEvent.Type.CLOSE);
+				PulseAudioSourceDataLineTest.this.listenerCalled++;
+			}
+		};
+
+		sourceDataLine = (SourceDataLine) mixer.getLine(new Line.Info(
+				SourceDataLine.class));
+		sourceDataLine.open();
+		sourceDataLine.addLineListener(closeListener);
+		sourceDataLine.close();
+		sourceDataLine.removeLineListener(closeListener);
+		Assert.assertEquals(1, listenerCalled);
+		listenerCalled = 0;
+	}
+
+	@Test
+	public void testCloseEventWrongListener() throws LineUnavailableException {
+		listenerCalled = 0;
+		LineListener closeListener = new LineListener() {
+			public void update(LineEvent event) {
+				PulseAudioSourceDataLineTest.this.listenerCalled++;
+			}
+		};
+
+		sourceDataLine = (SourceDataLine) mixer.getLine(new Line.Info(
+				SourceDataLine.class));
+
+		sourceDataLine.open();
+		sourceDataLine.addLineListener(closeListener);
+		sourceDataLine.removeLineListener(closeListener);
+		sourceDataLine.close();
+		Assert.assertEquals(0, listenerCalled);
+		listenerCalled = 0;
+
+	}
+
+	@Test
+	public void testFramePosition() throws UnsupportedAudioFileException,
+			IOException, LineUnavailableException {
+		File soundFile = new File("testsounds/logout.wav");
+		AudioInputStream audioInputStream = AudioSystem
+				.getAudioInputStream(soundFile);
+		AudioFormat audioFormat = audioInputStream.getFormat();
+
+		sourceDataLine = (SourceDataLine) mixer.getLine(new DataLine.Info(
+				SourceDataLine.class, audioFormat));
+		Assert.assertNotNull(sourceDataLine);
+
+		sourceDataLine.open(audioFormat);
+		sourceDataLine.start();
+
+		byte[] abData = new byte[1000];
+		int bytesRead = 0;
+
+		while (bytesRead >= 0) {
+			bytesRead = audioInputStream.read(abData, 0, abData.length);
+			if (bytesRead > 0) {
+				sourceDataLine.write(abData, 0, bytesRead);
+			}
+		}
+
+		sourceDataLine.drain();
+		sourceDataLine.stop();
+		System.out.println("frame position: "
+				+ sourceDataLine.getFramePosition());
+		long expected = 136703;
+		long granularity = 100;
+		long pos = sourceDataLine.getFramePosition();
+		Assert.assertTrue(Math.abs(expected - pos) < granularity);
+		sourceDataLine.close();
+	}
+
+	@Test
+	public void testFramePositionAfterPlayingTwice()
+			throws UnsupportedAudioFileException, IOException,
+			LineUnavailableException {
+		File soundFile = new File("testsounds/logout.wav");
+		AudioInputStream audioInputStream = AudioSystem
+				.getAudioInputStream(soundFile);
+		AudioFormat audioFormat = audioInputStream.getFormat();
+
+		sourceDataLine = (SourceDataLine) mixer.getLine(new DataLine.Info(
+				SourceDataLine.class, audioFormat));
+		Assert.assertNotNull(sourceDataLine);
+
+		sourceDataLine.open(audioFormat);
+		sourceDataLine.start();
+
+		byte[] abData = new byte[1000];
+		int bytesRead = 0;
+
+		while (bytesRead >= 0) {
+			bytesRead = audioInputStream.read(abData, 0, abData.length);
+			if (bytesRead > 0) {
+				sourceDataLine.write(abData, 0, bytesRead);
+			}
+		}
+
+		sourceDataLine.drain();
+		sourceDataLine.stop();
+
+		soundFile = new File("testsounds/logout.wav");
+		audioInputStream = AudioSystem.getAudioInputStream(soundFile);
+		audioFormat = audioInputStream.getFormat();
+
+		sourceDataLine.start();
+
+		abData = new byte[1000];
+		bytesRead = 0;
+
+		while (bytesRead >= 0) {
+			bytesRead = audioInputStream.read(abData, 0, abData.length);
+			if (bytesRead > 0) {
+				sourceDataLine.write(abData, 0, bytesRead);
+			}
+		}
+
+		sourceDataLine.drain();
+		sourceDataLine.stop();
+
+		System.out.println("frame position: "
+				+ sourceDataLine.getFramePosition());
+		long expected = 136703 * 2;
+		long granularity = 100;
+		long pos = sourceDataLine.getFramePosition();
+		Assert.assertTrue(Math.abs(expected - pos) < granularity);
+		sourceDataLine.close();
+	}
+
+	@Test
+	public void testMicroSecondPosition() throws UnsupportedAudioFileException,
+			IOException, LineUnavailableException {
+
+		File soundFile = new File("testsounds/logout.wav");
+		AudioInputStream audioInputStream = AudioSystem
+				.getAudioInputStream(soundFile);
+		AudioFormat audioFormat = audioInputStream.getFormat();
+
+		sourceDataLine = (SourceDataLine) mixer.getLine(new DataLine.Info(
+				SourceDataLine.class, audioFormat));
+		Assert.assertNotNull(sourceDataLine);
+
+		sourceDataLine.open(audioFormat);
+		sourceDataLine.start();
+
+		byte[] abData = new byte[1000];
+		int bytesRead = 0;
+
+		while (bytesRead >= 0) {
+			bytesRead = audioInputStream.read(abData, 0, abData.length);
+			if (bytesRead > 0) {
+				sourceDataLine.write(abData, 0, bytesRead);
+			}
+		}
+
+		sourceDataLine.drain();
+		sourceDataLine.stop();
+		System.out.println("time position: "
+				+ sourceDataLine.getMicrosecondPosition());
+		long expected = 6200;
+		long granularity = 100;
+		long pos = sourceDataLine.getMicrosecondPosition();
+		Assert.assertTrue(Math.abs(expected - pos) < granularity);
+		sourceDataLine.close();
+
+	}
+
+	@Test
+	public void testBufferSizes() throws LineUnavailableException {
+		sourceDataLine = (SourceDataLine) mixer.getLine(new Line.Info(
+				SourceDataLine.class));
+		sourceDataLine.open(aSupportedFormat, 10000);
+		Assert.assertEquals(10000, sourceDataLine.getBufferSize());
+		sourceDataLine.close();
+	}
+
+	@Test
+	public void testHasADefaultFormat() throws LineUnavailableException {
+		System.out.println("This test checks that a SourceDataLine has "
+				+ " a default format, and it can be opened with"
+				+ " that format");
+
+		sourceDataLine = (SourceDataLine) mixer.getLine(new Line.Info(
+				SourceDataLine.class));
+
+		/* check that there is a default format */
+		Assert.assertNotNull(sourceDataLine.getFormat());
+		System.out.println(sourceDataLine.getFormat());
+
+		/* check that the line can be opened with the default format */
+		sourceDataLine.open();
+		sourceDataLine.close();
+
+	}
+
+	@Test
+	public void testDefaultFormatWithGetLine() throws LineUnavailableException {
+		sourceDataLine = (SourceDataLine) mixer.getLine(new DataLine.Info(
+				SourceDataLine.class, aSupportedFormat, 1000));
+		Assert.assertEquals(aSupportedFormat, sourceDataLine.getFormat());
+
+	}
+
+	@Test
+	public void testDefaultBufferSize() throws LineUnavailableException {
+		sourceDataLine = (SourceDataLine) mixer.getLine(new DataLine.Info(
+				SourceDataLine.class, aSupportedFormat, 1000));
+		Assert.assertEquals(StreamBufferAttributes.SANE_DEFAULT, sourceDataLine
+				.getBufferSize());
+	}
+
+	@Test
+	public void testDrainTwice() throws LineUnavailableException {
+		sourceDataLine = (SourceDataLine) mixer.getLine(new DataLine.Info(
+				SourceDataLine.class, aSupportedFormat, 1000));
+
+		sourceDataLine.open();
+		sourceDataLine.drain();
+		sourceDataLine.drain();
+		sourceDataLine.close();
+
+	}
+
+	@Test
+	public void testDrainWithoutStartDataOnTheLine()
+			throws LineUnavailableException, UnsupportedAudioFileException,
+			IOException, InterruptedException {
+
+		File soundFile = new File("testsounds/logout.wav");
+		final AudioInputStream audioInputStream = AudioSystem
+				.getAudioInputStream(soundFile);
+		AudioFormat audioFormat = audioInputStream.getFormat();
+
+		sourceDataLine = (SourceDataLine) mixer.getLine(new DataLine.Info(
+				SourceDataLine.class, audioFormat, 1000));
+		Assert.assertNotNull(sourceDataLine);
+		sourceDataLine.open();
+		int available = sourceDataLine.available();
+		Assert.assertTrue(available > 1000);
+
+		try {
+			final byte[] abData = new byte[2000];
+			int bytesRead = 0;
+
+			bytesRead = audioInputStream.read(abData, 0, abData.length);
+			Assert.assertTrue(bytesRead > 0);
+			sourceDataLine.write(abData, 0, bytesRead);
+		} catch (IOException e) {
+			System.out.println("Error");
+		}
+
+		Thread drainer = new Thread() {
+			@Override
+			public void run() {
+				sourceDataLine.drain();
+			}
+		};
+
+		drainer.start();
+
+		drainer.join(1000);
+
+		if (drainer.isAlive()) {
+			sourceDataLine.close();
+			drainer.join(1000);
+			if (drainer.isAlive()) {
+				Assert
+						.fail("drain() does not return when the line has been closed");
+			}
+		} else {
+			Assert.fail("drain() does not block when there is data on the "
+					+ "source data line and it hasnt been started");
+		}
+
+	}
+
+	@Test
+	public void testDrainWithoutStartNoDataOnTheLine()
+			throws LineUnavailableException, UnsupportedAudioFileException,
+			IOException, InterruptedException {
+
+		File soundFile = new File("testsounds/logout.wav");
+		final AudioInputStream audioInputStream = AudioSystem
+				.getAudioInputStream(soundFile);
+		AudioFormat audioFormat = audioInputStream.getFormat();
+
+		sourceDataLine = (SourceDataLine) mixer.getLine(new DataLine.Info(
+				SourceDataLine.class, audioFormat, 1000));
+		Assert.assertNotNull(sourceDataLine);
+		sourceDataLine.open();
+		int available = sourceDataLine.available();
+		Assert.assertTrue(available > 1000);
+
+		Thread drainer = new Thread() {
+			@Override
+			public void run() {
+				sourceDataLine.drain();
+			}
+		};
+
+		drainer.start();
+		drainer.join(1000);
+
+		if (drainer.isAlive()) {
+			Assert
+					.fail("drain() does not return when there is no data on a line that hasn't been started");
+		}
+	}
+
+	@Test(expected = IllegalStateException.class)
+	public void testDrainWithoutOpen() throws LineUnavailableException {
+
+		sourceDataLine = (SourceDataLine) mixer.getLine(new DataLine.Info(
+				SourceDataLine.class, aSupportedFormat, 1000));
+
+		sourceDataLine.drain();
+
+	}
+
+	@Test
+	public void testFlushTwice() throws LineUnavailableException {
+		sourceDataLine = (SourceDataLine) mixer.getLine(new DataLine.Info(
+				SourceDataLine.class, aSupportedFormat, 1000));
+
+		sourceDataLine.open();
+		sourceDataLine.flush();
+		sourceDataLine.flush();
+		sourceDataLine.close();
+
+	}
+
+	@Test(expected = IllegalStateException.class)
+	public void testFlushWithoutOpen() throws LineUnavailableException {
+		sourceDataLine = (SourceDataLine) mixer.getLine(new DataLine.Info(
+				SourceDataLine.class, aSupportedFormat, 1000));
+
+		sourceDataLine.flush();
+
+	}
+
+	@Test
+	public void testFlushWithoutStart() throws LineUnavailableException {
+		sourceDataLine = (SourceDataLine) mixer.getLine(new DataLine.Info(
+				SourceDataLine.class, aSupportedFormat, 1000));
+		sourceDataLine.open();
+		sourceDataLine.flush();
+
+	}
+
+	@Test
+	public void testMixerKnowsAboutOpenLines() throws LineUnavailableException {
+		sourceDataLine = (SourceDataLine) mixer.getLine(new Line.Info(
+				SourceDataLine.class));
+
+		Assert.assertEquals(0, mixer.getSourceLines().length);
+		sourceDataLine.open();
+		Assert.assertEquals(1, mixer.getSourceLines().length);
+		Assert.assertTrue(sourceDataLine == mixer.getSourceLines()[0]);
+		sourceDataLine.close();
+		Assert.assertEquals(0, mixer.getSourceLines().length);
+
+	}
+
+	@Test
+	public void testMixerKnowsAboutOpen2Lines() throws LineUnavailableException {
+		sourceDataLine = (SourceDataLine) mixer.getLine(new Line.Info(
+				SourceDataLine.class));
+
+		Assert.assertEquals(0, mixer.getSourceLines().length);
+		sourceDataLine.open(aSupportedFormat);
+		Assert.assertEquals(1, mixer.getSourceLines().length);
+		Assert.assertTrue(sourceDataLine == mixer.getSourceLines()[0]);
+		sourceDataLine.close();
+		Assert.assertEquals(0, mixer.getSourceLines().length);
+
+	}
+
+	@Test
+	public void testMixerKnowsAboutOpen3Lines() throws LineUnavailableException {
+		sourceDataLine = (SourceDataLine) mixer.getLine(new Line.Info(
+				SourceDataLine.class));
+
+		Assert.assertEquals(0, mixer.getSourceLines().length);
+		sourceDataLine.open(aSupportedFormat, 10000);
+		Assert.assertEquals(1, mixer.getSourceLines().length);
+		Assert.assertTrue(sourceDataLine == mixer.getSourceLines()[0]);
+		sourceDataLine.close();
+		Assert.assertEquals(0, mixer.getSourceLines().length);
+
+	}
+
+	@Test
+	public void testAllSourceLinesClosed() {
+		Assert.assertEquals(0, mixer.getSourceLines().length);
+
+	}
+
+	public static void debug(String string) {
+		System.out.println("DEBUG: " + string);
+	}
+
+	@Ignore
+	@Test
+	public void testSynchronization() throws Exception {
+		Mixer.Info mixerInfos[] = AudioSystem.getMixerInfo();
+		Mixer.Info selectedMixerInfo = null;
+		// int i = 0;
+		for (Mixer.Info info : mixerInfos) {
+			// System.out.println("Mixer Line " + i++ + ": " + info.getName() +
+			// " " + info.getDescription());
+			if (info.getName().contains("PulseAudio")) {
+				selectedMixerInfo = info;
+				System.out.println(selectedMixerInfo);
+			}
+		}
+
+		PulseAudioMixer mixer = (PulseAudioMixer) AudioSystem
+				.getMixer(selectedMixerInfo);
+
+		mixer.open();
+
+		String fileName1 = "testsounds/startup.wav";
+		File soundFile1 = new File(fileName1);
+		AudioInputStream audioInputStream1 = AudioSystem
+				.getAudioInputStream(soundFile1);
+
+		PulseAudioSourceDataLine line1;
+		line1 = (PulseAudioSourceDataLine) mixer.getLine(new Line.Info(
+				SourceDataLine.class));
+		line1.setName("Line 1");
+
+		String fileName2 = "testsounds/logout.wav";
+		File soundFile = new File(fileName2);
+		AudioInputStream audioInputStream2 = AudioSystem
+				.getAudioInputStream(soundFile);
+
+		PulseAudioSourceDataLine line2;
+		line2 = (PulseAudioSourceDataLine) mixer.getLine(new Line.Info(
+				SourceDataLine.class));
+		line2.setName("Line 2");
+
+		ThreadWriter writer1 = new ThreadWriter(audioInputStream1, line1);
+		ThreadWriter writer2 = new ThreadWriter(audioInputStream2, line2);
+		// line2.start();
+		// line1.start();
+
+		Line[] lines = { line1, line2 };
+		mixer.synchronize(lines, true);
+
+		// line2.stop();
+
+		debug("PulseAudioMixer: " + line1.getName() + " and " + line2.getName()
+				+ " synchronized");
+		writer1.start();
+		writer2.start();
+
+		debug("PulseAudioMixer: writers started");
+		line2.start();
+		// line1.stop();
+		// line1.start();
+		debug("PulseAudioMixer: Started a line");
+
+		writer1.join();
+		writer2.join();
+
+		debug("PulseAudioMixer: both lines joined");
+
+		line2.close();
+		debug("PulseAudioMixer: " + line2.getName() + " closed");
+
+		line1.close();
+		debug("PulseAudioMixer: " + line1.getName() + " closed");
+
+		mixer.close();
+		debug("PulseAudioMixer: mixer closed");
+
+	}
+
+	@After
+	public void tearDown() throws Exception {
+		started = 0;
+		stopped = 0;
+
+		if (sourceDataLine != null && sourceDataLine.isOpen()) {
+			sourceDataLine.close();
+		}
+
+		if (mixer.isOpen()) {
+			mixer.close();
+		}
+
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pulseaudio/unittests/org/classpath/icedtea/pulseaudio/PulseAudioSourcePortTest.java	Fri Oct 10 16:03:12 2008 -0400
@@ -0,0 +1,122 @@
+/* PulseAudioSourcePortTest.java
+   Copyright (C) 2008 Red Hat, Inc.
+
+This file is part of IcedTea.
+
+IcedTea is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License as published by
+the Free Software Foundation, version 2.
+
+IcedTea is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with IcedTea; see the file COPYING.  If not, write to
+the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library.  Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module.  An independent module is a module which is not derived from
+or based on this library.  If you modify this library, you may extend
+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;
+
+import static org.junit.Assert.assertNotNull;
+
+import javax.sound.sampled.AudioSystem;
+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.Info mixerInfos[] = AudioSystem.getMixerInfo();
+		Mixer.Info selectedMixerInfo = null;
+		// int i = 0;
+		for (Mixer.Info info : mixerInfos) {
+			// System.out.println("Mixer Line " + i++ + ": " + info.getName() +
+			// " " + info.getDescription());
+			if (info.getName().contains("PulseAudio")) {
+				selectedMixerInfo = info;
+			}
+		}
+		assertNotNull(selectedMixerInfo);
+		mixer = AudioSystem.getMixer(selectedMixerInfo);
+		assertNotNull(mixer);
+		if (mixer.isOpen()) {
+			mixer.close();
+		}
+
+		mixer.open();
+
+	}
+
+	@Test
+	public void testClose() throws LineUnavailableException {
+		Line.Info[] lineInfos = mixer.getSourceLineInfo();
+		for (Line.Info info : lineInfos) {
+			if (info.getLineClass() == Port.class) {
+				System.out.println(info.toString());
+				Port port = (Port) mixer.getLine(info);
+				port.close();
+			}
+		}
+	}
+
+	@Test
+	public void testControls() throws LineUnavailableException {
+		Line.Info[] lineInfos = mixer.getSourceLineInfo();
+		for (Line.Info info : lineInfos) {
+			if (info.getLineClass() == Port.class) {
+				System.out.println(info.toString());
+				Port port = (Port) mixer.getLine(info);
+				if (!port.isOpen()) {
+					port.open();
+				}
+				FloatControl volumeControl = (FloatControl) port
+						.getControl(FloatControl.Type.VOLUME);
+				volumeControl.setValue(60000);
+				BooleanControl muteControl = (BooleanControl) port
+						.getControl(BooleanControl.Type.MUTE);
+				muteControl.setValue(true);
+				muteControl.setValue(false);
+				port.close();
+			}
+		}
+	}
+
+	@After
+	public void tearDown() {
+		if (mixer.isOpen()) {
+			mixer.close();
+		}
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pulseaudio/unittests/org/classpath/icedtea/pulseaudio/PulseAudioTargetDataLineTest.java	Fri Oct 10 16:03:12 2008 -0400
@@ -0,0 +1,648 @@
+/* PulseAudioTargetDataLineTest.java
+   Copyright (C) 2008 Red Hat, Inc.
+
+This file is part of IcedTea.
+
+IcedTea is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License as published by
+the Free Software Foundation, version 2.
+
+IcedTea is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with IcedTea; see the file COPYING.  If not, write to
+the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library.  Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module.  An independent module is a module which is not derived from
+or based on this library.  If you modify this library, you may extend
+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;
+
+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.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.TargetDataLine;
+import javax.sound.sampled.UnsupportedAudioFileException;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class PulseAudioTargetDataLineTest {
+
+	private Mixer mixer;
+	private TargetDataLine targetDataLine;
+
+	int started = 0;
+	int stopped = 0;
+
+	AudioFormat aSupportedFormat = new AudioFormat(
+			AudioFormat.Encoding.PCM_UNSIGNED, 44100f, 8, 1, 1, 44100f, true);
+
+	class ThreadReader extends Thread {
+		TargetDataLine line;
+		byte[] buffer;
+
+		public ThreadReader(TargetDataLine line, byte[] buffer)
+				throws LineUnavailableException {
+
+			this.line = line;
+			this.buffer = buffer;
+
+		}
+
+		@Override
+		public void run() {
+			int bytesRead = 0;
+
+			bytesRead = line.read(buffer, 0, buffer.length);
+			// System.out.println("read data");
+
+		}
+	}
+
+	@Before
+	public void setUp() throws LineUnavailableException {
+		Mixer.Info[] mixerInfos = AudioSystem.getMixerInfo();
+		Mixer.Info wantedMixerInfo = null;
+		for (Mixer.Info mixerInfo : mixerInfos) {
+			if (mixerInfo.getName().contains("PulseAudio")) {
+				wantedMixerInfo = mixerInfo;
+			}
+		}
+		Assert.assertNotNull(wantedMixerInfo);
+		mixer = AudioSystem.getMixer(wantedMixerInfo);
+		Assert.assertNotNull(mixer);
+		mixer.open();
+		targetDataLine = null;
+		started = 0;
+		stopped = 0;
+
+	}
+
+	@Test
+	public void testOpenClose() throws LineUnavailableException {
+		targetDataLine = (TargetDataLine) mixer.getLine(new Line.Info(
+				TargetDataLine.class));
+		Assert.assertNotNull(targetDataLine);
+		targetDataLine.open();
+		targetDataLine.close();
+	}
+
+	@Test
+	public void testIsActiveAndIsOpen() throws LineUnavailableException {
+
+		TargetDataLine line = (TargetDataLine) mixer.getLine(new DataLine.Info(
+				TargetDataLine.class, aSupportedFormat, 1000));
+
+		Assert.assertFalse(line.isActive());
+		Assert.assertFalse(line.isOpen());
+		line.open();
+		Assert.assertTrue(line.isOpen());
+		Assert.assertFalse(line.isActive());
+		line.start();
+		Assert.assertTrue(line.isOpen());
+		Assert.assertTrue(line.isActive());
+		line.stop();
+		Assert.assertTrue(line.isOpen());
+		Assert.assertFalse(line.isActive());
+		line.close();
+		Assert.assertFalse(line.isOpen());
+		Assert.assertFalse(line.isActive());
+
+	}
+
+	@Test
+	public void testOpenEvents() throws LineUnavailableException {
+		targetDataLine = (TargetDataLine) mixer.getLine(new Line.Info(
+				TargetDataLine.class));
+		Assert.assertNotNull(targetDataLine);
+
+		LineListener openListener = new LineListener() {
+			private int calledCount = 0;
+
+			@Override
+			public void update(LineEvent event) {
+				Assert.assertEquals(LineEvent.Type.OPEN, event.getType());
+				System.out.println("OPEN");
+				calledCount++;
+				Assert.assertEquals(1, calledCount);
+			}
+
+		};
+
+		targetDataLine.addLineListener(openListener);
+		targetDataLine.open();
+		targetDataLine.removeLineListener(openListener);
+		targetDataLine.close();
+
+	}
+
+	@Test
+	public void testOpenWithFormat() throws LineUnavailableException {
+		System.out.println("This test checks that open(AudioFormat) works");
+
+		targetDataLine = (TargetDataLine) mixer.getLine(new Line.Info(
+				TargetDataLine.class));
+		Assert.assertNotNull(targetDataLine);
+		targetDataLine.open(aSupportedFormat);
+
+	}
+
+	@Test
+	public void testRead() throws LineUnavailableException {
+		System.out.println("This test checks that read() sort of wroks");
+
+		targetDataLine = (TargetDataLine) mixer.getLine(new Line.Info(
+				TargetDataLine.class));
+		Assert.assertNotNull(targetDataLine);
+		targetDataLine.open(aSupportedFormat);
+
+		byte[] buffer = new byte[1000];
+		for (int i = 0; i < buffer.length; i++) {
+			buffer[i] = 0;
+		}
+		targetDataLine.start();
+
+		targetDataLine.read(buffer, 0, buffer.length);
+		Assert.assertTrue(buffer[999] != 0);
+
+		buffer = new byte[1000];
+		for (int i = 0; i < buffer.length; i++) {
+			buffer[i] = 0;
+		}
+
+		targetDataLine.read(buffer, 0, buffer.length - 2);
+		Assert.assertTrue(buffer[999] == 0);
+
+		targetDataLine.stop();
+		targetDataLine.close();
+
+	}
+
+	@Test(expected = IllegalArgumentException.class)
+	public void testReadLessThanFrameSize() throws LineUnavailableException {
+		System.out.println("This test checks that read() throws an exception "
+				+ "when not reading an integral number of frames");
+
+		targetDataLine = (TargetDataLine) mixer.getLine(new Line.Info(
+				TargetDataLine.class));
+		Assert.assertNotNull(targetDataLine);
+		AudioFormat breakingFormat = new AudioFormat(
+				AudioFormat.Encoding.PCM_UNSIGNED, 44100f, 8, 2, 2, 44100f,
+				true);
+		targetDataLine.open(breakingFormat);
+
+		byte[] buffer = new byte[1000];
+		for (int i = 0; i < buffer.length; i++) {
+			buffer[i] = 0;
+		}
+		targetDataLine.start();
+
+		targetDataLine.read(buffer, 0, buffer.length - 1);
+
+		targetDataLine.stop();
+		targetDataLine.close();
+
+	}
+
+	@Test
+	public void testReadAndClose() throws LineUnavailableException,
+			InterruptedException {
+		System.out.println("This test tries to close a line while "
+				+ "read()ing to check that read() returns");
+		targetDataLine = (TargetDataLine) mixer.getLine(new Line.Info(
+				TargetDataLine.class));
+		Assert.assertNotNull(targetDataLine);
+		AudioFormat breakingFormat = new AudioFormat(
+				AudioFormat.Encoding.PCM_UNSIGNED, 44100f, 8, 2, 2, 44100f,
+				true);
+		targetDataLine.open(breakingFormat);
+		targetDataLine.start();
+		byte[] buffer = new byte[1000000];
+
+		ThreadReader reader = new ThreadReader(targetDataLine, buffer);
+		reader.start();
+
+		Thread.sleep(100);
+
+		Assert.assertTrue(reader.isAlive());
+
+		targetDataLine.close();
+
+		reader.join(500);
+
+		Assert.assertFalse(reader.isAlive());
+
+	}
+
+	@Test
+	public void testReadAndStop() throws LineUnavailableException,
+			InterruptedException {
+		targetDataLine = (TargetDataLine) mixer.getLine(new Line.Info(
+				TargetDataLine.class));
+		Assert.assertNotNull(targetDataLine);
+		AudioFormat breakingFormat = new AudioFormat(
+				AudioFormat.Encoding.PCM_UNSIGNED, 44100f, 8, 2, 2, 44100f,
+				true);
+		targetDataLine.open(breakingFormat);
+		targetDataLine.start();
+		byte[] buffer = new byte[10000000];
+
+		ThreadReader reader = new ThreadReader(targetDataLine, buffer);
+		reader.start();
+
+		Thread.sleep(100);
+
+		Assert.assertTrue(reader.isAlive());
+
+		targetDataLine.stop();
+
+		Thread.sleep(100);
+
+		Assert.assertFalse(reader.isAlive());
+
+		targetDataLine.close();
+
+	}
+
+	// this is kind of messed up
+	// drain should hang on a started data line
+	// but read should return
+	@Test
+	public void testReadAndDrain() throws LineUnavailableException,
+			InterruptedException {
+		targetDataLine = (TargetDataLine) mixer.getLine(new Line.Info(
+				TargetDataLine.class));
+		Assert.assertNotNull(targetDataLine);
+		AudioFormat breakingFormat = new AudioFormat(
+				AudioFormat.Encoding.PCM_UNSIGNED, 44100f, 8, 2, 2, 44100f,
+				true);
+		targetDataLine.open(breakingFormat);
+		targetDataLine.start();
+		byte[] buffer = new byte[10000000];
+
+		ThreadReader reader = new ThreadReader(targetDataLine, buffer);
+		reader.start();
+
+		Thread.sleep(100);
+
+		Assert.assertTrue(reader.isAlive());
+
+		Thread drainer = new Thread() {
+
+			@Override
+			public void run() {
+				targetDataLine.drain();
+
+			}
+
+		};
+
+		drainer.start();
+
+		Thread.sleep(100);
+
+		Assert.assertFalse(reader.isAlive());
+
+		targetDataLine.stop();
+
+		Thread.sleep(100);
+		Assert.assertFalse(drainer.isAlive());
+
+		targetDataLine.close();
+	}
+
+	@Test
+	public void testReadAndFlush() throws LineUnavailableException,
+			InterruptedException {
+		targetDataLine = (TargetDataLine) mixer.getLine(new Line.Info(
+				TargetDataLine.class));
+		Assert.assertNotNull(targetDataLine);
+		AudioFormat breakingFormat = new AudioFormat(
+				AudioFormat.Encoding.PCM_UNSIGNED, 44100f, 8, 2, 2, 44100f,
+				true);
+		targetDataLine.open(breakingFormat);
+		targetDataLine.start();
+		byte[] buffer = new byte[10000000];
+
+		ThreadReader reader = new ThreadReader(targetDataLine, buffer);
+		reader.start();
+
+		Thread.sleep(100);
+
+		Assert.assertTrue(reader.isAlive());
+
+		targetDataLine.flush();
+
+		Thread.sleep(100);
+
+		Assert.assertFalse(reader.isAlive());
+
+		targetDataLine.stop();
+		targetDataLine.close();
+	}
+
+	@Test
+	public void testDrain() throws LineUnavailableException,
+			InterruptedException {
+		System.out
+				.println("This test checks that drain() on a start()ed TargetDataLine hangs");
+
+		targetDataLine = (TargetDataLine) mixer.getLine(new Line.Info(
+				TargetDataLine.class));
+		Assert.assertNotNull(targetDataLine);
+
+		targetDataLine.open();
+		targetDataLine.start();
+
+		Thread th = new Thread(new Runnable() {
+
+			@Override
+			public void run() {
+				targetDataLine.drain();
+			}
+
+		});
+
+		th.start();
+
+		th.join(5000);
+
+		if (!th.isAlive()) {
+			targetDataLine.stop();
+			th.join();
+			targetDataLine.close();
+			Assert.fail("drain() on a opened TargetDataLine should hang");
+		}
+	}
+
+	@Test(expected = IllegalStateException.class)
+	public void testDrainWihtoutOpen() throws LineUnavailableException {
+		System.out
+				.println("This test checks that drain() fails on a line not opened");
+
+		targetDataLine = (TargetDataLine) mixer.getLine(new Line.Info(
+				TargetDataLine.class));
+		Assert.assertNotNull(targetDataLine);
+
+		targetDataLine.drain();
+
+	}
+
+	@Test
+	public void testFlush() throws LineUnavailableException {
+		System.out.println("This test checks that flush() wroks");
+
+		targetDataLine = (TargetDataLine) mixer.getLine(new Line.Info(
+				TargetDataLine.class));
+		Assert.assertNotNull(targetDataLine);
+		targetDataLine.open();
+
+		byte[] buffer = new byte[1000];
+		for (int i = 0; i < buffer.length; i++) {
+			buffer[i] = 0;
+		}
+		targetDataLine.start();
+
+		targetDataLine.read(buffer, 0, buffer.length);
+		targetDataLine.stop();
+		targetDataLine.flush();
+		targetDataLine.close();
+	}
+
+	@Test(expected = IllegalStateException.class)
+	public void testFlushWithoutOpen() throws LineUnavailableException {
+		System.out
+				.println("This test checks that flush() fails on a line not opened");
+
+		targetDataLine = (TargetDataLine) mixer.getLine(new Line.Info(
+				TargetDataLine.class));
+		Assert.assertNotNull(targetDataLine);
+
+		targetDataLine.flush();
+	}
+
+	@Test
+	public void testCloseEvents() throws LineUnavailableException {
+		targetDataLine = (TargetDataLine) mixer.getLine(new Line.Info(
+				TargetDataLine.class));
+		Assert.assertNotNull(targetDataLine);
+
+		LineListener closeListener = new LineListener() {
+			private int calledCount = 0;
+
+			@Override
+			public void update(LineEvent event) {
+				Assert.assertEquals(LineEvent.Type.CLOSE, event.getType());
+				System.out.println("CLOSE");
+				calledCount++;
+				Assert.assertEquals(1, calledCount);
+			}
+
+		};
+
+		targetDataLine.open();
+		targetDataLine.addLineListener(closeListener);
+		targetDataLine.close();
+		targetDataLine.removeLineListener(closeListener);
+
+	}
+
+	@Test
+	public void testStartedStopped() throws LineUnavailableException,
+			UnsupportedAudioFileException, IOException, InterruptedException {
+
+		File soundFile = new File("testsounds/startup.wav");
+		AudioInputStream audioInputStream = AudioSystem
+				.getAudioInputStream(soundFile);
+		AudioFormat audioFormat = audioInputStream.getFormat();
+
+		TargetDataLine line;
+		line = (TargetDataLine) mixer.getLine(new DataLine.Info(
+				TargetDataLine.class, audioFormat));
+		Assert.assertNotNull(line);
+
+		started = 0;
+		stopped = 0;
+
+		line.open(audioFormat);
+
+		LineListener startStopListener = new LineListener() {
+
+			@Override
+			public void update(LineEvent event) {
+				if (event.getType() == LineEvent.Type.START) {
+					System.out.println("START");
+					started++;
+					Assert.assertEquals(1, started);
+				}
+
+				if (event.getType() == LineEvent.Type.STOP) {
+					System.out.println("STOP");
+					stopped++;
+					Assert.assertEquals(1, stopped);
+				}
+			}
+
+		};
+
+		line.addLineListener(startStopListener);
+
+		line.start();
+
+		Thread.sleep(100);
+
+		line.stop();
+		line.close();
+
+		Assert.assertEquals(1, started);
+		Assert.assertEquals(1, stopped);
+
+	}
+
+	@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();
+	}
+
+	@Test
+	public void testFramePosition() throws LineUnavailableException {
+		System.out
+				.println("This test tests frame position for a target data line");
+
+		final int CHUNCKS = 100;
+		final int BUFFER_SIZE = 1000;
+		targetDataLine = (TargetDataLine) mixer.getLine(new Line.Info(
+				TargetDataLine.class));
+
+		targetDataLine.open();
+		targetDataLine.start();
+		byte[] data = new byte[BUFFER_SIZE];
+
+		for (int i = 0; i < CHUNCKS; i++) {
+			targetDataLine.read(data, 0, data.length);
+		}
+
+		targetDataLine.stop();
+		long pos = targetDataLine.getLongFramePosition();
+		System.out.println("Frames read: " + pos);
+		long expected = BUFFER_SIZE * CHUNCKS
+				/ targetDataLine.getFormat().getFrameSize();
+		System.out.println("Expected: " + expected);
+		long granularity = 2;
+		Assert.assertTrue(Math.abs(expected - pos) < granularity);
+		targetDataLine.close();
+	}
+
+	@Test
+	public void testFramePositionWithStartAndStop()
+			throws LineUnavailableException, InterruptedException {
+		System.out
+				.println("This test tests frame position for a target data line");
+
+		final int CHUNCKS = 100;
+		final int BUFFER_SIZE = 1000;
+		targetDataLine = (TargetDataLine) mixer.getLine(new Line.Info(
+				TargetDataLine.class));
+
+		targetDataLine.open();
+		targetDataLine.start();
+		byte[] data = new byte[BUFFER_SIZE];
+
+		for (int i = 0; i < CHUNCKS; i++) {
+			if (i == CHUNCKS / 2) {
+				targetDataLine.stop();
+				Thread.sleep(1000);
+				targetDataLine.start();
+			}
+
+			targetDataLine.read(data, 0, data.length);
+		}
+
+		targetDataLine.stop();
+		long pos = targetDataLine.getLongFramePosition();
+		System.out.println("Frames read: " + pos);
+		long expected = BUFFER_SIZE * CHUNCKS
+				/ targetDataLine.getFormat().getFrameSize();
+		System.out.println("Expected: " + expected);
+		long granularity = 2;
+		Assert.assertTrue(Math.abs(expected - pos) < granularity);
+		targetDataLine.close();
+
+	}
+
+	@After
+	public void tearDown() {
+		if (targetDataLine != null) {
+			if (targetDataLine.isActive()) {
+				targetDataLine.stop();
+			}
+
+			if (targetDataLine.isOpen()) {
+				targetDataLine.close();
+			}
+		}
+
+		if (mixer.isOpen()) {
+			mixer.close();
+		}
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pulseaudio/unittests/org/classpath/icedtea/pulseaudio/PulseAudioTargetPortTest.java	Fri Oct 10 16:03:12 2008 -0400
@@ -0,0 +1,122 @@
+/* PulseAudioTargetPortTest.java
+   Copyright (C) 2008 Red Hat, Inc.
+
+This file is part of IcedTea.
+
+IcedTea is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License as published by
+the Free Software Foundation, version 2.
+
+IcedTea is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with IcedTea; see the file COPYING.  If not, write to
+the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library.  Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module.  An independent module is a module which is not derived from
+or based on this library.  If you modify this library, you may extend
+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;
+
+import static org.junit.Assert.assertNotNull;
+
+import javax.sound.sampled.AudioSystem;
+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.Info mixerInfos[] = AudioSystem.getMixerInfo();
+		Mixer.Info selectedMixerInfo = null;
+		// int i = 0;
+		for (Mixer.Info info : mixerInfos) {
+			// System.out.println("Mixer Line " + i++ + ": " + info.getName() +
+			// " " + info.getDescription());
+			if (info.getName().contains("PulseAudio")) {
+				selectedMixerInfo = info;
+			}
+		}
+		assertNotNull(selectedMixerInfo);
+		mixer = AudioSystem.getMixer(selectedMixerInfo);
+		assertNotNull(mixer);
+		if (mixer.isOpen()) {
+			mixer.close();
+		}
+		
+		mixer.open();
+
+	}
+
+	@Test
+	public void testClose() throws LineUnavailableException {
+		Line.Info[] lineInfos = mixer.getTargetLineInfo();
+		for (Line.Info info : lineInfos) {
+			if (info.getLineClass() == Port.class) {
+				System.out.println(info.toString());
+				Port port = (Port) mixer.getLine(info);
+				port.close();
+			}
+		}
+	}
+
+	@Test
+	public void testControls() throws LineUnavailableException {
+		Line.Info[] lineInfos = mixer.getTargetLineInfo();
+		for (Line.Info info : lineInfos) {
+			if (info.getLineClass() == Port.class) {
+				System.out.println(info.toString());
+				Port port = (Port) mixer.getLine(info);
+				if (!port.isOpen()) {
+					port.open();
+				}
+				FloatControl volumeControl = (FloatControl) port
+						.getControl(FloatControl.Type.VOLUME);
+				volumeControl.setValue(60000);
+				BooleanControl muteControl = (BooleanControl) port
+						.getControl(BooleanControl.Type.MUTE);
+				muteControl.setValue(true);
+				muteControl.setValue(false);
+				port.close();
+			}
+		}
+	}
+
+	@After
+	public void tearDown() {
+		if (mixer.isOpen()) {
+			mixer.close();
+		}
+	}
+
+}