From 3922bb7ec46cad00f3febe1317389760eb1d97d1 Mon Sep 17 00:00:00 2001 From: Seonmi Jin Date: Thu, 17 Sep 2015 14:43:04 +0900 Subject: [PATCH 2/8] Initial import the package for pulseaudio tizen modules [Version] pulseaudio 5.0-40 [Profile] Common Change-Id: I34d93c2ee8a39dfa06698862bd8fb204e41bf66b Signed-off-by: Seonmi Jin --- LICENSE.LGPL-2.1+ | 510 ++++ Makefile.am | 74 + autogen.sh | 6 + config.rpath | 0 configure.ac | 449 ++++ install-sh | 527 ++++ m4/acx_libwrap.m4 | 19 + m4/ax_check_define.m4 | 92 + m4/ax_check_flag.m4 | 147 ++ m4/ax_define_dir.m4 | 49 + m4/ax_pthread.m4 | 317 +++ m4/ax_tls.m4 | 76 + m4/orc.m4 | 69 + packaging/pulseaudio-modules-tizen.spec | 79 + pulseaudio-modules-tizen.manifest | 5 + src/communicator.c | 90 + src/communicator.h | 21 + src/device-manager.c | 4277 +++++++++++++++++++++++++++++++ src/device-manager.h | 75 + src/hal-manager.c | 312 +++ src/hal-manager.h | 64 + src/module-defs.h.m4 | 35 + src/module-policy.c | 1544 +++++++++++ src/module-sound-player.c | 626 +++++ src/module-tizenaudio-sink.c | 480 ++++ src/stream-manager-priv.h | 98 + src/stream-manager-volume-priv.h | 30 + src/stream-manager-volume.c | 858 +++++++ src/stream-manager-volume.h | 14 + src/stream-manager.c | 2632 +++++++++++++++++++ src/stream-manager.h | 51 + src/tizen-audio.h | 341 +++ 32 files changed, 13967 insertions(+) create mode 100755 LICENSE.LGPL-2.1+ create mode 100644 Makefile.am create mode 100755 autogen.sh create mode 100755 config.rpath create mode 100644 configure.ac create mode 100755 install-sh create mode 100644 m4/acx_libwrap.m4 create mode 100644 m4/ax_check_define.m4 create mode 100644 m4/ax_check_flag.m4 create mode 100644 m4/ax_define_dir.m4 create mode 100644 m4/ax_pthread.m4 create mode 100644 m4/ax_tls.m4 create mode 100644 m4/orc.m4 create mode 100644 packaging/pulseaudio-modules-tizen.spec create mode 100644 pulseaudio-modules-tizen.manifest create mode 100644 src/communicator.c create mode 100644 src/communicator.h create mode 100644 src/device-manager.c create mode 100644 src/device-manager.h create mode 100644 src/hal-manager.c create mode 100644 src/hal-manager.h create mode 100644 src/module-defs.h.m4 create mode 100644 src/module-policy.c create mode 100644 src/module-sound-player.c create mode 100644 src/module-tizenaudio-sink.c create mode 100644 src/stream-manager-priv.h create mode 100644 src/stream-manager-volume-priv.h create mode 100644 src/stream-manager-volume.c create mode 100644 src/stream-manager-volume.h create mode 100644 src/stream-manager.c create mode 100644 src/stream-manager.h create mode 100644 src/tizen-audio.h diff --git a/LICENSE.LGPL-2.1+ b/LICENSE.LGPL-2.1+ new file mode 100755 index 0000000..1bf7621 --- /dev/null +++ b/LICENSE.LGPL-2.1+ @@ -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..190c2d0 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,74 @@ +# This file is part of PulseAudio. +# +# PulseAudio 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 of the License, or +# (at your option) any later version. +# +# PulseAudio is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with PulseAudio; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +# USA. + +ACLOCAL_AMFLAGS = -I m4 + +pulsemodlibexecdir= $(libdir)/pulse-5.0/modules + +AM_CFLAGS = \ + $(PTHREAD_CFLAGS) + +AM_LIBADD = $(PTHREAD_LIBS) $(INTLLIBS) +AM_LDFLAGS = $(NODELETE_LDFLAGS) + +MODULE_CFLAGS = $(AM_CFLAGS) +MODULE_LDFLAGS = $(AM_LDFLAGS) -module -disable-static -avoid-version +MODULE_LIBADD = $(AM_LIBADD) $(LIBLTDL) + +pulsemodlibexec_LTLIBRARIES = \ + module-tizenaudio-sink.la \ + module-sound-player.la \ + module-policy.la + +# These are generated by an M4 script +SYMDEF_FILES = \ + module-tizenaudio-sink-symdef.h \ + module-sound-player-symdef.h \ + module-policy-symdef.h + +if HAVE_DBUS + +endif + +EXTRA_DIST = $(SYMDEF_FILES) +BUILT_SOURCES = $(SYMDEF_FILES) + +$(SYMDEF_FILES): src/module-defs.h.m4 + $(AM_V_at)$(MKDIR_P) . + $(AM_V_GEN)$(M4) -Dfname="$@" $< > $@ + +module_tizenaudio_sink_la_SOURCES = src/module-tizenaudio-sink.c +module_tizenaudio_sink_la_LDFLAGS = $(PACORE_LDFLAGS) $(PA_LDFLAGS) $(MODULE_LDFLAGS) +module_tizenaudio_sink_la_LIBADD = $(PACORE_LIBS) $(PA_LIBS) $(MODULE_LIBADD) $(ASOUNDLIB_LIBS) +module_tizenaudio_sink_la_CFLAGS = $(PACORE_CFLAGS) $(PA_CFLAGS) $(AM_CFLAGS) $(ASOUNDLIB_CFLAGS) + +module_sound_player_la_SOURCES = src/module-sound-player.c +module_sound_player_la_LDFLAGS = $(MODULE_LDFLAGS) $(PACORE_LDFLAGS) $(PA_LDFLAGS) +module_sound_player_la_LIBADD = $(MODULE_LIBADD) $(PACORE_LIBS) $(PA_LIBS) $(AM_LIBADD) $(DBUS_LIBS) +module_sound_player_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS) $(PACORE_CFLAGS) $(PA_CFLAGS) + +module_policy_la_SOURCES = \ + src/module-policy.c \ + src/communicator.c src/communicator.h \ + src/hal-manager.c src/hal-manager.h \ + src/stream-manager.c src/stream-manager.h src/stream-manager-priv.h \ + src/stream-manager-volume.c src/stream-manager-volume.h src/stream-manager-volume-priv.h \ + src/device-manager.c src/device-manager.h +module_policy_la_LDFLAGS = $(MODULE_LDFLAGS) $(PACORE_LDFLAGS) $(PA_LDFLAGS) -L$(pulsemodlibexecdir) +module_policy_la_LIBADD = $(AM_LIBADD) $(PACORE_LIBS) $(PA_LIBS) $(DBUS_LIBS) $(VCONF_LIBS) $(INIPARSER_LIBS) $(LIBJSON_LIBS) $(ASOUNDLIB_LIBS) +module_policy_la_CFLAGS = $(AM_CFLAGS) $(PACORE_CFLAGS) $(PA_CFLAGS) $(DBUS_CFLAGS) $(VCONF_CFLAGS) $(INIPARSER_CFLAGS) $(LIBJSON_CFLAGS) $(ASOUNDLIB_CFLAGS) + diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 0000000..a2bf629 --- /dev/null +++ b/autogen.sh @@ -0,0 +1,6 @@ +aclocal +libtoolize --copy +autoheader +autoconf +automake --add-missing --copy --foreign +#./configure --with-xo-machine=W1 diff --git a/config.rpath b/config.rpath new file mode 100755 index 0000000..e69de29 diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..29532c2 --- /dev/null +++ b/configure.ac @@ -0,0 +1,449 @@ +# -*- Autoconf -*- +# Process this file with autoconf to produce a configure script. + +# This file is part of PulseAudio. +# +# Copyright 2004-2008 Lennart Poettering +# Copyright 2006-2007 Pierre Ossman for Cendio AB +# +# PulseAudio 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 of the License, or +# (at your option) any later version. +# +# PulseAudio is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with PulseAudio; if not, write to the Free Software Foundation, +# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +AC_PREREQ(2.63) + +AC_INIT([pulseaudio-module-tizen],[0.1]) +AC_CONFIG_SRCDIR([src/module-policy.c]) +AC_CONFIG_MACRO_DIR([m4]) +AC_CONFIG_HEADERS([config.h]) +AM_INIT_AUTOMAKE([foreign 1.11 -Wall -Wno-portability subdir-objects silent-rules color-tests dist-xz tar-ustar]) + +#### Checks for programs. #### + +# mkdir -p + +AC_PROG_MKDIR_P + +# CC + +AC_PROG_CC +AC_PROG_CC_C99 +AM_PROG_CC_C_O +# Only required if you want the WebRTC canceller -- no runtime dep on +# libstdc++ otherwise +AC_PROG_CXX +AC_PROG_GCC_TRADITIONAL +AC_USE_SYSTEM_EXTENSIONS + +# M4 + +AC_CHECK_PROGS([M4], gm4 m4, no) +AS_IF([test "x$M4" = "xno"], AC_MSG_ERROR([m4 missing])) + +# pkg-config + +PKG_PROG_PKG_CONFIG + +#### Compiler flags #### + +AX_APPEND_COMPILE_FLAGS( + [-Wall -W -Wextra -pipe -Wno-long-long -Wno-overlength-strings -Wunsafe-loop-optimizations -Wundef -Wformat=2 -Wlogical-op -Wsign-compare -Wformat-security -Wmissing-include-dirs -Wformat-nonliteral -Wold-style-definition -Wpointer-arith -Winit-self -Wdeclaration-after-statement -Wfloat-equal -Wmissing-prototypes -Wstrict-prototypes -Wredundant-decls -Wmissing-declarations -Wmissing-noreturn -Wshadow -Wendif-labels -Wcast-align -Wstrict-aliasing -Wwrite-strings -Wno-unused-parameter -ffast-math -fno-common -fdiagnostics-show-option], + [], [-pedantic -Werror]) + +# Only enable fastpath asserts when doing a debug build, e.g. from bootstrap.sh. +AS_CASE([" $CFLAGS "], [*" -O0 "*], [], [AX_APPEND_FLAG(["-DFASTPATH"], [CPPFLAGS])]) + +# Only set _FORTIFY_SOURCE when optimizations are enabled. If optimizations +# are disabled, _FORTIFY_SOURCE doesn't do anything, and causes tons of +# warnings during compiling on some distributions (at least Fedora). +AS_CASE([" $CFLAGS "], [*" -O0 "*], [], [AX_APPEND_FLAG(["-D_FORTIFY_SOURCE=2"], [CPPFLAGS])]) + + +#### Linker flags #### + +# Use immediate (now) bindings; avoids the funky re-call in itself. +# The -z now syntax is lifted from Sun's linker and works with GNU's too, other linkers might be added later. +AX_APPEND_LINK_FLAGS([-Wl,-z,now], [IMMEDIATE_LDFLAGS]) +AC_SUBST([IMMEDIATE_LDFLAGS]) + +# On ELF systems we don't want the libraries to be unloaded since we don't clean them up properly, +# so we request the nodelete flag to be enabled. +# On other systems, we don't really know how to do that, but it's welcome if somebody can tell. +AX_APPEND_LINK_FLAGS([-Wl,-z,nodelete], [NODELETE_LDFLAGS]) +AC_SUBST([NODELETE_LDFLAGS]) + +# Check for the proper way to build libraries that have no undefined symbols +case $host in + # FreeBSD (et al.) does not complete linking for shared objects when pthreads + # are requested, as different implementations are present. + *-freebsd* | *-openbsd*) ;; + *) + for possible_flag in "-Wl,--no-undefined" "-Wl,-z,defs"; do + AX_CHECK_LINK_FLAG([$possible_flag], [NOUNDEFINED_LDFLAGS="$possible_flag"; break]) + done + ;; +esac +AC_SUBST([NOUNDEFINED_LDFLAGS]) + + +#### Atomic operations #### + +# 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])) + +AC_ARG_ENABLE([atomic-arm-memory-barrier], + AS_HELP_STRING([--enable-atomic-arm-memory-barrier],[only really needed in SMP arm systems])) + +if test "x$enable_atomic_arm_memory_barrier" = "xyes"; then + AC_DEFINE_UNQUOTED(ATOMIC_ARM_MEMORY_BARRIER_ENABLED, 1, [Enable memory barriers]) +fi + +# If everything else fails use libatomic_ops +need_libatomic_ops=yes + +AC_CACHE_CHECK([whether $CC knows __sync_bool_compare_and_swap()], + pulseaudio_cv_sync_bool_compare_and_swap, [ + AC_LINK_IFELSE( + [AC_LANG_PROGRAM([], [[int a = 4; __sync_bool_compare_and_swap(&a, 4, 5);]])], + [pulseaudio_cv_sync_bool_compare_and_swap=yes], + [pulseaudio_cv_sync_bool_compare_and_swap=no]) + ]) + +if test "$pulseaudio_cv_sync_bool_compare_and_swap" = "yes" ; then + AC_DEFINE([HAVE_ATOMIC_BUILTINS], 1, [Have __sync_bool_compare_and_swap() and friends.]) + need_libatomic_ops=no +else + # HW specific atomic ops stuff + AC_MSG_CHECKING([architecture for native atomic operations]) + case $host in + arm*) + AC_MSG_RESULT([arm]) + 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$os_is_linux" = "x1" && test "x$enable_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]) + AC_CACHE_CHECK([compiler support for arm inline asm atomic operations], + pulseaudio_cv_support_arm_atomic_ops, [ + AC_COMPILE_IFELSE( + [AC_LANG_PROGRAM([], [[ + 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); + ]])], + [pulseaudio_cv_support_arm_atomic_ops=yes], + [pulseaudio_cv_support_arm_atomic_ops=no]) + ]) + AS_IF([test "$pulseaudio_cv_support_arm_atomic_ops" = "yes"], [ + AC_DEFINE([ATOMIC_ARM_INLINE_ASM], 1, [Have ARM atomic instructions.]) + need_libatomic_ops=no + ]) + fi + ;; + *-netbsdelf5*) + AC_MSG_RESULT([yes]) + need_libatomic_ops=no + ;; + *-freebsd*) + AC_MSG_RESULT([yes]) + need_libatomic_ops=no + ;; + *) + AC_MSG_RESULT([unknown]) + ;; + esac +fi + +# If we're on ARM, check for the ARMV6 instructions we need */ +case $host in + arm*) + AC_CACHE_CHECK([support for required armv6 instructions], + pulseaudio_cv_support_armv6, + [AC_COMPILE_IFELSE( + [AC_LANG_PROGRAM([], + [[volatile int a = -60000, b = 0xaaaabbbb, c = 0xccccdddd; + asm volatile ("ldr r0, %2 \n" + "ldr r2, %3 \n" + "ldr r3, %4 \n" + "ssat r1, #8, r0 \n" + "str r1, %0 \n" + "pkhbt r1, r3, r2, LSL #8 \n" + "str r1, %1 \n" + : "=m" (a), "=m" (b) + : "m" (a), "m" (b), "m" (c) + : "r0", "r1", "r2", "r3", "cc"); + return (a == -128 && b == 0xaabbdddd) ? 0 : -1; + ]])], + [pulseaudio_cv_support_armv6=yes], + [pulseaudio_cv_support_armv6=no]) + ]) + AS_IF([test "$pulseaudio_cv_support_armv6" = "yes"], [ + AC_DEFINE([HAVE_ARMV6], 1, [Have ARMv6 instructions.]) + ]) + ;; + *) + ;; +esac + +#### libtool stuff #### + +LT_PREREQ(2.4) +LT_INIT([dlopen win32-dll disable-static]) + +dnl As an extra safety device, check for lt_dladvise_init() which is +dnl only implemented in libtool 2.x, and refine as we go if we have +dnl refined requirements. +dnl +dnl Check the header files first since the system may have a +dnl libltdl.so for runtime, but no headers, and we want to bail out as +dnl soon as possible. +dnl +dnl We don't need any special variable for this though, since the user +dnl can give the proper place to find libltdl through the standard +dnl variables like LDFLAGS and CPPFLAGS. + +AC_CHECK_HEADER([ltdl.h], + [AC_CHECK_LIB([ltdl], [lt_dladvise_init], [LIBLTDL=-lltdl], [LIBLTDL=])], + [LIBLTDL=]) + +AS_IF([test "x$LIBLTDL" = "x"], + [AC_MSG_ERROR([Unable to find libltdl version 2. Makes sure you have libtool 2.4 or later installed.])]) +AC_SUBST([LIBLTDL]) + + +################################### +# Basic environment checks # +################################### + +#### Checks for header files. #### + +# ISO +AC_HEADER_STDC + +# POSIX +AC_CHECK_HEADERS_ONCE([arpa/inet.h glob.h grp.h netdb.h netinet/in.h \ + netinet/in_systm.h netinet/tcp.h poll.h pwd.h sched.h \ + sys/mman.h sys/select.h sys/socket.h sys/wait.h \ + sys/uio.h syslog.h sys/dl.h dlfcn.h linux/sockios.h]) +AC_CHECK_HEADERS([netinet/ip.h], [], [], + [#include + #if HAVE_NETINET_IN_H + # include + #endif + #if HAVE_NETINET_IN_SYSTM_H + # include + #endif + ]) +AC_CHECK_HEADERS([sys/resource.h], [HAVE_SYS_RESOURCE_H=1], [HAVE_SYS_RESOURCE_H=0]) +AC_SUBST(HAVE_SYS_RESOURCE_H) +AC_CHECK_HEADERS([sys/un.h], [HAVE_AF_UNIX=1], [HAVE_AF_UNIX=0]) +AM_CONDITIONAL(HAVE_AF_UNIX, test "x$HAVE_AF_UNIX" = "x1") +AC_SUBST(HAVE_AF_UNIX) + +# Linux +AC_CHECK_HEADERS([linux/input.h], [HAVE_EVDEV=1], [HAVE_EVDEV=0]) +AM_CONDITIONAL([HAVE_EVDEV], [test "x$HAVE_EVDEV" = "x1"]) + +AC_CHECK_HEADERS_ONCE([sys/prctl.h]) + +#### Check for libs #### + +# ISO +AC_SEARCH_LIBS([pow], [m]) + +# POSIX +AC_SEARCH_LIBS([sched_setscheduler], [rt]) +AC_SEARCH_LIBS([dlopen], [dl]) +AC_SEARCH_LIBS([shm_open], [rt]) +AC_SEARCH_LIBS([inet_ntop], [nsl]) +AC_SEARCH_LIBS([timer_create], [rt]) +AC_SEARCH_LIBS([pthread_setaffinity_np], [pthread]) +AC_SEARCH_LIBS([pthread_getname_np], [pthread]) +AC_SEARCH_LIBS([pthread_setname_np], [pthread]) + +# BSD +AC_SEARCH_LIBS([connect], [socket]) +AC_SEARCH_LIBS([backtrace], [execinfo ubacktrace]) + + +#### Check for functions #### + +# ISO +AC_CHECK_FUNCS_ONCE([lrintf strtof]) + +# POSIX +AC_FUNC_FORK +AC_FUNC_GETGROUPS +AC_CHECK_FUNCS_ONCE([chmod chown fstat fchown fchmod clock_gettime getaddrinfo getgrgid_r getgrnam_r \ + getpwnam_r getpwuid_r gettimeofday getuid mlock nanosleep \ + pipe posix_fadvise posix_madvise posix_memalign setpgid setsid shm_open \ + sigaction sleep symlink sysconf uname pthread_setaffinity_np pthread_getname_np pthread_setname_np]) +AC_CHECK_FUNCS([mkfifo], [HAVE_MKFIFO=1], [HAVE_MKFIFO=0]) +AC_SUBST(HAVE_MKFIFO) +AM_CONDITIONAL(HAVE_MKFIFO, test "x$HAVE_MKFIFO" = "x1") + +# X/OPEN +AC_CHECK_FUNCS_ONCE([readlink]) + +# SUSv2 +AC_CHECK_FUNCS_ONCE([ctime_r usleep]) + +# SUSv3 +AC_CHECK_FUNCS_ONCE([strerror_r]) + +# BSD +AC_CHECK_FUNCS_ONCE([lstat]) + +# Non-standard +AC_CHECK_FUNCS_ONCE([setresuid setresgid setreuid setregid seteuid setegid ppoll strsignal sig2str strtof_l pipe2 accept4]) + +AC_FUNC_ALLOCA + +AC_CHECK_FUNCS([regexec], [HAVE_REGEX=1], [HAVE_REGEX=0]) +AM_CONDITIONAL(HAVE_REGEX, [test "x$HAVE_REGEX" = "x1"]) + +# Large File-Support (LFS) +AC_SYS_LARGEFILE +# Check for open64 to know if the current system does have open64() and similar functions +AC_CHECK_FUNCS_ONCE([open64]) + + +################################### +# External libraries # +################################### + +PKG_CHECK_MODULES(PA, libpulse) +AC_SUBST(PA_CFLAGS) +AC_SUBST(PA_LIBS) + +PKG_CHECK_MODULES(PACORE, pulsecore) +AC_SUBST(PACORE_CFLAGS) +AC_SUBST(PACORE_LIBS) +AC_SUBST(PACORE_LDFLAGS) + +#### [lib]iconv #### + +AM_ICONV + +#### json parsing #### + +PKG_CHECK_MODULES(LIBJSON, [ json-c >= 0.11 ], [], + [PKG_CHECK_MODULES(LIBJSON, [ json >= 0.9 ])]) + +#### Sound file #### +PKG_CHECK_MODULES(INIPARSER, iniparser) +AC_SUBST(INIPARSER_CFLAGS) +AC_SUBST(INIPARSER_LIBS) + +PKG_CHECK_MODULES(VCONF, vconf) +AC_SUBST(VCONF_CFLAGS) +AC_SUBST(VCONF_LIBS) + +dnl use dlog -------------------------------------------------------------------------- +AC_ARG_ENABLE(dlog, AC_HELP_STRING([--enable-dlog], [using dlog]), +[ + case "${enableval}" in + yes) USE_DLOG=yes ;; + no) USE_DLOG=no ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-dlog) ;; + esac + ],[USE_DLOG=no]) + +if test "x$USE_DLOG" = "xyes"; then +PKG_CHECK_MODULES(DLOG, dlog) +AC_SUBST(DLOG_CFLAGS) +AC_SUBST(DLOG_LIBS) +fi +AM_CONDITIONAL(USE_DLOG, test "x$USE_DLOG" = "xyes") +dnl end -------------------------------------------------------------------- + +#### ALSA support (optional) #### + +AC_ARG_ENABLE([alsa], + AS_HELP_STRING([--disable-alsa],[Disable optional ALSA support])) + +AS_IF([test "x$enable_alsa" != "xno"], + [PKG_CHECK_MODULES(ASOUNDLIB, [ alsa >= 1.0.19 ], HAVE_ALSA=1, HAVE_ALSA=0)], + HAVE_ALSA=0) + +AS_IF([test "x$enable_alsa" = "xyes" && test "x$HAVE_ALSA" = "x0"], + [AC_MSG_ERROR([*** Needed alsa >= 1.0.19 support not found])]) + +AS_IF([test "x$HAVE_ALSA" = "x1"], + [ + save_CPPFLAGS="$CPPFLAGS"; CPPFLAGS="$CPPFLAGS $ASOUNDLIB_CFLAGS" + AC_CHECK_HEADERS([use-case.h], HAVE_ALSA_UCM=1, HAVE_ALSA_UCM=0) + CPPFLAGS="$save_CPPFLAGS" + ], + HAVE_ALSA_UCM=0) + +AC_SUBST(ASOUNDLIB_CFLAGS) +AC_SUBST(ASOUNDLIB_LIBS) +AC_SUBST(HAVE_ALSA) +AM_CONDITIONAL([HAVE_ALSA], [test "x$HAVE_ALSA" = x1]) +AS_IF([test "x$HAVE_ALSA" = "x1"], AC_DEFINE([HAVE_ALSA], 1, [Have ALSA?])) +AS_IF([test "x$HAVE_ALSA_UCM" = "x1"], AC_DEFINE([HAVE_ALSA_UCM], 1, [Have ALSA UCM?])) + +#### D-Bus support (optional) #### + +AC_ARG_ENABLE([dbus], + AS_HELP_STRING([--disable-dbus],[Disable optional D-Bus support])) + +AS_IF([test "x$enable_dbus" != "xno"], + [PKG_CHECK_MODULES(DBUS, [ dbus-1 >= 1.4.12 ], HAVE_DBUS=1, HAVE_DBUS=0)], + HAVE_DBUS=0) + +AS_IF([test "x$enable_dbus" = "xyes" && test "x$HAVE_DBUS" = "x0"], + [AC_MSG_ERROR([*** D-Bus not available or too old version])]) + +AS_IF([test "x$HAVE_DBUS" = "x1"], + [ + save_CFLAGS="$CFLAGS"; CFLAGS="$CFLAGS $DBUS_CFLAGS" + save_LIBS="$LIBS"; LIBS="$LIBS $DBUS_LIBS" + AC_CHECK_FUNCS(dbus_watch_get_unix_fd) + CFLAGS="$save_CFLAGS" + LIBS="$save_LIBS" + ]) + +AC_SUBST(HAVE_DBUS) +AM_CONDITIONAL([HAVE_DBUS], [test "x$HAVE_DBUS" = x1]) +AS_IF([test "x$HAVE_DBUS" = "x1"], AC_DEFINE([HAVE_DBUS], 1, [Have D-Bus.])) + +PA_MACHINE_ID="${sysconfdir}/machine-id" +AX_DEFINE_DIR(PA_MACHINE_ID, PA_MACHINE_ID, [D-Bus machine-id file]) +PA_MACHINE_ID_FALLBACK="${localstatedir}/lib/dbus/machine-id" +AX_DEFINE_DIR(PA_MACHINE_ID_FALLBACK, PA_MACHINE_ID_FALLBACK, + [Fallback machine-id file]) + +################################### +# Output # +################################### + +AC_DEFINE_UNQUOTED(PA_CFLAGS, "$CFLAGS", [The CFLAGS used during compilation]) + +AC_CONFIG_FILES([ +Makefile +]) +AC_OUTPUT diff --git a/install-sh b/install-sh new file mode 100755 index 0000000..a9244eb --- /dev/null +++ b/install-sh @@ -0,0 +1,527 @@ +#!/bin/sh +# install - install a program, script, or datafile + +scriptversion=2011-01-19.21; # UTC + +# This originates from X11R5 (mit/util/scripts/install.sh), which was +# later released in X11R6 (xc/config/util/install.sh) with the +# following copyright and license. +# +# Copyright (C) 1994 X Consortium +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +# AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNEC- +# TION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# Except as contained in this notice, the name of the X Consortium shall not +# be used in advertising or otherwise to promote the sale, use or other deal- +# ings in this Software without prior written authorization from the X Consor- +# tium. +# +# +# FSF changes to this file are in the public domain. +# +# Calling this script install-sh is preferred over install.sh, to prevent +# `make' implicit rules from creating a file called install from it +# when there is no Makefile. +# +# This script is compatible with the BSD install script, but was written +# from scratch. + +nl=' +' +IFS=" "" $nl" + +# set DOITPROG to echo to test this script + +# Don't use :- since 4.3BSD and earlier shells don't like it. +doit=${DOITPROG-} +if test -z "$doit"; then + doit_exec=exec +else + doit_exec=$doit +fi + +# Put in absolute file names if you don't have them in your path; +# or use environment vars. + +chgrpprog=${CHGRPPROG-chgrp} +chmodprog=${CHMODPROG-chmod} +chownprog=${CHOWNPROG-chown} +cmpprog=${CMPPROG-cmp} +cpprog=${CPPROG-cp} +mkdirprog=${MKDIRPROG-mkdir} +mvprog=${MVPROG-mv} +rmprog=${RMPROG-rm} +stripprog=${STRIPPROG-strip} + +posix_glob='?' +initialize_posix_glob=' + test "$posix_glob" != "?" || { + if (set -f) 2>/dev/null; then + posix_glob= + else + posix_glob=: + fi + } +' + +posix_mkdir= + +# Desired mode of installed file. +mode=0755 + +chgrpcmd= +chmodcmd=$chmodprog +chowncmd= +mvcmd=$mvprog +rmcmd="$rmprog -f" +stripcmd= + +src= +dst= +dir_arg= +dst_arg= + +copy_on_change=false +no_target_directory= + +usage="\ +Usage: $0 [OPTION]... [-T] SRCFILE DSTFILE + or: $0 [OPTION]... SRCFILES... DIRECTORY + or: $0 [OPTION]... -t DIRECTORY SRCFILES... + or: $0 [OPTION]... -d DIRECTORIES... + +In the 1st form, copy SRCFILE to DSTFILE. +In the 2nd and 3rd, copy all SRCFILES to DIRECTORY. +In the 4th, create DIRECTORIES. + +Options: + --help display this help and exit. + --version display version info and exit. + + -c (ignored) + -C install only if different (preserve the last data modification time) + -d create directories instead of installing files. + -g GROUP $chgrpprog installed files to GROUP. + -m MODE $chmodprog installed files to MODE. + -o USER $chownprog installed files to USER. + -s $stripprog installed files. + -t DIRECTORY install into DIRECTORY. + -T report an error if DSTFILE is a directory. + +Environment variables override the default commands: + CHGRPPROG CHMODPROG CHOWNPROG CMPPROG CPPROG MKDIRPROG MVPROG + RMPROG STRIPPROG +" + +while test $# -ne 0; do + case $1 in + -c) ;; + + -C) copy_on_change=true;; + + -d) dir_arg=true;; + + -g) chgrpcmd="$chgrpprog $2" + shift;; + + --help) echo "$usage"; exit $?;; + + -m) mode=$2 + case $mode in + *' '* | *' '* | *' +'* | *'*'* | *'?'* | *'['*) + echo "$0: invalid mode: $mode" >&2 + exit 1;; + esac + shift;; + + -o) chowncmd="$chownprog $2" + shift;; + + -s) stripcmd=$stripprog;; + + -t) dst_arg=$2 + # Protect names problematic for `test' and other utilities. + case $dst_arg in + -* | [=\(\)!]) dst_arg=./$dst_arg;; + esac + shift;; + + -T) no_target_directory=true;; + + --version) echo "$0 $scriptversion"; exit $?;; + + --) shift + break;; + + -*) echo "$0: invalid option: $1" >&2 + exit 1;; + + *) break;; + esac + shift +done + +if test $# -ne 0 && test -z "$dir_arg$dst_arg"; then + # When -d is used, all remaining arguments are directories to create. + # When -t is used, the destination is already specified. + # Otherwise, the last argument is the destination. Remove it from $@. + for arg + do + if test -n "$dst_arg"; then + # $@ is not empty: it contains at least $arg. + set fnord "$@" "$dst_arg" + shift # fnord + fi + shift # arg + dst_arg=$arg + # Protect names problematic for `test' and other utilities. + case $dst_arg in + -* | [=\(\)!]) dst_arg=./$dst_arg;; + esac + done +fi + +if test $# -eq 0; then + if test -z "$dir_arg"; then + echo "$0: no input file specified." >&2 + exit 1 + fi + # It's OK to call `install-sh -d' without argument. + # This can happen when creating conditional directories. + exit 0 +fi + +if test -z "$dir_arg"; then + do_exit='(exit $ret); exit $ret' + trap "ret=129; $do_exit" 1 + trap "ret=130; $do_exit" 2 + trap "ret=141; $do_exit" 13 + trap "ret=143; $do_exit" 15 + + # Set umask so as not to create temps with too-generous modes. + # However, 'strip' requires both read and write access to temps. + case $mode in + # Optimize common cases. + *644) cp_umask=133;; + *755) cp_umask=22;; + + *[0-7]) + if test -z "$stripcmd"; then + u_plus_rw= + else + u_plus_rw='% 200' + fi + cp_umask=`expr '(' 777 - $mode % 1000 ')' $u_plus_rw`;; + *) + if test -z "$stripcmd"; then + u_plus_rw= + else + u_plus_rw=,u+rw + fi + cp_umask=$mode$u_plus_rw;; + esac +fi + +for src +do + # Protect names problematic for `test' and other utilities. + case $src in + -* | [=\(\)!]) src=./$src;; + esac + + if test -n "$dir_arg"; then + dst=$src + dstdir=$dst + test -d "$dstdir" + dstdir_status=$? + else + + # Waiting for this to be detected by the "$cpprog $src $dsttmp" command + # might cause directories to be created, which would be especially bad + # if $src (and thus $dsttmp) contains '*'. + if test ! -f "$src" && test ! -d "$src"; then + echo "$0: $src does not exist." >&2 + exit 1 + fi + + if test -z "$dst_arg"; then + echo "$0: no destination specified." >&2 + exit 1 + fi + dst=$dst_arg + + # If destination is a directory, append the input filename; won't work + # if double slashes aren't ignored. + if test -d "$dst"; then + if test -n "$no_target_directory"; then + echo "$0: $dst_arg: Is a directory" >&2 + exit 1 + fi + dstdir=$dst + dst=$dstdir/`basename "$src"` + dstdir_status=0 + else + # Prefer dirname, but fall back on a substitute if dirname fails. + dstdir=` + (dirname "$dst") 2>/dev/null || + expr X"$dst" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$dst" : 'X\(//\)[^/]' \| \ + X"$dst" : 'X\(//\)$' \| \ + X"$dst" : 'X\(/\)' \| . 2>/dev/null || + echo X"$dst" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q' + ` + + test -d "$dstdir" + dstdir_status=$? + fi + fi + + obsolete_mkdir_used=false + + if test $dstdir_status != 0; then + case $posix_mkdir in + '') + # Create intermediate dirs using mode 755 as modified by the umask. + # This is like FreeBSD 'install' as of 1997-10-28. + umask=`umask` + case $stripcmd.$umask in + # Optimize common cases. + *[2367][2367]) mkdir_umask=$umask;; + .*0[02][02] | .[02][02] | .[02]) mkdir_umask=22;; + + *[0-7]) + mkdir_umask=`expr $umask + 22 \ + - $umask % 100 % 40 + $umask % 20 \ + - $umask % 10 % 4 + $umask % 2 + `;; + *) mkdir_umask=$umask,go-w;; + esac + + # With -d, create the new directory with the user-specified mode. + # Otherwise, rely on $mkdir_umask. + if test -n "$dir_arg"; then + mkdir_mode=-m$mode + else + mkdir_mode= + fi + + posix_mkdir=false + case $umask in + *[123567][0-7][0-7]) + # POSIX mkdir -p sets u+wx bits regardless of umask, which + # is incompatible with FreeBSD 'install' when (umask & 300) != 0. + ;; + *) + tmpdir=${TMPDIR-/tmp}/ins$RANDOM-$$ + trap 'ret=$?; rmdir "$tmpdir/d" "$tmpdir" 2>/dev/null; exit $ret' 0 + + if (umask $mkdir_umask && + exec $mkdirprog $mkdir_mode -p -- "$tmpdir/d") >/dev/null 2>&1 + then + if test -z "$dir_arg" || { + # Check for POSIX incompatibilities with -m. + # HP-UX 11.23 and IRIX 6.5 mkdir -m -p sets group- or + # other-writeable bit of parent directory when it shouldn't. + # FreeBSD 6.1 mkdir -m -p sets mode of existing directory. + ls_ld_tmpdir=`ls -ld "$tmpdir"` + case $ls_ld_tmpdir in + d????-?r-*) different_mode=700;; + d????-?--*) different_mode=755;; + *) false;; + esac && + $mkdirprog -m$different_mode -p -- "$tmpdir" && { + ls_ld_tmpdir_1=`ls -ld "$tmpdir"` + test "$ls_ld_tmpdir" = "$ls_ld_tmpdir_1" + } + } + then posix_mkdir=: + fi + rmdir "$tmpdir/d" "$tmpdir" + else + # Remove any dirs left behind by ancient mkdir implementations. + rmdir ./$mkdir_mode ./-p ./-- 2>/dev/null + fi + trap '' 0;; + esac;; + esac + + if + $posix_mkdir && ( + umask $mkdir_umask && + $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir" + ) + then : + else + + # The umask is ridiculous, or mkdir does not conform to POSIX, + # or it failed possibly due to a race condition. Create the + # directory the slow way, step by step, checking for races as we go. + + case $dstdir in + /*) prefix='/';; + [-=\(\)!]*) prefix='./';; + *) prefix='';; + esac + + eval "$initialize_posix_glob" + + oIFS=$IFS + IFS=/ + $posix_glob set -f + set fnord $dstdir + shift + $posix_glob set +f + IFS=$oIFS + + prefixes= + + for d + do + test X"$d" = X && continue + + prefix=$prefix$d + if test -d "$prefix"; then + prefixes= + else + if $posix_mkdir; then + (umask=$mkdir_umask && + $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir") && break + # Don't fail if two instances are running concurrently. + test -d "$prefix" || exit 1 + else + case $prefix in + *\'*) qprefix=`echo "$prefix" | sed "s/'/'\\\\\\\\''/g"`;; + *) qprefix=$prefix;; + esac + prefixes="$prefixes '$qprefix'" + fi + fi + prefix=$prefix/ + done + + if test -n "$prefixes"; then + # Don't fail if two instances are running concurrently. + (umask $mkdir_umask && + eval "\$doit_exec \$mkdirprog $prefixes") || + test -d "$dstdir" || exit 1 + obsolete_mkdir_used=true + fi + fi + fi + + if test -n "$dir_arg"; then + { test -z "$chowncmd" || $doit $chowncmd "$dst"; } && + { test -z "$chgrpcmd" || $doit $chgrpcmd "$dst"; } && + { test "$obsolete_mkdir_used$chowncmd$chgrpcmd" = false || + test -z "$chmodcmd" || $doit $chmodcmd $mode "$dst"; } || exit 1 + else + + # Make a couple of temp file names in the proper directory. + dsttmp=$dstdir/_inst.$$_ + rmtmp=$dstdir/_rm.$$_ + + # Trap to clean up those temp files at exit. + trap 'ret=$?; rm -f "$dsttmp" "$rmtmp" && exit $ret' 0 + + # Copy the file name to the temp name. + (umask $cp_umask && $doit_exec $cpprog "$src" "$dsttmp") && + + # and set any options; do chmod last to preserve setuid bits. + # + # If any of these fail, we abort the whole thing. If we want to + # ignore errors from any of these, just make sure not to ignore + # errors from the above "$doit $cpprog $src $dsttmp" command. + # + { test -z "$chowncmd" || $doit $chowncmd "$dsttmp"; } && + { test -z "$chgrpcmd" || $doit $chgrpcmd "$dsttmp"; } && + { test -z "$stripcmd" || $doit $stripcmd "$dsttmp"; } && + { test -z "$chmodcmd" || $doit $chmodcmd $mode "$dsttmp"; } && + + # If -C, don't bother to copy if it wouldn't change the file. + if $copy_on_change && + old=`LC_ALL=C ls -dlL "$dst" 2>/dev/null` && + new=`LC_ALL=C ls -dlL "$dsttmp" 2>/dev/null` && + + eval "$initialize_posix_glob" && + $posix_glob set -f && + set X $old && old=:$2:$4:$5:$6 && + set X $new && new=:$2:$4:$5:$6 && + $posix_glob set +f && + + test "$old" = "$new" && + $cmpprog "$dst" "$dsttmp" >/dev/null 2>&1 + then + rm -f "$dsttmp" + else + # Rename the file to the real destination. + $doit $mvcmd -f "$dsttmp" "$dst" 2>/dev/null || + + # The rename failed, perhaps because mv can't rename something else + # to itself, or perhaps because mv is so ancient that it does not + # support -f. + { + # Now remove or move aside any old file at destination location. + # We try this two ways since rm can't unlink itself on some + # systems and the destination file might be busy for other + # reasons. In this case, the final cleanup might fail but the new + # file should still install successfully. + { + test ! -f "$dst" || + $doit $rmcmd -f "$dst" 2>/dev/null || + { $doit $mvcmd -f "$dst" "$rmtmp" 2>/dev/null && + { $doit $rmcmd -f "$rmtmp" 2>/dev/null; :; } + } || + { echo "$0: cannot unlink or rename $dst" >&2 + (exit 1); exit 1 + } + } && + + # Now rename the file to the real destination. + $doit $mvcmd "$dsttmp" "$dst" + } + fi || exit 1 + + trap '' 0 + fi +done + +# Local variables: +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "scriptversion=" +# time-stamp-format: "%:y-%02m-%02d.%02H" +# time-stamp-time-zone: "UTC" +# time-stamp-end: "; # UTC" +# End: diff --git a/m4/acx_libwrap.m4 b/m4/acx_libwrap.m4 new file mode 100644 index 0000000..ccf8afc --- /dev/null +++ b/m4/acx_libwrap.m4 @@ -0,0 +1,19 @@ +AC_DEFUN([ACX_LIBWRAP], [ +LIBWRAP_LIBS= +saved_LIBS="$LIBS" +LIBS="$LIBS -lwrap" +AC_MSG_CHECKING([for tcpwrap library and headers]) +AC_LINK_IFELSE( +[AC_LANG_PROGRAM( +[#include +#include +int allow_severity = LOG_INFO; +int deny_severity = LOG_WARNING;], +[struct request_info *req; +return hosts_access (req);])], +[AC_DEFINE(HAVE_LIBWRAP, [], [Have tcpwrap?]) +LIBWRAP_LIBS="-lwrap" +AC_MSG_RESULT(yes)], +[AC_MSG_RESULT(no)]) +LIBS="$saved_LIBS" +]) diff --git a/m4/ax_check_define.m4 b/m4/ax_check_define.m4 new file mode 100644 index 0000000..4bc6948 --- /dev/null +++ b/m4/ax_check_define.m4 @@ -0,0 +1,92 @@ +# =========================================================================== +# http://www.gnu.org/software/autoconf-archive/ax_check_define.html +# =========================================================================== +# +# SYNOPSIS +# +# AC_CHECK_DEFINE([symbol], [ACTION-IF-FOUND], [ACTION-IF-NOT]) +# AX_CHECK_DEFINE([includes],[symbol], [ACTION-IF-FOUND], [ACTION-IF-NOT]) +# +# DESCRIPTION +# +# Complements AC_CHECK_FUNC but it does not check for a function but for a +# define to exist. Consider a usage like: +# +# AC_CHECK_DEFINE(__STRICT_ANSI__, CFLAGS="$CFLAGS -D_XOPEN_SOURCE=500") +# +# LICENSE +# +# Copyright (c) 2008 Guido U. Draheim +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; either version 3 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see . +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. + +#serial 8 + +AU_ALIAS([AC_CHECK_DEFINED], [AC_CHECK_DEFINE]) +AC_DEFUN([AC_CHECK_DEFINE],[ +AS_VAR_PUSHDEF([ac_var],[ac_cv_defined_$1])dnl +AC_CACHE_CHECK([for $1 defined], ac_var, +AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[]], [[ + #ifdef $1 + int ok; + #else + choke me + #endif +]])],[AS_VAR_SET(ac_var, yes)],[AS_VAR_SET(ac_var, no)])) +AS_IF([test AS_VAR_GET(ac_var) != "no"], [$2], [$3])dnl +AS_VAR_POPDEF([ac_var])dnl +]) + +AU_ALIAS([AX_CHECK_DEFINED], [AX_CHECK_DEFINE]) +AC_DEFUN([AX_CHECK_DEFINE],[ +AS_VAR_PUSHDEF([ac_var],[ac_cv_defined_$2_$1])dnl +AC_CACHE_CHECK([for $2 defined in $1], ac_var, +AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include <$1>]], [[ + #ifdef $2 + int ok; + #else + choke me + #endif +]])],[AS_VAR_SET(ac_var, yes)],[AS_VAR_SET(ac_var, no)])) +AS_IF([test AS_VAR_GET(ac_var) != "no"], [$3], [$4])dnl +AS_VAR_POPDEF([ac_var])dnl +]) + +AC_DEFUN([AX_CHECK_FUNC], +[AS_VAR_PUSHDEF([ac_var], [ac_cv_func_$2])dnl +AC_CACHE_CHECK([for $2], ac_var, +dnl AC_LANG_FUNC_LINK_TRY +[AC_LINK_IFELSE([AC_LANG_PROGRAM([$1 + #undef $2 + char $2 ();],[ + char (*f) () = $2; + return f != $2; ])], + [AS_VAR_SET(ac_var, yes)], + [AS_VAR_SET(ac_var, no)])]) +AS_IF([test AS_VAR_GET(ac_var) = yes], [$3], [$4])dnl +AS_VAR_POPDEF([ac_var])dnl +])# AC_CHECK_FUNC diff --git a/m4/ax_check_flag.m4 b/m4/ax_check_flag.m4 new file mode 100644 index 0000000..52405fd --- /dev/null +++ b/m4/ax_check_flag.m4 @@ -0,0 +1,147 @@ +# =========================================================================== +# http://www.gnu.org/software/autoconf-archive/ax_check_flag.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_CHECK_PREPROC_FLAG(FLAG, [ACTION-SUCCESS], [ACTION-FAILURE], [EXTRA-FLAGS]) +# AX_CHECK_COMPILE_FLAG(FLAG, [ACTION-SUCCESS], [ACTION-FAILURE], [EXTRA-FLAGS]) +# AX_CHECK_LINK_FLAG(FLAG, [ACTION-SUCCESS], [ACTION-FAILURE], [EXTRA-FLAGS]) +# AX_APPEND_FLAG(FLAG, [FLAGS-VARIABLE]) +# AX_APPEND_COMPILE_FLAGS([FLAG1 FLAG2 ...], [FLAGS-VARIABLE], [EXTRA-FLAGS]) +# AX_APPEND_LINK_FLAGS([FLAG1 FLAG2 ...], [FLAGS-VARIABLE], [EXTRA-FLAGS]) +# +# DESCRIPTION +# +# Check whether the given FLAG works with the current language's +# preprocessor/compiler/linker, or whether they give an error. (Warnings, +# however, are ignored.) +# +# ACTION-SUCCESS/ACTION-FAILURE are shell commands to execute on +# success/failure. +# +# If EXTRA-FLAGS is defined, it is added to the current language's default +# flags (e.g. CFLAGS) when the check is done. The check us thus made +# with the following flags: "CFLAGS EXTRA-FLAGS FLAG". EXTRA-FLAGS can +# for example be used to force the compiler to issue an error when a bad +# flag is given. +# +# AX_APPEND_FLAG appends the FLAG to the FLAG-VARIABLE shell variable or +# the current language's flags if not specified. FLAG is not added to +# FLAG-VARIABLE if it is already in the shell variable. +# +# AX_APPEND_COMPILE_FLAGS checks for each FLAG1, FLAG2, etc. using +# AX_CHECK_COMPILE_FLAG and if the check is successful the flag is added +# to the appropriate FLAGS variable with AX_APPEND_FLAG. The +# FLAGS-VARIABLE and EXTRA-FLAGS arguments are the same as in the other +# macros. AX_APPEND_LINK_FLAGS does the same for linker flags. +# +# NOTE: Based on AX_CHECK_COMPILER_FLAGS and AX_CFLAGS_GCC_OPTION. +# +# LICENSE +# +# Copyright (c) 2008 Guido U. Draheim +# Copyright (c) 2009 Steven G. Johnson +# Copyright (c) 2009 Matteo Frigo +# Copyright (c) 2011 Maarten Bosmans +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation, either version 3 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see . +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. + +#serial 2 + +AC_DEFUN([AX_CHECK_PREPROC_FLAG], +[AC_PREREQ(2.59) dnl for _AC_LANG_PREFIX +AS_VAR_PUSHDEF([CACHEVAR],[ax_cv_check_[]_AC_LANG_ABBREV[]cppflags_$4_$1])dnl +AC_CACHE_CHECK([whether _AC_LANG preprocessor accepts $1], CACHEVAR, [ + ax_check_save_flags=$CPPFLAGS + CPPFLAGS="$CPPFLAGS $4 $1" + AC_PREPROC_IFELSE([AC_LANG_PROGRAM()], + [AS_VAR_SET([CACHEVAR],[yes])], + [AS_VAR_SET([CACHEVAR],[no])]) + CPPFLAGS=$ax_check_save_flags]) +AS_VAR_IF([CACHEVAR], "yes", + [m4_default([$2], :)], + [m4_default([$3], :)]) +AS_VAR_POPDEF([CACHEVAR])dnl +])dnl AX_CHECK_PREPROC_FLAGS + +AC_DEFUN([AX_CHECK_COMPILE_FLAG], +[AC_PREREQ(2.59) dnl for _AC_LANG_PREFIX +AS_VAR_PUSHDEF([CACHEVAR],[ax_cv_check_[]_AC_LANG_ABBREV[]flags_$4_$1])dnl +AC_CACHE_CHECK([whether _AC_LANG compiler accepts $1], CACHEVAR, [ + ax_check_save_flags=$[]_AC_LANG_PREFIX[]FLAGS + _AC_LANG_PREFIX[]FLAGS="$[]_AC_LANG_PREFIX[]FLAGS $4 $1" + AC_COMPILE_IFELSE([AC_LANG_PROGRAM()], + [AS_VAR_SET([CACHEVAR],[yes])], + [AS_VAR_SET([CACHEVAR],[no])]) + _AC_LANG_PREFIX[]FLAGS=$ax_check_save_flags]) +AS_VAR_IF([CACHEVAR], "yes", + [m4_default([$2], :)], + [m4_default([$3], :)]) +AS_VAR_POPDEF([CACHEVAR])dnl +])dnl AX_CHECK_COMPILE_FLAGS + +AC_DEFUN([AX_CHECK_LINK_FLAG], +[AS_VAR_PUSHDEF([CACHEVAR],[ax_cv_check_ldflags_$4_$1])dnl +AC_CACHE_CHECK([whether the linker accepts $1], CACHEVAR, [ + ax_check_save_flags=$LDFLAGS + LDFLAGS="$LDFLAGS $4 $1" + AC_LINK_IFELSE([AC_LANG_PROGRAM()], + [AS_VAR_SET([CACHEVAR],[yes])], + [AS_VAR_SET([CACHEVAR],[no])]) + LDFLAGS=$ax_check_save_flags]) +AS_VAR_IF([CACHEVAR], "yes", + [m4_default([$2], :)], + [m4_default([$3], :)]) +AS_VAR_POPDEF([CACHEVAR])dnl +])dnl AX_CHECK_LINK_FLAGS + + +AC_DEFUN([AX_APPEND_FLAG], +[AC_PREREQ(2.59) dnl for _AC_LANG_PREFIX +AC_REQUIRE([AC_PROG_GREP]) +AS_VAR_PUSHDEF([FLAGS], [m4_default($2,_AC_LANG_PREFIX[]FLAGS)])dnl +AS_VAR_SET_IF([FLAGS], + [AS_IF([AS_ECHO(" $[]FLAGS ") | $GREP " $1 " 2>&1 >/dev/null], + [AC_RUN_LOG([: FLAGS already contains $1])], + [AC_RUN_LOG([: FLAGS="$FLAGS $1"]) + AS_VAR_APPEND([FLAGS], [" $1"])])], + [AS_VAR_SET([FLAGS],[$1])]) +AS_VAR_POPDEF([FLAGS])dnl +])dnl AX_APPEND_FLAG + +AC_DEFUN([AX_APPEND_COMPILE_FLAGS], +[for flag in $1; do + AX_CHECK_COMPILE_FLAG([$flag], [AX_APPEND_FLAG([$flag], [$2])], [], [$3]) +done +])dnl AX_APPEND_COMPILE_FLAGS + +AC_DEFUN([AX_APPEND_LINK_FLAGS], +[for flag in $1; do + AX_CHECK_LINK_FLAG([$flag], [AX_APPEND_FLAG([$flag], [m4_default([$2], [LDFLAGS])])], [], [$3]) +done +])dnl AX_APPEND_LINK_FLAGS diff --git a/m4/ax_define_dir.m4 b/m4/ax_define_dir.m4 new file mode 100644 index 0000000..b74d155 --- /dev/null +++ b/m4/ax_define_dir.m4 @@ -0,0 +1,49 @@ +# =========================================================================== +# http://www.gnu.org/software/autoconf-archive/ax_define_dir.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_DEFINE_DIR(VARNAME, DIR [, DESCRIPTION]) +# +# DESCRIPTION +# +# This macro sets VARNAME to the expansion of the DIR variable, taking +# care of fixing up ${prefix} and such. +# +# VARNAME is then offered as both an output variable and a C preprocessor +# symbol. +# +# Example: +# +# AX_DEFINE_DIR([DATADIR], [datadir], [Where data are placed to.]) +# +# LICENSE +# +# Copyright (c) 2008 Stepan Kasal +# Copyright (c) 2008 Andreas Schwab +# Copyright (c) 2008 Guido U. Draheim +# Copyright (c) 2008 Alexandre Oliva +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 6 + +AU_ALIAS([AC_DEFINE_DIR], [AX_DEFINE_DIR]) +AC_DEFUN([AX_DEFINE_DIR], [ + prefix_NONE= + exec_prefix_NONE= + test "x$prefix" = xNONE && prefix_NONE=yes && prefix=$ac_default_prefix + test "x$exec_prefix" = xNONE && exec_prefix_NONE=yes && exec_prefix=$prefix +dnl In Autoconf 2.60, ${datadir} refers to ${datarootdir}, which in turn +dnl refers to ${prefix}. Thus we have to use `eval' twice. + eval ax_define_dir="\"[$]$2\"" + eval ax_define_dir="\"$ax_define_dir\"" + AC_SUBST($1, "$ax_define_dir") + AC_DEFINE_UNQUOTED($1, "$ax_define_dir", [$3]) + test "$prefix_NONE" && prefix=NONE + test "$exec_prefix_NONE" && exec_prefix=NONE +]) diff --git a/m4/ax_pthread.m4 b/m4/ax_pthread.m4 new file mode 100644 index 0000000..6d400ed --- /dev/null +++ b/m4/ax_pthread.m4 @@ -0,0 +1,317 @@ +# =========================================================================== +# http://www.gnu.org/software/autoconf-archive/ax_pthread.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_PTHREAD([ACTION-IF-FOUND[, ACTION-IF-NOT-FOUND]]) +# +# DESCRIPTION +# +# This macro figures out how to build C programs using POSIX threads. It +# sets the PTHREAD_LIBS output variable to the threads library and linker +# flags, and the PTHREAD_CFLAGS output variable to any special C compiler +# flags that are needed. (The user can also force certain compiler +# flags/libs to be tested by setting these environment variables.) +# +# Also sets PTHREAD_CC to any special C compiler that is needed for +# multi-threaded programs (defaults to the value of CC otherwise). (This +# is necessary on AIX to use the special cc_r compiler alias.) +# +# NOTE: You are assumed to not only compile your program with these flags, +# but also link it with them as well. e.g. you should link with +# $PTHREAD_CC $CFLAGS $PTHREAD_CFLAGS $LDFLAGS ... $PTHREAD_LIBS $LIBS +# +# If you are only building threads programs, you may wish to use these +# variables in your default LIBS, CFLAGS, and CC: +# +# LIBS="$PTHREAD_LIBS $LIBS" +# CFLAGS="$CFLAGS $PTHREAD_CFLAGS" +# CC="$PTHREAD_CC" +# +# In addition, if the PTHREAD_CREATE_JOINABLE thread-attribute constant +# has a nonstandard name, defines PTHREAD_CREATE_JOINABLE to that name +# (e.g. PTHREAD_CREATE_UNDETACHED on AIX). +# +# Also HAVE_PTHREAD_PRIO_INHERIT is defined if pthread is found and the +# PTHREAD_PRIO_INHERIT symbol is defined when compiling with +# PTHREAD_CFLAGS. +# +# ACTION-IF-FOUND is a list of shell commands to run if a threads library +# is found, and ACTION-IF-NOT-FOUND is a list of commands to run it if it +# is not found. If ACTION-IF-FOUND is not specified, the default action +# will define HAVE_PTHREAD. +# +# Please let the authors know if this macro fails on any platform, or if +# you have any other suggestions or comments. This macro was based on work +# by SGJ on autoconf scripts for FFTW (http://www.fftw.org/) (with help +# from M. Frigo), as well as ac_pthread and hb_pthread macros posted by +# Alejandro Forero Cuervo to the autoconf macro repository. We are also +# grateful for the helpful feedback of numerous users. +# +# Updated for Autoconf 2.68 by Daniel Richard G. +# +# LICENSE +# +# Copyright (c) 2008 Steven G. Johnson +# Copyright (c) 2011 Daniel Richard G. +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation, either version 3 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see . +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. + +#serial 20 + +AU_ALIAS([ACX_PTHREAD], [AX_PTHREAD]) +AC_DEFUN([AX_PTHREAD], [ +AC_REQUIRE([AC_CANONICAL_HOST]) +AC_LANG_PUSH([C]) +ax_pthread_ok=no + +# We used to check for pthread.h first, but this fails if pthread.h +# requires special compiler flags (e.g. on True64 or Sequent). +# It gets checked for in the link test anyway. + +# First of all, check if the user has set any of the PTHREAD_LIBS, +# etcetera environment variables, and if threads linking works using +# them: +if test x"$PTHREAD_LIBS$PTHREAD_CFLAGS" != x; then + save_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + save_LIBS="$LIBS" + LIBS="$PTHREAD_LIBS $LIBS" + AC_MSG_CHECKING([for pthread_join in LIBS=$PTHREAD_LIBS with CFLAGS=$PTHREAD_CFLAGS]) + AC_TRY_LINK_FUNC(pthread_join, ax_pthread_ok=yes) + AC_MSG_RESULT($ax_pthread_ok) + if test x"$ax_pthread_ok" = xno; then + PTHREAD_LIBS="" + PTHREAD_CFLAGS="" + fi + LIBS="$save_LIBS" + CFLAGS="$save_CFLAGS" +fi + +# We must check for the threads library under a number of different +# names; the ordering is very important because some systems +# (e.g. DEC) have both -lpthread and -lpthreads, where one of the +# libraries is broken (non-POSIX). + +# Create a list of thread flags to try. Items starting with a "-" are +# C compiler flags, and other items are library names, except for "none" +# which indicates that we try without any flags at all, and "pthread-config" +# which is a program returning the flags for the Pth emulation library. + +ax_pthread_flags="pthreads none -Kthread -kthread lthread -pthread -pthreads -mthreads pthread --thread-safe -mt pthread-config" + +# The ordering *is* (sometimes) important. Some notes on the +# individual items follow: + +# pthreads: AIX (must check this before -lpthread) +# none: in case threads are in libc; should be tried before -Kthread and +# other compiler flags to prevent continual compiler warnings +# -Kthread: Sequent (threads in libc, but -Kthread needed for pthread.h) +# -kthread: FreeBSD kernel threads (preferred to -pthread since SMP-able) +# lthread: LinuxThreads port on FreeBSD (also preferred to -pthread) +# -pthread: Linux/gcc (kernel threads), BSD/gcc (userland threads) +# -pthreads: Solaris/gcc +# -mthreads: Mingw32/gcc, Lynx/gcc +# -mt: Sun Workshop C (may only link SunOS threads [-lthread], but it +# doesn't hurt to check since this sometimes defines pthreads too; +# also defines -D_REENTRANT) +# ... -mt is also the pthreads flag for HP/aCC +# pthread: Linux, etcetera +# --thread-safe: KAI C++ +# pthread-config: use pthread-config program (for GNU Pth library) + +case ${host_os} in + solaris*) + + # On Solaris (at least, for some versions), libc contains stubbed + # (non-functional) versions of the pthreads routines, so link-based + # tests will erroneously succeed. (We need to link with -pthreads/-mt/ + # -lpthread.) (The stubs are missing pthread_cleanup_push, or rather + # a function called by this macro, so we could check for that, but + # who knows whether they'll stub that too in a future libc.) So, + # we'll just look for -pthreads and -lpthread first: + + ax_pthread_flags="-pthreads pthread -mt -pthread $ax_pthread_flags" + ;; + + darwin*) + ax_pthread_flags="-pthread $ax_pthread_flags" + ;; +esac + +if test x"$ax_pthread_ok" = xno; then +for flag in $ax_pthread_flags; do + + case $flag in + none) + AC_MSG_CHECKING([whether pthreads work without any flags]) + ;; + + -*) + AC_MSG_CHECKING([whether pthreads work with $flag]) + PTHREAD_CFLAGS="$flag" + ;; + + pthread-config) + AC_CHECK_PROG(ax_pthread_config, pthread-config, yes, no) + if test x"$ax_pthread_config" = xno; then continue; fi + PTHREAD_CFLAGS="`pthread-config --cflags`" + PTHREAD_LIBS="`pthread-config --ldflags` `pthread-config --libs`" + ;; + + *) + AC_MSG_CHECKING([for the pthreads library -l$flag]) + PTHREAD_LIBS="-l$flag" + ;; + esac + + save_LIBS="$LIBS" + save_CFLAGS="$CFLAGS" + LIBS="$PTHREAD_LIBS $LIBS" + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + + # Check for various functions. We must include pthread.h, + # since some functions may be macros. (On the Sequent, we + # need a special flag -Kthread to make this header compile.) + # We check for pthread_join because it is in -lpthread on IRIX + # while pthread_create is in libc. We check for pthread_attr_init + # due to DEC craziness with -lpthreads. We check for + # pthread_cleanup_push because it is one of the few pthread + # functions on Solaris that doesn't have a non-functional libc stub. + # We try pthread_create on general principles. + AC_LINK_IFELSE([AC_LANG_PROGRAM([#include + static void routine(void *a) { a = 0; } + static void *start_routine(void *a) { return a; }], + [pthread_t th; pthread_attr_t attr; + pthread_create(&th, 0, start_routine, 0); + pthread_join(th, 0); + pthread_attr_init(&attr); + pthread_cleanup_push(routine, 0); + pthread_cleanup_pop(0) /* ; */])], + [ax_pthread_ok=yes], + []) + + LIBS="$save_LIBS" + CFLAGS="$save_CFLAGS" + + AC_MSG_RESULT($ax_pthread_ok) + if test "x$ax_pthread_ok" = xyes; then + break; + fi + + PTHREAD_LIBS="" + PTHREAD_CFLAGS="" +done +fi + +# Various other checks: +if test "x$ax_pthread_ok" = xyes; then + save_LIBS="$LIBS" + LIBS="$PTHREAD_LIBS $LIBS" + save_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + + # Detect AIX lossage: JOINABLE attribute is called UNDETACHED. + AC_MSG_CHECKING([for joinable pthread attribute]) + attr_name=unknown + for attr in PTHREAD_CREATE_JOINABLE PTHREAD_CREATE_UNDETACHED; do + AC_LINK_IFELSE([AC_LANG_PROGRAM([#include ], + [int attr = $attr; return attr /* ; */])], + [attr_name=$attr; break], + []) + done + AC_MSG_RESULT($attr_name) + if test "$attr_name" != PTHREAD_CREATE_JOINABLE; then + AC_DEFINE_UNQUOTED(PTHREAD_CREATE_JOINABLE, $attr_name, + [Define to necessary symbol if this constant + uses a non-standard name on your system.]) + fi + + AC_MSG_CHECKING([if more special flags are required for pthreads]) + flag=no + case ${host_os} in + aix* | freebsd* | darwin*) flag="-D_THREAD_SAFE";; + osf* | hpux*) flag="-D_REENTRANT";; + solaris*) + if test "$GCC" = "yes"; then + flag="-D_REENTRANT" + else + flag="-mt -D_REENTRANT" + fi + ;; + esac + AC_MSG_RESULT(${flag}) + if test "x$flag" != xno; then + PTHREAD_CFLAGS="$flag $PTHREAD_CFLAGS" + fi + + AC_CACHE_CHECK([for PTHREAD_PRIO_INHERIT], + ax_cv_PTHREAD_PRIO_INHERIT, [ + AC_LINK_IFELSE([ + AC_LANG_PROGRAM([[#include ]], [[int i = PTHREAD_PRIO_INHERIT;]])], + [ax_cv_PTHREAD_PRIO_INHERIT=yes], + [ax_cv_PTHREAD_PRIO_INHERIT=no]) + ]) + AS_IF([test "x$ax_cv_PTHREAD_PRIO_INHERIT" = "xyes"], + AC_DEFINE([HAVE_PTHREAD_PRIO_INHERIT], 1, [Have PTHREAD_PRIO_INHERIT.])) + + LIBS="$save_LIBS" + CFLAGS="$save_CFLAGS" + + # More AIX lossage: compile with *_r variant + if test "x$GCC" != xyes; then + case $host_os in + aix*) + AS_CASE(["x/$CC"], + [x*/c89|x*/c89_128|x*/c99|x*/c99_128|x*/cc|x*/cc128|x*/xlc|x*/xlc_v6|x*/xlc128|x*/xlc128_v6], + [#handle absolute path differently from PATH based program lookup + AS_CASE(["x$CC"], + [x/*], + [AS_IF([AS_EXECUTABLE_P([${CC}_r])],[PTHREAD_CC="${CC}_r"])], + [AC_CHECK_PROGS([PTHREAD_CC],[${CC}_r],[$CC])])]) + ;; + esac + fi +fi + +test -n "$PTHREAD_CC" || PTHREAD_CC="$CC" + +AC_SUBST(PTHREAD_LIBS) +AC_SUBST(PTHREAD_CFLAGS) +AC_SUBST(PTHREAD_CC) + +# Finally, execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND: +if test x"$ax_pthread_ok" = xyes; then + ifelse([$1],,AC_DEFINE(HAVE_PTHREAD,1,[Define if you have POSIX threads libraries and header files.]),[$1]) + : +else + ax_pthread_ok=no + $2 +fi +AC_LANG_POP +])dnl AX_PTHREAD diff --git a/m4/ax_tls.m4 b/m4/ax_tls.m4 new file mode 100644 index 0000000..033e3b1 --- /dev/null +++ b/m4/ax_tls.m4 @@ -0,0 +1,76 @@ +# =========================================================================== +# http://www.gnu.org/software/autoconf-archive/ax_tls.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_TLS([action-if-found], [action-if-not-found]) +# +# DESCRIPTION +# +# Provides a test for the compiler support of thread local storage (TLS) +# extensions. Defines TLS if it is found. Currently knows about GCC/ICC +# and MSVC. I think SunPro uses the same as GCC, and Borland apparently +# supports either. +# +# LICENSE +# +# Copyright (c) 2008 Alan Woodland +# Copyright (c) 2010 Diego Elio Petteno` +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation, either version 3 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see . +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. + +#serial 10 + +AC_DEFUN([AX_TLS], [ + AC_MSG_CHECKING(for thread local storage (TLS) class) + AC_CACHE_VAL(ac_cv_tls, [ + ax_tls_keywords="__thread __declspec(thread) none" + for ax_tls_keyword in $ax_tls_keywords; do + AS_CASE([$ax_tls_keyword], + [none], [ac_cv_tls=none ; break], + [AC_TRY_COMPILE( + [#include + static void + foo(void) { + static ] $ax_tls_keyword [ int bar; + exit(1); + }], + [], + [ac_cv_tls=$ax_tls_keyword ; break], + ac_cv_tls=none + )]) + done + ]) + AC_MSG_RESULT($ac_cv_tls) + + AS_IF([test "$ac_cv_tls" != "none"], + AC_DEFINE_UNQUOTED([TLS], $ac_cv_tls, [If the compiler supports a TLS storage class define it to that here]) + m4_ifnblank([$1], [$1]), + m4_ifnblank([$2], [$2]) + ) +]) diff --git a/m4/orc.m4 b/m4/orc.m4 new file mode 100644 index 0000000..983d462 --- /dev/null +++ b/m4/orc.m4 @@ -0,0 +1,69 @@ +dnl pkg-config-based checks for Orc + +dnl specific: +dnl ORC_CHECK([REQUIRED_VERSION]) + +AC_DEFUN([ORC_CHECK], +[ + ORC_REQ=ifelse([$1], , "0.4.6", [$1]) + + AC_ARG_ENABLE(orc, + AC_HELP_STRING([--enable-orc],[use Orc if installed]), + [case "${enableval}" in + auto) enable_orc=auto ;; + yes) enable_orc=yes ;; + no) enable_orc=no ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-orc) ;; + esac + ], + [enable_orc=auto]) dnl Default value + + if test "x$enable_orc" != "xno" ; then + PKG_CHECK_MODULES(ORC, orc-0.4 >= $ORC_REQ, [ + AC_DEFINE(HAVE_ORC, 1, [Use Orc]) + HAVE_ORC=yes + if test "x$ORCC" = "x" ; then + AC_MSG_CHECKING(for usable orcc) + ORCC=`$PKG_CONFIG --variable=orcc orc-0.4` + dnl check whether the orcc found by pkg-config can be run from the build environment + dnl if this is not the case (e.g. when cross-compiling) fall back to orcc from PATH + AS_IF([$ORCC --version 1> /dev/null 2> /dev/null], [], [ORCC=`which orcc`]) + AC_MSG_RESULT($ORCC) + fi + AC_SUBST(ORCC) + ORCC_FLAGS="--compat $ORC_REQ" + AC_SUBST(ORCC_FLAGS) + AS_IF([test "x$ORCC" = "x"], [HAVE_ORCC=no], [HAVE_ORCC=yes]) + ], [ + if test "x$enable_orc" = "xyes" ; then + AC_MSG_ERROR([--enable-orc specified, but Orc >= $ORC_REQ not found]) + fi + AC_DEFINE(DISABLE_ORC, 1, [Disable Orc]) + HAVE_ORC=no + HAVE_ORCC=no + ]) + else + AC_DEFINE(DISABLE_ORC, 1, [Disable Orc]) + HAVE_ORC=no + HAVE_ORCC=no + fi + AM_CONDITIONAL(HAVE_ORC, [test "x$HAVE_ORC" = "xyes"]) + AM_CONDITIONAL(HAVE_ORCC, [test "x$HAVE_ORCC" = "xyes"]) + +])) + +AC_DEFUN([ORC_OUTPUT], +[ + if test "$HAVE_ORC" = yes ; then + printf "configure: *** Orc acceleration enabled.\n" + else + if test "x$enable_orc" = "xno" ; then + printf "configure: *** Orc acceleration disabled by --disable-orc. Slower code paths\n" + printf " will be used.\n" + else + printf "configure: *** Orc acceleration disabled. Requires Orc >= $ORC_REQ, which was\n" + printf " not found. Slower code paths will be used.\n" + fi + fi + printf "\n" +]) diff --git a/packaging/pulseaudio-modules-tizen.spec b/packaging/pulseaudio-modules-tizen.spec new file mode 100644 index 0000000..83d0ddf --- /dev/null +++ b/packaging/pulseaudio-modules-tizen.spec @@ -0,0 +1,79 @@ +%define pulseversion 5.0 +%define udev_dir %{_prefix}/lib/udev + +Name: pulseaudio-modules-tizen +Summary: Improved Linux sound server +Version: 5.0 +Release: 40 +Group: Multimedia/Audio +License: LGPL-2.1+ +URL: http://pulseaudio.org +Source0: http://www.freedesktop.org/software/pulseaudio/releases/%{name}-%{version}.tar.gz +BuildRequires: libtool-ltdl-devel +BuildRequires: libtool +BuildRequires: intltool +BuildRequires: pkgconfig(alsa) +BuildRequires: pkgconfig(dbus-1) +BuildRequires: pkgconfig(iniparser) +BuildRequires: pkgconfig(libudev) +BuildRequires: pkgconfig(json) +BuildRequires: pkgconfig(vconf) +BuildRequires: pkgconfig(libpulse) +BuildRequires: pkgconfig(pulsecore) +BuildRequires: pulseaudio +BuildRequires: m4 +BuildRequires: systemd-devel +BuildRequires: libcap-devel +%if %{with pulseaudio_dlog} +BuildRequires: pkgconfig(dlog) +%endif +Requires: udev +Requires(post): /sbin/ldconfig +Requires(postun): /sbin/ldconfig + +%description +This package contains pulseaudio modules for tizen audio system. + +%prep +%setup -q + +%build +export CFLAGS="%{optflags} -fno-strict-aliasing -D__TIZEN__ -D__TIZEN_BT__ -D__TIZEN_LOG__ -DTIZEN_MICRO -DBLUETOOTH_APTX_SUPPORT" +%if 0%{?sec_build_binary_debug_enable} +export CFLAGS+=" -DTIZEN_DEBUG_ENABLE" +export CXXFLAGS="$CXXFLAGS -DTIZEN_DEBUG_ENABLE" +export FFLAGS="$FFLAGS -DTIZEN_DEBUG_ENABLE" +%endif + +export LD_AS_NEEDED=0 +%reconfigure --prefix=%{_prefix} \ + --disable-static \ + --enable-alsa \ + --enable-systemd \ + --with-database=tdb \ +%if %{with pulseaudio_dlog} + --enable-dlog \ +%endif + --with-udev-rules-dir=%{udev_dir}/rules.d \ + --with-system-user=pulse \ + --with-system-group=pulse \ + --with-access-group=pulse-access + +%__make %{?_smp_mflags} V=1 + +%install +%make_install + +%post +/sbin/ldconfig + +%postun +/sbin/ldconfig + +%files +%manifest pulseaudio-modules-tizen.manifest +%defattr(-,root,root,-) +%license LICENSE.LGPL-2.1+ +%{_libdir}/pulse-%{version}/modules/module-tizenaudio-sink.so +%{_libdir}/pulse-%{version}/modules/module-sound-player.so +%{_libdir}/pulse-%{version}/modules/module-policy.so diff --git a/pulseaudio-modules-tizen.manifest b/pulseaudio-modules-tizen.manifest new file mode 100644 index 0000000..2a0cec5 --- /dev/null +++ b/pulseaudio-modules-tizen.manifest @@ -0,0 +1,5 @@ + + + + + diff --git a/src/communicator.c b/src/communicator.c new file mode 100644 index 0000000..1a06abc --- /dev/null +++ b/src/communicator.c @@ -0,0 +1,90 @@ +/*** + This file is part of PulseAudio. + + Copyright 2015 Sangchul Lee + + PulseAudio 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. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "communicator.h" +#include + +struct _pa_communicator { + PA_REFCNT_DECLARE; + + pa_core *core; + pa_hook hooks[PA_COMMUNICATOR_HOOK_MAX]; +}; + +pa_communicator* pa_communicator_get(pa_core *core) { + pa_communicator *c; + unsigned i; + + pa_assert(core); + + if ((c = pa_shared_get(core, "communicator"))) + return pa_communicator_ref(c); + + c = pa_xnew0(pa_communicator, 1); + PA_REFCNT_INIT(c); + c->core = core; + + for (i = 0; i < PA_COMMUNICATOR_HOOK_MAX; i++) + pa_hook_init(&c->hooks[i], c); + + pa_shared_set(core, "communicator", c); + + return c; +} + +pa_communicator* pa_communicator_ref(pa_communicator *c) { + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) > 0); + + PA_REFCNT_INC(c); + + return c; +} + +void pa_communicator_unref(pa_communicator *c) { + unsigned i; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) > 0); + + if (PA_REFCNT_DEC(c) > 0) + return; + + for (i = 0; i < PA_COMMUNICATOR_HOOK_MAX; i++) + pa_hook_done(&c->hooks[i]); + + if (c->core) + pa_shared_remove(c->core, "communicator"); + + pa_xfree(c); +} + +pa_hook* pa_communicator_hook(pa_communicator *c, pa_communicator_hook_t hook) { + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) > 0); + + return &c->hooks[hook]; +} + diff --git a/src/communicator.h b/src/communicator.h new file mode 100644 index 0000000..790652d --- /dev/null +++ b/src/communicator.h @@ -0,0 +1,21 @@ +#ifndef foocommunicatorfoo +#define foocommunicatorfoo +#include + +typedef enum pa_communicator_hook { + PA_COMMUNICATOR_HOOK_SELECT_INIT_SINK_OR_SOURCE, + PA_COMMUNICATOR_HOOK_CHANGE_ROUTE, + PA_COMMUNICATOR_HOOK_UPDATE_ROUTE_OPTION, + PA_COMMUNICATOR_HOOK_DEVICE_CONNECTION_CHANGED, + PA_COMMUNICATOR_HOOK_DEVICE_INFORMATION_CHANGED, + PA_COMMUNICATOR_HOOK_MAX +} pa_communicator_hook_t; + +typedef struct _pa_communicator pa_communicator; + +pa_communicator* pa_communicator_get(pa_core *c); +pa_communicator* pa_communicator_ref(pa_communicator *c); +void pa_communicator_unref(pa_communicator *c); +pa_hook* pa_communicator_hook(pa_communicator *c, pa_communicator_hook_t hook); + +#endif diff --git a/src/device-manager.c b/src/device-manager.c new file mode 100644 index 0000000..361b2ea --- /dev/null +++ b/src/device-manager.c @@ -0,0 +1,4277 @@ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#ifdef HAVE_DBUS +#include +#include +#include +#endif + +#include "communicator.h" +#include "device-manager.h" + +#define DEVICE_MAP_FILE "/etc/pulse/device-map.json" +#define DEVICE_PROFILE_MAX 2 +#define DEVICE_STR_MAX 30 +#define DEVICE_DIRECTION_MAX 3 +#define DEVICE_PARAM_STRING_MAX 50 +#define DEVICE_AVAIL_COND_NUM_MAX 2 +#define DEVICE_AVAIL_COND_STR_MAX 6 +#define DEVICE_FILE_PER_TYPE_MAX 4 +#define DEVICE_FILE_STRING_MAX 4 + +#define DEVICE_TYPE_OBJECT "device-types" +#define DEVICE_FILE_OBJECT "device-files" +#define DEVICE_TYPE_PROP_DEVICE_TYPE "device-type" +#define DEVICE_TYPE_PROP_BUILTIN "builtin" +#define DEVICE_TYPE_PROP_DIRECTION "direction" +#define DEVICE_TYPE_PROP_AVAIL_CONDITION "avail-conditioin" +#define DEVICE_TYPE_PROP_PLAYBACK_DEVICES "playback-devices" +#define DEVICE_TYPE_PROP_CAPTURE_DEVICES "capture-devices" +#define DEVICE_TYPE_PROP_DEVICE_STRING "device-string" +#define DEVICE_TYPE_PROP_DEFAULT_PARAMS "default-params" +#define DEVICE_TYPE_PROP_ROLE "role" + +#define DEVICE_TYPE_STR_MAX 20 + +#define DEVICE_DIRECTION_STR_NONE "none" +#define DEVICE_DIRECTION_STR_OUT "out" +#define DEVICE_DIRECTION_STR_IN "in" +#define DEVICE_DIRECTION_STR_BOTH "both" + +#define DEVICE_AVAIL_CONDITION_STR_PULSE "pulse" +#define DEVICE_AVAIL_CONDITION_STR_DBUS "dbus" + +/* Properties of sink/sources */ +#define DEVICE_API_BLUEZ "bluez" +#define DEVICE_API_ALSA "alsa" +#define DEVICE_API_NULL "null" +#define DEVICE_BUS_USB "usb" +#define DEVICE_CLASS_SOUND "sound" +#define DEVICE_CLASS_MONITOR "monitor" + +/* Dbus defines */ +#define DBUS_INTERFACE_DEVICE_MANAGER "org.pulseaudio.DeviceManager" +#define DBUS_OBJECT_DEVICE_MANAGER "/org/pulseaudio/DeviceManager" + +#define DBUS_INTERFACE_DEVICED_SYSNOTI "org.tizen.system.deviced.SysNoti" +#define DBUS_OBJECT_DEVICED_SYSNOTI "/Org/Tizen/System/DeviceD/SysNoti" + +#define DBUS_INTERFACE_SOUND_SERVER "org.tizen.SoundServer1" +#define DBUS_OBJECT_SOUND_SERVER "/org/tizen/SoundServer1" + +#define DBUS_SERVICE_BLUEZ "org.bluez" +#define DBUS_INTERFACE_BLUEZ_HEADSET "org.bluez.Headset" +#define DBUS_INTERFACE_BLUEZ_DEVICE "org.bluez.Device1" +#define DBUS_OBJECT_BLUEZ "/org/bluez" + +#define DBUS_INTERFACE_MIRRORING_SERVER "org.tizen.scmirroring.server" +#define DBUS_OBJECT_MIRRORING_SERVER "/org/tizen/scmirroring/server" + +#define DBUS_SERVICE_HFP_AGENT "org.bluez.ag_agent" +#define DBUS_OBJECT_HFP_AGENT "/org/bluez/hfp_agent" +#define DBUS_INTERFACE_HFP_AGENT "Org.Hfp.App.Interface" + +#define DEVICE_MANAGER_INTROSPECT_XML \ + DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ + "\n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " " \ + " " \ + " " \ + " " \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + "\n" + + +#define FILTER_DEVICED_SYSNOTI \ + "type='signal'," \ + " interface='" DBUS_INTERFACE_DEVICED_SYSNOTI "'" + +#define FILTER_SOUND_SERVER \ + "type='signal'," \ + " interface='" DBUS_INTERFACE_SOUND_SERVER "'" + +#define FILTER_MIRRORING \ + "type='signal'," \ + " interface='" DBUS_INTERFACE_MIRRORING_SERVER "', member='miracast_wfd_source_status_changed'" + +#define FILTER_BLUEZ \ + "type='signal'," \ + " interface='" DBUS_INTERFACE_BLUEZ_HEADSET "', member='PropertyChanged'" + +static const char* const valid_alsa_device_modargs[] = { + "name", + "sink_name", + "sink_properties", + "source_name", + "source_properties", + "namereg_fail", + "device", + "device_id", + "format", + "rate", + "alternate_rate", + "channels", + "channel_map", + "fragments", + "fragment_size", + "mmap", + "tsched", + "tsched_buffer_size", + "tsched_buffer_watermark", + "ignore_dB", + "control", + "rewind_safeguard", + "deferred_volume", + "deferred_volume_safety_margin", + "deferred_volume_extra_delay", + "fixed_latency_range", + "need_audio_pm", + "start_threshold", + NULL +}; + + + +/* A macro to ease iteration through all entries */ +#define PA_HASHMAP_FOREACH_KEY(e, h, state, key) \ + for ((state) = NULL, (e) = pa_hashmap_iterate((h), &(state),(const void**)&(key)); (e); (e) = pa_hashmap_iterate((h), &(state), (const void**)&(key))) + +#define PA_DEVICE(pulse_device, pdt) \ + pdt == PA_DEVICE_TYPE_SINK ? ((pa_sink *) pulse_device) : ((pa_source *) pulse_device) + +#define PA_DEVICES(core, pdt) \ + pdt == PA_DEVICE_TYPE_SINK ? (((pa_core *) core)->sinks) : (((pa_core *) core)->sources) + +#define MAKE_SINK(s) ((pa_sink*) (s)) +#define MAKE_SOURCE(s) ((pa_source*) (s)) + +#define COMPOUND_STATE(d) (((dm_device_profile*)d)->playback_state | ((dm_device_profile*)d)->capture_state) + +#define BT_CVSD_CODEC_ID 1 // narrow-band +#define BT_MSBC_CODEC_ID 2 // wide-band +/* + Enums for represent values which is defined on other module. + This is needed to identify values which are sent by dbus or vconf. +*/ +typedef enum external_value_earjack_type { + EARJACK_DISCONNECTED = 0, + EARJACK_TYPE_SPK_ONLY = 1, + EARJACK_TYPE_SPK_WITH_MIC = 3, +} external_value_earjack_t; + +typedef enum external_value_bt_sco_type { + BT_SCO_DISCONNECTED = 0, + BT_SCO_CONNECTED = 1, +} external_value_bt_sco_t; + +typedef enum external_value_forwarding_type { + FORWARDING_DISCONNECTED = 0, + FORWARDING_CONNECTED = 1, +} external_value_mirroring_t; + +typedef enum external_value_hdmi_type { + HDMI_AUDIO_DISCONNECTED = -1, + HDMI_AUDIO_NOT_AVAILABLE = 0, + HDMI_AUDIO_AVAILABLE = 1, +} external_value_hdmi_t; + + +/* + Enums for represent device detected status (through dbus) + When some device is detected, one of these values should be saved in device_status hashmap. + device_detected_type_t is needed to distinguish detected device-types ( ex. earjack which can be out or both way) + So If you just want to know whether detected or not, can device_detected_t as mask. +*/ +typedef enum device_detected { + DEVICE_NOT_DETECTED = 0x00, + DEVICE_DETECTED = 0x01, +} device_detected_t; + +typedef enum device_detected_type { + DEVICE_DETECTED_BT_SCO = DEVICE_DETECTED, + DEVICE_DETECTED_FORWARDING = DEVICE_DETECTED, + DEVICE_DETECTED_HDMI = DEVICE_DETECTED, + DEVICE_DETECTED_AUDIO_JACK_BOTH_DIREC = DEVICE_DETECTED | 0x2, + DEVICE_DETECTED_AUDIO_JACK_OUT_DIREC = DEVICE_DETECTED | 0x4, +} device_detected_type_t; + +typedef enum dm_device_class_type { + DM_DEVICE_CLASS_NONE, + DM_DEVICE_CLASS_ALSA, + DM_DEVICE_CLASS_TIZEN, + DM_DEVICE_CLASS_BT, + DM_DEVICE_CLASS_NULL, + DM_DEVICE_CLASS_MAX, +} dm_device_class_t; + + +typedef enum { + DEVICE_IO_DIRECTION_IN_FLAG = 0x0001, /**< Flag for input devices */ + DEVICE_IO_DIRECTION_OUT_FLAG = 0x0002, /**< Flag for output devices */ + DEVICE_IO_DIRECTION_BOTH_FLAG = 0x0004, /**< Flag for input/output devices (both directions are available) */ + DEVICE_TYPE_INTERNAL_FLAG = 0x0010, /**< Flag for built-in devices */ + DEVICE_TYPE_EXTERNAL_FLAG = 0x0020, /**< Flag for external devices */ + DEVICE_STATE_DEACTIVATED_FLAG = 0x1000, /**< Flag for deactivated devices */ + DEVICE_STATE_ACTIVATED_FLAG = 0x2000, /**< Flag for activated devices */ + DEVICE_ALL_FLAG = 0xFFFF, /**< Flag for all devices */ +} device_flag_t; + +typedef enum { + DEVICE_IO_DIRECTION_FLAGS = 0x000F, /**< Flag for io direction */ + DEVICE_TYPE_FLAGS = 0x00F0, /**< Flag for device type */ + DEVICE_STATE_FLAGS = 0xF000, /**< Flag for device state */ +} device_flags_type_t; + + +/************* structures for represent device items can be connected/disconnected */ +/* + Before beginning, There are two structure(dm_device, dm_device_profile) + for represent device by following reasons. + When bt-a2dp and bt-sco are on physically same device ,it is of course same device. + So those physically same device item are represended by dm_device, + and each profile is represented by dm_device_profile. +*/ + + +/* + Structure to represent physicall device. + This is profile known data-structure, which means it can have multiple profiles ( ex. bt-a2dp and sco) +*/ +struct dm_device { + uint32_t id; + char *type; + char *name; + const char *identifier; + + /* Indicate currently activated profile */ + uint32_t active_profile; + /* include profile_items(dm_device_profile) , currently has only one item except bt case*/ + pa_idxset *profiles; + + pa_device_manager *dm; +}; + +/* + Structure to represent each device profile (subtype). + Even if both-way device(earjack, sco..) , one device_profile. +*/ +typedef struct dm_device_profile { + char *profile; + dm_device_direction_t direction; + dm_device_state_t playback_state; + dm_device_state_t capture_state; + + /* Can get proper sink/source in hashmaps with key(=device_role) */ + pa_hashmap *playback_devices; + pa_hashmap *capture_devices; + + /* device belongs to */ + dm_device *device_item; +} dm_device_profile; + +/* + Structure to save parsed information about device-file. +*/ +struct device_file_map { + /* { key:device_string -> value:device_file_prop } */ + pa_idxset *playback; + pa_idxset *capture; +}; + +struct pa_device_manager { + pa_core *core; + pa_hook_slot *sink_put_hook_slot, *sink_unlink_hook_slot; + pa_hook_slot *source_put_hook_slot, *source_unlink_hook_slot; + pa_communicator *comm; + + /* + Idxset for save parsed information about device-type. + { device_type_info } + */ + pa_idxset *type_infos; + /* For save Parsed information about device-file */ + struct device_file_map *file_map; + + /* device list */ + pa_idxset *device_list; + /* + Hashmap for save statuses got through dbus. + { key:device_type -> value:(audio_detected_type_t or device_detected_t) } + */ + pa_idxset *device_status; + pa_dbus_connection *dbus_conn; +}; + +/***************** structures for static information get from json *********/ + +/* + Structure for informations related to some device-file(ex. 0:0) +*/ +struct device_file_info { + /* + String for identify target device. + ex) alsa:0,0 or null .. + */ + const char *device_string; + /* + For save roles which are supported on device file, and parameters. + { key:device_role -> value:parameters for load sink/source } + ex) "normal"->"rate=44100 tsched=0", "uhqa"->"rate=192000 mmap=1" + */ + pa_hashmap *roles; + /* + For save device-types related to device file. + { key:device_type-> value:pulse_device_prop } + */ + pa_hashmap *device_types; +}; + +/* structure for represent device-types(ex. builtin-speaker) properties*/ +struct device_type_info { + const char *type; + const char *profile; + pa_bool_t builtin; + /* + Possible directions of this device. + ex) speaker is always out, but earjack can be both or out. + */ + dm_device_direction_t direction[DEVICE_DIRECTION_MAX]; + /* + Conditions for make device available. + ex) Speaker be available, only if proper pcm-device exists. + but audio-jack is available, if pcm-device exists and got detected status. + */ + char avail_condition[DEVICE_AVAIL_COND_NUM_MAX][DEVICE_AVAIL_COND_STR_MAX]; + int num; + /* + For save supported roles and related device-file. + { key:role -> value:device_string ] + */ + pa_hashmap *playback_devices; + pa_hashmap *capture_devices; +}; + +struct device_status_info { + const char *type; + const char *profile; + /* Identify devices among same device-types (for multi-device), currently not works*/ + const char *identifier; + device_detected_t detected; + device_detected_type_t detected_type; +}; + +struct pulse_device_prop { + /* roles related to (device_type + device_file)*/ + pa_idxset *roles; + /* For save that this devie_type is activated or not on sink/source */ + int status; +}; +/******************************************************************************/ + +int device_id_max_g = 1; + +#ifdef HAVE_DBUS + +/*** Defines for method handle ***/ +/* method handlers */ +static void handle_get_connected_device_list(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_bt_a2dp_status(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_load_sink(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_test_device_status_change(DBusConnection *conn, DBusMessage *msg, void *userdata); + +static void notify_device_connection_changed(dm_device *device_item, pa_bool_t connected, pa_device_manager *dm); +static void notify_device_info_changed(dm_device *device_item, dm_device_changed_info_t changed_type, pa_device_manager *dm); + +static int method_call_bt_sco(DBusConnection *conn, pa_bool_t onoff); +static int method_call_bt_sco_get_property(DBusConnection *conn, pa_bool_t *is_wide_band, pa_bool_t *nrec); +static int method_call_bt_get_name(DBusConnection *conn, const char *device_path, char **name); + +enum method_handler_index { + METHOD_HANDLER_GET_CONNECTED_DEVICE_LIST, + METHOD_HANDLER_GET_BT_A2DP_STATUS, + METHOD_HANDLER_LOAD_SINK, + METHOD_HANDLER_STATUS_TEST, + METHOD_HANDLER_MAX +}; + +static pa_dbus_method_handler method_handlers[METHOD_HANDLER_MAX] = { + [METHOD_HANDLER_GET_CONNECTED_DEVICE_LIST] = { + .method_name = "GetConnectedDeviceList", + .receive_cb = handle_get_connected_device_list }, + [METHOD_HANDLER_GET_BT_A2DP_STATUS] = { + .method_name = "GetBTA2DPStatus", + .receive_cb = handle_get_bt_a2dp_status }, + [METHOD_HANDLER_LOAD_SINK] = { + .method_name = "LoadSink", + .receive_cb = handle_load_sink}, + [METHOD_HANDLER_STATUS_TEST] = { + .method_name = "TestStatusChange", + .receive_cb = handle_test_device_status_change}, +}; + +/*** Defines for signal send ***/ + +enum signal_index { + SIGNAL_DEVICE_CONNECTED, + SIGNAL_DEVICE_INFO_CHANGED, + SIGNAL_MAX +}; + +#endif + +static pa_bool_t device_type_is_valid(const char *device_type) { + if (!device_type) + return FALSE; + else if (pa_streq(device_type, DEVICE_TYPE_SPEAKER)) + return TRUE; + else if (pa_streq(device_type, DEVICE_TYPE_RECEIVER)) + return TRUE; + else if (pa_streq(device_type, DEVICE_TYPE_MIC)) + return TRUE; + else if (pa_streq(device_type, DEVICE_TYPE_AUDIO_JACK)) + return TRUE; + else if (pa_streq(device_type, DEVICE_TYPE_BT)) + return TRUE; + else if (pa_streq(device_type, DEVICE_TYPE_HDMI)) + return TRUE; + else if (pa_streq(device_type, DEVICE_TYPE_FORWARDING)) + return TRUE; + else if (pa_streq(device_type, DEVICE_TYPE_USB_AUDIO)) + return TRUE; + else + return FALSE; +} + +static const char* device_direction_to_string(dm_device_direction_t direction) { + if (direction <= DM_DEVICE_DIRECTION_NONE || direction > DM_DEVICE_DIRECTION_BOTH) { + return NULL; + } + + if (direction == DM_DEVICE_DIRECTION_NONE) + return DEVICE_DIRECTION_STR_NONE; + else if (direction == DM_DEVICE_DIRECTION_OUT) + return DEVICE_DIRECTION_STR_OUT; + else if (direction == DM_DEVICE_DIRECTION_IN) + return DEVICE_DIRECTION_STR_IN; + else if (direction == DM_DEVICE_DIRECTION_BOTH) + return DEVICE_DIRECTION_STR_BOTH; + else + return NULL; +} + +static pa_bool_t device_role_is_valid(const char *device_role) { + if (!device_role) + return FALSE; + else if (pa_streq(device_role, DEVICE_ROLE_NORMAL)) + return TRUE; + else if (pa_streq(device_role, DEVICE_ROLE_VOIP)) + return TRUE; + else if (pa_streq(device_role, DEVICE_ROLE_LOW_LATENCY)) + return TRUE; + else if (pa_streq(device_role, DEVICE_ROLE_HIGH_LATENCY)) + return TRUE; + else if (pa_streq(device_role, DEVICE_ROLE_UHQA)) + return TRUE; + else + return FALSE; +} + +static dm_device_direction_t device_direction_to_int(const char *device_direction) { + if (!device_direction) { + return -1; + } + + if (pa_streq(device_direction, DEVICE_DIRECTION_STR_NONE)) { + return DM_DEVICE_DIRECTION_NONE; + } else if (pa_streq(device_direction, DEVICE_DIRECTION_STR_OUT)) { + return DM_DEVICE_DIRECTION_OUT; + } else if (pa_streq(device_direction, DEVICE_DIRECTION_STR_IN)) { + return DM_DEVICE_DIRECTION_IN; + } else if (pa_streq(device_direction, DEVICE_DIRECTION_STR_BOTH)) { + return DM_DEVICE_DIRECTION_BOTH; + } else { + return -1; + } +} + +static void type_info_free_func(struct device_type_info *type_info) { + if (!type_info) + return; + + if (type_info->playback_devices) + pa_hashmap_free(type_info->playback_devices); + if (type_info->capture_devices) + pa_hashmap_free(type_info->capture_devices); + +} + +static void file_info_free_func(struct device_file_info *file_info) { + if (!file_info) + return; + + if (file_info->roles) + pa_hashmap_free(file_info->roles); +} + +static void profile_item_free_func(dm_device_profile *profile_item) { + if (!profile_item) + return; + + if (profile_item->profile) { + pa_xfree(profile_item->profile); + } + + if (profile_item->playback_devices) { + pa_hashmap_free(profile_item->playback_devices); + } + if (profile_item->capture_devices) { + pa_hashmap_free(profile_item->capture_devices); + } + + profile_item->device_item = NULL; + + pa_xfree(profile_item); +} + +static void device_item_free_func(dm_device *device_item) { + if (!device_item) + return; + + if (device_item->type) + pa_xfree(device_item->type); + if (device_item->name) + pa_xfree(device_item->name); + if (device_item->profiles) + pa_idxset_free(device_item->profiles, (pa_free_cb_t)profile_item_free_func); + + pa_xfree(device_item); +} + +static pa_proplist* pulse_device_get_proplist(void *pulse_device, pa_device_type_t pdt) { + if (pdt == PA_DEVICE_TYPE_SINK) + return MAKE_SINK(pulse_device)->proplist; + else + return MAKE_SOURCE(pulse_device)->proplist; +} + +static pa_core* pulse_device_get_core(void *pulse_device, pa_device_type_t pdt) { + if (pdt == PA_DEVICE_TYPE_SINK) + return MAKE_SINK(pulse_device)->core; + else + return MAKE_SOURCE(pulse_device)->core; +} + +static char* pulse_device_get_name(void *pulse_device, pa_device_type_t pdt) { + if (pdt == PA_DEVICE_TYPE_SINK) + return MAKE_SINK(pulse_device)->name; + else + return MAKE_SOURCE(pulse_device)->name; +} + +static pa_idxset* pulse_core_get_device_list(pa_core *core, pa_device_type_t pdt) { + if (pdt == PA_DEVICE_TYPE_SINK) + return core->sinks; + else + return core->sources; +} + +static pa_bool_t pulse_device_is_alsa(pa_proplist *prop) { + const char *api_name = NULL; + + if (!prop) { + return FALSE; + } + + if ((api_name = pa_proplist_gets(prop, PA_PROP_DEVICE_API))) { + if (pa_streq (api_name, DEVICE_API_ALSA)) { + return TRUE; + } else { + return FALSE; + } + } else { + return FALSE; + } +} + + +static pa_bool_t pulse_device_is_bluez(pa_proplist *prop) { + const char *api_name = NULL; + + if (!prop) { + return FALSE; + } + + if ((api_name = pa_proplist_gets(prop, PA_PROP_DEVICE_API))) { + if (pa_streq (api_name, DEVICE_API_BLUEZ)) { + return TRUE; + } else { + return FALSE; + } + } else { + return FALSE; + } +} + +static pa_bool_t pulse_device_is_tizenaudio(void *pulse_device, pa_device_type_t pdt) { + pa_sink *sink; + + if (!pulse_device) { + return FALSE; + } + + if (pdt == PA_DEVICE_TYPE_SOURCE) { + return FALSE; + } + + sink = (pa_sink *) pulse_device; + return pa_streq(sink->module->name, "module-tizenaudio-sink"); +} + +static pa_bool_t pulse_device_is_usb(pa_proplist *prop) { + const char *bus_name = NULL; + + if ((bus_name = pa_proplist_gets(prop, PA_PROP_DEVICE_BUS))) { + if (pa_streq (bus_name, DEVICE_BUS_USB)) { + return TRUE; + } else { + return FALSE; + } + } else { + pa_log_debug("This device doesn't have property '%s'", PA_PROP_DEVICE_BUS); + return FALSE; + } +} + +static pa_bool_t pulse_device_is_null(void *pulse_device, pa_device_type_t pdt) { + pa_sink *sink; + pa_source *source; + + if (!pulse_device) + return FALSE; + + if (pdt == PA_DEVICE_TYPE_SINK) { + sink = (pa_sink *) pulse_device; + return pa_streq(sink->module->name, "module-null-sink"); + } else { + source = (pa_source *) pulse_device; + + return pa_streq(source->module->name, "module-null-source"); + } +} + +static const char* device_class_to_string(dm_device_class_t device_class) { + if (device_class == DM_DEVICE_CLASS_ALSA) { + return "alsa"; + } else if (device_class == DM_DEVICE_CLASS_TIZEN) { + return "tizen"; + } else if (device_class == DM_DEVICE_CLASS_BT) { + return "bt"; + } else if (device_class == DM_DEVICE_CLASS_NULL) { + return "null"; + } else if (device_class == DM_DEVICE_CLASS_NONE) { + return "none"; + } else { + return NULL; + } +} + +static dm_device_class_t device_string_get_class(const char *device_string) { + if (!device_string) { + return DM_DEVICE_CLASS_NONE; + } + + if (device_string == strstr(device_string, "alsa")) { + return DM_DEVICE_CLASS_ALSA; + } else if (device_string == strstr(device_string, "null")) { + return DM_DEVICE_CLASS_NULL; + } else if (device_string == strstr(device_string, "tizen")) { + return DM_DEVICE_CLASS_TIZEN; + } else { + return DM_DEVICE_CLASS_NONE; + } +} + +static const char* device_string_get_value(const char *device_string) { + int len; + const char *end_p, *value_p; + + if (!device_string) { + return NULL; + } + + len = strlen(device_string); + end_p = device_string + len -1; + + if (!(value_p = strchr(device_string, ':'))) { + return NULL; + } + if (value_p < end_p) { + return value_p + 1; + } else { + return NULL; + } +} + +static dm_device_class_t pulse_device_get_class(void *pulse_device, pa_device_type_t pdt) { + pa_sink *sink = NULL; + pa_source *source = NULL; + + if (!pulse_device) { + pa_log_error("pulse_device null"); + return DM_DEVICE_CLASS_NONE; + } + + if (pdt == PA_DEVICE_TYPE_SINK) + sink = (pa_sink *) pulse_device; + else + source = (pa_source *) pulse_device; + + if (pulse_device_is_null(pulse_device, pdt)) { + return DM_DEVICE_CLASS_NULL; + } else if (pulse_device_is_alsa(pdt == PA_DEVICE_TYPE_SINK ? sink->proplist : source->proplist)) { + return DM_DEVICE_CLASS_ALSA; + } else if (pulse_device_is_tizenaudio(pulse_device, pdt)) { + return DM_DEVICE_CLASS_TIZEN; + } else if (pulse_device_is_bluez(pdt == PA_DEVICE_TYPE_SINK ? sink->proplist : source->proplist)) { + return DM_DEVICE_CLASS_BT; + } else { + return DM_DEVICE_CLASS_NONE; + } +} + +static const char* device_class_get_module_name(dm_device_class_t device_class, pa_device_type_t pdt) { + if (device_class == DM_DEVICE_CLASS_NONE) { + return NULL; + } else if (device_class == DM_DEVICE_CLASS_ALSA) { + return pdt == PA_DEVICE_TYPE_SINK ? "module-alsa-sink" : "module-alsa-source"; + } else if (device_class == DM_DEVICE_CLASS_TIZEN) { + return pdt == PA_DEVICE_TYPE_SINK ? "module-tizenaudio-sink" : NULL; + } else if (device_class == DM_DEVICE_CLASS_BT) { + return pdt == PA_DEVICE_TYPE_SINK ? "module-bluez5-device" : NULL; + } else if (device_class == DM_DEVICE_CLASS_NULL) { + return pdt == PA_DEVICE_TYPE_SINK ? "module-null-sink" : "module-null-source"; + } else { + return NULL; + } +} + +static int compare_device_profile(const char *device_profile1, const char *device_profile2) { + if (!device_profile1 && !device_profile2) { + return 0; + } else if (!device_profile1 || !device_profile2) { + return 1; + } else if (pa_streq(device_profile1, device_profile2)) { + return 0; + } else { + return 1; + } +} + +static int compare_device_type(const char *device_type1, const char *device_profile1, const char *device_type2, const char *device_profile2) { + if (!device_type1 || !device_type2) { + return -1; + } + if (pa_streq(device_type1, device_type2)) { + return compare_device_profile(device_profile1, device_profile2); + } + return 1; +} + +static struct device_type_info* _device_manager_get_type_info(pa_idxset *type_infos, const char *device_type, const char *device_profile) { + struct device_type_info *type_info; + uint32_t type_idx; + + PA_IDXSET_FOREACH(type_info, type_infos, type_idx) { + if (!compare_device_type(type_info->type, type_info->profile, device_type, device_profile)) { + return type_info; + } + } + + return NULL; +} + +static struct device_status_info* _device_manager_get_status_info(pa_idxset *status_infos, const char *device_type, const char *device_profile, const char *identifier) { + struct device_status_info *status_info; + uint32_t status_idx; + + PA_IDXSET_FOREACH(status_info, status_infos, status_idx) { + if (!compare_device_type(status_info->type, status_info->profile, device_type, device_profile)) { + if (!status_info->identifier && !identifier) { + return status_info; + } else if (!status_info->identifier || !identifier) { + continue; + } else if (pa_streq(status_info->identifier, identifier)) { + return status_info; + } else { + continue; + } + } + } + + return NULL; +} + +static struct device_file_info* _device_manager_get_file_info(pa_idxset *file_infos, const char *device_string) { + struct device_file_info *file_info; + uint32_t file_idx; + if (!file_infos) + return NULL; + + PA_IDXSET_FOREACH(file_info, file_infos, file_idx) { + if (file_info->device_string) { + if (pa_streq(file_info->device_string, device_string)) { + return file_info; + } + } + } + + return NULL; +} + +static dm_device* _device_manager_get_device(pa_idxset *device_list, const char *device_type) { + dm_device *device_item; + uint32_t device_idx; + + if (!device_list || !device_type) + return NULL; + + PA_IDXSET_FOREACH(device_item, device_list, device_idx) { + if (pa_streq(device_item->type, device_type)) { + return device_item; + } + } + + return NULL; +} + +static dm_device* _device_manager_get_device_with_id(pa_idxset *device_list, uint32_t id) { + dm_device *device_item; + uint32_t idx; + + pa_assert(device_list); + + PA_IDXSET_FOREACH(device_item, device_list, idx) { + if (device_item->id == id) { + return device_item; + } + } + return NULL; +} + +static void dump_playback_device_list(pa_hashmap *playback_devices) { + pa_sink *sink = NULL; + void *state = NULL; + const char *role; + + if (!playback_devices) { + return ; + } + + pa_log_debug(" playback device list"); + if (pa_hashmap_size(playback_devices) == 0) { + pa_log_debug(" empty"); + return; + } + PA_HASHMAP_FOREACH_KEY(sink, playback_devices, state, role) { + pa_log_debug(" %-13s -> %s", role, sink->name); + } +} + +static void dump_capture_device_list(pa_hashmap *capture_devices) { + pa_source *source= NULL; + void *state = NULL; + const char *role; + + if (!capture_devices) { + return ; + } + + pa_log_debug(" capture device list"); + if (pa_hashmap_size(capture_devices) == 0) { + pa_log_debug(" empty"); + return; + } + PA_HASHMAP_FOREACH_KEY(source, capture_devices, state, role) { + pa_log_debug(" %-13s -> %s", role, source->name); + } +} + +static void dump_device_profile_info(dm_device_profile *profile_item) { + if (!profile_item) + return; + + pa_log_debug(" profile : %s", profile_item->profile); + pa_log_debug(" direction : %s", device_direction_to_string(profile_item->direction)); + pa_log_debug(" activated : %s", COMPOUND_STATE(profile_item) == DM_DEVICE_STATE_ACTIVATED ? "activated" : "not activated"); + dump_playback_device_list(profile_item->playback_devices); + dump_capture_device_list(profile_item->capture_devices); +} + +static void dump_device_info(dm_device *device_item) { + dm_device_profile *profile_item = NULL; + uint32_t device_idx = 0; + + if (!device_item) + return; + if (!device_item->profiles) { + pa_log_warn("empty device item"); + return; + } + + pa_log_debug(" id : %u", device_item->id); + pa_log_debug(" type : %s", device_item->type); + pa_log_debug(" name : %s", device_item->name); + pa_log_debug(" active-profile : %u", device_item->active_profile); + PA_IDXSET_FOREACH(profile_item, device_item->profiles, device_idx) { + pa_log_debug(" (Profile #%u)", device_idx); + dump_device_profile_info(profile_item); + } +} + +static void dump_device_list(pa_device_manager *dm) { + dm_device *device_item = NULL; + uint32_t device_idx = 0; + + if (!dm || !dm->device_list) { + return; + } + + pa_log_debug("====== Device List Dump ======"); + PA_IDXSET_FOREACH(device_item, dm->device_list, device_idx) { + pa_log_debug("[ Device #%u ]", device_item->id); + dump_device_info(device_item); + } + pa_log_debug("==================================="); +} +static pa_bool_t pulse_device_class_is_sound(pa_proplist *prop) { + const char *device_class = NULL; + + if ((device_class = pa_proplist_gets(prop, PA_PROP_DEVICE_CLASS))) { + if (device_class && pa_streq (device_class, DEVICE_CLASS_SOUND)) { + return TRUE; + } else { + return FALSE; + } + } else { + return FALSE; + } +} + +static pa_bool_t pulse_device_class_is_monitor(pa_proplist *prop) { + const char *device_class = NULL; + + if (!prop) { + return FALSE; + } + + if ((device_class = pa_proplist_gets(prop, PA_PROP_DEVICE_CLASS))) { + if (device_class && pa_streq (device_class, DEVICE_CLASS_MONITOR)) { + return TRUE; + } else { + return FALSE; + } + } else { + return FALSE; + } +} +static void* pulse_device_get_opposite_sibling_device(void *pulse_device, pa_device_type_t pdt) { + const char *sysfs_path, *sysfs_path_tmp; + uint32_t device_idx; + void *pulse_device_tmp; + pa_core *core; + + pa_assert(pulse_device); + + if (!(sysfs_path = pa_proplist_gets(pulse_device_get_proplist(pulse_device, pdt), "sysfs.path"))) { + pa_log_warn("No sysfs.path for '%s'", pulse_device_get_name(pulse_device, pdt)); + return NULL; + } + + core = pulse_device_get_core(pulse_device, pdt); + + PA_IDXSET_FOREACH(pulse_device_tmp, pulse_core_get_device_list(core, !pdt), device_idx) { + if (!pulse_device_class_is_sound(pulse_device_get_proplist(pulse_device_tmp, !pdt))) + continue; + sysfs_path_tmp = pa_proplist_gets(pulse_device_get_proplist(pulse_device_tmp, !pdt), "sysfs.path"); + if (sysfs_path_tmp && pa_streq(sysfs_path_tmp, sysfs_path)) { + return pulse_device_tmp; + } + } + + return NULL; +} + +static int pulse_device_get_alsa_device_string(pa_proplist *prop, char **device_string) { + const char *device_string_prop = NULL; + char *device_string_tmp; + + if (!prop || !device_string) { + pa_log_error("Invalid Parameter"); + return -1; + } + + if (!(device_string_prop = pa_proplist_gets(prop, "device.string"))) { + pa_log_error("failed to get property 'device.string'"); + return -1; + } + if (!(device_string_tmp = strchr(device_string_prop, ':'))) { + pa_log_error("failed to parse device string"); + return -1; + } + + if (((device_string_tmp + 1) == '\0')) { + pa_log_error("no device string value"); + return -1; + } + + *device_string = device_string_tmp + 1; + + return 0; +} + +static const char* build_params_to_load_device(const char *device_string, const char *params, dm_device_class_t device_class) { + pa_strbuf *args_buf; + static char args[DEVICE_PARAM_STRING_MAX] = {0,}; + + if (!device_string) { + pa_log_error("device string null"); + return NULL; + } + + if (device_class == DM_DEVICE_CLASS_NULL) { + return params; + } else if (device_class == DM_DEVICE_CLASS_ALSA) { + const char *alsa_device_name; + if (!(alsa_device_name = device_string_get_value(device_string))) { + pa_log_error("Invalid device string for alsa-device, '%s'", device_string); + return NULL; + } + args_buf = pa_strbuf_new(); + pa_strbuf_printf(args_buf, "device=hw:%s ", alsa_device_name); + if (params) { + pa_strbuf_printf(args_buf, "%s", params); + } + strncpy(args, pa_strbuf_tostring_free(args_buf), DEVICE_PARAM_STRING_MAX); + } else { + return params; + } + + return (const char*) args; +} + +static const char* pulse_device_get_device_string_removed_argument(void *pulse_device, pa_device_type_t pdt) { + static char removed_param[DEVICE_PARAM_STRING_MAX] = {0,}; + char *device_string_p = NULL; + char *next_p = NULL; + const char *params_p, *params; + char *end_p = NULL; + int len = 0, prev_len = 0; + pa_sink *sink; + pa_source *source; + + if (pdt == PA_DEVICE_TYPE_SINK) + sink = (pa_sink *) pulse_device; + else + source = (pa_source *) pulse_device; + + params = pdt == PA_DEVICE_TYPE_SINK ? sink->module->argument : source->module->argument; + params_p = params; + + if (!params) { + return NULL; + } + if (!(device_string_p = strstr(params, "device="))) { + return params; + } + + next_p = device_string_p; + while (!isblank(*next_p)) { + next_p++; + } + while (isblank(*next_p)) { + next_p++; + } + + strncpy(removed_param, next_p, DEVICE_PARAM_STRING_MAX); + + if (device_string_p > params_p) { + prev_len = device_string_p - params_p; + len = strlen(removed_param); + end_p = removed_param + len; + *end_p = ' '; + end_p++; + strncpy(end_p, params_p, prev_len); + } + + return removed_param; +} + + +static int compare_device_params(const char *params1, const char *params2) { + const char *key = NULL; + const char *value1, *value2; + pa_modargs *modargs1, *modargs2; + void *state = NULL; + int ret = 0; + + if (!params1 && !params2) + return 0; + if (!params1 || !params2) + return -1; + + modargs1 = pa_modargs_new(params1, valid_alsa_device_modargs); + modargs2 = pa_modargs_new(params2, valid_alsa_device_modargs); + + if (!modargs1 || !modargs2) { + ret = 1; + goto end; + } + + for (state = NULL, key = pa_modargs_iterate(modargs1, &state); key; key = pa_modargs_iterate(modargs1, &state)) { + value1 = pa_modargs_get_value(modargs1, key, NULL); + value2 = pa_modargs_get_value(modargs2, key, NULL); + if (!value1 || !value2 || !pa_streq(value1, value2)) { + ret = 1; + goto end; + } + } + + for (state = NULL, key = pa_modargs_iterate(modargs2, &state); key; key = pa_modargs_iterate(modargs2, &state)) { + value1 = pa_modargs_get_value(modargs1, key, NULL); + value2 = pa_modargs_get_value(modargs2, key, NULL); + if (!value1 || !value2 || !pa_streq(value1, value2)) { + ret = 1; + goto end; + } + } + +end: + + if (modargs1) + pa_modargs_free(modargs1); + if (modargs2) + pa_modargs_free(modargs2); + + + return ret; +} + +static int compare_device_params_with_module_args(void *pulse_device, pa_device_type_t pdt, const char *params) { + const char *removed_module_args; + const char *module_args; + pa_sink *sink; + pa_source *source; + + if (pdt == PA_DEVICE_TYPE_SINK) { + sink = (pa_sink *) pulse_device; + module_args = sink->module->argument; + } else { + source = (pa_source *) pulse_device; + module_args = source->module->argument; + } + + if (!params && !module_args) + return 0; + if (!params || !module_args) + return -1; + + removed_module_args = pulse_device_get_device_string_removed_argument(pulse_device, pdt); + return compare_device_params(params, removed_module_args); +} + +static const char* pulse_device_get_device_string(void *pulse_device, pa_device_type_t pdt) { + dm_device_class_t device_class; + static char device_string[DEVICE_STR_MAX] = {0,}; + char *device_string_val = NULL; + pa_sink *sink; + pa_source *source; + + if (!pulse_device) { + pa_log_error("pulse_device null"); + return NULL; + } + + device_class = pulse_device_get_class(pulse_device, pdt); + + if (pdt == PA_DEVICE_TYPE_SINK) + sink = (pa_sink *) pulse_device; + else + source = (pa_source *) pulse_device; + + if (device_class == DM_DEVICE_CLASS_ALSA) { + if (pulse_device_get_alsa_device_string(pdt == PA_DEVICE_TYPE_SINK ? sink->proplist : source->proplist, &device_string_val) < 0) + return NULL; + snprintf(device_string, DEVICE_STR_MAX, "alsa:%s", device_string_val); + return device_string; + } else if (device_class == DM_DEVICE_CLASS_NULL) { + return "null"; + } else if (device_class == DM_DEVICE_CLASS_TIZEN) { + return "tizen"; + } else if (device_class == DM_DEVICE_CLASS_BT) { + return "bt"; + } else { + return device_string; + } +} + +/* pulse_device is sink or source */ +static pa_bool_t pulse_device_same_device_string(void *pulse_device, pa_device_type_t pdt, const char *device_string) { + const char *pulse_device_string; + + if (!pulse_device || !device_string) { + return FALSE; + } + + if (!(pulse_device_string = pulse_device_get_device_string(pulse_device, pdt))) { + return FALSE; + } + + return pa_streq(pulse_device_string, device_string); +} + +static dm_device* _device_item_set_active_profile(dm_device *device_item, const char *device_profile) { + dm_device_profile *profile_item = NULL; + uint32_t idx, active_profile_idx = PA_INVALID_INDEX, prev_active_profile = PA_INVALID_INDEX; + + if (!device_item || !device_item->profiles ) { + pa_log_error("Invalid Parameter"); + return NULL; + } + + prev_active_profile = device_item->active_profile; + PA_IDXSET_FOREACH(profile_item, device_item->profiles, idx) { + if (!compare_device_profile(profile_item->profile, device_profile)) { + active_profile_idx = idx; + } + } + + if (active_profile_idx != PA_INVALID_INDEX) { + device_item->active_profile = active_profile_idx; + } else { + return NULL; + } + + if (prev_active_profile != device_item->active_profile) { + pa_log_debug("%s's active profile : %u", device_item->name, device_item->active_profile); + notify_device_info_changed(device_item, DM_DEVICE_CHANGED_INFO_SUBTYPE, device_item->dm); + } + + return device_item; +} + +static int get_profile_priority(const char *device_profile) { + if (!device_profile) { + return 0; + } else if (pa_streq(device_profile, DEVICE_PROFILE_BT_SCO)) { + return 1; + } else if (pa_streq(device_profile, DEVICE_PROFILE_BT_A2DP)) { + return 2; + } else { + return -1; + } +} + +static int compare_profile_priority(const char *device_profile1, const char *device_profile2) { + int priority1, priority2; + + priority1 = get_profile_priority(device_profile1); + priority2 = get_profile_priority(device_profile2); + + if (priority1 > priority2) { + return 1; + } else if (priority1 == priority2) { + return 0; + } else { + return -1; + } +} + +static dm_device* _device_item_set_active_profile_auto(dm_device *device_item) { + dm_device_profile *profile_item = NULL, *prev_profile_item = NULL; + uint32_t idx, prev_active_profile; + unsigned int device_size; + + if (!device_item || !device_item->profiles ) { + pa_log_error("Invalid Parameter"); + return NULL; + } + + prev_active_profile = device_item->active_profile; + + device_size = pa_idxset_size(device_item->profiles); + if (device_size == 1) { + pa_idxset_first(device_item->profiles, &idx); + device_item->active_profile = idx; + } else if (device_size == 0) { + device_item->active_profile = PA_INVALID_INDEX; + return device_item; + } else { + PA_IDXSET_FOREACH(profile_item, device_item->profiles, idx) { + if (prev_profile_item) { + if (compare_profile_priority(profile_item->profile, prev_profile_item->profile) > 0) { + device_item->active_profile = idx; + } + } + prev_profile_item = profile_item; + } + } + + if (prev_active_profile != device_item->active_profile) { + pa_log_debug("%s's active profile : %u", device_item->name, device_item->active_profile); + notify_device_info_changed(device_item, DM_DEVICE_CHANGED_INFO_SUBTYPE, device_item->dm); + } + + return device_item; +} + +static dm_device* _device_item_add_profile(dm_device *device_item, dm_device_profile *profile_item, uint32_t *idx, pa_device_manager *dm) { + uint32_t profile_idx; + + pa_assert(device_item); + pa_assert(device_item->profiles); + pa_assert(profile_item); + pa_assert(dm); + + pa_idxset_put(device_item->profiles, profile_item, &profile_idx); + _device_item_set_active_profile_auto(device_item); + profile_item->device_item = device_item; + + return device_item; +} + + +static int _device_list_add_device(pa_idxset *device_list, dm_device *device_item, pa_device_manager *dm) { + pa_assert(device_list); + pa_assert(device_item); + + if (pa_idxset_put(device_list, device_item, NULL) < 0) + return -1; + + pa_log_debug("Notify Device connected"); + + return 0; +} + + +static int _device_list_remove_device(pa_idxset *device_list, dm_device *device_item, pa_device_manager *dm) { + pa_assert(device_list); + pa_assert(device_item); + + if (!pa_idxset_remove_by_data(device_list, device_item, NULL)) + return -1; + + return 0; +} + + + +static dm_device* create_device_item(const char *device_type, const char *name, dm_device_profile *profile_item, pa_device_manager *dm) { + dm_device *device_item = NULL; + + pa_assert(device_type); + pa_assert(profile_item); + + pa_log_debug("Create device item for %s", device_type); + + device_item = (dm_device *)pa_xmalloc(sizeof(dm_device)); + device_item->id = device_id_max_g++; + device_item->type = strdup(device_type); + device_item->active_profile = PA_INVALID_INDEX; + device_item->profiles = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + device_item->dm = dm; + + if (name) { + device_item->name = strdup(name); + } else { + device_item->name = strdup(device_type); + } + + _device_item_add_profile(device_item, profile_item, NULL, dm); + _device_list_add_device(dm->device_list, device_item, dm); + notify_device_connection_changed(device_item, TRUE, dm); + + return device_item; +} + +static void destroy_device_item(dm_device *device_item, pa_device_manager *dm) { + if (!device_item) { + return; + } + + pa_log_debug("Destroy device item which of type is %s", device_item->type); + + _device_list_remove_device(dm->device_list, device_item, dm); + + device_item_free_func(device_item); +} + +static unsigned _device_item_get_size(dm_device *device_item) { + unsigned int profile_num; + pa_assert(device_item); + + profile_num = pa_idxset_size(device_item->profiles); + return profile_num; +} + +static dm_device* _device_item_remove_profile(dm_device *device_item, dm_device_profile *profile_item, pa_device_manager *dm) { + unsigned int profile_num; + + pa_assert(device_item); + pa_assert(device_item->profiles); + pa_assert(profile_item); + pa_assert(dm); + + profile_num = pa_idxset_size(device_item->profiles); + + if (profile_num == 0) { + pa_log_error("Already Empty device_item"); + return NULL; + } + + pa_idxset_remove_by_data(device_item->profiles, profile_item, NULL); + _device_item_set_active_profile_auto(device_item); + + return device_item; +} + +static dm_device_profile* create_device_profile(const char *device_profile, dm_device_direction_t direction, pa_hashmap *playback, pa_hashmap *capture) { + dm_device_profile *profile_item = NULL; + + pa_assert(direction >= DM_DEVICE_DIRECTION_IN && direction <= DM_DEVICE_DIRECTION_BOTH); + + pa_log_debug("Create device profile for %s, direction:%s", device_profile, device_direction_to_string(direction)); + + if (!(profile_item = (dm_device_profile *)pa_xmalloc(sizeof(dm_device_profile)))) { + pa_log_error("Cannot alloc for device item"); + return NULL; + } + profile_item->profile = device_profile ? strdup(device_profile) : NULL; + profile_item->direction = direction; + profile_item->playback_state = DM_DEVICE_STATE_DEACTIVATED; + profile_item->capture_state = DM_DEVICE_STATE_DEACTIVATED; + profile_item->playback_devices = playback; + profile_item->capture_devices = capture; + + return profile_item; +} + +static dm_device* destroy_device_profile(dm_device_profile *profile_item, pa_device_manager *dm) { + dm_device *device_item; + + pa_assert(profile_item); + pa_assert(profile_item->device_item); + + device_item = profile_item->device_item; + + pa_log_debug("Destroy device profile item which of profile is %s", profile_item->profile); + + if (_device_item_get_size(device_item) == 1) { + destroy_device_item(device_item, dm); + return NULL; + } else { + _device_item_remove_profile(device_item, profile_item, dm); + profile_item_free_func(profile_item); + return device_item; + } +} + +static void _device_profile_update_direction(dm_device_profile *profile_item) { + int prev_direction; + pa_bool_t playback_exist = FALSE, capture_exist = FALSE; + + if (!profile_item) + return; + + prev_direction = profile_item->direction; + + if (profile_item->playback_devices) { + if (pa_hashmap_size(profile_item->playback_devices) > 0) { + playback_exist = TRUE; + } + } + if (profile_item->capture_devices) { + if (pa_hashmap_size(profile_item->capture_devices) > 0) { + capture_exist = TRUE; + } + } + + if (playback_exist && capture_exist) { + profile_item->direction = DM_DEVICE_DIRECTION_BOTH; + } else if (playback_exist) { + profile_item->direction = DM_DEVICE_DIRECTION_OUT; + } else if (capture_exist) { + profile_item->direction = DM_DEVICE_DIRECTION_IN; + } else { + profile_item->direction = DM_DEVICE_DIRECTION_NONE; + } + + pa_log_debug("direction updated '%s'->'%s'", device_direction_to_string(prev_direction), device_direction_to_string(profile_item->direction)); +} + +static dm_device_profile* _device_profile_add_pulse_device(dm_device_profile *profile_item, const char *role, void *pulse_device, pa_device_type_t pdt) { + if (!profile_item || !pulse_device || !device_role_is_valid(role)) { + pa_log_error("Invalid Parameter"); + return NULL; + } + + if (pdt == PA_DEVICE_TYPE_SINK) { + if (!(profile_item->playback_devices)) + profile_item->playback_devices = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + if(pa_hashmap_put(profile_item->playback_devices, (void *)role, pulse_device) < 0) + return NULL; + } else { + if (!(profile_item->capture_devices)) + profile_item->capture_devices = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + if(pa_hashmap_put(profile_item->capture_devices, (void *)role, pulse_device) < 0) + return NULL; + } + + return profile_item; +} + +static dm_device_profile* _device_profile_add_sink(dm_device_profile *profile_item, const char *role, pa_sink *sink) { + if (!profile_item || !sink || !device_role_is_valid(role)) { + pa_log_error("Invalid Parameter"); + return NULL; + } + + if (!(profile_item->playback_devices)) + profile_item->playback_devices = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + if(pa_hashmap_put(profile_item->playback_devices, (void *)role, sink) < 0) + return NULL; + + return profile_item; +} + +static dm_device_profile* _device_profile_remove_sink(dm_device_profile *profile_item, const char *role) { + if (!profile_item || !device_role_is_valid(role)) { + pa_log_error("Invalid Parameter"); + return NULL; + } + pa_hashmap_remove(profile_item->playback_devices, role); + return profile_item; +} + +static dm_device_profile* _device_profile_add_source(dm_device_profile *profile_item, const char *role, pa_source *source) { + if (!profile_item || !source || !device_role_is_valid(role)) { + pa_log_error("Invalid Parameter"); + return NULL; + } + + if (!(profile_item->capture_devices)) + profile_item->capture_devices = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + if(pa_hashmap_put(profile_item->capture_devices, (void *)role, source) < 0) + return NULL; + + return profile_item; +} + +static int _device_profile_get_size(dm_device_profile *profile_item, unsigned int *playback_num, unsigned int *capture_num) { + if (!profile_item || !playback_num || !capture_num) { + pa_log_error("Invalid Parameter"); + return -1; + } + + if (!(profile_item->playback_devices)) + *playback_num = 0; + else + *playback_num = pa_hashmap_size(profile_item->playback_devices); + + if (!(profile_item->capture_devices)) + *capture_num = 0; + else + *capture_num = pa_hashmap_size(profile_item->capture_devices); + + return 0; +} + + +static dm_device_profile* _device_profile_remove_source(dm_device_profile *profile_item, const char *role) { + if (!profile_item || !(profile_item->capture_devices) || !device_role_is_valid(role)) { + pa_log_error("Invalid Parameter"); + return NULL; + } + pa_hashmap_remove(profile_item->capture_devices, role); + return profile_item; +} + +void _device_profile_set_state(dm_device_profile *profile_item, dm_device_direction_t direction, dm_device_state_t state) { + dm_device_state_t prev_state, new_state; + pa_assert(profile_item); + + prev_state = COMPOUND_STATE(profile_item); + pa_log_debug("previous playback_state : %d, capture_state : %d => state %d", profile_item->playback_state, profile_item->capture_state, prev_state); + if (direction & DM_DEVICE_DIRECTION_IN) + profile_item->capture_state = state; + if (direction & DM_DEVICE_DIRECTION_OUT) + profile_item->playback_state = state; + new_state = COMPOUND_STATE(profile_item); + pa_log_debug("new playback_state : %d, capture_state : %d => state %d", profile_item->playback_state, profile_item->capture_state, new_state); + + if (prev_state != new_state) { + notify_device_info_changed(profile_item->device_item, DM_DEVICE_CHANGED_INFO_STATE, profile_item->device_item->dm); + } +} + + +static int device_type_get_direction(pa_device_manager *dm, const char *device_type, const char *device_profile, const char *identifier) { + struct device_type_info *type_info = NULL; + struct device_status_info *status_info; + dm_device_direction_t direction = 0, d_num = 0, d_idx = 0, correct_d_idx = 0; + + if (!dm || !device_type) { + pa_log_error("Invalid Parameter"); + return -1; + } + + + if (!(type_info = _device_manager_get_type_info(dm->type_infos, device_type, device_profile))) { + pa_log_error("No type map for %s", device_type); + return -1; + } + + for (d_idx = 0; d_idx < DEVICE_DIRECTION_MAX; d_idx++) { + if (type_info->direction[d_idx] != DM_DEVICE_DIRECTION_NONE) { + correct_d_idx = d_idx; + d_num++; + } + } + + if (d_num == 1) { + direction = type_info->direction[correct_d_idx]; + } else { + /* Actually, only 'audio-jack' should come here */ + if (pa_streq(device_type, DEVICE_TYPE_AUDIO_JACK)) { + status_info = _device_manager_get_status_info(dm->device_status, type_info->type, type_info->profile, identifier); + if (status_info->detected_type == DEVICE_DETECTED_AUDIO_JACK_BOTH_DIREC) { + direction = DM_DEVICE_DIRECTION_BOTH; + } else if (status_info->detected_type == DEVICE_DETECTED_AUDIO_JACK_OUT_DIREC) { + direction = DM_DEVICE_DIRECTION_OUT; + } else { + pa_log_debug("Cannot get audio jack device direction"); + return -1; + } + } else { + pa_log_error("Weird case, '%s' is not expected to have multiple direction", device_type); + return -1; + } + } + + return direction; +} + +static int pulse_device_get_device_type(void *pulse_device, pa_device_type_t pdt, dm_device_class_t device_class, const char **device_type, const char **device_profile, const char **device_name) { + pa_proplist *prop; + pa_sink *sink; + pa_source *source; + + pa_assert(pulse_device); + pa_assert(device_type); + pa_assert(device_profile); + + if (pdt == PA_DEVICE_TYPE_SINK) { + sink = (pa_sink *) pulse_device; + prop = sink->proplist; + } else { + source = (pa_source*) pulse_device; + prop = source->proplist; + } + + if (device_class == DM_DEVICE_CLASS_ALSA) { + if (pulse_device_is_usb(prop)) { + *device_type = DEVICE_TYPE_USB_AUDIO; + *device_profile = NULL; + *device_name = pa_proplist_gets(prop, PA_PROP_DEVICE_SERIAL); + } else { + pa_log_warn("This is alsa device, but not usb. really unknown device"); + return -1; + } + } else if (device_class == DM_DEVICE_CLASS_BT) { + *device_type = DEVICE_TYPE_BT; + *device_profile = DEVICE_PROFILE_BT_A2DP; + *device_name = pa_proplist_gets(prop, "bluez.alias"); + } else { + pa_log_warn("Invalid device type, neither alsa nor bluez"); + return -1; + } + + return 0; +} + +static dm_device_profile* _device_item_get_profile(dm_device *device_item, const char *profile) { + dm_device_profile *profile_item; + uint32_t profile_idx; + + if (!device_item || !device_item->profiles) + return NULL; + + PA_IDXSET_FOREACH(profile_item, device_item->profiles, profile_idx) { + if (!compare_device_profile(profile_item->profile, profile)) { + return profile_item; + } + } + return NULL; +} + +static dm_device_profile* _device_item_get_active_profile(dm_device *device_item) { + dm_device_profile *profile_item; + + if (!device_item || !device_item->profiles) + return NULL; + + if ((profile_item = pa_idxset_get_by_index(device_item->profiles, device_item->active_profile))) + return profile_item; + + + return NULL; +} + +static pa_sink* _device_manager_set_default_sink(pa_device_manager *dm, const char *device_type, const char *device_profile, const char *role) { + dm_device *device_item; + dm_device_profile *profile_item; + pa_sink *sink; + + if (!device_type || !role) { + pa_log_warn("Argument for set_default_sink invalid"); + return NULL; + } + + if (!(device_item = _device_manager_get_device(dm->device_list, device_type))) { + pa_log_warn("cannot get device item for %s", device_type); + return NULL; + } + if (!(profile_item = _device_item_get_profile(device_item, device_profile))) { + pa_log_warn("cannot get profile item for %s", device_profile); + return NULL; + } + + if (!(sink = pa_hashmap_get(profile_item->playback_devices, role))) { + pa_log_warn("cannot get sink for %s", role); + return NULL; + } + + sink = pa_namereg_set_default_sink(dm->core, sink); + return sink; +} + +static pa_source* _device_manager_set_default_source(pa_device_manager *dm, const char *device_type, const char *device_profile, const char *role) { + dm_device *device_item; + dm_device_profile *profile_item; + pa_source *source; + + if (!device_type || !role) { + pa_log_warn("Argument for set_default_source invalid"); + return NULL; + } + + if (!(device_item = _device_manager_get_device(dm->device_list, device_type))) { + pa_log_warn("cannot get device item for %s", device_type); + return NULL; + } + if (!(profile_item = _device_item_get_profile(device_item, device_profile))) { + pa_log_warn("cannot get profile item for %s", device_profile); + return NULL; + } + + if (!(source= pa_hashmap_get(profile_item->capture_devices, role))) { + pa_log_warn("cannot get source for %s", role); + return NULL; + } + + source = pa_namereg_set_default_source(dm->core, source); + return source; +} + +static dm_device_profile* handle_not_predefined_device_profile(void *pulse_device, pa_device_type_t pdt, const char *device_profile) { + dm_device_profile *profile_item = NULL; + dm_device_direction_t direc; + + pa_log_debug("Create device profile item %s", device_profile); + if (pdt == PA_DEVICE_TYPE_SINK) + direc = DM_DEVICE_DIRECTION_OUT; + else + direc = DM_DEVICE_DIRECTION_IN; + + if(!(profile_item = create_device_profile(device_profile, direc, NULL, NULL))) { + pa_log_error("create_device_profile failed"); + goto failed; + } + if (pdt == PA_DEVICE_TYPE_SINK) { + if (!(_device_profile_add_sink(profile_item, DEVICE_ROLE_NORMAL, pulse_device))) { + pa_log_error("failed to add sink"); + goto failed; + } + } else { + if (!(_device_profile_add_source(profile_item, DEVICE_ROLE_NORMAL, pulse_device))) { + pa_log_error("failed to add source"); + goto failed; + } + } + + return profile_item; +failed : + if (profile_item) + pa_xfree(profile_item); + return NULL; +} + + +static dm_device* handle_not_predefined_device(pa_device_manager *dm, void *pulse_device, pa_device_type_t pdt, dm_device_class_t device_class) { + dm_device_profile *profile_item = NULL; + const char *device_type, *device_profile, *device_name; + dm_device *device_item = NULL; + pa_source *sibling_source, *source; + pa_sink *sibling_sink, *sink; + + pa_assert(dm); + pa_assert(pulse_device); + + pa_log_debug("handle_not_predefined_device"); + + if (pulse_device_get_device_type(pulse_device, pdt, device_class, &device_type, &device_profile, &device_name) < 0) { + pa_log_warn("Cannot get device type of this device"); + return NULL; + } + + /* + Find opposite direction sink/sources on same device. + If Found, add sink or source to same device_item. + */ + if (pa_streq(device_type, DEVICE_TYPE_USB_AUDIO)) { + if (pdt == PA_DEVICE_TYPE_SINK) { + sink = (pa_sink *) pulse_device; + if ((sibling_source = pulse_device_get_opposite_sibling_device(sink, PA_DEVICE_TYPE_SINK))) { + if (sibling_source->device_item) { + device_item = (dm_device *) sibling_source->device_item; + profile_item = _device_item_get_profile(device_item, NULL); + if (!(_device_profile_add_sink(profile_item, DEVICE_ROLE_NORMAL, sink))) { + pa_log_error("failed to add sink beside sibling source"); + goto failed; + } + _device_profile_update_direction(profile_item); + notify_device_info_changed(device_item, DM_DEVICE_CHANGED_INFO_IO_DIRECTION, dm); + goto end; + } + } + } else { + source = (pa_source*) pulse_device; + if ((sibling_sink = pulse_device_get_opposite_sibling_device(source, PA_DEVICE_TYPE_SOURCE))) { + if (sibling_sink->device_item) { + device_item = (dm_device *) sibling_sink->device_item; + profile_item = _device_item_get_profile(device_item, NULL); + if (!(_device_profile_add_source(profile_item, DEVICE_ROLE_NORMAL, source))) { + pa_log_error("failed to add source beside sibling sink"); + goto failed; + } + _device_profile_update_direction(profile_item); + notify_device_info_changed(device_item, DM_DEVICE_CHANGED_INFO_IO_DIRECTION, dm); + goto end; + } + } + } + } + + if(!(profile_item = handle_not_predefined_device_profile(pulse_device, pdt, device_profile))) { + pa_log_error("failed to handle unknown device profile"); + goto failed; + } + _device_profile_update_direction(profile_item); + + if (device_class == DM_DEVICE_CLASS_BT) { + if((device_item = _device_manager_get_device(dm->device_list, DEVICE_TYPE_BT))) { + pa_log_debug("found bt device"); + _device_item_add_profile(device_item, profile_item, NULL, dm); + goto end; + } + } + + if (!(device_item = create_device_item(device_type, device_name, profile_item, dm))) { + pa_log_error("failed to create device item for not predefined device"); + goto failed; + } + +end: + + if (pdt == PA_DEVICE_TYPE_SINK) { + sink = (pa_sink *) pulse_device; + sink->device_item = device_item; + } else { + source = (pa_source *) pulse_device; + source->device_item = device_item; + } + + return device_item; + +failed: + pa_log_error("Failed to handle external device"); + if (profile_item) + pa_xfree(profile_item); + if (pdt == PA_DEVICE_TYPE_SINK) { + sink = (pa_sink *) pulse_device; + sink->device_item = device_item; + } else { + source = (pa_source *) pulse_device; + source->device_item = device_item; + } + + + return NULL; +} + +static pa_bool_t pulse_device_loaded_with_param(pa_core *core, pa_device_type_t pdt, const char *device_string, const char *params) { + pa_sink *sink; + pa_source *source; + uint32_t device_idx; + + pa_assert(core); + pa_assert(device_string); + + if (pdt == PA_DEVICE_TYPE_SINK) { + PA_IDXSET_FOREACH(sink, core->sinks, device_idx) { + if (pulse_device_class_is_monitor(sink->proplist)) + continue; + if (pa_streq(device_string, pulse_device_get_device_string(sink, pdt))) { + if (!compare_device_params_with_module_args(sink, pdt, params)) { + return TRUE; + } + } + } + } else { + PA_IDXSET_FOREACH(source, core->sources, device_idx) { + if (pulse_device_class_is_monitor(source->proplist)) + continue; + if (pa_streq(device_string, pulse_device_get_device_string(source, pdt))) { + if (!compare_device_params_with_module_args(source, pdt, params)) { + return TRUE; + } + } + } + } + return FALSE; +} + +static int device_type_get_pulse_devices(struct device_type_info *type_info, pa_hashmap **playback, pa_hashmap **capture, pa_device_manager *dm) { + struct device_file_info *file_info; + const char *device_string, *params, *role; + uint32_t device_idx; + pa_sink *sink; + pa_source *source; + void *state; + + pa_assert(type_info); + pa_assert(playback); + pa_assert(capture); + pa_assert(dm); + + if (type_info->playback_devices) { + *playback = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + PA_HASHMAP_FOREACH_KEY(device_string, type_info->playback_devices, state, role) { + if (!(file_info = _device_manager_get_file_info(dm->file_map->playback, device_string))) { + pa_log_error("No playback file map for '%s'", device_string); + goto failed; + } + + if (!(params = pa_hashmap_get(file_info->roles, role))) { + pa_log_error("No params for '%s:%s'", device_string, role); + goto failed; + } + + PA_IDXSET_FOREACH(sink, dm->core->sinks, device_idx) { + if (pulse_device_class_is_monitor(sink->proplist)) + continue; + if (pulse_device_same_device_string(sink, PA_DEVICE_TYPE_SINK, device_string)) { + if (!compare_device_params_with_module_args(sink, PA_DEVICE_TYPE_SINK, params)) { + pa_hashmap_put(*playback, (void *)role, sink); + pa_log_debug("role:%s <- sink:%s", role, sink->name); + break; + } + } + } + } + } + + + if (type_info->capture_devices) { + *capture = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + PA_HASHMAP_FOREACH_KEY(device_string, type_info->capture_devices, state, role) { + if (!(file_info = _device_manager_get_file_info(dm->file_map->capture, device_string))) { + pa_log_error("No capture file map for '%s'", device_string); + goto failed; + } + if (!(params = pa_hashmap_get(file_info->roles, role))) { + pa_log_error("No params for '%s:%s'", device_string, role); + goto failed; + } + PA_IDXSET_FOREACH(source, dm->core->sources, device_idx) { + if (pulse_device_class_is_monitor(source->proplist)) + continue; + if (pulse_device_same_device_string(source, PA_DEVICE_TYPE_SOURCE, device_string)) { + if (!compare_device_params_with_module_args(source, PA_DEVICE_TYPE_SOURCE, params)) { + pa_hashmap_put(*capture, (void *)role, source); + pa_log_debug("role:%s <- source:%s", role, source->name); + break; + } + } + } + } + } + + return 0; + +failed: + if (!(*playback)) + pa_hashmap_free(*playback); + if (!(*capture)) + pa_hashmap_free(*capture); + *playback = NULL; + *capture = NULL; + + return -1; +} + +static const char* device_type_info_get_device_string(struct device_type_info *type_info, const char *role, pa_device_type_t pdt) { + pa_assert(type_info); + pa_assert(role); + + if (pdt == PA_DEVICE_TYPE_SINK) { + if (type_info->playback_devices) + return pa_hashmap_get(type_info->playback_devices, role); + else + return NULL; + } else { + if (type_info->capture_devices) + return pa_hashmap_get(type_info->capture_devices, role); + else + return NULL; + } + +} + +static const char* device_file_info_get_role_with_params(struct device_file_info *file_info, const char *params) { + char *params_tmp, *role; + void *state; + + PA_HASHMAP_FOREACH_KEY(params_tmp, file_info->roles, state, role) { + if (!compare_device_params(params_tmp, params)) { + return role; + } + } + return NULL; +} + +static dm_device* handle_device_type_available(struct device_type_info *type_info, const char *name, pa_device_manager *dm) { + dm_device_profile *profile_item = NULL; + dm_device *device_item = NULL; + pa_bool_t made_newly = FALSE; + dm_device_direction_t direction; + pa_hashmap *playback = NULL, *capture = NULL; + + pa_assert(dm); + pa_assert(dm->type_infos); + + pa_log_debug("handle_device_type_available, type:%s, profile:%s, name:%s", type_info->type, type_info->profile, name); + + + + /* Directions of some types are not statically defined, ex) earjack */ + if ((direction = device_type_get_direction(dm, type_info->type, type_info->profile, NULL)) < 0) { + pa_log_error("Failed to get direction of %s.%s", type_info->type, type_info->profile); + return NULL; + } + pa_log_debug("Direction of %s.%s is %s", type_info->type, type_info->profile, device_direction_to_string(direction)); + + /* Get Sink/Sources for device_type, profile */ + if (device_type_get_pulse_devices(type_info, &playback, &capture, dm) < 0) { + pa_log_error("Failed to get sink/sources related to %s.%s", type_info->type, type_info->profile); + return NULL; + } + + /* Check whether Sink/Sources for direction of type are loaded */ + if ((((direction & DM_DEVICE_DIRECTION_IN) && !capture) || ((direction & DM_DEVICE_DIRECTION_OUT) && !playback))) { + pa_log_debug("Sink/Sources for %s.%s are not fully loaded yet", type_info->type, type_info->profile); + goto failed; + } + + profile_item = create_device_profile(type_info->profile, direction, playback, capture); + + if (!(device_item = _device_manager_get_device(dm->device_list, type_info->type))) { + pa_log_debug("No device item for %s, Create", type_info->type); + device_item = create_device_item(type_info->type, name, profile_item, dm); + made_newly = TRUE; + } else { + _device_item_add_profile(device_item, profile_item, NULL, dm); + } + + return device_item; + +failed : + if (playback) + pa_hashmap_free(playback); + if (capture) + pa_hashmap_free(capture); + if (device_item && made_newly) + pa_xfree(device_item); + if (profile_item) + pa_xfree(profile_item); + return NULL; +} + +/* FIXME to get identifier of physical device */ +static const char* pulse_device_get_identifier(void *pulse_device, pa_device_type_t pdt, dm_device_class_t device_class) { +/* + const char *sysfs_path; + + if (device_class == TIZEN_AUDIO_DEVICE_CLASS_ALSA) { + if (!(sysfs_path = pa_proplist_gets(sink->proplist, "sysfs.path"))) { + pa_log_warn("No sysfs.path for sink '%s'", sink->name); + return NULL; + } else { + return sysfs_path; + } + } else if (device_class == TIZEN_AUDIO_DEVICE_CLASS_BT) { + } + */ + return NULL; +} + +static void handle_predefined_device_loaded(void *pulse_device, pa_device_type_t pdt, dm_device_class_t device_class, const char *device_string, const char *role, pa_device_manager *dm) { + const char *identifier, *device_string_tmp; + struct device_type_info *type_info; + struct device_status_info *status_info; + uint32_t type_idx; + dm_device *device_item; + dm_device_profile *profile_item; + + pa_assert(pulse_device); + pa_assert(dm); + pa_assert(dm->file_map); + pa_assert(device_string); + pa_assert(role); + + pa_log_debug("Predefined device loaded, Type:%s, Class:%d, device_string:%s, role:%s", pdt == PA_DEVICE_TYPE_SINK ? "sink" : "source", device_class, device_string, role); + + identifier = pulse_device_get_identifier(pulse_device, pdt, device_class); + PA_IDXSET_FOREACH(type_info, dm->type_infos, type_idx) { + /* foreach matching types (which has device_string-role) */ + if ((device_string_tmp = device_type_info_get_device_string(type_info, role, pdt)) && pa_streq(device_string_tmp, device_string)) { + /* + Check device_item is already exists. + If already exists, add loaded sink or source to that. + */ + if((device_item = _device_manager_get_device(dm->device_list, type_info->type))) { + if((profile_item = _device_item_get_profile(device_item, type_info->profile))) { + pa_log_debug("device_item for %s.%s already exists", type_info->type, type_info->profile); + if (!_device_profile_add_pulse_device(profile_item, role, pulse_device, pdt)) + pa_log_error("add pulse device to profile_item failed"); + continue; + } + } + + /* Get status_info for device_type, profile*/ + if (!(status_info = _device_manager_get_status_info(dm->device_status, type_info->type, type_info->profile, identifier))) { + pa_log_error("%s.%s.%s doesn't have status_info", type_info->type, type_info->profile, identifier); + continue; + } + /* Only if device_type is on detected state*/ + if (status_info->detected == DEVICE_DETECTED) { + pa_log_debug("%s.%s type is detected status", type_info->type, type_info->profile); + + handle_device_type_available(type_info, NULL, dm); + } else { + pa_log_debug(" This type is not detected status"); + } + } + } +} + +static pa_bool_t _device_type_direction_available(struct device_type_info *type_info, dm_device_direction_t direction) { + int direc_idx; + + for (direc_idx = 0; direc_idx < DEVICE_DIRECTION_MAX; direc_idx++) { + if (type_info->direction[direc_idx] == direction) { + return TRUE; + } + } + + return FALSE; +} + +static void handle_sink_unloaded(pa_sink *sink, pa_device_manager *dm) { + dm_device_profile *profile_item= NULL; + struct device_type_info *type_info; + dm_device *device_item; + uint32_t device_idx = 0, profile_idx; + pa_sink *sink_iter = NULL; + void *state = NULL; + const char *role; + + if (!sink || !dm) { + pa_log_error("Invalid Paramter"); + return; + } + pa_assert(sink); + pa_assert(dm); + pa_assert(dm->device_list); + + pa_log_debug("Sink unloaded, Let's remove associated device_profiles with this sink"); + + PA_IDXSET_FOREACH(device_item, dm->device_list, device_idx) { + PA_IDXSET_FOREACH(profile_item, device_item->profiles, profile_idx) { + if (profile_item->playback_devices) { + PA_HASHMAP_FOREACH_KEY(sink_iter, profile_item->playback_devices, state, role) { + if (sink_iter == sink) { + unsigned int profile_playback_size = 0, profile_capture_size = 0, item_size = 0; + pa_log_debug("device '%s' have this sink", device_item->name); + _device_profile_get_size(profile_item, &profile_playback_size, &profile_capture_size); + item_size = _device_item_get_size(device_item); + pa_log_debug("profile playback size : %u, capture size : %u, item size : %u", profile_playback_size, profile_capture_size, item_size); + if (profile_playback_size == 1 && profile_capture_size == 0) { + if (item_size == 1) { + pa_log_debug("notify device disconnected"); + notify_device_connection_changed(device_item, FALSE, dm); + } + } + _device_profile_remove_sink(profile_item, role); + } + } + if (!pa_hashmap_size(profile_item->playback_devices)) { + pa_hashmap_free(profile_item->playback_devices); + profile_item->playback_devices = NULL; + + if (profile_item->direction == DM_DEVICE_DIRECTION_BOTH) { + type_info = _device_manager_get_type_info(dm->type_infos, profile_item->device_item->type, profile_item->profile); + if (_device_type_direction_available(type_info, DM_DEVICE_DIRECTION_IN)) { + profile_item->direction = DM_DEVICE_DIRECTION_IN; + } else { + if (!destroy_device_profile(profile_item, dm)) + break; + } + } else { + if (!destroy_device_profile(profile_item, dm)) + break; + } + } else { + _device_profile_update_direction(profile_item); + } + } + } + } +} + +static void handle_source_unloaded(pa_source *source, pa_device_manager *dm) { + dm_device_profile *profile_item= NULL; + struct device_type_info *type_info; + dm_device *device_item; + uint32_t device_idx = 0, profile_idx; + pa_source *source_iter = NULL; + void *state = NULL; + const char *role; + + if (!source|| !dm) { + pa_log_error("Invalid Paramter"); + return; + } + pa_assert(source); + pa_assert(dm); + pa_assert(dm->device_list); + + pa_log_debug("Source unloaded, Let's remove associated device_profiles with this source"); + + PA_IDXSET_FOREACH(device_item, dm->device_list, device_idx) { + PA_IDXSET_FOREACH(profile_item, device_item->profiles, profile_idx) { + if (profile_item->capture_devices) { + PA_HASHMAP_FOREACH_KEY(source_iter, profile_item->capture_devices, state, role) { + if (source_iter == source) { + unsigned int profile_playback_size = 0, profile_capture_size = 0, item_size = 0; + pa_log_debug("device '%s' have this source", device_item->name); + _device_profile_get_size(profile_item, &profile_playback_size, &profile_capture_size); + item_size = _device_item_get_size(device_item); + pa_log_debug("profile playback size : %u, capture size : %u, item size : %u", profile_playback_size, profile_capture_size, item_size); + if (profile_capture_size == 1 && profile_playback_size == 0) { + if (item_size == 1) { + pa_log_debug("notify device disconnected"); + notify_device_connection_changed(device_item, FALSE, dm); + } + } + + _device_profile_remove_source(profile_item, role); + } + } + + if (!pa_hashmap_size(profile_item->capture_devices)) { + pa_hashmap_free(profile_item->capture_devices); + profile_item->capture_devices= NULL; + + if (profile_item->direction == DM_DEVICE_DIRECTION_BOTH) { + type_info = _device_manager_get_type_info(dm->type_infos, profile_item->device_item->type, profile_item->profile); + if (_device_type_direction_available(type_info, DM_DEVICE_DIRECTION_OUT)) { + profile_item->direction = DM_DEVICE_DIRECTION_OUT; + } else { + if (!destroy_device_profile(profile_item, dm)) + break; + } + } else { + if (!destroy_device_profile(profile_item, dm)) + break; + } + + } else { + _device_profile_update_direction(profile_item); + } + } + } + } +} + +static pa_hook_result_t sink_put_hook_callback(pa_core *c, pa_sink *sink, pa_device_manager *dm) { + const char *device_string = NULL, *role = NULL, *device_string_removed_params = NULL; + struct device_file_info *file_info = NULL; + dm_device_class_t device_class; + + pa_assert(c); + pa_assert(sink); + pa_assert(sink->proplist); + pa_assert(dm); + + if (pulse_device_class_is_monitor(sink->proplist)) { + pa_log_debug("This device's class is monitor. Skip this"); + return PA_HOOK_OK; + } + + pa_log_debug("========== Sink Put Hook Callback '%s'(%d) ==========", sink->name, sink->index); + + device_class = pulse_device_get_class(sink, PA_DEVICE_TYPE_SINK); + pa_log_debug("Device Class '%s'", device_class_to_string(device_class)); + + if (!(device_string = pulse_device_get_device_string(sink, PA_DEVICE_TYPE_SINK))) { + return PA_HOOK_OK; + } else { + pa_log_debug("Device String '%s'", device_string); + } + + if (device_class == DM_DEVICE_CLASS_BT) { + handle_not_predefined_device(dm, sink, PA_DEVICE_TYPE_SINK, device_class); + } else if ((file_info = _device_manager_get_file_info(dm->file_map->playback, device_string))) { + /* module's argument includes device-string(ex. device=hw:0,0 ), + but key params for device_types hashmap is not. */ + if (!(device_string_removed_params = pulse_device_get_device_string_removed_argument(sink, PA_DEVICE_TYPE_SINK))) { + pa_log_debug("argument null"); + return PA_HOOK_OK; + } + if(!(role = device_file_info_get_role_with_params(file_info, device_string_removed_params))) { + pa_log_error("No role for %s", file_info->device_string); + return PA_HOOK_OK; + } + + handle_predefined_device_loaded(sink, PA_DEVICE_TYPE_SINK, device_class, device_string, role, dm); + } else { + pa_log_debug("Not-predefined device"); + handle_not_predefined_device(dm, sink, PA_DEVICE_TYPE_SINK, device_class); + } + + dump_device_list(dm); + return PA_HOOK_OK; +} + +static pa_hook_result_t sink_unlink_hook_callback(pa_core *c, pa_sink *sink, pa_device_manager *dm) { + pa_assert(c); + pa_assert(sink); + pa_assert(sink->proplist); + pa_assert(dm); + + if (pulse_device_class_is_monitor(sink->proplist)) { + pa_log_debug("This device's class is monitor. Skip this"); + return PA_HOOK_OK; + } + + pa_log_debug("=========== Sink unlink Hook Callback '%s'(%d) ==========", sink->name, sink->index); + handle_sink_unloaded(sink, dm); + dump_device_list(dm); + return PA_HOOK_OK; +} + + +static pa_hook_result_t source_put_hook_callback(pa_core *c, pa_source *source, pa_device_manager *dm) { + const char *device_string = NULL, *role = NULL, *device_string_removed_params = NULL; + struct device_file_info *file_info = NULL; + dm_device_class_t device_class; + + pa_assert(c); + pa_assert(source); + pa_assert(source->proplist); + pa_assert(dm); + + if (pulse_device_class_is_monitor(source->proplist)) { + pa_log_debug("This device's class is monitor. Skip this"); + return PA_HOOK_OK; + } + + pa_log_debug("========== Source Put Hook Callback '%s'(%d) ==========", source->name, source->index); + + + device_class = pulse_device_get_class(source, PA_DEVICE_TYPE_SOURCE); + pa_log_debug("Device Class '%s'", device_class_to_string(device_class)); + + if (!(device_string = pulse_device_get_device_string(source, PA_DEVICE_TYPE_SOURCE))) { + return PA_HOOK_OK; + } else { + pa_log_debug("Device String '%s'", device_string); + } + + if (device_class == DM_DEVICE_CLASS_BT) { + handle_not_predefined_device(dm, source, PA_DEVICE_TYPE_SOURCE, device_class); + } else if ((file_info = _device_manager_get_file_info(dm->file_map->capture, device_string))) { + /* module's argument includes device-string(ex. device=hw:0,0 ), + but key params for device_types hashmap is not. */ + if (!(device_string_removed_params = pulse_device_get_device_string_removed_argument(source, PA_DEVICE_TYPE_SOURCE))) { + pa_log_debug("argument null"); + return PA_HOOK_OK; + } + if(!(role = device_file_info_get_role_with_params(file_info, device_string_removed_params))) { + pa_log_error("No role for %s", file_info->device_string); + return PA_HOOK_OK; + } + + handle_predefined_device_loaded(source, PA_DEVICE_TYPE_SOURCE, device_class, device_string, role, dm); + } else { + pa_log_debug("Not-predefined device"); + handle_not_predefined_device(dm, source, PA_DEVICE_TYPE_SOURCE, device_class); + } + + dump_device_list(dm); + return PA_HOOK_OK; +} + +static pa_hook_result_t source_unlink_hook_callback(pa_core *c, pa_source *source, pa_device_manager *dm) { + pa_assert(c); + pa_assert(source); + pa_assert(source->proplist); + pa_assert(dm); + + if (pulse_device_class_is_monitor(source->proplist)) { + pa_log_debug("This device's class is monitor. Skip this"); + return PA_HOOK_OK; + } + + pa_log_debug("========== Source unlink Hook Callback '%s'(%d) ==========", source->name, source->index); + handle_source_unloaded(source, dm); + dump_device_list(dm); + + return PA_HOOK_OK; +} + +/* + Build params for load sink or source, and load it. +*/ + +static void* load_device(pa_core *c, pa_device_type_t pdt, const char *device_string, const char *device_params) { + const char *args = NULL; + const char *module_name; + pa_module *module; + pa_sink *sink; + pa_source *source; + uint32_t device_idx; + dm_device_class_t device_class; + + pa_assert(c); + pa_assert(device_string); + pa_assert(device_params); + + pa_log_debug("-------- load_%s_device : '%s' '%s' -------", pdt == PA_DEVICE_TYPE_SINK ? "playback" : "capture", device_string, device_params); + + device_class = device_string_get_class(device_string); + if (device_class <= DM_DEVICE_CLASS_NONE || device_class >= DM_DEVICE_CLASS_MAX) { + pa_log_warn("Invalid device_string '%s'", device_string); + return NULL; + } + + if (!(module_name = device_class_get_module_name(device_class, pdt))) { + pa_log_error("Get proper module name to load failed"); + return NULL; + } + if (!(args = build_params_to_load_device(device_string, device_params, device_class))) { + pa_log_error("Get proper module name to load failed"); + return NULL; + } + if (!(module = pa_module_load(c, module_name, args))) { + pa_log_error("Load module with name '%s' argu '%s' failed", module_name, args); + return NULL; + } + + + if (pdt == PA_DEVICE_TYPE_SINK) { + PA_IDXSET_FOREACH(sink, c->sinks, device_idx) { + if (sink->module == module) { + return sink; + } + } + } else { + PA_IDXSET_FOREACH(source, c->sources, device_idx) { + if (source->module == module) { + return source; + } + } + } + + return NULL; +} + +/* + Load sink/sources with information written in device-file map, + If there is several roles in same device-file, then first load with 'normal' params + and other roles with same params just reference it. if there is a role which is on same device + but have different params, then do not load it. (ex.uhqa) + This does not make device_item , just load sink or source. +*/ +static int load_builtin_devices(pa_device_manager *dm) { + void *role_state = NULL; + struct device_file_info *file_info = NULL; + const char *params, *role; + uint32_t file_idx; + + pa_assert(dm); + + pa_log_debug("\n==================== Load Builtin Devices ===================="); + + if (dm->file_map->playback) { + PA_IDXSET_FOREACH(file_info, dm->file_map->playback, file_idx) { + pa_log_debug("---------------- load sink for '%s' ------------------", file_info->device_string); + + /* if normal device exists , load first */ + if ((params = pa_hashmap_get(file_info->roles, DEVICE_ROLE_NORMAL))) { + if (!load_device(dm->core, PA_DEVICE_TYPE_SINK, file_info->device_string, params)) + pa_log_error("load normal playback device failed"); + } + + PA_HASHMAP_FOREACH_KEY(params, file_info->roles, role_state, role) { + if (pa_streq(role, DEVICE_ROLE_NORMAL)) + continue; + pa_log_debug("load sink for role %s", role); + if (!pulse_device_loaded_with_param(dm->core, PA_DEVICE_TYPE_SINK, file_info->device_string, params)) { + if (!load_device(dm->core, PA_DEVICE_TYPE_SINK, file_info->device_string, params)) + pa_log_error("load playback device failed"); + } + } + } + } + + + + if (dm->file_map->capture) { + PA_IDXSET_FOREACH(file_info, dm->file_map->capture, file_idx) { + pa_log_debug("---------------- load source for '%s' ------------------", file_info->device_string); + + /* if normal device exists , load first */ + if ((params = pa_hashmap_get(file_info->roles, DEVICE_ROLE_NORMAL))) { + if (!load_device(dm->core, PA_DEVICE_TYPE_SOURCE, file_info->device_string, params)) pa_log_error("load normal capture device failed"); + } + + PA_HASHMAP_FOREACH_KEY(params, file_info->roles, role_state, role) { + if (pa_streq(role, DEVICE_ROLE_NORMAL)) + continue; + pa_log_debug("load source for role %s", role); + if (!pulse_device_loaded_with_param(dm->core, PA_DEVICE_TYPE_SOURCE, file_info->device_string, params)) { + if (!load_device(dm->core, PA_DEVICE_TYPE_SOURCE, file_info->device_string, params)) { + pa_log_error("load capture device failed"); + } + } + } + } + } + + return 0; +} + + +/***************** Parse json file *******************/ +static pa_hashmap* parse_device_role_object(json_object *device_role_o) { + pa_hashmap *roles = NULL; + const char *params, *device_role; + struct json_object_iterator it, it_end; + json_object *params_o; + + pa_assert(device_role_o); + pa_assert(json_object_is_type(device_role_o, json_type_object)); + + roles = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + if (!roles) { + pa_log_debug("hashmap new failed"); + goto failed; + } + + it = json_object_iter_begin(device_role_o); + it_end = json_object_iter_end(device_role_o); + + while (!json_object_iter_equal(&it, &it_end)) { + device_role = json_object_iter_peek_name(&it); + params_o = json_object_iter_peek_value(&it); + + if (!(params = json_object_get_string(params_o))) { + pa_log_debug("There is no device params for role '%s'", device_role); + } + pa_log_debug("[DEBUG_PARSE] role '%s' - params '%s'", device_role, params); + if (device_role_is_valid(device_role)) { + if (pa_hashmap_put(roles, (void *)device_role, (void *)params)) { + pa_log_error("put new role to hashmap faild"); + goto failed; + } + } else { + pa_log_error("Invalid device role '%s'", device_role); + } + + json_object_iter_next(&it); + } + + if (pa_hashmap_size(roles) == 0) { + pa_log_warn("There is no role for device.. free hashmap"); + pa_hashmap_free(roles); + roles = NULL; + } + + return roles; + +failed: + if (roles) + pa_hashmap_free(roles); + + return NULL; +} + +static struct device_file_info* parse_device_file_object(json_object *device_file_o, const char **device_string_key) { + pa_hashmap *roles = NULL; + json_object *device_file_prop_o = NULL; + const char *device_string = NULL; + struct device_file_info *file_info = NULL; + + pa_assert(device_file_o); + pa_assert(device_string_key); + pa_assert(json_object_is_type(device_file_o, json_type_object)); + + if ((device_file_prop_o = json_object_object_get(device_file_o, "device-string")) && json_object_is_type(device_file_prop_o, json_type_string)) { + if ((device_string = json_object_get_string(device_file_prop_o))) { + pa_log_debug("[DEBUG_PARSE] ---------------- Device File '%s' ----------------", device_string); + } else { + pa_log_error("Get device-string failed"); + return NULL; + } + } else { + pa_log_error("Get device-string object failed"); + return NULL; + } + + if ((device_file_prop_o = json_object_object_get(device_file_o, DEVICE_TYPE_PROP_ROLE))) { + if (!(roles = parse_device_role_object(device_file_prop_o))) { + pa_log_error("Parse device role for '%s' failed", device_string); + goto failed; + } + } else { + pa_log_error("Get device role object failed"); + } + + file_info = pa_xmalloc0(sizeof(struct device_file_info)); + file_info->device_string = device_string; + file_info->device_types = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + file_info->roles = roles; + +// *device_string_key = device_string; + + return file_info; + +failed : + if (roles) + pa_xfree(roles); + + return NULL; +} + +static pa_idxset* parse_device_file_array_object(json_object *device_file_array_o) { + int device_file_num, device_file_idx; + struct device_file_info *file_info = NULL; + json_object *device_file_o = NULL; + pa_idxset *device_files = NULL; + const char *device_string = NULL; + + pa_assert(device_file_array_o); + pa_assert(json_object_is_type(device_file_array_o, json_type_array)); + + device_files = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + + device_file_num = json_object_array_length(device_file_array_o); + for (device_file_idx = 0; device_file_idx < device_file_num; device_file_idx++) { + if ((device_file_o = json_object_array_get_idx(device_file_array_o, device_file_idx)) && json_object_is_type(device_file_o, json_type_object)) { + if ((file_info = parse_device_file_object(device_file_o, &device_string))) { + pa_idxset_put(device_files, file_info, NULL); + } else { + pa_log_error("parse device file object failed"); + goto failed; + } + } else { + pa_log_error("Get device file object failed"); + goto failed; + } + } + + if (pa_idxset_size(device_files) == 0) { + pa_idxset_free(device_files, NULL); + device_files = NULL; + } + return device_files; +failed: + if (device_files) + pa_xfree(device_files); + return NULL; +} + +static struct device_file_map *parse_device_file_map() { + struct device_file_map *file_map = NULL; + json_object *o, *device_files_o; + json_object *playback_devices_o = NULL, *capture_devices_o = NULL; + + pa_log_debug("\n[DEBUG_PARSE] ==================== Parse device files ===================="); + + o = json_object_from_file(DEVICE_MAP_FILE); + + if (is_error(o)) { + pa_log_error("Read device-map file failed"); + return NULL; + } + + file_map = pa_xmalloc0(sizeof(struct device_file_map)); + + if ((device_files_o = json_object_object_get(o, DEVICE_FILE_OBJECT)) && json_object_is_type(device_files_o, json_type_object)) { + if ((playback_devices_o = json_object_object_get(device_files_o, "playback-devices"))) { + pa_log_debug("[DEBUG_PARSE] ----------------- Playback Device Files ------------------"); + file_map->playback = parse_device_file_array_object(playback_devices_o); + } + if ((capture_devices_o = json_object_object_get(device_files_o, "capture-devices"))) { + pa_log_debug("[DEBUG_PARSE] ----------------- Capture Device Files ------------------"); + file_map->capture = parse_device_file_array_object(capture_devices_o); + } + } + else { + pa_log_error("Get device files object failed"); + return NULL; + } + + return file_map; +} + + +static pa_hashmap* parse_device_role_map(json_object *device_role_map_o) { + pa_hashmap *roles = NULL; + const char *device_string, *device_role; + struct json_object_iterator it, it_end; + json_object *device_string_o; + + pa_assert(device_role_map_o); + pa_assert(json_object_is_type(device_role_map_o, json_type_object)); + + roles = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + + it = json_object_iter_begin(device_role_map_o); + it_end = json_object_iter_end(device_role_map_o); + + while (!json_object_iter_equal(&it, &it_end)) { + device_role = json_object_iter_peek_name(&it); + device_string_o = json_object_iter_peek_value(&it); + + if (!(device_string = json_object_get_string(device_string_o))) { + pa_log_debug("There is no device string for role '%s'", device_role); + } + pa_log_debug("[DEBUG_PARSE] role '%s' - device_string '%s'", device_role, device_string); + if (device_role_is_valid(device_role)) { + if (pa_hashmap_put(roles, (void *)device_role, (void *)device_string)) { + pa_log_error("put new role to hashmap faild"); + goto failed; + } + } else { + pa_log_error("Invalid device role '%s'", device_role); + goto failed; + } + + json_object_iter_next(&it); + } + + return roles; + +failed : + if (roles) + pa_xfree(roles); + + return NULL; +} + + + +static pa_idxset* parse_device_type_infos() { + json_object *o, *device_array_o = NULL; + int device_type_num = 0; + int device_type_idx = 0; + json_bool builtin; + struct device_type_info *type_info = NULL; + //pa_hashmap *type_infos = NULL; + pa_idxset *type_infos = NULL; + + o = json_object_from_file(DEVICE_MAP_FILE); + if (is_error(o)) { + pa_log_error("Read device-map file failed"); + return NULL; + } + + pa_log_debug("\n[DEBUG_PARSE] ==================== Parse device types ===================="); + type_infos = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + + if ((device_array_o = json_object_object_get(o, DEVICE_TYPE_OBJECT)) && json_object_is_type(device_array_o, json_type_array)) { + device_type_num = json_object_array_length(device_array_o); + for (device_type_idx = 0; device_type_idx < device_type_num ; device_type_idx++) { + json_object *device_o; + + if ((device_o = json_object_array_get_idx(device_array_o, device_type_idx)) && json_object_is_type(device_o, json_type_object)) { + json_object *device_prop_o; + json_object *array_item_o; + int array_len, array_idx; + const char *device_type = NULL, *device_profile = NULL; + type_info = pa_xmalloc0(sizeof(struct device_type_info)); + + if ((device_prop_o = json_object_object_get(device_o, "device-type")) && json_object_is_type(device_prop_o, json_type_string)) { + device_type = json_object_get_string(device_prop_o); + pa_log_debug("[DEBUG_PARSE] ---------------- Parse device '%s' ----------------", device_type); + type_info->type = device_type; + } else { + pa_log_error("Get device type failed"); + goto failed; + } + if ((device_prop_o = json_object_object_get(device_o, "profile")) && json_object_is_type(device_prop_o, json_type_string)) { + device_profile = json_object_get_string(device_prop_o); + pa_log_debug("[DEBUG_PARSE] Profile: %s", device_profile); + type_info->profile= device_profile; + } else { + pa_log_debug("no device-profile"); + } + + + if ((device_prop_o = json_object_object_get(device_o, DEVICE_TYPE_PROP_BUILTIN)) && json_object_is_type(device_prop_o, json_type_boolean)) { + builtin = json_object_get_boolean(device_prop_o); + pa_log_debug("[DEBUG_PARSE] builtin: %d", builtin); + type_info->builtin = builtin; + } else { + pa_log_error("Get device prop '%s' failed", DEVICE_TYPE_PROP_BUILTIN); + } + + if ((device_prop_o = json_object_object_get(device_o, DEVICE_TYPE_PROP_DIRECTION)) && json_object_is_type(device_prop_o, json_type_array)) { + const char *direction; + array_len = json_object_array_length(device_prop_o); + if ((array_len = json_object_array_length(device_prop_o)) > DEVICE_DIRECTION_MAX) { + pa_log_error("Invalid case, The number of direction is too big (%d)", array_len); + goto failed; + } + for (array_idx = 0; array_idx < array_len; array_idx++) { + if ((array_item_o = json_object_array_get_idx(device_prop_o, array_idx)) && json_object_is_type(array_item_o, json_type_string)) { + direction = json_object_get_string(array_item_o); + pa_log_debug("[DEBUG_PARSE] direction : %s", direction); + type_info->direction[array_idx] = device_direction_to_int(direction); + } + } + } else { + pa_log_error("Get device prop '%s' failed", DEVICE_TYPE_PROP_DIRECTION); + } + + if ((device_prop_o = json_object_object_get(device_o, "avail-condition")) && json_object_is_type(device_prop_o, json_type_array)) { + const char *avail_cond; + if ((array_len = json_object_array_length(device_prop_o)) > DEVICE_AVAIL_COND_NUM_MAX) { + pa_log_error("Invalid case, The number of avail-condition is too big (%d)", array_len); + goto failed; + } + for (array_idx = 0; array_idx < array_len; array_idx++) { + if ((array_item_o = json_object_array_get_idx(device_prop_o, array_idx)) && json_object_is_type(array_item_o, json_type_string)) { + avail_cond = json_object_get_string(array_item_o); + pa_log_debug("[DEBUG_PARSE] avail-condition : %s", avail_cond); + strncpy(type_info->avail_condition[array_idx], avail_cond, DEVICE_AVAIL_COND_STR_MAX); + } + } + } else { + pa_log_error("Get device prop 'avail-condition' failed"); + } + + if ((device_prop_o = json_object_object_get(device_o, "playback-devices")) && json_object_is_type(device_prop_o, json_type_object)) { + pa_log_debug("[DEBUG_PARSE] ------ playback devices ------"); + type_info->playback_devices = parse_device_role_map(device_prop_o); + } + + if ((device_prop_o = json_object_object_get(device_o, "capture-devices")) && json_object_is_type(device_prop_o, json_type_object)) { + pa_log_debug("[DEBUG_PARSE] ------ capture devices ------"); + type_info->capture_devices = parse_device_role_map(device_prop_o); + } + pa_idxset_put(type_infos, type_info, NULL); + + } + else { + pa_log_debug("Get device type object failed"); + } + } + } + else { + pa_log_debug("Get device type array object failed"); + } + return type_infos; + +failed : + if (type_infos) + pa_xfree(type_infos); + + return NULL; +} + +/* + Handle device connection detected through dbus. + First, update device-status hashmap. + And if correnspondent sink/sources for device_type exist, should make device_item and notify it. + Use [device_type->roles] mappings in sink/source for find proper sink/source. +*/ +static int handle_device_connected(pa_device_manager *dm, const char *device_type, const char *device_profile, const char *name, const char *identifier, int detected_type) { + struct device_status_info *status_info; + struct device_type_info *type_info; + dm_device *device_item; + dm_device_profile *profile_item; + + pa_assert(dm); + pa_assert(dm->device_status); + + pa_log_debug("Device %s connected, detected_type : %d", device_type, detected_type); + if (!(status_info = _device_manager_get_status_info(dm->device_status, device_type, device_profile, identifier))) { + pa_log_error("No device_status_info for %s.%s", device_type, device_profile); + return -1; + } + status_info->detected = DEVICE_DETECTED; + status_info->detected_type = detected_type; + + if (!(type_info = _device_manager_get_type_info(dm->type_infos, device_type, device_profile))) { + pa_log_error("Failed to get type_info for %s.%s", device_type, device_profile); + } + + if((device_item = _device_manager_get_device(dm->device_list, type_info->type))) { + if((profile_item = _device_item_get_profile(device_item, type_info->profile))) { + pa_log_debug("device_item for %s.%s already exists", type_info->type, type_info->profile); + return 0; + } + } + + handle_device_type_available(type_info, name, dm); + + return 0; +} + +/* + Handle device disconnection detected through dbus. + First, update device-status hashmap. + And if there is device_item which has the device_type, remove it. +*/ +static int handle_device_disconnected(pa_device_manager *dm, const char *device_type, const char *device_profile, const char *identifier) { + dm_device_profile *profile_item; + dm_device *device_item; + struct device_status_info *status_info; + uint32_t device_idx = 0; + + pa_assert(dm); + pa_assert(dm->device_status); + + pa_log_debug("Device %s disconnected", device_type); + if (!(status_info = _device_manager_get_status_info(dm->device_status, device_type, device_profile, identifier))) { + pa_log_error("No device_status_info for %s.%s", device_type, device_profile); + return -1; + } + status_info->detected = DEVICE_NOT_DETECTED; + + PA_IDXSET_FOREACH(device_item, dm->device_list, device_idx) { + if (pa_streq(device_item->type, device_type)) { + if((profile_item = _device_item_get_profile(device_item, device_profile))) { + if (_device_item_get_size(device_item) == 1) + notify_device_connection_changed(device_item, FALSE, dm); + destroy_device_profile(profile_item, dm); + } else { + pa_log_debug("no matching profile"); + } + } + } + + return 0; +} + + +/* + look detected status which is external value, make conversion to internal consistent value, and handle it + device_type, device_profile : which type of device is detected + identifier : identifier among same device types for support multi-device +*/ +static int handle_device_status_changed(pa_device_manager *dm, const char *device_type, const char *device_profile, const char *name, const char *identifier, int detected_status) { + pa_assert(dm); + pa_assert(device_type_is_valid(device_type)); + + pa_log_debug("Device Status Changed, type : '%s', profile : '%s', identifier : '%s', detected_status : %d", device_type, device_profile, identifier, detected_status); + if (pa_streq(device_type, DEVICE_TYPE_AUDIO_JACK)) { + if (detected_status == EARJACK_DISCONNECTED) { + handle_device_disconnected(dm, device_type, device_profile, identifier); + } else if (detected_status == EARJACK_TYPE_SPK_ONLY) { + handle_device_connected(dm, device_type, device_profile, name, identifier, DEVICE_DETECTED_AUDIO_JACK_OUT_DIREC); + } else if (detected_status == EARJACK_TYPE_SPK_WITH_MIC) { + handle_device_connected(dm, device_type, device_profile, name, identifier, DEVICE_DETECTED_AUDIO_JACK_BOTH_DIREC); + } else { + pa_log_warn("Got invalid audio-jack detected value"); + return -1; + } + } else if (pa_streq(device_type, DEVICE_TYPE_BT) && device_profile && pa_streq(device_profile, DEVICE_PROFILE_BT_SCO)) { + if (detected_status == BT_SCO_DISCONNECTED) { + handle_device_disconnected(dm, device_type, device_profile, identifier); + } else if (detected_status == BT_SCO_CONNECTED) { + handle_device_connected(dm, device_type, device_profile, name, identifier, DEVICE_DETECTED_BT_SCO); + } else { + pa_log_warn("Got invalid bt-sco detected value"); + return -1; + } + } else if (pa_streq(device_type, DEVICE_TYPE_HDMI)) { + if (detected_status == HDMI_AUDIO_DISCONNECTED) { + handle_device_disconnected(dm, device_type, device_profile, identifier); + } else if (detected_status >= HDMI_AUDIO_AVAILABLE) { + handle_device_connected(dm, device_type, device_profile, name, identifier, DEVICE_DETECTED_HDMI); + } else if (detected_status == HDMI_AUDIO_NOT_AVAILABLE) { + pa_log_debug("HDMI audio not available"); + return -1; + } else { + pa_log_warn("Got invalid hdmi detected value"); + return -1; + } + } else if (pa_streq(device_type, DEVICE_TYPE_FORWARDING)) { + if (detected_status == FORWARDING_DISCONNECTED) { + handle_device_disconnected(dm, device_type, device_profile, identifier); + } else if (detected_status == FORWARDING_CONNECTED) { + handle_device_connected(dm, device_type, device_profile, name, identifier, DEVICE_DETECTED_FORWARDING); + } else { + pa_log_warn("Got invalid mirroring detected value"); + return -1; + } + } else { + pa_log_debug("unknown device type"); + } + return 0; +} + +/* + Initialize device-status idxset. + This is for device-status detected through dbus. + So, if device_type is not detected through dbus, let's initialize them to detected. (ex. spk, rcv,...) + If not, initialize to not detected. +*/ +static pa_idxset* device_type_status_init(pa_idxset *type_infos) { + int avail_cond_idx = 0, avail_cond_num = 0, correct_avail_cond = 0; + struct device_type_info *type_info; + struct device_status_info *status_info; + pa_idxset *device_status; + uint32_t type_idx; + + pa_assert(type_infos); + + pa_log_debug("\n==================== Init Device Status ===================="); + + device_status = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + + PA_IDXSET_FOREACH(type_info, type_infos, type_idx) { + status_info = (struct device_status_info *) pa_xmalloc0(sizeof(struct device_status_info)); + status_info->type = type_info->type; + status_info->profile = type_info->profile; + if (!compare_device_type(status_info->type, status_info->profile, DEVICE_TYPE_AUDIO_JACK, NULL)) { + int earjack_status = 0; + if (vconf_get_int(VCONFKEY_SYSMAN_EARJACK, &earjack_status) < 0) { + status_info->detected = DEVICE_NOT_DETECTED; + pa_log_error("Get earjack status failed"); + } else if (earjack_status == EARJACK_DISCONNECTED) { + status_info->detected = DEVICE_NOT_DETECTED; + } else if (earjack_status == EARJACK_TYPE_SPK_ONLY) { + status_info->detected = DEVICE_DETECTED; + status_info->detected_type = DEVICE_DETECTED_AUDIO_JACK_OUT_DIREC; + } else if (earjack_status == EARJACK_TYPE_SPK_WITH_MIC) { + status_info->detected = DEVICE_DETECTED; + status_info->detected_type = DEVICE_DETECTED_AUDIO_JACK_BOTH_DIREC; + } else { + status_info->detected = DEVICE_NOT_DETECTED; + pa_log_warn("Unknown earjack status : %d", earjack_status); + } + } else if (!compare_device_type(status_info->type, status_info->profile, DEVICE_TYPE_BT, DEVICE_PROFILE_BT_SCO)) { + } else if (!compare_device_type(status_info->type, status_info->profile, DEVICE_TYPE_FORWARDING, NULL)) { + int miracast_wfd_status = 0; + if (vconf_get_bool(VCONFKEY_MIRACAST_WFD_SOURCE_STATUS, &miracast_wfd_status) < 0) { + status_info->detected = DEVICE_NOT_DETECTED; + pa_log_error("Get mirroring status failed"); + } else if (miracast_wfd_status == FORWARDING_DISCONNECTED) { + status_info->detected = DEVICE_NOT_DETECTED; + } else if (miracast_wfd_status == FORWARDING_CONNECTED) { + status_info->detected = DEVICE_DETECTED; + status_info->detected_type = DEVICE_DETECTED_FORWARDING; + } else { + status_info->detected = DEVICE_NOT_DETECTED; + pa_log_warn("Unknown mirroring status : %d", miracast_wfd_status); + } + } else { + for (avail_cond_idx = 0, avail_cond_num = 0; avail_cond_idx < DEVICE_AVAIL_COND_NUM_MAX; avail_cond_idx++) { + if (pa_streq(type_info->avail_condition[avail_cond_idx], "")) { + avail_cond_num++; + } + } + if (avail_cond_num == 1 && pa_streq(type_info->avail_condition[correct_avail_cond], DEVICE_AVAIL_CONDITION_STR_PULSE)) { + /* device types which don't need to be detected from other-side, let's just set 'detected'*/ + status_info->detected = DEVICE_DETECTED; + } else { + status_info->detected = DEVICE_NOT_DETECTED; + } + } + + pa_log_debug("Set %-17s %s detected", type_info->type, (status_info->detected == DEVICE_DETECTED) ? "" : "not"); + pa_idxset_put(device_status, status_info, NULL); + } + return device_status; +} + +static int device_list_init(pa_device_manager *dm) { + pa_assert(dm); + + dm->device_list = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + + return 0; +} + +#ifdef HAVE_DBUS + +static DBusHandlerResult dbus_filter_device_detect_handler(DBusConnection *c, DBusMessage *s, void *userdata) { + DBusError error; + int status = 0; + pa_device_manager *dm = (pa_device_manager *) userdata; + + pa_assert(userdata); + + if (dbus_message_get_type(s) != DBUS_MESSAGE_TYPE_SIGNAL) + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + pa_log_info("Dbus device detect handler received msg"); + + pa_log_debug("path : %s", dbus_message_get_path(s)); + pa_log_debug("interface : %s", dbus_message_get_interface(s)); + pa_log_debug("member : %s", dbus_message_get_member(s)); + pa_log_debug("siganature : %s", dbus_message_get_signature(s)); + + dbus_error_init(&error); + + if (dbus_message_is_signal(s, DBUS_INTERFACE_DEVICED_SYSNOTI, "ChangedEarjack")) { + if (!dbus_message_get_args(s, NULL, DBUS_TYPE_INT32, &status, DBUS_TYPE_INVALID)) { + goto fail; + } else { + handle_device_status_changed(dm, DEVICE_TYPE_AUDIO_JACK, NULL, NULL, NULL, status); + } + } else if (dbus_message_is_signal(s, DBUS_INTERFACE_DEVICED_SYSNOTI, "ChangedHDMIAudio")) { + if (!dbus_message_get_args(s, NULL, DBUS_TYPE_INT32, &status, DBUS_TYPE_INVALID)) { + goto fail; + } else { + handle_device_status_changed(dm, DEVICE_TYPE_HDMI, NULL, NULL, NULL, status); + } + } else if (dbus_message_is_signal(s, DBUS_INTERFACE_MIRRORING_SERVER, "miracast_wfd_source_status_changed")) { + if (!dbus_message_get_args(s, NULL, DBUS_TYPE_INT32, &status, DBUS_TYPE_INVALID)) { + goto fail; + } else { + handle_device_status_changed(dm, DEVICE_TYPE_FORWARDING, NULL, NULL, NULL, status); + } + } else if (dbus_message_is_signal(s, DBUS_INTERFACE_BLUEZ_HEADSET, "PropertyChanged")) { + DBusMessageIter msg_iter, variant_iter; + char *property_name; + + pa_log_debug("Got %s PropertyChanged signal", DBUS_INTERFACE_BLUEZ_HEADSET); + dbus_message_iter_init(s, &msg_iter); + if (dbus_message_iter_get_arg_type(&msg_iter) != DBUS_TYPE_STRING) { + pa_log_error("Property name not string"); + goto fail; + } + dbus_message_iter_get_basic(&msg_iter, &property_name); + pa_log_debug("property name : %s", property_name); + + if (!dbus_message_iter_next(&msg_iter)) { + pa_log_debug("Property value missing"); + goto fail; + } + + if (dbus_message_iter_get_arg_type(&msg_iter) != DBUS_TYPE_VARIANT) { + pa_log_debug("Property value not a variant."); + goto fail; + } + + dbus_message_iter_recurse(&msg_iter, &variant_iter); + + if (DBUS_TYPE_BOOLEAN == dbus_message_iter_get_arg_type(&variant_iter)) { + dbus_bool_t value; + char *name; + dbus_message_iter_get_basic(&variant_iter, &value); + if (pa_streq(property_name, "Playing")) { + dm_device *device_item; + pa_log_debug("SCO Playing : %d", value); + if ((device_item = _device_manager_get_device(dm->device_list, DEVICE_TYPE_BT))) { + if (value) + _device_item_set_active_profile(device_item, DEVICE_PROFILE_BT_SCO); + else + _device_item_set_active_profile_auto(device_item); + } + } else if (pa_streq(property_name, "Connected")) { + pa_log_debug("HFP Connection : %d", value); + if (value) { + method_call_bt_get_name(c, dbus_message_get_path(s), &name); + status = BT_SCO_CONNECTED; + } else { + status = BT_SCO_DISCONNECTED; + } + handle_device_status_changed(dm, DEVICE_TYPE_BT, DEVICE_PROFILE_BT_SCO, name, NULL, status); + } + } + } else { + pa_log_info("Unknown message, not handle it"); + dbus_error_free(&error); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + pa_log_debug("Dbus Message handled"); + + dbus_error_free(&error); + return DBUS_HANDLER_RESULT_HANDLED; + +fail: + pa_log_error("Fail to handle dbus signal"); + dbus_error_free(&error); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +static int watch_signals(pa_device_manager *dm) { + DBusError error; + + pa_assert(dm); + pa_assert(dm->dbus_conn); + + dbus_error_init(&error); + + pa_log_debug("Watch Dbus signals"); + + if (!dbus_connection_add_filter(pa_dbus_connection_get(dm->dbus_conn), dbus_filter_device_detect_handler, dm, NULL)) { + pa_log_error("Unable to add D-Bus filter : %s: %s", error.name, error.message); + goto fail; + } + + if (pa_dbus_add_matches(pa_dbus_connection_get(dm->dbus_conn), &error, FILTER_DEVICED_SYSNOTI, FILTER_SOUND_SERVER, FILTER_BLUEZ, FILTER_MIRRORING, NULL) < 0) { + pa_log_error("Unable to subscribe to signals: %s: %s", error.name, error.message); + goto fail; + } + return 0; + +fail: + dbus_error_free(&error); + return -1; +} + +static void unwatch_signals(pa_device_manager *dm) { + pa_log_debug("Unwatch Dbus signals"); + + pa_assert(dm); + pa_assert(dm->dbus_conn); + + pa_dbus_remove_matches(pa_dbus_connection_get(dm->dbus_conn), FILTER_DEVICED_SYSNOTI, FILTER_SOUND_SERVER, FILTER_BLUEZ, FILTER_MIRRORING, NULL); + dbus_connection_remove_filter(pa_dbus_connection_get(dm->dbus_conn), dbus_filter_device_detect_handler, dm); +} + + +static void send_device_connected_signal(dm_device *device_item, pa_bool_t connected, pa_device_manager *dm) { + DBusMessage *signal_msg; + DBusMessageIter msg_iter, device_iter; + dm_device_profile *profile_item; + dbus_bool_t _connected = connected; + dm_device_state_t compound_state; + dbus_int32_t device_id; + + pa_assert(device_item); + pa_assert(device_item->profiles); + pa_assert(dm); + + pa_log_debug("Send following device %s signal", connected ? "Connected" : "Disconnected"); + dump_device_info(device_item); + + pa_assert_se(signal_msg = dbus_message_new_signal(DBUS_OBJECT_DEVICE_MANAGER, DBUS_INTERFACE_DEVICE_MANAGER, "DeviceConnected")); + dbus_message_iter_init_append(signal_msg, &msg_iter); + pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_STRUCT, NULL, &device_iter)); + if (!(profile_item = _device_item_get_active_profile(device_item))) { + pa_log_error("active profile null"); + return; + } + + device_id = (dbus_int32_t) device_item->id; + compound_state = COMPOUND_STATE(profile_item); + dbus_message_iter_append_basic(&device_iter, DBUS_TYPE_INT32, &device_id); + dbus_message_iter_append_basic(&device_iter, DBUS_TYPE_STRING, &device_item->type); + dbus_message_iter_append_basic(&device_iter, DBUS_TYPE_INT32, &profile_item->direction); + dbus_message_iter_append_basic(&device_iter, DBUS_TYPE_INT32, &compound_state); + dbus_message_iter_append_basic(&device_iter, DBUS_TYPE_STRING, &device_item->name); + pa_assert_se(dbus_message_iter_close_container(&msg_iter, &device_iter)); + dbus_message_iter_append_basic(&msg_iter, DBUS_TYPE_BOOLEAN, &_connected); + + + pa_assert_se(dbus_connection_send(pa_dbus_connection_get(dm->dbus_conn), signal_msg, NULL)); + dbus_message_unref(signal_msg); +} + +static void send_device_info_changed_signal(dm_device *device_item, int changed_type, pa_device_manager *dm) { + DBusMessage *signal_msg; + DBusMessageIter msg_iter, device_iter; + dm_device_profile *profile_item; + dm_device_state_t compound_state; + dbus_int32_t device_id; + + pa_assert(device_item); + pa_assert(device_item->profiles); + pa_assert(dm); + + pa_log_debug("Send folling device info changed signal"); + dump_device_info(device_item); + + pa_assert_se(signal_msg = dbus_message_new_signal(DBUS_OBJECT_DEVICE_MANAGER, DBUS_INTERFACE_DEVICE_MANAGER, "DeviceInfoChanged")); + dbus_message_iter_init_append(signal_msg, &msg_iter); + pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_STRUCT, NULL, &device_iter)); + if (!(profile_item = _device_item_get_active_profile(device_item))) { + pa_log_error("active profile null"); + return; + } + device_id = (dbus_int32_t) device_item->id; + compound_state = COMPOUND_STATE(profile_item); + dbus_message_iter_append_basic(&device_iter, DBUS_TYPE_INT32, &device_id); + dbus_message_iter_append_basic(&device_iter, DBUS_TYPE_STRING, &device_item->type); + dbus_message_iter_append_basic(&device_iter, DBUS_TYPE_INT32, &profile_item->direction); + dbus_message_iter_append_basic(&device_iter, DBUS_TYPE_INT32, &compound_state); + dbus_message_iter_append_basic(&device_iter, DBUS_TYPE_STRING, &device_item->name); + pa_assert_se(dbus_message_iter_close_container(&msg_iter, &device_iter)); + dbus_message_iter_append_basic(&msg_iter, DBUS_TYPE_INT32, &changed_type); + + + pa_assert_se(dbus_connection_send(pa_dbus_connection_get(dm->dbus_conn), signal_msg, NULL)); + dbus_message_unref(signal_msg); +} + +static void notify_device_connection_changed(dm_device *device_item, pa_bool_t connected, pa_device_manager *dm) { + pa_device_manager_hook_data_for_conn_changed hook_data; + + send_device_connected_signal(device_item, connected, dm); + hook_data.is_connected = connected; + hook_data.device = device_item; + pa_hook_fire(pa_communicator_hook(dm->comm, PA_COMMUNICATOR_HOOK_DEVICE_CONNECTION_CHANGED), &hook_data); +} + +static void notify_device_info_changed(dm_device *device_item, dm_device_changed_info_t changed_type, pa_device_manager *dm) { + pa_device_manager_hook_data_for_info_changed hook_data; + + send_device_info_changed_signal(device_item, changed_type, dm); + + hook_data.changed_info = changed_type; + hook_data.device = device_item; + pa_hook_fire(pa_communicator_hook(dm->comm, PA_COMMUNICATOR_HOOK_DEVICE_INFORMATION_CHANGED), &hook_data); +} + +static pa_bool_t device_item_match_for_mask(dm_device *device_item, int device_flags, pa_device_manager *dm) { + dm_device_profile *profile_item = NULL; + pa_bool_t match = FALSE; + int need_to_check_for_io_direction = device_flags & DEVICE_IO_DIRECTION_FLAGS; + int need_to_check_for_state = device_flags & DEVICE_STATE_FLAGS; + int need_to_check_for_type = device_flags & DEVICE_TYPE_FLAGS; + + pa_assert(device_item); + + if (device_flags == DEVICE_ALL_FLAG) + return TRUE; + + profile_item = _device_item_get_active_profile(device_item); + if (need_to_check_for_io_direction) { + if ((profile_item->direction == DM_DEVICE_DIRECTION_IN) && (device_flags & DEVICE_IO_DIRECTION_IN_FLAG)) match = TRUE; + else if ((profile_item->direction == DM_DEVICE_DIRECTION_OUT) && (device_flags & DEVICE_IO_DIRECTION_OUT_FLAG)) match = TRUE; + else if ((profile_item->direction == DM_DEVICE_DIRECTION_BOTH) && (device_flags & DEVICE_IO_DIRECTION_BOTH_FLAG)) match = TRUE; + if (match) { + if (!need_to_check_for_state && !need_to_check_for_type) return TRUE; + } else { + return FALSE; + } + } + if (need_to_check_for_state) { + match = FALSE; + if ((COMPOUND_STATE(profile_item)== DM_DEVICE_STATE_DEACTIVATED) && (device_flags & DEVICE_STATE_DEACTIVATED_FLAG)) + match = TRUE; + else if ((COMPOUND_STATE(profile_item) == DM_DEVICE_STATE_ACTIVATED) && (device_flags & DEVICE_STATE_ACTIVATED_FLAG)) + match = TRUE; + if (match) { + if (!need_to_check_for_type) + return TRUE; + } else { + return FALSE; + } + } + if (need_to_check_for_type) { + struct device_type_info *type_info; + if (!(type_info = _device_manager_get_type_info(dm->type_infos, device_item->type, profile_item->profile))) { + pa_log_error("No type_info for %s.%s", device_item->type, profile_item->profile); + return FALSE; + } + if (type_info->builtin && (device_flags & DEVICE_TYPE_INTERNAL_FLAG)) + return TRUE; + else if (!type_info->builtin && (device_flags & DEVICE_TYPE_EXTERNAL_FLAG)) + return TRUE; + } + + return FALSE; +} + + +static int method_call_bt_sco(DBusConnection *conn, pa_bool_t onoff) { + DBusMessage *msg, *reply; + DBusError err; + const char *method; + + method = onoff ? "Play" : "Stop"; + if (!(msg = dbus_message_new_method_call(DBUS_SERVICE_HFP_AGENT, DBUS_OBJECT_HFP_AGENT, DBUS_INTERFACE_HFP_AGENT, method))) { + pa_log_error("dbus method call failed"); + return -1; + } + + dbus_error_init(&err); + if (!(reply = dbus_connection_send_with_reply_and_block(conn, msg, -1, &err))) { + pa_log_error("Failed to method call %s.%s, %s", DBUS_INTERFACE_HFP_AGENT, method, err.message); + dbus_error_free(&err); + return -1; + } + + dbus_message_unref(reply); + return 0; +} + +static int method_call_bt_sco_get_property(DBusConnection *conn, pa_bool_t *is_wide_band, pa_bool_t *nrec) { + DBusMessage *msg, *reply; + DBusMessageIter reply_iter, reply_iter_entry; + DBusError err; + unsigned int codec; + const char *property; + + pa_assert(conn); + + if (!is_wide_band && !nrec) { + return -1; + } + + if (!(msg = dbus_message_new_method_call(DBUS_SERVICE_HFP_AGENT, DBUS_OBJECT_HFP_AGENT, DBUS_INTERFACE_HFP_AGENT, "GetProperties"))) { + pa_log_error("dbus method call failed"); + return -1; + } + + dbus_error_init(&err); + if (!(reply = dbus_connection_send_with_reply_and_block(conn, msg, -1, &err))) { + pa_log_error("Failed to method call %s.%s, %s", DBUS_INTERFACE_HFP_AGENT, "GetProperties", err.message); + dbus_error_free(&err); + return -1; + } + + dbus_message_iter_init(reply, &reply_iter); + + if (dbus_message_iter_get_arg_type(&reply_iter) != DBUS_TYPE_ARRAY) { + pa_log_error("Cannot get reply argument"); + return -1; + } + + dbus_message_iter_recurse(&reply_iter, &reply_iter_entry); + + while (dbus_message_iter_get_arg_type(&reply_iter_entry) == DBUS_TYPE_DICT_ENTRY) { + DBusMessageIter dict_entry, dict_entry_val; + dbus_message_iter_recurse(&reply_iter_entry, &dict_entry); + dbus_message_iter_get_basic(&dict_entry, &property); + pa_log_debug("String received = %s", property); + if (property) { + if (pa_streq("codec", property) && is_wide_band) { + dbus_message_iter_next(&dict_entry); + dbus_message_iter_recurse(&dict_entry, &dict_entry_val); + if (dbus_message_iter_get_arg_type(&dict_entry_val) != DBUS_TYPE_UINT32) + continue; + dbus_message_iter_get_basic(&dict_entry_val, &codec); + pa_log_debug("Codec = [%d]", codec); + *is_wide_band= codec == BT_MSBC_CODEC_ID ? TRUE : FALSE; + } else if (pa_streq("nrec", property) && nrec) { + dbus_message_iter_next(&dict_entry); + dbus_message_iter_recurse(&dict_entry, &dict_entry_val); + if (dbus_message_iter_get_arg_type(&dict_entry_val) != DBUS_TYPE_BOOLEAN) + continue; + dbus_message_iter_get_basic(&dict_entry_val, nrec); + pa_log_debug("nrec= [%d]", *nrec); + } + } + dbus_message_iter_next(&reply_iter_entry); + } + + + dbus_message_unref(reply); + return 0; +} + + +static int method_call_bt_get_name(DBusConnection *conn, const char *device_path, char **name) { + const char *intf = DBUS_INTERFACE_BLUEZ_DEVICE, *prop = "Alias"; + DBusMessage *msg, *reply; + DBusMessageIter reply_iter, variant_iter; + DBusError err; + + pa_assert(conn); + pa_assert(device_path); + pa_assert(name); + + if (!(msg = dbus_message_new_method_call(DBUS_SERVICE_BLUEZ, device_path, "org.freedesktop.DBus.Properties", "Get"))) { + pa_log_error("dbus method call failed"); + return -1; + } + + dbus_message_append_args(msg, + DBUS_TYPE_STRING, &intf, + DBUS_TYPE_STRING, &prop, + DBUS_TYPE_INVALID); + + dbus_error_init(&err); + if (!(reply = dbus_connection_send_with_reply_and_block(conn, msg, -1, &err))) { + pa_log_error("Failed to method call %s.%s, %s", DBUS_INTERFACE_BLUEZ_DEVICE, "Get", err.message); + dbus_error_free(&err); + return -1; + } + + dbus_message_iter_init(reply, &reply_iter); + + if (dbus_message_iter_get_arg_type(&reply_iter) != DBUS_TYPE_VARIANT) { + pa_log_error("Cannot get reply argument"); + return -1; + } + + dbus_message_iter_recurse(&reply_iter, &variant_iter); + + if (dbus_message_iter_get_arg_type(&variant_iter) == DBUS_TYPE_STRING) { + dbus_message_iter_get_basic(&variant_iter, name); + } + + dbus_message_unref(reply); + return 0; +} + + +static void handle_get_connected_device_list(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_device_manager *dm; + DBusMessage *reply = NULL; + DBusMessageIter msg_iter, array_iter, device_iter; + dm_device *device_item; + dm_device_profile *profile_item; + dm_device_state_t compound_state; + uint32_t device_idx; + dbus_int32_t device_id; + int mask_flags; + + pa_assert(conn); + pa_assert(msg); + pa_assert(userdata); + + pa_log_debug("Get connected device list"); + + dm = (pa_device_manager*) userdata; + + pa_assert_se((reply = dbus_message_new_method_return(msg))); + + pa_assert_se(dbus_message_get_args(msg, NULL, + DBUS_TYPE_INT32, &mask_flags, + DBUS_TYPE_INVALID)); + + dbus_message_iter_init_append(reply, &msg_iter); + pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "(isiis)", &array_iter)); + + PA_IDXSET_FOREACH(device_item, dm->device_list, device_idx) { + if(!(profile_item = pa_idxset_get_by_index(device_item->profiles, device_item->active_profile))) { + pa_log_error("no active profile"); + continue; + } + compound_state = COMPOUND_STATE(profile_item); + if (device_item_match_for_mask(device_item, mask_flags, dm)) { + device_id = (dbus_int32_t)device_item->id; + pa_assert_se(dbus_message_iter_open_container(&array_iter, DBUS_TYPE_STRUCT, NULL, &device_iter)); + dbus_message_iter_append_basic(&device_iter, DBUS_TYPE_INT32, &device_id); + dbus_message_iter_append_basic(&device_iter, DBUS_TYPE_STRING, &profile_item->device_item->type); + dbus_message_iter_append_basic(&device_iter, DBUS_TYPE_INT32, &profile_item->direction); + dbus_message_iter_append_basic(&device_iter, DBUS_TYPE_INT32, &compound_state); + dbus_message_iter_append_basic(&device_iter, DBUS_TYPE_STRING, &device_item->name); + pa_assert_se(dbus_message_iter_close_container(&array_iter, &device_iter)); + } + } + + pa_assert_se(dbus_message_iter_close_container(&msg_iter, &array_iter)); + + pa_assert_se(dbus_connection_send(conn, reply, NULL)); + dbus_message_unref(reply); +} + + +static void handle_get_bt_a2dp_status(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_device_manager *dm; + DBusMessage *reply = NULL; + dm_device *device_item; + dm_device_profile *profile_item; + dbus_bool_t is_bt_on = FALSE; + char *bt_name = "none"; + + pa_assert(conn); + pa_assert(msg); + pa_assert(userdata); + + pa_log_debug("Get bt a2dp list"); + + dm = (pa_device_manager*) userdata; + + pa_assert_se((reply = dbus_message_new_method_return(msg))); + + if (!(device_item = _device_manager_get_device(dm->device_list, DEVICE_TYPE_BT))) { + if (!(profile_item = _device_item_get_profile(device_item, DEVICE_PROFILE_BT_A2DP))) { + is_bt_on = TRUE; + bt_name = device_item->name; + } + } + + pa_assert_se(dbus_message_append_args(reply, DBUS_TYPE_BOOLEAN, &is_bt_on, + DBUS_TYPE_STRING, &bt_name, + DBUS_TYPE_INVALID)); + + pa_assert_se(dbus_connection_send(conn, reply, NULL)); + dbus_message_unref(reply); +} + + + +static void handle_load_sink(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_device_manager *dm; + char *device_type, *device_profile, *role; + DBusMessage *reply = NULL; + + pa_assert_se((reply = dbus_message_new_method_return(msg))); + dm = (pa_device_manager *) userdata; + pa_assert_se(dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &device_type, + DBUS_TYPE_STRING, &device_profile, + DBUS_TYPE_STRING, &role, + DBUS_TYPE_INVALID)); + + if (pa_streq(device_profile, "none")) + device_profile = NULL; + pa_device_manager_load_sink(device_type, device_profile, role, dm); + pa_assert_se(dbus_connection_send(conn, reply, NULL)); + dbus_message_unref(reply); +} + +static void handle_test_device_status_change(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_device_manager *dm = (pa_device_manager *)userdata; + char *device_type, *device_profile; + dbus_int32_t status; + DBusMessage *reply = NULL; + DBusError error; + + pa_assert_se((reply = dbus_message_new_method_return(msg))); + + dbus_error_init(&error); + if(!dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &device_type, + DBUS_TYPE_STRING, &device_profile, + DBUS_TYPE_INT32, &status, + DBUS_TYPE_INVALID)) { + pa_log_error("failed to get dbus args : %s", error.message); + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "%s", error.message); + dbus_error_free(&error); + } + + pa_log_debug("handle_test_device_status_change, type:%s, profile:%s, status:%d", device_type, device_profile, status); + if (pa_streq(device_profile, "none")) + device_profile = NULL; + + handle_device_status_changed(dm, device_type, device_profile, NULL, NULL, status); + pa_assert_se(dbus_connection_send(conn, reply, NULL)); + dbus_message_unref(reply); +} + +static DBusHandlerResult handle_introspect(DBusConnection *conn, DBusMessage *msg, void *userdata) { + const char *xml = DEVICE_MANAGER_INTROSPECT_XML; + DBusMessage *r = NULL; + + pa_assert(conn); + pa_assert(msg); + pa_assert(userdata); + + pa_assert_se(r = dbus_message_new_method_return(msg)); + pa_assert_se(dbus_message_append_args(r, DBUS_TYPE_STRING, &xml, DBUS_TYPE_INVALID)); + + if (r) { + pa_assert_se(dbus_connection_send((conn), r, NULL)); + dbus_message_unref(r); + } + + return DBUS_HANDLER_RESULT_HANDLED; +} + +static DBusHandlerResult handle_device_manager_methods(DBusConnection *conn, DBusMessage *msg, void *userdata) { + int method_idx = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(userdata); + + for (method_idx = 0; method_idx < METHOD_HANDLER_MAX; method_idx++) { + if (dbus_message_is_method_call(msg, DBUS_INTERFACE_DEVICE_MANAGER, method_handlers[method_idx].method_name )) { + method_handlers[method_idx].receive_cb(conn, msg, userdata); + return DBUS_HANDLER_RESULT_HANDLED; + } + } + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +static DBusHandlerResult method_call_handler(DBusConnection *c, DBusMessage *m, void *userdata) { + struct userdata *u = userdata; + const char *path, *interface, *member; + + pa_assert(c); + pa_assert(m); + pa_assert(u); + + path = dbus_message_get_path(m); + interface = dbus_message_get_interface(m); + member = dbus_message_get_member(m); + + pa_log_debug("DeviceManager Method Call Handler : path=%s, interface=%s, member=%s", path, interface, member); + + if (!pa_streq(path, DBUS_OBJECT_DEVICE_MANAGER)) + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) { + return handle_introspect(c, m, u); + /* + } else if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Properties", "Get")) { + return handle_get_property(c, m, u); + } else if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Properties", "Set")) { + return handle_set_property(c, m, u); + } else if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Properties", "GetAll")) { + return handle_get_all_property(c, m, u); + */ + } else { + return handle_device_manager_methods(c, m, u); + } + + return DBUS_HANDLER_RESULT_HANDLED; +} + +static void endpoint_init(pa_device_manager *dm) { + static const DBusObjectPathVTable vtable_endpoint = { + .message_function = method_call_handler, + }; + + pa_log_debug("Device manager dbus endpoint init"); + + if (dm && dm->dbus_conn) { + if (!dbus_connection_register_object_path(pa_dbus_connection_get(dm->dbus_conn), DBUS_OBJECT_DEVICE_MANAGER, &vtable_endpoint, dm)) + pa_log_error("Failed to register object path"); + } else { + pa_log_error("Cannot get dbus connection to register object path"); + } +} + +static void endpoint_done(pa_device_manager *dm) { + pa_log_debug("Device manager dbus endpoint done"); + if (dm && dm->dbus_conn) { + if (!dbus_connection_unregister_object_path(pa_dbus_connection_get(dm->dbus_conn), DBUS_OBJECT_DEVICE_MANAGER)) + pa_log_error("Failed to unregister object path"); + } else { + pa_log_error("Cannot get dbus connection to unregister object path"); + } +} + +static void dbus_init(pa_device_manager *dm) { + DBusError error; + pa_dbus_connection *connection = NULL; + + pa_assert(dm); + pa_log_debug("Dbus init"); + dbus_error_init(&error); + + if (!(connection = pa_dbus_bus_get(dm->core, DBUS_BUS_SYSTEM, &error)) || dbus_error_is_set(&error)) { + if (connection) { + pa_dbus_connection_unref(connection); + } + pa_log_error("Unable to contact D-Bus system bus: %s: %s", error.name, error.message); + goto fail; + } else { + pa_log_debug("Got dbus connection"); + } + + dm->dbus_conn = connection; + + if (watch_signals(dm) < 0) + pa_log_error("dbus watch signals failed"); + else + pa_log_debug("dbus ready to get signals"); + + endpoint_init(dm); + +fail: + dbus_error_free(&error); +} + +static void dbus_deinit(pa_device_manager *dm) { + pa_assert(dm); + + pa_log_debug("Dbus deinit"); + + endpoint_done(dm); + unwatch_signals(dm); + + if (dm->dbus_conn) { + pa_dbus_connection_unref(dm->dbus_conn); + dm->dbus_conn = NULL; + } +} +#endif + +pa_idxset* pa_device_manager_get_device_list(pa_device_manager *dm) { + pa_assert(dm); + pa_assert(dm->device_list); + + return dm->device_list; +} + +dm_device* pa_device_manager_get_device(pa_device_manager *dm, const char *device_type) { + pa_assert(dm); + + return _device_manager_get_device(dm->device_list, device_type); +} + +dm_device* pa_device_manager_get_device_by_id(pa_device_manager *dm, uint32_t id) { + pa_assert(dm); + + return _device_manager_get_device_with_id(dm->device_list, id); +} + +pa_sink* pa_device_manager_get_sink(dm_device *device_item, const char *role) { + dm_device_profile *profile_item; + + pa_assert(device_item); + pa_assert(profile_item = _device_item_get_active_profile(device_item)); + + if (!profile_item->playback_devices) { + pa_log_warn("No playback device in %s", device_item->name); + return NULL; + } + + return pa_hashmap_get(profile_item->playback_devices, role); +} + +pa_source* pa_device_manager_get_source(dm_device *device_item, const char *role) { + dm_device_profile *profile_item; + + pa_assert(device_item); + pa_assert(profile_item = _device_item_get_active_profile(device_item)); + + if (!profile_item->capture_devices) { + pa_log_warn("No capture device in %s", device_item->name); + return NULL; + } + + return pa_hashmap_get(profile_item->capture_devices, role); +} + +void pa_device_manager_set_device_state(dm_device *device_item, dm_device_direction_t direction, dm_device_state_t state) { + dm_device_profile *profile_item; + + pa_assert(device_item); + pa_assert(profile_item = _device_item_get_active_profile(device_item)); + + pa_log_debug("pa_device_manager_set_device_state : %s.%s direction %s -> %d", device_item->type, profile_item->profile, device_direction_to_string(direction), state); + _device_profile_set_state(profile_item, direction, state); +} + +dm_device_state_t pa_device_manager_get_device_state(dm_device *device_item, dm_device_direction_t direction) { + dm_device_profile *profile_item; + + pa_assert(device_item); + pa_assert(profile_item = _device_item_get_active_profile(device_item)); + + if (direction == DM_DEVICE_DIRECTION_BOTH) + return COMPOUND_STATE(profile_item); + else if (direction == DM_DEVICE_DIRECTION_OUT) + return profile_item->playback_state; + else if (direction == DM_DEVICE_DIRECTION_IN) + return profile_item->capture_state; + else + return DM_DEVICE_STATE_DEACTIVATED; +} + +uint32_t pa_device_manager_get_device_id(dm_device *device_item) { + pa_assert(device_item); + + return device_item->id; +} + +const char* pa_device_manager_get_device_type(dm_device *device_item) { + pa_assert(device_item); + + return device_item->type; +} + +const char* pa_device_manager_get_device_subtype(dm_device *device_item) { + dm_device_profile *profile_item; + + pa_assert(device_item); + pa_assert(profile_item = _device_item_get_active_profile(device_item)); + + return profile_item->profile; +} + +dm_device_direction_t pa_device_manager_get_device_direction(dm_device *device_item) { + dm_device_profile *profile_item; + + pa_assert(device_item); + pa_assert(profile_item = _device_item_get_active_profile(device_item)); + + return profile_item->direction; +} + +int pa_device_manager_bt_sco_open(pa_device_manager *dm) { + struct device_status_info *status_info; + + pa_assert(dm); + pa_assert(dm->dbus_conn); + + if (!(status_info = _device_manager_get_status_info(dm->device_status, DEVICE_TYPE_BT, DEVICE_PROFILE_BT_SCO, NULL))) { + pa_log_error("No status info for bt-sco"); + return -1; + } + if (!status_info->detected) { + pa_log_error("bt-sco not detected"); + return -1; + } + + pa_log_debug("bt sco open start"); + if (method_call_bt_sco(pa_dbus_connection_get(dm->dbus_conn), TRUE) < 0) { + pa_log_error("Failed to bt sco on"); + return -1; + } + + pa_log_debug("bt sco open end"); + + return 0; +} + +int pa_device_manager_bt_sco_close(pa_device_manager *dm) { + struct device_status_info *status_info; + + pa_assert(dm); + pa_assert(dm->dbus_conn); + + if (!(status_info = _device_manager_get_status_info(dm->device_status, DEVICE_TYPE_BT, DEVICE_PROFILE_BT_SCO, NULL))) { + pa_log_error("No status info for bt-sco"); + return -1; + } + if (!status_info->detected) { + pa_log_error("bt-sco not detected"); + return -1; + } + + pa_log_debug("bt sco close start"); + if (method_call_bt_sco(pa_dbus_connection_get(dm->dbus_conn), FALSE) < 0) { + pa_log_error("Failed to bt sco close"); + return -1; + } + pa_log_debug("bt sco close end"); + + return 0; +} + +int pa_device_manager_bt_sco_get_property(pa_device_manager *dm, pa_bool_t *is_wide_band, pa_bool_t *nrec) { + pa_assert(dm); + pa_assert(dm->dbus_conn); + + pa_log_debug("bt sco get property start"); + + if (method_call_bt_sco_get_property(pa_dbus_connection_get(dm->dbus_conn), is_wide_band, nrec) < 0) { + pa_log_error("Failed to get bt property"); + return -1; + } + + pa_log_debug("bt sco get property end"); + + return 0; +} + +int pa_device_manager_load_sink(const char *device_type, const char *device_profile, const char *role, pa_device_manager *dm) { + const char *device_string, *params; + struct device_type_info *type_info; + struct device_file_info *file_info; + dm_device_profile *profile_item; + dm_device *device_item; + pa_sink *sink; + uint32_t device_idx; + + pa_assert(dm); + pa_assert(dm->device_list); + + pa_log_debug("load sink for '%s,%s'", device_type, role); + PA_IDXSET_FOREACH(device_item, dm->device_list, device_idx) { + if (pa_streq(device_type, device_item->type)) { + if ((profile_item = _device_item_get_profile(device_item, device_profile))) { + if (pa_hashmap_get(profile_item->playback_devices, role)) { + pa_log_warn("Proper sink for '%s:%s' already loaded", device_type, role); + return -1; + } + } + } + } + + if (!(type_info = _device_manager_get_type_info(dm->type_infos, device_type, device_profile))) { + pa_log_error("No type map for %s", device_type); + return -1; + } + + if (!(device_string = pa_hashmap_get(type_info->playback_devices, role))) { + pa_log_error("No device-string for '%s:%s'", device_type, role); + goto failed; + } + + if (!(file_info = _device_manager_get_file_info(dm->file_map->playback, device_string))) { + pa_log_error("No playback file-map for '%s'", device_string); + goto failed; + } + + if (!(params = pa_hashmap_get(file_info->roles, role))) { + pa_log_error("No params for '%s,%s'", device_string, role); + goto failed; + } + + if ((sink = load_device(dm->core, PA_DEVICE_TYPE_SINK, device_string, params))) { + pa_log_debug("loaded sink '%s' for '%s,%s' success", sink->name, device_type, role); + } else { + pa_log_warn("Cannot load playback device with '%s,%s'", device_string, params); + goto failed; + } + + return 0; + +failed: + return -1; +} + +int pa_device_manager_load_source(const char *device_type, const char *device_profile, const char *role, pa_device_manager *dm) { + const char *device_string, *params; + struct device_type_info *type_info; + struct device_file_info *file_info; + dm_device_profile *profile_item; + dm_device *device_item; + pa_source *source; + uint32_t device_idx; + + pa_assert(dm); + + pa_log_debug("load source for '%s,%s'", device_type, role); + + PA_IDXSET_FOREACH(device_item, dm->device_list, device_idx) { + if (pa_streq(device_type, device_item->type)) { + if ((profile_item = _device_item_get_profile(device_item, device_profile))) { + if (pa_hashmap_get(profile_item->capture_devices, role)) { + pa_log_warn("Proper source for '%s:%s' already loaded", device_type, role); + return -1; + } + } + } + } + + + if (!(type_info = _device_manager_get_type_info(dm->type_infos, device_type, device_profile))) { + pa_log_error("No type map for %s", device_type); + return -1; + } + + if (!(device_string = pa_hashmap_get(type_info->capture_devices, role))) { + pa_log_error("No device-string for '%s:%s'", device_type, role); + goto failed; + } + + if (!(file_info = _device_manager_get_file_info(dm->file_map->capture, device_string))) { + pa_log_error("No capture file-map for '%s'", device_string); + goto failed; + } + + if (!(params = pa_hashmap_get(file_info->roles, role))) { + pa_log_error("No params for '%s,%s'", device_string, role); + goto failed; + } + + if ((source = load_device(dm->core, PA_DEVICE_TYPE_SOURCE, device_string, params))) { + pa_log_debug("loaded source '%s' for '%s,%s' success", source->name, device_type, role); + } else { + pa_log_warn("Cannot load capture device with '%s,%s'", device_string, params); + goto failed; + } + + return 0; + +failed: + return -1; +} + +pa_device_manager* pa_device_manager_init(pa_core *c) { + pa_device_manager *dm; + + pa_log_debug("pa_device_manager_init start"); + + dm = pa_xnew0(pa_device_manager, 1); + dm->core = c; + + dbus_init(dm); + + dm->sink_put_hook_slot = pa_hook_connect(&dm->core->hooks[PA_CORE_HOOK_SINK_PUT], PA_HOOK_LATE+10, (pa_hook_cb_t) sink_put_hook_callback, dm); + dm->sink_unlink_hook_slot = pa_hook_connect(&dm->core->hooks[PA_CORE_HOOK_SINK_UNLINK], PA_HOOK_EARLY, (pa_hook_cb_t) sink_unlink_hook_callback, dm); + dm->source_put_hook_slot = pa_hook_connect(&dm->core->hooks[PA_CORE_HOOK_SOURCE_PUT], PA_HOOK_LATE+10, (pa_hook_cb_t) source_put_hook_callback, dm); + dm->source_unlink_hook_slot = pa_hook_connect(&dm->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK], PA_HOOK_EARLY, (pa_hook_cb_t) source_unlink_hook_callback, dm); + + dm->comm = pa_communicator_get(dm->core); + if (!(dm->type_infos = parse_device_type_infos())) { + pa_log_error("Parse device-type-map failed"); + return NULL; + } + + if (!(dm->file_map = parse_device_file_map())) { + pa_log_error("Parse device-file-map failed"); + return NULL; + } + + if (device_list_init(dm) < 0) { + pa_log_error("Init device list failed"); + return NULL; + } + + if (!(dm->device_status = device_type_status_init(dm->type_infos))) { + pa_log_error("Init device status failed"); + return NULL; + } + + if (load_builtin_devices(dm) != 0) { + pa_log_error("Load Builtin Devices faled"); + return NULL; + } + + /* Just for convenience when test*/ + if (!_device_manager_set_default_sink(dm, DEVICE_TYPE_SPEAKER, NULL, "normal")) { + pa_log_warn("Set default sink with speaker(normal) failed"); + } + if (!_device_manager_set_default_source(dm, DEVICE_TYPE_MIC, NULL, "normal")) { + pa_log_warn("Set default source with mic(normal) failed"); + } + + pa_log_debug("pa_device_manager_init end"); + + return dm; +} + +void pa_device_manager_done(pa_device_manager *dm) { + + if (!dm) + return; + + pa_log_debug("pa_device_manager_done start"); + + if (dm->sink_put_hook_slot) + pa_hook_slot_free(dm->sink_put_hook_slot); + if (dm->sink_unlink_hook_slot) + pa_hook_slot_free(dm->sink_unlink_hook_slot); + if (dm->source_put_hook_slot) + pa_hook_slot_free(dm->source_put_hook_slot); + if (dm->source_unlink_hook_slot) + pa_hook_slot_free(dm->source_unlink_hook_slot); + + if (dm->comm) + pa_communicator_unref(dm->comm); + + if (dm->type_infos) + pa_idxset_free(dm->type_infos, (pa_free_cb_t)type_info_free_func); + if (dm->file_map) { + if (dm->file_map->playback) + pa_idxset_free(dm->file_map->playback, (pa_free_cb_t)file_info_free_func); + if (dm->file_map->capture) + pa_idxset_free(dm->file_map->capture, (pa_free_cb_t)file_info_free_func); + pa_xfree(dm->file_map); + } + if (dm->device_list) + pa_idxset_free(dm->device_list, (pa_free_cb_t)device_item_free_func); + if (dm->device_status) + pa_idxset_free(dm->device_status, NULL); + + dbus_deinit(dm); + + pa_log_debug("pa_device_manager_done end"); +} diff --git a/src/device-manager.h b/src/device-manager.h new file mode 100644 index 0000000..a62d701 --- /dev/null +++ b/src/device-manager.h @@ -0,0 +1,75 @@ + +#include + +#define DEVICE_TYPE_SPEAKER "builtin-speaker" +#define DEVICE_TYPE_RECEIVER "builtin-receiver" +#define DEVICE_TYPE_MIC "builtin-mic" +#define DEVICE_TYPE_AUDIO_JACK "audio-jack" +#define DEVICE_TYPE_BT "bt" +#define DEVICE_TYPE_HDMI "hdmi" +#define DEVICE_TYPE_FORWARDING "forwarding" +#define DEVICE_TYPE_USB_AUDIO "usb-audio" +#define DEVICE_TYPE_NONE "none" + +#define DEVICE_PROFILE_BT_SCO "sco" +#define DEVICE_PROFILE_BT_A2DP "a2dp" + +#define DEVICE_ROLE_NORMAL "normal" +#define DEVICE_ROLE_VOIP "voip" +#define DEVICE_ROLE_LOW_LATENCY "low-latency" +#define DEVICE_ROLE_HIGH_LATENCY "high-latency" +#define DEVICE_ROLE_UHQA "uhqa" + +typedef enum dm_device_direction_type { + DM_DEVICE_DIRECTION_NONE, + DM_DEVICE_DIRECTION_IN = 0x1, + DM_DEVICE_DIRECTION_OUT = 0x2, + DM_DEVICE_DIRECTION_BOTH = DM_DEVICE_DIRECTION_IN | DM_DEVICE_DIRECTION_OUT +} dm_device_direction_t; + +typedef enum dm_device_changed_into_type { + DM_DEVICE_CHANGED_INFO_STATE, + DM_DEVICE_CHANGED_INFO_IO_DIRECTION, + DM_DEVICE_CHANGED_INFO_SUBTYPE, +} dm_device_changed_info_t; + +typedef enum dm_device_state_type { + DM_DEVICE_STATE_DEACTIVATED = 0, + DM_DEVICE_STATE_ACTIVATED +} dm_device_state_t; + +typedef struct pa_device_manager pa_device_manager; +typedef struct dm_device dm_device; + +typedef struct _hook_call_data_for_conn_changed { + pa_bool_t is_connected; + dm_device *device; +} pa_device_manager_hook_data_for_conn_changed; + +typedef struct _hook_call_data_for_info_changed { + dm_device_changed_info_t changed_info; + dm_device *device; +} pa_device_manager_hook_data_for_info_changed; + +pa_device_manager* pa_device_manager_init(pa_core* core); +void pa_device_manager_done(pa_device_manager *dm); + +pa_idxset* pa_device_manager_get_device_list(pa_device_manager *dm); +dm_device* pa_device_manager_get_device(pa_device_manager *dm, const char *device_type); +dm_device* pa_device_manager_get_device_by_id(pa_device_manager *dm, uint32_t id); + +pa_sink* pa_device_manager_get_sink(dm_device *device, const char *role); +pa_source* pa_device_manager_get_source(dm_device *device, const char *role); +void pa_device_manager_set_device_state(dm_device *device, dm_device_direction_t direction, dm_device_state_t state); +dm_device_state_t pa_device_manager_get_device_state(dm_device *device_item, dm_device_direction_t direction); +uint32_t pa_device_manager_get_device_id(dm_device *device); +const char* pa_device_manager_get_device_type(dm_device *device); +const char* pa_device_manager_get_device_subtype(dm_device *device); +dm_device_direction_t pa_device_manager_get_device_direction(dm_device *device); + +int pa_device_manager_load_sink(const char *device_type, const char *device_profile, const char *role, pa_device_manager *dm); +int pa_device_manager_load_source(const char *device_type, const char *device_profile, const char *role, pa_device_manager *dm); + +int pa_device_manager_bt_sco_open(pa_device_manager *dm); +int pa_device_manager_bt_sco_close(pa_device_manager *dm); +int pa_device_manager_bt_sco_get_property(pa_device_manager *dm, pa_bool_t *is_wide_band, pa_bool_t *nrec); diff --git a/src/hal-manager.c b/src/hal-manager.c new file mode 100644 index 0000000..25ec6c4 --- /dev/null +++ b/src/hal-manager.c @@ -0,0 +1,312 @@ +/*** + This file is part of PulseAudio. + + Copyright 2015 Sangchul Lee + + PulseAudio 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. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "hal-manager.h" +#include + +/* Audio HAL library */ +#define LIB_TIZEN_AUDIO "libtizen-audio.so" + +pa_hal_manager* pa_hal_manager_get(pa_core *core, void *user_data) { + pa_hal_manager *h; + + pa_assert(core); + + if ((h = pa_shared_get(core, "hal-manager"))) + return pa_hal_manager_ref(h); + + h = pa_xnew0(pa_hal_manager, 1); + PA_REFCNT_INIT(h); + h->core = core; + + /* Load library & init HAL manager */ + h->dl_handle = dlopen(LIB_TIZEN_AUDIO, RTLD_NOW); + if (h->dl_handle) { + h->intf.init = dlsym(h->dl_handle, "audio_init"); + h->intf.deinit = dlsym(h->dl_handle, "audio_deinit"); + h->intf.reset_volume = dlsym(h->dl_handle, "audio_reset_volume"); + h->intf.get_volume_level_max = dlsym(h->dl_handle, "audio_get_volume_level_max"); + h->intf.get_volume_level = dlsym(h->dl_handle, "audio_get_volume_level"); + h->intf.set_volume_level = dlsym(h->dl_handle, "audio_set_volume_level"); + h->intf.get_volume_value = dlsym(h->dl_handle, "audio_get_volume_value"); + h->intf.get_volume_mute = dlsym(h->dl_handle, "audio_get_volume_mute"); + h->intf.set_volume_mute = dlsym(h->dl_handle, "audio_set_volume_mute"); + h->intf.alsa_pcm_open = dlsym(h->dl_handle, "audio_alsa_pcm_open"); + h->intf.alsa_pcm_close = dlsym(h->dl_handle, "audio_alsa_pcm_close"); + h->intf.pcm_open = dlsym(h->dl_handle, "audio_pcm_open"); + h->intf.pcm_close = dlsym(h->dl_handle, "audio_pcm_close"); + h->intf.pcm_avail = dlsym(h->dl_handle, "audio_pcm_avail"); + h->intf.pcm_write = dlsym(h->dl_handle, "audio_pcm_write"); + h->intf.do_route = dlsym(h->dl_handle, "audio_do_route"); + h->intf.update_route_option = dlsym(h->dl_handle, "audio_update_route_option"); + h->intf.update_stream_connection_info = dlsym(h->dl_handle, "audio_update_stream_connection_info"); + h->intf.get_buffer_attr = dlsym(h->dl_handle, "audio_get_buffer_attr"); + if (h->intf.init) { + /* TODO : no need to pass platform_data as second param. need to fix hal. */ + if (h->intf.init(&h->data, user_data) != AUDIO_RET_OK) { + pa_log_error("hal_manager init failed"); + } + } +#if 1 /* remove comment after enable NEW_HAL */ + pa_shared_set(core, "tizen-audio-data", h->data); + pa_shared_set(core, "tizen-audio-interface", &h->intf); +#endif + + } else { + pa_log_error("open hal_manager failed :%s", dlerror()); + return NULL; + } + + pa_shared_set(core, "hal-manager", h); + + return h; +} + +pa_hal_manager* pa_hal_manager_ref(pa_hal_manager *h) { + pa_assert(h); + pa_assert(PA_REFCNT_VALUE(h) > 0); + + PA_REFCNT_INC(h); + + return h; +} + +void pa_hal_manager_unref(pa_hal_manager *h) { + pa_assert(h); + pa_assert(PA_REFCNT_VALUE(h) > 0); + + if (PA_REFCNT_DEC(h) > 0) + return; + + /* Deinit HAL manager & unload library */ + if (h->intf.deinit) { + if (h->intf.deinit(&h->data) != AUDIO_RET_OK) { + pa_log_error("hal_manager deinit failed"); + } + } + if (h->dl_handle) { + dlclose(h->dl_handle); + } + + if (h->core) + pa_shared_remove(h->core, "hal-manager"); + + pa_xfree(h); +} + +int32_t pa_hal_manager_get_buffer_attribute(pa_hal_manager *h, io_direction_t direction, const char *latency, void *new_data, uint32_t *maxlength, uint32_t *tlength, uint32_t *prebuf, uint32_t* minreq, uint32_t *fragsize) { + int32_t ret = 0; + audio_return_t hal_ret = AUDIO_RET_OK; + pa_sample_spec *sample_spec = NULL; + + pa_assert(h); + pa_assert(new_data); + + sample_spec = (direction==DIRECTION_OUT)?(&((pa_sink_input_new_data*)new_data)->sample_spec):(&((pa_source_output_new_data*)new_data)->sample_spec); + pa_log_info("latency:%s, rate:%u, format:%d, channels:%u", latency, sample_spec->rate, sample_spec->format, sample_spec->channels); + + if (AUDIO_IS_ERROR(hal_ret = h->intf.get_buffer_attr(h->data, direction, latency, sample_spec->rate, sample_spec->format, sample_spec->channels, maxlength, tlength, prebuf, minreq, fragsize))) { + pa_log_error("get_buffer_attr returns error:0x%x", hal_ret); + ret = -1; + } else + pa_log_info("maxlength:%d, tlength:%d, prebuf:%d, minreq:%d, fragsize:%d", *maxlength, *tlength, *prebuf, *minreq, *fragsize); + + return ret; +} + +int32_t pa_hal_manager_reset_volume (pa_hal_manager *h) { + int32_t ret = 0; + audio_return_t hal_ret = AUDIO_RET_OK; + + pa_assert(h); + + if (AUDIO_IS_ERROR(hal_ret = h->intf.reset_volume(h->data))) { + pa_log_error("reset volume returns error:0x%x", hal_ret); + ret = -1; + } + return ret; +} + +int32_t pa_hal_manager_get_volume_level_max (pa_hal_manager *h, const char *volume_type, io_direction_t direction, uint32_t *level) { + int32_t ret = 0; + audio_return_t hal_ret = AUDIO_RET_OK; + audio_volume_info_t info = {NULL, NULL, 0}; + + pa_assert(h); + pa_assert(volume_type); + pa_assert(level); + + info.type = volume_type; + info.direction = direction; + + if (AUDIO_IS_ERROR((hal_ret = h->intf.get_volume_level_max(h->data, &info, level)))) { + pa_log_error("get_volume_level_max returns error:0x%x", hal_ret); + ret = -1; + } + return ret; +} + +int32_t pa_hal_manager_get_volume_level (pa_hal_manager *h, const char *volume_type, io_direction_t direction, uint32_t *level) { + int32_t ret = 0; + audio_return_t hal_ret = AUDIO_RET_OK; + audio_volume_info_t info = {NULL, NULL, 0}; + + pa_assert(h); + pa_assert(volume_type); + pa_assert(level); + + info.type = volume_type; + info.direction = direction; + + if (AUDIO_IS_ERROR((hal_ret = h->intf.get_volume_level(h->data, &info, level)))) { + pa_log_error("get_volume_level returns error:0x%x", hal_ret); + ret = -1; + } + return ret; +} + +int32_t pa_hal_manager_set_volume_level (pa_hal_manager *h, const char *volume_type, io_direction_t direction, uint32_t level) { + int32_t ret = 0; + audio_return_t hal_ret = AUDIO_RET_OK; + audio_volume_info_t info = {NULL, NULL, 0}; + + pa_assert(h); + pa_assert(volume_type); + + info.type = volume_type; + info.direction = direction; + + if (AUDIO_IS_ERROR((hal_ret = h->intf.set_volume_level(h->data, &info, level)))) { + pa_log_error("set_volume_level returns error:0x%x", hal_ret); + ret = -1; + } + + return ret; +} + +int32_t pa_hal_manager_get_volume_value (pa_hal_manager *h, const char *volume_type, const char *gain_type, io_direction_t direction, uint32_t level, double *value) { + int32_t ret = 0; + audio_return_t hal_ret = AUDIO_RET_OK; + audio_volume_info_t info = {NULL, NULL, 0}; + + pa_assert(h); + pa_assert(volume_type); + pa_assert(value); + + info.type = volume_type; + info.gain = gain_type; + info.direction = direction; + + if (AUDIO_IS_ERROR((hal_ret = h->intf.get_volume_value(h->data, &info, level, value)))) { + pa_log_error("get_volume_value returns error:0x%x", hal_ret); + ret = -1; + } + + return ret; +} + +int32_t pa_hal_manager_get_mute (pa_hal_manager *h, const char *volume_type, io_direction_t direction, uint32_t *mute) { + int32_t ret = 0; + audio_return_t hal_ret = AUDIO_RET_OK; + audio_volume_info_t info = {NULL, NULL, 0}; + + pa_assert(h); + pa_assert(volume_type); + pa_assert(mute); + + info.type = volume_type; + info.direction = direction; + + if (AUDIO_IS_ERROR(hal_ret = h->intf.get_volume_mute(h->data, &info, mute))) { + pa_log_error("get_mute returns error:0x%x", hal_ret); + ret = -1; + } + return ret; +} + +int32_t pa_hal_manager_set_mute (pa_hal_manager *h, const char *volume_type, io_direction_t direction, uint32_t mute) { + int32_t ret = 0; + audio_return_t hal_ret = AUDIO_RET_OK; + audio_volume_info_t info = {NULL, NULL, 0}; + + pa_assert(h); + pa_assert(volume_type); + + info.type = volume_type; + info.direction = direction; + + if (AUDIO_IS_ERROR(hal_ret = h->intf.set_volume_mute(h->data, &info, mute))) { + pa_log_error("set_mute returns error:0x%x", hal_ret); + ret = -1; + } + return ret; +} + +int32_t pa_hal_manager_do_route (pa_hal_manager *h, hal_route_info *info) { + int32_t ret = 0; + audio_return_t hal_ret = AUDIO_RET_OK; + + pa_assert(h); + pa_assert(info); + + if (AUDIO_IS_ERROR(hal_ret = h->intf.do_route(h->data, (audio_route_info_t*)info))) { + pa_log_error("do_route returns error:0x%x", hal_ret); + ret = -1; + } + return ret; +} + +int32_t pa_hal_manager_update_route_option (pa_hal_manager *h, hal_route_option *option) { + int32_t ret = 0; + audio_return_t hal_ret = AUDIO_RET_OK; + + pa_assert(h); + pa_assert(option); + + if (AUDIO_IS_ERROR(hal_ret = h->intf.update_route_option(h->data, (audio_route_option_t*)option))) { + pa_log_error("update_route_option returns error:0x%x", hal_ret); + ret = -1; + } + return ret; +} + +int32_t pa_hal_manager_update_stream_connection_info (pa_hal_manager *h, hal_stream_connection_info *info) { + int32_t ret = 0; + audio_return_t hal_ret = AUDIO_RET_OK; + audio_stream_info_t hal_info; + + pa_assert(h); + pa_assert(info); + + hal_info.role = info->role; + hal_info.direction = info->direction; + hal_info.idx = info->idx; + + if (AUDIO_IS_ERROR(hal_ret = h->intf.update_stream_connection_info(h->data, &hal_info, (uint32_t)info->is_connected))) { + pa_log_error("update_stream_connection_info returns error:0x%x", hal_ret); + ret = -1; + } + return ret; +} diff --git a/src/hal-manager.h b/src/hal-manager.h new file mode 100644 index 0000000..d92c267 --- /dev/null +++ b/src/hal-manager.h @@ -0,0 +1,64 @@ +#ifndef foohalmanagerfoo +#define foohalmanagerfoo +#include +#include + +#include "tizen-audio.h" +/* TODO : move below structure to hal-manager.c */ +struct _pa_hal_manager { + PA_REFCNT_DECLARE; + + pa_core *core; + void *dl_handle; + void *data; + audio_interface_t intf; +}; + +typedef struct _pa_hal_manager pa_hal_manager; + +typedef enum _io_direction { + DIRECTION_IN, + DIRECTION_OUT, +} io_direction_t; + +typedef struct _hal_device_info { + const char *type; + uint32_t direction; + uint32_t id; +} hal_device_info; + +typedef struct _hal_route_info { + const char *role; + hal_device_info *device_infos; + uint32_t num_of_devices; +} hal_route_info; + +typedef struct _hal_route_option { + const char *role; + const char *name; + int32_t value; +} hal_route_option; + +typedef struct _hal_stream_connection_info { + const char *role; + uint32_t direction; + uint32_t idx; + pa_bool_t is_connected; +} hal_stream_connection_info; + +pa_hal_manager* pa_hal_manager_get(pa_core *core, void *user_data); +pa_hal_manager* pa_hal_manager_ref(pa_hal_manager *h); +void pa_hal_manager_unref(pa_hal_manager *h); +int32_t pa_hal_manager_get_buffer_attribute(pa_hal_manager *h, io_direction_t direction, const char *latency, void *new_data, uint32_t *maxlength, uint32_t *tlength, uint32_t *prebuf, uint32_t* minreq, uint32_t *fragsize); +int32_t pa_hal_manager_reset_volume (pa_hal_manager *h); +int32_t pa_hal_manager_get_volume_level_max (pa_hal_manager *h, const char *volume_type, io_direction_t direction, uint32_t *level); +int32_t pa_hal_manager_get_volume_level (pa_hal_manager *h, const char *volume_type, io_direction_t direction, uint32_t *level); +int32_t pa_hal_manager_set_volume_level (pa_hal_manager *h, const char *volume_type, io_direction_t direction, uint32_t level); +int32_t pa_hal_manager_get_volume_value (pa_hal_manager *h, const char *volume_type, const char *gain_type, io_direction_t direction, uint32_t level, double *value); +int32_t pa_hal_manager_get_mute (pa_hal_manager *h, const char *volume_type, io_direction_t direction, uint32_t *mute); +int32_t pa_hal_manager_set_mute (pa_hal_manager *h, const char *volume_type, io_direction_t direction, uint32_t mute); +int32_t pa_hal_manager_do_route (pa_hal_manager *h, hal_route_info *info); +int32_t pa_hal_manager_update_route_option (pa_hal_manager *h, hal_route_option *option); +int32_t pa_hal_manager_update_stream_connection_info (pa_hal_manager *h, hal_stream_connection_info *info); + +#endif diff --git a/src/module-defs.h.m4 b/src/module-defs.h.m4 new file mode 100644 index 0000000..838b8e8 --- /dev/null +++ b/src/module-defs.h.m4 @@ -0,0 +1,35 @@ +changecom(`/*', `*/')dnl +define(`module_name', patsubst(patsubst(patsubst(fname, `-symdef.h$'), `^.*/'), `[^0-9a-zA-Z]', `_'))dnl +define(`c_symbol', patsubst(module_name, `[^0-9a-zA-Z]', `_'))dnl +define(`c_macro', patsubst(module_name, `[^0-9a-zA-Z]', `'))dnl +define(`incmacro', `foo'c_macro`symdeffoo')dnl +define(`gen_symbol', `#define $1 'module_name`_LTX_$1')dnl +#ifndef incmacro +#define incmacro + +#include +#include +#include + +gen_symbol(pa__init) +gen_symbol(pa__done) +gen_symbol(pa__get_author) +gen_symbol(pa__get_description) +gen_symbol(pa__get_usage) +gen_symbol(pa__get_version) +gen_symbol(pa__get_deprecated) +gen_symbol(pa__load_once) +gen_symbol(pa__get_n_used) + +int pa__init(pa_module*m); +void pa__done(pa_module*m); +int pa__get_n_used(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); +const char* pa__get_deprecated(void); +bool pa__load_once(void); + +#endif diff --git a/src/module-policy.c b/src/module-policy.c new file mode 100644 index 0000000..af44db5 --- /dev/null +++ b/src/module-policy.c @@ -0,0 +1,1544 @@ +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include // for mono +#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 +#ifdef HAVE_DBUS +#include +#include +#include +#endif + +#include "module-policy-symdef.h" +#include "communicator.h" +#include "hal-manager.h" +#include "stream-manager.h" +#include "stream-manager-volume.h" +#define DEVICE_MANAGER +#ifdef DEVICE_MANAGER +#include "device-manager.h" +#endif + +//To be changed +#ifndef VCONFKEY_SOUND_CAPTURE_STATUS +#define VCONFKEY_SOUND_CAPTURE_STATUS "memory/Sound/SoundCaptureStatus" +#endif + + +PA_MODULE_AUTHOR("Seungbae Shin"); +PA_MODULE_DESCRIPTION("Media Policy module"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(TRUE); +PA_MODULE_USAGE(" "); + +static const char* const valid_modargs[] = { + NULL +}; + +struct userdata; + +#ifdef HAVE_DBUS + +/*** Defines for module policy dbus interface ***/ +#define OBJECT_PATH "/org/pulseaudio/policy1" +#define INTERFACE_POLICY "org.PulseAudio.Ext.Policy1" +#define POLICY_INTROSPECT_XML \ + DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ + "" \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + "" + + +static DBusHandlerResult handle_get_property(DBusConnection *conn, DBusMessage *msg, void *userdata); +static DBusHandlerResult handle_get_all_property(DBusConnection *conn, DBusMessage *msg, void *userdata); +static DBusHandlerResult handle_set_property(DBusConnection *conn, DBusMessage *msg, void *userdata); +static DBusHandlerResult handle_policy_methods(DBusConnection *conn, DBusMessage *msg, void *userdata); +static DBusHandlerResult handle_introspect(DBusConnection *conn, DBusMessage *msg, void *userdata); +static DBusHandlerResult method_call_handler(DBusConnection *c, DBusMessage *m, void *userdata); +static void endpoint_init(struct userdata *u); +static void endpoint_done(struct userdata* u); + +/*** Called when module-policy load/unload ***/ +static void dbus_init(struct userdata* u); +static void dbus_deinit(struct userdata* u); + +/*** Defines for Property handle ***/ +/* property handlers */ +static void handle_get_property_test1(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_set_property_test1(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata); +static void handle_get_property_test2(DBusConnection *conn, DBusMessage *msg, void *userdata); + +enum property_index { + PROPERTY_TEST1, + PROPERTY_TEST2, + PROPERTY_MAX +}; + +static pa_dbus_property_handler property_handlers[PROPERTY_MAX] = { + [PROPERTY_TEST1] = { .property_name = "PropertyTest1", .type = "i", + .get_cb = handle_get_property_test1, + .set_cb = handle_set_property_test1 }, + [PROPERTY_TEST2] = { .property_name = "PropertyTest2", .type = "s", + .get_cb = handle_get_property_test2, + .set_cb = NULL }, +}; + + +/*** Defines for method handle ***/ +/* method handlers */ +static void handle_method_test1(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_method_test2(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata); + +enum method_handler_index { + METHOD_HANDLER_TEST1, + METHOD_HANDLER_TEST2, + METHOD_HANDLER_MAX +}; + +static pa_dbus_arg_info method1_args[] = { { "arg1", "s", "in" }, + { "arg2", "u", "out" } }; + +static pa_dbus_arg_info method2_args[] = { { "arg1", "i", "in" }, + { "arg2", "i", "in" }, + { "arg3", "i", "out" } }; + +static const char* method_arg_signatures[] = { "s", "ii" }; + +static pa_dbus_method_handler method_handlers[METHOD_HANDLER_MAX] = { + [METHOD_HANDLER_TEST1] = { + .method_name = "MethodTest1", + .arguments = method1_args, + .n_arguments = sizeof(method1_args) / sizeof(pa_dbus_arg_info), + .receive_cb = handle_method_test1 }, + [METHOD_HANDLER_TEST2] = { + .method_name = "MethodTest2", + .arguments = method2_args, + .n_arguments = sizeof(method2_args) / sizeof(pa_dbus_arg_info), + .receive_cb = handle_method_test2 } +}; + +/*** Defines for signal send ***/ +static int watch_signals(struct userdata* u); +static void unwatch_signals(struct userdata* u); +static void send_prop1_changed_signal(struct userdata* u); + +enum signal_index { + SIGNAL_PROP_CHANGED, + SIGNAL_TEST2, + SIGNAL_MAX +}; + +/*** Defines for get signal ***/ +#define SOUND_SERVER_INTERFACE_NAME "org.tizen.soundserver.service" +#define AUDIO_CLIENT_INTERFACE_NAME "org.tizen.audioclient.service" + +#define SOUND_SERVER_FILTER \ + "type='signal'," \ + " interface='" SOUND_SERVER_INTERFACE_NAME "'" +#define AUDIO_CLIENT_FILTER \ + "type='signal'," \ + " interface='" AUDIO_CLIENT_INTERFACE_NAME "'" + +#endif + +/* Sink & Source names */ +#define SINK_COMBINED "sink_combined" +#define SINK_NULL "sink_null" +#define SOURCE_NULL "source_null" + +/* Policies */ +#define POLICY_HIGH_LATENCY "high-latency" +#define POLICY_HIGH_LATENCY_UHQA "high-latency-uhqa" + +/* Macros */ +#define CONVERT_TO_DEVICE_DIRECTION(stream_type) \ + ((stream_type==STREAM_SINK_INPUT)?DM_DEVICE_DIRECTION_OUT:DM_DEVICE_DIRECTION_IN) + +/* PCM Dump */ +#define PA_DUMP_INI_DEFAULT_PATH "/usr/etc/mmfw_audio_pcm_dump.ini" +#define PA_DUMP_INI_TEMP_PATH "/opt/system/mmfw_audio_pcm_dump.ini" +#define PA_DUMP_VCONF_KEY "memory/private/sound/pcm_dump" +#define PA_DUMP_PLAYBACK_DECODER_OUT 0x00000001 +#define PA_DUMP_PLAYBACK_RESAMPLER_IN 0x00000008 +#define PA_DUMP_PLAYBACK_RESAMPLER_OUT 0x00000010 +#define PA_DUMP_CAPTURE_ENCODER_IN 0x80000000 + +/* check if this sink is bluez */ + +struct pa_hal_device_event_data { + audio_device_info_t device_info; + audio_device_param_info_t params[AUDIO_DEVICE_PARAM_MAX]; +}; + +struct userdata { + pa_core *core; + pa_module *module; + + pa_native_protocol *protocol; + +#ifdef HAVE_DBUS + pa_dbus_connection *dbus_conn; + int32_t test_property1; +#endif + + struct { + pa_communicator *comm; + pa_hook_slot *comm_hook_select_proper_sink_or_source_slot; + pa_hook_slot *comm_hook_change_route_slot; + pa_hook_slot *comm_hook_update_route_option_slot; + pa_hook_slot *comm_hook_device_connection_changed_slot; + } communicator; + + pa_hal_manager *hal_manager; + pa_stream_manager *stream_manager; +#ifdef DEVICE_MANAGER + pa_device_manager *device_manager; +#endif + pa_module *module_combine_sink; + pa_module *module_null_sink; + pa_module *module_null_source; +}; + +enum { + SUBCOMMAND_TEST, + SUBCOMMAND_GET_VOLUME_LEVEL, + SUBCOMMAND_SET_VOLUME_LEVEL, + SUBCOMMAND_GET_MUTE, + SUBCOMMAND_SET_MUTE, +}; + +static int __convert_volume_type_to_string(uint32_t volume_type, const char **volume_type_str) { + int ret = 0; + switch (volume_type) { + case AUDIO_VOLUME_TYPE_SYSTEM: + *volume_type_str = "system"; + break; + case AUDIO_VOLUME_TYPE_NOTIFICATION: + *volume_type_str = "notification"; + break; + case AUDIO_VOLUME_TYPE_ALARM: + *volume_type_str = "alarm"; + break; + case AUDIO_VOLUME_TYPE_RINGTONE: + *volume_type_str = "ringtone"; + break; + case AUDIO_VOLUME_TYPE_MEDIA: + *volume_type_str = "media"; + break; + case AUDIO_VOLUME_TYPE_CALL: + *volume_type_str = "call"; + break; + case AUDIO_VOLUME_TYPE_VOIP: + *volume_type_str = "voip"; + break; + case AUDIO_VOLUME_TYPE_VOICE: + *volume_type_str = "voice"; + break; + case AUDIO_VOLUME_TYPE_FIXED: + *volume_type_str = "fixed"; + break; + default: + ret = -1; + } + pa_log_debug("volume_type[%d] => [%s], ret[%d]", volume_type, *volume_type_str, ret); + return ret; +} + +static void __load_dump_config(struct userdata *u) +{ + dictionary * dict = NULL; + int vconf_dump = 0; + + dict = iniparser_load(PA_DUMP_INI_DEFAULT_PATH); + if (!dict) { + pa_log_debug("%s load failed. Use temporary file", PA_DUMP_INI_DEFAULT_PATH); + dict = iniparser_load(PA_DUMP_INI_TEMP_PATH); + if (!dict) { + pa_log_warn("%s load failed", PA_DUMP_INI_TEMP_PATH); + return; + } + } + + vconf_dump |= iniparser_getboolean(dict, "pcm_dump:decoder_out", 0) ? PA_DUMP_PLAYBACK_DECODER_OUT : 0; + vconf_dump |= iniparser_getboolean(dict, "pcm_dump:resampler_in", 0) ? PA_DUMP_PLAYBACK_RESAMPLER_IN : 0; + vconf_dump |= iniparser_getboolean(dict, "pcm_dump:resampler_out", 0) ? PA_DUMP_PLAYBACK_RESAMPLER_OUT : 0; + vconf_dump |= iniparser_getboolean(dict, "pcm_dump:encoder_in", 0) ? PA_DUMP_CAPTURE_ENCODER_IN : 0; + u->core->dump_sink = (pa_bool_t)iniparser_getboolean(dict, "pcm_dump:pa_sink", 0); + u->core->dump_sink_input = (pa_bool_t)iniparser_getboolean(dict, "pcm_dump:pa_sink_input", 0); + u->core->dump_source = (pa_bool_t)iniparser_getboolean(dict, "pcm_dump:pa_source", 0); + u->core->dump_source_output = (pa_bool_t)iniparser_getboolean(dict, "pcm_dump:pa_source_output", 0); + + iniparser_freedict(dict); + + if (vconf_set_int(PA_DUMP_VCONF_KEY, vconf_dump)) { + pa_log_warn("vconf_set_int %s=%x failed", PA_DUMP_VCONF_KEY, vconf_dump); + } +} + +#define EXT_VERSION 1 + +static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connection *c, uint32_t tag, pa_tagstruct *t) { + struct userdata *u = NULL; + uint32_t command; + pa_tagstruct *reply = NULL; + + pa_assert(p); + pa_assert(m); + pa_assert(c); + pa_assert(t); + + u = m->userdata; + + if (pa_tagstruct_getu32(t, &command) < 0) + goto fail; + + reply = pa_tagstruct_new(NULL, 0); + pa_tagstruct_putu32(reply, PA_COMMAND_REPLY); + pa_tagstruct_putu32(reply, tag); + + switch (command) { + case SUBCOMMAND_TEST: { + if (!pa_tagstruct_eof(t)) + goto fail; + + pa_tagstruct_putu32(reply, EXT_VERSION); + break; + } + /* it will be removed soon */ + case SUBCOMMAND_GET_VOLUME_LEVEL: { + uint32_t stream_idx = PA_INVALID_INDEX; + uint32_t volume_type = 0; + uint32_t volume_level = 0; + const char *volume_str = NULL; + + pa_tagstruct_getu32(t, &stream_idx); + pa_tagstruct_getu32(t, &volume_type); + + __convert_volume_type_to_string(volume_type, &volume_str); + pa_stream_manager_volume_get_level(u->stream_manager, STREAM_SINK_INPUT, volume_str, &volume_level); + + pa_tagstruct_putu32(reply, volume_level); + break; + } + /* it will be removed soon */ + case SUBCOMMAND_SET_VOLUME_LEVEL: { + uint32_t stream_idx = PA_INVALID_INDEX; + uint32_t volume_type = 0; + uint32_t volume_level = 0; + const char *volume_str = NULL; + + pa_tagstruct_getu32(t, &stream_idx); + pa_tagstruct_getu32(t, &volume_type); + pa_tagstruct_getu32(t, &volume_level); + + __convert_volume_type_to_string(volume_type, &volume_str); + pa_stream_manager_volume_set_level(u->stream_manager, STREAM_SINK_INPUT, volume_str, volume_level); + break; + } + /* it will be removed soon */ + case SUBCOMMAND_GET_MUTE: { + uint32_t stream_idx = PA_INVALID_INDEX; + uint32_t volume_type = 0; + uint32_t direction = 0; + pa_bool_t mute = FALSE; + const char *volume_str = NULL; + + pa_tagstruct_getu32(t, &stream_idx); + pa_tagstruct_getu32(t, &volume_type); + pa_tagstruct_getu32(t, &direction); + + __convert_volume_type_to_string(volume_type, &volume_str); + pa_stream_manager_volume_get_mute(u->stream_manager, STREAM_SINK_INPUT, volume_str, &mute); + + pa_tagstruct_putu32(reply, (uint32_t)mute); + break; + } + /* it will be removed soon */ + case SUBCOMMAND_SET_MUTE: { + uint32_t stream_idx = PA_INVALID_INDEX; + uint32_t volume_type = 0; + uint32_t direction = 0; + uint32_t mute = 0; + const char *volume_str = NULL; + + pa_tagstruct_getu32(t, &stream_idx); + pa_tagstruct_getu32(t, &volume_type); + pa_tagstruct_getu32(t, &direction); + pa_tagstruct_getu32(t, &mute); + + __convert_volume_type_to_string(volume_type, &volume_str); + if ((int32_t)stream_idx == -1) + pa_stream_manager_volume_set_mute(u->stream_manager, STREAM_SINK_INPUT, volume_str, (pa_bool_t)mute); + else + pa_stream_manager_volume_set_mute_by_idx(u->stream_manager, STREAM_SINK_INPUT, stream_idx, (pa_bool_t)mute); + break; + } + + default: + goto fail; + } + + pa_pstream_send_tagstruct(pa_native_connection_get_pstream(c), reply); + return 0; + + fail: + + if (reply) + pa_tagstruct_free(reply); + + return -1; +} + +/* Set the proper sink(source) according to the data of the parameter. */ +/* - ROUTE_TYPE_AUTO(_ALL) */ +/* 1. Find the proper sink/source comparing between avail_devices */ +/* and current connected devices. */ +/* 2. If not found, set it to null sink/source. */ +/* - ROUTE_TYPE_MANUAL */ +/* 1. Find the proper sink/source comparing between avail_devices */ +/* and manual_devices that have been set by user. */ +/* 2. If not found, set it to null sink/source. */ +static pa_hook_result_t select_proper_sink_or_source_hook_cb(pa_core *c, pa_stream_manager_hook_data_for_select *data, struct userdata *u) { +#ifdef DEVICE_MANAGER + uint32_t idx = 0; + uint32_t m_idx = 0; + uint32_t conn_idx = 0; + uint32_t *device_id = NULL; + const char *device_type = NULL; + const char *dm_device_type = NULL; + const char *dm_device_subtype = NULL; + dm_device *device = NULL; + dm_device_direction_t device_direction = DM_DEVICE_DIRECTION_NONE; + pa_idxset *conn_devices = NULL; + + pa_log_info("select_proper_sink_or_source_hook_cb is called. (%p), stream_type(%d), stream_role(%s), route_type(%d)", + data, data->stream_type, data->stream_role, data->route_type); + + if ((data->route_type == STREAM_ROUTE_TYPE_AUTO || data->route_type == STREAM_ROUTE_TYPE_AUTO_ALL) && data->idx_avail_devices) { + /* Get current connected devices */ + conn_devices = pa_device_manager_get_device_list(u->device_manager); + PA_IDXSET_FOREACH(device_type, data->idx_avail_devices, idx) { + pa_log_debug("[AUTO(_ALL)] avail_device[%u] for this role[%s]: type(%s)", idx, data->stream_role, device_type); + PA_IDXSET_FOREACH(device, conn_devices, conn_idx) { + dm_device_type = pa_device_manager_get_device_type(device); + dm_device_subtype = pa_device_manager_get_device_subtype(device); + device_direction = pa_device_manager_get_device_direction(device); + pa_log_debug("[AUTO(_ALL)] conn_devices, type[%s], subtype[%s], direction[0x%x]", dm_device_type, dm_device_subtype, device_direction); + if (pa_streq(device_type, dm_device_type) && + (((data->stream_type==STREAM_SINK_INPUT) && (device_direction & DM_DEVICE_DIRECTION_OUT)) || + ((data->stream_type==STREAM_SOURCE_OUTPUT) && (device_direction & DM_DEVICE_DIRECTION_IN)))) { + pa_log_debug("[AUTO(_ALL)] found a matched device: type[%s], direction[0x%x]", device_type, device_direction); + + if (data->stream_type == STREAM_SINK_INPUT && u->module_combine_sink) { + *(data->proper_sink) = pa_namereg_get(u->module->core, SINK_COMBINED, PA_NAMEREG_SINK); + pa_log_debug("[AUTO(_ALL)] found the combine-sink, set it to the sink"); + } else if (data->stream_type == STREAM_SINK_INPUT) + *(data->proper_sink) = pa_device_manager_get_sink(device, DEVICE_ROLE_NORMAL); + else + *(data->proper_source) = pa_device_manager_get_source(device, DEVICE_ROLE_NORMAL); + goto SUCCESS; + } + } + } + /* need to add logic for auto-all. (use combine-sink, move sink-input/source-output) */ + + } else if (data->route_type == STREAM_ROUTE_TYPE_MANUAL && data->idx_manual_devices && data->idx_avail_devices) { + PA_IDXSET_FOREACH(device_type, data->idx_avail_devices, idx) { + pa_log_debug("[MANUAL] avail_device[%u] for this role[%s]: type(%s)", idx, data->stream_role, device_type); + PA_IDXSET_FOREACH(device_id, data->idx_manual_devices, m_idx) { + device = pa_device_manager_get_device_by_id(u->device_manager, *device_id); + if (device) { + dm_device_type = pa_device_manager_get_device_type(device); + dm_device_subtype = pa_device_manager_get_device_subtype(device); + device_direction = pa_device_manager_get_device_direction(device); + pa_log_debug("[MANUAL] manual_devices, type[%s], subtype[%s], direction[0x%x], device id[%u]", + dm_device_type, dm_device_subtype, device_direction, *device_id); + if (pa_streq(device_type, dm_device_type) && + (((data->stream_type==STREAM_SINK_INPUT) && (device_direction & DM_DEVICE_DIRECTION_OUT)) || + ((data->stream_type==STREAM_SOURCE_OUTPUT) && (device_direction & DM_DEVICE_DIRECTION_IN)))) { + pa_log_debug("[MANUAL] found a matched device: type[%s], direction[0x%x]", device_type, device_direction); + if (data->stream_type == STREAM_SINK_INPUT) + *(data->proper_sink) = pa_device_manager_get_sink(device, DEVICE_ROLE_NORMAL); + else + *(data->proper_source) = pa_device_manager_get_source(device, DEVICE_ROLE_NORMAL); + goto SUCCESS; + } + } + } + } + } + + if ((data->stream_type==STREAM_SINK_INPUT)?!(*(data->proper_sink)):!(*(data->proper_source))) { + pa_log_warn("could not find a proper sink/source, set it to null sink/source"); + if (data->stream_type == STREAM_SINK_INPUT) + *(data->proper_sink) = (pa_sink*)pa_namereg_get(u->core, SINK_NULL, PA_NAMEREG_SINK); + else + *(data->proper_source) = (pa_source*)pa_namereg_get(u->core, SOURCE_NULL, PA_NAMEREG_SOURCE); + } +SUCCESS: +#endif + return PA_HOOK_OK; +} + +/* Change the route setting according to the data from argument. */ +/* This function is called only when it needs to change routing path via HAL. */ +/* - role is "reset" */ +/* 1. It will be received when it is needed to terminate playback */ +/* or capture routing path. */ +/* 2. Update the state of the device to be deactivated. */ +/* 3. Call HAL API to reset routing. */ +/* - ROUTE_TYPE_AUTO */ +/* 1. Find the proper sink/source comparing between avail_devices */ +/* and current connected devices. */ +/* : Need to check the priority of the device list by order of receipt. */ +/* 2. Update the state of devices. */ +/* 3. Call HAL API to apply the routing setting */ +/* - ROUTE_TYPE_AUTO_ALL */ +/* 1. Find the proper sink/source comparing between avail_devices */ +/* and current connected devices. */ +/* : Might use combine-sink according to the conditions. */ +/* 2. Update the state of devices. */ +/* 3. Call HAL API to apply the routing setting */ +/* - ROUTE_TYPE_MANUAL */ +/* 1. Find the proper sink/source comparing between avail_devices */ +/* and manual_devices that have been set by user. */ +/* 2. Update the state of devices. */ +/* 3. Call HAL API to apply the routing setting */ +static pa_hook_result_t route_change_hook_cb(pa_core *c, pa_stream_manager_hook_data_for_route *data, struct userdata *u) { +#ifdef DEVICE_MANAGER + uint32_t i = 0; + uint32_t idx = 0; + uint32_t d_idx = 0; + uint32_t s_idx = 0; + uint32_t *stream_idx = NULL; + hal_route_info route_info = {NULL, NULL, 0}; + uint32_t conn_idx = 0; + uint32_t *device_id = NULL; + uint32_t device_idx = 0; + const char *device_type = NULL; + dm_device *device = NULL; + dm_device *_device = NULL; + const char *dm_device_type = NULL; + const char *dm_device_subtype = NULL; + dm_device_state_t device_state = DM_DEVICE_STATE_DEACTIVATED; + dm_device_direction_t device_direction = DM_DEVICE_DIRECTION_NONE; + void *s = NULL; + pa_sink *sink = NULL; + pa_source *source = NULL; + pa_idxset *conn_devices = NULL; + pa_sink *combine_sink_arg1 = NULL; + pa_sink *combine_sink_arg2 = NULL; + char *args = NULL; + + pa_log_info("route_change_hook_cb is called. (%p), stream_type(%d), stream_role(%s), route_type(%d)", + data, data->stream_type, data->stream_role, data->route_type); + route_info.role = data->stream_role; + + /* Streams */ + if (data->idx_streams) { + PA_IDXSET_FOREACH(stream_idx, data->idx_streams, idx) { + pa_log_debug("-- stream[%u]: idx(%u)", idx, *stream_idx); + } + } + + /* Devices */ + if (pa_streq(data->stream_role, "reset")) { + /* Get current connected devices */ + conn_devices = pa_device_manager_get_device_list(u->device_manager); + /* Set device state to deactivate */ + PA_IDXSET_FOREACH(device, conn_devices, conn_idx) { + dm_device_type = pa_device_manager_get_device_type(device); + device_state = pa_device_manager_get_device_state(device, CONVERT_TO_DEVICE_DIRECTION(data->stream_type)); + device_direction = pa_device_manager_get_device_direction(device); + if (device_state == DM_DEVICE_STATE_ACTIVATED && + (((data->stream_type==STREAM_SINK_INPUT) && (device_direction & DM_DEVICE_DIRECTION_OUT)) || + ((data->stream_type==STREAM_SOURCE_OUTPUT) && (device_direction & DM_DEVICE_DIRECTION_IN)))) { + pa_log_debug("[RESET] found a matched device and set state to DE-ACTIVATED: type[%s], direction[0x%x]", dm_device_type, device_direction); + /* set device state to deactivated */ + pa_device_manager_set_device_state(device, CONVERT_TO_DEVICE_DIRECTION(data->stream_type), DM_DEVICE_STATE_DEACTIVATED); + } + } + route_info.num_of_devices = 1; + route_info.device_infos = pa_xmalloc0(sizeof(hal_device_info)*route_info.num_of_devices); + route_info.device_infos[0].direction = (data->stream_type==STREAM_SINK_INPUT)?DIRECTION_OUT:DIRECTION_IN; + + /* unload combine sink */ + if (data->stream_type==STREAM_SINK_INPUT && u->module_combine_sink) { + pa_log_debug ("[RESET] unload module[%s]", SINK_COMBINED); + pa_sink_suspend(pa_namereg_get(u->module->core, SINK_COMBINED, PA_NAMEREG_SINK), TRUE, PA_SUSPEND_USER); + pa_module_unload(u->module->core, u->module_combine_sink, TRUE); + u->module_combine_sink = NULL; + } + + } else if ((data->route_type == STREAM_ROUTE_TYPE_AUTO || data->route_type == STREAM_ROUTE_TYPE_AUTO_ALL) && data->idx_avail_devices) { + /* Get current connected devices */ + conn_devices = pa_device_manager_get_device_list(u->device_manager); + PA_IDXSET_FOREACH(device_type, data->idx_avail_devices, idx) { + pa_log_debug("[AUTO(_ALL)] avail_device[%u] for this role[%s]: type[%s]", idx, route_info.role, device_type); + PA_IDXSET_FOREACH(device, conn_devices, conn_idx) { + dm_device_type = pa_device_manager_get_device_type(device); + dm_device_subtype = pa_device_manager_get_device_subtype(device); + device_direction = pa_device_manager_get_device_direction(device); + device_idx = pa_device_manager_get_device_id(device); + pa_log_debug("[AUTO(_ALL)] conn_devices, type[%s], subtype[%s], direction[0x%x], id[%u]", + dm_device_type, dm_device_subtype, device_direction, device_idx); + if (pa_streq(device_type, dm_device_type) && + (((data->stream_type==STREAM_SINK_INPUT) && (device_direction & DM_DEVICE_DIRECTION_OUT)) || + ((data->stream_type==STREAM_SOURCE_OUTPUT) && (device_direction & DM_DEVICE_DIRECTION_IN)))) { + if (dm_device_subtype && pa_streq(DEVICE_PROFILE_BT_SCO, dm_device_subtype)) + continue; + route_info.num_of_devices++; + route_info.device_infos = pa_xrealloc(route_info.device_infos, sizeof(hal_device_info)*route_info.num_of_devices); + route_info.device_infos[route_info.num_of_devices-1].type = dm_device_type; + route_info.device_infos[route_info.num_of_devices-1].direction = (data->stream_type==STREAM_SINK_INPUT)?DIRECTION_OUT:DIRECTION_IN; + route_info.device_infos[route_info.num_of_devices-1].id = device_idx; + pa_log_debug("[AUTO(_ALL)] found a matched device and set state to ACTIVATED: type[%s], direction[0x%x], id[%u]", + route_info.device_infos[route_info.num_of_devices-1].type, device_direction, device_idx); + /* Set device state to activated */ + pa_device_manager_set_device_state(device, CONVERT_TO_DEVICE_DIRECTION(data->stream_type), DM_DEVICE_STATE_ACTIVATED); + break; + } + } + if (data->route_type == STREAM_ROUTE_TYPE_AUTO && device) { + /* Set other device's state to deactivated */ + PA_IDXSET_FOREACH(_device, conn_devices, conn_idx) { + if (device == _device) + continue; + pa_device_manager_set_device_state(_device, CONVERT_TO_DEVICE_DIRECTION(data->stream_type), DM_DEVICE_STATE_DEACTIVATED); + } + + /* Move sink-inputs/source-outputs if needed */ + if (data->stream_type == STREAM_SINK_INPUT) + sink = pa_device_manager_get_sink(device, DEVICE_ROLE_NORMAL); + else if (data->stream_type == STREAM_SOURCE_OUTPUT) + source = pa_device_manager_get_source(device, DEVICE_ROLE_NORMAL); + if (data->idx_streams) { + PA_IDXSET_FOREACH (s, data->idx_streams, s_idx) { + if (sink && (sink != ((pa_sink_input*)s)->sink)) { + pa_sink_input_move_to(s, sink, FALSE); + pa_log_debug("[AUTO] *** sink-input(%p,%u) moves to sink(%p,%s)", s, ((pa_sink_input*)s)->index, sink, sink->name); + } else if (source && (source != ((pa_source_output*)s)->source)) { + pa_source_output_move_to(s, source, FALSE); + pa_log_debug("[AUTO] *** source-output(%p,%u) moves to source(%p,%s)", s, ((pa_source_output*)s)->index, source, source->name); + } + } + } + /* unload combine sink */ + if (data->stream_type==STREAM_SINK_INPUT && u->module_combine_sink) { + pa_sink *combine_sink = pa_namereg_get(u->module->core, SINK_COMBINED, PA_NAMEREG_SINK); + if (combine_sink->inputs) { + PA_IDXSET_FOREACH (s, combine_sink->inputs, s_idx) { + pa_sink_input_move_to(s, sink, FALSE); + pa_log_debug("[AUTO] *** sink-input(%p,%u) moves to sink(%p,%s)", s, ((pa_sink_input*)s)->index, sink, sink->name); + } + } + pa_log_debug ("[AUTO] unload module[%s]", SINK_COMBINED); + pa_sink_suspend(pa_namereg_get(u->module->core, SINK_COMBINED, PA_NAMEREG_SINK), TRUE, PA_SUSPEND_USER); + pa_module_unload(u->module->core, u->module_combine_sink, TRUE); + u->module_combine_sink = NULL; + } + break; + + } else if (data->route_type == STREAM_ROUTE_TYPE_AUTO_ALL && device) { + /* find the proper sink/source */ + /* currently, we support two sinks for combining */ + if (data->stream_type == STREAM_SINK_INPUT && u->module_combine_sink) { + sink = pa_namereg_get(u->module->core, SINK_COMBINED, PA_NAMEREG_SINK); + pa_log_debug ("[AUTO_ALL] found the combine_sink already existed"); + } else if (data->stream_type == STREAM_SINK_INPUT && !combine_sink_arg1) { + sink = combine_sink_arg1 = pa_device_manager_get_sink(device, DEVICE_ROLE_NORMAL); + pa_log_debug ("[AUTO_ALL] combine_sink_arg1[%s], combine_sink_arg2[%p]", sink->name, combine_sink_arg2); + } else if (data->stream_type == STREAM_SINK_INPUT && !combine_sink_arg2) { + sink = pa_device_manager_get_sink(device, DEVICE_ROLE_NORMAL); + if(sink && !pa_streq(sink->name, combine_sink_arg1->name)) { + pa_log_debug ("[AUTO_ALL] combine_sink_arg2[%s]", sink->name); + combine_sink_arg2 = pa_device_manager_get_sink(device, DEVICE_ROLE_NORMAL); + /* load combine sink */ + if (!u->module_combine_sink) { + args = pa_sprintf_malloc("sink_name=%s slaves=\"%s,%s\"", SINK_COMBINED, combine_sink_arg1->name, combine_sink_arg2->name); + pa_log_debug ("[AUTO_ALL] combined sink is not prepared, now load module[%s]", args); + u->module_combine_sink = pa_module_load(u->module->core, "module-combine-sink", args); + pa_xfree(args); + } + sink = pa_namereg_get(u->module->core, SINK_COMBINED, PA_NAMEREG_SINK); + PA_IDXSET_FOREACH (s, combine_sink_arg1->inputs, s_idx) { + pa_sink_input_move_to(s, sink, FALSE); + pa_log_debug("[AUTO_ALL] *** sink-input(%p,%u) moves to sink(%p,%s)", s, ((pa_sink_input*)s)->index, sink, sink->name); + } + } + } else if (data->stream_type == STREAM_SOURCE_OUTPUT) { + source = pa_device_manager_get_source(device, DEVICE_ROLE_NORMAL); + } + + if (data->origins_from_new_data) { + if (data->stream_type == STREAM_SINK_INPUT) + *(data->proper_sink) = sink; + else + *(data->proper_source) = source; + } else { + /* Move sink-inputs/source-outputs if needed */ + if (data->idx_streams) { + PA_IDXSET_FOREACH (s, data->idx_streams, s_idx) { + if (sink && (sink != ((pa_sink_input*)s)->sink)) { + pa_sink_input_move_to(s, sink, FALSE); + pa_log_debug("[AUTO(_ALL)] *** sink-input(%p,%u) moves to sink(%p,%s)", s, ((pa_sink_input*)s)->index, sink, sink->name); + } else if (source && (source != ((pa_source_output*)s)->source)) { + pa_source_output_move_to(s, source, FALSE); + pa_log_debug("[AUTO(_ALL)] *** source-output(%p,%u) moves to source(%p,%s)", s, ((pa_source_output*)s)->index, source, source->name); + } + } + } + if (u->module_null_sink) { + pa_sink *null_sink = pa_namereg_get(u->module->core, SINK_NULL, PA_NAMEREG_SINK); + if (null_sink) { + PA_IDXSET_FOREACH (s, null_sink->inputs, s_idx) { + pa_sink_input_move_to(s, sink, FALSE); + pa_log_debug("[AUTO(_ALL)] *** sink-input(%p,%u) moves to sink(%p,%s)", s, ((pa_sink_input*)s)->index, sink, sink->name); + } + } + } + } + } + } + + if (data->route_type == STREAM_ROUTE_TYPE_AUTO_ALL && route_info.num_of_devices) { + /* Set other device's state to deactivated */ + PA_IDXSET_FOREACH(_device, conn_devices, conn_idx) { + pa_bool_t need_to_deactive = TRUE; + device_idx = pa_device_manager_get_device_id(_device); + for (i = 0; i < route_info.num_of_devices; i++) { + if (device_idx == route_info.device_infos[i].id) { + need_to_deactive = FALSE; + break; + } + } + if (need_to_deactive) + pa_device_manager_set_device_state(_device, CONVERT_TO_DEVICE_DIRECTION(data->stream_type), DM_DEVICE_STATE_DEACTIVATED); + } + } + + } else if (data->route_type == STREAM_ROUTE_TYPE_MANUAL && data->idx_manual_devices && data->idx_avail_devices) { + PA_IDXSET_FOREACH(device_type, data->idx_avail_devices, idx) { + pa_log_debug("[MANUAL] avail_device[%u] for this role[%s]: type(%s)", idx, data->stream_role, device_type); + PA_IDXSET_FOREACH(device_id, data->idx_manual_devices, d_idx) { + pa_log_debug("[MANUAL] manual_device[%u] for this role[%s]: device_id(%u)", idx, data->stream_role, *device_id); + device = pa_device_manager_get_device_by_id(u->device_manager, *device_id); + if (device) { + dm_device_type = pa_device_manager_get_device_type(device); + dm_device_subtype = pa_device_manager_get_device_subtype(device); + device_direction = pa_device_manager_get_device_direction(device); + pa_log_debug("[MANUAL] manual_device, type[%s], subtype[%s], direction[0x%x]", dm_device_type, dm_device_subtype, device_direction); + if (pa_streq(device_type, dm_device_type) && + (((data->stream_type==STREAM_SINK_INPUT) && (device_direction & DM_DEVICE_DIRECTION_OUT)) || + ((data->stream_type==STREAM_SOURCE_OUTPUT) && (device_direction & DM_DEVICE_DIRECTION_IN)))) { + pa_log_debug("[MANUAL] found a matched device: type[%s], direction[0x%x]", device_type, device_direction); + route_info.num_of_devices++; + route_info.device_infos = pa_xrealloc(route_info.device_infos, sizeof(hal_device_info)*route_info.num_of_devices); + route_info.device_infos[route_info.num_of_devices-1].type = dm_device_type; + route_info.device_infos[route_info.num_of_devices-1].direction = (data->stream_type==STREAM_SINK_INPUT)?DIRECTION_OUT:DIRECTION_IN; + pa_log_debug("[MANUAL] found a matched device and set state to ACTIVATED: type[%s], direction[0x%x]", + route_info.device_infos[route_info.num_of_devices-1].type, device_direction); + /* Set device state to activated */ + pa_device_manager_set_device_state(device, CONVERT_TO_DEVICE_DIRECTION(data->stream_type), DM_DEVICE_STATE_ACTIVATED); + } + } + } + } + + /* Move sink-inputs/source-outputs if needed */ + if (device && !data->origins_from_new_data) { + if (data->stream_type == STREAM_SINK_INPUT) + sink = pa_device_manager_get_sink(device, DEVICE_ROLE_NORMAL); + else if (data->stream_type == STREAM_SOURCE_OUTPUT) + source = pa_device_manager_get_source(device, DEVICE_ROLE_NORMAL); + if (data->idx_streams) { + PA_IDXSET_FOREACH (s, data->idx_streams, idx) { + if (sink && (sink != ((pa_sink_input*)s)->sink)) { + pa_sink_input_move_to(s, sink, FALSE); + pa_log_debug("[MANUAL] *** sink-input(%p,%u) moves to sink(%p,%s)", s, ((pa_sink_input*)s)->index, sink, sink->name); + } else if (source && (source != ((pa_source_output*)s)->source)) { + pa_source_output_move_to(s, source, FALSE); + pa_log_debug("[MANUAL] *** source-output(%p,%u) moves to source(%p,%s)", s, ((pa_source_output*)s)->index, source, source->name); + } + } + } + } + } + + if (route_info.device_infos) { + /* Send information to HAL to set routing */ + if(pa_hal_manager_do_route (u->hal_manager, &route_info)) + pa_log_error("Failed to pa_hal_manager_do_route()"); + pa_xfree(route_info.device_infos); + } +#endif + return PA_HOOK_OK; +} + +/* Forward routing option to HAL */ +static pa_hook_result_t route_option_update_hook_cb(pa_core *c, pa_stream_manager_hook_data_for_option *data, struct userdata *u) { + hal_route_option route_option; + + pa_log_info("route_option_update_hook_cb is called. (%p), stream_role(%s), option[name(%s)/value(%d)]", + data, data->stream_role, data->name, data->value); + route_option.role = data->stream_role; + route_option.name = data->name; + route_option.value = data->value; + + /* Send information to HAL to update routing option */ + if(pa_hal_manager_update_route_option (u->hal_manager, &route_option)) + pa_log_error("Failed to pa_hal_manager_update_route_option()"); + + return PA_HOOK_OK; +} + +/* Reorganize routing when a device has been connected or disconnected */ +static pa_hook_result_t device_connection_changed_hook_cb(pa_core *c, pa_device_manager_hook_data_for_conn_changed *conn, struct userdata *u) { + uint32_t s_idx = 0; + pa_sink_input *s = NULL; + const char *device_type = NULL; + const char *device_subtype = NULL; + dm_device_direction_t device_direction = DM_DEVICE_DIRECTION_OUT; + + device_direction = pa_device_manager_get_device_direction(conn->device); + device_type = pa_device_manager_get_device_type(conn->device); + device_subtype = pa_device_manager_get_device_subtype(conn->device); + pa_log_info("device_connection_changed_hook_cb is called. conn(%p), is_connected(%d), device(%p,%s,%s), direction(0x%x)", + conn, conn->is_connected, conn->device, device_type, device_subtype, device_direction); + + if (!conn->is_connected && pa_streq(DEVICE_TYPE_BT, device_type) && + device_subtype && pa_streq(DEVICE_PROFILE_BT_A2DP, device_subtype) && + device_direction == DM_DEVICE_DIRECTION_OUT) { + if (u->module_combine_sink) { + /* unload combine sink */ + pa_sink *combine_sink = pa_namereg_get(u->module->core, SINK_COMBINED, PA_NAMEREG_SINK); + pa_sink *null_sink = pa_namereg_get(u->module->core, SINK_NULL, PA_NAMEREG_SINK); + if (combine_sink->inputs) { + PA_IDXSET_FOREACH (s, combine_sink->inputs, s_idx) { + pa_sink_input_move_to(s, null_sink, FALSE); + pa_log_debug(" *** sink-input(%p,%u) moves to sink(%p,%s)", s, ((pa_sink_input*)s)->index, null_sink, null_sink->name); + } + } + pa_sink_suspend(pa_namereg_get(u->module->core, SINK_COMBINED, PA_NAMEREG_SINK), TRUE, PA_SUSPEND_USER); + pa_module_unload(u->module->core, u->module_combine_sink, TRUE); + u->module_combine_sink = NULL; + } + } + + return PA_HOOK_OK; +} + +#ifdef HAVE_DBUS +static void _do_something1(char* arg1, int arg2, void *data) +{ + pa_assert(data); + pa_assert(arg1); + + pa_log_debug("Do Something 1 , arg1 (%s) arg2 (%d)", arg1, arg2); +} + +static void _do_something2(char* arg1, void *data) +{ + pa_assert(data); + pa_assert(arg1); + + pa_log_debug("Do Something 2 , arg1 (%s) ", arg1); +} + +static void handle_get_property_test1(DBusConnection *conn, DBusMessage *msg, void *userdata) +{ + struct userdata *u = userdata; + dbus_int32_t value_i = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(u); + + value_i = u->test_property1; + + pa_dbus_send_basic_value_reply(conn, msg, DBUS_TYPE_INT32, &value_i); +} + +static void handle_set_property_test1(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) +{ + struct userdata *u = userdata; + dbus_int32_t value_i = 0; + pa_assert(conn); + pa_assert(msg); + pa_assert(u); + + dbus_message_iter_get_basic(iter, &value_i); + + u->test_property1 = value_i; + pa_dbus_send_empty_reply(conn, msg); + + /* send signal to notify change of property1*/ + send_prop1_changed_signal(u); +} + +/* test property handler : return module name */ +static void handle_get_property_test2(DBusConnection *conn, DBusMessage *msg, void *userdata) +{ + struct userdata *u = userdata; + pa_assert(conn); + pa_assert(msg); + pa_assert(u); + + if (!u->module->name) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "property(module name) null"); + return; + } + + pa_dbus_send_basic_value_reply(conn, msg, DBUS_TYPE_STRING, &u->module->name); +} + +static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) +{ + struct userdata *u = userdata; + dbus_int32_t value_i = 0; + + DBusMessage *reply = NULL; + DBusMessageIter msg_iter; + DBusMessageIter dict_iter; + + pa_assert(conn); + pa_assert(msg); + pa_assert(u); + + pa_assert_se((reply = dbus_message_new_method_return(msg))); + + value_i = u->test_property1; + + dbus_message_iter_init_append(reply, &msg_iter); + pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter)); + + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_TEST1].property_name, DBUS_TYPE_INT32, &value_i); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_TEST2].property_name, DBUS_TYPE_STRING, &u->module->name); + pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter)); + pa_assert_se(dbus_connection_send(conn, reply, NULL)); + dbus_message_unref(reply); +} + +/* test method : return length of argument string */ +static void handle_method_test1(DBusConnection *conn, DBusMessage *msg, void *userdata) +{ + const char* arg1_s = NULL; + dbus_uint32_t value_u = 0; + pa_assert(conn); + pa_assert(msg); + + pa_assert_se(dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &arg1_s, DBUS_TYPE_INVALID)); + value_u = strlen(arg1_s); + pa_dbus_send_basic_value_reply(conn, msg, DBUS_TYPE_UINT32, &value_u); +} + +static void handle_method_test2(DBusConnection *conn, DBusMessage *msg, void *userdata) +{ + dbus_int32_t value1, value2, result; + pa_assert(conn); + pa_assert(msg); + + pa_assert_se(dbus_message_get_args(msg, NULL, + DBUS_TYPE_INT32, &value1, + DBUS_TYPE_INT32, &value2, + DBUS_TYPE_INVALID)); + + result = value1 * value2; + + pa_dbus_send_basic_value_reply(conn, msg, DBUS_TYPE_INT32, &result); +} + +static DBusMessage* _generate_basic_property_change_signal_msg(int property_index, int property_type, void *data) { + DBusMessage *signal_msg; + DBusMessageIter signal_iter, dict_iter; + const char *interface = INTERFACE_POLICY; + + /* org.freedesktop.DBus.Properties.PropertiesChanged ( + STRING interface_name, + DICT changed_properties, + ARRAY invalidated_properties); */ + + pa_assert_se(signal_msg = dbus_message_new_signal(OBJECT_PATH, DBUS_INTERFACE_PROPERTIES, SIGNAL_PROP_CHANGED)); + dbus_message_iter_init_append(signal_msg, &signal_iter); + + /* STRING interface_name */ + dbus_message_iter_append_basic(&signal_iter, DBUS_TYPE_STRING, &interface); + + /* DICT changed_properties */ + pa_assert_se(dbus_message_iter_open_container(&signal_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter)); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[property_index].property_name, + property_type, data); + dbus_message_iter_close_container(&signal_iter, &dict_iter); + + /* ARRAY invalidated_properties (empty) */ + dbus_message_iter_open_container(&signal_iter, DBUS_TYPE_ARRAY, "s", &dict_iter); + dbus_message_iter_close_container(&signal_iter, &dict_iter); + + return signal_msg; +} + +static void send_prop1_changed_signal(struct userdata* u) { + DBusMessage *signal_msg = _generate_basic_property_change_signal_msg(PROPERTY_TEST1, DBUS_TYPE_INT32, &u->test_property1); + +#ifdef USE_DBUS_PROTOCOL + pa_dbus_protocol_send_signal(u->dbus_protocol, signal_msg); +#else + dbus_connection_send(pa_dbus_connection_get(u->dbus_conn), signal_msg, NULL); +#endif + + dbus_message_unref(signal_msg); +} + +static DBusHandlerResult dbus_filter_audio_handler(DBusConnection *c, DBusMessage *s, void *userdata) +{ + DBusError error; + char* arg_s = NULL; + int arg_i = 0; + + pa_assert(userdata); + + if(dbus_message_get_type(s)!=DBUS_MESSAGE_TYPE_SIGNAL) + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + pa_log_info("Audio handler received msg"); + dbus_error_init(&error); + + if (dbus_message_is_signal(s, SOUND_SERVER_INTERFACE_NAME, "TestSignalFromSS1")) { + if (!dbus_message_get_args(s, NULL, + DBUS_TYPE_STRING, &arg_s, + DBUS_TYPE_INT32, &arg_i , + DBUS_TYPE_INVALID)) { + goto fail; + } else { + _do_something1(arg_s, arg_i, userdata); + } + } else if (dbus_message_is_signal(s, SOUND_SERVER_INTERFACE_NAME, "TestSignalFromSS2")) { + if (!dbus_message_get_args(s, NULL, + DBUS_TYPE_STRING, &arg_s, + DBUS_TYPE_INVALID)) { + goto fail; + } else{ + _do_something2(arg_s, userdata); + } + } else if (dbus_message_is_signal(s, AUDIO_CLIENT_INTERFACE_NAME, "TestSignalFromClient1")) { + if (!dbus_message_get_args(s, NULL, + DBUS_TYPE_STRING, &arg_s, + DBUS_TYPE_INVALID)) { + goto fail; + } else{ + _do_something2(arg_s, userdata); + } + } else { + pa_log_info("Unknown message, not handle it"); + dbus_error_free(&error); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + pa_log_debug("Dbus Message handled"); + + dbus_error_free(&error); + return DBUS_HANDLER_RESULT_HANDLED; + +fail: + pa_log_error("Fail to handle dbus signal"); + dbus_error_free(&error); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +static DBusHandlerResult handle_get_property(DBusConnection *conn, DBusMessage *msg, void *userdata) +{ + int prop_idx = 0; + const char *interface_name, *property_name; + + pa_assert(conn); + pa_assert(msg); + pa_assert(userdata); + + if (pa_streq(dbus_message_get_signature(msg), "ss")) { + pa_assert_se(dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &interface_name, + DBUS_TYPE_STRING, &property_name, + DBUS_TYPE_INVALID)); + if (pa_streq(interface_name, INTERFACE_POLICY)) { + for (prop_idx = 0; prop_idx < PROPERTY_MAX; prop_idx++) { + if (pa_streq(property_name, property_handlers[prop_idx].property_name)) { + property_handlers[prop_idx].get_cb(conn, msg, userdata); + return DBUS_HANDLER_RESULT_HANDLED; + } + } + } + else{ + pa_log_warn("Not our interface, not handle it"); + } + } else{ + pa_log_warn("Wrong Signature"); + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_SIGNATURE, "Wrong Signature, Expected (ss)"); + } + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +static DBusHandlerResult handle_get_all_property(DBusConnection *conn, DBusMessage *msg, void *userdata) +{ + const char *interface_name; + + pa_assert(conn); + pa_assert(msg); + pa_assert(userdata); + + if (pa_streq(dbus_message_get_signature(msg), "s")) { + pa_assert_se(dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &interface_name, + DBUS_TYPE_INVALID)); + if (pa_streq(interface_name, INTERFACE_POLICY)) { + handle_get_all(conn, msg, userdata); + return DBUS_HANDLER_RESULT_HANDLED; + } + else{ + pa_log_warn("Not our interface, not handle it"); + } + } else{ + pa_log_warn("Wrong Signature"); + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_SIGNATURE, "Wrong Signature, Expected (ss)"); + } + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +static DBusHandlerResult handle_set_property(DBusConnection *conn, DBusMessage *msg, void *userdata) +{ + int prop_idx = 0; + const char *interface_name, *property_name, *property_sig; + DBusMessageIter msg_iter; + DBusMessageIter variant_iter; + + pa_assert(conn); + pa_assert(msg); + pa_assert(userdata); + + if (pa_streq(dbus_message_get_signature(msg), "ssv")) { + pa_assert_se(dbus_message_iter_init(msg, &msg_iter)); + dbus_message_iter_get_basic(&msg_iter, &interface_name); + pa_assert_se(dbus_message_iter_next(&msg_iter)); + dbus_message_iter_get_basic(&msg_iter, &property_name); + pa_assert_se(dbus_message_iter_next(&msg_iter)); + + dbus_message_iter_recurse(&msg_iter, &variant_iter); + + property_sig = dbus_message_iter_get_signature(&variant_iter); + + if (pa_streq(interface_name, INTERFACE_POLICY)) { + for (prop_idx = 0; prop_idx < PROPERTY_MAX; prop_idx++) { + if (pa_streq(property_name, property_handlers[prop_idx].property_name)) { + if (pa_streq(property_handlers[prop_idx].type,property_sig)) { + property_handlers[prop_idx].set_cb(conn, msg, &variant_iter, userdata); + return DBUS_HANDLER_RESULT_HANDLED; + } + else{ + pa_log_warn("Wrong Property Signature"); + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_SIGNATURE, "Wrong Signature, Expected (ssv)"); + } + break; + } + } + } + else{ + pa_log_warn("Not our interface, not handle it"); + } + } else{ + pa_log_warn("Wrong Signature"); + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_SIGNATURE, "Wrong Signature, Expected (ssv)"); + } + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +static DBusHandlerResult handle_policy_methods(DBusConnection *conn, DBusMessage *msg, void *userdata) +{ + int method_idx = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(userdata); + + for (method_idx = 0; method_idx < METHOD_HANDLER_MAX; method_idx++) { + if (dbus_message_is_method_call(msg, INTERFACE_POLICY, method_handlers[method_idx].method_name )) { + if (pa_streq(dbus_message_get_signature(msg), method_arg_signatures[method_idx])) { + method_handlers[method_idx].receive_cb(conn, msg, userdata); + return DBUS_HANDLER_RESULT_HANDLED; + } + else{ + pa_log_warn("Wrong Argument Signature"); + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_SIGNATURE, "Wrong Signature, Expected %s", method_arg_signatures[method_idx]); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + } + } + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +static DBusHandlerResult handle_introspect(DBusConnection *conn, DBusMessage *msg, void *userdata) +{ + const char *xml = POLICY_INTROSPECT_XML; + DBusMessage *r = NULL; + + pa_assert(conn); + pa_assert(msg); + pa_assert(userdata); + + pa_assert_se(r = dbus_message_new_method_return(msg)); + pa_assert_se(dbus_message_append_args(r, DBUS_TYPE_STRING, &xml, DBUS_TYPE_INVALID)); + + if (r) { + pa_assert_se(dbus_connection_send((conn), r, NULL)); + dbus_message_unref(r); + } + + return DBUS_HANDLER_RESULT_HANDLED; +} + +static DBusHandlerResult method_call_handler(DBusConnection *c, DBusMessage *m, void *userdata) +{ + struct userdata *u = userdata; + const char *path, *interface, *member; + + pa_assert(c); + pa_assert(m); + pa_assert(u); + + path = dbus_message_get_path(m); + interface = dbus_message_get_interface(m); + member = dbus_message_get_member(m); + + pa_log_debug("dbus: path=%s, interface=%s, member=%s", path, interface, member); + + if (!pa_streq(path, OBJECT_PATH)) + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) { + return handle_introspect(c, m, u); + } else if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Properties", "Get")){ + return handle_get_property(c, m, u); + } else if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Properties", "Set")){ + return handle_set_property(c, m, u); + } else if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Properties", "GetAll")){ + return handle_get_all_property(c, m, u); + } else{ + return handle_policy_methods(c, m, u); + } + + return DBUS_HANDLER_RESULT_HANDLED; +} + +static void endpoint_init(struct userdata *u) +{ + static const DBusObjectPathVTable vtable_endpoint = { + .message_function = method_call_handler, + }; + + pa_log_debug("Dbus endpoint init"); + + if (u && u->dbus_conn) { + if(!dbus_connection_register_object_path(pa_dbus_connection_get(u->dbus_conn), OBJECT_PATH, &vtable_endpoint, u)) + pa_log_error("Failed to register object path"); + } else{ + pa_log_error("Cannot get dbus connection to register object path"); + } +} + +static void endpoint_done(struct userdata* u) +{ + pa_log_debug("Dbus endpoint done"); + if (u && u->dbus_conn) { + if(!dbus_connection_unregister_object_path(pa_dbus_connection_get(u->dbus_conn), OBJECT_PATH)) + pa_log_error("Failed to unregister object path"); + } else{ + pa_log_error("Cannot get dbus connection to unregister object path"); + } +} + +static int watch_signals(struct userdata* u) +{ + DBusError error; + + dbus_error_init(&error); + + pa_log_debug("Watch Dbus signals"); + + if (u && u->dbus_conn) { + + if (!dbus_connection_add_filter(pa_dbus_connection_get(u->dbus_conn), dbus_filter_audio_handler, u, NULL)) { + pa_log_error("Unable to add D-Bus filter : %s: %s", error.name, error.message); + goto fail; + } + + if (pa_dbus_add_matches(pa_dbus_connection_get(u->dbus_conn), &error, SOUND_SERVER_FILTER, AUDIO_CLIENT_FILTER, NULL) < 0) { + pa_log_error("Unable to subscribe to signals: %s: %s", error.name, error.message); + goto fail; + } + return 0; + } + +fail: + dbus_error_free(&error); + return -1; +} + +static void unwatch_signals(struct userdata* u) +{ + pa_log_debug("Unwatch Dbus signals"); + + if (u && u->dbus_conn) { + pa_dbus_remove_matches(pa_dbus_connection_get(u->dbus_conn), SOUND_SERVER_FILTER, AUDIO_CLIENT_FILTER, NULL); + dbus_connection_remove_filter(pa_dbus_connection_get(u->dbus_conn), dbus_filter_audio_handler, u); + } +} + + + +static void dbus_init(struct userdata* u) +{ + DBusError error; + pa_dbus_connection *connection = NULL; + + pa_log_debug("Dbus init"); + dbus_error_init(&error); + + if (!(connection = pa_dbus_bus_get(u->core, DBUS_BUS_SYSTEM, &error)) || dbus_error_is_set(&error)) { + if (connection) { + pa_dbus_connection_unref(connection); + } + pa_log_error("Unable to contact D-Bus system bus: %s: %s", error.name, error.message); + goto fail; + } else{ + pa_log_debug("Got dbus connection"); + } + + u->dbus_conn = connection; + + if( watch_signals(u) < 0 ) + pa_log_error("dbus watch signals failed"); + else + pa_log_debug("dbus ready to get signals"); + + endpoint_init(u); + +fail: + dbus_error_free(&error); + +} + +static void dbus_deinit(struct userdata* u) +{ + pa_log_debug("Dbus deinit"); + if (u) { + + endpoint_done(u); + unwatch_signals(u); + + if (u->dbus_conn){ + pa_dbus_connection_unref(u->dbus_conn); + u->dbus_conn = NULL; + } + } +} +#endif + +int pa__init(pa_module *m) +{ + pa_modargs *ma = NULL; + struct userdata *u; + char *args = NULL; + + pa_assert(m); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments"); + goto fail; + } + + m->userdata = u = pa_xnew0(struct userdata, 1); + u->core = m->core; + u->module = m; + +#ifdef HAVE_DBUS + u->dbus_conn = NULL; + u->test_property1 = 123; +#endif + + u->protocol = pa_native_protocol_get(m->core); + pa_native_protocol_install_ext(u->protocol, m, extension_cb); + + u->hal_manager = pa_hal_manager_get(u->core, (void *)u); + + u->communicator.comm = pa_communicator_get(u->core); + if (u->communicator.comm) { + u->communicator.comm_hook_select_proper_sink_or_source_slot = pa_hook_connect( + pa_communicator_hook(u->communicator.comm, PA_COMMUNICATOR_HOOK_SELECT_INIT_SINK_OR_SOURCE), + PA_HOOK_EARLY, (pa_hook_cb_t)select_proper_sink_or_source_hook_cb, u); + u->communicator.comm_hook_change_route_slot = pa_hook_connect( + pa_communicator_hook(u->communicator.comm, PA_COMMUNICATOR_HOOK_CHANGE_ROUTE), + PA_HOOK_EARLY, (pa_hook_cb_t)route_change_hook_cb, u); + u->communicator.comm_hook_update_route_option_slot = pa_hook_connect( + pa_communicator_hook(u->communicator.comm, PA_COMMUNICATOR_HOOK_UPDATE_ROUTE_OPTION), + PA_HOOK_EARLY, (pa_hook_cb_t)route_option_update_hook_cb, u); + u->communicator.comm_hook_device_connection_changed_slot = pa_hook_connect( + pa_communicator_hook(u->communicator.comm, PA_COMMUNICATOR_HOOK_DEVICE_CONNECTION_CHANGED), + PA_HOOK_EARLY, (pa_hook_cb_t)device_connection_changed_hook_cb, u); + } + u->stream_manager = pa_stream_manager_init(u->core); + +#ifdef DEVICE_MANAGER + u->device_manager = pa_device_manager_init(u->core); +#endif + + /* load null sink/source */ + args = pa_sprintf_malloc("sink_name=%s", SINK_NULL); + u->module_null_sink = pa_module_load(u->module->core, "module-null-sink", args); + pa_xfree(args); + args = pa_sprintf_malloc("source_name=%s", SOURCE_NULL); + u->module_null_source = pa_module_load(u->module->core, "module-null-source", args); + pa_xfree(args); + + __load_dump_config(u); + +#ifdef HAVE_DBUS + dbus_init(u); +#endif + + pa_log_info("policy module is loaded\n"); + + if (ma) + pa_modargs_free(ma); + + 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)) + return; + + pa_module_unload(u->module->core, u->module_null_sink, TRUE); + u->module_null_sink = NULL; + pa_module_unload(u->module->core, u->module_null_source, TRUE); + u->module_null_source = NULL; + +#ifdef HAVE_DBUS + dbus_deinit(u); +#endif +#ifdef DEVICE_MANAGER + if (u->device_manager) + pa_device_manager_done(u->device_manager); +#endif + if (u->protocol) { + pa_native_protocol_remove_ext(u->protocol, m); + pa_native_protocol_unref(u->protocol); + } + + if (u->stream_manager) + pa_stream_manager_done(u->stream_manager); + + if (u->communicator.comm) { + if (u->communicator.comm_hook_change_route_slot) + pa_hook_slot_free(u->communicator.comm_hook_change_route_slot); + if (u->communicator.comm_hook_change_route_slot) + pa_hook_slot_free(u->communicator.comm_hook_update_route_option_slot); + if (u->communicator.comm_hook_device_connection_changed_slot) + pa_hook_slot_free(u->communicator.comm_hook_device_connection_changed_slot); + pa_communicator_unref(u->communicator.comm); + } + + if (u->hal_manager) + pa_hal_manager_unref(u->hal_manager); + + pa_xfree(u); + + + pa_log_info("policy module is unloaded\n"); +} diff --git a/src/module-sound-player.c b/src/module-sound-player.c new file mode 100644 index 0000000..9f98543 --- /dev/null +++ b/src/module-sound-player.c @@ -0,0 +1,626 @@ +/*** + This file is part of PulseAudio. + + Copyright 2014 Sangchul Lee + + PulseAudio 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. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_DBUS +#include +#include +#include +#endif + +#include "module-sound-player-symdef.h" + +#include + +PA_MODULE_AUTHOR("Sangchul Lee"); +PA_MODULE_DESCRIPTION("Sound Player module"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(TRUE); + +#ifdef HAVE_DBUS +#define ARR_ARG_MAX 32 +#define SOUND_PLAYER_OBJECT_PATH "/org/pulseaudio/SoundPlayer" +#define SOUND_PLAYER_INTERFACE "org.pulseaudio.SoundPlayer" +#define SOUND_PLAYER_METHOD_NAME_SIMPLE_PLAY "SimplePlay" +#define SOUND_PLAYER_METHOD_NAME_SAMPLE_PLAY "SamplePlay" +#define SOUND_PLAYER_SIGNAL_EOS "EOS" + +static DBusHandlerResult method_handler_for_vt(DBusConnection *c, DBusMessage *m, void *userdata); +static DBusHandlerResult handle_introspect(DBusConnection *conn, DBusMessage *msg, void *userdata); +static DBusHandlerResult handle_methods(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_simple_play(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_sample_play(DBusConnection *conn, DBusMessage *msg, void *userdata); + +enum method_handler_index { + METHOD_HANDLER_SIMPLE_PLAY, + METHOD_HANDLER_SAMPLE_PLAY, + METHOD_HANDLER_MAX +}; + +static pa_dbus_arg_info simple_play_args[] = { { "uri", "s", "in" }, + { "role", "s", "in" }, + { "volume_gain", "s", "in" }}; +static pa_dbus_arg_info sample_play_args[] = { { "sample_name", "s", "in" }, + { "role", "s", "in" }, + { "volume_gain", "s", "in" }}; + +static const char* signature_args_for_in[] = { "sss", "sss" }; + +static pa_dbus_method_handler method_handlers[METHOD_HANDLER_MAX] = { + [METHOD_HANDLER_SIMPLE_PLAY] = { + .method_name = SOUND_PLAYER_METHOD_NAME_SIMPLE_PLAY, + .arguments = simple_play_args, + .n_arguments = sizeof(simple_play_args) / sizeof(pa_dbus_arg_info), + .receive_cb = handle_simple_play }, + [METHOD_HANDLER_SAMPLE_PLAY] = { + .method_name = SOUND_PLAYER_METHOD_NAME_SAMPLE_PLAY, + .arguments = sample_play_args, + .n_arguments = sizeof(sample_play_args) / sizeof(pa_dbus_arg_info), + .receive_cb = handle_sample_play } +}; + +#ifdef USE_DBUS_PROTOCOL + +static pa_dbus_interface_info sound_player_interface_info = { + .name = SOUND_PLAYER_INTERFACE, + .method_handlers = method_handlers, + .n_method_handlers = METHOD_HANDLER_MAX, + .property_handlers = , + .n_property_handlers = , + .get_all_properties_cb =, + .signals =, + .n_signals = +}; + +#else + +#define SOUND_PLAYER_INTROSPECT_XML \ + DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ + "" \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + "" +#endif + +#endif + +static void io_event_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_io_event_flags_t events, void*userdata); + +struct userdata { + int fd; + pa_io_event *io; + pa_module *module; + pa_hook_slot *sink_input_unlink_slot; +#ifdef HAVE_DBUS +#ifdef USE_DBUS_PROTOCOL + pa_dbus_protocol *dbus_protocol; +#else + pa_dbus_connection *dbus_conn; +#endif + pa_idxset *stream_idxs; +#endif +}; + +#define FILE_FULL_PATH 1024 /* File path length */ +#define ROLE_NAME_LEN 64 /* Role name length */ +#define VOLUME_GAIN_TYPE_LEN 64 /* Volume gain type length */ +#define RETRY_NUM 100 + +struct ipc_data { + char filename[FILE_FULL_PATH]; + char role[ROLE_NAME_LEN]; + char volume_gain_type[VOLUME_GAIN_TYPE_LEN]; +}; + +#define KEYTONE_PATH "/tmp/keytone" /* Keytone pipe path */ +#define KEYTONE_GROUP 6526 /* Keytone group : assigned by security */ +#define DEFAULT_IPC_TYPE IPC_TYPE_PIPE + +#define MAX_NAME_LEN 256 +static int _simple_play(struct userdata *u, const char *file_path, const char *role, const char *vol_gain_type) { + int ret = 0; + pa_sink *sink = NULL; + pa_proplist *p; + const char *name_prefix = "SIMPLE_PLAY"; + + char name[MAX_NAME_LEN] = {0}; + + uint32_t stream_idx = 0; + uint32_t scache_idx = 0; + + p = pa_proplist_new(); + + /* Set role type of stream */ + if (role) + pa_proplist_sets(p, PA_PROP_MEDIA_ROLE, role); + + /* Set volume gain type of stream */ + if (vol_gain_type) + pa_proplist_sets(p, PA_PROP_MEDIA_TIZEN_VOLUME_GAIN_TYPE, vol_gain_type); + + sink = pa_namereg_get_default_sink(u->module->core); + + pa_log_debug("role[%s], volume_gain_type[%s]", role, vol_gain_type); + snprintf(name, sizeof(name)-1, "%s_%s", name_prefix, file_path); + scache_idx = pa_scache_get_id_by_name(u->module->core, name); + if (scache_idx != PA_IDXSET_INVALID) { + pa_log_debug("found cached index [%u] for name [%s]", scache_idx, file_path); + } else { + /* for more precision, need to update volume value here */ + if ((ret = pa_scache_add_file_lazy(u->module->core, name, file_path, &scache_idx)) != 0) { + pa_log_error("failed to add file [%s]", file_path); + goto exit; + } else { + pa_log_debug("success to add file [%s], index [%u]", file_path, scache_idx); + } + } + + pa_log_debug("pa_scache_play_item() start"); + ret = pa_scache_play_item(u->module->core, name, sink, PA_VOLUME_NORM, p, &stream_idx); + if (ret < 0) { + pa_log_error("pa_scache_play_item fail, ret[%d]", ret); + goto exit; + } + pa_log_debug("pa_scache_play_item() end, stream_idx(%u)", stream_idx); + + if (!ret) + ret = (int32_t)stream_idx; +exit: + pa_proplist_free(p); + return ret; +} + +static int _sample_play(struct userdata *u, const char *sample_name, const char *role, const char *vol_gain_type) { + int ret = 0; + pa_sink *sink = NULL; + pa_proplist *p; + + uint32_t stream_idx = 0; + uint32_t scache_idx = 0; + + p = pa_proplist_new(); + + /* Set role type of stream */ + if (role) + pa_proplist_sets(p, PA_PROP_MEDIA_ROLE, role); + + /* Set volume gain type of stream */ + if (vol_gain_type) + pa_proplist_sets(p, PA_PROP_MEDIA_TIZEN_VOLUME_GAIN_TYPE, vol_gain_type); + + sink = pa_namereg_get_default_sink(u->module->core); + + pa_log_debug("role[%s], volume_gain_type[%s]", role, vol_gain_type); + + scache_idx = pa_scache_get_id_by_name(u->module->core, sample_name); + if (scache_idx != PA_IDXSET_INVALID) { + pa_log_debug("pa_scache_play_item() start, scache idx[%u] for name[%s]", scache_idx, sample_name); + /* for more precision, need to update volume value here */ + if ((ret = pa_scache_play_item(u->module->core, sample_name, sink, PA_VOLUME_NORM, p, &stream_idx) < 0)) { + pa_log_error("pa_scache_play_item fail"); + goto exit; + } + pa_log_debug("pa_scache_play_item() end, stream_idx(%u)", stream_idx); + } else + pa_log_error("could not find the scache item for [%s]", sample_name); + + if (!ret) + ret = (int32_t)stream_idx; +exit: + pa_proplist_free(p); + return ret; +} + +#ifdef HAVE_DBUS +static DBusHandlerResult handle_introspect(DBusConnection *conn, DBusMessage *msg, void *userdata) { + const char *xml = SOUND_PLAYER_INTROSPECT_XML; + DBusMessage *r = NULL; + + pa_assert(conn); + pa_assert(msg); + pa_assert(userdata); + + pa_assert_se(r = dbus_message_new_method_return(msg)); + pa_assert_se(dbus_message_append_args(r, DBUS_TYPE_STRING, &xml, DBUS_TYPE_INVALID)); + + if (r) { + pa_assert_se(dbus_connection_send((conn), r, NULL)); + dbus_message_unref(r); + } + + return DBUS_HANDLER_RESULT_HANDLED; +} + +static void handle_simple_play(DBusConnection *conn, DBusMessage *msg, void *userdata) { + char *uri = NULL; + char *role = NULL; + char *volume_gain = NULL; + dbus_int32_t result = 0; + struct userdata *u = (struct userdata*)userdata; + pa_assert(conn); + pa_assert(msg); + pa_assert(u); + + pa_assert_se(dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &uri, + DBUS_TYPE_STRING, &role, + DBUS_TYPE_STRING, &volume_gain, + DBUS_TYPE_INVALID)); + pa_log_info("uri[%s], role[%s], volume_gain[%s]", uri, role, volume_gain); + if (uri) + result = _simple_play(u, uri, role, volume_gain); + else + result = -1; + + if (result != -1) { + uint32_t idx = 0; + int32_t *stream_idx = NULL; + stream_idx = pa_xmalloc0(sizeof(int32_t)); + *stream_idx = result; + pa_idxset_put(u->stream_idxs, stream_idx, &idx); + } + pa_dbus_send_basic_value_reply(conn, msg, DBUS_TYPE_INT32, &result); +} + +static void handle_sample_play(DBusConnection *conn, DBusMessage *msg, void *userdata) { + char *sample_name = NULL; + char *role = NULL; + char *volume_gain = NULL; + dbus_int32_t result = 0; + struct userdata *u = (struct userdata*)userdata; + pa_assert(conn); + pa_assert(msg); + pa_assert(u); + + pa_assert_se(dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &sample_name, + DBUS_TYPE_STRING, &role, + DBUS_TYPE_STRING, &volume_gain, + DBUS_TYPE_INVALID)); + pa_log_info("sample_name[%s], role[%s], volume_gain[%s]", sample_name, role, volume_gain); + if (sample_name) + result = _sample_play(u, sample_name, role, volume_gain); + else + result = -1; + + if (result != -1) { + uint32_t idx = 0; + int32_t *stream_idx = NULL; + stream_idx = pa_xmalloc0(sizeof(int32_t)); + *stream_idx = result; + pa_idxset_put(u->stream_idxs, stream_idx, &idx); + } + + pa_dbus_send_basic_value_reply(conn, msg, DBUS_TYPE_INT32, &result); +} + +static DBusHandlerResult handle_methods(DBusConnection *conn, DBusMessage *msg, void *userdata) { + int idx = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(userdata); + + for (idx = 0; idx < METHOD_HANDLER_MAX; idx++) { + if (dbus_message_is_method_call(msg, SOUND_PLAYER_INTERFACE, method_handlers[idx].method_name )) { + if (pa_streq(dbus_message_get_signature(msg), signature_args_for_in[idx])) { + method_handlers[idx].receive_cb(conn, msg, userdata); + return DBUS_HANDLER_RESULT_HANDLED; + } else { + pa_log_warn("Wrong Argument Signature"); + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_SIGNATURE, "Wrong Signature, Expected %s", signature_args_for_in[idx]); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + } + } + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +static DBusHandlerResult method_handler_for_vt(DBusConnection *c, DBusMessage *m, void *userdata) { + struct userdata *u = userdata; + const char *path, *interface, *member; + + pa_assert(c); + pa_assert(m); + pa_assert(u); + + path = dbus_message_get_path(m); + interface = dbus_message_get_interface(m); + member = dbus_message_get_member(m); + + pa_log_debug("dbus: path=%s, interface=%s, member=%s", path, interface, member); + + if (!pa_streq(path, SOUND_PLAYER_OBJECT_PATH)) + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) { + return handle_introspect(c, m, u); + } else { + return handle_methods(c, m, u); + } + + return DBUS_HANDLER_RESULT_HANDLED; +} + +static void send_signal_for_eos(struct userdata *u, int32_t stream_idx) { + DBusMessage *signal_msg = NULL; + + pa_assert(u); + + pa_log_debug("Send EOS signal for stream_idx(%d)", stream_idx); + + pa_assert_se(signal_msg = dbus_message_new_signal(SOUND_PLAYER_OBJECT_PATH, SOUND_PLAYER_INTERFACE, SOUND_PLAYER_SIGNAL_EOS)); + pa_assert_se(dbus_message_append_args(signal_msg, DBUS_TYPE_INT32, &stream_idx, DBUS_TYPE_INVALID)); + pa_assert_se(dbus_connection_send(pa_dbus_connection_get(u->dbus_conn), signal_msg, NULL)); + dbus_message_unref(signal_msg); +} +#endif + +static int init_ipc (struct userdata *u) { + int pre_mask; +#ifdef HAVE_DBUS +#ifndef USE_DBUS_PROTOCOL + DBusError err; + pa_dbus_connection *conn = NULL; + static const DBusObjectPathVTable vtable = { + .message_function = method_handler_for_vt, + }; +#endif +#endif + pa_assert(u); + + pa_log_info("Initialization for IPC"); + + pre_mask = umask(0); + if (mknod(KEYTONE_PATH,S_IFIFO|0660,0)<0) + pa_log_warn("mknod failed. errno=[%d][%s]", errno, strerror(errno)); + + umask(pre_mask); + + u->fd = open(KEYTONE_PATH, O_RDWR); + if (u->fd == -1) { + pa_log_warn("Check ipc node %s\n", KEYTONE_PATH); + goto fail; + } + + /* for non-blocking read */ + fcntl(u->fd, F_SETFL, O_NONBLOCK); + + /* change access mode so group can use keytone pipe */ + if (fchmod (u->fd, 0666) == -1) + pa_log_warn("Changing keytone access mode is failed. errno=[%d][%s]", errno, strerror(errno)); + + /* change group due to security request */ + if (fchown (u->fd, -1, KEYTONE_GROUP) == -1) + pa_log_warn("Changing keytone group is failed. errno=[%d][%s]", errno, strerror(errno)); + + u->io = u->module->core->mainloop->io_new(u->module->core->mainloop, u->fd, PA_IO_EVENT_INPUT|PA_IO_EVENT_HANGUP, io_event_callback, u); + +#ifdef HAVE_DBUS +#ifdef USE_DBUS_PROTOCOL + u->dbus_protocol = pa_dbus_protocol_get(u->module->core); + pa_assert_se(pa_dbus_protocol_add_interface(u->dbus_protocol, SOUND_PLAYER_OBJECT_PATH, &sound_player_interface_info, u) >= 0); + pa_assert_se(pa_dbus_protocol_register_extension(u->dbus_protocol, SOUND_PLAYER_INTERFACE) >= 0); +#else + dbus_error_init(&err); + + if (!(conn = pa_dbus_bus_get(u->module->core, DBUS_BUS_SYSTEM, &err)) || dbus_error_is_set(&err)) { + if (conn) { + pa_dbus_connection_unref(conn); + } + pa_log_error("Unable to contact D-Bus system bus: %s: %s", err.name, err.message); + goto fail; + } else + pa_log_notice("Got dbus connection"); + + u->dbus_conn = conn; + pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(conn), SOUND_PLAYER_OBJECT_PATH, &vtable, u)); +#endif +#else + pa_log_error("DBUS is not supported\n"); + goto fail; +#endif + + return 0; +fail: + return -1; +} + +static void deinit_ipc (struct userdata *u) { + + pa_assert(u); + + if (u->io) + u->module->core->mainloop->io_free(u->io); + + if (u->fd > -1) + close(u->fd); + +#ifdef HAVE_DBUS +#ifdef USE_DBUS_PROTOCOL + if (u->dbus_protocol) { + pa_assert_se(pa_dbus_protocol_unregister_extension(u->dbus_protocol, SOUND_PLAYER_INTERFACE) >= 0); + pa_assert_se(pa_dbus_protocol_remove_interface(u->dbus_protocol, SOUND_PLAYER_OBJECT_PATH, sound_player_interface_info.name) >= 0); + pa_dbus_protocol_unref(u->dbus_protocol); + u->dbus_protocol = NULL; + } +#else + if (u->dbus_conn) { + if(!dbus_connection_unregister_object_path(pa_dbus_connection_get(u->dbus_conn), SOUND_PLAYER_OBJECT_PATH)) + pa_log_error("Failed to unregister object path"); + u->dbus_conn = NULL; + } +#endif +#endif +} + +static void io_event_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_io_event_flags_t events, void*userdata) { + struct userdata *u = userdata; + struct ipc_data data; + int ret = 0; + int data_size = 0; + int read_sum = 0; + int retry_count = 0; + + pa_assert(io); + pa_assert(u); + + if (events & (PA_IO_EVENT_HANGUP|PA_IO_EVENT_ERROR)) { + pa_log_warn("Lost connection to client side"); + goto fail; + } + + if (events & PA_IO_EVENT_INPUT) { + data_size = sizeof(data); + memset(&data, 0, data_size); + while (read_sum != data_size && retry_count < RETRY_NUM) { + ret = read(fd, ((void *)&data)+read_sum, data_size-read_sum); + if (ret < 0 && errno == EAGAIN) + retry_count++; + else + read_sum += ret; + } + if (read_sum == data_size) { + pa_log_info("name(%s), role(%s), volume_gain_type(%s)", data.filename, data.role, data.volume_gain_type); + _simple_play(u, data.filename, data.role, data.volume_gain_type); + } else { + pa_log_warn("Fail to read, retry_count(%d), read sum(%d), err(%s)", retry_count, read_sum, pa_cstrerror(errno)); + } + } + + return; + +fail: + u->module->core->mainloop->io_free(u->io); + u->io = NULL; + + pa_module_unload_request(u->module, TRUE); +} + +static pa_hook_result_t sink_input_unlink_cb(pa_core *core, pa_sink_input *i, struct userdata *u) { + int32_t *stream_idx = NULL; + uint32_t idx = 0; + pa_core_assert_ref(core); + pa_sink_input_assert_ref(i); + + pa_log_info("start sink_input_unlink_cb, i(%p, index:%u)", i, i->index); + +#ifdef HAVE_DBUS + PA_IDXSET_FOREACH(stream_idx, u->stream_idxs, idx) { + if (*stream_idx == (int32_t)(i->index)) { +#ifndef USE_DBUS_PROTOCOL + /* Send EOS signal for this stream */ + send_signal_for_eos(u, *stream_idx); +#endif + pa_idxset_remove_by_data(u->stream_idxs, stream_idx, NULL); + pa_xfree(stream_idx); + } + } +#endif + + return PA_HOOK_OK; +} + +int pa__init(pa_module *m) { + struct userdata *u; + + pa_assert(m); + + m->userdata = u = pa_xnew0(struct userdata, 1); + u->module = m; + u->io = NULL; + u->fd = -1; +#ifdef HAVE_DBUS +#ifdef USE_DBUS_PROTOCOL + u->dbus_protocol = NULL; +#else + u->dbus_conn = NULL; +#endif + u->stream_idxs = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); +#endif + if (init_ipc(u)) + goto fail; + + u->sink_input_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_UNLINK], PA_HOOK_EARLY, (pa_hook_cb_t) sink_input_unlink_cb, u); + + return 0; + +fail: + pa__done(m); + return -1; +} + +void pa__done(pa_module *m) { + struct userdata *u; + pa_assert(m); + + if (!(u = m->userdata)) + return; + + if (u->sink_input_unlink_slot) + pa_hook_slot_free(u->sink_input_unlink_slot); + +#ifdef HAVE_DBUS + if (u->stream_idxs) + pa_idxset_free(u->stream_idxs, NULL); +#endif + + deinit_ipc(u); + + pa_xfree(u); +} diff --git a/src/module-tizenaudio-sink.c b/src/module-tizenaudio-sink.c new file mode 100644 index 0000000..c9d59f5 --- /dev/null +++ b/src/module-tizenaudio-sink.c @@ -0,0 +1,480 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2008 Lennart Poettering + + PulseAudio 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. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tizen-audio.h" +#include "module-tizenaudio-sink-symdef.h" + + +PA_MODULE_AUTHOR("Tizen"); +PA_MODULE_DESCRIPTION("TizenAudio sink"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(false); +PA_MODULE_USAGE( + "name= " + "sink_name= " + "sink_properties= " + "namereg_fail= " + "device= " + "device_id= " + "format= " + "rate= " + "channels= " + "channel_map="); + +#define DEFAULT_SINK_NAME "tizenaudio" +#define BLOCK_USEC (PA_USEC_PER_SEC * 0.032) + +struct userdata { + pa_core *core; + pa_module *module; + pa_sink *sink; + + pa_thread *thread; + pa_thread_mq thread_mq; + pa_rtpoll *rtpoll; + + snd_pcm_t *pcm_handle; + + pa_usec_t block_usec; + pa_usec_t timestamp; + + char* device_name; + bool first, after_rewind; + + uint64_t write_count; + uint64_t since_start; +}; + +static const char* const valid_modargs[] = { + "name", + "sink_name", + "sink_properties", + "namereg_fail", + "device", + "device_id", + "format", + "rate", + "channels", + "channel_map", + NULL +}; + +/* Called from IO context */ +static int suspend(struct userdata *u) { + void *audio_data; + audio_interface_t *audio_intf; + + pa_assert(u); + pa_assert(u->pcm_handle); + audio_data = pa_shared_get(u->core, "tizen-audio-data"); + audio_intf = pa_shared_get(u->core, "tizen-audio-interface"); + + /* Let's suspend -- we don't call snd_pcm_drain() here since that might + * take awfully long with our long buffer sizes today. */ + if (audio_intf && audio_intf->pcm_close) { + audio_intf->pcm_close(audio_data, u->pcm_handle); + u->pcm_handle = NULL; + } + pa_log_info("Device suspended...[%s]", u->device_name); + + return 0; +} + +/* Called from IO context */ +static int unsuspend(struct userdata *u) { + pa_sample_spec sample_spec; + void *audio_data; + audio_interface_t *audio_intf; + + pa_assert(u); + pa_assert(!u->pcm_handle); + + pa_log_info("Trying resume..."); + + audio_data = pa_shared_get(u->core, "tizen-audio-data"); + audio_intf = pa_shared_get(u->core, "tizen-audio-interface"); + sample_spec = u->sink->sample_spec; + + if (audio_intf && audio_intf->pcm_open) { + audio_return_t audio_ret = AUDIO_RET_OK; + if (AUDIO_IS_ERROR((audio_ret = audio_intf->pcm_open(audio_data, (void **)&u->pcm_handle, &sample_spec, AUDIO_DIRECTION_OUT)))) { + pa_log("Error opening PCM device %x", audio_ret); + goto fail; + } + } + pa_log_info("Trying sw param..."); + + u->write_count = 0; + u->first = true; + u->since_start = 0; + + pa_log_info("Resumed successfully..."); + + return 0; + +fail: + if (u->pcm_handle) { + if (audio_intf && audio_intf->pcm_close) + audio_intf->pcm_close(audio_data, u->pcm_handle); + + u->pcm_handle = NULL; + } + return -PA_ERR_IO; +} + +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; + int r; + + switch (code) { + case PA_SINK_MESSAGE_SET_STATE: + switch ((pa_sink_state_t) PA_PTR_TO_UINT(data)) { + case PA_SINK_SUSPENDED: { + pa_assert(PA_SINK_IS_OPENED(u->sink->thread_info.state)); + if ((r = suspend(u)) < 0) + return r; + + break; + } + + case PA_SINK_IDLE: + case PA_SINK_RUNNING: { + u->timestamp = pa_rtclock_now(); + if (u->sink->thread_info.state == PA_SINK_SUSPENDED) { + if ((r = unsuspend(u)) < 0) + return r; + } + break; + } + + case PA_SINK_UNLINKED: + case PA_SINK_INIT: + case PA_SINK_INVALID_STATE: + break; + } + break; + + case PA_SINK_MESSAGE_GET_LATENCY: { + pa_usec_t now; + + now = pa_rtclock_now(); + *((pa_usec_t*) data) = u->timestamp > now ? u->timestamp - now : 0ULL; + + return 0; + } + } + + return pa_sink_process_msg(o, code, data, offset, chunk); +} + +static void sink_update_requested_latency_cb(pa_sink *s) { + struct userdata *u; + size_t nbytes; + + pa_sink_assert_ref(s); + pa_assert_se(u = s->userdata); + + u->block_usec = pa_sink_get_requested_latency_within_thread(s); + + if (u->block_usec == (pa_usec_t) -1) + u->block_usec = s->thread_info.max_latency; + + nbytes = pa_usec_to_bytes(u->block_usec, &s->sample_spec); + pa_sink_set_max_rewind_within_thread(s, nbytes); + pa_sink_set_max_request_within_thread(s, nbytes); +} + +static void process_rewind(struct userdata *u, pa_usec_t now) { + size_t rewind_nbytes, in_buffer; + pa_usec_t delay; + + pa_assert(u); + + rewind_nbytes = u->sink->thread_info.rewind_nbytes; + + if (!PA_SINK_IS_OPENED(u->sink->thread_info.state) || rewind_nbytes <= 0) + goto do_nothing; + + pa_log_debug("Requested to rewind %lu bytes.", (unsigned long) rewind_nbytes); + + if (u->timestamp <= now) + goto do_nothing; + + delay = u->timestamp - now; + in_buffer = pa_usec_to_bytes(delay, &u->sink->sample_spec); + + if (in_buffer <= 0) + goto do_nothing; + + if (rewind_nbytes > in_buffer) + rewind_nbytes = in_buffer; + + pa_sink_process_rewind(u->sink, rewind_nbytes); + u->timestamp -= pa_bytes_to_usec(rewind_nbytes, &u->sink->sample_spec); + + pa_log_debug("Rewound %lu bytes.", (unsigned long) rewind_nbytes); + return; + +do_nothing: + + pa_sink_process_rewind(u->sink, 0); +} + +static void process_render(struct userdata *u, pa_usec_t now) { + size_t ate = 0; + void *p; + snd_pcm_sframes_t frames_to_write; + size_t frame_size; + audio_interface_t *audio_intf = pa_shared_get(u->core, "tizen-audio-interface"); + + pa_assert(u); + + /* This is the configured latency. Sink inputs connected to us + might not have a single frame more than the maxrequest value + queued. Hence: at maximum read this many bytes from the sink + inputs. */ + + /* Fill the buffer up the latency size */ + while (u->timestamp < now + u->block_usec) { + pa_memchunk chunk; + + audio_intf->pcm_avail(u->pcm_handle); + frame_size = pa_frame_size(&u->sink->sample_spec); + frames_to_write = u->sink->thread_info.max_request / frame_size; + + pa_sink_render_full(u->sink, frames_to_write * frame_size, &chunk); + p = pa_memblock_acquire(chunk.memblock); + + audio_intf->pcm_write(u->pcm_handle,(const uint8_t*) p + chunk.index, (snd_pcm_uframes_t) frames_to_write); + + pa_memblock_release(chunk.memblock); + pa_memblock_unref(chunk.memblock); + u->timestamp += pa_bytes_to_usec(chunk.length, &u->sink->sample_spec); + + ate += chunk.length; + if (ate >= u->sink->thread_info.max_request) { + break; + } + } +} + +static void thread_func(void *userdata) { + struct userdata *u = userdata; + + pa_assert(u); + pa_log_debug("Thread starting up"); + pa_thread_mq_install(&u->thread_mq); + u->timestamp = pa_rtclock_now(); + + for (;;) { + pa_usec_t now = 0; + int ret; + + if (PA_SINK_IS_OPENED(u->sink->thread_info.state)) + now = pa_rtclock_now(); + + if (PA_UNLIKELY(u->sink->thread_info.rewind_requested)) + process_rewind(u, now); + + /* Render some data and drop it immediately */ + if (PA_SINK_IS_OPENED(u->sink->thread_info.state)) { + if (u->timestamp <= now) { + process_render(u, now); + } + pa_rtpoll_set_timer_absolute(u->rtpoll, u->timestamp); + } else + pa_rtpoll_set_timer_disabled(u->rtpoll); + + /* Hmm, nothing to do. Let's 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"); +} + +int pa__init(pa_module*m) { + struct userdata *u = NULL; + pa_sample_spec ss; + pa_channel_map map; + pa_modargs *ma = NULL; + pa_sink_new_data data; + size_t nbytes; + uint32_t alternate_sample_rate; + + pa_assert(m); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments."); + goto fail; + } + + ss = m->core->default_sample_spec; + map = m->core->default_channel_map; + if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0) { + pa_log("Invalid sample format specification or 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; + } + + m->userdata = u = pa_xnew0(struct userdata, 1); + u->core = m->core; + u->module = m; + u->rtpoll = pa_rtpoll_new(); + pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll); + + pa_sink_new_data_init(&data); + data.driver = __FILE__; + data.module = m; + pa_sink_new_data_set_name(&data, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME)); + pa_sink_new_data_set_sample_spec(&data, &ss); + pa_sink_new_data_set_channel_map(&data, &map); + pa_proplist_sets(data.proplist, PA_PROP_DEVICE_DESCRIPTION, _("Tizen audio sink")); + pa_proplist_sets(data.proplist, PA_PROP_DEVICE_CLASS, "abstract"); + + if (pa_modargs_get_proplist(ma, "sink_properties", data.proplist, PA_UPDATE_REPLACE) < 0) { + pa_log("Invalid properties"); + pa_sink_new_data_done(&data); + goto fail; + } + + u->sink = pa_sink_new(m->core, &data, PA_SINK_LATENCY); + pa_sink_new_data_done(&data); + + if (!u->sink) { + pa_log("Failed to create sink object."); + goto fail; + } + + u->sink->parent.process_msg = sink_process_msg; + u->sink->update_requested_latency = sink_update_requested_latency_cb; + u->sink->userdata = u; + + pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); + pa_sink_set_rtpoll(u->sink, u->rtpoll); + + unsuspend (u); + + u->block_usec = BLOCK_USEC; + nbytes = pa_usec_to_bytes(u->block_usec, &u->sink->sample_spec); + pa_sink_set_max_rewind(u->sink, 0); + pa_sink_set_max_request(u->sink, nbytes); + + if (!(u->thread = pa_thread_new("tizenaudio-sink", thread_func, u))) { + pa_log("Failed to create thread."); + goto fail; + } + pa_sink_set_fixed_latency(u->sink, 32000); + pa_sink_put(u->sink); + pa_modargs_free(ma); + + return 0; + +fail: + if (ma) + pa_modargs_free(ma); + + pa__done(m); + return -1; +} + +int pa__get_n_used(pa_module *m) { + struct userdata *u; + + pa_assert(m); + pa_assert_se(u = m->userdata); + + return pa_sink_linked_by(u->sink); +} + +void pa__done(pa_module*m) { + struct userdata *u; + + pa_assert(m); + + if (!(u = m->userdata)) + return; + + 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->rtpoll) + pa_rtpoll_free(u->rtpoll); + + pa_xfree(u); +} diff --git a/src/stream-manager-priv.h b/src/stream-manager-priv.h new file mode 100644 index 0000000..5244e53 --- /dev/null +++ b/src/stream-manager-priv.h @@ -0,0 +1,98 @@ +#ifndef foostreammanagerprivfoo +#define foostreammanagerprivfoo + +#include "stream-manager.h" +#include "hal-manager.h" +#include "communicator.h" +#include "device-manager.h" + +#include +#include +#ifdef HAVE_DBUS +#include +#include +#include +#endif + +#define GET_STREAM_NEW_PROPLIST(stream, type) \ + (type == STREAM_SINK_INPUT? ((pa_sink_input_new_data*)stream)->proplist : ((pa_source_output_new_data*)stream)->proplist) + +#define GET_STREAM_PROPLIST(stream, type) \ + (type == STREAM_SINK_INPUT? ((pa_sink_input*)stream)->proplist : ((pa_source_output*)stream)->proplist) + +#define GET_STREAM_NEW_SAMPLE_SPEC(stream, type) \ + (type == STREAM_SINK_INPUT? ((pa_sink_input_new_data*)stream)->sample_spec : ((pa_source_output_new_data*)stream)->sample_spec) + +#define GET_STREAM_SAMPLE_SPEC(stream, type) \ + (type == STREAM_SINK_INPUT? ((pa_sink_input*)stream)->sample_spec : ((pa_source_output*)stream)->sample_spec) + +#define IS_FOCUS_ACQUIRED(focus, type) \ + (type == STREAM_SINK_INPUT? (focus & STREAM_FOCUS_ACQUIRED_PLAYBACK) : (focus & STREAM_FOCUS_ACQUIRED_CAPTURE)) + +enum stream_direction { + STREAM_DIRECTION_IN, + STREAM_DIRECTION_OUT, + STREAM_DIRECTION_MAX, +}; + +typedef struct _stream_info { + int32_t priority; + const char *volume_type[STREAM_DIRECTION_MAX]; + stream_route_type_t route_type; + pa_idxset *idx_avail_in_devices; + pa_idxset *idx_avail_out_devices; + pa_idxset *idx_avail_frameworks; +} stream_info; + +typedef struct _volume_info { + pa_bool_t is_hal_volume_type; + struct _values { + pa_bool_t is_muted; + uint32_t current_level; + pa_idxset *idx_volume_values; + } values[STREAM_DIRECTION_MAX]; +} volume_info; + +typedef struct _prior_max_priority_stream { + pa_sink_input *sink_input; + pa_source_output *source_output; + pa_bool_t need_to_update_si; + pa_bool_t need_to_update_so; +} cur_max_priority_stream; + +struct _stream_manager { + pa_core *core; + pa_hal_manager *hal; + pa_hashmap *volume_infos; + pa_hashmap *volume_modifiers; + pa_hashmap *stream_infos; + pa_hashmap *stream_parents; + cur_max_priority_stream cur_highest_priority; + pa_hook_slot + *sink_input_new_slot, + *sink_input_put_slot, + *sink_input_unlink_slot, + *sink_input_state_changed_slot, + *sink_input_move_start_slot, + *sink_input_move_finish_slot, + *source_output_new_slot, + *source_output_put_slot, + *source_output_unlink_slot, + *source_output_state_changed_slot; +#ifdef HAVE_DBUS +#ifdef USE_DBUS_PROTOCOL + pa_dbus_protocol *dbus_protocol; +#else + pa_dbus_connection *dbus_conn; +#endif +#endif + pa_subscription *subscription; + struct { + pa_communicator *comm; + pa_hook_slot *comm_hook_device_connection_changed_slot; + pa_hook_slot *comm_hook_device_information_changed_slot; + } comm; +}; + + +#endif diff --git a/src/stream-manager-volume-priv.h b/src/stream-manager-volume-priv.h new file mode 100644 index 0000000..f4a9a0f --- /dev/null +++ b/src/stream-manager-volume-priv.h @@ -0,0 +1,30 @@ +#ifndef foostreammanagervolumeprivfoo +#define foostreammanagervolumeprivfoo + +#include "stream-manager.h" + +#include +#include + +#define VCONFKEY_OUT_VOLUME_PREFIX "file/private/sound/volume/" +#define MASTER_VOLUME_TYPE "master" +#define MASTER_VOLUME_LEVEL_MAX 100 + +typedef enum { + GET_VOLUME_CURRENT_LEVEL, + GET_VOLUME_MAX_LEVEL +} pa_volume_get_command_t; + +int32_t init_volumes(pa_stream_manager *m); +void deinit_volumes(pa_stream_manager *m); +int32_t set_volume_level_by_type(pa_stream_manager *m, stream_type_t stream_type, const char *volume_type, uint32_t volume_level); +int32_t get_volume_level_by_type(pa_stream_manager *m, pa_volume_get_command_t command, stream_type_t stream_type, const char *volume_type, uint32_t *volume_level); +int32_t set_volume_level_by_idx(pa_stream_manager *m, stream_type_t stream_type, uint32_t idx, uint32_t volume_level); +int32_t set_volume_level_with_new_data(pa_stream_manager *m, stream_type_t stream_type, void *nd, uint32_t volume_level); +int32_t set_volume_mute_by_type(pa_stream_manager *m, stream_type_t stream_type, const char *volume_type, pa_bool_t volume_mute); +int32_t get_volume_mute_by_type(pa_stream_manager *m, stream_type_t stream_type, const char *volume_type, pa_bool_t *volume_mute); +int32_t set_volume_mute_by_idx(pa_stream_manager *m, stream_type_t stream_type, uint32_t stream_idx, pa_bool_t volume_mute); +int32_t set_volume_mute_with_new_data(pa_stream_manager *m, stream_type_t stream_type, void *nd, pa_bool_t volume_mute); +int32_t get_volume_mute_by_idx(pa_stream_manager *m, stream_type_t stream_type, uint32_t stream_idx, pa_bool_t *volume_mute); + +#endif diff --git a/src/stream-manager-volume.c b/src/stream-manager-volume.c new file mode 100644 index 0000000..009d0de --- /dev/null +++ b/src/stream-manager-volume.c @@ -0,0 +1,858 @@ +/*** + This file is part of PulseAudio. + + Copyright 2015 Sangchul Lee + + PulseAudio 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. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include "stream-manager-priv.h" +#include "stream-manager-volume.h" +#include "stream-manager-volume-priv.h" + +#define VOLUME_INI_DEFAULT_PATH "/usr/etc/mmfw_audio_volume.ini" +#define VOLUME_INI_TUNED_PATH "/opt/system/mmfw_audio_volume.ini" +#define DEFAULT_TABLE "volumes" + +/* TODO : after preparing gain map, we can remove it */ +static const char *__get_gain_type_string_by_idx (uint32_t gain_type_idx) { + switch (gain_type_idx) { + case AUDIO_GAIN_TYPE_DEFAULT: return "default"; + case AUDIO_GAIN_TYPE_DIALER: return "dialer"; + case AUDIO_GAIN_TYPE_TOUCH: return "touch"; + case AUDIO_GAIN_TYPE_AF: return "af"; + case AUDIO_GAIN_TYPE_SHUTTER1: return "shutter1"; + case AUDIO_GAIN_TYPE_SHUTTER2: return "shutter2"; + case AUDIO_GAIN_TYPE_CAMCODING: return "camcording"; + case AUDIO_GAIN_TYPE_MIDI: return "midi"; + case AUDIO_GAIN_TYPE_BOOTING: return "booting"; + case AUDIO_GAIN_TYPE_VIDEO: return "video"; + case AUDIO_GAIN_TYPE_TTS: return "tts"; + default: return "invalid"; + } +} + +static int load_out_volume_conf_file (pa_stream_manager *m) { + int ret = 0; + dictionary *dict = NULL; + uint32_t gain_type_idx = 0; + int size = 0; + const char delimiter[] = ", "; + char *key = NULL; + char *list_str = NULL; + char *token = NULL; + char *ptr = NULL; + const char *table_str = DEFAULT_TABLE; + volume_info* v = NULL; + void *state = NULL; + const char *vol_type_str = NULL; + pa_assert(m); + + dict = iniparser_load(VOLUME_INI_TUNED_PATH); + if (!dict) { + pa_log_warn("Loading tuned volume & gain table from ini file failed"); + dict = iniparser_load(VOLUME_INI_DEFAULT_PATH); + if (!dict) { + pa_log_warn("Loading default volume & gain table from ini file failed"); + ret = -1; + goto FAILURE; + } + } + + /* Load volume table */ + while ((v = pa_hashmap_iterate(m->volume_infos, &state, (const void**)&vol_type_str))) { + size = strlen(table_str) + strlen(vol_type_str) + 2; + key = pa_xmalloc0(size); + if (key) { + snprintf(key, size, "%s:%s", table_str, vol_type_str); + list_str = iniparser_getstring(dict, key, NULL); + if (list_str) { + token = strtok_r(list_str, delimiter, &ptr); + while (token) { + /* convert dB volume to linear volume */ + double *vol_value = pa_xmalloc0(sizeof(double)); + *vol_value = 0.0f; + if(strncmp(token, "0", strlen(token))) + *vol_value = pow(10.0, (atof(token) - 100) / 20.0); + if (!v->values[STREAM_DIRECTION_OUT].idx_volume_values) + v->values[STREAM_DIRECTION_OUT].idx_volume_values = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + pa_idxset_put(v->values[STREAM_DIRECTION_OUT].idx_volume_values, vol_value, NULL); + token = strtok_r(NULL, delimiter, &ptr); + } + } else { + pa_log_warn("[%s] is not defined, skip it", key); + } + } else { + pa_log_error("failed to pa_xmalloc0()"); + ret = -1; + goto FAILURE; + } + if (key) { + free(key); + key = NULL; + } + } + + /* Load gain table */ + for (gain_type_idx = AUDIO_GAIN_TYPE_DEFAULT + 1; gain_type_idx < AUDIO_GAIN_TYPE_MAX; gain_type_idx++) { + const char *gain_type_str = __get_gain_type_string_by_idx(gain_type_idx); + size = strlen(table_str) + strlen("gain") + strlen(gain_type_str) + 3; + key = pa_xmalloc0(size); + if (key) { + snprintf(key, size, "%s:gain_%s", table_str, gain_type_str); + token = iniparser_getstring(dict, key, NULL); + if (token) { + double *modifier_gain = pa_xmalloc0(sizeof(double)); + *modifier_gain = atof(token); + if (!m->volume_modifiers) + m->volume_modifiers = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + pa_hashmap_put(m->volume_modifiers, (void*)gain_type_str, modifier_gain); + } else { + pa_log_warn("[%s] is not defined, skip it", key); + } + } else { + pa_log_error("failed to pa_xmalloc0()"); + ret = -1; + goto FAILURE; + } + if (key) { + free(key); + key = NULL; + } + } + +FAILURE: + if (key) + free(key); + if (dict) + iniparser_freedict(dict); + + return ret; +} + +static int is_hal_volume_by_type(pa_stream_manager *m, const char *volume_type, pa_bool_t *is_hal_volume) { + volume_info *v = NULL; + pa_assert(m); + pa_assert(volume_type); + pa_assert(is_hal_volume); + + if (m->volume_infos) { + v = pa_hashmap_get(m->volume_infos, volume_type); + if (v) + *is_hal_volume = v->is_hal_volume_type; + else + return -1; + } else + return -1; + + return 0; +} + +static int get_volume_value(pa_stream_manager *m, stream_type_t stream_type, pa_bool_t is_hal_volume, const char *volume_type, uint32_t volume_level, double *volume_value) { + int ret = 0; + double volume_linear = 1.0; + pa_assert(m); + pa_assert(volume_type); + pa_assert(volume_value); + + /* Get volume value by type & level */ + if (is_hal_volume) { + /* Get value from HAL */ + if (pa_hal_manager_get_volume_value(m->hal, volume_type, NULL, (stream_type==STREAM_SINK_INPUT)?DIRECTION_OUT:DIRECTION_IN, volume_level, &volume_linear)) { + ret = -1; + goto FAILURE; + } + } else { + volume_info *v = NULL; + void *volumes = m->volume_infos; + /* Get value from stream-manager */ + if (volumes) { + v = pa_hashmap_get(volumes, volume_type); + if (v && v->values[(stream_type==STREAM_SINK_INPUT)?STREAM_DIRECTION_OUT:STREAM_DIRECTION_IN].idx_volume_values) { + double *value = NULL; + value = pa_idxset_get_by_index(v->values[(stream_type==STREAM_SINK_INPUT)?STREAM_DIRECTION_OUT:STREAM_DIRECTION_IN].idx_volume_values, volume_level); + if (value) { + volume_linear = *value; + /* Apply master volume */ + v = pa_hashmap_get(volumes, MASTER_VOLUME_TYPE); + if (v && !v->is_hal_volume_type) + volume_linear *= (double)(v->values[(stream_type==STREAM_SINK_INPUT)?STREAM_DIRECTION_OUT:STREAM_DIRECTION_IN].current_level)/100.0; + } else { + pa_log_error("failed to pa_idxset_get_by_index()"); + ret = -1; + goto FAILURE; + } + } else { + pa_log_error("could not get volume value for stream_type[%d], volume_type[%s], level[%u]", stream_type, volume_type, volume_level); + ret = -1; + goto FAILURE; + } + } else { + pa_log_error("could not get volumes in volume infos, stream_type(%d), volume_type(%s)", stream_type, volume_type); + ret = -1; + goto FAILURE; + } + } + + *volume_value = volume_linear; + + pa_log_debug("get_volume_value() : stream_type[%d], volume_type[%s], level[%u], value[%f]", + stream_type, volume_type, volume_level, volume_linear); +FAILURE: + return ret; +} + +int32_t set_volume_level_by_type(pa_stream_manager *m, stream_type_t stream_type, const char *volume_type, uint32_t volume_level) { + pa_bool_t is_hal_volume = FALSE; + volume_info *v = NULL; + double volume_linear = 1.0; + double *modifier_gain_value = NULL; + uint32_t idx = 0; + const char *volume_type_str = NULL; + const char *modifier_gain = NULL; + void *s = NULL; + pa_hashmap *volumes = NULL; + pa_cvolume cv; + pa_assert(m); + pa_assert(volume_type); + + /* Check if it is related to HAL volume */ + if (is_hal_volume_by_type(m, volume_type, &is_hal_volume)) { + pa_log_error("failed to is_hal_volume_by_type(), volume_type(%s)", volume_type); + return -1; + } + + if (is_hal_volume) + if (pa_hal_manager_set_volume_level(m->hal, volume_type, (stream_type==STREAM_SINK_INPUT)?DIRECTION_OUT:DIRECTION_IN, volume_level)) + return -1; + + volumes = m->volume_infos; + if (volumes) { + v = pa_hashmap_get(volumes, volume_type); + if (v) { + if (pa_streq(volume_type, MASTER_VOLUME_TYPE) && MASTER_VOLUME_LEVEL_MAX < volume_level) { + pa_log_error("could not set volume level of MASTER type, out of range(%u)", volume_level); + return -1; + } + v->values[(stream_type==STREAM_SINK_INPUT)?STREAM_DIRECTION_OUT:STREAM_DIRECTION_IN].current_level = volume_level; + if (is_hal_volume && pa_streq(volume_type, MASTER_VOLUME_TYPE)) { + /* no need to update the value of pulseaudio stream */ + return 0; + } + } else { + pa_log_error("could not get volume_info, stream_type(%d), volume_type(%s)", stream_type, volume_type); + return -1; + } + } else { + pa_log_error("could not get volumes in volume infos, stream_type(%d), volume_type(%s)", stream_type, volume_type); + return -1; + } + + if (!pa_streq(volume_type, MASTER_VOLUME_TYPE)) { + if (get_volume_value(m, stream_type, is_hal_volume, volume_type, volume_level, &volume_linear)) + return -1; + + PA_IDXSET_FOREACH(s, stream_type==STREAM_SINK_INPUT?m->core->sink_inputs:m->core->source_outputs, idx) { + if ((volume_type_str = pa_proplist_gets(GET_STREAM_PROPLIST(s, stream_type), PA_PROP_MEDIA_TIZEN_VOLUME_TYPE))) { + /* Get modifier for gain */ + modifier_gain = pa_proplist_gets(GET_STREAM_PROPLIST(s, stream_type), PA_PROP_MEDIA_TIZEN_VOLUME_GAIN_TYPE); + } else { + continue; + } + /* Update volume level of stream if it has requested the volume type */ + if (pa_streq(volume_type_str, volume_type)) { + if (modifier_gain) { + if (m->volume_modifiers) { + modifier_gain_value = pa_hashmap_get(m->volume_modifiers, modifier_gain); + if (modifier_gain_value) { + volume_linear *= (*modifier_gain_value); + pa_log_info("set_volume_level_by_type() : apply the modifier for the gain value(%s=>%f), result volume_linear(%f)", + modifier_gain, *modifier_gain_value, volume_linear); + } + } + } + pa_cvolume_set(&cv, GET_STREAM_SAMPLE_SPEC(s, stream_type).channels, pa_sw_volume_from_linear(volume_linear)); + if (stream_type == STREAM_SINK_INPUT) + pa_sink_input_set_volume((pa_sink_input*)s, &cv, TRUE, TRUE); + else if (stream_type == STREAM_SOURCE_OUTPUT) + pa_source_output_set_volume((pa_source_output*)s, &cv, TRUE, TRUE); + } + } + } else { + PA_IDXSET_FOREACH(s, stream_type==STREAM_SINK_INPUT?m->core->sink_inputs:m->core->source_outputs, idx) { + if ((volume_type_str = pa_proplist_gets(GET_STREAM_PROPLIST(s, stream_type), PA_PROP_MEDIA_TIZEN_VOLUME_TYPE))) { + /* Get modifier for gain */ + modifier_gain = pa_proplist_gets(GET_STREAM_PROPLIST(s, stream_type), PA_PROP_MEDIA_TIZEN_VOLUME_GAIN_TYPE); + /* Check if it is related to HAL volume */ + if (is_hal_volume_by_type(m, volume_type_str, &is_hal_volume)) { + pa_log_error("failed to is_hal_volume_by_type(), volume_type(%s)", volume_type_str); + continue; + } + /* Get volume level of this type */ + v = pa_hashmap_get(volumes, volume_type_str); + if (v) + volume_level = v->values[(stream_type==STREAM_SINK_INPUT)?STREAM_DIRECTION_OUT:STREAM_DIRECTION_IN].current_level; + else + continue; + } else { + continue; + } + if (get_volume_value(m, stream_type, is_hal_volume, volume_type_str, volume_level, &volume_linear)) + continue; + + if (modifier_gain) { + if (m->volume_modifiers) { + modifier_gain_value = pa_hashmap_get(m->volume_modifiers, modifier_gain); + if (modifier_gain_value) { + volume_linear *= (*modifier_gain_value); + pa_log_info("set_volume_level_by_type() : apply the modifier for the gain value(%s=>%f), result volume_linear(%f)", + modifier_gain, *modifier_gain_value, volume_linear); + } + } + } + pa_cvolume_set(&cv, GET_STREAM_SAMPLE_SPEC(s, stream_type).channels, pa_sw_volume_from_linear(volume_linear)); + if (stream_type == STREAM_SINK_INPUT) + pa_sink_input_set_volume((pa_sink_input*)s, &cv, TRUE, TRUE); + else if (stream_type == STREAM_SOURCE_OUTPUT) + pa_source_output_set_volume((pa_source_output*)s, &cv, TRUE, TRUE); + } + } + + pa_log_debug("set_volume_level_by_type() : stream_type[%d], volume_type[%s], level[%u], value[%f]", + stream_type, volume_type, volume_level, volume_linear); + + return 0; +} + +int32_t get_volume_level_by_type(pa_stream_manager *m, pa_volume_get_command_t command, stream_type_t stream_type, const char *volume_type, uint32_t *volume_level) { + int32_t ret = 0; + pa_bool_t is_hal_volume = FALSE; + pa_hashmap *volumes = NULL; + pa_assert(m); + pa_assert(volume_type); + + /* Check if it is related to HAL volume */ + if (is_hal_volume_by_type(m, volume_type, &is_hal_volume)) { + pa_log_error("failed to is_hal_volume_by_type(), volume_type(%s)", volume_type); + return -1; + } + + if (command == GET_VOLUME_CURRENT_LEVEL) { + /* Get level */ + if (is_hal_volume) { + /* from HAL */ + if (pa_hal_manager_get_volume_level(m->hal, volume_type, (stream_type==STREAM_SINK_INPUT)?DIRECTION_OUT:DIRECTION_IN, volume_level)) + ret = -1; + } else { + /* from stream-manager */ + volumes = m->volume_infos; + if (volumes) { + volume_info *v = pa_hashmap_get(volumes, volume_type); + if (v) + *volume_level = v->values[(stream_type==STREAM_SINK_INPUT)?STREAM_DIRECTION_OUT:STREAM_DIRECTION_IN].current_level; + else { + pa_log_error("could not get volume_info, stream_type(%d), volume_type(%s)", stream_type, volume_type); + return -1; + } + } else { + pa_log_error("could not get volumes in volume infos, stream_type(%d), volume_type(%s)", stream_type, volume_type); + return -1; + } + } + } else if (command == GET_VOLUME_MAX_LEVEL) { + /* If it is the master volume type */ + if (pa_streq(volume_type, MASTER_VOLUME_TYPE)) { + *volume_level = MASTER_VOLUME_LEVEL_MAX; + } else { + /* Get max level */ + if (is_hal_volume) { + /* from HAL */ + if (pa_hal_manager_get_volume_level_max(m->hal, volume_type, (stream_type==STREAM_SINK_INPUT)?DIRECTION_OUT:DIRECTION_IN, volume_level)) + ret = -1; + } else { + /* from stream-manager */ + volumes = m->volume_infos; + if (volumes) { + volume_info *v = pa_hashmap_get(volumes, volume_type); + if (v && v->values[(stream_type==STREAM_SINK_INPUT)?STREAM_DIRECTION_OUT:STREAM_DIRECTION_IN].idx_volume_values) + *volume_level = pa_idxset_size(v->values[(stream_type==STREAM_SINK_INPUT)?STREAM_DIRECTION_OUT:STREAM_DIRECTION_IN].idx_volume_values); + else { + pa_log_error("could not get volume_info, stream_type(%d), volume_type(%s)", stream_type, volume_type); + return -1; + } + } else { + pa_log_error("could not get volumes in volume infos, stream_type(%d), volume_type(%s)", stream_type, volume_type); + return -1; + } + } + } + } + + return ret; +} + +int32_t set_volume_level_by_idx(pa_stream_manager *m, stream_type_t stream_type, uint32_t idx, uint32_t volume_level) { + pa_bool_t is_hal_volume = FALSE; + void *s = NULL; + pa_cvolume cv; + double volume_linear = 1.0; + const char *volume_type_str = NULL; + const char *modifier_gain = NULL; + pa_assert(m); + + s = pa_idxset_get_by_index(stream_type==STREAM_SINK_INPUT?m->core->sink_inputs:m->core->source_outputs, idx); + if ((volume_type_str = pa_proplist_gets(GET_STREAM_PROPLIST(s, stream_type), PA_PROP_MEDIA_TIZEN_VOLUME_TYPE))) { + /* Get modifier for gain */ + modifier_gain = pa_proplist_gets(GET_STREAM_PROPLIST(s, stream_type), PA_PROP_MEDIA_TIZEN_VOLUME_GAIN_TYPE); + } else { + pa_log_debug("idx[%u] doesn't have volume type", idx); + return -1; + } + + /* Check if it is related to HAL volume */ + if (is_hal_volume_by_type(m, volume_type_str, &is_hal_volume)) { + pa_log_error("failed to is_hal_volume_by_type(), volume_type(%s)", volume_type_str); + return -1; + } + + if (is_hal_volume) + if (pa_hal_manager_set_volume_level(m->hal, volume_type_str, (stream_type==STREAM_SINK_INPUT)?DIRECTION_OUT:DIRECTION_IN, volume_level)) + return -1; + + if (!get_volume_value(m, stream_type, is_hal_volume, volume_type_str, volume_level, &volume_linear)) { + if (modifier_gain) { + double *modifier_gain_value = NULL; + modifier_gain_value = pa_hashmap_get(m->volume_modifiers, modifier_gain); + if (modifier_gain_value) { + volume_linear *= (*modifier_gain_value); + pa_log_info("set_volume_level_by_idx() : apply the modifier for the gain value(%s=>%f), result volume_linear(%f)", + modifier_gain, *modifier_gain_value, volume_linear); + } + } + pa_cvolume_set(&cv, GET_STREAM_SAMPLE_SPEC(s, stream_type).channels, pa_sw_volume_from_linear(volume_linear)); + if (stream_type == STREAM_SINK_INPUT) + pa_sink_input_set_volume((pa_sink_input*)s, &cv, TRUE, TRUE); + else if (stream_type == STREAM_SOURCE_OUTPUT) + pa_source_output_set_volume((pa_source_output*)s, &cv, TRUE, TRUE); + } + pa_log_debug("set_volume_level_by_idx() : stream_type[%d], idx[%u]=>volume_type[%s], level[%u], value[%f]", + stream_type, idx, volume_type_str, volume_level, volume_linear); + + return 0; +} + +int32_t set_volume_level_with_new_data(pa_stream_manager *m, stream_type_t stream_type, void *nd, uint32_t volume_level) { + pa_bool_t is_hal_volume = FALSE; + pa_cvolume cv; + double volume_linear = 1.0; + const char *volume_type_str = NULL; + const char *modifier_gain = NULL; + pa_assert(m); + + if ((volume_type_str = pa_proplist_gets(GET_STREAM_NEW_PROPLIST(nd, stream_type), PA_PROP_MEDIA_TIZEN_VOLUME_TYPE))) { + /* Get modifier for gain */ + modifier_gain = pa_proplist_gets(GET_STREAM_NEW_PROPLIST(nd, stream_type), PA_PROP_MEDIA_TIZEN_VOLUME_GAIN_TYPE); + } else { + pa_log_debug("new_data[%p] doesn't have volume type", nd); + return -1; + } + + /* Check if it is related to HAL volume */ + if (is_hal_volume_by_type(m, volume_type_str, &is_hal_volume)) { + pa_log_error("failed to is_hal_volume_by_type(), volume_type(%s)", volume_type_str); + return -1; + } + + if (is_hal_volume) + if (pa_hal_manager_set_volume_level(m->hal, volume_type_str, (stream_type==STREAM_SINK_INPUT)?DIRECTION_OUT:DIRECTION_IN, volume_level)) + return -1; + + if (!get_volume_value(m, stream_type, is_hal_volume, volume_type_str, volume_level, &volume_linear)) { + if (modifier_gain) { + double *modifier_gain_value = NULL; + modifier_gain_value = pa_hashmap_get(m->volume_modifiers, modifier_gain); + if (modifier_gain_value) { + volume_linear *= (*modifier_gain_value); + pa_log_info("set_volume_level_by_idx() : apply the modifier for the gain value(%s=>%f), result volume_linear(%f)", + modifier_gain, *modifier_gain_value, volume_linear); + } + } + pa_cvolume_set(&cv, GET_STREAM_NEW_SAMPLE_SPEC(nd, stream_type).channels, pa_sw_volume_from_linear(volume_linear)); + if (stream_type == STREAM_SINK_INPUT) + pa_sink_input_new_data_set_volume((pa_sink_input_new_data*)nd, &cv, TRUE); + else if (stream_type == STREAM_SOURCE_OUTPUT) + pa_source_output_new_data_set_volume((pa_source_output_new_data*)nd, &cv, TRUE); + } + pa_log_debug("set_volume_level_with_new_data() : stream_type[%d], volume_type[%s], level[%u], value[%f]", + stream_type, volume_type_str, volume_level, volume_linear); + + return 0; +} + +int32_t set_volume_mute_by_type(pa_stream_manager *m, stream_type_t stream_type, const char *volume_type, pa_bool_t volume_mute) { + pa_bool_t is_hal_volume = FALSE; + volume_info *v = NULL; + void *s = NULL; + pa_hashmap *volumes = NULL; + uint32_t idx; + const char *volume_type_str = NULL; + pa_assert(m); + pa_assert(volume_type); + + /* Check if it is related to HAL volume */ + if (is_hal_volume_by_type(m, volume_type, &is_hal_volume)) { + pa_log_error("failed to is_hal_volume_by_type(), volume_type(%s)", volume_type); + return -1; + } + + if (is_hal_volume) + if (pa_hal_manager_set_mute(m->hal, volume_type, (stream_type==STREAM_SINK_INPUT)?DIRECTION_OUT:DIRECTION_IN, (uint32_t)volume_mute)) + return -1; + + /* Set mute */ + volumes = m->volume_infos; + if (volumes) { + v = pa_hashmap_get(volumes, volume_type); + if (v) + v->values[(stream_type==STREAM_SINK_INPUT)?STREAM_DIRECTION_OUT:STREAM_DIRECTION_IN].is_muted = volume_mute; + else { + pa_log_error("could not get volume_info, stream_type(%d), volume_type(%s)", stream_type, volume_type); + return -1; + } + } else { + pa_log_error("could not get volumes in volume infos, volume_type(%s)", volume_type); + return -1; + } + + PA_IDXSET_FOREACH(s, (stream_type==STREAM_SINK_INPUT)?m->core->sink_inputs:m->core->source_outputs, idx) { + if ((volume_type_str = pa_proplist_gets(GET_STREAM_PROPLIST(s, stream_type), PA_PROP_MEDIA_TIZEN_VOLUME_TYPE))) { + /* do nothing */ + } else { + continue; + } + /* Update mute of stream if it has requested the volume type */ + if (pa_streq(volume_type_str, volume_type)) { + if (stream_type == STREAM_SINK_INPUT) + pa_sink_input_set_mute((pa_sink_input*)s, volume_mute, TRUE); + else if (stream_type == STREAM_SOURCE_OUTPUT) + pa_source_output_set_mute((pa_source_output*)s, volume_mute, TRUE); + } + } + + pa_log_info("pa_stream_manager_volume_set_mute, stream_type:%d volume_type:%s mute:%d", stream_type, volume_type, volume_mute); + + return 0; +} + +int32_t get_volume_mute_by_type(pa_stream_manager *m, stream_type_t stream_type, const char *volume_type, pa_bool_t *volume_mute) { + volume_info *v = NULL; + pa_hashmap *volumes = NULL; + pa_assert(m); + pa_assert(volume_type); + pa_assert(volume_mute); + + /* Get mute */ + volumes = m->volume_infos; + if (volumes) { + v = pa_hashmap_get(volumes, volume_type); + if (v) + *volume_mute = v->values[(stream_type==STREAM_SINK_INPUT)?STREAM_DIRECTION_OUT:STREAM_DIRECTION_IN].is_muted; + else { + pa_log_error("could not get volume_info, stream_type(%d), volume_type(%s)", stream_type, volume_type); + return -1; + } + } else { + pa_log_error("could not get volumes in volume infos, volume_type(%s)", volume_type); + return -1; + } + + pa_log_info("pa_stream_manager_volume_get_mute, volume_type:%s mute:%d", volume_type, *volume_mute); + + return 0; +} + +int32_t set_volume_mute_by_idx(pa_stream_manager *m, stream_type_t stream_type, uint32_t stream_idx, pa_bool_t volume_mute) { + void *s = NULL; + uint32_t idx = 0; + const char *volume_type_str = NULL; + volume_info *v = NULL; + pa_hashmap *volumes = NULL; + pa_bool_t muted_by_type = FALSE; + pa_assert(m); + + pa_log_info("set_volume_mute_by_idx, stream_type:%d stream_idx:%u mute:%d", stream_type, stream_idx, volume_mute); + + if (stream_idx != (uint32_t)-1) { + if ((s = pa_idxset_get_by_index((stream_type==STREAM_SINK_INPUT)?m->core->sink_inputs:m->core->source_outputs, stream_idx))) { + if ((volume_type_str = pa_proplist_gets(GET_STREAM_PROPLIST(s, stream_type), PA_PROP_MEDIA_TIZEN_VOLUME_TYPE))) { + /* do nothing */ + } else { + pa_log_warn("stream[%d] doesn't have volume type", stream_idx); + } + } else { + pa_log_warn("stream[%u] doesn't exist", stream_idx); + } + } + + /* Get mute state of the volume type of this stream */ + if (volume_type_str) { + volumes = m->volume_infos; + if (volumes) { + v = pa_hashmap_get(volumes, volume_type_str); + if (v) + muted_by_type = v->values[(stream_type==STREAM_SINK_INPUT)?STREAM_DIRECTION_OUT:STREAM_DIRECTION_IN].is_muted; + } else { + pa_log_error("could not get volumes in volume map, stream_type(%d), volume_type(%s)", stream_type, volume_type_str); + return -1; + } + } + + if (!muted_by_type) { + PA_IDXSET_FOREACH(s, (stream_type==STREAM_SINK_INPUT)?m->core->sink_inputs:m->core->source_outputs, idx) { + /* Update mute of the stream if it has requested idx */ + if (stream_idx == idx) { + if (stream_type == STREAM_SINK_INPUT) + pa_sink_input_set_mute((pa_sink_input*)s, volume_mute, TRUE); + else if (stream_type == STREAM_SOURCE_OUTPUT) + pa_source_output_set_mute((pa_source_output*)s, volume_mute, TRUE); + break; + } + } + } + + return 0; +} + +int32_t get_volume_mute_by_idx(pa_stream_manager *m, stream_type_t stream_type, uint32_t stream_idx, pa_bool_t *volume_mute) { + int32_t ret = 0; + void *s = NULL; + uint32_t idx = 0; + pa_assert(m); + pa_assert(volume_mute); + + PA_IDXSET_FOREACH(s, (stream_type==STREAM_SINK_INPUT)?m->core->sink_inputs:m->core->source_outputs, idx) { + /* Update mute of the stream if it has requested idx */ + if (stream_idx == idx) { + if (stream_type == STREAM_SINK_INPUT) + *volume_mute = ((pa_sink_input*)s)->muted; + else if (stream_type == STREAM_SOURCE_OUTPUT) + *volume_mute = ((pa_source_output*)s)->muted; + break; + } + } + if (!s) + ret = -1; + + pa_log_info("get_volume_mute_by_idx, stream_type:%d stream_idx:%u mute:%d, ret:%d", stream_type, stream_idx, *volume_mute, ret); + + return ret; +} + +int32_t set_volume_mute_with_new_data(pa_stream_manager *m, stream_type_t stream_type, void *nd, pa_bool_t volume_mute) { + pa_bool_t is_hal_volume = FALSE; + const char *volume_type_str = NULL; + pa_assert(m); + + pa_log_info("set_volume_mute_with_new_data, stream_type:%d mute:%d", stream_type, volume_mute); + + if ((volume_type_str = pa_proplist_gets(GET_STREAM_NEW_PROPLIST(nd, stream_type), PA_PROP_MEDIA_TIZEN_VOLUME_TYPE))) { + /* do nothing */ + } else { + pa_log_debug("new_data[%p] doesn't have volume type", nd); + return -1; + } + + /* Check if it is related to HAL volume */ + if (is_hal_volume_by_type(m, volume_type_str, &is_hal_volume)) { + pa_log_error("failed to is_hal_volume_by_type(), volume_type(%s)", volume_type_str); + return -1; + } + + if (is_hal_volume) + if (pa_hal_manager_set_mute(m->hal, volume_type_str, (stream_type==STREAM_SINK_INPUT)?DIRECTION_OUT:DIRECTION_IN, volume_mute)) + return -1; + + if (stream_type == STREAM_SINK_INPUT) + pa_sink_input_new_data_set_muted((pa_sink_input_new_data*)nd, volume_mute); + else if (stream_type == STREAM_SOURCE_OUTPUT) + pa_source_output_new_data_set_muted((pa_source_output_new_data*)nd, volume_mute); + + return 0; +} + +static void dump_volumes (pa_stream_manager *m) { + volume_info *s = NULL; + const char *volume_type = NULL; + const char *modifier_gain = NULL; + double *level = NULL; + double *gain_value = NULL; + void *state = NULL; + uint32_t idx = 0; + pa_assert(m); + + pa_log_debug("==========[START volume infos dump]=========="); + while (m->volume_infos && (s = pa_hashmap_iterate(m->volume_infos, &state, (const void**)&volume_type))) { + if (s) { + pa_log_debug("[volume_type : %s]", volume_type); + pa_log_debug(" - is_hal_volume_type : %d", s->is_hal_volume_type); + if (s->values[STREAM_DIRECTION_IN].idx_volume_values) { + pa_log_debug(" - (in)max level : %u", pa_idxset_size(s->values[STREAM_DIRECTION_IN].idx_volume_values)); + PA_IDXSET_FOREACH(level, s->values[STREAM_DIRECTION_IN].idx_volume_values, idx) + pa_log_debug(" - (in)value[%u] : %f", idx, *level); + } + if (s->values[STREAM_DIRECTION_OUT].idx_volume_values) { + pa_log_debug(" - (out)max level : %u", pa_idxset_size(s->values[STREAM_DIRECTION_OUT].idx_volume_values)); + PA_IDXSET_FOREACH(level, s->values[STREAM_DIRECTION_OUT].idx_volume_values, idx) + pa_log_debug(" - (out)value[%u] : %f", idx, *level); + } + } + } + state = NULL; + while (m->volume_modifiers && (gain_value = pa_hashmap_iterate(m->volume_modifiers, &state, (const void**)&modifier_gain))) + pa_log_debug("[modifier gain:%s, value:%f]", modifier_gain, *gain_value); + pa_log_debug("===========[END volume infos dump]==========="); + + return; +} + +int32_t init_volumes (pa_stream_manager *m) { + int ret = 0; + void *state = NULL; + volume_info *v = NULL; + pa_assert(m); + + /* For now, we only care about volumes for the output stream */ + ret = load_out_volume_conf_file(m); + if (ret) + pa_log_error("failed to load_out_volume_conf_file(), ret[%d]", ret); + + dump_volumes(m); + + /* Apply initial output volume level from vconf volume level */ + { + #define VCONF_ADDR_LEN 64 + char vconf_vol_type_addr[VCONF_ADDR_LEN] = {0,}; + const char *volume_type = NULL; + int level = 10; + state = NULL; + while ((v = pa_hashmap_iterate(m->volume_infos, &state, (const void**)&volume_type))) { + memset(vconf_vol_type_addr, 0, VCONF_ADDR_LEN); + pa_snprintf(vconf_vol_type_addr, VCONF_ADDR_LEN, "%s%s", VCONFKEY_OUT_VOLUME_PREFIX, volume_type); + if (vconf_get_int(vconf_vol_type_addr, &level)) + pa_log_error("failed to get volume level of the vconf[%s]", vconf_vol_type_addr); + else { + set_volume_level_by_type(m, STREAM_SINK_INPUT, volume_type, (uint32_t)level); + pa_log_debug("type(%s), current level(%u)", volume_type, v->values[STREAM_DIRECTION_OUT].current_level); + } + } + } +#if 0 + /* Apply initial output volume mute from vconf volume mute */ + { + + } +#endif + if (ret) + pa_log_error("Failed to initialize volumes"); + return ret; +} + +void deinit_volumes (pa_stream_manager *m) { + volume_info *v = NULL; + void *state = NULL; + uint32_t idx = 0; + double *level = NULL; + double *gain = NULL; + int i = 0; + pa_assert(m); + + if (m->volume_infos) { + PA_HASHMAP_FOREACH(v, m->volume_infos, state) { + for (i = 0; i < STREAM_DIRECTION_MAX; i++) { + if (v->values[i].idx_volume_values) { + PA_IDXSET_FOREACH(level, v->values[i].idx_volume_values, idx) + pa_xfree(level); + pa_idxset_free(v->values[i].idx_volume_values, NULL); + } + } + } + } + if (m->volume_modifiers) { + PA_HASHMAP_FOREACH(gain, m->volume_modifiers, state) + pa_xfree(gain); + pa_hashmap_free(m->volume_modifiers); + } + + return; +} + +int32_t pa_stream_manager_volume_get_max_level(pa_stream_manager *m, stream_type_t stream_type, const char *volume_type, uint32_t *volume_level) { + int32_t ret = 0; + pa_assert(m); + pa_assert(volume_type); + + ret = get_volume_level_by_type(m, GET_VOLUME_MAX_LEVEL, stream_type, volume_type, volume_level); + + pa_log_info("pa_stream_manager_volume_get_max_level, type:%s max_level:%u, ret:%d", volume_type, *volume_level, ret); + + return ret; +} + +int32_t pa_stream_manager_volume_get_level(pa_stream_manager *m, stream_type_t stream_type, const char *volume_type, uint32_t *volume_level) { + int32_t ret = 0; + pa_assert(m); + pa_assert(volume_type); + + ret = get_volume_level_by_type(m, GET_VOLUME_CURRENT_LEVEL, stream_type, volume_type, volume_level); + + pa_log_info("pa_stream_manager_volume_get_level, type:%s level:%u, ret:%d", volume_type, *volume_level, ret); + + return ret; +} + +int32_t pa_stream_manager_volume_set_level(pa_stream_manager *m, stream_type_t stream_type, const char *volume_type, uint32_t volume_level) { + int32_t ret = 0; + pa_assert(m); + pa_assert(volume_type); + + ret = set_volume_level_by_type(m, stream_type, volume_type, volume_level); + + pa_log_info(" pa_stream_manager_volume_set_level, type:%s level:%u, ret:%d", volume_type, volume_level, ret); + + return ret; +} + +int32_t pa_stream_manager_volume_get_mute(pa_stream_manager *m, stream_type_t stream_type, const char *volume_type, pa_bool_t *volume_mute) { + return get_volume_mute_by_type(m, stream_type, volume_type, volume_mute); +} + +int32_t pa_stream_manager_volume_set_mute(pa_stream_manager *m, stream_type_t stream_type, const char *volume_type, pa_bool_t volume_mute) { + return set_volume_mute_by_type(m, stream_type, volume_type, volume_mute); +} + +int32_t pa_stream_manager_volume_get_mute_by_idx(pa_stream_manager *m, stream_type_t stream_type, uint32_t stream_idx, pa_bool_t *volume_mute) { + return get_volume_mute_by_idx(m, stream_type, stream_idx, volume_mute); +} + +int32_t pa_stream_manager_volume_set_mute_by_idx(pa_stream_manager *m, stream_type_t stream_type, uint32_t stream_idx, pa_bool_t volume_mute) { + return set_volume_mute_by_idx(m, stream_type, stream_idx, volume_mute); +} diff --git a/src/stream-manager-volume.h b/src/stream-manager-volume.h new file mode 100644 index 0000000..e1d2718 --- /dev/null +++ b/src/stream-manager-volume.h @@ -0,0 +1,14 @@ +#ifndef foostreammanagervolumefoo +#define foostreammanagervolumefoo + +#include "stream-manager.h" + +int32_t pa_stream_manager_volume_get_level(pa_stream_manager *m, stream_type_t stream_type, const char *volume_type, uint32_t *volume_level); +int32_t pa_stream_manager_volume_set_level(pa_stream_manager *m, stream_type_t stream_type, const char *volume_type, uint32_t volume_level); +int32_t pa_stream_manager_volume_get_mute(pa_stream_manager *m, stream_type_t stream_type, const char *volume_type, pa_bool_t *volume_mute); +int32_t pa_stream_manager_volume_set_mute(pa_stream_manager *m, stream_type_t stream_type, const char *volume_type, pa_bool_t volume_mute); +int32_t pa_stream_manager_volume_get_max_level(pa_stream_manager *m, stream_type_t stream_type, const char *volume_type, uint32_t *volume_level); +int32_t pa_stream_manager_volume_get_mute_by_idx(pa_stream_manager *m, stream_type_t stream_type, uint32_t stream_idx, pa_bool_t *volume_mute); +int32_t pa_stream_manager_volume_set_mute_by_idx(pa_stream_manager *m, stream_type_t stream_type, uint32_t stream_idx, pa_bool_t volume_mute); + +#endif diff --git a/src/stream-manager.c b/src/stream-manager.c new file mode 100644 index 0000000..c229c0b --- /dev/null +++ b/src/stream-manager.c @@ -0,0 +1,2632 @@ +/*** + This file is part of PulseAudio. + + Copyright 2015 Sangchul Lee + + PulseAudio 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. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include "stream-manager.h" +#include "stream-manager-priv.h" +#include "stream-manager-volume-priv.h" + +#ifdef HAVE_DBUS +#define ARR_ARG_MAX 32 +#define STREAM_MANAGER_OBJECT_PATH "/org/pulseaudio/StreamManager" +#define STREAM_MANAGER_INTERFACE "org.pulseaudio.StreamManager" +#define STREAM_MANAGER_METHOD_NAME_GET_STREAM_INFO "GetStreamInfo" +#define STREAM_MANAGER_METHOD_NAME_GET_STREAM_LIST "GetStreamList" +#define STREAM_MANAGER_METHOD_NAME_SET_STREAM_ROUTE_DEVICES "SetStreamRouteDevices" +#define STREAM_MANAGER_METHOD_NAME_SET_STREAM_ROUTE_OPTION "SetStreamRouteOption" +#define STREAM_MANAGER_METHOD_NAME_SET_VOLUME_LEVEL "SetVolumeLevel" +#define STREAM_MANAGER_METHOD_NAME_GET_VOLUME_LEVEL "GetVolumeLevel" +#define STREAM_MANAGER_METHOD_NAME_GET_VOLUME_MAX_LEVEL "GetVolumeMaxLevel" +#define STREAM_MANAGER_METHOD_NAME_SET_VOLUME_MUTE "SetVolumeMute" +#define STREAM_MANAGER_METHOD_NAME_GET_VOLUME_MUTE "GetVolumeMute" +#define STREAM_MANAGER_METHOD_NAME_GET_CURRENT_VOLUME_TYPE "GetCurrentVolumeType" /* the type that belongs to the stream of the current max priority */ +#define STREAM_MANAGER_METHOD_NAME_UPDATE_FOCUS_STATUS "UpdateFocusStatus" + +static DBusHandlerResult method_handler_for_vt(DBusConnection *c, DBusMessage *m, void *userdata); +static DBusHandlerResult handle_introspect(DBusConnection *conn, DBusMessage *msg, void *userdata); +static DBusHandlerResult handle_methods(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_stream_info(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_stream_list(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_set_stream_route_devices(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_set_stream_route_option(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_set_volume_level(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_volume_level(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_volume_max_level(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_set_volume_mute(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_volume_mute(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_current_volume_type(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_update_focus_status(DBusConnection *conn, DBusMessage *msg, void *userdata); + +enum method_handler_index { + METHOD_HANDLER_GET_STREAM_INFO, + METHOD_HANDLER_GET_STREAM_LIST, + METHOD_HANDLER_SET_STREAM_ROUTE_DEVICES, + METHOD_HANDLER_SET_STREAM_ROUTE_OPTION, + METHOD_HANDLER_SET_VOLUME_LEVEL, + METHOD_HANDLER_GET_VOLUME_LEVEL, + METHOD_HANDLER_GET_VOLUME_MAX_LEVEL, + METHOD_HANDLER_SET_VOLUME_MUTE, + METHOD_HANDLER_GET_VOLUME_MUTE, + METHOD_HANDLER_GET_CURRENT_VOLUME_TYPE, + METHOD_HANDLER_UPDATE_FOCUS_STATUS, + METHOD_HANDLER_MAX +}; + +static pa_dbus_arg_info get_stream_info_args[] = { { "stream_type", "s", "in" }, + { "priority", "i", "out" }, + { "route_type", "i", "out" }, + { "avail_in_devices", "as", "out" }, + { "avail_out_devices", "as", "out" }, + { "avail_frameworks", "as", "out"} }; +static pa_dbus_arg_info get_stream_list_args[] = { { "stream_type", "as", "out" }, + { "priority", "ai", "out" } }; +static pa_dbus_arg_info set_stream_route_devices_args[] = { { "parent_id", "u", "in" }, + { "route_in_devices", "au", "in" }, + { "route_out_devices", "au", "in" }, + { "ret_msg", "s", "out" } }; +static pa_dbus_arg_info set_stream_route_option_args[] = { { "parent_id", "u", "in" }, + { "name", "s", "in" }, + { "value", "i", "in" }, + { "ret_msg", "s", "out" } }; +static pa_dbus_arg_info set_volume_level_args[] = { { "io_direction", "s", "in" }, + { "type", "s", "in" }, + { "level", "u", "in" }, + { "ret_msg", "s", "out" } }; +static pa_dbus_arg_info get_volume_level_args[] = { { "io_direction", "s", "in" }, + { "type", "s", "in" }, + { "level", "u", "out" }, + { "ret_msg", "s", "out" } }; +static pa_dbus_arg_info get_volume_max_level_args[] = { { "io_direction", "s", "in" }, + { "type", "s", "in" }, + { "level", "u", "out" }, + { "ret_msg", "s", "out" } }; +static pa_dbus_arg_info set_volume_mute_args[] = { { "io_direction", "s", "in" }, + { "type", "s", "in" }, + { "on/off", "u", "in" }, + { "ret_msg", "s", "out" } }; +static pa_dbus_arg_info get_volume_mute_args[] = { { "io_direction", "s", "in" }, + { "type", "s", "in" }, + { "on/off", "u", "out" }, + { "ret_msg", "s", "out" } }; +static pa_dbus_arg_info get_current_volume_type_args[] = { { "io_direction", "s", "in" }, + { "type", "s", "out" }, + { "ret_msg", "s", "out" } }; +static pa_dbus_arg_info update_focus_status_args[] = { { "parent_id", "u", "in" }, + { "focus_status", "u", "in" }, + { "ret_msg", "s", "out" } }; +static const char* signature_args_for_in[] = { "s", "", "uauau", "usi", "ssu", "ss", "ss", "ssu", "ss", "s", "uu"}; + +static pa_dbus_method_handler method_handlers[METHOD_HANDLER_MAX] = { + [METHOD_HANDLER_GET_STREAM_INFO] = { + .method_name = STREAM_MANAGER_METHOD_NAME_GET_STREAM_INFO, + .arguments = get_stream_info_args, + .n_arguments = sizeof(get_stream_info_args) / sizeof(pa_dbus_arg_info), + .receive_cb = handle_get_stream_info }, + [METHOD_HANDLER_GET_STREAM_LIST] = { + .method_name = STREAM_MANAGER_METHOD_NAME_GET_STREAM_LIST, + .arguments = get_stream_list_args, + .n_arguments = sizeof(get_stream_list_args) / sizeof(pa_dbus_arg_info), + .receive_cb = handle_get_stream_list }, + [METHOD_HANDLER_SET_STREAM_ROUTE_DEVICES] = { + .method_name = STREAM_MANAGER_METHOD_NAME_SET_STREAM_ROUTE_DEVICES, + .arguments = set_stream_route_devices_args, + .n_arguments = sizeof(set_stream_route_devices_args) / sizeof(pa_dbus_arg_info), + .receive_cb = handle_set_stream_route_devices }, + [METHOD_HANDLER_SET_STREAM_ROUTE_OPTION] = { + .method_name = STREAM_MANAGER_METHOD_NAME_SET_STREAM_ROUTE_OPTION, + .arguments = set_stream_route_option_args, + .n_arguments = sizeof(set_stream_route_option_args) / sizeof(pa_dbus_arg_info), + .receive_cb = handle_set_stream_route_option }, + [METHOD_HANDLER_SET_VOLUME_LEVEL] = { + .method_name = STREAM_MANAGER_METHOD_NAME_SET_VOLUME_LEVEL, + .arguments = set_volume_level_args, + .n_arguments = sizeof(set_volume_level_args) / sizeof(pa_dbus_arg_info), + .receive_cb = handle_set_volume_level }, + [METHOD_HANDLER_GET_VOLUME_LEVEL] = { + .method_name = STREAM_MANAGER_METHOD_NAME_GET_VOLUME_LEVEL, + .arguments = get_volume_level_args, + .n_arguments = sizeof(get_volume_level_args) / sizeof(pa_dbus_arg_info), + .receive_cb = handle_get_volume_level }, + [METHOD_HANDLER_GET_VOLUME_MAX_LEVEL] = { + .method_name = STREAM_MANAGER_METHOD_NAME_GET_VOLUME_MAX_LEVEL, + .arguments = get_volume_max_level_args, + .n_arguments = sizeof(get_volume_max_level_args) / sizeof(pa_dbus_arg_info), + .receive_cb = handle_get_volume_max_level }, + [METHOD_HANDLER_SET_VOLUME_MUTE] = { + .method_name = STREAM_MANAGER_METHOD_NAME_SET_VOLUME_MUTE, + .arguments = set_volume_mute_args, + .n_arguments = sizeof(set_volume_mute_args) / sizeof(pa_dbus_arg_info), + .receive_cb = handle_set_volume_mute }, + [METHOD_HANDLER_GET_VOLUME_MUTE] = { + .method_name = STREAM_MANAGER_METHOD_NAME_GET_VOLUME_MUTE, + .arguments = get_volume_mute_args, + .n_arguments = sizeof(get_volume_mute_args) / sizeof(pa_dbus_arg_info), + .receive_cb = handle_get_volume_mute }, + [METHOD_HANDLER_GET_CURRENT_VOLUME_TYPE] = { + .method_name = STREAM_MANAGER_METHOD_NAME_GET_CURRENT_VOLUME_TYPE, + .arguments = get_current_volume_type_args, + .n_arguments = sizeof(get_current_volume_type_args) / sizeof(pa_dbus_arg_info), + .receive_cb = handle_get_current_volume_type }, + [METHOD_HANDLER_UPDATE_FOCUS_STATUS] = { + .method_name = STREAM_MANAGER_METHOD_NAME_UPDATE_FOCUS_STATUS, + .arguments = update_focus_status_args, + .n_arguments = sizeof(update_focus_status_args) / sizeof(pa_dbus_arg_info), + .receive_cb = handle_update_focus_status }, +}; + +const char *dbus_str_none = "none"; +const char* stream_manager_dbus_ret_str[] = {"STREAM_MANAGER_RETURN_OK","STREAM_MANAGER_RETURN_ERROR", "STREAM_MANAGER_RETURN_ERROR_NO_STREAM"}; +enum { + RET_MSG_INDEX_OK, + RET_MSG_INDEX_ERROR, + RET_MSG_INDEX_ERROR_NO_STREAM, +}; + +#ifdef USE_DBUS_PROTOCOL + +static pa_dbus_interface_info stream_manager_interface_info = { + .name = STREAM_MANAGER_INTERFACE, + .method_handlers = method_handlers, + .n_method_handlers = METHOD_HANDLER_MAX, + .property_handlers = , + .n_property_handlers = , + .get_all_properties_cb =, + .signals =, + .n_signals = +}; + +#else + +#define STREAM_MGR_INTROSPECT_XML \ + DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ + "" \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " "\ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + "" +#endif + +#endif + +#define STREAM_MANAGER_CLIENT_NAME "SOUND_MANAGER_STREAM_INFO" +#define DEFAULT_ROLE "media" +#define SKIP_ROLE "skip" + +/* There are some streams to be skipped. + * In other words, we do not care about streams that have a name of listed as below */ +#define NAME_FOR_SKIP_MAX 1 +const char* stream_manager_media_names_for_skip[NAME_FOR_SKIP_MAX] = {"pulsesink probe"}; + +#define STREAM_FOCUS_NONE "0" +#define STREAM_FOCUS_PLAYBACK "1" +#define STREAM_FOCUS_CAPTURE "2" + +typedef enum _focus_acquired_status { + STREAM_FOCUS_ACQUIRED_NONE = 0x00, + STREAM_FOCUS_ACQUIRED_PLAYBACK = 0x01, + STREAM_FOCUS_ACQUIRED_CAPTURE = 0x02, +} focus_acquired_status_t; + +typedef enum _process_stream_result { + PROCESS_STREAM_RESULT_OK, + PROCESS_STREAM_RESULT_STOP, + PROCESS_STREAM_RESULT_SKIP, +} process_stream_result_t; + +typedef enum _process_command_type { + PROCESS_COMMAND_PREPARE, + PROCESS_COMMAND_CHANGE_ROUTE_BY_STREAM_STARTED_WITH_NEW_DATA, + PROCESS_COMMAND_CHANGE_ROUTE_BY_STREAM_STARTED, + PROCESS_COMMAND_CHANGE_ROUTE_BY_STREAM_ENDED, + PROCESS_COMMAND_CHANGE_ROUTE_BY_STREAM_FOCUS_CHANGED, + PROCESS_COMMAND_UPDATE_VOLUME, + PROCESS_COMMAND_ADD_PARENT_ID, + PROCESS_COMMAND_REMOVE_PARENT_ID, +} process_command_type_t; + +typedef enum _notify_command_type { + NOTIFY_COMMAND_SELECT_PROPER_SINK_OR_SOURCE_FOR_INIT, + NOTIFY_COMMAND_CHANGE_ROUTE_START_WITH_NEW_DATA, + NOTIFY_COMMAND_CHANGE_ROUTE_START, + NOTIFY_COMMAND_CHANGE_ROUTE_END, + NOTIFY_COMMAND_UPDATE_ROUTE_OPTION, + NOTIFY_COMMAND_INFORM_STREAM_CONNECTED, + NOTIFY_COMMAND_INFORM_STREAM_DISCONNECTED, +} notify_command_type_t; + +const char* process_command_type_str[] = { + "PREPARE", + "CHANGE_ROUTE_BY_STREAM_STARTED_WITH_NEW_DATA", + "CHANGE_ROUTE_BY_STREAM_STARTED", + "CHANGE_ROUTE_BY_STREAM_ENDED", + "CHANGE_ROUTE_BY_STREAM_FOCUS_CHANGED", + "UPDATE_VOLUME", + "ADD_PARENT_ID", + "REMOVE_PARENT_ID", +}; + +const char* notify_command_type_str[] = { + "SELECT_PROPER_SINK_OR_SOURCE_FOR_INIT", + "CHANGE_ROUTE_START_WITH_NEW_DATA", + "CHANGE_ROUTE_START", + "CHANGE_ROUTE_END", + "UPDATE_ROUTE_OPTION", + "INFORM_STREAM_CONNECTED", + "INFORM_STREAM_DISCONNECTED", +}; + +#define STREAM_MAP_FILE "/etc/pulse/stream-map.json" +#define STREAM_MAP_VOLUMES "volumes" +#define STREAM_MAP_VOLUME_TYPE "type" +#define STREAM_MAP_VOLUME_IS_FOR_HAL "is-hal-volume" +#define STREAM_MAP_STREAMS "streams" +#define STREAM_MAP_STREAM_ROLE "role" +#define STREAM_MAP_STREAM_PRIORITY "priority" +#define STREAM_MAP_STREAM_ROUTE_TYPE "route-type" +#define STREAM_MAP_STREAM_DIRECTIONS "directions" +#define STREAM_MAP_STREAM_VOLUME_TYPES "volume-types" +#define STREAM_MAP_STREAM_VOLUME_TYPE_IN "in" +#define STREAM_MAP_STREAM_VOLUME_TYPE_OUT "out" +#define STREAM_MAP_STREAM_CAPTURE_VOLUME_TYPE "capture-volume-type" +#define STREAM_MAP_STREAM_PLAYBACK_VOLUME_TYPE "playback-volume-type" +#define STREAM_MAP_STREAM_AVAIL_IN_DEVICES "avail-in-devices" +#define STREAM_MAP_STREAM_AVAIL_OUT_DEVICES "avail-out-devices" +#define STREAM_MAP_STREAM_AVAIL_FRAMEWORKS "avail-frameworks" + +typedef struct _stream_parent { + pa_idxset *idx_sink_inputs; + pa_idxset *idx_source_outputs; + pa_idxset *idx_route_in_devices; + pa_idxset *idx_route_out_devices; + focus_acquired_status_t focus_status; +} stream_parent; + +#define AVAIL_DEVICES_MAX 16 +#define AVAIL_FRAMEWORKS_MAX 16 +#define AVAIL_STREAMS_MAX 32 +typedef struct _stream_info_per_type { + int32_t priority; + int32_t route_type; + int32_t num_of_in_devices; + int32_t num_of_out_devices; + int32_t num_of_frameworks; + char *avail_in_devices[AVAIL_DEVICES_MAX]; + char *avail_out_devices[AVAIL_DEVICES_MAX]; + char *avail_frameworks[AVAIL_FRAMEWORKS_MAX]; +} stream_info_per_type; +typedef struct _stream_list { + int32_t num_of_streams; + char* types[AVAIL_STREAMS_MAX]; + int32_t priorities[AVAIL_STREAMS_MAX]; +} stream_list; +typedef struct _stream_route_option { + const char *name; + int32_t value; +} stream_route_option; + +static void do_notify(pa_stream_manager *m, notify_command_type_t command, stream_type_t type, void *user_data); +static process_stream_result_t process_stream(stream_type_t type, void *stream, process_command_type_t command, pa_stream_manager *m); + +static int get_available_streams(pa_stream_manager *m, stream_list *list) { + void *state = NULL; + stream_info *s = NULL; + char *role = NULL; + int i = 0; + + pa_log_info("get_available_streams"); + if (m->stream_infos) { + while ((s = pa_hashmap_iterate(m->stream_infos, &state, (const void**)&role))) { + if (i < AVAIL_STREAMS_MAX) { + list->priorities[i] = s->priority; + list->types[i++] = role; + pa_log_debug(" [%d] stream_type[%s], priority[%d]", i-1, role, s->priority); + } else { + pa_log_error(" out of range, [%d]", i); + break; + } + } + list->num_of_streams = i; + pa_log_debug(" num_of_streams[%d]",i); + } else { + pa_log_error("stream_map is not initialized.."); + return -1; + } + return 0; +} + +static int get_stream_info(pa_stream_manager *m, const char *stream_role, stream_info_per_type *info) { + uint32_t idx = 0; + char *name; + stream_info *s = NULL; + int i = 0; + int j = 0; + int k = 0; + pa_log_info("get_stream_info : role[%s]", stream_role); + if (m->stream_infos) { + s = pa_hashmap_get(m->stream_infos, stream_role); + if (s) { + info->priority = s->priority; + info->route_type = s->route_type; + PA_IDXSET_FOREACH(name, s->idx_avail_in_devices, idx) { + pa_log_debug(" avail-in-device[%d] name : %s", i, name); + if (i < AVAIL_DEVICES_MAX) + info->avail_in_devices[i++] = name; + else + pa_log_error(" avail-in-devices, out of range, [%d]", i); + } + info->num_of_in_devices = i; + PA_IDXSET_FOREACH(name, s->idx_avail_out_devices, idx) { + pa_log_debug(" avail-out-device[%d] name : %s", j, name); + if (j < AVAIL_DEVICES_MAX) + info->avail_out_devices[j++] = name; + else + pa_log_error(" avail-out-devices, out of range, [%d]", j); + } + info->num_of_out_devices = j; + PA_IDXSET_FOREACH(name, s->idx_avail_frameworks, idx) { + pa_log_debug(" avail-frameworks[%d] name : %s", k, name); + if (k < AVAIL_FRAMEWORKS_MAX) + info->avail_frameworks[k++] = name; + else + pa_log_error(" avail-frameworks, out of range, [%d]", k); + } + info->num_of_frameworks = k; + } + } else { + pa_log_error("stream_map is not initialized.."); + return -1; + } + return 0; +} + +#ifdef HAVE_DBUS +static DBusHandlerResult handle_introspect(DBusConnection *conn, DBusMessage *msg, void *userdata) { + const char *xml = STREAM_MGR_INTROSPECT_XML; + DBusMessage *r = NULL; + + pa_assert(conn); + pa_assert(msg); + pa_assert(userdata); + + pa_assert_se(r = dbus_message_new_method_return(msg)); + pa_assert_se(dbus_message_append_args(r, DBUS_TYPE_STRING, &xml, DBUS_TYPE_INVALID)); + + if (r) { + pa_assert_se(dbus_connection_send((conn), r, NULL)); + dbus_message_unref(r); + } + + return DBUS_HANDLER_RESULT_HANDLED; +} + +static void handle_get_stream_list(DBusConnection *conn, DBusMessage *msg, void *userdata) { + stream_list list; + DBusMessage *reply = NULL; + DBusMessageIter msg_iter; + pa_stream_manager *m = (pa_stream_manager*)userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(m); + + pa_assert_se(dbus_message_get_args(msg, NULL, + DBUS_TYPE_INVALID)); + pa_log_info("handle_get_stream_list() dbus method is called"); + + memset(&list, 0, sizeof(stream_list)); + pa_assert_se((reply = dbus_message_new_method_return(msg))); + dbus_message_iter_init_append(reply, &msg_iter); + if(!get_available_streams(m, &list)) { + pa_dbus_append_basic_array_variant(&msg_iter, DBUS_TYPE_STRING, &list.types, list.num_of_streams); + pa_dbus_append_basic_array_variant(&msg_iter, DBUS_TYPE_INT32, &list.priorities, list.num_of_streams); + } else { + pa_dbus_append_basic_array_variant(&msg_iter, DBUS_TYPE_STRING, NULL, 0); + pa_dbus_append_basic_array_variant(&msg_iter, DBUS_TYPE_INT32, NULL, 0); + } + pa_assert_se(dbus_connection_send(conn, reply, NULL)); + dbus_message_unref(reply); +} + +static void handle_get_stream_info(DBusConnection *conn, DBusMessage *msg, void *userdata) { + char *type; + stream_info_per_type info; + DBusMessage *reply = NULL; + DBusMessageIter msg_iter; + pa_stream_manager *m = (pa_stream_manager*)userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(m); + + pa_assert_se(dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &type, + DBUS_TYPE_INVALID)); + pa_log_info("handle_get_stream_info(), type[%s]", type); + + memset(&info, 0, sizeof(stream_info_per_type)); + pa_assert_se((reply = dbus_message_new_method_return(msg))); + dbus_message_iter_init_append(reply, &msg_iter); + if(!get_stream_info(m, type, &info)) { + pa_dbus_append_basic_variant(&msg_iter, DBUS_TYPE_INT32, &info.priority); + pa_dbus_append_basic_variant(&msg_iter, DBUS_TYPE_INT32, &info.route_type); + pa_dbus_append_basic_array_variant(&msg_iter, DBUS_TYPE_STRING, &info.avail_in_devices, info.num_of_in_devices); + pa_dbus_append_basic_array_variant(&msg_iter, DBUS_TYPE_STRING, &info.avail_out_devices, info.num_of_out_devices); + pa_dbus_append_basic_array_variant(&msg_iter, DBUS_TYPE_STRING, &info.avail_frameworks, info.num_of_frameworks); + } else { + pa_dbus_append_basic_variant(&msg_iter, DBUS_TYPE_INT32, 0); + pa_dbus_append_basic_variant(&msg_iter, DBUS_TYPE_INT32, 0); + pa_dbus_append_basic_array_variant(&msg_iter, DBUS_TYPE_STRING, NULL, 0); + pa_dbus_append_basic_array_variant(&msg_iter, DBUS_TYPE_STRING, NULL, 0); + pa_dbus_append_basic_array_variant(&msg_iter, DBUS_TYPE_STRING, NULL, 0); + } + pa_assert_se(dbus_connection_send(conn, reply, NULL)); + dbus_message_unref(reply); +} + +static void handle_set_stream_route_devices(DBusConnection *conn, DBusMessage *msg, void *userdata) { + uint32_t id = 0; + int i = 0; + uint32_t *in_device_list = NULL; + uint32_t *out_device_list = NULL; + int list_len_in = 0; + int list_len_out = 0; + uint32_t idx = 0; + uint32_t *device_id = NULL; + stream_parent *sp = NULL; + DBusMessage *reply = NULL; + pa_stream_manager *m = (pa_stream_manager*)userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(m); + + pa_assert_se(dbus_message_get_args(msg, NULL, + DBUS_TYPE_UINT32, &id, + DBUS_TYPE_ARRAY, DBUS_TYPE_UINT32, &in_device_list, &list_len_in, + DBUS_TYPE_ARRAY, DBUS_TYPE_UINT32, &out_device_list, &list_len_out, + DBUS_TYPE_INVALID)); + pa_log_info("handle_set_stream_route_devices(), id[%u], in_device_list[%p]:length[%d], out_device_list[%p]:length[%d]", + id, in_device_list, list_len_in, out_device_list, list_len_out); + + pa_assert_se((reply = dbus_message_new_method_return(msg))); + + sp = pa_hashmap_get(m->stream_parents, (const void*)id); + if (sp) { + if (!in_device_list && !out_device_list) { + pa_log_error("invalid arguments"); + goto FAILURE; + } + + if (sp->idx_route_in_devices) { + PA_IDXSET_FOREACH(device_id, sp->idx_route_in_devices, idx) { + pa_idxset_remove_by_data(sp->idx_route_in_devices, device_id, NULL); + pa_xfree(device_id); + } + if (in_device_list && list_len_in) { + for (i = 0; i < list_len_in; i++) { + pa_idxset_put(sp->idx_route_in_devices, pa_xmemdup(&in_device_list[i], sizeof(uint32_t)), NULL); + pa_log_debug(" -- [in] device id:%u", in_device_list[i]); + } + } + if (m->cur_highest_priority.source_output) { + /* if any stream that belongs to this id has been activated, do notify right away */ + if (pa_idxset_get_by_data(sp->idx_source_outputs, m->cur_highest_priority.source_output, NULL)) { + pa_log_debug(" -- cur_highest_priority.source_output->index[%u] belongs to this parent id[%u], do notify for the route change", + (m->cur_highest_priority.source_output)->index, id); + do_notify(m, NOTIFY_COMMAND_CHANGE_ROUTE_START, STREAM_SOURCE_OUTPUT, m->cur_highest_priority.source_output); + } + } + } else { + pa_log_error("failed to update, idx_route_in_devices[%p]", sp->idx_route_in_devices); + goto FAILURE; + } + + if (sp->idx_route_out_devices) { + PA_IDXSET_FOREACH(device_id, sp->idx_route_out_devices, idx) { + pa_idxset_remove_by_data(sp->idx_route_out_devices, device_id, NULL); + pa_xfree(device_id); + } + if (out_device_list && list_len_out) { + for (i = 0; i < list_len_out; i++) { + pa_idxset_put(sp->idx_route_out_devices, pa_xmemdup(&out_device_list[i], sizeof(uint32_t)), NULL); + pa_log_debug(" -- [out] device id:%u", out_device_list[i]); + } + } + if (m->cur_highest_priority.sink_input) { + /* if any stream that belongs to this id has been activated, do notify right away */ + if (pa_idxset_get_by_data(sp->idx_sink_inputs, m->cur_highest_priority.sink_input, NULL)) { + pa_log_debug(" -- cur_highest_priority.sink_input->index[%u] belongs to this parent id[%u], do notify for the route change", + (m->cur_highest_priority.sink_input)->index, id); + do_notify(m, NOTIFY_COMMAND_CHANGE_ROUTE_START, STREAM_SINK_INPUT, m->cur_highest_priority.sink_input); + } + } + } else { + pa_log_error("failed to update, idx_route_out_devices[%p]", sp->idx_route_out_devices); + goto FAILURE; + } + pa_assert_se(dbus_message_append_args(reply, DBUS_TYPE_STRING, &stream_manager_dbus_ret_str[RET_MSG_INDEX_OK], DBUS_TYPE_INVALID)); + } else { + pa_log_error("could not find matching client for this parent_id[%u]", id); + goto FAILURE; + } + + pa_assert_se(dbus_connection_send(conn, reply, NULL)); + dbus_message_unref(reply); + return; +FAILURE: + pa_assert_se(dbus_message_append_args(reply, DBUS_TYPE_STRING, &stream_manager_dbus_ret_str[RET_MSG_INDEX_ERROR], DBUS_TYPE_INVALID)); + pa_assert_se(dbus_connection_send(conn, reply, NULL)); + dbus_message_unref(reply); + return; +} + +static void handle_set_stream_route_option(DBusConnection *conn, DBusMessage *msg, void *userdata) { + uint32_t id = 0; + const char *name = NULL; + int32_t value = 0; + pa_bool_t updated = FALSE; + stream_parent *sp = NULL; + stream_route_option route_option; + DBusMessage *reply = NULL; + pa_stream_manager *m = (pa_stream_manager*)userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(m); + + pa_assert_se(dbus_message_get_args(msg, NULL, + DBUS_TYPE_UINT32, &id, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INT32, &value, + DBUS_TYPE_INVALID)); + pa_log_info("handle_set_stream_route_option(), name[%s], value[%d]", name, value); + + pa_assert_se((reply = dbus_message_new_method_return(msg))); + + sp = pa_hashmap_get(m->stream_parents, (const void*)id); + if (sp) { + if (name) { + route_option.name = name; + route_option.value = value; + + /* if any stream that belongs to this id has been activated, do notify right away */ + if (m->cur_highest_priority.sink_input) { + if (pa_idxset_get_by_data(sp->idx_sink_inputs, m->cur_highest_priority.sink_input, NULL)) { + pa_log_debug(" -- cur_highest_priority.sink_input->index[%u] belongs to this parent id[%u], do notify for the options", + (m->cur_highest_priority.sink_input)->index, id); + do_notify(m, NOTIFY_COMMAND_UPDATE_ROUTE_OPTION, STREAM_SINK_INPUT, &route_option); + updated = TRUE; + } + } + if (m->cur_highest_priority.source_output) { + if (pa_idxset_get_by_data(sp->idx_source_outputs, m->cur_highest_priority.source_output, NULL)) { + pa_log_debug(" -- cur_highest_priority.source_output->index[%u] belongs to this parent id[%u], do notify for the options", + (m->cur_highest_priority.source_output)->index, id); + do_notify(m, NOTIFY_COMMAND_UPDATE_ROUTE_OPTION, STREAM_SOURCE_OUTPUT, &route_option); + updated = TRUE; + } + } + if (!updated) { + pa_log_error("invalid state"); + pa_assert_se(dbus_message_append_args(reply, DBUS_TYPE_STRING, &stream_manager_dbus_ret_str[RET_MSG_INDEX_ERROR_NO_STREAM], DBUS_TYPE_INVALID)); + } else + pa_assert_se(dbus_message_append_args(reply, DBUS_TYPE_STRING, &stream_manager_dbus_ret_str[RET_MSG_INDEX_OK], DBUS_TYPE_INVALID)); + } else { + pa_log_error("invalid arguments"); + pa_assert_se(dbus_message_append_args(reply, DBUS_TYPE_STRING, &stream_manager_dbus_ret_str[RET_MSG_INDEX_ERROR], DBUS_TYPE_INVALID)); + } + + } else { + pa_log_error("could not find matching client for this parent_id[%u]", id); + pa_assert_se(dbus_message_append_args(reply, DBUS_TYPE_STRING, &stream_manager_dbus_ret_str[RET_MSG_INDEX_ERROR], DBUS_TYPE_INVALID)); + } + + pa_assert_se(dbus_connection_send(conn, reply, NULL)); + dbus_message_unref(reply); + return; +} + +static void send_volume_changed_signal(DBusConnection *conn, const char *direction, const char *volume_type, const uint32_t volume_level) { + DBusMessage *signal_msg; + DBusMessageIter msg_iter; + + pa_assert(conn); + pa_assert(volume_type); + + pa_log_debug("Send volume changed signal : direction %s, type %s, level %d", direction, volume_type, volume_level); + + pa_assert_se(signal_msg = dbus_message_new_signal(STREAM_MANAGER_OBJECT_PATH, STREAM_MANAGER_INTERFACE, "VolumeChanged")); + dbus_message_iter_init_append(signal_msg, &msg_iter); + + dbus_message_iter_append_basic(&msg_iter, DBUS_TYPE_STRING, &direction); + dbus_message_iter_append_basic(&msg_iter, DBUS_TYPE_STRING, &volume_type); + dbus_message_iter_append_basic(&msg_iter, DBUS_TYPE_UINT32, &volume_level); + + pa_assert_se(dbus_connection_send(conn, signal_msg, NULL)); + dbus_message_unref(signal_msg); + return; +} + +static void handle_set_volume_level(DBusConnection *conn, DBusMessage *msg, void *userdata) { + const char *direction = NULL; + const char *type = NULL; + uint32_t level = 0; + stream_type_t stream_type = STREAM_SINK_INPUT; + DBusMessage *reply = NULL; + pa_stream_manager *m = (pa_stream_manager*)userdata; + int ret = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(m); + + pa_assert_se(dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &direction, + DBUS_TYPE_STRING, &type, + DBUS_TYPE_UINT32, &level, + DBUS_TYPE_INVALID)); + pa_log_info("handle_set_volume_level(), direction[%s], type[%s], level[%u]", direction, type, level); + + pa_assert_se((reply = dbus_message_new_method_return(msg))); + + if (pa_streq(direction, "in")) + stream_type = STREAM_SOURCE_OUTPUT; + else if (pa_streq(direction, "out")) + stream_type = STREAM_SINK_INPUT; + else { + pa_assert_se(dbus_message_append_args(reply, DBUS_TYPE_STRING, &stream_manager_dbus_ret_str[RET_MSG_INDEX_ERROR], DBUS_TYPE_INVALID)); + goto FAILURE; + } + + if ((ret = set_volume_level_by_type(m, stream_type, type, level))) + pa_assert_se(dbus_message_append_args(reply, DBUS_TYPE_STRING, &stream_manager_dbus_ret_str[RET_MSG_INDEX_ERROR], DBUS_TYPE_INVALID)); + else + pa_assert_se(dbus_message_append_args(reply, DBUS_TYPE_STRING, &stream_manager_dbus_ret_str[RET_MSG_INDEX_OK], DBUS_TYPE_INVALID)); + +FAILURE: + pa_assert_se(dbus_connection_send(conn, reply, NULL)); + dbus_message_unref(reply); + + if (!ret) + send_volume_changed_signal(conn, direction, type, level); + return; +} + +static void handle_get_volume_level(DBusConnection *conn, DBusMessage *msg, void *userdata) { + const char *direction = NULL; + const char *type = NULL; + uint32_t level = 0; + stream_type_t stream_type = STREAM_SINK_INPUT; + DBusMessage *reply = NULL; + pa_stream_manager *m = (pa_stream_manager*)userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(m); + + pa_assert_se(dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &direction, + DBUS_TYPE_STRING, &type, + DBUS_TYPE_INVALID)); + pa_log_info("handle_get_volume_level(), direction(%s), type(%s)", direction, type); + + pa_assert_se((reply = dbus_message_new_method_return(msg))); + + if (pa_streq(direction, "in")) + stream_type = STREAM_SOURCE_OUTPUT; + else if (pa_streq(direction, "out")) + stream_type = STREAM_SINK_INPUT; + else { + pa_assert_se(dbus_message_append_args(reply, DBUS_TYPE_UINT32, 0, DBUS_TYPE_INVALID)); + pa_assert_se(dbus_message_append_args(reply, DBUS_TYPE_STRING, &stream_manager_dbus_ret_str[RET_MSG_INDEX_ERROR], DBUS_TYPE_INVALID)); + goto FAILURE; + } + + if (get_volume_level_by_type(m, GET_VOLUME_CURRENT_LEVEL, stream_type, type, &level)) { + pa_assert_se(dbus_message_append_args(reply, DBUS_TYPE_UINT32, 0, DBUS_TYPE_INVALID)); + pa_assert_se(dbus_message_append_args(reply, DBUS_TYPE_STRING, &stream_manager_dbus_ret_str[RET_MSG_INDEX_ERROR], DBUS_TYPE_INVALID)); + } else { + pa_assert_se(dbus_message_append_args(reply, DBUS_TYPE_UINT32, &level, DBUS_TYPE_INVALID)); + pa_assert_se(dbus_message_append_args(reply, DBUS_TYPE_STRING, &stream_manager_dbus_ret_str[RET_MSG_INDEX_OK], DBUS_TYPE_INVALID)); + } + +FAILURE: + pa_assert_se(dbus_connection_send(conn, reply, NULL)); + dbus_message_unref(reply); + return; +} + +static void handle_get_volume_max_level(DBusConnection *conn, DBusMessage *msg, void *userdata) { + const char *direction = NULL; + const char *type = NULL; + uint32_t level = 0; + stream_type_t stream_type = STREAM_SINK_INPUT; + DBusMessage *reply = NULL; + pa_stream_manager *m = (pa_stream_manager*)userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(m); + + pa_assert_se(dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &direction, + DBUS_TYPE_STRING, &type, + DBUS_TYPE_INVALID)); + pa_log_info("handle_get_volume_max_level(), direction[%s], type[%s]", direction, type); + + pa_assert_se((reply = dbus_message_new_method_return(msg))); + + if (pa_streq(direction, "in")) + stream_type = STREAM_SOURCE_OUTPUT; + else if (pa_streq(direction, "out")) + stream_type = STREAM_SINK_INPUT; + else { + pa_assert_se(dbus_message_append_args(reply, DBUS_TYPE_UINT32, 0, DBUS_TYPE_INVALID)); + pa_assert_se(dbus_message_append_args(reply, DBUS_TYPE_STRING, &stream_manager_dbus_ret_str[RET_MSG_INDEX_ERROR], DBUS_TYPE_INVALID)); + goto FAILURE; + } + + if (get_volume_level_by_type(m, GET_VOLUME_MAX_LEVEL, stream_type, type, &level)) { + pa_assert_se(dbus_message_append_args(reply, DBUS_TYPE_UINT32, 0, DBUS_TYPE_INVALID)); + pa_assert_se(dbus_message_append_args(reply, DBUS_TYPE_STRING, &stream_manager_dbus_ret_str[RET_MSG_INDEX_ERROR], DBUS_TYPE_INVALID)); + } else { + pa_assert_se(dbus_message_append_args(reply, DBUS_TYPE_UINT32, &level, DBUS_TYPE_INVALID)); + pa_assert_se(dbus_message_append_args(reply, DBUS_TYPE_STRING, &stream_manager_dbus_ret_str[RET_MSG_INDEX_OK], DBUS_TYPE_INVALID)); + } +FAILURE: + pa_assert_se(dbus_connection_send(conn, reply, NULL)); + dbus_message_unref(reply); + return; +} + +static void handle_set_volume_mute(DBusConnection *conn, DBusMessage *msg, void *userdata) { + const char *direction = NULL; + const char *type = NULL; + uint32_t do_mute = 0; + stream_type_t stream_type = STREAM_SINK_INPUT; + DBusMessage *reply = NULL; + pa_stream_manager *m = (pa_stream_manager*)userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(m); + + pa_assert_se(dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &direction, + DBUS_TYPE_STRING, &type, + DBUS_TYPE_UINT32, &do_mute, + DBUS_TYPE_INVALID)); + pa_log_info("handle_set_volume_mute(), direction[%s], type[%s], do_mute[%u]", direction, type, do_mute); + + pa_assert_se((reply = dbus_message_new_method_return(msg))); + + if (pa_streq(direction, "in")) + stream_type = STREAM_SOURCE_OUTPUT; + else if (pa_streq(direction, "out")) + stream_type = STREAM_SINK_INPUT; + else { + pa_assert_se(dbus_message_append_args(reply, DBUS_TYPE_STRING, &stream_manager_dbus_ret_str[RET_MSG_INDEX_ERROR], DBUS_TYPE_INVALID)); + goto FAILURE; + } + + if (set_volume_mute_by_type(m, stream_type, type, (pa_bool_t)do_mute)) + pa_assert_se(dbus_message_append_args(reply, DBUS_TYPE_STRING, &stream_manager_dbus_ret_str[RET_MSG_INDEX_ERROR], DBUS_TYPE_INVALID)); + else + pa_assert_se(dbus_message_append_args(reply, DBUS_TYPE_STRING, &stream_manager_dbus_ret_str[RET_MSG_INDEX_OK], DBUS_TYPE_INVALID)); + +FAILURE: + pa_assert_se(dbus_connection_send(conn, reply, NULL)); + dbus_message_unref(reply); + return; +} + +static void handle_get_volume_mute(DBusConnection *conn, DBusMessage *msg, void *userdata) { + const char *direction = NULL; + const char *type = NULL; + uint32_t is_muted = 0; + stream_type_t stream_type = STREAM_SINK_INPUT; + DBusMessage *reply = NULL; + pa_stream_manager *m = (pa_stream_manager*)userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(m); + + pa_assert_se(dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &direction, + DBUS_TYPE_STRING, &type, + DBUS_TYPE_INVALID)); + pa_log_info("handle_get_volume_mute(), direction[%s], type[%s]", direction, type); + + pa_assert_se((reply = dbus_message_new_method_return(msg))); + + if (pa_streq(direction, "in")) + stream_type = STREAM_SOURCE_OUTPUT; + else if (pa_streq(direction, "out")) + stream_type = STREAM_SINK_INPUT; + else { + pa_assert_se(dbus_message_append_args(reply, DBUS_TYPE_UINT32, 0, DBUS_TYPE_INVALID)); + pa_assert_se(dbus_message_append_args(reply, DBUS_TYPE_STRING, &stream_manager_dbus_ret_str[RET_MSG_INDEX_ERROR], DBUS_TYPE_INVALID)); + goto FAILURE; + } + + if (get_volume_mute_by_type(m, stream_type, type, (pa_bool_t*)&is_muted)) { + pa_assert_se(dbus_message_append_args(reply, DBUS_TYPE_UINT32, 0, DBUS_TYPE_INVALID)); + pa_assert_se(dbus_message_append_args(reply, DBUS_TYPE_STRING, &stream_manager_dbus_ret_str[RET_MSG_INDEX_ERROR], DBUS_TYPE_INVALID)); + } else { + pa_assert_se(dbus_message_append_args(reply, DBUS_TYPE_UINT32, &is_muted, DBUS_TYPE_INVALID)); + pa_assert_se(dbus_message_append_args(reply, DBUS_TYPE_STRING, &stream_manager_dbus_ret_str[RET_MSG_INDEX_OK], DBUS_TYPE_INVALID)); + } + +FAILURE: + pa_assert_se(dbus_connection_send(conn, reply, NULL)); + dbus_message_unref(reply); + return; +} + +static void handle_get_current_volume_type(DBusConnection *conn, DBusMessage *msg, void *userdata) { + const char *direction = NULL; + const char *type = NULL; + void *s = NULL; + stream_type_t stream_type = STREAM_SINK_INPUT; + DBusMessage *reply = NULL; + pa_stream_manager *m = (pa_stream_manager*)userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(m); + + pa_assert_se(dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &direction, + DBUS_TYPE_INVALID)); + pa_log_info("handle_get_current_volume_type(), direction[%s]", direction); + + pa_assert_se((reply = dbus_message_new_method_return(msg))); + + if (pa_streq(direction, "in")) + stream_type = STREAM_SOURCE_OUTPUT; + else if (pa_streq(direction, "out")) + stream_type = STREAM_SINK_INPUT; + else { + pa_assert_se(dbus_message_append_args(reply, DBUS_TYPE_STRING, &dbus_str_none, DBUS_TYPE_INVALID)); + pa_assert_se(dbus_message_append_args(reply, DBUS_TYPE_STRING, &stream_manager_dbus_ret_str[RET_MSG_INDEX_ERROR], DBUS_TYPE_INVALID)); + goto FAILURE; + } + + s = (stream_type == STREAM_SINK_INPUT)?(void*)(m->cur_highest_priority.sink_input):(void*)(m->cur_highest_priority.source_output); + if (s) { + type = pa_proplist_gets(GET_STREAM_PROPLIST(s, stream_type), PA_PROP_MEDIA_TIZEN_VOLUME_TYPE); + pa_assert_se(dbus_message_append_args(reply, DBUS_TYPE_STRING, &type, DBUS_TYPE_INVALID)); + pa_assert_se(dbus_message_append_args(reply, DBUS_TYPE_STRING, &stream_manager_dbus_ret_str[RET_MSG_INDEX_OK], DBUS_TYPE_INVALID)); + } else { + pa_assert_se(dbus_message_append_args(reply, DBUS_TYPE_STRING, &dbus_str_none, DBUS_TYPE_INVALID)); + pa_assert_se(dbus_message_append_args(reply, DBUS_TYPE_STRING, &stream_manager_dbus_ret_str[RET_MSG_INDEX_ERROR_NO_STREAM], DBUS_TYPE_INVALID)); + } + +FAILURE: + pa_assert_se(dbus_connection_send(conn, reply, NULL)); + dbus_message_unref(reply); + return; +} + +static void handle_update_focus_status(DBusConnection *conn, DBusMessage *msg, void *userdata) { + uint32_t id = 0; + uint32_t idx = 0; + uint32_t count = 0; + uint32_t acquired_focus_status = 0; + stream_parent *sp = NULL; + void *stream = NULL; + DBusMessage *reply = NULL; + pa_stream_manager *m = (pa_stream_manager*)userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(m); + + pa_assert_se(dbus_message_get_args(msg, NULL, + DBUS_TYPE_UINT32, &id, + DBUS_TYPE_UINT32, &acquired_focus_status, + DBUS_TYPE_INVALID)); + pa_log_info("handle_update_focus_status(), id[%u], acquired_focus_status[0x%x]", id, acquired_focus_status); + + pa_assert_se((reply = dbus_message_new_method_return(msg))); + + sp = pa_hashmap_get(m->stream_parents, (const void*)id); + if (sp) { + if (sp->focus_status != (acquired_focus_status & (STREAM_FOCUS_ACQUIRED_PLAYBACK|STREAM_FOCUS_ACQUIRED_CAPTURE))) { + /* need to update */ + sp->focus_status = acquired_focus_status & (STREAM_FOCUS_ACQUIRED_PLAYBACK|STREAM_FOCUS_ACQUIRED_CAPTURE); + if (sp->idx_sink_inputs) + count = pa_idxset_size(sp->idx_sink_inputs); + PA_IDXSET_FOREACH(stream, sp->idx_sink_inputs, idx) { + pa_proplist_sets(GET_STREAM_PROPLIST(stream, STREAM_SINK_INPUT), PA_PROP_MEDIA_FOCUS_STATUS, + IS_FOCUS_ACQUIRED(sp->focus_status, STREAM_SINK_INPUT)?STREAM_FOCUS_PLAYBACK:STREAM_FOCUS_NONE); + if (--count == 0) + process_stream(STREAM_SINK_INPUT, stream, PROCESS_COMMAND_CHANGE_ROUTE_BY_STREAM_FOCUS_CHANGED, m); + } + if (sp->idx_source_outputs) + count = pa_idxset_size(sp->idx_source_outputs); + PA_IDXSET_FOREACH(stream, sp->idx_source_outputs, idx) { + pa_proplist_sets(GET_STREAM_PROPLIST(stream, STREAM_SOURCE_OUTPUT), PA_PROP_MEDIA_FOCUS_STATUS, + IS_FOCUS_ACQUIRED(sp->focus_status, STREAM_SOURCE_OUTPUT)?STREAM_FOCUS_CAPTURE:STREAM_FOCUS_NONE); + if (--count == 0) + process_stream(STREAM_SOURCE_OUTPUT, stream, PROCESS_COMMAND_CHANGE_ROUTE_BY_STREAM_FOCUS_CHANGED, m); + } + } else + pa_log_debug("same as before, skip updating focus status[0x%x]", acquired_focus_status); + + } else { + pa_log_error("could not find matching client for this parent_id[%u]", id); + pa_assert_se(dbus_message_append_args(reply, DBUS_TYPE_STRING, &stream_manager_dbus_ret_str[RET_MSG_INDEX_ERROR], DBUS_TYPE_INVALID)); + goto FAILURE; + } + pa_assert_se(dbus_message_append_args(reply, DBUS_TYPE_STRING, &stream_manager_dbus_ret_str[RET_MSG_INDEX_OK], DBUS_TYPE_INVALID)); +FAILURE: + pa_assert_se(dbus_connection_send(conn, reply, NULL)); + dbus_message_unref(reply); + return; +} + +static DBusHandlerResult handle_methods(DBusConnection *conn, DBusMessage *msg, void *userdata) { + int idx = 0; + pa_stream_manager *m = (pa_stream_manager*)userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(m); + + for (idx = 0; idx < METHOD_HANDLER_MAX; idx++) { + if (dbus_message_is_method_call(msg, STREAM_MANAGER_INTERFACE, method_handlers[idx].method_name )) { + pa_log_debug("Message signature [%s] (Expected [%s])", dbus_message_get_signature(msg), signature_args_for_in[idx]); + if (pa_streq(dbus_message_get_signature(msg), signature_args_for_in[idx])) { + method_handlers[idx].receive_cb(conn, msg, userdata); + return DBUS_HANDLER_RESULT_HANDLED; + } else { + pa_log_warn("Wrong Argument Signature"); + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_SIGNATURE, "Wrong Signature, Expected %s", signature_args_for_in[idx]); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + } + } + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +static DBusHandlerResult method_handler_for_vt(DBusConnection *c, DBusMessage *m, void *userdata) { + pa_stream_manager *u = (pa_stream_manager*)userdata; + const char *path, *interface, *member; + + pa_assert(c); + pa_assert(m); + pa_assert(u); + + path = dbus_message_get_path(m); + interface = dbus_message_get_interface(m); + member = dbus_message_get_member(m); + + pa_log_debug("dbus: path=%s, interface=%s, member=%s", path, interface, member); + + if (!pa_streq(path, STREAM_MANAGER_OBJECT_PATH)) + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) { + return handle_introspect(c, m, u); + } else { + return handle_methods(c, m, u); + } + + return DBUS_HANDLER_RESULT_HANDLED; +} +#endif + +static int convert_route_type(stream_route_type_t *route_type, const char *route_type_string) { + int ret = 0; + + pa_assert(route_type); + pa_assert(route_type_string); + + if (pa_streq("auto", route_type_string)) + *route_type = STREAM_ROUTE_TYPE_AUTO; + else if (pa_streq("auto-all", route_type_string)) + *route_type = STREAM_ROUTE_TYPE_AUTO_ALL; + else if (pa_streq("manual", route_type_string)) + *route_type = STREAM_ROUTE_TYPE_MANUAL; + else { + ret = -1; + pa_log_error("Not supported route_type(%s)", route_type_string); + } + + return ret; +} + +static void dump_stream_map (pa_stream_manager *m) { + stream_info *s = NULL; + const char *role = NULL; + char *name = NULL; + void *state = NULL; + uint32_t idx = 0; + pa_assert(m); + pa_log_debug("==========[START stream-map dump]=========="); + while (m->stream_infos && (s = pa_hashmap_iterate(m->stream_infos, &state, (const void **)&role))) { + pa_log_debug("[role : %s]", role); + pa_log_debug(" - priority : %d", s->priority); + pa_log_debug(" - route-type : %d (0:auto,1:auto-all,2:manual)", s->route_type); + pa_log_debug(" - volume-types : in[%s], out[%s]", s->volume_type[STREAM_DIRECTION_IN], s->volume_type[STREAM_DIRECTION_OUT]); + pa_log_debug(" - avail-in-devices"); + PA_IDXSET_FOREACH(name, s->idx_avail_in_devices, idx) + pa_log_debug(" name[%d] : %s", idx, name); + pa_log_debug(" - avail-out-devices"); + PA_IDXSET_FOREACH(name, s->idx_avail_out_devices, idx) + pa_log_debug(" name[%d] : %s", idx, name); + pa_log_debug(" - avail-frameworks"); + PA_IDXSET_FOREACH(name, s->idx_avail_frameworks, idx) + pa_log_debug(" name[%d] : %s", idx, name); + } + pa_log_debug("===========[END stream-map dump]==========="); + return; +} + +static int init_stream_map (pa_stream_manager *m) { + volume_info *v; + stream_info *s; + json_object *o; + json_object *volume_array_o; + json_object *stream_array_o; + json_object *volume_type_o; + json_object *is_hal_volume_o; + json_object *role_o; + json_object *priority_o; + json_object *route_type_o; + json_object *volume_types_o; + json_object *avail_in_devices_o; + json_object *avail_out_devices_o; + json_object *avail_frameworks_o; + int num_of_volume_types = 0; + int num_of_stream_types = 0; + const char *volume_type = NULL; + const char *role = NULL; + int i = 0, j = 0; + int num_of_avail_in_devices; + int num_of_avail_out_devices; + int num_of_avail_frameworks; + json_object *out_device_o; + json_object *in_device_o; + json_object *framework_o; + json_object *volume_o; + json_object *stream_o; + const char *volume_type_in_str = NULL; + const char *volume_type_out_str = NULL; + json_object *volume_type_in_o; + json_object *volume_type_out_o; + void *state = NULL; + + pa_assert(m); + + o = json_object_from_file(STREAM_MAP_FILE); + if(is_error(o)) { + pa_log_error("Read stream-map file(%s) failed", STREAM_MAP_FILE); + return -1; + } + + /* Volumes */ + m->volume_infos = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + if((volume_array_o = json_object_object_get(o, STREAM_MAP_VOLUMES)) && json_object_is_type(volume_array_o, json_type_array)) { + num_of_volume_types = json_object_array_length(volume_array_o); + for (i = 0; i < num_of_volume_types; i++) { + if((volume_o = json_object_array_get_idx(volume_array_o, i)) && json_object_is_type(volume_o, json_type_object)) { + v = pa_xmalloc0(sizeof(volume_info)); + pa_log_debug("volume found [%d]", i); + if((volume_type_o = json_object_object_get(volume_o, STREAM_MAP_VOLUME_TYPE)) && json_object_is_type(volume_type_o, json_type_string)) { + volume_type = json_object_get_string(volume_type_o); + pa_log_debug(" - type : %s", volume_type); + } else { + pa_log_error("Get volume type failed"); + goto failed; + } + if((is_hal_volume_o = json_object_object_get(volume_o, STREAM_MAP_VOLUME_IS_FOR_HAL)) && json_object_is_type(is_hal_volume_o, json_type_int)) { + v->is_hal_volume_type = (pa_bool_t)json_object_get_int(is_hal_volume_o); + pa_log_debug(" - is-hal-volume : %d", v->is_hal_volume_type); + } else { + pa_log_error("Get is-hal-volume failed"); + goto failed; + } + } + pa_hashmap_put(m->volume_infos,(void*)volume_type, v); + } + } + + /* Streams */ + m->stream_infos = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + if((stream_array_o = json_object_object_get(o, STREAM_MAP_STREAMS)) && json_object_is_type(stream_array_o, json_type_array)) { + num_of_stream_types = json_object_array_length(stream_array_o); + for (i = 0; i < num_of_stream_types; i++) { + + if((stream_o = json_object_array_get_idx(stream_array_o, i)) && json_object_is_type(stream_o, json_type_object)) { + s = pa_xmalloc0(sizeof(stream_info)); + pa_log_debug("stream found [%d]", i); + if((role_o = json_object_object_get(stream_o, STREAM_MAP_STREAM_ROLE)) && json_object_is_type(role_o, json_type_string)) { + role = json_object_get_string(role_o); + pa_log_debug(" - role : %s", role); + } else { + pa_log_error("Get stream role failed"); + goto failed; + } + if((priority_o = json_object_object_get(stream_o, STREAM_MAP_STREAM_PRIORITY)) && json_object_is_type(priority_o, json_type_int)) { + s->priority = json_object_get_int(priority_o); + pa_log_debug(" - priority : %d", s->priority); + } else { + pa_log_error("Get stream priority failed"); + goto failed; + } + if((route_type_o = json_object_object_get(stream_o, STREAM_MAP_STREAM_ROUTE_TYPE)) && json_object_is_type(route_type_o, json_type_string)) { + if (convert_route_type(&(s->route_type), json_object_get_string(route_type_o))) { + pa_log_error("convert stream route-type failed"); + goto failed; + } + pa_log_debug(" - route-type : %d", s->route_type); + } else { + pa_log_error("Get stream route-type failed"); + goto failed; + } + if((volume_types_o = json_object_object_get(stream_o, STREAM_MAP_STREAM_VOLUME_TYPES)) && json_object_is_type(volume_types_o, json_type_object)) { + if((volume_type_in_o = json_object_object_get(volume_types_o, STREAM_MAP_STREAM_VOLUME_TYPE_IN)) && json_object_is_type(volume_type_in_o, json_type_string)) { + volume_type_in_str = json_object_get_string(volume_type_in_o); + if (!pa_streq(volume_type_in_str, "none")) + s->volume_type[STREAM_DIRECTION_IN] = volume_type_in_str; + } else { + pa_log_error("Get stream volume-type-in failed"); + goto failed; + } + if((volume_type_out_o = json_object_object_get(volume_types_o, STREAM_MAP_STREAM_VOLUME_TYPE_OUT)) && json_object_is_type(volume_type_out_o, json_type_string)) { + volume_type_out_str = json_object_get_string(volume_type_out_o); + if (!pa_streq(volume_type_out_str, "none")) + s->volume_type[STREAM_DIRECTION_OUT] = volume_type_out_str; + } else { + pa_log_error("Get stream volume-type-out failed"); + goto failed; + } + pa_log_debug(" - volume-types : in[%s], out[%s]", s->volume_type[STREAM_DIRECTION_IN], s->volume_type[STREAM_DIRECTION_OUT]); + } else { + pa_log_error("Get stream volume-types failed"); + goto failed; + } + if((avail_in_devices_o = json_object_object_get(stream_o, STREAM_MAP_STREAM_AVAIL_IN_DEVICES)) && json_object_is_type(avail_in_devices_o, json_type_array)) { + j = 0; + s->idx_avail_in_devices = pa_idxset_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + num_of_avail_in_devices = json_object_array_length(avail_in_devices_o); + pa_log_debug(" - avail-in-devices"); + for (j = 0; j < num_of_avail_in_devices; j++) { + if((in_device_o = json_object_array_get_idx(avail_in_devices_o, j)) && json_object_is_type(in_device_o, json_type_string)) { + pa_idxset_put(s->idx_avail_in_devices, (void*)json_object_get_string(in_device_o), NULL); + pa_log_debug(" device[%d] : %s", j, json_object_get_string(in_device_o)); + } + } + } else { + pa_log_error("Get stream avail-in-devices failed"); + goto failed; + } + if((avail_out_devices_o = json_object_object_get(stream_o, STREAM_MAP_STREAM_AVAIL_OUT_DEVICES)) && json_object_is_type(avail_out_devices_o, json_type_array)) { + j = 0; + s->idx_avail_out_devices = pa_idxset_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + num_of_avail_out_devices = json_object_array_length(avail_out_devices_o); + pa_log_debug(" - avail-out-devices"); + for (j = 0; j < num_of_avail_out_devices; j++) { + if((out_device_o = json_object_array_get_idx(avail_out_devices_o, j)) && json_object_is_type(out_device_o, json_type_string)) { + pa_idxset_put(s->idx_avail_out_devices, (void*)json_object_get_string(out_device_o), NULL); + pa_log_debug(" device[%d] : %s", j, json_object_get_string(out_device_o)); + } + } + } else { + pa_log_error("Get stream avail-out-devices failed"); + goto failed; + } + if((avail_frameworks_o = json_object_object_get(stream_o, STREAM_MAP_STREAM_AVAIL_FRAMEWORKS)) && json_object_is_type(avail_frameworks_o, json_type_array)) { + j = 0; + s->idx_avail_frameworks = pa_idxset_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + num_of_avail_frameworks = json_object_array_length(avail_frameworks_o); + pa_log_debug(" - avail-frameworks"); + for (j = 0; j < num_of_avail_frameworks; j++) { + if((framework_o = json_object_array_get_idx(avail_frameworks_o, j)) && json_object_is_type(framework_o, json_type_string)) { + pa_idxset_put(s->idx_avail_frameworks, (void*)json_object_get_string(framework_o), NULL); + pa_log_debug(" framework[%d] : %s", j, json_object_get_string(framework_o)); + } + } + } else { + pa_log_error("Get stream avail-frameworks failed"); + goto failed; + } + pa_hashmap_put(m->stream_infos,(void*)role, s); + } + } + } else { + pa_log_error("Get streams object failed"); + goto failed; + } + + dump_stream_map(m); + + return 0; +failed: + pa_log_error("Failed to initialize stream-map"); + if (m->stream_infos) { + PA_HASHMAP_FOREACH(s, m->stream_infos, state) { + if (s->idx_avail_in_devices) + pa_idxset_free(s->idx_avail_in_devices, NULL); + if (s->idx_avail_out_devices) + pa_idxset_free(s->idx_avail_out_devices, NULL); + if (s->idx_avail_frameworks) + pa_idxset_free(s->idx_avail_frameworks, NULL); + pa_xfree(s); + } + pa_hashmap_free(m->stream_infos); + } + if (m->volume_infos) { + PA_HASHMAP_FOREACH(v, m->volume_infos, state) { + pa_xfree(v); + } + pa_hashmap_free(m->volume_infos); + } + return -1; +} + +static void deinit_stream_map (pa_stream_manager *m) { + stream_info *s = NULL; + volume_info *v = NULL; + void *state = NULL; + pa_assert(m); + + if (m->stream_infos) { + PA_HASHMAP_FOREACH(s, m->stream_infos, state) { + if (s->idx_avail_in_devices) + pa_idxset_free(s->idx_avail_in_devices, NULL); + if (s->idx_avail_out_devices) + pa_idxset_free(s->idx_avail_out_devices, NULL); + if (s->idx_avail_frameworks) + pa_idxset_free(s->idx_avail_frameworks, NULL); + pa_xfree(s); + } + pa_hashmap_free(m->stream_infos); + } + if (m->volume_infos) { + PA_HASHMAP_FOREACH(v, m->volume_infos, state) { + pa_xfree(v); + } + pa_hashmap_free(m->volume_infos); + } + + return; +} + +static pa_bool_t check_name_to_skip(pa_stream_manager *m, process_command_type_t command, stream_type_t type, void *stream) { + pa_bool_t ret = FALSE; + const char *name = NULL; + const char *role = NULL; + int i = 0; + + pa_assert(m); + pa_assert(stream); + + if (command == PROCESS_COMMAND_PREPARE) { + name = pa_proplist_gets(GET_STREAM_NEW_PROPLIST(stream, type), PA_PROP_MEDIA_NAME); + if (name) { + for (i = 0; i < NAME_FOR_SKIP_MAX; i++) + if (pa_streq(name, stream_manager_media_names_for_skip[i])) { + ret = TRUE; + pa_proplist_sets(GET_STREAM_NEW_PROPLIST(stream, type), PA_PROP_MEDIA_ROLE, SKIP_ROLE); + break; + } + pa_log_info("name is [%s], skip(%d)", name, ret); + } + } else { + if (command == PROCESS_COMMAND_CHANGE_ROUTE_BY_STREAM_STARTED_WITH_NEW_DATA || + command == PROCESS_COMMAND_UPDATE_VOLUME) + role = pa_proplist_gets(GET_STREAM_NEW_PROPLIST(stream, type), PA_PROP_MEDIA_ROLE); + else + role = pa_proplist_gets(GET_STREAM_PROPLIST(stream, type), PA_PROP_MEDIA_ROLE); + + if (role && pa_streq(role, "skip")) + ret = TRUE; + } + + return ret; +} + +static pa_bool_t check_role_to_skip(pa_stream_manager *m, const char *role) { + pa_bool_t ret = TRUE; + stream_info *s = NULL; + + pa_assert(m); + pa_assert(role); + + if (m->stream_infos) { + s = pa_hashmap_get(m->stream_infos, role); + if (s) + ret = FALSE; + } + + pa_log_info("role is [%s], skip(%d)", role, ret); + + return ret; +} + +static pa_bool_t update_priority_of_stream(pa_stream_manager *m, process_command_type_t command, stream_type_t type, void *stream, const char *role) { + stream_info *s = NULL; + + pa_assert(m); + pa_assert(role); + + if (m->stream_infos) + s = pa_hashmap_get(m->stream_infos, role); + else + return FALSE; + + if (s) { + if (command == PROCESS_COMMAND_PREPARE) + pa_proplist_set(GET_STREAM_NEW_PROPLIST(stream, type), PA_PROP_MEDIA_ROLE_PRIORITY, (const void*)&(s->priority), sizeof(s->priority)); + else + pa_proplist_set(GET_STREAM_PROPLIST(stream, type), PA_PROP_MEDIA_ROLE_PRIORITY, (const void*)&(s->priority), sizeof(s->priority)); + } + + return TRUE; +} + +static pa_bool_t update_routing_type_of_stream(pa_stream_manager *m, void *stream, stream_type_t type, const char *role) { + stream_route_type_t route_type = STREAM_ROUTE_TYPE_AUTO; + stream_info *s = NULL; + + pa_assert(m); + pa_assert(role); + + if (m->stream_infos) { + s = pa_hashmap_get(m->stream_infos, role); + if (s) + route_type = s->route_type; + } else + return FALSE; + + pa_proplist_setf(GET_STREAM_NEW_PROPLIST(stream, type), PA_PROP_MEDIA_ROLE_ROUTE_TYPE, "%d", route_type); + + return TRUE; +} + +static pa_bool_t update_volume_type_of_stream(pa_stream_manager *m, stream_type_t type, void *stream, const char *role) { + const char *volume_type = NULL; + stream_info *s = NULL; + + pa_assert(m); + pa_assert(role); + + if (m->stream_infos) { + s = pa_hashmap_get(m->stream_infos, role); + if (s) + volume_type = s->volume_type[!type]; + } else + return FALSE; + + if (volume_type) + pa_proplist_sets(GET_STREAM_NEW_PROPLIST(stream, type), PA_PROP_MEDIA_TIZEN_VOLUME_TYPE, volume_type); + else + pa_log_warn("this stream[%p] does not have any volume type, skip updating volume type. stream_type[%d], role[%s]", stream, type, role); + + return TRUE; +} + +static pa_bool_t update_focus_status_of_stream(pa_stream_manager *m, process_command_type_t command, void *stream, stream_type_t type) { + const char *p_idx; + uint32_t parent_idx; + stream_parent *sp = NULL; + + pa_assert(m); + pa_assert(stream); + + if (command == PROCESS_COMMAND_CHANGE_ROUTE_BY_STREAM_STARTED_WITH_NEW_DATA) + p_idx = pa_proplist_gets(GET_STREAM_NEW_PROPLIST(stream, type), PA_PROP_MEDIA_PARENT_ID); + else + p_idx = pa_proplist_gets(GET_STREAM_PROPLIST(stream, type), PA_PROP_MEDIA_PARENT_ID); + if (p_idx && !pa_atou(p_idx, &parent_idx)) { + pa_log_debug("p_idx(%s), idx(%u)", p_idx, parent_idx); + sp = pa_hashmap_get(m->stream_parents, (const void*)parent_idx); + if (sp) { + if (command == PROCESS_COMMAND_CHANGE_ROUTE_BY_STREAM_STARTED_WITH_NEW_DATA) + pa_proplist_setf(GET_STREAM_NEW_PROPLIST(stream, type), PA_PROP_MEDIA_FOCUS_STATUS, "%u", sp->focus_status); + else + pa_proplist_setf(GET_STREAM_PROPLIST(stream, type), PA_PROP_MEDIA_FOCUS_STATUS, "%u", sp->focus_status); + } else { + pa_log_error("could not find matching client for this parent_id(%u)", parent_idx); + return FALSE; + } + } else { + pa_log_warn("p_idx(%s) or idx(%u) is not valid", p_idx, parent_idx); + return FALSE; + } + + return TRUE; +} + +static pa_bool_t update_stream_parent_info(pa_stream_manager *m, process_command_type_t command, stream_type_t type, void *stream) { + const char *p_idx; + uint32_t parent_idx; + stream_parent *sp = NULL; + + pa_assert(m); + pa_assert(stream); + + p_idx = pa_proplist_gets(GET_STREAM_PROPLIST(stream, type), PA_PROP_MEDIA_PARENT_ID); + if (p_idx && !pa_atou(p_idx, &parent_idx)) { + pa_log_debug("p_idx(%s), idx(%u)", p_idx, parent_idx); + sp = pa_hashmap_get(m->stream_parents, (const void*)parent_idx); + if (sp) { + uint32_t idx = (type==STREAM_SINK_INPUT)?((pa_sink_input*)stream)->index:((pa_source_output*)stream)->index; + if (command == PROCESS_COMMAND_ADD_PARENT_ID) { + /* append this stream to the parent stream info. */ + pa_log_debug(" - append this stream(%p, %u) to the list. sp(%p), stream_type(%d)", stream, idx, sp, type); + pa_idxset_put(type==STREAM_SINK_INPUT?(sp->idx_sink_inputs):(sp->idx_source_outputs), stream, NULL); + return TRUE; + } else if (command == PROCESS_COMMAND_REMOVE_PARENT_ID) { + /* remove this stream from the parent stream info. */ + pa_log_debug(" - remove this stream(%p, %u) from the list. sp(%p), stream_type(%d)", stream, idx, sp, type); + pa_idxset_remove_by_data(type==STREAM_SINK_INPUT?(sp->idx_sink_inputs):(sp->idx_source_outputs), stream, NULL); + return TRUE; + } else { + pa_log_error("invalid command(%d)", command); + return FALSE; + } + } else { + pa_log_error("could not find matching client for this parent_id(%u)", parent_idx); + return FALSE; + } + } else { + pa_log_warn("p_idx(%s) or idx(%u) is not valid", p_idx, parent_idx); + return FALSE; + } + return TRUE; +} + +static pa_bool_t update_the_highest_priority_stream(pa_stream_manager *m, process_command_type_t command, void *mine, + stream_type_t type, const char *role, pa_bool_t *need_to_update) { + uint32_t idx = 0; + size_t size = 0; + const int32_t *priority = NULL; + const char *focus_status = NULL; + void *cur_max_stream = NULL; + void *cur_max_stream_tmp = NULL; + const int32_t *cur_max_priority = NULL; + const char *cur_max_role = NULL; + int32_t cur_max_focus_status_int = 0; + int32_t focus_status_int = 0; + void *i = NULL; + const char *_role = NULL; + pa_idxset *streams = NULL; + + pa_assert(m); + pa_assert(mine); + if (!role) { + pa_log_error("invalid input, role(%s)", role); + return FALSE; + } + + *need_to_update = FALSE; + + if (type == STREAM_SINK_INPUT) { + cur_max_stream = m->cur_highest_priority.sink_input; + } else if (type == STREAM_SOURCE_OUTPUT) { + cur_max_stream = m->cur_highest_priority.source_output; + } + + pa_log_info("update_the_highest_priority_stream(), stream_type(%d), role(%s), command(%d)", type, role, command); + if (command == PROCESS_COMMAND_CHANGE_ROUTE_BY_STREAM_STARTED_WITH_NEW_DATA || + command == PROCESS_COMMAND_CHANGE_ROUTE_BY_STREAM_STARTED) { + /* get focus status */ + if (command == PROCESS_COMMAND_CHANGE_ROUTE_BY_STREAM_STARTED_WITH_NEW_DATA) + focus_status = pa_proplist_gets(GET_STREAM_NEW_PROPLIST(mine, type), PA_PROP_MEDIA_FOCUS_STATUS); + else + focus_status = pa_proplist_gets(GET_STREAM_PROPLIST(mine, type), PA_PROP_MEDIA_FOCUS_STATUS); + if (focus_status && !pa_atoi(focus_status, &focus_status_int)) { + pa_log_debug("focus status(0x%x)", focus_status_int); + } + if (cur_max_stream == NULL) { + *need_to_update = TRUE; + pa_log_debug("set cur_highest to mine"); + } else { + /* TODO : need to check if this stream should be played to external devices */ + if (command == PROCESS_COMMAND_CHANGE_ROUTE_BY_STREAM_STARTED_WITH_NEW_DATA) { + if (pa_proplist_get(GET_STREAM_NEW_PROPLIST(mine, type), PA_PROP_MEDIA_ROLE_PRIORITY, (const void**)&priority, &size)) + pa_log_error("Failed to pa_proplist_get() for priority"); + } else { + if (cur_max_stream == mine) { + pa_log_debug("it has already been processed, skip it.."); + return FALSE; + } + if (pa_proplist_get(GET_STREAM_PROPLIST(mine, type), PA_PROP_MEDIA_ROLE_PRIORITY, (const void**)&priority, &size)) + pa_log_error("Failed to pa_proplist_get() for priority"); + } + if (pa_proplist_get(GET_STREAM_PROPLIST(cur_max_stream, type), PA_PROP_MEDIA_ROLE_PRIORITY, (const void**)&cur_max_priority, &size)) + pa_log_error("Failed to pa_proplist_get() for priority"); + focus_status = pa_proplist_gets(GET_STREAM_PROPLIST(cur_max_stream, type), PA_PROP_MEDIA_FOCUS_STATUS); + if (focus_status && !pa_atoi(focus_status, &cur_max_focus_status_int)) { + pa_log_debug("cur_max_focus status(0x%x)", cur_max_focus_status_int); + } + cur_max_role = pa_proplist_gets(GET_STREAM_PROPLIST(cur_max_stream, type), PA_PROP_MEDIA_ROLE); + if (!cur_max_priority || !cur_max_role) { + pa_log_error("Failed to pa_proplist_gets() for getting current max priority(%p) and it's role(%s)", cur_max_priority, cur_max_role); + return FALSE; + } else { + if (priority && cur_max_priority) { + if (IS_FOCUS_ACQUIRED(focus_status_int, type) || + (!IS_FOCUS_ACQUIRED(cur_max_focus_status_int, type) && *priority >= *cur_max_priority)) { + *need_to_update = TRUE; + pa_log_debug("update cur_highest to mine(%s)", role); + } else { + /* no need to trigger */ + return TRUE; + } + } + } + } + } else if (command == PROCESS_COMMAND_CHANGE_ROUTE_BY_STREAM_ENDED || + command == PROCESS_COMMAND_CHANGE_ROUTE_BY_STREAM_FOCUS_CHANGED) { + if (cur_max_stream == mine || command == PROCESS_COMMAND_CHANGE_ROUTE_BY_STREAM_FOCUS_CHANGED) { + if (type == STREAM_SINK_INPUT) { + streams = ((pa_sink_input*)mine)->sink->inputs; + } else if (type == STREAM_SOURCE_OUTPUT) { + streams = ((pa_source_output*)mine)->source->outputs; + } + /* find the next highest priority input */ + //PA_IDXSET_FOREACH(i, m->core->sinks, idx) { /* need to check a sink which this stream belongs to */ + PA_IDXSET_FOREACH(i, streams, idx) { + if (!(_role = pa_proplist_gets(GET_STREAM_PROPLIST(i, type), PA_PROP_MEDIA_ROLE))) { + pa_log_error("Failed to pa_proplist_gets() for role"); + continue; + } + if (pa_proplist_get(GET_STREAM_PROPLIST(i, type), PA_PROP_MEDIA_ROLE_PRIORITY, (const void**)&priority, &size)) { + pa_log_warn("Failed to pa_proplist_get() for priority, skip it"); + continue; + } + if (!(focus_status = pa_proplist_gets(GET_STREAM_PROPLIST(i, type), PA_PROP_MEDIA_FOCUS_STATUS))) { + pa_log_warn("Failed to pa_proplist_gets() for focus status"); + } else { + if (!pa_atoi(focus_status, &focus_status_int)) { + pa_log_debug("focus status(0x%x)", focus_status_int); + } + } + pa_log_debug("role(%s)/priority(%p)/focus_state(0x%x)/stream(%p)", _role, priority, focus_status_int, i); + if (cur_max_priority == NULL) { + cur_max_priority = priority; + cur_max_focus_status_int = focus_status_int; + cur_max_stream_tmp = i; + } + if (cur_max_priority && priority) { + if (IS_FOCUS_ACQUIRED(cur_max_focus_status_int, type) || + (!IS_FOCUS_ACQUIRED(focus_status_int, type) && (*cur_max_priority > *priority))) { + /* skip */ + } else { + cur_max_priority = priority; + cur_max_focus_status_int = focus_status_int; + cur_max_stream_tmp = i; + } + } + } + pa_log_debug("updated max priority(%p)/stream(%p)", cur_max_priority, cur_max_stream_tmp); + if (cur_max_stream_tmp) { + if (type == STREAM_SINK_INPUT) { + m->cur_highest_priority.sink_input = cur_max_stream_tmp; + } else if (type == STREAM_SOURCE_OUTPUT) { + m->cur_highest_priority.source_output = cur_max_stream_tmp; + } + } else { + if (type == STREAM_SINK_INPUT) { + m->cur_highest_priority.sink_input = NULL; + } else if (type == STREAM_SOURCE_OUTPUT) { + m->cur_highest_priority.source_output = NULL; + } + } + *need_to_update = TRUE; + pa_log_info("need to update: type(%d), cur_highest_priority(sink_input=%p/source_output=%p)", + type, (void*)m->cur_highest_priority.sink_input, (void*)m->cur_highest_priority.sink_input); + } else { + /* no need to trigger */ + return TRUE; + } + } + return TRUE; +} + +static void fill_device_info_to_hook_data(void *hook_data, notify_command_type_t command, stream_type_t type, void *stream, pa_stream_manager *m) { + char *device_none = NULL; + const char *p_idx = NULL; + uint32_t parent_idx = 0; + stream_parent *sp = NULL; + uint32_t idx = 0; + pa_stream_manager_hook_data_for_select *select_data = NULL; + pa_stream_manager_hook_data_for_route *route_data = NULL; + stream_info *si; + pa_idxset *avail_devices; + uint32_t list_len = 0; + + pa_assert(hook_data); + pa_assert(m); + pa_log_warn("fill_device_info_to_hook_data() for %s", notify_command_type_str[command]); + + switch (command) { + case NOTIFY_COMMAND_SELECT_PROPER_SINK_OR_SOURCE_FOR_INIT: { + select_data = (pa_stream_manager_hook_data_for_select*)hook_data; + si = pa_hashmap_get(m->stream_infos, select_data->stream_role); + select_data->route_type = si->route_type; + avail_devices = (type==STREAM_SINK_INPUT)?si->idx_avail_out_devices:si->idx_avail_in_devices; + list_len = pa_idxset_size(avail_devices); + device_none = pa_idxset_get_by_data(avail_devices, "none", NULL); + if (list_len == 0 || device_none) { + pa_log_warn("there is no available device, stream_type(%d)", type); + break; + } + select_data->idx_avail_devices = avail_devices; + if (si->route_type == STREAM_ROUTE_TYPE_MANUAL) { + p_idx = pa_proplist_gets(GET_STREAM_NEW_PROPLIST(stream, type), PA_PROP_MEDIA_PARENT_ID); + if (p_idx && !pa_atou(p_idx, &parent_idx)) { + /* find parent idx, it's device info. and it's stream idxs */ + sp = pa_hashmap_get(m->stream_parents, (const void*)parent_idx); + if (sp) + select_data->idx_manual_devices = (type==STREAM_SINK_INPUT)?(sp->idx_route_out_devices):(sp->idx_route_in_devices); + else + pa_log_warn("Failed to get the stream parent of idx(%u)", idx); + } + } + break; + } + case NOTIFY_COMMAND_CHANGE_ROUTE_START_WITH_NEW_DATA: + case NOTIFY_COMMAND_CHANGE_ROUTE_START: + case NOTIFY_COMMAND_CHANGE_ROUTE_END: { + route_data = (pa_stream_manager_hook_data_for_route*)hook_data; + si = pa_hashmap_get(m->stream_infos, route_data->stream_role); + avail_devices = (type==STREAM_SINK_INPUT)?si->idx_avail_out_devices:si->idx_avail_in_devices; + route_data->route_type = si->route_type; + list_len = pa_idxset_size(avail_devices); + device_none = pa_idxset_get_by_data(avail_devices, "none", NULL); + + if (command == NOTIFY_COMMAND_CHANGE_ROUTE_START_WITH_NEW_DATA) + p_idx = pa_proplist_gets(GET_STREAM_NEW_PROPLIST(stream, type), PA_PROP_MEDIA_PARENT_ID); + else if (command == NOTIFY_COMMAND_CHANGE_ROUTE_START || command == NOTIFY_COMMAND_CHANGE_ROUTE_END) + p_idx = pa_proplist_gets(GET_STREAM_PROPLIST(stream, type), PA_PROP_MEDIA_PARENT_ID); + if (p_idx && !pa_atou(p_idx, &parent_idx)) { + sp = pa_hashmap_get(m->stream_parents, (const void*)parent_idx); + if (!sp) + pa_log_warn("Failed to get the stream parent of idx(%u)", parent_idx); + } else + pa_log_warn("Could not get the parent id of this stream, but keep going..."); + + if (list_len == 0 || device_none) { + pa_log_warn("there is no available device, stream_type(%d)", type); + break; + } + route_data->idx_avail_devices = avail_devices; + if (si->route_type == STREAM_ROUTE_TYPE_MANUAL) { + if (sp) { + route_data->idx_manual_devices = (type==STREAM_SINK_INPUT)?(sp->idx_route_out_devices):(sp->idx_route_in_devices); + route_data->idx_streams = (type==STREAM_SINK_INPUT)?(sp->idx_sink_inputs):(sp->idx_source_outputs); + } else + pa_log_warn("Failed to get the stream parent of idx(%u)", parent_idx); + } + break; + } + default: + break; + } + return; +} + +static void do_notify(pa_stream_manager *m, notify_command_type_t command, stream_type_t type, void *user_data) { + pa_stream_manager_hook_data_for_select hook_call_select_data; + pa_stream_manager_hook_data_for_route hook_call_route_data; + pa_stream_manager_hook_data_for_option hook_call_option_data; + hal_stream_connection_info stream_conn_info; + const char *role = NULL; + void *s = NULL; + + pa_assert(m); + pa_log_debug("do_notify(%s): type(%d), user_data(%p)", notify_command_type_str[command], type, user_data); + + switch (command) { + case NOTIFY_COMMAND_SELECT_PROPER_SINK_OR_SOURCE_FOR_INIT: { + pa_assert(user_data); + memset(&hook_call_select_data, 0, sizeof(pa_stream_manager_hook_data_for_select)); + s = user_data; + if (s) { + hook_call_select_data.stream_type = type; + hook_call_select_data.stream_role = pa_proplist_gets(GET_STREAM_NEW_PROPLIST(s, type), PA_PROP_MEDIA_ROLE); + fill_device_info_to_hook_data(&hook_call_select_data, command, type, s, m); + hook_call_select_data.sample_spec = GET_STREAM_NEW_SAMPLE_SPEC(s, type); + if (type == STREAM_SINK_INPUT) + hook_call_select_data.proper_sink = &(((pa_sink_input_new_data*)s)->sink); + else if (type == STREAM_SOURCE_OUTPUT) + hook_call_select_data.proper_source = &(((pa_source_output_new_data*)s)->source); + pa_hook_fire(pa_communicator_hook(m->comm.comm, PA_COMMUNICATOR_HOOK_SELECT_INIT_SINK_OR_SOURCE), &hook_call_select_data); + } + break; + } + case NOTIFY_COMMAND_CHANGE_ROUTE_START_WITH_NEW_DATA: + case NOTIFY_COMMAND_CHANGE_ROUTE_START: { + pa_assert(user_data); + memset(&hook_call_route_data, 0, sizeof(pa_stream_manager_hook_data_for_route)); + s = user_data; + if (s) { + if (command == NOTIFY_COMMAND_CHANGE_ROUTE_START_WITH_NEW_DATA) { + hook_call_route_data.origins_from_new_data = TRUE; + role = pa_proplist_gets(GET_STREAM_NEW_PROPLIST(s, type), PA_PROP_MEDIA_ROLE); + hook_call_route_data.sample_spec = GET_STREAM_NEW_SAMPLE_SPEC(s, type); + if (type == STREAM_SINK_INPUT) { + hook_call_route_data.proper_sink = &(((pa_sink_input_new_data*)s)->sink); + } else if (type == STREAM_SOURCE_OUTPUT) { + hook_call_route_data.proper_source = &(((pa_source_output_new_data*)s)->source); + } + } else { + role = pa_proplist_gets(GET_STREAM_PROPLIST(s, type), PA_PROP_MEDIA_ROLE); + hook_call_route_data.sample_spec = GET_STREAM_SAMPLE_SPEC(s, type); + if (type == STREAM_SINK_INPUT) { + if (((pa_sink_input*)s)->sink) + hook_call_route_data.idx_streams = ((pa_sink_input*)s)->sink->inputs; + } else if (type == STREAM_SOURCE_OUTPUT) { + if (((pa_source_output*)s)->source) + hook_call_route_data.idx_streams = ((pa_source_output*)s)->source->outputs; + } + } + hook_call_route_data.stream_type = type; + hook_call_route_data.stream_role = role; + fill_device_info_to_hook_data(&hook_call_route_data, command, type, s, m); + if (hook_call_route_data.route_type == STREAM_ROUTE_TYPE_MANUAL) { + if (hook_call_route_data.idx_manual_devices && !pa_idxset_size(hook_call_route_data.idx_manual_devices)) { + pa_log_info("no manual device for this type(%d), need to unset route", type); + hook_call_route_data.stream_role = "reset"; + } + } + pa_hook_fire(pa_communicator_hook(m->comm.comm, PA_COMMUNICATOR_HOOK_CHANGE_ROUTE), &hook_call_route_data); + } + break; + } + case NOTIFY_COMMAND_CHANGE_ROUTE_END: { + memset(&hook_call_route_data, 0, sizeof(pa_stream_manager_hook_data_for_route)); + s = (type==STREAM_SINK_INPUT)?(void*)(m->cur_highest_priority.sink_input):(void*)(m->cur_highest_priority.source_output); + if (s) { + role = pa_proplist_gets(GET_STREAM_PROPLIST(s, type), PA_PROP_MEDIA_ROLE); + hook_call_route_data.stream_type = type; + hook_call_route_data.stream_role = role; + hook_call_route_data.sample_spec = GET_STREAM_SAMPLE_SPEC(s, type); + hook_call_route_data.idx_streams = (type==STREAM_SINK_INPUT)?((pa_sink_input*)s)->sink->inputs:((pa_source_output*)s)->source->outputs; + fill_device_info_to_hook_data(&hook_call_route_data, command, type, s, m); + } else { + pa_log_info("no stream for this type(%d), need to unset route", type); + hook_call_route_data.stream_type = type; + hook_call_route_data.stream_role = "reset"; + } + pa_hook_fire(pa_communicator_hook(m->comm.comm, PA_COMMUNICATOR_HOOK_CHANGE_ROUTE), &hook_call_route_data); + break; + } + case NOTIFY_COMMAND_UPDATE_ROUTE_OPTION: { + pa_assert(user_data); + memset(&hook_call_option_data, 0, sizeof(pa_stream_manager_hook_data_for_option)); + s = (type==STREAM_SINK_INPUT)?(void*)(m->cur_highest_priority.sink_input):(void*)(m->cur_highest_priority.source_output); + if (s) { + role = pa_proplist_gets(GET_STREAM_PROPLIST(s, type), PA_PROP_MEDIA_ROLE); + hook_call_option_data.stream_role = role; + hook_call_option_data.name = ((stream_route_option*)user_data)->name; + hook_call_option_data.value = ((stream_route_option*)user_data)->value; + pa_hook_fire(pa_communicator_hook(m->comm.comm, PA_COMMUNICATOR_HOOK_UPDATE_ROUTE_OPTION), &hook_call_option_data); + } + break; + } + case NOTIFY_COMMAND_INFORM_STREAM_CONNECTED: + case NOTIFY_COMMAND_INFORM_STREAM_DISCONNECTED: { + pa_assert(user_data); + memset(&stream_conn_info, 0, sizeof(hal_stream_connection_info)); + s = user_data; + if (s) { + stream_conn_info.role = pa_proplist_gets(GET_STREAM_PROPLIST(s, type), PA_PROP_MEDIA_ROLE); + stream_conn_info.direction = (type==STREAM_SINK_INPUT)?DIRECTION_OUT:DIRECTION_IN; + stream_conn_info.idx = (type==STREAM_SINK_INPUT)?((pa_sink_input*)s)->index:((pa_source_output*)s)->index; + stream_conn_info.is_connected = (command == NOTIFY_COMMAND_INFORM_STREAM_CONNECTED)?TRUE:FALSE; + pa_hal_manager_update_stream_connection_info(m->hal, &stream_conn_info); + } + break; + } + } + return; +} + +static process_stream_result_t process_stream(stream_type_t type, void *stream, process_command_type_t command, pa_stream_manager *m) { + process_stream_result_t result = PROCESS_STREAM_RESULT_OK; + const char *role = NULL; + pa_bool_t ret = TRUE; + pa_bool_t need_update = FALSE; + int32_t volume_ret = 0; + volume_info *v = NULL; + const char *si_volume_type_str = NULL; + const int32_t *prior_priority = NULL; + size_t size = 0; + pa_format_info *req_format = NULL; + char *format_str = NULL; + const char *rate_str = NULL; + const char *ch_str = NULL; + + pa_log_info("START process_stream(%s): stream_type(%d), stream(%p), m(%p)", process_command_type_str[command], type, stream, m); + pa_assert(stream); + pa_assert(m); + + if (check_name_to_skip(m, command, type, stream)) { + result = PROCESS_STREAM_RESULT_SKIP; + goto FAILURE; + } + + if (command == PROCESS_COMMAND_PREPARE) { + if (type == STREAM_SINK_INPUT) { + /* Parse request formats for samplerate, channel, format infomation */ + if (((pa_sink_input_new_data*)stream)->req_formats) { + req_format = pa_idxset_first(((pa_sink_input_new_data*)stream)->req_formats, NULL); + if (req_format && req_format->plist) { + /* set sample_spec */ + rate_str = pa_proplist_gets(req_format->plist, PA_PROP_FORMAT_RATE); + ch_str = pa_proplist_gets(req_format->plist, PA_PROP_FORMAT_CHANNELS); + if (pa_format_info_get_prop_string(req_format, PA_PROP_FORMAT_SAMPLE_FORMAT, &format_str)==0) + ((pa_sink_input_new_data*)stream)->sample_spec.format = pa_parse_sample_format((const char*)format_str); + pa_log_info("req rate(%s), req ch(%s), req format(%s)", rate_str, ch_str, format_str); + if (ch_str) + ((pa_sink_input_new_data*)stream)->sample_spec.channels = atoi (ch_str); + if (rate_str) + ((pa_sink_input_new_data*)stream)->sample_spec.rate = atoi (rate_str); + /* set channel map if it is not set by client */ + if (!((pa_sink_input_new_data*)stream)->channel_map_is_set) { + pa_channel_map_init_auto(&(((pa_sink_input_new_data*)stream)->channel_map), ((pa_sink_input_new_data*)stream)->sample_spec.channels, PA_CHANNEL_MAP_ALSA); + pa_log_info("set default channel_map: channels(%u)",((pa_sink_input_new_data*)stream)->channel_map.channels); + ((pa_sink_input_new_data*)stream)->channel_map_is_set = TRUE; + } + } + } else { + pa_log_debug("no request formats available"); + } + } + role = pa_proplist_gets(GET_STREAM_NEW_PROPLIST(stream, type), PA_PROP_MEDIA_ROLE); + if (!role) { + /* set default value for role and priority */ + role = DEFAULT_ROLE; + pa_proplist_sets(GET_STREAM_NEW_PROPLIST(stream, type), PA_PROP_MEDIA_ROLE, role); + pa_log_warn("role is null, set default to [%s]", role); + } else { + /* skip roles */ + if (check_role_to_skip(m, role)) { + result = PROCESS_STREAM_RESULT_SKIP; + goto FAILURE; + } + } + /* update the priority of this stream */ + ret = update_priority_of_stream(m, command, type, stream, role); + if (ret == FALSE) { + pa_log_error("could not update the priority of '%s' role.", role); + result = PROCESS_STREAM_RESULT_STOP; + goto FAILURE; + } + /* update the volume type of this stream */ + ret = update_volume_type_of_stream(m, type, stream, role); + if (ret == FALSE) { + pa_log_error("could not update the volume type of '%s' role.", role); + result = PROCESS_STREAM_RESULT_STOP; + goto FAILURE; + } + /* update the routing type of this stream */ + ret = update_routing_type_of_stream(m, stream, type, role); + if (ret == FALSE) { + pa_log_error("could not update the route type of '%s' role.", role); + result = PROCESS_STREAM_RESULT_STOP; + goto FAILURE; + } + + /* notify to select sink or source */ + do_notify(m, NOTIFY_COMMAND_SELECT_PROPER_SINK_OR_SOURCE_FOR_INIT, type, stream); + + } else if (command == PROCESS_COMMAND_CHANGE_ROUTE_BY_STREAM_STARTED_WITH_NEW_DATA || + command == PROCESS_COMMAND_CHANGE_ROUTE_BY_STREAM_STARTED) { + if (command == PROCESS_COMMAND_CHANGE_ROUTE_BY_STREAM_STARTED_WITH_NEW_DATA) + role = pa_proplist_gets(GET_STREAM_NEW_PROPLIST(stream, type), PA_PROP_MEDIA_ROLE); + else + role = pa_proplist_gets(GET_STREAM_PROPLIST(stream, type), PA_PROP_MEDIA_ROLE); + + /* skip roles */ + if (check_role_to_skip(m, role)) { + result = PROCESS_STREAM_RESULT_SKIP; + goto FAILURE; + } + + if (command == PROCESS_COMMAND_CHANGE_ROUTE_BY_STREAM_STARTED ) { + /* update the priority of this stream */ + ret = update_priority_of_stream(m, command, type, stream, role); + if (ret == FALSE) { + pa_log_error("could not update the priority of '%s' role.", role); + result = PROCESS_STREAM_RESULT_STOP; + goto FAILURE; + } + } + + /* update the focus status */ + ret = update_focus_status_of_stream(m, command, stream, type); + if (ret == FALSE) + pa_log_warn("could not update focus status"); + + /* update the highest priority */ + ret = update_the_highest_priority_stream(m, command, stream, type, role, &need_update); + if (ret == FALSE) { + pa_log_error("could not update the highest priority stream"); + result = PROCESS_STREAM_RESULT_SKIP; + goto FAILURE; + } + + /* need to skip if this stream does not belong to internal device */ + /* if needed, notify to update */ + if (need_update) { + if (command == PROCESS_COMMAND_CHANGE_ROUTE_BY_STREAM_STARTED_WITH_NEW_DATA) { + do_notify(m, NOTIFY_COMMAND_CHANGE_ROUTE_START_WITH_NEW_DATA, type, stream); + if (type==STREAM_SINK_INPUT) + m->cur_highest_priority.need_to_update_si = TRUE; + else + m->cur_highest_priority.need_to_update_so = TRUE; + } else { + do_notify(m, NOTIFY_COMMAND_CHANGE_ROUTE_START, type, stream); + if (type==STREAM_SINK_INPUT) + m->cur_highest_priority.sink_input = stream; + else + m->cur_highest_priority.source_output = stream; + } + } + if (command == PROCESS_COMMAND_CHANGE_ROUTE_BY_STREAM_STARTED) + do_notify(m, NOTIFY_COMMAND_INFORM_STREAM_CONNECTED, type, stream); + + } else if (command == PROCESS_COMMAND_CHANGE_ROUTE_BY_STREAM_ENDED) { + role = pa_proplist_gets(GET_STREAM_PROPLIST(stream, type), PA_PROP_MEDIA_ROLE); + if (role) { + /* skip roles */ + if (check_role_to_skip(m, role)) { + result = PROCESS_STREAM_RESULT_SKIP; + goto FAILURE; + } + + /* check if it has already been processed (unlink or state_changed_cb) */ + if (pa_proplist_get(GET_STREAM_PROPLIST(stream, type), PA_PROP_MEDIA_ROLE_PRIORITY, (const void**)&prior_priority, &size)) { + pa_log_debug("it has already been processed for PROCESS_COMMAND_CHANGE_ROUTE_BY_STREAM_ENDED, skip it.."); + result = PROCESS_STREAM_RESULT_SKIP; + goto FAILURE; + } + + pa_proplist_unset(GET_STREAM_PROPLIST(stream, type), PA_PROP_MEDIA_ROLE_PRIORITY); + ret = update_the_highest_priority_stream(m, command, stream, type, role, &need_update); + if (ret == FALSE) { + pa_log_error("could not update the highest priority stream"); + result = PROCESS_STREAM_RESULT_STOP; + goto FAILURE; + } + + do_notify(m, NOTIFY_COMMAND_INFORM_STREAM_DISCONNECTED, type, stream); + + /* need to skip if this stream does not belong to internal device */ + /* if needed, notify to update */ + if (need_update) + do_notify(m, NOTIFY_COMMAND_CHANGE_ROUTE_END, type, NULL); + + } else { + pa_log_error("role is null, skip it"); + } + + } else if (command == PROCESS_COMMAND_CHANGE_ROUTE_BY_STREAM_FOCUS_CHANGED) { + role = pa_proplist_gets(GET_STREAM_PROPLIST(stream, type), PA_PROP_MEDIA_ROLE); + if (role) { + /* skip roles */ + if (check_role_to_skip(m, role)) { + result = PROCESS_STREAM_RESULT_SKIP; + goto FAILURE; + } + + ret = update_the_highest_priority_stream(m, command, stream, type, role, &need_update); + if (ret == FALSE) { + pa_log_error("could not update the highest priority stream"); + result = PROCESS_STREAM_RESULT_STOP; + goto FAILURE; + } + + /* need to skip if this stream does not belong to internal device */ + /* if needed, notify to update */ + if (need_update) + do_notify(m, NOTIFY_COMMAND_CHANGE_ROUTE_END, type, NULL); + + } else { + pa_log_error("role is null, skip it"); + } + + } else if (command == PROCESS_COMMAND_UPDATE_VOLUME) { + if ((si_volume_type_str = pa_proplist_gets(GET_STREAM_NEW_PROPLIST(stream, type), PA_PROP_MEDIA_TIZEN_VOLUME_TYPE))) { + v = pa_hashmap_get(m->volume_infos, si_volume_type_str); + if (v && v->values[(type==STREAM_SINK_INPUT)?STREAM_DIRECTION_OUT:STREAM_DIRECTION_IN].idx_volume_values) { + /* Update volume-level */ + volume_ret = set_volume_level_with_new_data(m, type, stream, v->values[(type==STREAM_SINK_INPUT)?STREAM_DIRECTION_OUT:STREAM_DIRECTION_IN].current_level); + if (volume_ret) + pa_log_error("failed to set_volume_level_by_idx(), stream_type(%d), level(%u), ret(0x%x)", + STREAM_SINK_INPUT, v->values[(type==STREAM_SINK_INPUT)?STREAM_DIRECTION_OUT:STREAM_DIRECTION_IN].current_level, volume_ret); + /* Update volume-mute */ + volume_ret = set_volume_mute_with_new_data(m, type, stream, v->values[(type==STREAM_SINK_INPUT)?STREAM_DIRECTION_OUT:STREAM_DIRECTION_IN].is_muted); + if (volume_ret) + pa_log_error("failed to set_volume_mute_by_idx(), stream_type(%d), mute(%d), ret(0x%x)", + STREAM_SINK_INPUT, v->values[(type==STREAM_SINK_INPUT)?STREAM_DIRECTION_OUT:STREAM_DIRECTION_IN].is_muted, volume_ret); + } + } + + } else if (command == PROCESS_COMMAND_ADD_PARENT_ID || command == PROCESS_COMMAND_REMOVE_PARENT_ID) { + if (command == PROCESS_COMMAND_ADD_PARENT_ID) { + if (type == STREAM_SINK_INPUT && m->cur_highest_priority.need_to_update_si) { + m->cur_highest_priority.sink_input = stream; + m->cur_highest_priority.need_to_update_si = FALSE; + } + if (type == STREAM_SOURCE_OUTPUT && m->cur_highest_priority.need_to_update_so) { + m->cur_highest_priority.source_output = stream; + m->cur_highest_priority.need_to_update_so = FALSE; + } + if (command == PROCESS_COMMAND_ADD_PARENT_ID) + do_notify(m, NOTIFY_COMMAND_INFORM_STREAM_CONNECTED, type, stream); + } + /* update parent stream info. */ + ret = update_stream_parent_info(m, command, type, stream); + if (ret == FALSE) { + pa_log_warn("could not update the parent information of this stream"); + //return PROCESS_STREAM_RESULT_STOP; + } + } + +FAILURE: + pa_log_info("END process_stream(%s): result(%d)", process_command_type_str[command], result); + return result; +} + +static void update_buffer_attribute(stream_type_t stream_type, void *new_data, pa_stream_manager *m) { + int32_t maxlength = -1; + int32_t tlength = -1; + int32_t prebuf = -1; + int32_t minreq = -1; + int32_t fragsize = -1; + const char* audio_latency = NULL; + + pa_assert(m); + pa_assert(new_data); + + if (m->hal == NULL) + return; + + audio_latency = pa_proplist_gets(GET_STREAM_NEW_PROPLIST(new_data, stream_type), PA_PROP_MEDIA_TIZEN_AUDIO_LATENCY); + pa_log_info("audio_latency : %s", audio_latency); + if (audio_latency == NULL) + return; + + if (!pa_hal_manager_get_buffer_attribute(m->hal, (io_direction_t)!stream_type, audio_latency, new_data, (uint32_t*)&maxlength, (uint32_t*)&tlength, (uint32_t*)&prebuf, (uint32_t*)&minreq, (uint32_t*)&fragsize)) { + pa_log_info(" - maxlength:%d, tlength:%d, prebuf:%d, minreq:%d, fragsize:%d", maxlength, tlength, prebuf, minreq, fragsize); + pa_proplist_setf(GET_STREAM_NEW_PROPLIST(new_data, stream_type), "maxlength", "%d", maxlength); + pa_proplist_setf(GET_STREAM_NEW_PROPLIST(new_data, stream_type), "tlength", "%d", tlength); + pa_proplist_setf(GET_STREAM_NEW_PROPLIST(new_data, stream_type), "prebuf", "%d", prebuf); + pa_proplist_setf(GET_STREAM_NEW_PROPLIST(new_data, stream_type), "minreq", "%d", minreq); + pa_proplist_setf(GET_STREAM_NEW_PROPLIST(new_data, stream_type), "fragsize", "%d", fragsize); + } + + return; +} + +static pa_hook_result_t sink_input_new_cb(pa_core *core, pa_sink_input_new_data *new_data, pa_stream_manager *m) { + pa_core_assert_ref(core); + + pa_log_info("start sink_input_new_cb"); + + process_stream(STREAM_SINK_INPUT, new_data, PROCESS_COMMAND_PREPARE, m); + /* Update buffer attributes from HAL */ + update_buffer_attribute(STREAM_SINK_INPUT, new_data, m); + process_stream(STREAM_SINK_INPUT, new_data, PROCESS_COMMAND_UPDATE_VOLUME, m); + process_stream(STREAM_SINK_INPUT, new_data, PROCESS_COMMAND_CHANGE_ROUTE_BY_STREAM_STARTED_WITH_NEW_DATA, m); + + return PA_HOOK_OK; +} + +static pa_hook_result_t sink_input_put_cb(pa_core *core, pa_sink_input *i, pa_stream_manager *m) { + pa_core_assert_ref(core); + pa_sink_input_assert_ref(i); + + pa_log_info("start sink_input_put_cb, i(%p, index:%u)", i, i->index); + + process_stream(STREAM_SINK_INPUT, i, PROCESS_COMMAND_ADD_PARENT_ID, m); + + return PA_HOOK_OK; +} + +static pa_hook_result_t sink_input_unlink_cb(pa_core *core, pa_sink_input *i, pa_stream_manager *m) { + pa_core_assert_ref(core); + pa_sink_input_assert_ref(i); + + pa_log_info("start sink_input_unlink_cb, i(%p, index:%u)", i, i->index); + process_stream(STREAM_SINK_INPUT, i, PROCESS_COMMAND_REMOVE_PARENT_ID, m); + process_stream(STREAM_SINK_INPUT, i, PROCESS_COMMAND_CHANGE_ROUTE_BY_STREAM_ENDED, m); + + return PA_HOOK_OK; +} + +static pa_hook_result_t sink_input_state_changed_cb(pa_core *core, pa_sink_input *i, pa_stream_manager *m) { + pa_sink_input_state_t state; + + pa_assert(i); + pa_assert(m); + + state = pa_sink_input_get_state(i); + pa_log_info("start sink_input_state_changed_cb(), sink-input(%p), state(%d)", i, state); + + switch(state) { + case PA_SINK_INPUT_CORKED: { + process_stream(STREAM_SINK_INPUT, i, PROCESS_COMMAND_CHANGE_ROUTE_BY_STREAM_ENDED, m); + break; + } + case PA_SINK_INPUT_DRAINED: + case PA_SINK_INPUT_RUNNING: { + process_stream(STREAM_SINK_INPUT, i, PROCESS_COMMAND_CHANGE_ROUTE_BY_STREAM_STARTED, m); + break; + } + default: + break; + } + + return PA_HOOK_OK; +} + +static pa_hook_result_t sink_input_move_start_cb(pa_core *core, pa_sink_input *i, pa_stream_manager *m) { + pa_core_assert_ref(core); + pa_sink_input_assert_ref(i); + + /* There's no point in doing anything if the core is shut down anyway */ + if (core->state == PA_CORE_SHUTDOWN) + return PA_HOOK_OK; + + pa_log_debug ("sink_input_move_start_cb, i(%p, index:%u)", i, i->index); + + set_volume_mute_by_idx(m, STREAM_SINK_INPUT, i->index, TRUE); + + return PA_HOOK_OK; +} + +static pa_hook_result_t sink_input_move_finish_cb(pa_core *core, pa_sink_input *i, pa_stream_manager *m) { + pa_core_assert_ref(core); + pa_sink_input_assert_ref(i); + + /* There's no point in doing anything if the core is shut down anyway */ + if (core->state == PA_CORE_SHUTDOWN) + return PA_HOOK_OK; + + pa_log_debug ("sink_input_move_finish_cb, i(%p, index:%u)", i, i->index); + + set_volume_mute_by_idx(m, STREAM_SINK_INPUT, i->index, FALSE); + + return PA_HOOK_OK; +} + +static pa_hook_result_t source_output_new_cb(pa_core *core, pa_source_output_new_data *new_data, pa_stream_manager *m) { + pa_core_assert_ref(core); + + pa_log_info("start source_output_new_new_cb"); + + process_stream(STREAM_SOURCE_OUTPUT, new_data, PROCESS_COMMAND_PREPARE, m); + /* Update buffer attributes from HAL */ + update_buffer_attribute(STREAM_SOURCE_OUTPUT, new_data, m); + process_stream(STREAM_SOURCE_OUTPUT, new_data, PROCESS_COMMAND_UPDATE_VOLUME, m); + process_stream(STREAM_SOURCE_OUTPUT, new_data, PROCESS_COMMAND_CHANGE_ROUTE_BY_STREAM_STARTED_WITH_NEW_DATA, m); + + return PA_HOOK_OK; +} + +static pa_hook_result_t source_output_put_cb(pa_core *core, pa_source_output *o, pa_stream_manager *m) { + pa_core_assert_ref(core); + pa_source_output_assert_ref(o); + + pa_log_info("start source_output_put_cb, o(%p, index:%u)", o, o->index); + + process_stream(STREAM_SOURCE_OUTPUT, o, PROCESS_COMMAND_ADD_PARENT_ID, m); + + return PA_HOOK_OK; +} + +static pa_hook_result_t source_output_unlink_cb(pa_core *core, pa_source_output *o, pa_stream_manager *m) { + pa_core_assert_ref(core); + pa_source_output_assert_ref(o); + + pa_log_info("start source_output_unlink_cb, o(%p, index:%u)", o, o->index); + + process_stream(STREAM_SOURCE_OUTPUT, o, PROCESS_COMMAND_REMOVE_PARENT_ID, m); + process_stream(STREAM_SOURCE_OUTPUT, o, PROCESS_COMMAND_CHANGE_ROUTE_BY_STREAM_ENDED, m); + + return PA_HOOK_OK; +} + +static pa_hook_result_t source_output_state_changed_cb(pa_core *core, pa_source_output *o, pa_stream_manager *m) { + pa_source_output_state_t state; + + pa_assert(o); + pa_assert(m); + + state = pa_source_output_get_state(o); + pa_log_debug("start source_output_state_changed_cb(), source-output(%p), state(%d)", o, state); + + switch(state) { + case PA_SOURCE_OUTPUT_CORKED: { + process_stream(STREAM_SOURCE_OUTPUT, o, PROCESS_COMMAND_CHANGE_ROUTE_BY_STREAM_ENDED, m); + break; + } + case PA_SOURCE_OUTPUT_RUNNING: { + process_stream(STREAM_SOURCE_OUTPUT, o, PROCESS_COMMAND_CHANGE_ROUTE_BY_STREAM_STARTED, m); + break; + } + default: + break; + } + + return PA_HOOK_OK; +} + +/* Reorganize routing when a device has been connected or disconnected */ +static pa_hook_result_t device_connection_changed_hook_cb(pa_core *c, pa_device_manager_hook_data_for_conn_changed *conn, pa_stream_manager *m) { + const char *route_type_str = NULL; + stream_route_type_t route_type; + dm_device_direction_t device_direction = DM_DEVICE_DIRECTION_OUT; + + device_direction = pa_device_manager_get_device_direction(conn->device); + pa_log_info("device_connection_changed_hook_cb is called. conn(%p), is_connected(%d), device(%p), direction(0x%x)", + conn, conn->is_connected, conn->device, device_direction); + + /* If the route type of the stream is not manual, notify again */ + if (m->cur_highest_priority.source_output && (device_direction & DM_DEVICE_DIRECTION_IN)) { + route_type_str = pa_proplist_gets(GET_STREAM_PROPLIST(m->cur_highest_priority.source_output, STREAM_SOURCE_OUTPUT), PA_PROP_MEDIA_ROLE_ROUTE_TYPE); + if(!pa_atoi(route_type_str, (int32_t*)&route_type)) + if (route_type != STREAM_ROUTE_TYPE_MANUAL) + do_notify(m, NOTIFY_COMMAND_CHANGE_ROUTE_START, STREAM_SOURCE_OUTPUT, m->cur_highest_priority.source_output); + } if (m->cur_highest_priority.sink_input && (device_direction & DM_DEVICE_DIRECTION_OUT)) { + route_type_str = pa_proplist_gets(GET_STREAM_PROPLIST(m->cur_highest_priority.sink_input, STREAM_SINK_INPUT), PA_PROP_MEDIA_ROLE_ROUTE_TYPE); + if(!pa_atoi(route_type_str, (int32_t*)&route_type)) + if (route_type != STREAM_ROUTE_TYPE_MANUAL) + do_notify(m, NOTIFY_COMMAND_CHANGE_ROUTE_START, STREAM_SINK_INPUT, m->cur_highest_priority.sink_input); + } + + return PA_HOOK_OK; +} + +/* Reorganize routing when device information has been changed */ +static pa_hook_result_t device_information_changed_hook_cb(pa_core *c, pa_device_manager_hook_data_for_info_changed *info, pa_stream_manager *m) { + pa_log_info("device_information_changed_hook_cb is called. info(%p), changed_info(%d), device(%p)", + info, info->changed_info, info->device); + + return PA_HOOK_OK; +} + +static void subscribe_cb(pa_core *core, pa_subscription_event_type_t t, uint32_t idx, pa_stream_manager *m) { + pa_client *client = NULL; + stream_parent *sp = NULL; + const char *name = NULL; + uint32_t *device_id = NULL; + uint32_t _idx = 0; + pa_core_assert_ref(core); + pa_assert(m); + pa_log_info("subscribe_cb() is called, t(%x), idx(%u)", t, idx); + + if (t == (PA_SUBSCRIPTION_EVENT_CLIENT|PA_SUBSCRIPTION_EVENT_CHANGE)) { + client = pa_idxset_get_by_index(core->clients, idx); + if (client == NULL) { + pa_log_error(" - could not find any client that has idx(%u)", idx); + return; + } + name = pa_proplist_gets(client->proplist, PA_PROP_APPLICATION_NAME); + if (strncmp (name, STREAM_MANAGER_CLIENT_NAME, strlen(STREAM_MANAGER_CLIENT_NAME))) { + pa_log_warn(" - this is not a client(%s) that we should take care of, skip it", name); + return; + } + /* add a stream parent */ + sp = pa_xmalloc0(sizeof(stream_parent)); + sp->idx_sink_inputs = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + sp->idx_source_outputs = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + sp->idx_route_in_devices = pa_idxset_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + sp->idx_route_out_devices = pa_idxset_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + pa_hashmap_put(m->stream_parents, (void*)idx, sp); + pa_log_debug(" - add sp(%p), idx(%u)", sp, idx); + } else if (t == (PA_SUBSCRIPTION_EVENT_CLIENT|PA_SUBSCRIPTION_EVENT_REMOVE)) { + /* remove the stream parent */ + sp = pa_hashmap_get(m->stream_parents, (const void*)idx); + if (sp) { + pa_log_debug(" - remove sp(%p), idx(%u)", sp, idx); + pa_hashmap_remove(m->stream_parents, (const void*)idx); + if (sp->idx_route_in_devices) + PA_IDXSET_FOREACH(device_id, sp->idx_route_in_devices, _idx) + pa_xfree(device_id); + if (sp->idx_route_out_devices) + PA_IDXSET_FOREACH(device_id, sp->idx_route_out_devices, _idx) + pa_xfree(device_id); + pa_idxset_free(sp->idx_sink_inputs, NULL); + pa_idxset_free(sp->idx_source_outputs, NULL); + pa_idxset_free(sp->idx_route_in_devices, NULL); + pa_idxset_free(sp->idx_route_out_devices, NULL); + pa_xfree(sp); + } else { + pa_log_error(" - could not find any stream_parent that has idx(%u)", idx); + } + } +} + +static int init_ipc (pa_stream_manager *m) { +#ifdef HAVE_DBUS +#ifdef USE_DBUS_PROTOCOL + pa_assert(m); + pa_log_info("Initialization for IPC"); + m->dbus_protocol = pa_dbus_protocol_get(m->core); + pa_assert_se(pa_dbus_protocol_add_interface(m->dbus_protocol, STREAM_MANAGER_OBJECT_PATH, &stream_manager_interface_info, m) >= 0); + pa_assert_se(pa_dbus_protocol_register_extension(m->dbus_protocol, STREAM_MANAGER_INTERFACE) >= 0); +#else + DBusError err; + pa_dbus_connection *conn = NULL; + static const DBusObjectPathVTable vtable = { + .message_function = method_handler_for_vt, + }; + + pa_assert(m); + pa_log_info("Initialization for IPC"); + + dbus_error_init(&err); + + if (!(conn = pa_dbus_bus_get(m->core, DBUS_BUS_SYSTEM, &err)) || dbus_error_is_set(&err)) { + if (conn) { + pa_dbus_connection_unref(conn); + } + pa_log_error("Unable to contact D-Bus system bus: %s: %s", err.name, err.message); + goto fail; + } else { + pa_log_notice("Got dbus connection"); + } + m->dbus_conn = conn; + pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(conn), STREAM_MANAGER_OBJECT_PATH, &vtable, m)); +#endif +#else + pa_log_error("DBUS is not supported\n"); + goto fail; +#endif + + return 0; +fail: + pa_log_error("Failed to initialize stream manager ipc"); + return -1; +} + +static void deinit_ipc (pa_stream_manager *m) { + pa_assert(m); + +#ifdef HAVE_DBUS +#ifdef USE_DBUS_PROTOCOL + if (m->dbus_protocol) { + pa_assert_se(pa_dbus_protocol_unregister_extension(m->dbus_protocol, STREAM_MANAGER_INTERFACE) >= 0); + pa_assert_se(pa_dbus_protocol_remove_interface(m->dbus_protocol, STREAM_MANAGER_OBJECT_PATH, stream_manager_interface_info.name) >= 0); + pa_dbus_protocol_unref(m->dbus_protocol); + m->dbus_protocol = NULL; + } +#else + if (m->dbus_conn) { + if(!dbus_connection_unregister_object_path(pa_dbus_connection_get(m->dbus_conn), STREAM_MANAGER_OBJECT_PATH)) + pa_log_error("Failed to unregister object path"); + m->dbus_conn = NULL; + } +#endif +#endif + return; +} + +pa_stream_manager* pa_stream_manager_init(pa_core *c) { + pa_stream_manager *m; + + pa_assert(c); + + m = pa_xnew0(pa_stream_manager, 1); + m->core = c; + + m->hal = pa_hal_manager_get(c, NULL); + +#ifdef HAVE_DBUS +#ifdef USE_DBUS_PROTOCOL + m->dbus_protocol = NULL; +#else + m->dbus_conn = NULL; +#endif +#endif + if (init_ipc(m)) + goto fail; + + if (init_stream_map(m)) + goto fail; + + if (init_volumes(m)) + goto fail; + + m->stream_parents = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + + m->sink_input_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_NEW], PA_HOOK_EARLY, (pa_hook_cb_t) sink_input_new_cb, m); + m->sink_input_put_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_PUT], PA_HOOK_EARLY, (pa_hook_cb_t) sink_input_put_cb, m); + m->sink_input_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_UNLINK], PA_HOOK_EARLY, (pa_hook_cb_t) sink_input_unlink_cb, m); + m->sink_input_state_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_STATE_CHANGED], PA_HOOK_EARLY, (pa_hook_cb_t) sink_input_state_changed_cb, m); + m->sink_input_move_start_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE_START], PA_HOOK_EARLY, (pa_hook_cb_t) sink_input_move_start_cb, m); + m->sink_input_move_finish_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE_FINISH], PA_HOOK_EARLY, (pa_hook_cb_t) sink_input_move_finish_cb, m); + m->source_output_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_NEW], PA_HOOK_EARLY, (pa_hook_cb_t) source_output_new_cb, m); + m->source_output_put_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_PUT], PA_HOOK_EARLY, (pa_hook_cb_t) source_output_put_cb, m); + m->source_output_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_UNLINK], PA_HOOK_EARLY, (pa_hook_cb_t) source_output_unlink_cb, m); + m->source_output_state_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_STATE_CHANGED], PA_HOOK_EARLY, (pa_hook_cb_t) source_output_state_changed_cb, m); + + + m->subscription = pa_subscription_new(m->core, PA_SUBSCRIPTION_MASK_CLIENT | PA_SUBSCRIPTION_MASK_SAMPLE_CACHE, (pa_subscription_cb_t)subscribe_cb, m); + + m->comm.comm = pa_communicator_get(c); + m->comm.comm_hook_device_connection_changed_slot = pa_hook_connect(pa_communicator_hook(m->comm.comm,PA_COMMUNICATOR_HOOK_DEVICE_CONNECTION_CHANGED), + PA_HOOK_EARLY + 10, (pa_hook_cb_t) device_connection_changed_hook_cb, m); + m->comm.comm_hook_device_information_changed_slot = pa_hook_connect(pa_communicator_hook(m->comm.comm,PA_COMMUNICATOR_HOOK_DEVICE_INFORMATION_CHANGED), + PA_HOOK_EARLY, (pa_hook_cb_t) device_information_changed_hook_cb, m); + + return m; + +fail: + pa_log_error("Failed to initialize stream-manager"); + deinit_volumes(m); + deinit_stream_map(m); + deinit_ipc(m); + if (m->hal) + pa_hal_manager_unref(m->hal); + pa_xfree(m); + return 0; +} + +void pa_stream_manager_done(pa_stream_manager *m) { + pa_assert(m); + + if (m->comm.comm) { + if (m->comm.comm_hook_device_connection_changed_slot) + pa_hook_slot_free(m->comm.comm_hook_device_connection_changed_slot); + if (m->comm.comm_hook_device_information_changed_slot) + pa_hook_slot_free(m->comm.comm_hook_device_information_changed_slot); + pa_communicator_unref(m->comm.comm); + } + + if (m->subscription) + pa_subscription_free(m->subscription); + + if (m->sink_input_new_slot) + pa_hook_slot_free(m->sink_input_new_slot); + if (m->sink_input_put_slot) + pa_hook_slot_free(m->sink_input_put_slot); + if (m->sink_input_unlink_slot) + pa_hook_slot_free(m->sink_input_unlink_slot); + if (m->sink_input_state_changed_slot) + pa_hook_slot_free(m->sink_input_state_changed_slot); + if (m->sink_input_move_start_slot) + pa_hook_slot_free(m->sink_input_move_start_slot); + if (m->sink_input_move_finish_slot) + pa_hook_slot_free(m->sink_input_move_finish_slot); + if (m->source_output_new_slot) + pa_hook_slot_free(m->source_output_new_slot); + if (m->source_output_put_slot) + pa_hook_slot_free(m->source_output_put_slot); + if (m->source_output_unlink_slot) + pa_hook_slot_free(m->source_output_unlink_slot); + if (m->source_output_state_changed_slot) + pa_hook_slot_free(m->source_output_state_changed_slot); + + if (m->stream_parents) + pa_hashmap_free(m->stream_parents); + + deinit_volumes(m); + deinit_stream_map(m); + deinit_ipc(m); + + if (m->hal) + pa_hal_manager_unref(m->hal); + + pa_xfree(m); +} diff --git a/src/stream-manager.h b/src/stream-manager.h new file mode 100644 index 0000000..02dc5db --- /dev/null +++ b/src/stream-manager.h @@ -0,0 +1,51 @@ +#ifndef foostreammanagerfoo +#define foostreammanagerfoo +#include + +typedef struct _stream_manager pa_stream_manager; + +typedef enum _stream_type { + STREAM_SINK_INPUT, + STREAM_SOURCE_OUTPUT, +} stream_type_t; + +typedef enum stream_route_type { + STREAM_ROUTE_TYPE_AUTO, /* the policy of decision device(s) is automatic and it's routing path is particular to one device */ + STREAM_ROUTE_TYPE_AUTO_ALL, /* the policy of decision device(s) is automatic and it's routing path can be several devices */ + STREAM_ROUTE_TYPE_MANUAL, /* the policy of decision device(s) is manual */ +} stream_route_type_t; + +typedef struct _hook_call_data_for_select { + const char *stream_role; + stream_type_t stream_type; + stream_route_type_t route_type; + pa_sink **proper_sink; + pa_source **proper_source; + pa_sample_spec sample_spec; + pa_idxset *idx_avail_devices; + pa_idxset *idx_manual_devices; +} pa_stream_manager_hook_data_for_select; + +typedef struct _hook_call_data_for_route { + const char *stream_role; + stream_type_t stream_type; + stream_route_type_t route_type; + pa_sink **proper_sink; + pa_source **proper_source; + pa_sample_spec sample_spec; + pa_idxset *idx_avail_devices; + pa_idxset *idx_manual_devices; + pa_idxset *idx_streams; + pa_bool_t origins_from_new_data; +} pa_stream_manager_hook_data_for_route; + +typedef struct _hook_call_data_for_option { + const char *stream_role; + const char *name; + int32_t value; +} pa_stream_manager_hook_data_for_option; + +pa_stream_manager* pa_stream_manager_init(pa_core *c); +void pa_stream_manager_done(pa_stream_manager* m); + +#endif diff --git a/src/tizen-audio.h b/src/tizen-audio.h new file mode 100644 index 0000000..054d13e --- /dev/null +++ b/src/tizen-audio.h @@ -0,0 +1,341 @@ +#ifndef foopulsetizenaudiofoo +#define foopulsetizenaudiofoo + +/*** + This file is part of PulseAudio. + + PulseAudio 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. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +/* FIXME : This file should be separated from PA in future */ + +#include +#include +#include + +#define AUDIO_REVISION 1 + +/* Error code */ + +#define AUDIO_IS_ERROR(ret) (ret < 0) + +typedef enum audio_return { + AUDIO_RET_OK = 0, + AUDIO_ERR_UNDEFINED = (int32_t)0x80001000, + AUDIO_ERR_RESOURCE = (int32_t)0x80001001, + AUDIO_ERR_PARAMETER = (int32_t)0x80001002, + AUDIO_ERR_IOCTL = (int32_t)0x80001003, + AUDIO_ERR_NOT_IMPLEMENTED = (int32_t)0x80001004, +} audio_return_t ; + + +/* Session */ +typedef enum audio_session { + AUDIO_SESSION_MEDIA, + AUDIO_SESSION_VOICECALL, + AUDIO_SESSION_VIDEOCALL, + AUDIO_SESSION_VOIP, + AUDIO_SESSION_FMRADIO, + AUDIO_SESSION_CAMCORDER, + AUDIO_SESSION_NOTIFICATION, + AUDIO_SESSION_ALARM, + AUDIO_SESSION_EMERGENCY, + AUDIO_SESSION_VOICE_RECOGNITION, + AUDIO_SESSION_MAX +} audio_session_t; + +/* Sub session */ +typedef enum audio_subsession { + AUDIO_SUBSESSION_NONE, + AUDIO_SUBSESSION_VOICE, + AUDIO_SUBSESSION_RINGTONE, + AUDIO_SUBSESSION_MEDIA, + AUDIO_SUBSESSION_INIT, + AUDIO_SUBSESSION_VR_NORMAL, + AUDIO_SUBSESSION_VR_DRIVE, + AUDIO_SUBSESSION_STEREO_REC, + AUDIO_SUBSESSION_MONO_REC, + AUDIO_SUBSESSION_MAX +} audio_subsession_t; + +/* Session command */ +typedef enum audio_session_command { + AUDIO_SESSION_CMD_START, + AUDIO_SESSION_CMD_SUBSESSION, + AUDIO_SESSION_CMD_END, +} audio_session_command_t; + + +/* Direction */ +typedef enum audio_direction { + AUDIO_DIRECTION_IN, /**< Capture */ + AUDIO_DIRECTION_OUT, /**< Playback */ +} audio_direction_t; + + +/* Device */ + +typedef enum audio_device_in { + AUDIO_DEVICE_IN_NONE, + AUDIO_DEVICE_IN_MIC, /**< Device builtin mic. */ + AUDIO_DEVICE_IN_WIRED_ACCESSORY, /**< Wired input devices */ + AUDIO_DEVICE_IN_BT_SCO, /**< Bluetooth SCO device */ + AUDIO_DEVICE_IN_MAX, +} audio_device_in_t; + +typedef enum audio_device_out { + AUDIO_DEVICE_OUT_NONE, + AUDIO_DEVICE_OUT_SPEAKER, /**< Device builtin speaker */ + AUDIO_DEVICE_OUT_RECEIVER, /**< Device builtin receiver */ + AUDIO_DEVICE_OUT_WIRED_ACCESSORY, /**< Wired output devices such as headphone, headset, and so on. */ + AUDIO_DEVICE_OUT_BT_SCO, /**< Bluetooth SCO device */ + AUDIO_DEVICE_OUT_BT_A2DP, /**< Bluetooth A2DP device */ + AUDIO_DEVICE_OUT_DOCK, /**< DOCK device */ + AUDIO_DEVICE_OUT_HDMI, /**< HDMI device */ + AUDIO_DEVICE_OUT_MIRRORING, /**< MIRRORING device */ + AUDIO_DEVICE_OUT_USB_AUDIO, /**< USB Audio device */ + AUDIO_DEVICE_OUT_MULTIMEDIA_DOCK, /**< Multimedia DOCK device */ + AUDIO_DEVICE_OUT_MAX, +} audio_device_out_t; + +typedef enum audio_route_flag { + AUDIO_ROUTE_FLAG_NONE = 0, + AUDIO_ROUTE_FLAG_MUTE_POLICY = 0x00000001, + AUDIO_ROUTE_FLAG_DUAL_OUT = 0x00000002, + AUDIO_ROUTE_FLAG_NOISE_REDUCTION = 0x00000010, + AUDIO_ROUTE_FLAG_EXTRA_VOL = 0x00000020, + AUDIO_ROUTE_FLAG_NETWORK_WB = 0x00000040, + AUDIO_ROUTE_FLAG_BT_WB = 0x00000100, + AUDIO_ROUTE_FLAG_BT_NREC = 0x00000200, + AUDIO_ROUTE_FLAG_VOICE_COMMAND = 0x00040000, +} audio_route_flag_t; + +typedef enum audio_device_api { + AUDIO_DEVICE_API_UNKNOWN, + AUDIO_DEVICE_API_ALSA, + AUDIO_DEVICE_API_BLUEZ, +} audio_device_api_t; + +typedef enum audio_device_param { + AUDIO_DEVICE_PARAM_NONE, + AUDIO_DEVICE_PARAM_CHANNELS, + AUDIO_DEVICE_PARAM_SAMPLERATE, + AUDIO_DEVICE_PARAM_FRAGMENT_SIZE, + AUDIO_DEVICE_PARAM_FRAGMENT_NB, + AUDIO_DEVICE_PARAM_START_THRESHOLD, + AUDIO_DEVICE_PARAM_USE_MMAP, + AUDIO_DEVICE_PARAM_USE_TSCHED, + AUDIO_DEVICE_PARAM_TSCHED_BUF_SIZE, + AUDIO_DEVICE_PARAM_SUSPEND_TIMEOUT, + AUDIO_DEVICE_PARAM_ALTERNATE_RATE, + AUDIO_DEVICE_PARAM_MAX, +} audio_device_param_t; + +/* +enum audio_device_type { + AUDIO_DEVICE_NONE, + AUDIO_DEVICE_BUILTIN_SPEAKER, + AUDIO_DEVICE_BUILTIN_RECEIVER, + AUDIO_DEVICE_BUILTIN_MIC, + AUDIO_DEVICE_AUDIO_JACK, + AUDIO_DEVICE_BT, + AUDIO_DEVICE_HDMI, + AUDIO_DEVICE_AUX, + AUDIO_DEVICE_MAX +}; + +enum audio_device_direction_type{ + AUDIO_DEVICE_DIRECTION_IN, + AUDIO_DEVICE_DIRECTION_OUT +}; +*/ + +typedef struct audio_device_param_info { + audio_device_param_t param; + union { + int64_t s64_v; + uint64_t u64_v; + int32_t s32_v; + uint32_t u32_v; + }; +} audio_device_param_info_t; + +typedef struct audio_device_alsa_info { + char *card_name; + uint32_t card_idx; + uint32_t device_idx; +} audio_device_alsa_info_t; + +typedef struct audio_device_bluz_info { + char *protocol; + uint32_t nrec; +} audio_device_bluez_info_t; + +typedef struct audio_device_info { + audio_device_api_t api; + audio_direction_t direction; + char *name; + uint8_t is_default_device; + union { + audio_device_alsa_info_t alsa; + audio_device_bluez_info_t bluez; + }; +} audio_device_info_t; + +typedef struct device_info { + char *type; + uint32_t direction; + uint32_t id; +} device_info_t; + +typedef struct audio_volume_info { + const char *type; + const char *gain; + uint32_t direction; +} audio_volume_info_t ; + +typedef struct audio_route_info { + const char *role; + device_info_t *device_infos; + uint32_t num_of_devices; +} audio_route_info_t; + +typedef struct audio_route_option { + const char *role; + const char *name; + int32_t value; +} audio_route_option_t; + +typedef struct audio_stream_info { + const char *role; + uint32_t direction; + uint32_t idx; +} audio_stream_info_t ; + +/* Stream */ + +typedef enum audio_volume { + AUDIO_VOLUME_TYPE_SYSTEM, /**< System volume type */ + AUDIO_VOLUME_TYPE_NOTIFICATION, /**< Notification volume type */ + AUDIO_VOLUME_TYPE_ALARM, /**< Alarm volume type */ + AUDIO_VOLUME_TYPE_RINGTONE, /**< Ringtone volume type */ + AUDIO_VOLUME_TYPE_MEDIA, /**< Media volume type */ + AUDIO_VOLUME_TYPE_CALL, /**< Call volume type */ + AUDIO_VOLUME_TYPE_VOIP, /**< VOIP volume type */ + AUDIO_VOLUME_TYPE_VOICE, /**< Voice volume type */ + AUDIO_VOLUME_TYPE_FIXED, /**< Volume type for fixed acoustic level */ + AUDIO_VOLUME_TYPE_MAX, /**< Volume type count */ +} audio_volume_t; + +typedef enum audio_gain { + AUDIO_GAIN_TYPE_DEFAULT, + AUDIO_GAIN_TYPE_DIALER, + AUDIO_GAIN_TYPE_TOUCH, + AUDIO_GAIN_TYPE_AF, + AUDIO_GAIN_TYPE_SHUTTER1, + AUDIO_GAIN_TYPE_SHUTTER2, + AUDIO_GAIN_TYPE_CAMCODING, + AUDIO_GAIN_TYPE_MIDI, + AUDIO_GAIN_TYPE_BOOTING, + AUDIO_GAIN_TYPE_VIDEO, + AUDIO_GAIN_TYPE_TTS, + AUDIO_GAIN_TYPE_MAX, +} audio_gain_t; + +/* audio format */ +typedef enum audio_sample_format { + AUDIO_SAMPLE_U8, + AUDIO_SAMPLE_ALAW, + AUDIO_SAMPLE_ULAW, + AUDIO_SAMPLE_S16LE, + AUDIO_SAMPLE_S16BE, + AUDIO_SAMPLE_FLOAT32LE, + AUDIO_SAMPLE_FLOAT32BE, + AUDIO_SAMPLE_S32LE, + AUDIO_SAMPLE_S32BE, + AUDIO_SAMPLE_S24LE, + AUDIO_SAMPLE_S24BE, + AUDIO_SAMPLE_S24_32LE, + AUDIO_SAMPLE_S24_32BE, + AUDIO_SAMPLE_MAX, + AUDIO_SAMPLE_INVALID = -1 +} audio_sample_format_t; + +/* stream latency */ +static const char* AUDIO_LATENCY_LOW = "low"; +static const char* AUDIO_LATENCY_MID = "mid"; +static const char* AUDIO_LATENCY_HIGH = "high"; +static const char* AUDIO_LATENCY_VOIP = "voip"; + +/* Overall */ + +typedef struct audio_cb_interface { + audio_return_t (*load_device)(void *platform_data, audio_device_info_t *device_info, audio_device_param_info_t *params); + audio_return_t (*open_device)(void *platform_data, audio_device_info_t *device_info, audio_device_param_info_t *params); + audio_return_t (*close_all_devices)(void *platform_data); + audio_return_t (*close_device)(void *platform_data, audio_device_info_t *device_info); + audio_return_t (*unload_device)(void *platform_data, audio_device_info_t *device_info); +} audio_cb_interface_t; + +typedef struct audio_interface { + audio_return_t (*init)(void **userdata, void *platform_data); + audio_return_t (*deinit)(void **userdata); + audio_return_t (*reset_volume)(void **userdata); + audio_return_t (*get_volume_level_max)(void *userdata, audio_volume_info_t *info, uint32_t *level); + audio_return_t (*get_volume_level)(void *userdata, audio_volume_info_t *info, uint32_t *level); + audio_return_t (*set_volume_level)(void *userdata, audio_volume_info_t *info, uint32_t level); + audio_return_t (*get_volume_value)(void *userdata, audio_volume_info_t *info, uint32_t level, double *value); + audio_return_t (*get_volume_mute)(void *userdata, audio_volume_info_t *info, uint32_t *mute); + audio_return_t (*set_volume_mute)(void *userdata, audio_volume_info_t *info, uint32_t mute); + audio_return_t (*do_route)(void *userdata, audio_route_info_t *info); + audio_return_t (*update_route_option)(void *userdata, audio_route_option_t *option); + audio_return_t (*update_stream_connection_info) (void *userdata, audio_stream_info_t *info, uint32_t is_connected); + audio_return_t (*alsa_pcm_open)(void *userdata, void **pcm_handle, char *device_name, uint32_t direction, int mode); + audio_return_t (*alsa_pcm_close)(void *userdata, void *pcm_handle); + audio_return_t (*pcm_open)(void *userdata, void **pcm_handle, void *sample_spec, uint32_t direction); + audio_return_t (*pcm_close)(void *userdata, void *pcm_handle); + audio_return_t (*pcm_avail)(void *pcm_handle); + audio_return_t (*pcm_write)(void *pcm_handle, const void *buffer, uint32_t frames); + audio_return_t (*get_buffer_attr)(void *userdata, uint32_t direction, const char *latency, uint32_t samplerate, audio_sample_format_t format, uint32_t channels, uint32_t *maxlength, uint32_t *tlength, uint32_t *prebuf, uint32_t* minreq, uint32_t *fragsize); + audio_return_t (*set_session)(void *userdata, uint32_t session, uint32_t subsession, uint32_t cmd); + audio_return_t (*set_route)(void *userdata, uint32_t session, uint32_t subsession, uint32_t device_in, uint32_t device_out, uint32_t route_flag); + audio_return_t (*set_callback)(void *userdata, audio_cb_interface_t *cb_interface); +} audio_interface_t; + +int audio_get_revision (void); +audio_return_t audio_init (void **userdata, void *platform_data); +audio_return_t audio_deinit (void **userdata); +audio_return_t audio_reset_volume (void **userdata); +audio_return_t audio_get_volume_level_max (void *userdata, audio_volume_info_t *info, uint32_t *level); +audio_return_t audio_get_volume_level (void *userdata, audio_volume_info_t *info, uint32_t *level); +audio_return_t audio_set_volume_level (void *userdata, audio_volume_info_t *info, uint32_t level); +audio_return_t audio_get_volume_value (void *userdata, audio_volume_info_t *info, uint32_t level, double *value); +audio_return_t audio_get_volume_mute (void *userdata, audio_volume_info_t *info, uint32_t *mute); +audio_return_t audio_set_volume_mute (void *userdata, audio_volume_info_t *info, uint32_t mute); +audio_return_t audio_do_route (void *userdata, audio_route_info_t *info); +audio_return_t audio_update_route_option (void *userdata, audio_route_option_t *option); +audio_return_t audio_update_stream_connection_info (void *userdata, audio_stream_info_t *info, uint32_t is_connected); +audio_return_t audio_alsa_pcm_open (void *userdata, void **pcm_handle, char *device_name, uint32_t direction, int mode); +audio_return_t audio_alsa_pcm_close (void *userdata, void *pcm_handle); +audio_return_t audio_pcm_open(void *userdata, void **pcm_handle, void *sample_spec, uint32_t direction); +audio_return_t audio_pcm_close (void *userdata, void *pcm_handle); +audio_return_t audio_pcm_avail(void *pcm_handle); +audio_return_t audio_pcm_write(void *pcm_handle, const void *buffer, uint32_t frames); +audio_return_t audio_get_buffer_attr(void *userdata, uint32_t direction, const char *latency, uint32_t samplerate, audio_sample_format_t format, uint32_t channels, uint32_t *maxlength, uint32_t *tlength, uint32_t *prebuf, uint32_t* minreq, uint32_t *fragsize); +audio_return_t audio_set_session (void *userdata, uint32_t session, uint32_t subsession, uint32_t cmd); +audio_return_t audio_set_route (void *userdata, uint32_t session, uint32_t subsession, uint32_t device_in, uint32_t device_out, uint32_t route_flag); +audio_return_t audio_set_callback (void *userdata, audio_cb_interface_t *cb_interface); +#endif -- 2.7.4 From c0f0a5172e0de2e828eb6dc8db5b3f1e306baef8 Mon Sep 17 00:00:00 2001 From: Mok Jeongho Date: Thu, 24 Sep 2015 12:23:46 +0900 Subject: [PATCH 3/8] device-manager: Get device item with sink/source [Version] pulseaudio 5.0-41 [Profile] Common Change-Id: I7a0fa6b53a3756cd9a92b2433b4c68980ca7998b --- packaging/pulseaudio-modules-tizen.spec | 2 +- src/device-manager.c | 12 ++++++++++++ src/device-manager.h | 2 ++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/packaging/pulseaudio-modules-tizen.spec b/packaging/pulseaudio-modules-tizen.spec index 83d0ddf..4aeec97 100644 --- a/packaging/pulseaudio-modules-tizen.spec +++ b/packaging/pulseaudio-modules-tizen.spec @@ -4,7 +4,7 @@ Name: pulseaudio-modules-tizen Summary: Improved Linux sound server Version: 5.0 -Release: 40 +Release: 41 Group: Multimedia/Audio License: LGPL-2.1+ URL: http://pulseaudio.org diff --git a/src/device-manager.c b/src/device-manager.c index 361b2ea..dac8530 100644 --- a/src/device-manager.c +++ b/src/device-manager.c @@ -3946,6 +3946,18 @@ pa_source* pa_device_manager_get_source(dm_device *device_item, const char *role return pa_hashmap_get(profile_item->capture_devices, role); } +dm_device* pa_device_manager_get_device_with_sink(pa_sink *sink) { + pa_assert(sink); + + return sink->device_item; +} + +dm_device* pa_device_manager_get_device_with_source(pa_source *source) { + pa_assert(source); + + return source->device_item; +} + void pa_device_manager_set_device_state(dm_device *device_item, dm_device_direction_t direction, dm_device_state_t state) { dm_device_profile *profile_item; diff --git a/src/device-manager.h b/src/device-manager.h index a62d701..b6f4c70 100644 --- a/src/device-manager.h +++ b/src/device-manager.h @@ -66,6 +66,8 @@ uint32_t pa_device_manager_get_device_id(dm_device *device); const char* pa_device_manager_get_device_type(dm_device *device); const char* pa_device_manager_get_device_subtype(dm_device *device); dm_device_direction_t pa_device_manager_get_device_direction(dm_device *device); +dm_device* pa_device_manager_get_device_with_sink(pa_sink *sink); +dm_device* pa_device_manager_get_device_with_source(pa_source *source); int pa_device_manager_load_sink(const char *device_type, const char *device_profile, const char *role, pa_device_manager *dm); int pa_device_manager_load_source(const char *device_type, const char *device_profile, const char *role, pa_device_manager *dm); -- 2.7.4 From 6acb786ee7ada185cee54b4f1c47867d3baa4975 Mon Sep 17 00:00:00 2001 From: Mok Jeongho Date: Thu, 1 Oct 2015 17:21:03 +0900 Subject: [PATCH 4/8] set device_item for sink/source of externel device before notification [Version] Release 5.0.42 [Profile] Common [Issue Type] Fix bug Change-Id: I0e40f71c473cb84abb1d6e31341ca1366a7c49ac --- packaging/pulseaudio-modules-tizen.spec | 2 +- src/device-manager.c | 41 +++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/packaging/pulseaudio-modules-tizen.spec b/packaging/pulseaudio-modules-tizen.spec index 4aeec97..c7bb3e1 100644 --- a/packaging/pulseaudio-modules-tizen.spec +++ b/packaging/pulseaudio-modules-tizen.spec @@ -4,7 +4,7 @@ Name: pulseaudio-modules-tizen Summary: Improved Linux sound server Version: 5.0 -Release: 41 +Release: 42 Group: Multimedia/Audio License: LGPL-2.1+ URL: http://pulseaudio.org diff --git a/src/device-manager.c b/src/device-manager.c index dac8530..2f0e509 100644 --- a/src/device-manager.c +++ b/src/device-manager.c @@ -1426,10 +1426,44 @@ static int _device_list_remove_device(pa_idxset *device_list, dm_device *device_ return 0; } +static pa_sink* _device_profile_get_sink(dm_device_profile *profile_item, const char *role) { + pa_sink *sink; + + if (!profile_item || !role || !device_role_is_valid(role)) { + pa_log_error("Invalid Parameter"); + return NULL; + } + + if (!profile_item->playback_devices) { + pa_log_error("No playback devices"); + return NULL; + } + + sink = pa_hashmap_get(profile_item->playback_devices, role); + return sink; +} + +static pa_source* _device_profile_get_source(dm_device_profile *profile_item, const char *role) { + pa_source *source; + + if (!profile_item || !role || !device_role_is_valid(role)) { + pa_log_error("Invalid Parameter"); + return NULL; + } + if (!profile_item->capture_devices) { + pa_log_error("No capture devices"); + return NULL; + } + + source = pa_hashmap_get(profile_item->capture_devices, role); + return source; +} static dm_device* create_device_item(const char *device_type, const char *name, dm_device_profile *profile_item, pa_device_manager *dm) { dm_device *device_item = NULL; + pa_sink *sink = NULL; + pa_source *source = NULL; pa_assert(device_type); pa_assert(profile_item); @@ -1451,6 +1485,13 @@ static dm_device* create_device_item(const char *device_type, const char *name, _device_item_add_profile(device_item, profile_item, NULL, dm); _device_list_add_device(dm->device_list, device_item, dm); + + // just for external device + if ((sink = _device_profile_get_sink(profile_item, DEVICE_ROLE_NORMAL))) + sink->device_item = device_item; + if ((source = _device_profile_get_source(profile_item, DEVICE_ROLE_NORMAL))) + source->device_item = device_item; + notify_device_connection_changed(device_item, TRUE, dm); return device_item; -- 2.7.4 From a7156431dec72607ee9ef884c580d25091b269bb Mon Sep 17 00:00:00 2001 From: Mok Jeongho Date: Fri, 2 Oct 2015 17:40:17 +0900 Subject: [PATCH 5/8] remove device item from list before notify device disconnection [Version] Release 5.0.43 [Profile] Common [Issue Type] Fix bug Change-Id: I076142e74e802613e3d1da365cc27b21db21b218 --- packaging/pulseaudio-modules-tizen.spec | 2 +- src/device-manager.c | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/packaging/pulseaudio-modules-tizen.spec b/packaging/pulseaudio-modules-tizen.spec index c7bb3e1..67d6621 100644 --- a/packaging/pulseaudio-modules-tizen.spec +++ b/packaging/pulseaudio-modules-tizen.spec @@ -4,7 +4,7 @@ Name: pulseaudio-modules-tizen Summary: Improved Linux sound server Version: 5.0 -Release: 42 +Release: 43 Group: Multimedia/Audio License: LGPL-2.1+ URL: http://pulseaudio.org diff --git a/src/device-manager.c b/src/device-manager.c index 2f0e509..f9698b6 100644 --- a/src/device-manager.c +++ b/src/device-manager.c @@ -1504,8 +1504,6 @@ static void destroy_device_item(dm_device *device_item, pa_device_manager *dm) { pa_log_debug("Destroy device item which of type is %s", device_item->type); - _device_list_remove_device(dm->device_list, device_item, dm); - device_item_free_func(device_item); } @@ -2332,6 +2330,7 @@ static void handle_sink_unloaded(pa_sink *sink, pa_device_manager *dm) { if (profile_playback_size == 1 && profile_capture_size == 0) { if (item_size == 1) { pa_log_debug("notify device disconnected"); + _device_list_remove_device(dm->device_list, device_item, dm); notify_device_connection_changed(device_item, FALSE, dm); } } @@ -2394,6 +2393,7 @@ static void handle_source_unloaded(pa_source *source, pa_device_manager *dm) { if (profile_capture_size == 1 && profile_playback_size == 0) { if (item_size == 1) { pa_log_debug("notify device disconnected"); + _device_list_remove_device(dm->device_list, device_item, dm); notify_device_connection_changed(device_item, FALSE, dm); } } @@ -3089,8 +3089,10 @@ static int handle_device_disconnected(pa_device_manager *dm, const char *device_ PA_IDXSET_FOREACH(device_item, dm->device_list, device_idx) { if (pa_streq(device_item->type, device_type)) { if((profile_item = _device_item_get_profile(device_item, device_profile))) { - if (_device_item_get_size(device_item) == 1) + if (_device_item_get_size(device_item) == 1) { + _device_list_remove_device(dm->device_list, device_item, dm); notify_device_connection_changed(device_item, FALSE, dm); + } destroy_device_profile(profile_item, dm); } else { pa_log_debug("no matching profile"); -- 2.7.4 From 6b1839bcf349aaed179b9b5c8e9903a2b811b7f5 Mon Sep 17 00:00:00 2001 From: Sangchul Lee Date: Thu, 24 Sep 2015 15:04:03 +0900 Subject: [PATCH 6/8] stream-manager: Support route type only for external devices (STREAM_ROUTE_TYPE_MANUAL_EXT) device-manager: Add sink/source state changed hook for updating state of external devices policy: Add routing logic for usecase of external devices [Version] 5.0-44 [Profile] Common [Issue Type] Enhance feature The new route type(STREAM_ROUTE_TYPE_MANUAL_EXT) is only for external audio devices that have their own audio codec.(eg.,usb,bluetooth) This can be declared in one of entries for stream configuration in stream-map.json file. With this policy, we can control external devices independently of devices that use internal audio codec. Change-Id: I6b48d7558a3978b2538de3dbbc8d27209a755ec0 Signed-off-by: Sangchul Lee --- packaging/pulseaudio-modules-tizen.spec | 2 +- src/communicator.h | 1 + src/device-manager.c | 98 +++- src/device-manager.h | 1 + src/module-policy.c | 824 ++++++++++++++++++++++++-------- src/stream-manager-priv.h | 24 +- src/stream-manager.c | 541 ++++++++++++++++----- src/stream-manager.h | 28 +- 8 files changed, 1171 insertions(+), 348 deletions(-) diff --git a/packaging/pulseaudio-modules-tizen.spec b/packaging/pulseaudio-modules-tizen.spec index 67d6621..96be79c 100644 --- a/packaging/pulseaudio-modules-tizen.spec +++ b/packaging/pulseaudio-modules-tizen.spec @@ -4,7 +4,7 @@ Name: pulseaudio-modules-tizen Summary: Improved Linux sound server Version: 5.0 -Release: 43 +Release: 44 Group: Multimedia/Audio License: LGPL-2.1+ URL: http://pulseaudio.org diff --git a/src/communicator.h b/src/communicator.h index 790652d..a157249 100644 --- a/src/communicator.h +++ b/src/communicator.h @@ -8,6 +8,7 @@ typedef enum pa_communicator_hook { PA_COMMUNICATOR_HOOK_UPDATE_ROUTE_OPTION, PA_COMMUNICATOR_HOOK_DEVICE_CONNECTION_CHANGED, PA_COMMUNICATOR_HOOK_DEVICE_INFORMATION_CHANGED, + PA_COMMUNICATOR_HOOK_NEED_UPDATE_ROUTE, PA_COMMUNICATOR_HOOK_MAX } pa_communicator_hook_t; diff --git a/src/device-manager.c b/src/device-manager.c index f9698b6..3bf5e4d 100644 --- a/src/device-manager.c +++ b/src/device-manager.c @@ -343,8 +343,8 @@ struct device_file_map { struct pa_device_manager { pa_core *core; - pa_hook_slot *sink_put_hook_slot, *sink_unlink_hook_slot; - pa_hook_slot *source_put_hook_slot, *source_unlink_hook_slot; + pa_hook_slot *sink_put_hook_slot, *sink_state_changed_slot, *sink_unlink_hook_slot; + pa_hook_slot *source_put_hook_slot, *source_state_changed_slot, *source_unlink_hook_slot; pa_communicator *comm; /* @@ -1930,6 +1930,11 @@ static dm_device* handle_not_predefined_device(pa_device_manager *dm, void *puls pa_log_debug("handle_not_predefined_device"); + if (pdt == PA_DEVICE_TYPE_SINK) + ((pa_sink*)pulse_device)->use_internal_codec = FALSE; + else + ((pa_source*)pulse_device)->use_internal_codec = FALSE; + if (pulse_device_get_device_type(pulse_device, pdt, device_class, &device_type, &device_profile, &device_name) < 0) { pa_log_warn("Cannot get device type of this device"); return NULL; @@ -2252,6 +2257,11 @@ static void handle_predefined_device_loaded(void *pulse_device, pa_device_type_t pa_log_debug("Predefined device loaded, Type:%s, Class:%d, device_string:%s, role:%s", pdt == PA_DEVICE_TYPE_SINK ? "sink" : "source", device_class, device_string, role); + if (pdt == PA_DEVICE_TYPE_SINK) + ((pa_sink*)pulse_device)->use_internal_codec = TRUE; + else + ((pa_source*)pulse_device)->use_internal_codec = TRUE; + identifier = pulse_device_get_identifier(pulse_device, pdt, device_class); PA_IDXSET_FOREACH(type_info, dm->type_infos, type_idx) { /* foreach matching types (which has device_string-role) */ @@ -2494,6 +2504,60 @@ static pa_hook_result_t sink_unlink_hook_callback(pa_core *c, pa_sink *sink, pa_ return PA_HOOK_OK; } +static pa_hook_result_t device_state_changed_hook_cb(pa_core *c, pa_object *o, pa_device_manager *dm) { + dm_device *device_item; + dm_device *_device_item; + pa_bool_t use_internal_codec = FALSE; + uint32_t idx = 0; + + pa_assert(c); + pa_object_assert_ref(o); + pa_assert(dm); + + if (pa_sink_isinstance(o)) { + pa_sink *s = PA_SINK(o); + pa_sink_state_t state = pa_sink_get_state(s); + pa_log_debug("=========== Sink(%p,%s) state has been changed to [%d](0:RUNNING, 1:IDLE, 2:SUSPEND) ==========", s, s->name, state); + if (s->use_internal_codec) { + if (state == PA_SINK_SUSPENDED) { + PA_IDXSET_FOREACH(_device_item, dm->device_list, idx) { + pa_device_manager_use_internal_codec(_device_item, DM_DEVICE_DIRECTION_OUT, DEVICE_ROLE_NORMAL, &use_internal_codec); + if (use_internal_codec) + pa_device_manager_set_device_state(_device_item, DM_DEVICE_DIRECTION_OUT, DM_DEVICE_STATE_DEACTIVATED); + } + } + } else { + if ((device_item = pa_device_manager_get_device_with_sink(s))) { + if (state == PA_SINK_RUNNING) + pa_device_manager_set_device_state(device_item, DM_DEVICE_DIRECTION_OUT, DM_DEVICE_STATE_ACTIVATED); + else if (state == PA_SINK_SUSPENDED) + pa_device_manager_set_device_state(device_item, DM_DEVICE_DIRECTION_OUT, DM_DEVICE_STATE_DEACTIVATED); + } + } + } else if (pa_source_isinstance(o)) { + pa_source *s = PA_SOURCE(o); + pa_source_state_t state = pa_source_get_state(s); + pa_log_debug("=========== Source(%p,%s) state has been changed to [%d](0:RUNNING, 1:IDLE, 2:SUSPEND) ==========", s, s->name, state); + if (s->use_internal_codec) { + if (state == PA_SOURCE_SUSPENDED) { + PA_IDXSET_FOREACH(_device_item, dm->device_list, idx) { + pa_device_manager_use_internal_codec(_device_item, DM_DEVICE_DIRECTION_IN, DEVICE_ROLE_NORMAL, &use_internal_codec); + if (use_internal_codec) + pa_device_manager_set_device_state(_device_item, DM_DEVICE_DIRECTION_IN, DM_DEVICE_STATE_DEACTIVATED); + } + } + } else { + if ((device_item = pa_device_manager_get_device_with_source(s))) { + if (state == PA_SOURCE_RUNNING) + pa_device_manager_set_device_state(device_item, DM_DEVICE_DIRECTION_IN, DM_DEVICE_STATE_ACTIVATED); + else if (state == PA_SOURCE_SUSPENDED) + pa_device_manager_set_device_state(device_item, DM_DEVICE_DIRECTION_IN, DM_DEVICE_STATE_DEACTIVATED); + } + } + } + + return PA_HOOK_OK; +} static pa_hook_result_t source_put_hook_callback(pa_core *c, pa_source *source, pa_device_manager *dm) { const char *device_string = NULL, *role = NULL, *device_string_removed_params = NULL; @@ -4057,6 +4121,30 @@ dm_device_direction_t pa_device_manager_get_device_direction(dm_device *device_i return profile_item->direction; } +void pa_device_manager_use_internal_codec(dm_device *device_item, dm_device_direction_t direction, const char *role, pa_bool_t *use_internal_codec) { + pa_sink *sink; + pa_source *source; + + pa_assert(device_item); + pa_assert(use_internal_codec); + pa_assert(role); + + if (direction == DM_DEVICE_DIRECTION_IN) { + if ((source = pa_device_manager_get_source(device_item, role))) + *use_internal_codec = source->use_internal_codec; + else + *use_internal_codec = FALSE; + } else if (direction == DM_DEVICE_DIRECTION_OUT) { + if ((sink = pa_device_manager_get_sink(device_item, role))) + *use_internal_codec = sink->use_internal_codec; + else + *use_internal_codec = FALSE; + } else + *use_internal_codec = FALSE; + + return; +} + int pa_device_manager_bt_sco_open(pa_device_manager *dm) { struct device_status_info *status_info; @@ -4250,8 +4338,10 @@ pa_device_manager* pa_device_manager_init(pa_core *c) { dbus_init(dm); dm->sink_put_hook_slot = pa_hook_connect(&dm->core->hooks[PA_CORE_HOOK_SINK_PUT], PA_HOOK_LATE+10, (pa_hook_cb_t) sink_put_hook_callback, dm); + dm->sink_state_changed_slot = pa_hook_connect(&dm->core->hooks[PA_CORE_HOOK_SINK_STATE_CHANGED], PA_HOOK_NORMAL, (pa_hook_cb_t) device_state_changed_hook_cb, dm); dm->sink_unlink_hook_slot = pa_hook_connect(&dm->core->hooks[PA_CORE_HOOK_SINK_UNLINK], PA_HOOK_EARLY, (pa_hook_cb_t) sink_unlink_hook_callback, dm); dm->source_put_hook_slot = pa_hook_connect(&dm->core->hooks[PA_CORE_HOOK_SOURCE_PUT], PA_HOOK_LATE+10, (pa_hook_cb_t) source_put_hook_callback, dm); + dm->source_state_changed_slot = pa_hook_connect(&dm->core->hooks[PA_CORE_HOOK_SOURCE_STATE_CHANGED], PA_HOOK_NORMAL, (pa_hook_cb_t) device_state_changed_hook_cb, dm); dm->source_unlink_hook_slot = pa_hook_connect(&dm->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK], PA_HOOK_EARLY, (pa_hook_cb_t) source_unlink_hook_callback, dm); dm->comm = pa_communicator_get(dm->core); @@ -4302,10 +4392,14 @@ void pa_device_manager_done(pa_device_manager *dm) { if (dm->sink_put_hook_slot) pa_hook_slot_free(dm->sink_put_hook_slot); + if (dm->sink_state_changed_slot) + pa_hook_slot_free(dm->sink_state_changed_slot); if (dm->sink_unlink_hook_slot) pa_hook_slot_free(dm->sink_unlink_hook_slot); if (dm->source_put_hook_slot) pa_hook_slot_free(dm->source_put_hook_slot); + if (dm->source_state_changed_slot) + pa_hook_slot_free(dm->source_state_changed_slot); if (dm->source_unlink_hook_slot) pa_hook_slot_free(dm->source_unlink_hook_slot); diff --git a/src/device-manager.h b/src/device-manager.h index b6f4c70..49de4af 100644 --- a/src/device-manager.h +++ b/src/device-manager.h @@ -66,6 +66,7 @@ uint32_t pa_device_manager_get_device_id(dm_device *device); const char* pa_device_manager_get_device_type(dm_device *device); const char* pa_device_manager_get_device_subtype(dm_device *device); dm_device_direction_t pa_device_manager_get_device_direction(dm_device *device); +void pa_device_manager_use_internal_codec(dm_device *device_item, dm_device_direction_t direction, const char *role, pa_bool_t *use_internal_codec); dm_device* pa_device_manager_get_device_with_sink(pa_sink *sink); dm_device* pa_device_manager_get_device_with_source(pa_source *source); diff --git a/src/module-policy.c b/src/module-policy.c index af44db5..d64fea7 100644 --- a/src/module-policy.c +++ b/src/module-policy.c @@ -46,10 +46,8 @@ #include "hal-manager.h" #include "stream-manager.h" #include "stream-manager-volume.h" -#define DEVICE_MANAGER -#ifdef DEVICE_MANAGER #include "device-manager.h" -#endif + //To be changed #ifndef VCONFKEY_SOUND_CAPTURE_STATUS @@ -213,17 +211,17 @@ enum signal_index { #endif +/* Modules for dynamic loading */ +#define MODULE_COMBINE_SINK "module-combine-sink" + /* Sink & Source names */ #define SINK_COMBINED "sink_combined" +#define SINK_COMBINED_EX "sink_combined_ex" #define SINK_NULL "sink_null" #define SOURCE_NULL "source_null" -/* Policies */ -#define POLICY_HIGH_LATENCY "high-latency" -#define POLICY_HIGH_LATENCY_UHQA "high-latency-uhqa" - /* Macros */ -#define CONVERT_TO_DEVICE_DIRECTION(stream_type) \ +#define CONVERT_TO_DEVICE_DIRECTION(stream_type)\ ((stream_type==STREAM_SINK_INPUT)?DM_DEVICE_DIRECTION_OUT:DM_DEVICE_DIRECTION_IN) /* PCM Dump */ @@ -235,8 +233,6 @@ enum signal_index { #define PA_DUMP_PLAYBACK_RESAMPLER_OUT 0x00000010 #define PA_DUMP_CAPTURE_ENCODER_IN 0x80000000 -/* check if this sink is bluez */ - struct pa_hal_device_event_data { audio_device_info_t device_info; audio_device_param_info_t params[AUDIO_DEVICE_PARAM_MAX]; @@ -263,10 +259,10 @@ struct userdata { pa_hal_manager *hal_manager; pa_stream_manager *stream_manager; -#ifdef DEVICE_MANAGER pa_device_manager *device_manager; -#endif + pa_module *module_combine_sink; + pa_module *module_combine_sink_for_ex; pa_module *module_null_sink; pa_module *module_null_source; }; @@ -279,6 +275,8 @@ enum { SUBCOMMAND_SET_MUTE, }; +static void _set_device_state(dm_device *device, stream_type_t stream_type, dm_device_state_t device_state); + static int __convert_volume_type_to_string(uint32_t volume_type, const char **volume_type_str) { int ret = 0; switch (volume_type) { @@ -461,17 +459,16 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio return -1; } -/* Set the proper sink(source) according to the data of the parameter. */ -/* - ROUTE_TYPE_AUTO(_ALL) */ -/* 1. Find the proper sink/source comparing between avail_devices */ -/* and current connected devices. */ -/* 2. If not found, set it to null sink/source. */ -/* - ROUTE_TYPE_MANUAL */ -/* 1. Find the proper sink/source comparing between avail_devices */ -/* and manual_devices that have been set by user. */ -/* 2. If not found, set it to null sink/source. */ +/* Set the proper sink(source) according to the data of the parameter. + * - ROUTE_TYPE_AUTO(_ALL) + * 1. Find the proper sink/source comparing between avail_devices + * and current connected devices. + * 2. If not found, set it to null sink/source. + * - ROUTE_TYPE_MANUAL(_EXT) + * 1. Find the proper sink/source comparing between avail_devices + * and manual_devices that have been set by user. + * 2. If not found, set it to null sink/source. */ static pa_hook_result_t select_proper_sink_or_source_hook_cb(pa_core *c, pa_stream_manager_hook_data_for_select *data, struct userdata *u) { -#ifdef DEVICE_MANAGER uint32_t idx = 0; uint32_t m_idx = 0; uint32_t conn_idx = 0; @@ -482,112 +479,215 @@ static pa_hook_result_t select_proper_sink_or_source_hook_cb(pa_core *c, pa_stre dm_device *device = NULL; dm_device_direction_t device_direction = DM_DEVICE_DIRECTION_NONE; pa_idxset *conn_devices = NULL; + pa_sink *sink = NULL; + pa_sink *null_sink = NULL; + pa_sink *combine_sink_arg1 = NULL; + pa_sink *combine_sink_arg2 = NULL; + pa_source *source = NULL; + pa_source *null_source = NULL; + char *args = NULL; + void *s = NULL; + uint32_t s_idx = 0; - pa_log_info("select_proper_sink_or_source_hook_cb is called. (%p), stream_type(%d), stream_role(%s), route_type(%d)", + pa_log_info("[SELECT] select_proper_sink_or_source_hook_cb is called. (%p), stream_type(%d), stream_role(%s), route_type(%d)", data, data->stream_type, data->stream_role, data->route_type); + null_sink = (pa_sink*)pa_namereg_get(u->core, SINK_NULL, PA_NAMEREG_SINK); + null_source = (pa_source*)pa_namereg_get(u->core, SOURCE_NULL, PA_NAMEREG_SOURCE); + if (!null_sink || !null_source) { + pa_log_error("[SELECT] could not get null_sink(%p) or null_source(%p)", null_sink, null_source); + return PA_HOOK_OK; + } + if ((data->route_type == STREAM_ROUTE_TYPE_AUTO || data->route_type == STREAM_ROUTE_TYPE_AUTO_ALL) && data->idx_avail_devices) { /* Get current connected devices */ conn_devices = pa_device_manager_get_device_list(u->device_manager); PA_IDXSET_FOREACH(device_type, data->idx_avail_devices, idx) { - pa_log_debug("[AUTO(_ALL)] avail_device[%u] for this role[%s]: type(%s)", idx, data->stream_role, device_type); + pa_log_debug("[SELECT][AUTO(_ALL)] avail_device[%u] for this role[%-16s]: type[%-16s]", idx, data->stream_role, device_type); PA_IDXSET_FOREACH(device, conn_devices, conn_idx) { dm_device_type = pa_device_manager_get_device_type(device); dm_device_subtype = pa_device_manager_get_device_subtype(device); device_direction = pa_device_manager_get_device_direction(device); - pa_log_debug("[AUTO(_ALL)] conn_devices, type[%s], subtype[%s], direction[0x%x]", dm_device_type, dm_device_subtype, device_direction); + pa_log_debug(" -- conn_devices, type[%-16s], subtype[%-5s], direction[0x%x]", dm_device_type, dm_device_subtype, device_direction); if (pa_streq(device_type, dm_device_type) && (((data->stream_type==STREAM_SINK_INPUT) && (device_direction & DM_DEVICE_DIRECTION_OUT)) || ((data->stream_type==STREAM_SOURCE_OUTPUT) && (device_direction & DM_DEVICE_DIRECTION_IN)))) { - pa_log_debug("[AUTO(_ALL)] found a matched device: type[%s], direction[0x%x]", device_type, device_direction); + pa_log_debug(" ** found a matched device: type[%-16s], direction[0x%x]", device_type, device_direction); - if (data->stream_type == STREAM_SINK_INPUT && u->module_combine_sink) { - *(data->proper_sink) = pa_namereg_get(u->module->core, SINK_COMBINED, PA_NAMEREG_SINK); - pa_log_debug("[AUTO(_ALL)] found the combine-sink, set it to the sink"); - } else if (data->stream_type == STREAM_SINK_INPUT) - *(data->proper_sink) = pa_device_manager_get_sink(device, DEVICE_ROLE_NORMAL); - else + if (data->stream_type == STREAM_SINK_INPUT) { + if (data->route_type == STREAM_ROUTE_TYPE_AUTO_ALL && u->module_combine_sink) { + *(data->proper_sink) = (pa_sink*)pa_namereg_get(u->core, SINK_COMBINED, PA_NAMEREG_SINK); + pa_log_debug(" -- found the combine-sink, set it to the sink"); + } else + *(data->proper_sink) = pa_device_manager_get_sink(device, DEVICE_ROLE_NORMAL); + } else *(data->proper_source) = pa_device_manager_get_source(device, DEVICE_ROLE_NORMAL); - goto SUCCESS; + + /* update activated device if it has the AUTO route type */ + if (data->route_type == STREAM_ROUTE_TYPE_AUTO) + pa_proplist_sets(GET_STREAM_NEW_PROPLIST(data->stream, data->stream_type), PA_PROP_MEDIA_ROUTE_AUTO_ACTIVE_DEV, dm_device_type); } } } - /* need to add logic for auto-all. (use combine-sink, move sink-input/source-output) */ } else if (data->route_type == STREAM_ROUTE_TYPE_MANUAL && data->idx_manual_devices && data->idx_avail_devices) { PA_IDXSET_FOREACH(device_type, data->idx_avail_devices, idx) { - pa_log_debug("[MANUAL] avail_device[%u] for this role[%s]: type(%s)", idx, data->stream_role, device_type); + pa_log_debug("[SELECT][MANUAL] avail_device[%u] for this role[%-16s]: type[%-16s]", idx, data->stream_role, device_type); PA_IDXSET_FOREACH(device_id, data->idx_manual_devices, m_idx) { - device = pa_device_manager_get_device_by_id(u->device_manager, *device_id); - if (device) { + if ((device = pa_device_manager_get_device_by_id(u->device_manager, *device_id))) { dm_device_type = pa_device_manager_get_device_type(device); dm_device_subtype = pa_device_manager_get_device_subtype(device); device_direction = pa_device_manager_get_device_direction(device); - pa_log_debug("[MANUAL] manual_devices, type[%s], subtype[%s], direction[0x%x], device id[%u]", + pa_log_debug(" -- manual_devices, type[%-16s], subtype[%-5s], direction[0x%x], device id[%u]", dm_device_type, dm_device_subtype, device_direction, *device_id); if (pa_streq(device_type, dm_device_type) && (((data->stream_type==STREAM_SINK_INPUT) && (device_direction & DM_DEVICE_DIRECTION_OUT)) || ((data->stream_type==STREAM_SOURCE_OUTPUT) && (device_direction & DM_DEVICE_DIRECTION_IN)))) { - pa_log_debug("[MANUAL] found a matched device: type[%s], direction[0x%x]", device_type, device_direction); - if (data->stream_type == STREAM_SINK_INPUT) - *(data->proper_sink) = pa_device_manager_get_sink(device, DEVICE_ROLE_NORMAL); - else - *(data->proper_source) = pa_device_manager_get_source(device, DEVICE_ROLE_NORMAL); - goto SUCCESS; + pa_log_debug(" ** found a matched device: type[%-16s], direction[0x%x]", device_type, device_direction); + if (data->stream_type == STREAM_SINK_INPUT) { + if ((*(data->proper_sink)) == null_sink) + pa_sink_input_move_to((pa_sink_input*)(data->stream), pa_device_manager_get_sink(device, DEVICE_ROLE_NORMAL), FALSE); + else + *(data->proper_sink) = pa_device_manager_get_sink(device, DEVICE_ROLE_NORMAL); + } else { + if ((*(data->proper_source)) == null_source) + pa_source_output_move_to((pa_source_output*)(data->stream), pa_device_manager_get_source(device, DEVICE_ROLE_NORMAL), FALSE); + else + *(data->proper_source) = pa_device_manager_get_source(device, DEVICE_ROLE_NORMAL); } + } + } + } + } + + } else if (data->route_type == STREAM_ROUTE_TYPE_MANUAL_EXT && data->idx_manual_devices && data->idx_avail_devices) { + PA_IDXSET_FOREACH(device_type, data->idx_avail_devices, idx) { + pa_log_debug("[SELECT][MANUAL_EXT] avail_device[%u] for this role[%-16s]: type[%-16s]", idx, data->stream_role, device_type); + PA_IDXSET_FOREACH(device_id, data->idx_manual_devices, m_idx) { + if ((device = pa_device_manager_get_device_by_id(u->device_manager, *device_id))) { + dm_device_type = pa_device_manager_get_device_type(device); + dm_device_subtype = pa_device_manager_get_device_subtype(device); + device_direction = pa_device_manager_get_device_direction(device); + pa_log_debug(" -- manual_devices, type[%-16s], subtype[%-5s], direction[0x%x], device id[%u]", + dm_device_type, dm_device_subtype, device_direction, *device_id); + if (pa_streq(device_type, dm_device_type) && + (((data->stream_type==STREAM_SINK_INPUT) && (device_direction & DM_DEVICE_DIRECTION_OUT)) || + ((data->stream_type==STREAM_SOURCE_OUTPUT) && (device_direction & DM_DEVICE_DIRECTION_IN)))) { + pa_log_debug(" ** found a matched device: type[%-16s], direction[0x%x]", device_type, device_direction); + /* currently, we support two sinks for combining */ + if (data->stream_type == STREAM_SINK_INPUT) { + if (!combine_sink_arg1) { + if ((sink = combine_sink_arg1 = pa_device_manager_get_sink(device, DEVICE_ROLE_NORMAL))) + pa_log_debug (" -- combine_sink_arg1[%s], combine_sink_arg2[%p]", sink->name, combine_sink_arg2); + else + pa_log_warn (" -- could not get combine_sink_arg1"); + } else if (!combine_sink_arg2) { + sink = combine_sink_arg2 = pa_device_manager_get_sink(device, DEVICE_ROLE_NORMAL); + if(sink && !pa_streq(sink->name, combine_sink_arg1->name)) { + pa_log_debug (" -- combine_sink_arg2[%s]", sink->name); + /* load combine sink */ + if (!u->module_combine_sink_for_ex) { + args = pa_sprintf_malloc("sink_name=%s slaves=\"%s,%s\"", SINK_COMBINED_EX, combine_sink_arg1->name, combine_sink_arg2->name); + pa_log_debug (" -- combined sink is not prepared, now load module[%s]", args); + u->module_combine_sink_for_ex = pa_module_load(u->core, MODULE_COMBINE_SINK, args); + pa_xfree(args); + } + sink = (pa_sink*)pa_namereg_get(u->core, SINK_COMBINED_EX, PA_NAMEREG_SINK); + PA_IDXSET_FOREACH (s, combine_sink_arg1->inputs, s_idx) { + if (s == data->stream) { + pa_sink_input_move_to(s, sink, FALSE); + pa_log_debug(" -- *** sink-input(%p,%u) moves to sink(%p,%s)", s, ((pa_sink_input*)s)->index, sink, sink->name); + break; + } + } + } else if (!sink) { + pa_log_warn (" -- could not get combine_sink_arg2"); + } + } + if (data->origins_from_new_data) + *(data->proper_sink) = sink; + else { + if (((pa_sink_input*)(data->stream))->sink != sink) + pa_sink_input_move_to(data->stream, sink, FALSE); + } + + } else if (data->stream_type == STREAM_SOURCE_OUTPUT) { + if ((source = pa_device_manager_get_source(device, DEVICE_ROLE_NORMAL))) { + if (data->origins_from_new_data) + *(data->proper_source) = source; + else { + if (((pa_source_output*)(data->stream))->source != source) + pa_source_output_move_to(data->stream, source, FALSE); + } + } else + pa_log_warn (" -- could not get source"); + } + } } } } } if ((data->stream_type==STREAM_SINK_INPUT)?!(*(data->proper_sink)):!(*(data->proper_source))) { - pa_log_warn("could not find a proper sink/source, set it to null sink/source"); + pa_log_warn("[SELECT] could not find a proper sink/source, set it to null sink/source"); if (data->stream_type == STREAM_SINK_INPUT) - *(data->proper_sink) = (pa_sink*)pa_namereg_get(u->core, SINK_NULL, PA_NAMEREG_SINK); + *(data->proper_sink) = null_sink; else - *(data->proper_source) = (pa_source*)pa_namereg_get(u->core, SOURCE_NULL, PA_NAMEREG_SOURCE); + *(data->proper_source) = null_source; } -SUCCESS: -#endif + return PA_HOOK_OK; } -/* Change the route setting according to the data from argument. */ -/* This function is called only when it needs to change routing path via HAL. */ -/* - role is "reset" */ -/* 1. It will be received when it is needed to terminate playback */ -/* or capture routing path. */ -/* 2. Update the state of the device to be deactivated. */ -/* 3. Call HAL API to reset routing. */ -/* - ROUTE_TYPE_AUTO */ -/* 1. Find the proper sink/source comparing between avail_devices */ -/* and current connected devices. */ -/* : Need to check the priority of the device list by order of receipt. */ -/* 2. Update the state of devices. */ -/* 3. Call HAL API to apply the routing setting */ -/* - ROUTE_TYPE_AUTO_ALL */ -/* 1. Find the proper sink/source comparing between avail_devices */ -/* and current connected devices. */ -/* : Might use combine-sink according to the conditions. */ -/* 2. Update the state of devices. */ -/* 3. Call HAL API to apply the routing setting */ -/* - ROUTE_TYPE_MANUAL */ -/* 1. Find the proper sink/source comparing between avail_devices */ -/* and manual_devices that have been set by user. */ -/* 2. Update the state of devices. */ -/* 3. Call HAL API to apply the routing setting */ +/* The state of a device using internal audio codec is handled here. + * Regarding the state of an external device, those is handled in device-manager.c */ +static void _set_device_state(dm_device *device, stream_type_t stream_type, dm_device_state_t device_state) { + pa_bool_t use_internal_codec = FALSE; + + pa_device_manager_use_internal_codec(device, CONVERT_TO_DEVICE_DIRECTION(stream_type), DEVICE_ROLE_NORMAL, &use_internal_codec); + if (use_internal_codec) + pa_device_manager_set_device_state(device, CONVERT_TO_DEVICE_DIRECTION(stream_type), device_state); + + return; +} + +/* Change the route setting according to the data from argument. + * This function is called only when it needs to change routing path via HAL. + * - role is "reset" + * 1. It will be received when it is needed to terminate playback + * or capture routing path. + * 2. Update the state of the device to be deactivated. + * 3. Call HAL API to reset routing. + * - ROUTE_TYPE_AUTO + * 1. Find the proper sink/source comparing between avail_devices + * and current connected devices. + * : Need to check the priority of the device list by order of receipt. + * 2. Update the state of devices. + * 3. Call HAL API to apply the routing setting + * - ROUTE_TYPE_AUTO_ALL + * 1. Find the proper sink/source comparing between avail_devices + * and current connected devices. + * : Might use combine-sink according to the conditions. + * 2. Update the state of devices. + * 3. Call HAL API to apply the routing setting + * - ROUTE_TYPE_MANUAL + * 1. Find the proper sink/source comparing between avail_devices + * and manual_devices that have been set by user. + * 2. Update the state of devices. + * 3. Call HAL API to apply the routing setting. */ static pa_hook_result_t route_change_hook_cb(pa_core *c, pa_stream_manager_hook_data_for_route *data, struct userdata *u) { -#ifdef DEVICE_MANAGER uint32_t i = 0; uint32_t idx = 0; uint32_t d_idx = 0; uint32_t s_idx = 0; - uint32_t *stream_idx = NULL; hal_route_info route_info = {NULL, NULL, 0}; uint32_t conn_idx = 0; uint32_t *device_id = NULL; uint32_t device_idx = 0; + stream_route_type_t route_type; const char *device_type = NULL; + const char *cur_device_type = NULL; + const char *role = NULL; dm_device *device = NULL; dm_device *_device = NULL; const char *dm_device_type = NULL; @@ -600,20 +700,17 @@ static pa_hook_result_t route_change_hook_cb(pa_core *c, pa_stream_manager_hook_ pa_idxset *conn_devices = NULL; pa_sink *combine_sink_arg1 = NULL; pa_sink *combine_sink_arg2 = NULL; + pa_sink *combine_sink = NULL; + pa_sink *null_sink = NULL; char *args = NULL; + pa_bool_t use_internal_codec = FALSE; + pa_bool_t available = FALSE; - pa_log_info("route_change_hook_cb is called. (%p), stream_type(%d), stream_role(%s), route_type(%d)", + pa_log_info("[ROUTE] route_change_hook_cb is called. (%p), stream_type(%d), stream_role(%s), route_type(%d)", data, data->stream_type, data->stream_role, data->route_type); - route_info.role = data->stream_role; - /* Streams */ - if (data->idx_streams) { - PA_IDXSET_FOREACH(stream_idx, data->idx_streams, idx) { - pa_log_debug("-- stream[%u]: idx(%u)", idx, *stream_idx); - } - } + route_info.role = data->stream_role; - /* Devices */ if (pa_streq(data->stream_role, "reset")) { /* Get current connected devices */ conn_devices = pa_device_manager_get_device_list(u->device_manager); @@ -625,10 +722,9 @@ static pa_hook_result_t route_change_hook_cb(pa_core *c, pa_stream_manager_hook_ if (device_state == DM_DEVICE_STATE_ACTIVATED && (((data->stream_type==STREAM_SINK_INPUT) && (device_direction & DM_DEVICE_DIRECTION_OUT)) || ((data->stream_type==STREAM_SOURCE_OUTPUT) && (device_direction & DM_DEVICE_DIRECTION_IN)))) { - pa_log_debug("[RESET] found a matched device and set state to DE-ACTIVATED: type[%s], direction[0x%x]", dm_device_type, device_direction); - /* set device state to deactivated */ - pa_device_manager_set_device_state(device, CONVERT_TO_DEVICE_DIRECTION(data->stream_type), DM_DEVICE_STATE_DEACTIVATED); - } + pa_log_debug("[ROUTE][RESET] found a matched device and set state to DE-ACTIVATED: type[%s], direction[0x%x]", dm_device_type, device_direction); + _set_device_state(device, data->stream_type, DM_DEVICE_STATE_DEACTIVATED); + } } route_info.num_of_devices = 1; route_info.device_infos = pa_xmalloc0(sizeof(hal_device_info)*route_info.num_of_devices); @@ -636,9 +732,19 @@ static pa_hook_result_t route_change_hook_cb(pa_core *c, pa_stream_manager_hook_ /* unload combine sink */ if (data->stream_type==STREAM_SINK_INPUT && u->module_combine_sink) { - pa_log_debug ("[RESET] unload module[%s]", SINK_COMBINED); - pa_sink_suspend(pa_namereg_get(u->module->core, SINK_COMBINED, PA_NAMEREG_SINK), TRUE, PA_SUSPEND_USER); - pa_module_unload(u->module->core, u->module_combine_sink, TRUE); + pa_log_debug ("[ROUTE][RESET] unload module[%s]", SINK_COMBINED); + combine_sink = (pa_sink*)pa_namereg_get(u->core, SINK_COMBINED, PA_NAMEREG_SINK); + null_sink = (pa_sink*)pa_namereg_get(u->core, SINK_NULL, PA_NAMEREG_SINK); + if (!combine_sink || !null_sink) + pa_log_error("[ROUTE][RESET] could not get combine_sink(%p) or null_sink(%p)", combine_sink, null_sink); + else { + PA_IDXSET_FOREACH (s, combine_sink->inputs, s_idx) { + pa_sink_input_move_to(s, null_sink, FALSE); + pa_log_debug("[ROUTE][RESET] *** sink-input(%p,%u) moves to sink(%p,%s)", s, ((pa_sink_input*)s)->index, null_sink, null_sink->name); + } + pa_sink_suspend(combine_sink, TRUE, PA_SUSPEND_USER); + } + pa_module_unload(u->core, u->module_combine_sink, TRUE); u->module_combine_sink = NULL; } @@ -646,37 +752,45 @@ static pa_hook_result_t route_change_hook_cb(pa_core *c, pa_stream_manager_hook_ /* Get current connected devices */ conn_devices = pa_device_manager_get_device_list(u->device_manager); PA_IDXSET_FOREACH(device_type, data->idx_avail_devices, idx) { - pa_log_debug("[AUTO(_ALL)] avail_device[%u] for this role[%s]: type[%s]", idx, route_info.role, device_type); + pa_log_debug("[ROUTE][AUTO(_ALL)] avail_device[%u] for this role[%-16s]: type[%-16s]", idx, route_info.role, device_type); PA_IDXSET_FOREACH(device, conn_devices, conn_idx) { dm_device_type = pa_device_manager_get_device_type(device); dm_device_subtype = pa_device_manager_get_device_subtype(device); device_direction = pa_device_manager_get_device_direction(device); device_idx = pa_device_manager_get_device_id(device); - pa_log_debug("[AUTO(_ALL)] conn_devices, type[%s], subtype[%s], direction[0x%x], id[%u]", + pa_log_debug(" -- conn_devices, type[%-16s], subtype[%-5s], direction[0x%x], id[%u]", dm_device_type, dm_device_subtype, device_direction, device_idx); if (pa_streq(device_type, dm_device_type) && (((data->stream_type==STREAM_SINK_INPUT) && (device_direction & DM_DEVICE_DIRECTION_OUT)) || ((data->stream_type==STREAM_SOURCE_OUTPUT) && (device_direction & DM_DEVICE_DIRECTION_IN)))) { - if (dm_device_subtype && pa_streq(DEVICE_PROFILE_BT_SCO, dm_device_subtype)) - continue; - route_info.num_of_devices++; - route_info.device_infos = pa_xrealloc(route_info.device_infos, sizeof(hal_device_info)*route_info.num_of_devices); - route_info.device_infos[route_info.num_of_devices-1].type = dm_device_type; - route_info.device_infos[route_info.num_of_devices-1].direction = (data->stream_type==STREAM_SINK_INPUT)?DIRECTION_OUT:DIRECTION_IN; - route_info.device_infos[route_info.num_of_devices-1].id = device_idx; - pa_log_debug("[AUTO(_ALL)] found a matched device and set state to ACTIVATED: type[%s], direction[0x%x], id[%u]", - route_info.device_infos[route_info.num_of_devices-1].type, device_direction, device_idx); - /* Set device state to activated */ - pa_device_manager_set_device_state(device, CONVERT_TO_DEVICE_DIRECTION(data->stream_type), DM_DEVICE_STATE_ACTIVATED); + pa_log_debug(" ** found a matched device: type[%-16s], direction[0x%x]", device_type, device_direction); + pa_device_manager_use_internal_codec(device, CONVERT_TO_DEVICE_DIRECTION(data->stream_type), DEVICE_ROLE_NORMAL, &use_internal_codec); + if (use_internal_codec) { + route_info.num_of_devices++; + route_info.device_infos = pa_xrealloc(route_info.device_infos, sizeof(hal_device_info)*route_info.num_of_devices); + route_info.device_infos[route_info.num_of_devices-1].type = dm_device_type; + route_info.device_infos[route_info.num_of_devices-1].direction = (data->stream_type==STREAM_SINK_INPUT)?DIRECTION_OUT:DIRECTION_IN; + route_info.device_infos[route_info.num_of_devices-1].id = device_idx; + pa_log_debug(" ** found a matched device and set state to ACTIVATED: type[%-16s], direction[0x%x], id[%u]", + route_info.device_infos[route_info.num_of_devices-1].type, device_direction, device_idx); + /* Set device state to activated */ + _set_device_state(device, data->stream_type, DM_DEVICE_STATE_ACTIVATED); + } else + pa_log_debug(" -- it does not use internal audio codec, skip it"); break; - } } + } if (data->route_type == STREAM_ROUTE_TYPE_AUTO && device) { - /* Set other device's state to deactivated */ - PA_IDXSET_FOREACH(_device, conn_devices, conn_idx) { - if (device == _device) - continue; - pa_device_manager_set_device_state(_device, CONVERT_TO_DEVICE_DIRECTION(data->stream_type), DM_DEVICE_STATE_DEACTIVATED); + /* check if this device uses internal codec */ + pa_device_manager_use_internal_codec(device, CONVERT_TO_DEVICE_DIRECTION(data->stream_type), DEVICE_ROLE_NORMAL, &use_internal_codec); + + if(use_internal_codec) { + /* Set other device's state to deactivated */ + PA_IDXSET_FOREACH(_device, conn_devices, conn_idx) { + if (device == _device) + continue; + _set_device_state(_device, data->stream_type, DM_DEVICE_STATE_DEACTIVATED); + } } /* Move sink-inputs/source-outputs if needed */ @@ -684,29 +798,51 @@ static pa_hook_result_t route_change_hook_cb(pa_core *c, pa_stream_manager_hook_ sink = pa_device_manager_get_sink(device, DEVICE_ROLE_NORMAL); else if (data->stream_type == STREAM_SOURCE_OUTPUT) source = pa_device_manager_get_source(device, DEVICE_ROLE_NORMAL); - if (data->idx_streams) { + + if (data->idx_streams) { /* idx_streams: data->stream->sink->sink_inputs/data->stream->source->source_outputs */ PA_IDXSET_FOREACH (s, data->idx_streams, s_idx) { - if (sink && (sink != ((pa_sink_input*)s)->sink)) { - pa_sink_input_move_to(s, sink, FALSE); - pa_log_debug("[AUTO] *** sink-input(%p,%u) moves to sink(%p,%s)", s, ((pa_sink_input*)s)->index, sink, sink->name); - } else if (source && (source != ((pa_source_output*)s)->source)) { - pa_source_output_move_to(s, source, FALSE); - pa_log_debug("[AUTO] *** source-output(%p,%u) moves to source(%p,%s)", s, ((pa_source_output*)s)->index, source, source->name); + /* check if the route type is AUTO and new device is available */ + if (!pa_stream_manager_get_route_type(s, FALSE, data->stream_type, &route_type)) { + pa_log_debug(" -- idx(%u), route_type(%d)", s_idx, route_type); + if (route_type == STREAM_ROUTE_TYPE_AUTO) { + if ((sink && (sink != ((pa_sink_input*)s)->sink)) || (source && (source != ((pa_source_output*)s)->source))) { + if ((cur_device_type = pa_proplist_gets(GET_STREAM_PROPLIST(s, data->stream_type), PA_PROP_MEDIA_ROUTE_AUTO_ACTIVE_DEV))) { + role = pa_proplist_gets(GET_STREAM_PROPLIST(s, data->stream_type), PA_PROP_MEDIA_ROLE); + pa_stream_manager_is_available_device_for_auto_route(cur_device_type, dm_device_type, role, data->stream_type, &available, u->stream_manager); + if (available) { + if (data->stream_type == STREAM_SINK_INPUT) { + pa_sink_input_move_to(s, sink, FALSE); + pa_log_debug("[ROUTE][AUTO] *** sink-input(%p,%u) moves to sink(%p,%s)", s, ((pa_sink_input*)s)->index, sink, sink->name); + } else { + pa_source_output_move_to(s, source, FALSE); + pa_log_debug("[ROUTE][AUTO] *** source-output(%p,%u) moves to source(%p,%s)", s, ((pa_source_output*)s)->index, source, source->name); + } + /* update activated device if it has the AUTO route type */ + pa_proplist_sets(GET_STREAM_PROPLIST(s, data->stream_type), PA_PROP_MEDIA_ROUTE_AUTO_ACTIVE_DEV, dm_device_type); + } + } + } else if ((sink && (sink == ((pa_sink_input*)s)->sink)) || (source && (source == ((pa_source_output*)s)->source))) { + /* update activated device if it has the AUTO route type */ + pa_proplist_sets(GET_STREAM_PROPLIST(s, data->stream_type), PA_PROP_MEDIA_ROUTE_AUTO_ACTIVE_DEV, dm_device_type); + } + } } } } + /* unload combine sink */ if (data->stream_type==STREAM_SINK_INPUT && u->module_combine_sink) { - pa_sink *combine_sink = pa_namereg_get(u->module->core, SINK_COMBINED, PA_NAMEREG_SINK); - if (combine_sink->inputs) { + if ((combine_sink = (pa_sink*)pa_namereg_get(u->core, SINK_COMBINED, PA_NAMEREG_SINK))) { PA_IDXSET_FOREACH (s, combine_sink->inputs, s_idx) { pa_sink_input_move_to(s, sink, FALSE); - pa_log_debug("[AUTO] *** sink-input(%p,%u) moves to sink(%p,%s)", s, ((pa_sink_input*)s)->index, sink, sink->name); + pa_log_debug("[ROUTE][AUTO] *** sink-input(%p,%u) moves to sink(%p,%s)", s, ((pa_sink_input*)s)->index, sink, sink->name); } - } - pa_log_debug ("[AUTO] unload module[%s]", SINK_COMBINED); - pa_sink_suspend(pa_namereg_get(u->module->core, SINK_COMBINED, PA_NAMEREG_SINK), TRUE, PA_SUSPEND_USER); - pa_module_unload(u->module->core, u->module_combine_sink, TRUE); + pa_sink_suspend(combine_sink, TRUE, PA_SUSPEND_USER); + } else + pa_log_error("[ROUTE][AUTO] could not get combine_sink"); + + pa_log_debug ("[ROUTE][AUTO] unload module[%s]", SINK_COMBINED); + pa_module_unload(u->core, u->module_combine_sink, TRUE); u->module_combine_sink = NULL; } break; @@ -715,27 +851,28 @@ static pa_hook_result_t route_change_hook_cb(pa_core *c, pa_stream_manager_hook_ /* find the proper sink/source */ /* currently, we support two sinks for combining */ if (data->stream_type == STREAM_SINK_INPUT && u->module_combine_sink) { - sink = pa_namereg_get(u->module->core, SINK_COMBINED, PA_NAMEREG_SINK); - pa_log_debug ("[AUTO_ALL] found the combine_sink already existed"); + sink = (pa_sink*)pa_namereg_get(u->core, SINK_COMBINED, PA_NAMEREG_SINK); + pa_log_debug ("[ROUTE][AUTO_ALL] found the combine_sink already existed"); } else if (data->stream_type == STREAM_SINK_INPUT && !combine_sink_arg1) { sink = combine_sink_arg1 = pa_device_manager_get_sink(device, DEVICE_ROLE_NORMAL); - pa_log_debug ("[AUTO_ALL] combine_sink_arg1[%s], combine_sink_arg2[%p]", sink->name, combine_sink_arg2); + pa_log_debug ("[ROUTE][AUTO_ALL] combine_sink_arg1[%s], combine_sink_arg2[%p]", sink->name, combine_sink_arg2); } else if (data->stream_type == STREAM_SINK_INPUT && !combine_sink_arg2) { - sink = pa_device_manager_get_sink(device, DEVICE_ROLE_NORMAL); + sink = combine_sink_arg2 = pa_device_manager_get_sink(device, DEVICE_ROLE_NORMAL); if(sink && !pa_streq(sink->name, combine_sink_arg1->name)) { - pa_log_debug ("[AUTO_ALL] combine_sink_arg2[%s]", sink->name); - combine_sink_arg2 = pa_device_manager_get_sink(device, DEVICE_ROLE_NORMAL); + pa_log_debug ("[ROUTE][AUTO_ALL] combine_sink_arg2[%s]", sink->name); /* load combine sink */ if (!u->module_combine_sink) { args = pa_sprintf_malloc("sink_name=%s slaves=\"%s,%s\"", SINK_COMBINED, combine_sink_arg1->name, combine_sink_arg2->name); - pa_log_debug ("[AUTO_ALL] combined sink is not prepared, now load module[%s]", args); - u->module_combine_sink = pa_module_load(u->module->core, "module-combine-sink", args); + pa_log_debug ("[ROUTE][AUTO_ALL] combined sink is not prepared, now load module[%s]", args); + u->module_combine_sink = pa_module_load(u->core, MODULE_COMBINE_SINK, args); pa_xfree(args); } - sink = pa_namereg_get(u->module->core, SINK_COMBINED, PA_NAMEREG_SINK); + sink = (pa_sink*)pa_namereg_get(u->core, SINK_COMBINED, PA_NAMEREG_SINK); PA_IDXSET_FOREACH (s, combine_sink_arg1->inputs, s_idx) { - pa_sink_input_move_to(s, sink, FALSE); - pa_log_debug("[AUTO_ALL] *** sink-input(%p,%u) moves to sink(%p,%s)", s, ((pa_sink_input*)s)->index, sink, sink->name); + if (s == data->stream) { + pa_sink_input_move_to(s, sink, FALSE); + pa_log_debug("[ROUTE][AUTO_ALL] *** sink-input(%p,%u) moves to sink(%p,%s)", s, ((pa_sink_input*)s)->index, sink, sink->name); + } } } } else if (data->stream_type == STREAM_SOURCE_OUTPUT) { @@ -750,22 +887,19 @@ static pa_hook_result_t route_change_hook_cb(pa_core *c, pa_stream_manager_hook_ } else { /* Move sink-inputs/source-outputs if needed */ if (data->idx_streams) { - PA_IDXSET_FOREACH (s, data->idx_streams, s_idx) { - if (sink && (sink != ((pa_sink_input*)s)->sink)) { - pa_sink_input_move_to(s, sink, FALSE); - pa_log_debug("[AUTO(_ALL)] *** sink-input(%p,%u) moves to sink(%p,%s)", s, ((pa_sink_input*)s)->index, sink, sink->name); - } else if (source && (source != ((pa_source_output*)s)->source)) { - pa_source_output_move_to(s, source, FALSE); - pa_log_debug("[AUTO(_ALL)] *** source-output(%p,%u) moves to source(%p,%s)", s, ((pa_source_output*)s)->index, source, source->name); - } - } - } - if (u->module_null_sink) { - pa_sink *null_sink = pa_namereg_get(u->module->core, SINK_NULL, PA_NAMEREG_SINK); - if (null_sink) { - PA_IDXSET_FOREACH (s, null_sink->inputs, s_idx) { - pa_sink_input_move_to(s, sink, FALSE); - pa_log_debug("[AUTO(_ALL)] *** sink-input(%p,%u) moves to sink(%p,%s)", s, ((pa_sink_input*)s)->index, sink, sink->name); + PA_IDXSET_FOREACH (s, data->idx_streams, s_idx) { /* data->idx_streams: null_sink */ + if ((sink && (sink != ((pa_sink_input*)s)->sink)) || (source && (source != ((pa_source_output*)s)->source))) { + if (!pa_stream_manager_get_route_type(s, FALSE, data->stream_type, &route_type) && (route_type == STREAM_ROUTE_TYPE_AUTO_ALL)) { + if (data->stream_type == STREAM_SINK_INPUT) { + pa_sink_input_move_to(s, sink, FALSE); + pa_log_debug("[ROUTE][AUTO_ALL] *** sink-input(%p,%u) moves to sink(%p,%s)", + s, ((pa_sink_input*)s)->index, sink, sink->name); + } else if (data->stream_type == STREAM_SOURCE_OUTPUT) { + pa_source_output_move_to(s, source, FALSE); + pa_log_debug("[ROUTE][AUTO_ALL] *** source-output(%p,%u) moves to source(%p,%s)", + s, ((pa_source_output*)s)->index, source, source->name); + } + } } } } @@ -785,33 +919,36 @@ static pa_hook_result_t route_change_hook_cb(pa_core *c, pa_stream_manager_hook_ } } if (need_to_deactive) - pa_device_manager_set_device_state(_device, CONVERT_TO_DEVICE_DIRECTION(data->stream_type), DM_DEVICE_STATE_DEACTIVATED); + _set_device_state(_device, data->stream_type, DM_DEVICE_STATE_DEACTIVATED); } } } else if (data->route_type == STREAM_ROUTE_TYPE_MANUAL && data->idx_manual_devices && data->idx_avail_devices) { PA_IDXSET_FOREACH(device_type, data->idx_avail_devices, idx) { - pa_log_debug("[MANUAL] avail_device[%u] for this role[%s]: type(%s)", idx, data->stream_role, device_type); + pa_log_debug("[ROUTE][MANUAL] avail_device[%u] for this role[%-16s]: type[%-16s]", idx, data->stream_role, device_type); PA_IDXSET_FOREACH(device_id, data->idx_manual_devices, d_idx) { - pa_log_debug("[MANUAL] manual_device[%u] for this role[%s]: device_id(%u)", idx, data->stream_role, *device_id); - device = pa_device_manager_get_device_by_id(u->device_manager, *device_id); - if (device) { + pa_log_debug(" -- manual_device[%u] for this role[%-16s]: device_id(%u)", idx, data->stream_role, *device_id); + if ((device = pa_device_manager_get_device_by_id(u->device_manager, *device_id))) { dm_device_type = pa_device_manager_get_device_type(device); dm_device_subtype = pa_device_manager_get_device_subtype(device); device_direction = pa_device_manager_get_device_direction(device); - pa_log_debug("[MANUAL] manual_device, type[%s], subtype[%s], direction[0x%x]", dm_device_type, dm_device_subtype, device_direction); + pa_log_debug(" -- manual_device, type[%-16s], subtype[%-5s], direction[0x%x]", dm_device_type, dm_device_subtype, device_direction); if (pa_streq(device_type, dm_device_type) && (((data->stream_type==STREAM_SINK_INPUT) && (device_direction & DM_DEVICE_DIRECTION_OUT)) || ((data->stream_type==STREAM_SOURCE_OUTPUT) && (device_direction & DM_DEVICE_DIRECTION_IN)))) { - pa_log_debug("[MANUAL] found a matched device: type[%s], direction[0x%x]", device_type, device_direction); - route_info.num_of_devices++; - route_info.device_infos = pa_xrealloc(route_info.device_infos, sizeof(hal_device_info)*route_info.num_of_devices); - route_info.device_infos[route_info.num_of_devices-1].type = dm_device_type; - route_info.device_infos[route_info.num_of_devices-1].direction = (data->stream_type==STREAM_SINK_INPUT)?DIRECTION_OUT:DIRECTION_IN; - pa_log_debug("[MANUAL] found a matched device and set state to ACTIVATED: type[%s], direction[0x%x]", - route_info.device_infos[route_info.num_of_devices-1].type, device_direction); - /* Set device state to activated */ - pa_device_manager_set_device_state(device, CONVERT_TO_DEVICE_DIRECTION(data->stream_type), DM_DEVICE_STATE_ACTIVATED); + pa_log_debug(" ** found a matched device: type[%-16s], direction[0x%x]", device_type, device_direction); + pa_device_manager_use_internal_codec(device, CONVERT_TO_DEVICE_DIRECTION(data->stream_type), DEVICE_ROLE_NORMAL, &use_internal_codec); + if (use_internal_codec) { + route_info.num_of_devices++; + route_info.device_infos = pa_xrealloc(route_info.device_infos, sizeof(hal_device_info)*route_info.num_of_devices); + route_info.device_infos[route_info.num_of_devices-1].type = dm_device_type; + route_info.device_infos[route_info.num_of_devices-1].direction = (data->stream_type==STREAM_SINK_INPUT)?DIRECTION_OUT:DIRECTION_IN; + pa_log_debug(" ** found a matched device and set state to ACTIVATED: type[%-16s], direction[0x%x]", + route_info.device_infos[route_info.num_of_devices-1].type, device_direction); + /* Set device state to activated */ + _set_device_state(device, data->stream_type, DM_DEVICE_STATE_ACTIVATED); + } else + pa_log_debug(" -- it does not use internal audio codec, skip it"); } } } @@ -827,10 +964,10 @@ static pa_hook_result_t route_change_hook_cb(pa_core *c, pa_stream_manager_hook_ PA_IDXSET_FOREACH (s, data->idx_streams, idx) { if (sink && (sink != ((pa_sink_input*)s)->sink)) { pa_sink_input_move_to(s, sink, FALSE); - pa_log_debug("[MANUAL] *** sink-input(%p,%u) moves to sink(%p,%s)", s, ((pa_sink_input*)s)->index, sink, sink->name); + pa_log_debug("[ROUTE][MANUAL] *** sink-input(%p,%u) moves to sink(%p,%s)", s, ((pa_sink_input*)s)->index, sink, sink->name); } else if (source && (source != ((pa_source_output*)s)->source)) { pa_source_output_move_to(s, source, FALSE); - pa_log_debug("[MANUAL] *** source-output(%p,%u) moves to source(%p,%s)", s, ((pa_source_output*)s)->index, source, source->name); + pa_log_debug("[ROUTE][MANUAL] *** source-output(%p,%u) moves to source(%p,%s)", s, ((pa_source_output*)s)->index, source, source->name); } } } @@ -840,10 +977,9 @@ static pa_hook_result_t route_change_hook_cb(pa_core *c, pa_stream_manager_hook_ if (route_info.device_infos) { /* Send information to HAL to set routing */ if(pa_hal_manager_do_route (u->hal_manager, &route_info)) - pa_log_error("Failed to pa_hal_manager_do_route()"); + pa_log_error("[ROUTE] Failed to pa_hal_manager_do_route()"); pa_xfree(route_info.device_infos); } -#endif return PA_HOOK_OK; } @@ -851,7 +987,7 @@ static pa_hook_result_t route_change_hook_cb(pa_core *c, pa_stream_manager_hook_ static pa_hook_result_t route_option_update_hook_cb(pa_core *c, pa_stream_manager_hook_data_for_option *data, struct userdata *u) { hal_route_option route_option; - pa_log_info("route_option_update_hook_cb is called. (%p), stream_role(%s), option[name(%s)/value(%d)]", + pa_log_info("[ROUTE_OPT] route_option_update_hook_cb is called. (%p), stream_role(%s), option[name(%s)/value(%d)]", data, data->stream_role, data->name, data->value); route_option.role = data->stream_role; route_option.name = data->name; @@ -859,41 +995,320 @@ static pa_hook_result_t route_option_update_hook_cb(pa_core *c, pa_stream_manage /* Send information to HAL to update routing option */ if(pa_hal_manager_update_route_option (u->hal_manager, &route_option)) - pa_log_error("Failed to pa_hal_manager_update_route_option()"); + pa_log_error("[ROUTE_OPT] Failed to pa_hal_manager_update_route_option()"); return PA_HOOK_OK; } -/* Reorganize routing when a device has been connected or disconnected */ -static pa_hook_result_t device_connection_changed_hook_cb(pa_core *c, pa_device_manager_hook_data_for_conn_changed *conn, struct userdata *u) { +#define MAX_CACHED_LEN 128 +typedef struct _cached_device_list { + const char *device_type; + int count; +} cached_device_list; + +static void update_sink_or_source_as_device_connection_change(stream_route_type_t stream_route_type, stream_type_t stream_type, pa_idxset *streams, dm_device *device, pa_bool_t is_connected, struct userdata *u) { + void *s = NULL; uint32_t s_idx = 0; - pa_sink_input *s = NULL; + const char *role = NULL; const char *device_type = NULL; - const char *device_subtype = NULL; + const char *cur_device_type = NULL; + const char *init_device_type = NULL; + char *next_device_type = NULL; + dm_device *next_device = NULL; + dm_device *_device = NULL; + stream_route_type_t route_type; + pa_sink *sink = NULL; + pa_sink *next_sink = NULL; + pa_sink *null_sink = NULL; + pa_source *source = NULL; + pa_source *next_source = NULL; + pa_source *null_source = NULL; + pa_bool_t available = FALSE; + pa_bool_t cur_highest_priority = FALSE; + cached_device_list cached_prev_dev_list[MAX_CACHED_LEN] = {{NULL,0},}; + uint32_t cnt = 0; + pa_sink *combine_sink = NULL; + pa_stream_manager_hook_data_for_update_route hook_call_update_route_data; + + null_sink = (pa_sink*)pa_namereg_get(u->core, SINK_NULL, PA_NAMEREG_SINK); + null_source = (pa_source*)pa_namereg_get(u->core, SOURCE_NULL, PA_NAMEREG_SOURCE); + if (!null_sink || !null_source) { + pa_log_error("[UPDATE_SINK_SOURCE] could not get null_sink(%p) or null_source(%p)", null_sink, null_source); + return; + } + device_type = pa_device_manager_get_device_type(device); + + if (stream_route_type == STREAM_ROUTE_TYPE_AUTO) { + pa_log_debug("[UPDATE_SINK_SOURCE][AUTO] deivce_type(%s), is_connected(%d))", device_type, is_connected); + if (stream_type == STREAM_SINK_INPUT) + sink = pa_device_manager_get_sink(device, DEVICE_ROLE_NORMAL); + else + source = pa_device_manager_get_source(device, DEVICE_ROLE_NORMAL); + + PA_IDXSET_FOREACH (s, streams, s_idx) { /* streams: core->source_outputs/core->sink_inputs */ + if (!pa_stream_manager_get_route_type(s, FALSE, stream_type, &route_type) && (route_type == stream_route_type)) { + role = pa_proplist_gets(GET_STREAM_PROPLIST(s, stream_type), PA_PROP_MEDIA_ROLE); + pa_log_debug(" -- idx(%u), route_type(%d), role(%s)", s_idx, route_type, role); + if (is_connected) { + /* CONNECTED: move a stream to the new device if possible */ + if (sink && (sink != ((pa_sink_input*)s)->sink)) { + if ((cur_device_type = pa_proplist_gets(GET_STREAM_PROPLIST(s, stream_type), PA_PROP_MEDIA_ROUTE_AUTO_ACTIVE_DEV))) { + pa_stream_manager_is_available_device_for_auto_route(cur_device_type, device_type, role, stream_type, &available, u->stream_manager); + if (available) { + pa_sink_input_move_to(s, sink, FALSE); + pa_log_debug(" -- *** sink-input(%p,%u) moves to sink(%p,%s), new device(%s)", + s, ((pa_sink_input*)s)->index, sink, sink->name, device_type); + /* update activated device */ + pa_proplist_sets(GET_STREAM_PROPLIST(s, stream_type), PA_PROP_MEDIA_ROUTE_AUTO_ACTIVE_DEV, device_type); + if ((_device = pa_device_manager_get_device(u->device_manager, device_type))) + _set_device_state(_device, stream_type, DM_DEVICE_STATE_ACTIVATED); + cached_prev_dev_list[cnt++].device_type = cur_device_type; + } + } else + pa_log_error(" -- could not find current device type for s->sink(%p)", ((pa_sink_input*)s)->sink); + } else if (source && (source != ((pa_source_output*)s)->source)) { + if ((cur_device_type = pa_proplist_gets(GET_STREAM_PROPLIST(s, stream_type), PA_PROP_MEDIA_ROUTE_AUTO_ACTIVE_DEV))) { + pa_stream_manager_is_available_device_for_auto_route(cur_device_type, device_type, role, stream_type, &available, u->stream_manager); + if (available) { + pa_source_output_move_to(s, source, FALSE); + pa_log_debug(" -- *** source-output(%p,%u) moves to source(%p,%s), new device(%s)", + s, ((pa_source_output*)s)->index, source, source->name, device_type); + /* update activated device */ + pa_proplist_sets(GET_STREAM_PROPLIST(s, stream_type), PA_PROP_MEDIA_ROUTE_AUTO_ACTIVE_DEV, device_type); + if ((_device = pa_device_manager_get_device(u->device_manager, device_type))) + _set_device_state(_device, stream_type, DM_DEVICE_STATE_ACTIVATED); + cached_prev_dev_list[cnt++].device_type = cur_device_type; + } + } else + pa_log_error(" -- could not find current device type for s->source(%p)", ((pa_source_output*)s)->source); + } + + } else if (!is_connected) { + /* DISCONNECTED: find a connected device that has the next priority */ + if (sink && (sink == ((pa_sink_input*)s)->sink)) { + init_device_type = device_type; + do { + pa_stream_manager_find_next_priority_device_for_auto_route(device_type, role, stream_type, &next_device_type, u->stream_manager); + pa_log_debug(" -- prev_device(%s), new_device(%s)", device_type, next_device_type); + if (next_device_type) { + if ((next_device = pa_device_manager_get_device(u->device_manager, next_device_type))) { + if ((next_sink = pa_device_manager_get_sink(next_device, DEVICE_ROLE_NORMAL))) { + pa_sink_input_move_to(s, next_sink, FALSE); + pa_log_debug(" -- *** sink-input(%p,%u) moves to sink(%p,%s), new device(%s)", + s, ((pa_sink_input*)s)->index, next_sink, next_sink->name, next_device_type); + /* update activated device */ + pa_proplist_sets(GET_STREAM_PROPLIST(s, stream_type), PA_PROP_MEDIA_ROUTE_AUTO_ACTIVE_DEV, next_device_type); + _set_device_state(next_device, stream_type, DM_DEVICE_STATE_ACTIVATED); + cached_prev_dev_list[cnt++].device_type = init_device_type; + + pa_stream_manager_is_current_highest_priority(s, stream_type, &cur_highest_priority, u->stream_manager); + if (cur_highest_priority && next_sink->use_internal_codec) { + /* trigger to update route path for internal audio codec */ + memset(&hook_call_update_route_data, 0, sizeof(pa_stream_manager_hook_data_for_update_route)); + hook_call_update_route_data.stream_type = stream_type; + pa_hook_fire(pa_communicator_hook(u->communicator.comm, PA_COMMUNICATOR_HOOK_NEED_UPDATE_ROUTE), &hook_call_update_route_data); + } + break; + } + } else { + device_type = next_device_type; + continue; + } + } + } while (next_device_type); + + if (!next_device_type || !next_sink) { + pa_sink_input_move_to(s, null_sink, FALSE); + pa_log_debug(" -- *** sink-input(%p,%u) moves to sink(%p,%s)", + s, ((pa_sink_input*)s)->index, null_sink, null_sink->name); + } + + } else if (source && (source == ((pa_source_output*)s)->source)) { + init_device_type = device_type; + do { + pa_stream_manager_find_next_priority_device_for_auto_route(device_type, role, stream_type, &next_device_type, u->stream_manager); + pa_log_debug(" -- prev_device(%s), new_device(%s)", device_type, next_device_type); + if (next_device_type) { + if ((next_device = pa_device_manager_get_device(u->device_manager, next_device_type))) { + if ((next_source = pa_device_manager_get_source(next_device, DEVICE_ROLE_NORMAL))) { + pa_source_output_move_to(s, next_source, FALSE); + pa_log_debug(" -- *** source-output(%p,%u) moves to source(%p,%s), new device(%s)", + s, ((pa_source_output*)s)->index, next_source, next_source->name, next_device_type); + /* update activated device */ + pa_proplist_sets(GET_STREAM_PROPLIST(s, stream_type), PA_PROP_MEDIA_ROUTE_AUTO_ACTIVE_DEV, next_device_type); + _set_device_state(next_device, stream_type, DM_DEVICE_STATE_ACTIVATED); + cached_prev_dev_list[cnt++].device_type = init_device_type; + + pa_stream_manager_is_current_highest_priority(s, stream_type, &cur_highest_priority, u->stream_manager); + if (cur_highest_priority && next_source->use_internal_codec) { + /* trigger to update route path for internal audio codec */ + memset(&hook_call_update_route_data, 0, sizeof(pa_stream_manager_hook_data_for_update_route)); + hook_call_update_route_data.stream_type = stream_type; + pa_hook_fire(pa_communicator_hook(u->communicator.comm, PA_COMMUNICATOR_HOOK_NEED_UPDATE_ROUTE), &hook_call_update_route_data); + } + break; + } + } else { + device_type = next_device_type; + continue; + } + } + } while (next_device_type); + + if (!next_device_type || !next_source) { + pa_source_output_move_to(s, null_source, FALSE); + pa_log_debug(" -- *** source-output(%p,%u) moves to source(%p,%s)", + s, ((pa_source_output*)s)->index, null_source, null_source->name); + } + } + } + } + } + + /* Set device state to deactivated */ + if (cnt) { + PA_IDXSET_FOREACH (s, streams, s_idx) { /* streams: core->source_outputs/core->sink_inputs */ + if ((cur_device_type = pa_proplist_gets(GET_STREAM_PROPLIST(s, stream_type), PA_PROP_MEDIA_ROUTE_AUTO_ACTIVE_DEV))) { + for (cnt = 0; cached_prev_dev_list[cnt].device_type; cnt++) { + pa_log_warn(" -- cur_device(%s), cached_device(%s)", cur_device_type, cached_prev_dev_list[cnt].device_type); + if (pa_streq(cur_device_type, cached_prev_dev_list[cnt].device_type)) + cached_prev_dev_list[cnt].count++; + } + } + } + if (stream_type == STREAM_SINK_INPUT) { + combine_sink = (pa_sink*)pa_namereg_get(u->core, SINK_COMBINED, PA_NAMEREG_SINK); + if (combine_sink && pa_idxset_size(combine_sink->inputs)) { + pa_log_warn(" -- combine sink has streams, skip it.."); + return; + } + } + for (cnt = 0; cached_prev_dev_list[cnt].device_type; cnt++) { + if (cached_prev_dev_list[cnt].count == 0) { + pa_log_debug(" -- device_type(%s)", cached_prev_dev_list[cnt].device_type); + _device = pa_device_manager_get_device(u->device_manager, cached_prev_dev_list[cnt].device_type); + if (_device) + _set_device_state(_device, stream_type, DM_DEVICE_STATE_DEACTIVATED); + else + pa_log_warn(" -- _device is null"); + } + } + } + + } else if (stream_route_type == STREAM_ROUTE_TYPE_MANUAL_EXT) { + pa_log_debug("[UPDATE_SINK_SOURCE][EXT] deivce_type(%s), is_connected(%d))", device_type, is_connected); + if (!is_connected) { + PA_IDXSET_FOREACH (s, streams, s_idx) { /* streams: source->outputs/sink->inputs */ + if (!pa_stream_manager_get_route_type(s, FALSE, stream_type, &route_type) && route_type == stream_route_type) { + if (stream_type == STREAM_SOURCE_OUTPUT) { + /* move it to null source if this role is for external device */ + pa_source_output_move_to((pa_source_output*)s, null_source, FALSE); + pa_log_debug(" -- *** source-output(%p,%u) moves to source(%p,%s)", s, ((pa_source_output*)s)->index, null_source, null_source->name); + } else { + /* move it to null sink if this role is for external device */ + pa_sink_input_move_to((pa_sink_input*)s, null_sink, FALSE); + pa_log_debug(" -- *** sink-input(%p,%u) moves to sink(%p,%s)", s, ((pa_sink_input*)s)->index, null_sink, null_sink->name); + } + } + } + } else { + /* do nothing, it will be updated by stream-manager */ + } + } else + pa_log_error("[UPDATE_SINK_SOURCE] could not handle it here, stream_route_type(%d)", stream_route_type); + + return; +} + +/* Reorganize routing when a device using external audio codec has been connected or disconnected */ +static pa_hook_result_t device_connection_changed_hook_cb(pa_core *c, pa_device_manager_hook_data_for_conn_changed *conn, struct userdata *u) { + uint32_t idx = 0; + pa_sink_input *s = NULL; dm_device_direction_t device_direction = DM_DEVICE_DIRECTION_OUT; + pa_sink *sink = NULL; + pa_sink *null_sink = NULL; + pa_source *source = NULL; + pa_source *null_source = NULL; + pa_sink *combine_sink = NULL; + pa_bool_t use_internal_codec = FALSE; + pa_idxset* conn_devices = NULL; + dm_device* device = NULL; device_direction = pa_device_manager_get_device_direction(conn->device); - device_type = pa_device_manager_get_device_type(conn->device); - device_subtype = pa_device_manager_get_device_subtype(conn->device); - pa_log_info("device_connection_changed_hook_cb is called. conn(%p), is_connected(%d), device(%p,%s,%s), direction(0x%x)", - conn, conn->is_connected, conn->device, device_type, device_subtype, device_direction); - - if (!conn->is_connected && pa_streq(DEVICE_TYPE_BT, device_type) && - device_subtype && pa_streq(DEVICE_PROFILE_BT_A2DP, device_subtype) && - device_direction == DM_DEVICE_DIRECTION_OUT) { - if (u->module_combine_sink) { - /* unload combine sink */ - pa_sink *combine_sink = pa_namereg_get(u->module->core, SINK_COMBINED, PA_NAMEREG_SINK); - pa_sink *null_sink = pa_namereg_get(u->module->core, SINK_NULL, PA_NAMEREG_SINK); - if (combine_sink->inputs) { - PA_IDXSET_FOREACH (s, combine_sink->inputs, s_idx) { - pa_sink_input_move_to(s, null_sink, FALSE); - pa_log_debug(" *** sink-input(%p,%u) moves to sink(%p,%s)", s, ((pa_sink_input*)s)->index, null_sink, null_sink->name); - } + + pa_log_info("[CONN] device_connection_changed_hook_cb is called. conn(%p), is_connected(%d), device(%p), direction(0x%x)", + conn, conn->is_connected, conn->device, device_direction); + + null_sink = (pa_sink*)pa_namereg_get(u->core, SINK_NULL, PA_NAMEREG_SINK); + null_source = (pa_source*)pa_namereg_get(u->core, SOURCE_NULL, PA_NAMEREG_SOURCE); + if (!null_sink || !null_source) { + pa_log_error("[CONN] could not get null_sink(%p) or null_source(%p)", null_sink, null_source); + return PA_HOOK_OK; + } + + pa_device_manager_use_internal_codec(conn->device, device_direction, DEVICE_ROLE_NORMAL, &use_internal_codec); + if (!use_internal_codec) { + if (!conn->is_connected && (device_direction & DM_DEVICE_DIRECTION_OUT)) { + if (u->module_combine_sink) { + /* unload combine sink */ + if ((combine_sink = (pa_sink*)pa_namereg_get(u->core, SINK_COMBINED, PA_NAMEREG_SINK))) { + conn_devices = pa_device_manager_get_device_list(u->device_manager); + PA_IDXSET_FOREACH(device, conn_devices, idx) { + device_direction = pa_device_manager_get_device_direction(device); + if (device_direction == DM_DEVICE_DIRECTION_OUT) { + pa_device_manager_use_internal_codec(device, device_direction, DEVICE_ROLE_NORMAL, &use_internal_codec); + if (use_internal_codec) { + sink = pa_device_manager_get_sink(device, DEVICE_ROLE_NORMAL); + break; + } + } + } + if (combine_sink->inputs) { + if (!sink) + sink = null_sink; + PA_IDXSET_FOREACH (s, combine_sink->inputs, idx) { + /* re-route this stream to the remaining device using internal codec */ + pa_sink_input_move_to(s, sink, FALSE); + pa_log_debug("[CONN] *** sink-input(%p,%u) moves to sink(%p,%s)", s, ((pa_sink_input*)s)->index, sink, sink->name); + } + } + pa_sink_suspend(combine_sink, TRUE, PA_SUSPEND_USER); + pa_module_unload(u->core, u->module_combine_sink, TRUE); + u->module_combine_sink = NULL; + } else + pa_log_error("[CONN] could not get combine_sink"); + } + if (u->module_combine_sink_for_ex) { + /* unload combine sink for external devices */ + if ((combine_sink = (pa_sink*)pa_namereg_get(u->core, SINK_COMBINED_EX, PA_NAMEREG_SINK))) { + if (combine_sink->inputs) { + PA_IDXSET_FOREACH (s, combine_sink->inputs, idx) { + pa_sink_input_move_to(s, null_sink, FALSE); + pa_log_debug("[CONN] *** sink-input(%p,%u) moves to sink(%p,%s)", s, ((pa_sink_input*)s)->index, null_sink, null_sink->name); + } + } + pa_sink_suspend(combine_sink, TRUE, PA_SUSPEND_USER); + pa_module_unload(u->core, u->module_combine_sink_for_ex, TRUE); + u->module_combine_sink_for_ex = NULL; + } else + pa_log_error("[CONN] could not get combine_sink_ex"); } - pa_sink_suspend(pa_namereg_get(u->module->core, SINK_COMBINED, PA_NAMEREG_SINK), TRUE, PA_SUSPEND_USER); - pa_module_unload(u->module->core, u->module_combine_sink, TRUE); - u->module_combine_sink = NULL; + } + if (device_direction & DM_DEVICE_DIRECTION_IN) { + if ((source = pa_device_manager_get_source(conn->device, DEVICE_ROLE_NORMAL))) + update_sink_or_source_as_device_connection_change(STREAM_ROUTE_TYPE_MANUAL_EXT, STREAM_SOURCE_OUTPUT, source->outputs, conn->device, conn->is_connected, u); + else + pa_log_error("[CONN] could not get source"); + + update_sink_or_source_as_device_connection_change(STREAM_ROUTE_TYPE_AUTO, STREAM_SOURCE_OUTPUT, u->core->source_outputs, conn->device, conn->is_connected, u); + } + if (device_direction & DM_DEVICE_DIRECTION_OUT) { + if ((sink = pa_device_manager_get_sink(conn->device, DEVICE_ROLE_NORMAL))) + update_sink_or_source_as_device_connection_change(STREAM_ROUTE_TYPE_MANUAL_EXT, STREAM_SINK_INPUT, sink->inputs, conn->device, conn->is_connected, u); + else + pa_log_error("[CONN] could not get sink"); + + update_sink_or_source_as_device_connection_change(STREAM_ROUTE_TYPE_AUTO, STREAM_SINK_INPUT, u->core->sink_inputs, conn->device, conn->is_connected, u); } } @@ -1461,16 +1876,14 @@ int pa__init(pa_module *m) } u->stream_manager = pa_stream_manager_init(u->core); -#ifdef DEVICE_MANAGER u->device_manager = pa_device_manager_init(u->core); -#endif /* load null sink/source */ args = pa_sprintf_malloc("sink_name=%s", SINK_NULL); - u->module_null_sink = pa_module_load(u->module->core, "module-null-sink", args); + u->module_null_sink = pa_module_load(u->core, "module-null-sink", args); pa_xfree(args); args = pa_sprintf_malloc("source_name=%s", SOURCE_NULL); - u->module_null_source = pa_module_load(u->module->core, "module-null-source", args); + u->module_null_source = pa_module_load(u->core, "module-null-source", args); pa_xfree(args); __load_dump_config(u); @@ -1504,18 +1917,17 @@ void pa__done(pa_module *m) if (!(u = m->userdata)) return; - pa_module_unload(u->module->core, u->module_null_sink, TRUE); + pa_module_unload(u->core, u->module_null_sink, TRUE); u->module_null_sink = NULL; - pa_module_unload(u->module->core, u->module_null_source, TRUE); + pa_module_unload(u->core, u->module_null_source, TRUE); u->module_null_source = NULL; #ifdef HAVE_DBUS dbus_deinit(u); #endif -#ifdef DEVICE_MANAGER if (u->device_manager) pa_device_manager_done(u->device_manager); -#endif + if (u->protocol) { pa_native_protocol_remove_ext(u->protocol, m); pa_native_protocol_unref(u->protocol); @@ -1525,9 +1937,11 @@ void pa__done(pa_module *m) pa_stream_manager_done(u->stream_manager); if (u->communicator.comm) { + if (u->communicator.comm_hook_select_proper_sink_or_source_slot) + pa_hook_slot_free(u->communicator.comm_hook_select_proper_sink_or_source_slot); if (u->communicator.comm_hook_change_route_slot) pa_hook_slot_free(u->communicator.comm_hook_change_route_slot); - if (u->communicator.comm_hook_change_route_slot) + if (u->communicator.comm_hook_update_route_option_slot) pa_hook_slot_free(u->communicator.comm_hook_update_route_option_slot); if (u->communicator.comm_hook_device_connection_changed_slot) pa_hook_slot_free(u->communicator.comm_hook_device_connection_changed_slot); diff --git a/src/stream-manager-priv.h b/src/stream-manager-priv.h index 5244e53..5ccbe24 100644 --- a/src/stream-manager-priv.h +++ b/src/stream-manager-priv.h @@ -14,11 +14,17 @@ #include #endif -#define GET_STREAM_NEW_PROPLIST(stream, type) \ - (type == STREAM_SINK_INPUT? ((pa_sink_input_new_data*)stream)->proplist : ((pa_source_output_new_data*)stream)->proplist) +typedef enum _focus_acquired_status { + STREAM_FOCUS_ACQUIRED_NONE = 0x00, + STREAM_FOCUS_ACQUIRED_PLAYBACK = 0x01, + STREAM_FOCUS_ACQUIRED_CAPTURE = 0x02, +} focus_acquired_status_t; -#define GET_STREAM_PROPLIST(stream, type) \ - (type == STREAM_SINK_INPUT? ((pa_sink_input*)stream)->proplist : ((pa_source_output*)stream)->proplist) +enum stream_direction { + STREAM_DIRECTION_IN, + STREAM_DIRECTION_OUT, + STREAM_DIRECTION_MAX, +}; #define GET_STREAM_NEW_SAMPLE_SPEC(stream, type) \ (type == STREAM_SINK_INPUT? ((pa_sink_input_new_data*)stream)->sample_spec : ((pa_source_output_new_data*)stream)->sample_spec) @@ -29,11 +35,6 @@ #define IS_FOCUS_ACQUIRED(focus, type) \ (type == STREAM_SINK_INPUT? (focus & STREAM_FOCUS_ACQUIRED_PLAYBACK) : (focus & STREAM_FOCUS_ACQUIRED_CAPTURE)) -enum stream_direction { - STREAM_DIRECTION_IN, - STREAM_DIRECTION_OUT, - STREAM_DIRECTION_MAX, -}; typedef struct _stream_info { int32_t priority; @@ -47,9 +48,9 @@ typedef struct _stream_info { typedef struct _volume_info { pa_bool_t is_hal_volume_type; struct _values { - pa_bool_t is_muted; + pa_bool_t is_muted; uint32_t current_level; - pa_idxset *idx_volume_values; + pa_idxset *idx_volume_values; } values[STREAM_DIRECTION_MAX]; } volume_info; @@ -91,6 +92,7 @@ struct _stream_manager { pa_communicator *comm; pa_hook_slot *comm_hook_device_connection_changed_slot; pa_hook_slot *comm_hook_device_information_changed_slot; + pa_hook_slot *comm_hook_need_update_route_slot; } comm; }; diff --git a/src/stream-manager.c b/src/stream-manager.c index c229c0b..bbfd86e 100644 --- a/src/stream-manager.c +++ b/src/stream-manager.c @@ -308,12 +308,6 @@ const char* stream_manager_media_names_for_skip[NAME_FOR_SKIP_MAX] = {"pulsesink #define STREAM_FOCUS_PLAYBACK "1" #define STREAM_FOCUS_CAPTURE "2" -typedef enum _focus_acquired_status { - STREAM_FOCUS_ACQUIRED_NONE = 0x00, - STREAM_FOCUS_ACQUIRED_PLAYBACK = 0x01, - STREAM_FOCUS_ACQUIRED_CAPTURE = 0x02, -} focus_acquired_status_t; - typedef enum _process_stream_result { PROCESS_STREAM_RESULT_OK, PROCESS_STREAM_RESULT_STOP, @@ -333,7 +327,6 @@ typedef enum _process_command_type { typedef enum _notify_command_type { NOTIFY_COMMAND_SELECT_PROPER_SINK_OR_SOURCE_FOR_INIT, - NOTIFY_COMMAND_CHANGE_ROUTE_START_WITH_NEW_DATA, NOTIFY_COMMAND_CHANGE_ROUTE_START, NOTIFY_COMMAND_CHANGE_ROUTE_END, NOTIFY_COMMAND_UPDATE_ROUTE_OPTION, @@ -354,7 +347,6 @@ const char* process_command_type_str[] = { const char* notify_command_type_str[] = { "SELECT_PROPER_SINK_OR_SOURCE_FOR_INIT", - "CHANGE_ROUTE_START_WITH_NEW_DATA", "CHANGE_ROUTE_START", "CHANGE_ROUTE_END", "UPDATE_ROUTE_OPTION", @@ -411,7 +403,7 @@ typedef struct _stream_route_option { int32_t value; } stream_route_option; -static void do_notify(pa_stream_manager *m, notify_command_type_t command, stream_type_t type, void *user_data); +static void do_notify(pa_stream_manager *m, notify_command_type_t command, stream_type_t type, pa_bool_t is_new_data, void *user_data); static process_stream_result_t process_stream(stream_type_t type, void *stream, process_command_type_t command, pa_stream_manager *m); static int get_available_streams(pa_stream_manager *m, stream_list *list) { @@ -579,7 +571,10 @@ static void handle_set_stream_route_devices(DBusConnection *conn, DBusMessage *m int list_len_out = 0; uint32_t idx = 0; uint32_t *device_id = NULL; + void *stream = NULL; stream_parent *sp = NULL; + const char *route_type_str = NULL; + stream_route_type_t route_type; DBusMessage *reply = NULL; pa_stream_manager *m = (pa_stream_manager*)userdata; @@ -615,12 +610,25 @@ static void handle_set_stream_route_devices(DBusConnection *conn, DBusMessage *m pa_log_debug(" -- [in] device id:%u", in_device_list[i]); } } - if (m->cur_highest_priority.source_output) { + PA_IDXSET_FOREACH(stream, sp->idx_source_outputs, idx) { + /* find route type of stream */ + route_type_str = pa_proplist_gets(GET_STREAM_PROPLIST(stream, STREAM_SOURCE_OUTPUT), PA_PROP_MEDIA_ROLE_ROUTE_TYPE); + if (route_type_str) { + pa_log_debug(" -- the route type of source_output that belongs to this parent id[%u] is [%s]", id, route_type_str); + break; + } + } /* if any stream that belongs to this id has been activated, do notify right away */ + if (IS_ROUTE_TYPE_FOR_EXTERNAL_DEV(route_type_str, route_type)) { + PA_IDXSET_FOREACH(stream, sp->idx_source_outputs, idx) { + pa_log_debug(" -- source_output[%p] belongs to this parent id[%u], do notify for the select proper source", stream, id); + do_notify(m, NOTIFY_COMMAND_SELECT_PROPER_SINK_OR_SOURCE_FOR_INIT, STREAM_SOURCE_OUTPUT, FALSE, stream); + } + } else if (m->cur_highest_priority.source_output) { if (pa_idxset_get_by_data(sp->idx_source_outputs, m->cur_highest_priority.source_output, NULL)) { pa_log_debug(" -- cur_highest_priority.source_output->index[%u] belongs to this parent id[%u], do notify for the route change", (m->cur_highest_priority.source_output)->index, id); - do_notify(m, NOTIFY_COMMAND_CHANGE_ROUTE_START, STREAM_SOURCE_OUTPUT, m->cur_highest_priority.source_output); + do_notify(m, NOTIFY_COMMAND_CHANGE_ROUTE_START, STREAM_SOURCE_OUTPUT, FALSE, m->cur_highest_priority.source_output); } } } else { @@ -639,12 +647,25 @@ static void handle_set_stream_route_devices(DBusConnection *conn, DBusMessage *m pa_log_debug(" -- [out] device id:%u", out_device_list[i]); } } - if (m->cur_highest_priority.sink_input) { + PA_IDXSET_FOREACH(stream, sp->idx_sink_inputs, idx) { + /* find route type of stream */ + route_type_str = pa_proplist_gets(GET_STREAM_PROPLIST(stream, STREAM_SINK_INPUT), PA_PROP_MEDIA_ROLE_ROUTE_TYPE); + if (route_type_str) { + pa_log_debug(" -- the route type of sink_input that belongs to this parent id[%u] is [%s]", id, route_type_str); + break; + } + } /* if any stream that belongs to this id has been activated, do notify right away */ + if (IS_ROUTE_TYPE_FOR_EXTERNAL_DEV(route_type_str, route_type)) { + PA_IDXSET_FOREACH(stream, sp->idx_sink_inputs, idx) { + pa_log_debug(" -- sink_input[%p] belongs to this parent id[%u], do notify for the select proper sink", stream, id); + do_notify(m, NOTIFY_COMMAND_SELECT_PROPER_SINK_OR_SOURCE_FOR_INIT, STREAM_SINK_INPUT, FALSE, stream); + } + } else if (m->cur_highest_priority.sink_input) { if (pa_idxset_get_by_data(sp->idx_sink_inputs, m->cur_highest_priority.sink_input, NULL)) { pa_log_debug(" -- cur_highest_priority.sink_input->index[%u] belongs to this parent id[%u], do notify for the route change", (m->cur_highest_priority.sink_input)->index, id); - do_notify(m, NOTIFY_COMMAND_CHANGE_ROUTE_START, STREAM_SINK_INPUT, m->cur_highest_priority.sink_input); + do_notify(m, NOTIFY_COMMAND_CHANGE_ROUTE_START, STREAM_SINK_INPUT, FALSE, m->cur_highest_priority.sink_input); } } } else { @@ -701,7 +722,7 @@ static void handle_set_stream_route_option(DBusConnection *conn, DBusMessage *ms if (pa_idxset_get_by_data(sp->idx_sink_inputs, m->cur_highest_priority.sink_input, NULL)) { pa_log_debug(" -- cur_highest_priority.sink_input->index[%u] belongs to this parent id[%u], do notify for the options", (m->cur_highest_priority.sink_input)->index, id); - do_notify(m, NOTIFY_COMMAND_UPDATE_ROUTE_OPTION, STREAM_SINK_INPUT, &route_option); + do_notify(m, NOTIFY_COMMAND_UPDATE_ROUTE_OPTION, STREAM_SINK_INPUT, FALSE, &route_option); updated = TRUE; } } @@ -709,7 +730,7 @@ static void handle_set_stream_route_option(DBusConnection *conn, DBusMessage *ms if (pa_idxset_get_by_data(sp->idx_source_outputs, m->cur_highest_priority.source_output, NULL)) { pa_log_debug(" -- cur_highest_priority.source_output->index[%u] belongs to this parent id[%u], do notify for the options", (m->cur_highest_priority.source_output)->index, id); - do_notify(m, NOTIFY_COMMAND_UPDATE_ROUTE_OPTION, STREAM_SOURCE_OUTPUT, &route_option); + do_notify(m, NOTIFY_COMMAND_UPDATE_ROUTE_OPTION, STREAM_SOURCE_OUTPUT, FALSE, &route_option); updated = TRUE; } } @@ -1138,6 +1159,8 @@ static int convert_route_type(stream_route_type_t *route_type, const char *route *route_type = STREAM_ROUTE_TYPE_AUTO_ALL; else if (pa_streq("manual", route_type_string)) *route_type = STREAM_ROUTE_TYPE_MANUAL; + else if (pa_streq("manual-ext", route_type_string)) + *route_type = STREAM_ROUTE_TYPE_MANUAL_EXT; else { ret = -1; pa_log_error("Not supported route_type(%s)", route_type_string); @@ -1157,7 +1180,7 @@ static void dump_stream_map (pa_stream_manager *m) { while (m->stream_infos && (s = pa_hashmap_iterate(m->stream_infos, &state, (const void **)&role))) { pa_log_debug("[role : %s]", role); pa_log_debug(" - priority : %d", s->priority); - pa_log_debug(" - route-type : %d (0:auto,1:auto-all,2:manual)", s->route_type); + pa_log_debug(" - route-type : %d (0:auto,1:auto-all,2:manual,3:manual-ext)", s->route_type); pa_log_debug(" - volume-types : in[%s], out[%s]", s->volume_type[STREAM_DIRECTION_IN], s->volume_type[STREAM_DIRECTION_OUT]); pa_log_debug(" - avail-in-devices"); PA_IDXSET_FOREACH(name, s->idx_avail_in_devices, idx) @@ -1237,8 +1260,8 @@ static int init_stream_map (pa_stream_manager *m) { pa_log_error("Get is-hal-volume failed"); goto failed; } + pa_hashmap_put(m->volume_infos,(void*)volume_type, v); } - pa_hashmap_put(m->volume_infos,(void*)volume_type, v); } } @@ -1354,7 +1377,7 @@ static int init_stream_map (pa_stream_manager *m) { return 0; failed: - pa_log_error("Failed to initialize stream-map"); + pa_log_error("failed to initialize stream-map"); if (m->stream_infos) { PA_HASHMAP_FOREACH(s, m->stream_infos, state) { if (s->idx_avail_in_devices) @@ -1450,12 +1473,29 @@ static pa_bool_t check_role_to_skip(pa_stream_manager *m, const char *role) { if (s) ret = FALSE; } - pa_log_info("role is [%s], skip(%d)", role, ret); return ret; } +static pa_bool_t check_route_type_to_skip(process_command_type_t command, const char *route_type_str) { + pa_bool_t ret = FALSE; + stream_route_type_t route_type; + pa_assert(route_type_str); + + if (!pa_atoi(route_type_str, (int32_t*)&route_type)) { + if ((route_type == STREAM_ROUTE_TYPE_MANUAL_EXT) && + (command > PROCESS_COMMAND_PREPARE && command < PROCESS_COMMAND_UPDATE_VOLUME)) + ret = TRUE; /* this route route is for external device, need to skip */ + } else { + pa_log_error("could not convert route_type_str(%s) to int", route_type_str); + ret = TRUE; + } + pa_log_debug("command is [%s], skip(%d) for route_type(%d)", process_command_type_str[command], ret, route_type); + + return ret; +} + static pa_bool_t update_priority_of_stream(pa_stream_manager *m, process_command_type_t command, stream_type_t type, void *stream, const char *role) { stream_info *s = NULL; @@ -1477,7 +1517,7 @@ static pa_bool_t update_priority_of_stream(pa_stream_manager *m, process_command return TRUE; } -static pa_bool_t update_routing_type_of_stream(pa_stream_manager *m, void *stream, stream_type_t type, const char *role) { +static pa_bool_t update_route_type_of_stream(pa_stream_manager *m, void *stream, stream_type_t type, const char *role) { stream_route_type_t route_type = STREAM_ROUTE_TYPE_AUTO; stream_info *s = NULL; @@ -1594,6 +1634,8 @@ static pa_bool_t update_the_highest_priority_stream(pa_stream_manager *m, proces uint32_t idx = 0; size_t size = 0; const int32_t *priority = NULL; + const char *route_type_str = NULL; + stream_route_type_t route_type; const char *focus_status = NULL; void *cur_max_stream = NULL; void *cur_max_stream_tmp = NULL; @@ -1638,24 +1680,20 @@ static pa_bool_t update_the_highest_priority_stream(pa_stream_manager *m, proces /* TODO : need to check if this stream should be played to external devices */ if (command == PROCESS_COMMAND_CHANGE_ROUTE_BY_STREAM_STARTED_WITH_NEW_DATA) { if (pa_proplist_get(GET_STREAM_NEW_PROPLIST(mine, type), PA_PROP_MEDIA_ROLE_PRIORITY, (const void**)&priority, &size)) - pa_log_error("Failed to pa_proplist_get() for priority"); + pa_log_error("failed to pa_proplist_get() for priority"); } else { - if (cur_max_stream == mine) { - pa_log_debug("it has already been processed, skip it.."); - return FALSE; - } if (pa_proplist_get(GET_STREAM_PROPLIST(mine, type), PA_PROP_MEDIA_ROLE_PRIORITY, (const void**)&priority, &size)) - pa_log_error("Failed to pa_proplist_get() for priority"); + pa_log_error("failed to pa_proplist_get() for priority"); } if (pa_proplist_get(GET_STREAM_PROPLIST(cur_max_stream, type), PA_PROP_MEDIA_ROLE_PRIORITY, (const void**)&cur_max_priority, &size)) - pa_log_error("Failed to pa_proplist_get() for priority"); + pa_log_error("failed to pa_proplist_get() for priority"); focus_status = pa_proplist_gets(GET_STREAM_PROPLIST(cur_max_stream, type), PA_PROP_MEDIA_FOCUS_STATUS); if (focus_status && !pa_atoi(focus_status, &cur_max_focus_status_int)) { pa_log_debug("cur_max_focus status(0x%x)", cur_max_focus_status_int); } cur_max_role = pa_proplist_gets(GET_STREAM_PROPLIST(cur_max_stream, type), PA_PROP_MEDIA_ROLE); if (!cur_max_priority || !cur_max_role) { - pa_log_error("Failed to pa_proplist_gets() for getting current max priority(%p) and it's role(%s)", cur_max_priority, cur_max_role); + pa_log_error("failed to pa_proplist_gets() for getting current max priority(%p) and it's role(%s)", cur_max_priority, cur_max_role); return FALSE; } else { if (priority && cur_max_priority) { @@ -1674,29 +1712,35 @@ static pa_bool_t update_the_highest_priority_stream(pa_stream_manager *m, proces command == PROCESS_COMMAND_CHANGE_ROUTE_BY_STREAM_FOCUS_CHANGED) { if (cur_max_stream == mine || command == PROCESS_COMMAND_CHANGE_ROUTE_BY_STREAM_FOCUS_CHANGED) { if (type == STREAM_SINK_INPUT) { - streams = ((pa_sink_input*)mine)->sink->inputs; + streams = m->core->sink_inputs; } else if (type == STREAM_SOURCE_OUTPUT) { - streams = ((pa_source_output*)mine)->source->outputs; + streams = m->core->source_outputs; } /* find the next highest priority input */ - //PA_IDXSET_FOREACH(i, m->core->sinks, idx) { /* need to check a sink which this stream belongs to */ PA_IDXSET_FOREACH(i, streams, idx) { if (!(_role = pa_proplist_gets(GET_STREAM_PROPLIST(i, type), PA_PROP_MEDIA_ROLE))) { - pa_log_error("Failed to pa_proplist_gets() for role"); + pa_log_error("failed to pa_proplist_gets() for role"); continue; } if (pa_proplist_get(GET_STREAM_PROPLIST(i, type), PA_PROP_MEDIA_ROLE_PRIORITY, (const void**)&priority, &size)) { - pa_log_warn("Failed to pa_proplist_get() for priority, skip it"); + pa_log_warn("failed to pa_proplist_get() for priority, skip it"); + continue; + } + if (!(route_type_str = pa_proplist_gets(GET_STREAM_PROPLIST(i, type), PA_PROP_MEDIA_ROLE_ROUTE_TYPE))) { + pa_log_warn("failed to pa_proplist_get() for route_type, skip it"); + continue; + } else if (IS_ROUTE_TYPE_FOR_EXTERNAL_DEV(route_type_str, route_type)) { + pa_log_warn("stream(%p) has the route type for external device, skip it", i); continue; } if (!(focus_status = pa_proplist_gets(GET_STREAM_PROPLIST(i, type), PA_PROP_MEDIA_FOCUS_STATUS))) { - pa_log_warn("Failed to pa_proplist_gets() for focus status"); + pa_log_warn("failed to pa_proplist_gets() for focus status"); } else { if (!pa_atoi(focus_status, &focus_status_int)) { pa_log_debug("focus status(0x%x)", focus_status_int); } } - pa_log_debug("role(%s)/priority(%p)/focus_state(0x%x)/stream(%p)", _role, priority, focus_status_int, i); + pa_log_debug("role(%s)/priority(%p)/route_type(%d)/focus_status(0x%x)/stream(%p)", _role, priority, route_type, focus_status_int, i); if (cur_max_priority == NULL) { cur_max_priority = priority; cur_max_focus_status_int = focus_status_int; @@ -1738,12 +1782,11 @@ static pa_bool_t update_the_highest_priority_stream(pa_stream_manager *m, proces return TRUE; } -static void fill_device_info_to_hook_data(void *hook_data, notify_command_type_t command, stream_type_t type, void *stream, pa_stream_manager *m) { +static void fill_device_info_to_hook_data(void *hook_data, notify_command_type_t command, stream_type_t type, void *stream, pa_bool_t is_new_data, pa_stream_manager *m) { char *device_none = NULL; const char *p_idx = NULL; uint32_t parent_idx = 0; stream_parent *sp = NULL; - uint32_t idx = 0; pa_stream_manager_hook_data_for_select *select_data = NULL; pa_stream_manager_hook_data_for_route *route_data = NULL; stream_info *si; @@ -1752,7 +1795,7 @@ static void fill_device_info_to_hook_data(void *hook_data, notify_command_type_t pa_assert(hook_data); pa_assert(m); - pa_log_warn("fill_device_info_to_hook_data() for %s", notify_command_type_str[command]); + pa_log_debug("fill_device_info_to_hook_data() for %s", notify_command_type_str[command]); switch (command) { case NOTIFY_COMMAND_SELECT_PROPER_SINK_OR_SOURCE_FOR_INIT: { @@ -1763,24 +1806,28 @@ static void fill_device_info_to_hook_data(void *hook_data, notify_command_type_t list_len = pa_idxset_size(avail_devices); device_none = pa_idxset_get_by_data(avail_devices, "none", NULL); if (list_len == 0 || device_none) { - pa_log_warn("there is no available device, stream_type(%d)", type); + pa_log_warn(" -- there is no available device, stream_type(%d)", type); break; } select_data->idx_avail_devices = avail_devices; - if (si->route_type == STREAM_ROUTE_TYPE_MANUAL) { - p_idx = pa_proplist_gets(GET_STREAM_NEW_PROPLIST(stream, type), PA_PROP_MEDIA_PARENT_ID); + select_data->origins_from_new_data = is_new_data; + if (si->route_type >= STREAM_ROUTE_TYPE_MANUAL) { + if (is_new_data) + p_idx = pa_proplist_gets(GET_STREAM_NEW_PROPLIST(stream, type), PA_PROP_MEDIA_PARENT_ID); + else + p_idx = pa_proplist_gets(GET_STREAM_PROPLIST(stream, type), PA_PROP_MEDIA_PARENT_ID); if (p_idx && !pa_atou(p_idx, &parent_idx)) { /* find parent idx, it's device info. and it's stream idxs */ sp = pa_hashmap_get(m->stream_parents, (const void*)parent_idx); if (sp) select_data->idx_manual_devices = (type==STREAM_SINK_INPUT)?(sp->idx_route_out_devices):(sp->idx_route_in_devices); else - pa_log_warn("Failed to get the stream parent of idx(%u)", idx); - } + pa_log_warn(" -- failed to get the stream parent of idx(%u)", parent_idx); + } else + pa_log_warn(" -- could not get the parent id of this stream, but keep going..."); } break; } - case NOTIFY_COMMAND_CHANGE_ROUTE_START_WITH_NEW_DATA: case NOTIFY_COMMAND_CHANGE_ROUTE_START: case NOTIFY_COMMAND_CHANGE_ROUTE_END: { route_data = (pa_stream_manager_hook_data_for_route*)hook_data; @@ -1790,28 +1837,28 @@ static void fill_device_info_to_hook_data(void *hook_data, notify_command_type_t list_len = pa_idxset_size(avail_devices); device_none = pa_idxset_get_by_data(avail_devices, "none", NULL); - if (command == NOTIFY_COMMAND_CHANGE_ROUTE_START_WITH_NEW_DATA) + if (is_new_data) p_idx = pa_proplist_gets(GET_STREAM_NEW_PROPLIST(stream, type), PA_PROP_MEDIA_PARENT_ID); - else if (command == NOTIFY_COMMAND_CHANGE_ROUTE_START || command == NOTIFY_COMMAND_CHANGE_ROUTE_END) + else p_idx = pa_proplist_gets(GET_STREAM_PROPLIST(stream, type), PA_PROP_MEDIA_PARENT_ID); if (p_idx && !pa_atou(p_idx, &parent_idx)) { sp = pa_hashmap_get(m->stream_parents, (const void*)parent_idx); if (!sp) - pa_log_warn("Failed to get the stream parent of idx(%u)", parent_idx); + pa_log_warn(" -- failed to get the stream parent of idx(%u)", parent_idx); } else - pa_log_warn("Could not get the parent id of this stream, but keep going..."); + pa_log_warn(" -- could not get the parent id of this stream, but keep going..."); if (list_len == 0 || device_none) { - pa_log_warn("there is no available device, stream_type(%d)", type); + pa_log_warn(" -- there is no available device, stream_type(%d)", type); break; } route_data->idx_avail_devices = avail_devices; - if (si->route_type == STREAM_ROUTE_TYPE_MANUAL) { + if (si->route_type >= STREAM_ROUTE_TYPE_MANUAL) { if (sp) { route_data->idx_manual_devices = (type==STREAM_SINK_INPUT)?(sp->idx_route_out_devices):(sp->idx_route_in_devices); route_data->idx_streams = (type==STREAM_SINK_INPUT)?(sp->idx_sink_inputs):(sp->idx_source_outputs); } else - pa_log_warn("Failed to get the stream parent of idx(%u)", parent_idx); + pa_log_warn(" -- failed to get the stream parent of idx(%u)", parent_idx); } break; } @@ -1821,7 +1868,7 @@ static void fill_device_info_to_hook_data(void *hook_data, notify_command_type_t return; } -static void do_notify(pa_stream_manager *m, notify_command_type_t command, stream_type_t type, void *user_data) { +static void do_notify(pa_stream_manager *m, notify_command_type_t command, stream_type_t type, pa_bool_t is_new_data, void *user_data) { pa_stream_manager_hook_data_for_select hook_call_select_data; pa_stream_manager_hook_data_for_route hook_call_route_data; pa_stream_manager_hook_data_for_option hook_call_option_data; @@ -1830,33 +1877,43 @@ static void do_notify(pa_stream_manager *m, notify_command_type_t command, strea void *s = NULL; pa_assert(m); - pa_log_debug("do_notify(%s): type(%d), user_data(%p)", notify_command_type_str[command], type, user_data); + pa_log_debug("do_notify(%s): type(%d), is_new_data(%d), user_data(%p)", notify_command_type_str[command], type, is_new_data, user_data); switch (command) { case NOTIFY_COMMAND_SELECT_PROPER_SINK_OR_SOURCE_FOR_INIT: { pa_assert(user_data); memset(&hook_call_select_data, 0, sizeof(pa_stream_manager_hook_data_for_select)); - s = user_data; + hook_call_select_data.stream = s = user_data; if (s) { hook_call_select_data.stream_type = type; - hook_call_select_data.stream_role = pa_proplist_gets(GET_STREAM_NEW_PROPLIST(s, type), PA_PROP_MEDIA_ROLE); - fill_device_info_to_hook_data(&hook_call_select_data, command, type, s, m); - hook_call_select_data.sample_spec = GET_STREAM_NEW_SAMPLE_SPEC(s, type); - if (type == STREAM_SINK_INPUT) - hook_call_select_data.proper_sink = &(((pa_sink_input_new_data*)s)->sink); - else if (type == STREAM_SOURCE_OUTPUT) - hook_call_select_data.proper_source = &(((pa_source_output_new_data*)s)->source); + hook_call_select_data.origins_from_new_data = is_new_data; + if (is_new_data) { + hook_call_select_data.stream_role = pa_proplist_gets(GET_STREAM_NEW_PROPLIST(s, type), PA_PROP_MEDIA_ROLE); + fill_device_info_to_hook_data(&hook_call_select_data, command, type, s, is_new_data, m); + hook_call_select_data.sample_spec = GET_STREAM_NEW_SAMPLE_SPEC(s, type); + if (type == STREAM_SINK_INPUT) + hook_call_select_data.proper_sink = &(((pa_sink_input_new_data*)s)->sink); + else if (type == STREAM_SOURCE_OUTPUT) + hook_call_select_data.proper_source = &(((pa_source_output_new_data*)s)->source); + } else { + hook_call_select_data.stream_role = pa_proplist_gets(GET_STREAM_PROPLIST(s, type), PA_PROP_MEDIA_ROLE); + fill_device_info_to_hook_data(&hook_call_select_data, command, type, s, is_new_data ,m); + hook_call_select_data.sample_spec = GET_STREAM_SAMPLE_SPEC(s, type); + if (type == STREAM_SINK_INPUT) + hook_call_select_data.proper_sink = &(((pa_sink_input*)s)->sink); + else if (type == STREAM_SOURCE_OUTPUT) + hook_call_select_data.proper_source = &(((pa_source_output*)s)->source); + } pa_hook_fire(pa_communicator_hook(m->comm.comm, PA_COMMUNICATOR_HOOK_SELECT_INIT_SINK_OR_SOURCE), &hook_call_select_data); } break; } - case NOTIFY_COMMAND_CHANGE_ROUTE_START_WITH_NEW_DATA: case NOTIFY_COMMAND_CHANGE_ROUTE_START: { pa_assert(user_data); memset(&hook_call_route_data, 0, sizeof(pa_stream_manager_hook_data_for_route)); - s = user_data; + hook_call_route_data.stream = s = user_data; if (s) { - if (command == NOTIFY_COMMAND_CHANGE_ROUTE_START_WITH_NEW_DATA) { + if (is_new_data) { hook_call_route_data.origins_from_new_data = TRUE; role = pa_proplist_gets(GET_STREAM_NEW_PROPLIST(s, type), PA_PROP_MEDIA_ROLE); hook_call_route_data.sample_spec = GET_STREAM_NEW_SAMPLE_SPEC(s, type); @@ -1868,21 +1925,15 @@ static void do_notify(pa_stream_manager *m, notify_command_type_t command, strea } else { role = pa_proplist_gets(GET_STREAM_PROPLIST(s, type), PA_PROP_MEDIA_ROLE); hook_call_route_data.sample_spec = GET_STREAM_SAMPLE_SPEC(s, type); - if (type == STREAM_SINK_INPUT) { - if (((pa_sink_input*)s)->sink) - hook_call_route_data.idx_streams = ((pa_sink_input*)s)->sink->inputs; - } else if (type == STREAM_SOURCE_OUTPUT) { - if (((pa_source_output*)s)->source) - hook_call_route_data.idx_streams = ((pa_source_output*)s)->source->outputs; - } + hook_call_route_data.idx_streams = (type==STREAM_SINK_INPUT)?((pa_sink_input*)s)->sink->inputs:((pa_source_output*)s)->source->outputs; } hook_call_route_data.stream_type = type; hook_call_route_data.stream_role = role; - fill_device_info_to_hook_data(&hook_call_route_data, command, type, s, m); - if (hook_call_route_data.route_type == STREAM_ROUTE_TYPE_MANUAL) { + fill_device_info_to_hook_data(&hook_call_route_data, command, type, s, is_new_data, m); + if (hook_call_route_data.route_type >= STREAM_ROUTE_TYPE_MANUAL) { if (hook_call_route_data.idx_manual_devices && !pa_idxset_size(hook_call_route_data.idx_manual_devices)) { - pa_log_info("no manual device for this type(%d), need to unset route", type); - hook_call_route_data.stream_role = "reset"; + pa_log_warn("no manual device for this type(%d), skip it..", type); + break; } } pa_hook_fire(pa_communicator_hook(m->comm.comm, PA_COMMUNICATOR_HOOK_CHANGE_ROUTE), &hook_call_route_data); @@ -1898,7 +1949,7 @@ static void do_notify(pa_stream_manager *m, notify_command_type_t command, strea hook_call_route_data.stream_role = role; hook_call_route_data.sample_spec = GET_STREAM_SAMPLE_SPEC(s, type); hook_call_route_data.idx_streams = (type==STREAM_SINK_INPUT)?((pa_sink_input*)s)->sink->inputs:((pa_source_output*)s)->source->outputs; - fill_device_info_to_hook_data(&hook_call_route_data, command, type, s, m); + fill_device_info_to_hook_data(&hook_call_route_data, command, type, s, is_new_data, m); } else { pa_log_info("no stream for this type(%d), need to unset route", type); hook_call_route_data.stream_type = type; @@ -1941,6 +1992,8 @@ static void do_notify(pa_stream_manager *m, notify_command_type_t command, strea static process_stream_result_t process_stream(stream_type_t type, void *stream, process_command_type_t command, pa_stream_manager *m) { process_stream_result_t result = PROCESS_STREAM_RESULT_OK; const char *role = NULL; + const char *route_type_str = NULL; + stream_route_type_t route_type; pa_bool_t ret = TRUE; pa_bool_t need_update = FALSE; int32_t volume_ret = 0; @@ -1953,7 +2006,7 @@ static process_stream_result_t process_stream(stream_type_t type, void *stream, const char *rate_str = NULL; const char *ch_str = NULL; - pa_log_info("START process_stream(%s): stream_type(%d), stream(%p), m(%p)", process_command_type_str[command], type, stream, m); + pa_log_info(">>> process_stream(%s): stream_type(%d), stream(%p), m(%p)", process_command_type_str[command], type, stream, m); pa_assert(stream); pa_assert(m); @@ -2016,23 +2069,34 @@ static process_stream_result_t process_stream(stream_type_t type, void *stream, result = PROCESS_STREAM_RESULT_STOP; goto FAILURE; } - /* update the routing type of this stream */ - ret = update_routing_type_of_stream(m, stream, type, role); + /* update the route type of this stream */ + ret = update_route_type_of_stream(m, stream, type, role); if (ret == FALSE) { pa_log_error("could not update the route type of '%s' role.", role); result = PROCESS_STREAM_RESULT_STOP; goto FAILURE; } + /* skip route types */ + if ((route_type_str = pa_proplist_gets(GET_STREAM_NEW_PROPLIST(stream, type), PA_PROP_MEDIA_ROLE_ROUTE_TYPE))) { + if (check_route_type_to_skip(command, route_type_str)) { + result = PROCESS_STREAM_RESULT_SKIP; + goto FAILURE; + } + } + /* notify to select sink or source */ - do_notify(m, NOTIFY_COMMAND_SELECT_PROPER_SINK_OR_SOURCE_FOR_INIT, type, stream); + do_notify(m, NOTIFY_COMMAND_SELECT_PROPER_SINK_OR_SOURCE_FOR_INIT, type, TRUE, stream); } else if (command == PROCESS_COMMAND_CHANGE_ROUTE_BY_STREAM_STARTED_WITH_NEW_DATA || command == PROCESS_COMMAND_CHANGE_ROUTE_BY_STREAM_STARTED) { - if (command == PROCESS_COMMAND_CHANGE_ROUTE_BY_STREAM_STARTED_WITH_NEW_DATA) + if (command == PROCESS_COMMAND_CHANGE_ROUTE_BY_STREAM_STARTED_WITH_NEW_DATA) { role = pa_proplist_gets(GET_STREAM_NEW_PROPLIST(stream, type), PA_PROP_MEDIA_ROLE); - else + route_type_str = pa_proplist_gets(GET_STREAM_NEW_PROPLIST(stream, type), PA_PROP_MEDIA_ROLE_ROUTE_TYPE); + } else { role = pa_proplist_gets(GET_STREAM_PROPLIST(stream, type), PA_PROP_MEDIA_ROLE); + route_type_str = pa_proplist_gets(GET_STREAM_PROPLIST(stream, type), PA_PROP_MEDIA_ROLE_ROUTE_TYPE); + } /* skip roles */ if (check_role_to_skip(m, role)) { @@ -2040,6 +2104,12 @@ static process_stream_result_t process_stream(stream_type_t type, void *stream, goto FAILURE; } + /* skip route types */ + if (check_route_type_to_skip(command, route_type_str)) { + result = PROCESS_STREAM_RESULT_SKIP; + goto FAILURE; + } + if (command == PROCESS_COMMAND_CHANGE_ROUTE_BY_STREAM_STARTED ) { /* update the priority of this stream */ ret = update_priority_of_stream(m, command, type, stream, role); @@ -2067,13 +2137,13 @@ static process_stream_result_t process_stream(stream_type_t type, void *stream, /* if needed, notify to update */ if (need_update) { if (command == PROCESS_COMMAND_CHANGE_ROUTE_BY_STREAM_STARTED_WITH_NEW_DATA) { - do_notify(m, NOTIFY_COMMAND_CHANGE_ROUTE_START_WITH_NEW_DATA, type, stream); + do_notify(m, NOTIFY_COMMAND_CHANGE_ROUTE_START, type, TRUE, stream); if (type==STREAM_SINK_INPUT) m->cur_highest_priority.need_to_update_si = TRUE; else m->cur_highest_priority.need_to_update_so = TRUE; } else { - do_notify(m, NOTIFY_COMMAND_CHANGE_ROUTE_START, type, stream); + do_notify(m, NOTIFY_COMMAND_CHANGE_ROUTE_START, type, FALSE, stream); if (type==STREAM_SINK_INPUT) m->cur_highest_priority.sink_input = stream; else @@ -2081,7 +2151,7 @@ static process_stream_result_t process_stream(stream_type_t type, void *stream, } } if (command == PROCESS_COMMAND_CHANGE_ROUTE_BY_STREAM_STARTED) - do_notify(m, NOTIFY_COMMAND_INFORM_STREAM_CONNECTED, type, stream); + do_notify(m, NOTIFY_COMMAND_INFORM_STREAM_CONNECTED, type, FALSE, stream); } else if (command == PROCESS_COMMAND_CHANGE_ROUTE_BY_STREAM_ENDED) { role = pa_proplist_gets(GET_STREAM_PROPLIST(stream, type), PA_PROP_MEDIA_ROLE); @@ -2092,6 +2162,14 @@ static process_stream_result_t process_stream(stream_type_t type, void *stream, goto FAILURE; } + /* skip route types */ + if ((route_type_str = pa_proplist_gets(GET_STREAM_PROPLIST(stream, type), PA_PROP_MEDIA_ROLE_ROUTE_TYPE))) { + if (check_route_type_to_skip(command, route_type_str)) { + result = PROCESS_STREAM_RESULT_SKIP; + goto FAILURE; + } + } + /* check if it has already been processed (unlink or state_changed_cb) */ if (pa_proplist_get(GET_STREAM_PROPLIST(stream, type), PA_PROP_MEDIA_ROLE_PRIORITY, (const void**)&prior_priority, &size)) { pa_log_debug("it has already been processed for PROCESS_COMMAND_CHANGE_ROUTE_BY_STREAM_ENDED, skip it.."); @@ -2107,12 +2185,12 @@ static process_stream_result_t process_stream(stream_type_t type, void *stream, goto FAILURE; } - do_notify(m, NOTIFY_COMMAND_INFORM_STREAM_DISCONNECTED, type, stream); + do_notify(m, NOTIFY_COMMAND_INFORM_STREAM_DISCONNECTED, type, FALSE, stream); /* need to skip if this stream does not belong to internal device */ /* if needed, notify to update */ if (need_update) - do_notify(m, NOTIFY_COMMAND_CHANGE_ROUTE_END, type, NULL); + do_notify(m, NOTIFY_COMMAND_CHANGE_ROUTE_END, type, FALSE, NULL); } else { pa_log_error("role is null, skip it"); @@ -2127,6 +2205,14 @@ static process_stream_result_t process_stream(stream_type_t type, void *stream, goto FAILURE; } + /* skip route types */ + if ((route_type_str = pa_proplist_gets(GET_STREAM_PROPLIST(stream, type), PA_PROP_MEDIA_ROLE_ROUTE_TYPE))) { + if (check_route_type_to_skip(command, route_type_str)) { + result = PROCESS_STREAM_RESULT_SKIP; + goto FAILURE; + } + } + ret = update_the_highest_priority_stream(m, command, stream, type, role, &need_update); if (ret == FALSE) { pa_log_error("could not update the highest priority stream"); @@ -2137,7 +2223,7 @@ static process_stream_result_t process_stream(stream_type_t type, void *stream, /* need to skip if this stream does not belong to internal device */ /* if needed, notify to update */ if (need_update) - do_notify(m, NOTIFY_COMMAND_CHANGE_ROUTE_END, type, NULL); + do_notify(m, NOTIFY_COMMAND_CHANGE_ROUTE_END, type, FALSE, NULL); } else { pa_log_error("role is null, skip it"); @@ -2161,28 +2247,47 @@ static process_stream_result_t process_stream(stream_type_t type, void *stream, } } else if (command == PROCESS_COMMAND_ADD_PARENT_ID || command == PROCESS_COMMAND_REMOVE_PARENT_ID) { - if (command == PROCESS_COMMAND_ADD_PARENT_ID) { - if (type == STREAM_SINK_INPUT && m->cur_highest_priority.need_to_update_si) { - m->cur_highest_priority.sink_input = stream; - m->cur_highest_priority.need_to_update_si = FALSE; + role = pa_proplist_gets(GET_STREAM_PROPLIST(stream, type), PA_PROP_MEDIA_ROLE); + if (role) { + /* skip roles */ + if (check_role_to_skip(m, role)) { + result = PROCESS_STREAM_RESULT_SKIP; + goto FAILURE; } - if (type == STREAM_SOURCE_OUTPUT && m->cur_highest_priority.need_to_update_so) { - m->cur_highest_priority.source_output = stream; - m->cur_highest_priority.need_to_update_so = FALSE; + + /* skip route types */ + if ((route_type_str = pa_proplist_gets(GET_STREAM_PROPLIST(stream, type), PA_PROP_MEDIA_ROLE_ROUTE_TYPE))) { + if (check_route_type_to_skip(command, route_type_str)) { + result = PROCESS_STREAM_RESULT_SKIP; + goto FAILURE; + } + } + + if (!IS_ROUTE_TYPE_FOR_EXTERNAL_DEV(route_type_str, route_type)) { + if (command == PROCESS_COMMAND_ADD_PARENT_ID) { + if (type == STREAM_SINK_INPUT && m->cur_highest_priority.need_to_update_si) { + m->cur_highest_priority.sink_input = stream; + m->cur_highest_priority.need_to_update_si = FALSE; + } + if (type == STREAM_SOURCE_OUTPUT && m->cur_highest_priority.need_to_update_so) { + m->cur_highest_priority.source_output = stream; + m->cur_highest_priority.need_to_update_so = FALSE; + } + do_notify(m, NOTIFY_COMMAND_INFORM_STREAM_CONNECTED, type, FALSE, stream); + } + } + + /* update parent stream info. */ + ret = update_stream_parent_info(m, command, type, stream); + if (ret == FALSE) { + pa_log_warn("could not update the parent information of this stream"); + //return PROCESS_STREAM_RESULT_STOP; } - if (command == PROCESS_COMMAND_ADD_PARENT_ID) - do_notify(m, NOTIFY_COMMAND_INFORM_STREAM_CONNECTED, type, stream); - } - /* update parent stream info. */ - ret = update_stream_parent_info(m, command, type, stream); - if (ret == FALSE) { - pa_log_warn("could not update the parent information of this stream"); - //return PROCESS_STREAM_RESULT_STOP; } } FAILURE: - pa_log_info("END process_stream(%s): result(%d)", process_command_type_str[command], result); + pa_log_info("<<< process_stream(%s): result(%d), stream(%p)", process_command_type_str[command], result, stream); return result; } @@ -2340,8 +2445,8 @@ static pa_hook_result_t source_output_unlink_cb(pa_core *core, pa_source_output pa_log_info("start source_output_unlink_cb, o(%p, index:%u)", o, o->index); - process_stream(STREAM_SOURCE_OUTPUT, o, PROCESS_COMMAND_REMOVE_PARENT_ID, m); process_stream(STREAM_SOURCE_OUTPUT, o, PROCESS_COMMAND_CHANGE_ROUTE_BY_STREAM_ENDED, m); + process_stream(STREAM_SOURCE_OUTPUT, o, PROCESS_COMMAND_REMOVE_PARENT_ID, m); return PA_HOOK_OK; } @@ -2371,36 +2476,83 @@ static pa_hook_result_t source_output_state_changed_cb(pa_core *core, pa_source_ return PA_HOOK_OK; } -/* Reorganize routing when a device has been connected or disconnected */ -static pa_hook_result_t device_connection_changed_hook_cb(pa_core *c, pa_device_manager_hook_data_for_conn_changed *conn, pa_stream_manager *m) { - const char *route_type_str = NULL; +/* Reorganize routing when a device using internal audio codec has been connected or disconnected */ +static pa_hook_result_t device_connection_changed_hook_cb(pa_core *c, pa_device_manager_hook_data_for_conn_changed *data, pa_stream_manager *m) { + const char *active_dev = NULL; + const char *device_type = NULL; stream_route_type_t route_type; dm_device_direction_t device_direction = DM_DEVICE_DIRECTION_OUT; + pa_bool_t use_internal_codec = FALSE; + + pa_assert(c); + pa_assert(data); + pa_assert(m); + + device_direction = pa_device_manager_get_device_direction(data->device); + device_type = pa_device_manager_get_device_type(data->device); + pa_device_manager_use_internal_codec(data->device, device_direction, DEVICE_ROLE_NORMAL, &use_internal_codec); - device_direction = pa_device_manager_get_device_direction(conn->device); - pa_log_info("device_connection_changed_hook_cb is called. conn(%p), is_connected(%d), device(%p), direction(0x%x)", - conn, conn->is_connected, conn->device, device_direction); + pa_log_info("[SM] device_connection_changed_hook_cb is called. data(%p), is_connected(%d), device(%p, %s), direction(0x%x), use_internal_codec(%d)", + data, data->is_connected, data->device, device_type, device_direction, use_internal_codec); /* If the route type of the stream is not manual, notify again */ if (m->cur_highest_priority.source_output && (device_direction & DM_DEVICE_DIRECTION_IN)) { - route_type_str = pa_proplist_gets(GET_STREAM_PROPLIST(m->cur_highest_priority.source_output, STREAM_SOURCE_OUTPUT), PA_PROP_MEDIA_ROLE_ROUTE_TYPE); - if(!pa_atoi(route_type_str, (int32_t*)&route_type)) - if (route_type != STREAM_ROUTE_TYPE_MANUAL) - do_notify(m, NOTIFY_COMMAND_CHANGE_ROUTE_START, STREAM_SOURCE_OUTPUT, m->cur_highest_priority.source_output); - } if (m->cur_highest_priority.sink_input && (device_direction & DM_DEVICE_DIRECTION_OUT)) { - route_type_str = pa_proplist_gets(GET_STREAM_PROPLIST(m->cur_highest_priority.sink_input, STREAM_SINK_INPUT), PA_PROP_MEDIA_ROLE_ROUTE_TYPE); - if(!pa_atoi(route_type_str, (int32_t*)&route_type)) - if (route_type != STREAM_ROUTE_TYPE_MANUAL) - do_notify(m, NOTIFY_COMMAND_CHANGE_ROUTE_START, STREAM_SINK_INPUT, m->cur_highest_priority.sink_input); + if (!pa_stream_manager_get_route_type(m->cur_highest_priority.source_output, FALSE, STREAM_SOURCE_OUTPUT, &route_type)) { + if (route_type < STREAM_ROUTE_TYPE_MANUAL) { + if (use_internal_codec) { + if (route_type == STREAM_ROUTE_TYPE_AUTO && !data->is_connected) { + /* remove activated device info. if it has the AUTO route type */ + active_dev = pa_proplist_gets(GET_STREAM_PROPLIST(m->cur_highest_priority.source_output, STREAM_SOURCE_OUTPUT), PA_PROP_MEDIA_ROUTE_AUTO_ACTIVE_DEV); + if (pa_streq(active_dev, device_type)) + pa_proplist_sets(GET_STREAM_PROPLIST(m->cur_highest_priority.source_output, STREAM_SOURCE_OUTPUT), PA_PROP_MEDIA_ROUTE_AUTO_ACTIVE_DEV, "removed"); + } + do_notify(m, NOTIFY_COMMAND_CHANGE_ROUTE_START, STREAM_SOURCE_OUTPUT, FALSE, m->cur_highest_priority.source_output); + } + } + } + } + if (m->cur_highest_priority.sink_input && (device_direction & DM_DEVICE_DIRECTION_OUT)) { + if (!pa_stream_manager_get_route_type(m->cur_highest_priority.sink_input, FALSE, STREAM_SINK_INPUT, &route_type)) { + if (route_type < STREAM_ROUTE_TYPE_MANUAL) { + if (use_internal_codec || (!use_internal_codec && (route_type == STREAM_ROUTE_TYPE_AUTO_ALL))) { + if (route_type == STREAM_ROUTE_TYPE_AUTO && !data->is_connected) { + /* remove activated device info. if it has the AUTO route type */ + active_dev = pa_proplist_gets(GET_STREAM_PROPLIST(m->cur_highest_priority.sink_input, STREAM_SINK_INPUT), PA_PROP_MEDIA_ROUTE_AUTO_ACTIVE_DEV); + if (pa_streq(active_dev, device_type)) + pa_proplist_sets(GET_STREAM_PROPLIST(m->cur_highest_priority.sink_input, STREAM_SINK_INPUT), PA_PROP_MEDIA_ROUTE_AUTO_ACTIVE_DEV, "removed"); + } + do_notify(m, NOTIFY_COMMAND_CHANGE_ROUTE_START, STREAM_SINK_INPUT, FALSE, m->cur_highest_priority.sink_input); + } + } + } } return PA_HOOK_OK; } /* Reorganize routing when device information has been changed */ -static pa_hook_result_t device_information_changed_hook_cb(pa_core *c, pa_device_manager_hook_data_for_info_changed *info, pa_stream_manager *m) { - pa_log_info("device_information_changed_hook_cb is called. info(%p), changed_info(%d), device(%p)", - info, info->changed_info, info->device); +static pa_hook_result_t device_information_changed_hook_cb(pa_core *c, pa_device_manager_hook_data_for_info_changed *data, pa_stream_manager *m) { +#if 0 + pa_assert(c); + pa_assert(data); + pa_assert(m); + + pa_log_info("[SM] device_information_changed_hook_cb is called. data(%p), changed_info(%d), device(%p)", + data, data->changed_info, data->device); +#endif + + return PA_HOOK_OK; +} + +/* Re-trigger for routing */ +static pa_hook_result_t need_update_route_hook_cb(pa_core *c, pa_stream_manager_hook_data_for_update_route *data, pa_stream_manager *m) { + pa_assert(c); + pa_assert(data); + pa_assert(m); + + pa_log_info("[SM] need_update_route_hook_cb is called. data(%p), stream_type(%d)", data, data->stream_type); + + do_notify(m, NOTIFY_COMMAND_CHANGE_ROUTE_START, data->stream_type, FALSE, (data->stream_type==STREAM_SINK_INPUT)?(void*)m->cur_highest_priority.sink_input:(void*)m->cur_highest_priority.source_output); return PA_HOOK_OK; } @@ -2496,7 +2648,7 @@ static int init_ipc (pa_stream_manager *m) { return 0; fail: - pa_log_error("Failed to initialize stream manager ipc"); + pa_log_error("failed to initialize stream manager ipc"); return -1; } @@ -2514,7 +2666,7 @@ static void deinit_ipc (pa_stream_manager *m) { #else if (m->dbus_conn) { if(!dbus_connection_unregister_object_path(pa_dbus_connection_get(m->dbus_conn), STREAM_MANAGER_OBJECT_PATH)) - pa_log_error("Failed to unregister object path"); + pa_log_error("failed to unregister object path"); m->dbus_conn = NULL; } #endif @@ -2522,6 +2674,139 @@ static void deinit_ipc (pa_stream_manager *m) { return; } +int32_t pa_stream_manager_get_route_type(void *stream, pa_bool_t origins_from_new_data, stream_type_t stream_type, stream_route_type_t *stream_route_type) { + const char *route_type_str = NULL; + + pa_assert(stream); + pa_assert(stream_route_type); + + if (origins_from_new_data) + route_type_str = pa_proplist_gets(GET_STREAM_NEW_PROPLIST(stream, stream_type), PA_PROP_MEDIA_ROLE_ROUTE_TYPE); + else + route_type_str = pa_proplist_gets(GET_STREAM_PROPLIST(stream, stream_type), PA_PROP_MEDIA_ROLE_ROUTE_TYPE); + + if (route_type_str) { + if (!pa_atoi(route_type_str, (int32_t*)stream_route_type)) + pa_log_debug("stream(%p), origins_from_new_data(%d), stream_type(%d): route_type(%d)", stream, origins_from_new_data, stream_type, *stream_route_type); + else { + pa_log_error("could not convert route_type_str(%s) to int", route_type_str); + return -1; + } + } else { + pa_log_warn("could not get route type from the stream(%p)", stream); + return -1; + } + + return 0; +} + +void pa_stream_manager_is_current_highest_priority(void *stream, stream_type_t stream_type, pa_bool_t *highest_priority, pa_stream_manager *m) { + pa_assert(stream); + pa_assert(highest_priority); + pa_assert(m); + + if ((stream_type == STREAM_SINK_INPUT) && (stream == m->cur_highest_priority.sink_input)) { + *highest_priority = TRUE; + pa_log_debug("stream(%p) is same as cur_highest_priority.sink_input(%p)", stream, m->cur_highest_priority.sink_input); + } else if ((stream_type == STREAM_SOURCE_OUTPUT) && (stream == m->cur_highest_priority.source_output)) { + *highest_priority = TRUE; + pa_log_debug("stream(%p) is same as cur_highest_priority.source_output(%p)", stream, m->cur_highest_priority.source_output); + } else + *highest_priority = FALSE; + + return; +} + +void pa_stream_manager_is_available_device_for_auto_route(const char *cur_device_type, const char *new_device_type, const char *role, stream_type_t stream_type, pa_bool_t *available, pa_stream_manager *m) { + stream_info *si = NULL; + pa_idxset *devices = NULL; + uint32_t idx = 0; + char *device_type = NULL; + + pa_assert(m); + pa_assert(role); + pa_assert(cur_device_type); + pa_assert(new_device_type); + pa_assert(available); + + if (m->stream_infos) { + si = pa_hashmap_get(m->stream_infos, role); + if (si) { + *available = FALSE; + devices = (stream_type==STREAM_SINK_INPUT)?si->idx_avail_out_devices:si->idx_avail_in_devices; + if (devices) { + PA_IDXSET_FOREACH(device_type, devices, idx) { + if (cur_device_type && pa_streq(device_type, cur_device_type)) { + pa_log_debug("cur_device[%s]'s priority is more higher than new_device[%s]", cur_device_type, new_device_type); + break; + } + if (pa_streq(device_type, new_device_type)) { + *available = TRUE; + break; + } + } + } else { + pa_log_error("could not found a device list for this role[%s], stream type[%d]", role, stream_type); + *available = FALSE; + } + } else { + pa_log_warn("not support this role[%s]", role); + *available = FALSE; + } + } else { + pa_log_error("stream_infos is null"); + *available = FALSE; + } + pa_log_debug("is new_device[%s] available for role[%s]/stream_type[%d]:%d", new_device_type, role, stream_type, *available); + + return; +} + +void pa_stream_manager_find_next_priority_device_for_auto_route(const char *cur_device_type, const char *role, stream_type_t stream_type, char **next_device_type, pa_stream_manager *m) { + stream_info *si = NULL; + pa_idxset *devices = NULL; + uint32_t idx = 0; + char *device_type = NULL; + pa_bool_t ret_next = FALSE; + + pa_assert(m); + pa_assert(role); + pa_assert(cur_device_type); + pa_assert(next_device_type); + + if (m->stream_infos) { + si = pa_hashmap_get(m->stream_infos, role); + if (si) { + devices = (stream_type==STREAM_SINK_INPUT)?si->idx_avail_out_devices:si->idx_avail_in_devices; + if (devices) { + PA_IDXSET_FOREACH(device_type, devices, idx) { + if (ret_next) { + *next_device_type = device_type; + pa_log_debug("found next device[%s]", *next_device_type); + break; + } + if (pa_streq(device_type, cur_device_type)) { + ret_next = TRUE; + continue; + } + } + } else { + pa_log_error("could not found a device list for this role[%s], stream type[%d]", role, stream_type); + *next_device_type = NULL; + } + } else { + pa_log_warn("not support this role[%s]", role); + *next_device_type = NULL; + } + } else { + pa_log_error("stream_infos is null"); + *next_device_type = NULL; + } + pa_log_debug("next_device_type is [%s] for role[%s]/stream_type[%d]", *next_device_type, role, stream_type); + + return; +} + pa_stream_manager* pa_stream_manager_init(pa_core *c) { pa_stream_manager *m; @@ -2569,11 +2854,13 @@ pa_stream_manager* pa_stream_manager_init(pa_core *c) { PA_HOOK_EARLY + 10, (pa_hook_cb_t) device_connection_changed_hook_cb, m); m->comm.comm_hook_device_information_changed_slot = pa_hook_connect(pa_communicator_hook(m->comm.comm,PA_COMMUNICATOR_HOOK_DEVICE_INFORMATION_CHANGED), PA_HOOK_EARLY, (pa_hook_cb_t) device_information_changed_hook_cb, m); + m->comm.comm_hook_need_update_route_slot = pa_hook_connect(pa_communicator_hook(m->comm.comm,PA_COMMUNICATOR_HOOK_NEED_UPDATE_ROUTE), + PA_HOOK_EARLY, (pa_hook_cb_t) need_update_route_hook_cb, m); return m; fail: - pa_log_error("Failed to initialize stream-manager"); + pa_log_error("failed to initialize stream-manager"); deinit_volumes(m); deinit_stream_map(m); deinit_ipc(m); @@ -2591,6 +2878,8 @@ void pa_stream_manager_done(pa_stream_manager *m) { pa_hook_slot_free(m->comm.comm_hook_device_connection_changed_slot); if (m->comm.comm_hook_device_information_changed_slot) pa_hook_slot_free(m->comm.comm_hook_device_information_changed_slot); + if (m->comm.comm_hook_need_update_route_slot) + pa_hook_slot_free(m->comm.comm_hook_need_update_route_slot); pa_communicator_unref(m->comm.comm); } diff --git a/src/stream-manager.h b/src/stream-manager.h index 02dc5db..e36850d 100644 --- a/src/stream-manager.h +++ b/src/stream-manager.h @@ -2,6 +2,15 @@ #define foostreammanagerfoo #include +#define IS_ROUTE_TYPE_FOR_EXTERNAL_DEV(route_type_str, route_type) \ + (route_type_str && !pa_atoi(route_type_str, (int32_t*)&route_type) && (route_type == STREAM_ROUTE_TYPE_MANUAL_EXT)) + +#define GET_STREAM_NEW_PROPLIST(stream, type) \ + (type == STREAM_SINK_INPUT? ((pa_sink_input_new_data*)stream)->proplist : ((pa_source_output_new_data*)stream)->proplist) + +#define GET_STREAM_PROPLIST(stream, type) \ + (type == STREAM_SINK_INPUT? ((pa_sink_input*)stream)->proplist : ((pa_source_output*)stream)->proplist) + typedef struct _stream_manager pa_stream_manager; typedef enum _stream_type { @@ -10,12 +19,14 @@ typedef enum _stream_type { } stream_type_t; typedef enum stream_route_type { - STREAM_ROUTE_TYPE_AUTO, /* the policy of decision device(s) is automatic and it's routing path is particular to one device */ - STREAM_ROUTE_TYPE_AUTO_ALL, /* the policy of decision device(s) is automatic and it's routing path can be several devices */ - STREAM_ROUTE_TYPE_MANUAL, /* the policy of decision device(s) is manual */ + STREAM_ROUTE_TYPE_AUTO, /* the policy of decision device(s) is automatic and it's routing path is particular to one device */ + STREAM_ROUTE_TYPE_AUTO_ALL, /* the policy of decision device(s) is automatic and it's routing path can be several devices */ + STREAM_ROUTE_TYPE_MANUAL, /* the policy of decision device(s) is manual */ + STREAM_ROUTE_TYPE_MANUAL_EXT, /* the policy of decision device(s) is manual and it's routing path is for only external devices */ } stream_route_type_t; typedef struct _hook_call_data_for_select { + void *stream; const char *stream_role; stream_type_t stream_type; stream_route_type_t route_type; @@ -24,9 +35,11 @@ typedef struct _hook_call_data_for_select { pa_sample_spec sample_spec; pa_idxset *idx_avail_devices; pa_idxset *idx_manual_devices; + pa_bool_t origins_from_new_data; } pa_stream_manager_hook_data_for_select; typedef struct _hook_call_data_for_route { + void *stream; const char *stream_role; stream_type_t stream_type; stream_route_type_t route_type; @@ -45,6 +58,15 @@ typedef struct _hook_call_data_for_option { int32_t value; } pa_stream_manager_hook_data_for_option; +typedef struct _hook_call_data_for_update_route { + stream_type_t stream_type; +} pa_stream_manager_hook_data_for_update_route; + +int32_t pa_stream_manager_get_route_type(void *stream, pa_bool_t origins_from_new_data, stream_type_t stream_type, stream_route_type_t *stream_route_type); +void pa_stream_manager_is_current_highest_priority(void *stream, stream_type_t stream_type, pa_bool_t *highest_priority, pa_stream_manager *m); +void pa_stream_manager_is_available_device_for_auto_route(const char *cur_device_type, const char *new_device_type, const char *role, stream_type_t stream_type, pa_bool_t *available, pa_stream_manager *m); +void pa_stream_manager_find_next_priority_device_for_auto_route(const char *cur_device_type, const char *role, stream_type_t stream_type, char **next_device_type, pa_stream_manager *m); + pa_stream_manager* pa_stream_manager_init(pa_core *c); void pa_stream_manager_done(pa_stream_manager* m); -- 2.7.4 From b81bd1c48afc4c2f97d403e4b6c4a2bb094adb8d Mon Sep 17 00:00:00 2001 From: Mok Jeongho Date: Wed, 7 Oct 2015 20:33:43 +0900 Subject: [PATCH 7/8] device-manager : modify sco code Add internal interface to get bt sco status Set bt-sco as active profile when bt_sco_open method call returned [Version] 5.0-45 [Profile] Common [Issue Type] Enhance feature Change-Id: I127d63485f2e0f481a6ed9853f9d739d6cbb96b1 --- packaging/pulseaudio-modules-tizen.spec | 2 +- src/device-manager.c | 45 +++++++++++++++++++++++++-------- src/device-manager.h | 7 +++++ 3 files changed, 43 insertions(+), 11 deletions(-) diff --git a/packaging/pulseaudio-modules-tizen.spec b/packaging/pulseaudio-modules-tizen.spec index 96be79c..236b670 100644 --- a/packaging/pulseaudio-modules-tizen.spec +++ b/packaging/pulseaudio-modules-tizen.spec @@ -4,7 +4,7 @@ Name: pulseaudio-modules-tizen Summary: Improved Linux sound server Version: 5.0 -Release: 44 +Release: 45 Group: Multimedia/Audio License: LGPL-2.1+ URL: http://pulseaudio.org diff --git a/src/device-manager.c b/src/device-manager.c index 3bf5e4d..51c90ee 100644 --- a/src/device-manager.c +++ b/src/device-manager.c @@ -363,6 +363,7 @@ struct pa_device_manager { */ pa_idxset *device_status; pa_dbus_connection *dbus_conn; + dm_device_sco_status_t bt_sco_status; }; /***************** structures for static information get from json *********/ @@ -3191,8 +3192,10 @@ static int handle_device_status_changed(pa_device_manager *dm, const char *devic } } else if (pa_streq(device_type, DEVICE_TYPE_BT) && device_profile && pa_streq(device_profile, DEVICE_PROFILE_BT_SCO)) { if (detected_status == BT_SCO_DISCONNECTED) { + dm->bt_sco_status = DM_DEVICE_BT_SCO_STATUS_DISCONNECTED; handle_device_disconnected(dm, device_type, device_profile, identifier); } else if (detected_status == BT_SCO_CONNECTED) { + dm->bt_sco_status = DM_DEVICE_BT_SCO_STATUS_CONNECTED; handle_device_connected(dm, device_type, device_profile, name, identifier, DEVICE_DETECTED_BT_SCO); } else { pa_log_warn("Got invalid bt-sco detected value"); @@ -3376,16 +3379,7 @@ static DBusHandlerResult dbus_filter_device_detect_handler(DBusConnection *c, DB dbus_bool_t value; char *name; dbus_message_iter_get_basic(&variant_iter, &value); - if (pa_streq(property_name, "Playing")) { - dm_device *device_item; - pa_log_debug("SCO Playing : %d", value); - if ((device_item = _device_manager_get_device(dm->device_list, DEVICE_TYPE_BT))) { - if (value) - _device_item_set_active_profile(device_item, DEVICE_PROFILE_BT_SCO); - else - _device_item_set_active_profile_auto(device_item); - } - } else if (pa_streq(property_name, "Connected")) { + if (pa_streq(property_name, "Connected")) { pa_log_debug("HFP Connection : %d", value); if (value) { method_call_bt_get_name(c, dbus_message_get_path(s), &name); @@ -4147,6 +4141,7 @@ void pa_device_manager_use_internal_codec(dm_device *device_item, dm_device_dire int pa_device_manager_bt_sco_open(pa_device_manager *dm) { struct device_status_info *status_info; + dm_device *bt_device; pa_assert(dm); pa_assert(dm->dbus_conn); @@ -4160,6 +4155,11 @@ int pa_device_manager_bt_sco_open(pa_device_manager *dm) { return -1; } + if ((bt_device = _device_manager_get_device(dm->device_list, DEVICE_TYPE_BT)) == NULL) { + pa_log_error("no bt device"); + return -1; + } + pa_log_debug("bt sco open start"); if (method_call_bt_sco(pa_dbus_connection_get(dm->dbus_conn), TRUE) < 0) { pa_log_error("Failed to bt sco on"); @@ -4168,11 +4168,26 @@ int pa_device_manager_bt_sco_open(pa_device_manager *dm) { pa_log_debug("bt sco open end"); + if (_device_item_set_active_profile(bt_device, DEVICE_PROFILE_BT_SCO) == NULL) { + pa_log_error("set bt sco as active profile failed"); + } + dm->bt_sco_status = DM_DEVICE_BT_SCO_STATUS_OPENED; + return 0; } +void pa_device_manager_bt_sco_get_status(pa_device_manager *dm, dm_device_sco_status_t *status) { + pa_assert(dm); + pa_assert(status); + + *status = dm->bt_sco_status; + + return; +} + int pa_device_manager_bt_sco_close(pa_device_manager *dm) { struct device_status_info *status_info; + dm_device *bt_device; pa_assert(dm); pa_assert(dm->dbus_conn); @@ -4186,12 +4201,21 @@ int pa_device_manager_bt_sco_close(pa_device_manager *dm) { return -1; } + if ((bt_device = _device_manager_get_device(dm->device_list, DEVICE_TYPE_BT)) == NULL) { + pa_log_error("no bt device"); + return -1; + } + pa_log_debug("bt sco close start"); if (method_call_bt_sco(pa_dbus_connection_get(dm->dbus_conn), FALSE) < 0) { pa_log_error("Failed to bt sco close"); return -1; } pa_log_debug("bt sco close end"); + if (_device_item_set_active_profile_auto(bt_device) == NULL) { + pa_log_error("set active profile auto failed"); + } + dm->bt_sco_status = DM_DEVICE_BT_SCO_STATUS_CONNECTED; return 0; } @@ -4334,6 +4358,7 @@ pa_device_manager* pa_device_manager_init(pa_core *c) { dm = pa_xnew0(pa_device_manager, 1); dm->core = c; + dm->bt_sco_status = DM_DEVICE_BT_SCO_STATUS_DISCONNECTED; dbus_init(dm); diff --git a/src/device-manager.h b/src/device-manager.h index 49de4af..c2485cf 100644 --- a/src/device-manager.h +++ b/src/device-manager.h @@ -38,6 +38,12 @@ typedef enum dm_device_state_type { DM_DEVICE_STATE_ACTIVATED } dm_device_state_t; +typedef enum dm_device_bt_sco_status_type { + DM_DEVICE_BT_SCO_STATUS_DISCONNECTED = 0, + DM_DEVICE_BT_SCO_STATUS_CONNECTED, + DM_DEVICE_BT_SCO_STATUS_OPENED +} dm_device_sco_status_t; + typedef struct pa_device_manager pa_device_manager; typedef struct dm_device dm_device; @@ -74,5 +80,6 @@ int pa_device_manager_load_sink(const char *device_type, const char *device_prof int pa_device_manager_load_source(const char *device_type, const char *device_profile, const char *role, pa_device_manager *dm); int pa_device_manager_bt_sco_open(pa_device_manager *dm); +void pa_device_manager_bt_sco_get_status(pa_device_manager *dm, dm_device_sco_status_t *status); int pa_device_manager_bt_sco_close(pa_device_manager *dm); int pa_device_manager_bt_sco_get_property(pa_device_manager *dm, pa_bool_t *is_wide_band, pa_bool_t *nrec); -- 2.7.4 From f78db82e839d93254747737e772025ea12782abb Mon Sep 17 00:00:00 2001 From: Sangchul Lee Date: Tue, 13 Oct 2015 12:29:17 +0900 Subject: [PATCH 8/8] policy: open/close BT SCO internally Add codes to open and close BT SCO if it is needed. Remove build warnings. Revise versioning policy. [Version] 5.0.1 [Profile] Common [Issue Type] Enhance feature Change-Id: I4e7e50dc50b0a5ad492ac0e6ad2332bcac637a3b Signed-off-by: Sangchul Lee --- packaging/pulseaudio-modules-tizen.spec | 8 +- src/module-policy.c | 156 ++++++++++++++++++++++---------- src/module-sound-player.c | 3 +- 3 files changed, 115 insertions(+), 52 deletions(-) diff --git a/packaging/pulseaudio-modules-tizen.spec b/packaging/pulseaudio-modules-tizen.spec index 236b670..bd0ee3b 100644 --- a/packaging/pulseaudio-modules-tizen.spec +++ b/packaging/pulseaudio-modules-tizen.spec @@ -3,8 +3,8 @@ Name: pulseaudio-modules-tizen Summary: Improved Linux sound server -Version: 5.0 -Release: 45 +Version: 5.0.1 +Release: 0 Group: Multimedia/Audio License: LGPL-2.1+ URL: http://pulseaudio.org @@ -74,6 +74,4 @@ export LD_AS_NEEDED=0 %manifest pulseaudio-modules-tizen.manifest %defattr(-,root,root,-) %license LICENSE.LGPL-2.1+ -%{_libdir}/pulse-%{version}/modules/module-tizenaudio-sink.so -%{_libdir}/pulse-%{version}/modules/module-sound-player.so -%{_libdir}/pulse-%{version}/modules/module-policy.so +%{_libdir}/pulse-5.0/modules/module-*.so diff --git a/src/module-policy.c b/src/module-policy.c index d64fea7..565c978 100644 --- a/src/module-policy.c +++ b/src/module-policy.c @@ -223,6 +223,8 @@ enum signal_index { /* Macros */ #define CONVERT_TO_DEVICE_DIRECTION(stream_type)\ ((stream_type==STREAM_SINK_INPUT)?DM_DEVICE_DIRECTION_OUT:DM_DEVICE_DIRECTION_IN) +#define IS_AVAILABLE_DIRECTION(stream_type, device_direction) \ + ((stream_type==STREAM_SINK_INPUT)?(device_direction & DM_DEVICE_DIRECTION_OUT):(device_direction & DM_DEVICE_DIRECTION_IN)) /* PCM Dump */ #define PA_DUMP_INI_DEFAULT_PATH "/usr/etc/mmfw_audio_pcm_dump.ini" @@ -489,6 +491,10 @@ static pa_hook_result_t select_proper_sink_or_source_hook_cb(pa_core *c, pa_stre void *s = NULL; uint32_t s_idx = 0; + pa_assert(c); + pa_assert(data); + pa_assert(u); + pa_log_info("[SELECT] select_proper_sink_or_source_hook_cb is called. (%p), stream_type(%d), stream_role(%s), route_type(%d)", data, data->stream_type, data->stream_role, data->route_type); @@ -500,7 +506,7 @@ static pa_hook_result_t select_proper_sink_or_source_hook_cb(pa_core *c, pa_stre } if ((data->route_type == STREAM_ROUTE_TYPE_AUTO || data->route_type == STREAM_ROUTE_TYPE_AUTO_ALL) && data->idx_avail_devices) { - /* Get current connected devices */ + /* get current connected devices */ conn_devices = pa_device_manager_get_device_list(u->device_manager); PA_IDXSET_FOREACH(device_type, data->idx_avail_devices, idx) { pa_log_debug("[SELECT][AUTO(_ALL)] avail_device[%u] for this role[%-16s]: type[%-16s]", idx, data->stream_role, device_type); @@ -509,9 +515,7 @@ static pa_hook_result_t select_proper_sink_or_source_hook_cb(pa_core *c, pa_stre dm_device_subtype = pa_device_manager_get_device_subtype(device); device_direction = pa_device_manager_get_device_direction(device); pa_log_debug(" -- conn_devices, type[%-16s], subtype[%-5s], direction[0x%x]", dm_device_type, dm_device_subtype, device_direction); - if (pa_streq(device_type, dm_device_type) && - (((data->stream_type==STREAM_SINK_INPUT) && (device_direction & DM_DEVICE_DIRECTION_OUT)) || - ((data->stream_type==STREAM_SOURCE_OUTPUT) && (device_direction & DM_DEVICE_DIRECTION_IN)))) { + if (pa_streq(device_type, dm_device_type) && IS_AVAILABLE_DIRECTION(data->stream_type, device_direction)) { pa_log_debug(" ** found a matched device: type[%-16s], direction[0x%x]", device_type, device_direction); if (data->stream_type == STREAM_SINK_INPUT) { @@ -540,9 +544,7 @@ static pa_hook_result_t select_proper_sink_or_source_hook_cb(pa_core *c, pa_stre device_direction = pa_device_manager_get_device_direction(device); pa_log_debug(" -- manual_devices, type[%-16s], subtype[%-5s], direction[0x%x], device id[%u]", dm_device_type, dm_device_subtype, device_direction, *device_id); - if (pa_streq(device_type, dm_device_type) && - (((data->stream_type==STREAM_SINK_INPUT) && (device_direction & DM_DEVICE_DIRECTION_OUT)) || - ((data->stream_type==STREAM_SOURCE_OUTPUT) && (device_direction & DM_DEVICE_DIRECTION_IN)))) { + if (pa_streq(device_type, dm_device_type) && IS_AVAILABLE_DIRECTION(data->stream_type, device_direction)) { pa_log_debug(" ** found a matched device: type[%-16s], direction[0x%x]", device_type, device_direction); if (data->stream_type == STREAM_SINK_INPUT) { if ((*(data->proper_sink)) == null_sink) @@ -570,9 +572,7 @@ static pa_hook_result_t select_proper_sink_or_source_hook_cb(pa_core *c, pa_stre device_direction = pa_device_manager_get_device_direction(device); pa_log_debug(" -- manual_devices, type[%-16s], subtype[%-5s], direction[0x%x], device id[%u]", dm_device_type, dm_device_subtype, device_direction, *device_id); - if (pa_streq(device_type, dm_device_type) && - (((data->stream_type==STREAM_SINK_INPUT) && (device_direction & DM_DEVICE_DIRECTION_OUT)) || - ((data->stream_type==STREAM_SOURCE_OUTPUT) && (device_direction & DM_DEVICE_DIRECTION_IN)))) { + if (pa_streq(device_type, dm_device_type) && IS_AVAILABLE_DIRECTION(data->stream_type, device_direction)) { pa_log_debug(" ** found a matched device: type[%-16s], direction[0x%x]", device_type, device_direction); /* currently, we support two sinks for combining */ if (data->stream_type == STREAM_SINK_INPUT) { @@ -644,6 +644,8 @@ static pa_hook_result_t select_proper_sink_or_source_hook_cb(pa_core *c, pa_stre static void _set_device_state(dm_device *device, stream_type_t stream_type, dm_device_state_t device_state) { pa_bool_t use_internal_codec = FALSE; + pa_assert(device); + pa_device_manager_use_internal_codec(device, CONVERT_TO_DEVICE_DIRECTION(stream_type), DEVICE_ROLE_NORMAL, &use_internal_codec); if (use_internal_codec) pa_device_manager_set_device_state(device, CONVERT_TO_DEVICE_DIRECTION(stream_type), device_state); @@ -651,6 +653,39 @@ static void _set_device_state(dm_device *device, stream_type_t stream_type, dm_d return; } +/* Open/Close BT SCO if it is possible */ +static int _set_bt_sco_state(pa_device_manager *dm, pa_bool_t open) { + dm_device_sco_status_t sco_status; + + pa_assert(dm); + + pa_device_manager_bt_sco_get_status(dm, &sco_status); + + if (!open && (sco_status == DM_DEVICE_BT_SCO_STATUS_OPENED)) { + /* close BT SCO */ + if (pa_device_manager_bt_sco_close(dm)) { + pa_log_error("BT SCO was opened, but failed to close SCO"); + return -1; + } else + pa_log_debug("BT SCO is now closed"); + + } else if (open) { + if (sco_status == DM_DEVICE_BT_SCO_STATUS_DISCONNECTED) { + pa_log_error("BT SCO is not available for this BT device"); + return -1; + } else if (sco_status == DM_DEVICE_BT_SCO_STATUS_CONNECTED) { + /* open BT SCO */ + if (pa_device_manager_bt_sco_open(dm)) { + pa_log_error("failed to open BT SCO"); + return -1; + } else + pa_log_debug("BT SCO is now opened"); + } + } + + return 0; +} + /* Change the route setting according to the data from argument. * This function is called only when it needs to change routing path via HAL. * - role is "reset" @@ -706,22 +741,27 @@ static pa_hook_result_t route_change_hook_cb(pa_core *c, pa_stream_manager_hook_ pa_bool_t use_internal_codec = FALSE; pa_bool_t available = FALSE; + pa_assert(c); + pa_assert(data); + pa_assert(u); + pa_log_info("[ROUTE] route_change_hook_cb is called. (%p), stream_type(%d), stream_role(%s), route_type(%d)", data, data->stream_type, data->stream_role, data->route_type); route_info.role = data->stream_role; if (pa_streq(data->stream_role, "reset")) { - /* Get current connected devices */ + /* update BT SCO: close */ + _set_bt_sco_state(u->device_manager, FALSE); + + /* get current connected devices */ conn_devices = pa_device_manager_get_device_list(u->device_manager); - /* Set device state to deactivate */ + /* set device state to deactivate */ PA_IDXSET_FOREACH(device, conn_devices, conn_idx) { dm_device_type = pa_device_manager_get_device_type(device); device_state = pa_device_manager_get_device_state(device, CONVERT_TO_DEVICE_DIRECTION(data->stream_type)); device_direction = pa_device_manager_get_device_direction(device); - if (device_state == DM_DEVICE_STATE_ACTIVATED && - (((data->stream_type==STREAM_SINK_INPUT) && (device_direction & DM_DEVICE_DIRECTION_OUT)) || - ((data->stream_type==STREAM_SOURCE_OUTPUT) && (device_direction & DM_DEVICE_DIRECTION_IN)))) { + if (device_state == DM_DEVICE_STATE_ACTIVATED && IS_AVAILABLE_DIRECTION(data->stream_type, device_direction)) { pa_log_debug("[ROUTE][RESET] found a matched device and set state to DE-ACTIVATED: type[%s], direction[0x%x]", dm_device_type, device_direction); _set_device_state(device, data->stream_type, DM_DEVICE_STATE_DEACTIVATED); } @@ -749,7 +789,10 @@ static pa_hook_result_t route_change_hook_cb(pa_core *c, pa_stream_manager_hook_ } } else if ((data->route_type == STREAM_ROUTE_TYPE_AUTO || data->route_type == STREAM_ROUTE_TYPE_AUTO_ALL) && data->idx_avail_devices) { - /* Get current connected devices */ + /* update BT SCO: close */ + _set_bt_sco_state(u->device_manager, FALSE); + + /* get current connected devices */ conn_devices = pa_device_manager_get_device_list(u->device_manager); PA_IDXSET_FOREACH(device_type, data->idx_avail_devices, idx) { pa_log_debug("[ROUTE][AUTO(_ALL)] avail_device[%u] for this role[%-16s]: type[%-16s]", idx, route_info.role, device_type); @@ -760,9 +803,7 @@ static pa_hook_result_t route_change_hook_cb(pa_core *c, pa_stream_manager_hook_ device_idx = pa_device_manager_get_device_id(device); pa_log_debug(" -- conn_devices, type[%-16s], subtype[%-5s], direction[0x%x], id[%u]", dm_device_type, dm_device_subtype, device_direction, device_idx); - if (pa_streq(device_type, dm_device_type) && - (((data->stream_type==STREAM_SINK_INPUT) && (device_direction & DM_DEVICE_DIRECTION_OUT)) || - ((data->stream_type==STREAM_SOURCE_OUTPUT) && (device_direction & DM_DEVICE_DIRECTION_IN)))) { + if (pa_streq(device_type, dm_device_type) && IS_AVAILABLE_DIRECTION(data->stream_type, device_direction)) { pa_log_debug(" ** found a matched device: type[%-16s], direction[0x%x]", device_type, device_direction); pa_device_manager_use_internal_codec(device, CONVERT_TO_DEVICE_DIRECTION(data->stream_type), DEVICE_ROLE_NORMAL, &use_internal_codec); if (use_internal_codec) { @@ -773,7 +814,7 @@ static pa_hook_result_t route_change_hook_cb(pa_core *c, pa_stream_manager_hook_ route_info.device_infos[route_info.num_of_devices-1].id = device_idx; pa_log_debug(" ** found a matched device and set state to ACTIVATED: type[%-16s], direction[0x%x], id[%u]", route_info.device_infos[route_info.num_of_devices-1].type, device_direction, device_idx); - /* Set device state to activated */ + /* set device state to activated */ _set_device_state(device, data->stream_type, DM_DEVICE_STATE_ACTIVATED); } else pa_log_debug(" -- it does not use internal audio codec, skip it"); @@ -785,7 +826,7 @@ static pa_hook_result_t route_change_hook_cb(pa_core *c, pa_stream_manager_hook_ pa_device_manager_use_internal_codec(device, CONVERT_TO_DEVICE_DIRECTION(data->stream_type), DEVICE_ROLE_NORMAL, &use_internal_codec); if(use_internal_codec) { - /* Set other device's state to deactivated */ + /* set other device's state to deactivated */ PA_IDXSET_FOREACH(_device, conn_devices, conn_idx) { if (device == _device) continue; @@ -793,7 +834,7 @@ static pa_hook_result_t route_change_hook_cb(pa_core *c, pa_stream_manager_hook_ } } - /* Move sink-inputs/source-outputs if needed */ + /* move sink-inputs/source-outputs if needed */ if (data->stream_type == STREAM_SINK_INPUT) sink = pa_device_manager_get_sink(device, DEVICE_ROLE_NORMAL); else if (data->stream_type == STREAM_SOURCE_OUTPUT) @@ -885,7 +926,7 @@ static pa_hook_result_t route_change_hook_cb(pa_core *c, pa_stream_manager_hook_ else *(data->proper_source) = source; } else { - /* Move sink-inputs/source-outputs if needed */ + /* move sink-inputs/source-outputs if needed */ if (data->idx_streams) { PA_IDXSET_FOREACH (s, data->idx_streams, s_idx) { /* data->idx_streams: null_sink */ if ((sink && (sink != ((pa_sink_input*)s)->sink)) || (source && (source != ((pa_source_output*)s)->source))) { @@ -908,7 +949,7 @@ static pa_hook_result_t route_change_hook_cb(pa_core *c, pa_stream_manager_hook_ } if (data->route_type == STREAM_ROUTE_TYPE_AUTO_ALL && route_info.num_of_devices) { - /* Set other device's state to deactivated */ + /* set other device's state to deactivated */ PA_IDXSET_FOREACH(_device, conn_devices, conn_idx) { pa_bool_t need_to_deactive = TRUE; device_idx = pa_device_manager_get_device_id(_device); @@ -931,30 +972,41 @@ static pa_hook_result_t route_change_hook_cb(pa_core *c, pa_stream_manager_hook_ if ((device = pa_device_manager_get_device_by_id(u->device_manager, *device_id))) { dm_device_type = pa_device_manager_get_device_type(device); dm_device_subtype = pa_device_manager_get_device_subtype(device); - device_direction = pa_device_manager_get_device_direction(device); - pa_log_debug(" -- manual_device, type[%-16s], subtype[%-5s], direction[0x%x]", dm_device_type, dm_device_subtype, device_direction); - if (pa_streq(device_type, dm_device_type) && - (((data->stream_type==STREAM_SINK_INPUT) && (device_direction & DM_DEVICE_DIRECTION_OUT)) || - ((data->stream_type==STREAM_SOURCE_OUTPUT) && (device_direction & DM_DEVICE_DIRECTION_IN)))) { - pa_log_debug(" ** found a matched device: type[%-16s], direction[0x%x]", device_type, device_direction); - pa_device_manager_use_internal_codec(device, CONVERT_TO_DEVICE_DIRECTION(data->stream_type), DEVICE_ROLE_NORMAL, &use_internal_codec); - if (use_internal_codec) { - route_info.num_of_devices++; - route_info.device_infos = pa_xrealloc(route_info.device_infos, sizeof(hal_device_info)*route_info.num_of_devices); - route_info.device_infos[route_info.num_of_devices-1].type = dm_device_type; - route_info.device_infos[route_info.num_of_devices-1].direction = (data->stream_type==STREAM_SINK_INPUT)?DIRECTION_OUT:DIRECTION_IN; - pa_log_debug(" ** found a matched device and set state to ACTIVATED: type[%-16s], direction[0x%x]", - route_info.device_infos[route_info.num_of_devices-1].type, device_direction); - /* Set device state to activated */ - _set_device_state(device, data->stream_type, DM_DEVICE_STATE_ACTIVATED); - } else - pa_log_debug(" -- it does not use internal audio codec, skip it"); + if (pa_streq(device_type, dm_device_type)) { + pa_log_debug(" ** found a matched device: type[%-16s]", device_type); + if (pa_streq(dm_device_type, DEVICE_TYPE_BT)) { + /* update BT SCO: open */ + if (_set_bt_sco_state(u->device_manager, TRUE)) { + pa_log_error(" ** could not open BT SCO"); + continue; + } + } else { + /* update BT SCO: close */ + _set_bt_sco_state(u->device_manager, FALSE); } + device_direction = pa_device_manager_get_device_direction(device); + dm_device_subtype = pa_device_manager_get_device_subtype(device); + pa_log_debug(" -- manual_device, type[%-16s], subtype[%-5s], direction[0x%x]", dm_device_type, dm_device_subtype, device_direction); + if (IS_AVAILABLE_DIRECTION(data->stream_type, device_direction)) { + pa_device_manager_use_internal_codec(device, CONVERT_TO_DEVICE_DIRECTION(data->stream_type), DEVICE_ROLE_NORMAL, &use_internal_codec); + if (use_internal_codec) { + route_info.num_of_devices++; + route_info.device_infos = pa_xrealloc(route_info.device_infos, sizeof(hal_device_info)*route_info.num_of_devices); + route_info.device_infos[route_info.num_of_devices-1].type = dm_device_type; + route_info.device_infos[route_info.num_of_devices-1].direction = (data->stream_type==STREAM_SINK_INPUT)?DIRECTION_OUT:DIRECTION_IN; + pa_log_debug(" ** found a matched device and set state to ACTIVATED: type[%-16s], direction[0x%x]", + route_info.device_infos[route_info.num_of_devices-1].type, device_direction); + /* set device state to activated */ + _set_device_state(device, data->stream_type, DM_DEVICE_STATE_ACTIVATED); + } else + pa_log_debug(" -- it does not use internal audio codec, skip it"); + } + } } } } - /* Move sink-inputs/source-outputs if needed */ + /* move sink-inputs/source-outputs if needed */ if (device && !data->origins_from_new_data) { if (data->stream_type == STREAM_SINK_INPUT) sink = pa_device_manager_get_sink(device, DEVICE_ROLE_NORMAL); @@ -975,7 +1027,7 @@ static pa_hook_result_t route_change_hook_cb(pa_core *c, pa_stream_manager_hook_ } if (route_info.device_infos) { - /* Send information to HAL to set routing */ + /* send information to HAL to set routing */ if(pa_hal_manager_do_route (u->hal_manager, &route_info)) pa_log_error("[ROUTE] Failed to pa_hal_manager_do_route()"); pa_xfree(route_info.device_infos); @@ -987,13 +1039,17 @@ static pa_hook_result_t route_change_hook_cb(pa_core *c, pa_stream_manager_hook_ static pa_hook_result_t route_option_update_hook_cb(pa_core *c, pa_stream_manager_hook_data_for_option *data, struct userdata *u) { hal_route_option route_option; + pa_assert(c); + pa_assert(data); + pa_assert(u); + pa_log_info("[ROUTE_OPT] route_option_update_hook_cb is called. (%p), stream_role(%s), option[name(%s)/value(%d)]", data, data->stream_role, data->name, data->value); route_option.role = data->stream_role; route_option.name = data->name; route_option.value = data->value; - /* Send information to HAL to update routing option */ + /* send information to HAL to update routing option */ if(pa_hal_manager_update_route_option (u->hal_manager, &route_option)) pa_log_error("[ROUTE_OPT] Failed to pa_hal_manager_update_route_option()"); @@ -1030,6 +1086,10 @@ static void update_sink_or_source_as_device_connection_change(stream_route_type_ pa_sink *combine_sink = NULL; pa_stream_manager_hook_data_for_update_route hook_call_update_route_data; + pa_assert(streams); + pa_assert(device); + pa_assert(u); + null_sink = (pa_sink*)pa_namereg_get(u->core, SINK_NULL, PA_NAMEREG_SINK); null_source = (pa_source*)pa_namereg_get(u->core, SOURCE_NULL, PA_NAMEREG_SOURCE); if (!null_sink || !null_source) { @@ -1165,7 +1225,7 @@ static void update_sink_or_source_as_device_connection_change(stream_route_type_ } } - /* Set device state to deactivated */ + /* set device state to deactivated */ if (cnt) { PA_IDXSET_FOREACH (s, streams, s_idx) { /* streams: core->source_outputs/core->sink_inputs */ if ((cur_device_type = pa_proplist_gets(GET_STREAM_PROPLIST(s, stream_type), PA_PROP_MEDIA_ROUTE_AUTO_ACTIVE_DEV))) { @@ -1234,6 +1294,10 @@ static pa_hook_result_t device_connection_changed_hook_cb(pa_core *c, pa_device_ pa_idxset* conn_devices = NULL; dm_device* device = NULL; + pa_assert(c); + pa_assert(conn); + pa_assert(u); + device_direction = pa_device_manager_get_device_direction(conn->device); pa_log_info("[CONN] device_connection_changed_hook_cb is called. conn(%p), is_connected(%d), device(%p), direction(0x%x)", diff --git a/src/module-sound-player.c b/src/module-sound-player.c index 9f98543..1e6f0a6 100644 --- a/src/module-sound-player.c +++ b/src/module-sound-player.c @@ -41,6 +41,7 @@ #include #include #include +#include #ifdef HAVE_DBUS #include #include @@ -529,7 +530,7 @@ static void io_event_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_io data_size = sizeof(data); memset(&data, 0, data_size); while (read_sum != data_size && retry_count < RETRY_NUM) { - ret = read(fd, ((void *)&data)+read_sum, data_size-read_sum); + ret = read(fd, (void*)(((char *)&data)+read_sum), data_size-read_sum); if (ret < 0 && errno == EAGAIN) retry_count++; else -- 2.7.4