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;
+ }
+}