diff --git a/pipewire-pulseaudio/LICENSE b/pipewire-pulseaudio/LICENSE new file mode 100644 index 000000000..19e307187 --- /dev/null +++ b/pipewire-pulseaudio/LICENSE @@ -0,0 +1,504 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +(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. + + {description} + Copyright (C) {year} {fullname} + + 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 Street, 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. + + {signature of Ty Coon}, 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/pipewire-pulseaudio/README.md b/pipewire-pulseaudio/README.md new file mode 100644 index 000000000..017669f60 --- /dev/null +++ b/pipewire-pulseaudio/README.md @@ -0,0 +1,2 @@ +# pipewire-pulseaudio +PulseAudio client library for PipeWire diff --git a/pipewire-pulseaudio/src/bitset.c b/pipewire-pulseaudio/src/bitset.c new file mode 100644 index 000000000..be834f5eb --- /dev/null +++ b/pipewire-pulseaudio/src/bitset.c @@ -0,0 +1,65 @@ +/*** + This file is part of PulseAudio. + + Copyright 2009 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, see . +***/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#include "internal.h" +#include "bitset.h" + +void pa_bitset_set(pa_bitset_t *b, unsigned k, bool v) { + pa_assert(b); + + if (v) + b[k >> 5] |= 1 << (k & 31); + else + b[k >> 5] &= ~((uint32_t) (1 << (k & 31))); +} + +bool pa_bitset_get(const pa_bitset_t *b, unsigned k) { + return !!(b[k >> 5] & (1 << (k & 31))); +} + +bool pa_bitset_equals(const pa_bitset_t *b, unsigned n, ...) { + va_list ap; + pa_bitset_t *a; + bool equal; + + a = alloca(PA_BITSET_SIZE(n)); + spa_memzero(a, PA_BITSET_SIZE(n)); + + va_start(ap, n); + for (;;) { + int j = va_arg(ap, int); + + if (j < 0) + break; + + pa_bitset_set(a, j, true); + } + va_end(ap); + + equal = memcmp(a, b, PA_BITSET_SIZE(n)) == 0; + + return equal; +} diff --git a/pipewire-pulseaudio/src/bitset.h b/pipewire-pulseaudio/src/bitset.h new file mode 100644 index 000000000..314dafc2a --- /dev/null +++ b/pipewire-pulseaudio/src/bitset.h @@ -0,0 +1,34 @@ +#ifndef foopulsecorebitsethfoo +#define foopulsecorebitsethfoo + +/*** + This file is part of PulseAudio. + + Copyright 2009 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, see . +***/ + +#include + +#define PA_BITSET_ELEMENTS(n) (((n)+31)/32) +#define PA_BITSET_SIZE(n) (PA_BITSET_ELEMENTS(n)*4) + +typedef uint32_t pa_bitset_t; + +void pa_bitset_set(pa_bitset_t *b, unsigned k, bool v); +bool pa_bitset_get(const pa_bitset_t *b, unsigned k); +bool pa_bitset_equals(const pa_bitset_t *b, unsigned n, ...); + +#endif diff --git a/pipewire-pulseaudio/src/channelmap.c b/pipewire-pulseaudio/src/channelmap.c new file mode 100644 index 000000000..825d564ba --- /dev/null +++ b/pipewire-pulseaudio/src/channelmap.c @@ -0,0 +1,851 @@ +/*** + This file is part of PulseAudio. + + Copyright 2005-2006 Lennart Poettering + Copyright 2006 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.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, see . +***/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include + +#include +#include + +#include "internal.h" +#include "bitset.h" +#include "sample-util.h" + +const char *const table[PA_CHANNEL_POSITION_MAX] = { + [PA_CHANNEL_POSITION_MONO] = "mono", + + [PA_CHANNEL_POSITION_FRONT_CENTER] = "front-center", + [PA_CHANNEL_POSITION_FRONT_LEFT] = "front-left", + [PA_CHANNEL_POSITION_FRONT_RIGHT] = "front-right", + + [PA_CHANNEL_POSITION_REAR_CENTER] = "rear-center", + [PA_CHANNEL_POSITION_REAR_LEFT] = "rear-left", + [PA_CHANNEL_POSITION_REAR_RIGHT] = "rear-right", + + [PA_CHANNEL_POSITION_LFE] = "lfe", + + [PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER] = "front-left-of-center", + [PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER] = "front-right-of-center", + + [PA_CHANNEL_POSITION_SIDE_LEFT] = "side-left", + [PA_CHANNEL_POSITION_SIDE_RIGHT] = "side-right", + + [PA_CHANNEL_POSITION_AUX0] = "aux0", + [PA_CHANNEL_POSITION_AUX1] = "aux1", + [PA_CHANNEL_POSITION_AUX2] = "aux2", + [PA_CHANNEL_POSITION_AUX3] = "aux3", + [PA_CHANNEL_POSITION_AUX4] = "aux4", + [PA_CHANNEL_POSITION_AUX5] = "aux5", + [PA_CHANNEL_POSITION_AUX6] = "aux6", + [PA_CHANNEL_POSITION_AUX7] = "aux7", + [PA_CHANNEL_POSITION_AUX8] = "aux8", + [PA_CHANNEL_POSITION_AUX9] = "aux9", + [PA_CHANNEL_POSITION_AUX10] = "aux10", + [PA_CHANNEL_POSITION_AUX11] = "aux11", + [PA_CHANNEL_POSITION_AUX12] = "aux12", + [PA_CHANNEL_POSITION_AUX13] = "aux13", + [PA_CHANNEL_POSITION_AUX14] = "aux14", + [PA_CHANNEL_POSITION_AUX15] = "aux15", + [PA_CHANNEL_POSITION_AUX16] = "aux16", + [PA_CHANNEL_POSITION_AUX17] = "aux17", + [PA_CHANNEL_POSITION_AUX18] = "aux18", + [PA_CHANNEL_POSITION_AUX19] = "aux19", + [PA_CHANNEL_POSITION_AUX20] = "aux20", + [PA_CHANNEL_POSITION_AUX21] = "aux21", + [PA_CHANNEL_POSITION_AUX22] = "aux22", + [PA_CHANNEL_POSITION_AUX23] = "aux23", + [PA_CHANNEL_POSITION_AUX24] = "aux24", + [PA_CHANNEL_POSITION_AUX25] = "aux25", + [PA_CHANNEL_POSITION_AUX26] = "aux26", + [PA_CHANNEL_POSITION_AUX27] = "aux27", + [PA_CHANNEL_POSITION_AUX28] = "aux28", + [PA_CHANNEL_POSITION_AUX29] = "aux29", + [PA_CHANNEL_POSITION_AUX30] = "aux30", + [PA_CHANNEL_POSITION_AUX31] = "aux31", + + [PA_CHANNEL_POSITION_TOP_CENTER] = "top-center", + + [PA_CHANNEL_POSITION_TOP_FRONT_CENTER] = "top-front-center", + [PA_CHANNEL_POSITION_TOP_FRONT_LEFT] = "top-front-left", + [PA_CHANNEL_POSITION_TOP_FRONT_RIGHT] = "top-front-right", + + [PA_CHANNEL_POSITION_TOP_REAR_CENTER] = "top-rear-center", + [PA_CHANNEL_POSITION_TOP_REAR_LEFT] = "top-rear-left", + [PA_CHANNEL_POSITION_TOP_REAR_RIGHT] = "top-rear-right" +}; + +const char *const pretty_table[PA_CHANNEL_POSITION_MAX] = { + [PA_CHANNEL_POSITION_MONO] = N_("Mono"), + + [PA_CHANNEL_POSITION_FRONT_CENTER] = N_("Front Center"), + [PA_CHANNEL_POSITION_FRONT_LEFT] = N_("Front Left"), + [PA_CHANNEL_POSITION_FRONT_RIGHT] = N_("Front Right"), + + [PA_CHANNEL_POSITION_REAR_CENTER] = N_("Rear Center"), + [PA_CHANNEL_POSITION_REAR_LEFT] = N_("Rear Left"), + [PA_CHANNEL_POSITION_REAR_RIGHT] = N_("Rear Right"), + + [PA_CHANNEL_POSITION_LFE] = N_("Subwoofer"), + + [PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER] = N_("Front Left-of-center"), + [PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER] = N_("Front Right-of-center"), + + [PA_CHANNEL_POSITION_SIDE_LEFT] = N_("Side Left"), + [PA_CHANNEL_POSITION_SIDE_RIGHT] = N_("Side Right"), + + [PA_CHANNEL_POSITION_AUX0] = N_("Auxiliary 0"), + [PA_CHANNEL_POSITION_AUX1] = N_("Auxiliary 1"), + [PA_CHANNEL_POSITION_AUX2] = N_("Auxiliary 2"), + [PA_CHANNEL_POSITION_AUX3] = N_("Auxiliary 3"), + [PA_CHANNEL_POSITION_AUX4] = N_("Auxiliary 4"), + [PA_CHANNEL_POSITION_AUX5] = N_("Auxiliary 5"), + [PA_CHANNEL_POSITION_AUX6] = N_("Auxiliary 6"), + [PA_CHANNEL_POSITION_AUX7] = N_("Auxiliary 7"), + [PA_CHANNEL_POSITION_AUX8] = N_("Auxiliary 8"), + [PA_CHANNEL_POSITION_AUX9] = N_("Auxiliary 9"), + [PA_CHANNEL_POSITION_AUX10] = N_("Auxiliary 10"), + [PA_CHANNEL_POSITION_AUX11] = N_("Auxiliary 11"), + [PA_CHANNEL_POSITION_AUX12] = N_("Auxiliary 12"), + [PA_CHANNEL_POSITION_AUX13] = N_("Auxiliary 13"), + [PA_CHANNEL_POSITION_AUX14] = N_("Auxiliary 14"), + [PA_CHANNEL_POSITION_AUX15] = N_("Auxiliary 15"), + [PA_CHANNEL_POSITION_AUX16] = N_("Auxiliary 16"), + [PA_CHANNEL_POSITION_AUX17] = N_("Auxiliary 17"), + [PA_CHANNEL_POSITION_AUX18] = N_("Auxiliary 18"), + [PA_CHANNEL_POSITION_AUX19] = N_("Auxiliary 19"), + [PA_CHANNEL_POSITION_AUX20] = N_("Auxiliary 20"), + [PA_CHANNEL_POSITION_AUX21] = N_("Auxiliary 21"), + [PA_CHANNEL_POSITION_AUX22] = N_("Auxiliary 22"), + [PA_CHANNEL_POSITION_AUX23] = N_("Auxiliary 23"), + [PA_CHANNEL_POSITION_AUX24] = N_("Auxiliary 24"), + [PA_CHANNEL_POSITION_AUX25] = N_("Auxiliary 25"), + [PA_CHANNEL_POSITION_AUX26] = N_("Auxiliary 26"), + [PA_CHANNEL_POSITION_AUX27] = N_("Auxiliary 27"), + [PA_CHANNEL_POSITION_AUX28] = N_("Auxiliary 28"), + [PA_CHANNEL_POSITION_AUX29] = N_("Auxiliary 29"), + [PA_CHANNEL_POSITION_AUX30] = N_("Auxiliary 30"), + [PA_CHANNEL_POSITION_AUX31] = N_("Auxiliary 31"), + + [PA_CHANNEL_POSITION_TOP_CENTER] = N_("Top Center"), + + [PA_CHANNEL_POSITION_TOP_FRONT_CENTER] = N_("Top Front Center"), + [PA_CHANNEL_POSITION_TOP_FRONT_LEFT] = N_("Top Front Left"), + [PA_CHANNEL_POSITION_TOP_FRONT_RIGHT] = N_("Top Front Right"), + + [PA_CHANNEL_POSITION_TOP_REAR_CENTER] = N_("Top Rear Center"), + [PA_CHANNEL_POSITION_TOP_REAR_LEFT] = N_("Top Rear Left"), + [PA_CHANNEL_POSITION_TOP_REAR_RIGHT] = N_("Top Rear Right") +}; + +SPA_EXPORT +pa_channel_map* pa_channel_map_init(pa_channel_map *m) { + unsigned c; + pa_assert(m); + + m->channels = 0; + + for (c = 0; c < PA_CHANNELS_MAX; c++) + m->map[c] = PA_CHANNEL_POSITION_INVALID; + + return m; +} + +SPA_EXPORT +pa_channel_map* pa_channel_map_init_mono(pa_channel_map *m) { + pa_assert(m); + + pa_channel_map_init(m); + + m->channels = 1; + m->map[0] = PA_CHANNEL_POSITION_MONO; + return m; +} + +SPA_EXPORT +pa_channel_map* pa_channel_map_init_stereo(pa_channel_map *m) { + pa_assert(m); + + pa_channel_map_init(m); + + m->channels = 2; + m->map[0] = PA_CHANNEL_POSITION_LEFT; + m->map[1] = PA_CHANNEL_POSITION_RIGHT; + return m; +} + +SPA_EXPORT +pa_channel_map* pa_channel_map_init_auto(pa_channel_map *m, unsigned channels, pa_channel_map_def_t def) { + pa_assert(m); + pa_assert(pa_channels_valid(channels)); + pa_assert(def < PA_CHANNEL_MAP_DEF_MAX); + + pa_channel_map_init(m); + + m->channels = (uint8_t) channels; + + switch (def) { + case PA_CHANNEL_MAP_AIFF: + + /* This is somewhat compatible with RFC3551 */ + + switch (channels) { + case 1: + m->map[0] = PA_CHANNEL_POSITION_MONO; + return m; + + case 6: + m->map[0] = PA_CHANNEL_POSITION_FRONT_LEFT; + m->map[1] = PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER; + m->map[2] = PA_CHANNEL_POSITION_FRONT_CENTER; + m->map[3] = PA_CHANNEL_POSITION_FRONT_RIGHT; + m->map[4] = PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER; + m->map[5] = PA_CHANNEL_POSITION_REAR_CENTER; + return m; + + case 5: + m->map[2] = PA_CHANNEL_POSITION_FRONT_CENTER; + m->map[3] = PA_CHANNEL_POSITION_REAR_LEFT; + m->map[4] = PA_CHANNEL_POSITION_REAR_RIGHT; + /* Fall through */ + + case 2: + m->map[0] = PA_CHANNEL_POSITION_FRONT_LEFT; + m->map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT; + return m; + + case 3: + m->map[0] = PA_CHANNEL_POSITION_LEFT; + m->map[1] = PA_CHANNEL_POSITION_RIGHT; + m->map[2] = PA_CHANNEL_POSITION_CENTER; + return m; + + case 4: + m->map[0] = PA_CHANNEL_POSITION_LEFT; + m->map[1] = PA_CHANNEL_POSITION_CENTER; + m->map[2] = PA_CHANNEL_POSITION_RIGHT; + m->map[3] = PA_CHANNEL_POSITION_REAR_CENTER; + return m; + + default: + return NULL; + } + + case PA_CHANNEL_MAP_ALSA: + + switch (channels) { + case 1: + m->map[0] = PA_CHANNEL_POSITION_MONO; + return m; + + case 8: + m->map[6] = PA_CHANNEL_POSITION_SIDE_LEFT; + m->map[7] = PA_CHANNEL_POSITION_SIDE_RIGHT; + /* Fall through */ + + case 6: + m->map[5] = PA_CHANNEL_POSITION_LFE; + /* Fall through */ + + case 5: + m->map[4] = PA_CHANNEL_POSITION_FRONT_CENTER; + /* Fall through */ + + case 4: + m->map[2] = PA_CHANNEL_POSITION_REAR_LEFT; + m->map[3] = PA_CHANNEL_POSITION_REAR_RIGHT; + /* Fall through */ + + case 2: + m->map[0] = PA_CHANNEL_POSITION_FRONT_LEFT; + m->map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT; + return m; + + default: + return NULL; + } + + case PA_CHANNEL_MAP_AUX: { + unsigned i; + + for (i = 0; i < channels; i++) + m->map[i] = PA_CHANNEL_POSITION_AUX0 + i; + + return m; + } + + case PA_CHANNEL_MAP_WAVEEX: + + /* Following http://www.microsoft.com/whdc/device/audio/multichaud.mspx#EKLAC */ + + switch (channels) { + case 1: + m->map[0] = PA_CHANNEL_POSITION_MONO; + return m; + + case 18: + m->map[15] = PA_CHANNEL_POSITION_TOP_REAR_LEFT; + m->map[16] = PA_CHANNEL_POSITION_TOP_REAR_CENTER; + m->map[17] = PA_CHANNEL_POSITION_TOP_REAR_RIGHT; + /* Fall through */ + + case 15: + m->map[12] = PA_CHANNEL_POSITION_TOP_FRONT_LEFT; + m->map[13] = PA_CHANNEL_POSITION_TOP_FRONT_CENTER; + m->map[14] = PA_CHANNEL_POSITION_TOP_FRONT_RIGHT; + /* Fall through */ + + case 12: + m->map[11] = PA_CHANNEL_POSITION_TOP_CENTER; + /* Fall through */ + + case 11: + m->map[9] = PA_CHANNEL_POSITION_SIDE_LEFT; + m->map[10] = PA_CHANNEL_POSITION_SIDE_RIGHT; + /* Fall through */ + + case 9: + m->map[8] = PA_CHANNEL_POSITION_REAR_CENTER; + /* Fall through */ + + case 8: + m->map[6] = PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER; + m->map[7] = PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER; + /* Fall through */ + + case 6: + m->map[4] = PA_CHANNEL_POSITION_REAR_LEFT; + m->map[5] = PA_CHANNEL_POSITION_REAR_RIGHT; + /* Fall through */ + + case 4: + m->map[3] = PA_CHANNEL_POSITION_LFE; + /* Fall through */ + + case 3: + m->map[2] = PA_CHANNEL_POSITION_FRONT_CENTER; + /* Fall through */ + + case 2: + m->map[0] = PA_CHANNEL_POSITION_FRONT_LEFT; + m->map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT; + return m; + + default: + return NULL; + } + + case PA_CHANNEL_MAP_OSS: + + switch (channels) { + case 1: + m->map[0] = PA_CHANNEL_POSITION_MONO; + return m; + + case 8: + m->map[6] = PA_CHANNEL_POSITION_REAR_LEFT; + m->map[7] = PA_CHANNEL_POSITION_REAR_RIGHT; + /* Fall through */ + + case 6: + m->map[4] = PA_CHANNEL_POSITION_SIDE_LEFT; + m->map[5] = PA_CHANNEL_POSITION_SIDE_RIGHT; + /* Fall through */ + + case 4: + m->map[3] = PA_CHANNEL_POSITION_LFE; + /* Fall through */ + + case 3: + m->map[2] = PA_CHANNEL_POSITION_FRONT_CENTER; + /* Fall through */ + + case 2: + m->map[0] = PA_CHANNEL_POSITION_FRONT_LEFT; + m->map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT; + return m; + + default: + return NULL; + } + + default: + pa_assert_not_reached(); + } +} + +SPA_EXPORT +pa_channel_map* pa_channel_map_init_extend(pa_channel_map *m, unsigned channels, pa_channel_map_def_t def) { + unsigned c; + + pa_assert(m); + pa_assert(pa_channels_valid(channels)); + pa_assert(def < PA_CHANNEL_MAP_DEF_MAX); + + pa_channel_map_init(m); + + for (c = channels; c > 0; c--) { + + if (pa_channel_map_init_auto(m, c, def)) { + unsigned i = 0; + + for (; c < channels; c++) { + + m->map[c] = PA_CHANNEL_POSITION_AUX0 + i; + i++; + } + + m->channels = (uint8_t) channels; + + return m; + } + } + + return NULL; +} + +SPA_EXPORT +const char* pa_channel_position_to_string(pa_channel_position_t pos) { + + if (pos < 0 || pos >= PA_CHANNEL_POSITION_MAX) + return NULL; + + return table[pos]; +} + +SPA_EXPORT +const char* pa_channel_position_to_pretty_string(pa_channel_position_t pos) { + + if (pos < 0 || pos >= PA_CHANNEL_POSITION_MAX) + return NULL; + + pa_init_i18n(); + + return _(pretty_table[pos]); +} + +SPA_EXPORT +int pa_channel_map_equal(const pa_channel_map *a, const pa_channel_map *b) { + unsigned c; + + pa_assert(a); + pa_assert(b); + + pa_return_val_if_fail(pa_channel_map_valid(a), 0); + + if (PA_UNLIKELY(a == b)) + return 1; + + pa_return_val_if_fail(pa_channel_map_valid(b), 0); + + if (a->channels != b->channels) + return 0; + + for (c = 0; c < a->channels; c++) + if (a->map[c] != b->map[c]) + return 0; + + return 1; +} + +SPA_EXPORT +char* pa_channel_map_snprint(char *s, size_t l, const pa_channel_map *map) { + unsigned channel; + bool first = true; + char *e; + + pa_assert(s); + pa_assert(l > 0); + pa_assert(map); + + pa_init_i18n(); + + if (!pa_channel_map_valid(map)) { + pa_snprintf(s, l, _("(invalid)")); + return s; + } + + *(e = s) = 0; + + for (channel = 0; channel < map->channels && l > 1; channel++) { + l -= pa_snprintf(e, l, "%s%s", + first ? "" : ",", + pa_channel_position_to_string(map->map[channel])); + + e = strchr(e, 0); + first = false; + } + + return s; +} + +SPA_EXPORT +pa_channel_position_t pa_channel_position_from_string(const char *p) { + pa_channel_position_t i; + pa_assert(p); + + /* Some special aliases */ + if (pa_streq(p, "left")) + return PA_CHANNEL_POSITION_LEFT; + else if (pa_streq(p, "right")) + return PA_CHANNEL_POSITION_RIGHT; + else if (pa_streq(p, "center")) + return PA_CHANNEL_POSITION_CENTER; + else if (pa_streq(p, "subwoofer")) + return PA_CHANNEL_POSITION_SUBWOOFER; + + for (i = 0; i < PA_CHANNEL_POSITION_MAX; i++) + if (pa_streq(p, table[i])) + return i; + + return PA_CHANNEL_POSITION_INVALID; +} + +SPA_EXPORT +pa_channel_map *pa_channel_map_parse(pa_channel_map *rmap, const char *s) { + pa_channel_map map; + char **tokens; + int i, n_tokens; + + pa_assert(rmap); + pa_assert(s); + + pa_channel_map_init(&map); + + /* We don't need to match against the well known channel mapping + * "mono" here explicitly, because that can be understood as + * listing with one channel called "mono". */ + + if (pa_streq(s, "stereo")) { + map.channels = 2; + map.map[0] = PA_CHANNEL_POSITION_LEFT; + map.map[1] = PA_CHANNEL_POSITION_RIGHT; + goto finish; + } else if (pa_streq(s, "surround-21")) { + map.channels = 3; + map.map[0] = PA_CHANNEL_POSITION_FRONT_LEFT; + map.map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT; + map.map[2] = PA_CHANNEL_POSITION_LFE; + goto finish; + } else if (pa_streq(s, "surround-40")) { + map.channels = 4; + map.map[0] = PA_CHANNEL_POSITION_FRONT_LEFT; + map.map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT; + map.map[2] = PA_CHANNEL_POSITION_REAR_LEFT; + map.map[3] = PA_CHANNEL_POSITION_REAR_RIGHT; + goto finish; + } else if (pa_streq(s, "surround-41")) { + map.channels = 5; + map.map[0] = PA_CHANNEL_POSITION_FRONT_LEFT; + map.map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT; + map.map[2] = PA_CHANNEL_POSITION_REAR_LEFT; + map.map[3] = PA_CHANNEL_POSITION_REAR_RIGHT; + map.map[4] = PA_CHANNEL_POSITION_LFE; + goto finish; + } else if (pa_streq(s, "surround-50")) { + map.channels = 5; + map.map[0] = PA_CHANNEL_POSITION_FRONT_LEFT; + map.map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT; + map.map[2] = PA_CHANNEL_POSITION_REAR_LEFT; + map.map[3] = PA_CHANNEL_POSITION_REAR_RIGHT; + map.map[4] = PA_CHANNEL_POSITION_FRONT_CENTER; + goto finish; + } else if (pa_streq(s, "surround-51")) { + map.channels = 6; + map.map[0] = PA_CHANNEL_POSITION_FRONT_LEFT; + map.map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT; + map.map[2] = PA_CHANNEL_POSITION_REAR_LEFT; + map.map[3] = PA_CHANNEL_POSITION_REAR_RIGHT; + map.map[4] = PA_CHANNEL_POSITION_FRONT_CENTER; + map.map[5] = PA_CHANNEL_POSITION_LFE; + goto finish; + } else if (pa_streq(s, "surround-71")) { + map.channels = 8; + map.map[0] = PA_CHANNEL_POSITION_FRONT_LEFT; + map.map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT; + map.map[2] = PA_CHANNEL_POSITION_REAR_LEFT; + map.map[3] = PA_CHANNEL_POSITION_REAR_RIGHT; + map.map[4] = PA_CHANNEL_POSITION_FRONT_CENTER; + map.map[5] = PA_CHANNEL_POSITION_LFE; + map.map[6] = PA_CHANNEL_POSITION_SIDE_LEFT; + map.map[7] = PA_CHANNEL_POSITION_SIDE_RIGHT; + goto finish; + } + + map.channels = 0; + + tokens = pw_split_strv(s, ",", INT_MAX, &n_tokens); + + for (i = 0; i < n_tokens; i++) { + pa_channel_position_t f; + + if (map.channels >= PA_CHANNELS_MAX) { + pw_free_strv(tokens); + return NULL; + } + + if ((f = pa_channel_position_from_string(tokens[i])) == PA_CHANNEL_POSITION_INVALID) { + pw_free_strv(tokens); + return NULL; + } + map.map[map.channels++] = f; + } + pw_free_strv(tokens); + +finish: + + if (!pa_channel_map_valid(&map)) + return NULL; + + *rmap = map; + return rmap; +} + +SPA_EXPORT +int pa_channel_map_valid(const pa_channel_map *map) { + unsigned c; + + pa_assert(map); + + if (!pa_channels_valid(map->channels)) + return 0; + + for (c = 0; c < map->channels; c++) + if (map->map[c] < 0 || map->map[c] >= PA_CHANNEL_POSITION_MAX) + return 0; + + return 1; +} + +SPA_EXPORT +int pa_channel_map_compatible(const pa_channel_map *map, const pa_sample_spec *ss) { + pa_assert(map); + pa_assert(ss); + + pa_return_val_if_fail(pa_channel_map_valid(map), 0); + pa_return_val_if_fail(pa_sample_spec_valid(ss), 0); + + return map->channels == ss->channels; +} + +SPA_EXPORT +int pa_channel_map_superset(const pa_channel_map *a, const pa_channel_map *b) { + pa_channel_position_mask_t am, bm; + + pa_assert(a); + pa_assert(b); + + pa_return_val_if_fail(pa_channel_map_valid(a), 0); + + if (PA_UNLIKELY(a == b)) + return 1; + + pa_return_val_if_fail(pa_channel_map_valid(b), 0); + + am = pa_channel_map_mask(a); + bm = pa_channel_map_mask(b); + + return (bm & am) == bm; +} + +SPA_EXPORT +int pa_channel_map_can_balance(const pa_channel_map *map) { + pa_channel_position_mask_t m; + + pa_assert(map); + pa_return_val_if_fail(pa_channel_map_valid(map), 0); + + m = pa_channel_map_mask(map); + + return + (PA_CHANNEL_POSITION_MASK_LEFT & m) && + (PA_CHANNEL_POSITION_MASK_RIGHT & m); +} + +SPA_EXPORT +int pa_channel_map_can_fade(const pa_channel_map *map) { + pa_channel_position_mask_t m; + + pa_assert(map); + pa_return_val_if_fail(pa_channel_map_valid(map), 0); + + m = pa_channel_map_mask(map); + + return + (PA_CHANNEL_POSITION_MASK_FRONT & m) && + (PA_CHANNEL_POSITION_MASK_REAR & m); +} + +SPA_EXPORT +int pa_channel_map_can_lfe_balance(const pa_channel_map *map) { + pa_channel_position_mask_t m; + + pa_assert(map); + pa_return_val_if_fail(pa_channel_map_valid(map), 0); + + m = pa_channel_map_mask(map); + + return + (PA_CHANNEL_POSITION_MASK_LFE & m) && + (PA_CHANNEL_POSITION_MASK_HFE & m); +} + +SPA_EXPORT +const char* pa_channel_map_to_name(const pa_channel_map *map) { + pa_bitset_t in_map[PA_BITSET_ELEMENTS(PA_CHANNEL_POSITION_MAX)]; + unsigned c; + + pa_assert(map); + + pa_return_val_if_fail(pa_channel_map_valid(map), NULL); + + memset(in_map, 0, sizeof(in_map)); + + for (c = 0; c < map->channels; c++) + pa_bitset_set(in_map, map->map[c], true); + + if (pa_bitset_equals(in_map, PA_CHANNEL_POSITION_MAX, + PA_CHANNEL_POSITION_MONO, -1)) + return "mono"; + + if (pa_bitset_equals(in_map, PA_CHANNEL_POSITION_MAX, + PA_CHANNEL_POSITION_LEFT, PA_CHANNEL_POSITION_RIGHT, -1)) + return "stereo"; + + if (pa_bitset_equals(in_map, PA_CHANNEL_POSITION_MAX, + PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, + PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, -1)) + return "surround-40"; + + if (pa_bitset_equals(in_map, PA_CHANNEL_POSITION_MAX, + PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, + PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, + PA_CHANNEL_POSITION_LFE, -1)) + return "surround-41"; + + if (pa_bitset_equals(in_map, PA_CHANNEL_POSITION_MAX, + PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, + PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, + PA_CHANNEL_POSITION_FRONT_CENTER, -1)) + return "surround-50"; + + if (pa_bitset_equals(in_map, PA_CHANNEL_POSITION_MAX, + PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, + PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, + PA_CHANNEL_POSITION_FRONT_CENTER, PA_CHANNEL_POSITION_LFE, -1)) + return "surround-51"; + + if (pa_bitset_equals(in_map, PA_CHANNEL_POSITION_MAX, + PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, + PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, + PA_CHANNEL_POSITION_FRONT_CENTER, PA_CHANNEL_POSITION_LFE, + PA_CHANNEL_POSITION_SIDE_LEFT, PA_CHANNEL_POSITION_SIDE_RIGHT, -1)) + return "surround-71"; + + return NULL; +} + +SPA_EXPORT +const char* pa_channel_map_to_pretty_name(const pa_channel_map *map) { + pa_bitset_t in_map[PA_BITSET_ELEMENTS(PA_CHANNEL_POSITION_MAX)]; + unsigned c; + + pa_assert(map); + + pa_return_val_if_fail(pa_channel_map_valid(map), NULL); + + memset(in_map, 0, sizeof(in_map)); + + for (c = 0; c < map->channels; c++) + pa_bitset_set(in_map, map->map[c], true); + + pa_init_i18n(); + + if (pa_bitset_equals(in_map, PA_CHANNEL_POSITION_MAX, + PA_CHANNEL_POSITION_MONO, -1)) + return _("Mono"); + + if (pa_bitset_equals(in_map, PA_CHANNEL_POSITION_MAX, + PA_CHANNEL_POSITION_LEFT, PA_CHANNEL_POSITION_RIGHT, -1)) + return _("Stereo"); + + if (pa_bitset_equals(in_map, PA_CHANNEL_POSITION_MAX, + PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, + PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, -1)) + return _("Surround 4.0"); + + if (pa_bitset_equals(in_map, PA_CHANNEL_POSITION_MAX, + PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, + PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, + PA_CHANNEL_POSITION_LFE, -1)) + return _("Surround 4.1"); + + if (pa_bitset_equals(in_map, PA_CHANNEL_POSITION_MAX, + PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, + PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, + PA_CHANNEL_POSITION_FRONT_CENTER, -1)) + return _("Surround 5.0"); + + if (pa_bitset_equals(in_map, PA_CHANNEL_POSITION_MAX, + PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, + PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, + PA_CHANNEL_POSITION_FRONT_CENTER, PA_CHANNEL_POSITION_LFE, -1)) + return _("Surround 5.1"); + + if (pa_bitset_equals(in_map, PA_CHANNEL_POSITION_MAX, + PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, + PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, + PA_CHANNEL_POSITION_FRONT_CENTER, PA_CHANNEL_POSITION_LFE, + PA_CHANNEL_POSITION_SIDE_LEFT, PA_CHANNEL_POSITION_SIDE_RIGHT, -1)) + return _("Surround 7.1"); + + return NULL; +} + +SPA_EXPORT +int pa_channel_map_has_position(const pa_channel_map *map, pa_channel_position_t p) { + unsigned c; + + pa_return_val_if_fail(pa_channel_map_valid(map), 0); + pa_return_val_if_fail(p < PA_CHANNEL_POSITION_MAX, 0); + + for (c = 0; c < map->channels; c++) + if (map->map[c] == p) + return 1; + + return 0; +} + +SPA_EXPORT +pa_channel_position_mask_t pa_channel_map_mask(const pa_channel_map *map) { + unsigned c; + pa_channel_position_mask_t r = 0; + + pa_return_val_if_fail(pa_channel_map_valid(map), 0); + + for (c = 0; c < map->channels; c++) + r |= PA_CHANNEL_POSITION_MASK(map->map[c]); + + return r; +} diff --git a/pipewire-pulseaudio/src/context.c b/pipewire-pulseaudio/src/context.c new file mode 100644 index 000000000..969803285 --- /dev/null +++ b/pipewire-pulseaudio/src/context.c @@ -0,0 +1,1282 @@ +/* PipeWire + * Copyright (C) 2018 Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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. + */ + +#include + +#include + +#include + +#include +#include +#include + +#include "internal.h" + +int pa_context_set_error(pa_context *c, int error) { + pa_assert(error >= 0); + pa_assert(error < PA_ERR_MAX); + if (c && c->error != error) { + pw_log_debug("context %p: error %d %s", c, error, pa_strerror(error)); + c->error = error; + } + return error; +} + +static void global_free(pa_context *c, struct global *g) +{ + spa_list_remove(&g->link); + + if (g->destroy) + g->destroy(g); + if (g->proxy) { + spa_hook_remove(&g->object_listener); + spa_hook_remove(&g->proxy_listener); + pw_proxy_destroy(g->proxy); + } + if (g->props) + pw_properties_free(g->props); + free(g); +} + +static void context_unlink(pa_context *c) +{ + pa_stream *s, *t; + struct global *g; + pa_operation *o; + + pw_log_debug("context %p: unlink %d", c, c->state); + + c->disconnect = true; + c->state_callback = NULL; + c->state_userdata = NULL; + + spa_list_for_each_safe(s, t, &c->streams, link) { + pa_stream_set_state(s, c->state == PA_CONTEXT_FAILED ? + PA_STREAM_FAILED : PA_STREAM_TERMINATED); + } + spa_list_consume(g, &c->globals, link) + global_free(c, g); + + spa_list_consume(o, &c->operations, link) + pa_operation_cancel(o); +} + +void pa_context_set_state(pa_context *c, pa_context_state_t st) { + pa_assert(c); + pa_assert(c->refcount >= 1); + + if (c->state == st) + return; + + pw_log_debug("context %p: state %d", c, st); + + pa_context_ref(c); + + c->state = st; + + if (c->state_callback) + c->state_callback(c, c->state_userdata); + + if (st == PA_CONTEXT_FAILED || st == PA_CONTEXT_TERMINATED) + context_unlink(c); + + pa_context_unref(c); +} + +static void context_fail(pa_context *c, int error) { + pa_assert(c); + pa_assert(c->refcount >= 1); + + pw_log_debug("context %p: error %d", c, error); + + pa_context_set_error(c, error); + pa_context_set_state(c, PA_CONTEXT_FAILED); +} + +SPA_EXPORT +pa_context *pa_context_new(pa_mainloop_api *mainloop, const char *name) +{ + return pa_context_new_with_proplist(mainloop, name, NULL); +} + +struct global *pa_context_find_global(pa_context *c, uint32_t id) +{ + struct global *g; + spa_list_for_each(g, &c->globals, link) { + if (g->id == id) + return g; + } + return NULL; +} + +struct global *pa_context_find_global_by_name(pa_context *c, uint32_t mask, const char *name) +{ + struct global *g; + const char *str; + uint32_t id = atoi(name); + + spa_list_for_each(g, &c->globals, link) { + if ((g->mask & mask) == 0) + continue; + if (g->props != NULL && + (str = pw_properties_get(g->props, PW_KEY_NODE_NAME)) != NULL && + strcmp(str, name) == 0) + return g; + if (g->id == id || (g->id == (id & PA_IDX_MASK_DSP))) + return g; + } + return NULL; +} + +struct global *pa_context_find_linked(pa_context *c, uint32_t idx) +{ + struct global *g, *f; + + spa_list_for_each(g, &c->globals, link) { + uint32_t src_node_id, dst_node_id; + + if (g->type != PW_TYPE_INTERFACE_Link) + continue; + + src_node_id = g->link_info.src->port_info.node_id; + dst_node_id = g->link_info.dst->port_info.node_id; + + pw_log_debug("context %p: %p %d %d %d", c, g, idx, + src_node_id, dst_node_id); + + if (src_node_id == idx) + f = pa_context_find_global(c, dst_node_id); + else if (dst_node_id == idx) + f = pa_context_find_global(c, src_node_id); + else + continue; + + if (f == NULL) + continue; + return f; + } + return NULL; +} + +static void emit_event(pa_context *c, struct global *g, pa_subscription_event_type_t event) +{ + if (c->subscribe_callback && (c->subscribe_mask & g->mask)) { + pw_log_debug("context %p: obj %d: emit %d:%d", c, g->id, event, g->event); + c->subscribe_callback(c, + event | g->event, + g->id, + c->subscribe_userdata); + + if (g->mask == (PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE)) { + pw_log_debug("context %p: obj %d: emit %d:%d", c, g->node_info.monitor, + event, PA_SUBSCRIPTION_EVENT_SOURCE); + c->subscribe_callback(c, + event | PA_SUBSCRIPTION_EVENT_SOURCE, + g->node_info.monitor, + c->subscribe_userdata); + } + } +} + +static void update_device_props(struct global *g) +{ + pa_card_info *i = &g->card_info.info; + const char *s; + + if ((s = pa_proplist_gets(i->proplist, PW_KEY_DEVICE_ICON_NAME))) + pa_proplist_sets(i->proplist, PA_PROP_DEVICE_ICON_NAME, s); +} + +static void device_event_info(void *object, const struct pw_device_info *info) +{ + struct global *g = object; + pa_card_info *i = &g->card_info.info; + const char *str; + uint32_t n; + + pw_log_debug("global %p: id:%d change-mask:%"PRIu64, g, g->id, info->change_mask); + info = g->info = pw_device_info_update(g->info, info); + + i->index = g->id; + i->name = info->props ? + spa_dict_lookup(info->props, PW_KEY_DEVICE_NAME) : "unknown"; + str = info->props ? spa_dict_lookup(info->props, PW_KEY_MODULE_ID) : NULL; + i->owner_module = str ? (unsigned)atoi(str) : SPA_ID_INVALID; + if (info->change_mask & PW_DEVICE_CHANGE_MASK_PROPS) { + i->driver = info->props ? + spa_dict_lookup(info->props, PW_KEY_DEVICE_API) : NULL; + + if (i->proplist) + pa_proplist_update_dict(i->proplist, info->props); + else { + i->proplist = pa_proplist_new_dict(info->props); + } + update_device_props(g); + } + if (info->change_mask & PW_DEVICE_CHANGE_MASK_PARAMS) { + for (n = 0; n < info->n_params; n++) { + if (!(info->params[n].flags & SPA_PARAM_INFO_READ)) + continue; + + switch (info->params[n].id) { + case SPA_PARAM_EnumProfile: + pw_device_proxy_enum_params((struct pw_device_proxy*)g->proxy, + 0, SPA_PARAM_EnumProfile, 0, -1, NULL); + break; + case SPA_PARAM_Profile: + pw_device_proxy_enum_params((struct pw_device_proxy*)g->proxy, + 0, SPA_PARAM_Profile, 0, -1, NULL); + break; + default: + break; + } + } + } + g->pending_seq = pw_proxy_sync(g->proxy, 0); +} + +static void device_event_param(void *object, int seq, + uint32_t id, uint32_t index, uint32_t next, + const struct spa_pod *param) +{ + struct global *g = object; + + switch (id) { + case SPA_PARAM_EnumProfile: + { + uint32_t id; + const char *name; + struct param *p; + + if (spa_pod_parse_object(param, + SPA_TYPE_OBJECT_ParamProfile, NULL, + SPA_PARAM_PROFILE_index, SPA_POD_Int(&id), + SPA_PARAM_PROFILE_name, SPA_POD_String(&name)) < 0) { + pw_log_warn("device %d: can't parse profile", g->id); + return; + } + p = malloc(sizeof(struct param) + SPA_POD_SIZE(param)); + if (p) { + p->id = id; + p->seq = seq; + p->param = SPA_MEMBER(p, sizeof(struct param), struct spa_pod); + memcpy(p->param, param, SPA_POD_SIZE(param)); + spa_list_append(&g->card_info.profiles, &p->link); + g->card_info.n_profiles++; + } + pw_log_debug("device %d: enum profile %d: \"%s\"", g->id, id, name); + break; + } + case SPA_PARAM_Profile: + { + uint32_t id; + if (spa_pod_parse_object(param, + SPA_TYPE_OBJECT_ParamProfile, NULL, + SPA_PARAM_PROFILE_index, SPA_POD_Int(&id)) < 0) { + pw_log_warn("device %d: can't parse profile", g->id); + return; + } + g->card_info.active_profile = id; + pw_log_debug("device %d: current profile %d", g->id, id); + break; + } + default: + break; + } +} + +static const struct pw_device_proxy_events device_events = { + PW_VERSION_DEVICE_PROXY_EVENTS, + .info = device_event_info, + .param = device_event_param, +}; + +static void device_destroy(void *data) +{ + struct global *global = data; + struct param *p; + + if (global->card_info.info.proplist) + pa_proplist_free(global->card_info.info.proplist); + spa_list_consume(p, &global->card_info.profiles, link) { + spa_list_remove(&p->link); + free(p); + } + if (global->info) + pw_device_info_free(global->info); +} + +static void node_event_info(void *object, const struct pw_node_info *info) +{ + struct global *g = object; + uint32_t i; + + pw_log_debug("update %d %"PRIu64, g->id, info->change_mask); + g->info = pw_node_info_update(g->info, info); + + if (info->change_mask & PW_NODE_CHANGE_MASK_PARAMS && !g->subscribed) { + uint32_t subscribed[32], n_subscribed = 0; + + for (i = 0; i < info->n_params; i++) { + switch (info->params[i].id) { + case SPA_PARAM_EnumFormat: + case SPA_PARAM_Props: + subscribed[n_subscribed++] = info->params[i].id; + break; + default: + break; + } + } + if (n_subscribed > 0) { + pw_node_proxy_subscribe_params((struct pw_node_proxy*)g->proxy, + subscribed, n_subscribed); + g->subscribed = true; + } + } + g->pending_seq = pw_proxy_sync(g->proxy, 0); +} + +static void parse_props(struct global *g, const struct spa_pod *param) +{ + struct spa_pod_prop *prop; + struct spa_pod_object *obj = (struct spa_pod_object *) param; + + SPA_POD_OBJECT_FOREACH(obj, prop) { + switch (prop->key) { + case SPA_PROP_volume: + spa_pod_get_float(&prop->value, &g->node_info.volume); + break; + case SPA_PROP_mute: + spa_pod_get_bool(&prop->value, &g->node_info.mute); + break; + case SPA_PROP_channelVolumes: + { + uint32_t n_vals; + + n_vals = spa_pod_copy_array(&prop->value, SPA_TYPE_Float, + g->node_info.channel_volumes, SPA_AUDIO_MAX_CHANNELS); + + if (n_vals != g->node_info.n_channel_volumes) { + emit_event(g->context, g, PA_SUBSCRIPTION_EVENT_REMOVE); + emit_event(g->context, g, PA_SUBSCRIPTION_EVENT_NEW); + g->node_info.n_channel_volumes = n_vals; + } + break; + } + default: + break; + } + } +} + +static void node_event_param(void *object, int seq, + uint32_t id, uint32_t index, uint32_t next, + const struct spa_pod *param) +{ + struct global *g = object; + pw_log_debug("update param %d %d", g->id, id); + + switch (id) { + case SPA_PARAM_Props: + parse_props(g, param); + break; + default: + break; + } +} + +static const struct pw_node_proxy_events node_events = { + PW_VERSION_NODE_PROXY_EVENTS, + .info = node_event_info, + .param = node_event_param, +}; + +static void node_destroy(void *data) +{ + struct global *global = data; + if (global->info) + pw_node_info_free(global->info); +} + +static void module_event_info(void *object, const struct pw_module_info *info) +{ + struct global *g = object; + pa_module_info *i = &g->module_info.info; + + pw_log_debug("update %d", g->id); + + info = g->info = pw_module_info_update(g->info, info); + + i->index = g->id; + if (info->change_mask & PW_MODULE_CHANGE_MASK_PROPS) { + if (i->proplist) + pa_proplist_update_dict(i->proplist, info->props); + else + i->proplist = pa_proplist_new_dict(info->props); + } + + i->name = info->name; + i->argument = info->args; + i->n_used = -1; + i->auto_unload = false; + g->pending_seq = pw_proxy_sync(g->proxy, 0); +} + +static const struct pw_module_proxy_events module_events = { + PW_VERSION_MODULE_PROXY_EVENTS, + .info = module_event_info, +}; + +static void module_destroy(void *data) +{ + struct global *global = data; + if (global->module_info.info.proplist) + pa_proplist_free(global->module_info.info.proplist); + if (global->info) + pw_module_info_free(global->info); +} + +static void client_event_info(void *object, const struct pw_client_info *info) +{ + struct global *g = object; + const char *str; + pa_client_info *i = &g->client_info.info; + + pw_log_debug("update %d", g->id); + info = g->info = pw_client_info_update(g->info, info); + + i->index = g->id; + str = info->props ? spa_dict_lookup(info->props, PW_KEY_MODULE_ID) : NULL; + i->owner_module = str ? (unsigned)atoi(str) : SPA_ID_INVALID; + + if (info->change_mask & PW_CLIENT_CHANGE_MASK_PROPS) { + if (i->proplist) + pa_proplist_update_dict(i->proplist, info->props); + else + i->proplist = pa_proplist_new_dict(info->props); + i->name = info->props ? + spa_dict_lookup(info->props, PW_KEY_APP_NAME) : NULL; + i->driver = info->props ? + spa_dict_lookup(info->props, PW_KEY_PROTOCOL) : NULL; + } + g->pending_seq = pw_proxy_sync(g->proxy, 0); +} + +static const struct pw_client_proxy_events client_events = { + PW_VERSION_CLIENT_PROXY_EVENTS, + .info = client_event_info, +}; + +static void client_destroy(void *data) +{ + struct global *global = data; + if (global->client_info.info.proplist) + pa_proplist_free(global->client_info.info.proplist); + if (global->info) + pw_client_info_free(global->info); +} + +static void proxy_destroy(void *data) +{ + struct global *g = data; + spa_hook_remove(&g->proxy_listener); + g->proxy = NULL; +} + +static void proxy_done(void *data, int seq) +{ + struct global *g = data; + pa_subscription_event_type_t event; + + if (g->pending_seq == seq) { + if (g->init) { + g->init = false; + event = PA_SUBSCRIPTION_EVENT_NEW; + } else { + event = PA_SUBSCRIPTION_EVENT_CHANGE; + } + emit_event(g->context, g, event); + } +} + +static const struct pw_proxy_events proxy_events = { + PW_VERSION_PROXY_EVENTS, + .destroy = proxy_destroy, + .done = proxy_done, +}; + +static int set_mask(pa_context *c, struct global *g) +{ + const char *str; + struct global *f; + const void *events = NULL; + pw_destroy_t destroy; + uint32_t client_version; + + switch (g->type) { + case PW_TYPE_INTERFACE_Device: + if (g->props == NULL) + return 0; + if ((str = pw_properties_get(g->props, PW_KEY_MEDIA_CLASS)) == NULL) + return 0; + if (strcmp(str, "Audio/Device") != 0) + return 0; + + pw_log_debug("found card %d", g->id); + g->mask = PA_SUBSCRIPTION_MASK_CARD; + g->event = PA_SUBSCRIPTION_EVENT_CARD; + + events = &device_events; + client_version = PW_VERSION_DEVICE_PROXY; + destroy = device_destroy; + spa_list_init(&g->card_info.profiles); + break; + + case PW_TYPE_INTERFACE_Node: + if (g->props == NULL) + return 0; + + if ((str = pw_properties_get(g->props, PW_KEY_PRIORITY_MASTER)) != NULL) + g->priority_master = pw_properties_parse_int(str); + + if ((str = pw_properties_get(g->props, PW_KEY_MEDIA_CLASS)) == NULL) { + pw_log_debug("node %d without "PW_KEY_MEDIA_CLASS, g->id); + return 0; + } + + if (strcmp(str, "Audio/Sink") == 0) { + pw_log_debug("found sink %d", g->id); + g->mask = PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE; + g->event = PA_SUBSCRIPTION_EVENT_SINK; + g->node_info.monitor = g->id | PA_IDX_FLAG_DSP; + } + else if (strcmp(str, "Audio/Source") == 0) { + pw_log_debug("found source %d", g->id); + g->mask = PA_SUBSCRIPTION_MASK_SOURCE; + g->event = PA_SUBSCRIPTION_EVENT_SOURCE; + } + else if (strcmp(str, "Stream/Output/Audio") == 0) { + pw_log_debug("found sink input %d", g->id); + g->mask = PA_SUBSCRIPTION_MASK_SINK_INPUT; + g->event = PA_SUBSCRIPTION_EVENT_SINK_INPUT; + } + else if (strcmp(str, "Stream/Input/Audio") == 0) { + pw_log_debug("found source output %d", g->id); + g->mask = PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT; + g->event = PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT; + } + + if ((str = pw_properties_get(g->props, PW_KEY_CLIENT_ID)) != NULL) + g->node_info.client_id = atoi(str); + + events = &node_events; + client_version = PW_VERSION_NODE_PROXY; + destroy = node_destroy; + g->node_info.volume = 1.0; + g->node_info.mute = false; + break; + + case PW_TYPE_INTERFACE_Module: + pw_log_debug("found module %d", g->id); + g->mask = PA_SUBSCRIPTION_MASK_MODULE; + g->event = PA_SUBSCRIPTION_EVENT_MODULE; + events = &module_events; + client_version = PW_VERSION_MODULE_PROXY; + destroy = module_destroy; + break; + + case PW_TYPE_INTERFACE_Client: + pw_log_debug("found client %d", g->id); + g->mask = PA_SUBSCRIPTION_MASK_CLIENT; + g->event = PA_SUBSCRIPTION_EVENT_CLIENT; + events = &client_events; + client_version = PW_VERSION_CLIENT_PROXY; + destroy = client_destroy; + break; + + case PW_TYPE_INTERFACE_Port: + if ((str = pw_properties_get(g->props, PW_KEY_NODE_ID)) != NULL) + g->port_info.node_id = atoi(str); + pw_log_debug("found port %d node %d", g->id, g->port_info.node_id); + break; + + case PW_TYPE_INTERFACE_Link: + if ((str = pw_properties_get(g->props, PW_KEY_LINK_OUTPUT_PORT)) == NULL) + return 0; + g->link_info.src = pa_context_find_global(c, pw_properties_parse_int(str)); + if ((str = pw_properties_get(g->props, PW_KEY_LINK_INPUT_PORT)) == NULL) + return 0; + g->link_info.dst = pa_context_find_global(c, pw_properties_parse_int(str)); + + if (g->link_info.src == NULL || g->link_info.dst == NULL) + return 0; + + pw_log_debug("link %d:%d->%d:%d", + g->link_info.src->port_info.node_id, + g->link_info.src->id, + g->link_info.dst->port_info.node_id, + g->link_info.dst->id); + + if ((f = pa_context_find_global(c, g->link_info.src->port_info.node_id)) != NULL && + !f->init) + emit_event(c, f, PA_SUBSCRIPTION_EVENT_CHANGE); + if ((f = pa_context_find_global(c, g->link_info.dst->port_info.node_id)) != NULL && + !f->init) + emit_event(c, f, PA_SUBSCRIPTION_EVENT_CHANGE); + + break; + + default: + return 0; + } + + pw_log_debug("global %p: id:%u mask %d/%d", g, g->id, g->mask, g->event); + + if (events) { + pw_log_debug("bind %d", g->id); + + g->proxy = pw_registry_proxy_bind(c->registry_proxy, g->id, g->type, + client_version, 0); + if (g->proxy == NULL) + return -ENOMEM; + + pw_proxy_add_object_listener(g->proxy, &g->object_listener, events, g); + pw_proxy_add_listener(g->proxy, &g->proxy_listener, &proxy_events, g); + g->destroy = destroy; + } else { + emit_event(c, g, PA_SUBSCRIPTION_EVENT_NEW); + } + + return 1; +} + +static inline void insert_global(pa_context *c, struct global *global) +{ + struct global *g, *t; + + spa_list_for_each_safe(g, t, &c->globals, link) { + if (g->priority_master < global->priority_master) { + g = g->link.prev; + break; + } + } + spa_list_prepend(&g->link, &global->link); +} + +static void registry_event_global(void *data, uint32_t id, + uint32_t permissions, uint32_t type, uint32_t version, + const struct spa_dict *props) +{ + pa_context *c = data; + struct global *g; + int res; + + g = calloc(1, sizeof(struct global)); + pw_log_debug("context %p: global %d %u %p", c, id, type, g); + g->context = c; + g->id = id; + g->type = type; + g->init = true; + g->props = props ? pw_properties_new_dict(props) : NULL; + + res = set_mask(c, g); + insert_global(c, g); + + if (res != 1) + global_free(c, g); +} + +static void registry_event_global_remove(void *object, uint32_t id) +{ + pa_context *c = object; + struct global *g; + + pw_log_debug("context %p: remove %d", c, id); + if ((g = pa_context_find_global(c, id)) == NULL) + return; + + emit_event(c, g, PA_SUBSCRIPTION_EVENT_REMOVE); + + pw_log_debug("context %p: free %d %p", c, id, g); + global_free(c, g); +} + +static const struct pw_registry_proxy_events registry_events = +{ + PW_VERSION_REGISTRY_PROXY_EVENTS, + .global = registry_event_global, + .global_remove = registry_event_global_remove, +}; + +static void complete_operations(pa_context *c, int seq) +{ + pa_operation *o, *t; + spa_list_for_each_safe(o, t, &c->operations, link) { + if (o->seq != seq) + continue; + pa_operation_ref(o); + if (o->callback) + o->callback(o, o->userdata); + pa_operation_unref(o); + } +} + +static void core_info(void *data, const struct pw_core_info *info) +{ + pa_context *c = data; + c->core_info = pw_core_info_update(c->core_info, info); +} + +static void core_done(void *data, uint32_t id, int seq) +{ + pa_context *c = data; + pw_log_debug("done %d", seq); + complete_operations(c, seq); +} + +static const struct pw_core_proxy_events core_events = { + PW_VERSION_CORE_EVENTS, + .info = core_info, + .done = core_done +}; + +static void remote_state_changed(void *data, enum pw_remote_state old, + enum pw_remote_state state, const char *error) +{ + pa_context *c = data; + + switch(state) { + case PW_REMOTE_STATE_ERROR: + if (c->core_proxy) { + spa_hook_remove(&c->core_listener); + c->core_proxy = NULL; + } + context_fail(c, PA_ERR_CONNECTIONTERMINATED); + break; + case PW_REMOTE_STATE_UNCONNECTED: + if (c->core_proxy) { + spa_hook_remove(&c->core_listener); + c->core_proxy = NULL; + } + if (!c->disconnect) + context_fail(c, PA_ERR_CONNECTIONTERMINATED); + break; + case PW_REMOTE_STATE_CONNECTING: + pa_context_set_state(c, PA_CONTEXT_CONNECTING); + break; + case PW_REMOTE_STATE_CONNECTED: + pa_context_set_state(c, PA_CONTEXT_AUTHORIZING); + pa_context_set_state(c, PA_CONTEXT_SETTING_NAME); + + c->core_proxy = pw_remote_get_core_proxy(c->remote); + pw_core_proxy_add_listener(c->core_proxy, &c->core_listener, &core_events, c); + + pa_context_set_state(c, PA_CONTEXT_READY); + break; + } +} + +static const struct pw_remote_events remote_events = { + PW_VERSION_REMOTE_EVENTS, + .state_changed = remote_state_changed, +}; + +struct success_data { + pa_context_success_cb_t cb; + void *userdata; + int ret; +}; + +static void on_success(pa_operation *o, void *userdata) +{ + struct success_data *d = userdata; + pa_context *c = o->context; + pa_operation_done(o); + if (d->cb) + d->cb(c, d->ret, d->userdata); +} + +SPA_EXPORT +pa_operation* pa_context_subscribe(pa_context *c, pa_subscription_mask_t m, pa_context_success_cb_t cb, void *userdata) +{ + pa_operation *o; + struct success_data *d; + + pa_assert(c); + pa_assert(c->refcount >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + + c->subscribe_mask = m; + + if (c->registry_proxy == NULL) { + c->registry_proxy = pw_core_proxy_get_registry(c->core_proxy, + PW_VERSION_REGISTRY_PROXY, 0); + pw_registry_proxy_add_listener(c->registry_proxy, + &c->registry_listener, + ®istry_events, c); + } + + o = pa_operation_new(c, NULL, on_success, sizeof(struct success_data)); + d = o->userdata; + d->ret = 0; + d->cb = cb; + d->userdata = userdata; + pa_operation_sync(o); + + return o; +} + +SPA_EXPORT +pa_context *pa_context_new_with_proplist(pa_mainloop_api *mainloop, const char *name, PA_CONST pa_proplist *p) +{ + struct pw_core *core; + struct pw_loop *loop; + struct pw_remote *r; + struct pw_properties *props; + pa_context *c; + + pa_assert(mainloop); + + props = pw_properties_new(NULL, NULL); + if (name) + pw_properties_set(props, PA_PROP_APPLICATION_NAME, name); + pw_properties_set(props, PW_KEY_CLIENT_API, "pulseaudio"); + if (p) + pw_properties_update(props, &p->props->dict); + + loop = mainloop->userdata; + core = pw_core_new(loop, NULL, 0); + + r = pw_remote_new(core, props, sizeof(struct pa_context)); + if (r == NULL) + return NULL; + + c = pw_remote_get_user_data(r); + c->loop = loop; + c->core = core; + c->remote = r; + + pw_remote_add_listener(r, &c->remote_listener, &remote_events, c); + + c->proplist = p ? pa_proplist_copy(p) : pa_proplist_new(); + c->refcount = 1; + c->client_index = PA_INVALID_INDEX; + + if (name) + pa_proplist_sets(c->proplist, PA_PROP_APPLICATION_NAME, name); + + c->mainloop = mainloop; + c->error = 0; + c->state = PA_CONTEXT_UNCONNECTED; + + spa_list_init(&c->globals); + + spa_list_init(&c->streams); + spa_list_init(&c->operations); + + return c; +} + +static void do_core_destroy(pa_mainloop_api*m, void *userdata) +{ + pa_context *c = userdata; + pw_core_destroy(c->core); +} + +static void context_free(pa_context *c) +{ + pw_log_debug("context %p: free", c); + + context_unlink(c); + + if (c->proplist) + pa_proplist_free(c->proplist); + if (c->core_info) + pw_core_info_free(c->core_info); + + pa_mainloop_api_once(c->mainloop, do_core_destroy, c); +} + +SPA_EXPORT +void pa_context_unref(pa_context *c) +{ + pa_assert(c); + pa_assert(c->refcount >= 1); + + if (--c->refcount == 0) + context_free(c); +} + +SPA_EXPORT +pa_context* pa_context_ref(pa_context *c) +{ + pa_assert(c); + pa_assert(c->refcount >= 1); + c->refcount++; + return c; +} + +SPA_EXPORT +void pa_context_set_state_callback(pa_context *c, pa_context_notify_cb_t cb, void *userdata) +{ + pa_assert(c); + pa_assert(c->refcount >= 1); + + if (c->state == PA_CONTEXT_TERMINATED || c->state == PA_CONTEXT_FAILED) + return; + + c->state_callback = cb; + c->state_userdata = userdata; +} + +SPA_EXPORT +void pa_context_set_event_callback(pa_context *c, pa_context_event_cb_t cb, void *userdata) +{ + pa_assert(c); + pa_assert(c->refcount >= 1); + + if (c->state == PA_CONTEXT_TERMINATED || c->state == PA_CONTEXT_FAILED) + return; + + c->event_callback = cb; + c->event_userdata = userdata; +} + +SPA_EXPORT +int pa_context_errno(PA_CONST pa_context *c) +{ + if (!c) + return PA_ERR_INVALID; + + pa_assert(c->refcount >= 1); + + return c->error; +} + +SPA_EXPORT +int pa_context_is_pending(PA_CONST pa_context *c) +{ + pa_assert(c); + pa_assert(c->refcount >= 1); + + PA_CHECK_VALIDITY(c, PA_CONTEXT_IS_GOOD(c->state), PA_ERR_BADSTATE); + + return !spa_list_is_empty(&c->operations); +} + +SPA_EXPORT +pa_context_state_t pa_context_get_state(PA_CONST pa_context *c) +{ + pa_assert(c); + pa_assert(c->refcount >= 1); + return c->state; +} + +SPA_EXPORT +int pa_context_connect(pa_context *c, const char *server, pa_context_flags_t flags, const pa_spawn_api *api) +{ + int res; + + pa_assert(c); + pa_assert(c->refcount >= 1); + + PA_CHECK_VALIDITY(c, c->state == PA_CONTEXT_UNCONNECTED, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY(c, !(flags & ~(PA_CONTEXT_NOAUTOSPAWN|PA_CONTEXT_NOFAIL)), PA_ERR_INVALID); + PA_CHECK_VALIDITY(c, !server || *server, PA_ERR_INVALID); + + pa_context_ref(c); + + c->no_fail = !!(flags & PA_CONTEXT_NOFAIL); + + res = pw_remote_connect(c->remote); + + pa_context_unref(c); + + return res; +} + +SPA_EXPORT +void pa_context_disconnect(pa_context *c) +{ + pa_assert(c); + pa_assert(c->refcount >= 1); + + c->disconnect = true; + pw_remote_disconnect(c->remote); + + if (PA_CONTEXT_IS_GOOD(c->state)) + pa_context_set_state(c, PA_CONTEXT_TERMINATED); +} + +struct notify_data { + pa_context_notify_cb_t cb; + void *userdata; +}; + +static void on_notify(pa_operation *o, void *userdata) +{ + struct notify_data *d = userdata; + pa_context *c = o->context; + pa_operation_done(o); + if (d->cb) + d->cb(c, d->userdata); +} + +SPA_EXPORT +pa_operation* pa_context_drain(pa_context *c, pa_context_notify_cb_t cb, void *userdata) +{ + pa_operation *o; + struct notify_data *d; + + o = pa_operation_new(c, NULL, on_notify, sizeof(struct notify_data)); + d = o->userdata; + d->cb = cb; + d->userdata = userdata; + pa_operation_sync(o); + + return o; +} + +SPA_EXPORT +pa_operation* pa_context_exit_daemon(pa_context *c, pa_context_success_cb_t cb, void *userdata) +{ + pa_operation *o; + struct success_data *d; + + o = pa_operation_new(c, NULL, on_success, sizeof(struct success_data)); + d = o->userdata; + d->ret = PA_ERR_ACCESS; + d->cb = cb; + d->userdata = userdata; + pa_operation_sync(o); + pw_log_warn("Not Implemented"); + + return o; +} + +SPA_EXPORT +pa_operation* pa_context_set_default_sink(pa_context *c, const char *name, pa_context_success_cb_t cb, void *userdata) +{ + pa_operation *o; + struct success_data *d; + + o = pa_operation_new(c, NULL, on_success, sizeof(struct success_data)); + d = o->userdata; + d->ret = PA_ERR_ACCESS; + d->cb = cb; + d->userdata = userdata; + pa_operation_sync(o); + pw_log_warn("Not Implemented"); + + return o; +} + +SPA_EXPORT +pa_operation* pa_context_set_default_source(pa_context *c, const char *name, pa_context_success_cb_t cb, void *userdata) +{ + pa_operation *o; + struct success_data *d; + + o = pa_operation_new(c, NULL, on_success, sizeof(struct success_data)); + d = o->userdata; + d->ret = PA_ERR_ACCESS; + d->cb = cb; + d->userdata = userdata; + pa_operation_sync(o); + pw_log_warn("Not Implemented"); + + return o; +} + +SPA_EXPORT +int pa_context_is_local(PA_CONST pa_context *c) +{ + pa_assert(c); + pa_assert(c->refcount >= 1); + + PA_CHECK_VALIDITY_RETURN_ANY(c, PA_CONTEXT_IS_GOOD(c->state), PA_ERR_BADSTATE, -1); + + return 1; +} + +SPA_EXPORT +pa_operation* pa_context_set_name(pa_context *c, const char *name, pa_context_success_cb_t cb, void *userdata) +{ + struct spa_dict dict; + struct spa_dict_item items[1]; + pa_operation *o; + struct success_data *d; + + pa_assert(c); + pa_assert(c->refcount >= 1); + pa_assert(name); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + + items[0] = SPA_DICT_ITEM_INIT(PA_PROP_APPLICATION_NAME, name); + dict = SPA_DICT_INIT(items, 1); + pw_remote_update_properties(c->remote, &dict); + + o = pa_operation_new(c, NULL, on_success, sizeof(struct success_data)); + d = o->userdata; + d->cb = cb; + d->userdata = userdata; + pa_operation_sync(o); + + return o; +} + +SPA_EXPORT +const char* pa_context_get_server(PA_CONST pa_context *c) +{ + const struct pw_core_info *info; + + pa_assert(c); + pa_assert(c->refcount >= 1); + + info = c->core_info; + PA_CHECK_VALIDITY_RETURN_NULL(c, info && info->name, PA_ERR_NOENTITY); + + return info->name; +} + +SPA_EXPORT +uint32_t pa_context_get_protocol_version(PA_CONST pa_context *c) +{ + return PA_PROTOCOL_VERSION; +} + +SPA_EXPORT +uint32_t pa_context_get_server_protocol_version(PA_CONST pa_context *c) +{ + pa_assert(c); + pa_assert(c->refcount >= 1); + + PA_CHECK_VALIDITY_RETURN_ANY(c, PA_CONTEXT_IS_GOOD(c->state), PA_ERR_BADSTATE, PA_INVALID_INDEX); + + return PA_PROTOCOL_VERSION; +} + +SPA_EXPORT +pa_operation *pa_context_proplist_update(pa_context *c, pa_update_mode_t mode, PA_CONST pa_proplist *p, pa_context_success_cb_t cb, void *userdata) +{ + pa_operation *o; + struct success_data *d; + + spa_assert(c); + spa_assert(c->refcount >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, mode == PA_UPDATE_SET || + mode == PA_UPDATE_MERGE || mode == PA_UPDATE_REPLACE, PA_ERR_INVALID); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + + pa_proplist_update(c->proplist, mode, p); + + o = pa_operation_new(c, NULL, on_success, sizeof(struct success_data)); + d = o->userdata; + d->cb = cb; + d->userdata = userdata; + pa_operation_sync(o); + return o; +} + +SPA_EXPORT +pa_operation *pa_context_proplist_remove(pa_context *c, const char *const keys[], pa_context_success_cb_t cb, void *userdata) +{ + pa_operation *o; + struct success_data *d; + + spa_assert(c); + spa_assert(c->refcount >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, keys && keys[0], PA_ERR_INVALID); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + + pw_log_warn("Not Implemented"); + + o = pa_operation_new(c, NULL, on_success, sizeof(struct success_data)); + d = o->userdata; + d->cb = cb; + d->userdata = userdata; + pa_operation_sync(o); + return o; +} + +SPA_EXPORT +uint32_t pa_context_get_index(PA_CONST pa_context *c) +{ + return c->client_index; +} + +SPA_EXPORT +pa_time_event* pa_context_rttime_new(PA_CONST pa_context *c, pa_usec_t usec, pa_time_event_cb_t cb, void *userdata) +{ + struct timeval tv; + + pa_assert(c); + pa_assert(c->refcount >= 1); + pa_assert(c->mainloop); + + if (usec == PA_USEC_INVALID) + return c->mainloop->time_new(c->mainloop, NULL, cb, userdata); + + pa_timeval_store(&tv, usec); + + return c->mainloop->time_new(c->mainloop, &tv, cb, userdata); +} + +SPA_EXPORT +void pa_context_rttime_restart(PA_CONST pa_context *c, pa_time_event *e, pa_usec_t usec) +{ + struct timeval tv; + + pa_assert(c); + pa_assert(c->refcount >= 1); + pa_assert(c->mainloop); + + if (usec == PA_USEC_INVALID) + c->mainloop->time_restart(e, NULL); + else { + pa_timeval_store(&tv, usec); + c->mainloop->time_restart(e, &tv); + } +} + +SPA_EXPORT +size_t pa_context_get_tile_size(PA_CONST pa_context *c, const pa_sample_spec *ss) +{ + size_t fs, mbs; + + pa_assert(c); + pa_assert(c->refcount >= 1); + + PA_CHECK_VALIDITY_RETURN_ANY(c, !ss || pa_sample_spec_valid(ss), PA_ERR_INVALID, (size_t) -1); + + fs = ss ? pa_frame_size(ss) : 1; + mbs = PA_ROUND_DOWN(4096, fs); + return PA_MAX(mbs, fs); +} + +SPA_EXPORT +int pa_context_load_cookie_from_file(pa_context *c, const char *cookie_file_path) +{ + return 0; +} diff --git a/pipewire-pulseaudio/src/core-format.c b/pipewire-pulseaudio/src/core-format.c new file mode 100644 index 000000000..4977a242d --- /dev/null +++ b/pipewire-pulseaudio/src/core-format.c @@ -0,0 +1,248 @@ +/*** + 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, see . +***/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "core-format.h" + +#include +#include + +#include "internal.h" + +SPA_EXPORT +int pa_format_info_get_sample_format(const pa_format_info *f, pa_sample_format_t *sf) { + int r; + char *sf_str; + pa_sample_format_t sf_local; + + pa_assert(f); + pa_assert(sf); + + r = pa_format_info_get_prop_string(f, PA_PROP_FORMAT_SAMPLE_FORMAT, &sf_str); + if (r < 0) + return r; + + sf_local = pa_parse_sample_format(sf_str); + pa_xfree(sf_str); + + if (!pa_sample_format_valid(sf_local)) { + pa_log_debug("Invalid sample format."); + return -PA_ERR_INVALID; + } + + *sf = sf_local; + + return 0; +} + +SPA_EXPORT +int pa_format_info_get_rate(const pa_format_info *f, uint32_t *rate) { + int r; + int rate_local; + + pa_assert(f); + pa_assert(rate); + + r = pa_format_info_get_prop_int(f, PA_PROP_FORMAT_RATE, &rate_local); + if (r < 0) + return r; + + if (!pa_sample_rate_valid(rate_local)) { + pa_log_debug("Invalid sample rate: %i", rate_local); + return -PA_ERR_INVALID; + } + + *rate = rate_local; + + return 0; +} + +SPA_EXPORT +int pa_format_info_get_channels(const pa_format_info *f, uint8_t *channels) { + int r; + int channels_local; + + pa_assert(f); + pa_assert(channels); + + r = pa_format_info_get_prop_int(f, PA_PROP_FORMAT_CHANNELS, &channels_local); + if (r < 0) + return r; + + if (!pa_channels_valid(channels_local)) { + pa_log_debug("Invalid channel count: %i", channels_local); + return -PA_ERR_INVALID; + } + + *channels = channels_local; + + return 0; +} + +SPA_EXPORT +int pa_format_info_get_channel_map(const pa_format_info *f, pa_channel_map *map) { + int r; + char *map_str; + + pa_assert(f); + pa_assert(map); + + r = pa_format_info_get_prop_string(f, PA_PROP_FORMAT_CHANNEL_MAP, &map_str); + if (r < 0) + return r; + + map = pa_channel_map_parse(map, map_str); + pa_xfree(map_str); + + if (!map) { + pa_log_debug("Failed to parse channel map."); + return -PA_ERR_INVALID; + } + + return 0; +} + +SPA_EXPORT +pa_format_info *pa_format_info_from_sample_spec2(const pa_sample_spec *ss, const pa_channel_map *map, bool set_format, + bool set_rate, bool set_channels) { + pa_format_info *format = NULL; + + pa_assert(ss); + + format = pa_format_info_new(); + format->encoding = PA_ENCODING_PCM; + + if (set_format) + pa_format_info_set_sample_format(format, ss->format); + + if (set_rate) + pa_format_info_set_rate(format, ss->rate); + + if (set_channels) { + pa_format_info_set_channels(format, ss->channels); + + if (map) { + if (map->channels != ss->channels) { + pa_log_debug("Channel map is incompatible with the sample spec."); + goto fail; + } + + pa_format_info_set_channel_map(format, map); + } + } + + return format; + +fail: + if (format) + pa_format_info_free(format); + + return NULL; +} + +SPA_EXPORT +int pa_format_info_to_sample_spec2(const pa_format_info *f, pa_sample_spec *ss, pa_channel_map *map, + const pa_sample_spec *fallback_ss, const pa_channel_map *fallback_map) { + int r, r2; + pa_sample_spec ss_local; + pa_channel_map map_local; + + pa_assert(f); + pa_assert(ss); + pa_assert(map); + pa_assert(fallback_ss); + pa_assert(fallback_map); + + if (!pa_format_info_is_pcm(f)) + return pa_format_info_to_sample_spec_fake(f, ss, map); + + r = pa_format_info_get_sample_format(f, &ss_local.format); + if (r == -PA_ERR_NOENTITY) + ss_local.format = fallback_ss->format; + else if (r < 0) + return r; + + pa_assert(pa_sample_format_valid(ss_local.format)); + + r = pa_format_info_get_rate(f, &ss_local.rate); + if (r == -PA_ERR_NOENTITY) + ss_local.rate = fallback_ss->rate; + else if (r < 0) + return r; + + pa_assert(pa_sample_rate_valid(ss_local.rate)); + + r = pa_format_info_get_channels(f, &ss_local.channels); + r2 = pa_format_info_get_channel_map(f, &map_local); + if (r == -PA_ERR_NOENTITY && r2 >= 0) + ss_local.channels = map_local.channels; + else if (r == -PA_ERR_NOENTITY) + ss_local.channels = fallback_ss->channels; + else if (r < 0) + return r; + + pa_assert(pa_channels_valid(ss_local.channels)); + + if (r2 >= 0 && map_local.channels != ss_local.channels) { + pa_log_debug("Channel map is not compatible with the sample spec."); + return -PA_ERR_INVALID; + } + + if (r2 == -PA_ERR_NOENTITY) { + if (fallback_map->channels == ss_local.channels) + map_local = *fallback_map; + else + pa_channel_map_init_extend(&map_local, ss_local.channels, PA_CHANNEL_MAP_DEFAULT); + } else if (r2 < 0) + return r2; + + pa_assert(pa_channel_map_valid(&map_local)); + pa_assert(ss_local.channels == map_local.channels); + + *ss = ss_local; + *map = map_local; + + return 0; +} + +SPA_EXPORT +int pa_format_info_to_sample_spec_fake(const pa_format_info *f, pa_sample_spec *ss, pa_channel_map *map) { + int rate; + + pa_assert(f); + pa_assert(ss); + + /* Note: When we add support for non-IEC61937 encapsulated compressed + * formats, this function should return a non-zero values for these. */ + + ss->format = PA_SAMPLE_S16LE; + ss->channels = 2; + + if (map) + pa_channel_map_init_stereo(map); + + pa_return_val_if_fail(pa_format_info_get_prop_int(f, PA_PROP_FORMAT_RATE, &rate) == 0, -PA_ERR_INVALID); + ss->rate = (uint32_t) rate; + + if (f->encoding == PA_ENCODING_EAC3_IEC61937) + ss->rate *= 4; + + return 0; +} diff --git a/pipewire-pulseaudio/src/core-format.h b/pipewire-pulseaudio/src/core-format.h new file mode 100644 index 000000000..37503041b --- /dev/null +++ b/pipewire-pulseaudio/src/core-format.h @@ -0,0 +1,79 @@ +#ifndef foocoreformathfoo +#define foocoreformathfoo + +/*** + 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, see . +***/ + +#include + +#include + +/* Gets the sample format stored in the format info. Returns a negative error + * code on failure. If the sample format property is not set at all, returns + * -PA_ERR_NOENTITY. */ +int pa_format_info_get_sample_format(const pa_format_info *f, pa_sample_format_t *sf); + +/* Gets the sample rate stored in the format info. Returns a negative error + * code on failure. If the sample rate property is not set at all, returns + * -PA_ERR_NOENTITY. */ +int pa_format_info_get_rate(const pa_format_info *f, uint32_t *rate); + +/* Gets the channel count stored in the format info. Returns a negative error + * code on failure. If the channels property is not set at all, returns + * -PA_ERR_NOENTITY. */ +int pa_format_info_get_channels(const pa_format_info *f, uint8_t *channels); + +/* Gets the channel map stored in the format info. Returns a negative error + * code on failure. If the channel map property is not set at all, returns + * -PA_ERR_NOENTITY. */ +int pa_format_info_get_channel_map(const pa_format_info *f, pa_channel_map *map); + +/* Convert a sample spec and an optional channel map to a new PCM format info + * object (remember to free it). If map is NULL, then the channel map will be + * left unspecified. If some fields of the sample spec should be ignored, pass + * false for set_format, set_rate and set_channels as appropriate, then those + * fields will be left unspecified. This function returns NULL if the input is + * invalid (for example, setting the sample rate was requested, but the rate + * in ss is invalid). + * + * pa_format_info_from_sample_spec() exists too. This "version 2" was created, + * because the original function doesn't provide the possibility of ignoring + * some of the sample spec fields. That functionality can't be added to the + * original function, because the function is a part of the public API and + * adding parameters to it would break the API. */ +pa_format_info *pa_format_info_from_sample_spec2(const pa_sample_spec *ss, const pa_channel_map *map, bool set_format, + bool set_rate, bool set_channels); + +/* Convert the format info into a sample spec and a channel map. If the format + * info doesn't contain some information, the fallback sample spec and channel + * map are used to populate the output. + * + * pa_format_info_to_sample_spec() exists too. This "version 2" was created, + * because the original function doesn't provide the possibility of specifying + * a fallback sample spec and channel map. That functionality can't be added to + * the original function, because the function is part of the public API and + * adding parameters to it would break the API. */ +int pa_format_info_to_sample_spec2(const pa_format_info *f, pa_sample_spec *ss, pa_channel_map *map, + const pa_sample_spec *fallback_ss, const pa_channel_map *fallback_map); + +/* For compressed formats. Converts the format info into a sample spec and a + * channel map that an ALSA device can use as its configuration parameters when + * playing back the compressed data. That is, the returned sample spec doesn't + * describe the audio content, but the device parameters. */ +int pa_format_info_to_sample_spec_fake(const pa_format_info *f, pa_sample_spec *ss, pa_channel_map *map); + +#endif diff --git a/pipewire-pulseaudio/src/direction.c b/pipewire-pulseaudio/src/direction.c new file mode 100644 index 000000000..d1dc6d722 --- /dev/null +++ b/pipewire-pulseaudio/src/direction.c @@ -0,0 +1,50 @@ +/* PipeWire + * Copyright (C) 2018 Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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. + */ + +#include + +#include +#include + +#define pa_init_i18n() +#define _(String) (String) + +SPA_EXPORT +int pa_direction_valid(pa_direction_t direction) +{ + if (direction != PA_DIRECTION_INPUT + && direction != PA_DIRECTION_OUTPUT + && direction != (PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT)) + return 0; + return 1; +} + +SPA_EXPORT +const char *pa_direction_to_string(pa_direction_t direction) { + pa_init_i18n(); + + if (direction == PA_DIRECTION_INPUT) + return _("input"); + if (direction == PA_DIRECTION_OUTPUT) + return _("output"); + if (direction == (PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT)) + return _("bidirectional"); + + return _("invalid"); +} diff --git a/pipewire-pulseaudio/src/error.c b/pipewire-pulseaudio/src/error.c new file mode 100644 index 000000000..abf05b634 --- /dev/null +++ b/pipewire-pulseaudio/src/error.c @@ -0,0 +1,75 @@ +/* PipeWire + * Copyright (C) 2018 Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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. + */ + +#include +#include + +#include + +#include +#include + +#define N_(String) (String) +#define _(String) (String) +#define pa_init_i18n() + +SPA_EXPORT +const char*pa_strerror(int error) +{ + static const char* const errortab[PA_ERR_MAX] = { + [PA_OK] = N_("OK"), + [PA_ERR_ACCESS] = N_("Access denied"), + [PA_ERR_COMMAND] = N_("Unknown command"), + [PA_ERR_INVALID] = N_("Invalid argument"), + [PA_ERR_EXIST] = N_("Entity exists"), + [PA_ERR_NOENTITY] = N_("No such entity"), + [PA_ERR_CONNECTIONREFUSED] = N_("Connection refused"), + [PA_ERR_PROTOCOL] = N_("Protocol error"), + [PA_ERR_TIMEOUT] = N_("Timeout"), + [PA_ERR_AUTHKEY] = N_("No authentication key"), + [PA_ERR_INTERNAL] = N_("Internal error"), + [PA_ERR_CONNECTIONTERMINATED] = N_("Connection terminated"), + [PA_ERR_KILLED] = N_("Entity killed"), + [PA_ERR_INVALIDSERVER] = N_("Invalid server"), + [PA_ERR_MODINITFAILED] = N_("Module initialization failed"), + [PA_ERR_BADSTATE] = N_("Bad state"), + [PA_ERR_NODATA] = N_("No data"), + [PA_ERR_VERSION] = N_("Incompatible protocol version"), + [PA_ERR_TOOLARGE] = N_("Too large"), + [PA_ERR_NOTSUPPORTED] = N_("Not supported"), + [PA_ERR_UNKNOWN] = N_("Unknown error code"), + [PA_ERR_NOEXTENSION] = N_("No such extension"), + [PA_ERR_OBSOLETE] = N_("Obsolete functionality"), + [PA_ERR_NOTIMPLEMENTED] = N_("Missing implementation"), + [PA_ERR_FORKED] = N_("Client forked"), + [PA_ERR_IO] = N_("Input/Output error"), + [PA_ERR_BUSY] = N_("Device or resource busy") + }; + + pa_init_i18n(); + + if (error < 0) + error = -error; + + if (error >= PA_ERR_MAX) + return NULL; + + return _(errortab[error]); +} + diff --git a/pipewire-pulseaudio/src/ext-device-manager.c b/pipewire-pulseaudio/src/ext-device-manager.c new file mode 100644 index 000000000..480afb308 --- /dev/null +++ b/pipewire-pulseaudio/src/ext-device-manager.c @@ -0,0 +1,113 @@ +/* PipeWire + * Copyright (C) 2018 Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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. + */ + +#include + +#include + +#include + +#include "internal.h" + + +SPA_EXPORT +pa_operation *pa_ext_device_manager_test( + pa_context *c, + pa_ext_device_manager_test_cb_t cb, + void *userdata) +{ + pw_log_warn("Not Implemented"); + return NULL; +} + +SPA_EXPORT +pa_operation *pa_ext_device_manager_read( + pa_context *c, + pa_ext_device_manager_read_cb_t cb, + void *userdata) +{ + pw_log_warn("Not Implemented"); + return NULL; +} + +SPA_EXPORT +pa_operation *pa_ext_device_manager_set_device_description( + pa_context *c, + const char* device, + const char* description, + pa_context_success_cb_t cb, + void *userdata) +{ + pw_log_warn("Not Implemented"); + return NULL; +} + +SPA_EXPORT +pa_operation *pa_ext_device_manager_delete( + pa_context *c, + const char *const s[], + pa_context_success_cb_t cb, + void *userdata) +{ + pw_log_warn("Not Implemented"); + return NULL; +} + +SPA_EXPORT +pa_operation *pa_ext_device_manager_enable_role_device_priority_routing( + pa_context *c, + int enable, + pa_context_success_cb_t cb, + void *userdata) +{ + pw_log_warn("Not Implemented"); + return NULL; +} + +SPA_EXPORT +pa_operation *pa_ext_device_manager_reorder_devices_for_role( + pa_context *c, + const char* role, + const char** devices, + pa_context_success_cb_t cb, + void *userdata) +{ + pw_log_warn("Not Implemented"); + return NULL; +} + +SPA_EXPORT +pa_operation *pa_ext_device_manager_subscribe( + pa_context *c, + int enable, + pa_context_success_cb_t cb, + void *userdata) +{ + pw_log_warn("Not Implemented"); + return NULL; +} + +SPA_EXPORT +void pa_ext_device_manager_set_subscribe_cb( + pa_context *c, + pa_ext_device_manager_subscribe_cb_t cb, + void *userdata) +{ + pw_log_warn("Not Implemented"); +} diff --git a/pipewire-pulseaudio/src/ext-device-restore.c b/pipewire-pulseaudio/src/ext-device-restore.c new file mode 100644 index 000000000..a2c0e686a --- /dev/null +++ b/pipewire-pulseaudio/src/ext-device-restore.c @@ -0,0 +1,194 @@ +/* PipeWire + * Copyright (C) 2018 Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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. + */ + +#include + +#include + +#include "internal.h" + +#define EXT_VERSION 1 + +struct ext_data { + pa_context *context; + pa_ext_device_restore_test_cb_t test_cb; + pa_ext_device_restore_read_device_formats_cb_t read_cb; + pa_context_success_cb_t success_cb; + void *userdata; +}; + +static void restore_test(pa_operation *o, void *userdata) +{ + struct ext_data *d = userdata; + if (d->test_cb) + d->test_cb(o->context, EXT_VERSION, d->userdata); + pa_operation_done(o); +} + +SPA_EXPORT +pa_operation *pa_ext_device_restore_test( + pa_context *c, + pa_ext_device_restore_test_cb_t cb, + void *userdata) +{ + pa_operation *o; + struct ext_data *d; + + pa_assert(c); + pa_assert(c->refcount >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + + o = pa_operation_new(c, NULL, restore_test, sizeof(struct ext_data)); + d = o->userdata; + d->context = c; + d->test_cb = cb; + d->userdata = userdata; + pa_operation_sync(o); + + return o; +} + +static void on_success(pa_operation *o, void *userdata) +{ + struct ext_data *d = userdata; + if (d->success_cb) + d->success_cb(o->context, PA_OK, d->userdata); + pa_operation_done(o); +} + +SPA_EXPORT +pa_operation *pa_ext_device_restore_subscribe( + pa_context *c, + int enable, + pa_context_success_cb_t cb, + void *userdata) +{ + pa_operation *o; + struct ext_data *d; + + pa_assert(c); + pa_assert(c->refcount >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + + o = pa_operation_new(c, NULL, on_success, sizeof(struct ext_data)); + d = o->userdata; + d->context = c; + d->success_cb = cb; + d->userdata = userdata; + pa_operation_sync(o); + + return o; +} + +SPA_EXPORT +void pa_ext_device_restore_set_subscribe_cb( + pa_context *c, + pa_ext_device_restore_subscribe_cb_t cb, + void *userdata) +{ + pw_log_warn("Not Implemented"); +} + +static void read_formats(pa_operation *o, void *userdata) +{ + struct ext_data *d = userdata; + if (d->read_cb) + d->read_cb(o->context, NULL, 1, d->userdata); + pa_operation_done(o); +} + +SPA_EXPORT +pa_operation *pa_ext_device_restore_read_formats_all( + pa_context *c, + pa_ext_device_restore_read_device_formats_cb_t cb, + void *userdata) +{ + pa_operation *o; + struct ext_data *d; + + pa_assert(c); + pa_assert(c->refcount >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + + o = pa_operation_new(c, NULL, read_formats, sizeof(struct ext_data)); + d = o->userdata; + d->context = c; + d->read_cb = cb; + d->userdata = userdata; + pa_operation_sync(o); + + return o; +} + +SPA_EXPORT +pa_operation *pa_ext_device_restore_read_formats( + pa_context *c, + pa_device_type_t type, + uint32_t idx, + pa_ext_device_restore_read_device_formats_cb_t cb, + void *userdata) +{ + pa_operation *o; + struct ext_data *d; + + pa_assert(c); + pa_assert(c->refcount >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + + o = pa_operation_new(c, NULL, read_formats, sizeof(struct ext_data)); + d = o->userdata; + d->context = c; + d->read_cb = cb; + d->userdata = userdata; + pa_operation_sync(o); + + return o; +} + +SPA_EXPORT +pa_operation *pa_ext_device_restore_save_formats( + pa_context *c, + pa_device_type_t type, + uint32_t idx, + uint8_t n_formats, + pa_format_info **formats, + pa_context_success_cb_t cb, + void *userdata) +{ + pa_operation *o; + struct ext_data *d; + + pa_assert(c); + pa_assert(c->refcount >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + + o = pa_operation_new(c, NULL, on_success, sizeof(struct ext_data)); + d = o->userdata; + d->context = c; + d->success_cb = cb; + d->userdata = userdata; + pa_operation_sync(o); + + return o; +} diff --git a/pipewire-pulseaudio/src/ext-stream-restore.c b/pipewire-pulseaudio/src/ext-stream-restore.c new file mode 100644 index 000000000..e5c977518 --- /dev/null +++ b/pipewire-pulseaudio/src/ext-stream-restore.c @@ -0,0 +1,201 @@ +/* PipeWire + * Copyright (C) 2018 Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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. + */ + +#include + +#include + +#include "internal.h" + +#define EXT_VERSION 1 + +struct stream_data { + pa_context *context; + pa_ext_stream_restore_test_cb_t test_cb; + pa_ext_stream_restore_read_cb_t read_cb; + pa_context_success_cb_t success_cb; + void *userdata; +}; + +static void restore_test(pa_operation *o, void *userdata) +{ + struct stream_data *d = userdata; + + if (d->test_cb) + d->test_cb(o->context, EXT_VERSION, d->userdata); + + pa_operation_done(o); +} + +SPA_EXPORT +pa_operation *pa_ext_stream_restore_test( + pa_context *c, + pa_ext_stream_restore_test_cb_t cb, + void *userdata) +{ + pa_operation *o; + struct stream_data *d; + + pa_assert(c); + pa_assert(c->refcount >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + + o = pa_operation_new(c, NULL, restore_test, sizeof(struct stream_data)); + d = o->userdata; + d->context = c; + d->test_cb = cb; + d->userdata = userdata; + pa_operation_sync(o); + + return o; +} + +static void restore_read(pa_operation *o, void *userdata) +{ + struct stream_data *d = userdata; + + if (d->read_cb) + d->read_cb(o->context, NULL, 1, d->userdata); + + pa_operation_done(o); +} + +SPA_EXPORT +pa_operation *pa_ext_stream_restore_read( + pa_context *c, + pa_ext_stream_restore_read_cb_t cb, + void *userdata) +{ + pa_operation *o; + struct stream_data *d; + + pa_assert(c); + pa_assert(c->refcount >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + + o = pa_operation_new(c, NULL, restore_read, sizeof(struct stream_data)); + d = o->userdata; + d->context = c; + d->read_cb = cb; + d->userdata = userdata; + pa_operation_sync(o); + + return o; +} + +static void on_success(pa_operation *o, void *userdata) +{ + struct stream_data *d = userdata; + if (d->success_cb) + d->success_cb(o->context, PA_OK, d->userdata); + pa_operation_done(o); +} + +SPA_EXPORT +pa_operation *pa_ext_stream_restore_write( + pa_context *c, + pa_update_mode_t mode, + const pa_ext_stream_restore_info data[], + unsigned n, + int apply_immediately, + pa_context_success_cb_t cb, + void *userdata) +{ + pa_operation *o; + struct stream_data *d; + + pa_assert(c); + pa_assert(c->refcount >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + + o = pa_operation_new(c, NULL, on_success, sizeof(struct stream_data)); + d = o->userdata; + d->context = c; + d->success_cb = cb; + d->userdata = userdata; + pa_operation_sync(o); + + return o; +} + +/** Delete entries from the stream database. \since 0.9.12 */ +SPA_EXPORT +pa_operation *pa_ext_stream_restore_delete( + pa_context *c, + const char *const s[], + pa_context_success_cb_t cb, + void *userdata) +{ + pa_operation *o; + struct stream_data *d; + + pa_assert(c); + pa_assert(c->refcount >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + + o = pa_operation_new(c, NULL, on_success, sizeof(struct stream_data)); + d = o->userdata; + d->context = c; + d->success_cb = cb; + d->userdata = userdata; + pa_operation_sync(o); + + return o; +} + +/** Subscribe to changes in the stream database. \since 0.9.12 */ +SPA_EXPORT +pa_operation *pa_ext_stream_restore_subscribe( + pa_context *c, + int enable, + pa_context_success_cb_t cb, + void *userdata) +{ + pa_operation *o; + struct stream_data *d; + + pa_assert(c); + pa_assert(c->refcount >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + + o = pa_operation_new(c, NULL, on_success, sizeof(struct stream_data)); + d = o->userdata; + d->context = c; + d->success_cb = cb; + d->userdata = userdata; + pa_operation_sync(o); + + return o; +} + +/** Set the subscription callback that is called when + * pa_ext_stream_restore_subscribe() was called. \since 0.9.12 */ +SPA_EXPORT +void pa_ext_stream_restore_set_subscribe_cb( + pa_context *c, + pa_ext_stream_restore_subscribe_cb_t cb, + void *userdata) +{ + pw_log_warn("Not Implemented"); +} diff --git a/pipewire-pulseaudio/src/format.c b/pipewire-pulseaudio/src/format.c new file mode 100644 index 000000000..a0160c360 --- /dev/null +++ b/pipewire-pulseaudio/src/format.c @@ -0,0 +1,707 @@ +/*** + This file is part of PulseAudio. + + Copyright 2011 Intel Corporation + Copyright 2011 Collabora Multimedia + Copyright 2011 Arun Raghavan + + 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, see . +***/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#include "core-format.h" +#include "json.h" +#include "internal.h" +#include "strbuf.h" + +#define PA_JSON_MIN_KEY "min" +#define PA_JSON_MAX_KEY "max" + +static int pa_format_info_prop_compatible(const char *one, const char *two); + +static const char* const _encoding_str_table[]= { + [PA_ENCODING_PCM] = "pcm", + [PA_ENCODING_AC3_IEC61937] = "ac3-iec61937", + [PA_ENCODING_EAC3_IEC61937] = "eac3-iec61937", + [PA_ENCODING_MPEG_IEC61937] = "mpeg-iec61937", + [PA_ENCODING_DTS_IEC61937] = "dts-iec61937", + [PA_ENCODING_MPEG2_AAC_IEC61937] = "mpeg2-aac-iec61937", + [PA_ENCODING_ANY] = "any", +}; + +SPA_EXPORT +const char *pa_encoding_to_string(pa_encoding_t e) { + if (e < 0 || e >= PA_ENCODING_MAX) + return NULL; + + return _encoding_str_table[e]; +} + +SPA_EXPORT +pa_encoding_t pa_encoding_from_string(const char *encoding) { + pa_encoding_t e; + + for (e = PA_ENCODING_ANY; e < PA_ENCODING_MAX; e++) + if (pa_streq(_encoding_str_table[e], encoding)) + return e; + + return PA_ENCODING_INVALID; +} + +SPA_EXPORT +pa_format_info* pa_format_info_new(void) { + pa_format_info *f = pa_xnew(pa_format_info, 1); + + f->encoding = PA_ENCODING_INVALID; + f->plist = pa_proplist_new(); + + return f; +} + +SPA_EXPORT +pa_format_info* pa_format_info_copy(const pa_format_info *src) { + pa_format_info *dest; + + pa_assert(src); + + dest = pa_xnew(pa_format_info, 1); + + dest->encoding = src->encoding; + + if (src->plist) + dest->plist = pa_proplist_copy(src->plist); + else + dest->plist = NULL; + + return dest; +} + +SPA_EXPORT +void pa_format_info_free(pa_format_info *f) { + pa_assert(f); + + pa_proplist_free(f->plist); + pa_xfree(f); +} + +SPA_EXPORT +int pa_format_info_valid(const pa_format_info *f) { + return (f->encoding >= 0 && f->encoding < PA_ENCODING_MAX && f->plist != NULL); +} + +SPA_EXPORT +int pa_format_info_is_pcm(const pa_format_info *f) { + return f->encoding == PA_ENCODING_PCM; +} + +SPA_EXPORT +char *pa_format_info_snprint(char *s, size_t l, const pa_format_info *f) { + char *tmp; + + pa_assert(s); + pa_assert(l > 0); + pa_assert(f); + + pa_init_i18n(); + + if (!pa_format_info_valid(f)) + pa_snprintf(s, l, _("(invalid)")); + else { + tmp = pa_proplist_to_string_sep(f->plist, " "); + if (tmp[0]) + pa_snprintf(s, l, "%s, %s", pa_encoding_to_string(f->encoding), tmp); + else + pa_snprintf(s, l, "%s", pa_encoding_to_string(f->encoding)); + pa_xfree(tmp); + } + + return s; +} + +SPA_EXPORT +pa_format_info* pa_format_info_from_string(const char *str) { + pa_format_info *f = pa_format_info_new(); + char *encoding = NULL, *properties = NULL; + size_t pos; + + pos = strcspn(str, ","); + + encoding = pa_xstrndup(str, pos); + f->encoding = pa_encoding_from_string(pa_strip(encoding)); + if (f->encoding == PA_ENCODING_INVALID) + goto error; + + if (pos != strlen(str)) { + pa_proplist *plist; + + properties = pa_xstrdup(&str[pos+1]); + plist = pa_proplist_from_string(properties); + + if (!plist) + goto error; + + pa_proplist_free(f->plist); + f->plist = plist; + } + +out: + if (encoding) + pa_xfree(encoding); + if (properties) + pa_xfree(properties); + return f; + +error: + pa_format_info_free(f); + f = NULL; + goto out; +} + +SPA_EXPORT +int pa_format_info_is_compatible(const pa_format_info *first, const pa_format_info *second) { + const char *key; + void *state = NULL; + + pa_assert(first); + pa_assert(second); + + if (first->encoding != second->encoding) + return false; + + while ((key = pa_proplist_iterate(first->plist, &state))) { + const char *value_one, *value_two; + + value_one = pa_proplist_gets(first->plist, key); + value_two = pa_proplist_gets(second->plist, key); + + if (!value_two || !pa_format_info_prop_compatible(value_one, value_two)) + return false; + } + + return true; +} + +SPA_EXPORT +pa_format_info* pa_format_info_from_sample_spec(const pa_sample_spec *ss, const pa_channel_map *map) { + char cm[PA_CHANNEL_MAP_SNPRINT_MAX]; + pa_format_info *f; + + pa_assert(ss && pa_sample_spec_valid(ss)); + pa_assert(!map || pa_channel_map_valid(map)); + + f = pa_format_info_new(); + f->encoding = PA_ENCODING_PCM; + + pa_format_info_set_sample_format(f, ss->format); + pa_format_info_set_rate(f, ss->rate); + pa_format_info_set_channels(f, ss->channels); + + if (map) { + pa_channel_map_snprint(cm, sizeof(cm), map); + pa_format_info_set_prop_string(f, PA_PROP_FORMAT_CHANNEL_MAP, cm); + } + + return f; +} + +/* For PCM streams */ +SPA_EXPORT +int pa_format_info_to_sample_spec(const pa_format_info *f, pa_sample_spec *ss, pa_channel_map *map) { + pa_assert(f); + pa_assert(ss); + + if (!pa_format_info_is_pcm(f)) + return pa_format_info_to_sample_spec_fake(f, ss, map); + + if (pa_format_info_get_sample_format(f, &ss->format) < 0) + return -PA_ERR_INVALID; + if (pa_format_info_get_rate(f, &ss->rate) < 0) + return -PA_ERR_INVALID; + if (pa_format_info_get_channels(f, &ss->channels) < 0) + return -PA_ERR_INVALID; + if (map && pa_format_info_get_channel_map(f, map) < 0) + return -PA_ERR_INVALID; + + return 0; +} + +SPA_EXPORT +pa_prop_type_t pa_format_info_get_prop_type(const pa_format_info *f, const char *key) { + const char *str; + pa_json_object *o; + const pa_json_object *o1; + pa_prop_type_t type; + + pa_assert(f); + pa_assert(key); + + str = pa_proplist_gets(f->plist, key); + if (!str) + return PA_PROP_TYPE_INVALID; + + o = pa_json_parse(str); + if (!o) + return PA_PROP_TYPE_INVALID; + + switch (pa_json_object_get_type(o)) { + case PA_JSON_TYPE_INT: + type = PA_PROP_TYPE_INT; + break; + + case PA_JSON_TYPE_STRING: + type = PA_PROP_TYPE_STRING; + break; + + case PA_JSON_TYPE_ARRAY: + if (pa_json_object_get_array_length(o) == 0) { + /* Unlikely, but let's account for this anyway. We need at + * least one element to figure out the array type. */ + type = PA_PROP_TYPE_INVALID; + break; + } + + o1 = pa_json_object_get_array_member(o, 0); + + if (pa_json_object_get_type(o1) == PA_JSON_TYPE_INT) + type = PA_PROP_TYPE_INT_ARRAY; + else if (pa_json_object_get_type(o1) == PA_JSON_TYPE_STRING) + type = PA_PROP_TYPE_STRING_ARRAY; + else + type = PA_PROP_TYPE_INVALID; + + break; + + case PA_JSON_TYPE_OBJECT: + /* We actually know at this point that it's a int range, but let's + * confirm. */ + if (!pa_json_object_get_object_member(o, PA_JSON_MIN_KEY)) { + type = PA_PROP_TYPE_INVALID; + break; + } + + if (!pa_json_object_get_object_member(o, PA_JSON_MAX_KEY)) { + type = PA_PROP_TYPE_INVALID; + break; + } + + type = PA_PROP_TYPE_INT_RANGE; + break; + + default: + type = PA_PROP_TYPE_INVALID; + break; + } + + pa_json_object_free(o); + return type; +} + +SPA_EXPORT +int pa_format_info_get_prop_int(const pa_format_info *f, const char *key, int *v) { + const char *str; + pa_json_object *o; + + pa_assert(f); + pa_assert(key); + pa_assert(v); + + str = pa_proplist_gets(f->plist, key); + if (!str) + return -PA_ERR_NOENTITY; + + o = pa_json_parse(str); + if (!o) { + pa_log_debug("Failed to parse format info property '%s'.", key); + return -PA_ERR_INVALID; + } + + if (pa_json_object_get_type(o) != PA_JSON_TYPE_INT) { + pa_log_debug("Format info property '%s' type is not int.", key); + pa_json_object_free(o); + return -PA_ERR_INVALID; + } + + *v = pa_json_object_get_int(o); + pa_json_object_free(o); + + return 0; +} + +SPA_EXPORT +int pa_format_info_get_prop_int_range(const pa_format_info *f, const char *key, int *min, int *max) { + const char *str; + pa_json_object *o; + const pa_json_object *o1; + int ret = -PA_ERR_INVALID; + + pa_assert(f); + pa_assert(key); + pa_assert(min); + pa_assert(max); + + str = pa_proplist_gets(f->plist, key); + if (!str) + return -PA_ERR_NOENTITY; + + o = pa_json_parse(str); + if (!o) { + pa_log_debug("Failed to parse format info property '%s'.", key); + return -PA_ERR_INVALID; + } + + if (pa_json_object_get_type(o) != PA_JSON_TYPE_OBJECT) + goto out; + + if (!(o1 = pa_json_object_get_object_member(o, PA_JSON_MIN_KEY)) || + (pa_json_object_get_type(o1) != PA_JSON_TYPE_INT)) + goto out; + + *min = pa_json_object_get_int(o1); + + if (!(o1 = pa_json_object_get_object_member(o, PA_JSON_MAX_KEY)) || + (pa_json_object_get_type(o1) != PA_JSON_TYPE_INT)) + goto out; + + *max = pa_json_object_get_int(o1); + + ret = 0; + +out: + if (ret < 0) + pa_log_debug("Format info property '%s' is not a valid int range.", key); + + pa_json_object_free(o); + return ret; +} + +SPA_EXPORT +int pa_format_info_get_prop_int_array(const pa_format_info *f, const char *key, int **values, int *n_values) { + const char *str; + pa_json_object *o; + const pa_json_object *o1; + int i, ret = -PA_ERR_INVALID; + + pa_assert(f); + pa_assert(key); + pa_assert(values); + pa_assert(n_values); + + str = pa_proplist_gets(f->plist, key); + if (!str) + return -PA_ERR_NOENTITY; + + o = pa_json_parse(str); + if (!o) { + pa_log_debug("Failed to parse format info property '%s'.", key); + return -PA_ERR_INVALID; + } + + if (pa_json_object_get_type(o) != PA_JSON_TYPE_ARRAY) + goto out; + + *n_values = pa_json_object_get_array_length(o); + *values = pa_xnew(int, *n_values); + + for (i = 0; i < *n_values; i++) { + o1 = pa_json_object_get_array_member(o, i); + + if (pa_json_object_get_type(o1) != PA_JSON_TYPE_INT) { + goto out; + } + + (*values)[i] = pa_json_object_get_int(o1); + } + + ret = 0; + +out: + if (ret < 0) + pa_log_debug("Format info property '%s' is not a valid int array.", key); + + pa_json_object_free(o); + return ret; +} + +SPA_EXPORT +int pa_format_info_get_prop_string(const pa_format_info *f, const char *key, char **v) { + const char *str = NULL; + pa_json_object *o; + + pa_assert(f); + pa_assert(key); + pa_assert(v); + + str = pa_proplist_gets(f->plist, key); + if (!str) + return -PA_ERR_NOENTITY; + + o = pa_json_parse(str); + if (!o) { + pa_log_debug("Failed to parse format info property '%s'.", key); + return -PA_ERR_INVALID; + } + + if (pa_json_object_get_type(o) != PA_JSON_TYPE_STRING) { + pa_log_debug("Format info property '%s' type is not string.", key); + pa_json_object_free(o); + return -PA_ERR_INVALID; + } + + *v = pa_xstrdup(pa_json_object_get_string(o)); + pa_json_object_free(o); + + return 0; +} + +SPA_EXPORT +int pa_format_info_get_prop_string_array(const pa_format_info *f, const char *key, char ***values, int *n_values) { + const char *str; + pa_json_object *o; + const pa_json_object *o1; + int i, ret = -PA_ERR_INVALID; + + pa_assert(f); + pa_assert(key); + pa_assert(values); + pa_assert(n_values); + + str = pa_proplist_gets(f->plist, key); + if (!str) + return -PA_ERR_NOENTITY; + + o = pa_json_parse(str); + if (!o) { + pa_log_debug("Failed to parse format info property '%s'.", key); + return -PA_ERR_INVALID; + } + + if (pa_json_object_get_type(o) != PA_JSON_TYPE_ARRAY) + goto out; + + *n_values = pa_json_object_get_array_length(o); + *values = pa_xnew(char *, *n_values); + + for (i = 0; i < *n_values; i++) { + o1 = pa_json_object_get_array_member(o, i); + + if (pa_json_object_get_type(o1) != PA_JSON_TYPE_STRING) { + goto out; + } + + (*values)[i] = pa_xstrdup(pa_json_object_get_string(o1)); + } + + ret = 0; + +out: + if (ret < 0) + pa_log_debug("Format info property '%s' is not a valid string array.", key); + + pa_json_object_free(o); + return ret; +} + +SPA_EXPORT +void pa_format_info_free_string_array(char **values, int n_values) { + int i; + + for (i = 0; i < n_values; i++) + pa_xfree(values[i]); + + pa_xfree(values); +} + +SPA_EXPORT +void pa_format_info_set_sample_format(pa_format_info *f, pa_sample_format_t sf) { + pa_format_info_set_prop_string(f, PA_PROP_FORMAT_SAMPLE_FORMAT, pa_sample_format_to_string(sf)); +} + +SPA_EXPORT +void pa_format_info_set_rate(pa_format_info *f, int rate) { + pa_format_info_set_prop_int(f, PA_PROP_FORMAT_RATE, rate); +} + +SPA_EXPORT +void pa_format_info_set_channels(pa_format_info *f, int channels) { + pa_format_info_set_prop_int(f, PA_PROP_FORMAT_CHANNELS, channels); +} + +SPA_EXPORT +void pa_format_info_set_channel_map(pa_format_info *f, const pa_channel_map *map) { + char map_str[PA_CHANNEL_MAP_SNPRINT_MAX]; + + pa_channel_map_snprint(map_str, sizeof(map_str), map); + + pa_format_info_set_prop_string(f, PA_PROP_FORMAT_CHANNEL_MAP, map_str); +} + +SPA_EXPORT +void pa_format_info_set_prop_int(pa_format_info *f, const char *key, int value) { + pa_assert(f); + pa_assert(key); + + pa_proplist_setf(f->plist, key, "%d", value); +} + +SPA_EXPORT +void pa_format_info_set_prop_int_array(pa_format_info *f, const char *key, const int *values, int n_values) { + pa_strbuf *buf; + char *str; + int i; + + pa_assert(f); + pa_assert(key); + pa_assert(n_values > 0); + + buf = pa_strbuf_new(); + + pa_strbuf_printf(buf, "[ %d", values[0]); + + for (i = 1; i < n_values; i++) + pa_strbuf_printf(buf, ", %d", values[i]); + + pa_strbuf_printf(buf, " ]"); + str = pa_strbuf_to_string_free(buf); + + pa_proplist_sets(f->plist, key, str); + pa_xfree (str); +} + +SPA_EXPORT +void pa_format_info_set_prop_int_range(pa_format_info *f, const char *key, int min, int max) { + pa_assert(f); + pa_assert(key); + + pa_proplist_setf(f->plist, key, "{ \"" PA_JSON_MIN_KEY "\": %d, \"" PA_JSON_MAX_KEY "\": %d }", + min, max); +} + +SPA_EXPORT +void pa_format_info_set_prop_string(pa_format_info *f, const char *key, const char *value) { + pa_assert(f); + pa_assert(key); + + pa_proplist_setf(f->plist, key, "\"%s\"", value); +} + +SPA_EXPORT +void pa_format_info_set_prop_string_array(pa_format_info *f, const char *key, const char **values, int n_values) { + pa_strbuf *buf; + char *str; + int i; + + pa_assert(f); + pa_assert(key); + + buf = pa_strbuf_new(); + + pa_strbuf_printf(buf, "[ \"%s\"", values[0]); + + for (i = 1; i < n_values; i++) + pa_strbuf_printf(buf, ", \"%s\"", values[i]); + + pa_strbuf_printf(buf, " ]"); + str = pa_strbuf_to_string_free(buf); + + pa_proplist_sets(f->plist, key, str); + pa_xfree (str); +} + +static bool pa_json_is_fixed_type(pa_json_object *o) { + switch(pa_json_object_get_type(o)) { + case PA_JSON_TYPE_OBJECT: + case PA_JSON_TYPE_ARRAY: + return false; + + default: + return true; + } +} + +static int pa_format_info_prop_compatible(const char *one, const char *two) { + pa_json_object *o1 = NULL, *o2 = NULL; + int i, ret = 0; + + o1 = pa_json_parse(one); + if (!o1) + goto out; + + o2 = pa_json_parse(two); + if (!o2) + goto out; + + /* We don't deal with both values being non-fixed - just because there is no immediate need (FIXME) */ + pa_return_val_if_fail(pa_json_is_fixed_type(o1) || pa_json_is_fixed_type(o2), false); + + if (pa_json_is_fixed_type(o1) && pa_json_is_fixed_type(o2)) { + ret = pa_json_object_equal(o1, o2); + goto out; + } + + if (pa_json_is_fixed_type(o1)) { + pa_json_object *tmp = o2; + o2 = o1; + o1 = tmp; + } + + /* o2 is now a fixed type, and o1 is not */ + + if (pa_json_object_get_type(o1) == PA_JSON_TYPE_ARRAY) { + for (i = 0; i < pa_json_object_get_array_length(o1); i++) { + if (pa_json_object_equal(pa_json_object_get_array_member(o1, i), o2)) { + ret = 1; + break; + } + } + } else if (pa_json_object_get_type(o1) == PA_JSON_TYPE_OBJECT) { + /* o1 should be a range type */ + int min, max, v; + const pa_json_object *o_min = NULL, *o_max = NULL; + + if (pa_json_object_get_type(o2) != PA_JSON_TYPE_INT) { + /* We don't support non-integer ranges */ + goto out; + } + + if (!(o_min = pa_json_object_get_object_member(o1, PA_JSON_MIN_KEY)) || + pa_json_object_get_type(o_min) != PA_JSON_TYPE_INT) + goto out; + + if (!(o_max = pa_json_object_get_object_member(o1, PA_JSON_MAX_KEY)) || + pa_json_object_get_type(o_max) != PA_JSON_TYPE_INT) + goto out; + + v = pa_json_object_get_int(o2); + min = pa_json_object_get_int(o_min); + max = pa_json_object_get_int(o_max); + + ret = v >= min && v <= max; + } else { + pa_log_warn("Got a format type that we don't support"); + } + +out: + if (o1) + pa_json_object_free(o1); + if (o2) + pa_json_object_free(o2); + + return ret; +} diff --git a/pipewire-pulseaudio/src/internal.h b/pipewire-pulseaudio/src/internal.h new file mode 100644 index 000000000..e407de66a --- /dev/null +++ b/pipewire-pulseaudio/src/internal.h @@ -0,0 +1,437 @@ +/* PipeWire + * Copyright (C) 2018 Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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. + */ + +#ifndef __PIPEWIRE_PULSEAUDIO_INTERNAL_H__ +#define __PIPEWIRE_PULSEAUDIO_INTERNAL_H__ + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +/* Some PulseAudio API added const qualifiers in 13.0 */ +#if PA_MAJOR >= 13 +#define PA_CONST const +#else +#define PA_CONST +#endif + +#define PA_MAX_FORMATS (PA_ENCODING_MAX) + +#ifdef __cplusplus +extern "C" { +#endif + +#define pa_streq(a,b) (!strcmp((a),(b))) +#define pa_strneq(a,b,n) (!strncmp((a),(b),(n))) + +#define PA_UNLIKELY SPA_UNLIKELY +#define PA_LIKELY SPA_LIKELY +#define PA_MIN SPA_MIN +#define PA_MAX SPA_MAX +#define pa_assert spa_assert +#define pa_assert_se spa_assert +#define pa_return_val_if_fail spa_return_val_if_fail +#define pa_assert_not_reached spa_assert_not_reached + +#define PA_INT_TYPE_SIGNED(type) (!!((type) 0 > (type) -1)) + +#define PA_INT_TYPE_HALF(type) ((type) 1 << (sizeof(type)*8 - 2)) + +#define PA_INT_TYPE_MAX(type) \ + ((type) (PA_INT_TYPE_SIGNED(type) \ + ? (PA_INT_TYPE_HALF(type) - 1 + PA_INT_TYPE_HALF(type)) \ + : (type) -1)) + +#define PA_INT_TYPE_MIN(type) \ + ((type) (PA_INT_TYPE_SIGNED(type) \ + ? (-1 - PA_INT_TYPE_MAX(type)) \ + : (type) 0)) + + +#ifdef __GNUC__ +#define PA_CLAMP_UNLIKELY(x, low, high) \ + __extension__ ({ \ + typeof(x) _x = (x); \ + typeof(low) _low = (low); \ + typeof(high) _high = (high); \ + (PA_UNLIKELY(_x > _high) ? _high : (PA_UNLIKELY(_x < _low) ? _low : _x)); \ + }) +#else +#define PA_CLAMP_UNLIKELY(x, low, high) (PA_UNLIKELY((x) > (high)) ? (high) : (PA_UNLIKELY((x) < (low)) ? (low) : (x))) +#endif + +#ifdef __GNUC__ +#define PA_ROUND_DOWN(a, b) \ + __extension__ ({ \ + typeof(a) _a = (a); \ + typeof(b) _b = (b); \ + (_a / _b) * _b; \ + }) +#else +#define PA_ROUND_DOWN(a, b) (((a) / (b)) * (b)) +#endif + + +#define pa_init_i18n() +#define _(String) (String) +#define N_(String) (String) + +#define pa_snprintf snprintf +#define pa_strip(n) pw_strip(n,"\n\r \t") + +#define pa_log pw_log_info +#define pa_log_debug pw_log_debug +#define pa_log_warn pw_log_warn + +static inline void* PA_ALIGN_PTR(const void *p) { + return (void*) (((size_t) p) & ~(sizeof(void*) - 1)); +} + +/* Rounds up */ +static inline size_t PA_ALIGN(size_t l) { + return ((l + sizeof(void*) - 1) & ~(sizeof(void*) - 1)); +} + +static inline const char *pa_strnull(const char *x) { + return x ? x : "(null)"; +} + +int pa_context_set_error(pa_context *c, int error); + +#define PA_CHECK_VALIDITY(context, expression, error) \ +do { \ + if (!(expression)) { \ + pw_log_trace("'%s' failed at %s:%u %s()", \ + #expression, __FILE__, __LINE__, __func__); \ + return -pa_context_set_error((context), (error)); \ + } \ +} while(false) + +#define PA_CHECK_VALIDITY_RETURN_ANY(context, expression, error, value) \ +do { \ + if (!(expression)) { \ + pw_log_trace("'%s' failed at %s:%u %s()", \ + #expression, __FILE__, __LINE__, __func__); \ + pa_context_set_error((context), (error)); \ + return value; \ + } \ +} while(false) + +#define PA_CHECK_VALIDITY_RETURN_NULL(context, expression, error) \ + PA_CHECK_VALIDITY_RETURN_ANY(context, expression, error, NULL) + +#define PA_FAIL(context, error) \ + do { \ + return -pa_context_set_error((context), (error)); \ + } while(false) + +#define PA_FAIL_RETURN_ANY(context, error, value) \ + do { \ + pa_context_set_error((context), (error)); \ + return value; \ + } while(false) + +#define PA_FAIL_RETURN_NULL(context, error) \ + PA_FAIL_RETURN_ANY(context, error, NULL) + +struct pa_proplist { + struct pw_properties *props; +}; + +pa_proplist* pa_proplist_new_props(struct pw_properties *props); +pa_proplist* pa_proplist_new_dict(struct spa_dict *dict); +int pa_proplist_update_dict(pa_proplist *p, struct spa_dict *dict); + +struct pa_io_event { + struct spa_source *source; + struct pa_mainloop *mainloop; + int fd; + pa_io_event_flags_t events; + pa_io_event_cb_t cb; + void *userdata; + pa_io_event_destroy_cb_t destroy; +}; + +struct pa_time_event { + struct spa_source *source; + struct pa_mainloop *mainloop; + pa_time_event_cb_t cb; + void *userdata; + pa_time_event_destroy_cb_t destroy; +}; + +struct pa_defer_event { + struct spa_source *source; + struct pa_mainloop *mainloop; + pa_defer_event_cb_t cb; + void *userdata; + pa_defer_event_destroy_cb_t destroy; +}; + +struct pa_mainloop { + struct pw_loop *loop; + struct spa_source *event; + + pa_mainloop_api api; + + bool quit; + int retval; + + int timeout; + int n_events; +}; + +struct param { + struct spa_list link; + uint32_t id; + int seq; + void *param; +}; + +#define PA_IDX_FLAG_DSP 0x800000U +#define PA_IDX_MASK_DSP 0x7fffffU + +struct global { + struct spa_list link; + uint32_t id; + uint32_t type; + struct pw_properties *props; + + pa_context *context; + pa_subscription_mask_t mask; + pa_subscription_event_type_t event; + + int priority_master; + int pending_seq; + int init:1; + int subscribed:1; + + void *info; + pw_destroy_t destroy; + + struct pw_proxy *proxy; + struct spa_hook proxy_listener; + struct spa_hook object_listener; + + union { + /* for links */ + struct { + struct global *src; + struct global *dst; + } link_info; + /* for sink/source */ + struct { + uint32_t client_id; + uint32_t monitor; + float volume; + bool mute; + uint32_t n_channel_volumes; + float channel_volumes[SPA_AUDIO_MAX_CHANNELS]; + } node_info; + struct { + uint32_t node_id; + } port_info; + /* for devices */ + struct { + struct spa_list profiles; + uint32_t n_profiles; + uint32_t active_profile; + pa_card_info info; + } card_info; + struct { + pa_module_info info; + } module_info; + struct { + pa_client_info info; + } client_info; + }; +}; + +struct pa_context { + int refcount; + uint32_t client_index; + + struct pw_loop *loop; + struct pw_core *core; + struct pw_remote *remote; + struct spa_hook remote_listener; + + struct pw_core_proxy *core_proxy; + struct spa_hook core_listener; + struct pw_core_info *core_info; + + struct pw_registry_proxy *registry_proxy; + struct spa_hook registry_listener; + + pa_proplist *proplist; + pa_mainloop_api *mainloop; + + int error; + pa_context_state_t state; + + pa_context_notify_cb_t state_callback; + void *state_userdata; + pa_context_event_cb_t event_callback; + void *event_userdata; + pa_context_subscribe_cb_t subscribe_callback; + void *subscribe_userdata; + pa_subscription_mask_t subscribe_mask; + + struct spa_list globals; + + struct spa_list streams; + struct spa_list operations; + + int no_fail:1; + int disconnect:1; +}; + +struct global *pa_context_find_global(pa_context *c, uint32_t id); +struct global *pa_context_find_global_by_name(pa_context *c, uint32_t mask, const char *name); +struct global *pa_context_find_linked(pa_context *c, uint32_t id); + +#define MAX_BUFFERS 64u +#define MASK_BUFFERS (MAX_BUFFERS-1) + +struct pa_stream { + struct spa_list link; + int refcount; + + struct pw_stream *stream; + struct spa_hook stream_listener; + + pa_context *context; + pa_proplist *proplist; + + pa_stream_direction_t direction; + pa_stream_state_t state; + pa_stream_flags_t flags; + bool disconnecting; + + pa_sample_spec sample_spec; + pa_channel_map channel_map; + uint8_t n_formats; + pa_format_info *req_formats[PA_MAX_FORMATS]; + pa_format_info *format; + + uint32_t stream_index; + + pa_buffer_attr buffer_attr; + + uint32_t device_index; + char *device_name; + + pa_timing_info timing_info; + + uint32_t direct_on_input; + + bool suspended:1; + bool corked:1; + bool timing_info_valid:1; + + pa_stream_notify_cb_t state_callback; + void *state_userdata; + pa_stream_request_cb_t read_callback; + void *read_userdata; + pa_stream_request_cb_t write_callback; + void *write_userdata; + pa_stream_notify_cb_t overflow_callback; + void *overflow_userdata; + pa_stream_notify_cb_t underflow_callback; + void *underflow_userdata; + pa_stream_notify_cb_t latency_update_callback; + void *latency_update_userdata; + pa_stream_notify_cb_t moved_callback; + void *moved_userdata; + pa_stream_notify_cb_t suspended_callback; + void *suspended_userdata; + pa_stream_notify_cb_t started_callback; + void *started_userdata; + pa_stream_event_cb_t event_callback; + void *event_userdata; + pa_stream_notify_cb_t buffer_attr_callback; + void *buffer_attr_userdata; + + int64_t offset; + + struct pw_buffer *dequeued[MAX_BUFFERS]; + struct spa_ringbuffer dequeued_ring; + size_t dequeued_size; + size_t maxsize; + struct spa_list pending; + + struct pw_buffer *buffer; + uint32_t buffer_index; + void *buffer_data; + uint32_t buffer_size; + uint32_t buffer_offset; + + uint32_t n_channel_volumes; + float channel_volumes[SPA_AUDIO_MAX_CHANNELS]; + bool mute; + pa_operation *drain; + uint64_t queued; +}; + +void pa_stream_set_state(pa_stream *s, pa_stream_state_t st); + +typedef void (*pa_operation_cb_t)(pa_operation *o, void *userdata); + +struct pa_operation +{ + struct spa_list link; + + int refcount; + pa_context *context; + pa_stream *stream; + + int seq; + pa_operation_state_t state; + + pa_operation_cb_t callback; + void *userdata; + + pa_operation_notify_cb_t state_callback; + void *state_userdata; +}; + + +pa_operation *pa_operation_new(pa_context *c, pa_stream *s, pa_operation_cb_t cb, size_t userdata_size); +void pa_operation_done(pa_operation *o); +int pa_operation_sync(pa_operation *o); + +#ifdef __cplusplus +} +#endif + +#endif /* __PIPEWIRE_PULSEAUDIO_INTERNAL_H__ */ diff --git a/pipewire-pulseaudio/src/introspect.c b/pipewire-pulseaudio/src/introspect.c new file mode 100644 index 000000000..419a0a069 --- /dev/null +++ b/pipewire-pulseaudio/src/introspect.c @@ -0,0 +1,2133 @@ +/* PipeWire + * Copyright (C) 2018 Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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. + */ + +#include + +#include + +#include + +#include + +#include "internal.h" + +struct success_ack { + pa_context_success_cb_t cb; + void *userdata; +}; + +static void on_success(pa_operation *o, void *userdata) +{ + struct success_ack *d = userdata; + pa_context *c = o->context; + if (d->cb) + d->cb(c, PA_OK, d->userdata); + pa_operation_done(o); +} + +struct sink_data { + pa_context *context; + pa_sink_info_cb_t cb; + void *userdata; + struct global *global; +}; + +static pa_sink_state_t node_state_to_sink(enum pw_node_state s) +{ + switch(s) { + case PW_NODE_STATE_ERROR: + return PA_SINK_UNLINKED; + case PW_NODE_STATE_CREATING: + return PA_SINK_INIT; + case PW_NODE_STATE_SUSPENDED: + return PA_SINK_SUSPENDED; + case PW_NODE_STATE_IDLE: + return PA_SINK_IDLE; + case PW_NODE_STATE_RUNNING: + return PA_SINK_RUNNING; + default: + return PA_SINK_INVALID_STATE; + } +} + +static int wait_global(pa_context *c, struct global *g, pa_operation *o) +{ + if (g->init) { + pa_operation_sync(o); + return -EBUSY; + } + return 0; +} + +static int wait_globals(pa_context *c, pa_subscription_mask_t mask, pa_operation *o) +{ + struct global *g; + spa_list_for_each(g, &c->globals, link) { + if (!(g->mask & mask)) + continue; + if (wait_global(c, g, o) < 0) + return -EBUSY; + } + return 0; +} + +static void sink_callback(struct sink_data *d) +{ + struct global *g = d->global; + struct pw_node_info *info = g->info; + const char *str; + uint32_t n; + pa_sink_info i; + pa_format_info ii[1]; + pa_format_info *ip[1]; + + spa_zero(i); + if (info->props && (str = spa_dict_lookup(info->props, PW_KEY_NODE_NAME))) + i.name = str; + else + i.name = "unknown"; + pw_log_debug("sink %d %s monitor %d", g->id, i.name, g->node_info.monitor); + i.index = g->id; + if (info->props && (str = spa_dict_lookup(info->props, PW_KEY_NODE_DESCRIPTION))) + i.description = str; + else + i.description = "unknown"; + + i.sample_spec.format = PA_SAMPLE_S16LE; + i.sample_spec.rate = 44100; + if (g->node_info.n_channel_volumes) + i.sample_spec.channels = g->node_info.n_channel_volumes; + else + i.sample_spec.channels = 2; + pa_channel_map_init_auto(&i.channel_map, i.sample_spec.channels, PA_CHANNEL_MAP_OSS); + i.owner_module = 0; + i.volume.channels = i.sample_spec.channels; + for (n = 0; n < i.volume.channels; n++) + i.volume.values[n] = g->node_info.volume * g->node_info.channel_volumes[n] * PA_VOLUME_NORM; + i.mute = g->node_info.mute; + i.monitor_source = g->node_info.monitor; + i.monitor_source_name = "unknown"; + i.latency = 0; + i.driver = "PipeWire"; + i.flags = PA_SINK_HARDWARE | + PA_SINK_HW_VOLUME_CTRL | PA_SINK_HW_MUTE_CTRL | + PA_SINK_LATENCY | PA_SINK_DYNAMIC_LATENCY | + PA_SINK_DECIBEL_VOLUME; + i.proplist = pa_proplist_new_dict(info->props); + i.configured_latency = 0; + i.base_volume = PA_VOLUME_NORM; + i.state = node_state_to_sink(info->state); + i.n_volume_steps = PA_VOLUME_NORM+1; + i.card = PA_INVALID_INDEX; + i.n_ports = 0; + i.ports = NULL; + i.active_port = NULL; + i.n_formats = 1; + ii[0].encoding = PA_ENCODING_PCM; + ii[0].plist = pa_proplist_new(); + ip[0] = ii; + i.formats = ip; + d->cb(d->context, &i, 0, d->userdata); + pa_proplist_free(i.proplist); + pa_proplist_free(ii[0].plist); +} + +static void sink_info(pa_operation *o, void *userdata) +{ + struct sink_data *d = userdata; + if (wait_global(d->context, d->global, o) < 0) + return; + sink_callback(d); + d->cb(d->context, NULL, 1, d->userdata); + pa_operation_done(o); +} + +SPA_EXPORT +pa_operation* pa_context_get_sink_info_by_name(pa_context *c, const char *name, pa_sink_info_cb_t cb, void *userdata) +{ + pa_operation *o; + struct global *g; + struct sink_data *d; + + pa_assert(c); + pa_assert(c->refcount >= 1); + pa_assert(cb); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, !name || *name, PA_ERR_INVALID); + + if ((g = pa_context_find_global_by_name(c, PA_SUBSCRIPTION_MASK_SINK, name)) == NULL) + return NULL; + + o = pa_operation_new(c, NULL, sink_info, sizeof(struct sink_data)); + d = o->userdata; + d->context = c; + d->cb = cb; + d->userdata = userdata; + d->global = g; + pa_operation_sync(o); + + return o; +} + +SPA_EXPORT +pa_operation* pa_context_get_sink_info_by_index(pa_context *c, uint32_t idx, pa_sink_info_cb_t cb, void *userdata) +{ + pa_operation *o; + struct global *g; + struct sink_data *d; + + pa_assert(c); + pa_assert(c->refcount >= 1); + pa_assert(cb); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, idx != PA_INVALID_INDEX, PA_ERR_INVALID); + + if ((g = pa_context_find_global(c, idx)) == NULL) + return NULL; + if (!(g->mask & PA_SUBSCRIPTION_MASK_SINK)) + return NULL; + + o = pa_operation_new(c, NULL, sink_info, sizeof(struct sink_data)); + d = o->userdata; + d->context = c; + d->cb = cb; + d->userdata = userdata; + d->global = g; + pa_operation_sync(o); + + return o; +} + +static void sink_info_list(pa_operation *o, void *userdata) +{ + struct sink_data *d = userdata; + pa_context *c = d->context; + struct global *g; + + if (wait_globals(c, PA_SUBSCRIPTION_MASK_SINK, o) < 0) + return; + spa_list_for_each(g, &c->globals, link) { + if (!(g->mask & PA_SUBSCRIPTION_MASK_SINK)) + continue; + d->global = g; + sink_callback(d); + } + d->cb(c, NULL, 1, d->userdata); + pa_operation_done(o); +} + +SPA_EXPORT +pa_operation* pa_context_get_sink_info_list(pa_context *c, pa_sink_info_cb_t cb, void *userdata) +{ + pa_operation *o; + struct sink_data *d; + + pa_assert(c); + pa_assert(c->refcount >= 1); + pa_assert(cb); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + + o = pa_operation_new(c, NULL, sink_info_list, sizeof(struct sink_data)); + d = o->userdata; + d->context = c; + d->cb = cb; + d->userdata = userdata; + pa_operation_sync(o); + + return o; +} + +static void set_stream_volume(pa_context *c, pa_stream *s, const pa_cvolume *volume, bool mute) +{ + uint32_t i, n_channel_volumes; + float channel_volumes[SPA_AUDIO_MAX_CHANNELS]; + float *vols; + + if (volume) { + for (i = 0; i < volume->channels; i++) + channel_volumes[i] = volume->values[i] / (float) PA_VOLUME_NORM; + vols = channel_volumes; + n_channel_volumes = volume->channels; + } else { + vols = s->channel_volumes; + n_channel_volumes = s->n_channel_volumes; + } + + if (n_channel_volumes != s->n_channel_volumes || + !memcmp(s->channel_volumes, vols, n_channel_volumes * sizeof(float)) || + s->mute != mute) { + float val = s->mute ? 1.0f : 0.0f; + pw_stream_set_control(s->stream, + SPA_PROP_mute, 1, &val, + SPA_PROP_channelVolumes, n_channel_volumes, vols, + 0); + } +} + +static void set_node_volume(pa_context *c, struct global *g, const pa_cvolume *volume, bool mute) +{ + char buf[1024]; + struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buf, sizeof(buf)); + uint32_t i, n_channel_volumes; + float channel_volumes[SPA_AUDIO_MAX_CHANNELS]; + float *vols; + + if (volume) { + for (i = 0; i < volume->channels; i++) + channel_volumes[i] = volume->values[i] / (float) PA_VOLUME_NORM; + vols = channel_volumes; + n_channel_volumes = volume->channels; + + if (n_channel_volumes == g->node_info.n_channel_volumes && + memcmp(g->node_info.channel_volumes, vols, n_channel_volumes * sizeof(float)) == 0 && + mute == g->node_info.mute) + return; + + memcpy(g->node_info.channel_volumes, vols, n_channel_volumes * sizeof(float)); + g->node_info.n_channel_volumes = n_channel_volumes; + } else { + n_channel_volumes = g->node_info.n_channel_volumes; + vols = g->node_info.channel_volumes; + if (mute == g->node_info.mute) + return; + } + g->node_info.mute = mute; + + pw_node_proxy_set_param((struct pw_node_proxy*)g->proxy, + SPA_PARAM_Props, 0, + spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_Props, SPA_PARAM_Props, + SPA_PROP_mute, SPA_POD_Bool(mute), + SPA_PROP_channelVolumes, SPA_POD_Array(sizeof(float), + SPA_TYPE_Float, + n_channel_volumes, + vols))); +} + + +SPA_EXPORT +pa_operation* pa_context_set_sink_volume_by_index(pa_context *c, uint32_t idx, const pa_cvolume *volume, pa_context_success_cb_t cb, void *userdata) +{ + pa_operation *o; + struct global *g; + struct success_ack *d; + + pa_assert(c); + pa_assert(c->refcount >= 1); + + pw_log_debug("context %p: index %d", c, idx); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, idx != PA_INVALID_INDEX, PA_ERR_INVALID); + PA_CHECK_VALIDITY_RETURN_NULL(c, pa_cvolume_valid(volume), PA_ERR_INVALID); + + if ((g = pa_context_find_global(c, idx)) == NULL) + return NULL; + if (!(g->mask & PA_SUBSCRIPTION_MASK_SINK)) + return NULL; + + set_node_volume(c, g, volume, g->node_info.mute); + + o = pa_operation_new(c, NULL, on_success, sizeof(struct success_ack)); + d = o->userdata; + d->cb = cb; + d->userdata = userdata; + pa_operation_sync(o); + return o; +} + +SPA_EXPORT +pa_operation* pa_context_set_sink_volume_by_name(pa_context *c, const char *name, const pa_cvolume *volume, pa_context_success_cb_t cb, void *userdata) +{ + pa_operation *o; + struct global *g; + struct success_ack *d; + + pa_assert(c); + pa_assert(c->refcount >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, !name || *name, PA_ERR_INVALID); + PA_CHECK_VALIDITY_RETURN_NULL(c, pa_cvolume_valid(volume), PA_ERR_INVALID); + + pw_log_debug("context %p: name %s", c, name); + + if ((g = pa_context_find_global_by_name(c, PA_SUBSCRIPTION_MASK_SINK, name)) == NULL) + return NULL; + + set_node_volume(c, g, volume, g->node_info.mute); + + o = pa_operation_new(c, NULL, on_success, sizeof(struct success_ack)); + d = o->userdata; + d->cb = cb; + d->userdata = userdata; + pa_operation_sync(o); + return o; +} + +SPA_EXPORT +pa_operation* pa_context_set_sink_mute_by_index(pa_context *c, uint32_t idx, int mute, pa_context_success_cb_t cb, void *userdata) +{ + pa_operation *o; + struct global *g; + struct success_ack *d; + + pa_assert(c); + pa_assert(c->refcount >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, idx != PA_INVALID_INDEX, PA_ERR_INVALID); + + pw_log_debug("context %p: index %d", c, idx); + + if ((g = pa_context_find_global(c, idx)) == NULL) + return NULL; + if (!(g->mask & PA_SUBSCRIPTION_MASK_SINK)) + return NULL; + + set_node_volume(c, g, NULL, mute); + + o = pa_operation_new(c, NULL, on_success, sizeof(struct success_ack)); + d = o->userdata; + d->cb = cb; + d->userdata = userdata; + pa_operation_sync(o); + return o; +} + +SPA_EXPORT +pa_operation* pa_context_set_sink_mute_by_name(pa_context *c, const char *name, int mute, pa_context_success_cb_t cb, void *userdata) +{ + pa_operation *o; + struct global *g; + struct success_ack *d; + + pa_assert(c); + pa_assert(c->refcount >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, !name || *name, PA_ERR_INVALID); + + pw_log_debug("context %p: name %s", c, name); + + if ((g = pa_context_find_global_by_name(c, PA_SUBSCRIPTION_MASK_SINK, name)) == NULL) + return NULL; + + set_node_volume(c, g, NULL, mute); + + o = pa_operation_new(c, NULL, on_success, sizeof(struct success_ack)); + d = o->userdata; + d->cb = cb; + d->userdata = userdata; + pa_operation_sync(o); + return o; +} + +SPA_EXPORT +pa_operation* pa_context_suspend_sink_by_name(pa_context *c, const char *sink_name, int suspend, pa_context_success_cb_t cb, void* userdata) +{ + pa_operation *o; + struct success_ack *d; + + o = pa_operation_new(c, NULL, on_success, sizeof(struct success_ack)); + d = o->userdata; + d->cb = cb; + d->userdata = userdata; + pa_operation_sync(o); + + pw_log_warn("Not Implemented"); + return o; +} + +SPA_EXPORT +pa_operation* pa_context_suspend_sink_by_index(pa_context *c, uint32_t idx, int suspend, pa_context_success_cb_t cb, void* userdata) +{ + pa_operation *o; + struct success_ack *d; + + o = pa_operation_new(c, NULL, on_success, sizeof(struct success_ack)); + d = o->userdata; + d->cb = cb; + d->userdata = userdata; + pa_operation_sync(o); + + pw_log_warn("Not Implemented"); + return o; +} + +SPA_EXPORT +pa_operation* pa_context_set_sink_port_by_index(pa_context *c, uint32_t idx, const char*port, pa_context_success_cb_t cb, void *userdata) +{ + pa_operation *o; + struct success_ack *d; + + o = pa_operation_new(c, NULL, on_success, sizeof(struct success_ack)); + d = o->userdata; + d->cb = cb; + d->userdata = userdata; + pa_operation_sync(o); + + pw_log_warn("Not Implemented"); + return o; +} + +SPA_EXPORT +pa_operation* pa_context_set_sink_port_by_name(pa_context *c, const char*name, const char*port, pa_context_success_cb_t cb, void *userdata) +{ + pa_operation *o; + struct success_ack *d; + + o = pa_operation_new(c, NULL, on_success, sizeof(struct success_ack)); + d = o->userdata; + d->cb = cb; + d->userdata = userdata; + pa_operation_sync(o); + + pw_log_warn("Not Implemented"); + return o; +} + + +struct source_data { + pa_context *context; + pa_source_info_cb_t cb; + void *userdata; + struct global *global; +}; + +static pa_source_state_t node_state_to_source(enum pw_node_state s) +{ + switch(s) { + case PW_NODE_STATE_ERROR: + return PA_SOURCE_UNLINKED; + case PW_NODE_STATE_CREATING: + return PA_SOURCE_INIT; + case PW_NODE_STATE_SUSPENDED: + return PA_SOURCE_SUSPENDED; + case PW_NODE_STATE_IDLE: + return PA_SOURCE_IDLE; + case PW_NODE_STATE_RUNNING: + return PA_SOURCE_RUNNING; + default: + return PA_SOURCE_INVALID_STATE; + } +} +static void source_callback(struct source_data *d) +{ + struct global *g = d->global; + struct pw_node_info *info = g->info; + const char *str; + uint32_t n; + pa_source_info i; + pa_format_info ii[1]; + pa_format_info *ip[1]; + enum pa_sink_flags flags; + + flags = PA_SOURCE_LATENCY | PA_SOURCE_DYNAMIC_LATENCY | + PA_SOURCE_DECIBEL_VOLUME; + + spa_zero(i); + if (info->props && (str = spa_dict_lookup(info->props, PW_KEY_NODE_NAME))) + i.name = str; + else + i.name = "unknown"; + i.index = g->id; + if (info->props && (str = spa_dict_lookup(info->props, PW_KEY_NODE_DESCRIPTION))) + i.description = str; + else + i.description = "unknown"; + i.sample_spec.format = PA_SAMPLE_S16LE; + i.sample_spec.rate = 44100; + if (g->node_info.n_channel_volumes) + i.sample_spec.channels = g->node_info.n_channel_volumes; + else + i.sample_spec.channels = 2; + pa_channel_map_init_auto(&i.channel_map, i.sample_spec.channels, PA_CHANNEL_MAP_OSS); + i.owner_module = 0; + i.volume.channels = i.sample_spec.channels; + for (n = 0; n < i.volume.channels; n++) + i.volume.values[n] = g->node_info.volume * g->node_info.channel_volumes[n] * PA_VOLUME_NORM; + i.mute = g->node_info.mute; + if (g->mask & PA_SUBSCRIPTION_MASK_SINK) { + i.monitor_of_sink = g->id; + i.monitor_of_sink_name = "unknown"; + i.index = g->node_info.monitor; + } else { + i.monitor_of_sink = PA_INVALID_INDEX; + i.monitor_of_sink_name = NULL; + flags |= PA_SOURCE_HARDWARE | PA_SOURCE_HW_VOLUME_CTRL | PA_SOURCE_HW_MUTE_CTRL; + } + i.latency = 0; + i.driver = "PipeWire"; + i.flags = flags; + i.proplist = pa_proplist_new_dict(info->props); + i.configured_latency = 0; + i.base_volume = PA_VOLUME_NORM; + i.state = node_state_to_source(info->state); + i.n_volume_steps = PA_VOLUME_NORM+1; + i.card = PA_INVALID_INDEX; + i.n_ports = 0; + i.ports = NULL; + i.active_port = NULL; + i.n_formats = 1; + ii[0].encoding = PA_ENCODING_PCM; + ii[0].plist = pa_proplist_new(); + ip[0] = ii; + i.formats = ip; + d->cb(d->context, &i, 0, d->userdata); + pa_proplist_free(i.proplist); + pa_proplist_free(ii[0].plist); +} + +static void source_info(pa_operation *o, void *userdata) +{ + struct source_data *d = userdata; + if (wait_global(d->context, d->global, o) < 0) + return; + source_callback(d); + d->cb(d->context, NULL, 1, d->userdata); + pa_operation_done(o); +} + +SPA_EXPORT +pa_operation* pa_context_get_source_info_by_name(pa_context *c, const char *name, pa_source_info_cb_t cb, void *userdata) +{ + pa_operation *o; + struct global *g; + struct source_data *d; + + pa_assert(c); + pa_assert(c->refcount >= 1); + pa_assert(cb); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, !name || *name, PA_ERR_INVALID); + + if ((g = pa_context_find_global_by_name(c, PA_SUBSCRIPTION_MASK_SOURCE, name)) == NULL) + return NULL; + + o = pa_operation_new(c, NULL, source_info, sizeof(struct source_data)); + d = o->userdata; + d->context = c; + d->cb = cb; + d->userdata = userdata; + d->global = g; + pa_operation_sync(o); + + return o; +} + +SPA_EXPORT +pa_operation* pa_context_get_source_info_by_index(pa_context *c, uint32_t idx, pa_source_info_cb_t cb, void *userdata) +{ + pa_operation *o; + struct global *g; + struct source_data *d; + + pa_assert(c); + pa_assert(c->refcount >= 1); + pa_assert(cb); + + PA_CHECK_VALIDITY_RETURN_NULL(c, idx != PA_INVALID_INDEX, PA_ERR_INVALID); + + pw_log_debug("context %p: index %d", c, idx); + + if (((g = pa_context_find_global(c, idx)) == NULL || + !(g->mask & PA_SUBSCRIPTION_MASK_SOURCE)) && + (((g = pa_context_find_global(c, idx & PA_IDX_MASK_DSP)) == NULL || + !(g->mask & PA_SUBSCRIPTION_MASK_SOURCE)))) + return NULL; + + o = pa_operation_new(c, NULL, source_info, sizeof(struct source_data)); + d = o->userdata; + d->context = c; + d->cb = cb; + d->userdata = userdata; + d->global = g; + pa_operation_sync(o); + + return o; +} + +static void source_info_list(pa_operation *o, void *userdata) +{ + struct source_data *d = userdata; + pa_context *c = d->context; + struct global *g; + + if (wait_globals(c, PA_SUBSCRIPTION_MASK_SOURCE, o) < 0) + return; + spa_list_for_each(g, &c->globals, link) { + if (!(g->mask & PA_SUBSCRIPTION_MASK_SOURCE)) + continue; + d->global = g; + source_callback(d); + } + d->cb(c, NULL, 1, d->userdata); + pa_operation_done(o); +} + +SPA_EXPORT +pa_operation* pa_context_get_source_info_list(pa_context *c, pa_source_info_cb_t cb, void *userdata) +{ + pa_operation *o; + struct source_data *d; + + pa_assert(c); + pa_assert(c->refcount >= 1); + pa_assert(cb); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + + o = pa_operation_new(c, NULL, source_info_list, sizeof(struct source_data)); + d = o->userdata; + d->context = c; + d->cb = cb; + d->userdata = userdata; + pa_operation_sync(o); + + return o; +} + +SPA_EXPORT +pa_operation* pa_context_set_source_volume_by_index(pa_context *c, uint32_t idx, const pa_cvolume *volume, pa_context_success_cb_t cb, void *userdata) +{ + pa_operation *o; + struct global *g; + struct success_ack *d; + + pa_assert(c); + pa_assert(c->refcount >= 1); + + pw_log_debug("context %p: index %d", c, idx); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, idx != PA_INVALID_INDEX, PA_ERR_INVALID); + PA_CHECK_VALIDITY_RETURN_NULL(c, pa_cvolume_valid(volume), PA_ERR_INVALID); + + if ((g = pa_context_find_global(c, idx)) == NULL) + return NULL; + if (!(g->mask & PA_SUBSCRIPTION_MASK_SOURCE)) + return NULL; + + set_node_volume(c, g, volume, g->node_info.mute); + + o = pa_operation_new(c, NULL, on_success, sizeof(struct success_ack)); + d = o->userdata; + d->cb = cb; + d->userdata = userdata; + pa_operation_sync(o); + return o; +} + +SPA_EXPORT +pa_operation* pa_context_set_source_volume_by_name(pa_context *c, const char *name, const pa_cvolume *volume, pa_context_success_cb_t cb, void *userdata) +{ + pa_operation *o; + struct global *g; + struct success_ack *d; + + pa_assert(c); + pa_assert(c->refcount >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, !name || *name, PA_ERR_INVALID); + PA_CHECK_VALIDITY_RETURN_NULL(c, pa_cvolume_valid(volume), PA_ERR_INVALID); + + pw_log_debug("context %p: name %s", c, name); + + if ((g = pa_context_find_global_by_name(c, PA_SUBSCRIPTION_MASK_SOURCE, name)) == NULL) + return NULL; + + set_node_volume(c, g, volume, g->node_info.mute); + + o = pa_operation_new(c, NULL, on_success, sizeof(struct success_ack)); + d = o->userdata; + d->cb = cb; + d->userdata = userdata; + pa_operation_sync(o); + return o; +} + +SPA_EXPORT +pa_operation* pa_context_set_source_mute_by_index(pa_context *c, uint32_t idx, int mute, pa_context_success_cb_t cb, void *userdata) +{ + pa_operation *o; + struct global *g; + struct success_ack *d; + + pa_assert(c); + pa_assert(c->refcount >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, idx != PA_INVALID_INDEX, PA_ERR_INVALID); + + pw_log_debug("context %p: index %d", c, idx); + + if ((g = pa_context_find_global(c, idx)) == NULL) + return NULL; + if (!(g->mask & PA_SUBSCRIPTION_MASK_SOURCE)) + return NULL; + + set_node_volume(c, g, NULL, mute); + + o = pa_operation_new(c, NULL, on_success, sizeof(struct success_ack)); + d = o->userdata; + d->cb = cb; + d->userdata = userdata; + pa_operation_sync(o); + return o; +} + +SPA_EXPORT +pa_operation* pa_context_set_source_mute_by_name(pa_context *c, const char *name, int mute, pa_context_success_cb_t cb, void *userdata) +{ + pa_operation *o; + struct global *g; + struct success_ack *d; + + pa_assert(c); + pa_assert(c->refcount >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, !name || *name, PA_ERR_INVALID); + + pw_log_debug("context %p: name %s", c, name); + + if ((g = pa_context_find_global_by_name(c, PA_SUBSCRIPTION_MASK_SOURCE, name)) == NULL) + return NULL; + + set_node_volume(c, g, NULL, mute); + + o = pa_operation_new(c, NULL, on_success, sizeof(struct success_ack)); + d = o->userdata; + d->cb = cb; + d->userdata = userdata; + pa_operation_sync(o); + return o; +} + +SPA_EXPORT +pa_operation* pa_context_suspend_source_by_name(pa_context *c, const char *source_name, int suspend, pa_context_success_cb_t cb, void* userdata) +{ + pa_operation *o; + struct success_ack *d; + + o = pa_operation_new(c, NULL, on_success, sizeof(struct success_ack)); + d = o->userdata; + d->cb = cb; + d->userdata = userdata; + pa_operation_sync(o); + + pw_log_warn("Not Implemented"); + return o; +} + +SPA_EXPORT +pa_operation* pa_context_suspend_source_by_index(pa_context *c, uint32_t idx, int suspend, pa_context_success_cb_t cb, void* userdata) +{ + pa_operation *o; + struct success_ack *d; + + o = pa_operation_new(c, NULL, on_success, sizeof(struct success_ack)); + d = o->userdata; + d->cb = cb; + d->userdata = userdata; + pa_operation_sync(o); + + pw_log_warn("Not Implemented"); + return o; +} + +SPA_EXPORT +pa_operation* pa_context_set_source_port_by_index(pa_context *c, uint32_t idx, const char*port, pa_context_success_cb_t cb, void *userdata) +{ + pa_operation *o; + struct success_ack *d; + + o = pa_operation_new(c, NULL, on_success, sizeof(struct success_ack)); + d = o->userdata; + d->cb = cb; + d->userdata = userdata; + pa_operation_sync(o); + + pw_log_warn("Not Implemented"); + return o; +} + +SPA_EXPORT +pa_operation* pa_context_set_source_port_by_name(pa_context *c, const char*name, const char*port, pa_context_success_cb_t cb, void *userdata) +{ + pa_operation *o; + struct success_ack *d; + + o = pa_operation_new(c, NULL, on_success, sizeof(struct success_ack)); + d = o->userdata; + d->cb = cb; + d->userdata = userdata; + pa_operation_sync(o); + + pw_log_warn("Not Implemented"); + return o; +} + +struct server_data { + pa_context *context; + pa_server_info_cb_t cb; + void *userdata; + struct global *global; +}; + +static void server_callback(struct server_data *d) +{ + pa_context *c = d->context; + const struct pw_core_info *info = c->core_info; + pa_server_info i; + + spa_zero(i); + i.user_name = info->user_name; + i.host_name = info->host_name; + i.server_version = info->version; + i.server_name = info->name; + i.sample_spec.format = PA_SAMPLE_S16LE; + i.sample_spec.rate = 44100; + i.sample_spec.channels = 2; + i.default_sink_name = "unknown"; + i.default_source_name = "unknown"; + i.cookie = info->cookie; + pa_channel_map_init_extend(&i.channel_map, i.sample_spec.channels, PA_CHANNEL_MAP_OSS); + d->cb(d->context, &i, d->userdata); +} + +static void server_info(pa_operation *o, void *userdata) +{ + struct server_data *d = userdata; + server_callback(d); + pa_operation_done(o); +} + +SPA_EXPORT +pa_operation* pa_context_get_server_info(pa_context *c, pa_server_info_cb_t cb, void *userdata) +{ + pa_operation *o; + struct server_data *d; + + pa_assert(c); + pa_assert(c->refcount >= 1); + pa_assert(cb); + + o = pa_operation_new(c, NULL, server_info, sizeof(struct server_data)); + d = o->userdata; + d->context = c; + d->cb = cb; + d->userdata = userdata; + pa_operation_sync(o); + + return o; +} + +struct module_data { + pa_context *context; + pa_module_info_cb_t cb; + void *userdata; + struct global *global; +}; + +static void module_callback(struct module_data *d) +{ + struct global *g = d->global; + d->cb(d->context, &g->module_info.info, 0, d->userdata); +} + +static void module_info(pa_operation *o, void *userdata) +{ + struct module_data *d = userdata; + module_callback(d); + d->cb(d->context, NULL, 1, d->userdata); + pa_operation_done(o); +} + +SPA_EXPORT +pa_operation* pa_context_get_module_info(pa_context *c, uint32_t idx, pa_module_info_cb_t cb, void *userdata) +{ + pa_operation *o; + struct global *g; + struct module_data *d; + + pa_assert(c); + pa_assert(c->refcount >= 1); + pa_assert(cb); + + PA_CHECK_VALIDITY_RETURN_NULL(c, idx != PA_INVALID_INDEX, PA_ERR_INVALID); + + if ((g = pa_context_find_global(c, idx)) == NULL) + return NULL; + if (!(g->mask & PA_SUBSCRIPTION_MASK_MODULE)) + return NULL; + + o = pa_operation_new(c, NULL, module_info, sizeof(struct module_data)); + d = o->userdata; + d->context = c; + d->cb = cb; + d->userdata = userdata; + d->global = g; + pa_operation_sync(o); + + return o; +} + +static void module_info_list(pa_operation *o, void *userdata) +{ + struct module_data *d = userdata; + pa_context *c = d->context; + struct global *g; + + spa_list_for_each(g, &c->globals, link) { + if (!(g->mask & PA_SUBSCRIPTION_MASK_MODULE)) + continue; + d->global = g; + module_callback(d); + } + d->cb(c, NULL, 1, d->userdata); + pa_operation_done(o); +} + +SPA_EXPORT +pa_operation* pa_context_get_module_info_list(pa_context *c, pa_module_info_cb_t cb, void *userdata) +{ + pa_operation *o; + struct module_data *d; + + pa_assert(c); + pa_assert(c->refcount >= 1); + pa_assert(cb); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + + o = pa_operation_new(c, NULL, module_info_list, sizeof(struct module_data)); + d = o->userdata; + d->context = c; + d->cb = cb; + d->userdata = userdata; + pa_operation_sync(o); + + return o; +} + +SPA_EXPORT +pa_operation* pa_context_load_module(pa_context *c, const char*name, const char *argument, pa_context_index_cb_t cb, void *userdata) +{ + pw_log_warn("Not Implemented"); + return NULL; +} + +SPA_EXPORT +pa_operation* pa_context_unload_module(pa_context *c, uint32_t idx, pa_context_success_cb_t cb, void *userdata) +{ + pa_operation *o; + struct success_ack *d; + + o = pa_operation_new(c, NULL, on_success, sizeof(struct success_ack)); + d = o->userdata; + d->cb = cb; + d->userdata = userdata; + pa_operation_sync(o); + + pw_log_warn("Not Implemented"); + return o; +} + +struct client_data { + pa_context *context; + pa_client_info_cb_t cb; + void *userdata; + struct global *global; +}; + +static void client_callback(struct client_data *d) +{ + struct global *g = d->global; + d->cb(d->context, &g->client_info.info, 0, d->userdata); +} + +static void client_info(pa_operation *o, void *userdata) +{ + struct client_data *d = userdata; + client_callback(d); + d->cb(d->context, NULL, 1, d->userdata); + pa_operation_done(o); +} + +SPA_EXPORT +pa_operation* pa_context_get_client_info(pa_context *c, uint32_t idx, pa_client_info_cb_t cb, void *userdata) +{ + pa_operation *o; + struct global *g; + struct client_data *d; + + pa_assert(c); + pa_assert(c->refcount >= 1); + pa_assert(cb); + + PA_CHECK_VALIDITY_RETURN_NULL(c, idx != PA_INVALID_INDEX, PA_ERR_INVALID); + + if ((g = pa_context_find_global(c, idx)) == NULL) + return NULL; + if (!(g->mask & PA_SUBSCRIPTION_MASK_CLIENT)) + return NULL; + + o = pa_operation_new(c, NULL, client_info, sizeof(struct client_data)); + d = o->userdata; + d->context = c; + d->cb = cb; + d->userdata = userdata; + d->global = g; + pa_operation_sync(o); + + return o; +} + +static void client_info_list(pa_operation *o, void *userdata) +{ + struct client_data *d = userdata; + pa_context *c = d->context; + struct global *g; + + spa_list_for_each(g, &c->globals, link) { + if (!(g->mask & PA_SUBSCRIPTION_MASK_CLIENT)) + continue; + d->global = g; + client_callback(d); + } + d->cb(c, NULL, 1, d->userdata); + pa_operation_done(o); +} + +SPA_EXPORT +pa_operation* pa_context_get_client_info_list(pa_context *c, pa_client_info_cb_t cb, void *userdata) +{ + pa_operation *o; + struct client_data *d; + + pa_assert(c); + pa_assert(c->refcount >= 1); + pa_assert(cb); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + + o = pa_operation_new(c, NULL, client_info_list, sizeof(struct client_data)); + d = o->userdata; + d->context = c; + d->cb = cb; + d->userdata = userdata; + pa_operation_sync(o); + + return o; +} + +SPA_EXPORT +pa_operation* pa_context_kill_client(pa_context *c, uint32_t idx, pa_context_success_cb_t cb, void *userdata) +{ + struct global *g; + pa_operation *o; + struct success_ack *d; + + PA_CHECK_VALIDITY_RETURN_NULL(c, idx != PA_INVALID_INDEX, PA_ERR_INVALID); + + if ((g = pa_context_find_global(c, idx)) == NULL) + return NULL; + if (!(g->mask & PA_SUBSCRIPTION_MASK_CLIENT)) + return NULL; + + pw_registry_proxy_destroy(c->registry_proxy, g->id); + + o = pa_operation_new(c, NULL, on_success, sizeof(struct success_ack)); + d = o->userdata; + d->cb = cb; + d->userdata = userdata; + pa_operation_sync(o); + + return o; +} + +struct card_data { + pa_context *context; + pa_card_info_cb_t cb; + pa_context_success_cb_t success_cb; + void *userdata; + struct global *global; + char *profile; +}; + +static void card_callback(struct card_data *d) +{ + struct global *g = d->global; + pa_card_info *i = &g->card_info.info; + int n_profiles, j; + struct param *p; + + n_profiles = g->card_info.n_profiles; + + i->profiles = alloca(sizeof(pa_card_profile_info) * n_profiles); + i->profiles2 = alloca(sizeof(pa_card_profile_info2 *) * n_profiles); + i->n_profiles = 0; + + pw_log_debug("context %p: info for %d", g->context, g->id); + + spa_list_for_each(p, &g->card_info.profiles, link) { + uint32_t id; + const char *name; + + if (spa_pod_parse_object(p->param, + SPA_TYPE_OBJECT_ParamProfile, NULL, + SPA_PARAM_PROFILE_index, SPA_POD_Int(&id), + SPA_PARAM_PROFILE_name, SPA_POD_String(&name)) < 0) { + pw_log_warn("device %d: can't parse profile", g->id); + continue; + } + + j = i->n_profiles++; + i->profiles[j].name = name; + i->profiles[j].description = name; + i->profiles[j].n_sinks = 1; + i->profiles[j].n_sources = 1; + i->profiles[j].priority = 1; + + i->profiles2[j] = alloca(sizeof(pa_card_profile_info2)); + i->profiles2[j]->name = i->profiles[j].name; + i->profiles2[j]->description = i->profiles[j].description; + i->profiles2[j]->n_sinks = i->profiles[j].n_sinks; + i->profiles2[j]->n_sources = i->profiles[j].n_sources; + i->profiles2[j]->priority = i->profiles[j].priority; + i->profiles2[j]->available = 1; + + if (g->card_info.active_profile == id) { + i->active_profile = &i->profiles[j]; + i->active_profile2 = i->profiles2[j]; + } + } + d->cb(d->context, i, 0, d->userdata); +} + +static void card_info(pa_operation *o, void *userdata) +{ + struct card_data *d = userdata; + if (wait_global(d->context, d->global, o) < 0) + return; + card_callback(d); + d->cb(d->context, NULL, 1, d->userdata); + pa_operation_done(o); +} + +SPA_EXPORT +pa_operation* pa_context_get_card_info_by_index(pa_context *c, uint32_t idx, pa_card_info_cb_t cb, void *userdata) +{ + pa_operation *o; + struct global *g; + struct card_data *d; + + pa_assert(c); + pa_assert(c->refcount >= 1); + pa_assert(cb); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, idx != PA_INVALID_INDEX, PA_ERR_INVALID); + + pw_log_debug("context %p: %u", c, idx); + + if ((g = pa_context_find_global(c, idx)) == NULL) + return NULL; + if (!(g->mask & PA_SUBSCRIPTION_MASK_CARD)) + return NULL; + + o = pa_operation_new(c, NULL, card_info, sizeof(struct card_data)); + d = o->userdata; + d->context = c; + d->cb = cb; + d->userdata = userdata; + d->global = g; + pa_operation_sync(o); + return o; +} + +SPA_EXPORT +pa_operation* pa_context_get_card_info_by_name(pa_context *c, const char *name, pa_card_info_cb_t cb, void *userdata) +{ + pa_operation *o; + struct global *g; + struct card_data *d; + + pa_assert(c); + pa_assert(c->refcount >= 1); + pa_assert(cb); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, !name || *name, PA_ERR_INVALID); + + pw_log_debug("context %p: %s", c, name); + + if ((g = pa_context_find_global_by_name(c, PA_SUBSCRIPTION_MASK_CARD, name)) == NULL) + return NULL; + + o = pa_operation_new(c, NULL, card_info, sizeof(struct card_data)); + d = o->userdata; + d->context = c; + d->cb = cb; + d->userdata = userdata; + d->global = g; + pa_operation_sync(o); + return o; +} + +static void card_info_list(pa_operation *o, void *userdata) +{ + struct card_data *d = userdata; + pa_context *c = d->context; + struct global *g; + + if (wait_globals(c, PA_SUBSCRIPTION_MASK_CARD, o) < 0) + return; + spa_list_for_each(g, &c->globals, link) { + if (!(g->mask & PA_SUBSCRIPTION_MASK_CARD)) + continue; + d->global = g; + card_callback(d); + } + d->cb(c, NULL, 1, d->userdata); + pa_operation_done(o); +} + +SPA_EXPORT +pa_operation* pa_context_get_card_info_list(pa_context *c, pa_card_info_cb_t cb, void *userdata) +{ + pa_operation *o; + struct card_data *d; + + pa_assert(c); + pa_assert(c->refcount >= 1); + pa_assert(cb); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + + pw_log_debug("context %p", c); + + o = pa_operation_new(c, NULL, card_info_list, sizeof(struct card_data)); + d = o->userdata; + d->context = c; + d->cb = cb; + d->userdata = userdata; + pa_operation_sync(o); + + return o; +} + +static void card_profile(pa_operation *o, void *userdata) +{ + struct card_data *d = userdata; + struct global *g = d->global; + pa_context *c = d->context; + int res = 0; + uint32_t id = SPA_ID_INVALID; + char buf[1024]; + struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buf, sizeof(buf)); + struct param *p; + + spa_list_for_each(p, &g->card_info.profiles, link) { + uint32_t test_id; + const char *name; + + if (spa_pod_parse_object(p->param, + SPA_TYPE_OBJECT_ParamProfile, NULL, + SPA_PARAM_PROFILE_index, SPA_POD_Int(&test_id), + SPA_PARAM_PROFILE_name, SPA_POD_String(&name)) < 0) { + pw_log_warn("device %d: can't parse profile", g->id); + continue; + } + if (strcmp(name, d->profile) == 0) { + id = test_id; + break; + } + } + if (id == SPA_ID_INVALID) + goto done;; + + pw_device_proxy_set_param((struct pw_device_proxy*)g->proxy, + SPA_PARAM_Profile, 0, + spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamProfile, SPA_PARAM_Profile, + SPA_PARAM_PROFILE_index, SPA_POD_Int(id))); + res = 1; +done: + if (d->success_cb) + d->success_cb(c, res, d->userdata); + pa_operation_done(o); + free(d->profile); +} + +SPA_EXPORT +pa_operation* pa_context_set_card_profile_by_index(pa_context *c, uint32_t idx, const char*profile, pa_context_success_cb_t cb, void *userdata) +{ + pa_operation *o; + struct global *g; + struct card_data *d; + + pa_assert(c); + pa_assert(c->refcount >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, idx != PA_INVALID_INDEX, PA_ERR_INVALID); + + if ((g = pa_context_find_global(c, idx)) == NULL) + return NULL; + if (!(g->mask & PA_SUBSCRIPTION_MASK_CARD)) + return NULL; + + pw_log_debug("Card set profile %s", profile); + + o = pa_operation_new(c, NULL, card_profile, sizeof(struct card_data)); + d = o->userdata; + d->context = c; + d->success_cb = cb; + d->userdata = userdata; + d->global = g; + d->profile = strdup(profile); + pa_operation_sync(o); + + return o; +} + +SPA_EXPORT +pa_operation* pa_context_set_card_profile_by_name(pa_context *c, const char*name, const char*profile, pa_context_success_cb_t cb, void *userdata) +{ + pa_operation *o; + struct global *g; + struct card_data *d; + + pa_assert(c); + pa_assert(c->refcount >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, !name || *name, PA_ERR_INVALID); + + if ((g = pa_context_find_global_by_name(c, PA_SUBSCRIPTION_MASK_CARD, name)) == NULL) + return NULL; + + pw_log_debug("Card set profile %s", profile); + + o = pa_operation_new(c, NULL, card_profile, sizeof(struct card_data)); + d = o->userdata; + d->context = c; + d->success_cb = cb; + d->userdata = userdata; + d->global = g; + d->profile = strdup(profile); + pa_operation_sync(o); + + return o; +} + +SPA_EXPORT +pa_operation* pa_context_set_port_latency_offset(pa_context *c, const char *card_name, const char *port_name, int64_t offset, pa_context_success_cb_t cb, void *userdata) +{ + pa_operation *o; + struct success_ack *d; + + o = pa_operation_new(c, NULL, on_success, sizeof(struct success_ack)); + d = o->userdata; + d->cb = cb; + d->userdata = userdata; + pa_operation_sync(o); + + pw_log_warn("Not Implemented"); + return o; +} + +static pa_stream *find_stream(pa_context *c, uint32_t idx) +{ + pa_stream *s; + spa_list_for_each(s, &c->streams, link) { + if (pw_stream_get_node_id(s->stream) == idx) + return s; + } + return NULL; +} + +struct sink_input_data { + pa_context *context; + pa_sink_input_info_cb_t cb; + void *userdata; + struct global *global; +}; + +static void sink_input_callback(struct sink_input_data *d) +{ + struct global *g = d->global, *cl; + struct pw_node_info *info = g->info; + const char *name; + uint32_t n; + pa_sink_input_info i; + pa_format_info ii[1]; + pa_stream *s; + + if (info == NULL) + return; + + s = find_stream(d->context, g->id); + + if (info->props) { + if ((name = spa_dict_lookup(info->props, PW_KEY_MEDIA_NAME)) == NULL && + (name = spa_dict_lookup(info->props, PW_KEY_APP_NAME)) == NULL && + (name = spa_dict_lookup(info->props, PW_KEY_NODE_NAME)) == NULL) + name = "unknown"; + } + else + name = "unknown"; + + cl = pa_context_find_global(d->context, g->node_info.client_id); + + spa_zero(i); + i.index = g->id; + i.name = name; + i.owner_module = PA_INVALID_INDEX; + i.client = g->node_info.client_id; + if (s) { + i.sink = s->device_index; + } + else { + struct global *l; + l = pa_context_find_linked(d->context, g->id); + i.sink = l ? l->id : PA_INVALID_INDEX; + } + if (s && s->sample_spec.channels > 0) { + i.sample_spec = s->sample_spec; + if (s->channel_map.channels == s->sample_spec.channels) + i.channel_map = s->channel_map; + else + pa_channel_map_init_auto(&i.channel_map, + i.sample_spec.channels, PA_CHANNEL_MAP_OSS); + i.format = s->format; + } + else { + i.sample_spec.format = PA_SAMPLE_S16LE; + i.sample_spec.rate = 44100; + i.sample_spec.channels = g->node_info.n_channel_volumes; + if (i.sample_spec.channels == 0) + i.sample_spec.channels = 2; + pa_channel_map_init_auto(&i.channel_map, i.sample_spec.channels, PA_CHANNEL_MAP_OSS); + ii[0].encoding = PA_ENCODING_PCM; + ii[0].plist = pa_proplist_new(); + i.format = ii; + } + pa_cvolume_init(&i.volume); + i.volume.channels = i.sample_spec.channels; + for (n = 0; n < i.volume.channels; n++) + i.volume.values[n] = g->node_info.volume * g->node_info.channel_volumes[n] * PA_VOLUME_NORM; + + i.mute = g->node_info.mute; + i.buffer_usec = 0; + i.sink_usec = 0; + i.resample_method = "PipeWire resampler"; + i.driver = "PipeWire"; + i.proplist = pa_proplist_new_dict(info->props); + if (cl && cl->client_info.info.proplist) + pa_proplist_update(i.proplist, PA_UPDATE_MERGE, cl->client_info.info.proplist); + i.corked = false; + i.has_volume = true; + i.volume_writable = true; + + pw_log_debug("context %p: sink info for %d sink:%d", g->context, i.index, i.sink); + + d->cb(d->context, &i, 0, d->userdata); + + pa_proplist_free(i.proplist); +} + +static void sink_input_info(pa_operation *o, void *userdata) +{ + struct sink_input_data *d = userdata; + if (wait_global(d->context, d->global, o) < 0) + return; + sink_input_callback(d); + d->cb(d->context, NULL, 1, d->userdata); + pa_operation_done(o); +} + +SPA_EXPORT +pa_operation* pa_context_get_sink_input_info(pa_context *c, uint32_t idx, pa_sink_input_info_cb_t cb, void *userdata) +{ + pa_operation *o; + struct global *g; + struct sink_input_data *d; + + pa_assert(c); + pa_assert(c->refcount >= 1); + pa_assert(cb); + + PA_CHECK_VALIDITY_RETURN_NULL(c, idx != PA_INVALID_INDEX, PA_ERR_INVALID); + + pw_log_debug("context %p: info for %d", c, idx); + + if ((g = pa_context_find_global(c, idx)) == NULL) + return NULL; + if (!(g->mask & PA_SUBSCRIPTION_MASK_SINK_INPUT)) + return NULL; + + o = pa_operation_new(c, NULL, sink_input_info, sizeof(struct sink_input_data)); + d = o->userdata; + d->context = c; + d->cb = cb; + d->userdata = userdata; + d->global = g; + pa_operation_sync(o); + + return o; +} + +static void sink_input_info_list(pa_operation *o, void *userdata) +{ + struct sink_input_data *d = userdata; + pa_context *c = d->context; + struct global *g; + + if (wait_globals(c, PA_SUBSCRIPTION_MASK_SINK_INPUT, o) < 0) + return; + spa_list_for_each(g, &c->globals, link) { + if (!(g->mask & PA_SUBSCRIPTION_MASK_SINK_INPUT)) + continue; + d->global = g; + sink_input_callback(d); + } + d->cb(c, NULL, 1, d->userdata); + pa_operation_done(o); +} + +SPA_EXPORT +pa_operation* pa_context_get_sink_input_info_list(pa_context *c, pa_sink_input_info_cb_t cb, void *userdata) +{ + pa_operation *o; + struct sink_input_data *d; + + pa_assert(c); + pa_assert(c->refcount >= 1); + pa_assert(cb); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + + pw_log_debug("context %p", c); + + o = pa_operation_new(c, NULL, sink_input_info_list, sizeof(struct sink_input_data)); + d = o->userdata; + d->context = c; + d->cb = cb; + d->userdata = userdata; + pa_operation_sync(o); + + return o; +} + +SPA_EXPORT +pa_operation* pa_context_move_sink_input_by_name(pa_context *c, uint32_t idx, const char *sink_name, pa_context_success_cb_t cb, void* userdata) +{ + pa_operation *o; + struct success_ack *d; + + o = pa_operation_new(c, NULL, on_success, sizeof(struct success_ack)); + d = o->userdata; + d->cb = cb; + d->userdata = userdata; + pa_operation_sync(o); + + pw_log_warn("Not Implemented"); + return o; +} + +SPA_EXPORT +pa_operation* pa_context_move_sink_input_by_index(pa_context *c, uint32_t idx, uint32_t sink_idx, pa_context_success_cb_t cb, void* userdata) +{ + pa_operation *o; + struct success_ack *d; + + o = pa_operation_new(c, NULL, on_success, sizeof(struct success_ack)); + d = o->userdata; + d->cb = cb; + d->userdata = userdata; + pa_operation_sync(o); + + pw_log_warn("Not Implemented"); + return o; +} + +SPA_EXPORT +pa_operation* pa_context_set_sink_input_volume(pa_context *c, uint32_t idx, const pa_cvolume *volume, pa_context_success_cb_t cb, void *userdata) +{ + pa_stream *s; + struct global *g; + pa_operation *o; + struct success_ack *d; + + pw_log_debug("contex %p: index %d", c, idx); + + if ((s = find_stream(c, idx)) == NULL) { + if ((g = pa_context_find_global(c, idx)) == NULL) + return NULL; + if (!(g->mask & PA_SUBSCRIPTION_MASK_SINK_INPUT)) + return NULL; + } + + if (s) { + set_stream_volume(c, s, volume, s->mute); + } + else if (g) { + set_node_volume(c, g, volume, g->node_info.mute); + } + o = pa_operation_new(c, NULL, on_success, sizeof(struct success_ack)); + d = o->userdata; + d->cb = cb; + d->userdata = userdata; + pa_operation_sync(o); + + return o; +} + +SPA_EXPORT +pa_operation* pa_context_set_sink_input_mute(pa_context *c, uint32_t idx, int mute, pa_context_success_cb_t cb, void *userdata) +{ + pa_stream *s; + struct global *g; + pa_operation *o; + struct success_ack *d; + + pw_log_debug("contex %p: index %d", c, idx); + + if ((s = find_stream(c, idx)) == NULL) { + if ((g = pa_context_find_global(c, idx)) == NULL) + return NULL; + if (!(g->mask & PA_SUBSCRIPTION_MASK_SINK_INPUT)) + return NULL; + } + + if (s) { + set_stream_volume(c, s, NULL, mute); + } + else if (g) { + set_node_volume(c, g, NULL, mute); + } + o = pa_operation_new(c, NULL, on_success, sizeof(struct success_ack)); + d = o->userdata; + d->cb = cb; + d->userdata = userdata; + pa_operation_sync(o); + + return o; +} + +SPA_EXPORT +pa_operation* pa_context_kill_sink_input(pa_context *c, uint32_t idx, pa_context_success_cb_t cb, void *userdata) +{ + pa_stream *s; + struct global *g; + pa_operation *o; + struct success_ack *d; + + if ((s = find_stream(c, idx)) == NULL) { + if ((g = pa_context_find_global(c, idx)) == NULL) + return NULL; + if (!(g->mask & PA_SUBSCRIPTION_MASK_SINK_INPUT)) + return NULL; + } + + if (s) { + pw_stream_destroy(s->stream); + } + else if (g) { + pw_registry_proxy_destroy(c->registry_proxy, g->id); + } + o = pa_operation_new(c, NULL, on_success, sizeof(struct success_ack)); + d = o->userdata; + d->cb = cb; + d->userdata = userdata; + pa_operation_sync(o); + + return o; +} + +struct source_output_data { + pa_context *context; + pa_source_output_info_cb_t cb; + void *userdata; + struct global *global; +}; + +static void source_output_callback(struct source_output_data *d) +{ + struct global *g = d->global, *l, *cl; + struct pw_node_info *info = g->info; + const char *name = NULL; + uint32_t n; + pa_source_output_info i; + pa_format_info ii[1]; + pa_stream *s; + + pw_log_debug("index %d", g->id); + if (info == NULL) + return; + + s = find_stream(d->context, g->id); + + if (info->props) { + if ((name = spa_dict_lookup(info->props, PW_KEY_MEDIA_NAME)) == NULL && + (name = spa_dict_lookup(info->props, PW_KEY_APP_NAME)) == NULL && + (name = spa_dict_lookup(info->props, PW_KEY_NODE_NAME)) == NULL) + name = NULL; + } + if (name == NULL) + name = "unknown"; + + cl = pa_context_find_global(d->context, g->node_info.client_id); + + spa_zero(i); + i.index = g->id; + i.name = name ? name : "Unknown"; + i.owner_module = PA_INVALID_INDEX; + i.client = g->node_info.client_id; + if (s) { + i.source = s->device_index; + } + else { + l = pa_context_find_linked(d->context, g->id); + i.source = l ? l->id : PA_INVALID_INDEX; + } + if (s && s->sample_spec.channels > 0) { + i.sample_spec = s->sample_spec; + if (s->channel_map.channels == s->sample_spec.channels) + i.channel_map = s->channel_map; + else + pa_channel_map_init_auto(&i.channel_map, + i.sample_spec.channels, PA_CHANNEL_MAP_OSS); + i.format = s->format; + } + else { + i.sample_spec.format = PA_SAMPLE_S16LE; + i.sample_spec.rate = 44100; + i.sample_spec.channels = g->node_info.n_channel_volumes; + if (i.sample_spec.channels == 0) + i.sample_spec.channels = 2; + pa_channel_map_init_auto(&i.channel_map, i.sample_spec.channels, PA_CHANNEL_MAP_OSS); + ii[0].encoding = PA_ENCODING_PCM; + ii[0].plist = pa_proplist_new(); + i.format = ii; + } + pa_cvolume_init(&i.volume); + i.volume.channels = i.sample_spec.channels; + for (n = 0; n < i.volume.channels; n++) + i.volume.values[n] = g->node_info.volume * g->node_info.channel_volumes[n] * PA_VOLUME_NORM; + + i.mute = g->node_info.mute; + i.buffer_usec = 0; + i.source_usec = 0; + i.resample_method = "PipeWire resampler"; + i.driver = "PipeWire"; + i.proplist = pa_proplist_new_dict(info->props); + if (cl && cl->client_info.info.proplist) + pa_proplist_update(i.proplist, PA_UPDATE_MERGE, cl->client_info.info.proplist); + i.corked = false; + i.has_volume = true; + i.volume_writable = true; + + d->cb(d->context, &i, 0, d->userdata); + + pa_proplist_free(i.proplist); +} + +static void source_output_info(pa_operation *o, void *userdata) +{ + struct source_output_data *d = userdata; + if (wait_global(d->context, d->global, o) < 0) + return; + source_output_callback(d); + d->cb(d->context, NULL, 1, d->userdata); + pa_operation_done(o); +} + +SPA_EXPORT +pa_operation* pa_context_get_source_output_info(pa_context *c, uint32_t idx, pa_source_output_info_cb_t cb, void *userdata) +{ + pa_operation *o; + struct global *g; + struct source_output_data *d; + + pa_assert(c); + pa_assert(c->refcount >= 1); + pa_assert(cb); + + PA_CHECK_VALIDITY_RETURN_NULL(c, idx != PA_INVALID_INDEX, PA_ERR_INVALID); + + if ((g = pa_context_find_global(c, idx)) == NULL) + return NULL; + if (!(g->mask & PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT)) + return NULL; + + o = pa_operation_new(c, NULL, source_output_info, sizeof(struct source_output_data)); + d = o->userdata; + d->context = c; + d->cb = cb; + d->userdata = userdata; + d->global = g; + pa_operation_sync(o); + + return o; +} + +static void source_output_info_list(pa_operation *o, void *userdata) +{ + struct source_output_data *d = userdata; + pa_context *c = d->context; + struct global *g; + + if (wait_globals(c, PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT, o) < 0) + return; + + spa_list_for_each(g, &c->globals, link) { + if (!(g->mask & PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT)) + continue; + d->global = g; + source_output_callback(d); + } + d->cb(c, NULL, 1, d->userdata); + pa_operation_done(o); +} + +SPA_EXPORT +pa_operation* pa_context_get_source_output_info_list(pa_context *c, pa_source_output_info_cb_t cb, void *userdata) +{ + pa_operation *o; + struct source_output_data *d; + + pa_assert(c); + pa_assert(c->refcount >= 1); + pa_assert(cb); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + + o = pa_operation_new(c, NULL, source_output_info_list, sizeof(struct source_output_data)); + d = o->userdata; + d->context = c; + d->cb = cb; + d->userdata = userdata; + pa_operation_sync(o); + + return o; +} + +SPA_EXPORT +pa_operation* pa_context_move_source_output_by_name(pa_context *c, uint32_t idx, const char *source_name, pa_context_success_cb_t cb, void* userdata) +{ + pa_operation *o; + struct success_ack *d; + + o = pa_operation_new(c, NULL, on_success, sizeof(struct success_ack)); + d = o->userdata; + d->cb = cb; + d->userdata = userdata; + pa_operation_sync(o); + + pw_log_warn("Not Implemented"); + return o; +} + +SPA_EXPORT +pa_operation* pa_context_move_source_output_by_index(pa_context *c, uint32_t idx, uint32_t source_idx, pa_context_success_cb_t cb, void* userdata) +{ + pa_operation *o; + struct success_ack *d; + + o = pa_operation_new(c, NULL, on_success, sizeof(struct success_ack)); + d = o->userdata; + d->cb = cb; + d->userdata = userdata; + pa_operation_sync(o); + + pw_log_warn("Not Implemented"); + return o; +} + +SPA_EXPORT +pa_operation* pa_context_set_source_output_volume(pa_context *c, uint32_t idx, const pa_cvolume *volume, pa_context_success_cb_t cb, void *userdata) +{ + pa_stream *s; + struct global *g; + pa_operation *o; + struct success_ack *d; + + pw_log_debug("contex %p: index %d", c, idx); + + if ((s = find_stream(c, idx)) == NULL) { + if ((g = pa_context_find_global(c, idx)) == NULL) + return NULL; + if (!(g->mask & PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT)) + return NULL; + } + + if (s) { + set_stream_volume(c, s, volume, s->mute); + } + else if (g) { + set_node_volume(c, g, volume, g->node_info.mute); + } + o = pa_operation_new(c, NULL, on_success, sizeof(struct success_ack)); + d = o->userdata; + d->cb = cb; + d->userdata = userdata; + pa_operation_sync(o); + + return o; +} + +SPA_EXPORT +pa_operation* pa_context_set_source_output_mute(pa_context *c, uint32_t idx, int mute, pa_context_success_cb_t cb, void *userdata) +{ + pa_stream *s; + struct global *g; + pa_operation *o; + struct success_ack *d; + + if ((s = find_stream(c, idx)) == NULL) { + if ((g = pa_context_find_global(c, idx)) == NULL) + return NULL; + if (!(g->mask & PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT)) + return NULL; + } + + if (s) { + set_stream_volume(c, s, NULL, mute); + } + else if (g) { + set_node_volume(c, g, NULL, mute); + } + o = pa_operation_new(c, NULL, on_success, sizeof(struct success_ack)); + d = o->userdata; + d->cb = cb; + d->userdata = userdata; + pa_operation_sync(o); + + return o; +} + +SPA_EXPORT +pa_operation* pa_context_kill_source_output(pa_context *c, uint32_t idx, pa_context_success_cb_t cb, void *userdata) +{ + pa_stream *s; + struct global *g; + pa_operation *o; + struct success_ack *d; + + if ((s = find_stream(c, idx)) == NULL) { + if ((g = pa_context_find_global(c, idx)) == NULL) + return NULL; + if (!(g->mask & PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT)) + return NULL; + } + + if (s) { + pw_stream_destroy(s->stream); + } + else if (g) { + pw_registry_proxy_destroy(c->registry_proxy, g->id); + } + o = pa_operation_new(c, NULL, on_success, sizeof(struct success_ack)); + d = o->userdata; + d->cb = cb; + d->userdata = userdata; + pa_operation_sync(o); + + return o; +} + +SPA_EXPORT +pa_operation* pa_context_stat(pa_context *c, pa_stat_info_cb_t cb, void *userdata) +{ + pw_log_warn("Not Implemented"); + return NULL; +} + +SPA_EXPORT +pa_operation* pa_context_get_sample_info_by_name(pa_context *c, const char *name, pa_sample_info_cb_t cb, void *userdata) +{ + pw_log_warn("Not Implemented"); + return NULL; +} + +SPA_EXPORT +pa_operation* pa_context_get_sample_info_by_index(pa_context *c, uint32_t idx, pa_sample_info_cb_t cb, void *userdata) +{ + pw_log_warn("Not Implemented"); + return NULL; +} + +SPA_EXPORT +pa_operation* pa_context_get_sample_info_list(pa_context *c, pa_sample_info_cb_t cb, void *userdata) +{ + pw_log_warn("Not Implemented"); + return NULL; +} + +SPA_EXPORT +pa_operation* pa_context_get_autoload_info_by_name(pa_context *c, const char *name, pa_autoload_type_t type, pa_autoload_info_cb_t cb, void *userdata) +{ + pw_log_warn("Deprecated: Not Implemented"); + return NULL; +} + +SPA_EXPORT +pa_operation* pa_context_get_autoload_info_by_index(pa_context *c, uint32_t idx, pa_autoload_info_cb_t cb, void *userdata) +{ + pw_log_warn("Deprecated: Not Implemented"); + return NULL; +} + +SPA_EXPORT +pa_operation* pa_context_get_autoload_info_list(pa_context *c, pa_autoload_info_cb_t cb, void *userdata) +{ + pw_log_warn("Deprecated: Not Implemented"); + return NULL; +} + +SPA_EXPORT +pa_operation* pa_context_add_autoload(pa_context *c, const char *name, pa_autoload_type_t type, const char *module, const char*argument, pa_context_index_cb_t cb, void* userdata) +{ + pw_log_warn("Deprecated: Not Implemented"); + return NULL; +} + +SPA_EXPORT +pa_operation* pa_context_remove_autoload_by_name(pa_context *c, const char *name, pa_autoload_type_t type, pa_context_success_cb_t cb, void* userdata) +{ + pw_log_warn("Deprecated: Not Implemented"); + return NULL; +} + +SPA_EXPORT +pa_operation* pa_context_remove_autoload_by_index(pa_context *c, uint32_t idx, pa_context_success_cb_t cb, void* userdata) +{ + pw_log_warn("Deprecated: Not Implemented"); + return NULL; +} diff --git a/pipewire-pulseaudio/src/json.c b/pipewire-pulseaudio/src/json.c new file mode 100644 index 000000000..9ca498bef --- /dev/null +++ b/pipewire-pulseaudio/src/json.c @@ -0,0 +1,659 @@ +/*** + This file is part of PulseAudio. + + Copyright 2016 Arun Raghavan + + 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, see . +***/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include + +#include +#include + +#include "internal.h" +#include "json.h" +#include "strbuf.h" + +#define MAX_NESTING_DEPTH 20 /* Arbitrary number to make sure we don't have a stack overflow */ + +typedef struct pa_json_item { + char *key; + pa_json_object *value; +} pa_json_item; + +struct pa_json_object { + pa_json_type type; + + union { + int int_value; + double double_value; + bool bool_value; + char *string_value; + struct pw_array values; /* objects */ + }; +}; + +static void clear_array(struct pw_array *array) +{ + pa_json_object **value; + pw_array_for_each(value, array) + pa_json_object_free(*value); + pw_array_clear(array); +} + +static void clear_item(pa_json_item *item) +{ + free(item->key); + pa_json_object_free(item->value); +} + +static void clear_object(struct pw_array *array) +{ + pa_json_item *item; + pw_array_for_each(item, array) + clear_item(item); + pw_array_clear(array); +} + +static const char* parse_value(const char *str, const char *end, pa_json_object **obj, unsigned int depth); + +static pa_json_object* json_object_new(void) { + pa_json_object *obj; + + obj = pa_xnew0(pa_json_object, 1); + + return obj; +} + +static bool is_whitespace(char c) { + return c == '\t' || c == '\n' || c == '\r' || c == ' '; +} + +static bool is_digit(char c) { + return c >= '0' && c <= '9'; +} + +static bool is_end(const char c, const char *end) { + if (!end) + return c == '\0'; + else { + while (*end) { + if (c == *end) + return true; + end++; + } + } + + return false; +} + +static const char* consume_string(const char *str, const char *expect) { + while (*expect) { + if (*str != *expect) + return NULL; + + str++; + expect++; + } + + return str; +} + +static const char* parse_null(const char *str, pa_json_object *obj) { + str = consume_string(str, "null"); + + if (str) + obj->type = PA_JSON_TYPE_NULL; + + return str; +} + +static const char* parse_boolean(const char *str, pa_json_object *obj) { + const char *tmp; + + tmp = consume_string(str, "true"); + + if (tmp) { + obj->type = PA_JSON_TYPE_BOOL; + obj->bool_value = true; + } else { + tmp = consume_string(str, "false"); + + if (str) { + obj->type = PA_JSON_TYPE_BOOL; + obj->bool_value = false; + } + } + + return tmp; +} + +static const char* parse_string(const char *str, pa_json_object *obj) { + pa_strbuf *buf = pa_strbuf_new(); + + str++; /* Consume leading '"' */ + + while (*str && *str != '"') { + if (*str != '\\') { + /* We only accept ASCII printable characters. */ + if (*str < 0x20 || *str > 0x7E) { + pa_log("Invalid non-ASCII character: 0x%x", (unsigned int) *str); + goto error; + } + + /* Normal character, juts consume */ + pa_strbuf_putc(buf, *str); + } else { + /* Need to unescape */ + str++; + + switch (*str) { + case '"': + case '\\': + case '/': + pa_strbuf_putc(buf, *str); + break; + + case 'b': + pa_strbuf_putc(buf, '\b' /* backspace */); + break; + + case 'f': + pa_strbuf_putc(buf, '\f' /* form feed */); + break; + + case 'n': + pa_strbuf_putc(buf, '\n' /* new line */); + break; + + case 'r': + pa_strbuf_putc(buf, '\r' /* carriage return */); + break; + + case 't': + pa_strbuf_putc(buf, '\t' /* horizontal tab */); + break; + + case 'u': + pa_log("Unicode code points are currently unsupported"); + goto error; + + default: + pa_log("Unexepcted escape value: %c", *str); + goto error; + } + } + + str++; + } + + if (*str != '"') { + pa_log("Failed to parse remainder of string: %s", str); + goto error; + } + + str++; + + obj->type = PA_JSON_TYPE_STRING; + obj->string_value = pa_strbuf_to_string_free(buf); + + return str; + +error: + pa_strbuf_free(buf); + return NULL; +} + +static const char* parse_number(const char *str, pa_json_object *obj) { + bool negative = false, has_fraction = false, has_exponent = false, valid = false; + unsigned int integer = 0; + unsigned int fraction = 0; + unsigned int fraction_digits = 0; + int exponent = 0; + + if (*str == '-') { + negative = true; + str++; + } + + if (*str == '0') { + valid = true; + str++; + goto fraction; + } + + while (is_digit(*str)) { + valid = true; + + if (integer > ((negative ? INT_MAX : UINT_MAX) / 10)) { + pa_log("Integer overflow while parsing number"); + goto error; + } + + integer = (integer * 10) + (*str - '0'); + str++; + } + +fraction: + + if (!valid) { + pa_log("Missing digits while parsing number"); + goto error; + } + + if (*str == '.') { + has_fraction = true; + str++; + valid = false; + + while (is_digit(*str)) { + valid = true; + + if (fraction > (UINT_MAX / 10)) { + pa_log("Integer overflow while parsing fractional part of number"); + goto error; + } + + fraction = (fraction * 10) + (*str - '0'); + fraction_digits++; + str++; + } + + if (!valid) { + pa_log("No digit after '.' while parsing fraction"); + goto error; + } + } + + if (*str == 'e' || *str == 'E') { + bool exponent_negative = false; + + has_exponent = true; + str++; + valid = false; + + if (*str == '-') { + exponent_negative = true; + str++; + } else if (*str == '+') + str++; + + while (is_digit(*str)) { + valid = true; + + if (exponent > (INT_MAX / 10)) { + pa_log("Integer overflow while parsing exponent part of number"); + goto error; + } + + exponent = (exponent * 10) + (*str - '0'); + str++; + } + + if (!valid) { + pa_log("No digit in exponent while parsing fraction"); + goto error; + } + + if (exponent_negative) + exponent *= -1; + } + + if (has_fraction || has_exponent) { + obj->type = PA_JSON_TYPE_DOUBLE; + obj->double_value = + (negative ? -1.0 : 1.0) * (integer + (double) fraction / pow(10, fraction_digits)) * pow(10, exponent); + } else { + obj->type = PA_JSON_TYPE_INT; + obj->int_value = (negative ? -1 : 1) * integer; + } + + return str; + +error: + return NULL; +} + +static const char *parse_object(const char *str, pa_json_object *obj, unsigned int depth) { + pa_json_object *name = NULL, *value = NULL; + pa_json_item *item; + + obj->values = PW_ARRAY_INIT(64); + + while (*str != '}') { + str++; /* Consume leading '{' or ',' */ + + str = parse_value(str, ":", &name, depth + 1); + if (!str || pa_json_object_get_type(name) != PA_JSON_TYPE_STRING) { + pa_log("Could not parse key for object"); + goto error; + } + + /* Consume the ':' */ + str++; + + str = parse_value(str, ",}", &value, depth + 1); + if (!str) { + pa_log("Could not parse value for object"); + goto error; + } + + item = pw_array_add(&obj->values, sizeof(pa_json_item)); + item->key = strdup(pa_json_object_get_string(name)); + item->value = value; + pa_json_object_free(name); + + name = NULL; + value = NULL; + } + + /* Drop trailing '}' */ + str++; + + /* We now know the value was correctly parsed */ + obj->type = PA_JSON_TYPE_OBJECT; + + return str; + +error: + clear_object(&obj->values); + + if (name) + pa_json_object_free(name); + if (value) + pa_json_object_free(value); + + return NULL; +} + +static const char *parse_array(const char *str, pa_json_object *obj, unsigned int depth) { + pa_json_object *value; + + obj->values = PW_ARRAY_INIT(64); + + while (*str != ']') { + str++; /* Consume leading '[' or ',' */ + + /* Need to chew up whitespaces as a special case to deal with the + * possibility of an empty array */ + while (is_whitespace(*str)) + str++; + + if (*str == ']') + break; + + str = parse_value(str, ",]", &value, depth + 1); + if (!str) { + pa_log("Could not parse value for array"); + goto error; + } + + pw_array_add_ptr(&obj->values, value); + } + + /* Drop trailing ']' */ + str++; + + /* We now know the value was correctly parsed */ + obj->type = PA_JSON_TYPE_ARRAY; + + return str; + +error: + clear_array(&obj->values); + return NULL; +} + +typedef enum { + JSON_PARSER_STATE_INIT, + JSON_PARSER_STATE_FINISH, +} json_parser_state; + +static const char* parse_value(const char *str, const char *end, pa_json_object **obj, unsigned int depth) { + json_parser_state state = JSON_PARSER_STATE_INIT; + pa_json_object *o; + + pa_assert(str != NULL); + + o = json_object_new(); + + if (depth > MAX_NESTING_DEPTH) { + pa_log("Exceeded maximum permitted nesting depth of objects (%u)", MAX_NESTING_DEPTH); + goto error; + } + + while (!is_end(*str, end)) { + switch (state) { + case JSON_PARSER_STATE_INIT: + if (is_whitespace(*str)) { + str++; + } else if (*str == 'n') { + str = parse_null(str, o); + state = JSON_PARSER_STATE_FINISH; + } else if (*str == 't' || *str == 'f') { + str = parse_boolean(str, o); + state = JSON_PARSER_STATE_FINISH; + } else if (*str == '"') { + str = parse_string(str, o); + state = JSON_PARSER_STATE_FINISH; + } else if (is_digit(*str) || *str == '-') { + str = parse_number(str, o); + state = JSON_PARSER_STATE_FINISH; + } else if (*str == '{') { + str = parse_object(str, o, depth); + state = JSON_PARSER_STATE_FINISH; + } else if (*str == '[') { + str = parse_array(str, o, depth); + state = JSON_PARSER_STATE_FINISH; + } else { + pa_log("Invalid JSON string: %s", str); + goto error; + } + + if (!str) + goto error; + + break; + + case JSON_PARSER_STATE_FINISH: + /* Consume trailing whitespaces */ + if (is_whitespace(*str)) { + str++; + } else { + goto error; + } + } + } + + if (pa_json_object_get_type(o) == PA_JSON_TYPE_INIT) { + /* We didn't actually get any data */ + pa_log("No data while parsing json string: '%s' till '%s'", str, pa_strnull(end)); + goto error; + } + + *obj = o; + + return str; + +error: + pa_json_object_free(o); + return NULL; +} + + +SPA_EXPORT +pa_json_object* pa_json_parse(const char *str) { + pa_json_object *obj; + + str = parse_value(str, NULL, &obj, 0); + + if (!str) { + pa_log("JSON parsing failed"); + return NULL; + } + + if (*str != '\0') { + pa_log("Unable to parse complete JSON string, remainder is: %s", str); + pa_json_object_free(obj); + return NULL; + } + + return obj; +} + +SPA_EXPORT +pa_json_type pa_json_object_get_type(const pa_json_object *obj) { + return obj->type; +} + +SPA_EXPORT +void pa_json_object_free(pa_json_object *obj) { + + switch (pa_json_object_get_type(obj)) { + case PA_JSON_TYPE_INIT: + case PA_JSON_TYPE_INT: + case PA_JSON_TYPE_DOUBLE: + case PA_JSON_TYPE_BOOL: + case PA_JSON_TYPE_NULL: + break; + + case PA_JSON_TYPE_STRING: + pa_xfree(obj->string_value); + break; + + case PA_JSON_TYPE_OBJECT: + clear_object(&obj->values); + break; + + case PA_JSON_TYPE_ARRAY: + clear_array(&obj->values); + break; + + default: + pa_assert_not_reached(); + } + + pa_xfree(obj); +} + +SPA_EXPORT +int pa_json_object_get_int(const pa_json_object *o) { + pa_assert(pa_json_object_get_type(o) == PA_JSON_TYPE_INT); + return o->int_value; +} + +SPA_EXPORT +double pa_json_object_get_double(const pa_json_object *o) { + pa_assert(pa_json_object_get_type(o) == PA_JSON_TYPE_DOUBLE); + return o->double_value; +} + +SPA_EXPORT +bool pa_json_object_get_bool(const pa_json_object *o) { + pa_assert(pa_json_object_get_type(o) == PA_JSON_TYPE_BOOL); + return o->bool_value; +} + +SPA_EXPORT +const char* pa_json_object_get_string(const pa_json_object *o) { + pa_assert(pa_json_object_get_type(o) == PA_JSON_TYPE_STRING); + return o->string_value; +} + +SPA_EXPORT +const pa_json_object* pa_json_object_get_object_member(const pa_json_object *o, const char *name) { + pa_json_item *item; + pa_assert(pa_json_object_get_type(o) == PA_JSON_TYPE_OBJECT); + pw_array_for_each(item, &o->values) { + if (pa_streq(item->key, name)) + return item->value; + } + return NULL; +} + +SPA_EXPORT +int pa_json_object_get_array_length(const pa_json_object *o) { + pa_assert(pa_json_object_get_type(o) == PA_JSON_TYPE_ARRAY); + return pw_array_get_len(&o->values, const pa_json_object*); +} + +SPA_EXPORT +const pa_json_object* pa_json_object_get_array_member(const pa_json_object *o, int index) { + pa_assert(pa_json_object_get_type(o) == PA_JSON_TYPE_ARRAY); + return pw_array_get_unchecked_s(&o->values, index, sizeof(pa_json_object*), + const pa_json_object); +} + +SPA_EXPORT +bool pa_json_object_equal(const pa_json_object *o1, const pa_json_object *o2) { + int i; + + if (pa_json_object_get_type(o1) != pa_json_object_get_type(o2)) + return false; + + switch (pa_json_object_get_type(o1)) { + case PA_JSON_TYPE_NULL: + return true; + + case PA_JSON_TYPE_BOOL: + return o1->bool_value == o2->bool_value; + + case PA_JSON_TYPE_INT: + return o1->int_value == o2->int_value; + + case PA_JSON_TYPE_DOUBLE: + return PA_DOUBLE_IS_EQUAL(o1->double_value, o2->double_value); + + case PA_JSON_TYPE_STRING: + return pa_streq(o1->string_value, o2->string_value); + + case PA_JSON_TYPE_ARRAY: + if (pa_json_object_get_array_length(o1) != pa_json_object_get_array_length(o2)) + return false; + + for (i = 0; i < pa_json_object_get_array_length(o1); i++) { + if (!pa_json_object_equal(pa_json_object_get_array_member(o1, i), + pa_json_object_get_array_member(o2, i))) + return false; + } + + return true; + + case PA_JSON_TYPE_OBJECT: { + const pa_json_object *value; + const pa_json_item *item; + + if (o1->values.size != o2->values.size) + return false; + + pw_array_for_each(item, &o1->values) { + value = pa_json_object_get_object_member(o2, item->key); + if (!value || !pa_json_object_equal(item->value, value)) + return false; + } + + return true; + } + + default: + pa_assert_not_reached(); + } +} diff --git a/pipewire-pulseaudio/src/json.h b/pipewire-pulseaudio/src/json.h new file mode 100644 index 000000000..7759bf2db --- /dev/null +++ b/pipewire-pulseaudio/src/json.h @@ -0,0 +1,53 @@ +/*** + This file is part of PulseAudio. + + Copyright 2016 Arun Raghavan + + 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, see . +***/ + +#include + +#define PA_DOUBLE_IS_EQUAL(x, y) (((x) - (y)) < 0.000001 && ((x) - (y)) > -0.000001) + +typedef enum { + PA_JSON_TYPE_INIT = 0, + PA_JSON_TYPE_NULL, + PA_JSON_TYPE_INT, + PA_JSON_TYPE_DOUBLE, + PA_JSON_TYPE_BOOL, + PA_JSON_TYPE_STRING, + PA_JSON_TYPE_ARRAY, + PA_JSON_TYPE_OBJECT, +} pa_json_type; + +typedef struct pa_json_object pa_json_object; + +pa_json_object* pa_json_parse(const char *str); +pa_json_type pa_json_object_get_type(const pa_json_object *obj); +void pa_json_object_free(pa_json_object *obj); + +/* All pointer members that are returned are valid while the corresponding object is valid */ + +int pa_json_object_get_int(const pa_json_object *o); +double pa_json_object_get_double(const pa_json_object *o); +bool pa_json_object_get_bool(const pa_json_object *o); +const char* pa_json_object_get_string(const pa_json_object *o); + +const pa_json_object* pa_json_object_get_object_member(const pa_json_object *o, const char *name); + +int pa_json_object_get_array_length(const pa_json_object *o); +const pa_json_object* pa_json_object_get_array_member(const pa_json_object *o, int index); + +bool pa_json_object_equal(const pa_json_object *o1, const pa_json_object *o2); diff --git a/pipewire-pulseaudio/src/mainloop-glib.c b/pipewire-pulseaudio/src/mainloop-glib.c new file mode 100644 index 000000000..7addd7d3d --- /dev/null +++ b/pipewire-pulseaudio/src/mainloop-glib.c @@ -0,0 +1,120 @@ +/* PipeWire + * Copyright (C) 2018 Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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. + */ + +#include + +#include + +#include +#include + +#include +#include + +#include "internal.h" + +struct source { + GSource base; + struct pw_loop *loop; +}; + +struct pa_glib_mainloop { + GMainContext *context; + pa_mainloop *loop; + struct source *source; + guint id; +}; + +static gboolean source_prepare (GSource *base, int *timeout) +{ + *timeout = -1; + return FALSE; +} + +static gboolean source_dispatch (GSource *source, GSourceFunc callback, gpointer user_data) +{ + struct source *s = (struct source *) source; + int result; + + pw_loop_enter (s->loop); + result = pw_loop_iterate (s->loop, 0); + pw_loop_leave (s->loop); + + if (result < 0) + g_warning ("pipewire_loop_iterate failed: %s", spa_strerror (result)); + + return TRUE; +} + +static GSourceFuncs source_funcs = +{ + source_prepare, + NULL, + source_dispatch, + NULL, + NULL, + NULL, +}; + +SPA_EXPORT +pa_glib_mainloop *pa_glib_mainloop_new(GMainContext *c) +{ + + pa_glib_mainloop *loop; + + loop = calloc(1, sizeof(pa_glib_mainloop)); + if (loop == NULL) + goto error; + + loop->context = c; + loop->loop = pa_mainloop_new(); + if (loop->loop == NULL) + goto error_free; + + loop->source = (struct source *) g_source_new(&source_funcs, sizeof(struct source)); + loop->source->loop = loop->loop->loop; + + g_source_add_unix_fd (&loop->source->base, + pw_loop_get_fd(loop->source->loop), + G_IO_IN | G_IO_ERR); + + loop->id = g_source_attach (&loop->source->base, loop->context); + + return loop; + + error_free: + free(loop); + error: + return NULL; + +} + +SPA_EXPORT +void pa_glib_mainloop_free(pa_glib_mainloop* g) +{ + g_source_destroy(&g->source->base); + pa_mainloop_free(g->loop); + free(g); +} + +SPA_EXPORT +pa_mainloop_api* pa_glib_mainloop_get_api(pa_glib_mainloop *g) +{ + return pa_mainloop_get_api(g->loop); +} diff --git a/pipewire-pulseaudio/src/mainloop-signal.c b/pipewire-pulseaudio/src/mainloop-signal.c new file mode 100644 index 000000000..5caf59200 --- /dev/null +++ b/pipewire-pulseaudio/src/mainloop-signal.c @@ -0,0 +1,114 @@ +/* PipeWire + * Copyright (C) 2018 Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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. + */ + +#include + +#include +#include + +#include +#include + +#include + +#include "internal.h" + + +static pa_mainloop_api *api = NULL; +static struct spa_list signals; +static struct pw_loop *loop = NULL; + +struct pa_signal_event { + struct spa_list link; + struct spa_source *source; + pa_signal_cb_t callback; + pa_signal_destroy_cb_t destroy; + void *userdata; +}; + +SPA_EXPORT +int pa_signal_init(pa_mainloop_api *a) +{ + pa_assert(a); + pa_assert(!api); + + api = a; + spa_list_init(&signals); + loop = a->userdata; + + return 0; +} + +SPA_EXPORT +void pa_signal_done(void) +{ + pa_signal_event *ev, *t; + + pa_assert(api); + + spa_list_for_each_safe(ev, t, &signals, link) + pa_signal_free(ev); + spa_list_init(&signals); + + api = NULL; +} + +static void source_signal_func (void *data, int signal_number) +{ + pa_signal_event *ev = data; + if (ev->callback) + ev->callback(api, ev, signal_number, ev->userdata); +} + +SPA_EXPORT +pa_signal_event* pa_signal_new(int sig, pa_signal_cb_t callback, void *userdata) +{ + pa_signal_event *ev; + + pa_assert(sig > 0); + pa_assert(callback); + + ev = calloc(1, sizeof(pa_signal_event)); + ev->source = spa_loop_utils_add_signal(loop->utils, sig, source_signal_func, ev); + ev->callback = callback; + ev->userdata = userdata; + + spa_list_append(&signals, &ev->link); + + return ev; +} + +SPA_EXPORT +void pa_signal_free(pa_signal_event *e) +{ + pa_assert(e); + + spa_list_remove(&e->link); + spa_loop_utils_destroy_source(loop->utils, e->source); + if (e->destroy) + e->destroy(api, e, e->userdata); + free(e); +} + +SPA_EXPORT +void pa_signal_set_destroy(pa_signal_event *e, pa_signal_destroy_cb_t callback) +{ + pa_assert(e); + e->destroy = callback; +} diff --git a/pipewire-pulseaudio/src/mainloop.c b/pipewire-pulseaudio/src/mainloop.c new file mode 100644 index 000000000..6198bd8c5 --- /dev/null +++ b/pipewire-pulseaudio/src/mainloop.c @@ -0,0 +1,433 @@ +/* PipeWire + * Copyright (C) 2018 Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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. + */ + +#include + +#include +#include + +#include +#include + +#include "internal.h" + +static void do_stop(void *data, uint64_t count) +{ + struct pa_mainloop *this = data; + this->quit = true; +} + +static uint32_t map_flags_to_spa(pa_io_event_flags_t flags) { + return (uint32_t) + ((flags & PA_IO_EVENT_INPUT ? SPA_IO_IN : 0) | + (flags & PA_IO_EVENT_OUTPUT ? SPA_IO_OUT : 0) | + (flags & PA_IO_EVENT_ERROR ? SPA_IO_ERR : 0) | + (flags & PA_IO_EVENT_HANGUP ? SPA_IO_HUP : 0)); +} + +static pa_io_event_flags_t map_flags_from_spa(uint32_t flags) { + return (flags & SPA_IO_IN ? PA_IO_EVENT_INPUT : 0) | + (flags & SPA_IO_OUT ? PA_IO_EVENT_OUTPUT : 0) | + (flags & SPA_IO_ERR ? PA_IO_EVENT_ERROR : 0) | + (flags & SPA_IO_HUP ? PA_IO_EVENT_HANGUP : 0); +} + +static void source_io_func(void *data, int fd, uint32_t mask) +{ + pa_io_event *ev = data; + if (ev->cb) + ev->cb(&ev->mainloop->api, ev, ev->fd, map_flags_from_spa(mask), ev->userdata); +} + +static pa_io_event* api_io_new(pa_mainloop_api*a, int fd, pa_io_event_flags_t events, pa_io_event_cb_t cb, void *userdata) +{ + pa_mainloop *mainloop = SPA_CONTAINER_OF(a, pa_mainloop, api); + pa_io_event *ev; + + pa_assert(a); + pa_assert(fd >= 0); + pa_assert(cb); + + ev = calloc(1, sizeof(pa_io_event)); + ev->source = pw_loop_add_io(mainloop->loop, fd, + map_flags_to_spa(events), false, source_io_func, ev); + ev->fd = fd; + ev->events = events; + ev->mainloop = mainloop; + ev->cb = cb; + ev->userdata = userdata; + + return ev; +} + +static void api_io_enable(pa_io_event* e, pa_io_event_flags_t events) +{ + pa_assert(e); + + if (e->events == events) + return; + + e->events = events; + pw_loop_update_io(e->mainloop->loop, e->source, map_flags_to_spa(events)); +} + +static void api_io_free(pa_io_event* e) +{ + pa_assert(e); + pw_loop_destroy_source(e->mainloop->loop, e->source); + if (e->destroy) + e->destroy(&e->mainloop->api, e, e->userdata); + free(e); +} + +static void api_io_set_destroy(pa_io_event *e, pa_io_event_destroy_cb_t cb) +{ + pa_assert(e); + e->destroy = cb; +} + +static void source_timer_func(void *data, uint64_t expirations) +{ + pa_time_event *ev = data; + struct timeval tv; + if (ev->cb) + ev->cb(&ev->mainloop->api, ev, &tv, ev->userdata); +} + +static pa_time_event* api_time_new(pa_mainloop_api*a, const struct timeval *tv, pa_time_event_cb_t cb, void *userdata) +{ + pa_mainloop *mainloop = SPA_CONTAINER_OF(a, pa_mainloop, api); + pa_time_event *ev; + struct timespec ts; + + ev = calloc(1, sizeof(pa_time_event)); + ev->source = pw_loop_add_timer(mainloop->loop, source_timer_func, ev); + ev->mainloop = mainloop; + ev->cb = cb; + ev->userdata = userdata; + + if (tv == NULL) { + ts.tv_sec = 0; + ts.tv_nsec = 1; + } + else { + ts.tv_sec = tv->tv_sec; + ts.tv_nsec = tv->tv_usec * 1000LL; + } + pw_log_debug("new timer %p %ld %ld", ev, ts.tv_sec, ts.tv_nsec); + pw_loop_update_timer(mainloop->loop, ev->source, &ts, NULL, true); + + return ev; +} + +static void api_time_restart(pa_time_event* e, const struct timeval *tv) +{ + struct timespec ts; + + pa_assert(e); + + if (tv == NULL) { + ts.tv_sec = 0; + ts.tv_nsec = 1; + } + else { + ts.tv_sec = tv->tv_sec; + ts.tv_nsec = tv->tv_usec * 1000LL; + } + pw_loop_update_timer(e->mainloop->loop, e->source, &ts, NULL, true); +} + +static void api_time_free(pa_time_event* e) +{ + pa_assert(e); + pw_loop_destroy_source(e->mainloop->loop, e->source); + if (e->destroy) + e->destroy(&e->mainloop->api, e, e->userdata); + free(e); +} + +static void api_time_set_destroy(pa_time_event *e, pa_time_event_destroy_cb_t cb) +{ + pa_assert(e); + e->destroy = cb; +} + +static void source_idle_func(void *data) +{ + pa_defer_event *ev = data; + if (ev->cb) + ev->cb(&ev->mainloop->api, ev, ev->userdata); +} + +static pa_defer_event* api_defer_new(pa_mainloop_api*a, pa_defer_event_cb_t cb, void *userdata) +{ + pa_mainloop *mainloop = SPA_CONTAINER_OF(a, pa_mainloop, api); + pa_defer_event *ev; + + pa_assert(a); + pa_assert(cb); + + ev = calloc(1, sizeof(pa_defer_event)); + ev->source = pw_loop_add_idle(mainloop->loop, true, source_idle_func, ev); + ev->mainloop = mainloop; + ev->cb = cb; + ev->userdata = userdata; + + return ev; +} + +static void api_defer_enable(pa_defer_event* e, int b) +{ + pa_assert(e); + pw_loop_enable_idle(e->mainloop->loop, e->source, b ? true : false); +} + +static void api_defer_free(pa_defer_event* e) +{ + pa_assert(e); + pw_loop_destroy_source(e->mainloop->loop, e->source); + if (e->destroy) + e->destroy(&e->mainloop->api, e, e->userdata); + free(e); +} + +static void api_defer_set_destroy(pa_defer_event *e, pa_defer_event_destroy_cb_t cb) +{ + pa_assert(e); + e->destroy = cb; +} + +static void api_quit(pa_mainloop_api*a, int retval) +{ + pa_mainloop *m = SPA_CONTAINER_OF(a, pa_mainloop, api); + m->quit = true; + m->retval = retval; + pa_mainloop_wakeup(m); +} + +static const pa_mainloop_api api = +{ + .io_new = api_io_new, + .io_enable = api_io_enable, + .io_free = api_io_free, + .io_set_destroy = api_io_set_destroy, + + .time_new = api_time_new, + .time_restart = api_time_restart, + .time_free = api_time_free, + .time_set_destroy = api_time_set_destroy, + + .defer_new = api_defer_new, + .defer_enable = api_defer_enable, + .defer_free = api_defer_free, + .defer_set_destroy = api_defer_set_destroy, + + .quit = api_quit, +}; + +SPA_EXPORT +pa_mainloop *pa_mainloop_new(void) +{ + pa_mainloop *loop; + + loop = calloc(1, sizeof(pa_mainloop)); + if (loop == NULL) + return NULL; + + loop->loop = pw_loop_new(NULL); + if (loop->loop == NULL) + goto no_loop; + + loop->event = pw_loop_add_event(loop->loop, do_stop, loop); + loop->api = api; + loop->api.userdata = loop->loop; + + return loop; + + no_loop: + free(loop); + return NULL; +} + +SPA_EXPORT +void pa_mainloop_free(pa_mainloop* m) +{ + pw_loop_destroy(m->loop); + free(m); +} + +SPA_EXPORT +int pa_mainloop_prepare(pa_mainloop *m, int timeout) +{ + if (m->quit) + return -2; + m->timeout = timeout; + m->n_events = -EIO; + return 0; +} + +/** Execute the previously prepared poll. Returns a negative value on error.*/ +SPA_EXPORT +int pa_mainloop_poll(pa_mainloop *m) +{ + int res; + + if (m->quit) + return -2; + + res = pw_loop_iterate(m->loop, m->timeout); + if (res < 0) { + if (res == -EINTR) + res = 0; + } + m->n_events = res; + return res; +} + +SPA_EXPORT +int pa_mainloop_dispatch(pa_mainloop *m) +{ + if (m->quit) + return -2; + + return m->n_events; +} + +SPA_EXPORT +int pa_mainloop_get_retval(PA_CONST pa_mainloop *m) +{ + return m->retval; +} + +/** Run a single iteration of the main loop. This is a convenience function +for pa_mainloop_prepare(), pa_mainloop_poll() and pa_mainloop_dispatch(). +Returns a negative value on error or exit request. If block is nonzero, +block for events if none are queued. Optionally return the return value as +specified with the main loop's quit() routine in the integer variable retval points +to. On success returns the number of sources dispatched in this iteration. */ +SPA_EXPORT +int pa_mainloop_iterate(pa_mainloop *m, int block, int *retval) +{ + int r; + pa_assert(m); + + if ((r = pa_mainloop_prepare(m, block ? -1 : 0)) < 0) + goto quit; + + if ((r = pa_mainloop_poll(m)) < 0) + goto quit; + + if ((r = pa_mainloop_dispatch(m)) < 0) + goto quit; + + return r; + + quit: + if ((r == -2) && retval) + *retval = pa_mainloop_get_retval(m); + return r; +} + +SPA_EXPORT +int pa_mainloop_run(pa_mainloop *m, int *retval) +{ + int r; + + while ((r = pa_mainloop_iterate(m, 1, retval)) >= 0) + ; + + if (r == -2) + return 1; + else + return -1; +} + + +SPA_EXPORT +pa_mainloop_api* pa_mainloop_get_api(pa_mainloop *m) +{ + pa_assert(m); + return &m->api; +} + +SPA_EXPORT +void pa_mainloop_quit(pa_mainloop *m, int retval) +{ + pa_assert(m); + m->api.quit(&m->api, retval); +} + +SPA_EXPORT +void pa_mainloop_wakeup(pa_mainloop *m) +{ + pa_assert(m); + pw_loop_signal_event(m->loop, m->event); +} + +SPA_EXPORT +void pa_mainloop_set_poll_func(pa_mainloop *m, pa_poll_func poll_func, void *userdata) +{ + pw_log_warn("Not Implemented"); +} + + +struct once_info { + void (*callback)(pa_mainloop_api*m, void *userdata); + void *userdata; +}; + +static void once_callback(pa_mainloop_api *m, pa_defer_event *e, void *userdata) { + struct once_info *i = userdata; + + pa_assert(m); + pa_assert(i); + + pa_assert(i->callback); + i->callback(m, i->userdata); + + pa_assert(m->defer_free); + m->defer_free(e); +} + +static void free_callback(pa_mainloop_api *m, pa_defer_event *e, void *userdata) { + struct once_info *i = userdata; + + pa_assert(m); + pa_assert(i); + pa_xfree(i); +} + + +void pa_mainloop_api_once(pa_mainloop_api* m, void (*callback)(pa_mainloop_api *m, void *userdata), void *userdata) { + struct once_info *i; + pa_defer_event *e; + + pa_assert(m); + pa_assert(callback); + + pa_init_i18n(); + + i = pa_xnew(struct once_info, 1); + i->callback = callback; + i->userdata = userdata; + + pa_assert(m->defer_new); + pa_assert_se(e = m->defer_new(m, once_callback, i)); + m->defer_set_destroy(e, free_callback); +} diff --git a/pipewire-pulseaudio/src/map-file b/pipewire-pulseaudio/src/map-file new file mode 100644 index 000000000..9b6cba223 --- /dev/null +++ b/pipewire-pulseaudio/src/map-file @@ -0,0 +1,386 @@ +PULSE_0 { +global: +pa_ascii_filter; +pa_ascii_valid; +pa_bytes_per_second; +pa_bytes_snprint; +pa_bytes_to_usec; +pa_channel_map_can_balance; +pa_channel_map_can_fade; +pa_channel_map_can_lfe_balance; +pa_channel_map_compatible; +pa_channel_map_equal; +pa_channel_map_has_position; +pa_channel_map_init; +pa_channel_map_init_auto; +pa_channel_map_init_extend; +pa_channel_map_init_mono; +pa_channel_map_init_stereo; +pa_channel_map_mask; +pa_channel_map_parse; +pa_channel_map_snprint; +pa_channel_map_superset; +pa_channel_map_to_name; +pa_channel_map_to_pretty_name; +pa_channel_map_valid; +pa_channel_position_from_string; +pa_channel_position_to_pretty_string; +pa_channel_position_to_string; +pa_channels_valid; +pa_context_add_autoload; +pa_context_connect; +pa_context_disconnect; +pa_context_drain; +pa_context_errno; +pa_context_exit_daemon; +pa_context_get_autoload_info_by_index; +pa_context_get_autoload_info_by_name; +pa_context_get_autoload_info_list; +pa_context_get_card_info_by_index; +pa_context_get_card_info_by_name; +pa_context_get_card_info_list; +pa_context_get_client_info; +pa_context_get_client_info_list; +pa_context_get_index; +pa_context_get_module_info; +pa_context_get_module_info_list; +pa_context_get_protocol_version; +pa_context_get_sample_info_by_index; +pa_context_get_sample_info_by_name; +pa_context_get_sample_info_list; +pa_context_get_server; +pa_context_get_server_info; +pa_context_get_server_protocol_version; +pa_context_get_sink_info_by_index; +pa_context_get_sink_info_by_name; +pa_context_get_sink_info_list; +pa_context_get_sink_input_info; +pa_context_get_sink_input_info_list; +pa_context_get_source_info_by_index; +pa_context_get_source_info_by_name; +pa_context_get_source_info_list; +pa_context_get_source_output_info; +pa_context_get_source_output_info_list; +pa_context_set_port_latency_offset; +pa_context_get_state; +pa_context_get_tile_size; +pa_context_is_local; +pa_context_is_pending; +pa_context_kill_client; +pa_context_kill_sink_input; +pa_context_kill_source_output; +pa_context_load_cookie_from_file; +pa_context_load_module; +pa_context_move_sink_input_by_index; +pa_context_move_sink_input_by_name; +pa_context_move_source_output_by_index; +pa_context_move_source_output_by_name; +pa_context_new; +pa_context_new_with_proplist; +pa_context_play_sample; +pa_context_play_sample_with_proplist; +pa_context_proplist_remove; +pa_context_proplist_update; +pa_context_ref; +pa_context_remove_autoload_by_index; +pa_context_remove_autoload_by_name; +pa_context_remove_sample; +pa_context_rttime_new; +pa_context_rttime_restart; +pa_context_set_card_profile_by_index; +pa_context_set_card_profile_by_name; +pa_context_set_default_sink; +pa_context_set_default_source; +pa_context_set_event_callback; +pa_context_set_name; +pa_context_set_sink_input_mute; +pa_context_set_sink_input_volume; +pa_context_set_sink_mute_by_index; +pa_context_set_sink_mute_by_name; +pa_context_set_sink_port_by_index; +pa_context_set_sink_port_by_name; +pa_context_set_sink_volume_by_index; +pa_context_set_sink_volume_by_name; +pa_context_set_source_output_mute; +pa_context_set_source_output_volume; +pa_context_set_source_mute_by_index; +pa_context_set_source_mute_by_name; +pa_context_set_source_port_by_index; +pa_context_set_source_port_by_name; +pa_context_set_source_volume_by_index; +pa_context_set_source_volume_by_name; +pa_context_set_state_callback; +pa_context_set_subscribe_callback; +pa_context_stat; +pa_context_subscribe; +pa_context_suspend_sink_by_index; +pa_context_suspend_sink_by_name; +pa_context_suspend_source_by_index; +pa_context_suspend_source_by_name; +pa_context_unload_module; +pa_context_unref; +pa_cvolume_avg; +pa_cvolume_avg_mask; +pa_cvolume_channels_equal_to; +pa_cvolume_compatible; +pa_cvolume_compatible_with_channel_map; +pa_cvolume_dec; +pa_cvolume_equal; +pa_cvolume_get_balance; +pa_cvolume_get_fade; +pa_cvolume_get_lfe_balance; +pa_cvolume_get_position; +pa_cvolume_inc; +pa_cvolume_inc_clamp; +pa_cvolume_init; +pa_cvolume_max; +pa_cvolume_max_mask; +pa_cvolume_merge; +pa_cvolume_min; +pa_cvolume_min_mask; +pa_cvolume_remap; +pa_cvolume_scale; +pa_cvolume_scale_mask; +pa_cvolume_set; +pa_cvolume_set_balance; +pa_cvolume_set_fade; +pa_cvolume_set_lfe_balance; +pa_cvolume_set_position; +pa_cvolume_snprint; +pa_cvolume_snprint_verbose; +pa_cvolume_valid; +pa_direction_to_string; +pa_direction_valid; +pa_encoding_from_string; +pa_encoding_to_string; +pa_ext_device_manager_delete; +pa_ext_device_manager_enable_role_device_priority_routing; +pa_ext_device_manager_read; +pa_ext_device_manager_reorder_devices_for_role; +pa_ext_device_manager_set_device_description; +pa_ext_device_manager_set_subscribe_cb; +pa_ext_device_manager_subscribe; +pa_ext_device_manager_test; +pa_ext_device_restore_read_formats; +pa_ext_device_restore_read_formats_all; +pa_ext_device_restore_save_formats; +pa_ext_device_restore_set_subscribe_cb; +pa_ext_device_restore_subscribe; +pa_ext_device_restore_test; +pa_ext_stream_restore_delete; +pa_ext_stream_restore_read; +pa_ext_stream_restore_set_subscribe_cb; +pa_ext_stream_restore_subscribe; +pa_ext_stream_restore_test; +pa_ext_stream_restore_write; +pa_format_info_copy; +pa_format_info_free; +pa_format_info_from_string; +pa_format_info_from_sample_spec; +pa_format_info_get_prop_type; +pa_format_info_get_prop_int; +pa_format_info_get_prop_int_range; +pa_format_info_get_prop_int_array; +pa_format_info_get_prop_string; +pa_format_info_get_prop_string_array; +pa_format_info_free_string_array; +pa_format_info_is_compatible; +pa_format_info_is_pcm; +pa_format_info_new; +pa_format_info_set_channel_map; +pa_format_info_set_channels; +pa_format_info_set_prop_int; +pa_format_info_set_prop_int_array; +pa_format_info_set_prop_int_range; +pa_format_info_set_prop_string; +pa_format_info_set_prop_string_array; +pa_format_info_set_rate; +pa_format_info_set_sample_format; +pa_format_info_snprint; +pa_format_info_to_sample_spec; +pa_format_info_valid; +pa_frame_size; +pa_get_binary_name; +pa_get_fqdn; +pa_get_home_dir; +pa_get_host_name; +pa_get_library_version; +pa_gettimeofday; +pa_get_user_name; +pa_glib_mainloop_free; +pa_glib_mainloop_get_api; +pa_glib_mainloop_new; +pa_locale_to_utf8; +pa_mainloop_api_once; +pa_mainloop_dispatch; +pa_mainloop_free; +pa_mainloop_get_api; +pa_mainloop_get_retval; +pa_mainloop_iterate; +pa_mainloop_new; +pa_mainloop_poll; +pa_mainloop_prepare; +pa_mainloop_quit; +pa_mainloop_run; +pa_mainloop_set_poll_func; +pa_mainloop_wakeup; +pa_msleep; +pa_operation_cancel; +pa_operation_get_state; +pa_operation_ref; +pa_operation_set_state_callback; +pa_operation_unref; +pa_parse_sample_format; +pa_path_get_filename; +pa_proplist_clear; +pa_proplist_contains; +pa_proplist_copy; +pa_proplist_equal; +pa_proplist_free; +pa_proplist_from_string; +pa_proplist_get; +pa_proplist_gets; +pa_proplist_isempty; +pa_proplist_iterate; +pa_proplist_key_valid; +pa_proplist_new; +pa_proplist_set; +pa_proplist_setf; +pa_proplist_setp; +pa_proplist_sets; +pa_proplist_size; +pa_proplist_to_string; +pa_proplist_to_string_sep; +pa_proplist_unset; +pa_proplist_unset_many; +pa_proplist_update; +pa_rtclock_now; +pa_sample_format_is_be; +pa_sample_format_is_le; +pa_sample_format_to_string; +pa_sample_format_valid; +pa_sample_rate_valid; +pa_sample_size; +pa_sample_size_of_format; +pa_sample_spec_equal; +pa_sample_spec_init; +pa_sample_spec_snprint; +pa_sample_spec_valid; +pa_signal_done; +pa_signal_free; +pa_signal_init; +pa_signal_new; +pa_signal_set_destroy; +pa_simple_drain; +pa_simple_flush; +pa_simple_free; +pa_simple_get_latency; +pa_simple_new; +pa_simple_read; +pa_simple_write; +pa_stream_begin_write; +pa_stream_cancel_write; +pa_stream_connect_playback; +pa_stream_connect_record; +pa_stream_connect_upload; +pa_stream_cork; +pa_stream_disconnect; +pa_stream_drain; +pa_stream_drop; +pa_stream_finish_upload; +pa_stream_flush; +pa_stream_get_buffer_attr; +pa_stream_get_channel_map; +pa_stream_get_context; +pa_stream_get_device_index; +pa_stream_get_device_name; +pa_stream_get_format_info; +pa_stream_get_index; +pa_stream_get_latency; +pa_stream_get_monitor_stream; +pa_stream_get_sample_spec; +pa_stream_get_state; +pa_stream_get_time; +pa_stream_get_timing_info; +pa_stream_get_underflow_index; +pa_stream_is_corked; +pa_stream_is_suspended; +pa_stream_new; +pa_stream_new_extended; +pa_stream_new_with_proplist; +pa_stream_peek; +pa_stream_prebuf; +pa_stream_proplist_remove; +pa_stream_proplist_update; +pa_stream_readable_size; +pa_stream_ref; +pa_stream_set_buffer_attr; +pa_stream_set_buffer_attr_callback; +pa_stream_set_event_callback; +pa_stream_set_latency_update_callback; +pa_stream_set_monitor_stream; +pa_stream_set_moved_callback; +pa_stream_set_name; +pa_stream_set_overflow_callback; +pa_stream_set_read_callback; +pa_stream_set_started_callback; +pa_stream_set_state_callback; +pa_stream_set_suspended_callback; +pa_stream_set_underflow_callback; +pa_stream_set_write_callback; +pa_stream_trigger; +pa_stream_unref; +pa_stream_update_sample_rate; +pa_stream_update_timing_info; +pa_stream_writable_size; +pa_stream_write; +pa_stream_write_ext_free; +pa_strerror; +pa_sw_cvolume_divide; +pa_sw_cvolume_divide_scalar; +pa_sw_cvolume_multiply; +pa_sw_cvolume_multiply_scalar; +pa_sw_cvolume_snprint_dB; +pa_sw_volume_divide; +pa_sw_volume_from_dB; +pa_sw_volume_from_linear; +pa_sw_volume_multiply; +pa_sw_volume_snprint_dB; +pa_sw_volume_to_dB; +pa_sw_volume_to_linear; +pa_threaded_mainloop_accept; +pa_threaded_mainloop_free; +pa_threaded_mainloop_get_api; +pa_threaded_mainloop_get_retval; +pa_threaded_mainloop_in_thread; +pa_threaded_mainloop_lock; +pa_threaded_mainloop_new; +pa_threaded_mainloop_set_name; +pa_threaded_mainloop_signal; +pa_threaded_mainloop_start; +pa_threaded_mainloop_stop; +pa_threaded_mainloop_unlock; +pa_threaded_mainloop_wait; +pa_timeval_add; +pa_timeval_age; +pa_timeval_cmp; +pa_timeval_diff; +pa_timeval_load; +pa_timeval_store; +pa_timeval_sub; +pa_usec_to_bytes; +pa_utf8_filter; +pa_utf8_to_locale; +pa_utf8_valid; +pa_volume_snprint; +pa_volume_snprint_verbose; +pa_xfree; +pa_xmalloc; +pa_xmalloc0; +pa_xmemdup; +pa_xrealloc; +pa_xstrdup; +pa_xstrndup; +local: +*; +}; diff --git a/pipewire-pulseaudio/src/meson.build b/pipewire-pulseaudio/src/meson.build new file mode 100644 index 000000000..d0955941d --- /dev/null +++ b/pipewire-pulseaudio/src/meson.build @@ -0,0 +1,65 @@ +pipewire_pulseaudio_sources = [ + 'bitset.c', + 'channelmap.c', + 'context.c', + 'core-format.c', + 'direction.c', + 'error.c', + 'ext-device-manager.c', + 'ext-device-restore.c', + 'ext-stream-restore.c', + 'format.c', + 'introspect.c', + 'json.c', + 'mainloop.c', + 'mainloop-signal.c', + 'operation.c', + 'proplist.c', + 'rtclock.c', + 'sample.c', + 'scache.c', + 'stream.c', + 'strbuf.c', + 'subscribe.c', + 'thread-mainloop.c', + 'timeval.c', + 'utf8.c', + 'util.c', + 'version.c', + 'volume.c', + 'xmalloc.c', + 'pipewire-pulseaudio.c', +] + +pipewire_mainloop_glib_sources = [ + 'mainloop-glib.c', +] + +pipewire_pulseaudio_c_args = [ + '-DHAVE_CONFIG_H', + '-D_GNU_SOURCE', + '-DPIC', +] + +mapfile = 'map-file' +vflag = '-Wl,--version-script,@0@/@1@'.format(meson.current_source_dir(), mapfile) + +pipewire_pulseaudio = shared_library('pulse', + pipewire_pulseaudio_sources, + soversion : '0', + c_args : pipewire_pulseaudio_c_args, + link_args : vflag, + include_directories : [configinc], + dependencies : [pipewire_dep, pulseaudio_dep, mathlib], + install : false, +) + +pipewire_pulseaudio = shared_library('pulse-mainloop-glib', + pipewire_mainloop_glib_sources, + soversion : '0', + c_args : pipewire_pulseaudio_c_args, + link_args : vflag, + include_directories : [configinc], + dependencies : [pipewire_dep, pulseaudio_dep, mathlib, glib_dep], + install : false, +) diff --git a/pipewire-pulseaudio/src/operation.c b/pipewire-pulseaudio/src/operation.c new file mode 100644 index 000000000..669c9c037 --- /dev/null +++ b/pipewire-pulseaudio/src/operation.c @@ -0,0 +1,165 @@ +/* PipeWire + * Copyright (C) 2018 Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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. + */ + +#include + +#include + +#include + +#include + +#include "internal.h" + +pa_operation *pa_operation_new(pa_context *c, pa_stream *s, pa_operation_cb_t cb, size_t userdata_size) { + pa_operation *o; + pa_assert(c); + + o = calloc(1, sizeof(pa_operation) + userdata_size); + + o->refcount = 1; + o->context = c; + o->stream = s ? pa_stream_ref(s) : NULL; + o->seq = SPA_ID_INVALID; + + o->state = PA_OPERATION_RUNNING; + o->callback = cb; + o->userdata = SPA_MEMBER(o, sizeof(pa_operation), void); + + spa_list_append(&c->operations, &o->link); + pa_operation_ref(o); + pw_log_debug("new %p", o); + + return o; +} + +int pa_operation_sync(pa_operation *o) +{ + pa_context *c = o->context; + o->seq = pw_core_proxy_sync(c->core_proxy, 0, 0); + pw_log_debug("operation %p: sync %d", o, o->seq); + return 0; +} + +SPA_EXPORT +pa_operation *pa_operation_ref(pa_operation *o) +{ + pa_assert(o); + pa_assert(o->refcount >= 1); + o->refcount++; + return o; +} + +static void operation_free(pa_operation *o) +{ + pa_assert(!o->context); + pa_assert(!o->stream); + pw_log_debug("%p %d", o, o->seq); + free(o); +} + +static void operation_unlink(pa_operation *o) { + pa_assert(o); + + pw_log_debug("%p %d", o, o->seq); + if (o->context) { + pa_assert(o->refcount >= 2); + + spa_list_remove(&o->link); + pa_operation_unref(o); + + o->context = NULL; + } + if (o->stream) + pa_stream_unref(o->stream); + o->stream = NULL; + o->callback = NULL; + o->userdata = NULL; + o->state_callback = NULL; + o->state_userdata = NULL; +} + + +SPA_EXPORT +void pa_operation_unref(pa_operation *o) +{ + pa_assert(o); + pa_assert(o->refcount >= 1); + if (--o->refcount == 0) + operation_free(o); +} + +static void operation_set_state(pa_operation *o, pa_operation_state_t st) { + pa_assert(o); + pa_assert(o->refcount >= 1); + + if (st == o->state) + return; + + pa_operation_ref(o); + + pw_log_debug("new state %p %d %d", o, o->seq, st); + o->state = st; + + if (o->state_callback) + o->state_callback(o, o->state_userdata); + + if ((o->state == PA_OPERATION_DONE) || (o->state == PA_OPERATION_CANCELED)) + operation_unlink(o); + + pa_operation_unref(o); +} + + +SPA_EXPORT +void pa_operation_cancel(pa_operation *o) +{ + pa_assert(o); + pa_assert(o->refcount >= 1); + pw_log_debug("%p %d", o, o->seq); + operation_set_state(o, PA_OPERATION_CANCELED); +} + +void pa_operation_done(pa_operation *o) { + pa_assert(o); + pa_assert(o->refcount >= 1); + operation_set_state(o, PA_OPERATION_DONE); +} + + +SPA_EXPORT +pa_operation_state_t pa_operation_get_state(PA_CONST pa_operation *o) +{ + pa_assert(o); + pa_assert(o->refcount >= 1); + return o->state; +} + +SPA_EXPORT +void pa_operation_set_state_callback(pa_operation *o, pa_operation_notify_cb_t cb, void *userdata) +{ + pa_assert(o); + pa_assert(o->refcount >= 1); + + if (o->state == PA_OPERATION_DONE || o->state == PA_OPERATION_CANCELED) + return; + + o->state_callback = cb; + o->state_userdata = userdata; +} diff --git a/pipewire-pulseaudio/src/pipewire-pulseaudio.c b/pipewire-pulseaudio/src/pipewire-pulseaudio.c new file mode 100644 index 000000000..3b3405984 --- /dev/null +++ b/pipewire-pulseaudio/src/pipewire-pulseaudio.c @@ -0,0 +1,30 @@ +/* PipeWire + * Copyright (C) 2018 Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +static void reg(void) __attribute__ ((constructor)); +static void reg(void) +{ + pw_init(NULL, NULL); +} diff --git a/pipewire-pulseaudio/src/proplist.c b/pipewire-pulseaudio/src/proplist.c new file mode 100644 index 000000000..247920d19 --- /dev/null +++ b/pipewire-pulseaudio/src/proplist.c @@ -0,0 +1,372 @@ +/* PipeWire + * Copyright (C) 2018 Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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. + */ + +#include + +#include + +#include +#include + +#include + +#include "internal.h" +#include "strbuf.h" + +pa_proplist* pa_proplist_new_dict(struct spa_dict *dict) +{ + pa_proplist *p; + + p = calloc(1, sizeof(struct pa_proplist)); + if (p == NULL) + return NULL; + + if (dict) + p->props = pw_properties_new_dict(dict); + else + p->props = pw_properties_new(NULL, NULL); + + return p; +} +pa_proplist* pa_proplist_new_props(struct pw_properties *props) +{ + return pa_proplist_new_dict(&props->dict); +} + +SPA_EXPORT +pa_proplist* pa_proplist_new(void) +{ + return pa_proplist_new_dict(NULL); +} + +int pa_proplist_update_dict(pa_proplist *p, struct spa_dict *dict) +{ + return pw_properties_update(p->props, dict); +} + + +SPA_EXPORT +void pa_proplist_free(pa_proplist* p) +{ + pw_properties_free(p->props); + free(p); +} + +SPA_EXPORT +int pa_proplist_key_valid(const char *key) +{ + const char *p; + for (p = key; *p; p++) + if ((unsigned char) *p >= 128) + return 0; + + if (strlen(key) < 1) + return 0; + + return 1; +} + +SPA_EXPORT +int pa_proplist_sets(pa_proplist *p, const char *key, const char *value) +{ + pa_assert(p); + pa_assert(key); + pa_assert(value); + + if (!pa_proplist_key_valid(key)) + return -1; + + pw_properties_set(p->props, key, value); + return 0; +} + +SPA_EXPORT +int pa_proplist_setp(pa_proplist *p, const char *pair) +{ + const char *t; + char *c; + int idx; + + pa_assert(p); + pa_assert(pair); + + if (!(t = strchr(pair, '='))) + return -1; + + idx = pair - t; + c = strdup(pair); + c[idx] = 0; + pa_proplist_sets(p, c, &c[idx]+1); + free(c); + + return 0; +} + +SPA_EXPORT +int pa_proplist_setf(pa_proplist *p, const char *key, const char *format, ...) +{ + va_list varargs; + + va_start(varargs, format); + pw_properties_setva(p->props, key, format, varargs); + va_end(varargs); + + return 0; +} + +SPA_EXPORT +int pa_proplist_set(pa_proplist *p, const char *key, const void *data, size_t nbytes) +{ + pa_assert(p); + pa_assert(key); + pa_assert(data || nbytes == 0); + + if (!pa_proplist_key_valid(key)) + return -1; + + pw_properties_set(p->props, key, data); + return 0; +} + +SPA_EXPORT +const char *pa_proplist_gets(PA_CONST pa_proplist *p, const char *key) +{ + return pw_properties_get(p->props, key); +} + +SPA_EXPORT +int pa_proplist_get(PA_CONST pa_proplist *p, const char *key, const void **data, size_t *nbytes) +{ + const char *val; + + spa_assert(p); + spa_assert(key); + + val = pw_properties_get(p->props, key); + + *data = val; + *nbytes = val ? strlen(val) : 0; + return 0; +} + +SPA_EXPORT +void pa_proplist_update(pa_proplist *p, pa_update_mode_t mode, const pa_proplist *other) +{ + uint32_t i; + + spa_assert(p); + spa_assert(mode == PA_UPDATE_SET || mode == PA_UPDATE_MERGE || mode == PA_UPDATE_REPLACE); + spa_assert(other); + + if (mode == PA_UPDATE_REPLACE) { + pa_proplist_update_dict(p, &other->props->dict); + return; + } + + if (mode == PA_UPDATE_SET) + pa_proplist_clear(p); + + for (i = 0; i < other->props->dict.n_items; i++) { + const struct spa_dict_item *oi; + + oi = &other->props->dict.items[i]; + if (pw_properties_get(p->props, oi->key) == NULL) + pw_properties_set(p->props, oi->key, oi->value); + } +} + +SPA_EXPORT +int pa_proplist_unset(pa_proplist *p, const char *key) +{ + spa_assert(p); + spa_assert(key); + + if (!pa_proplist_key_valid(key)) + return -1; + + return pw_properties_set(p->props, key, NULL); +} + +SPA_EXPORT +int pa_proplist_unset_many(pa_proplist *p, const char * const keys[]) +{ + const char * const * k; + int n = 0; + + spa_assert(p); + spa_assert(keys); + + for (k = keys; *k; k++) + if (!pa_proplist_key_valid(*k)) + return -1; + + for (k = keys; *k; k++) + if (pa_proplist_unset(p, *k) >= 0) + n++; + return n; +} + +SPA_EXPORT +const char *pa_proplist_iterate(PA_CONST pa_proplist *p, void **state) +{ + spa_assert(p); + spa_assert(state); + return pw_properties_iterate(p->props, state); +} + +SPA_EXPORT +char *pa_proplist_to_string(PA_CONST pa_proplist *p) +{ + spa_assert(p); + return pa_proplist_to_string_sep(p, ","); +} + +SPA_EXPORT +char *pa_proplist_to_string_sep(PA_CONST pa_proplist *p, const char *sep) +{ + const char *key; + void *state = NULL; + pa_strbuf *buf; + + spa_assert(p); + spa_assert(sep); + + buf = pa_strbuf_new(); + + while ((key = pa_proplist_iterate(p, &state))) { + const char *v; + const char *t; + + if (!pa_strbuf_isempty(buf)) + pa_strbuf_puts(buf, sep); + + if ((v = pa_proplist_gets(p, key)) == NULL) + continue; + + pa_strbuf_printf(buf, "%s = \"", key); + + for (t = v;;) { + size_t h; + + h = strcspn(t, "\""); + + if (h > 0) + pa_strbuf_putsn(buf, t, h); + + t += h; + + if (*t == 0) + break; + + pa_assert(*t == '"'); + pa_strbuf_puts(buf, "\\\""); + + t++; + } + pa_strbuf_puts(buf, "\""); + } + return pa_strbuf_to_string_free(buf); +} + +SPA_EXPORT +pa_proplist *pa_proplist_from_string(const char *str) +{ + spa_assert(str); + pw_log_warn("Not Implemented"); + return NULL; +} + +SPA_EXPORT +int pa_proplist_contains(PA_CONST pa_proplist *p, const char *key) +{ + spa_assert(p); + spa_assert(key); + + if (!pa_proplist_key_valid(key)) + return -1; + + if (pw_properties_get(p->props, key) == NULL) + return 0; + + return 1; +} + +SPA_EXPORT +void pa_proplist_clear(pa_proplist *p) +{ + spa_assert(p); + pw_properties_clear(p->props); +} + +SPA_EXPORT +pa_proplist* pa_proplist_copy(const pa_proplist *p) +{ + pa_proplist *c; + + spa_assert(p); + + c = calloc(1, sizeof(struct pa_proplist)); + if (c == NULL) + return NULL; + + c->props = pw_properties_copy(p->props); + return c; +} + +SPA_EXPORT +unsigned pa_proplist_size(PA_CONST pa_proplist *p) +{ + spa_assert(p); + return p->props->dict.n_items; +} + +SPA_EXPORT +int pa_proplist_isempty(PA_CONST pa_proplist *p) +{ + spa_assert(p); + return p->props->dict.n_items == 0 ? 1 : 0; +} + +SPA_EXPORT +int pa_proplist_equal(PA_CONST pa_proplist *a, PA_CONST pa_proplist *b) +{ + uint32_t i; + + spa_assert(a); + spa_assert(b); + + if (a == b) + return 1; + + if (pa_proplist_size(a) != pa_proplist_size(b)) + return 0; + + for (i = 0; i < a->props->dict.n_items; i++) { + const struct spa_dict_item *ai, *bi; + + ai = &a->props->dict.items[i]; + bi = spa_dict_lookup_item(&b->props->dict, ai->key); + + if (bi == NULL || bi->value == NULL || ai->value == NULL) + return 0; + if (strcmp(ai->value, bi->value) != 0) + return 0; + } + return 1; +} diff --git a/pipewire-pulseaudio/src/rtclock.c b/pipewire-pulseaudio/src/rtclock.c new file mode 100644 index 000000000..a3e5e4cb0 --- /dev/null +++ b/pipewire-pulseaudio/src/rtclock.c @@ -0,0 +1,38 @@ +/* PipeWire + * Copyright (C) 2018 Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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. + */ + +#include + +#include + +#include + +#include + +SPA_EXPORT +pa_usec_t pa_rtclock_now(void) +{ + struct timespec ts; + pa_usec_t res; + + clock_gettime(CLOCK_MONOTONIC, &ts); + res = (ts.tv_sec * SPA_USEC_PER_SEC) + (ts.tv_nsec / SPA_NSEC_PER_USEC); + return res; +} + diff --git a/pipewire-pulseaudio/src/sample-util.h b/pipewire-pulseaudio/src/sample-util.h new file mode 100644 index 000000000..45528ef6d --- /dev/null +++ b/pipewire-pulseaudio/src/sample-util.h @@ -0,0 +1,134 @@ +#ifndef foosampleutilhfoo +#define foosampleutilhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 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.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, see . +***/ + +#include +#include + +#include +#include +#include +#include + +size_t pa_frame_align(size_t l, const pa_sample_spec *ss) PA_GCC_PURE; + +bool pa_frame_aligned(size_t l, const pa_sample_spec *ss) PA_GCC_PURE; + +void pa_interleave(const void *src[], unsigned channels, void *dst, size_t ss, unsigned n); +void pa_deinterleave(const void *src, void *dst[], unsigned channels, size_t ss, unsigned n); + +void pa_sample_clamp(pa_sample_format_t format, void *dst, size_t dstr, const void *src, size_t sstr, unsigned n); + +static inline int32_t pa_mult_s16_volume(int16_t v, int32_t cv) { +#ifdef HAVE_FAST_64BIT_OPERATIONS + /* Multiply with 64 bit integers on 64 bit platforms */ + return (v * (int64_t) cv) >> 16; +#else + /* Multiplying the 32 bit volume factor with the + * 16 bit sample might result in an 48 bit value. We + * want to do without 64 bit integers and hence do + * the multiplication independently for the HI and + * LO part of the volume. */ + + int32_t hi = cv >> 16; + int32_t lo = cv & 0xFFFF; + return ((v * lo) >> 16) + (v * hi); +#endif +} + +pa_usec_t pa_bytes_to_usec_round_up(uint64_t length, const pa_sample_spec *spec); +size_t pa_usec_to_bytes_round_up(pa_usec_t t, const pa_sample_spec *spec); + +typedef void (*pa_do_volume_func_t) (void *samples, const void *volumes, unsigned channels, unsigned length); + +pa_do_volume_func_t pa_get_volume_func(pa_sample_format_t f); +void pa_set_volume_func(pa_sample_format_t f, pa_do_volume_func_t func); + +size_t pa_convert_size(size_t size, const pa_sample_spec *from, const pa_sample_spec *to); + +#define PA_CHANNEL_POSITION_MASK_LEFT \ + (PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_FRONT_LEFT) \ + | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_REAR_LEFT) \ + | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER) \ + | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_SIDE_LEFT) \ + | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_FRONT_LEFT) \ + | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_REAR_LEFT)) \ + +#define PA_CHANNEL_POSITION_MASK_RIGHT \ + (PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_FRONT_RIGHT) \ + | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_REAR_RIGHT) \ + | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER) \ + | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_SIDE_RIGHT) \ + | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_FRONT_RIGHT) \ + | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_REAR_RIGHT)) + +#define PA_CHANNEL_POSITION_MASK_CENTER \ + (PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_FRONT_CENTER) \ + | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_REAR_CENTER) \ + | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_CENTER) \ + | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_FRONT_CENTER) \ + | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_REAR_CENTER)) + +#define PA_CHANNEL_POSITION_MASK_FRONT \ + (PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_FRONT_LEFT) \ + | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_FRONT_RIGHT) \ + | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_FRONT_CENTER) \ + | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER) \ + | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER) \ + | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_FRONT_LEFT) \ + | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_FRONT_RIGHT) \ + | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_FRONT_CENTER)) + +#define PA_CHANNEL_POSITION_MASK_REAR \ + (PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_REAR_LEFT) \ + | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_REAR_RIGHT) \ + | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_REAR_CENTER) \ + | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_REAR_LEFT) \ + | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_REAR_RIGHT) \ + | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_REAR_CENTER)) + +#define PA_CHANNEL_POSITION_MASK_LFE \ + PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_LFE) + +#define PA_CHANNEL_POSITION_MASK_HFE \ + (PA_CHANNEL_POSITION_MASK_REAR | PA_CHANNEL_POSITION_MASK_FRONT \ + | PA_CHANNEL_POSITION_MASK_LEFT | PA_CHANNEL_POSITION_MASK_RIGHT \ + | PA_CHANNEL_POSITION_MASK_CENTER) + +#define PA_CHANNEL_POSITION_MASK_SIDE_OR_TOP_CENTER \ + (PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_SIDE_LEFT) \ + | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_SIDE_RIGHT) \ + | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_CENTER)) + +#define PA_CHANNEL_POSITION_MASK_TOP \ + (PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_CENTER) \ + | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_FRONT_LEFT) \ + | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_FRONT_RIGHT) \ + | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_FRONT_CENTER) \ + | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_REAR_LEFT) \ + | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_REAR_RIGHT) \ + | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_REAR_CENTER)) + +#define PA_CHANNEL_POSITION_MASK_ALL \ + ((pa_channel_position_mask_t) (PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_MAX)-1)) + +#endif diff --git a/pipewire-pulseaudio/src/sample.c b/pipewire-pulseaudio/src/sample.c new file mode 100644 index 000000000..07e903219 --- /dev/null +++ b/pipewire-pulseaudio/src/sample.c @@ -0,0 +1,337 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman for Cendio AB + Copyright 2018 Wim Taymans + + 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, see . +***/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#include + +#include +#include + +#define pa_init_i18n() +#define _(String) (String) + +static const size_t size_table[] = { + [PA_SAMPLE_U8] = 1, + [PA_SAMPLE_ULAW] = 1, + [PA_SAMPLE_ALAW] = 1, + [PA_SAMPLE_S16LE] = 2, + [PA_SAMPLE_S16BE] = 2, + [PA_SAMPLE_FLOAT32LE] = 4, + [PA_SAMPLE_FLOAT32BE] = 4, + [PA_SAMPLE_S32LE] = 4, + [PA_SAMPLE_S32BE] = 4, + [PA_SAMPLE_S24LE] = 3, + [PA_SAMPLE_S24BE] = 3, + [PA_SAMPLE_S24_32LE] = 4, + [PA_SAMPLE_S24_32BE] = 4 +}; + +SPA_EXPORT +size_t pa_sample_size_of_format(pa_sample_format_t f) +{ + spa_assert(pa_sample_format_valid(f)); + + return size_table[f]; +} + +SPA_EXPORT +size_t pa_sample_size(const pa_sample_spec * spec) +{ + spa_assert(spec); + spa_assert(pa_sample_spec_valid(spec)); + + return size_table[spec->format]; +} + +SPA_EXPORT +size_t pa_frame_size(const pa_sample_spec * spec) +{ + spa_assert(spec); + spa_assert(pa_sample_spec_valid(spec)); + + return size_table[spec->format] * spec->channels; +} + +SPA_EXPORT +size_t pa_bytes_per_second(const pa_sample_spec * spec) +{ + spa_assert(spec); + spa_assert(pa_sample_spec_valid(spec)); + + return spec->rate * size_table[spec->format] * spec->channels; +} + +SPA_EXPORT +pa_usec_t pa_bytes_to_usec(uint64_t length, const pa_sample_spec * spec) +{ + spa_assert(spec); + spa_assert(pa_sample_spec_valid(spec)); + + return (((pa_usec_t) + (length / (size_table[spec->format] * spec->channels)) + * PA_USEC_PER_SEC) / spec->rate); +} + +SPA_EXPORT +size_t pa_usec_to_bytes(pa_usec_t t, const pa_sample_spec * spec) +{ + spa_assert(spec); + spa_assert(pa_sample_spec_valid(spec)); + + return (size_t) (((t * spec->rate) / PA_USEC_PER_SEC)) * + (size_table[spec->format] * spec->channels); +} + +SPA_EXPORT +pa_sample_spec *pa_sample_spec_init(pa_sample_spec * spec) +{ + spa_assert(spec); + + spec->format = PA_SAMPLE_INVALID; + spec->rate = 0; + spec->channels = 0; + + return spec; +} + +SPA_EXPORT +int pa_sample_format_valid(unsigned format) +{ + return format < PA_SAMPLE_MAX; +} + +SPA_EXPORT +int pa_sample_rate_valid(uint32_t rate) +{ + /* The extra 1% is due to module-loopback: it temporarily sets + * a higher-than-nominal rate to get rid of excessive buffer + * latency */ + return rate > 0 && rate <= PA_RATE_MAX * 101 / 100; +} + +SPA_EXPORT +int pa_channels_valid(uint8_t channels) +{ + return channels > 0 && channels <= PA_CHANNELS_MAX; +} + +SPA_EXPORT +int pa_sample_spec_valid(const pa_sample_spec * spec) +{ + spa_assert(spec); + + if (SPA_UNLIKELY(!pa_sample_rate_valid(spec->rate) || + !pa_channels_valid(spec->channels) || + !pa_sample_format_valid(spec->format))) + return 0; + + return 1; +} + +SPA_EXPORT +int pa_sample_spec_equal(const pa_sample_spec * a, const pa_sample_spec * b) +{ + spa_assert(a); + spa_assert(b); + + spa_return_val_if_fail(pa_sample_spec_valid(a), 0); + + if (SPA_UNLIKELY(a == b)) + return 1; + + spa_return_val_if_fail(pa_sample_spec_valid(b), 0); + + return + (a->format == b->format) && + (a->rate == b->rate) && (a->channels == b->channels); +} + +SPA_EXPORT +const char *pa_sample_format_to_string(pa_sample_format_t f) +{ + static const char *const table[] = { + [PA_SAMPLE_U8] = "u8", + [PA_SAMPLE_ALAW] = "aLaw", + [PA_SAMPLE_ULAW] = "uLaw", + [PA_SAMPLE_S16LE] = "s16le", + [PA_SAMPLE_S16BE] = "s16be", + [PA_SAMPLE_FLOAT32LE] = "float32le", + [PA_SAMPLE_FLOAT32BE] = "float32be", + [PA_SAMPLE_S32LE] = "s32le", + [PA_SAMPLE_S32BE] = "s32be", + [PA_SAMPLE_S24LE] = "s24le", + [PA_SAMPLE_S24BE] = "s24be", + [PA_SAMPLE_S24_32LE] = "s24-32le", + [PA_SAMPLE_S24_32BE] = "s24-32be", + }; + + if (!pa_sample_format_valid(f)) + return NULL; + + return table[f]; +} + +SPA_EXPORT +char *pa_sample_spec_snprint(char *s, size_t l, const pa_sample_spec * spec) +{ + spa_assert(s); + spa_assert(l > 0); + spa_assert(spec); + + pa_init_i18n(); + + if (!pa_sample_spec_valid(spec)) + snprintf(s, l, _("(invalid)")); + else + snprintf(s, l, _("%s %uch %uHz"), + pa_sample_format_to_string(spec->format), + spec->channels, spec->rate); + + return s; +} + +SPA_EXPORT +char *pa_bytes_snprint(char *s, size_t l, unsigned v) +{ + spa_assert(s); + spa_assert(l > 0); + + pa_init_i18n(); + + if (v >= ((unsigned)1024) * 1024 * 1024) + snprintf(s, l, _("%0.1f GiB"), + ((double)v) / 1024 / 1024 / 1024); + else if (v >= ((unsigned)1024) * 1024) + snprintf(s, l, _("%0.1f MiB"), ((double)v) / 1024 / 1024); + else if (v >= (unsigned)1024) + snprintf(s, l, _("%0.1f KiB"), ((double)v) / 1024); + else + snprintf(s, l, _("%u B"), (unsigned)v); + + return s; +} + +SPA_EXPORT +pa_sample_format_t pa_parse_sample_format(const char *format) +{ + spa_assert(format); + + if (strcasecmp(format, "s16le") == 0) + return PA_SAMPLE_S16LE; + else if (strcasecmp(format, "s16be") == 0) + return PA_SAMPLE_S16BE; + else if (strcasecmp(format, "s16ne") == 0 + || strcasecmp(format, "s16") == 0 + || strcasecmp(format, "16") == 0) + return PA_SAMPLE_S16NE; + else if (strcasecmp(format, "s16re") == 0) + return PA_SAMPLE_S16RE; + else if (strcasecmp(format, "u8") == 0 || strcasecmp(format, "8") == 0) + return PA_SAMPLE_U8; + else if (strcasecmp(format, "float32") == 0 + || strcasecmp(format, "float32ne") == 0 + || strcasecmp(format, "float") == 0) + return PA_SAMPLE_FLOAT32NE; + else if (strcasecmp(format, "float32re") == 0) + return PA_SAMPLE_FLOAT32RE; + else if (strcasecmp(format, "float32le") == 0) + return PA_SAMPLE_FLOAT32LE; + else if (strcasecmp(format, "float32be") == 0) + return PA_SAMPLE_FLOAT32BE; + else if (strcasecmp(format, "ulaw") == 0 + || strcasecmp(format, "mulaw") == 0) + return PA_SAMPLE_ULAW; + else if (strcasecmp(format, "alaw") == 0) + return PA_SAMPLE_ALAW; + else if (strcasecmp(format, "s32le") == 0) + return PA_SAMPLE_S32LE; + else if (strcasecmp(format, "s32be") == 0) + return PA_SAMPLE_S32BE; + else if (strcasecmp(format, "s32ne") == 0 + || strcasecmp(format, "s32") == 0 + || strcasecmp(format, "32") == 0) + return PA_SAMPLE_S32NE; + else if (strcasecmp(format, "s32re") == 0) + return PA_SAMPLE_S24RE; + else if (strcasecmp(format, "s24le") == 0) + return PA_SAMPLE_S24LE; + else if (strcasecmp(format, "s24be") == 0) + return PA_SAMPLE_S24BE; + else if (strcasecmp(format, "s24ne") == 0 + || strcasecmp(format, "s24") == 0 + || strcasecmp(format, "24") == 0) + return PA_SAMPLE_S24NE; + else if (strcasecmp(format, "s24re") == 0) + return PA_SAMPLE_S24RE; + else if (strcasecmp(format, "s24-32le") == 0) + return PA_SAMPLE_S24_32LE; + else if (strcasecmp(format, "s24-32be") == 0) + return PA_SAMPLE_S24_32BE; + else if (strcasecmp(format, "s24-32ne") == 0 + || strcasecmp(format, "s24-32") == 0) + return PA_SAMPLE_S24_32NE; + else if (strcasecmp(format, "s24-32re") == 0) + return PA_SAMPLE_S24_32RE; + + return PA_SAMPLE_INVALID; +} + +SPA_EXPORT +int pa_sample_format_is_le(pa_sample_format_t f) +{ + spa_assert(pa_sample_format_valid(f)); + + switch (f) { + case PA_SAMPLE_S16LE: + case PA_SAMPLE_S24LE: + case PA_SAMPLE_S32LE: + case PA_SAMPLE_S24_32LE: + case PA_SAMPLE_FLOAT32LE: + return 1; + + case PA_SAMPLE_S16BE: + case PA_SAMPLE_S24BE: + case PA_SAMPLE_S32BE: + case PA_SAMPLE_S24_32BE: + case PA_SAMPLE_FLOAT32BE: + return 0; + + default: + return -1; + } +} + +SPA_EXPORT +int pa_sample_format_is_be(pa_sample_format_t f) +{ + int r; + + if ((r = pa_sample_format_is_le(f)) < 0) + return r; + + return !r; +} diff --git a/pipewire-pulseaudio/src/scache.c b/pipewire-pulseaudio/src/scache.c new file mode 100644 index 000000000..65f725f2d --- /dev/null +++ b/pipewire-pulseaudio/src/scache.c @@ -0,0 +1,62 @@ +/* PipeWire + * Copyright (C) 2018 Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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. + */ + +#include + +#include + +#include "internal.h" + +SPA_EXPORT +int pa_stream_connect_upload(pa_stream *s, size_t length) +{ + pw_log_warn("Not Implemented"); + return 0; +} + +SPA_EXPORT +int pa_stream_finish_upload(pa_stream *s) +{ + pw_log_warn("Not Implemented"); + return 0; +} + +SPA_EXPORT +pa_operation* pa_context_remove_sample(pa_context *c, const char *name, pa_context_success_cb_t cb, void *userdata) +{ + pw_log_warn("Not Implemented"); + return NULL; +} + +SPA_EXPORT +pa_operation* pa_context_play_sample(pa_context *c, const char *name, const char *dev, + pa_volume_t volume, pa_context_success_cb_t cb, void *userdata) +{ + pw_log_warn("Not Implemented"); + return NULL; +} + +SPA_EXPORT +pa_operation* pa_context_play_sample_with_proplist(pa_context *c, const char *name, + const char *dev, pa_volume_t volume, PA_CONST pa_proplist *proplist, + pa_context_play_sample_cb_t cb, void *userdata) +{ + pw_log_warn("Not Implemented"); + return NULL; +} diff --git a/pipewire-pulseaudio/src/strbuf.c b/pipewire-pulseaudio/src/strbuf.c new file mode 100644 index 000000000..9e281d578 --- /dev/null +++ b/pipewire-pulseaudio/src/strbuf.c @@ -0,0 +1,193 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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, see . +***/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include + +#include + +#include "internal.h" +#include "strbuf.h" + +/* A chunk of the linked list that makes up the string */ +struct chunk { + struct chunk *next; + size_t length; +}; + +#define CHUNK_TO_TEXT(c) ((char*) (c) + PA_ALIGN(sizeof(struct chunk))) + +struct pa_strbuf { + size_t length; + struct chunk *head, *tail; +}; + +pa_strbuf *pa_strbuf_new(void) { + pa_strbuf *sb; + + sb = pa_xnew(pa_strbuf, 1); + sb->length = 0; + sb->head = sb->tail = NULL; + + return sb; +} + +void pa_strbuf_free(pa_strbuf *sb) { + pa_assert(sb); + + while (sb->head) { + struct chunk *c = sb->head; + sb->head = sb->head->next; + pa_xfree(c); + } + + pa_xfree(sb); +} + +/* Make a C string from the string buffer. The caller has to free + * string with pa_xfree(). */ +char *pa_strbuf_to_string(pa_strbuf *sb) { + char *t, *e; + struct chunk *c; + + pa_assert(sb); + + e = t = pa_xmalloc(sb->length+1); + + for (c = sb->head; c; c = c->next) { + pa_assert((size_t) (e-t) <= sb->length); + memcpy(e, CHUNK_TO_TEXT(c), c->length); + e += c->length; + } + + /* Trailing NUL */ + *e = 0; + + pa_assert(e == t+sb->length); + + return t; +} + +/* Combination of pa_strbuf_free() and pa_strbuf_to_string() */ +char *pa_strbuf_to_string_free(pa_strbuf *sb) { + char *t; + + pa_assert(sb); + t = pa_strbuf_to_string(sb); + pa_strbuf_free(sb); + + return t; +} + +/* Append a string to the string buffer */ +void pa_strbuf_puts(pa_strbuf *sb, const char *t) { + + pa_assert(sb); + pa_assert(t); + + pa_strbuf_putsn(sb, t, strlen(t)); +} + +/* Append a character to the string buffer */ +void pa_strbuf_putc(pa_strbuf *sb, char c) { + pa_assert(sb); + + pa_strbuf_putsn(sb, &c, 1); +} + +/* Append a new chunk to the linked list */ +static void append(pa_strbuf *sb, struct chunk *c) { + pa_assert(sb); + pa_assert(c); + + if (sb->tail) { + pa_assert(sb->head); + sb->tail->next = c; + } else { + pa_assert(!sb->head); + sb->head = c; + } + + sb->tail = c; + sb->length += c->length; + c->next = NULL; +} + +/* Append up to l bytes of a string to the string buffer */ +void pa_strbuf_putsn(pa_strbuf *sb, const char *t, size_t l) { + struct chunk *c; + + pa_assert(sb); + pa_assert(t); + + if (!l) + return; + + c = pa_xmalloc(PA_ALIGN(sizeof(struct chunk)) + l); + c->length = l; + memcpy(CHUNK_TO_TEXT(c), t, l); + + append(sb, c); +} + +/* Append a printf() style formatted string to the string buffer. */ +/* The following is based on an example from the GNU libc documentation */ +size_t pa_strbuf_printf(pa_strbuf *sb, const char *format, ...) { + size_t size = 100; + struct chunk *c = NULL; + + pa_assert(sb); + pa_assert(format); + + for(;;) { + va_list ap; + int r; + + c = pa_xrealloc(c, PA_ALIGN(sizeof(struct chunk)) + size); + + va_start(ap, format); + r = vsnprintf(CHUNK_TO_TEXT(c), size, format, ap); + CHUNK_TO_TEXT(c)[size-1] = 0; + va_end(ap); + + if (r > -1 && (size_t) r < size) { + c->length = (size_t) r; + append(sb, c); + return (size_t) r; + } + + if (r > -1) /* glibc 2.1 */ + size = (size_t) r+1; + else /* glibc 2.0 */ + size *= 2; + } +} + +bool pa_strbuf_isempty(pa_strbuf *sb) { + pa_assert(sb); + + return sb->length <= 0; +} diff --git a/pipewire-pulseaudio/src/strbuf.h b/pipewire-pulseaudio/src/strbuf.h new file mode 100644 index 000000000..f5d9921e6 --- /dev/null +++ b/pipewire-pulseaudio/src/strbuf.h @@ -0,0 +1,39 @@ +#ifndef foostrbufhfoo +#define foostrbufhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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, see . +***/ + +#include + +typedef struct pa_strbuf pa_strbuf; + +pa_strbuf *pa_strbuf_new(void); +void pa_strbuf_free(pa_strbuf *sb); +char *pa_strbuf_to_string(pa_strbuf *sb); +char *pa_strbuf_to_string_free(pa_strbuf *sb); + +size_t pa_strbuf_printf(pa_strbuf *sb, const char *format, ...) PA_GCC_PRINTF_ATTR(2,3); +void pa_strbuf_puts(pa_strbuf *sb, const char *t); +void pa_strbuf_putsn(pa_strbuf *sb, const char *t, size_t m); +void pa_strbuf_putc(pa_strbuf *sb, char c); + +bool pa_strbuf_isempty(pa_strbuf *sb); + +#endif diff --git a/pipewire-pulseaudio/src/stream.c b/pipewire-pulseaudio/src/stream.c new file mode 100644 index 000000000..71df4c296 --- /dev/null +++ b/pipewire-pulseaudio/src/stream.c @@ -0,0 +1,1908 @@ +/* PipeWire + * Copyright (C) 2018 Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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. + */ + +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include +#include "core-format.h" +#include "internal.h" + +#define MIN_QUEUED 1 + +#define MAX_SIZE (4*1024*1024) + +static const uint32_t audio_formats[] = { + [PA_SAMPLE_U8] = SPA_AUDIO_FORMAT_U8, + [PA_SAMPLE_ALAW] = SPA_AUDIO_FORMAT_UNKNOWN, + [PA_SAMPLE_ULAW] = SPA_AUDIO_FORMAT_UNKNOWN, + [PA_SAMPLE_S16NE] = SPA_AUDIO_FORMAT_S16, + [PA_SAMPLE_S16RE] = SPA_AUDIO_FORMAT_S16_OE, + [PA_SAMPLE_FLOAT32NE] = SPA_AUDIO_FORMAT_F32, + [PA_SAMPLE_FLOAT32RE] = SPA_AUDIO_FORMAT_F32_OE, + [PA_SAMPLE_S32NE] = SPA_AUDIO_FORMAT_S32, + [PA_SAMPLE_S32RE] = SPA_AUDIO_FORMAT_S32_OE, + [PA_SAMPLE_S24NE] = SPA_AUDIO_FORMAT_S24, + [PA_SAMPLE_S24RE] = SPA_AUDIO_FORMAT_S24_OE, + [PA_SAMPLE_S24_32NE] = SPA_AUDIO_FORMAT_S24_32, + [PA_SAMPLE_S24_32RE] = SPA_AUDIO_FORMAT_S24_32_OE, +}; + +static inline uint32_t format_pa2id(pa_stream *s, pa_sample_format_t format) +{ + if (format < 0 || (size_t)format >= SPA_N_ELEMENTS(audio_formats)) + return SPA_AUDIO_FORMAT_UNKNOWN; + return audio_formats[format]; +} + +static inline pa_sample_format_t format_id2pa(pa_stream *s, uint32_t id) +{ + size_t i; + for (i = 0; i < SPA_N_ELEMENTS(audio_formats); i++) { + if (id == audio_formats[i]) + return i; + } + return PA_SAMPLE_INVALID; +} + +static const uint32_t audio_channels[] = { + [PA_CHANNEL_POSITION_MONO] = SPA_AUDIO_CHANNEL_MONO, + + [PA_CHANNEL_POSITION_FRONT_LEFT] = SPA_AUDIO_CHANNEL_FL, + [PA_CHANNEL_POSITION_FRONT_RIGHT] = SPA_AUDIO_CHANNEL_FR, + [PA_CHANNEL_POSITION_FRONT_CENTER] = SPA_AUDIO_CHANNEL_FC, + + [PA_CHANNEL_POSITION_REAR_CENTER] = SPA_AUDIO_CHANNEL_RC, + [PA_CHANNEL_POSITION_REAR_LEFT] = SPA_AUDIO_CHANNEL_RL, + [PA_CHANNEL_POSITION_REAR_RIGHT] = SPA_AUDIO_CHANNEL_RR, + + [PA_CHANNEL_POSITION_LFE] = SPA_AUDIO_CHANNEL_LFE, + [PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER] = SPA_AUDIO_CHANNEL_FLC, + [PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER] = SPA_AUDIO_CHANNEL_FRC, + + [PA_CHANNEL_POSITION_SIDE_LEFT] = SPA_AUDIO_CHANNEL_SL, + [PA_CHANNEL_POSITION_SIDE_RIGHT] = SPA_AUDIO_CHANNEL_SR, + + [PA_CHANNEL_POSITION_AUX0] = SPA_AUDIO_CHANNEL_CUSTOM_START + 1, + [PA_CHANNEL_POSITION_AUX1] = SPA_AUDIO_CHANNEL_CUSTOM_START + 2, + [PA_CHANNEL_POSITION_AUX2] = SPA_AUDIO_CHANNEL_CUSTOM_START + 3, + [PA_CHANNEL_POSITION_AUX3] = SPA_AUDIO_CHANNEL_CUSTOM_START + 4, + [PA_CHANNEL_POSITION_AUX4] = SPA_AUDIO_CHANNEL_CUSTOM_START + 5, + [PA_CHANNEL_POSITION_AUX5] = SPA_AUDIO_CHANNEL_CUSTOM_START + 6, + [PA_CHANNEL_POSITION_AUX6] = SPA_AUDIO_CHANNEL_CUSTOM_START + 7, + [PA_CHANNEL_POSITION_AUX7] = SPA_AUDIO_CHANNEL_CUSTOM_START + 8, + [PA_CHANNEL_POSITION_AUX8] = SPA_AUDIO_CHANNEL_CUSTOM_START + 9, + [PA_CHANNEL_POSITION_AUX9] = SPA_AUDIO_CHANNEL_CUSTOM_START + 10, + [PA_CHANNEL_POSITION_AUX10] = SPA_AUDIO_CHANNEL_CUSTOM_START + 11, + [PA_CHANNEL_POSITION_AUX11] = SPA_AUDIO_CHANNEL_CUSTOM_START + 12, + [PA_CHANNEL_POSITION_AUX12] = SPA_AUDIO_CHANNEL_CUSTOM_START + 13, + [PA_CHANNEL_POSITION_AUX13] = SPA_AUDIO_CHANNEL_CUSTOM_START + 14, + [PA_CHANNEL_POSITION_AUX14] = SPA_AUDIO_CHANNEL_CUSTOM_START + 15, + [PA_CHANNEL_POSITION_AUX15] = SPA_AUDIO_CHANNEL_CUSTOM_START + 16, + [PA_CHANNEL_POSITION_AUX16] = SPA_AUDIO_CHANNEL_CUSTOM_START + 17, + [PA_CHANNEL_POSITION_AUX17] = SPA_AUDIO_CHANNEL_CUSTOM_START + 18, + [PA_CHANNEL_POSITION_AUX18] = SPA_AUDIO_CHANNEL_CUSTOM_START + 19, + [PA_CHANNEL_POSITION_AUX19] = SPA_AUDIO_CHANNEL_CUSTOM_START + 20, + [PA_CHANNEL_POSITION_AUX20] = SPA_AUDIO_CHANNEL_CUSTOM_START + 21, + [PA_CHANNEL_POSITION_AUX21] = SPA_AUDIO_CHANNEL_CUSTOM_START + 22, + [PA_CHANNEL_POSITION_AUX22] = SPA_AUDIO_CHANNEL_CUSTOM_START + 23, + [PA_CHANNEL_POSITION_AUX23] = SPA_AUDIO_CHANNEL_CUSTOM_START + 24, + [PA_CHANNEL_POSITION_AUX24] = SPA_AUDIO_CHANNEL_CUSTOM_START + 25, + [PA_CHANNEL_POSITION_AUX25] = SPA_AUDIO_CHANNEL_CUSTOM_START + 26, + [PA_CHANNEL_POSITION_AUX26] = SPA_AUDIO_CHANNEL_CUSTOM_START + 27, + [PA_CHANNEL_POSITION_AUX27] = SPA_AUDIO_CHANNEL_CUSTOM_START + 28, + [PA_CHANNEL_POSITION_AUX28] = SPA_AUDIO_CHANNEL_CUSTOM_START + 29, + [PA_CHANNEL_POSITION_AUX29] = SPA_AUDIO_CHANNEL_CUSTOM_START + 30, + [PA_CHANNEL_POSITION_AUX30] = SPA_AUDIO_CHANNEL_CUSTOM_START + 31, + [PA_CHANNEL_POSITION_AUX31] = SPA_AUDIO_CHANNEL_CUSTOM_START + 32, + + [PA_CHANNEL_POSITION_TOP_CENTER] = SPA_AUDIO_CHANNEL_TC, + + [PA_CHANNEL_POSITION_TOP_FRONT_LEFT] = SPA_AUDIO_CHANNEL_TFL, + [PA_CHANNEL_POSITION_TOP_FRONT_RIGHT] = SPA_AUDIO_CHANNEL_TFR, + [PA_CHANNEL_POSITION_TOP_FRONT_CENTER] = SPA_AUDIO_CHANNEL_TFC, + + [PA_CHANNEL_POSITION_TOP_REAR_LEFT] = SPA_AUDIO_CHANNEL_TRL, + [PA_CHANNEL_POSITION_TOP_REAR_RIGHT] = SPA_AUDIO_CHANNEL_TRR, + [PA_CHANNEL_POSITION_TOP_REAR_CENTER] = SPA_AUDIO_CHANNEL_TRC, +}; + +static inline uint32_t channel_pa2id(pa_stream *s, pa_channel_position_t channel) +{ + if (channel < 0 || (size_t)channel >= SPA_N_ELEMENTS(audio_channels)) + return SPA_AUDIO_CHANNEL_UNKNOWN; + return audio_channels[channel]; +} + +static inline pa_channel_position_t channel_id2pa(pa_stream *s, uint32_t id) +{ + size_t i; + for (i = 0; i < SPA_N_ELEMENTS(audio_channels); i++) { + if (id == audio_channels[i]) + return i; + } + return PA_CHANNEL_POSITION_INVALID; +} + +static inline int dequeue_buffer(pa_stream *s) +{ + struct pw_buffer *buf; + uint32_t index; + + buf = pw_stream_dequeue_buffer(s->stream); + if (buf == NULL) + return -EPIPE; + + spa_ringbuffer_get_write_index(&s->dequeued_ring, &index); + s->dequeued[index & MASK_BUFFERS] = buf; + if (s->direction == PA_STREAM_PLAYBACK) + s->dequeued_size += buf->buffer->datas[0].maxsize; + else + s->dequeued_size += buf->buffer->datas[0].chunk->size; + spa_ringbuffer_write_update(&s->dequeued_ring, index + 1); + + return 0; +} + +static void dump_buffer_attr(pa_stream *s, pa_buffer_attr *attr) +{ + pw_log_info("stream %p: maxlength: %u", s, attr->maxlength); + pw_log_info("stream %p: tlength: %u", s, attr->tlength); + pw_log_info("stream %p: minreq: %u", s, attr->minreq); + pw_log_info("stream %p: prebuf: %u", s, attr->prebuf); + pw_log_info("stream %p: fragsize: %u", s, attr->fragsize); +} + +static void configure_buffers(pa_stream *s) +{ + s->buffer_attr.maxlength = s->maxsize; + if (s->buffer_attr.prebuf == (uint32_t)-1) + s->buffer_attr.prebuf = s->buffer_attr.minreq; + s->buffer_attr.fragsize = s->buffer_attr.minreq; + dump_buffer_attr(s, &s->buffer_attr); +} + +static void configure_device(pa_stream *s) +{ + struct global *g; + const char *str; + + g = pa_context_find_linked(s->context, pa_stream_get_index(s)); + if (g == NULL) { + s->device_index = PA_INVALID_INDEX; + s->device_name = NULL; + } + else { + if (s->direction == PA_STREAM_RECORD) { + if (g->mask == (PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE)) + s->device_index = g->node_info.monitor; + else + s->device_index = g->id; + } + else { + s->device_index = g->id; + } + + if ((str = pw_properties_get(g->props, PW_KEY_NODE_NAME)) == NULL) + s->device_name = strdup("unknown"); + else + s->device_name = strdup(str); + } + pw_log_debug("stream %p: linked to %d '%s'", s, s->device_index, s->device_name); +} + +static void stream_state_changed(void *data, enum pw_stream_state old, + enum pw_stream_state state, const char *error) +{ + pa_stream *s = data; + + pw_log_debug("stream %p: state '%s'->'%s'", s, pw_stream_state_as_string(old), + pw_stream_state_as_string(state)); + + if (s->state == PA_STREAM_TERMINATED) + return; + + switch(state) { + case PW_STREAM_STATE_ERROR: + pa_stream_set_state(s, PA_STREAM_FAILED); + break; + case PW_STREAM_STATE_UNCONNECTED: + if (!s->disconnecting) { + pa_context_set_error(s->context, PA_ERR_KILLED); + pa_stream_set_state(s, PA_STREAM_FAILED); + } else { + pa_stream_set_state(s, PA_STREAM_TERMINATED); + } + break; + case PW_STREAM_STATE_CONNECTING: + pa_stream_set_state(s, PA_STREAM_CREATING); + break; + case PW_STREAM_STATE_CONFIGURE: + case PW_STREAM_STATE_READY: + break; + case PW_STREAM_STATE_PAUSED: + if (old < PW_STREAM_STATE_PAUSED) { + if (s->suspended && s->suspended_callback) { + s->suspended = false; + s->suspended_callback(s, s->suspended_userdata); + } + configure_device(s); + configure_buffers(s); + pa_stream_set_state(s, PA_STREAM_READY); + } + else { + if (!s->suspended && s->suspended_callback) { + s->suspended = true; + s->suspended_callback(s, s->suspended_userdata); + } + } + break; + case PW_STREAM_STATE_STREAMING: + break; + } +} + +static const struct spa_pod *get_buffers_param(pa_stream *s, pa_buffer_attr *attr, struct spa_pod_builder *b) +{ + const struct spa_pod *param; + uint32_t blocks, buffers, size, maxsize, stride; + + blocks = 1; + stride = pa_frame_size(&s->sample_spec); + + if (attr->tlength == (uint32_t)-1 || attr->tlength == 0) + maxsize = 1024; + else + maxsize = (attr->tlength / stride); + + if (attr->minreq == (uint32_t)-1 || attr->minreq == 0) + size = maxsize; + else + size = SPA_MIN(attr->minreq / stride, maxsize); + + if (attr->maxlength == (uint32_t)-1) + buffers = 3; + else + buffers = SPA_CLAMP(attr->maxlength / (size * stride), 3u, MAX_BUFFERS); + + pw_log_info("stream %p: stride %d maxsize %d size %u buffers %d", s, stride, maxsize, + size, buffers); + + param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, + SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(buffers, buffers, MAX_BUFFERS), + SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(blocks), + SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int( + size * stride, + size * stride, + maxsize * stride), + SPA_PARAM_BUFFERS_stride, SPA_POD_Int(stride), + SPA_PARAM_BUFFERS_align, SPA_POD_Int(16)); + return param; +} + +static void patch_buffer_attr(pa_stream *s, pa_buffer_attr *attr, pa_stream_flags_t *flags) { + const char *e; + + pa_assert(s); + pa_assert(attr); + + if ((e = getenv("PULSE_LATENCY_MSEC"))) { + uint32_t ms; + pa_sample_spec ss; + + pa_sample_spec_init(&ss); + + if (pa_sample_spec_valid(&s->sample_spec)) + ss = s->sample_spec; + else if (s->n_formats == 1) + pa_format_info_to_sample_spec(s->req_formats[0], &ss, NULL); + + if ((ms = atoi(e)) == 0) { + pa_log_debug("Failed to parse $PULSE_LATENCY_MSEC: %s", e); + } + else if (!pa_sample_spec_valid(&s->sample_spec)) { + pa_log_debug("Ignoring $PULSE_LATENCY_MSEC: %s (invalid sample spec)", e); + } + else { + attr->maxlength = (uint32_t) -1; + attr->tlength = pa_usec_to_bytes(ms * PA_USEC_PER_MSEC, &ss); + attr->minreq = (uint32_t) -1; + attr->prebuf = (uint32_t) -1; + attr->fragsize = attr->tlength; + + if (flags) + *flags |= PA_STREAM_ADJUST_LATENCY; + } + } + + if (attr->maxlength == (uint32_t) -1) + attr->maxlength = 4*1024*1024; /* 4MB is the maximum queue length PulseAudio <= 0.9.9 supported. */ + + if (attr->tlength == (uint32_t) -1) + attr->tlength = (uint32_t) pa_usec_to_bytes(250*PA_USEC_PER_MSEC, &s->sample_spec); /* 250ms of buffering */ + + if (attr->minreq == (uint32_t) -1) + attr->minreq = attr->tlength; /* Ask for more data when there are only 200ms left in the playback buffer */ + + if (attr->prebuf == (uint32_t) -1) + attr->prebuf = attr->tlength; /* Start to play only when the playback is fully filled up once */ + + if (attr->fragsize == (uint32_t) -1) + attr->fragsize = attr->tlength; /* Pass data to the app only when the buffer is filled up once */ + + dump_buffer_attr(s, attr); +} + +static void stream_format_changed(void *data, const struct spa_pod *format) +{ + pa_stream *s = data; + const struct spa_pod *params[4]; + uint32_t n_params = 0; + uint8_t buffer[4096]; + struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); + struct spa_audio_info info = { 0 }; + int res; + unsigned int i; + + if (format == NULL) { + res = 0; + goto done; + } + + spa_format_parse(format, &info.media_type, &info.media_subtype); + + if (info.media_type != SPA_MEDIA_TYPE_audio || + info.media_subtype != SPA_MEDIA_SUBTYPE_raw || + spa_format_audio_raw_parse(format, &info.info.raw) < 0 || + !SPA_AUDIO_FORMAT_IS_INTERLEAVED(info.info.raw.format)) { + res = -EINVAL; + goto done; + } + + s->sample_spec.format = format_id2pa(s, info.info.raw.format); + if (s->sample_spec.format == PA_SAMPLE_INVALID) { + res = -EINVAL; + goto done; + } + s->sample_spec.rate = info.info.raw.rate; + s->sample_spec.channels = info.info.raw.channels; + + pa_channel_map_init(&s->channel_map); + s->channel_map.channels = info.info.raw.channels; + for (i = 0; i < info.info.raw.channels; i++) + s->channel_map.map[i] = channel_id2pa(s, info.info.raw.position[i]); + + if (!pa_channel_map_valid(&s->channel_map)) + pa_channel_map_init_auto(&s->channel_map, info.info.raw.channels, PA_CHANNEL_MAP_DEFAULT); + + if (s->format) + pa_format_info_free(s->format); + s->format = pa_format_info_from_sample_spec(&s->sample_spec, &s->channel_map); + + patch_buffer_attr(s, &s->buffer_attr, NULL); + + params[n_params++] = get_buffers_param(s, &s->buffer_attr, &b); + + res = 0; + + done: + pw_stream_finish_format(s->stream, res, params, n_params); +} + +static void stream_control_info(void *data, uint32_t id, const struct pw_stream_control *control) +{ + pa_stream *s = data; + + switch (id) { + case SPA_PROP_mute: + if (control->n_values > 0) + s->mute = control->values[0] >= 0.5f; + break; + case SPA_PROP_channelVolumes: + s->n_channel_volumes = SPA_MAX(SPA_AUDIO_MAX_CHANNELS, control->n_values); + memcpy(s->channel_volumes, control->values, s->n_channel_volumes * sizeof(float)); + break; + default: + break; + } +} + +static void stream_add_buffer(void *data, struct pw_buffer *buffer) +{ + pa_stream *s = data; + s->maxsize += buffer->buffer->datas[0].maxsize; +} +static void stream_remove_buffer(void *data, struct pw_buffer *buffer) +{ + pa_stream *s = data; + s->maxsize -= buffer->buffer->datas[0].maxsize; +} + +static void update_timing_info(pa_stream *s) +{ + struct pw_time pwt; + pa_timing_info *ti = &s->timing_info; + size_t stride = pa_frame_size(&s->sample_spec); + int64_t delay, queued, ticks; + + pw_stream_get_time(s->stream, &pwt); + s->timing_info_valid = false; + s->queued = pwt.queued; + pw_log_trace("stream %p: %"PRIu64, s, s->queued); + + if (pwt.rate.denom == 0) + return; + + pa_timeval_store(&ti->timestamp, pwt.now / SPA_NSEC_PER_USEC); + ti->synchronized_clocks = true; + ti->transport_usec = 0; + ti->playing = 1; + ti->write_index_corrupt = false; + ti->read_index_corrupt = false; + + queued = pwt.queued + (pwt.ticks * s->sample_spec.rate / pwt.rate.denom) * stride; + ticks = ((pwt.ticks + pwt.delay) * s->sample_spec.rate / pwt.rate.denom) * stride; + + delay = pwt.delay * SPA_USEC_PER_SEC / pwt.rate.denom; + if (s->direction == PA_STREAM_PLAYBACK) { + ti->sink_usec = -delay; + ti->write_index = queued; + ti->read_index = ticks; + } + else { + ti->source_usec = delay; + ti->read_index = queued; + ti->write_index = ticks; + } + + ti->configured_sink_usec = 0; + ti->configured_source_usec = 0; + ti->since_underrun = 0; + s->timing_info_valid = true; +} + +static void stream_process(void *data) +{ + pa_stream *s = data; + + update_timing_info(s); + + while (dequeue_buffer(s) == 0); + + if (s->dequeued_size <= 0) + return; + + if (s->direction == PA_STREAM_PLAYBACK) { + if (s->write_callback) + s->write_callback(s, s->dequeued_size, s->write_userdata); + } + else { + if (s->read_callback) + s->read_callback(s, s->dequeued_size, s->read_userdata); + } +} + +static void stream_drained(void *data) +{ + pa_stream *s = data; + + if (s->drain) { + pa_operation *o = s->drain; + pa_operation_ref(o); + if (o->callback) + o->callback(o, o->userdata); + pa_operation_unref(o); + s->drain = NULL; + } +} + +static const struct pw_stream_events stream_events = +{ + PW_VERSION_STREAM_EVENTS, + .state_changed = stream_state_changed, + .format_changed = stream_format_changed, + .control_info = stream_control_info, + .add_buffer = stream_add_buffer, + .remove_buffer = stream_remove_buffer, + .process = stream_process, + .drained = stream_drained, +}; + +static pa_stream* stream_new(pa_context *c, const char *name, + const pa_sample_spec *ss, const pa_channel_map *map, + pa_format_info * const * formats, unsigned int n_formats, + pa_proplist *p) +{ + pa_stream *s; + char str[1024]; + unsigned int i; + struct pw_properties *props; + + spa_assert(c); + spa_assert(c->refcount >= 1); + pa_assert((ss == NULL && map == NULL) || (formats == NULL && n_formats == 0)); + pa_assert(n_formats < PA_MAX_FORMATS); + + PA_CHECK_VALIDITY_RETURN_NULL(c, name || + (p && pa_proplist_contains(p, PA_PROP_MEDIA_NAME)), PA_ERR_INVALID); + + s = calloc(1, sizeof(pa_stream)); + if (s == NULL) + return NULL; + + s->proplist = p ? pa_proplist_copy(p) : pa_proplist_new(); + if (name) + pa_proplist_sets(s->proplist, PA_PROP_MEDIA_NAME, name); + else + name = pa_proplist_gets(s->proplist, PA_PROP_MEDIA_NAME); + + props = pw_properties_new(PW_KEY_CLIENT_API, "pulseaudio", + NULL); + pw_properties_update(props, &s->proplist->props->dict); + + s->stream = pw_stream_new(c->remote, name, props); + s->refcount = 1; + s->context = c; + spa_list_init(&s->pending); + + pw_stream_add_listener(s->stream, &s->stream_listener, &stream_events, s); + + s->direction = PA_STREAM_NODIRECTION; + s->state = PA_STREAM_UNCONNECTED; + s->flags = 0; + + if (ss) + s->sample_spec = *ss; + else + pa_sample_spec_init(&s->sample_spec); + + if (map) + s->channel_map = *map; + else + pa_channel_map_init(&s->channel_map); + + pw_log_debug("channel map: %p %s", map, pa_channel_map_snprint(str, sizeof(str), &s->channel_map)); + + s->n_formats = 0; + if (formats) { + s->n_formats = n_formats; + for (i = 0; i < n_formats; i++) + s->req_formats[i] = pa_format_info_copy(formats[i]); + } + s->format = NULL; + + s->direct_on_input = PA_INVALID_INDEX; + + s->stream_index = PA_INVALID_INDEX; + + s->buffer_attr.maxlength = (uint32_t) -1; + if (ss) + s->buffer_attr.tlength = (uint32_t) pa_usec_to_bytes(250*PA_USEC_PER_MSEC, ss); /* 250ms of buffering */ + else { + /* FIXME: We assume a worst-case compressed format corresponding to + * 48000 Hz, 2 ch, S16 PCM, but this can very well be incorrect */ + pa_sample_spec tmp_ss = { + .format = PA_SAMPLE_S16NE, + .rate = 48000, + .channels = 2, + }; + s->buffer_attr.tlength = (uint32_t) pa_usec_to_bytes(250*PA_USEC_PER_MSEC, &tmp_ss); /* 250ms of buffering */ + } + s->buffer_attr.minreq = (uint32_t) -1; + s->buffer_attr.prebuf = (uint32_t) -1; + s->buffer_attr.fragsize = (uint32_t) -1; + + s->device_index = PA_INVALID_INDEX; + s->device_name = NULL; + + spa_ringbuffer_init(&s->dequeued_ring); + + spa_list_append(&c->streams, &s->link); + pa_stream_ref(s); + + return s; +} + +SPA_EXPORT +pa_stream* pa_stream_new(pa_context *c, const char *name, const pa_sample_spec *ss, + const pa_channel_map *map) +{ + return stream_new(c, name, ss, map, NULL, 0, NULL); +} + +SPA_EXPORT +pa_stream* pa_stream_new_with_proplist(pa_context *c, const char *name, + const pa_sample_spec *ss, const pa_channel_map *map, pa_proplist *p) +{ + pa_channel_map tmap; + + if (!map) + PA_CHECK_VALIDITY_RETURN_NULL(c, map = pa_channel_map_init_auto(&tmap, ss->channels, PA_CHANNEL_MAP_DEFAULT), PA_ERR_INVALID); + + return stream_new(c, name, ss, map, NULL, 0, p); +} + +SPA_EXPORT +pa_stream *pa_stream_new_extended(pa_context *c, const char *name, + pa_format_info * const * formats, unsigned int n_formats, pa_proplist *p) +{ + return stream_new(c, name, NULL, NULL, formats, n_formats, p); +} + +static void stream_unlink(pa_stream *s) +{ + pa_context *c = s->context; + pa_operation *o, *t; + + if (c == NULL) + return; + + pw_log_debug("stream %p: unlink %d", s, s->refcount); + + spa_list_for_each_safe(o, t, &c->operations, link) { + if (o->stream == s) + pa_operation_cancel(o); + } + + spa_list_remove(&s->link); + pw_stream_set_active(s->stream, false); + + s->context = NULL; + pa_stream_unref(s); +} + +static void stream_free(pa_stream *s) +{ + int i; + + pw_log_debug("stream %p", s); + + if (s->stream) { + spa_hook_remove(&s->stream_listener); + pw_stream_destroy(s->stream); + } + + if (s->proplist) + pa_proplist_free(s->proplist); + + for (i = 0; i < s->n_formats; i++) + pa_format_info_free(s->req_formats[i]); + + if (s->format) + pa_format_info_free(s->format); + + free(s->device_name); + free(s); +} + +SPA_EXPORT +void pa_stream_unref(pa_stream *s) +{ + spa_assert(s); + spa_assert(s->refcount >= 1); + + if (--s->refcount == 0) + stream_free(s); +} + +SPA_EXPORT +pa_stream *pa_stream_ref(pa_stream *s) +{ + spa_assert(s); + spa_assert(s->refcount >= 1); + + s->refcount++; + return s; +} + +SPA_EXPORT +pa_stream_state_t pa_stream_get_state(PA_CONST pa_stream *s) +{ + spa_assert(s); + spa_assert(s->refcount >= 1); + return s->state; +} + +SPA_EXPORT +pa_context* pa_stream_get_context(PA_CONST pa_stream *s) +{ + spa_assert(s); + spa_assert(s->refcount >= 1); + return s->context; +} + +SPA_EXPORT +uint32_t pa_stream_get_index(PA_CONST pa_stream *s) +{ + uint32_t idx; + + spa_assert(s); + spa_assert(s->refcount >= 1); + + idx = pw_stream_get_node_id(s->stream); + pw_log_debug("stream %p: index %u", s, idx); + return idx; +} + +void pa_stream_set_state(pa_stream *s, pa_stream_state_t st) { + spa_assert(s); + spa_assert(s->refcount >= 1); + + if (s->state == st) + return; + + pa_stream_ref(s); + + pw_log_debug("stream %p: state %d -> %d", s, s->state, st); + s->state = st; + + if (s->state_callback) + s->state_callback(s, s->state_userdata); + + if ((st == PA_STREAM_FAILED || st == PA_STREAM_TERMINATED)) + stream_unlink(s); + + pa_stream_unref(s); +} + + +SPA_EXPORT +uint32_t pa_stream_get_device_index(PA_CONST pa_stream *s) +{ + spa_assert(s); + spa_assert(s->refcount >= 1); + + PA_CHECK_VALIDITY_RETURN_ANY(s->context, s->state == PA_STREAM_READY, + PA_ERR_BADSTATE, PA_INVALID_INDEX); + PA_CHECK_VALIDITY_RETURN_ANY(s->context, s->direction != PA_STREAM_UPLOAD, + PA_ERR_BADSTATE, PA_INVALID_INDEX); + PA_CHECK_VALIDITY_RETURN_ANY(s->context, s->device_index != PA_INVALID_INDEX, + PA_ERR_BADSTATE, PA_INVALID_INDEX); + + pw_log_trace("stream %p: %d", s, s->device_index); + return s->device_index; +} + +SPA_EXPORT +const char *pa_stream_get_device_name(PA_CONST pa_stream *s) +{ + spa_assert(s); + spa_assert(s->refcount >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE); +// PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->device_name, PA_ERR_BADSTATE); + + if (s->device_name == NULL) + return "unnamed"; + + return s->device_name; +} + +SPA_EXPORT +int pa_stream_is_suspended(PA_CONST pa_stream *s) +{ + spa_assert(s); + spa_assert(s->refcount >= 1); + + PA_CHECK_VALIDITY(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE); + + return s->suspended; +} + +SPA_EXPORT +int pa_stream_is_corked(PA_CONST pa_stream *s) +{ + spa_assert(s); + spa_assert(s->refcount >= 1); + + PA_CHECK_VALIDITY(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE); + + pw_log_trace("stream %p: corked %d", s, s->corked); + return s->corked; +} + +static const struct spa_pod *get_param(pa_stream *s, pa_sample_spec *ss, pa_channel_map *map, + struct spa_pod_builder *b) +{ + struct spa_audio_info_raw info; + + info = SPA_AUDIO_INFO_RAW_INIT( .format = format_pa2id(s, ss->format), + .channels = ss->channels, + .rate = ss->rate); + if (map) { + int i; + for (i = 0; i < map->channels; i++) + info.position[i] = channel_pa2id(s, map->map[i]); + } + return spa_format_audio_raw_build(b, SPA_PARAM_EnumFormat, &info); +} + +static int create_stream(pa_stream_direction_t direction, + pa_stream *s, + const char *dev, + const pa_buffer_attr *attr, + pa_stream_flags_t flags, + const pa_cvolume *volume, + pa_stream *sync_stream) +{ + int res; + enum pw_stream_flags fl; + const struct spa_pod *params[16]; + uint32_t i, n_params = 0; + uint8_t buffer[4096]; + struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); + uint32_t sample_rate = 0, stride = 0; + const char *str; + uint32_t devid; + struct global *g; + struct spa_dict_item items[5]; + char latency[64]; + bool monitor; + + spa_assert(s); + spa_assert(s->refcount >= 1); + + pw_log_debug("stream %p: connect %s %08x", s, dev, flags); + + s->direction = direction; + s->timing_info_valid = false; + s->disconnecting = false; + if (volume) { + for (i = 0; i < volume->channels; i++) + s->channel_volumes[i] = volume->values[i] / (float) PA_VOLUME_NORM; + s->n_channel_volumes = volume->channels; + } else { + for (i = 0; i < SPA_AUDIO_MAX_CHANNELS; i++) + s->channel_volumes[i] = 1.0; + s->n_channel_volumes = 0; + } + s->mute = false; + + pa_stream_set_state(s, PA_STREAM_CREATING); + + fl = PW_STREAM_FLAG_AUTOCONNECT | + PW_STREAM_FLAG_MAP_BUFFERS; + + s->corked = SPA_FLAG_IS_SET(flags, PA_STREAM_START_CORKED); + + if (s->corked) + fl |= PW_STREAM_FLAG_INACTIVE; + if (flags & PA_STREAM_PASSTHROUGH) + fl |= PW_STREAM_FLAG_EXCLUSIVE; + if (flags & PA_STREAM_DONT_MOVE) + fl |= PW_STREAM_FLAG_DONT_RECONNECT; + monitor = (flags & PA_STREAM_PEAK_DETECT); + + if (pa_sample_spec_valid(&s->sample_spec)) { + params[n_params++] = get_param(s, &s->sample_spec, &s->channel_map, &b); + sample_rate = s->sample_spec.rate; + stride = pa_frame_size(&s->sample_spec); + } + else { + pa_sample_spec ss; + pa_channel_map chmap; + int i; + + for (i = 0; i < s->n_formats; i++) { + if ((res = pa_format_info_to_sample_spec(s->req_formats[i], &ss, NULL)) < 0) { + char buf[4096]; + pw_log_warn("can't convert format %d %s", res, + pa_format_info_snprint(buf,4096,s->req_formats[i])); + continue; + } + if (pa_format_info_get_channel_map(s->req_formats[i], &chmap) < 0) + pa_channel_map_init_auto(&chmap, ss.channels, PA_CHANNEL_MAP_DEFAULT); + + params[n_params++] = get_param(s, &ss, &chmap, &b); + if (ss.rate > sample_rate) { + sample_rate = ss.rate; + stride = pa_frame_size(&ss); + } + } + } + if (sample_rate == 0) { + sample_rate = 48000; + stride = sizeof(int16_t) * 2; + } + + if (attr) + s->buffer_attr = *attr; + patch_buffer_attr(s, &s->buffer_attr, &flags); + + if (direction == PA_STREAM_RECORD) + devid = s->direct_on_input; + else + devid = SPA_ID_INVALID; + + if (dev == NULL) { + if ((str = getenv("PIPEWIRE_NODE")) != NULL) + devid = atoi(str); + } + else if (devid == SPA_ID_INVALID) { + uint32_t mask; + + if (direction == PA_STREAM_PLAYBACK) + mask = PA_SUBSCRIPTION_MASK_SINK; + else if (direction == PA_STREAM_RECORD) + mask = PA_SUBSCRIPTION_MASK_SOURCE; + else + mask = 0; + + if ((g = pa_context_find_global_by_name(s->context, mask, dev)) != NULL) + devid = g->id; + } + + if ((str = pa_proplist_gets(s->proplist, PA_PROP_MEDIA_ROLE)) == NULL) + str = "Music"; + else if (strcmp(str, "video") == 0) + str = "Movie"; + else if (strcmp(str, "music") == 0) + str = "Music"; + else if (strcmp(str, "game") == 0) + str = "Game"; + else if (strcmp(str, "event") == 0) + str = "Notification"; + else if (strcmp(str, "phone") == 0) + str = "Communication"; + else if (strcmp(str, "animation") == 0) + str = "Movie"; + else if (strcmp(str, "production") == 0) + str = "Production"; + else if (strcmp(str, "a11y") == 0) + str = "Accessibility"; + else if (strcmp(str, "test") == 0) + str = "Test"; + else + str = "Music"; + + sprintf(latency, "%u/%u", s->buffer_attr.minreq / stride, sample_rate); + items[0] = SPA_DICT_ITEM_INIT(PW_KEY_NODE_LATENCY, latency); + items[1] = SPA_DICT_ITEM_INIT(PW_KEY_MEDIA_TYPE, "Audio"); + items[2] = SPA_DICT_ITEM_INIT(PW_KEY_MEDIA_CATEGORY, + direction == PA_STREAM_PLAYBACK ? + "Playback" : "Capture"); + items[3] = SPA_DICT_ITEM_INIT(PW_KEY_MEDIA_ROLE, str); + items[4] = SPA_DICT_ITEM_INIT(PW_KEY_STREAM_MONITOR, monitor ? "1" : "0"); + + pw_stream_update_properties(s->stream, &SPA_DICT_INIT(items, 5)); + + res = pw_stream_connect(s->stream, + direction == PA_STREAM_PLAYBACK ? + PW_DIRECTION_OUTPUT : + PW_DIRECTION_INPUT, + devid, + fl, + params, n_params); + + return res; +} + +SPA_EXPORT +int pa_stream_connect_playback( + pa_stream *s, + const char *dev, + const pa_buffer_attr *attr, + pa_stream_flags_t flags, + const pa_cvolume *volume, + pa_stream *sync_stream) +{ + return create_stream(PA_STREAM_PLAYBACK, s, dev, attr, flags, volume, sync_stream); +} + +SPA_EXPORT +int pa_stream_connect_record( + pa_stream *s, + const char *dev, + const pa_buffer_attr *attr, + pa_stream_flags_t flags) +{ + return create_stream(PA_STREAM_RECORD, s, dev, attr, flags, NULL, NULL); +} + +static void on_disconnected(pa_operation *o, void *userdata) +{ + pa_stream_set_state(o->stream, PA_STREAM_TERMINATED); +} + +SPA_EXPORT +int pa_stream_disconnect(pa_stream *s) +{ + pa_operation *o; + pa_context *c = s->context; + + spa_assert(s); + spa_assert(s->refcount >= 1); + + PA_CHECK_VALIDITY(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + + pw_log_debug("stream %p: disconnect", s); + pa_stream_ref(s); + + s->disconnecting = true; + pw_stream_disconnect(s->stream); + + o = pa_operation_new(c, s, on_disconnected, 0); + pa_operation_sync(o); + pa_operation_unref(o); + pa_stream_unref(s); + + return 0; +} + +int peek_buffer(pa_stream *s) +{ + int32_t avail; + uint32_t index; + + if (s->buffer != NULL) + return 0; + + if ((avail = spa_ringbuffer_get_read_index(&s->dequeued_ring, &index)) < MIN_QUEUED) + return -EPIPE; + + s->buffer = s->dequeued[index & MASK_BUFFERS]; + s->buffer_index = index; + s->buffer_data = s->buffer->buffer->datas[0].data; + if (s->direction == PA_STREAM_RECORD) { + s->buffer_size = s->buffer->buffer->datas[0].chunk->size; + s->buffer_offset = s->buffer->buffer->datas[0].chunk->offset; + } + else { + s->buffer_size = s->buffer->buffer->datas[0].maxsize; + } + return 0; +} + +int queue_buffer(pa_stream *s) +{ + if (s->buffer == NULL) + return 0; + + if (s->direction == PA_STREAM_PLAYBACK) + s->dequeued_size -= s->buffer->buffer->datas[0].maxsize; + else + s->dequeued_size -= s->buffer->buffer->datas[0].chunk->size; + spa_ringbuffer_read_update(&s->dequeued_ring, s->buffer_index + 1); + + s->buffer->size = s->buffer->buffer->datas[0].chunk->size; + pw_log_trace("%p %"PRIu64"/%d", s->buffer, s->buffer->size, + s->buffer->buffer->datas[0].chunk->offset); + + pw_stream_queue_buffer(s->stream, s->buffer); + s->buffer = NULL; + s->buffer_offset = 0; + return 0; +} + +SPA_EXPORT +int pa_stream_begin_write( + pa_stream *s, + void **data, + size_t *nbytes) +{ + int res; + + spa_assert(s); + spa_assert(s->refcount >= 1); + + PA_CHECK_VALIDITY(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY(s->context, s->direction == PA_STREAM_PLAYBACK || + s->direction == PA_STREAM_UPLOAD, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY(s->context, data, PA_ERR_INVALID); + PA_CHECK_VALIDITY(s->context, nbytes && *nbytes != 0, PA_ERR_INVALID); + + if ((res = peek_buffer(s)) < 0) { + *data = NULL; + *nbytes = 0; + } + else { + size_t max = s->buffer_size - s->buffer_offset; + *data = SPA_MEMBER(s->buffer_data, s->buffer_offset, void); + *nbytes = *nbytes != (size_t)-1 ? SPA_MIN(*nbytes, max) : max; + } + pw_log_trace("peek buffer %p %zd", *data, *nbytes); + + return 0; +} + +SPA_EXPORT +int pa_stream_cancel_write(pa_stream *s) +{ + spa_assert(s); + spa_assert(s->refcount >= 1); + + PA_CHECK_VALIDITY(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY(s->context, s->direction == PA_STREAM_PLAYBACK || + s->direction == PA_STREAM_UPLOAD, PA_ERR_BADSTATE); + + pw_log_debug("cancel %p %p %d", s->buffer, s->buffer_data, s->buffer_size); + s->buffer = NULL; + + return 0; +} + +SPA_EXPORT +int pa_stream_write(pa_stream *s, + const void *data, + size_t nbytes, + pa_free_cb_t free_cb, + int64_t offset, + pa_seek_mode_t seek) +{ + return pa_stream_write_ext_free(s, data, nbytes, free_cb, (void*) data, offset, seek); +} + +SPA_EXPORT +int pa_stream_write_ext_free(pa_stream *s, + const void *data, + size_t nbytes, + pa_free_cb_t free_cb, + void *free_cb_data, + int64_t offset, + pa_seek_mode_t seek) +{ + spa_assert(s); + spa_assert(s->refcount >= 1); + spa_assert(data); + + PA_CHECK_VALIDITY(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY(s->context, s->direction == PA_STREAM_PLAYBACK || + s->direction == PA_STREAM_UPLOAD, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY(s->context, seek <= PA_SEEK_RELATIVE_END, PA_ERR_INVALID); + PA_CHECK_VALIDITY(s->context, s->direction == PA_STREAM_PLAYBACK || + (seek == PA_SEEK_RELATIVE && offset == 0), PA_ERR_INVALID); + PA_CHECK_VALIDITY(s->context, + !s->buffer || + ((data >= s->buffer_data) && + ((const char*) data + nbytes <= (const char*) s->buffer_data + s->buffer_size)), + PA_ERR_INVALID); + PA_CHECK_VALIDITY(s->context, offset % pa_frame_size(&s->sample_spec) == 0, PA_ERR_INVALID); + PA_CHECK_VALIDITY(s->context, nbytes % pa_frame_size(&s->sample_spec) == 0, PA_ERR_INVALID); + PA_CHECK_VALIDITY(s->context, !free_cb || !s->buffer, PA_ERR_INVALID); + + if (s->buffer == NULL) { + void *dst; + const void *src = data; + size_t towrite = nbytes, dsize; + + while (towrite > 0) { + dsize = towrite; + + if (pa_stream_begin_write(s, &dst, &dsize) < 0 || + dst == NULL || dsize == 0) { + pw_log_debug("stream %p: out of buffers, wanted %zd bytes", s, nbytes); + break; + } + + memcpy(dst, src, dsize); + + s->buffer_offset += dsize; + + if (s->buffer_offset >= s->buffer_size) { + s->buffer->buffer->datas[0].chunk->offset = 0; + s->buffer->buffer->datas[0].chunk->size = s->buffer_offset; + queue_buffer(s); + } + towrite -= dsize; + src = SPA_MEMBER(src, dsize, void); + } + if (free_cb) + free_cb(free_cb_data); + + s->buffer = NULL; + } + else { + s->buffer->buffer->datas[0].chunk->offset = SPA_PTRDIFF(data, s->buffer_data); + s->buffer->buffer->datas[0].chunk->size = nbytes; + queue_buffer(s); + } + + update_timing_info(s); + + return 0; +} + +SPA_EXPORT +int pa_stream_peek(pa_stream *s, + const void **data, + size_t *nbytes) +{ + spa_assert(s); + spa_assert(s->refcount >= 1); + spa_assert(data); + spa_assert(nbytes); + + PA_CHECK_VALIDITY(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY(s->context, s->direction == PA_STREAM_RECORD, PA_ERR_BADSTATE); + + if (peek_buffer(s) < 0) { + *data = NULL; + *nbytes = 0; + pw_log_debug("stream %p: no buffer", s); + return 0; + } + *data = SPA_MEMBER(s->buffer_data, s->buffer_offset, void); + *nbytes = s->buffer_size; + pw_log_trace("stream %p: %p %zd %f", s, *data, *nbytes, *(float*)*data); + + return 0; +} + +SPA_EXPORT +int pa_stream_drop(pa_stream *s) +{ + spa_assert(s); + spa_assert(s->refcount >= 1); + + PA_CHECK_VALIDITY(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY(s->context, s->direction == PA_STREAM_RECORD, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY(s->context, s->buffer, PA_ERR_BADSTATE); + + pw_log_trace("stream %p", s); + queue_buffer(s); + + return 0; +} + +SPA_EXPORT +size_t pa_stream_writable_size(PA_CONST pa_stream *s) +{ + spa_assert(s); + spa_assert(s->refcount >= 1); + + PA_CHECK_VALIDITY_RETURN_ANY(s->context, s->state == PA_STREAM_READY, + PA_ERR_BADSTATE, (size_t) -1); + PA_CHECK_VALIDITY_RETURN_ANY(s->context, s->direction != PA_STREAM_RECORD, + PA_ERR_BADSTATE, (size_t) -1); + + pw_log_trace("stream %p: %zd", s, s->dequeued_size); + return s->dequeued_size; +} + +SPA_EXPORT +size_t pa_stream_readable_size(PA_CONST pa_stream *s) +{ + spa_assert(s); + spa_assert(s->refcount >= 1); + + PA_CHECK_VALIDITY_RETURN_ANY(s->context, s->state == PA_STREAM_READY, + PA_ERR_BADSTATE, (size_t) -1); + PA_CHECK_VALIDITY_RETURN_ANY(s->context, s->direction == PA_STREAM_RECORD, + PA_ERR_BADSTATE, (size_t) -1); + + return s->dequeued_size; +} + +struct success_ack { + pa_stream_success_cb_t cb; + void *userdata; +}; + +static void on_success(pa_operation *o, void *userdata) +{ + struct success_ack *d = userdata; + pa_stream *s = o->stream; + pa_operation_done(o); + if (d->cb) + d->cb(s, 1, d->userdata); +} + +SPA_EXPORT +pa_operation* pa_stream_drain(pa_stream *s, pa_stream_success_cb_t cb, void *userdata) +{ + pa_operation *o; + struct success_ack *d; + + spa_assert(s); + spa_assert(s->refcount >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction == PA_STREAM_PLAYBACK, PA_ERR_BADSTATE); + + pw_log_debug("stream %p", s); + pw_stream_flush(s->stream, true); + o = pa_operation_new(s->context, s, on_success, sizeof(struct success_ack)); + d = o->userdata; + d->cb = cb; + d->userdata = userdata; + if (s->drain) + pa_operation_cancel(s->drain); + s->drain = o; + + return o; +} + +static void on_timing_success(pa_operation *o, void *userdata) +{ + struct success_ack *d = userdata; + pa_stream *s = o->stream; + + update_timing_info(s); + pa_operation_done(o); + + if (d->cb) + d->cb(s, s->timing_info_valid, d->userdata); +} + +SPA_EXPORT +pa_operation* pa_stream_update_timing_info(pa_stream *s, pa_stream_success_cb_t cb, void *userdata) +{ + pa_operation *o; + struct success_ack *d; + + spa_assert(s); + spa_assert(s->refcount >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE); + + o = pa_operation_new(s->context, s, on_timing_success, sizeof(struct success_ack)); + d = o->userdata; + d->cb = cb; + d->userdata = userdata; + pa_operation_sync(o); + + return o; +} + +SPA_EXPORT +void pa_stream_set_state_callback(pa_stream *s, pa_stream_notify_cb_t cb, void *userdata) +{ + spa_assert(s); + spa_assert(s->refcount >= 1); + + if (s->state == PA_STREAM_TERMINATED || s->state == PA_STREAM_FAILED) + return; + + s->state_callback = cb; + s->state_userdata = userdata; +} + +SPA_EXPORT +void pa_stream_set_write_callback(pa_stream *s, pa_stream_request_cb_t cb, void *userdata) +{ + spa_assert(s); + spa_assert(s->refcount >= 1); + + if (s->state == PA_STREAM_TERMINATED || s->state == PA_STREAM_FAILED) + return; + + s->write_callback = cb; + s->write_userdata = userdata; +} + +SPA_EXPORT +void pa_stream_set_read_callback(pa_stream *s, pa_stream_request_cb_t cb, void *userdata) +{ + spa_assert(s); + spa_assert(s->refcount >= 1); + + if (s->state == PA_STREAM_TERMINATED || s->state == PA_STREAM_FAILED) + return; + + s->read_callback = cb; + s->read_userdata = userdata; +} + +SPA_EXPORT +void pa_stream_set_overflow_callback(pa_stream *s, pa_stream_notify_cb_t cb, void *userdata) +{ + spa_assert(s); + spa_assert(s->refcount >= 1); + + if (s->state == PA_STREAM_TERMINATED || s->state == PA_STREAM_FAILED) + return; + + s->overflow_callback = cb; + s->overflow_userdata = userdata; +} + +SPA_EXPORT +int64_t pa_stream_get_underflow_index(PA_CONST pa_stream *s) +{ + pw_log_warn("Not Implemented"); + return 0; +} + +SPA_EXPORT +void pa_stream_set_underflow_callback(pa_stream *s, pa_stream_notify_cb_t cb, void *userdata) +{ + spa_assert(s); + spa_assert(s->refcount >= 1); + + if (s->state == PA_STREAM_TERMINATED || s->state == PA_STREAM_FAILED) + return; + + s->underflow_callback = cb; + s->underflow_userdata = userdata; +} + +SPA_EXPORT +void pa_stream_set_started_callback(pa_stream *s, pa_stream_notify_cb_t cb, void *userdata) +{ + spa_assert(s); + spa_assert(s->refcount >= 1); + + if (s->state == PA_STREAM_TERMINATED || s->state == PA_STREAM_FAILED) + return; + + s->started_callback = cb; + s->started_userdata = userdata; +} + +SPA_EXPORT +void pa_stream_set_latency_update_callback(pa_stream *s, pa_stream_notify_cb_t cb, void *userdata) +{ + spa_assert(s); + spa_assert(s->refcount >= 1); + + if (s->state == PA_STREAM_TERMINATED || s->state == PA_STREAM_FAILED) + return; + + s->latency_update_callback = cb; + s->latency_update_userdata = userdata; +} + +SPA_EXPORT +void pa_stream_set_moved_callback(pa_stream *s, pa_stream_notify_cb_t cb, void *userdata) +{ + spa_assert(s); + spa_assert(s->refcount >= 1); + + if (s->state == PA_STREAM_TERMINATED || s->state == PA_STREAM_FAILED) + return; + + s->moved_callback = cb; + s->moved_userdata = userdata; +} + +SPA_EXPORT +void pa_stream_set_suspended_callback(pa_stream *s, pa_stream_notify_cb_t cb, void *userdata) +{ + spa_assert(s); + spa_assert(s->refcount >= 1); + + if (s->state == PA_STREAM_TERMINATED || s->state == PA_STREAM_FAILED) + return; + + s->suspended_callback = cb; + s->suspended_userdata = userdata; +} + +SPA_EXPORT +void pa_stream_set_event_callback(pa_stream *s, pa_stream_event_cb_t cb, void *userdata) +{ + spa_assert(s); + spa_assert(s->refcount >= 1); + + if (s->state == PA_STREAM_TERMINATED || s->state == PA_STREAM_FAILED) + return; + + s->event_callback = cb; + s->event_userdata = userdata; +} + +SPA_EXPORT +void pa_stream_set_buffer_attr_callback(pa_stream *s, pa_stream_notify_cb_t cb, void *userdata) +{ + spa_assert(s); + spa_assert(s->refcount >= 1); + + if (s->state == PA_STREAM_TERMINATED || s->state == PA_STREAM_FAILED) + return; + + s->buffer_attr_callback = cb; + s->buffer_attr_userdata = userdata; +} + +SPA_EXPORT +pa_operation* pa_stream_cork(pa_stream *s, int b, pa_stream_success_cb_t cb, void *userdata) +{ + pa_operation *o; + struct success_ack *d; + + spa_assert(s); + spa_assert(s->refcount >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE); + + s->corked = b; + + pw_stream_set_active(s->stream, !b); + o = pa_operation_new(s->context, s, on_success, sizeof(struct success_ack)); + d = o->userdata; + d->cb = cb; + d->userdata = userdata; + pa_operation_sync(o); + + return o; +} + +SPA_EXPORT +pa_operation* pa_stream_flush(pa_stream *s, pa_stream_success_cb_t cb, void *userdata) +{ + pa_operation *o; + struct success_ack *d; + + spa_assert(s); + spa_assert(s->refcount >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE); + + pw_stream_flush(s->stream, false); + update_timing_info(s); + o = pa_operation_new(s->context, s, on_success, sizeof(struct success_ack)); + d = o->userdata; + d->cb = cb; + d->userdata = userdata; + pa_operation_sync(o); + + return o; +} + +SPA_EXPORT +pa_operation* pa_stream_prebuf(pa_stream *s, pa_stream_success_cb_t cb, void *userdata) +{ + pa_operation *o; + struct success_ack *d; + + spa_assert(s); + spa_assert(s->refcount >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction == PA_STREAM_PLAYBACK, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->buffer_attr.prebuf > 0, PA_ERR_BADSTATE); + + pw_log_warn("Not Implemented"); + o = pa_operation_new(s->context, s, on_success, sizeof(struct success_ack)); + d = o->userdata; + d->cb = cb; + d->userdata = userdata; + pa_operation_sync(o); + + return o; +} + +SPA_EXPORT +pa_operation* pa_stream_trigger(pa_stream *s, pa_stream_success_cb_t cb, void *userdata) +{ + pa_operation *o; + struct success_ack *d; + + spa_assert(s); + spa_assert(s->refcount >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction == PA_STREAM_PLAYBACK, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->buffer_attr.prebuf > 0, PA_ERR_BADSTATE); + + pw_log_warn("Not Implemented"); + o = pa_operation_new(s->context, s, on_success, sizeof(struct success_ack)); + d = o->userdata; + d->cb = cb; + d->userdata = userdata; + pa_operation_sync(o); + + return o; +} + +SPA_EXPORT +pa_operation* pa_stream_set_name(pa_stream *s, const char *name, pa_stream_success_cb_t cb, void *userdata) +{ + pa_operation *o; + struct success_ack *d; + struct spa_dict dict; + struct spa_dict_item items[1]; + + spa_assert(s); + spa_assert(s->refcount >= 1); + spa_assert(name); + + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE); + + items[0] = SPA_DICT_ITEM_INIT(PW_KEY_MEDIA_NAME, name); + dict = SPA_DICT_INIT(items, 1); + pw_stream_update_properties(s->stream, &dict); + + o = pa_operation_new(s->context, s, on_success, sizeof(struct success_ack)); + d = o->userdata; + d->cb = cb; + d->userdata = userdata; + pa_operation_sync(o); + + return o; +} + +SPA_EXPORT +int pa_stream_get_time(pa_stream *s, pa_usec_t *r_usec) +{ + pa_usec_t res; + struct timespec ts; + uint64_t now, delay, read_time; + pa_timing_info *i; + + spa_assert(s); + spa_assert(s->refcount >= 1); + + PA_CHECK_VALIDITY(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY(s->context, s->timing_info_valid, PA_ERR_NODATA); + + clock_gettime(CLOCK_MONOTONIC, &ts); + now = SPA_TIMESPEC_TO_USEC(&ts); + + i = &s->timing_info; + delay = now - SPA_TIMEVAL_TO_USEC(&i->timestamp); + read_time = pa_bytes_to_usec((uint64_t) i->read_index, &s->sample_spec); + + res = delay + read_time; + + if (r_usec) + *r_usec = res; + + pw_log_trace("stream %p: %"PRIu64" %"PRIu64" %"PRIu64" %"PRIi64" %"PRIi64" %"PRIi64" %"PRIu64, + s, now, delay, read_time, + i->write_index, i->read_index, + i->write_index - i->read_index, + res); + + return 0; +} + +static pa_usec_t time_counter_diff(const pa_stream *s, pa_usec_t a, pa_usec_t b, int *negative) { + pa_assert(s); + pa_assert(s->refcount >= 1); + + if (negative) + *negative = 0; + + if (a >= b) + return a-b; + else { + if (negative && s->direction == PA_STREAM_RECORD) { + *negative = 1; + return b-a; + } else + return 0; + } +} + +SPA_EXPORT +int pa_stream_get_latency(pa_stream *s, pa_usec_t *r_usec, int *negative) +{ + pa_usec_t t, c; + int64_t cindex; + + spa_assert(s); + spa_assert(s->refcount >= 1); + spa_assert(r_usec); + + PA_CHECK_VALIDITY(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY(s->context, s->timing_info_valid, PA_ERR_NODATA); + + pa_stream_get_time(s, &t); + + if (s->direction == PA_STREAM_PLAYBACK) + cindex = s->timing_info.write_index; + else + cindex = s->timing_info.read_index; + + if (cindex < 0) + cindex = 0; + + c = pa_bytes_to_usec((uint64_t) cindex, &s->sample_spec); + + if (s->direction == PA_STREAM_PLAYBACK) + *r_usec = time_counter_diff(s, c, t, negative); + else + *r_usec = time_counter_diff(s, t, c, negative); + + return 0; +} + +SPA_EXPORT +const pa_timing_info* pa_stream_get_timing_info(pa_stream *s) +{ + spa_assert(s); + spa_assert(s->refcount >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->timing_info_valid, PA_ERR_NODATA); + + pw_log_trace("stream %p: %"PRIi64" %"PRIi64" %"PRIi64, s, + s->timing_info.write_index, s->timing_info.read_index, + (s->timing_info.write_index - s->timing_info.read_index)); + + return &s->timing_info; +} + +SPA_EXPORT +const pa_sample_spec* pa_stream_get_sample_spec(pa_stream *s) +{ + spa_assert(s); + spa_assert(s->refcount >= 1); + return &s->sample_spec; +} + +SPA_EXPORT +const pa_channel_map* pa_stream_get_channel_map(pa_stream *s) +{ + spa_assert(s); + spa_assert(s->refcount >= 1); + return &s->channel_map; +} + +SPA_EXPORT +const pa_format_info* pa_stream_get_format_info(PA_CONST pa_stream *s) +{ + spa_assert(s); + spa_assert(s->refcount >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE); + + return s->format; +} + +SPA_EXPORT +const pa_buffer_attr* pa_stream_get_buffer_attr(pa_stream *s) +{ + spa_assert(s); + spa_assert(s->refcount >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE); + + return &s->buffer_attr; +} + +SPA_EXPORT +pa_operation *pa_stream_set_buffer_attr(pa_stream *s, const pa_buffer_attr *attr, pa_stream_success_cb_t cb, void *userdata) +{ + pa_operation *o; + struct success_ack *d; + + spa_assert(s); + spa_assert(s->refcount >= 1); + spa_assert(attr); + + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE); + + pw_log_warn("Not Implemented"); + o = pa_operation_new(s->context, s, on_success, sizeof(struct success_ack)); + d = o->userdata; + d->cb = cb; + d->userdata = userdata; + pa_operation_sync(o); + return o; +} + +SPA_EXPORT +pa_operation *pa_stream_update_sample_rate(pa_stream *s, uint32_t rate, pa_stream_success_cb_t cb, void *userdata) +{ + pa_operation *o; + struct success_ack *d; + + spa_assert(s); + spa_assert(s->refcount >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(s->context, pa_sample_rate_valid(rate), PA_ERR_INVALID); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->flags & PA_STREAM_VARIABLE_RATE, PA_ERR_BADSTATE); + + pw_log_warn("Not Implemented"); + o = pa_operation_new(s->context, s, on_success, sizeof(struct success_ack)); + d = o->userdata; + d->cb = cb; + d->userdata = userdata; + pa_operation_sync(o); + return o; +} + +SPA_EXPORT +pa_operation *pa_stream_proplist_update(pa_stream *s, pa_update_mode_t mode, pa_proplist *p, pa_stream_success_cb_t cb, void *userdata) +{ + pa_operation *o; + struct success_ack *d; + + spa_assert(s); + spa_assert(s->refcount >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(s->context, mode == PA_UPDATE_SET || + mode == PA_UPDATE_MERGE || mode == PA_UPDATE_REPLACE, PA_ERR_INVALID); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE); + + pa_proplist_update(s->proplist, mode, p); + + o = pa_operation_new(s->context, s, on_success, sizeof(struct success_ack)); + d = o->userdata; + d->cb = cb; + d->userdata = userdata; + pa_operation_sync(o); + return o; +} + +SPA_EXPORT +pa_operation *pa_stream_proplist_remove(pa_stream *s, const char *const keys[], pa_stream_success_cb_t cb, void *userdata) +{ + pa_operation *o; + struct success_ack *d; + + spa_assert(s); + spa_assert(s->refcount >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(s->context, keys && keys[0], PA_ERR_INVALID); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE); + + pw_log_warn("Not Implemented"); + o = pa_operation_new(s->context, s, on_success, sizeof(struct success_ack)); + d = o->userdata; + d->cb = cb; + d->userdata = userdata; + pa_operation_sync(o); + return o; +} + +SPA_EXPORT +int pa_stream_set_monitor_stream(pa_stream *s, uint32_t sink_input_idx) +{ + spa_assert(s); + spa_assert(s->refcount >= 1); + + pw_log_warn("stream %p: Not implemented %d", s, sink_input_idx); + + PA_CHECK_VALIDITY(s->context, sink_input_idx != PA_INVALID_INDEX, PA_ERR_INVALID); + PA_CHECK_VALIDITY(s->context, s->state == PA_STREAM_UNCONNECTED, PA_ERR_BADSTATE); + + s->direct_on_input = sink_input_idx; + return 0; +} + +SPA_EXPORT +uint32_t pa_stream_get_monitor_stream(PA_CONST pa_stream *s) +{ + spa_assert(s); + spa_assert(s->refcount >= 1); + + PA_CHECK_VALIDITY_RETURN_ANY(s->context, s->direct_on_input != PA_INVALID_INDEX, + PA_ERR_BADSTATE, PA_INVALID_INDEX); + + return s->direct_on_input; +} diff --git a/pipewire-pulseaudio/src/subscribe.c b/pipewire-pulseaudio/src/subscribe.c new file mode 100644 index 000000000..4ccdc7c70 --- /dev/null +++ b/pipewire-pulseaudio/src/subscribe.c @@ -0,0 +1,43 @@ +/* PipeWire + * Copyright (C) 2018 Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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. + */ + +#include + +#include + +#include "internal.h" + +struct subscribe_data +{ + pa_context_success_cb_t cb; + void *userdata; +}; + +SPA_EXPORT +void pa_context_set_subscribe_callback(pa_context *c, pa_context_subscribe_cb_t cb, void *userdata) +{ + pa_assert(c); + pa_assert(c->refcount >= 1); + + if (c->state == PA_CONTEXT_TERMINATED || c->state == PA_CONTEXT_FAILED) + return; + + c->subscribe_callback = cb; + c->subscribe_userdata = userdata; +} diff --git a/pipewire-pulseaudio/src/thread-mainloop.c b/pipewire-pulseaudio/src/thread-mainloop.c new file mode 100644 index 000000000..266625f8f --- /dev/null +++ b/pipewire-pulseaudio/src/thread-mainloop.c @@ -0,0 +1,131 @@ +/* PipeWire + * Copyright (C) 2018 Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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. + */ + +#include +#include + +#include +#include + +#include "internal.h" + +struct pa_threaded_mainloop +{ + pa_mainloop *loop; + struct pw_thread_loop *tloop; +}; + +SPA_EXPORT +pa_threaded_mainloop *pa_threaded_mainloop_new(void) +{ + pa_threaded_mainloop *m; + + m = calloc(1, sizeof(pa_threaded_mainloop)); + if (m == NULL) + return NULL; + + m->loop = pa_mainloop_new(); + if (m->loop == NULL) + goto no_mem; + + m->tloop = pw_thread_loop_new(m->loop->loop, NULL); + if (m->tloop == NULL) + goto no_mem; + + return m; + + no_mem: + if (m->loop) + pa_mainloop_free(m->loop); + free(m); + return NULL; +} + +SPA_EXPORT +void pa_threaded_mainloop_free(pa_threaded_mainloop* m) +{ + pw_thread_loop_destroy(m->tloop); + pa_mainloop_free(m->loop); + free(m); +} + +SPA_EXPORT +int pa_threaded_mainloop_start(pa_threaded_mainloop *m) +{ + return pw_thread_loop_start(m->tloop); +} + +SPA_EXPORT +void pa_threaded_mainloop_stop(pa_threaded_mainloop *m) +{ + pw_thread_loop_stop(m->tloop); +} + +SPA_EXPORT +void pa_threaded_mainloop_lock(pa_threaded_mainloop *m) +{ + pw_thread_loop_lock(m->tloop); +} + +SPA_EXPORT +void pa_threaded_mainloop_unlock(pa_threaded_mainloop *m) +{ + pw_thread_loop_unlock(m->tloop); +} + +SPA_EXPORT +void pa_threaded_mainloop_wait(pa_threaded_mainloop *m) +{ + pw_thread_loop_wait(m->tloop); +} + +SPA_EXPORT +void pa_threaded_mainloop_signal(pa_threaded_mainloop *m, int wait_for_accept) +{ + pw_thread_loop_signal(m->tloop, wait_for_accept); +} + +SPA_EXPORT +void pa_threaded_mainloop_accept(pa_threaded_mainloop *m) +{ + pw_thread_loop_accept(m->tloop); +} + +SPA_EXPORT +int pa_threaded_mainloop_get_retval(PA_CONST pa_threaded_mainloop *m) +{ + return pa_mainloop_get_retval(m->loop); +} + +SPA_EXPORT +pa_mainloop_api* pa_threaded_mainloop_get_api(pa_threaded_mainloop*m) +{ + return pa_mainloop_get_api(m->loop); +} + +SPA_EXPORT +int pa_threaded_mainloop_in_thread(pa_threaded_mainloop *m) +{ + return pw_thread_loop_in_thread(m->tloop); +} + +SPA_EXPORT +void pa_threaded_mainloop_set_name(pa_threaded_mainloop *m, const char *name) +{ +} diff --git a/pipewire-pulseaudio/src/timeval.c b/pipewire-pulseaudio/src/timeval.c new file mode 100644 index 000000000..e8ca09a9d --- /dev/null +++ b/pipewire-pulseaudio/src/timeval.c @@ -0,0 +1,220 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 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.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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see . +***/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#ifdef HAVE_WINDOWS_H +#include +#endif + +#include + +#include "internal.h" + +#define HAVE_GETTIMEOFDAY + +SPA_EXPORT +struct timeval *pa_gettimeofday(struct timeval *tv) { + pa_assert(tv); + +#if defined(OS_IS_WIN32) + /* + * Copied from implementation by Steven Edwards (LGPL). + * Found on wine mailing list. + */ +#if defined(_MSC_VER) || defined(__BORLANDC__) +#define EPOCHFILETIME (116444736000000000i64) +#else +#define EPOCHFILETIME (116444736000000000LL) +#endif +{ + FILETIME ft; + LARGE_INTEGER li; + int64_t t; + + GetSystemTimeAsFileTime(&ft); + li.LowPart = ft.dwLowDateTime; + li.HighPart = ft.dwHighDateTime; + t = li.QuadPart; /* In 100-nanosecond intervals */ + t -= EPOCHFILETIME; /* Offset to the Epoch time */ + t /= 10; /* In microseconds */ + tv->tv_sec = (time_t) (t / PA_USEC_PER_SEC); + tv->tv_usec = (suseconds_t) (t % PA_USEC_PER_SEC); +} +#elif defined(HAVE_GETTIMEOFDAY) + pa_assert_se(gettimeofday(tv, NULL) == 0); +#else +#error "Platform lacks gettimeofday() or equivalent function." +#endif + + return tv; +} + +SPA_EXPORT +pa_usec_t pa_timeval_diff(const struct timeval *a, const struct timeval *b) { + pa_usec_t r; + + pa_assert(a); + pa_assert(b); + + /* Check which is the earlier time and swap the two arguments if required. */ + if (PA_UNLIKELY(pa_timeval_cmp(a, b) < 0)) { + const struct timeval *c; + c = a; + a = b; + b = c; + } + + /* Calculate the second difference*/ + r = ((pa_usec_t) a->tv_sec - (pa_usec_t) b->tv_sec) * PA_USEC_PER_SEC; + + /* Calculate the microsecond difference */ + if (a->tv_usec > b->tv_usec) + r += (pa_usec_t) a->tv_usec - (pa_usec_t) b->tv_usec; + else if (a->tv_usec < b->tv_usec) + r -= (pa_usec_t) b->tv_usec - (pa_usec_t) a->tv_usec; + + return r; +} + +SPA_EXPORT +int pa_timeval_cmp(const struct timeval *a, const struct timeval *b) { + pa_assert(a); + pa_assert(b); + + if (a->tv_sec < b->tv_sec) + return -1; + + if (a->tv_sec > b->tv_sec) + return 1; + + if (a->tv_usec < b->tv_usec) + return -1; + + if (a->tv_usec > b->tv_usec) + return 1; + + return 0; +} + +SPA_EXPORT +pa_usec_t pa_timeval_age(const struct timeval *tv) { + struct timeval now; + pa_assert(tv); + + return pa_timeval_diff(pa_gettimeofday(&now), tv); +} + +SPA_EXPORT +struct timeval* pa_timeval_add(struct timeval *tv, pa_usec_t v) { + time_t secs; + pa_assert(tv); + + secs = (time_t) (v/PA_USEC_PER_SEC); + + if (PA_UNLIKELY(tv->tv_sec > PA_INT_TYPE_MAX(time_t) - secs)) + goto overflow; + + tv->tv_sec += secs; + v -= (pa_usec_t) secs * PA_USEC_PER_SEC; + tv->tv_usec += (suseconds_t) v; + + /* Normalize */ + while ((pa_usec_t) tv->tv_usec >= PA_USEC_PER_SEC) { + + if (PA_UNLIKELY(tv->tv_sec >= PA_INT_TYPE_MAX(time_t))) + goto overflow; + + tv->tv_sec++; + tv->tv_usec -= (suseconds_t) PA_USEC_PER_SEC; + } + + return tv; + +overflow: + tv->tv_sec = PA_INT_TYPE_MAX(time_t); + tv->tv_usec = (suseconds_t) (PA_USEC_PER_SEC-1); + return tv; +} + +SPA_EXPORT +struct timeval* pa_timeval_sub(struct timeval *tv, pa_usec_t v) { + time_t secs; + pa_assert(tv); + + secs = (time_t) (v/PA_USEC_PER_SEC); + + if (PA_UNLIKELY(tv->tv_sec < secs)) + goto underflow; + + tv->tv_sec -= secs; + v -= (pa_usec_t) secs * PA_USEC_PER_SEC; + + if (tv->tv_usec >= (suseconds_t) v) + tv->tv_usec -= (suseconds_t) v; + else { + + if (PA_UNLIKELY(tv->tv_sec <= 0)) + goto underflow; + + tv->tv_sec --; + tv->tv_usec += (suseconds_t) (PA_USEC_PER_SEC - v); + } + + return tv; + +underflow: + tv->tv_sec = 0; + tv->tv_usec = 0; + return tv; +} + +SPA_EXPORT +struct timeval* pa_timeval_store(struct timeval *tv, pa_usec_t v) { + pa_assert(tv); + + if (PA_UNLIKELY(v == PA_USEC_INVALID)) { + tv->tv_sec = PA_INT_TYPE_MAX(time_t); + tv->tv_usec = (suseconds_t) (PA_USEC_PER_SEC-1); + + return tv; + } + + tv->tv_sec = (time_t) (v / PA_USEC_PER_SEC); + tv->tv_usec = (suseconds_t) (v % PA_USEC_PER_SEC); + + return tv; +} + +SPA_EXPORT +pa_usec_t pa_timeval_load(const struct timeval *tv) { + + if (PA_UNLIKELY(!tv)) + return PA_USEC_INVALID; + + return + (pa_usec_t) tv->tv_sec * PA_USEC_PER_SEC + + (pa_usec_t) tv->tv_usec; +} diff --git a/pipewire-pulseaudio/src/utf8.c b/pipewire-pulseaudio/src/utf8.c new file mode 100644 index 000000000..35e4b6d5f --- /dev/null +++ b/pipewire-pulseaudio/src/utf8.c @@ -0,0 +1,295 @@ +/*** + This file is part of PulseAudio. + + Copyright 2006 Lennart Poettering + Copyright 2006 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.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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see . +***/ + +/* This file is based on the GLIB utf8 validation functions. The + * original license text follows. */ + +/* gutf8.c - Operations on UTF-8 strings. + * + * Copyright (C) 1999 Tom Tromey + * Copyright (C) 2000 Red Hat, Inc. + * + * 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 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, see . + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include + +#ifdef HAVE_ICONV +#include +#endif + +#include +#include + +#include "internal.h" + +#define FILTER_CHAR '_' + +static inline bool is_unicode_valid(uint32_t ch) { + + if (ch >= 0x110000) /* End of unicode space */ + return false; + if ((ch & 0xFFFFF800) == 0xD800) /* Reserved area for UTF-16 */ + return false; + if ((ch >= 0xFDD0) && (ch <= 0xFDEF)) /* Reserved */ + return false; + if ((ch & 0xFFFE) == 0xFFFE) /* BOM (Byte Order Mark) */ + return false; + + return true; +} + +static inline bool is_continuation_char(uint8_t ch) { + if ((ch & 0xc0) != 0x80) /* 10xxxxxx */ + return false; + return true; +} + +static inline void merge_continuation_char(uint32_t *u_ch, uint8_t ch) { + *u_ch <<= 6; + *u_ch |= ch & 0x3f; +} + +static char* utf8_validate(const char *str, char *output) { + uint32_t val = 0; + uint32_t min = 0; + const uint8_t *p, *last; + int size; + uint8_t *o; + + pa_assert(str); + + o = (uint8_t*) output; + for (p = (const uint8_t*) str; *p; p++) { + if (*p < 128) { + if (o) + *o = *p; + } else { + last = p; + + if ((*p & 0xe0) == 0xc0) { /* 110xxxxx two-char seq. */ + size = 2; + min = 128; + val = (uint32_t) (*p & 0x1e); + goto ONE_REMAINING; + } else if ((*p & 0xf0) == 0xe0) { /* 1110xxxx three-char seq.*/ + size = 3; + min = (1 << 11); + val = (uint32_t) (*p & 0x0f); + goto TWO_REMAINING; + } else if ((*p & 0xf8) == 0xf0) { /* 11110xxx four-char seq */ + size = 4; + min = (1 << 16); + val = (uint32_t) (*p & 0x07); + } else + goto error; + + p++; + if (!is_continuation_char(*p)) + goto error; + merge_continuation_char(&val, *p); + +TWO_REMAINING: + p++; + if (!is_continuation_char(*p)) + goto error; + merge_continuation_char(&val, *p); + +ONE_REMAINING: + p++; + if (!is_continuation_char(*p)) + goto error; + merge_continuation_char(&val, *p); + + if (val < min) + goto error; + + if (!is_unicode_valid(val)) + goto error; + + if (o) { + memcpy(o, last, (size_t) size); + o += size; + } + + continue; + +error: + if (o) { + *o = FILTER_CHAR; + p = last; /* We retry at the next character */ + } else + goto failure; + } + + if (o) + o++; + } + + if (o) { + *o = '\0'; + return output; + } + + return (char*) str; + +failure: + return NULL; +} + +SPA_EXPORT +char* pa_utf8_valid (const char *str) { + return utf8_validate(str, NULL); +} + +SPA_EXPORT +char* pa_utf8_filter (const char *str) { + char *new_str; + + pa_assert(str); + new_str = pa_xmalloc(strlen(str) + 1); + return utf8_validate(str, new_str); +} + +#ifdef HAVE_ICONV + +static char* iconv_simple(const char *str, const char *to, const char *from) { + char *new_str; + size_t len, inlen; + iconv_t cd; + ICONV_CONST char *inbuf; + char *outbuf; + size_t res, inbytes, outbytes; + + pa_assert(str); + pa_assert(to); + pa_assert(from); + + cd = iconv_open(to, from); + if (cd == (iconv_t)-1) + return NULL; + + inlen = len = strlen(str) + 1; + new_str = pa_xmalloc(len); + + for (;;) { + inbuf = (ICONV_CONST char*) str; /* Brain dead prototype for iconv() */ + inbytes = inlen; + outbuf = new_str; + outbytes = len; + + res = iconv(cd, &inbuf, &inbytes, &outbuf, &outbytes); + + if (res != (size_t)-1) + break; + + if (errno != E2BIG) { + pa_xfree(new_str); + new_str = NULL; + break; + } + + pa_assert(inbytes != 0); + + len += inbytes; + new_str = pa_xrealloc(new_str, len); + } + + iconv_close(cd); + + return new_str; +} + +SPA_EXPORT +char* pa_utf8_to_locale (const char *str) { + return iconv_simple(str, "", "UTF-8"); +} + +SPA_EXPORT +char* pa_locale_to_utf8 (const char *str) { + return iconv_simple(str, "UTF-8", ""); +} + +#else + +SPA_EXPORT +char* pa_utf8_to_locale (const char *str) { + pa_assert(str); + + return pa_ascii_filter(str); +} + +SPA_EXPORT +char* pa_locale_to_utf8 (const char *str) { + pa_assert(str); + + if (pa_utf8_valid(str)) + return pa_xstrdup(str); + + return NULL; +} + +#endif + +SPA_EXPORT +char *pa_ascii_valid(const char *str) { + const char *p; + pa_assert(str); + + for (p = str; *p; p++) + if ((unsigned char) *p >= 128) + return NULL; + + return (char*) str; +} + +SPA_EXPORT +char *pa_ascii_filter(const char *str) { + char *r, *s, *d; + pa_assert(str); + + r = pa_xstrdup(str); + + for (s = r, d = r; *s; s++) + if ((unsigned char) *s < 128) + *(d++) = *s; + + *d = 0; + + return r; +} diff --git a/pipewire-pulseaudio/src/util.c b/pipewire-pulseaudio/src/util.c new file mode 100644 index 000000000..b457ccbba --- /dev/null +++ b/pipewire-pulseaudio/src/util.c @@ -0,0 +1,84 @@ +/* PipeWire + * Copyright (C) 2018 Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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. + */ + +#include + +#include +#include + +#include + +#define PA_PATH_SEP_CHAR '/' + +SPA_EXPORT +char *pa_get_user_name(char *s, size_t l) +{ + return strncpy(s, pw_get_user_name(), l); +} + + +SPA_EXPORT +char *pa_get_host_name(char *s, size_t l) +{ + return strncpy(s, pw_get_host_name(), l); +} + +SPA_EXPORT +char *pa_get_fqdn(char *s, size_t l) +{ + return strncpy(s, pw_get_host_name(), l); +} + +SPA_EXPORT +char *pa_get_home_dir(char *s, size_t l) +{ + pw_log_warn("Not Implemented"); + return NULL; +} + +SPA_EXPORT +char *pa_get_binary_name(char *s, size_t l) +{ + return strncpy(s, pw_get_prgname(), l); +} + +SPA_EXPORT +char *pa_path_get_filename(const char *p) +{ + char *fn; + + if (!p) + return NULL; + + if ((fn = strrchr(p, PA_PATH_SEP_CHAR))) + return fn+1; + + return (char*) p; +} + +SPA_EXPORT +int pa_msleep(unsigned long t) +{ + struct timespec ts; + + ts.tv_sec = (time_t) (t / SPA_MSEC_PER_SEC); + ts.tv_nsec = (long) ((t % SPA_MSEC_PER_SEC) * SPA_NSEC_PER_MSEC); + + return nanosleep(&ts, NULL); +} diff --git a/pipewire-pulseaudio/src/version.c b/pipewire-pulseaudio/src/version.c new file mode 100644 index 000000000..bb0e387ad --- /dev/null +++ b/pipewire-pulseaudio/src/version.c @@ -0,0 +1,29 @@ +/* PipeWire + * Copyright (C) 2018 Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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. + */ + +#include + +#include + +SPA_EXPORT +const char* pa_get_library_version(void) +{ + return pa_get_headers_version(); +} + diff --git a/pipewire-pulseaudio/src/volume.c b/pipewire-pulseaudio/src/volume.c new file mode 100644 index 000000000..35527ba75 --- /dev/null +++ b/pipewire-pulseaudio/src/volume.c @@ -0,0 +1,1037 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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, see . +***/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include + +#include + +#include "internal.h" +#include "sample-util.h" + +SPA_EXPORT +int pa_cvolume_equal(const pa_cvolume *a, const pa_cvolume *b) { + int i; + pa_assert(a); + pa_assert(b); + + pa_return_val_if_fail(pa_cvolume_valid(a), 0); + + if (PA_UNLIKELY(a == b)) + return 1; + + pa_return_val_if_fail(pa_cvolume_valid(b), 0); + + if (a->channels != b->channels) + return 0; + + for (i = 0; i < a->channels; i++) + if (a->values[i] != b->values[i]) + return 0; + + return 1; +} + +SPA_EXPORT +pa_cvolume* pa_cvolume_init(pa_cvolume *a) { + unsigned c; + + pa_assert(a); + + a->channels = 0; + + for (c = 0; c < PA_CHANNELS_MAX; c++) + a->values[c] = PA_VOLUME_INVALID; + + return a; +} + +SPA_EXPORT +pa_cvolume* pa_cvolume_set(pa_cvolume *a, unsigned channels, pa_volume_t v) { + int i; + + pa_assert(a); + pa_assert(pa_channels_valid(channels)); + + a->channels = (uint8_t) channels; + + for (i = 0; i < a->channels; i++) + /* Clamp in case there is stale data that exceeds the current + * PA_VOLUME_MAX */ + a->values[i] = PA_CLAMP_VOLUME(v); + + return a; +} + +SPA_EXPORT +pa_volume_t pa_cvolume_avg(const pa_cvolume *a) { + uint64_t sum = 0; + unsigned c; + + pa_assert(a); + pa_return_val_if_fail(pa_cvolume_valid(a), PA_VOLUME_MUTED); + + for (c = 0; c < a->channels; c++) + sum += a->values[c]; + + sum /= a->channels; + + return (pa_volume_t) sum; +} + +SPA_EXPORT +pa_volume_t pa_cvolume_avg_mask(const pa_cvolume *a, const pa_channel_map *cm, pa_channel_position_mask_t mask) { + uint64_t sum = 0; + unsigned c, n; + + pa_assert(a); + + if (!cm) + return pa_cvolume_avg(a); + + pa_return_val_if_fail(pa_cvolume_compatible_with_channel_map(a, cm), PA_VOLUME_MUTED); + + for (c = n = 0; c < a->channels; c++) { + + if (!(PA_CHANNEL_POSITION_MASK(cm->map[c]) & mask)) + continue; + + sum += a->values[c]; + n ++; + } + + if (n > 0) + sum /= n; + + return (pa_volume_t) sum; +} + +SPA_EXPORT +pa_volume_t pa_cvolume_max(const pa_cvolume *a) { + pa_volume_t m = PA_VOLUME_MUTED; + unsigned c; + + pa_assert(a); + pa_return_val_if_fail(pa_cvolume_valid(a), PA_VOLUME_MUTED); + + for (c = 0; c < a->channels; c++) + if (a->values[c] > m) + m = a->values[c]; + + return m; +} + +SPA_EXPORT +pa_volume_t pa_cvolume_min(const pa_cvolume *a) { + pa_volume_t m = PA_VOLUME_MAX; + unsigned c; + + pa_assert(a); + pa_return_val_if_fail(pa_cvolume_valid(a), PA_VOLUME_MUTED); + + for (c = 0; c < a->channels; c++) + if (a->values[c] < m) + m = a->values[c]; + + return m; +} + +SPA_EXPORT +pa_volume_t pa_cvolume_max_mask(const pa_cvolume *a, const pa_channel_map *cm, pa_channel_position_mask_t mask) { + pa_volume_t m = PA_VOLUME_MUTED; + unsigned c; + + pa_assert(a); + + if (!cm) + return pa_cvolume_max(a); + + pa_return_val_if_fail(pa_cvolume_compatible_with_channel_map(a, cm), PA_VOLUME_MUTED); + + for (c = 0; c < a->channels; c++) { + + if (!(PA_CHANNEL_POSITION_MASK(cm->map[c]) & mask)) + continue; + + if (a->values[c] > m) + m = a->values[c]; + } + + return m; +} + +SPA_EXPORT +pa_volume_t pa_cvolume_min_mask(const pa_cvolume *a, const pa_channel_map *cm, pa_channel_position_mask_t mask) { + pa_volume_t m = PA_VOLUME_MAX; + unsigned c; + + pa_assert(a); + + if (!cm) + return pa_cvolume_min(a); + + pa_return_val_if_fail(pa_cvolume_compatible_with_channel_map(a, cm), PA_VOLUME_MUTED); + + for (c = 0; c < a->channels; c++) { + + if (!(PA_CHANNEL_POSITION_MASK(cm->map[c]) & mask)) + continue; + + if (a->values[c] < m) + m = a->values[c]; + } + + return m; +} + +SPA_EXPORT +pa_volume_t pa_sw_volume_multiply(pa_volume_t a, pa_volume_t b) { + uint64_t result; + + pa_return_val_if_fail(PA_VOLUME_IS_VALID(a), PA_VOLUME_INVALID); + pa_return_val_if_fail(PA_VOLUME_IS_VALID(b), PA_VOLUME_INVALID); + + /* cbrt((a/PA_VOLUME_NORM)^3*(b/PA_VOLUME_NORM)^3)*PA_VOLUME_NORM = a*b/PA_VOLUME_NORM */ + + result = ((uint64_t) a * (uint64_t) b + (uint64_t) PA_VOLUME_NORM / 2ULL) / (uint64_t) PA_VOLUME_NORM; + + if (result > (uint64_t)PA_VOLUME_MAX) + pa_log_warn("pa_sw_volume_multiply: Volume exceeds maximum allowed value and will be clipped. Please check your volume settings."); + + return (pa_volume_t) PA_CLAMP_VOLUME(result); +} + +SPA_EXPORT +pa_volume_t pa_sw_volume_divide(pa_volume_t a, pa_volume_t b) { + uint64_t result; + + pa_return_val_if_fail(PA_VOLUME_IS_VALID(a), PA_VOLUME_INVALID); + pa_return_val_if_fail(PA_VOLUME_IS_VALID(b), PA_VOLUME_INVALID); + + if (b <= PA_VOLUME_MUTED) + return 0; + + result = ((uint64_t) a * (uint64_t) PA_VOLUME_NORM + (uint64_t) b / 2ULL) / (uint64_t) b; + + if (result > (uint64_t)PA_VOLUME_MAX) + pa_log_warn("pa_sw_volume_divide: Volume exceeds maximum allowed value and will be clipped. Please check your volume settings."); + + return (pa_volume_t) PA_CLAMP_VOLUME(result); +} + +/* Amplitude, not power */ +static double linear_to_dB(double v) { + return 20.0 * log10(v); +} + +static double dB_to_linear(double v) { + return pow(10.0, v / 20.0); +} + +SPA_EXPORT +pa_volume_t pa_sw_volume_from_dB(double dB) { + if (isinf(dB) < 0 || dB <= PA_DECIBEL_MININFTY) + return PA_VOLUME_MUTED; + + return pa_sw_volume_from_linear(dB_to_linear(dB)); +} + +SPA_EXPORT +double pa_sw_volume_to_dB(pa_volume_t v) { + + pa_return_val_if_fail(PA_VOLUME_IS_VALID(v), PA_DECIBEL_MININFTY); + + if (v <= PA_VOLUME_MUTED) + return PA_DECIBEL_MININFTY; + + return linear_to_dB(pa_sw_volume_to_linear(v)); +} + +SPA_EXPORT +pa_volume_t pa_sw_volume_from_linear(double v) { + + if (v <= 0.0) + return PA_VOLUME_MUTED; + + /* + * We use a cubic mapping here, as suggested and discussed here: + * + * http://www.robotplanet.dk/audio/audio_gui_design/ + * http://lists.linuxaudio.org/pipermail/linux-audio-dev/2009-May/thread.html#23151 + * + * We make sure that the conversion to linear and back yields the + * same volume value! That's why we need the lround() below! + */ + + return (pa_volume_t) PA_CLAMP_VOLUME((uint64_t) lround(cbrt(v) * PA_VOLUME_NORM)); +} + +SPA_EXPORT +double pa_sw_volume_to_linear(pa_volume_t v) { + double f; + + pa_return_val_if_fail(PA_VOLUME_IS_VALID(v), 0.0); + + if (v <= PA_VOLUME_MUTED) + return 0.0; + + if (v == PA_VOLUME_NORM) + return 1.0; + + f = ((double) v / PA_VOLUME_NORM); + + return f*f*f; +} + +SPA_EXPORT +char *pa_cvolume_snprint(char *s, size_t l, const pa_cvolume *c) { + unsigned channel; + bool first = true; + char *e; + + pa_assert(s); + pa_assert(l > 0); + pa_assert(c); + + pa_init_i18n(); + + if (!pa_cvolume_valid(c)) { + pa_snprintf(s, l, _("(invalid)")); + return s; + } + + *(e = s) = 0; + + for (channel = 0; channel < c->channels && l > 1; channel++) { + l -= pa_snprintf(e, l, "%s%u: %3u%%", + first ? "" : " ", + channel, + (unsigned)(((uint64_t)c->values[channel] * 100 + (uint64_t)PA_VOLUME_NORM / 2) / (uint64_t)PA_VOLUME_NORM)); + + e = strchr(e, 0); + first = false; + } + + return s; +} + +SPA_EXPORT +char *pa_volume_snprint(char *s, size_t l, pa_volume_t v) { + pa_assert(s); + pa_assert(l > 0); + + pa_init_i18n(); + + if (!PA_VOLUME_IS_VALID(v)) { + pa_snprintf(s, l, _("(invalid)")); + return s; + } + + pa_snprintf(s, l, "%3u%%", (unsigned)(((uint64_t)v * 100 + (uint64_t)PA_VOLUME_NORM / 2) / (uint64_t)PA_VOLUME_NORM)); + return s; +} + +SPA_EXPORT +char *pa_sw_cvolume_snprint_dB(char *s, size_t l, const pa_cvolume *c) { + unsigned channel; + bool first = true; + char *e; + + pa_assert(s); + pa_assert(l > 0); + pa_assert(c); + + pa_init_i18n(); + + if (!pa_cvolume_valid(c)) { + pa_snprintf(s, l, _("(invalid)")); + return s; + } + + *(e = s) = 0; + + for (channel = 0; channel < c->channels && l > 1; channel++) { + double f = pa_sw_volume_to_dB(c->values[channel]); + + l -= pa_snprintf(e, l, "%s%u: %0.2f dB", + first ? "" : " ", + channel, + isinf(f) < 0 || f <= PA_DECIBEL_MININFTY ? -INFINITY : f); + + e = strchr(e, 0); + first = false; + } + + return s; +} + +SPA_EXPORT +char *pa_cvolume_snprint_verbose(char *s, size_t l, const pa_cvolume *c, const pa_channel_map *map, int print_dB) { + char *current = s; + bool first = true; + + pa_assert(s); + pa_assert(l > 0); + pa_assert(c); + + pa_init_i18n(); + + if (!pa_cvolume_valid(c)) { + pa_snprintf(s, l, _("(invalid)")); + return s; + } + + pa_assert(!map || (map->channels == c->channels)); + pa_assert(!map || pa_channel_map_valid(map)); + + current[0] = 0; + + for (unsigned channel = 0; channel < c->channels && l > 1; channel++) { + char channel_position[32]; + size_t bytes_printed; + char buf[PA_VOLUME_SNPRINT_VERBOSE_MAX]; + + if (map) + pa_snprintf(channel_position, sizeof(channel_position), "%s", pa_channel_position_to_string(map->map[channel])); + else + pa_snprintf(channel_position, sizeof(channel_position), "%u", channel); + + bytes_printed = pa_snprintf(current, l, "%s%s: %s", + first ? "" : ", ", + channel_position, + pa_volume_snprint_verbose(buf, sizeof(buf), c->values[channel], print_dB)); + l -= bytes_printed; + current += bytes_printed; + first = false; + } + + return s; +} + +SPA_EXPORT +char *pa_sw_volume_snprint_dB(char *s, size_t l, pa_volume_t v) { + double f; + + pa_assert(s); + pa_assert(l > 0); + + pa_init_i18n(); + + if (!PA_VOLUME_IS_VALID(v)) { + pa_snprintf(s, l, _("(invalid)")); + return s; + } + + f = pa_sw_volume_to_dB(v); + pa_snprintf(s, l, "%0.2f dB", isinf(f) < 0 || f <= PA_DECIBEL_MININFTY ? -INFINITY : f); + + return s; +} + +SPA_EXPORT +char *pa_volume_snprint_verbose(char *s, size_t l, pa_volume_t v, int print_dB) { + char dB[PA_SW_VOLUME_SNPRINT_DB_MAX]; + + pa_assert(s); + pa_assert(l > 0); + + pa_init_i18n(); + + if (!PA_VOLUME_IS_VALID(v)) { + pa_snprintf(s, l, _("(invalid)")); + return s; + } + + pa_snprintf(s, l, "%" PRIu32 " / %3u%%%s%s", + v, + (unsigned)(((uint64_t)v * 100 + (uint64_t)PA_VOLUME_NORM / 2) / (uint64_t)PA_VOLUME_NORM), + print_dB ? " / " : "", + print_dB ? pa_sw_volume_snprint_dB(dB, sizeof(dB), v) : ""); + + return s; +} + +SPA_EXPORT +int pa_cvolume_channels_equal_to(const pa_cvolume *a, pa_volume_t v) { + unsigned c; + pa_assert(a); + + pa_return_val_if_fail(pa_cvolume_valid(a), 0); + pa_return_val_if_fail(PA_VOLUME_IS_VALID(v), 0); + + for (c = 0; c < a->channels; c++) + if (a->values[c] != v) + return 0; + + return 1; +} + +SPA_EXPORT +pa_cvolume *pa_sw_cvolume_multiply(pa_cvolume *dest, const pa_cvolume *a, const pa_cvolume *b) { + unsigned i; + + pa_assert(dest); + pa_assert(a); + pa_assert(b); + + pa_return_val_if_fail(pa_cvolume_valid(a), NULL); + pa_return_val_if_fail(pa_cvolume_valid(b), NULL); + + dest->channels = PA_MIN(a->channels, b->channels); + + for (i = 0; i < dest->channels; i++) + dest->values[i] = pa_sw_volume_multiply(a->values[i], b->values[i]); + + return dest; +} + +SPA_EXPORT +pa_cvolume *pa_sw_cvolume_multiply_scalar(pa_cvolume *dest, const pa_cvolume *a, pa_volume_t b) { + unsigned i; + + pa_assert(dest); + pa_assert(a); + + pa_return_val_if_fail(pa_cvolume_valid(a), NULL); + pa_return_val_if_fail(PA_VOLUME_IS_VALID(b), NULL); + + for (i = 0; i < a->channels; i++) + dest->values[i] = pa_sw_volume_multiply(a->values[i], b); + + dest->channels = (uint8_t) i; + + return dest; +} + +SPA_EXPORT +pa_cvolume *pa_sw_cvolume_divide(pa_cvolume *dest, const pa_cvolume *a, const pa_cvolume *b) { + unsigned i; + + pa_assert(dest); + pa_assert(a); + pa_assert(b); + + pa_return_val_if_fail(pa_cvolume_valid(a), NULL); + pa_return_val_if_fail(pa_cvolume_valid(b), NULL); + + dest->channels = PA_MIN(a->channels, b->channels); + + for (i = 0; i < dest->channels; i++) + dest->values[i] = pa_sw_volume_divide(a->values[i], b->values[i]); + + return dest; +} + +SPA_EXPORT +pa_cvolume *pa_sw_cvolume_divide_scalar(pa_cvolume *dest, const pa_cvolume *a, pa_volume_t b) { + unsigned i; + + pa_assert(dest); + pa_assert(a); + + pa_return_val_if_fail(pa_cvolume_valid(a), NULL); + pa_return_val_if_fail(PA_VOLUME_IS_VALID(b), NULL); + + for (i = 0; i < a->channels; i++) + dest->values[i] = pa_sw_volume_divide(a->values[i], b); + + dest->channels = (uint8_t) i; + + return dest; +} + +SPA_EXPORT +int pa_cvolume_valid(const pa_cvolume *v) { + unsigned c; + + pa_assert(v); + + if (!pa_channels_valid(v->channels)) + return 0; + + for (c = 0; c < v->channels; c++) + if (!PA_VOLUME_IS_VALID(v->values[c])) + return 0; + + return 1; +} + +static bool on_left(pa_channel_position_t p) { + return !!(PA_CHANNEL_POSITION_MASK(p) & PA_CHANNEL_POSITION_MASK_LEFT); +} + +static bool on_right(pa_channel_position_t p) { + return !!(PA_CHANNEL_POSITION_MASK(p) & PA_CHANNEL_POSITION_MASK_RIGHT); +} + +static bool on_center(pa_channel_position_t p) { + return !!(PA_CHANNEL_POSITION_MASK(p) & PA_CHANNEL_POSITION_MASK_CENTER); +} + +static bool on_hfe(pa_channel_position_t p) { + return !!(PA_CHANNEL_POSITION_MASK(p) & PA_CHANNEL_POSITION_MASK_HFE); +} + +static bool on_lfe(pa_channel_position_t p) { + return !!(PA_CHANNEL_POSITION_MASK(p) & PA_CHANNEL_POSITION_MASK_LFE); +} + +static bool on_front(pa_channel_position_t p) { + return !!(PA_CHANNEL_POSITION_MASK(p) & PA_CHANNEL_POSITION_MASK_FRONT); +} + +static bool on_rear(pa_channel_position_t p) { + return !!(PA_CHANNEL_POSITION_MASK(p) & PA_CHANNEL_POSITION_MASK_REAR); +} + +SPA_EXPORT +pa_cvolume *pa_cvolume_remap(pa_cvolume *v, const pa_channel_map *from, const pa_channel_map *to) { + int a, b; + pa_cvolume result; + + pa_assert(v); + pa_assert(from); + pa_assert(to); + + pa_return_val_if_fail(pa_channel_map_valid(to), NULL); + pa_return_val_if_fail(pa_cvolume_compatible_with_channel_map(v, from), NULL); + + if (pa_channel_map_equal(from, to)) + return v; + + result.channels = to->channels; + + for (b = 0; b < to->channels; b++) { + pa_volume_t k = 0; + int n = 0; + + for (a = 0; a < from->channels; a++) + if (from->map[a] == to->map[b]) { + k += v->values[a]; + n ++; + } + + if (n <= 0) { + for (a = 0; a < from->channels; a++) + if ((on_left(from->map[a]) && on_left(to->map[b])) || + (on_right(from->map[a]) && on_right(to->map[b])) || + (on_center(from->map[a]) && on_center(to->map[b])) || + (on_lfe(from->map[a]) && on_lfe(to->map[b]))) { + + k += v->values[a]; + n ++; + } + } + + if (n <= 0) + k = pa_cvolume_avg(v); + else + k /= n; + + result.values[b] = k; + } + + *v = result; + return v; +} + +SPA_EXPORT +int pa_cvolume_compatible(const pa_cvolume *v, const pa_sample_spec *ss) { + + pa_assert(v); + pa_assert(ss); + + pa_return_val_if_fail(pa_cvolume_valid(v), 0); + pa_return_val_if_fail(pa_sample_spec_valid(ss), 0); + + return v->channels == ss->channels; +} + +SPA_EXPORT +int pa_cvolume_compatible_with_channel_map(const pa_cvolume *v, const pa_channel_map *cm) { + pa_assert(v); + pa_assert(cm); + + pa_return_val_if_fail(pa_cvolume_valid(v), 0); + pa_return_val_if_fail(pa_channel_map_valid(cm), 0); + + return v->channels == cm->channels; +} + +/* + * Returns the average volume of l and r, where l and r are two disjoint sets of channels + * (e g left and right, or front and rear). + */ +static void get_avg(const pa_channel_map *map, const pa_cvolume *v, pa_volume_t *l, pa_volume_t *r, + bool (*on_l)(pa_channel_position_t), bool (*on_r)(pa_channel_position_t)) { + int c; + pa_volume_t left = 0, right = 0; + unsigned n_left = 0, n_right = 0; + + pa_assert(v); + pa_assert(map); + pa_assert(map->channels == v->channels); + pa_assert(l); + pa_assert(r); + + for (c = 0; c < map->channels; c++) { + if (on_l(map->map[c])) { + left += v->values[c]; + n_left++; + } else if (on_r(map->map[c])) { + right += v->values[c]; + n_right++; + } + } + + if (n_left <= 0) + *l = PA_VOLUME_NORM; + else + *l = left / n_left; + + if (n_right <= 0) + *r = PA_VOLUME_NORM; + else + *r = right / n_right; +} + +SPA_EXPORT +float pa_cvolume_get_balance(const pa_cvolume *v, const pa_channel_map *map) { + pa_volume_t left, right; + + pa_assert(v); + pa_assert(map); + + pa_return_val_if_fail(pa_cvolume_compatible_with_channel_map(v, map), 0.0f); + + if (!pa_channel_map_can_balance(map)) + return 0.0f; + + get_avg(map, v, &left, &right, on_left, on_right); + + if (left == right) + return 0.0f; + + /* 1.0, 0.0 => -1.0 + 0.0, 1.0 => 1.0 + 0.0, 0.0 => 0.0 + 0.5, 0.5 => 0.0 + 1.0, 0.5 => -0.5 + 1.0, 0.25 => -0.75 + 0.75, 0.25 => -0.66 + 0.5, 0.25 => -0.5 */ + + if (left > right) + return -1.0f + ((float) right / (float) left); + else + return 1.0f - ((float) left / (float) right); +} + +static pa_cvolume* set_balance(pa_cvolume *v, const pa_channel_map *map, float new_balance, + bool (*on_l)(pa_channel_position_t), bool (*on_r)(pa_channel_position_t)) { + + pa_volume_t left, nleft, right, nright, m; + unsigned c; + + get_avg(map, v, &left, &right, on_l, on_r); + + m = PA_MAX(left, right); + + if (new_balance <= 0) { + nright = (new_balance + 1.0f) * m; + nleft = m; + } else { + nleft = (1.0f - new_balance) * m; + nright = m; + } + + for (c = 0; c < map->channels; c++) { + if (on_l(map->map[c])) { + if (left == 0) + v->values[c] = nleft; + else + v->values[c] = (pa_volume_t) PA_CLAMP_VOLUME(((uint64_t) v->values[c] * (uint64_t) nleft) / (uint64_t) left); + } else if (on_r(map->map[c])) { + if (right == 0) + v->values[c] = nright; + else + v->values[c] = (pa_volume_t) PA_CLAMP_VOLUME(((uint64_t) v->values[c] * (uint64_t) nright) / (uint64_t) right); + } + } + + return v; +} + + +SPA_EXPORT +pa_cvolume* pa_cvolume_set_balance(pa_cvolume *v, const pa_channel_map *map, float new_balance) { + pa_assert(map); + pa_assert(v); + + pa_return_val_if_fail(pa_cvolume_compatible_with_channel_map(v, map), NULL); + pa_return_val_if_fail(new_balance >= -1.0f, NULL); + pa_return_val_if_fail(new_balance <= 1.0f, NULL); + + if (!pa_channel_map_can_balance(map)) + return v; + + return set_balance(v, map, new_balance, on_left, on_right); +} + +SPA_EXPORT +pa_cvolume* pa_cvolume_scale(pa_cvolume *v, pa_volume_t max) { + unsigned c; + pa_volume_t t = 0; + + pa_assert(v); + + pa_return_val_if_fail(pa_cvolume_valid(v), NULL); + pa_return_val_if_fail(PA_VOLUME_IS_VALID(max), NULL); + + t = pa_cvolume_max(v); + + if (t <= PA_VOLUME_MUTED) + return pa_cvolume_set(v, v->channels, max); + + for (c = 0; c < v->channels; c++) + v->values[c] = (pa_volume_t) PA_CLAMP_VOLUME(((uint64_t) v->values[c] * (uint64_t) max) / (uint64_t) t); + + return v; +} + +SPA_EXPORT +#if PA_CHECK_VERSION(12, 0, 0) +pa_cvolume* pa_cvolume_scale_mask(pa_cvolume *v, pa_volume_t max, const pa_channel_map *cm, pa_channel_position_mask_t mask) { +#else +pa_cvolume* pa_cvolume_scale_mask(pa_cvolume *v, pa_volume_t max, pa_channel_map *cm, pa_channel_position_mask_t mask) { +#endif + unsigned c; + pa_volume_t t = 0; + + pa_assert(v); + + pa_return_val_if_fail(PA_VOLUME_IS_VALID(max), NULL); + + if (!cm) + return pa_cvolume_scale(v, max); + + pa_return_val_if_fail(pa_cvolume_compatible_with_channel_map(v, cm), NULL); + + t = pa_cvolume_max_mask(v, cm, mask); + + if (t <= PA_VOLUME_MUTED) + return pa_cvolume_set(v, v->channels, max); + + for (c = 0; c < v->channels; c++) + v->values[c] = (pa_volume_t) PA_CLAMP_VOLUME(((uint64_t) v->values[c] * (uint64_t) max) / (uint64_t) t); + + return v; +} + +SPA_EXPORT +float pa_cvolume_get_fade(const pa_cvolume *v, const pa_channel_map *map) { + pa_volume_t rear, front; + + pa_assert(v); + pa_assert(map); + + pa_return_val_if_fail(pa_cvolume_compatible_with_channel_map(v, map), 0.0f); + + if (!pa_channel_map_can_fade(map)) + return 0.0f; + + get_avg(map, v, &rear, &front, on_rear, on_front); + + if (front == rear) + return 0.0f; + + if (rear > front) + return -1.0f + ((float) front / (float) rear); + else + return 1.0f - ((float) rear / (float) front); +} + +SPA_EXPORT +pa_cvolume* pa_cvolume_set_fade(pa_cvolume *v, const pa_channel_map *map, float new_fade) { + pa_assert(map); + pa_assert(v); + + pa_return_val_if_fail(pa_cvolume_compatible_with_channel_map(v, map), NULL); + pa_return_val_if_fail(new_fade >= -1.0f, NULL); + pa_return_val_if_fail(new_fade <= 1.0f, NULL); + + if (!pa_channel_map_can_fade(map)) + return v; + + return set_balance(v, map, new_fade, on_rear, on_front); +} + +SPA_EXPORT +float pa_cvolume_get_lfe_balance(const pa_cvolume *v, const pa_channel_map *map) { + pa_volume_t hfe, lfe; + + pa_assert(v); + pa_assert(map); + + pa_return_val_if_fail(pa_cvolume_compatible_with_channel_map(v, map), 0.0f); + + if (!pa_channel_map_can_lfe_balance(map)) + return 0.0f; + + get_avg(map, v, &hfe, &lfe, on_hfe, on_lfe); + + if (hfe == lfe) + return 0.0f; + + if (hfe > lfe) + return -1.0f + ((float) lfe / (float) hfe); + else + return 1.0f - ((float) hfe / (float) lfe); +} + +SPA_EXPORT +pa_cvolume* pa_cvolume_set_lfe_balance(pa_cvolume *v, const pa_channel_map *map, float new_balance) { + pa_assert(map); + pa_assert(v); + + pa_return_val_if_fail(pa_cvolume_compatible_with_channel_map(v, map), NULL); + pa_return_val_if_fail(new_balance >= -1.0f, NULL); + pa_return_val_if_fail(new_balance <= 1.0f, NULL); + + if (!pa_channel_map_can_lfe_balance(map)) + return v; + + return set_balance(v, map, new_balance, on_hfe, on_lfe); +} + +SPA_EXPORT +pa_cvolume* pa_cvolume_set_position( + pa_cvolume *cv, + const pa_channel_map *map, + pa_channel_position_t t, + pa_volume_t v) { + + unsigned c; + bool good = false; + + pa_assert(cv); + pa_assert(map); + + pa_return_val_if_fail(pa_cvolume_compatible_with_channel_map(cv, map), NULL); + pa_return_val_if_fail(t < PA_CHANNEL_POSITION_MAX, NULL); + pa_return_val_if_fail(PA_VOLUME_IS_VALID(v), NULL); + + for (c = 0; c < map->channels; c++) + if (map->map[c] == t) { + cv->values[c] = v; + good = true; + } + + return good ? cv : NULL; +} + +SPA_EXPORT +pa_volume_t pa_cvolume_get_position( + PA_CONST pa_cvolume *cv, + const pa_channel_map *map, + pa_channel_position_t t) { + + unsigned c; + pa_volume_t v = PA_VOLUME_MUTED; + + pa_assert(cv); + pa_assert(map); + + pa_return_val_if_fail(pa_cvolume_compatible_with_channel_map(cv, map), PA_VOLUME_MUTED); + pa_return_val_if_fail(t < PA_CHANNEL_POSITION_MAX, PA_VOLUME_MUTED); + + for (c = 0; c < map->channels; c++) + if (map->map[c] == t) + if (cv->values[c] > v) + v = cv->values[c]; + + return v; +} + +SPA_EXPORT +pa_cvolume* pa_cvolume_merge(pa_cvolume *dest, const pa_cvolume *a, const pa_cvolume *b) { + unsigned i; + + pa_assert(dest); + pa_assert(a); + pa_assert(b); + + pa_return_val_if_fail(pa_cvolume_valid(a), NULL); + pa_return_val_if_fail(pa_cvolume_valid(b), NULL); + + dest->channels = PA_MIN(a->channels, b->channels); + + for (i = 0; i < dest->channels; i++) + dest->values[i] = PA_MAX(a->values[i], b->values[i]); + + return dest; +} + +SPA_EXPORT +pa_cvolume* pa_cvolume_inc_clamp(pa_cvolume *v, pa_volume_t inc, pa_volume_t limit) { + pa_volume_t m; + + pa_assert(v); + + pa_return_val_if_fail(pa_cvolume_valid(v), NULL); + pa_return_val_if_fail(PA_VOLUME_IS_VALID(inc), NULL); + + m = pa_cvolume_max(v); + + if (m >= limit - inc) + m = limit; + else + m += inc; + + return pa_cvolume_scale(v, m); +} + +SPA_EXPORT +pa_cvolume* pa_cvolume_inc(pa_cvolume *v, pa_volume_t inc) { + return pa_cvolume_inc_clamp(v, inc, PA_VOLUME_MAX); +} + +SPA_EXPORT +pa_cvolume* pa_cvolume_dec(pa_cvolume *v, pa_volume_t dec) { + pa_volume_t m; + + pa_assert(v); + + pa_return_val_if_fail(pa_cvolume_valid(v), NULL); + pa_return_val_if_fail(PA_VOLUME_IS_VALID(dec), NULL); + + m = pa_cvolume_max(v); + + if (m <= PA_VOLUME_MUTED + dec) + m = PA_VOLUME_MUTED; + else + m -= dec; + + return pa_cvolume_scale(v, m); +} diff --git a/pipewire-pulseaudio/src/xmalloc.c b/pipewire-pulseaudio/src/xmalloc.c new file mode 100644 index 000000000..b38af54d0 --- /dev/null +++ b/pipewire-pulseaudio/src/xmalloc.c @@ -0,0 +1,122 @@ +/* PipeWire + * Copyright (C) 2018 Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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. + */ + +#include +#include + +#include + +#include + +#define MAX_ALLOC_SIZE (1024*1024*96) /* 96MB */ + +static void oom(void) { + static const char e[] = "Not enough memory\n"; + write(STDERR_FILENO, e, sizeof(e)-1); +#ifdef SIGQUIT + raise(SIGQUIT); +#endif + _exit(1); +} + +SPA_EXPORT +void* pa_xmalloc(size_t l) +{ + void *p; + spa_assert(l > 0); + spa_assert(l < MAX_ALLOC_SIZE); + + if (!(p = malloc(l))) + oom(); + + return p; +} + +SPA_EXPORT +void *pa_xmalloc0(size_t l) +{ + void *p; + spa_assert(l > 0); + spa_assert(l < MAX_ALLOC_SIZE); + + if (!(p = calloc(1, l))) + oom(); + + return p; +} + +SPA_EXPORT +void *pa_xrealloc(void *ptr, size_t size) +{ + void *p; + spa_assert(size > 0); + spa_assert(size < MAX_ALLOC_SIZE); + + if (!(p = realloc(ptr, size))) + oom(); + return p; +} + +SPA_EXPORT +void pa_xfree(void *p) +{ + int saved_errno; + if (!p) + return; + saved_errno = errno; + free(p); + errno = saved_errno; +} + +SPA_EXPORT +char *pa_xstrdup(const char *s) +{ + if (!s) + return NULL; + return pa_xmemdup(s, strlen(s)+1); +} + +SPA_EXPORT +char *pa_xstrndup(const char *s, size_t l) +{ + char *e, *r; + + if (!s) + return NULL; + + if ((e = memchr(s, 0, l))) + return pa_xmemdup(s, (size_t) (e-s+1)); + + r = pa_xmalloc(l+1); + memcpy(r, s, l); + r[l] = 0; + return r; +} + +SPA_EXPORT +void* pa_xmemdup(const void *p, size_t l) +{ + if (!p) + return NULL; + else { + char *r = pa_xmalloc(l); + memcpy(r, p, l); + return r; + } +}