diff --git a/pipewire-jack/LICENSE b/pipewire-jack/LICENSE new file mode 100644 index 000000000..19e307187 --- /dev/null +++ b/pipewire-jack/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-jack/README.md b/pipewire-jack/README.md new file mode 100644 index 000000000..928ce3357 --- /dev/null +++ b/pipewire-jack/README.md @@ -0,0 +1,2 @@ +# pipewire-jack +JACK client library for PipeWire diff --git a/pipewire-jack/examples/video-dsp-play.c b/pipewire-jack/examples/video-dsp-play.c new file mode 100644 index 000000000..e503ef709 --- /dev/null +++ b/pipewire-jack/examples/video-dsp-play.c @@ -0,0 +1,206 @@ +/* PipeWire + * + * Copyright © 2019 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include + +#include + +#include + +#define WIDTH 320 +#define HEIGHT 240 +#define BPP 16 + +#define MAX_BUFFERS 64 + +#define JACK_DEFAULT_VIDEO_TYPE "32 bit float RGBA video" + +#define CLAMP(v,low,high) \ +({ \ + __typeof__(v) _v = (v); \ + __typeof__(low) _low = (low); \ + __typeof__(high) _high = (high); \ + (_v < _low) ? _low : (_v > _high) ? _high : _v; \ +}) + +struct pixel { + float r, g, b, a; +}; + +struct data { + const char *path; + + SDL_Renderer *renderer; + SDL_Window *window; + SDL_Texture *texture; + SDL_Texture *cursor; + + jack_client_t *client; + const char *client_name; + jack_port_t *in_port; + + uint32_t width; + uint32_t height; + int32_t stride; + + int counter; + SDL_Rect rect; + SDL_Rect cursor_rect; +}; + +static void handle_events(struct data *data) +{ + SDL_Event event; + while (SDL_PollEvent(&event)) { + switch (event.type) { + case SDL_QUIT: + exit(0); + break; + } + } +} + +static int +process (jack_nframes_t nframes, void *arg) +{ + struct data *data = (struct data*)arg; + void *sdata, *ddata; + int sstride, dstride; + uint32_t i, j; + uint8_t *src, *dst; + + sdata = jack_port_get_buffer (data->in_port, nframes); + + handle_events(data); + + if (SDL_LockTexture(data->texture, NULL, &ddata, &dstride) < 0) { + fprintf(stderr, "Couldn't lock texture: %s\n", SDL_GetError()); + goto done; + } + + /* copy video image in texture */ + sstride = data->stride; + + src = sdata; + dst = ddata; + + for (i = 0; i < data->height; i++) { + struct pixel *p = (struct pixel *) src; + for (j = 0; j < data->width; j++) { + dst[j * 4 + 0] = CLAMP(lrintf(p[j].r * 255.0f), 0, 255); + dst[j * 4 + 1] = CLAMP(lrintf(p[j].g * 255.0f), 0, 255); + dst[j * 4 + 2] = CLAMP(lrintf(p[j].b * 255.0f), 0, 255); + dst[j * 4 + 3] = CLAMP(lrintf(p[j].a * 255.0f), 0, 255); + } + src += sstride; + dst += dstride; + } + SDL_UnlockTexture(data->texture); + + SDL_RenderClear(data->renderer); + SDL_RenderCopy(data->renderer, data->texture, &data->rect, NULL); + SDL_RenderPresent(data->renderer); + + done: + return 0; +} + +int main(int argc, char *argv[]) +{ + struct data data = { 0, }; + jack_options_t options = JackNullOption; + jack_status_t status; + + data.client = jack_client_open ("video-dsp-play", options, &status); + if (data.client == NULL) { + fprintf (stderr, "jack_client_open() failed, " + "status = 0x%2.0x\n", status); + if (status & JackServerFailed) { + fprintf (stderr, "Unable to connect to JACK server\n"); + } + exit (1); + } + if (status & JackServerStarted) { + fprintf (stderr, "JACK server started\n"); + } + if (status & JackNameNotUnique) { + data.client_name = jack_get_client_name(data.client); + fprintf (stderr, "unique name `%s' assigned\n", data.client_name); + } + + jack_set_process_callback (data.client, process, &data); + + data.width = WIDTH; + data.height = HEIGHT; + data.stride = data.width * 4 * sizeof(float); + + if (SDL_Init(SDL_INIT_VIDEO) < 0) { + fprintf(stderr, "can't initialize SDL: %s\n", SDL_GetError()); + return -1; + } + + if (SDL_CreateWindowAndRenderer + (WIDTH, HEIGHT, SDL_WINDOW_RESIZABLE, &data.window, &data.renderer)) { + fprintf(stderr, "can't create window: %s\n", SDL_GetError()); + return -1; + } + + data.texture = SDL_CreateTexture(data.renderer, + SDL_PIXELFORMAT_RGBA32, + SDL_TEXTUREACCESS_STREAMING, + data.width, + data.height); + data.rect.x = 0; + data.rect.y = 0; + data.rect.w = data.width; + data.rect.h = data.height; + + data.in_port = jack_port_register (data.client, "input", + JACK_DEFAULT_VIDEO_TYPE, + JackPortIsInput, 0); + + if (data.in_port == NULL) { + fprintf(stderr, "no more JACK ports available\n"); + exit (1); + } + + if (jack_activate (data.client)) { + fprintf (stderr, "cannot activate client"); + exit (1); + } + + while (1) { + sleep (1); + } + + jack_client_close (data.client); + + SDL_DestroyTexture(data.texture); + SDL_DestroyRenderer(data.renderer); + SDL_DestroyWindow(data.window); + + return 0; +} diff --git a/pipewire-jack/src/meson.build b/pipewire-jack/src/meson.build new file mode 100644 index 000000000..7287ab482 --- /dev/null +++ b/pipewire-jack/src/meson.build @@ -0,0 +1,33 @@ +pipewire_jack_sources = [ + 'pipewire-jack.c', + 'metadata.c', + 'ringbuffer.c', + 'uuid.c', +] + +pipewire_jack_c_args = [ + '-DHAVE_CONFIG_H', + '-D_GNU_SOURCE', + '-DPIC', +] + +#optional dependencies +jack_dep = dependency('jack', version : '>= 1.9.10', required : false) + +pipewire_jack = shared_library('jack', + pipewire_jack_sources, + soversion : 0, + c_args : pipewire_jack_c_args, + include_directories : [configinc], + dependencies : [pipewire_dep, jack_dep, mathlib], + install : false, +) + +if sdl_dep.found() + executable('video-dsp-play', + '../examples/video-dsp-play.c', + c_args : [ '-D_GNU_SOURCE' ], + install: false, + dependencies : [jack_dep, sdl_dep, mathlib], + ) +endif diff --git a/pipewire-jack/src/metadata.c b/pipewire-jack/src/metadata.c new file mode 100644 index 000000000..8ec3b212b --- /dev/null +++ b/pipewire-jack/src/metadata.c @@ -0,0 +1,173 @@ +/* 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 +#include +#include + +#include +#include + +#include + + +static struct pw_properties * get_properties(void) +{ + static struct pw_properties *properties = NULL; + if (properties == NULL) { + properties = pw_properties_new(NULL, NULL); + } + return properties; +} + +static void make_key(char *dst, jack_uuid_t subject, const char *key, int keylen) +{ + int len; + jack_uuid_unparse (subject, dst); + len = strlen(dst); + dst[len] = '@'; + memcpy(&dst[len+1], key, keylen+1); +} + +SPA_EXPORT +int jack_set_property(jack_client_t*client, + jack_uuid_t subject, + const char* key, + const char* value, + const char* type) +{ + int keylen = strlen(key); + char *dst = alloca(JACK_UUID_STRING_SIZE + keylen); + struct pw_properties * props = get_properties(); + + make_key(dst, subject, key, keylen); + + pw_properties_setf(props, dst, "%s@%s", value, type); + pw_log_debug("set '%s' to '%s@%s'", dst, value, type); + + return 0; +} + +SPA_EXPORT +int jack_get_property(jack_uuid_t subject, + const char* key, + char** value, + char** type) +{ + int keylen = strlen(key); + char *dst = alloca(JACK_UUID_STRING_SIZE + keylen); + struct pw_properties * props = get_properties(); + const char *str, *at; + + make_key(dst, subject, key, keylen); + + if ((str = pw_properties_get(props, dst)) == NULL) { + pw_log_warn("no property '%s'", dst); + return -1; + } + + at = strrchr(str, '@'); + if (at == NULL) { + pw_log_warn("property '%s' invalid value '%s'", dst, str); + return -1; + } + + *value = strndup(str, at - str); + *type = strdup(at + 1); + + pw_log_debug("got '%s' with value:'%s' type:'%s'", dst, *value, *type); + + return 0; +} + +SPA_EXPORT +void jack_free_description (jack_description_t* desc, int free_description_itself) +{ + pw_log_warn("not implemented"); +} + +SPA_EXPORT +int jack_get_properties (jack_uuid_t subject, + jack_description_t* desc) +{ + pw_log_warn("not implemented"); + return -1; +} + +SPA_EXPORT +int jack_get_all_properties (jack_description_t** descs) +{ + pw_log_warn("not implemented"); + return -1; +} + +SPA_EXPORT +int jack_remove_property (jack_client_t* client, jack_uuid_t subject, const char* key) +{ + int keylen = strlen(key); + char *dst = alloca(JACK_UUID_STRING_SIZE + keylen); + struct pw_properties * props = get_properties(); + + make_key(dst, subject, key, keylen); + + pw_properties_set(props, dst, NULL); + pw_log_debug("removed %s", dst); + + return 0; +} + +SPA_EXPORT +int jack_remove_properties (jack_client_t* client, jack_uuid_t subject) +{ + pw_log_warn("not implemented"); + return -1; +} + +SPA_EXPORT +int jack_remove_all_properties (jack_client_t* client) +{ + pw_log_warn("not implemented"); + return -1; +} + +SPA_EXPORT +int jack_set_property_change_callback (jack_client_t* client, + JackPropertyChangeCallback callback, + void* arg) +{ + pw_log_warn("not implemented"); + return -1; +} + +SPA_EXPORT +const char* JACK_METADATA_PRETTY_NAME = "http://jackaudio.org/metadata/pretty-name"; +SPA_EXPORT +const char* JACK_METADATA_HARDWARE = "http://jackaudio.org/metadata/hardware"; +SPA_EXPORT +const char* JACK_METADATA_CONNECTED = "http://jackaudio.org/metadata/connected"; +SPA_EXPORT +const char* JACK_METADATA_PORT_GROUP = "http://jackaudio.org/metadata/port-group"; +SPA_EXPORT +const char* JACK_METADATA_ICON_SMALL = "http://jackaudio.org/metadata/icon-small"; +SPA_EXPORT +const char* JACK_METADATA_ICON_LARGE = "http://jackaudio.org/metadata/icon-large"; diff --git a/pipewire-jack/src/pipewire-jack.c b/pipewire-jack/src/pipewire-jack.c new file mode 100644 index 000000000..736f65725 --- /dev/null +++ b/pipewire-jack/src/pipewire-jack.c @@ -0,0 +1,4275 @@ +/* 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 +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "extensions/client-node.h" + +#define JACK_DEFAULT_VIDEO_TYPE "32 bit float RGBA video" + +#define JACK_CLIENT_NAME_SIZE 64 +#define JACK_PORT_NAME_SIZE 256 +#define JACK_PORT_MAX 4096 +#define JACK_PORT_TYPE_SIZE 32 +#define CONNECTION_NUM_FOR_PORT 1024 + +#define MAX_BUFFER_FRAMES 8192 + +#define MAX_ALIGN 16 +#define MAX_OBJECTS 8192 +#define MAX_PORTS 1024 +#define MAX_BUFFERS 2 +#define MAX_BUFFER_DATAS 4u +#define MAX_BUFFER_MEMS 4 +#define MAX_MIX 4096 +#define MAX_IO 32 + +#define DEFAULT_SAMPLE_RATE 48000 +#define DEFAULT_BUFFER_FRAMES 1024 +#define DEFAULT_LATENCY SPA_STRINGIFY(DEFAULT_BUFFER_FRAMES/DEFAULT_SAMPLE_RATE) + +#define REAL_JACK_PORT_NAME_SIZE (JACK_CLIENT_NAME_SIZE + JACK_PORT_NAME_SIZE) + +#define NAME "jack-client" + +struct client; +struct port; + +struct globals { + jack_thread_creator_t creator; +}; + +static struct globals globals; + +#define OBJECT_CHUNK 8 + +typedef void (*mix2_func) (float *dst, float *src1, float *src2, int n_samples); + +static mix2_func mix2; + +struct object { + struct spa_list link; + + struct client *client; + + uint32_t type; + uint32_t id; + + union { + struct { + char name[JACK_CLIENT_NAME_SIZE+1]; + int32_t priority; + } node; + struct { + uint32_t src; + uint32_t dst; + } port_link; + struct { + unsigned long flags; + char name[REAL_JACK_PORT_NAME_SIZE+1]; + char alias1[REAL_JACK_PORT_NAME_SIZE+1]; + char alias2[REAL_JACK_PORT_NAME_SIZE+1]; + uint32_t type_id; + uint32_t node_id; + uint32_t port_id; + uint32_t monitor_requests; + jack_latency_range_t capture_latency; + jack_latency_range_t playback_latency; + int32_t priority; + } port; + }; +}; + +struct midi_buffer { +#define MIDI_BUFFER_MAGIC 0x900df00d + uint32_t magic; + int32_t buffer_size; + uint32_t nframes; + int32_t write_pos; + uint32_t event_count; + uint32_t lost_events; +}; + +#define MIDI_INLINE_MAX 4 + +struct midi_event { + uint16_t time; + uint16_t size; + union { + uint32_t byte_offset; + uint8_t inline_data[MIDI_INLINE_MAX]; + }; +}; + +struct buffer { + struct spa_list link; +#define BUFFER_FLAG_OUT (1<<0) +#define BUFFER_FLAG_MAPPED (1<<1) + uint32_t flags; + uint32_t id; + + struct spa_data datas[MAX_BUFFER_DATAS]; + uint32_t n_datas; + + struct pw_memmap *mem[MAX_BUFFER_DATAS+1]; + uint32_t n_mem; +}; + +struct link { + uint32_t node_id; + struct pw_memmap *mem; + struct pw_node_activation *activation; + int signalfd; +}; + +struct mix { + struct spa_list link; + struct spa_list port_link; + uint32_t id; + struct port *port; + + struct spa_io_buffers *io; + + struct buffer buffers[MAX_BUFFERS]; + uint32_t n_buffers; + struct spa_list queue; +}; + +struct port { + bool valid; + struct spa_list link; + + struct client *client; + + enum spa_direction direction; + uint32_t id; + struct object *object; + + struct spa_io_buffers io; + struct spa_list mix; + + bool have_format; + uint32_t rate; + + bool zeroed; + float *emptyptr; + float empty[MAX_BUFFER_FRAMES + MAX_ALIGN]; +}; + +struct context { + struct pw_main_loop *main; + struct pw_thread_loop *loop; + struct pw_core *core; + + struct pw_map globals; + struct spa_list free_objects; + struct spa_list ports; + struct spa_list nodes; + struct spa_list links; +}; + +#define GET_DIRECTION(f) ((f) & JackPortIsInput ? SPA_DIRECTION_INPUT : SPA_DIRECTION_OUTPUT) + +#define GET_IN_PORT(c,p) (&c->port_pool[SPA_DIRECTION_INPUT][p]) +#define GET_OUT_PORT(c,p) (&c->port_pool[SPA_DIRECTION_OUTPUT][p]) +#define GET_PORT(c,d,p) (d == SPA_DIRECTION_INPUT ? GET_IN_PORT(c,p) : GET_OUT_PORT(c,p)) + +struct client { + char name[JACK_CLIENT_NAME_SIZE+1]; + + struct context context; + + struct pw_data_loop *loop; + + struct pw_remote *remote; + struct spa_hook remote_listener; + + struct pw_core_proxy *core_proxy; + struct spa_hook core_listener; + int last_sync; + bool error; + + struct pw_registry_proxy *registry_proxy; + struct spa_hook registry_listener; + + struct pw_client_node_proxy *node_proxy; + struct spa_hook node_listener; + struct spa_hook proxy_listener; + + uint32_t node_id; + struct spa_source *socket_source; + + JackThreadCallback thread_callback; + void *thread_arg; + JackThreadInitCallback thread_init_callback; + void *thread_init_arg; + JackShutdownCallback shutdown_callback; + void *shutdown_arg; + JackInfoShutdownCallback info_shutdown_callback; + void *info_shutdown_arg; + JackProcessCallback process_callback; + void *process_arg; + JackFreewheelCallback freewheel_callback; + void *freewheel_arg; + JackBufferSizeCallback bufsize_callback; + void *bufsize_arg; + JackSampleRateCallback srate_callback; + void *srate_arg; + JackClientRegistrationCallback registration_callback; + void *registration_arg; + JackPortRegistrationCallback portregistration_callback; + void *portregistration_arg; + JackPortConnectCallback connect_callback; + void *connect_arg; + JackPortRenameCallback rename_callback; + void *rename_arg; + JackGraphOrderCallback graph_callback; + void *graph_arg; + JackXRunCallback xrun_callback; + void *xrun_arg; + JackLatencyCallback latency_callback; + void *latency_arg; + JackSyncCallback sync_callback; + void *sync_arg; + JackTimebaseCallback timebase_callback; + void *timebase_arg; + + struct spa_io_position *position; + uint32_t sample_rate; + uint32_t buffer_frames; + + struct mix mix_pool[MAX_MIX]; + struct spa_list free_mix; + + struct port port_pool[2][MAX_PORTS]; + struct spa_list ports[2]; + struct spa_list free_ports[2]; + + struct pw_array links; + uint32_t driver_id; + struct pw_node_activation *driver_activation; + + struct pw_memmap *mem; + struct pw_node_activation *activation; + uint32_t xrun_count; + + unsigned int started:1; + unsigned int active:1; + unsigned int destroyed:1; + unsigned int first:1; + unsigned int thread_entered:1; + + jack_position_t jack_position; + jack_transport_state_t jack_state; +}; + +static void init_port_pool(struct client *c, enum spa_direction direction) +{ + int i; + + spa_list_init(&c->ports[direction]); + spa_list_init(&c->free_ports[direction]); + for (i = 0; i < MAX_PORTS; i++) { + c->port_pool[direction][i].direction = direction; + c->port_pool[direction][i].id = i; + c->port_pool[direction][i].emptyptr = + SPA_PTR_ALIGN(c->port_pool[direction][i].empty, MAX_ALIGN, float); + spa_list_append(&c->free_ports[direction], &c->port_pool[direction][i].link); + } +} + +static struct object * alloc_object(struct client *c) +{ + struct object *o; + int i; + + if (spa_list_is_empty(&c->context.free_objects)) { + o = calloc(OBJECT_CHUNK, sizeof(struct object)); + if (o == NULL) + return NULL; + for (i = 0; i < OBJECT_CHUNK; i++) + spa_list_append(&c->context.free_objects, &o[i].link); + } + + o = spa_list_first(&c->context.free_objects, struct object, link); + spa_list_remove(&o->link); + o->client = c; + + return o; +} + +static void free_object(struct client *c, struct object *o) +{ + spa_list_remove(&o->link); + spa_list_append(&c->context.free_objects, &o->link); +} + +static struct mix *find_mix(struct client *c, struct port *port, uint32_t mix_id) +{ + struct mix *mix; + + spa_list_for_each(mix, &port->mix, port_link) { + if (mix->id == mix_id) + return mix; + } + return NULL; +} + +static struct mix *ensure_mix(struct client *c, struct port *port, uint32_t mix_id) +{ + struct mix *mix; + + if ((mix = find_mix(c, port, mix_id)) != NULL) + return mix; + + if (spa_list_is_empty(&c->free_mix)) + return NULL; + + mix = spa_list_first(&c->free_mix, struct mix, link); + spa_list_remove(&mix->link); + + spa_list_append(&port->mix, &mix->port_link); + + mix->id = mix_id; + mix->port = port; + mix->io = NULL; + mix->n_buffers = 0; + + return mix; +} + +static void free_mix(struct client *c, struct mix *mix) +{ + spa_list_remove(&mix->link); + spa_list_remove(&mix->port_link); + spa_list_append(&c->free_mix, &mix->link); +} + +static struct port * alloc_port(struct client *c, enum spa_direction direction) +{ + struct port *p; + struct object *o; + + if (spa_list_is_empty(&c->free_ports[direction])) + return NULL; + + p = spa_list_first(&c->free_ports[direction], struct port, link); + spa_list_remove(&p->link); + + o = alloc_object(c); + o->type = PW_TYPE_INTERFACE_Port; + o->id = SPA_ID_INVALID; + o->port.node_id = c->node_id; + o->port.port_id = p->id; + spa_list_append(&c->context.ports, &o->link); + + p->valid = true; + p->zeroed = false; + p->client = c; + p->object = o; + spa_list_init(&p->mix); + + spa_list_append(&c->ports[direction], &p->link); + + return p; +} + +static void free_port(struct client *c, struct port *p) +{ + struct mix *m, *t; + + if (!p->valid) + return; + + spa_list_for_each_safe(m, t, &p->mix, port_link) + free_mix(c, m); + + spa_list_remove(&p->link); + p->valid = false; + free_object(c, p->object); + spa_list_append(&c->free_ports[p->direction], &p->link); +} + +static struct object *find_port(struct client *c, const char *name) +{ + struct object *o; + + spa_list_for_each(o, &c->context.ports, link) { + if (!strcmp(o->port.name, name)) + return o; + } + return NULL; +} + +static struct object *find_link(struct client *c, uint32_t src, uint32_t dst) +{ + struct object *l; + + spa_list_for_each(l, &c->context.links, link) { + if (l->port_link.src == src && + l->port_link.dst == dst) { + return l; + } + } + return NULL; +} + +static struct buffer *dequeue_buffer(struct mix *mix) +{ + struct buffer *b; + + if (spa_list_is_empty(&mix->queue)) + return NULL; + + b = spa_list_first(&mix->queue, struct buffer, link); + spa_list_remove(&b->link); + SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUT); + + return b; +} + +#if defined (__SSE__) +#include +static void mix2_sse(float *dst, float *src1, float *src2, int n_samples) +{ + int n, unrolled; + __m128 in[2]; + + if (SPA_IS_ALIGNED(src1, 16) && + SPA_IS_ALIGNED(src2, 16) && + SPA_IS_ALIGNED(dst, 16)) + unrolled = n_samples / 4; + else + unrolled = 0; + + for (n = 0; unrolled--; n += 4) { + in[0] = _mm_load_ps(&src1[n]), + in[1] = _mm_load_ps(&src2[n]), + in[0] = _mm_add_ps(in[0], in[1]); + _mm_store_ps(&dst[n], in[0]); + } + for (; n < n_samples; n++) { + in[0] = _mm_load_ss(&src1[n]), + in[1] = _mm_load_ss(&src2[n]), + in[0] = _mm_add_ss(in[0], in[1]); + _mm_store_ss(&dst[n], in[0]); + } +} +#endif + +static void mix2_c(float *dst, float *src1, float *src2, int n_samples) +{ + int i; + for (i = 0; i < n_samples; i++) + dst[i] = src1[i] + src2[i]; +} + +SPA_EXPORT +void jack_get_version(int *major_ptr, int *minor_ptr, int *micro_ptr, int *proto_ptr) +{ + *major_ptr = 0; + *minor_ptr = 0; + *micro_ptr = 0; + *proto_ptr = 0; +} + +SPA_EXPORT +const char * +jack_get_version_string(void) +{ + return "0.0.0.0"; +} + +static void on_state_changed(void *data, enum pw_remote_state old, + enum pw_remote_state state, const char *error) +{ + struct client *client = data; + + pw_log_debug(NAME" %p: state %s", client, pw_remote_state_as_string(state)); + switch (state) { + case PW_REMOTE_STATE_ERROR: + client->error = true; + /* fallthrough*/ + case PW_REMOTE_STATE_UNCONNECTED: + /* don't call shutdown when we do client_close, only + * on unexpected errors */ + if (client->shutdown_callback && !client->destroyed) + client->shutdown_callback(client->shutdown_arg); + /* fallthrough*/ + case PW_REMOTE_STATE_CONNECTED: + pw_thread_loop_signal(client->context.loop, false); + break; + default: + break; + } +} + +static const struct pw_remote_events remote_events = { + PW_VERSION_REMOTE_EVENTS, + .state_changed = on_state_changed, +}; + +static void on_sync_reply(void *data, uint32_t id, int seq) +{ + struct client *client = data; + if (id != 0) + return; + client->last_sync = seq; + pw_thread_loop_signal(client->context.loop, false); +} + +static const struct pw_core_proxy_events core_events = { + PW_VERSION_CORE_EVENTS, + .done = on_sync_reply, +}; + +static int do_sync(struct client *client) +{ + int seq; + + seq = pw_proxy_sync((struct pw_proxy*)client->core_proxy, client->last_sync); + + while (true) { + pw_thread_loop_wait(client->context.loop); + + if (client->error) + return -1; + + if (client->last_sync == seq) + break; + } + return 0; +} + +static void on_node_proxy_destroy(void *data) +{ + struct client *client = data; + + client->node_proxy = NULL; + spa_hook_remove(&client->proxy_listener); + +} + +static const struct pw_proxy_events proxy_events = { + PW_VERSION_PROXY_EVENTS, + .destroy = on_node_proxy_destroy, +}; + +static struct link *find_activation(struct pw_array *links, uint32_t node_id) +{ + struct link *l; + + pw_array_for_each(l, links) { + if (l->node_id == node_id) + return l; + } + return NULL; +} + +static int +do_remove_sources(struct spa_loop *loop, + bool async, uint32_t seq, const void *data, size_t size, void *user_data) +{ + struct client *c = user_data; + + if (c->socket_source) { + pw_loop_destroy_source(c->loop->loop, c->socket_source); + c->socket_source = NULL; + } + return 0; +} + +static void unhandle_socket(struct client *c) +{ + pw_loop_invoke(c->loop->loop, + do_remove_sources, 1, NULL, 0, true, c); +} + +static void reuse_buffer(struct client *c, struct mix *mix, uint32_t id) +{ + struct buffer *b; + + b = &mix->buffers[id]; + + if (SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_OUT)) { + pw_log_trace(NAME" %p: port %p: recycle buffer %d", c, mix->port, id); + spa_list_append(&mix->queue, &b->link); + SPA_FLAG_CLEAR(b->flags, BUFFER_FLAG_OUT); + } +} + + +static void convert_from_midi(void *midi, void *buffer, size_t size) +{ + struct spa_pod_builder b = { 0, }; + uint32_t i, count; + struct spa_pod_frame f; + + count = jack_midi_get_event_count(midi); + + spa_pod_builder_init(&b, buffer, size); + spa_pod_builder_push_sequence(&b, &f, 0); + + for (i = 0; i < count; i++) { + jack_midi_event_t ev; + jack_midi_event_get(&ev, midi, i); + spa_pod_builder_control(&b, ev.time, SPA_CONTROL_Midi); + spa_pod_builder_bytes(&b, ev.buffer, ev.size); + } + spa_pod_builder_pop(&b, &f); +} + +static void convert_to_midi(struct spa_pod_sequence **seq, uint32_t n_seq, void *midi) +{ + struct spa_pod_control *c[n_seq]; + uint32_t i; + + for (i = 0; i < n_seq; i++) { + c[i] = spa_pod_control_first(&seq[i]->body); + } + + while (true) { + struct spa_pod_control *next = NULL; + uint32_t next_index = 0; + + for (i = 0; i < n_seq; i++) { + if (!spa_pod_control_is_inside(&seq[i]->body, + SPA_POD_BODY_SIZE(seq[i]), c[i])) + continue; + + if (next == NULL || c[i]->offset < next->offset) { + next = c[i]; + next_index = i; + } + } + if (next == NULL) + break; + + switch(next->type) { + case SPA_CONTROL_Midi: + jack_midi_event_write(midi, + next->offset, + SPA_POD_BODY(&next->value), + SPA_POD_BODY_SIZE(&next->value)); + break; + } + c[next_index] = spa_pod_control_next(c[next_index]); + } +} + + +static void *get_buffer_output(struct client *c, struct port *p, uint32_t frames, uint32_t stride) +{ + struct mix *mix; + void *ptr = NULL; + + p->io.status = -EPIPE; + p->io.buffer_id = SPA_ID_INVALID; + + if ((mix = find_mix(c, p, -1)) != NULL) { + struct buffer *b; + + if (mix->n_buffers == 0) + goto done; + + pw_log_trace(NAME" %p: port %p %d get buffer %d n_buffers:%d", + c, p, p->id, frames, mix->n_buffers); + + if ((b = dequeue_buffer(mix)) == NULL) { + pw_log_warn("port %p: out of buffers", p); + goto done; + } + reuse_buffer(c, mix, b->id); + ptr = b->datas[0].data; + + b->datas[0].chunk->offset = 0; + b->datas[0].chunk->size = frames * sizeof(float); + b->datas[0].chunk->stride = stride; + + p->io.status = SPA_STATUS_HAVE_DATA; + p->io.buffer_id = b->id; + } +done: + spa_list_for_each(mix, &p->mix, port_link) { + struct spa_io_buffers *mio = mix->io; + if (mio == NULL) + continue; + pw_log_trace(NAME" %p: port %p tee %d.%d get buffer %d io:%p", + c, p, p->id, mix->id, frames, mio); + *mio = p->io; + } + return ptr; +} + +static void process_tee(struct client *c) +{ + struct port *p; + + spa_list_for_each(p, &c->ports[SPA_DIRECTION_OUTPUT], link) { + if (p->object->port.type_id != 1) + continue; + void *ptr = get_buffer_output(c, p, MAX_BUFFER_FRAMES, 1); + if (ptr != NULL) + convert_from_midi(p->emptyptr, ptr, MAX_BUFFER_FRAMES * sizeof(float)); + } +} + +static inline void debug_position(struct client *c, jack_position_t *p) +{ + pw_log_trace("usecs: %lu", p->usecs); + pw_log_trace("frame_rate: %u", p->frame_rate); + pw_log_trace("frame: %u", p->frame); + pw_log_trace("valid: %08x", p->valid); + + if (p->valid & JackPositionBBT) { + pw_log_trace("BBT"); + pw_log_trace(" bar: %u", p->bar); + pw_log_trace(" beat: %u", p->beat); + pw_log_trace(" tick: %u", p->tick); + pw_log_trace(" bar_start_tick: %f", p->bar_start_tick); + pw_log_trace(" beats_per_bar: %f", p->beats_per_bar); + pw_log_trace(" beat_type: %f", p->beat_type); + pw_log_trace(" ticks_per_beat: %f", p->ticks_per_beat); + pw_log_trace(" beats_per_minute: %f", p->beats_per_minute); + } + if (p->valid & JackPositionTimecode) { + pw_log_trace("Timecode:"); + pw_log_trace(" frame_time: %f", p->frame_time); + pw_log_trace(" next_time: %f", p->next_time); + } + if (p->valid & JackBBTFrameOffset) { + pw_log_trace("BBTFrameOffset:"); + pw_log_trace(" bbt_offset: %u", p->bbt_offset); + } + if (p->valid & JackAudioVideoRatio) { + pw_log_trace("AudioVideoRatio:"); + pw_log_trace(" audio_frames_per_video_frame: %f", p->audio_frames_per_video_frame); + } + if (p->valid & JackVideoFrameOffset) { + pw_log_trace("JackVideoFrameOffset:"); + pw_log_trace(" video_offset: %u", p->video_offset); + } +} + +static inline void jack_to_position(jack_position_t *s, struct pw_node_activation *a) +{ + struct spa_io_segment *d = &a->segment; + + if (s->valid & JackPositionBBT) { + d->bar.flags = SPA_IO_SEGMENT_BAR_FLAG_VALID; + if (s->valid & JackBBTFrameOffset) + d->bar.offset = s->bbt_offset; + else + d->bar.offset = 0; + d->bar.signature_num = s->beats_per_bar; + d->bar.signature_denom = s->beat_type; + d->bar.bpm = s->beats_per_minute; + d->bar.beat = (s->bar - 1) * s->beats_per_bar + (s->beat - 1) + + (s->tick / s->ticks_per_beat); + } +} + +static inline jack_transport_state_t position_to_jack(struct pw_node_activation *a, jack_position_t *d) +{ + struct spa_io_position *s = &a->position; + jack_transport_state_t state; + struct spa_io_segment *seg = &s->segments[0]; + uint64_t running; + + switch (s->state) { + default: + case SPA_IO_POSITION_STATE_STOPPED: + state = JackTransportStopped; + break; + case SPA_IO_POSITION_STATE_STARTING: + state = JackTransportStarting; + break; + case SPA_IO_POSITION_STATE_RUNNING: + if (seg->flags & SPA_IO_SEGMENT_FLAG_LOOPING) + state = JackTransportLooping; + else + state = JackTransportRolling; + break; + } + if (d == NULL) + return state; + + + d->unique_1++; + d->usecs = s->clock.nsec / SPA_NSEC_PER_USEC; + d->frame_rate = s->clock.rate.denom; + + running = s->clock.position - s->offset; + + if (running >= seg->start && + (seg->duration == 0 || running < seg->start + seg->duration)) + d->frame = (running - seg->start) * seg->rate + seg->position; + else + d->frame = seg->position; + + d->valid = 0; + if (a->segment_owner[0] && SPA_FLAG_IS_SET(seg->bar.flags, SPA_IO_SEGMENT_BAR_FLAG_VALID)) { + double abs_beat; + long beats; + + d->valid |= JackPositionBBT; + + d->bbt_offset = seg->bar.offset; + if (seg->bar.offset) + d->valid |= JackBBTFrameOffset; + + d->beats_per_bar = seg->bar.signature_num; + d->beat_type = seg->bar.signature_denom; + d->ticks_per_beat = 1920.0f; + d->beats_per_minute = seg->bar.bpm; + + abs_beat = seg->bar.beat; + + d->bar = abs_beat / d->beats_per_bar; + beats = d->bar * d->beats_per_bar; + d->bar_start_tick = beats * d->ticks_per_beat; + d->beat = abs_beat - beats; + beats += d->beat; + d->tick = (abs_beat - beats) * d->ticks_per_beat; + d->bar++; + d->beat++; + } + d->unique_2 = d->unique_1; + return state; +} + +static inline uint32_t cycle_run(struct client *c) +{ + uint64_t cmd, nsec; + int fd = c->socket_source->fd; + uint32_t buffer_frames, sample_rate; + struct spa_io_position *pos = c->position; + struct pw_node_activation *activation = c->activation; + struct pw_node_activation *driver = c->driver_activation; + + /* this is blocking if nothing ready */ + if (read(fd, &cmd, sizeof(cmd)) != sizeof(cmd)) { + pw_log_warn(NAME" %p: read failed %m", c); + if (errno == EWOULDBLOCK) + return 0; + } + if (cmd > 1) + pw_log_warn(NAME" %p: missed %"PRIu64" wakeups", c, cmd - 1); + + if (pos == NULL) { + pw_log_error(NAME" %p: missing position", c); + return 0; + } + + nsec = pos->clock.nsec; + activation->status = PW_NODE_ACTIVATION_AWAKE; + activation->awake_time = nsec; + if (c->first) { + if (c->thread_init_callback) + c->thread_init_callback(c->thread_init_arg); + c->first = false; + } + + buffer_frames = pos->clock.duration; + if (buffer_frames != c->buffer_frames) { + pw_log_info(NAME" %p: bufferframes %d", c, buffer_frames); + c->buffer_frames = buffer_frames; + if (c->bufsize_callback) + c->bufsize_callback(c->buffer_frames, c->bufsize_arg); + } + + sample_rate = pos->clock.rate.denom; + if (sample_rate != c->sample_rate) { + pw_log_info(NAME" %p: sample_rate %d", c, sample_rate); + c->sample_rate = sample_rate; + if (c->srate_callback) + c->srate_callback(c->sample_rate, c->srate_arg); + } + + c->jack_state = position_to_jack(driver, &c->jack_position); + + if (driver) { + if (activation->pending_sync) { + if (c->sync_callback == NULL || + c->sync_callback(c->jack_state, &c->jack_position, c->sync_arg)) + activation->pending_sync = false; + } + if (c->xrun_count != driver->xrun_count && + c->xrun_count != 0 && c->xrun_callback) + c->xrun_callback(c->xrun_arg); + c->xrun_count = driver->xrun_count; + } + pw_log_trace(NAME" %p: wait %"PRIu64" frames:%d rate:%d pos:%d delay:%"PRIi64" corr:%f", c, + activation->awake_time, c->buffer_frames, c->sample_rate, + c->jack_position.frame, pos->clock.delay, pos->clock.rate_diff); + + return buffer_frames; +} + +static inline uint32_t cycle_wait(struct client *c) +{ + int res; + + res = pw_data_loop_wait(c->loop, -1); + if (res <= 0) { + pw_log_warn(NAME" %p: wait error %m", c); + return 0; + } + return cycle_run(c); +} + +static inline void signal_sync(struct client *c) +{ + struct timespec ts; + uint64_t cmd, nsec; + struct link *l; + struct pw_node_activation *activation = c->activation; + + process_tee(c); + + clock_gettime(CLOCK_MONOTONIC, &ts); + nsec = SPA_TIMESPEC_TO_NSEC(&ts); + activation->status = PW_NODE_ACTIVATION_FINISHED; + activation->finish_time = nsec; + + cmd = 1; + pw_array_for_each(l, &c->links) { + struct pw_node_activation_state *state; + + if (l->activation == NULL) + continue; + + state = &l->activation->state[0]; + + pw_log_trace(NAME" %p: link %p %p %d/%d", c, l, state, + state->pending, state->required); + + if (pw_node_activation_state_dec(state, 1)) { + l->activation->status = PW_NODE_ACTIVATION_TRIGGERED; + l->activation->signal_time = nsec; + + pw_log_trace(NAME" %p: signal %p %p", c, l, state); + + if (write(l->signalfd, &cmd, sizeof(cmd)) != sizeof(cmd)) + pw_log_warn(NAME" %p: write failed %m", c); + } + } +} + +static inline void cycle_signal(struct client *c, int status) +{ + struct pw_node_activation *driver = c->driver_activation; + struct pw_node_activation *activation = c->activation; + + if (status == 0) { + if (c->timebase_callback && driver && driver->segment_owner[0] == c->node_id) { + if (activation->pending_new_pos || + c->jack_state == JackTransportRolling || + c->jack_state == JackTransportLooping) { + c->timebase_callback(c->jack_state, + c->buffer_frames, + &c->jack_position, + activation->pending_new_pos, + c->timebase_arg); + + activation->pending_new_pos = false; + + debug_position(c, &c->jack_position); + jack_to_position(&c->jack_position, activation); + } + } + } + signal_sync(c); +} + +static void +on_rtsocket_condition(void *data, int fd, uint32_t mask) +{ + struct client *c = data; + + if (mask & (SPA_IO_ERR | SPA_IO_HUP)) { + pw_log_warn(NAME" %p: got error", c); + unhandle_socket(c); + return; + } + if (c->thread_callback) { + if (!c->thread_entered) { + c->thread_entered = true; + c->thread_callback(c->thread_arg); + } + return; + } else if (mask & SPA_IO_IN) { + uint32_t buffer_frames; + int status; + + buffer_frames = cycle_run(c); + + status = c->process_callback ? c->process_callback(buffer_frames, c->process_arg) : 0; + + cycle_signal(c, status); + } +} + +static void clear_link(struct client *c, struct link *link) +{ + link->node_id = SPA_ID_INVALID; + link->activation = NULL; + pw_memmap_free(link->mem); + close(link->signalfd); +} + +static void clean_transport(struct client *c) +{ + struct link *l; + + if (c->node_id == SPA_ID_INVALID) + return; + + pw_data_loop_stop(c->loop); + + unhandle_socket(c); + + pw_array_for_each(l, &c->links) + if (l->node_id != SPA_ID_INVALID) + clear_link(c, l); + pw_array_clear(&c->links); + + c->node_id = SPA_ID_INVALID; +} + +static int client_node_transport(void *object, + uint32_t node_id, + int readfd, int writefd, + uint32_t mem_id, uint32_t offset, uint32_t size) +{ + struct client *c = (struct client *) object; + + clean_transport(c); + + c->node_id = node_id; + + c->mem = pw_mempool_map_id(c->remote->pool, mem_id, + PW_MEMMAP_FLAG_READWRITE, offset, size, NULL); + if (c->mem == NULL) { + pw_log_debug(NAME" %p: can't map activation: %m", c); + return -errno; + } + c->activation = c->mem->ptr; + + pw_log_debug(NAME" %p: create client transport with fds %d %d for node %u", + c, readfd, writefd, node_id); + + close(writefd); + c->socket_source = pw_loop_add_io(c->loop->loop, + readfd, + SPA_IO_ERR | SPA_IO_HUP, + true, on_rtsocket_condition, c); + return 0; +} + +static int client_node_set_param(void *object, + uint32_t id, uint32_t flags, + const struct spa_pod *param) +{ + struct client *c = (struct client *) object; + pw_proxy_error((struct pw_proxy*)c->node_proxy, -ENOTSUP, "not supported"); + return -ENOTSUP; +} + +static int update_driver_activation(struct client *c) +{ + struct link *link; + pw_log_debug(NAME" %p: driver %d", c, c->driver_id); + + link = find_activation(&c->links, c->driver_id); + c->driver_activation = link ? link->activation : NULL; + return 0; +} + +static int client_node_set_io(void *object, + uint32_t id, + uint32_t mem_id, + uint32_t offset, + uint32_t size) +{ + struct client *c = (struct client *) object; + struct pw_memmap *mm; + void *ptr; + uint32_t tag[5] = { c->node_id, id, }; + + if ((mm = pw_mempool_find_tag(c->remote->pool, tag, sizeof(tag))) != NULL) + pw_memmap_free(mm); + + if (mem_id == SPA_ID_INVALID) { + mm = ptr = NULL; + size = 0; + } + else { + mm = pw_mempool_map_id(c->remote->pool, mem_id, + PW_MEMMAP_FLAG_READWRITE, offset, size, tag); + if (mm == NULL) { + pw_log_warn(NAME" %p: can't map memory id %u", c, mem_id); + return -errno; + } + ptr = mm->ptr; + } + pw_log_debug(NAME" %p: set io %s %p", c, + spa_debug_type_find_name(spa_type_io, id), ptr); + + switch (id) { + case SPA_IO_Position: + c->position = ptr; + c->driver_id = ptr ? c->position->clock.id : SPA_ID_INVALID; + update_driver_activation(c); + break; + default: + break; + } + + return 0; +} + +static int client_node_event(void *object, const struct spa_event *event) +{ + return -ENOTSUP; +} + +static int client_node_command(void *object, const struct spa_command *command) +{ + struct client *c = (struct client *) object; + + pw_log_debug(NAME" %p: got command %d", c, SPA_COMMAND_TYPE(command)); + + switch (SPA_NODE_COMMAND_ID(command)) { + case SPA_NODE_COMMAND_Suspend: + case SPA_NODE_COMMAND_Pause: + if (c->started) { + pw_loop_update_io(c->loop->loop, + c->socket_source, SPA_IO_ERR | SPA_IO_HUP); + + c->started = false; + } + break; + + case SPA_NODE_COMMAND_Start: + if (!c->started) { + pw_loop_update_io(c->loop->loop, + c->socket_source, + SPA_IO_IN | SPA_IO_ERR | SPA_IO_HUP); + c->started = true; + c->first = true; + c->thread_entered = false; + } + break; + default: + pw_log_warn(NAME" %p: unhandled node command %d", c, SPA_COMMAND_TYPE(command)); + pw_proxy_error((struct pw_proxy*)c->node_proxy, -ENOTSUP, + "unhandled command %d", SPA_COMMAND_TYPE(command)); + } + return 0; +} + +static int client_node_add_port(void *object, + enum spa_direction direction, + uint32_t port_id, const struct spa_dict *props) +{ + struct client *c = (struct client *) object; + pw_proxy_error((struct pw_proxy*)c->node_proxy, -ENOTSUP, "add port not supported"); + return -ENOTSUP; +} + +static int client_node_remove_port(void *object, + enum spa_direction direction, + uint32_t port_id) +{ + struct client *c = (struct client *) object; + pw_proxy_error((struct pw_proxy*)c->node_proxy, -ENOTSUP, "remove port not supported"); + return -ENOTSUP; +} + +static int clear_buffers(struct client *c, struct mix *mix) +{ + struct port *port = mix->port; + struct buffer *b; + uint32_t i, j; + + pw_log_debug(NAME" %p: port %p clear buffers", c, port); + + for (i = 0; i < mix->n_buffers; i++) { + b = &mix->buffers[i]; + + for (j = 0; j < b->n_mem; j++) + pw_memmap_free(b->mem[j]); + + b->n_mem = 0; + } + mix->n_buffers = 0; + spa_list_init(&mix->queue); + return 0; +} + +static int param_enum_format(struct client *c, struct port *p, + struct spa_pod **param, struct spa_pod_builder *b) +{ + switch (p->object->port.type_id) { + case 0: + *param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, + SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), + SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), + SPA_FORMAT_AUDIO_format, SPA_POD_Id(SPA_AUDIO_FORMAT_F32P), + SPA_FORMAT_AUDIO_rate, SPA_POD_CHOICE_RANGE_Int(DEFAULT_SAMPLE_RATE, 1, INT32_MAX), + SPA_FORMAT_AUDIO_channels, SPA_POD_Int(1)); + break; + case 1: + *param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, + SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_application), + SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control)); + break; + case 2: + *param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, + SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), + SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), + SPA_FORMAT_VIDEO_format, SPA_POD_Id(SPA_VIDEO_FORMAT_RGBA_F32), + SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle( + &SPA_RECTANGLE(320, 240), + &SPA_RECTANGLE(1,1), + &SPA_RECTANGLE(INT32_MAX, INT32_MAX)), + SPA_FORMAT_VIDEO_framerate, SPA_POD_CHOICE_RANGE_Fraction( + &SPA_FRACTION(25,1), + &SPA_FRACTION(0,1), + &SPA_FRACTION(INT32_MAX,1))); + break; + default: + return -EINVAL; + } + return 1; +} + +static int param_format(struct client *c, struct port *p, + struct spa_pod **param, struct spa_pod_builder *b) +{ + uint32_t channels[] = { SPA_AUDIO_CHANNEL_MONO }; + struct spa_pod_frame f; + switch (p->object->port.type_id) { + case 0: + spa_pod_builder_push_object(b, &f, SPA_TYPE_OBJECT_Format, SPA_PARAM_Format); + spa_pod_builder_add(b, + SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), + SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), + SPA_FORMAT_AUDIO_format, SPA_POD_Id(SPA_AUDIO_FORMAT_F32P), NULL); + if (p->have_format) { + spa_pod_builder_add(b, + SPA_FORMAT_AUDIO_rate, SPA_POD_Int(p->rate), NULL); + } else { + spa_pod_builder_add(b, + SPA_FORMAT_AUDIO_rate, SPA_POD_CHOICE_RANGE_Int(DEFAULT_SAMPLE_RATE, + 1, INT32_MAX), NULL); + } + spa_pod_builder_add(b, + SPA_FORMAT_AUDIO_channels, SPA_POD_Int(1), + SPA_FORMAT_AUDIO_position, SPA_POD_Array(sizeof(uint32_t), SPA_TYPE_Id, 1, channels), NULL); + *param = spa_pod_builder_pop(b, &f); + break; + case 1: + *param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_Format, SPA_PARAM_Format, + SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_application), + SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control)); + break; + case 2: + *param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_Format, SPA_PARAM_Format, + SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), + SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), + SPA_FORMAT_VIDEO_format, SPA_POD_Id(SPA_VIDEO_FORMAT_RGBA_F32), + SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle( + &SPA_RECTANGLE(320, 240), + &SPA_RECTANGLE(1,1), + &SPA_RECTANGLE(INT32_MAX, INT32_MAX)), + SPA_FORMAT_VIDEO_framerate, SPA_POD_CHOICE_RANGE_Fraction( + &SPA_FRACTION(25,1), + &SPA_FRACTION(0,1), + &SPA_FRACTION(INT32_MAX,1))); + break; + default: + return -EINVAL; + } + return 1; +} + +static int param_buffers(struct client *c, struct port *p, + struct spa_pod **param, struct spa_pod_builder *b) +{ + switch (p->object->port.type_id) { + case 0: + case 1: + *param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, + SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(1, 1, MAX_BUFFERS), + SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), + SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_STEP_Int( + MAX_BUFFER_FRAMES * sizeof(float), + sizeof(float), + MAX_BUFFER_FRAMES * sizeof(float), + sizeof(float)), + SPA_PARAM_BUFFERS_stride, SPA_POD_Int(4), + SPA_PARAM_BUFFERS_align, SPA_POD_Int(16)); + break; + case 2: + *param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, + SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(1, 1, MAX_BUFFERS), + SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), + SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int( + 320 * 240 * 4 * 4, + 0, + INT32_MAX), + SPA_PARAM_BUFFERS_stride, SPA_POD_CHOICE_RANGE_Int(4, 4, INT32_MAX), + SPA_PARAM_BUFFERS_align, SPA_POD_Int(16)); + break; + default: + return -EINVAL; + } + return 1; +} + +static int param_io(struct client *c, struct port *p, + struct spa_pod **param, struct spa_pod_builder *b) +{ + *param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_ParamIO, SPA_PARAM_IO, + SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Buffers), + SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_buffers))); + return 1; +} + +static int port_set_format(struct client *c, struct port *p, + uint32_t flags, const struct spa_pod *param) +{ + if (param == NULL) { + struct mix *mix; + + pw_log_debug(NAME" %p: port %p clear format", c, p); + + spa_list_for_each(mix, &p->mix, port_link) + clear_buffers(c, mix); + p->have_format = false; + } + else { + struct spa_audio_info info = { 0 }; + spa_format_parse(param, &info.media_type, &info.media_subtype); + + switch (info.media_type) { + case SPA_MEDIA_TYPE_audio: + { + if (info.media_subtype != SPA_MEDIA_SUBTYPE_raw) + return -EINVAL; + + if (spa_format_audio_raw_parse(param, &info.info.raw) < 0) + return -EINVAL; + + p->rate = info.info.raw.rate; + break; + } + case SPA_MEDIA_TYPE_application: + if (info.media_subtype != SPA_MEDIA_SUBTYPE_control) + return -EINVAL; + break; + case SPA_MEDIA_TYPE_video: + { + struct spa_video_info vinfo = { 0 }; + + if (info.media_subtype != SPA_MEDIA_SUBTYPE_raw) + return -EINVAL; + if (spa_format_video_raw_parse(param, &vinfo.info.raw) < 0) + return -EINVAL; + break; + } + default: + return -EINVAL; + } + p->have_format = true; + } + return 0; +} + +static int client_node_port_set_param(void *object, + enum spa_direction direction, + uint32_t port_id, + uint32_t id, uint32_t flags, + const struct spa_pod *param) +{ + struct client *c = (struct client *) object; + struct port *p = GET_PORT(c, direction, port_id); + struct spa_pod *params[4]; + uint8_t buffer[4096]; + struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); + + pw_log_debug("port %p: %d.%d id:%d %p", p, direction, port_id, id, param); + + if (id == SPA_PARAM_Format) { + port_set_format(c, p, flags, param); + } + + param_enum_format(c, p, ¶ms[0], &b); + param_format(c, p, ¶ms[1], &b); + param_buffers(c, p, ¶ms[2], &b); + param_io(c, p, ¶ms[3], &b); + + return pw_client_node_proxy_port_update(c->node_proxy, + direction, + port_id, + PW_CLIENT_NODE_PORT_UPDATE_PARAMS, + 4, + (const struct spa_pod **) params, + NULL); +} + +static void init_buffer(struct port *p, void *data, size_t maxframes) +{ + if (p->object->port.type_id == 1) { + struct midi_buffer *mb = data; + mb->magic = MIDI_BUFFER_MAGIC; + mb->buffer_size = MAX_BUFFER_FRAMES * sizeof(float); + mb->nframes = maxframes; + mb->write_pos = 0; + mb->event_count = 0; + mb->lost_events = 0; + pw_log_debug("port %p: init midi buffer %p size:%d", p, data, mb->buffer_size); + } + else + memset(data, 0, maxframes * sizeof(float)); +} + +static int client_node_port_use_buffers(void *object, + enum spa_direction direction, + uint32_t port_id, + uint32_t mix_id, + uint32_t flags, + uint32_t n_buffers, + struct pw_client_node_buffer *buffers) +{ + struct client *c = (struct client *) object; + struct port *p = GET_PORT(c, direction, port_id); + struct buffer *b; + uint32_t i, j, fl, res; + struct mix *mix; + + if (!p->valid) { + res = -EINVAL; + goto done; + } + + if ((mix = ensure_mix(c, p, mix_id)) == NULL) { + res = -ENOMEM; + goto done; + } + + pw_log_debug(NAME" %p: port %p %d %d.%d use_buffers %d", c, p, direction, + port_id, mix_id, n_buffers); + + if (p->object->port.type_id == 2 && direction == SPA_DIRECTION_INPUT) { + fl = PW_MEMMAP_FLAG_READ; + } else { + /* some apps write to the input buffer so we want everything readwrite */ + fl = PW_MEMMAP_FLAG_READWRITE; + } + + /* clear previous buffers */ + clear_buffers(c, mix); + + for (i = 0; i < n_buffers; i++) { + off_t offset; + struct spa_buffer *buf; + struct pw_memmap *mm; + + mm = pw_mempool_map_id(c->remote->pool, buffers[i].mem_id, + fl, buffers[i].offset, buffers[i].size, NULL); + if (mm == NULL) { + pw_log_warn(NAME" %p: can't map memory id %u: %m", c, buffers[i].mem_id); + continue; + } + + buf = buffers[i].buffer; + + b = &mix->buffers[i]; + b->id = i; + b->flags = 0; + b->n_mem = 0; + b->mem[b->n_mem++] = mm; + + pw_log_debug(NAME" %p: add buffer id:%u offset:%u size:%u map:%p ptr:%p", + c, buffers[i].mem_id, buffers[i].offset, + buffers[i].size, mm, mm->ptr); + + offset = 0; + for (j = 0; j < buf->n_metas; j++) { + struct spa_meta *m = &buf->metas[j]; + offset += SPA_ROUND_UP_N(m->size, 8); + } + + b->n_datas = SPA_MIN(buf->n_datas, MAX_BUFFER_DATAS); + + for (j = 0; j < b->n_datas; j++) { + struct spa_data *d = &b->datas[j]; + + memcpy(d, &buf->datas[j], sizeof(struct spa_data)); + d->chunk = + SPA_MEMBER(mm->ptr, offset + sizeof(struct spa_chunk) * j, + struct spa_chunk); + + if (d->type == SPA_DATA_MemId) { + uint32_t mem_id = SPA_PTR_TO_UINT32(d->data); + struct pw_memblock *bm; + struct pw_memmap *bmm; + + bm = pw_mempool_find_id(c->remote->pool, mem_id); + if (bm == NULL) { + pw_log_error(NAME" %p: unknown buffer mem %u", c, mem_id); + res = -ENODEV; + goto done; + + } + + d->fd = bm->fd; + d->type = bm->type; + d->data = NULL; + + bmm = pw_memblock_map(bm, fl, d->mapoffset, d->maxsize, NULL); + if (bmm == NULL) { + res = -errno; + pw_log_error(NAME" %p: failed to map buffer mem %m", c); + d->data = NULL; + goto done; + } + b->mem[b->n_mem++] = bmm; + d->data = bmm->ptr; + + pw_log_debug(NAME" %p: data %d %u -> fd %d %d", + c, j, bm->id, bm->fd, d->maxsize); + } else if (d->type == SPA_DATA_MemPtr) { + int offs = SPA_PTR_TO_INT(d->data); + d->data = SPA_MEMBER(mm->ptr, offs, void); + d->fd = -1; + pw_log_debug(NAME" %p: data %d %u -> mem %p %d", + c, j, b->id, d->data, d->maxsize); + } else { + pw_log_warn("unknown buffer data type %d", d->type); + } + if (mlock(d->data, d->maxsize) < 0) + pw_log_warn(NAME" %p: Failed to mlock memory %p %u: %m", c, + d->data, d->maxsize); + } + + init_buffer(p, p->emptyptr, MAX_BUFFER_FRAMES); + p->zeroed = true; + + SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUT); + if (direction == SPA_DIRECTION_OUTPUT) + reuse_buffer(c, mix, b->id); + + } + pw_log_debug(NAME" %p: have %d buffers", c, n_buffers); + mix->n_buffers = n_buffers; + res = 0; + + done: + if (res < 0) + pw_proxy_error((struct pw_proxy*)c->node_proxy, res, spa_strerror(res)); + return res; +} + +static int client_node_port_set_io(void *object, + enum spa_direction direction, + uint32_t port_id, + uint32_t mix_id, + uint32_t id, + uint32_t mem_id, + uint32_t offset, + uint32_t size) +{ + struct client *c = (struct client *) object; + struct port *p = GET_PORT(c, direction, port_id); + struct pw_memmap *mm; + struct mix *mix; + uint32_t tag[5] = { c->node_id, direction, port_id, mix_id, id }; + void *ptr; + int res = 0; + + if ((mix = ensure_mix(c, p, mix_id)) == NULL) { + res = -ENOMEM; + goto exit; + } + + if ((mm = pw_mempool_find_tag(c->remote->pool, tag, sizeof(tag))) != NULL) + pw_memmap_free(mm); + + if (mem_id == SPA_ID_INVALID) { + mm = ptr = NULL; + size = 0; + } + else { + mm = pw_mempool_map_id(c->remote->pool, mem_id, + PW_MEMMAP_FLAG_READWRITE, offset, size, tag); + if (mm == NULL) { + pw_log_warn(NAME" %p: can't map memory id %u", c, mem_id); + res = -EINVAL; + goto exit; + } + ptr = mm->ptr; + } + + pw_log_debug(NAME" %p: port %p mix:%d set io:%s id:%u ptr:%p", c, p, mix_id, + spa_debug_type_find_name(spa_type_io, id), id, ptr); + + switch (id) { + case SPA_IO_Buffers: + mix->io = ptr; + break; + default: + break; + } + + exit: + if (res < 0) + pw_proxy_error((struct pw_proxy*)c->node_proxy, res, spa_strerror(res)); + return res; +} + +static int client_node_set_activation(void *object, + uint32_t node_id, + int signalfd, + uint32_t mem_id, + uint32_t offset, + uint32_t size) +{ + struct client *c = (struct client *) object; + struct pw_memmap *mm; + struct link *link; + void *ptr; + int res = 0; + + if (c->node_id == node_id) { + pw_log_debug(NAME" %p: our activation %u: %u %u %u", c, node_id, + mem_id, offset, size); + close(signalfd); + return 0; + } + + if (mem_id == SPA_ID_INVALID) { + mm = ptr = NULL; + size = 0; + } + else { + mm = pw_mempool_map_id(c->remote->pool, mem_id, + PW_MEMMAP_FLAG_READWRITE, offset, size, NULL); + if (mm == NULL) { + pw_log_warn(NAME" %p: can't map memory id %u", c, mem_id); + res = -EINVAL; + goto exit; + } + ptr = mm->ptr; + } + + pw_log_debug(NAME" %p: set activation %u: %u %u %u %p", c, node_id, + mem_id, offset, size, ptr); + + if (ptr) { + link = pw_array_add(&c->links, sizeof(struct link)); + if (link == NULL) { + res = -errno; + goto exit; + } + link->node_id = node_id; + link->mem = mm; + link->activation = ptr; + link->signalfd = signalfd; + } + else { + link = find_activation(&c->links, node_id); + if (link == NULL) { + res = -EINVAL; + goto exit; + } + clear_link(c, link); + } + + if (c->driver_id == node_id) + update_driver_activation(c); + + exit: + if (res < 0) + pw_proxy_error((struct pw_proxy*)c->node_proxy, res, spa_strerror(res)); + return res; +} + +static const struct pw_client_node_proxy_events client_node_events = { + PW_VERSION_CLIENT_NODE_PROXY_EVENTS, + .transport = client_node_transport, + .set_param = client_node_set_param, + .set_io = client_node_set_io, + .event = client_node_event, + .command = client_node_command, + .add_port = client_node_add_port, + .remove_port = client_node_remove_port, + .port_set_param = client_node_port_set_param, + .port_use_buffers = client_node_port_use_buffers, + .port_set_io = client_node_port_set_io, + .set_activation = client_node_set_activation, +}; + +static jack_port_type_id_t string_to_type(const char *port_type) +{ + if (!strcmp(JACK_DEFAULT_AUDIO_TYPE, port_type)) + return 0; + else if (!strcmp(JACK_DEFAULT_MIDI_TYPE, port_type)) + return 1; + else if (!strcmp(JACK_DEFAULT_VIDEO_TYPE, port_type)) + return 2; + else if (!strcmp("other", port_type)) + return 3; + else + return SPA_ID_INVALID; +} + +static const char* type_to_string(jack_port_type_id_t type_id) +{ + switch(type_id) { + case 0: + return JACK_DEFAULT_AUDIO_TYPE; + case 1: + return JACK_DEFAULT_MIDI_TYPE; + case 2: + return JACK_DEFAULT_VIDEO_TYPE; + case 3: + return "other"; + default: + return NULL; + } +} + +static void registry_event_global(void *data, uint32_t id, + uint32_t permissions, uint32_t type, uint32_t version, + const struct spa_dict *props) +{ + struct client *c = (struct client *) data; + struct object *o, *ot; + const char *str; + size_t size; + + if (props == NULL) + return; + + switch (type) { + case PW_TYPE_INTERFACE_Node: + o = alloc_object(c); + + if ((str = spa_dict_lookup(props, PW_KEY_NODE_DESCRIPTION)) == NULL && + (str = spa_dict_lookup(props, PW_KEY_NODE_NICK)) == NULL && + (str = spa_dict_lookup(props, PW_KEY_NODE_NAME)) == NULL) { + str = "node"; + } + snprintf(o->node.name, sizeof(o->node.name), "%s/%d", str, id); + + if ((str = spa_dict_lookup(props, PW_KEY_PRIORITY_MASTER)) != NULL) + o->node.priority = pw_properties_parse_int(str); + + pw_log_debug(NAME" %p: add node %d", c, id); + spa_list_append(&c->context.nodes, &o->link); + break; + + case PW_TYPE_INTERFACE_Port: + { + const struct spa_dict_item *item; + unsigned long flags = 0; + jack_port_type_id_t type_id; + uint32_t node_id; + char full_name[1024]; + + if ((str = spa_dict_lookup(props, PW_KEY_FORMAT_DSP)) == NULL) + str = "other"; + if ((type_id = string_to_type(str)) == SPA_ID_INVALID) + goto exit; + + if ((str = spa_dict_lookup(props, PW_KEY_NODE_ID)) == NULL) + goto exit; + + node_id = atoi(str); + + if ((str = spa_dict_lookup(props, PW_KEY_PORT_NAME)) == NULL) + goto exit; + + spa_dict_for_each(item, props) { + if (!strcmp(item->key, PW_KEY_PORT_DIRECTION)) { + if (strcmp(item->value, "in") == 0) + flags |= JackPortIsInput; + else if (strcmp(item->value, "out") == 0) + flags |= JackPortIsOutput; + } + else if (!strcmp(item->key, PW_KEY_PORT_PHYSICAL)) { + if (pw_properties_parse_bool(item->value)) + flags |= JackPortIsPhysical; + } + else if (!strcmp(item->key, PW_KEY_PORT_TERMINAL)) { + if (pw_properties_parse_bool(item->value)) + flags |= JackPortIsTerminal; + } + else if (!strcmp(item->key, PW_KEY_PORT_CONTROL)) { + if (pw_properties_parse_bool(item->value)) + type_id = 1; + } + } + + o = NULL; + if (node_id == c->node_id) { + snprintf(full_name, sizeof(full_name), "%s:%s", c->name, str); + o = find_port(c, full_name); + if (o != NULL) + pw_log_debug(NAME" %p: %s found our port %p", c, full_name, o); + } + if (o == NULL) { + o = alloc_object(c); + if (o == NULL) + goto exit; + + spa_list_append(&c->context.ports, &o->link); + ot = pw_map_lookup(&c->context.globals, node_id); + if (ot == NULL || ot->type != PW_TYPE_INTERFACE_Node) + goto exit_free; + + snprintf(o->port.name, sizeof(o->port.name), "%s:%s", ot->node.name, str); + o->port.port_id = SPA_ID_INVALID; + o->port.priority = ot->node.priority; + } + + if ((str = spa_dict_lookup(props, PW_KEY_OBJECT_PATH)) != NULL) + snprintf(o->port.alias1, sizeof(o->port.alias1), "%s", str); + else + o->port.alias1[0] = '\0'; + + if ((str = spa_dict_lookup(props, PW_KEY_PORT_ALIAS)) != NULL) + snprintf(o->port.alias2, sizeof(o->port.alias2), "%s", str); + else + o->port.alias2[0] = '\0'; + + o->port.flags = flags; + o->port.type_id = type_id; + o->port.node_id = node_id; + + if (o->port.flags & JackPortIsOutput) { + o->port.capture_latency.min = 1024; + o->port.capture_latency.max = 1024; + } else { + o->port.playback_latency.min = 1024; + o->port.playback_latency.max = 1024; + } + + pw_log_debug(NAME" %p: add port %d %s %d", c, id, o->port.name, type_id); + break; + } + case PW_TYPE_INTERFACE_Link: + o = alloc_object(c); + spa_list_append(&c->context.links, &o->link); + + if ((str = spa_dict_lookup(props, PW_KEY_LINK_OUTPUT_PORT)) == NULL) + goto exit_free; + o->port_link.src = pw_properties_parse_int(str); + + if ((str = spa_dict_lookup(props, PW_KEY_LINK_INPUT_PORT)) == NULL) + goto exit_free; + o->port_link.dst = pw_properties_parse_int(str); + + pw_log_debug(NAME" %p: add link %d %d->%d", c, id, + o->port_link.src, o->port_link.dst); + break; + + default: + goto exit; + } + + o->type = type; + o->id = id; + + size = pw_map_get_size(&c->context.globals); + while (id > size) + pw_map_insert_at(&c->context.globals, size++, NULL); + pw_map_insert_at(&c->context.globals, id, o); + + pw_thread_loop_unlock(c->context.loop); + + switch (type) { + case PW_TYPE_INTERFACE_Node: + if (c->registration_callback) + c->registration_callback(o->node.name, 1, c->registration_arg); + break; + + case PW_TYPE_INTERFACE_Port: + if (c->portregistration_callback) + c->portregistration_callback(o->id, 1, c->portregistration_arg); + break; + + case PW_TYPE_INTERFACE_Link: + if (c->connect_callback) + c->connect_callback(o->port_link.src, o->port_link.dst, 1, c->connect_arg); + break; + } + pw_thread_loop_lock(c->context.loop); + + exit: + return; + exit_free: + free_object(c, o); + return; +} + +static void registry_event_global_remove(void *object, uint32_t id) +{ + struct client *c = (struct client *) object; + struct object *o; + + pw_log_debug(NAME" %p: removed: %u", c, id); + + o = pw_map_lookup(&c->context.globals, id); + if (o == NULL) + return; + + pw_thread_loop_unlock(c->context.loop); + + switch (o->type) { + case PW_TYPE_INTERFACE_Node: + if (c->registration_callback) + c->registration_callback(o->node.name, 0, c->registration_arg); + break; + case PW_TYPE_INTERFACE_Port: + if (c->portregistration_callback) + c->portregistration_callback(o->id, 0, c->portregistration_arg); + break; + case PW_TYPE_INTERFACE_Link: + if (c->connect_callback) + c->connect_callback(o->port_link.src, o->port_link.dst, 0, c->connect_arg); + break; + } + pw_thread_loop_lock(c->context.loop); + + /* JACK clients expect the objects to hang around after + * they are unregistered. We keep them in the map but reuse the + * object when we can + * pw_map_insert_at(&c->context.globals, id, NULL); + **/ + free_object(c, o); + return; +} + +static const struct pw_registry_proxy_events registry_events = { + PW_VERSION_REGISTRY_PROXY_EVENTS, + .global = registry_event_global, + .global_remove = registry_event_global_remove, +}; + +SPA_EXPORT +jack_client_t * jack_client_open (const char *client_name, + jack_options_t options, + jack_status_t *status, ...) +{ + struct client *client; + bool busy = true; + struct spa_dict props; + struct spa_dict_item items[6]; + const struct spa_support *support; + uint32_t n_support; + const char *str; + struct spa_cpu *cpu_iface; + struct spa_node_info ni; + int i; + + if (getenv("PIPEWIRE_NOJACK") != NULL) + goto disabled; + + client = calloc(1, sizeof(struct client)); + if (client == NULL) + goto init_failed; + + pw_log_debug(NAME" %p: open '%s' options:%d", client, client_name, options); + + client->node_id = SPA_ID_INVALID; + strncpy(client->name, client_name, JACK_CLIENT_NAME_SIZE); + client->context.main = pw_main_loop_new(NULL); + client->context.loop = pw_thread_loop_new(pw_main_loop_get_loop(client->context.main), client_name); + client->context.core = pw_core_new(pw_thread_loop_get_loop(client->context.loop), NULL, 0); + spa_list_init(&client->context.free_objects); + spa_list_init(&client->context.nodes); + spa_list_init(&client->context.ports); + spa_list_init(&client->context.links); + + support = pw_core_get_support(client->context.core, &n_support); + + mix2 = mix2_c; + cpu_iface = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_CPU); + if (cpu_iface) { +#if defined (__SSE__) + uint32_t flags = spa_cpu_get_flags(cpu_iface); + if (flags & SPA_CPU_FLAG_SSE) + mix2 = mix2_sse; +#endif + } + + client->loop = pw_data_loop_new(NULL); + if (client->loop == NULL) + goto init_failed; + + pw_array_init(&client->links, 64); + + client->buffer_frames = (uint32_t)-1; + client->sample_rate = (uint32_t)-1; + + spa_list_init(&client->free_mix); + for (i = 0; i < MAX_MIX; i++) + spa_list_append(&client->free_mix, &client->mix_pool[i].link); + + init_port_pool(client, SPA_DIRECTION_INPUT); + init_port_pool(client, SPA_DIRECTION_OUTPUT); + + pw_map_init(&client->context.globals, 64, 64); + + pw_thread_loop_start(client->context.loop); + + pw_thread_loop_lock(client->context.loop); + client->remote = pw_remote_new(client->context.core, + pw_properties_new( + PW_KEY_CLIENT_NAME, client_name, + PW_KEY_CLIENT_API, "jack", + NULL), + 0); + + pw_remote_add_listener(client->remote, &client->remote_listener, &remote_events, client); + + if (pw_remote_connect(client->remote) < 0) + goto server_failed; + + while (busy) { + const char *error = NULL; + + switch (pw_remote_get_state(client->remote, &error)) { + case PW_REMOTE_STATE_ERROR: + goto server_failed; + + case PW_REMOTE_STATE_CONNECTED: + busy = false; + break; + + default: + break; + } + if (busy) + pw_thread_loop_wait(client->context.loop); + + } + client->core_proxy = pw_remote_get_core_proxy(client->remote); + pw_core_proxy_add_listener(client->core_proxy, + &client->core_listener, + &core_events, client); + client->registry_proxy = pw_core_proxy_get_registry(client->core_proxy, + PW_VERSION_REGISTRY_PROXY, 0); + pw_registry_proxy_add_listener(client->registry_proxy, + &client->registry_listener, + ®istry_events, client); + + + props = SPA_DICT_INIT(items, 0); + items[props.n_items++] = SPA_DICT_ITEM_INIT(PW_KEY_NODE_NAME, client_name); + items[props.n_items++] = SPA_DICT_ITEM_INIT(PW_KEY_MEDIA_TYPE, "Audio"); + items[props.n_items++] = SPA_DICT_ITEM_INIT(PW_KEY_MEDIA_CATEGORY, "Duplex"); + items[props.n_items++] = SPA_DICT_ITEM_INIT(PW_KEY_MEDIA_ROLE, "DSP"); + if ((str = getenv("PIPEWIRE_LATENCY")) == NULL) + str = DEFAULT_LATENCY; + items[props.n_items++] = SPA_DICT_ITEM_INIT(PW_KEY_NODE_LATENCY, str); + items[props.n_items++] = SPA_DICT_ITEM_INIT(PW_KEY_NODE_ALWAYS_PROCESS, "1"); + + client->node_proxy = pw_core_proxy_create_object(client->core_proxy, + "client-node", + PW_TYPE_INTERFACE_ClientNode, + PW_VERSION_CLIENT_NODE, + &props, + 0); + if (client->node_proxy == NULL) + goto init_failed; + + pw_client_node_proxy_add_listener(client->node_proxy, + &client->node_listener, &client_node_events, client); + pw_proxy_add_listener((struct pw_proxy*)client->node_proxy, + &client->proxy_listener, &proxy_events, client); + + ni = SPA_NODE_INFO_INIT(); + ni.max_input_ports = MAX_PORTS; + ni.max_output_ports = MAX_PORTS; + ni.change_mask = SPA_NODE_CHANGE_MASK_FLAGS; + ni.flags = SPA_NODE_FLAG_RT; + + pw_client_node_proxy_update(client->node_proxy, + PW_CLIENT_NODE_UPDATE_INFO, + 0, NULL, &ni); + + if (do_sync(client) < 0) + goto init_failed; + + pw_thread_loop_unlock(client->context.loop); + + if (status) + *status = 0; + + pw_log_trace(NAME" %p: new", client); + return (jack_client_t *)client; + + init_failed: + if (status) + *status = JackFailure | JackInitFailure; + goto exit; + server_failed: + if (status) + *status = JackFailure | JackServerFailed; + goto exit; + exit: + pw_thread_loop_unlock(client->context.loop); + return NULL; + disabled: + if (status) + *status = JackFailure | JackServerFailed; + return NULL; +} + +SPA_EXPORT +jack_client_t * jack_client_new (const char *client_name) +{ + jack_options_t options = JackUseExactName; + jack_status_t status; + + if (getenv("JACK_START_SERVER") == NULL) + options |= JackNoStartServer; + + return jack_client_open(client_name, options, &status, NULL); +} + +SPA_EXPORT +int jack_client_close (jack_client_t *client) +{ + struct client *c = (struct client *) client; + + pw_log_debug(NAME" %p: close", client); + + pw_thread_loop_stop(c->context.loop); + + c->destroyed = true; + pw_core_destroy(c->context.core); + pw_thread_loop_destroy(c->context.loop); + pw_main_loop_destroy(c->context.main); + + pw_log_debug(NAME" %p: free", client); + free(c); + + return 0; +} + +SPA_EXPORT +int jack_client_name_size (void) +{ + pw_log_trace("%d", JACK_CLIENT_NAME_SIZE); + return JACK_CLIENT_NAME_SIZE; +} + +SPA_EXPORT +char * jack_get_client_name (jack_client_t *client) +{ + struct client *c = (struct client *) client; + pw_log_trace(NAME" %p: %s", c, c->name); + return c->name; +} + +static jack_uuid_t cuuid = 0x2; + +SPA_EXPORT +char *jack_get_uuid_for_client_name (jack_client_t *client, + const char *client_name) +{ + struct client *c = (struct client *) client; + struct object *o; + + spa_list_for_each(o, &c->context.nodes, link) { + if (strcmp(o->node.name, client_name) == 0) { + char *uuid; + asprintf(&uuid, "%" PRIu64, (cuuid << 32) | o->id); + pw_log_debug(NAME" %p: name %s -> %s", + client, client_name, uuid); + return uuid; + } + } + return NULL; +} + +SPA_EXPORT +char *jack_get_client_name_by_uuid (jack_client_t *client, + const char *client_uuid ) +{ + struct client *c = (struct client *) client; + struct object *o; + jack_uuid_t uuid; + jack_uuid_t cuuid = 0x2; + + if (jack_uuid_parse(client_uuid, &uuid) < 0) + return NULL; + + spa_list_for_each(o, &c->context.nodes, link) { + if ((cuuid << 32 | o->id) == uuid) { + pw_log_debug(NAME" %p: uuid %s (%"PRIu64")-> %s", + client, client_uuid, uuid, o->node.name); + return strdup(o->node.name); + } + } + return NULL; +} + +SPA_EXPORT +int jack_internal_client_new (const char *client_name, + const char *load_name, + const char *load_init) +{ + pw_log_warn("not implemented %s %s %s", client_name, load_name, load_init); + return -ENOTSUP; +} + +SPA_EXPORT +void jack_internal_client_close (const char *client_name) +{ + pw_log_warn("not implemented %s", client_name); +} + +static int do_activate(struct client *c) +{ + int res; + + pw_data_loop_start(c->loop); + + pw_thread_loop_lock(c->context.loop); + pw_log_debug(NAME" %p: activate", c); + pw_client_node_proxy_set_active(c->node_proxy, true); + + res = do_sync(c); + + pw_thread_loop_unlock(c->context.loop); + return res; +} + +SPA_EXPORT +int jack_activate (jack_client_t *client) +{ + struct client *c = (struct client *) client; + int res; + + if (c->active) + return 0; + + if ((res = do_activate(c)) < 0) + return res; + + c->activation->pending_new_pos = true; + c->activation->pending_sync = true; + c->active = true; + + return 0; +} + +SPA_EXPORT +int jack_deactivate (jack_client_t *client) +{ + struct client *c = (struct client *) client; + int res; + + if (!c->active) + return 0; + + pw_thread_loop_lock(c->context.loop); + pw_log_debug(NAME" %p: deactivate", c); + pw_client_node_proxy_set_active(c->node_proxy, false); + + c->activation->pending_new_pos = false; + c->activation->pending_sync = false; + + res = do_sync(c); + + pw_thread_loop_unlock(c->context.loop); + + pw_data_loop_stop(c->loop); + + if (res < 0) + return res; + + c->active = false; + return 0; +} + +SPA_EXPORT +int jack_get_client_pid (const char *name) +{ + pw_log_error("not implemented on library side"); + return 0; +} + +SPA_EXPORT +jack_native_thread_t jack_client_thread_id (jack_client_t *client) +{ + return pthread_self(); +} + +SPA_EXPORT +int jack_is_realtime (jack_client_t *client) +{ + return 1; +} + +SPA_EXPORT +jack_nframes_t jack_thread_wait (jack_client_t *client, int status) +{ + pw_log_error(NAME" %p: jack_thread_wait: deprecated, use jack_cycle_wait/jack_cycle_signal", client); + return 0; +} + +SPA_EXPORT +jack_nframes_t jack_cycle_wait (jack_client_t* client) +{ + struct client *c = (struct client *) client; + jack_nframes_t res; + res = cycle_wait(c); + pw_log_trace(NAME" %p: result:%d", c, res); + return res; +} + +SPA_EXPORT +void jack_cycle_signal (jack_client_t* client, int status) +{ + struct client *c = (struct client *) client; + pw_log_trace(NAME" %p: status:%d", c, status); + cycle_signal(c, status); +} + +SPA_EXPORT +int jack_set_process_thread(jack_client_t* client, JackThreadCallback thread_callback, void *arg) +{ + struct client *c = (struct client *) client; + + if (c->active) { + pw_log_error(NAME" %p: can't set callback on active client", c); + return -EIO; + } else if (c->process_callback) { + pw_log_error(NAME" %p: process callback was already set", c); + return -EIO; + } + pw_log_debug(NAME" %p: %p %p", c, thread_callback, arg); + c->thread_callback = thread_callback; + c->thread_arg = arg; + return 0; +} + +SPA_EXPORT +int jack_set_thread_init_callback (jack_client_t *client, + JackThreadInitCallback thread_init_callback, + void *arg) +{ + struct client *c = (struct client *) client; + pw_log_debug(NAME" %p: %p %p", c, thread_init_callback, arg); + c->thread_init_callback = thread_init_callback; + c->thread_init_arg = arg; + return 0; +} + +SPA_EXPORT +void jack_on_shutdown (jack_client_t *client, + JackShutdownCallback shutdown_callback, void *arg) +{ + struct client *c = (struct client *) client; + + if (c->active) { + pw_log_error(NAME" %p: can't set callback on active client", c); + } else { + pw_log_debug(NAME" %p: %p %p", c, shutdown_callback, arg); + c->shutdown_callback = shutdown_callback; + c->shutdown_arg = arg; + } +} + +SPA_EXPORT +void jack_on_info_shutdown (jack_client_t *client, + JackInfoShutdownCallback shutdown_callback, void *arg) +{ + struct client *c = (struct client *) client; + + if (c->active) { + pw_log_error(NAME" %p: can't set callback on active client", c); + } else { + pw_log_debug(NAME" %p: %p %p", c, shutdown_callback, arg); + c->info_shutdown_callback = shutdown_callback; + c->info_shutdown_arg = arg; + } +} + +SPA_EXPORT +int jack_set_process_callback (jack_client_t *client, + JackProcessCallback process_callback, + void *arg) +{ + struct client *c = (struct client *) client; + + if (c->active) { + pw_log_error(NAME" %p: can't set callback on active client", c); + return -EIO; + } else if (c->thread_callback) { + pw_log_error(NAME" %p: thread callback was already set", c); + return -EIO; + } + + pw_log_debug(NAME" %p: %p %p", c, process_callback, arg); + c->process_callback = process_callback; + c->process_arg = arg; + return 0; +} + +SPA_EXPORT +int jack_set_freewheel_callback (jack_client_t *client, + JackFreewheelCallback freewheel_callback, + void *arg) +{ + struct client *c = (struct client *) client; + if (c->active) { + pw_log_error(NAME" %p: can't set callback on active client", c); + return -EIO; + } + pw_log_debug(NAME" %p: %p %p", c, freewheel_callback, arg); + c->freewheel_callback = freewheel_callback; + c->freewheel_arg = arg; + return 0; +} + +SPA_EXPORT +int jack_set_buffer_size_callback (jack_client_t *client, + JackBufferSizeCallback bufsize_callback, + void *arg) +{ + struct client *c = (struct client *) client; + if (c->active) { + pw_log_error(NAME" %p: can't set callback on active client", c); + return -EIO; + } + pw_log_debug(NAME" %p: %p %p", c, bufsize_callback, arg); + c->bufsize_callback = bufsize_callback; + c->bufsize_arg = arg; + return 0; +} + +SPA_EXPORT +int jack_set_sample_rate_callback (jack_client_t *client, + JackSampleRateCallback srate_callback, + void *arg) +{ + struct client *c = (struct client *) client; + if (c->active) { + pw_log_error(NAME" %p: can't set callback on active client", c); + return -EIO; + } + pw_log_debug(NAME" %p: %p %p", c, srate_callback, arg); + c->srate_callback = srate_callback; + c->srate_arg = arg; + return 0; +} + +SPA_EXPORT +int jack_set_client_registration_callback (jack_client_t *client, + JackClientRegistrationCallback + registration_callback, void *arg) +{ + struct client *c = (struct client *) client; + if (c->active) { + pw_log_error(NAME" %p: can't set callback on active client", c); + return -EIO; + } + pw_log_debug(NAME" %p: %p %p", c, registration_callback, arg); + c->registration_callback = registration_callback; + c->registration_arg = arg; + return 0; +} + +SPA_EXPORT +int jack_set_port_registration_callback (jack_client_t *client, + JackPortRegistrationCallback + registration_callback, void *arg) +{ + struct client *c = (struct client *) client; + if (c->active) { + pw_log_error(NAME" %p: can't set callback on active client", c); + return -EIO; + } + pw_log_debug(NAME" %p: %p %p", c, registration_callback, arg); + c->portregistration_callback = registration_callback; + c->portregistration_arg = arg; + return 0; +} + + +SPA_EXPORT +int jack_set_port_connect_callback (jack_client_t *client, + JackPortConnectCallback + connect_callback, void *arg) +{ + struct client *c = (struct client *) client; + if (c->active) { + pw_log_error(NAME" %p: can't set callback on active client", c); + return -EIO; + } + pw_log_debug(NAME" %p: %p %p", c, connect_callback, arg); + c->connect_callback = connect_callback; + c->connect_arg = arg; + return 0; +} + +SPA_EXPORT +int jack_set_port_rename_callback (jack_client_t *client, + JackPortRenameCallback rename_callback, + void *arg) +{ + struct client *c = (struct client *) client; + if (c->active) { + pw_log_error(NAME" %p: can't set callback on active client", c); + return -EIO; + } + pw_log_debug(NAME" %p: %p %p", c, rename_callback, arg); + c->rename_callback = rename_callback; + c->rename_arg = arg; + return 0; +} + +SPA_EXPORT +int jack_set_graph_order_callback (jack_client_t *client, + JackGraphOrderCallback graph_callback, + void *data) +{ + struct client *c = (struct client *) client; + if (c->active) { + pw_log_error(NAME" %p: can't set callback on active client", c); + return -1; + } + pw_log_trace(NAME" %p: %p %p", c, graph_callback, data); + c->graph_callback = graph_callback; + c->graph_arg = data; + return 0; +} + +SPA_EXPORT +int jack_set_xrun_callback (jack_client_t *client, + JackXRunCallback xrun_callback, void *arg) +{ + struct client *c = (struct client *) client; + if (c->active) { + pw_log_error(NAME" %p: can't set callback on active client", c); + return -1; + } + pw_log_debug(NAME" %p: %p %p", c, xrun_callback, arg); + c->xrun_callback = xrun_callback; + c->xrun_arg = arg; + return 0; +} + +SPA_EXPORT +int jack_set_latency_callback (jack_client_t *client, + JackLatencyCallback latency_callback, + void *data) +{ + struct client *c = (struct client *) client; + if (c->active) { + pw_log_error(NAME" %p: can't set callback on active client", c); + return -EIO; + } + pw_log_debug(NAME" %p: %p %p", c, latency_callback, data); + c->latency_callback = latency_callback; + c->latency_arg = data; + return 0; +} + +SPA_EXPORT +int jack_set_freewheel(jack_client_t* client, int onoff) +{ + pw_log_warn(NAME" %p: not implemented %d", client, onoff); + return -ENOTSUP; +} + +SPA_EXPORT +int jack_set_buffer_size (jack_client_t *client, jack_nframes_t nframes) +{ + struct client *c = (struct client *) client; + struct spa_node_info ni; + struct spa_dict_item items[1]; + char latency[128]; + + snprintf(latency, sizeof(latency), "%d/%d", nframes, jack_get_sample_rate(client)); + + ni = SPA_NODE_INFO_INIT(); + ni.max_input_ports = MAX_PORTS; + ni.max_output_ports = MAX_PORTS; + ni.change_mask = SPA_NODE_CHANGE_MASK_PROPS; + items[0] = SPA_DICT_ITEM_INIT(PW_KEY_NODE_LATENCY, latency); + ni.props = &SPA_DICT_INIT_ARRAY(items); + + pw_client_node_proxy_update(c->node_proxy, + PW_CLIENT_NODE_UPDATE_INFO, + 0, NULL, &ni); + + return 0; +} + +SPA_EXPORT +jack_nframes_t jack_get_sample_rate (jack_client_t *client) +{ + struct client *c = (struct client *) client; + if (c->sample_rate == (uint32_t)-1) + return DEFAULT_SAMPLE_RATE; + return c->sample_rate; +} + +SPA_EXPORT +jack_nframes_t jack_get_buffer_size (jack_client_t *client) +{ + struct client *c = (struct client *) client; + if (c->buffer_frames == (uint32_t)-1) + return DEFAULT_BUFFER_FRAMES; + return c->buffer_frames; +} + +SPA_EXPORT +int jack_engine_takeover_timebase (jack_client_t *client) +{ + pw_log_error(NAME" %p: deprecated", client); + return 0; +} + +SPA_EXPORT +float jack_cpu_load (jack_client_t *client) +{ + struct client *c = (struct client *) client; + float res = 0.0f; + + if (c->driver_activation) + res = c->driver_activation->cpu_load[0] * 100.0f; + + pw_log_trace(NAME" %p: cpu load %f", client, res); + return res; +} + +#include "statistics.c" + +SPA_EXPORT +jack_port_t * jack_port_register (jack_client_t *client, + const char *port_name, + const char *port_type, + unsigned long flags, + unsigned long buffer_frames) +{ + struct client *c = (struct client *) client; + enum spa_direction direction; + struct spa_port_info port_info; + struct spa_param_info port_params[5]; + struct spa_dict dict; + struct spa_dict_item items[10]; + struct object *o; + jack_port_type_id_t type_id; + uint8_t buffer[1024]; + struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); + struct spa_pod *params[4]; + uint32_t n_params = 0; + struct port *p; + int res; + + pw_log_debug(NAME" %p: port register \"%s\" \"%s\" %08lx %ld", + c, port_name, port_type, flags, buffer_frames); + + if (flags & JackPortIsInput) + direction = PW_DIRECTION_INPUT; + else if (flags & JackPortIsOutput) + direction = PW_DIRECTION_OUTPUT; + else + return NULL; + + if ((type_id = string_to_type(port_type)) == SPA_ID_INVALID) + return NULL; + + if ((p = alloc_port(c, direction)) == NULL) + return NULL; + + o = p->object; + o->port.flags = flags; + snprintf(o->port.name, sizeof(o->port.name), "%s:%s", c->name, port_name); + o->port.type_id = type_id; + + pw_log_debug(NAME" %p: port %p", c, p); + + spa_list_init(&p->mix); + + port_info = SPA_PORT_INFO_INIT(); + port_info.change_mask |= SPA_PORT_CHANGE_MASK_FLAGS; + port_info.flags = SPA_PORT_FLAG_NO_REF; + port_info.change_mask |= SPA_PORT_CHANGE_MASK_PROPS; + dict = SPA_DICT_INIT(items, 0); + items[dict.n_items++] = SPA_DICT_ITEM_INIT(PW_KEY_FORMAT_DSP, port_type); + items[dict.n_items++] = SPA_DICT_ITEM_INIT(PW_KEY_PORT_NAME, port_name); + port_info.props = &dict; + port_info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; + port_params[0] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); + port_params[1] = SPA_PARAM_INFO(SPA_PARAM_Buffers, SPA_PARAM_INFO_READ); + port_params[2] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); + port_params[3] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); + port_info.params = port_params; + port_info.n_params = 4; + + param_enum_format(c, p, ¶ms[n_params++], &b); + param_buffers(c, p, ¶ms[n_params++], &b); + param_io(c, p, ¶ms[n_params++], &b); + + pw_thread_loop_lock(c->context.loop); + + pw_client_node_proxy_port_update(c->node_proxy, + direction, + p->id, + PW_CLIENT_NODE_PORT_UPDATE_PARAMS | + PW_CLIENT_NODE_PORT_UPDATE_INFO, + n_params, + (const struct spa_pod **) params, + &port_info); + + res = do_sync(c); + + pw_thread_loop_unlock(c->context.loop); + + if (res < 0) + return NULL; + + return (jack_port_t *) o; +} + +SPA_EXPORT +int jack_port_unregister (jack_client_t *client, jack_port_t *port) +{ + struct object *o = (struct object *) port; + struct client *c = o->client; + struct port *p; + int res; + + if (o->type != PW_TYPE_INTERFACE_Port || o->port.port_id == SPA_ID_INVALID) { + pw_log_error(NAME" %p: invalid port %p", client, port); + return -EINVAL; + } + pw_log_debug(NAME" %p: port unregister %p", client, port); + + pw_thread_loop_lock(c->context.loop); + + p = GET_PORT(c, GET_DIRECTION(o->port.flags), o->port.port_id); + + free_port(c, p); + + pw_client_node_proxy_port_update(c->node_proxy, + p->direction, + p->id, + 0, 0, NULL, NULL); + + res = do_sync(c); + + pw_thread_loop_unlock(c->context.loop); + + return res; +} + +static inline void *get_buffer_input_float(struct client *c, struct port *p, jack_nframes_t frames) +{ + struct mix *mix; + struct buffer *b; + struct spa_io_buffers *io; + int layer = 0; + void *ptr = NULL; + + spa_list_for_each(mix, &p->mix, port_link) { + pw_log_trace(NAME" %p: port %p mix %d.%d get buffer %d", + c, p, p->id, mix->id, frames); + io = mix->io; + if (io == NULL || io->buffer_id >= mix->n_buffers) + continue; + + io->status = SPA_STATUS_NEED_DATA; + b = &mix->buffers[io->buffer_id]; + if (layer++ == 0) + ptr = b->datas[0].data; + else { + mix2(p->emptyptr, ptr, b->datas[0].data, frames); + ptr = p->emptyptr; + p->zeroed = false; + } + } + return ptr; +} + +static inline void *get_buffer_input_midi(struct client *c, struct port *p, jack_nframes_t frames) +{ + struct mix *mix; + struct spa_io_buffers *io; + void *ptr = p->emptyptr; + struct spa_pod_sequence *seq[CONNECTION_NUM_FOR_PORT]; + uint32_t n_seq = 0; + + jack_midi_clear_buffer(ptr); + + spa_list_for_each(mix, &p->mix, port_link) { + struct spa_data *d; + void *pod; + + pw_log_trace(NAME" %p: port %p mix %d.%d get buffer %d", + c, p, p->id, mix->id, frames); + + io = mix->io; + if (io == NULL || io->buffer_id >= mix->n_buffers) + continue; + + io->status = SPA_STATUS_NEED_DATA; + d = &mix->buffers[io->buffer_id].datas[0]; + + if ((pod = spa_pod_from_data(d->data, d->maxsize, d->chunk->offset, d->chunk->size)) == NULL) + continue; + if (!spa_pod_is_sequence(pod)) + continue; + + seq[n_seq++] = pod; + } + convert_to_midi(seq, n_seq, ptr); + + return ptr; +} + +static inline void *get_buffer_output_float(struct client *c, struct port *p, jack_nframes_t frames) +{ + void *ptr; + + ptr = get_buffer_output(c, p, frames, sizeof(float)); + if (ptr == NULL) + ptr = p->emptyptr; + + return ptr; +} + +static inline void *get_buffer_output_midi(struct client *c, struct port *p, jack_nframes_t frames) +{ + return p->emptyptr; +} + +SPA_EXPORT +void * jack_port_get_buffer (jack_port_t *port, jack_nframes_t frames) +{ + struct object *o = (struct object *) port; + struct client *c; + struct port *p; + void *ptr = NULL; + + if (o == NULL) + return NULL; + + c = o->client; + + if (o->type != PW_TYPE_INTERFACE_Port || o->port.port_id == SPA_ID_INVALID) { + pw_log_error(NAME" %p: invalid port %p", c, port); + return NULL; + } + p = GET_PORT(c, GET_DIRECTION(o->port.flags), o->port.port_id); + + if (p->direction == SPA_DIRECTION_INPUT) { + switch (p->object->port.type_id) { + case 0: + ptr = get_buffer_input_float(c, p, frames); + break; + case 1: + ptr = get_buffer_input_midi(c, p, frames); + break; + case 2: + ptr = get_buffer_input_float(c, p, frames); + break; + } + if (ptr == NULL) { + ptr = p->emptyptr; + if (!p->zeroed) { + init_buffer(p, ptr, MAX_BUFFER_FRAMES); + p->zeroed = true; + } + } + } else { + switch (p->object->port.type_id) { + case 0: + ptr = get_buffer_output_float(c, p, frames); + break; + case 1: + ptr = get_buffer_output_midi(c, p, frames); + break; + case 2: + ptr = get_buffer_output_float(c, p, frames); + break; + } + } + + pw_log_trace(NAME" %p: port %p buffer %p", c, p, ptr); + return ptr; +} + +SPA_EXPORT +jack_uuid_t jack_port_uuid (const jack_port_t *port) +{ + struct object *o = (struct object *) port; + return jack_port_uuid_generate(o->id); +} + +SPA_EXPORT +const char * jack_port_name (const jack_port_t *port) +{ + struct object *o = (struct object *) port; + return o->port.name; +} + +SPA_EXPORT +const char * jack_port_short_name (const jack_port_t *port) +{ + struct object *o = (struct object *) port; + return strchr(o->port.name, ':') + 1; +} + +SPA_EXPORT +int jack_port_flags (const jack_port_t *port) +{ + struct object *o = (struct object *) port; + return o->port.flags; +} + +SPA_EXPORT +const char * jack_port_type (const jack_port_t *port) +{ + struct object *o = (struct object *) port; + return type_to_string(o->port.type_id); +} + +SPA_EXPORT +jack_port_type_id_t jack_port_type_id (const jack_port_t *port) +{ + struct object *o = (struct object *) port; + return o->port.type_id; +} + +SPA_EXPORT +int jack_port_is_mine (const jack_client_t *client, const jack_port_t *port) +{ + struct object *o = (struct object *) port; + return o->type == PW_TYPE_INTERFACE_Port && o->port.port_id != SPA_ID_INVALID; +} + +SPA_EXPORT +int jack_port_connected (const jack_port_t *port) +{ + struct object *o = (struct object *) port; + struct client *c = o->client; + struct object *l; + int res = 0; + + pw_thread_loop_lock(c->context.loop); + spa_list_for_each(l, &c->context.links, link) { + if (l->port_link.src == o->id || + l->port_link.dst == o->id) + res++; + } + pw_thread_loop_unlock(c->context.loop); + + return res; +} + +SPA_EXPORT +int jack_port_connected_to (const jack_port_t *port, + const char *port_name) +{ + struct object *o = (struct object *) port; + struct client *c = o->client; + struct object *p, *l; + int res = 0; + + pw_thread_loop_lock(c->context.loop); + + p = find_port(c, port_name); + if (p == NULL) + goto exit; + + if (GET_DIRECTION(p->port.flags) == GET_DIRECTION(o->port.flags)) + goto exit; + + if (p->port.flags & JackPortIsOutput) { + l = p; + p = o; + o = l; + } + if (find_link(c, o->id, p->id)) + res = 1; + + exit: + pw_thread_loop_unlock(c->context.loop); + + return res; +} + +SPA_EXPORT +const char ** jack_port_get_connections (const jack_port_t *port) +{ + struct object *o = (struct object *) port; + struct client *c = o->client; + + return jack_port_get_all_connections((jack_client_t *)c, port); +} + +SPA_EXPORT +const char ** jack_port_get_all_connections (const jack_client_t *client, + const jack_port_t *port) +{ + struct client *c = (struct client *) client; + struct object *o = (struct object *) port; + struct object *p, *l; + const char **res = malloc(sizeof(char*) * (CONNECTION_NUM_FOR_PORT + 1)); + int count = 0; + + pw_thread_loop_lock(c->context.loop); + + spa_list_for_each(l, &c->context.links, link) { + if (l->port_link.src == o->id) + p = pw_map_lookup(&c->context.globals, l->port_link.dst); + else if (l->port_link.dst == o->id) + p = pw_map_lookup(&c->context.globals, l->port_link.src); + else + continue; + + if (p == NULL) + continue; + + res[count++] = p->port.name; + if (count == CONNECTION_NUM_FOR_PORT) + break; + } + pw_thread_loop_unlock(c->context.loop); + + if (count == 0) { + free(res); + res = NULL; + } else + res[count] = NULL; + + return res; +} + +SPA_EXPORT +int jack_port_tie (jack_port_t *src, jack_port_t *dst) +{ + pw_log_warn("not implemented %p %p", src, dst); + return -ENOTSUP; +} + +SPA_EXPORT +int jack_port_untie (jack_port_t *port) +{ + pw_log_warn("not implemented %p", port); + return -ENOTSUP; +} + +SPA_EXPORT +int jack_port_set_name (jack_port_t *port, const char *port_name) +{ + pw_log_warn("deprecated"); + return 0; +} + +SPA_EXPORT +int jack_port_rename (jack_client_t* client, jack_port_t *port, const char *port_name) +{ + struct client *c = (struct client *) client; + struct object *o = (struct object *) port; + struct port *p; + struct spa_port_info port_info; + struct spa_dict dict; + struct spa_dict_item items[1]; + + pw_thread_loop_lock(c->context.loop); + + p = GET_PORT(c, GET_DIRECTION(o->port.flags), o->port.port_id); + + port_info = SPA_PORT_INFO_INIT(); + port_info.change_mask |= SPA_PORT_CHANGE_MASK_PROPS; + dict = SPA_DICT_INIT(items, 0); + items[dict.n_items++] = SPA_DICT_ITEM_INIT(PW_KEY_PORT_NAME, port_name); + port_info.props = &dict; + + pw_client_node_proxy_port_update(c->node_proxy, + p->direction, + p->id, + PW_CLIENT_NODE_PORT_UPDATE_INFO, + 0, NULL, + &port_info); + pw_thread_loop_unlock(c->context.loop); + + return 0; +} + +SPA_EXPORT +int jack_port_set_alias (jack_port_t *port, const char *alias) +{ + struct object *o = (struct object *) port; + struct client *c = o->client; + struct port *p; + struct spa_port_info port_info; + struct spa_dict dict; + struct spa_dict_item items[1]; + const char *key; + + if (c == NULL) + return -1; + + pw_thread_loop_lock(c->context.loop); + + if (o->port.alias1[0] == '\0') { + key = PW_KEY_OBJECT_PATH; + snprintf(o->port.alias1, sizeof(o->port.alias1), "%s", alias); + } + else if (o->port.alias2[0] == '\0') { + key = PW_KEY_PORT_ALIAS; + snprintf(o->port.alias2, sizeof(o->port.alias2), "%s", alias); + } + else + goto error; + + p = GET_PORT(c, GET_DIRECTION(o->port.flags), o->port.port_id); + + port_info = SPA_PORT_INFO_INIT(); + port_info.change_mask |= SPA_PORT_CHANGE_MASK_PROPS; + dict = SPA_DICT_INIT(items, 0); + items[dict.n_items++] = SPA_DICT_ITEM_INIT(key, alias); + port_info.props = &dict; + + pw_client_node_proxy_port_update(c->node_proxy, + p->direction, + p->id, + PW_CLIENT_NODE_PORT_UPDATE_INFO, + 0, NULL, + &port_info); + pw_thread_loop_unlock(c->context.loop); + + return 0; + +error: + pw_thread_loop_unlock(c->context.loop); + return -1; +} + +SPA_EXPORT +int jack_port_unset_alias (jack_port_t *port, const char *alias) +{ + struct object *o = (struct object *) port; + struct client *c = o->client; + struct port *p; + struct spa_port_info port_info; + struct spa_dict dict; + struct spa_dict_item items[1]; + const char *key; + + if (c == NULL) + return -1; + + pw_thread_loop_lock(c->context.loop); + + if (strcmp(o->port.alias1, alias) == 0) + key = PW_KEY_OBJECT_PATH; + else if (strcmp(o->port.alias2, alias) == 0) + key = PW_KEY_PORT_ALIAS; + else + goto error; + + p = GET_PORT(c, GET_DIRECTION(o->port.flags), o->port.port_id); + + port_info = SPA_PORT_INFO_INIT(); + port_info.change_mask |= SPA_PORT_CHANGE_MASK_PROPS; + dict = SPA_DICT_INIT(items, 0); + items[dict.n_items++] = SPA_DICT_ITEM_INIT(key, NULL); + port_info.props = &dict; + + pw_client_node_proxy_port_update(c->node_proxy, + p->direction, + p->id, + PW_CLIENT_NODE_PORT_UPDATE_INFO, + 0, NULL, + &port_info); + pw_thread_loop_unlock(c->context.loop); + + return 0; + +error: + pw_thread_loop_unlock(c->context.loop); + return -1; +} + +SPA_EXPORT +int jack_port_get_aliases (const jack_port_t *port, char* const aliases[2]) +{ + struct object *o = (struct object *) port; + struct client *c = o->client; + int res = 0; + + pw_thread_loop_lock(c->context.loop); + + if (o->port.alias1[0] != '\0') { + snprintf(aliases[0], REAL_JACK_PORT_NAME_SIZE+1, "%s", o->port.alias1); + res++; + } + if (o->port.alias2[0] != '\0') { + snprintf(aliases[1], REAL_JACK_PORT_NAME_SIZE+1, "%s", o->port.alias2); + res++; + } + pw_thread_loop_unlock(c->context.loop); + + return res; +} + +SPA_EXPORT +int jack_port_request_monitor (jack_port_t *port, int onoff) +{ + struct object *o = (struct object *) port; + if (onoff) + o->port.monitor_requests++; + else if (o->port.monitor_requests > 0) + o->port.monitor_requests--; + return 0; +} + +SPA_EXPORT +int jack_port_request_monitor_by_name (jack_client_t *client, + const char *port_name, int onoff) +{ + struct client *c = (struct client *) client; + struct object *p; + + pw_thread_loop_lock(c->context.loop); + + p = find_port(c, port_name); + + pw_thread_loop_unlock(c->context.loop); + + if (p == NULL) { + pw_log_error(NAME" %p: jack_port_request_monitor_by_name called" + " with an incorrect port %s", client, port_name); + return -1; + } + + return jack_port_request_monitor((jack_port_t*)p, onoff); +} + +SPA_EXPORT +int jack_port_ensure_monitor (jack_port_t *port, int onoff) +{ + struct object *o = (struct object *) port; + if (onoff) { + if (o->port.monitor_requests == 0) + o->port.monitor_requests++; + } else { + if (o->port.monitor_requests > 0) + o->port.monitor_requests = 0; + } + return 0; +} + +SPA_EXPORT +int jack_port_monitoring_input (jack_port_t *port) +{ + struct object *o = (struct object *) port; + return o->port.monitor_requests > 0; +} + +SPA_EXPORT +int jack_connect (jack_client_t *client, + const char *source_port, + const char *destination_port) +{ + struct client *c = (struct client *) client; + struct object *src, *dst; + struct spa_dict props; + struct spa_dict_item items[5]; + char val[4][16]; + int res; + + pw_log_debug(NAME" %p: connect %s %s", client, source_port, destination_port); + + pw_thread_loop_lock(c->context.loop); + + src = find_port(c, source_port); + dst = find_port(c, destination_port); + + if (src == NULL || dst == NULL || + !(src->port.flags & JackPortIsOutput) || + !(dst->port.flags & JackPortIsInput) || + src->port.type_id != dst->port.type_id) { + res = -EINVAL; + goto exit; + } + + snprintf(val[0], sizeof(val[0]), "%d", src->port.node_id); + snprintf(val[1], sizeof(val[1]), "%d", src->id); + snprintf(val[2], sizeof(val[2]), "%d", dst->port.node_id); + snprintf(val[3], sizeof(val[3]), "%d", dst->id); + + props = SPA_DICT_INIT(items, 0); + items[props.n_items++] = SPA_DICT_ITEM_INIT(PW_KEY_LINK_OUTPUT_NODE, val[0]); + items[props.n_items++] = SPA_DICT_ITEM_INIT(PW_KEY_LINK_OUTPUT_PORT, val[1]); + items[props.n_items++] = SPA_DICT_ITEM_INIT(PW_KEY_LINK_INPUT_NODE, val[2]); + items[props.n_items++] = SPA_DICT_ITEM_INIT(PW_KEY_LINK_INPUT_PORT, val[3]); + items[props.n_items++] = SPA_DICT_ITEM_INIT(PW_KEY_OBJECT_LINGER, "1"); + + pw_core_proxy_create_object(c->core_proxy, + "link-factory", + PW_TYPE_INTERFACE_Link, + PW_VERSION_LINK_PROXY, + &props, + 0); + res = do_sync(c); + + exit: + pw_thread_loop_unlock(c->context.loop); + + return res; +} + +SPA_EXPORT +int jack_disconnect (jack_client_t *client, + const char *source_port, + const char *destination_port) +{ + struct client *c = (struct client *) client; + struct object *src, *dst, *l; + int res; + + pw_log_debug(NAME" %p: disconnect %s %s", client, source_port, destination_port); + + pw_thread_loop_lock(c->context.loop); + + src = find_port(c, source_port); + dst = find_port(c, destination_port); + + pw_log_debug(NAME" %p: %d %d", client, src->id, dst->id); + + if (src == NULL || dst == NULL || + !(src->port.flags & JackPortIsOutput) || + !(dst->port.flags & JackPortIsInput)) { + res = -EINVAL; + goto exit; + } + + if ((l = find_link(c, src->id, dst->id)) == NULL) { + res = -ENOENT; + goto exit; + } + + pw_registry_proxy_destroy(c->registry_proxy, l->id); + + res = do_sync(c); + + exit: + pw_thread_loop_unlock(c->context.loop); + + return res; +} + +SPA_EXPORT +int jack_port_disconnect (jack_client_t *client, jack_port_t *port) +{ + struct client *c = (struct client *) client; + struct object *o = (struct object *) port; + struct object *l; + int res; + + pw_log_debug(NAME" %p: disconnect %p", client, port); + + pw_thread_loop_lock(c->context.loop); + + spa_list_for_each(l, &c->context.links, link) { + if (l->port_link.src == o->id || + l->port_link.dst == o->id) { + pw_registry_proxy_destroy(c->registry_proxy, l->id); + } + } + res = do_sync(c); + + pw_thread_loop_unlock(c->context.loop); + + return res; +} + +SPA_EXPORT +int jack_port_name_size(void) +{ + return REAL_JACK_PORT_NAME_SIZE+1; +} + +SPA_EXPORT +int jack_port_type_size(void) +{ + return JACK_PORT_TYPE_SIZE+1; +} + +SPA_EXPORT +size_t jack_port_type_get_buffer_size (jack_client_t *client, const char *port_type) +{ + if (!strcmp(JACK_DEFAULT_AUDIO_TYPE, port_type)) + return jack_get_buffer_size(client) * sizeof(float); + else if (!strcmp(JACK_DEFAULT_MIDI_TYPE, port_type)) + return MAX_BUFFER_FRAMES * sizeof(float); + else if (!strcmp(JACK_DEFAULT_VIDEO_TYPE, port_type)) + return 320 * 240 * 4 * sizeof(float); + else + return 0; +} + +SPA_EXPORT +void jack_port_set_latency (jack_port_t *port, jack_nframes_t frames) +{ + struct object *o = (struct object *) port; + jack_latency_range_t range = { frames, frames }; + if (o->port.flags & JackPortIsOutput) { + jack_port_set_latency_range(port, JackCaptureLatency, &range); + } + if (o->port.flags & JackPortIsInput) { + jack_port_set_latency_range(port, JackPlaybackLatency, &range); + } +} + +SPA_EXPORT +void jack_port_get_latency_range (jack_port_t *port, jack_latency_callback_mode_t mode, jack_latency_range_t *range) +{ + struct object *o = (struct object *) port; + if (mode == JackCaptureLatency) { + *range = o->port.capture_latency; + } else { + *range = o->port.playback_latency; + } +} + +SPA_EXPORT +void jack_port_set_latency_range (jack_port_t *port, jack_latency_callback_mode_t mode, jack_latency_range_t *range) +{ + struct object *o = (struct object *) port; + if (mode == JackCaptureLatency) { + o->port.capture_latency = *range; + } else { + o->port.playback_latency = *range; + } +} + +SPA_EXPORT +int jack_recompute_total_latencies (jack_client_t *client) +{ + pw_log_warn(NAME" %p: not implemented", client); + return 0; +} + +SPA_EXPORT +jack_nframes_t jack_port_get_latency (jack_port_t *port) +{ + struct object *o = (struct object *) port; + jack_latency_range_t range; + if (o->port.flags & JackPortIsOutput) { + jack_port_get_latency_range(port, JackCaptureLatency, &range); + } + if (o->port.flags & JackPortIsInput) { + jack_port_get_latency_range(port, JackPlaybackLatency, &range); + } + return (range.min + range.max) / 2; +} + +SPA_EXPORT +jack_nframes_t jack_port_get_total_latency (jack_client_t *client, + jack_port_t *port) +{ + pw_log_warn(NAME" %p: not implemented %p", client, port); + return 0; +} + +SPA_EXPORT +int jack_recompute_total_latency (jack_client_t *client, jack_port_t* port) +{ + pw_log_warn(NAME" %p: not implemented %p", client, port); + return 0; +} + +static int port_compare_func(const void *v1, const void *v2) +{ + const struct object *const*o1 = v1, *const*o2 = v2; + + if ((*o1)->port.type_id != (*o2)->port.type_id) + return (*o1)->port.type_id - (*o2)->port.type_id; + + if ((*o1)->port.priority != (*o2)->port.priority) + return (*o2)->port.priority - (*o1)->port.priority; + + return (*o1)->id - (*o2)->id; +} + +SPA_EXPORT +const char ** jack_get_ports (jack_client_t *client, + const char *port_name_pattern, + const char *type_name_pattern, + unsigned long flags) +{ + struct client *c = (struct client *) client; + const char **res; + struct object *o; + struct object *tmp[JACK_PORT_MAX]; + const char *str; + uint32_t i, count, id; + regex_t port_regex, type_regex; + + if ((str = getenv("PIPEWIRE_NODE")) != NULL) + id = pw_properties_parse_int(str); + else + id = SPA_ID_INVALID; + + if (port_name_pattern && port_name_pattern[0]) + regcomp(&port_regex, port_name_pattern, REG_EXTENDED | REG_NOSUB); + if (type_name_pattern && type_name_pattern[0]) + regcomp(&type_regex, type_name_pattern, REG_EXTENDED | REG_NOSUB); + + pw_thread_loop_lock(c->context.loop); + + pw_log_debug(NAME" %p: ports id:%d name:%s type:%s flags:%08lx", c, id, + port_name_pattern, type_name_pattern, flags); + + count = 0; + spa_list_for_each(o, &c->context.ports, link) { + pw_log_debug(NAME" %p: check port type:%d flags:%08lx name:%s", c, + o->port.type_id, o->port.flags, o->port.name); + if (count == JACK_PORT_MAX) + break; + if (o->port.type_id > 2) + continue; + if (!SPA_FLAG_IS_SET(o->port.flags, flags)) + continue; + if (id != SPA_ID_INVALID && o->port.node_id != id) + continue; + + if (port_name_pattern && port_name_pattern[0]) { + if (regexec(&port_regex, o->port.name, 0, NULL, 0) == REG_NOMATCH) + continue; + } + if (type_name_pattern && type_name_pattern[0]) { + if (regexec(&type_regex, type_to_string(o->port.type_id), + 0, NULL, 0) == REG_NOMATCH) + continue; + } + + pw_log_debug(NAME" %p: port %s prio:%d matches (%d)", + c, o->port.name, o->port.priority, count); + tmp[count++] = o; + } + if (count > 0) { + qsort(tmp, count, sizeof(struct object *), port_compare_func); + + res = malloc(sizeof(char*) * (count + 1)); + for (i = 0; i < count; i++) + res[i] = tmp[i]->port.name; + res[count] = NULL; + } else { + res = NULL; + } + + pw_thread_loop_unlock(c->context.loop); + + if (port_name_pattern && port_name_pattern[0]) + regfree(&port_regex); + if (type_name_pattern && type_name_pattern[0]) + regfree(&type_regex); + + return res; +} + +SPA_EXPORT +jack_port_t * jack_port_by_name (jack_client_t *client, const char *port_name) +{ + struct client *c = (struct client *) client; + struct object *res; + + pw_thread_loop_lock(c->context.loop); + + res = find_port(c, port_name); + + pw_thread_loop_unlock(c->context.loop); + + return (jack_port_t *)res; +} + +SPA_EXPORT +jack_port_t * jack_port_by_id (jack_client_t *client, + jack_port_id_t port_id) +{ + struct client *c = (struct client *) client; + struct object *res = NULL, *o; + + pw_thread_loop_lock(c->context.loop); + + o = pw_map_lookup(&c->context.globals, port_id); + pw_log_debug(NAME" %p: port %d -> %p", c, port_id, o); + + if (o == NULL || o->type != PW_TYPE_INTERFACE_Port) + goto exit; + + res = o; + + exit: + pw_thread_loop_unlock(c->context.loop); + + return (jack_port_t *)res; +} + +SPA_EXPORT +jack_nframes_t jack_frames_since_cycle_start (const jack_client_t *client) +{ + struct client *c = (struct client *) client; + struct spa_io_position *pos = c->position; + struct timespec ts; + uint64_t diff; + + if (pos == NULL) + return 0; + + clock_gettime(CLOCK_MONOTONIC, &ts); + diff = SPA_TIMESPEC_TO_NSEC(&ts) - pos->clock.nsec; + return (jack_nframes_t) floor(((float)c->sample_rate * diff) / SPA_NSEC_PER_SEC); +} + +SPA_EXPORT +jack_nframes_t jack_frame_time (const jack_client_t *client) +{ + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return jack_time_to_frames(client, SPA_TIMESPEC_TO_USEC(&ts)); +} + +SPA_EXPORT +jack_nframes_t jack_last_frame_time (const jack_client_t *client) +{ + struct client *c = (struct client *) client; + struct spa_io_position *pos = c->position; + + if (pos == NULL) + return 0; + + return pos->clock.position; +} + +SPA_EXPORT +int jack_get_cycle_times(const jack_client_t *client, + jack_nframes_t *current_frames, + jack_time_t *current_usecs, + jack_time_t *next_usecs, + float *period_usecs) +{ + struct client *c = (struct client *) client; + struct spa_io_position *pos = c->position; + + if (pos == NULL) + return -1; + + *current_frames = pos->clock.position; + *current_usecs = pos->clock.nsec / SPA_NSEC_PER_USEC; + *period_usecs = pos->clock.duration * (float)SPA_USEC_PER_SEC / (c->sample_rate * pos->clock.rate_diff); + *next_usecs = pos->clock.next_nsec / SPA_NSEC_PER_USEC; + + pw_log_trace(NAME" %p: %d %"PRIu64" %"PRIu64" %f", c, *current_frames, + *current_usecs, *next_usecs, *period_usecs); + return 0; +} + +SPA_EXPORT +jack_time_t jack_frames_to_time(const jack_client_t *client, jack_nframes_t frames) +{ + struct client *c = (struct client *) client; + struct spa_io_position *pos = c->position; + double df; + + if (pos == NULL) + return 0; + + df = (frames - pos->clock.position) * (double)SPA_NSEC_PER_SEC / c->sample_rate; + return (pos->clock.nsec + (int64_t)rint(df)) / SPA_NSEC_PER_USEC; +} + +SPA_EXPORT +jack_nframes_t jack_time_to_frames(const jack_client_t *client, jack_time_t usecs) +{ + struct client *c = (struct client *) client; + struct spa_io_position *pos = c->position; + double du; + + if (pos == NULL) + return 0; + + du = (usecs - pos->clock.nsec/SPA_NSEC_PER_USEC) * (double)c->sample_rate / SPA_USEC_PER_SEC; + return pos->clock.position + (int32_t)rint(du); +} + +SPA_EXPORT +jack_time_t jack_get_time() +{ + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return SPA_TIMESPEC_TO_USEC(&ts); +} + +SPA_EXPORT +void jack_set_error_function (void (*func)(const char *)) +{ + pw_log_warn("not implemented"); +} + +SPA_EXPORT +void jack_set_info_function (void (*func)(const char *)) +{ + pw_log_warn("not implemented"); +} + +SPA_EXPORT +void jack_free(void* ptr) +{ + free(ptr); +} + +SPA_EXPORT +int jack_release_timebase (jack_client_t *client) +{ + struct client *c = (struct client *) client; + struct pw_node_activation *a = c->driver_activation; + + if (a == NULL) + return -EIO; + + if (!ATOMIC_CAS(a->segment_owner[0], c->node_id, 0)) + return -EINVAL; + + c->timebase_callback = NULL; + c->timebase_arg = NULL; + c->activation->pending_new_pos = false; + + return 0; +} + +SPA_EXPORT +int jack_set_sync_callback (jack_client_t *client, + JackSyncCallback sync_callback, + void *arg) +{ + int res; + struct client *c = (struct client *) client; + + c->sync_callback = sync_callback; + c->sync_arg = arg; + + if ((res = do_activate(c)) < 0) + return res; + + c->activation->pending_sync = true; + return 0; +} + +SPA_EXPORT +int jack_set_sync_timeout (jack_client_t *client, + jack_time_t timeout) +{ + struct client *c = (struct client *) client; + struct pw_node_activation *a = c->driver_activation; + + if (a == NULL) + return -EIO; + + ATOMIC_STORE(a->sync_timeout, timeout); + + return 0; +} + +SPA_EXPORT +int jack_set_timebase_callback (jack_client_t *client, + int conditional, + JackTimebaseCallback timebase_callback, + void *arg) +{ + int res; + struct client *c = (struct client *) client; + struct pw_node_activation *a = c->driver_activation; + uint32_t owner; + + pw_log_debug(NAME" %p: activation %p", c, a); + + if (a == NULL) + return -EIO; + + /* was ok */ + owner = ATOMIC_LOAD(a->segment_owner[0]); + if (owner == c->node_id) + return 0; + + /* try to become master */ + if (conditional) { + if (!ATOMIC_CAS(a->segment_owner[0], 0, c->node_id)) { + pw_log_debug(NAME" %p: owner:%u id:%u", c, owner, c->node_id); + return -EBUSY; + } + } else { + ATOMIC_STORE(a->segment_owner[0], c->node_id); + } + + c->timebase_callback = timebase_callback; + c->timebase_arg = arg; + + pw_log_debug(NAME" %p: timebase set id:%u", c, c->node_id); + + if ((res = do_activate(c)) < 0) + return res; + + c->activation->pending_new_pos = true; + + return 0; +} + +SPA_EXPORT +int jack_transport_locate (jack_client_t *client, + jack_nframes_t frame) +{ + jack_position_t pos; + pos.frame = frame; + pos.valid = (jack_position_bits_t)0; + return jack_transport_reposition(client, &pos); +} + +SPA_EXPORT +jack_transport_state_t jack_transport_query (const jack_client_t *client, + jack_position_t *pos) +{ + struct client *c = (struct client *) client; + struct pw_node_activation *a = c->driver_activation; + jack_transport_state_t jack_state = JackTransportStopped; + + if (a != NULL) + jack_state = position_to_jack(a, pos); + else if (pos != NULL) + memset(pos, 0, sizeof(jack_position_t)); + + return jack_state; +} + +SPA_EXPORT +jack_nframes_t jack_get_current_transport_frame (const jack_client_t *client) +{ + struct client *c = (struct client *) client; + struct pw_node_activation *a = c->driver_activation; + struct spa_io_position *pos; + struct spa_io_segment *seg; + uint64_t running; + if (!a) + return -1; + + pos = &a->position; + running = pos->clock.position - pos->offset; + + if (pos->state == SPA_IO_POSITION_STATE_RUNNING) { + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + uint64_t nsecs = SPA_TIMESPEC_TO_NSEC(&ts) - pos->clock.nsec; + running += (uint64_t)floor((((float) c->sample_rate) / SPA_NSEC_PER_SEC) * nsecs); + } + seg = &pos->segments[0]; + + return (running - seg->start) * seg->rate + seg->position; +} + +SPA_EXPORT +int jack_transport_reposition (jack_client_t *client, + const jack_position_t *pos) +{ + struct client *c = (struct client *) client; + struct pw_node_activation *a = c->driver_activation; + struct pw_node_activation *na = c->activation; + if (!a || !na) + return -EIO; + + if (pos->valid & ~(JackPositionBBT|JackPositionTimecode)) + return -EINVAL; + + pw_log_debug("frame:%u", pos->frame); + na->reposition.flags = 0; + na->reposition.start = 0; + na->reposition.duration = 0; + na->reposition.position = pos->frame; + na->reposition.rate = 1.0; + ATOMIC_STORE(a->reposition_owner, c->node_id); + + return 0; +} + +static void update_command(struct client *c, uint32_t command) +{ + struct pw_node_activation *a = c->driver_activation; + if (!a) + return; + ATOMIC_STORE(a->command, command); +} + +SPA_EXPORT +void jack_transport_start (jack_client_t *client) +{ + struct client *c = (struct client *) client; + update_command(c, PW_NODE_ACTIVATION_COMMAND_START); +} + +SPA_EXPORT +void jack_transport_stop (jack_client_t *client) +{ + struct client *c = (struct client *) client; + update_command(c, PW_NODE_ACTIVATION_COMMAND_STOP); +} + +SPA_EXPORT +void jack_get_transport_info (jack_client_t *client, + jack_transport_info_t *tinfo) +{ + pw_log_error(NAME" %p: deprecated", client); + if (tinfo) + memset(tinfo, 0, sizeof(jack_transport_info_t)); +} + +SPA_EXPORT +void jack_set_transport_info (jack_client_t *client, + jack_transport_info_t *tinfo) +{ + pw_log_error(NAME" %p: deprecated", client); + if (tinfo) + memset(tinfo, 0, sizeof(jack_transport_info_t)); +} + +SPA_EXPORT +int jack_set_session_callback (jack_client_t *client, + JackSessionCallback session_callback, + void *arg) +{ + struct client *c = (struct client *) client; + if (c->active) { + pw_log_error(NAME" %p: can't set callback on active client", c); + return -EIO; + } + pw_log_warn(NAME" %p: not implemented", client); + return -ENOTSUP; +} + +SPA_EXPORT +int jack_session_reply (jack_client_t *client, + jack_session_event_t *event) +{ + pw_log_warn(NAME" %p: not implemented", client); + return -ENOTSUP; +} + + +SPA_EXPORT +void jack_session_event_free (jack_session_event_t *event) +{ + pw_log_warn("not implemented"); +} + +SPA_EXPORT +char *jack_client_get_uuid (jack_client_t *client) +{ + struct client *c = (struct client *) client; + char *uuid = NULL; + asprintf(&uuid, "%d", c->node_id); + return uuid; +} + +SPA_EXPORT +int jack_client_real_time_priority (jack_client_t * client) +{ + return 20; +} + +SPA_EXPORT +int jack_client_max_real_time_priority (jack_client_t *client) +{ + return 20; +} + +SPA_EXPORT +int jack_acquire_real_time_scheduling (jack_native_thread_t thread, int priority) +{ + pw_log_warn("not implemented %lu %d", thread, priority); + return -ENOTSUP; +} + +/** + * Create a thread for JACK or one of its clients. The thread is + * created executing @a start_routine with @a arg as its sole + * argument. + * + * @param client the JACK client for whom the thread is being created. May be + * NULL if the client is being created within the JACK server. + * @param thread place to return POSIX thread ID. + * @param priority thread priority, if realtime. + * @param realtime true for the thread to use realtime scheduling. On + * some systems that may require special privileges. + * @param start_routine function the thread calls when it starts. + * @param arg parameter passed to the @a start_routine. + * + * @returns 0, if successful; otherwise some error number. + */ +SPA_EXPORT +int jack_client_create_thread (jack_client_t* client, + jack_native_thread_t *thread, + int priority, + int realtime, /* boolean */ + void *(*start_routine)(void*), + void *arg) +{ + if (globals.creator == NULL) + globals.creator = pthread_create; + + pw_log_info("client %p: create thread", client); + return globals.creator(thread, NULL, start_routine, arg); +} + +SPA_EXPORT +int jack_drop_real_time_scheduling (jack_native_thread_t thread) +{ + pw_log_warn("not implemented %lu", thread); + return -ENOTSUP; +} + +SPA_EXPORT +int jack_client_stop_thread(jack_client_t* client, jack_native_thread_t thread) +{ + void* status; + + if (thread == (jack_native_thread_t)NULL) + return -1; + + pw_log_debug("join thread %lu", thread); + pthread_join(thread, &status); + pw_log_debug("stopped thread %lu", thread); + return 0; +} + +SPA_EXPORT +int jack_client_kill_thread(jack_client_t* client, jack_native_thread_t thread) +{ + void* status; + + if (thread == (jack_native_thread_t)NULL) + return -1; + + pw_log_debug("cancel thread %lu", thread); + pthread_cancel(thread); + pw_log_debug("join thread %lu", thread); + pthread_join(thread, &status); + pw_log_debug("stopped thread %lu", thread); + return 0; +} + +SPA_EXPORT +void jack_set_thread_creator (jack_thread_creator_t creator) +{ + if (creator == NULL) + globals.creator = pthread_create; + else + globals.creator = creator; +} + +static inline uint8_t * midi_event_data (void* port_buffer, + const struct midi_event* event) +{ + if (event->size <= MIDI_INLINE_MAX) + return (uint8_t *)event->inline_data; + else + return SPA_MEMBER(port_buffer, event->byte_offset, uint8_t); +} + +SPA_EXPORT +uint32_t jack_midi_get_event_count(void* port_buffer) +{ + struct midi_buffer *mb = port_buffer; + return mb->event_count; +} + +SPA_EXPORT +int jack_midi_event_get(jack_midi_event_t *event, + void *port_buffer, + uint32_t event_index) +{ + struct midi_buffer *mb = port_buffer; + struct midi_event *ev = SPA_MEMBER(mb, sizeof(*mb), struct midi_event); + ev += event_index; + event->time = ev->time; + event->size = ev->size; + event->buffer = midi_event_data (port_buffer, ev); + return 0; +} + +SPA_EXPORT +void jack_midi_clear_buffer(void *port_buffer) +{ + struct midi_buffer *mb = port_buffer; + mb->event_count = 0; + mb->write_pos = 0; + mb->lost_events = 0; +} + +SPA_EXPORT +void jack_midi_reset_buffer(void *port_buffer) +{ + jack_midi_clear_buffer(port_buffer); +} + +SPA_EXPORT +size_t jack_midi_max_event_size(void* port_buffer) +{ + struct midi_buffer *mb = port_buffer; + size_t buffer_size = mb->buffer_size; + + /* (event_count + 1) below accounts for jack_midi_port_internal_event_t + * which would be needed to store the next event */ + size_t used_size = sizeof(struct midi_buffer) + + mb->write_pos + + ((mb->event_count + 1) + * sizeof(struct midi_event)); + + if (used_size > buffer_size) { + return 0; + } else if ((buffer_size - used_size) < MIDI_INLINE_MAX) { + return MIDI_INLINE_MAX; + } else { + return buffer_size - used_size; + } +} + +SPA_EXPORT +jack_midi_data_t* jack_midi_event_reserve(void *port_buffer, + jack_nframes_t time, + size_t data_size) +{ + struct midi_buffer *mb = port_buffer; + struct midi_event *events = SPA_MEMBER(mb, sizeof(*mb), struct midi_event); + size_t buffer_size = mb->buffer_size; + + if (time < 0 || time >= mb->nframes) { + pw_log_warn("midi %p: time:%d frames:%d", port_buffer, time, mb->nframes); + goto failed; + } + + if (mb->event_count > 0 && time < events[mb->event_count - 1].time) { + pw_log_warn("midi %p: time:%d ev:%d", port_buffer, time, mb->event_count); + goto failed; + } + + /* Check if data_size is >0 and there is enough space in the buffer for the event. */ + if (data_size <= 0) { + pw_log_warn("midi %p: data_size:%zd", port_buffer, data_size); + goto failed; // return NULL? + } else if (jack_midi_max_event_size (port_buffer) < data_size) { + pw_log_warn("midi %p: event too large: data_size:%zd", port_buffer, data_size); + goto failed; + } else { + struct midi_event *ev = &events[mb->event_count]; + uint8_t *res; + + ev->time = time; + ev->size = data_size; + if (data_size <= MIDI_INLINE_MAX) { + res = ev->inline_data; + } else { + mb->write_pos += data_size; + ev->byte_offset = buffer_size - 1 - mb->write_pos; + res = SPA_MEMBER(mb, ev->byte_offset, uint8_t); + } + mb->event_count += 1; + return res; + } +failed: + mb->lost_events++; + return NULL; +} + +SPA_EXPORT +int jack_midi_event_write(void *port_buffer, + jack_nframes_t time, + const jack_midi_data_t *data, + size_t data_size) +{ + jack_midi_data_t *retbuf = jack_midi_event_reserve (port_buffer, time, data_size); + if (retbuf) { + memcpy (retbuf, data, data_size); + return 0; + } else { + return ENOBUFS; + } +} + +SPA_EXPORT +uint32_t jack_midi_get_lost_event_count(void *port_buffer) +{ + struct midi_buffer *mb = port_buffer; + return mb->lost_events; +} + +static void reg(void) __attribute__ ((constructor)); +static void reg(void) +{ + pw_init(NULL, NULL); +} diff --git a/pipewire-jack/src/ringbuffer.c b/pipewire-jack/src/ringbuffer.c new file mode 100644 index 000000000..f76e38620 --- /dev/null +++ b/pipewire-jack/src/ringbuffer.c @@ -0,0 +1,299 @@ +/* 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 +#include +#include +#include + +#include + +#include + +SPA_EXPORT +jack_ringbuffer_t *jack_ringbuffer_create(size_t sz) +{ + size_t power_of_two; + jack_ringbuffer_t *rb; + + rb = calloc(1, sizeof(jack_ringbuffer_t)); + if (rb == NULL) + return NULL; + + for (power_of_two = 1; 1u << power_of_two < sz; power_of_two++); + + rb->size = 1 << power_of_two; + rb->size_mask = rb->size - 1; + if ((rb->buf = calloc(1, rb->size)) == NULL) { + free (rb); + return NULL; + } + rb->mlocked = 0; + + return rb; +} + +SPA_EXPORT +void jack_ringbuffer_free(jack_ringbuffer_t *rb) +{ +#ifdef USE_MLOCK + if (rb->mlocked) + munlock (rb->buf, rb->size); +#endif /* USE_MLOCK */ + free (rb->buf); + free (rb); +} + +SPA_EXPORT +void jack_ringbuffer_get_read_vector(const jack_ringbuffer_t *rb, + jack_ringbuffer_data_t *vec) +{ + size_t free_cnt; + size_t cnt2; + size_t w, r; + + w = rb->write_ptr; + r = rb->read_ptr; + + if (w > r) + free_cnt = w - r; + else + free_cnt = (w - r + rb->size) & rb->size_mask; + + cnt2 = r + free_cnt; + + if (cnt2 > rb->size) { + vec[0].buf = &(rb->buf[r]); + vec[0].len = rb->size - r; + vec[1].buf = rb->buf; + vec[1].len = cnt2 & rb->size_mask; + } else { + vec[0].buf = &(rb->buf[r]); + vec[0].len = free_cnt; + vec[1].len = 0; + } +} + +SPA_EXPORT +void jack_ringbuffer_get_write_vector(const jack_ringbuffer_t *rb, + jack_ringbuffer_data_t *vec) +{ + size_t free_cnt; + size_t cnt2; + size_t w, r; + + w = rb->write_ptr; + r = rb->read_ptr; + + if (w > r) + free_cnt = ((r - w + rb->size) & rb->size_mask) - 1; + else if (w < r) + free_cnt = (r - w) - 1; + else + free_cnt = rb->size - 1; + + cnt2 = w + free_cnt; + + if (cnt2 > rb->size) { + vec[0].buf = &(rb->buf[w]); + vec[0].len = rb->size - w; + vec[1].buf = rb->buf; + vec[1].len = cnt2 & rb->size_mask; + } else { + vec[0].buf = &(rb->buf[w]); + vec[0].len = free_cnt; + vec[1].len = 0; + } +} + +SPA_EXPORT +size_t jack_ringbuffer_read(jack_ringbuffer_t *rb, char *dest, size_t cnt) +{ + size_t free_cnt; + size_t cnt2; + size_t to_read; + size_t n1, n2; + + if ((free_cnt = jack_ringbuffer_read_space (rb)) == 0) + return 0; + + to_read = cnt > free_cnt ? free_cnt : cnt; + + cnt2 = rb->read_ptr + to_read; + + if (cnt2 > rb->size) { + n1 = rb->size - rb->read_ptr; + n2 = cnt2 & rb->size_mask; + } else { + n1 = to_read; + n2 = 0; + } + + memcpy (dest, &(rb->buf[rb->read_ptr]), n1); + rb->read_ptr = (rb->read_ptr + n1) & rb->size_mask; + if (n2) { + memcpy (dest + n1, &(rb->buf[rb->read_ptr]), n2); + rb->read_ptr = (rb->read_ptr + n2) & rb->size_mask; + } + return to_read; +} + +SPA_EXPORT +size_t jack_ringbuffer_peek(jack_ringbuffer_t *rb, char *dest, size_t cnt) +{ + size_t free_cnt; + size_t cnt2; + size_t to_read; + size_t n1, n2; + size_t tmp_read_ptr; + + tmp_read_ptr = rb->read_ptr; + + if ((free_cnt = jack_ringbuffer_read_space (rb)) == 0) + return 0; + + to_read = cnt > free_cnt ? free_cnt : cnt; + + cnt2 = tmp_read_ptr + to_read; + + if (cnt2 > rb->size) { + n1 = rb->size - tmp_read_ptr; + n2 = cnt2 & rb->size_mask; + } else { + n1 = to_read; + n2 = 0; + } + + memcpy (dest, &(rb->buf[tmp_read_ptr]), n1); + tmp_read_ptr = (tmp_read_ptr + n1) & rb->size_mask; + + if (n2) + memcpy (dest + n1, &(rb->buf[tmp_read_ptr]), n2); + + return to_read; +} + +SPA_EXPORT +void jack_ringbuffer_read_advance(jack_ringbuffer_t *rb, size_t cnt) +{ + size_t tmp = (rb->read_ptr + cnt) & rb->size_mask; + rb->read_ptr = tmp; +} + +SPA_EXPORT +size_t jack_ringbuffer_read_space(const jack_ringbuffer_t *rb) +{ + size_t w, r; + + w = rb->write_ptr; + r = rb->read_ptr; + + if (w > r) + return w - r; + else + return (w - r + rb->size) & rb->size_mask; +} + +SPA_EXPORT +int jack_ringbuffer_mlock(jack_ringbuffer_t *rb) +{ +#ifdef USE_MLOCK + if (mlock (rb->buf, rb->size)) + return -1; +#endif /* USE_MLOCK */ + rb->mlocked = 1; + return 0; +} + +SPA_EXPORT +void jack_ringbuffer_reset(jack_ringbuffer_t *rb) +{ + rb->read_ptr = 0; + rb->write_ptr = 0; + memset(rb->buf, 0, rb->size); +} + +SPA_EXPORT +void jack_ringbuffer_reset_size (jack_ringbuffer_t * rb, size_t sz) +{ + rb->size = sz; + rb->size_mask = rb->size - 1; + rb->read_ptr = 0; + rb->write_ptr = 0; +} + +SPA_EXPORT +size_t jack_ringbuffer_write(jack_ringbuffer_t *rb, const char *src, + size_t cnt) +{ + size_t free_cnt; + size_t cnt2; + size_t to_write; + size_t n1, n2; + + if ((free_cnt = jack_ringbuffer_write_space (rb)) == 0) + return 0; + + to_write = cnt > free_cnt ? free_cnt : cnt; + + cnt2 = rb->write_ptr + to_write; + + if (cnt2 > rb->size) { + n1 = rb->size - rb->write_ptr; + n2 = cnt2 & rb->size_mask; + } else { + n1 = to_write; + n2 = 0; + } + + memcpy (&(rb->buf[rb->write_ptr]), src, n1); + rb->write_ptr = (rb->write_ptr + n1) & rb->size_mask; + if (n2) { + memcpy (&(rb->buf[rb->write_ptr]), src + n1, n2); + rb->write_ptr = (rb->write_ptr + n2) & rb->size_mask; + } + return to_write; +} + +SPA_EXPORT +void jack_ringbuffer_write_advance(jack_ringbuffer_t *rb, size_t cnt) +{ + size_t tmp = (rb->write_ptr + cnt) & rb->size_mask; + rb->write_ptr = tmp; +} + +SPA_EXPORT +size_t jack_ringbuffer_write_space(const jack_ringbuffer_t *rb) +{ + size_t w, r; + + w = rb->write_ptr; + r = rb->read_ptr; + + if (w > r) + return ((r - w + rb->size) & rb->size_mask) - 1; + else if (w < r) + return (r - w) - 1; + else + return rb->size - 1; +} diff --git a/pipewire-jack/src/statistics.c b/pipewire-jack/src/statistics.c new file mode 100644 index 000000000..260b10b8e --- /dev/null +++ b/pipewire-jack/src/statistics.c @@ -0,0 +1,54 @@ +/* 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 + +SPA_EXPORT +float jack_get_max_delayed_usecs (jack_client_t *client) +{ + struct client *c = (struct client *) client; + float res = 0.0f; + + if (c->driver_activation) + res = (float)c->driver_activation->max_delay / SPA_USEC_PER_SEC; + + pw_log_trace(NAME" %p: max delay %f", client, res); + return res; +} + +SPA_EXPORT +float jack_get_xrun_delayed_usecs (jack_client_t *client) +{ + struct client *c = (struct client *) client; + float res = 0.0f; + + if (c->driver_activation) + res = (float)c->driver_activation->xrun_delay / SPA_USEC_PER_SEC; + + pw_log_trace(NAME" %p: xrun delay %f", client, res); + return res; +} + +SPA_EXPORT +void jack_reset_max_delayed_usecs (jack_client_t *client) +{ + struct client *c = (struct client *) client; + if (c->driver_activation) + c->driver_activation->max_delay = 0; +} diff --git a/pipewire-jack/src/uuid.c b/pipewire-jack/src/uuid.c new file mode 100644 index 000000000..8d7a7668c --- /dev/null +++ b/pipewire-jack/src/uuid.c @@ -0,0 +1,102 @@ +/* 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 +#include +#include + +#include + +#include + +SPA_EXPORT +jack_uuid_t jack_client_uuid_generate () +{ + static uint32_t uuid_cnt = 0; + jack_uuid_t uuid = 0x2; /* JackUUIDClient */; + uuid = (uuid << 32) | ++uuid_cnt; + pw_log_debug("uuid %"PRIu64, uuid); + return uuid; +} + +SPA_EXPORT +jack_uuid_t jack_port_uuid_generate (uint32_t port_id) +{ + jack_uuid_t uuid = 0x1; /* JackUUIDPort */ + uuid = (uuid << 32) | (port_id + 1); + pw_log_debug("uuid %d -> %"PRIu64, port_id, uuid); + return uuid; +} + +SPA_EXPORT +uint32_t jack_uuid_to_index (jack_uuid_t id) +{ + return (id & 0xffff) - 1; +} + +SPA_EXPORT +int jack_uuid_compare (jack_uuid_t id1, jack_uuid_t id2) +{ + if (id1 == id2) + return 0; + if (id1 < id2) + return -1; + return 1; +} + +SPA_EXPORT +void jack_uuid_copy (jack_uuid_t* dst, jack_uuid_t src) +{ + *dst = src; +} + +SPA_EXPORT +void jack_uuid_clear (jack_uuid_t *id) +{ + *id = 0; +} + +SPA_EXPORT +int jack_uuid_parse (const char *buf, jack_uuid_t *id) +{ + if (sscanf (buf, "%" PRIu64, id) == 1) { + if (*id < (0x1LL << 32)) { + /* has not type bits set - not legal */ + return -1; + } + return 0; + } + return -1; +} + +SPA_EXPORT +void jack_uuid_unparse (jack_uuid_t id, char buf[JACK_UUID_STRING_SIZE]) +{ + snprintf (buf, JACK_UUID_STRING_SIZE, "%" PRIu64, id); +} + +SPA_EXPORT +int jack_uuid_empty (jack_uuid_t id) +{ + return id == 0; +}