diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..cafb5d2 --- /dev/null +++ b/COPYING @@ -0,0 +1,510 @@ + + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin St, 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. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations +below. + + When we speak of free software, we are referring to freedom of use, +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 and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it +becomes a de-facto standard. To achieve this, non-free programs must +be allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, 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 library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete 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 distribute a copy of this License along with the +Library. + + 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 Library or any portion +of it, thus forming a work based on the Library, 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) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +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 Library, 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 Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you 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. + + If distribution of 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 satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at least + three years, to give the same user the materials specified in + Subsection 6a, above, for a charge no more than the cost of + performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be 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. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library 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. + + 9. 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 Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +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 with +this License. + + 11. 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 Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library 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 Library. + +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. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library 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. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser 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 Library +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 Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +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 + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "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 +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. 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 LIBRARY 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 +LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), 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 Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms +of the ordinary General Public License). + + To apply these terms, attach the following notices to the library. +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. + + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or +your school, if any, to sign a "copyright disclaimer" for the library, +if necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James + Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..26226b0 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,3 @@ +SUBDIRS = src + +ACLOCAL_AMFLAGS = -I m4 diff --git a/bootstrap.sh b/bootstrap.sh new file mode 100755 index 0000000..32e7b0f --- /dev/null +++ b/bootstrap.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +autoreconf -vfi + +if type -p colorgcc > /dev/null ; then + export CC=colorgcc +fi + +export CFLAGS=${CFLAGS-"-g -O0"} + +if test "x$NOCONFIGURE" = "x"; then + ./configure --enable-maintainer-mode --disable-processing "$@" + make clean +fi diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..f425ace --- /dev/null +++ b/configure.ac @@ -0,0 +1,248 @@ +AC_PREREQ(2.60) + +m4_define(PA_MAJOR, [2]) +m4_define(PA_MINOR, [1]) +m4_define(NEMO_MICRO, [0]) + +AC_INIT([pulseaudio-modules-droid], [PA_MAJOR.PA_MINOR.NEMO_MICRO], [mer-general@lists.merproject.org]) +AC_CONFIG_HEADER([config.h]) +AM_INIT_AUTOMAKE([foreign -Wall silent-rules]) +AC_CONFIG_MACRO_DIR(m4) +AM_SILENT_RULES([yes]) + +if type -p stow > /dev/null && test -d /usr/local/stow ; then + AC_MSG_NOTICE([*** Found /usr/local/stow: default install prefix set to /usr/local/stow/${PACKAGE_NAME}-${PACKAGE_VERSION} ***]) + ac_default_prefix="/usr/local/stow/${PACKAGE_NAME}-${PACKAGE_VERSION}" +fi + +AC_PROG_CC +AC_PROG_CC_C99 +AM_PROG_CC_C_O +AC_PROG_GCC_TRADITIONAL + +AC_SUBST(PA_MAJORMINOR, PA_MAJOR.PA_MINOR) + +DESIRED_FLAGS="-Wall -W -Wextra -pedantic -pipe -Wno-long-long -Winline -Wvla -Wno-overlength-strings -Wunsafe-loop-optimizations -Wundef -Wformat=2 -Wlogical-op -Wsign-compare -Wpacked -Wformat-security -Wmissing-include-dirs -Wformat-nonliteral -Wold-style-definition -Wpointer-arith -Winit-self -Wfloat-equal -Wmissing-prototypes -Wstrict-prototypes -Wredundant-decls -Wmissing-declarations -Wmissing-noreturn -Wshadow -Wendif-labels -Wstrict-aliasing=2 -Wwrite-strings -Wno-unused-parameter -ffast-math -Wp,-D_FORTIFY_SOURCE=2 -fno-common -fdiagnostics-show-option" # PulseAudio 0.9.15 usess same + -Wcast-align -Wdeclaration-after-statement + +for flag in $DESIRED_FLAGS ; do + CC_CHECK_CFLAGS([$flag], [CFLAGS="$CFLAGS $flag"]) +done + +# FIXME: we should push that as .m4 macro installed by pulseaudio-dev package +# Native atomic operation support +AC_ARG_ENABLE([atomic-arm-linux-helpers], + AS_HELP_STRING([--disable-atomic-arm-linux-helpers],[use inline asm or libatomic_ops instead]), + [ + case "${enableval}" in + yes) atomic_arm_linux_helpers=yes ;; + no) atomic_arm_linux_helpers=no ;; + *) AC_MSG_ERROR(bad value ${enableval} for --disable-atomic-arm-linux-helpers) ;; + esac + ], + [atomic_arm_linux_helpers=auto]) + +AC_ARG_ENABLE([atomic-arm-memory-barrier], + AS_HELP_STRING([--enable-atomic-arm-memory-barrier],[only really needed in SMP arm systems]), + [ + case "${enableval}" in + yes) AC_DEFINE_UNQUOTED(ATOMIC_ARM_MEMORY_BARRIER_ENABLED, 1, [Enable memory barriers]) ;; + no) ;; + *) AC_MSG_ERROR(bad value ${enableval} for --disable-atomic-arm-linux-helpers) ;; + esac + ],) + +AC_CANONICAL_HOST +AC_DEFINE_UNQUOTED([CANONICAL_HOST], "$host", [Canonical host string.]) + +AC_MSG_CHECKING([target operating system]) + +case $host in + *-*-linux*) + AC_MSG_RESULT([linux]) + pulse_target_os=linux + ;; + *) + AC_MSG_RESULT([unknown]) + pulse_target_os=unknown + ;; +esac + +# If everything else fails use libatomic_ops +need_libatomic_ops=yes + +AC_MSG_CHECKING([whether $CC knows __sync_bool_compare_and_swap()]) +AC_LANG_CONFTEST([int main() { int a = 4; __sync_bool_compare_and_swap(&a, 4, 5); }]) +$CC conftest.c $CFLAGS -o conftest > /dev/null 2> /dev/null +ret=$? +rm -f conftest.o conftest +if test $ret -eq 0 ; then + AC_DEFINE([HAVE_ATOMIC_BUILTINS], 1, [Have __sync_bool_compare_and_swap() and friends.]) + AC_MSG_RESULT([yes]) + need_libatomic_ops=no +else + AC_MSG_RESULT([no]) + # HW specific atomic ops stuff + AC_MSG_CHECKING([architecture for native atomic operations]) + case $host_cpu in + arm*) + AC_MSG_RESULT([arm]) + AC_MSG_CHECKING([compiler support for arm inline asm atomic operations]) + AC_LANG_CONFTEST([[int main() + { + volatile int a=0; + int o=0, n=1, r; + asm volatile ("ldrex %0, [%1]\n" + "subs %0, %0, %2\n" + "strexeq %0, %3, [%1]\n" + : "=&r" (r) + : "r" (&a), "Ir" (o), "r" (n) + : "cc"); + return (a==1 ? 0 : -1); + }]]) + $CC conftest.c $CFLAGS -o conftest > /dev/null 2>&1 + ret=$? + rm -f conftest.o conftest + if test $ret -eq 0 ; then + AC_DEFINE([ATOMIC_ARM_INLINE_ASM], 1, [Have ARMv6 instructions.]) + AC_MSG_RESULT([yes]) + need_libatomic_ops=no + else + # We really want inline asm atomic operations!! + echo "ERROR No ARMv6 instructions available check your compiler!!!" + exit 1 + + AC_MSG_CHECKING([whether we can use Linux kernel helpers]) + # The Linux kernel helper functions have been there since 2.6.16. However + # compile time checking for kernel version in cross compile environment + # (which is usually the case for arm cpu) is tricky (or impossible). + if test "x$pulse_target_os" = "xlinux" && test "x$atomic_arm_linux_helpers" != "xno"; then + AC_MSG_RESULT([yes]) + AC_DEFINE_UNQUOTED(ATOMIC_ARM_LINUX_HELPERS, 1, [special arm linux implementation]) + need_libatomic_ops=no + else + AC_MSG_RESULT([no]) + fi + fi + ;; + *) + AC_MSG_RESULT([unknown]) + ;; + esac +fi + +AC_MSG_CHECKING([whether we need libatomic_ops]) +if test "x$need_libatomic_ops" = "xyes"; then + AC_MSG_RESULT([yes]) + AC_CHECK_HEADERS([atomic_ops.h], [], [ + AC_MSG_ERROR([*** libatomic-ops headers not found]) + ]) + + # Win32 does not need the lib and breaks horribly if we try to include it + if test "x$os_is_win32" != "x1" ; then + LIBS="$LIBS -latomic_ops" + fi +else + AC_MSG_RESULT([no]) +fi + +CC_CHECK_TLS + +AC_CACHE_CHECK([whether $CC knows _Bool], + pulseaudio_cv__Bool, + [AC_COMPILE_IFELSE( + AC_LANG_PROGRAM([], [[_Bool b;]]), + [pulseaudio_cv__Bool=yes], + [pulseaudio_cv__Bool=no]) + ]) + +AS_IF([test "$pulseaudio_cv__Bool" = "yes"], [ + AC_DEFINE([HAVE_STD_BOOL], 1, [Have _Bool.]) + ]) + +#### libtool stuff (FIXME: when sbox will have 2.2 )#### +#LT_PREREQ(2.2) +#LT_INIT([dlopen win32-dll disable-static]) +AC_PROG_LIBTOOL + +PKG_CHECK_MODULES([PULSEAUDIO], [libpulse >= 2.1 pulsecore >= 2.1 ]) +AC_SUBST(PULSEAUDIO_CFLAGS) +AC_SUBST(PULSEAUDIO_LIBS) + +pulseaudiodir=`pkg-config --variable=prefix pulsecore` + +#PKG_CHECK_MODULES([DROIDHEADERS], [android-headers >= 0.0.6]) +# android-headers.pc has broken version field +PKG_CHECK_MODULES([DROIDHEADERS], [android-headers]) +AC_SUBST(DROIDHEADERS_CFLAGS) + +PKG_CHECK_MODULES([HYBRIS], [libhardware >= 0.1.0]) +AC_SUBST(HYBRIS_CFLAGS) +AC_SUBST(HYBRIS_LIBS) + +AC_ARG_WITH([module-dir], + AS_HELP_STRING([--with-module-dir],[Directory where to install the modules to (defaults to ${pulseaudiodir}/lib/pulse-${PA_MAJORMINOR}/modules/]), + [modlibexecdir=$withval], [modlibexecdir="${pulseaudiodir}/lib/pulse-${PA_MAJORMINOR}/modules"]) + +AC_SUBST(modlibexecdir) + +############################################ +# Processing +AC_ARG_ENABLE([processing], + AS_HELP_STRING([--disable-processing],[Disable optional processing support]), + [ + case "${enableval}" in + yes) processing=yes ;; + no) processing=no ;; + *) AC_MSG_ERROR(bad value ${enableval} for --disable-processing) ;; + esac + ], + [processing=yes]) + + +############################################ +# x86 +AC_MSG_CHECKING([Use x86 libraries]) +case $host_cpu in + arm*) x86=no; AC_MSG_RESULT([no]) ;; + *) x86=yes; AC_MSG_RESULT([yes]) ;; +esac +AM_CONDITIONAL([X86], [test "x$x86" = xyes]) +ENABLE_X86=no +if test "x$x86" = xyes ; then + AC_DEFINE([X86], 1, [x86 libraries used.]) + ENABLE_X86=yes +fi + +AC_MSG_CHECKING([If we are using hardfp tool chain]) +case `echo | gcc -v -xc -o - - 2>&1 | grep COLLECT_GCC_OPTIONS | tail -1` in + *float-abi=hard*) hardfp=yes; AC_MSG_RESULT([yes]) ;; + *) hardfp=no; AC_MSG_RESULT([no]) ;; +esac +AM_CONDITIONAL([ARM_HARDFP], [test "x$hardfp" = xyes]) +ENABLE_ARM_HARDFP=no +if test "x$hardfp" = xyes ; then + AC_DEFINE([ARM_HARDFP], 1, [ARM Hard FP toolchain used.]) + ENABLE_ARM_HARDFP=yes +fi + +AC_CONFIG_FILES([ +Makefile +src/Makefile +src/droid/Makefile +]) + +AC_OUTPUT + +echo " + + ---{ $PACKAGE_NAME $VERSION }--- + + compiler: ${CC} + CFLAGS: ${CFLAGS} + + prefix: ${prefix} + PulseAudio prefix: ${pulseaudiodir} + modules directory: ${modlibexecdir} + + Enable x86 libraries ${ENABLE_X86} + " diff --git a/m4/.gitignore b/m4/.gitignore new file mode 100644 index 0000000..38066dd --- /dev/null +++ b/m4/.gitignore @@ -0,0 +1,5 @@ +libtool.m4 +ltoptions.m4 +ltsugar.m4 +ltversion.m4 +lt~obsolete.m4 diff --git a/m4/attributes.m4 b/m4/attributes.m4 new file mode 100644 index 0000000..9c4a0c8 --- /dev/null +++ b/m4/attributes.m4 @@ -0,0 +1,258 @@ +dnl Macros to check the presence of generic (non-typed) symbols. +dnl Copyright (c) 2006-2007 Diego Pettenò +dnl Copyright (c) 2006-2007 xine project +dnl +dnl This program is free software; you can redistribute it and/or modify +dnl it under the terms of the GNU General Public License as published by +dnl the Free Software Foundation; either version 2, or (at your option) +dnl any later version. +dnl +dnl This program 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 +dnl GNU General Public License for more details. +dnl +dnl You should have received a copy of the GNU General Public License +dnl along with this program; if not, write to the Free Software +dnl Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +dnl 02110-1301, USA. +dnl +dnl As a special exception, the copyright owners of the +dnl macro gives unlimited permission to copy, distribute and modify the +dnl configure scripts that are the output of Autoconf when processing the +dnl Macro. You need not follow the terms of the GNU General Public +dnl License when using or distributing such scripts, even though portions +dnl of the text of the Macro appear in them. The GNU General Public +dnl License (GPL) does govern all other use of the material that +dnl constitutes the Autoconf Macro. +dnl +dnl This special exception to the GPL applies to versions of the +dnl Autoconf Macro released by this project. When you make and +dnl distribute a modified version of the Autoconf Macro, you may extend +dnl this special exception to the GPL to apply to your modified version as +dnl well. + +AC_DEFUN([CC_CHECK_CFLAGS_SILENT], [ + AC_CACHE_VAL(AS_TR_SH([cc_cv_cflags_$1]), + [ac_save_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS $1" + AC_COMPILE_IFELSE([int a;], + [eval "AS_TR_SH([cc_cv_cflags_$1])='yes'"], + [eval "AS_TR_SH([cc_cv_cflags_$1])='no'"]) + CFLAGS="$ac_save_CFLAGS" + ]) + + AS_IF([eval test x$]AS_TR_SH([cc_cv_cflags_$1])[ = xyes], + [$2], [$3]) +]) + +AC_DEFUN([CC_CHECK_CFLAGS], [ + AC_CACHE_CHECK([if $CC supports $1 flag], + AS_TR_SH([cc_cv_cflags_$1]), + CC_CHECK_CFLAGS_SILENT([$1]) dnl Don't execute actions here! + ) + + AS_IF([eval test x$]AS_TR_SH([cc_cv_cflags_$1])[ = xyes], + [$2], [$3]) +]) + +AC_DEFUN([CC_CHECK_LDFLAGS], [ + AC_CACHE_CHECK([if $CC supports $1 flag], + AS_TR_SH([cc_cv_ldflags_$1]), + [ac_save_LDFLAGS="$LDFLAGS" + LDFLAGS="$LDFLAGS $1" + AC_LINK_IFELSE([int main() { return 1; }], + [eval "AS_TR_SH([cc_cv_ldflags_$1])='yes'"], + [eval "AS_TR_SH([cc_cv_ldflags_$1])="]) + LDFLAGS="$ac_save_LDFLAGS" + ]) + + AS_IF([eval test x$]AS_TR_SH([cc_cv_ldflags_$1])[ = xyes], + [$2], [$3]) +]) + +dnl Check for a -Werror flag or equivalent. -Werror is the GCC +dnl and ICC flag that tells the compiler to treat all the warnings +dnl as fatal. We usually need this option to make sure that some +dnl constructs (like attributes) are not simply ignored. +dnl +dnl Other compilers don't support -Werror per se, but they support +dnl an equivalent flag: +dnl - Sun Studio compiler supports -errwarn=%all +AC_DEFUN([CC_CHECK_WERROR], [ + AC_CACHE_CHECK( + [for $CC way to treat warnings as errors], + [cc_cv_werror], + [CC_CHECK_CFLAGS_SILENT([-Werror], [cc_cv_werror=-Werror], + [CC_CHECK_CFLAGS_SILENT([-errwarn=%all], [cc_cv_werror=-errwarn=%all])]) + ]) +]) + +AC_DEFUN([CC_CHECK_ATTRIBUTE], [ + AC_REQUIRE([CC_CHECK_WERROR]) + AC_CACHE_CHECK([if $CC supports __attribute__(( ifelse([$2], , [$1], [$2]) ))], + AS_TR_SH([cc_cv_attribute_$1]), + [ac_save_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS $cc_cv_werror" + AC_COMPILE_IFELSE([$3], + [eval "AS_TR_SH([cc_cv_attribute_$1])='yes'"], + [eval "AS_TR_SH([cc_cv_attribute_$1])='no'"]) + CFLAGS="$ac_save_CFLAGS" + ]) + + AS_IF([eval test x$]AS_TR_SH([cc_cv_attribute_$1])[ = xyes], + [AC_DEFINE( + AS_TR_CPP([SUPPORT_ATTRIBUTE_$1]), 1, + [Define this if the compiler supports __attribute__(( ifelse([$2], , [$1], [$2]) ))] + ) + $4], + [$5]) +]) + +AC_DEFUN([CC_ATTRIBUTE_CONSTRUCTOR], [ + CC_CHECK_ATTRIBUTE( + [constructor],, + [void __attribute__((constructor)) ctor() { int a; }], + [$1], [$2]) +]) + +AC_DEFUN([CC_ATTRIBUTE_FORMAT], [ + CC_CHECK_ATTRIBUTE( + [format], [format(printf, n, n)], + [void __attribute__((format(printf, 1, 2))) printflike(const char *fmt, ...) { fmt = (void *)0; }], + [$1], [$2]) +]) + +AC_DEFUN([CC_ATTRIBUTE_FORMAT_ARG], [ + CC_CHECK_ATTRIBUTE( + [format_arg], [format_arg(printf)], + [char *__attribute__((format_arg(1))) gettextlike(const char *fmt) { fmt = (void *)0; }], + [$1], [$2]) +]) + +AC_DEFUN([CC_ATTRIBUTE_VISIBILITY], [ + CC_CHECK_ATTRIBUTE( + [visibility_$1], [visibility("$1")], + [void __attribute__((visibility("$1"))) $1_function() { }], + [$2], [$3]) +]) + +AC_DEFUN([CC_ATTRIBUTE_NONNULL], [ + CC_CHECK_ATTRIBUTE( + [nonnull], [nonnull()], + [void __attribute__((nonnull())) some_function(void *foo, void *bar) { foo = (void*)0; bar = (void*)0; }], + [$1], [$2]) +]) + +AC_DEFUN([CC_ATTRIBUTE_UNUSED], [ + CC_CHECK_ATTRIBUTE( + [unused], , + [void some_function(void *foo, __attribute__((unused)) void *bar);], + [$1], [$2]) +]) + +AC_DEFUN([CC_ATTRIBUTE_SENTINEL], [ + CC_CHECK_ATTRIBUTE( + [sentinel], , + [void some_function(void *foo, ...) __attribute__((sentinel));], + [$1], [$2]) +]) + +AC_DEFUN([CC_ATTRIBUTE_DEPRECATED], [ + CC_CHECK_ATTRIBUTE( + [deprecated], , + [void some_function(void *foo, ...) __attribute__((deprecated));], + [$1], [$2]) +]) + +AC_DEFUN([CC_ATTRIBUTE_ALIAS], [ + CC_CHECK_ATTRIBUTE( + [alias], [weak, alias], + [void other_function(void *foo) { } + void some_function(void *foo) __attribute__((weak, alias("other_function")));], + [$1], [$2]) +]) + +AC_DEFUN([CC_ATTRIBUTE_MALLOC], [ + CC_CHECK_ATTRIBUTE( + [malloc], , + [void * __attribute__((malloc)) my_alloc(int n);], + [$1], [$2]) +]) + +AC_DEFUN([CC_ATTRIBUTE_PACKED], [ + CC_CHECK_ATTRIBUTE( + [packed], , + [struct astructure { char a; int b; long c; void *d; } __attribute__((packed));], + [$1], [$2]) +]) + +AC_DEFUN([CC_ATTRIBUTE_CONST], [ + CC_CHECK_ATTRIBUTE( + [const], , + [int __attribute__((const)) twopow(int n) { return 1 << n; } ], + [$1], [$2]) +]) + +AC_DEFUN([CC_FLAG_VISIBILITY], [ + AC_REQUIRE([CC_CHECK_WERROR]) + AC_CACHE_CHECK([if $CC supports -fvisibility=hidden], + [cc_cv_flag_visibility], + [cc_flag_visibility_save_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS $cc_cv_werror" + CC_CHECK_CFLAGS_SILENT([-fvisibility=hidden], + cc_cv_flag_visibility='yes', + cc_cv_flag_visibility='no') + CFLAGS="$cc_flag_visibility_save_CFLAGS"]) + + AS_IF([test "x$cc_cv_flag_visibility" = "xyes"], + [AC_DEFINE([SUPPORT_FLAG_VISIBILITY], 1, + [Define this if the compiler supports the -fvisibility flag]) + $1], + [$2]) +]) + +AC_DEFUN([CC_FUNC_EXPECT], [ + AC_REQUIRE([CC_CHECK_WERROR]) + AC_CACHE_CHECK([if compiler has __builtin_expect function], + [cc_cv_func_expect], + [ac_save_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS $cc_cv_werror" + AC_COMPILE_IFELSE( + [int some_function() { + int a = 3; + return (int)__builtin_expect(a, 3); + }], + [cc_cv_func_expect=yes], + [cc_cv_func_expect=no]) + CFLAGS="$ac_save_CFLAGS" + ]) + + AS_IF([test "x$cc_cv_func_expect" = "xyes"], + [AC_DEFINE([SUPPORT__BUILTIN_EXPECT], 1, + [Define this if the compiler supports __builtin_expect() function]) + $1], + [$2]) +]) + +AC_DEFUN([CC_ATTRIBUTE_ALIGNED], [ + AC_REQUIRE([CC_CHECK_WERROR]) + AC_CACHE_CHECK([highest __attribute__ ((aligned ())) supported], + [cc_cv_attribute_aligned], + [ac_save_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS $cc_cv_werror" + for cc_attribute_align_try in 64 32 16 8 4 2; do + AC_COMPILE_IFELSE([ + int main() { + static char c __attribute__ ((aligned($cc_attribute_align_try))) = 0; + return c; + }], [cc_cv_attribute_aligned=$cc_attribute_align_try; break]) + done + CFLAGS="$ac_save_CFLAGS" + ]) + + if test "x$cc_cv_attribute_aligned" != "x"; then + AC_DEFINE_UNQUOTED([ATTRIBUTE_ALIGNED_MAX], [$cc_cv_attribute_aligned], + [Define the highest alignment supported]) + fi +]) diff --git a/m4/tls.m4 b/m4/tls.m4 new file mode 100644 index 0000000..3808f06 --- /dev/null +++ b/m4/tls.m4 @@ -0,0 +1,17 @@ +AC_DEFUN([CC_CHECK_TLS], [ + AC_CACHE_CHECK([whether $CC knows __thread for Thread-Local Storage], + cc_cv_tls___thread, + [AC_COMPILE_IFELSE( + AC_LANG_PROGRAM( + [[static __thread int a = 6;]], + [[a = 5;]]), + [cc_cv_tls___thread=yes], + [cc_cv_tls___thread=no]) + ]) + + AS_IF([test "x$cc_cv_tls___thread" = "xyes"], + [AC_DEFINE([SUPPORT_TLS___THREAD], 1, + [Define this if the compiler supports __thread for Thread-Local Storage]) + $1], + [$2]) +]) diff --git a/src/.gitignore b/src/.gitignore new file mode 100644 index 0000000..0c620eb --- /dev/null +++ b/src/.gitignore @@ -0,0 +1,5 @@ +.deps +.libs +*.la +*.lo +*.o diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..59897bf --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1 @@ +SUBDIRS = droid diff --git a/src/droid/Makefile.am b/src/droid/Makefile.am new file mode 100644 index 0000000..ec59021 --- /dev/null +++ b/src/droid/Makefile.am @@ -0,0 +1,49 @@ +AM_LIBADD = \ + $(PULSEAUDIO_LIBS) \ + $(HYBRIS_LIBS) +AM_CFLAGS = \ + $(PULSEAUDIO_CFLAGS) \ + $(DROIDHEADERS_CFLAGS) \ + $(HYBRIS_CFLAGS) \ + -I$(top_srcdir)/src/droid + + +modlibexec_LTLIBRARIES = \ + libdroid-util.la \ + libdroid-sink.la \ + libdroid-source.la \ + module-droid-sink.la \ + module-droid-source.la \ + module-droid-card.la + +noinst_HEADERS = module-droid-sink-symdef.h module-droid-card-symdef.h + +libdroid_util_la_SOURCES = droid-util.c droid-util.h +libdroid_util_la_LDFLAGS = -avoid-version -Wl,-no-undefined -Wl,-z,noexecstack -lhybris-common +libdroid_util_la_LIBADD = $(AM_LIBADD) +libdroid_util_la_CFLAGS = $(AM_CFLAGS) + +libdroid_sink_la_SOURCES = droid-sink.c droid-sink.h +libdroid_sink_la_LDFLAGS = -avoid-version -Wl,-no-undefined -Wl,-z,noexecstack -lhybris-common +libdroid_sink_la_LIBADD = $(AM_LIBADD) libdroid-util.la +libdroid_sink_la_CFLAGS = $(AM_CFLAGS) + +libdroid_source_la_SOURCES = droid-source.c droid-source.h +libdroid_source_la_LDFLAGS = -avoid-version -Wl,-no-undefined -Wl,-z,noexecstack -lhybris-common +libdroid_source_la_LIBADD = $(AM_LIBADD) libdroid-util.la +libdroid_source_la_CFLAGS = $(AM_CFLAGS) + +module_droid_sink_la_SOURCES = module-droid-sink.c +module_droid_sink_la_LDFLAGS = -module -avoid-version -Wl,-no-undefined -Wl,-z,noexecstack -lhybris-common +module_droid_sink_la_LIBADD = $(AM_LIBADD) -lm libdroid-util.la libdroid-sink.la +module_droid_sink_la_CFLAGS = $(AM_CFLAGS) + +module_droid_source_la_SOURCES = module-droid-source.c +module_droid_source_la_LDFLAGS = -module -avoid-version -Wl,-no-undefined -Wl,-z,noexecstack -lhybris-common +module_droid_source_la_LIBADD = $(AM_LIBADD) -lm libdroid-util.la libdroid-source.la +module_droid_source_la_CFLAGS = $(AM_CFLAGS) + +module_droid_card_la_SOURCES = module-droid-card.c +module_droid_card_la_LDFLAGS = -module -avoid-version -Wl,-no-undefined -Wl,-z,noexecstack -lhybris-common +module_droid_card_la_LIBADD = $(AM_LIBADD) -lm libdroid-util.la libdroid-sink.la libdroid-source.la +module_droid_card_la_CFLAGS = $(AM_CFLAGS) diff --git a/src/droid/droid-sink.c b/src/droid/droid-sink.c new file mode 100644 index 0000000..8882613 --- /dev/null +++ b/src/droid/droid-sink.c @@ -0,0 +1,745 @@ +/* + * Copyright (C) 2013 Jolla Ltd. + * + * Contact: Juho Hämäläinen + * + * These PulseAudio Modules are free software; you can redistribute + * it and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation + * version 2.1 of the License. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#define __STDC_FORMAT_MACROS +#include + +#ifdef HAVE_VALGRIND_MEMCHECK_H +#include +#endif + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "droid-sink.h" +#include "droid-util.h" + +struct userdata { + pa_core *core; + pa_module *module; + pa_card *card; + pa_sink *sink; + + pa_thread *thread; + pa_thread_mq thread_mq; + pa_rtpoll *rtpoll; + + pa_bool_t deferred_volume; /* TODO */ + + pa_memblockq *memblockq; + pa_memchunk silence; + size_t buffer_count; + size_t buffer_size; + pa_usec_t buffer_latency; + pa_usec_t timestamp; + + audio_devices_t primary_devices; + audio_devices_t enabled_devices; + + pa_droid_config_audio *config; /* Only used when used without card */ + pa_droid_hw_module *hw_module; + struct audio_stream_out *stream_out; +}; + +#define DEFAULT_MODULE_ID "primary" + +static void userdata_free(struct userdata *u); + +static pa_bool_t do_routing(struct userdata *u, audio_devices_t devices) { + char tmp[32]; + + pa_assert(u); + pa_assert(u->stream_out); + + if (u->primary_devices == devices) { + pa_log_debug("No change in enabled devices."); + return FALSE; + } + + u->enabled_devices &= ~u->primary_devices; + u->primary_devices = devices; + u->enabled_devices |= u->primary_devices; + + pa_snprintf(tmp, sizeof(tmp), "routing=%u;", u->enabled_devices); + pa_log_debug("set_parameters(): %s", tmp); + u->stream_out->common.set_parameters(&u->stream_out->common, tmp); + + return TRUE; +} + +static pa_bool_t parse_device_list(const char *str, audio_devices_t *dst) { + pa_assert(str); + pa_assert(dst); + + char *dev; + const char *state = NULL; + + *dst = 0; + + while ((dev = pa_split(str, "|", &state))) { + audio_devices_t d; + + if (!pa_string_convert_output_device_str_to_num(dev, &d)) { + pa_log_warn("Unknown device %s", dev); + pa_xfree(dev); + return FALSE; + } + + *dst |= d; + + pa_xfree(dev); + } + + return TRUE; +} + +static int thread_write(struct userdata *u) { + pa_memchunk c; + const void *p; + ssize_t wrote; + + pa_memblockq_peek_fixed_size(u->memblockq, u->buffer_size, &c); + + /* We should be able to write everything in one go as long as memblock size + * is multiples of buffer_size. */ + + for (;;) { + p = pa_memblock_acquire(c.memblock); + wrote = u->stream_out->write(u->stream_out, (const uint8_t*) p + c.index, c.length); + pa_memblock_release(c.memblock); + + if (wrote < 0) { + pa_memblockq_drop(u->memblockq, c.length); + pa_memblock_unref(c.memblock); + return -1; + } + + if (wrote < (ssize_t) c.length) { + c.index += wrote; + c.length -= wrote; + continue; + } + + pa_memblockq_drop(u->memblockq, c.length); + pa_memblock_unref(c.memblock); + + break; + } + + return 0; +} +static void thread_render(struct userdata *u) { + size_t length; + size_t missing; + + length = pa_memblockq_get_length(u->memblockq); + missing = u->buffer_size * u->buffer_count - length; + + if (missing > 0) { + pa_memchunk c; + pa_sink_render_full(u->sink, missing, &c); + pa_memblockq_push_align(u->memblockq, &c); + pa_memblock_unref(c.memblock); + } +} + +static void process_rewind(struct userdata *u) { + size_t rewind_nbytes; + size_t max_rewind_nbytes; + size_t queue_length; + + pa_assert(u); + + if (u->sink->thread_info.rewind_nbytes == 0) { + pa_sink_process_rewind(u->sink, 0); + return; + } + + rewind_nbytes = u->sink->thread_info.rewind_nbytes; + u->sink->thread_info.rewind_nbytes = 0; + + pa_assert(rewind_nbytes > 0); + pa_log_debug("Requested to rewind %lu bytes.", (unsigned long) rewind_nbytes); + + queue_length = pa_memblockq_get_length(u->memblockq); + if (queue_length <= u->buffer_size) + goto do_nothing; + max_rewind_nbytes = queue_length - u->buffer_size; + if (max_rewind_nbytes == 0) + goto do_nothing; + + if (rewind_nbytes > max_rewind_nbytes) + rewind_nbytes = max_rewind_nbytes; + + pa_memblockq_drop(u->memblockq, rewind_nbytes); + + pa_sink_process_rewind(u->sink, rewind_nbytes); + + pa_log_debug("Rewound %lu bytes.", (unsigned long) rewind_nbytes); + return; + +do_nothing: + pa_log_debug("Rewound 0 bytes."); + pa_sink_process_rewind(u->sink, 0); +} + +static void thread_func(void *userdata) { + struct userdata *u = userdata; + + pa_assert(u); + + pa_log_debug("Thread starting up."); + + if (u->core->realtime_scheduling) + pa_make_realtime(u->core->realtime_priority); + + pa_thread_mq_install(&u->thread_mq); + + u->timestamp = 0; + + for (;;) { + int ret; + + if (PA_SINK_IS_OPENED(u->sink->thread_info.state)) { + + u->timestamp = pa_rtclock_now(); + + if (PA_UNLIKELY(u->sink->thread_info.rewind_requested)) + process_rewind(u); + else + thread_render(u); + + if (pa_rtpoll_timer_elapsed(u->rtpoll)) { + pa_usec_t now, sleept; + + thread_write(u); + + now = pa_rtclock_now(); + + if (now - u->timestamp > u->buffer_latency / 2) + sleept = 0; + else + sleept = u->buffer_latency / 2 - (now - u->timestamp) ; + + pa_rtpoll_set_timer_relative(u->rtpoll, sleept); + } + } else + pa_rtpoll_set_timer_disabled(u->rtpoll); + + /* Sleep */ + if ((ret = pa_rtpoll_run(u->rtpoll, TRUE)) < 0) + goto fail; + + if (ret == 0) + goto finish; + } + +fail: + /* If this was no regular exit from the loop we have to continue + * processing messages until we received PA_MESSAGE_SHUTDOWN */ + pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL); + pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN); + +finish: + pa_log_debug("Thread shutting down."); +} + +/* Called from IO context */ +static int suspend(struct userdata *u) { + int ret; + size_t length; + + pa_assert(u); + pa_assert(u->sink); + pa_assert(u->stream_out); + + ret = u->stream_out->common.standby(&u->stream_out->common); + + if (ret == 0) { + pa_sink_set_max_request_within_thread(u->sink, 0); + pa_log_info("Device suspended."); + } else + pa_log("Couldn't set standby, err %d", ret); + + /* Clear memblockq */ + if ((length = pa_memblockq_get_length(u->memblockq)) > 0) + pa_memblockq_drop(u->memblockq, length); + + return ret; +} + +static int unsuspend(struct userdata *u) { + pa_assert(u); + pa_assert(u->sink); + + /* HAL resumes automagically when writing to standby stream, but let's set max request */ + pa_sink_set_max_request_within_thread(u->sink, u->buffer_size); + + pa_log_info("Resuming..."); + + return 0; +} + +/* Called from IO context */ +static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { + struct userdata *u = PA_SINK(o)->userdata; + + switch (code) { + case PA_SINK_MESSAGE_GET_LATENCY: { + pa_usec_t r = 0; + + /* HAL reports milliseconds */ + if (u->stream_out) + r = u->stream_out->get_latency(u->stream_out) * PA_USEC_PER_MSEC * u->buffer_count; + + *((pa_usec_t*) data) = r; + + return 0; + } + + case PA_SINK_MESSAGE_SET_STATE: { + switch ((pa_sink_state_t) PA_PTR_TO_UINT(data)) { + case PA_SINK_SUSPENDED: { + int r; + + pa_assert(PA_SINK_IS_OPENED(u->sink->thread_info.state)); + + if ((r = suspend(u)) < 0) + return r; + + break; + } + + case PA_SINK_IDLE: + /* Fall through */ + case PA_SINK_RUNNING: { + int r; + u->timestamp = 0; + + if (u->sink->thread_info.state == PA_SINK_SUSPENDED) { + if ((r = unsuspend(u)) < 0) + return r; + } + + pa_rtpoll_set_timer_absolute(u->rtpoll, pa_rtclock_now()); + break; + } + + /* not needed */ + case PA_SINK_UNLINKED: + case PA_SINK_INIT: + case PA_SINK_INVALID_STATE: + ; + } + break; + } + } + + return pa_sink_process_msg(o, code, data, offset, chunk); +} + +static int sink_set_port_cb(pa_sink *s, pa_device_port *p) { + struct userdata *u = s->userdata; + pa_droid_port_data *data; + + pa_assert(u); + pa_assert(p); + + data = PA_DEVICE_PORT_DATA(p); + + pa_log_debug("Sink set port %u", data->device); + + do_routing(u, data->device); + + return 0; +} + +static void sink_set_volume_cb(pa_sink *s) { + struct userdata *u = s->userdata; + pa_cvolume r; + + /* Shift up by the base volume */ + pa_sw_cvolume_divide_scalar(&r, &s->real_volume, s->base_volume); + + if (r.channels == 1) { + float val = pa_sw_volume_to_linear(r.values[0]); + pa_log_debug("Set hw volume %f", val); + if (u->stream_out->set_volume(u->stream_out, val, val) < 0) + return; + } else if (r.channels == 2) { + float val[2]; + for (unsigned i = 0; i < 2; i++) + val[i] = pa_sw_volume_to_linear(r.values[i]); + pa_log_debug("Set hw volume %f : %f", val[0], val[1]); + if (u->stream_out->set_volume(u->stream_out, val[0], val[1]) < 0) + return; + } +} + +static void update_volumes(struct userdata *u) { + int ret = -1; + + if (u->stream_out->set_volume) { + pa_log_debug("Probe hw volume support for %s", u->sink->name); + ret = u->stream_out->set_volume(u->stream_out, 1.0f, 1.0f); + } + + /* Use hardware volume */ + if (ret == 0) { + pa_log_debug("Using hardware volume control for %s", u->sink->name); + pa_sink_set_set_volume_callback(u->sink, sink_set_volume_cb); + } else { + pa_log_debug("Using software volume control for %s", u->sink->name); + pa_sink_set_set_volume_callback(u->sink, NULL); + } +} + +static void set_sink_name(pa_modargs *ma, pa_sink_new_data *data, const char *module_id) { + const char *tmp; + + pa_assert(ma); + pa_assert(data); + pa_assert(module_id); + + if ((tmp = pa_modargs_get_value(ma, "sink_name", NULL))) { + pa_sink_new_data_set_name(data, tmp); + data->namereg_fail = TRUE; + } else { + char *tt = pa_sprintf_malloc("sink.%s", module_id); + pa_sink_new_data_set_name(data, tt); + pa_xfree(tt); + data->namereg_fail = FALSE; + } +} + +pa_sink *pa_droid_sink_new(pa_module *m, + pa_modargs *ma, + const char *driver, + pa_droid_hw_module *hw_module, + audio_output_flags_t flags, + pa_droid_mapping *am, + pa_card *card) { + + struct userdata *u = NULL; + pa_bool_t deferred_volume = FALSE; + char *thread_name = NULL; + pa_sink_new_data data; + const char *module_id = NULL; + const char *tmp; + char *list = NULL; + uint32_t alternate_sample_rate; + uint32_t sample_rate; + audio_devices_t dev_out; + pa_sample_spec sample_spec; + pa_channel_map channel_map; + pa_bool_t namereg_fail = FALSE; + uint32_t total_latency; + int ret; + + audio_format_t hal_audio_format = 0; + audio_channel_mask_t hal_channel_mask = 0; + + pa_assert(m); + pa_assert(ma); + pa_assert(driver); + + deferred_volume = m->core->deferred_volume; + if (pa_modargs_get_value_boolean(ma, "deferred_volume", &deferred_volume) < 0) { + pa_log("Failed to parse deferred_volume argument."); + goto fail; + } + + if (card && am) + module_id = am->output->module->name; + else + module_id = pa_modargs_get_value(ma, "module_id", DEFAULT_MODULE_ID); + + sample_spec = m->core->default_sample_spec; + channel_map = m->core->default_channel_map; + + if (pa_modargs_get_sample_spec_and_channel_map(ma, &sample_spec, &channel_map, PA_CHANNEL_MAP_AIFF) < 0) { + pa_log("Failed to parse sample specification and channel map."); + goto fail; + } + + alternate_sample_rate = m->core->alternate_sample_rate; + if (pa_modargs_get_alternate_sample_rate(ma, &alternate_sample_rate) < 0) { + pa_log("Failed to parse alternate sample rate."); + goto fail; + } + + u = pa_xnew0(struct userdata, 1); + u->core = m->core; + u->module = m; + u->card = card; + u->deferred_volume = deferred_volume; + u->rtpoll = pa_rtpoll_new(); + pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll); + + if (hw_module) { + pa_assert(card); + u->hw_module = hw_module; + } else { + /* Sink wasn't created from inside card module, so we'll need to open + * hw module ourselves. + * TODO some way to share hw module between other sinks/sources since + * opening same module from different places likely isn't a good thing. */ + + if (!(u->config = pa_droid_config_load(ma))) + goto fail; + + if (!(u->hw_module = pa_droid_hw_module_open(u->config, module_id, u))) + goto fail; + } + + if (!pa_convert_format(sample_spec.format, CONV_FROM_PA, &hal_audio_format)) { + pa_log("Sample spec format %u not supported.", sample_spec.format); + goto fail; + } + + for (int i = 0; i < channel_map.channels; i++) { + audio_channel_mask_t c; + if (!pa_convert_output_channel(channel_map.map[i], CONV_FROM_PA, &c)) { + pa_log("Failed to convert channel map."); + goto fail; + } + hal_channel_mask |= c; + } + + struct audio_config config_out = { + .sample_rate = sample_spec.rate, + .channel_mask = hal_channel_mask, + .format = hal_audio_format + }; + + /* Default routing */ + dev_out = AUDIO_DEVICE_OUT_DEFAULT; + + if ((tmp = pa_modargs_get_value(ma, "output_devices", NULL))) { + audio_devices_t tmp_dev; + + if (parse_device_list(tmp, &tmp_dev) && tmp_dev) + dev_out = tmp_dev; + + pa_log_debug("Set initial devices %s", tmp); + } + + if (am) + flags = am->output->flags; + + ret = u->hw_module->device->open_output_stream(u->hw_module->device, + u->hw_module->stream_out_id++, + dev_out, + flags, + &config_out, + &u->stream_out); + + if (!u->stream_out) { + pa_log("Failed to open output stream. (errno %d)", ret); + goto fail; + } + + u->buffer_size = u->stream_out->common.get_buffer_size(&u->stream_out->common); + u->buffer_latency = pa_bytes_to_usec(u->buffer_size, &sample_spec); + /* Disable internal rewinding for now. */ + u->buffer_count = 1; + + if ((sample_rate = u->stream_out->common.get_sample_rate(&u->stream_out->common)) != sample_spec.rate) { + pa_log_warn("Requested sample rate %u but got %u instead.", sample_spec.rate, sample_rate); + sample_spec.rate = sample_rate; + } + + pa_log_info("Created Android stream with device: %u flags: %u sample rate: %u channel mask: %u format: %u buffer size: %u", + dev_out, + flags, + sample_rate, + config_out.channel_mask, + config_out.format, + u->buffer_size); + + + pa_silence_memchunk_get(&u->core->silence_cache, u->core->mempool, &u->silence, &sample_spec, 0); + u->memblockq = pa_memblockq_new("droid-sink", 0, u->buffer_size * u->buffer_count, u->buffer_size * u->buffer_count, &sample_spec, 1, 0, 0, &u->silence); + + pa_sink_new_data_init(&data); + data.driver = driver; + data.module = m; + data.card = card; + + set_sink_name(ma, &data, module_id); + + /* We need to give pa_modargs_get_value_boolean() a pointer to a local + * variable instead of using &data.namereg_fail directly, because + * data.namereg_fail is a bitfield and taking the address of a bitfield + * variable is impossible. */ + namereg_fail = data.namereg_fail; + if (pa_modargs_get_value_boolean(ma, "namereg_fail", &namereg_fail) < 0) { + pa_log("Failed to parse namereg_fail argument."); + pa_sink_new_data_done(&data); + goto fail; + } + data.namereg_fail = namereg_fail; + + pa_sink_new_data_set_sample_spec(&data, &sample_spec); + pa_sink_new_data_set_channel_map(&data, &channel_map); + pa_sink_new_data_set_alternate_sample_rate(&data, alternate_sample_rate); + + /* + if (!(list = pa_list_string_output_device(dev_out))) { + pa_log("Couldn't format device list string."); + goto fail; + } + pa_proplist_sets(data.proplist, PROP_DROID_DEVICES, list); + pa_xfree(list); + + if (flags) { + if (!(list = pa_list_string_flags(flags))) { + pa_log("Couldn't format flag list string."); + goto fail; + } + } else + list = NULL; + + pa_proplist_sets(data.proplist, PROP_DROID_FLAGS, list ? list : ""); + pa_xfree(list); + */ + + if (am) + pa_droid_add_ports(&data.ports, am, card); + + u->sink = pa_sink_new(m->core, &data, PA_SINK_HARDWARE | PA_SINK_LATENCY | PA_SINK_FLAT_VOLUME); + pa_sink_new_data_done(&data); + + if (!u->sink) { + pa_log("Failed to create sink."); + goto fail; + } + + u->sink->userdata = u; + + u->sink->parent.process_msg = sink_process_msg; + + u->sink->set_port = sink_set_port_cb; + + pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); + pa_sink_set_rtpoll(u->sink, u->rtpoll); + + /* Rewind internal memblockq */ + pa_sink_set_max_rewind(u->sink, u->buffer_size * (u->buffer_count - 1)); + + thread_name = pa_sprintf_malloc("droid-sink-%s", module_id); + if (!(u->thread = pa_thread_new(thread_name, thread_func, u))) { + pa_log("Failed to create thread."); + goto fail; + } + pa_xfree(thread_name); + thread_name = NULL; + + /* Latency consists of HAL latency + our memblockq latency */ + total_latency = u->stream_out->get_latency(u->stream_out) + (uint32_t) pa_bytes_to_usec(u->buffer_size * u->buffer_count, &sample_spec); + pa_sink_set_fixed_latency(u->sink, total_latency); + pa_log_debug("Set fixed latency %lu usec", (unsigned long) pa_bytes_to_usec(total_latency, &sample_spec)); + pa_sink_set_max_request(u->sink, u->buffer_size * u->buffer_count); + + if (u->sink->active_port) + sink_set_port_cb(u->sink, u->sink->active_port); + + update_volumes(u); + + pa_sink_put(u->sink); + + return u->sink; + +fail: + pa_xfree(thread_name); + + if (u) + userdata_free(u); + + return NULL; +} + +void pa_droid_sink_free(pa_sink *s) { + struct userdata *u; + + pa_sink_assert_ref(s); + pa_assert_se(u = s->userdata); + + userdata_free(u); +} + +static void userdata_free(struct userdata *u) { + + if (u->sink) + pa_sink_unlink(u->sink); + + if (u->thread) { + pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL); + pa_thread_free(u->thread); + } + + pa_thread_mq_done(&u->thread_mq); + + if (u->sink) + pa_sink_unref(u->sink); + + if (u->hw_module && u->stream_out) + u->hw_module->device->close_output_stream(u->hw_module->device, u->stream_out); + + if (u->memblockq) + pa_memblockq_free(u->memblockq); + + if (u->silence.memblock) + pa_memblock_unref(u->silence.memblock); + + /* Stand-alone sink */ + if (!u->card && u->hw_module) + pa_droid_hw_module_close(u->hw_module); + + if (u->config) + pa_xfree(u->config); + + pa_xfree(u); +} diff --git a/src/droid/droid-sink.h b/src/droid/droid-sink.h new file mode 100644 index 0000000..a4f3183 --- /dev/null +++ b/src/droid/droid-sink.h @@ -0,0 +1,55 @@ +#ifndef foodroidsinkfoo +#define foodroidsinkfoo + +/* + * Copyright (C) 2013 Jolla Ltd. + * + * Contact: Juho Hämäläinen + * + * These PulseAudio Modules are free software; you can redistribute + * it and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation + * version 2.1 of the License. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#ifdef HAVE_VALGRIND_MEMCHECK_H +#include +#endif + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "droid-util.h" + +pa_sink *pa_droid_sink_new(pa_module *m, + pa_modargs *ma, + const char *driver, + pa_droid_hw_module *hw_module, + audio_output_flags_t flags, + pa_droid_mapping *am, + pa_card *card); +void pa_droid_sink_free(pa_sink *s); + +#endif diff --git a/src/droid/droid-source.c b/src/droid/droid-source.c new file mode 100644 index 0000000..203ead7 --- /dev/null +++ b/src/droid/droid-source.c @@ -0,0 +1,539 @@ +/* + * Copyright (C) 2013 Jolla Ltd. + * + * Contact: Juho Hämäläinen + * + * These PulseAudio Modules are free software; you can redistribute + * it and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation + * version 2.1 of the License. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#ifdef HAVE_VALGRIND_MEMCHECK_H +#include +#endif + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "droid-source.h" +#include "droid-util.h" + +struct userdata { + pa_core *core; + pa_module *module; + pa_card *card; + pa_source *source; + + pa_thread *thread; + pa_thread_mq thread_mq; + pa_rtpoll *rtpoll; + + pa_memchunk memchunk; + audio_devices_t primary_devices; + audio_devices_t enabled_devices; + size_t buffer_size; + pa_usec_t timestamp; + + pa_droid_config_audio *config; /* Only used when used without card */ + pa_droid_hw_module *hw_module; + audio_stream_in_t *stream; +}; + +#define DEFAULT_MODULE_ID "primary" + +static void userdata_free(struct userdata *u); + +static pa_bool_t do_routing(struct userdata *u, audio_devices_t devices) { + char tmp[32]; + char *devlist; + + pa_assert(u); + pa_assert(u->stream); + + if (u->primary_devices == devices) { + pa_log_debug("No change in enabled devices."); + return FALSE; + } + + u->enabled_devices &= ~u->primary_devices; + u->primary_devices = devices; + u->enabled_devices |= u->primary_devices; + + devlist = pa_list_string_input_device(devices); + pa_snprintf(tmp, sizeof(tmp), "routing=%u", devices); + pa_log_debug("set_parameters(): %s (%s)", tmp, devlist); + pa_xfree(devlist); + u->stream->common.set_parameters(&u->stream->common, tmp); + + return TRUE; +} + +static pa_bool_t parse_device_list(const char *str, audio_devices_t *dst) { + pa_assert(str); + pa_assert(dst); + + char *dev; + const char *state = NULL; + + *dst = 0; + + while ((dev = pa_split(str, "|", &state))) { + audio_devices_t d; + + if (!pa_string_convert_input_device_str_to_num(dev, &d)) { + pa_log_warn("Unknown device %s", dev); + pa_xfree(dev); + return FALSE; + } + + *dst |= d; + + pa_xfree(dev); + } + + return TRUE; +} + +static int thread_read(struct userdata *u) { + void *p; + ssize_t readd; + pa_memchunk chunk; + + chunk.memblock = pa_memblock_new(u->core->mempool, (size_t) u->buffer_size); + + p = pa_memblock_acquire(chunk.memblock); + readd = u->stream->read(u->stream, (uint8_t*) p, pa_memblock_get_length(chunk.memblock)); + pa_memblock_release(chunk.memblock); + + if (readd < 0) { + pa_log("Failed to read from stream. (err %i)", readd); + goto end; + } + + u->timestamp += pa_bytes_to_usec(readd, &u->source->sample_spec); + + chunk.index = 0; + chunk.length = readd; + + if (chunk.length > 0) + pa_source_post(u->source, &chunk); + +end: + pa_memblock_unref(chunk.memblock); + + return 0; +} + +static void thread_func(void *userdata) { + struct userdata *u = userdata; + + pa_assert(u); + + pa_log_debug("Thread starting up."); + + if (u->core->realtime_scheduling) + pa_make_realtime(u->core->realtime_priority); + + pa_thread_mq_install(&u->thread_mq); + + u->timestamp = pa_rtclock_now(); + + for (;;) { + int ret; + + if (PA_SOURCE_IS_OPENED(u->source->thread_info.state)) { + thread_read(u); + + pa_rtpoll_set_timer_absolute(u->rtpoll, u->timestamp); + } else + pa_rtpoll_set_timer_disabled(u->rtpoll); + + /* Sleep */ + if ((ret = pa_rtpoll_run(u->rtpoll, TRUE)) < 0) + goto fail; + + if (ret == 0) + goto finish; + + } + +fail: + /* If this was no regular exit from the loop we have to continue + * processing messages until we received PA_MESSAGE_SHUTDOWN */ + pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL); + pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN); + +finish: + pa_log_debug("Thread shutting down."); +} + +/* Called from IO context */ +static int suspend(struct userdata *u) { + int ret; + + pa_assert(u); + pa_assert(u->stream); + + ret = u->stream->common.standby(&u->stream->common); + + if (ret == 0) + pa_log_info("Device suspended."); + + return ret; +} + +/* Called from IO context */ +static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { + struct userdata *u = PA_SOURCE(o)->userdata; + + switch (code) { + case PA_SOURCE_MESSAGE_SET_STATE: { + switch ((pa_source_state_t) PA_PTR_TO_UINT(data)) { + case PA_SOURCE_SUSPENDED: { + int r; + + pa_assert(PA_SOURCE_IS_OPENED(u->source->thread_info.state)); + + if ((r = suspend(u)) < 0) + return r; + + break; + } + + case PA_SOURCE_IDLE: + break; + case PA_SOURCE_RUNNING: { + pa_log_info("Resuming..."); + u->timestamp = pa_rtclock_now(); + break; + } + + /* not needed */ + case PA_SOURCE_UNLINKED: + case PA_SOURCE_INIT: + case PA_SOURCE_INVALID_STATE: + ; + } + break; + } + } + + return pa_source_process_msg(o, code, data, offset, chunk); +} + +static int source_set_port_cb(pa_source *s, pa_device_port *p) { + struct userdata *u = s->userdata; + pa_droid_port_data *data; + + pa_assert(u); + pa_assert(p); + + data = PA_DEVICE_PORT_DATA(p); + + pa_log_debug("Source set port %u", data->device); + + do_routing(u, data->device); + + return 0; +} + + +static void source_set_name(pa_modargs *ma, pa_source_new_data *data, const char *module_id) { + const char *tmp; + + pa_assert(ma); + pa_assert(data); + pa_assert(module_id); + + if ((tmp = pa_modargs_get_value(ma, "source_name", NULL))) { + pa_source_new_data_set_name(data, tmp); + data->namereg_fail = TRUE; + } else { + char *tt = pa_sprintf_malloc("source.%s", module_id); + pa_source_new_data_set_name(data, tt); + pa_xfree(tt); + data->namereg_fail = FALSE; + } +} + +pa_source *pa_droid_source_new(pa_module *m, + pa_modargs *ma, + const char *driver, + pa_droid_hw_module *hw_module, + pa_droid_mapping *am, + pa_card *card) { + + struct userdata *u = NULL; + char *thread_name = NULL; + pa_source_new_data data; + const char *module_id = NULL; + const char *tmp; + uint32_t sample_rate; + uint32_t alternate_sample_rate; + audio_devices_t dev_in; + pa_sample_spec sample_spec; + pa_channel_map channel_map; + pa_bool_t namereg_fail = FALSE; + int ret; + + audio_format_t hal_audio_format = 0; + audio_channel_mask_t hal_channel_mask = 0; + + pa_assert(m); + pa_assert(ma); + pa_assert(driver); + + /* When running under card use hw module name for source by default. */ + if (card && ma) + module_id = am->input->module->name; + else + module_id = pa_modargs_get_value(ma, "module_id", DEFAULT_MODULE_ID); + + sample_spec = m->core->default_sample_spec; + channel_map = m->core->default_channel_map; + + if (pa_modargs_get_sample_spec_and_channel_map(ma, &sample_spec, &channel_map, PA_CHANNEL_MAP_AIFF) < 0) { + pa_log("Failed to parse sample specification and channel map."); + goto fail; + } + + alternate_sample_rate = m->core->alternate_sample_rate; + if (pa_modargs_get_alternate_sample_rate(ma, &alternate_sample_rate) < 0) { + pa_log("Failed to parse alternate sample rate."); + goto fail; + } + + u = pa_xnew0(struct userdata, 1); + u->core = m->core; + u->module = m; + u->card = card; + u->rtpoll = pa_rtpoll_new(); + pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll); + + if (hw_module) { + pa_assert(card); + u->hw_module = hw_module; + } else { + /* Stand-alone source */ + + if (!(u->config = pa_droid_config_load(ma))) + goto fail; + + if (!(u->hw_module = pa_droid_hw_module_open(u->config, module_id, u))) + goto fail; + } + + if (!pa_convert_format(sample_spec.format, CONV_FROM_PA, &hal_audio_format)) { + pa_log("Sample spec format %u not supported.", sample_spec.format); + goto fail; + } + + for (int i = 0; i < channel_map.channels; i++) { + audio_channel_mask_t c; + if (!pa_convert_input_channel(channel_map.map[i], CONV_FROM_PA, &c)) { + pa_log("Failed to convert channel map."); + goto fail; + } + hal_channel_mask |= c; + } + + struct audio_config config_in = { + .sample_rate = sample_spec.rate, + .channel_mask = hal_channel_mask, + .format = hal_audio_format + }; + + /* Default routing */ + /* FIXME So while setting routing through stream with HALv2 API fails, creation of stream + * requires HALv2 style device to work properly. So until that oddity is resolved we always + * set AUDIO_DEVICE_IN_BUILTIN_MIC as initial device here. */ +#if 0 + pa_assert_se(pa_string_convert_input_device_str_to_num("AUDIO_DEVICE_IN_BUILTIN_MIC", &dev_in)); + + if ((tmp = pa_modargs_get_value(ma, "input_devices", NULL))) { + audio_devices_t tmp_dev; + + if (parse_device_list(tmp, &tmp_dev) && tmp_dev) + dev_in = tmp_dev; + + pa_log_debug("Set initial devices %s", tmp); + } +#else + pa_log_info("FIXME: Setting AUDIO_DEVICE_IN_BUILTIN_MIC as initial device."); + dev_in = AUDIO_DEVICE_IN_BUILTIN_MIC; +#endif + dev_in = AUDIO_DEVICE_IN_DEFAULT; + ret = u->hw_module->device->open_input_stream(u->hw_module->device, + u->hw_module->stream_in_id++, + dev_in, + &config_in, + &u->stream); + + if (ret < 0) { + pa_log("Failed to open input stream."); + goto fail; + } + + u->buffer_size = u->stream->common.get_buffer_size(&u->stream->common); + + if ((sample_rate = u->stream->common.get_sample_rate(&u->stream->common)) != sample_spec.rate) { + pa_log_warn("Requested sample rate %u but got %u instead.", sample_spec.rate, sample_rate); + sample_spec.rate = sample_rate; + } + + pa_log_info("Created Android stream with device: %u sample rate: %u channel mask: %u format: %u buffer size: %u", + dev_in, + sample_rate, + config_in.channel_mask, + config_in.format, + u->buffer_size); + + + pa_source_new_data_init(&data); + data.driver = driver; + data.module = m; + data.card = card; + + source_set_name(ma, &data, module_id); + + /* We need to give pa_modargs_get_value_boolean() a pointer to a local + * variable instead of using &data.namereg_fail directly, because + * data.namereg_fail is a bitfield and taking the address of a bitfield + * variable is impossible. */ + namereg_fail = data.namereg_fail; + if (pa_modargs_get_value_boolean(ma, "namereg_fail", &namereg_fail) < 0) { + pa_log("Failed to parse namereg_fail argument."); + pa_source_new_data_done(&data); + goto fail; + } + data.namereg_fail = namereg_fail; + + pa_source_new_data_set_sample_spec(&data, &sample_spec); + pa_source_new_data_set_channel_map(&data, &channel_map); + pa_source_new_data_set_alternate_sample_rate(&data, alternate_sample_rate); + + if (am) + pa_droid_add_ports(&data.ports, am, card); + + u->source = pa_source_new(m->core, &data, PA_SOURCE_HARDWARE); + pa_source_new_data_done(&data); + + if (!u->source) { + pa_log("Failed to create source."); + goto fail; + } + + u->source->userdata = u; + + u->source->parent.process_msg = source_process_msg; + + u->source->set_port = source_set_port_cb; + + pa_source_set_asyncmsgq(u->source, u->thread_mq.inq); + pa_source_set_rtpoll(u->source, u->rtpoll); + + /* Disable rewind for droid source */ + pa_source_set_max_rewind(u->source, 0); + + thread_name = pa_sprintf_malloc("droid-source-%s", module_id); + if (!(u->thread = pa_thread_new(thread_name, thread_func, u))) { + pa_log("Failed to create thread."); + goto fail; + } + pa_xfree(thread_name); + thread_name = NULL; + + pa_source_set_fixed_latency(u->source, pa_bytes_to_usec(u->buffer_size, &sample_spec)); + pa_log_debug("Set fixed latency %" PRIu64 " usec", pa_bytes_to_usec(u->buffer_size, &sample_spec)); + + if (u->source->active_port) + source_set_port_cb(u->source, u->source->active_port); + + pa_source_put(u->source); + + return u->source; + +fail: + pa_xfree(thread_name); + + if (u) + userdata_free(u); + + return NULL; +} + +void pa_droid_source_free(pa_source *s) { + struct userdata *u; + + pa_source_assert_ref(s); + pa_assert_se(u = s->userdata); + + userdata_free(u); +} + +static void userdata_free(struct userdata *u) { + + if (u->source) + pa_source_unlink(u->source); + + if (u->thread) { + pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL); + pa_thread_free(u->thread); + } + + pa_thread_mq_done(&u->thread_mq); + + if (u->source) + pa_source_unref(u->source); + + if (u->memchunk.memblock) + pa_memblock_unref(u->memchunk.memblock); + + if (u->hw_module && u->stream) + u->hw_module->device->close_input_stream(u->hw_module->device, u->stream); + + // Stand alone source + if (!u->card && u->hw_module) + pa_droid_hw_module_close(u->hw_module); + + if (u->config) + pa_xfree(u->config); + + pa_xfree(u); +} diff --git a/src/droid/droid-source.h b/src/droid/droid-source.h new file mode 100644 index 0000000..0b123a2 --- /dev/null +++ b/src/droid/droid-source.h @@ -0,0 +1,54 @@ +#ifndef foodroidsourcefoo +#define foodroidsourcefoo + +/* + * Copyright (C) 2013 Jolla Ltd. + * + * Contact: Juho Hämäläinen + * + * These PulseAudio Modules are free software; you can redistribute + * it and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation + * version 2.1 of the License. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#ifdef HAVE_VALGRIND_MEMCHECK_H +#include +#endif + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "droid-util.h" + +pa_source *pa_droid_source_new(pa_module *m, + pa_modargs *ma, + const char *driver, + pa_droid_hw_module *hw_module, + pa_droid_mapping *am, + pa_card *card); +void pa_droid_source_free(pa_source *s); + +#endif diff --git a/src/droid/droid-util-41qc.h b/src/droid/droid-util-41qc.h new file mode 100644 index 0000000..3e3877c --- /dev/null +++ b/src/droid/droid-util-41qc.h @@ -0,0 +1,267 @@ +/* + * Copyright (C) 2013 Jolla Ltd. + * + * Contact: Juho Hämäläinen + * + * These PulseAudio Modules are free software; you can redistribute + * it and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation + * version 2.1 of the License. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA. + */ + +#ifndef _ANDROID_UTIL_V412_H_ +#define _ANDROID_UTIL_V412_H_ + +// PulseAudio value - Android value + +static uint32_t conversion_table_output_channel[][2] = { + { PA_CHANNEL_POSITION_MONO, AUDIO_CHANNEL_OUT_MONO }, + { PA_CHANNEL_POSITION_FRONT_LEFT, AUDIO_CHANNEL_OUT_FRONT_LEFT }, + { PA_CHANNEL_POSITION_FRONT_RIGHT, AUDIO_CHANNEL_OUT_FRONT_RIGHT}, + { PA_CHANNEL_POSITION_FRONT_CENTER, AUDIO_CHANNEL_OUT_FRONT_CENTER }, + { PA_CHANNEL_POSITION_SUBWOOFER, AUDIO_CHANNEL_OUT_LOW_FREQUENCY }, + { PA_CHANNEL_POSITION_REAR_LEFT, AUDIO_CHANNEL_OUT_BACK_LEFT }, + { PA_CHANNEL_POSITION_REAR_RIGHT, AUDIO_CHANNEL_OUT_BACK_RIGHT }, + { PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER, AUDIO_CHANNEL_OUT_FRONT_LEFT_OF_CENTER }, + { PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER, AUDIO_CHANNEL_OUT_FRONT_RIGHT_OF_CENTER }, + { PA_CHANNEL_POSITION_REAR_CENTER, AUDIO_CHANNEL_OUT_BACK_CENTER }, + { PA_CHANNEL_POSITION_SIDE_LEFT, AUDIO_CHANNEL_OUT_SIDE_LEFT }, + { PA_CHANNEL_POSITION_SIDE_RIGHT, AUDIO_CHANNEL_OUT_SIDE_RIGHT }, + { PA_CHANNEL_POSITION_TOP_CENTER, AUDIO_CHANNEL_OUT_TOP_CENTER }, + { PA_CHANNEL_POSITION_TOP_FRONT_LEFT, AUDIO_CHANNEL_OUT_TOP_FRONT_LEFT }, + { PA_CHANNEL_POSITION_TOP_FRONT_CENTER, AUDIO_CHANNEL_OUT_TOP_FRONT_CENTER }, + { PA_CHANNEL_POSITION_TOP_FRONT_RIGHT, AUDIO_CHANNEL_OUT_TOP_FRONT_RIGHT }, + { PA_CHANNEL_POSITION_TOP_REAR_LEFT, AUDIO_CHANNEL_OUT_TOP_BACK_LEFT }, + { PA_CHANNEL_POSITION_TOP_REAR_CENTER, AUDIO_CHANNEL_OUT_TOP_BACK_CENTER }, + { PA_CHANNEL_POSITION_TOP_REAR_RIGHT, AUDIO_CHANNEL_OUT_TOP_BACK_RIGHT } +}; + +static uint32_t conversion_table_input_channel[][2] = { + { PA_CHANNEL_POSITION_MONO, AUDIO_CHANNEL_IN_MONO }, + { PA_CHANNEL_POSITION_FRONT_LEFT, AUDIO_CHANNEL_IN_LEFT }, + { PA_CHANNEL_POSITION_FRONT_RIGHT, AUDIO_CHANNEL_IN_RIGHT}, + { PA_CHANNEL_POSITION_FRONT_CENTER, AUDIO_CHANNEL_IN_FRONT }, + { PA_CHANNEL_POSITION_REAR_CENTER, AUDIO_CHANNEL_IN_BACK }, + /* Following are missing suitable counterparts on PulseAudio side. */ + { AUDIO_CHANNEL_IN_LEFT_PROCESSED, AUDIO_CHANNEL_IN_LEFT_PROCESSED }, + { AUDIO_CHANNEL_IN_RIGHT_PROCESSED, AUDIO_CHANNEL_IN_RIGHT_PROCESSED }, + { AUDIO_CHANNEL_IN_FRONT_PROCESSED, AUDIO_CHANNEL_IN_FRONT_PROCESSED }, + { AUDIO_CHANNEL_IN_BACK_PROCESSED, AUDIO_CHANNEL_IN_BACK_PROCESSED }, + { AUDIO_CHANNEL_IN_PRESSURE, AUDIO_CHANNEL_IN_PRESSURE }, + { AUDIO_CHANNEL_IN_X_AXIS, AUDIO_CHANNEL_IN_X_AXIS }, + { AUDIO_CHANNEL_IN_Y_AXIS, AUDIO_CHANNEL_IN_Y_AXIS }, + { AUDIO_CHANNEL_IN_Z_AXIS, AUDIO_CHANNEL_IN_Z_AXIS }, + { AUDIO_CHANNEL_IN_VOICE_UPLINK, AUDIO_CHANNEL_IN_VOICE_UPLINK }, + { AUDIO_CHANNEL_IN_VOICE_DNLINK, AUDIO_CHANNEL_IN_VOICE_DNLINK } +}; + +static uint32_t conversion_table_format[][2] = { + { PA_SAMPLE_U8, AUDIO_FORMAT_PCM_8_BIT }, + { PA_SAMPLE_S16LE, AUDIO_FORMAT_PCM_16_BIT }, + { PA_SAMPLE_S32LE, AUDIO_FORMAT_PCM_32_BIT }, + { PA_SAMPLE_S24LE, AUDIO_FORMAT_PCM_8_24_BIT } +}; + +struct string_conversion { + uint32_t value; + const char *str; +}; + +#if defined(STRING_ENTRY) || defined(STRING_ENTRY) +#error STRING_ENTRY already defined somewhere, fix this lib. +#endif +#define STRING_ENTRY(str) { str, #str } +/* Output devices */ +static struct string_conversion string_conversion_table_output_device[] = { + STRING_ENTRY(AUDIO_DEVICE_OUT_EARPIECE), + STRING_ENTRY(AUDIO_DEVICE_OUT_SPEAKER), + STRING_ENTRY(AUDIO_DEVICE_OUT_WIRED_HEADSET), + STRING_ENTRY(AUDIO_DEVICE_OUT_WIRED_HEADPHONE), + STRING_ENTRY(AUDIO_DEVICE_OUT_BLUETOOTH_SCO), + STRING_ENTRY(AUDIO_DEVICE_OUT_BLUETOOTH_SCO_HEADSET), + STRING_ENTRY(AUDIO_DEVICE_OUT_BLUETOOTH_SCO_CARKIT), + STRING_ENTRY(AUDIO_DEVICE_OUT_BLUETOOTH_A2DP), + STRING_ENTRY(AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES), + STRING_ENTRY(AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER), + STRING_ENTRY(AUDIO_DEVICE_OUT_AUX_DIGITAL), + STRING_ENTRY(AUDIO_DEVICE_OUT_ANLG_DOCK_HEADSET), + STRING_ENTRY(AUDIO_DEVICE_OUT_DGTL_DOCK_HEADSET), + STRING_ENTRY(AUDIO_DEVICE_OUT_USB_ACCESSORY), + STRING_ENTRY(AUDIO_DEVICE_OUT_USB_DEVICE), + STRING_ENTRY(AUDIO_DEVICE_OUT_FM), + STRING_ENTRY(AUDIO_DEVICE_OUT_FM_TX), + STRING_ENTRY(AUDIO_DEVICE_OUT_ANC_HEADSET), + STRING_ENTRY(AUDIO_DEVICE_OUT_ANC_HEADPHONE), + STRING_ENTRY(AUDIO_DEVICE_OUT_PROXY), + STRING_ENTRY(AUDIO_DEVICE_OUT_ALL), + STRING_ENTRY(AUDIO_DEVICE_OUT_ALL_A2DP), + STRING_ENTRY(AUDIO_DEVICE_OUT_ALL_SCO), + STRING_ENTRY(AUDIO_DEVICE_OUT_ALL_USB), + { 0, NULL } +}; + +static struct string_conversion string_conversion_table_output_device_fancy[] = { + { AUDIO_DEVICE_OUT_EARPIECE, "output-earpiece" }, + { AUDIO_DEVICE_OUT_SPEAKER, "output-speaker" }, + { AUDIO_DEVICE_OUT_WIRED_HEADSET, "output-wired_headset" }, + { AUDIO_DEVICE_OUT_WIRED_HEADPHONE, "output-wired_headphone" }, + { AUDIO_DEVICE_OUT_BLUETOOTH_SCO, "output-bluetooth_sco" }, + { AUDIO_DEVICE_OUT_BLUETOOTH_SCO_HEADSET, "output-sco_headset" }, + { AUDIO_DEVICE_OUT_BLUETOOTH_SCO_CARKIT, "output-sco_carkit" }, + { AUDIO_DEVICE_OUT_BLUETOOTH_A2DP, "output-a2dp" }, + { AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES, "output-a2dp_headphones" }, + { AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER, "output-a2dp_speaker" }, + { AUDIO_DEVICE_OUT_AUX_DIGITAL, "output-aux_digital" }, + { AUDIO_DEVICE_OUT_ANLG_DOCK_HEADSET, "output-analog_dock_headset" }, + { AUDIO_DEVICE_OUT_DGTL_DOCK_HEADSET, "output-digital_dock_headset" }, + { AUDIO_DEVICE_OUT_USB_ACCESSORY, "output-usb_accessory" }, + { AUDIO_DEVICE_OUT_USB_DEVICE, "output-usb_device" }, + { 0, NULL } +}; + +/* Input devices */ +static struct string_conversion string_conversion_table_input_device[] = { + STRING_ENTRY(AUDIO_DEVICE_IN_COMMUNICATION), + STRING_ENTRY(AUDIO_DEVICE_IN_AMBIENT), + STRING_ENTRY(AUDIO_DEVICE_IN_BUILTIN_MIC), + STRING_ENTRY(AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET), + STRING_ENTRY(AUDIO_DEVICE_IN_WIRED_HEADSET), + STRING_ENTRY(AUDIO_DEVICE_IN_AUX_DIGITAL), + STRING_ENTRY(AUDIO_DEVICE_IN_VOICE_CALL), + STRING_ENTRY(AUDIO_DEVICE_IN_BACK_MIC), + STRING_ENTRY(AUDIO_DEVICE_IN_ANC_HEADSET), + STRING_ENTRY(AUDIO_DEVICE_IN_FM_RX), + STRING_ENTRY(AUDIO_DEVICE_IN_FM_RX_A2DP), + STRING_ENTRY(AUDIO_DEVICE_IN_PROXY), + { 0, NULL } +}; + +static struct string_conversion string_conversion_table_input_device_fancy[] = { + { AUDIO_DEVICE_IN_COMMUNICATION, "input-in_communication" }, + { AUDIO_DEVICE_IN_AMBIENT, "input-ambient" }, + { AUDIO_DEVICE_IN_BUILTIN_MIC, "input-builtin_mic" }, + { AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET, "input-bluetooth_sco_headset" }, + { AUDIO_DEVICE_IN_WIRED_HEADSET, "input-wired_headset" }, + { AUDIO_DEVICE_IN_AUX_DIGITAL, "input-aux_digital" }, + { AUDIO_DEVICE_IN_VOICE_CALL, "input-voice_call" }, + { AUDIO_DEVICE_IN_BACK_MIC, "input-back_mic" }, + { AUDIO_DEVICE_IN_ANC_HEADSET, "input-anc_headset" }, + { AUDIO_DEVICE_IN_FM_RX, "input-fm_rx" }, + { AUDIO_DEVICE_IN_FM_RX_A2DP, "input-fm_rx_a2dp" }, + { AUDIO_DEVICE_IN_PROXY, "input-in_proxy" }, + { 0, NULL } +}; + +/* Flags */ +static struct string_conversion string_conversion_table_flag[] = { + STRING_ENTRY(AUDIO_OUTPUT_FLAG_NONE), + STRING_ENTRY(AUDIO_OUTPUT_FLAG_DIRECT), + STRING_ENTRY(AUDIO_OUTPUT_FLAG_PRIMARY), + STRING_ENTRY(AUDIO_OUTPUT_FLAG_FAST), + STRING_ENTRY(AUDIO_OUTPUT_FLAG_DEEP_BUFFER), + /* Qualcomm flags */ + STRING_ENTRY(AUDIO_OUTPUT_FLAG_LPA), + STRING_ENTRY(AUDIO_OUTPUT_FLAG_TUNNEL), + STRING_ENTRY(AUDIO_OUTPUT_FLAG_VOIP_RX), + { 0, NULL } +}; + +/* Channels */ +static struct string_conversion string_conversion_table_output_channels[] = { + STRING_ENTRY(AUDIO_CHANNEL_OUT_FRONT_LEFT), + STRING_ENTRY(AUDIO_CHANNEL_OUT_FRONT_RIGHT), + STRING_ENTRY(AUDIO_CHANNEL_OUT_FRONT_CENTER), + STRING_ENTRY(AUDIO_CHANNEL_OUT_LOW_FREQUENCY), + STRING_ENTRY(AUDIO_CHANNEL_OUT_BACK_LEFT), + STRING_ENTRY(AUDIO_CHANNEL_OUT_BACK_RIGHT), + STRING_ENTRY(AUDIO_CHANNEL_OUT_FRONT_LEFT_OF_CENTER), + STRING_ENTRY(AUDIO_CHANNEL_OUT_FRONT_RIGHT_OF_CENTER), + STRING_ENTRY(AUDIO_CHANNEL_OUT_BACK_CENTER), + STRING_ENTRY(AUDIO_CHANNEL_OUT_SIDE_LEFT), + STRING_ENTRY(AUDIO_CHANNEL_OUT_SIDE_RIGHT), + STRING_ENTRY(AUDIO_CHANNEL_OUT_TOP_CENTER), + STRING_ENTRY(AUDIO_CHANNEL_OUT_TOP_FRONT_LEFT), + STRING_ENTRY(AUDIO_CHANNEL_OUT_TOP_FRONT_CENTER), + STRING_ENTRY(AUDIO_CHANNEL_OUT_TOP_FRONT_RIGHT), + STRING_ENTRY(AUDIO_CHANNEL_OUT_TOP_BACK_LEFT), + STRING_ENTRY(AUDIO_CHANNEL_OUT_TOP_BACK_CENTER), + STRING_ENTRY(AUDIO_CHANNEL_OUT_TOP_BACK_RIGHT), + STRING_ENTRY(AUDIO_CHANNEL_OUT_MONO), + STRING_ENTRY(AUDIO_CHANNEL_OUT_STEREO), + STRING_ENTRY(AUDIO_CHANNEL_OUT_QUAD), + STRING_ENTRY(AUDIO_CHANNEL_OUT_SURROUND), + STRING_ENTRY(AUDIO_CHANNEL_OUT_5POINT1), + STRING_ENTRY(AUDIO_CHANNEL_OUT_7POINT1), + STRING_ENTRY(AUDIO_CHANNEL_OUT_ALL), + { 0, NULL } +}; +static struct string_conversion string_conversion_table_input_channels[] = { + STRING_ENTRY(AUDIO_CHANNEL_IN_LEFT), + STRING_ENTRY(AUDIO_CHANNEL_IN_RIGHT), + STRING_ENTRY(AUDIO_CHANNEL_IN_FRONT), + STRING_ENTRY(AUDIO_CHANNEL_IN_BACK), + STRING_ENTRY(AUDIO_CHANNEL_IN_LEFT_PROCESSED), + STRING_ENTRY(AUDIO_CHANNEL_IN_RIGHT_PROCESSED), + STRING_ENTRY(AUDIO_CHANNEL_IN_FRONT_PROCESSED), + STRING_ENTRY(AUDIO_CHANNEL_IN_BACK_PROCESSED), + STRING_ENTRY(AUDIO_CHANNEL_IN_PRESSURE), + STRING_ENTRY(AUDIO_CHANNEL_IN_X_AXIS), + STRING_ENTRY(AUDIO_CHANNEL_IN_Y_AXIS), + STRING_ENTRY(AUDIO_CHANNEL_IN_Z_AXIS), + STRING_ENTRY(AUDIO_CHANNEL_IN_VOICE_UPLINK), + STRING_ENTRY(AUDIO_CHANNEL_IN_VOICE_DNLINK), + STRING_ENTRY(AUDIO_CHANNEL_IN_MONO), + STRING_ENTRY(AUDIO_CHANNEL_IN_STEREO), + STRING_ENTRY(AUDIO_CHANNEL_IN_5POINT1), + STRING_ENTRY(AUDIO_CHANNEL_IN_VOICE_UPLINK_MONO), + STRING_ENTRY(AUDIO_CHANNEL_IN_VOICE_DNLINK_MONO), + STRING_ENTRY(AUDIO_CHANNEL_IN_VOICE_CALL_MONO), + STRING_ENTRY(AUDIO_CHANNEL_IN_ALL), + { 0, NULL } +}; + +/* Formats */ +static struct string_conversion string_conversion_table_format[] = { + STRING_ENTRY(AUDIO_FORMAT_DEFAULT), + STRING_ENTRY(AUDIO_FORMAT_PCM), + STRING_ENTRY(AUDIO_FORMAT_MP3), + STRING_ENTRY(AUDIO_FORMAT_AMR_NB), + STRING_ENTRY(AUDIO_FORMAT_AMR_WB), + STRING_ENTRY(AUDIO_FORMAT_AAC), + STRING_ENTRY(AUDIO_FORMAT_HE_AAC_V1), + STRING_ENTRY(AUDIO_FORMAT_HE_AAC_V2), + STRING_ENTRY(AUDIO_FORMAT_VORBIS), + STRING_ENTRY(AUDIO_FORMAT_EVRC), + STRING_ENTRY(AUDIO_FORMAT_QCELP), + STRING_ENTRY(AUDIO_FORMAT_AC3), + STRING_ENTRY(AUDIO_FORMAT_AC3_PLUS), + STRING_ENTRY(AUDIO_FORMAT_DTS), + STRING_ENTRY(AUDIO_FORMAT_WMA), + STRING_ENTRY(AUDIO_FORMAT_WMA_PRO), + STRING_ENTRY(AUDIO_FORMAT_AAC_ADIF), + STRING_ENTRY(AUDIO_FORMAT_EVRCB), + STRING_ENTRY(AUDIO_FORMAT_EVRCWB), + STRING_ENTRY(AUDIO_FORMAT_EAC3), + STRING_ENTRY(AUDIO_FORMAT_DTS_LBR), + STRING_ENTRY(AUDIO_FORMAT_AMR_WB_PLUS), + /* Currently we support only PCM formats, but keep all formats + * here so audio_policy.conf can be parsed. */ + STRING_ENTRY(AUDIO_FORMAT_PCM_16_BIT), + STRING_ENTRY(AUDIO_FORMAT_PCM_8_BIT), + STRING_ENTRY(AUDIO_FORMAT_PCM_32_BIT), + STRING_ENTRY(AUDIO_FORMAT_PCM_8_24_BIT), + { 0, NULL } +}; +#undef STRING_ENTRY + +#endif diff --git a/src/droid/droid-util-42.h b/src/droid/droid-util-42.h new file mode 100644 index 0000000..9ae54fb --- /dev/null +++ b/src/droid/droid-util-42.h @@ -0,0 +1,273 @@ +/* + * Copyright (C) 2013 Jolla Ltd. + * + * Contact: Juho Hämäläinen + * + * These PulseAudio Modules are free software; you can redistribute + * it and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation + * version 2.1 of the License. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA. + */ + +#ifndef _ANDROID_UTIL_V42_H_ +#define _ANDROID_UTIL_V42_H_ + +#define HAL_V2 + +// PulseAudio value - Android value + +static uint32_t conversion_table_output_channel[][2] = { + { PA_CHANNEL_POSITION_MONO, AUDIO_CHANNEL_OUT_MONO }, + { PA_CHANNEL_POSITION_FRONT_LEFT, AUDIO_CHANNEL_OUT_FRONT_LEFT }, + { PA_CHANNEL_POSITION_FRONT_RIGHT, AUDIO_CHANNEL_OUT_FRONT_RIGHT}, + { PA_CHANNEL_POSITION_FRONT_CENTER, AUDIO_CHANNEL_OUT_FRONT_CENTER }, + { PA_CHANNEL_POSITION_SUBWOOFER, AUDIO_CHANNEL_OUT_LOW_FREQUENCY }, + { PA_CHANNEL_POSITION_REAR_LEFT, AUDIO_CHANNEL_OUT_BACK_LEFT }, + { PA_CHANNEL_POSITION_REAR_RIGHT, AUDIO_CHANNEL_OUT_BACK_RIGHT }, + { PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER, AUDIO_CHANNEL_OUT_FRONT_LEFT_OF_CENTER }, + { PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER, AUDIO_CHANNEL_OUT_FRONT_RIGHT_OF_CENTER }, + { PA_CHANNEL_POSITION_REAR_CENTER, AUDIO_CHANNEL_OUT_BACK_CENTER }, + { PA_CHANNEL_POSITION_SIDE_LEFT, AUDIO_CHANNEL_OUT_SIDE_LEFT }, + { PA_CHANNEL_POSITION_SIDE_RIGHT, AUDIO_CHANNEL_OUT_SIDE_RIGHT }, + { PA_CHANNEL_POSITION_TOP_CENTER, AUDIO_CHANNEL_OUT_TOP_CENTER }, + { PA_CHANNEL_POSITION_TOP_FRONT_LEFT, AUDIO_CHANNEL_OUT_TOP_FRONT_LEFT }, + { PA_CHANNEL_POSITION_TOP_FRONT_CENTER, AUDIO_CHANNEL_OUT_TOP_FRONT_CENTER }, + { PA_CHANNEL_POSITION_TOP_FRONT_RIGHT, AUDIO_CHANNEL_OUT_TOP_FRONT_RIGHT }, + { PA_CHANNEL_POSITION_TOP_REAR_LEFT, AUDIO_CHANNEL_OUT_TOP_BACK_LEFT }, + { PA_CHANNEL_POSITION_TOP_REAR_CENTER, AUDIO_CHANNEL_OUT_TOP_BACK_CENTER }, + { PA_CHANNEL_POSITION_TOP_REAR_RIGHT, AUDIO_CHANNEL_OUT_TOP_BACK_RIGHT } +}; + +static uint32_t conversion_table_input_channel[][2] = { + { PA_CHANNEL_POSITION_MONO, AUDIO_CHANNEL_IN_MONO }, + { PA_CHANNEL_POSITION_FRONT_LEFT, AUDIO_CHANNEL_IN_LEFT }, + { PA_CHANNEL_POSITION_FRONT_RIGHT, AUDIO_CHANNEL_IN_RIGHT}, + { PA_CHANNEL_POSITION_FRONT_CENTER, AUDIO_CHANNEL_IN_FRONT }, + { PA_CHANNEL_POSITION_REAR_CENTER, AUDIO_CHANNEL_IN_BACK }, + /* Following are missing suitable counterparts on PulseAudio side. */ + { AUDIO_CHANNEL_IN_LEFT_PROCESSED, AUDIO_CHANNEL_IN_LEFT_PROCESSED }, + { AUDIO_CHANNEL_IN_RIGHT_PROCESSED, AUDIO_CHANNEL_IN_RIGHT_PROCESSED }, + { AUDIO_CHANNEL_IN_FRONT_PROCESSED, AUDIO_CHANNEL_IN_FRONT_PROCESSED }, + { AUDIO_CHANNEL_IN_BACK_PROCESSED, AUDIO_CHANNEL_IN_BACK_PROCESSED }, + { AUDIO_CHANNEL_IN_PRESSURE, AUDIO_CHANNEL_IN_PRESSURE }, + { AUDIO_CHANNEL_IN_X_AXIS, AUDIO_CHANNEL_IN_X_AXIS }, + { AUDIO_CHANNEL_IN_Y_AXIS, AUDIO_CHANNEL_IN_Y_AXIS }, + { AUDIO_CHANNEL_IN_Z_AXIS, AUDIO_CHANNEL_IN_Z_AXIS }, + { AUDIO_CHANNEL_IN_VOICE_UPLINK, AUDIO_CHANNEL_IN_VOICE_UPLINK }, + { AUDIO_CHANNEL_IN_VOICE_DNLINK, AUDIO_CHANNEL_IN_VOICE_DNLINK } +}; + +static uint32_t conversion_table_format[][2] = { + { PA_SAMPLE_U8, AUDIO_FORMAT_PCM_8_BIT }, + { PA_SAMPLE_S16LE, AUDIO_FORMAT_PCM_16_BIT }, + { PA_SAMPLE_S32LE, AUDIO_FORMAT_PCM_32_BIT }, + { PA_SAMPLE_S24LE, AUDIO_FORMAT_PCM_8_24_BIT } +}; + +struct string_conversion { + uint32_t value; + const char *str; +}; + +#if defined(STRING_ENTRY) +#error STRING_ENTRY already defined somewhere, fix this lib. +#endif +#define STRING_ENTRY(str) { str, #str } +/* Output devices */ +static struct string_conversion string_conversion_table_output_device[] = { + STRING_ENTRY(AUDIO_DEVICE_OUT_EARPIECE), + STRING_ENTRY(AUDIO_DEVICE_OUT_SPEAKER), + STRING_ENTRY(AUDIO_DEVICE_OUT_WIRED_HEADSET), + STRING_ENTRY(AUDIO_DEVICE_OUT_WIRED_HEADPHONE), + STRING_ENTRY(AUDIO_DEVICE_OUT_BLUETOOTH_SCO), + STRING_ENTRY(AUDIO_DEVICE_OUT_BLUETOOTH_SCO_HEADSET), + STRING_ENTRY(AUDIO_DEVICE_OUT_BLUETOOTH_SCO_CARKIT), + STRING_ENTRY(AUDIO_DEVICE_OUT_BLUETOOTH_A2DP), + STRING_ENTRY(AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES), + STRING_ENTRY(AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER), + STRING_ENTRY(AUDIO_DEVICE_OUT_AUX_DIGITAL), + STRING_ENTRY(AUDIO_DEVICE_OUT_ANLG_DOCK_HEADSET), + STRING_ENTRY(AUDIO_DEVICE_OUT_DGTL_DOCK_HEADSET), + STRING_ENTRY(AUDIO_DEVICE_OUT_USB_ACCESSORY), + STRING_ENTRY(AUDIO_DEVICE_OUT_USB_DEVICE), + STRING_ENTRY(AUDIO_DEVICE_OUT_REMOTE_SUBMIX), + STRING_ENTRY(AUDIO_DEVICE_OUT_DEFAULT), + STRING_ENTRY(AUDIO_DEVICE_OUT_ALL), + STRING_ENTRY(AUDIO_DEVICE_OUT_ALL_A2DP), + STRING_ENTRY(AUDIO_DEVICE_OUT_ALL_SCO), + STRING_ENTRY(AUDIO_DEVICE_OUT_ALL_USB), + { 0, NULL } +}; + +static struct string_conversion string_conversion_table_output_device_fancy[] = { + { AUDIO_DEVICE_OUT_EARPIECE, "output-earpiece" }, + { AUDIO_DEVICE_OUT_SPEAKER, "output-speaker" }, + { AUDIO_DEVICE_OUT_WIRED_HEADSET, "output-wired_headset" }, + { AUDIO_DEVICE_OUT_WIRED_HEADPHONE, "output-wired_headphone" }, + { AUDIO_DEVICE_OUT_BLUETOOTH_SCO, "output-bluetooth_sco" }, + { AUDIO_DEVICE_OUT_BLUETOOTH_SCO_HEADSET, "output-sco_headset" }, + { AUDIO_DEVICE_OUT_BLUETOOTH_SCO_CARKIT, "output-sco_carkit" }, + { AUDIO_DEVICE_OUT_BLUETOOTH_A2DP, "output-a2dp" }, + { AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES, "output-a2dp_headphones" }, + { AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER, "output-a2dp_speaker" }, + { AUDIO_DEVICE_OUT_AUX_DIGITAL, "output-aux_digital" }, + { AUDIO_DEVICE_OUT_ANLG_DOCK_HEADSET, "output-analog_dock_headset" }, + { AUDIO_DEVICE_OUT_DGTL_DOCK_HEADSET, "output-digital_dock_headset" }, + { AUDIO_DEVICE_OUT_USB_ACCESSORY, "output-usb_accessory" }, + { AUDIO_DEVICE_OUT_USB_DEVICE, "output-usb_device" }, + { AUDIO_DEVICE_OUT_REMOTE_SUBMIX, "output-remote_submix" }, + { 0, NULL } +}; + +/* Input devices */ +#ifdef MAKO +static struct string_conversion string_conversion_table_input_device[] = { + { 0x10000, "AUDIO_DEVICE_IN_COMMUNICATION" }, + { 0x20000, "AUDIO_DEVICE_IN_AMBIENT" }, + { 0x40000, "AUDIO_DEVICE_IN_BUILTIN_MIC" }, + { 0x80000, "AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET" }, + { 0x100000, "AUDIO_DEVICE_IN_WIRED_HEADSET" }, + { 0x200000, "AUDIO_DEVICE_IN_AUX_DIGITAL" }, + { 0x400000, "AUDIO_DEVICE_IN_VOICE_CALL" }, + { 0x800000, "AUDIO_DEVICE_IN_BACK_MIC" }, + { 0x80000000, "AUDIO_DEVICE_IN_DEFAULT" }, + { 0x80000000, "AUDIO_DEVICE_IN_REMOTE_SUBMIX" }, // What's this really?? + { 0, NULL } +}; + +static struct string_conversion string_conversion_table_input_device_fancy[] = { + { 0x10000, "input-communication" }, + { 0x20000, "input-ambient" }, + { 0x40000, "input-builtin_mic" }, + { 0x80000, "input-bluetooth_sco_headset" }, + { 0x100000, "input-wired_headset" }, + { 0x200000, "input-aux_digital" }, + { 0x400000, "input-voice_call" }, + { 0x800000, "input-back_mic" }, + { 0x80000000, "input-remote_submix" }, + { 0, NULL } +}; +#else +static struct string_conversion string_conversion_table_input_device[] = { + STRING_ENTRY(AUDIO_DEVICE_IN_COMMUNICATION), + STRING_ENTRY(AUDIO_DEVICE_IN_AMBIENT), + STRING_ENTRY(AUDIO_DEVICE_IN_BUILTIN_MIC), + STRING_ENTRY(AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET), + STRING_ENTRY(AUDIO_DEVICE_IN_WIRED_HEADSET), + STRING_ENTRY(AUDIO_DEVICE_IN_AUX_DIGITAL), + STRING_ENTRY(AUDIO_DEVICE_IN_VOICE_CALL), + STRING_ENTRY(AUDIO_DEVICE_IN_BACK_MIC), + STRING_ENTRY(AUDIO_DEVICE_IN_REMOTE_SUBMIX), + STRING_ENTRY(AUDIO_DEVICE_IN_ANLG_DOCK_HEADSET), + STRING_ENTRY(AUDIO_DEVICE_IN_DGTL_DOCK_HEADSET), + STRING_ENTRY(AUDIO_DEVICE_IN_USB_ACCESSORY), + STRING_ENTRY(AUDIO_DEVICE_IN_USB_DEVICE), + { 0, NULL } +}; + +static struct string_conversion string_conversion_table_input_device_fancy[] = { + { AUDIO_DEVICE_IN_COMMUNICATION, "input-communication" }, + { AUDIO_DEVICE_IN_AMBIENT, "input-ambient" }, + { AUDIO_DEVICE_IN_BUILTIN_MIC, "input-builtin_mic" }, + { AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET, "input-bluetooth_sco_headset" }, + { AUDIO_DEVICE_IN_WIRED_HEADSET, "input-wired_headset" }, + { AUDIO_DEVICE_IN_AUX_DIGITAL, "input-aux_digital" }, + { AUDIO_DEVICE_IN_VOICE_CALL, "input-voice_call" }, + { AUDIO_DEVICE_IN_BACK_MIC, "input-back_mic" }, + { AUDIO_DEVICE_IN_REMOTE_SUBMIX, "input-remote_submix" }, + { 0, NULL } +}; +#endif + +/* Flags */ +static struct string_conversion string_conversion_table_flag[] = { + STRING_ENTRY(AUDIO_OUTPUT_FLAG_NONE), + STRING_ENTRY(AUDIO_OUTPUT_FLAG_DIRECT), + STRING_ENTRY(AUDIO_OUTPUT_FLAG_PRIMARY), + STRING_ENTRY(AUDIO_OUTPUT_FLAG_FAST), + STRING_ENTRY(AUDIO_OUTPUT_FLAG_DEEP_BUFFER), + { 0, NULL } +}; + +/* Channels */ +static struct string_conversion string_conversion_table_output_channels[] = { + STRING_ENTRY(AUDIO_CHANNEL_OUT_FRONT_LEFT), + STRING_ENTRY(AUDIO_CHANNEL_OUT_FRONT_RIGHT), + STRING_ENTRY(AUDIO_CHANNEL_OUT_FRONT_CENTER), + STRING_ENTRY(AUDIO_CHANNEL_OUT_LOW_FREQUENCY), + STRING_ENTRY(AUDIO_CHANNEL_OUT_BACK_LEFT), + STRING_ENTRY(AUDIO_CHANNEL_OUT_BACK_RIGHT), + STRING_ENTRY(AUDIO_CHANNEL_OUT_FRONT_LEFT_OF_CENTER), + STRING_ENTRY(AUDIO_CHANNEL_OUT_FRONT_RIGHT_OF_CENTER), + STRING_ENTRY(AUDIO_CHANNEL_OUT_BACK_CENTER), + STRING_ENTRY(AUDIO_CHANNEL_OUT_SIDE_LEFT), + STRING_ENTRY(AUDIO_CHANNEL_OUT_SIDE_RIGHT), + STRING_ENTRY(AUDIO_CHANNEL_OUT_TOP_CENTER), + STRING_ENTRY(AUDIO_CHANNEL_OUT_TOP_FRONT_LEFT), + STRING_ENTRY(AUDIO_CHANNEL_OUT_TOP_FRONT_CENTER), + STRING_ENTRY(AUDIO_CHANNEL_OUT_TOP_FRONT_RIGHT), + STRING_ENTRY(AUDIO_CHANNEL_OUT_TOP_BACK_LEFT), + STRING_ENTRY(AUDIO_CHANNEL_OUT_TOP_BACK_CENTER), + STRING_ENTRY(AUDIO_CHANNEL_OUT_TOP_BACK_RIGHT), + STRING_ENTRY(AUDIO_CHANNEL_OUT_MONO), + STRING_ENTRY(AUDIO_CHANNEL_OUT_STEREO), + STRING_ENTRY(AUDIO_CHANNEL_OUT_QUAD), + STRING_ENTRY(AUDIO_CHANNEL_OUT_SURROUND), + STRING_ENTRY(AUDIO_CHANNEL_OUT_5POINT1), + STRING_ENTRY(AUDIO_CHANNEL_OUT_7POINT1), + STRING_ENTRY(AUDIO_CHANNEL_OUT_ALL), + { 0, NULL } +}; +static struct string_conversion string_conversion_table_input_channels[] = { + STRING_ENTRY(AUDIO_CHANNEL_IN_LEFT), + STRING_ENTRY(AUDIO_CHANNEL_IN_RIGHT), + STRING_ENTRY(AUDIO_CHANNEL_IN_FRONT), + STRING_ENTRY(AUDIO_CHANNEL_IN_BACK), + STRING_ENTRY(AUDIO_CHANNEL_IN_LEFT_PROCESSED), + STRING_ENTRY(AUDIO_CHANNEL_IN_RIGHT_PROCESSED), + STRING_ENTRY(AUDIO_CHANNEL_IN_FRONT_PROCESSED), + STRING_ENTRY(AUDIO_CHANNEL_IN_BACK_PROCESSED), + STRING_ENTRY(AUDIO_CHANNEL_IN_PRESSURE), + STRING_ENTRY(AUDIO_CHANNEL_IN_X_AXIS), + STRING_ENTRY(AUDIO_CHANNEL_IN_Y_AXIS), + STRING_ENTRY(AUDIO_CHANNEL_IN_Z_AXIS), + STRING_ENTRY(AUDIO_CHANNEL_IN_VOICE_UPLINK), + STRING_ENTRY(AUDIO_CHANNEL_IN_VOICE_DNLINK), + STRING_ENTRY(AUDIO_CHANNEL_IN_MONO), + STRING_ENTRY(AUDIO_CHANNEL_IN_STEREO), + STRING_ENTRY(AUDIO_CHANNEL_IN_ALL), + { 0, NULL } +}; + +/* Formats */ +static struct string_conversion string_conversion_table_format[] = { + STRING_ENTRY(AUDIO_FORMAT_DEFAULT), + STRING_ENTRY(AUDIO_FORMAT_PCM), + STRING_ENTRY(AUDIO_FORMAT_MP3), + STRING_ENTRY(AUDIO_FORMAT_AMR_NB), + STRING_ENTRY(AUDIO_FORMAT_AMR_WB), + STRING_ENTRY(AUDIO_FORMAT_AAC), + STRING_ENTRY(AUDIO_FORMAT_HE_AAC_V1), + STRING_ENTRY(AUDIO_FORMAT_HE_AAC_V2), + STRING_ENTRY(AUDIO_FORMAT_VORBIS), + STRING_ENTRY(AUDIO_FORMAT_MAIN_MASK), + STRING_ENTRY(AUDIO_FORMAT_SUB_MASK), + STRING_ENTRY(AUDIO_FORMAT_PCM_16_BIT), + STRING_ENTRY(AUDIO_FORMAT_PCM_8_BIT), + STRING_ENTRY(AUDIO_FORMAT_PCM_32_BIT), + STRING_ENTRY(AUDIO_FORMAT_PCM_8_24_BIT), + { 0, NULL } +}; +#undef STRING_ENTRY + +#endif diff --git a/src/droid/droid-util.c b/src/droid/droid-util.c new file mode 100644 index 0000000..2174081 --- /dev/null +++ b/src/droid/droid-util.c @@ -0,0 +1,1028 @@ +/* + * Copyright (C) 2013 Jolla Ltd. + * + * Contact: Juho Hämäläinen + * + * These PulseAudio Modules are free software; you can redistribute + * it and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation + * version 2.1 of the License. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#ifdef HAVE_VALGRIND_MEMCHECK_H +#include +#endif + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "droid-util.h" + +#include + +#ifndef ANDROID_VERSION_MAJOR +#error "ANDROID_VERSION_* not defined." +#endif + +#if ANDROID_VERSION_MAJOR == 4 && ANDROID_VERSION_MINOR == 1 +#include "droid-util-41qc.h" +#elif ANDROID_VERSION_MAJOR == 4 && ANDROID_VERSION_MINOR == 2 +#include "droid-util-42.h" +#else +#error "No valid ANDROID_VERSION found." +#endif + +#define CONVERT_FUNC(TABL) \ +pa_bool_t pa_convert_ ## TABL (uint32_t value, pa_conversion_field_t field, uint32_t *to_value) { \ + for (unsigned int i = 0; i < sizeof( conversion_table_ ## TABL )/(sizeof(uint32_t)*2); i++) { \ + if ( conversion_table_ ## TABL [i][field] == value) { \ + *to_value = conversion_table_ ## TABL [i][!field]; \ + return TRUE; \ + } \ + } \ + return FALSE; \ +} struct __funny_extra_to_allow_semicolon + +/* Creates convert_format convert_channel etc. + * pa_bool_t pa_convert_func(uint32_t value, pa_conversion_field_t field, uint32_t *to_value); + * return TRUE if conversion succesful */ +CONVERT_FUNC(format); +CONVERT_FUNC(output_channel); +CONVERT_FUNC(input_channel); + + +static pa_bool_t string_convert_num_to_str(const struct string_conversion *list, const uint32_t value, const char **to_str) { + pa_assert(list); + pa_assert(to_str); + + for (unsigned int i = 0; list[i].str; i++) { + if (list[i].value == value) { + *to_str = list[i].str; + return TRUE; + } + } + return FALSE; +} + +static pa_bool_t string_convert_str_to_num(const struct string_conversion *list, const char *str, uint32_t *to_value) { + pa_assert(list); + pa_assert(str); + pa_assert(to_value); + + for (unsigned int i = 0; list[i].str; i++) { + if (pa_streq(list[i].str, str)) { + *to_value = list[i].value; + return TRUE; + } + } + return FALSE; +} + +static char *list_string(struct string_conversion *list, uint32_t flags) { + char *str = NULL; + char *tmp; + +#ifdef HAL_V2 + if (flags & AUDIO_DEVICE_BIT_IN) + flags &= ~AUDIO_DEVICE_BIT_IN; +#endif + + for (unsigned int i = 0; list[i].str; i++) { +#ifdef HAL_V2 + if (list[i].value & AUDIO_DEVICE_BIT_IN && + popcount(list[i].value & ~AUDIO_DEVICE_BIT_IN)) + continue; +#else + if (popcount(list[i].value) != 1) + continue; +#endif + + if (flags & list[i].value) { + if (str) { + tmp = pa_sprintf_malloc("%s|%s", str, list[i].str); + pa_xfree(str); + str = tmp; + } else { + str = pa_sprintf_malloc("%s", list[i].str); + } + } + } + + return str; +} + + +/* Output device */ +pa_bool_t pa_string_convert_output_device_num_to_str(audio_devices_t value, const char **to_str) { + return string_convert_num_to_str(string_conversion_table_output_device, (uint32_t) value, to_str); +} + +pa_bool_t pa_string_convert_output_device_str_to_num(const char *str, audio_devices_t *to_value) { + return string_convert_str_to_num(string_conversion_table_output_device, str, (uint32_t*) to_value); +} + +char *pa_list_string_output_device(audio_devices_t devices) { + return list_string(string_conversion_table_output_device, devices); +} + +/* Input device */ +pa_bool_t pa_string_convert_input_device_num_to_str(audio_devices_t value, const char **to_str) { + return string_convert_num_to_str(string_conversion_table_input_device, (uint32_t) value, to_str); +} + +pa_bool_t pa_string_convert_input_device_str_to_num(const char *str, audio_devices_t *to_value) { + return string_convert_str_to_num(string_conversion_table_input_device, str, (uint32_t*) to_value); +} + +char *pa_list_string_input_device(audio_devices_t devices) { + return list_string(string_conversion_table_input_device, devices); +} + +/* Flags */ +pa_bool_t pa_string_convert_flag_num_to_str(audio_output_flags_t value, const char **to_str) { + return string_convert_num_to_str(string_conversion_table_flag, (uint32_t) value, to_str); +} + +pa_bool_t pa_string_convert_flag_str_to_num(const char *str, audio_output_flags_t *to_value) { + return string_convert_str_to_num(string_conversion_table_flag, str, (uint32_t*) to_value); +} + +char *pa_list_string_flags(audio_output_flags_t flags) { + return list_string(string_conversion_table_flag, flags); +} + +/* Config parser */ + +#define WHITESPACE "\n\r \t" + +static int parse_list(const struct string_conversion *table, const char *str, uint32_t *dst) { + int count = 0; + char *entry; + const char *state = NULL; + + pa_assert(table); + pa_assert(str); + pa_assert(dst); + + *dst = 0; + + while ((entry = pa_split(str, "|", &state))) { + uint32_t d = 0; + + if (!string_convert_str_to_num(table, entry, &d)) { + pa_log("Unknown entry %s", entry); + pa_xfree(entry); + return -1; + } + + *dst |= d; + count++; + + pa_xfree(entry); + } + + return count; +} + +static pa_bool_t parse_sampling_rates(const char *str, uint32_t sampling_rates[32]) { + pa_assert(str); + + char *entry; + const char *state = NULL; + + uint32_t pos = 0; + while ((entry = pa_split(str, "|", &state))) { + int32_t val; + + if (pos == AUDIO_MAX_SAMPLING_RATES) { + pa_log("Too many sample rate entries (> %d)", AUDIO_MAX_SAMPLING_RATES); + pa_xfree(entry); + return FALSE; + } + + if (pa_atoi(entry, &val) < 0) { + pa_log("Bad sample rate value %s", entry); + pa_xfree(entry); + return FALSE; + } + + sampling_rates[pos++] = val; + + pa_xfree(entry); + + } + + sampling_rates[pos] = 0; + + return TRUE; +} + +static pa_bool_t parse_formats(const char *str, audio_format_t *formats) { + pa_assert(str); + pa_assert(formats); + + return parse_list(string_conversion_table_format, str, formats) > 0; +} + +static int parse_channels(const char *str, pa_bool_t in_output, audio_channel_mask_t *channels) { + pa_assert(str); + pa_assert(channels); + + /* Needs to be probed later */ + if (pa_streq(str, "dynamic")) { + *channels = 0; + return TRUE; + } + + if (in_output) + return parse_list(string_conversion_table_output_channels, str, channels); + else + return parse_list(string_conversion_table_input_channels, str, channels); +} + +static pa_bool_t parse_devices(const char *str, pa_bool_t in_output, audio_devices_t *devices) { + pa_assert(str); + pa_assert(devices); + + if (in_output) + return parse_list(string_conversion_table_output_device, str, devices) > 0; + else + return parse_list(string_conversion_table_input_device, str, devices) > 0; +} + +static pa_bool_t parse_flags(const char *str, audio_output_flags_t *flags) { + pa_assert(str); + pa_assert(flags); + + return parse_list(string_conversion_table_flag, str, flags) > 0; +} + +pa_bool_t pa_parse_droid_audio_config(const char *filename, pa_droid_config_audio *config) { + FILE *f; + int n = 0; + pa_bool_t ret = TRUE; + + enum config_loc { + IN_ROOT = 0, + IN_GLOBAL = 1, + IN_HW_MODULES = 1, + IN_MODULE = 2, + IN_OUTPUT_INPUT = 3, + IN_CONFIG = 4 + } loc = IN_ROOT; + + + pa_bool_t in_global = FALSE; + pa_bool_t in_output = TRUE; + + pa_droid_config_hw_module *module = NULL; + pa_droid_config_output *output = NULL; + pa_droid_config_input *input = NULL; + + pa_assert(filename); + pa_assert(config); + + memset(config, 0, sizeof(pa_droid_config_audio)); + + f = fopen(filename, "r"); + + if (!f) { + pa_log_info("Failed to open config file (%s): %s", filename, pa_cstrerror(errno)); + ret = FALSE; + goto finish; + } + + pa_lock_fd(fileno(f), 1); + + while (!feof(f)) { + char ln[512]; + char *d, *v, *val; + + if (!fgets(ln, sizeof(ln), f)) + break; + + n++; + + pa_strip_nl(ln); + + if (ln[0] == '#' || !*ln ) + continue; + + /* Enter section */ + if (ln[strlen(ln)-1] == '{') { + d = ln+strspn(ln, WHITESPACE); + v = d; + d = v+strcspn(v, WHITESPACE); + d[0] = '\0'; + + if (!*v) { + pa_log(__FILE__ ": [%s:%u] failed to parse line - too few words", filename, n); + goto finish; + } + + switch (loc) { + case IN_ROOT: + if (pa_streq(v, GLOBAL_CONFIG_TAG)) { + in_global = TRUE; + loc = IN_GLOBAL; + } + else if (pa_streq(v, AUDIO_HW_MODULE_TAG)) + loc = IN_HW_MODULES; + else { + pa_log(__FILE__ ": [%s:%u] failed to parse line - unknown field (%s)", filename, n, v); + ret = FALSE; + goto finish; + } + break; + + case IN_HW_MODULES: + module = &config->hw_modules[config->hw_modules_size]; + config->hw_modules_size++; + strncpy(module->name, v, AUDIO_HARDWARE_MODULE_ID_MAX_LEN); + module->config = config; + loc = IN_MODULE; + pa_log_debug("config: New module: %s", module->name); + break; + + case IN_MODULE: + if (pa_streq(v, OUTPUTS_TAG)) { + loc = IN_OUTPUT_INPUT; + in_output = TRUE; + } else if (pa_streq(v, INPUTS_TAG)) { + loc = IN_OUTPUT_INPUT; + in_output = FALSE; + } else { + pa_log(__FILE__ ": [%s:%u] failed to parse line - unknown field (%s)", filename, n, v); + ret = FALSE; + goto finish; + } + break; + + case IN_OUTPUT_INPUT: + pa_assert(module); + + if (in_output) { + output = &module->outputs[module->outputs_size]; + module->outputs_size++; + strncpy(output->name, v, AUDIO_HARDWARE_MODULE_ID_MAX_LEN); + output->module = module; + loc = IN_CONFIG; + pa_log_debug("config: %s: New output: %s", module->name, output->name); + } else { + input = &module->inputs[module->inputs_size]; + module->inputs_size++; + strncpy(input->name, v, AUDIO_HARDWARE_MODULE_ID_MAX_LEN); + input->module = module; + loc = IN_CONFIG; + pa_log_debug("config: %s: New input: %s", module->name, input->name); + } + break; + + case IN_CONFIG: + pa_log(__FILE__ ": [%s:%u] failed to parse line - unknown field in config (%s)", filename, n, v); + ret = FALSE; + goto finish; + } + + continue; + } + + /* Exit section */ + if (ln[strlen(ln)-1] == '}') { + if (loc == IN_ROOT) { + pa_log(__FILE__ ": [%s:%u] failed to parse line - extra closing bracket", filename, n); + ret = FALSE; + goto finish; + } + + loc--; + if (loc == IN_MODULE) { + if (in_output) + output = NULL; + else + input = NULL; + } + if (loc == IN_ROOT) + module = NULL; + + in_global = FALSE; + + continue; + } + + /* Parse global configuration */ + if (in_global) { + pa_bool_t success = FALSE; + + d = ln+strspn(ln, WHITESPACE); + v = d; + d = v+strcspn(v, WHITESPACE); + + val = d+strspn(d, WHITESPACE); + d[0] = '\0'; + d = val+strcspn(val, WHITESPACE); + d[0] = '\0'; + + if (pa_streq(v, ATTACHED_OUTPUT_DEVICES_TAG)) + success = parse_devices(val, TRUE, &config->global_config.attached_output_devices); + else if (pa_streq(v, DEFAULT_OUTPUT_DEVICE_TAG)) + success = parse_devices(val, TRUE, &config->global_config.default_output_device); + else if (pa_streq(v, ATTACHED_INPUT_DEVICES_TAG)) + success = parse_devices(val, FALSE, &config->global_config.attached_input_devices); + else { + pa_log(__FILE__ ": [%s:%u] failed to parse line - unknown config entry %s", filename, n, v); + success = FALSE; + } + + if (!success) { + ret = FALSE; + goto finish; + } + } + + /* Parse per-output or per-input configuration */ + if (loc == IN_CONFIG) { + pa_bool_t success = FALSE; + + pa_assert(module); + + d = ln+strspn(ln, WHITESPACE); + v = d; + d = v+strcspn(v, WHITESPACE); + + val = d+strspn(d, WHITESPACE); + d[0] = '\0'; + d = val+strcspn(val, WHITESPACE); + d[0] = '\0'; + + + if ((in_output && !output) || (!in_output && !input)) { + pa_log(__FILE__ ": [%s:%u] failed to parse line", filename, n); + ret = FALSE; + goto finish; + } + + if (pa_streq(v, SAMPLING_RATES_TAG)) + success = parse_sampling_rates(val, in_output ? output->sampling_rates : input->sampling_rates); + else if (pa_streq(v, FORMATS_TAG)) + success = parse_formats(val, in_output ? &output->formats : &input->formats); + else if (pa_streq(v, CHANNELS_TAG)) { + if (in_output) + success = (parse_channels(val, TRUE, &output->channel_masks) > 0); + else + success = (parse_channels(val, FALSE, &input->channel_masks) > 0); + } else if (pa_streq(v, DEVICES_TAG)) { + if (in_output) + success = parse_devices(val, TRUE, &output->devices); + else + success = parse_devices(val, FALSE, &input->devices); + } else if (pa_streq(v, FLAGS_TAG)) { + if (in_output) + success = parse_flags(val, &output->flags); + else { + pa_log(__FILE__ ": [%s:%u] failed to parse line - output flags inside input definition", filename, n); + success = FALSE; + } + } else { + pa_log(__FILE__ ": [%s:%u] failed to parse line - unknown config entry %s", filename, n, v); + success = FALSE; + } + + if (!success) { + ret = FALSE; + goto finish; + } + } + } + + pa_log_info("Parsed config file (%s): %u modules.", filename, config->hw_modules_size); + +finish: + if (f) { + pa_lock_fd(fileno(f), 0); + fclose(f); + } + + return ret; +} + + +const pa_droid_config_output *pa_droid_config_find_output(const pa_droid_config_hw_module *module, const char *name) { + pa_assert(module); + pa_assert(name); + + for (unsigned i = 0; i < module->outputs_size; i++) { + if (pa_streq(name, module->outputs[i].name)) + return &module->outputs[i]; + } + + return NULL; +} + +const pa_droid_config_input *pa_droid_config_find_input(const pa_droid_config_hw_module *module, const char *name) { + pa_assert(module); + pa_assert(name); + + for (unsigned i = 0; i < module->inputs_size; i++) { + if (pa_streq(name, module->inputs[i].name)) + return &module->inputs[i]; + } + + return NULL; +} + +const pa_droid_config_hw_module *pa_droid_config_find_module(const pa_droid_config_audio *config, const char* module_id) { + pa_assert(config); + pa_assert(module_id); + + for (unsigned i = 0; i < config->hw_modules_size; i++) { + if (pa_streq(module_id, config->hw_modules[i].name)) + return &config->hw_modules[i]; + } + + return NULL; +} + +pa_droid_profile *pa_droid_profile_new(pa_droid_profile_set *ps, const pa_droid_config_output *output, const pa_droid_config_input *input) { + pa_droid_profile *p; + + pa_assert(ps); + pa_assert(output); + + p = pa_xnew0(pa_droid_profile, 1); + p->profile_set = ps; + p->module = output->module; + p->name = pa_sprintf_malloc("%s%s%s", output->name, input ? "-" : "", input ? input->name : ""); + p->description = pa_sprintf_malloc("%s output%s%s%s", output->name, + input ? " and " : "", + input ? input->name : "", + input ? " input." : ""); + p->priority = 100; + if (pa_streq(output->name, "primary")) { + p->priority += 100; + + if (input && pa_streq(input->name, "primary")) + p->priority += 100; + } + + if (output) + p->output = pa_droid_mapping_get(ps, PA_DIRECTION_OUTPUT, output); + if (input) + p->input = pa_droid_mapping_get(ps, PA_DIRECTION_INPUT, input); + + pa_hashmap_put(ps->profiles, p->name, p); + + return p; +} + +static void add_profile(pa_droid_profile_set *ps, const pa_droid_config_output *output, const pa_droid_config_input *input) { + pa_droid_profile *ap; + + pa_log_debug("New profile: %s-%s", output->name, input ? input->name : "no input"); + + ap = pa_droid_profile_new(ps, output, input); + + pa_hashmap_put(ps->profiles, ap->name, ap); +} + +pa_droid_profile_set *pa_droid_profile_set_new(const pa_droid_config_hw_module *module) { + pa_droid_profile_set *ps; + + pa_assert(module); + + ps = pa_xnew0(pa_droid_profile_set, 1); + ps->config = module->config; + ps->profiles = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + ps->output_mappings = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + ps->input_mappings = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + ps->all_ports = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + + /* Each distinct hw module output matches one profile. If there are multiple inputs + * combinations are made so that all possible outputs and inputs can be selected. + * So for outputs "primary" and "hdmi" and input "primary" profiles + * "primary-primary" and "hdmi-primary" are created. */ + + for (unsigned o = 0; o < module->outputs_size; o++) { + + if (module->inputs_size > 0) { + for (unsigned i = 0; i < module->inputs_size; i++) { + add_profile(ps, &module->outputs[o], &module->inputs[i]); + } + } else + add_profile(ps, &module->outputs[o], NULL); + } + + return ps; +} + +void pa_droid_mapping_free(pa_droid_mapping *am) { + pa_assert(am); + + pa_xfree(am->name); + pa_proplist_free(am->proplist); + pa_idxset_free(am->ports, NULL, NULL); + pa_xfree(am); +} + +void pa_droid_profile_free(pa_droid_profile *ap) { + pa_assert(ap); + + pa_xfree(ap->name); + pa_xfree(ap->description); + pa_xfree(ap); +} + +static void droid_port_free(pa_droid_port *p) { + pa_assert(p); + + pa_xfree(p->name); + pa_xfree(p->description); + pa_xfree(p); +} + +void pa_droid_profile_set_free(pa_droid_profile_set *ps) { + pa_assert(ps); + + if (ps->output_mappings) { + pa_droid_mapping *am; + + while ((am = pa_hashmap_steal_first(ps->output_mappings))) + pa_droid_mapping_free(am); + + pa_hashmap_free(ps->output_mappings, NULL, NULL); + } + + if (ps->input_mappings) { + pa_droid_mapping *am; + + while ((am = pa_hashmap_steal_first(ps->input_mappings))) + pa_droid_mapping_free(am); + + pa_hashmap_free(ps->input_mappings, NULL, NULL); + } + + if (ps->all_ports) { + pa_droid_port *p; + + while ((p = pa_hashmap_steal_first(ps->all_ports))) + droid_port_free(p); + + pa_hashmap_free(ps->all_ports, NULL, NULL); + } + + if (ps->profiles) { + pa_droid_profile *p; + + while ((p = pa_hashmap_steal_first(ps->profiles))) + pa_droid_profile_free(p); + + pa_hashmap_free(ps->profiles, NULL, NULL); + } + + pa_xfree(ps); +} + +static void add_o_ports(pa_droid_mapping *am) { + pa_droid_port *p; + const char *name; + char *desc; + uint32_t devices; + uint32_t i = 0; + + pa_assert(am); + + devices = am->output->devices; + + while (devices) { + uint32_t cur_device = (1 << i++); + + if (devices & cur_device) { + + pa_droid_output_port_name(cur_device, &name); + + if (!(p = pa_hashmap_get(am->profile_set->all_ports, name))) { + pa_log_debug(" New output port %s", name); + p = pa_xnew0(pa_droid_port, 1); + + p->mapping = am; + p->name = pa_xstrdup(name); + desc = pa_replace(name, "output-", "Output to "); + p->description = pa_replace(desc, "_", " "); + pa_xfree(desc); + p->priority = 100; + p->device = cur_device; + + if (am->profile_set->config->global_config.attached_output_devices & cur_device) + p->priority += 100; + + if (am->profile_set->config->global_config.default_output_device & cur_device) + p->priority += 100; + + pa_hashmap_put(am->profile_set->all_ports, p->name, p); + } else + pa_log_debug(" Output port %s from cache", name); + + pa_idxset_put(am->ports, p, NULL); + + devices &= ~cur_device; + } + } +} + +static void add_i_ports(pa_droid_mapping *am) { + pa_droid_port *p; + const char *name; + char *desc; + uint32_t devices; + uint32_t i = 0; + + pa_assert(am); + + devices = am->input->devices; +#ifdef HAL_V2 + devices &= ~AUDIO_DEVICE_BIT_IN; +#endif + + while (devices) { + uint32_t cur_device = (1 << i++); + + if (devices & cur_device) { + + pa_droid_input_port_name(cur_device, &name); + + if (!(p = pa_hashmap_get(am->profile_set->all_ports, name))) { + pa_log_debug(" New input port %s", name); + p = pa_xnew0(pa_droid_port, 1); + + p->mapping = am; + p->name = pa_xstrdup(name); + desc = pa_replace(name, "input-", "Input from "); + p->description = pa_replace(desc, "_", " "); + pa_xfree(desc); + p->priority = 100; + p->device = cur_device; + + if (am->profile_set->config->global_config.attached_input_devices & cur_device) + p->priority += 100; + + pa_hashmap_put(am->profile_set->all_ports, p->name, p); + } else + pa_log_debug(" Input port %s from cache", name); + + pa_idxset_put(am->ports, p, NULL); + + devices &= ~cur_device; + } + } +} + +pa_droid_mapping *pa_droid_mapping_get(pa_droid_profile_set *ps, pa_direction_t direction, const void *data) { + pa_droid_mapping *am; + pa_hashmap *map; + const char *name; + const pa_droid_config_output *output = NULL; + const pa_droid_config_input *input = NULL; + + if (direction == PA_DIRECTION_OUTPUT) { + output = (pa_droid_config_output *) data; + map = ps->output_mappings; + name = output->name; + } else { + input = (pa_droid_config_input *) data; + map = ps->input_mappings; + name = input->name; + } + + if ((am = pa_hashmap_get(map, name))) { + pa_log_debug(" %s mapping %s from cache", input ? "Input" : "Output", name); + return am; + } + pa_log_debug(" New %s mapping %s", input ? "input" : "output", name); + + am = pa_xnew0(pa_droid_mapping, 1); + am->profile_set = ps; + am->name = pa_xstrdup(name); + am->proplist = pa_proplist_new(); + am->direction = direction; + am->output = output; + am->input = input; + am->ports = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);; + + if (am->direction == PA_DIRECTION_OUTPUT) + add_o_ports(am); + else + add_i_ports(am); + + pa_hashmap_put(map, am->name, am); + + return am; +} + +pa_bool_t pa_droid_output_port_name(audio_devices_t value, const char **to_str) { + return string_convert_num_to_str(string_conversion_table_output_device_fancy, (uint32_t) value, to_str); +} + +pa_bool_t pa_droid_input_port_name(audio_devices_t value, const char **to_str) { + return string_convert_num_to_str(string_conversion_table_input_device_fancy, (uint32_t) value, to_str); +} + +static int add_ports(pa_core *core, pa_card_profile *cp, pa_hashmap *ports, pa_droid_mapping *am, pa_hashmap *extra) { + pa_droid_port *p; + pa_device_port *dp; + pa_droid_port_data *data; + uint32_t idx; + int count = 0; + + pa_log_debug("Ports for %s%s: %s", cp ? "card " : "", am->direction == PA_DIRECTION_OUTPUT ? "output" : "input", am->name); + + PA_IDXSET_FOREACH(p, am->ports, idx) { + if (!(dp = pa_hashmap_get(ports, p->name))) { + pa_log_debug(" New port %s", p->name); + dp = pa_device_port_new(core, p->name, p->description, sizeof(pa_droid_port_data)); + dp->priority = p->priority; + + pa_hashmap_put(ports, dp->name, dp); + dp->profiles = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + + data = PA_DEVICE_PORT_DATA(dp); + data->device = p->device; + } else + pa_log_debug(" Port %s from cache", p->name); + + dp->is_output = p->mapping->direction == PA_DIRECTION_OUTPUT; + dp->is_input = p->mapping->direction == PA_DIRECTION_INPUT; + + if (cp) + pa_hashmap_put(dp->profiles, cp->name, cp); + + count++; + + if (extra) { + pa_hashmap_put(extra, dp->name, dp); + pa_device_port_ref(dp); + } + } + + return count; +} + + +void pa_droid_add_ports(pa_hashmap **p, pa_droid_mapping *am, pa_card *card) { + pa_assert(p); + pa_assert(!*p); + + if (!*p) + *p = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + + add_ports(card->core, NULL, card->ports, am, *p); +} + +void pa_droid_add_card_ports(pa_card_profile *cp, pa_hashmap *ports, pa_droid_mapping *am, pa_core *core) { + pa_assert(cp); + pa_assert(am); + pa_assert(core); + + add_ports(core, cp, ports, am, NULL); +} + +pa_droid_hw_module *pa_droid_hw_module_open(pa_droid_config_audio *config, const char *module_id, void *userdata) { + const pa_droid_config_hw_module *module; + pa_droid_hw_module *hw = NULL; + struct hw_module_t *hwmod = NULL; + audio_hw_device_t *device = NULL; + int ret; + + pa_assert(config); + pa_assert(module_id); + + if (!(module = pa_droid_config_find_module(config, module_id))) { + pa_log("Couldn't find module with id %s", module_id); + goto fail; + } + + hw_get_module_by_class(AUDIO_HARDWARE_MODULE_ID, module->name, (const hw_module_t**) &hwmod); + if (!hwmod) { + pa_log("Failed to get hw module %s.", module->name); + goto fail; + } + + pa_log_debug("Loaded hw module %s", module->name); + + ret = audio_hw_device_open(hwmod, &device); + if (!device) { + pa_log("Failed to open device (errno %d).", ret); + goto fail; + } + + if ((ret = device->init_check(device)) != 0) { + pa_log("Failed init_check() (errno %d)", ret); + goto fail; + } + + hw = pa_xnew0(pa_droid_hw_module, 1); + hw->hwmod = hwmod; + hw->device = device; + hw->config = config; + hw->enabled_module = pa_droid_config_find_module(hw->config, module_id); + hw->module_id = hw->enabled_module->name; + hw->userdata = userdata; + + return hw; + +fail: + if (device) + audio_hw_device_close(device); + + if (hw) + pa_xfree(hw); + + return NULL; +} + +void pa_droid_hw_module_close(pa_droid_hw_module *hw) { + pa_assert(hw); + + /* + if (hw->config) + pa_xfree(hw->config); + */ + + if (hw->device) + audio_hw_device_close(hw->device); + + pa_xfree(hw); +} + +pa_droid_config_audio *pa_droid_config_load(pa_modargs *ma) { + pa_droid_config_audio *config; + const char *config_location; + + pa_assert(ma); + + config = pa_xnew0(pa_droid_config_audio, 1); + + if ((config_location = pa_modargs_get_value(ma, "config", NULL))) { + if (!pa_parse_droid_audio_config(config_location, config)) { + pa_log("Failed to parse configuration from %s", config_location); + goto fail; + } + } else { + config_location = AUDIO_POLICY_VENDOR_CONFIG_FILE; + + if (!pa_parse_droid_audio_config(config_location, config)) { + pa_log_debug("Failed to parse configuration from vendor %s", config_location); + + config_location = AUDIO_POLICY_CONFIG_FILE; + + if (!pa_parse_droid_audio_config(config_location, config)) { + pa_log("Failed to parse configuration from %s", config_location); + goto fail; + } + } + } + + return config; + +fail: + pa_xfree(config); + return NULL; +} diff --git a/src/droid/droid-util.h b/src/droid/droid-util.h new file mode 100644 index 0000000..daa112a --- /dev/null +++ b/src/droid/droid-util.h @@ -0,0 +1,227 @@ +#ifndef foodroidutilfoo +#define foodroidutilfoo + +/* + * Copyright (C) 2013 Jolla Ltd. + * + * Contact: Juho Hämäläinen + * + * These PulseAudio Modules are free software; you can redistribute + * it and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation + * version 2.1 of the License. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif +#include +#include + +#include +#include + +#define PROP_DROID_DEVICES "droid.devices" +#define PROP_DROID_FLAGS "droid.flags" +#define PROP_DROID_HW_MODULE "droid.hw_module" + +typedef struct pa_droid_hw_module pa_droid_hw_module; +typedef void (*common_set_parameters_cb_t)(pa_droid_hw_module *module, const char *str); + +typedef struct pa_droid_config_audio pa_droid_config_audio; +typedef struct pa_droid_config_hw_module pa_droid_config_hw_module; + +struct pa_droid_hw_module { + void *userdata; + + pa_droid_config_audio *config; + const pa_droid_config_hw_module *enabled_module; + + struct hw_module_t *hwmod; + audio_hw_device_t *device; + + const char *module_id; + + uint32_t stream_out_id; + uint32_t stream_in_id; + + /* General functions */ + common_set_parameters_cb_t set_parameters; +}; + +#define AUDIO_MAX_SAMPLING_RATES (32) +#define AUDIO_MAX_HW_MODULES (8) +#define AUDIO_MAX_INPUTS (8) +#define AUDIO_MAX_OUTPUTS (8) + +typedef struct pa_droid_config_global { + audio_devices_t attached_output_devices; + audio_devices_t default_output_device; + audio_devices_t attached_input_devices; +} pa_droid_config_global; + +typedef struct pa_droid_config_output { + const pa_droid_config_hw_module *module; + + char name[AUDIO_HARDWARE_MODULE_ID_MAX_LEN]; + uint32_t sampling_rates[AUDIO_MAX_SAMPLING_RATES]; + audio_channel_mask_t channel_masks; /* 0 -> dynamic */ + audio_format_t formats; + audio_devices_t devices; + audio_output_flags_t flags; +} pa_droid_config_output; + +typedef struct pa_droid_config_input { + const pa_droid_config_hw_module *module; + + char name[AUDIO_HARDWARE_MODULE_ID_MAX_LEN]; + uint32_t sampling_rates[AUDIO_MAX_SAMPLING_RATES]; + audio_channel_mask_t channel_masks; /* 0 -> dynamic */ + audio_format_t formats; + audio_devices_t devices; +} pa_droid_config_input; + +struct pa_droid_config_hw_module { + const pa_droid_config_audio *config; + + char name[AUDIO_HARDWARE_MODULE_ID_MAX_LEN]; + pa_droid_config_output outputs[AUDIO_MAX_OUTPUTS]; + uint32_t outputs_size; + pa_droid_config_input inputs[AUDIO_MAX_INPUTS]; + uint32_t inputs_size; +}; + +struct pa_droid_config_audio { + pa_droid_config_global global_config; + pa_droid_config_hw_module hw_modules[AUDIO_MAX_HW_MODULES]; + uint32_t hw_modules_size; +}; + + +/* Profiles */ + +typedef struct pa_droid_profile_set pa_droid_profile_set; +typedef struct pa_droid_mapping pa_droid_mapping; + +typedef struct pa_droid_port_data { + audio_devices_t device; +} pa_droid_port_data; + +typedef struct pa_droid_port { + pa_droid_mapping *mapping; + + audio_devices_t device; + char *name; + char *description; + unsigned priority; +} pa_droid_port; + +struct pa_droid_mapping { + pa_droid_profile_set *profile_set; + + const pa_droid_config_output *output; + const pa_droid_config_input *input; + + char *name; + char *description; + unsigned priority; + pa_proplist *proplist; + + /* Mapping doesn't own the ports */ + pa_idxset *ports; + + pa_direction_t direction; + + pa_sink *sink; + pa_source *source; +}; + +typedef struct pa_droid_profile { + pa_droid_profile_set *profile_set; + + const pa_droid_config_hw_module *module; + + char *name; + char *description; + unsigned priority; + + /* Profile doesn't own the mappings */ + pa_droid_mapping *output; + pa_droid_mapping *input; + +} pa_droid_profile; + +struct pa_droid_profile_set { + const pa_droid_config_audio *config; + + pa_hashmap *all_ports; + pa_hashmap *output_mappings; + pa_hashmap *input_mappings; + pa_hashmap *profiles; +}; + +/* Open hardware module */ +pa_droid_hw_module *pa_droid_hw_module_open(pa_droid_config_audio *config, const char *module_id, void *userdata); +void pa_droid_hw_module_close(pa_droid_hw_module *hw); + +/* Conversion helpers */ +typedef enum { + CONV_FROM_PA, + CONV_FROM_HAL +} pa_conversion_field_t; + +pa_bool_t pa_convert_output_channel(uint32_t value, pa_conversion_field_t from, uint32_t *to_value); +pa_bool_t pa_convert_input_channel(uint32_t value, pa_conversion_field_t from, uint32_t *to_value); +pa_bool_t pa_convert_format(uint32_t value, pa_conversion_field_t from, uint32_t *to_value); + +pa_bool_t pa_string_convert_output_device_num_to_str(audio_devices_t value, const char **to_str); +pa_bool_t pa_string_convert_output_device_str_to_num(const char *str, audio_devices_t *to_value); +pa_bool_t pa_string_convert_input_device_num_to_str(audio_devices_t value, const char **to_str); +pa_bool_t pa_string_convert_input_device_str_to_num(const char *str, audio_devices_t *to_value); + +pa_bool_t pa_string_convert_flag_num_to_str(audio_output_flags_t value, const char **to_str); +pa_bool_t pa_string_convert_flag_str_to_num(const char *str, audio_output_flags_t *to_value); + +char *pa_list_string_output_device(audio_devices_t devices); +char *pa_list_string_input_device(audio_devices_t devices); +char *pa_list_string_flags(audio_output_flags_t flags); + +/* Config parser */ +pa_bool_t pa_parse_droid_audio_config(const char *filename, pa_droid_config_audio *config); +pa_droid_config_audio *pa_droid_config_load(pa_modargs *ma); + +const pa_droid_config_output *pa_droid_config_find_output(const pa_droid_config_hw_module *module, const char *name); +const pa_droid_config_input *pa_droid_config_find_input(const pa_droid_config_hw_module *module, const char *name); +const pa_droid_config_hw_module *pa_droid_config_find_module(const pa_droid_config_audio *config, const char* module_id); + + +/* Profiles */ +pa_droid_profile_set *pa_droid_profile_set_new(const pa_droid_config_hw_module *module); +void pa_droid_profile_set_free(pa_droid_profile_set *ps); + +pa_droid_profile *pa_droid_profile_new(pa_droid_profile_set *ps, const pa_droid_config_output *output, const pa_droid_config_input *input); +void pa_droid_profile_free(pa_droid_profile *p); + +pa_droid_mapping *pa_droid_mapping_get(pa_droid_profile_set *ps, pa_direction_t direction, const void *data); +void pa_droid_mapping_free(pa_droid_mapping *am); + +/* Add ports from sinks/sources */ +void pa_droid_add_ports(pa_hashmap **p, pa_droid_mapping *am, pa_card *card); +/* Add ports from card */ +void pa_droid_add_card_ports(pa_card_profile *cp, pa_hashmap *ports, pa_droid_mapping *am, pa_core *core); + +/* Pretty port names */ +pa_bool_t pa_droid_output_port_name(audio_devices_t value, const char **to_str); +pa_bool_t pa_droid_input_port_name(audio_devices_t value, const char **to_str); + +#endif diff --git a/src/droid/module-droid-card-symdef.h b/src/droid/module-droid-card-symdef.h new file mode 100644 index 0000000..664db2b --- /dev/null +++ b/src/droid/module-droid-card-symdef.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2013 Jolla Ltd. + * + * Contact: Juho Hämäläinen + * + * These PulseAudio Modules are free software; you can redistribute + * it and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation + * version 2.1 of the License. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA. + */ +#ifndef foomoduledroidcardsymdeffoo +#define foomoduledroidcardsymdeffoo + +#include +#include + +#define pa__init module_droid_card_LTX_pa__init +#define pa__done module_droid_card_LTX_pa__done +#define pa__get_author module_droid_card_LTX_pa__get_author +#define pa__get_description module_droid_card_LTX_pa__get_description +#define pa__get_usage module_droid_card_LTX_pa__get_usage +#define pa__get_version module_droid_card_LTX_pa__get_version + +int pa__init(struct pa_module*m); +void pa__done(struct pa_module*m); + +const char* pa__get_author(void); +const char* pa__get_description(void); +const char* pa__get_usage(void); +const char* pa__get_version(void); + +#endif diff --git a/src/droid/module-droid-card.c b/src/droid/module-droid-card.c new file mode 100644 index 0000000..6a9b2f8 --- /dev/null +++ b/src/droid/module-droid-card.c @@ -0,0 +1,516 @@ +/* + * Copyright (C) 2013 Jolla Ltd. + * + * Contact: Juho Hämäläinen + * + * These PulseAudio Modules are free software; you can redistribute + * it and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation + * version 2.1 of the License. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#ifdef HAVE_VALGRIND_MEMCHECK_H +#include +#endif + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +//#include +//#include + +#include "droid-util.h" +#include "droid-sink.h" +#include "droid-source.h" + +#include "module-droid-card-symdef.h" + +PA_MODULE_AUTHOR("Juho Hämäläinen"); +PA_MODULE_DESCRIPTION("Droid card"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_USAGE( + "card_name= " + "sink_name= " + "source_name= " + "namereg_fail= " + "rate= " + "output_flags= " + "module_id= " + "deferred_volume= " + "config=" +); + +static const char* const valid_modargs[] = { + "card_name", + "sink_name", + "source_name", + "namereg_fail", + "format", + "rate", + "output_flags", + "module_id", + "deferred_volume", + "config", + NULL, +}; + +#define DEFAULT_MODULE_ID "primary" +#define DEFAULT_AUDIO_POLICY_CONF "/system/etc/audio_policy.conf" +#define VOICE_CALL_PROFILE_NAME "voicecall" + +struct userdata { + pa_core *core; + pa_module *module; + + pa_thread *thread; + pa_thread_mq thread_mq; + pa_rtpoll *rtpoll; + + pa_droid_profile_set *profile_set; + + pa_droid_config_audio *config; + pa_droid_hw_module *hw_module; + + struct call_profile_data { + pa_droid_profile *profile; + pa_droid_profile *old_profile; + } call_profile; + + pa_modargs *modargs; + pa_card *card; +}; + +struct profile_data { + pa_droid_profile *profile; +}; + +static void add_disabled_profile(pa_hashmap *profiles) { + pa_card_profile *cp; + struct profile_data *d; + + cp = pa_card_profile_new("off", _("Off"), sizeof(struct profile_data)); + + d = PA_CARD_PROFILE_DATA(cp); + d->profile = NULL; + + pa_hashmap_put(profiles, cp->name, cp); +} + +/* Special profile for calls */ +static void add_call_profile(struct userdata *u, pa_hashmap *profiles) { + pa_droid_profile *ap; + pa_card_profile *cp; + struct profile_data *d; + + pa_assert(u); + pa_assert(u->profile_set); + + pa_log_debug("New profile: %s", VOICE_CALL_PROFILE_NAME); + + ap = pa_xnew0(pa_droid_profile, 1); + ap->profile_set = u->profile_set; + ap->name = pa_sprintf_malloc(VOICE_CALL_PROFILE_NAME); + ap->description = pa_sprintf_malloc("Call mode"); + ap->priority = 50; + + u->call_profile.profile = ap; + + pa_hashmap_put(u->profile_set->profiles, ap->name, ap); + + cp = pa_card_profile_new(ap->name, ap->description, sizeof(struct profile_data)); + d = PA_CARD_PROFILE_DATA(cp); + d->profile = ap; + + pa_hashmap_put(profiles, cp->name, cp); +} + +static void set_parameters_cb(pa_droid_hw_module *module, const char *str) { + pa_assert(module); + pa_assert(str); + + if (module->device) + module->device->set_parameters(module->device, str); +} + +static void set_card_name(pa_modargs *ma, pa_card_new_data *data, const char *module_id) { + const char *tmp; + char *name; + + pa_assert(ma); + pa_assert(data); + pa_assert(module_id); + + if ((tmp = pa_modargs_get_value(ma, "card_name", NULL))) { + pa_card_new_data_set_name(data, tmp); + data->namereg_fail = TRUE; + return; + } + + name = pa_sprintf_malloc("droid_card.%s", module_id); + pa_card_new_data_set_name(data, name); + pa_xfree(name); + data->namereg_fail = FALSE; +} + +static void add_profile(struct userdata *u, pa_hashmap *h, pa_hashmap *ports, pa_droid_profile *ap) { + pa_card_profile *cp; + struct profile_data *d; + + pa_assert(u); + pa_assert(h); + pa_assert(ports); + pa_assert(ap); + + pa_log_debug("Card profile %s", ap->name); + + cp = pa_card_profile_new(ap->name, ap->description, sizeof(struct profile_data)); + cp->priority = ap->priority; + + cp->n_sinks = 1; + pa_droid_add_card_ports(cp, ports, ap->output, u->core); + cp->max_sink_channels = popcount(ap->output->output->channel_masks); + if (ap->input) { + pa_droid_add_card_ports(cp, ports, ap->input, u->core); + cp->n_sources = 1; + cp->max_source_channels = popcount(ap->input->input->channel_masks); + } + + d = PA_CARD_PROFILE_DATA(cp); + d->profile = ap; + + pa_hashmap_put(h, cp->name, cp); +} + +static void add_profiles(struct userdata *u, pa_hashmap *h, pa_hashmap *ports) { + void *state; + pa_droid_profile *ap; + + pa_assert(u); + pa_assert(h); + + PA_HASHMAP_FOREACH(ap, u->profile_set->profiles, state) { + add_profile(u, h, ports, ap); + } +} + +static void init_profile(struct userdata *u) { + pa_droid_mapping *am; + struct profile_data *d; + + pa_assert(u); + + pa_log_debug("Init profile."); + + d = PA_CARD_PROFILE_DATA(u->card->active_profile); + + if (d->profile && d->profile->output) { + am = d->profile->output; + am->sink = pa_droid_sink_new(u->module, u->modargs, __FILE__, u->hw_module, 0, am, u->card); + } + + if (d->profile && d->profile->input) { + am = d->profile->input; + am->source = pa_droid_source_new(u->module, u->modargs, __FILE__, u->hw_module, am, u->card); + } +} + +static int set_call_mode(struct userdata *u, audio_mode_t mode) { + pa_assert(u); + pa_assert(u->hw_module); + pa_assert(u->hw_module->device); + + pa_log_debug("Set mode to %s.", mode == AUDIO_MODE_IN_CALL ? "AUDIO_MODE_IN_CALL" : "AUDIO_MODE_NORMAL"); + + if (u->hw_module->device->set_mode(u->hw_module->device, mode) < 0) { + pa_log("Failed to set mode."); + return -1; + } + + return 0; +} + +static int card_set_profile(pa_card *c, pa_card_profile *new_profile) { + struct userdata *u; + pa_droid_mapping *am; + struct profile_data *nd, *od; + pa_queue *sink_inputs = NULL, *source_outputs = NULL; + + pa_assert(c); + pa_assert(new_profile); + pa_assert_se(u = c->userdata); + + nd = PA_CARD_PROFILE_DATA(new_profile); + od = PA_CARD_PROFILE_DATA(c->active_profile); + + if (nd->profile == u->call_profile.profile) { + pa_assert(u->call_profile.old_profile == NULL); + + set_call_mode(u, AUDIO_MODE_IN_CALL); + + /* Transfer ownership of sinks and sources to + * call profile */ + u->call_profile.old_profile = od->profile; + return 0; + } + + if (od->profile == u->call_profile.profile) { + set_call_mode(u, AUDIO_MODE_NORMAL); + + /* If new profile is the same as from which we switched to + * call profile, transfer ownership back to that profile. + * Otherwise destroy sinks & sources and switch to new profile. */ + if (nd->profile == u->call_profile.old_profile) { + u->call_profile.old_profile = NULL; + return 0; + } else { + od->profile = u->call_profile.old_profile; + u->call_profile.old_profile = NULL; + + /* Continue to sink-input transfer below */ + } + } + + /* If there are connected sink inputs/source outputs in old profile's sinks/sources move + * them all to new sinks/sources. */ + + if (od->profile && od->profile->output) { + do { + am = od->profile->output; + + if (!am->sink) + continue; + + if (nd->profile && nd->profile->output && am == nd->profile->output) + continue; + + sink_inputs = pa_sink_move_all_start(am->sink, sink_inputs); + pa_droid_sink_free(am->sink); + am->sink = NULL; + } while(0); + } + + if (od->profile && od->profile->input) { + do { + am = od->profile->input; + + if (!am->source) + continue; + + if (nd->profile && nd->profile->input && am == nd->profile->input) + continue; + + source_outputs = pa_source_move_all_start(am->source, source_outputs); + pa_droid_source_free(am->source); + am->source = NULL; + } while(0); + } + + if (nd->profile && nd->profile->output) { + am = nd->profile->output; + + if (!am->sink) + am->sink = pa_droid_sink_new(u->module, u->modargs, __FILE__, u->hw_module, 0, am, u->card); + + if (sink_inputs && am->sink) { + pa_sink_move_all_finish(am->sink, sink_inputs, FALSE); + sink_inputs = NULL; + } + } + + if (nd->profile && nd->profile->input) { + am = nd->profile->input; + + if (!am->source) + am->source = pa_droid_source_new(u->module, u->modargs, __FILE__, u->hw_module, am, u->card); + + if (source_outputs && am->source) { + pa_source_move_all_finish(am->source, source_outputs, FALSE); + source_outputs = NULL; + } + } + + if (sink_inputs) + pa_sink_move_all_fail(sink_inputs); + + if (source_outputs) + pa_source_move_all_fail(source_outputs); + + return 0; +} + + +int pa__init(pa_module *m) { + pa_modargs *ma = NULL; + pa_card_new_data data; + const char *module_id; + pa_bool_t namereg_fail = FALSE; + + pa_assert(m); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module argumets."); + goto fail; + } + + struct userdata *u = pa_xnew0(struct userdata, 1); + u->core = m->core; + + if (!(u->config = pa_droid_config_load(ma))) + goto fail; + + module_id = pa_modargs_get_value(ma, "module_id", DEFAULT_MODULE_ID); + + if (!(u->hw_module = pa_droid_hw_module_open(u->config, module_id, u))) + goto fail; + + u->hw_module->set_parameters = set_parameters_cb; + + u->profile_set = pa_droid_profile_set_new(u->hw_module->enabled_module); + + pa_card_new_data_init(&data); + data.driver = __FILE__; + data.module = m; + + set_card_name(ma, &data, u->hw_module->module_id); + + /* We need to give pa_modargs_get_value_boolean() a pointer to a local + * variable instead of using &data.namereg_fail directly, because + * data.namereg_fail is a bitfield and taking the address of a bitfield + * variable is impossible. */ + namereg_fail = data.namereg_fail; + if (pa_modargs_get_value_boolean(ma, "namereg_fail", &namereg_fail) < 0) { + pa_log("Failed to parse namereg_fail argument."); + pa_card_new_data_done(&data); + goto fail; + } + data.namereg_fail = namereg_fail; + + /* FIXME hashmap_new not needed with 4.0 */ + data.profiles = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + add_profiles(u, data.profiles, data.ports); + + if (pa_hashmap_isempty(data.profiles)) { + pa_log("Failed to find a working profile."); + pa_card_new_data_done(&data); + goto fail; + } + + add_call_profile(u, data.profiles); + + add_disabled_profile(data.profiles); + + pa_proplist_sets(data.proplist, PROP_DROID_HW_MODULE, u->hw_module->module_id); + + u->card = pa_card_new(m->core, &data); + pa_card_new_data_done(&data); + + if (!u->card) { + pa_log("Couldn't create card."); + goto fail; + } + + u->card->userdata = u; + u->card->set_profile = card_set_profile; + + u->modargs = ma; + u->module = m; + + m->userdata = u; + + init_profile(u); + + + return 0; + +fail: + if (ma) + pa_modargs_free(ma); + + pa__done(m); + + return -1; +} + +void pa__done(pa_module *m) { + struct userdata *u; + + pa_assert(m); + + if ((u = m->userdata)) { + + /* FIXME _remove_all in 4.x */ + if (u->card && u->card->sinks) { + pa_sink *s; + + while ((s = pa_idxset_steal_first(u->card->sinks, NULL))) + pa_droid_sink_free(s); + } + + if (u->card && u->card->sources) { + pa_source *s; + + while ((s = pa_idxset_steal_first(u->card->sources, NULL))) + pa_droid_source_free(s); + } + + + if (u->card) + pa_card_free(u->card); + + if (u->modargs) + pa_modargs_free(u->modargs); + + if (u->profile_set) + pa_droid_profile_set_free(u->profile_set); + + if (u->hw_module) + pa_droid_hw_module_close(u->hw_module); + + if (u->config) + pa_xfree(u->config); + + pa_xfree(u); + } +} diff --git a/src/droid/module-droid-sink-symdef.h b/src/droid/module-droid-sink-symdef.h new file mode 100644 index 0000000..f78b03b --- /dev/null +++ b/src/droid/module-droid-sink-symdef.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2013 Jolla Ltd. + * + * Contact: Juho Hämäläinen + * + * These PulseAudio Modules are free software; you can redistribute + * it and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation + * version 2.1 of the License. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA. + */ +#ifndef foomoduledroidsinksymdeffoo +#define foomoduledroidsinksymdeffoo + +#include +#include + +#define pa__init module_droid_sink_LTX_pa__init +#define pa__done module_droid_sink_LTX_pa__done +#define pa__get_author module_droid_sink_LTX_pa__get_author +#define pa__get_description module_droid_sink_LTX_pa__get_description +#define pa__get_usage module_droid_sink_LTX_pa__get_usage +#define pa__get_version module_droid_sink_LTX_pa__get_version + +int pa__init(struct pa_module*m); +void pa__done(struct pa_module*m); + +const char* pa__get_author(void); +const char* pa__get_description(void); +const char* pa__get_usage(void); +const char* pa__get_version(void); + +#endif diff --git a/src/droid/module-droid-sink.c b/src/droid/module-droid-sink.c new file mode 100644 index 0000000..274d723 --- /dev/null +++ b/src/droid/module-droid-sink.c @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2013 Jolla Ltd. + * + * Contact: Juho Hämäläinen + * + * These PulseAudio Modules are free software; you can redistribute + * it and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation + * version 2.1 of the License. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#ifdef HAVE_VALGRIND_MEMCHECK_H +#include +#endif + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "droid-util.h" +#include "droid-sink.h" + +#include "module-droid-sink-symdef.h" + +PA_MODULE_AUTHOR("Juho Hämäläinen"); +PA_MODULE_DESCRIPTION("Droid sink"); +PA_MODULE_USAGE("master_sink= " + "sink_name="); +PA_MODULE_VERSION(PACKAGE_VERSION); + +static const char* const valid_modargs[] = { + "rate", + "flags", + "devices", + "sink_name", + "module_id", + "deferred_volume", + NULL, +}; + +void pa__done(pa_module *m) { + pa_sink *sink; + + pa_assert(m); + + if ((sink = m->userdata)) + pa_droid_sink_free(sink); +} + +int pa__init(pa_module *m) { + pa_modargs *ma = NULL; + + pa_assert(m); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module argumets."); + goto fail; + } + + if (!(m->userdata = pa_droid_sink_new(m, ma, __FILE__, NULL, 0, NULL, NULL))) + goto fail; + + pa_modargs_free(ma); + + return 0; + +fail: + if (ma) + pa_modargs_free(ma); + + pa__done(m); + + return -1; +} diff --git a/src/droid/module-droid-source-symdef.h b/src/droid/module-droid-source-symdef.h new file mode 100644 index 0000000..7610b30 --- /dev/null +++ b/src/droid/module-droid-source-symdef.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2013 Jolla Ltd. + * + * Contact: Juho Hämäläinen + * + * These PulseAudio Modules are free software; you can redistribute + * it and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation + * version 2.1 of the License. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA. + */ +#ifndef foomoduledroidsourcesymdeffoo +#define foomoduledroidsourcesymdeffoo + +#include +#include + +#define pa__init module_droid_source_LTX_pa__init +#define pa__done module_droid_source_LTX_pa__done +#define pa__get_author module_droid_source_LTX_pa__get_author +#define pa__get_description module_droid_source_LTX_pa__get_description +#define pa__get_usage module_droid_source_LTX_pa__get_usage +#define pa__get_version module_droid_source_LTX_pa__get_version + +int pa__init(struct pa_module*m); +void pa__done(struct pa_module*m); + +const char* pa__get_author(void); +const char* pa__get_description(void); +const char* pa__get_usage(void); +const char* pa__get_version(void); + +#endif diff --git a/src/droid/module-droid-source.c b/src/droid/module-droid-source.c new file mode 100644 index 0000000..1814302 --- /dev/null +++ b/src/droid/module-droid-source.c @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2013 Jolla Ltd. + * + * Contact: Juho Hämäläinen + * + * These PulseAudio Modules are free software; you can redistribute + * it and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation + * version 2.1 of the License. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#ifdef HAVE_VALGRIND_MEMCHECK_H +#include +#endif + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "droid-util.h" +#include "droid-source.h" + +#include "module-droid-source-symdef.h" + +PA_MODULE_AUTHOR("Juho Hämäläinen"); +PA_MODULE_DESCRIPTION("Droid source"); +PA_MODULE_USAGE("master_source= " + "source_name="); +PA_MODULE_VERSION(PACKAGE_VERSION); + +static const char* const valid_modargs[] = { + "rate", + "flags", + "devices", + "source_name", + "module_id", + "deferred_volume", + NULL, +}; + +void pa__done(pa_module *m) { + pa_source *source; + + pa_assert(m); + + if ((source = m->userdata)) + pa_droid_source_free(source); +} + +int pa__init(pa_module *m) { + pa_modargs *ma = NULL; + + pa_assert(m); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module argumets."); + goto fail; + } + + if (!(m->userdata = pa_droid_source_new(m, ma, __FILE__, NULL, NULL, NULL))) + goto fail; + + pa_modargs_free(ma); + + return 0; + +fail: + if (ma) + pa_modargs_free(ma); + + pa__done(m); + + return -1; +}