diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:28:17 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:28:17 +0000 |
commit | 7a46c07230b8d8108c0e8e80df4522d0ac116538 (patch) | |
tree | d483300dab478b994fe199a5d19d18d74153718a /pipewire-jack | |
parent | Initial commit. (diff) | |
download | pipewire-7a46c07230b8d8108c0e8e80df4522d0ac116538.tar.xz pipewire-7a46c07230b8d8108c0e8e80df4522d0ac116538.zip |
Adding upstream version 0.3.65.upstream/0.3.65upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'pipewire-jack')
31 files changed, 14227 insertions, 0 deletions
diff --git a/pipewire-jack/examples/video-dsp-play.c b/pipewire-jack/examples/video-dsp-play.c new file mode 100644 index 0000000..be4c94c --- /dev/null +++ b/pipewire-jack/examples/video-dsp-play.c @@ -0,0 +1,203 @@ +/* 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 <stdio.h> +#include <unistd.h> +#include <sys/mman.h> + +#include <SDL2/SDL.h> + +#include <jack/jack.h> +#include <pipewire-jack-extensions.h> + +#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; + + jack_image_size_t size; + + 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->size.stride; + + src = sdata; + dst = ddata; + + for (i = 0; i < data->size.height; i++) { + struct pixel *p = (struct pixel *) src; + for (j = 0; j < data->size.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; + int res; + + 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); + + if ((res = jack_get_video_image_size(data.client, &data.size)) < 0) { + fprintf(stderr, "can't get video size: %d %s\n", res, strerror(-res)); + return -1; + } + + if (SDL_Init(SDL_INIT_VIDEO) < 0) { + fprintf(stderr, "can't initialize SDL: %s\n", SDL_GetError()); + return -1; + } + + if (SDL_CreateWindowAndRenderer + (data.size.width, data.size.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.size.width, + data.size.height); + data.rect.x = 0; + data.rect.y = 0; + data.rect.w = data.size.width; + data.rect.h = data.size.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/jack/control.h b/pipewire-jack/jack/control.h new file mode 100644 index 0000000..e466abc --- /dev/null +++ b/pipewire-jack/jack/control.h @@ -0,0 +1,658 @@ +/* -*- Mode: C ; c-basic-offset: 4 -*- */ +/* + JACK control API + + Copyright (C) 2008 Nedko Arnaudov + Copyright (C) 2008 GRAME + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ +/** + * @file jack/control.h + * @ingroup publicheader + * @brief JACK control API + * + */ + +#ifndef JACKCTL_H__2EEDAD78_DF4C_4B26_83B7_4FF1A446A47E__INCLUDED +#define JACKCTL_H__2EEDAD78_DF4C_4B26_83B7_4FF1A446A47E__INCLUDED + +#include <jack/types.h> +#include <jack/jslist.h> +#include <jack/systemdeps.h> +#if !defined(sun) && !defined(__sun__) +#include <stdbool.h> +#endif + +/** Parameter types, intentionally similar to jack_driver_param_type_t */ +typedef enum +{ + JackParamInt = 1, /**< @brief value type is a signed integer */ + JackParamUInt, /**< @brief value type is an unsigned integer */ + JackParamChar, /**< @brief value type is a char */ + JackParamString, /**< @brief value type is a string with max size of ::JACK_PARAM_STRING_MAX+1 chars */ + JackParamBool, /**< @brief value type is a boolean */ +} jackctl_param_type_t; + +/** Driver types */ +typedef enum +{ + JackMaster = 1, /**< @brief master driver */ + JackSlave /**< @brief slave driver */ +} jackctl_driver_type_t; + +/** @brief Max value that jackctl_param_type_t type can have */ +#define JACK_PARAM_MAX (JackParamBool + 1) + +/** @brief Max length of string parameter value, excluding terminating null char */ +#define JACK_PARAM_STRING_MAX 127 + +/** @brief Type for parameter value */ +/* intentionally similar to jack_driver_param_value_t */ +union jackctl_parameter_value +{ + uint32_t ui; /**< @brief member used for ::JackParamUInt */ + int32_t i; /**< @brief member used for ::JackParamInt */ + char c; /**< @brief member used for ::JackParamChar */ + char str[JACK_PARAM_STRING_MAX + 1]; /**< @brief member used for ::JackParamString */ + bool b; /**< @brief member used for ::JackParamBool */ +}; + +/** opaque type for server object */ +typedef struct jackctl_server jackctl_server_t; + +/** opaque type for driver object */ +typedef struct jackctl_driver jackctl_driver_t; + +/** opaque type for internal client object */ +typedef struct jackctl_internal jackctl_internal_t; + +/** opaque type for parameter object */ +typedef struct jackctl_parameter jackctl_parameter_t; + +/** opaque type for sigmask object */ +typedef struct jackctl_sigmask jackctl_sigmask_t; + +#ifdef __cplusplus +extern "C" { +#endif +#if 0 +} /* Adjust editor indent */ +#endif + +/** + * @defgroup ControlAPI The API for starting and controlling a JACK server + * @{ + */ + +/** + * Call this function to setup process signal handling. As a general + * rule, it is required for proper operation for the server object. + * + * @param flags signals setup flags, use 0 for none. Currently no + * flags are defined + * + * @return the configurated signal set. + */ +jackctl_sigmask_t * +jackctl_setup_signals( + unsigned int flags); + +/** + * Call this function to wait on a signal set. + * + * @param signals signals set to wait on + */ +void +jackctl_wait_signals( + jackctl_sigmask_t * signals); + +/** + * \bold THIS FUNCTION IS DEPRECATED AND SHOULD NOT BE USED IN + * NEW JACK PROJECTS + * + * @deprecated Please use jackctl_server_create2(). + */ +jackctl_server_t * +jackctl_server_create( + bool (* on_device_acquire)(const char * device_name), + void (* on_device_release)(const char * device_name)); + +/** + * Call this function to create server object. + * + * @param on_device_acquire - Optional callback to be called before device is acquired. If false is returned, device usage will fail + * @param on_device_release - Optional callback to be called after device is released. + * @param on_device_reservation_loop - Optional callback to be called when looping/idling the reservation. + * + * @return server object handle, NULL if creation of server object + * failed. Successfully created server object must be destroyed with + * paired call to ::jackctl_server_destroy + */ +jackctl_server_t * +jackctl_server_create2( + bool (* on_device_acquire)(const char * device_name), + void (* on_device_release)(const char * device_name), + void (* on_device_reservation_loop)(void)); + +/** + * Call this function to destroy server object. + * + * @param server server object handle to destroy + */ +void +jackctl_server_destroy( + jackctl_server_t * server); + +/** + * Call this function to open JACK server + * + * @param server server object handle + * @param driver driver to use + * + * @return success status: true - success, false - fail + */ +bool +jackctl_server_open( + jackctl_server_t * server, + jackctl_driver_t * driver); + +/** + * Call this function to start JACK server + * + * @param server server object handle + * + * @return success status: true - success, false - fail + */ +bool +jackctl_server_start( + jackctl_server_t * server); + +/** + * Call this function to stop JACK server + * + * @param server server object handle + * + * @return success status: true - success, false - fail + */ +bool +jackctl_server_stop( + jackctl_server_t * server); + +/** + * Call this function to close JACK server + * + * @param server server object handle + * + * @return success status: true - success, false - fail + */ +bool +jackctl_server_close( + jackctl_server_t * server); + +/** + * Call this function to get list of available drivers. List node data + * pointers is a driver object handle (::jackctl_driver_t). + * + * @param server server object handle to get drivers for + * + * @return Single linked list of driver object handles. Must not be + * modified. Always same for same server object. + */ +const JSList * +jackctl_server_get_drivers_list( + jackctl_server_t * server); + +/** + * Call this function to get list of server parameters. List node data + * pointers is a parameter object handle (::jackctl_parameter_t). + * + * @param server server object handle to get parameters for + * + * @return Single linked list of parameter object handles. Must not be + * modified. Always same for same server object. + */ +const JSList * +jackctl_server_get_parameters( + jackctl_server_t * server); + +/** + * Call this function to get list of available internal clients. List node data + * pointers is a internal client object handle (::jackctl_internal_t). + * + * @param server server object handle to get internal clients for + * + * @return Single linked list of internal client object handles. Must not be + * modified. Always same for same server object. + */ +const JSList * +jackctl_server_get_internals_list( + jackctl_server_t * server); + +/** + * Call this function to load one internal client. + * (can be used when the server is running) + * + * @param server server object handle + * @param internal internal to use + * + * @return success status: true - success, false - fail + */ +bool +jackctl_server_load_internal( + jackctl_server_t * server, + jackctl_internal_t * internal); + +/** + * Call this function to unload one internal client. + * (can be used when the server is running) + * + * @param server server object handle + * @param internal internal to unload + * + * @return success status: true - success, false - fail + */ +bool +jackctl_server_unload_internal( + jackctl_server_t * server, + jackctl_internal_t * internal); + +/** + * Call this function to load a session file. + * (can be used when the server is running) + * + * @param server server object handle + * @param file the session file to load, containing a list of + * internal clients and connections to be made. + * + * @return success status: true - success, false - fail + */ +bool jackctl_server_load_session_file( + jackctl_server_t * server_ptr, + const char * file); + +/** + * Call this function to add a slave in the driver slave list. + * (cannot be used when the server is running that is between + * jackctl_server_start and jackctl_server_stop) + * + * @param server server object handle + * @param driver driver to add in the driver slave list. + * + * @return success status: true - success, false - fail + */ +bool +jackctl_server_add_slave(jackctl_server_t * server, + jackctl_driver_t * driver); + +/** + * Call this function to remove a slave from the driver slave list. + * (cannot be used when the server is running that is between + * jackctl_server_start and jackctl_server_stop) + * + * @param server server object handle + * @param driver driver to remove from the driver slave list. + * + * @return success status: true - success, false - fail + */ +bool +jackctl_server_remove_slave(jackctl_server_t * server, + jackctl_driver_t * driver); + +/** + * Call this function to switch master driver. + * + * @param server server object handle + * @param driver driver to switch to + * + * @return success status: true - success, false - fail + */ +bool +jackctl_server_switch_master(jackctl_server_t * server, + jackctl_driver_t * driver); + + +/** + * Call this function to get name of driver. + * + * @param driver driver object handle to get name of + * + * @return driver name. Must not be modified. Always same for same + * driver object. + */ +const char * +jackctl_driver_get_name( + jackctl_driver_t * driver); + +/** + * Call this function to get type of driver. + * + * @param driver driver object handle to get name of + * + * @return driver type. Must not be modified. Always same for same + * driver object. + */ +jackctl_driver_type_t +jackctl_driver_get_type( + jackctl_driver_t * driver); + +/** + * Call this function to get list of driver parameters. List node data + * pointers is a parameter object handle (::jackctl_parameter_t). + * + * @param driver driver object handle to get parameters for + * + * @return Single linked list of parameter object handles. Must not be + * modified. Always same for same driver object. + */ +const JSList * +jackctl_driver_get_parameters( + jackctl_driver_t * driver); + +/** + * Call this function to parse parameters for a driver. + * + * @param driver driver object handle + * @param argc parameter list len + * @param argv parameter list, as an array of char* + * + * @return success status: true - success, false - fail + */ +int +jackctl_driver_params_parse( + jackctl_driver_t * driver, + int argc, + char* argv[]); + +/** + * Call this function to get name of internal client. + * + * @param internal internal object handle to get name of + * + * @return internal name. Must not be modified. Always same for same + * internal object. + */ +const char * +jackctl_internal_get_name( + jackctl_internal_t * internal); + +/** + * Call this function to get list of internal parameters. List node data + * pointers is a parameter object handle (::jackctl_parameter_t). + * + * @param internal internal object handle to get parameters for + * + * @return Single linked list of parameter object handles. Must not be + * modified. Always same for same internal object. + */ +const JSList * +jackctl_internal_get_parameters( + jackctl_internal_t * internal); + +/** + * Call this function to get parameter name. + * + * @param parameter parameter object handle to get name of + * + * @return parameter name. Must not be modified. Always same for same + * parameter object. + */ +const char * +jackctl_parameter_get_name( + jackctl_parameter_t * parameter); + +/** + * Call this function to get parameter short description. + * + * @param parameter parameter object handle to get short description of + * + * @return parameter short description. Must not be modified. Always + * same for same parameter object. + */ +const char * +jackctl_parameter_get_short_description( + jackctl_parameter_t * parameter); + +/** + * Call this function to get parameter long description. + * + * @param parameter parameter object handle to get long description of + * + * @return parameter long description. Must not be modified. Always + * same for same parameter object. + */ +const char * +jackctl_parameter_get_long_description( + jackctl_parameter_t * parameter); + +/** + * Call this function to get parameter type. + * + * @param parameter parameter object handle to get type of + * + * @return parameter type. Always same for same parameter object. + */ +jackctl_param_type_t +jackctl_parameter_get_type( + jackctl_parameter_t * parameter); + +/** + * Call this function to get parameter character. + * + * @param parameter parameter object handle to get character of + * + * @return character. + */ +char +jackctl_parameter_get_id( + jackctl_parameter_t * parameter); + +/** + * Call this function to check whether parameter has been set, or its + * default value is being used. + * + * @param parameter parameter object handle to check + * + * @return true - parameter is set, false - parameter is using default + * value. + */ +bool +jackctl_parameter_is_set( + jackctl_parameter_t * parameter); + +/** + * Call this function to reset parameter to its default value. + * + * @param parameter parameter object handle to reset value of + * + * @return success status: true - success, false - fail + */ +bool +jackctl_parameter_reset( + jackctl_parameter_t * parameter); + +/** + * Call this function to get parameter value. + * + * @param parameter parameter object handle to get value of + * + * @return parameter value. + */ +union jackctl_parameter_value +jackctl_parameter_get_value( + jackctl_parameter_t * parameter); + +/** + * Call this function to set parameter value. + * + * @param parameter parameter object handle to get value of + * @param value_ptr pointer to variable containing parameter value + * + * @return success status: true - success, false - fail + */ +bool +jackctl_parameter_set_value( + jackctl_parameter_t * parameter, + const union jackctl_parameter_value * value_ptr); + +/** + * Call this function to get parameter default value. + * + * @param parameter parameter object handle to get default value of + * + * @return parameter default value. + */ +union jackctl_parameter_value +jackctl_parameter_get_default_value( + jackctl_parameter_t * parameter); + +/** + * Call this function check whether parameter has range constraint. + * + * @param parameter object handle of parameter to check + * + * @return whether parameter has range constraint. + */ +bool +jackctl_parameter_has_range_constraint( + jackctl_parameter_t * parameter); + +/** + * Call this function check whether parameter has enumeration constraint. + * + * @param parameter object handle of parameter to check + * + * @return whether parameter has enumeration constraint. + */ +bool +jackctl_parameter_has_enum_constraint( + jackctl_parameter_t * parameter); + +/** + * Call this function get how many enumeration values parameter has. + * + * @param parameter object handle of parameter + * + * @return number of enumeration values + */ +uint32_t +jackctl_parameter_get_enum_constraints_count( + jackctl_parameter_t * parameter); + +/** + * Call this function to get parameter enumeration value. + * + * @param parameter object handle of parameter + * @param index index of parameter enumeration value + * + * @return enumeration value. + */ +union jackctl_parameter_value +jackctl_parameter_get_enum_constraint_value( + jackctl_parameter_t * parameter, + uint32_t index); + +/** + * Call this function to get parameter enumeration value description. + * + * @param parameter object handle of parameter + * @param index index of parameter enumeration value + * + * @return enumeration value description. + */ +const char * +jackctl_parameter_get_enum_constraint_description( + jackctl_parameter_t * parameter, + uint32_t index); + +/** + * Call this function to get parameter range. + * + * @param parameter object handle of parameter + * @param min_ptr pointer to variable receiving parameter minimum value + * @param max_ptr pointer to variable receiving parameter maximum value + */ +void +jackctl_parameter_get_range_constraint( + jackctl_parameter_t * parameter, + union jackctl_parameter_value * min_ptr, + union jackctl_parameter_value * max_ptr); + +/** + * Call this function to check whether parameter constraint is strict, + * i.e. whether supplying non-matching value will not work for sure. + * + * @param parameter parameter object handle to check + * + * @return whether parameter constraint is strict. + */ +bool +jackctl_parameter_constraint_is_strict( + jackctl_parameter_t * parameter); + +/** + * Call this function to check whether parameter has fake values, + * i.e. values have no user meaningful meaning and only value + * description is meaningful to user. + * + * @param parameter parameter object handle to check + * + * @return whether parameter constraint is strict. + */ +bool +jackctl_parameter_constraint_is_fake_value( + jackctl_parameter_t * parameter); + +/** + * Call this function to log an error message. + * + * @param format string + */ +void +jack_error( + const char *format, + ...); + +/** + * Call this function to log an information message. + * + * @param format string + */ +void +jack_info( + const char *format, + ...); + +/** + * Call this function to log an information message but only when + * verbose mode is enabled. + * + * @param format string + */ +void +jack_log( + const char *format, + ...); + +/* @} */ + +#if 0 +{ /* Adjust editor indent */ +#endif +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* #ifndef JACKCTL_H__2EEDAD78_DF4C_4B26_83B7_4FF1A446A47E__INCLUDED */ diff --git a/pipewire-jack/jack/intclient.h b/pipewire-jack/jack/intclient.h new file mode 100644 index 0000000..d4503ae --- /dev/null +++ b/pipewire-jack/jack/intclient.h @@ -0,0 +1,130 @@ +/* +* Copyright (C) 2004 Jack O'Quin +* +* This program 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 program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +* +*/ + +#ifndef __jack_intclient_h__ +#define __jack_intclient_h__ + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include <jack/types.h> + +/** + * Get an internal client's name. This is useful when @ref + * JackUseExactName was not specified on jack_internal_client_load() + * and @ref JackNameNotUnique status was returned. In that case, the + * actual name will differ from the @a client_name requested. + * + * @param client requesting JACK client's handle. + * + * @param intclient handle returned from jack_internal_client_load() + * or jack_internal_client_handle(). + * + * @return NULL if unsuccessful, otherwise pointer to the internal + * client name obtained from the heap via malloc(). The caller should + * jack_free() this storage when no longer needed. + */ +char *jack_get_internal_client_name (jack_client_t *client, + jack_intclient_t intclient); + +/** + * Return the @ref jack_intclient_t handle for an internal client + * running in the JACK server. + * + * @param client requesting JACK client's handle. + * + * @param client_name for the internal client of no more than + * jack_client_name_size() characters. The name scope is local to the + * current server. + * + * @param status (if non-NULL) an address for JACK to return + * information from this operation. This status word is formed by + * OR-ing together the relevant @ref JackStatus bits. + * + * @return Opaque internal client handle if successful. If 0, the + * internal client was not found, and @a *status includes the @ref + * JackNoSuchClient and @ref JackFailure bits. + */ +jack_intclient_t jack_internal_client_handle (jack_client_t *client, + const char *client_name, + jack_status_t *status); + +/** + * Load an internal client into the JACK server. + * + * Internal clients run inside the JACK server process. They can use + * most of the same functions as external clients. Each internal + * client is built as a shared object module, which must declare + * jack_initialize() and jack_finish() entry points called at load and + * unload times. See @ref inprocess.c for an example. + * + * @param client loading JACK client's handle. + * + * @param client_name of at most jack_client_name_size() characters + * for the internal client to load. The name scope is local to the + * current server. + * + * @param options formed by OR-ing together @ref JackOptions bits. + * Only the @ref JackLoadOptions bits are valid. + * + * @param status (if non-NULL) an address for JACK to return + * information from the load operation. This status word is formed by + * OR-ing together the relevant @ref JackStatus bits. + * + * <b>Optional parameters:</b> depending on corresponding [@a options + * bits] additional parameters may follow @a status (in this order). + * + * @arg [@ref JackLoadName] <em>(char *) load_name</em> is the shared + * object file from which to load the new internal client (otherwise + * use the @a client_name). + * + * @arg [@ref JackLoadInit] <em>(char *) load_init</em> an arbitrary + * string passed to the internal client's jack_initialize() routine + * (otherwise NULL), of no more than @ref JACK_LOAD_INIT_LIMIT bytes. + * + * @return Opaque internal client handle if successful. If this is 0, + * the load operation failed, the internal client was not loaded, and + * @a *status includes the @ref JackFailure bit. + */ +jack_intclient_t jack_internal_client_load (jack_client_t *client, + const char *client_name, + jack_options_t options, + jack_status_t *status, ...); +/** + * Unload an internal client from a JACK server. This calls the + * intclient's jack_finish() entry point then removes it. See @ref + * inprocess.c for an example. + * + * @param client unloading JACK client's handle. + * + * @param intclient handle returned from jack_internal_client_load() or + * jack_internal_client_handle(). + * + * @return 0 if successful, otherwise @ref JackStatus bits. + */ +jack_status_t jack_internal_client_unload (jack_client_t *client, + jack_intclient_t intclient); + +#ifdef __cplusplus +} +#endif + +#endif /* __jack_intclient_h__ */ diff --git a/pipewire-jack/jack/jack.h b/pipewire-jack/jack/jack.h new file mode 100644 index 0000000..2b83cb1 --- /dev/null +++ b/pipewire-jack/jack/jack.h @@ -0,0 +1,1477 @@ +/* + Copyright (C) 2001 Paul Davis + Copyright (C) 2004 Jack O'Quin + + This program 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 program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef __jack_h__ +#define __jack_h__ + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include <jack/systemdeps.h> +#include <jack/types.h> +#include <jack/transport.h> + +/** + * Note: More documentation can be found in jack/types.h. + */ + + /************************************************************* + * NOTE: JACK_WEAK_EXPORT ***MUST*** be used on every function + * added to the JACK API after the 0.116.2 release. + * + * Functions that predate this release are marked with + * JACK_WEAK_OPTIONAL_EXPORT which can be defined at compile + * time in a variety of ways. The default definition is empty, + * so that these symbols get normal linkage. If you wish to + * use all JACK symbols with weak linkage, include + * <jack/weakjack.h> before jack.h. + *************************************************************/ + +#include <jack/weakmacros.h> + +/** + * Call this function to get version of the JACK, in form of several numbers + * + * @param major_ptr pointer to variable receiving major version of JACK. + * + * @param minor_ptr pointer to variable receiving minor version of JACK. + * + * @param major_ptr pointer to variable receiving micro version of JACK. + * + * @param major_ptr pointer to variable receiving protocol version of JACK. + * + */ +void +jack_get_version( + int *major_ptr, + int *minor_ptr, + int *micro_ptr, + int *proto_ptr) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Call this function to get version of the JACK, in form of a string + * + * @return Human readable string describing JACK version being used. + * + */ +const char * +jack_get_version_string(void) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @defgroup ClientFunctions Creating & manipulating clients + * @{ + */ + +/** + * Open an external client session with a JACK server. This interface + * is more complex but more powerful than jack_client_new(). With it, + * clients may choose which of several servers to connect, and control + * whether and how to start the server automatically, if it was not + * already running. There is also an option for JACK to generate a + * unique client name, when necessary. + * + * @param client_name of at most jack_client_name_size() characters. + * The name scope is local to each server. Unless forbidden by the + * @ref JackUseExactName option, the server will modify this name to + * create a unique variant, if needed. + * + * @param options formed by OR-ing together @ref JackOptions bits. + * Only the @ref JackOpenOptions bits are allowed. + * + * @param status (if non-NULL) an address for JACK to return + * information from the open operation. This status word is formed by + * OR-ing together the relevant @ref JackStatus bits. + * + * + * <b>Optional parameters:</b> depending on corresponding [@a options + * bits] additional parameters may follow @a status (in this order). + * + * @arg [@ref JackServerName] <em>(char *) server_name</em> selects + * from among several possible concurrent server instances. Server + * names are unique to each user. If unspecified, use "default" + * unless \$JACK_DEFAULT_SERVER is defined in the process environment. + * + * @return Opaque client handle if successful. If this is NULL, the + * open operation failed, @a *status includes @ref JackFailure and the + * caller is not a JACK client. + */ +jack_client_t * jack_client_open (const char *client_name, + jack_options_t options, + jack_status_t *status, ...) JACK_OPTIONAL_WEAK_EXPORT; + +/** +* \bold THIS FUNCTION IS DEPRECATED AND SHOULD NOT BE USED IN +* NEW JACK CLIENTS +* +* @deprecated Please use jack_client_open(). +*/ +jack_client_t * jack_client_new (const char *client_name) JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT; + +/** + * Disconnects an external client from a JACK server. + * + * @return 0 on success, otherwise a non-zero error code + */ +int jack_client_close (jack_client_t *client) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return the maximum number of characters in a JACK client name + * including the final NULL character. This value is a constant. + */ +int jack_client_name_size (void) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return pointer to actual client name. This is useful when @ref + * JackUseExactName is not specified on open and @ref + * JackNameNotUnique status was returned. In that case, the actual + * name will differ from the @a client_name requested. + */ +char * jack_get_client_name (jack_client_t *client) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Get the session ID for a client name. + * + * The session manager needs this to reassociate a client name to the session_id. + * + * The caller is responsible for calling jack_free(3) on any non-NULL + * returned value. + */ +char *jack_get_uuid_for_client_name (jack_client_t *client, + const char *client_name) JACK_WEAK_EXPORT; + +/** + * Get the client name for a session_id. + * + * In order to snapshot the graph connections, the session manager needs to map + * session_ids to client names. + * + * The caller is responsible for calling jack_free(3) on any non-NULL + * returned value. + */ +char *jack_get_client_name_by_uuid (jack_client_t *client, + const char *client_uuid ) JACK_WEAK_EXPORT; + +/** + * Load an internal client into the Jack server. + * + * Internal clients run inside the JACK server process. They can use + * most of the same functions as external clients. Each internal + * client must declare jack_initialize() and jack_finish() entry + * points, called at load and unload times. See inprocess.c for an + * example of how to write an internal client. + * + * @deprecated Please use jack_internal_client_load(). + * + * @param client_name of at most jack_client_name_size() characters. + * + * @param load_name of a shared object file containing the code for + * the new client. + * + * @param load_init an arbitrary string passed to the jack_initialize() + * routine of the new client (may be NULL). + * + * @return 0 if successful. + */ +int jack_internal_client_new (const char *client_name, + const char *load_name, + const char *load_init) JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT; + +/** + * Remove an internal client from a JACK server. + * + * @deprecated Please use jack_internal_client_unload(). + */ +void jack_internal_client_close (const char *client_name) JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT; + +/** + * Tell the Jack server that the program is ready to start processing + * audio. + * + * @return 0 on success, otherwise a non-zero error code + */ +int jack_activate (jack_client_t *client) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Tell the Jack server to remove this @a client from the process + * graph. Also, disconnect all ports belonging to it, since inactive + * clients have no port connections. + * + * @return 0 on success, otherwise a non-zero error code + */ +int jack_deactivate (jack_client_t *client) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return pid of client. If not available, 0 will be returned. + */ +int jack_get_client_pid (const char *name) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return the pthread ID of the thread running the JACK client side + * real-time code. + */ +jack_native_thread_t jack_client_thread_id (jack_client_t *client) JACK_OPTIONAL_WEAK_EXPORT; + +/*@}*/ + +/** + * @param client pointer to JACK client structure. + * + * Check if the JACK subsystem is running with -R (--realtime). + * + * @return 1 if JACK is running realtime, 0 otherwise + */ +int jack_is_realtime (jack_client_t *client) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @defgroup NonCallbackAPI The non-callback API + * @{ + */ + +/** + * \bold THIS FUNCTION IS DEPRECATED AND SHOULD NOT BE USED IN + * NEW JACK CLIENTS. + * + * @deprecated Please use jack_cycle_wait() and jack_cycle_signal() functions. + */ +jack_nframes_t jack_thread_wait (jack_client_t *client, int status) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Wait until this JACK client should process data. + * + * @param client - pointer to a JACK client structure + * + * @return the number of frames of data to process + */ +jack_nframes_t jack_cycle_wait (jack_client_t* client) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Signal next clients in the graph. + * + * @param client - pointer to a JACK client structure + * @param status - if non-zero, calling thread should exit + */ +void jack_cycle_signal (jack_client_t* client, int status) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Tell the Jack server to call @a thread_callback in the RT thread. + * Typical use are in conjunction with @a jack_cycle_wait and @a jack_cycle_signal functions. + * The code in the supplied function must be suitable for real-time + * execution. That means that it cannot call functions that might + * block for a long time. This includes malloc, free, printf, + * pthread_mutex_lock, sleep, wait, poll, select, pthread_join, + * pthread_cond_wait, etc, etc. See + * http://jackit.sourceforge.net/docs/design/design.html#SECTION00411000000000000000 + * for more information. + * + * NOTE: this function cannot be called while the client is activated + * (after jack_activate has been called.) + * + * @return 0 on success, otherwise a non-zero error code. +*/ +int jack_set_process_thread(jack_client_t* client, JackThreadCallback thread_callback, void *arg) JACK_OPTIONAL_WEAK_EXPORT; + +/*@}*/ + +/** + * @defgroup ClientCallbacks Setting Client Callbacks + * @{ + */ + +/** + * Tell JACK to call @a thread_init_callback once just after + * the creation of the thread in which all other callbacks + * will be handled. + * + * The code in the supplied function does not need to be + * suitable for real-time execution. + * + * NOTE: this function cannot be called while the client is activated + * (after jack_activate has been called.) + * + * @return 0 on success, otherwise a non-zero error code, causing JACK + * to remove that client from the process() graph. + */ +int jack_set_thread_init_callback (jack_client_t *client, + JackThreadInitCallback thread_init_callback, + void *arg) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @param client pointer to JACK client structure. + * @param function The jack_shutdown function pointer. + * @param arg The arguments for the jack_shutdown function. + * + * Register a function (and argument) to be called if and when the + * JACK server shuts down the client thread. The function must + * be written as if it were an asynchronous POSIX signal + * handler --- use only async-safe functions, and remember that it + * is executed from another thread. A typical function might + * set a flag or write to a pipe so that the rest of the + * application knows that the JACK client thread has shut + * down. + * + * NOTE: clients do not need to call this. It exists only + * to help more complex clients understand what is going + * on. It should be called before jack_client_activate(). + * + * NOTE: if a client calls this AND jack_on_info_shutdown(), then + * in case of a client thread shutdown, the callback + * passed to this function will not be called, and the one passed to + * jack_on_info_shutdown() will. + * + * NOTE: application should typically signal another thread to correctly + * finish cleanup, that is by calling "jack_client_close" + * (since "jack_client_close" cannot be called directly in the context + * of the thread that calls the shutdown callback). + */ +void jack_on_shutdown (jack_client_t *client, + JackShutdownCallback shutdown_callback, void *arg) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @param client pointer to JACK client structure. + * @param function The jack_info_shutdown function pointer. + * @param arg The arguments for the jack_info_shutdown function. + * + * Register a function (and argument) to be called if and when the + * JACK server shuts down the client thread. The function must + * be written as if it were an asynchronous POSIX signal + * handler --- use only async-safe functions, and remember that it + * is executed from another thread. A typical function might + * set a flag or write to a pipe so that the rest of the + * application knows that the JACK client thread has shut + * down. + * + * NOTE: clients do not need to call this. It exists only + * to help more complex clients understand what is going + * on. It should be called before jack_client_activate(). + * + * NOTE: if a client calls this AND jack_on_shutdown(), then + * in case of a client thread shutdown, the callback passed to + * jack_on_info_shutdown() will be called. + * + * NOTE: application should typically signal another thread to correctly + * finish cleanup, that is by calling "jack_client_close" + * (since "jack_client_close" cannot be called directly in the context + * of the thread that calls the shutdown callback). + */ +void jack_on_info_shutdown (jack_client_t *client, + JackInfoShutdownCallback shutdown_callback, void *arg) JACK_WEAK_EXPORT; + +/** + * Tell the Jack server to call @a process_callback whenever there is + * work be done, passing @a arg as the second argument. + * + * The code in the supplied function must be suitable for real-time + * execution. That means that it cannot call functions that might + * block for a long time. This includes malloc, free, printf, + * pthread_mutex_lock, sleep, wait, poll, select, pthread_join, + * pthread_cond_wait, etc, etc. See + * http://jackit.sourceforge.net/docs/design/design.html#SECTION00411000000000000000 + * for more information. + * + * NOTE: this function cannot be called while the client is activated + * (after jack_activate has been called.) + * + * @return 0 on success, otherwise a non-zero error code. + */ +int jack_set_process_callback (jack_client_t *client, + JackProcessCallback process_callback, + void *arg) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Tell the Jack server to call @a freewheel_callback + * whenever we enter or leave "freewheel" mode, passing @a + * arg as the second argument. The first argument to the + * callback will be non-zero if JACK is entering freewheel + * mode, and zero otherwise. + * + * All "notification events" are received in a separated non RT thread, + * the code in the supplied function does not need to be + * suitable for real-time execution. + * + * NOTE: this function cannot be called while the client is activated + * (after jack_activate has been called.) + * + * @return 0 on success, otherwise a non-zero error code. + */ +int jack_set_freewheel_callback (jack_client_t *client, + JackFreewheelCallback freewheel_callback, + void *arg) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Tell JACK to call @a bufsize_callback whenever the size of the + * buffer that will be passed to the @a process_callback is about to + * change. Clients that depend on knowing the buffer size must supply + * a @a bufsize_callback before activating themselves. + * + * All "notification events" are received in a separated non RT thread, + * the code in the supplied function does not need to be + * suitable for real-time execution. + * + * NOTE: this function cannot be called while the client is activated + * (after jack_activate has been called.) + * + * @param client pointer to JACK client structure. + * @param bufsize_callback function to call when the buffer size changes. + * @param arg argument for @a bufsize_callback. + * + * @return 0 on success, otherwise a non-zero error code + */ +int jack_set_buffer_size_callback (jack_client_t *client, + JackBufferSizeCallback bufsize_callback, + void *arg) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Tell the Jack server to call @a srate_callback whenever the system + * sample rate changes. + * + * All "notification events" are received in a separated non RT thread, + * the code in the supplied function does not need to be + * suitable for real-time execution. + * + * NOTE: this function cannot be called while the client is activated + * (after jack_activate has been called.) + * + * @return 0 on success, otherwise a non-zero error code + */ +int jack_set_sample_rate_callback (jack_client_t *client, + JackSampleRateCallback srate_callback, + void *arg) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Tell the JACK server to call @a client_registration_callback whenever a + * client is registered or unregistered, passing @a arg as a parameter. + * + * All "notification events" are received in a separated non RT thread, + * the code in the supplied function does not need to be + * suitable for real-time execution. + * + * NOTE: this function cannot be called while the client is activated + * (after jack_activate has been called.) + * + * @return 0 on success, otherwise a non-zero error code + */ +int jack_set_client_registration_callback (jack_client_t *client, + JackClientRegistrationCallback + registration_callback, void *arg) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Tell the JACK server to call @a registration_callback whenever a + * port is registered or unregistered, passing @a arg as a parameter. + * + * All "notification events" are received in a separated non RT thread, + * the code in the supplied function does not need to be + * suitable for real-time execution. + * + * NOTE: this function cannot be called while the client is activated + * (after jack_activate has been called.) + * + * @return 0 on success, otherwise a non-zero error code + */ + int jack_set_port_registration_callback (jack_client_t *client, + JackPortRegistrationCallback + registration_callback, void *arg) JACK_OPTIONAL_WEAK_EXPORT; + + /** + * Tell the JACK server to call @a connect_callback whenever a + * port is connected or disconnected, passing @a arg as a parameter. + * + * All "notification events" are received in a separated non RT thread, + * the code in the supplied function does not need to be + * suitable for real-time execution. + * + * NOTE: this function cannot be called while the client is activated + * (after jack_activate has been called.) + * + * @return 0 on success, otherwise a non-zero error code + */ +int jack_set_port_connect_callback (jack_client_t *client, + JackPortConnectCallback + connect_callback, void *arg) JACK_OPTIONAL_WEAK_EXPORT; + + /** + * Tell the JACK server to call @a rename_callback whenever a + * port is renamed, passing @a arg as a parameter. + * + * All "notification events" are received in a separated non RT thread, + * the code in the supplied function does not need to be + * suitable for real-time execution. + * + * NOTE: this function cannot be called while the client is activated + * (after jack_activate has been called.) + * + * @return 0 on success, otherwise a non-zero error code + */ +int jack_set_port_rename_callback (jack_client_t *client, + JackPortRenameCallback + rename_callback, void *arg) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Tell the JACK server to call @a graph_callback whenever the + * processing graph is reordered, passing @a arg as a parameter. + * + * All "notification events" are received in a separated non RT thread, + * the code in the supplied function does not need to be + * suitable for real-time execution. + * + * NOTE: this function cannot be called while the client is activated + * (after jack_activate has been called.) + * + * @return 0 on success, otherwise a non-zero error code + */ +int jack_set_graph_order_callback (jack_client_t *client, + JackGraphOrderCallback graph_callback, + void *) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Tell the JACK server to call @a xrun_callback whenever there is a + * xrun, passing @a arg as a parameter. + * + * All "notification events" are received in a separated non RT thread, + * the code in the supplied function does not need to be + * suitable for real-time execution. + * + * NOTE: this function cannot be called while the client is activated + * (after jack_activate has been called.) + * + * @return 0 on success, otherwise a non-zero error code + */ +int jack_set_xrun_callback (jack_client_t *client, + JackXRunCallback xrun_callback, void *arg) JACK_OPTIONAL_WEAK_EXPORT; + +/*@}*/ + +/** + * Tell the Jack server to call @a latency_callback whenever it + * is necessary to recompute the latencies for some or all + * Jack ports. + * + * @a latency_callback will be called twice each time it is + * needed, once being passed JackCaptureLatency and once + * JackPlaybackLatency. See @ref LatencyFunctions for + * the definition of each type of latency and related functions. + * + * <b>IMPORTANT: Most JACK clients do NOT need to register a latency + * callback.</b> + * + * Clients that meet any of the following conditions do NOT + * need to register a latency callback: + * + * - have only input ports + * - have only output ports + * - their output is totally unrelated to their input + * - their output is not delayed relative to their input + * (i.e. data that arrives in a given process() + * callback is processed and output again in the + * same callback) + * + * Clients NOT registering a latency callback MUST also + * satisfy this condition: + * + * - have no multiple distinct internal signal pathways + * + * This means that if your client has more than 1 input and + * output port, and considers them always "correlated" + * (e.g. as a stereo pair), then there is only 1 (e.g. stereo) + * signal pathway through the client. This would be true, + * for example, of a stereo FX rack client that has a + * left/right input pair and a left/right output pair. + * + * However, this is somewhat a matter of perspective. The + * same FX rack client could be connected so that its + * two input ports were connected to entirely separate + * sources. Under these conditions, the fact that the client + * does not register a latency callback MAY result + * in port latency values being incorrect. + * + * Clients that do not meet any of those conditions SHOULD + * register a latency callback. + * + * Another case is when a client wants to use + * @ref jack_port_get_latency_range(), which only returns meaningful + * values when ports get connected and latency values change. + * + * See the documentation for @ref jack_port_set_latency_range() + * on how the callback should operate. Remember that the @a mode + * argument given to the latency callback will need to be + * passed into @ref jack_port_set_latency_range() + * + * @return 0 on success, otherwise a non-zero error code + */ +int jack_set_latency_callback (jack_client_t *client, + JackLatencyCallback latency_callback, + void *) JACK_WEAK_EXPORT; +/*@}*/ + +/** + * @defgroup ServerClientControl Controlling & querying JACK server operation + * @{ + */ + +/** + * Start/Stop JACK's "freewheel" mode. + * + * When in "freewheel" mode, JACK no longer waits for + * any external event to begin the start of the next process + * cycle. + * + * As a result, freewheel mode causes "faster than realtime" + * execution of a JACK graph. If possessed, real-time + * scheduling is dropped when entering freewheel mode, and + * if appropriate it is reacquired when stopping. + * + * IMPORTANT: on systems using capabilities to provide real-time + * scheduling (i.e. Linux kernel 2.4), if onoff is zero, this function + * must be called from the thread that originally called jack_activate(). + * This restriction does not apply to other systems (e.g. Linux kernel 2.6 + * or OS X). + * + * @param client pointer to JACK client structure + * @param onoff if non-zero, freewheel mode starts. Otherwise + * freewheel mode ends. + * + * @return 0 on success, otherwise a non-zero error code. + */ +int jack_set_freewheel(jack_client_t* client, int onoff) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Change the buffer size passed to the @a process_callback. + * + * This operation stops the JACK engine process cycle, then calls all + * registered @a bufsize_callback functions before restarting the + * process cycle. This will cause a gap in the audio flow, so it + * should only be done at appropriate stopping points. + * + * @see jack_set_buffer_size_callback() + * + * @param client pointer to JACK client structure. + * @param nframes new buffer size. Must be a power of two. + * + * @return 0 on success, otherwise a non-zero error code + */ +int jack_set_buffer_size (jack_client_t *client, jack_nframes_t nframes) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return the sample rate of the jack system, as set by the user when + * jackd was started. + */ +jack_nframes_t jack_get_sample_rate (jack_client_t *) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return the current maximum size that will ever be passed to the @a + * process_callback. It should only be used *before* the client has + * been activated. This size may change, clients that depend on it + * must register a @a bufsize_callback so they will be notified if it + * does. + * + * @see jack_set_buffer_size_callback() + */ +jack_nframes_t jack_get_buffer_size (jack_client_t *) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Old-style interface to become the timebase for the entire JACK + * subsystem. + * + * @deprecated This function still exists for compatibility with the + * earlier transport interface, but it does nothing. Instead, see + * transport.h and use jack_set_timebase_callback(). + * + * @return ENOSYS, function not implemented. + */ +int jack_engine_takeover_timebase (jack_client_t *) JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT; + +/** + * @return the current CPU load estimated by JACK. This is a running + * average of the time it takes to execute a full process cycle for + * all clients as a percentage of the real time available per cycle + * determined by the buffer size and sample rate. + */ +float jack_cpu_load (jack_client_t *client) JACK_OPTIONAL_WEAK_EXPORT; + +/*@}*/ + +/** + * @defgroup PortFunctions Creating & manipulating ports + * @{ + */ + +/** + * Create a new port for the client. This is an object used for moving + * data of any type in or out of the client. Ports may be connected + * in various ways. + * + * Each port has a short name. The port's full name contains the name + * of the client concatenated with a colon (:) followed by its short + * name. The jack_port_name_size() is the maximum length of this full + * name. Exceeding that will cause the port registration to fail and + * return NULL. + * + * The @a port_name must be unique among all ports owned by this client. + * If the name is not unique, the registration will fail. + * + * All ports have a type, which may be any non-NULL and non-zero + * length string, passed as an argument. Some port types are built + * into the JACK API, currently only JACK_DEFAULT_AUDIO_TYPE. + * + * @param client pointer to JACK client structure. + * @param port_name non-empty short name for the new port (not + * including the leading @a "client_name:"). Must be unique. + * @param port_type port type name. If longer than + * jack_port_type_size(), only that many characters are significant. + * @param flags @ref JackPortFlags bit mask. + * @param buffer_size must be non-zero if this is not a built-in @a + * port_type. Otherwise, it is ignored. + * + * @return jack_port_t pointer on success, otherwise NULL. + */ +jack_port_t * jack_port_register (jack_client_t *client, + const char *port_name, + const char *port_type, + unsigned long flags, + unsigned long buffer_size) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Remove the port from the client, disconnecting any existing + * connections. + * + * @return 0 on success, otherwise a non-zero error code + */ +int jack_port_unregister (jack_client_t *client, jack_port_t *port) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * This returns a pointer to the memory area associated with the + * specified port. For an output port, it will be a memory area + * that can be written to; for an input port, it will be an area + * containing the data from the port's connection(s), or + * zero-filled. if there are multiple inbound connections, the data + * will be mixed appropriately. + * + * FOR OUTPUT PORTS ONLY : DEPRECATED in Jack 2.0 !! + * --------------------------------------------------- + * You may cache the value returned, but only between calls to + * your "blocksize" callback. For this reason alone, you should + * either never cache the return value or ensure you have + * a "blocksize" callback and be sure to invalidate the cached + * address from there. + * + * Caching output ports is DEPRECATED in Jack 2.0, due to some new optimization (like "pipelining"). + * Port buffers have to be retrieved in each callback for proper functioning. + */ +void * jack_port_get_buffer (jack_port_t *port, jack_nframes_t) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return the UUID of the jack_port_t + * + * @see jack_uuid_to_string() to convert into a string representation + */ +jack_uuid_t jack_port_uuid (const jack_port_t *port) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return the full name of the jack_port_t (including the @a + * "client_name:" prefix). + * + * @see jack_port_name_size(). + */ +const char * jack_port_name (const jack_port_t *port) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return the short name of the jack_port_t (not including the @a + * "client_name:" prefix). + * + * @see jack_port_name_size(). + */ +const char * jack_port_short_name (const jack_port_t *port) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return the @ref JackPortFlags of the jack_port_t. + */ +int jack_port_flags (const jack_port_t *port) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return the @a port type, at most jack_port_type_size() characters + * including a final NULL. + */ +const char * jack_port_type (const jack_port_t *port) JACK_OPTIONAL_WEAK_EXPORT; + + /** + * @return the @a port type id. + */ +jack_port_type_id_t jack_port_type_id (const jack_port_t *port) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return TRUE if the jack_port_t belongs to the jack_client_t. + */ +int jack_port_is_mine (const jack_client_t *client, const jack_port_t *port) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return number of connections to or from @a port. + * + * @pre The calling client must own @a port. + */ +int jack_port_connected (const jack_port_t *port) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return TRUE if the locally-owned @a port is @b directly connected + * to the @a port_name. + * + * @see jack_port_name_size() + */ +int jack_port_connected_to (const jack_port_t *port, + const char *port_name) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return a null-terminated array of full port names to which the @a + * port is connected. If none, returns NULL. + * + * The caller is responsible for calling jack_free() on any non-NULL + * returned value. + * + * @param port locally owned jack_port_t pointer. + * + * @see jack_port_name_size(), jack_port_get_all_connections() + */ +const char ** jack_port_get_connections (const jack_port_t *port) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return a null-terminated array of full port names to which the @a + * port is connected. If none, returns NULL. + * + * The caller is responsible for calling jack_free() on any non-NULL + * returned value. + * + * This differs from jack_port_get_connections() in two important + * respects: + * + * 1) You may not call this function from code that is + * executed in response to a JACK event. For example, + * you cannot use it in a GraphReordered handler. + * + * 2) You need not be the owner of the port to get information + * about its connections. + * + * @see jack_port_name_size() + */ +const char ** jack_port_get_all_connections (const jack_client_t *client, + const jack_port_t *port) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * + * @deprecated This function will be removed from a future version + * of JACK. Do not use it. There is no replacement. It has + * turned out to serve essentially no purpose in real-life + * JACK clients. + */ +int jack_port_tie (jack_port_t *src, jack_port_t *dst) JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT; + +/** + * + * @deprecated This function will be removed from a future version + * of JACK. Do not use it. There is no replacement. It has + * turned out to serve essentially no purpose in real-life + * JACK clients. + */ +int jack_port_untie (jack_port_t *port) JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT; + +/** + * \bold THIS FUNCTION IS DEPRECATED AND SHOULD NOT BE USED IN + * NEW JACK CLIENTS + * + * Modify a port's short name. May be called at any time. If the + * resulting full name (including the @a "client_name:" prefix) is + * longer than jack_port_name_size(), it will be truncated. + * + * @return 0 on success, otherwise a non-zero error code. + */ +int jack_port_set_name (jack_port_t *port, const char *port_name) JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT; + +/** + * Modify a port's short name. May NOT be called from a callback handling a server event. + * If the resulting full name (including the @a "client_name:" prefix) is + * longer than jack_port_name_size(), it will be truncated. + * + * @return 0 on success, otherwise a non-zero error code. + * + * This differs from jack_port_set_name() by triggering PortRename notifications to + * clients that have registered a port rename handler. + */ +int jack_port_rename (jack_client_t* client, jack_port_t *port, const char *port_name) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Set @a alias as an alias for @a port. May be called at any time. + * If the alias is longer than jack_port_name_size(), it will be truncated. + * + * After a successful call, and until JACK exits or + * @function jack_port_unset_alias() is called, @alias may be + * used as a alternate name for the port. + * + * Ports can have up to two aliases - if both are already + * set, this function will return an error. + * + * @return 0 on success, otherwise a non-zero error code. + */ +int jack_port_set_alias (jack_port_t *port, const char *alias) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Remove @a alias as an alias for @a port. May be called at any time. + * + * After a successful call, @a alias can no longer be + * used as a alternate name for the port. + * + * @return 0 on success, otherwise a non-zero error code. + */ +int jack_port_unset_alias (jack_port_t *port, const char *alias) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Get any aliases known for @port. + * + * @return the number of aliases discovered for the port + */ +int jack_port_get_aliases (const jack_port_t *port, char* const aliases[2]) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * If @ref JackPortCanMonitor is set for this @a port, turn input + * monitoring on or off. Otherwise, do nothing. + */ +int jack_port_request_monitor (jack_port_t *port, int onoff) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * If @ref JackPortCanMonitor is set for this @a port_name, turn input + * monitoring on or off. Otherwise, do nothing. + * + * @return 0 on success, otherwise a non-zero error code. + * + * @see jack_port_name_size() + */ +int jack_port_request_monitor_by_name (jack_client_t *client, + const char *port_name, int onoff) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * If @ref JackPortCanMonitor is set for a port, this function turns + * on input monitoring if it was off, and turns it off if only one + * request has been made to turn it on. Otherwise it does nothing. + * + * @return 0 on success, otherwise a non-zero error code + */ +int jack_port_ensure_monitor (jack_port_t *port, int onoff) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return TRUE if input monitoring has been requested for @a port. + */ +int jack_port_monitoring_input (jack_port_t *port) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Establish a connection between two ports. + * + * When a connection exists, data written to the source port will + * be available to be read at the destination port. + * + * @pre The port types must be identical. + * + * @pre The @ref JackPortFlags of the @a source_port must include @ref + * JackPortIsOutput. + * + * @pre The @ref JackPortFlags of the @a destination_port must include + * @ref JackPortIsInput. + * + * @return 0 on success, EEXIST if the connection is already made, + * otherwise a non-zero error code + */ +int jack_connect (jack_client_t *client, + const char *source_port, + const char *destination_port) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Remove a connection between two ports. + * + * @pre The port types must be identical. + * + * @pre The @ref JackPortFlags of the @a source_port must include @ref + * JackPortIsOutput. + * + * @pre The @ref JackPortFlags of the @a destination_port must include + * @ref JackPortIsInput. + * + * @return 0 on success, otherwise a non-zero error code + */ +int jack_disconnect (jack_client_t *client, + const char *source_port, + const char *destination_port) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Perform the same function as jack_disconnect() using port handles + * rather than names. This avoids the name lookup inherent in the + * name-based version. + * + * Clients connecting their own ports are likely to use this function, + * while generic connection clients (e.g. patchbays) would use + * jack_disconnect(). + */ +int jack_port_disconnect (jack_client_t *client, jack_port_t *port) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return the maximum number of characters in a full JACK port name + * including the final NULL character. This value is a constant. + * + * A port's full name contains the owning client name concatenated + * with a colon (:) followed by its short name and a NULL + * character. + */ +int jack_port_name_size(void) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return the maximum number of characters in a JACK port type name + * including the final NULL character. This value is a constant. + */ +int jack_port_type_size(void) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return the buffersize of a port of type @arg port_type. + * + * this function may only be called in a buffer_size callback. + */ +size_t jack_port_type_get_buffer_size (jack_client_t *client, const char *port_type) JACK_WEAK_EXPORT; + +/*@}*/ + +/** + * @defgroup LatencyFunctions Managing and determining latency + * + * The purpose of JACK's latency API is to allow clients to + * easily answer two questions: + * + * - How long has it been since the data read from a port arrived + * at the edge of the JACK graph (either via a physical port + * or being synthesized from scratch)? + * + * - How long will it be before the data written to a port arrives + * at the edge of a JACK graph? + + * To help answering these two questions, all JACK ports have two + * latency values associated with them, both measured in frames: + * + * <b>capture latency</b>: how long since the data read from + * the buffer of a port arrived at + * a port marked with JackPortIsTerminal. + * The data will have come from the "outside + * world" if the terminal port is also + * marked with JackPortIsPhysical, or + * will have been synthesized by the client + * that owns the terminal port. + * + * <b>playback latency</b>: how long until the data + * written to the buffer of port will reach a port + * marked with JackPortIsTerminal. + * + * Both latencies might potentially have more than one value + * because there may be multiple pathways to/from a given port + * and a terminal port. Latency is therefore generally + * expressed a min/max pair. + * + * In most common setups, the minimum and maximum latency + * are the same, but this design accommodates more complex + * routing, and allows applications (and thus users) to + * detect cases where routing is creating an anomalous + * situation that may either need fixing or more + * sophisticated handling by clients that care about + * latency. + * + * See also @ref jack_set_latency_callback for details on how + * clients that add latency to the signal path should interact + * with JACK to ensure that the correct latency figures are + * used. + * @{ + */ + +/** + * The port latency is zero by default. Clients that control + * physical hardware with non-zero latency should call this + * to set the latency to its correct value. Note that the value + * should include any systemic latency present "outside" the + * physical hardware controlled by the client. For example, + * for a client controlling a digital audio interface connected + * to an external digital converter, the latency setting should + * include both buffering by the audio interface *and* the converter. + * + * @deprecated This method will be removed in the next major + * release of JACK. It should not be used in new code, and should + * be replaced by a latency callback that calls @ref + * jack_port_set_latency_range(). + */ +void jack_port_set_latency (jack_port_t *port, jack_nframes_t) JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT; + +/** + * return the latency range defined by @a mode for + * @a port, in frames. + * + * See @ref LatencyFunctions for the definition of each latency value. + * + * This function is best used from callbacks, specifically the latency callback. + * Before a port is connected, this returns the default latency: zero. + * Therefore it only makes sense to call jack_port_get_latency_range() when + * the port is connected, and that gets signalled by the latency callback. + * See @ref jack_set_latency_callback() for details. + */ +void jack_port_get_latency_range (jack_port_t *port, jack_latency_callback_mode_t mode, jack_latency_range_t *range) JACK_WEAK_EXPORT; + +/** + * set the minimum and maximum latencies defined by + * @a mode for @a port, in frames. + * + * See @ref LatencyFunctions for the definition of each latency value. + * + * This function should ONLY be used inside a latency + * callback. The client should determine the current + * value of the latency using @ref jack_port_get_latency_range() + * (called using the same mode as @a mode) + * and then add some number of frames to that reflects + * latency added by the client. + * + * How much latency a client adds will vary + * dramatically. For most clients, the answer is zero + * and there is no reason for them to register a latency + * callback and thus they should never call this + * function. + * + * More complex clients that take an input signal, + * transform it in some way and output the result but + * not during the same process() callback will + * generally know a single constant value to add + * to the value returned by @ref jack_port_get_latency_range(). + * + * Such clients would register a latency callback (see + * @ref jack_set_latency_callback) and must know what input + * ports feed which output ports as part of their + * internal state. Their latency callback will update + * the ports' latency values appropriately. + * + * A pseudo-code example will help. The @a mode argument to the latency + * callback will determine whether playback or capture + * latency is being set. The callback will use + * @ref jack_port_set_latency_range() as follows: + * + * \code + * jack_latency_range_t range; + * if (mode == JackPlaybackLatency) { + * foreach input_port in (all self-registered port) { + * jack_port_get_latency_range (port_feeding_input_port, JackPlaybackLatency, &range); + * range.min += min_delay_added_as_signal_flows_from port_feeding to input_port; + * range.max += max_delay_added_as_signal_flows_from port_feeding to input_port; + * jack_port_set_latency_range (input_port, JackPlaybackLatency, &range); + * } + * } else if (mode == JackCaptureLatency) { + * foreach output_port in (all self-registered port) { + * jack_port_get_latency_range (port_fed_by_output_port, JackCaptureLatency, &range); + * range.min += min_delay_added_as_signal_flows_from_output_port_to_fed_by_port; + * range.max += max_delay_added_as_signal_flows_from_output_port_to_fed_by_port; + * jack_port_set_latency_range (output_port, JackCaptureLatency, &range); + * } + * } + * \endcode + * + * In this relatively simple pseudo-code example, it is assumed that + * each input port or output is connected to only 1 output or input + * port respectively. + * + * If a port is connected to more than 1 other port, then the + * range.min and range.max values passed to @ref + * jack_port_set_latency_range() should reflect the minimum and + * maximum values across all connected ports. + * + * See the description of @ref jack_set_latency_callback for more + * information. + */ +void jack_port_set_latency_range (jack_port_t *port, jack_latency_callback_mode_t mode, jack_latency_range_t *range) JACK_WEAK_EXPORT; + +/** + * Request a complete recomputation of all port latencies. This + * can be called by a client that has just changed the internal + * latency of its port using jack_port_set_latency + * and wants to ensure that all signal pathways in the graph + * are updated with respect to the values that will be returned + * by jack_port_get_total_latency. It allows a client + * to change multiple port latencies without triggering a + * recompute for each change. + * + * @return zero for successful execution of the request. non-zero + * otherwise. + */ +int jack_recompute_total_latencies (jack_client_t *client) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return the time (in frames) between data being available or + * delivered at/to a port, and the time at which it arrived at or is + * delivered to the "other side" of the port. E.g. for a physical + * audio output port, this is the time between writing to the port and + * when the signal will leave the connector. For a physical audio + * input port, this is the time between the sound arriving at the + * connector and the corresponding frames being readable from the + * port. + * + * @deprecated This method will be removed in the next major + * release of JACK. It should not be used in new code, and should + * be replaced by jack_port_get_latency_range() in any existing + * use cases. + */ +jack_nframes_t jack_port_get_latency (jack_port_t *port) JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT; + +/** + * The maximum of the sum of the latencies in every + * connection path that can be drawn between the port and other + * ports with the @ref JackPortIsTerminal flag set. + * + * @deprecated This method will be removed in the next major + * release of JACK. It should not be used in new code, and should + * be replaced by jack_port_get_latency_range() in any existing + * use cases. + */ +jack_nframes_t jack_port_get_total_latency (jack_client_t *client, + jack_port_t *port) JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT; + +/** + * Request a complete recomputation of a port's total latency. This + * can be called by a client that has just changed the internal + * latency of its port using jack_port_set_latency + * and wants to ensure that all signal pathways in the graph + * are updated with respect to the values that will be returned + * by jack_port_get_total_latency. + * + * @return zero for successful execution of the request. non-zero + * otherwise. + * + * @deprecated This method will be removed in the next major + * release of JACK. It should not be used in new code, and should + * be replaced by jack_recompute_total_latencies() in any existing + * use cases. + */ +int jack_recompute_total_latency (jack_client_t*, jack_port_t* port) JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT; + +/*@}*/ + +/** + * @defgroup PortSearching Looking up ports + * @{ + */ + +/** + * @param port_name_pattern A regular expression used to select + * ports by name. If NULL or of zero length, no selection based + * on name will be carried out. + * @param type_name_pattern A regular expression used to select + * ports by type. If NULL or of zero length, no selection based + * on type will be carried out. + * @param flags A value used to select ports by their flags. + * If zero, no selection based on flags will be carried out. + * + * @return a NULL-terminated array of ports that match the specified + * arguments. The caller is responsible for calling jack_free() any + * non-NULL returned value. + * + * @see jack_port_name_size(), jack_port_type_size() + */ +const char ** jack_get_ports (jack_client_t *client, + const char *port_name_pattern, + const char *type_name_pattern, + unsigned long flags) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return address of the jack_port_t named @a port_name. + * + * @see jack_port_name_size() + */ +jack_port_t * jack_port_by_name (jack_client_t *client, const char *port_name) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return address of the jack_port_t of a @a port_id. + */ +jack_port_t * jack_port_by_id (jack_client_t *client, + jack_port_id_t port_id) JACK_OPTIONAL_WEAK_EXPORT; + +/*@}*/ + +/** + * @defgroup TimeFunctions Handling time + * @{ + * + * JACK time is in units of 'frames', according to the current sample rate. + * The absolute value of frame times is meaningless, frame times have meaning + * only relative to each other. + */ + +/** + * @return the estimated time in frames that has passed since the JACK + * server began the current process cycle. + */ +jack_nframes_t jack_frames_since_cycle_start (const jack_client_t *) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return the estimated current time in frames. + * This function is intended for use in other threads (not the process + * callback). The return value can be compared with the value of + * jack_last_frame_time to relate time in other threads to JACK time. + */ +jack_nframes_t jack_frame_time (const jack_client_t *) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return the precise time at the start of the current process cycle. + * This function may only be used from the process callback, and can + * be used to interpret timestamps generated by jack_frame_time() in + * other threads with respect to the current process cycle. + * + * This is the only jack time function that returns exact time: + * when used during the process callback it always returns the same + * value (until the next process callback, where it will return + * that value + nframes, etc). The return value is guaranteed to be + * monotonic and linear in this fashion unless an xrun occurs. + * If an xrun occurs, clients must check this value again, as time + * may have advanced in a non-linear way (e.g. cycles may have been skipped). + */ +jack_nframes_t jack_last_frame_time (const jack_client_t *client) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * This function may only be used from the process callback. + * It provides the internal cycle timing information as used by + * most of the other time related functions. This allows the + * caller to map between frame counts and microseconds with full + * precision (i.e. without rounding frame times to integers), + * and also provides e.g. the microseconds time of the start of + * the current cycle directly (it has to be computed otherwise). + * + * If the return value is zero, the following information is + * provided in the variables pointed to by the arguments: + * + * current_frames: the frame time counter at the start of the + * current cycle, same as jack_last_frame_time(). + * current_usecs: the microseconds time at the start of the + * current cycle. + * next_usecs: the microseconds time of the start of the next + * next cycle as computed by the DLL. + * period_usecs: the current best estimate of the period time in + * microseconds. + * + * NOTES: + * + * Because of the types used, all the returned values except period_usecs + * are unsigned. In computations mapping between frames and microseconds + * *signed* differences are required. The easiest way is to compute those + * separately and assign them to the appropriate signed variables, + * int32_t for frames and int64_t for usecs. See the implementation of + * jack_frames_to_time() and Jack_time_to_frames() for an example. + * + * Unless there was an xrun, skipped cycles, or the current cycle is the + * first after freewheeling or starting Jack, the value of current_usecs + * will always be the value of next_usecs of the previous cycle. + * + * The value of period_usecs will in general NOT be exactly equal to + * the difference of next_usecs and current_usecs. This is because to + * ensure stability of the DLL and continuity of the mapping, a fraction + * of the loop error must be included in next_usecs. For an accurate + * mapping between frames and microseconds, the difference of next_usecs + * and current_usecs should be used, and not period_usecs. + * + * @return zero if OK, non-zero otherwise. + */ +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) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return the estimated time in microseconds of the specified frame time + */ +jack_time_t jack_frames_to_time(const jack_client_t *client, jack_nframes_t) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return the estimated time in frames for the specified system time. + */ +jack_nframes_t jack_time_to_frames(const jack_client_t *client, jack_time_t) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return return JACK's current system time in microseconds, + * using the JACK clock source. + * + * The value returned is guaranteed to be monotonic, but not linear. + */ +jack_time_t jack_get_time(void) JACK_OPTIONAL_WEAK_EXPORT; + +/*@}*/ + +/** + * @defgroup ErrorOutput Controlling error/information output + */ +/*@{*/ + +/** + * Display JACK error message. + * + * Set via jack_set_error_function(), otherwise a JACK-provided + * default will print @a msg (plus a newline) to stderr. + * + * @param msg error message text (no newline at end). + */ +extern void (*jack_error_callback)(const char *msg) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Set the @ref jack_error_callback for error message display. + * Set it to NULL to restore default_jack_error_callback function. + * + * The JACK library provides two built-in callbacks for this purpose: + * default_jack_error_callback() and silent_jack_error_callback(). + */ +void jack_set_error_function (void (*func)(const char *)) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Display JACK info message. + * + * Set via jack_set_info_function(), otherwise a JACK-provided + * default will print @a msg (plus a newline) to stdout. + * + * @param msg info message text (no newline at end). + */ +extern void (*jack_info_callback)(const char *msg) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Set the @ref jack_info_callback for info message display. + * Set it to NULL to restore default_jack_info_callback function. + * + * The JACK library provides two built-in callbacks for this purpose: + * default_jack_info_callback() and silent_jack_info_callback(). + */ +void jack_set_info_function (void (*func)(const char *)) JACK_OPTIONAL_WEAK_EXPORT; + +/*@}*/ + +/** + * The free function to be used on memory returned by jack_port_get_connections, + * jack_port_get_all_connections, jack_get_ports and jack_get_internal_client_name functions. + * This is MANDATORY on Windows when otherwise all nasty runtime version related crashes can occur. + * Developers are strongly encouraged to use this function instead of the standard "free" function in new code. + * + * @param ptr the memory pointer to be deallocated. + */ +void jack_free(void* ptr) JACK_OPTIONAL_WEAK_EXPORT; + + +#ifdef __cplusplus +} +#endif + +#endif /* __jack_h__ */ diff --git a/pipewire-jack/jack/jslist.h b/pipewire-jack/jack/jslist.h new file mode 100644 index 0000000..3ec0ce9 --- /dev/null +++ b/pipewire-jack/jack/jslist.h @@ -0,0 +1,293 @@ +/* + Based on gslist.c from glib-1.2.9 (LGPL). + + Adaption to JACK, Copyright (C) 2002 Kai Vehmanen. + - replaced use of gtypes with normal ANSI C types + - glib's memory allocation routines replaced with + malloc/free calls + + This program 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 program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef __jack_jslist_h__ +#define __jack_jslist_h__ + +#include <stdlib.h> +#include <jack/systemdeps.h> + +#ifdef sun +#define __inline__ +#endif + +typedef struct _JSList JSList; + +typedef int (*JCompareFunc) (void* a, void* b); +struct _JSList +{ + void *data; + JSList *next; +}; + +static __inline__ +JSList* +jack_slist_alloc (void) +{ + JSList *new_list; + + new_list = (JSList*)malloc(sizeof(JSList)); + if (new_list) { + new_list->data = NULL; + new_list->next = NULL; + } + + return new_list; +} + +static __inline__ +JSList* +jack_slist_prepend (JSList* list, void* data) +{ + JSList *new_list; + + new_list = (JSList*)malloc(sizeof(JSList)); + if (new_list) { + new_list->data = data; + new_list->next = list; + } + + return new_list; +} + +#define jack_slist_next(slist) ((slist) ? (((JSList *)(slist))->next) : NULL) +static __inline__ +JSList* +jack_slist_last (JSList *list) +{ + if (list) { + while (list->next) + list = list->next; + } + + return list; +} + +static __inline__ +JSList* +jack_slist_remove_link (JSList *list, + JSList *link) +{ + JSList *tmp; + JSList *prev; + + prev = NULL; + tmp = list; + + while (tmp) { + if (tmp == link) { + if (prev) + prev->next = tmp->next; + if (list == tmp) + list = list->next; + + tmp->next = NULL; + break; + } + + prev = tmp; + tmp = tmp->next; + } + + return list; +} + +static __inline__ +void +jack_slist_free (JSList *list) +{ + while (list) { + JSList *next = list->next; + free(list); + list = next; + } +} + +static __inline__ +void +jack_slist_free_1 (JSList *list) +{ + if (list) { + free(list); + } +} + +static __inline__ +JSList* +jack_slist_remove (JSList *list, + void *data) +{ + JSList *tmp; + JSList *prev; + + prev = NULL; + tmp = list; + + while (tmp) { + if (tmp->data == data) { + if (prev) + prev->next = tmp->next; + if (list == tmp) + list = list->next; + + tmp->next = NULL; + jack_slist_free (tmp); + + break; + } + + prev = tmp; + tmp = tmp->next; + } + + return list; +} + +static __inline__ +unsigned int +jack_slist_length (JSList *list) +{ + unsigned int length; + + length = 0; + while (list) { + length++; + list = list->next; + } + + return length; +} + +static __inline__ +JSList* +jack_slist_find (JSList *list, + void *data) +{ + while (list) { + if (list->data == data) + break; + list = list->next; + } + + return list; +} + +static __inline__ +JSList* +jack_slist_copy (JSList *list) +{ + JSList *new_list = NULL; + + if (list) { + JSList *last; + + new_list = jack_slist_alloc (); + new_list->data = list->data; + last = new_list; + list = list->next; + while (list) { + last->next = jack_slist_alloc (); + last = last->next; + last->data = list->data; + list = list->next; + } + } + + return new_list; +} + +static __inline__ +JSList* +jack_slist_append (JSList *list, + void *data) +{ + JSList *new_list; + JSList *last; + + new_list = jack_slist_alloc (); + new_list->data = data; + + if (list) { + last = jack_slist_last (list); + last->next = new_list; + + return list; + } else + return new_list; +} + +static __inline__ +JSList* +jack_slist_sort_merge (JSList *l1, + JSList *l2, + JCompareFunc compare_func) +{ + JSList list, *l; + + l = &list; + + while (l1 && l2) { + if (compare_func(l1->data, l2->data) < 0) { + l = l->next = l1; + l1 = l1->next; + } else { + l = l->next = l2; + l2 = l2->next; + } + } + l->next = l1 ? l1 : l2; + + return list.next; +} + +static __inline__ +JSList* +jack_slist_sort (JSList *list, + JCompareFunc compare_func) +{ + JSList *l1, *l2; + + if (!list) + return NULL; + if (!list->next) + return list; + + l1 = list; + l2 = list->next; + + while ((l2 = l2->next) != NULL) { + if ((l2 = l2->next) == NULL) + break; + l1 = l1->next; + } + l2 = l1->next; + l1->next = NULL; + + return jack_slist_sort_merge (jack_slist_sort (list, compare_func), + jack_slist_sort (l2, compare_func), + compare_func); +} + +#endif /* __jack_jslist_h__ */ + diff --git a/pipewire-jack/jack/metadata.h b/pipewire-jack/jack/metadata.h new file mode 100644 index 0000000..75a4105 --- /dev/null +++ b/pipewire-jack/jack/metadata.h @@ -0,0 +1,322 @@ +/* + Copyright (C) 2011-2014 David Robillard + Copyright (C) 2013 Paul Davis + + This program 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 program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +/** + * @file jack/metadata.h + * @ingroup publicheader + * @brief JACK Metadata API + * + */ + +#ifndef __jack_metadata_h__ +#define __jack_metadata_h__ + +#include <jack/types.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @defgroup Metadata Metadata API. + * @{ + */ + +/** + * A single property (key:value pair). + * + * Although there is no semantics imposed on metadata keys and values, it is + * much less useful to use it to associate highly structured data with a port + * (or client), since this then implies the need for some (presumably + * library-based) code to parse the structure and be able to use it. + * + * The real goal of the metadata API is to be able to tag ports (and clients) + * with small amounts of data that is outside of the core JACK API but + * nevertheless useful. + */ +typedef struct { + /** The key of this property (URI string). */ + const char* key; + + /** The property value (null-terminated string). */ + const char* data; + + /** + * Type of data, either a MIME type or URI. + * + * If type is NULL or empty, the data is assumed to be a UTF-8 encoded + * string (text/plain). The data is a null-terminated string regardless of + * type, so values can always be copied, but clients should not try to + * interpret values of an unknown type. + * + * Example values: + * - image/png;base64 (base64 encoded PNG image) + * - http://www.w3.org/2001/XMLSchema#int (integer) + * + * Official types are preferred, but clients may use any syntactically + * valid MIME type (which start with a type and slash, like "text/..."). + * If a URI type is used, it must be a complete absolute URI + * (which start with a scheme and colon, like "http:"). + */ + const char* type; +} jack_property_t; + +/** + * Set a property on @p subject. + * + * See the above documentation for rules about @p subject and @p key. + * @param subject The subject to set the property on. + * @param key The key of the property. + * @param value The value of the property. + * @param type The type of the property. See the discussion of + * types in the definition of jack_property_t above. + * @return 0 on success. + */ +int +jack_set_property(jack_client_t*, + jack_uuid_t subject, + const char* key, + const char* value, + const char* type); + +/** + * Get a property on @p subject. + * + * @param subject The subject to get the property from. + * @param key The key of the property. + * @param value Set to the value of the property if found, or NULL otherwise. + * The caller must free this value with jack_free(). + * @param type The type of the property if set, or NULL. See the discussion + * of types in the definition of jack_property_t above. + * If non-null, the caller must free this value with jack_free(). + * + * @return 0 on success, -1 if the @p subject has no @p key property. + */ +int +jack_get_property(jack_uuid_t subject, + const char* key, + char** value, + char** type); + +/** + * A description of a subject (a set of properties). + */ +typedef struct { + jack_uuid_t subject; /**< Subject being described. */ + uint32_t property_cnt; /**< Number of elements in "properties". */ + jack_property_t* properties; /**< Array of properties. */ + uint32_t property_size; /**< Private, do not use. */ +} jack_description_t; + +/** + * Free a description. + * + * @param desc a jack_description_t whose associated memory will all be released + * @param free_description_itself if non-zero, then @param desc will also be passed to free() + */ +void +jack_free_description (jack_description_t* desc, int free_description_itself); + +/** + * Get a description of @p subject. + * @param subject The subject to get all properties of. + * @param desc Set to the description of subject if found, or NULL otherwise. + * The caller must free this value with jack_free_description(). + * @return the number of properties, -1 if no @p subject with any properties exists. + */ +int +jack_get_properties (jack_uuid_t subject, + jack_description_t* desc); + +/** + * Get descriptions for all subjects with metadata. + * @param descs Set to an array of descriptions. + * The caller must free each of these with jack_free_description(), + * and the array itself with jack_free(). + * @return the number of descriptions, or -1 on error. + */ +int +jack_get_all_properties (jack_description_t** descs); + +/** + * Remove a single property on a subject. + * + * @param client The JACK client making the request to remove the property. + * @param subject The subject to remove the property from. + * @param key The key of the property to be removed. + * + * @return 0 on success, -1 otherwise + */ +int jack_remove_property (jack_client_t* client, jack_uuid_t subject, const char* key); + +/** + * Remove all properties on a subject. + * + * @param client The JACK client making the request to remove some properties. + * @param subject The subject to remove all properties from. + * + * @return a count of the number of properties removed, or -1 on error. + */ +int jack_remove_properties (jack_client_t* client, jack_uuid_t subject); + +/** + * Remove all properties. + * + * WARNING!! This deletes all metadata managed by a running JACK server. + * Data lost cannot be recovered (though it can be recreated by new calls + * to jack_set_property()). + * + * @param client The JACK client making the request to remove all properties + * + * @return 0 on success, -1 otherwise + */ +int jack_remove_all_properties (jack_client_t* client); + +typedef enum { + PropertyCreated, + PropertyChanged, + PropertyDeleted +} jack_property_change_t; + +/** + * Prototype for the client supplied function that is called by the + * engine anytime a property or properties have been modified. + * + * Note that when the key is empty, it means all properties have been + * modified. This is often used to indicate that the removal of all keys. + * + * @param subject The subject the change relates to, this can be either a client or port + * @param key The key of the modified property (URI string) + * @param change Wherever the key has been created, changed or deleted + * @param arg pointer to a client supplied structure + */ +typedef void (*JackPropertyChangeCallback)(jack_uuid_t subject, + const char* key, + jack_property_change_t change, + void* arg); + +/** + * Arrange for @p client to call @p callback whenever a property is created, + * changed or deleted. + * + * @param client the JACK client making the request + * @param callback the function to be invoked when a property change occurs + * @param arg the argument to be passed to @param callback when it is invoked + * + * @return 0 success, -1 otherwise. + */ +int jack_set_property_change_callback (jack_client_t* client, + JackPropertyChangeCallback callback, + void* arg); + +/** + * A value that identifies what the hardware port is connected to (an external + * device of some kind). Possible values might be "E-Piano" or "Master 2 Track". + */ +extern const char* JACK_METADATA_CONNECTED; + +/** + * The supported event types of an event port. + * + * This is a kludge around Jack only supporting MIDI, particularly for OSC. + * This property is a comma-separated list of event types, currently "MIDI" or + * "OSC". If this contains "OSC", the port may carry OSC bundles (first byte + * '#') or OSC messages (first byte '/'). Note that the "status byte" of both + * OSC events is not a valid MIDI status byte, so MIDI clients that check the + * status byte will gracefully ignore OSC messages if the user makes an + * inappropriate connection. + */ +extern const char* JACK_METADATA_EVENT_TYPES; + +/** + * A value that should be shown when attempting to identify the + * specific hardware outputs of a client. Typical values might be + * "ADAT1", "S/PDIF L" or "MADI 43". + */ +extern const char* JACK_METADATA_HARDWARE; + +/** + * A value with a MIME type of "image/png;base64" that is an encoding of an + * NxN (with 32 < N <= 128) image to be used when displaying a visual + * representation of that client or port. + */ +extern const char* JACK_METADATA_ICON_LARGE; + +/** + * The name of the icon for the subject (typically client). + * + * This is used for looking up icons on the system, possibly with many sizes or + * themes. Icons should be searched for according to the freedesktop Icon + * + * Theme Specification: + * https://specifications.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html + */ +extern const char* JACK_METADATA_ICON_NAME; + +/** + * A value with a MIME type of "image/png;base64" that is an encoding of an + * NxN (with N <=32) image to be used when displaying a visual representation + * of that client or port. + */ +extern const char* JACK_METADATA_ICON_SMALL; + +/** + * Order for a port. + * + * This is used to specify the best order to show ports in user interfaces. + * The value MUST be an integer. There are no other requirements, so there may + * be gaps in the orders for several ports. Applications should compare the + * orders of ports to determine their relative order, but must not assign any + * other relevance to order values. + * + * It is encouraged to use http://www.w3.org/2001/XMLSchema#int as the type. + */ +extern const char* JACK_METADATA_ORDER; + +/** + * A value that should be shown to the user when displaying a port to the user, + * unless the user has explicitly overridden that a request to show the port + * name, or some other key value. + */ +extern const char* JACK_METADATA_PRETTY_NAME; + +/** + */ +extern const char* JACK_METADATA_PORT_GROUP; + +/** + * The type of an audio signal. + * + * This property allows audio ports to be tagged with a "meaning". The value + * is a simple string. Currently, the only type is "CV", for "control voltage" + * ports. Hosts SHOULD be take care to not treat CV ports as audible and send + * their output directly to speakers. In particular, CV ports are not + * necessarily periodic at all and may have very high DC. + */ +extern const char* JACK_METADATA_SIGNAL_TYPE; + +/** + * @} + */ + +#ifdef __cplusplus +} /* namespace */ +#endif + +#endif /* __jack_metadata_h__ */ diff --git a/pipewire-jack/jack/midiport.h b/pipewire-jack/jack/midiport.h new file mode 100644 index 0000000..0ae5e79 --- /dev/null +++ b/pipewire-jack/jack/midiport.h @@ -0,0 +1,197 @@ +/* + Copyright (C) 2004 Ian Esten + + This program 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 program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + + +#ifndef __JACK_MIDIPORT_H +#define __JACK_MIDIPORT_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <jack/weakmacros.h> +#include <jack/types.h> +#include <stdlib.h> + + +/** Type for raw event data contained in @ref jack_midi_event_t. */ +typedef unsigned char jack_midi_data_t; + + +/** A Jack MIDI event. */ +typedef struct _jack_midi_event +{ + jack_nframes_t time; /**< Sample index at which event is valid */ + size_t size; /**< Number of bytes of data in \a buffer */ + jack_midi_data_t *buffer; /**< Raw MIDI data */ +} jack_midi_event_t; + + +/** + * @defgroup MIDIAPI Reading and writing MIDI data + * @{ + */ + +/** Get number of events in a port buffer. + * + * @param port_buffer Port buffer from which to retrieve event. + * @return number of events inside @a port_buffer + */ +uint32_t +jack_midi_get_event_count(void* port_buffer) JACK_OPTIONAL_WEAK_EXPORT; + + +/** Get a MIDI event from an event port buffer. + * + * Jack MIDI is normalised, the MIDI event returned by this function is + * guaranteed to be a complete MIDI event (the status byte will always be + * present, and no realtime events will interspersed with the event). + * + * This rule does not apply to System Exclusive MIDI messages + * since they can be of arbitrary length. + * To maintain smooth realtime operation such events CAN be delivered + * as multiple, non-normalised events. + * The maximum size of one event "chunk" depends on the MIDI backend in use. + * For example the midiseq driver will create chunks of 256 bytes. + * The first SysEx "chunked" event starts with 0xF0 and the last + * delivered chunk ends with 0xF7. + * To receive the full SysEx message, a caller of jack_midi_event_get() + * must concatenate chunks until a chunk ends with 0xF7. + * + * @param event Event structure to store retrieved event in. + * @param port_buffer Port buffer from which to retrieve event. + * @param event_index Index of event to retrieve. + * @return 0 on success, ENODATA if buffer is empty. + */ +int +jack_midi_event_get(jack_midi_event_t *event, + void *port_buffer, + uint32_t event_index) JACK_OPTIONAL_WEAK_EXPORT; + + +/** Clear an event buffer. + * + * This should be called at the beginning of each process cycle before calling + * @ref jack_midi_event_reserve or @ref jack_midi_event_write. This + * function may not be called on an input port's buffer. + * + * @param port_buffer Port buffer to clear (must be an output port buffer). + */ +void +jack_midi_clear_buffer(void *port_buffer) JACK_OPTIONAL_WEAK_EXPORT; + +/** Reset an event buffer (from data allocated outside of JACK). + * + * This should be called at the beginning of each process cycle before calling + * @ref jack_midi_event_reserve or @ref jack_midi_event_write. This + * function may not be called on an input port's buffer. + * + * @deprecated Please use jack_midi_clear_buffer(). + * + * @param port_buffer Port buffer to reset. + */ +void +jack_midi_reset_buffer(void *port_buffer) JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT; + + +/** Get the size of the largest event that can be stored by the port. + * + * This function returns the current space available, taking into account + * events already stored in the port. + * + * @param port_buffer Port buffer to check size of. + */ +size_t +jack_midi_max_event_size(void* port_buffer) JACK_OPTIONAL_WEAK_EXPORT; + + +/** Allocate space for an event to be written to an event port buffer. + * + * Clients are to write the actual event data to be written starting at the + * pointer returned by this function. Clients must not write more than + * @a data_size bytes into this buffer. Clients must write normalised + * MIDI data to the port - no running status and no (1-byte) realtime + * messages interspersed with other messages (realtime messages are fine + * when they occur on their own, like other messages). + * + * Events must be written in order, sorted by their sample offsets. + * JACK will not sort the events for you, and will refuse to store + * out-of-order events. + * + * @param port_buffer Buffer to write event to. + * @param time Sample offset of event. + * @param data_size Length of event's raw data in bytes. + * @return Pointer to the beginning of the reserved event's data buffer, or + * NULL on error (ie not enough space). + */ +jack_midi_data_t* +jack_midi_event_reserve(void *port_buffer, + jack_nframes_t time, + size_t data_size) JACK_OPTIONAL_WEAK_EXPORT; + + +/** Write an event into an event port buffer. + * + * This function is simply a wrapper for @ref jack_midi_event_reserve + * which writes the event data into the space reserved in the buffer. + * + * Clients must not write more than + * @a data_size bytes into this buffer. Clients must write normalised + * MIDI data to the port - no running status and no (1-byte) realtime + * messages interspersed with other messages (realtime messages are fine + * when they occur on their own, like other messages). + * + * Events must be written in order, sorted by their sample offsets. + * JACK will not sort the events for you, and will refuse to store + * out-of-order events. + * + * @param port_buffer Buffer to write event to. + * @param time Sample offset of event. + * @param data Message data to be written. + * @param data_size Length of @a data in bytes. + * @return 0 on success, ENOBUFS if there's not enough space in buffer for event. + */ +int +jack_midi_event_write(void *port_buffer, + jack_nframes_t time, + const jack_midi_data_t *data, + size_t data_size) JACK_OPTIONAL_WEAK_EXPORT; + + +/** Get the number of events that could not be written to @a port_buffer. + * + * This function returning a non-zero value implies @a port_buffer is full. + * Currently the only way this can happen is if events are lost on port mixdown. + * + * @param port_buffer Port to receive count for. + * @returns Number of events that could not be written to @a port_buffer. + */ +uint32_t +jack_midi_get_lost_event_count(void *port_buffer) JACK_OPTIONAL_WEAK_EXPORT; + +/*@}*/ + +#ifdef __cplusplus +} +#endif + + +#endif /* __JACK_MIDIPORT_H */ + + diff --git a/pipewire-jack/jack/net.h b/pipewire-jack/jack/net.h new file mode 100644 index 0000000..3f214be --- /dev/null +++ b/pipewire-jack/jack/net.h @@ -0,0 +1,429 @@ +/* + Copyright (C) 2009-2010 Grame + + This program 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 program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef __net_h__ +#define __net_h__ + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include <jack/systemdeps.h> +#include <jack/types.h> +#include <jack/weakmacros.h> + +#define DEFAULT_MULTICAST_IP "225.3.19.154" +#define DEFAULT_PORT 19000 +#define DEFAULT_MTU 1500 +#define MASTER_NAME_SIZE 256 + +// Possible error codes + +#define NO_ERROR 0 +#define SOCKET_ERROR -1 +#define SYNC_PACKET_ERROR -2 +#define DATA_PACKET_ERROR -3 + +#define RESTART_CB_API 1 + +enum JackNetEncoder { + + JackFloatEncoder = 0, // samples are transmitted as float + JackIntEncoder = 1, // samples are transmitted as 16 bits integer + JackCeltEncoder = 2, // samples are transmitted using CELT codec (http://www.celt-codec.org/) + JackOpusEncoder = 3, // samples are transmitted using OPUS codec (http://www.opus-codec.org/) +}; + +typedef struct { + + int audio_input; // from master or to slave (-1 to take master audio physical inputs) + int audio_output; // to master or from slave (-1 to take master audio physical outputs) + int midi_input; // from master or to slave (-1 to take master MIDI physical inputs) + int midi_output; // to master or from slave (-1 to take master MIDI physical outputs) + int mtu; // network Maximum Transmission Unit + int time_out; // in second, -1 means infinite + int encoder; // encoder type (one of JackNetEncoder) + int kbps; // KB per second for CELT or OPUS codec + int latency; // network latency in number of buffers + +} jack_slave_t; + +typedef struct { + + int audio_input; // master audio physical outputs (-1 to take slave wanted audio inputs) + int audio_output; // master audio physical inputs (-1 to take slave wanted audio outputs) + int midi_input; // master MIDI physical outputs (-1 to take slave wanted MIDI inputs) + int midi_output; // master MIDI physical inputs (-1 to take slave wanted MIDI outputs) + jack_nframes_t buffer_size; // master buffer size + jack_nframes_t sample_rate; // master sample rate + char master_name[MASTER_NAME_SIZE]; // master machine name + int time_out; // in second, -1 means infinite + int partial_cycle; // if 'true', partial buffers will be used + +} jack_master_t; + +/** + * jack_net_slave_t is an opaque type. You may only access it using the + * API provided. + */ +typedef struct _jack_net_slave jack_net_slave_t; + + /** + * Open a network connection with the master machine. + * + * @param ip the multicast address of the master + * @param port the connection port + * @param name the JACK client name + * @param request a connection request structure + * @param result a connection result structure + * + * @return Opaque net handle if successful or NULL in case of error. + */ +jack_net_slave_t* jack_net_slave_open(const char* ip, int port, const char* name, jack_slave_t* request, jack_master_t* result); + +/** + * Close the network connection with the master machine. + * + * @param net the network connection to be closed + * + * @return 0 on success, otherwise a non-zero error code + */ +int jack_net_slave_close(jack_net_slave_t* net); + +/** + * Prototype for Process callback. + * + * @param nframes buffer size + * @param audio_input number of audio inputs + * @param audio_input_buffer an array of audio input buffers (from master) + * @param midi_input number of MIDI inputs + * @param midi_input_buffer an array of MIDI input buffers (from master) + * @param audio_output number of audio outputs + * @param audio_output_buffer an array of audio output buffers (to master) + * @param midi_output number of MIDI outputs + * @param midi_output_buffer an array of MIDI output buffers (to master) + * @param arg pointer to a client supplied structure supplied by jack_set_net_process_callback() + * + * @return zero on success, non-zero on error + */ +typedef int (* JackNetSlaveProcessCallback) (jack_nframes_t buffer_size, + int audio_input, + float** audio_input_buffer, + int midi_input, + void** midi_input_buffer, + int audio_output, + float** audio_output_buffer, + int midi_output, + void** midi_output_buffer, + void* data); + +/** + * Set network process callback. + * + * @param net the network connection + * @param net_callback the process callback + * @param arg pointer to a client supplied structure + * + * @return 0 on success, otherwise a non-zero error code + */ +int jack_set_net_slave_process_callback(jack_net_slave_t * net, JackNetSlaveProcessCallback net_callback, void *arg); + +/** + * Start processing thread, the net_callback will start to be called. + * + * @param net the network connection + * + * @return 0 on success, otherwise a non-zero error code + */ +int jack_net_slave_activate(jack_net_slave_t* net); + +/** + * Stop processing thread. + * + * @param net the network connection + * + * @return 0 on success, otherwise a non-zero error code + */ +int jack_net_slave_deactivate(jack_net_slave_t* net); + +/** + * Test if slave is still active. + * + * @param net the network connection + * + * @return a boolean + */ +int jack_net_slave_is_active(jack_net_slave_t* net); + +/** + * Prototype for BufferSize callback. + * + * @param nframes buffer size + * @param arg pointer to a client supplied structure supplied by jack_set_net_buffer_size_callback() + * + * @return zero on success, non-zero on error + */ +typedef int (*JackNetSlaveBufferSizeCallback)(jack_nframes_t nframes, void *arg); + +/** + * Set network buffer size callback. + * + * @param net the network connection + * @param bufsize_callback the buffer size callback + * @param arg pointer to a client supplied structure + * + * @return 0 on success, otherwise a non-zero error code + */ +int jack_set_net_slave_buffer_size_callback(jack_net_slave_t *net, JackNetSlaveBufferSizeCallback bufsize_callback, void *arg); + +/** + * Prototype for SampleRate callback. + * + * @param nframes sample rate + * @param arg pointer to a client supplied structure supplied by jack_set_net_sample_rate_callback() + * + * @return zero on success, non-zero on error + */ +typedef int (*JackNetSlaveSampleRateCallback)(jack_nframes_t nframes, void *arg); + +/** + * Set network sample rate callback. + * + * @param net the network connection + * @param samplerate_callback the sample rate callback + * @param arg pointer to a client supplied structure + * + * @return 0 on success, otherwise a non-zero error code + */ +int jack_set_net_slave_sample_rate_callback(jack_net_slave_t *net, JackNetSlaveSampleRateCallback samplerate_callback, void *arg); + +/** + * Prototype for server Shutdown callback (if not set, the client will just restart, waiting for an available master again). + * + * @param arg pointer to a client supplied structure supplied by jack_set_net_shutdown_callback() + */ +typedef void (*JackNetSlaveShutdownCallback)(void* arg); + +/** + * Set network shutdown callback. + * + * @param net the network connection + * @param shutdown_callback the shutdown callback + * @param arg pointer to a client supplied structure + * + * @return 0 on success, otherwise a non-zero error code + */ +int jack_set_net_slave_shutdown_callback(jack_net_slave_t *net, JackNetSlaveShutdownCallback shutdown_callback, void *arg) JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT; + +/** + * Prototype for server Restart callback : this is the new preferable way to be notified when the master has disappeared. + * The client may want to retry connecting a certain number of time (which will be done using the time_out value given in jack_net_slave_open) + * by returning 0. Otherwise returning a non-zero error code will definitively close the connection + * (and jack_net_slave_is_active will later on return false). + * If both Shutdown and Restart are supplied, Restart callback will be used. + * + * @param arg pointer to a client supplied structure supplied by jack_set_net_restart_callback() + * + * @return 0 on success, otherwise a non-zero error code + */ +typedef int (*JackNetSlaveRestartCallback)(void* arg); + +/** + * Set network restart callback. + * + * @param net the network connection + * @param restart_callback the shutdown callback + * @param arg pointer to a client supplied structure + * + * @return 0 on success, otherwise a non-zero error code + */ +int jack_set_net_slave_restart_callback(jack_net_slave_t *net, JackNetSlaveRestartCallback restart_callback, void *arg) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Prototype for server Error callback. + * + * @param error_code an error code (see "Possible error codes") + * @param arg pointer to a client supplied structure supplied by jack_set_net_error_callback() + */ +typedef void (*JackNetSlaveErrorCallback) (int error_code, void* arg); + +/** + * Set error restart callback. + * + * @param net the network connection + * @param error_callback the error callback + * @param arg pointer to a client supplied structure + * + * @return 0 on success, otherwise a non-zero error code + */ +int jack_set_net_slave_error_callback(jack_net_slave_t *net, JackNetSlaveErrorCallback error_callback, void *arg) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * jack_net_master_t is an opaque type, you may only access it using the API provided. + */ +typedef struct _jack_net_master jack_net_master_t; + + /** + * Open a network connection with the slave machine. + * + * @param ip the multicast address of the master + * @param port the connection port + * @param request a connection request structure + * @param result a connection result structure + * + * @return Opaque net handle if successful or NULL in case of error. + */ +jack_net_master_t* jack_net_master_open(const char* ip, int port, jack_master_t* request, jack_slave_t* result); + +/** + * Close the network connection with the slave machine. + * + * @param net the network connection to be closed + * + * @return 0 on success, otherwise a non-zero error code + */ +int jack_net_master_close(jack_net_master_t* net); + +/** + * Receive sync and data from the network (complete buffer). + * + * @param net the network connection + * @param audio_input number of audio inputs + * @param audio_input_buffer an array of audio input buffers + * @param midi_input number of MIDI inputs + * @param midi_input_buffer an array of MIDI input buffers + * + * @return zero on success, non-zero on error + */ +int jack_net_master_recv(jack_net_master_t* net, int audio_input, float** audio_input_buffer, int midi_input, void** midi_input_buffer); + +/** + * Receive sync and data from the network (incomplete buffer). + * + * @param net the network connection + * @param audio_input number of audio inputs + * @param audio_input_buffer an array of audio input buffers + * @param midi_input number of MIDI inputs + * @param midi_input_buffer an array of MIDI input buffers + * @param frames the number of frames to receive + * + * @return zero on success, non-zero on error + */ +int jack_net_master_recv_slice(jack_net_master_t* net, int audio_input, float** audio_input_buffer, int midi_input, void** midi_input_buffer, int frames); + +/** + * Send sync and data to the network (complete buffer). + * + * @param net the network connection + * @param audio_output number of audio outputs + * @param audio_output_buffer an array of audio output buffers + * @param midi_output number of MIDI outputs + * @param midi_output_buffer an array of MIDI output buffers + * + * @return zero on success, non-zero on error + */ +int jack_net_master_send(jack_net_master_t* net, int audio_output, float** audio_output_buffer, int midi_output, void** midi_output_buffer); + +/** + * Send sync and data to the network (incomplete buffer). + * + * @param net the network connection + * @param audio_output number of audio outputs + * @param audio_output_buffer an array of audio output buffers + * @param midi_output number of MIDI outputs + * @param midi_output_buffer an array of MIDI output buffers + * @param frames the number of frames to send + * + * @return zero on success, non-zero on error + */ +int jack_net_master_send_slice(jack_net_master_t* net, int audio_output, float** audio_output_buffer, int midi_output, void** midi_output_buffer, int frames); + +// Experimental Adapter API + +/** + * jack_adapter_t is an opaque type, you may only access it using the API provided. + */ +typedef struct _jack_adapter jack_adapter_t; + +/** + * Create an adapter. + * + * @param input number of audio inputs + * @param output of audio outputs + * @param host_buffer_size the host buffer size in frames + * @param host_sample_rate the host buffer sample rate + * @param adapted_buffer_size the adapted buffer size in frames + * @param adapted_sample_rate the adapted buffer sample rate + * + * @return 0 on success, otherwise a non-zero error code + */ +jack_adapter_t* jack_create_adapter(int input, int output, + jack_nframes_t host_buffer_size, + jack_nframes_t host_sample_rate, + jack_nframes_t adapted_buffer_size, + jack_nframes_t adapted_sample_rate); + +/** + * Destroy an adapter. + * + * @param adapter the adapter to be destroyed + * + * @return 0 on success, otherwise a non-zero error code + */ +int jack_destroy_adapter(jack_adapter_t* adapter); + +/** + * Flush internal state of an adapter. + * + * @param adapter the adapter to be flushed + * + * @return 0 on success, otherwise a non-zero error code + */ +void jack_flush_adapter(jack_adapter_t* adapter); + +/** + * Push input to and pull output from adapter ringbuffer. + * + * @param adapter the adapter + * @param input an array of audio input buffers + * @param output an array of audio output buffers + * @param frames number of frames + * + * @return 0 on success, otherwise a non-zero error code + */ +int jack_adapter_push_and_pull(jack_adapter_t* adapter, float** input, float** output, unsigned int frames); + +/** + * Pull input from and push output to adapter ringbuffer. + * + * @param adapter the adapter + * @param input an array of audio input buffers + * @param output an array of audio output buffers + * @param frames number of frames + * + * @return 0 on success, otherwise a non-zero error code + */ +int jack_adapter_pull_and_push(jack_adapter_t* adapter, float** input, float** output, unsigned int frames); + +#ifdef __cplusplus +} +#endif + +#endif /* __net_h__ */ diff --git a/pipewire-jack/jack/ringbuffer.h b/pipewire-jack/jack/ringbuffer.h new file mode 100644 index 0000000..5204383 --- /dev/null +++ b/pipewire-jack/jack/ringbuffer.h @@ -0,0 +1,243 @@ +/* + Copyright (C) 2000 Paul Davis + Copyright (C) 2003 Rohan Drape + + This program 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 program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef _RINGBUFFER_H +#define _RINGBUFFER_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include <sys/types.h> + +/** @file ringbuffer.h + * + * A set of library functions to make lock-free ringbuffers available + * to JACK clients. The `capture_client.c' (in the example_clients + * directory) is a fully functioning user of this API. + * + * The key attribute of a ringbuffer is that it can be safely accessed + * by two threads simultaneously -- one reading from the buffer and + * the other writing to it -- without using any synchronization or + * mutual exclusion primitives. For this to work correctly, there can + * only be a single reader and a single writer thread. Their + * identities cannot be interchanged. + */ + +typedef struct { + char *buf; + size_t len; +} +jack_ringbuffer_data_t ; + +typedef struct { + char *buf; + volatile size_t write_ptr; + volatile size_t read_ptr; + size_t size; + size_t size_mask; + int mlocked; +} +jack_ringbuffer_t ; + +/** + * Allocates a ringbuffer data structure of a specified size. The + * caller must arrange for a call to jack_ringbuffer_free() to release + * the memory associated with the ringbuffer. + * + * @param sz the ringbuffer size in bytes. + * + * @return a pointer to a new jack_ringbuffer_t, if successful; NULL + * otherwise. + */ +jack_ringbuffer_t *jack_ringbuffer_create(size_t sz); + +/** + * Frees the ringbuffer data structure allocated by an earlier call to + * jack_ringbuffer_create(). + * + * @param rb a pointer to the ringbuffer structure. + */ +void jack_ringbuffer_free(jack_ringbuffer_t *rb); + +/** + * Fill a data structure with a description of the current readable + * data held in the ringbuffer. This description is returned in a two + * element array of jack_ringbuffer_data_t. Two elements are needed + * because the data to be read may be split across the end of the + * ringbuffer. + * + * The first element will always contain a valid @a len field, which + * may be zero or greater. If the @a len field is non-zero, then data + * can be read in a contiguous fashion using the address given in the + * corresponding @a buf field. + * + * If the second element has a non-zero @a len field, then a second + * contiguous stretch of data can be read from the address given in + * its corresponding @a buf field. + * + * @param rb a pointer to the ringbuffer structure. + * @param vec a pointer to a 2 element array of jack_ringbuffer_data_t. + * + */ +void jack_ringbuffer_get_read_vector(const jack_ringbuffer_t *rb, + jack_ringbuffer_data_t *vec); + +/** + * Fill a data structure with a description of the current writable + * space in the ringbuffer. The description is returned in a two + * element array of jack_ringbuffer_data_t. Two elements are needed + * because the space available for writing may be split across the end + * of the ringbuffer. + * + * The first element will always contain a valid @a len field, which + * may be zero or greater. If the @a len field is non-zero, then data + * can be written in a contiguous fashion using the address given in + * the corresponding @a buf field. + * + * If the second element has a non-zero @a len field, then a second + * contiguous stretch of data can be written to the address given in + * the corresponding @a buf field. + * + * @param rb a pointer to the ringbuffer structure. + * @param vec a pointer to a 2 element array of jack_ringbuffer_data_t. + */ +void jack_ringbuffer_get_write_vector(const jack_ringbuffer_t *rb, + jack_ringbuffer_data_t *vec); + +/** + * Read data from the ringbuffer. + * + * @param rb a pointer to the ringbuffer structure. + * @param dest a pointer to a buffer where data read from the + * ringbuffer will go. + * @param cnt the number of bytes to read. + * + * @return the number of bytes read, which may range from 0 to cnt. + */ +size_t jack_ringbuffer_read(jack_ringbuffer_t *rb, char *dest, size_t cnt); + +/** + * Read data from the ringbuffer. Opposed to jack_ringbuffer_read() + * this function does not move the read pointer. Thus it's + * a convenient way to inspect data in the ringbuffer in a + * continuous fashion. The price is that the data is copied + * into a user provided buffer. For "raw" non-copy inspection + * of the data in the ringbuffer use jack_ringbuffer_get_read_vector(). + * + * @param rb a pointer to the ringbuffer structure. + * @param dest a pointer to a buffer where data read from the + * ringbuffer will go. + * @param cnt the number of bytes to read. + * + * @return the number of bytes read, which may range from 0 to cnt. + */ +size_t jack_ringbuffer_peek(jack_ringbuffer_t *rb, char *dest, size_t cnt); + +/** + * Advance the read pointer. + * + * After data have been read from the ringbuffer using the pointers + * returned by jack_ringbuffer_get_read_vector(), use this function to + * advance the buffer pointers, making that space available for future + * write operations. + * + * @param rb a pointer to the ringbuffer structure. + * @param cnt the number of bytes read. + */ +void jack_ringbuffer_read_advance(jack_ringbuffer_t *rb, size_t cnt); + +/** + * Return the number of bytes available for reading. + * + * @param rb a pointer to the ringbuffer structure. + * + * @return the number of bytes available to read. + */ +size_t jack_ringbuffer_read_space(const jack_ringbuffer_t *rb); + +/** + * Lock a ringbuffer data block into memory. + * + * Uses the mlock() system call. This is not a realtime operation. + * + * @param rb a pointer to the ringbuffer structure. + */ +int jack_ringbuffer_mlock(jack_ringbuffer_t *rb); + +/** + * Reset the read and write pointers, making an empty buffer. + * + * This is not thread safe. + * + * @param rb a pointer to the ringbuffer structure. + */ +void jack_ringbuffer_reset(jack_ringbuffer_t *rb); + +/** + * Reset the internal "available" size, and read and write pointers, making an empty buffer. + * + * This is not thread safe. + * + * @param rb a pointer to the ringbuffer structure. + * @param sz the new size, that must be less than allocated size. + */ +void jack_ringbuffer_reset_size (jack_ringbuffer_t * rb, size_t sz); + +/** + * Write data into the ringbuffer. + * + * @param rb a pointer to the ringbuffer structure. + * @param src a pointer to the data to be written to the ringbuffer. + * @param cnt the number of bytes to write. + * + * @return the number of bytes write, which may range from 0 to cnt + */ +size_t jack_ringbuffer_write(jack_ringbuffer_t *rb, const char *src, + size_t cnt); + +/** + * Advance the write pointer. + * + * After data have been written the ringbuffer using the pointers + * returned by jack_ringbuffer_get_write_vector(), use this function + * to advance the buffer pointer, making the data available for future + * read operations. + * + * @param rb a pointer to the ringbuffer structure. + * @param cnt the number of bytes written. + */ +void jack_ringbuffer_write_advance(jack_ringbuffer_t *rb, size_t cnt); + +/** + * Return the number of bytes available for writing. + * + * @param rb a pointer to the ringbuffer structure. + * + * @return the amount of free space (in bytes) available for writing. + */ +size_t jack_ringbuffer_write_space(const jack_ringbuffer_t *rb); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/pipewire-jack/jack/session.h b/pipewire-jack/jack/session.h new file mode 100644 index 0000000..1198a09 --- /dev/null +++ b/pipewire-jack/jack/session.h @@ -0,0 +1,302 @@ +/* + Copyright (C) 2001 Paul Davis + Copyright (C) 2004 Jack O'Quin + Copyright (C) 2010 Torben Hohn + + This program 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 program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef __jack_session_h__ +#define __jack_session_h__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <jack/types.h> +#include <jack/weakmacros.h> + +/** + * @defgroup SessionClientFunctions Session API for clients. + * + * @deprecated Use of JACK-Session is currently deprecated and unsupported. + * JACK developers recommend the use of NSM instead. + * See https://github.com/linuxaudio/new-session-manager + * @{ + */ + + +/** + * Session event type. + * + * If a client can't save templates, i might just do a normal save. + * + * There is no "quit without saving" event because a client might refuse to + * quit when it has unsaved data, but other clients may have already quit. + * This results in too much confusion, so it is unsupported. + */ +enum JackSessionEventType { + /** + * Save the session completely. + * + * The client may save references to data outside the provided directory, + * but it must do so by creating a link inside the provided directory and + * referring to that in any save files. The client must not refer to data + * files outside the provided directory directly in save files, because + * this makes it impossible for the session manager to create a session + * archive for distribution or archival. + */ + JackSessionSave = 1, + + /** + * Save the session completely, then quit. + * + * The rules for saving are exactly the same as for JackSessionSave. + */ + JackSessionSaveAndQuit = 2, + + /** + * Save a session template. + * + * A session template is a "skeleton" of the session, but without any data. + * Clients must save a session that, when restored, will create the same + * ports as a full save would have. However, the actual data contained in + * the session may not be saved (e.g. a DAW would create the necessary + * tracks, but not save the actual recorded data). + */ + JackSessionSaveTemplate = 3 +}; + +typedef enum JackSessionEventType jack_session_event_type_t; + +/** + * @ref jack_session_flags_t bits + */ +enum JackSessionFlags { + /** + * An error occurred while saving. + */ + JackSessionSaveError = 0x01, + + /** + * Client needs to be run in a terminal. + */ + JackSessionNeedTerminal = 0x02 +}; + +/** + * Session flags. + */ +typedef enum JackSessionFlags jack_session_flags_t; + +struct _jack_session_event { + /** + * The type of this session event. + */ + jack_session_event_type_t type; + + /** + * Session directory path, with trailing separator. + * + * This directory is exclusive to the client; when saving the client may + * create any files it likes in this directory. + */ + const char *session_dir; + + /** + * Client UUID which must be passed to jack_client_open on session load. + * + * The client can specify this in the returned command line, or save it + * in a state file within the session directory. + */ + const char *client_uuid; + + /** + * Reply (set by client): the command line needed to restore the client. + * + * This is a platform dependent command line. It must contain + * ${SESSION_DIR} instead of the actual session directory path. More + * generally, just as in session files, clients should not include any + * paths outside the session directory here as this makes + * archival/distribution impossible. + * + * This field is set to NULL by Jack when the event is delivered to the + * client. The client must set to allocated memory that is safe to + * free(). This memory will be freed by jack_session_event_free. + */ + char *command_line; + + /** + * Reply (set by client): Session flags. + */ + jack_session_flags_t flags; + + /** + * Future flags. Set to zero for now. + */ + uint32_t future; +}; + +typedef struct _jack_session_event jack_session_event_t; + +/** + * Prototype for the client supplied function that is called + * whenever a session notification is sent via jack_session_notify(). + * + * Ownership of the memory of @a event is passed to the application. + * It must be freed using jack_session_event_free when it's not used anymore. + * + * The client must promptly call jack_session_reply for this event. + * + * @deprecated Use of JACK-Session is currently deprecated and unsupported. + * JACK developers recommend the use of NSM instead. + * See https://github.com/linuxaudio/new-session-manager + * + * @param event The event structure. + * @param arg Pointer to a client supplied structure. + */ +typedef void (*JackSessionCallback)(jack_session_event_t *event, + void *arg); + +/** + * Tell the JACK server to call @a session_callback when a session event + * is to be delivered. + * + * setting more than one session_callback per process is probably a design + * error. if you have a multiclient application its more sensible to create + * a jack_client with only a session callback set. + * + * @deprecated Use of JACK-Session is currently deprecated and unsupported. + * JACK developers recommend the use of NSM instead. + * See https://github.com/linuxaudio/new-session-manager + * + * @return 0 on success, otherwise a non-zero error code + */ +int jack_set_session_callback (jack_client_t *client, + JackSessionCallback session_callback, + void *arg) JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT; + +/** + * Reply to a session event. + * + * This can either be called directly from the callback, or later from a + * different thread. For example, it is possible to push the event through a + * queue and execute the save code from the GUI thread. + * + * @deprecated Use of JACK-Session is currently deprecated and unsupported. + * JACK developers recommend the use of NSM instead. + * See https://github.com/linuxaudio/new-session-manager + * + * @return 0 on success, otherwise a non-zero error code + */ +int jack_session_reply (jack_client_t *client, + jack_session_event_t *event) JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT; + + +/** + * Free memory used by a jack_session_event_t. + * + * This also frees the memory used by the command_line pointer, if its non NULL. + * + * @deprecated Use of JACK-Session is currently deprecated and unsupported. + * JACK developers recommend the use of NSM instead. + * See https://github.com/linuxaudio/new-session-manager + */ +void jack_session_event_free (jack_session_event_t *event) JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT; + + +/** + * Get the assigned uuid for client. + * Safe to call from callback and all other threads. + * + * The caller is responsible for calling jack_free(3) on any non-NULL + * returned value. + */ +char *jack_client_get_uuid (jack_client_t *client) JACK_WEAK_EXPORT; + +/** + * @} + */ + +/** + * @defgroup JackSessionManagerAPI API for a session manager. + * + * @{ + */ + +typedef struct { + const char *uuid; + const char *client_name; + const char *command; + jack_session_flags_t flags; +} jack_session_command_t; + +/** + * Send an event to all clients listening for session callbacks. + * + * The returned strings of the clients are accumulated and returned as an array + * of jack_session_command_t. its terminated by ret[i].uuid == NULL target == + * NULL means send to all interested clients. otherwise a clientname + */ +jack_session_command_t *jack_session_notify ( + jack_client_t* client, + const char *target, + jack_session_event_type_t type, + const char *path) JACK_WEAK_EXPORT; + +/** + * Free the memory allocated by a session command. + * + * @deprecated Use of JACK-Session is currently deprecated and unsupported. + * JACK developers recommend the use of NSM instead. + * See https://github.com/linuxaudio/new-session-manager + */ +void jack_session_commands_free (jack_session_command_t *cmds) JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT; + +/** + * Reserve a client name and associate it with a UUID. + * + * When a client later calls jack_client_open() and specifies the UUID, jackd + * will assign the reserved name. This allows a session manager to know in + * advance under which client name its managed clients will appear. + * + * @return 0 on success, otherwise a non-zero error code + */ +int +jack_reserve_client_name (jack_client_t *client, + const char *name, + const char *uuid) JACK_WEAK_EXPORT; + +/** + * Find out whether a client has set up a session callback. + * + * @deprecated Use of JACK-Session is currently deprecated and unsupported. + * JACK developers recommend the use of NSM instead. + * See https://github.com/linuxaudio/new-session-manager + * + * @return 0 when the client has no session callback, 1 when it has one. + * -1 on error. + */ +int +jack_client_has_session_callback (jack_client_t *client, const char *client_name) JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT; + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif +#endif diff --git a/pipewire-jack/jack/statistics.h b/pipewire-jack/jack/statistics.h new file mode 100644 index 0000000..28c270d --- /dev/null +++ b/pipewire-jack/jack/statistics.h @@ -0,0 +1,57 @@ +/* +* Copyright (C) 2004 Rui Nuno Capela, Lee Revell +* +* This program 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 program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this program; if not, write to the Free +* Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +* 02111-1307, USA. +* +*/ + +#ifndef __statistics_h__ +#define __statistics_h__ + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include <jack/types.h> + +/** + * @return the maximum delay reported by the backend since + * startup or reset. When compared to the period size in usecs, this + * can be used to estimate the ideal period size for a given setup. + */ +float jack_get_max_delayed_usecs (jack_client_t *client); + +/** + * @return the delay in microseconds due to the most recent XRUN + * occurrence. This probably only makes sense when called from a @ref + * JackXRunCallback defined using jack_set_xrun_callback(). + */ +float jack_get_xrun_delayed_usecs (jack_client_t *client); + +/** + * Reset the maximum delay counter. This would be useful + * to estimate the effect that a change to the configuration of a running + * system (e.g. toggling kernel preemption) has on the delay + * experienced by JACK, without having to restart the JACK engine. + */ +void jack_reset_max_delayed_usecs (jack_client_t *client); + +#ifdef __cplusplus +} +#endif + +#endif /* __statistics_h__ */ diff --git a/pipewire-jack/jack/systemdeps.h b/pipewire-jack/jack/systemdeps.h new file mode 100644 index 0000000..84b4ce3 --- /dev/null +++ b/pipewire-jack/jack/systemdeps.h @@ -0,0 +1,141 @@ +/* +Copyright (C) 2004-2012 Grame + +This program 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 program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#ifndef __jack_systemdeps_h__ +#define __jack_systemdeps_h__ + +#ifndef POST_PACKED_STRUCTURE + + #ifdef __GNUC__ + /* POST_PACKED_STRUCTURE needs to be a macro which + expands into a compiler directive. The directive must + tell the compiler to arrange the preceding structure + declaration so that it is packed on byte-boundaries rather + than use the natural alignment of the processor and/or + compiler. + */ + + #define PRE_PACKED_STRUCTURE + #define POST_PACKED_STRUCTURE __attribute__((__packed__)) + + #else + + #ifdef _MSC_VER + #define PRE_PACKED_STRUCTURE1 __pragma(pack(push,1)) + #define PRE_PACKED_STRUCTURE PRE_PACKED_STRUCTURE1 + /* PRE_PACKED_STRUCTURE needs to be a macro which + expands into a compiler directive. The directive must + tell the compiler to arrange the following structure + declaration so that it is packed on byte-boundaries rather + than use the natural alignment of the processor and/or + compiler. + */ + #define POST_PACKED_STRUCTURE ;__pragma(pack(pop)) + /* and POST_PACKED_STRUCTURE needs to be a macro which + restores the packing to its previous setting */ + #else + #define PRE_PACKED_STRUCTURE + #define POST_PACKED_STRUCTURE + #endif /* _MSC_VER */ + + #endif /* __GNUC__ */ + +#endif + +#if defined(_WIN32) && !defined(__CYGWIN__) && !defined(GNU_WIN32) + + #ifdef __MINGW32__ + # include <winsock2.h> // mingw gives warning if we include windows.h before winsock2.h + #endif + + #include <windows.h> + + #ifdef _MSC_VER /* Microsoft compiler */ + #define __inline__ inline + #if (!defined(int8_t) && !defined(_STDINT_H)) + #define __int8_t_defined + typedef INT8 int8_t; + typedef UINT8 uint8_t; + typedef INT16 int16_t; + typedef UINT16 uint16_t; + typedef INT32 int32_t; + typedef UINT32 uint32_t; + typedef INT64 int64_t; + typedef UINT64 uint64_t; + #endif + #elif __MINGW32__ /* MINGW */ + #include <stdint.h> + #include <sys/types.h> + #else /* other compilers ...*/ + #include <inttypes.h> + #include <pthread.h> + #include <sys/types.h> + #endif + + #if !defined(_PTHREAD_H) && !defined(PTHREAD_WIN32) + /** + * to make jack API independent of different thread implementations, + * we define jack_native_thread_t to HANDLE here. + */ + typedef HANDLE jack_native_thread_t; + #else + #ifdef PTHREAD_WIN32 // Added by JE - 10-10-2011 + #include <ptw32/pthread.h> // Makes sure we #include the ptw32 version ! + #endif + /** + * to make jack API independent of different thread implementations, + * we define jack_native_thread_t to pthread_t here. + */ + typedef pthread_t jack_native_thread_t; + #endif + +#endif /* _WIN32 && !__CYGWIN__ && !GNU_WIN32 */ + +#if defined(__APPLE__) || defined(__linux__) || defined(__sun__) || defined(sun) || defined(__unix__) || defined(__CYGWIN__) || defined(GNU_WIN32) + + #if defined(__CYGWIN__) || defined(GNU_WIN32) + #include <stdint.h> + #endif + #include <inttypes.h> + #include <pthread.h> + #include <sys/types.h> + + /** + * to make jack API independent of different thread implementations, + * we define jack_native_thread_t to pthread_t here. + */ + typedef pthread_t jack_native_thread_t; + +#endif /* __APPLE__ || __linux__ || __sun__ || sun */ + +#if (defined(__arm__) || defined(__aarch64__) || defined(__mips__) || defined(__ppc__) || defined(__powerpc__)) && !defined(__APPLE__) + #undef POST_PACKED_STRUCTURE + #define POST_PACKED_STRUCTURE +#endif /* __arm__ || __aarch64__ || __mips__ || __ppc__ || __powerpc__ */ + +/** define JACK_LIB_EXPORT, useful for internal clients */ +#if defined(_WIN32) + #define JACK_LIB_EXPORT __declspec(dllexport) +#elif defined(__GNUC__) + #define JACK_LIB_EXPORT __attribute__((visibility("default"))) +#else + #define JACK_LIB_EXPORT +#endif + +#endif /* __jack_systemdeps_h__ */ diff --git a/pipewire-jack/jack/thread.h b/pipewire-jack/jack/thread.h new file mode 100644 index 0000000..776c520 --- /dev/null +++ b/pipewire-jack/jack/thread.h @@ -0,0 +1,160 @@ +/* + Copyright (C) 2004 Paul Davis + + This program 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 program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef __jack_thread_h__ +#define __jack_thread_h__ + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include <jack/systemdeps.h> +#include <jack/weakmacros.h> + +/* use 512KB stack per thread - the default is way too high to be feasible + * with mlockall() on many systems */ +#define THREAD_STACK 524288 + +/** @file thread.h + * + * Library functions to standardize thread creation for JACK and its + * clients. These interfaces hide some system variations in the + * handling of realtime scheduling and associated privileges. + */ + +/** + * @defgroup ClientThreads Creating and managing client threads + * @{ + */ + + /** + * @returns if JACK is running with realtime scheduling, this returns + * the priority that any JACK-created client threads will run at. + * Otherwise returns -1. + */ + +int jack_client_real_time_priority (jack_client_t*) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @returns if JACK is running with realtime scheduling, this returns + * the maximum priority that a JACK client thread should use if the thread + * is subject to realtime scheduling. Otherwise returns -1. + */ + +int jack_client_max_real_time_priority (jack_client_t*) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Attempt to enable realtime scheduling for a thread. On some + * systems that may require special privileges. + * + * @param thread POSIX thread ID. + * @param priority requested thread priority. + * + * @returns 0, if successful; EPERM, if the calling process lacks + * required realtime privileges; otherwise some other error number. + */ +int jack_acquire_real_time_scheduling (jack_native_thread_t thread, int priority) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * 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. + */ +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) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Drop realtime scheduling for a thread. + * + * @param thread POSIX thread ID. + * + * @returns 0, if successful; otherwise an error number. + */ +int jack_drop_real_time_scheduling (jack_native_thread_t thread) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Stop the thread, waiting for the thread handler to terminate. + * + * @param thread POSIX thread ID. + * + * @returns 0, if successful; otherwise an error number. + */ +int jack_client_stop_thread(jack_client_t* client, jack_native_thread_t thread) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Kill the thread. + * + * @param thread POSIX thread ID. + * + * @returns 0, if successful; otherwise an error number. + */ + int jack_client_kill_thread(jack_client_t* client, jack_native_thread_t thread) JACK_OPTIONAL_WEAK_EXPORT; + +#ifndef _WIN32 + + typedef int (*jack_thread_creator_t)(pthread_t*, + const pthread_attr_t*, + void* (*function)(void*), + void* arg); +/** + * This function can be used in very very specialized cases + * where it is necessary that client threads created by JACK + * are created by something other than pthread_create(). After + * it is used, any threads that JACK needs for the client will + * will be created by calling the function passed to this + * function. + * + * No normal application/client should consider calling this. + * The specific case for which it was created involves running + * win32/x86 plugins under Wine on Linux, where it is necessary + * that all threads that might call win32 functions are known + * to Wine. + * + * Set it to NULL to restore thread creation function. + * + * @param creator a function that creates a new thread + * + */ +void jack_set_thread_creator (jack_thread_creator_t creator) JACK_OPTIONAL_WEAK_EXPORT; + +#endif + +/* @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* __jack_thread_h__ */ diff --git a/pipewire-jack/jack/transport.h b/pipewire-jack/jack/transport.h new file mode 100644 index 0000000..4cec6e0 --- /dev/null +++ b/pipewire-jack/jack/transport.h @@ -0,0 +1,247 @@ +/* + Copyright (C) 2002 Paul Davis + Copyright (C) 2003 Jack O'Quin + + This program 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 program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef __jack_transport_h__ +#define __jack_transport_h__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <jack/types.h> +#include <jack/weakmacros.h> + +/** + * @defgroup TransportControl Transport and Timebase control + * @{ + */ + +/** + * Called by the timebase master to release itself from that + * responsibility. + * + * If the timebase master releases the timebase or leaves the JACK + * graph for any reason, the JACK engine takes over at the start of + * the next process cycle. The transport state does not change. If + * rolling, it continues to play, with frame numbers as the only + * available position information. + * + * @see jack_set_timebase_callback + * + * @param client the JACK client structure. + * + * @return 0 on success, otherwise a non-zero error code. + */ +int jack_release_timebase (jack_client_t *client) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Register (or unregister) as a slow-sync client, one that cannot + * respond immediately to transport position changes. + * + * The @a sync_callback will be invoked at the first available + * opportunity after its registration is complete. If the client is + * currently active this will be the following process cycle, + * otherwise it will be the first cycle after calling jack_activate(). + * After that, it runs according to the ::JackSyncCallback rules. + * Clients that don't set a @a sync_callback are assumed to be ready + * immediately any time the transport wants to start. + * + * @param client the JACK client structure. + * @param sync_callback is a realtime function that returns TRUE when + * the client is ready. Setting @a sync_callback to NULL declares that + * this client no longer requires slow-sync processing. + * @param arg an argument for the @a sync_callback function. + * + * @return 0 on success, otherwise a non-zero error code. + */ +int jack_set_sync_callback (jack_client_t *client, + JackSyncCallback sync_callback, + void *arg) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Set the timeout value for slow-sync clients. + * + * This timeout prevents unresponsive slow-sync clients from + * completely halting the transport mechanism. The default is two + * seconds. When the timeout expires, the transport starts rolling, + * even if some slow-sync clients are still unready. The @a + * sync_callbacks of these clients continue being invoked, giving them + * a chance to catch up. + * + * @see jack_set_sync_callback + * + * @param client the JACK client structure. + * @param timeout is delay (in microseconds) before the timeout expires. + * + * @return 0 on success, otherwise a non-zero error code. + */ +int jack_set_sync_timeout (jack_client_t *client, + jack_time_t timeout) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Register as timebase master for the JACK subsystem. + * + * The timebase master registers a callback that updates extended + * position information such as beats or timecode whenever necessary. + * Without this extended information, there is no need for this + * function. + * + * There is never more than one master at a time. When a new client + * takes over, the former @a timebase_callback is no longer called. + * Taking over the timebase may be done conditionally, so it fails if + * there was a master already. + * + * @param client the JACK client structure. + * @param conditional non-zero for a conditional request. + * @param timebase_callback is a realtime function that returns + * position information. + * @param arg an argument for the @a timebase_callback function. + * + * @return + * - 0 on success; + * - EBUSY if a conditional request fails because there was already a + * timebase master; + * - other non-zero error code. + */ +int jack_set_timebase_callback (jack_client_t *client, + int conditional, + JackTimebaseCallback timebase_callback, + void *arg) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Reposition the transport to a new frame number. + * + * May be called at any time by any client. The new position takes + * effect in two process cycles. If there are slow-sync clients and + * the transport is already rolling, it will enter the + * ::JackTransportStarting state and begin invoking their @a + * sync_callbacks until ready. This function is realtime-safe. + * + * @see jack_transport_reposition, jack_set_sync_callback + * + * @param client the JACK client structure. + * @param frame frame number of new transport position. + * + * @return 0 if valid request, non-zero otherwise. + */ +int jack_transport_locate (jack_client_t *client, + jack_nframes_t frame) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Query the current transport state and position. + * + * This function is realtime-safe, and can be called from any thread. + * If called from the process thread, @a pos corresponds to the first + * frame of the current cycle and the state returned is valid for the + * entire cycle. + * + * @param client the JACK client structure. + * @param pos pointer to structure for returning current transport + * position; @a pos->valid will show which fields contain valid data. + * If @a pos is NULL, do not return position information. + * + * @return Current transport state. + */ +jack_transport_state_t jack_transport_query (const jack_client_t *client, + jack_position_t *pos) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Return an estimate of the current transport frame, + * including any time elapsed since the last transport + * positional update. + * + * @param client the JACK client structure + */ +jack_nframes_t jack_get_current_transport_frame (const jack_client_t *client) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Request a new transport position. + * + * May be called at any time by any client. The new position takes + * effect in two process cycles. If there are slow-sync clients and + * the transport is already rolling, it will enter the + * ::JackTransportStarting state and begin invoking their @a + * sync_callbacks until ready. This function is realtime-safe. + * + * @see jack_transport_locate, jack_set_sync_callback + * + * @param client the JACK client structure. + * @param pos requested new transport position. + * + * @return 0 if valid request, EINVAL if position structure rejected. + */ +int jack_transport_reposition (jack_client_t *client, + const jack_position_t *pos) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Start the JACK transport rolling. + * + * Any client can make this request at any time. It takes effect no + * sooner than the next process cycle, perhaps later if there are + * slow-sync clients. This function is realtime-safe. + * + * @see jack_set_sync_callback + * + * @param client the JACK client structure. + */ +void jack_transport_start (jack_client_t *client) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Stop the JACK transport. + * + * Any client can make this request at any time. It takes effect on + * the next process cycle. This function is realtime-safe. + * + * @param client the JACK client structure. + */ +void jack_transport_stop (jack_client_t *client) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Gets the current transport info structure (deprecated). + * + * @param client the JACK client structure. + * @param tinfo current transport info structure. The "valid" field + * describes which fields contain valid data. + * + * @deprecated This is for compatibility with the earlier transport + * interface. Use jack_transport_query(), instead. + * + * @pre Must be called from the process thread. + */ +void jack_get_transport_info (jack_client_t *client, + jack_transport_info_t *tinfo) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Set the transport info structure (deprecated). + * + * @deprecated This function still exists for compatibility with the + * earlier transport interface, but it does nothing. Instead, define + * a ::JackTimebaseCallback. + */ +void jack_set_transport_info (jack_client_t *client, + jack_transport_info_t *tinfo) JACK_OPTIONAL_WEAK_EXPORT; + +/*@}*/ + +#ifdef __cplusplus +} +#endif + +#endif /* __jack_transport_h__ */ diff --git a/pipewire-jack/jack/types.h b/pipewire-jack/jack/types.h new file mode 100644 index 0000000..b62af96 --- /dev/null +++ b/pipewire-jack/jack/types.h @@ -0,0 +1,740 @@ +/* + Copyright (C) 2001 Paul Davis + Copyright (C) 2004 Jack O'Quin + + This program 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 program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef __jack_types_h__ +#define __jack_types_h__ + +#include <jack/systemdeps.h> + +typedef uint64_t jack_uuid_t; + +typedef int32_t jack_shmsize_t; + +/** + * Type used to represent sample frame counts. + */ +typedef uint32_t jack_nframes_t; + +/** + * Maximum value that can be stored in jack_nframes_t + */ +#define JACK_MAX_FRAMES (4294967295U) /* This should be UINT32_MAX, but C++ has a problem with that. */ + +/** + * Type used to represent the value of free running + * monotonic clock with units of microseconds. + */ +typedef uint64_t jack_time_t; + +/** + * Maximum size of @a load_init string passed to an internal client + * jack_initialize() function via jack_internal_client_load(). + */ +#define JACK_LOAD_INIT_LIMIT 1024 + +/** + * jack_intclient_t is an opaque type representing a loaded internal + * client. You may only access it using the API provided in @ref + * intclient.h "<jack/intclient.h>". + */ +typedef uint64_t jack_intclient_t; + +/** + * jack_port_t is an opaque type. You may only access it using the + * API provided. + */ +typedef struct _jack_port jack_port_t; + +/** + * jack_client_t is an opaque type. You may only access it using the + * API provided. + */ +typedef struct _jack_client jack_client_t; + +/** + * Ports have unique ids. A port registration callback is the only + * place you ever need to know their value. + */ +typedef uint32_t jack_port_id_t; + +typedef uint32_t jack_port_type_id_t; + +/** + * @ref jack_options_t bits + */ +enum JackOptions { + + /** + * Null value to use when no option bits are needed. + */ + JackNullOption = 0x00, + + /** + * Do not automatically start the JACK server when it is not + * already running. This option is always selected if + * \$JACK_NO_START_SERVER is defined in the calling process + * environment. + */ + JackNoStartServer = 0x01, + + /** + * Use the exact client name requested. Otherwise, JACK + * automatically generates a unique one, if needed. + */ + JackUseExactName = 0x02, + + /** + * Open with optional <em>(char *) server_name</em> parameter. + */ + JackServerName = 0x04, + + /** + * Load internal client from optional <em>(char *) + * load_name</em>. Otherwise use the @a client_name. + */ + JackLoadName = 0x08, + + /** + * Pass optional <em>(char *) load_init</em> string to the + * jack_initialize() entry point of an internal client. + */ + JackLoadInit = 0x10, + + /** + * pass a SessionID Token this allows the sessionmanager to identify the client again. + */ + JackSessionID = 0x20 +}; + +/** Valid options for opening an external client. */ +#define JackOpenOptions (JackSessionID|JackServerName|JackNoStartServer|JackUseExactName) + +/** Valid options for loading an internal client. */ +#define JackLoadOptions (JackLoadInit|JackLoadName|JackUseExactName) + +/** + * Options for several JACK operations, formed by OR-ing together the + * relevant @ref JackOptions bits. + */ +typedef enum JackOptions jack_options_t; + +/** + * @ref jack_status_t bits + */ +enum JackStatus { + + /** + * Overall operation failed. + */ + JackFailure = 0x01, + + /** + * The operation contained an invalid or unsupported option. + */ + JackInvalidOption = 0x02, + + /** + * The desired client name was not unique. With the @ref + * JackUseExactName option this situation is fatal. Otherwise, + * the name was modified by appending a dash and a two-digit + * number in the range "-01" to "-99". The + * jack_get_client_name() function will return the exact string + * that was used. If the specified @a client_name plus these + * extra characters would be too long, the open fails instead. + */ + JackNameNotUnique = 0x04, + + /** + * The JACK server was started as a result of this operation. + * Otherwise, it was running already. In either case the caller + * is now connected to jackd, so there is no race condition. + * When the server shuts down, the client will find out. + */ + JackServerStarted = 0x08, + + /** + * Unable to connect to the JACK server. + */ + JackServerFailed = 0x10, + + /** + * Communication error with the JACK server. + */ + JackServerError = 0x20, + + /** + * Requested client does not exist. + */ + JackNoSuchClient = 0x40, + + /** + * Unable to load internal client + */ + JackLoadFailure = 0x80, + + /** + * Unable to initialize client + */ + JackInitFailure = 0x100, + + /** + * Unable to access shared memory + */ + JackShmFailure = 0x200, + + /** + * Client's protocol version does not match + */ + JackVersionError = 0x400, + + /** + * Backend error + */ + JackBackendError = 0x800, + + /** + * Client zombified failure + */ + JackClientZombie = 0x1000 +}; + +/** + * Status word returned from several JACK operations, formed by + * OR-ing together the relevant @ref JackStatus bits. + */ +typedef enum JackStatus jack_status_t; + +/** + * @ref jack_latency_callback_mode_t + */ +enum JackLatencyCallbackMode { + + /** + * Latency Callback for Capture Latency. + * Input Ports have their latency value setup. + * In the Callback the client needs to set the latency of the output ports + */ + JackCaptureLatency, + + /** + * Latency Callback for Playback Latency. + * Output Ports have their latency value setup. + * In the Callback the client needs to set the latency of the input ports + */ + JackPlaybackLatency + +}; + +/** + * Type of Latency Callback (Capture or Playback) + */ +typedef enum JackLatencyCallbackMode jack_latency_callback_mode_t; + +/** + * Prototype for the client supplied function that is called + * by the engine when port latencies need to be recalculated + * + * @param mode playback or capture latency + * @param arg pointer to a client supplied data + * + * @return zero on success, non-zero on error + */ +typedef void (*JackLatencyCallback)(jack_latency_callback_mode_t mode, void *arg); + +/** + * the new latency API operates on Ranges. + */ +PRE_PACKED_STRUCTURE +struct _jack_latency_range +{ + /** + * minimum latency + */ + jack_nframes_t min; + /** + * maximum latency + */ + jack_nframes_t max; +} POST_PACKED_STRUCTURE; + +typedef struct _jack_latency_range jack_latency_range_t; + +/** + * Prototype for the client supplied function that is called + * by the engine anytime there is work to be done. + * + * @pre nframes == jack_get_buffer_size() + * @pre nframes == pow(2,x) + * + * @param nframes number of frames to process + * @param arg pointer to a client supplied structure + * + * @return zero on success, non-zero on error + */ +typedef int (*JackProcessCallback)(jack_nframes_t nframes, void *arg); + +/** + * Prototype for the client thread routine called + * by the engine when the client is inserted in the graph. + * + * @param arg pointer to a client supplied structure + * + */ +typedef void *(*JackThreadCallback)(void* arg); + +/** + * Prototype for the client supplied function that is called + * once after the creation of the thread in which other + * callbacks will be made. Special thread characteristics + * can be set from this callback, for example. This is a + * highly specialized callback and most clients will not + * and should not use it. + * + * @param arg pointer to a client supplied structure + * + * @return void + */ +typedef void (*JackThreadInitCallback)(void *arg); + +/** + * Prototype for the client supplied function that is called + * whenever the processing graph is reordered. + * + * @param arg pointer to a client supplied structure + * + * @return zero on success, non-zero on error + */ +typedef int (*JackGraphOrderCallback)(void *arg); + +/** + * Prototype for the client-supplied function that is called whenever + * an xrun has occurred. + * + * @see jack_get_xrun_delayed_usecs() + * + * @param arg pointer to a client supplied structure + * + * @return zero on success, non-zero on error + */ +typedef int (*JackXRunCallback)(void *arg); + +/** + * Prototype for the @a bufsize_callback that is invoked whenever the + * JACK engine buffer size changes. Although this function is called + * in the JACK process thread, the normal process cycle is suspended + * during its operation, causing a gap in the audio flow. So, the @a + * bufsize_callback can allocate storage, touch memory not previously + * referenced, and perform other operations that are not realtime + * safe. + * + * @param nframes buffer size + * @param arg pointer supplied by jack_set_buffer_size_callback(). + * + * @return zero on success, non-zero on error + */ +typedef int (*JackBufferSizeCallback)(jack_nframes_t nframes, void *arg); + +/** + * Prototype for the client supplied function that is called + * when the engine sample rate changes. + * + * @param nframes new engine sample rate + * @param arg pointer to a client supplied structure + * + * @return zero on success, non-zero on error + */ +typedef int (*JackSampleRateCallback)(jack_nframes_t nframes, void *arg); + +/** + * Prototype for the client supplied function that is called + * whenever a port is registered or unregistered. + * + * @param port the ID of the port + * @param arg pointer to a client supplied data + * @param register non-zero if the port is being registered, + * zero if the port is being unregistered + */ +typedef void (*JackPortRegistrationCallback)(jack_port_id_t port, int /* register */, void *arg); + +/** + * Prototype for the client supplied function that is called + * whenever a client is registered or unregistered. + * + * @param name a null-terminated string containing the client name + * @param register non-zero if the client is being registered, + * zero if the client is being unregistered + * @param arg pointer to a client supplied structure + */ +typedef void (*JackClientRegistrationCallback)(const char* name, int /* register */, void *arg); + +/** + * Prototype for the client supplied function that is called + * whenever a port is connected or disconnected. + * + * @param a one of two ports connected or disconnected + * @param b one of two ports connected or disconnected + * @param connect non-zero if ports were connected + * zero if ports were disconnected + * @param arg pointer to a client supplied data + */ +typedef void (*JackPortConnectCallback)(jack_port_id_t a, jack_port_id_t b, int connect, void* arg); + +/** + * Prototype for the client supplied function that is called + * whenever the port name has been changed. + * + * @param port the port that has been renamed + * @param new_name the new name + * @param arg pointer to a client supplied structure + */ +typedef void (*JackPortRenameCallback)(jack_port_id_t port, const char* old_name, const char* new_name, void *arg); + +/** + * Prototype for the client supplied function that is called + * whenever jackd starts or stops freewheeling. + * + * @param starting non-zero if we start starting to freewheel, zero otherwise + * @param arg pointer to a client supplied structure + */ +typedef void (*JackFreewheelCallback)(int starting, void *arg); + +/** + * Prototype for the client supplied function that is called + * whenever jackd is shutdown. Note that after server shutdown, + * the client pointer is *not* deallocated by libjack, + * the application is responsible to properly use jack_client_close() + * to release client resources. Warning: jack_client_close() cannot be + * safely used inside the shutdown callback and has to be called outside of + * the callback context. + * + * @param arg pointer to a client supplied structure + */ +typedef void (*JackShutdownCallback)(void *arg); + +/** + * Prototype for the client supplied function that is called + * whenever jackd is shutdown. Note that after server shutdown, + * the client pointer is *not* deallocated by libjack, + * the application is responsible to properly use jack_client_close() + * to release client resources. Warning: jack_client_close() cannot be + * safely used inside the shutdown callback and has to be called outside of + * the callback context. + + * @param code a status word, formed by OR-ing together the relevant @ref JackStatus bits. + * @param reason a string describing the shutdown reason (backend failure, server crash... etc...). + * Note that this string will not be available anymore after the callback returns, so possibly copy it. + * @param arg pointer to a client supplied structure + */ +typedef void (*JackInfoShutdownCallback)(jack_status_t code, const char* reason, void *arg); + +/** + * Used for the type argument of jack_port_register() for default + * audio ports and midi ports. + */ +#define JACK_DEFAULT_AUDIO_TYPE "32 bit float mono audio" +#define JACK_DEFAULT_MIDI_TYPE "8 bit raw midi" + +/** + * For convenience, use this typedef if you want to be able to change + * between float and double. You may want to typedef sample_t to + * jack_default_audio_sample_t in your application. + */ +typedef float jack_default_audio_sample_t; + +/** + * A port has a set of flags that are formed by AND-ing together the + * desired values from the list below. The flags "JackPortIsInput" and + * "JackPortIsOutput" are mutually exclusive and it is an error to use + * them both. + */ +enum JackPortFlags { + + /** + * if JackPortIsInput is set, then the port can receive + * data. + */ + JackPortIsInput = 0x1, + + /** + * if JackPortIsOutput is set, then data can be read from + * the port. + */ + JackPortIsOutput = 0x2, + + /** + * if JackPortIsPhysical is set, then the port corresponds + * to some kind of physical I/O connector. + */ + JackPortIsPhysical = 0x4, + + /** + * if JackPortCanMonitor is set, then a call to + * jack_port_request_monitor() makes sense. + * + * Precisely what this means is dependent on the client. A typical + * result of it being called with TRUE as the second argument is + * that data that would be available from an output port (with + * JackPortIsPhysical set) is sent to a physical output connector + * as well, so that it can be heard/seen/whatever. + * + * Clients that do not control physical interfaces + * should never create ports with this bit set. + */ + JackPortCanMonitor = 0x8, + + /** + * JackPortIsTerminal means: + * + * for an input port: the data received by the port + * will not be passed on or made + * available at any other port + * + * for an output port: the data available at the port + * does not originate from any other port + * + * Audio synthesizers, I/O hardware interface clients, HDR + * systems are examples of clients that would set this flag for + * their ports. + */ + JackPortIsTerminal = 0x10, + +}; + +/** + * Transport states. + */ +typedef enum { + + /* the order matters for binary compatibility */ + JackTransportStopped = 0, /**< Transport halted */ + JackTransportRolling = 1, /**< Transport playing */ + JackTransportLooping = 2, /**< For OLD_TRANSPORT, now ignored */ + JackTransportStarting = 3, /**< Waiting for sync ready */ + JackTransportNetStarting = 4, /**< Waiting for sync ready on the network*/ + +} jack_transport_state_t; + +typedef uint64_t jack_unique_t; /**< Unique ID (opaque) */ + +/** + * Optional struct jack_position_t fields. + */ +typedef enum { + + JackPositionBBT = 0x10, /**< Bar, Beat, Tick */ + JackPositionTimecode = 0x20, /**< External timecode */ + JackBBTFrameOffset = 0x40, /**< Frame offset of BBT information */ + JackAudioVideoRatio = 0x80, /**< audio frames per video frame */ + JackVideoFrameOffset = 0x100 /**< frame offset of first video frame */ + +} jack_position_bits_t; + +/** all valid position bits */ +#define JACK_POSITION_MASK (JackPositionBBT|JackPositionTimecode) +#define EXTENDED_TIME_INFO + +PRE_PACKED_STRUCTURE +struct _jack_position { + + /* these four cannot be set from clients: the server sets them */ + jack_unique_t unique_1; /**< unique ID */ + jack_time_t usecs; /**< monotonic, free-rolling */ + jack_nframes_t frame_rate; /**< current frame rate (per second) */ + jack_nframes_t frame; /**< frame number, always present */ + + jack_position_bits_t valid; /**< which other fields are valid */ + + /* JackPositionBBT fields: */ + int32_t bar; /**< current bar */ + int32_t beat; /**< current beat-within-bar */ + int32_t tick; /**< current tick-within-beat */ + double bar_start_tick; + + float beats_per_bar; /**< time signature "numerator" */ + float beat_type; /**< time signature "denominator" */ + double ticks_per_beat; + double beats_per_minute; + + /* JackPositionTimecode fields: (EXPERIMENTAL: could change) */ + double frame_time; /**< current time in seconds */ + double next_time; /**< next sequential frame_time + (unless repositioned) */ + + /* JackBBTFrameOffset fields: */ + jack_nframes_t bbt_offset; /**< frame offset for the BBT fields + (the given bar, beat, and tick + values actually refer to a time + frame_offset frames before the + start of the cycle), should + be assumed to be 0 if + JackBBTFrameOffset is not + set. If JackBBTFrameOffset is + set and this value is zero, the BBT + time refers to the first frame of this + cycle. If the value is positive, + the BBT time refers to a frame that + many frames before the start of the + cycle. */ + + /* JACK video positional data (experimental) */ + + float audio_frames_per_video_frame; /**< number of audio frames + per video frame. Should be assumed + zero if JackAudioVideoRatio is not + set. If JackAudioVideoRatio is set + and the value is zero, no video + data exists within the JACK graph */ + + jack_nframes_t video_offset; /**< audio frame at which the first video + frame in this cycle occurs. Should + be assumed to be 0 if JackVideoFrameOffset + is not set. If JackVideoFrameOffset is + set, but the value is zero, there is + no video frame within this cycle. */ + + /* For binary compatibility, new fields should be allocated from + * this padding area with new valid bits controlling access, so + * the existing structure size and offsets are preserved. */ + int32_t padding[7]; + + /* When (unique_1 == unique_2) the contents are consistent. */ + jack_unique_t unique_2; /**< unique ID */ + +} POST_PACKED_STRUCTURE; + +typedef struct _jack_position jack_position_t; + +/** + * Prototype for the @a sync_callback defined by slow-sync clients. + * When the client is active, this callback is invoked just before + * process() in the same thread. This occurs once after registration, + * then subsequently whenever some client requests a new position, or + * the transport enters the ::JackTransportStarting state. This + * realtime function must not wait. + * + * The transport @a state will be: + * + * - ::JackTransportStopped when a new position is requested; + * - ::JackTransportStarting when the transport is waiting to start; + * - ::JackTransportRolling when the timeout has expired, and the + * position is now a moving target. + * + * @param state current transport state. + * @param pos new transport position. + * @param arg the argument supplied by jack_set_sync_callback(). + * + * @return TRUE (non-zero) when ready to roll. + */ +typedef int (*JackSyncCallback)(jack_transport_state_t state, + jack_position_t *pos, + void *arg); + + +/** + * Prototype for the @a timebase_callback used to provide extended + * position information. Its output affects all of the following + * process cycle. This realtime function must not wait. + * + * This function is called immediately after process() in the same + * thread whenever the transport is rolling, or when any client has + * requested a new position in the previous cycle. The first cycle + * after jack_set_timebase_callback() is also treated as a new + * position, or the first cycle after jack_activate() if the client + * had been inactive. + * + * The timebase master may not use its @a pos argument to set @a + * pos->frame. To change position, use jack_transport_reposition() or + * jack_transport_locate(). These functions are realtime-safe, the @a + * timebase_callback can call them directly. + * + * @param state current transport state. + * @param nframes number of frames in current period. + * @param pos address of the position structure for the next cycle; @a + * pos->frame will be its frame number. If @a new_pos is FALSE, this + * structure contains extended position information from the current + * cycle. If TRUE, it contains whatever was set by the requester. + * The @a timebase_callback's task is to update the extended + * information here. + * @param new_pos TRUE (non-zero) for a newly requested @a pos, or for + * the first cycle after the @a timebase_callback is defined. + * @param arg the argument supplied by jack_set_timebase_callback(). + */ +typedef void (*JackTimebaseCallback)(jack_transport_state_t state, + jack_nframes_t nframes, + jack_position_t *pos, + int new_pos, + void *arg); + +/********************************************************************* + * The following interfaces are DEPRECATED. They are only provided + * for compatibility with the earlier JACK transport implementation. + *********************************************************************/ + +/** + * Optional struct jack_transport_info_t fields. + * + * @see jack_position_bits_t. + */ +typedef enum { + + JackTransportState = 0x1, /**< Transport state */ + JackTransportPosition = 0x2, /**< Frame number */ + JackTransportLoop = 0x4, /**< Loop boundaries (ignored) */ + JackTransportSMPTE = 0x8, /**< SMPTE (ignored) */ + JackTransportBBT = 0x10 /**< Bar, Beat, Tick */ + +} jack_transport_bits_t; + +/** + * Deprecated struct for transport position information. + * + * @deprecated This is for compatibility with the earlier transport + * interface. Use the jack_position_t struct, instead. + */ +typedef struct { + + /* these two cannot be set from clients: the server sets them */ + + jack_nframes_t frame_rate; /**< current frame rate (per second) */ + jack_time_t usecs; /**< monotonic, free-rolling */ + + jack_transport_bits_t valid; /**< which fields are legal to read */ + jack_transport_state_t transport_state; + jack_nframes_t frame; + jack_nframes_t loop_start; + jack_nframes_t loop_end; + + long smpte_offset; /**< SMPTE offset (from frame 0) */ + float smpte_frame_rate; /**< 29.97, 30, 24 etc. */ + + int bar; + int beat; + int tick; + double bar_start_tick; + + float beats_per_bar; + float beat_type; + double ticks_per_beat; + double beats_per_minute; + +} jack_transport_info_t; + + +#endif /* __jack_types_h__ */ diff --git a/pipewire-jack/jack/uuid.h b/pipewire-jack/jack/uuid.h new file mode 100644 index 0000000..406c119 --- /dev/null +++ b/pipewire-jack/jack/uuid.h @@ -0,0 +1,50 @@ +/* + Copyright (C) 2013 Paul Davis + + This program 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 program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef __jack_uuid_h__ +#define __jack_uuid_h__ + +#include <jack/types.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define JACK_UUID_SIZE 36 +#define JACK_UUID_STRING_SIZE (JACK_UUID_SIZE+1) /* includes trailing null */ +#define JACK_UUID_EMPTY_INITIALIZER 0 + +extern jack_uuid_t jack_client_uuid_generate (void); +extern jack_uuid_t jack_port_uuid_generate (uint32_t port_id); + +extern uint32_t jack_uuid_to_index (jack_uuid_t); + +extern int jack_uuid_compare (jack_uuid_t, jack_uuid_t); +extern void jack_uuid_copy (jack_uuid_t* dst, jack_uuid_t src); +extern void jack_uuid_clear (jack_uuid_t*); +extern int jack_uuid_parse (const char *buf, jack_uuid_t*); +extern void jack_uuid_unparse (jack_uuid_t, char buf[JACK_UUID_STRING_SIZE]); +extern int jack_uuid_empty (jack_uuid_t); + +#ifdef __cplusplus +} /* namespace */ +#endif + +#endif /* __jack_uuid_h__ */ + diff --git a/pipewire-jack/jack/weakjack.h b/pipewire-jack/jack/weakjack.h new file mode 100644 index 0000000..c253c63 --- /dev/null +++ b/pipewire-jack/jack/weakjack.h @@ -0,0 +1,125 @@ +/* + Copyright (C) 2010 Paul Davis + + This program 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 program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef __weakjack_h__ +#define __weakjack_h__ + +/** + * @defgroup WeakLinkage Managing support for newer/older versions of JACK + * @{ One challenge faced by developers is that of taking + * advantage of new features introduced in new versions + * of [ JACK ] while still supporting older versions of + * the system. Normally, if an application uses a new + * feature in a library/API, it is unable to run on + * earlier versions of the library/API that do not + * support that feature. Such applications would either + * fail to launch or crash when an attempt to use the + * feature was made. This problem cane be solved using + * weakly-linked symbols. + * + * When a symbol in a framework is defined as weakly + * linked, the symbol does not have to be present at + * runtime for a process to continue running. The static + * linker identifies a weakly linked symbol as such in + * any code module that references the symbol. The + * dynamic linker uses this same information at runtime + * to determine whether a process can continue + * running. If a weakly linked symbol is not present in + * the framework, the code module can continue to run as + * long as it does not reference the symbol. However, if + * the symbol is present, the code can use it normally. + * + * (adapted from: http://developer.apple.com/library/mac/#documentation/MacOSX/Conceptual/BPFrameworks/Concepts/WeakLinking.html) + * + * A concrete example will help. Suppose that someone uses a version + * of a JACK client we'll call "Jill". Jill was linked against a version + * of JACK that contains a newer part of the API (say, jack_set_latency_callback()) + * and would like to use it if it is available. + * + * When Jill is run on a system that has a suitably "new" version of + * JACK, this function will be available entirely normally. But if Jill + * is run on a system with an old version of JACK, the function isn't + * available. + * + * With normal symbol linkage, this would create a startup error whenever + * someone tries to run Jill with the "old" version of JACK. However, functions + * added to JACK after version 0.116.2 are all declared to have "weak" linkage + * which means that their absence doesn't cause an error during program + * startup. Instead, Jill can test whether or not the symbol jack_set_latency_callback + * is null or not. If its null, it means that the JACK installed on this machine + * is too old to support this function. If it's not null, then Jill can use it + * just like any other function in the API. For example: + * + * \code + * if (jack_set_latency_callback) { + * jack_set_latency_callback (jill_client, jill_latency_callback, arg); + * } + * \endcode + * + * However, there are clients that may want to use this approach to parts of the + * the JACK API that predate 0.116.2. For example, they might want to see if even + * really old basic parts of the API like jack_client_open() exist at runtime. + * + * Such clients should include <jack/weakjack.h> before any other JACK header. + * This will make the \b entire JACK API be subject to weak linkage, so that any + * and all functions can be checked for existence at runtime. It is important + * to understand that very few clients need to do this - if you use this + * feature you should have a clear reason to do so. + * + * + */ + +#ifdef __APPLE__ +#define WEAK_ATTRIBUTE weak_import +#else +#define WEAK_ATTRIBUTE __weak__ +#endif + +#ifndef JACK_OPTIONAL_WEAK_EXPORT +/* JACK_OPTIONAL_WEAK_EXPORT needs to be a macro which + expands into a compiler directive. If non-null, the directive + must tell the compiler to arrange for weak linkage of + the symbol it used with. For this to work fully may + require linker arguments for the client as well. +*/ +#ifdef __GNUC__ +#define JACK_OPTIONAL_WEAK_EXPORT __attribute__((WEAK_ATTRIBUTE)) +#else +/* Add other things here for non-gcc platforms */ +#endif +#endif + +#ifndef JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT +/* JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT needs to be a macro + which expands into a compiler directive. If non-null, the directive + must tell the compiler to arrange for weak linkage of the + symbol it is used with AND optionally to mark the symbol + as deprecated. For this to work fully may require + linker arguments for the client as well. +*/ +#ifdef __GNUC__ +#define JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT __attribute__((WEAK_ATTRIBUTE,__deprecated__)) +#else +/* Add other things here for non-gcc platforms */ +#endif +#endif + +/*@}*/ + +#endif /* weakjack */ diff --git a/pipewire-jack/jack/weakmacros.h b/pipewire-jack/jack/weakmacros.h new file mode 100644 index 0000000..944fddb --- /dev/null +++ b/pipewire-jack/jack/weakmacros.h @@ -0,0 +1,97 @@ +/* + Copyright (C) 2010 Paul Davis + + This program 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 program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef __weakmacros_h__ +#define __weakmacros_h__ + +/************************************************************* + * NOTE: JACK_WEAK_EXPORT ***MUST*** be used on every function + * added to the JACK API after the 0.116.2 release. + * + * Functions that predate this release are marked with + * JACK_WEAK_OPTIONAL_EXPORT which can be defined at compile + * time in a variety of ways. The default definition is empty, + * so that these symbols get normal linkage. If you wish to + * use all JACK symbols with weak linkage, include + * <jack/weakjack.h> before jack.h. + *************************************************************/ + +#ifdef __APPLE__ +#define WEAK_ATTRIBUTE weak_import +#else +#define WEAK_ATTRIBUTE __weak__ +#endif + +#ifndef JACK_WEAK_EXPORT +#ifdef __GNUC__ +/* JACK_WEAK_EXPORT needs to be a macro which + expands into a compiler directive. If non-null, the directive + must tell the compiler to arrange for weak linkage of + the symbol it used with. For this to work full may + require linker arguments in the client as well. +*/ + +#ifdef _WIN32 + /* + Not working with __declspec(dllexport) so normal linking + Linking with JackWeakAPI.cpp will be the preferred way. + */ + #define JACK_WEAK_EXPORT +#else + #define JACK_WEAK_EXPORT __attribute__((WEAK_ATTRIBUTE)) +#endif + +#else +/* Add other things here for non-gcc platforms */ + +#ifdef _WIN32 +#define JACK_WEAK_EXPORT +#endif + +#endif +#endif + +#ifndef JACK_WEAK_EXPORT +#define JACK_WEAK_EXPORT +#endif + +#ifndef JACK_OPTIONAL_WEAK_EXPORT +#define JACK_OPTIONAL_WEAK_EXPORT +#endif + +#ifndef JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT +#ifdef __GNUC__ +#define JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT __attribute__((__deprecated__)) +#else +/* Add other things here for non-gcc platforms */ + +#ifdef _WIN32 +#define JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT +#endif + +#endif /* __GNUC__ */ + +#ifndef JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT +#define JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT +#endif + +#endif + +#endif /* __weakmacros_h__ */ + diff --git a/pipewire-jack/meson.build b/pipewire-jack/meson.build new file mode 100644 index 0000000..fecc282 --- /dev/null +++ b/pipewire-jack/meson.build @@ -0,0 +1,5 @@ +jack_inc = include_directories('.') +if get_option('jack-devel') == true + install_subdir('jack', install_dir: get_option('includedir'), strip_directory: false) +endif +subdir('src') diff --git a/pipewire-jack/src/control.c b/pipewire-jack/src/control.c new file mode 100644 index 0000000..da06e2c --- /dev/null +++ b/pipewire-jack/src/control.c @@ -0,0 +1,472 @@ +/* PipeWire + * + * Copyright © 2021 Florian Hülsmann <fh@cbix.de> + * + * 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 "config.h" + +#include <stdio.h> +#include <unistd.h> +#include <signal.h> + +#include <jack/control.h> +#include <jack/jslist.h> + +#include <pipewire/pipewire.h> + +struct jackctl_sigmask +{ + sigset_t signals; +}; + +struct jackctl_sigmask sigmask; + +SPA_EXPORT +jackctl_sigmask_t * jackctl_setup_signals(unsigned int flags) +{ + // stub + pw_log_warn("not implemented %d", flags); + sigemptyset(&sigmask.signals); + return &sigmask; +} + +SPA_EXPORT +void jackctl_wait_signals(jackctl_sigmask_t * signals) +{ + // stub + pw_log_warn("not implemented %p", signals); +} + +SPA_EXPORT +jackctl_server_t * jackctl_server_create( + bool (* on_device_acquire)(const char * device_name), + void (* on_device_release)(const char * device_name)) +{ + pw_log_error("deprecated"); + return jackctl_server_create2(on_device_acquire, on_device_release, NULL); +} + +struct jackctl_server +{ + // stub + JSList * empty; + JSList * drivers; +}; + +struct jackctl_driver +{ + // stub +}; + +SPA_EXPORT +jackctl_server_t * jackctl_server_create2( + bool (* on_device_acquire)(const char * device_name), + void (* on_device_release)(const char * device_name), + void (* on_device_reservation_loop)(void)) +{ + // stub + pw_log_warn("not implemented %p %p %p", on_device_acquire, on_device_release, on_device_reservation_loop); + + // setup server + jackctl_server_t * server; + server = (jackctl_server_t *)malloc(sizeof(jackctl_server_t)); + if (server == NULL) { + return NULL; + } + server->empty = NULL; + server->drivers = NULL; + + // setup dummy (default) driver + jackctl_driver_t * dummy; + dummy = (jackctl_driver_t *)malloc(sizeof(jackctl_driver_t)); + if (dummy == NULL) { + free(server); + return NULL; + } + server->drivers = jack_slist_append (server->drivers, dummy); + + return server; +} + +SPA_EXPORT +void jackctl_server_destroy(jackctl_server_t * server) +{ + // stub + pw_log_warn("%p: not implemented", server); + + if (server) { + if (server->drivers) { + free(server->drivers->data); + } + jack_slist_free(server->empty); + jack_slist_free(server->drivers); + free(server); + } +} + +SPA_EXPORT +bool jackctl_server_open(jackctl_server_t * server, jackctl_driver_t * driver) +{ + // stub + pw_log_warn("%p: not implemented %p", server, driver); + return true; +} + +SPA_EXPORT +bool jackctl_server_start(jackctl_server_t * server) +{ + // stub + pw_log_warn("%p: not implemented", server); + return true; +} + +SPA_EXPORT +bool jackctl_server_stop(jackctl_server_t * server) +{ + // stub + pw_log_warn("%p: not implemented", server); + return false; +} + +SPA_EXPORT +bool jackctl_server_close(jackctl_server_t * server) +{ + // stub + pw_log_warn("%p: not implemented", server); + return false; +} + +SPA_EXPORT +const JSList * jackctl_server_get_drivers_list(jackctl_server_t * server) +{ + // stub + pw_log_warn("%p: not implemented", server); + if (server == NULL) { + pw_log_warn("server == NULL"); + return NULL; + } + return server->drivers; +} + +SPA_EXPORT +const JSList * jackctl_server_get_parameters(jackctl_server_t * server) +{ + // stub + pw_log_warn("%p: not implemented", server); + if (server == NULL) { + return NULL; + } + return server->empty; +} + +SPA_EXPORT +const JSList * jackctl_server_get_internals_list(jackctl_server_t * server) +{ + // stub + pw_log_warn("%p: not implemented", server); + if (server == NULL) { + return NULL; + } + return server->empty; +} + +SPA_EXPORT +bool jackctl_server_load_internal(jackctl_server_t * server, jackctl_internal_t * internal) +{ + // stub + pw_log_warn("%p: not implemented %p", server, internal); + return true; +} + +SPA_EXPORT +bool jackctl_server_unload_internal(jackctl_server_t * server, jackctl_internal_t * internal) +{ + // stub + pw_log_warn("%p: not implemented %p", server, internal); + return true; +} + +SPA_EXPORT +bool jackctl_server_load_session_file(jackctl_server_t * server_ptr, const char * file) +{ + // stub + pw_log_warn("%p: not implemented %s", server_ptr, file); + return false; +} + +SPA_EXPORT +bool jackctl_server_add_slave(jackctl_server_t * server, jackctl_driver_t * driver) +{ + // stub + pw_log_warn("%p: not implemented %p", server, driver); + return false; +} + +SPA_EXPORT +bool jackctl_server_remove_slave(jackctl_server_t * server, jackctl_driver_t * driver) +{ + // stub + pw_log_warn("%p: not implemented %p", server, driver); + return false; +} + +SPA_EXPORT +bool jackctl_server_switch_master(jackctl_server_t * server, jackctl_driver_t * driver) +{ + // stub + pw_log_warn("%p: not implemented %p", server, driver); + return false; +} + + +SPA_EXPORT +const char * jackctl_driver_get_name(jackctl_driver_t * driver) +{ + // stub + pw_log_warn("%p: not implemented", driver); + return "dummy"; +} + +SPA_EXPORT +jackctl_driver_type_t jackctl_driver_get_type(jackctl_driver_t * driver) +{ + // stub + pw_log_warn("%p: not implemented", driver); + return (jackctl_driver_type_t)0; +} + +SPA_EXPORT +const JSList * jackctl_driver_get_parameters(jackctl_driver_t * driver) +{ + // stub + pw_log_warn("%p: not implemented", driver); + return NULL; +} + +SPA_EXPORT +int jackctl_driver_params_parse(jackctl_driver_t * driver, int argc, char* argv[]) +{ + // stub + pw_log_warn("%p: not implemented %d %p", driver, argc, argv); + return 1; +} + +SPA_EXPORT +const char * jackctl_internal_get_name(jackctl_internal_t * internal) +{ + // stub + pw_log_warn("not implemented %p", internal); + return "pipewire-jack-stub"; +} + +SPA_EXPORT +const JSList * jackctl_internal_get_parameters(jackctl_internal_t * internal) +{ + // stub + pw_log_warn("not implemented %p", internal); + return NULL; +} + +SPA_EXPORT +const char * jackctl_parameter_get_name(jackctl_parameter_t * parameter) +{ + // stub + pw_log_warn("%p: not implemented", parameter); + return "pipewire-jack-stub"; +} + +SPA_EXPORT +const char * jackctl_parameter_get_short_description(jackctl_parameter_t * parameter) +{ + // stub + pw_log_warn("%p: not implemented", parameter); + return "pipewire-jack-stub"; +} + +SPA_EXPORT +const char * jackctl_parameter_get_long_description(jackctl_parameter_t * parameter) +{ + // stub + pw_log_warn("%p: not implemented", parameter); + return "pipewire-jack-stub"; +} + +SPA_EXPORT +jackctl_param_type_t jackctl_parameter_get_type(jackctl_parameter_t * parameter) +{ + // stub + pw_log_warn("%p: not implemented", parameter); + return (jackctl_param_type_t)0; +} + +SPA_EXPORT +char jackctl_parameter_get_id(jackctl_parameter_t * parameter) +{ + // stub + pw_log_warn("%p: not implemented", parameter); + return 0; +} + +SPA_EXPORT +bool jackctl_parameter_is_set(jackctl_parameter_t * parameter) +{ + // stub + pw_log_warn("%p: not implemented", parameter); + return false; +} + +SPA_EXPORT +bool jackctl_parameter_reset(jackctl_parameter_t * parameter) +{ + // stub + pw_log_warn("%p: not implemented", parameter); + return false; +} + +SPA_EXPORT +union jackctl_parameter_value jackctl_parameter_get_value(jackctl_parameter_t * parameter) +{ + // stub + pw_log_warn("%p: not implemented", parameter); + union jackctl_parameter_value value; + memset(&value, 0, sizeof(value)); + return value; +} + +SPA_EXPORT +bool jackctl_parameter_set_value( + jackctl_parameter_t * parameter, + const union jackctl_parameter_value * value_ptr) +{ + // stub + pw_log_warn("%p: not implemented", parameter); + return false; +} + +SPA_EXPORT +union jackctl_parameter_value jackctl_parameter_get_default_value(jackctl_parameter_t * parameter) +{ + // stub + pw_log_warn("%p: not implemented", parameter); + union jackctl_parameter_value value; + memset(&value, 0, sizeof(value)); + return value; +} + +SPA_EXPORT +bool jackctl_parameter_has_range_constraint(jackctl_parameter_t * parameter) +{ + // stub + pw_log_warn("%p: not implemented", parameter); + return false; +} + +SPA_EXPORT +bool jackctl_parameter_has_enum_constraint(jackctl_parameter_t * parameter) +{ + // stub + pw_log_warn("%p: not implemented", parameter); + return false; +} + +SPA_EXPORT +uint32_t jackctl_parameter_get_enum_constraints_count(jackctl_parameter_t * parameter) +{ + // stub + pw_log_warn("%p: not implemented", parameter); + return 0; +} + +SPA_EXPORT +union jackctl_parameter_value jackctl_parameter_get_enum_constraint_value( + jackctl_parameter_t * parameter, + uint32_t index) +{ + // stub + pw_log_warn("%p: not implemented %d", parameter, index); + union jackctl_parameter_value value; + memset(&value, 0, sizeof(value)); + return value; +} + +SPA_EXPORT +const char * jackctl_parameter_get_enum_constraint_description( + jackctl_parameter_t * parameter, + uint32_t index) +{ + // stub + pw_log_warn("%p: not implemented %d", parameter, index); + return "pipewire-jack-stub"; +} + +SPA_EXPORT +void jackctl_parameter_get_range_constraint( + jackctl_parameter_t * parameter, + union jackctl_parameter_value * min_ptr, + union jackctl_parameter_value * max_ptr) +{ + // stub + pw_log_warn("%p: not implemented %p %p", parameter, min_ptr, max_ptr); +} + +SPA_EXPORT +bool jackctl_parameter_constraint_is_strict(jackctl_parameter_t * parameter) +{ + // stub + pw_log_warn("not implemented %p", parameter); + return false; +} + +SPA_EXPORT +bool jackctl_parameter_constraint_is_fake_value(jackctl_parameter_t * parameter) +{ + // stub + pw_log_warn("not implemented %p", parameter); + return false; +} + +SPA_EXPORT SPA_PRINTF_FUNC(1, 2) +void jack_error(const char *format, ...) +{ + va_list args; + va_start(args, format); + pw_log_logv(SPA_LOG_LEVEL_ERROR, "", 0, "", format, args); + va_end(args); +} + +SPA_EXPORT SPA_PRINTF_FUNC(1, 2) +void jack_info(const char *format, ...) +{ + va_list args; + va_start(args, format); + pw_log_logv(SPA_LOG_LEVEL_INFO, "", 0, "", format, args); + va_end(args); +} + +SPA_EXPORT SPA_PRINTF_FUNC(1, 2) +void jack_log(const char *format, ...) +{ + va_list args; + va_start(args, format); + pw_log_logv(SPA_LOG_LEVEL_DEBUG, "", 0, "", format, args); + va_end(args); +} diff --git a/pipewire-jack/src/dummy.c b/pipewire-jack/src/dummy.c new file mode 100644 index 0000000..1d6bb57 --- /dev/null +++ b/pipewire-jack/src/dummy.c @@ -0,0 +1,39 @@ +/* PipeWire + * + * Copyright © 2020 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 "config.h" + +#include <stdio.h> +#include <unistd.h> +#include <sys/mman.h> +#include <regex.h> +#include <math.h> + +#include <pipewire/pipewire.h> + +static void reg(void) __attribute__ ((constructor)); +static void reg(void) +{ + pw_init(NULL, NULL); +} diff --git a/pipewire-jack/src/export.c b/pipewire-jack/src/export.c new file mode 100644 index 0000000..4c9daf0 --- /dev/null +++ b/pipewire-jack/src/export.c @@ -0,0 +1,36 @@ +/* PipeWire + * + * Copyright © 2020 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 + */ + +#include <spa/utils/defs.h> + +#define JACK_METADATA_PREFIX "http://jackaudio.org/metadata/" +SPA_EXPORT const char *JACK_METADATA_CONNECTED = JACK_METADATA_PREFIX "connected"; +SPA_EXPORT const char *JACK_METADATA_EVENT_TYPES = JACK_METADATA_PREFIX "event-types"; +SPA_EXPORT const char *JACK_METADATA_HARDWARE = JACK_METADATA_PREFIX "hardware"; +SPA_EXPORT const char *JACK_METADATA_ICON_LARGE = JACK_METADATA_PREFIX "icon-large"; +SPA_EXPORT const char *JACK_METADATA_ICON_NAME = JACK_METADATA_PREFIX "icon-name"; +SPA_EXPORT const char *JACK_METADATA_ICON_SMALL = JACK_METADATA_PREFIX "icon-small"; +SPA_EXPORT const char *JACK_METADATA_ORDER = JACK_METADATA_PREFIX "order"; +SPA_EXPORT const char *JACK_METADATA_PORT_GROUP = JACK_METADATA_PREFIX "port-group"; +SPA_EXPORT const char *JACK_METADATA_PRETTY_NAME = JACK_METADATA_PREFIX "pretty-name"; +SPA_EXPORT const char *JACK_METADATA_SIGNAL_TYPE = JACK_METADATA_PREFIX "signal-type"; +#undef JACK_METADATA_PREFIX diff --git a/pipewire-jack/src/meson.build b/pipewire-jack/src/meson.build new file mode 100644 index 0000000..20d1ccf --- /dev/null +++ b/pipewire-jack/src/meson.build @@ -0,0 +1,98 @@ +pipewire_jack_sources = [ + 'export.c', + 'pipewire-jack.c', + 'ringbuffer.c', + 'uuid.c', +] + +pipewire_jackserver_sources = pipewire_jack_sources +pipewire_jackserver_sources += [ + 'control.c', +] + +pipewire_net_sources = [ + 'net.c', +] +pipewire_jack_c_args = [ + '-DPIC', +] + +libjack_path = get_option('libjack-path') +if libjack_path == '' + libjack_path = modules_install_dir / 'jack' + libjack_path_dlopen = modules_install_dir_dlopen / 'jack' +else + libjack_path_dlopen = libjack_path +endif + +tools_config = configuration_data() +tools_config.set('LIBJACK_PATH', libjack_path_dlopen) + +configure_file(input : 'pw-jack.in', + output : 'pw-jack', + configuration : tools_config, + install_dir : pipewire_bindir) + +pipewire_jack = shared_library('jack', + pipewire_jack_sources, + soversion : soversion, + version : libversion, + c_args : pipewire_jack_c_args, + include_directories : [configinc, jack_inc], + dependencies : [pipewire_dep, mathlib], + install : true, + install_dir : libjack_path, +) + +pipewire_jackserver = shared_library('jackserver', + pipewire_jackserver_sources, + soversion : soversion, + version : libversion, + c_args : pipewire_jack_c_args, + include_directories : [configinc, jack_inc], + dependencies : [pipewire_dep, mathlib], + install : true, + install_dir : libjack_path, +) + +pipewire_jacknet = shared_library('jacknet', + pipewire_net_sources, + soversion : soversion, + version : libversion, + c_args : pipewire_jack_c_args, + include_directories : [configinc, jack_inc], + dependencies : [pipewire_dep, mathlib], + install : true, + install_dir : libjack_path, +) + + +if get_option('jack-devel') == true + if meson.version().version_compare('<0.59.0') + error( + ''' + Before version 0.59.0 Meson creates a wrong jack pkg-config file. + For that reason this is now an error. Please update Meson, + if you want to have JACK development files. + ''') + endif + + pkgconfig.generate(filebase : 'jack', + libraries : [pipewire_jack], + name : 'jack', + description : 'PipeWire JACK API', + version : '1.9.17', + extra_cflags : '-D_REENTRANT', + unescaped_variables: ['server_libs=-L${libdir} -ljackserver', 'jack_implementation=pipewire']) +endif + +if sdl_dep.found() + executable('video-dsp-play', + '../examples/video-dsp-play.c', + include_directories : [jack_inc], + install : installed_tests_enabled, + install_dir : installed_tests_execdir / 'examples' / 'jack', + dependencies : [sdl_dep, mathlib], + link_with: pipewire_jack, + ) +endif diff --git a/pipewire-jack/src/metadata.c b/pipewire-jack/src/metadata.c new file mode 100644 index 0000000..da3d75f --- /dev/null +++ b/pipewire-jack/src/metadata.c @@ -0,0 +1,421 @@ +/* PipeWire + * + * Copyright © 2018 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 "config.h" + +#include <stdio.h> +#include <unistd.h> +#include <sys/mman.h> + +#include <spa/utils/string.h> + +#include <jack/metadata.h> +#include <jack/uuid.h> + +#include <pipewire/pipewire.h> +#include <pipewire/extensions/metadata.h> + +static jack_description_t *find_description(jack_uuid_t subject) +{ + jack_description_t *desc; + pw_array_for_each(desc, &globals.descriptions) { + if (jack_uuid_compare(desc->subject, subject) == 0) + return desc; + } + return NULL; +} + +static void set_property(jack_property_t *prop, const char *key, const char *value, const char *type) +{ + prop->key = strdup(key); + prop->data = strdup(value); + prop->type = strdup(type); +} + +static void clear_property(jack_property_t *prop) +{ + free((char*)prop->key); + free((char*)prop->data); + free((char*)prop->type); +} + +static jack_property_t *copy_properties(jack_property_t *src, uint32_t cnt) +{ + jack_property_t *dst; + uint32_t i; + dst = malloc(sizeof(jack_property_t) * cnt); + if (dst != NULL) { + for (i = 0; i < cnt; i++) + set_property(&dst[i], src[i].key, src[i].data, src[i].type); + } + return dst; +} + +static int copy_description(jack_description_t *dst, jack_description_t *src) +{ + dst->properties = copy_properties(src->properties, src->property_cnt); + if (dst->properties == NULL) + return -errno; + jack_uuid_copy(&dst->subject, src->subject); + dst->property_cnt = src->property_cnt; + dst->property_size = src->property_size; + return dst->property_cnt; +} + +static jack_description_t *add_description(jack_uuid_t subject) +{ + jack_description_t *desc; + desc = pw_array_add(&globals.descriptions, sizeof(*desc)); + if (desc != NULL) { + spa_zero(*desc); + jack_uuid_copy(&desc->subject, subject); + } + return desc; +} + +static void remove_description(jack_description_t *desc) +{ + jack_free_description(desc, false); + pw_array_remove(&globals.descriptions, desc); +} + +static jack_property_t *find_property(jack_description_t *desc, const char *key) +{ + uint32_t i; + for (i = 0; i < desc->property_cnt; i++) { + jack_property_t *prop = &desc->properties[i]; + if (spa_streq(prop->key, key)) + return prop; + } + return NULL; +} + +static jack_property_t *add_property(jack_description_t *desc, const char *key, + const char *value, const char *type) +{ + jack_property_t *prop; + void *np; + size_t ns; + + if (desc->property_cnt == desc->property_size) { + ns = desc->property_size > 0 ? desc->property_size * 2 : 8; + np = pw_reallocarray(desc->properties, ns, sizeof(*prop)); + if (np == NULL) + return NULL; + desc->property_size = ns; + desc->properties = np; + } + prop = &desc->properties[desc->property_cnt++]; + set_property(prop, key, value, type); + return prop; +} + +static void remove_property(jack_description_t *desc, jack_property_t *prop) +{ + clear_property(prop); + desc->property_cnt--; + memmove(desc->properties, SPA_PTROFF(prop, sizeof(*prop), void), + SPA_PTRDIFF(SPA_PTROFF(desc->properties, sizeof(*prop) * desc->property_cnt, void), + prop)); + + if (desc->property_cnt == 0) + remove_description(desc); +} + +static int change_property(jack_property_t *prop, const char *value, const char *type) +{ + int changed = 0; + if (!spa_streq(prop->data, value)) { + free((char*)prop->data); + prop->data = strdup(value); + changed++; + } + if (!spa_streq(prop->type, type)) { + free((char*)prop->type); + prop->type = strdup(type); + changed++; + } + return changed; +} + +static int update_property(struct client *c, + jack_uuid_t subject, + const char* key, + const char* type, + const char* value) +{ + jack_property_change_t change; + jack_description_t *desc; + int changed = 0; + + pthread_mutex_lock(&globals.lock); + desc = find_description(subject); + + if (key == NULL) { + if (desc != NULL) { + remove_description(desc); + change = PropertyDeleted; + changed++; + } + } else { + jack_property_t *prop; + + prop = desc ? find_property(desc, key) : NULL; + + if (value == NULL || type == NULL) { + if (prop != NULL) { + remove_property(desc, prop); + change = PropertyDeleted; + changed++; + } + } else if (prop == NULL) { + if (desc == NULL) + desc = add_description(subject); + if (desc == NULL) { + changed = -errno; + pw_log_warn("add_description failed: %m"); + } else if (add_property(desc, key, value, type) == NULL) { + changed = -errno; + pw_log_warn("add_property failed: %m"); + } else { + change = PropertyCreated; + changed++; + } + } else { + changed = change_property(prop, value, type); + change = PropertyChanged; + } + } + pthread_mutex_unlock(&globals.lock); + + if (c->property_callback && changed > 0) { + pw_log_info("emit %"PRIu64" %s", (uint64_t)subject, key); + c->property_callback(subject, key, change, c->property_arg); + } + return changed; +} + + +SPA_EXPORT +int jack_set_property(jack_client_t*client, + jack_uuid_t subject, + const char* key, + const char* value, + const char* type) +{ + struct client *c = (struct client *) client; + struct object *o; + uint32_t serial; + int res = -1; + + spa_return_val_if_fail(c != NULL, -EINVAL); + spa_return_val_if_fail(key != NULL, -EINVAL); + spa_return_val_if_fail(value != NULL, -EINVAL); + + pw_thread_loop_lock(c->context.loop); + if (c->metadata == NULL) + goto done; + + if (subject & (1<<30)) + goto done; + + serial = jack_uuid_to_index(subject); + if ((o = find_by_serial(c, serial)) == NULL) + goto done; + + if (type == NULL) + type = ""; + + pw_log_info("set id:%u (%"PRIu64") '%s' to '%s@%s'", o->id, subject, key, value, type); + if (update_property(c, subject, key, type, value)) + pw_metadata_set_property(c->metadata->proxy, o->id, key, type, value); + res = 0; +done: + pw_thread_loop_unlock(c->context.loop); + + return res; +} + +SPA_EXPORT +int jack_get_property(jack_uuid_t subject, + const char* key, + char** value, + char** type) +{ + jack_description_t *desc; + jack_property_t *prop; + int res = -1; + + pthread_mutex_lock(&globals.lock); + desc = find_description(subject); + if (desc == NULL) + goto done; + + prop = find_property(desc, key); + if (prop == NULL) + goto done; + + *value = strdup(prop->data); + *type = strdup(prop->type); + res = 0; + + pw_log_debug("subject:%"PRIu64" key:'%s' value:'%s' type:'%s'", + subject, key, *value, *type); +done: + pthread_mutex_unlock(&globals.lock); + return res; +} + +SPA_EXPORT +void jack_free_description (jack_description_t* desc, int free_description_itself) +{ + uint32_t n; + + for (n = 0; n < desc->property_cnt; ++n) + clear_property(&desc->properties[n]); + free(desc->properties); + if (free_description_itself) + free(desc); +} + +SPA_EXPORT +int jack_get_properties (jack_uuid_t subject, + jack_description_t* desc) +{ + jack_description_t *d; + int res = -1; + + spa_return_val_if_fail(desc != NULL, -EINVAL); + + pthread_mutex_lock(&globals.lock); + d = find_description(subject); + if (d == NULL) + goto done; + + res = copy_description(desc, d); +done: + pthread_mutex_unlock(&globals.lock); + return res; +} + +SPA_EXPORT +int jack_get_all_properties (jack_description_t** result) +{ + uint32_t i; + jack_description_t *dst, *src; + struct pw_array *descriptions; + uint32_t len; + + pthread_mutex_lock(&globals.lock); + descriptions = &globals.descriptions; + len = pw_array_get_len(descriptions, jack_description_t); + src = descriptions->data; + dst = malloc(descriptions->size); + for (i = 0; i < len; i++) + copy_description(&dst[i], &src[i]); + *result = dst; + pthread_mutex_unlock(&globals.lock); + + return len; +} + +SPA_EXPORT +int jack_remove_property (jack_client_t* client, jack_uuid_t subject, const char* key) +{ + struct client *c = (struct client *) client; + uint32_t id; + int res = -1; + + spa_return_val_if_fail(c != NULL, -EINVAL); + spa_return_val_if_fail(key != NULL, -EINVAL); + + pw_thread_loop_lock(c->context.loop); + + if (c->metadata == NULL) + goto done; + + id = jack_uuid_to_index(subject); + + pw_log_info("remove id:%u (%"PRIu64") '%s'", id, subject, key); + pw_metadata_set_property(c->metadata->proxy, + id, key, NULL, NULL); + res = 0; +done: + pw_thread_loop_unlock(c->context.loop); + + return res; +} + +SPA_EXPORT +int jack_remove_properties (jack_client_t* client, jack_uuid_t subject) +{ + struct client *c = (struct client *) client; + uint32_t id; + int res = -1; + + spa_return_val_if_fail(c != NULL, -EINVAL); + + pw_thread_loop_lock(c->context.loop); + if (c->metadata == NULL) + goto done; + + id = jack_uuid_to_index(subject); + + pw_log_info("remove id:%u (%"PRIu64")", id, subject); + pw_metadata_set_property(c->metadata->proxy, + id, NULL, NULL, NULL); + res = 0; +done: + pw_thread_loop_unlock(c->context.loop); + + return res; +} + +SPA_EXPORT +int jack_remove_all_properties (jack_client_t* client) +{ + struct client *c = (struct client *) client; + + spa_return_val_if_fail(c != NULL, -EINVAL); + + pw_thread_loop_lock(c->context.loop); + pw_metadata_clear(c->metadata->proxy); + pw_thread_loop_unlock(c->context.loop); + + return 0; +} + +SPA_EXPORT +int jack_set_property_change_callback (jack_client_t* client, + JackPropertyChangeCallback callback, + void* arg) +{ + struct client *c = (struct client *) client; + + spa_return_val_if_fail(c != NULL, -EINVAL); + + c->property_callback = callback; + c->property_arg = arg; + return 0; +} diff --git a/pipewire-jack/src/net.c b/pipewire-jack/src/net.c new file mode 100644 index 0000000..e48b76b --- /dev/null +++ b/pipewire-jack/src/net.c @@ -0,0 +1,169 @@ +/* PipeWire + * + * Copyright © 2022 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 "config.h" + +#include <stdio.h> +#include <unistd.h> +#include <errno.h> + +#include <jack/net.h> + +#include <pipewire/pipewire.h> + +SPA_EXPORT +jack_net_slave_t* jack_net_slave_open(const char* ip, int port, const char* name, + jack_slave_t* request, jack_master_t* result) +{ + return NULL; +} + +SPA_EXPORT +int jack_net_slave_close(jack_net_slave_t* net) +{ + return ENOTSUP; +} + +SPA_EXPORT +int jack_set_net_slave_process_callback(jack_net_slave_t * net, JackNetSlaveProcessCallback net_callback, void *arg) +{ + return ENOTSUP; +} + +SPA_EXPORT +int jack_net_slave_activate(jack_net_slave_t* net) +{ + return ENOTSUP; +} + +SPA_EXPORT +int jack_net_slave_deactivate(jack_net_slave_t* net) +{ + return ENOTSUP; +} + +SPA_EXPORT +int jack_net_slave_is_active(jack_net_slave_t* net) +{ + return false; +} + +SPA_EXPORT +int jack_set_net_slave_buffer_size_callback(jack_net_slave_t *net, JackNetSlaveBufferSizeCallback bufsize_callback, void *arg) +{ + return ENOTSUP; +} + +SPA_EXPORT +int jack_set_net_slave_sample_rate_callback(jack_net_slave_t *net, JackNetSlaveSampleRateCallback samplerate_callback, void *arg) +{ + return ENOTSUP; +} + +SPA_EXPORT +int jack_set_net_slave_shutdown_callback(jack_net_slave_t *net, JackNetSlaveShutdownCallback shutdown_callback, void *arg) +{ + return ENOTSUP; +} + +SPA_EXPORT +int jack_set_net_slave_restart_callback(jack_net_slave_t *net, JackNetSlaveRestartCallback restart_callback, void *arg) +{ + return ENOTSUP; +} + +SPA_EXPORT +int jack_set_net_slave_error_callback(jack_net_slave_t *net, JackNetSlaveErrorCallback error_callback, void *arg) +{ + return ENOTSUP; +} + +SPA_EXPORT +jack_net_master_t* jack_net_master_open(const char* ip, int port, jack_master_t* request, jack_slave_t* result) +{ + return NULL; +} + +SPA_EXPORT +int jack_net_master_close(jack_net_master_t* net) +{ + return ENOTSUP; +} + +SPA_EXPORT +int jack_net_master_recv(jack_net_master_t* net, int audio_input, float** audio_input_buffer, int midi_input, void** midi_input_buffer) +{ + return ENOTSUP; +} + +SPA_EXPORT +int jack_net_master_recv_slice(jack_net_master_t* net, int audio_input, float** audio_input_buffer, int midi_input, void** midi_input_buffer, int frames) +{ + return ENOTSUP; +} + +SPA_EXPORT +int jack_net_master_send(jack_net_master_t* net, int audio_output, float** audio_output_buffer, int midi_output, void** midi_output_buffer) +{ + return ENOTSUP; +} + +SPA_EXPORT +int jack_net_master_send_slice(jack_net_master_t* net, int audio_output, float** audio_output_buffer, int midi_output, void** midi_output_buffer, int frames) +{ + return ENOTSUP; +} + +SPA_EXPORT +jack_adapter_t* jack_create_adapter(int input, int output, + jack_nframes_t host_buffer_size, + jack_nframes_t host_sample_rate, + jack_nframes_t adapted_buffer_size, + jack_nframes_t adapted_sample_rate) +{ + return NULL; +} + +SPA_EXPORT +int jack_destroy_adapter(jack_adapter_t* adapter) +{ + return ENOTSUP; +} + +SPA_EXPORT +void jack_flush_adapter(jack_adapter_t* adapter) +{ +} + +SPA_EXPORT +int jack_adapter_push_and_pull(jack_adapter_t* adapter, float** input, float** output, unsigned int frames) +{ + return ENOTSUP; +} + +SPA_EXPORT +int jack_adapter_pull_and_push(jack_adapter_t* adapter, float** input, float** output, unsigned int frames) +{ + return ENOTSUP; +} diff --git a/pipewire-jack/src/pipewire-jack-extensions.h b/pipewire-jack/src/pipewire-jack-extensions.h new file mode 100644 index 0000000..8e38e9e --- /dev/null +++ b/pipewire-jack/src/pipewire-jack-extensions.h @@ -0,0 +1,50 @@ +/* PipeWire JACK extensions + * + * Copyright © 2020 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. + */ + +#ifndef PIPEWIRE_JACK_EXTENSIONS_H +#define PIPEWIRE_JACK_EXTENSIONS_H +#include <stdint.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** 1.0 gamma, full range HDR 0.0 -> 1.0, pre-multiplied + * alpha, BT.2020 primaries, progressive */ +#define JACK_DEFAULT_VIDEO_TYPE "32 bit float RGBA video" + +typedef struct jack_image_size { + uint32_t width; + uint32_t height; + uint32_t stride; + uint32_t flags; +} jack_image_size_t; + +int jack_get_video_image_size(jack_client_t *client, jack_image_size_t *size); + +#ifdef __cplusplus +} +#endif + +#endif /* PIPEWIRE_JACK_EXTENSIONS_H */ diff --git a/pipewire-jack/src/pipewire-jack.c b/pipewire-jack/src/pipewire-jack.c new file mode 100644 index 0000000..2e82677 --- /dev/null +++ b/pipewire-jack/src/pipewire-jack.c @@ -0,0 +1,6509 @@ +/* PipeWire + * + * Copyright © 2018 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 "config.h" + +#include <stdio.h> +#include <unistd.h> +#include <sys/mman.h> +#include <regex.h> +#include <math.h> + +#include <jack/jack.h> +#include <jack/session.h> +#include <jack/thread.h> +#include <jack/midiport.h> +#include <jack/uuid.h> +#include <jack/metadata.h> + +#include <spa/support/cpu.h> +#include <spa/param/audio/format-utils.h> +#include <spa/param/video/format-utils.h> +#include <spa/debug/types.h> +#include <spa/debug/pod.h> +#include <spa/utils/json.h> +#include <spa/utils/string.h> + +#include <pipewire/pipewire.h> +#include <pipewire/private.h> +#include <pipewire/thread.h> +#include <pipewire/data-loop.h> + +#include "pipewire/extensions/client-node.h" +#include "pipewire/extensions/metadata.h" +#include "pipewire-jack-extensions.h" + +#define JACK_DEFAULT_VIDEO_TYPE "32 bit float RGBA video" + +/* use 512KB stack per thread - the default is way too high to be feasible + * with mlockall() on many systems */ +#define THREAD_STACK 524288 + +#define DEFAULT_RT_MAX 88 + +#define JACK_CLIENT_NAME_SIZE 256 +#define JACK_PORT_NAME_SIZE 256 +#define JACK_PORT_TYPE_SIZE 32 +#define MONITOR_EXT " Monitor" + +#define MAX_MIX 1024 +#define MAX_BUFFER_FRAMES 8192 + +#define MAX_ALIGN 16 +#define MAX_BUFFERS 2 +#define MAX_BUFFER_DATAS 1u + +#define REAL_JACK_PORT_NAME_SIZE (JACK_CLIENT_NAME_SIZE + JACK_PORT_NAME_SIZE) + +PW_LOG_TOPIC_STATIC(jack_log_topic, "jack"); +#define PW_LOG_TOPIC_DEFAULT jack_log_topic + +#define TYPE_ID_AUDIO 0 +#define TYPE_ID_MIDI 1 +#define TYPE_ID_VIDEO 2 +#define TYPE_ID_OTHER 3 + +#define SELF_CONNECT_ALLOW 0 +#define SELF_CONNECT_FAIL_EXT -1 +#define SELF_CONNECT_IGNORE_EXT 1 +#define SELF_CONNECT_FAIL_ALL -2 +#define SELF_CONNECT_IGNORE_ALL 2 + +struct client; +struct port; + +struct globals { + jack_thread_creator_t creator; + pthread_mutex_t lock; + struct pw_array descriptions; + struct spa_list free_objects; + struct spa_thread_utils *thread_utils; +}; + +static struct globals globals; +static bool mlock_warned = false; + +#define OBJECT_CHUNK 8 +#define RECYCLE_THRESHOLD 128 + +typedef void (*mix_func) (float *dst, float *src[], uint32_t n_src, bool aligned, uint32_t n_samples); + +static mix_func mix_function; + +struct object { + struct spa_list link; + + struct client *client; + +#define INTERFACE_Port 0 +#define INTERFACE_Node 1 +#define INTERFACE_Link 2 + uint32_t type; + uint32_t id; + uint32_t serial; + + union { + struct { + char name[JACK_CLIENT_NAME_SIZE+1]; + char node_name[512]; + int32_t priority; + uint32_t client_id; + } node; + struct { + uint32_t src; + uint32_t dst; + uint32_t src_serial; + uint32_t dst_serial; + bool src_ours; + bool dst_ours; + bool is_complete; + struct port *our_input; + struct port *our_output; + } 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]; + char system[REAL_JACK_PORT_NAME_SIZE+1]; + uint32_t system_id; + uint32_t type_id; + uint32_t node_id; + uint32_t monitor_requests; + int32_t priority; + struct port *port; + bool is_monitor; + struct object *node; + struct spa_latency_info latency[2]; + } port; + }; + struct pw_proxy *proxy; + struct spa_hook proxy_listener; + struct spa_hook object_listener; + unsigned int removing:1; + unsigned int removed:1; +}; + +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 mix { + struct spa_list link; + struct spa_list port_link; + uint32_t id; + uint32_t peer_id; + struct port *port; + struct port *peer_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 port_id; + struct object *object; + struct pw_properties *props; + struct spa_port_info info; +#define IDX_EnumFormat 0 +#define IDX_Buffers 1 +#define IDX_IO 2 +#define IDX_Format 3 +#define IDX_Latency 4 +#define N_PORT_PARAMS 5 + struct spa_param_info params[N_PORT_PARAMS]; + + struct spa_io_buffers io; + struct spa_list mix; + struct mix *global_mix; + + unsigned int empty_out:1; + unsigned int zeroed:1; + + float *emptyptr; + float empty[MAX_BUFFER_FRAMES + MAX_ALIGN]; + + void *(*get_buffer) (struct port *p, jack_nframes_t frames); +}; + +struct link { + struct spa_list link; + struct spa_list target_link; + struct client *client; + uint32_t node_id; + struct pw_memmap *mem; + struct pw_node_activation *activation; + int signalfd; +}; + +struct context { + struct pw_loop *l; + struct pw_thread_loop *loop; /* thread_lock protects all below */ + struct pw_context *context; + + struct spa_thread_utils *old_thread_utils; + struct spa_thread_utils thread_utils; + pthread_mutex_t lock; /* protects map and lists below, in addition to thread_lock */ + struct spa_list objects; + uint32_t free_count; +}; + +#define GET_DIRECTION(f) ((f) & JackPortIsInput ? SPA_DIRECTION_INPUT : SPA_DIRECTION_OUTPUT) + +#define GET_PORT(c,d,p) (pw_map_lookup(&c->ports[d], p)) + +struct metadata { + struct pw_metadata *proxy; + struct spa_hook proxy_listener; + struct spa_hook listener; + + char default_audio_sink[1024]; + char default_audio_source[1024]; +}; + +struct client { + char name[JACK_CLIENT_NAME_SIZE+1]; + + struct context context; + + char *server_name; + char *load_name; /* load module name */ + char *load_init; /* initialization string */ + jack_uuid_t session_id; /* requested session_id */ + + struct pw_data_loop *loop; + struct pw_properties *props; + + struct pw_core *core; + struct spa_hook core_listener; + struct pw_mempool *pool; + int pending_sync; + int last_sync; + int last_res; + + struct spa_node_info info; + + struct pw_registry *registry; + struct spa_hook registry_listener; + + struct pw_client_node *node; + struct spa_hook node_listener; + struct spa_hook proxy_listener; + + struct metadata *metadata; + struct metadata *settings; + + uint32_t node_id; + uint32_t serial; + 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; + JackPropertyChangeCallback property_callback; + void *property_arg; + + struct spa_io_position *position; + uint32_t sample_rate; + uint32_t buffer_frames; + struct spa_fraction latency; + + struct spa_list mix; + struct spa_list free_mix; + + struct spa_list free_ports; + struct pw_map ports[2]; + + struct spa_list links; + uint32_t driver_id; + struct pw_node_activation *driver_activation; + + struct pw_memmap *mem; + struct pw_node_activation *activation; + uint32_t xrun_count; + + struct { + struct spa_io_position *position; + struct pw_node_activation *driver_activation; + struct spa_list target_links; + } rt; + + pthread_mutex_t rt_lock; + unsigned int rt_locked:1; + unsigned int data_locked:1; + + unsigned int started:1; + unsigned int active:1; + unsigned int destroyed:1; + unsigned int first:1; + unsigned int thread_entered:1; + unsigned int has_transport:1; + unsigned int allow_mlock:1; + unsigned int warn_mlock:1; + unsigned int timeowner_conditional:1; + unsigned int show_monitor:1; + unsigned int merge_monitor:1; + unsigned int short_name:1; + unsigned int filter_name:1; + unsigned int freewheeling:1; + unsigned int locked_process:1; + unsigned int default_as_system:1; + int self_connect_mode; + int rt_max; + unsigned int fix_midi_events:1; + unsigned int global_buffer_size:1; + char filter_char; + + jack_position_t jack_position; + jack_transport_state_t jack_state; +}; + +static int do_sync(struct client *client); +static struct object *find_by_serial(struct client *c, uint32_t serial); + +#include "metadata.c" + +int pw_jack_match_rules(const char *rules, size_t size, const struct spa_dict *props, + int (*matched) (void *data, const char *action, const char *val, int len), + void *data); + +static struct object * alloc_object(struct client *c, int type) +{ + struct object *o; + int i; + + pthread_mutex_lock(&globals.lock); + if (spa_list_is_empty(&globals.free_objects)) { + o = calloc(OBJECT_CHUNK, sizeof(struct object)); + if (o == NULL) { + pthread_mutex_unlock(&globals.lock); + return NULL; + } + for (i = 0; i < OBJECT_CHUNK; i++) + spa_list_append(&globals.free_objects, &o[i].link); + } + o = spa_list_first(&globals.free_objects, struct object, link); + spa_list_remove(&o->link); + pthread_mutex_unlock(&globals.lock); + + o->client = c; + o->removed = false; + o->type = type; + pw_log_debug("%p: object:%p type:%d", c, o, type); + + return o; +} + +static void recycle_objects(struct client *c, uint32_t remain) +{ + struct object *o, *t; + pthread_mutex_lock(&globals.lock); + spa_list_for_each_safe(o, t, &c->context.objects, link) { + if (o->removed) { + pw_log_info("%p: recycle object:%p type:%d id:%u/%u", + c, o, o->type, o->id, o->serial); + spa_list_remove(&o->link); + memset(o, 0, sizeof(struct object)); + spa_list_append(&globals.free_objects, &o->link); + if (--c->context.free_count == remain) + break; + } + } + pthread_mutex_unlock(&globals.lock); +} + +/* JACK clients expect the objects to hang around after + * they are unregistered and freed. We mark the object removed and + * move it to the end of the queue. */ +static void free_object(struct client *c, struct object *o) +{ + pw_log_debug("%p: object:%p type:%d", c, o, o->type); + pthread_mutex_lock(&c->context.lock); + spa_list_remove(&o->link); + o->removed = true; + o->id = SPA_ID_INVALID; + spa_list_append(&c->context.objects, &o->link); + if (++c->context.free_count > RECYCLE_THRESHOLD) + recycle_objects(c, RECYCLE_THRESHOLD / 2); + pthread_mutex_unlock(&c->context.lock); + +} + +static void init_mix(struct mix *mix, uint32_t mix_id, struct port *port) +{ + mix->id = mix_id; + mix->port = port; + mix->io = NULL; + mix->n_buffers = 0; + spa_list_init(&mix->queue); + if (mix_id == SPA_ID_INVALID) + port->global_mix = mix; +} +static struct mix *find_mix_peer(struct client *c, uint32_t peer_id) +{ + struct mix *mix; + spa_list_for_each(mix, &c->mix, link) { + if (mix->peer_id == peer_id) + return mix; + } + return NULL; +} + +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; + uint32_t i; + + if ((mix = find_mix(c, port, mix_id)) != NULL) + return mix; + + if (spa_list_is_empty(&c->free_mix)) { + mix = calloc(OBJECT_CHUNK, sizeof(struct mix)); + if (mix == NULL) + return NULL; + for (i = 0; i < OBJECT_CHUNK; i++) + spa_list_append(&c->free_mix, &mix[i].link); + } + mix = spa_list_first(&c->free_mix, struct mix, link); + spa_list_remove(&mix->link); + spa_list_append(&c->mix, &mix->link); + + spa_list_append(&port->mix, &mix->port_link); + + init_mix(mix, mix_id, port); + + return mix; +} + +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("%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 void free_mix(struct client *c, struct mix *mix) +{ + clear_buffers(c, mix); + spa_list_remove(&mix->port_link); + if (mix->id == SPA_ID_INVALID) + mix->port->global_mix = NULL; + spa_list_remove(&mix->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; + uint32_t i; + + if (spa_list_is_empty(&c->free_ports)) { + p = calloc(OBJECT_CHUNK, sizeof(struct port)); + if (p == NULL) + return NULL; + for (i = 0; i < OBJECT_CHUNK; i++) + spa_list_append(&c->free_ports, &p[i].link); + } + p = spa_list_first(&c->free_ports, struct port, link); + spa_list_remove(&p->link); + + o = alloc_object(c, INTERFACE_Port); + o->id = SPA_ID_INVALID; + o->port.node_id = c->node_id; + o->port.port = p; + o->port.latency[SPA_DIRECTION_INPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_INPUT); + o->port.latency[SPA_DIRECTION_OUTPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_OUTPUT); + + p->valid = true; + p->zeroed = false; + p->client = c; + p->object = o; + spa_list_init(&p->mix); + p->props = pw_properties_new(NULL, NULL); + + p->direction = direction; + p->emptyptr = SPA_PTR_ALIGN(p->empty, MAX_ALIGN, float); + p->port_id = pw_map_insert_new(&c->ports[direction], p); + + pthread_mutex_lock(&c->context.lock); + spa_list_append(&c->context.objects, &o->link); + pthread_mutex_unlock(&c->context.lock); + + return p; +} + +static void free_port(struct client *c, struct port *p) +{ + struct mix *m; + + spa_list_consume(m, &p->mix, port_link) + free_mix(c, m); + + pw_map_remove(&c->ports[p->direction], p->port_id); + free_object(c, p->object); + pw_properties_free(p->props); + spa_list_append(&c->free_ports, &p->link); +} + +static struct object *find_node(struct client *c, const char *name) +{ + struct object *o; + + spa_list_for_each(o, &c->context.objects, link) { + if (o->removing || o->removed || o->type != INTERFACE_Node) + continue; + if (spa_streq(o->node.name, name)) + return o; + } + return NULL; +} + +static bool is_port_default(struct client *c, struct object *o) +{ + struct object *ot; + + if (c->metadata == NULL) + return false; + + if ((ot = o->port.node) != NULL && + (spa_streq(ot->node.node_name, c->metadata->default_audio_source) || + spa_streq(ot->node.node_name, c->metadata->default_audio_sink))) + return true; + + return false; +} + +static struct object *find_port_by_name(struct client *c, const char *name) +{ + struct object *o; + + spa_list_for_each(o, &c->context.objects, link) { + if (o->type != INTERFACE_Port || o->removed) + continue; + if (spa_streq(o->port.name, name) || + spa_streq(o->port.alias1, name) || + spa_streq(o->port.alias2, name)) + return o; + if (is_port_default(c, o) && spa_streq(o->port.system, name)) + return o; + } + return NULL; +} + +static struct object *find_by_id(struct client *c, uint32_t id) +{ + struct object *o; + spa_list_for_each(o, &c->context.objects, link) { + if (o->id == id) + return o; + } + return NULL; +} + +static struct object *find_by_serial(struct client *c, uint32_t serial) +{ + struct object *o; + spa_list_for_each(o, &c->context.objects, link) { + if (o->serial == serial) + return o; + } + return NULL; +} + +static struct object *find_id(struct client *c, uint32_t id, bool valid) +{ + struct object *o = find_by_id(c, id); + if (o != NULL && (!valid || o->client == c)) + return o; + return NULL; +} + +static struct object *find_type(struct client *c, uint32_t id, uint32_t type, bool valid) +{ + struct object *o = find_id(c, id, valid); + if (o != NULL && o->type == type) + 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.objects, link) { + if (l->type != INTERFACE_Link || l->removed) + continue; + if (l->port_link.src == src && + l->port_link.dst == dst) { + return l; + } + } + return NULL; +} + +static struct buffer *dequeue_buffer(struct client *c, struct mix *mix) +{ + struct buffer *b; + + if (SPA_UNLIKELY(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); + pw_log_trace_fp("%p: port %p: dequeue buffer %d", c, mix->port, b->id); + + return b; +} + +#if defined (__SSE__) +#include <xmmintrin.h> +static void mix_sse(float *dst, float *src[], uint32_t n_src, bool aligned, uint32_t n_samples) +{ + uint32_t i, n, unrolled; + __m128 in[1]; + + if (SPA_IS_ALIGNED(dst, 16) && aligned) + unrolled = n_samples & ~3; + else + unrolled = 0; + + for (n = 0; n < unrolled; n += 4) { + in[0] = _mm_load_ps(&src[0][n]); + for (i = 1; i < n_src; i++) + in[0] = _mm_add_ps(in[0], _mm_load_ps(&src[i][n])); + _mm_store_ps(&dst[n], in[0]); + } + for (; n < n_samples; n++) { + in[0] = _mm_load_ss(&src[0][n]); + for (i = 1; i < n_src; i++) + in[0] = _mm_add_ss(in[0], _mm_load_ss(&src[i][n])); + _mm_store_ss(&dst[n], in[0]); + } +} +#endif + +static void mix_c(float *dst, float *src[], uint32_t n_src, bool aligned, uint32_t n_samples) +{ + uint32_t n, i; + for (n = 0; n < n_samples; n++) { + float t = src[0][n]; + for (i = 1; i < n_src; i++) + t += src[i][n]; + dst[n] = t; + } +} + +SPA_EXPORT +void jack_get_version(int *major_ptr, int *minor_ptr, int *micro_ptr, int *proto_ptr) +{ + if (major_ptr) + *major_ptr = 3; + if (minor_ptr) + *minor_ptr = 0; + if (micro_ptr) + *micro_ptr = 0; + if (proto_ptr) + *proto_ptr = 0; +} + +#define do_callback_expr(c,expr,callback,...) \ +({ \ + if (c->callback && c->active) { \ + pw_thread_loop_unlock(c->context.loop); \ + if (c->locked_process) \ + pthread_mutex_lock(&c->rt_lock); \ + (expr); \ + pw_log_debug("emit " #callback); \ + c->callback(__VA_ARGS__); \ + if (c->locked_process) \ + pthread_mutex_unlock(&c->rt_lock); \ + pw_thread_loop_lock(c->context.loop); \ + } else { \ + if (c->active) \ + (expr); \ + pw_log_debug("skip " #callback \ + " cb:%p active:%d", c->callback, \ + c->active); \ + } \ +}) + +#define do_callback(c,callback,...) do_callback_expr(c,(void)0,callback,__VA_ARGS__) + +#define do_rt_callback_res(c,callback,...) \ +({ \ + int res = 0; \ + if (c->callback) { \ + if (pthread_mutex_trylock(&c->rt_lock) == 0) { \ + c->rt_locked = true; \ + res = c->callback(__VA_ARGS__); \ + c->rt_locked = false; \ + pthread_mutex_unlock(&c->rt_lock); \ + } else { \ + pw_log_debug("skip " #callback \ + " cb:%p", c->callback); \ + } \ + } \ + res; \ +}) + +SPA_EXPORT +const char * +jack_get_version_string(void) +{ + static char name[1024]; + snprintf(name, sizeof(name), "3.0.0.0 (using PipeWire %s)", pw_get_library_version()); + return name; +} + +static void on_sync_reply(void *data, uint32_t id, int seq) +{ + struct client *client = data; + if (id != PW_ID_CORE) + return; + client->last_sync = seq; + if (client->pending_sync == seq) + pw_thread_loop_signal(client->context.loop, false); +} + + +static void on_error(void *data, uint32_t id, int seq, int res, const char *message) +{ + struct client *client = data; + + pw_log_warn("%p: error id:%u seq:%d res:%d (%s): %s", client, + id, seq, res, spa_strerror(res), message); + + if (id == PW_ID_CORE) { + client->last_res = res; + if (!client->destroyed) + do_callback(client, shutdown_callback, client->shutdown_arg); + } + pw_thread_loop_signal(client->context.loop, false); +} + +static const struct pw_core_events core_events = { + PW_VERSION_CORE_EVENTS, + .done = on_sync_reply, + .error = on_error, +}; + +static int do_sync(struct client *client) +{ + bool in_data_thread = pw_data_loop_in_thread(client->loop); + + if (pw_thread_loop_in_thread(client->context.loop)) { + pw_log_warn("sync requested from callback"); + return 0; + } + if (client->last_res == -EPIPE) + return -EPIPE; + + client->last_res = 0; + client->pending_sync = pw_proxy_sync((struct pw_proxy*)client->core, client->pending_sync); + + while (true) { + if (in_data_thread) { + if (client->rt_locked) + pthread_mutex_unlock(&client->rt_lock); + client->data_locked = true; + } + pw_thread_loop_wait(client->context.loop); + + if (in_data_thread) { + client->data_locked = false; + if (client->rt_locked) + pthread_mutex_lock(&client->rt_lock); + } + + if (client->last_res < 0) + return client->last_res; + + if (client->pending_sync == client->last_sync) + break; + } + return 0; +} + +static void on_node_removed(void *data) +{ + struct client *client = data; + pw_proxy_destroy((struct pw_proxy*)client->node); +} + +static void on_node_destroy(void *data) +{ + struct client *client = data; + client->node = NULL; + spa_hook_remove(&client->proxy_listener); + spa_hook_remove(&client->node_listener); +} + +static void on_node_bound(void *data, uint32_t global_id) +{ + struct client *client = data; + client->node_id = global_id; +} + +static const struct pw_proxy_events node_proxy_events = { + PW_VERSION_PROXY_EVENTS, + .removed = on_node_removed, + .destroy = on_node_destroy, + .bound = on_node_bound, +}; + +static struct link *find_activation(struct spa_list *links, uint32_t node_id) +{ + struct link *l; + + spa_list_for_each(l, links, link) { + if (l->node_id == node_id) + return l; + } + return NULL; +} + +static void client_remove_source(struct client *c) +{ + if (c->socket_source) { + pw_loop_destroy_source(c->loop->loop, c->socket_source); + c->socket_source = 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; + client_remove_source(c); + return 0; +} + +static inline 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_fp("%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 size_t 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); + return b.state.offset; +} + +static inline void fix_midi_event(uint8_t *data, size_t size) +{ + /* fixup NoteOn with vel 0 */ + if (size > 2 && (data[0] & 0xF0) == 0x90 && data[2] == 0x00) { + data[0] = 0x80 + (data[0] & 0x0F); + data[2] = 0x40; + } +} + +static inline int event_sort(struct spa_pod_control *a, struct spa_pod_control *b) +{ + if (a->offset < b->offset) + return -1; + if (a->offset > b->offset) + return 1; + if (a->type != b->type) + return 0; + switch(a->type) { + case SPA_CONTROL_Midi: + { + /* 11 (controller) > 12 (program change) > + * 8 (note off) > 9 (note on) > 10 (aftertouch) > + * 13 (channel pressure) > 14 (pitch bend) */ + static int priotab[] = { 5,4,3,7,6,2,1,0 }; + uint8_t *da, *db; + + if (SPA_POD_BODY_SIZE(&a->value) < 1 || + SPA_POD_BODY_SIZE(&b->value) < 1) + return 0; + + da = SPA_POD_BODY(&a->value); + db = SPA_POD_BODY(&b->value); + if ((da[0] & 0xf) != (db[0] & 0xf)) + return 0; + return priotab[(db[0]>>4) & 7] - priotab[(da[0]>>4) & 7]; + } + default: + return 0; + } +} + +static void convert_to_midi(struct spa_pod_sequence **seq, uint32_t n_seq, void *midi, bool fix) +{ + struct spa_pod_control *c[n_seq]; + uint32_t i; + int res; + + 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 || event_sort(c[i], next) <= 0) { + next = c[i]; + next_index = i; + } + } + if (SPA_UNLIKELY(next == NULL)) + break; + + switch(next->type) { + case SPA_CONTROL_Midi: + { + uint8_t *data = SPA_POD_BODY(&next->value); + size_t size = SPA_POD_BODY_SIZE(&next->value); + + if (fix) + fix_midi_event(data, size); + + if ((res = jack_midi_event_write(midi, next->offset, data, size)) < 0) + pw_log_warn("midi %p: can't write event: %s", midi, + spa_strerror(res)); + break; + } + } + c[next_index] = spa_pod_control_next(c[next_index]); + } +} + + +static inline void *get_buffer_output(struct port *p, uint32_t frames, uint32_t stride, struct buffer **buf) +{ + struct mix *mix; + struct client *c = p->client; + void *ptr = NULL; + struct buffer *b; + struct spa_data *d; + + if (frames == 0 || !p->valid) + return NULL; + + if (SPA_UNLIKELY((mix = p->global_mix) == NULL)) + return NULL; + + pw_log_trace_fp("%p: port %s %d get buffer %d n_buffers:%d", + c, p->object->port.name, p->port_id, frames, mix->n_buffers); + + if (SPA_UNLIKELY(mix->n_buffers == 0)) + return NULL; + + if (p->io.status == SPA_STATUS_HAVE_DATA && + p->io.buffer_id < mix->n_buffers) { + b = &mix->buffers[p->io.buffer_id]; + d = &b->datas[0]; + } else { + if (p->io.buffer_id < mix->n_buffers) { + reuse_buffer(c, mix, p->io.buffer_id); + p->io.buffer_id = SPA_ID_INVALID; + } + if (SPA_UNLIKELY((b = dequeue_buffer(c, mix)) == NULL)) { + pw_log_warn("port %p: out of buffers", p); + return NULL; + } + d = &b->datas[0]; + d->chunk->offset = 0; + d->chunk->size = frames * sizeof(float); + d->chunk->stride = stride; + + p->io.status = SPA_STATUS_HAVE_DATA; + p->io.buffer_id = b->id; + } + ptr = d->data; + if (buf) + *buf = b; + return ptr; +} + +static inline void process_empty(struct port *p, uint32_t frames) +{ + void *ptr; + + switch (p->object->port.type_id) { + case TYPE_ID_AUDIO: + ptr = get_buffer_output(p, frames, sizeof(float), NULL); + if (SPA_LIKELY(ptr != NULL)) + memcpy(ptr, p->emptyptr, frames * sizeof(float)); + break; + case TYPE_ID_MIDI: + { + struct buffer *b; + ptr = get_buffer_output(p, MAX_BUFFER_FRAMES, 1, &b); + if (SPA_LIKELY(ptr != NULL)) { + b->datas[0].chunk->size = convert_from_midi(p->emptyptr, + ptr, MAX_BUFFER_FRAMES * sizeof(float)); + } + break; + } + default: + pw_log_warn("port %p: unhandled format %d", p, p->object->port.type_id); + break; + } +} + +static void prepare_output(struct port *p, uint32_t frames) +{ + struct mix *mix; + + if (SPA_UNLIKELY(p->empty_out)) + process_empty(p, frames); + + spa_list_for_each(mix, &p->mix, port_link) { + if (SPA_LIKELY(mix->io != NULL)) + *mix->io = p->io; + } +} + +static void complete_process(struct client *c, uint32_t frames) +{ + struct port *p; + struct mix *mix; + union pw_map_item *item; + + pw_array_for_each(item, &c->ports[SPA_DIRECTION_INPUT].items) { + if (pw_map_item_is_free(item)) + continue; + p = item->data; + if (!p->valid) + continue; + spa_list_for_each(mix, &p->mix, port_link) { + if (SPA_LIKELY(mix->io != NULL)) + mix->io->status = SPA_STATUS_NEED_DATA; + } + } + pw_array_for_each(item, &c->ports[SPA_DIRECTION_OUTPUT].items) { + if (pw_map_item_is_free(item)) + continue; + p = item->data; + if (!p->valid) + continue; + prepare_output(p, frames); + p->io.status = SPA_STATUS_NEED_DATA; + } +} + +static inline void debug_position(struct client *c, jack_position_t *p) +{ + pw_log_trace("usecs: %"PRIu64, 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 (SPA_UNLIKELY(d == NULL)) + return state; + + d->unique_1++; + d->usecs = s->clock.nsec / SPA_NSEC_PER_USEC; + d->frame_rate = s->clock.rate.denom; + + if ((int64_t)s->clock.position < s->offset) { + d->frame = seg->position; + } else { + 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 void recompute_latencies(struct client *c) +{ + do_callback(c, latency_callback, JackCaptureLatency, c->latency_arg); + do_callback(c, latency_callback, JackPlaybackLatency, c->latency_arg); +} + +static int +do_buffer_frames(struct spa_loop *loop, + bool async, uint32_t seq, const void *data, size_t size, void *user_data) +{ + uint32_t buffer_frames = *((uint32_t*)data); + struct client *c = user_data; + if (c->buffer_frames != buffer_frames) + do_callback_expr(c, c->buffer_frames = buffer_frames, bufsize_callback, buffer_frames, c->bufsize_arg); + recompute_latencies(c); + return 0; +} + +static inline int check_buffer_frames(struct client *c, struct spa_io_position *pos) +{ + uint32_t buffer_frames = pos->clock.duration; + if (SPA_UNLIKELY(buffer_frames != c->buffer_frames)) { + pw_log_info("%p: bufferframes old:%d new:%d cb:%p", c, + c->buffer_frames, buffer_frames, c->bufsize_callback); + if (c->buffer_frames != (uint32_t)-1) + pw_loop_invoke(c->context.l, do_buffer_frames, 0, + &buffer_frames, sizeof(buffer_frames), false, c); + else + c->buffer_frames = buffer_frames; + } + return c->buffer_frames == buffer_frames; +} + +static int +do_sample_rate(struct spa_loop *loop, + bool async, uint32_t seq, const void *data, size_t size, void *user_data) +{ + struct client *c = user_data; + uint32_t sample_rate = *((uint32_t*)data); + do_callback_expr(c, c->sample_rate = sample_rate, srate_callback, sample_rate, c->srate_arg); + return 0; +} + +static inline int check_sample_rate(struct client *c, struct spa_io_position *pos) +{ + uint32_t sample_rate = pos->clock.rate.denom; + if (SPA_UNLIKELY(sample_rate != c->sample_rate)) { + pw_log_info("%p: sample_rate old:%d new:%d cb:%p", c, + c->sample_rate, sample_rate, c->srate_callback); + if (c->srate_callback != NULL) { + pw_loop_invoke(c->context.l, do_sample_rate, 0, + &sample_rate, sizeof(sample_rate), false, c); + } else { + c->sample_rate = sample_rate; + } + } + return c->sample_rate == sample_rate; +} + +static inline uint32_t cycle_run(struct client *c) +{ + uint64_t cmd; + struct timespec ts; + int fd = c->socket_source->fd; + struct spa_io_position *pos = c->rt.position; + struct pw_node_activation *activation = c->activation; + struct pw_node_activation *driver = c->rt.driver_activation; + + while (true) { + if (SPA_UNLIKELY(read(fd, &cmd, sizeof(cmd)) != sizeof(cmd))) { + if (errno == EINTR) + continue; + if (errno == EWOULDBLOCK || errno == EAGAIN) + return 0; + pw_log_warn("%p: read failed %m", c); + } + break; + } + if (SPA_UNLIKELY(cmd > 1)) + pw_log_info("%p: missed %"PRIu64" wakeups", c, cmd - 1); + + clock_gettime(CLOCK_MONOTONIC, &ts); + activation->status = PW_NODE_ACTIVATION_AWAKE; + activation->awake_time = SPA_TIMESPEC_TO_NSEC(&ts); + + if (SPA_UNLIKELY(c->first)) { + if (c->thread_init_callback) + c->thread_init_callback(c->thread_init_arg); + c->first = false; + } + + if (SPA_UNLIKELY(pos == NULL)) { + pw_log_error("%p: missing position", c); + return 0; + } + + if (check_buffer_frames(c, pos) == 0) + return 0; + if (check_sample_rate(c, pos) == 0) + return 0; + + if (SPA_LIKELY(driver)) { + c->jack_state = position_to_jack(driver, &c->jack_position); + + if (SPA_UNLIKELY(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 (SPA_UNLIKELY(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_fp("%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 c->buffer_frames; +} + +static inline uint32_t cycle_wait(struct client *c) +{ + int res; + uint32_t nframes; + + do { + res = pw_data_loop_wait(c->loop, -1); + if (SPA_UNLIKELY(res <= 0)) { + pw_log_warn("%p: wait error %m", c); + return 0; + } + nframes = cycle_run(c); + } while (!nframes); + + return nframes; +} + +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; + + complete_process(c, c->buffer_frames); + + clock_gettime(CLOCK_MONOTONIC, &ts); + nsec = SPA_TIMESPEC_TO_NSEC(&ts); + activation->status = PW_NODE_ACTIVATION_FINISHED; + activation->finish_time = nsec; + + cmd = 1; + spa_list_for_each(l, &c->rt.target_links, target_link) { + struct pw_node_activation_state *state; + + if (SPA_UNLIKELY(l->activation == NULL)) + continue; + + state = &l->activation->state[0]; + + pw_log_trace_fp("%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_fp("%p: signal %p %p", c, l, state); + + if (SPA_UNLIKELY(write(l->signalfd, &cmd, sizeof(cmd)) != sizeof(cmd))) + pw_log_warn("%p: write failed %m", c); + } + } +} + +static inline void cycle_signal(struct client *c, int status) +{ + struct pw_node_activation *driver = c->rt.driver_activation; + struct pw_node_activation *activation = c->activation; + + if (SPA_LIKELY(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 (SPA_UNLIKELY(mask & (SPA_IO_ERR | SPA_IO_HUP))) { + pw_log_warn("%p: got error", c); + client_remove_source(c); + return; + } + if (SPA_UNLIKELY(c->thread_callback)) { + if (!c->thread_entered) { + c->thread_entered = true; + c->thread_callback(c->thread_arg); + } + } else if (SPA_LIKELY(mask & SPA_IO_IN)) { + uint32_t buffer_frames; + int status = 0; + + buffer_frames = cycle_run(c); + + if (buffer_frames > 0) + status = do_rt_callback_res(c, process_callback, buffer_frames, c->process_arg); + + cycle_signal(c, status); + } +} + +static int +do_clear_link(struct spa_loop *loop, + bool async, uint32_t seq, const void *data, size_t size, void *user_data) +{ + struct link *link = user_data; + spa_list_remove(&link->target_link); + return 0; +} + +static void clear_link(struct client *c, struct link *link) +{ + pw_data_loop_invoke(c->loop, + do_clear_link, 1, NULL, 0, !c->data_locked, link); + pw_memmap_free(link->mem); + close(link->signalfd); + spa_list_remove(&link->link); + free(link); +} + +static void clean_transport(struct client *c) +{ + struct link *l; + + if (!c->has_transport) + return; + + pw_data_loop_invoke(c->loop, + do_remove_sources, 1, NULL, 0, !c->data_locked, c); + + spa_list_consume(l, &c->links, link) + clear_link(c, l); + + c->has_transport = false; +} + +static int client_node_transport(void *data, + int readfd, int writefd, + uint32_t mem_id, uint32_t offset, uint32_t size) +{ + struct client *c = (struct client *) data; + + clean_transport(c); + + c->mem = pw_mempool_map_id(c->pool, mem_id, + PW_MEMMAP_FLAG_READWRITE, offset, size, NULL); + if (c->mem == NULL) { + pw_log_debug("%p: can't map activation: %m", c); + return -errno; + } + c->activation = c->mem->ptr; + + pw_log_debug("%p: create client transport with fds %d %d for node %u", + c, readfd, writefd, c->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); + + c->has_transport = true; + c->position = &c->activation->position; + pw_thread_loop_signal(c->context.loop, false); + + return 0; +} + +static int client_node_set_param(void *data, + uint32_t id, uint32_t flags, + const struct spa_pod *param) +{ + struct client *c = (struct client *) data; + pw_proxy_error((struct pw_proxy*)c->node, -ENOTSUP, "not supported"); + return -ENOTSUP; +} + +static int install_timeowner(struct client *c) +{ + struct pw_node_activation *a; + uint32_t owner; + + if (!c->timebase_callback) + return 0; + + if ((a = c->driver_activation) == NULL) + return -EIO; + + pw_log_debug("%p: activation %p", c, a); + + /* was ok */ + owner = ATOMIC_LOAD(a->segment_owner[0]); + if (owner == c->node_id) + return 0; + + /* try to become owner */ + if (c->timeowner_conditional) { + if (!ATOMIC_CAS(a->segment_owner[0], 0, c->node_id)) { + pw_log_debug("%p: owner:%u id:%u", c, owner, c->node_id); + return -EBUSY; + } + } else { + ATOMIC_STORE(a->segment_owner[0], c->node_id); + } + + pw_log_debug("%p: timebase installed for id:%u", c, c->node_id); + + return 0; +} + +static int +do_update_driver_activation(struct spa_loop *loop, + bool async, uint32_t seq, const void *data, size_t size, void *user_data) +{ + struct client *c = user_data; + c->rt.position = c->position; + c->rt.driver_activation = c->driver_activation; + if (c->position) { + pw_log_info("%p: driver:%d clock:%s", c, + c->driver_id, c->position->clock.name); + check_sample_rate(c, c->position); + check_buffer_frames(c, c->position); + } + return 0; +} + +static int update_driver_activation(struct client *c) +{ + jack_client_t *client = (jack_client_t*)c; + struct link *link; + bool freewheeling; + + pw_log_debug("%p: driver %d", c, c->driver_id); + + freewheeling = SPA_FLAG_IS_SET(c->position->clock.flags, SPA_IO_CLOCK_FLAG_FREEWHEEL); + if (c->freewheeling != freewheeling) { + jack_native_thread_t thr = jack_client_thread_id(client); + + c->freewheeling = freewheeling; + if (freewheeling && thr) { + jack_drop_real_time_scheduling(thr); + } + + do_callback(c, freewheel_callback, freewheeling, c->freewheel_arg); + + if (!freewheeling && thr) { + jack_acquire_real_time_scheduling(thr, + jack_client_real_time_priority(client)); + } + } + + link = find_activation(&c->links, c->driver_id); + c->driver_activation = link ? link->activation : NULL; + pw_data_loop_invoke(c->loop, + do_update_driver_activation, SPA_ID_INVALID, NULL, 0, false, c); + install_timeowner(c); + + return 0; +} + +static int client_node_set_io(void *data, + uint32_t id, + uint32_t mem_id, + uint32_t offset, + uint32_t size) +{ + struct client *c = (struct client *) data; + struct pw_memmap *old, *mm; + void *ptr; + uint32_t tag[5] = { c->node_id, id, }; + + old = pw_mempool_find_tag(c->pool, tag, sizeof(tag)); + + if (mem_id == SPA_ID_INVALID) { + mm = ptr = NULL; + } else { + mm = pw_mempool_map_id(c->pool, mem_id, + PW_MEMMAP_FLAG_READWRITE, offset, size, tag); + if (mm == NULL) { + pw_log_warn("%p: can't map memory id %u", c, mem_id); + return -errno; + } + ptr = mm->ptr; + } + pw_log_debug("%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; + } + pw_memmap_free(old); + + return 0; +} + +static int client_node_event(void *data, const struct spa_event *event) +{ + return -ENOTSUP; +} + +static int client_node_command(void *data, const struct spa_command *command) +{ + struct client *c = (struct client *) data; + + pw_log_debug("%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("%p: unhandled node command %d", c, SPA_COMMAND_TYPE(command)); + pw_proxy_errorf((struct pw_proxy*)c->node, -ENOTSUP, + "unhandled command %d", SPA_COMMAND_TYPE(command)); + } + return 0; +} + +static int client_node_add_port(void *data, + enum spa_direction direction, + uint32_t port_id, const struct spa_dict *props) +{ + struct client *c = (struct client *) data; + pw_proxy_error((struct pw_proxy*)c->node, -ENOTSUP, "add port not supported"); + return -ENOTSUP; +} + +static int client_node_remove_port(void *data, + enum spa_direction direction, + uint32_t port_id) +{ + struct client *c = (struct client *) data; + pw_proxy_error((struct pw_proxy*)c->node, -ENOTSUP, "remove port not supported"); + return -ENOTSUP; +} + +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 TYPE_ID_AUDIO: + *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_dsp), + SPA_FORMAT_AUDIO_format, SPA_POD_Id(SPA_AUDIO_FORMAT_DSP_F32)); + break; + case TYPE_ID_MIDI: + *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 TYPE_ID_VIDEO: + *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_dsp), + SPA_FORMAT_VIDEO_format, SPA_POD_Id(SPA_VIDEO_FORMAT_DSP_F32)); + 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) +{ + switch (p->object->port.type_id) { + case TYPE_ID_AUDIO: + *param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_Format, SPA_PARAM_Format, + SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), + SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_dsp), + SPA_FORMAT_AUDIO_format, SPA_POD_Id(SPA_AUDIO_FORMAT_DSP_F32)); + break; + case TYPE_ID_MIDI: + *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 TYPE_ID_VIDEO: + *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_dsp), + SPA_FORMAT_VIDEO_format, SPA_POD_Id(SPA_VIDEO_FORMAT_DSP_F32)); + 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 TYPE_ID_AUDIO: + case TYPE_ID_MIDI: + *param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, + SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(2, 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), + INT32_MAX, + sizeof(float)), + SPA_PARAM_BUFFERS_stride, SPA_POD_Int(p->object->port.type_id == TYPE_ID_AUDIO ? + sizeof(float) : 1)); + break; + case TYPE_ID_VIDEO: + *param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, + SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(2, 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)); + 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 param_latency(struct client *c, struct port *p, + struct spa_pod **param, struct spa_pod_builder *b) +{ + *param = spa_latency_build(b, SPA_PARAM_Latency, + &p->object->port.latency[p->direction]); + return 1; +} + +static int param_latency_other(struct client *c, struct port *p, + struct spa_pod **param, struct spa_pod_builder *b) +{ + *param = spa_latency_build(b, SPA_PARAM_Latency, + &p->object->port.latency[SPA_DIRECTION_REVERSE(p->direction)]); + return 1; +} + +/* called from thread-loop */ +static int port_set_format(struct client *c, struct port *p, + uint32_t flags, const struct spa_pod *param) +{ + struct spa_pod *params[6]; + uint8_t buffer[4096]; + struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); + + if (param == NULL) { + struct mix *mix; + + pw_log_debug("%p: port %p clear format", c, p); + + spa_list_for_each(mix, &p->mix, port_link) + clear_buffers(c, mix); + + p->params[IDX_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); + } + else { + struct spa_audio_info info = { 0 }; + if (spa_format_parse(param, &info.media_type, &info.media_subtype) < 0) + return -EINVAL; + + switch (info.media_type) { + case SPA_MEDIA_TYPE_audio: + { + if (info.media_subtype != SPA_MEDIA_SUBTYPE_dsp) + return -EINVAL; + + if (spa_format_audio_dsp_parse(param, &info.info.dsp) < 0) + return -EINVAL; + if (info.info.dsp.format != SPA_AUDIO_FORMAT_DSP_F32) + return -EINVAL; + 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_dsp) + return -EINVAL; + if (spa_format_video_dsp_parse(param, &vinfo.info.dsp) < 0) + return -EINVAL; + if (vinfo.info.dsp.format != SPA_VIDEO_FORMAT_DSP_F32) + return -EINVAL; + break; + } + default: + return -EINVAL; + } + p->params[IDX_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READWRITE); + } + + pw_log_info("port %s: update", p->object->port.name); + + p->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; + + 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); + param_latency(c, p, ¶ms[4], &b); + param_latency_other(c, p, ¶ms[5], &b); + + pw_client_node_port_update(c->node, + p->direction, + p->port_id, + PW_CLIENT_NODE_PORT_UPDATE_PARAMS | + PW_CLIENT_NODE_PORT_UPDATE_INFO, + SPA_N_ELEMENTS(params), + (const struct spa_pod **) params, + &p->info); + p->info.change_mask = 0; + return 0; +} + +/* called from thread-loop */ +static void port_update_latency(struct port *p) +{ + struct client *c = p->client; + struct spa_pod *params[6]; + uint8_t buffer[4096]; + struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); + + 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); + param_latency(c, p, ¶ms[4], &b); + param_latency_other(c, p, ¶ms[5], &b); + + pw_log_info("port %s: update", p->object->port.name); + + p->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; + p->params[IDX_Latency].flags ^= SPA_PARAM_INFO_SERIAL; + + pw_client_node_port_update(c->node, + p->direction, + p->port_id, + PW_CLIENT_NODE_PORT_UPDATE_PARAMS | + PW_CLIENT_NODE_PORT_UPDATE_INFO, + SPA_N_ELEMENTS(params), + (const struct spa_pod **) params, + &p->info); + p->info.change_mask = 0; +} + +/* called from thread-loop */ +static void default_latency(struct client *c, enum spa_direction direction, + struct spa_latency_info *latency) +{ + enum spa_direction other; + union pw_map_item *item; + struct port *p; + + other = SPA_DIRECTION_REVERSE(direction); + + spa_latency_info_combine_start(latency, direction); + + pw_array_for_each(item, &c->ports[other].items) { + if (pw_map_item_is_free(item)) + continue; + p = item->data; + spa_latency_info_combine(latency, &p->object->port.latency[direction]); + } + + spa_latency_info_combine_finish(latency); +} + +/* called from thread-loop */ +static void default_latency_callback(jack_latency_callback_mode_t mode, struct client *c) +{ + struct spa_latency_info latency, *current; + enum spa_direction direction; + union pw_map_item *item; + struct port *p; + + if (mode == JackPlaybackLatency) + direction = SPA_DIRECTION_INPUT; + else + direction = SPA_DIRECTION_OUTPUT; + + default_latency(c, direction, &latency); + + pw_log_info("client %p: update %s latency %f-%f %d-%d %"PRIu64"-%"PRIu64, c, + latency.direction == SPA_DIRECTION_INPUT ? "playback" : "capture", + latency.min_quantum, latency.max_quantum, + latency.min_rate, latency.max_rate, + latency.min_ns, latency.max_ns); + + pw_array_for_each(item, &c->ports[direction].items) { + if (pw_map_item_is_free(item)) + continue; + p = item->data; + current = &p->object->port.latency[direction]; + if (spa_latency_info_compare(current, &latency) == 0) + continue; + *current = latency; + port_update_latency(p); + } +} + +/* called from thread-loop */ +static int port_set_latency(struct client *c, struct port *p, + uint32_t flags, const struct spa_pod *param) +{ + struct spa_latency_info info; + jack_latency_callback_mode_t mode; + struct spa_latency_info *current; + int res; + + if (param == NULL) + return 0; + + if ((res = spa_latency_parse(param, &info)) < 0) + return res; + + current = &p->object->port.latency[info.direction]; + if (spa_latency_info_compare(current, &info) == 0) + return 0; + + *current = info; + + pw_log_info("port %s: set %s latency %f-%f %d-%d %"PRIu64"-%"PRIu64, p->object->port.name, + info.direction == SPA_DIRECTION_INPUT ? "playback" : "capture", + info.min_quantum, info.max_quantum, + info.min_rate, info.max_rate, + info.min_ns, info.max_ns); + + if (info.direction == p->direction) + return 0; + + if (info.direction == SPA_DIRECTION_INPUT) + mode = JackPlaybackLatency; + else + mode = JackCaptureLatency; + + if (c->latency_callback) + do_callback(c, latency_callback, mode, c->latency_arg); + else + default_latency_callback(mode, c); + + port_update_latency(p); + + return 0; +} + +/* called from thread-loop */ +static int client_node_port_set_param(void *data, + enum spa_direction direction, + uint32_t port_id, + uint32_t id, uint32_t flags, + const struct spa_pod *param) +{ + struct client *c = (struct client *) data; + struct port *p = GET_PORT(c, direction, port_id); + + if (p == NULL || !p->valid) + return -EINVAL; + + pw_log_info("client %p: port %s %d.%d id:%d (%s) %p", c, p->object->port.name, + direction, port_id, id, + spa_debug_type_find_name(spa_type_param, id), param); + + switch (id) { + case SPA_PARAM_Format: + return port_set_format(c, p, flags, param); + break; + case SPA_PARAM_Latency: + return port_set_latency(c, p, flags, param); + default: + break; + } + return 0; +} + +static inline void *init_buffer(struct port *p) +{ + void *data = p->emptyptr; + if (p->zeroed) + return data; + + if (p->object->port.type_id == TYPE_ID_MIDI) { + struct midi_buffer *mb = data; + mb->magic = MIDI_BUFFER_MAGIC; + mb->buffer_size = MAX_BUFFER_FRAMES * sizeof(float); + mb->nframes = MAX_BUFFER_FRAMES; + mb->write_pos = 0; + mb->event_count = 0; + mb->lost_events = 0; + pw_log_debug("port %p: init midi buffer size:%d", p, mb->buffer_size); + } else + memset(data, 0, MAX_BUFFER_FRAMES * sizeof(float)); + + p->zeroed = true; + return data; +} + +static int client_node_port_use_buffers(void *data, + 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 *) data; + struct port *p = GET_PORT(c, direction, port_id); + struct buffer *b; + uint32_t i, j, fl; + int res; + struct mix *mix; + + if (p == NULL || !p->valid) { + res = -EINVAL; + goto done; + } + if ((mix = ensure_mix(c, p, mix_id)) == NULL) { + res = -ENOMEM; + goto done; + } + + pw_log_debug("%p: port %p %d %d.%d use_buffers %d", c, p, direction, + port_id, mix_id, n_buffers); + + if (n_buffers > MAX_BUFFERS) { + pw_log_error("%p: too many buffers %u > %u", c, n_buffers, MAX_BUFFERS); + return -ENOSPC; + } + + if (p->object->port.type_id == TYPE_ID_VIDEO && 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->pool, buffers[i].mem_id, + fl, buffers[i].offset, buffers[i].size, NULL); + if (mm == NULL) { + pw_log_warn("%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("%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_PTROFF(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->pool, mem_id); + if (bm == NULL) { + pw_log_error("%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("%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("%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_PTROFF(mm->ptr, offs, void); + d->fd = -1; + pw_log_debug("%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 (c->allow_mlock && mlock(d->data, d->maxsize) < 0) { + if (errno != ENOMEM || !mlock_warned) { + pw_log(c->warn_mlock ? SPA_LOG_LEVEL_WARN : SPA_LOG_LEVEL_DEBUG, + "%p: Failed to mlock memory %p %u: %s", c, + d->data, d->maxsize, + errno == ENOMEM ? + "This is not a problem but for best performance, " + "consider increasing RLIMIT_MEMLOCK" : strerror(errno)); + mlock_warned |= errno == ENOMEM; + } + } + } + SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUT); + if (direction == SPA_DIRECTION_OUTPUT) + reuse_buffer(c, mix, b->id); + + } + pw_log_debug("%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, res, spa_strerror(res)); + return res; +} + +static int client_node_port_set_io(void *data, + 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 *) data; + struct port *p = GET_PORT(c, direction, port_id); + struct pw_memmap *mm, *old; + struct mix *mix; + uint32_t tag[5] = { c->node_id, direction, port_id, mix_id, id }; + void *ptr; + int res = 0; + + if (p == NULL || !p->valid) { + res = -EINVAL; + goto exit; + } + + if ((mix = ensure_mix(c, p, mix_id)) == NULL) { + res = -ENOMEM; + goto exit; + } + + old = pw_mempool_find_tag(c->pool, tag, sizeof(tag)); + + if (mem_id == SPA_ID_INVALID) { + mm = ptr = NULL; + } + else { + mm = pw_mempool_map_id(c->pool, mem_id, + PW_MEMMAP_FLAG_READWRITE, offset, size, tag); + if (mm == NULL) { + pw_log_warn("%p: can't map memory id %u", c, mem_id); + res = -EINVAL; + goto exit_free; + } + ptr = mm->ptr; + } + + pw_log_debug("%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_free: + pw_memmap_free(old); +exit: + if (res < 0) + pw_proxy_error((struct pw_proxy*)c->node, res, spa_strerror(res)); + return res; +} + +static int +do_activate_link(struct spa_loop *loop, + bool async, uint32_t seq, const void *data, size_t size, void *user_data) +{ + struct link *link = user_data; + struct client *c = link->client; + pw_log_trace("link %p activate", link); + spa_list_append(&c->rt.target_links, &link->target_link); + return 0; +} + +static int client_node_set_activation(void *data, + uint32_t node_id, + int signalfd, + uint32_t mem_id, + uint32_t offset, + uint32_t size) +{ + struct client *c = (struct client *) data; + struct pw_memmap *mm; + struct link *link; + void *ptr; + int res = 0; + + if (c->node_id == node_id) { + pw_log_debug("%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->pool, mem_id, + PW_MEMMAP_FLAG_READWRITE, offset, size, NULL); + if (mm == NULL) { + pw_log_warn("%p: can't map memory id %u", c, mem_id); + res = -EINVAL; + goto exit; + } + ptr = mm->ptr; + } + + pw_log_debug("%p: set activation %u: %u %u %u %p", c, node_id, + mem_id, offset, size, ptr); + + if (ptr) { + link = calloc(1, sizeof(struct link)); + if (link == NULL) { + res = -errno; + goto exit; + } + link->client = c; + link->node_id = node_id; + link->mem = mm; + link->activation = ptr; + link->signalfd = signalfd; + spa_list_append(&c->links, &link->link); + + pw_data_loop_invoke(c->loop, + do_activate_link, SPA_ID_INVALID, NULL, 0, false, link); + } + 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, res, spa_strerror(res)); + return res; +} + +static int client_node_port_set_mix_info(void *data, + enum spa_direction direction, + uint32_t port_id, + uint32_t mix_id, + uint32_t peer_id, + const struct spa_dict *props) +{ + struct client *c = (struct client *) data; + struct port *p = GET_PORT(c, direction, port_id); + struct mix *mix; + struct object *l; + uint32_t src, dst; + int res = 0; + + if (p == NULL || !p->valid) { + res = -EINVAL; + goto exit; + } + + if ((mix = ensure_mix(c, p, mix_id)) == NULL) { + res = -ENOMEM; + goto exit; + } + mix->peer_id = peer_id; + + if (direction == SPA_DIRECTION_INPUT) { + src = peer_id; + dst = p->object->id; + } else { + src = p->object->id; + dst = peer_id; + } + + if ((l = find_link(c, src, dst)) != NULL) { + if (direction == SPA_DIRECTION_INPUT) + mix->peer_port = l->port_link.our_output; + else + mix->peer_port = l->port_link.our_input; + + pw_log_debug("peer port %p %p %p", mix->peer_port, + l->port_link.our_output, l->port_link.our_input); + + if (!l->port_link.is_complete) { + l->port_link.is_complete = true; + pw_log_info("%p: our link %u/%u -> %u/%u completed", c, + l->port_link.src, l->port_link.src_serial, + l->port_link.dst, l->port_link.dst_serial); + do_callback(c, connect_callback, + l->port_link.src_serial, l->port_link.dst_serial, 1, c->connect_arg); + recompute_latencies(c); + do_callback(c, graph_callback, c->graph_arg); + } + } + +exit: + if (res < 0) + pw_proxy_error((struct pw_proxy*)c->node, res, spa_strerror(res)); + return res; +} + +static const struct pw_client_node_events client_node_events = { + PW_VERSION_CLIENT_NODE_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, + .port_set_mix_info = client_node_port_set_mix_info, +}; + +#define CHECK(expression,label) \ +do { \ + if ((errno = expression) != 0) { \ + res = -errno; \ + pw_log_error(#expression ": %s", strerror(errno)); \ + goto label; \ + } \ +} while(false); + +static struct spa_thread *impl_create(void *object, + const struct spa_dict *props, + void *(*start)(void*), void *arg) +{ + struct client *c = (struct client *) object; + struct spa_thread *thr; + int res = 0; + + pw_log_info("create thread"); + if (globals.creator != NULL) { + pthread_t pt; + pthread_attr_t *attr = NULL, attributes; + + attr = pw_thread_fill_attr(props, &attributes); + + res = -globals.creator(&pt, attr, start, arg); + if (attr) + pthread_attr_destroy(attr); + if (res != 0) + goto error; + thr = (struct spa_thread*)pt; + } else { + thr = spa_thread_utils_create(c->context.old_thread_utils, props, start, arg); + } + return thr; +error: + pw_log_warn("create RT thread failed: %s", strerror(res)); + errno = -res; + return NULL; + +} + +static int impl_join(void *object, + struct spa_thread *thread, void **retval) +{ + struct client *c = (struct client *) object; + pw_log_info("join thread"); + return spa_thread_utils_join(c->context.old_thread_utils, thread, retval); +} + +static int impl_acquire_rt(void *object, struct spa_thread *thread, int priority) +{ + struct client *c = (struct client *) object; + return spa_thread_utils_acquire_rt(c->context.old_thread_utils, thread, priority); +} + +static int impl_drop_rt(void *object, struct spa_thread *thread) +{ + struct client *c = (struct client *) object; + return spa_thread_utils_drop_rt(c->context.old_thread_utils, thread); +} + +static struct spa_thread_utils_methods thread_utils_impl = { + SPA_VERSION_THREAD_UTILS_METHODS, + .create = impl_create, + .join = impl_join, + .acquire_rt = impl_acquire_rt, + .drop_rt = impl_drop_rt, +}; + +static jack_port_type_id_t string_to_type(const char *port_type) +{ + if (spa_streq(JACK_DEFAULT_AUDIO_TYPE, port_type)) + return TYPE_ID_AUDIO; + else if (spa_streq(JACK_DEFAULT_MIDI_TYPE, port_type)) + return TYPE_ID_MIDI; + else if (spa_streq(JACK_DEFAULT_VIDEO_TYPE, port_type)) + return TYPE_ID_VIDEO; + else if (spa_streq("other", port_type)) + return TYPE_ID_OTHER; + else + return SPA_ID_INVALID; +} + +static const char* type_to_string(jack_port_type_id_t type_id) +{ + switch(type_id) { + case TYPE_ID_AUDIO: + return JACK_DEFAULT_AUDIO_TYPE; + case TYPE_ID_MIDI: + return JACK_DEFAULT_MIDI_TYPE; + case TYPE_ID_VIDEO: + return JACK_DEFAULT_VIDEO_TYPE; + case TYPE_ID_OTHER: + return "other"; + default: + return NULL; + } +} + +static jack_uuid_t client_make_uuid(uint32_t id, bool monitor) +{ + jack_uuid_t uuid = 0x2; /* JackUUIDClient */ + uuid = (uuid << 32) | (id + 1); + if (monitor) + uuid |= (1 << 30); + pw_log_debug("uuid %d -> %"PRIu64, id, uuid); + return uuid; +} + +static int json_object_find(const char *obj, const char *key, char *value, size_t len) +{ + struct spa_json it[2]; + const char *v; + char k[128]; + + spa_json_init(&it[0], obj, strlen(obj)); + if (spa_json_enter_object(&it[0], &it[1]) <= 0) + return -EINVAL; + + while (spa_json_get_string(&it[1], k, sizeof(k)) > 0) { + if (spa_streq(k, key)) { + if (spa_json_get_string(&it[1], value, len) <= 0) + continue; + return 0; + } else { + if (spa_json_next(&it[1], &v) <= 0) + break; + } + } + return -ENOENT; +} + +static int metadata_property(void *data, uint32_t id, + const char *key, const char *type, const char *value) +{ + struct client *c = (struct client *) data; + struct object *o; + jack_uuid_t uuid; + + pw_log_debug("set id:%u key:'%s' value:'%s' type:'%s'", id, key, value, type); + + if (id == PW_ID_CORE) { + if (key == NULL || spa_streq(key, "default.audio.sink")) { + if (value != NULL) { + if (json_object_find(value, "name", + c->metadata->default_audio_sink, + sizeof(c->metadata->default_audio_sink)) < 0) + value = NULL; + } + if (value == NULL) + c->metadata->default_audio_sink[0] = '\0'; + } + if (key == NULL || spa_streq(key, "default.audio.source")) { + if (value != NULL) { + if (json_object_find(value, "name", + c->metadata->default_audio_source, + sizeof(c->metadata->default_audio_source)) < 0) + value = NULL; + } + if (value == NULL) + c->metadata->default_audio_source[0] = '\0'; + } + } else { + if ((o = find_id(c, id, true)) == NULL) + return -EINVAL; + + switch (o->type) { + case INTERFACE_Node: + uuid = client_make_uuid(o->serial, false); + break; + case INTERFACE_Port: + uuid = jack_port_uuid_generate(o->serial); + break; + default: + return -EINVAL; + } + update_property(c, uuid, key, type, value); + } + + return 0; +} + +static const struct pw_metadata_events metadata_events = { + PW_VERSION_METADATA_EVENTS, + .property = metadata_property +}; + +static void metadata_proxy_removed(void *data) +{ + struct client *c = data; + pw_proxy_destroy((struct pw_proxy*)c->metadata->proxy); +} + +static void metadata_proxy_destroy(void *data) +{ + struct client *c = data; + spa_hook_remove(&c->metadata->proxy_listener); + spa_hook_remove(&c->metadata->listener); + c->metadata = NULL; +} + +static const struct pw_proxy_events metadata_proxy_events = { + PW_VERSION_PROXY_EVENTS, + .removed = metadata_proxy_removed, + .destroy = metadata_proxy_destroy, +}; + +static void settings_proxy_removed(void *data) +{ + struct client *c = data; + pw_proxy_destroy((struct pw_proxy*)c->settings->proxy); +} + +static void settings_proxy_destroy(void *data) +{ + struct client *c = data; + spa_hook_remove(&c->settings->proxy_listener); + c->settings = NULL; +} + +static const struct pw_proxy_events settings_proxy_events = { + PW_VERSION_PROXY_EVENTS, + .removed = settings_proxy_removed, + .destroy = settings_proxy_destroy, +}; +static void proxy_removed(void *data) +{ + struct object *o = data; + pw_proxy_destroy(o->proxy); +} + +static void proxy_destroy(void *data) +{ + struct object *o = data; + spa_hook_remove(&o->proxy_listener); + spa_hook_remove(&o->object_listener); + o->proxy = NULL; +} + +static const struct pw_proxy_events proxy_events = { + PW_VERSION_PROXY_EVENTS, + .removed = proxy_removed, + .destroy = proxy_destroy, +}; + +static void port_param(void *data, int seq, + uint32_t id, uint32_t index, uint32_t next, + const struct spa_pod *param) +{ + struct object *o = data; + + switch (id) { + case SPA_PARAM_Latency: + { + struct spa_latency_info info; + if (spa_latency_parse(param, &info) < 0) + return; + o->port.latency[info.direction] = info; + break; + } + default: + break; + } +} + +static const struct pw_port_events port_events = { + PW_VERSION_PORT, + .param = port_param, +}; + +#define FILTER_NAME " ()[].:*$" +#define FILTER_PORT " ()[].*$" + +static void filter_name(char *str, const char *filter, char filter_char) +{ + char *p; + for (p = str; *p; p++) { + if (strchr(filter, *p) != NULL) + *p = filter_char; + } +} + +static void registry_event_global(void *data, uint32_t id, + uint32_t permissions, const char *type, uint32_t version, + const struct spa_dict *props) +{ + struct client *c = (struct client *) data; + struct object *o, *ot, *op; + const char *str; + bool is_first = false, graph_changed = false; + uint32_t serial; + + if (props == NULL) + return; + + str = spa_dict_lookup(props, PW_KEY_OBJECT_SERIAL); + if (!spa_atou32(str, &serial, 0)) + serial = SPA_ID_INVALID; + + pw_log_debug("new %s id:%u serial:%u", type, id, serial); + + if (spa_streq(type, PW_TYPE_INTERFACE_Node)) { + const char *app, *node_name; + char tmp[JACK_CLIENT_NAME_SIZE+1]; + + o = alloc_object(c, INTERFACE_Node); + + if ((str = spa_dict_lookup(props, PW_KEY_CLIENT_ID)) != NULL) + o->node.client_id = atoi(str); + + node_name = spa_dict_lookup(props, PW_KEY_NODE_NAME); + + if (id == c->node_id) { + pw_log_debug("%p: add our node %d", c, id); + if (node_name != NULL) + snprintf(c->name, sizeof(c->name), "%s", node_name); + c->serial = serial; + } + snprintf(o->node.node_name, sizeof(o->node.node_name), + "%s", node_name); + + app = spa_dict_lookup(props, PW_KEY_APP_NAME); + + if (c->short_name) { + str = spa_dict_lookup(props, PW_KEY_NODE_NICK); + if (str == NULL) + str = spa_dict_lookup(props, PW_KEY_NODE_DESCRIPTION); + } else { + str = spa_dict_lookup(props, PW_KEY_NODE_DESCRIPTION); + if (str == NULL) + str = spa_dict_lookup(props, PW_KEY_NODE_NICK); + } + if (str == NULL) + str = node_name; + if (str == NULL) + str = "node"; + + if (app && !spa_streq(app, str)) + snprintf(tmp, sizeof(tmp), "%s/%s", app, str); + else + snprintf(tmp, sizeof(tmp), "%s", str); + + if (c->filter_name) + filter_name(tmp, FILTER_NAME, c->filter_char); + + ot = find_node(c, tmp); + if (ot != NULL && o->node.client_id != ot->node.client_id) { + snprintf(o->node.name, sizeof(o->node.name), "%.*s-%d", + (int)(sizeof(tmp)-11), tmp, id); + } else { + is_first = ot == NULL; + snprintf(o->node.name, sizeof(o->node.name), "%s", tmp); + } + + if ((str = spa_dict_lookup(props, PW_KEY_PRIORITY_SESSION)) != NULL) + o->node.priority = pw_properties_parse_int(str); + + pw_log_debug("%p: add node %d", c, id); + + pthread_mutex_lock(&c->context.lock); + spa_list_append(&c->context.objects, &o->link); + pthread_mutex_unlock(&c->context.lock); + } + else if (spa_streq(type, 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; + bool is_monitor = false; + char tmp[REAL_JACK_PORT_NAME_SIZE+1]; + + 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_EXTRA)) != NULL && + spa_strstartswith(str, "jack:flags:")) + flags = atoi(str+11); + + if ((str = spa_dict_lookup(props, PW_KEY_PORT_NAME)) == NULL) + goto exit; + + spa_dict_for_each(item, props) { + if (spa_streq(item->key, PW_KEY_PORT_DIRECTION)) { + if (spa_streq(item->value, "in")) + flags |= JackPortIsInput; + else if (spa_streq(item->value, "out")) + flags |= JackPortIsOutput; + } + else if (spa_streq(item->key, PW_KEY_PORT_PHYSICAL)) { + if (pw_properties_parse_bool(item->value)) + flags |= JackPortIsPhysical; + } + else if (spa_streq(item->key, PW_KEY_PORT_TERMINAL)) { + if (pw_properties_parse_bool(item->value)) + flags |= JackPortIsTerminal; + } + else if (spa_streq(item->key, PW_KEY_PORT_CONTROL)) { + if (pw_properties_parse_bool(item->value)) + type_id = TYPE_ID_MIDI; + } + else if (spa_streq(item->key, PW_KEY_PORT_MONITOR)) { + is_monitor = pw_properties_parse_bool(item->value); + } + } + if (is_monitor && !c->show_monitor) + goto exit; + + o = NULL; + if (node_id == c->node_id) { + snprintf(tmp, sizeof(tmp), "%s:%s", c->name, str); + o = find_port_by_name(c, tmp); + if (o != NULL) + pw_log_info("%p: %s found our port %p", c, tmp, o); + } + if (o == NULL) { + if ((ot = find_type(c, node_id, INTERFACE_Node, true)) == NULL) + goto exit; + + o = alloc_object(c, INTERFACE_Port); + if (o == NULL) + goto exit; + + o->port.system_id = 0; + o->port.priority = ot->node.priority; + o->port.node = ot; + o->port.latency[SPA_DIRECTION_INPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_INPUT); + o->port.latency[SPA_DIRECTION_OUTPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_OUTPUT); + + o->proxy = pw_registry_bind(c->registry, + id, type, PW_VERSION_PORT, 0); + if (o->proxy) { + uint32_t ids[1] = { SPA_PARAM_Latency }; + + pw_proxy_add_listener(o->proxy, + &o->proxy_listener, &proxy_events, o); + pw_proxy_add_object_listener(o->proxy, + &o->object_listener, &port_events, o); + + pw_port_subscribe_params((struct pw_port*)o->proxy, + ids, 1); + } + pthread_mutex_lock(&c->context.lock); + spa_list_append(&c->context.objects, &o->link); + pthread_mutex_unlock(&c->context.lock); + + if (is_monitor && !c->merge_monitor) + snprintf(tmp, sizeof(tmp), "%.*s%s:%s", + (int)(JACK_CLIENT_NAME_SIZE-(sizeof(MONITOR_EXT)-1)), + ot->node.name, MONITOR_EXT, str); + else + snprintf(tmp, sizeof(tmp), "%s:%s", ot->node.name, str); + + if (c->filter_name) + filter_name(tmp, FILTER_PORT, c->filter_char); + + op = find_port_by_name(c, tmp); + if (op != NULL) + snprintf(o->port.name, sizeof(o->port.name), "%.*s-%u", + (int)(sizeof(tmp)-11), tmp, serial); + else + snprintf(o->port.name, sizeof(o->port.name), "%s", tmp); + } + + if ((str = spa_dict_lookup(props, PW_KEY_OBJECT_PATH)) != NULL) + snprintf(o->port.alias1, sizeof(o->port.alias1), "%s", str); + + if ((str = spa_dict_lookup(props, PW_KEY_PORT_ALIAS)) != NULL) + snprintf(o->port.alias2, sizeof(o->port.alias2), "%s", str); + + if ((str = spa_dict_lookup(props, PW_KEY_PORT_ID)) != NULL) { + o->port.system_id = atoi(str); + snprintf(o->port.system, sizeof(o->port.system), "system:%s_%d", + flags & JackPortIsInput ? "playback" : + is_monitor ? "monitor" : "capture", + o->port.system_id+1); + } + + o->port.flags = flags; + o->port.type_id = type_id; + o->port.node_id = node_id; + o->port.is_monitor = is_monitor; + + pw_log_debug("%p: %p add port %d name:%s %d", c, o, id, + o->port.name, type_id); + } + else if (spa_streq(type, PW_TYPE_INTERFACE_Link)) { + struct object *p; + + o = alloc_object(c, INTERFACE_Link); + + pthread_mutex_lock(&c->context.lock); + spa_list_append(&c->context.objects, &o->link); + pthread_mutex_unlock(&c->context.lock); + + 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 ((p = find_type(c, o->port_link.src, INTERFACE_Port, true)) == NULL) + goto exit_free; + o->port_link.src_serial = p->serial; + + o->port_link.src_ours = p->port.port != NULL && + p->port.port->client == c; + if (o->port_link.src_ours) + o->port_link.our_output = p->port.port; + + if ((str = spa_dict_lookup(props, PW_KEY_LINK_INPUT_PORT)) == NULL) + goto exit_free; + o->port_link.dst = pw_properties_parse_int(str); + + if ((p = find_type(c, o->port_link.dst, INTERFACE_Port, true)) == NULL) + goto exit_free; + o->port_link.dst_serial = p->serial; + + o->port_link.dst_ours = p->port.port != NULL && + p->port.port->client == c; + if (o->port_link.dst_ours) + o->port_link.our_input = p->port.port; + + o->port_link.is_complete = !o->port_link.src_ours && !o->port_link.dst_ours; + pw_log_debug("%p: add link %d %u/%u->%u/%u", c, id, + o->port_link.src, o->port_link.src_serial, + o->port_link.dst, o->port_link.dst_serial); + } + else if (spa_streq(type, PW_TYPE_INTERFACE_Metadata)) { + struct pw_proxy *proxy; + + if (c->metadata != NULL) + goto exit; + if ((str = spa_dict_lookup(props, PW_KEY_METADATA_NAME)) == NULL) + goto exit; + + if (spa_streq(str, "default")) { + proxy = pw_registry_bind(c->registry, + id, type, PW_VERSION_METADATA, sizeof(struct metadata)); + + c->metadata = pw_proxy_get_user_data(proxy); + c->metadata->proxy = (struct pw_metadata*)proxy; + c->metadata->default_audio_sink[0] = '\0'; + c->metadata->default_audio_source[0] = '\0'; + + pw_proxy_add_listener(proxy, + &c->metadata->proxy_listener, + &metadata_proxy_events, c); + pw_metadata_add_listener(proxy, + &c->metadata->listener, + &metadata_events, c); + } else if (spa_streq(str, "settings")) { + proxy = pw_registry_bind(c->registry, + id, type, PW_VERSION_METADATA, sizeof(struct metadata)); + + c->settings = pw_proxy_get_user_data(proxy); + c->settings->proxy = (struct pw_metadata*)proxy; + pw_proxy_add_listener(proxy, + &c->settings->proxy_listener, + &settings_proxy_events, c); + } + goto exit; + } + else { + goto exit; + } + + o->id = id; + o->serial = serial; + + switch (o->type) { + case INTERFACE_Node: + if (is_first) { + pw_log_info("%p: client added \"%s\"", c, o->node.name); + do_callback(c, registration_callback, + o->node.name, 1, c->registration_arg); + graph_changed = true; + } + break; + + case INTERFACE_Port: + pw_log_info("%p: port added %u/%u \"%s\"", c, o->id, o->serial, o->port.name); + do_callback(c, portregistration_callback, + o->serial, 1, c->portregistration_arg); + graph_changed = true; + break; + + case INTERFACE_Link: + pw_log_info("%p: link %u %u/%u -> %u/%u added complete:%d", c, + o->id, o->port_link.src, o->port_link.src_serial, + o->port_link.dst, o->port_link.dst_serial, + o->port_link.is_complete); + if (o->port_link.is_complete) { + do_callback(c, connect_callback, + o->port_link.src_serial, + o->port_link.dst_serial, 1, c->connect_arg); + graph_changed = true; + } + break; + } + if (graph_changed) { + recompute_latencies(c); + do_callback(c, graph_callback, c->graph_arg); + } + + exit: + return; + exit_free: + free_object(c, o); + return; +} + +static void registry_event_global_remove(void *data, uint32_t id) +{ + struct client *c = (struct client *) data; + struct object *o; + bool graph_changed = false; + + pw_log_debug("%p: removed: %u", c, id); + + if ((o = find_id(c, id, true)) == NULL) + return; + + if (o->proxy) { + pw_proxy_destroy(o->proxy); + o->proxy = NULL; + } + o->removing = true; + + switch (o->type) { + case INTERFACE_Node: + if (c->metadata) { + if (spa_streq(o->node.node_name, c->metadata->default_audio_sink)) + c->metadata->default_audio_sink[0] = '\0'; + if (spa_streq(o->node.node_name, c->metadata->default_audio_source)) + c->metadata->default_audio_source[0] = '\0'; + } + if (find_node(c, o->node.name) == NULL) { + pw_log_info("%p: client %u removed \"%s\"", c, o->id, o->node.name); + do_callback(c, registration_callback, + o->node.name, 0, c->registration_arg); + graph_changed = true; + } + break; + case INTERFACE_Port: + pw_log_info("%p: port %u/%u removed \"%s\"", c, o->id, o->serial, o->port.name); + do_callback(c, portregistration_callback, + o->serial, 0, c->portregistration_arg); + graph_changed = true; + break; + case INTERFACE_Link: + if (o->port_link.is_complete && + find_type(c, o->port_link.src, INTERFACE_Port, true) != NULL && + find_type(c, o->port_link.dst, INTERFACE_Port, true) != NULL) { + pw_log_info("%p: link %u %u/%u -> %u/%u removed", c, o->id, + o->port_link.src, o->port_link.src_serial, + o->port_link.dst, o->port_link.dst_serial); + o->port_link.is_complete = false; + do_callback(c, connect_callback, + o->port_link.src_serial, o->port_link.dst_serial, 0, c->connect_arg); + graph_changed = true; + } else + pw_log_warn("unlink between unknown ports %d and %d", + o->port_link.src, o->port_link.dst); + break; + } + if (graph_changed) { + recompute_latencies(c); + do_callback(c, graph_callback, c->graph_arg); + } + + o->removing = false; + free_object(c, o); + + return; +} + +static const struct pw_registry_events registry_events = { + PW_VERSION_REGISTRY_EVENTS, + .global = registry_event_global, + .global_remove = registry_event_global_remove, +}; + +static void varargs_parse (struct client *c, jack_options_t options, va_list ap) +{ + if ((options & JackServerName)) + c->server_name = va_arg(ap, char *); + if ((options & JackLoadName)) + c->load_name = va_arg(ap, char *); + if ((options & JackLoadInit)) + c->load_init = va_arg(ap, char *); + if ((options & JackSessionID)) { + char *sid = va_arg(ap, char *); + if (sid) { + const long long id = atoll(sid); + if (id > 0) + c->session_id = id; + } + } +} + + +static int execute_match(void *data, const char *location, const char *action, + const char *val, size_t len) +{ + struct client *client = data; + if (spa_streq(action, "update-props")) + pw_properties_update_string(client->props, val, len); + return 1; +} + +SPA_EXPORT +jack_client_t * jack_client_open (const char *client_name, + jack_options_t options, + jack_status_t *status, ...) +{ + struct client *client; + const struct spa_support *support; + uint32_t n_support; + const char *str; + struct spa_cpu *cpu_iface; + va_list ap; + + if (getenv("PIPEWIRE_NOJACK") != NULL || + getenv("PIPEWIRE_INTERNAL") != NULL || + strstr(pw_get_library_version(), "0.2") != NULL) + goto disabled; + + spa_return_val_if_fail(client_name != NULL, NULL); + + client = calloc(1, sizeof(struct client)); + if (client == NULL) + goto disabled; + + pw_log_info("%p: open '%s' options:%d", client, client_name, options); + + va_start(ap, status); + varargs_parse(client, options, ap); + va_end(ap); + + snprintf(client->name, sizeof(client->name), "pw-%s", client_name); + + pthread_mutex_init(&client->context.lock, NULL); + spa_list_init(&client->context.objects); + + client->node_id = SPA_ID_INVALID; + + client->buffer_frames = (uint32_t)-1; + client->sample_rate = (uint32_t)-1; + client->latency = SPA_FRACTION(-1, -1); + + spa_list_init(&client->mix); + spa_list_init(&client->free_mix); + + spa_list_init(&client->free_ports); + pw_map_init(&client->ports[SPA_DIRECTION_INPUT], 32, 32); + pw_map_init(&client->ports[SPA_DIRECTION_OUTPUT], 32, 32); + + spa_list_init(&client->links); + client->driver_id = SPA_ID_INVALID; + + spa_list_init(&client->rt.target_links); + pthread_mutex_init(&client->rt_lock, NULL); + + if (client->server_name != NULL && + spa_streq(client->server_name, "default")) + client->server_name = NULL; + + client->props = pw_properties_new( + "loop.cancel", "true", + PW_KEY_REMOTE_NAME, client->server_name, + PW_KEY_CLIENT_NAME, client_name, + PW_KEY_CLIENT_API, "jack", + PW_KEY_CONFIG_NAME, "jack.conf", + NULL); + if (client->props == NULL) + goto no_props; + + client->context.loop = pw_thread_loop_new(client->name, NULL); + client->context.l = pw_thread_loop_get_loop(client->context.loop); + client->context.context = pw_context_new( + client->context.l, + pw_properties_copy(client->props), + 0); + if (client->context.context == NULL) + goto no_props; + + client->allow_mlock = client->context.context->settings.mem_allow_mlock; + client->warn_mlock = client->context.context->settings.mem_warn_mlock; + + pw_context_conf_update_props(client->context.context, + "jack.properties", client->props); + + pw_context_conf_section_match_rules(client->context.context, "jack.rules", + &client->context.context->properties->dict, execute_match, client); + + support = pw_context_get_support(client->context.context, &n_support); + + mix_function = mix_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) + mix_function = mix_sse; +#endif + } + client->context.old_thread_utils = + pw_context_get_object(client->context.context, + SPA_TYPE_INTERFACE_ThreadUtils); + if (client->context.old_thread_utils == NULL) + client->context.old_thread_utils = pw_thread_utils_get(); + + globals.thread_utils = client->context.old_thread_utils; + + client->context.thread_utils.iface = SPA_INTERFACE_INIT( + SPA_TYPE_INTERFACE_ThreadUtils, + SPA_VERSION_THREAD_UTILS, + &thread_utils_impl, client); + + client->loop = client->context.context->data_loop_impl; + pw_data_loop_stop(client->loop); + + pw_context_set_object(client->context.context, + SPA_TYPE_INTERFACE_ThreadUtils, + &client->context.thread_utils); + + pw_thread_loop_start(client->context.loop); + + pw_thread_loop_lock(client->context.loop); + + client->core = pw_context_connect(client->context.context, + pw_properties_copy(client->props), 0); + if (client->core == NULL) + goto server_failed; + + client->pool = pw_core_get_mempool(client->core); + + pw_core_add_listener(client->core, + &client->core_listener, + &core_events, client); + client->registry = pw_core_get_registry(client->core, + PW_VERSION_REGISTRY, 0); + pw_registry_add_listener(client->registry, + &client->registry_listener, + ®istry_events, client); + + if ((str = getenv("PIPEWIRE_PROPS")) != NULL) + pw_properties_update_string(client->props, str, strlen(str)); + if ((str = getenv("PIPEWIRE_QUANTUM")) != NULL) { + struct spa_fraction q; + if (sscanf(str, "%u/%u", &q.num, &q.denom) == 2 && q.denom != 0) { + pw_properties_setf(client->props, PW_KEY_NODE_RATE, + "1/%u", q.denom); + pw_properties_setf(client->props, PW_KEY_NODE_LATENCY, + "%u/%u", q.num, q.denom); + } else { + pw_log_warn("invalid PIPEWIRE_QUANTUM: %s", str); + } + } + if ((str = getenv("PIPEWIRE_LATENCY")) != NULL) + pw_properties_set(client->props, PW_KEY_NODE_LATENCY, str); + if ((str = getenv("PIPEWIRE_RATE")) != NULL) + pw_properties_set(client->props, PW_KEY_NODE_RATE, str); + if ((str = getenv("PIPEWIRE_LINK_PASSIVE")) != NULL) + pw_properties_set(client->props, PW_KEY_NODE_PASSIVE, str); + + if ((str = pw_properties_get(client->props, PW_KEY_NODE_LATENCY)) != NULL) { + uint32_t num, denom; + if (sscanf(str, "%u/%u", &num, &denom) == 2 && denom != 0) { + client->latency = SPA_FRACTION(num, denom); + } + } + if (pw_properties_get(client->props, PW_KEY_NODE_NAME) == NULL) + pw_properties_set(client->props, PW_KEY_NODE_NAME, client_name); + if (pw_properties_get(client->props, PW_KEY_NODE_GROUP) == NULL) + pw_properties_setf(client->props, PW_KEY_NODE_GROUP, "jack-%d", getpid()); + if (pw_properties_get(client->props, PW_KEY_NODE_DESCRIPTION) == NULL) + pw_properties_set(client->props, PW_KEY_NODE_DESCRIPTION, client_name); + if (pw_properties_get(client->props, PW_KEY_MEDIA_TYPE) == NULL) + pw_properties_set(client->props, PW_KEY_MEDIA_TYPE, "Audio"); + if (pw_properties_get(client->props, PW_KEY_MEDIA_CATEGORY) == NULL) + pw_properties_set(client->props, PW_KEY_MEDIA_CATEGORY, "Duplex"); + if (pw_properties_get(client->props, PW_KEY_MEDIA_ROLE) == NULL) + pw_properties_set(client->props, PW_KEY_MEDIA_ROLE, "DSP"); + if (pw_properties_get(client->props, PW_KEY_NODE_ALWAYS_PROCESS) == NULL) + pw_properties_set(client->props, PW_KEY_NODE_ALWAYS_PROCESS, "true"); + if (pw_properties_get(client->props, PW_KEY_NODE_LOCK_QUANTUM) == NULL) + pw_properties_set(client->props, PW_KEY_NODE_LOCK_QUANTUM, "true"); + pw_properties_set(client->props, PW_KEY_NODE_TRANSPORT_SYNC, "true"); + + client->node = pw_core_create_object(client->core, + "client-node", + PW_TYPE_INTERFACE_ClientNode, + PW_VERSION_CLIENT_NODE, + &client->props->dict, + 0); + if (client->node == NULL) + goto init_failed; + + pw_client_node_add_listener(client->node, + &client->node_listener, &client_node_events, client); + pw_proxy_add_listener((struct pw_proxy*)client->node, + &client->proxy_listener, &node_proxy_events, client); + + client->info = SPA_NODE_INFO_INIT(); + client->info.max_input_ports = UINT32_MAX; + client->info.max_output_ports = UINT32_MAX; + client->info.change_mask = SPA_NODE_CHANGE_MASK_FLAGS | + SPA_NODE_CHANGE_MASK_PROPS; + client->info.flags = SPA_NODE_FLAG_RT; + client->info.props = &client->props->dict; + + pw_client_node_update(client->node, + PW_CLIENT_NODE_UPDATE_INFO, + 0, NULL, &client->info); + client->info.change_mask = 0; + + client->show_monitor = pw_properties_get_bool(client->props, "jack.show-monitor", true); + client->merge_monitor = pw_properties_get_bool(client->props, "jack.merge-monitor", true); + client->short_name = pw_properties_get_bool(client->props, "jack.short-name", false); + client->filter_name = pw_properties_get_bool(client->props, "jack.filter-name", false); + client->filter_char = ' '; + if ((str = pw_properties_get(client->props, "jack.filter-char")) != NULL && str[0] != '\0') + client->filter_char = str[0]; + client->locked_process = pw_properties_get_bool(client->props, "jack.locked-process", true); + client->default_as_system = pw_properties_get_bool(client->props, "jack.default-as-system", false); + client->fix_midi_events = pw_properties_get_bool(client->props, "jack.fix-midi-events", true); + client->global_buffer_size = pw_properties_get_bool(client->props, "jack.global-buffer-size", false); + + client->self_connect_mode = SELF_CONNECT_ALLOW; + if ((str = pw_properties_get(client->props, "jack.self-connect-mode")) != NULL) { + if (spa_streq(str, "fail-external")) + client->self_connect_mode = SELF_CONNECT_FAIL_EXT; + else if (spa_streq(str, "ignore-external")) + client->self_connect_mode = SELF_CONNECT_IGNORE_EXT; + else if (spa_streq(str, "fail-all")) + client->self_connect_mode = SELF_CONNECT_FAIL_ALL; + else if (spa_streq(str, "ignore-all")) + client->self_connect_mode = SELF_CONNECT_IGNORE_ALL; + } + client->rt_max = pw_properties_get_int32(client->props, "rt.prio", DEFAULT_RT_MAX); + + if (status) + *status = 0; + + while (true) { + pw_thread_loop_wait(client->context.loop); + + if (client->last_res < 0) + goto init_failed; + + if (client->has_transport) + break; + } + + if (!spa_streq(client->name, client_name)) { + if (status) + *status |= JackNameNotUnique; + if (options & JackUseExactName) + goto exit_unlock; + } + pw_thread_loop_unlock(client->context.loop); + + pw_log_info("%p: opened", client); + return (jack_client_t *)client; + +no_props: + if (status) + *status = JackFailure | JackInitFailure; + goto exit; +init_failed: + if (status) + *status = JackFailure | JackInitFailure; + goto exit_unlock; +server_failed: + if (status) + *status = JackFailure | JackServerFailed; + goto exit_unlock; +exit_unlock: + pw_thread_loop_unlock(client->context.loop); +exit: + jack_client_close((jack_client_t *) client); + return NULL; +disabled: + if (status) + *status = JackFailure | JackInitFailure; + 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; + struct object *o; + int res; + + spa_return_val_if_fail(c != NULL, -EINVAL); + + pw_log_info("%p: close", client); + + c->destroyed = true; + + res = jack_deactivate(client); + + clean_transport(c); + + if (c->context.loop) + pw_thread_loop_stop(c->context.loop); + + if (c->registry) { + spa_hook_remove(&c->registry_listener); + pw_proxy_destroy((struct pw_proxy*)c->registry); + } + if (c->metadata && c->metadata->proxy) { + pw_proxy_destroy((struct pw_proxy*)c->metadata->proxy); + } + if (c->settings && c->settings->proxy) { + pw_proxy_destroy((struct pw_proxy*)c->settings->proxy); + } + + if (c->core) { + spa_hook_remove(&c->core_listener); + pw_core_disconnect(c->core); + } + + if (c->context.context) + pw_context_destroy(c->context.context); + + if (c->context.loop) + pw_thread_loop_destroy(c->context.loop); + + pw_log_debug("%p: free", client); + + spa_list_consume(o, &c->context.objects, link) + free_object(c, o); + recycle_objects(c, 0); + + pw_map_clear(&c->ports[SPA_DIRECTION_INPUT]); + pw_map_clear(&c->ports[SPA_DIRECTION_OUTPUT]); + + pthread_mutex_destroy(&c->context.lock); + pthread_mutex_destroy(&c->rt_lock); + pw_properties_free(c->props); + free(c); + + return res; +} + +SPA_EXPORT +jack_intclient_t jack_internal_client_handle (jack_client_t *client, + const char *client_name, jack_status_t *status) +{ + struct client *c = (struct client *) client; + spa_return_val_if_fail(c != NULL, 0); + if (status) + *status = JackNoSuchClient | JackFailure; + return 0; +} + +SPA_EXPORT +jack_intclient_t jack_internal_client_load (jack_client_t *client, + const char *client_name, jack_options_t options, + jack_status_t *status, ...) +{ + struct client *c = (struct client *) client; + spa_return_val_if_fail(c != NULL, 0); + if (status) + *status = JackNoSuchClient | JackFailure; + return 0; +} + +SPA_EXPORT +jack_status_t jack_internal_client_unload (jack_client_t *client, + jack_intclient_t intclient) +{ + struct client *c = (struct client *) client; + spa_return_val_if_fail(c != NULL, 0); + return JackFailure | JackNoSuchClient; +} + +SPA_EXPORT +char *jack_get_internal_client_name (jack_client_t *client, + jack_intclient_t intclient) +{ + struct client *c = (struct client *) client; + spa_return_val_if_fail(c != NULL, NULL); + return strdup(c->name); +} + +SPA_EXPORT +int jack_client_name_size (void) +{ + /* The JACK API specifies that this value includes the final NULL character. */ + pw_log_trace("%d", JACK_CLIENT_NAME_SIZE+1); + return JACK_CLIENT_NAME_SIZE+1; +} + +SPA_EXPORT +char * jack_get_client_name (jack_client_t *client) +{ + struct client *c = (struct client *) client; + spa_return_val_if_fail(c != NULL, NULL); + return c->name; +} + +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; + char *uuid = NULL; + bool monitor; + + spa_return_val_if_fail(c != NULL, NULL); + spa_return_val_if_fail(client_name != NULL, NULL); + + monitor = spa_strendswith(client_name, MONITOR_EXT); + + pthread_mutex_lock(&c->context.lock); + + spa_list_for_each(o, &c->context.objects, link) { + if (o->type != INTERFACE_Node) + continue; + if (spa_streq(o->node.name, client_name) || + (monitor && spa_strneq(o->node.name, client_name, + strlen(client_name) - strlen(MONITOR_EXT)))) { + uuid = spa_aprintf( "%" PRIu64, client_make_uuid(o->serial, monitor)); + break; + } + } + pw_log_debug("%p: name %s -> %s", client, client_name, uuid); + pthread_mutex_unlock(&c->context.lock); + return uuid; +} + +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; + char *name = NULL; + bool monitor; + + spa_return_val_if_fail(c != NULL, NULL); + spa_return_val_if_fail(client_uuid != NULL, NULL); + + if (jack_uuid_parse(client_uuid, &uuid) < 0) + return NULL; + + monitor = uuid & (1 << 30); + + pthread_mutex_lock(&c->context.lock); + spa_list_for_each(o, &c->context.objects, link) { + if (o->type != INTERFACE_Node) + continue; + if (client_make_uuid(o->serial, monitor) == uuid) { + pw_log_debug("%p: uuid %s (%"PRIu64")-> %s", + client, client_uuid, uuid, o->node.name); + name = spa_aprintf("%s%s", o->node.name, monitor ? MONITOR_EXT : ""); + break; + } + } + pthread_mutex_unlock(&c->context.lock); + return name; +} + +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_client_node_set_active(c->node, true); + res = do_sync(c); + return res; +} + +SPA_EXPORT +int jack_activate (jack_client_t *client) +{ + struct client *c = (struct client *) client; + int res = 0; + + spa_return_val_if_fail(c != NULL, -EINVAL); + + pw_log_info("%p: active:%d", c, c->active); + + if (c->active) + return 0; + + pw_thread_loop_lock(c->context.loop); + pw_data_loop_start(c->loop); + + if ((res = do_activate(c)) < 0) + goto done; + + c->activation->pending_new_pos = true; + c->activation->pending_sync = true; + + c->active = true; + + do_callback(c, graph_callback, c->graph_arg); + +done: + if (res < 0) + pw_data_loop_stop(c->loop); + + pw_thread_loop_unlock(c->context.loop); + + return res; +} + +SPA_EXPORT +int jack_deactivate (jack_client_t *client) +{ + struct object *l; + struct client *c = (struct client *) client; + int res; + + spa_return_val_if_fail(c != NULL, -EINVAL); + + pw_log_info("%p: active:%d", c, c->active); + + if (!c->active) + return 0; + + pw_thread_loop_lock(c->context.loop); + pw_data_loop_stop(c->loop); + + pw_client_node_set_active(c->node, false); + + c->activation->pending_new_pos = false; + c->activation->pending_sync = false; + + spa_list_for_each(l, &c->context.objects, link) { + if (l->type != INTERFACE_Link || l->removed) + continue; + if (l->port_link.src_ours || l->port_link.dst_ours) + pw_registry_destroy(c->registry, l->id); + } + + res = do_sync(c); + + pw_thread_loop_unlock(c->context.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) +{ + struct client *c = (struct client *) client; + + spa_return_val_if_fail(c != NULL, (pthread_t){0}); + + return (jack_native_thread_t)pw_data_loop_get_thread(c->loop); +} + +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("%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; + + spa_return_val_if_fail(c != NULL, 0); + + res = cycle_wait(c); + pw_log_trace("%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; + + spa_return_if_fail(c != NULL); + + pw_log_trace("%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; + + spa_return_val_if_fail(c != NULL, -EINVAL); + + if (c->active) { + pw_log_error("%p: can't set callback on active client", c); + return -EIO; + } else if (c->process_callback) { + pw_log_error("%p: process callback was already set", c); + return -EIO; + } + pw_log_debug("%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; + + spa_return_val_if_fail(c != NULL, -EINVAL); + + pw_log_debug("%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; + + spa_return_if_fail(c != NULL); + + if (c->active) { + pw_log_error("%p: can't set callback on active client", c); + } else { + pw_log_debug("%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; + + spa_return_if_fail(c != NULL); + + if (c->active) { + pw_log_error("%p: can't set callback on active client", c); + } else { + pw_log_debug("%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; + + spa_return_val_if_fail(c != NULL, -EINVAL); + + if (c->active) { + pw_log_error("%p: can't set callback on active client", c); + return -EIO; + } else if (c->thread_callback) { + pw_log_error("%p: thread callback was already set", c); + return -EIO; + } + + pw_log_debug("%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; + + spa_return_val_if_fail(c != NULL, -EINVAL); + + if (c->active) { + pw_log_error("%p: can't set callback on active client", c); + return -EIO; + } + pw_log_debug("%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; + + spa_return_val_if_fail(c != NULL, -EINVAL); + + if (c->active) { + pw_log_error("%p: can't set callback on active client", c); + return -EIO; + } + pw_log_debug("%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; + + spa_return_val_if_fail(c != NULL, -EINVAL); + + if (c->active) { + pw_log_error("%p: can't set callback on active client", c); + return -EIO; + } + pw_log_debug("%p: %p %p", c, srate_callback, arg); + c->srate_callback = srate_callback; + c->srate_arg = arg; + if (c->srate_callback && c->sample_rate != (uint32_t)-1) + c->srate_callback(c->sample_rate, c->srate_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; + + spa_return_val_if_fail(c != NULL, -EINVAL); + + if (c->active) { + pw_log_error("%p: can't set callback on active client", c); + return -EIO; + } + pw_log_debug("%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; + + spa_return_val_if_fail(c != NULL, -EINVAL); + + if (c->active) { + pw_log_error("%p: can't set callback on active client", c); + return -EIO; + } + pw_log_debug("%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; + + spa_return_val_if_fail(c != NULL, -EINVAL); + + if (c->active) { + pw_log_error("%p: can't set callback on active client", c); + return -EIO; + } + pw_log_debug("%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; + + spa_return_val_if_fail(c != NULL, -EINVAL); + + if (c->active) { + pw_log_error("%p: can't set callback on active client", c); + return -EIO; + } + pw_log_debug("%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; + + spa_return_val_if_fail(c != NULL, -EINVAL); + + if (c->active) { + pw_log_error("%p: can't set callback on active client", c); + return -EIO; + } + pw_log_debug("%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; + + spa_return_val_if_fail(c != NULL, -EINVAL); + + if (c->active) { + pw_log_error("%p: can't set callback on active client", c); + return -EIO; + } + pw_log_debug("%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; + + spa_return_val_if_fail(c != NULL, -EINVAL); + + if (c->active) { + pw_log_error("%p: can't set callback on active client", c); + return -EIO; + } + pw_log_debug("%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) +{ + struct client *c = (struct client *) client; + + pw_log_info("%p: freewheel %d", client, onoff); + + pw_thread_loop_lock(c->context.loop); + pw_properties_set(c->props, "node.group", + onoff ? "pipewire.freewheel" : ""); + + c->info.change_mask |= SPA_NODE_CHANGE_MASK_PROPS; + c->info.props = &c->props->dict; + + pw_client_node_update(c->node, + PW_CLIENT_NODE_UPDATE_INFO, + 0, NULL, &c->info); + c->info.change_mask = 0; + pw_thread_loop_unlock(c->context.loop); + + return 0; +} + +SPA_EXPORT +int jack_set_buffer_size (jack_client_t *client, jack_nframes_t nframes) +{ + struct client *c = (struct client *) client; + + spa_return_val_if_fail(c != NULL, -EINVAL); + + pw_log_info("%p: buffer-size %u", client, nframes); + + pw_thread_loop_lock(c->context.loop); + if (c->global_buffer_size && c->settings && c->settings->proxy) { + char val[256]; + snprintf(val, sizeof(val), "%u", nframes == 1 ? 0: nframes); + pw_metadata_set_property(c->settings->proxy, 0, + "clock.force-quantum", "", val); + } else { + pw_properties_setf(c->props, PW_KEY_NODE_FORCE_QUANTUM, "%u", nframes); + + c->info.change_mask |= SPA_NODE_CHANGE_MASK_PROPS; + c->info.props = &c->props->dict; + + pw_client_node_update(c->node, + PW_CLIENT_NODE_UPDATE_INFO, + 0, NULL, &c->info); + c->info.change_mask = 0; + } + pw_thread_loop_unlock(c->context.loop); + + return 0; +} + +SPA_EXPORT +jack_nframes_t jack_get_sample_rate (jack_client_t *client) +{ + struct client *c = (struct client *) client; + jack_nframes_t res = -1; + + spa_return_val_if_fail(c != NULL, 0); + + if (!c->active) + res = c->latency.denom; + if (c->active || res == (uint32_t)-1) { + res = c->sample_rate; + if (res == (uint32_t)-1) { + if (c->rt.position) + res = c->rt.position->clock.rate.denom; + else if (c->position) + res = c->position->clock.rate.denom; + } + } + pw_log_debug("sample_rate: %u", res); + return res; +} + +SPA_EXPORT +jack_nframes_t jack_get_buffer_size (jack_client_t *client) +{ + struct client *c = (struct client *) client; + jack_nframes_t res = -1; + + spa_return_val_if_fail(c != NULL, 0); + + if (!c->active) + res = c->latency.num; + if (c->active || res == (uint32_t)-1) { + res = c->buffer_frames; + if (res == (uint32_t)-1) { + if (c->rt.position) + res = c->rt.position->clock.duration; + else if (c->position) + res = c->position->clock.duration; + } + } + c->buffer_frames = res; + pw_log_debug("buffer_frames: %u", res); + return res; +} + +SPA_EXPORT +int jack_engine_takeover_timebase (jack_client_t *client) +{ + pw_log_error("%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; + + spa_return_val_if_fail(c != NULL, 0.0); + + if (c->driver_activation) + res = c->driver_activation->cpu_load[0] * 100.0f; + + pw_log_trace("%p: cpu load %f", client, res); + return res; +} + +#include "statistics.c" + +static void *get_buffer_input_float(struct port *p, jack_nframes_t frames); +static void *get_buffer_input_midi(struct port *p, jack_nframes_t frames); +static void *get_buffer_input_empty(struct port *p, jack_nframes_t frames); +static void *get_buffer_output_float(struct port *p, jack_nframes_t frames); +static void *get_buffer_output_midi(struct port *p, jack_nframes_t frames); +static void *get_buffer_output_empty(struct port *p, jack_nframes_t frames); + +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 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[6]; + uint32_t n_params = 0; + struct port *p; + int res; + + spa_return_val_if_fail(c != NULL, NULL); + spa_return_val_if_fail(port_name != NULL, NULL); + spa_return_val_if_fail(port_type != NULL, NULL); + + pw_log_info("%p: port register \"%s:%s\" \"%s\" %08lx %ld", + c, c->name, port_name, port_type, flags, buffer_frames); + + if (flags & JackPortIsInput) + direction = PW_DIRECTION_INPUT; + else if (flags & JackPortIsOutput) + direction = PW_DIRECTION_OUTPUT; + else { + pw_log_warn("invalid port flags %lu for %s", flags, port_name); + return NULL; + } + + if ((type_id = string_to_type(port_type)) == SPA_ID_INVALID) { + pw_log_warn("unknown port type %s", port_type); + return NULL; + } + + if ((p = alloc_port(c, direction)) == NULL) { + pw_log_warn("can't allocate port %s: %m", port_name); + 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; + + init_buffer(p); + + if (direction == SPA_DIRECTION_INPUT) { + switch (type_id) { + case TYPE_ID_AUDIO: + case TYPE_ID_VIDEO: + p->get_buffer = get_buffer_input_float; + break; + case TYPE_ID_MIDI: + p->get_buffer = get_buffer_input_midi; + break; + default: + p->get_buffer = get_buffer_input_empty; + break; + } + } else { + switch (type_id) { + case TYPE_ID_AUDIO: + case TYPE_ID_VIDEO: + p->get_buffer = get_buffer_output_float; + break; + case TYPE_ID_MIDI: + p->get_buffer = get_buffer_output_midi; + break; + default: + p->get_buffer = get_buffer_output_empty; + break; + } + } + + pw_log_debug("%p: port %p", c, p); + + spa_list_init(&p->mix); + + pw_properties_set(p->props, PW_KEY_FORMAT_DSP, port_type); + pw_properties_set(p->props, PW_KEY_PORT_NAME, port_name); + if (flags > 0x1f) { + pw_properties_setf(p->props, PW_KEY_PORT_EXTRA, + "jack:flags:%lu", flags & ~0x1f); + } + if (flags & JackPortIsPhysical) + pw_properties_set(p->props, PW_KEY_PORT_PHYSICAL, "true"); + if (flags & JackPortIsTerminal) + pw_properties_set(p->props, PW_KEY_PORT_TERMINAL, "true"); + + p->info = SPA_PORT_INFO_INIT(); + p->info.change_mask |= SPA_PORT_CHANGE_MASK_FLAGS; + p->info.flags = SPA_PORT_FLAG_NO_REF; + p->info.change_mask |= SPA_PORT_CHANGE_MASK_PROPS; + p->info.props = &p->props->dict; + p->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; + p->params[IDX_EnumFormat] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); + p->params[IDX_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, SPA_PARAM_INFO_READ); + p->params[IDX_IO] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); + p->params[IDX_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); + p->params[IDX_Latency] = SPA_PARAM_INFO(SPA_PARAM_Latency, SPA_PARAM_INFO_READWRITE); + p->info.params = p->params; + p->info.n_params = N_PORT_PARAMS; + + 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); + param_latency(c, p, ¶ms[n_params++], &b); + param_latency_other(c, p, ¶ms[n_params++], &b); + + pw_thread_loop_lock(c->context.loop); + + pw_client_node_port_update(c->node, + direction, + p->port_id, + PW_CLIENT_NODE_PORT_UPDATE_PARAMS | + PW_CLIENT_NODE_PORT_UPDATE_INFO, + n_params, + (const struct spa_pod **) params, + &p->info); + + p->info.change_mask = 0; + + res = do_sync(c); + + pw_thread_loop_unlock(c->context.loop); + + if (res < 0) { + pw_log_warn("can't create port %s: %s", port_name, + spa_strerror(res)); + return NULL; + } + + return (jack_port_t *) o; +} + +static int +do_invalidate_port(struct spa_loop *loop, + bool async, uint32_t seq, const void *data, size_t size, void *user_data) +{ + struct port *p = user_data; + p->valid = false; + return 0; +} + +SPA_EXPORT +int jack_port_unregister (jack_client_t *client, jack_port_t *port) +{ + struct client *c = (struct client *) client; + struct object *o = (struct object *) port; + struct port *p; + int res; + + spa_return_val_if_fail(c != NULL, -EINVAL); + spa_return_val_if_fail(o != NULL, -EINVAL); + + pw_thread_loop_lock(c->context.loop); + + p = o->port.port; + if (o->type != INTERFACE_Port || p == NULL || !p->valid || + o->client != c) { + pw_log_error("%p: invalid port %p", client, port); + res = -EINVAL; + goto done; + } + pw_data_loop_invoke(c->loop, + do_invalidate_port, 1, NULL, 0, !c->data_locked, p); + + pw_log_info("%p: port %p unregister \"%s\"", client, port, o->port.name); + + pw_client_node_port_update(c->node, + p->direction, + p->port_id, + 0, 0, NULL, NULL); + + res = do_sync(c); + if (res < 0) { + pw_log_warn("can't unregister port %s: %s", o->port.name, + spa_strerror(res)); + } + free_port(c, p); +done: + pw_thread_loop_unlock(c->context.loop); + + return res; +} + +static struct buffer *get_mix_buffer(struct mix *mix, jack_nframes_t frames) +{ + struct spa_io_buffers *io; + + if (mix->peer_port != NULL) + prepare_output(mix->peer_port, frames); + + io = mix->io; + if (io == NULL || + io->status != SPA_STATUS_HAVE_DATA || + io->buffer_id >= mix->n_buffers) + return NULL; + + return &mix->buffers[io->buffer_id]; +} + +static void *get_buffer_input_float(struct port *p, jack_nframes_t frames) +{ + struct mix *mix; + struct buffer *b; + void *ptr = NULL; + float *mix_ptr[MAX_MIX], *np; + uint32_t n_ptr = 0; + bool ptr_aligned = true; + + spa_list_for_each(mix, &p->mix, port_link) { + struct spa_data *d; + uint32_t offset, size; + + pw_log_trace_fp("%p: port %s mix %d.%d get buffer %d", + p->client, p->object->port.name, p->port_id, mix->id, frames); + + if ((b = get_mix_buffer(mix, frames)) == NULL) + continue; + + d = &b->datas[0]; + offset = SPA_MIN(d->chunk->offset, d->maxsize); + size = SPA_MIN(d->chunk->size, d->maxsize - offset); + if (size / sizeof(float) < frames) + continue; + + np = SPA_PTROFF(d->data, offset, float); + if (!SPA_IS_ALIGNED(np, 16)) + ptr_aligned = false; + + mix_ptr[n_ptr++] = np; + if (n_ptr == MAX_MIX) + break; + } + if (n_ptr == 1) { + ptr = mix_ptr[0]; + } else if (n_ptr > 1) { + ptr = p->emptyptr; + mix_function(ptr, mix_ptr, n_ptr, ptr_aligned, frames); + p->zeroed = false; + } + if (ptr == NULL) + ptr = init_buffer(p); + return ptr; +} + +static void *get_buffer_input_midi(struct port *p, jack_nframes_t frames) +{ + struct mix *mix; + void *ptr = p->emptyptr; + struct spa_pod_sequence *seq[MAX_MIX]; + uint32_t n_seq = 0; + + jack_midi_clear_buffer(ptr); + + spa_list_for_each(mix, &p->mix, port_link) { + struct spa_data *d; + struct buffer *b; + void *pod; + + pw_log_trace_fp("%p: port %p mix %d.%d get buffer %d", + p->client, p, p->port_id, mix->id, frames); + + if ((b = get_mix_buffer(mix, frames)) == NULL) + continue; + + d = &b->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; + if (n_seq == MAX_MIX) + break; + } + convert_to_midi(seq, n_seq, ptr, p->client->fix_midi_events); + + return ptr; +} + +static void *get_buffer_output_float(struct port *p, jack_nframes_t frames) +{ + void *ptr; + + ptr = get_buffer_output(p, frames, sizeof(float), NULL); + if (SPA_UNLIKELY(p->empty_out = (ptr == NULL))) + ptr = p->emptyptr; + return ptr; +} + +static void *get_buffer_output_midi(struct port *p, jack_nframes_t frames) +{ + p->empty_out = true; + return p->emptyptr; +} + +static void *get_buffer_output_empty(struct port *p, jack_nframes_t frames) +{ + p->empty_out = true; + return p->emptyptr; +} + +static void *get_buffer_input_empty(struct port *p, jack_nframes_t frames) +{ + return init_buffer(p); +} + +SPA_EXPORT +void * jack_port_get_buffer (jack_port_t *port, jack_nframes_t frames) +{ + struct object *o = (struct object *) port; + struct port *p; + void *ptr; + + spa_return_val_if_fail(o != NULL, NULL); + + if (o->type != INTERFACE_Port || o->client == NULL) + return NULL; + + if ((p = o->port.port) == NULL) { + struct mix *mix; + struct buffer *b; + struct spa_data *d; + uint32_t offset, size; + + if ((mix = find_mix_peer(o->client, o->id)) == NULL) + return NULL; + + pw_log_trace("peer mix: %p %d", mix, mix->peer_id); + + if ((b = get_mix_buffer(mix, frames)) == NULL) + return NULL; + + d = &b->datas[0]; + offset = SPA_MIN(d->chunk->offset, d->maxsize); + size = SPA_MIN(d->chunk->size, d->maxsize - offset); + if (size / sizeof(float) < frames) + return NULL; + + return SPA_PTROFF(d->data, offset, void); + } + if (!p->valid) + return NULL; + + ptr = p->get_buffer(p, frames); + pw_log_trace_fp("%p: port %p buffer %p empty:%u", p->client, p, ptr, p->empty_out); + return ptr; +} + +SPA_EXPORT +jack_uuid_t jack_port_uuid (const jack_port_t *port) +{ + struct object *o = (struct object *) port; + spa_return_val_if_fail(o != NULL, 0); + return jack_port_uuid_generate(o->serial); +} + +static const char *port_name(struct object *o) +{ + const char *name; + struct client *c = o->client; + if (c->default_as_system && is_port_default(c, o)) + name = o->port.system; + else + name = o->port.name; + return name; +} + +SPA_EXPORT +const char * jack_port_name (const jack_port_t *port) +{ + struct object *o = (struct object *) port; + spa_return_val_if_fail(o != NULL, NULL); + return port_name(o); +} + +SPA_EXPORT +const char * jack_port_short_name (const jack_port_t *port) +{ + struct object *o = (struct object *) port; + spa_return_val_if_fail(o != NULL, NULL); + return strchr(port_name(o), ':') + 1; +} + +SPA_EXPORT +int jack_port_flags (const jack_port_t *port) +{ + struct object *o = (struct object *) port; + spa_return_val_if_fail(o != NULL, 0); + return o->port.flags; +} + +SPA_EXPORT +const char * jack_port_type (const jack_port_t *port) +{ + struct object *o = (struct object *) port; + spa_return_val_if_fail(o != NULL, NULL); + 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; + spa_return_val_if_fail(o != NULL, 0); + 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; + spa_return_val_if_fail(o != NULL, 0); + return o->type == INTERFACE_Port && + o->port.port != NULL && + o->port.port->client == (struct client*)client; +} + +SPA_EXPORT +int jack_port_connected (const jack_port_t *port) +{ + struct object *o = (struct object *) port; + struct client *c; + struct object *l; + int res = 0; + + spa_return_val_if_fail(o != NULL, 0); + if (o->type != INTERFACE_Port || o->client == NULL) + return 0; + + c = o->client; + + pthread_mutex_lock(&c->context.lock); + spa_list_for_each(l, &c->context.objects, link) { + if (l->type != INTERFACE_Link || l->removed) + continue; + if (!l->port_link.is_complete) + continue; + if (l->port_link.src_serial == o->serial || + l->port_link.dst_serial == o->serial) + res++; + } + pthread_mutex_unlock(&c->context.lock); + + pw_log_debug("%p: id:%u/%u res:%d", port, o->id, o->serial, res); + + 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; + struct object *p, *l; + int res = 0; + + spa_return_val_if_fail(o != NULL, 0); + spa_return_val_if_fail(port_name != NULL, 0); + if (o->type != INTERFACE_Port || o->client == NULL) + return 0; + + c = o->client; + + pthread_mutex_lock(&c->context.lock); + + p = find_port_by_name(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 ((l = find_link(c, o->id, p->id)) != NULL && + l->port_link.is_complete) + res = 1; + + exit: + pthread_mutex_unlock(&c->context.lock); + pw_log_debug("%p: id:%u/%u name:%s res:%d", port, o->id, + o->serial, port_name, res); + + return res; +} + +SPA_EXPORT +const char ** jack_port_get_connections (const jack_port_t *port) +{ + struct object *o = (struct object *) port; + + spa_return_val_if_fail(o != NULL, NULL); + if (o->type != INTERFACE_Port || o->client == NULL) + return NULL; + + return jack_port_get_all_connections((jack_client_t *)o->client, 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; + int count = 0; + struct pw_array tmp; + + spa_return_val_if_fail(c != NULL, NULL); + spa_return_val_if_fail(o != NULL, NULL); + + pw_array_init(&tmp, sizeof(void*) * 32); + + pthread_mutex_lock(&c->context.lock); + spa_list_for_each(l, &c->context.objects, link) { + if (l->type != INTERFACE_Link || l->removed) + continue; + if (l->port_link.src_serial == o->serial) + p = find_type(c, l->port_link.dst, INTERFACE_Port, true); + else if (l->port_link.dst_serial == o->serial) + p = find_type(c, l->port_link.src, INTERFACE_Port, true); + else + continue; + + if (p == NULL) + continue; + + pw_array_add_ptr(&tmp, (void*)port_name(p)); + count++; + } + pthread_mutex_unlock(&c->context.lock); + + if (count == 0) { + pw_array_clear(&tmp); + res = NULL; + } else { + pw_array_add_ptr(&tmp, NULL); + res = tmp.data; + } + 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; + int res = 0; + + spa_return_val_if_fail(c != NULL, -EINVAL); + spa_return_val_if_fail(o != NULL, -EINVAL); + spa_return_val_if_fail(port_name != NULL, -EINVAL); + + pw_thread_loop_lock(c->context.loop); + + pw_log_info("%p: port rename %p %s -> %s:%s", + client, port, o->port.name, c->name, port_name); + + p = o->port.port; + if (p == NULL || !p->valid) { + res = -EINVAL; + goto done; + } + + pw_properties_set(p->props, PW_KEY_PORT_NAME, port_name); + snprintf(o->port.name, sizeof(o->port.name), "%s:%s", c->name, port_name); + + p->info.change_mask |= SPA_PORT_CHANGE_MASK_PROPS; + p->info.props = &p->props->dict; + + pw_client_node_port_update(c->node, + p->direction, + p->port_id, + PW_CLIENT_NODE_PORT_UPDATE_INFO, + 0, NULL, + &p->info); + p->info.change_mask = 0; + +done: + pw_thread_loop_unlock(c->context.loop); + + return res; +} + +SPA_EXPORT +int jack_port_set_alias (jack_port_t *port, const char *alias) +{ + struct object *o = (struct object *) port; + struct client *c; + struct port *p; + const char *key; + int res = 0; + + spa_return_val_if_fail(o != NULL, -EINVAL); + spa_return_val_if_fail(alias != NULL, -EINVAL); + + c = o->client; + if (o->type != INTERFACE_Port || c == NULL) + return -EINVAL; + + pw_thread_loop_lock(c->context.loop); + + p = o->port.port; + if (p == NULL || !p->valid) { + res = -EINVAL; + goto done; + } + + 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 { + res = -1; + goto done; + } + + pw_properties_set(p->props, key, alias); + + p->info.change_mask |= SPA_PORT_CHANGE_MASK_PROPS; + p->info.props = &p->props->dict; + + pw_client_node_port_update(c->node, + p->direction, + p->port_id, + PW_CLIENT_NODE_PORT_UPDATE_INFO, + 0, NULL, + &p->info); + p->info.change_mask = 0; + +done: + pw_thread_loop_unlock(c->context.loop); + + return res; +} + +SPA_EXPORT +int jack_port_unset_alias (jack_port_t *port, const char *alias) +{ + struct object *o = (struct object *) port; + struct client *c; + struct port *p; + const char *key; + int res = 0; + + spa_return_val_if_fail(o != NULL, -EINVAL); + spa_return_val_if_fail(alias != NULL, -EINVAL); + + c = o->client; + if (o->type != INTERFACE_Port || c == NULL) + return -EINVAL; + + pw_thread_loop_lock(c->context.loop); + p = o->port.port; + if (p == NULL || !p->valid) { + res = -EINVAL; + goto done; + } + + if (spa_streq(o->port.alias1, alias)) + key = PW_KEY_OBJECT_PATH; + else if (spa_streq(o->port.alias2, alias)) + key = PW_KEY_PORT_ALIAS; + else { + res = -1; + goto done; + } + + pw_properties_set(p->props, key, NULL); + + p->info.change_mask |= SPA_PORT_CHANGE_MASK_PROPS; + p->info.props = &p->props->dict; + + pw_client_node_port_update(c->node, + p->direction, + p->port_id, + PW_CLIENT_NODE_PORT_UPDATE_INFO, + 0, NULL, + &p->info); + p->info.change_mask = 0; + +done: + pw_thread_loop_unlock(c->context.loop); + + return res; +} + +SPA_EXPORT +int jack_port_get_aliases (const jack_port_t *port, char* const aliases[2]) +{ + struct object *o = (struct object *) port; + int res = 0; + + spa_return_val_if_fail(o != NULL, -EINVAL); + spa_return_val_if_fail(aliases != NULL, -EINVAL); + spa_return_val_if_fail(aliases[0] != NULL, -EINVAL); + spa_return_val_if_fail(aliases[1] != NULL, -EINVAL); + + 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++; + } + + return res; +} + +SPA_EXPORT +int jack_port_request_monitor (jack_port_t *port, int onoff) +{ + struct object *o = (struct object *) port; + + spa_return_val_if_fail(o != NULL, -EINVAL); + + 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; + + spa_return_val_if_fail(c != NULL, -EINVAL); + spa_return_val_if_fail(port_name != NULL, -EINVAL); + + pthread_mutex_lock(&c->context.lock); + p = find_port_by_name(c, port_name); + pthread_mutex_unlock(&c->context.lock); + + if (p == NULL) { + pw_log_error("%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; + + spa_return_val_if_fail(o != NULL, -EINVAL); + + 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; + spa_return_val_if_fail(o != NULL, -EINVAL); + return o->port.monitor_requests > 0; +} + +static void link_proxy_error(void *data, int seq, int res, const char *message) +{ + int *link_res = data; + *link_res = res; +} + +static const struct pw_proxy_events link_proxy_events = { + PW_VERSION_PROXY_EVENTS, + .error = link_proxy_error, +}; + +static int check_connect(struct client *c, struct object *src, struct object *dst) +{ + int src_self, dst_self, sum; + + if (c->self_connect_mode == SELF_CONNECT_ALLOW) + return 1; + + src_self = src->port.node_id == c->node_id ? 1 : 0; + dst_self = dst->port.node_id == c->node_id ? 1 : 0; + sum = src_self + dst_self; + /* check for no self connection first */ + if (sum == 0) + return 1; + + /* internal connection */ + if (sum == 2 && + (c->self_connect_mode == SELF_CONNECT_FAIL_EXT || + c->self_connect_mode == SELF_CONNECT_IGNORE_EXT)) + return 1; + + /* failure -> -1 */ + if (c->self_connect_mode < 0) + return -1; + + /* ignore -> 0 */ + return 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[6]; + struct pw_proxy *proxy; + struct spa_hook listener; + char val[4][16]; + const char *str; + int res, link_res = 0; + + spa_return_val_if_fail(c != NULL, EINVAL); + spa_return_val_if_fail(source_port != NULL, EINVAL); + spa_return_val_if_fail(destination_port != NULL, EINVAL); + + pw_log_info("%p: connect %s %s", client, source_port, destination_port); + + pw_thread_loop_lock(c->context.loop); + + src = find_port_by_name(c, source_port); + dst = find_port_by_name(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; + } + if ((res = check_connect(c, src, dst)) != 1) + 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, "true"); + if ((str = pw_properties_get(c->props, PW_KEY_NODE_PASSIVE)) != NULL && + pw_properties_parse_bool(str)) + items[props.n_items++] = SPA_DICT_ITEM_INIT(PW_KEY_LINK_PASSIVE, "true"); + + proxy = pw_core_create_object(c->core, + "link-factory", + PW_TYPE_INTERFACE_Link, + PW_VERSION_LINK, + &props, + 0); + if (proxy == NULL) { + res = -errno; + goto exit; + } + + spa_zero(listener); + pw_proxy_add_listener(proxy, &listener, &link_proxy_events, &link_res); + + res = do_sync(c); + + spa_hook_remove(&listener); + + if (link_res < 0) + res = link_res; + + pw_proxy_destroy(proxy); + + 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; + + spa_return_val_if_fail(c != NULL, -EINVAL); + spa_return_val_if_fail(source_port != NULL, -EINVAL); + spa_return_val_if_fail(destination_port != NULL, -EINVAL); + + pw_log_info("%p: disconnect %s %s", client, source_port, destination_port); + + pw_thread_loop_lock(c->context.loop); + + src = find_port_by_name(c, source_port); + dst = find_port_by_name(c, destination_port); + + pw_log_debug("%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 ((res = check_connect(c, src, dst)) != 1) + goto exit; + + if ((l = find_link(c, src->id, dst->id)) == NULL) { + res = -ENOENT; + goto exit; + } + + pw_registry_destroy(c->registry, 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; + + spa_return_val_if_fail(c != NULL, -EINVAL); + spa_return_val_if_fail(o != NULL, -EINVAL); + + pw_log_debug("%p: disconnect %p", client, port); + + pw_thread_loop_lock(c->context.loop); + + spa_list_for_each(l, &c->context.objects, link) { + if (l->type != INTERFACE_Link || l->removed) + continue; + if (l->port_link.src_serial == o->serial || + l->port_link.dst_serial == o->serial) { + pw_registry_destroy(c->registry, 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) +{ + spa_return_val_if_fail(client != NULL, 0); + spa_return_val_if_fail(port_type != NULL, 0); + + if (spa_streq(JACK_DEFAULT_AUDIO_TYPE, port_type)) + return jack_get_buffer_size(client) * sizeof(float); + else if (spa_streq(JACK_DEFAULT_MIDI_TYPE, port_type)) + return MAX_BUFFER_FRAMES * sizeof(float); + else if (spa_streq(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; + struct client *c; + jack_latency_range_t range = { frames, frames }; + + spa_return_if_fail(o != NULL); + c = o->client; + + pw_log_debug("%p: %s set latency %d", c, o->port.name, 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; + struct client *c; + jack_nframes_t nframes, rate; + int direction; + struct spa_latency_info *info; + + spa_return_if_fail(o != NULL); + if (o->type != INTERFACE_Port || o->client == NULL) + return; + c = o->client; + + if (mode == JackCaptureLatency) + direction = SPA_DIRECTION_OUTPUT; + else + direction = SPA_DIRECTION_INPUT; + + nframes = jack_get_buffer_size((jack_client_t*)c); + rate = jack_get_sample_rate((jack_client_t*)c); + info = &o->port.latency[direction]; + + range->min = (info->min_quantum * nframes) + + info->min_rate + (info->min_ns * rate) / SPA_NSEC_PER_SEC; + range->max = (info->max_quantum * nframes) + + info->max_rate + (info->max_ns * rate) / SPA_NSEC_PER_SEC; + + pw_log_debug("%p: %s get %d latency range %d %d", c, o->port.name, + mode, range->min, range->max); +} + +static int +do_port_update_latency(struct spa_loop *loop, + bool async, uint32_t seq, const void *data, size_t size, void *user_data) +{ + struct port *p = user_data; + port_update_latency(p); + return 0; +} + +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; + struct client *c; + enum spa_direction direction; + struct spa_latency_info *current, latency; + jack_nframes_t nframes; + struct port *p; + + spa_return_if_fail(o != NULL); + if (o->type != INTERFACE_Port || o->client == NULL) + return; + c = o->client; + + if (mode == JackCaptureLatency) + direction = SPA_DIRECTION_OUTPUT; + else + direction = SPA_DIRECTION_INPUT; + + pw_log_info("%p: %s set %d latency range %d %d", c, o->port.name, mode, range->min, range->max); + + latency = SPA_LATENCY_INFO(direction); + + nframes = jack_get_buffer_size((jack_client_t*)c); + + latency.min_rate = range->min; + if (latency.min_rate >= nframes) { + latency.min_quantum = latency.min_rate / nframes; + latency.min_rate %= nframes; + } + + latency.max_rate = range->max; + if (latency.max_rate >= nframes) { + latency.max_quantum = latency.max_rate / nframes; + latency.max_rate %= nframes; + } + + current = &o->port.latency[direction]; + + if ((p = o->port.port) == NULL) + return; + if (spa_latency_info_compare(current, &latency) == 0) + return; + + pw_log_info("%p: %s update %s latency %f-%f %d-%d %"PRIu64"-%"PRIu64, c, + o->port.name, + latency.direction == SPA_DIRECTION_INPUT ? "playback" : "capture", + latency.min_quantum, latency.max_quantum, + latency.min_rate, latency.max_rate, + latency.min_ns, latency.max_ns); + + *current = latency; + + pw_loop_invoke(c->context.l, do_port_update_latency, 0, + NULL, 0, false, p); +} + +static int +do_recompute_latencies(struct spa_loop *loop, + bool async, uint32_t seq, const void *data, size_t size, void *user_data) +{ + struct client *c = user_data; + pw_log_debug("start"); + recompute_latencies(c); + pw_log_debug("stop"); + return 0; +} + +SPA_EXPORT +int jack_recompute_total_latencies (jack_client_t *client) +{ + struct client *c = (struct client *) client; + pw_loop_invoke(c->context.l, do_recompute_latencies, 0, + NULL, 0, false, c); + return 0; +} + +static jack_nframes_t port_get_latency (jack_port_t *port) +{ + struct object *o = (struct object *) port; + jack_latency_range_t range = { 0, 0 }; + + spa_return_val_if_fail(o != NULL, 0); + + 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_latency (jack_port_t *port) +{ + return port_get_latency(port); +} + +SPA_EXPORT +jack_nframes_t jack_port_get_total_latency (jack_client_t *client, + jack_port_t *port) +{ + return port_get_latency(port); +} + +SPA_EXPORT +int jack_recompute_total_latency (jack_client_t *client, jack_port_t* port) +{ + pw_log_warn("%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; + struct client *c = (*o1)->client; + int res; + bool is_cap1, is_cap2, is_def1 = false, is_def2 = false; + + is_cap1 = ((*o1)->port.flags & JackPortIsOutput) == JackPortIsOutput && + !(*o1)->port.is_monitor; + is_cap2 = ((*o2)->port.flags & JackPortIsOutput) == JackPortIsOutput && + !(*o2)->port.is_monitor; + + if (c->metadata) { + struct object *ot1, *ot2; + + ot1 = (*o1)->port.node; + + if (is_cap1) + is_def1 = ot1 != NULL && spa_streq(ot1->node.node_name, + c->metadata->default_audio_source); + else if (!is_cap1) + is_def1 = ot1 != NULL && spa_streq(ot1->node.node_name, + c->metadata->default_audio_sink); + ot2 = (*o2)->port.node; + + if (is_cap2) + is_def2 = ot2 != NULL && spa_streq(ot2->node.node_name, + c->metadata->default_audio_source); + else if (!is_cap2) + is_def2 = ot2 != NULL && spa_streq(ot2->node.node_name, + c->metadata->default_audio_sink); + } + if ((*o1)->port.type_id != (*o2)->port.type_id) + res = (*o1)->port.type_id - (*o2)->port.type_id; + else if ((is_cap1 || is_cap2) && is_cap1 != is_cap2) + res = is_cap2 - is_cap1; + else if ((is_def1 || is_def2) && is_def1 != is_def2) + res = is_def2 - is_def1; + else if ((*o1)->port.priority != (*o2)->port.priority) + res = (*o2)->port.priority - (*o1)->port.priority; + else if ((res = (*o1)->port.node_id - (*o2)->port.node_id) == 0) { + if ((*o1)->port.is_monitor != (*o2)->port.is_monitor) + res = (*o1)->port.is_monitor - (*o2)->port.is_monitor; + if (res == 0) + res = (*o1)->port.system_id - (*o2)->port.system_id; + if (res == 0) + res = (*o1)->serial - (*o2)->serial; + } + pw_log_debug("port %s<->%s type:%d<->%d def:%d<->%d prio:%d<->%d id:%d<->%d res:%d", + (*o1)->port.name, (*o2)->port.name, + (*o1)->port.type_id, (*o2)->port.type_id, + is_def1, is_def2, + (*o1)->port.priority, (*o2)->port.priority, + (*o1)->serial, (*o2)->serial, res); + return res; +} + +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 pw_array tmp; + const char *str; + uint32_t i, count; + int r; + regex_t port_regex, type_regex; + + spa_return_val_if_fail(c != NULL, NULL); + + str = getenv("PIPEWIRE_NODE"); + + if (port_name_pattern && port_name_pattern[0]) { + if ((r = regcomp(&port_regex, port_name_pattern, REG_EXTENDED | REG_NOSUB)) != 0) { + pw_log_error("cant compile regex %s: %d", port_name_pattern, r); + return NULL; + } + } + if (type_name_pattern && type_name_pattern[0]) { + if ((r = regcomp(&type_regex, type_name_pattern, REG_EXTENDED | REG_NOSUB)) != 0) { + pw_log_error("cant compile regex %s: %d", type_name_pattern, r); + return NULL; + } + } + + pw_log_debug("%p: ports target:%s name:\"%s\" type:\"%s\" flags:%08lx", c, str, + port_name_pattern, type_name_pattern, flags); + + pthread_mutex_lock(&c->context.lock); + pw_array_init(&tmp, sizeof(void*) * 32); + count = 0; + + spa_list_for_each(o, &c->context.objects, link) { + if (o->type != INTERFACE_Port || o->removed) + continue; + pw_log_debug("%p: check port type:%d flags:%08lx name:\"%s\"", c, + o->port.type_id, o->port.flags, o->port.name); + if (o->port.type_id > TYPE_ID_VIDEO) + continue; + if (!SPA_FLAG_IS_SET(o->port.flags, flags)) + continue; + if (str != NULL && o->port.node != NULL) { + if (!spa_strstartswith(o->port.name, str) && + o->port.node->serial != atoll(str)) + continue; + } + + if (port_name_pattern && port_name_pattern[0]) { + bool match; + match = regexec(&port_regex, o->port.name, 0, NULL, 0) == 0; + if (!match && is_port_default(c, o)) + match = regexec(&port_regex, o->port.system, 0, NULL, 0) == 0; + if (!match) + 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("%p: port \"%s\" prio:%d matches (%d)", + c, o->port.name, o->port.priority, count); + + pw_array_add_ptr(&tmp, o); + count++; + } + pthread_mutex_unlock(&c->context.lock); + + if (count > 0) { + qsort(tmp.data, count, sizeof(struct object *), port_compare_func); + pw_array_add_ptr(&tmp, NULL); + res = tmp.data; + for (i = 0; i < count; i++) + res[i] = port_name((struct object*)res[i]); + } else { + pw_array_clear(&tmp); + res = NULL; + } + + 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; + + spa_return_val_if_fail(c != NULL, NULL); + + pthread_mutex_lock(&c->context.lock); + res = find_port_by_name(c, port_name); + pthread_mutex_unlock(&c->context.lock); + + if (res == NULL) + pw_log_info("%p: port \"%s\" not found", c, port_name); + + 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; + + spa_return_val_if_fail(c != NULL, NULL); + + pthread_mutex_lock(&c->context.lock); + res = find_by_serial(c, port_id); + if (res && res->type != INTERFACE_Port) + res = NULL; + pw_log_debug("%p: port %d -> %p", c, port_id, res); + pthread_mutex_unlock(&c->context.lock); + + if (res == NULL) + pw_log_info("%p: port %d not found", c, port_id); + + 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; + struct timespec ts; + uint64_t diff; + + spa_return_val_if_fail(c != NULL, 0); + + if (SPA_UNLIKELY((pos = c->rt.position) == NULL)) + return 0; + + clock_gettime(CLOCK_MONOTONIC, &ts); + diff = SPA_TIMESPEC_TO_NSEC(&ts) - pos->clock.nsec; + return (jack_nframes_t) floor(((double)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; + + spa_return_val_if_fail(c != NULL, 0); + + if (SPA_UNLIKELY((pos = c->rt.position) == 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; + + spa_return_val_if_fail(c != NULL, -EINVAL); + + if (SPA_UNLIKELY((pos = c->rt.position) == NULL)) + return -EIO; + + *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("%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; + double df; + + spa_return_val_if_fail(c != NULL, -EINVAL); + + if (SPA_UNLIKELY((pos = c->rt.position) == 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; + double du; + + spa_return_val_if_fail(c != NULL, -EINVAL); + + if (SPA_UNLIKELY((pos = c->rt.position) == 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(void) +{ + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return SPA_TIMESPEC_TO_USEC(&ts); +} + +SPA_EXPORT +void default_jack_error_callback(const char *desc) +{ + pw_log_error("pw jack error: %s",desc); +} + +SPA_EXPORT +void silent_jack_error_callback(const char *desc) +{ +} + +SPA_EXPORT +void (*jack_error_callback)(const char *msg); + +SPA_EXPORT +void jack_set_error_function (void (*func)(const char *)) +{ + jack_error_callback = (func == NULL) ? &default_jack_error_callback : func; +} + +SPA_EXPORT +void default_jack_info_callback(const char *desc) +{ + pw_log_info("pw jack info: %s", desc); +} + +SPA_EXPORT +void silent_jack_info_callback(const char *desc) +{ +} + +SPA_EXPORT +void (*jack_info_callback)(const char *msg); + + +SPA_EXPORT +void jack_set_info_function (void (*func)(const char *)) +{ + jack_info_callback = (func == NULL) ? &default_jack_info_callback : func; +} + +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; + + spa_return_val_if_fail(c != NULL, -EINVAL); + + if ((a = c->driver_activation) == 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 = 0; + struct client *c = (struct client *) client; + + spa_return_val_if_fail(c != NULL, -EINVAL); + + pw_thread_loop_lock(c->context.loop); + + c->sync_callback = sync_callback; + c->sync_arg = arg; + + if ((res = do_activate(c)) < 0) + goto done; + + c->activation->pending_sync = true; +done: + pw_thread_loop_unlock(c->context.loop); + + return res; +} + +SPA_EXPORT +int jack_set_sync_timeout (jack_client_t *client, + jack_time_t timeout) +{ + int res = 0; + struct client *c = (struct client *) client; + struct pw_node_activation *a; + + spa_return_val_if_fail(c != NULL, -EINVAL); + + pw_thread_loop_lock(c->context.loop); + + if ((a = c->activation) == NULL) + res = -EIO; + else + a->sync_timeout = timeout; + pw_thread_loop_unlock(c->context.loop); + + return res; +} + +SPA_EXPORT +int jack_set_timebase_callback (jack_client_t *client, + int conditional, + JackTimebaseCallback timebase_callback, + void *arg) +{ + int res = 0; + struct client *c = (struct client *) client; + + spa_return_val_if_fail(c != NULL, -EINVAL); + spa_return_val_if_fail(timebase_callback != NULL, -EINVAL); + + pw_thread_loop_lock(c->context.loop); + + c->timebase_callback = timebase_callback; + c->timebase_arg = arg; + c->timeowner_conditional = conditional; + install_timeowner(c); + + pw_log_debug("%p: timebase set id:%u", c, c->node_id); + + if ((res = do_activate(c)) < 0) + goto done; + + c->activation->pending_new_pos = true; +done: + pw_thread_loop_unlock(c->context.loop); + + return res; +} + +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; + jack_transport_state_t jack_state = JackTransportStopped; + + spa_return_val_if_fail(c != NULL, JackTransportStopped); + + if (SPA_LIKELY((a = c->rt.driver_activation) != NULL)) { + jack_state = position_to_jack(a, pos); + } else if ((a = c->driver_activation) != NULL) { + jack_state = position_to_jack(a, pos); + } else if (pos != NULL) { + memset(pos, 0, sizeof(jack_position_t)); + pos->frame_rate = jack_get_sample_rate((jack_client_t*)client); + } + 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; + struct spa_io_position *pos; + struct spa_io_segment *seg; + uint64_t running; + + spa_return_val_if_fail(c != NULL, -EINVAL); + + if (SPA_UNLIKELY((a = c->rt.driver_activation) == NULL)) + return -EIO; + + 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((((double) 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, *na; + + spa_return_val_if_fail(c != NULL, -EINVAL); + + a = c->rt.driver_activation; + na = c->activation; + if (!a || !na) + return -EIO; + + if (pos->valid & ~(JackPositionBBT|JackPositionTimecode)) + return -EINVAL; + + pw_log_debug("frame:%u", pos->frame); + spa_zero(na->reposition); + 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->rt.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; + spa_return_if_fail(c != NULL); + update_command(c, PW_NODE_ACTIVATION_COMMAND_START); +} + +SPA_EXPORT +void jack_transport_stop (jack_client_t *client) +{ + struct client *c = (struct client *) client; + spa_return_if_fail(c != NULL); + 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("%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("%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; + + spa_return_val_if_fail(c != NULL, -EINVAL); + + if (c->active) { + pw_log_error("%p: can't set callback on active client", c); + return -EIO; + } + pw_log_warn("%p: not implemented", client); + return -ENOTSUP; +} + +SPA_EXPORT +int jack_session_reply (jack_client_t *client, + jack_session_event_t *event) +{ + pw_log_warn("%p: not implemented", client); + return -ENOTSUP; +} + + +SPA_EXPORT +void jack_session_event_free (jack_session_event_t *event) +{ + if (event) { + free((void *)event->session_dir); + free((void *)event->client_uuid); + free(event->command_line); + free(event); + } +} + +SPA_EXPORT +char *jack_client_get_uuid (jack_client_t *client) +{ + struct client *c = (struct client *) client; + + spa_return_val_if_fail(c != NULL, NULL); + + return spa_aprintf("%"PRIu64, client_make_uuid(c->serial, false)); +} + +SPA_EXPORT +jack_session_command_t *jack_session_notify ( + jack_client_t* client, + const char *target, + jack_session_event_type_t type, + const char *path) +{ + struct client *c = (struct client *) client; + jack_session_command_t *cmds; + spa_return_val_if_fail(c != NULL, NULL); + pw_log_warn("not implemented"); + cmds = calloc(1, sizeof(jack_session_command_t)); + return cmds; +} + +SPA_EXPORT +void jack_session_commands_free (jack_session_command_t *cmds) +{ + int i; + if (cmds == NULL) + return; + + for (i = 0; cmds[i].uuid != NULL; i++) { + free((char*)cmds[i].client_name); + free((char*)cmds[i].command); + free((char*)cmds[i].uuid); + } + free(cmds); +} + +SPA_EXPORT +int jack_reserve_client_name (jack_client_t *client, + const char *name, + const char *uuid) +{ + struct client *c = (struct client *) client; + spa_return_val_if_fail(c != NULL, -1); + pw_log_warn("not implemented"); + return 0; +} + +SPA_EXPORT +int jack_client_has_session_callback (jack_client_t *client, const char *client_name) +{ + struct client *c = (struct client *) client; + spa_return_val_if_fail(c != NULL, -1); + return 0; +} + + +SPA_EXPORT +int jack_client_real_time_priority (jack_client_t * client) +{ + return jack_client_max_real_time_priority(client) - 5; +} + +SPA_EXPORT +int jack_client_max_real_time_priority (jack_client_t *client) +{ + struct client *c = (struct client *) client; + int min, max; + + spa_return_val_if_fail(c != NULL, -1); + + spa_thread_utils_get_rt_range(&c->context.thread_utils, NULL, &min, &max); + return SPA_MIN(max, c->rt_max) - 1; +} + +SPA_EXPORT +int jack_acquire_real_time_scheduling (jack_native_thread_t thread, int priority) +{ + struct spa_thread *t = (struct spa_thread*)thread; + pw_log_info("acquire %p", t); + spa_return_val_if_fail(globals.thread_utils != NULL, -1); + spa_return_val_if_fail(t != NULL, -1); + return spa_thread_utils_acquire_rt(globals.thread_utils, t, priority); +} + +SPA_EXPORT +int jack_drop_real_time_scheduling (jack_native_thread_t thread) +{ + struct spa_thread *t = (struct spa_thread*)thread; + pw_log_info("drop %p", t); + spa_return_val_if_fail(globals.thread_utils != NULL, -1); + spa_return_val_if_fail(t != NULL, -1); + return spa_thread_utils_drop_rt(globals.thread_utils, t); +} + +/** + * 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) +{ + struct client *c = (struct client *) client; + int res = 0; + struct spa_thread *thr; + + spa_return_val_if_fail(client != NULL, -EINVAL); + spa_return_val_if_fail(thread != NULL, -EINVAL); + spa_return_val_if_fail(start_routine != NULL, -EINVAL); + + pw_log_info("client %p: create thread rt:%d prio:%d", client, realtime, priority); + + thr = spa_thread_utils_create(&c->context.thread_utils, NULL, start_routine, arg); + if (thr == NULL) + res = -errno; + *thread = (pthread_t)thr; + + if (res != 0) { + pw_log_warn("client %p: create RT thread failed: %s", + client, strerror(res)); + } else if (realtime) { + /* Try to acquire RT scheduling, we don't fail here but the + * function will emit a warning. Real JACK fails here. */ + jack_acquire_real_time_scheduling(*thread, priority); + } + return res; +} + +SPA_EXPORT +int jack_client_stop_thread(jack_client_t* client, jack_native_thread_t thread) +{ + struct client *c = (struct client *) client; + void* status; + + if (thread == (jack_native_thread_t)NULL) + return -EINVAL; + + spa_return_val_if_fail(client != NULL, -EINVAL); + + pw_log_debug("join thread %lu", thread); + spa_thread_utils_join(&c->context.thread_utils, (struct spa_thread*)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) +{ + struct client *c = (struct client *) client; + void* status; + + if (thread == (jack_native_thread_t)NULL) + return -EINVAL; + + spa_return_val_if_fail(client != NULL, -EINVAL); + + pw_log_debug("cancel thread %lu", thread); + pthread_cancel(thread); + pw_log_debug("join thread %lu", thread); + spa_thread_utils_join(&c->context.thread_utils, (struct spa_thread*)thread, &status); + pw_log_debug("stopped thread %lu", thread); + return 0; +} + +SPA_EXPORT +void jack_set_thread_creator (jack_thread_creator_t creator) +{ + globals.creator = creator; +} + +static inline uint8_t * midi_event_data (void* port_buffer, + const struct midi_event* event) +{ + if (SPA_LIKELY(event->size <= MIDI_INLINE_MAX)) + return (uint8_t *)event->inline_data; + else + return SPA_PTROFF(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; + if (mb == NULL || mb->magic != MIDI_BUFFER_MAGIC) + return 0; + 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_PTROFF(mb, sizeof(*mb), struct midi_event); + spa_return_val_if_fail(mb != NULL, -EINVAL); + spa_return_val_if_fail(ev != NULL, -EINVAL); + if (event_index >= mb->event_count) + return -ENOBUFS; + 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; + spa_return_if_fail(mb != NULL); + 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; + + spa_return_val_if_fail(mb != NULL, 0); + + 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 (SPA_UNLIKELY(used_size > buffer_size)) { + return 0; + } else if (SPA_LIKELY((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_PTROFF(mb, sizeof(*mb), struct midi_event); + size_t buffer_size; + + spa_return_val_if_fail(mb != NULL, NULL); + + buffer_size = mb->buffer_size; + + if (SPA_UNLIKELY(time >= mb->nframes)) { + pw_log_warn("midi %p: time:%d frames:%d", port_buffer, time, mb->nframes); + goto failed; + } + + if (SPA_UNLIKELY(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 (SPA_UNLIKELY(data_size <= 0)) { + pw_log_warn("midi %p: data_size:%zd", port_buffer, data_size); + goto failed; // return NULL? + } else if (SPA_UNLIKELY(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 (SPA_LIKELY(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_PTROFF(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 (SPA_UNLIKELY(retbuf == NULL)) + return -ENOBUFS; + memcpy (retbuf, data, data_size); + return 0; +} + +SPA_EXPORT +uint32_t jack_midi_get_lost_event_count(void *port_buffer) +{ + struct midi_buffer *mb = port_buffer; + spa_return_val_if_fail(mb != NULL, 0); + return mb->lost_events; +} + +/** extensions */ + +SPA_EXPORT +int jack_get_video_image_size(jack_client_t *client, jack_image_size_t *size) +{ + struct client *c = (struct client *) client; + struct pw_node_activation *a; + + spa_return_val_if_fail(c != NULL, 0); + + a = c->rt.driver_activation; + if (SPA_UNLIKELY(a == NULL)) + a = c->activation; + if (SPA_UNLIKELY(a == NULL)) + return -EIO; + + if (SPA_UNLIKELY(!(a->position.video.flags & SPA_IO_VIDEO_SIZE_VALID))) + return -EIO; + + size->width = a->position.video.size.width; + size->height = a->position.video.size.height; + size->stride = a->position.video.stride; + size->flags = 0; + return size->stride * size->height; +} + + +static void reg(void) __attribute__ ((constructor)); +static void reg(void) +{ + pw_init(NULL, NULL); + PW_LOG_TOPIC_INIT(jack_log_topic); + pthread_mutex_init(&globals.lock, NULL); + pw_array_init(&globals.descriptions, 16); + spa_list_init(&globals.free_objects); +} diff --git a/pipewire-jack/src/pw-jack.in b/pipewire-jack/src/pw-jack.in new file mode 100755 index 0000000..f3dba1d --- /dev/null +++ b/pipewire-jack/src/pw-jack.in @@ -0,0 +1,78 @@ +#!/bin/sh + +# This file is part of PipeWire. +# +# Copyright © 2020 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. +# + +DEFAULT_SAMPLERATE=48000 + +while getopts 'hr:vs:p:' param ; do + case $param in + r) + PIPEWIRE_REMOTE="$OPTARG" + export PIPEWIRE_REMOTE + ;; + v) + if [ -z "$PIPEWIRE_DEBUG" ]; then + PIPEWIRE_DEBUG=3 + else + PIPEWIRE_DEBUG=$(( PIPEWIRE_DEBUG + 1 )) + fi + export PIPEWIRE_DEBUG + ;; + s) + SAMPLERATE="$OPTARG" + ;; + p) + PERIOD="$OPTARG" + ;; + *) + echo "$0 - run JACK applications on PipeWire" + echo " " + echo "$0 [options] application [arguments]" + echo " " + echo "options:" + echo " -h show brief help" + echo " -r <remote> remote daemon name" + echo " -v verbose debug info" + echo " -s samplerate (default \"$DEFAULT_SAMPLERATE\")" + echo " -p period in samples" + exit 0 + ;; + esac +done + +shift $(( OPTIND - 1 )) + +if [ -n "$PERIOD" ]; then + if [ -n "$SAMPLERATE" ]; then + PIPEWIRE_QUANTUM="$PERIOD/$SAMPLERATE" + else + PIPEWIRE_QUANTUM="$PERIOD/$DEFAULT_SAMPLERATE" + fi + export PIPEWIRE_QUANTUM +fi +LD_LIBRARY_PATH='@LIBJACK_PATH@'"${LD_LIBRARY_PATH+":$LD_LIBRARY_PATH"}" +export LD_LIBRARY_PATH + +exec "$@" diff --git a/pipewire-jack/src/ringbuffer.c b/pipewire-jack/src/ringbuffer.c new file mode 100644 index 0000000..887c7dc --- /dev/null +++ b/pipewire-jack/src/ringbuffer.c @@ -0,0 +1,302 @@ +/* PipeWire + * + * Copyright © 2018 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 "config.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <spa/utils/defs.h> + +#include <jack/ringbuffer.h> + +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 0000000..25ffb3e --- /dev/null +++ b/pipewire-jack/src/statistics.c @@ -0,0 +1,66 @@ +/* PipeWire + * + * Copyright © 2018 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 <jack/statistics.h> + +SPA_EXPORT +float jack_get_max_delayed_usecs (jack_client_t *client) +{ + struct client *c = (struct client *) client; + float res = 0.0f; + + spa_return_val_if_fail(c != NULL, 0.0); + + if (c->driver_activation) + res = (float)c->driver_activation->max_delay / SPA_USEC_PER_SEC; + + pw_log_trace("%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; + + spa_return_val_if_fail(c != NULL, 0.0); + + if (c->driver_activation) + res = (float)c->driver_activation->xrun_delay / SPA_USEC_PER_SEC; + + pw_log_trace("%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; + + spa_return_if_fail(c != NULL); + + 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 0000000..8584e11 --- /dev/null +++ b/pipewire-jack/src/uuid.c @@ -0,0 +1,111 @@ +/* PipeWire + * + * Copyright © 2018 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 "config.h" + +#include <stdio.h> +#include <unistd.h> +#include <sys/mman.h> + +#include <jack/uuid.h> + +#include <pipewire/pipewire.h> + +SPA_EXPORT +jack_uuid_t jack_client_uuid_generate (void) +{ + 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 & 0xffffff) - 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) +{ + spa_return_if_fail(dst != NULL); + *dst = src; +} + +SPA_EXPORT +void jack_uuid_clear (jack_uuid_t *id) +{ + spa_return_if_fail(id != NULL); + *id = 0; +} + +SPA_EXPORT +int jack_uuid_parse (const char *buf, jack_uuid_t *id) +{ + spa_return_val_if_fail(buf != NULL, -EINVAL); + spa_return_val_if_fail(id != NULL, -EINVAL); + + 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]) +{ + spa_return_if_fail(buf != NULL); + snprintf (buf, JACK_UUID_STRING_SIZE, "%" PRIu64, id); +} + +SPA_EXPORT +int jack_uuid_empty (jack_uuid_t id) +{ + return id == 0; +} |