changeset 0:86856b2e3d18

PR1741: Break PulseAudio provider out into IcedTea-Sound 2014-06-11 Andrew John Hughes <gnu.andrew@redhat.com> PR1741: Break PulseAudio provider out into IcedTea-Sound Initial import into IcedTea-Sound. * .hgignore, * AUTHORS, * COPYING, * ChangeLog, * Makefile.am, * NEWS, * README, * acinclude.m4, * autogen.sh, * configure.ac, * javac.in, * src/java/META-INF/services/javax.sound.sampled.spi.MixerProvider, * src/java/org/classpath/icedtea/pulseaudio/ContextEvent.java, * src/java/org/classpath/icedtea/pulseaudio/ContextListener.java, * src/java/org/classpath/icedtea/pulseaudio/Debug.java, * src/java/org/classpath/icedtea/pulseaudio/EventLoop.java, * src/java/org/classpath/icedtea/pulseaudio/Operation.java, * src/java/org/classpath/icedtea/pulseaudio/PulseAudioClip.java, * src/java/org/classpath/icedtea/pulseaudio/PulseAudioDataLine.java, * src/java/org/classpath/icedtea/pulseaudio/PulseAudioLine.java, * src/java/org/classpath/icedtea/pulseaudio/PulseAudioMixer.java, * src/java/org/classpath/icedtea/pulseaudio/PulseAudioMixerInfo.java, * src/java/org/classpath/icedtea/pulseaudio/PulseAudioMixerProvider.java, * src/java/org/classpath/icedtea/pulseaudio/PulseAudioPlaybackLine.java, * src/java/org/classpath/icedtea/pulseaudio/PulseAudioPort.java, * src/java/org/classpath/icedtea/pulseaudio/PulseAudioSourceDataLine.java, * src/java/org/classpath/icedtea/pulseaudio/PulseAudioSourcePort.java, * src/java/org/classpath/icedtea/pulseaudio/PulseAudioTargetDataLine.java, * src/java/org/classpath/icedtea/pulseaudio/PulseAudioTargetPort.java, * src/java/org/classpath/icedtea/pulseaudio/PulseAudioVolumeControl.java, * src/java/org/classpath/icedtea/pulseaudio/SecurityWrapper.java, * src/java/org/classpath/icedtea/pulseaudio/Stream.java, * src/java/org/classpath/icedtea/pulseaudio/StreamBufferAttributes.java, * src/java/org/classpath/icedtea/pulseaudio/StreamSampleSpecification.java, * src/native/jni-common.c, * src/native/jni-common.h, * src/native/org_classpath_icedtea_pulseaudio_ContextEvent.c, * src/native/org_classpath_icedtea_pulseaudio_EventLoop.c, * src/native/org_classpath_icedtea_pulseaudio_Operation.c, * src/native/org_classpath_icedtea_pulseaudio_PulseAudioSourcePort.c, * src/native/org_classpath_icedtea_pulseaudio_PulseAudioTargetPort.c, * src/native/org_classpath_icedtea_pulseaudio_Stream.c, * testsounds/README, * unittests/org/classpath/icedtea/pulseaudio/OtherSoundProvidersAvailableTest.java, * unittests/org/classpath/icedtea/pulseaudio/PulseAudioClipTest.java, * unittests/org/classpath/icedtea/pulseaudio/PulseAudioEventLoopOverhead.java, * unittests/org/classpath/icedtea/pulseaudio/PulseAudioMixerProviderTest.java, * unittests/org/classpath/icedtea/pulseaudio/PulseAudioMixerRawTest.java, * unittests/org/classpath/icedtea/pulseaudio/PulseAudioMixerTest.java, * unittests/org/classpath/icedtea/pulseaudio/PulseAudioSourceDataLineRawTest.java, * unittests/org/classpath/icedtea/pulseaudio/PulseAudioSourceDataLineTest.java, * unittests/org/classpath/icedtea/pulseaudio/PulseAudioSourcePortTest.java, * unittests/org/classpath/icedtea/pulseaudio/PulseAudioTargetDataLineTest.java, * unittests/org/classpath/icedtea/pulseaudio/PulseAudioTargetPortTest.java: Added and updated.
author andrew
date Thu, 12 Jun 2014 00:42:40 +0100
parents
children 264997426923
files .hgignore AUTHORS COPYING ChangeLog Makefile.am NEWS README acinclude.m4 autogen.sh configure.ac javac.in src/java/META-INF/services/javax.sound.sampled.spi.MixerProvider src/java/org/classpath/icedtea/pulseaudio/ContextEvent.java src/java/org/classpath/icedtea/pulseaudio/ContextListener.java src/java/org/classpath/icedtea/pulseaudio/Debug.java src/java/org/classpath/icedtea/pulseaudio/EventLoop.java src/java/org/classpath/icedtea/pulseaudio/Operation.java src/java/org/classpath/icedtea/pulseaudio/PulseAudioClip.java src/java/org/classpath/icedtea/pulseaudio/PulseAudioDataLine.java src/java/org/classpath/icedtea/pulseaudio/PulseAudioLine.java src/java/org/classpath/icedtea/pulseaudio/PulseAudioMixer.java src/java/org/classpath/icedtea/pulseaudio/PulseAudioMixerInfo.java src/java/org/classpath/icedtea/pulseaudio/PulseAudioMixerProvider.java src/java/org/classpath/icedtea/pulseaudio/PulseAudioPlaybackLine.java src/java/org/classpath/icedtea/pulseaudio/PulseAudioPort.java src/java/org/classpath/icedtea/pulseaudio/PulseAudioSourceDataLine.java src/java/org/classpath/icedtea/pulseaudio/PulseAudioSourcePort.java src/java/org/classpath/icedtea/pulseaudio/PulseAudioTargetDataLine.java src/java/org/classpath/icedtea/pulseaudio/PulseAudioTargetPort.java src/java/org/classpath/icedtea/pulseaudio/PulseAudioVolumeControl.java src/java/org/classpath/icedtea/pulseaudio/SecurityWrapper.java src/java/org/classpath/icedtea/pulseaudio/Stream.java src/java/org/classpath/icedtea/pulseaudio/StreamBufferAttributes.java src/java/org/classpath/icedtea/pulseaudio/StreamSampleSpecification.java src/native/jni-common.c src/native/jni-common.h src/native/org_classpath_icedtea_pulseaudio_ContextEvent.c src/native/org_classpath_icedtea_pulseaudio_EventLoop.c src/native/org_classpath_icedtea_pulseaudio_Operation.c src/native/org_classpath_icedtea_pulseaudio_PulseAudioSourcePort.c src/native/org_classpath_icedtea_pulseaudio_PulseAudioTargetPort.c src/native/org_classpath_icedtea_pulseaudio_Stream.c testsounds/README testsounds/error.wav testsounds/logout.wav testsounds/startup.wav unittests/org/classpath/icedtea/pulseaudio/OtherSoundProvidersAvailableTest.java unittests/org/classpath/icedtea/pulseaudio/PulseAudioClipTest.java unittests/org/classpath/icedtea/pulseaudio/PulseAudioEventLoopOverhead.java unittests/org/classpath/icedtea/pulseaudio/PulseAudioMixerProviderTest.java unittests/org/classpath/icedtea/pulseaudio/PulseAudioMixerRawTest.java unittests/org/classpath/icedtea/pulseaudio/PulseAudioMixerTest.java unittests/org/classpath/icedtea/pulseaudio/PulseAudioSourceDataLineRawTest.java unittests/org/classpath/icedtea/pulseaudio/PulseAudioSourceDataLineTest.java unittests/org/classpath/icedtea/pulseaudio/PulseAudioSourcePortTest.java unittests/org/classpath/icedtea/pulseaudio/PulseAudioTargetDataLineTest.java unittests/org/classpath/icedtea/pulseaudio/PulseAudioTargetPortTest.java
diffstat 57 files changed, 13929 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.hgignore	Thu Jun 12 00:42:40 2014 +0100
@@ -0,0 +1,13 @@
+syntax: glob
+*~
+build
+configure
+missing
+aclocal.m4
+autom4te.cache
+config.guess
+config.sub
+config.status
+install-sh
+Makefile.in
+compile
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/AUTHORS	Thu Jun 12 00:42:40 2014 +0100
@@ -0,0 +1,32 @@
+# AUTHORS -- List of authors
+# Copyright (C) 2014 Red Hat, Inc.
+#
+# This file is part of IcedTea-Sound.
+#
+# IcedTea-Sound 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-Sound 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-Sound; see the file COPYING.  If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 USA.
+
+The following people have made contibutions to this project.
+project. If your name does not appear, but should, let
+us know. Please keep this list in alphabetic order.
+
+Andrew John Hughes <gnu_andrew@member.fsf.org, gnu.andrew@redhat.com>
+Ioana Ivan <iivan@redhat.com>
+Matthias Klose <doko@ubuntu.com>
+Denis Lila <dlila@redhat.com>
+Omair Majid <omajid@redhat.com>
+Xerxes RĂ„nby <xerxes@zafena.se>
+Marc Schoenefeld <mschoene@redhat.com>
+Joshua Sumali <jsumali@redhat.com>
+Jon VanAlten <jon.vanalten@redhat.com>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/COPYING	Thu Jun 12 00:42:40 2014 +0100
@@ -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/ChangeLog	Thu Jun 12 00:42:40 2014 +0100
@@ -0,0 +1,1016 @@
+2014-06-11  Andrew John Hughes  <gnu.andrew@redhat.com>
+
+	PR1741: Break PulseAudio provider out into IcedTea-Sound
+	Initial import into IcedTea-Sound.
+	* .hgignore,
+	* AUTHORS,
+	* COPYING,
+	* ChangeLog,
+	* Makefile.am,
+	* NEWS,
+	* README,
+	* acinclude.m4,
+	* autogen.sh,
+	* configure.ac,
+	* javac.in,
+	* src/java/META-INF/services/javax.sound.sampled.spi.MixerProvider,
+	* src/java/org/classpath/icedtea/pulseaudio/ContextEvent.java,
+	* src/java/org/classpath/icedtea/pulseaudio/ContextListener.java,
+	* src/java/org/classpath/icedtea/pulseaudio/Debug.java,
+	* src/java/org/classpath/icedtea/pulseaudio/EventLoop.java,
+	* src/java/org/classpath/icedtea/pulseaudio/Operation.java,
+	* src/java/org/classpath/icedtea/pulseaudio/PulseAudioClip.java,
+	* src/java/org/classpath/icedtea/pulseaudio/PulseAudioDataLine.java,
+	* src/java/org/classpath/icedtea/pulseaudio/PulseAudioLine.java,
+	* src/java/org/classpath/icedtea/pulseaudio/PulseAudioMixer.java,
+	* src/java/org/classpath/icedtea/pulseaudio/PulseAudioMixerInfo.java,
+	* src/java/org/classpath/icedtea/pulseaudio/PulseAudioMixerProvider.java,
+	* src/java/org/classpath/icedtea/pulseaudio/PulseAudioPlaybackLine.java,
+	* src/java/org/classpath/icedtea/pulseaudio/PulseAudioPort.java,
+	* src/java/org/classpath/icedtea/pulseaudio/PulseAudioSourceDataLine.java,
+	* src/java/org/classpath/icedtea/pulseaudio/PulseAudioSourcePort.java,
+	* src/java/org/classpath/icedtea/pulseaudio/PulseAudioTargetDataLine.java,
+	* src/java/org/classpath/icedtea/pulseaudio/PulseAudioTargetPort.java,
+	* src/java/org/classpath/icedtea/pulseaudio/PulseAudioVolumeControl.java,
+	* src/java/org/classpath/icedtea/pulseaudio/SecurityWrapper.java,
+	* src/java/org/classpath/icedtea/pulseaudio/Stream.java,
+	* src/java/org/classpath/icedtea/pulseaudio/StreamBufferAttributes.java,
+	* src/java/org/classpath/icedtea/pulseaudio/StreamSampleSpecification.java,
+	* src/native/jni-common.c,
+	* src/native/jni-common.h,
+	* src/native/org_classpath_icedtea_pulseaudio_ContextEvent.c,
+	* src/native/org_classpath_icedtea_pulseaudio_EventLoop.c,
+	* src/native/org_classpath_icedtea_pulseaudio_Operation.c,
+	* src/native/org_classpath_icedtea_pulseaudio_PulseAudioSourcePort.c,
+	* src/native/org_classpath_icedtea_pulseaudio_PulseAudioTargetPort.c,
+	* src/native/org_classpath_icedtea_pulseaudio_Stream.c,
+	* testsounds/README,
+	* unittests/org/classpath/icedtea/pulseaudio/OtherSoundProvidersAvailableTest.java,
+	* unittests/org/classpath/icedtea/pulseaudio/PulseAudioClipTest.java,
+	* unittests/org/classpath/icedtea/pulseaudio/PulseAudioEventLoopOverhead.java,
+	* unittests/org/classpath/icedtea/pulseaudio/PulseAudioMixerProviderTest.java,
+	* unittests/org/classpath/icedtea/pulseaudio/PulseAudioMixerRawTest.java,
+	* unittests/org/classpath/icedtea/pulseaudio/PulseAudioMixerTest.java,
+	* unittests/org/classpath/icedtea/pulseaudio/PulseAudioSourceDataLineRawTest.java,
+	* unittests/org/classpath/icedtea/pulseaudio/PulseAudioSourceDataLineTest.java,
+	* unittests/org/classpath/icedtea/pulseaudio/PulseAudioSourcePortTest.java,
+	* unittests/org/classpath/icedtea/pulseaudio/PulseAudioTargetDataLineTest.java,
+	* unittests/org/classpath/icedtea/pulseaudio/PulseAudioTargetPortTest.java:
+	Added and updated.
+
+2012-06-28  Omair Majid  <omajid@redhat.com>
+
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioClip.java
+	(getMicrosecondLength, getMicrosecondPosition)
+	(setMicrosecondPosition): Use correct factor to convert seconds to
+	microseconds.
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioSourceDataLine.java
+	(getMicrosecondPosition): Likewise.
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioPlaybackLine.java:
+	Define SECONDS_TO_MICROSECONDS.
+
+2012-06-28  Omair Majid  <omajid@redhat.com>
+
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioSourceDataLine.java
+	(write): Synchronize access to writeInterrupted flag.
+
+2012-06-28  Omair Majid  <omajid@redhat.com>
+
+	* NEWS: Update with fix.
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/Stream.java:
+	Add new member variable contextPointer.
+	* pulseaudio/src/native/org_classpath_icedtea_pulseaudio_Stream.c
+	(Java_org_classpath_icedtea_pulseaudio_Stream_native_1pa_1stream_1new):
+	Save j_context as contextPointer.
+	(Java_org_classpath_icedtea_pulseaudio_Stream_native_1pa_1stream_1unref):
+	Delete the global ref and dellocate the java context.
+	(cork_callback): Don't check userdata. It is NULL.
+	(Java_org_classpath_icedtea_pulseaudio_Stream_native_1pa_1stream_1cork):
+	Dont allocate and pass a java_context to pa_stream_cork. It is not needed.
+	* pulseaudio/unittests/org/classpath/icedtea/pulseaudio/PulseAudioClipTest.java
+	(testOpenCloseLotsOfTimes): New method.
+
+2011-10-03  Xerxes RĂ„nby  <xerxes@zafena.se>
+	    Robert Lougher <rob@jamvm.org.uk>
+
+	* pulseaudio/src/native/org_classpath_icedtea_pulseaudio_Stream.c
+	  (Java_org_classpath_icedtea_pulseaudio_Stream_native_1pa_1stream_1get_1buffer_1attr):
+	  Make code compliant to the JNI specification.
+	  Enable pulseaudio to work in combination with JVM that strictly
+	  implement JNI spec.
+
+2011-10-03  Xerxes RĂ„nby  <xerxes@zafena.se>
+	    David Henningsson <david.henningsson@canonical.com>
+	    Matthias Klose <doko@ubuntu.com>
+
+	LP862286
+	* pulseaudio/src/native/org_classpath_icedtea_pulseaudio_EventLoop.c
+	  (context_change_callback):
+	  Fix exception on trying to start PulseAudio playback on ARM.
+
+2011-09-29  Xerxes RĂ„nby  <xerxes@zafena.se>
+	    David Henningsson <david.henningsson@canonical.com>
+
+	LP862286
+	* NEWS: Updated.
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/
+	  PulseAudioDataLine.java (addStreamListeners): 
+	  Fix exception on trying to start PulseAudio playback.
+
+2011-06-20  Denis Lila  <dlila@redhat.com>
+
+	* pulseaudio/src/native/org_classpath_icedtea_pulseaudio_Stream.c
+	(SET_STREAM_ENUM): Add an underscore after java_prefix so that
+	the produced string matches the names in Stream.java.
+
+2011-06-20  Denis Lila  <dlila@redhat.com>
+
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioDataLine.java
+	(bufferSize): Remove.
+	(getBufferSize): Return stream.getBufferSize().
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioSourceDataLine.java
+	(connectLine): Improve formatting.
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioTargetDataLine.java
+	(connectLine): Set up flags to adjust the latency, if needed.
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/Stream.java
+	(bufAttr, bufAttrMutex): New members.
+	(setBufAttr, bufferAttrCallback): New methods. They both set bufAttr.
+	(getBufferSize): Return the current buffer size.
+	(connectForRecording): Add a flags argument to allow callers to chose the
+	flags.
+	(stateCallback): When the stream is ready, set the buffer attributes to
+	the actual ones used by the server.
+	* pulseaudio/src/native/org_classpath_icedtea_pulseaudio_Stream.c
+	(buf_attr_changed_callback): New function. 
+	(Java_org_classpath_icedtea_pulseaudio_Stream_native_1pa_1stream_1new):
+	Set the buffer attribute callback.
+
+2011-06-17  Denis Lila  <dlila@redhat.com>
+
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/Stream.java
+	(FLAG_NOFLAGS, FLAG_START_CORKED, FLAG_INTERPOLATE_TIMING,
+	 FLAG_NOT_MONOTONIC, FLAG_AUTO_TIMING_UPDATE, FLAG_NO_REMAP_CHANNELS,
+	 FLAG_NO_REMIX_CHANNELS, FLAG_FIX_FORMAT, FLAG_FIX_RATE,
+	 FLAG_FIX_CHANNELS, FLAG_DONT_MOVE, FLAG_VARIABLE_RATE, FLAG_PEAK_DETECT,
+	 FLAG_START_MUTED, FLAG_ADJUST_LATENCY, FLAG_EARLY_REQUESTS,
+	 FLAG_DONT_INHIBIT_AUTO_SUSPEND, FLAG_START_UNMUTED, FLAG_FAIL_ON_SUSPEND):
+	New static long variables mirroring pa_stream_flag_t values.
+	(STATE_UNCONNECTED, STATE_CREATING, STATE_READY, STATE_FAILED,
+	 STATE_TERMINATED): Add the STATE_ prefix to distinguish them from
+	the flag variables.
+	(native_pa_stream_connect_playback, native_pa_stream_connect_record):
+	Change flags parameter to long.
+	(connectForPlayback, connectForRecording): Start the stream corked.
+	Change formatting to make it more readable.
+	* pulseaudio/src/native/org_classpath_icedtea_pulseaudio_Stream.c
+	(SET_STREAM_ENUM): Renamed from SET_STREAM_STATE_ENUM, since the
+	macro could have been used for any PA_STREAM constants, not just
+	stream states (and indeed, we now use it for flag constants too).
+	(Java_org_classpath_icedtea_pulseaudio_Stream_init_1constants):
+	Initialize flag constants in addition to the stream states.
+	(Java_org_classpath_icedtea_pulseaudio_Stream_native_1pa_1stream_1connect_1playback):
+	Change flags parameter to jlong (from jint), remove commented out
+	dead code, remove obsolete comment, and start the stream with whatever
+	flags were passed in the flags parameter, instead of ignoring that
+	parameter and using PA_STREAM_START_CORKED.
+	(Java_org_classpath_icedtea_pulseaudio_Stream_native_1pa_1stream_1connect_1record):
+	Change flags parameter to jlong (from jint), remove commented out
+	dead code.
+
+2011-06-16  Denis Lila  <dlila@redhat.com>
+
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioDataLine.java
+	(addStreamListeners): Remove this.notifyAll() from
+	openCloseListener.update; change this.notifyAll() to
+	PulseAudioDataLine.this.notifyAll() in startedListener.update.
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioTargetDataLine.java
+	(read): Put fragmentBuffer null check in the synchronized block.
+	(flush): Make it synchronized to avoid race condition with read().
+
+2011-06-16  Denis Lila  <dlila@redhat.com>
+
+	* Makefile.am: Add ContextEvent to the list of pulse audio classes that
+	need javah run on them.
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/ContextEvent.java
+	(Type): Remove and replace with...
+	(UNCONNECTED, CONNECTING, AUTHORIZING, SETTING_NAME, READY, FAILED,
+	 TERMINATED): New static long variables replacing enum Type.
+	(init_constants): New native method to initialize the above variables.
+	(checkNativeEnumReturn): Make sure that the input is one of the longs
+	representing the type of ContextEvent.
+	(type): Change type from Type to long.
+	(ContextEvent): Take a long argument, instead of a Type.
+	(getType): Return a long, not a Type.
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/EventLoop.java
+	(status): Change from int to long.
+	(native_set_sink_volume): Remove. It was unimplemented in the JNI side.
+	(getStatus): Return long instead of int.
+	(update): Replace int argument with long argument. Remove the switch
+	statement.
+	(setVolume): Remove. Unused.
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/Operation.java
+	(State): Remove and replace with...
+	(Running, Done, Cancelled): Static longs, enumerating the possible
+	operation states.
+	(init_constants): New native method to initialize the above variables.
+	(checkNativeOperationState): Make sure that the input is one of the longs
+	representing the operation state.
+	(native_get_state): Change return type from int to long.
+	(getState): Change return type to long; remove switch.
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioDataLine.java
+	Remove the names of enums from the names of constants since most of them
+	were changed to static longs.
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioMixer.java
+	Same changes as in PulseAudioDataLine.java.
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/Stream.java
+	(State): Remove and replace with...
+	(UNCONNECTED, CREATING, READY, FAILED, TERMINATED): New static long variables
+	replacing enum Type.
+	(init_constants): New native method to initialize the above variables.
+	(checkNativeStreamState): Make sure that the input is one of the longs
+	representing the kind of StreamState.
+	(native_pa_stream_get_state): Change the return from int to long.
+	(getState): Remove the switch.
+	* pulseaudio/src/native/jni-common.h
+	(SET_JAVA_STATIC_LONG_FIELD_TO_PA_ENUM): Macro that sets one of the java
+	 static longs to the corresponding pa_constant.
+	* pulseaudio/src/native/org_classpath_icedtea_pulseaudio_ContextEvent.c
+	New file.
+	(SET_CONTEXT_ENUM): Macro that sets the ContextEvent types.
+	(Java_org_classpath_icedtea_pulseaudio_ContextEvent_init_1constants):
+	Implementation of ContextEvent.init_constants.
+	* pulseaudio/src/native/org_classpath_icedtea_pulseaudio_EventLoop.c
+	(context_change_callback): Change the fourth argument of GetMethodID
+	to "(J)V" to reflect the change in the signature of EventLoop.update.
+	* pulseaudio/src/native/org_classpath_icedtea_pulseaudio_Operation.c
+	(SET_OP_ENUM): Macro that sets the operation types.
+	(Java_org_classpath_icedtea_pulseaudio_Operation_init_1constants):
+	Implementation of Operation.init_constants.
+	(Java_org_classpath_icedtea_pulseaudio_Operation_native_1get_1state):
+	Change return type to jlong.
+	* pulseaudio/src/native/org_classpath_icedtea_pulseaudio_Stream.c
+	(SET_STREAM_ENUM): Macro that sets the stream states.
+	(Java_org_classpath_icedtea_pulseaudio_Stream_init_1constants):
+	Implementation of Stream.init_constants.
+	(Java_org_classpath_icedtea_pulseaudio_Stream_native_1pa_1stream_1get_1state):
+	Change return type to jlong.
+
+2011-06-15  Denis Lila  <dlila@redhat.com>
+
+	* Makefile.am: Add back the -classpath option to javah in
+	building the pulse audio header files. The bootstrap javah
+	doesn't recognize -J-Xbootclasspath/p: so it couldn't find
+	the classfiles.
+
+2011-06-10  Denis Lila  <dlila@redhat.com>
+
+	* pulseaudio/*: Fix whitespace.
+
+2011-06-10  Denis Lila  <dlila@redhat.com>
+
+	* Makefile.am
+	(stamps/pulse-java-headers.stamp): Prepend the java build directory
+	to the boot class path to avoid generating headers for the system
+	pulse-java classes.
+
+2010-07-12 Jon VanAlten <jon.vanalten@redhat.com>
+
+	* 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/PulseAudioSourceDataLine.java
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioTargetDataLine.java:
+	Eliminate spurious exception throwing from open, close, read, write,
+	drain, and flush calls on closed lines.
+	Use isOpen() API call instead of instance variable where appropriate.
+
+2010-07-16  Andrew John Hughes  <gnu.andrew@redhat.com>
+
+	* Makefile.am:
+	(clean-pulse-java): Always check for existence before
+	using rmdir.
+
+2010-07-06  Andrew John Hughes  <gnu.andrew@redhat.com>
+
+	* Makefile.am:
+	(PULSE_JAVA_BUILDDIR): Use a different name from
+	the source directory to avoid confusion.
+
+2010-06-04 Jon VanAlten  <jon.vanalten@redhat.com>
+
+	PR icedtea/438
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioLine.java:
+	(isControlSupported): Correctly determine if control-type is
+	supported.
+	* Makefile.am
+	(stamps/icedtea.stamp): Add PulseAudio sources to src.zip
+
+2010-03-08  Andrew John Hughes  <gnu.andrew@redhat.com>
+
+	* Makefile.am:
+	(IT_CFLAGS): General CFLAGS for all IcedTea
+	C builds, which includes ARCHFLAG.
+	($(PULSE_JAVA_NATIVE_BUILDDIR)/%.o): Use IT_CFLAGS.
+
+2010-02-24 Andrew John Hughes  <gnu.andrew@redhat.com>
+
+	* Makefile.am:
+	Add missing pulse-java alias and reorganise
+	existing aliases alphabetically.
+	($(PULSE_JAVA_NATIVE_BUILDDIR)/%.o): Add include
+	directive for build directory to pick up generated
+	headers.
+
+2010-02-24 Andrew John Hughes  <gnu.andrew@redhat.com>
+
+	* Makefile.am:
+	(install-exec-local): Prefix libpulse-java.so with
+	build directory path.
+	(clean-pulse-java): No need to delete libpulse-java.so
+	as no longer at top-level.
+
+2010-02-24 Andrew John Hughes  <gnu.andrew@redhat.com>
+
+	* pulseaudio/src/native/org_classpath_icedtea_pulseaudio_PulseAudioStreamVolumeControl.c:
+	Removed, never built and no corresponding
+	Java source file.
+	* Makefile.am:
+	(PULSE_JAVA_NATIVE_SRCS): List of PulseAudio source
+	files.
+	(PULSE_JAVA_NATIVE_OBJECTS): List of PulseAudio object
+	files.
+	(pulse-java.stamp): Depend on compiled library rather than
+	headers target.
+	($(PULSE_JAVA_NATIVE_BUILDDIR)/%.o): New target to compile
+	object files from source files using make substitution.
+	($(PULSE_JAVA_NATIVE_BUILDDIR)/libpulse-java.so): New target
+	to build the shared library.
+	(pulse-java-native-code.stamp): Removed.
+
+2009-11-17  Andrew John Hughes  <gnu.andrew@redhat.com>
+
+	* Makefile.am:
+	(clean-pulse-java): Remove the empty pulseaudio
+	directory if building out-of-tree.
+
+2009-11-02  Andrew John Hughes  <gnu.andrew@redhat.com>
+
+	* Makefile.am:
+	(stamps/pulse-java.stamp): Move native code generation to its
+	own target, making this a top-level dependency target.
+	(stamps/pulse-java-native-code.stamp): New target for native
+	code generation for PulseAudio plugin.
+	(clean-pulse-java): Always remove stamps.
+
+2009-09-23  Jon VanAlten  <jon.vanalten@redhat.com>
+
+	* Makefile.am:
+	Build PulseAudio class files into distinct subdirectory.
+
+2009-08-27  Matthias Klose  <doko@ubuntu.com>
+
+	* acinclude.m4, configure.ac (FIND_PULSEAUDIO): Remove.
+	* INSTALL: Don't require the pulseaudio binary.
+
+2009-02-11  Omair Majid  <omajid@redhat.com>
+
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioSourceDataLine.java
+	(write): Throw IllegalArgumentException instead of
+	ArrayIndexOutOfBoundsException for length.
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioTargetDataLine.java
+	(read): Fixed bounds checking.
+
+2009-02-11  Marc Schoenefeld <mschoene@redhat.com>
+            Omair Majid  <omajid@redhat.com>
+
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioSourceDataLine.java
+	(write): Fix bounds checking.
+
+2009-01-30  Andrew John Hughes  <gnu.andrew@redhat.com>
+
+	* Makefile.am:
+	Rename PULSEAUDIO_SOURCES
+	to avoid conflict with autoconf names.
+
+2009-01-22  Omair Majid  <omajid@redhat.com>
+	    Ioana Ivan <iivan@redhat.com>
+
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioClip.java
+	(start): Return silently if already started.
+	(stop): Return silently if already stopped.
+
+2009-01-21  Omair Majid  <omajid@redhat.com>
+
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioClip.java:
+	Remove unused variables volume and muted.
+	(PulseAudioClip): Remove variable volume.
+	(open): Remove muteControl, volume and muted.
+	(native_setVolume): Rename to native_set_volume.
+	(native_update_volume): New function.
+	(isMuted): Remove.
+	(setMuted): Remove.
+	(setVolume): Rename to setCachedVolume.
+	(getVolume): Rename to getCachedVolume.
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioMuteControl.java:
+	Remove file. Mute relied on chaning the volume only through the api. That
+	assumption is invalid as the user can change volume through pulseaudio's
+	controls.
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioPlaybackLine.java
+	(isMuted): Remove.
+	(setMuted): Remove.
+	(native_setVolume): Rename to native_set_volume.
+	(native_update_volume): New function.
+	(getVolume): Rename to getCachedVolume.
+	(setVolume): Rename to setCachedVolume.
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioPort.java:
+	Remove muted and muteControl. Rename volume to cachedVolume. 
+	(PulseAudioPort): Remove muteControl.
+	(isMuted): Remove function.
+	(setMuted): Remove.
+	(native_setVolume): Rename to native_set_volume.
+	(native_updateVolumeInfo): Rename to native_update_volume.
+	(setVolume): Rename to setCachedVolume.
+	(getVolume): Rename to getCachedVolume.
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioSourceDataLine.java:
+	Remove muteControl, muted and volume.
+	(open): Remove muteControl.
+	(native_setVolume): Rename to native_set_volume.
+	(native_update_volume): New function.
+	(isMuted): Remove.
+	(setMuted): Remove.
+	(getVolume): Rename to getCachedVolume.
+	(setVolume): Rename to setCachedVolume.
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioSourcePort.java
+	(native_setVolume): Rename to native_set_volume.
+	(native_updateVolumeInfo): Rename to native_update_volume.
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioTargetPort.java
+	(native_setVolume): Rename to native_set_volume.
+	(native_updateVolumeInfo): Rename to native_update_volume.
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioVolumeControl.java
+	(setValue): Dont check for mute.
+	(getValue): Query pulseaudio for any change in volume.
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/Stream.java:
+	New variable cachedVolume.
+	(native_setVolume): Rename to native_set_volume.
+	(native_update_volume): New function.
+	(getCachedVolume): New function.
+	(setCachedVolume): New function.
+	(update_channels_and_volume): New function.
+	* pulseaudio/src/native/org_classpath_icedtea_pulseaudio_EventLoop.c
+	(sink_input_volume_change_complete): Remove.
+	(sink_input_change_volume): Remove.
+	(Java_org_classpath_icedtea_pulseaudio_EventLoop_native_1set_1sink_1volume):
+	Remove.
+	* pulseaudio/src/native/org_classpath_icedtea_pulseaudio_PulseAudioSourcePort.c
+	(Java_org_classpath_icedtea_pulseaudio_PulseAudioSourcePort_native_1updateVolumeInfo):
+	Rename to
+	Java_org_classpath_icedtea_pulseaudio_PulseAudioSourcePort_native_1update_1volume.
+	(Java_org_classpath_icedtea_pulseaudio_PulseAudioSourcePort_native_1setVolume):
+	Rename to
+	Java_org_classpath_icedtea_pulseaudio_PulseAudioSourcePort_native_1set_1volume.
+	* pulseaudio/src/native/org_classpath_icedtea_pulseaudio_PulseAudioTargetPort.c
+	(Java_org_classpath_icedtea_pulseaudio_PulseAudioTargetPort_native_1updateVolumeInfo):
+	Rename to
+	Java_org_classpath_icedtea_pulseaudio_PulseAudioTargetPort_native_1update_1volume.
+	(Java_org_classpath_icedtea_pulseaudio_PulseAudioTargetPort_native_1setVolume):
+	Rename to
+	Java_org_classpath_icedtea_pulseaudio_PulseAudioTargetPort_native_1set_1volume.
+	* pulseaudio/src/native/org_classpath_icedtea_pulseaudio_Stream.c
+	(Java_org_classpath_icedtea_pulseaudio_Stream_native_1setVolume): Rename
+	to Java_org_classpath_icedtea_pulseaudio_Stream_native_1set_1volume.
+	(get_sink_input_volume_callback): New function.
+	(Java_org_classpath_icedtea_pulseaudio_Stream_native_1update_1volume): New
+	function.
+
+	* pulseaudio/unittests/org/classpath/icedtea/pulseaudio/PulseAudioClipTest.java
+	(testSupportedControls): Update to not check for MuteControl.
+	* pulseaudio/unittests/org/classpath/icedtea/pulseaudio/PulseAudioSourceDataLineRawTest.java
+	(testVolumeAndMute): Rename to testVolume. Remove test for MuteControl.
+	* pulseaudio/unittests/org/classpath/icedtea/pulseaudio/PulseAudioSourceDataLineTest.java
+	(testVolumeAndMute): Likewise.
+	(testSupportedControls): Update to not check for MuteControl.
+	* pulseaudio/unittests/org/classpath/icedtea/pulseaudio/PulseAudioSourcePortTest.java:
+	(testControls): Update to not check for MuteControl.
+	* pulseaudio/unittests/org/classpath/icedtea/pulseaudio/PulseAudioTargetPortTest.java
+	(testControls): Likewise.
+
+2008-12-02  Omair Majid  <omajid@redhat.com>
+
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/ContextEvent.java:
+	Change scope of class and functions from public to package-private.
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/ContextListener.java:
+	Likewise.
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/Debug.java:
+	Likewise.
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/EventLoop.java:
+	Likewise.
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/Operation.java:
+	Likewise.
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioClip.java:
+	Change class to be final. Change clip name to 'Audio Clip' and add some
+	documentation and annotations.
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioDataLine.java:
+	Change class scope to package-private. Add annoatations to functions.
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioLine.java:
+	Add override annotations to functions.
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioMixer.java:
+	Change class to final.
+	(getLine): Check for audio permissions before returning Ports.
+	(openRemote): Check for permissions to connect before connecting to a
+	remote PulseAudio server.
+	(openImpl): New function. Connect to the PulseAudio server.
+	(debug): Removed.
+	(main): Removed
+	(ThreadWriter): Removed.
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioMixerInfo.java:
+	Change class to final. Bump version.
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioMixerProvider.java:
+	(PulseAudioMixerProvider): Removed.
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioMuteControl.java:
+	Change class to final. Formatting fixes.
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioPort.java:
+	Change class scope to package private. Make constructor package-private. 
+	Add override annotations.
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioSourceDataLine.java:
+	Change class to final. Make constructor package-private. Add override
+	annotations.
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioSourcePort.java:
+	Make class final. Make constructor package-private.
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioTargetDataLine.java:
+	Make class final. Add override annotations
+	(PulseAudioTargetDataLine): Make constructor package-private.
+	(fragmentBuffer): Make variable private.
+	(drained): Likewise.
+	(flushed): Likewise.
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioTargetPort.java:
+	Make class final. Constructor now pacakge-private.
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioVolumeControl.java:
+	Make class final.
+	(MIN_VOLUME),
+	(MAX_VOLUME): Make variables pacakge-private.
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/SecurityWrapper.java:
+	Make class final.
+	(loadNativeLibrary): Make package-private.
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/Stream.java:
+	Make class final. Change scope of as many functions from public to
+	package-private as possible.
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/StreamBufferAttributes.java:
+	Make class and all functions package-private.
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/StreamSampleSpecification.java:
+	Make class package-private.
+
+2008-12-02  Matthias Klose  <doko@ubuntu.com>
+
+	* Makefile.am (stamps/pulse-java.stamp): Add missing include
+	directives.
+
+2008-11-18  Andrew John Hughes  <gnu.andrew@redhat.com>
+
+	* Makefile.am: Fix issues with
+	distcheck so pulse-audio
+	files are correctly removed.
+
+2008-11-14  Omair Majid  <omajid@redhat.com>
+
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/EventLoop.java
+	Removed useless SuppressWarnings.
+	(static): Delegate the loading of native libraries to SecurityWrapper.
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/Operation.java
+	(static): Likewise.
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioPort.java
+	(static): Likewise.
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioSourcePort.java
+	(static): Likewise.
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioTargetPort.java 
+	(static): Likewise.
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/Stream.java
+	(static): Likewise.
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/SecurityWrapper.java
+	New class.
+	(loadNativeLibrary): Loads libpulse-java.so in a privileged operation to
+	work when a security manager is installed.
+
+2008-11-10  Omair Majid  <omajid@redhat.com>
+
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/Debug.java
+	New class containing debugging functions.
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/EventLoop.java
+	(run): Print some debugging info.
+	(update): Likewise.
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioClip.java
+	(ClipThread.writeFrames): Likewise.
+	(close): Likewise.
+	(open): Likewise.
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioMixer.java
+	(PulseAudioMixer): Likewise.
+	(getLine): Likewise.
+	(close): Likewise.
+	(open): Likewise.
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioMixerProvider.java
+	(PulseAudioMixerProvider): Initialize Debug class.
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioSourceDataLine.java
+	(open): Print some debug info.
+	(close): Likewise.
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioTargetDataLine.java
+	(open): Likewise.
+	(close): Likewise.
+
+2008-11-10  Omair Majid  <omajid@redhat.com>
+
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioClip.java
+	(close): Dont check for permission to play audio. Always granted.
+	Infact, checking it causes an AccessControlException for untrusted
+	applets. The ALSA based backend doesnt check this permission at all.
+	(open): Likewise.
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioMixer.java
+	(getLine): Likewise.
+	(getSourceLines): Likewise.
+	(close): Likewise.
+	(open): Likewise.
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioSourceDataLine.java
+	(open): Likewise.
+	(close): Likewise.
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioTargetPort.java
+	(open): Likewise.
+	(close): Likewise.
+
+2008-11-10  Omair Majid  <omajid@redhat.com>
+
+	* pulseaudio/src/native/org_classpath_icedtea_pulseaudio_EventLoop.c
+	(Java_org_classpath_icedtea_pulseaudio_EventLoop_native_1set_1sink_1volume):
+	Deallocate unused memory.
+
+2008-11-04  Omair Majid  <omajid@redhat.com>
+
+	* Makefile.am (stamps/pulse-java.stamp): Link in libpulse.so after all
+	the object files that use it.
+	* pulseaudio/unittests/org/classpath/icedtea/pulseaudio/PulseAudioSourceDataLineTest.java
+	(testWriteIntegralNumberFrames): New function. Tests that a SourceDataLine
+	will only write an integral number of frames.
+	(testWriteNegativeLength): New function. Tests that a
+	SourceDataLine.write() wont accept a negative length.
+	(testWriteNegativeOffset): New function. Tests that a
+	SourceDataLine.write() will not accept a negative offset.
+	(testWriteMoreThanArrayLength): New function. Tests that
+	SourceDataLine.write() wont write more than the length of the array.
+	(testWriteMoreThanArrayLength2): Likewise.
+	(testWriteWithoutStart): Added a check to avoid throwing an
+	IllegalStateException.
+
+2008-10-25  Matthias Klose  <doko@ubuntu.com>
+
+	* Makefile.am (clean-pulse-java): Remove object files.
+
+2008-10-24  Omair Majid <omajid@redhat.com>
+
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioSourceDataLine.java
+	(write): Check for offset being negative.
+	* pulseaudio/unittests/org/classpath/icedtea/pulseaudio/PulseAudioClipTest.java
+	(testMixerKnowsAboutOpenClips): Removed the assumption that mixer had no
+	other lines open.
+
+2008-10-24  Matthias Klose  <doko@ubuntu.com>
+
+	* Makefile.am (clean-pulseaudio): Don't remove source files. Might
+	not be correct, but doesn't do harm in any build environment.
+
+2008-10-20  Andrew John Hughes  <gnu.andrew@redhat.com>
+
+	* Makefile.am: Compile PulseAudio Java
+	files with debugging information.
+
+2008-10-20  Andrew John Hughes  <gnu.andrew@redhat.com>
+
+	* Makefile.am:
+	Fix PulseAudio paths to work
+	when srcdir != builddir.
+
+2008-10-19  Andrew John Hughes  <gnu_andrew@member.fsf.org>
+
+	* Makefile.am:
+	Fix location of PulseAudio build output.
+
+2008-10-19  Andrew John Hughes  <gnu_andrew@member.fsf.org>
+
+	* Makefile.am:
+	Make PULSE_JAVA_DIR use srcdir
+
+2008-10-14 Ioana Ivan <iivand@redhat.com>
+
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioDataLine.java
+	(open): startedListener only sends a START event when playback first
+	starts and after an underflow
+	(start): sends a START event if there's data on the line
+
+2008-10-14 Ioana Ivan <iivan@redhat.com>
+
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioClip.java
+	(connectLine): changed bufferAttributes to fix crackling in the clip tests
+
+
+2008-10-14 Omair Majid <omajid@redhat.com>
+
+	* pulseaudio/src/java/org/classpat/icedtea/pulseaudio/PulseAudioDataLine.java
+	(connect): Removed debug output.
+
+2008-10-14 Omair Majid <omajid@redhat.com>
+
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioTargetDataLine.java
+	(read): Commented out debug output.
+
+2008-10-14 Omair Majid <omajid@redhat.com>
+
+	* Makefile.am (clean-pulse-java): Dont attempt to remove jni-common.o from
+	current directory.
+
+2008-10-14 Omair Majid <omajid@redhat.com>
+
+	* .hgignore: Added pulse-java.jar to ignore list.
+	* Makefile.am: Move jni-common.o out of the way when done.
+
+2008-10-14 Omair Majid <omajid@redhat.com>
+
+	* Makefile.am (stamps/pulse-java.stamp): Added
+	-I$(ICEDTEA_BOOT_DIR)/include/linux.
+
+2008-10-14 Omair Majid <omajid@redhat.com>
+
+	* .hgignore: Added generated files to ignore list.
+
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/EventLoop.java
+	(EventLoop): Initialize eventLoop object on construction.
+
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioClip.java
+	(PulseAudioClip): Removed the parameter eventLoop.
+	(getMicrosecondLength): Return time in microseconds.
+	(getMicrosecondPosition): Likewise.
+	(open): Dont throw an exception if Mixer is not open. Let super handle it.
+	(setFramePosition): Check frame position for being positive.
+	(setLoopPoints): Check that the starting frame is valid.
+	(setMicrosecondPosition): Deal with negative value and values over the 
+	maximum.
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioDataLine.java
+	(open): Open the mixer if it isnt open.
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioMixer.java
+	(getLine): Dont pass eventLoop as a paramter.
+	(close): Close all open lines on exit.
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioPort.java
+	(PulseAudioPort): Modified to not take an EventLoop paramter.
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioSourceDataLine.java
+	(PulseAudioSourceDataLine): Likewise.
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioSourcePort.java
+	(PulseAudioSourcePort): Likewise.
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioTargetDataLine.java
+	(PulseAudioTargetDataLine): Likewise.
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioTargetPort.java
+	(PulseAudioTargetPort): Likewise.
+
+	* pulseaudio/unittests/org/classpath/icedtea/pulseaudio/PulseAudioTargetDataLineTest.java
+	(testMixerKnowsAboutOpenLines): Work even if mixer has some lines
+	initially open.
+
+2008-10-12  Matthias Klose  <doko@ubuntu.com>
+
+	* Makefile.am (stamps/pulse-java.stamp): Add -I$(ICEDTEA_BOOT_DIR)/include
+	to build the source files.
+
+2008-10-11  Matthias Klose  <doko@ubuntu.com>
+
+	* Makefile.am (clean-pulse-java): Don't rm -rf / if configuring
+	without pulseaudio, remove jni-common.o.
+
+	* Makefile.am (stamps/pulse-java.stamp): Remove -fpic flag, add
+	-fPIC to build jni-common.c. (gcjwebplugin.so): Use -fPIC.
+
+2008-10-10 Ioana Ivan <iivan@redhat.com>
+	* src/java/org/classpath/icedtea/pulseaudio/PulseAudioMixer.java
+	(getLine): LineUnavailablException is no longer thrown if the mixer is 
+	not open
+	(getTargetLines) : returns a Line[] array instead of a
+	TargetDataLine[] array
+
+	* src/java/org/classpath/icedtea/pulseaudio/PulseAudioDataLine
+	(open(AudioFormat format, int bufferSize)): throws
+	LineUnavailableException if the mixer is not open
+
+	* src/java/org/classpath/icedtea/pulseaudio/PulseAudioClip
+	(open()): throws LineUnavailableException if the mixer is not open
+
+	* src/java/org/classpath/icedtea/pulseaudio/PulseAudioPort
+	(PulseAudioPort): calls open()
+
+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-10 Ioana Ivan <iivan@redhat.com>
+
+	* src/java/org/classpath/icedtea/pulseaudio/PulseAudioDataLine.java
+	(getLineInfo): removed method, moved to each subclass
+	* src/java/org/classpath/icedtea/pulseaudio/PulseAudioSourceDataLine.java
+	(getLineInfo): new method
+	* src/java/org/classpath/icedtea/pulseaudio/PulseAudioTargetDataLine.java
+	(getLineInfo): new method
+	* src/java/org/classpath/icedtea/pulseaudio/PulseAudioClip.java
+	getLineInfo): new method
+
+
+2008-10-08 Ioana Ivan <iivan@redhat.com>
+	* src/java/org/classpath/icedtea/pulseaudio/PulseAudioDataLine.java
+	(addStreamListener): startedListener always fires a START event
+			     strtedListener notifies drain that there is data
+			     on the line
+	(stop): sets writeInterrupted to true
+	(start): doesn't send any events
+	(getBytesInBuffer): new function, returns the number of bytes
+	currently present in a stream's buffer
+
+	* src/java/org/classpath/icedtea/pulseaudio/PulseAudioSourceDataLine.java:
+	(write): -checks writeInterrupted instead of drain and flush
+	         -if stop() was called before write() it writes data to the
+		 buffer until the buffer fills, then blocks
+		 -moved some code from the synchronized(this) block, since it
+		 was causing one test to hang
+	(close): sets writeInterrupted to true
+	(drain): sets writeInterrupted to true
+		 if the line is stopped and there is no data on the line,
+		 returns immediately, if there is data, blocks until the line
+		 is started
+	* src/java/org/classpath/icedtea/pulseaudio/Stream.java
+	(native_pa_stream_updateTimingInfo): new function
+	( bytesInBuffer): new function
+
+	* src/native/org_classpath_icedtea_pulseaudio_Stream.c
+	(JNICALL Java_org_classpath_icedtea_pulseaudio_Stream_bytesInBuffer):
+	new function
+	(Java_org_classpath_icedtea_pulseaudio_Stream_native_1pa_1stream_1updateTimingInfo):
+	new function
+	(update_timing_info_callback): new function
+	(Java_org_classpath_icedtea_pulseaudio_Stream_native_1pa_1stream_1writable_1size):
+	return 0 if the stream has been closed
+
+2008-09-25 Ioana Ivan <iivan@redhat.com>
+	* src/java/org/classpath/icedtea/pulseaudio/PulseAudioDataLine.java:
+	removed all references to the boolean variable corked since it was
+	redundant.
+	(start) : only send a START event if there's been a call to stop between
+	the last call to start and this one.
+	(stop): only send a STOP event if there's been a call to start between
+	the last call the stop and this one.
+	(startedListener.update): send a START event the first time data is
+	being written to the line and after an underflow. 
+
+2008-09-25 Ioana Ivan <iivan@redhat.com>
+
+	* src/native/org_classpath_icedtea_Stream.c
+	(Java_org_classpath_icedtea_pulseaudio_Stream_native_1pa_1stream_1connect_1playback):
+	pass 0 as a flag to pa_stream_connect_playback
+
+	* src/java/org/classpath/icedtea/pulseaudio/PulseAudioSourceDataLine.java:
+	formating changes
+
+2008-09-23 Ioana Ivan <iivan@redhat.com>
+	* src/java/org/classpath/icedtea/pulseaudio/PulseAudioDataLine.java:
+		-split open() into createStream(), addStreamListeners() and
+		connect(), which can be reused when reconnecting the line for
+		synchronization
+		-added recconectForSynchronization()
+		-made some changes to stop()/start() in order to send
+		START/STOP events both when corking and in case of underflow
+	* src/java/org/classpath/icedtea/pulseaudio/PulseAudioMixer.java:
+		-changes to synchronize()
+	* src/java/org/classpath/icedtea/pulseaudio/PulseAudioSourceDataLine.java:
+	         -changed connectLine to take the masterStream as a
+		 parameter in case we want to synchronize the Line
+	* src/java/org/classpath/icedtea/pulseaudio/PulseAudioClip.java:
+		 -changed connectLine to take the masterStream as a
+		 parameter in case we want to synchronize the Line
+
+2008-08-13 Ioana Ivan  <iivan@redhat.com>
+	* src/java/org/classpath/icedtea/pulseaudio/PulseAudioPort.java:
+		superclass for TargetPort and SourcePort
+	* src/java/org/classpath/icedtea/pulseaudio/PulseAudioSourcePort.java:
+		implements the abstract methods required in PulseAudioPort
+	* src/java/org/classpath/icedtea/pulseaudio/PulseAudioTargetPort.java:
+		implements the abstract methods required in PulseAudioPort
+	* unittests/org/classpath/icedtea/pulseaudio/PulseAudioSourcePortTest.java
+	* unittests/org/classpath/icedtea/pulseaudio/PulseAudioTargetPortTest.java:
+		new tests
+
+
+2008-08-13 Ioana Ivan  <iivan@redhat.com>
+
+        * src/java/org/classpath/icedtea/pulseaudio/PulseAudioTargetPort.java:
+	new class representing a targetport
+	* some refactoring in the control classes (controls for the target
+	port are now supported)
+
+2008-08-13 Ioana Ivan  <iivan@redhat.com>
+
+        * src/java/org/classpath/icedtea/pulseaudio/PulseAudioDataLine.java:
+	        superclass for TargetDataLine and SourceDataLine; moved open
+		and close here
+	* src/java/org/classpath/icedtea/pulseaudio/PulseAudioTargetDataLine.java:
+		chnaged the methods written so far to use Stream.java
+
+
+2008-08-13 Ioana Ivan  <iivan@redhat.com>
+
+        * src/java/org/classpath/icedtea/pulseaudio/PulseAudioMixer.java:
+	modified getLine() to allow us to obtain a TargetDataLine
+	* src/java/org/classpath/icedtea/pulseaudio/PulseAudioTargetDataLine.java: 
+	added open(), read() and close()
+	* src/java/org/classpath/icedtea/pulseaudio/PulseAudioSourceDataLine.java
+	marged the streamListeners into a single listener
+
+
+
+2008-08-13 Ioana Ivan  <iivan@redhat.com>
+
+        * src/java/org/classpath/icedtea/pulseaudio/PulseAudioMixer.java: the
+	list of formats recognized by PulseAudio is being set here, so it can
+	be used by all DataLines. Also made some changes to get*LineInfo() and
+	isLineSupported()
+	* src/java/org/classpath/icedtea/pulseaudio/PulseAudioSourceDataLine.java:
+	changed the constructor 
+
+
+2008-08-11  Joshua Sumali  <jsumali@redhat.com>
+
+	* configure.ac: Add check for libpulse.
+	* src/native/Makefile.am: Use flags from PKG_CHECK_MODULES.
+
+2008-08-11  Joshua Sumali  <jsumali@redhat.com>
+
+	* src/native/Makefile.am: Add flags to link against pulseaudio.
+
+2008-08-11  Joshua Sumali  <jsumali@redhat.com>
+
+	* .hgignore: Updated.
+	* Makefile.am: Add libpulse-java.so to clean-local.
+	* src/native/Makefile.am: Copy libpulse-java.so to the top dir after
+	building.
+
+2008-08-11  Joshua Sumali  <jsumali@redhat.com>
+
+	* .hgignore: Updated.
+
+2008-08-11  Joshua Sumali  <jsumali@redhat.com>
+
+	* ChangeLog: New file.
+	* INSTALL: Likewise.
+	* NEWS: Likewise.
+	* Makefile.am: Likewise.
+	* build.xml: Likewise.
+	* config.guess: Likewise.
+	* config.sub: Likewise.
+	* configure.ac: Likewise.
+	* depcomp: Likewise.
+	* install-sh: Likewise.
+	* libtool: Likewise.
+	* ltmain.sh: Likewise.
+	* missing: Likewise.
+	* src/org/*: Moved to src/java.
+	* src/META-INF: Moved to src/java.
+	* src/native/Makefile.am: New file.
+	* makefile: Removed.
+	* src/*.c: Moved to src/native.
+	* src/*.h: Likewise.
+
+# ChangeLog -- List of changes
+# Copyright (C) 2014 Red Hat, Inc.
+#
+# This file is part of IcedTea-Sound.
+#
+# IcedTea-Sound 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-Sound 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-Sound; see the file COPYING.  If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 USA.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Makefile.am	Thu Jun 12 00:42:40 2014 +0100
@@ -0,0 +1,166 @@
+# Makefile.am -- Input for automake
+# Copyright (C) 2014 Red Hat, Inc.
+#
+# This file is part of IcedTea-Sound.
+#
+# IcedTea-Sound 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-Sound 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-Sound; see the file COPYING.  If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 USA.
+
+# Source directories
+PULSE_JAVA_DIR = $(abs_top_srcdir)
+PULSE_JAVA_NATIVE_SRCDIR = $(PULSE_JAVA_DIR)/src/native
+PULSE_JAVA_NATIVE_SRCS = $(wildcard $(PULSE_JAVA_NATIVE_SRCDIR)/*.c)
+PULSE_JAVA_JAVA_SRCDIR = $(PULSE_JAVA_DIR)/src/java
+
+# Build directories
+RUNTIME = $(SYSTEM_JDK_DIR)/jre/lib/rt.jar
+PULSE_JAVA_BUILDDIR = $(abs_top_builddir)/pulseaudio.build
+PULSE_JAVA_NATIVE_BUILDDIR = $(PULSE_JAVA_BUILDDIR)/native
+PULSE_JAVA_NATIVE_OBJECTS = \
+	$(subst $(PULSE_JAVA_NATIVE_SRCDIR),$(PULSE_JAVA_NATIVE_BUILDDIR),$(patsubst %.c,%.o,$(PULSE_JAVA_NATIVE_SRCS)))
+PULSE_JAVA_CLASS_DIR = $(PULSE_JAVA_BUILDDIR)/classes
+
+# Files
+# Must use relative paths so as not to break make distcheck
+PULSEAUDIO_JAVA_SRCS = $(top_srcdir)/src/java/org/classpath/icedtea/*/*.java
+PULSEAUDIO_SRCS = $(PULSEAUDIO_JAVA_SRCS) \
+	$(top_srcdir)/src/java/META-INF/services/javax.sound.sampled.spi.MixerProvider \
+	$(top_srcdir)/src/native/*.h \
+	$(top_srcdir)/src/native/*.c \
+	$(top_srcdir)/testsounds/README \
+	$(top_srcdir)/testsounds/startup.wav \
+	$(top_srcdir)/testsounds/logout.wav \
+	$(top_srcdir)/testsounds/error.wav \
+	$(top_srcdir)/unittests/org/classpath/icedtea/*/*.java
+EXTRA_DIST = $(PULSEAUDIO_SRCS)
+
+# Flags
+IT_LANGUAGE_SOURCE_VERSION=6
+IT_CLASS_TARGET_VERSION=6
+IT_JAVAC_SETTINGS=-g -encoding utf-8 $(JAVACFLAGS) $(MEMORY_LIMIT) $(PREFER_SOURCE)
+IT_JAVACFLAGS=$(IT_JAVAC_SETTINGS) -source $(IT_LANGUAGE_SOURCE_VERSION) -target $(IT_CLASS_TARGET_VERSION)
+IT_CFLAGS=$(CFLAGS) $(ARCHFLAG)
+if ENABLE_DOCS
+ITS_PKGS=org.classpath.icedtea.pulseaudio
+JAVADOC_OPTS=-use -keywords -encoding UTF-8 -splitIndex \
+ -bottom '<font size="-1"> <a href="http://icedtea.classpath.org/bugzilla">Submit a bug or feature</a></font>'
+if JAVADOC_SUPPORTS_J_OPTIONS
+JAVADOC_MEM_OPTS=-J-Xmx1024m -J-Xms128m -J-XX:PermSize=32m -J-XX:MaxPermSize=160m
+endif
+endif
+
+# Top-Level Targets
+# =================
+
+all-local: stamps/pulse-java.stamp stamps/docs.stamp
+
+clean-local: clean-pulse-java clean-docs
+	rm -f $(abs_top_builddir)/src.zip
+	if [ -e stamps ] ; then rmdir stamps ; fi
+
+.PHONY: clean-pulse-java
+
+install-exec-local:
+	${mkinstalldirs} $(DESTDIR)$(libdir)
+	${INSTALL_PROGRAM} $(PULSE_JAVA_NATIVE_BUILDDIR)/libpulse-java.so $(DESTDIR)$(libdir)
+
+install-data-local:
+	${mkinstalldirs} $(DESTDIR)$(datadir)/$(PACKAGE_NAME)/
+	${INSTALL_DATA} pulse-java.jar $(DESTDIR)$(datadir)/$(PACKAGE_NAME)
+	(cd $(PULSE_JAVA_JAVA_SRCDIR) && \
+	   $(ZIP) -qr $(abs_top_builddir)/src.zip org )
+	${INSTALL_DATA} src.zip $(DESTDIR)$(datadir)/$(PACKAGE_NAME)
+if ENABLE_DOCS
+	${mkinstalldirs} $(DESTDIR)$(htmldir)
+	(cd ${abs_top_builddir}/docs; \
+	 for files in $$($(FIND) . -type f); \
+	 do \
+	   ${INSTALL_DATA} -D $${files} $(DESTDIR)$(htmldir)/$${files}; \
+	 done)
+endif
+
+uninstall-local:
+	rm -f $(DESTDIR)$(libdir)/libpulse-java.so
+	rm -f $(DESTDIR)$(datadir)/$(PACKAGE_NAME)/pulse-java.jar
+	rm -f $(DESTDIR)$(datadir)/$(PACKAGE_NAME)/src.zip
+	rmdir $(DESTDIR)$(datadir)/$(PACKAGE_NAME)
+	rm -rf $(DESTDIR)$(htmldir)
+
+stamps/pulse-java.stamp: stamps/pulse-java-jar.stamp $(PULSE_JAVA_NATIVE_BUILDDIR)/libpulse-java.so
+	mkdir -p stamps
+	touch $@
+
+stamps/pulse-java-class.stamp:
+	mkdir -p $(PULSE_JAVA_CLASS_DIR)
+	$(abs_top_builddir)/javac $(IT_JAVACFLAGS) -d $(PULSE_JAVA_CLASS_DIR) \
+	 -bootclasspath $(RUNTIME) $(PULSEAUDIO_JAVA_SRCS)
+	cp  -r $(PULSE_JAVA_JAVA_SRCDIR)/META-INF $(PULSE_JAVA_CLASS_DIR)
+	chmod -R ug+w $(PULSE_JAVA_CLASS_DIR)/META-INF
+	mkdir -p stamps
+	touch $@
+
+stamps/pulse-java-jar.stamp: stamps/pulse-java-class.stamp
+	$(JAR) cf pulse-java.jar -C $(PULSE_JAVA_CLASS_DIR) .;
+	mkdir -p stamps
+	touch $@
+
+stamps/pulse-java-headers.stamp: stamps/pulse-java-class.stamp
+	mkdir -p $(PULSE_JAVA_NATIVE_BUILDDIR)
+	$(JAVAH) -d $(PULSE_JAVA_NATIVE_BUILDDIR) -classpath $(PULSE_JAVA_CLASS_DIR) \
+	  -J-Xbootclasspath/p:$(PULSE_JAVA_CLASS_DIR) org.classpath.icedtea.pulseaudio.EventLoop ; \
+	$(JAVAH) -d $(PULSE_JAVA_NATIVE_BUILDDIR) -classpath $(PULSE_JAVA_CLASS_DIR) \
+	  -J-Xbootclasspath/p:$(PULSE_JAVA_CLASS_DIR) org.classpath.icedtea.pulseaudio.Stream ; \
+	$(JAVAH) -d $(PULSE_JAVA_NATIVE_BUILDDIR) -classpath $(PULSE_JAVA_CLASS_DIR) \
+	  -J-Xbootclasspath/p:$(PULSE_JAVA_CLASS_DIR) org.classpath.icedtea.pulseaudio.Operation; \
+	$(JAVAH) -d $(PULSE_JAVA_NATIVE_BUILDDIR) -classpath $(PULSE_JAVA_CLASS_DIR) \
+	  -J-Xbootclasspath/p:$(PULSE_JAVA_CLASS_DIR) org.classpath.icedtea.pulseaudio.PulseAudioSourcePort ; \
+	$(JAVAH) -d $(PULSE_JAVA_NATIVE_BUILDDIR) -classpath $(PULSE_JAVA_CLASS_DIR) \
+	  -J-Xbootclasspath/p:$(PULSE_JAVA_CLASS_DIR) org.classpath.icedtea.pulseaudio.PulseAudioTargetPort ; \
+	$(JAVAH) -d $(PULSE_JAVA_NATIVE_BUILDDIR) -classpath $(PULSE_JAVA_CLASS_DIR) \
+	  -J-Xbootclasspath/p:$(PULSE_JAVA_CLASS_DIR) org.classpath.icedtea.pulseaudio.ContextEvent
+	mkdir -p stamps
+	touch $@
+
+$(PULSE_JAVA_NATIVE_BUILDDIR)/%.o: $(PULSE_JAVA_NATIVE_SRCDIR)/%.c stamps/pulse-java-headers.stamp
+	$(CC) $(IT_CFLAGS) -fPIC -I$(SYSTEM_JDK_DIR)/include/linux -I$(SYSTEM_JDK_DIR)/include \
+	 -I$(PULSE_JAVA_NATIVE_BUILDDIR) -o $@ -c $<
+
+$(PULSE_JAVA_NATIVE_BUILDDIR)/libpulse-java.so: $(PULSE_JAVA_NATIVE_OBJECTS)
+	$(CC) $(LDFLAGS) -shared $(PULSE_JAVA_NATIVE_OBJECTS) $(LIBPULSE_LIBS) \
+	 -o $(PULSE_JAVA_NATIVE_BUILDDIR)/libpulse-java.so
+
+clean-pulse-java:
+	rm -rf $(PULSE_JAVA_NATIVE_BUILDDIR)
+	rm -rf $(PULSE_JAVA_CLASS_DIR)
+	rm -f pulse-java.jar
+	if [ -e $(PULSE_JAVA_BUILDDIR) ]; then \
+	  rmdir $(PULSE_JAVA_BUILDDIR) ; \
+	fi
+	rm -f stamps/pulse-java*.stamp
+
+stamps/docs.stamp:
+if ENABLE_DOCS
+	$(JAVADOC) $(JAVADOC_MEM_OPTS) $(JAVADOC_OPTS) \
+	 -d ${abs_top_builddir}/docs -sourcepath $(PULSE_JAVA_JAVA_SRCDIR) \
+	 -doctitle 'IcedTea-Sound: API Specification' \
+	 -windowtitle 'IcedTea-Sound' \
+	 -header '<strong>IcedTea-Sound</strong>' \
+	 $(ITS_PKGS)
+endif
+	mkdir -p stamps
+	touch stamps/docs.stamp
+
+clean-docs:
+	rm -rf ${abs_top_builddir}/docs
+	rm -f stamps/docs.stamp
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/NEWS	Thu Jun 12 00:42:40 2014 +0100
@@ -0,0 +1,36 @@
+# NEWS -- Release notes
+# Copyright (C) 2014 Red Hat, Inc.
+#
+# This file is part of IcedTea-Sound.
+#
+# IcedTea-Sound 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-Sound 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-Sound; see the file COPYING.  If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 USA.
+
+Key:
+
+SX  - http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=X
+PRX - http://icedtea.classpath.org/bugzilla/show_bug.cgi?id=X
+RHX - https://bugzilla.redhat.com/show_bug.cgi?id=X
+DX  - http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=X
+GX  - http://bugs.gentoo.org/show_bug.cgi?id=X
+CAX - http://server.complang.tuwien.ac.at/cgi-bin/bugzilla/show_bug.cgi?id=X
+LPX - https://bugs.launchpad.net/bugs/X
+JSRX - http://jcp.org/en/jsr/detail?id=X
+JEPX - http://openjdk.java.net/jeps/X
+
+CVE-XXXX-YYYY: http://www.cve.mitre.org/cgi-bin/cvename.cgi?name=XXXX-YYYY
+
+New in release 1.0.0 (2014-XX-XX):
+
+* PR1741: Break PulseAudio provider out into IcedTea-Sound
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/README	Thu Jun 12 00:42:40 2014 +0100
@@ -0,0 +1,54 @@
+IcedTea-Sound README - Last updated: 11th of June, 2014
+Copyright (C) 2014 Red Hat, Inc.
+
+This file is part of IcedTea-Sound.
+
+IcedTea-Sound 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-Sound 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-Sound; see the file COPYING.  If not, write to
+the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+What Is This?
+=============
+
+A collection of audio SPI implementations for Java, including:
+
+ * the use of PulseAudio as a mixer 
+  (so as a bonus you get all of the ear candy that PulseAudio provides)
+
+How to Build?
+=============
+
+$ ./autogen.sh && ./configure && make
+
+Note: Building requires PulseAudio 0.9.11 and a JDK.
+ 
+How to Run the Tests?
+=====================
+
+After building, do:
+  
+$ make check
+
+(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/acinclude.m4	Thu Jun 12 00:42:40 2014 +0100
@@ -0,0 +1,574 @@
+dnl acinclude.m4 -- Additional macros for configure.ac
+dnl Copyright (C) 2014 Red Hat, Inc.
+dnl
+dnl This file is part of IcedTea-Sound.
+dnl
+dnl IcedTea-Sound is free software; you can redistribute it and/or
+dnl modify it under the terms of the GNU General Public License as published by
+dnl the Free Software Foundation, version 2.
+dnl 
+dnl IcedTea-Sound is distributed in the hope that it will be useful,
+dnl but WITHOUT ANY WARRANTY; without even the implied warranty of
+dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+dnl General Public License for more details.
+dnl 
+dnl You should have received a copy of the GNU General Public License
+dnl along with IcedTea-Sound; see the file COPYING.  If not, write to
+dnl the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+dnl 02110-1301 USA.
+
+AC_DEFUN([IT_SET_ARCH_SETTINGS],
+[
+  case "${host_cpu}" in
+    x86_64)
+      BUILD_ARCH_DIR=amd64
+      INSTALL_ARCH_DIR=amd64
+      JRE_ARCH_DIR=amd64
+      ARCHFLAG="-m64"
+      ;;
+    i?86)
+      BUILD_ARCH_DIR=i586
+      INSTALL_ARCH_DIR=i386
+      JRE_ARCH_DIR=i386
+      ARCH_PREFIX=${LINUX32}
+      ARCHFLAG="-m32"
+      ;;
+    alpha*)
+      BUILD_ARCH_DIR=alpha
+      INSTALL_ARCH_DIR=alpha
+      JRE_ARCH_DIR=alpha
+      ;;
+    arm*)
+      BUILD_ARCH_DIR=arm
+      INSTALL_ARCH_DIR=arm
+      JRE_ARCH_DIR=arm
+      ARCHFLAG="-D_LITTLE_ENDIAN"
+      ;;
+    arm64|aarch64)
+      BUILD_ARCH_DIR=aarch64
+      INSTALL_ARCH_DIR=aarch64
+      JRE_ARCH_DIR=aarch64
+      ARCHFLAG="-D_LITTLE_ENDIAN"
+      ;;
+    mips)
+      BUILD_ARCH_DIR=mips
+      INSTALL_ARCH_DIR=mips
+      JRE_ARCH_DIR=mips
+       ;;
+    mipsel)
+      BUILD_ARCH_DIR=mipsel
+      INSTALL_ARCH_DIR=mipsel
+      JRE_ARCH_DIR=mipsel
+       ;;
+    powerpc)
+      BUILD_ARCH_DIR=ppc
+      INSTALL_ARCH_DIR=ppc
+      JRE_ARCH_DIR=ppc
+      ARCH_PREFIX=${LINUX32}
+      ARCHFLAG="-m32"
+      ;;
+    powerpc64)
+      BUILD_ARCH_DIR=ppc64
+      INSTALL_ARCH_DIR=ppc64
+      JRE_ARCH_DIR=ppc64
+      ARCHFLAG="-m64"
+       ;;
+    powerpc64le)
+      BUILD_ARCH_DIR=ppc64
+      INSTALL_ARCH_DIR=ppc64
+      JRE_ARCH_DIR=ppc64
+      ARCHFLAG="-m64"
+       ;;
+    sparc)
+      BUILD_ARCH_DIR=sparc
+      INSTALL_ARCH_DIR=sparc
+      JRE_ARCH_DIR=sparc
+      CROSS_TARGET_ARCH=sparc
+      ARCH_PREFIX=${LINUX32}
+      ARCHFLAG="-m32"
+       ;;
+    sparc64)
+      BUILD_ARCH_DIR=sparcv9
+      INSTALL_ARCH_DIR=sparcv9
+      JRE_ARCH_DIR=sparc64
+      ARCHFLAG="-m64"
+       ;;
+    s390)
+      BUILD_ARCH_DIR=s390
+      INSTALL_ARCH_DIR=s390
+      JRE_ARCH_DIR=s390
+      ARCH_PREFIX=${LINUX32}
+      ARCHFLAG="-m31"
+       ;;
+    s390x)
+      BUILD_ARCH_DIR=s390x
+      INSTALL_ARCH_DIR=s390x
+      JRE_ARCH_DIR=s390x
+      CROSS_TARGET_ARCH=s390x
+      ARCHFLAG="-m64"
+      ;;
+    sh*)
+      BUILD_ARCH_DIR=sh
+      INSTALL_ARCH_DIR=sh
+      JRE_ARCH_DIR=sh
+      CROSS_TARGET_ARCH=sh
+      ;;
+    *)
+      BUILD_ARCH_DIR=`uname -m`
+      INSTALL_ARCH_DIR=$BUILD_ARCH_DIR
+      JRE_ARCH_DIR=$INSTALL_ARCH_DIR
+      ;;
+  esac
+  AC_SUBST(BUILD_ARCH_DIR)
+  AC_SUBST(INSTALL_ARCH_DIR)
+  AC_SUBST(JRE_ARCH_DIR)
+  AC_SUBST(ARCH_PREFIX)
+  AC_SUBST(ARCHFLAG)
+])
+
+AC_DEFUN([IT_FIND_COMPILER],
+[
+  AC_REQUIRE([IT_FIND_JAVA])
+  AC_REQUIRE([IT_FIND_ECJ_JAR])
+
+  IT_FIND_JAVAC
+  IT_FIND_ECJ
+  IT_USING_ECJ
+  
+  if test "x${ECJ_JAR}" = "xno"; then
+    if test "x${JAVAC}" = "x"; then
+      AC_MSG_ERROR("No compiler or ecj JAR file was found.")
+    fi
+  fi
+
+  AC_SUBST(ECJ)
+  AC_SUBST(JAVAC)
+])
+
+AC_DEFUN_ONCE([IT_FIND_ECJ],
+[
+  ECJ_DEFAULT=/usr/bin/ecj
+  AC_MSG_CHECKING([if an ecj binary was specified])
+  AC_ARG_WITH([ecj],
+	      [AS_HELP_STRING(--with-ecj,bytecode compilation with ecj)],
+  [
+    if test "x${withval}" = "xyes"; then
+      ECJ=no
+    else
+      ECJ="${withval}"
+    fi
+  ],
+  [ 
+    ECJ=no
+  ])
+  AC_MSG_RESULT(${ECJ})
+  if test "x${ECJ}" = "xno"; then
+    ECJ=${ECJ_DEFAULT}
+  fi
+  AC_MSG_CHECKING([if $ECJ is a valid executable file])
+  if test -x "${ECJ}" && test -f "${ECJ}"; then
+    AC_MSG_RESULT([yes])
+  else
+    ECJ=""
+    AC_PATH_PROG(ECJ, "ecj")
+    if test -z "${ECJ}"; then
+      AC_PATH_PROG(ECJ, "ecj-3.1")
+    fi
+    if test -z "${ECJ}"; then
+      AC_PATH_PROG(ECJ, "ecj-3.2")
+    fi
+    if test -z "${ECJ}"; then
+      AC_PATH_PROG(ECJ, "ecj-3.3")
+    fi
+    if test -z "${ECJ}"; then
+      AC_PATH_PROG(ECJ, "ecj-3.4")
+    fi
+  fi
+])
+
+AC_DEFUN_ONCE([IT_FIND_JAVAC],
+[
+  AC_REQUIRE([IT_CHECK_FOR_JDK])
+  JAVAC_DEFAULT=${SYSTEM_JDK_DIR}/bin/javac
+  AC_MSG_CHECKING([if a javac binary was specified])
+  AC_ARG_WITH([javac],
+	      [AS_HELP_STRING([--with-javac[[=PATH]]],the path to a javac binary)],
+  [
+    if test "x${withval}" = "xyes"; then
+      JAVAC=no
+    else
+      JAVAC="${withval}"
+    fi
+  ],
+  [
+    JAVAC=no
+  ])
+  AC_MSG_RESULT(${JAVAC})
+  if test "x${JAVAC}" = "xno"; then
+    JAVAC=${JAVAC_DEFAULT}
+  fi
+  AC_MSG_CHECKING([if $JAVAC is a valid executable file])
+  if test -x "${JAVAC}" && test -f "${JAVAC}"; then
+    AC_MSG_RESULT([yes])
+  else
+    AC_MSG_RESULT([no])
+    JAVAC=""
+    AC_PATH_PROG(JAVAC, "javac")
+  fi
+  AC_SUBST(JAVAC)
+  ])
+])
+
+AC_DEFUN_ONCE([IT_FIND_JAVA],
+[
+  AC_REQUIRE([IT_CHECK_FOR_JDK])
+  JAVA_DEFAULT=${SYSTEM_JDK_DIR}/bin/java
+  AC_MSG_CHECKING([if a java binary was specified])
+  AC_ARG_WITH([java],
+              [AS_HELP_STRING([--with-java[[=PATH]]],specify location of a 1.5 Java VM)],
+  [
+    if test "x${withval}" = "xyes"; then
+      JAVA=no
+    else
+      JAVA="${withval}"
+    fi
+  ],
+  [
+    JAVA=no
+  ])
+  AC_MSG_RESULT(${JAVA})
+  if test "x${JAVA}" = "xno"; then
+    JAVA=${JAVA_DEFAULT}
+  fi
+  AC_MSG_CHECKING([if $JAVA is a valid executable file])
+  if test -x "${JAVA}" && test -f "${JAVA}"; then
+    AC_MSG_RESULT([yes])
+  else
+    AC_MSG_RESULT([no])
+    JAVA=""
+    AC_PATH_PROG(JAVA, "java")
+    if test -z "${JAVA}"; then
+      AC_PATH_PROG(JAVA, "gij")
+    fi
+    if test -z "${JAVA}"; then
+      AC_PATH_PROG(JAVA, "cacao")
+    fi
+    if test -z "${JAVA}"; then
+      AC_MSG_ERROR("A 1.5-compatible Java VM is required.")
+    fi
+  fi
+  AC_SUBST(JAVA)
+])
+
+AC_DEFUN_ONCE([IT_FIND_ECJ_JAR],
+[
+  AC_MSG_CHECKING([for an ecj JAR file])
+  AC_ARG_WITH([ecj-jar],
+              [AS_HELP_STRING([--with-ecj-jar[[=PATH]]],specify location of an ECJ JAR file)],
+  [
+    if test -f "${withval}"; then
+      ECJ_JAR="${withval}"
+    fi
+  ],
+  [
+    ECJ_JAR=
+  ])
+  if test -z "${ECJ_JAR}"; then
+    for jar in /usr/share/java/eclipse-ecj.jar \
+      /usr/share/java/ecj.jar \
+      /usr/share/eclipse-ecj-3.{2,3,4,5}/lib/ecj.jar; do
+        if test -e $jar; then
+          ECJ_JAR=$jar
+	  break
+        fi
+      done
+      if test -z "${ECJ_JAR}"; then
+        ECJ_JAR=no
+      fi
+  fi
+  AC_MSG_RESULT(${ECJ_JAR})
+  AC_SUBST(ECJ_JAR)
+])
+
+AC_DEFUN_ONCE([IT_FIND_JAVAH],
+[
+  AC_REQUIRE([IT_CHECK_FOR_JDK])
+  JAVAH_DEFAULT=${SYSTEM_JDK_DIR}/bin/javah
+  AC_MSG_CHECKING([if a javah executable is specified])
+  AC_ARG_WITH([javah],
+              [AS_HELP_STRING([--with-javah[[=PATH]]],specify location of javah)],
+  [
+    if test "x${withval}" = "xyes"; then
+      JAVAH=no
+    else
+      JAVAH="${withval}"
+    fi
+  ],
+  [
+    JAVAH=no
+  ])
+  AC_MSG_RESULT(${JAVAH})
+  if test "x${JAVAH}" == "xno"; then
+    JAVAH=${JAVAH_DEFAULT}
+  fi
+  AC_MSG_CHECKING([if $JAVAH is a valid executable file])
+  if test -x "${JAVAH}" && test -f "${JAVAH}"; then
+    AC_MSG_RESULT([yes])
+  else
+    AC_MSG_RESULT([no])
+    JAVAH=""
+    AC_PATH_PROG(JAVAH, "javah")
+    if test -z "${JAVAH}"; then
+      AC_PATH_PROG(JAVAH, "gjavah")
+    fi
+    if test -z "${JAVAH}"; then
+      AC_MSG_ERROR("A Java header generator was not found.")
+    fi
+  fi
+  AC_SUBST(JAVAH)
+])
+
+AC_DEFUN_ONCE([IT_FIND_JAR],
+[
+  AC_REQUIRE([IT_CHECK_FOR_JDK])
+  JAR_DEFAULT=${SYSTEM_JDK_DIR}/bin/jar
+  AC_MSG_CHECKING([if a jar executable is specified])
+  AC_ARG_WITH([jar],
+              [AS_HELP_STRING([--with-jar[[=PATH]]],specify location of jar)],
+  [
+    if test "x${withval}" = "xyes"; then
+      JAR=no
+    else
+      JAR="${withval}"
+     fi
+  ],
+  [
+    JAR=no
+  ])
+  AC_MSG_RESULT(${JAR})
+  if test "x${JAR}" == "xno"; then
+    JAR=${JAR_DEFAULT}
+  fi
+  AC_MSG_CHECKING([if $JAR is a valid executable file])
+  if test -x "${JAR}" && test -f "${JAR}"; then
+    AC_MSG_RESULT([yes])
+  else
+    AC_MSG_RESULT([no])
+    JAR=""
+    AC_PATH_PROG(JAR, "jar")
+    if test -z "${JAR}"; then
+      AC_PATH_PROG(JAR, "gjar")
+    fi
+    if test -z "${JAR}"; then
+      AC_MSG_ERROR("No Java archive tool was found.")
+    fi
+  fi
+  AC_SUBST(JAR)
+])
+
+AC_DEFUN([IT_FIND_JAVADOC],
+[
+  AC_REQUIRE([IT_CHECK_FOR_JDK])
+  JAVADOC_DEFAULT=${SYSTEM_JDK_DIR}/bin/javadoc
+  AC_MSG_CHECKING([if a javadoc executable is specified])
+  AC_ARG_WITH([javadoc],
+              [AS_HELP_STRING([--with-javadoc[[=PATH]]],specify location of javadoc)],
+  [
+    if test "x${withval}" = "xyes"; then
+      JAVADOC=no
+    else
+      JAVADOC="${withval}"
+     fi
+  ],
+  [
+    JAVADOC=no
+  ])
+  AC_MSG_RESULT(${JAVADOC})
+  if test "x${JAVADOC}" == "xno"; then
+    JAVADOC=${JAVADOC_DEFAULT}
+  fi
+  AC_MSG_CHECKING([if $JAVADOC is a valid executable file])
+  if test -x "${JAVADOC}" && test -f "${JAVADOC}"; then
+    AC_MSG_RESULT([yes])
+  else
+    AC_MSG_RESULT([no])
+    JAVADOC=""
+    AC_PATH_PROG(JAVADOC, "javadoc")
+    if test -z "${JAVADOC}"; then
+      AC_PATH_PROG(JAVADOC, "gjdoc")
+    fi
+    if test -z "${JAVADOC}" && test "x$ENABLE_DOCS" = "xyes"; then
+      AC_MSG_ERROR("No Java documentation tool was found.")
+    fi
+  fi
+  AC_MSG_CHECKING([whether javadoc supports -J options])
+  CLASS=pkg/Test.java
+  mkdir tmp.$$
+  cd tmp.$$
+  mkdir pkg
+  cat << \EOF > $CLASS
+[/* [#]line __oline__ "configure" */
+package pkg;
+
+public class Test
+{
+  /**
+   * Does stuff.
+   *
+   * @param args arguments from cli.
+   */
+  public static void main(String[] args)
+  {
+    System.out.println("Hello World!");
+  }
+}
+]
+EOF
+  if $JAVADOC -J-Xmx896m pkg >&AS_MESSAGE_LOG_FD 2>&1; then
+    JAVADOC_KNOWS_J_OPTIONS=yes
+  else
+    JAVADOC_KNOWS_J_OPTIONS=no
+  fi
+  cd ..
+  rm -rf tmp.$$
+  AC_MSG_RESULT([${JAVADOC_KNOWS_J_OPTIONS}])
+  AC_SUBST(JAVADOC)
+  AC_SUBST(JAVADOC_KNOWS_J_OPTIONS)
+  AM_CONDITIONAL([JAVADOC_SUPPORTS_J_OPTIONS], test x"${JAVADOC_KNOWS_J_OPTIONS}" = "xyes")
+])
+
+AC_DEFUN([IT_FIND_TOOL],
+[AC_PATH_TOOL([$1],[$2])
+ if test x"$$1" = x ; then
+   AC_MSG_ERROR([The following program was not found on the PATH: $2])
+ fi
+ AC_SUBST([$1])
+])
+
+AC_DEFUN([IT_FIND_TOOLS],
+[AC_PATH_PROGS([$1],[$2])
+ if test x"$$1" = x ; then
+   AC_MSG_ERROR([None of the following programs could be found on the PATH: $2])
+ fi
+ AC_SUBST([$1])
+])
+
+AC_DEFUN_ONCE([IT_CHECK_FOR_JDK],
+[
+  AC_MSG_CHECKING([for a JDK home directory])
+  AC_ARG_WITH([jdk-home],
+	      [AS_HELP_STRING([--with-jdk-home[[=PATH]]],
+                              [jdk home directory (default is first predefined JDK found)])],
+              [
+                if test "x${withval}" = xyes
+                then
+                  SYSTEM_JDK_DIR=
+                elif test "x${withval}" = xno
+                then
+	          SYSTEM_JDK_DIR=
+	        else
+                  SYSTEM_JDK_DIR=${withval}
+                fi
+              ],
+              [
+	        SYSTEM_JDK_DIR=
+              ])
+  if test -z "${SYSTEM_JDK_DIR}"; then
+    AC_MSG_RESULT([not specified])
+    if test "x${enable_bootstrap}" = "xyes"; then
+      BOOTSTRAP_VMS="/usr/lib/jvm/java-gcj /usr/lib/jvm/gcj-jdk /usr/lib/jvm/cacao";
+    fi
+    ICEDTEA6_VMS="/usr/lib/jvm/icedtea-6 /usr/lib/jvm/icedtea6 /usr/lib/jvm/java-6-openjdk
+    		  /usr/lib/jvm/java-1.6.0-openjdk.x86_64 /usr/lib64/jvm/java-1.6.0-openjdk
+		  /usr/lib/jvm/java-1.6.0"
+    ICEDTEA7_VMS="/usr/lib/jvm/icedtea-7 /usr/lib/jvm/icedtea7 /usr/lib/jvm/java-1.7.0-openjdk
+    		  /usr/lib/jvm/java-1.7.0-openjdk.x86_64 /usr/lib64/jvm/java-1.7.0-openjdk
+		  /usr/lib/jvm/java-1.7.0 /usr/lib/jvm/java-7-openjdk"
+    for dir in ${BOOTSTRAP_VMS} ${ICEDTEA7_VMS} ${ICEDTEA6_VMS} \
+    	       /usr/lib/jvm/java-openjdk /usr/lib/jvm/openjdk /usr/lib/jvm/java-icedtea \
+	       /etc/alternatives/java_sdk_openjdk ; do
+       AC_MSG_CHECKING([for ${dir}]);
+       if test -d $dir; then
+         SYSTEM_JDK_DIR=$dir ;
+	 AC_MSG_RESULT([found]) ;
+	 break ;
+       else
+         AC_MSG_RESULT([not found]) ;
+       fi
+    done
+  else
+    AC_MSG_RESULT(${SYSTEM_JDK_DIR})
+  fi
+  if ! test -d "${SYSTEM_JDK_DIR}"; then
+    AC_MSG_ERROR("A JDK home directory could not be found.")
+  fi
+  AC_SUBST(SYSTEM_JDK_DIR)
+])
+
+AC_DEFUN([IT_USING_ECJ],[
+AC_CACHE_CHECK([if we are using ecj as javac], it_cv_ecj, [
+if $JAVAC -version 2>&1| grep '^Eclipse' >&AS_MESSAGE_LOG_FD ; then
+  it_cv_ecj=yes;
+else
+  it_cv_ecj=no;
+fi
+])
+USING_ECJ=$it_cv_ecj
+AC_SUBST(USING_ECJ)
+AC_PROVIDE([$0])dnl
+])
+
+dnl check that javac and java work
+AC_DEFUN_ONCE([IT_CHECK_JAVA_AND_JAVAC_WORK],[
+  AC_REQUIRE([IT_FIND_JAVA])
+  AC_REQUIRE([IT_FIND_COMPILER])
+  AC_CACHE_CHECK([if the VM and compiler work together], it_cv_jdk_works, [
+  CLASS=Test.java
+  BYTECODE=$(echo $CLASS|sed 's#\.java##')
+  mkdir tmp.$$
+  cd tmp.$$
+  cat << \EOF > $CLASS
+[/* [#]line __oline__ "configure" */
+
+public class Test
+{
+    public static void main(String[] args)
+    {
+      System.out.println("Hello World!");
+    }
+}]
+EOF
+  if $JAVAC -cp . $JAVACFLAGS -source 5 -target 5 $CLASS >&AS_MESSAGE_LOG_FD 2>&1; then
+    if $JAVA -classpath . $BYTECODE >&AS_MESSAGE_LOG_FD 2>&1; then
+      it_cv_jdk_works=yes;
+    else
+      it_cv_jdk_works=no;
+      AC_MSG_ERROR([VM failed to run compiled class.])
+    fi
+  else
+    it_cv_jdk_works=no;
+    AC_MSG_ERROR([Compiler failed to compile Java code.])
+  fi
+  rm -f $CLASS *.class
+  cd ..
+  rmdir tmp.$$
+  ])
+AC_PROVIDE([$0])dnl
+])
+
+AC_DEFUN([IT_CHECK_ENABLE_WARNINGS],
+[
+  AC_MSG_CHECKING(whether to enable Java compiler warnings)
+  AC_ARG_ENABLE([warnings],
+	      [AS_HELP_STRING(--enable-warnings,produce warnings from javac/ecj [[default=no]])],
+  [
+    ENABLE_WARNINGS="${enableval}"
+  ],
+  [
+    ENABLE_WARNINGS=no
+  ])
+
+  AC_MSG_RESULT(${ENABLE_WARNINGS})
+  AM_CONDITIONAL(ENABLE_WARNINGS, test x"${ENABLE_WARNINGS}" = "xyes")
+  AC_SUBST(ENABLE_WARNINGS)
+])
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/autogen.sh	Thu Jun 12 00:42:40 2014 +0100
@@ -0,0 +1,145 @@
+#!/bin/sh
+
+# autogen.sh -- Generate autotools build infrastructure
+# Copyright (C) 2014 Red Hat, Inc.
+#
+# This file is part of IcedTea-Sound.
+#
+# IcedTea-Sound 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-Sound 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-Sound; see the file COPYING.  If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 USA.
+
+# Test for autoconf commands.
+
+# Test for autoconf.
+
+HAVE_AUTOCONF=false
+
+for AUTOCONF in autoconf autoconf259; do
+    if ${AUTOCONF} --version > /dev/null 2>&1; then
+        AUTOCONF_VERSION=`${AUTOCONF} --version | head -1 | sed 's/^[^0-9]*\([0-9.][0-9.]*\).*/\1/'`
+#        echo ${AUTOCONF_VERSION}
+        case ${AUTOCONF_VERSION} in
+            2.59* | 2.6[0-9]* )
+                HAVE_AUTOCONF=true
+                break;
+                ;;
+        esac
+    fi
+done
+
+# Test for autoheader.
+
+HAVE_AUTOHEADER=false
+
+for AUTOHEADER in autoheader autoheader259; do
+    if ${AUTOHEADER} --version > /dev/null 2>&1; then
+        AUTOHEADER_VERSION=`${AUTOHEADER} --version | head -1 | sed 's/^[^0-9]*\([0-9.][0-9.]*\).*/\1/'`
+#        echo ${AUTOHEADER_VERSION}
+        case ${AUTOHEADER_VERSION} in
+            2.59* | 2.6[0-9]* )
+                HAVE_AUTOHEADER=true
+                break;
+                ;;
+        esac
+    fi
+done
+
+# Test for autoreconf.
+
+HAVE_AUTORECONF=false
+
+for AUTORECONF in autoreconf; do
+    if ${AUTORECONF} --version > /dev/null 2>&1; then
+        AUTORECONF_VERSION=`${AUTORECONF} --version | head -1 | sed 's/^[^0-9]*\([0-9.][0-9.]*\).*/\1/'`
+#        echo ${AUTORECONF_VERSION}
+        case ${AUTORECONF_VERSION} in
+            2.59* | 2.6[0-9]* )
+                HAVE_AUTORECONF=true
+                break;
+                ;;
+        esac
+    fi
+done
+
+if test ${HAVE_AUTOCONF} = false; then
+    echo "No proper autoconf was found."
+    echo "You must have autoconf 2.59 or later installed."
+    exit 1
+fi
+
+if test ${HAVE_AUTOHEADER} = false; then
+    echo "No proper autoheader was found."
+    echo "You must have autoconf 2.59 or later installed."
+    exit 1
+fi
+
+if test ${HAVE_AUTORECONF} = false; then
+    echo "No proper autoreconf was found."
+    echo "You must have autoconf 2.59 or later installed."
+    exit 1
+fi
+
+
+# Test for automake commands.
+
+# Test for aclocal.
+
+HAVE_ACLOCAL=false
+
+for ACLOCAL in aclocal aclocal-1.10; do
+    if ${ACLOCAL} --version > /dev/null 2>&1; then
+        ACLOCAL_VERSION=`${ACLOCAL} --version | head -1 | sed 's/^[^0-9]*\([0-9.][0-9.]*\).*/\1/'`
+#        echo ${ACLOCAL_VERSION}
+        case ${ACLOCAL_VERSION} in
+            1.9.[6-9] | 1.1[0-9]* )
+                HAVE_ACLOCAL=true
+                break;
+                ;;
+        esac
+    fi
+done
+
+# Test for automake.
+
+HAVE_AUTOMAKE=false
+
+for AUTOMAKE in automake automake-1.10; do
+    if ${AUTOMAKE} --version > /dev/null 2>&1; then
+        AUTOMAKE_VERSION=`${AUTOMAKE} --version | head -1 | sed 's/^[^0-9]*\([0-9.][0-9.]*\).*/\1/'`
+#        echo ${AUTOMAKE_VERSION}
+        case ${AUTOMAKE_VERSION} in
+            1.9.[6-9] | 1.1[0-9]* )
+                HAVE_AUTOMAKE=true
+                break;
+                ;;
+        esac
+    fi
+done
+
+if test ${HAVE_ACLOCAL} = false; then
+    echo "No proper aclocal was found."
+    echo "You must have automake 1.9.6 or later installed."
+    exit 1
+fi
+
+if test ${HAVE_AUTOMAKE} = false; then
+    echo "No proper automake was found."
+    echo "You must have automake 1.9.6 or later installed."
+    exit 1
+fi
+
+
+export ACLOCAL AUTOCONF AUTOHEADER AUTOMAKE
+
+${AUTORECONF} --force --install
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/configure.ac	Thu Jun 12 00:42:40 2014 +0100
@@ -0,0 +1,77 @@
+dnl configure.ac -- Input for configure script
+dnl Copyright (C) 2014 Red Hat, Inc.
+dnl
+dnl This file is part of IcedTea-Sound.
+dnl
+dnl IcedTea-Sound is free software; you can redistribute it and/or
+dnl modify it under the terms of the GNU General Public License as published by
+dnl the Free Software Foundation, version 2.
+dnl 
+dnl IcedTea-Sound is distributed in the hope that it will be useful,
+dnl but WITHOUT ANY WARRANTY; without even the implied warranty of
+dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+dnl General Public License for more details.
+dnl 
+dnl You should have received a copy of the GNU General Public License
+dnl along with IcedTea-Sound; see the file COPYING.  If not, write to
+dnl the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+dnl 02110-1301 USA.
+
+AC_INIT([icedtea-sound], [1.0.0pre], [distro-pkg-dev@openjdk.java.net])
+AM_INIT_AUTOMAKE([1.9 tar-pax foreign])
+AM_MAINTAINER_MODE([enable])
+AC_CONFIG_FILES([Makefile])
+
+# Older automake doesn't generate these correctly
+abs_top_builddir=`pwd -P`
+AC_SUBST(abs_top_builddir)
+abs_top_srcdir=`dirname $0`
+cd $abs_top_srcdir
+abs_top_srcdir=`pwd`
+cd $abs_top_builddir
+AC_SUBST(abs_top_srcdir)
+
+AC_CANONICAL_HOST
+
+AC_PROG_CC
+if test "x$CC" = x; then
+   AC_MSG_ERROR([No C compiler found.])
+fi
+
+IT_SET_ARCH_SETTINGS
+
+IT_FIND_TOOLS([FIND], [gfind find])
+IT_FIND_TOOL([ZIP], [zip])
+
+IT_CHECK_JAVA_AND_JAVAC_WORK
+IT_FIND_JAVAH
+IT_FIND_JAR
+IT_FIND_JAVADOC
+AC_CONFIG_FILES([javac], [chmod +x javac])
+
+IT_CHECK_ENABLE_WARNINGS
+
+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)
+
+AC_MSG_CHECKING([whether to build documentation])
+AC_ARG_ENABLE([docs],
+	      [AS_HELP_STRING([--disable-docs],
+	      		      [Disable generation of documentation])],
+	      [ENABLE_DOCS="${enableval}"], [ENABLE_DOCS='yes'])
+AM_CONDITIONAL([ENABLE_DOCS], [test x$ENABLE_DOCS = xyes])
+AC_MSG_RESULT(${ENABLE_DOCS})
+
+# Arguments passed to configure.
+AC_SUBST(CONFIGURE_ARGS)
+CONFIGURE_ARGS="$ac_configure_args"
+
+AC_OUTPUT
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/javac.in	Thu Jun 12 00:42:40 2014 +0100
@@ -0,0 +1,104 @@
+#!/usr/bin/perl -w
+
+# javac.in -- Template wrapper script for running javac
+# Copyright (C) 2014 Red Hat, Inc.
+#
+# This file is part of IcedTea-Sound.
+#
+# IcedTea-Sound 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-Sound 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-Sound; see the file COPYING.  If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 USA.
+
+use strict;
+use constant NO_DUP_ARGS => qw(-source -target -d -encoding);
+use constant STRIP_ARGS_1 => qw(-Werror -implicit:none);
+use constant STRIP_ARGS_2 => qw(-Xmaxwarns);
+
+my ($ECJ_WARNINGS, $JAVAC_WARNINGS);
+
+if ("@ENABLE_WARNINGS@" eq "yes")
+{
+    $ECJ_WARNINGS="-warn:-unused,warningToken";
+    $JAVAC_WARNINGS="-Xlint";
+}
+else
+{
+    $ECJ_WARNINGS="-nowarn";
+    $JAVAC_WARNINGS="-nowarn";
+}
+
+my @bcoption;
+my @bcoptions = grep {$_ =~ '^-Xbootclasspath/p:' } @ARGV;
+my $bc = $bcoptions[0];
+my $systembc = glob '@abs_top_builddir@/bootstrap/jdk1.6.0/jre/lib/rt.jar';
+if ($bc)
+{
+    $bc =~ s/^[^:]*://;
+    $systembc = join ":", $bc, $systembc;
+}
+push @bcoption, '-bootclasspath', $systembc
+    unless grep {$_ eq '-bootclasspath'} @ARGV;
+my @ecj_parms = ($ECJ_WARNINGS, @bcoption);
+my @javac_parms = ($JAVAC_WARNINGS, '-Xprefer:source',
+		   '-XDignore.symbol.file=true', '-J-Xmx1024m');
+
+# Work around ecj's inability to handle duplicate command-line
+# options and unknown javac options.
+sub gen_ecj_opts
+{
+    my @new_args = @{$_[0]};
+
+    for my $opt (NO_DUP_ARGS) 
+    {
+	my @indices = reverse grep {$new_args[$_] eq $opt} 0..$#new_args;
+	if (@indices > 1) {
+	    shift @indices;    # keep last instance only
+	    splice @new_args, $_, 2 for @indices;
+	}
+    }
+
+    for my $opt (STRIP_ARGS_1) 
+    {
+	my @indices = reverse grep {$new_args[$_] eq $opt} 0..$#new_args;
+	splice @new_args, $_, 1 for @indices;
+    }
+
+    for my $opt (STRIP_ARGS_2) 
+    {
+	my @indices = reverse grep {$new_args[$_] eq $opt} 0..$#new_args;
+	splice @new_args, $_, 2 for @indices;
+    }
+
+    return @new_args;
+}
+
+if ( -e "@JAVAC@" )
+{
+    if ("@USING_ECJ@" eq "yes")
+    {
+	my @ecj_args = gen_ecj_opts( \@ARGV );
+	exec '@JAVAC@', @ecj_parms, @ecj_args ;
+    }
+    else
+    {
+	exec '@JAVAC@', @javac_parms, @ARGV ;
+    }
+}
+else
+{
+    my @ecj_args = gen_ecj_opts( \@ARGV );
+    my @CLASSPATH = ('@ECJ_JAR@');
+    push @CLASSPATH, split /:/, $ENV{"CLASSPATH"} if exists $ENV{"CLASSPATH"};
+    $ENV{"CLASSPATH"} = join ':', @CLASSPATH;
+    exec '@JAVA@', 'org.eclipse.jdt.internal.compiler.batch.Main', @ecj_parms, @ecj_args;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java/META-INF/services/javax.sound.sampled.spi.MixerProvider	Thu Jun 12 00:42:40 2014 +0100
@@ -0,0 +1,3 @@
+# Providers of sound mixers
+
+org.classpath.icedtea.pulseaudio.PulseAudioMixerProvider
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java/org/classpath/icedtea/pulseaudio/ContextEvent.java	Thu Jun 12 00:42:40 2014 +0100
@@ -0,0 +1,104 @@
+/* ContextEvent.java
+   Copyright (C) 2008 Red Hat, Inc.
+
+This file is part of IcedTea-Sound.
+
+IcedTea-Sound 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-Sound 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-Sound; 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.Arrays;
+
+/**
+ * This class encapsulates a change in the PulseAudio's connection context.
+ *
+ * When this event is fired, something has happened to the connection of this
+ * program to the PulseAudio server.
+ */
+class ContextEvent {
+    // There are certain enumerations from pulse audio that we need to use in
+    // the java side. For all of these, we declare static longs in the proper
+    // java classes and we use static native methods to initialize them to
+    // the values used by pulse audio. This makes us immune to changes in
+    // the integer values of the enum symbols in pulse audio.
+    /**
+     *  Basically, what is the new state of the context
+     *  These will be initialized to the proper values in the JNI.
+     */
+    static long UNCONNECTED  = -1,
+                CONNECTING   = -1,
+                AUTHORIZING  = -1,
+                SETTING_NAME = -1,
+                READY        = -1,
+                FAILED       = -1,
+                TERMINATED   = -1;
+
+    private static native void init_constants();
+
+    static {
+        SecurityWrapper.loadNativeLibrary();
+        init_constants();
+    }
+
+    /**
+     * Throws an IllegalStateException if value is not one of the above
+     * context events. We do this for all pulse audio enumerations that
+     * we need to use in the java side. If pulse audio decides to add
+     * new events/states, we need to add them to the classes. The exception
+     * will let us know.
+     * return the input if there is no error
+     *
+     * @param value is the context event to be checked against one of the known
+     *        events.
+     * @return value if it is a known event. Otherwise throw an exception.
+     */
+    public static long checkNativeEnumReturn(long value) {
+        if (!Arrays.asList(
+                UNCONNECTED, CONNECTING, AUTHORIZING, SETTING_NAME,
+                READY, FAILED, TERMINATED
+            ).contains(value)) {
+            throw new IllegalStateException("Illegal constant for ContextEvent: " + value);
+        }
+        return value;
+    }
+
+    private long type;
+
+    public ContextEvent(long type) {
+        this.type = checkNativeEnumReturn(type);
+    }
+
+    public long getType() {
+        return type;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java/org/classpath/icedtea/pulseaudio/ContextListener.java	Thu Jun 12 00:42:40 2014 +0100
@@ -0,0 +1,50 @@
+/* ContextListener.java
+   Copyright (C) 2008 Red Hat, Inc.
+
+This file is part of IcedTea-Sound.
+
+IcedTea-Sound 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-Sound 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-Sound; 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;
+
+/**
+ * This interface specifies a listener for a change in PulseAudio's context state
+ * eg: the connection to the server becomes ready, fails or ends.
+ *
+ */
+
+interface ContextListener {
+
+    void update(ContextEvent e);
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java/org/classpath/icedtea/pulseaudio/Debug.java	Thu Jun 12 00:42:40 2014 +0100
@@ -0,0 +1,114 @@
+/* EventLoop.java
+   Copyright (C) 2008 Red Hat, Inc.
+
+This file is part of IcedTea-Sound.
+
+IcedTea-Sound 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-Sound 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-Sound; 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;
+
+/**
+ *
+ * A simple debugging class. Set the debugging level through the system property
+ * pulseaudio.debugLevel=<Level>. Level is one of Verbose, Debug, Info, Warning,
+ * Error or None
+ *
+ * and then do DebugLevel.println(level, string) and so on
+ *
+ */
+
+class Debug {
+
+    enum DebugLevel {
+        Verbose, Debug, Info, Warning, Error, None
+    }
+
+    private static DebugLevel currentDebugLevel = DebugLevel.None;
+
+    static {
+        // System.out.println("PulseAudio: initializing Debug");
+
+        String systemSetting;
+        try {
+            systemSetting = System.getProperty("pulseaudio.debugLevel");
+        } catch (SecurityException e) {
+            // sigh, we cant read that property
+            systemSetting = null;
+        }
+
+        DebugLevel wantedLevel;
+        try {
+            wantedLevel = DebugLevel.valueOf(systemSetting);
+
+        } catch (IllegalArgumentException e) {
+            wantedLevel = DebugLevel.Info;
+        } catch (NullPointerException e) {
+            wantedLevel = DebugLevel.None;
+        }
+
+        currentDebugLevel = wantedLevel;
+        println(DebugLevel.Info, "Using debug level: " + currentDebugLevel);
+    }
+
+    static void println(String string) {
+        println(DebugLevel.Info, string);
+    }
+
+    static void print(DebugLevel level, String string) {
+        int result = level.compareTo(currentDebugLevel);
+        if (result >= 0) {
+            if (level.compareTo(DebugLevel.Error) >= 0) {
+                System.err.print(string);
+            } else {
+                System.out.print(string);
+            }
+        } else {
+            // do nothing
+        }
+    }
+
+    static void println(DebugLevel level, String string) {
+
+        int result = level.compareTo(currentDebugLevel);
+        if (result >= 0) {
+            if (level.compareTo(DebugLevel.Error) >= 0) {
+                System.err.println("DEBUG: pulse-java: " + string);
+            } else {
+                System.out.println("DEBUG: pulse-java: " + string);
+            }
+        } else {
+            // do nothing
+        }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java/org/classpath/icedtea/pulseaudio/EventLoop.java	Thu Jun 12 00:42:40 2014 +0100
@@ -0,0 +1,246 @@
+/* EventLoop.java
+   Copyright (C) 2008 Red Hat, Inc.
+
+This file is part of IcedTea-Sound.
+
+IcedTea-Sound 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-Sound 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-Sound; 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 org.classpath.icedtea.pulseaudio.ContextEvent;
+import org.classpath.icedtea.pulseaudio.Debug.DebugLevel;
+
+/**
+ * This class wraps pulseaudio's event loop. It also holds the lock used in the
+ * rest of pulse-java
+ */
+
+final class EventLoop implements Runnable {
+
+    /*
+     * any methods that can obstruct the behaviour of pa_mainloop should run
+     * synchronized
+     */
+
+    /*
+     * the threadLock object is the object used for synchronizing the
+     * non-thread-safe operations of pulseaudio's c api
+     */
+    final Object threadLock = new Object();
+
+    private static EventLoop instance = null;
+
+    private List<ContextListener> contextListeners;
+    // private List<SourceDataLine> lines;
+    private String appName;
+    private String serverString;
+
+    private long status;
+    // private boolean eventLoopIsRunning = false;
+
+    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();
+
+    /*
+     * These fields hold pointers
+     */
+    private byte[] contextPointer;
+    private byte[] mainloopPointer;
+
+    static {
+        SecurityWrapper.loadNativeLibrary();
+    }
+
+    private EventLoop() {
+        contextListeners = new ArrayList<ContextListener>();
+    }
+
+    synchronized static EventLoop getEventLoop() {
+        if (instance == null) {
+            instance = new EventLoop();
+        }
+        return instance;
+    }
+
+    void setAppName(String appName) {
+        this.appName = appName;
+    }
+
+    void setServer(String serverString) {
+        this.serverString = serverString;
+    }
+
+    @Override
+    public void run() {
+        native_setup(this.appName, this.serverString);
+
+        Debug.println(DebugLevel.Info, "Eventloop.run(): eventloop starting");
+
+        /*
+         * 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();
+
+                    // clean up the listeners
+                    synchronized (contextListeners) {
+                        contextListeners.clear();
+                    }
+
+                    Debug.println(DebugLevel.Info,
+                            "EventLoop.run(): event loop terminated");
+
+                    return;
+
+                }
+            }
+        }
+
+    }
+
+    void addContextListener(ContextListener contextListener) {
+        synchronized (contextListeners) {
+            contextListeners.add(contextListener);
+        }
+    }
+
+    void removeContextListener(ContextListener contextListener) {
+        synchronized (contextListeners) {
+            contextListeners.remove(contextListener);
+        }
+    }
+
+    long getStatus() {
+        return this.status;
+    }
+
+    void update(long status) {
+        synchronized (threadLock) {
+            // System.out.println(this.getClass().getName()
+            // + ".update() called! status = " + status);
+            this.status = status;
+            fireEvent(new ContextEvent(status));
+        }
+
+        if (status == ContextEvent.FAILED) {
+            Debug.println(DebugLevel.Warning,
+                          "EventLoop.update(): Context failed");
+        }
+    }
+
+    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);
+            }
+        }
+
+    }
+
+    byte[] getContextPointer() {
+        return contextPointer;
+    }
+
+    byte[] getMainLoopPointer() {
+        return mainloopPointer;
+    }
+
+    private native byte[] nativeUpdateTargetPortNameList();
+
+    private native byte[] nativeUpdateSourcePortNameList();
+
+    synchronized List<String> updateTargetPortNameList() {
+        targetPortNameList = new ArrayList<String>();
+        Operation op;
+        synchronized (this.threadLock) {
+            op = new Operation(nativeUpdateTargetPortNameList());
+        }
+
+        op.waitForCompletion();
+
+        assert (op.getState() == Operation.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.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/src/java/org/classpath/icedtea/pulseaudio/Operation.java	Thu Jun 12 00:42:40 2014 +0100
@@ -0,0 +1,166 @@
+/* Operation.java
+   Copyright (C) 2008 Red Hat, Inc.
+
+This file is part of IcedTea-Sound.
+
+IcedTea-Sound 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-Sound 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-Sound; 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.Arrays;
+
+/**
+ * Encapsulates a pa_operation object
+ *
+ * This is really needed only so that we can deallocate the reference counted
+ * object. Any time a function returns an Operation object, the reference has
+ * been incremented. The object wont be freed unless a releaseReference() is
+ * done.
+ *
+ * Please see the pulseaudio api docs for more information on a pa_opreation
+ * object
+ *
+ *
+ *
+ */
+
+class Operation {
+
+    private byte[] operationPointer;
+    private EventLoop eventLoop;
+
+    // These should never be written to in java. They will be initialized
+    // properly in native code.
+    public static long RUNNING   = -1,
+                       DONE      = -1,
+                       CANCELLED = -1;
+
+    private static native void init_constants();
+
+    static {
+        SecurityWrapper.loadNativeLibrary();
+        init_constants();
+    }
+
+    // If value is not one of RUNNING, DONE, CANCELLED, throw an
+    // IllegalStateException. Otherwise return the input.
+    private static long checkNativeOperationState(long value) {
+        if (!Arrays.asList(RUNNING, DONE, CANCELLED).contains(value)) {
+            throw new IllegalStateException("Unknown operation state: " + value);
+        }
+        return value;
+    }
+
+    private native void native_ref();
+
+    private native void native_unref();
+
+    private native long native_get_state();
+
+    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();
+    }
+
+    /**
+     * Increase reference count by 1
+     */
+    void addReference() {
+        assert (operationPointer != null);
+        synchronized (eventLoop.threadLock) {
+            native_ref();
+        }
+    }
+
+    /**
+     * Decrease reference count by 1. If the count reaches 0, object will be freed
+     */
+    void releaseReference() {
+        assert (operationPointer != null);
+        synchronized (eventLoop.threadLock) {
+            native_unref();
+        }
+        operationPointer = null;
+    }
+
+    // FIXME broken function
+    boolean isNull() {
+        if (operationPointer == null) {
+            return true;
+        }
+        return false;
+    }
+
+    long getState() {
+        assert (operationPointer != null);
+        synchronized (eventLoop.threadLock) {
+            return checkNativeOperationState(native_get_state());
+        }
+    }
+
+    /**
+     * Block until the operation has completed
+     *
+     */
+    void waitForCompletion() {
+        assert (operationPointer != null);
+
+        boolean interrupted = false;
+        do {
+            synchronized (eventLoop.threadLock) {
+                if (getState() == DONE) {
+                    return;
+                }
+                try {
+                    eventLoop.threadLock.wait();
+                } catch (InterruptedException e) {
+                    // ingore the interrupt for now
+                    interrupted = true;
+                }
+            }
+        } while (getState() != DONE);
+
+        // let the caller know about the interrupt
+        if (interrupted) {
+            Thread.currentThread().interrupt();
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java/org/classpath/icedtea/pulseaudio/PulseAudioClip.java	Thu Jun 12 00:42:40 2014 +0100
@@ -0,0 +1,574 @@
+/* PulseAudioClip.java
+   Copyright (C) 2008 Red Hat, Inc.
+
+This file is part of IcedTea-Sound.
+
+IcedTea-Sound 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-Sound 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-Sound; 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.AudioSystem;
+import javax.sound.sampled.Clip;
+import javax.sound.sampled.DataLine;
+import javax.sound.sampled.Line;
+import javax.sound.sampled.LineUnavailableException;
+
+import org.classpath.icedtea.pulseaudio.Debug.DebugLevel;
+import org.classpath.icedtea.pulseaudio.Stream.WriteListener;
+
+public final class PulseAudioClip extends PulseAudioDataLine implements Clip,
+        PulseAudioPlaybackLine {
+
+    private byte[] data = null;
+
+    // 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 = "Audio Clip";
+
+    private Object clipLock = new Object();
+    private int loopsLeft = 0;
+
+    // private Semaphore clipSemaphore = new Semaphore(1);
+
+    /**
+     * This thread runs
+     *
+     */
+    private final 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);
+
+        Debug.println(DebugLevel.Verbose,
+                "PulseAudioClip$ClipThread.writeFrames(): Writing");
+
+        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);
+    }
+
+    PulseAudioClip(AudioFormat[] formats, AudioFormat defaultFormat) {
+        this.supportedFormats = formats;
+        this.defaultFormat = defaultFormat;
+        this.currentFormat = defaultFormat;
+        this.streamName = DEFAULT_CLIP_NAME;
+
+        clipThread = new ClipThread();
+
+    }
+
+    @Override
+    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 available() {
+        return 0; // a clip always returns 0
+    }
+
+    @Override
+    public void close() {
+
+        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();
+
+        Debug.println(DebugLevel.Verbose, "PulseAudioClip.close(): "
+                + "Clip closed");
+
+    }
+
+    /*
+     *
+     * drain() on a Clip should block until the entire clip has finished playing
+     * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4732218
+     */
+    @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 (long) (frameCount / currentFormat.getFrameRate() * SECONDS_TO_MICROSECONDS);
+        }
+    }
+
+    @Override
+    public long getMicrosecondPosition() {
+        if (!isOpen) {
+            throw new IllegalStateException("Line not open");
+        }
+
+        synchronized (clipLock) {
+            return (long) (framesSinceOpen / currentFormat.getFrameRate() * SECONDS_TO_MICROSECONDS);
+        }
+    }
+
+    @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 {
+
+        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);
+        controls.add(volumeControl);
+
+        PulseAudioMixer mixer = PulseAudioMixer.getInstance();
+        mixer.addSourceLine(this);
+
+        isOpen = true;
+        Debug.println(DebugLevel.Verbose, "PulseAudioClip.open(): Clip opened");
+
+    }
+
+    // FIXME
+    @Override
+    public byte[] native_set_volume(float value) {
+        return stream.native_set_volume(value);
+    }
+
+    public byte[] native_update_volume() {
+        return stream.native_update_volume();
+    }
+
+    @Override
+    public float getCachedVolume() {
+        return stream.getCachedVolume();
+    }
+
+    @Override
+    public void setCachedVolume(float value) {
+        stream.setCachedVolume(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 < 0 || 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");
+        }
+
+        if (start < 0) {
+            throw new IllegalArgumentException(
+                    "starting point must be greater than or equal to 0");
+        }
+
+        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() / SECONDS_TO_MICROSECONDS;
+
+        /* make frameIndex positive */
+        while (frameIndex < 0) {
+            frameIndex += frameCount;
+        }
+
+        /* frameIndex is in the range [0, frameCount-1], inclusive */
+        frameIndex = frameIndex % frameCount;
+
+        synchronized (clipLock) {
+            currentFrame = (int) frameIndex;
+        }
+
+    }
+
+    @Override
+    public void start() {
+        if (isStarted) {
+            return;
+        }
+
+        super.start();
+
+        if (!clipThread.isAlive()) {
+            synchronized (clipLock) {
+                loopsLeft = 0;
+            }
+            clipThread = new ClipThread();
+            clipThread.start();
+        }
+
+    }
+
+    @Override
+    public void stop() {
+        if (!isOpen) {
+            throw new IllegalStateException("Line not open");
+        }
+
+        /* do what start does and ignore if called at the wrong time */
+        if (!isStarted) {
+            return;
+        }
+
+        if (clipThread.isAlive()) {
+            clipThread.interrupt();
+        }
+        try {
+            clipThread.join();
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+        synchronized (clipLock) {
+            loopsLeft = 0;
+        }
+
+        super.stop();
+
+    }
+
+    @Override
+    public 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/src/java/org/classpath/icedtea/pulseaudio/PulseAudioDataLine.java	Thu Jun 12 00:42:40 2014 +0100
@@ -0,0 +1,498 @@
+/* PulseAudioClip.java
+   Copyright (C) 2008 Red Hat, Inc.
+
+This file is part of IcedTea-Sound.
+
+IcedTea-Sound 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-Sound 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-Sound; 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;
+
+/**
+ *
+ * This class contains code that is used by Clip, SourceDataLine and
+ * TargetDataLine
+ *
+ */
+abstract class PulseAudioDataLine extends PulseAudioLine implements DataLine {
+
+    protected static final int DEFAULT_BUFFER_SIZE = StreamBufferAttributes.SANE_DEFAULT;
+
+    // override this to set the stream name
+    protected String streamName;
+
+    // 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;
+
+    // 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");
+        }
+
+        PulseAudioMixer mixer = PulseAudioMixer.getInstance();
+        if (!mixer.isOpen()) {
+            mixer.open();
+        }
+
+        eventLoop = EventLoop.getEventLoop();
+
+        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;
+                }
+
+                String formatString = (String) myFormat
+                        .getProperty(PulseAudioMixer.PULSEAUDIO_FORMAT_KEY);
+                synchronized (eventLoop.threadLock) {
+
+                    stream = new Stream(eventLoop.getContextPointer(),
+                            streamName, Stream.Format.valueOf(formatString),
+                            (int) sampleRate, myFormat.getChannels());
+
+                }
+                currentFormat = format;
+                isOpen = true;
+            }
+        }
+
+        if (!isOpen()) {
+            throw new IllegalArgumentException("Invalid format");
+        }
+
+        // System.out.println("Stream " + stream + " created");
+
+    }
+
+    /**
+     * This method adds the listeners used to find out when the stream has
+     * connected/disconnected etc to the actual stream.
+     */
+    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)));
+                        }
+                        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() {
+                if (!dataWritten) {
+                    fireLineEvent(new LineEvent(PulseAudioDataLine.this,
+                            LineEvent.Type.START, framesSinceOpen));
+                    synchronized (PulseAudioDataLine.this) {
+                        PulseAudioDataLine.this.notifyAll();
+                    }
+                }
+                dataWritten = true;
+
+            }
+        };
+
+        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;
+
+        }
+        try {
+            semaphore.acquire();
+            synchronized (eventLoop.threadLock) {
+                if (stream.getState() != Stream.STATE_READY) {
+                    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);
+
+    }
+
+    @Override
+    public void open() throws LineUnavailableException {
+        assert (defaultFormat != null);
+        open(defaultFormat, DEFAULT_BUFFER_SIZE);
+    }
+
+    @Override
+    public void close() {
+
+        if (!isOpen()) {
+            // For whatever reason, we are being asked to close
+            // a line that is not even open.
+            return;
+        }
+
+        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;
+    }
+
+    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;
+    }
+
+    @Override
+    public void start() {
+        if (!isOpen()) {
+            throw new IllegalStateException(
+                    "Line must be open()ed before it can be start()ed");
+        }
+
+        if (isStarted) {
+            return;
+
+        }
+        if (dataWritten && (!isStarted)) {
+            fireLineEvent(new LineEvent(PulseAudioDataLine.this,
+                    LineEvent.Type.START, framesSinceOpen));
+        }
+
+        Operation op;
+        synchronized (eventLoop.threadLock) {
+            op = stream.unCork();
+        }
+
+        op.waitForCompletion();
+        op.releaseReference();
+        synchronized (this) {
+            this.notifyAll();
+        }
+        isStarted = true;
+
+    }
+
+    @Override
+    public synchronized void stop() {
+        if (!isOpen()) {
+            // For some reason, we are being asked to stop a line
+            // that isn't even open.
+            return;
+        }
+        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)
+     */
+    @Override
+    public boolean isActive() {
+        return isStarted;
+    }
+
+    @Override
+    public boolean isRunning() {
+        return isStarted && dataWritten;
+    }
+
+    protected abstract void connectLine(int bufferSize, Stream masterStream)
+            throws LineUnavailableException;
+
+    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 stream.getBufferSize();
+    }
+
+    @Override
+    public AudioFormat getFormat() {
+        if (!isOpen()) {
+            return defaultFormat;
+        }
+        return currentFormat;
+    }
+
+    @Override
+    public float getLevel() {
+        return AudioSystem.NOT_SPECIFIED;
+    }
+
+    /**
+     *
+     * @param streamName
+     *            the name of this audio stream
+     */
+    public void setName(String streamName) {
+        if (isOpen()) {
+
+            Operation o;
+            synchronized (eventLoop.threadLock) {
+                o = stream.setName(streamName);
+            }
+            o.waitForCompletion();
+            o.releaseReference();
+
+        }
+
+        this.streamName = streamName;
+
+    }
+
+    /**
+     *
+     * @return the name of this audio stream/clip
+     */
+    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/src/java/org/classpath/icedtea/pulseaudio/PulseAudioLine.java	Thu Jun 12 00:42:40 2014 +0100
@@ -0,0 +1,124 @@
+/* PulseAudioClip.java
+   Copyright (C) 2008 Red Hat, Inc.
+
+This file is part of IcedTea-Sound.
+
+IcedTea-Sound 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-Sound 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-Sound; 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;
+
+    @Override
+    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]);
+    }
+
+    @Override
+    public boolean isControlSupported(Type control) {
+        for (Control myControl : controls) {
+            //Control.Type's known descendants keep a set of
+            //static Types.
+            if (myControl.getType().equals(control)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public boolean isOpen() {
+        return isOpen;
+    }
+
+    @Override
+    public void removeLineListener(LineListener listener) {
+        lineListeners.remove(listener);
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java/org/classpath/icedtea/pulseaudio/PulseAudioMixer.java	Thu Jun 12 00:42:40 2014 +0100
@@ -0,0 +1,792 @@
+/* PulseAudioMixer.java
+   Copyright (C) 2008 Red Hat, Inc.
+
+This file is part of IcedTea-Sound.
+
+IcedTea-Sound 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-Sound 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-Sound; 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.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Semaphore;
+
+import javax.sound.sampled.AudioFormat;
+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.AudioFormat.Encoding;
+import javax.sound.sampled.Control.Type;
+
+import org.classpath.icedtea.pulseaudio.Debug.DebugLevel;
+
+public final class PulseAudioMixer implements Mixer {
+    // singleton
+
+    private 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";
+    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() {
+
+        Debug.println(DebugLevel.Verbose, "PulseAudioMixer.PulseAudioMixer(): "
+                + "Contructing 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();
+
+        Debug.println(DebugLevel.Verbose, "PulseAudioMixer.PulseAudioMixer(): "
+                + "Finished constructing PulseAudioMixer");
+
+    }
+
+    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);
+        }
+
+        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)) {
+            return new PulseAudioSourceDataLine(formats, defaultFormat);
+        }
+
+        if ((info.getLineClass() == TargetDataLine.class)) {
+            /* check for permission to record audio */
+            AudioPermission perm = new AudioPermission("record", null);
+            perm.checkGuard(null);
+
+            return new PulseAudioTargetDataLine(formats, defaultFormat);
+        }
+
+        if ((info.getLineClass() == Clip.class)) {
+            return new PulseAudioClip(formats, defaultFormat);
+        }
+
+        if (Port.Info.class.isInstance(info)) {
+            Port.Info portInfo = (Port.Info) info;
+            if (portInfo.isSource()) {
+                /* check for permission to record audio */
+                AudioPermission perm = new AudioPermission("record", null);
+                perm.checkGuard(null);
+
+                return new PulseAudioSourcePort(portInfo.getName());
+            } else {
+                return new PulseAudioTargetPort(portInfo.getName());
+            }
+        }
+
+        Debug.println(DebugLevel.Info, "PulseAudioMixer.getLine(): "
+                + "No matching line supported by PulseAudio");
+
+        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 Line.Info[] getSourceLineInfo() {
+        return sourceLineInfos.toArray(new Line.Info[0]);
+    }
+
+    @Override
+    public Line.Info[] getSourceLineInfo(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[0]);
+    }
+
+    @Override
+    public Line[] getSourceLines() {
+        return sourceLines.toArray(new Line[0]);
+
+    }
+
+    @Override
+    public Line.Info[] getTargetLineInfo() {
+        return targetLineInfos.toArray(new Line.Info[0]);
+    }
+
+    @Override
+    public Line.Info[] getTargetLineInfo(Line.Info info) {
+        ArrayList<Line.Info> infos = new ArrayList<Line.Info>();
+
+        for (Line.Info supportedInfo : targetLineInfos) {
+            if (info.matches(supportedInfo)) {
+                infos.add(supportedInfo);
+            }
+        }
+        return infos.toArray(new Line.Info[0]);
+    }
+
+    @Override
+    public Line[] getTargetLines() {
+
+        /* check for permission to record audio */
+        AudioPermission perm = new AudioPermission("record", null);
+        perm.checkGuard(null);
+
+        return (Line[]) targetLines.toArray(new Line[0]);
+    }
+
+    @Override
+    public boolean isLineSupported(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
+         */
+
+        if (!this.isOpen) {
+            throw new IllegalStateException("Mixer is not open; cant close");
+        }
+
+        List<Line> linesToClose = new LinkedList<Line>();
+        linesToClose.addAll(sourceLines);
+        if (sourceLines.size() > 0) {
+
+            Debug.println(DebugLevel.Warning, "PulseAudioMixer.close(): "
+                    + linesToClose.size()
+                    + " source lines were not closed. closing them now.");
+
+            linesToClose.addAll(sourceLines);
+            for (Line line : linesToClose) {
+                if (line.isOpen()) {
+                    line.close();
+                }
+            }
+        }
+        linesToClose.clear();
+
+        if (targetLines.size() > 0) {
+            Debug.println(DebugLevel.Warning, "PulseAudioMixer.close(): "
+                    + linesToClose.size()
+                    + " target lines have not been closed");
+
+            linesToClose.addAll(targetLines);
+            for (Line line : linesToClose) {
+                if (line.isOpen()) {
+                    line.close();
+                }
+            }
+        }
+
+        synchronized (lineListeners) {
+            lineListeners.clear();
+        }
+
+        eventLoopThread.interrupt();
+
+        try {
+            eventLoopThread.join();
+        } catch (InterruptedException e) {
+            System.out.println(this.getClass().getName()
+                    + ": interrupted while waiting for eventloop to finish");
+        }
+
+        isOpen = false;
+
+        refreshSourceAndTargetLines();
+
+        Debug.println(DebugLevel.Verbose, "PulseAudioMixer.close(): "
+                + "Mixer closed");
+
+    }
+
+    @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 openLocal() throws LineUnavailableException {
+        openLocal(DEFAULT_APP_NAME);
+    }
+
+    public void openLocal(String appName) throws LineUnavailableException {
+        openImpl(appName, null);
+    }
+
+    public void openRemote(String appName, String host)
+            throws UnknownHostException, LineUnavailableException {
+        if (host == null) {
+            throw new NullPointerException("hostname");
+        }
+
+        final int PULSEAUDIO_DEFAULT_PORT = 4713;
+
+        /*
+         * If trying to connect to a remote machine, check for permissions
+         */
+        SecurityManager sm = System.getSecurityManager();
+        if (sm != null) {
+            sm.checkConnect(host,PULSEAUDIO_DEFAULT_PORT );
+        }
+
+        openImpl(appName, host);
+    }
+
+    public void openRemote(String appName, String host, int port)
+            throws UnknownHostException, LineUnavailableException {
+
+        if ((port < 1) && (port != -1)) {
+            throw new IllegalArgumentException("Invalid value for port");
+        }
+
+        if (host == null) {
+            throw new NullPointerException("hostname");
+        }
+
+        /*
+         * If trying to connect to a remote machine, check for permissions
+         */
+        SecurityManager sm = System.getSecurityManager();
+        if (sm != null) {
+            sm.checkConnect(host, port);
+        }
+
+        InetAddress addr = InetAddress.getAllByName(host)[0];
+
+        host = addr.getHostAddress();
+        host = host + ":" + String.valueOf(port);
+
+        openImpl(appName, host);
+
+    }
+
+    /*
+     *
+     * @param appName name of the application
+     *
+     * @param hostAndIp a string consisting of the host and ip address of the
+     * server to connect to. Format: "<host>:<ip>". Set to null to indicate a
+     * local connection
+     */
+    synchronized private void openImpl(String appName, String hostAndIp)
+            throws LineUnavailableException {
+
+        if (isOpen) {
+            throw new IllegalStateException("Mixer is already open");
+        }
+
+        EventLoop eventLoop;
+        eventLoop = EventLoop.getEventLoop();
+        eventLoop.setAppName(appName);
+        eventLoop.setServer(hostAndIp);
+
+        ContextListener generalEventListener = new ContextListener() {
+            @Override
+            public void update(ContextEvent e) {
+                if (e.getType() == ContextEvent.READY) {
+                    fireEvent(new LineEvent(PulseAudioMixer.this,
+                            LineEvent.Type.OPEN, AudioSystem.NOT_SPECIFIED));
+                } else if (e.getType() == ContextEvent.FAILED
+                        || e.getType() == ContextEvent.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.READY
+                        || e.getType() == ContextEvent.FAILED
+                        || e.getType() == ContextEvent.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() != ContextEvent.READY) {
+                /*
+                 * 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));
+        }
+
+        Debug.println(DebugLevel.Debug, "PulseAudioMixer.open(): "
+                + "Mixer opened");
+
+    }
+
+    @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);
+            }
+        }
+    }
+
+    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/src/java/org/classpath/icedtea/pulseaudio/PulseAudioMixerInfo.java	Thu Jun 12 00:42:40 2014 +0100
@@ -0,0 +1,62 @@
+/* PulseAudioMixerInfo.java
+   Copyright (C) 2008 Red Hat, Inc.
+
+This file is part of IcedTea-Sound.
+
+IcedTea-Sound 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-Sound 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-Sound; 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 final 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-Sound",
+                    "the ear-candy mixer", "0.02");
+        }
+
+        return _instance;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java/org/classpath/icedtea/pulseaudio/PulseAudioMixerProvider.java	Thu Jun 12 00:42:40 2014 +0100
@@ -0,0 +1,63 @@
+/* PulseAudioMixerProvider.java
+   Copyright (C) 2008 Red Hat, Inc.
+
+This file is part of IcedTea-Sound.
+
+IcedTea-Sound 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-Sound 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-Sound; 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/src/java/org/classpath/icedtea/pulseaudio/PulseAudioPlaybackLine.java	Thu Jun 12 00:42:40 2014 +0100
@@ -0,0 +1,87 @@
+/* PulseAudioClip.java
+   Copyright (C) 2008 Red Hat, Inc.
+
+This file is part of IcedTea-Sound.
+
+IcedTea-Sound 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-Sound 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-Sound; 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;
+
+/**
+ * Represents a Line that supports changing the volume
+ */
+interface PulseAudioPlaybackLine {
+
+    static final int SECONDS_TO_MICROSECONDS = 1000000;
+
+    /**
+     * Set the volume of the Line (ie, sink input, source, or sink)
+     *
+     * @return an Operation object which can be used to check if the operation
+     *         has completed
+     */
+    byte[] native_set_volume(float value);
+
+    /**
+     *
+     * Update the volume information of a Line (sink input, source or sink)
+     *
+     * @return an Operation object which can be used to check if the operation
+     *         has been completed
+     */
+    byte[] native_update_volume();
+
+
+    /**
+     * Gets the cached volume. To get the current volume, call
+     * native_update_volume, and then call this method to get the updated
+     * volume.
+     *
+     * @return the cached volume of the Line
+     */
+    float getCachedVolume();
+
+    /**
+     * Set the cached value of a line
+     *
+     */
+    void setCachedVolume(float volume);
+
+    /**
+     * Check if a line is open
+     *
+     * @return <code>true</code> if line is open
+     */
+    boolean isOpen();
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java/org/classpath/icedtea/pulseaudio/PulseAudioPort.java	Thu Jun 12 00:42:40 2014 +0100
@@ -0,0 +1,161 @@
+/* PulseAudioClip.java
+   Copyright (C) 2008 Red Hat, Inc.
+
+This file is part of IcedTea-Sound.
+
+IcedTea-Sound 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-Sound 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-Sound; 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.LineEvent;
+import javax.sound.sampled.Port;
+
+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 cachedVolume;
+
+    private PulseAudioVolumeControl volumeControl;
+
+    static {
+        SecurityWrapper.loadNativeLibrary();
+    }
+
+    PulseAudioPort(String portName) {
+        this.name = portName;
+        this.eventLoop = EventLoop.getEventLoop();
+        this.contextPointer = eventLoop.getContextPointer();
+
+        updateVolumeInfo();
+
+        volumeControl = new PulseAudioVolumeControl(this, eventLoop);
+        controls.add(volumeControl);
+
+        /*
+         * unlike other lines, Ports must either be open or close
+         *
+         * close = no sound. open = sound
+         */
+        open();
+
+        // System.out.println("Opened Target Port " + name);
+    }
+
+    // FIXME why public
+    @Override
+    public abstract byte[] native_set_volume(float newValue);
+
+    /**
+     *
+     * @see {@link update_channels_and_volume}
+     */
+    // FIXME why public
+    public abstract byte[] native_update_volume();
+
+    @Override
+    public float getCachedVolume() {
+        return this.cachedVolume;
+    }
+
+    @Override
+    public void setCachedVolume(float value) {
+        this.cachedVolume = value;
+
+    }
+
+    private void updateVolumeInfo() {
+        Operation op;
+        synchronized (eventLoop.threadLock) {
+            op = new Operation(native_update_volume());
+        }
+
+        op.waitForCompletion();
+        op.releaseReference();
+    }
+
+    /**
+     * Callback used by JNI when native_update_volume completes
+     *
+     * @param channels
+     *            the number of channels
+     * @param cachedVolume
+     *            the new volume
+     */
+    @SuppressWarnings("unused")
+    void update_channels_and_volume(int channels, float volume) {
+        this.channels = channels;
+        this.cachedVolume = volume;
+    }
+
+    @Override
+    public void close() {
+
+        native_set_volume((float) 0);
+        isOpen = false;
+        fireLineEvent(new LineEvent(this, LineEvent.Type.CLOSE,
+                AudioSystem.NOT_SPECIFIED));
+    }
+
+    @Override
+    public abstract Line.Info getLineInfo();
+
+    @Override
+    public void open() {
+        if (isOpen) {
+            return;
+        }
+        native_set_volume(cachedVolume);
+        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/src/java/org/classpath/icedtea/pulseaudio/PulseAudioSourceDataLine.java	Thu Jun 12 00:42:40 2014 +0100
@@ -0,0 +1,347 @@
+/* PulseAudioSourceDataLine.java
+   Copyright (C) 2008 Red Hat, Inc.
+
+This file is part of IcedTea-Sound.
+
+IcedTea-Sound 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-Sound 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-Sound; 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.Line;
+import javax.sound.sampled.LineListener;
+import javax.sound.sampled.LineUnavailableException;
+import javax.sound.sampled.SourceDataLine;
+
+import org.classpath.icedtea.pulseaudio.Debug.DebugLevel;
+
+public final class PulseAudioSourceDataLine extends PulseAudioDataLine
+        implements SourceDataLine, PulseAudioPlaybackLine {
+
+    private PulseAudioVolumeControl volumeControl;
+
+    public static final String DEFAULT_SOURCEDATALINE_NAME = "Audio Stream";
+
+    /*
+     * Package-private constructor only called by PulseAudioMixer
+     */
+    PulseAudioSourceDataLine(AudioFormat[] formats, AudioFormat defaultFormat) {
+
+        this.supportedFormats = formats;
+        this.lineListeners = new ArrayList<LineListener>();
+        this.defaultFormat = defaultFormat;
+        this.currentFormat = defaultFormat;
+        this.streamName = DEFAULT_SOURCEDATALINE_NAME;
+
+    }
+
+    @Override
+    synchronized public void open(AudioFormat format, int bufferSize)
+            throws LineUnavailableException {
+
+        super.open(format, bufferSize);
+
+        volumeControl = new PulseAudioVolumeControl(this, eventLoop);
+        controls.add(volumeControl);
+
+        PulseAudioMixer parentMixer = PulseAudioMixer.getInstance();
+        parentMixer.addSourceLine(this);
+
+        Debug.println(DebugLevel.Verbose, "PulseAudioSourceDataLine.open(): "
+                + "line opened");
+
+    }
+
+    @Override
+    public void open(AudioFormat format) throws LineUnavailableException {
+        open(format, DEFAULT_BUFFER_SIZE);
+    }
+
+    // FIXME
+    public byte[] native_set_volume(float value) {
+        synchronized (eventLoop.threadLock) {
+            return stream.native_set_volume(value);
+        }
+    }
+
+    public byte[] native_update_volume() {
+        synchronized (eventLoop.threadLock) {
+            return stream.native_update_volume();
+        }
+    }
+
+    @Override
+    public float getCachedVolume() {
+        return stream.getCachedVolume();
+    }
+
+    @Override
+    synchronized public void setCachedVolume(float value) {
+        stream.setCachedVolume(value);
+    }
+
+    @Override
+    protected void connectLine(int bufferSize, Stream masterStream)
+            throws LineUnavailableException {
+        StreamBufferAttributes bufferAttributes =
+            new StreamBufferAttributes(
+                    bufferSize,
+                    bufferSize / 4,
+                    bufferSize / 8,
+                    Math.max(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()) {
+            // A closed line can write exactly 0 bytes.
+            return 0;
+        }
+
+        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 < 0 || offset < 0 || offset > data.length - length) {
+            throw new ArrayIndexOutOfBoundsException(
+                    "Overflow condition: buffer.length=" + data.length +
+                            " offset= " + offset + " length=" + length );
+        }
+
+        int position = offset;
+        int remainingLength = length;
+        int availableSize = 0;
+
+        int sizeWritten = 0;
+
+        boolean interrupted = false;
+
+        while (remainingLength != 0) {
+
+            synchronized (eventLoop.threadLock) {
+
+                do {
+                    synchronized (this) {
+                        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;
+    }
+
+    @Override
+    public int available() {
+        synchronized (eventLoop.threadLock) {
+            return stream.getWritableSize();
+        }
+    };
+
+    @Override
+    public int getFramePosition() {
+        return (int) framesSinceOpen;
+    }
+
+    @Override
+    public long getLongFramePosition() {
+        return framesSinceOpen;
+    }
+
+    @Override
+    public long getMicrosecondPosition() {
+
+        float frameRate = currentFormat.getFrameRate();
+        float time = framesSinceOpen / frameRate; // seconds
+        long microseconds = (long) (time * SECONDS_TO_MICROSECONDS);
+        return microseconds;
+    }
+
+    @Override
+    public void drain() {
+
+        synchronized (this) {
+            writeInterrupted = true;
+        }
+
+        do {
+            synchronized (this) {
+                if (!isOpen()) {
+                    return;
+                }
+                if (getBytesInBuffer() == 0) {
+                    return;
+                }
+                if (isStarted) {
+                    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() {
+        synchronized (this) {
+            writeInterrupted = true;
+        }
+
+        if (isOpen()) {
+            Operation operation;
+            synchronized (eventLoop.threadLock) {
+                operation = stream.flush();
+            }
+
+            operation.waitForCompletion();
+            operation.releaseReference();
+        }
+
+    }
+
+    @Override
+    synchronized public void close() {
+
+        if (!isOpen()) {
+            return;
+        }
+
+        writeInterrupted = true;
+
+        PulseAudioMixer parent = PulseAudioMixer.getInstance();
+        parent.removeSourceLine(this);
+
+        super.close();
+
+        Debug.println(DebugLevel.Verbose, "PulseAudioSourceDataLine.close():"
+                + " line closed");
+
+    }
+
+    @Override
+    public 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/src/java/org/classpath/icedtea/pulseaudio/PulseAudioSourcePort.java	Thu Jun 12 00:42:40 2014 +0100
@@ -0,0 +1,95 @@
+/* PulseAudioClip.java
+   Copyright (C) 2008 Red Hat, Inc.
+
+This file is part of IcedTea-Sound.
+
+IcedTea-Sound 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-Sound 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-Sound; 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.Line;
+import javax.sound.sampled.Port;
+
+final class PulseAudioSourcePort extends PulseAudioPort {
+
+    /* aka mic */
+
+    static {
+        SecurityWrapper.loadNativeLibrary();
+    }
+
+    PulseAudioSourcePort(String name) {
+        super(name);
+    }
+
+    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();
+    }
+
+    // FIXME
+    public native byte[] native_set_volume(float newValue);
+
+    // FIXME
+    public native byte[] native_update_volume();
+
+    @Override
+    public Line.Info getLineInfo() {
+        return new Port.Info(Port.class, getName(), false);
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java/org/classpath/icedtea/pulseaudio/PulseAudioTargetDataLine.java	Thu Jun 12 00:42:40 2014 +0100
@@ -0,0 +1,421 @@
+/* PulseAudioTargetDataLine.java
+   Copyright (C) 2008 Red Hat, Inc.
+
+This file is part of IcedTea-Sound.
+
+IcedTea-Sound 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-Sound 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-Sound; 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.AudioPermission;
+import javax.sound.sampled.DataLine;
+import javax.sound.sampled.Line;
+import javax.sound.sampled.LineEvent;
+import javax.sound.sampled.LineUnavailableException;
+import javax.sound.sampled.TargetDataLine;
+
+import org.classpath.icedtea.pulseaudio.Debug.DebugLevel;
+
+public final 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
+     */
+    private byte[] fragmentBuffer;
+
+    /*
+     * these are set to true only by the respective functions (flush(), drain())
+     * set to false only by read()
+     */
+    private boolean flushed = false;
+    private boolean drained = false;
+
+    public static final String DEFAULT_TARGETDATALINE_NAME = "Audio Stream";
+
+    PulseAudioTargetDataLine(AudioFormat[] formats, AudioFormat defaultFormat) {
+        this.supportedFormats = formats;
+        this.defaultFormat = defaultFormat;
+        this.currentFormat = defaultFormat;
+        this.streamName = DEFAULT_TARGETDATALINE_NAME;
+
+    }
+
+    @Override
+    synchronized public void close() {
+        if (!isOpen()) {
+            // Probably due to some programmer error, we are being
+            // asked to close an already closed line.  Oh well.
+            Debug.println(DebugLevel.Verbose,
+                    "PulseAudioTargetDataLine.close(): "
+                    + "Line closed that wasn't open.");
+            return;
+        }
+
+        /* check for permission to record audio */
+        AudioPermission perm = new AudioPermission("record", null);
+        perm.checkGuard(null);
+
+        PulseAudioMixer parentMixer = PulseAudioMixer.getInstance();
+        parentMixer.removeTargetLine(this);
+
+        super.close();
+
+        Debug.println(DebugLevel.Verbose, "PulseAudioTargetDataLine.close(): "
+                + "Line closed");
+    }
+
+    @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);
+
+        Debug.println(DebugLevel.Verbose, "PulseAudioTargetDataLine.open(): "
+                + "Line opened");
+    }
+
+    @Override
+    synchronized public void open(AudioFormat format)
+            throws LineUnavailableException {
+        open(format, DEFAULT_BUFFER_SIZE);
+    }
+
+    @Override
+    protected void connectLine(int bufferSize, Stream masterStream)
+            throws LineUnavailableException {
+        int fs = currentFormat.getFrameSize();
+        float fr = currentFormat.getFrameRate();
+        int bps = (int)(fs*fr); // bytes per second.
+
+        // if 2 seconds' worth of data can fit in the buffer of the specified
+        // size, we don't have to adjust the latency. Otherwise we do, so as
+        // to avoid overruns.
+        long flags = Stream.FLAG_START_CORKED;
+        StreamBufferAttributes bufferAttributes;
+        if (bps*2 < bufferSize) {
+            // pulse audio completely ignores our fragmentSize attribute unless
+            // ADJUST_LATENCY is set, so we just leave it at -1.
+            bufferAttributes = new StreamBufferAttributes(bufferSize, -1, -1, -1, -1);
+        } else {
+            flags |= Stream.FLAG_ADJUST_LATENCY;
+            // in this case, the pulse audio docs:
+            // http://www.pulseaudio.org/wiki/LatencyControl
+            // say every field (including bufferSize) must be initialized
+            // to -1 except fragmentSize.
+            // XXX: but in my tests, it just sets it to about 4MB, which
+            // effectively makes it impossible to allocate a small buffer
+            // and nothing bad happens (yet) when you don't set it to -1
+            // so we just leave it at bufferSize.
+            // XXX: the java api has no way to specify latency, which probably
+            // means it should be as low as possible. Right now this method's
+            // primary concern is avoiding dropouts, and if the user-provided
+            // buffer size is large enough, we leave the latency up to pulse
+            // audio (which sets it to something extremely high - about 2
+            // seconds). We might want to always set a low latency.
+            int fragmentSize = bufferSize/2;
+            fragmentSize = Math.max((fragmentSize/fs)*fs, fs);
+            bufferAttributes = new StreamBufferAttributes(bufferSize, -1, -1, -1, fragmentSize);
+        }
+
+        synchronized (eventLoop.threadLock) {
+            stream.connectForRecording(Stream.DEFAULT_DEVICE, flags, bufferAttributes);
+        }
+    }
+
+    @Override
+    public int read(byte[] data, int offset, int length) {
+
+        /* check state and inputs */
+
+        if (!isOpen()) {
+            // A closed line can produce zero bytes of data.
+            return 0;
+        }
+
+        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 ( offset < 0 || offset > data.length - length) {
+            throw new ArrayIndexOutOfBoundsException("array size: " + data.length
+                    + " offset:" + offset + " length:" + 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 */
+        synchronized (this) {
+            if (fragmentBuffer != null) {
+                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) {
+                        Debug.println(DebugLevel.Verbose,
+                                "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() {
+
+        // 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 {
+                //TODO: Is this the best length of sleep?
+                //Maybe in case this loop runs for a long time
+                //it would be good to switch to a longer
+                //sleep.  Like bump it up each iteration after
+                //the Nth iteration, up to a MAXSLEEP length.
+                Thread.sleep(100);
+            } catch (InterruptedException e) {
+                // do nothing
+            }
+        }
+
+        synchronized (this) {
+            drained = true;
+        }
+    }
+
+    @Override
+    public synchronized void flush() {
+        if (isOpen()) {
+
+            /* flush the buffer on pulseaudio's side */
+            Operation operation;
+            synchronized (eventLoop.threadLock) {
+                operation = stream.flush();
+            }
+            operation.waitForCompletion();
+            operation.releaseReference();
+        }
+
+        flushed = true;
+        /* flush the partial fragment we stored */
+        fragmentBuffer = null;
+    }
+
+    @Override
+    public int available() {
+        if (!isOpen()) {
+            // a closed line has 0 bytes available.
+            return 0;
+        }
+
+        synchronized (eventLoop.threadLock) {
+            return stream.getReableSize();
+        }
+    }
+
+    @Override
+    public int getFramePosition() {
+        return (int) framesSinceOpen;
+    }
+
+    @Override
+    public long getLongFramePosition() {
+        return framesSinceOpen;
+    }
+
+    @Override
+    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));
+    }
+
+    @Override
+    public 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/src/java/org/classpath/icedtea/pulseaudio/PulseAudioTargetPort.java	Thu Jun 12 00:42:40 2014 +0100
@@ -0,0 +1,88 @@
+/* PulseAudioClip.java
+   Copyright (C) 2008 Red Hat, Inc.
+
+This file is part of IcedTea-Sound.
+
+IcedTea-Sound 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-Sound 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-Sound; 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.Line;
+import javax.sound.sampled.Port;
+
+final class PulseAudioTargetPort extends PulseAudioPort {
+
+    /* aka speaker */
+
+    static {
+        SecurityWrapper.loadNativeLibrary();
+    }
+
+    PulseAudioTargetPort(String name) {
+        super(name);
+    }
+
+    @Override
+    public void open() {
+
+        super.open();
+
+        PulseAudioMixer parent = PulseAudioMixer.getInstance();
+        parent.addTargetLine(this);
+    }
+
+    @Override
+    public void close() {
+
+        if (!isOpen) {
+            throw new IllegalStateException("not open, so cant close Port");
+        }
+
+        PulseAudioMixer parent = PulseAudioMixer.getInstance();
+        parent.removeTargetLine(this);
+
+        super.close();
+    }
+
+    // FIXME
+    public native byte[] native_set_volume(float newValue);
+
+    // FIXME
+    public native byte[] native_update_volume();
+
+    @Override
+    public Line.Info getLineInfo() {
+        return new Port.Info(Port.class, getName(), false);
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java/org/classpath/icedtea/pulseaudio/PulseAudioVolumeControl.java	Thu Jun 12 00:42:40 2014 +0100
@@ -0,0 +1,101 @@
+/* PulseAudioVolumeControl.java
+   Copyright (C) 2008 Red Hat, Inc.
+
+This file is part of IcedTea-Sound.
+
+IcedTea-Sound 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-Sound 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-Sound; 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;
+
+final class PulseAudioVolumeControl extends FloatControl {
+
+    static final int MAX_VOLUME = 65536;
+    static final int MIN_VOLUME = 0;
+
+    protected PulseAudioVolumeControl(PulseAudioPlaybackLine line,
+            EventLoop eventLoop) {
+
+        /*
+         * the initial volume is ignored by pulseaudio.
+         */
+        super(FloatControl.Type.VOLUME, MIN_VOLUME, MAX_VOLUME, 1, -1, line
+                .getCachedVolume(), "pulseaudio units", "Volume Off",
+                "Default Volume", "Full Volume");
+        this.line = line;
+        this.eventLoop = eventLoop;
+    }
+
+    private EventLoop eventLoop;
+    private PulseAudioPlaybackLine line;
+
+    @Override
+    public synchronized void setValue(float newValue) {
+        if (newValue > MAX_VOLUME || newValue < MIN_VOLUME) {
+            throw new IllegalArgumentException("invalid value");
+        }
+
+        if (!line.isOpen()) {
+            return;
+        }
+
+        setStreamVolume(newValue);
+
+        line.setCachedVolume(newValue);
+    }
+
+    protected synchronized void setStreamVolume(float newValue) {
+        Operation op;
+        synchronized (eventLoop.threadLock) {
+            op = new Operation(line.native_set_volume(newValue));
+        }
+
+        op.waitForCompletion();
+        op.releaseReference();
+
+    }
+
+    public synchronized float getValue() {
+        Operation op;
+        synchronized (eventLoop.threadLock) {
+            op = new Operation(line.native_update_volume());
+        }
+
+        op.waitForCompletion();
+        op.releaseReference();
+
+        return line.getCachedVolume();
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java/org/classpath/icedtea/pulseaudio/SecurityWrapper.java	Thu Jun 12 00:42:40 2014 +0100
@@ -0,0 +1,31 @@
+package org.classpath.icedtea.pulseaudio;
+
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+
+/**
+ * A wrapper around the security sensitive functions
+ *
+ */
+final class SecurityWrapper {
+
+    static void loadNativeLibrary() {
+
+        if (System.getSecurityManager() != null) {
+            PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
+                @Override
+                public Boolean run() {
+                    System.loadLibrary("pulse-java");
+                    return true;
+                }
+
+            };
+
+            AccessController.doPrivileged(action);
+
+        } else {
+            System.loadLibrary("pulse-java");
+        }
+
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java/org/classpath/icedtea/pulseaudio/Stream.java	Thu Jun 12 00:42:40 2014 +0100
@@ -0,0 +1,869 @@
+/* PulseAudioClip.java
+   Copyright (C) 2008 Red Hat, Inc.
+
+This file is part of IcedTea-Sound.
+
+IcedTea-Sound 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-Sound 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-Sound; 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.Arrays;
+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
+ *
+ *
+ * for more details on this see the pa_stream_* functions in the pulseaudio api
+ * docs
+ *
+ */
+final 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();
+    }
+
+    // see comments in ContextEvent.java and Operation.java
+    // These are the possible stream states.
+    public static long STATE_UNCONNECTED = -1,
+                       STATE_CREATING    = -1,
+                       STATE_READY       = -1,
+                       STATE_FAILED      = -1,
+                       STATE_TERMINATED  = -1;
+
+    // Throw an IllegalStateException if value is not one of the possible
+    // states. Otherwise return the input.
+    public static long checkNativeStreamState(long value) {
+        if (!Arrays.asList(STATE_UNCONNECTED, STATE_CREATING,
+                STATE_READY, STATE_FAILED, STATE_TERMINATED
+            ).contains(value)) {
+            throw new IllegalStateException("Illegal constant for ContextEvent: " + value);
+        }
+        return value;
+    }
+
+    // These are stream flags.
+    public static long FLAG_NOFLAGS                   = -1,
+                       FLAG_START_CORKED              = -1,
+                       FLAG_INTERPOLATE_TIMING        = -1,
+                       FLAG_NOT_MONOTONIC             = -1,
+                       FLAG_AUTO_TIMING_UPDATE        = -1,
+                       FLAG_NO_REMAP_CHANNELS         = -1,
+                       FLAG_NO_REMIX_CHANNELS         = -1,
+                       FLAG_FIX_FORMAT                = -1,
+                       FLAG_FIX_RATE                  = -1,
+                       FLAG_FIX_CHANNELS              = -1,
+                       FLAG_DONT_MOVE                 = -1,
+                       FLAG_VARIABLE_RATE             = -1,
+                       FLAG_PEAK_DETECT               = -1,
+                       FLAG_START_MUTED               = -1,
+                       FLAG_ADJUST_LATENCY            = -1,
+                       FLAG_EARLY_REQUESTS            = -1,
+                       FLAG_DONT_INHIBIT_AUTO_SUSPEND = -1,
+                       FLAG_START_UNMUTED             = -1,
+                       FLAG_FAIL_ON_SUSPEND           = -1;
+
+    private static native void init_constants();
+
+    // We don't change this to static longs like we did with all other pulse
+    // audio enums mirrored in java because we never use the pulse audio
+    // integer value of formats on the java side. In java, the handling of
+    // formats is strictly symbolic and string based. For that, enums are
+    // better.
+    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;
+
+    // stores a pointer to pa_stream
+    private byte[] streamPointer;
+    // stores a pointer to the java_context/this object for callbacks
+    private byte[] contextPointer;
+
+    static {
+        SecurityWrapper.loadNativeLibrary();
+        init_constants();
+    }
+
+    private Format format;
+    private float cachedVolume;
+
+    private StreamBufferAttributes bufAttr = new StreamBufferAttributes(0,0,0,0,0);
+    private static final Object bufAttrMutex = new Object();
+
+    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 long 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, long 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, long 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_streamp,
+     * pa_stream_success_cb_t cb, voiduserdata) 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_streams, pa_stream_success_cb_t cb,
+     * voiduserdata) 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_streams) Return the
+     * latest raw timing data structure.
+     */
+
+    private native StreamSampleSpecification native_pa_stream_get_sample_spec();
+
+    /*
+     * const pa_channel_map pa_stream_get_channel_map (pa_streams) Return a
+     * pointer to the stream's channel map. const
+     */
+    private native StreamBufferAttributes native_pa_stream_get_buffer_attr();
+
+    private native byte[] native_pa_stream_set_buffer_attr(
+            StreamBufferAttributes info);
+
+    private native byte[] native_pa_stream_update_sample_rate(int rate);
+
+    native byte[] native_set_volume(float newValue);
+
+    native byte[] native_update_volume();
+
+    /*
+     * pa_operation pa_stream_proplist_update (pa_streams, pa_update_mode_t
+     * mode, pa_proplistp, pa_stream_success_cb_t cb, voiduserdata) Update the
+     * property list of the sink input/source output of this stream, adding new
+     * entries. pa_operation pa_stream_proplist_remove (pa_streams, const char
+     * const keys[], pa_stream_success_cb_t cb, voiduserdata) Update the
+     * property list of the sink input/source output of this stream, remove
+     * entries. int pa_stream_set_monitor_stream (pa_streams, 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_streams) Return what has been set with
+     * pa_stream_set_monitor_stream() ebfore.
+     */
+
+    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());
+    }
+
+    void addStateListener(StateListener listener) {
+        synchronized (stateListeners) {
+            stateListeners.add(listener);
+        }
+    }
+
+    void removeStateListener(StateListener listener) {
+        synchronized (stateListeners) {
+            stateListeners.remove(listener);
+        }
+
+    }
+
+    void addWriteListener(WriteListener listener) {
+        synchronized (writeListeners) {
+            writeListeners.add(listener);
+        }
+    }
+
+    void removeWriteListener(WriteListener listener) {
+        synchronized (writeListeners) {
+            writeListeners.remove(listener);
+        }
+    }
+
+    void addReadListener(ReadListener listener) {
+        synchronized (readListeners) {
+            readListeners.add(listener);
+        }
+    }
+
+    void removeReadListener(ReadListener listener) {
+        synchronized (readListeners) {
+            readListeners.remove(listener);
+        }
+    }
+
+    void addOverflowListener(OverflowListener listener) {
+        synchronized (overflowListeners) {
+            overflowListeners.add(listener);
+        }
+    }
+
+    void removeOverflowListener(OverflowListener listener) {
+        synchronized (overflowListeners) {
+            overflowListeners.remove(listener);
+        }
+    }
+
+    void addUnderflowListener(UnderflowListener listener) {
+        synchronized (underflowListeners) {
+            underflowListeners.add(listener);
+        }
+    }
+
+    void removeUnderflowListener(UnderflowListener listener) {
+        synchronized (underflowListeners) {
+            underflowListeners.remove(listener);
+        }
+    }
+
+    void addCorkListener(CorkListener listener) {
+        synchronized (corkListeners) {
+            corkListeners.add(listener);
+        }
+    }
+
+    void removeCorkListener(CorkListener listener) {
+        synchronized (corkListeners) {
+            corkListeners.remove(listener);
+        }
+    }
+
+    void addPlaybackStartedListener(PlaybackStartedListener listener) {
+        synchronized (playbackStartedListeners) {
+            playbackStartedListeners.add(listener);
+        }
+    }
+
+    void removePlaybackStartedListener(PlaybackStartedListener listener) {
+        synchronized (playbackStartedListeners) {
+            playbackStartedListeners.remove(listener);
+        }
+    }
+
+    void addLatencyUpdateListener(LatencyUpdateListener listener) {
+        synchronized (latencyUpdateListeners) {
+            latencyUpdateListeners.add(listener);
+        }
+    }
+
+    void removeLatencyUpdateListener(LatencyUpdateListener listener) {
+        synchronized (playbackStartedListeners) {
+            latencyUpdateListeners.remove(listener);
+        }
+    }
+
+    void addMovedListener(MovedListener listener) {
+        synchronized (movedListeners) {
+            movedListeners.add(listener);
+        }
+    }
+
+    void removeMovedListener(MovedListener listener) {
+        synchronized (movedListeners) {
+            movedListeners.remove(listener);
+        }
+    }
+
+    void addSuspendedListener(SuspendedListener listener) {
+        synchronized (suspendedListeners) {
+            suspendedListeners.add(listener);
+        }
+    }
+
+    void removeSuspendedListener(SuspendedListener listener) {
+        synchronized (suspendedListeners) {
+            suspendedListeners.remove(listener);
+        }
+    }
+
+    long getState() {
+        return checkNativeStreamState(native_pa_stream_get_state());
+    }
+
+    byte[] getContextPointer() {
+        return native_pa_stream_get_context();
+    }
+
+    int getSinkInputIndex() {
+        return native_pa_stream_get_index();
+    }
+
+    /**
+     *
+     * @return the index of the sink or source this stream is connected to in
+     *         the server
+     */
+    int getDeviceIndex() {
+        return native_pa_stream_get_device_index();
+    }
+
+    private void setBufAttr() {
+        synchronized(bufAttrMutex) {
+            bufAttr = native_pa_stream_get_buffer_attr();
+        }
+    }
+
+    @SuppressWarnings("unused")
+    private void bufferAttrCallback() {
+        setBufAttr();
+    }
+
+    int getBufferSize() {
+        synchronized (bufAttrMutex) {
+            return bufAttr.getMaxLength();
+        }
+    }
+
+    /**
+     *
+     * @return the name of the sink or source this stream is connected to in the
+     *         server
+     */
+    String getDeviceName() {
+        return native_pa_stream_get_device_name();
+    }
+
+    /**
+     * if the sink or source this stream is connected to has been suspended.
+     *
+     * @return
+     */
+    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
+     */
+    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(),
+                              FLAG_START_CORKED, null, syncStreamPointer
+                          );
+        if (returnValue < 0) {
+            throw new LineUnavailableException(
+                    "Unable To connect a line for playback");
+        }
+    }
+
+    /**
+     * Connect the stream to a source.
+     *
+     * @throws LineUnavailableException
+     *
+     */
+    void connectForRecording(String deviceName, long flags,
+            StreamBufferAttributes bufferAttributes)
+            throws LineUnavailableException {
+
+        int returnValue = native_pa_stream_connect_record(
+                              deviceName,
+                              bufferAttributes.getMaxLength(),
+                              bufferAttributes.getTargetLength(),
+                              bufferAttributes.getPreBuffering(),
+                              bufferAttributes.getMinimumRequest(),
+                              bufferAttributes.getFragmentSize(),
+                              flags, null, null
+                          );
+        if (returnValue < 0) {
+            throw new LineUnavailableException(
+                    "Unable to connect line for recording");
+        }
+    }
+
+    /**
+     * Disconnect a stream from a source/sink.
+     */
+    void disconnect() {
+        int returnValue = native_pa_stream_disconnect();
+        assert (returnValue == 0);
+    }
+
+    /**
+     * Write data to the server
+     *
+     * @param data
+     * @param length
+     * @return
+     */
+    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
+     */
+    byte[] peek() {
+        return native_pa_stream_peek();
+    }
+
+    /**
+     *
+     * Remove the current fragment on record streams.
+     */
+    void drop() {
+        native_pa_stream_drop();
+    }
+
+    /**
+     * Return the number of bytes that may be written using write().
+     *
+     * @return
+     */
+    int getWritableSize() {
+        return native_pa_stream_writable_size();
+    }
+
+    /**
+     * Return the number of bytes that may be read using peek().
+     *
+     * @return
+     */
+    int getReableSize() {
+        return native_pa_stream_readable_size();
+    }
+
+    /**
+     * Drain a playback stream
+     *
+     * @return
+     */
+    Operation drain() {
+        Operation drainOperation = new Operation(native_pa_stream_drain());
+        return drainOperation;
+    }
+
+    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(EventLoop.getEventLoop().threadLock) {
+            if (getState() == Stream.STATE_READY) {
+                setBufAttr();
+            }
+        }
+        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();
+            }
+        }
+    }
+
+    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
+     */
+    Operation cork(boolean cork) {
+        int yes = cork ? 1 : 0;
+        Operation corkOperation = new Operation(native_pa_stream_cork(yes));
+        return corkOperation;
+    }
+
+    Operation cork() {
+        return cork(true);
+    }
+
+    Operation unCork() {
+        return cork(false);
+    }
+
+    /**
+     * Flush the playback buffer of this stream.
+     *
+     * @return
+     */
+    Operation flush() {
+        Operation flushOperation = new Operation(native_pa_stream_flush());
+        return flushOperation;
+    }
+
+    /*
+     * Operation pa_stream_prebuf (pa_streams, 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.
+     */
+    Operation triggerStart() {
+        Operation triggerOperation = new Operation(native_pa_stream_trigger());
+        return triggerOperation;
+    }
+
+    /**
+     * set the stream's name
+     *
+     * @param name
+     * @return
+     */
+    Operation setName(String name) {
+        Operation setNameOperation = new Operation(
+                native_pa_stream_set_name(name));
+        return setNameOperation;
+    }
+
+    /**
+     *
+     *
+     * @return a time between ? and ?
+     */
+    long getTime() {
+        return native_pa_stream_get_time();
+    }
+
+    /**
+     * @return the total stream latency in microseconds
+     */
+    long getLatency() {
+        return native_pa_stream_get_latency();
+    }
+
+    /*
+     * const pa_timing_info pa_stream_get_timing_info (pa_streams) Return the
+     * latest raw timing data structure.
+     */
+
+    Format getFormat() {
+        return format;
+    }
+
+    /*
+     * const pa_channel_map pa_stream_get_channel_map (pa_streams) Return a
+     * pointer to the stream's channel map.
+     */
+
+    StreamBufferAttributes getBufferAttributes() {
+        return native_pa_stream_get_buffer_attr();
+    }
+
+    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));
+    }
+
+    byte[] getStreamPointer() {
+        return streamPointer;
+    }
+
+    void free() {
+        native_pa_stream_unref();
+    }
+
+    float getCachedVolume() {
+        return this.cachedVolume;
+    }
+
+    void setCachedVolume(float volume) {
+        this.cachedVolume = volume;
+    }
+
+    void update_channels_and_volume(int channels, float volume) {
+        this.cachedVolume = volume;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java/org/classpath/icedtea/pulseaudio/StreamBufferAttributes.java	Thu Jun 12 00:42:40 2014 +0100
@@ -0,0 +1,84 @@
+/* PulseAudioClip.java
+   Copyright (C) 2008 Red Hat, Inc.
+
+This file is part of IcedTea-Sound.
+
+IcedTea-Sound 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-Sound 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-Sound; 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;
+
+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;
+
+    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;
+    }
+
+    int getMaxLength() {
+        return maxLength;
+    }
+
+    int getTargetLength() {
+        return targetLength;
+    }
+
+    int getPreBuffering() {
+        return preBuffering;
+    }
+
+    int getMinimumRequest() {
+        return minimumRequest;
+    }
+
+    int getFragmentSize() {
+        return fragmentSize;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java/org/classpath/icedtea/pulseaudio/StreamSampleSpecification.java	Thu Jun 12 00:42:40 2014 +0100
@@ -0,0 +1,70 @@
+/* PulseAudioClip.java
+   Copyright (C) 2008 Red Hat, Inc.
+
+This file is part of IcedTea-Sound.
+
+IcedTea-Sound 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-Sound 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-Sound; 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;
+
+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;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/native/jni-common.c	Thu Jun 12 00:42:40 2014 +0100
@@ -0,0 +1,236 @@
+/* jni-common.c
+ Copyright (C) 2008 Red Hat, Inc.
+
+ This file is part of IcedTea-Sound.
+
+ IcedTea-Sound 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-Sound 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-Sound; 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/src/native/jni-common.h	Thu Jun 12 00:42:40 2014 +0100
@@ -0,0 +1,100 @@
+/* jni-common.h
+ Copyright (C) 2008 Red Hat, Inc.
+
+ This file is part of IcedTea-Sound.
+
+ IcedTea-Sound 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-Sound 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-Sound; 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
+ *
+ */
+
+// Sets the field with name field_name from jclass clz to pa_prefix_field_name.
+#define SET_JAVA_STATIC_LONG_FIELD_TO_PA_ENUM(env, clz, java_prefix, pa_prefix, name) \
+    do { \
+        char *java_full_name = #java_prefix #name; \
+        jfieldID fid = (*env)->GetStaticFieldID(env, clz, java_full_name, "J"); \
+        assert(fid); \
+        (*env)->SetStaticLongField(env, clz, fid, PA_##pa_prefix##_##name); \
+    } while(0);
+
+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/src/native/org_classpath_icedtea_pulseaudio_ContextEvent.c	Thu Jun 12 00:42:40 2014 +0100
@@ -0,0 +1,63 @@
+/* org_classpath_icedtea_pulseaudio_EventLoop.c
+ Copyright (C) 2011 Red Hat, Inc.
+
+ This file is part of IcedTea-Sound.
+
+ IcedTea-Sound 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-Sound 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-Sound; 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_ContextEvent.h"
+#include "jni-common.h"
+
+// we don't prefix the java names with anything, so we leave the third argument
+// empty
+#define SET_CONTEXT_ENUM(env, clz, name) \
+    SET_JAVA_STATIC_LONG_FIELD_TO_PA_ENUM(env, clz, , CONTEXT, name)
+
+/*
+ * Class:     org_classpath_icedtea_pulseaudio_ContextEvent
+ * Method:    init_constants
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL Java_org_classpath_icedtea_pulseaudio_ContextEvent_init_1constants
+  (JNIEnv *env, jclass clz) {
+    SET_CONTEXT_ENUM(env, clz, UNCONNECTED);
+    SET_CONTEXT_ENUM(env, clz, CONNECTING);
+    SET_CONTEXT_ENUM(env, clz, AUTHORIZING);
+    SET_CONTEXT_ENUM(env, clz, SETTING_NAME);
+    SET_CONTEXT_ENUM(env, clz, READY);
+    SET_CONTEXT_ENUM(env, clz, FAILED);
+    SET_CONTEXT_ENUM(env, clz, TERMINATED);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/native/org_classpath_icedtea_pulseaudio_EventLoop.c	Thu Jun 12 00:42:40 2014 +0100
@@ -0,0 +1,297 @@
+/* org_classpath_icedtea_pulseaudio_EventLoop.c
+ Copyright (C) 2008 Red Hat, Inc.
+
+ This file is part of IcedTea-Sound.
+
+ IcedTea-Sound 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-Sound 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-Sound; 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", "(J)V");
+    assert(mid);
+    (*env)->CallVoidMethod(env, obj, mid, (jlong) 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");
+
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/native/org_classpath_icedtea_pulseaudio_Operation.c	Thu Jun 12 00:42:40 2014 +0100
@@ -0,0 +1,100 @@
+/* org_classpath_icedtea_pulseaudio_Operation.c
+ Copyright (C) 2008 Red Hat, Inc.
+
+ This file is part of IcedTea-Sound.
+
+ IcedTea-Sound 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-Sound 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-Sound; 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>
+
+// we don't prefix the java names with anything, so we leave the third argument
+// empty
+#define SET_OP_ENUM(env, clz, name) \
+    SET_JAVA_STATIC_LONG_FIELD_TO_PA_ENUM(env, clz, , OPERATION, name)
+
+/*
+ * Class:     org_classpath_icedtea_pulseaudio_Operation
+ * Method:    init_constants
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL Java_org_classpath_icedtea_pulseaudio_Operation_init_1constants
+  (JNIEnv *env, jclass clz) {
+    SET_OP_ENUM(env, clz, RUNNING);
+    SET_OP_ENUM(env, clz, DONE);
+    SET_OP_ENUM(env, clz, CANCELLED);
+}
+
+
+/*
+ * 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 jlong 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);
+    jlong state = pa_operation_get_state(operation);
+    return state;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/native/org_classpath_icedtea_pulseaudio_PulseAudioSourcePort.c	Thu Jun 12 00:42:40 2014 +0100
@@ -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_update_volume
+ * Signature: ()[B
+ */
+JNIEXPORT jbyteArray JNICALL Java_org_classpath_icedtea_pulseaudio_PulseAudioSourcePort_native_1update_1volume
+(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_set_volume
+ * Signature: (F)[B
+ */
+JNIEXPORT jbyteArray JNICALL Java_org_classpath_icedtea_pulseaudio_PulseAudioSourcePort_native_1set_1volume
+(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/src/native/org_classpath_icedtea_pulseaudio_PulseAudioTargetPort.c	Thu Jun 12 00:42:40 2014 +0100
@@ -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;
+
+static void sink_callback(pa_context *context, int success, void *userdata) {
+    notifyWaitingOperations(pulse_thread_env);
+}
+
+static 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_update_volume
+ * Signature: ()[B
+ */
+JNIEXPORT jbyteArray JNICALL Java_org_classpath_icedtea_pulseaudio_PulseAudioTargetPort_native_1update_1volume
+(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_set_volume
+ * Signature: (F)[B
+ */
+JNIEXPORT jbyteArray JNICALL Java_org_classpath_icedtea_pulseaudio_PulseAudioTargetPort_native_1set_1volume
+(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/src/native/org_classpath_icedtea_pulseaudio_Stream.c	Thu Jun 12 00:42:40 2014 +0100
@@ -0,0 +1,1103 @@
+#include "org_classpath_icedtea_pulseaudio_Stream.h"
+
+#include "jni-common.h"
+#include <pulse/pulseaudio.h>
+#include <string.h>
+
+#define STREAM_POINTER "streamPointer"
+#define CONTEXT_POINTER "contextPointer"
+
+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");
+    }
+
+}
+
+static void buf_attr_changed_callback(pa_stream *stream, void *userdata) {
+    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, "bufferAttrCallback");
+    } else {
+        callJavaVoidMethod(pulse_thread_env, context->obj, "bufferAttrCallback");
+    }
+}
+
+// used to set stream flags and states.
+// The names in Stream.java have a {STATE_,FLAG_} prefix, but we don't want to
+// add the underscore in every line in init_constants, so we add it here. If
+// constants with no prefix are ever introduced (i.e. java_prefix is "",
+// it's important to remove the ##_ )
+#define SET_STREAM_ENUM(env, clz, java_prefix, state_name) \
+    SET_JAVA_STATIC_LONG_FIELD_TO_PA_ENUM(env, clz, java_prefix##_, STREAM, state_name)
+
+/*
+ * Class:     org_classpath_icedtea_pulseaudio_Stream
+ * Method:    init_constants
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL Java_org_classpath_icedtea_pulseaudio_Stream_init_1constants
+  (JNIEnv *env, jclass clz) {
+    // set states.
+    SET_STREAM_ENUM(env, clz, STATE, UNCONNECTED);
+    SET_STREAM_ENUM(env, clz, STATE, CREATING);
+    SET_STREAM_ENUM(env, clz, STATE, READY);
+    SET_STREAM_ENUM(env, clz, STATE, FAILED);
+    SET_STREAM_ENUM(env, clz, STATE, TERMINATED);
+
+    // set flags.
+    SET_STREAM_ENUM(env, clz, FLAG, NOFLAGS);
+    SET_STREAM_ENUM(env, clz, FLAG, START_CORKED);
+    SET_STREAM_ENUM(env, clz, FLAG, INTERPOLATE_TIMING);
+    SET_STREAM_ENUM(env, clz, FLAG, NOT_MONOTONIC);
+    SET_STREAM_ENUM(env, clz, FLAG, AUTO_TIMING_UPDATE);
+    SET_STREAM_ENUM(env, clz, FLAG, NO_REMAP_CHANNELS);
+    SET_STREAM_ENUM(env, clz, FLAG, NO_REMIX_CHANNELS);
+    SET_STREAM_ENUM(env, clz, FLAG, FIX_FORMAT);
+    SET_STREAM_ENUM(env, clz, FLAG, FIX_RATE);
+    SET_STREAM_ENUM(env, clz, FLAG, FIX_CHANNELS);
+    SET_STREAM_ENUM(env, clz, FLAG, DONT_MOVE);
+    SET_STREAM_ENUM(env, clz, FLAG, VARIABLE_RATE);
+    SET_STREAM_ENUM(env, clz, FLAG, PEAK_DETECT);
+    SET_STREAM_ENUM(env, clz, FLAG, START_MUTED);
+    SET_STREAM_ENUM(env, clz, FLAG, ADJUST_LATENCY);
+    SET_STREAM_ENUM(env, clz, FLAG, EARLY_REQUESTS);
+    SET_STREAM_ENUM(env, clz, FLAG, DONT_INHIBIT_AUTO_SUSPEND);
+    SET_STREAM_ENUM(env, clz, FLAG, START_UNMUTED);
+    SET_STREAM_ENUM(env, clz, FLAG, FAIL_ON_SUSPEND);
+}
+
+/*
+ * 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);
+
+    setJavaPointer(env, obj, CONTEXT_POINTER, j_context);
+
+    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, STREAM_POINTER, 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);
+    pa_stream_set_buffer_attr_callback(stream, buf_attr_changed_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) {
+
+    java_context* j_context = getJavaPointer(env, obj, CONTEXT_POINTER);
+    assert(j_context);
+    (*env)->DeleteGlobalRef(env, j_context->obj);
+    free(j_context);
+    setJavaPointer(env, obj, CONTEXT_POINTER, NULL);
+
+    pa_stream* stream = getJavaPointer(env, obj, STREAM_POINTER);
+    assert(stream);
+    pa_stream_unref(stream);
+    setJavaPointer(env, obj, STREAM_POINTER, NULL);
+}
+
+/*
+ * Class:     org_classpath_icedtea_pulseaudio_Stream
+ * Method:    native_pa_stream_get_state
+ * Signature: ()I
+ */
+JNIEXPORT jlong 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, jlong 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;
+
+    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_playback(stream, dev, &buffer_attr,
+            (pa_stream_flags_t) flags, 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, jlong 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;
+
+    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,
+                                         (pa_stream_flags_t) 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) {
+
+    assert(stream);
+    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);
+    pa_operation* operation = pa_stream_cork(stream, yes, cork_callback, NULL);
+    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 = "org/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_set_volume
+ * Signature: (F)[B
+ */
+JNIEXPORT jbyteArray JNICALL Java_org_classpath_icedtea_pulseaudio_Stream_native_1set_1volume
+(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);
+
+}
+
+
+static void get_sink_input_volume_callback(pa_context *context, const pa_sink_input_info *i,
+        int eol, void *userdata) {
+
+    JNIEnv* env = pulse_thread_env;
+
+    assert(context);
+    assert(env);
+    jobject obj = (jobject) userdata;
+    assert(obj);
+
+    if (eol == 0) {
+        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);
+        (*env)->DeleteGlobalRef(env, obj);
+    }
+}
+
+/*
+ * Class:     org_classpath_icedtea_pulseaudio_Stream
+ * Method:    native_update_volume
+ * Signature: ()[B
+ */
+JNIEXPORT jbyteArray JNICALL Java_org_classpath_icedtea_pulseaudio_Stream_native_1update_1volume
+(JNIEnv* env, jobject obj) {
+
+    pa_stream* stream = getJavaPointer(env, obj, STREAM_POINTER);
+    assert(stream);
+
+    int sink_input_index = pa_stream_get_index(stream);
+
+    pa_context* context = pa_stream_get_context(stream);
+    assert(context);
+
+    obj = (*env)->NewGlobalRef(env, obj);
+    pa_operation *o = pa_context_get_sink_input_info(context, sink_input_index , get_sink_input_volume_callback, obj);
+    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/testsounds/README	Thu Jun 12 00:42:40 2014 +0100
@@ -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 testsounds/error.wav has changed
Binary file testsounds/logout.wav has changed
Binary file testsounds/startup.wav has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/unittests/org/classpath/icedtea/pulseaudio/OtherSoundProvidersAvailableTest.java	Thu Jun 12 00:42:40 2014 +0100
@@ -0,0 +1,114 @@
+/* OtherSoundProvidersAvailableTest.java
+   Copyright (C) 2008 Red Hat, Inc.
+
+This file is part of IcedTea-Sound.
+
+IcedTea-Sound 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-Sound 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-Sound; 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/unittests/org/classpath/icedtea/pulseaudio/PulseAudioClipTest.java	Thu Jun 12 00:42:40 2014 +0100
@@ -0,0 +1,637 @@
+/* PulseAudioClipTest.java
+   Copyright (C) 2008 Red Hat, Inc.
+
+This file is part of IcedTea-Sound.
+
+IcedTea-Sound 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-Sound 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-Sound; 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 testOpenCloseLotsOfTimes() throws LineUnavailableException,
+            UnsupportedAudioFileException, IOException {
+        File soundFile = new File("testsounds/startup.wav");
+        AudioInputStream audioInputStream = AudioSystem
+                .getAudioInputStream(soundFile);
+        for (int i = 0; i < 1000; i++) {
+            Clip clip = (Clip) mixer.getLine(new Line.Info(Clip.class));
+            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 >= 1);
+                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);
+
+                int initiallyOpenClips = mixer.getSourceLines().length;
+                Assert.assertEquals(initiallyOpenClips, mixer.getSourceLines().length);
+                clip.open(audioInputStream1);
+                Assert.assertEquals(initiallyOpenClips + 1, mixer.getSourceLines().length);
+                Assert.assertEquals(clip, mixer.getSourceLines()[initiallyOpenClips]);
+                clip.close();
+                Assert.assertEquals(initiallyOpenClips, mixer.getSourceLines().length);
+
+        }
+
+        @After
+        public void tearDown() {
+                mixer.close();
+
+        }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/unittests/org/classpath/icedtea/pulseaudio/PulseAudioEventLoopOverhead.java	Thu Jun 12 00:42:40 2014 +0100
@@ -0,0 +1,100 @@
+/* PulseAudioEventLoopOverhead.java
+   Copyright (C) 2008 Red Hat, Inc.
+
+This file is part of IcedTea-Sound.
+
+IcedTea-Sound 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-Sound 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-Sound; 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/unittests/org/classpath/icedtea/pulseaudio/PulseAudioMixerProviderTest.java	Thu Jun 12 00:42:40 2014 +0100
@@ -0,0 +1,118 @@
+/* PulseAudioMixerProviderTest.java
+   Copyright (C) 2008 Red Hat, Inc.
+
+This file is part of IcedTea-Sound.
+
+IcedTea-Sound 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-Sound 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-Sound; 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/unittests/org/classpath/icedtea/pulseaudio/PulseAudioMixerRawTest.java	Thu Jun 12 00:42:40 2014 +0100
@@ -0,0 +1,120 @@
+/* PulseAudioMixerRawTest.java
+   Copyright (C) 2008 Red Hat, Inc.
+
+This file is part of IcedTea-Sound.
+
+IcedTea-Sound 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-Sound 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-Sound; 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/unittests/org/classpath/icedtea/pulseaudio/PulseAudioMixerTest.java	Thu Jun 12 00:42:40 2014 +0100
@@ -0,0 +1,434 @@
+/* PulseAudioMixerTest.java
+   Copyright (C) 2008 Red Hat, Inc.
+
+This file is part of IcedTea-Sound.
+
+IcedTea-Sound 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-Sound 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-Sound; 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/unittests/org/classpath/icedtea/pulseaudio/PulseAudioSourceDataLineRawTest.java	Thu Jun 12 00:42:40 2014 +0100
@@ -0,0 +1,299 @@
+/* PulseAudioSourceDataLineRawTest.java
+   Copyright (C) 2008 Red Hat, Inc.
+
+This file is part of IcedTea-Sound.
+
+IcedTea-Sound 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-Sound 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-Sound; 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.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 testVolume() 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);
+
+                volume.setValue(PulseAudioVolumeControl.MAX_VOLUME);
+
+                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/unittests/org/classpath/icedtea/pulseaudio/PulseAudioSourceDataLineTest.java	Thu Jun 12 00:42:40 2014 +0100
@@ -0,0 +1,1406 @@
+/* PulseSourceDataLineTest.java
+   Copyright (C) 2008 Red Hat, Inc.
+
+This file is part of IcedTea-Sound.
+
+IcedTea-Sound 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-Sound 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-Sound; 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(expected = IllegalArgumentException.class)
+        public void testWriteIntegralNumberFrames() throws LineUnavailableException {
+                sourceDataLine = (SourceDataLine) mixer.getLine(new Line.Info(
+                                SourceDataLine.class));
+
+                /* try writing an non-integral number of frames size */
+                sourceDataLine.open();
+                int frameSize = sourceDataLine.getFormat().getFrameSize();
+                byte[] buffer = new byte[(frameSize * 2) - 1];
+                sourceDataLine.write(buffer, 0, buffer.length);
+        }
+
+        @Test(expected = IllegalArgumentException.class)
+        public void testWriteNegativeLength() throws LineUnavailableException {
+                sourceDataLine = (SourceDataLine) mixer.getLine(new Line.Info(
+                                SourceDataLine.class));
+
+                sourceDataLine.open();
+                int frameSize = sourceDataLine.getFormat().getFrameSize();
+                byte[] buffer = new byte[(frameSize * 2)];
+                /* try writing a negative length */
+                sourceDataLine.write(buffer, 0, -2);
+        }
+
+        @Test(expected = ArrayIndexOutOfBoundsException.class)
+        public void testWriteNegativeOffset() throws LineUnavailableException {
+                sourceDataLine = (SourceDataLine) mixer.getLine(new Line.Info(
+                                SourceDataLine.class));
+
+                sourceDataLine.open();
+                int frameSize = sourceDataLine.getFormat().getFrameSize();
+                byte[] buffer = new byte[(frameSize * 2)];
+                /* try writing with a negative offset */
+                sourceDataLine.write(buffer, -1, buffer.length);
+        }
+
+        @Test(expected = ArrayIndexOutOfBoundsException.class)
+        public void testWriteMoreThanArrayLength() throws LineUnavailableException {
+                sourceDataLine = (SourceDataLine) mixer.getLine(new Line.Info(
+                                SourceDataLine.class));
+
+                sourceDataLine.open();
+                int frameSize = sourceDataLine.getFormat().getFrameSize();
+                byte[] buffer = new byte[(frameSize * 2)];
+                /* try writing more than the array length */
+                sourceDataLine.write(buffer, 0, frameSize * 3);
+        }
+
+        @Test(expected = ArrayIndexOutOfBoundsException.class)
+        public void testWriteMoreThanArrayLength2() throws LineUnavailableException {
+                sourceDataLine = (SourceDataLine) mixer.getLine(new Line.Info(
+                                SourceDataLine.class));
+
+                sourceDataLine.open();
+                int frameSize = sourceDataLine.getFormat().getFrameSize();
+                byte[] buffer = new byte[(frameSize * 2)];
+                /* try writing more than the array length */
+                sourceDataLine.write(buffer, 1, buffer.length);
+        }
+
+        // FIXME
+        @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);
+                                                }
+
+                                                // when the line is closed (in tearDown),
+                                                // break out of the loop
+                                                if (!sourceDataLine.isOpen()) {
+                                                        break;
+                                                }
+                                                total++;
+                                        }
+                                } catch (LineUnavailableException e) {
+                                        Assert.fail();
+                                } catch (IOException e) {
+                                        Assert.fail();
+                                }
+                        }
+
+                };
+
+                writer.start();
+
+                Thread.sleep(100);
+
+                writer.join(2000);
+
+                /* 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));
+                sourceDataLine.close();
+        }
+
+        @Test
+        public void testVolume() 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);
+
+                volume.setValue(volume.getMaximum());
+
+                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/unittests/org/classpath/icedtea/pulseaudio/PulseAudioSourcePortTest.java	Thu Jun 12 00:42:40 2014 +0100
@@ -0,0 +1,117 @@
+/* PulseAudioSourcePortTest.java
+   Copyright (C) 2008 Red Hat, Inc.
+
+This file is part of IcedTea-Sound.
+
+IcedTea-Sound 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-Sound 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-Sound; 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.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);
+                                port.close();
+                        }
+                }
+        }
+
+        @After
+        public void tearDown() {
+                if (mixer.isOpen()) {
+                        mixer.close();
+                }
+        }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/unittests/org/classpath/icedtea/pulseaudio/PulseAudioTargetDataLineTest.java	Thu Jun 12 00:42:40 2014 +0100
@@ -0,0 +1,648 @@
+/* PulseAudioTargetDataLineTest.java
+   Copyright (C) 2008 Red Hat, Inc.
+
+This file is part of IcedTea-Sound.
+
+IcedTea-Sound 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-Sound 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-Sound; 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));
+
+                int initiallyOpen = mixer.getTargetLines().length;
+                targetDataLine.open();
+                Assert.assertEquals(initiallyOpen+1, mixer.getTargetLines().length);
+                targetDataLine.close();
+                Assert.assertEquals(initiallyOpen, 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/unittests/org/classpath/icedtea/pulseaudio/PulseAudioTargetPortTest.java	Thu Jun 12 00:42:40 2014 +0100
@@ -0,0 +1,117 @@
+/* PulseAudioTargetPortTest.java
+   Copyright (C) 2008 Red Hat, Inc.
+
+This file is part of IcedTea-Sound.
+
+IcedTea-Sound 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-Sound 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-Sound; 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.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);
+                                port.close();
+                        }
+                }
+        }
+
+        @After
+        public void tearDown() {
+                if (mixer.isOpen()) {
+                        mixer.close();
+                }
+        }
+
+}