Mercurial > hg > icedtea-sound
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.
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
--- /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(); + } + } + +}