diff options
Diffstat (limited to '')
92 files changed, 34970 insertions, 0 deletions
diff --git a/src/pipewire/array.h b/src/pipewire/array.h new file mode 100644 index 0000000..44f31a8 --- /dev/null +++ b/src/pipewire/array.h @@ -0,0 +1,176 @@ +/* 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. + */ + +#ifndef PIPEWIRE_ARRAY_H +#define PIPEWIRE_ARRAY_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <errno.h> + +#include <spa/utils/defs.h> + +/** \defgroup pw_array Array + * + * \brief An array object + * + * The array is a dynamically resizable data structure that can + * hold items of the same size. + */ + +/** + * \addtogroup pw_array + * \{ + */ +struct pw_array { + void *data; /**< pointer to array data */ + size_t size; /**< length of array in bytes */ + size_t alloc; /**< number of allocated memory in \a data */ + size_t extend; /**< number of bytes to extend with */ +}; + +#define PW_ARRAY_INIT(extend) ((struct pw_array) { NULL, 0, 0, (extend) }) + +#define pw_array_get_len_s(a,s) ((a)->size / (s)) +#define pw_array_get_unchecked_s(a,idx,s,t) SPA_PTROFF((a)->data,(idx)*(s),t) +#define pw_array_check_index_s(a,idx,s) ((idx) < pw_array_get_len_s(a,s)) + +/** Get the number of items of type \a t in array */ +#define pw_array_get_len(a,t) pw_array_get_len_s(a,sizeof(t)) +/** Get the item with index \a idx and type \a t from array */ +#define pw_array_get_unchecked(a,idx,t) pw_array_get_unchecked_s(a,idx,sizeof(t),t) +/** Check if an item with index \a idx and type \a t exist in array */ +#define pw_array_check_index(a,idx,t) pw_array_check_index_s(a,idx,sizeof(t)) + +#define pw_array_first(a) ((a)->data) +#define pw_array_end(a) SPA_PTROFF((a)->data, (a)->size, void) +#define pw_array_check(a,p) (SPA_PTROFF(p,sizeof(*(p)),void) <= pw_array_end(a)) + +#define pw_array_for_each(pos, array) \ + for ((pos) = (__typeof__(pos)) pw_array_first(array); \ + pw_array_check(array, pos); \ + (pos)++) + +#define pw_array_consume(pos, array) \ + for ((pos) = (__typeof__(pos)) pw_array_first(array); \ + pw_array_check(array, pos); \ + (pos) = (__typeof__(pos)) pw_array_first(array)) + +#define pw_array_remove(a,p) \ +({ \ + (a)->size -= sizeof(*(p)); \ + memmove(p, SPA_PTROFF((p), sizeof(*(p)), void), \ + SPA_PTRDIFF(pw_array_end(a),(p))); \ +}) + +/** Initialize the array with given extend */ +static inline void pw_array_init(struct pw_array *arr, size_t extend) +{ + arr->data = NULL; + arr->size = arr->alloc = 0; + arr->extend = extend; +} + +/** Clear the array */ +static inline void pw_array_clear(struct pw_array *arr) +{ + free(arr->data); + pw_array_init(arr, arr->extend); +} + +/** Reset the array */ +static inline void pw_array_reset(struct pw_array *arr) +{ + arr->size = 0; +} + +/** Make sure \a size bytes can be added to the array */ +static inline int pw_array_ensure_size(struct pw_array *arr, size_t size) +{ + size_t alloc, need; + + alloc = arr->alloc; + need = arr->size + size; + + if (SPA_UNLIKELY(alloc < need)) { + void *data; + alloc = SPA_MAX(alloc, arr->extend); + spa_assert(alloc != 0); /* forgot pw_array_init */ + while (alloc < need) + alloc *= 2; + if (SPA_UNLIKELY((data = realloc(arr->data, alloc)) == NULL)) + return -errno; + arr->data = data; + arr->alloc = alloc; + } + return 0; +} + +/** Add \a ref size bytes to \a arr. A pointer to memory that can + * hold at least \a size bytes is returned */ +static inline void *pw_array_add(struct pw_array *arr, size_t size) +{ + void *p; + + if (pw_array_ensure_size(arr, size) < 0) + return NULL; + + p = SPA_PTROFF(arr->data, arr->size, void); + arr->size += size; + + return p; +} + +/** Add \a ref size bytes to \a arr. When there is not enough memory to + * hold \a size bytes, NULL is returned */ +static inline void *pw_array_add_fixed(struct pw_array *arr, size_t size) +{ + void *p; + + if (SPA_UNLIKELY(arr->alloc < arr->size + size)) { + errno = ENOSPC; + return NULL; + } + + p = SPA_PTROFF(arr->data, arr->size, void); + arr->size += size; + + return p; +} + +/** Add a pointer to array */ +#define pw_array_add_ptr(a,p) \ + *((void**) pw_array_add(a, sizeof(void*))) = (p) + +/** + * \} + */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* PIPEWIRE_ARRAY_H */ diff --git a/src/pipewire/buffers.c b/src/pipewire/buffers.c new file mode 100644 index 0000000..c3cd7d8 --- /dev/null +++ b/src/pipewire/buffers.c @@ -0,0 +1,368 @@ +/* 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 <spa/node/utils.h> +#include <spa/pod/parser.h> +#include <spa/param/param.h> +#include <spa/buffer/alloc.h> + +#include "pipewire/keys.h" +#include "pipewire/private.h" + +#include "buffers.h" + +PW_LOG_TOPIC_EXTERN(log_buffers); +#define PW_LOG_TOPIC_DEFAULT log_buffers + +#define MAX_ALIGN 32 +#define MAX_BLOCKS 64u + +struct port { + struct spa_node *node; + enum spa_direction direction; + uint32_t port_id; +}; + +/* Allocate an array of buffers that can be shared */ +static int alloc_buffers(struct pw_mempool *pool, + uint32_t n_buffers, + uint32_t n_params, + struct spa_pod **params, + uint32_t n_datas, + uint32_t *data_sizes, + int32_t *data_strides, + uint32_t *data_aligns, + uint32_t *data_types, + uint32_t flags, + struct pw_buffers *allocation) +{ + struct spa_buffer **buffers; + void *skel, *data; + uint32_t i; + uint32_t n_metas; + struct spa_meta *metas; + struct spa_data *datas; + struct pw_memblock *m; + struct spa_buffer_alloc_info info = { 0, }; + + if (!SPA_FLAG_IS_SET(flags, PW_BUFFERS_FLAG_SHARED)) + SPA_FLAG_SET(info.flags, SPA_BUFFER_ALLOC_FLAG_INLINE_ALL); + + n_metas = 0; + + metas = alloca(sizeof(struct spa_meta) * n_params); + datas = alloca(sizeof(struct spa_data) * n_datas); + + /* collect metadata */ + for (i = 0; i < n_params; i++) { + if (spa_pod_is_object_type (params[i], SPA_TYPE_OBJECT_ParamMeta)) { + uint32_t type, size; + + if (spa_pod_parse_object(params[i], + SPA_TYPE_OBJECT_ParamMeta, NULL, + SPA_PARAM_META_type, SPA_POD_Id(&type), + SPA_PARAM_META_size, SPA_POD_Int(&size)) < 0) + continue; + + pw_log_debug("%p: enable meta %d %d", allocation, type, size); + + metas[n_metas].type = type; + metas[n_metas].size = size; + n_metas++; + } + } + + for (i = 0; i < n_datas; i++) { + struct spa_data *d = &datas[i]; + + spa_zero(*d); + if (data_sizes[i] > 0) { + /* we allocate memory */ + d->type = SPA_DATA_MemPtr; + d->maxsize = data_sizes[i]; + SPA_FLAG_SET(d->flags, SPA_DATA_FLAG_READWRITE); + } else { + /* client allocates memory. Set the mask of possible + * types in the type field */ + d->type = data_types[i]; + d->maxsize = 0; + } + if (SPA_FLAG_IS_SET(flags, PW_BUFFERS_FLAG_DYNAMIC)) + SPA_FLAG_SET(d->flags, SPA_DATA_FLAG_DYNAMIC); + } + + spa_buffer_alloc_fill_info(&info, n_metas, metas, n_datas, datas, data_aligns); + + buffers = calloc(1, info.max_align + n_buffers * (sizeof(struct spa_buffer *) + info.skel_size)); + if (buffers == NULL) + return -errno; + + skel = SPA_PTROFF(buffers, n_buffers * sizeof(struct spa_buffer *), void); + skel = SPA_PTR_ALIGN(skel, info.max_align, void); + + if (SPA_FLAG_IS_SET(flags, PW_BUFFERS_FLAG_SHARED)) { + /* pointer to buffer structures */ + m = pw_mempool_alloc(pool, + PW_MEMBLOCK_FLAG_READWRITE | + PW_MEMBLOCK_FLAG_SEAL | + PW_MEMBLOCK_FLAG_MAP, + SPA_DATA_MemFd, + n_buffers * info.mem_size); + if (m == NULL) { + free(buffers); + return -errno; + } + + data = m->map->ptr; + } else { + m = NULL; + data = NULL; + } + + pw_log_debug("%p: layout buffers skel:%p data:%p buffers:%p", + allocation, skel, data, buffers); + spa_buffer_alloc_layout_array(&info, n_buffers, buffers, skel, data); + + allocation->mem = m; + allocation->n_buffers = n_buffers; + allocation->buffers = buffers; + allocation->flags = flags; + + return 0; +} + +static int +param_filter(struct pw_buffers *this, + struct port *in_port, + struct port *out_port, + uint32_t id, + struct spa_pod_builder *result) +{ + uint8_t ibuf[4096]; + struct spa_pod_builder ib = { 0 }; + struct spa_pod *oparam, *iparam; + uint32_t iidx, oidx; + int in_res = -EIO, out_res = -EIO, num = 0; + + for (iidx = 0;;) { + spa_pod_builder_init(&ib, ibuf, sizeof(ibuf)); + pw_log_debug("%p: input param %d id:%d", this, iidx, id); + in_res = spa_node_port_enum_params_sync(in_port->node, + in_port->direction, in_port->port_id, + id, &iidx, NULL, &iparam, &ib); + + if (in_res < 1) { + /* in_res == -ENOENT : unknown parameter, assume NULL and we will + * exit the loop below. + * in_res < 1 : some error or no data, exit now + */ + if (in_res == -ENOENT) + iparam = NULL; + else + break; + } + + pw_log_pod(SPA_LOG_LEVEL_DEBUG, iparam); + + for (oidx = 0;;) { + pw_log_debug("%p: output param %d id:%d", this, oidx, id); + out_res = spa_node_port_enum_params_sync(out_port->node, + out_port->direction, out_port->port_id, + id, &oidx, iparam, &oparam, result); + + /* out_res < 1 : no value or error, exit now */ + if (out_res < 1) + break; + + pw_log_pod(SPA_LOG_LEVEL_DEBUG, oparam); + num++; + } + if (out_res == -ENOENT && iparam) { + /* no output param known but we have an input param, + * use that one */ + spa_pod_builder_raw_padded(result, iparam, SPA_POD_SIZE(iparam)); + num++; + } + /* no more input values, exit */ + if (in_res < 1) + break; + } + if (num == 0) { + if (out_res == -ENOENT && in_res == -ENOENT) + return 0; + if (in_res < 0) + return in_res; + if (out_res < 0) + return out_res; + return -EINVAL; + } + return num; +} + +static struct spa_pod *find_param(struct spa_pod **params, uint32_t n_params, uint32_t type) +{ + uint32_t i; + + for (i = 0; i < n_params; i++) { + if (spa_pod_is_object_type(params[i], type)) + return params[i]; + } + return NULL; +} + +SPA_EXPORT +int pw_buffers_negotiate(struct pw_context *context, uint32_t flags, + struct spa_node *outnode, uint32_t out_port_id, + struct spa_node *innode, uint32_t in_port_id, + struct pw_buffers *result) +{ + struct spa_pod **params, *param; + uint8_t buffer[4096]; + struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); + uint32_t i, offset, n_params; + uint32_t max_buffers, blocks; + size_t minsize, stride, align; + uint32_t *data_sizes; + int32_t *data_strides; + uint32_t *data_aligns; + uint32_t types, *data_types; + struct port output = { outnode, SPA_DIRECTION_OUTPUT, out_port_id }; + struct port input = { innode, SPA_DIRECTION_INPUT, in_port_id }; + int res; + + if (flags & PW_BUFFERS_FLAG_IN_PRIORITY) { + struct port tmp = output; + output = input; + input = tmp; + } + + res = param_filter(result, &input, &output, SPA_PARAM_Buffers, &b); + if (res < 0) { + pw_context_debug_port_params(context, input.node, input.direction, + input.port_id, SPA_PARAM_Buffers, res, + "input param"); + pw_context_debug_port_params(context, output.node, output.direction, + output.port_id, SPA_PARAM_Buffers, res, + "output param"); + return res; + } + n_params = res; + if ((res = param_filter(result, &input, &output, SPA_PARAM_Meta, &b)) > 0) + n_params += res; + + params = alloca(n_params * sizeof(struct spa_pod *)); + for (i = 0, offset = 0; i < n_params; i++) { + params[i] = SPA_PTROFF(buffer, offset, struct spa_pod); + spa_pod_fixate(params[i]); + pw_log_debug("%p: fixated param %d:", result, i); + pw_log_pod(SPA_LOG_LEVEL_DEBUG, params[i]); + offset += SPA_ROUND_UP_N(SPA_POD_SIZE(params[i]), 8); + } + + max_buffers = context->settings.link_max_buffers; + + align = pw_properties_get_uint32(context->properties, PW_KEY_CPU_MAX_ALIGN, MAX_ALIGN); + + minsize = stride = 0; + types = SPA_ID_INVALID; /* bitmask of allowed types */ + blocks = 1; + + param = find_param(params, n_params, SPA_TYPE_OBJECT_ParamBuffers); + if (param) { + uint32_t qmax_buffers = max_buffers, + qminsize = minsize, qstride = stride, qalign = align; + uint32_t qtypes = types, qblocks = blocks; + + spa_pod_parse_object(param, + SPA_TYPE_OBJECT_ParamBuffers, NULL, + SPA_PARAM_BUFFERS_buffers, SPA_POD_OPT_Int(&qmax_buffers), + SPA_PARAM_BUFFERS_blocks, SPA_POD_OPT_Int(&qblocks), + SPA_PARAM_BUFFERS_size, SPA_POD_OPT_Int(&qminsize), + SPA_PARAM_BUFFERS_stride, SPA_POD_OPT_Int(&qstride), + SPA_PARAM_BUFFERS_align, SPA_POD_OPT_Int(&qalign), + SPA_PARAM_BUFFERS_dataType, SPA_POD_OPT_Int(&qtypes)); + + max_buffers = + qmax_buffers == 0 ? max_buffers : SPA_MIN(qmax_buffers, + max_buffers); + blocks = SPA_CLAMP(qblocks, blocks, MAX_BLOCKS); + minsize = SPA_MAX(minsize, qminsize); + stride = SPA_MAX(stride, qstride); + align = SPA_MAX(align, qalign); + types = qtypes; + + pw_log_debug("%p: %d %d %d %d %d %d -> %d %zd %zd %d %zd %d", result, + qblocks, qminsize, qstride, qmax_buffers, qalign, qtypes, + blocks, minsize, stride, max_buffers, align, types); + } else { + pw_log_warn("%p: no buffers param", result); + minsize = 8192; + max_buffers = 2; + } + if (SPA_FLAG_IS_SET(flags, PW_BUFFERS_FLAG_SHARED_MEM)) { + if (types != SPA_ID_INVALID) + SPA_FLAG_CLEAR(types, 1<<SPA_DATA_MemPtr); + if (types == 0 || types == SPA_ID_INVALID) + types = 1<<SPA_DATA_MemFd; + } + + if (SPA_FLAG_IS_SET(flags, PW_BUFFERS_FLAG_NO_MEM)) + minsize = 0; + + data_sizes = alloca(sizeof(uint32_t) * blocks); + data_strides = alloca(sizeof(int32_t) * blocks); + data_aligns = alloca(sizeof(uint32_t) * blocks); + data_types = alloca(sizeof(uint32_t) * blocks); + + for (i = 0; i < blocks; i++) { + data_sizes[i] = minsize; + data_strides[i] = stride; + data_aligns[i] = align; + data_types[i] = types; + } + + if ((res = alloc_buffers(context->pool, + max_buffers, + n_params, + params, + blocks, + data_sizes, data_strides, + data_aligns, data_types, + flags, + result)) < 0) { + pw_log_error("%p: can't alloc buffers: %s", result, spa_strerror(res)); + } + + return res; +} + +SPA_EXPORT +void pw_buffers_clear(struct pw_buffers *buffers) +{ + pw_log_debug("%p: clear %d buffers:%p", buffers, buffers->n_buffers, buffers->buffers); + if (buffers->mem) + pw_memblock_unref(buffers->mem); + free(buffers->buffers); + spa_zero(*buffers); +} diff --git a/src/pipewire/buffers.h b/src/pipewire/buffers.h new file mode 100644 index 0000000..abef392 --- /dev/null +++ b/src/pipewire/buffers.h @@ -0,0 +1,75 @@ +/* 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. + */ + +#ifndef PIPEWIRE_BUFFERS_H +#define PIPEWIRE_BUFFERS_H + +#include <spa/node/node.h> + +#include <pipewire/context.h> +#include <pipewire/mem.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** \defgroup pw_buffers Buffers + * Buffer handling + */ + +/** + * \addtogroup pw_buffers + * \{ + */ + +#define PW_BUFFERS_FLAG_NONE 0 +#define PW_BUFFERS_FLAG_NO_MEM (1<<0) /**< don't allocate buffer memory */ +#define PW_BUFFERS_FLAG_SHARED (1<<1) /**< buffers can be shared */ +#define PW_BUFFERS_FLAG_DYNAMIC (1<<2) /**< buffers have dynamic data */ +#define PW_BUFFERS_FLAG_SHARED_MEM (1<<3) /**< buffers need shared memory */ +#define PW_BUFFERS_FLAG_IN_PRIORITY (1<<4) /**< input parameters have priority */ + +struct pw_buffers { + struct pw_memblock *mem; /**< allocated buffer memory */ + struct spa_buffer **buffers; /**< port buffers */ + uint32_t n_buffers; /**< number of port buffers */ + uint32_t flags; /**< flags */ +}; + +int pw_buffers_negotiate(struct pw_context *context, uint32_t flags, + struct spa_node *outnode, uint32_t out_port_id, + struct spa_node *innode, uint32_t in_port_id, + struct pw_buffers *result); + +void pw_buffers_clear(struct pw_buffers *buffers); + +/** + * \} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* PIPEWIRE_BUFFERS_H */ diff --git a/src/pipewire/client.h b/src/pipewire/client.h new file mode 100644 index 0000000..2ed4c5b --- /dev/null +++ b/src/pipewire/client.h @@ -0,0 +1,186 @@ +/* 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. + */ + +#ifndef PIPEWIRE_CLIENT_H +#define PIPEWIRE_CLIENT_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <spa/utils/defs.h> +#include <spa/param/param.h> + +#include <pipewire/proxy.h> +#include <pipewire/permission.h> + +/** \defgroup pw_client Client + * Client interface + */ + +/** + * \addtogroup pw_client + * \{ + */ +#define PW_TYPE_INTERFACE_Client PW_TYPE_INFO_INTERFACE_BASE "Client" + +#define PW_VERSION_CLIENT 3 +struct pw_client; + +/* default ID of the current client after connect */ +#define PW_ID_CLIENT 1 + +/** The client information. Extra information can be added in later versions */ +struct pw_client_info { + uint32_t id; /**< id of the global */ +#define PW_CLIENT_CHANGE_MASK_PROPS (1 << 0) +#define PW_CLIENT_CHANGE_MASK_ALL ((1 << 1)-1) + uint64_t change_mask; /**< bitfield of changed fields since last call */ + struct spa_dict *props; /**< extra properties */ +}; + +/** Update an existing \ref pw_client_info with \a update with reset */ +struct pw_client_info * +pw_client_info_update(struct pw_client_info *info, + const struct pw_client_info *update); +/** Merge an existing \ref pw_client_info with \a update */ +struct pw_client_info * +pw_client_info_merge(struct pw_client_info *info, + const struct pw_client_info *update, bool reset); +/** Free a \ref pw_client_info */ +void pw_client_info_free(struct pw_client_info *info); + + +#define PW_CLIENT_EVENT_INFO 0 +#define PW_CLIENT_EVENT_PERMISSIONS 1 +#define PW_CLIENT_EVENT_NUM 2 + +/** Client events */ +struct pw_client_events { +#define PW_VERSION_CLIENT_EVENTS 0 + uint32_t version; + /** + * Notify client info + * + * \param info info about the client + */ + void (*info) (void *data, const struct pw_client_info *info); + /** + * Notify a client permission + * + * Event emitted as a result of the get_permissions method. + * + * \param default_permissions the default permissions + * \param index the index of the first permission entry + * \param n_permissions the number of permissions + * \param permissions the permissions + */ + void (*permissions) (void *data, + uint32_t index, + uint32_t n_permissions, + const struct pw_permission *permissions); +}; + + +#define PW_CLIENT_METHOD_ADD_LISTENER 0 +#define PW_CLIENT_METHOD_ERROR 1 +#define PW_CLIENT_METHOD_UPDATE_PROPERTIES 2 +#define PW_CLIENT_METHOD_GET_PERMISSIONS 3 +#define PW_CLIENT_METHOD_UPDATE_PERMISSIONS 4 +#define PW_CLIENT_METHOD_NUM 5 + +/** Client methods */ +struct pw_client_methods { +#define PW_VERSION_CLIENT_METHODS 0 + uint32_t version; + + int (*add_listener) (void *object, + struct spa_hook *listener, + const struct pw_client_events *events, + void *data); + /** + * Send an error to a client + * + * \param id the global id to report the error on + * \param res an errno style error code + * \param message an error string + */ + int (*error) (void *object, uint32_t id, int res, const char *message); + /** + * Update client properties + * + * \param props new properties + */ + int (*update_properties) (void *object, const struct spa_dict *props); + + /** + * Get client permissions + * + * A permissions event will be emitted with the permissions. + * + * \param index the first index to query, 0 for first + * \param num the maximum number of items to get + */ + int (*get_permissions) (void *object, uint32_t index, uint32_t num); + /** + * Manage the permissions of the global objects for this + * client + * + * Update the permissions of the global objects using the + * provided array with permissions + * + * Globals can use the default permissions or can have specific + * permissions assigned to them. + * + * \param n_permissions number of permissions + * \param permissions array of permissions + */ + int (*update_permissions) (void *object, uint32_t n_permissions, + const struct pw_permission *permissions); +}; + +#define pw_client_method(o,method,version,...) \ +({ \ + int _res = -ENOTSUP; \ + spa_interface_call_res((struct spa_interface*)o, \ + struct pw_client_methods, _res, \ + method, version, ##__VA_ARGS__); \ + _res; \ +}) + +#define pw_client_add_listener(c,...) pw_client_method(c,add_listener,0,__VA_ARGS__) +#define pw_client_error(c,...) pw_client_method(c,error,0,__VA_ARGS__) +#define pw_client_update_properties(c,...) pw_client_method(c,update_properties,0,__VA_ARGS__) +#define pw_client_get_permissions(c,...) pw_client_method(c,get_permissions,0,__VA_ARGS__) +#define pw_client_update_permissions(c,...) pw_client_method(c,update_permissions,0,__VA_ARGS__) + +/** + * \} + */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* PIPEWIRE_CLIENT_H */ diff --git a/src/pipewire/conf.c b/src/pipewire/conf.c new file mode 100644 index 0000000..700d8ee --- /dev/null +++ b/src/pipewire/conf.c @@ -0,0 +1,1186 @@ +/* PipeWire + * + * Copyright © 2021 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 <signal.h> +#include <getopt.h> +#include <limits.h> +#include <sys/stat.h> +#include <sys/mman.h> +#include <sys/types.h> +#include <fcntl.h> +#include <unistd.h> +#include <sys/wait.h> +#include <dirent.h> +#include <regex.h> +#ifdef HAVE_PWD_H +#include <pwd.h> +#endif +#if defined(__FreeBSD__) || defined(__MidnightBSD__) +#ifndef O_PATH +#define O_PATH 0 +#endif +#endif + +#include <spa/utils/result.h> +#include <spa/utils/string.h> +#include <spa/utils/json.h> + +#include <pipewire/impl.h> +#include <pipewire/private.h> + +PW_LOG_TOPIC_EXTERN(log_conf); +#define PW_LOG_TOPIC_DEFAULT log_conf + +static int make_path(char *path, size_t size, const char *paths[]) +{ + int i, len; + char *p = path; + for (i = 0; paths[i] != NULL; i++) { + len = snprintf(p, size, "%s%s", i == 0 ? "" : "/", paths[i]); + if (len < 0) + return -errno; + if ((size_t)len >= size) + return -ENOSPC; + p += len; + size -= len; + } + return 0; +} + +static int get_abs_path(char *path, size_t size, const char *prefix, const char *name) +{ + if (prefix[0] == '/') { + const char *paths[] = { prefix, name, NULL }; + if (make_path(path, size, paths) == 0 && + access(path, R_OK) == 0) + return 1; + return -ENOENT; + } + return 0; +} + +static int get_envconf_path(char *path, size_t size, const char *prefix, const char *name) +{ + const char *dir; + + dir = getenv("PIPEWIRE_CONFIG_DIR"); + if (dir != NULL) { + const char *paths[] = { dir, prefix, name, NULL }; + if (make_path(path, size, paths) == 0 && + access(path, R_OK) == 0) + return 1; + return -ENOENT; + } + return 0; +} + +static int get_homeconf_path(char *path, size_t size, const char *prefix, const char *name) +{ + char buffer[4096]; + const char *dir; + + dir = getenv("XDG_CONFIG_HOME"); + if (dir != NULL) { + const char *paths[] = { dir, "pipewire", prefix, name, NULL }; + if (make_path(path, size, paths) == 0 && + access(path, R_OK) == 0) + return 1; + } + dir = getenv("HOME"); + if (dir == NULL) { + struct passwd pwd, *result = NULL; + if (getpwuid_r(getuid(), &pwd, buffer, sizeof(buffer), &result) == 0) + dir = result ? result->pw_dir : NULL; + } + if (dir != NULL) { + const char *paths[] = { dir, ".config", "pipewire", prefix, name, NULL }; + if (make_path(path, size, paths) == 0 && + access(path, R_OK) == 0) + return 1; + } + return 0; +} + +static int get_configdir_path(char *path, size_t size, const char *prefix, const char *name) +{ + const char *dir; + dir = PIPEWIRE_CONFIG_DIR; + if (dir != NULL) { + const char *paths[] = { dir, prefix, name, NULL }; + if (make_path(path, size, paths) == 0 && + access(path, R_OK) == 0) + return 1; + } + return 0; +} + +static int get_confdata_path(char *path, size_t size, const char *prefix, const char *name) +{ + const char *dir; + dir = PIPEWIRE_CONFDATADIR; + if (dir != NULL) { + const char *paths[] = { dir, prefix, name, NULL }; + if (make_path(path, size, paths) == 0 && + access(path, R_OK) == 0) + return 1; + } + return 0; +} + +static int get_config_path(char *path, size_t size, const char *prefix, const char *name) +{ + int res; + + if (prefix == NULL) { + prefix = name; + name = NULL; + } + if ((res = get_abs_path(path, size, prefix, name)) != 0) + return res; + + if (pw_check_option("no-config", "true")) + goto no_config; + + if ((res = get_envconf_path(path, size, prefix, name)) != 0) + return res; + + if ((res = get_homeconf_path(path, size, prefix, name)) != 0) + return res; + + if ((res = get_configdir_path(path, size, prefix, name)) != 0) + return res; +no_config: + if ((res = get_confdata_path(path, size, prefix, name)) != 0) + return res; + return 0; +} + +static int get_config_dir(char *path, size_t size, const char *prefix, const char *name, int *level) +{ + int res; + + if (prefix == NULL) { + prefix = name; + name = NULL; + } + if ((res = get_abs_path(path, size, prefix, name)) != 0) { + if ((*level)++ == 0) + return res; + return -ENOENT; + } + + if (pw_check_option("no-config", "true")) + goto no_config; + + if ((res = get_envconf_path(path, size, prefix, name)) != 0) { + if ((*level)++ == 0) + return res; + return -ENOENT; + } + + if (*level == 0) { + (*level)++; + if ((res = get_homeconf_path(path, size, prefix, name)) != 0) + return res; + } + if (*level == 1) { + (*level)++; + if ((res = get_configdir_path(path, size, prefix, name)) != 0) + return res; + } + if (*level == 2) { +no_config: + (*level)++; + if ((res = get_confdata_path(path, size, prefix, name)) != 0) + return res; + } + return 0; +} + +static int get_envstate_path(char *path, size_t size, const char *prefix, const char *name) +{ + const char *dir; + dir = getenv("PIPEWIRE_STATE_DIR"); + if (dir != NULL) { + const char *paths[] = { dir, prefix, name, NULL }; + if (make_path(path, size, paths) == 0 && + access(path, R_OK) == 0) + return 1; + return -ENOENT; + } + return 0; +} + +static int get_homestate_path(char *path, size_t size, const char *prefix, const char *name) +{ + const char *dir; + char buffer[4096]; + + dir = getenv("XDG_STATE_HOME"); + if (dir != NULL) { + const char *paths[] = { dir, "pipewire", prefix, name, NULL }; + if (make_path(path, size, paths) == 0 && + access(path, R_OK) == 0) + return 1; + } + dir = getenv("HOME"); + if (dir == NULL) { + struct passwd pwd, *result = NULL; + if (getpwuid_r(getuid(), &pwd, buffer, sizeof(buffer), &result) == 0) + dir = result ? result->pw_dir : NULL; + } + if (dir != NULL) { + const char *paths[] = { dir, ".local", "state", "pipewire", prefix, name, NULL }; + if (make_path(path, size, paths) == 0 && + access(path, R_OK) == 0) + return 1; + } + if (dir != NULL) { + /* fallback for old XDG_CONFIG_HOME */ + const char *paths[] = { dir, ".config", "pipewire", prefix, name, NULL }; + if (make_path(path, size, paths) == 0 && + access(path, R_OK) == 0) + return 1; + } + return 0; +} + +static int get_state_path(char *path, size_t size, const char *prefix, const char *name) +{ + int res; + + if (prefix == NULL) { + prefix = name; + name = NULL; + } + if ((res = get_abs_path(path, size, prefix, name)) != 0) + return res; + + if ((res = get_envstate_path(path, size, prefix, name)) != 0) + return res; + + if ((res = get_homestate_path(path, size, prefix, name)) != 0) + return res; + + return 0; +} + +static int ensure_path(char *path, int size, const char *paths[]) +{ + int i, len, mode; + char *p = path; + + for (i = 0; paths[i] != NULL; i++) { + len = snprintf(p, size, "%s/", paths[i]); + if (len < 0) + return -errno; + if (len >= size) + return -ENOSPC; + + p += len; + size -= len; + + mode = X_OK; + if (paths[i+1] == NULL) + mode |= R_OK | W_OK; + + if (access(path, mode) < 0) { + if (errno != ENOENT) + return -errno; + if (mkdir(path, 0700) < 0) { + pw_log_info("Can't create directory %s: %m", path); + return -errno; + } + if (access(path, mode) < 0) + return -errno; + + pw_log_info("created directory %s", path); + } + } + return 0; +} + +static int open_write_dir(char *path, int size, const char *prefix) +{ + const char *dir; + char buffer[4096]; + int res; + + if (prefix != NULL && prefix[0] == '/') { + const char *paths[] = { prefix, NULL }; + if (ensure_path(path, size, paths) == 0) + goto found; + } + dir = getenv("XDG_STATE_HOME"); + if (dir != NULL) { + const char *paths[] = { dir, "pipewire", prefix, NULL }; + if (ensure_path(path, size, paths) == 0) + goto found; + } + dir = getenv("HOME"); + if (dir == NULL) { + struct passwd pwd, *result = NULL; + if (getpwuid_r(getuid(), &pwd, buffer, sizeof(buffer), &result) == 0) + dir = result ? result->pw_dir : NULL; + } + if (dir != NULL) { + const char *paths[] = { dir, ".local", "state", "pipewire", prefix, NULL }; + if (ensure_path(path, size, paths) == 0) + goto found; + } + return -ENOENT; +found: + if ((res = open(path, O_CLOEXEC | O_DIRECTORY | O_PATH)) < 0) { + pw_log_error("Can't open state directory %s: %m", path); + return -errno; + } + return res; +} + +SPA_EXPORT +int pw_conf_save_state(const char *prefix, const char *name, const struct pw_properties *conf) +{ + char path[PATH_MAX]; + char *tmp_name; + int res, sfd, fd, count = 0; + FILE *f; + + if ((sfd = open_write_dir(path, sizeof(path), prefix)) < 0) + return sfd; + + tmp_name = alloca(strlen(name)+5); + sprintf(tmp_name, "%s.tmp", name); + if ((fd = openat(sfd, tmp_name, O_CLOEXEC | O_CREAT | O_WRONLY | O_TRUNC, 0600)) < 0) { + pw_log_error("can't open file '%s': %m", tmp_name); + res = -errno; + goto error; + } + + f = fdopen(fd, "w"); + fprintf(f, "{"); + count += pw_properties_serialize_dict(f, &conf->dict, PW_PROPERTIES_FLAG_NL); + fprintf(f, "%s}", count == 0 ? " " : "\n"); + fclose(f); + + if (renameat(sfd, tmp_name, sfd, name) < 0) { + pw_log_error("can't rename temp file '%s': %m", tmp_name); + res = -errno; + goto error; + } + res = 0; + pw_log_info("%p: saved state '%s%s'", conf, path, name); +error: + close(sfd); + return res; +} + +static int conf_load(const char *path, struct pw_properties *conf) +{ + char *data; + struct stat sbuf; + int fd, count; + + if ((fd = open(path, O_CLOEXEC | O_RDONLY)) < 0) + goto error; + + if (fstat(fd, &sbuf) < 0) + goto error_close; + + if (sbuf.st_size > 0) { + if ((data = mmap(NULL, sbuf.st_size, PROT_READ, MAP_PRIVATE, fd, 0)) == MAP_FAILED) + goto error_close; + + count = pw_properties_update_string(conf, data, sbuf.st_size); + munmap(data, sbuf.st_size); + } else { + count = 0; + } + close(fd); + + pw_log_info("%p: loaded config '%s' with %d items", conf, path, count); + + return 0; + +error_close: + close(fd); +error: + pw_log_warn("%p: error loading config '%s': %m", conf, path); + return -errno; +} + +static bool check_override(struct pw_properties *conf, const char *name, int level) +{ + const struct spa_dict_item *it; + + spa_dict_for_each(it, &conf->dict) { + int lev, idx; + + if (!spa_streq(name, it->value)) + continue; + if (sscanf(it->key, "override.%d.%d.config.name", &lev, &idx) != 2) + continue; + if (lev < level) + return false; + } + return true; +} + +static void add_override(struct pw_properties *conf, struct pw_properties *override, + const char *path, const char *name, int level, int index) +{ + const struct spa_dict_item *it; + char key[1024]; + + snprintf(key, sizeof(key), "override.%d.%d.config.path", level, index); + pw_properties_set(conf, key, path); + snprintf(key, sizeof(key), "override.%d.%d.config.name", level, index); + pw_properties_set(conf, key, name); + spa_dict_for_each(it, &override->dict) { + snprintf(key, sizeof(key), "override.%d.%d.%s", level, index, it->key); + pw_properties_set(conf, key, it->value); + } +} + +static int conf_filter(const struct dirent *entry) +{ + return spa_strendswith(entry->d_name, ".conf"); +} + +SPA_EXPORT +int pw_conf_load_conf(const char *prefix, const char *name, struct pw_properties *conf) +{ + char path[PATH_MAX]; + char fname[PATH_MAX + 256]; + int i, res, level = 0; + struct pw_properties *override = NULL; + const char *dname; + + if (name == NULL) { + pw_log_debug("%p: config name must not be NULL", conf); + return -EINVAL; + } + + if (get_config_path(path, sizeof(path), prefix, name) == 0) { + pw_log_debug("%p: can't load config '%s': %m", conf, path); + return -ENOENT; + } + pw_properties_set(conf, "config.prefix", prefix); + pw_properties_set(conf, "config.name", name); + pw_properties_set(conf, "config.path", path); + + if ((res = conf_load(path, conf)) < 0) + return res; + + pw_properties_setf(conf, "config.name.d", "%s.d", name); + dname = pw_properties_get(conf, "config.name.d"); + + while (true) { + struct dirent **entries = NULL; + int n; + + if (get_config_dir(path, sizeof(path), prefix, dname, &level) <= 0) + break; + + n = scandir(path, &entries, conf_filter, alphasort); + if (n == 0) + continue; + if (n < 0) { + pw_log_warn("scandir %s failed: %m", path); + continue; + } + if (override == NULL && + (override = pw_properties_new(NULL, NULL)) == NULL) + return -errno; + + for (i = 0; i < n; i++) { + const char *name = entries[i]->d_name; + + snprintf(fname, sizeof(fname), "%s/%s", path, name); + if (check_override(conf, name, level)) { + if (conf_load(fname, override) >= 0) + add_override(conf, override, fname, name, level, i); + pw_properties_clear(override); + } else { + pw_log_info("skip override %s with lower priority", fname); + } + free(entries[i]); + } + free(entries); + } + pw_properties_free(override); + return 0; +} + +SPA_EXPORT +int pw_conf_load_state(const char *prefix, const char *name, struct pw_properties *conf) +{ + char path[PATH_MAX]; + + if (name == NULL) { + pw_log_debug("%p: config name must not be NULL", conf); + return -EINVAL; + } + + if (get_state_path(path, sizeof(path), prefix, name) == 0) { + pw_log_debug("%p: can't load config '%s': %m", conf, path); + return -ENOENT; + } + return conf_load(path, conf); +} + +struct data { + struct pw_context *context; + struct pw_properties *props; + int count; +}; + +/* context.spa-libs = { + * <factory-name regex> = <library-name> + * } + */ +static int parse_spa_libs(void *user_data, const char *location, + const char *section, const char *str, size_t len) +{ + struct data *d = user_data; + struct pw_context *context = d->context; + struct spa_json it[2]; + char key[512], value[512]; + + spa_json_init(&it[0], str, len); + if (spa_json_enter_object(&it[0], &it[1]) < 0) { + pw_log_error("config file error: context.spa-libs is not an object"); + return -EINVAL; + } + + while (spa_json_get_string(&it[1], key, sizeof(key)) > 0) { + if (spa_json_get_string(&it[1], value, sizeof(value)) > 0) { + pw_context_add_spa_lib(context, key, value); + d->count++; + } + } + return 0; +} + +static int load_module(struct pw_context *context, const char *key, const char *args, const char *flags) +{ + if (pw_context_load_module(context, key, args, NULL) == NULL) { + if (errno == ENOENT && flags && strstr(flags, "ifexists") != NULL) { + pw_log_info("%p: skipping unavailable module %s", + context, key); + } else if (flags == NULL || strstr(flags, "nofail") == NULL) { + pw_log_error("%p: could not load mandatory module \"%s\": %m", + context, key); + return -errno; + } else { + pw_log_info("%p: could not load optional module \"%s\": %m", + context, key); + } + } else { + pw_log_info("%p: loaded module %s", context, key); + } + return 0; +} + +/* + * context.modules = [ + * { name = <module-name> + * [ args = { <key> = <value> ... } ] + * [ flags = [ [ ifexists ] [ nofail ] ] + * } + * ] + */ +static int parse_modules(void *user_data, const char *location, + const char *section, const char *str, size_t len) +{ + struct data *d = user_data; + struct pw_context *context = d->context; + struct spa_json it[3]; + char key[512], *s; + int res = 0; + + s = strndup(str, len); + spa_json_init(&it[0], s, len); + if (spa_json_enter_array(&it[0], &it[1]) < 0) { + pw_log_error("config file error: context.modules is not an array"); + res = -EINVAL; + goto exit; + } + + while (spa_json_enter_object(&it[1], &it[2]) > 0) { + char *name = NULL, *args = NULL, *flags = NULL; + + while (spa_json_get_string(&it[2], key, sizeof(key)) > 0) { + const char *val; + int len; + + if ((len = spa_json_next(&it[2], &val)) <= 0) + break; + + if (spa_streq(key, "name")) { + name = (char*)val; + spa_json_parse_stringn(val, len, name, len+1); + } else if (spa_streq(key, "args")) { + if (spa_json_is_container(val, len)) + len = spa_json_container_len(&it[2], val, len); + + args = (char*)val; + spa_json_parse_stringn(val, len, args, len+1); + } else if (spa_streq(key, "flags")) { + if (spa_json_is_container(val, len)) + len = spa_json_container_len(&it[2], val, len); + flags = (char*)val; + spa_json_parse_stringn(val, len, flags, len+1); + } + } + if (name != NULL) + res = load_module(context, name, args, flags); + + if (res < 0) + break; + + d->count++; + } +exit: + free(s); + return res; +} + +static int create_object(struct pw_context *context, const char *key, const char *args, const char *flags) +{ + struct pw_impl_factory *factory; + void *obj; + + pw_log_debug("find factory %s", key); + factory = pw_context_find_factory(context, key); + if (factory == NULL) { + if (flags && strstr(flags, "nofail") != NULL) + return 0; + pw_log_error("can't find factory %s", key); + return -ENOENT; + } + pw_log_debug("create object with args %s", args); + obj = pw_impl_factory_create_object(factory, + NULL, NULL, 0, + args ? pw_properties_new_string(args) : NULL, + SPA_ID_INVALID); + if (obj == NULL) { + if (flags && strstr(flags, "nofail") != NULL) + return 0; + pw_log_error("can't create object from factory %s: %m", key); + return -errno; + } + return 0; +} + +/* + * context.objects = [ + * { factory = <factory-name> + * [ args = { <key> = <value> ... } ] + * [ flags = [ [ nofail ] ] ] + * } + * ] + */ +static int parse_objects(void *user_data, const char *location, + const char *section, const char *str, size_t len) +{ + struct data *d = user_data; + struct pw_context *context = d->context; + struct spa_json it[3]; + char key[512], *s; + int res = 0; + + s = strndup(str, len); + spa_json_init(&it[0], s, len); + if (spa_json_enter_array(&it[0], &it[1]) < 0) { + pw_log_error("config file error: context.objects is not an array"); + res = -EINVAL; + goto exit; + } + + while (spa_json_enter_object(&it[1], &it[2]) > 0) { + char *factory = NULL, *args = NULL, *flags = NULL; + + while (spa_json_get_string(&it[2], key, sizeof(key)) > 0) { + const char *val; + int len; + + if ((len = spa_json_next(&it[2], &val)) <= 0) + break; + + if (spa_streq(key, "factory")) { + factory = (char*)val; + spa_json_parse_stringn(val, len, factory, len+1); + } else if (spa_streq(key, "args")) { + if (spa_json_is_container(val, len)) + len = spa_json_container_len(&it[2], val, len); + + args = (char*)val; + spa_json_parse_stringn(val, len, args, len+1); + } else if (spa_streq(key, "flags")) { + if (spa_json_is_container(val, len)) + len = spa_json_container_len(&it[2], val, len); + + flags = (char*)val; + spa_json_parse_stringn(val, len, flags, len+1); + } + } + if (factory != NULL) + res = create_object(context, factory, args, flags); + + if (res < 0) + break; + d->count++; + } +exit: + free(s); + return res; +} + +static int do_exec(struct pw_context *context, const char *key, const char *args) +{ + int pid, res, n_args; + + pid = fork(); + + if (pid == 0) { + char *cmd, **argv; + + /* Double fork to avoid zombies; we don't want to set SIGCHLD handler */ + pid = fork(); + + if (pid < 0) { + pw_log_error("fork error: %m"); + exit(1); + } else if (pid != 0) { + exit(0); + } + + cmd = spa_aprintf("%s %s", key, args ? args : ""); + argv = pw_split_strv(cmd, " \t", INT_MAX, &n_args); + free(cmd); + + pw_log_info("exec %s '%s'", key, args); + res = execvp(key, argv); + pw_free_strv(argv); + + if (res == -1) { + res = -errno; + pw_log_error("execvp error '%s': %m", key); + } + + exit(1); + } else if (pid < 0) { + pw_log_error("fork error: %m"); + } else { + int status = 0; + do { + errno = 0; + res = waitpid(pid, &status, 0); + } while (res < 0 && errno == EINTR); + pw_log_debug("exec got pid %d res:%d status:%d", (int)pid, res, status); + } + return 0; +} + +/* + * context.exec = [ + * { path = <program-name> + * [ args = "<arguments>" ] + * } + * ] + */ +static int parse_exec(void *user_data, const char *location, + const char *section, const char *str, size_t len) +{ + struct data *d = user_data; + struct pw_context *context = d->context; + struct spa_json it[3]; + char key[512], *s; + int res = 0; + + s = strndup(str, len); + spa_json_init(&it[0], s, len); + if (spa_json_enter_array(&it[0], &it[1]) < 0) { + pw_log_error("config file error: context.exec is not an array"); + res = -EINVAL; + goto exit; + } + + while (spa_json_enter_object(&it[1], &it[2]) > 0) { + char *path = NULL, *args = NULL; + + while (spa_json_get_string(&it[2], key, sizeof(key)) > 0) { + const char *val; + int len; + + if ((len = spa_json_next(&it[2], &val)) <= 0) + break; + + if (spa_streq(key, "path")) { + path = (char*)val; + spa_json_parse_stringn(val, len, path, len+1); + } else if (spa_streq(key, "args")) { + args = (char*)val; + spa_json_parse_stringn(val, len, args, len+1); + } + } + if (path != NULL) + res = do_exec(context, path, args); + + if (res < 0) + break; + + d->count++; + } +exit: + free(s); + return res; +} + + +SPA_EXPORT +int pw_context_conf_section_for_each(struct pw_context *context, const char *section, + int (*callback) (void *data, const char *location, const char *section, + const char *str, size_t len), + void *data) +{ + struct pw_properties *conf = context->conf; + const char *path = NULL; + const struct spa_dict_item *it; + int res = 0; + + spa_dict_for_each(it, &conf->dict) { + if (spa_strendswith(it->key, "config.path")) { + path = it->value; + continue; + + } else if (spa_streq(it->key, section)) { + pw_log_info("handle config '%s' section '%s'", path, section); + } else if (spa_strstartswith(it->key, "override.") && + spa_strendswith(it->key, section)) { + pw_log_info("handle override '%s' section '%s'", path, section); + } else + continue; + + res = callback(data, path, section, it->value, strlen(it->value)); + if (res != 0) + break; + } + return res; +} + +SPA_EXPORT +int pw_context_parse_conf_section(struct pw_context *context, + struct pw_properties *conf, const char *section) +{ + struct data data = { .context = context }; + int res; + + if (spa_streq(section, "context.spa-libs")) + res = pw_context_conf_section_for_each(context, section, + parse_spa_libs, &data); + else if (spa_streq(section, "context.modules")) + res = pw_context_conf_section_for_each(context, section, + parse_modules, &data); + else if (spa_streq(section, "context.objects")) + res = pw_context_conf_section_for_each(context, section, + parse_objects, &data); + else if (spa_streq(section, "context.exec")) + res = pw_context_conf_section_for_each(context, section, + parse_exec, &data); + else + res = -EINVAL; + + return res == 0 ? data.count : res; +} + +static int update_props(void *user_data, const char *location, const char *key, + const char *val, size_t len) +{ + struct data *data = user_data; + data->count += pw_properties_update_string(data->props, val, len); + return 0; +} + +static int try_load_conf(const char *conf_prefix, const char *conf_name, + struct pw_properties *conf) +{ + int res; + + if (conf_name == NULL) + return -EINVAL; + if (spa_streq(conf_name, "null")) + return 0; + if ((res = pw_conf_load_conf(conf_prefix, conf_name, conf)) < 0) { + bool skip_prefix = conf_prefix == NULL || conf_name[0] == '/'; + pw_log_warn("can't load config %s%s%s: %s", + skip_prefix ? "" : conf_prefix, + skip_prefix ? "" : "/", + conf_name, spa_strerror(res)); + } + return res; +} + +SPA_EXPORT +int pw_conf_load_conf_for_context(struct pw_properties *props, struct pw_properties *conf) +{ + const char *conf_prefix, *conf_name; + int res; + + conf_prefix = getenv("PIPEWIRE_CONFIG_PREFIX"); + if (conf_prefix == NULL) + conf_prefix = pw_properties_get(props, PW_KEY_CONFIG_PREFIX); + + conf_name = getenv("PIPEWIRE_CONFIG_NAME"); + if ((res = try_load_conf(conf_prefix, conf_name, conf)) < 0) { + conf_name = pw_properties_get(props, PW_KEY_CONFIG_NAME); + if ((res = try_load_conf(conf_prefix, conf_name, conf)) < 0) { + conf_name = "client.conf"; + if ((res = try_load_conf(conf_prefix, conf_name, conf)) < 0) { + pw_log_error("can't load default config %s: %s", + conf_name, spa_strerror(res)); + return res; + } + } + } + + conf_name = pw_properties_get(props, PW_KEY_CONFIG_OVERRIDE_NAME); + if (conf_name != NULL) { + struct pw_properties *override; + const char *path, *name; + + override = pw_properties_new(NULL, NULL); + if (override == NULL) { + res = -errno; + return res; + } + + conf_prefix = pw_properties_get(props, PW_KEY_CONFIG_OVERRIDE_PREFIX); + if ((res = try_load_conf(conf_prefix, conf_name, override)) < 0) { + pw_log_error("can't load default override config %s: %s", + conf_name, spa_strerror(res)); + pw_properties_free (override); + return res; + } + path = pw_properties_get(override, "config.path"); + name = pw_properties_get(override, "config.name"); + add_override(conf, override, path, name, 0, 1); + pw_properties_free(override); + } + + return res; +} + +SPA_EXPORT +int pw_context_conf_update_props(struct pw_context *context, + const char *section, struct pw_properties *props) +{ + struct data data = { .context = context, .props = props }; + int res; + const char *str = pw_properties_get(props, "config.ext"); + + res = pw_context_conf_section_for_each(context, section, + update_props, &data); + if (res == 0 && str != NULL) { + char key[128]; + snprintf(key, sizeof(key), "%s.%s", section, str); + res = pw_context_conf_section_for_each(context, key, + update_props, &data); + } + return res == 0 ? data.count : res; +} + + +/* + * { + * # all keys must match the value. ~ in value starts regex. + * <key> = <value> + * ... + * } + */ +static bool find_match(struct spa_json *arr, const struct spa_dict *props) +{ + struct spa_json it[1]; + + while (spa_json_enter_object(arr, &it[0]) > 0) { + char key[256], val[1024]; + const char *str, *value; + int match = 0, fail = 0; + int len; + + while (spa_json_get_string(&it[0], key, sizeof(key)) > 0) { + bool success = false; + + if ((len = spa_json_next(&it[0], &value)) <= 0) + break; + + str = spa_dict_lookup(props, key); + + if (spa_json_is_null(value, len)) { + success = str == NULL; + } else { + if (spa_json_parse_stringn(value, len, val, sizeof(val)) < 0) + continue; + value = val; + len = strlen(val); + } + if (str != NULL) { + if (value[0] == '~') { + regex_t preg; + if (regcomp(&preg, value+1, REG_EXTENDED | REG_NOSUB) == 0) { + if (regexec(&preg, str, 0, NULL, 0) == 0) + success = true; + regfree(&preg); + } + } else if (strncmp(str, value, len) == 0 && + strlen(str) == (size_t)len) { + success = true; + } + } + if (success) { + match++; + pw_log_debug("'%s' match '%s' < > '%.*s'", key, str, len, value); + } + else + fail++; + } + if (match > 0 && fail == 0) + return true; + } + return false; +} + +/** + * [ + * { + * matches = [ + * # any of the items in matches needs to match, if one does, + * # actions are emited. + * { + * # all keys must match the value. ~ in value starts regex. + * <key> = <value> + * ... + * } + * ... + * ] + * actions = { + * <action> = <value> + * ... + * } + * } + * ] + */ +SPA_EXPORT +int pw_conf_match_rules(const char *str, size_t len, const char *location, + const struct spa_dict *props, + int (*callback) (void *data, const char *location, const char *action, + const char *str, size_t len), + void *data) +{ + const char *val; + struct spa_json it[4], actions; + + spa_json_init(&it[0], str, len); + if (spa_json_enter_array(&it[0], &it[1]) < 0) + return 0; + + while (spa_json_enter_object(&it[1], &it[2]) > 0) { + char key[64]; + bool have_match = false, have_actions = false; + + while (spa_json_get_string(&it[2], key, sizeof(key)) > 0) { + if (spa_streq(key, "matches")) { + if (spa_json_enter_array(&it[2], &it[3]) < 0) + break; + + have_match = find_match(&it[3], props); + } + else if (spa_streq(key, "actions")) { + if (spa_json_enter_object(&it[2], &actions) > 0) + have_actions = true; + } + else if (spa_json_next(&it[2], &val) <= 0) + break; + } + if (!have_match || !have_actions) + continue; + + while (spa_json_get_string(&actions, key, sizeof(key)) > 0) { + int res, len; + pw_log_debug("action %s", key); + + if ((len = spa_json_next(&actions, &val)) <= 0) + break; + + if (spa_json_is_container(val, len)) + len = spa_json_container_len(&actions, val, len); + + if ((res = callback(data, location, key, val, len)) < 0) + return res; + } + } + return 0; +} + +struct match { + const struct spa_dict *props; + int (*matched) (void *data, const char *location, const char *action, + const char *val, size_t len); + void *data; +}; + +static int match_rules(void *data, const char *location, const char *section, + const char *str, size_t len) +{ + struct match *match = data; + return pw_conf_match_rules(str, len, location, + match->props, match->matched, match->data); +} + +SPA_EXPORT +int pw_context_conf_section_match_rules(struct pw_context *context, const char *section, + const struct spa_dict *props, + int (*callback) (void *data, const char *location, const char *action, + const char *str, size_t len), + void *data) +{ + struct match match = { + .props = props, + .matched = callback, + .data = data }; + int res; + const char *str = spa_dict_lookup(props, "config.ext"); + + res = pw_context_conf_section_for_each(context, section, + match_rules, &match); + if (res == 0 && str != NULL) { + char key[128]; + snprintf(key, sizeof(key), "%s.%s", section, str); + res = pw_context_conf_section_for_each(context, key, + match_rules, &match); + } + return res; +} diff --git a/src/pipewire/conf.h b/src/pipewire/conf.h new file mode 100644 index 0000000..8ada200 --- /dev/null +++ b/src/pipewire/conf.h @@ -0,0 +1,49 @@ +/* PipeWire + * + * Copyright © 2021 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 <pipewire/context.h> + +/** \defgroup pw_conf Configuration + * Loading/saving properties from/to configuration files. + */ + +/** + * \addtogroup pw_conf + * \{ + */ + +int pw_conf_load_conf_for_context(struct pw_properties *props, struct pw_properties *conf); +int pw_conf_load_conf(const char *prefix, const char *name, struct pw_properties *conf); +int pw_conf_load_state(const char *prefix, const char *name, struct pw_properties *conf); +int pw_conf_save_state(const char *prefix, const char *name, const struct pw_properties *conf); + +int pw_conf_match_rules(const char *str, size_t len, const char *location, + const struct spa_dict *props, + int (*callback) (void *data, const char *location, const char *action, + const char *str, size_t len), + void *data); + +/** + * \} + */ diff --git a/src/pipewire/context.c b/src/pipewire/context.c new file mode 100644 index 0000000..6ba6488 --- /dev/null +++ b/src/pipewire/context.c @@ -0,0 +1,1461 @@ +/* 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 <errno.h> +#include <unistd.h> +#include <time.h> +#include <stdio.h> +#include <regex.h> +#include <limits.h> +#include <sys/mman.h> + +#include <pipewire/log.h> + +#include <spa/support/cpu.h> +#include <spa/support/dbus.h> +#include <spa/support/plugin.h> +#include <spa/support/plugin-loader.h> +#include <spa/node/utils.h> +#include <spa/utils/names.h> +#include <spa/utils/string.h> +#include <spa/debug/types.h> + +#include <pipewire/impl.h> +#include <pipewire/private.h> +#include <pipewire/thread.h> +#include <pipewire/conf.h> + +#include <pipewire/extensions/protocol-native.h> + +PW_LOG_TOPIC_EXTERN(log_context); +#define PW_LOG_TOPIC_DEFAULT log_context + +/** \cond */ +struct impl { + struct pw_context this; + struct spa_handle *dbus_handle; + struct spa_plugin_loader plugin_loader; + unsigned int recalc:1; + unsigned int recalc_pending:1; +}; + + +struct factory_entry { + regex_t regex; + char *lib; +}; +/** \endcond */ + +static void fill_properties(struct pw_context *context) +{ + struct pw_properties *properties = context->properties; + + if (!pw_properties_get(properties, PW_KEY_APP_NAME)) + pw_properties_set(properties, PW_KEY_APP_NAME, pw_get_client_name()); + + if (!pw_properties_get(properties, PW_KEY_APP_PROCESS_BINARY)) + pw_properties_set(properties, PW_KEY_APP_PROCESS_BINARY, pw_get_prgname()); + + if (!pw_properties_get(properties, PW_KEY_APP_LANGUAGE)) { + pw_properties_set(properties, PW_KEY_APP_LANGUAGE, getenv("LANG")); + } + if (!pw_properties_get(properties, PW_KEY_APP_PROCESS_ID)) { + pw_properties_setf(properties, PW_KEY_APP_PROCESS_ID, "%zd", (size_t) getpid()); + } + if (!pw_properties_get(properties, PW_KEY_APP_PROCESS_USER)) + pw_properties_set(properties, PW_KEY_APP_PROCESS_USER, pw_get_user_name()); + + if (!pw_properties_get(properties, PW_KEY_APP_PROCESS_HOST)) + pw_properties_set(properties, PW_KEY_APP_PROCESS_HOST, pw_get_host_name()); + + if (!pw_properties_get(properties, PW_KEY_APP_PROCESS_SESSION_ID)) { + pw_properties_set(properties, PW_KEY_APP_PROCESS_SESSION_ID, + getenv("XDG_SESSION_ID")); + } + if (!pw_properties_get(properties, PW_KEY_WINDOW_X11_DISPLAY)) { + pw_properties_set(properties, PW_KEY_WINDOW_X11_DISPLAY, + getenv("DISPLAY")); + } + pw_properties_set(properties, PW_KEY_CORE_VERSION, context->core->info.version); + pw_properties_set(properties, PW_KEY_CORE_NAME, context->core->info.name); +} + +static int context_set_freewheel(struct pw_context *context, bool freewheel) +{ + struct spa_thread *thr; + int res = 0; + + if ((thr = pw_data_loop_get_thread(context->data_loop_impl)) == NULL) + return -EIO; + + if (freewheel) { + pw_log_info("%p: enter freewheel", context); + if (context->thread_utils) + res = spa_thread_utils_drop_rt(context->thread_utils, thr); + } else { + pw_log_info("%p: exit freewheel", context); + /* Use the priority as configured within the realtime module */ + if (context->thread_utils) + res = spa_thread_utils_acquire_rt(context->thread_utils, thr, -1); + } + if (res < 0) + pw_log_info("%p: freewheel error:%s", context, spa_strerror(res)); + + context->freewheeling = freewheel; + + return res; +} + +static struct spa_handle *impl_plugin_loader_load(void *object, const char *factory_name, const struct spa_dict *info) +{ + struct impl *impl = object; + + if (impl == NULL || factory_name == NULL) { + errno = EINVAL; + return NULL; + } + + return pw_context_load_spa_handle(&impl->this, factory_name, info); +} + +static int impl_plugin_loader_unload(void *object, struct spa_handle *handle) +{ + spa_return_val_if_fail(object != NULL, -EINVAL); + return pw_unload_spa_handle(handle); +} + +static const struct spa_plugin_loader_methods impl_plugin_loader = { + SPA_VERSION_PLUGIN_LOADER_METHODS, + .load = impl_plugin_loader_load, + .unload = impl_plugin_loader_unload, +}; + +static void init_plugin_loader(struct impl *impl) +{ + impl->plugin_loader.iface = SPA_INTERFACE_INIT( + SPA_TYPE_INTERFACE_PluginLoader, + SPA_VERSION_PLUGIN_LOADER, + &impl_plugin_loader, + impl); +} + +static int do_data_loop_setup(struct spa_loop *loop, bool async, uint32_t seq, + const void *data, size_t size, void *user_data) +{ + struct pw_context *this = user_data; + const char *str; + struct spa_cpu *cpu; + + cpu = spa_support_find(this->support, this->n_support, SPA_TYPE_INTERFACE_CPU); + + if ((str = pw_properties_get(this->properties, SPA_KEY_CPU_ZERO_DENORMALS)) != NULL && + cpu != NULL) { + pw_log_info("setting zero denormals: %s", str); + spa_cpu_zero_denormals(cpu, spa_atob(str)); + } + return 0; +} + +/** Create a new context object + * + * \param main_loop the main loop to use + * \param properties extra properties for the context, ownership it taken + * + * \return a newly allocated context object + */ +SPA_EXPORT +struct pw_context *pw_context_new(struct pw_loop *main_loop, + struct pw_properties *properties, + size_t user_data_size) +{ + struct impl *impl; + struct pw_context *this; + const char *lib, *str; + void *dbus_iface = NULL; + uint32_t n_support; + struct pw_properties *pr, *conf; + struct spa_cpu *cpu; + int res = 0; + + impl = calloc(1, sizeof(struct impl) + user_data_size); + if (impl == NULL) { + res = -errno; + goto error_cleanup; + } + + this = &impl->this; + + pw_log_debug("%p: new", this); + + if (user_data_size > 0) + this->user_data = SPA_PTROFF(impl, sizeof(struct impl), void); + + pw_array_init(&this->factory_lib, 32); + pw_array_init(&this->objects, 32); + pw_map_init(&this->globals, 128, 32); + + spa_list_init(&this->core_impl_list); + spa_list_init(&this->protocol_list); + spa_list_init(&this->core_list); + spa_list_init(&this->registry_resource_list); + spa_list_init(&this->global_list); + spa_list_init(&this->module_list); + spa_list_init(&this->device_list); + spa_list_init(&this->client_list); + spa_list_init(&this->node_list); + spa_list_init(&this->factory_list); + spa_list_init(&this->metadata_list); + spa_list_init(&this->link_list); + spa_list_init(&this->control_list[0]); + spa_list_init(&this->control_list[1]); + spa_list_init(&this->export_list); + spa_list_init(&this->driver_list); + spa_hook_list_init(&this->listener_list); + spa_hook_list_init(&this->driver_listener_list); + + this->sc_pagesize = sysconf(_SC_PAGESIZE); + + if (properties == NULL) + properties = pw_properties_new(NULL, NULL); + if (properties == NULL) { + res = -errno; + goto error_free; + } + this->properties = properties; + + conf = pw_properties_new(NULL, NULL); + if (conf == NULL) { + res = -errno; + goto error_free; + } + this->conf = conf; + if ((res = pw_conf_load_conf_for_context (properties, conf)) < 0) + goto error_free; + + n_support = pw_get_support(this->support, SPA_N_ELEMENTS(this->support) - 6); + cpu = spa_support_find(this->support, n_support, SPA_TYPE_INTERFACE_CPU); + + res = pw_context_conf_update_props(this, "context.properties", properties); + pw_log_info("%p: parsed %d context.properties items", this, res); + + if ((str = getenv("PIPEWIRE_CORE"))) { + pw_log_info("using core.name from environment: %s", str); + pw_properties_set(properties, PW_KEY_CORE_NAME, str); + } + + if ((str = pw_properties_get(properties, "vm.overrides")) != NULL) { + if (cpu != NULL && spa_cpu_get_vm_type(cpu) != SPA_CPU_VM_NONE) + pw_properties_update_string(properties, str, strlen(str)); + pw_properties_set(properties, "vm.overrides", NULL); + } + if (cpu != NULL) { + if (pw_properties_get(properties, PW_KEY_CPU_MAX_ALIGN) == NULL) + pw_properties_setf(properties, PW_KEY_CPU_MAX_ALIGN, + "%u", spa_cpu_get_max_align(cpu)); + } + + if (getenv("PIPEWIRE_DEBUG") == NULL && + (str = pw_properties_get(properties, "log.level")) != NULL) + pw_log_set_level(atoi(str)); + + if (pw_properties_get_bool(properties, "mem.mlock-all", false)) { + if (mlockall(MCL_CURRENT | MCL_FUTURE) < 0) + pw_log_warn("%p: could not mlockall; %m", impl); + else + pw_log_info("%p: mlockall succeeded", impl); + } + + pw_settings_init(this); + this->settings = this->defaults; + + pr = pw_properties_copy(properties); + if ((str = pw_properties_get(pr, "context.data-loop." PW_KEY_LIBRARY_NAME_SYSTEM))) + pw_properties_set(pr, PW_KEY_LIBRARY_NAME_SYSTEM, str); + + this->data_loop_impl = pw_data_loop_new(&pr->dict); + pw_properties_free(pr); + if (this->data_loop_impl == NULL) { + res = -errno; + goto error_free; + } + + this->pool = pw_mempool_new(NULL); + if (this->pool == NULL) { + res = -errno; + goto error_free; + } + + this->data_loop = pw_data_loop_get_loop(this->data_loop_impl); + this->data_system = this->data_loop->system; + this->main_loop = main_loop; + + this->work_queue = pw_work_queue_new(this->main_loop); + if (this->work_queue == NULL) { + res = -errno; + goto error_free; + } + + init_plugin_loader(impl); + + this->support[n_support++] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_System, this->main_loop->system); + this->support[n_support++] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_Loop, this->main_loop->loop); + this->support[n_support++] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_LoopUtils, this->main_loop->utils); + this->support[n_support++] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_DataSystem, this->data_system); + this->support[n_support++] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_DataLoop, this->data_loop->loop); + this->support[n_support++] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_PluginLoader, &impl->plugin_loader); + + if ((str = pw_properties_get(properties, "support.dbus")) == NULL || + pw_properties_parse_bool(str)) { + lib = pw_properties_get(properties, PW_KEY_LIBRARY_NAME_DBUS); + if (lib == NULL) + lib = "support/libspa-dbus"; + + impl->dbus_handle = pw_load_spa_handle(lib, + SPA_NAME_SUPPORT_DBUS, NULL, + n_support, this->support); + + if (impl->dbus_handle == NULL) { + pw_log_warn("%p: can't load dbus library: %s", this, lib); + } else if ((res = spa_handle_get_interface(impl->dbus_handle, + SPA_TYPE_INTERFACE_DBus, &dbus_iface)) < 0) { + pw_log_warn("%p: can't load dbus interface: %s", this, spa_strerror(res)); + } else { + this->support[n_support++] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_DBus, dbus_iface); + } + } + this->n_support = n_support; + spa_assert(n_support <= SPA_N_ELEMENTS(this->support)); + + this->core = pw_context_create_core(this, pw_properties_copy(properties), 0); + if (this->core == NULL) { + res = -errno; + goto error_free; + } + pw_impl_core_register(this->core, NULL); + + fill_properties(this); + + if ((res = pw_context_parse_conf_section(this, conf, "context.spa-libs")) < 0) + goto error_free; + pw_log_info("%p: parsed %d context.spa-libs items", this, res); + if ((res = pw_context_parse_conf_section(this, conf, "context.modules")) < 0) + goto error_free; + if (res > 0) + pw_log_info("%p: parsed %d context.modules items", this, res); + else + pw_log_warn("%p: no modules loaded from context.modules", this); + if ((res = pw_context_parse_conf_section(this, conf, "context.objects")) < 0) + goto error_free; + pw_log_info("%p: parsed %d context.objects items", this, res); + if ((res = pw_context_parse_conf_section(this, conf, "context.exec")) < 0) + goto error_free; + pw_log_info("%p: parsed %d context.exec items", this, res); + + if ((res = pw_data_loop_start(this->data_loop_impl)) < 0) + goto error_free; + + pw_data_loop_invoke(this->data_loop_impl, + do_data_loop_setup, 0, NULL, 0, false, this); + + pw_settings_expose(this); + + pw_log_debug("%p: created", this); + + return this; + +error_free: + pw_context_destroy(this); +error_cleanup: + errno = -res; + return NULL; +} + +/** Destroy a context object + * + * \param context a context to destroy + */ +SPA_EXPORT +void pw_context_destroy(struct pw_context *context) +{ + struct impl *impl = SPA_CONTAINER_OF(context, struct impl, this); + struct pw_global *global; + struct pw_impl_client *client; + struct pw_impl_module *module; + struct pw_impl_device *device; + struct pw_core *core; + struct pw_resource *resource; + struct pw_impl_node *node; + struct factory_entry *entry; + struct pw_impl_metadata *metadata; + struct pw_impl_core *core_impl; + + pw_log_debug("%p: destroy", context); + pw_context_emit_destroy(context); + + spa_list_consume(core, &context->core_list, link) + pw_core_disconnect(core); + + spa_list_consume(client, &context->client_list, link) + pw_impl_client_destroy(client); + + spa_list_consume(node, &context->node_list, link) + pw_impl_node_destroy(node); + + spa_list_consume(device, &context->device_list, link) + pw_impl_device_destroy(device); + + spa_list_consume(resource, &context->registry_resource_list, link) + pw_resource_destroy(resource); + + if (context->data_loop_impl) + pw_data_loop_stop(context->data_loop_impl); + + spa_list_consume(module, &context->module_list, link) + pw_impl_module_destroy(module); + + spa_list_consume(global, &context->global_list, link) + pw_global_destroy(global); + + spa_list_consume(metadata, &context->metadata_list, link) + pw_impl_metadata_destroy(metadata); + + spa_list_consume(core_impl, &context->core_impl_list, link) + pw_impl_core_destroy(core_impl); + + pw_log_debug("%p: free", context); + pw_context_emit_free(context); + + if (context->data_loop_impl) + pw_data_loop_destroy(context->data_loop_impl); + + if (context->pool) + pw_mempool_destroy(context->pool); + + if (context->work_queue) + pw_work_queue_destroy(context->work_queue); + + pw_properties_free(context->properties); + pw_properties_free(context->conf); + + pw_settings_clean(context); + + if (impl->dbus_handle) + pw_unload_spa_handle(impl->dbus_handle); + + pw_array_for_each(entry, &context->factory_lib) { + regfree(&entry->regex); + free(entry->lib); + } + pw_array_clear(&context->factory_lib); + + pw_array_clear(&context->objects); + + pw_map_clear(&context->globals); + + spa_hook_list_clean(&context->listener_list); + spa_hook_list_clean(&context->driver_listener_list); + + free(context); +} + +SPA_EXPORT +void *pw_context_get_user_data(struct pw_context *context) +{ + return context->user_data; +} + +SPA_EXPORT +void pw_context_add_listener(struct pw_context *context, + struct spa_hook *listener, + const struct pw_context_events *events, + void *data) +{ + spa_hook_list_append(&context->listener_list, listener, events, data); +} + +SPA_EXPORT +const struct spa_support *pw_context_get_support(struct pw_context *context, uint32_t *n_support) +{ + *n_support = context->n_support; + return context->support; +} + +SPA_EXPORT +struct pw_loop *pw_context_get_main_loop(struct pw_context *context) +{ + return context->main_loop; +} + +SPA_EXPORT +struct pw_data_loop *pw_context_get_data_loop(struct pw_context *context) +{ + return context->data_loop_impl; +} + +SPA_EXPORT +struct pw_work_queue *pw_context_get_work_queue(struct pw_context *context) +{ + return context->work_queue; +} + +SPA_EXPORT +const struct pw_properties *pw_context_get_properties(struct pw_context *context) +{ + return context->properties; +} + +SPA_EXPORT +const char *pw_context_get_conf_section(struct pw_context *context, const char *section) +{ + return pw_properties_get(context->conf, section); +} + +/** Update context properties + * + * \param context a context + * \param dict properties to update + * + * Update the context object with the given properties + */ +SPA_EXPORT +int pw_context_update_properties(struct pw_context *context, const struct spa_dict *dict) +{ + int changed; + + changed = pw_properties_update(context->properties, dict); + pw_log_debug("%p: updated %d properties", context, changed); + + return changed; +} + +static bool global_can_read(struct pw_context *context, struct pw_global *global) +{ + if (context->current_client && + !PW_PERM_IS_R(pw_global_get_permissions(global, context->current_client))) + return false; + return true; +} + +SPA_EXPORT +int pw_context_for_each_global(struct pw_context *context, + int (*callback) (void *data, struct pw_global *global), + void *data) +{ + struct pw_global *g, *t; + int res; + + spa_list_for_each_safe(g, t, &context->global_list, link) { + if (!global_can_read(context, g)) + continue; + if ((res = callback(data, g)) != 0) + return res; + } + return 0; +} + +SPA_EXPORT +struct pw_global *pw_context_find_global(struct pw_context *context, uint32_t id) +{ + struct pw_global *global; + + global = pw_map_lookup(&context->globals, id); + if (global == NULL || !global->registered) { + errno = ENOENT; + return NULL; + } + + if (!global_can_read(context, global)) { + errno = EACCES; + return NULL; + } + return global; +} + +SPA_PRINTF_FUNC(7, 8) int pw_context_debug_port_params(struct pw_context *this, + struct spa_node *node, enum spa_direction direction, + uint32_t port_id, uint32_t id, int err, const char *debug, ...) +{ + struct spa_pod_builder b = { 0 }; + uint8_t buffer[4096]; + uint32_t state; + struct spa_pod *param; + int res; + va_list args; + + va_start(args, debug); + vsnprintf((char*)buffer, sizeof(buffer), debug, args); + va_end(args); + + pw_log_error("params %s: %d:%d %s (%s)", + spa_debug_type_find_name(spa_type_param, id), + direction, port_id, spa_strerror(err), buffer); + + if (err == -EBUSY) + return 0; + + state = 0; + while (true) { + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + res = spa_node_port_enum_params_sync(node, + direction, port_id, + id, &state, + NULL, ¶m, &b); + if (res != 1) { + if (res < 0) + pw_log_error(" error: %s", spa_strerror(res)); + break; + } + pw_log_pod(SPA_LOG_LEVEL_ERROR, param); + } + return 0; +} + +/** Find a common format between two ports + * + * \param context a context object + * \param output an output port + * \param input an input port + * \param props extra properties + * \param n_format_filters number of format filters + * \param format_filters array of format filters + * \param[out] format the common format between the ports + * \param builder builder to use for processing + * \param[out] error an error when something is wrong + * \return a common format of NULL on error + * + * Find a common format between the given ports. The format will + * be restricted to a subset given with the format filters. + */ +int pw_context_find_format(struct pw_context *context, + struct pw_impl_port *output, + struct pw_impl_port *input, + struct pw_properties *props, + uint32_t n_format_filters, + struct spa_pod **format_filters, + struct spa_pod **format, + struct spa_pod_builder *builder, + char **error) +{ + uint32_t out_state, in_state; + int res; + uint32_t iidx = 0, oidx = 0; + struct spa_pod_builder fb = { 0 }; + uint8_t fbuf[4096]; + struct spa_pod *filter; + + out_state = output->state; + in_state = input->state; + + pw_log_debug("%p: finding best format %d %d", context, out_state, in_state); + + /* when a port is configured but the node is idle, we can reconfigure with a different format */ + if (out_state > PW_IMPL_PORT_STATE_CONFIGURE && output->node->info.state == PW_NODE_STATE_IDLE) + out_state = PW_IMPL_PORT_STATE_CONFIGURE; + if (in_state > PW_IMPL_PORT_STATE_CONFIGURE && input->node->info.state == PW_NODE_STATE_IDLE) + in_state = PW_IMPL_PORT_STATE_CONFIGURE; + + pw_log_debug("%p: states %d %d", context, out_state, in_state); + + if (in_state == PW_IMPL_PORT_STATE_CONFIGURE && out_state > PW_IMPL_PORT_STATE_CONFIGURE) { + /* only input needs format */ + spa_pod_builder_init(&fb, fbuf, sizeof(fbuf)); + if ((res = spa_node_port_enum_params_sync(output->node->node, + output->direction, output->port_id, + SPA_PARAM_Format, &oidx, + NULL, &filter, &fb)) != 1) { + if (res < 0) + *error = spa_aprintf("error get output format: %s", spa_strerror(res)); + else + *error = spa_aprintf("no output formats"); + goto error; + } + pw_log_debug("%p: Got output format:", context); + pw_log_format(SPA_LOG_LEVEL_DEBUG, filter); + + if ((res = spa_node_port_enum_params_sync(input->node->node, + input->direction, input->port_id, + SPA_PARAM_EnumFormat, &iidx, + filter, format, builder)) <= 0) { + if (res == -ENOENT || res == 0) { + pw_log_debug("%p: no input format filter, using output format: %s", + context, spa_strerror(res)); + *format = filter; + } else { + *error = spa_aprintf("error input enum formats: %s", spa_strerror(res)); + goto error; + } + } + } else if (out_state >= PW_IMPL_PORT_STATE_CONFIGURE && in_state > PW_IMPL_PORT_STATE_CONFIGURE) { + /* only output needs format */ + spa_pod_builder_init(&fb, fbuf, sizeof(fbuf)); + if ((res = spa_node_port_enum_params_sync(input->node->node, + input->direction, input->port_id, + SPA_PARAM_Format, &iidx, + NULL, &filter, &fb)) != 1) { + if (res < 0) + *error = spa_aprintf("error get input format: %s", spa_strerror(res)); + else + *error = spa_aprintf("no input format"); + goto error; + } + pw_log_debug("%p: Got input format:", context); + pw_log_format(SPA_LOG_LEVEL_DEBUG, filter); + + if ((res = spa_node_port_enum_params_sync(output->node->node, + output->direction, output->port_id, + SPA_PARAM_EnumFormat, &oidx, + filter, format, builder)) <= 0) { + if (res == -ENOENT || res == 0) { + pw_log_debug("%p: no output format filter, using input format: %s", + context, spa_strerror(res)); + *format = filter; + } else { + *error = spa_aprintf("error output enum formats: %s", spa_strerror(res)); + goto error; + } + } + } else if (in_state == PW_IMPL_PORT_STATE_CONFIGURE && out_state == PW_IMPL_PORT_STATE_CONFIGURE) { + again: + /* both ports need a format */ + pw_log_debug("%p: do enum input %d", context, iidx); + spa_pod_builder_init(&fb, fbuf, sizeof(fbuf)); + if ((res = spa_node_port_enum_params_sync(input->node->node, + input->direction, input->port_id, + SPA_PARAM_EnumFormat, &iidx, + NULL, &filter, &fb)) != 1) { + if (res == -ENOENT) { + pw_log_debug("%p: no input filter", context); + filter = NULL; + } else { + if (res < 0) + *error = spa_aprintf("error input enum formats: %s", spa_strerror(res)); + else + *error = spa_aprintf("no more input formats"); + goto error; + } + } + pw_log_debug("%p: enum output %d with filter: %p", context, oidx, filter); + pw_log_format(SPA_LOG_LEVEL_DEBUG, filter); + + if ((res = spa_node_port_enum_params_sync(output->node->node, + output->direction, output->port_id, + SPA_PARAM_EnumFormat, &oidx, + filter, format, builder)) != 1) { + if (res == 0 && filter != NULL) { + oidx = 0; + goto again; + } + *error = spa_aprintf("error output enum formats: %s", spa_strerror(res)); + goto error; + } + + pw_log_debug("%p: Got filtered:", context); + pw_log_format(SPA_LOG_LEVEL_DEBUG, *format); + } else { + res = -EBADF; + *error = spa_aprintf("error bad node state"); + goto error; + } + return res; +error: + if (res == 0) + res = -EINVAL; + return res; +} + +static int ensure_state(struct pw_impl_node *node, bool running) +{ + enum pw_node_state state = node->info.state; + if (node->active && !SPA_FLAG_IS_SET(node->spa_flags, SPA_NODE_FLAG_NEED_CONFIGURE) && running) + state = PW_NODE_STATE_RUNNING; + else if (state > PW_NODE_STATE_IDLE) + state = PW_NODE_STATE_IDLE; + return pw_impl_node_set_state(node, state); +} + +static int collect_nodes(struct pw_context *context, struct pw_impl_node *node, struct spa_list *collect) +{ + struct spa_list queue; + struct pw_impl_node *n, *t; + struct pw_impl_port *p; + struct pw_impl_link *l; + + pw_log_debug("node %p: '%s'", node, node->name); + + /* start with node in the queue */ + spa_list_init(&queue); + spa_list_append(&queue, &node->sort_link); + node->visited = true; + + /* now follow all the links from the nodes in the queue + * and add the peers to the queue. */ + spa_list_consume(n, &queue, sort_link) { + pw_log_debug(" next node %p: '%s'", n, n->name); + + spa_list_remove(&n->sort_link); + spa_list_append(collect, &n->sort_link); + n->passive = true; + + if (!n->active) + continue; + + spa_list_for_each(p, &n->input_ports, link) { + spa_list_for_each(l, &p->links, input_link) { + t = l->output->node; + + if (!t->active) + continue; + + pw_impl_link_prepare(l); + + if (!l->prepared) + continue; + + if (!l->passive) + node->passive = n->passive = false; + + if (!t->visited) { + t->visited = true; + spa_list_append(&queue, &t->sort_link); + } + } + } + spa_list_for_each(p, &n->output_ports, link) { + spa_list_for_each(l, &p->links, output_link) { + t = l->input->node; + + if (!t->active) + continue; + + pw_impl_link_prepare(l); + + if (!l->prepared) + continue; + + if (!l->passive) + node->passive = n->passive = false; + + if (!t->visited) { + t->visited = true; + spa_list_append(&queue, &t->sort_link); + } + } + } + /* now go through all the nodes that have the same group and + * that are not yet visited */ + if (n->group[0] == '\0') + continue; + + spa_list_for_each(t, &context->node_list, link) { + if (t->exported || t == n || !t->active || t->visited) + continue; + if (!spa_streq(t->group, n->group)) + continue; + pw_log_debug("%p join group %s: '%s'", t, t->group, n->group); + t->visited = true; + spa_list_append(&queue, &t->sort_link); + } + } + return 0; +} + +static void move_to_driver(struct pw_context *context, struct spa_list *nodes, + struct pw_impl_node *driver) +{ + struct pw_impl_node *n; + pw_log_debug("driver: %p %s", driver, driver->name); + spa_list_consume(n, nodes, sort_link) { + spa_list_remove(&n->sort_link); + pw_log_debug(" follower: %p %s", n, n->name); + pw_impl_node_set_driver(n, driver); + } +} +static void remove_from_driver(struct pw_context *context, struct spa_list *nodes) +{ + struct pw_impl_node *n; + spa_list_consume(n, nodes, sort_link) { + spa_list_remove(&n->sort_link); + pw_impl_node_set_driver(n, NULL); + ensure_state(n, false); + } +} + +static inline void get_quantums(struct pw_context *context, uint32_t *def, + uint32_t *min, uint32_t *max, uint32_t *limit, uint32_t *rate) +{ + struct settings *s = &context->settings; + if (s->clock_force_quantum != 0) { + *def = *min = *max = s->clock_force_quantum; + *rate = 0; + } else { + *def = s->clock_quantum; + *min = s->clock_min_quantum; + *max = s->clock_max_quantum; + *rate = s->clock_rate; + } + *limit = s->clock_quantum_limit; +} + +static inline const uint32_t *get_rates(struct pw_context *context, uint32_t *def, uint32_t *n_rates, + bool *force) +{ + struct settings *s = &context->settings; + if (s->clock_force_rate != 0) { + *force = true; + *n_rates = 1; + *def = s->clock_force_rate; + return &s->clock_force_rate; + } else { + *force = false; + *n_rates = s->n_clock_rates; + *def = s->clock_rate; + return s->clock_rates; + } +} +static void reconfigure_driver(struct pw_context *context, struct pw_impl_node *n) +{ + struct pw_impl_node *s; + + spa_list_for_each(s, &n->follower_list, follower_link) { + if (s == n) + continue; + pw_log_debug("%p: follower %p: '%s' suspend", + context, s, s->name); + pw_impl_node_set_state(s, PW_NODE_STATE_SUSPENDED); + } + pw_log_debug("%p: driver %p: '%s' suspend", + context, n, n->name); + + if (n->info.state >= PW_NODE_STATE_IDLE) + n->reconfigure = true; + pw_impl_node_set_state(n, PW_NODE_STATE_SUSPENDED); +} + +/* find smaller power of 2 */ +static uint32_t flp2(uint32_t x) +{ + x = x | (x >> 1); + x = x | (x >> 2); + x = x | (x >> 4); + x = x | (x >> 8); + x = x | (x >> 16); + return x - (x >> 1); +} + +/* cmp fractions, avoiding overflows */ +static int fraction_compare(const struct spa_fraction *a, const struct spa_fraction *b) +{ + uint64_t fa = (uint64_t)a->num * (uint64_t)b->denom; + uint64_t fb = (uint64_t)b->num * (uint64_t)a->denom; + return fa < fb ? -1 : (fa > fb ? 1 : 0); +} + +static uint32_t find_best_rate(const uint32_t *rates, uint32_t n_rates, uint32_t rate, uint32_t best) +{ + uint32_t i; + for (i = 0; i < n_rates; i++) { + if (SPA_ABS((int32_t)rate - (int32_t)rates[i]) < + SPA_ABS((int32_t)rate - (int32_t)best)) + best = rates[i]; + } + return best; +} + +/* here we evaluate the complete state of the graph. + * + * It roughly operates in 3 stages: + * + * 1. go over all drivers and collect the nodes that need to be scheduled with the + * driver. This include all nodes that have an active link with the driver or + * with a node already scheduled with the driver. + * + * 2. go over all nodes that are not assigned to a driver. The ones that require + * a driver are moved to some random active driver found in step 1. + * + * 3. go over all drivers again, collect the quantum/rate of all followers, select + * the desired final value and activate the followers and then the driver. + * + * A complete graph evaluation is performed for each change that is made to the + * graph, such as making/destroying links, adding/removing nodes, property changes such + * as quantum/rate changes or metadata changes. + */ +int pw_context_recalc_graph(struct pw_context *context, const char *reason) +{ + struct impl *impl = SPA_CONTAINER_OF(context, struct impl, this); + struct settings *settings = &context->settings; + struct pw_impl_node *n, *s, *target, *fallback; + const uint32_t *rates; + uint32_t max_quantum, min_quantum, def_quantum, lim_quantum, rate_quantum; + uint32_t n_rates, def_rate; + bool freewheel = false, global_force_rate, global_force_quantum; + struct spa_list collect; + + pw_log_info("%p: busy:%d reason:%s", context, impl->recalc, reason); + + if (impl->recalc) { + impl->recalc_pending = true; + return -EBUSY; + } + +again: + impl->recalc = true; + + get_quantums(context, &def_quantum, &min_quantum, &max_quantum, &lim_quantum, &rate_quantum); + rates = get_rates(context, &def_rate, &n_rates, &global_force_rate); + + global_force_quantum = rate_quantum == 0; + + /* start from all drivers and group all nodes that are linked + * to it. Some nodes are not (yet) linked to anything and they + * will end up 'unassigned' to a driver. Other nodes are drivers + * and if they have active followers, we can use them to schedule + * the unassigned nodes. */ + target = fallback = NULL; + spa_list_for_each(n, &context->driver_list, driver_link) { + if (n->exported) + continue; + + if (!n->visited) { + spa_list_init(&collect); + collect_nodes(context, n, &collect); + move_to_driver(context, &collect, n); + } + /* from now on we are only interested in active driving nodes. + * We're going to see if there are active followers. */ + if (!n->driving || !n->active) + continue; + + /* first active driving node is fallback */ + if (fallback == NULL) + fallback = n; + + if (n->passive) + continue; + + spa_list_for_each(s, &n->follower_list, follower_link) { + pw_log_debug("%p: driver %p: follower %p %s: active:%d", + context, n, s, s->name, s->active); + if (s != n && s->active) { + /* if the driving node has active followers, it + * is a target for our unassigned nodes */ + if (target == NULL) + target = n; + if (n->freewheel) + freewheel = true; + break; + } + } + } + /* no active node, use fallback driving node */ + if (target == NULL) + target = fallback; + + /* update the freewheel status */ + if (context->freewheeling != freewheel) + context_set_freewheel(context, freewheel); + + /* now go through all available nodes. The ones we didn't visit + * in collect_nodes() are not linked to any driver. We assign them + * to either an active driver or the first driver if they are in a + * group that needs a driver. Else we remove them from a driver + * and stop them. */ + spa_list_for_each(n, &context->node_list, link) { + struct pw_impl_node *t, *driver; + + if (n->exported || n->visited) + continue; + + pw_log_debug("%p: unassigned node %p: '%s' active:%d want_driver:%d target:%p", + context, n, n->name, n->active, n->want_driver, target); + + /* collect all nodes in this group */ + spa_list_init(&collect); + collect_nodes(context, n, &collect); + + driver = NULL; + spa_list_for_each(t, &collect, sort_link) { + /* is any active and want a driver or it want process */ + if ((t->want_driver && t->active && !n->passive) || + t->always_process) + driver = target; + } + if (driver != NULL) { + /* driver needed for this group */ + driver->passive = false; + move_to_driver(context, &collect, driver); + } else { + /* no driver, make sure the nodes stops */ + remove_from_driver(context, &collect); + } + } + /* clean up the visited flag now */ + spa_list_for_each(n, &context->node_list, link) + n->visited = false; + + /* assign final quantum and set state for followers and drivers */ + spa_list_for_each(n, &context->driver_list, driver_link) { + bool running = false, lock_quantum = false, lock_rate = false; + struct spa_fraction latency = SPA_FRACTION(0, 0); + struct spa_fraction max_latency = SPA_FRACTION(0, 0); + struct spa_fraction rate = SPA_FRACTION(0, 0); + uint32_t quantum, target_rate, current_rate; + uint64_t quantum_stamp = 0, rate_stamp = 0; + bool force_rate, force_quantum; + const uint32_t *node_rates; + uint32_t node_n_rates, node_def_rate; + uint32_t node_max_quantum, node_min_quantum, node_def_quantum, node_rate_quantum; + + if (!n->driving || n->exported) + continue; + + node_def_quantum = def_quantum; + node_min_quantum = min_quantum; + node_max_quantum = max_quantum; + node_rate_quantum = rate_quantum; + force_quantum = global_force_quantum; + + node_def_rate = def_rate; + node_n_rates = n_rates; + node_rates = rates; + force_rate = global_force_rate; + + /* collect quantum and rate */ + spa_list_for_each(s, &n->follower_list, follower_link) { + + if (!s->moved) { + /* We only try to enforce the lock flags for nodes that + * are not recently moved between drivers. The nodes that + * are moved should try to enforce their quantum on the + * new driver. */ + lock_quantum |= s->lock_quantum; + lock_rate |= s->lock_rate; + } + if (!global_force_quantum && s->force_quantum > 0 && + s->stamp > quantum_stamp) { + node_def_quantum = node_min_quantum = node_max_quantum = s->force_quantum; + node_rate_quantum = 0; + quantum_stamp = s->stamp; + force_quantum = true; + } + if (!global_force_rate && s->force_rate > 0 && + s->stamp > rate_stamp) { + node_def_rate = s->force_rate; + node_n_rates = 1; + node_rates = &s->force_rate; + force_rate = true; + rate_stamp = s->stamp; + } + + /* smallest latencies */ + if (latency.denom == 0 || + (s->latency.denom > 0 && + fraction_compare(&s->latency, &latency) < 0)) + latency = s->latency; + if (max_latency.denom == 0 || + (s->max_latency.denom > 0 && + fraction_compare(&s->max_latency, &max_latency) < 0)) + max_latency = s->max_latency; + + /* largest rate */ + if (rate.denom == 0 || + (s->rate.denom > 0 && + fraction_compare(&s->rate, &rate) > 0)) + rate = s->rate; + + if (s->active) + running = !n->passive; + + pw_log_debug("%p: follower %p running:%d passive:%d rate:%u/%u latency %u/%u '%s'", + context, s, running, s->passive, rate.num, rate.denom, + latency.num, latency.denom, s->name); + + s->moved = false; + } + + if (force_quantum) + lock_quantum = false; + if (force_rate) + lock_rate = false; + + if (n->reconfigure) + running = true; + + current_rate = n->current_rate.denom; + if (lock_rate || n->reconfigure || + (!force_rate && + (n->info.state > PW_NODE_STATE_IDLE))) + /* when someone wants us to lock the rate of this driver or + * when the driver is busy and we don't need to force a rate, + * keep the current rate */ + target_rate = current_rate; + else { + /* Here we are allowed to change the rate of the driver. + * Start with the default rate. If the desired rate is + * allowed, switch to it */ + target_rate = node_def_rate; + if (rate.denom != 0 && rate.num == 1) + target_rate = find_best_rate(node_rates, node_n_rates, + rate.denom, target_rate); + } + + if (target_rate != current_rate) { + bool do_reconfigure = false; + /* we doing a rate switch */ + pw_log_info("(%s-%u) state:%s new rate:%u->%u", + n->name, n->info.id, + pw_node_state_as_string(n->info.state), + n->current_rate.denom, + target_rate); + + if (force_rate) { + if (settings->clock_rate_update_mode == CLOCK_RATE_UPDATE_MODE_HARD) + do_reconfigure = true; + } else { + if (n->info.state >= PW_NODE_STATE_SUSPENDED) + do_reconfigure = true; + } + if (do_reconfigure) + reconfigure_driver(context, n); + + /* we're setting the pending rate. This will become the new + * current rate in the next iteration of the graph. */ + n->current_rate = SPA_FRACTION(1, target_rate); + n->current_pending = true; + current_rate = target_rate; + /* we might be suspended now and the links need to be prepared again */ + if (do_reconfigure) + goto again; + } + + if (node_rate_quantum != 0 && current_rate != node_rate_quantum) { + /* the quantum values are scaled with the current rate */ + node_def_quantum = node_def_quantum * current_rate / node_rate_quantum; + node_min_quantum = node_min_quantum * current_rate / node_rate_quantum; + node_max_quantum = node_max_quantum * current_rate / node_rate_quantum; + } + + /* calculate desired quantum */ + if (max_latency.denom != 0) { + uint32_t tmp = (max_latency.num * current_rate / max_latency.denom); + if (tmp < node_max_quantum) + node_max_quantum = tmp; + } + + quantum = node_def_quantum; + if (latency.denom != 0) + quantum = (latency.num * current_rate / latency.denom); + quantum = SPA_CLAMP(quantum, node_min_quantum, node_max_quantum); + quantum = SPA_MIN(quantum, lim_quantum); + + if (settings->clock_power_of_two_quantum) + quantum = flp2(quantum); + + if (running && quantum != n->current_quantum && !lock_quantum) { + pw_log_info("(%s-%u) new quantum:%"PRIu64"->%u", + n->name, n->info.id, + n->current_quantum, + quantum); + /* this is the new pending quantum */ + n->current_quantum = quantum; + n->current_pending = true; + } + + if (n->info.state < PW_NODE_STATE_RUNNING && n->current_pending) { + /* the driver node is not actually running and we have a + * pending change. Apply the change to the position now so + * that we have the right values when we change the node + * states of the driver and followers to RUNNING below */ + pw_log_debug("%p: apply duration:%"PRIu64" rate:%u/%u", context, + n->current_quantum, n->current_rate.num, + n->current_rate.denom); + n->rt.position->clock.duration = n->current_quantum; + n->rt.position->clock.rate = n->current_rate; + n->current_pending = false; + } + + pw_log_debug("%p: driver %p running:%d passive:%d quantum:%u '%s'", + context, n, running, n->passive, quantum, n->name); + + /* first change the node states of the followers to the new target */ + spa_list_for_each(s, &n->follower_list, follower_link) { + if (s == n) + continue; + pw_log_debug("%p: follower %p: active:%d '%s'", + context, s, s->active, s->name); + ensure_state(s, running); + } + /* now that all the followers are ready, start the driver */ + ensure_state(n, running); + } + impl->recalc = false; + if (impl->recalc_pending) { + impl->recalc_pending = false; + goto again; + } + + return 0; +} + +SPA_EXPORT +int pw_context_add_spa_lib(struct pw_context *context, + const char *factory_regexp, const char *lib) +{ + struct factory_entry *entry; + int err; + + entry = pw_array_add(&context->factory_lib, sizeof(*entry)); + if (entry == NULL) + return -errno; + + if ((err = regcomp(&entry->regex, factory_regexp, REG_EXTENDED | REG_NOSUB)) != 0) { + char errbuf[1024]; + regerror(err, &entry->regex, errbuf, sizeof(errbuf)); + pw_log_error("%p: can compile regex: %s", context, errbuf); + pw_array_remove(&context->factory_lib, entry); + return -EINVAL; + } + + entry->lib = strdup(lib); + pw_log_debug("%p: map factory regex '%s' to '%s", context, + factory_regexp, lib); + return 0; +} + +SPA_EXPORT +const char *pw_context_find_spa_lib(struct pw_context *context, const char *factory_name) +{ + struct factory_entry *entry; + + pw_array_for_each(entry, &context->factory_lib) { + if (regexec(&entry->regex, factory_name, 0, NULL, 0) == 0) + return entry->lib; + } + return NULL; +} + +SPA_EXPORT +struct spa_handle *pw_context_load_spa_handle(struct pw_context *context, + const char *factory_name, + const struct spa_dict *info) +{ + const char *lib; + const struct spa_support *support; + uint32_t n_support; + struct spa_handle *handle; + + pw_log_debug("%p: load factory %s", context, factory_name); + + lib = pw_context_find_spa_lib(context, factory_name); + if (lib == NULL && info != NULL) + lib = spa_dict_lookup(info, SPA_KEY_LIBRARY_NAME); + if (lib == NULL) { + errno = ENOENT; + pw_log_warn("%p: no library for %s: %m", + context, factory_name); + return NULL; + } + + support = pw_context_get_support(context, &n_support); + + handle = pw_load_spa_handle(lib, factory_name, + info, n_support, support); + + return handle; +} + +SPA_EXPORT +int pw_context_register_export_type(struct pw_context *context, struct pw_export_type *type) +{ + if (pw_context_find_export_type(context, type->type)) { + pw_log_warn("context %p: duplicate export type %s", context, type->type); + return -EEXIST; + } + pw_log_debug("context %p: Add export type %s to context", context, type->type); + spa_list_append(&context->export_list, &type->link); + return 0; +} + +SPA_EXPORT +const struct pw_export_type *pw_context_find_export_type(struct pw_context *context, const char *type) +{ + const struct pw_export_type *t; + spa_list_for_each(t, &context->export_list, link) { + if (spa_streq(t->type, type)) + return t; + } + return NULL; +} + +struct object_entry { + const char *type; + void *value; +}; + +static struct object_entry *find_object(struct pw_context *context, const char *type) +{ + struct object_entry *entry; + pw_array_for_each(entry, &context->objects) { + if (spa_streq(entry->type, type)) + return entry; + } + return NULL; +} + +SPA_EXPORT +int pw_context_set_object(struct pw_context *context, const char *type, void *value) +{ + struct object_entry *entry; + + entry = find_object(context, type); + + if (value == NULL) { + if (entry) + pw_array_remove(&context->objects, entry); + } else { + if (entry == NULL) { + entry = pw_array_add(&context->objects, sizeof(*entry)); + if (entry == NULL) + return -errno; + entry->type = type; + } + entry->value = value; + } + if (spa_streq(type, SPA_TYPE_INTERFACE_ThreadUtils)) { + context->thread_utils = value; + if (context->data_loop_impl) + pw_data_loop_set_thread_utils(context->data_loop_impl, + context->thread_utils); + } + return 0; +} + +SPA_EXPORT +void *pw_context_get_object(struct pw_context *context, const char *type) +{ + struct object_entry *entry; + + if ((entry = find_object(context, type)) != NULL) + return entry->value; + + return NULL; +} diff --git a/src/pipewire/context.h b/src/pipewire/context.h new file mode 100644 index 0000000..fe658a6 --- /dev/null +++ b/src/pipewire/context.h @@ -0,0 +1,195 @@ +/* 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. + */ + +#ifndef PIPEWIRE_CONTEXT_H +#define PIPEWIRE_CONTEXT_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <spa/utils/defs.h> +#include <spa/utils/hook.h> + +/** \defgroup pw_context Context + * + * \brief The PipeWire context object manages all locally available + * resources. It is used by both clients and servers. + * + * The context is used to: + * + * - Load modules and extend the functionality. This includes + * extending the protocol with new object types or creating + * any of the available objects. + * + * - Create implementations of various objects like nodes, + * devices, factories, modules, etc.. This will usually also + * create pw_global objects that can then be shared with + * clients. + * + * - Connect to another PipeWire instance (the main daemon, for + * example) and interact with it (See \ref page_core_api). + * + * - Export a local implementation of an object to another + * instance. + */ + +/** + * \addtogroup pw_context + * @{ + */ +struct pw_context; + +struct pw_global; +struct pw_impl_client; + +#include <pipewire/core.h> +#include <pipewire/loop.h> +#include <pipewire/properties.h> + +/** context events emitted by the context object added with \ref pw_context_add_listener */ +struct pw_context_events { +#define PW_VERSION_CONTEXT_EVENTS 0 + uint32_t version; + + /** The context is being destroyed */ + void (*destroy) (void *data); + /** The context is being freed */ + void (*free) (void *data); + /** a new client object is added */ + void (*check_access) (void *data, struct pw_impl_client *client); + /** a new global object was added */ + void (*global_added) (void *data, struct pw_global *global); + /** a global object was removed */ + void (*global_removed) (void *data, struct pw_global *global); +}; + +/** Make a new context object for a given main_loop. Ownership of the properties is taken */ +struct pw_context * pw_context_new(struct pw_loop *main_loop, /**< a main loop to run in */ + struct pw_properties *props, /**< extra properties */ + size_t user_data_size /**< extra user data size */); + +/** destroy a context object, all resources except the main_loop will be destroyed */ +void pw_context_destroy(struct pw_context *context); + +/** Get the context user data */ +void *pw_context_get_user_data(struct pw_context *context); + +/** Add a new event listener to a context */ +void pw_context_add_listener(struct pw_context *context, + struct spa_hook *listener, + const struct pw_context_events *events, + void *data); + +/** Get the context properties */ +const struct pw_properties *pw_context_get_properties(struct pw_context *context); + +/** Update the context properties */ +int pw_context_update_properties(struct pw_context *context, const struct spa_dict *dict); + +/** Get a config section for this context. Since 0.3.22, deprecated, + * use pw_context_conf_section_for_each(). */ +const char *pw_context_get_conf_section(struct pw_context *context, const char *section); +/** Parse a standard config section for this context. Since 0.3.22 */ +int pw_context_parse_conf_section(struct pw_context *context, + struct pw_properties *conf, const char *section); + +/** update properties from a section into props. Since 0.3.45 */ +int pw_context_conf_update_props(struct pw_context *context, const char *section, + struct pw_properties *props); +/** emit callback for all config sections. Since 0.3.45 */ +int pw_context_conf_section_for_each(struct pw_context *context, const char *section, + int (*callback) (void *data, const char *location, const char *section, + const char *str, size_t len), + void *data); +/** emit callback for all matched properties. Since 0.3.46 */ +int pw_context_conf_section_match_rules(struct pw_context *context, const char *section, + const struct spa_dict *props, + int (*callback) (void *data, const char *location, const char *action, + const char *str, size_t len), + void *data); + +/** Get the context support objects */ +const struct spa_support *pw_context_get_support(struct pw_context *context, uint32_t *n_support); + +/** get the context main loop */ +struct pw_loop *pw_context_get_main_loop(struct pw_context *context); + +/** get the context data loop. Since 0.3.56 */ +struct pw_data_loop *pw_context_get_data_loop(struct pw_context *context); + +/** Get the work queue from the context: Since 0.3.26 */ +struct pw_work_queue *pw_context_get_work_queue(struct pw_context *context); + +/** Iterate the globals of the context. The callback should return + * 0 to fetch the next item, any other value stops the iteration and returns + * the value. When all callbacks return 0, this function returns 0 when all + * globals are iterated. */ +int pw_context_for_each_global(struct pw_context *context, + int (*callback) (void *data, struct pw_global *global), + void *data); + +/** Find a context global by id */ +struct pw_global *pw_context_find_global(struct pw_context *context, /**< the context */ + uint32_t id /**< the global id */); + +/** add a spa library for the given factory_name regex */ +int pw_context_add_spa_lib(struct pw_context *context, const char *factory_regex, const char *lib); + +/** find the library name for a spa factory */ +const char * pw_context_find_spa_lib(struct pw_context *context, const char *factory_name); + +struct spa_handle *pw_context_load_spa_handle(struct pw_context *context, + const char *factory_name, + const struct spa_dict *info); + + +/** data for registering export functions */ +struct pw_export_type { + struct spa_list link; + const char *type; + struct pw_proxy * (*func) (struct pw_core *core, + const char *type, const struct spa_dict *props, void *object, + size_t user_data_size); +}; + +/** register a type that can be exported on a context_proxy. This is usually used by + * extension modules */ +int pw_context_register_export_type(struct pw_context *context, struct pw_export_type *type); +/** find information about registered export type */ +const struct pw_export_type *pw_context_find_export_type(struct pw_context *context, const char *type); + +/** add an object to the context */ +int pw_context_set_object(struct pw_context *context, const char *type, void *value); +/** get an object from the context */ +void *pw_context_get_object(struct pw_context *context, const char *type); + +/** + * \} + */ +#ifdef __cplusplus +} +#endif + +#endif /* PIPEWIRE_CONTEXT_H */ diff --git a/src/pipewire/control.c b/src/pipewire/control.c new file mode 100644 index 0000000..0e3afd7 --- /dev/null +++ b/src/pipewire/control.c @@ -0,0 +1,267 @@ +/* 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 <spa/pod/parser.h> +#include <spa/debug/types.h> + +#include <pipewire/control.h> +#include <pipewire/private.h> + +#define NAME "control" + +struct impl { + struct pw_control this; + + struct pw_memblock *mem; +}; + +struct pw_control * +pw_control_new(struct pw_context *context, + struct pw_impl_port *port, + uint32_t id, uint32_t size, + size_t user_data_size) +{ + struct impl *impl; + struct pw_control *this; + enum spa_direction direction; + + switch (id) { + case SPA_IO_Control: + direction = SPA_DIRECTION_INPUT; + break; + case SPA_IO_Notify: + direction = SPA_DIRECTION_OUTPUT; + break; + default: + errno = ENOTSUP; + goto error_exit; + } + + impl = calloc(1, sizeof(struct impl) + user_data_size); + if (impl == NULL) + goto error_exit; + + this = &impl->this; + this->id = id; + this->size = size; + + pw_log_debug(NAME" %p: new %s %d", this, + spa_debug_type_find_name(spa_type_io, this->id), direction); + + this->context = context; + this->port = port; + this->direction = direction; + + spa_list_init(&this->links); + + if (user_data_size > 0) + this->user_data = SPA_PTROFF(impl, sizeof(struct impl), void); + + spa_hook_list_init(&this->listener_list); + + spa_list_append(&context->control_list[direction], &this->link); + if (port) { + spa_list_append(&port->control_list[direction], &this->port_link); + pw_impl_port_emit_control_added(port, this); + } + return this; + +error_exit: + return NULL; +} + +void pw_control_destroy(struct pw_control *control) +{ + struct impl *impl = SPA_CONTAINER_OF(control, struct impl, this); + struct pw_control_link *link; + + pw_log_debug(NAME" %p: destroy", control); + + pw_control_emit_destroy(control); + + if (control->direction == SPA_DIRECTION_OUTPUT) { + spa_list_consume(link, &control->links, out_link) + pw_control_remove_link(link); + } + else { + spa_list_consume(link, &control->links, in_link) + pw_control_remove_link(link); + } + + spa_list_remove(&control->link); + + if (control->port) { + spa_list_remove(&control->port_link); + pw_impl_port_emit_control_removed(control->port, control); + } + + pw_log_debug(NAME" %p: free", control); + pw_control_emit_free(control); + + spa_hook_list_clean(&control->listener_list); + + if (control->direction == SPA_DIRECTION_OUTPUT) { + if (impl->mem) + pw_memblock_unref(impl->mem); + } + free(control); +} + +SPA_EXPORT +struct pw_impl_port *pw_control_get_port(struct pw_control *control) +{ + return control->port; +} + +SPA_EXPORT +void pw_control_add_listener(struct pw_control *control, + struct spa_hook *listener, + const struct pw_control_events *events, + void *data) +{ + spa_hook_list_append(&control->listener_list, listener, events, data); +} + +static int port_set_io(struct pw_impl_port *port, uint32_t mix, uint32_t id, void *data, uint32_t size) +{ + int res; + + if (port->mix) { + res = spa_node_port_set_io(port->mix, port->direction, mix, id, data, size); + if (SPA_RESULT_IS_OK(res)) + return res; + } + + if ((res = spa_node_port_set_io(port->node->node, + port->direction, port->port_id, + id, data, size)) < 0) { + pw_log_warn("port %p: set io failed %d %s", port, + res, spa_strerror(res)); + } + return res; +} + +SPA_EXPORT +int pw_control_add_link(struct pw_control *control, uint32_t cmix, + struct pw_control *other, uint32_t omix, + struct pw_control_link *link) +{ + int res = 0; + struct impl *impl; + uint32_t size; + + if (control->direction == SPA_DIRECTION_INPUT) { + SPA_SWAP(control, other); + SPA_SWAP(cmix, omix); + } + if (control->direction != SPA_DIRECTION_OUTPUT || + other->direction != SPA_DIRECTION_INPUT) + return -EINVAL; + + impl = SPA_CONTAINER_OF(control, struct impl, this); + + pw_log_debug(NAME" %p: link to %p %s", control, other, + spa_debug_type_find_name(spa_type_io, control->id)); + + size = SPA_MAX(control->size, other->size); + + if (impl->mem == NULL) { + impl->mem = pw_mempool_alloc(control->context->pool, + PW_MEMBLOCK_FLAG_READWRITE | + PW_MEMBLOCK_FLAG_SEAL | + PW_MEMBLOCK_FLAG_MAP, + SPA_DATA_MemFd, size); + if (impl->mem == NULL) { + res = -errno; + goto exit; + } + } + + if (spa_list_is_empty(&control->links)) { + if (control->port) { + if ((res = port_set_io(control->port, cmix, + control->id, + impl->mem->map->ptr, size)) < 0) { + pw_log_warn(NAME" %p: set io failed %d %s", control, + res, spa_strerror(res)); + goto exit; + } + } + } + + if (other->port) { + if ((res = port_set_io(other->port, omix, + other->id, impl->mem->map->ptr, size)) < 0) { + pw_log_warn(NAME" %p: set io failed %d %s", control, + res, spa_strerror(res)); + goto exit; + } + } + + link->output = control; + link->input = other; + link->out_port = cmix; + link->in_port = omix; + link->valid = true; + spa_list_append(&control->links, &link->out_link); + spa_list_append(&other->links, &link->in_link); + + pw_control_emit_linked(control, other); + pw_control_emit_linked(other, control); +exit: + return res; +} + +SPA_EXPORT +int pw_control_remove_link(struct pw_control_link *link) +{ + int res = 0; + struct pw_control *output = link->output; + struct pw_control *input = link->input; + + pw_log_debug(NAME" %p: unlink from %p", output, input); + + spa_list_remove(&link->in_link); + spa_list_remove(&link->out_link); + link->valid = false; + + if (spa_list_is_empty(&output->links)) { + if ((res = port_set_io(output->port, link->out_port, + output->id, NULL, 0)) < 0) { + pw_log_warn(NAME" %p: can't unset port control io", output); + } + } + + if (input->port) { + if ((res = port_set_io(input->port, link->in_port, + input->id, NULL, 0)) < 0) { + pw_log_warn(NAME" %p: can't unset port control io", output); + } + } + + pw_control_emit_unlinked(output, input); + pw_control_emit_unlinked(input, output); + + return res; +} diff --git a/src/pipewire/control.h b/src/pipewire/control.h new file mode 100644 index 0000000..92d89a1 --- /dev/null +++ b/src/pipewire/control.h @@ -0,0 +1,82 @@ +/* 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. + */ + +#ifndef PIPEWIRE_CONTROL_H +#define PIPEWIRE_CONTROL_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <spa/utils/hook.h> + +/** \defgroup pw_control Control + * + * \brief A control can be used to control a port property. + */ + +/** + * \addtogroup pw_control + * \{ + */ +struct pw_control; + +#include <pipewire/impl.h> + +/** Port events, use \ref pw_control_add_listener */ +struct pw_control_events { +#define PW_VERSION_CONTROL_EVENTS 0 + uint32_t version; + + /** The control is destroyed */ + void (*destroy) (void *data); + + /** The control is freed */ + void (*free) (void *data); + + /** control is linked to another control */ + void (*linked) (void *data, struct pw_control *other); + /** control is unlinked from another control */ + void (*unlinked) (void *data, struct pw_control *other); + +}; + +/** Get the control parent port or NULL when not set */ +struct pw_impl_port *pw_control_get_port(struct pw_control *control); + +/** Add an event listener on the control */ +void pw_control_add_listener(struct pw_control *control, + struct spa_hook *listener, + const struct pw_control_events *events, + void *data); + +/** + * \} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* PIPEWIRE_CONTROL_H */ diff --git a/src/pipewire/core.c b/src/pipewire/core.c new file mode 100644 index 0000000..dd7e05e --- /dev/null +++ b/src/pipewire/core.c @@ -0,0 +1,495 @@ +/* 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 <stdio.h> +#include <unistd.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <errno.h> +#include <sys/mman.h> + +#include <spa/pod/parser.h> +#include <spa/debug/types.h> + +#include "pipewire/pipewire.h" +#include "pipewire/private.h" + +#include "pipewire/extensions/protocol-native.h" + +PW_LOG_TOPIC_EXTERN(log_core); +#define PW_LOG_TOPIC_DEFAULT log_core + +static void core_event_ping(void *data, uint32_t id, int seq) +{ + struct pw_core *this = data; + pw_log_debug("%p: object %u ping %u", this, id, seq); + pw_core_pong(this->core, id, seq); +} + +static void core_event_done(void *data, uint32_t id, int seq) +{ + struct pw_core *this = data; + struct pw_proxy *proxy; + + pw_log_trace("%p: object %u done %d", this, id, seq); + + proxy = pw_map_lookup(&this->objects, id); + if (proxy) + pw_proxy_emit_done(proxy, seq); +} + +static void core_event_error(void *data, uint32_t id, int seq, int res, const char *message) +{ + struct pw_core *this = data; + struct pw_proxy *proxy; + + proxy = pw_map_lookup(&this->objects, id); + + pw_log_debug("%p: proxy %p id:%u: bound:%d seq:%d res:%d (%s) msg:\"%s\"", + this, proxy, id, proxy ? proxy->bound_id : SPA_ID_INVALID, + seq, res, spa_strerror(res), message); + if (proxy) + pw_proxy_emit_error(proxy, seq, res, message); +} + +static void core_event_remove_id(void *data, uint32_t id) +{ + struct pw_core *this = data; + struct pw_proxy *proxy; + + pw_log_debug("%p: object remove %u", this, id); + if ((proxy = pw_map_lookup(&this->objects, id)) != NULL) + pw_proxy_remove(proxy); +} + +static void core_event_bound_id(void *data, uint32_t id, uint32_t global_id) +{ + struct pw_core *this = data; + struct pw_proxy *proxy; + + pw_log_debug("%p: proxy id %u bound %u", this, id, global_id); + if ((proxy = pw_map_lookup(&this->objects, id)) != NULL) { + pw_proxy_set_bound_id(proxy, global_id); + } +} + +static void core_event_add_mem(void *data, uint32_t id, uint32_t type, int fd, uint32_t flags) +{ + struct pw_core *this = data; + struct pw_memblock *m; + + pw_log_debug("%p: add mem %u type:%u fd:%d flags:%u", this, id, type, fd, flags); + + m = pw_mempool_import(this->pool, flags, type, fd); + if (m->id != id) { + pw_log_error("%p: invalid mem id %u, fd:%d expected %u", + this, id, fd, m->id); + pw_proxy_errorf(&this->proxy, -EINVAL, "invalid mem id %u, expected %u", id, m->id); + pw_memblock_unref(m); + } +} + +static void core_event_remove_mem(void *data, uint32_t id) +{ + struct pw_core *this = data; + pw_log_debug("%p: remove mem %u", this, id); + pw_mempool_remove_id(this->pool, id); +} + +static const struct pw_core_events core_events = { + PW_VERSION_CORE_EVENTS, + .error = core_event_error, + .ping = core_event_ping, + .done = core_event_done, + .remove_id = core_event_remove_id, + .bound_id = core_event_bound_id, + .add_mem = core_event_add_mem, + .remove_mem = core_event_remove_mem, +}; + +SPA_EXPORT +struct pw_context *pw_core_get_context(struct pw_core *core) +{ + return core->context; +} + +SPA_EXPORT +const struct pw_properties *pw_core_get_properties(struct pw_core *core) +{ + return core->properties; +} + +SPA_EXPORT +int pw_core_update_properties(struct pw_core *core, const struct spa_dict *dict) +{ + int changed; + + changed = pw_properties_update(core->properties, dict); + + pw_log_debug("%p: updated %d properties", core, changed); + + if (!changed) + return 0; + + if (core->client) + pw_client_update_properties(core->client, &core->properties->dict); + + return changed; +} + +SPA_EXPORT +void *pw_core_get_user_data(struct pw_core *core) +{ + return core->user_data; +} + +static int remove_proxy(void *object, void *data) +{ + struct pw_core *core = data; + struct pw_proxy *p = object; + + if (object == NULL) + return 0; + + if (object != core) + pw_proxy_remove(p); + + return 0; +} + +static int destroy_proxy(void *object, void *data) +{ + struct pw_core *core = data; + struct pw_proxy *p = object; + + if (object == NULL) + return 0; + + if (object != core) { + pw_log_warn("%p: leaked proxy %p id:%d", core, p, p->id); + p->core = NULL; + } + return 0; +} + +static void proxy_core_removed(void *data) +{ + struct pw_core *core = data; + struct pw_stream *stream, *s2; + struct pw_filter *filter, *f2; + + if (core->removed) + return; + + core->removed = true; + + pw_log_debug("%p: core proxy removed", core); + spa_list_remove(&core->link); + + spa_list_for_each_safe(stream, s2, &core->stream_list, link) + pw_stream_disconnect(stream); + spa_list_for_each_safe(filter, f2, &core->filter_list, link) + pw_filter_disconnect(filter); + + pw_map_for_each(&core->objects, remove_proxy, core); +} + +static void proxy_core_destroy(void *data) +{ + struct pw_core *core = data; + struct pw_stream *stream; + struct pw_filter *filter; + + if (core->destroyed) + return; + + core->destroyed = true; + + pw_log_debug("%p: core proxy destroy", core); + + spa_list_consume(stream, &core->stream_list, link) + pw_stream_destroy(stream); + spa_list_consume(filter, &core->filter_list, link) + pw_filter_destroy(filter); + + pw_proxy_destroy((struct pw_proxy*)core->client); + + pw_map_for_each(&core->objects, destroy_proxy, core); + pw_map_reset(&core->objects); + + pw_protocol_client_disconnect(core->conn); + + pw_mempool_destroy(core->pool); + + pw_protocol_client_destroy(core->conn); + + pw_map_clear(&core->objects); + + pw_log_debug("%p: free", core); + pw_properties_free(core->properties); + + spa_hook_remove(&core->core_listener); + spa_hook_remove(&core->proxy_core_listener); +} + +static const struct pw_proxy_events proxy_core_events = { + PW_VERSION_PROXY_EVENTS, + .removed = proxy_core_removed, + .destroy = proxy_core_destroy, +}; + +SPA_EXPORT +struct pw_client * pw_core_get_client(struct pw_core *core) +{ + return core->client; +} + +SPA_EXPORT +struct pw_proxy *pw_core_find_proxy(struct pw_core *core, uint32_t id) +{ + return pw_map_lookup(&core->objects, id); +} + +SPA_EXPORT +struct pw_proxy *pw_core_export(struct pw_core *core, + const char *type, const struct spa_dict *props, void *object, + size_t user_data_size) +{ + struct pw_proxy *proxy; + const struct pw_export_type *t; + int res; + + t = pw_context_find_export_type(core->context, type); + if (t == NULL) { + res = -EPROTO; + goto error_export_type; + } + + proxy = t->func(core, t->type, props, object, user_data_size); + if (proxy == NULL) { + res = -errno; + goto error_proxy_failed; + } + pw_log_debug("%p: export:%s proxy:%p", core, type, proxy); + return proxy; + +error_export_type: + pw_log_error("%p: can't export type %s: %s", core, type, spa_strerror(res)); + goto exit; +error_proxy_failed: + pw_log_error("%p: failed to create proxy: %s", core, spa_strerror(res)); + goto exit; +exit: + errno = -res; + return NULL; +} + +static struct pw_core *core_new(struct pw_context *context, + struct pw_properties *properties, size_t user_data_size) +{ + struct pw_core *p; + struct pw_protocol *protocol; + const char *protocol_name; + int res; + + p = calloc(1, sizeof(struct pw_core) + user_data_size); + if (p == NULL) { + res = -errno; + goto exit_cleanup; + } + pw_log_debug("%p: new", p); + + if (properties == NULL) + properties = pw_properties_new(NULL, NULL); + if (properties == NULL) + goto error_properties; + + pw_properties_add(properties, &context->properties->dict); + + p->proxy.core = p; + p->context = context; + p->properties = properties; + p->pool = pw_mempool_new(NULL); + p->core = p; + if (user_data_size > 0) + p->user_data = SPA_PTROFF(p, sizeof(struct pw_core), void); + p->proxy.user_data = p->user_data; + + pw_map_init(&p->objects, 64, 32); + spa_list_init(&p->stream_list); + spa_list_init(&p->filter_list); + + if ((protocol_name = pw_properties_get(properties, PW_KEY_PROTOCOL)) == NULL && + (protocol_name = pw_properties_get(context->properties, PW_KEY_PROTOCOL)) == NULL) + protocol_name = PW_TYPE_INFO_PROTOCOL_Native; + + protocol = pw_context_find_protocol(context, protocol_name); + if (protocol == NULL) { + res = -ENOTSUP; + goto error_protocol; + } + + p->conn = pw_protocol_new_client(protocol, p, &properties->dict); + if (p->conn == NULL) + goto error_connection; + + if ((res = pw_proxy_init(&p->proxy, PW_TYPE_INTERFACE_Core, PW_VERSION_CORE)) < 0) + goto error_proxy; + + p->client = (struct pw_client*)pw_proxy_new(&p->proxy, + PW_TYPE_INTERFACE_Client, PW_VERSION_CLIENT, 0); + if (p->client == NULL) { + res = -errno; + goto error_proxy; + } + + pw_core_add_listener(p, &p->core_listener, &core_events, p); + pw_proxy_add_listener(&p->proxy, &p->proxy_core_listener, &proxy_core_events, p); + + pw_core_hello(p, PW_VERSION_CORE); + pw_client_update_properties(p->client, &p->properties->dict); + + spa_list_append(&context->core_list, &p->link); + + return p; + +error_properties: + res = -errno; + pw_log_error("%p: can't create properties: %m", p); + goto exit_free; +error_protocol: + pw_log_error("%p: can't find protocol '%s': %s", p, protocol_name, spa_strerror(res)); + goto exit_free; +error_connection: + res = -errno; + pw_log_error("%p: can't create new native protocol connection: %m", p); + goto exit_free; +error_proxy: + pw_log_error("%p: can't initialize proxy: %s", p, spa_strerror(res)); + goto exit_free; + +exit_free: + free(p); +exit_cleanup: + pw_properties_free(properties); + errno = -res; + return NULL; +} + +SPA_EXPORT +struct pw_core * +pw_context_connect(struct pw_context *context, struct pw_properties *properties, + size_t user_data_size) +{ + struct pw_core *core; + int res; + + core = core_new(context, properties, user_data_size); + if (core == NULL) + return NULL; + + pw_log_debug("%p: connect", core); + + if ((res = pw_protocol_client_connect(core->conn, + &core->properties->dict, + NULL, NULL)) < 0) + goto error_free; + + return core; + +error_free: + pw_core_disconnect(core); + errno = -res; + return NULL; +} + +SPA_EXPORT +struct pw_core * +pw_context_connect_fd(struct pw_context *context, int fd, struct pw_properties *properties, + size_t user_data_size) +{ + struct pw_core *core; + int res; + + core = core_new(context, properties, user_data_size); + if (core == NULL) + return NULL; + + pw_log_debug("%p: connect fd:%d", core, fd); + + if ((res = pw_protocol_client_connect_fd(core->conn, fd, true)) < 0) + goto error_free; + + return core; + +error_free: + pw_core_disconnect(core); + errno = -res; + return NULL; +} + +SPA_EXPORT +struct pw_core * +pw_context_connect_self(struct pw_context *context, struct pw_properties *properties, + size_t user_data_size) +{ + if (properties == NULL) + properties = pw_properties_new(NULL, NULL); + if (properties == NULL) + return NULL; + + pw_properties_set(properties, PW_KEY_REMOTE_NAME, "internal"); + + return pw_context_connect(context, properties, user_data_size); +} + +SPA_EXPORT +int pw_core_steal_fd(struct pw_core *core) +{ + int fd = pw_protocol_client_steal_fd(core->conn); + pw_log_debug("%p: fd:%d", core, fd); + return fd; +} + +SPA_EXPORT +int pw_core_set_paused(struct pw_core *core, bool paused) +{ + pw_log_debug("%p: state:%s", core, paused ? "pause" : "resume"); + return pw_protocol_client_set_paused(core->conn, paused); +} + +SPA_EXPORT +struct pw_mempool * pw_core_get_mempool(struct pw_core *core) +{ + return core->pool; +} + +SPA_EXPORT +int pw_core_disconnect(struct pw_core *core) +{ + pw_log_debug("%p: disconnect", core); + pw_proxy_remove(&core->proxy); + pw_proxy_destroy(&core->proxy); + return 0; +} diff --git a/src/pipewire/core.h b/src/pipewire/core.h new file mode 100644 index 0000000..604e7cc --- /dev/null +++ b/src/pipewire/core.h @@ -0,0 +1,629 @@ +/* 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. + */ + +#ifndef PIPEWIRE_CORE_H +#define PIPEWIRE_CORE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <stdarg.h> +#include <errno.h> + +#include <spa/utils/hook.h> + +/** \defgroup pw_core Core + * + * \brief The core global object. + * + * This is a special singleton object. It is used for internal PipeWire + * protocol features. Connecting to a PipeWire instance returns one core + * object, the caller should then register event listeners + * using \ref pw_core_add_listener. + * + * Updates to the core object are then provided through the \ref + * pw_core_events interface. See \ref page_tutorial2 for an example. + */ + +/** + * \addtogroup pw_core + * \{ + */ +#define PW_TYPE_INTERFACE_Core PW_TYPE_INFO_INTERFACE_BASE "Core" +#define PW_TYPE_INTERFACE_Registry PW_TYPE_INFO_INTERFACE_BASE "Registry" + +#define PW_VERSION_CORE 3 +struct pw_core; +#define PW_VERSION_REGISTRY 3 +struct pw_registry; + +/** The default remote name to connect to */ +#define PW_DEFAULT_REMOTE "pipewire-0" + +/** default ID for the core object after connect */ +#define PW_ID_CORE 0 + +/* invalid ID that matches any object when used for permissions */ +#define PW_ID_ANY (uint32_t)(0xffffffff) + +/** The core information. Extra information may be added in later versions, + * clients must not assume a constant struct size */ +struct pw_core_info { + uint32_t id; /**< id of the global */ + uint32_t cookie; /**< a random cookie for identifying this instance of PipeWire */ + const char *user_name; /**< name of the user that started the core */ + const char *host_name; /**< name of the machine the core is running on */ + const char *version; /**< version of the core */ + const char *name; /**< name of the core */ +#define PW_CORE_CHANGE_MASK_PROPS (1 << 0) +#define PW_CORE_CHANGE_MASK_ALL ((1 << 1)-1) + uint64_t change_mask; /**< bitfield of changed fields since last call */ + struct spa_dict *props; /**< extra properties */ +}; + +#include <pipewire/context.h> +#include <pipewire/properties.h> +#include <pipewire/proxy.h> + +/** Update an existing \ref pw_core_info with \a update with reset */ +struct pw_core_info * +pw_core_info_update(struct pw_core_info *info, + const struct pw_core_info *update); +/** Update an existing \ref pw_core_info with \a update */ +struct pw_core_info * +pw_core_info_merge(struct pw_core_info *info, + const struct pw_core_info *update, bool reset); +/** Free a \ref pw_core_info */ +void pw_core_info_free(struct pw_core_info *info); + +/** Core */ + +#define PW_CORE_EVENT_INFO 0 +#define PW_CORE_EVENT_DONE 1 +#define PW_CORE_EVENT_PING 2 +#define PW_CORE_EVENT_ERROR 3 +#define PW_CORE_EVENT_REMOVE_ID 4 +#define PW_CORE_EVENT_BOUND_ID 5 +#define PW_CORE_EVENT_ADD_MEM 6 +#define PW_CORE_EVENT_REMOVE_MEM 7 +#define PW_CORE_EVENT_NUM 8 + +/** \struct pw_core_events + * \brief Core events + */ +struct pw_core_events { +#define PW_VERSION_CORE_EVENTS 0 + uint32_t version; + + /** + * Notify new core info + * + * This event is emitted when first bound to the core or when the + * hello method is called. + * + * \param info new core info + */ + void (*info) (void *data, const struct pw_core_info *info); + /** + * Emit a done event + * + * The done event is emitted as a result of a sync method with the + * same seq number. + * + * \param seq the seq number passed to the sync method call + */ + void (*done) (void *data, uint32_t id, int seq); + + /** Emit a ping event + * + * The client should reply with a pong reply with the same seq + * number. + */ + void (*ping) (void *data, uint32_t id, int seq); + + /** + * Fatal error event + * + * The error event is sent out when a fatal (non-recoverable) + * error has occurred. The id argument is the proxy object where + * the error occurred, most often in response to a request to that + * object. The message is a brief description of the error, + * for (debugging) convenience. + * + * This event is usually also emitted on the proxy object with + * \a id. + * + * \param id object where the error occurred + * \param seq the sequence number that generated the error + * \param res error code + * \param message error description + */ + void (*error) (void *data, uint32_t id, int seq, int res, const char *message); + /** + * Remove an object ID + * + * This event is used internally by the object ID management + * logic. When a client deletes an object, the server will send + * this event to acknowledge that it has seen the delete request. + * When the client receives this event, it will know that it can + * safely reuse the object ID. + * + * \param id deleted object ID + */ + void (*remove_id) (void *data, uint32_t id); + + /** + * Notify an object binding + * + * This event is emitted when a local object ID is bound to a + * global ID. It is emitted before the global becomes visible in the + * registry. + * + * \param id bound object ID + * \param global_id the global id bound to + */ + void (*bound_id) (void *data, uint32_t id, uint32_t global_id); + + /** + * Add memory for a client + * + * Memory is given to a client as \a fd of a certain + * memory \a type. + * + * Further references to this fd will be made with the per memory + * unique identifier \a id. + * + * \param id the unique id of the memory + * \param type the memory type, one of enum spa_data_type + * \param fd the file descriptor + * \param flags extra flags + */ + void (*add_mem) (void *data, uint32_t id, uint32_t type, int fd, uint32_t flags); + + /** + * Remove memory for a client + * + * \param id the memory id to remove + */ + void (*remove_mem) (void *data, uint32_t id); +}; + +#define PW_CORE_METHOD_ADD_LISTENER 0 +#define PW_CORE_METHOD_HELLO 1 +#define PW_CORE_METHOD_SYNC 2 +#define PW_CORE_METHOD_PONG 3 +#define PW_CORE_METHOD_ERROR 4 +#define PW_CORE_METHOD_GET_REGISTRY 5 +#define PW_CORE_METHOD_CREATE_OBJECT 6 +#define PW_CORE_METHOD_DESTROY 7 +#define PW_CORE_METHOD_NUM 8 + +/** + * \struct pw_core_methods + * \brief Core methods + * + * The core global object. This is a singleton object used for + * creating new objects in the remote PipeWire instance. It is + * also used for internal features. + */ +struct pw_core_methods { +#define PW_VERSION_CORE_METHODS 0 + uint32_t version; + + int (*add_listener) (void *object, + struct spa_hook *listener, + const struct pw_core_events *events, + void *data); + /** + * Start a conversation with the server. This will send + * the core info and will destroy all resources for the client + * (except the core and client resource). + */ + int (*hello) (void *object, uint32_t version); + /** + * Do server roundtrip + * + * Ask the server to emit the 'done' event with \a seq. + * + * Since methods are handled in-order and events are delivered + * in-order, this can be used as a barrier to ensure all previous + * methods and the resulting events have been handled. + * + * \param seq the seq number passed to the done event + */ + int (*sync) (void *object, uint32_t id, int seq); + /** + * Reply to a server ping event. + * + * Reply to the server ping event with the same seq. + * + * \param seq the seq number received in the ping event + */ + int (*pong) (void *object, uint32_t id, int seq); + /** + * Fatal error event + * + * The error method is sent out when a fatal (non-recoverable) + * error has occurred. The id argument is the proxy object where + * the error occurred, most often in response to an event on that + * object. The message is a brief description of the error, + * for (debugging) convenience. + * + * This method is usually also emitted on the resource object with + * \a id. + * + * \param id object where the error occurred + * \param res error code + * \param message error description + */ + int (*error) (void *object, uint32_t id, int seq, int res, const char *message); + /** + * Get the registry object + * + * Create a registry object that allows the client to list and bind + * the global objects available from the PipeWire server + * \param version the client version + * \param user_data_size extra size + */ + struct pw_registry * (*get_registry) (void *object, uint32_t version, + size_t user_data_size); + + /** + * Create a new object on the PipeWire server from a factory. + * + * \param factory_name the factory name to use + * \param type the interface to bind to + * \param version the version of the interface + * \param props extra properties + * \param user_data_size extra size + */ + void * (*create_object) (void *object, + const char *factory_name, + const char *type, + uint32_t version, + const struct spa_dict *props, + size_t user_data_size); + /** + * Destroy an resource + * + * Destroy the server resource for the given proxy. + * + * \param obj the proxy to destroy + */ + int (*destroy) (void *object, void *proxy); +}; + +#define pw_core_method(o,method,version,...) \ +({ \ + int _res = -ENOTSUP; \ + spa_interface_call_res((struct spa_interface*)o, \ + struct pw_core_methods, _res, \ + method, version, ##__VA_ARGS__); \ + _res; \ +}) + +#define pw_core_add_listener(c,...) pw_core_method(c,add_listener,0,__VA_ARGS__) +#define pw_core_hello(c,...) pw_core_method(c,hello,0,__VA_ARGS__) +#define pw_core_sync(c,...) pw_core_method(c,sync,0,__VA_ARGS__) +#define pw_core_pong(c,...) pw_core_method(c,pong,0,__VA_ARGS__) +#define pw_core_error(c,...) pw_core_method(c,error,0,__VA_ARGS__) + + +static inline +SPA_PRINTF_FUNC(5, 0) int +pw_core_errorv(struct pw_core *core, uint32_t id, int seq, + int res, const char *message, va_list args) +{ + char buffer[1024]; + vsnprintf(buffer, sizeof(buffer), message, args); + buffer[1023] = '\0'; + return pw_core_error(core, id, seq, res, buffer); +} + +static inline +SPA_PRINTF_FUNC(5, 6) int +pw_core_errorf(struct pw_core *core, uint32_t id, int seq, + int res, const char *message, ...) +{ + va_list args; + int r; + va_start(args, message); + r = pw_core_errorv(core, id, seq, res, message, args); + va_end(args); + return r; +} + +static inline struct pw_registry * +pw_core_get_registry(struct pw_core *core, uint32_t version, size_t user_data_size) +{ + struct pw_registry *res = NULL; + spa_interface_call_res((struct spa_interface*)core, + struct pw_core_methods, res, + get_registry, 0, version, user_data_size); + return res; +} + +static inline void * +pw_core_create_object(struct pw_core *core, + const char *factory_name, + const char *type, + uint32_t version, + const struct spa_dict *props, + size_t user_data_size) +{ + void *res = NULL; + spa_interface_call_res((struct spa_interface*)core, + struct pw_core_methods, res, + create_object, 0, factory_name, + type, version, props, user_data_size); + return res; +} + +#define pw_core_destroy(c,...) pw_core_method(c,destroy,0,__VA_ARGS__) + +/** + * \} + */ + +/** \defgroup pw_registry Registry + * + * The registry object is a singleton object that keeps track of + * global objects on the PipeWire instance. See also \ref pw_global. + * + * Global objects typically represent an actual object in PipeWire + * (for example, a module or node) or they are singleton + * objects such as the core. + * + * When a client creates a registry object, the registry object + * will emit a global event for each global currently in the + * registry. Globals come and go as a result of device hotplugs or + * reconfiguration or other events, and the registry will send out + * global and global_remove events to keep the client up to date + * with the changes. To mark the end of the initial burst of + * events, the client can use the pw_core.sync methosd immediately + * after calling pw_core.get_registry. + * + * A client can bind to a global object by using the bind + * request. This creates a client-side proxy that lets the object + * emit events to the client and lets the client invoke methods on + * the object. See \ref page_proxy + * + * Clients can also change the permissions of the global objects that + * it can see. This is interesting when you want to configure a + * pipewire session before handing it to another application. You + * can, for example, hide certain existing or new objects or limit + * the access permissions on an object. + */ + +/** + * \addtogroup pw_registry + * \{ + */ + +#define PW_REGISTRY_EVENT_GLOBAL 0 +#define PW_REGISTRY_EVENT_GLOBAL_REMOVE 1 +#define PW_REGISTRY_EVENT_NUM 2 + +/** Registry events */ +struct pw_registry_events { +#define PW_VERSION_REGISTRY_EVENTS 0 + uint32_t version; + /** + * Notify of a new global object + * + * The registry emits this event when a new global object is + * available. + * + * \param id the global object id + * \param permissions the permissions of the object + * \param type the type of the interface + * \param version the version of the interface + * \param props extra properties of the global + */ + void (*global) (void *data, uint32_t id, + uint32_t permissions, const char *type, uint32_t version, + const struct spa_dict *props); + /** + * Notify of a global object removal + * + * Emitted when a global object was removed from the registry. + * If the client has any bindings to the global, it should destroy + * those. + * + * \param id the id of the global that was removed + */ + void (*global_remove) (void *data, uint32_t id); +}; + +#define PW_REGISTRY_METHOD_ADD_LISTENER 0 +#define PW_REGISTRY_METHOD_BIND 1 +#define PW_REGISTRY_METHOD_DESTROY 2 +#define PW_REGISTRY_METHOD_NUM 3 + +/** Registry methods */ +struct pw_registry_methods { +#define PW_VERSION_REGISTRY_METHODS 0 + uint32_t version; + + int (*add_listener) (void *object, + struct spa_hook *listener, + const struct pw_registry_events *events, + void *data); + /** + * Bind to a global object + * + * Bind to the global object with \a id and use the client proxy + * with new_id as the proxy. After this call, methods can be + * send to the remote global object and events can be received + * + * \param id the global id to bind to + * \param type the interface type to bind to + * \param version the interface version to use + * \returns the new object + */ + void * (*bind) (void *object, uint32_t id, const char *type, uint32_t version, + size_t use_data_size); + + /** + * Attempt to destroy a global object + * + * Try to destroy the global object. + * + * \param id the global id to destroy + */ + int (*destroy) (void *object, uint32_t id); +}; + +#define pw_registry_method(o,method,version,...) \ +({ \ + int _res = -ENOTSUP; \ + spa_interface_call_res((struct spa_interface*)o, \ + struct pw_registry_methods, _res, \ + method, version, ##__VA_ARGS__); \ + _res; \ +}) + +/** Registry */ +#define pw_registry_add_listener(p,...) pw_registry_method(p,add_listener,0,__VA_ARGS__) + +static inline void * +pw_registry_bind(struct pw_registry *registry, + uint32_t id, const char *type, uint32_t version, + size_t user_data_size) +{ + void *res = NULL; + spa_interface_call_res((struct spa_interface*)registry, + struct pw_registry_methods, res, + bind, 0, id, type, version, user_data_size); + return res; +} + +#define pw_registry_destroy(p,...) pw_registry_method(p,destroy,0,__VA_ARGS__) + +/** + * \} + */ + +/** + * \addtogroup pw_core + * \{ + */ + +/** Connect to a PipeWire instance + * + * \param context a \ref pw_context + * \param properties optional properties, ownership of the properties is + * taken. + * \param user_data_size extra user data size + * + * \return a \ref pw_core on success or NULL with errno set on error. The core + * will have an id of \ref PW_ID_CORE (0) + */ +struct pw_core * +pw_context_connect(struct pw_context *context, + struct pw_properties *properties, + size_t user_data_size); + +/** Connect to a PipeWire instance on the given socket + * + * \param context a \ref pw_context + * \param fd the connected socket to use, the socket will be closed + * automatically on disconnect or error. + * \param properties optional properties, ownership of the properties is + * taken. + * \param user_data_size extra user data size + * + * \return a \ref pw_core on success or NULL with errno set on error */ +struct pw_core * +pw_context_connect_fd(struct pw_context *context, + int fd, + struct pw_properties *properties, + size_t user_data_size); + +/** Connect to a given PipeWire instance + * + * \param context a \ref pw_context to connect to + * \param properties optional properties, ownership of the properties is + * taken. + * \param user_data_size extra user data size + * + * \return a \ref pw_core on success or NULL with errno set on error */ +struct pw_core * +pw_context_connect_self(struct pw_context *context, + struct pw_properties *properties, + size_t user_data_size); + +/** Steal the fd of the core connection or < 0 on error. The core + * will be disconnected after this call. */ +int pw_core_steal_fd(struct pw_core *core); + +/** Pause or resume the core. When the core is paused, no new events + * will be dispatched until the core is resumed again. */ +int pw_core_set_paused(struct pw_core *core, bool paused); + +/** disconnect and destroy a core */ +int pw_core_disconnect(struct pw_core *core); + +/** Get the user_data. It is of the size specified when this object was + * constructed */ +void *pw_core_get_user_data(struct pw_core *core); + +/** Get the client proxy of the connected core. This will have the id + * of PW_ID_CLIENT (1) */ +struct pw_client * pw_core_get_client(struct pw_core *core); + +/** Get the context object used to created this core */ +struct pw_context * pw_core_get_context(struct pw_core *core); + +/** Get properties from the core */ +const struct pw_properties *pw_core_get_properties(struct pw_core *core); + +/** Update the core properties. This updates the properties + * of the associated client. + * \return the number of properties that were updated */ +int pw_core_update_properties(struct pw_core *core, const struct spa_dict *dict); + +/** Get the core mempool object */ +struct pw_mempool * pw_core_get_mempool(struct pw_core *core); + +/** Get the proxy with the given id */ +struct pw_proxy *pw_core_find_proxy(struct pw_core *core, uint32_t id); + +/** Export an object into the PipeWire instance associated with core */ +struct pw_proxy *pw_core_export(struct pw_core *core, /**< the core */ + const char *type, /**< the type of object */ + const struct spa_dict *props, /**< extra properties */ + void *object, /**< object to export */ + size_t user_data_size /**< extra user data */); + +/** + * \} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* PIPEWIRE_CORE_H */ diff --git a/src/pipewire/data-loop.c b/src/pipewire/data-loop.c new file mode 100644 index 0000000..4f4a2bc --- /dev/null +++ b/src/pipewire/data-loop.c @@ -0,0 +1,292 @@ +/* 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 <pthread.h> +#include <errno.h> +#include <sys/resource.h> + +#include "pipewire/log.h" +#include "pipewire/data-loop.h" +#include "pipewire/private.h" +#include "pipewire/thread.h" + +PW_LOG_TOPIC_EXTERN(log_data_loop); +#define PW_LOG_TOPIC_DEFAULT log_data_loop + +SPA_EXPORT +int pw_data_loop_wait(struct pw_data_loop *this, int timeout) +{ + int res; + + while (true) { + if (SPA_UNLIKELY(!this->running)) { + res = -ECANCELED; + break; + } + if (SPA_UNLIKELY((res = pw_loop_iterate(this->loop, timeout)) < 0)) { + if (res == -EINTR) + continue; + } + break; + } + return res; +} + +SPA_EXPORT +void pw_data_loop_exit(struct pw_data_loop *this) +{ + this->running = false; +} + +static void thread_cleanup(void *arg) +{ + struct pw_data_loop *this = arg; + pw_log_debug("%p: leave thread", this); + this->running = false; + pw_loop_leave(this->loop); +} + +static void *do_loop(void *user_data) +{ + struct pw_data_loop *this = user_data; + int res; + + pw_log_debug("%p: enter thread", this); + pw_loop_enter(this->loop); + + pthread_cleanup_push(thread_cleanup, this); + + while (SPA_LIKELY(this->running)) { + if (SPA_UNLIKELY((res = pw_loop_iterate(this->loop, -1)) < 0)) { + if (res == -EINTR) + continue; + pw_log_error("%p: iterate error %d (%s)", + this, res, spa_strerror(res)); + } + } + pthread_cleanup_pop(1); + + return NULL; +} + +static int do_stop(struct spa_loop *loop, bool async, uint32_t seq, + const void *data, size_t size, void *user_data) +{ + struct pw_data_loop *this = user_data; + pw_log_debug("%p: stopping", this); + this->running = false; + return 0; +} + +static struct pw_data_loop *loop_new(struct pw_loop *loop, const struct spa_dict *props) +{ + struct pw_data_loop *this; + const char *str; + int res; + + this = calloc(1, sizeof(struct pw_data_loop)); + if (this == NULL) { + res = -errno; + goto error_cleanup; + } + + pw_log_debug("%p: new", this); + + if (loop == NULL) { + loop = pw_loop_new(props); + this->created = true; + } + if (loop == NULL) { + res = -errno; + pw_log_error("%p: can't create loop: %m", this); + goto error_free; + } + this->loop = loop; + + if (props != NULL && + (str = spa_dict_lookup(props, "loop.cancel")) != NULL) + this->cancel = pw_properties_parse_bool(str); + + spa_hook_list_init(&this->listener_list); + + return this; + +error_free: + free(this); +error_cleanup: + errno = -res; + return NULL; +} + +/** Create a new \ref pw_data_loop. + * \return a newly allocated data loop + * + */ +SPA_EXPORT +struct pw_data_loop *pw_data_loop_new(const struct spa_dict *props) +{ + return loop_new(NULL, props); +} + + +/** Destroy a data loop + * \param loop the data loop to destroy + */ +SPA_EXPORT +void pw_data_loop_destroy(struct pw_data_loop *loop) +{ + pw_log_debug("%p: destroy", loop); + + pw_data_loop_emit_destroy(loop); + + pw_data_loop_stop(loop); + + if (loop->created) + pw_loop_destroy(loop->loop); + + spa_hook_list_clean(&loop->listener_list); + + free(loop); +} + +SPA_EXPORT +void pw_data_loop_add_listener(struct pw_data_loop *loop, + struct spa_hook *listener, + const struct pw_data_loop_events *events, + void *data) +{ + spa_hook_list_append(&loop->listener_list, listener, events, data); +} + +SPA_EXPORT +struct pw_loop * +pw_data_loop_get_loop(struct pw_data_loop *loop) +{ + return loop->loop; +} + +/** Start a data loop + * \param loop the data loop to start + * \return 0 if ok, -1 on error + * + * This will start the realtime thread that manages the loop. + * + */ +SPA_EXPORT +int pw_data_loop_start(struct pw_data_loop *loop) +{ + if (!loop->running) { + struct spa_thread_utils *utils; + struct spa_thread *thr; + + loop->running = true; + + if ((utils = loop->thread_utils) == NULL) + utils = pw_thread_utils_get(); + thr = spa_thread_utils_create(utils, NULL, do_loop, loop); + loop->thread = (pthread_t)thr; + if (thr == NULL) { + pw_log_error("%p: can't create thread: %m", loop); + loop->running = false; + return -errno; + } + spa_thread_utils_acquire_rt(utils, thr, -1); + } + return 0; +} + +/** Stop a data loop + * \param loop the data loop to Stop + * \return 0 + * + * This will stop and join the realtime thread that manages the loop. + * + */ +SPA_EXPORT +int pw_data_loop_stop(struct pw_data_loop *loop) +{ + pw_log_debug("%p stopping", loop); + if (loop->running) { + struct spa_thread_utils *utils; + if (loop->cancel) { + pw_log_debug("%p cancel", loop); + pthread_cancel(loop->thread); + } else { + pw_log_debug("%p signal", loop); + pw_loop_invoke(loop->loop, do_stop, 1, NULL, 0, false, loop); + } + pw_log_debug("%p join", loop); + if ((utils = loop->thread_utils) == NULL) + utils = pw_thread_utils_get(); + spa_thread_utils_join(utils, (struct spa_thread*)loop->thread, NULL); + pw_log_debug("%p joined", loop); + } + pw_log_debug("%p stopped", loop); + return 0; +} + +/** Check if we are inside the data loop + * \param loop the data loop to check + * \return true is the current thread is the data loop thread + * + */ +SPA_EXPORT +bool pw_data_loop_in_thread(struct pw_data_loop * loop) +{ + return loop->running && pthread_equal(loop->thread, pthread_self()); +} + +/** Get the thread object. + * \param loop the data loop to get the thread of + * \return the thread object or NULL when the thread is not running + * + * On posix based systems this returns a pthread_t + */ +SPA_EXPORT +struct spa_thread *pw_data_loop_get_thread(struct pw_data_loop * loop) +{ + return loop->running ? (struct spa_thread*)loop->thread : NULL; +} + +SPA_EXPORT +int pw_data_loop_invoke(struct pw_data_loop *loop, + spa_invoke_func_t func, uint32_t seq, const void *data, size_t size, + bool block, void *user_data) +{ + return pw_loop_invoke(loop->loop, func, seq, data, size, block, user_data); +} + +/** Set a thread utils implementation. + * \param loop the data loop to set the thread utils on + * \param impl the thread utils implementation + * + * This configures a custom spa_thread_utils implementation for this data + * loop. Use NULL to restore the system default implementation. + */ +SPA_EXPORT +void pw_data_loop_set_thread_utils(struct pw_data_loop *loop, + struct spa_thread_utils *impl) +{ + loop->thread_utils = impl; +} diff --git a/src/pipewire/data-loop.h b/src/pipewire/data-loop.h new file mode 100644 index 0000000..a459c19 --- /dev/null +++ b/src/pipewire/data-loop.h @@ -0,0 +1,113 @@ +/* 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. + */ + +#ifndef PIPEWIRE_DATA_LOOP_H +#define PIPEWIRE_DATA_LOOP_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <spa/utils/hook.h> +#include <spa/support/thread.h> + +/** \defgroup pw_data_loop Data Loop + * + * \brief PipeWire rt-loop object + * + * This loop starts a new real-time thread that + * is designed to run the processing graph. + */ + +/** + * \addtogroup pw_data_loop + * \{ + */ +struct pw_data_loop; + +#include <pipewire/loop.h> +#include <pipewire/properties.h> + +/** Loop events, use \ref pw_data_loop_add_listener to add a listener */ +struct pw_data_loop_events { +#define PW_VERSION_DATA_LOOP_EVENTS 0 + uint32_t version; + /** The loop is destroyed */ + void (*destroy) (void *data); +}; + +/** Make a new loop. */ +struct pw_data_loop * +pw_data_loop_new(const struct spa_dict *props); + +/** Add an event listener to loop */ +void pw_data_loop_add_listener(struct pw_data_loop *loop, + struct spa_hook *listener, + const struct pw_data_loop_events *events, + void *data); + +/** wait for activity on the loop up to \a timeout milliseconds. + * Should be called from the loop function */ +int pw_data_loop_wait(struct pw_data_loop *loop, int timeout); + +/** make sure the thread will exit. Can be called from a loop callback */ +void pw_data_loop_exit(struct pw_data_loop *loop); + +/** Get the loop implementation of this data loop */ +struct pw_loop * +pw_data_loop_get_loop(struct pw_data_loop *loop); + +/** Destroy the loop */ +void pw_data_loop_destroy(struct pw_data_loop *loop); + +/** Start the processing thread */ +int pw_data_loop_start(struct pw_data_loop *loop); + +/** Stop the processing thread */ +int pw_data_loop_stop(struct pw_data_loop *loop); + +/** Check if the current thread is the processing thread */ +bool pw_data_loop_in_thread(struct pw_data_loop *loop); +/** Get the thread object */ +struct spa_thread *pw_data_loop_get_thread(struct pw_data_loop *loop); + +/** invoke func in the context of the thread or in the caller thread when + * the loop is not running. Since 0.3.3 */ +int pw_data_loop_invoke(struct pw_data_loop *loop, + spa_invoke_func_t func, uint32_t seq, const void *data, size_t size, + bool block, void *user_data); + +/** Set a custom spa_thread_utils for this loop. Setting NULL restores the + * system default implementation. Since 0.3.50 */ +void pw_data_loop_set_thread_utils(struct pw_data_loop *loop, + struct spa_thread_utils *impl); +/** + * \} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* PIPEWIRE_DATA_LOOP_H */ diff --git a/src/pipewire/device.h b/src/pipewire/device.h new file mode 100644 index 0000000..5bcaf80 --- /dev/null +++ b/src/pipewire/device.h @@ -0,0 +1,178 @@ +/* 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. + */ + +#ifndef PIPEWIRE_DEVICE_H +#define PIPEWIRE_DEVICE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <spa/utils/defs.h> +#include <spa/utils/hook.h> + +#include <pipewire/proxy.h> + +/** \defgroup pw_device Device + * Device interface + */ + +/** + * \addtogroup pw_device + * \{ + */ + +#define PW_TYPE_INTERFACE_Device PW_TYPE_INFO_INTERFACE_BASE "Device" + +#define PW_VERSION_DEVICE 3 +struct pw_device; + +/** The device information. Extra information can be added in later versions */ +struct pw_device_info { + uint32_t id; /**< id of the global */ +#define PW_DEVICE_CHANGE_MASK_PROPS (1 << 0) +#define PW_DEVICE_CHANGE_MASK_PARAMS (1 << 1) +#define PW_DEVICE_CHANGE_MASK_ALL ((1 << 2)-1) + uint64_t change_mask; /**< bitfield of changed fields since last call */ + struct spa_dict *props; /**< extra properties */ + struct spa_param_info *params; /**< parameters */ + uint32_t n_params; /**< number of items in \a params */ +}; + +/** Update and existing \ref pw_device_info with \a update and reset */ +struct pw_device_info * +pw_device_info_update(struct pw_device_info *info, + const struct pw_device_info *update); +/** Merge and existing \ref pw_device_info with \a update */ +struct pw_device_info * +pw_device_info_merge(struct pw_device_info *info, + const struct pw_device_info *update, bool reset); +/** Free a \ref pw_device_info */ +void pw_device_info_free(struct pw_device_info *info); + +#define PW_DEVICE_EVENT_INFO 0 +#define PW_DEVICE_EVENT_PARAM 1 +#define PW_DEVICE_EVENT_NUM 2 + +/** Device events */ +struct pw_device_events { +#define PW_VERSION_DEVICE_EVENTS 0 + uint32_t version; + /** + * Notify device info + * + * \param info info about the device + */ + void (*info) (void *data, const struct pw_device_info *info); + /** + * Notify a device param + * + * Event emitted as a result of the enum_params method. + * + * \param seq the sequence number of the request + * \param id the param id + * \param index the param index + * \param next the param index of the next param + * \param param the parameter + */ + void (*param) (void *data, int seq, + uint32_t id, uint32_t index, uint32_t next, + const struct spa_pod *param); +}; + + +#define PW_DEVICE_METHOD_ADD_LISTENER 0 +#define PW_DEVICE_METHOD_SUBSCRIBE_PARAMS 1 +#define PW_DEVICE_METHOD_ENUM_PARAMS 2 +#define PW_DEVICE_METHOD_SET_PARAM 3 +#define PW_DEVICE_METHOD_NUM 4 + +/** Device methods */ +struct pw_device_methods { +#define PW_VERSION_DEVICE_METHODS 0 + uint32_t version; + + int (*add_listener) (void *object, + struct spa_hook *listener, + const struct pw_device_events *events, + void *data); + /** + * Subscribe to parameter changes + * + * Automatically emit param events for the given ids when + * they are changed. + * + * \param ids an array of param ids + * \param n_ids the number of ids in \a ids + */ + int (*subscribe_params) (void *object, uint32_t *ids, uint32_t n_ids); + + /** + * Enumerate device parameters + * + * Start enumeration of device parameters. For each param, a + * param event will be emitted. + * + * \param seq a sequence number to place in the reply + * \param id the parameter id to enum or PW_ID_ANY for all + * \param start the start index or 0 for the first param + * \param num the maximum number of params to retrieve + * \param filter a param filter or NULL + */ + int (*enum_params) (void *object, int seq, uint32_t id, uint32_t start, uint32_t num, + const struct spa_pod *filter); + /** + * Set a parameter on the device + * + * \param id the parameter id to set + * \param flags extra parameter flags + * \param param the parameter to set + */ + int (*set_param) (void *object, uint32_t id, uint32_t flags, + const struct spa_pod *param); +}; + +#define pw_device_method(o,method,version,...) \ +({ \ + int _res = -ENOTSUP; \ + spa_interface_call_res((struct spa_interface*)o, \ + struct pw_device_methods, _res, \ + method, version, ##__VA_ARGS__); \ + _res; \ +}) + +#define pw_device_add_listener(c,...) pw_device_method(c,add_listener,0,__VA_ARGS__) +#define pw_device_subscribe_params(c,...) pw_device_method(c,subscribe_params,0,__VA_ARGS__) +#define pw_device_enum_params(c,...) pw_device_method(c,enum_params,0,__VA_ARGS__) +#define pw_device_set_param(c,...) pw_device_method(c,set_param,0,__VA_ARGS__) + +/** + * \} + */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* PIPEWIRE_DEVICE_H */ diff --git a/src/pipewire/extensions/client-node.h b/src/pipewire/extensions/client-node.h new file mode 100644 index 0000000..15dbae3 --- /dev/null +++ b/src/pipewire/extensions/client-node.h @@ -0,0 +1,357 @@ +/* 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. + */ + +#ifndef PIPEWIRE_EXT_CLIENT_NODE_H +#define PIPEWIRE_EXT_CLIENT_NODE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <spa/utils/defs.h> +#include <spa/param/param.h> + +/** \defgroup pw_client_node Client Node + * Client node interface + */ + +/** + * \addtogroup pw_client_node + * \{ + */ +#define PW_TYPE_INTERFACE_ClientNode PW_TYPE_INFO_INTERFACE_BASE "ClientNode" + +#define PW_VERSION_CLIENT_NODE 4 +struct pw_client_node; + +#define PW_EXTENSION_MODULE_CLIENT_NODE PIPEWIRE_MODULE_PREFIX "module-client-node" + +/** information about a buffer */ +struct pw_client_node_buffer { + uint32_t mem_id; /**< the memory id for the metadata */ + uint32_t offset; /**< offset in memory */ + uint32_t size; /**< size in memory */ + struct spa_buffer *buffer; /**< buffer describing metadata and buffer memory */ +}; + +#define PW_CLIENT_NODE_EVENT_TRANSPORT 0 +#define PW_CLIENT_NODE_EVENT_SET_PARAM 1 +#define PW_CLIENT_NODE_EVENT_SET_IO 2 +#define PW_CLIENT_NODE_EVENT_EVENT 3 +#define PW_CLIENT_NODE_EVENT_COMMAND 4 +#define PW_CLIENT_NODE_EVENT_ADD_PORT 5 +#define PW_CLIENT_NODE_EVENT_REMOVE_PORT 6 +#define PW_CLIENT_NODE_EVENT_PORT_SET_PARAM 7 +#define PW_CLIENT_NODE_EVENT_PORT_USE_BUFFERS 8 +#define PW_CLIENT_NODE_EVENT_PORT_SET_IO 9 +#define PW_CLIENT_NODE_EVENT_SET_ACTIVATION 10 +#define PW_CLIENT_NODE_EVENT_PORT_SET_MIX_INFO 11 +#define PW_CLIENT_NODE_EVENT_NUM 12 + +/** \ref pw_client_node events */ +struct pw_client_node_events { +#define PW_VERSION_CLIENT_NODE_EVENTS 1 + uint32_t version; + /** + * Notify of a new transport area + * + * The transport area is used to signal the client and the server. + * + * \param readfd fd for signal data can be read + * \param writefd fd for signal data can be written + * \param mem_id id for activation memory + * \param offset offset of activation memory + * \param size size of activation memory + */ + int (*transport) (void *data, + int readfd, + int writefd, + uint32_t mem_id, + uint32_t offset, + uint32_t size); + /** + * Notify of a property change + * + * When the server configures the properties on the node + * this event is sent + * + * \param id the id of the parameter + * \param flags parameter flags + * \param param the param to set + */ + int (*set_param) (void *data, + uint32_t id, uint32_t flags, + const struct spa_pod *param); + /** + * Configure an IO area for the client + * + * IO areas are identified with an id and are used to + * exchange state between client and server + * + * \param id the id of the io area + * \param mem_id the id of the memory to use + * \param offset offset of io area in memory + * \param size size of the io area + */ + int (*set_io) (void *data, + uint32_t id, + uint32_t mem_id, + uint32_t offset, + uint32_t size); + /** + * Receive an event from the client node + * \param event the received event */ + int (*event) (void *data, const struct spa_event *event); + /** + * Notify of a new node command + * + * \param command the command + */ + int (*command) (void *data, const struct spa_command *command); + /** + * A new port was added to the node + * + * The server can at any time add a port to the node when there + * are free ports available. + * + * \param direction the direction of the port + * \param port_id the new port id + * \param props extra properties + */ + int (*add_port) (void *data, + enum spa_direction direction, + uint32_t port_id, + const struct spa_dict *props); + /** + * A port was removed from the node + * + * \param direction a port direction + * \param port_id the remove port id + */ + int (*remove_port) (void *data, + enum spa_direction direction, + uint32_t port_id); + /** + * A parameter was configured on the port + * + * \param direction a port direction + * \param port_id the port id + * \param id the id of the parameter + * \param flags flags used when setting the param + * \param param the new param + */ + int (*port_set_param) (void *data, + enum spa_direction direction, + uint32_t port_id, + uint32_t id, uint32_t flags, + const struct spa_pod *param); + /** + * Notify the port of buffers + * + * \param direction a port direction + * \param port_id the port id + * \param mix_id the mixer port id + * \param n_buffer the number of buffers + * \param buffers and array of buffer descriptions + */ + int (*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); + /** + * Configure the io area with \a id of \a port_id. + * + * \param direction the direction of the port + * \param port_id the port id + * \param mix_id the mixer port id + * \param id the id of the io area to set + * \param mem_id the id of the memory to use + * \param offset offset of io area in memory + * \param size size of the io area + */ + int (*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); + + /** + * Notify the activation record of the next + * node to trigger + * + * \param node_id the peer node id + * \param signalfd the fd to wake up the peer + * \param mem_id the mem id of the memory + * \param the offset in \a mem_id to map + * \param the size of \a mem_id to map + */ + int (*set_activation) (void *data, + uint32_t node_id, + int signalfd, + uint32_t mem_id, + uint32_t offset, + uint32_t size); + + /** + * Notify about the peer of mix_id + * + * \param direction the direction of the port + * \param port_id the port id + * \param mix_id the mix id + * \param peer_id the id of the peer port + * \param props extra properties + * + * Since version 4:1 + */ + int (*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); +}; + +#define PW_CLIENT_NODE_METHOD_ADD_LISTENER 0 +#define PW_CLIENT_NODE_METHOD_GET_NODE 1 +#define PW_CLIENT_NODE_METHOD_UPDATE 2 +#define PW_CLIENT_NODE_METHOD_PORT_UPDATE 3 +#define PW_CLIENT_NODE_METHOD_SET_ACTIVE 4 +#define PW_CLIENT_NODE_METHOD_EVENT 5 +#define PW_CLIENT_NODE_METHOD_PORT_BUFFERS 6 +#define PW_CLIENT_NODE_METHOD_NUM 7 + +/** \ref pw_client_node methods */ +struct pw_client_node_methods { +#define PW_VERSION_CLIENT_NODE_METHODS 0 + uint32_t version; + + int (*add_listener) (void *object, + struct spa_hook *listener, + const struct pw_client_node_events *events, + void *data); + /** get the node object + */ + struct pw_node * (*get_node) (void *object, uint32_t version, size_t user_data_size); + /** + * Update the node ports and properties + * + * Update the maximum number of ports and the params of the + * client node. + * \param change_mask bitfield with changed parameters + * \param max_input_ports new max input ports + * \param max_output_ports new max output ports + * \param params new params + */ + int (*update) (void *object, +#define PW_CLIENT_NODE_UPDATE_PARAMS (1 << 0) +#define PW_CLIENT_NODE_UPDATE_INFO (1 << 1) + uint32_t change_mask, + uint32_t n_params, + const struct spa_pod **params, + const struct spa_node_info *info); + + /** + * Update a node port + * + * Update the information of one port of a node. + * \param direction the direction of the port + * \param port_id the port id to update + * \param change_mask a bitfield of changed items + * \param n_params number of port parameters + * \param params array of port parameters + * \param info port information + */ + int (*port_update) (void *object, + enum spa_direction direction, + uint32_t port_id, +#define PW_CLIENT_NODE_PORT_UPDATE_PARAMS (1 << 0) +#define PW_CLIENT_NODE_PORT_UPDATE_INFO (1 << 1) + uint32_t change_mask, + uint32_t n_params, + const struct spa_pod **params, + const struct spa_port_info *info); + /** + * Activate or deactivate the node + */ + int (*set_active) (void *object, bool active); + /** + * Send an event to the node + * \param event the event to send + */ + int (*event) (void *object, const struct spa_event *event); + + /** + * Send allocated buffers + */ + int (*port_buffers) (void *object, + enum spa_direction direction, + uint32_t port_id, + uint32_t mix_id, + uint32_t n_buffers, + struct spa_buffer **buffers); +}; + + +#define pw_client_node_method(o,method,version,...) \ +({ \ + int _res = -ENOTSUP; \ + spa_interface_call_res((struct spa_interface*)o, \ + struct pw_client_node_methods, _res, \ + method, version, ##__VA_ARGS__); \ + _res; \ +}) + +#define pw_client_node_add_listener(c,...) pw_client_node_method(c,add_listener,0,__VA_ARGS__) + +static inline struct pw_node * +pw_client_node_get_node(struct pw_client_node *p, uint32_t version, size_t user_data_size) +{ + struct pw_node *res = NULL; + spa_interface_call_res((struct spa_interface*)p, + struct pw_client_node_methods, res, + get_node, 0, version, user_data_size); + return res; +} + +#define pw_client_node_update(c,...) pw_client_node_method(c,update,0,__VA_ARGS__) +#define pw_client_node_port_update(c,...) pw_client_node_method(c,port_update,0,__VA_ARGS__) +#define pw_client_node_set_active(c,...) pw_client_node_method(c,set_active,0,__VA_ARGS__) +#define pw_client_node_event(c,...) pw_client_node_method(c,event,0,__VA_ARGS__) +#define pw_client_node_port_buffers(c,...) pw_client_node_method(c,port_buffers,0,__VA_ARGS__) + +/** + * \} + */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* PIPEWIRE_EXT_CLIENT_NODE_H */ diff --git a/src/pipewire/extensions/meson.build b/src/pipewire/extensions/meson.build new file mode 100644 index 0000000..983809c --- /dev/null +++ b/src/pipewire/extensions/meson.build @@ -0,0 +1,21 @@ +pipewire_ext_sm_headers = [ + 'session-manager/impl-interfaces.h', + 'session-manager/interfaces.h', + 'session-manager/introspect.h', + 'session-manager/introspect-funcs.h', + 'session-manager/keys.h', +] + +pipewire_ext_headers = [ + 'client-node.h', + 'metadata.h', + 'profiler.h', + 'protocol-native.h', + 'session-manager.h', +] + +install_headers(pipewire_ext_sm_headers, + subdir : pipewire_headers_dir / 'extensions' / 'session-manager') + +install_headers(pipewire_ext_headers, + subdir : pipewire_headers_dir / 'extensions') diff --git a/src/pipewire/extensions/metadata.h b/src/pipewire/extensions/metadata.h new file mode 100644 index 0000000..9541e2a --- /dev/null +++ b/src/pipewire/extensions/metadata.h @@ -0,0 +1,112 @@ +/* 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. + */ + +#ifndef PIPEWIRE_EXT_METADATA_H +#define PIPEWIRE_EXT_METADATA_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <spa/utils/defs.h> + +/** \defgroup pw_metadata Metadata + * Metadata interface + */ + +/** + * \addtogroup pw_metadata + * \{ + */ +#define PW_TYPE_INTERFACE_Metadata PW_TYPE_INFO_INTERFACE_BASE "Metadata" + +#define PW_VERSION_METADATA 3 +struct pw_metadata; + +#define PW_EXTENSION_MODULE_METADATA PIPEWIRE_MODULE_PREFIX "module-metadata" + +#define PW_METADATA_EVENT_PROPERTY 0 +#define PW_METADATA_EVENT_NUM 1 + +/** \ref pw_metadata events */ +struct pw_metadata_events { +#define PW_VERSION_METADATA_EVENTS 0 + uint32_t version; + + int (*property) (void *data, + uint32_t subject, + const char *key, + const char *type, + const char *value); +}; + +#define PW_METADATA_METHOD_ADD_LISTENER 0 +#define PW_METADATA_METHOD_SET_PROPERTY 1 +#define PW_METADATA_METHOD_CLEAR 2 +#define PW_METADATA_METHOD_NUM 3 + +/** \ref pw_metadata methods */ +struct pw_metadata_methods { +#define PW_VERSION_METADATA_METHODS 0 + uint32_t version; + + int (*add_listener) (void *object, + struct spa_hook *listener, + const struct pw_metadata_events *events, + void *data); + + int (*set_property) (void *object, + uint32_t subject, + const char *key, + const char *type, + const char *value); + + int (*clear) (void *object); +}; + + +#define pw_metadata_method(o,method,version,...) \ +({ \ + int _res = -ENOTSUP; \ + spa_interface_call_res((struct spa_interface*)o, \ + struct pw_metadata_methods, _res, \ + method, version, ##__VA_ARGS__); \ + _res; \ +}) + +#define pw_metadata_add_listener(c,...) pw_metadata_method(c,add_listener,0,__VA_ARGS__) +#define pw_metadata_set_property(c,...) pw_metadata_method(c,set_property,0,__VA_ARGS__) +#define pw_metadata_clear(c) pw_metadata_method(c,clear,0) + +#define PW_KEY_METADATA_NAME "metadata.name" + +/** + * \} + */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* PIPEWIRE_EXT_METADATA_H */ diff --git a/src/pipewire/extensions/profiler.h b/src/pipewire/extensions/profiler.h new file mode 100644 index 0000000..a5940a1 --- /dev/null +++ b/src/pipewire/extensions/profiler.h @@ -0,0 +1,95 @@ +/* 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. + */ + +#ifndef PIPEWIRE_EXT_PROFILER_H +#define PIPEWIRE_EXT_PROFILER_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <spa/utils/defs.h> + +/** \defgroup pw_profiler Profiler + * Profiler interface + */ + +/** + * \addtogroup pw_profiler + * \{ + */ +#define PW_TYPE_INTERFACE_Profiler PW_TYPE_INFO_INTERFACE_BASE "Profiler" + +#define PW_VERSION_PROFILER 3 +struct pw_profiler; + +#define PW_EXTENSION_MODULE_PROFILER PIPEWIRE_MODULE_PREFIX "module-profiler" + +#define PW_PROFILER_EVENT_PROFILE 0 +#define PW_PROFILER_EVENT_NUM 1 + +/** \ref pw_profiler events */ +struct pw_profiler_events { +#define PW_VERSION_PROFILER_EVENTS 0 + uint32_t version; + + void (*profile) (void *data, const struct spa_pod *pod); +}; + +#define PW_PROFILER_METHOD_ADD_LISTENER 0 +#define PW_PROFILER_METHOD_NUM 1 + +/** \ref pw_profiler methods */ +struct pw_profiler_methods { +#define PW_VERSION_PROFILER_METHODS 0 + uint32_t version; + + int (*add_listener) (void *object, + struct spa_hook *listener, + const struct pw_profiler_events *events, + void *data); +}; + +#define pw_profiler_method(o,method,version,...) \ +({ \ + int _res = -ENOTSUP; \ + spa_interface_call_res((struct spa_interface*)o, \ + struct pw_profiler_methods, _res, \ + method, version, ##__VA_ARGS__); \ + _res; \ +}) + +#define pw_profiler_add_listener(c,...) pw_profiler_method(c,add_listener,0,__VA_ARGS__) + +#define PW_KEY_PROFILER_NAME "profiler.name" + +/** + * \} + */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* PIPEWIRE_EXT_PROFILER_H */ diff --git a/src/pipewire/extensions/protocol-native.h b/src/pipewire/extensions/protocol-native.h new file mode 100644 index 0000000..4fe1905 --- /dev/null +++ b/src/pipewire/extensions/protocol-native.h @@ -0,0 +1,105 @@ +/* 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. + */ + +#ifndef PIPEWIRE_EXT_PROTOCOL_NATIVE_H +#define PIPEWIRE_EXT_PROTOCOL_NATIVE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <spa/utils/defs.h> + +#include <pipewire/proxy.h> +#include <pipewire/resource.h> + +/** \defgroup pw_protocol_native Native Protocol + * PipeWire native protocol interface + */ + +/** + * \addtogroup pw_protocol_native + * \{ + */ +#define PW_TYPE_INFO_PROTOCOL_Native PW_TYPE_INFO_PROTOCOL_BASE "Native" + +struct pw_protocol_native_message { + uint32_t id; + uint32_t opcode; + void *data; + uint32_t size; + uint32_t n_fds; + int *fds; + int seq; +}; + +struct pw_protocol_native_demarshal { + int (*func) (void *object, const struct pw_protocol_native_message *msg); + uint32_t permissions; + uint32_t flags; +}; + +/** \ref pw_protocol_native_ext methods */ +struct pw_protocol_native_ext { +#define PW_VERSION_PROTOCOL_NATIVE_EXT 0 + uint32_t version; + + struct spa_pod_builder * (*begin_proxy) (struct pw_proxy *proxy, + uint8_t opcode, struct pw_protocol_native_message **msg); + + uint32_t (*add_proxy_fd) (struct pw_proxy *proxy, int fd); + int (*get_proxy_fd) (struct pw_proxy *proxy, uint32_t index); + + int (*end_proxy) (struct pw_proxy *proxy, + struct spa_pod_builder *builder); + + struct spa_pod_builder * (*begin_resource) (struct pw_resource *resource, + uint8_t opcode, struct pw_protocol_native_message **msg); + + uint32_t (*add_resource_fd) (struct pw_resource *resource, int fd); + int (*get_resource_fd) (struct pw_resource *resource, uint32_t index); + + int (*end_resource) (struct pw_resource *resource, + struct spa_pod_builder *builder); +}; + +#define pw_protocol_native_begin_proxy(p,...) pw_protocol_ext(pw_proxy_get_protocol(p),struct pw_protocol_native_ext,begin_proxy,p,__VA_ARGS__) +#define pw_protocol_native_add_proxy_fd(p,...) pw_protocol_ext(pw_proxy_get_protocol(p),struct pw_protocol_native_ext,add_proxy_fd,p,__VA_ARGS__) +#define pw_protocol_native_get_proxy_fd(p,...) pw_protocol_ext(pw_proxy_get_protocol(p),struct pw_protocol_native_ext,get_proxy_fd,p,__VA_ARGS__) +#define pw_protocol_native_end_proxy(p,...) pw_protocol_ext(pw_proxy_get_protocol(p),struct pw_protocol_native_ext,end_proxy,p,__VA_ARGS__) + +#define pw_protocol_native_begin_resource(r,...) pw_protocol_ext(pw_resource_get_protocol(r),struct pw_protocol_native_ext,begin_resource,r,__VA_ARGS__) +#define pw_protocol_native_add_resource_fd(r,...) pw_protocol_ext(pw_resource_get_protocol(r),struct pw_protocol_native_ext,add_resource_fd,r,__VA_ARGS__) +#define pw_protocol_native_get_resource_fd(r,...) pw_protocol_ext(pw_resource_get_protocol(r),struct pw_protocol_native_ext,get_resource_fd,r,__VA_ARGS__) +#define pw_protocol_native_end_resource(r,...) pw_protocol_ext(pw_resource_get_protocol(r),struct pw_protocol_native_ext,end_resource,r,__VA_ARGS__) + +/** + * \} + */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* PIPEWIRE_EXT_PROTOCOL_NATIVE_H */ diff --git a/src/pipewire/extensions/session-manager.h b/src/pipewire/extensions/session-manager.h new file mode 100644 index 0000000..1226d23 --- /dev/null +++ b/src/pipewire/extensions/session-manager.h @@ -0,0 +1,47 @@ +/* PipeWire + * + * Copyright © 2019 Collabora Ltd. + * @author George Kiagiadakis <george.kiagiadakis@collabora.com> + * + * 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_EXT_SESSION_MANAGER_H +#define PIPEWIRE_EXT_SESSION_MANAGER_H + +/** \defgroup pw_session_manager Session Manager + * Session manager interface + */ + +/** + * \addtogroup pw_session_manager + * \{ + */ + +#include "session-manager/introspect.h" +#include "session-manager/interfaces.h" +#include "session-manager/impl-interfaces.h" +#include "session-manager/keys.h" + +/** + * \} + */ + +#endif /* PIPEWIRE_EXT_SESSION_MANAGER_H */ diff --git a/src/pipewire/extensions/session-manager/impl-interfaces.h b/src/pipewire/extensions/session-manager/impl-interfaces.h new file mode 100644 index 0000000..20dbc11 --- /dev/null +++ b/src/pipewire/extensions/session-manager/impl-interfaces.h @@ -0,0 +1,298 @@ +/* PipeWire + * + * Copyright © 2019 Collabora Ltd. + * @author George Kiagiadakis <george.kiagiadakis@collabora.com> + * + * 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_EXT_SESSION_MANAGER_IMPL_INTERFACES_H +#define PIPEWIRE_EXT_SESSION_MANAGER_IMPL_INTERFACES_H + +#include <spa/utils/defs.h> +#include <spa/utils/hook.h> +#include <errno.h> + +#include "introspect.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * \addtogroup pw_session_manager + * \{ + */ + +#define PW_TYPE_INTERFACE_ClientEndpoint PW_TYPE_INFO_INTERFACE_BASE "ClientEndpoint" + +#define PW_VERSION_CLIENT_ENDPOINT 0 +struct pw_client_endpoint; + +#define PW_CLIENT_ENDPOINT_EVENT_SET_SESSION_ID 0 +#define PW_CLIENT_ENDPOINT_EVENT_SET_PARAM 1 +#define PW_CLIENT_ENDPOINT_EVENT_STREAM_SET_PARAM 2 +#define PW_CLIENT_ENDPOINT_EVENT_CREATE_LINK 3 +#define PW_CLIENT_ENDPOINT_EVENT_NUM 4 + +struct pw_client_endpoint_events { +#define PW_VERSION_CLIENT_ENDPOINT_EVENTS 0 + uint32_t version; /**< version of this structure */ + + /** + * Sets the session id of the \a endpoint. + * + * On endpoints that are not session masters, this method notifies + * the implementation that it has been associated with a session. + * The implementation is obliged to set this id in the + * #struct pw_endpoint_info \a session_id field. + * + * \param endpoint a #pw_endpoint + * \param id the session id associated with this endpoint + * + * \return 0 on success + * -EINVAL when the session id has already been set + * -ENOTSUP when the endpoint is a session master + */ + int (*set_session_id) (void *data, uint32_t session_id); + + /** + * Set the configurable parameter in \a endpoint. + * + * Usually, \a param will be obtained from enum_params and then + * modified but it is also possible to set another spa_pod + * as long as its keys and types match a supported object. + * + * Objects with property keys that are not known are ignored. + * + * This function must be called from the main thread. + * + * \param endpoint a #struct pw_endpoint + * \param id the parameter id to configure + * \param flags additional flags + * \param param the parameter to configure + * + * \return 0 on success + * -EINVAL when \a endpoint is NULL + * -ENOTSUP when there are no parameters implemented on \a endpoint + * -ENOENT the parameter is unknown + */ + int (*set_param) (void *data, + uint32_t id, uint32_t flags, + const struct spa_pod *param); + + /** + * Set a parameter on \a stream_id of \a endpoint. + * + * When \a param is NULL, the parameter will be unset. + * + * This function must be called from the main thread. + * + * \param endpoint a #struct pw_endpoint + * \param stream_id the stream to configure + * \param id the parameter id to set + * \param flags optional flags + * \param param a #struct spa_pod with the parameter to set + * \return 0 on success + * 1 on success, the value of \a param might have been + * changed depending on \a flags and the final value can + * be found by doing stream_enum_params. + * -EINVAL when \a endpoint is NULL or invalid arguments are given + * -ESRCH when the type or size of a property is not correct. + * -ENOENT when the param id is not found + */ + int (*stream_set_param) (void *data, uint32_t stream_id, + uint32_t id, uint32_t flags, + const struct spa_pod *param); + + int (*create_link) (void *data, const struct spa_dict *props); +}; + +#define PW_CLIENT_ENDPOINT_METHOD_ADD_LISTENER 0 +#define PW_CLIENT_ENDPOINT_METHOD_UPDATE 1 +#define PW_CLIENT_ENDPOINT_METHOD_STREAM_UPDATE 2 +#define PW_CLIENT_ENDPOINT_METHOD_NUM 3 + +struct pw_client_endpoint_methods { +#define PW_VERSION_CLIENT_ENDPOINT_METHODS 0 + uint32_t version; /**< version of this structure */ + + int (*add_listener) (void *object, + struct spa_hook *listener, + const struct pw_client_endpoint_events *events, + void *data); + + /** Update endpoint information */ + int (*update) (void *object, +#define PW_CLIENT_ENDPOINT_UPDATE_PARAMS (1 << 0) +#define PW_CLIENT_ENDPOINT_UPDATE_INFO (1 << 1) + uint32_t change_mask, + uint32_t n_params, + const struct spa_pod **params, + const struct pw_endpoint_info *info); + + /** Update stream information */ + int (*stream_update) (void *object, + uint32_t stream_id, +#define PW_CLIENT_ENDPOINT_STREAM_UPDATE_PARAMS (1 << 0) +#define PW_CLIENT_ENDPOINT_STREAM_UPDATE_INFO (1 << 1) +#define PW_CLIENT_ENDPOINT_STREAM_UPDATE_DESTROYED (1 << 2) + uint32_t change_mask, + uint32_t n_params, + const struct spa_pod **params, + const struct pw_endpoint_stream_info *info); +}; + +#define pw_client_endpoint_method(o,method,version,...) \ +({ \ + int _res = -ENOTSUP; \ + spa_interface_call_res((struct spa_interface*)o, \ + struct pw_client_endpoint_methods, _res, \ + method, version, ##__VA_ARGS__); \ + _res; \ +}) + +#define pw_client_endpoint_add_listener(o,...) pw_client_endpoint_method(o,add_listener,0,__VA_ARGS__) +#define pw_client_endpoint_update(o,...) pw_client_endpoint_method(o,update,0,__VA_ARGS__) +#define pw_client_endpoint_stream_update(o,...) pw_client_endpoint_method(o,stream_update,0,__VA_ARGS__) + +#define PW_TYPE_INTERFACE_ClientSession PW_TYPE_INFO_INTERFACE_BASE "ClientSession" + +#define PW_VERSION_CLIENT_SESSION 0 +struct pw_client_session; + +#define PW_CLIENT_SESSION_EVENT_SET_PARAM 0 +#define PW_CLIENT_SESSION_EVENT_LINK_SET_PARAM 1 +#define PW_CLIENT_SESSION_EVENT_LINK_REQUEST_STATE 2 +#define PW_CLIENT_SESSION_EVENT_NUM 3 + +struct pw_client_session_events { +#define PW_VERSION_CLIENT_SESSION_EVENTS 0 + uint32_t version; /**< version of this structure */ + + /** + * Set the configurable parameter in \a session. + * + * Usually, \a param will be obtained from enum_params and then + * modified but it is also possible to set another spa_pod + * as long as its keys and types match a supported object. + * + * Objects with property keys that are not known are ignored. + * + * This function must be called from the main thread. + * + * \param session a #struct pw_session + * \param id the parameter id to configure + * \param flags additional flags + * \param param the parameter to configure + * + * \return 0 on success + * -EINVAL when \a session is NULL + * -ENOTSUP when there are no parameters implemented on \a session + * -ENOENT the parameter is unknown + */ + int (*set_param) (void *data, + uint32_t id, uint32_t flags, + const struct spa_pod *param); + + /** + * Set a parameter on \a link_id of \a session. + * + * When \a param is NULL, the parameter will be unset. + * + * This function must be called from the main thread. + * + * \param session a #struct pw_session + * \param link_id the link to configure + * \param id the parameter id to set + * \param flags optional flags + * \param param a #struct spa_pod with the parameter to set + * \return 0 on success + * 1 on success, the value of \a param might have been + * changed depending on \a flags and the final value can + * be found by doing link_enum_params. + * -EINVAL when \a session is NULL or invalid arguments are given + * -ESRCH when the type or size of a property is not correct. + * -ENOENT when the param id is not found + */ + int (*link_set_param) (void *data, uint32_t link_id, + uint32_t id, uint32_t flags, + const struct spa_pod *param); + + int (*link_request_state) (void *data, uint32_t link_id, uint32_t state); +}; + +#define PW_CLIENT_SESSION_METHOD_ADD_LISTENER 0 +#define PW_CLIENT_SESSION_METHOD_UPDATE 1 +#define PW_CLIENT_SESSION_METHOD_LINK_UPDATE 2 +#define PW_CLIENT_SESSION_METHOD_NUM 3 + +struct pw_client_session_methods { +#define PW_VERSION_CLIENT_SESSION_METHODS 0 + uint32_t version; /**< version of this structure */ + + int (*add_listener) (void *object, + struct spa_hook *listener, + const struct pw_client_session_events *events, + void *data); + + /** Update session information */ + int (*update) (void *object, +#define PW_CLIENT_SESSION_UPDATE_PARAMS (1 << 0) +#define PW_CLIENT_SESSION_UPDATE_INFO (1 << 1) + uint32_t change_mask, + uint32_t n_params, + const struct spa_pod **params, + const struct pw_session_info *info); + + /** Update link information */ + int (*link_update) (void *object, + uint32_t link_id, +#define PW_CLIENT_SESSION_LINK_UPDATE_PARAMS (1 << 0) +#define PW_CLIENT_SESSION_LINK_UPDATE_INFO (1 << 1) +#define PW_CLIENT_SESSION_LINK_UPDATE_DESTROYED (1 << 2) + uint32_t change_mask, + uint32_t n_params, + const struct spa_pod **params, + const struct pw_endpoint_link_info *info); +}; + +#define pw_client_session_method(o,method,version,...) \ +({ \ + int _res = -ENOTSUP; \ + spa_interface_call_res((struct spa_interface*)o, \ + struct pw_client_session_methods, _res, \ + method, version, ##__VA_ARGS__); \ + _res; \ +}) + +#define pw_client_session_add_listener(o,...) pw_client_session_method(o,add_listener,0,__VA_ARGS__) +#define pw_client_session_update(o,...) pw_client_session_method(o,update,0,__VA_ARGS__) +#define pw_client_session_link_update(o,...) pw_client_session_method(o,link_update,0,__VA_ARGS__) + +/** + * \} + */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* PIPEWIRE_EXT_SESSION_MANAGER_IMPL_INTERFACES_H */ diff --git a/src/pipewire/extensions/session-manager/interfaces.h b/src/pipewire/extensions/session-manager/interfaces.h new file mode 100644 index 0000000..0c11e1d --- /dev/null +++ b/src/pipewire/extensions/session-manager/interfaces.h @@ -0,0 +1,479 @@ +/* PipeWire + * + * Copyright © 2019 Collabora Ltd. + * @author George Kiagiadakis <george.kiagiadakis@collabora.com> + * + * 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_EXT_SESSION_MANAGER_INTERFACES_H +#define PIPEWIRE_EXT_SESSION_MANAGER_INTERFACES_H + +#include <spa/utils/defs.h> +#include <spa/utils/hook.h> + +#include "introspect.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * \addtogroup pw_session_manager + * \{ + */ + +#define PW_TYPE_INTERFACE_Session PW_TYPE_INFO_INTERFACE_BASE "Session" +#define PW_VERSION_SESSION 0 +struct pw_session; + +#define PW_TYPE_INTERFACE_Endpoint PW_TYPE_INFO_INTERFACE_BASE "Endpoint" +#define PW_VERSION_ENDPOINT 0 +struct pw_endpoint; + +#define PW_TYPE_INTERFACE_EndpointStream PW_TYPE_INFO_INTERFACE_BASE "EndpointStream" +#define PW_VERSION_ENDPOINT_STREAM 0 +struct pw_endpoint_stream; + +#define PW_TYPE_INTERFACE_EndpointLink PW_TYPE_INFO_INTERFACE_BASE "EndpointLink" +#define PW_VERSION_ENDPOINT_LINK 0 +struct pw_endpoint_link; + +/* Session */ + +#define PW_SESSION_EVENT_INFO 0 +#define PW_SESSION_EVENT_PARAM 1 +#define PW_SESSION_EVENT_NUM 2 + +struct pw_session_events { +#define PW_VERSION_SESSION_EVENTS 0 + uint32_t version; /**< version of this structure */ + + /** + * Notify session info + * + * \param info info about the session + */ + void (*info) (void *data, const struct pw_session_info *info); + + /** + * Notify a session param + * + * Event emitted as a result of the enum_params method. + * + * \param seq the sequence number of the request + * \param id the param id + * \param index the param index + * \param next the param index of the next param + * \param param the parameter + */ + void (*param) (void *data, int seq, + uint32_t id, uint32_t index, uint32_t next, + const struct spa_pod *param); +}; + +#define PW_SESSION_METHOD_ADD_LISTENER 0 +#define PW_SESSION_METHOD_SUBSCRIBE_PARAMS 1 +#define PW_SESSION_METHOD_ENUM_PARAMS 2 +#define PW_SESSION_METHOD_SET_PARAM 3 +#define PW_SESSION_METHOD_CREATE_LINK 4 +#define PW_SESSION_METHOD_NUM 5 + +struct pw_session_methods { +#define PW_VERSION_SESSION_METHODS 0 + uint32_t version; /**< version of this structure */ + + int (*add_listener) (void *object, + struct spa_hook *listener, + const struct pw_session_events *events, + void *data); + + /** + * Subscribe to parameter changes + * + * Automatically emit param events for the given ids when + * they are changed. + * + * \param ids an array of param ids + * \param n_ids the number of ids in \a ids + */ + int (*subscribe_params) (void *object, uint32_t *ids, uint32_t n_ids); + + /** + * Enumerate session parameters + * + * Start enumeration of session parameters. For each param, a + * param event will be emitted. + * + * \param seq a sequence number returned in the reply + * \param id the parameter id to enumerate + * \param start the start index or 0 for the first param + * \param num the maximum number of params to retrieve + * \param filter a param filter or NULL + */ + int (*enum_params) (void *object, int seq, + uint32_t id, uint32_t start, uint32_t num, + const struct spa_pod *filter); + + /** + * Set a parameter on the session + * + * \param id the parameter id to set + * \param flags extra parameter flags + * \param param the parameter to set + */ + int (*set_param) (void *object, uint32_t id, uint32_t flags, + const struct spa_pod *param); +}; + +#define pw_session_method(o,method,version,...) \ +({ \ + int _res = -ENOTSUP; \ + spa_interface_call_res((struct spa_interface*)o, \ + struct pw_session_methods, _res, \ + method, version, ##__VA_ARGS__); \ + _res; \ +}) + +#define pw_session_add_listener(c,...) pw_session_method(c,add_listener,0,__VA_ARGS__) +#define pw_session_subscribe_params(c,...) pw_session_method(c,subscribe_params,0,__VA_ARGS__) +#define pw_session_enum_params(c,...) pw_session_method(c,enum_params,0,__VA_ARGS__) +#define pw_session_set_param(c,...) pw_session_method(c,set_param,0,__VA_ARGS__) + + +/* Endpoint */ + +#define PW_ENDPOINT_EVENT_INFO 0 +#define PW_ENDPOINT_EVENT_PARAM 1 +#define PW_ENDPOINT_EVENT_NUM 2 + +struct pw_endpoint_events { +#define PW_VERSION_ENDPOINT_EVENTS 0 + uint32_t version; /**< version of this structure */ + + /** + * Notify endpoint info + * + * \param info info about the endpoint + */ + void (*info) (void *data, const struct pw_endpoint_info *info); + + /** + * Notify a endpoint param + * + * Event emitted as a result of the enum_params method. + * + * \param seq the sequence number of the request + * \param id the param id + * \param index the param index + * \param next the param index of the next param + * \param param the parameter + */ + void (*param) (void *data, int seq, + uint32_t id, uint32_t index, uint32_t next, + const struct spa_pod *param); +}; + +#define PW_ENDPOINT_METHOD_ADD_LISTENER 0 +#define PW_ENDPOINT_METHOD_SUBSCRIBE_PARAMS 1 +#define PW_ENDPOINT_METHOD_ENUM_PARAMS 2 +#define PW_ENDPOINT_METHOD_SET_PARAM 3 +#define PW_ENDPOINT_METHOD_CREATE_LINK 4 +#define PW_ENDPOINT_METHOD_NUM 5 + +struct pw_endpoint_methods { +#define PW_VERSION_ENDPOINT_METHODS 0 + uint32_t version; /**< version of this structure */ + + int (*add_listener) (void *object, + struct spa_hook *listener, + const struct pw_endpoint_events *events, + void *data); + + /** + * Subscribe to parameter changes + * + * Automatically emit param events for the given ids when + * they are changed. + * + * \param ids an array of param ids + * \param n_ids the number of ids in \a ids + */ + int (*subscribe_params) (void *object, uint32_t *ids, uint32_t n_ids); + + /** + * Enumerate endpoint parameters + * + * Start enumeration of endpoint parameters. For each param, a + * param event will be emitted. + * + * \param seq a sequence number returned in the reply + * \param id the parameter id to enumerate + * \param start the start index or 0 for the first param + * \param num the maximum number of params to retrieve + * \param filter a param filter or NULL + */ + int (*enum_params) (void *object, int seq, + uint32_t id, uint32_t start, uint32_t num, + const struct spa_pod *filter); + + /** + * Set a parameter on the endpoint + * + * \param id the parameter id to set + * \param flags extra parameter flags + * \param param the parameter to set + */ + int (*set_param) (void *object, uint32_t id, uint32_t flags, + const struct spa_pod *param); + + int (*create_link) (void *object, const struct spa_dict *props); +}; + +#define pw_endpoint_method(o,method,version,...) \ +({ \ + int _res = -ENOTSUP; \ + spa_interface_call_res((struct spa_interface*)o, \ + struct pw_endpoint_methods, _res, \ + method, version, ##__VA_ARGS__); \ + _res; \ +}) + +#define pw_endpoint_add_listener(c,...) pw_endpoint_method(c,add_listener,0,__VA_ARGS__) +#define pw_endpoint_subscribe_params(c,...) pw_endpoint_method(c,subscribe_params,0,__VA_ARGS__) +#define pw_endpoint_enum_params(c,...) pw_endpoint_method(c,enum_params,0,__VA_ARGS__) +#define pw_endpoint_set_param(c,...) pw_endpoint_method(c,set_param,0,__VA_ARGS__) +#define pw_endpoint_create_link(c,...) pw_endpoint_method(c,create_link,0,__VA_ARGS__) + +/* Endpoint Stream */ + +#define PW_ENDPOINT_STREAM_EVENT_INFO 0 +#define PW_ENDPOINT_STREAM_EVENT_PARAM 1 +#define PW_ENDPOINT_STREAM_EVENT_NUM 2 + +struct pw_endpoint_stream_events { +#define PW_VERSION_ENDPOINT_STREAM_EVENTS 0 + uint32_t version; /**< version of this structure */ + + /** + * Notify endpoint stream info + * + * \param info info about the endpoint stream + */ + void (*info) (void *data, const struct pw_endpoint_stream_info *info); + + /** + * Notify a endpoint stream param + * + * Event emitted as a result of the enum_params method. + * + * \param seq the sequence number of the request + * \param id the param id + * \param index the param index + * \param next the param index of the next param + * \param param the parameter + */ + void (*param) (void *data, int seq, + uint32_t id, uint32_t index, uint32_t next, + const struct spa_pod *param); +}; + +#define PW_ENDPOINT_STREAM_METHOD_ADD_LISTENER 0 +#define PW_ENDPOINT_STREAM_METHOD_SUBSCRIBE_PARAMS 1 +#define PW_ENDPOINT_STREAM_METHOD_ENUM_PARAMS 2 +#define PW_ENDPOINT_STREAM_METHOD_SET_PARAM 3 +#define PW_ENDPOINT_STREAM_METHOD_NUM 4 + +struct pw_endpoint_stream_methods { +#define PW_VERSION_ENDPOINT_STREAM_METHODS 0 + uint32_t version; /**< version of this structure */ + + int (*add_listener) (void *object, + struct spa_hook *listener, + const struct pw_endpoint_stream_events *events, + void *data); + + /** + * Subscribe to parameter changes + * + * Automatically emit param events for the given ids when + * they are changed. + * + * \param ids an array of param ids + * \param n_ids the number of ids in \a ids + */ + int (*subscribe_params) (void *object, uint32_t *ids, uint32_t n_ids); + + /** + * Enumerate stream parameters + * + * Start enumeration of stream parameters. For each param, a + * param event will be emitted. + * + * \param seq a sequence number returned in the reply + * \param id the parameter id to enumerate + * \param start the start index or 0 for the first param + * \param num the maximum number of params to retrieve + * \param filter a param filter or NULL + */ + int (*enum_params) (void *object, int seq, + uint32_t id, uint32_t start, uint32_t num, + const struct spa_pod *filter); + + /** + * Set a parameter on the stream + * + * \param id the parameter id to set + * \param flags extra parameter flags + * \param param the parameter to set + */ + int (*set_param) (void *object, uint32_t id, uint32_t flags, + const struct spa_pod *param); +}; + +#define pw_endpoint_stream_method(o,method,version,...) \ +({ \ + int _res = -ENOTSUP; \ + spa_interface_call_res((struct spa_interface*)o, \ + struct pw_endpoint_stream_methods, _res, \ + method, version, ##__VA_ARGS__); \ + _res; \ +}) + +#define pw_endpoint_stream_add_listener(c,...) pw_endpoint_stream_method(c,add_listener,0,__VA_ARGS__) +#define pw_endpoint_stream_subscribe_params(c,...) pw_endpoint_stream_method(c,subscribe_params,0,__VA_ARGS__) +#define pw_endpoint_stream_enum_params(c,...) pw_endpoint_stream_method(c,enum_params,0,__VA_ARGS__) +#define pw_endpoint_stream_set_param(c,...) pw_endpoint_stream_method(c,set_param,0,__VA_ARGS__) + +/* Endpoint Link */ + +#define PW_ENDPOINT_LINK_EVENT_INFO 0 +#define PW_ENDPOINT_LINK_EVENT_PARAM 1 +#define PW_ENDPOINT_LINK_EVENT_NUM 2 + +struct pw_endpoint_link_events { +#define PW_VERSION_ENDPOINT_LINK_EVENTS 0 + uint32_t version; /**< version of this structure */ + + /** + * Notify endpoint link info + * + * \param info info about the endpoint link + */ + void (*info) (void *data, const struct pw_endpoint_link_info *info); + + /** + * Notify a endpoint link param + * + * Event emitted as a result of the enum_params method. + * + * \param seq the sequence number of the request + * \param id the param id + * \param index the param index + * \param next the param index of the next param + * \param param the parameter + */ + void (*param) (void *data, int seq, + uint32_t id, uint32_t index, uint32_t next, + const struct spa_pod *param); +}; + +#define PW_ENDPOINT_LINK_METHOD_ADD_LISTENER 0 +#define PW_ENDPOINT_LINK_METHOD_SUBSCRIBE_PARAMS 1 +#define PW_ENDPOINT_LINK_METHOD_ENUM_PARAMS 2 +#define PW_ENDPOINT_LINK_METHOD_SET_PARAM 3 +#define PW_ENDPOINT_LINK_METHOD_REQUEST_STATE 4 +#define PW_ENDPOINT_LINK_METHOD_DESTROY 5 +#define PW_ENDPOINT_LINK_METHOD_NUM 6 + +struct pw_endpoint_link_methods { +#define PW_VERSION_ENDPOINT_LINK_METHODS 0 + uint32_t version; /**< version of this structure */ + + int (*add_listener) (void *object, + struct spa_hook *listener, + const struct pw_endpoint_link_events *events, + void *data); + + /** + * Subscribe to parameter changes + * + * Automatically emit param events for the given ids when + * they are changed. + * + * \param ids an array of param ids + * \param n_ids the number of ids in \a ids + */ + int (*subscribe_params) (void *object, uint32_t *ids, uint32_t n_ids); + + /** + * Enumerate link parameters + * + * Start enumeration of link parameters. For each param, a + * param event will be emitted. + * + * \param seq a sequence number returned in the reply + * \param id the parameter id to enumerate + * \param start the start index or 0 for the first param + * \param num the maximum number of params to retrieve + * \param filter a param filter or NULL + */ + int (*enum_params) (void *object, int seq, + uint32_t id, uint32_t start, uint32_t num, + const struct spa_pod *filter); + + /** + * Set a parameter on the link + * + * \param id the parameter id to set + * \param flags extra parameter flags + * \param param the parameter to set + */ + int (*set_param) (void *object, uint32_t id, uint32_t flags, + const struct spa_pod *param); + + int (*request_state) (void *object, enum pw_endpoint_link_state state); +}; + +#define pw_endpoint_link_method(o,method,version,...) \ +({ \ + int _res = -ENOTSUP; \ + spa_interface_call_res((struct spa_interface*)o, \ + struct pw_endpoint_link_methods, _res, \ + method, version, ##__VA_ARGS__); \ + _res; \ +}) + +#define pw_endpoint_link_add_listener(c,...) pw_endpoint_link_method(c,add_listener,0,__VA_ARGS__) +#define pw_endpoint_link_subscribe_params(c,...) pw_endpoint_link_method(c,subscribe_params,0,__VA_ARGS__) +#define pw_endpoint_link_enum_params(c,...) pw_endpoint_link_method(c,enum_params,0,__VA_ARGS__) +#define pw_endpoint_link_set_param(c,...) pw_endpoint_link_method(c,set_param,0,__VA_ARGS__) +#define pw_endpoint_link_request_state(c,...) pw_endpoint_link_method(c,request_state,0,__VA_ARGS__) + + +/** + * \} + */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* PIPEWIRE_EXT_SESSION_MANAGER_INTERFACES_H */ diff --git a/src/pipewire/extensions/session-manager/introspect-funcs.h b/src/pipewire/extensions/session-manager/introspect-funcs.h new file mode 100644 index 0000000..24df45d --- /dev/null +++ b/src/pipewire/extensions/session-manager/introspect-funcs.h @@ -0,0 +1,323 @@ +/* PipeWire + * + * Copyright © 2020 Collabora Ltd. + * @author George Kiagiadakis <george.kiagiadakis@collabora.com> + * + * 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_EXT_SESSION_MANAGER_INTROSPECT_FUNCS_H +#define PIPEWIRE_EXT_SESSION_MANAGER_INTROSPECT_FUNCS_H + +#include "introspect.h" +#include <spa/pod/builder.h> +#include <pipewire/pipewire.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * \addtogroup pw_session_manager + * \{ + */ + +static inline struct pw_session_info * +pw_session_info_update (struct pw_session_info *info, + const struct pw_session_info *update) +{ + struct extended_info { + struct pw_properties *props_storage; + struct pw_session_info info; + } *ext; + + if (update == NULL) + return info; + + if (info == NULL) { + ext = (struct extended_info *) calloc(1, sizeof(*ext)); + if (ext == NULL) + return NULL; + + info = &ext->info; + info->id = update->id; + } else { + ext = SPA_CONTAINER_OF(info, struct extended_info, info); + } + + info->change_mask = update->change_mask; + + if (update->change_mask & PW_SESSION_CHANGE_MASK_PROPS) { + if (!ext->props_storage) { + ext->props_storage = pw_properties_new(NULL, NULL); + info->props = &ext->props_storage->dict; + } + pw_properties_clear(ext->props_storage); + pw_properties_update(ext->props_storage, update->props); + } + if (update->change_mask & PW_SESSION_CHANGE_MASK_PARAMS) { + info->n_params = update->n_params; + free((void *) info->params); + if (update->params) { + size_t size = info->n_params * sizeof(struct spa_param_info); + info->params = (struct spa_param_info *) malloc(size); + memcpy(info->params, update->params, size); + } + else + info->params = NULL; + } + return info; +} + +static inline void +pw_session_info_free (struct pw_session_info *info) +{ + struct extended_info { + struct pw_properties *props_storage; + struct pw_session_info info; + } *ext = SPA_CONTAINER_OF(info, struct extended_info, info); + + pw_properties_free(ext->props_storage); + free((void *) info->params); + free(ext); +} + +static inline struct pw_endpoint_info * +pw_endpoint_info_update (struct pw_endpoint_info *info, + const struct pw_endpoint_info *update) +{ + struct extended_info { + struct pw_properties *props_storage; + struct pw_endpoint_info info; + } *ext; + + if (update == NULL) + return info; + + if (info == NULL) { + ext = (struct extended_info *) calloc(1, sizeof(*ext)); + if (ext == NULL) + return NULL; + + info = &ext->info; + info->id = update->id; + info->name = strdup(update->name); + info->media_class = strdup(update->media_class); + info->direction = update->direction; + info->flags = update->flags; + } else { + ext = SPA_CONTAINER_OF(info, struct extended_info, info); + } + + info->change_mask = update->change_mask; + + if (update->change_mask & PW_ENDPOINT_CHANGE_MASK_STREAMS) + info->n_streams = update->n_streams; + + if (update->change_mask & PW_ENDPOINT_CHANGE_MASK_SESSION) + info->session_id = update->session_id; + + if (update->change_mask & PW_ENDPOINT_CHANGE_MASK_PROPS) { + if (!ext->props_storage) { + ext->props_storage = pw_properties_new(NULL, NULL); + info->props = &ext->props_storage->dict; + } + pw_properties_clear(ext->props_storage); + pw_properties_update(ext->props_storage, update->props); + } + if (update->change_mask & PW_ENDPOINT_CHANGE_MASK_PARAMS) { + info->n_params = update->n_params; + free((void *) info->params); + if (update->params) { + size_t size = info->n_params * sizeof(struct spa_param_info); + info->params = (struct spa_param_info *) malloc(size); + memcpy(info->params, update->params, size); + } + else + info->params = NULL; + } + return info; +} + +static inline void +pw_endpoint_info_free (struct pw_endpoint_info *info) +{ + struct extended_info { + struct pw_properties *props_storage; + struct pw_endpoint_info info; + } *ext = SPA_CONTAINER_OF(info, struct extended_info, info); + + pw_properties_free(ext->props_storage); + free(info->name); + free(info->media_class); + free((void *) info->params); + free(ext); +} + +static inline struct pw_endpoint_stream_info * +pw_endpoint_stream_info_update (struct pw_endpoint_stream_info *info, + const struct pw_endpoint_stream_info *update) +{ + struct extended_info { + struct pw_properties *props_storage; + struct pw_endpoint_stream_info info; + } *ext; + + if (update == NULL) + return info; + + if (info == NULL) { + ext = (struct extended_info *) calloc(1, sizeof(*ext)); + if (ext == NULL) + return NULL; + + info = &ext->info; + info->id = update->id; + info->endpoint_id = update->endpoint_id; + info->name = strdup(update->name); + } else { + ext = SPA_CONTAINER_OF(info, struct extended_info, info); + } + + info->change_mask = update->change_mask; + + if (update->change_mask & PW_ENDPOINT_STREAM_CHANGE_MASK_LINK_PARAMS) { + free(info->link_params); + info->link_params = update->link_params ? + spa_pod_copy(update->link_params) : NULL; + } + if (update->change_mask & PW_ENDPOINT_STREAM_CHANGE_MASK_PROPS) { + if (!ext->props_storage) { + ext->props_storage = pw_properties_new(NULL, NULL); + info->props = &ext->props_storage->dict; + } + pw_properties_clear(ext->props_storage); + pw_properties_update(ext->props_storage, update->props); + } + if (update->change_mask & PW_ENDPOINT_STREAM_CHANGE_MASK_PARAMS) { + info->n_params = update->n_params; + free((void *) info->params); + if (update->params) { + size_t size = info->n_params * sizeof(struct spa_param_info); + info->params = (struct spa_param_info *) malloc(size); + memcpy(info->params, update->params, size); + } + else + info->params = NULL; + } + return info; +} + +static inline void +pw_endpoint_stream_info_free (struct pw_endpoint_stream_info *info) +{ + struct extended_info { + struct pw_properties *props_storage; + struct pw_endpoint_stream_info info; + } *ext = SPA_CONTAINER_OF(info, struct extended_info, info); + + pw_properties_free(ext->props_storage); + free(info->name); + free(info->link_params); + free((void *) info->params); + free(ext); +} + + +static inline struct pw_endpoint_link_info * +pw_endpoint_link_info_update (struct pw_endpoint_link_info *info, + const struct pw_endpoint_link_info *update) +{ + struct extended_info { + struct pw_properties *props_storage; + struct pw_endpoint_link_info info; + } *ext; + + if (update == NULL) + return info; + + if (info == NULL) { + ext = (struct extended_info *) calloc(1, sizeof(*ext)); + if (ext == NULL) + return NULL; + + info = &ext->info; + info->id = update->id; + info->session_id = update->session_id; + info->output_endpoint_id = update->output_endpoint_id; + info->output_stream_id = update->output_stream_id; + info->input_endpoint_id = update->input_endpoint_id; + info->input_stream_id = update->input_stream_id; + } else { + ext = SPA_CONTAINER_OF(info, struct extended_info, info); + } + + info->change_mask = update->change_mask; + + if (update->change_mask & PW_ENDPOINT_LINK_CHANGE_MASK_STATE) { + info->state = update->state; + free(info->error); + info->error = update->error ? strdup(update->error) : NULL; + } + if (update->change_mask & PW_ENDPOINT_LINK_CHANGE_MASK_PROPS) { + if (!ext->props_storage) { + ext->props_storage = pw_properties_new(NULL, NULL); + info->props = &ext->props_storage->dict; + } + pw_properties_clear(ext->props_storage); + pw_properties_update(ext->props_storage, update->props); + } + if (update->change_mask & PW_ENDPOINT_LINK_CHANGE_MASK_PARAMS) { + info->n_params = update->n_params; + free((void *) info->params); + if (update->params) { + size_t size = info->n_params * sizeof(struct spa_param_info); + info->params = (struct spa_param_info *) malloc(size); + memcpy(info->params, update->params, size); + } + else + info->params = NULL; + } + return info; +} + +static inline void +pw_endpoint_link_info_free (struct pw_endpoint_link_info *info) +{ + struct extended_info { + struct pw_properties *props_storage; + struct pw_endpoint_link_info info; + } *ext = SPA_CONTAINER_OF(info, struct extended_info, info); + + pw_properties_free(ext->props_storage); + free(info->error); + free((void *) info->params); + free(ext); +} + +/** + * \} + */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* PIPEWIRE_EXT_SESSION_MANAGER_INTROSPECT_FUNCS_H */ diff --git a/src/pipewire/extensions/session-manager/introspect.h b/src/pipewire/extensions/session-manager/introspect.h new file mode 100644 index 0000000..a35a70b --- /dev/null +++ b/src/pipewire/extensions/session-manager/introspect.h @@ -0,0 +1,130 @@ +/* PipeWire + * + * Copyright © 2019 Collabora Ltd. + * @author George Kiagiadakis <george.kiagiadakis@collabora.com> + * + * 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_EXT_SESSION_MANAGER_INTROSPECT_H +#define PIPEWIRE_EXT_SESSION_MANAGER_INTROSPECT_H + +#include <spa/utils/defs.h> +#include <spa/utils/dict.h> +#include <spa/param/param.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * \addtogroup pw_session_manager + * \{ + */ + +enum pw_endpoint_link_state { + PW_ENDPOINT_LINK_STATE_ERROR = -1, + PW_ENDPOINT_LINK_STATE_PREPARING, + PW_ENDPOINT_LINK_STATE_INACTIVE, + PW_ENDPOINT_LINK_STATE_ACTIVE, +}; + +struct pw_session_info { +#define PW_VERSION_SESSION_INFO 0 + uint32_t version; /**< version of this structure */ + uint32_t id; /**< the session id (global) */ +#define PW_SESSION_CHANGE_MASK_PROPS (1 << 0) +#define PW_SESSION_CHANGE_MASK_PARAMS (1 << 1) +#define PW_SESSION_CHANGE_MASK_ALL ((1 << 2)-1) + uint64_t change_mask; /**< bitfield of changed fields since last call */ + struct spa_dict *props; /**< extra properties */ + struct spa_param_info *params; /**< parameters */ + uint32_t n_params; /**< number of items in \a params */ +}; + +struct pw_endpoint_info { +#define PW_VERSION_ENDPOINT_INFO 0 + uint32_t version; /**< version of this structure */ + uint32_t id; /**< the endpoint id (global) */ + char *name; /**< name of the endpoint */ + char *media_class; /**< media class of the endpoint */ + enum pw_direction direction; /**< direction of the endpoint */ +#define PW_ENDPOINT_FLAG_PROVIDES_SESSION (1 << 0) + uint32_t flags; /**< additional flags */ +#define PW_ENDPOINT_CHANGE_MASK_STREAMS (1 << 0) +#define PW_ENDPOINT_CHANGE_MASK_SESSION (1 << 1) +#define PW_ENDPOINT_CHANGE_MASK_PROPS (1 << 2) +#define PW_ENDPOINT_CHANGE_MASK_PARAMS (1 << 3) +#define PW_ENDPOINT_CHANGE_MASK_ALL ((1 << 4)-1) + uint64_t change_mask; /**< bitfield of changed fields since last call */ + uint32_t n_streams; /**< number of streams available */ + uint32_t session_id; /**< the id of the controlling session */ + struct spa_dict *props; /**< extra properties */ + struct spa_param_info *params; /**< parameters */ + uint32_t n_params; /**< number of items in \a params */ +}; + +struct pw_endpoint_stream_info { +#define PW_VERSION_ENDPOINT_STREAM_INFO 0 + uint32_t version; /**< version of this structure */ + uint32_t id; /**< the stream id (local or global) */ + uint32_t endpoint_id; /**< the endpoint id (global) */ + char *name; /**< name of the stream */ +#define PW_ENDPOINT_STREAM_CHANGE_MASK_LINK_PARAMS (1 << 0) +#define PW_ENDPOINT_STREAM_CHANGE_MASK_PROPS (1 << 1) +#define PW_ENDPOINT_STREAM_CHANGE_MASK_PARAMS (1 << 2) +#define PW_ENDPOINT_STREAM_CHANGE_MASK_ALL ((1 << 3)-1) + uint64_t change_mask; /**< bitfield of changed fields since last call */ + struct spa_pod *link_params; /**< information for linking this stream */ + struct spa_dict *props; /**< extra properties */ + struct spa_param_info *params; /**< parameters */ + uint32_t n_params; /**< number of items in \a params */ +}; + +struct pw_endpoint_link_info { +#define PW_VERSION_ENDPOINT_LINK_INFO 0 + uint32_t version; /**< version of this structure */ + uint32_t id; /**< the link id (global) */ + uint32_t session_id; /**< the session id (global) */ + uint32_t output_endpoint_id; /**< the output endpoint id (global) */ + uint32_t output_stream_id; /**< the output stream id (local or global) */ + uint32_t input_endpoint_id; /**< the input endpoint id (global) */ + uint32_t input_stream_id; /**< the input stream id (local or global) */ +#define PW_ENDPOINT_LINK_CHANGE_MASK_STATE (1 << 0) +#define PW_ENDPOINT_LINK_CHANGE_MASK_PROPS (1 << 1) +#define PW_ENDPOINT_LINK_CHANGE_MASK_PARAMS (1 << 2) +#define PW_ENDPOINT_LINK_CHANGE_MASK_ALL ((1 << 3)-1) + uint64_t change_mask; /**< bitfield of changed fields since last call */ + enum pw_endpoint_link_state state; /**< the state of the link */ + char *error; /**< error string if state == ERROR */ + struct spa_dict *props; /**< extra properties */ + struct spa_param_info *params; /**< parameters */ + uint32_t n_params; /**< number of items in \a params */ +}; + +/** + * \} + */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* PIPEWIRE_EXT_SESSION_MANAGER_INTROSPECT_H */ diff --git a/src/pipewire/extensions/session-manager/keys.h b/src/pipewire/extensions/session-manager/keys.h new file mode 100644 index 0000000..9ff89f9 --- /dev/null +++ b/src/pipewire/extensions/session-manager/keys.h @@ -0,0 +1,67 @@ +/* PipeWire + * + * Copyright © 2019 Collabora Ltd. + * @author George Kiagiadakis <george.kiagiadakis@collabora.com> + * + * 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_EXT_SESSION_MANAGER_KEYS_H +#define PIPEWIRE_EXT_SESSION_MANAGER_KEYS_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * \addtogroup pw_session_manager + * \{ + */ + +#define PW_KEY_SESSION_ID "session.id" /**< id of a session manager */ + +#define PW_KEY_ENDPOINT_ID "endpoint.id" /**< id of an endpoint */ +#define PW_KEY_ENDPOINT_NAME "endpoint.name" /**< the name of an endpoint */ +#define PW_KEY_ENDPOINT_MONITOR "endpoint.monitor" /**< endpoint is monitor of given endpoint */ +#define PW_KEY_ENDPOINT_CLIENT_ID "endpoint.client.id" /**< client of the endpoint */ +#define PW_KEY_ENDPOINT_ICON_NAME "endpoint.icon-name" /**< an XDG icon name for the device. + * Ex. "sound-card-speakers-usb" */ +#define PW_KEY_ENDPOINT_AUTOCONNECT "endpoint.autoconnect" /**< try to automatically connect this + * endpoint. */ +#define PW_KEY_ENDPOINT_TARGET "endpoint.target" /**< the suggested target to connect to */ + +#define PW_KEY_ENDPOINT_STREAM_ID "endpoint-stream.id" /**< id of a stream */ +#define PW_KEY_ENDPOINT_STREAM_NAME "endpoint-stream.name" /**< unique name of a stream */ +#define PW_KEY_ENDPOINT_STREAM_DESCRIPTION "endpoint-stream.description" /**< description of a stream */ + +#define PW_KEY_ENDPOINT_LINK_OUTPUT_ENDPOINT "endpoint-link.output.endpoint" /**< output endpoint of link */ +#define PW_KEY_ENDPOINT_LINK_OUTPUT_STREAM "endpoint-link.output.stream" /**< output stream of link */ +#define PW_KEY_ENDPOINT_LINK_INPUT_ENDPOINT "endpoint-link.input.endpoint" /**< input endpoint of link */ +#define PW_KEY_ENDPOINT_LINK_INPUT_STREAM "endpoint-link.input.stream" /**< input stream of link */ + +/** + * \} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* PIPEWIRE_EXT_SESSION_MANAGER_KEYS_H */ diff --git a/src/pipewire/factory.h b/src/pipewire/factory.h new file mode 100644 index 0000000..c747dd9 --- /dev/null +++ b/src/pipewire/factory.h @@ -0,0 +1,123 @@ +/* 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. + */ + +#ifndef PIPEWIRE_FACTORY_H +#define PIPEWIRE_FACTORY_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <stdarg.h> +#include <errno.h> + +#include <spa/utils/defs.h> +#include <spa/utils/hook.h> + +#include <pipewire/proxy.h> + +/** \defgroup pw_factory Factory + * Factory interface + */ + +/** + * \addtogroup pw_factory + * \{ + */ +#define PW_TYPE_INTERFACE_Factory PW_TYPE_INFO_INTERFACE_BASE "Factory" + +#define PW_VERSION_FACTORY 3 +struct pw_factory; + +/** The factory information. Extra information can be added in later versions */ +struct pw_factory_info { + uint32_t id; /**< id of the global */ + const char *name; /**< name the factory */ + const char *type; /**< type of the objects created by this factory */ + uint32_t version; /**< version of the objects */ +#define PW_FACTORY_CHANGE_MASK_PROPS (1 << 0) +#define PW_FACTORY_CHANGE_MASK_ALL ((1 << 1)-1) + uint64_t change_mask; /**< bitfield of changed fields since last call */ + struct spa_dict *props; /**< the properties of the factory */ +}; + +struct pw_factory_info * +pw_factory_info_update(struct pw_factory_info *info, + const struct pw_factory_info *update); +struct pw_factory_info * +pw_factory_info_merge(struct pw_factory_info *info, + const struct pw_factory_info *update, bool reset); +void +pw_factory_info_free(struct pw_factory_info *info); + + +#define PW_FACTORY_EVENT_INFO 0 +#define PW_FACTORY_EVENT_NUM 1 + +/** Factory events */ +struct pw_factory_events { +#define PW_VERSION_FACTORY_EVENTS 0 + uint32_t version; + /** + * Notify factory info + * + * \param info info about the factory + */ + void (*info) (void *data, const struct pw_factory_info *info); +}; + +#define PW_FACTORY_METHOD_ADD_LISTENER 0 +#define PW_FACTORY_METHOD_NUM 1 + +/** Factory methods */ +struct pw_factory_methods { +#define PW_VERSION_FACTORY_METHODS 0 + uint32_t version; + + int (*add_listener) (void *object, + struct spa_hook *listener, + const struct pw_factory_events *events, + void *data); +}; + +#define pw_factory_method(o,method,version,...) \ +({ \ + int _res = -ENOTSUP; \ + spa_interface_call_res((struct spa_interface*)o, \ + struct pw_factory_methods, _res, \ + method, version, ##__VA_ARGS__); \ + _res; \ +}) + +#define pw_factory_add_listener(c,...) pw_factory_method(c,add_listener,0,__VA_ARGS__) + +/** + * \} + */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* PIPEWIRE_FACTORY_H */ diff --git a/src/pipewire/filter.c b/src/pipewire/filter.c new file mode 100644 index 0000000..4ddf081 --- /dev/null +++ b/src/pipewire/filter.c @@ -0,0 +1,1962 @@ +/* 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 <errno.h> +#include <stdio.h> +#include <math.h> +#include <sys/mman.h> +#include <time.h> + +#include <spa/buffer/alloc.h> +#include <spa/param/props.h> +#include <spa/node/io.h> +#include <spa/node/utils.h> +#include <spa/utils/ringbuffer.h> +#include <spa/utils/string.h> +#include <spa/pod/filter.h> +#include <spa/pod/dynamic.h> +#include <spa/debug/types.h> + +#include "pipewire/pipewire.h" +#include "pipewire/filter.h" +#include "pipewire/private.h" + +PW_LOG_TOPIC_EXTERN(log_filter); +#define PW_LOG_TOPIC_DEFAULT log_filter + +#define MAX_SAMPLES 8192 +#define MAX_BUFFERS 64 + +#define MASK_BUFFERS (MAX_BUFFERS-1) + +static bool mlock_warned = false; + +static uint32_t mappable_dataTypes = (1<<SPA_DATA_MemFd); + +struct buffer { + struct pw_buffer this; + uint32_t id; +#define BUFFER_FLAG_MAPPED (1 << 0) +#define BUFFER_FLAG_QUEUED (1 << 1) +#define BUFFER_FLAG_ADDED (1 << 2) + uint32_t flags; +}; + +struct queue { + uint32_t ids[MAX_BUFFERS]; + struct spa_ringbuffer ring; + uint64_t incount; + uint64_t outcount; +}; + +struct data { + struct pw_context *context; + struct spa_hook filter_listener; +}; + +struct param { + uint32_t id; +#define PARAM_FLAG_LOCKED (1 << 0) + uint32_t flags; + struct spa_list link; + struct spa_pod *param; +}; + +struct port { + struct spa_list link; + + struct filter *filter; + + enum spa_direction direction; + uint32_t id; + uint32_t flags; + struct pw_port *port; + + struct pw_properties *props; + + uint32_t change_mask_all; + struct spa_port_info info; + struct spa_list param_list; +#define IDX_EnumFormat 0 +#define IDX_Meta 1 +#define IDX_IO 2 +#define IDX_Format 3 +#define IDX_Buffers 4 +#define IDX_Latency 5 +#define N_PORT_PARAMS 6 + struct spa_param_info params[N_PORT_PARAMS]; + + struct spa_io_buffers *io; + + struct buffer buffers[MAX_BUFFERS]; + uint32_t n_buffers; + + struct queue dequeued; + struct queue queued; + + struct spa_latency_info latency[2]; + + /* from here is what the caller gets as user_data */ + uint8_t user_data[0]; +}; + +struct filter { + struct pw_filter this; + + const char *path; + + struct pw_context *context; + + enum pw_filter_flags flags; + + struct spa_node impl_node; + struct spa_hook_list hooks; + struct spa_callbacks callbacks; + struct spa_io_position *position; + + struct { + struct spa_io_position *position; + } rt; + + struct spa_list port_list; + struct pw_map ports[2]; + + uint32_t change_mask_all; + struct spa_node_info info; + struct spa_list param_list; +#define IDX_PropInfo 0 +#define IDX_Props 1 +#define IDX_ProcessLatency 2 +#define N_NODE_PARAMS 3 + struct spa_param_info params[N_NODE_PARAMS]; + + struct spa_process_latency_info process_latency; + + struct data data; + uintptr_t seq; + struct pw_time time; + uint64_t base_pos; + uint32_t clock_id; + + struct spa_callbacks rt_callbacks; + + unsigned int disconnecting:1; + unsigned int disconnect_core:1; + unsigned int subscribe:1; + unsigned int draining:1; + unsigned int allow_mlock:1; + unsigned int warn_mlock:1; + unsigned int process_rt:1; +}; + +static int get_param_index(uint32_t id) +{ + switch (id) { + case SPA_PARAM_PropInfo: + return IDX_PropInfo; + case SPA_PARAM_Props: + return IDX_Props; + case SPA_PARAM_ProcessLatency: + return IDX_ProcessLatency; + default: + return -1; + } +} + +static int get_port_param_index(uint32_t id) +{ + switch (id) { + case SPA_PARAM_EnumFormat: + return IDX_EnumFormat; + case SPA_PARAM_Meta: + return IDX_Meta; + case SPA_PARAM_IO: + return IDX_IO; + case SPA_PARAM_Format: + return IDX_Format; + case SPA_PARAM_Buffers: + return IDX_Buffers; + case SPA_PARAM_Latency: + return IDX_Latency; + default: + return -1; + } +} + +static void fix_datatype(const struct spa_pod *param) +{ + const struct spa_pod_prop *pod_param; + const struct spa_pod *vals; + uint32_t dataType, n_vals, choice; + + pod_param = spa_pod_find_prop(param, NULL, SPA_PARAM_BUFFERS_dataType); + if (pod_param == NULL) + return; + + vals = spa_pod_get_values(&pod_param->value, &n_vals, &choice); + if (n_vals == 0) + return; + + if (spa_pod_get_int(&vals[0], (int32_t*)&dataType) < 0) + return; + + pw_log_debug("dataType: %u", dataType); + if (dataType & (1u << SPA_DATA_MemPtr)) { + SPA_POD_VALUE(struct spa_pod_int, &vals[0]) = + dataType | mappable_dataTypes; + pw_log_debug("Change dataType: %u -> %u", dataType, + SPA_POD_VALUE(struct spa_pod_int, &vals[0])); + } +} + +static struct param *add_param(struct filter *impl, struct port *port, + uint32_t id, uint32_t flags, const struct spa_pod *param) +{ + struct param *p; + int idx; + + if (param == NULL || !spa_pod_is_object(param)) { + errno = EINVAL; + return NULL; + } + if (id == SPA_ID_INVALID) + id = SPA_POD_OBJECT_ID(param); + + p = malloc(sizeof(struct param) + SPA_POD_SIZE(param)); + if (p == NULL) + return NULL; + + if (id == SPA_PARAM_Buffers && port != NULL && + SPA_FLAG_IS_SET(port->flags, PW_FILTER_PORT_FLAG_MAP_BUFFERS) && + port->direction == SPA_DIRECTION_INPUT) + fix_datatype(param); + + if (id == SPA_PARAM_ProcessLatency && port == NULL) + spa_process_latency_parse(param, &impl->process_latency); + + p->id = id; + p->flags = flags; + p->param = SPA_PTROFF(p, sizeof(struct param), struct spa_pod); + memcpy(p->param, param, SPA_POD_SIZE(param)); + SPA_POD_OBJECT_ID(p->param) = id; + + pw_log_debug("%p: port %p param id %d (%s)", impl, p, id, + spa_debug_type_find_name(spa_type_param, id)); + + if (port) { + idx = get_port_param_index(id); + spa_list_append(&port->param_list, &p->link); + if (idx != -1) { + port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; + port->params[idx].flags |= SPA_PARAM_INFO_READ; + port->params[idx].user++; + } + } else { + idx = get_param_index(id); + spa_list_append(&impl->param_list, &p->link); + if (idx != -1) { + impl->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; + impl->params[idx].flags |= SPA_PARAM_INFO_READ; + impl->params[idx].user++; + } + } + return p; +} + +static void clear_params(struct filter *impl, struct port *port, uint32_t id) +{ + struct param *p, *t; + struct spa_list *param_list; + + if (port) + param_list = &port->param_list; + else + param_list = &impl->param_list; + + spa_list_for_each_safe(p, t, param_list, link) { + if (id == SPA_ID_INVALID || + (p->id == id && !(p->flags & PARAM_FLAG_LOCKED))) { + spa_list_remove(&p->link); + free(p); + } + } +} + +static struct port *alloc_port(struct filter *filter, + enum spa_direction direction, uint32_t user_data_size) +{ + struct port *p; + + p = calloc(1, sizeof(struct port) + user_data_size); + p->filter = filter; + p->direction = direction; + p->latency[SPA_DIRECTION_INPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_INPUT); + p->latency[SPA_DIRECTION_OUTPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_OUTPUT); + + spa_list_init(&p->param_list); + spa_ringbuffer_init(&p->dequeued.ring); + spa_ringbuffer_init(&p->queued.ring); + p->id = pw_map_insert_new(&filter->ports[direction], p); + spa_list_append(&filter->port_list, &p->link); + + return p; +} + +static inline struct port *get_port(struct filter *filter, enum spa_direction direction, uint32_t port_id) +{ + if ((direction != SPA_DIRECTION_INPUT && direction != SPA_DIRECTION_OUTPUT)) + return NULL; + return pw_map_lookup(&filter->ports[direction], port_id); +} + +static inline int push_queue(struct port *port, struct queue *queue, struct buffer *buffer) +{ + uint32_t index; + + if (SPA_FLAG_IS_SET(buffer->flags, BUFFER_FLAG_QUEUED)) + return -EINVAL; + + SPA_FLAG_SET(buffer->flags, BUFFER_FLAG_QUEUED); + queue->incount += buffer->this.size; + + spa_ringbuffer_get_write_index(&queue->ring, &index); + queue->ids[index & MASK_BUFFERS] = buffer->id; + spa_ringbuffer_write_update(&queue->ring, index + 1); + + return 0; +} + +static inline struct buffer *pop_queue(struct port *port, struct queue *queue) +{ + uint32_t index, id; + struct buffer *buffer; + + if (spa_ringbuffer_get_read_index(&queue->ring, &index) < 1) { + errno = EPIPE; + return NULL; + } + + id = queue->ids[index & MASK_BUFFERS]; + spa_ringbuffer_read_update(&queue->ring, index + 1); + + buffer = &port->buffers[id]; + queue->outcount += buffer->this.size; + SPA_FLAG_CLEAR(buffer->flags, BUFFER_FLAG_QUEUED); + + return buffer; +} + +static inline void clear_queue(struct port *port, struct queue *queue) +{ + spa_ringbuffer_init(&queue->ring); + queue->incount = queue->outcount; +} + +static bool filter_set_state(struct pw_filter *filter, enum pw_filter_state state, const char *error) +{ + enum pw_filter_state old = filter->state; + bool res = old != state; + + if (res) { + free(filter->error); + filter->error = error ? strdup(error) : NULL; + + pw_log_debug("%p: update state from %s -> %s (%s)", filter, + pw_filter_state_as_string(old), + pw_filter_state_as_string(state), filter->error); + + if (state == PW_FILTER_STATE_ERROR) + pw_log_error("%p: error %s", filter, error); + + filter->state = state; + pw_filter_emit_state_changed(filter, old, state, error); + } + return res; +} + +static int enum_params(struct filter *d, struct spa_list *param_list, int seq, + uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) +{ + struct spa_result_node_params result; + uint8_t buffer[1024]; + struct spa_pod_dynamic_builder b; + uint32_t count = 0; + struct param *p; + bool found = false; + + spa_return_val_if_fail(num != 0, -EINVAL); + + result.id = id; + result.next = 0; + + pw_log_debug("%p: %p param id %d (%s) start:%d num:%d", d, param_list, id, + spa_debug_type_find_name(spa_type_param, id), + start, num); + + spa_list_for_each(p, param_list, link) { + struct spa_pod *param; + + param = p->param; + if (param == NULL || p->id != id) + continue; + + found = true; + + result.index = result.next++; + if (result.index < start) + continue; + + spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 4096); + if (spa_pod_filter(&b.b, &result.param, param, filter) == 0) { + spa_node_emit_result(&d->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); + count++; + } + spa_pod_dynamic_builder_clean(&b); + + if (count == num) + break; + } + return found ? 0 : -ENOENT; +} + +static int impl_enum_params(void *object, int seq, uint32_t id, uint32_t start, uint32_t num, + const struct spa_pod *filter) +{ + struct filter *impl = object; + return enum_params(impl, &impl->param_list, seq, id, start, num, filter); +} + +static int impl_set_param(void *object, uint32_t id, uint32_t flags, const struct spa_pod *param) +{ + struct filter *impl = object; + struct pw_filter *filter = &impl->this; + + if (id != SPA_PARAM_Props) + return -ENOTSUP; + + pw_filter_emit_param_changed(filter, NULL, id, param); + return 0; +} + +static int +do_set_position(struct spa_loop *loop, + bool async, uint32_t seq, const void *data, size_t size, void *user_data) +{ + struct filter *impl = user_data; + impl->rt.position = impl->position; + return 0; +} + +static int impl_set_io(void *object, uint32_t id, void *data, size_t size) +{ + struct filter *impl = object; + + pw_log_debug("%p: io %d %p/%zd", impl, id, data, size); + + switch(id) { + case SPA_IO_Position: + if (data && size >= sizeof(struct spa_io_position)) + impl->position = data; + else + impl->position = NULL; + pw_loop_invoke(impl->context->data_loop, + do_set_position, 1, NULL, 0, true, impl); + break; + } + pw_filter_emit_io_changed(&impl->this, NULL, id, data, size); + + return 0; +} + +static int impl_send_command(void *object, const struct spa_command *command) +{ + struct filter *impl = object; + struct pw_filter *filter = &impl->this; + + switch (SPA_NODE_COMMAND_ID(command)) { + case SPA_NODE_COMMAND_Suspend: + case SPA_NODE_COMMAND_Flush: + case SPA_NODE_COMMAND_Pause: + pw_loop_invoke(impl->context->main_loop, + NULL, 0, NULL, 0, false, impl); + if (filter->state == PW_FILTER_STATE_STREAMING) { + pw_log_debug("%p: pause", filter); + filter_set_state(filter, PW_FILTER_STATE_PAUSED, NULL); + } + break; + case SPA_NODE_COMMAND_Start: + if (filter->state == PW_FILTER_STATE_PAUSED) { + pw_log_debug("%p: start", filter); + filter_set_state(filter, PW_FILTER_STATE_STREAMING, NULL); + } + break; + default: + break; + } + pw_filter_emit_command(filter, command); + return 0; +} + +static void emit_node_info(struct filter *d, bool full) +{ + uint32_t i; + uint64_t old = full ? d->info.change_mask : 0; + if (full) + d->info.change_mask = d->change_mask_all; + if (d->info.change_mask != 0) { + if (d->info.change_mask & SPA_NODE_CHANGE_MASK_PARAMS) { + for (i = 0; i < d->info.n_params; i++) { + if (d->params[i].user > 0) { + d->params[i].flags ^= SPA_PARAM_INFO_SERIAL; + d->params[i].user = 0; + } + } + } + spa_node_emit_info(&d->hooks, &d->info); + } + d->info.change_mask = old; +} + +static void emit_port_info(struct filter *d, struct port *p, bool full) +{ + uint32_t i; + uint64_t old = full ? p->info.change_mask : 0; + if (full) + p->info.change_mask = p->change_mask_all; + if (p->info.change_mask != 0) { + if (p->info.change_mask & SPA_PORT_CHANGE_MASK_PARAMS) { + for (i = 0; i < p->info.n_params; i++) { + if (p->params[i].user > 0) { + p->params[i].flags ^= SPA_PARAM_INFO_SERIAL; + p->params[i].user = 0; + } + } + } + spa_node_emit_port_info(&d->hooks, p->direction, p->id, &p->info); + } + p->info.change_mask = old; +} + +static int impl_add_listener(void *object, + struct spa_hook *listener, + const struct spa_node_events *events, + void *data) +{ + struct filter *d = object; + struct spa_hook_list save; + struct port *p; + + spa_hook_list_isolate(&d->hooks, &save, listener, events, data); + + emit_node_info(d, true); + + spa_list_for_each(p, &d->port_list, link) + emit_port_info(d, p, true); + + spa_hook_list_join(&d->hooks, &save); + + return 0; +} + +static int impl_set_callbacks(void *object, + const struct spa_node_callbacks *callbacks, void *data) +{ + struct filter *d = object; + + d->callbacks = SPA_CALLBACKS_INIT(callbacks, data); + + return 0; +} + +static int impl_port_set_io(void *object, enum spa_direction direction, uint32_t port_id, + uint32_t id, void *data, size_t size) +{ + struct filter *impl = object; + struct port *port; + + pw_log_debug("%p: id:%d (%s) %p %zd", impl, id, + spa_debug_type_find_name(spa_type_io, id), data, size); + + if ((port = get_port(impl, direction, port_id)) == NULL) + return -EINVAL; + + switch (id) { + case SPA_IO_Buffers: + if (data && size >= sizeof(struct spa_io_buffers)) + port->io = data; + else + port->io = NULL; + break; + } + + pw_filter_emit_io_changed(&impl->this, port->user_data, id, data, size); + + return 0; +} + +static int impl_port_enum_params(void *object, int seq, + enum spa_direction direction, uint32_t port_id, + uint32_t id, uint32_t start, uint32_t num, + const struct spa_pod *filter) +{ + struct filter *d = object; + struct port *port; + + if ((port = get_port(d, direction, port_id)) == NULL) + return -EINVAL; + + return enum_params(d, &port->param_list, seq, id, start, num, filter); +} + +static int update_params(struct filter *impl, struct port *port, uint32_t id, + const struct spa_pod **params, uint32_t n_params) +{ + uint32_t i; + int res = 0; + bool update_latency = false; + + if (id != SPA_ID_INVALID) { + clear_params(impl, port, id); + } else { + for (i = 0; i < n_params; i++) { + if (params[i] == NULL || !spa_pod_is_object(params[i])) + continue; + clear_params(impl, port, SPA_POD_OBJECT_ID(params[i])); + } + } + for (i = 0; i < n_params; i++) { + if (params[i] == NULL) + continue; + + if (port != NULL && + spa_pod_is_object(params[i]) && + SPA_POD_OBJECT_ID(params[i]) == SPA_PARAM_Latency) { + struct spa_latency_info info; + if (spa_latency_parse(params[i], &info) >= 0) { + port->latency[info.direction] = info; + pw_log_debug("port %p: set %s latency %f-%f %d-%d %"PRIu64"-%"PRIu64, port, + info.direction == SPA_DIRECTION_INPUT ? "input" : "output", + info.min_quantum, info.max_quantum, + info.min_rate, info.max_rate, + info.min_ns, info.max_ns); + update_latency = true; + } + continue; + } + if (add_param(impl, port, id, 0, params[i]) == NULL) { + res = -errno; + break; + } + } + if (port != NULL && update_latency) { + uint8_t buffer[4096]; + struct spa_pod_builder b; + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + add_param(impl, port, SPA_PARAM_Latency, 0, + spa_latency_build(&b, SPA_PARAM_Latency, &port->latency[0])); + add_param(impl, port, SPA_PARAM_Latency, 0, + spa_latency_build(&b, SPA_PARAM_Latency, &port->latency[1])); + } + return res; +} + +static int map_data(struct filter *impl, struct spa_data *data, int prot) +{ + void *ptr; + struct pw_map_range range; + + pw_map_range_init(&range, data->mapoffset, data->maxsize, impl->context->sc_pagesize); + + ptr = mmap(NULL, range.size, prot, MAP_SHARED, data->fd, range.offset); + if (ptr == MAP_FAILED) { + pw_log_error("%p: failed to mmap buffer mem: %m", impl); + return -errno; + } + data->data = SPA_PTROFF(ptr, range.start, void); + pw_log_debug("%p: fd %"PRIi64" mapped %d %d %p", impl, data->fd, + range.offset, range.size, data->data); + + if (impl->allow_mlock && mlock(data->data, data->maxsize) < 0) { + if (errno != ENOMEM || !mlock_warned) { + pw_log(impl->warn_mlock ? SPA_LOG_LEVEL_WARN : SPA_LOG_LEVEL_DEBUG, + "%p: Failed to mlock memory %p %u: %s", impl, + data->data, data->maxsize, + errno == ENOMEM ? + "This is not a problem but for best performance, " + "consider increasing RLIMIT_MEMLOCK" : strerror(errno)); + mlock_warned |= errno == ENOMEM; + } + } + return 0; +} + +static int unmap_data(struct filter *impl, struct spa_data *data) +{ + struct pw_map_range range; + + pw_map_range_init(&range, data->mapoffset, data->maxsize, impl->context->sc_pagesize); + + if (munmap(SPA_PTROFF(data->data, -range.start, void), range.size) < 0) + pw_log_warn("%p: failed to unmap: %m", impl); + + pw_log_debug("%p: fd %"PRIi64" unmapped", impl, data->fd); + return 0; +} + +static void clear_buffers(struct port *port) +{ + uint32_t i, j; + struct filter *impl = port->filter; + + pw_log_debug("%p: clear buffers %d", impl, port->n_buffers); + + for (i = 0; i < port->n_buffers; i++) { + struct buffer *b = &port->buffers[i]; + + if (SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_ADDED)) + pw_filter_emit_remove_buffer(&impl->this, port->user_data, &b->this); + + if (SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_MAPPED)) { + for (j = 0; j < b->this.buffer->n_datas; j++) { + struct spa_data *d = &b->this.buffer->datas[j]; + pw_log_debug("%p: clear buffer %d mem", + impl, b->id); + unmap_data(impl, d); + } + } + } + port->n_buffers = 0; + clear_queue(port, &port->dequeued); + clear_queue(port, &port->queued); +} + + +static int default_latency(struct filter *impl, struct port *port, enum spa_direction direction) +{ + struct pw_filter *filter = &impl->this; + struct spa_latency_info info; + struct port *p; + + spa_latency_info_combine_start(&info, direction); + spa_list_for_each(p, &impl->port_list, link) { + if (p->direction == direction) + continue; + spa_latency_info_combine(&info, &p->latency[direction]); + } + spa_latency_info_combine_finish(&info); + + spa_process_latency_info_add(&impl->process_latency, &info); + + spa_list_for_each(p, &impl->port_list, link) { + uint8_t buffer[4096]; + struct spa_pod_builder b; + const struct spa_pod *params[1]; + + if (p->direction != direction) + continue; + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + params[0] = spa_latency_build(&b, SPA_PARAM_Latency, &info); + pw_filter_update_params(filter, p->user_data, params, 1); + } + return 0; +} + +static int handle_latency(struct filter *impl, struct port *port, const struct spa_pod *param) +{ + struct pw_filter *filter = &impl->this; + struct spa_latency_info info; + int res; + + if (param == NULL) + return 0; + + if ((res = spa_latency_parse(param, &info)) < 0) + return res; + + pw_log_info("port %p: set %s latency %f-%f %d-%d %"PRIu64"-%"PRIu64, port, + info.direction == SPA_DIRECTION_INPUT ? "input" : "output", + info.min_quantum, info.max_quantum, + info.min_rate, info.max_rate, + info.min_ns, info.max_ns); + + if (info.direction == port->direction) + return 0; + + if (SPA_FLAG_IS_SET(impl->flags, PW_FILTER_FLAG_CUSTOM_LATENCY)) { + pw_filter_emit_param_changed(filter, port->user_data, + SPA_PARAM_Latency, param); + } else { + default_latency(impl, port, info.direction); + } + return 0; +} + +static int impl_port_set_param(void *object, + enum spa_direction direction, uint32_t port_id, + uint32_t id, uint32_t flags, + const struct spa_pod *param) +{ + struct filter *impl = object; + struct pw_filter *filter = &impl->this; + struct port *port; + int res; + bool emit = true; + const struct spa_pod *params[1]; + uint32_t n_params = 0; + + pw_log_debug("%p: port:%d.%d id:%d (%s) param:%p disconnecting:%d", impl, + direction, port_id, id, + spa_debug_type_find_name(spa_type_param, id), param, + impl->disconnecting); + + if (impl->disconnecting && param != NULL) + return -EIO; + + if ((port = get_port(impl, direction, port_id)) == NULL) + return -EINVAL; + + if (param) + pw_log_pod(SPA_LOG_LEVEL_DEBUG, param); + + params[0] = param; + n_params = param ? 1 : 0; + + if ((res = update_params(impl, port, id, params, n_params)) < 0) + return res; + + switch (id) { + case SPA_PARAM_Format: + clear_buffers(port); + break; + case SPA_PARAM_Latency: + handle_latency(impl, port, param); + emit = false; + break; + } + + if (emit) + pw_filter_emit_param_changed(filter, port->user_data, id, param); + + if (filter->state == PW_FILTER_STATE_ERROR) + return -EIO; + + emit_port_info(impl, port, false); + + return res; +} + +static int impl_port_use_buffers(void *object, + enum spa_direction direction, uint32_t port_id, + uint32_t flags, + struct spa_buffer **buffers, uint32_t n_buffers) +{ + struct filter *impl = object; + struct port *port; + struct pw_filter *filter = &impl->this; + uint32_t i, j, impl_flags; + int prot, res; + int size = 0; + + pw_log_debug("%p: port:%d.%d buffers:%u disconnecting:%d", impl, + direction, port_id, n_buffers, impl->disconnecting); + + if ((port = get_port(impl, direction, port_id)) == NULL) + return -EINVAL; + + if (impl->disconnecting && n_buffers > 0) + return -EIO; + + clear_buffers(port); + + impl_flags = port->flags; + prot = PROT_READ | (direction == SPA_DIRECTION_OUTPUT ? PROT_WRITE : 0); + + if (n_buffers > MAX_BUFFERS) + return -ENOSPC; + + for (i = 0; i < n_buffers; i++) { + int buf_size = 0; + struct buffer *b = &port->buffers[i]; + + b->flags = 0; + b->id = i; + + if (SPA_FLAG_IS_SET(impl_flags, PW_FILTER_PORT_FLAG_MAP_BUFFERS)) { + for (j = 0; j < buffers[i]->n_datas; j++) { + struct spa_data *d = &buffers[i]->datas[j]; + if ((mappable_dataTypes & (1<<d->type)) > 0) { + if ((res = map_data(impl, d, prot)) < 0) + return res; + SPA_FLAG_SET(b->flags, BUFFER_FLAG_MAPPED); + } + else if (d->type == SPA_DATA_MemPtr && d->data == NULL) { + pw_log_error("%p: invalid buffer mem", filter); + return -EINVAL; + } + buf_size += d->maxsize; + } + + if (size > 0 && buf_size != size) { + pw_log_error("%p: invalid buffer size %d", filter, buf_size); + return -EINVAL; + } else + size = buf_size; + } + pw_log_debug("%p: got buffer %d %d datas, mapped size %d", filter, i, + buffers[i]->n_datas, size); + } + + for (i = 0; i < n_buffers; i++) { + struct buffer *b = &port->buffers[i]; + + b->this.buffer = buffers[i]; + + if (port->direction == SPA_DIRECTION_OUTPUT) { + pw_log_trace("%p: recycle buffer %d", filter, b->id); + push_queue(port, &port->dequeued, b); + } + + SPA_FLAG_SET(b->flags, BUFFER_FLAG_ADDED); + pw_filter_emit_add_buffer(filter, port->user_data, &b->this); + } + + port->n_buffers = n_buffers; + + return 0; +} + +static int impl_port_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id) +{ + struct filter *impl = object; + struct port *port; + + if ((port = get_port(impl, SPA_DIRECTION_OUTPUT, port_id)) == NULL) + return -EINVAL; + + pw_log_trace("%p: recycle buffer %d", impl, buffer_id); + if (buffer_id < port->n_buffers) + push_queue(port, &port->queued, &port->buffers[buffer_id]); + + return 0; +} + +static inline void copy_position(struct filter *impl) +{ + struct spa_io_position *p = impl->rt.position; + if (SPA_UNLIKELY(p != NULL)) { + SEQ_WRITE(impl->seq); + impl->time.now = p->clock.nsec; + impl->time.rate = p->clock.rate; + if (SPA_UNLIKELY(impl->clock_id != p->clock.id)) { + impl->base_pos = p->clock.position - impl->time.ticks; + impl->clock_id = p->clock.id; + } + impl->time.ticks = p->clock.position - impl->base_pos; + impl->time.delay = 0; + SEQ_WRITE(impl->seq); + } +} + +static int +do_call_process(struct spa_loop *loop, + bool async, uint32_t seq, const void *data, size_t size, void *user_data) +{ + struct filter *impl = user_data; + struct pw_filter *filter = &impl->this; + pw_log_trace("%p: do process", filter); + pw_filter_emit_process(filter, impl->position); + return 0; +} + +static void call_process(struct filter *impl) +{ + pw_log_trace("%p: call process", impl); + if (SPA_FLAG_IS_SET(impl->flags, PW_FILTER_FLAG_RT_PROCESS)) { + spa_callbacks_call(&impl->rt_callbacks, struct pw_filter_events, + process, 0, impl->rt.position); + } + else { + pw_loop_invoke(impl->context->main_loop, + do_call_process, 1, NULL, 0, false, impl); + } +} + +static int +do_call_drained(struct spa_loop *loop, + bool async, uint32_t seq, const void *data, size_t size, void *user_data) +{ + struct filter *impl = user_data; + struct pw_filter *filter = &impl->this; + pw_log_trace("%p: drained", filter); + pw_filter_emit_drained(filter); + impl->draining = false; + return 0; +} + +static void call_drained(struct filter *impl) +{ + pw_loop_invoke(impl->context->main_loop, + do_call_drained, 1, NULL, 0, false, impl); +} + +static int impl_node_process(void *object) +{ + struct filter *impl = object; + struct port *p; + struct buffer *b; + bool drained = true; + + pw_log_trace("%p: do process %p", impl, impl->rt.position); + + /** first dequeue and recycle buffers */ + spa_list_for_each(p, &impl->port_list, link) { + struct spa_io_buffers *io = p->io; + + if (io == NULL || + io->buffer_id >= p->n_buffers) + continue; + + if (p->direction == SPA_DIRECTION_INPUT) { + if (io->status != SPA_STATUS_HAVE_DATA) + continue; + + /* push new buffer */ + b = &p->buffers[io->buffer_id]; + pw_log_trace("%p: dequeue buffer %d", impl, b->id); + push_queue(p, &p->dequeued, b); + drained = false; + } else { + if (io->status == SPA_STATUS_HAVE_DATA) + continue; + + /* recycle old buffer */ + b = &p->buffers[io->buffer_id]; + pw_log_trace("%p: recycle buffer %d", impl, b->id); + push_queue(p, &p->dequeued, b); + } + } + + copy_position(impl); + call_process(impl); + + /** recycle/push queued buffers */ + spa_list_for_each(p, &impl->port_list, link) { + struct spa_io_buffers *io = p->io; + + if (io == NULL) + continue; + + if (p->direction == SPA_DIRECTION_INPUT) { + if (io->status != SPA_STATUS_HAVE_DATA) + continue; + + /* pop buffer to recycle */ + if ((b = pop_queue(p, &p->queued)) != NULL) { + pw_log_trace("%p: recycle buffer %d", impl, b->id); + io->buffer_id = b->id; + } else { + io->buffer_id = SPA_ID_INVALID; + } + io->status = SPA_STATUS_NEED_DATA; + } else { + if (io->status == SPA_STATUS_HAVE_DATA) + continue; + + if ((b = pop_queue(p, &p->queued)) != NULL) { + pw_log_trace("%p: pop %d %p", impl, b->id, io); + io->buffer_id = b->id; + io->status = SPA_STATUS_HAVE_DATA; + drained = false; + } else { + io->buffer_id = SPA_ID_INVALID; + io->status = SPA_STATUS_NEED_DATA; + } + } + } + if (drained && impl->draining) + call_drained(impl); + + return SPA_STATUS_NEED_DATA | SPA_STATUS_HAVE_DATA; +} + +static const struct spa_node_methods impl_node = { + SPA_VERSION_NODE_METHODS, + .add_listener = impl_add_listener, + .set_callbacks = impl_set_callbacks, + .enum_params = impl_enum_params, + .set_param = impl_set_param, + .set_io = impl_set_io, + .send_command = impl_send_command, + .port_set_io = impl_port_set_io, + .port_enum_params = impl_port_enum_params, + .port_set_param = impl_port_set_param, + .port_use_buffers = impl_port_use_buffers, + .port_reuse_buffer = impl_port_reuse_buffer, + .process = impl_node_process, +}; + +static void proxy_removed(void *_data) +{ + struct pw_filter *filter = _data; + pw_log_debug("%p: removed", filter); + spa_hook_remove(&filter->proxy_listener); + filter->node_id = SPA_ID_INVALID; + filter_set_state(filter, PW_FILTER_STATE_UNCONNECTED, NULL); +} + +static void proxy_destroy(void *_data) +{ + struct pw_filter *filter = _data; + pw_log_debug("%p: destroy", filter); + proxy_removed(_data); +} + +static void proxy_error(void *_data, int seq, int res, const char *message) +{ + struct pw_filter *filter = _data; + /* we just emit the state change here to inform the application. + * If this is supposed to be a permanent error, the app should + * do a pw_stream_set_error() */ + pw_filter_emit_state_changed(filter, filter->state, + PW_FILTER_STATE_ERROR, message); +} + +static void proxy_bound(void *_data, uint32_t global_id) +{ + struct pw_filter *filter = _data; + filter->node_id = global_id; + filter_set_state(filter, PW_FILTER_STATE_PAUSED, NULL); +} + +static const struct pw_proxy_events proxy_events = { + PW_VERSION_PROXY_EVENTS, + .removed = proxy_removed, + .destroy = proxy_destroy, + .error = proxy_error, + .bound = proxy_bound, +}; + +static void on_core_error(void *_data, uint32_t id, int seq, int res, const char *message) +{ + struct pw_filter *filter = _data; + + pw_log_debug("%p: error id:%u seq:%d res:%d (%s): %s", filter, + id, seq, res, spa_strerror(res), message); + + if (id == PW_ID_CORE && res == -EPIPE) { + filter_set_state(filter, PW_FILTER_STATE_UNCONNECTED, message); + } +} + +static const struct pw_core_events core_events = { + PW_VERSION_CORE_EVENTS, + .error = on_core_error, +}; + +struct match { + struct pw_filter *filter; + int count; +}; +#define MATCH_INIT(f) ((struct match){ .filter = (f) }) + +static int execute_match(void *data, const char *location, const char *action, + const char *val, size_t len) +{ + struct match *match = data; + struct pw_filter *this = match->filter; + if (spa_streq(action, "update-props")) + match->count += pw_properties_update_string(this->properties, val, len); + return 1; +} + +static struct filter * +filter_new(struct pw_context *context, const char *name, + struct pw_properties *props, const struct pw_properties *extra) +{ + struct filter *impl; + struct pw_filter *this; + const char *str; + struct match match; + int res; + + impl = calloc(1, sizeof(struct filter)); + if (impl == NULL) { + res = -errno; + goto error_cleanup; + } + + this = &impl->this; + pw_log_debug("%p: new", impl); + + if (props == NULL) { + props = pw_properties_new(PW_KEY_MEDIA_NAME, name, NULL); + } else if (pw_properties_get(props, PW_KEY_MEDIA_NAME) == NULL) { + pw_properties_set(props, PW_KEY_MEDIA_NAME, name); + } + if (props == NULL) { + res = -errno; + goto error_properties; + } + spa_hook_list_init(&impl->hooks); + this->properties = props; + + pw_context_conf_update_props(context, "filter.properties", props); + + match = MATCH_INIT(this); + pw_context_conf_section_match_rules(context, "filter.rules", + &this->properties->dict, execute_match, &match); + + if ((str = getenv("PIPEWIRE_PROPS")) != NULL) + pw_properties_update_string(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(props, PW_KEY_NODE_RATE, + "1/%u", q.denom); + pw_properties_setf(props, PW_KEY_NODE_LATENCY, + "%u/%u", q.num, q.denom); + } + } + if ((str = getenv("PIPEWIRE_LATENCY")) != NULL) + pw_properties_set(props, PW_KEY_NODE_LATENCY, str); + if ((str = getenv("PIPEWIRE_RATE")) != NULL) + pw_properties_set(props, PW_KEY_NODE_RATE, str); + + if (pw_properties_get(props, PW_KEY_NODE_NAME) == NULL && extra) { + str = pw_properties_get(extra, PW_KEY_APP_NAME); + if (str == NULL) + str = pw_properties_get(extra, PW_KEY_APP_PROCESS_BINARY); + if (str == NULL) + str = name; + pw_properties_set(props, PW_KEY_NODE_NAME, str); + } + + this->name = name ? strdup(name) : NULL; + this->node_id = SPA_ID_INVALID; + + spa_list_init(&impl->param_list); + spa_list_init(&impl->port_list); + pw_map_init(&impl->ports[SPA_DIRECTION_INPUT], 32, 32); + pw_map_init(&impl->ports[SPA_DIRECTION_OUTPUT], 32, 32); + + spa_hook_list_init(&this->listener_list); + spa_list_init(&this->controls); + + this->state = PW_FILTER_STATE_UNCONNECTED; + + impl->context = context; + impl->allow_mlock = context->settings.mem_allow_mlock; + impl->warn_mlock = context->settings.mem_warn_mlock; + + return impl; + +error_properties: + free(impl); +error_cleanup: + pw_properties_free(props); + errno = -res; + return NULL; +} + +SPA_EXPORT +struct pw_filter * pw_filter_new(struct pw_core *core, const char *name, + struct pw_properties *props) +{ + struct filter *impl; + struct pw_filter *this; + struct pw_context *context = core->context; + + impl = filter_new(context, name, props, core->properties); + if (impl == NULL) + return NULL; + + this = &impl->this; + this->core = core; + spa_list_append(&this->core->filter_list, &this->link); + pw_core_add_listener(core, + &this->core_listener, &core_events, this); + + return this; +} + +SPA_EXPORT +struct pw_filter * +pw_filter_new_simple(struct pw_loop *loop, + const char *name, + struct pw_properties *props, + const struct pw_filter_events *events, + void *data) +{ + struct pw_filter *this; + struct filter *impl; + struct pw_context *context; + int res; + + if (props == NULL) + props = pw_properties_new(NULL, NULL); + if (props == NULL) + return NULL; + + context = pw_context_new(loop, NULL, 0); + if (context == NULL) { + res = -errno; + goto error_cleanup; + } + + impl = filter_new(context, name, props, props); + if (impl == NULL) { + props = NULL; + res = -errno; + goto error_cleanup; + } + + this = &impl->this; + + impl->data.context = context; + pw_filter_add_listener(this, &impl->data.filter_listener, events, data); + + return this; + +error_cleanup: + if (context) + pw_context_destroy(context); + pw_properties_free(props); + errno = -res; + return NULL; +} + +SPA_EXPORT +const char *pw_filter_state_as_string(enum pw_filter_state state) +{ + switch (state) { + case PW_FILTER_STATE_ERROR: + return "error"; + case PW_FILTER_STATE_UNCONNECTED: + return "unconnected"; + case PW_FILTER_STATE_CONNECTING: + return "connecting"; + case PW_FILTER_STATE_PAUSED: + return "paused"; + case PW_FILTER_STATE_STREAMING: + return "streaming"; + } + return "invalid-state"; +} + +SPA_EXPORT +void pw_filter_destroy(struct pw_filter *filter) +{ + struct filter *impl = SPA_CONTAINER_OF(filter, struct filter, this); + struct port *p; + + pw_log_debug("%p: destroy", filter); + + pw_filter_emit_destroy(filter); + + if (!impl->disconnecting) + pw_filter_disconnect(filter); + + spa_list_consume(p, &impl->port_list, link) + pw_filter_remove_port(p->user_data); + + if (filter->core) { + spa_hook_remove(&filter->core_listener); + spa_list_remove(&filter->link); + } + + clear_params(impl, NULL, SPA_ID_INVALID); + + pw_log_debug("%p: free", filter); + free(filter->error); + + pw_properties_free(filter->properties); + + spa_hook_list_clean(&impl->hooks); + spa_hook_list_clean(&filter->listener_list); + + pw_map_clear(&impl->ports[SPA_DIRECTION_INPUT]); + pw_map_clear(&impl->ports[SPA_DIRECTION_OUTPUT]); + + free(filter->name); + + if (impl->data.context) + pw_context_destroy(impl->data.context); + + free(impl); +} + +static void hook_removed(struct spa_hook *hook) +{ + struct filter *impl = hook->priv; + spa_zero(impl->rt_callbacks); + hook->priv = NULL; + hook->removed = NULL; +} + +SPA_EXPORT +void pw_filter_add_listener(struct pw_filter *filter, + struct spa_hook *listener, + const struct pw_filter_events *events, + void *data) +{ + struct filter *impl = SPA_CONTAINER_OF(filter, struct filter, this); + spa_hook_list_append(&filter->listener_list, listener, events, data); + if (events->process && impl->rt_callbacks.funcs == NULL) { + impl->rt_callbacks = SPA_CALLBACKS_INIT(events, data); + listener->removed = hook_removed; + listener->priv = impl; + } +} + +SPA_EXPORT +enum pw_filter_state pw_filter_get_state(struct pw_filter *filter, const char **error) +{ + if (error) + *error = filter->error; + return filter->state; +} + +SPA_EXPORT +struct pw_core *pw_filter_get_core(struct pw_filter *filter) +{ + return filter->core; +} + +SPA_EXPORT +const char *pw_filter_get_name(struct pw_filter *filter) +{ + return filter->name; +} + +SPA_EXPORT +const struct pw_properties *pw_filter_get_properties(struct pw_filter *filter, void *port_data) +{ + struct port *port = SPA_CONTAINER_OF(port_data, struct port, user_data); + + if (port_data) { + return port->props; + } else { + return filter->properties; + } + return NULL; +} + +SPA_EXPORT +int pw_filter_update_properties(struct pw_filter *filter, void *port_data, const struct spa_dict *dict) +{ + struct filter *impl = SPA_CONTAINER_OF(filter, struct filter, this); + struct port *port = SPA_CONTAINER_OF(port_data, struct port, user_data); + int changed = 0; + + if (port_data) { + changed = pw_properties_update(port->props, dict); + port->info.props = &port->props->dict; + if (changed > 0) { + port->info.change_mask |= SPA_PORT_CHANGE_MASK_PROPS; + emit_port_info(impl, port, false); + } + } else { + struct match match; + + changed = pw_properties_update(filter->properties, dict); + + match = MATCH_INIT(filter); + pw_context_conf_section_match_rules(impl->context, "filter.rules", + &filter->properties->dict, execute_match, &match); + + impl->info.props = &filter->properties->dict; + if (changed > 0 || match.count > 0) { + impl->info.change_mask |= SPA_NODE_CHANGE_MASK_PROPS; + emit_node_info(impl, false); + } + } + return changed; +} + +SPA_EXPORT +int +pw_filter_connect(struct pw_filter *filter, + enum pw_filter_flags flags, + const struct spa_pod **params, + uint32_t n_params) +{ + struct filter *impl = SPA_CONTAINER_OF(filter, struct filter, this); + int res; + uint32_t i; + struct spa_dict_item items[1]; + + pw_log_debug("%p: connect", filter); + impl->flags = flags; + + impl->process_rt = SPA_FLAG_IS_SET(flags, PW_FILTER_FLAG_RT_PROCESS); + + impl->warn_mlock = pw_properties_get_bool(filter->properties, "mem.warn-mlock", impl->warn_mlock); + + impl->impl_node.iface = SPA_INTERFACE_INIT( + SPA_TYPE_INTERFACE_Node, + SPA_VERSION_NODE, + &impl_node, impl); + + impl->change_mask_all = + SPA_NODE_CHANGE_MASK_FLAGS | + SPA_NODE_CHANGE_MASK_PROPS | + SPA_NODE_CHANGE_MASK_PARAMS; + + impl->info = SPA_NODE_INFO_INIT(); + impl->info.max_input_ports = UINT32_MAX; + impl->info.max_output_ports = UINT32_MAX; + impl->info.flags = impl->process_rt ? SPA_NODE_FLAG_RT : 0; + impl->info.props = &filter->properties->dict; + impl->params[IDX_PropInfo] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, 0); + impl->params[IDX_Props] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_WRITE); + impl->params[IDX_ProcessLatency] = SPA_PARAM_INFO(SPA_PARAM_ProcessLatency, 0); + impl->info.params = impl->params; + impl->info.n_params = N_NODE_PARAMS; + impl->info.change_mask = impl->change_mask_all; + + clear_params(impl, NULL, SPA_ID_INVALID); + for (i = 0; i < n_params; i++) { + add_param(impl, NULL, SPA_ID_INVALID, 0, params[i]); + } + + impl->disconnecting = false; + filter_set_state(filter, PW_FILTER_STATE_CONNECTING, NULL); + + if (flags & PW_FILTER_FLAG_DRIVER) + pw_properties_set(filter->properties, PW_KEY_NODE_DRIVER, "true"); + if ((pw_properties_get(filter->properties, PW_KEY_NODE_WANT_DRIVER) == NULL)) + pw_properties_set(filter->properties, PW_KEY_NODE_WANT_DRIVER, "true"); + + if (filter->core == NULL) { + filter->core = pw_context_connect(impl->context, + pw_properties_copy(filter->properties), 0); + if (filter->core == NULL) { + res = -errno; + goto error_connect; + } + spa_list_append(&filter->core->filter_list, &filter->link); + pw_core_add_listener(filter->core, + &filter->core_listener, &core_events, filter); + impl->disconnect_core = true; + } + + pw_log_debug("%p: export node %p", filter, &impl->impl_node); + + items[0] = SPA_DICT_ITEM_INIT(PW_KEY_OBJECT_REGISTER, "false"); + filter->proxy = pw_core_export(filter->core, + SPA_TYPE_INTERFACE_Node, &SPA_DICT_INIT_ARRAY(items), + &impl->impl_node, 0); + if (filter->proxy == NULL) { + res = -errno; + goto error_proxy; + } + + pw_proxy_add_listener(filter->proxy, &filter->proxy_listener, &proxy_events, filter); + + return 0; + +error_connect: + pw_log_error("%p: can't connect: %s", filter, spa_strerror(res)); + return res; +error_proxy: + pw_log_error("%p: can't make proxy: %s", filter, spa_strerror(res)); + return res; +} + +SPA_EXPORT +uint32_t pw_filter_get_node_id(struct pw_filter *filter) +{ + return filter->node_id; +} + +SPA_EXPORT +int pw_filter_disconnect(struct pw_filter *filter) +{ + struct filter *impl = SPA_CONTAINER_OF(filter, struct filter, this); + + pw_log_debug("%p: disconnect", filter); + impl->disconnecting = true; + + if (filter->proxy) { + pw_proxy_destroy(filter->proxy); + filter->proxy = NULL; + } + if (impl->disconnect_core) { + impl->disconnect_core = false; + spa_hook_remove(&filter->core_listener); + spa_list_remove(&filter->link); + pw_core_disconnect(filter->core); + filter->core = NULL; + } + return 0; +} + +static void add_port_params(struct filter *impl, struct port *port) +{ + uint8_t buffer[4096]; + struct spa_pod_builder b; + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + add_param(impl, port, SPA_PARAM_IO, PARAM_FLAG_LOCKED, + 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)))); +} + +static void add_audio_dsp_port_params(struct filter *impl, struct port *port) +{ + uint8_t buffer[4096]; + struct spa_pod_builder b; + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + add_param(impl, port, SPA_PARAM_EnumFormat, PARAM_FLAG_LOCKED, + 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))); + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + add_param(impl, port, SPA_PARAM_Buffers, PARAM_FLAG_LOCKED, + spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, + SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(1, 1, MAX_BUFFERS), + SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), + SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_STEP_Int( + MAX_SAMPLES * sizeof(float), + sizeof(float), + MAX_SAMPLES * sizeof(float), + sizeof(float)), + SPA_PARAM_BUFFERS_stride, SPA_POD_Int(4))); +} + +static void add_video_dsp_port_params(struct filter *impl, struct port *port) +{ + uint8_t buffer[4096]; + struct spa_pod_builder b; + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + add_param(impl, port, SPA_PARAM_EnumFormat, PARAM_FLAG_LOCKED, + 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))); +} + +static void add_control_dsp_port_params(struct filter *impl, struct port *port) +{ + uint8_t buffer[4096]; + struct spa_pod_builder b; + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + add_param(impl, port, SPA_PARAM_EnumFormat, PARAM_FLAG_LOCKED, + 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))); +} + +SPA_EXPORT +void *pw_filter_add_port(struct pw_filter *filter, + enum pw_direction direction, + enum pw_filter_port_flags flags, + size_t port_data_size, + struct pw_properties *props, + const struct spa_pod **params, uint32_t n_params) +{ + struct filter *impl = SPA_CONTAINER_OF(filter, struct filter, this); + struct port *p; + const char *str; + + if (props == NULL) + props = pw_properties_new(NULL, NULL); + if (props == NULL) + return NULL; + + if ((p = alloc_port(impl, direction, port_data_size)) == NULL) + goto error_cleanup; + + p->props = props; + p->flags = flags; + + p->change_mask_all = SPA_PORT_CHANGE_MASK_FLAGS | + SPA_PORT_CHANGE_MASK_PROPS; + p->info = SPA_PORT_INFO_INIT(); + p->info.flags = 0; + if (SPA_FLAG_IS_SET(flags, PW_FILTER_PORT_FLAG_ALLOC_BUFFERS)) + p->info.flags |= SPA_PORT_FLAG_CAN_ALLOC_BUFFERS; + p->info.props = &p->props->dict; + p->change_mask_all |= SPA_PORT_CHANGE_MASK_PARAMS; + p->params[IDX_EnumFormat] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, 0); + p->params[IDX_Meta] = SPA_PARAM_INFO(SPA_PARAM_Meta, 0); + p->params[IDX_IO] = SPA_PARAM_INFO(SPA_PARAM_IO, 0); + p->params[IDX_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); + p->params[IDX_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); + p->params[IDX_Latency] = SPA_PARAM_INFO(SPA_PARAM_Latency, SPA_PARAM_INFO_WRITE); + p->info.params = p->params; + p->info.n_params = N_PORT_PARAMS; + + /* first configure default params */ + add_port_params(impl, p); + if ((str = pw_properties_get(props, PW_KEY_FORMAT_DSP)) != NULL) { + if (spa_streq(str, "32 bit float mono audio")) + add_audio_dsp_port_params(impl, p); + else if (spa_streq(str, "32 bit float RGBA video")) + add_video_dsp_port_params(impl, p); + else if (spa_streq(str, "8 bit raw midi") || + spa_streq(str, "8 bit raw control")) + add_control_dsp_port_params(impl, p); + } + /* then override with user provided if any */ + if (update_params(impl, p, SPA_ID_INVALID, params, n_params) < 0) + goto error_free; + + emit_port_info(impl, p, true); + + return p->user_data; + + +error_free: + clear_params(impl, p, SPA_ID_INVALID); + free(p); +error_cleanup: + pw_properties_free(props); + return NULL; +} + +SPA_EXPORT +int pw_filter_remove_port(void *port_data) +{ + struct port *port = SPA_CONTAINER_OF(port_data, struct port, user_data); + struct filter *impl = port->filter; + + spa_node_emit_port_info(&impl->hooks, port->direction, port->id, NULL); + + spa_list_remove(&port->link); + pw_map_remove(&impl->ports[port->direction], port->id); + + clear_buffers(port); + clear_params(impl, port, SPA_ID_INVALID); + pw_properties_free(port->props); + free(port); + + return 0; +} + +SPA_EXPORT +int pw_filter_set_error(struct pw_filter *filter, + int res, const char *error, ...) +{ + if (res < 0) { + va_list args; + char *value; + int r; + + va_start(args, error); + r = vasprintf(&value, error, args); + va_end(args); + if (r < 0) + return -errno; + + if (filter->proxy) + pw_proxy_error(filter->proxy, res, value); + filter_set_state(filter, PW_FILTER_STATE_ERROR, value); + + free(value); + } + return res; +} + +SPA_EXPORT +int pw_filter_update_params(struct pw_filter *filter, + void *port_data, + const struct spa_pod **params, + uint32_t n_params) +{ + struct filter *impl = SPA_CONTAINER_OF(filter, struct filter, this); + struct port *port; + int res; + + pw_log_debug("%p: update params", filter); + + port = port_data ? SPA_CONTAINER_OF(port_data, struct port, user_data) : NULL; + + res = update_params(impl, port, SPA_ID_INVALID, params, n_params); + if (res < 0) + return res; + + if (port) + emit_port_info(impl, port, false); + else + emit_node_info(impl, false); + + return res; +} + +SPA_EXPORT +int pw_filter_set_active(struct pw_filter *filter, bool active) +{ + pw_log_debug("%p: active:%d", filter, active); + return 0; +} + +SPA_EXPORT +int pw_filter_get_time(struct pw_filter *filter, struct pw_time *time) +{ + struct filter *impl = SPA_CONTAINER_OF(filter, struct filter, this); + uintptr_t seq1, seq2; + + do { + seq1 = SEQ_READ(impl->seq); + *time = impl->time; + seq2 = SEQ_READ(impl->seq); + } while (!SEQ_READ_SUCCESS(seq1, seq2)); + + pw_log_trace("%p: %"PRIi64" %"PRIi64" %"PRIu64" %d/%d ", filter, + time->now, time->delay, time->ticks, + time->rate.num, time->rate.denom); + + return 0; +} + +static int +do_process(struct spa_loop *loop, + bool async, uint32_t seq, const void *data, size_t size, void *user_data) +{ + struct filter *impl = user_data; + int res = impl_node_process(impl); + return spa_node_call_ready(&impl->callbacks, res); +} + +static inline int call_trigger(struct filter *impl) +{ + int res = 0; + if (SPA_FLAG_IS_SET(impl->flags, PW_FILTER_FLAG_DRIVER)) { + res = pw_loop_invoke(impl->context->data_loop, + do_process, 1, NULL, 0, false, impl); + } + return res; +} + +SPA_EXPORT +struct pw_buffer *pw_filter_dequeue_buffer(void *port_data) +{ + struct port *p = SPA_CONTAINER_OF(port_data, struct port, user_data); + struct filter *impl = p->filter; + struct buffer *b; + int res; + + if ((b = pop_queue(p, &p->dequeued)) == NULL) { + res = -errno; + pw_log_trace("%p: no more buffers: %m", impl); + errno = -res; + return NULL; + } + pw_log_trace("%p: dequeue buffer %d", impl, b->id); + + return &b->this; +} + +SPA_EXPORT +int pw_filter_queue_buffer(void *port_data, struct pw_buffer *buffer) +{ + struct port *p = SPA_CONTAINER_OF(port_data, struct port, user_data); + struct filter *impl = p->filter; + struct buffer *b = SPA_CONTAINER_OF(buffer, struct buffer, this); + int res; + + pw_log_trace("%p: queue buffer %d", impl, b->id); + if ((res = push_queue(p, &p->queued, b)) < 0) + return res; + + return call_trigger(impl); +} + +SPA_EXPORT +void *pw_filter_get_dsp_buffer(void *port_data, uint32_t n_samples) +{ + struct port *p = SPA_CONTAINER_OF(port_data, struct port, user_data); + struct pw_buffer *buf; + struct spa_data *d; + + if ((buf = pw_filter_dequeue_buffer(port_data)) == NULL) + return NULL; + + d = &buf->buffer->datas[0]; + + if (p->direction == SPA_DIRECTION_OUTPUT) { + d->chunk->offset = 0; + d->chunk->size = n_samples * sizeof(float); + d->chunk->stride = sizeof(float); + d->chunk->flags = 0; + } + + pw_filter_queue_buffer(port_data, buf); + + return d->data; +} + +static int +do_flush(struct spa_loop *loop, + bool async, uint32_t seq, const void *data, size_t size, void *user_data) +{ +#if 0 + struct filter *impl = user_data; + struct buffer *b; + + pw_log_trace("%p: flush", impl); + do { + b = pop_queue(impl, &impl->queued); + if (b != NULL) + push_queue(impl, &impl->dequeued, b); + } + while (b); + + impl->time.queued = impl->queued.outcount = impl->dequeued.incount = + impl->dequeued.outcount = impl->queued.incount; + +#endif + return 0; +} +static int +do_drain(struct spa_loop *loop, + bool async, uint32_t seq, const void *data, size_t size, void *user_data) +{ + struct filter *impl = user_data; + impl->draining = true; + return 0; +} + +SPA_EXPORT +int pw_filter_flush(struct pw_filter *filter, bool drain) +{ + struct filter *impl = SPA_CONTAINER_OF(filter, struct filter, this); + pw_loop_invoke(impl->context->data_loop, + drain ? do_drain : do_flush, 1, NULL, 0, true, impl); + return 0; +} diff --git a/src/pipewire/filter.h b/src/pipewire/filter.h new file mode 100644 index 0000000..255608a --- /dev/null +++ b/src/pipewire/filter.h @@ -0,0 +1,250 @@ +/* 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. + */ + +#ifndef PIPEWIRE_FILTER_H +#define PIPEWIRE_FILTER_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** \defgroup pw_filter Filter + * + * \brief PipeWire filter object class + * + * The filter object provides a convenient way to implement + * processing filters. + * + * See also \ref api_pw_core + */ + +/** + * \addtogroup pw_filter + * \{ + */ +struct pw_filter; + +#include <spa/buffer/buffer.h> +#include <spa/node/io.h> +#include <spa/param/param.h> +#include <spa/pod/command.h> + +#include <pipewire/core.h> +#include <pipewire/stream.h> + +/** \enum pw_filter_state The state of a filter */ +enum pw_filter_state { + PW_FILTER_STATE_ERROR = -1, /**< the stream is in error */ + PW_FILTER_STATE_UNCONNECTED = 0, /**< unconnected */ + PW_FILTER_STATE_CONNECTING = 1, /**< connection is in progress */ + PW_FILTER_STATE_PAUSED = 2, /**< filter is connected and paused */ + PW_FILTER_STATE_STREAMING = 3 /**< filter is streaming */ +}; + +#if 0 +struct pw_buffer { + struct spa_buffer *buffer; /**< the spa buffer */ + void *user_data; /**< user data attached to the buffer */ + uint64_t size; /**< For input ports, this field is set by pw_filter + * with the duration of the buffer in ticks. + * For output ports, this field is set by the user. + * This field is added for all queued buffers and + * returned in the time info. */ +}; +#endif + +/** Events for a filter. These events are always called from the mainloop + * unless explicitly documented otherwise. */ +struct pw_filter_events { +#define PW_VERSION_FILTER_EVENTS 1 + uint32_t version; + + void (*destroy) (void *data); + /** when the filter state changes */ + void (*state_changed) (void *data, enum pw_filter_state old, + enum pw_filter_state state, const char *error); + + /** when io changed on a port of the filter (when port_data is NULL). */ + void (*io_changed) (void *data, void *port_data, + uint32_t id, void *area, uint32_t size); + /** when a parameter changed on a port of the filter (when port_data is NULL). */ + void (*param_changed) (void *data, void *port_data, + uint32_t id, const struct spa_pod *param); + + /** when a new buffer was created for a port */ + void (*add_buffer) (void *data, void *port_data, struct pw_buffer *buffer); + /** when a buffer was destroyed for a port */ + void (*remove_buffer) (void *data, void *port_data, struct pw_buffer *buffer); + + /** do processing. This is normally called from the + * mainloop but can also be called directly from the realtime data + * thread if the user is prepared to deal with this. */ + void (*process) (void *data, struct spa_io_position *position); + + /** The filter is drained */ + void (*drained) (void *data); + + /** A command notify, Since 0.3.39:1 */ + void (*command) (void *data, const struct spa_command *command); +}; + +/** Convert a filter state to a readable string */ +const char * pw_filter_state_as_string(enum pw_filter_state state); + +/** \enum pw_filter_flags Extra flags that can be used in \ref pw_filter_connect() */ +enum pw_filter_flags { + PW_FILTER_FLAG_NONE = 0, /**< no flags */ + PW_FILTER_FLAG_INACTIVE = (1 << 0), /**< start the filter inactive, + * pw_filter_set_active() needs to be + * called explicitly */ + PW_FILTER_FLAG_DRIVER = (1 << 1), /**< be a driver */ + PW_FILTER_FLAG_RT_PROCESS = (1 << 2), /**< call process from the realtime + * thread */ + PW_FILTER_FLAG_CUSTOM_LATENCY = (1 << 3), /**< don't call the default latency algorithm + * but emit the param_changed event for the + * ports when Latency params are received. */ +}; + +enum pw_filter_port_flags { + PW_FILTER_PORT_FLAG_NONE = 0, /**< no flags */ + PW_FILTER_PORT_FLAG_MAP_BUFFERS = (1 << 0), /**< mmap the buffers except DmaBuf */ + PW_FILTER_PORT_FLAG_ALLOC_BUFFERS = (1 << 1), /**< the application will allocate buffer + * memory. In the add_buffer event, the + * data of the buffer should be set */ +}; + +/** Create a new unconneced \ref pw_filter + * \return a newly allocated \ref pw_filter */ +struct pw_filter * +pw_filter_new(struct pw_core *core, /**< a \ref pw_core */ + const char *name, /**< a filter media name */ + struct pw_properties *props /**< filter properties, ownership is taken */); + +struct pw_filter * +pw_filter_new_simple(struct pw_loop *loop, /**< a \ref pw_loop to use */ + const char *name, /**< a filter media name */ + struct pw_properties *props, /**< filter properties, ownership is taken */ + const struct pw_filter_events *events, /**< filter events */ + void *data /**< data passed to events */); + +/** Destroy a filter */ +void pw_filter_destroy(struct pw_filter *filter); + +void pw_filter_add_listener(struct pw_filter *filter, + struct spa_hook *listener, + const struct pw_filter_events *events, + void *data); + +enum pw_filter_state pw_filter_get_state(struct pw_filter *filter, const char **error); + +const char *pw_filter_get_name(struct pw_filter *filter); + +struct pw_core *pw_filter_get_core(struct pw_filter *filter); + +/** Connect a filter for processing. + * \return 0 on success < 0 on error. + * + * You should connect to the process event and use pw_filter_dequeue_buffer() + * to get the latest metadata and data. */ +int +pw_filter_connect(struct pw_filter *filter, /**< a \ref pw_filter */ + enum pw_filter_flags flags, /**< filter flags */ + const struct spa_pod **params, /**< an array with params. */ + uint32_t n_params /**< number of items in \a params */); + +/** Get the node ID of the filter. + * \return node ID. */ +uint32_t +pw_filter_get_node_id(struct pw_filter *filter); + +/** Disconnect \a filter */ +int pw_filter_disconnect(struct pw_filter *filter); + +/** add a port to the filter, returns user data of port_data_size. */ +void *pw_filter_add_port(struct pw_filter *filter, /**< a \ref pw_filter */ + enum pw_direction direction, /**< port direction */ + enum pw_filter_port_flags flags, /**< port flags */ + size_t port_data_size, /**< allocated and given to the user as port_data */ + struct pw_properties *props, /**< port properties, ownership is taken */ + const struct spa_pod **params, /**< an array of params. The params should + * ideally contain the supported formats */ + uint32_t n_params /**< number of elements in \a params */); + +/** remove a port from the filter */ +int pw_filter_remove_port(void *port_data /**< data associated with port */); + +/** get properties, port_data of NULL will give global properties */ +const struct pw_properties *pw_filter_get_properties(struct pw_filter *filter, + void *port_data); + +/** Update properties, use NULL port_data for global filter properties */ +int pw_filter_update_properties(struct pw_filter *filter, + void *port_data, const struct spa_dict *dict); + +/** Set the filter in error state */ +int pw_filter_set_error(struct pw_filter *filter, /**< a \ref pw_filter */ + int res, /**< a result code */ + const char *error, /**< an error message */ + ... + ) SPA_PRINTF_FUNC(3, 4); + +/** Update params, use NULL port_data for global filter params */ +int +pw_filter_update_params(struct pw_filter *filter, /**< a \ref pw_filter */ + void *port_data, /**< data associated with port */ + const struct spa_pod **params, /**< an array of params. */ + uint32_t n_params /**< number of elements in \a params */); + + +/** Query the time on the filter, deprecated, use the spa_io_position in the + * process() method for timing information. */ +SPA_DEPRECATED +int pw_filter_get_time(struct pw_filter *filter, struct pw_time *time); + +/** Get a buffer that can be filled for output ports or consumed + * for input ports. */ +struct pw_buffer *pw_filter_dequeue_buffer(void *port_data); + +/** Submit a buffer for playback or recycle a buffer for capture. */ +int pw_filter_queue_buffer(void *port_data, struct pw_buffer *buffer); + +/** Get a data pointer to the buffer data */ +void *pw_filter_get_dsp_buffer(void *port_data, uint32_t n_samples); + +/** Activate or deactivate the filter */ +int pw_filter_set_active(struct pw_filter *filter, bool active); + +/** Flush a filter. When \a drain is true, the drained callback will + * be called when all data is played or recorded */ +int pw_filter_flush(struct pw_filter *filter, bool drain); + +/** + * \} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* PIPEWIRE_FILTER_H */ diff --git a/src/pipewire/global.c b/src/pipewire/global.c new file mode 100644 index 0000000..d069ac8 --- /dev/null +++ b/src/pipewire/global.c @@ -0,0 +1,430 @@ +/* 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 <errno.h> +#include <unistd.h> +#include <time.h> +#include <stdio.h> + +#include <pipewire/impl.h> +#include <pipewire/private.h> + +#include <spa/debug/types.h> +#include <spa/utils/string.h> + +PW_LOG_TOPIC_EXTERN(log_global); +#define PW_LOG_TOPIC_DEFAULT log_global + +/** \cond */ +struct impl { + struct pw_global this; +}; +/** \endcond */ + +SPA_EXPORT +uint32_t pw_global_get_permissions(struct pw_global *global, struct pw_impl_client *client) +{ + if (client->permission_func == NULL) + return PW_PERM_ALL; + + return client->permission_func(global, client, client->permission_data); +} + +/** Create a new global + * + * \param context a context object + * \param type the type of the global + * \param version the version of the type + * \param properties extra properties + * \param func a function to bind to this global + * \param object the associated object + * \return a result global + * + */ +SPA_EXPORT +struct pw_global * +pw_global_new(struct pw_context *context, + const char *type, + uint32_t version, + struct pw_properties *properties, + pw_global_bind_func_t func, + void *object) +{ + struct impl *impl; + struct pw_global *this; + int res; + + if (properties == NULL) + properties = pw_properties_new(NULL, NULL); + if (properties == NULL) + return NULL; + + impl = calloc(1, sizeof(struct impl)); + if (impl == NULL) { + res = -errno; + goto error_cleanup; + } + + this = &impl->this; + + this->context = context; + this->type = type; + this->version = version; + this->func = func; + this->object = object; + this->properties = properties; + this->id = pw_map_insert_new(&context->globals, this); + if (this->id == SPA_ID_INVALID) { + res = -errno; + pw_log_error("%p: can't allocate new id: %m", this); + goto error_free; + } + this->serial = SPA_ID_INVALID; + + spa_list_init(&this->resource_list); + spa_hook_list_init(&this->listener_list); + + pw_log_debug("%p: new %s %d", this, this->type, this->id); + + return this; + +error_free: + free(impl); +error_cleanup: + pw_properties_free(properties); + errno = -res; + return NULL; +} + +SPA_EXPORT +uint64_t pw_global_get_serial(struct pw_global *global) +{ + struct pw_context *context = global->context; + if (global->serial == SPA_ID_INVALID) + global->serial = context->serial++; + if ((uint32_t)context->serial == SPA_ID_INVALID) + context->serial++; + return global->serial; +} + +/** register a global to the context registry + * + * \param global a global to add + * \return 0 on success < 0 errno value on failure + * + */ +SPA_EXPORT +int pw_global_register(struct pw_global *global) +{ + struct pw_resource *registry; + struct pw_context *context = global->context; + struct pw_impl_client *client; + + if (global->registered) + return -EEXIST; + + spa_list_append(&context->global_list, &global->link); + global->registered = true; + + global->generation = ++context->generation; + + spa_list_for_each(registry, &context->registry_resource_list, link) { + uint32_t permissions = pw_global_get_permissions(global, registry->client); + pw_log_debug("registry %p: global %d %08x serial:%"PRIu64" generation:%"PRIu64, + registry, global->id, permissions, global->serial, global->generation); + if (PW_PERM_IS_R(permissions)) + pw_registry_resource_global(registry, + global->id, + permissions, + global->type, + global->version, + &global->properties->dict); + } + + /* Ensure a message is sent also to clients without registries, to force + * generation number update. */ + spa_list_for_each(client, &context->client_list, link) { + uint32_t permissions; + + if (client->sent_generation >= context->generation) + continue; + if (!client->core_resource) + continue; + + permissions = pw_global_get_permissions(global, client); + if (PW_PERM_IS_R(permissions)) { + pw_log_debug("impl-client %p: (no registry) global %d %08x serial:%"PRIu64 + " generation:%"PRIu64, client, global->id, permissions, global->serial, + global->generation); + pw_core_resource_done(client->core_resource, SPA_ID_INVALID, 0); + } + } + + pw_log_debug("%p: registered %u", global, global->id); + pw_context_emit_global_added(context, global); + + return 0; +} + +static int global_unregister(struct pw_global *global) +{ + struct pw_context *context = global->context; + struct pw_resource *resource; + + if (!global->registered) + return 0; + + spa_list_for_each(resource, &context->registry_resource_list, link) { + uint32_t permissions = pw_global_get_permissions(global, resource->client); + pw_log_debug("registry %p: global %d %08x", resource, global->id, permissions); + if (PW_PERM_IS_R(permissions)) + pw_registry_resource_global_remove(resource, global->id); + } + + spa_list_remove(&global->link); + global->registered = false; + global->serial = SPA_ID_INVALID; + + pw_log_debug("%p: unregistered %u", global, global->id); + pw_context_emit_global_removed(context, global); + + return 0; +} + +SPA_EXPORT +struct pw_context *pw_global_get_context(struct pw_global *global) +{ + return global->context; +} + +SPA_EXPORT +const char * pw_global_get_type(struct pw_global *global) +{ + return global->type; +} + +SPA_EXPORT +bool pw_global_is_type(struct pw_global *global, const char *type) +{ + return spa_streq(global->type, type); +} + +SPA_EXPORT +uint32_t pw_global_get_version(struct pw_global *global) +{ + return global->version; +} + +SPA_EXPORT +const struct pw_properties *pw_global_get_properties(struct pw_global *global) +{ + return global->properties; +} + +SPA_EXPORT +int pw_global_update_keys(struct pw_global *global, + const struct spa_dict *dict, const char * const keys[]) +{ + if (global->registered) + return -EINVAL; + return pw_properties_update_keys(global->properties, dict, keys); +} + +SPA_EXPORT +void * pw_global_get_object(struct pw_global *global) +{ + return global->object; +} + +SPA_EXPORT +uint32_t pw_global_get_id(struct pw_global *global) +{ + return global->id; +} + +SPA_EXPORT +int pw_global_add_resource(struct pw_global *global, struct pw_resource *resource) +{ + resource->global = global; + pw_log_debug("%p: resource %p id:%d global:%d", global, resource, + resource->id, global->id); + spa_list_append(&global->resource_list, &resource->link); + pw_resource_set_bound_id(resource, global->id); + return 0; +} + +SPA_EXPORT +int pw_global_for_each_resource(struct pw_global *global, + int (*callback) (void *data, struct pw_resource *resource), + void *data) +{ + struct pw_resource *resource, *t; + int res; + + spa_list_for_each_safe(resource, t, &global->resource_list, link) + if ((res = callback(data, resource)) != 0) + return res; + return 0; +} + +SPA_EXPORT +void pw_global_add_listener(struct pw_global *global, + struct spa_hook *listener, + const struct pw_global_events *events, + void *data) +{ + spa_hook_list_append(&global->listener_list, listener, events, data); +} + +/** Bind to a global + * + * \param global the global to bind to + * \param client the client that binds + * \param permissions the \ref pw_permission + * \param version the version + * \param id the id of the resource + * + * Let \a client bind to \a global with the given version and id. + * After binding, the client and the global object will be able to + * exchange messages on the proxy/resource with \a id. + * + */ +SPA_EXPORT int +pw_global_bind(struct pw_global *global, struct pw_impl_client *client, uint32_t permissions, + uint32_t version, uint32_t id) +{ + int res; + + if (global->version < version) + goto error_version; + + if ((res = global->func(global->object, client, permissions, version, id)) < 0) + goto error_bind; + + return res; + +error_version: + res = -EPROTO; + if (client->core_resource) + pw_core_resource_errorf(client->core_resource, id, client->recv_seq, + res, "id %d: interface version %d < %d", + id, global->version, version); + goto error_exit; +error_bind: + if (client->core_resource) + pw_core_resource_errorf(client->core_resource, id, client->recv_seq, + res, "can't bind global %u/%u: %d (%s)", id, version, + res, spa_strerror(res)); + goto error_exit; + +error_exit: + pw_log_error("%p: can't bind global %u/%u: %d (%s)", global, id, + version, res, spa_strerror(res)); + pw_map_insert_at(&client->objects, id, NULL); + if (client->core_resource) + pw_core_resource_remove_id(client->core_resource, id); + return res; +} + +SPA_EXPORT +int pw_global_update_permissions(struct pw_global *global, struct pw_impl_client *client, + uint32_t old_permissions, uint32_t new_permissions) +{ + struct pw_context *context = global->context; + struct pw_resource *resource, *t; + bool do_hide, do_show; + + do_hide = PW_PERM_IS_R(old_permissions) && !PW_PERM_IS_R(new_permissions); + do_show = !PW_PERM_IS_R(old_permissions) && PW_PERM_IS_R(new_permissions); + + pw_log_debug("%p: client %p permissions changed %d %08x -> %08x", + global, client, global->id, old_permissions, new_permissions); + + pw_global_emit_permissions_changed(global, client, old_permissions, new_permissions); + + spa_list_for_each(resource, &context->registry_resource_list, link) { + if (resource->client != client) + continue; + + if (do_hide) { + pw_log_debug("client %p: resource %p hide global %d", + client, resource, global->id); + pw_registry_resource_global_remove(resource, global->id); + } + else if (do_show) { + pw_log_debug("client %p: resource %p show global %d serial:%"PRIu64, + client, resource, global->id, global->serial); + pw_registry_resource_global(resource, + global->id, + new_permissions, + global->type, + global->version, + &global->properties->dict); + } + } + + spa_list_for_each_safe(resource, t, &global->resource_list, link) { + if (resource->client != client) + continue; + + /* don't ever destroy the core resource */ + if (!PW_PERM_IS_R(new_permissions) && global->id != PW_ID_CORE) + pw_resource_destroy(resource); + else + resource->permissions = new_permissions; + } + return 0; +} + +/** Destroy a global + * + * \param global a global to destroy + * + */ +SPA_EXPORT +void pw_global_destroy(struct pw_global *global) +{ + struct pw_resource *resource; + struct pw_context *context = global->context; + + global->destroyed = true; + + pw_log_debug("%p: destroy %u", global, global->id); + pw_global_emit_destroy(global); + + spa_list_consume(resource, &global->resource_list, link) + pw_resource_destroy(resource); + + global_unregister(global); + + pw_log_debug("%p: free", global); + pw_global_emit_free(global); + + pw_map_remove(&context->globals, global->id); + spa_hook_list_clean(&global->listener_list); + + pw_properties_free(global->properties); + + free(global); +} diff --git a/src/pipewire/global.h b/src/pipewire/global.h new file mode 100644 index 0000000..0cf2a7c --- /dev/null +++ b/src/pipewire/global.h @@ -0,0 +1,165 @@ +/* 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. + */ + +#ifndef PIPEWIRE_GLOBAL_H +#define PIPEWIRE_GLOBAL_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** \defgroup pw_global Global + * + * \brief A global object visible to remote clients + * + * A global object is visible to remote clients and represents a resource + * that can be used or inspected. + * + * Global objects represent resources that are available on the PipeWire + * context and are accessible to remote clients. + * Globals come and go when devices or other resources become available for + * clients. + * + * Remote clients receives a list of globals when it binds to the registry + * object. See \ref pw_registry. + * + * A client can bind to a global to send methods or receive events from + * the global. + * + * See \ref page_proxy + */ + +/** + * \addtogroup pw_global + * \{ + */ +struct pw_global; + +#include <pipewire/impl.h> + +typedef int (*pw_global_bind_func_t) (void *object, /**< global object, see \ref pw_global_new */ + struct pw_impl_client *client, /**< client that binds */ + uint32_t permissions, /**< permissions for the bind */ + uint32_t version, /**< client interface version */ + uint32_t id /**< client proxy id */); + +/** Global events, use \ref pw_global_add_listener */ +struct pw_global_events { +#define PW_VERSION_GLOBAL_EVENTS 0 + uint32_t version; + + /** The global is destroyed */ + void (*destroy) (void *data); + /** The global is freed */ + void (*free) (void *data); + /** The permissions changed for a client */ + void (*permissions_changed) (void *data, + struct pw_impl_client *client, + uint32_t old_permissions, + uint32_t new_permissions); +}; + +/** Create a new global object */ +struct pw_global * +pw_global_new(struct pw_context *context, /**< the context */ + const char *type, /**< the interface type of the global */ + uint32_t version, /**< the interface version of the global */ + struct pw_properties *properties, /**< extra properties */ + pw_global_bind_func_t func, /**< function to bind */ + void *object /**< global object */); + +/** Register a global object to the context registry */ +int pw_global_register(struct pw_global *global); + +/** Add an event listener on the global */ +void pw_global_add_listener(struct pw_global *global, + struct spa_hook *listener, + const struct pw_global_events *events, + void *data); + +/** Get the permissions of the global for a given client */ +uint32_t pw_global_get_permissions(struct pw_global *global, struct pw_impl_client *client); + +/** Get the context object of this global */ +struct pw_context *pw_global_get_context(struct pw_global *global); + +/** Get the global type */ +const char *pw_global_get_type(struct pw_global *global); + +/** Check a global type */ +bool pw_global_is_type(struct pw_global *global, const char *type); + +/** Get the global version */ +uint32_t pw_global_get_version(struct pw_global *global); + +/** Get the global properties */ +const struct pw_properties *pw_global_get_properties(struct pw_global *global); + +/** Update the global properties, must be done when unregistered */ +int pw_global_update_keys(struct pw_global *global, + const struct spa_dict *dict, const char * const keys[]); + +/** Get the object associated with the global. This depends on the type of the + * global */ +void *pw_global_get_object(struct pw_global *global); + +/** Get the unique id of the global */ +uint32_t pw_global_get_id(struct pw_global *global); + +/** Get the serial number of the global */ +uint64_t pw_global_get_serial(struct pw_global *global); + +/** Add a resource to a global */ +int pw_global_add_resource(struct pw_global *global, struct pw_resource *resource); + +/** Iterate all resources added to the global The callback should return + * 0 to fetch the next item, any other value stops the iteration and returns + * the value. When all callbacks return 0, this function returns 0 when all + * items are iterated. */ +int pw_global_for_each_resource(struct pw_global *global, + int (*callback) (void *data, struct pw_resource *resource), + void *data); + +/** Let a client bind to a global */ +int pw_global_bind(struct pw_global *global, + struct pw_impl_client *client, + uint32_t permissions, + uint32_t version, + uint32_t id); + +int pw_global_update_permissions(struct pw_global *global, struct pw_impl_client *client, + uint32_t old_permissions, uint32_t new_permissions); + +/** Destroy a global */ +void pw_global_destroy(struct pw_global *global); + +/** + * \} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* PIPEWIRE_GLOBAL_H */ diff --git a/src/pipewire/i18n.h b/src/pipewire/i18n.h new file mode 100644 index 0000000..aa7b0b3 --- /dev/null +++ b/src/pipewire/i18n.h @@ -0,0 +1,56 @@ +/* PipeWire + * + * Copyright © 2021 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_I18N_H +#define PIPEWIRE_I18N_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** \defgroup pw_gettext Internationalization + * Gettext interface + */ + +/** + * \addtogroup pw_gettext + * \{ + */ +#include <spa/support/i18n.h> + +SPA_FORMAT_ARG_FUNC(1) const char *pw_gettext(const char *msgid); +SPA_FORMAT_ARG_FUNC(1) const char *pw_ngettext(const char *msgid, const char *msgid_plural, unsigned long int n); + +#define _(String) (pw_gettext(String)) +#define N_(String) (String) + +/** + * \} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* PIPEWIRE_I18N_H */ diff --git a/src/pipewire/impl-client.c b/src/pipewire/impl-client.c new file mode 100644 index 0000000..f45e382 --- /dev/null +++ b/src/pipewire/impl-client.c @@ -0,0 +1,780 @@ +/* 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 <errno.h> +#include <string.h> +#include <assert.h> + +#include <spa/utils/string.h> + +#include "pipewire/impl.h" +#include "pipewire/private.h" + +PW_LOG_TOPIC_EXTERN(log_client); +#define PW_LOG_TOPIC_DEFAULT log_client + +/** \cond */ +struct impl { + struct pw_impl_client this; + struct spa_hook context_listener; + struct pw_array permissions; + struct spa_hook pool_listener; + unsigned int registered:1; +}; + +#define pw_client_resource(r,m,v,...) pw_resource_call(r,struct pw_client_events,m,v,__VA_ARGS__) +#define pw_client_resource_info(r,...) pw_client_resource(r,info,0,__VA_ARGS__) +#define pw_client_resource_permissions(r,...) pw_client_resource(r,permissions,0,__VA_ARGS__) + +struct resource_data { + struct pw_resource *resource; + struct spa_hook resource_listener; + struct spa_hook object_listener; + struct pw_impl_client *client; +}; + +/** find a specific permission for a global or the default when there is none */ +static struct pw_permission * +find_permission(struct pw_impl_client *client, uint32_t id) +{ + struct impl *impl = SPA_CONTAINER_OF(client, struct impl, this); + struct pw_permission *p; + uint32_t idx = id + 1; + + if (id == PW_ID_ANY) + goto do_default; + + if (!pw_array_check_index(&impl->permissions, idx, struct pw_permission)) + goto do_default; + + p = pw_array_get_unchecked(&impl->permissions, idx, struct pw_permission); + if (p->permissions == PW_PERM_INVALID) + goto do_default; + + return p; + +do_default: + return pw_array_get_unchecked(&impl->permissions, 0, struct pw_permission); +} + +static struct pw_permission *ensure_permissions(struct pw_impl_client *client, uint32_t id) +{ + struct impl *impl = SPA_CONTAINER_OF(client, struct impl, this); + struct pw_permission *p; + uint32_t idx = id + 1; + size_t len, i; + + len = pw_array_get_len(&impl->permissions, struct pw_permission); + if (len <= idx) { + size_t diff = idx - len + 1; + + p = pw_array_add(&impl->permissions, diff * sizeof(struct pw_permission)); + if (p == NULL) + return NULL; + + for (i = 0; i < diff; i++) { + p[i] = PW_PERMISSION_INIT(len + i - 1, PW_PERM_INVALID); + } + } + p = pw_array_get_unchecked(&impl->permissions, idx, struct pw_permission); + return p; +} + +/** \endcond */ + +static uint32_t +client_permission_func(struct pw_global *global, + struct pw_impl_client *client, void *data) +{ + struct pw_permission *p = find_permission(client, global->id); + return p->permissions; +} + +struct error_data { + uint32_t id; + int res; + const char *error; +}; + +static int error_resource(void *object, void *data) +{ + struct pw_resource *r = object; + struct error_data *d = data; + if (r && r->bound_id == d->id) + pw_resource_error(r, d->res, d->error); + return 0; +} + +static int client_error(void *object, uint32_t id, int res, const char *error) +{ + struct resource_data *data = object; + struct pw_impl_client *client = data->client; + struct error_data d = { id, res, error }; + + pw_log_debug("%p: error for global %d", client, id); + pw_map_for_each(&client->objects, error_resource, &d); + return 0; +} + +static bool has_key(const char * const keys[], const char *key) +{ + int i; + for (i = 0; keys[i]; i++) { + if (spa_streq(keys[i], key)) + return true; + } + return false; +} + +static int update_properties(struct pw_impl_client *client, const struct spa_dict *dict, bool filter) +{ + static const char * const ignored[] = { + PW_KEY_OBJECT_ID, + NULL + }; + + struct pw_resource *resource; + int changed = 0; + uint32_t i; + const char *old; + + for (i = 0; i < dict->n_items; i++) { + if (filter) { + if (spa_strstartswith(dict->items[i].key, "pipewire.") && + (old = pw_properties_get(client->properties, dict->items[i].key)) != NULL && + (dict->items[i].value == NULL || !spa_streq(old, dict->items[i].value))) { + pw_log_warn("%p: refuse property update '%s' from '%s' to '%s'", + client, dict->items[i].key, old, + dict->items[i].value); + continue; + + } + if (has_key(ignored, dict->items[i].key)) + continue; + } + changed += pw_properties_set(client->properties, dict->items[i].key, dict->items[i].value); + } + client->info.props = &client->properties->dict; + + pw_log_debug("%p: updated %d properties", client, changed); + + if (!changed) + return 0; + + client->info.change_mask |= PW_CLIENT_CHANGE_MASK_PROPS; + + pw_impl_client_emit_info_changed(client, &client->info); + + if (client->global) + spa_list_for_each(resource, &client->global->resource_list, link) + pw_client_resource_info(resource, &client->info); + + client->info.change_mask = 0; + + return changed; +} + +static void update_busy(struct pw_impl_client *client) +{ + struct pw_permission *def; + def = find_permission(client, PW_ID_CORE); + pw_impl_client_set_busy(client, (def->permissions & PW_PERM_R) ? false : true); +} + +static int finish_register(struct pw_impl_client *client) +{ + static const char * const keys[] = { + PW_KEY_ACCESS, + PW_KEY_CLIENT_ACCESS, + PW_KEY_APP_NAME, + NULL + }; + + struct impl *impl = SPA_CONTAINER_OF(client, struct impl, this); + struct pw_impl_client *current; + + if (impl->registered) + return 0; + + impl->registered = true; + + current = client->context->current_client; + client->context->current_client = NULL; + pw_context_emit_check_access(client->context, client); + client->context->current_client = current; + + update_busy(client); + + pw_global_update_keys(client->global, client->info.props, keys); + pw_global_register(client->global); + +#ifdef OLD_MEDIA_SESSION_WORKAROUND + /* + * XXX: temporary workaround for pipewire-media-session, see #2159 + */ + if (spa_streq(spa_dict_lookup(client->info.props, PW_KEY_APP_NAME), + "pipewire-media-session")) { + client->recv_generation = UINT64_MAX; + pw_log_info("impl-client %p: enable old pipewire-media-session workaround", + client); + } +#endif + + return 0; +} + +static int client_update_properties(void *object, const struct spa_dict *props) +{ + struct resource_data *data = object; + struct pw_impl_client *client = data->client; + int res = update_properties(client, props, true); + finish_register(client); + return res; +} + +static int client_get_permissions(void *object, uint32_t index, uint32_t num) +{ + struct resource_data *data = object; + struct pw_resource *resource = data->resource; + struct pw_impl_client *client = data->client; + struct impl *impl = SPA_CONTAINER_OF(client, struct impl, this); + size_t len; + + len = pw_array_get_len(&impl->permissions, struct pw_permission); + if ((size_t)index >= len) + num = 0; + else if ((size_t)index + (size_t)num >= len) + num = len - index; + + pw_client_resource_permissions(resource, index, + num, pw_array_get_unchecked(&impl->permissions, index, struct pw_permission)); + return 0; +} + +static int client_update_permissions(void *object, + uint32_t n_permissions, const struct pw_permission *permissions) +{ + struct resource_data *data = object; + struct pw_impl_client *client = data->client; + return pw_impl_client_update_permissions(client, n_permissions, permissions); +} + +static const struct pw_client_methods client_methods = { + PW_VERSION_CLIENT_METHODS, + .error = client_error, + .update_properties = client_update_properties, + .get_permissions = client_get_permissions, + .update_permissions = client_update_permissions +}; + +static void client_unbind_func(void *data) +{ + struct resource_data *d = data; + struct pw_resource *resource = d->resource; + spa_hook_remove(&d->resource_listener); + spa_hook_remove(&d->object_listener); + if (resource->id == 1) + resource->client->client_resource = NULL; +} + +static const struct pw_resource_events resource_events = { + PW_VERSION_RESOURCE_EVENTS, + .destroy = client_unbind_func, +}; + +static int +global_bind(void *object, struct pw_impl_client *client, uint32_t permissions, + uint32_t version, uint32_t id) +{ + struct pw_impl_client *this = object; + struct pw_global *global = this->global; + struct pw_resource *resource; + struct resource_data *data; + + resource = pw_resource_new(client, id, permissions, global->type, version, sizeof(*data)); + if (resource == NULL) + goto error_resource; + + data = pw_resource_get_user_data(resource); + data->resource = resource; + data->client = this; + pw_resource_add_listener(resource, + &data->resource_listener, + &resource_events, data); + pw_resource_add_object_listener(resource, + &data->object_listener, + &client_methods, data); + + pw_log_debug("%p: bound to %d", this, resource->id); + pw_global_add_resource(global, resource); + + if (resource->id == 1) + client->client_resource = resource; + + this->info.change_mask = PW_CLIENT_CHANGE_MASK_ALL; + pw_client_resource_info(resource, &this->info); + this->info.change_mask = 0; + + return 0; + +error_resource: + pw_log_error("%p: can't create client resource: %m", this); + return -errno; +} + +static void pool_added(void *data, struct pw_memblock *block) +{ + struct impl *impl = data; + struct pw_impl_client *client = &impl->this; + + pw_log_debug("%p: added block %d", client, block->id); + if (client->core_resource) { + pw_core_resource_add_mem(client->core_resource, + block->id, block->type, block->fd, + block->flags & PW_MEMBLOCK_FLAG_READWRITE); + } +} + +static void pool_removed(void *data, struct pw_memblock *block) +{ + struct impl *impl = data; + struct pw_impl_client *client = &impl->this; + pw_log_debug("%p: removed block %d", client, block->id); + if (client->core_resource) + pw_core_resource_remove_mem(client->core_resource, block->id); +} + +static const struct pw_mempool_events pool_events = { + PW_VERSION_MEMPOOL_EVENTS, + .added = pool_added, + .removed = pool_removed, +}; + +static void +context_global_removed(void *data, struct pw_global *global) +{ + struct impl *impl = data; + struct pw_impl_client *client = &impl->this; + struct pw_permission *p; + + p = find_permission(client, global->id); + pw_log_debug("%p: global %d removed, %p", client, global->id, p); + if (p->id != PW_ID_ANY) + p->permissions = PW_PERM_INVALID; +} + +static const struct pw_context_events context_events = { + PW_VERSION_CONTEXT_EVENTS, + .global_removed = context_global_removed, +}; + +/** Make a new client object + * + * \param core a \ref pw_context object to register the client with + * \param properties optional client properties, ownership is taken + * \return a newly allocated client object + * + */ +SPA_EXPORT +struct pw_impl_client *pw_context_create_client(struct pw_impl_core *core, + struct pw_protocol *protocol, + struct pw_properties *properties, + size_t user_data_size) +{ + struct pw_impl_client *this; + struct impl *impl; + struct pw_permission *p; + int res; + + impl = calloc(1, sizeof(struct impl) + user_data_size); + if (impl == NULL) { + res = -errno; + goto error_cleanup; + } + + this = &impl->this; + pw_log_debug("%p: new", this); + + this->refcount = 1; + this->context = core->context; + this->core = core; + this->protocol = protocol; + + if (properties == NULL) + properties = pw_properties_new(NULL, NULL); + if (properties == NULL) { + res = -errno; + goto error_free; + } + + pw_array_init(&impl->permissions, 1024); + p = pw_array_add(&impl->permissions, sizeof(struct pw_permission)); + if (p == NULL) { + res = -errno; + goto error_clear_array; + } + p->id = PW_ID_ANY; + p->permissions = 0; + + this->pool = pw_mempool_new(NULL); + if (this->pool == NULL) { + res = -errno; + goto error_clear_array; + } + pw_mempool_add_listener(this->pool, &impl->pool_listener, &pool_events, impl); + + this->properties = properties; + this->permission_func = client_permission_func; + this->permission_data = impl; + + if (user_data_size > 0) + this->user_data = SPA_PTROFF(impl, sizeof(struct impl), void); + + spa_hook_list_init(&this->listener_list); + + pw_map_init(&this->objects, 0, 32); + + pw_context_add_listener(this->context, &impl->context_listener, &context_events, impl); + + this->info.props = &this->properties->dict; + + return this; + +error_clear_array: + pw_array_clear(&impl->permissions); +error_free: + free(impl); +error_cleanup: + pw_properties_free(properties); + errno = -res; + return NULL; +} + +static void global_destroy(void *data) +{ + struct pw_impl_client *client = data; + spa_hook_remove(&client->global_listener); + client->global = NULL; + pw_impl_client_destroy(client); +} + +static const struct pw_global_events global_events = { + PW_VERSION_GLOBAL_EVENTS, + .destroy = global_destroy, +}; + +SPA_EXPORT +int pw_impl_client_register(struct pw_impl_client *client, + struct pw_properties *properties) +{ + static const char * const keys[] = { + PW_KEY_OBJECT_SERIAL, + PW_KEY_MODULE_ID, + PW_KEY_PROTOCOL, + PW_KEY_SEC_PID, + PW_KEY_SEC_UID, + PW_KEY_SEC_GID, + PW_KEY_SEC_LABEL, + NULL + }; + + struct pw_context *context = client->context; + + if (client->registered) + goto error_existed; + + pw_log_debug("%p: register", client); + + client->global = pw_global_new(context, + PW_TYPE_INTERFACE_Client, + PW_VERSION_CLIENT, + properties, + global_bind, + client); + if (client->global == NULL) + return -errno; + + spa_list_append(&context->client_list, &client->link); + client->registered = true; + + client->info.id = client->global->id; + pw_properties_setf(client->properties, PW_KEY_OBJECT_ID, "%d", client->info.id); + pw_properties_setf(client->properties, PW_KEY_OBJECT_SERIAL, "%"PRIu64, + pw_global_get_serial(client->global)); + client->info.props = &client->properties->dict; + pw_global_add_listener(client->global, &client->global_listener, &global_events, client); + + pw_global_update_keys(client->global, client->info.props, keys); + + pw_impl_client_emit_initialized(client); + + return 0; + +error_existed: + pw_properties_free(properties); + return -EEXIST; +} + +SPA_EXPORT +struct pw_context *pw_impl_client_get_context(struct pw_impl_client *client) +{ + return client->core->context; +} + +SPA_EXPORT +struct pw_protocol *pw_impl_client_get_protocol(struct pw_impl_client *client) +{ + return client->protocol; +} + +SPA_EXPORT +struct pw_resource *pw_impl_client_get_core_resource(struct pw_impl_client *client) +{ + return client->core_resource; +} + +SPA_EXPORT +struct pw_resource *pw_impl_client_find_resource(struct pw_impl_client *client, uint32_t id) +{ + return pw_map_lookup(&client->objects, id); +} + +SPA_EXPORT +struct pw_global *pw_impl_client_get_global(struct pw_impl_client *client) +{ + return client->global; +} + +SPA_EXPORT +const struct pw_properties *pw_impl_client_get_properties(struct pw_impl_client *client) +{ + return client->properties; +} + +SPA_EXPORT +void *pw_impl_client_get_user_data(struct pw_impl_client *client) +{ + return client->user_data; +} + +static int destroy_resource(void *object, void *data) +{ + if (object) + pw_resource_destroy(object); + return 0; +} + + +SPA_EXPORT +void pw_impl_client_unref(struct pw_impl_client *client) +{ + struct impl *impl = SPA_CONTAINER_OF(client, struct impl, this); + + assert(client->refcount > 0); + if (--client->refcount > 0) + return; + + pw_log_debug("%p: free", impl); + assert(client->destroyed); + + pw_impl_client_emit_free(client); + + spa_hook_list_clean(&client->listener_list); + + pw_map_clear(&client->objects); + pw_array_clear(&impl->permissions); + + spa_hook_remove(&impl->pool_listener); + pw_mempool_destroy(client->pool); + + pw_properties_free(client->properties); + + free(impl); +} + +/** Destroy a client object + * + * \param client the client to destroy + * + */ +SPA_EXPORT +void pw_impl_client_destroy(struct pw_impl_client *client) +{ + struct impl *impl = SPA_CONTAINER_OF(client, struct impl, this); + + pw_log_debug("%p: destroy", client); + + assert(!client->destroyed); + client->destroyed = true; + + pw_impl_client_emit_destroy(client); + + spa_hook_remove(&impl->context_listener); + + if (client->registered) + spa_list_remove(&client->link); + + pw_map_for_each(&client->objects, destroy_resource, client); + + if (client->global) { + spa_hook_remove(&client->global_listener); + pw_global_destroy(client->global); + } + + pw_impl_client_unref(client); +} + +SPA_EXPORT +void pw_impl_client_add_listener(struct pw_impl_client *client, + struct spa_hook *listener, + const struct pw_impl_client_events *events, + void *data) +{ + spa_hook_list_append(&client->listener_list, listener, events, data); +} + +SPA_EXPORT +const struct pw_client_info *pw_impl_client_get_info(struct pw_impl_client *client) +{ + return &client->info; +} + +/** Update client properties + * + * \param client the client + * \param dict a struct spa_dict with properties + * + * Add all properties in \a dict to the client properties. Existing + * properties are overwritten. Items can be removed by setting the value + * to NULL. + * + */ +SPA_EXPORT +int pw_impl_client_update_properties(struct pw_impl_client *client, const struct spa_dict *dict) +{ + int res = update_properties(client, dict, false); + finish_register(client); + return res; +} + +SPA_EXPORT +int pw_impl_client_update_permissions(struct pw_impl_client *client, + uint32_t n_permissions, const struct pw_permission *permissions) +{ + struct pw_impl_core *core = client->core; + struct pw_context *context = core->context; + struct pw_permission *def; + uint32_t i; + + if ((def = find_permission(client, PW_ID_ANY)) == NULL) + return -EIO; + + for (i = 0; i < n_permissions; i++) { + struct pw_permission *p; + uint32_t old_perm, new_perm; + struct pw_global *global; + + if (permissions[i].id == PW_ID_ANY) { + old_perm = def->permissions; + new_perm = permissions[i].permissions; + + if (context->current_client == client) + new_perm &= old_perm; + + pw_log_debug("%p: set default permissions %08x -> %08x", + client, old_perm, new_perm); + + def->permissions = new_perm; + + spa_list_for_each(global, &context->global_list, link) { + if (global->id == client->info.id) + continue; + p = find_permission(client, global->id); + if (p->id != PW_ID_ANY) + continue; + pw_global_update_permissions(global, client, old_perm, new_perm); + } + } + else { + struct pw_global *global; + + global = pw_context_find_global(context, permissions[i].id); + if (global == NULL || global->id != permissions[i].id) { + pw_log_warn("%p: invalid global %d", client, permissions[i].id); + continue; + } + p = ensure_permissions(client, permissions[i].id); + if (p == NULL) { + pw_log_warn("%p: can't ensure permission: %m", client); + return -errno; + } + if ((def = find_permission(client, PW_ID_ANY)) == NULL) + return -EIO; + old_perm = p->permissions == PW_PERM_INVALID ? def->permissions : p->permissions; + new_perm = permissions[i].permissions; + + if (context->current_client == client) + new_perm &= old_perm; + + pw_log_debug("%p: set global %d permissions %08x -> %08x", + client, global->id, old_perm, new_perm); + + p->permissions = new_perm; + pw_global_update_permissions(global, client, old_perm, new_perm); + } + } + update_busy(client); + return 0; +} + +SPA_EXPORT +void pw_impl_client_set_busy(struct pw_impl_client *client, bool busy) +{ + if (client->busy != busy) { + pw_log_debug("%p: busy %d", client, busy); + client->busy = busy; + pw_impl_client_emit_busy_changed(client, busy); + } +} + +SPA_EXPORT +int pw_impl_client_check_permissions(struct pw_impl_client *client, + uint32_t global_id, uint32_t permissions) +{ + struct pw_context *context = client->context; + struct pw_global *global; + uint32_t perms; + + if ((global = pw_context_find_global(context, global_id)) == NULL) + return -ENOENT; + + if (client->recv_generation != 0 && global->generation > client->recv_generation) + return -ESTALE; + + perms = pw_global_get_permissions(global, client); + if ((perms & permissions) != permissions) + return -EPERM; + + return 0; +} diff --git a/src/pipewire/impl-client.h b/src/pipewire/impl-client.h new file mode 100644 index 0000000..3d20ae6 --- /dev/null +++ b/src/pipewire/impl-client.h @@ -0,0 +1,185 @@ +/* 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. + */ + +#ifndef PIPEWIRE_IMPL_CLIENT_H +#define PIPEWIRE_IMPL_CLIENT_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <spa/utils/hook.h> + +/** \page page_client_impl Client Implementation + * + * \section sec_page_client_impl_overview Overview + * + * The \ref pw_impl_client object is created by a protocol implementation when + * a new client connects. + * + * The client is used to keep track of all resources belonging to one + * connection with the PipeWire server. + * + * \section sec_page_client_impl_credentials Credentials + * + * The client object will have its credentials filled in by the protocol. + * This information is used to check if a resource or action is available + * for this client. + * + * \section sec_page_client_impl_types Types + * + * The client and server maintain a mapping between the client and server + * types. All type ids that are in messages exchanged between the client + * and server will automatically be remapped. + * + * \section sec_page_client_impl_resources Resources + * + * When a client binds to context global object, a resource is made for this + * binding and a unique id is assigned to the resources. The client and + * server will use this id as the destination when exchanging messages. + * See also \ref pw_resource + */ + +/** \defgroup pw_impl_client Client Impl + * + * \brief PipeWire client object class + * + * The client object represents a client connection with the PipeWire + * server. + * + * Each client has its own list of resources it is bound to along with + * a mapping between the client types and server types. + * + * See: \ref page_client_impl + */ + +/** + * \addtogroup pw_impl_client + * \{ + */ +struct pw_impl_client; + +#include <pipewire/context.h> +#include <pipewire/global.h> +#include <pipewire/properties.h> +#include <pipewire/resource.h> +#include <pipewire/permission.h> + +/** The events that a client can emit */ +struct pw_impl_client_events { +#define PW_VERSION_IMPL_CLIENT_EVENTS 0 + uint32_t version; + + /** emitted when the client is destroyed */ + void (*destroy) (void *data); + + /** emitted right before the client is freed */ + void (*free) (void *data); + + /** the client is initialized */ + void (*initialized) (void *data); + + /** emitted when the client info changed */ + void (*info_changed) (void *data, const struct pw_client_info *info); + + /** emitted when a new resource is added for client */ + void (*resource_added) (void *data, struct pw_resource *resource); + + /** emitted when a resource is removed */ + void (*resource_removed) (void *data, struct pw_resource *resource); + + /** emitted when the client becomes busy processing an asynchronous + * message. In the busy state no messages should be processed. + * Processing should resume when the client becomes not busy */ + void (*busy_changed) (void *data, bool busy); +}; + +/** Create a new client. This is mainly used by protocols. */ +struct pw_impl_client * +pw_context_create_client(struct pw_impl_core *core, /**< the core object */ + struct pw_protocol *protocol, /**< the client protocol */ + struct pw_properties *properties, /**< client properties */ + size_t user_data_size /**< extra user data size */); + +/** Destroy a previously created client */ +void pw_impl_client_destroy(struct pw_impl_client *client); + +/** Finish configuration and register a client */ +int pw_impl_client_register(struct pw_impl_client *client, /**< the client to register */ + struct pw_properties *properties/**< extra properties */); + +/** Get the client user data */ +void *pw_impl_client_get_user_data(struct pw_impl_client *client); + +/** Get the client information */ +const struct pw_client_info *pw_impl_client_get_info(struct pw_impl_client *client); + +/** Update the client properties */ +int pw_impl_client_update_properties(struct pw_impl_client *client, const struct spa_dict *dict); + +/** Update the client permissions */ +int pw_impl_client_update_permissions(struct pw_impl_client *client, uint32_t n_permissions, + const struct pw_permission *permissions); + +/** check if a client has permissions for global_id, Since 0.3.9 */ +int pw_impl_client_check_permissions(struct pw_impl_client *client, + uint32_t global_id, uint32_t permissions); + +/** Get the client properties */ +const struct pw_properties *pw_impl_client_get_properties(struct pw_impl_client *client); + +/** Get the context used to create this client */ +struct pw_context *pw_impl_client_get_context(struct pw_impl_client *client); +/** Get the protocol used to create this client */ +struct pw_protocol *pw_impl_client_get_protocol(struct pw_impl_client *client); + +/** Get the client core resource */ +struct pw_resource *pw_impl_client_get_core_resource(struct pw_impl_client *client); + +/** Get a resource with the given id */ +struct pw_resource *pw_impl_client_find_resource(struct pw_impl_client *client, uint32_t id); + +/** Get the global associated with this client */ +struct pw_global *pw_impl_client_get_global(struct pw_impl_client *client); + +/** listen to events from this client */ +void pw_impl_client_add_listener(struct pw_impl_client *client, + struct spa_hook *listener, + const struct pw_impl_client_events *events, + void *data); + + +/** Mark the client busy. This can be used when an asynchronous operation is + * started and no further processing is allowed to happen for the client */ +void pw_impl_client_set_busy(struct pw_impl_client *client, bool busy); + +/** + * \} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* PIPEWIRE_IMPL_CLIENT_H */ diff --git a/src/pipewire/impl-core.c b/src/pipewire/impl-core.c new file mode 100644 index 0000000..776f030 --- /dev/null +++ b/src/pipewire/impl-core.c @@ -0,0 +1,673 @@ +/* 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 "config.h" + +#include <unistd.h> +#ifndef ENODATA +#define ENODATA 9919 +#endif + +#include <spa/debug/types.h> +#include <spa/utils/string.h> + +#include "pipewire/impl.h" +#include "pipewire/private.h" + +#include "pipewire/extensions/protocol-native.h" + +PW_LOG_TOPIC_EXTERN(log_core); +#define PW_LOG_TOPIC_DEFAULT log_core + +struct resource_data { + struct pw_resource *resource; + struct spa_hook resource_listener; + struct spa_hook object_listener; +}; + +static void * registry_bind(void *object, uint32_t id, + const char *type, uint32_t version, size_t user_data_size) +{ + struct resource_data *data = object; + struct pw_resource *resource = data->resource; + struct pw_impl_client *client = resource->client; + struct pw_context *context = resource->context; + struct pw_global *global; + uint32_t permissions, new_id = user_data_size; + + if ((global = pw_context_find_global(context, id)) == NULL) + goto error_no_id; + + permissions = pw_global_get_permissions(global, client); + + if (!PW_PERM_IS_R(permissions)) + goto error_no_id; + + if (resource->client->recv_generation != 0 && global->generation > resource->client->recv_generation) + goto error_stale_id; + + if (!spa_streq(global->type, type)) + goto error_wrong_interface; + + pw_log_debug("global %p: bind global id %d, iface %s/%d to %d", global, id, + type, version, new_id); + + if (pw_global_bind(global, client, permissions, version, new_id) < 0) + goto error_exit_clean; + + return NULL; + +error_stale_id: + pw_log_debug("registry %p: not binding stale global " + "id %u to %u, generation:%"PRIu64" recv-generation:%"PRIu64, + resource, id, new_id, global->generation, resource->client->recv_generation); + pw_resource_errorf_id(resource, new_id, -ESTALE, "no global %u any more", id); + goto error_exit_clean; +error_no_id: + pw_log_debug("registry %p: no global with id %u to bind to %u", resource, id, new_id); + pw_resource_errorf_id(resource, new_id, -ENOENT, "no global %u", id); + goto error_exit_clean; +error_wrong_interface: + pw_log_debug("registry %p: global with id %u has no interface %s", resource, id, type); + pw_resource_errorf_id(resource, new_id, -ENOSYS, "no interface %s", type); + goto error_exit_clean; +error_exit_clean: + /* unmark the new_id the map, the client does not yet know about the failed + * bind and will choose the next id, which we would refuse when we don't mark + * new_id as 'used and freed' */ + pw_map_insert_at(&client->objects, new_id, NULL); + pw_core_resource_remove_id(client->core_resource, new_id); + return NULL; +} + +static int registry_destroy(void *object, uint32_t id) +{ + struct resource_data *data = object; + struct pw_resource *resource = data->resource; + struct pw_impl_client *client = resource->client; + struct pw_context *context = resource->context; + struct pw_global *global; + uint32_t permissions; + int res; + + if ((global = pw_context_find_global(context, id)) == NULL) + goto error_no_id; + + permissions = pw_global_get_permissions(global, client); + + if (!PW_PERM_IS_R(permissions)) + goto error_no_id; + + if (resource->client->recv_generation != 0 && global->generation > resource->client->recv_generation) + goto error_stale_id; + + if (id == PW_ID_CORE || !PW_PERM_IS_X(permissions)) + goto error_not_allowed; + + pw_log_debug("global %p: destroy global id %d", global, id); + + pw_global_destroy(global); + return 0; + +error_stale_id: + pw_log_debug("registry %p: not destroying stale global " + "id %u, generation:%"PRIu64" recv-generation:%"PRIu64, + resource, id, global->generation, resource->client->recv_generation); + pw_resource_errorf(resource, -ESTALE, "no global %u any more", id); + res = -ESTALE; + goto error_exit; +error_no_id: + pw_log_debug("registry %p: no global with id %u to destroy", resource, id); + pw_resource_errorf(resource, -ENOENT, "no global %u", id); + res = -ENOENT; + goto error_exit; +error_not_allowed: + pw_log_debug("registry %p: destroy of id %u not allowed", resource, id); + pw_resource_errorf(resource, -EPERM, "no permission to destroy %u", id); + res = -EPERM; + goto error_exit; +error_exit: + return res; +} + +static const struct pw_registry_methods registry_methods = { + PW_VERSION_REGISTRY_METHODS, + .bind = registry_bind, + .destroy = registry_destroy +}; + +static void destroy_registry_resource(void *_data) +{ + struct resource_data *data = _data; + struct pw_resource *resource = data->resource; + spa_list_remove(&resource->link); + spa_hook_remove(&data->resource_listener); + spa_hook_remove(&data->object_listener); +} + +static const struct pw_resource_events resource_events = { + PW_VERSION_RESOURCE_EVENTS, + .destroy = destroy_registry_resource +}; + +static int destroy_resource(void *object, void *data) +{ + struct pw_resource *resource = object; + struct pw_impl_client *client; + + if (resource && + (client = resource->client) != NULL && + resource != client->core_resource) { + pw_resource_remove(resource); + } + return 0; +} + +static int core_hello(void *object, uint32_t version) +{ + struct pw_resource *resource = object; + struct pw_impl_client *client = resource->client; + struct pw_context *context = client->context; + struct pw_impl_core *this = client->core; + int res; + + pw_log_debug("%p: hello %d from resource %p", context, version, resource); + pw_map_for_each(&client->objects, destroy_resource, client); + + pw_mempool_clear(client->pool); + + this->info.change_mask = PW_CORE_CHANGE_MASK_ALL; + pw_core_resource_info(resource, &this->info); + + if (version >= 3) { + if ((res = pw_global_bind(client->global, client, + PW_PERM_ALL, PW_VERSION_CLIENT, PW_ID_CLIENT)) < 0) + return res; + } + return 0; +} + +static int core_sync(void *object, uint32_t id, int seq) +{ + struct pw_resource *resource = object; + pw_log_trace("%p: sync %d for resource %d", resource->context, seq, id); + pw_core_resource_done(resource, id, seq); + return 0; +} + +static int core_pong(void *object, uint32_t id, int seq) +{ + struct pw_resource *resource = object; + struct pw_impl_client *client = resource->client; + struct pw_resource *r; + + pw_log_debug("%p: pong %d for resource %d", resource->context, seq, id); + + if ((r = pw_impl_client_find_resource(client, id)) == NULL) + return -EINVAL; + + pw_resource_emit_pong(r, seq); + return 0; +} + +static int core_error(void *object, uint32_t id, int seq, int res, const char *message) +{ + struct pw_resource *resource = object; + struct pw_impl_client *client = resource->client; + struct pw_resource *r; + + pw_log_error("%p: error %d for resource %d: %s", resource->context, res, id, message); + + if ((r = pw_impl_client_find_resource(client, id)) == NULL) + return -EINVAL; + + pw_resource_emit_error(r, seq, res, message); + return 0; +} + +static struct pw_registry *core_get_registry(void *object, uint32_t version, size_t user_data_size) +{ + struct pw_resource *resource = object; + struct pw_impl_client *client = resource->client; + struct pw_context *context = client->context; + struct pw_global *global; + struct pw_resource *registry_resource; + struct resource_data *data; + uint32_t new_id = user_data_size; + int res; + + registry_resource = pw_resource_new(client, + new_id, + PW_PERM_ALL, + PW_TYPE_INTERFACE_Registry, + version, + sizeof(*data)); + if (registry_resource == NULL) { + res = -errno; + goto error_resource; + } + + data = pw_resource_get_user_data(registry_resource); + data->resource = registry_resource; + pw_resource_add_listener(registry_resource, + &data->resource_listener, + &resource_events, + data); + pw_resource_add_object_listener(registry_resource, + &data->object_listener, + ®istry_methods, + data); + + spa_list_append(&context->registry_resource_list, ®istry_resource->link); + + spa_list_for_each(global, &context->global_list, link) { + uint32_t permissions = pw_global_get_permissions(global, client); + if (PW_PERM_IS_R(permissions)) { + pw_registry_resource_global(registry_resource, + global->id, + permissions, + global->type, + global->version, + &global->properties->dict); + } + } + + return (struct pw_registry *)registry_resource; + +error_resource: + pw_core_resource_errorf(client->core_resource, new_id, + client->recv_seq, res, + "can't create registry resource: %d (%s)", + res, spa_strerror(res)); + pw_map_insert_at(&client->objects, new_id, NULL); + pw_core_resource_remove_id(client->core_resource, new_id); + errno = -res; + return NULL; +} + +static void * +core_create_object(void *object, + const char *factory_name, + const char *type, + uint32_t version, + const struct spa_dict *props, + size_t user_data_size) +{ + struct pw_resource *resource = object; + struct pw_impl_client *client = resource->client; + struct pw_impl_factory *factory; + void *obj; + struct pw_properties *properties; + struct pw_context *context = client->context; + uint32_t new_id = user_data_size; + int res; + + factory = pw_context_find_factory(context, factory_name); + if (factory == NULL || factory->global == NULL) + goto error_no_factory; + + if (!PW_PERM_IS_R(pw_global_get_permissions(factory->global, client))) + goto error_no_factory; + + if (!spa_streq(factory->info.type, type)) + goto error_type; + + if (factory->info.version < version) { + pw_log_info("%p: version %d < %d", context, + factory->info.version, version); + } + + if (props) { + properties = pw_properties_new_dict(props); + if (properties == NULL) + goto error_properties; + } else + properties = NULL; + + /* error will be posted */ + obj = pw_impl_factory_create_object(factory, resource, type, + version, properties, new_id); + if (obj == NULL) + goto error_create_failed; + + return 0; + +error_no_factory: + res = -ENOENT; + pw_log_debug("%p: can't find factory '%s'", context, factory_name); + pw_resource_errorf_id(resource, new_id, res, "unknown factory name %s", factory_name); + goto error_exit; +error_type: + res = -EPROTO; + pw_log_debug("%p: invalid resource type/version", context); + pw_resource_errorf_id(resource, new_id, res, "wrong resource type/version"); + goto error_exit; +error_properties: + res = -errno; + pw_log_debug("%p: can't create properties: %m", context); + pw_resource_errorf_id(resource, new_id, res, "can't create properties: %s", spa_strerror(res)); + goto error_exit; +error_create_failed: + res = -errno; + goto error_exit; +error_exit: + pw_map_insert_at(&client->objects, new_id, NULL); + pw_core_resource_remove_id(client->core_resource, new_id); + errno = -res; + return NULL; +} + +static int core_destroy(void *object, void *proxy) +{ + struct pw_resource *resource = object; + struct pw_impl_client *client = resource->client; + struct pw_impl_core *this = client->core; + struct pw_resource *r = proxy; + pw_log_debug("%p: destroy resource %p from client %p", this, r, client); + pw_resource_destroy(r); + return 0; +} + +static const struct pw_core_methods core_methods = { + PW_VERSION_CORE_METHODS, + .hello = core_hello, + .sync = core_sync, + .pong = core_pong, + .error = core_error, + .get_registry = core_get_registry, + .create_object = core_create_object, + .destroy = core_destroy, +}; + +SPA_EXPORT +struct pw_impl_core *pw_context_create_core(struct pw_context *context, + struct pw_properties *properties, + size_t user_data_size) +{ + struct pw_impl_core *this; + const char *name; + int res; + + if (properties == NULL) + properties = pw_properties_new(NULL, NULL); + if (properties == NULL) + return NULL; + + this = calloc(1, sizeof(*this) + user_data_size); + if (this == NULL) { + res = -errno; + goto error_exit; + }; + + this->context = context; + this->properties = properties; + + if ((name = pw_properties_get(properties, PW_KEY_CORE_NAME)) == NULL) { + pw_properties_setf(properties, + PW_KEY_CORE_NAME, "pipewire-%s-%d", + pw_get_user_name(), getpid()); + name = pw_properties_get(properties, PW_KEY_CORE_NAME); + } + + this->info.user_name = pw_get_user_name(); + this->info.host_name = pw_get_host_name(); + this->info.version = pw_get_library_version(); + do { + res = pw_getrandom(&this->info.cookie, + sizeof(this->info.cookie), 0); + } while ((res == -1) && (errno == EINTR)); + if (res == -1) { + res = -errno; + goto error_exit; + } else if (res != sizeof(this->info.cookie)) { + res = -ENODATA; + goto error_exit; + } + this->info.name = name; + spa_hook_list_init(&this->listener_list); + + if (user_data_size > 0) + this->user_data = SPA_PTROFF(this, sizeof(*this), void); + + pw_log_debug("%p: new %s", this, name); + + return this; + +error_exit: + pw_properties_free(properties); + free(this); + errno = -res; + return NULL; +} + +SPA_EXPORT +struct pw_impl_core *pw_context_get_default_core(struct pw_context *context) +{ + return context->core; +} + +SPA_EXPORT +void pw_impl_core_destroy(struct pw_impl_core *core) +{ + pw_log_debug("%p: destroy", core); + pw_impl_core_emit_destroy(core); + + if (core->registered) + spa_list_remove(&core->link); + + if (core->global) { + spa_hook_remove(&core->global_listener); + pw_global_destroy(core->global); + } + + pw_impl_core_emit_free(core); + pw_log_debug("%p: free", core); + + spa_hook_list_clean(&core->listener_list); + + pw_properties_free(core->properties); + + free(core); +} + +static void core_unbind_func(void *data) +{ + struct resource_data *d = data; + struct pw_resource *resource = d->resource; + spa_hook_remove(&d->resource_listener); + spa_hook_remove(&d->object_listener); + if (resource->id == 0) + resource->client->core_resource = NULL; +} + +static const struct pw_resource_events core_resource_events = { + PW_VERSION_RESOURCE_EVENTS, + .destroy = core_unbind_func, +}; + +static int +global_bind(void *object, + struct pw_impl_client *client, + uint32_t permissions, + uint32_t version, + uint32_t id) +{ + struct pw_impl_core *this = object; + struct pw_global *global = this->global; + struct pw_resource *resource; + struct resource_data *data; + int res; + + resource = pw_resource_new(client, id, permissions, global->type, version, sizeof(*data)); + if (resource == NULL) { + res = -errno; + goto error; + } + + data = pw_resource_get_user_data(resource); + data->resource = resource; + + pw_resource_add_listener(resource, + &data->resource_listener, + &core_resource_events, data); + pw_resource_add_object_listener(resource, + &data->object_listener, + &core_methods, resource); + + pw_global_add_resource(global, resource); + + if (resource->id == 0) { + client->core_resource = resource; + } + else { + this->info.change_mask = PW_CORE_CHANGE_MASK_ALL; + pw_core_resource_info(resource, &this->info); + this->info.change_mask = 0; + } + + pw_log_debug("%p: bound to %d", this, resource->id); + + return 0; + +error: + pw_log_error("%p: can't create resource: %m", this); + return res; +} + +static void global_destroy(void *data) +{ + struct pw_impl_core *core = data; + spa_hook_remove(&core->global_listener); + core->global = NULL; + pw_impl_core_destroy(core); +} + +static const struct pw_global_events global_events = { + PW_VERSION_GLOBAL_EVENTS, + .destroy = global_destroy, +}; + +SPA_EXPORT +const struct pw_properties *pw_impl_core_get_properties(struct pw_impl_core *core) +{ + return core->properties; +} + +SPA_EXPORT +int pw_impl_core_update_properties(struct pw_impl_core *core, const struct spa_dict *dict) +{ + struct pw_resource *resource; + int changed; + + changed = pw_properties_update(core->properties, dict); + core->info.props = &core->properties->dict; + + pw_log_debug("%p: updated %d properties", core, changed); + + if (!changed) + return 0; + + core->info.change_mask |= PW_CORE_CHANGE_MASK_PROPS; + if (core->global) + spa_list_for_each(resource, &core->global->resource_list, link) + pw_core_resource_info(resource, &core->info); + core->info.change_mask = 0; + + return changed; +} + +SPA_EXPORT +int pw_impl_core_register(struct pw_impl_core *core, + struct pw_properties *properties) +{ + static const char * const keys[] = { + PW_KEY_OBJECT_SERIAL, + PW_KEY_USER_NAME, + PW_KEY_HOST_NAME, + PW_KEY_CORE_NAME, + PW_KEY_CORE_VERSION, + NULL + }; + + struct pw_context *context = core->context; + int res; + + if (core->registered) + goto error_existed; + + core->global = pw_global_new(context, + PW_TYPE_INTERFACE_Core, + PW_VERSION_CORE, + properties, + global_bind, + core); + if (core->global == NULL) + return -errno; + + spa_list_append(&context->core_impl_list, &core->link); + core->registered = true; + + core->info.id = core->global->id; + pw_properties_setf(core->properties, PW_KEY_OBJECT_ID, "%d", core->info.id); + pw_properties_setf(core->properties, PW_KEY_OBJECT_SERIAL, "%"PRIu64, + pw_global_get_serial(core->global)); + core->info.props = &core->properties->dict; + + pw_global_update_keys(core->global, core->info.props, keys); + + pw_impl_core_emit_initialized(core); + + pw_global_add_listener(core->global, &core->global_listener, &global_events, core); + pw_global_register(core->global); + + return 0; + +error_existed: + res = -EEXIST; + goto error_exit; +error_exit: + pw_properties_free(properties); + return res; +} + +SPA_EXPORT +void *pw_impl_core_get_user_data(struct pw_impl_core *core) +{ + return core->user_data; +} + +SPA_EXPORT +struct pw_global *pw_impl_core_get_global(struct pw_impl_core *core) +{ + return core->global; +} + +SPA_EXPORT +void pw_impl_core_add_listener(struct pw_impl_core *core, + struct spa_hook *listener, + const struct pw_impl_core_events *events, + void *data) +{ + spa_hook_list_append(&core->listener_list, listener, events, data); +} diff --git a/src/pipewire/impl-core.h b/src/pipewire/impl-core.h new file mode 100644 index 0000000..ec4107e --- /dev/null +++ b/src/pipewire/impl-core.h @@ -0,0 +1,104 @@ +/* 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. + */ + +#ifndef PIPEWIRE_IMPL_CORE_H +#define PIPEWIRE_IMPL_CORE_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** \defgroup pw_impl_core Core Impl + * + * \brief PipeWire core interface. + * + * The core is used to make objects on demand. + */ + +/** + * \addtogroup pw_impl_core + * \{ + */ + +struct pw_impl_core; + +#include <pipewire/context.h> +#include <pipewire/global.h> +#include <pipewire/properties.h> +#include <pipewire/resource.h> + +/** Factory events, listen to them with \ref pw_impl_core_add_listener */ +struct pw_impl_core_events { +#define PW_VERSION_IMPL_CORE_EVENTS 0 + uint32_t version; + + /** the core is destroyed */ + void (*destroy) (void *data); + /** the core is freed */ + void (*free) (void *data); + /** the core is initialized */ + void (*initialized) (void *data); +}; + +struct pw_impl_core *pw_context_create_core(struct pw_context *context, + struct pw_properties *properties, + size_t user_data_size); + +/* get the default core in a context */ +struct pw_impl_core *pw_context_get_default_core(struct pw_context *context); + +/** Get the core properties */ +const struct pw_properties *pw_impl_core_get_properties(struct pw_impl_core *core); + +/** Get the core information */ +const struct pw_core_info *pw_impl_core_get_info(struct pw_impl_core *core); + +/** Update the core properties */ +int pw_impl_core_update_properties(struct pw_impl_core *core, const struct spa_dict *dict); + +int pw_impl_core_register(struct pw_impl_core *core, + struct pw_properties *properties); + +void pw_impl_core_destroy(struct pw_impl_core *core); + +void *pw_impl_core_get_user_data(struct pw_impl_core *core); + +/** Get the global of this core */ +struct pw_global *pw_impl_core_get_global(struct pw_impl_core *core); + +/** Add an event listener */ +void pw_impl_core_add_listener(struct pw_impl_core *core, + struct spa_hook *listener, + const struct pw_impl_core_events *events, + void *data); + +/** + * \} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* PIPEWIRE_IMPL_CORE_H */ diff --git a/src/pipewire/impl-device.c b/src/pipewire/impl-device.c new file mode 100644 index 0000000..855b046 --- /dev/null +++ b/src/pipewire/impl-device.c @@ -0,0 +1,935 @@ +/* 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 <errno.h> + +#include <spa/debug/types.h> +#include <spa/monitor/utils.h> +#include <spa/pod/filter.h> +#include <spa/pod/dynamic.h> +#include <spa/utils/string.h> + +#include "pipewire/impl.h" +#include "pipewire/private.h" + +PW_LOG_TOPIC_EXTERN(log_device); +#define PW_LOG_TOPIC_DEFAULT log_device + +struct impl { + struct pw_impl_device this; + + struct spa_list param_list; + struct spa_list pending_list; + + unsigned int cache_params:1; +}; + +#define pw_device_resource(r,m,v,...) pw_resource_call(r,struct pw_device_events,m,v,__VA_ARGS__) +#define pw_device_resource_info(r,...) pw_device_resource(r,info,0,__VA_ARGS__) +#define pw_device_resource_param(r,...) pw_device_resource(r,param,0,__VA_ARGS__) + +struct result_device_params_data { + struct impl *impl; + void *data; + int (*callback) (void *data, int seq, + uint32_t id, uint32_t index, uint32_t next, + struct spa_pod *param); + int seq; + uint32_t count; + unsigned int cache:1; +}; + +struct resource_data { + struct pw_impl_device *device; + struct pw_resource *resource; + + struct spa_hook resource_listener; + struct spa_hook object_listener; + + uint32_t subscribe_ids[MAX_PARAMS]; + uint32_t n_subscribe_ids; + + /* for async replies */ + int seq; + int orig_seq; + int end; + struct spa_param_info *pi; + struct result_device_params_data data; + struct spa_hook listener; +}; + +struct object_data { + struct spa_list link; + uint32_t id; +#define OBJECT_NODE 0 +#define OBJECT_DEVICE 1 + uint32_t type; + struct spa_handle *handle; + void *object; + struct spa_hook listener; +}; + +static void object_destroy(struct object_data *od) +{ + switch (od->type) { + case OBJECT_NODE: + pw_impl_node_destroy(od->object); + break; + case OBJECT_DEVICE: + pw_impl_device_destroy(od->object); + break; + } +} + +static void object_update(struct object_data *od, const struct spa_dict *props) +{ + switch (od->type) { + case OBJECT_NODE: + pw_impl_node_update_properties(od->object, props); + break; + case OBJECT_DEVICE: + pw_impl_device_update_properties(od->object, props); + break; + } +} + +static void object_register(struct object_data *od) +{ + switch (od->type) { + case OBJECT_NODE: + pw_impl_node_register(od->object, NULL); + pw_impl_node_set_active(od->object, true); + break; + case OBJECT_DEVICE: + pw_impl_device_register(od->object, NULL); + break; + } +} + +static void check_properties(struct pw_impl_device *device) +{ + const char *str; + + if ((str = pw_properties_get(device->properties, PW_KEY_DEVICE_NAME)) && + (device->name == NULL || !spa_streq(str, device->name))) { + free(device->name); + device->name = strdup(str); + pw_log_debug("%p: name '%s'", device, device->name); + } +} + +SPA_EXPORT +struct pw_impl_device *pw_context_create_device(struct pw_context *context, + struct pw_properties *properties, + size_t user_data_size) +{ + struct impl *impl; + struct pw_impl_device *this; + int res; + + impl = calloc(1, sizeof(struct impl) + user_data_size); + if (impl == NULL) { + res = -errno; + goto error_cleanup; + } + spa_list_init(&impl->param_list); + spa_list_init(&impl->pending_list); + impl->cache_params = true; + + this = &impl->this; + this->name = strdup("device"); + pw_log_debug("%p: new", this); + + if (properties == NULL) + properties = pw_properties_new(NULL, NULL); + if (properties == NULL) { + res = -errno; + goto error_free; + } + + this->context = context; + this->properties = properties; + + this->info.props = &properties->dict; + this->info.params = this->params; + spa_hook_list_init(&this->listener_list); + + spa_list_init(&this->object_list); + + if (user_data_size > 0) + this->user_data = SPA_PTROFF(this, sizeof(struct impl), void); + + check_properties(this); + + return this; + +error_free: + free(impl); +error_cleanup: + pw_properties_free(properties); + errno = -res; + return NULL; +} + +SPA_EXPORT +void pw_impl_device_destroy(struct pw_impl_device *device) +{ + struct impl *impl = SPA_CONTAINER_OF(device, struct impl, this); + struct object_data *od; + + pw_log_debug("%p: destroy", device); + pw_impl_device_emit_destroy(device); + + spa_list_consume(od, &device->object_list, link) + object_destroy(od); + + if (device->registered) + spa_list_remove(&device->link); + + if (device->device) + spa_hook_remove(&device->listener); + + if (device->global) { + spa_hook_remove(&device->global_listener); + pw_global_destroy(device->global); + } + pw_log_debug("%p: free", device); + pw_impl_device_emit_free(device); + + pw_param_clear(&impl->param_list, SPA_ID_INVALID); + pw_param_clear(&impl->pending_list, SPA_ID_INVALID); + + spa_hook_list_clean(&device->listener_list); + + pw_properties_free(device->properties); + free(device->name); + + free(device); +} + +static void remove_busy_resource(struct resource_data *d) +{ + struct pw_impl_device *device = d->device; + struct impl *impl = SPA_CONTAINER_OF(device, struct impl, this); + + if (d->end != -1) { + if (d->pi && d->data.cache) { + pw_param_update(&impl->param_list, &impl->pending_list, 0, NULL); + d->pi->user = 1; + d->pi = NULL; + } + spa_hook_remove(&d->listener); + d->end = -1; + pw_impl_client_set_busy(d->resource->client, false); + } +} + +static void resource_destroy(void *data) +{ + struct resource_data *d = data; + remove_busy_resource(d); + spa_hook_remove(&d->resource_listener); + spa_hook_remove(&d->object_listener); +} + +static void resource_pong(void *data, int seq) +{ + struct resource_data *d = data; + struct pw_resource *resource = d->resource; + pw_log_debug("%p: resource %p: got pong %d", d->device, + resource, seq); +} + +static const struct pw_resource_events resource_events = { + PW_VERSION_RESOURCE_EVENTS, + .destroy = resource_destroy, + .pong = resource_pong, +}; + +static void result_device_params(void *data, int seq, int res, uint32_t type, const void *result) +{ + struct result_device_params_data *d = data; + struct impl *impl = d->impl; + pw_log_debug("%p: type %d", impl, type); + + switch (type) { + case SPA_RESULT_TYPE_DEVICE_PARAMS: + { + const struct spa_result_device_params *r = result; + d->callback(d->data, seq, r->id, r->index, r->next, r->param); + if (d->cache) { + pw_log_debug("%p: add param %d", impl, r->id); + if (d->count++ == 0) + pw_param_add(&impl->pending_list, seq, r->id, NULL); + pw_param_add(&impl->pending_list, seq, r->id, r->param); + } + break; + } + default: + break; + } +} + +SPA_EXPORT +int pw_impl_device_for_each_param(struct pw_impl_device *device, + int seq, uint32_t param_id, + uint32_t index, uint32_t max, + const struct spa_pod *filter, + int (*callback) (void *data, int seq, + uint32_t id, uint32_t index, uint32_t next, + struct spa_pod *param), + void *data) +{ + int res; + struct impl *impl = SPA_CONTAINER_OF(device, struct impl, this); + struct result_device_params_data user_data = { impl, data, callback, seq, 0, false }; + struct spa_hook listener; + struct spa_param_info *pi; + static const struct spa_device_events device_events = { + SPA_VERSION_DEVICE_EVENTS, + .result = result_device_params, + }; + + pi = pw_param_info_find(device->info.params, device->info.n_params, param_id); + if (pi == NULL) + return -ENOENT; + + if (max == 0) + max = UINT32_MAX; + + pw_log_debug("%p: params id:%d (%s) index:%u max:%u cached:%d", device, param_id, + spa_debug_type_find_name(spa_type_param, param_id), + index, max, pi->user); + + if (pi->user == 1) { + struct pw_param *p; + uint8_t buffer[4096]; + struct spa_pod_dynamic_builder b; + struct spa_result_device_params result; + uint32_t count = 0; + + result.id = param_id; + result.next = 0; + + spa_list_for_each(p, &impl->param_list, link) { + if (p->id != param_id) + continue; + + result.index = result.next++; + if (result.index < index) + continue; + + spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 4096); + if (spa_pod_filter(&b.b, &result.param, p->param, filter) == 0) { + pw_log_debug("%p: %d param %u", device, seq, result.index); + result_device_params(&user_data, seq, 0, SPA_RESULT_TYPE_DEVICE_PARAMS, &result); + count++; + } + spa_pod_dynamic_builder_clean(&b); + + if (count == max) + break; + } + res = 0; + } else { + user_data.cache = impl->cache_params && + (filter == NULL && index == 0 && max == UINT32_MAX); + + spa_zero(listener); + spa_device_add_listener(device->device, &listener, + &device_events, &user_data); + res = spa_device_enum_params(device->device, seq, + param_id, index, max, filter); + spa_hook_remove(&listener); + + if (!SPA_RESULT_IS_ASYNC(res) && user_data.cache) { + pw_param_update(&impl->param_list, &impl->pending_list, 0, NULL); + pi->user = 1; + } + } + + return res; +} + +static int reply_param(void *data, int seq, uint32_t id, + uint32_t index, uint32_t next, struct spa_pod *param) +{ + struct resource_data *d = data; + pw_device_resource_param(d->resource, seq, id, index, next, param); + return 0; +} + +static void result_device_params_async(void *data, int seq, int res, uint32_t type, const void *result) +{ + struct resource_data *d = data; + + pw_log_debug("%p: async result %d %d (%d/%d)", d->device, + res, seq, d->seq, d->end); + + if (seq == d->seq) + result_device_params(&d->data, d->orig_seq, res, type, result); + if (seq == d->end) + remove_busy_resource(d); +} + +static int device_enum_params(void *object, int seq, uint32_t id, uint32_t start, uint32_t num, + const struct spa_pod *filter) +{ + struct resource_data *data = object; + struct pw_resource *resource = data->resource; + struct pw_impl_device *device = data->device; + struct impl *impl = SPA_CONTAINER_OF(device, struct impl, this); + struct pw_impl_client *client = resource->client; + int res; + static const struct spa_device_events device_events = { + SPA_VERSION_DEVICE_EVENTS, + .result = result_device_params_async, + }; + + res = pw_impl_device_for_each_param(device, seq, id, start, num, + filter, reply_param, data); + + if (res < 0) { + pw_resource_errorf(resource, res, + "enum params id:%d (%s) failed", id, + spa_debug_type_find_name(spa_type_param, id)); + } else if (SPA_RESULT_IS_ASYNC(res)) { + pw_impl_client_set_busy(client, true); + data->data.impl = impl; + data->data.data = data; + data->data.callback = reply_param; + data->data.count = 0; + data->data.cache = impl->cache_params && + (filter == NULL && start == 0); + if (data->end == -1) + spa_device_add_listener(device->device, &data->listener, + &device_events, data); + data->pi = pw_param_info_find(device->info.params, + device->info.n_params, id); + data->orig_seq = seq; + data->seq = res; + data->end = spa_device_sync(device->device, res); + } + + return res; +} + +static int device_subscribe_params(void *object, uint32_t *ids, uint32_t n_ids) +{ + struct resource_data *data = object; + struct pw_resource *resource = data->resource; + uint32_t i; + + n_ids = SPA_MIN(n_ids, SPA_N_ELEMENTS(data->subscribe_ids)); + data->n_subscribe_ids = n_ids; + + for (i = 0; i < n_ids; i++) { + data->subscribe_ids[i] = ids[i]; + pw_log_debug("%p: resource %p subscribe param id:%d (%s)", + data->device, resource, ids[i], + spa_debug_type_find_name(spa_type_param, ids[i])); + device_enum_params(data, 1, ids[i], 0, UINT32_MAX, NULL); + } + return 0; +} + +static void result_device_done(void *data, int seq, int res, uint32_t type, const void *result) +{ + struct resource_data *d = data; + + pw_log_debug("%p: async result %d %d (%d/%d)", d->device, + res, seq, d->seq, d->end); + + if (seq == d->end) + remove_busy_resource(d); +} + +static int device_set_param(void *object, uint32_t id, uint32_t flags, + const struct spa_pod *param) +{ + struct resource_data *data = object; + struct pw_resource *resource = data->resource; + struct pw_impl_device *device = data->device; + struct pw_impl_client *client = resource->client; + int res; + static const struct spa_device_events device_events = { + SPA_VERSION_DEVICE_EVENTS, + .result = result_device_done, + }; + + if ((res = spa_device_set_param(device->device, id, flags, param)) < 0) { + pw_resource_errorf(resource, res, + "set param id:%d (%s) flags:%08x failed", id, + spa_debug_type_find_name(spa_type_param, id), flags); + } else if (SPA_RESULT_IS_ASYNC(res)) { + pw_impl_client_set_busy(client, true); + data->data.data = data; + if (data->end == -1) + spa_device_add_listener(device->device, &data->listener, + &device_events, data); + data->seq = res; + data->end = spa_device_sync(device->device, res); + } + return res; +} + +static const struct pw_device_methods device_methods = { + PW_VERSION_DEVICE_METHODS, + .subscribe_params = device_subscribe_params, + .enum_params = device_enum_params, + .set_param = device_set_param +}; + +static int +global_bind(void *object, struct pw_impl_client *client, uint32_t permissions, + uint32_t version, uint32_t id) +{ + struct pw_impl_device *this = object; + struct pw_global *global = this->global; + struct pw_resource *resource; + struct resource_data *data; + + resource = pw_resource_new(client, id, permissions, global->type, version, sizeof(*data)); + if (resource == NULL) + goto error_resource; + + data = pw_resource_get_user_data(resource); + data->device = this; + data->resource = resource; + data->end = -1; + + pw_resource_add_listener(resource, + &data->resource_listener, + &resource_events, data); + pw_resource_add_object_listener(resource, + &data->object_listener, + &device_methods, data); + + pw_log_debug("%p: bound to %d", this, resource->id); + pw_global_add_resource(global, resource); + + this->info.change_mask = PW_DEVICE_CHANGE_MASK_ALL; + pw_device_resource_info(resource, &this->info); + this->info.change_mask = 0; + + return 0; + +error_resource: + pw_log_error("%p: can't create device resource: %m", this); + return -errno; +} + +static void global_destroy(void *data) +{ + struct pw_impl_device *device = data; + spa_hook_remove(&device->global_listener); + device->global = NULL; + pw_impl_device_destroy(device); +} + +static const struct pw_global_events global_events = { + PW_VERSION_GLOBAL_EVENTS, + .destroy = global_destroy, +}; + +SPA_EXPORT +int pw_impl_device_register(struct pw_impl_device *device, + struct pw_properties *properties) +{ + static const char * const keys[] = { + PW_KEY_OBJECT_SERIAL, + PW_KEY_OBJECT_PATH, + PW_KEY_MODULE_ID, + PW_KEY_FACTORY_ID, + PW_KEY_CLIENT_ID, + PW_KEY_DEVICE_API, + PW_KEY_DEVICE_DESCRIPTION, + PW_KEY_DEVICE_NAME, + PW_KEY_DEVICE_NICK, + PW_KEY_MEDIA_CLASS, + NULL + }; + + struct pw_context *context = device->context; + struct object_data *od; + + if (device->registered) + goto error_existed; + + device->global = pw_global_new(context, + PW_TYPE_INTERFACE_Device, + PW_VERSION_DEVICE, + properties, + global_bind, + device); + if (device->global == NULL) + return -errno; + + spa_list_append(&context->device_list, &device->link); + device->registered = true; + + device->info.id = device->global->id; + pw_properties_setf(device->properties, PW_KEY_OBJECT_ID, "%d", device->info.id); + pw_properties_setf(device->properties, PW_KEY_OBJECT_SERIAL, "%"PRIu64, + pw_global_get_serial(device->global)); + device->info.props = &device->properties->dict; + + pw_global_update_keys(device->global, device->info.props, keys); + + pw_impl_device_emit_initialized(device); + + pw_global_add_listener(device->global, &device->global_listener, &global_events, device); + pw_global_register(device->global); + + spa_list_for_each(od, &device->object_list, link) + object_register(od); + + return 0; + +error_existed: + pw_properties_free(properties); + return -EEXIST; +} + +static void on_object_destroy(void *data) +{ + struct object_data *od = data; + spa_list_remove(&od->link); +} + +static void on_object_free(void *data) +{ + struct object_data *od = data; + pw_unload_spa_handle(od->handle); +} + +static const struct pw_impl_node_events node_object_events = { + PW_VERSION_IMPL_NODE_EVENTS, + .destroy = on_object_destroy, + .free = on_object_free, +}; + +static const struct pw_impl_device_events device_object_events = { + PW_VERSION_IMPL_DEVICE_EVENTS, + .destroy = on_object_destroy, + .free = on_object_free, +}; + +static void emit_info_changed(struct pw_impl_device *device) +{ + struct pw_resource *resource; + + pw_impl_device_emit_info_changed(device, &device->info); + + if (device->global) + spa_list_for_each(resource, &device->global->resource_list, link) + pw_device_resource_info(resource, &device->info); + + device->info.change_mask = 0; +} + +static int update_properties(struct pw_impl_device *device, const struct spa_dict *dict, bool filter) +{ + static const char * const ignored[] = { + PW_KEY_OBJECT_ID, + PW_KEY_MODULE_ID, + PW_KEY_FACTORY_ID, + PW_KEY_CLIENT_ID, + NULL + }; + + int changed; + + changed = pw_properties_update_ignore(device->properties, dict, filter ? ignored : NULL); + device->info.props = &device->properties->dict; + + pw_log_debug("%p: updated %d properties", device, changed); + + if (!changed) + return 0; + + device->info.change_mask |= PW_DEVICE_CHANGE_MASK_PROPS; + + return changed; +} + +static int resource_is_subscribed(struct pw_resource *resource, uint32_t id) +{ + struct resource_data *data = pw_resource_get_user_data(resource); + uint32_t i; + + for (i = 0; i < data->n_subscribe_ids; i++) { + if (data->subscribe_ids[i] == id) + return 1; + } + return 0; +} + +static int notify_param(void *data, int seq, uint32_t id, + uint32_t index, uint32_t next, struct spa_pod *param) +{ + struct pw_impl_device *device = data; + struct pw_resource *resource; + + spa_list_for_each(resource, &device->global->resource_list, link) { + if (!resource_is_subscribed(resource, id)) + continue; + + pw_log_debug("%p: resource %p notify param %d", device, resource, id); + pw_device_resource_param(resource, seq, id, index, next, param); + } + return 0; +} + +static void emit_params(struct pw_impl_device *device, uint32_t *changed_ids, uint32_t n_changed_ids) +{ + uint32_t i; + int res; + + if (device->global == NULL) + return; + + pw_log_debug("%p: emit %d params", device, n_changed_ids); + + for (i = 0; i < n_changed_ids; i++) { + struct pw_resource *resource; + int subscribed = 0; + + /* first check if anyone is subscribed */ + spa_list_for_each(resource, &device->global->resource_list, link) { + if ((subscribed = resource_is_subscribed(resource, changed_ids[i]))) + break; + } + if (!subscribed) + continue; + + if ((res = pw_impl_device_for_each_param(device, 1, changed_ids[i], 0, UINT32_MAX, + NULL, notify_param, device)) < 0) { + pw_log_error("%p: error %d (%s)", device, res, spa_strerror(res)); + } + } +} + +static void device_info(void *data, const struct spa_device_info *info) +{ + struct pw_impl_device *device = data; + uint32_t changed_ids[MAX_PARAMS], n_changed_ids = 0; + + pw_log_debug("%p: flags:%08"PRIx64" change_mask:%08"PRIx64, + device, info->flags, info->change_mask); + + if (info->change_mask & SPA_DEVICE_CHANGE_MASK_PROPS) { + update_properties(device, info->props, true); + } + if (info->change_mask & SPA_DEVICE_CHANGE_MASK_PARAMS) { + uint32_t i; + + device->info.change_mask |= PW_DEVICE_CHANGE_MASK_PARAMS; + device->info.n_params = SPA_MIN(info->n_params, SPA_N_ELEMENTS(device->params)); + + for (i = 0; i < device->info.n_params; i++) { + uint32_t id = info->params[i].id; + + pw_log_debug("%p: param %d id:%d (%s) %08x:%08x", device, i, + id, spa_debug_type_find_name(spa_type_param, id), + device->info.params[i].flags, info->params[i].flags); + + device->info.params[i].id = device->params[i].id; + if (device->info.params[i].flags == info->params[i].flags) + continue; + + pw_log_debug("%p: update param %d", device, id); + device->info.params[i] = info->params[i]; + device->info.params[i].user = 0; + + if (info->params[i].flags & SPA_PARAM_INFO_READ) + changed_ids[n_changed_ids++] = id; + } + } + emit_info_changed(device); + + if (n_changed_ids > 0) + emit_params(device, changed_ids, n_changed_ids); +} + +static void device_add_object(struct pw_impl_device *device, uint32_t id, + const struct spa_device_object_info *info) +{ + struct pw_context *context = device->context; + struct spa_handle *handle; + struct pw_properties *props; + int res; + void *iface; + struct object_data *od = NULL; + + if (info->factory_name == NULL) { + pw_log_debug("%p: missing factory name", device); + return; + } + + handle = pw_context_load_spa_handle(context, info->factory_name, info->props); + if (handle == NULL) { + pw_log_warn("%p: can't load handle %s: %m", + device, info->factory_name); + return; + } + + if ((res = spa_handle_get_interface(handle, info->type, &iface)) < 0) { + pw_log_error("%p: can't get %s interface: %s", device, info->type, + spa_strerror(res)); + return; + } + + props = pw_properties_copy(device->properties); + if (info->props && props) + pw_properties_update(props, info->props); + + if (spa_streq(info->type, SPA_TYPE_INTERFACE_Node)) { + struct pw_impl_node *node; + node = pw_context_create_node(context, props, sizeof(struct object_data)); + + od = pw_impl_node_get_user_data(node); + od->object = node; + od->type = OBJECT_NODE; + pw_impl_node_add_listener(node, &od->listener, &node_object_events, od); + pw_impl_node_set_implementation(node, iface); + } else if (spa_streq(info->type, SPA_TYPE_INTERFACE_Device)) { + struct pw_impl_device *dev; + dev = pw_context_create_device(context, props, sizeof(struct object_data)); + + od = pw_impl_device_get_user_data(dev); + od->object = dev; + od->type = OBJECT_DEVICE; + pw_impl_device_add_listener(dev, &od->listener, &device_object_events, od); + pw_impl_device_set_implementation(dev, iface); + } else { + pw_log_warn("%p: unknown type %s", device, info->type); + pw_properties_free(props); + } + + if (od) { + od->id = id; + od->handle = handle; + spa_list_append(&device->object_list, &od->link); + if (device->global) + object_register(od); + } + return; +} + +static struct object_data *find_object(struct pw_impl_device *device, uint32_t id) +{ + struct object_data *od; + spa_list_for_each(od, &device->object_list, link) { + if (od->id == id) + return od; + } + return NULL; +} + +static void device_object_info(void *data, uint32_t id, + const struct spa_device_object_info *info) +{ + struct pw_impl_device *device = data; + struct object_data *od; + + od = find_object(device, id); + + if (info == NULL) { + pw_log_debug("%p: remove node %d (%p)", device, id, od); + if (od) + object_destroy(od); + } + else if (od != NULL) { + if (info->change_mask & SPA_DEVICE_OBJECT_CHANGE_MASK_PROPS) + object_update(od, info->props); + } + else { + device_add_object(device, id, info); + } +} + +static const struct spa_device_events device_events = { + SPA_VERSION_DEVICE_EVENTS, + .info = device_info, + .object_info = device_object_info, +}; + +SPA_EXPORT +int pw_impl_device_set_implementation(struct pw_impl_device *device, struct spa_device *spa_device) +{ + pw_log_debug("%p: implementation %p", device, spa_device); + + if (device->device) { + pw_log_error("%p: implementation existed %p", + device, device->device); + return -EEXIST; + } + device->device = spa_device; + spa_device_add_listener(device->device, + &device->listener, &device_events, device); + + return 0; +} + +SPA_EXPORT +struct spa_device *pw_impl_device_get_implementation(struct pw_impl_device *device) +{ + return device->device; +} + +SPA_EXPORT +const struct pw_properties *pw_impl_device_get_properties(struct pw_impl_device *device) +{ + return device->properties; +} + +SPA_EXPORT +int pw_impl_device_update_properties(struct pw_impl_device *device, const struct spa_dict *dict) +{ + int changed = update_properties(device, dict, false); + emit_info_changed(device); + return changed; +} + +SPA_EXPORT +void *pw_impl_device_get_user_data(struct pw_impl_device *device) +{ + return device->user_data; +} + +SPA_EXPORT +struct pw_global *pw_impl_device_get_global(struct pw_impl_device *device) +{ + return device->global; +} + +SPA_EXPORT +void pw_impl_device_add_listener(struct pw_impl_device *device, + struct spa_hook *listener, + const struct pw_impl_device_events *events, + void *data) +{ + spa_hook_list_append(&device->listener_list, listener, events, data); +} diff --git a/src/pipewire/impl-device.h b/src/pipewire/impl-device.h new file mode 100644 index 0000000..016d6dc --- /dev/null +++ b/src/pipewire/impl-device.h @@ -0,0 +1,116 @@ +/* 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. + */ + +#ifndef PIPEWIRE_IMPL_DEVICE_H +#define PIPEWIRE_IMPL_DEVICE_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** \defgroup pw_impl_device Device Impl + * + * The device is an object that manages nodes. It typically + * corresponds to a physical hardware device but it does not + * have to be. + * + * The purpose of the device is to provide an interface to + * dynamically create/remove/configure the nodes it manages. + */ + +/** + * \addtogroup pw_impl_device + * \{ + */ +struct pw_impl_device; + +#include <spa/monitor/device.h> + +#include <pipewire/context.h> +#include <pipewire/global.h> +#include <pipewire/properties.h> +#include <pipewire/resource.h> + +/** Device events, listen to them with \ref pw_impl_device_add_listener */ +struct pw_impl_device_events { +#define PW_VERSION_IMPL_DEVICE_EVENTS 0 + uint32_t version; + + /** the device is destroyed */ + void (*destroy) (void *data); + /** the device is freed */ + void (*free) (void *data); + /** the device is initialized */ + void (*initialized) (void *data); + + /** the device info changed */ + void (*info_changed) (void *data, const struct pw_device_info *info); +}; + +struct pw_impl_device *pw_context_create_device(struct pw_context *context, + struct pw_properties *properties, + size_t user_data_size); + +int pw_impl_device_register(struct pw_impl_device *device, + struct pw_properties *properties); + +void pw_impl_device_destroy(struct pw_impl_device *device); + +void *pw_impl_device_get_user_data(struct pw_impl_device *device); + +/** Set the device implementation */ +int pw_impl_device_set_implementation(struct pw_impl_device *device, struct spa_device *spa_device); +/** Get the device implementation */ +struct spa_device *pw_impl_device_get_implementation(struct pw_impl_device *device); + +/** Get the global of this device */ +struct pw_global *pw_impl_device_get_global(struct pw_impl_device *device); + +/** Add an event listener */ +void pw_impl_device_add_listener(struct pw_impl_device *device, + struct spa_hook *listener, + const struct pw_impl_device_events *events, + void *data); + +int pw_impl_device_update_properties(struct pw_impl_device *device, const struct spa_dict *dict); + +const struct pw_properties *pw_impl_device_get_properties(struct pw_impl_device *device); + +int pw_impl_device_for_each_param(struct pw_impl_device *device, + int seq, uint32_t param_id, + uint32_t index, uint32_t max, + const struct spa_pod *filter, + int (*callback) (void *data, int seq, + uint32_t id, uint32_t index, uint32_t next, + struct spa_pod *param), + void *data); +/** + * \} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* PIPEWIRE_IMPL_DEVICE_H */ diff --git a/src/pipewire/impl-factory.c b/src/pipewire/impl-factory.c new file mode 100644 index 0000000..07572c3 --- /dev/null +++ b/src/pipewire/impl-factory.c @@ -0,0 +1,301 @@ +/* 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 <errno.h> + +#include <spa/debug/types.h> +#include <spa/utils/string.h> + +#include "pipewire/impl.h" +#include "pipewire/private.h" + +PW_LOG_TOPIC_EXTERN(log_factory); +#define PW_LOG_TOPIC_DEFAULT log_factory + +#define pw_factory_resource_info(r,...) pw_resource_call(r,struct pw_factory_events,info,0,__VA_ARGS__) + +SPA_EXPORT +struct pw_impl_factory *pw_context_create_factory(struct pw_context *context, + const char *name, + const char *type, + uint32_t version, + struct pw_properties *properties, + size_t user_data_size) +{ + struct pw_impl_factory *this; + int res; + + if (properties == NULL) + properties = pw_properties_new(NULL, NULL); + if (properties == NULL) + return NULL; + + this = calloc(1, sizeof(*this) + user_data_size); + if (this == NULL) { + res = -errno; + goto error_exit; + }; + + this->context = context; + this->properties = properties; + + this->info.name = strdup(name); + this->info.type = type; + this->info.version = version; + this->info.props = &properties->dict; + spa_hook_list_init(&this->listener_list); + + if (user_data_size > 0) + this->user_data = SPA_PTROFF(this, sizeof(*this), void); + + pw_log_debug("%p: new %s", this, name); + + return this; + +error_exit: + pw_properties_free(properties); + errno = -res; + return NULL; +} + +SPA_EXPORT +void pw_impl_factory_destroy(struct pw_impl_factory *factory) +{ + pw_log_debug("%p: destroy", factory); + pw_impl_factory_emit_destroy(factory); + + if (factory->registered) + spa_list_remove(&factory->link); + + if (factory->global) { + spa_hook_remove(&factory->global_listener); + pw_global_destroy(factory->global); + } + + pw_impl_factory_emit_free(factory); + pw_log_debug("%p: free", factory); + + spa_hook_list_clean(&factory->listener_list); + + free((char *)factory->info.name); + + pw_properties_free(factory->properties); + + free(factory); +} + +static int +global_bind(void *object, struct pw_impl_client *client, uint32_t permissions, + uint32_t version, uint32_t id) +{ + struct pw_impl_factory *this = object; + struct pw_global *global = this->global; + struct pw_resource *resource; + + resource = pw_resource_new(client, id, permissions, global->type, version, 0); + if (resource == NULL) + goto error_resource; + + pw_log_debug("%p: bound to %d", this, resource->id); + pw_global_add_resource(global, resource); + + this->info.change_mask = PW_FACTORY_CHANGE_MASK_ALL; + pw_factory_resource_info(resource, &this->info); + this->info.change_mask = 0; + + return 0; + +error_resource: + pw_log_error("%p: can't create factory resource: %m", this); + return -errno; +} + +static void global_destroy(void *data) +{ + struct pw_impl_factory *factory = data; + spa_hook_remove(&factory->global_listener); + factory->global = NULL; + pw_impl_factory_destroy(factory); +} + +static const struct pw_global_events global_events = { + PW_VERSION_GLOBAL_EVENTS, + .destroy = global_destroy, +}; + +SPA_EXPORT +const struct pw_properties *pw_impl_factory_get_properties(struct pw_impl_factory *factory) +{ + return factory->properties; +} + +SPA_EXPORT +int pw_impl_factory_update_properties(struct pw_impl_factory *factory, const struct spa_dict *dict) +{ + struct pw_resource *resource; + int changed; + + changed = pw_properties_update(factory->properties, dict); + factory->info.props = &factory->properties->dict; + + pw_log_debug("%p: updated %d properties", factory, changed); + + if (!changed) + return 0; + + factory->info.change_mask |= PW_FACTORY_CHANGE_MASK_PROPS; + if (factory->global) + spa_list_for_each(resource, &factory->global->resource_list, link) + pw_factory_resource_info(resource, &factory->info); + factory->info.change_mask = 0; + + return changed; +} + +SPA_EXPORT +int pw_impl_factory_register(struct pw_impl_factory *factory, + struct pw_properties *properties) +{ + static const char * const keys[] = { + PW_KEY_OBJECT_SERIAL, + PW_KEY_MODULE_ID, + PW_KEY_FACTORY_NAME, + PW_KEY_FACTORY_TYPE_NAME, + PW_KEY_FACTORY_TYPE_VERSION, + NULL + }; + + struct pw_context *context = factory->context; + + if (factory->registered) + goto error_existed; + + factory->global = pw_global_new(context, + PW_TYPE_INTERFACE_Factory, + PW_VERSION_FACTORY, + properties, + global_bind, + factory); + if (factory->global == NULL) + return -errno; + + spa_list_append(&context->factory_list, &factory->link); + factory->registered = true; + + factory->info.id = factory->global->id; + pw_properties_setf(factory->properties, PW_KEY_OBJECT_ID, "%d", factory->info.id); + pw_properties_setf(factory->properties, PW_KEY_OBJECT_SERIAL, "%"PRIu64, + pw_global_get_serial(factory->global)); + pw_properties_set(factory->properties, PW_KEY_FACTORY_NAME, factory->info.name); + pw_properties_setf(factory->properties, PW_KEY_FACTORY_TYPE_NAME, "%s", factory->info.type); + pw_properties_setf(factory->properties, PW_KEY_FACTORY_TYPE_VERSION, "%d", factory->info.version); + factory->info.props = &factory->properties->dict; + + pw_global_update_keys(factory->global, factory->info.props, keys); + + pw_impl_factory_emit_initialized(factory); + + pw_global_add_listener(factory->global, &factory->global_listener, &global_events, factory); + pw_global_register(factory->global); + + return 0; + +error_existed: + pw_properties_free(properties); + return -EEXIST; +} + +SPA_EXPORT +void *pw_impl_factory_get_user_data(struct pw_impl_factory *factory) +{ + return factory->user_data; +} + +SPA_EXPORT +const struct pw_factory_info *pw_impl_factory_get_info(struct pw_impl_factory *factory) +{ + return &factory->info; +} + +SPA_EXPORT +struct pw_global *pw_impl_factory_get_global(struct pw_impl_factory *factory) +{ + return factory->global; +} + +SPA_EXPORT +void pw_impl_factory_add_listener(struct pw_impl_factory *factory, + struct spa_hook *listener, + const struct pw_impl_factory_events *events, + void *data) +{ + spa_hook_list_append(&factory->listener_list, listener, events, data); +} + +SPA_EXPORT +void pw_impl_factory_set_implementation(struct pw_impl_factory *factory, + const struct pw_impl_factory_implementation *implementation, + void *data) +{ + factory->impl = SPA_CALLBACKS_INIT(implementation, data); +} + +SPA_EXPORT +void *pw_impl_factory_create_object(struct pw_impl_factory *factory, + struct pw_resource *resource, + const char *type, + uint32_t version, + struct pw_properties *properties, + uint32_t new_id) +{ + void *res = NULL; + spa_callbacks_call_res(&factory->impl, + struct pw_impl_factory_implementation, + res, create_object, 0, + resource, type, version, properties, new_id); + return res; +} + +/** Find a factory by name + * + * \param context the context object + * \param name the name of the factory to find + * + * Find in the list of factories registered in \a context for one with + * the given \a name. + * + * \ingroup pw_context + */ +SPA_EXPORT +struct pw_impl_factory *pw_context_find_factory(struct pw_context *context, + const char *name) +{ + struct pw_impl_factory *factory; + + spa_list_for_each(factory, &context->factory_list, link) { + if (spa_streq(factory->info.name, name)) + return factory; + } + return NULL; +} diff --git a/src/pipewire/impl-factory.h b/src/pipewire/impl-factory.h new file mode 100644 index 0000000..f3cc546 --- /dev/null +++ b/src/pipewire/impl-factory.h @@ -0,0 +1,131 @@ +/* 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. + */ + +#ifndef PIPEWIRE_IMPL_FACTORY_H +#define PIPEWIRE_IMPL_FACTORY_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** \defgroup pw_impl_factory Factory Impl + * + * The factory is used to make objects on demand. + */ + +/** + * \addtogroup pw_impl_factory + * \{ + */ +struct pw_impl_factory; + +#include <pipewire/context.h> +#include <pipewire/impl-client.h> +#include <pipewire/global.h> +#include <pipewire/properties.h> +#include <pipewire/resource.h> + +/** Factory events, listen to them with \ref pw_impl_factory_add_listener */ +struct pw_impl_factory_events { +#define PW_VERSION_IMPL_FACTORY_EVENTS 0 + uint32_t version; + + /** the factory is destroyed */ + void (*destroy) (void *data); + /** the factory is freed */ + void (*free) (void *data); + /** the factory is initialized */ + void (*initialized) (void *data); +}; + +struct pw_impl_factory_implementation { +#define PW_VERSION_IMPL_FACTORY_IMPLEMENTATION 0 + uint32_t version; + + /** The function to create an object from this factory */ + void *(*create_object) (void *data, + struct pw_resource *resource, + const char *type, + uint32_t version, + struct pw_properties *properties, + uint32_t new_id); +}; + +struct pw_impl_factory *pw_context_create_factory(struct pw_context *context, + const char *name, + const char *type, + uint32_t version, + struct pw_properties *properties, + size_t user_data_size); + +/** Get the factory properties */ +const struct pw_properties *pw_impl_factory_get_properties(struct pw_impl_factory *factory); + +/** Get the factory info */ +const struct pw_factory_info *pw_impl_factory_get_info(struct pw_impl_factory *factory); + +/** Update the factory properties */ +int pw_impl_factory_update_properties(struct pw_impl_factory *factory, const struct spa_dict *dict); + +int pw_impl_factory_register(struct pw_impl_factory *factory, + struct pw_properties *properties); + +void pw_impl_factory_destroy(struct pw_impl_factory *factory); + +void *pw_impl_factory_get_user_data(struct pw_impl_factory *factory); + +/** Get the global of this factory */ +struct pw_global *pw_impl_factory_get_global(struct pw_impl_factory *factory); + +/** Add an event listener */ +void pw_impl_factory_add_listener(struct pw_impl_factory *factory, + struct spa_hook *listener, + const struct pw_impl_factory_events *events, + void *data); + +void pw_impl_factory_set_implementation(struct pw_impl_factory *factory, + const struct pw_impl_factory_implementation *implementation, + void *data); + +void *pw_impl_factory_create_object(struct pw_impl_factory *factory, + struct pw_resource *resource, + const char *type, + uint32_t version, + struct pw_properties *properties, + uint32_t new_id); + +/** Find a factory by name */ +struct pw_impl_factory * +pw_context_find_factory(struct pw_context *context /**< the context */, + const char *name /**< the factory name */); + +/** + * \} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* PIPEWIRE_IMPL_FACTORY_H */ diff --git a/src/pipewire/impl-link.c b/src/pipewire/impl-link.c new file mode 100644 index 0000000..f773728 --- /dev/null +++ b/src/pipewire/impl-link.c @@ -0,0 +1,1508 @@ +/* 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 <errno.h> +#include <string.h> +#include <stdio.h> +#include <time.h> + +#include <spa/node/utils.h> +#include <spa/pod/parser.h> +#include <spa/pod/compare.h> +#include <spa/param/param.h> +#include <spa/debug/types.h> + +#include "pipewire/impl-link.h" +#include "pipewire/private.h" + +PW_LOG_TOPIC_EXTERN(log_link); +#define PW_LOG_TOPIC_DEFAULT log_link + +#define MAX_HOPS 32 + +#define pw_link_resource_info(r,...) pw_resource_call(r,struct pw_link_events,info,0,__VA_ARGS__) + +/** \cond */ +struct impl { + struct pw_impl_link this; + + unsigned int io_set:1; + unsigned int activated:1; + + struct pw_work_queue *work; + + uint32_t output_busy_id; + uint32_t input_busy_id; + + struct spa_pod *format_filter; + struct pw_properties *properties; + + struct spa_hook input_port_listener; + struct spa_hook input_node_listener; + struct spa_hook input_global_listener; + struct spa_hook output_port_listener; + struct spa_hook output_node_listener; + struct spa_hook output_global_listener; + + struct spa_io_buffers io; + + struct pw_impl_node *inode, *onode; +}; + +/** \endcond */ + +static void info_changed(struct pw_impl_link *link) +{ + struct pw_resource *resource; + + if (link->info.change_mask == 0) + return; + + pw_impl_link_emit_info_changed(link, &link->info); + + if (link->global) + spa_list_for_each(resource, &link->global->resource_list, link) + pw_link_resource_info(resource, &link->info); + + link->info.change_mask = 0; +} + +static void link_update_state(struct pw_impl_link *link, enum pw_link_state state, int res, char *error) +{ + struct impl *impl = SPA_CONTAINER_OF(link, struct impl, this); + enum pw_link_state old = link->info.state; + + link->info.state = state; + free((char*)link->info.error); + link->info.error = error; + + if (state == old) + return; + + pw_log_debug("%p: %s -> %s (%s)", link, + pw_link_state_as_string(old), + pw_link_state_as_string(state), error); + + if (state == PW_LINK_STATE_ERROR) { + pw_log_error("(%s) %s -> error (%s)", link->name, + pw_link_state_as_string(old), error); + } else { + pw_log_info("(%s) %s -> %s", link->name, + pw_link_state_as_string(old), + pw_link_state_as_string(state)); + } + + pw_impl_link_emit_state_changed(link, old, state, error); + + link->info.change_mask |= PW_LINK_CHANGE_MASK_STATE; + if (state == PW_LINK_STATE_ERROR || + state == PW_LINK_STATE_PAUSED || + state == PW_LINK_STATE_ACTIVE) + info_changed(link); + + if (state == PW_LINK_STATE_ERROR && link->global) { + struct pw_resource *resource; + spa_list_for_each(resource, &link->global->resource_list, link) + pw_resource_error(resource, res, error); + } + + if (old < PW_LINK_STATE_PAUSED && state == PW_LINK_STATE_PAUSED) { + link->prepared = true; + link->preparing = false; + pw_context_recalc_graph(link->context, "link prepared"); + } else if (old == PW_LINK_STATE_PAUSED && state < PW_LINK_STATE_PAUSED) { + link->prepared = false; + link->preparing = false; + pw_context_recalc_graph(link->context, "link unprepared"); + } else if (state == PW_LINK_STATE_INIT) { + link->prepared = false; + link->preparing = false; + if (impl->output_busy_id != SPA_ID_INVALID) { + impl->output_busy_id = SPA_ID_INVALID; + link->output->busy_count--; + } + pw_work_queue_cancel(impl->work, &link->output_link, SPA_ID_INVALID); + if (impl->input_busy_id != SPA_ID_INVALID) { + impl->input_busy_id = SPA_ID_INVALID; + link->input->busy_count--; + } + pw_work_queue_cancel(impl->work, &link->input_link, SPA_ID_INVALID); + } +} + +static void complete_ready(void *obj, void *data, int res, uint32_t id) +{ + struct pw_impl_port *port; + struct pw_impl_link *this = data; + struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this); + + if (obj == &this->input_link) + port = this->input; + else + port = this->output; + + if (id == impl->input_busy_id) { + impl->input_busy_id = SPA_ID_INVALID; + port->busy_count--; + } else if (id == impl->output_busy_id) { + impl->output_busy_id = SPA_ID_INVALID; + port->busy_count--; + } else if (id != SPA_ID_INVALID) + return; + + pw_log_debug("%p: obj:%p port %p complete state:%d: %s", this, obj, port, + port->state, spa_strerror(res)); + + if (SPA_RESULT_IS_OK(res)) { + if (port->state < PW_IMPL_PORT_STATE_READY) + pw_impl_port_update_state(port, PW_IMPL_PORT_STATE_READY, + 0, NULL); + } else { + pw_impl_port_update_state(port, PW_IMPL_PORT_STATE_ERROR, + res, spa_aprintf("port error going to READY: %s", spa_strerror(res))); + } + if (this->input->state >= PW_IMPL_PORT_STATE_READY && + this->output->state >= PW_IMPL_PORT_STATE_READY) + link_update_state(this, PW_LINK_STATE_ALLOCATING, 0, NULL); +} + +static void complete_paused(void *obj, void *data, int res, uint32_t id) +{ + struct pw_impl_port *port; + struct pw_impl_link *this = data; + struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this); + struct pw_impl_port_mix *mix; + + if (obj == &this->input_link) { + port = this->input; + mix = &this->rt.in_mix; + } else { + port = this->output; + mix = &this->rt.out_mix; + } + + if (id == impl->input_busy_id) { + impl->input_busy_id = SPA_ID_INVALID; + port->busy_count--; + } else if (id == impl->output_busy_id) { + impl->output_busy_id = SPA_ID_INVALID; + port->busy_count--; + } else if (id != SPA_ID_INVALID) + return; + + pw_log_debug("%p: obj:%p port %p complete state:%d: %s", this, obj, port, + port->state, spa_strerror(res)); + + if (SPA_RESULT_IS_OK(res)) { + if (port->state < PW_IMPL_PORT_STATE_PAUSED) + pw_impl_port_update_state(port, PW_IMPL_PORT_STATE_PAUSED, + 0, NULL); + mix->have_buffers = true; + } else { + pw_impl_port_update_state(port, PW_IMPL_PORT_STATE_ERROR, + res, spa_aprintf("port error going to PAUSED: %s", spa_strerror(res))); + mix->have_buffers = false; + } + if (this->rt.in_mix.have_buffers && this->rt.out_mix.have_buffers) + link_update_state(this, PW_LINK_STATE_PAUSED, 0, NULL); +} + +static void complete_sync(void *obj, void *data, int res, uint32_t id) +{ + struct pw_impl_port *port; + struct pw_impl_link *this = data; + + if (obj == &this->input_link) + port = this->input; + else + port = this->output; + + pw_log_debug("%p: obj:%p port %p complete state:%d: %s", this, obj, port, + port->state, spa_strerror(res)); +} + +static int do_negotiate(struct pw_impl_link *this) +{ + struct pw_context *context = this->context; + struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this); + int res = -EIO, res2; + struct spa_pod *format = NULL, *current; + char *error = NULL; + bool changed = true; + struct pw_impl_port *input, *output; + uint8_t buffer[4096]; + struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); + uint32_t index; + uint32_t in_state, out_state; + + if (this->info.state >= PW_LINK_STATE_NEGOTIATING) + return 0; + + input = this->input; + output = this->output; + + in_state = input->state; + out_state = output->state; + + pw_log_debug("%p: in_state:%d out_state:%d", this, in_state, out_state); + + if (in_state != PW_IMPL_PORT_STATE_CONFIGURE && out_state != PW_IMPL_PORT_STATE_CONFIGURE) + return 0; + + link_update_state(this, PW_LINK_STATE_NEGOTIATING, 0, NULL); + + input = this->input; + output = this->output; + + /* find a common format for the ports */ + if ((res = pw_context_find_format(context, + output, input, NULL, 0, NULL, + &format, &b, &error)) < 0) { + format = NULL; + goto error; + } + + format = spa_pod_copy(format); + spa_pod_fixate(format); + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + + /* if output port had format and is idle, check if it changed. If so, renegotiate */ + if (out_state > PW_IMPL_PORT_STATE_CONFIGURE && output->node->info.state == PW_NODE_STATE_IDLE) { + index = 0; + res = spa_node_port_enum_params_sync(output->node->node, + output->direction, output->port_id, + SPA_PARAM_Format, &index, + NULL, ¤t, &b); + switch (res) { + case -EIO: + current = NULL; + res = 0; + SPA_FALLTHROUGH + case 1: + break; + case 0: + res = -EBADF; + SPA_FALLTHROUGH + default: + error = spa_aprintf("error get output format: %s", spa_strerror(res)); + goto error; + } + if (current == NULL || spa_pod_compare(current, format) != 0) { + pw_log_debug("%p: output format change, renegotiate", this); + if (current) + pw_log_pod(SPA_LOG_LEVEL_DEBUG, current); + pw_log_pod(SPA_LOG_LEVEL_DEBUG, format); + pw_impl_node_set_state(output->node, PW_NODE_STATE_SUSPENDED); + out_state = PW_IMPL_PORT_STATE_CONFIGURE; + } + else { + pw_log_debug("%p: format was already set", this); + changed = false; + } + } + /* if input port had format and is idle, check if it changed. If so, renegotiate */ + if (in_state > PW_IMPL_PORT_STATE_CONFIGURE && input->node->info.state == PW_NODE_STATE_IDLE) { + index = 0; + res = spa_node_port_enum_params_sync(input->node->node, + input->direction, input->port_id, + SPA_PARAM_Format, &index, + NULL, ¤t, &b); + switch (res) { + case -EIO: + current = NULL; + res = 0; + SPA_FALLTHROUGH + case 1: + break; + case 0: + res = -EBADF; + SPA_FALLTHROUGH + default: + error = spa_aprintf("error get input format: %s", spa_strerror(res)); + goto error; + } + if (current == NULL || spa_pod_compare(current, format) != 0) { + pw_log_debug("%p: input format change, renegotiate", this); + if (current) + pw_log_pod(SPA_LOG_LEVEL_DEBUG, current); + pw_log_pod(SPA_LOG_LEVEL_DEBUG, format); + pw_impl_node_set_state(input->node, PW_NODE_STATE_SUSPENDED); + in_state = PW_IMPL_PORT_STATE_CONFIGURE; + } + else { + pw_log_debug("%p: format was already set", this); + changed = false; + } + } + + pw_log_pod(SPA_LOG_LEVEL_DEBUG, format); + + SPA_POD_OBJECT_ID(format) = SPA_PARAM_Format; + pw_log_debug("%p: doing set format %p fixated:%d", this, + format, spa_pod_is_fixated(format)); + + if (out_state == PW_IMPL_PORT_STATE_CONFIGURE) { + pw_log_debug("%p: doing set format on output", this); + if ((res = pw_impl_port_set_param(output, + SPA_PARAM_Format, 0, + format)) < 0) { + error = spa_aprintf("error set output format: %d (%s)", res, spa_strerror(res)); + pw_log_error("tried to set output format:"); + pw_log_pod(SPA_LOG_LEVEL_ERROR, format); + goto error; + } + if (SPA_RESULT_IS_ASYNC(res)) { + output->busy_count++; + res = spa_node_sync(output->node->node, res); + impl->output_busy_id = pw_work_queue_add(impl->work, &this->output_link, res, + complete_ready, this); + } else { + complete_ready(&this->output_link, this, res, SPA_ID_INVALID); + } + } + if (in_state == PW_IMPL_PORT_STATE_CONFIGURE) { + pw_log_debug("%p: doing set format on input", this); + if ((res2 = pw_impl_port_set_param(input, + SPA_PARAM_Format, 0, + format)) < 0) { + error = spa_aprintf("error set input format: %d (%s)", res2, spa_strerror(res2)); + pw_log_error("tried to set input format:"); + pw_log_pod(SPA_LOG_LEVEL_ERROR, format); + goto error; + } + if (SPA_RESULT_IS_ASYNC(res2)) { + input->busy_count++; + res2 = spa_node_sync(input->node->node, res2); + impl->input_busy_id = pw_work_queue_add(impl->work, &this->input_link, res2, + complete_ready, this); + if (res == 0) + res = res2; + } else { + complete_ready(&this->input_link, this, res2, SPA_ID_INVALID); + } + } + + free(this->info.format); + this->info.format = format; + + if (changed) + this->info.change_mask |= PW_LINK_CHANGE_MASK_FORMAT; + + pw_log_debug("%p: result %d", this, res); + return res; + +error: + pw_context_debug_port_params(context, input->node->node, input->direction, + input->port_id, SPA_PARAM_EnumFormat, res, + "input format (%s)", error); + pw_context_debug_port_params(context, output->node->node, output->direction, + output->port_id, SPA_PARAM_EnumFormat, res, + "output format (%s)", error); + link_update_state(this, PW_LINK_STATE_ERROR, res, error); + free(format); + return res; +} + +static int port_set_io(struct pw_impl_link *this, struct pw_impl_port *port, uint32_t id, + void *data, size_t size, struct pw_impl_port_mix *mix) +{ + int res = 0; + + mix->io = data; + pw_log_debug("%p: %s port %p %d.%d set io: %d %p %zd", this, + pw_direction_as_string(port->direction), + port, port->port_id, mix->port.port_id, id, data, size); + + if ((res = spa_node_port_set_io(port->mix, + mix->port.direction, + mix->port.port_id, + id, data, size)) < 0) { + if (res == -ENOTSUP) + res = 0; + else + pw_log_warn("%p: port %p can't set io:%d (%s): %s", + this, port, id, + spa_debug_type_find_name(spa_type_io, id), + spa_strerror(res)); + } + return res; +} + +static void select_io(struct pw_impl_link *this) +{ + struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this); + struct spa_io_buffers *io; + + io = this->rt.in_mix.io; + if (io == NULL) + io = this->rt.out_mix.io; + if (io == NULL) + io = &impl->io; + + this->io = io; + *this->io = SPA_IO_BUFFERS_INIT; +} + +static int do_allocation(struct pw_impl_link *this) +{ + struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this); + int res; + uint32_t in_flags, out_flags; + char *error = NULL; + struct pw_impl_port *input, *output; + + if (this->info.state > PW_LINK_STATE_ALLOCATING) + return 0; + + output = this->output; + input = this->input; + + pw_log_debug("%p: out-state:%d in-state:%d", this, output->state, input->state); + + if (input->state < PW_IMPL_PORT_STATE_READY || output->state < PW_IMPL_PORT_STATE_READY) + return 0; + + link_update_state(this, PW_LINK_STATE_ALLOCATING, 0, NULL); + + out_flags = output->spa_flags; + in_flags = input->spa_flags; + + pw_log_debug("%p: out-node:%p in-node:%p: out-flags:%08x in-flags:%08x", + this, output->node, input->node, out_flags, in_flags); + + this->rt.in_mix.have_buffers = false; + this->rt.out_mix.have_buffers = false; + + if (out_flags & SPA_PORT_FLAG_LIVE) { + pw_log_debug("%p: setting link as live", this); + output->node->live = true; + input->node->live = true; + } + + if (output->buffers.n_buffers) { + pw_log_debug("%p: reusing %d output buffers %p", this, + output->buffers.n_buffers, output->buffers.buffers); + this->rt.out_mix.have_buffers = true; + } else { + uint32_t flags, alloc_flags; + + flags = 0; + /* always shared buffers for the link */ + alloc_flags = PW_BUFFERS_FLAG_SHARED; + if (output->node->remote || input->node->remote) + alloc_flags |= PW_BUFFERS_FLAG_SHARED_MEM; + + if (output->node->driver) + alloc_flags |= PW_BUFFERS_FLAG_IN_PRIORITY; + + /* if output port can alloc buffers, alloc skeleton buffers */ + if (SPA_FLAG_IS_SET(out_flags, SPA_PORT_FLAG_CAN_ALLOC_BUFFERS)) { + SPA_FLAG_SET(alloc_flags, PW_BUFFERS_FLAG_NO_MEM); + flags |= SPA_NODE_BUFFERS_FLAG_ALLOC; + } + + if ((res = pw_buffers_negotiate(this->context, alloc_flags, + output->node->node, output->port_id, + input->node->node, input->port_id, + &output->buffers)) < 0) { + error = spa_aprintf("error alloc buffers: %s", spa_strerror(res)); + goto error; + } + + pw_log_debug("%p: allocating %d buffers %p", this, + output->buffers.n_buffers, output->buffers.buffers); + + if ((res = pw_impl_port_use_buffers(output, &this->rt.out_mix, flags, + output->buffers.buffers, + output->buffers.n_buffers)) < 0) { + error = spa_aprintf("error use output buffers: %d (%s)", res, + spa_strerror(res)); + goto error_clear; + } + if (SPA_RESULT_IS_ASYNC(res)) { + output->busy_count++; + res = spa_node_sync(output->node->node, res); + impl->output_busy_id = pw_work_queue_add(impl->work, &this->output_link, res, + complete_paused, this); + if (flags & SPA_NODE_BUFFERS_FLAG_ALLOC) + return 0; + } else { + complete_paused(&this->output_link, this, res, SPA_ID_INVALID); + } + } + + pw_log_debug("%p: using %d buffers %p on input port", this, + output->buffers.n_buffers, output->buffers.buffers); + + if ((res = pw_impl_port_use_buffers(input, &this->rt.in_mix, 0, + output->buffers.buffers, + output->buffers.n_buffers)) < 0) { + error = spa_aprintf("error use input buffers: %d (%s)", res, + spa_strerror(res)); + goto error; + } + + if (SPA_RESULT_IS_ASYNC(res)) { + input->busy_count++; + res = spa_node_sync(input->node->node, res); + impl->input_busy_id = pw_work_queue_add(impl->work, &this->input_link, res, + complete_paused, this); + } else { + complete_paused(&this->input_link, this, res, SPA_ID_INVALID); + } + return 0; + +error_clear: + pw_buffers_clear(&output->buffers); +error: + link_update_state(this, PW_LINK_STATE_ERROR, res, error); + 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 pw_impl_link *this = user_data; + struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this); + + pw_log_trace("%p: activate", this); + + spa_list_append(&this->output->rt.mix_list, &this->rt.out_mix.rt_link); + spa_list_append(&this->input->rt.mix_list, &this->rt.in_mix.rt_link); + + if (impl->inode != impl->onode) { + struct pw_node_activation_state *state; + + this->rt.target.activation = impl->inode->rt.activation; + spa_list_append(&impl->onode->rt.target_list, &this->rt.target.link); + + state = &this->rt.target.activation->state[0]; + if (!this->rt.target.active && impl->onode->rt.driver_target.node != NULL) { + state->required++; + this->rt.target.active = true; + } + + pw_log_trace("%p: node:%p state:%p pending:%d/%d", this, impl->inode, + state, state->pending, state->required); + } + return 0; +} + +int pw_impl_link_activate(struct pw_impl_link *this) +{ + struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this); + int res; + + pw_log_debug("%p: activate activated:%d state:%s", this, impl->activated, + pw_link_state_as_string(this->info.state)); + + if (impl->activated || !this->prepared || + !impl->inode->active || !impl->onode->active) + return 0; + + if (!impl->io_set) { + if ((res = port_set_io(this, this->output, SPA_IO_Buffers, this->io, + sizeof(struct spa_io_buffers), &this->rt.out_mix)) < 0) + return res; + + if ((res = port_set_io(this, this->input, SPA_IO_Buffers, this->io, + sizeof(struct spa_io_buffers), &this->rt.in_mix)) < 0) + return res; + impl->io_set = true; + } + pw_loop_invoke(this->output->node->data_loop, + do_activate_link, SPA_ID_INVALID, NULL, 0, false, this); + + impl->activated = true; + pw_log_info("(%s) activated", this->name); + link_update_state(this, PW_LINK_STATE_ACTIVE, 0, NULL); + + return 0; +} +static void check_states(void *obj, void *user_data, int res, uint32_t id) +{ + struct pw_impl_link *this = obj; + struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this); + int in_state, out_state; + struct pw_impl_port *input, *output; + + if (this->info.state == PW_LINK_STATE_ERROR) + return; + + if (this->info.state >= PW_LINK_STATE_PAUSED) + return; + + output = this->output; + input = this->input; + + if (output == NULL || input == NULL) { + link_update_state(this, PW_LINK_STATE_ERROR, -EIO, + strdup("link without input or output port")); + return; + } + + if (output->node->info.state == PW_NODE_STATE_ERROR || + input->node->info.state == PW_NODE_STATE_ERROR) { + pw_log_warn("%p: one of the nodes is in error out:%s in:%s", this, + pw_node_state_as_string(output->node->info.state), + pw_node_state_as_string(input->node->info.state)); + return; + } + + out_state = output->state; + in_state = input->state; + + pw_log_debug("%p: output state %d, input state %d", this, out_state, in_state); + + if (out_state == PW_IMPL_PORT_STATE_ERROR || in_state == PW_IMPL_PORT_STATE_ERROR) { + link_update_state(this, PW_LINK_STATE_ERROR, -EIO, strdup("ports are in error")); + return; + } + + if (PW_IMPL_PORT_IS_CONTROL(output) && PW_IMPL_PORT_IS_CONTROL(input)) { + pw_impl_port_update_state(output, PW_IMPL_PORT_STATE_PAUSED, 0, NULL); + pw_impl_port_update_state(input, PW_IMPL_PORT_STATE_PAUSED, 0, NULL); + link_update_state(this, PW_LINK_STATE_PAUSED, 0, NULL); + } + + if (output->busy_count > 0) { + pw_log_debug("%p: output port %p was busy", this, output); + res = spa_node_sync(output->node->node, 0); + pw_work_queue_add(impl->work, &this->output_link, res, complete_sync, this); + goto exit; + } + else if (input->busy_count > 0) { + pw_log_debug("%p: input port %p was busy", this, input); + res = spa_node_sync(input->node->node, 0); + pw_work_queue_add(impl->work, &this->input_link, res, complete_sync, this); + goto exit; + } + + if ((res = do_negotiate(this)) != 0) + goto exit; + + if ((res = do_allocation(this)) != 0) + goto exit; + +exit: + if (SPA_RESULT_IS_ERROR(res)) { + pw_log_debug("%p: got error result %d (%s)", this, res, spa_strerror(res)); + return; + } + + pw_work_queue_add(impl->work, + this, -EBUSY, (pw_work_func_t) check_states, this); +} + +static void input_remove(struct pw_impl_link *this, struct pw_impl_port *port) +{ + struct impl *impl = (struct impl *) this; + struct pw_impl_port_mix *mix = &this->rt.in_mix; + int res; + + pw_log_debug("%p: remove input port %p", this, port); + + if (impl->input_busy_id != SPA_ID_INVALID) { + impl->input_busy_id = SPA_ID_INVALID; + port->busy_count--; + } + spa_hook_remove(&impl->input_port_listener); + spa_hook_remove(&impl->input_node_listener); + spa_hook_remove(&impl->input_global_listener); + + spa_list_remove(&this->input_link); + pw_impl_port_emit_link_removed(this->input, this); + + pw_impl_port_recalc_latency(this->input); + + if ((res = pw_impl_port_use_buffers(port, mix, 0, NULL, 0)) < 0) { + pw_log_warn("%p: port %p clear error %s", this, port, spa_strerror(res)); + } + pw_impl_port_release_mix(port, mix); + + pw_work_queue_cancel(impl->work, &this->input_link, SPA_ID_INVALID); + this->input = NULL; +} + +static void output_remove(struct pw_impl_link *this, struct pw_impl_port *port) +{ + struct impl *impl = (struct impl *) this; + struct pw_impl_port_mix *mix = &this->rt.out_mix; + + pw_log_debug("%p: remove output port %p", this, port); + + if (impl->output_busy_id != SPA_ID_INVALID) { + impl->output_busy_id = SPA_ID_INVALID; + port->busy_count--; + } + spa_hook_remove(&impl->output_port_listener); + spa_hook_remove(&impl->output_node_listener); + spa_hook_remove(&impl->output_global_listener); + + spa_list_remove(&this->output_link); + pw_impl_port_emit_link_removed(this->output, this); + + pw_impl_port_recalc_latency(this->output); + + /* we don't clear output buffers when the link goes away. They will get + * cleared when the node goes to suspend */ + pw_impl_port_release_mix(port, mix); + + pw_work_queue_cancel(impl->work, &this->output_link, SPA_ID_INVALID); + this->output = NULL; +} + +int pw_impl_link_prepare(struct pw_impl_link *this) +{ + struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this); + + pw_log_debug("%p: prepared:%d preparing:%d in_active:%d out_active:%d", + this, this->prepared, this->preparing, + impl->inode->active, impl->onode->active); + + if (!impl->inode->active || !impl->onode->active) + return 0; + + if (this->preparing || this->prepared) + return 0; + + this->preparing = true; + + pw_work_queue_add(impl->work, + this, -EBUSY, (pw_work_func_t) check_states, this); + + return 0; +} + +static int +do_deactivate_link(struct spa_loop *loop, + bool async, uint32_t seq, const void *data, size_t size, void *user_data) +{ + struct pw_impl_link *this = user_data; + struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this); + + pw_log_trace("%p: disable %p and %p", this, &this->rt.in_mix, &this->rt.out_mix); + + spa_list_remove(&this->rt.out_mix.rt_link); + spa_list_remove(&this->rt.in_mix.rt_link); + + if (impl->inode != impl->onode) { + struct pw_node_activation_state *state; + + spa_list_remove(&this->rt.target.link); + state = &this->rt.target.activation->state[0]; + if (this->rt.target.active) { + state->required--; + this->rt.target.active = false; + } + + pw_log_trace("%p: node:%p state:%p pending:%d/%d", this, impl->inode, + state, state->pending, state->required); + } + + return 0; +} + +int pw_impl_link_deactivate(struct pw_impl_link *this) +{ + struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this); + + pw_log_debug("%p: deactivate activated:%d", this, impl->activated); + + if (!impl->activated) + return 0; + + pw_loop_invoke(this->output->node->data_loop, + do_deactivate_link, SPA_ID_INVALID, NULL, 0, true, this); + + port_set_io(this, this->output, SPA_IO_Buffers, NULL, 0, + &this->rt.out_mix); + port_set_io(this, this->input, SPA_IO_Buffers, NULL, 0, + &this->rt.in_mix); + + impl->io_set = false; + impl->activated = false; + pw_log_info("(%s) deactivated", this->name); + link_update_state(this, PW_LINK_STATE_PAUSED, 0, NULL); + + return 0; +} + +static int +global_bind(void *object, struct pw_impl_client *client, uint32_t permissions, + uint32_t version, uint32_t id) +{ + struct pw_impl_link *this = object; + struct pw_global *global = this->global; + struct pw_resource *resource; + + resource = pw_resource_new(client, id, permissions, global->type, version, 0); + if (resource == NULL) + goto error_resource; + + pw_log_debug("%p: bound to %d", this, resource->id); + pw_global_add_resource(global, resource); + + this->info.change_mask = PW_LINK_CHANGE_MASK_ALL; + pw_link_resource_info(resource, &this->info); + this->info.change_mask = 0; + + return 0; + +error_resource: + pw_log_error("%p: can't create link resource: %m", this); + return -errno; +} + +static void port_state_changed(struct pw_impl_link *this, struct pw_impl_port *port, + struct pw_impl_port *other, enum pw_impl_port_state old, + enum pw_impl_port_state state, const char *error) +{ + pw_log_debug("%p: port %p old:%d -> state:%d prepared:%d preparing:%d", + this, port, old, state, this->prepared, this->preparing); + + switch (state) { + case PW_IMPL_PORT_STATE_ERROR: + link_update_state(this, PW_LINK_STATE_ERROR, -EIO, error ? strdup(error) : NULL); + break; + case PW_IMPL_PORT_STATE_INIT: + case PW_IMPL_PORT_STATE_CONFIGURE: + if (this->prepared || state < old) { + this->prepared = false; + link_update_state(this, PW_LINK_STATE_INIT, 0, NULL); + } + break; + case PW_IMPL_PORT_STATE_READY: + if (this->prepared || state < old) { + this->prepared = false; + link_update_state(this, PW_LINK_STATE_NEGOTIATING, 0, NULL); + } + break; + case PW_IMPL_PORT_STATE_PAUSED: + break; + } +} + +static void port_param_changed(struct pw_impl_link *this, uint32_t id, + struct pw_impl_port *outport, struct pw_impl_port *inport) +{ + enum pw_impl_port_state target; + + pw_log_debug("%p: outport %p input %p param %d (%s)", this, + outport, inport, id, spa_debug_type_find_name(spa_type_param, id)); + + switch (id) { + case SPA_PARAM_EnumFormat: + target = PW_IMPL_PORT_STATE_CONFIGURE; + break; + default: + return; + } + if (outport) + pw_impl_port_update_state(outport, target, 0, NULL); + if (inport) + pw_impl_port_update_state(inport, target, 0, NULL); + + this->preparing = this->prepared = false; + link_update_state(this, PW_LINK_STATE_INIT, 0, NULL); + pw_impl_link_prepare(this); +} + +static void input_port_param_changed(void *data, uint32_t id) +{ + struct impl *impl = data; + struct pw_impl_link *this = &impl->this; + port_param_changed(this, id, this->output, this->input); +} + +static void input_port_state_changed(void *data, enum pw_impl_port_state old, + enum pw_impl_port_state state, const char *error) +{ + struct impl *impl = data; + struct pw_impl_link *this = &impl->this; + port_state_changed(this, this->input, this->output, old, state, error); +} + +static void output_port_param_changed(void *data, uint32_t id) +{ + struct impl *impl = data; + struct pw_impl_link *this = &impl->this; + port_param_changed(this, id, this->output, this->input); +} + +static void output_port_state_changed(void *data, enum pw_impl_port_state old, + enum pw_impl_port_state state, const char *error) +{ + struct impl *impl = data; + struct pw_impl_link *this = &impl->this; + port_state_changed(this, this->output, this->input, old, state, error); +} + +static void input_port_latency_changed(void *data) +{ + struct impl *impl = data; + struct pw_impl_link *this = &impl->this; + if (!this->feedback) + pw_impl_port_recalc_latency(this->output); +} + +static void output_port_latency_changed(void *data) +{ + struct impl *impl = data; + struct pw_impl_link *this = &impl->this; + if (!this->feedback) + pw_impl_port_recalc_latency(this->input); +} + +static const struct pw_impl_port_events input_port_events = { + PW_VERSION_IMPL_PORT_EVENTS, + .param_changed = input_port_param_changed, + .state_changed = input_port_state_changed, + .latency_changed = input_port_latency_changed, +}; + +static const struct pw_impl_port_events output_port_events = { + PW_VERSION_IMPL_PORT_EVENTS, + .param_changed = output_port_param_changed, + .state_changed = output_port_state_changed, + .latency_changed = output_port_latency_changed, +}; + +static void node_result(struct impl *impl, void *obj, + int seq, int res, uint32_t type, const void *result) +{ + if (SPA_RESULT_IS_ASYNC(seq)) + pw_work_queue_complete(impl->work, obj, SPA_RESULT_ASYNC_SEQ(seq), res); +} + +static void input_node_result(void *data, int seq, int res, uint32_t type, const void *result) +{ + struct impl *impl = data; + struct pw_impl_port *port = impl->this.input; + pw_log_trace("%p: input port %p result seq:%d res:%d type:%u", + impl, port, seq, res, type); + node_result(impl, &impl->this.input_link, seq, res, type, result); +} + +static void output_node_result(void *data, int seq, int res, uint32_t type, const void *result) +{ + struct impl *impl = data; + struct pw_impl_port *port = impl->this.output; + pw_log_trace("%p: output port %p result seq:%d res:%d type:%u", + impl, port, seq, res, type); + node_result(impl, &impl->this.output_link, seq, res, type, result); +} + +static void node_active_changed(void *data, bool active) +{ + struct impl *impl = data; + pw_impl_link_prepare(&impl->this); +} + +static const struct pw_impl_node_events input_node_events = { + PW_VERSION_IMPL_NODE_EVENTS, + .result = input_node_result, + .active_changed = node_active_changed, +}; + +static const struct pw_impl_node_events output_node_events = { + PW_VERSION_IMPL_NODE_EVENTS, + .result = output_node_result, + .active_changed = node_active_changed, +}; + +static bool pw_impl_node_can_reach(struct pw_impl_node *output, struct pw_impl_node *input, int hop) +{ + struct pw_impl_port *p; + struct pw_impl_link *l; + + output->loopchecked = true; + + if (output == input) + return true; + + if (hop == MAX_HOPS) { + pw_log_warn("exceeded hops (%d) %s -> %s", hop, output->name, input->name); + return false; + } + + spa_list_for_each(p, &output->output_ports, link) { + spa_list_for_each(l, &p->links, output_link) + l->input->node->loopchecked = l->feedback; + } + spa_list_for_each(p, &output->output_ports, link) { + spa_list_for_each(l, &p->links, output_link) { + if (l->input->node->loopchecked) + continue; + if (pw_impl_node_can_reach(l->input->node, input, hop+1)) + return true; + } + } + return false; +} + +static void try_link_controls(struct impl *impl, struct pw_impl_port *output, struct pw_impl_port *input) +{ + struct pw_control *cin, *cout; + struct pw_impl_link *this = &impl->this; + uint32_t omix, imix; + int res; + + imix = this->rt.in_mix.port.port_id; + omix = this->rt.out_mix.port.port_id; + + pw_log_debug("%p: trying controls", impl); + spa_list_for_each(cout, &output->control_list[SPA_DIRECTION_OUTPUT], port_link) { + spa_list_for_each(cin, &input->control_list[SPA_DIRECTION_INPUT], port_link) { + if ((res = pw_control_add_link(cout, omix, cin, imix, &this->control)) < 0) + pw_log_error("%p: failed to link controls: %s", + this, spa_strerror(res)); + break; + } + } + spa_list_for_each(cin, &output->control_list[SPA_DIRECTION_INPUT], port_link) { + spa_list_for_each(cout, &input->control_list[SPA_DIRECTION_OUTPUT], port_link) { + if ((res = pw_control_add_link(cout, imix, cin, omix, &this->notify)) < 0) + pw_log_error("%p: failed to link controls: %s", + this, spa_strerror(res)); + break; + } + } +} + +static void try_unlink_controls(struct impl *impl, struct pw_impl_port *output, struct pw_impl_port *input) +{ + struct pw_impl_link *this = &impl->this; + int res; + + pw_log_debug("%p: unlinking controls", impl); + if (this->control.valid) { + if ((res = pw_control_remove_link(&this->control)) < 0) + pw_log_error("%p: failed to unlink controls: %s", + this, spa_strerror(res)); + } + if (this->notify.valid) { + if ((res = pw_control_remove_link(&this->notify)) < 0) + pw_log_error("%p: failed to unlink controls: %s", + this, spa_strerror(res)); + } +} + +static int +check_permission(struct pw_context *context, + struct pw_impl_port *output, + struct pw_impl_port *input, + struct pw_properties *properties) +{ + return 0; +} + +static void permissions_changed(struct pw_impl_link *this, struct pw_impl_port *other, + struct pw_impl_client *client, uint32_t old, uint32_t new) +{ + uint32_t perm; + + perm = pw_global_get_permissions(other->global, client); + old &= perm; + new &= perm; + pw_log_debug("%p: permissions changed %08x -> %08x", this, old, new); + + if (check_permission(this->context, this->output, this->input, this->properties) < 0) { + pw_impl_link_destroy(this); + } else if (this->global != NULL) { + pw_global_update_permissions(this->global, client, old, new); + } +} + +static void output_permissions_changed(void *data, + struct pw_impl_client *client, uint32_t old, uint32_t new) +{ + struct pw_impl_link *this = data; + permissions_changed(this, this->input, client, old, new); +} + +static const struct pw_global_events output_global_events = { + PW_VERSION_GLOBAL_EVENTS, + .permissions_changed = output_permissions_changed, +}; + +static void input_permissions_changed(void *data, + struct pw_impl_client *client, uint32_t old, uint32_t new) +{ + struct pw_impl_link *this = data; + permissions_changed(this, this->output, client, old, new); +} + +static const struct pw_global_events input_global_events = { + PW_VERSION_GLOBAL_EVENTS, + .permissions_changed = input_permissions_changed, +}; + +SPA_EXPORT +struct pw_impl_link *pw_context_create_link(struct pw_context *context, + struct pw_impl_port *output, + struct pw_impl_port *input, + struct spa_pod *format_filter, + struct pw_properties *properties, + size_t user_data_size) +{ + struct impl *impl; + struct pw_impl_link *this; + struct pw_impl_node *input_node, *output_node; + int res; + + if (output == input) + goto error_same_ports; + + if (output->direction != PW_DIRECTION_OUTPUT || + input->direction != PW_DIRECTION_INPUT) + goto error_wrong_direction; + + if (pw_impl_link_find(output, input)) + goto error_link_exists; + + if (check_permission(context, output, input, properties) < 0) + goto error_link_not_allowed; + + output_node = output->node; + input_node = input->node; + + if (properties == NULL) + properties = pw_properties_new(NULL, NULL); + if (properties == NULL) + goto error_no_mem; + + impl = calloc(1, sizeof(struct impl) + user_data_size); + if (impl == NULL) + goto error_no_mem; + + impl->input_busy_id = SPA_ID_INVALID; + impl->output_busy_id = SPA_ID_INVALID; + + this = &impl->this; + this->feedback = pw_impl_node_can_reach(input_node, output_node, 0); + pw_properties_set(properties, PW_KEY_LINK_FEEDBACK, this->feedback ? "true" : NULL); + + pw_log_debug("%p: new out-port:%p -> in-port:%p", this, output, input); + + if (user_data_size > 0) + this->user_data = SPA_PTROFF(impl, sizeof(struct impl), void); + + impl->work = pw_context_get_work_queue(context); + + this->context = context; + this->properties = properties; + this->info.state = PW_LINK_STATE_INIT; + + this->output = output; + this->input = input; + + /* passive means that this link does not make the nodes active */ + this->passive = pw_properties_get_bool(properties, PW_KEY_LINK_PASSIVE, false); + + spa_hook_list_init(&this->listener_list); + + impl->format_filter = format_filter; + this->info.format = NULL; + this->info.props = &this->properties->dict; + + this->rt.out_mix.peer_id = input->global->id; + this->rt.in_mix.peer_id = output->global->id; + + if ((res = pw_impl_port_init_mix(output, &this->rt.out_mix)) < 0) + goto error_output_mix; + if ((res = pw_impl_port_init_mix(input, &this->rt.in_mix)) < 0) + goto error_input_mix; + + pw_impl_port_add_listener(input, &impl->input_port_listener, &input_port_events, impl); + pw_impl_node_add_listener(input_node, &impl->input_node_listener, &input_node_events, impl); + pw_global_add_listener(input->global, &impl->input_global_listener, &input_global_events, impl); + pw_impl_port_add_listener(output, &impl->output_port_listener, &output_port_events, impl); + pw_impl_node_add_listener(output_node, &impl->output_node_listener, &output_node_events, impl); + pw_global_add_listener(output->global, &impl->output_global_listener, &output_global_events, impl); + + input_node->live = output_node->live; + + pw_log_debug("%p: output node %p live %d, feedback %d", + this, output_node, output_node->live, this->feedback); + + spa_list_append(&output->links, &this->output_link); + spa_list_append(&input->links, &this->input_link); + + impl->io = SPA_IO_BUFFERS_INIT; + + select_io(this); + + if (this->feedback) { + impl->inode = output_node; + impl->onode = input_node; + } + else { + impl->onode = output_node; + impl->inode = input_node; + } + + this->rt.target.signal_func = impl->inode->rt.target.signal_func; + this->rt.target.data = impl->inode->rt.target.data; + + pw_log_debug("%p: constructed out:%p:%d.%d -> in:%p:%d.%d", impl, + output_node, output->port_id, this->rt.out_mix.port.port_id, + input_node, input->port_id, this->rt.in_mix.port.port_id); + + if (asprintf(&this->name, "%d.%d -> %d.%d", + output_node->info.id, output->port_id, + input_node->info.id, input->port_id) < 0) + this->name = NULL; + pw_log_info("(%s) (%s) -> (%s)", this->name, output_node->name, input_node->name); + + pw_impl_port_emit_link_added(output, this); + pw_impl_port_emit_link_added(input, this); + + try_link_controls(impl, output, input); + + pw_impl_port_recalc_latency(output); + pw_impl_port_recalc_latency(input); + + pw_impl_node_emit_peer_added(impl->onode, impl->inode); + + return this; + +error_same_ports: + res = -EINVAL; + pw_log_debug("can't link the same ports"); + goto error_exit; +error_wrong_direction: + res = -EINVAL; + pw_log_debug("ports have wrong direction"); + goto error_exit; +error_link_exists: + res = -EEXIST; + pw_log_debug("link already exists"); + goto error_exit; +error_link_not_allowed: + res = -EPERM; + pw_log_debug("link not allowed"); + goto error_exit; +error_no_mem: + res = -errno; + pw_log_debug("alloc failed: %m"); + goto error_exit; +error_output_mix: + pw_log_error("%p: can't get output mix %d (%s)", this, res, spa_strerror(res)); + goto error_free; +error_input_mix: + pw_log_error("%p: can't get input mix %d (%s)", this, res, spa_strerror(res)); + pw_impl_port_release_mix(output, &this->rt.out_mix); + goto error_free; +error_free: + free(impl); +error_exit: + pw_properties_free(properties); + errno = -res; + return NULL; +} + +static void global_destroy(void *data) +{ + struct pw_impl_link *link = data; + spa_hook_remove(&link->global_listener); + link->global = NULL; + pw_impl_link_destroy(link); +} + +static const struct pw_global_events global_events = { + PW_VERSION_GLOBAL_EVENTS, + .destroy = global_destroy, +}; + +SPA_EXPORT +int pw_impl_link_register(struct pw_impl_link *link, + struct pw_properties *properties) +{ + static const char * const keys[] = { + PW_KEY_OBJECT_SERIAL, + PW_KEY_OBJECT_PATH, + PW_KEY_MODULE_ID, + PW_KEY_FACTORY_ID, + PW_KEY_CLIENT_ID, + PW_KEY_LINK_OUTPUT_PORT, + PW_KEY_LINK_INPUT_PORT, + PW_KEY_LINK_OUTPUT_NODE, + PW_KEY_LINK_INPUT_NODE, + NULL + }; + + struct pw_context *context = link->context; + struct pw_impl_node *output_node, *input_node; + + if (link->registered) + goto error_existed; + + output_node = link->output->node; + input_node = link->input->node; + + link->info.output_node_id = output_node->global->id; + link->info.output_port_id = link->output->global->id; + link->info.input_node_id = input_node->global->id; + link->info.input_port_id = link->input->global->id; + + link->global = pw_global_new(context, + PW_TYPE_INTERFACE_Link, + PW_VERSION_LINK, + properties, + global_bind, + link); + if (link->global == NULL) + return -errno; + + spa_list_append(&context->link_list, &link->link); + link->registered = true; + + link->info.id = link->global->id; + pw_properties_setf(link->properties, PW_KEY_OBJECT_ID, "%d", link->info.id); + pw_properties_setf(link->properties, PW_KEY_OBJECT_SERIAL, "%"PRIu64, + pw_global_get_serial(link->global)); + pw_properties_setf(link->properties, PW_KEY_LINK_OUTPUT_NODE, "%u", link->info.output_node_id); + pw_properties_setf(link->properties, PW_KEY_LINK_OUTPUT_PORT, "%u", link->info.output_port_id); + pw_properties_setf(link->properties, PW_KEY_LINK_INPUT_NODE, "%u", link->info.input_node_id); + pw_properties_setf(link->properties, PW_KEY_LINK_INPUT_PORT, "%u", link->info.input_port_id); + link->info.props = &link->properties->dict; + + pw_global_update_keys(link->global, link->info.props, keys); + + pw_impl_link_emit_initialized(link); + + pw_global_add_listener(link->global, &link->global_listener, &global_events, link); + pw_global_register(link->global); + + pw_impl_link_prepare(link); + + return 0; + +error_existed: + pw_properties_free(properties); + return -EEXIST; +} + +SPA_EXPORT +void pw_impl_link_destroy(struct pw_impl_link *link) +{ + struct impl *impl = SPA_CONTAINER_OF(link, struct impl, this); + + pw_log_debug("%p: destroy", impl); + pw_log_info("(%s) destroy", link->name); + pw_impl_link_emit_destroy(link); + + pw_impl_link_deactivate(link); + + if (link->registered) + spa_list_remove(&link->link); + + pw_impl_node_emit_peer_removed(impl->onode, impl->inode); + + try_unlink_controls(impl, link->output, link->input); + + output_remove(link, link->output); + input_remove(link, link->input); + + if (link->global) { + spa_hook_remove(&link->global_listener); + pw_global_destroy(link->global); + } + + if (link->prepared) + pw_context_recalc_graph(link->context, "link destroy"); + + pw_log_debug("%p: free", impl); + pw_impl_link_emit_free(link); + + pw_work_queue_cancel(impl->work, link, SPA_ID_INVALID); + + spa_hook_list_clean(&link->listener_list); + + pw_properties_free(link->properties); + + free(link->name); + free(link->info.format); + free(impl); +} + +SPA_EXPORT +void pw_impl_link_add_listener(struct pw_impl_link *link, + struct spa_hook *listener, + const struct pw_impl_link_events *events, + void *data) +{ + pw_log_debug("%p: add listener %p", link, listener); + spa_hook_list_append(&link->listener_list, listener, events, data); +} + +struct pw_impl_link *pw_impl_link_find(struct pw_impl_port *output_port, struct pw_impl_port *input_port) +{ + struct pw_impl_link *pl; + + spa_list_for_each(pl, &output_port->links, output_link) { + if (pl->input == input_port) + return pl; + } + return NULL; +} + +SPA_EXPORT +struct pw_context *pw_impl_link_get_context(struct pw_impl_link *link) +{ + return link->context; +} + +SPA_EXPORT +void *pw_impl_link_get_user_data(struct pw_impl_link *link) +{ + return link->user_data; +} + +SPA_EXPORT +const struct pw_link_info *pw_impl_link_get_info(struct pw_impl_link *link) +{ + return &link->info; +} + +SPA_EXPORT +struct pw_global *pw_impl_link_get_global(struct pw_impl_link *link) +{ + return link->global; +} + +SPA_EXPORT +struct pw_impl_port *pw_impl_link_get_output(struct pw_impl_link *link) +{ + return link->output; +} + +SPA_EXPORT +struct pw_impl_port *pw_impl_link_get_input(struct pw_impl_link *link) +{ + return link->input; +} diff --git a/src/pipewire/impl-link.h b/src/pipewire/impl-link.h new file mode 100644 index 0000000..5a3b73f --- /dev/null +++ b/src/pipewire/impl-link.h @@ -0,0 +1,126 @@ +/* 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. + */ + +#ifndef PIPEWIRE_IMPL_LINK_H +#define PIPEWIRE_IMPL_LINK_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** \defgroup pw_impl_link Link Impl + * + * \brief PipeWire link object. + */ + +/** + * \addtogroup pw_impl_link + * \{ + */ +struct pw_impl_link; +struct pw_impl_port; + +#include <pipewire/impl.h> + +/** link events added with \ref pw_impl_link_add_listener */ +struct pw_impl_link_events { +#define PW_VERSION_IMPL_LINK_EVENTS 0 + uint32_t version; + + /** A link is destroyed */ + void (*destroy) (void *data); + + /** A link is freed */ + void (*free) (void *data); + + /** a Link is initialized */ + void (*initialized) (void *data); + + /** The info changed on a link */ + void (*info_changed) (void *data, const struct pw_link_info *info); + + /** The link state changed, \a error is only valid when the state is + * in error. */ + void (*state_changed) (void *data, enum pw_link_state old, + enum pw_link_state state, const char *error); + + /** A port is unlinked */ + void (*port_unlinked) (void *data, struct pw_impl_port *port); +}; + + +/** Make a new link between two ports + * \return a newly allocated link */ +struct pw_impl_link * +pw_context_create_link(struct pw_context *context, /**< the context object */ + struct pw_impl_port *output, /**< an output port */ + struct pw_impl_port *input, /**< an input port */ + struct spa_pod *format_filter, /**< an optional format filter */ + struct pw_properties *properties /**< extra properties */, + size_t user_data_size /**< extra user data size */); + +/** Destroy a link */ +void pw_impl_link_destroy(struct pw_impl_link *link); + +/** Add an event listener to \a link */ +void pw_impl_link_add_listener(struct pw_impl_link *link, + struct spa_hook *listener, + const struct pw_impl_link_events *events, + void *data); + +/** Finish link configuration and register */ +int pw_impl_link_register(struct pw_impl_link *link, /**< the link to register */ + struct pw_properties *properties /**< extra properties */); + +/** Get the context of a link */ +struct pw_context *pw_impl_link_get_context(struct pw_impl_link *link); + +/** Get the user_data of a link, the size of the memory is given when + * constructing the link */ +void *pw_impl_link_get_user_data(struct pw_impl_link *link); + +/** Get the link info */ +const struct pw_link_info *pw_impl_link_get_info(struct pw_impl_link *link); + +/** Get the global of the link */ +struct pw_global *pw_impl_link_get_global(struct pw_impl_link *link); + +/** Get the output port of the link */ +struct pw_impl_port *pw_impl_link_get_output(struct pw_impl_link *link); + +/** Get the input port of the link */ +struct pw_impl_port *pw_impl_link_get_input(struct pw_impl_link *link); + +/** Find the link between 2 ports */ +struct pw_impl_link *pw_impl_link_find(struct pw_impl_port *output, struct pw_impl_port *input); + +/** + * \} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* PIPEWIRE_IMPL_LINK_H */ diff --git a/src/pipewire/impl-metadata.c b/src/pipewire/impl-metadata.c new file mode 100644 index 0000000..9f7aa41 --- /dev/null +++ b/src/pipewire/impl-metadata.c @@ -0,0 +1,627 @@ +/* PipeWire + * + * Copyright © 2021 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 <errno.h> + +#include <spa/debug/types.h> +#include <spa/utils/string.h> + +#include "pipewire/impl.h" +#include "pipewire/private.h" + +#include "pipewire/extensions/metadata.h" + +PW_LOG_TOPIC_EXTERN(log_metadata); +#define PW_LOG_TOPIC_DEFAULT log_metadata + +#define pw_metadata_emit(hooks,method,version,...) \ + spa_hook_list_call_simple(hooks, struct pw_metadata_events, \ + method, version, ##__VA_ARGS__) + +#define pw_metadata_emit_property(hooks,...) pw_metadata_emit(hooks,property, 0, ##__VA_ARGS__) + +struct metadata { + struct spa_interface iface; + struct pw_array storage; + struct spa_hook_list hooks; /**< event listeners */ +}; + +struct item { + uint32_t subject; + char *key; + char *type; + char *value; +}; + +static void clear_item(struct item *item) +{ + free(item->key); + free(item->type); + free(item->value); + spa_zero(*item); +} + +static void set_item(struct item *item, uint32_t subject, const char *key, const char *type, const char *value) +{ + item->subject = subject; + item->key = strdup(key); + item->type = type ? strdup(type) : NULL; + item->value = strdup(value); +} + +static int change_item(struct item *item, const char *type, const char *value) +{ + int changed = 0; + if (!spa_streq(item->type, type)) { + free((char*)item->type); + item->type = type ? strdup(type) : NULL; + changed++; + } + if (!spa_streq(item->value, value)) { + free((char*)item->value); + item->value = value ? strdup(value) : NULL; + changed++; + } + return changed; +} + +static void emit_properties(struct metadata *this) +{ + struct item *item; + pw_array_for_each(item, &this->storage) { + pw_log_debug("metadata %p: %d %s %s %s", + this, item->subject, item->key, item->type, item->value); + pw_metadata_emit_property(&this->hooks, + item->subject, + item->key, + item->type, + item->value); + } +} + +static int impl_add_listener(void *object, + struct spa_hook *listener, + const struct pw_metadata_events *events, + void *data) +{ + struct metadata *this = object; + struct spa_hook_list save; + + spa_return_val_if_fail(this != NULL, -EINVAL); + spa_return_val_if_fail(events != NULL, -EINVAL); + + pw_log_debug("metadata %p:", this); + + spa_hook_list_isolate(&this->hooks, &save, listener, events, data); + + emit_properties(this); + + spa_hook_list_join(&this->hooks, &save); + + return 0; +} + +static struct item *find_item(struct pw_array *storage, uint32_t subject, const char *key) +{ + struct item *item; + + pw_array_for_each(item, storage) { + if (item->subject == subject && (key == NULL || spa_streq(item->key, key))) + return item; + } + return NULL; +} + +static int clear_subjects(struct metadata *this, struct pw_array *storage, uint32_t subject) +{ + struct item *item; + uint32_t removed = 0; + + while (true) { + item = find_item(storage, subject, NULL); + if (item == NULL) + break; + + pw_log_debug("%p: remove id:%d key:%s", this, subject, item->key); + + clear_item(item); + pw_array_remove(storage, item); + removed++; + } + if (removed > 0) + pw_metadata_emit_property(&this->hooks, subject, NULL, NULL, NULL); + + return 0; +} + +static void clear_items(struct metadata *this) +{ + struct item *item; + struct pw_array tmp; + + /* copy to tmp and reinitialize the storage so that the callbacks + * will operate on the new empty metadata. Otherwise, if a callbacks + * adds new metadata we just keep on emptying the metadata forever. */ + tmp = this->storage; + pw_array_init(&this->storage, 4096); + + pw_array_consume(item, &tmp) + clear_subjects(this, &tmp, item->subject); + pw_array_clear(&tmp); +} + +static int impl_set_property(void *object, + uint32_t subject, + const char *key, + const char *type, + const char *value) +{ + struct metadata *this = object; + struct item *item = NULL; + int changed = 0; + + pw_log_debug("%p: id:%d key:%s type:%s value:%s", this, subject, key, type, value); + + if (key == NULL) + return clear_subjects(this, &this->storage, subject); + + item = find_item(&this->storage, subject, key); + if (value == NULL) { + if (item != NULL) { + clear_item(item); + pw_array_remove(&this->storage, item); + type = NULL; + changed++; + pw_log_info("%p: remove id:%d key:%s", this, + subject, key); + } + } else if (item == NULL) { + item = pw_array_add(&this->storage, sizeof(*item)); + if (item == NULL) + return -errno; + set_item(item, subject, key, type, value); + changed++; + pw_log_info("%p: add id:%d key:%s type:%s value:%s", this, + subject, key, type, value); + } else { + if (type == NULL) + type = item->type; + changed = change_item(item, type, value); + if (changed) + pw_log_info("%p: change id:%d key:%s type:%s value:%s", this, + subject, key, type, value); + } + + if (changed) { + pw_metadata_emit_property(&this->hooks, + subject, key, type, value); + } + return 0; +} + +static int impl_clear(void *object) +{ + struct metadata *this = object; + clear_items(this); + return 0; +} + +static const struct pw_metadata_methods impl_metadata = { + PW_VERSION_METADATA_METHODS, + .add_listener = impl_add_listener, + .set_property = impl_set_property, + .clear = impl_clear, +}; + +static struct pw_metadata *metadata_init(struct metadata *this) +{ + this->iface = SPA_INTERFACE_INIT( + PW_TYPE_INTERFACE_Metadata, + PW_VERSION_METADATA, + &impl_metadata, this); + pw_array_init(&this->storage, 4096); + spa_hook_list_init(&this->hooks); + return (struct pw_metadata*)&this->iface; +} + +static void metadata_reset(struct metadata *this) +{ + spa_hook_list_clean(&this->hooks); + clear_items(this); + pw_array_clear(&this->storage); +} + +struct impl { + struct pw_impl_metadata this; + + struct metadata def; +}; + +struct resource_data { + struct pw_impl_metadata *impl; + + struct pw_resource *resource; + struct spa_hook resource_listener; + struct spa_hook object_listener; + struct spa_hook metadata_listener; +}; + + +static int metadata_property(void *data, uint32_t subject, const char *key, + const char *type, const char *value) +{ + struct pw_impl_metadata *this = data; + pw_impl_metadata_emit_property(this, subject, key, type, value); + return 0; +} + +static const struct pw_metadata_events metadata_events = { + PW_VERSION_METADATA_EVENTS, + .property = metadata_property, +}; + +SPA_EXPORT +struct pw_impl_metadata *pw_context_create_metadata(struct pw_context *context, + const char *name, struct pw_properties *properties, + size_t user_data_size) +{ + struct impl *impl; + struct pw_impl_metadata *this; + int res; + + if (properties == NULL) + properties = pw_properties_new(NULL, NULL); + if (properties == NULL) + return NULL; + + impl = calloc(1, sizeof(*impl) + user_data_size); + if (impl == NULL) { + res = -errno; + goto error_exit; + }; + this = &impl->this; + + this->context = context; + this->properties = properties; + + if (name != NULL) + pw_properties_set(properties, PW_KEY_METADATA_NAME, name); + + spa_hook_list_init(&this->listener_list); + + pw_impl_metadata_set_implementation(this, metadata_init(&impl->def)); + + if (user_data_size > 0) + this->user_data = SPA_PTROFF(this, sizeof(*this), void); + + pw_log_debug("%p: new", this); + + return this; + +error_exit: + pw_properties_free(properties); + errno = -res; + return NULL; +} + +SPA_EXPORT +const struct pw_properties *pw_impl_metadata_get_properties(struct pw_impl_metadata *metadata) +{ + return metadata->properties; +} + +SPA_EXPORT +int pw_impl_metadata_set_implementation(struct pw_impl_metadata *metadata, + struct pw_metadata *meta) +{ + struct impl *impl = SPA_CONTAINER_OF(metadata, struct impl, this); + + if (metadata->metadata == meta) + return 0; + + if (metadata->metadata) + spa_hook_remove(&metadata->metadata_listener); + if (meta == NULL) + meta = (struct pw_metadata*)&impl->def.iface; + + metadata->metadata = meta; + pw_metadata_add_listener(meta, &metadata->metadata_listener, + &metadata_events, metadata); + + return 0; +} +SPA_EXPORT +struct pw_metadata *pw_impl_metadata_get_implementation(struct pw_impl_metadata *metadata) +{ + return metadata->metadata; +} + +SPA_EXPORT +void pw_impl_metadata_destroy(struct pw_impl_metadata *metadata) +{ + struct impl *impl = SPA_CONTAINER_OF(metadata, struct impl, this); + + pw_log_debug("%p: destroy", metadata); + pw_impl_metadata_emit_destroy(metadata); + + if (metadata->registered) + spa_list_remove(&metadata->link); + + if (metadata->global) { + spa_hook_remove(&metadata->global_listener); + pw_global_destroy(metadata->global); + } + + pw_impl_metadata_emit_free(metadata); + pw_log_debug("%p: free", metadata); + + metadata_reset(&impl->def); + + spa_hook_list_clean(&metadata->listener_list); + + pw_properties_free(metadata->properties); + + free(metadata); +} + +#define pw_metadata_resource(r,m,v,...) \ + pw_resource_call_res(r,struct pw_metadata_events,m,v,__VA_ARGS__) + +#define pw_metadata_resource_property(r,...) \ + pw_metadata_resource(r,property,0,__VA_ARGS__) + +static int metadata_resource_property(void *data, + uint32_t subject, + const char *key, + const char *type, + const char *value) +{ + struct resource_data *d = data; + struct pw_resource *resource = d->resource; + struct pw_impl_client *client = pw_resource_get_client(resource); + + if (pw_impl_client_check_permissions(client, subject, PW_PERM_R) >= 0) + pw_metadata_resource_property(d->resource, subject, key, type, value); + return 0; +} + +static const struct pw_metadata_events metadata_resource_events = { + PW_VERSION_METADATA_EVENTS, + .property = metadata_resource_property, +}; + +static int metadata_set_property(void *object, + uint32_t subject, + const char *key, + const char *type, + const char *value) +{ + struct resource_data *d = object; + struct pw_impl_metadata *impl = d->impl; + struct pw_resource *resource = d->resource; + struct pw_impl_client *client = pw_resource_get_client(resource); + int res; + + if ((res = pw_impl_client_check_permissions(client, subject, PW_PERM_R)) < 0) + goto error; + + pw_metadata_set_property(impl->metadata, subject, key, type, value); + return 0; + +error: + pw_resource_errorf(resource, res, "set property error for id %d: %s", + subject, spa_strerror(res)); + return res; +} + +static int metadata_clear(void *object) +{ + struct resource_data *d = object; + struct pw_impl_metadata *impl = d->impl; + pw_metadata_clear(impl->metadata); + return 0; +} + +static const struct pw_metadata_methods metadata_methods = { + PW_VERSION_METADATA_METHODS, + .set_property = metadata_set_property, + .clear = metadata_clear, +}; + +static void global_unbind(void *data) +{ + struct resource_data *d = data; + if (d->resource) { + spa_hook_remove(&d->resource_listener); + spa_hook_remove(&d->object_listener); + spa_hook_remove(&d->metadata_listener); + } +} + +static const struct pw_resource_events resource_events = { + PW_VERSION_RESOURCE_EVENTS, + .destroy = global_unbind, +}; + +static int +global_bind(void *object, struct pw_impl_client *client, uint32_t permissions, + uint32_t version, uint32_t id) +{ + struct pw_impl_metadata *this = object; + struct pw_global *global = this->global; + struct pw_resource *resource; + struct resource_data *data; + + resource = pw_resource_new(client, id, permissions, global->type, version, sizeof(*data)); + if (resource == NULL) + goto error_resource; + + data = pw_resource_get_user_data(resource); + data->impl = this; + data->resource = resource; + + pw_log_debug("%p: bound to %d", this, resource->id); + pw_global_add_resource(global, resource); + + /* listen for when the resource goes away */ + pw_resource_add_listener(resource, + &data->resource_listener, + &resource_events, data); + + /* resource methods -> implementation */ + pw_resource_add_object_listener(resource, + &data->object_listener, + &metadata_methods, data); + + /* implementation events -> resource */ + pw_metadata_add_listener(this->metadata, + &data->metadata_listener, + &metadata_resource_events, data); + + return 0; + +error_resource: + pw_log_error("%p: can't create metadata resource: %m", this); + return -errno; +} + +static void global_destroy(void *data) +{ + struct pw_impl_metadata *metadata = data; + spa_hook_remove(&metadata->global_listener); + metadata->global = NULL; + pw_impl_metadata_destroy(metadata); +} + +static const struct pw_global_events global_events = { + PW_VERSION_GLOBAL_EVENTS, + .destroy = global_destroy, +}; + +SPA_EXPORT +int pw_impl_metadata_register(struct pw_impl_metadata *metadata, + struct pw_properties *properties) +{ + struct pw_context *context = metadata->context; + static const char * const keys[] = { + PW_KEY_OBJECT_SERIAL, + PW_KEY_MODULE_ID, + PW_KEY_FACTORY_ID, + PW_KEY_METADATA_NAME, + NULL + }; + + if (metadata->registered) + goto error_existed; + + metadata->global = pw_global_new(context, + PW_TYPE_INTERFACE_Metadata, + PW_VERSION_METADATA, + properties, + global_bind, + metadata); + if (metadata->global == NULL) + return -errno; + + spa_list_append(&context->metadata_list, &metadata->link); + metadata->registered = true; + + pw_properties_setf(metadata->properties, PW_KEY_OBJECT_SERIAL, "%"PRIu64, + pw_global_get_serial(metadata->global)); + + pw_global_update_keys(metadata->global, &metadata->properties->dict, keys); + + pw_global_add_listener(metadata->global, &metadata->global_listener, &global_events, metadata); + pw_global_register(metadata->global); + + return 0; + +error_existed: + pw_properties_free(properties); + return -EEXIST; +} + +SPA_EXPORT +void *pw_impl_metadata_get_user_data(struct pw_impl_metadata *metadata) +{ + return metadata->user_data; +} + +SPA_EXPORT +struct pw_global *pw_impl_metadata_get_global(struct pw_impl_metadata *metadata) +{ + return metadata->global; +} + +SPA_EXPORT +void pw_impl_metadata_add_listener(struct pw_impl_metadata *metadata, + struct spa_hook *listener, + const struct pw_impl_metadata_events *events, + void *data) +{ + spa_hook_list_append(&metadata->listener_list, listener, events, data); +} + +SPA_EXPORT +int pw_impl_metadata_set_property(struct pw_impl_metadata *metadata, + uint32_t subject, const char *key, const char *type, + const char *value) +{ + return pw_metadata_set_property(metadata->metadata, subject, key, type, value); +} + +SPA_EXPORT +int pw_impl_metadata_set_propertyf(struct pw_impl_metadata *metadata, + uint32_t subject, const char *key, const char *type, + const char *fmt, ...) +{ + va_list args; + int n = 0, res; + size_t size = 0; + char *p = NULL; + + va_start(args, fmt); + n = vsnprintf(p, size, fmt, args); + va_end(args); + if (n < 0) + return -errno; + + size = (size_t) n + 1; + p = malloc(size); + if (p == NULL) + return -errno; + + va_start(args, fmt); + n = vsnprintf(p, size, fmt, args); + va_end(args); + + if (n < 0) { + free(p); + return -errno; + } + res = pw_impl_metadata_set_property(metadata, subject, key, type, p); + free(p); + + return res; +} diff --git a/src/pipewire/impl-metadata.h b/src/pipewire/impl-metadata.h new file mode 100644 index 0000000..4b81cac --- /dev/null +++ b/src/pipewire/impl-metadata.h @@ -0,0 +1,114 @@ +/* PipeWire + * + * Copyright © 2021 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_IMPL_METADATA_H +#define PIPEWIRE_IMPL_METADATA_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** \defgroup pw_impl_metadata Metadata Impl + * + * The metadata is used to store key/type/value pairs per object id. + */ + +/** + * \addtogroup pw_impl_metadata + * \{ + */ +struct pw_impl_metadata; + +#include <pipewire/context.h> +#include <pipewire/impl-client.h> +#include <pipewire/global.h> +#include <pipewire/properties.h> +#include <pipewire/resource.h> + +#include "pipewire/extensions/metadata.h" + +/** Metadata events, listen to them with \ref pw_impl_metadata_add_listener */ +struct pw_impl_metadata_events { +#define PW_VERSION_IMPL_METADATA_EVENTS 0 + uint32_t version; + + /** the metadata is destroyed */ + void (*destroy) (void *data); + /** the metadata is freed */ + void (*free) (void *data); + + /** a property changed */ + int (*property) (void *data, + uint32_t subject, + const char *key, + const char *type, + const char *value); +}; + +struct pw_impl_metadata *pw_context_create_metadata(struct pw_context *context, + const char *name, struct pw_properties *properties, + size_t user_data_size); + +/** Get the metadata properties */ +const struct pw_properties *pw_impl_metadata_get_properties(struct pw_impl_metadata *metadata); + +int pw_impl_metadata_register(struct pw_impl_metadata *metadata, + struct pw_properties *properties); + +void pw_impl_metadata_destroy(struct pw_impl_metadata *metadata); + +void *pw_impl_metadata_get_user_data(struct pw_impl_metadata *metadata); + +int pw_impl_metadata_set_implementation(struct pw_impl_metadata *metadata, + struct pw_metadata *impl); + +struct pw_metadata *pw_impl_metadata_get_implementation(struct pw_impl_metadata *metadata); + +/** Get the global of this metadata */ +struct pw_global *pw_impl_metadata_get_global(struct pw_impl_metadata *metadata); + +/** Add an event listener */ +void pw_impl_metadata_add_listener(struct pw_impl_metadata *metadata, + struct spa_hook *listener, + const struct pw_impl_metadata_events *events, + void *data); + +/** Set a property */ +int pw_impl_metadata_set_property(struct pw_impl_metadata *metadata, + uint32_t subject, const char *key, const char *type, + const char *value); + +int pw_impl_metadata_set_propertyf(struct pw_impl_metadata *metadata, + uint32_t subject, const char *key, const char *type, + const char *fmt, ...) SPA_PRINTF_FUNC(5,6); + +/** + * \} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* PIPEWIRE_IMPL_METADATA_H */ diff --git a/src/pipewire/impl-module.c b/src/pipewire/impl-module.c new file mode 100644 index 0000000..282ba08 --- /dev/null +++ b/src/pipewire/impl-module.c @@ -0,0 +1,427 @@ +/* PipeWire + * Copyright © 2016 Axis Communications <dev-gstreamer@axis.com> + * @author Linus Svensson <linus.svensson@axis.com> + * 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 <dlfcn.h> +#include <dirent.h> +#include <limits.h> +#include <sys/stat.h> +#include <errno.h> + +#include <spa/utils/string.h> + +#include "pipewire/impl.h" +#include "pipewire/private.h" + +PW_LOG_TOPIC_EXTERN(log_module); +#define PW_LOG_TOPIC_DEFAULT log_module + +/** \cond */ +struct impl { + struct pw_impl_module this; + void *hnd; + uint32_t destroy_work_id; +}; + +#define pw_module_resource_info(r,...) pw_resource_call(r,struct pw_module_events,info,0,__VA_ARGS__) + + +/** \endcond */ + +static char *find_module(const char *path, const char *name, int level) +{ + char *filename; + struct dirent *entry; + struct stat s; + DIR *dir; + int res; + + filename = spa_aprintf("%s/%s.so", path, name); + if (filename == NULL) + return NULL; + + if (stat(filename, &s) == 0 && S_ISREG(s.st_mode)) { + /* found a regular file with name */ + return filename; + } + + free(filename); + filename = NULL; + + /* now recurse down in subdirectories and look for it there */ + if (level <= 0) + return NULL; + + dir = opendir(path); + if (dir == NULL) { + res = -errno; + pw_log_warn("could not open %s: %m", path); + errno = -res; + return NULL; + } + + while ((entry = readdir(dir))) { + char *newpath; + + if (spa_streq(entry->d_name, ".") || spa_streq(entry->d_name, "..")) + continue; + + newpath = spa_aprintf("%s/%s", path, entry->d_name); + if (newpath == NULL) + break; + + if (stat(newpath, &s) == 0 && S_ISDIR(s.st_mode)) + filename = find_module(newpath, name, level - 1); + + free(newpath); + + if (filename != NULL) + break; + } + + closedir(dir); + + return filename; +} + +static int +global_bind(void *object, struct pw_impl_client *client, uint32_t permissions, + uint32_t version, uint32_t id) +{ + struct pw_impl_module *this = object; + struct pw_global *global = this->global; + struct pw_resource *resource; + + resource = pw_resource_new(client, id, permissions, global->type, version, 0); + if (resource == NULL) + goto error_resource; + + pw_log_debug("%p: bound to %d", this, resource->id); + pw_global_add_resource(global, resource); + + this->info.change_mask = PW_MODULE_CHANGE_MASK_ALL; + pw_module_resource_info(resource, &this->info); + this->info.change_mask = 0; + + return 0; + +error_resource: + pw_log_error("%p: can't create module resource: %m", this); + return -errno; +} + +static void global_destroy(void *data) +{ + struct pw_impl_module *module = data; + spa_hook_remove(&module->global_listener); + module->global = NULL; + pw_impl_module_destroy(module); +} + +static const struct pw_global_events global_events = { + PW_VERSION_GLOBAL_EVENTS, + .destroy = global_destroy, +}; + +/** Load a module + * + * \param context a \ref pw_context + * \param name name of the module to load + * \param args A string with arguments for the module + * \param properties extra global properties + * \return A \ref pw_impl_module if the module could be loaded, or NULL on failure. + * + */ +SPA_EXPORT +struct pw_impl_module * +pw_context_load_module(struct pw_context *context, + const char *name, const char *args, + struct pw_properties *properties) +{ + struct pw_impl_module *this; + struct impl *impl; + void *hnd; + char *filename = NULL; + const char *module_dir; + int res; + pw_impl_module_init_func_t init_func; + const char *state = NULL, *p; + size_t len; + char path_part[PATH_MAX]; + static const char * const keys[] = { + PW_KEY_OBJECT_SERIAL, + PW_KEY_MODULE_NAME, + NULL + }; + + pw_log_info("%p: name:%s args:%s", context, name, args); + + module_dir = getenv("PIPEWIRE_MODULE_DIR"); + if (module_dir == NULL) { + module_dir = MODULEDIR; + pw_log_debug("moduledir set to: %s", module_dir); + } + else { + pw_log_debug("PIPEWIRE_MODULE_DIR set to: %s", module_dir); + } + + while ((p = pw_split_walk(module_dir, ":", &len, &state))) { + if ((res = spa_scnprintf(path_part, sizeof(path_part), "%.*s", (int)len, p)) > 0) { + filename = find_module(path_part, name, 8); + if (filename != NULL) { + pw_log_debug("trying to load module: %s (%s) args(%s)", name, filename, args); + + hnd = dlopen(filename, RTLD_NOW | RTLD_LOCAL); + if (hnd != NULL) + break; + + pw_log_debug("open failed: %s", dlerror()); + free(filename); + filename = NULL; + } + } + } + + if (filename == NULL) + goto error_not_found; + if (hnd == NULL) + goto error_open_failed; + + if ((init_func = dlsym(hnd, PIPEWIRE_SYMBOL_MODULE_INIT)) == NULL) + goto error_no_pw_module; + + if (properties == NULL) + properties = pw_properties_new(NULL, NULL); + if (properties == NULL) + goto error_no_mem; + + impl = calloc(1, sizeof(struct impl)); + if (impl == NULL) + goto error_no_mem; + + impl->hnd = hnd; + impl->destroy_work_id = SPA_ID_INVALID; + hnd = NULL; + + this = &impl->this; + this->context = context; + this->properties = properties; + properties = NULL; + + spa_hook_list_init(&this->listener_list); + + pw_properties_set(this->properties, PW_KEY_MODULE_NAME, name); + + this->info.name = name ? strdup(name) : NULL; + this->info.filename = filename; + filename = NULL; + this->info.args = args ? strdup(args) : NULL; + + spa_list_prepend(&context->module_list, &this->link); + + this->global = pw_global_new(context, + PW_TYPE_INTERFACE_Module, + PW_VERSION_MODULE, + NULL, + global_bind, + this); + + if (this->global == NULL) + goto error_no_global; + + this->info.id = this->global->id; + pw_properties_setf(this->properties, PW_KEY_OBJECT_ID, "%d", this->info.id); + pw_properties_setf(this->properties, PW_KEY_OBJECT_SERIAL, "%"PRIu64, + pw_global_get_serial(this->global)); + this->info.props = &this->properties->dict; + + pw_global_update_keys(this->global, &this->properties->dict, keys); + + pw_impl_module_emit_initialized(this); + + pw_global_add_listener(this->global, &this->global_listener, &global_events, this); + + if ((res = init_func(this, args)) < 0) + goto error_init_failed; + + pw_global_register(this->global); + + pw_impl_module_emit_registered(this); + + pw_log_debug("%p: loaded module: %s", this, this->info.name); + + return this; + +error_not_found: + res = -ENOENT; + pw_log_info("No module \"%s\" was found", name); + goto error_cleanup; +error_open_failed: + res = -EIO; + pw_log_error("Failed to open module: \"%s\" %s", filename, dlerror()); + goto error_free_filename; +error_no_pw_module: + res = -ENOSYS; + pw_log_error("\"%s\": is not a pipewire module", filename); + goto error_close; +error_no_mem: + res = -errno; + pw_log_error("can't allocate module: %m"); + goto error_close; +error_no_global: + res = -errno; + pw_log_error("\"%s\": failed to create global: %m", this->info.filename); + goto error_free_module; +error_init_failed: + pw_log_debug("\"%s\": failed to initialize: %s", this->info.filename, spa_strerror(res)); + goto error_free_module; + +error_free_module: + pw_impl_module_destroy(this); +error_close: + if (hnd) + dlclose(hnd); +error_free_filename: + if (filename) + free(filename); +error_cleanup: + pw_properties_free(properties); + errno = -res; + return NULL; +} + +/** Destroy a module + * \param module the module to destroy + */ +SPA_EXPORT +void pw_impl_module_destroy(struct pw_impl_module *module) +{ + struct impl *impl = SPA_CONTAINER_OF(module, struct impl, this); + + pw_log_debug("%p: destroy %s", module, module->info.name); + pw_impl_module_emit_destroy(module); + + spa_list_remove(&module->link); + + if (module->global) { + spa_hook_remove(&module->global_listener); + pw_global_destroy(module->global); + } + + pw_log_debug("%p: free", module); + pw_impl_module_emit_free(module); + free((char *) module->info.name); + free((char *) module->info.filename); + free((char *) module->info.args); + + pw_properties_free(module->properties); + + spa_hook_list_clean(&module->listener_list); + + if (impl->destroy_work_id != SPA_ID_INVALID) + pw_work_queue_cancel(pw_context_get_work_queue(module->context), + module, SPA_ID_INVALID); + + if (!pw_in_valgrind() && dlclose(impl->hnd) != 0) + pw_log_warn("%p: dlclose failed: %s", module, dlerror()); + free(impl); +} + +SPA_EXPORT +struct pw_context * +pw_impl_module_get_context(struct pw_impl_module *module) +{ + return module->context; +} + +SPA_EXPORT +struct pw_global * pw_impl_module_get_global(struct pw_impl_module *module) +{ + return module->global; +} + +SPA_EXPORT +const struct pw_properties *pw_impl_module_get_properties(struct pw_impl_module *module) +{ + return module->properties; +} + +SPA_EXPORT +int pw_impl_module_update_properties(struct pw_impl_module *module, const struct spa_dict *dict) +{ + struct pw_resource *resource; + int changed; + + changed = pw_properties_update(module->properties, dict); + module->info.props = &module->properties->dict; + + pw_log_debug("%p: updated %d properties", module, changed); + + if (!changed) + return 0; + + module->info.change_mask |= PW_MODULE_CHANGE_MASK_PROPS; + if (module->global) + spa_list_for_each(resource, &module->global->resource_list, link) + pw_module_resource_info(resource, &module->info); + module->info.change_mask = 0; + + return changed; +} + +SPA_EXPORT +const struct pw_module_info * +pw_impl_module_get_info(struct pw_impl_module *module) +{ + return &module->info; +} + +SPA_EXPORT +void pw_impl_module_add_listener(struct pw_impl_module *module, + struct spa_hook *listener, + const struct pw_impl_module_events *events, + void *data) +{ + spa_hook_list_append(&module->listener_list, listener, events, data); +} + +static void do_destroy_module(void *obj, void *data, int res, uint32_t id) +{ + pw_impl_module_destroy(obj); +} + +SPA_EXPORT +void pw_impl_module_schedule_destroy(struct pw_impl_module *module) +{ + struct impl *impl = SPA_CONTAINER_OF(module, struct impl, this); + + if (impl->destroy_work_id != SPA_ID_INVALID) + return; + + impl->destroy_work_id = pw_work_queue_add(pw_context_get_work_queue(module->context), + module, 0, do_destroy_module, NULL); +} diff --git a/src/pipewire/impl-module.h b/src/pipewire/impl-module.h new file mode 100644 index 0000000..0135498 --- /dev/null +++ b/src/pipewire/impl-module.h @@ -0,0 +1,120 @@ +/* PipeWire + * Copyright © 2016 Axis Communications <dev-gstreamer@axis.com> + * @author Linus Svensson <linus.svensson@axis.com> + * 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. + */ + +#ifndef PIPEWIRE_IMPL_MODULE_H +#define PIPEWIRE_IMPL_MODULE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <spa/utils/hook.h> + +#include <pipewire/context.h> + +#define PIPEWIRE_SYMBOL_MODULE_INIT "pipewire__module_init" +#define PIPEWIRE_MODULE_PREFIX "libpipewire-" + +/** \defgroup pw_impl_module Module Impl + * + * A dynamically loadable module + */ + +/** + * \addtogroup pw_impl_module + * \{ + */ +struct pw_impl_module; + +/** Module init function signature + * + * \param module A \ref pw_impl_module + * \param args Arguments to the module + * \return 0 on success, < 0 otherwise with an errno style error + * + * A module should provide an init function with this signature. This function + * will be called when a module is loaded. + */ +typedef int (*pw_impl_module_init_func_t) (struct pw_impl_module *module, const char *args); + +/** Module events added with \ref pw_impl_module_add_listener */ +struct pw_impl_module_events { +#define PW_VERSION_IMPL_MODULE_EVENTS 0 + uint32_t version; + + /** The module is destroyed */ + void (*destroy) (void *data); + /** The module is freed */ + void (*free) (void *data); + /** The module is initialized */ + void (*initialized) (void *data); + + /** The module is registered. This is a good time to register + * objects created from the module. */ + void (*registered) (void *data); +}; + +struct pw_impl_module * +pw_context_load_module(struct pw_context *context, + const char *name, + const char *args, + struct pw_properties *properties); + +/** Get the context of a module */ +struct pw_context * pw_impl_module_get_context(struct pw_impl_module *module); + +/** Get the global of a module */ +struct pw_global * pw_impl_module_get_global(struct pw_impl_module *module); + +/** Get the module properties */ +const struct pw_properties *pw_impl_module_get_properties(struct pw_impl_module *module); + +/** Update the module properties */ +int pw_impl_module_update_properties(struct pw_impl_module *module, const struct spa_dict *dict); + +/** Get the module info */ +const struct pw_module_info *pw_impl_module_get_info(struct pw_impl_module *module); + +/** Add an event listener to a module */ +void pw_impl_module_add_listener(struct pw_impl_module *module, + struct spa_hook *listener, + const struct pw_impl_module_events *events, + void *data); + +/** Destroy a module */ +void pw_impl_module_destroy(struct pw_impl_module *module); + +/** Schedule a destroy later on the main thread */ +void pw_impl_module_schedule_destroy(struct pw_impl_module *module); + +/** + * \} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* PIPEWIRE_IMPL_MODULE_H */ diff --git a/src/pipewire/impl-node.c b/src/pipewire/impl-node.c new file mode 100644 index 0000000..6200e80 --- /dev/null +++ b/src/pipewire/impl-node.c @@ -0,0 +1,2316 @@ +/* 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 <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <errno.h> +#include <time.h> + +#include <spa/support/system.h> +#include <spa/pod/parser.h> +#include <spa/pod/filter.h> +#include <spa/pod/dynamic.h> +#include <spa/node/utils.h> +#include <spa/debug/types.h> +#include <spa/utils/string.h> + +#include "pipewire/impl-node.h" +#include "pipewire/private.h" + +PW_LOG_TOPIC_EXTERN(log_node); +#define PW_LOG_TOPIC_DEFAULT log_node + +#define DEFAULT_SYNC_TIMEOUT ((uint64_t)(5 * SPA_NSEC_PER_SEC)) + +/** \cond */ +struct impl { + struct pw_impl_node this; + + enum pw_node_state pending_state; + uint32_t pending_id; + + struct pw_work_queue *work; + + int last_error; + + struct spa_list param_list; + struct spa_list pending_list; + + unsigned int cache_params:1; + unsigned int pending_play:1; +}; + +#define pw_node_resource(r,m,v,...) pw_resource_call(r,struct pw_node_events,m,v,__VA_ARGS__) +#define pw_node_resource_info(r,...) pw_node_resource(r,info,0,__VA_ARGS__) +#define pw_node_resource_param(r,...) pw_node_resource(r,param,0,__VA_ARGS__) + +struct resource_data { + struct pw_impl_node *node; + + struct pw_resource *resource; + struct spa_hook resource_listener; + struct spa_hook object_listener; + + uint32_t subscribe_ids[MAX_PARAMS]; + uint32_t n_subscribe_ids; + + /* for async replies */ + int seq; + int end; + struct spa_hook listener; +}; + +/** \endcond */ + +static void add_node(struct pw_impl_node *this, struct pw_impl_node *driver) +{ + struct pw_node_activation_state *dstate, *nstate; + struct pw_node_target *t; + + if (this->exported) + return; + + pw_log_trace("%p: add to driver %p %p %p", this, driver, + driver->rt.activation, this->rt.activation); + + /* signal the driver */ + this->rt.driver_target.activation = driver->rt.activation; + this->rt.driver_target.node = driver; + this->rt.driver_target.data = driver; + spa_list_append(&this->rt.target_list, &this->rt.driver_target.link); + + spa_list_append(&driver->rt.target_list, &this->rt.target.link); + nstate = &this->rt.activation->state[0]; + if (!this->rt.target.active) { + nstate->required++; + this->rt.target.active = true; + } + + spa_list_for_each(t, &this->rt.target_list, link) { + dstate = &t->activation->state[0]; + if (!t->active) { + dstate->required++; + t->active = true; + } + pw_log_trace("%p: driver state:%p pending:%d/%d, node state:%p pending:%d/%d", + this, dstate, dstate->pending, dstate->required, + nstate, nstate->pending, nstate->required); + } +} + +static void remove_node(struct pw_impl_node *this) +{ + struct pw_node_activation_state *dstate, *nstate; + struct pw_node_target *t; + + if (this->exported) + return; + + pw_log_trace("%p: remove from driver %p %p %p", + this, this->rt.driver_target.data, + this->rt.driver_target.activation, this->rt.activation); + + spa_list_remove(&this->rt.target.link); + + nstate = &this->rt.activation->state[0]; + if (this->rt.target.active) { + nstate->required--; + this->rt.target.active = false; + } + + spa_list_for_each(t, &this->rt.target_list, link) { + dstate = &t->activation->state[0]; + if (t->active) { + dstate->required--; + t->active = false; + } + pw_log_trace("%p: driver state:%p pending:%d/%d, node state:%p pending:%d/%d", + this, dstate, dstate->pending, dstate->required, + nstate, nstate->pending, nstate->required); + } + spa_list_remove(&this->rt.driver_target.link); + + this->rt.driver_target.node = NULL; +} + +static int +do_node_add(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) +{ + struct pw_impl_node *this = user_data; + struct pw_impl_node *driver = this->driver_node; + + this->added = true; + if (this->source.loop == NULL) { + struct spa_system *data_system = this->context->data_system; + uint64_t dummy; + int res; + + /* clear the eventfd in case it was written to while the node was stopped */ + res = spa_system_eventfd_read(data_system, this->source.fd, &dummy); + if (SPA_UNLIKELY(res != -EAGAIN && res != 0)) + pw_log_warn("%p: read failed %m", this); + + spa_loop_add_source(loop, &this->source); + add_node(this, driver); + } + return 0; +} + +static int +do_node_remove(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) +{ + struct pw_impl_node *this = user_data; + if (this->source.loop != NULL) { + spa_loop_remove_source(loop, &this->source); + remove_node(this); + } + this->added = false; + return 0; +} + +static void node_deactivate(struct pw_impl_node *this) +{ + struct pw_impl_port *port; + struct pw_impl_link *link; + + pw_log_debug("%p: deactivate", this); + + /* make sure the node doesn't get woken up while not active */ + pw_loop_invoke(this->data_loop, do_node_remove, 1, NULL, 0, true, this); + + spa_list_for_each(port, &this->input_ports, link) { + spa_list_for_each(link, &port->links, input_link) + pw_impl_link_deactivate(link); + } + spa_list_for_each(port, &this->output_ports, link) { + spa_list_for_each(link, &port->links, output_link) + pw_impl_link_deactivate(link); + } +} + +static int idle_node(struct pw_impl_node *this) +{ + struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this); + int res = 0; + + pw_log_debug("%p: idle node state:%s pending:%s pause-on-idle:%d", this, + pw_node_state_as_string(this->info.state), + pw_node_state_as_string(impl->pending_state), + this->pause_on_idle); + + if (impl->pending_state <= PW_NODE_STATE_IDLE) + return 0; + + if (!this->pause_on_idle) + return 0; + + node_deactivate(this); + + res = spa_node_send_command(this->node, + &SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_Pause)); + if (res < 0) + pw_log_debug("%p: pause node error %s", this, spa_strerror(res)); + + return res; +} + +static void node_activate(struct pw_impl_node *this) +{ + struct pw_impl_port *port; + + pw_log_debug("%p: activate", this); + spa_list_for_each(port, &this->output_ports, link) { + struct pw_impl_link *link; + spa_list_for_each(link, &port->links, output_link) + pw_impl_link_activate(link); + } + spa_list_for_each(port, &this->input_ports, link) { + struct pw_impl_link *link; + spa_list_for_each(link, &port->links, input_link) + pw_impl_link_activate(link); + } +} + +static int start_node(struct pw_impl_node *this) +{ + struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this); + int res = 0; + + node_activate(this); + + if (impl->pending_state >= PW_NODE_STATE_RUNNING) + return 0; + + pw_log_debug("%p: start node driving:%d driver:%d added:%d", this, + this->driving, this->driver, this->added); + + if (!(this->driving && this->driver)) { + impl->pending_play = true; + res = spa_node_send_command(this->node, + &SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_Start)); + } else { + /* driver nodes will wait until all other nodes are started before + * they are started */ + res = EBUSY; + } + + if (res < 0) + pw_log_error("(%s-%u) start node error %d: %s", this->name, this->info.id, + res, spa_strerror(res)); + + return res; +} + +static void emit_info_changed(struct pw_impl_node *node, bool flags_changed) +{ + if (node->info.change_mask == 0 && !flags_changed) + return; + + pw_impl_node_emit_info_changed(node, &node->info); + + if (node->global && node->info.change_mask != 0) { + struct pw_resource *resource; + spa_list_for_each(resource, &node->global->resource_list, link) + pw_node_resource_info(resource, &node->info); + } + + node->info.change_mask = 0; +} + +static int resource_is_subscribed(struct pw_resource *resource, uint32_t id) +{ + struct resource_data *data = pw_resource_get_user_data(resource); + uint32_t i; + + for (i = 0; i < data->n_subscribe_ids; i++) { + if (data->subscribe_ids[i] == id) + return 1; + } + return 0; +} + +static int notify_param(void *data, int seq, uint32_t id, + uint32_t index, uint32_t next, struct spa_pod *param) +{ + struct pw_impl_node *node = data; + struct pw_resource *resource; + + spa_list_for_each(resource, &node->global->resource_list, link) { + if (!resource_is_subscribed(resource, id)) + continue; + + pw_log_debug("%p: resource %p notify param %d", node, resource, id); + pw_node_resource_param(resource, seq, id, index, next, param); + } + return 0; +} + +static void emit_params(struct pw_impl_node *node, uint32_t *changed_ids, uint32_t n_changed_ids) +{ + uint32_t i; + int res; + + if (node->global == NULL) + return; + + pw_log_debug("%p: emit %d params", node, n_changed_ids); + + for (i = 0; i < n_changed_ids; i++) { + struct pw_resource *resource; + int subscribed = 0; + + /* first check if anyone is subscribed */ + spa_list_for_each(resource, &node->global->resource_list, link) { + if ((subscribed = resource_is_subscribed(resource, changed_ids[i]))) + break; + } + if (!subscribed) + continue; + + if ((res = pw_impl_node_for_each_param(node, 1, changed_ids[i], 0, UINT32_MAX, + NULL, notify_param, node)) < 0) { + pw_log_error("%p: error %d (%s)", node, res, spa_strerror(res)); + } + } +} + +static void node_update_state(struct pw_impl_node *node, enum pw_node_state state, int res, char *error) +{ + struct impl *impl = SPA_CONTAINER_OF(node, struct impl, this); + enum pw_node_state old = node->info.state; + + switch (state) { + case PW_NODE_STATE_RUNNING: + pw_log_debug("%p: start node driving:%d driver:%d added:%d", node, + node->driving, node->driver, node->added); + + if (res >= 0) { + pw_loop_invoke(node->data_loop, do_node_add, 1, NULL, 0, true, node); + } + if (node->driving && node->driver) { + res = spa_node_send_command(node->node, + &SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_Start)); + if (res < 0) { + state = PW_NODE_STATE_ERROR; + error = spa_aprintf("Start error: %s", spa_strerror(res)); + pw_loop_invoke(node->data_loop, do_node_remove, 1, NULL, 0, true, node); + } + } + break; + case PW_NODE_STATE_IDLE: + case PW_NODE_STATE_SUSPENDED: + case PW_NODE_STATE_ERROR: + if (state != PW_NODE_STATE_IDLE || node->pause_on_idle) + pw_loop_invoke(node->data_loop, do_node_remove, 1, NULL, 0, true, node); + break; + default: + break; + } + + free((char*)node->info.error); + node->info.error = error; + node->info.state = state; + impl->pending_state = state; + + pw_log_debug("%p: (%s) %s -> %s (%s)", node, node->name, + pw_node_state_as_string(old), pw_node_state_as_string(state), error); + + if (old == state) + return; + + if (state == PW_NODE_STATE_ERROR) { + pw_log_error("(%s-%u) %s -> error (%s)", node->name, node->info.id, + pw_node_state_as_string(old), error); + } else { + pw_log_info("(%s-%u) %s -> %s", node->name, node->info.id, + pw_node_state_as_string(old), pw_node_state_as_string(state)); + } + pw_impl_node_emit_state_changed(node, old, state, error); + + node->info.change_mask |= PW_NODE_CHANGE_MASK_STATE; + emit_info_changed(node, false); + + if (state == PW_NODE_STATE_ERROR && node->global) { + struct pw_resource *resource; + spa_list_for_each(resource, &node->global->resource_list, link) + pw_resource_error(resource, res, error); + } + if (node->reconfigure) { + if (state == PW_NODE_STATE_SUSPENDED && + node->pause_on_idle) { + node->reconfigure = false; + } + if (state == PW_NODE_STATE_RUNNING) + node->reconfigure = false; + } + if (old == PW_NODE_STATE_RUNNING && + state == PW_NODE_STATE_IDLE && + node->suspend_on_idle) { + pw_impl_node_set_state(node, PW_NODE_STATE_SUSPENDED); + } +} + +static int suspend_node(struct pw_impl_node *this) +{ + int res = 0; + struct pw_impl_port *p; + + pw_log_debug("%p: suspend node state:%s", this, + pw_node_state_as_string(this->info.state)); + + if (this->info.state > 0 && this->info.state <= PW_NODE_STATE_SUSPENDED) + return 0; + + node_deactivate(this); + + spa_list_for_each(p, &this->input_ports, link) { + if ((res = pw_impl_port_set_param(p, SPA_PARAM_Format, 0, NULL)) < 0) + pw_log_warn("%p: error unset format input: %s", + this, spa_strerror(res)); + /* force CONFIGURE in case of async */ + p->state = PW_IMPL_PORT_STATE_CONFIGURE; + } + + spa_list_for_each(p, &this->output_ports, link) { + if ((res = pw_impl_port_set_param(p, SPA_PARAM_Format, 0, NULL)) < 0) + pw_log_warn("%p: error unset format output: %s", + this, spa_strerror(res)); + /* force CONFIGURE in case of async */ + p->state = PW_IMPL_PORT_STATE_CONFIGURE; + } + + pw_log_debug("%p: suspend node driving:%d driver:%d added:%d", this, + this->driving, this->driver, this->added); + + res = spa_node_send_command(this->node, + &SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_Suspend)); + if (res == -ENOTSUP) + res = spa_node_send_command(this->node, + &SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_Pause)); + if (res < 0 && res != -EIO) + pw_log_warn("%p: suspend node error %s", this, spa_strerror(res)); + + node_update_state(this, PW_NODE_STATE_SUSPENDED, 0, NULL); + + return res; +} + +static void +clear_info(struct pw_impl_node *this) +{ + free(this->name); + free((char*)this->info.error); +} + +static int reply_param(void *data, int seq, uint32_t id, + uint32_t index, uint32_t next, struct spa_pod *param) +{ + struct resource_data *d = data; + pw_log_debug("%p: resource %p reply param %d", d->node, d->resource, seq); + pw_node_resource_param(d->resource, seq, id, index, next, param); + return 0; +} + +static int node_enum_params(void *object, int seq, uint32_t id, + uint32_t index, uint32_t num, const struct spa_pod *filter) +{ + struct resource_data *data = object; + struct pw_resource *resource = data->resource; + struct pw_impl_node *node = data->node; + int res; + + pw_log_debug("%p: resource %p enum params seq:%d id:%d (%s) index:%u num:%u", + node, resource, seq, id, + spa_debug_type_find_name(spa_type_param, id), index, num); + + if ((res = pw_impl_node_for_each_param(node, seq, id, index, num, + filter, reply_param, data)) < 0) { + pw_resource_errorf(resource, res, + "enum params id:%d (%s) failed", id, + spa_debug_type_find_name(spa_type_param, id)); + } + return 0; +} + +static int node_subscribe_params(void *object, uint32_t *ids, uint32_t n_ids) +{ + struct resource_data *data = object; + struct pw_resource *resource = data->resource; + uint32_t i; + + n_ids = SPA_MIN(n_ids, SPA_N_ELEMENTS(data->subscribe_ids)); + data->n_subscribe_ids = n_ids; + + for (i = 0; i < n_ids; i++) { + data->subscribe_ids[i] = ids[i]; + pw_log_debug("%p: resource %p subscribe param id:%d (%s)", + data->node, resource, ids[i], + spa_debug_type_find_name(spa_type_param, ids[i])); + node_enum_params(data, 1, ids[i], 0, UINT32_MAX, NULL); + } + return 0; +} + +static void remove_busy_resource(struct resource_data *d) +{ + if (d->end != -1) { + spa_hook_remove(&d->listener); + d->end = -1; + pw_impl_client_set_busy(d->resource->client, false); + } +} + +static void result_node_sync(void *data, int seq, int res, uint32_t type, const void *result) +{ + struct resource_data *d = data; + + pw_log_debug("%p: sync result %d %d (%d/%d)", d->node, res, seq, d->seq, d->end); + if (seq == d->end) + remove_busy_resource(d); +} + +static int node_set_param(void *object, uint32_t id, uint32_t flags, + const struct spa_pod *param) +{ + struct resource_data *data = object; + struct pw_resource *resource = data->resource; + struct pw_impl_node *node = data->node; + struct pw_impl_client *client = resource->client; + int res; + static const struct spa_node_events node_events = { + SPA_VERSION_NODE_EVENTS, + .result = result_node_sync, + }; + + pw_log_debug("%p: resource %p set param id:%d (%s) %08x", node, resource, + id, spa_debug_type_find_name(spa_type_param, id), flags); + + res = spa_node_set_param(node->node, id, flags, param); + + if (res < 0) { + pw_resource_errorf(resource, res, + "set param id:%d (%s) flags:%08x failed", id, + spa_debug_type_find_name(spa_type_param, id), flags); + + } else if (SPA_RESULT_IS_ASYNC(res)) { + pw_impl_client_set_busy(client, true); + if (data->end == -1) + spa_node_add_listener(node->node, &data->listener, + &node_events, data); + data->seq = res; + data->end = spa_node_sync(node->node, res); + } + return 0; +} + +static int node_send_command(void *object, const struct spa_command *command) +{ + struct resource_data *data = object; + struct pw_impl_node *node = data->node; + + switch (SPA_NODE_COMMAND_ID(command)) { + case SPA_NODE_COMMAND_Suspend: + suspend_node(node); + break; + default: + spa_node_send_command(node->node, command); + break; + } + return 0; +} + +static const struct pw_node_methods node_methods = { + PW_VERSION_NODE_METHODS, + .subscribe_params = node_subscribe_params, + .enum_params = node_enum_params, + .set_param = node_set_param, + .send_command = node_send_command +}; + +static void resource_destroy(void *data) +{ + struct resource_data *d = data; + remove_busy_resource(d); + spa_hook_remove(&d->resource_listener); + spa_hook_remove(&d->object_listener); +} + +static void resource_pong(void *data, int seq) +{ + struct resource_data *d = data; + struct pw_resource *resource = d->resource; + pw_log_debug("%p: resource %p: got pong %d", d->node, + resource, seq); +} + +static const struct pw_resource_events resource_events = { + PW_VERSION_RESOURCE_EVENTS, + .destroy = resource_destroy, + .pong = resource_pong, +}; + +static int +global_bind(void *object, struct pw_impl_client *client, uint32_t permissions, + uint32_t version, uint32_t id) +{ + struct pw_impl_node *this = object; + struct pw_global *global = this->global; + struct pw_resource *resource; + struct resource_data *data; + + resource = pw_resource_new(client, id, permissions, global->type, version, sizeof(*data)); + if (resource == NULL) + goto error_resource; + + data = pw_resource_get_user_data(resource); + data->node = this; + data->resource = resource; + data->end = -1; + + pw_resource_add_listener(resource, + &data->resource_listener, + &resource_events, data); + pw_resource_add_object_listener(resource, + &data->object_listener, + &node_methods, data); + + pw_log_debug("%p: bound to %d", this, resource->id); + pw_global_add_resource(global, resource); + + this->info.change_mask = PW_NODE_CHANGE_MASK_ALL; + pw_node_resource_info(resource, &this->info); + this->info.change_mask = 0; + + return 0; + +error_resource: + pw_log_error("%p: can't create node resource: %m", this); + return -errno; +} + +static void global_destroy(void *data) +{ + struct pw_impl_node *this = data; + spa_hook_remove(&this->global_listener); + this->global = NULL; + pw_impl_node_destroy(this); +} + +static const struct pw_global_events global_events = { + PW_VERSION_GLOBAL_EVENTS, + .destroy = global_destroy, +}; + +static inline void insert_driver(struct pw_context *context, struct pw_impl_node *node) +{ + struct pw_impl_node *n, *t; + + spa_list_for_each_safe(n, t, &context->driver_list, driver_link) { + if (n->priority_driver < node->priority_driver) + break; + } + spa_list_append(&n->driver_link, &node->driver_link); +} + +static void update_io(struct pw_impl_node *node) +{ + pw_log_debug("%p: id:%d", node, node->info.id); + + if (spa_node_set_io(node->node, + SPA_IO_Position, + &node->rt.activation->position, + sizeof(struct spa_io_position)) >= 0) { + pw_log_debug("%p: set position %p", node, &node->rt.activation->position); + node->rt.position = &node->rt.activation->position; + + node->current_rate = node->rt.position->clock.rate; + node->current_quantum = node->rt.position->clock.duration; + } else if (node->driver) { + pw_log_warn("%p: can't set position on driver", node); + } + if (spa_node_set_io(node->node, + SPA_IO_Clock, + &node->rt.activation->position.clock, + sizeof(struct spa_io_clock)) >= 0) { + pw_log_debug("%p: set clock %p", node, &node->rt.activation->position.clock); + node->rt.clock = &node->rt.activation->position.clock; + } +} + +SPA_EXPORT +int pw_impl_node_register(struct pw_impl_node *this, + struct pw_properties *properties) +{ + static const char * const keys[] = { + PW_KEY_OBJECT_SERIAL, + PW_KEY_OBJECT_PATH, + PW_KEY_MODULE_ID, + PW_KEY_FACTORY_ID, + PW_KEY_CLIENT_ID, + PW_KEY_DEVICE_ID, + PW_KEY_PRIORITY_SESSION, + PW_KEY_PRIORITY_DRIVER, + PW_KEY_APP_NAME, + PW_KEY_NODE_DESCRIPTION, + PW_KEY_NODE_NAME, + PW_KEY_NODE_NICK, + PW_KEY_NODE_SESSION, + PW_KEY_MEDIA_CLASS, + PW_KEY_MEDIA_TYPE, + PW_KEY_MEDIA_CATEGORY, + PW_KEY_MEDIA_ROLE, + NULL + }; + + struct pw_context *context = this->context; + struct pw_impl_port *port; + + pw_log_debug("%p: register", this); + + if (this->registered) + goto error_existed; + + this->global = pw_global_new(context, + PW_TYPE_INTERFACE_Node, + PW_VERSION_NODE, + properties, + global_bind, + this); + if (this->global == NULL) + return -errno; + + spa_list_append(&context->node_list, &this->link); + if (this->driver) + insert_driver(context, this); + this->registered = true; + + this->rt.activation->position.clock.id = this->global->id; + + this->info.id = this->global->id; + pw_properties_setf(this->properties, PW_KEY_OBJECT_ID, "%d", this->info.id); + pw_properties_setf(this->properties, PW_KEY_OBJECT_SERIAL, "%"PRIu64, + pw_global_get_serial(this->global)); + this->info.props = &this->properties->dict; + + pw_global_update_keys(this->global, &this->properties->dict, keys); + + pw_impl_node_initialized(this); + + pw_global_add_listener(this->global, &this->global_listener, &global_events, this); + pw_global_register(this->global); + + if (this->node) + update_io(this); + + spa_list_for_each(port, &this->input_ports, link) + pw_impl_port_register(port, NULL); + spa_list_for_each(port, &this->output_ports, link) + pw_impl_port_register(port, NULL); + + if (this->active) + pw_context_recalc_graph(context, "register active node"); + + return 0; + +error_existed: + pw_properties_free(properties); + return -EEXIST; +} + +SPA_EXPORT +int pw_impl_node_initialized(struct pw_impl_node *this) +{ + pw_log_debug("%p initialized", this); + pw_impl_node_emit_initialized(this); + node_update_state(this, PW_NODE_STATE_SUSPENDED, 0, NULL); + return 0; +} + +static int +do_move_nodes(struct spa_loop *loop, + bool async, uint32_t seq, const void *data, size_t size, void *user_data) +{ + struct impl *impl = user_data; + struct pw_impl_node *driver = *(struct pw_impl_node **)data; + struct pw_impl_node *node = &impl->this; + + pw_log_trace("%p: driver:%p->%p", node, node->driver_node, driver); + + pw_log_trace("%p: set position %p", node, &driver->rt.activation->position); + node->rt.position = &driver->rt.activation->position; + + node->current_rate = node->rt.position->clock.rate; + node->current_quantum = node->rt.position->clock.duration; + + if (node->source.loop != NULL) { + remove_node(node); + add_node(node, driver); + } + return 0; +} + +static void remove_segment_owner(struct pw_impl_node *driver, uint32_t node_id) +{ + struct pw_node_activation *a = driver->rt.activation; + ATOMIC_CAS(a->segment_owner[0], node_id, 0); + ATOMIC_CAS(a->segment_owner[1], node_id, 0); +} + +SPA_EXPORT +int pw_impl_node_set_driver(struct pw_impl_node *node, struct pw_impl_node *driver) +{ + struct impl *impl = SPA_CONTAINER_OF(node, struct impl, this); + struct pw_impl_node *old = node->driver_node; + int res; + bool was_driving; + + if (driver == NULL) + driver = node; + + spa_list_remove(&node->follower_link); + spa_list_append(&driver->follower_list, &node->follower_link); + + if (old == driver) + return 0; + + remove_segment_owner(old, node->info.id); + + if (old != node && old->driving && driver->info.state < PW_NODE_STATE_RUNNING) { + driver->current_rate = old->current_rate; + driver->current_quantum = old->current_quantum; + driver->current_pending = true; + pw_log_info("move quantum:%"PRIu64" rate:%d (%s-%d -> %s-%d)", + driver->current_quantum, + driver->current_rate.denom, + old->name, old->info.id, + driver->name, driver->info.id); + } + was_driving = node->driving; + node->driving = node->driver && driver == node; + + /* When a node was driver (and is waiting for all nodes to complete + * the Start command) cancel the pending state and let the new driver + * calculate a new state so that the Start command is sent to the + * node */ + if (was_driving && !node->driving) + impl->pending_state = node->info.state; + + pw_log_debug("%p: driver %p driving:%u", node, + driver, node->driving); + pw_log_info("(%s-%u) -> change driver (%s-%d -> %s-%d)", + node->name, node->info.id, + old->name, old->info.id, driver->name, driver->info.id); + + node->driver_node = driver; + node->moved = true; + + if ((res = spa_node_set_io(node->node, + SPA_IO_Position, + &driver->rt.activation->position, + sizeof(struct spa_io_position))) < 0) { + pw_log_debug("%p: set position: %s", node, spa_strerror(res)); + } + + pw_loop_invoke(node->data_loop, + do_move_nodes, SPA_ID_INVALID, &driver, sizeof(struct pw_impl_node *), + true, impl); + + pw_impl_node_emit_driver_changed(node, old, driver); + + return 0; +} + +static void check_properties(struct pw_impl_node *node) +{ + struct impl *impl = SPA_CONTAINER_OF(node, struct impl, this); + struct pw_context *context = node->context; + const char *str, *recalc_reason = NULL; + struct spa_fraction frac; + uint32_t value; + bool driver; + + if ((str = pw_properties_get(node->properties, PW_KEY_PRIORITY_DRIVER))) { + node->priority_driver = pw_properties_parse_int(str); + pw_log_debug("%p: priority driver %d", node, node->priority_driver); + } + + if ((str = pw_properties_get(node->properties, PW_KEY_NODE_NAME)) && + (node->name == NULL || !spa_streq(node->name, str))) { + free(node->name); + node->name = strdup(str); + pw_log_debug("%p: name '%s'", node, node->name); + } + + node->pause_on_idle = pw_properties_get_bool(node->properties, PW_KEY_NODE_PAUSE_ON_IDLE, true); + node->suspend_on_idle = pw_properties_get_bool(node->properties, PW_KEY_NODE_SUSPEND_ON_IDLE, false); + node->transport_sync = pw_properties_get_bool(node->properties, PW_KEY_NODE_TRANSPORT_SYNC, false); + impl->cache_params = pw_properties_get_bool(node->properties, PW_KEY_NODE_CACHE_PARAMS, true); + driver = pw_properties_get_bool(node->properties, PW_KEY_NODE_DRIVER, false); + + if (node->driver != driver) { + pw_log_debug("%p: driver %d -> %d", node, node->driver, driver); + node->driver = driver; + if (node->registered) { + if (driver) + insert_driver(context, node); + else + spa_list_remove(&node->driver_link); + } + recalc_reason = "driver changed"; + } + + /* not scheduled automatically so we add an additional required trigger */ + if (pw_properties_get_bool(node->properties, PW_KEY_NODE_TRIGGER, false)) + node->rt.activation->state[0].required++; + + /* group defines what nodes are scheduled together */ + if ((str = pw_properties_get(node->properties, PW_KEY_NODE_GROUP)) == NULL) + str = ""; + + if (!spa_streq(str, node->group)) { + pw_log_info("%p: group '%s'->'%s'", node, node->group, str); + snprintf(node->group, sizeof(node->group), "%s", str); + node->freewheel = spa_streq(node->group, "pipewire.freewheel"); + recalc_reason = "group changed"; + } + + + node->want_driver = pw_properties_get_bool(node->properties, PW_KEY_NODE_WANT_DRIVER, false); + node->always_process = pw_properties_get_bool(node->properties, PW_KEY_NODE_ALWAYS_PROCESS, false); + + if (node->always_process) + node->want_driver = true; + + if ((str = pw_properties_get(node->properties, PW_KEY_NODE_LATENCY))) { + if (sscanf(str, "%u/%u", &frac.num, &frac.denom) == 2 && frac.denom != 0) { + if (node->latency.num != frac.num || node->latency.denom != frac.denom) { + pw_log_info("(%s-%u) latency:%u/%u -> %u/%u", node->name, + node->info.id, node->latency.num, + node->latency.denom, frac.num, frac.denom); + node->latency = frac; + recalc_reason = "quantum changed"; + } + } + } + if ((str = pw_properties_get(node->properties, PW_KEY_NODE_MAX_LATENCY))) { + if (sscanf(str, "%u/%u", &frac.num, &frac.denom) == 2 && frac.denom != 0) { + if (node->max_latency.num != frac.num || node->max_latency.denom != frac.denom) { + pw_log_info("(%s-%u) max-latency:%u/%u -> %u/%u", node->name, + node->info.id, node->max_latency.num, + node->max_latency.denom, frac.num, frac.denom); + node->max_latency = frac; + recalc_reason = "max quantum changed"; + } + } + } + node->lock_quantum = pw_properties_get_bool(node->properties, PW_KEY_NODE_LOCK_QUANTUM, false); + + if ((str = pw_properties_get(node->properties, PW_KEY_NODE_FORCE_QUANTUM))) { + if (spa_atou32(str, &value, 0) && + node->force_quantum != value) { + node->force_quantum = value; + node->stamp = ++context->stamp; + recalc_reason = "force quantum changed"; + } + } + + if ((str = pw_properties_get(node->properties, PW_KEY_NODE_RATE))) { + if (sscanf(str, "%u/%u", &frac.num, &frac.denom) == 2 && frac.denom != 0) { + if (node->rate.num != frac.num || node->rate.denom != frac.denom) { + pw_log_info("(%s-%u) rate:%u/%u -> %u/%u", node->name, + node->info.id, node->rate.num, + node->rate.denom, frac.num, frac.denom); + node->rate = frac; + recalc_reason = "node rate changed"; + } + } + } + node->lock_rate = pw_properties_get_bool(node->properties, PW_KEY_NODE_LOCK_RATE, false); + + if ((str = pw_properties_get(node->properties, PW_KEY_NODE_FORCE_RATE))) { + if (spa_atou32(str, &value, 0) && + node->force_rate != value) { + node->force_rate = value; + node->stamp = ++context->stamp; + recalc_reason = "force rate changed"; + } + } + + pw_log_debug("%p: driver:%d recalc:%s active:%d", node, node->driver, + recalc_reason, node->active); + + if (recalc_reason != NULL && node->active) + pw_context_recalc_graph(context, recalc_reason); +} + +static const char *str_status(uint32_t status) +{ + switch (status) { + case PW_NODE_ACTIVATION_NOT_TRIGGERED: + return "not-triggered"; + case PW_NODE_ACTIVATION_TRIGGERED: + return "triggered"; + case PW_NODE_ACTIVATION_AWAKE: + return "awake"; + case PW_NODE_ACTIVATION_FINISHED: + return "finished"; + } + return "unknown"; +} + +static void dump_states(struct pw_impl_node *driver) +{ + struct pw_node_target *t; + struct pw_node_activation *na = driver->rt.activation; + struct spa_io_clock *cl = &na->position.clock; + + spa_list_for_each(t, &driver->rt.target_list, link) { + struct pw_node_activation *a = t->activation; + struct pw_node_activation_state *state = &a->state[0]; + if (t->node == NULL) + continue; + if (a->status == PW_NODE_ACTIVATION_TRIGGERED || + a->status == PW_NODE_ACTIVATION_AWAKE) { + pw_log_info("(%s-%u) client too slow! rate:%u/%u pos:%"PRIu64" status:%s", + t->node->name, t->node->info.id, + (uint32_t)(cl->rate.num * cl->duration), cl->rate.denom, + cl->position, str_status(a->status)); + } + pw_log_debug("(%s-%u) state:%p pending:%d/%d s:%"PRIu64" a:%"PRIu64" f:%"PRIu64 + " waiting:%"PRIu64" process:%"PRIu64" status:%s sync:%d", + t->node->name, t->node->info.id, state, + state->pending, state->required, + a->signal_time, + a->awake_time, + a->finish_time, + a->awake_time - a->signal_time, + a->finish_time - a->awake_time, + str_status(a->status), a->pending_sync); + } +} + +static inline int resume_node(struct pw_impl_node *this, int status) +{ + struct pw_node_target *t; + struct timespec ts; + struct pw_node_activation *activation = this->rt.activation; + struct spa_system *data_system = this->context->data_system; + uint64_t nsec; + + spa_system_clock_gettime(data_system, CLOCK_MONOTONIC, &ts); + nsec = SPA_TIMESPEC_TO_NSEC(&ts); + activation->status = PW_NODE_ACTIVATION_FINISHED; + activation->finish_time = nsec; + + pw_log_trace_fp("%p: trigger peers %"PRIu64, this, nsec); + + spa_list_for_each(t, &this->rt.target_list, link) { + struct pw_node_activation *a = t->activation; + struct pw_node_activation_state *state = &a->state[0]; + + pw_log_trace_fp("%p: state:%p pending:%d/%d", t->node, state, + state->pending, state->required); + + if (pw_node_activation_state_dec(state, 1)) { + a->status = PW_NODE_ACTIVATION_TRIGGERED; + a->signal_time = nsec; + t->signal_func(t->data); + } + } + return 0; +} + +static inline void calculate_stats(struct pw_impl_node *this, struct pw_node_activation *a) +{ + if (SPA_LIKELY(a->signal_time > a->prev_signal_time)) { + uint64_t process_time = a->finish_time - a->signal_time; + uint64_t period_time = a->signal_time - a->prev_signal_time; + float load = (float) process_time / (float) period_time; + a->cpu_load[0] = (a->cpu_load[0] + load) / 2.0f; + a->cpu_load[1] = (a->cpu_load[1] * 7.0f + load) / 8.0f; + a->cpu_load[2] = (a->cpu_load[2] * 31.0f + load) / 32.0f; + } +} + +static inline int process_node(void *data) +{ + struct pw_impl_node *this = data; + struct timespec ts; + struct pw_impl_port *p; + struct pw_node_activation *a = this->rt.activation; + struct spa_system *data_system = this->context->data_system; + int status; + + spa_system_clock_gettime(data_system, CLOCK_MONOTONIC, &ts); + a->status = PW_NODE_ACTIVATION_AWAKE; + a->awake_time = SPA_TIMESPEC_TO_NSEC(&ts); + + pw_log_trace_fp("%p: process %"PRIu64, this, a->awake_time); + + /* when transport sync is not supported, just clear the flag */ + if (!this->transport_sync) + a->pending_sync = false; + + if (this->added) { + spa_list_for_each(p, &this->rt.input_mix, rt.node_link) + spa_node_process(p->mix); + + status = spa_node_process(this->node); + + if (status & SPA_STATUS_HAVE_DATA) { + spa_list_for_each(p, &this->rt.output_mix, rt.node_link) + spa_node_process(p->mix); + } + } else { + /* This can happen when we deactivated the node but some links are + * still not shut down. We simply don't schedule the node and make + * sure we trigger the peers in resume_node below. */ + pw_log_debug("%p: scheduling non-active node %s", this, this->name); + status = SPA_STATUS_HAVE_DATA; + } + a->state[0].status = status; + + if (SPA_UNLIKELY(this == this->driver_node && !this->exported)) { + spa_system_clock_gettime(data_system, CLOCK_MONOTONIC, &ts); + a->status = PW_NODE_ACTIVATION_FINISHED; + a->signal_time = a->finish_time; + a->finish_time = SPA_TIMESPEC_TO_NSEC(&ts); + + /* calculate CPU time */ + calculate_stats(this, a); + + pw_log_trace_fp("%p: graph completed wait:%"PRIu64" run:%"PRIu64 + " busy:%"PRIu64" period:%"PRIu64" cpu:%f:%f:%f", this, + a->awake_time - a->signal_time, + a->finish_time - a->awake_time, + a->finish_time - a->signal_time, + a->signal_time - a->prev_signal_time, + a->cpu_load[0], a->cpu_load[1], a->cpu_load[2]); + + pw_context_driver_emit_complete(this->context, this); + + } else if (status == SPA_STATUS_OK) { + pw_log_trace_fp("%p: async continue", this); + } else { + resume_node(this, status); + } + if (status & SPA_STATUS_DRAINED) { + pw_context_driver_emit_drained(this->context, this); + } + return 0; +} + +static void node_on_fd_events(struct spa_source *source) +{ + struct pw_impl_node *this = source->data; + struct spa_system *data_system = this->context->data_system; + + if (SPA_UNLIKELY(source->rmask & (SPA_IO_ERR | SPA_IO_HUP))) { + pw_log_warn("%p: got socket error %08x", this, source->rmask); + return; + } + + if (SPA_LIKELY(source->rmask & SPA_IO_IN)) { + uint64_t cmd; + + if (SPA_UNLIKELY(spa_system_eventfd_read(data_system, this->source.fd, &cmd) < 0)) + pw_log_warn("%p: read failed %m", this); + else if (SPA_UNLIKELY(cmd > 1)) + pw_log_info("(%s-%u) client missed %"PRIu64" wakeups", + this->name, this->info.id, cmd - 1); + + pw_log_trace_fp("%p: got process", this); + this->rt.target.signal_func(this->rt.target.data); + } +} + +static void reset_segment(struct spa_io_segment *seg) +{ + spa_zero(*seg); + seg->rate = 1.0; +} + +static void reset_position(struct pw_impl_node *this, struct spa_io_position *pos) +{ + uint32_t i; + struct settings *s = &this->context->settings; + uint32_t quantum = s->clock_force_quantum == 0 ? s->clock_quantum : s->clock_force_quantum; + uint32_t rate = s->clock_force_rate == 0 ? s->clock_rate : s->clock_force_rate; + + this->current_rate = SPA_FRACTION(1, rate); + this->current_quantum = quantum; + + pos->clock.rate = this->current_rate; + pos->clock.duration = this->current_quantum; + pos->video.flags = SPA_IO_VIDEO_SIZE_VALID; + pos->video.size = s->video_size; + pos->video.stride = pos->video.size.width * 16; + pos->video.framerate = s->video_rate; + pos->offset = INT64_MIN; + + pos->n_segments = 1; + for (i = 0; i < SPA_IO_POSITION_MAX_SEGMENTS; i++) + reset_segment(&pos->segments[i]); +} + +SPA_EXPORT +struct pw_impl_node *pw_context_create_node(struct pw_context *context, + struct pw_properties *properties, + size_t user_data_size) +{ + struct impl *impl; + struct pw_impl_node *this; + size_t size; + struct spa_system *data_system = context->data_system; + int res; + + impl = calloc(1, sizeof(struct impl) + user_data_size); + if (impl == NULL) { + res = -errno; + goto error_exit; + } + + spa_list_init(&impl->param_list); + spa_list_init(&impl->pending_list); + + this = &impl->this; + this->context = context; + this->name = strdup("node"); + + if (user_data_size > 0) + this->user_data = SPA_PTROFF(impl, sizeof(struct impl), void); + + if (properties == NULL) + properties = pw_properties_new(NULL, NULL); + if (properties == NULL) { + res = -errno; + goto error_clean; + } + + this->properties = properties; + + if ((res = spa_system_eventfd_create(data_system, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK)) < 0) + goto error_clean; + + pw_log_debug("%p: new fd:%d", this, res); + + this->source.fd = res; + this->source.func = node_on_fd_events; + this->source.data = this; + this->source.mask = SPA_IO_IN | SPA_IO_ERR | SPA_IO_HUP; + this->source.rmask = 0; + + size = sizeof(struct pw_node_activation); + + this->activation = pw_mempool_alloc(this->context->pool, + PW_MEMBLOCK_FLAG_READWRITE | + PW_MEMBLOCK_FLAG_SEAL | + PW_MEMBLOCK_FLAG_MAP, + SPA_DATA_MemFd, size); + if (this->activation == NULL) { + res = -errno; + goto error_clean; + } + + impl->work = pw_context_get_work_queue(this->context); + impl->pending_id = SPA_ID_INVALID; + + this->data_loop = context->data_loop; + + spa_list_init(&this->follower_list); + + spa_hook_list_init(&this->listener_list); + + this->info.state = PW_NODE_STATE_CREATING; + this->info.props = &this->properties->dict; + this->info.params = this->params; + + spa_list_init(&this->input_ports); + pw_map_init(&this->input_port_map, 64, 64); + spa_list_init(&this->output_ports); + pw_map_init(&this->output_port_map, 64, 64); + + spa_list_init(&this->rt.input_mix); + spa_list_init(&this->rt.output_mix); + spa_list_init(&this->rt.target_list); + + this->rt.activation = this->activation->map->ptr; + this->rt.target.activation = this->rt.activation; + this->rt.target.node = this; + this->rt.target.signal_func = process_node; + this->rt.target.data = this; + this->rt.driver_target.signal_func = process_node; + + reset_position(this, &this->rt.activation->position); + this->rt.activation->sync_timeout = DEFAULT_SYNC_TIMEOUT; + this->rt.activation->sync_left = 0; + + this->rt.rate_limit.interval = 2 * SPA_NSEC_PER_SEC; + this->rt.rate_limit.burst = 1; + + check_properties(this); + + this->driver_node = this; + spa_list_append(&this->follower_list, &this->follower_link); + this->driving = true; + + return this; + +error_clean: + if (this->activation) + pw_memblock_unref(this->activation); + if (this->source.fd != -1) + spa_system_close(this->context->data_system, this->source.fd); + free(impl); +error_exit: + pw_properties_free(properties); + errno = -res; + return NULL; +} + +SPA_EXPORT +const struct pw_node_info *pw_impl_node_get_info(struct pw_impl_node *node) +{ + return &node->info; +} + +SPA_EXPORT +void * pw_impl_node_get_user_data(struct pw_impl_node *node) +{ + return node->user_data; +} + +SPA_EXPORT +struct pw_context * pw_impl_node_get_context(struct pw_impl_node *node) +{ + return node->context; +} + +SPA_EXPORT +struct pw_global *pw_impl_node_get_global(struct pw_impl_node *node) +{ + return node->global; +} + +SPA_EXPORT +const struct pw_properties *pw_impl_node_get_properties(struct pw_impl_node *node) +{ + return node->properties; +} + +static int update_properties(struct pw_impl_node *node, const struct spa_dict *dict, bool filter) +{ + static const char * const ignored[] = { + PW_KEY_OBJECT_ID, + PW_KEY_MODULE_ID, + PW_KEY_FACTORY_ID, + PW_KEY_CLIENT_ID, + PW_KEY_DEVICE_ID, + NULL + }; + + int changed; + + changed = pw_properties_update_ignore(node->properties, dict, filter ? ignored : NULL); + node->info.props = &node->properties->dict; + + pw_log_debug("%p: updated %d properties", node, changed); + + if (changed) { + check_properties(node); + node->info.change_mask |= PW_NODE_CHANGE_MASK_PROPS; + } + return changed; +} + +SPA_EXPORT +int pw_impl_node_update_properties(struct pw_impl_node *node, const struct spa_dict *dict) +{ + int changed = update_properties(node, dict, false); + emit_info_changed(node, false); + return changed; +} + +static void node_info(void *data, const struct spa_node_info *info) +{ + struct pw_impl_node *node = data; + uint32_t changed_ids[MAX_PARAMS], n_changed_ids = 0; + bool flags_changed = false; + + node->info.max_input_ports = info->max_input_ports; + node->info.max_output_ports = info->max_output_ports; + + pw_log_debug("%p: flags:%08"PRIx64" change_mask:%08"PRIx64" max_in:%u max_out:%u", + node, info->flags, info->change_mask, info->max_input_ports, + info->max_output_ports); + + if (info->change_mask & SPA_NODE_CHANGE_MASK_FLAGS) { + if (node->spa_flags != info->flags) { + flags_changed = node->spa_flags != 0; + pw_log_debug("%p: flags %"PRIu64"->%"PRIu64, node, node->spa_flags, info->flags); + node->spa_flags = info->flags; + } + } + if (info->change_mask & SPA_NODE_CHANGE_MASK_PROPS) { + update_properties(node, info->props, true); + } + if (info->change_mask & SPA_NODE_CHANGE_MASK_PARAMS) { + uint32_t i; + + node->info.change_mask |= PW_NODE_CHANGE_MASK_PARAMS; + node->info.n_params = SPA_MIN(info->n_params, SPA_N_ELEMENTS(node->params)); + + for (i = 0; i < node->info.n_params; i++) { + uint32_t id = info->params[i].id; + + pw_log_debug("%p: param %d id:%d (%s) %08x:%08x", node, i, + id, spa_debug_type_find_name(spa_type_param, id), + node->info.params[i].flags, info->params[i].flags); + + node->info.params[i].id = info->params[i].id; + if (node->info.params[i].flags == info->params[i].flags) + continue; + + pw_log_debug("%p: update param %d", node, id); + node->info.params[i] = info->params[i]; + node->info.params[i].user = 0; + + if (info->params[i].flags & SPA_PARAM_INFO_READ) + changed_ids[n_changed_ids++] = id; + } + } + emit_info_changed(node, flags_changed); + + if (n_changed_ids > 0) + emit_params(node, changed_ids, n_changed_ids); + + if (flags_changed) + pw_context_recalc_graph(node->context, "node flags changed"); +} + +static void node_port_info(void *data, enum spa_direction direction, uint32_t port_id, + const struct spa_port_info *info) +{ + struct pw_impl_node *node = data; + struct pw_impl_port *port = pw_impl_node_find_port(node, direction, port_id); + + if (info == NULL) { + if (port) { + pw_log_debug("%p: %s port %d removed", node, + pw_direction_as_string(direction), port_id); + pw_impl_port_destroy(port); + } else { + pw_log_warn("%p: %s port %d unknown", node, + pw_direction_as_string(direction), port_id); + } + } else if (port) { + pw_log_debug("%p: %s port %d changed", node, + pw_direction_as_string(direction), port_id); + pw_impl_port_update_info(port, info); + } else { + int res; + + pw_log_debug("%p: %s port %d added", node, + pw_direction_as_string(direction), port_id); + + if ((port = pw_context_create_port(node->context, direction, port_id, info, + node->port_user_data_size))) { + if ((res = pw_impl_port_add(port, node)) < 0) { + pw_log_error("%p: can't add port %p: %d, %s", + node, port, res, spa_strerror(res)); + pw_impl_port_destroy(port); + } + } + } +} + +static void node_result(void *data, int seq, int res, uint32_t type, const void *result) +{ + struct pw_impl_node *node = data; + struct impl *impl = SPA_CONTAINER_OF(node, struct impl, this); + + pw_log_trace("%p: result seq:%d res:%d type:%u", node, seq, res, type); + if (res < 0) + impl->last_error = res; + + if (SPA_RESULT_IS_ASYNC(seq)) + pw_work_queue_complete(impl->work, &impl->this, SPA_RESULT_ASYNC_SEQ(seq), res); + + pw_impl_node_emit_result(node, seq, res, type, result); +} + +static void node_event(void *data, const struct spa_event *event) +{ + struct pw_impl_node *node = data; + struct impl *impl = SPA_CONTAINER_OF(node, struct impl, this); + + pw_log_trace("%p: event %d", node, SPA_EVENT_TYPE(event)); + + switch (SPA_NODE_EVENT_ID(event)) { + case SPA_NODE_EVENT_Error: + impl->last_error = -EFAULT; + node_update_state(node, PW_NODE_STATE_ERROR, + -EFAULT, strdup("Received error event")); + break; + case SPA_NODE_EVENT_RequestProcess: + pw_log_debug("request process"); + if (!node->driving) { + pw_impl_node_send_command(node->driver_node, + &SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_RequestProcess)); + } + break; + default: + pw_log_debug("unhandled event %d", SPA_NODE_EVENT_ID(event)); + break; + } + pw_impl_node_emit_event(node, event); +} + +static const struct spa_node_events node_events = { + SPA_VERSION_NODE_EVENTS, + .info = node_info, + .port_info = node_port_info, + .result = node_result, + .event = node_event, +}; + +#define SYNC_CHECK 0 +#define SYNC_START 1 +#define SYNC_STOP 2 + +static int check_updates(struct pw_impl_node *node, uint32_t *reposition_owner) +{ + int res = SYNC_CHECK; + struct pw_node_activation *a = node->rt.activation; + uint32_t command; + + if (SPA_UNLIKELY(a->position.offset == INT64_MIN)) + a->position.offset = a->position.clock.position; + + command = ATOMIC_XCHG(a->command, PW_NODE_ACTIVATION_COMMAND_NONE); + *reposition_owner = ATOMIC_XCHG(a->reposition_owner, 0); + + if (SPA_UNLIKELY(command != PW_NODE_ACTIVATION_COMMAND_NONE)) { + pw_log_debug("%p: update command:%u", node, command); + switch (command) { + case PW_NODE_ACTIVATION_COMMAND_STOP: + a->position.state = SPA_IO_POSITION_STATE_STOPPED; + res = SYNC_STOP; + break; + case PW_NODE_ACTIVATION_COMMAND_START: + a->position.state = SPA_IO_POSITION_STATE_STARTING; + a->sync_left = a->sync_timeout / + ((a->position.clock.duration * SPA_NSEC_PER_SEC) / + a->position.clock.rate.denom); + res = SYNC_START; + break; + } + } + return res; +} + +static void do_reposition(struct pw_impl_node *driver, struct pw_impl_node *node) +{ + struct pw_node_activation *a = driver->rt.activation; + struct spa_io_segment *dst, *src; + + src = &node->rt.activation->reposition; + dst = &a->position.segments[0]; + + pw_log_info("%p: update position:%"PRIu64, node, src->position); + + dst->version = src->version; + dst->flags = src->flags; + dst->start = src->start; + dst->duration = src->duration; + dst->rate = src->rate; + dst->position = src->position; + if (src->bar.flags & SPA_IO_SEGMENT_BAR_FLAG_VALID) + dst->bar = src->bar; + if (src->video.flags & SPA_IO_SEGMENT_VIDEO_FLAG_VALID) + dst->video = src->video; + + if (dst->start == 0) + dst->start = a->position.clock.position - a->position.offset; + + switch (a->position.state) { + case SPA_IO_POSITION_STATE_RUNNING: + a->position.state = SPA_IO_POSITION_STATE_STARTING; + a->sync_left = a->sync_timeout / + ((a->position.clock.duration * SPA_NSEC_PER_SEC) / + a->position.clock.rate.denom); + break; + } +} + +static void update_position(struct pw_impl_node *node, int all_ready) +{ + struct pw_node_activation *a = node->rt.activation; + + if (a->position.state == SPA_IO_POSITION_STATE_STARTING) { + if (!all_ready && --a->sync_left == 0) { + pw_log_warn("(%s-%u) sync timeout, going to RUNNING", + node->name, node->info.id); + pw_context_driver_emit_timeout(node->context, node); + dump_states(node); + all_ready = true; + } + if (all_ready) + a->position.state = SPA_IO_POSITION_STATE_RUNNING; + } + if (a->position.state != SPA_IO_POSITION_STATE_RUNNING) + a->position.offset += a->position.clock.duration; +} + +static int node_ready(void *data, int status) +{ + struct pw_impl_node *node = data, *reposition_node = NULL; + struct pw_impl_node *driver = node->driver_node; + struct pw_node_target *t; + struct pw_impl_port *p; + + pw_log_trace_fp("%p: ready driver:%d exported:%d %p status:%d added:%d", node, + node->driver, node->exported, driver, status, node->added); + + if (!node->added) { + /* This can happen when we are stopping a node and removed it from the + * graph but we still have not completed the Pause/Suspend command on + * the node. In that case, the node might still emit ready events, + * which we should simply ignore here. */ + pw_log_info("%p: ready non-active node %s in state %d", node, node->name, node->info.state); + return -EIO; + } + + if (SPA_UNLIKELY(node == driver)) { + struct pw_node_activation *a = node->rt.activation; + struct pw_node_activation_state *state = &a->state[0]; + int sync_type, all_ready, update_sync, target_sync; + uint32_t owner[2], reposition_owner; + uint64_t min_timeout = UINT64_MAX; + + if (SPA_UNLIKELY(state->pending > 0)) { + pw_context_driver_emit_incomplete(node->context, node); + if (ratelimit_test(&node->rt.rate_limit, a->signal_time, SPA_LOG_LEVEL_DEBUG)) { + pw_log_debug("(%s-%u) graph not finished: state:%p quantum:%"PRIu64 + " pending %d/%d", node->name, node->info.id, + state, a->position.clock.duration, + state->pending, state->required); + dump_states(node); + } + node->rt.target.signal_func(node->rt.target.data); + } + + if (node->current_pending) { + node->rt.position->clock.duration = node->current_quantum; + node->rt.position->clock.rate = node->current_rate; + node->current_pending = false; + } + + sync_type = check_updates(node, &reposition_owner); + owner[0] = ATOMIC_LOAD(a->segment_owner[0]); + owner[1] = ATOMIC_LOAD(a->segment_owner[1]); +again: + all_ready = sync_type == SYNC_CHECK; + update_sync = !all_ready; + target_sync = sync_type == SYNC_START ? true : false; + + spa_list_for_each(t, &driver->rt.target_list, link) { + struct pw_node_activation *ta = t->activation; + + ta->status = PW_NODE_ACTIVATION_NOT_TRIGGERED; + pw_node_activation_state_reset(&ta->state[0]); + + if (SPA_LIKELY(t->node)) { + uint32_t id = t->node->info.id; + + /* this is the node with reposition info */ + if (SPA_UNLIKELY(id == reposition_owner)) + reposition_node = t->node; + + /* update extra segment info if it is the owner */ + if (SPA_UNLIKELY(id == owner[0])) + a->position.segments[0].bar = ta->segment.bar; + if (SPA_UNLIKELY(id == owner[1])) + a->position.segments[0].video = ta->segment.video; + + min_timeout = SPA_MIN(min_timeout, ta->sync_timeout); + } + + if (SPA_UNLIKELY(update_sync)) { + ta->pending_sync = target_sync; + ta->pending_new_pos = target_sync; + } else { + all_ready &= ta->pending_sync == false; + } + } + a->prev_signal_time = a->signal_time; + a->sync_timeout = SPA_MIN(min_timeout, DEFAULT_SYNC_TIMEOUT); + + if (SPA_UNLIKELY(reposition_node)) { + do_reposition(node, reposition_node); + sync_type = SYNC_START; + reposition_owner = 0; + reposition_node = NULL; + goto again; + } + + update_position(node, all_ready); + + pw_context_driver_emit_start(node->context, node); + } + if (SPA_UNLIKELY(node->driver && !node->driving)) + return 0; + + if (!node->driver) { + struct timespec ts; + struct pw_node_activation *a = node->rt.activation; + struct spa_system *data_system = node->context->data_system; + + spa_system_clock_gettime(data_system, CLOCK_MONOTONIC, &ts); + a->status = PW_NODE_ACTIVATION_AWAKE; + a->signal_time = a->awake_time = SPA_TIMESPEC_TO_NSEC(&ts); + } + + if (status & SPA_STATUS_HAVE_DATA) { + spa_list_for_each(p, &node->rt.output_mix, rt.node_link) + spa_node_process(p->mix); + } + return resume_node(node, status); +} + +static int node_reuse_buffer(void *data, uint32_t port_id, uint32_t buffer_id) +{ + struct pw_impl_node *node = data; + struct pw_impl_port *p; + + spa_list_for_each(p, &node->rt.input_mix, rt.node_link) { + if (p->port_id != port_id) + continue; + spa_node_port_reuse_buffer(p->mix, 0, buffer_id); + break; + } + return 0; +} + +static void update_xrun_stats(struct pw_node_activation *a, uint64_t trigger, uint64_t delay) +{ + a->xrun_count++; + a->xrun_time = trigger; + a->xrun_delay = delay; + a->max_delay = SPA_MAX(a->max_delay, delay); +} + +static int node_xrun(void *data, uint64_t trigger, uint64_t delay, struct spa_pod *info) +{ + struct pw_impl_node *this = data; + struct pw_node_activation *a = this->rt.activation; + struct pw_node_activation *da = this->rt.driver_target.activation; + + update_xrun_stats(a, trigger, delay); + if (da && da != a) + update_xrun_stats(da, trigger, delay); + + if (ratelimit_test(&this->rt.rate_limit, a->signal_time, SPA_LOG_LEVEL_INFO)) { + struct spa_fraction rate; + if (da) { + struct spa_io_clock *cl = &da->position.clock; + rate.num = cl->rate.num * cl->duration; + rate.denom = cl->rate.denom; + } else { + rate = SPA_FRACTION(0,0); + } + pw_log_info("(%s-%d) XRun! rate:%u/%u count:%u time:%"PRIu64 + " delay:%"PRIu64" max:%"PRIu64, + this->name, this->info.id, + rate.num, rate.denom, a->xrun_count, + trigger, delay, a->max_delay); + } + + pw_context_driver_emit_xrun(this->context, this); + + return 0; +} + +static const struct spa_node_callbacks node_callbacks = { + SPA_VERSION_NODE_CALLBACKS, + .ready = node_ready, + .reuse_buffer = node_reuse_buffer, + .xrun = node_xrun, +}; + +SPA_EXPORT +int pw_impl_node_set_implementation(struct pw_impl_node *node, + struct spa_node *spa_node) +{ + int res; + + pw_log_debug("%p: implementation %p", node, spa_node); + + if (node->node) { + pw_log_error("%p: implementation existed %p", node, node->node); + return -EEXIST; + } + + node->node = spa_node; + spa_node_set_callbacks(node->node, &node_callbacks, node); + res = spa_node_add_listener(node->node, &node->listener, &node_events, node); + + if (node->registered) + update_io(node); + + return res; +} + +SPA_EXPORT +struct spa_node *pw_impl_node_get_implementation(struct pw_impl_node *node) +{ + return node->node; +} + +SPA_EXPORT +void pw_impl_node_add_listener(struct pw_impl_node *node, + struct spa_hook *listener, + const struct pw_impl_node_events *events, + void *data) +{ + spa_hook_list_append(&node->listener_list, listener, events, data); +} + +/** Destroy a node + * \param node a node to destroy + * + * Remove \a node. This will stop the transfer on the node and + * free the resources allocated by \a node. + */ +SPA_EXPORT +void pw_impl_node_destroy(struct pw_impl_node *node) +{ + struct impl *impl = SPA_CONTAINER_OF(node, struct impl, this); + struct pw_impl_port *port; + struct pw_impl_node *follower; + struct pw_context *context = node->context; + bool active, had_driver; + + active = node->active; + node->active = false; + + pw_log_debug("%p: destroy", impl); + pw_log_info("(%s-%u) destroy", node->name, node->info.id); + + node_deactivate(node); + + suspend_node(node); + + pw_impl_node_emit_destroy(node); + + pw_log_debug("%p: driver node %p", impl, node->driver_node); + had_driver = node != node->driver_node; + + /* remove ourself as a follower from the driver node */ + spa_list_remove(&node->follower_link); + remove_segment_owner(node->driver_node, node->info.id); + + spa_list_consume(follower, &node->follower_list, follower_link) { + pw_log_debug("%p: reassign follower %p", impl, follower); + pw_impl_node_set_driver(follower, NULL); + } + + if (node->registered) { + spa_list_remove(&node->link); + if (node->driver) + spa_list_remove(&node->driver_link); + } + + if (node->node) { + spa_hook_remove(&node->listener); + spa_node_set_callbacks(node->node, NULL, NULL); + } + + pw_log_debug("%p: destroy ports", node); + spa_list_consume(port, &node->input_ports, link) + pw_impl_port_destroy(port); + spa_list_consume(port, &node->output_ports, link) + pw_impl_port_destroy(port); + + if (node->global) { + spa_hook_remove(&node->global_listener); + pw_global_destroy(node->global); + } + + if (active || had_driver) + pw_context_recalc_graph(context, + "active node destroy"); + + pw_log_debug("%p: free", node); + pw_impl_node_emit_free(node); + + spa_hook_list_clean(&node->listener_list); + + pw_memblock_unref(node->activation); + + pw_param_clear(&impl->param_list, SPA_ID_INVALID); + pw_param_clear(&impl->pending_list, SPA_ID_INVALID); + + pw_map_clear(&node->input_port_map); + pw_map_clear(&node->output_port_map); + + pw_work_queue_cancel(impl->work, node, SPA_ID_INVALID); + + pw_properties_free(node->properties); + + clear_info(node); + + spa_system_close(context->data_system, node->source.fd); + free(impl); +} + +SPA_EXPORT +int pw_impl_node_for_each_port(struct pw_impl_node *node, + enum pw_direction direction, + int (*callback) (void *data, struct pw_impl_port *port), + void *data) +{ + struct spa_list *ports; + struct pw_impl_port *p, *t; + int res; + + if (direction == PW_DIRECTION_INPUT) + ports = &node->input_ports; + else + ports = &node->output_ports; + + spa_list_for_each_safe(p, t, ports, link) + if ((res = callback(data, p)) != 0) + return res; + return 0; +} + +struct result_node_params_data { + struct impl *impl; + void *data; + int (*callback) (void *data, int seq, + uint32_t id, uint32_t index, uint32_t next, + struct spa_pod *param); + int seq; + uint32_t count; + unsigned int cache:1; +}; + +static void result_node_params(void *data, int seq, int res, uint32_t type, const void *result) +{ + struct result_node_params_data *d = data; + struct impl *impl = d->impl; + switch (type) { + case SPA_RESULT_TYPE_NODE_PARAMS: + { + const struct spa_result_node_params *r = result; + if (d->seq == seq) { + d->callback(d->data, seq, r->id, r->index, r->next, r->param); + if (d->cache) { + if (d->count++ == 0) + pw_param_add(&impl->pending_list, seq, r->id, NULL); + pw_param_add(&impl->pending_list, seq, r->id, r->param); + } + } + break; + } + default: + break; + } +} + +SPA_EXPORT +int pw_impl_node_for_each_param(struct pw_impl_node *node, + int seq, uint32_t param_id, + uint32_t index, uint32_t max, + const struct spa_pod *filter, + int (*callback) (void *data, int seq, + uint32_t id, uint32_t index, uint32_t next, + struct spa_pod *param), + void *data) +{ + int res; + struct impl *impl = SPA_CONTAINER_OF(node, struct impl, this); + struct result_node_params_data user_data = { impl, data, callback, seq, 0, false }; + struct spa_hook listener; + struct spa_param_info *pi; + static const struct spa_node_events node_events = { + SPA_VERSION_NODE_EVENTS, + .result = result_node_params, + }; + + pi = pw_param_info_find(node->info.params, node->info.n_params, param_id); + if (pi == NULL) + return -ENOENT; + + if (max == 0) + max = UINT32_MAX; + + pw_log_debug("%p: params id:%d (%s) index:%u max:%u cached:%d", node, param_id, + spa_debug_type_find_name(spa_type_param, param_id), + index, max, pi->user); + + if (pi->user == 1) { + struct pw_param *p; + uint8_t buffer[4096]; + struct spa_pod_dynamic_builder b; + struct spa_result_node_params result; + uint32_t count = 0; + + result.id = param_id; + result.next = 0; + + spa_list_for_each(p, &impl->param_list, link) { + if (p->id != param_id) + continue; + + result.index = result.next++; + if (result.index < index) + continue; + + spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 4096); + + if (spa_pod_filter(&b.b, &result.param, p->param, filter) == 0) { + pw_log_debug("%p: %d param %u", node, seq, result.index); + result_node_params(&user_data, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); + count++; + } + spa_pod_dynamic_builder_clean(&b); + + if (count == max) + break; + } + res = 0; + } else { + user_data.cache = impl->cache_params && + (filter == NULL && index == 0 && max == UINT32_MAX); + + spa_zero(listener); + spa_node_add_listener(node->node, &listener, &node_events, &user_data); + res = spa_node_enum_params(node->node, seq, + param_id, index, max, + filter); + spa_hook_remove(&listener); + + if (user_data.cache) { + pw_param_update(&impl->param_list, &impl->pending_list, 0, NULL); + pi->user = 1; + } + } + return res; +} + +SPA_EXPORT +int pw_impl_node_set_param(struct pw_impl_node *node, + uint32_t id, uint32_t flags, const struct spa_pod *param) +{ + pw_log_debug("%p: set_param id:%d (%s) flags:%08x param:%p", node, id, + spa_debug_type_find_name(spa_type_param, id), flags, param); + return spa_node_set_param(node->node, id, flags, param); +} + +SPA_EXPORT +struct pw_impl_port * +pw_impl_node_find_port(struct pw_impl_node *node, enum pw_direction direction, uint32_t port_id) +{ + struct pw_impl_port *port, *p; + struct pw_map *portmap; + struct spa_list *ports; + + if (direction == PW_DIRECTION_INPUT) { + portmap = &node->input_port_map; + ports = &node->input_ports; + } else { + portmap = &node->output_port_map; + ports = &node->output_ports; + } + + if (port_id != PW_ID_ANY) + port = pw_map_lookup(portmap, port_id); + else { + port = NULL; + /* try to find an unlinked port */ + spa_list_for_each(p, ports, link) { + if (spa_list_is_empty(&p->links)) { + port = p; + break; + } + /* We can use this port if it can multiplex */ + if (SPA_FLAG_IS_SET(p->mix_flags, PW_IMPL_PORT_MIX_FLAG_MULTI)) + port = p; + } + } + pw_log_debug("%p: return %s port %d: %p", node, + pw_direction_as_string(direction), port_id, port); + return port; +} + +SPA_EXPORT +uint32_t pw_impl_node_get_free_port_id(struct pw_impl_node *node, enum pw_direction direction) +{ + uint32_t n_ports, max_ports; + struct pw_map *portmap; + uint32_t port_id; + bool dynamic; + int res; + + if (direction == PW_DIRECTION_INPUT) { + max_ports = node->info.max_input_ports; + n_ports = node->info.n_input_ports; + portmap = &node->input_port_map; + dynamic = SPA_FLAG_IS_SET(node->spa_flags, SPA_NODE_FLAG_IN_DYNAMIC_PORTS); + } else { + max_ports = node->info.max_output_ports; + n_ports = node->info.n_output_ports; + portmap = &node->output_port_map; + dynamic = SPA_FLAG_IS_SET(node->spa_flags, SPA_NODE_FLAG_OUT_DYNAMIC_PORTS); + } + pw_log_debug("%p: direction %s n_ports:%u max_ports:%u", + node, pw_direction_as_string(direction), n_ports, max_ports); + + if (!dynamic || n_ports >= max_ports) { + res = -ENOSPC; + goto error; + } + + port_id = pw_map_insert_new(portmap, NULL); + if (port_id == SPA_ID_INVALID) { + res = -errno; + goto error; + } + + pw_log_debug("%p: free port %d", node, port_id); + + return port_id; + +error: + pw_log_warn("%p: no more port available: %s", node, spa_strerror(res)); + errno = -res; + return SPA_ID_INVALID; +} + +static void on_state_complete(void *obj, void *data, int res, uint32_t seq) +{ + struct pw_impl_node *node = obj; + struct impl *impl = SPA_CONTAINER_OF(node, struct impl, this); + enum pw_node_state state = SPA_PTR_TO_INT(data); + char *error = NULL; + + /* driver nodes added -EBUSY. This is then not an error */ + if (res == -EBUSY) + res = 0; + + impl->pending_id = SPA_ID_INVALID; + impl->pending_play = false; + + pw_log_debug("%p: state complete res:%d seq:%d", node, res, seq); + if (impl->last_error < 0) { + res = impl->last_error; + impl->last_error = 0; + } + if (SPA_RESULT_IS_ERROR(res)) { + if (node->info.state == PW_NODE_STATE_SUSPENDED) { + state = PW_NODE_STATE_SUSPENDED; + res = 0; + } else { + error = spa_aprintf("error changing node state: %s", spa_strerror(res)); + state = PW_NODE_STATE_ERROR; + } + } + node_update_state(node, state, res, error); +} + +/** Set the node state + * \param node a \ref pw_impl_node + * \param state a \ref pw_node_state + * \return 0 on success < 0 on error + * + * Set the state of \a node to \a state. + */ +SPA_EXPORT +int pw_impl_node_set_state(struct pw_impl_node *node, enum pw_node_state state) +{ + int res = 0; + struct impl *impl = SPA_CONTAINER_OF(node, struct impl, this); + enum pw_node_state old = impl->pending_state; + + pw_log_debug("%p: set state (%s) %s -> %s, active %d pause_on_idle:%d", node, + pw_node_state_as_string(node->info.state), + pw_node_state_as_string(old), + pw_node_state_as_string(state), + node->active, + node->pause_on_idle); + + if (old != state) + pw_impl_node_emit_state_request(node, state); + + switch (state) { + case PW_NODE_STATE_CREATING: + return -EIO; + + case PW_NODE_STATE_SUSPENDED: + res = suspend_node(node); + break; + + case PW_NODE_STATE_IDLE: + res = idle_node(node); + break; + + case PW_NODE_STATE_RUNNING: + if (node->active) + res = start_node(node); + break; + + case PW_NODE_STATE_ERROR: + break; + } + if (SPA_RESULT_IS_ERROR(res)) + return res; + + if (SPA_RESULT_IS_ASYNC(res)) { + res = spa_node_sync(node->node, res); + } + + if (old != state) { + if (impl->pending_id != SPA_ID_INVALID) { + pw_log_debug("cancel state from %s to %s to %s", + pw_node_state_as_string(node->info.state), + pw_node_state_as_string(impl->pending_state), + pw_node_state_as_string(state)); + + if (impl->pending_state == PW_NODE_STATE_RUNNING && + state < PW_NODE_STATE_RUNNING && + impl->pending_play) { + impl->pending_play = false; + idle_node(node); + } + pw_work_queue_cancel(impl->work, node, impl->pending_id); + node->info.state = impl->pending_state; + } + /* driver nodes return EBUSY to add a -EBUSY to the work queue. This + * will wait until all previous items in the work queue are + * completed */ + impl->pending_state = state; + impl->pending_id = pw_work_queue_add(impl->work, + node, res == EBUSY ? -EBUSY : res, + on_state_complete, SPA_INT_TO_PTR(state)); + } + return res; +} + +SPA_EXPORT +int pw_impl_node_set_active(struct pw_impl_node *node, bool active) +{ + bool old = node->active; + + if (old != active) { + pw_log_debug("%p: %s registered:%d exported:%d", node, + active ? "activate" : "deactivate", + node->registered, node->exported); + + node->active = active; + pw_impl_node_emit_active_changed(node, active); + + if (node->registered) + pw_context_recalc_graph(node->context, + active ? "node activate" : "node deactivate"); + else if (!active && node->exported) + pw_loop_invoke(node->data_loop, do_node_remove, 1, NULL, 0, true, node); + } + return 0; +} + +SPA_EXPORT +bool pw_impl_node_is_active(struct pw_impl_node *node) +{ + return node->active; +} + +SPA_EXPORT +int pw_impl_node_send_command(struct pw_impl_node *node, const struct spa_command *command) +{ + return spa_node_send_command(node->node, command); +} diff --git a/src/pipewire/impl-node.h b/src/pipewire/impl-node.h new file mode 100644 index 0000000..978a6d2 --- /dev/null +++ b/src/pipewire/impl-node.h @@ -0,0 +1,190 @@ +/* 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. + */ + +#ifndef PIPEWIRE_IMPL_NODE_H +#define PIPEWIRE_IMPL_NODE_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** \defgroup pw_impl_node Node Impl + * + * The node object processes data. The node has a list of + * input and output ports (\ref pw_impl_port) on which it + * will receive and send out buffers respectively. + */ +/** + * \addtogroup pw_impl_node + * \{ + */ +struct pw_impl_node; +struct pw_impl_port; + +#include <spa/node/node.h> +#include <spa/node/event.h> + +#include <pipewire/impl.h> + +/** Node events, listen to them with \ref pw_impl_node_add_listener */ +struct pw_impl_node_events { +#define PW_VERSION_IMPL_NODE_EVENTS 0 + uint32_t version; + + /** the node is destroyed */ + void (*destroy) (void *data); + /** the node is about to be freed */ + void (*free) (void *data); + /** the node is initialized */ + void (*initialized) (void *data); + + /** a port is being initialized on the node */ + void (*port_init) (void *data, struct pw_impl_port *port); + /** a port was added */ + void (*port_added) (void *data, struct pw_impl_port *port); + /** a port was removed */ + void (*port_removed) (void *data, struct pw_impl_port *port); + + /** the node info changed */ + void (*info_changed) (void *data, const struct pw_node_info *info); + /** a port on the node changed info */ + void (*port_info_changed) (void *data, struct pw_impl_port *port, + const struct pw_port_info *info); + /** the node active state changed */ + void (*active_changed) (void *data, bool active); + + /** a new state is requested on the node */ + void (*state_request) (void *data, enum pw_node_state state); + /** the state of the node changed */ + void (*state_changed) (void *data, enum pw_node_state old, + enum pw_node_state state, const char *error); + + /** a result was received */ + void (*result) (void *data, int seq, int res, uint32_t type, const void *result); + + /** an event is emitted */ + void (*event) (void *data, const struct spa_event *event); + + /** the driver of the node changed */ + void (*driver_changed) (void *data, struct pw_impl_node *old, struct pw_impl_node *driver); + + /** a peer was added */ + void (*peer_added) (void *data, struct pw_impl_node *peer); + /** a peer was removed */ + void (*peer_removed) (void *data, struct pw_impl_node *peer); +}; + +/** Create a new node */ +struct pw_impl_node * +pw_context_create_node(struct pw_context *context, /**< the context */ + struct pw_properties *properties, /**< extra properties */ + size_t user_data_size /**< user data size */); + +/** Complete initialization of the node and register */ +int pw_impl_node_register(struct pw_impl_node *node, /**< node to register */ + struct pw_properties *properties /**< extra properties */); + +/** Destroy a node */ +void pw_impl_node_destroy(struct pw_impl_node *node); + +/** Get the node info */ +const struct pw_node_info *pw_impl_node_get_info(struct pw_impl_node *node); + +/** Get node user_data. The size of the memory was given in \ref pw_context_create_node */ +void * pw_impl_node_get_user_data(struct pw_impl_node *node); + +/** Get the context of this node */ +struct pw_context *pw_impl_node_get_context(struct pw_impl_node *node); + +/** Get the global of this node */ +struct pw_global *pw_impl_node_get_global(struct pw_impl_node *node); + +/** Get the node properties */ +const struct pw_properties *pw_impl_node_get_properties(struct pw_impl_node *node); + +/** Update the node properties */ +int pw_impl_node_update_properties(struct pw_impl_node *node, const struct spa_dict *dict); + +/** Set the node implementation */ +int pw_impl_node_set_implementation(struct pw_impl_node *node, struct spa_node *spa_node); + +/** Get the node implementation */ +struct spa_node *pw_impl_node_get_implementation(struct pw_impl_node *node); + +/** Add an event listener */ +void pw_impl_node_add_listener(struct pw_impl_node *node, + struct spa_hook *listener, + const struct pw_impl_node_events *events, + void *data); + +/** Iterate the ports in the given direction. The callback should return + * 0 to fetch the next item, any other value stops the iteration and returns + * the value. When all callbacks return 0, this function returns 0 when all + * items are iterated. */ +int pw_impl_node_for_each_port(struct pw_impl_node *node, + enum pw_direction direction, + int (*callback) (void *data, struct pw_impl_port *port), + void *data); + +int pw_impl_node_for_each_param(struct pw_impl_node *node, + int seq, uint32_t param_id, + uint32_t index, uint32_t max, + const struct spa_pod *filter, + int (*callback) (void *data, int seq, + uint32_t id, uint32_t index, uint32_t next, + struct spa_pod *param), + void *data); + +/** Find the port with direction and port_id or NULL when not found. Passing + * PW_ID_ANY for port_id will return any port, preferably an unlinked one. */ +struct pw_impl_port * +pw_impl_node_find_port(struct pw_impl_node *node, enum pw_direction direction, uint32_t port_id); + +/** Get a free unused port_id from the node */ +uint32_t pw_impl_node_get_free_port_id(struct pw_impl_node *node, enum pw_direction direction); + +int pw_impl_node_initialized(struct pw_impl_node *node); + +/** Set a node active. This will start negotiation with all linked active + * nodes and start data transport */ +int pw_impl_node_set_active(struct pw_impl_node *node, bool active); + +/** Check if a node is active */ +bool pw_impl_node_is_active(struct pw_impl_node *node); + +/** Check if a node is active, Since 0.3.39 */ +int pw_impl_node_send_command(struct pw_impl_node *node, const struct spa_command *command); + +/** Set a param on the node, Since 0.3.65 */ +int pw_impl_node_set_param(struct pw_impl_node *node, + uint32_t id, uint32_t flags, const struct spa_pod *param); +/** + * \} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* PIPEWIRE_IMPL_NODE_H */ diff --git a/src/pipewire/impl-port.c b/src/pipewire/impl-port.c new file mode 100644 index 0000000..ef8fa06 --- /dev/null +++ b/src/pipewire/impl-port.c @@ -0,0 +1,1614 @@ +/* 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 <string.h> +#include <stdlib.h> +#include <errno.h> +#include <float.h> + +#include <spa/pod/parser.h> +#include <spa/param/audio/format-utils.h> +#include <spa/node/utils.h> +#include <spa/utils/names.h> +#include <spa/utils/string.h> +#include <spa/debug/types.h> +#include <spa/pod/filter.h> +#include <spa/pod/dynamic.h> + +#include "pipewire/impl.h" +#include "pipewire/private.h" + +PW_LOG_TOPIC_EXTERN(log_port); +#define PW_LOG_TOPIC_DEFAULT log_port + +/** \cond */ +struct impl { + struct pw_impl_port this; + struct spa_node mix_node; /**< mix node implementation */ + + struct spa_list param_list; + struct spa_list pending_list; + + unsigned int cache_params:1; +}; + +#define pw_port_resource(r,m,v,...) pw_resource_call(r,struct pw_port_events,m,v,__VA_ARGS__) +#define pw_port_resource_info(r,...) pw_port_resource(r,info,0,__VA_ARGS__) +#define pw_port_resource_param(r,...) pw_port_resource(r,param,0,__VA_ARGS__) + +struct resource_data { + struct pw_impl_port *port; + struct pw_resource *resource; + + struct spa_hook resource_listener; + struct spa_hook object_listener; + + uint32_t subscribe_ids[MAX_PARAMS]; + uint32_t n_subscribe_ids; +}; + +/** \endcond */ + +static void emit_info_changed(struct pw_impl_port *port) +{ + struct pw_resource *resource; + + if (port->info.change_mask == 0) + return; + + pw_impl_port_emit_info_changed(port, &port->info); + if (port->node) + pw_impl_node_emit_port_info_changed(port->node, port, &port->info); + + if (port->global) + spa_list_for_each(resource, &port->global->resource_list, link) + pw_port_resource_info(resource, &port->info); + + port->info.change_mask = 0; +} + +static const char *port_state_as_string(enum pw_impl_port_state state) +{ + switch (state) { + case PW_IMPL_PORT_STATE_ERROR: + return "error"; + case PW_IMPL_PORT_STATE_INIT: + return "init"; + case PW_IMPL_PORT_STATE_CONFIGURE: + return "configure"; + case PW_IMPL_PORT_STATE_READY: + return "ready"; + case PW_IMPL_PORT_STATE_PAUSED: + return "paused"; + } + return "invalid-state"; +} + +void pw_impl_port_update_state(struct pw_impl_port *port, enum pw_impl_port_state state, int res, char *error) +{ + enum pw_impl_port_state old = port->state; + + port->state = state; + free((void*)port->error); + port->error = error; + + if (old == state) + return; + + pw_log(state == PW_IMPL_PORT_STATE_ERROR ? + SPA_LOG_LEVEL_ERROR : SPA_LOG_LEVEL_DEBUG, + "%p: state %s -> %s (%s)", port, + port_state_as_string(old), port_state_as_string(state), error); + + pw_impl_port_emit_state_changed(port, old, state, error); + + if (state == PW_IMPL_PORT_STATE_ERROR && port->global) { + struct pw_resource *resource; + spa_list_for_each(resource, &port->global->resource_list, link) + pw_resource_error(resource, res, error); + } +} + +static int tee_process(void *object) +{ + struct impl *impl = object; + struct pw_impl_port *this = &impl->this; + struct pw_impl_port_mix *mix; + struct spa_io_buffers *io = &this->rt.io; + + pw_log_trace_fp("%p: tee input %d %d", this, io->status, io->buffer_id); + spa_list_for_each(mix, &this->rt.mix_list, rt_link) { + pw_log_trace_fp("%p: port %d %p->%p %d", this, + mix->port.port_id, io, mix->io, mix->io->buffer_id); + *mix->io = *io; + } + io->status = SPA_STATUS_NEED_DATA; + + return SPA_STATUS_HAVE_DATA | SPA_STATUS_NEED_DATA; +} + +static int tee_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id) +{ + struct impl *impl = object; + struct pw_impl_port *this = &impl->this; + + pw_log_trace_fp("%p: tee reuse buffer %d %d", this, port_id, buffer_id); + spa_node_port_reuse_buffer(this->node->node, this->port_id, buffer_id); + + return 0; +} + +static const struct spa_node_methods schedule_tee_node = { + SPA_VERSION_NODE_METHODS, + .process = tee_process, + .port_reuse_buffer = tee_reuse_buffer, +}; + +static int schedule_mix_input(void *object) +{ + struct impl *impl = object; + struct pw_impl_port *this = &impl->this; + struct spa_io_buffers *io = &this->rt.io; + struct pw_impl_port_mix *mix; + + if (SPA_UNLIKELY(PW_IMPL_PORT_IS_CONTROL(this))) + return SPA_STATUS_HAVE_DATA | SPA_STATUS_NEED_DATA; + + spa_list_for_each(mix, &this->rt.mix_list, rt_link) { + pw_log_trace_fp("%p: mix input %d %p->%p %d %d", this, + mix->port.port_id, mix->io, io, mix->io->status, mix->io->buffer_id); + *io = *mix->io; + mix->io->status = SPA_STATUS_NEED_DATA; + break; + } + return SPA_STATUS_HAVE_DATA | SPA_STATUS_NEED_DATA; +} + +static int schedule_mix_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id) +{ + struct impl *impl = object; + struct pw_impl_port *this = &impl->this; + struct pw_impl_port_mix *mix; + + spa_list_for_each(mix, &this->rt.mix_list, rt_link) { + pw_log_trace_fp("%p: reuse buffer %d %d", this, port_id, buffer_id); + /* FIXME send reuse buffer to peer */ + break; + } + return 0; +} + +static const struct spa_node_methods schedule_mix_node = { + SPA_VERSION_NODE_METHODS, + .process = schedule_mix_input, + .port_reuse_buffer = schedule_mix_reuse_buffer, +}; + +SPA_EXPORT +int pw_impl_port_init_mix(struct pw_impl_port *port, struct pw_impl_port_mix *mix) +{ + uint32_t port_id; + struct pw_impl_node *node = port->node; + int res = 0; + + port_id = pw_map_insert_new(&port->mix_port_map, mix); + if (port_id == SPA_ID_INVALID) + return -errno; + + if ((res = spa_node_add_port(port->mix, port->direction, port_id, NULL)) < 0 && + res != -ENOTSUP) + goto error_remove_map; + + mix->port.direction = port->direction; + mix->port.port_id = port_id; + mix->p = port; + + if ((res = pw_impl_port_call_init_mix(port, mix)) < 0) + goto error_remove_port; + + /* set the same format on the mixer as on the port if any */ + { + uint32_t idx = 0; + uint8_t buffer[1024]; + struct spa_pod_dynamic_builder b; + struct spa_pod *param; + + spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 4096); + + if (spa_node_port_enum_params_sync(port->mix, + pw_direction_reverse(port->direction), 0, + SPA_PARAM_Format, &idx, NULL, ¶m, &b.b) == 1) { + spa_node_port_set_param(port->mix, + port->direction, port_id, + SPA_PARAM_Format, 0, param); + } + spa_pod_dynamic_builder_clean(&b); + } + + spa_list_append(&port->mix_list, &mix->link); + port->n_mix++; + + pw_log_debug("%p: init mix n_mix:%d %d.%d io:%p: (%s)", port, + port->n_mix, port->port_id, mix->port.port_id, + mix->io, spa_strerror(res)); + + if (port->n_mix == 1) { + pw_log_debug("%p: setting port io", port); + spa_node_port_set_io(node->node, + port->direction, port->port_id, + SPA_IO_Buffers, + &port->rt.io, sizeof(port->rt.io)); + } + return res; + +error_remove_port: + spa_node_remove_port(port->mix, port->direction, port_id); +error_remove_map: + pw_map_remove(&port->mix_port_map, port_id); + return res; +} + +SPA_EXPORT +int pw_impl_port_release_mix(struct pw_impl_port *port, struct pw_impl_port_mix *mix) +{ + int res = 0; + uint32_t port_id = mix->port.port_id; + struct pw_impl_node *node = port->node; + + pw_map_remove(&port->mix_port_map, port_id); + spa_list_remove(&mix->link); + port->n_mix--; + + res = pw_impl_port_call_release_mix(port, mix); + + if ((res = spa_node_remove_port(port->mix, port->direction, port_id)) < 0 && + res != -ENOTSUP) + pw_log_warn("can't remove mix port %d: %s", port_id, spa_strerror(res)); + + pw_log_debug("%p: release mix %d %d.%d", port, + port->n_mix, port->port_id, mix->port.port_id); + + if (port->n_mix == 0) { + pw_log_debug("%p: clearing port io", port); + spa_node_port_set_io(node->node, + port->direction, port->port_id, + SPA_IO_Buffers, + NULL, sizeof(port->rt.io)); + } + return res; +} + +static int update_properties(struct pw_impl_port *port, const struct spa_dict *dict, bool filter) +{ + static const char * const ignored[] = { + PW_KEY_OBJECT_ID, + PW_KEY_PORT_DIRECTION, + PW_KEY_PORT_CONTROL, + PW_KEY_NODE_ID, + PW_KEY_PORT_ID, + NULL + }; + + int changed; + + changed = pw_properties_update_ignore(port->properties, dict, filter ? ignored : NULL); + port->info.props = &port->properties->dict; + + if (changed) { + pw_log_debug("%p: updated %d properties", port, changed); + port->info.change_mask |= PW_PORT_CHANGE_MASK_PROPS; + } + return changed; +} + +static int resource_is_subscribed(struct pw_resource *resource, uint32_t id) +{ + struct resource_data *data = pw_resource_get_user_data(resource); + uint32_t i; + + for (i = 0; i < data->n_subscribe_ids; i++) { + if (data->subscribe_ids[i] == id) + return 1; + } + return 0; +} + +static int notify_param(void *data, int seq, uint32_t id, + uint32_t index, uint32_t next, struct spa_pod *param) +{ + struct pw_impl_port *port = data; + struct pw_resource *resource; + + spa_list_for_each(resource, &port->global->resource_list, link) { + if (!resource_is_subscribed(resource, id)) + continue; + + pw_log_debug("%p: resource %p notify param %d", port, resource, id); + pw_port_resource_param(resource, seq, id, index, next, param); + } + return 0; +} + +static void emit_params(struct pw_impl_port *port, uint32_t *changed_ids, uint32_t n_changed_ids) +{ + uint32_t i; + int res; + + if (port->global == NULL) + return; + + pw_log_debug("%p: emit %d params", port, n_changed_ids); + + for (i = 0; i < n_changed_ids; i++) { + struct pw_resource *resource; + int subscribed = 0; + + pw_log_debug("%p: emit param %d/%d: %d", port, i, n_changed_ids, + changed_ids[i]); + + pw_impl_port_emit_param_changed(port, changed_ids[i]); + + /* first check if anyone is subscribed */ + spa_list_for_each(resource, &port->global->resource_list, link) { + if ((subscribed = resource_is_subscribed(resource, changed_ids[i]))) + break; + } + if (!subscribed) + continue; + + if ((res = pw_impl_port_for_each_param(port, 1, changed_ids[i], 0, UINT32_MAX, + NULL, notify_param, port)) < 0) { + pw_log_error("%p: error %d (%s)", port, res, spa_strerror(res)); + } + } +} + +static int process_latency_param(void *data, int seq, + uint32_t id, uint32_t index, uint32_t next, struct spa_pod *param) +{ + struct pw_impl_port *this = data; + struct spa_latency_info latency; + + if (id != SPA_PARAM_Latency) + return -EINVAL; + + if (spa_latency_parse(param, &latency) < 0) + return 0; + if (spa_latency_info_compare(&this->latency[latency.direction], &latency) == 0) + return 0; + + pw_log_debug("port %p: got %s latency %f-%f %d-%d %"PRIu64"-%"PRIu64, this, + pw_direction_as_string(latency.direction), + latency.min_quantum, latency.max_quantum, + latency.min_rate, latency.max_rate, + latency.min_ns, latency.max_ns); + + this->latency[latency.direction] = latency; + if (latency.direction == this->direction) + pw_impl_port_emit_latency_changed(this); + + return 0; +} + +static void update_info(struct pw_impl_port *port, const struct spa_port_info *info) +{ + uint32_t changed_ids[MAX_PARAMS], n_changed_ids = 0; + + pw_log_debug("%p: %p flags:%08"PRIx64" change_mask:%08"PRIx64, + port, info, info->flags, info->change_mask); + + if (info->change_mask & SPA_PORT_CHANGE_MASK_FLAGS) { + port->spa_flags = info->flags; + } + if (info->change_mask & SPA_PORT_CHANGE_MASK_PROPS) { + if (info->props) { + update_properties(port, info->props, true); + } else { + pw_log_warn("%p: port PROPS update but no properties", port); + } + } + if (info->change_mask & SPA_PORT_CHANGE_MASK_PARAMS) { + uint32_t i; + + port->info.change_mask |= PW_PORT_CHANGE_MASK_PARAMS; + port->info.n_params = SPA_MIN(info->n_params, SPA_N_ELEMENTS(port->params)); + + for (i = 0; i < port->info.n_params; i++) { + uint32_t id = info->params[i].id; + + pw_log_debug("%p: param %d id:%d (%s) %08x:%08x", port, i, + id, spa_debug_type_find_name(spa_type_param, id), + port->info.params[i].flags, info->params[i].flags); + + port->info.params[i].id = info->params[i].id; + if (port->info.params[i].flags == info->params[i].flags) + continue; + + pw_log_debug("%p: update param %d", port, id); + port->info.params[i] = info->params[i]; + port->info.params[i].user = 0; + + if (info->params[i].flags & SPA_PARAM_INFO_READ) + changed_ids[n_changed_ids++] = id; + + switch (id) { + case SPA_PARAM_Latency: + port->have_latency_param = + SPA_FLAG_IS_SET(info->params[i].flags, SPA_PARAM_INFO_WRITE); + if (port->node != NULL) + pw_impl_port_for_each_param(port, 0, id, 0, UINT32_MAX, + NULL, process_latency_param, port); + break; + default: + break; + } + } + } + + if (n_changed_ids > 0) + emit_params(port, changed_ids, n_changed_ids); +} + +SPA_EXPORT +struct pw_impl_port *pw_context_create_port( + struct pw_context *context, + enum pw_direction direction, + uint32_t port_id, + const struct spa_port_info *info, + size_t user_data_size) +{ + struct impl *impl; + struct pw_impl_port *this; + struct pw_properties *properties; + const struct spa_node_methods *mix_methods; + int res; + + impl = calloc(1, sizeof(struct impl) + user_data_size); + if (impl == NULL) + return NULL; + + spa_list_init(&impl->param_list); + spa_list_init(&impl->pending_list); + impl->cache_params = true; + + this = &impl->this; + pw_log_debug("%p: new %s %d", this, + pw_direction_as_string(direction), port_id); + + if (info && info->change_mask & SPA_PORT_CHANGE_MASK_PROPS && info->props) + properties = pw_properties_new_dict(info->props); + else + properties = pw_properties_new(NULL, NULL); + + if (properties == NULL) { + res = -errno; + goto error_no_mem; + } + pw_properties_setf(properties, PW_KEY_PORT_ID, "%u", port_id); + + if (info) { + if (SPA_FLAG_IS_SET(info->flags, SPA_PORT_FLAG_PHYSICAL)) + pw_properties_set(properties, PW_KEY_PORT_PHYSICAL, "true"); + if (SPA_FLAG_IS_SET(info->flags, SPA_PORT_FLAG_TERMINAL)) + pw_properties_set(properties, PW_KEY_PORT_TERMINAL, "true"); + this->spa_flags = info->flags; + } + + this->direction = direction; + this->port_id = port_id; + this->properties = properties; + this->state = PW_IMPL_PORT_STATE_INIT; + this->rt.io = SPA_IO_BUFFERS_INIT; + + if (user_data_size > 0) + this->user_data = SPA_PTROFF(impl, sizeof(struct impl), void); + + this->info.direction = direction; + this->info.params = this->params; + this->info.change_mask = PW_PORT_CHANGE_MASK_PROPS; + this->info.props = &this->properties->dict; + + spa_list_init(&this->links); + spa_list_init(&this->mix_list); + spa_list_init(&this->rt.mix_list); + spa_list_init(&this->control_list[0]); + spa_list_init(&this->control_list[1]); + + spa_hook_list_init(&this->listener_list); + + if (this->direction == PW_DIRECTION_INPUT) + mix_methods = &schedule_mix_node; + else + mix_methods = &schedule_tee_node; + + impl->mix_node.iface = SPA_INTERFACE_INIT( + SPA_TYPE_INTERFACE_Node, + SPA_VERSION_NODE, + mix_methods, impl); + + pw_impl_port_set_mix(this, NULL, 0); + + pw_map_init(&this->mix_port_map, 64, 64); + + this->latency[SPA_DIRECTION_INPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_INPUT); + this->latency[SPA_DIRECTION_OUTPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_OUTPUT); + + if (info) + update_info(this, info); + + return this; + +error_no_mem: + pw_log_warn("%p: new failed", impl); + free(impl); + errno = -res; + return NULL; +} + +SPA_EXPORT +int pw_impl_port_set_mix(struct pw_impl_port *port, struct spa_node *node, uint32_t flags) +{ + struct impl *impl = SPA_CONTAINER_OF(port, struct impl, this); + struct pw_impl_port_mix *mix; + + if (node == NULL) { + node = &impl->mix_node; + flags = 0; + } + + pw_log_debug("%p: mix node %p->%p", port, port->mix, node); + + if (port->mix != NULL && port->mix != node) { + spa_list_for_each(mix, &port->mix_list, link) + spa_node_remove_port(port->mix, mix->port.direction, mix->port.port_id); + + spa_node_port_set_io(port->mix, + pw_direction_reverse(port->direction), 0, + SPA_IO_Buffers, NULL, 0); + } + if (port->mix_handle != NULL) { + pw_unload_spa_handle(port->mix_handle); + port->mix_handle = NULL; + } + + port->mix_flags = flags; + port->mix = node; + + if (port->mix) { + spa_list_for_each(mix, &port->mix_list, link) + spa_node_add_port(port->mix, mix->port.direction, mix->port.port_id, NULL); + + spa_node_port_set_io(port->mix, + pw_direction_reverse(port->direction), 0, + SPA_IO_Buffers, + &port->rt.io, sizeof(port->rt.io)); + } + return 0; +} + +static int setup_mixer(struct pw_impl_port *port, const struct spa_pod *param) +{ + uint32_t media_type, media_subtype; + int res; + const char *fallback_lib, *factory_name; + struct spa_handle *handle; + struct spa_dict_item items[2]; + char quantum_limit[16]; + void *iface; + struct pw_context *context = port->node->context; + + if ((res = spa_format_parse(param, &media_type, &media_subtype)) < 0) + return res; + + pw_log_debug("%p: %s/%s", port, + spa_debug_type_find_name(spa_type_media_type, media_type), + spa_debug_type_find_name(spa_type_media_subtype, media_subtype)); + + switch (media_type) { + case SPA_MEDIA_TYPE_audio: + switch (media_subtype) { + case SPA_MEDIA_SUBTYPE_dsp: + { + struct spa_audio_info_dsp info; + if ((res = spa_format_audio_dsp_parse(param, &info)) < 0) + return res; + + if (info.format != SPA_AUDIO_FORMAT_DSP_F32) + return -ENOTSUP; + + fallback_lib = "audiomixer/libspa-audiomixer"; + factory_name = SPA_NAME_AUDIO_MIXER_DSP; + break; + } + case SPA_MEDIA_SUBTYPE_raw: + fallback_lib = "audiomixer/libspa-audiomixer"; + factory_name = SPA_NAME_AUDIO_MIXER; + break; + default: + return -ENOTSUP; + } + break; + + case SPA_MEDIA_TYPE_application: + switch (media_subtype) { + case SPA_MEDIA_SUBTYPE_control: + fallback_lib = "control/libspa-control"; + factory_name = SPA_NAME_CONTROL_MIXER; + break; + default: + return -ENOTSUP; + } + break; + + default: + return -ENOTSUP; + } + + items[0] = SPA_DICT_ITEM_INIT(SPA_KEY_LIBRARY_NAME, fallback_lib); + spa_scnprintf(quantum_limit, sizeof(quantum_limit), "%u", + context->settings.clock_quantum_limit); + items[1] = SPA_DICT_ITEM_INIT("clock.quantum-limit", quantum_limit); + + handle = pw_context_load_spa_handle(context, factory_name, + &SPA_DICT_INIT_ARRAY(items)); + if (handle == NULL) + return -errno; + + if ((res = spa_handle_get_interface(handle, + SPA_TYPE_INTERFACE_Node, &iface)) < 0) { + pw_unload_spa_handle(handle); + return res; + } + + pw_log_debug("mix node handle:%p iface:%p", handle, iface); + pw_impl_port_set_mix(port, (struct spa_node*)iface, + PW_IMPL_PORT_MIX_FLAG_MULTI | + PW_IMPL_PORT_MIX_FLAG_NEGOTIATE); + port->mix_handle = handle; + + return 0; +} + +SPA_EXPORT +enum pw_direction pw_impl_port_get_direction(struct pw_impl_port *port) +{ + return port->direction; +} + +SPA_EXPORT +uint32_t pw_impl_port_get_id(struct pw_impl_port *port) +{ + return port->port_id; +} + +SPA_EXPORT +const struct pw_properties *pw_impl_port_get_properties(struct pw_impl_port *port) +{ + return port->properties; +} + +SPA_EXPORT +int pw_impl_port_update_properties(struct pw_impl_port *port, const struct spa_dict *dict) +{ + int changed = update_properties(port, dict, false); + emit_info_changed(port); + return changed; +} + +void pw_impl_port_update_info(struct pw_impl_port *port, const struct spa_port_info *info) +{ + update_info(port, info); + emit_info_changed(port); +} + +SPA_EXPORT +struct pw_impl_node *pw_impl_port_get_node(struct pw_impl_port *port) +{ + return port->node; +} + +SPA_EXPORT +void pw_impl_port_add_listener(struct pw_impl_port *port, + struct spa_hook *listener, + const struct pw_impl_port_events *events, + void *data) +{ + spa_hook_list_append(&port->listener_list, listener, events, data); +} + +SPA_EXPORT +const struct pw_port_info *pw_impl_port_get_info(struct pw_impl_port *port) +{ + return &port->info; +} + +SPA_EXPORT +void * pw_impl_port_get_user_data(struct pw_impl_port *port) +{ + return port->user_data; +} + +static int do_add_port(struct spa_loop *loop, + bool async, uint32_t seq, const void *data, size_t size, void *user_data) +{ + struct pw_impl_port *this = user_data; + + pw_log_trace("%p: add port", this); + if (this->direction == PW_DIRECTION_INPUT) + spa_list_append(&this->node->rt.input_mix, &this->rt.node_link); + else + spa_list_append(&this->node->rt.output_mix, &this->rt.node_link); + + return 0; +} + +static int check_param_io(void *data, int seq, uint32_t id, + uint32_t index, uint32_t next, struct spa_pod *param) +{ + struct pw_impl_port *port = data; + struct pw_impl_node *node = port->node; + uint32_t pid, psize; + + if (spa_pod_parse_object(param, + SPA_TYPE_OBJECT_ParamIO, NULL, + SPA_PARAM_IO_id, SPA_POD_Id(&pid), + SPA_PARAM_IO_size, SPA_POD_Int(&psize)) < 0) + return 0; + + pw_log_debug("%p: got io id:%d (%s)", port, pid, + spa_debug_type_find_name(spa_type_io, pid)); + + switch (pid) { + case SPA_IO_Control: + case SPA_IO_Notify: + pw_control_new(node->context, port, pid, psize, 0); + SPA_FLAG_SET(port->flags, PW_IMPL_PORT_FLAG_CONTROL); + break; + case SPA_IO_Buffers: + SPA_FLAG_SET(port->flags, PW_IMPL_PORT_FLAG_BUFFERS); + break; + default: + break; + } + return 0; +} + +static int reply_param(void *data, int seq, uint32_t id, + uint32_t index, uint32_t next, struct spa_pod *param) +{ + struct resource_data *d = data; + struct pw_resource *resource = d->resource; + pw_log_debug("%p: resource %p reply param %u %u %u", d->port, + resource, id, index, next); + pw_port_resource_param(resource, seq, id, index, next, param); + return 0; +} + +static int port_enum_params(void *object, int seq, uint32_t id, uint32_t index, uint32_t num, + const struct spa_pod *filter) +{ + struct resource_data *data = object; + struct pw_resource *resource = data->resource; + struct pw_impl_port *port = data->port; + int res; + + pw_log_debug("%p: resource %p enum params seq:%d id:%d (%s) index:%u num:%u", port, + resource, seq, id, spa_debug_type_find_name(spa_type_param, id), + index, num); + + if ((res = pw_impl_port_for_each_param(port, seq, id, index, num, filter, + reply_param, data)) < 0) + pw_resource_errorf(resource, res, + "enum params id:%d (%s) failed", id, + spa_debug_type_find_name(spa_type_param, id)); + return res; +} + +static int port_subscribe_params(void *object, uint32_t *ids, uint32_t n_ids) +{ + struct resource_data *data = object; + struct pw_resource *resource = data->resource; + uint32_t i; + + n_ids = SPA_MIN(n_ids, SPA_N_ELEMENTS(data->subscribe_ids)); + data->n_subscribe_ids = n_ids; + + for (i = 0; i < n_ids; i++) { + data->subscribe_ids[i] = ids[i]; + pw_log_debug("%p: resource %p subscribe param id:%d (%s)", data->port, + resource, ids[i], + spa_debug_type_find_name(spa_type_param, ids[i])); + port_enum_params(data, 1, ids[i], 0, UINT32_MAX, NULL); + } + return 0; +} + +static const struct pw_port_methods port_methods = { + PW_VERSION_PORT_METHODS, + .subscribe_params = port_subscribe_params, + .enum_params = port_enum_params +}; + +static void resource_destroy(void *data) +{ + struct resource_data *d = data; + spa_hook_remove(&d->resource_listener); + spa_hook_remove(&d->object_listener); +} + +static const struct pw_resource_events resource_events = { + PW_VERSION_RESOURCE_EVENTS, + .destroy = resource_destroy, +}; + +static int +global_bind(void *object, struct pw_impl_client *client, uint32_t permissions, + uint32_t version, uint32_t id) +{ + struct pw_impl_port *this = object; + struct pw_global *global = this->global; + struct pw_resource *resource; + struct resource_data *data; + int res; + + resource = pw_resource_new(client, id, permissions, global->type, version, sizeof(*data)); + if (resource == NULL) { + res = -errno; + goto error_resource; + } + + data = pw_resource_get_user_data(resource); + data->port = this; + data->resource = resource; + + pw_resource_add_listener(resource, + &data->resource_listener, + &resource_events, data); + pw_resource_add_object_listener(resource, + &data->object_listener, + &port_methods, data); + + pw_log_debug("%p: bound to %d", this, resource->id); + pw_global_add_resource(global, resource); + + this->info.change_mask = PW_PORT_CHANGE_MASK_ALL; + pw_port_resource_info(resource, &this->info); + this->info.change_mask = 0; + return 0; + +error_resource: + pw_log_error("%p: can't create port resource: %m", this); + return res; +} + +static void global_destroy(void *data) +{ + struct pw_impl_port *port = data; + spa_hook_remove(&port->global_listener); + port->global = NULL; + pw_impl_port_destroy(port); +} + +static const struct pw_global_events global_events = { + PW_VERSION_GLOBAL_EVENTS, + .destroy = global_destroy, +}; + +int pw_impl_port_register(struct pw_impl_port *port, + struct pw_properties *properties) +{ + static const char * const keys[] = { + PW_KEY_OBJECT_SERIAL, + PW_KEY_OBJECT_PATH, + PW_KEY_FORMAT_DSP, + PW_KEY_NODE_ID, + PW_KEY_AUDIO_CHANNEL, + PW_KEY_PORT_ID, + PW_KEY_PORT_NAME, + PW_KEY_PORT_DIRECTION, + PW_KEY_PORT_MONITOR, + PW_KEY_PORT_PHYSICAL, + PW_KEY_PORT_TERMINAL, + PW_KEY_PORT_CONTROL, + PW_KEY_PORT_ALIAS, + PW_KEY_PORT_EXTRA, + NULL + }; + + struct pw_impl_node *node = port->node; + + if (node == NULL || node->global == NULL) + return -EIO; + + port->global = pw_global_new(node->context, + PW_TYPE_INTERFACE_Port, + PW_VERSION_PORT, + properties, + global_bind, + port); + if (port->global == NULL) + return -errno; + + pw_global_add_listener(port->global, &port->global_listener, &global_events, port); + + port->info.id = port->global->id; + pw_properties_setf(port->properties, PW_KEY_NODE_ID, "%d", node->global->id); + pw_properties_setf(port->properties, PW_KEY_OBJECT_ID, "%d", port->info.id); + pw_properties_setf(port->properties, PW_KEY_OBJECT_SERIAL, "%"PRIu64, + pw_global_get_serial(port->global)); + port->info.props = &port->properties->dict; + + pw_global_update_keys(port->global, &port->properties->dict, keys); + + pw_impl_port_emit_initialized(port); + + return pw_global_register(port->global); +} + +SPA_EXPORT +int pw_impl_port_add(struct pw_impl_port *port, struct pw_impl_node *node) +{ + uint32_t port_id = port->port_id; + struct spa_list *ports; + struct pw_map *portmap; + struct pw_impl_port *find; + bool control; + const char *str, *dir; + int res; + + if (port->node != NULL) + return -EEXIST; + + if (port->direction == PW_DIRECTION_INPUT) { + ports = &node->input_ports; + portmap = &node->input_port_map; + } else { + ports = &node->output_ports; + portmap = &node->output_port_map; + } + + find = pw_map_lookup(portmap, port_id); + if (find != NULL) + return -EEXIST; + + if ((res = pw_map_insert_at(portmap, port_id, port)) < 0) + return res; + + port->node = node; + + pw_impl_node_emit_port_init(node, port); + + pw_impl_port_for_each_param(port, 0, SPA_PARAM_IO, 0, 0, NULL, check_param_io, port); + pw_impl_port_for_each_param(port, 0, SPA_PARAM_Latency, 0, 0, NULL, process_latency_param, port); + + control = PW_IMPL_PORT_IS_CONTROL(port); + if (control) { + dir = port->direction == PW_DIRECTION_INPUT ? "control" : "notify"; + pw_properties_set(port->properties, PW_KEY_PORT_CONTROL, "true"); + } + else { + dir = port->direction == PW_DIRECTION_INPUT ? "in" : "out"; + } + pw_properties_set(port->properties, PW_KEY_PORT_DIRECTION, dir); + + if (pw_properties_get(port->properties, PW_KEY_PORT_NAME) == NULL) { + if ((str = pw_properties_get(port->properties, PW_KEY_AUDIO_CHANNEL)) != NULL && + !spa_streq(str, "UNK")) { + pw_properties_setf(port->properties, PW_KEY_PORT_NAME, "%s_%s", dir, str); + } + else { + pw_properties_setf(port->properties, PW_KEY_PORT_NAME, "%s_%d", dir, port->port_id); + } + } + if (pw_properties_get(port->properties, PW_KEY_PORT_ALIAS) == NULL) { + const struct pw_properties *nprops; + const char *node_name; + + nprops = pw_impl_node_get_properties(node); + if ((node_name = pw_properties_get(nprops, PW_KEY_NODE_NICK)) == NULL && + (node_name = pw_properties_get(nprops, PW_KEY_NODE_DESCRIPTION)) == NULL && + (node_name = pw_properties_get(nprops, PW_KEY_NODE_NAME)) == NULL) + node_name = "node"; + + pw_properties_setf(port->properties, PW_KEY_PORT_ALIAS, "%s:%s", + node_name, + pw_properties_get(port->properties, PW_KEY_PORT_NAME)); + } + + port->info.props = &port->properties->dict; + + if (control) { + pw_log_debug("%p: setting node control", port); + } else { + pw_log_debug("%p: setting mixer io", port); + spa_node_port_set_io(port->mix, + pw_direction_reverse(port->direction), 0, + SPA_IO_Buffers, + &port->rt.io, sizeof(port->rt.io)); + } + + pw_log_debug("%p: %d add to node %p", port, port_id, node); + + spa_list_append(ports, &port->link); + + if (port->direction == PW_DIRECTION_INPUT) { + node->info.n_input_ports++; + node->info.change_mask |= PW_NODE_CHANGE_MASK_INPUT_PORTS; + } else { + node->info.n_output_ports++; + node->info.change_mask |= PW_NODE_CHANGE_MASK_OUTPUT_PORTS; + } + + if (node->global) + pw_impl_port_register(port, NULL); + + if (port->state <= PW_IMPL_PORT_STATE_INIT) + pw_impl_port_update_state(port, PW_IMPL_PORT_STATE_CONFIGURE, 0, NULL); + + pw_impl_node_emit_port_added(node, port); + emit_info_changed(port); + + return 0; +} + +static int do_destroy_link(void *data, struct pw_impl_link *link) +{ + pw_impl_link_destroy(link); + return 0; +} + +void pw_impl_port_unlink(struct pw_impl_port *port) +{ + pw_impl_port_for_each_link(port, do_destroy_link, port); +} + +static int do_remove_port(struct spa_loop *loop, + bool async, uint32_t seq, const void *data, size_t size, void *user_data) +{ + struct pw_impl_port *this = user_data; + + pw_log_trace("%p: remove port", this); + spa_list_remove(&this->rt.node_link); + + return 0; +} + +static void pw_impl_port_remove(struct pw_impl_port *port) +{ + struct pw_impl_node *node = port->node; + int res; + + if (node == NULL) + return; + + pw_log_debug("%p: remove added:%d", port, port->added); + + if (port->added) { + pw_loop_invoke(node->data_loop, do_remove_port, + SPA_ID_INVALID, NULL, 0, true, port); + port->added = false; + } + + if (SPA_FLAG_IS_SET(port->flags, PW_IMPL_PORT_FLAG_TO_REMOVE)) { + if ((res = spa_node_remove_port(node->node, port->direction, port->port_id)) < 0) + pw_log_warn("%p: can't remove: %s", port, spa_strerror(res)); + } + + if (port->direction == PW_DIRECTION_INPUT) { + if ((res = pw_map_insert_at(&node->input_port_map, port->port_id, NULL)) < 0) + pw_log_warn("%p: can't remove input port: %s", port, spa_strerror(res)); + node->info.n_input_ports--; + } else { + if ((res = pw_map_insert_at(&node->output_port_map, port->port_id, NULL)) < 0) + pw_log_warn("%p: can't remove output port: %s", port, spa_strerror(res)); + node->info.n_output_ports--; + } + + pw_impl_port_set_mix(port, NULL, 0); + + spa_list_remove(&port->link); + pw_impl_node_emit_port_removed(node, port); + port->node = NULL; +} + +void pw_impl_port_destroy(struct pw_impl_port *port) +{ + struct impl *impl = SPA_CONTAINER_OF(port, struct impl, this); + struct pw_control *control; + + pw_log_debug("%p: destroy", port); + + port->destroying = true; + pw_impl_port_emit_destroy(port); + + pw_impl_port_unlink(port); + + pw_log_debug("%p: control destroy", port); + spa_list_consume(control, &port->control_list[0], port_link) + pw_control_destroy(control); + spa_list_consume(control, &port->control_list[1], port_link) + pw_control_destroy(control); + + pw_impl_port_remove(port); + + if (port->global) { + spa_hook_remove(&port->global_listener); + pw_global_destroy(port->global); + } + + pw_log_debug("%p: free", port); + pw_impl_port_emit_free(port); + + spa_hook_list_clean(&port->listener_list); + + pw_buffers_clear(&port->buffers); + pw_buffers_clear(&port->mix_buffers); + free((void*)port->error); + + pw_param_clear(&impl->param_list, SPA_ID_INVALID); + pw_param_clear(&impl->pending_list, SPA_ID_INVALID); + + pw_map_clear(&port->mix_port_map); + + pw_properties_free(port->properties); + + free(port); +} + +struct result_port_params_data { + struct impl *impl; + void *data; + int (*callback) (void *data, int seq, + uint32_t id, uint32_t index, uint32_t next, + struct spa_pod *param); + int seq; + uint32_t count; + unsigned int cache:1; +}; + +static void result_port_params(void *data, int seq, int res, uint32_t type, const void *result) +{ + struct result_port_params_data *d = data; + struct impl *impl = d->impl; + switch (type) { + case SPA_RESULT_TYPE_NODE_PARAMS: + { + const struct spa_result_node_params *r = result; + if (d->seq == seq) { + d->callback(d->data, seq, r->id, r->index, r->next, r->param); + if (d->cache) { + if (d->count++ == 0) + pw_param_add(&impl->pending_list, seq, r->id, NULL); + pw_param_add(&impl->pending_list, seq, r->id, r->param); + } + } + break; + } + default: + break; + } +} + +int pw_impl_port_for_each_param(struct pw_impl_port *port, + int seq, + uint32_t param_id, + uint32_t index, uint32_t max, + const struct spa_pod *filter, + int (*callback) (void *data, int seq, + uint32_t id, uint32_t index, uint32_t next, + struct spa_pod *param), + void *data) +{ + int res; + struct impl *impl = SPA_CONTAINER_OF(port, struct impl, this); + struct pw_impl_node *node = port->node; + struct result_port_params_data user_data = { impl, data, callback, seq, 0, false }; + struct spa_hook listener; + struct spa_param_info *pi; + static const struct spa_node_events node_events = { + SPA_VERSION_NODE_EVENTS, + .result = result_port_params, + }; + + pi = pw_param_info_find(port->info.params, port->info.n_params, param_id); + if (pi == NULL) + return -ENOENT; + + if (max == 0) + max = UINT32_MAX; + + pw_log_debug("%p: params id:%d (%s) index:%u max:%u cached:%d", port, param_id, + spa_debug_type_find_name(spa_type_param, param_id), + index, max, pi->user); + + if (pi->user == 1) { + struct pw_param *p; + uint8_t buffer[1024]; + struct spa_pod_dynamic_builder b; + struct spa_result_node_params result; + uint32_t count = 0; + + result.id = param_id; + result.next = 0; + + spa_list_for_each(p, &impl->param_list, link) { + if (p->id != param_id) + continue; + + result.index = result.next++; + if (result.index < index) + continue; + + spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 4096); + + if (spa_pod_filter(&b.b, &result.param, p->param, filter) >= 0) { + pw_log_debug("%p: %d param %u", port, seq, result.index); + result_port_params(&user_data, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); + count++; + } + spa_pod_dynamic_builder_clean(&b); + + if (count == max) + break; + } + res = 0; + } else { + user_data.cache = impl->cache_params && + (filter == NULL && index == 0 && max == UINT32_MAX); + + spa_zero(listener); + spa_node_add_listener(node->node, &listener, &node_events, &user_data); + res = spa_node_port_enum_params(node->node, seq, + port->direction, port->port_id, + param_id, index, max, + filter); + spa_hook_remove(&listener); + + if (user_data.cache) { + pw_param_update(&impl->param_list, &impl->pending_list, 0, NULL); + pi->user = 1; + } + } + + pw_log_debug("%p: res %d: (%s)", port, res, spa_strerror(res)); + return res; +} + +struct param_filter { + struct pw_impl_port *in_port; + struct pw_impl_port *out_port; + int seq; + uint32_t in_param_id; + uint32_t out_param_id; + int (*callback) (void *data, int seq, uint32_t id, uint32_t index, + uint32_t next, struct spa_pod *param); + void *data; + uint32_t n_params; +}; + +static int do_filter(void *data, int seq, uint32_t id, uint32_t index, uint32_t next, struct spa_pod *param) +{ + struct param_filter *f = data; + f->n_params++; + return pw_impl_port_for_each_param(f->out_port, seq, f->out_param_id, 0, 0, param, f->callback, f->data); +} + +int pw_impl_port_for_each_filtered_param(struct pw_impl_port *in_port, + struct pw_impl_port *out_port, + int seq, + uint32_t in_param_id, + uint32_t out_param_id, + const struct spa_pod *filter, + int (*callback) (void *data, int seq, + uint32_t id, uint32_t index, uint32_t next, + struct spa_pod *param), + void *data) +{ + int res; + struct param_filter fd = { in_port, out_port, seq, in_param_id, out_param_id, callback, data, 0 }; + + if ((res = pw_impl_port_for_each_param(in_port, seq, in_param_id, 0, 0, filter, do_filter, &fd)) < 0) + return res; + + if (fd.n_params == 0) + res = do_filter(&fd, seq, 0, 0, 0, NULL); + + return res; +} + +int pw_impl_port_for_each_link(struct pw_impl_port *port, + int (*callback) (void *data, struct pw_impl_link *link), + void *data) +{ + struct pw_impl_link *l, *t; + int res = 0; + + if (port->direction == PW_DIRECTION_OUTPUT) { + spa_list_for_each_safe(l, t, &port->links, output_link) + if ((res = callback(data, l)) != 0) + break; + } else { + spa_list_for_each_safe(l, t, &port->links, input_link) + if ((res = callback(data, l)) != 0) + break; + } + return res; +} + +int pw_impl_port_recalc_latency(struct pw_impl_port *port) +{ + struct pw_impl_link *l; + struct spa_latency_info latency, *current; + struct pw_impl_port *other; + struct spa_pod *param; + struct spa_pod_builder b = { 0 }; + uint8_t buffer[1024]; + bool changed; + + if (port->destroying) + return 0; + + /* given an output port, we calculate the total latency to the sinks or the input + * latency. */ + spa_latency_info_combine_start(&latency, SPA_DIRECTION_REVERSE(port->direction)); + + if (port->direction == PW_DIRECTION_OUTPUT) { + spa_list_for_each(l, &port->links, output_link) { + other = l->input; + spa_latency_info_combine(&latency, &other->latency[other->direction]); + pw_log_debug("port %d: peer %d: latency %f-%f %d-%d %"PRIu64"-%"PRIu64, + port->info.id, other->info.id, + latency.min_quantum, latency.max_quantum, + latency.min_rate, latency.max_rate, + latency.min_ns, latency.max_ns); + } + } else { + spa_list_for_each(l, &port->links, input_link) { + other = l->output; + spa_latency_info_combine(&latency, &other->latency[other->direction]); + pw_log_debug("port %d: peer %d: latency %f-%f %d-%d %"PRIu64"-%"PRIu64, + port->info.id, other->info.id, + latency.min_quantum, latency.max_quantum, + latency.min_rate, latency.max_rate, + latency.min_ns, latency.max_ns); + } + } + spa_latency_info_combine_finish(&latency); + + current = &port->latency[latency.direction]; + + changed = spa_latency_info_compare(current, &latency) != 0; + + pw_log_info("port %d: %s %s latency %f-%f %d-%d %"PRIu64"-%"PRIu64, + port->info.id, changed ? "set" : "keep", + pw_direction_as_string(latency.direction), + latency.min_quantum, latency.max_quantum, + latency.min_rate, latency.max_rate, + latency.min_ns, latency.max_ns); + + if (!changed) + return 0; + + *current = latency; + + if (!port->have_latency_param) + return 0; + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + param = spa_latency_build(&b, SPA_PARAM_Latency, &latency); + return pw_impl_port_set_param(port, SPA_PARAM_Latency, 0, param); +} + +SPA_EXPORT +int pw_impl_port_is_linked(struct pw_impl_port *port) +{ + return spa_list_is_empty(&port->links) ? 0 : 1; +} + +SPA_EXPORT +int pw_impl_port_set_param(struct pw_impl_port *port, uint32_t id, uint32_t flags, + const struct spa_pod *param) +{ + int res; + struct pw_impl_node *node = port->node; + + pw_log_debug("%p: %d set param %d %p", port, port->state, id, param); + + /* set parameter on node */ + res = spa_node_port_set_param(node->node, + port->direction, port->port_id, + id, flags, param); + + pw_log_debug("%p: %d set param on node %d:%d id:%d (%s): %d (%s)", port, port->state, + port->direction, port->port_id, id, + spa_debug_type_find_name(spa_type_param, id), + res, spa_strerror(res)); + + /* set the parameters on all ports of the mixer node if possible */ + if (res >= 0) { + struct pw_impl_port_mix *mix; + + if (port->direction == PW_DIRECTION_INPUT && + id == SPA_PARAM_Format && param != NULL && + !SPA_FLAG_IS_SET(port->flags, PW_IMPL_PORT_FLAG_NO_MIXER)) { + setup_mixer(port, param); + } + + spa_list_for_each(mix, &port->mix_list, link) { + spa_node_port_set_param(port->mix, + mix->port.direction, mix->port.port_id, + id, flags, param); + } + spa_node_port_set_param(port->mix, + pw_direction_reverse(port->direction), 0, + id, flags, param); + } + + if (id == SPA_PARAM_Format) { + pw_log_debug("%p: %d %p %d", port, port->state, param, res); + + if (port->added) { + pw_loop_invoke(node->data_loop, do_remove_port, SPA_ID_INVALID, NULL, 0, true, port); + port->added = false; + } + /* setting the format always destroys the negotiated buffers */ + if (port->direction == PW_DIRECTION_OUTPUT) { + struct pw_impl_link *l; + /* remove all buffers shared with an output port peer */ + spa_list_for_each(l, &port->links, output_link) + pw_impl_port_use_buffers(l->input, &l->rt.in_mix, 0, NULL, 0); + } + pw_buffers_clear(&port->buffers); + pw_buffers_clear(&port->mix_buffers); + + if (param == NULL || res < 0) { + pw_impl_port_update_state(port, PW_IMPL_PORT_STATE_CONFIGURE, 0, NULL); + } + else if (spa_pod_is_fixated(param) <= 0) { + pw_impl_port_update_state(port, PW_IMPL_PORT_STATE_CONFIGURE, 0, NULL); + pw_impl_port_emit_param_changed(port, id); + } + else if (!SPA_RESULT_IS_ASYNC(res)) { + pw_impl_port_update_state(port, PW_IMPL_PORT_STATE_READY, 0, NULL); + } + } + return res; +} + +static int negotiate_mixer_buffers(struct pw_impl_port *port, uint32_t flags, + struct spa_buffer **buffers, uint32_t n_buffers) +{ + int res; + struct pw_impl_node *node = port->node; + + if (SPA_FLAG_IS_SET(port->mix_flags, PW_IMPL_PORT_MIX_FLAG_MIX_ONLY)) + return 0; + + if (SPA_FLAG_IS_SET(port->mix_flags, PW_IMPL_PORT_MIX_FLAG_NEGOTIATE)) { + int alloc_flags; + + /* try dynamic data */ + alloc_flags = PW_BUFFERS_FLAG_DYNAMIC; + + pw_log_debug("%p: %d.%d negotiate %d buffers on node: %p", + port, port->direction, port->port_id, n_buffers, node->node); + + if (port->added) { + pw_loop_invoke(node->data_loop, do_remove_port, SPA_ID_INVALID, NULL, 0, true, port); + port->added = false; + } + + pw_buffers_clear(&port->mix_buffers); + + if (n_buffers > 0) { + if ((res = pw_buffers_negotiate(node->context, alloc_flags, + port->mix, 0, + node->node, port->port_id, + &port->mix_buffers)) < 0) { + pw_log_warn("%p: can't negotiate buffers: %s", + port, spa_strerror(res)); + return res; + } + buffers = port->mix_buffers.buffers; + n_buffers = port->mix_buffers.n_buffers; + flags = 0; + } + } + + pw_log_debug("%p: %d.%d use %d buffers on node: %p", + port, port->direction, port->port_id, n_buffers, node->node); + + res = spa_node_port_use_buffers(node->node, + port->direction, port->port_id, + flags, buffers, n_buffers); + + if (SPA_RESULT_IS_OK(res)) { + spa_node_port_use_buffers(port->mix, + pw_direction_reverse(port->direction), 0, + 0, buffers, n_buffers); + } + if (!port->added && n_buffers > 0) { + pw_loop_invoke(node->data_loop, do_add_port, SPA_ID_INVALID, NULL, 0, false, port); + port->added = true; + } + return res; +} + + +SPA_EXPORT +int pw_impl_port_use_buffers(struct pw_impl_port *port, struct pw_impl_port_mix *mix, uint32_t flags, + struct spa_buffer **buffers, uint32_t n_buffers) +{ + int res = 0, res2; + + pw_log_debug("%p: %d:%d.%d: %d buffers flags:%d state:%d n_mix:%d", port, + port->direction, port->port_id, mix->id, + n_buffers, flags, port->state, port->n_mix); + + if (n_buffers == 0 && port->state <= PW_IMPL_PORT_STATE_READY) + return 0; + + if (n_buffers > 0 && port->state < PW_IMPL_PORT_STATE_READY) + return -EIO; + + if (n_buffers == 0) { + mix->have_buffers = false; + if (port->n_mix == 1) + pw_impl_port_update_state(port, PW_IMPL_PORT_STATE_READY, 0, NULL); + } + + /* first negotiate with the node, this makes it possible to let the + * node allocate buffer memory if needed */ + if (port->state == PW_IMPL_PORT_STATE_READY) { + res = negotiate_mixer_buffers(port, flags, buffers, n_buffers); + + if (res < 0) { + pw_log_error("%p: negotiate buffers on node: %d (%s)", + port, res, spa_strerror(res)); + pw_impl_port_update_state(port, PW_IMPL_PORT_STATE_ERROR, res, + strdup("can't negotiate buffers on port")); + } else if (n_buffers > 0 && !SPA_RESULT_IS_ASYNC(res)) { + pw_impl_port_update_state(port, PW_IMPL_PORT_STATE_PAUSED, 0, NULL); + } + } + + /* then use the buffers on the mixer */ + if (!SPA_FLAG_IS_SET(port->mix_flags, PW_IMPL_PORT_MIX_FLAG_MIX_ONLY)) + flags &= ~SPA_NODE_BUFFERS_FLAG_ALLOC; + + res2 = spa_node_port_use_buffers(port->mix, + mix->port.direction, mix->port.port_id, flags, + buffers, n_buffers); + if (res2 < 0) { + if (res2 != -ENOTSUP && n_buffers > 0) { + pw_log_warn("%p: mix use buffers failed: %d (%s)", + port, res2, spa_strerror(res2)); + return res2; + } + } + else if (SPA_RESULT_IS_ASYNC(res2)) + res = res2; + + return res; +} diff --git a/src/pipewire/impl-port.h b/src/pipewire/impl-port.h new file mode 100644 index 0000000..c275104 --- /dev/null +++ b/src/pipewire/impl-port.h @@ -0,0 +1,144 @@ +/* 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. + */ + +#ifndef PIPEWIRE_IMPL_PORT_H +#define PIPEWIRE_IMPL_PORT_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <spa/utils/hook.h> + +/** \defgroup pw_impl_port Port Impl + * + * \brief A port can be used to link two nodes. + */ + +/** + * \addtogroup pw_impl_port + * \{ + */ +struct pw_impl_port; +struct pw_impl_link; +struct pw_control; + +#include <pipewire/impl.h> + +enum pw_impl_port_state { + PW_IMPL_PORT_STATE_ERROR = -1, /**< the port is in error */ + PW_IMPL_PORT_STATE_INIT = 0, /**< the port is being created */ + PW_IMPL_PORT_STATE_CONFIGURE = 1, /**< the port is ready for format negotiation */ + PW_IMPL_PORT_STATE_READY = 2, /**< the port is ready for buffer allocation */ + PW_IMPL_PORT_STATE_PAUSED = 3, /**< the port is paused */ +}; + +/** Port events, use \ref pw_impl_port_add_listener */ +struct pw_impl_port_events { +#define PW_VERSION_IMPL_PORT_EVENTS 2 + uint32_t version; + + /** The port is destroyed */ + void (*destroy) (void *data); + + /** The port is freed */ + void (*free) (void *data); + + /** The port is initialized */ + void (*initialized) (void *data); + + /** the port info changed */ + void (*info_changed) (void *data, const struct pw_port_info *info); + + /** a new link is added on this port */ + void (*link_added) (void *data, struct pw_impl_link *link); + + /** a link is removed from this port */ + void (*link_removed) (void *data, struct pw_impl_link *link); + + /** the state of the port changed */ + void (*state_changed) (void *data, enum pw_impl_port_state old, + enum pw_impl_port_state state, const char *error); + + /** a control was added to the port */ + void (*control_added) (void *data, struct pw_control *control); + + /** a control was removed from the port */ + void (*control_removed) (void *data, struct pw_control *control); + + /** a parameter changed, since version 1 */ + void (*param_changed) (void *data, uint32_t id); + + /** latency changed. Since version 2 */ + void (*latency_changed) (void *data); +}; + +/** Create a new port + * \return a newly allocated port */ +struct pw_impl_port * +pw_context_create_port(struct pw_context *context, + enum pw_direction direction, + uint32_t port_id, + const struct spa_port_info *info, + size_t user_data_size); + +/** Get the port direction */ +enum pw_direction pw_impl_port_get_direction(struct pw_impl_port *port); + +/** Get the port properties */ +const struct pw_properties *pw_impl_port_get_properties(struct pw_impl_port *port); + +/** Update the port properties */ +int pw_impl_port_update_properties(struct pw_impl_port *port, const struct spa_dict *dict); + +/** Get the port info */ +const struct pw_port_info *pw_impl_port_get_info(struct pw_impl_port *port); + +/** Get the port id */ +uint32_t pw_impl_port_get_id(struct pw_impl_port *port); + +/** Get the port parent node or NULL when not yet set */ +struct pw_impl_node *pw_impl_port_get_node(struct pw_impl_port *port); + +/** check is a port has links, return 0 if not, 1 if it is linked */ +int pw_impl_port_is_linked(struct pw_impl_port *port); + +/** Add a port to a node */ +int pw_impl_port_add(struct pw_impl_port *port, struct pw_impl_node *node); + +/** Add an event listener on the port */ +void pw_impl_port_add_listener(struct pw_impl_port *port, + struct spa_hook *listener, + const struct pw_impl_port_events *events, + void *data); + +/** + * \} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* PIPEWIRE_IMPL_PORT_H */ diff --git a/src/pipewire/impl.h b/src/pipewire/impl.h new file mode 100644 index 0000000..8caa673 --- /dev/null +++ b/src/pipewire/impl.h @@ -0,0 +1,62 @@ +/* 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. + */ + +#ifndef PIPEWIRE_IMPL_H +#define PIPEWIRE_IMPL_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * \addtogroup api_pw_impl + */ + +struct pw_impl_client; +struct pw_impl_module; +struct pw_global; +struct pw_node; +struct pw_impl_port; +struct pw_resource; + +#include <pipewire/pipewire.h> +#include <pipewire/control.h> +#include <pipewire/impl-core.h> +#include <pipewire/impl-client.h> +#include <pipewire/impl-device.h> +#include <pipewire/impl-factory.h> +#include <pipewire/global.h> +#include <pipewire/impl-link.h> +#include <pipewire/impl-metadata.h> +#include <pipewire/impl-module.h> +#include <pipewire/impl-node.h> +#include <pipewire/impl-port.h> +#include <pipewire/resource.h> +#include <pipewire/work-queue.h> + +#ifdef __cplusplus +} +#endif + +#endif /* PIPEWIRE_IMPL_H */ diff --git a/src/pipewire/introspect.c b/src/pipewire/introspect.c new file mode 100644 index 0000000..e52ef28 --- /dev/null +++ b/src/pipewire/introspect.c @@ -0,0 +1,590 @@ +/* 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 <string.h> + +#include <spa/pod/builder.h> + +#include "pipewire/pipewire.h" + +#include "pipewire/core.h" + +SPA_EXPORT +const char *pw_node_state_as_string(enum pw_node_state state) +{ + switch (state) { + case PW_NODE_STATE_ERROR: + return "error"; + case PW_NODE_STATE_CREATING: + return "creating"; + case PW_NODE_STATE_SUSPENDED: + return "suspended"; + case PW_NODE_STATE_IDLE: + return "idle"; + case PW_NODE_STATE_RUNNING: + return "running"; + } + return "invalid-state"; +} + +SPA_EXPORT +const char *pw_direction_as_string(enum pw_direction direction) +{ + switch (direction) { + case PW_DIRECTION_INPUT: + return "input"; + case PW_DIRECTION_OUTPUT: + return "output"; + } + return "invalid"; +} + +SPA_EXPORT +const char *pw_link_state_as_string(enum pw_link_state state) +{ + switch (state) { + case PW_LINK_STATE_ERROR: + return "error"; + case PW_LINK_STATE_UNLINKED: + return "unlinked"; + case PW_LINK_STATE_INIT: + return "init"; + case PW_LINK_STATE_NEGOTIATING: + return "negotiating"; + case PW_LINK_STATE_ALLOCATING: + return "allocating"; + case PW_LINK_STATE_PAUSED: + return "paused"; + case PW_LINK_STATE_ACTIVE: + return "active"; + } + return "invalid-state"; +} + +static void pw_spa_dict_destroy(struct spa_dict *dict) +{ + const struct spa_dict_item *item; + + spa_dict_for_each(item, dict) { + free((void *) item->key); + free((void *) item->value); + } + free((void*)dict->items); + free(dict); +} + +static struct spa_dict *pw_spa_dict_copy(struct spa_dict *dict) +{ + struct spa_dict *copy; + struct spa_dict_item *items; + uint32_t i; + + if (dict == NULL) + return NULL; + + copy = calloc(1, sizeof(struct spa_dict)); + if (copy == NULL) + goto no_mem; + copy->items = items = calloc(dict->n_items, sizeof(struct spa_dict_item)); + if (copy->items == NULL) + goto no_items; + copy->n_items = dict->n_items; + + for (i = 0; i < dict->n_items; i++) { + items[i].key = strdup(dict->items[i].key); + items[i].value = dict->items[i].value ? strdup(dict->items[i].value) : NULL; + } + return copy; + + no_items: + free(copy); + no_mem: + return NULL; +} + +SPA_EXPORT +struct pw_core_info *pw_core_info_merge(struct pw_core_info *info, + const struct pw_core_info *update, bool reset) +{ + if (update == NULL) + return info; + + if (info == NULL) { + info = calloc(1, sizeof(struct pw_core_info)); + if (info == NULL) + return NULL; + + info->id = update->id; + info->cookie = update->cookie; + info->user_name = update->user_name ? strdup(update->user_name) : NULL; + info->host_name = update->host_name ? strdup(update->host_name) : NULL; + info->version = update->version ? strdup(update->version) : NULL; + info->name = update->name ? strdup(update->name) : NULL; + } + if (reset) + info->change_mask = 0; + info->change_mask |= update->change_mask; + + if (update->change_mask & PW_CORE_CHANGE_MASK_PROPS) { + if (info->props) + pw_spa_dict_destroy(info->props); + info->props = pw_spa_dict_copy(update->props); + } + return info; +} + +SPA_EXPORT +struct pw_core_info *pw_core_info_update(struct pw_core_info *info, + const struct pw_core_info *update) +{ + return pw_core_info_merge(info, update, true); +} + +SPA_EXPORT +void pw_core_info_free(struct pw_core_info *info) +{ + free((void *) info->user_name); + free((void *) info->host_name); + free((void *) info->version); + free((void *) info->name); + if (info->props) + pw_spa_dict_destroy(info->props); + free(info); +} + +SPA_EXPORT +struct pw_node_info *pw_node_info_merge(struct pw_node_info *info, + const struct pw_node_info *update, bool reset) +{ + if (update == NULL) + return info; + + if (info == NULL) { + info = calloc(1, sizeof(struct pw_node_info)); + if (info == NULL) + return NULL; + + info->id = update->id; + info->max_input_ports = update->max_input_ports; + info->max_output_ports = update->max_output_ports; + } + if (reset) + info->change_mask = 0; + info->change_mask |= update->change_mask; + + if (update->change_mask & PW_NODE_CHANGE_MASK_INPUT_PORTS) { + info->n_input_ports = update->n_input_ports; + } + if (update->change_mask & PW_NODE_CHANGE_MASK_OUTPUT_PORTS) { + info->n_output_ports = update->n_output_ports; + } + if (update->change_mask & PW_NODE_CHANGE_MASK_STATE) { + info->state = update->state; + free((void *) info->error); + info->error = update->error ? strdup(update->error) : NULL; + } + if (update->change_mask & PW_NODE_CHANGE_MASK_PROPS) { + if (info->props) + pw_spa_dict_destroy(info->props); + info->props = pw_spa_dict_copy(update->props); + } + if (update->change_mask & PW_NODE_CHANGE_MASK_PARAMS) { + uint32_t i, n_params = update->n_params; + void *np; + + np = pw_reallocarray(info->params, n_params, sizeof(struct spa_param_info)); + if (np == NULL) { + free(info->params); + info->params = NULL; + info->n_params = n_params = 0; + } + info->params = np; + + for (i = 0; i < SPA_MIN(info->n_params, n_params); i++) { + info->params[i].id = update->params[i].id; + if (reset) + info->params[i].user = 0; + if (info->params[i].flags != update->params[i].flags) { + info->params[i].flags = update->params[i].flags; + info->params[i].user++; + } + } + info->n_params = n_params; + for (; i < info->n_params; i++) { + info->params[i].id = update->params[i].id; + info->params[i].flags = update->params[i].flags; + info->params[i].user = 1; + } + } + return info; +} + +SPA_EXPORT +struct pw_node_info *pw_node_info_update(struct pw_node_info *info, + const struct pw_node_info *update) +{ + return pw_node_info_merge(info, update, true); +} + +SPA_EXPORT +void pw_node_info_free(struct pw_node_info *info) +{ + free((void *) info->error); + if (info->props) + pw_spa_dict_destroy(info->props); + free((void *) info->params); + free(info); +} + +SPA_EXPORT +struct pw_port_info *pw_port_info_merge(struct pw_port_info *info, + const struct pw_port_info *update, bool reset) +{ + + if (update == NULL) + return info; + + if (info == NULL) { + info = calloc(1, sizeof(struct pw_port_info)); + if (info == NULL) + return NULL; + + info->id = update->id; + info->direction = update->direction; + } + if (reset) + info->change_mask = 0; + info->change_mask |= update->change_mask; + + if (update->change_mask & PW_PORT_CHANGE_MASK_PROPS) { + if (info->props) + pw_spa_dict_destroy(info->props); + info->props = pw_spa_dict_copy(update->props); + } + if (update->change_mask & PW_PORT_CHANGE_MASK_PARAMS) { + uint32_t i, n_params = update->n_params; + void *np; + + np = pw_reallocarray(info->params, n_params, sizeof(struct spa_param_info)); + if (np == NULL) { + free(info->params); + info->params = NULL; + info->n_params = n_params = 0; + } + info->params = np; + + for (i = 0; i < SPA_MIN(info->n_params, n_params); i++) { + info->params[i].id = update->params[i].id; + if (reset) + info->params[i].user = 0; + if (info->params[i].flags != update->params[i].flags) { + info->params[i].flags = update->params[i].flags; + info->params[i].user++; + } + } + info->n_params = n_params; + for (; i < info->n_params; i++) { + info->params[i].id = update->params[i].id; + info->params[i].flags = update->params[i].flags; + info->params[i].user = 1; + } + } + return info; +} + +SPA_EXPORT +struct pw_port_info *pw_port_info_update(struct pw_port_info *info, + const struct pw_port_info *update) +{ + return pw_port_info_merge(info, update, true); +} + +SPA_EXPORT +void pw_port_info_free(struct pw_port_info *info) +{ + if (info->props) + pw_spa_dict_destroy(info->props); + free((void *) info->params); + free(info); +} + +SPA_EXPORT +struct pw_factory_info *pw_factory_info_merge(struct pw_factory_info *info, + const struct pw_factory_info *update, bool reset) +{ + if (update == NULL) + return info; + + if (info == NULL) { + info = calloc(1, sizeof(struct pw_factory_info)); + if (info == NULL) + return NULL; + + info->id = update->id; + info->name = update->name ? strdup(update->name) : NULL; + info->type = update->type ? strdup(update->type) : NULL; + info->version = update->version; + } + if (reset) + info->change_mask = 0; + info->change_mask |= update->change_mask; + + if (update->change_mask & PW_FACTORY_CHANGE_MASK_PROPS) { + if (info->props) + pw_spa_dict_destroy(info->props); + info->props = pw_spa_dict_copy(update->props); + } + return info; +} + +SPA_EXPORT +struct pw_factory_info *pw_factory_info_update(struct pw_factory_info *info, + const struct pw_factory_info *update) +{ + return pw_factory_info_merge(info, update, true); +} + +SPA_EXPORT +void pw_factory_info_free(struct pw_factory_info *info) +{ + free((void *) info->name); + free((void *) info->type); + if (info->props) + pw_spa_dict_destroy(info->props); + free(info); +} + +SPA_EXPORT +struct pw_module_info *pw_module_info_merge(struct pw_module_info *info, + const struct pw_module_info *update, bool reset) +{ + if (update == NULL) + return info; + + if (info == NULL) { + info = calloc(1, sizeof(struct pw_module_info)); + if (info == NULL) + return NULL; + + info->id = update->id; + info->name = update->name ? strdup(update->name) : NULL; + info->filename = update->filename ? strdup(update->filename) : NULL; + info->args = update->args ? strdup(update->args) : NULL; + } + if (reset) + info->change_mask = 0; + info->change_mask |= update->change_mask; + + if (update->change_mask & PW_MODULE_CHANGE_MASK_PROPS) { + if (info->props) + pw_spa_dict_destroy(info->props); + info->props = pw_spa_dict_copy(update->props); + } + return info; +} + +SPA_EXPORT +struct pw_module_info *pw_module_info_update(struct pw_module_info *info, + const struct pw_module_info *update) +{ + return pw_module_info_merge(info, update, true); +} + +SPA_EXPORT +void pw_module_info_free(struct pw_module_info *info) +{ + free((void *) info->name); + free((void *) info->filename); + free((void *) info->args); + if (info->props) + pw_spa_dict_destroy(info->props); + free(info); +} + +SPA_EXPORT +struct pw_device_info *pw_device_info_merge(struct pw_device_info *info, + const struct pw_device_info *update, bool reset) +{ + if (update == NULL) + return info; + + if (info == NULL) { + info = calloc(1, sizeof(struct pw_device_info)); + if (info == NULL) + return NULL; + + info->id = update->id; + } + if (reset) + info->change_mask = 0; + info->change_mask |= update->change_mask; + + if (update->change_mask & PW_DEVICE_CHANGE_MASK_PROPS) { + if (info->props) + pw_spa_dict_destroy(info->props); + info->props = pw_spa_dict_copy(update->props); + } + if (update->change_mask & PW_DEVICE_CHANGE_MASK_PARAMS) { + uint32_t i, n_params = update->n_params; + void *np; + + np = pw_reallocarray(info->params, n_params, sizeof(struct spa_param_info)); + if (np == NULL) { + free(info->params); + info->params = NULL; + info->n_params = n_params = 0; + } + info->params = np; + + for (i = 0; i < SPA_MIN(info->n_params, n_params); i++) { + info->params[i].id = update->params[i].id; + if (reset) + info->params[i].user = 0; + if (info->params[i].flags != update->params[i].flags) { + info->params[i].flags = update->params[i].flags; + info->params[i].user++; + } + } + info->n_params = n_params; + for (; i < info->n_params; i++) { + info->params[i].id = update->params[i].id; + info->params[i].flags = update->params[i].flags; + info->params[i].user = 1; + } + } + return info; +} + +SPA_EXPORT +struct pw_device_info *pw_device_info_update(struct pw_device_info *info, + const struct pw_device_info *update) +{ + return pw_device_info_merge(info, update, true); +} + +SPA_EXPORT +void pw_device_info_free(struct pw_device_info *info) +{ + if (info->props) + pw_spa_dict_destroy(info->props); + free((void *) info->params); + free(info); +} + +SPA_EXPORT +struct pw_client_info *pw_client_info_merge(struct pw_client_info *info, + const struct pw_client_info *update, bool reset) +{ + if (update == NULL) + return info; + + if (info == NULL) { + info = calloc(1, sizeof(struct pw_client_info)); + if (info == NULL) + return NULL; + + info->id = update->id; + } + if (reset) + info->change_mask = 0; + info->change_mask |= update->change_mask; + + if (update->change_mask & PW_CLIENT_CHANGE_MASK_PROPS) { + if (info->props) + pw_spa_dict_destroy(info->props); + info->props = pw_spa_dict_copy(update->props); + } + return info; +} + +SPA_EXPORT +struct pw_client_info *pw_client_info_update(struct pw_client_info *info, + const struct pw_client_info *update) +{ + return pw_client_info_merge(info, update, true); +} + +SPA_EXPORT +void pw_client_info_free(struct pw_client_info *info) +{ + if (info->props) + pw_spa_dict_destroy(info->props); + free(info); +} + +SPA_EXPORT +struct pw_link_info *pw_link_info_merge(struct pw_link_info *info, + const struct pw_link_info *update, bool reset) +{ + if (update == NULL) + return info; + + if (info == NULL) { + info = calloc(1, sizeof(struct pw_link_info)); + if (info == NULL) + return NULL; + + info->id = update->id; + info->output_node_id = update->output_node_id; + info->output_port_id = update->output_port_id; + info->input_node_id = update->input_node_id; + info->input_port_id = update->input_port_id; + } + if (reset) + info->change_mask = 0; + info->change_mask |= update->change_mask; + + if (update->change_mask & PW_LINK_CHANGE_MASK_STATE) { + info->state = update->state; + free((void *) info->error); + info->error = update->error ? strdup(update->error) : NULL; + } + if (update->change_mask & PW_LINK_CHANGE_MASK_FORMAT) { + free(info->format); + info->format = update->format ? spa_pod_copy(update->format) : NULL; + } + if (update->change_mask & PW_LINK_CHANGE_MASK_PROPS) { + if (info->props) + pw_spa_dict_destroy(info->props); + info->props = pw_spa_dict_copy(update->props); + } + return info; +} + +SPA_EXPORT +struct pw_link_info *pw_link_info_update(struct pw_link_info *info, + const struct pw_link_info *update) +{ + return pw_link_info_merge(info, update, true); +} + +SPA_EXPORT +void pw_link_info_free(struct pw_link_info *info) +{ + free((void *) info->error); + free(info->format); + if (info->props) + pw_spa_dict_destroy(info->props); + free(info); +} diff --git a/src/pipewire/keys.h b/src/pipewire/keys.h new file mode 100644 index 0000000..b0faf61 --- /dev/null +++ b/src/pipewire/keys.h @@ -0,0 +1,356 @@ +/* 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. + */ + +#ifndef PIPEWIRE_KEYS_H +#define PIPEWIRE_KEYS_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <pipewire/utils.h> +/** + * \defgroup pw_keys Key Names + * + * A collection of keys that are used to add extra information on objects. + * + * Keys that start with "pipewire." are in general set-once and then + * read-only. They are usually used for security sensitive information that + * needs to be fixed. + * + * Properties from other objects can also appear. This usually suggests some + * sort of parent/child or owner/owned relationship. + * + * \addtogroup pw_keys + * \{ + */ +#define PW_KEY_PROTOCOL "pipewire.protocol" /**< protocol used for connection */ +#define PW_KEY_ACCESS "pipewire.access" /**< how the client access is controlled */ +#define PW_KEY_CLIENT_ACCESS "pipewire.client.access"/**< how the client wants to be access + * controlled */ + +/** Various keys related to the identity of a client process and its security. + * Must be obtained from trusted sources by the protocol and placed as + * read-only properties. */ +#define PW_KEY_SEC_PID "pipewire.sec.pid" /**< Client pid, set by protocol */ +#define PW_KEY_SEC_UID "pipewire.sec.uid" /**< Client uid, set by protocol*/ +#define PW_KEY_SEC_GID "pipewire.sec.gid" /**< client gid, set by protocol*/ +#define PW_KEY_SEC_LABEL "pipewire.sec.label" /**< client security label, set by protocol*/ + +#define PW_KEY_LIBRARY_NAME_SYSTEM "library.name.system" /**< name of the system library to use */ +#define PW_KEY_LIBRARY_NAME_LOOP "library.name.loop" /**< name of the loop library to use */ +#define PW_KEY_LIBRARY_NAME_DBUS "library.name.dbus" /**< name of the dbus library to use */ + +/** object properties */ +#define PW_KEY_OBJECT_PATH "object.path" /**< unique path to construct the object */ +#define PW_KEY_OBJECT_ID "object.id" /**< a global object id */ +#define PW_KEY_OBJECT_SERIAL "object.serial" /**< a 64 bit object serial number. This is a number + * incremented for each object that is created. + * The lower 32 bits are guaranteed to never be + * SPA_ID_INVALID. */ +#define PW_KEY_OBJECT_LINGER "object.linger" /**< the object lives on even after the client + * that created it has been destroyed */ +#define PW_KEY_OBJECT_REGISTER "object.register" /**< If the object should be registered. */ + + +/* config */ +#define PW_KEY_CONFIG_PREFIX "config.prefix" /**< a config prefix directory */ +#define PW_KEY_CONFIG_NAME "config.name" /**< a config file name */ +#define PW_KEY_CONFIG_OVERRIDE_PREFIX "config.override.prefix" /**< a config override prefix directory */ +#define PW_KEY_CONFIG_OVERRIDE_NAME "config.override.name" /**< a config override file name */ + +/* context */ +#define PW_KEY_CONTEXT_PROFILE_MODULES "context.profile.modules" /**< a context profile for modules, deprecated */ +#define PW_KEY_USER_NAME "context.user-name" /**< The user name that runs pipewire */ +#define PW_KEY_HOST_NAME "context.host-name" /**< The host name of the machine */ + +/* core */ +#define PW_KEY_CORE_NAME "core.name" /**< The name of the core. Default is + * `pipewire-<username>-<pid>`, overwritten + * by env(PIPEWIRE_CORE) */ +#define PW_KEY_CORE_VERSION "core.version" /**< The version of the core. */ +#define PW_KEY_CORE_DAEMON "core.daemon" /**< If the core is listening for connections. */ + +#define PW_KEY_CORE_ID "core.id" /**< the core id */ +#define PW_KEY_CORE_MONITORS "core.monitors" /**< the apis monitored by core. */ + +/* cpu */ +#define PW_KEY_CPU_MAX_ALIGN "cpu.max-align" /**< maximum alignment needed to support + * all CPU optimizations */ +#define PW_KEY_CPU_CORES "cpu.cores" /**< number of cores */ + +/* priorities */ +#define PW_KEY_PRIORITY_SESSION "priority.session" /**< priority in session manager */ +#define PW_KEY_PRIORITY_DRIVER "priority.driver" /**< priority to be a driver */ + +/* remote keys */ +#define PW_KEY_REMOTE_NAME "remote.name" /**< The name of the remote to connect to, + * default pipewire-0, overwritten by + * env(PIPEWIRE_REMOTE) */ +#define PW_KEY_REMOTE_INTENTION "remote.intention" /**< The intention of the remote connection, + * "generic", "screencast" */ + +/** application keys */ +#define PW_KEY_APP_NAME "application.name" /**< application name. Ex: "Totem Music Player" */ +#define PW_KEY_APP_ID "application.id" /**< a textual id for identifying an + * application logically. Ex: "org.gnome.Totem" */ +#define PW_KEY_APP_VERSION "application.version" /**< application version. Ex: "1.2.0" */ +#define PW_KEY_APP_ICON "application.icon" /**< aa base64 blob with PNG image data */ +#define PW_KEY_APP_ICON_NAME "application.icon-name" /**< an XDG icon name for the application. + * Ex: "totem" */ +#define PW_KEY_APP_LANGUAGE "application.language" /**< application language if applicable, in + * standard POSIX format. Ex: "en_GB" */ + +#define PW_KEY_APP_PROCESS_ID "application.process.id" /**< process id (pid)*/ +#define PW_KEY_APP_PROCESS_BINARY "application.process.binary" /**< binary name */ +#define PW_KEY_APP_PROCESS_USER "application.process.user" /**< user name */ +#define PW_KEY_APP_PROCESS_HOST "application.process.host" /**< host name */ +#define PW_KEY_APP_PROCESS_MACHINE_ID "application.process.machine-id" /**< the D-Bus host id the + * application runs on */ +#define PW_KEY_APP_PROCESS_SESSION_ID "application.process.session-id" /**< login session of the + * application, on Unix the + * value of $XDG_SESSION_ID. */ +/** window system */ +#define PW_KEY_WINDOW_X11_DISPLAY "window.x11.display" /**< the X11 display string. Ex. ":0.0" */ + +/** Client properties */ +#define PW_KEY_CLIENT_ID "client.id" /**< a client id */ +#define PW_KEY_CLIENT_NAME "client.name" /**< the client name */ +#define PW_KEY_CLIENT_API "client.api" /**< the client api used to access + * PipeWire */ + +/** Node keys */ +#define PW_KEY_NODE_ID "node.id" /**< node id */ +#define PW_KEY_NODE_NAME "node.name" /**< node name */ +#define PW_KEY_NODE_NICK "node.nick" /**< short node name */ +#define PW_KEY_NODE_DESCRIPTION "node.description" /**< localized human readable node one-line + * description. Ex. "Foobar USB Headset" */ +#define PW_KEY_NODE_PLUGGED "node.plugged" /**< when the node was created. As a uint64 in + * nanoseconds. */ + +#define PW_KEY_NODE_SESSION "node.session" /**< the session id this node is part of */ +#define PW_KEY_NODE_GROUP "node.group" /**< the group id this node is part of. Nodes + * in the same group are always scheduled + * with the same driver. */ +#define PW_KEY_NODE_EXCLUSIVE "node.exclusive" /**< node wants exclusive access to resources */ +#define PW_KEY_NODE_AUTOCONNECT "node.autoconnect" /**< node wants to be automatically connected + * to a compatible node */ +#define PW_KEY_NODE_LATENCY "node.latency" /**< the requested latency of the node as + * a fraction. Ex: 128/48000 */ +#define PW_KEY_NODE_MAX_LATENCY "node.max-latency" /**< the maximum supported latency of the + * node as a fraction. Ex: 1024/48000 */ +#define PW_KEY_NODE_LOCK_QUANTUM "node.lock-quantum" /**< don't change quantum when this node + * is active */ +#define PW_KEY_NODE_FORCE_QUANTUM "node.force-quantum" /**< force a quantum while the node is + * active */ +#define PW_KEY_NODE_RATE "node.rate" /**< the requested rate of the graph as + * a fraction. Ex: 1/48000 */ +#define PW_KEY_NODE_LOCK_RATE "node.lock-rate" /**< don't change rate when this node + * is active */ +#define PW_KEY_NODE_FORCE_RATE "node.force-rate" /**< force a rate while the node is + * active */ + +#define PW_KEY_NODE_DONT_RECONNECT "node.dont-reconnect" /**< don't reconnect this node. The node is + * initially linked to target.object or the + * default node. If the target is removed, + * the node is destroyed */ +#define PW_KEY_NODE_ALWAYS_PROCESS "node.always-process" /**< process even when unlinked */ +#define PW_KEY_NODE_WANT_DRIVER "node.want-driver" /**< the node wants to be grouped with a driver + * node in order to schedule the graph. */ +#define PW_KEY_NODE_PAUSE_ON_IDLE "node.pause-on-idle" /**< pause the node when idle */ +#define PW_KEY_NODE_SUSPEND_ON_IDLE "node.suspend-on-idle" /**< suspend the node when idle */ +#define PW_KEY_NODE_CACHE_PARAMS "node.cache-params" /**< cache the node params */ +#define PW_KEY_NODE_TRANSPORT_SYNC "node.transport.sync" /**< the node handles transport sync */ +#define PW_KEY_NODE_DRIVER "node.driver" /**< node can drive the graph */ +#define PW_KEY_NODE_STREAM "node.stream" /**< node is a stream, the server side should + * add a converter */ +#define PW_KEY_NODE_VIRTUAL "node.virtual" /**< the node is some sort of virtual + * object */ +#define PW_KEY_NODE_PASSIVE "node.passive" /**< indicate that a node wants passive links + * on output/input/all ports when the value is + * "out"/"in"/"true" respectively */ +#define PW_KEY_NODE_LINK_GROUP "node.link-group" /**< the node is internally linked to + * nodes with the same link-group */ +#define PW_KEY_NODE_NETWORK "node.network" /**< the node is on a network */ +#define PW_KEY_NODE_TRIGGER "node.trigger" /**< the node is not scheduled automatically + * based on the dependencies in the graph + * but it will be triggered explicitly. */ +#define PW_KEY_NODE_CHANNELNAMES "node.channel-names" /**< names of node's + * channels (unrelated to positions) */ +#define PW_KEY_NODE_DEVICE_PORT_NAME_PREFIX "node.device-port-name-prefix" /** override + * port name prefix for device ports, like capture and playback + * or disable the prefix completely if an empty string is provided */ + +/** Port keys */ +#define PW_KEY_PORT_ID "port.id" /**< port id */ +#define PW_KEY_PORT_NAME "port.name" /**< port name */ +#define PW_KEY_PORT_DIRECTION "port.direction" /**< the port direction, one of "in" or "out" + * or "control" and "notify" for control ports */ +#define PW_KEY_PORT_ALIAS "port.alias" /**< port alias */ +#define PW_KEY_PORT_PHYSICAL "port.physical" /**< if this is a physical port */ +#define PW_KEY_PORT_TERMINAL "port.terminal" /**< if this port consumes the data */ +#define PW_KEY_PORT_CONTROL "port.control" /**< if this port is a control port */ +#define PW_KEY_PORT_MONITOR "port.monitor" /**< if this port is a monitor port */ +#define PW_KEY_PORT_CACHE_PARAMS "port.cache-params" /**< cache the node port params */ +#define PW_KEY_PORT_EXTRA "port.extra" /**< api specific extra port info, API name + * should be prefixed. "jack:flags:56" */ + +/** link properties */ +#define PW_KEY_LINK_ID "link.id" /**< a link id */ +#define PW_KEY_LINK_INPUT_NODE "link.input.node" /**< input node id of a link */ +#define PW_KEY_LINK_INPUT_PORT "link.input.port" /**< input port id of a link */ +#define PW_KEY_LINK_OUTPUT_NODE "link.output.node" /**< output node id of a link */ +#define PW_KEY_LINK_OUTPUT_PORT "link.output.port" /**< output port id of a link */ +#define PW_KEY_LINK_PASSIVE "link.passive" /**< indicate that a link is passive and + * does not cause the graph to be + * runnable. */ +#define PW_KEY_LINK_FEEDBACK "link.feedback" /**< indicate that a link is a feedback + * link and the target will receive data + * in the next cycle */ + +/** device properties */ +#define PW_KEY_DEVICE_ID "device.id" /**< device id */ +#define PW_KEY_DEVICE_NAME "device.name" /**< device name */ +#define PW_KEY_DEVICE_PLUGGED "device.plugged" /**< when the device was created. As a uint64 in + * nanoseconds. */ +#define PW_KEY_DEVICE_NICK "device.nick" /**< a short device nickname */ +#define PW_KEY_DEVICE_STRING "device.string" /**< device string in the underlying layer's + * format. Ex. "surround51:0" */ +#define PW_KEY_DEVICE_API "device.api" /**< API this device is accessed with. + * Ex. "alsa", "v4l2" */ +#define PW_KEY_DEVICE_DESCRIPTION "device.description" /**< localized human readable device one-line + * description. Ex. "Foobar USB Headset" */ +#define PW_KEY_DEVICE_BUS_PATH "device.bus-path" /**< bus path to the device in the OS' + * format. Ex. "pci-0000:00:14.0-usb-0:3.2:1.0" */ +#define PW_KEY_DEVICE_SERIAL "device.serial" /**< Serial number if applicable */ +#define PW_KEY_DEVICE_VENDOR_ID "device.vendor.id" /**< vendor ID if applicable */ +#define PW_KEY_DEVICE_VENDOR_NAME "device.vendor.name" /**< vendor name if applicable */ +#define PW_KEY_DEVICE_PRODUCT_ID "device.product.id" /**< product ID if applicable */ +#define PW_KEY_DEVICE_PRODUCT_NAME "device.product.name" /**< product name if applicable */ +#define PW_KEY_DEVICE_CLASS "device.class" /**< device class */ +#define PW_KEY_DEVICE_FORM_FACTOR "device.form-factor" /**< form factor if applicable. One of + * "internal", "speaker", "handset", "tv", + * "webcam", "microphone", "headset", + * "headphone", "hands-free", "car", "hifi", + * "computer", "portable" */ +#define PW_KEY_DEVICE_BUS "device.bus" /**< bus of the device if applicable. One of + * "isa", "pci", "usb", "firewire", + * "bluetooth" */ +#define PW_KEY_DEVICE_SUBSYSTEM "device.subsystem" /**< device subsystem */ +#define PW_KEY_DEVICE_SYSFS_PATH "device.sysfs.path" /**< device sysfs path */ +#define PW_KEY_DEVICE_ICON "device.icon" /**< icon for the device. A base64 blob + * containing PNG image data */ +#define PW_KEY_DEVICE_ICON_NAME "device.icon-name" /**< an XDG icon name for the device. + * Ex. "sound-card-speakers-usb" */ +#define PW_KEY_DEVICE_INTENDED_ROLES "device.intended-roles" /**< intended use. A space separated list of + * roles (see PW_KEY_MEDIA_ROLE) this device + * is particularly well suited for, due to + * latency, quality or form factor. */ +#define PW_KEY_DEVICE_CACHE_PARAMS "device.cache-params" /**< cache the device spa params */ + +/** module properties */ +#define PW_KEY_MODULE_ID "module.id" /**< the module id */ +#define PW_KEY_MODULE_NAME "module.name" /**< the name of the module */ +#define PW_KEY_MODULE_AUTHOR "module.author" /**< the author's name */ +#define PW_KEY_MODULE_DESCRIPTION "module.description" /**< a human readable one-line description + * of the module's purpose.*/ +#define PW_KEY_MODULE_USAGE "module.usage" /**< a human readable usage description of + * the module's arguments. */ +#define PW_KEY_MODULE_VERSION "module.version" /**< a version string for the module. */ + +/** Factory properties */ +#define PW_KEY_FACTORY_ID "factory.id" /**< the factory id */ +#define PW_KEY_FACTORY_NAME "factory.name" /**< the name of the factory */ +#define PW_KEY_FACTORY_USAGE "factory.usage" /**< the usage of the factory */ +#define PW_KEY_FACTORY_TYPE_NAME "factory.type.name" /**< the name of the type created by a factory */ +#define PW_KEY_FACTORY_TYPE_VERSION "factory.type.version" /**< the version of the type created by a factory */ + +/** Stream properties */ +#define PW_KEY_STREAM_IS_LIVE "stream.is-live" /**< Indicates that the stream is live. */ +#define PW_KEY_STREAM_LATENCY_MIN "stream.latency.min" /**< The minimum latency of the stream. */ +#define PW_KEY_STREAM_LATENCY_MAX "stream.latency.max" /**< The maximum latency of the stream */ +#define PW_KEY_STREAM_MONITOR "stream.monitor" /**< Indicates that the stream is monitoring + * and might select a less accurate but faster + * conversion algorithm. */ +#define PW_KEY_STREAM_DONT_REMIX "stream.dont-remix" /**< don't remix channels */ +#define PW_KEY_STREAM_CAPTURE_SINK "stream.capture.sink" /**< Try to capture the sink output instead of + * source output */ + +/** Media */ +#define PW_KEY_MEDIA_TYPE "media.type" /**< Media type, one of + * Audio, Video, Midi */ +#define PW_KEY_MEDIA_CATEGORY "media.category" /**< Media Category: + * Playback, Capture, Duplex, Monitor, Manager */ +#define PW_KEY_MEDIA_ROLE "media.role" /**< Role: Movie, Music, Camera, + * Screen, Communication, Game, + * Notification, DSP, Production, + * Accessibility, Test */ +#define PW_KEY_MEDIA_CLASS "media.class" /**< class Ex: "Video/Source" */ +#define PW_KEY_MEDIA_NAME "media.name" /**< media name. Ex: "Pink Floyd: Time" */ +#define PW_KEY_MEDIA_TITLE "media.title" /**< title. Ex: "Time" */ +#define PW_KEY_MEDIA_ARTIST "media.artist" /**< artist. Ex: "Pink Floyd" */ +#define PW_KEY_MEDIA_COPYRIGHT "media.copyright" /**< copyright string */ +#define PW_KEY_MEDIA_SOFTWARE "media.software" /**< generator software */ +#define PW_KEY_MEDIA_LANGUAGE "media.language" /**< language in POSIX format. Ex: en_GB */ +#define PW_KEY_MEDIA_FILENAME "media.filename" /**< filename */ +#define PW_KEY_MEDIA_ICON "media.icon" /**< icon for the media, a base64 blob with + * PNG image data */ +#define PW_KEY_MEDIA_ICON_NAME "media.icon-name" /**< an XDG icon name for the media. + * Ex: "audio-x-mp3" */ +#define PW_KEY_MEDIA_COMMENT "media.comment" /**< extra comment */ +#define PW_KEY_MEDIA_DATE "media.date" /**< date of the media */ +#define PW_KEY_MEDIA_FORMAT "media.format" /**< format of the media */ + +/** format related properties */ +#define PW_KEY_FORMAT_DSP "format.dsp" /**< a dsp format. + * Ex: "32 bit float mono audio" */ +/** audio related properties */ +#define PW_KEY_AUDIO_CHANNEL "audio.channel" /**< an audio channel. Ex: "FL" */ +#define PW_KEY_AUDIO_RATE "audio.rate" /**< an audio samplerate */ +#define PW_KEY_AUDIO_CHANNELS "audio.channels" /**< number of audio channels */ +#define PW_KEY_AUDIO_FORMAT "audio.format" /**< an audio format. Ex: "S16LE" */ +#define PW_KEY_AUDIO_ALLOWED_RATES "audio.allowed-rates" /**< a list of allowed samplerates + * ex. "[ 44100 48000 ]" */ + +/** video related properties */ +#define PW_KEY_VIDEO_RATE "video.framerate" /**< a video framerate */ +#define PW_KEY_VIDEO_FORMAT "video.format" /**< a video format */ +#define PW_KEY_VIDEO_SIZE "video.size" /**< a video size as "<width>x<height" */ + +#define PW_KEY_TARGET_OBJECT "target.object" /**< a target object to link to. This can be + * and object name or object.serial */ + +#ifndef PW_REMOVE_DEPRECATED +#define PW_KEY_PRIORITY_MASTER PW_DEPRECATED("priority.master") /**< deprecated, use priority.driver */ +#define PW_KEY_NODE_TARGET PW_DEPRECATED("node.target") /**< deprecated since 0.3.64, use target.object. */ +#endif /* PW_REMOVE_DEPRECATED */ + +/** \} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* PIPEWIRE_KEYS_H */ diff --git a/src/pipewire/link.h b/src/pipewire/link.h new file mode 100644 index 0000000..8130f4b --- /dev/null +++ b/src/pipewire/link.h @@ -0,0 +1,148 @@ +/* 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. + */ + +#ifndef PIPEWIRE_LINK_H +#define PIPEWIRE_LINK_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <spa/utils/defs.h> +#include <spa/utils/hook.h> + +#include <pipewire/proxy.h> + +/** \defgroup pw_link Link + * + * A link is the connection between 2 nodes (\ref pw_node). Nodes are + * linked together on ports. + * + * The link is responsible for negotiating the format and buffers for + * the nodes. + * + */ + +/** + * \addtogroup pw_link + * \{ + */ + +#define PW_TYPE_INTERFACE_Link PW_TYPE_INFO_INTERFACE_BASE "Link" + +#define PW_VERSION_LINK 3 +struct pw_link; + +/** \enum pw_link_state The different link states */ +enum pw_link_state { + PW_LINK_STATE_ERROR = -2, /**< the link is in error */ + PW_LINK_STATE_UNLINKED = -1, /**< the link is unlinked */ + PW_LINK_STATE_INIT = 0, /**< the link is initialized */ + PW_LINK_STATE_NEGOTIATING = 1, /**< the link is negotiating formats */ + PW_LINK_STATE_ALLOCATING = 2, /**< the link is allocating buffers */ + PW_LINK_STATE_PAUSED = 3, /**< the link is paused */ + PW_LINK_STATE_ACTIVE = 4, /**< the link is active */ +}; + +/** Convert a \ref pw_link_state to a readable string */ +const char * pw_link_state_as_string(enum pw_link_state state); +/** The link information. Extra information can be added in later versions */ +struct pw_link_info { + uint32_t id; /**< id of the global */ + uint32_t output_node_id; /**< server side output node id */ + uint32_t output_port_id; /**< output port id */ + uint32_t input_node_id; /**< server side input node id */ + uint32_t input_port_id; /**< input port id */ +#define PW_LINK_CHANGE_MASK_STATE (1 << 0) +#define PW_LINK_CHANGE_MASK_FORMAT (1 << 1) +#define PW_LINK_CHANGE_MASK_PROPS (1 << 2) +#define PW_LINK_CHANGE_MASK_ALL ((1 << 3)-1) + uint64_t change_mask; /**< bitfield of changed fields since last call */ + enum pw_link_state state; /**< the current state of the link */ + const char *error; /**< an error reason if \a state is error */ + struct spa_pod *format; /**< format over link */ + struct spa_dict *props; /**< the properties of the link */ +}; + +struct pw_link_info * +pw_link_info_update(struct pw_link_info *info, + const struct pw_link_info *update); + +struct pw_link_info * +pw_link_info_merge(struct pw_link_info *info, + const struct pw_link_info *update, bool reset); + +void +pw_link_info_free(struct pw_link_info *info); + + +#define PW_LINK_EVENT_INFO 0 +#define PW_LINK_EVENT_NUM 1 + +/** Link events */ +struct pw_link_events { +#define PW_VERSION_LINK_EVENTS 0 + uint32_t version; + /** + * Notify link info + * + * \param info info about the link + */ + void (*info) (void *data, const struct pw_link_info *info); +}; + +#define PW_LINK_METHOD_ADD_LISTENER 0 +#define PW_LINK_METHOD_NUM 1 + +/** Link methods */ +struct pw_link_methods { +#define PW_VERSION_LINK_METHODS 0 + uint32_t version; + + int (*add_listener) (void *object, + struct spa_hook *listener, + const struct pw_link_events *events, + void *data); +}; + +#define pw_link_method(o,method,version,...) \ +({ \ + int _res = -ENOTSUP; \ + spa_interface_call_res((struct spa_interface*)o, \ + struct pw_link_methods, _res, \ + method, version, ##__VA_ARGS__); \ + _res; \ +}) + +#define pw_link_add_listener(c,...) pw_link_method(c,add_listener,0,__VA_ARGS__) + +/** + * \} + */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* PIPEWIRE_LINK_H */ diff --git a/src/pipewire/log.c b/src/pipewire/log.c new file mode 100644 index 0000000..2484109 --- /dev/null +++ b/src/pipewire/log.c @@ -0,0 +1,291 @@ +/* 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 <limits.h> +#include <fnmatch.h> + +#include <spa/support/log-impl.h> + +#include <spa/pod/pod.h> +#include <spa/debug/types.h> +#include <spa/pod/iter.h> +#include <spa/utils/list.h> + +#include <pipewire/log.h> +#include <pipewire/private.h> + +SPA_LOG_IMPL(default_log); + +#define DEFAULT_LOG_LEVEL SPA_LOG_LEVEL_WARN + +SPA_EXPORT +enum spa_log_level pw_log_level = DEFAULT_LOG_LEVEL; + +static struct spa_log *global_log = &default_log.log; + +SPA_EXPORT +struct spa_log_topic *PW_LOG_TOPIC_DEFAULT; + +PW_LOG_TOPIC_STATIC(log_topic, "pw.log"); /* log topic for this file here */ +PW_LOG_TOPIC(log_buffers, "pw.buffers"); +PW_LOG_TOPIC(log_client, "pw.client"); +PW_LOG_TOPIC(log_conf, "pw.conf"); +PW_LOG_TOPIC(log_context, "pw.context"); +PW_LOG_TOPIC(log_core, "pw.core"); +PW_LOG_TOPIC(log_data_loop, "pw.data-loop"); +PW_LOG_TOPIC(log_device, "pw.device"); +PW_LOG_TOPIC(log_factory, "pw.factory"); +PW_LOG_TOPIC(log_filter, "pw.filter"); +PW_LOG_TOPIC(log_global, "pw.global"); +PW_LOG_TOPIC(log_link, "pw.link"); +PW_LOG_TOPIC(log_loop, "pw.loop"); +PW_LOG_TOPIC(log_main_loop, "pw.main-loop"); +PW_LOG_TOPIC(log_mem, "pw.mem"); +PW_LOG_TOPIC(log_metadata, "pw.metadata"); +PW_LOG_TOPIC(log_module, "pw.module"); +PW_LOG_TOPIC(log_node, "pw.node"); +PW_LOG_TOPIC(log_port, "pw.port"); +PW_LOG_TOPIC(log_properties, "pw.props"); +PW_LOG_TOPIC(log_protocol, "pw.protocol"); +PW_LOG_TOPIC(log_proxy, "pw.proxy"); +PW_LOG_TOPIC(log_resource, "pw.resource"); +PW_LOG_TOPIC(log_stream, "pw.stream"); +PW_LOG_TOPIC(log_thread_loop, "pw.thread-loop"); +PW_LOG_TOPIC(log_work_queue, "pw.work-queue"); + +PW_LOG_TOPIC(PW_LOG_TOPIC_DEFAULT, "default"); + +/** Set the global log interface + * \param log the global log to set + */ +SPA_EXPORT +void pw_log_set(struct spa_log *log) +{ + global_log = log ? log : &default_log.log; + global_log->level = pw_log_level; +} + +bool pw_log_is_default(void) +{ + return global_log == &default_log.log; +} + +/** Get the global log interface + * \return the global log + */ +SPA_EXPORT +struct spa_log *pw_log_get(void) +{ + return global_log; +} + +/** Set the global log level + * \param level the new log level + */ +SPA_EXPORT +void pw_log_set_level(enum spa_log_level level) +{ + pw_log_level = level; + global_log->level = level; +} + +/** Log a message for the given topic + * \param level the log level + * \param topic the topic + * \param file the file this message originated from + * \param line the line number + * \param func the function + * \param fmt the printf style format + * \param ... printf style arguments to log + * + */ +SPA_EXPORT +void +pw_log_logt(enum spa_log_level level, + const struct spa_log_topic *topic, + const char *file, + int line, + const char *func, + const char *fmt, ...) +{ + if (SPA_UNLIKELY(pw_log_topic_enabled(level, topic))) { + va_list args; + va_start(args, fmt); + spa_log_logtv(global_log, level, topic, file, line, func, fmt, args); + va_end(args); + } +} + +/** Log a message for the given topic with va_list + * \param level the log level + * \param topic the topic + * \param file the file this message originated from + * \param line the line number + * \param func the function + * \param fmt the printf style format + * \param args a va_list of arguments + * + */ +SPA_EXPORT +void +pw_log_logtv(enum spa_log_level level, + const struct spa_log_topic *topic, + const char *file, + int line, + const char *func, + const char *fmt, + va_list args) +{ + spa_log_logtv(global_log, level, topic, file, line, func, fmt, args); +} + + +/** Log a message for the default topic with va_list + * \param level the log level + * \param file the file this message originated from + * \param line the line number + * \param func the function + * \param fmt the printf style format + * \param args a va_list of arguments + * + */ +SPA_EXPORT +void +pw_log_logv(enum spa_log_level level, + const char *file, + int line, + const char *func, + const char *fmt, + va_list args) +{ + pw_log_logtv(level, PW_LOG_TOPIC_DEFAULT, file, line, func, fmt, args); +} + +/** Log a message for the default topic + * \param level the log level + * \param file the file this message originated from + * \param line the line number + * \param func the function + * \param fmt the printf style format + * \param ... printf style arguments to log + * + */ +SPA_EXPORT +void +pw_log_log(enum spa_log_level level, + const char *file, + int line, + const char *func, + const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + pw_log_logtv(level, PW_LOG_TOPIC_DEFAULT, file, line, func, fmt, args); + va_end(args); +} + +/** \fn void pw_log_error (const char *format, ...) + * Log an error message + * \param format a printf style format + * \param ... printf style arguments + */ +/** \fn void pw_log_warn (const char *format, ...) + * Log a warning message + * \param format a printf style format + * \param ... printf style arguments + */ +/** \fn void pw_log_info (const char *format, ...) + * Log an info message + * \param format a printf style format + * \param ... printf style arguments + */ +/** \fn void pw_log_debug (const char *format, ...) + * Log a debug message + * \param format a printf style format + * \param ... printf style arguments + */ +/** \fn void pw_log_trace (const char *format, ...) + * Log a trace message. Trace messages may be generated from + * \param format a printf style format + * \param ... printf style arguments + * realtime threads + */ + +#include <spa/debug/pod.h> +#include <spa/debug/log.h> + +void pw_log_log_object(enum spa_log_level level, + const struct spa_log_topic *topic, const char *file, + int line, const char *func, uint32_t flags, const void *object) +{ + struct spa_debug_log_ctx ctx = SPA_LOGF_DEBUG_INIT(global_log, level, + topic, file, line, func ); + if (flags & PW_LOG_OBJECT_POD) { + const struct spa_pod *pod = object; + if (pod == NULL) { + pw_log_logt(level, topic, file, line, func, "NULL"); + } else { + spa_debugc_pod(&ctx.ctx, 0, SPA_TYPE_ROOT, pod); + } + } +} + +SPA_EXPORT +void +_pw_log_topic_new(struct spa_log_topic *topic) +{ + spa_log_topic_init(global_log, topic); +} + +void +pw_log_init(void) +{ + PW_LOG_TOPIC_INIT(PW_LOG_TOPIC_DEFAULT); + PW_LOG_TOPIC_INIT(log_buffers); + PW_LOG_TOPIC_INIT(log_client); + PW_LOG_TOPIC_INIT(log_conf); + PW_LOG_TOPIC_INIT(log_context); + PW_LOG_TOPIC_INIT(log_core); + PW_LOG_TOPIC_INIT(log_data_loop); + PW_LOG_TOPIC_INIT(log_device); + PW_LOG_TOPIC_INIT(log_factory); + PW_LOG_TOPIC_INIT(log_filter); + PW_LOG_TOPIC_INIT(log_global); + PW_LOG_TOPIC_INIT(log_link); + PW_LOG_TOPIC_INIT(log_loop); + PW_LOG_TOPIC_INIT(log_main_loop); + PW_LOG_TOPIC_INIT(log_mem); + PW_LOG_TOPIC_INIT(log_metadata); + PW_LOG_TOPIC_INIT(log_module); + PW_LOG_TOPIC_INIT(log_node); + PW_LOG_TOPIC_INIT(log_port); + PW_LOG_TOPIC_INIT(log_properties); + PW_LOG_TOPIC_INIT(log_protocol); + PW_LOG_TOPIC_INIT(log_proxy); + PW_LOG_TOPIC_INIT(log_resource); + PW_LOG_TOPIC_INIT(log_stream); + PW_LOG_TOPIC_INIT(log_thread_loop); + PW_LOG_TOPIC_INIT(log_topic); + PW_LOG_TOPIC_INIT(log_work_queue); +} diff --git a/src/pipewire/log.h b/src/pipewire/log.h new file mode 100644 index 0000000..f91dc11 --- /dev/null +++ b/src/pipewire/log.h @@ -0,0 +1,182 @@ +/* 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. + */ + +#ifndef PIPEWIRE_LOG_H +#define PIPEWIRE_LOG_H + +#include <spa/support/log.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** \defgroup pw_log Logging + * + * \brief Logging functions of PipeWire + * + * Logging is performed to stdout and stderr. Trace logging is performed + * in a lockfree ringbuffer and written out from the main thread as to not + * block the realtime threads. + */ + +/** + * \addtogroup pw_log + * \{ + */ +/** The global log level */ +extern enum spa_log_level pw_log_level; + +extern struct spa_log_topic *PW_LOG_TOPIC_DEFAULT; + +/** Configure a logging module. This is usually done automatically + * in pw_init() but you can install a custom logger before calling + * pw_init(). */ +void pw_log_set(struct spa_log *log); + +/** Get the log interface */ +struct spa_log *pw_log_get(void); + +/** Configure the logging level */ +void pw_log_set_level(enum spa_log_level level); + +/** Log a message for a topic */ +void +pw_log_logt(enum spa_log_level level, + const struct spa_log_topic *topic, + const char *file, + int line, const char *func, + const char *fmt, ...) SPA_PRINTF_FUNC(6, 7); + +/** Log a message for a topic */ +void +pw_log_logtv(enum spa_log_level level, + const struct spa_log_topic *topic, + const char *file, + int line, const char *func, + const char *fmt, va_list args) SPA_PRINTF_FUNC(6, 0); + + + +/** Log a message for the default topic */ +void +pw_log_log(enum spa_log_level level, + const char *file, + int line, const char *func, + const char *fmt, ...) SPA_PRINTF_FUNC(5, 6); + +/** Log a message for the default topic */ +void +pw_log_logv(enum spa_log_level level, + const char *file, + int line, const char *func, + const char *fmt, va_list args) SPA_PRINTF_FUNC(5, 0); + +/** Initialize the log topic. The returned topic is owned by the pipewire + * context and the topic must not be modified or freed. + * Do not use this function directly, use one of PW_LOG_TOPIC_* instead. + * + * \see PW_LOG_TOPIC_STATIC + * \see PW_LOG_TOPIC_EXTERN + * \see PW_LOG_TOPIC + */ +void +_pw_log_topic_new(struct spa_log_topic *topic); + +/** + * Declare a static log topic named \a var. The usual usage is: + * \code + * PW_LOG_TOPIC_STATIC(my_topic); + * #define PW_LOG_TOPIC_DEFAULT my_topic + * + * void foo() { + * pw_log_debug("bar"); + * } + * \endcode + */ +#define PW_LOG_TOPIC_STATIC(var, topic) \ + static struct spa_log_topic var##__LINE__ = SPA_LOG_TOPIC(0, topic); \ + static struct spa_log_topic *(var) = &(var##__LINE__) + +/** + * Declare a static log topic named \a var. + * See \ref PW_LOG_TOPIC_STATIC for an example usage. + */ +#define PW_LOG_TOPIC_EXTERN(var) \ + extern struct spa_log_topic *var + +/** + * Declare a static log topic named \a var. + * See \ref PW_LOG_TOPIC_STATIC for an example usage. + */ +#define PW_LOG_TOPIC(var, topic) \ + struct spa_log_topic var##__LINE__ = SPA_LOG_TOPIC(0, topic); \ + struct spa_log_topic *(var) = &(var##__LINE__) + +#define PW_LOG_TOPIC_INIT(var) \ + spa_log_topic_init(pw_log_get(), var); + +/** Check if a loglevel is enabled */ +#define pw_log_level_enabled(lev) (pw_log_level >= (lev)) +#define pw_log_topic_enabled(lev,t) ((t) && (t)->has_custom_level ? (t)->level >= (lev) : pw_log_level_enabled((lev))) + +#define pw_logtv(lev,topic,fmt,ap) \ +({ \ + if (SPA_UNLIKELY(pw_log_topic_enabled(lev,topic))) \ + pw_log_logtv(lev,topic,__FILE__,__LINE__,__func__,fmt,ap); \ +}) + +#define pw_logt(lev,topic,...) \ +({ \ + if (SPA_UNLIKELY(pw_log_topic_enabled(lev,topic))) \ + pw_log_logt(lev,topic,__FILE__,__LINE__,__func__,__VA_ARGS__); \ +}) + +#define pw_log(lev,...) pw_logt(lev,PW_LOG_TOPIC_DEFAULT,__VA_ARGS__) + +#define pw_log_error(...) pw_log(SPA_LOG_LEVEL_ERROR,__VA_ARGS__) +#define pw_log_warn(...) pw_log(SPA_LOG_LEVEL_WARN,__VA_ARGS__) +#define pw_log_info(...) pw_log(SPA_LOG_LEVEL_INFO,__VA_ARGS__) +#define pw_log_debug(...) pw_log(SPA_LOG_LEVEL_DEBUG,__VA_ARGS__) +#define pw_log_trace(...) pw_log(SPA_LOG_LEVEL_TRACE,__VA_ARGS__) + +#define pw_logt_error(t,...) pw_logt(SPA_LOG_LEVEL_ERROR,t,__VA_ARGS__) +#define pw_logt_warn(t,...) pw_logt(SPA_LOG_LEVEL_WARN,t,__VA_ARGS__) +#define pw_logt_info(t,...) pw_logt(SPA_LOG_LEVEL_INFO,t,__VA_ARGS__) +#define pw_logt_debug(t,...) pw_logt(SPA_LOG_LEVEL_DEBUG,t,__VA_ARGS__) +#define pw_logt_trace(t,...) pw_logt(SPA_LOG_LEVEL_TRACE,t,__VA_ARGS__) + +#ifndef FASTPATH +#define pw_log_trace_fp(...) pw_log(SPA_LOG_LEVEL_TRACE,__VA_ARGS__) +#else +#define pw_log_trace_fp(...) +#endif + +/** + * \} + */ + +#ifdef __cplusplus +} +#endif +#endif /* PIPEWIRE_LOG_H */ diff --git a/src/pipewire/loop.c b/src/pipewire/loop.c new file mode 100644 index 0000000..da766a6 --- /dev/null +++ b/src/pipewire/loop.c @@ -0,0 +1,162 @@ +/* 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 <stdio.h> + +#include <spa/support/loop.h> +#include <spa/utils/names.h> +#include <spa/utils/result.h> + +#include <pipewire/pipewire.h> +#include <pipewire/loop.h> +#include <pipewire/log.h> +#include <pipewire/type.h> + +PW_LOG_TOPIC_EXTERN(log_loop); +#define PW_LOG_TOPIC_DEFAULT log_loop + +/** \cond */ + +struct impl { + struct pw_loop this; + + struct spa_handle *system_handle; + struct spa_handle *loop_handle; +}; +/** \endcond */ + +/** Create a new loop + * \returns a newly allocated loop + */ +SPA_EXPORT +struct pw_loop *pw_loop_new(const struct spa_dict *props) +{ + int res; + struct impl *impl; + struct pw_loop *this; + void *iface; + struct spa_support support[32]; + uint32_t n_support; + const char *lib; + + n_support = pw_get_support(support, 32); + + impl = calloc(1, sizeof(struct impl)); + if (impl == NULL) { + res = -errno; + goto error_cleanup; + } + + this = &impl->this; + + if (props) + lib = spa_dict_lookup(props, PW_KEY_LIBRARY_NAME_SYSTEM); + else + lib = NULL; + + impl->system_handle = pw_load_spa_handle(lib, + SPA_NAME_SUPPORT_SYSTEM, + props, n_support, support); + if (impl->system_handle == NULL) { + res = -errno; + pw_log_error("%p: can't make "SPA_NAME_SUPPORT_SYSTEM" handle: %m", this); + goto error_free; + } + + if ((res = spa_handle_get_interface(impl->system_handle, + SPA_TYPE_INTERFACE_System, + &iface)) < 0) { + pw_log_error("%p: can't get System interface: %s", this, spa_strerror(res)); + goto error_unload_system; + } + this->system = iface; + + support[n_support++] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_System, iface); + + if (props) + lib = spa_dict_lookup(props, PW_KEY_LIBRARY_NAME_LOOP); + else + lib = NULL; + + impl->loop_handle = pw_load_spa_handle(lib, + SPA_NAME_SUPPORT_LOOP, props, + n_support, support); + if (impl->loop_handle == NULL) { + res = -errno; + pw_log_error("%p: can't make "SPA_NAME_SUPPORT_LOOP" handle: %m", this); + goto error_unload_system; + } + + if ((res = spa_handle_get_interface(impl->loop_handle, + SPA_TYPE_INTERFACE_Loop, + &iface)) < 0) { + pw_log_error("%p: can't get Loop interface: %s", + this, spa_strerror(res)); + goto error_unload_loop; + } + this->loop = iface; + + if ((res = spa_handle_get_interface(impl->loop_handle, + SPA_TYPE_INTERFACE_LoopControl, + &iface)) < 0) { + pw_log_error("%p: can't get LoopControl interface: %s", + this, spa_strerror(res)); + goto error_unload_loop; + } + this->control = iface; + + if ((res = spa_handle_get_interface(impl->loop_handle, + SPA_TYPE_INTERFACE_LoopUtils, + &iface)) < 0) { + pw_log_error("%p: can't get LoopUtils interface: %s", + this, spa_strerror(res)); + goto error_unload_loop; + } + this->utils = iface; + + return this; + +error_unload_loop: + pw_unload_spa_handle(impl->loop_handle); +error_unload_system: + pw_unload_spa_handle(impl->system_handle); +error_free: + free(impl); +error_cleanup: + errno = -res; + return NULL; +} + +/** Destroy a loop + * \param loop a loop to destroy + */ +SPA_EXPORT +void pw_loop_destroy(struct pw_loop *loop) +{ + struct impl *impl = SPA_CONTAINER_OF(loop, struct impl, this); + + pw_unload_spa_handle(impl->loop_handle); + pw_unload_spa_handle(impl->system_handle); + free(impl); +} diff --git a/src/pipewire/loop.h b/src/pipewire/loop.h new file mode 100644 index 0000000..42a630d --- /dev/null +++ b/src/pipewire/loop.h @@ -0,0 +1,90 @@ +/* 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. + */ + +#ifndef PIPEWIRE_LOOP_H +#define PIPEWIRE_LOOP_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <spa/support/loop.h> +#include <spa/utils/dict.h> + +/** \defgroup pw_loop Loop + * + * PipeWire loop object provides an implementation of + * the spa loop interfaces. It can be used to implement various + * event loops. + */ + +/** + * \addtogroup pw_loop + * \{ + */ + +struct pw_loop { + struct spa_system *system; /**< system utils */ + struct spa_loop *loop; /**< wrapped loop */ + struct spa_loop_control *control; /**< loop control */ + struct spa_loop_utils *utils; /**< loop utils */ +}; + +struct pw_loop * +pw_loop_new(const struct spa_dict *props); + +void +pw_loop_destroy(struct pw_loop *loop); + +#define pw_loop_add_source(l,...) spa_loop_add_source((l)->loop,__VA_ARGS__) +#define pw_loop_update_source(l,...) spa_loop_update_source((l)->loop,__VA_ARGS__) +#define pw_loop_remove_source(l,...) spa_loop_remove_source((l)->loop,__VA_ARGS__) +#define pw_loop_invoke(l,...) spa_loop_invoke((l)->loop,__VA_ARGS__) + +#define pw_loop_get_fd(l) spa_loop_control_get_fd((l)->control) +#define pw_loop_add_hook(l,...) spa_loop_control_add_hook((l)->control,__VA_ARGS__) +#define pw_loop_enter(l) spa_loop_control_enter((l)->control) +#define pw_loop_iterate(l,...) spa_loop_control_iterate((l)->control,__VA_ARGS__) +#define pw_loop_leave(l) spa_loop_control_leave((l)->control) + +#define pw_loop_add_io(l,...) spa_loop_utils_add_io((l)->utils,__VA_ARGS__) +#define pw_loop_update_io(l,...) spa_loop_utils_update_io((l)->utils,__VA_ARGS__) +#define pw_loop_add_idle(l,...) spa_loop_utils_add_idle((l)->utils,__VA_ARGS__) +#define pw_loop_enable_idle(l,...) spa_loop_utils_enable_idle((l)->utils,__VA_ARGS__) +#define pw_loop_add_event(l,...) spa_loop_utils_add_event((l)->utils,__VA_ARGS__) +#define pw_loop_signal_event(l,...) spa_loop_utils_signal_event((l)->utils,__VA_ARGS__) +#define pw_loop_add_timer(l,...) spa_loop_utils_add_timer((l)->utils,__VA_ARGS__) +#define pw_loop_update_timer(l,...) spa_loop_utils_update_timer((l)->utils,__VA_ARGS__) +#define pw_loop_add_signal(l,...) spa_loop_utils_add_signal((l)->utils,__VA_ARGS__) +#define pw_loop_destroy_source(l,...) spa_loop_utils_destroy_source((l)->utils,__VA_ARGS__) + +/** + * \} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* PIPEWIRE_LOOP_H */ diff --git a/src/pipewire/main-loop.c b/src/pipewire/main-loop.c new file mode 100644 index 0000000..4e8c8aa --- /dev/null +++ b/src/pipewire/main-loop.c @@ -0,0 +1,157 @@ +/* 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 "pipewire/log.h" +#include "pipewire/main-loop.h" +#include "pipewire/private.h" + +PW_LOG_TOPIC_EXTERN(log_main_loop); +#define PW_LOG_TOPIC_DEFAULT log_main_loop + +static int do_stop(struct spa_loop *loop, bool async, uint32_t seq, + const void *data, size_t size, void *user_data) +{ + struct pw_main_loop *this = user_data; + pw_log_debug("%p: do stop", this); + this->running = false; + return 0; +} + +static struct pw_main_loop *loop_new(struct pw_loop *loop, const struct spa_dict *props) +{ + struct pw_main_loop *this; + int res; + + this = calloc(1, sizeof(struct pw_main_loop)); + if (this == NULL) { + res = -errno; + goto error_cleanup; + } + + pw_log_debug("%p: new", this); + + if (loop == NULL) { + loop = pw_loop_new(props); + this->created = true; + } + if (loop == NULL) { + res = -errno; + goto error_free; + } + this->loop = loop; + + spa_hook_list_init(&this->listener_list); + + return this; + +error_free: + free(this); +error_cleanup: + errno = -res; + return NULL; +} + +/** Create a new main loop + * \return a newly allocated \ref pw_main_loop + * + */ +SPA_EXPORT +struct pw_main_loop *pw_main_loop_new(const struct spa_dict *props) +{ + return loop_new(NULL, props); +} + +/** Destroy a main loop + * \param loop the main loop to destroy + * + */ +SPA_EXPORT +void pw_main_loop_destroy(struct pw_main_loop *loop) +{ + pw_log_debug("%p: destroy", loop); + pw_main_loop_emit_destroy(loop); + + if (loop->created) + pw_loop_destroy(loop->loop); + + spa_hook_list_clean(&loop->listener_list); + + free(loop); +} + +SPA_EXPORT +void pw_main_loop_add_listener(struct pw_main_loop *loop, + struct spa_hook *listener, + const struct pw_main_loop_events *events, + void *data) +{ + spa_hook_list_append(&loop->listener_list, listener, events, data); +} + +SPA_EXPORT +struct pw_loop * pw_main_loop_get_loop(struct pw_main_loop *loop) +{ + return loop->loop; +} + +/** Stop a main loop + * \param loop a \ref pw_main_loop to stop + * + * The call to \ref pw_main_loop_run() will return + * + */ +SPA_EXPORT +int pw_main_loop_quit(struct pw_main_loop *loop) +{ + pw_log_debug("%p: quit", loop); + return pw_loop_invoke(loop->loop, do_stop, 1, NULL, 0, false, loop); +} + +/** Start a main loop + * \param loop the main loop to start + * + * Start running \a loop. This function blocks until \ref pw_main_loop_quit() + * has been called + * + */ +SPA_EXPORT +int pw_main_loop_run(struct pw_main_loop *loop) +{ + int res = 0; + + pw_log_debug("%p: run", loop); + + loop->running = true; + pw_loop_enter(loop->loop); + while (loop->running) { + if ((res = pw_loop_iterate(loop->loop, -1)) < 0) { + if (res == -EINTR) + continue; + pw_log_warn("%p: iterate error %d (%s)", + loop, res, spa_strerror(res)); + } + } + pw_loop_leave(loop->loop); + return res; +} diff --git a/src/pipewire/main-loop.h b/src/pipewire/main-loop.h new file mode 100644 index 0000000..2225bee --- /dev/null +++ b/src/pipewire/main-loop.h @@ -0,0 +1,86 @@ +/* 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. + */ + +#ifndef PIPEWIRE_MAIN_LOOP_H +#define PIPEWIRE_MAIN_LOOP_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** \defgroup pw_main_loop Main Loop + * + * A main loop object + */ + +/** + * \addtogroup pw_main_loop + * \{ + */ + +/** A main loop object */ +struct pw_main_loop; + +#include <pipewire/loop.h> + +/** Events of the main loop */ +struct pw_main_loop_events { +#define PW_VERSION_MAIN_LOOP_EVENTS 0 + uint32_t version; + + /** Emitted when the main loop is destroyed */ + void (*destroy) (void *data); +}; + +/** Create a new main loop. */ +struct pw_main_loop * +pw_main_loop_new(const struct spa_dict *props); + +/** Add an event listener */ +void pw_main_loop_add_listener(struct pw_main_loop *loop, + struct spa_hook *listener, + const struct pw_main_loop_events *events, + void *data); + +/** Get the loop implementation */ +struct pw_loop * pw_main_loop_get_loop(struct pw_main_loop *loop); + +/** Destroy a loop */ +void pw_main_loop_destroy(struct pw_main_loop *loop); + +/** Run a main loop. This blocks until \ref pw_main_loop_quit is called */ +int pw_main_loop_run(struct pw_main_loop *loop); + +/** Quit a main loop */ +int pw_main_loop_quit(struct pw_main_loop *loop); + +/** + * \} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* PIPEWIRE_MAIN_LOOP_H */ diff --git a/src/pipewire/map.h b/src/pipewire/map.h new file mode 100644 index 0000000..9a46082 --- /dev/null +++ b/src/pipewire/map.h @@ -0,0 +1,252 @@ +/* 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. + */ + +#ifndef PIPEWIRE_MAP_H +#define PIPEWIRE_MAP_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <string.h> +#include <errno.h> + +#include <spa/utils/defs.h> +#include <pipewire/array.h> + +/** \defgroup pw_map Map + * + * \brief A map that holds pointers to objects indexed by id + * + * The map is a sparse version of the \ref pw_array "pw_array" that manages the + * indices of elements for the caller. Adding items with + * pw_map_insert_new() returns the assigned index for that item; if items + * are removed the map re-uses indices to keep the array at the minimum + * required size. + * + * \code{.c} + * struct pw_map map = PW_MAP_INIT(4); + * + * idx1 = pw_map_insert_new(&map, ptr1); + * idx2 = pw_map_insert_new(&map, ptr2); + * // the map is now [ptr1, ptr2], size 2 + * pw_map_remove(&map, idx1); + * // the map is now [<unused>, ptr2], size 2 + * pw_map_insert_new(&map, ptr3); + * // the map is now [ptr3, ptr2], size 2 + * \endcode + */ + +/** + * \addtogroup pw_map + * \{ + */ + +/** \private + * An entry in the map. This is used internally only. Each element in the + * backing pw_array is a union pw_map_item. For real items, the data pointer + * points to the item. If an element has been removed, pw_map->free_list + * is the index of the most recently removed item. That item contains + * the index of the next removed item until item->next is SPA_ID_INVALID. + * + * The free list is prepended only, the last item to be removed will be the + * first item to get re-used on the next insert. + */ +union pw_map_item { + uintptr_t next; /* next free index */ + void *data; /* data of this item, must be an even address */ +}; + +/** A map. This struct should be treated as opaque by the caller. */ +struct pw_map { + struct pw_array items; /* an array with the map items */ + uint32_t free_list; /* first free index */ +}; + +/** \param extend the amount of bytes to grow the map with when needed */ +#define PW_MAP_INIT(extend) ((struct pw_map) { PW_ARRAY_INIT(extend), SPA_ID_INVALID }) + +/** + * Get the number of currently allocated elements in the map. + * \note pw_map_get_size() returns the currently allocated number of + * elements in the map, not the number of actually set elements. + * \return the number of available elements before the map needs to grow + */ +#define pw_map_get_size(m) pw_array_get_len(&(m)->items, union pw_map_item) +#define pw_map_get_item(m,id) pw_array_get_unchecked(&(m)->items,id,union pw_map_item) +#define pw_map_item_is_free(item) ((item)->next & 0x1) +#define pw_map_id_is_free(m,id) (pw_map_item_is_free(pw_map_get_item(m,id))) +/** \return true if the id fits within the current map size */ +#define pw_map_check_id(m,id) ((id) < pw_map_get_size(m)) +/** \return true if there is a valid item at \a id */ +#define pw_map_has_item(m,id) (pw_map_check_id(m,id) && !pw_map_id_is_free(m, id)) +#define pw_map_lookup_unchecked(m,id) pw_map_get_item(m,id)->data + +/** Convert an id to a pointer that can be inserted into the map */ +#define PW_MAP_ID_TO_PTR(id) (SPA_UINT32_TO_PTR((id)<<1)) +/** Convert a pointer to an id that can be retrieved from the map */ +#define PW_MAP_PTR_TO_ID(p) (SPA_PTR_TO_UINT32(p)>>1) + +/** Initialize a map + * \param map the map to initialize + * \param size the initial size of the map + * \param extend the amount to bytes to grow the map with when needed + */ +static inline void pw_map_init(struct pw_map *map, size_t size, size_t extend) +{ + pw_array_init(&map->items, extend * sizeof(union pw_map_item)); + pw_array_ensure_size(&map->items, size * sizeof(union pw_map_item)); + map->free_list = SPA_ID_INVALID; +} + +/** Clear a map and free the data storage. All previously returned ids + * must be treated as invalid. + */ +static inline void pw_map_clear(struct pw_map *map) +{ + pw_array_clear(&map->items); +} + +/** Reset a map but keep previously allocated storage. All previously + * returned ids must be treated as invalid. + */ +static inline void pw_map_reset(struct pw_map *map) +{ + pw_array_reset(&map->items); + map->free_list = SPA_ID_INVALID; +} + +/** Insert data in the map. This function causes the map to grow if required. + * \param map the map to insert into + * \param data the item to add + * \return the id where the item was inserted or SPA_ID_INVALID when the + * item can not be inserted. + */ +static inline uint32_t pw_map_insert_new(struct pw_map *map, void *data) +{ + union pw_map_item *start, *item; + uint32_t id; + + if (map->free_list != SPA_ID_INVALID) { + start = (union pw_map_item *) map->items.data; + item = &start[map->free_list >> 1]; /* lsb always 1, see pw_map_remove */ + map->free_list = item->next; + } else { + item = (union pw_map_item *) pw_array_add(&map->items, sizeof(union pw_map_item)); + if (item == NULL) + return SPA_ID_INVALID; + start = (union pw_map_item *) map->items.data; + } + item->data = data; + id = (item - start); + return id; +} + +/** Replace the data in the map at an index. + * + * \param map the map to insert into + * \param id the index to insert at, must be less or equal to pw_map_get_size() + * \param data the data to insert + * \return 0 on success, -ENOSPC value when the index is invalid or a negative errno + */ +static inline int pw_map_insert_at(struct pw_map *map, uint32_t id, void *data) +{ + size_t size = pw_map_get_size(map); + union pw_map_item *item; + + if (id > size) + return -ENOSPC; + else if (id == size) { + item = (union pw_map_item *) pw_array_add(&map->items, sizeof(union pw_map_item)); + if (item == NULL) + return -errno; + } else { + item = pw_map_get_item(map, id); + if (pw_map_item_is_free(item)) + return -EINVAL; + } + item->data = data; + return 0; +} + +/** Remove an item at index. The id may get re-used in the future. + * + * \param map the map to remove from + * \param id the index to remove + */ +static inline void pw_map_remove(struct pw_map *map, uint32_t id) +{ + if (pw_map_id_is_free(map, id)) + return; + + pw_map_get_item(map, id)->next = map->free_list; + map->free_list = (id << 1) | 1; +} + +/** Find an item in the map + * \param map the map to use + * \param id the index to look at + * \return the item at \a id or NULL when no such item exists + */ +static inline void *pw_map_lookup(struct pw_map *map, uint32_t id) +{ + if (SPA_LIKELY(pw_map_check_id(map, id))) { + union pw_map_item *item = pw_map_get_item(map, id); + if (!pw_map_item_is_free(item)) + return item->data; + } + return NULL; +} + +/** Iterate all map items + * \param map the map to iterate + * \param func the function to call for each item, the item data and \a data is + * passed to the function. When \a func returns a non-zero result, + * iteration ends and the result is returned. + * \param data data to pass to \a func + * \return the result of the last call to \a func or 0 when all callbacks returned 0. + */ +static inline int pw_map_for_each(struct pw_map *map, + int (*func) (void *item_data, void *data), void *data) +{ + union pw_map_item *item; + int res = 0; + + pw_array_for_each(item, &map->items) { + if (!pw_map_item_is_free(item)) + if ((res = func(item->data, data)) != 0) + break; + } + return res; +} + +/** + * \} + */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* PIPEWIRE_MAP_H */ diff --git a/src/pipewire/mem.c b/src/pipewire/mem.c new file mode 100644 index 0000000..ae9e1e4 --- /dev/null +++ b/src/pipewire/mem.c @@ -0,0 +1,808 @@ +/* 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 <string.h> +#include <stddef.h> +#include <stdio.h> +#include <errno.h> +#include <sys/mman.h> +#include <fcntl.h> +#include <unistd.h> +#include <stdlib.h> +#include <sys/syscall.h> + +#include <spa/utils/list.h> +#include <spa/buffer/buffer.h> + +#include <pipewire/log.h> +#include <pipewire/map.h> +#include <pipewire/mem.h> + +PW_LOG_TOPIC_EXTERN(log_mem); +#define PW_LOG_TOPIC_DEFAULT log_mem + +#if !defined(__FreeBSD__) && !defined(__MidnightBSD__) && !defined(HAVE_MEMFD_CREATE) +/* + * No glibc wrappers exist for memfd_create(2), so provide our own. + * + * Also define memfd fcntl sealing macros. While they are already + * defined in the kernel header file <linux/fcntl.h>, that file as + * a whole conflicts with the original glibc header <fnctl.h>. + */ + +static inline int memfd_create(const char *name, unsigned int flags) +{ + return syscall(SYS_memfd_create, name, flags); +} + +#define HAVE_MEMFD_CREATE 1 +#endif + +#if defined(__FreeBSD__) || defined(__MidnightBSD__) +#define MAP_LOCKED 0 +#endif + +/* memfd_create(2) flags */ + +#ifndef MFD_CLOEXEC +#define MFD_CLOEXEC 0x0001U +#endif + +#ifndef MFD_ALLOW_SEALING +#define MFD_ALLOW_SEALING 0x0002U +#endif + +/* fcntl() seals-related flags */ + +#ifndef F_LINUX_SPECIFIC_BASE +#define F_LINUX_SPECIFIC_BASE 1024 +#endif + +#ifndef F_ADD_SEALS +#define F_ADD_SEALS (F_LINUX_SPECIFIC_BASE + 9) +#define F_GET_SEALS (F_LINUX_SPECIFIC_BASE + 10) + +#define F_SEAL_SEAL 0x0001 /* prevent further seals from being set */ +#define F_SEAL_SHRINK 0x0002 /* prevent file from shrinking */ +#define F_SEAL_GROW 0x0004 /* prevent file from growing */ +#define F_SEAL_WRITE 0x0008 /* prevent writes */ +#endif + +#define pw_mempool_emit(p,m,v,...) spa_hook_list_call(&p->listener_list, struct pw_mempool_events, m, v, ##__VA_ARGS__) +#define pw_mempool_emit_destroy(p) pw_mempool_emit(p, destroy, 0) +#define pw_mempool_emit_added(p,b) pw_mempool_emit(p, added, 0, b) +#define pw_mempool_emit_removed(p,b) pw_mempool_emit(p, removed, 0, b) + +struct mempool { + struct pw_mempool this; + + struct spa_hook_list listener_list; + + struct pw_map map; /* map memblock to id */ + struct spa_list blocks; /* list of memblock */ + uint32_t pagesize; +}; + +struct memblock { + struct pw_memblock this; + struct spa_list link; /* link in mempool */ + struct spa_list mappings; /* list of struct mapping */ + struct spa_list memmaps; /* list of struct memmap */ +}; + +/* a mapped region of a block */ +struct mapping { + struct memblock *block; + int ref; + uint32_t offset; + uint32_t size; + unsigned int do_unmap:1; + struct spa_list link; + void *ptr; +}; + +/* a reference to a (part of a) mapped region */ +struct memmap { + struct pw_memmap this; + struct mapping *mapping; + struct spa_list link; +}; + +SPA_EXPORT +struct pw_mempool *pw_mempool_new(struct pw_properties *props) +{ + struct mempool *impl; + struct pw_mempool *this; + + impl = calloc(1, sizeof(struct mempool)); + if (impl == NULL) + return NULL; + + this = &impl->this; + this->props = props; + + impl->pagesize = sysconf(_SC_PAGESIZE); + + pw_log_debug("%p: new", this); + + spa_hook_list_init(&impl->listener_list); + pw_map_init(&impl->map, 64, 64); + spa_list_init(&impl->blocks); + + return this; +} + +SPA_EXPORT +void pw_mempool_clear(struct pw_mempool *pool) +{ + struct mempool *impl = SPA_CONTAINER_OF(pool, struct mempool, this); + struct memblock *b; + + pw_log_debug("%p: clear", pool); + + spa_list_consume(b, &impl->blocks, link) + pw_memblock_free(&b->this); + pw_map_reset(&impl->map); +} + +SPA_EXPORT +void pw_mempool_destroy(struct pw_mempool *pool) +{ + struct mempool *impl = SPA_CONTAINER_OF(pool, struct mempool, this); + + pw_log_debug("%p: destroy", pool); + + pw_mempool_emit_destroy(impl); + + pw_mempool_clear(pool); + + spa_hook_list_clean(&impl->listener_list); + + pw_map_clear(&impl->map); + pw_properties_free(pool->props); + free(impl); +} + +SPA_EXPORT +void pw_mempool_add_listener(struct pw_mempool *pool, + struct spa_hook *listener, + const struct pw_mempool_events *events, + void *data) +{ + struct mempool *impl = SPA_CONTAINER_OF(pool, struct mempool, this); + spa_hook_list_append(&impl->listener_list, listener, events, data); +} + +#if 0 +/** Map a memblock + * \param mem a memblock + * \return 0 on success, < 0 on error + */ +SPA_EXPORT +int pw_memblock_map_old(struct pw_memblock *mem) +{ + if (mem->ptr != NULL) + return 0; + + if (mem->flags & PW_MEMBLOCK_FLAG_MAP_READWRITE) { + int prot = 0; + + if (mem->flags & PW_MEMBLOCK_FLAG_MAP_READ) + prot |= PROT_READ; + if (mem->flags & PW_MEMBLOCK_FLAG_MAP_WRITE) + prot |= PROT_WRITE; + + if (mem->flags & PW_MEMBLOCK_FLAG_MAP_TWICE) { + void *ptr, *wrap; + + mem->ptr = + mmap(NULL, mem->size << 1, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, + 0); + if (mem->ptr == MAP_FAILED) + return -errno; + + ptr = + mmap(mem->ptr, mem->size, prot, MAP_FIXED | MAP_SHARED, mem->fd, + mem->offset); + if (ptr != mem->ptr) { + munmap(mem->ptr, mem->size << 1); + return -ENOMEM; + } + + wrap = SPA_PTROFF(mem->ptr, mem->size, void); + + ptr = + mmap(wrap, mem->size, prot, MAP_FIXED | MAP_SHARED, + mem->fd, mem->offset); + if (ptr != wrap) { + munmap(mem->ptr, mem->size << 1); + return -ENOMEM; + } + } else { + mem->ptr = mmap(NULL, mem->size, prot, MAP_SHARED, mem->fd, 0); + if (mem->ptr == MAP_FAILED) + return -errno; + } + } else { + mem->ptr = NULL; + } + + pw_log_debug("%p: map to %p", mem, mem->ptr); + + return 0; +} +#endif + +static struct mapping * memblock_find_mapping(struct memblock *b, + uint32_t flags, uint32_t offset, uint32_t size) +{ + struct mapping *m; + struct pw_mempool *pool = b->this.pool; + + spa_list_for_each(m, &b->mappings, link) { + pw_log_debug("%p: check %p offset:(%u <= %u) end:(%u >= %u)", + pool, m, m->offset, offset, m->offset + m->size, + offset + size); + if (m->offset <= offset && (m->offset + m->size) >= (offset + size)) { + pw_log_debug("%p: found %p id:%u fd:%d offs:%u size:%u ref:%d", + pool, &b->this, b->this.id, b->this.fd, + offset, size, b->this.ref); + return m; + } + } + return NULL; +} + +static struct mapping * memblock_map(struct memblock *b, + enum pw_memmap_flags flags, uint32_t offset, uint32_t size) +{ + struct mempool *p = SPA_CONTAINER_OF(b->this.pool, struct mempool, this); + struct mapping *m; + void *ptr; + int prot = 0, fl = 0; + + if (flags & PW_MEMMAP_FLAG_READ) + prot |= PROT_READ; + if (flags & PW_MEMMAP_FLAG_WRITE) + prot |= PROT_WRITE; + + if (flags & PW_MEMMAP_FLAG_PRIVATE) + fl |= MAP_PRIVATE; + else + fl |= MAP_SHARED; + + if (flags & PW_MEMMAP_FLAG_LOCKED) + fl |= MAP_LOCKED; + + if (flags & PW_MEMMAP_FLAG_TWICE) { + pw_log_error("%p: implement me PW_MEMMAP_FLAG_TWICE", p); + errno = ENOTSUP; + return NULL; + } + + + ptr = mmap(NULL, size, prot, fl, b->this.fd, offset); + if (ptr == MAP_FAILED) { + pw_log_error("%p: Failed to mmap memory fd:%d offset:%u size:%u: %m", + p, b->this.fd, offset, size); + return NULL; + } + + m = calloc(1, sizeof(struct mapping)); + if (m == NULL) { + munmap(ptr, size); + return NULL; + } + m->ptr = ptr; + m->do_unmap = true; + m->block = b; + m->offset = offset; + m->size = size; + b->this.ref++; + spa_list_append(&b->mappings, &m->link); + + pw_log_debug("%p: block:%p fd:%d map:%p ptr:%p (%u %u) block-ref:%d", p, &b->this, + b->this.fd, m, m->ptr, offset, size, b->this.ref); + + return m; +} + +static void mapping_free(struct mapping *m) +{ + struct memblock *b = m->block; + struct mempool *p = SPA_CONTAINER_OF(b->this.pool, struct mempool, this); + + pw_log_debug("%p: mapping:%p block:%p fd:%d ptr:%p size:%u block-ref:%d", + p, m, b, b->this.fd, m->ptr, m->size, b->this.ref); + + if (m->do_unmap) + munmap(m->ptr, m->size); + spa_list_remove(&m->link); + free(m); +} + +static void mapping_unmap(struct mapping *m) +{ + struct memblock *b = m->block; + struct mempool *p = SPA_CONTAINER_OF(b->this.pool, struct mempool, this); + pw_log_debug("%p: mapping:%p block:%p fd:%d ptr:%p size:%u block-ref:%d", + p, m, b, b->this.fd, m->ptr, m->size, b->this.ref); + mapping_free(m); + pw_memblock_unref(&b->this); +} + +SPA_EXPORT +struct pw_memmap * pw_memblock_map(struct pw_memblock *block, + enum pw_memmap_flags flags, uint32_t offset, uint32_t size, uint32_t tag[5]) +{ + struct memblock *b = SPA_CONTAINER_OF(block, struct memblock, this); + struct mempool *p = SPA_CONTAINER_OF(block->pool, struct mempool, this); + struct mapping *m; + struct memmap *mm; + struct pw_map_range range; + + pw_map_range_init(&range, offset, size, p->pagesize); + + m = memblock_find_mapping(b, flags, offset, size); + if (m == NULL) + m = memblock_map(b, flags, range.offset, range.size); + if (m == NULL) + return NULL; + + mm = calloc(1, sizeof(struct memmap)); + if (mm == NULL) { + if (m->ref == 0) + mapping_unmap(m); + return NULL; + } + + m->ref++; + mm->mapping = m; + mm->this.block = block; + mm->this.flags = flags; + mm->this.offset = offset; + mm->this.size = size; + mm->this.ptr = SPA_PTROFF(m->ptr, range.start, void); + + pw_log_debug("%p: map:%p block:%p fd:%d ptr:%p (%u %u) mapping:%p ref:%d", p, + &mm->this, b, b->this.fd, mm->this.ptr, offset, size, m, m->ref); + + if (tag) { + memcpy(mm->this.tag, tag, sizeof(mm->this.tag)); + pw_log_debug("%p: tag:%u:%u:%u:%u:%u", p, + tag[0], tag[1], tag[2], tag[3], tag[4]); + } + + spa_list_append(&b->memmaps, &mm->link); + + return &mm->this; +} + +SPA_EXPORT +struct pw_memmap * pw_mempool_map_id(struct pw_mempool *pool, + uint32_t id, enum pw_memmap_flags flags, uint32_t offset, uint32_t size, uint32_t tag[5]) +{ + struct mempool *impl = SPA_CONTAINER_OF(pool, struct mempool, this); + struct memblock *b; + + b = pw_map_lookup(&impl->map, id); + if (b == NULL) { + errno = ENOENT; + return NULL; + } + return pw_memblock_map(&b->this, flags, offset, size, tag); +} + +SPA_EXPORT +int pw_memmap_free(struct pw_memmap *map) +{ + struct memmap *mm; + struct mapping *m; + struct memblock *b; + struct mempool *p; + + if (map == NULL) + return 0; + + mm = SPA_CONTAINER_OF(map, struct memmap, this); + m = mm->mapping; + b = m->block; + p = SPA_CONTAINER_OF(b->this.pool, struct mempool, this); + + pw_log_debug("%p: map:%p block:%p fd:%d ptr:%p mapping:%p ref:%d", p, + &mm->this, b, b->this.fd, mm->this.ptr, m, m->ref); + + spa_list_remove(&mm->link); + + if (--m->ref == 0) + mapping_unmap(m); + + free(mm); + + return 0; +} + +static inline enum pw_memmap_flags block_flags_to_mem(enum pw_memblock_flags flags) +{ + enum pw_memmap_flags fl = 0; + + if (flags & PW_MEMBLOCK_FLAG_READABLE) + fl |= PW_MEMMAP_FLAG_READ; + if (flags & PW_MEMBLOCK_FLAG_WRITABLE) + fl |= PW_MEMMAP_FLAG_WRITE; + + return fl; +} + +/** Create a new memblock + * \param pool the pool to use + * \param flags memblock flags + * \param type the requested memory type one of enum spa_data_type + * \param size size to allocate + * \return a memblock structure or NULL with errno on error + */ +SPA_EXPORT +struct pw_memblock * pw_mempool_alloc(struct pw_mempool *pool, enum pw_memblock_flags flags, + uint32_t type, size_t size) +{ + struct mempool *impl = SPA_CONTAINER_OF(pool, struct mempool, this); + struct memblock *b; + int res; + + b = calloc(1, sizeof(struct memblock)); + if (b == NULL) + return NULL; + + b->this.ref = 1; + b->this.pool = pool; + b->this.flags = flags; + b->this.type = type; + b->this.size = size; + spa_list_init(&b->mappings); + spa_list_init(&b->memmaps); + +#ifdef HAVE_MEMFD_CREATE + char name[128]; + snprintf(name, sizeof(name), + "pipewire-memfd:flags=0x%08x,type=%" PRIu32 ",size=%zu", + (unsigned int) flags, type, size); + + b->this.fd = memfd_create(name, MFD_CLOEXEC | MFD_ALLOW_SEALING); + if (b->this.fd == -1) { + res = -errno; + pw_log_error("%p: Failed to create memfd: %m", pool); + goto error_free; + } +#elif defined(__FreeBSD__) || defined(__MidnightBSD__) + b->this.fd = shm_open(SHM_ANON, O_CREAT | O_RDWR | O_CLOEXEC, 0); + if (b->this.fd == -1) { + res = -errno; + pw_log_error("%p: Failed to create SHM_ANON fd: %m", pool); + goto error_free; + } +#else + char filename[128]; + snprintf(filename, sizeof(filename), + "/dev/shm/pipewire-tmpfile:flags=0x%08x,type=%" PRIu32 ",size=%zu:XXXXXX", + (unsigned int) flags, type, size); + + b->this.fd = mkostemp(filename, O_CLOEXEC); + if (b->this.fd == -1) { + res = -errno; + pw_log_error("%p: Failed to create temporary file: %m", pool); + goto error_free; + } + unlink(filename); +#endif + pw_log_debug("%p: new fd:%d", pool, b->this.fd); + + if (ftruncate(b->this.fd, size) < 0) { + res = -errno; + pw_log_warn("%p: Failed to truncate temporary file: %m", pool); + goto error_close; + } +#ifdef HAVE_MEMFD_CREATE + if (flags & PW_MEMBLOCK_FLAG_SEAL) { + unsigned int seals = F_SEAL_GROW | F_SEAL_SHRINK | F_SEAL_SEAL; + if (fcntl(b->this.fd, F_ADD_SEALS, seals) == -1) { + pw_log_warn("%p: Failed to add seals: %m", pool); + } + } +#endif + if (flags & PW_MEMBLOCK_FLAG_MAP && size > 0) { + b->this.map = pw_memblock_map(&b->this, + block_flags_to_mem(flags), 0, size, NULL); + if (b->this.map == NULL) { + res = -errno; + pw_log_warn("%p: Failed to map: %m", pool); + goto error_close; + } + b->this.ref--; + } + + b->this.id = pw_map_insert_new(&impl->map, b); + spa_list_append(&impl->blocks, &b->link); + pw_log_debug("%p: block:%p id:%d type:%u size:%zu", pool, + &b->this, b->this.id, type, size); + + if (!SPA_FLAG_IS_SET(flags, PW_MEMBLOCK_FLAG_DONT_NOTIFY)) + pw_mempool_emit_added(impl, &b->this); + + return &b->this; + +error_close: + pw_log_debug("%p: close fd:%d", pool, b->this.fd); + close(b->this.fd); +error_free: + free(b); + errno = -res; + return NULL; +} + +static struct memblock * mempool_find_fd(struct pw_mempool *pool, int fd) +{ + struct mempool *impl = SPA_CONTAINER_OF(pool, struct mempool, this); + struct memblock *b; + + spa_list_for_each(b, &impl->blocks, link) { + if (fd == b->this.fd) { + pw_log_debug("%p: found %p id:%u fd:%d ref:%d", + pool, &b->this, b->this.id, fd, b->this.ref); + return b; + } + } + return NULL; +} + +SPA_EXPORT +struct pw_memblock * pw_mempool_import(struct pw_mempool *pool, + enum pw_memblock_flags flags, uint32_t type, int fd) +{ + struct mempool *impl = SPA_CONTAINER_OF(pool, struct mempool, this); + struct memblock *b; + + b = mempool_find_fd(pool, fd); + if (b != NULL) { + b->this.ref++; + return &b->this; + } + + b = calloc(1, sizeof(struct memblock)); + if (b == NULL) + return NULL; + + spa_list_init(&b->memmaps); + spa_list_init(&b->mappings); + + b->this.ref = 1; + b->this.pool = pool; + b->this.type = type; + b->this.fd = fd; + b->this.flags = flags; + b->this.id = pw_map_insert_new(&impl->map, b); + spa_list_append(&impl->blocks, &b->link); + + pw_log_debug("%p: block:%p id:%u flags:%08x type:%u fd:%d", + pool, b, b->this.id, flags, type, fd); + + if (!SPA_FLAG_IS_SET(flags, PW_MEMBLOCK_FLAG_DONT_NOTIFY)) + pw_mempool_emit_added(impl, &b->this); + + return &b->this; +} + +SPA_EXPORT +struct pw_memblock * pw_mempool_import_block(struct pw_mempool *pool, + struct pw_memblock *mem) +{ + pw_log_debug("%p: import block:%p type:%d fd:%d", pool, + mem, mem->type, mem->fd); + return pw_mempool_import(pool, + mem->flags | PW_MEMBLOCK_FLAG_DONT_CLOSE, + mem->type, mem->fd); +} + +SPA_EXPORT +struct pw_memmap * pw_mempool_import_map(struct pw_mempool *pool, + struct pw_mempool *other, void *data, uint32_t size, uint32_t tag[5]) +{ + struct pw_memblock *old, *block; + struct memblock *b; + struct pw_memmap *map; + uint32_t offset; + + old = pw_mempool_find_ptr(other, data); + if (old == NULL || old->map == NULL) { + errno = EFAULT; + return NULL; + } + + block = pw_mempool_import_block(pool, old); + if (block == NULL) + return NULL; + + if (block->ref == 1) { + struct mapping *m; + + b = SPA_CONTAINER_OF(block, struct memblock, this); + + m = calloc(1, sizeof(struct mapping)); + if (m == NULL) { + pw_memblock_unref(block); + return NULL; + } + m->ptr = old->map->ptr; + m->block = b; + m->offset = old->map->offset; + m->size = old->map->size; + spa_list_append(&b->mappings, &m->link); + pw_log_debug("%p: mapping:%p block:%p offset:%u size:%u ref:%u", + pool, m, block, m->offset, m->size, block->ref); + } else { + block->ref--; + } + + offset = SPA_PTRDIFF(data, old->map->ptr); + + map = pw_memblock_map(block, + block_flags_to_mem(block->flags), offset, size, tag); + if (map == NULL) + return NULL; + + pw_log_debug("%p: from pool:%p block:%p id:%u data:%p size:%u ref:%d", + pool, other, block, block->id, data, size, block->ref); + + return map; +} + +SPA_EXPORT +int pw_mempool_remove_id(struct pw_mempool *pool, uint32_t id) +{ + struct mempool *impl = SPA_CONTAINER_OF(pool, struct mempool, this); + struct memblock *b; + + b = pw_map_lookup(&impl->map, id); + if (b == NULL) + return -ENOENT; + + pw_log_debug("%p: block:%p id:%u fd:%d ref:%d", + pool, b, id, b->this.fd, b->this.ref); + + b->this.id = SPA_ID_INVALID; + pw_map_remove(&impl->map, id); + pw_memblock_unref(&b->this); + + return 0; +} + +/** Free a memblock + * \param block a memblock + */ +SPA_EXPORT +void pw_memblock_free(struct pw_memblock *block) +{ + struct memblock *b = SPA_CONTAINER_OF(block, struct memblock, this); + struct pw_mempool *pool = block->pool; + struct mempool *impl = SPA_CONTAINER_OF(pool, struct mempool, this); + struct memmap *mm; + struct mapping *m; + + spa_return_if_fail(block != NULL); + + pw_log_debug("%p: block:%p id:%d fd:%d ref:%d", + pool, block, block->id, block->fd, block->ref); + + block->ref++; + if (block->map) + block->ref++; + + if (block->id != SPA_ID_INVALID) + pw_map_remove(&impl->map, block->id); + spa_list_remove(&b->link); + + if (!SPA_FLAG_IS_SET(block->flags, PW_MEMBLOCK_FLAG_DONT_NOTIFY)) + pw_mempool_emit_removed(impl, block); + + spa_list_consume(mm, &b->memmaps, link) + pw_memmap_free(&mm->this); + + spa_list_consume(m, &b->mappings, link) { + pw_log_warn("%p: stray mapping:%p", pool, m); + mapping_free(m); + } + + if (block->fd != -1 && !(block->flags & PW_MEMBLOCK_FLAG_DONT_CLOSE)) { + pw_log_debug("%p: close fd:%d", pool, block->fd); + close(block->fd); + } + free(b); +} + +SPA_EXPORT +struct pw_memblock * pw_mempool_find_ptr(struct pw_mempool *pool, const void *ptr) +{ + struct mempool *impl = SPA_CONTAINER_OF(pool, struct mempool, this); + struct memblock *b; + struct mapping *m; + + spa_list_for_each(b, &impl->blocks, link) { + spa_list_for_each(m, &b->mappings, link) { + if (ptr >= m->ptr && ptr < SPA_PTROFF(m->ptr, m->size, void)) { + pw_log_debug("%p: block:%p id:%u for %p", pool, + b, b->this.id, ptr); + return &b->this; + } + } + } + return NULL; +} + +SPA_EXPORT +struct pw_memblock * pw_mempool_find_id(struct pw_mempool *pool, uint32_t id) +{ + struct mempool *impl = SPA_CONTAINER_OF(pool, struct mempool, this); + struct memblock *b; + + b = pw_map_lookup(&impl->map, id); + pw_log_debug("%p: block:%p for %u", pool, b, id); + if (b == NULL) + return NULL; + + return &b->this; +} + +SPA_EXPORT +struct pw_memblock * pw_mempool_find_fd(struct pw_mempool *pool, int fd) +{ + struct memblock *b; + + b = mempool_find_fd(pool, fd); + if (b == NULL) + return NULL; + + return &b->this; +} + +SPA_EXPORT +struct pw_memmap * pw_mempool_find_tag(struct pw_mempool *pool, uint32_t tag[5], size_t size) +{ + struct mempool *impl = SPA_CONTAINER_OF(pool, struct mempool, this); + struct memblock *b; + struct memmap *mm; + + pw_log_debug("%p: find tag %u:%u:%u:%u:%u size:%zu", pool, + tag[0], tag[1], tag[2], tag[3], tag[4], size); + + spa_list_for_each(b, &impl->blocks, link) { + spa_list_for_each(mm, &b->memmaps, link) { + if (memcmp(tag, mm->this.tag, size) == 0) { + pw_log_debug("%p: found %p", pool, mm); + return &mm->this; + } + } + } + return NULL; +} diff --git a/src/pipewire/mem.h b/src/pipewire/mem.h new file mode 100644 index 0000000..4edfab6 --- /dev/null +++ b/src/pipewire/mem.h @@ -0,0 +1,212 @@ +/* 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. + */ + +#ifndef PIPEWIRE_MEM_H +#define PIPEWIRE_MEM_H + +#include <pipewire/properties.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** \defgroup pw_memblock Memory Blocks + * Memory allocation and pools. + */ + +/** + * \addtogroup pw_memblock + * \{ + */ + +/** Flags passed to \ref pw_mempool_alloc() */ +enum pw_memblock_flags { + PW_MEMBLOCK_FLAG_NONE = 0, + PW_MEMBLOCK_FLAG_READABLE = (1 << 0), /**< memory is readable */ + PW_MEMBLOCK_FLAG_WRITABLE = (1 << 1), /**< memory is writable */ + PW_MEMBLOCK_FLAG_SEAL = (1 << 2), /**< seal the fd */ + PW_MEMBLOCK_FLAG_MAP = (1 << 3), /**< mmap the fd */ + PW_MEMBLOCK_FLAG_DONT_CLOSE = (1 << 4), /**< don't close fd */ + PW_MEMBLOCK_FLAG_DONT_NOTIFY = (1 << 5), /**< don't notify events */ + + PW_MEMBLOCK_FLAG_READWRITE = PW_MEMBLOCK_FLAG_READABLE | PW_MEMBLOCK_FLAG_WRITABLE, +}; + +enum pw_memmap_flags { + PW_MEMMAP_FLAG_NONE = 0, + PW_MEMMAP_FLAG_READ = (1 << 0), /**< map in read mode */ + PW_MEMMAP_FLAG_WRITE = (1 << 1), /**< map in write mode */ + PW_MEMMAP_FLAG_TWICE = (1 << 2), /**< map the same area twice after each other, + * creating a circular ringbuffer */ + PW_MEMMAP_FLAG_PRIVATE = (1 << 3), /**< writes will be private */ + PW_MEMMAP_FLAG_LOCKED = (1 << 4), /**< lock the memory into RAM */ + PW_MEMMAP_FLAG_READWRITE = PW_MEMMAP_FLAG_READ | PW_MEMMAP_FLAG_WRITE, +}; + +struct pw_memchunk; + +/** + * + * A memory pool is a collection of pw_memblocks */ +struct pw_mempool { + struct pw_properties *props; +}; + +/** + * Memory block structure */ +struct pw_memblock { + struct pw_mempool *pool; /**< owner pool */ + uint32_t id; /**< unique id */ + int ref; /**< refcount */ + uint32_t flags; /**< flags for the memory block on of enum pw_memblock_flags */ + uint32_t type; /**< type of the fd, one of enum spa_data_type */ + int fd; /**< fd */ + uint32_t size; /**< size of memory */ + struct pw_memmap *map; /**< optional map when PW_MEMBLOCK_FLAG_MAP was given */ +}; + +/** a mapped region of a pw_memblock */ +struct pw_memmap { + struct pw_memblock *block; /**< owner memblock */ + void *ptr; /**< mapped pointer */ + uint32_t flags; /**< flags for the mapping on of enum pw_memmap_flags */ + uint32_t offset; /**< offset in memblock */ + uint32_t size; /**< size in memblock */ + uint32_t tag[5]; /**< user tag */ +}; + +struct pw_mempool_events { +#define PW_VERSION_MEMPOOL_EVENTS 0 + uint32_t version; + + /** the pool is destroyed */ + void (*destroy) (void *data); + + /** a new memory block is added to the pool */ + void (*added) (void *data, struct pw_memblock *block); + + /** a memory block is removed from the pool */ + void (*removed) (void *data, struct pw_memblock *block); +}; + +/** Create a new memory pool */ +struct pw_mempool *pw_mempool_new(struct pw_properties *props); + +/** Listen for events */ +void pw_mempool_add_listener(struct pw_mempool *pool, + struct spa_hook *listener, + const struct pw_mempool_events *events, + void *data); + +/** Clear a pool */ +void pw_mempool_clear(struct pw_mempool *pool); + +/** Clear and destroy a pool */ +void pw_mempool_destroy(struct pw_mempool *pool); + + +/** Allocate a memory block from the pool */ +struct pw_memblock * pw_mempool_alloc(struct pw_mempool *pool, + enum pw_memblock_flags flags, uint32_t type, size_t size); + +/** Import a block from another pool */ +struct pw_memblock * pw_mempool_import_block(struct pw_mempool *pool, + struct pw_memblock *mem); + +/** Import an fd into the pool */ +struct pw_memblock * pw_mempool_import(struct pw_mempool *pool, + enum pw_memblock_flags flags, uint32_t type, int fd); + +/** Free a memblock regardless of the refcount and destroy all mappings */ +void pw_memblock_free(struct pw_memblock *mem); + +/** Unref a memblock */ +static inline void pw_memblock_unref(struct pw_memblock *mem) +{ + if (--mem->ref == 0) + pw_memblock_free(mem); +} + +/** Remove a memblock for given \a id */ +int pw_mempool_remove_id(struct pw_mempool *pool, uint32_t id); + +/** Find memblock for given \a ptr */ +struct pw_memblock * pw_mempool_find_ptr(struct pw_mempool *pool, const void *ptr); + +/** Find memblock for given \a id */ +struct pw_memblock * pw_mempool_find_id(struct pw_mempool *pool, uint32_t id); + +/** Find memblock for given \a fd */ +struct pw_memblock * pw_mempool_find_fd(struct pw_mempool *pool, int fd); + + +/** Map a region of a memory block */ +struct pw_memmap * pw_memblock_map(struct pw_memblock *block, + enum pw_memmap_flags flags, uint32_t offset, uint32_t size, + uint32_t tag[5]); + +/** Map a region of a memory block with \a id */ +struct pw_memmap * pw_mempool_map_id(struct pw_mempool *pool, uint32_t id, + enum pw_memmap_flags flags, uint32_t offset, uint32_t size, + uint32_t tag[5]); + +struct pw_memmap * pw_mempool_import_map(struct pw_mempool *pool, + struct pw_mempool *other, void *data, uint32_t size, uint32_t tag[5]); + +/** find a map with the given tag */ +struct pw_memmap * pw_mempool_find_tag(struct pw_mempool *pool, uint32_t tag[5], size_t size); + +/** Unmap a region */ +int pw_memmap_free(struct pw_memmap *map); + + +/** parameters to map a memory range */ +struct pw_map_range { + uint32_t start; /** offset in first page with start of data */ + uint32_t offset; /** page aligned offset to map */ + uint32_t size; /** size to map */ +}; + +#define PW_MAP_RANGE_INIT (struct pw_map_range){ 0, } + +/** Calculate parameters to mmap() memory into \a range so that + * \a size bytes at \a offset can be mapped with mmap(). */ +static inline void pw_map_range_init(struct pw_map_range *range, + uint32_t offset, uint32_t size, + uint32_t page_size) +{ + range->offset = SPA_ROUND_DOWN_N(offset, page_size); + range->start = offset - range->offset; + range->size = SPA_ROUND_UP_N(range->start + size, page_size); +} + +/** + * \} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* PIPEWIRE_MEM_H */ diff --git a/src/pipewire/meson.build b/src/pipewire/meson.build new file mode 100644 index 0000000..b19631a --- /dev/null +++ b/src/pipewire/meson.build @@ -0,0 +1,138 @@ +pipewire_headers = [ + 'array.h', + 'buffers.h', + 'impl-core.h', + 'impl-client.h', + 'client.h', + 'conf.h', + 'context.h', + 'control.h', + 'core.h', + 'device.h', + 'impl-device.h', + 'data-loop.h', + 'factory.h', + 'impl-factory.h', + 'filter.h', + 'global.h', + 'keys.h', + 'impl.h', + 'i18n.h', + 'impl-link.h', + 'link.h', + 'log.h', + 'loop.h', + 'main-loop.h', + 'map.h', + 'mem.h', + 'impl-metadata.h', + 'impl-module.h', + 'module.h', + 'impl-node.h', + 'node.h', + 'permission.h', + 'pipewire.h', + 'impl-port.h', + 'port.h', + 'properties.h', + 'protocol.h', + 'proxy.h', + 'resource.h', + 'stream.h', + 'thread.h', + 'thread-loop.h', + 'type.h', + 'utils.h', + 'work-queue.h', +] + +pipewire_sources = [ + 'buffers.c', + 'impl-core.c', + 'impl-client.c', + 'conf.c', + 'context.c', + 'control.c', + 'core.c', + 'data-loop.c', + 'impl-device.c', + 'filter.c', + 'global.c', + 'introspect.c', + 'impl-link.c', + 'log.c', + 'loop.c', + 'main-loop.c', + 'mem.c', + 'impl-module.c', + 'impl-node.c', + 'impl-factory.c', + 'impl-metadata.c', + 'pipewire.c', + 'impl-port.c', + 'properties.c', + 'protocol.c', + 'proxy.c', + 'resource.c', + 'settings.c', + 'stream.c', + 'thread.c', + 'thread-loop.c', + 'utils.c', + 'work-queue.c', +] + +configure_file(input : 'version.h.in', + output : 'version.h', + install_dir : get_option('includedir') / pipewire_headers_dir, + configuration : versiondata) + + +install_headers(pipewire_headers, subdir : pipewire_headers_dir) + +libpipewire_c_args = [ + '-DOLD_MEDIA_SESSION_WORKAROUND=1' +] + +if host_machine.system() != 'freebsd' and host_machine.system() != 'midnightbsd' + libpipewire_c_args += [ + '-D_POSIX_C_SOURCE' + ] +endif + +libpipewire = shared_library(pipewire_name, pipewire_sources, + version : libversion, + soversion : soversion, + c_args : libpipewire_c_args, + include_directories : [pipewire_inc, configinc, includes_inc], + install : true, + dependencies : [spa_dep, dl_lib, mathlib, pthread_lib, libintl_dep, atomic_dep, ], +) + +pipewire_dep = declare_dependency(link_with : libpipewire, + include_directories : [pipewire_inc, configinc], + dependencies : [pthread_lib, atomic_dep, spa_dep], + variables : { + 'moduledir' : meson.current_build_dir() / '..' / 'modules', + 'confdatadir' : meson.current_build_dir() / '..' / 'daemon', + } +) + +pkgconfig.generate(libpipewire, + filebase : 'lib@0@'.format(pipewire_name), + requires : ['lib@0@'.format(spa_name)], + name : 'libpipewire', + subdirs : pipewire_name, + description : 'PipeWire Interface', + version : pipewire_version, + extra_cflags : '-D_REENTRANT', + variables : ['moduledir=${libdir}/@0@'.format(pipewire_name)], + uninstalled_variables : [ + 'moduledir=${prefix}/src/modules', + 'confdatadir=${prefix}/src/daemon', + ], +) + +meson.override_dependency('lib@0@'.format(pipewire_name), pipewire_dep) + +subdir('extensions') diff --git a/src/pipewire/module.h b/src/pipewire/module.h new file mode 100644 index 0000000..aba6de8 --- /dev/null +++ b/src/pipewire/module.h @@ -0,0 +1,121 @@ +/* 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. + */ + +#ifndef PIPEWIRE_MODULE_H +#define PIPEWIRE_MODULE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <spa/utils/defs.h> +#include <spa/utils/hook.h> + +#include <pipewire/proxy.h> + +/** \defgroup pw_module Module + * Module interface + */ + +/** + * \addtogroup pw_module + * \{ + */ +#define PW_TYPE_INTERFACE_Module PW_TYPE_INFO_INTERFACE_BASE "Module" + +#define PW_VERSION_MODULE 3 +struct pw_module; + +/** The module information. Extra information can be added in later versions */ +struct pw_module_info { + uint32_t id; /**< id of the global */ + const char *name; /**< name of the module */ + const char *filename; /**< filename of the module */ + const char *args; /**< arguments passed to the module */ +#define PW_MODULE_CHANGE_MASK_PROPS (1 << 0) +#define PW_MODULE_CHANGE_MASK_ALL ((1 << 1)-1) + uint64_t change_mask; /**< bitfield of changed fields since last call */ + struct spa_dict *props; /**< extra properties */ +}; + +/** Update and existing \ref pw_module_info with \a update with reset */ +struct pw_module_info * +pw_module_info_update(struct pw_module_info *info, + const struct pw_module_info *update); +/** Merge and existing \ref pw_module_info with \a update */ +struct pw_module_info * +pw_module_info_merge(struct pw_module_info *info, + const struct pw_module_info *update, bool reset); +/** Free a \ref pw_module_info */ +void pw_module_info_free(struct pw_module_info *info); + +#define PW_MODULE_EVENT_INFO 0 +#define PW_MODULE_EVENT_NUM 1 + +/** Module events */ +struct pw_module_events { +#define PW_VERSION_MODULE_EVENTS 0 + uint32_t version; + /** + * Notify module info + * + * \param info info about the module + */ + void (*info) (void *data, const struct pw_module_info *info); +}; + +#define PW_MODULE_METHOD_ADD_LISTENER 0 +#define PW_MODULE_METHOD_NUM 1 + +/** Module methods */ +struct pw_module_methods { +#define PW_VERSION_MODULE_METHODS 0 + uint32_t version; + + int (*add_listener) (void *object, + struct spa_hook *listener, + const struct pw_module_events *events, + void *data); +}; + +#define pw_module_method(o,method,version,...) \ +({ \ + int _res = -ENOTSUP; \ + spa_interface_call_res((struct spa_interface*)o, \ + struct pw_module_methods, _res, \ + method, version, ##__VA_ARGS__); \ + _res; \ +}) + +#define pw_module_add_listener(c,...) pw_module_method(c,add_listener,0,__VA_ARGS__) + +/** + * \} + */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* PIPEWIRE_MODULE_H */ diff --git a/src/pipewire/node.h b/src/pipewire/node.h new file mode 100644 index 0000000..e5b8995 --- /dev/null +++ b/src/pipewire/node.h @@ -0,0 +1,216 @@ +/* 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. + */ + +#ifndef PIPEWIRE_NODE_H +#define PIPEWIRE_NODE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <stdarg.h> +#include <errno.h> + +#include <spa/utils/defs.h> +#include <spa/utils/hook.h> +#include <spa/node/command.h> +#include <spa/param/param.h> + +#include <pipewire/proxy.h> + +/** \defgroup pw_node Node + * Node interface + */ + +/** + * \addtogroup pw_node + * \{ + */ +#define PW_TYPE_INTERFACE_Node PW_TYPE_INFO_INTERFACE_BASE "Node" + +#define PW_VERSION_NODE 3 +struct pw_node; + +/** \enum pw_node_state The different node states */ +enum pw_node_state { + PW_NODE_STATE_ERROR = -1, /**< error state */ + PW_NODE_STATE_CREATING = 0, /**< the node is being created */ + PW_NODE_STATE_SUSPENDED = 1, /**< the node is suspended, the device might + * be closed */ + PW_NODE_STATE_IDLE = 2, /**< the node is running but there is no active + * port */ + PW_NODE_STATE_RUNNING = 3, /**< the node is running */ +}; + +/** Convert a \ref pw_node_state to a readable string */ +const char * pw_node_state_as_string(enum pw_node_state state); + +/** The node information. Extra information can be added in later versions */ +struct pw_node_info { + uint32_t id; /**< id of the global */ + uint32_t max_input_ports; /**< maximum number of inputs */ + uint32_t max_output_ports; /**< maximum number of outputs */ +#define PW_NODE_CHANGE_MASK_INPUT_PORTS (1 << 0) +#define PW_NODE_CHANGE_MASK_OUTPUT_PORTS (1 << 1) +#define PW_NODE_CHANGE_MASK_STATE (1 << 2) +#define PW_NODE_CHANGE_MASK_PROPS (1 << 3) +#define PW_NODE_CHANGE_MASK_PARAMS (1 << 4) +#define PW_NODE_CHANGE_MASK_ALL ((1 << 5)-1) + uint64_t change_mask; /**< bitfield of changed fields since last call */ + uint32_t n_input_ports; /**< number of inputs */ + uint32_t n_output_ports; /**< number of outputs */ + enum pw_node_state state; /**< the current state of the node */ + const char *error; /**< an error reason if \a state is error */ + struct spa_dict *props; /**< the properties of the node */ + struct spa_param_info *params; /**< parameters */ + uint32_t n_params; /**< number of items in \a params */ +}; + +struct pw_node_info * +pw_node_info_update(struct pw_node_info *info, + const struct pw_node_info *update); + +struct pw_node_info * +pw_node_info_merge(struct pw_node_info *info, + const struct pw_node_info *update, bool reset); + +void +pw_node_info_free(struct pw_node_info *info); + +#define PW_NODE_EVENT_INFO 0 +#define PW_NODE_EVENT_PARAM 1 +#define PW_NODE_EVENT_NUM 2 + +/** Node events */ +struct pw_node_events { +#define PW_VERSION_NODE_EVENTS 0 + uint32_t version; + /** + * Notify node info + * + * \param info info about the node + */ + void (*info) (void *data, const struct pw_node_info *info); + /** + * Notify a node param + * + * Event emitted as a result of the enum_params method. + * + * \param seq the sequence number of the request + * \param id the param id + * \param index the param index + * \param next the param index of the next param + * \param param the parameter + */ + void (*param) (void *data, int seq, + uint32_t id, uint32_t index, uint32_t next, + const struct spa_pod *param); +}; + +#define PW_NODE_METHOD_ADD_LISTENER 0 +#define PW_NODE_METHOD_SUBSCRIBE_PARAMS 1 +#define PW_NODE_METHOD_ENUM_PARAMS 2 +#define PW_NODE_METHOD_SET_PARAM 3 +#define PW_NODE_METHOD_SEND_COMMAND 4 +#define PW_NODE_METHOD_NUM 5 + +/** Node methods */ +struct pw_node_methods { +#define PW_VERSION_NODE_METHODS 0 + uint32_t version; + + int (*add_listener) (void *object, + struct spa_hook *listener, + const struct pw_node_events *events, + void *data); + /** + * Subscribe to parameter changes + * + * Automatically emit param events for the given ids when + * they are changed. + * + * \param ids an array of param ids + * \param n_ids the number of ids in \a ids + */ + int (*subscribe_params) (void *object, uint32_t *ids, uint32_t n_ids); + + /** + * Enumerate node parameters + * + * Start enumeration of node parameters. For each param, a + * param event will be emitted. + * + * \param seq a sequence number to place in the reply + * \param id the parameter id to enum or PW_ID_ANY for all + * \param start the start index or 0 for the first param + * \param num the maximum number of params to retrieve + * \param filter a param filter or NULL + */ + int (*enum_params) (void *object, int seq, uint32_t id, + uint32_t start, uint32_t num, + const struct spa_pod *filter); + + /** + * Set a parameter on the node + * + * \param id the parameter id to set + * \param flags extra parameter flags + * \param param the parameter to set + */ + int (*set_param) (void *object, uint32_t id, uint32_t flags, + const struct spa_pod *param); + + /** + * Send a command to the node + * + * \param command the command to send + */ + int (*send_command) (void *object, const struct spa_command *command); +}; + +#define pw_node_method(o,method,version,...) \ +({ \ + int _res = -ENOTSUP; \ + spa_interface_call_res((struct spa_interface*)o, \ + struct pw_node_methods, _res, \ + method, version, ##__VA_ARGS__); \ + _res; \ +}) + +/** Node */ +#define pw_node_add_listener(c,...) pw_node_method(c,add_listener,0,__VA_ARGS__) +#define pw_node_subscribe_params(c,...) pw_node_method(c,subscribe_params,0,__VA_ARGS__) +#define pw_node_enum_params(c,...) pw_node_method(c,enum_params,0,__VA_ARGS__) +#define pw_node_set_param(c,...) pw_node_method(c,set_param,0,__VA_ARGS__) +#define pw_node_send_command(c,...) pw_node_method(c,send_command,0,__VA_ARGS__) + +/** + * \} + */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* PIPEWIRE_NODE_H */ diff --git a/src/pipewire/permission.h b/src/pipewire/permission.h new file mode 100644 index 0000000..1bcebc3 --- /dev/null +++ b/src/pipewire/permission.h @@ -0,0 +1,86 @@ +/* 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. + */ + +#ifndef PIPEWIRE_PERMISSION_H +#define PIPEWIRE_PERMISSION_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <spa/utils/defs.h> + +/** \defgroup pw_permission Permission + * + * Permissions are kept for a client and describe what the client is + * allowed to do with an object. + * + * See \ref page_core_api + */ + +/** + * \addtogroup pw_permission + * \{ + */ + +#define PW_PERM_R 0400 /**< object can be seen and events can be received */ +#define PW_PERM_W 0200 /**< methods can be called that modify the object */ +#define PW_PERM_X 0100 /**< methods can be called on the object. The W flag must be + * present in order to call methods that modify the object. */ +#define PW_PERM_M 0010 /**< metadata can be set on object, Since 0.3.9 */ + +#define PW_PERM_RWX (PW_PERM_R|PW_PERM_W|PW_PERM_X) +#define PW_PERM_RWXM (PW_PERM_RWX|PW_PERM_M) + +#define PW_PERM_IS_R(p) (((p)&PW_PERM_R) == PW_PERM_R) +#define PW_PERM_IS_W(p) (((p)&PW_PERM_W) == PW_PERM_W) +#define PW_PERM_IS_X(p) (((p)&PW_PERM_X) == PW_PERM_X) +#define PW_PERM_IS_M(p) (((p)&PW_PERM_M) == PW_PERM_M) + +#define PW_PERM_ALL PW_PERM_RWXM +#define PW_PERM_INVALID (uint32_t)(0xffffffff) + +struct pw_permission { + uint32_t id; /**< id of object, PW_ID_ANY for default permission */ + uint32_t permissions; /**< bitmask of above permissions */ +}; + +#define PW_PERMISSION_INIT(id,p) ((struct pw_permission){ (id), (p) }) + +#define PW_PERMISSION_FORMAT "%c%c%c%c" +#define PW_PERMISSION_ARGS(permission) \ + (permission) & PW_PERM_R ? 'r' : '-', \ + (permission) & PW_PERM_W ? 'w' : '-', \ + (permission) & PW_PERM_X ? 'x' : '-', \ + (permission) & PW_PERM_M ? 'm' : '-' + +/** + * \} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* PIPEWIRE_PERMISSION_H */ diff --git a/src/pipewire/pipewire.c b/src/pipewire/pipewire.c new file mode 100644 index 0000000..104b52b --- /dev/null +++ b/src/pipewire/pipewire.c @@ -0,0 +1,871 @@ +/* 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 <unistd.h> +#include <limits.h> +#include <stdio.h> +#if !defined(__FreeBSD__) && !defined(__MidnightBSD__) +#include <sys/prctl.h> +#endif +#include <pwd.h> +#include <errno.h> +#include <dlfcn.h> +#include <pthread.h> + +#include <locale.h> +#include <libintl.h> + +#include <valgrind/valgrind.h> + +#include <spa/utils/names.h> +#include <spa/utils/string.h> +#include <spa/support/cpu.h> +#include <spa/support/i18n.h> + +#include "pipewire.h" +#include "private.h" + +#define MAX_SUPPORT 32 + +#define SUPPORTLIB "support/libspa-support" + +PW_LOG_TOPIC_EXTERN(log_context); +#define PW_LOG_TOPIC_DEFAULT log_context + +static char *prgname; + +static struct spa_i18n *_pipewire_i18n = NULL; + +struct plugin { + struct spa_list link; + char *filename; + void *hnd; + spa_handle_factory_enum_func_t enum_func; + int ref; +}; + +struct handle { + struct spa_list link; + struct plugin *plugin; + char *factory_name; + int ref; + struct spa_handle handle SPA_ALIGNED(8); +}; + +struct registry { + struct spa_list plugins; + struct spa_list handles; /* all handles across all plugins by age (youngest first) */ +}; + +struct support { + const char *plugin_dir; + const char *support_lib; + struct registry registry; + char *i18n_domain; + struct spa_interface i18n_iface; + struct spa_support support[MAX_SUPPORT]; + uint32_t n_support; + uint32_t init_count; + unsigned int in_valgrind:1; + unsigned int no_color:1; + unsigned int no_config:1; + unsigned int do_dlclose:1; +}; + +static pthread_mutex_t init_lock = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t support_lock = PTHREAD_MUTEX_INITIALIZER; +static struct support global_support; + +static struct plugin * +find_plugin(struct registry *registry, const char *filename) +{ + struct plugin *p; + spa_list_for_each(p, ®istry->plugins, link) { + if (spa_streq(p->filename, filename)) + return p; + } + return NULL; +} + +static struct plugin * +open_plugin(struct registry *registry, + const char *path, size_t len, const char *lib) +{ + struct plugin *plugin; + char filename[PATH_MAX]; + void *hnd; + spa_handle_factory_enum_func_t enum_func; + int res; + + if ((res = spa_scnprintf(filename, sizeof(filename), "%.*s/%s.so", (int)len, path, lib)) < 0) + goto error_out; + + if ((plugin = find_plugin(registry, filename)) != NULL) { + plugin->ref++; + return plugin; + } + + if ((hnd = dlopen(filename, RTLD_NOW)) == NULL) { + res = -ENOENT; + pw_log_debug("can't load %s: %s", filename, dlerror()); + goto error_out; + } + if ((enum_func = dlsym(hnd, SPA_HANDLE_FACTORY_ENUM_FUNC_NAME)) == NULL) { + res = -ENOSYS; + pw_log_debug("can't find enum function: %s", dlerror()); + goto error_dlclose; + } + + if ((plugin = calloc(1, sizeof(struct plugin))) == NULL) { + res = -errno; + goto error_dlclose; + } + + pw_log_debug("loaded plugin:'%s'", filename); + plugin->ref = 1; + plugin->filename = strdup(filename); + plugin->hnd = hnd; + plugin->enum_func = enum_func; + + spa_list_append(®istry->plugins, &plugin->link); + + return plugin; + +error_dlclose: + dlclose(hnd); +error_out: + errno = -res; + return NULL; +} + +static void +unref_plugin(struct plugin *plugin) +{ + if (--plugin->ref == 0) { + spa_list_remove(&plugin->link); + pw_log_debug("unloaded plugin:'%s'", plugin->filename); + if (global_support.do_dlclose) + dlclose(plugin->hnd); + free(plugin->filename); + free(plugin); + } +} + +static const struct spa_handle_factory *find_factory(struct plugin *plugin, const char *factory_name) +{ + int res = -ENOENT; + uint32_t index; + const struct spa_handle_factory *factory; + + for (index = 0;;) { + if ((res = plugin->enum_func(&factory, &index)) <= 0) { + if (res == 0) + break; + goto out; + } + if (factory->version < 1) { + pw_log_warn("factory version %d < 1 not supported", + factory->version); + continue; + } + if (spa_streq(factory->name, factory_name)) + return factory; + } + res = -ENOENT; +out: + pw_log_debug("can't find factory %s: %s", factory_name, spa_strerror(res)); + errno = -res; + return NULL; +} + +static void unref_handle(struct handle *handle) +{ + if (--handle->ref == 0) { + spa_list_remove(&handle->link); + pw_log_debug("clear handle '%s'", handle->factory_name); + pthread_mutex_unlock(&support_lock); + spa_handle_clear(&handle->handle); + pthread_mutex_lock(&support_lock); + unref_plugin(handle->plugin); + free(handle->factory_name); + free(handle); + } +} + +SPA_EXPORT +uint32_t pw_get_support(struct spa_support *support, uint32_t max_support) +{ + uint32_t i, n = SPA_MIN(global_support.n_support, max_support); + for (i = 0; i < n; i++) + support[i] = global_support.support[i]; + return n; +} + +static struct spa_handle *load_spa_handle(const char *lib, + const char *factory_name, + const struct spa_dict *info, + uint32_t n_support, + const struct spa_support support[]) +{ + struct support *sup = &global_support; + struct plugin *plugin; + struct handle *handle; + const struct spa_handle_factory *factory; + const char *state = NULL, *p; + int res; + size_t len; + + if (factory_name == NULL) { + res = -EINVAL; + goto error_out; + } + + if (lib == NULL) + lib = sup->support_lib; + + pw_log_debug("load lib:'%s' factory-name:'%s'", lib, factory_name); + + plugin = NULL; + res = -ENOENT; + + if (sup->plugin_dir == NULL) { + pw_log_error("load lib: plugin directory undefined, set SPA_PLUGIN_DIR"); + goto error_out; + } + while ((p = pw_split_walk(sup->plugin_dir, ":", &len, &state))) { + if ((plugin = open_plugin(&sup->registry, p, len, lib)) != NULL) + break; + res = -errno; + } + if (plugin == NULL) + goto error_out; + + pthread_mutex_unlock(&support_lock); + + factory = find_factory(plugin, factory_name); + if (factory == NULL) { + res = -errno; + goto error_unref_plugin; + } + + handle = calloc(1, sizeof(struct handle) + spa_handle_factory_get_size(factory, info)); + if (handle == NULL) { + res = -errno; + goto error_unref_plugin; + } + + if ((res = spa_handle_factory_init(factory, + &handle->handle, info, + support, n_support)) < 0) { + pw_log_debug("can't make factory instance '%s': %d (%s)", + factory_name, res, spa_strerror(res)); + goto error_free_handle; + } + + pthread_mutex_lock(&support_lock); + handle->ref = 1; + handle->plugin = plugin; + handle->factory_name = strdup(factory_name); + spa_list_prepend(&sup->registry.handles, &handle->link); + + return &handle->handle; + +error_free_handle: + free(handle); +error_unref_plugin: + pthread_mutex_lock(&support_lock); + unref_plugin(plugin); +error_out: + errno = -res; + return NULL; +} + +SPA_EXPORT +struct spa_handle *pw_load_spa_handle(const char *lib, + const char *factory_name, + const struct spa_dict *info, + uint32_t n_support, + const struct spa_support support[]) +{ + struct spa_handle *handle; + pthread_mutex_lock(&support_lock); + handle = load_spa_handle(lib, factory_name, info, n_support, support); + pthread_mutex_unlock(&support_lock); + return handle; +} + +static struct handle *find_handle(struct spa_handle *handle) +{ + struct registry *registry = &global_support.registry; + struct handle *h; + + spa_list_for_each(h, ®istry->handles, link) { + if (&h->handle == handle) + return h; + } + + return NULL; +} + +SPA_EXPORT +int pw_unload_spa_handle(struct spa_handle *handle) +{ + struct handle *h; + int res = 0; + + pthread_mutex_lock(&support_lock); + if ((h = find_handle(handle)) == NULL) + res = -ENOENT; + else + unref_handle(h); + pthread_mutex_unlock(&support_lock); + + return res; +} + +static void *add_interface(struct support *support, + const char *factory_name, + const char *type, + const struct spa_dict *info) +{ + struct spa_handle *handle; + void *iface = NULL; + int res = -ENOENT; + + handle = load_spa_handle(support->support_lib, + factory_name, info, + support->n_support, support->support); + if (handle == NULL) + return NULL; + + pthread_mutex_unlock(&support_lock); + res = spa_handle_get_interface(handle, type, &iface); + pthread_mutex_lock(&support_lock); + + if (res < 0 || iface == NULL) { + pw_log_error("can't get %s interface %d: %s", type, res, + spa_strerror(res)); + return NULL; + } + + support->support[support->n_support++] = + SPA_SUPPORT_INIT(type, iface); + return iface; +} + +SPA_EXPORT +int pw_set_domain(const char *domain) +{ + struct support *support = &global_support; + free(support->i18n_domain); + if (domain == NULL) + support->i18n_domain = NULL; + else if ((support->i18n_domain = strdup(domain)) == NULL) + return -errno; + return 0; +} + +SPA_EXPORT +const char *pw_get_domain(void) +{ + struct support *support = &global_support; + return support->i18n_domain; +} + +static const char *i18n_text(void *object, const char *msgid) +{ + struct support *support = object; + return dgettext(support->i18n_domain, msgid); +} + +static const char *i18n_ntext(void *object, const char *msgid, const char *msgid_plural, + unsigned long int n) +{ + struct support *support = object; + return dngettext(support->i18n_domain, msgid, msgid_plural, n); +} + +static void init_i18n(struct support *support) +{ + bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR); + bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"); + pw_set_domain(GETTEXT_PACKAGE); +} + +static void *add_i18n(struct support *support) +{ + static const struct spa_i18n_methods i18n_methods = { + SPA_VERSION_I18N_METHODS, + .text = i18n_text, + .ntext = i18n_ntext, + }; + + support->i18n_iface = SPA_INTERFACE_INIT( + SPA_TYPE_INTERFACE_I18N, + SPA_VERSION_I18N, + &i18n_methods, support); + _pipewire_i18n = (struct spa_i18n*) &support->i18n_iface; + + support->support[support->n_support++] = + SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_I18N, _pipewire_i18n); + + return 0; +} + +SPA_EXPORT +const char *pw_gettext(const char *msgid) +{ + return spa_i18n_text(_pipewire_i18n, msgid); +} +SPA_EXPORT +const char *pw_ngettext(const char *msgid, const char *msgid_plural, unsigned long int n) +{ + return spa_i18n_ntext(_pipewire_i18n, msgid, msgid_plural, n); +} + +#ifdef HAVE_SYSTEMD +static struct spa_log *load_journal_logger(struct support *support, + const struct spa_dict *info) +{ + struct spa_handle *handle; + void *iface = NULL; + int res = -ENOENT; + uint32_t i; + + /* is the journal even available? */ + if (access("/run/systemd/journal/socket", F_OK) != 0) + return NULL; + + handle = load_spa_handle("support/libspa-journal", + SPA_NAME_SUPPORT_LOG, info, + support->n_support, support->support); + if (handle == NULL) + return NULL; + + pthread_mutex_unlock(&support_lock); + res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_Log, &iface); + pthread_mutex_lock(&support_lock); + + if (res < 0 || iface == NULL) { + pw_log_error("can't get log interface %d: %s", res, + spa_strerror(res)); + return NULL; + } + + /* look for an existing logger, and + * replace it with the journal logger */ + for (i = 0; i < support->n_support; i++) { + if (spa_streq(support->support[i].type, SPA_TYPE_INTERFACE_Log)) { + support->support[i].data = iface; + break; + } + } + return (struct spa_log *) iface; +} +#endif + +static bool +parse_log_level(const char *str, enum spa_log_level *l) +{ + uint32_t lvl; + if (strlen(str) == 1) { + switch(str[0]) { + case 'X': lvl = SPA_LOG_LEVEL_NONE; break; + case 'E': lvl = SPA_LOG_LEVEL_ERROR; break; + case 'W': lvl = SPA_LOG_LEVEL_WARN; break; + case 'I': lvl = SPA_LOG_LEVEL_INFO; break; + case 'D': lvl = SPA_LOG_LEVEL_DEBUG; break; + case 'T': lvl = SPA_LOG_LEVEL_TRACE; break; + default: + goto check_int; + } + } else { +check_int: + if (!spa_atou32(str, &lvl, 0)) + return false; + if (lvl > SPA_LOG_LEVEL_TRACE) + return false; + } + *l = lvl; + return true; +} + +static char * +parse_pw_debug_env(void) +{ + const char *str; + char **tokens; + int n_tokens; + size_t slen; + char json[1024] = {0}; + char *pos = json; + char *end = pos + sizeof(json) - 1; + enum spa_log_level lvl; + + str = getenv("PIPEWIRE_DEBUG"); + + if (!str || (slen = strlen(str)) == 0) + return NULL; + + /* String format is PIPEWIRE_DEBUG=[<glob>:]<level>,..., + * converted into [{ conn.* = 0}, {glob = level}, {glob = level}, ....] , + * with the connection namespace disabled by default. + */ + pos += spa_scnprintf(pos, end - pos, "[ { conn.* = %d },", SPA_LOG_LEVEL_NONE); + + tokens = pw_split_strv(str, ",", INT_MAX, &n_tokens); + if (n_tokens > 0) { + int i; + for (i = 0; i < n_tokens; i++) { + int n_tok; + char **tok; + char *pattern; + + tok = pw_split_strv(tokens[i], ":", 2, &n_tok); + if (n_tok == 2 && parse_log_level(tok[1], &lvl)) { + pattern = tok[0]; + pos += spa_scnprintf(pos, end - pos, "{ %s = %d },", + pattern, lvl); + } else if (n_tok == 1 && parse_log_level(tok[0], &lvl)) { + pw_log_set_level(lvl); + } else { + pw_log_warn("Ignoring invalid format in PIPEWIRE_DEBUG: '%s'", + tokens[i]); + } + + pw_free_strv(tok); + } + } + pw_free_strv(tokens); + pos += spa_scnprintf(pos, end - pos, "]"); + return strdup(json); +} + +/** Initialize PipeWire + * + * \param argc pointer to argc + * \param argv pointer to argv + * + * Initialize the PipeWire system, parse and modify any parameters given + * by \a argc and \a argv and set up debugging. + * + * This function can be called multiple times. + */ +SPA_EXPORT +void pw_init(int *argc, char **argv[]) +{ + const char *str; + struct spa_dict_item items[6]; + uint32_t n_items; + struct spa_dict info; + struct support *support = &global_support; + struct spa_log *log; + char level[32]; + + pthread_mutex_lock(&init_lock); + if (support->init_count > 0) + goto done; + + pthread_mutex_lock(&support_lock); + support->in_valgrind = RUNNING_ON_VALGRIND; + + support->do_dlclose = true; + if ((str = getenv("PIPEWIRE_DLCLOSE")) != NULL) + support->do_dlclose = pw_properties_parse_bool(str); + + if (getenv("NO_COLOR") != NULL) + support->no_color = true; + + if ((str = getenv("PIPEWIRE_NO_CONFIG")) != NULL) + support->no_config = pw_properties_parse_bool(str); + + init_i18n(support); + + if ((str = getenv("SPA_PLUGIN_DIR")) == NULL) + str = PLUGINDIR; + support->plugin_dir = str; + + if ((str = getenv("SPA_SUPPORT_LIB")) == NULL) + str = SUPPORTLIB; + support->support_lib = str; + + spa_list_init(&support->registry.plugins); + spa_list_init(&support->registry.handles); + + if (pw_log_is_default()) { + char *patterns = NULL; + + n_items = 0; + if (!support->no_color) + items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_LOG_COLORS, "true"); + items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_LOG_TIMESTAMP, "true"); + if ((str = getenv("PIPEWIRE_LOG_LINE")) == NULL || spa_atob(str)) + items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_LOG_LINE, "true"); + snprintf(level, sizeof(level), "%d", pw_log_level); + items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_LOG_LEVEL, level); + if ((str = getenv("PIPEWIRE_LOG")) != NULL) + items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_LOG_FILE, str); + if ((patterns = parse_pw_debug_env()) != NULL) + items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_LOG_PATTERNS, patterns); + info = SPA_DICT_INIT(items, n_items); + + log = add_interface(support, SPA_NAME_SUPPORT_LOG, SPA_TYPE_INTERFACE_Log, &info); + if (log) + pw_log_set(log); + +#ifdef HAVE_SYSTEMD + if ((str = getenv("PIPEWIRE_LOG_SYSTEMD")) == NULL || spa_atob(str)) { + log = load_journal_logger(support, &info); + if (log) + pw_log_set(log); + } +#endif + free(patterns); + } else { + support->support[support->n_support++] = + SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_Log, pw_log_get()); + } + + pw_log_init(); + + n_items = 0; + if ((str = getenv("PIPEWIRE_CPU"))) + items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_CPU_FORCE, str); + if ((str = getenv("PIPEWIRE_VM"))) + items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_CPU_VM_TYPE, str); + info = SPA_DICT_INIT(items, n_items); + + add_interface(support, SPA_NAME_SUPPORT_CPU, SPA_TYPE_INTERFACE_CPU, &info); + + add_i18n(support); + + pw_log_info("version %s", pw_get_library_version()); + pthread_mutex_unlock(&support_lock); +done: + support->init_count++; + pthread_mutex_unlock(&init_lock); +} + +/** Deinitialize PipeWire + * + * Deinitialize the PipeWire system and free up all resources allocated + * by pw_init(). + * + * Before 0.3.49 this function can only be called once after which the pipewire + * library can not be used again. This is usually called by test programs to + * check for memory leaks. + * + * Since 0.3.49 this function must be paired with an equal amount of pw_init() + * calls to deinitialize the PipeWire library. The PipeWire library can be + * used again after being deinitialized with a new pw_init() call. + */ +SPA_EXPORT +void pw_deinit(void) +{ + struct support *support = &global_support; + struct registry *registry = &support->registry; + struct handle *h; + + pthread_mutex_lock(&init_lock); + if (support->init_count == 0) + goto done; + if (--support->init_count > 0) + goto done; + + pthread_mutex_lock(&support_lock); + pw_log_set(NULL); + + spa_list_consume(h, ®istry->handles, link) + unref_handle(h); + + free(support->i18n_domain); + spa_zero(global_support); + pthread_mutex_unlock(&support_lock); +done: + pthread_mutex_unlock(&init_lock); + +} + +/** Check if a debug category is enabled + * + * \param name the name of the category to check + * \return true if enabled + * + * Debugging categories can be enabled by using the PIPEWIRE_DEBUG + * environment variable + */ +SPA_EXPORT +bool pw_debug_is_category_enabled(const char *name) +{ + struct spa_log_topic t = SPA_LOG_TOPIC(0, name); + PW_LOG_TOPIC_INIT(&t); + return t.has_custom_level; +} + +/** Get the application name */ +SPA_EXPORT +const char *pw_get_application_name(void) +{ + errno = ENOTSUP; + return NULL; +} + +static void init_prgname(void) +{ + static char name[PATH_MAX]; + + spa_memzero(name, sizeof(name)); +#if defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__MidnightBSD_kernel__) + { + if (readlink("/proc/self/exe", name, sizeof(name)-1) > 0) { + prgname = strrchr(name, '/') + 1; + return; + } + } +#endif +#if defined __FreeBSD__ || defined(__MidnightBSD__) + { + ssize_t len; + + if ((len = readlink("/proc/curproc/file", name, sizeof(name)-1)) > 0) { + prgname = strrchr(name, '/') + 1; + return; + } + } +#endif +#if !defined(__FreeBSD__) && !defined(__MidnightBSD__) + { + if (prctl(PR_GET_NAME, (unsigned long) name, 0, 0, 0) == 0) { + prgname = name; + return; + } + } +#endif + snprintf(name, sizeof(name), "pid-%d", getpid()); + prgname = name; +} + +/** Get the program name */ +SPA_EXPORT +const char *pw_get_prgname(void) +{ + static pthread_once_t prgname_is_initialized = PTHREAD_ONCE_INIT; + + pthread_once(&prgname_is_initialized, init_prgname); + return prgname; +} + +/** Get the user name */ +SPA_EXPORT +const char *pw_get_user_name(void) +{ + struct passwd *pw; + + if ((pw = getpwuid(getuid()))) + return pw->pw_name; + + return NULL; +} + +/** Get the host name */ +SPA_EXPORT +const char *pw_get_host_name(void) +{ + static char hname[256]; + + if (gethostname(hname, 256) < 0) + return NULL; + + hname[255] = 0; + return hname; +} + +SPA_EXPORT +bool pw_in_valgrind(void) +{ + return global_support.in_valgrind; +} + +SPA_EXPORT +bool pw_check_option(const char *option, const char *value) +{ + if (spa_streq(option, "in-valgrind")) + return global_support.in_valgrind == spa_atob(value); + else if (spa_streq(option, "no-color")) + return global_support.no_color == spa_atob(value); + else if (spa_streq(option, "no-config")) + return global_support.no_config == spa_atob(value); + else if (spa_streq(option, "do-dlclose")) + return global_support.do_dlclose == spa_atob(value); + return false; +} + +/** Get the client name + * + * Make a new PipeWire client name that can be used to construct a remote. + * + */ +SPA_EXPORT +const char *pw_get_client_name(void) +{ + const char *cc; + static char cname[256]; + + if ((cc = pw_get_application_name()) || (cc = pw_get_prgname())) + return cc; + else if (snprintf(cname, sizeof(cname), "pipewire-pid-%zd", (size_t) getpid()) < 0) + return NULL; + return cname; +} + +/** Reverse the direction */ +SPA_EXPORT +enum pw_direction pw_direction_reverse(enum pw_direction direction) +{ + if (direction == PW_DIRECTION_INPUT) + return PW_DIRECTION_OUTPUT; + else if (direction == PW_DIRECTION_OUTPUT) + return PW_DIRECTION_INPUT; + return direction; +} + +/** Get the currently running version */ +SPA_EXPORT +const char* pw_get_library_version(void) +{ + return pw_get_headers_version(); +} + +static const struct spa_type_info type_info[] = { + { SPA_ID_INVALID, SPA_ID_INVALID, "spa_types", spa_types }, + { 0, 0, NULL, NULL }, +}; + +SPA_EXPORT +const struct spa_type_info * pw_type_info(void) +{ + return type_info; +} diff --git a/src/pipewire/pipewire.h b/src/pipewire/pipewire.h new file mode 100644 index 0000000..ed24fbd --- /dev/null +++ b/src/pipewire/pipewire.h @@ -0,0 +1,123 @@ +/* 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. + */ + +#ifndef PIPEWIRE_H +#define PIPEWIRE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <spa/support/plugin.h> + +#include <pipewire/array.h> +#include <pipewire/client.h> +#include <pipewire/conf.h> +#include <pipewire/context.h> +#include <pipewire/device.h> +#include <pipewire/buffers.h> +#include <pipewire/core.h> +#include <pipewire/factory.h> +#include <pipewire/keys.h> +#include <pipewire/log.h> +#include <pipewire/loop.h> +#include <pipewire/link.h> +#include <pipewire/main-loop.h> +#include <pipewire/map.h> +#include <pipewire/mem.h> +#include <pipewire/module.h> +#include <pipewire/node.h> +#include <pipewire/properties.h> +#include <pipewire/proxy.h> +#include <pipewire/permission.h> +#include <pipewire/protocol.h> +#include <pipewire/port.h> +#include <pipewire/stream.h> +#include <pipewire/filter.h> +#include <pipewire/thread-loop.h> +#include <pipewire/data-loop.h> +#include <pipewire/type.h> +#include <pipewire/utils.h> +#include <pipewire/version.h> + +/** \defgroup pw_pipewire Initialization + * Initializing PipeWire and loading SPA modules. + */ + +/** + * \addtogroup pw_pipewire + * \{ + */ +void +pw_init(int *argc, char **argv[]); + +void pw_deinit(void); + +bool +pw_debug_is_category_enabled(const char *name); + +const char * +pw_get_application_name(void); + +const char * +pw_get_prgname(void); + +const char * +pw_get_user_name(void); + +const char * +pw_get_host_name(void); + +const char * +pw_get_client_name(void); + +bool pw_in_valgrind(void); + +bool pw_check_option(const char *option, const char *value); + +enum pw_direction +pw_direction_reverse(enum pw_direction direction); + +int pw_set_domain(const char *domain); +const char *pw_get_domain(void); + +uint32_t pw_get_support(struct spa_support *support, uint32_t max_support); + +struct spa_handle *pw_load_spa_handle(const char *lib, + const char *factory_name, + const struct spa_dict *info, + uint32_t n_support, + const struct spa_support support[]); + +int pw_unload_spa_handle(struct spa_handle *handle); + +/** + * \} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* PIPEWIRE_H */ diff --git a/src/pipewire/port.h b/src/pipewire/port.h new file mode 100644 index 0000000..463af23 --- /dev/null +++ b/src/pipewire/port.h @@ -0,0 +1,179 @@ +/* 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. + */ + +#ifndef PIPEWIRE_PORT_H +#define PIPEWIRE_PORT_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <stdarg.h> +#include <errno.h> + +#include <spa/utils/defs.h> +#include <spa/utils/hook.h> +#include <spa/param/param.h> + +#include <pipewire/proxy.h> + +/** \defgroup pw_port Port + * Port interface + */ + +/** + * \addtogroup pw_port + * \{ + */ + +#define PW_TYPE_INTERFACE_Port PW_TYPE_INFO_INTERFACE_BASE "Port" + +#define PW_VERSION_PORT 3 +struct pw_port; + +/** The direction of a port */ +#define pw_direction spa_direction +#define PW_DIRECTION_INPUT SPA_DIRECTION_INPUT +#define PW_DIRECTION_OUTPUT SPA_DIRECTION_OUTPUT + +/** Convert a \ref pw_direction to a readable string */ +const char * pw_direction_as_string(enum pw_direction direction); + +struct pw_port_info { + uint32_t id; /**< id of the global */ + enum pw_direction direction; /**< port direction */ +#define PW_PORT_CHANGE_MASK_PROPS (1 << 0) +#define PW_PORT_CHANGE_MASK_PARAMS (1 << 1) +#define PW_PORT_CHANGE_MASK_ALL ((1 << 2)-1) + uint64_t change_mask; /**< bitfield of changed fields since last call */ + struct spa_dict *props; /**< the properties of the port */ + struct spa_param_info *params; /**< parameters */ + uint32_t n_params; /**< number of items in \a params */ +}; + +struct pw_port_info * +pw_port_info_update(struct pw_port_info *info, + const struct pw_port_info *update); + +struct pw_port_info * +pw_port_info_merge(struct pw_port_info *info, + const struct pw_port_info *update, bool reset); + +void +pw_port_info_free(struct pw_port_info *info); + +#define PW_PORT_EVENT_INFO 0 +#define PW_PORT_EVENT_PARAM 1 +#define PW_PORT_EVENT_NUM 2 + +/** Port events */ +struct pw_port_events { +#define PW_VERSION_PORT_EVENTS 0 + uint32_t version; + /** + * Notify port info + * + * \param info info about the port + */ + void (*info) (void *data, const struct pw_port_info *info); + /** + * Notify a port param + * + * Event emitted as a result of the enum_params method. + * + * \param seq the sequence number of the request + * \param id the param id + * \param index the param index + * \param next the param index of the next param + * \param param the parameter + */ + void (*param) (void *data, int seq, + uint32_t id, uint32_t index, uint32_t next, + const struct spa_pod *param); +}; + +#define PW_PORT_METHOD_ADD_LISTENER 0 +#define PW_PORT_METHOD_SUBSCRIBE_PARAMS 1 +#define PW_PORT_METHOD_ENUM_PARAMS 2 +#define PW_PORT_METHOD_NUM 3 + +/** Port methods */ +struct pw_port_methods { +#define PW_VERSION_PORT_METHODS 0 + uint32_t version; + + int (*add_listener) (void *object, + struct spa_hook *listener, + const struct pw_port_events *events, + void *data); + /** + * Subscribe to parameter changes + * + * Automatically emit param events for the given ids when + * they are changed. + * + * \param ids an array of param ids + * \param n_ids the number of ids in \a ids + */ + int (*subscribe_params) (void *object, uint32_t *ids, uint32_t n_ids); + + /** + * Enumerate port parameters + * + * Start enumeration of port parameters. For each param, a + * param event will be emitted. + * + * \param seq a sequence number returned in the reply + * \param id the parameter id to enumerate + * \param start the start index or 0 for the first param + * \param num the maximum number of params to retrieve + * \param filter a param filter or NULL + */ + int (*enum_params) (void *object, int seq, + uint32_t id, uint32_t start, uint32_t num, + const struct spa_pod *filter); +}; + +#define pw_port_method(o,method,version,...) \ +({ \ + int _res = -ENOTSUP; \ + spa_interface_call_res((struct spa_interface*)o, \ + struct pw_port_methods, _res, \ + method, version, ##__VA_ARGS__); \ + _res; \ +}) + +#define pw_port_add_listener(c,...) pw_port_method(c,add_listener,0,__VA_ARGS__) +#define pw_port_subscribe_params(c,...) pw_port_method(c,subscribe_params,0,__VA_ARGS__) +#define pw_port_enum_params(c,...) pw_port_method(c,enum_params,0,__VA_ARGS__) + +/** + * \} + */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* PIPEWIRE_PORT_H */ diff --git a/src/pipewire/private.h b/src/pipewire/private.h new file mode 100644 index 0000000..6fa8d77 --- /dev/null +++ b/src/pipewire/private.h @@ -0,0 +1,1310 @@ +/* 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. + */ + +#ifndef PIPEWIRE_PRIVATE_H +#define PIPEWIRE_PRIVATE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <sys/socket.h> +#include <sys/types.h> /* for pthread_t */ + +#include "pipewire/impl.h" + +#include <spa/support/plugin.h> +#include <spa/pod/builder.h> +#include <spa/param/latency-utils.h> +#include <spa/utils/result.h> +#include <spa/utils/type-info.h> + +#if defined(__FreeBSD__) || defined(__MidnightBSD__) +struct ucred { +}; +#endif + +#define MAX_RATES 32u +#define CLOCK_MIN_QUANTUM 4u +#define CLOCK_MAX_QUANTUM 65536u + +struct settings { + uint32_t log_level; + uint32_t clock_rate; /* default clock rate */ + uint32_t clock_rates[MAX_RATES]; /* allowed clock rates */ + uint32_t n_clock_rates; /* number of alternative clock rates */ + uint32_t clock_quantum; /* default quantum */ + uint32_t clock_min_quantum; /* min quantum */ + uint32_t clock_max_quantum; /* max quantum */ + uint32_t clock_quantum_limit; /* quantum limit */ + struct spa_rectangle video_size; + struct spa_fraction video_rate; + uint32_t link_max_buffers; + unsigned int mem_warn_mlock:1; + unsigned int mem_allow_mlock:1; + unsigned int clock_power_of_two_quantum:1; + unsigned int check_quantum:1; + unsigned int check_rate:1; +#define CLOCK_RATE_UPDATE_MODE_HARD 0 +#define CLOCK_RATE_UPDATE_MODE_SOFT 1 + int clock_rate_update_mode; + uint32_t clock_force_rate; /* force a clock rate */ + uint32_t clock_force_quantum; /* force a quantum */ +}; + +struct ratelimit { + uint64_t interval; + uint64_t begin; + unsigned burst; + unsigned n_printed, n_missed; +}; + +static inline bool ratelimit_test(struct ratelimit *r, uint64_t now, enum spa_log_level level) +{ + if (r->begin + r->interval < now) { + if (r->n_missed) + pw_log(level, "%u events suppressed", r->n_missed); + r->begin = now; + r->n_printed = 0; + r->n_missed = 0; + } else if (r->n_printed >= r->burst) { + r->n_missed++; + return false; + } + r->n_printed++; + return true; +} + +#define MAX_PARAMS 32 + +struct pw_param { + uint32_t id; + int32_t seq; + struct spa_list link; + struct spa_pod *param; +}; + +static inline uint32_t pw_param_clear(struct spa_list *param_list, uint32_t id) +{ + struct pw_param *p, *t; + uint32_t count = 0; + + spa_list_for_each_safe(p, t, param_list, link) { + if (id == SPA_ID_INVALID || p->id == id) { + spa_list_remove(&p->link); + free(p); + count++; + } + } + return count; +} + +static inline struct pw_param *pw_param_add(struct spa_list *params, int32_t seq, + uint32_t id, const struct spa_pod *param) +{ + struct pw_param *p; + + if (id == SPA_ID_INVALID) { + if (param == NULL || !spa_pod_is_object(param)) { + errno = EINVAL; + return NULL; + } + id = SPA_POD_OBJECT_ID(param); + } + + if ((p = malloc(sizeof(*p) + (param != NULL ? SPA_POD_SIZE(param) : 0))) == NULL) + return NULL; + + p->id = id; + p->seq = seq; + if (param != NULL) { + p->param = SPA_PTROFF(p, sizeof(*p), struct spa_pod); + memcpy(p->param, param, SPA_POD_SIZE(param)); + } else { + pw_param_clear(params, id); + p->param = NULL; + } + spa_list_append(params, &p->link); + return p; +} + +static inline void pw_param_update(struct spa_list *param_list, struct spa_list *pending_list, + uint32_t n_params, struct spa_param_info *params) +{ + struct pw_param *p, *t; + uint32_t i; + + for (i = 0; i < n_params; i++) { + spa_list_for_each_safe(p, t, pending_list, link) { + if (p->id == params[i].id && + p->seq != params[i].seq && + p->param != NULL) { + spa_list_remove(&p->link); + free(p); + } + } + } + spa_list_consume(p, pending_list, link) { + spa_list_remove(&p->link); + if (p->param == NULL) { + pw_param_clear(param_list, p->id); + free(p); + } else { + spa_list_append(param_list, &p->link); + } + } +} + +static inline struct spa_param_info *pw_param_info_find(struct spa_param_info info[], + uint32_t n_info, uint32_t id) +{ + uint32_t i; + for (i = 0; i < n_info; i++) { + if (info[i].id == id) + return &info[i]; + } + return NULL; +} + +#define pw_protocol_emit_destroy(p) spa_hook_list_call(&(p)->listener_list, struct pw_protocol_events, destroy, 0) + +struct pw_protocol { + struct spa_list link; /**< link in context protocol_list */ + struct pw_context *context; /**< context for this protocol */ + + char *name; /**< type name of the protocol */ + + struct spa_list marshal_list; /**< list of marshallers for supported interfaces */ + struct spa_list client_list; /**< list of current clients */ + struct spa_list server_list; /**< list of current servers */ + struct spa_hook_list listener_list; /**< event listeners */ + + const struct pw_protocol_implementation *implementation; /**< implementation of the protocol */ + + const void *extension; /**< extension API */ + + void *user_data; /**< user data for the implementation */ +}; + +/** the permission function. It returns the allowed access permissions for \a global + * for \a client */ +typedef uint32_t (*pw_permission_func_t) (struct pw_global *global, + struct pw_impl_client *client, void *data); + +#define pw_impl_client_emit(o,m,v,...) spa_hook_list_call(&o->listener_list, struct pw_impl_client_events, m, v, ##__VA_ARGS__) + +#define pw_impl_client_emit_destroy(o) pw_impl_client_emit(o, destroy, 0) +#define pw_impl_client_emit_free(o) pw_impl_client_emit(o, free, 0) +#define pw_impl_client_emit_initialized(o) pw_impl_client_emit(o, initialized, 0) +#define pw_impl_client_emit_info_changed(o,i) pw_impl_client_emit(o, info_changed, 0, i) +#define pw_impl_client_emit_resource_added(o,r) pw_impl_client_emit(o, resource_added, 0, r) +#define pw_impl_client_emit_resource_impl(o,r) pw_impl_client_emit(o, resource_impl, 0, r) +#define pw_impl_client_emit_resource_removed(o,r) pw_impl_client_emit(o, resource_removed, 0, r) +#define pw_impl_client_emit_busy_changed(o,b) pw_impl_client_emit(o, busy_changed, 0, b) + +enum spa_node0_event { + SPA_NODE0_EVENT_START = SPA_TYPE_VENDOR_PipeWire, + SPA_NODE0_EVENT_RequestClockUpdate, +}; + +enum spa_node0_command { + SPA_NODE0_COMMAND_START = SPA_TYPE_VENDOR_PipeWire, + SPA_NODE0_COMMAND_ClockUpdate, +}; + +struct protocol_compat_v2 { + /* v2 typemap */ + struct pw_map types; + unsigned int send_types:1; +}; + +#define pw_impl_core_emit(s,m,v,...) spa_hook_list_call(&s->listener_list, struct pw_impl_core_events, m, v, ##__VA_ARGS__) + +#define pw_impl_core_emit_destroy(s) pw_impl_core_emit(s, destroy, 0) +#define pw_impl_core_emit_free(s) pw_impl_core_emit(s, free, 0) +#define pw_impl_core_emit_initialized(s) pw_impl_core_emit(s, initialized, 0) + +struct pw_impl_core { + struct pw_context *context; + struct spa_list link; /**< link in context object core_impl list */ + struct pw_global *global; /**< global object created for this core */ + struct spa_hook global_listener; + + struct pw_properties *properties; /**< core properties */ + struct pw_core_info info; /**< core info */ + + struct spa_hook_list listener_list; + void *user_data; /**< extra user data */ + + unsigned int registered:1; +}; + +#define pw_impl_metadata_emit(s,m,v,...) spa_hook_list_call(&s->listener_list, struct pw_impl_metadata_events, m, v, ##__VA_ARGS__) + +#define pw_impl_metadata_emit_destroy(s) pw_impl_metadata_emit(s, destroy, 0) +#define pw_impl_metadata_emit_free(s) pw_impl_metadata_emit(s, free, 0) +#define pw_impl_metadata_emit_property(s, ...) pw_impl_metadata_emit(s, property, 0, __VA_ARGS__) + +struct pw_impl_metadata { + struct pw_context *context; /**< the context */ + struct spa_list link; /**< link in context metadata_list */ + struct pw_global *global; /**< global for this metadata */ + struct spa_hook global_listener; + + struct pw_properties *properties; /**< properties of the metadata */ + + struct pw_metadata *metadata; + struct spa_hook metadata_listener; + + struct spa_hook_list listener_list; /**< event listeners */ + void *user_data; + + unsigned int registered:1; +}; + +struct pw_impl_client { + struct pw_impl_core *core; /**< core object */ + struct pw_context *context; /**< context object */ + + struct spa_list link; /**< link in context object client list */ + struct pw_global *global; /**< global object created for this client */ + struct spa_hook global_listener; + + pw_permission_func_t permission_func; /**< get permissions of an object */ + void *permission_data; /**< data passed to permission function */ + + struct pw_properties *properties; /**< Client properties */ + + struct pw_client_info info; /**< client info */ + + struct pw_mempool *pool; /**< client mempool */ + struct pw_resource *core_resource; /**< core resource object */ + struct pw_resource *client_resource; /**< client resource object */ + + struct pw_map objects; /**< list of resource objects */ + + struct spa_hook_list listener_list; + + struct pw_protocol *protocol; /**< protocol in use */ + int recv_seq; /**< last received sequence number */ + int send_seq; /**< last sender sequence number */ + uint64_t recv_generation; /**< last received registry generation */ + uint64_t sent_generation; /**< last sent registry generation */ + + void *user_data; /**< extra user data */ + + struct ucred ucred; /**< ucred information */ + unsigned int registered:1; + unsigned int ucred_valid:1; /**< if the ucred member is valid */ + unsigned int busy:1; + unsigned int destroyed:1; + + int refcount; + + /* v2 compatibility data */ + void *compat_v2; +}; + +#define pw_global_emit(o,m,v,...) spa_hook_list_call(&o->listener_list, struct pw_global_events, m, v, ##__VA_ARGS__) + +#define pw_global_emit_registering(g) pw_global_emit(g, registering, 0) +#define pw_global_emit_destroy(g) pw_global_emit(g, destroy, 0) +#define pw_global_emit_free(g) pw_global_emit(g, free, 0) +#define pw_global_emit_permissions_changed(g,...) pw_global_emit(g, permissions_changed, 0, __VA_ARGS__) + +struct pw_global { + struct pw_context *context; /**< the context */ + + struct spa_list link; /**< link in context list of globals */ + uint32_t id; /**< server id of the object */ + + struct pw_properties *properties; /**< properties of the global */ + + struct spa_hook_list listener_list; + + const char *type; /**< type of interface */ + uint32_t version; /**< version of interface */ + + pw_global_bind_func_t func; /**< bind function */ + void *object; /**< object associated with the interface */ + uint64_t serial; /**< increasing serial number */ + uint64_t generation; /**< registry generation number */ + + struct spa_list resource_list; /**< The list of resources of this global */ + + unsigned int registered:1; + unsigned int destroyed:1; +}; + +#define pw_core_resource(r,m,v,...) pw_resource_call(r, struct pw_core_events, m, v, ##__VA_ARGS__) +#define pw_core_resource_info(r,...) pw_core_resource(r,info,0,__VA_ARGS__) +#define pw_core_resource_done(r,...) pw_core_resource(r,done,0,__VA_ARGS__) +#define pw_core_resource_ping(r,...) pw_core_resource(r,ping,0,__VA_ARGS__) +#define pw_core_resource_error(r,...) pw_core_resource(r,error,0,__VA_ARGS__) +#define pw_core_resource_remove_id(r,...) pw_core_resource(r,remove_id,0,__VA_ARGS__) +#define pw_core_resource_bound_id(r,...) pw_core_resource(r,bound_id,0,__VA_ARGS__) +#define pw_core_resource_add_mem(r,...) pw_core_resource(r,add_mem,0,__VA_ARGS__) +#define pw_core_resource_remove_mem(r,...) pw_core_resource(r,remove_mem,0,__VA_ARGS__) + +static inline SPA_PRINTF_FUNC(5,0) void +pw_core_resource_errorv(struct pw_resource *resource, uint32_t id, int seq, + int res, const char *message, va_list args) +{ + char buffer[1024]; + vsnprintf(buffer, sizeof(buffer), message, args); + buffer[1023] = '\0'; + pw_log_debug("resource %p: id:%d seq:%d res:%d (%s) msg:\"%s\"", + resource, id, seq, res, spa_strerror(res), buffer); + if (resource) + pw_core_resource_error(resource, id, seq, res, buffer); + else + pw_log_error("id:%d seq:%d res:%d (%s) msg:\"%s\"", + id, seq, res, spa_strerror(res), buffer); +} + +static inline SPA_PRINTF_FUNC(5,6) void +pw_core_resource_errorf(struct pw_resource *resource, uint32_t id, int seq, + int res, const char *message, ...) +{ + va_list args; + va_start(args, message); + pw_core_resource_errorv(resource, id, seq, res, message, args); + va_end(args); +} + +#define pw_context_driver_emit(c,m,v,...) spa_hook_list_call_simple(&c->driver_listener_list, struct pw_context_driver_events, m, v, ##__VA_ARGS__) +#define pw_context_driver_emit_start(c,n) pw_context_driver_emit(c, start, 0, n) +#define pw_context_driver_emit_xrun(c,n) pw_context_driver_emit(c, xrun, 0, n) +#define pw_context_driver_emit_incomplete(c,n) pw_context_driver_emit(c, incomplete, 0, n) +#define pw_context_driver_emit_timeout(c,n) pw_context_driver_emit(c, timeout, 0, n) +#define pw_context_driver_emit_drained(c,n) pw_context_driver_emit(c, drained, 0, n) +#define pw_context_driver_emit_complete(c,n) pw_context_driver_emit(c, complete, 0, n) + +struct pw_context_driver_events { +#define PW_VERSION_CONTEXT_DRIVER_EVENTS 0 + uint32_t version; + + /** The driver graph is started */ + void (*start) (void *data, struct pw_impl_node *node); + /** The driver under/overruns */ + void (*xrun) (void *data, struct pw_impl_node *node); + /** The driver could not complete the graph */ + void (*incomplete) (void *data, struct pw_impl_node *node); + /** The driver got a sync timeout */ + void (*timeout) (void *data, struct pw_impl_node *node); + /** a node drained */ + void (*drained) (void *data, struct pw_impl_node *node); + /** The driver completed the graph */ + void (*complete) (void *data, struct pw_impl_node *node); +}; + +#define pw_registry_resource(r,m,v,...) pw_resource_call(r, struct pw_registry_events,m,v,##__VA_ARGS__) +#define pw_registry_resource_global(r,...) pw_registry_resource(r,global,0,__VA_ARGS__) +#define pw_registry_resource_global_remove(r,...) pw_registry_resource(r,global_remove,0,__VA_ARGS__) + +#define pw_context_emit(o,m,v,...) spa_hook_list_call(&o->listener_list, struct pw_context_events, m, v, ##__VA_ARGS__) +#define pw_context_emit_destroy(c) pw_context_emit(c, destroy, 0) +#define pw_context_emit_free(c) pw_context_emit(c, free, 0) +#define pw_context_emit_info_changed(c,i) pw_context_emit(c, info_changed, 0, i) +#define pw_context_emit_check_access(c,cl) pw_context_emit(c, check_access, 0, cl) +#define pw_context_emit_global_added(c,g) pw_context_emit(c, global_added, 0, g) +#define pw_context_emit_global_removed(c,g) pw_context_emit(c, global_removed, 0, g) + +struct pw_context { + struct pw_impl_core *core; /**< core object */ + + struct pw_properties *conf; /**< configuration of the context */ + struct pw_properties *properties; /**< properties of the context */ + + struct settings defaults; /**< default parameters */ + struct settings settings; /**< current parameters */ + + void *settings_impl; /**< settings metadata */ + + struct pw_mempool *pool; /**< global memory pool */ + + uint64_t stamp; + uint64_t serial; + uint64_t generation; /**< registry generation number */ + struct pw_map globals; /**< map of globals */ + + struct spa_list core_impl_list; /**< list of core_imp */ + struct spa_list protocol_list; /**< list of protocols */ + struct spa_list core_list; /**< list of core connections */ + struct spa_list registry_resource_list; /**< list of registry resources */ + struct spa_list module_list; /**< list of modules */ + struct spa_list device_list; /**< list of devices */ + struct spa_list global_list; /**< list of globals */ + struct spa_list client_list; /**< list of clients */ + struct spa_list node_list; /**< list of nodes */ + struct spa_list factory_list; /**< list of factories */ + struct spa_list metadata_list; /**< list of metadata */ + struct spa_list link_list; /**< list of links */ + struct spa_list control_list[2]; /**< list of controls, indexed by direction */ + struct spa_list export_list; /**< list of export types */ + struct spa_list driver_list; /**< list of driver nodes */ + + struct spa_hook_list driver_listener_list; + struct spa_hook_list listener_list; + + struct spa_thread_utils *thread_utils; + struct pw_loop *main_loop; /**< main loop for control */ + struct pw_loop *data_loop; /**< data loop for data passing */ + struct pw_data_loop *data_loop_impl; + struct spa_system *data_system; /**< data system for data passing */ + struct pw_work_queue *work_queue; /**< work queue */ + + struct spa_support support[16]; /**< support for spa plugins */ + uint32_t n_support; /**< number of support items */ + struct pw_array factory_lib; /**< mapping of factory_name regexp to library */ + + struct pw_array objects; /**< objects */ + + struct pw_impl_client *current_client; /**< client currently executing code in mainloop */ + + long sc_pagesize; + unsigned int freewheeling:1; + + void *user_data; /**< extra user data */ +}; + +#define pw_data_loop_emit(o,m,v,...) spa_hook_list_call(&o->listener_list, struct pw_data_loop_events, m, v, ##__VA_ARGS__) +#define pw_data_loop_emit_destroy(o) pw_data_loop_emit(o, destroy, 0) + +struct pw_data_loop { + struct pw_loop *loop; + + struct spa_hook_list listener_list; + + struct spa_thread_utils *thread_utils; + + pthread_t thread; + unsigned int cancel:1; + unsigned int created:1; + unsigned int running:1; +}; + +#define pw_main_loop_emit(o,m,v,...) spa_hook_list_call(&o->listener_list, struct pw_main_loop_events, m, v, ##__VA_ARGS__) +#define pw_main_loop_emit_destroy(o) pw_main_loop_emit(o, destroy, 0) + +struct pw_main_loop { + struct pw_loop *loop; + + struct spa_hook_list listener_list; + + unsigned int created:1; + unsigned int running:1; +}; + +#define pw_impl_device_emit(o,m,v,...) spa_hook_list_call(&o->listener_list, struct pw_impl_device_events, m, v, ##__VA_ARGS__) +#define pw_impl_device_emit_destroy(m) pw_impl_device_emit(m, destroy, 0) +#define pw_impl_device_emit_free(m) pw_impl_device_emit(m, free, 0) +#define pw_impl_device_emit_initialized(m) pw_impl_device_emit(m, initialized, 0) +#define pw_impl_device_emit_info_changed(n,i) pw_impl_device_emit(n, info_changed, 0, i) + +struct pw_impl_device { + struct pw_context *context; /**< the context object */ + struct spa_list link; /**< link in the context device_list */ + struct pw_global *global; /**< global object for this device */ + struct spa_hook global_listener; + + struct pw_properties *properties; /**< properties of the device */ + struct pw_device_info info; /**< introspectable device info */ + struct spa_param_info params[MAX_PARAMS]; + + char *name; /**< device name for debug */ + + struct spa_device *device; /**< device implementation */ + struct spa_hook listener; + struct spa_hook_list listener_list; + + struct spa_list object_list; + + void *user_data; /**< device user_data */ + + unsigned int registered:1; +}; + +#define pw_impl_module_emit(o,m,v,...) spa_hook_list_call(&o->listener_list, struct pw_impl_module_events, m, v, ##__VA_ARGS__) +#define pw_impl_module_emit_destroy(m) pw_impl_module_emit(m, destroy, 0) +#define pw_impl_module_emit_free(m) pw_impl_module_emit(m, free, 0) +#define pw_impl_module_emit_initialized(m) pw_impl_module_emit(m, initialized, 0) +#define pw_impl_module_emit_registered(m) pw_impl_module_emit(m, registered, 0) + +struct pw_impl_module { + struct pw_context *context; /**< the context object */ + struct spa_list link; /**< link in the context module_list */ + struct pw_global *global; /**< global object for this module */ + struct spa_hook global_listener; + + struct pw_properties *properties; /**< properties of the module */ + struct pw_module_info info; /**< introspectable module info */ + + struct spa_hook_list listener_list; + + void *user_data; /**< module user_data */ +}; + +struct pw_node_activation_state { + int status; /**< current status, the result of spa_node_process() */ + int32_t required; /**< required number of signals */ + int32_t pending; /**< number of pending signals */ +}; + +static inline void pw_node_activation_state_reset(struct pw_node_activation_state *state) +{ + state->pending = state->required; +} + +#define pw_node_activation_state_dec(s,c) (__atomic_sub_fetch(&(s)->pending, c, __ATOMIC_SEQ_CST) == 0) + +struct pw_node_target { + struct spa_list link; + struct pw_impl_node *node; + struct pw_node_activation *activation; + int (*signal_func) (void *data); + void *data; + unsigned int active:1; +}; + +struct pw_node_activation { +#define PW_NODE_ACTIVATION_NOT_TRIGGERED 0 +#define PW_NODE_ACTIVATION_TRIGGERED 1 +#define PW_NODE_ACTIVATION_AWAKE 2 +#define PW_NODE_ACTIVATION_FINISHED 3 + uint32_t status; + + unsigned int version:1; + unsigned int pending_sync:1; /* a sync is pending */ + unsigned int pending_new_pos:1; /* a new position is pending */ + + struct pw_node_activation_state state[2]; /* one current state and one next state, + * as version flag */ + uint64_t signal_time; + uint64_t awake_time; + uint64_t finish_time; + uint64_t prev_signal_time; + + /* updates */ + struct spa_io_segment reposition; /* reposition info, used when driver reposition_owner + * has this node id */ + struct spa_io_segment segment; /* update for the extra segment info fields. + * used when driver segment_owner has this node id */ + + /* for drivers, shared with all nodes */ + uint32_t segment_owner[32]; /* id of owners for each segment info struct. + * nodes that want to update segment info need to + * CAS their node id in this array. */ + struct spa_io_position position; /* contains current position and segment info. + * extra info is updated by nodes that have set + * themselves as owner in the segment structs */ + + uint64_t sync_timeout; /* sync timeout in nanoseconds + * position goes to RUNNING without waiting any + * longer for sync clients. */ + uint64_t sync_left; /* number of cycles before timeout */ + + + float cpu_load[3]; /* averaged over short, medium, long time */ + uint32_t xrun_count; /* number of xruns */ + uint64_t xrun_time; /* time of last xrun in microseconds */ + uint64_t xrun_delay; /* delay of last xrun in microseconds */ + uint64_t max_delay; /* max of all xruns in microseconds */ + +#define PW_NODE_ACTIVATION_COMMAND_NONE 0 +#define PW_NODE_ACTIVATION_COMMAND_START 1 +#define PW_NODE_ACTIVATION_COMMAND_STOP 2 + uint32_t command; /* next command */ + uint32_t reposition_owner; /* owner id with new reposition info, last one + * to update wins */ +}; + +#define ATOMIC_CAS(v,ov,nv) \ +({ \ + __typeof__(v) __ov = (ov); \ + __atomic_compare_exchange_n(&(v), &__ov, (nv), \ + 0, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST); \ +}) + +#define ATOMIC_DEC(s) __atomic_sub_fetch(&(s), 1, __ATOMIC_SEQ_CST) +#define ATOMIC_INC(s) __atomic_add_fetch(&(s), 1, __ATOMIC_SEQ_CST) +#define ATOMIC_LOAD(s) __atomic_load_n(&(s), __ATOMIC_SEQ_CST) +#define ATOMIC_STORE(s,v) __atomic_store_n(&(s), (v), __ATOMIC_SEQ_CST) +#define ATOMIC_XCHG(s,v) __atomic_exchange_n(&(s), (v), __ATOMIC_SEQ_CST) + +#define SEQ_WRITE(s) ATOMIC_INC(s) +#define SEQ_WRITE_SUCCESS(s1,s2) ((s1) + 1 == (s2) && ((s2) & 1) == 0) + +#define SEQ_READ(s) ATOMIC_LOAD(s) +#define SEQ_READ_SUCCESS(s1,s2) ((s1) == (s2) && ((s2) & 1) == 0) + +#define pw_impl_node_emit(o,m,v,...) spa_hook_list_call(&o->listener_list, struct pw_impl_node_events, m, v, ##__VA_ARGS__) +#define pw_impl_node_emit_destroy(n) pw_impl_node_emit(n, destroy, 0) +#define pw_impl_node_emit_free(n) pw_impl_node_emit(n, free, 0) +#define pw_impl_node_emit_initialized(n) pw_impl_node_emit(n, initialized, 0) +#define pw_impl_node_emit_port_init(n,p) pw_impl_node_emit(n, port_init, 0, p) +#define pw_impl_node_emit_port_added(n,p) pw_impl_node_emit(n, port_added, 0, p) +#define pw_impl_node_emit_port_removed(n,p) pw_impl_node_emit(n, port_removed, 0, p) +#define pw_impl_node_emit_info_changed(n,i) pw_impl_node_emit(n, info_changed, 0, i) +#define pw_impl_node_emit_port_info_changed(n,p,i) pw_impl_node_emit(n, port_info_changed, 0, p, i) +#define pw_impl_node_emit_active_changed(n,a) pw_impl_node_emit(n, active_changed, 0, a) +#define pw_impl_node_emit_state_request(n,s) pw_impl_node_emit(n, state_request, 0, s) +#define pw_impl_node_emit_state_changed(n,o,s,e) pw_impl_node_emit(n, state_changed, 0, o, s, e) +#define pw_impl_node_emit_async_complete(n,s,r) pw_impl_node_emit(n, async_complete, 0, s, r) +#define pw_impl_node_emit_result(n,s,r,t,result) pw_impl_node_emit(n, result, 0, s, r, t, result) +#define pw_impl_node_emit_event(n,e) pw_impl_node_emit(n, event, 0, e) +#define pw_impl_node_emit_driver_changed(n,o,d) pw_impl_node_emit(n, driver_changed, 0, o, d) +#define pw_impl_node_emit_peer_added(n,p) pw_impl_node_emit(n, peer_added, 0, p) +#define pw_impl_node_emit_peer_removed(n,p) pw_impl_node_emit(n, peer_removed, 0, p) + +struct pw_impl_node { + struct pw_context *context; /**< context object */ + struct spa_list link; /**< link in context node_list */ + struct pw_global *global; /**< global for this node */ + struct spa_hook global_listener; + + struct pw_properties *properties; /**< properties of the node */ + + struct pw_node_info info; /**< introspectable node info */ + struct spa_param_info params[MAX_PARAMS]; + + char *name; /** for debug */ + + uint32_t priority_driver; /** priority for being driver */ + char group[128]; /** group to schedule this node in */ + uint64_t spa_flags; + + unsigned int registered:1; + unsigned int active:1; /**< if the node is active */ + unsigned int live:1; /**< if the node is live */ + unsigned int driver:1; /**< if the node can drive the graph */ + unsigned int exported:1; /**< if the node is exported */ + unsigned int remote:1; /**< if the node is implemented remotely */ + unsigned int driving:1; /**< a driving node is one of the driver nodes that + * is selected to drive the graph */ + unsigned int visited:1; /**< for sorting */ + unsigned int want_driver:1; /**< this node wants to be assigned to a driver */ + unsigned int passive:1; /**< driver graph only has passive links */ + unsigned int freewheel:1; /**< if this is the freewheel driver */ + unsigned int loopchecked:1; /**< for feedback loop checking */ + unsigned int always_process:1; /**< this node wants to always be processing, even when idle */ + unsigned int lock_quantum:1; /**< don't change graph quantum */ + unsigned int lock_rate:1; /**< don't change graph rate */ + unsigned int transport_sync:1; /**< supports transport sync */ + unsigned int current_pending:1; /**< a quantum/rate update is pending */ + unsigned int moved:1; /**< the node was moved drivers */ + unsigned int added:1; /**< the node was add to graph */ + unsigned int pause_on_idle:1; /**< Pause processing when IDLE */ + unsigned int suspend_on_idle:1; + unsigned int reconfigure:1; + + uint32_t port_user_data_size; /**< extra size for port user data */ + + struct spa_list driver_link; + struct pw_impl_node *driver_node; + struct spa_list follower_list; + struct spa_list follower_link; + + struct spa_list sort_link; /**< link used to sort nodes */ + + struct spa_node *node; /**< SPA node implementation */ + struct spa_hook listener; + + struct spa_list input_ports; /**< list of input ports */ + struct pw_map input_port_map; /**< map from port_id to port */ + struct spa_list output_ports; /**< list of output ports */ + struct pw_map output_port_map; /**< map from port_id to port */ + + struct spa_hook_list listener_list; + + struct pw_loop *data_loop; /**< the data loop for this node */ + + struct spa_fraction latency; /**< requested latency */ + struct spa_fraction max_latency; /**< maximum latency */ + struct spa_fraction rate; /**< requested rate */ + uint32_t force_quantum; /**< forced quantum */ + uint32_t force_rate; /**< forced rate */ + uint32_t stamp; /**< stamp of last update */ + struct spa_source source; /**< source to remotely trigger this node */ + struct pw_memblock *activation; + struct { + struct spa_io_clock *clock; /**< io area of the clock or NULL */ + struct spa_io_position *position; + struct pw_node_activation *activation; + + struct spa_list target_list; /* list of targets to signal after + * this node */ + struct pw_node_target driver_target; /* driver target that we signal */ + struct spa_list input_mix; /* our input ports (and mixers) */ + struct spa_list output_mix; /* output ports (and mixers) */ + + struct pw_node_target target; /* our target that is signaled by the + driver */ + struct spa_list driver_link; /* our link in driver */ + + struct ratelimit rate_limit; + } rt; + struct spa_fraction current_rate; + uint64_t current_quantum; + + void *user_data; /**< extra user data */ +}; + +struct pw_impl_port_mix { + struct spa_list link; + struct spa_list rt_link; + struct pw_impl_port *p; + struct { + enum spa_direction direction; + uint32_t port_id; + } port; + struct spa_io_buffers *io; + uint32_t id; + uint32_t peer_id; + unsigned int have_buffers:1; +}; + +struct pw_impl_port_implementation { +#define PW_VERSION_PORT_IMPLEMENTATION 0 + uint32_t version; + + int (*init_mix) (void *data, struct pw_impl_port_mix *mix); + int (*release_mix) (void *data, struct pw_impl_port_mix *mix); +}; + +#define pw_impl_port_call(p,m,v,...) \ +({ \ + int _res = 0; \ + spa_callbacks_call_res(&(p)->impl, \ + struct pw_impl_port_implementation, \ + _res, m, v, ## __VA_ARGS__); \ + _res; \ +}) + +#define pw_impl_port_call_init_mix(p,m) pw_impl_port_call(p,init_mix,0,m) +#define pw_impl_port_call_release_mix(p,m) pw_impl_port_call(p,release_mix,0,m) + +#define pw_impl_port_emit(o,m,v,...) spa_hook_list_call(&o->listener_list, struct pw_impl_port_events, m, v, ##__VA_ARGS__) +#define pw_impl_port_emit_destroy(p) pw_impl_port_emit(p, destroy, 0) +#define pw_impl_port_emit_free(p) pw_impl_port_emit(p, free, 0) +#define pw_impl_port_emit_initialized(p) pw_impl_port_emit(p, initialized, 0) +#define pw_impl_port_emit_info_changed(p,i) pw_impl_port_emit(p, info_changed, 0, i) +#define pw_impl_port_emit_link_added(p,l) pw_impl_port_emit(p, link_added, 0, l) +#define pw_impl_port_emit_link_removed(p,l) pw_impl_port_emit(p, link_removed, 0, l) +#define pw_impl_port_emit_state_changed(p,o,s,e) pw_impl_port_emit(p, state_changed, 0, o, s, e) +#define pw_impl_port_emit_control_added(p,c) pw_impl_port_emit(p, control_added, 0, c) +#define pw_impl_port_emit_control_removed(p,c) pw_impl_port_emit(p, control_removed, 0, c) +#define pw_impl_port_emit_param_changed(p,i) pw_impl_port_emit(p, param_changed, 1, i) +#define pw_impl_port_emit_latency_changed(p) pw_impl_port_emit(p, latency_changed, 2) + +#define PW_IMPL_PORT_IS_CONTROL(port) SPA_FLAG_MASK((port)->flags, \ + PW_IMPL_PORT_FLAG_BUFFERS|PW_IMPL_PORT_FLAG_CONTROL,\ + PW_IMPL_PORT_FLAG_CONTROL) +struct pw_impl_port { + struct spa_list link; /**< link in node port_list */ + + struct pw_impl_node *node; /**< owner node */ + struct pw_global *global; /**< global for this port */ + struct spa_hook global_listener; + +#define PW_IMPL_PORT_FLAG_TO_REMOVE (1<<0) /**< if the port should be removed from the + * implementation when destroyed */ +#define PW_IMPL_PORT_FLAG_BUFFERS (1<<1) /**< port has data */ +#define PW_IMPL_PORT_FLAG_CONTROL (1<<2) /**< port has control */ +#define PW_IMPL_PORT_FLAG_NO_MIXER (1<<3) /**< don't try to add mixer to port */ + uint32_t flags; + uint64_t spa_flags; + + enum pw_direction direction; /**< port direction */ + uint32_t port_id; /**< port id */ + + enum pw_impl_port_state state; /**< state of the port */ + const char *error; /**< error state */ + + struct pw_properties *properties; /**< properties of the port */ + struct pw_port_info info; + struct spa_param_info params[MAX_PARAMS]; + + struct pw_buffers buffers; /**< buffers managed by this port, only on + * output ports, shared with all links */ + + struct spa_list links; /**< list of \ref pw_impl_link */ + + struct spa_list control_list[2];/**< list of \ref pw_control indexed by direction */ + + struct spa_hook_list listener_list; + + struct spa_callbacks impl; + + struct spa_node *mix; /**< port buffer mix/split */ +#define PW_IMPL_PORT_MIX_FLAG_MULTI (1<<0) /**< multi input or output */ +#define PW_IMPL_PORT_MIX_FLAG_MIX_ONLY (1<<1) /**< only negotiate mix ports */ +#define PW_IMPL_PORT_MIX_FLAG_NEGOTIATE (1<<2) /**< negotiate buffers */ + uint32_t mix_flags; /**< flags for the mixing */ + struct spa_handle *mix_handle; /**< mix plugin handle */ + struct pw_buffers mix_buffers; /**< buffers between mixer and node */ + + struct spa_list mix_list; /**< list of \ref pw_impl_port_mix */ + struct pw_map mix_port_map; /**< map from port_id from mixer */ + uint32_t n_mix; + + struct { + struct spa_io_buffers io; /**< io area of the port */ + struct spa_io_clock clock; /**< io area of the clock */ + struct spa_list mix_list; + struct spa_list node_link; + } rt; /**< data only accessed from the data thread */ + unsigned int added:1; + unsigned int destroying:1; + int busy_count; + + struct spa_latency_info latency[2]; /**< latencies */ + unsigned int have_latency_param:1; + + void *owner_data; /**< extra owner data */ + void *user_data; /**< extra user data */ +}; + +struct pw_control_link { + struct spa_list out_link; + struct spa_list in_link; + struct pw_control *output; + struct pw_control *input; + uint32_t out_port; + uint32_t in_port; + unsigned int valid:1; +}; + +#define pw_impl_link_emit(o,m,v,...) spa_hook_list_call(&o->listener_list, struct pw_impl_link_events, m, v, ##__VA_ARGS__) +#define pw_impl_link_emit_destroy(l) pw_impl_link_emit(l, destroy, 0) +#define pw_impl_link_emit_free(l) pw_impl_link_emit(l, free, 0) +#define pw_impl_link_emit_initialized(l) pw_impl_link_emit(l, initialized, 0) +#define pw_impl_link_emit_info_changed(l,i) pw_impl_link_emit(l, info_changed, 0, i) +#define pw_impl_link_emit_state_changed(l,...) pw_impl_link_emit(l, state_changed, 0, __VA_ARGS__) +#define pw_impl_link_emit_port_unlinked(l,p) pw_impl_link_emit(l, port_unlinked, 0, p) + +struct pw_impl_link { + struct pw_context *context; /**< context object */ + struct spa_list link; /**< link in context link_list */ + struct pw_global *global; /**< global for this link */ + struct spa_hook global_listener; + + char *name; + + struct pw_link_info info; /**< introspectable link info */ + struct pw_properties *properties; /**< extra link properties */ + + struct spa_io_buffers *io; /**< link io area */ + + struct pw_impl_port *output; /**< output port */ + struct spa_list output_link; /**< link in output port links */ + struct pw_impl_port *input; /**< input port */ + struct spa_list input_link; /**< link in input port links */ + + struct spa_hook_list listener_list; + + struct pw_control_link control; + struct pw_control_link notify; + + struct { + struct pw_impl_port_mix out_mix; /**< port added to the output mixer */ + struct pw_impl_port_mix in_mix; /**< port added to the input mixer */ + struct pw_node_target target; /**< target to trigger the input node */ + } rt; + + void *user_data; + + unsigned int registered:1; + unsigned int feedback:1; + unsigned int preparing:1; + unsigned int prepared:1; + unsigned int passive:1; +}; + +#define pw_resource_emit(o,m,v,...) spa_hook_list_call(&o->listener_list, struct pw_resource_events, m, v, ##__VA_ARGS__) + +#define pw_resource_emit_destroy(o) pw_resource_emit(o, destroy, 0) +#define pw_resource_emit_pong(o,s) pw_resource_emit(o, pong, 0, s) +#define pw_resource_emit_error(o,s,r,m) pw_resource_emit(o, error, 0, s, r, m) + +struct pw_resource { + struct spa_interface impl; /**< object implementation */ + + struct pw_context *context; /**< the context object */ + struct pw_global *global; /**< global of resource */ + struct spa_list link; /**< link in global resource_list */ + + struct pw_impl_client *client; /**< owner client */ + + uint32_t id; /**< per client unique id, index in client objects */ + uint32_t permissions; /**< resource permissions */ + const char *type; /**< type of the client interface */ + uint32_t version; /**< version of the client interface */ + uint32_t bound_id; /**< global id we are bound to */ + int refcount; + + unsigned int removed:1; /**< resource was removed from server */ + unsigned int destroyed:1; /**< resource was destroyed */ + + struct spa_hook_list listener_list; + struct spa_hook_list object_listener_list; + + const struct pw_protocol_marshal *marshal; + + void *user_data; /**< extra user data */ +}; + +#define pw_proxy_emit(o,m,v,...) spa_hook_list_call(&o->listener_list, struct pw_proxy_events, m, v, ##__VA_ARGS__) +#define pw_proxy_emit_destroy(p) pw_proxy_emit(p, destroy, 0) +#define pw_proxy_emit_bound(p,g) pw_proxy_emit(p, bound, 0, g) +#define pw_proxy_emit_removed(p) pw_proxy_emit(p, removed, 0) +#define pw_proxy_emit_done(p,s) pw_proxy_emit(p, done, 0, s) +#define pw_proxy_emit_error(p,s,r,m) pw_proxy_emit(p, error, 0, s, r, m) + +struct pw_proxy { + struct spa_interface impl; /**< object implementation */ + + struct pw_core *core; /**< the owner core of this proxy */ + + uint32_t id; /**< client side id */ + const char *type; /**< type of the interface */ + uint32_t version; /**< client side version */ + uint32_t bound_id; /**< global id we are bound to */ + int refcount; + unsigned int zombie:1; /**< proxy is removed locally and waiting to + * be removed from server */ + unsigned int removed:1; /**< proxy was removed from server */ + unsigned int destroyed:1; /**< proxy was destroyed by client */ + unsigned int in_map:1; /**< proxy is in core object map */ + + struct spa_hook_list listener_list; + struct spa_hook_list object_listener_list; + + const struct pw_protocol_marshal *marshal; /**< protocol specific marshal functions */ + + void *user_data; /**< extra user data */ +}; + +struct pw_core { + struct pw_proxy proxy; + + struct pw_context *context; /**< context */ + struct spa_list link; /**< link in context core_list */ + struct pw_properties *properties; /**< extra properties */ + + struct pw_mempool *pool; /**< memory pool */ + struct pw_core *core; /**< proxy for the core object */ + struct spa_hook core_listener; + struct spa_hook proxy_core_listener; + + struct pw_map objects; /**< map of client side proxy objects + * indexed with the client id */ + struct pw_client *client; /**< proxy for the client object */ + + struct spa_list stream_list; /**< list of \ref pw_stream objects */ + struct spa_list filter_list; /**< list of \ref pw_stream objects */ + + struct pw_protocol_client *conn; /**< the protocol client connection */ + int recv_seq; /**< last received sequence number */ + int send_seq; /**< last protocol result code */ + uint64_t recv_generation; /**< last received registry generation */ + + unsigned int removed:1; + unsigned int destroyed:1; + + void *user_data; /**< extra user data */ +}; + +#define pw_stream_emit(s,m,v,...) spa_hook_list_call(&s->listener_list, struct pw_stream_events, m, v, ##__VA_ARGS__) +#define pw_stream_emit_destroy(s) pw_stream_emit(s, destroy, 0) +#define pw_stream_emit_state_changed(s,o,n,e) pw_stream_emit(s, state_changed,0,o,n,e) +#define pw_stream_emit_io_changed(s,i,a,t) pw_stream_emit(s, io_changed,0,i,a,t) +#define pw_stream_emit_param_changed(s,i,p) pw_stream_emit(s, param_changed,0,i,p) +#define pw_stream_emit_add_buffer(s,b) pw_stream_emit(s, add_buffer, 0, b) +#define pw_stream_emit_remove_buffer(s,b) pw_stream_emit(s, remove_buffer, 0, b) +#define pw_stream_emit_process(s) pw_stream_emit(s, process, 0) +#define pw_stream_emit_drained(s) pw_stream_emit(s, drained,0) +#define pw_stream_emit_control_info(s,i,c) pw_stream_emit(s, control_info, 0, i, c) +#define pw_stream_emit_command(s,c) pw_stream_emit(s, command,1,c) +#define pw_stream_emit_trigger_done(s) pw_stream_emit(s, trigger_done,2) + + +struct pw_stream { + struct pw_core *core; /**< the owner core */ + struct spa_hook core_listener; + + struct spa_list link; /**< link in the core */ + + char *name; /**< the name of the stream */ + struct pw_properties *properties; /**< properties of the stream */ + + uint32_t node_id; /**< node id for remote node, available from + * CONFIGURE state and higher */ + enum pw_stream_state state; /**< stream state */ + char *error; /**< error reason when state is in error */ + + struct spa_hook_list listener_list; + + struct pw_proxy *proxy; + struct spa_hook proxy_listener; + + struct spa_hook node_listener; + + struct spa_list controls; +}; + +#define pw_filter_emit(s,m,v,...) spa_hook_list_call(&(s)->listener_list, struct pw_filter_events, m, v, ##__VA_ARGS__) +#define pw_filter_emit_destroy(s) pw_filter_emit(s, destroy, 0) +#define pw_filter_emit_state_changed(s,o,n,e) pw_filter_emit(s, state_changed,0,o,n,e) +#define pw_filter_emit_io_changed(s,p,i,d,t) pw_filter_emit(s, io_changed,0,p,i,d,t) +#define pw_filter_emit_param_changed(s,p,i,f) pw_filter_emit(s, param_changed,0,p,i,f) +#define pw_filter_emit_add_buffer(s,p,b) pw_filter_emit(s, add_buffer, 0, p, b) +#define pw_filter_emit_remove_buffer(s,p,b) pw_filter_emit(s, remove_buffer, 0, p, b) +#define pw_filter_emit_process(s,p) pw_filter_emit(s, process, 0, p) +#define pw_filter_emit_drained(s) pw_filter_emit(s, drained, 0) +#define pw_filter_emit_command(s,c) pw_filter_emit(s, command, 1, c) + + +struct pw_filter { + struct pw_core *core; /**< the owner core proxy */ + struct spa_hook core_listener; + + struct spa_list link; /**< link in the core proxy */ + + char *name; /**< the name of the filter */ + struct pw_properties *properties; /**< properties of the filter */ + + uint32_t node_id; /**< node id for remote node, available from + * CONFIGURE state and higher */ + enum pw_filter_state state; /**< filter state */ + char *error; /**< error reason when state is in error */ + + struct spa_hook_list listener_list; + + struct pw_proxy *proxy; + struct spa_hook proxy_listener; + + struct spa_list controls; +}; + +#define pw_impl_factory_emit(s,m,v,...) spa_hook_list_call(&s->listener_list, struct pw_impl_factory_events, m, v, ##__VA_ARGS__) + +#define pw_impl_factory_emit_destroy(s) pw_impl_factory_emit(s, destroy, 0) +#define pw_impl_factory_emit_free(s) pw_impl_factory_emit(s, free, 0) +#define pw_impl_factory_emit_initialized(s) pw_impl_factory_emit(s, initialized, 0) + +struct pw_impl_factory { + struct pw_context *context; /**< the context */ + struct spa_list link; /**< link in context factory_list */ + struct pw_global *global; /**< global for this factory */ + struct spa_hook global_listener; + + struct pw_factory_info info; /**< introspectable factory info */ + struct pw_properties *properties; /**< properties of the factory */ + + struct spa_hook_list listener_list; /**< event listeners */ + + struct spa_callbacks impl; + + void *user_data; + + unsigned int registered:1; +}; + +#define pw_control_emit(c,m,v,...) spa_hook_list_call(&c->listener_list, struct pw_control_events, m, v, ##__VA_ARGS__) +#define pw_control_emit_destroy(c) pw_control_emit(c, destroy, 0) +#define pw_control_emit_free(c) pw_control_emit(c, free, 0) +#define pw_control_emit_linked(c,o) pw_control_emit(c, linked, 0, o) +#define pw_control_emit_unlinked(c,o) pw_control_emit(c, unlinked, 0, o) + +struct pw_control { + struct spa_list link; /**< link in context control_list */ + struct pw_context *context; /**< the context */ + + struct pw_impl_port *port; /**< owner port or NULL */ + struct spa_list port_link; /**< link in port control_list */ + + enum spa_direction direction; /**< the direction */ + struct spa_list links; /**< list of pw_control_link */ + + uint32_t id; + int32_t size; + + struct spa_hook_list listener_list; + + void *user_data; +}; + +/** Find a good format between 2 ports */ +int pw_context_find_format(struct pw_context *context, + struct pw_impl_port *output, + struct pw_impl_port *input, + struct pw_properties *props, + uint32_t n_format_filters, + struct spa_pod **format_filters, + struct spa_pod **format, + struct spa_pod_builder *builder, + char **error); + +int pw_context_debug_port_params(struct pw_context *context, + struct spa_node *node, enum spa_direction direction, + uint32_t port_id, uint32_t id, int err, const char *debug, ...); + +const struct pw_export_type *pw_context_find_export_type(struct pw_context *context, const char *type); + +int pw_proxy_init(struct pw_proxy *proxy, const char *type, uint32_t version); + +void pw_proxy_remove(struct pw_proxy *proxy); + +int pw_context_recalc_graph(struct pw_context *context, const char *reason); + +void pw_impl_port_update_info(struct pw_impl_port *port, const struct spa_port_info *info); + +int pw_impl_port_register(struct pw_impl_port *port, + struct pw_properties *properties); + +/** Get the user data of a port, the size of the memory was given \ref in pw_context_create_port */ +void * pw_impl_port_get_user_data(struct pw_impl_port *port); + +int pw_impl_port_set_mix(struct pw_impl_port *port, struct spa_node *node, uint32_t flags); + +int pw_impl_port_init_mix(struct pw_impl_port *port, struct pw_impl_port_mix *mix); +int pw_impl_port_release_mix(struct pw_impl_port *port, struct pw_impl_port_mix *mix); + +void pw_impl_port_update_state(struct pw_impl_port *port, enum pw_impl_port_state state, int res, char *error); + +/** Unlink a port */ +void pw_impl_port_unlink(struct pw_impl_port *port); + +/** Destroy a port */ +void pw_impl_port_destroy(struct pw_impl_port *port); + +/** Iterate the params of the given port. The callback should return + * 1 to fetch the next item, 0 to stop iteration or <0 on error. + * The function returns 0 on success or the error returned by the callback. */ +int pw_impl_port_for_each_param(struct pw_impl_port *port, + int seq, uint32_t param_id, + uint32_t index, uint32_t max, + const struct spa_pod *filter, + int (*callback) (void *data, int seq, + uint32_t id, uint32_t index, uint32_t next, + struct spa_pod *param), + void *data); + +int pw_impl_port_for_each_filtered_param(struct pw_impl_port *in_port, + struct pw_impl_port *out_port, + int seq, + uint32_t in_param_id, + uint32_t out_param_id, + const struct spa_pod *filter, + int (*callback) (void *data, int seq, + uint32_t id, uint32_t index, uint32_t next, + struct spa_pod *param), + void *data); + +/** Iterate the links of the port. The callback should return + * 0 to fetch the next item, any other value stops the iteration and returns + * the value. When all callbacks return 0, this function returns 0 when all + * items are iterated. */ +int pw_impl_port_for_each_link(struct pw_impl_port *port, + int (*callback) (void *data, struct pw_impl_link *link), + void *data); + +/** Set a param on a port, use SPA_ID_INVALID for mix_id to set + * the param on all mix ports */ +int pw_impl_port_set_param(struct pw_impl_port *port, + uint32_t id, uint32_t flags, const struct spa_pod *param); + +/** Use buffers on a port */ +int pw_impl_port_use_buffers(struct pw_impl_port *port, struct pw_impl_port_mix *mix, uint32_t flags, + struct spa_buffer **buffers, uint32_t n_buffers); + +int pw_impl_port_recalc_latency(struct pw_impl_port *port); + +/** Change the state of the node */ +int pw_impl_node_set_state(struct pw_impl_node *node, enum pw_node_state state); + + +int pw_impl_node_update_ports(struct pw_impl_node *node); + +int pw_impl_node_set_driver(struct pw_impl_node *node, struct pw_impl_node *driver); + +/** Prepare a link + * Starts the negotiation of formats and buffers on \a link */ +int pw_impl_link_prepare(struct pw_impl_link *link); +/** starts streaming on a link */ +int pw_impl_link_activate(struct pw_impl_link *link); + +/** Deactivate a link */ +int pw_impl_link_deactivate(struct pw_impl_link *link); + +struct pw_control * +pw_control_new(struct pw_context *context, + struct pw_impl_port *owner, /**< can be NULL */ + uint32_t id, uint32_t size, + size_t user_data_size /**< extra user data */); + +int pw_control_add_link(struct pw_control *control, uint32_t cmix, + struct pw_control *other, uint32_t omix, + struct pw_control_link *link); + +int pw_control_remove_link(struct pw_control_link *link); + +void pw_control_destroy(struct pw_control *control); + +void pw_impl_client_unref(struct pw_impl_client *client); + +#define PW_LOG_OBJECT_POD (1<<0) +void pw_log_log_object(enum spa_log_level level, const struct spa_log_topic *topic, + const char *file, int line, const char *func, uint32_t flags, + const void *object); + +#define pw_log_object(lev,t,fl,obj) \ +({ \ + if (SPA_UNLIKELY(pw_log_topic_enabled(lev,t))) \ + pw_log_log_object(lev,t,__FILE__,__LINE__, \ + __func__,(fl),(obj)); \ +}) + +#define pw_log_pod(lev,pod) pw_log_object(lev,PW_LOG_TOPIC_DEFAULT,PW_LOG_OBJECT_POD,pod) +#define pw_log_format(lev,pod) pw_log_object(lev,PW_LOG_TOPIC_DEFAULT,PW_LOG_OBJECT_POD,pod) + +bool pw_log_is_default(void); + +void pw_log_init(void); +void pw_log_deinit(void); + +void pw_settings_init(struct pw_context *context); +int pw_settings_expose(struct pw_context *context); +void pw_settings_clean(struct pw_context *context); + +pthread_attr_t *pw_thread_fill_attr(const struct spa_dict *props, pthread_attr_t *attr); + +/** \endcond */ + +#ifdef __cplusplus +} +#endif + +#endif /* PIPEWIRE_PRIVATE_H */ diff --git a/src/pipewire/properties.c b/src/pipewire/properties.c new file mode 100644 index 0000000..be52a7c --- /dev/null +++ b/src/pipewire/properties.c @@ -0,0 +1,733 @@ +/* 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 <stdio.h> +#include <stdarg.h> +#include <spa/utils/json.h> +#include <spa/utils/string.h> + +#include "pipewire/array.h" +#include "pipewire/log.h" +#include "pipewire/utils.h" +#include "pipewire/properties.h" + +PW_LOG_TOPIC_EXTERN(log_properties); +#define PW_LOG_TOPIC_DEFAULT log_properties + +/** \cond */ +struct properties { + struct pw_properties this; + + struct pw_array items; +}; +/** \endcond */ + +static int add_func(struct pw_properties *this, char *key, char *value) +{ + struct spa_dict_item *item; + struct properties *impl = SPA_CONTAINER_OF(this, struct properties, this); + + item = pw_array_add(&impl->items, sizeof(struct spa_dict_item)); + if (item == NULL) { + free(key); + free(value); + return -errno; + } + + item->key = key; + item->value = value; + + this->dict.items = impl->items.data; + this->dict.n_items++; + return 0; +} + +static void clear_item(struct spa_dict_item *item) +{ + free((char *) item->key); + free((char *) item->value); +} + +static int find_index(const struct pw_properties *this, const char *key) +{ + const struct spa_dict_item *item; + item = spa_dict_lookup_item(&this->dict, key); + if (item == NULL) + return -1; + return item - this->dict.items; +} + +static struct properties *properties_new(int prealloc) +{ + struct properties *impl; + + impl = calloc(1, sizeof(struct properties)); + if (impl == NULL) + return NULL; + + pw_array_init(&impl->items, 16); + pw_array_ensure_size(&impl->items, sizeof(struct spa_dict_item) * prealloc); + + return impl; +} + +/** Make a new properties object + * + * \param key a first key + * \param ... value and more keys NULL terminated + * \return a newly allocated properties object + */ +SPA_EXPORT +struct pw_properties *pw_properties_new(const char *key, ...) +{ + struct properties *impl; + va_list varargs; + const char *value; + + impl = properties_new(16); + if (impl == NULL) + return NULL; + + va_start(varargs, key); + while (key != NULL) { + value = va_arg(varargs, char *); + if (value && key[0]) + add_func(&impl->this, strdup(key), strdup(value)); + key = va_arg(varargs, char *); + } + va_end(varargs); + + return &impl->this; +} + +/** Make a new properties object from the given dictionary + * + * \param dict a dictionary. keys and values are copied + * \return a new properties object + */ +SPA_EXPORT +struct pw_properties *pw_properties_new_dict(const struct spa_dict *dict) +{ + uint32_t i; + struct properties *impl; + + impl = properties_new(SPA_ROUND_UP_N(dict->n_items, 16)); + if (impl == NULL) + return NULL; + + for (i = 0; i < dict->n_items; i++) { + const struct spa_dict_item *it = &dict->items[i]; + if (it->key != NULL && it->key[0] && it->value != NULL) + add_func(&impl->this, strdup(it->key), + strdup(it->value)); + } + + return &impl->this; +} + +/** Update the properties from the given string, overwriting any + * existing keys with the new values from \a str. + * + * \a str should be a whitespace separated list of key=value + * strings or a json object, see pw_properties_new_string(). + * + * \return The number of properties added or updated + */ +SPA_EXPORT +int pw_properties_update_string(struct pw_properties *props, const char *str, size_t size) +{ + struct properties *impl = SPA_CONTAINER_OF(props, struct properties, this); + struct spa_json it[2]; + char key[1024], *val; + int count = 0; + + spa_json_init(&it[0], str, size); + if (spa_json_enter_object(&it[0], &it[1]) <= 0) + spa_json_init(&it[1], str, size); + + while (spa_json_get_string(&it[1], key, sizeof(key)) > 0) { + int len; + const char *value; + + if ((len = spa_json_next(&it[1], &value)) <= 0) + break; + + if (spa_json_is_null(value, len)) + val = NULL; + else { + if (spa_json_is_container(value, len)) + len = spa_json_container_len(&it[1], value, len); + + if ((val = malloc(len+1)) != NULL) + spa_json_parse_stringn(value, len, val, len+1); + } + count += pw_properties_set(&impl->this, key, val); + free(val); + } + return count; +} + +/** Make a new properties object from the given str + * + * \a object should be a whitespace separated list of key=value + * strings or a json object. + * + * \param object a property description + * \return a new properties object + */ +SPA_EXPORT +struct pw_properties * +pw_properties_new_string(const char *object) +{ + struct properties *impl; + int res; + + impl = properties_new(16); + if (impl == NULL) + return NULL; + + if ((res = pw_properties_update_string(&impl->this, object, strlen(object))) < 0) + goto error; + + return &impl->this; +error: + pw_properties_free(&impl->this); + errno = -res; + return NULL; +} + +/** Copy a properties object + * + * \param properties properties to copy + * \return a new properties object + */ +SPA_EXPORT +struct pw_properties *pw_properties_copy(const struct pw_properties *properties) +{ + return pw_properties_new_dict(&properties->dict); +} + +/** Copy multiple keys from one property to another + * + * \param props properties to copy to + * \param dict properties to copy from + * \param keys a NULL terminated list of keys to copy + * \return the number of keys changed in \a dest + */ +SPA_EXPORT +int pw_properties_update_keys(struct pw_properties *props, + const struct spa_dict *dict, const char * const keys[]) +{ + int i, changed = 0; + const char *str; + + for (i = 0; keys[i]; i++) { + if ((str = spa_dict_lookup(dict, keys[i])) != NULL) + changed += pw_properties_set(props, keys[i], str); + } + return changed; +} + +static bool has_key(const char * const keys[], const char *key) +{ + int i; + for (i = 0; keys[i]; i++) { + if (spa_streq(keys[i], key)) + return true; + } + return false; +} + +SPA_EXPORT +int pw_properties_update_ignore(struct pw_properties *props, + const struct spa_dict *dict, const char * const ignore[]) +{ + const struct spa_dict_item *it; + int changed = 0; + + spa_dict_for_each(it, dict) { + if (ignore == NULL || !has_key(ignore, it->key)) + changed += pw_properties_set(props, it->key, it->value); + } + return changed; +} + +/** Clear a properties object + * + * \param properties properties to clear + */ +SPA_EXPORT +void pw_properties_clear(struct pw_properties *properties) +{ + struct properties *impl = SPA_CONTAINER_OF(properties, struct properties, this); + struct spa_dict_item *item; + + pw_array_for_each(item, &impl->items) + clear_item(item); + pw_array_reset(&impl->items); + properties->dict.n_items = 0; +} + +/** Update properties + * + * \param props properties to update + * \param dict new properties + * \return the number of changed properties + * + * The properties in \a props are updated with \a dict. Keys in \a dict + * with NULL values are removed from \a props. + */ +SPA_EXPORT +int pw_properties_update(struct pw_properties *props, + const struct spa_dict *dict) +{ + const struct spa_dict_item *it; + int changed = 0; + + spa_dict_for_each(it, dict) + changed += pw_properties_set(props, it->key, it->value); + + return changed; +} + +/** Add properties + * + * \param props properties to add + * \param dict new properties + * \return the number of added properties + * + * The properties from \a dict that are not yet in \a props are added. + */ +SPA_EXPORT +int pw_properties_add(struct pw_properties *props, + const struct spa_dict *dict) +{ + uint32_t i; + int added = 0; + + for (i = 0; i < dict->n_items; i++) { + if (pw_properties_get(props, dict->items[i].key) == NULL) + added += pw_properties_set(props, dict->items[i].key, dict->items[i].value); + } + return added; +} + +/** Add keys + * + * \param props properties to add + * \param dict new properties + * \param keys a NULL terminated list of keys to add + * \return the number of added properties + * + * The properties with \a keys from \a dict that are not yet + * in \a props are added. + */ +SPA_EXPORT +int pw_properties_add_keys(struct pw_properties *props, + const struct spa_dict *dict, const char * const keys[]) +{ + uint32_t i; + int added = 0; + const char *str; + + for (i = 0; keys[i]; i++) { + if ((str = spa_dict_lookup(dict, keys[i])) == NULL) + continue; + if (pw_properties_get(props, keys[i]) == NULL) + added += pw_properties_set(props, keys[i], str); + } + return added; +} + +/** Free a properties object + * + * \param properties the properties to free + */ +SPA_EXPORT +void pw_properties_free(struct pw_properties *properties) +{ + struct properties *impl; + + if (properties == NULL) + return; + + impl = SPA_CONTAINER_OF(properties, struct properties, this); + pw_properties_clear(properties); + pw_array_clear(&impl->items); + free(impl); +} + +static int do_replace(struct pw_properties *properties, const char *key, char *value, bool copy) +{ + struct properties *impl = SPA_CONTAINER_OF(properties, struct properties, this); + int index; + + if (key == NULL || key[0] == 0) + goto exit_noupdate; + + index = find_index(properties, key); + + if (index == -1) { + if (value == NULL) + return 0; + add_func(properties, strdup(key), copy ? strdup(value) : value); + SPA_FLAG_CLEAR(properties->dict.flags, SPA_DICT_FLAG_SORTED); + } else { + struct spa_dict_item *item = + pw_array_get_unchecked(&impl->items, index, struct spa_dict_item); + + if (value && spa_streq(item->value, value)) + goto exit_noupdate; + + if (value == NULL) { + struct spa_dict_item *last = pw_array_get_unchecked(&impl->items, + pw_array_get_len(&impl->items, struct spa_dict_item) - 1, + struct spa_dict_item); + clear_item(item); + item->key = last->key; + item->value = last->value; + impl->items.size -= sizeof(struct spa_dict_item); + properties->dict.n_items--; + SPA_FLAG_CLEAR(properties->dict.flags, SPA_DICT_FLAG_SORTED); + } else { + free((char *) item->value); + item->value = copy ? strdup(value) : value; + } + } + return 1; +exit_noupdate: + if (!copy) + free(value); + return 0; +} + +/** Set a property value + * + * \param properties the properties to change + * \param key a key + * \param value a value or NULL to remove the key + * \return 1 if the properties were changed. 0 if nothing was changed because + * the property already existed with the same value or because the key to remove + * did not exist. + * + * Set the property in \a properties with \a key to \a value. Any previous value + * of \a key will be overwritten. When \a value is NULL, the key will be + * removed. + */ +SPA_EXPORT +int pw_properties_set(struct pw_properties *properties, const char *key, const char *value) +{ + return do_replace(properties, key, (char*)value, true); +} + +SPA_EXPORT +int pw_properties_setva(struct pw_properties *properties, + const char *key, const char *format, va_list args) +{ + char *value = NULL; + if (format != NULL) { + if (vasprintf(&value, format, args) < 0) + return -errno; + } + return do_replace(properties, key, value, false); +} + +/** Set a property value by format + * + * \param properties a \ref pw_properties + * \param key a key + * \param format a value + * \param ... extra arguments + * \return 1 if the property was changed. 0 if nothing was changed because + * the property already existed with the same value or because the key to remove + * did not exist. + * + * Set the property in \a properties with \a key to the value in printf style \a format + * Any previous value of \a key will be overwritten. + */ +SPA_EXPORT +int pw_properties_setf(struct pw_properties *properties, const char *key, const char *format, ...) +{ + int res; + va_list varargs; + + va_start(varargs, format); + res = pw_properties_setva(properties, key, format, varargs); + va_end(varargs); + + return res; +} + +/** Get a property + * + * \param properties a \ref pw_properties + * \param key a key + * \return the property for \a key or NULL when the key was not found + * + * Get the property in \a properties with \a key. + */ +SPA_EXPORT +const char *pw_properties_get(const struct pw_properties *properties, const char *key) +{ + struct properties *impl = SPA_CONTAINER_OF(properties, struct properties, this); + int index = find_index(properties, key); + + if (index == -1) + return NULL; + + return pw_array_get_unchecked(&impl->items, index, struct spa_dict_item)->value; +} + +/** Fetch a property as uint32_t. + * + * \param properties a \ref pw_properties + * \param key a key + * \param value set to the value of the property on success, otherwise left + * unmodified + * \return 0 on success or a negative errno otherwise + * \retval -ENOENT The property does not exist + * \retval -EINVAL The property is not in the expected format + */ +SPA_EXPORT +int pw_properties_fetch_uint32(const struct pw_properties *properties, const char *key, + uint32_t *value) +{ + const char *str = pw_properties_get(properties, key); + bool success; + + if (!str) + return -ENOENT; + + success = spa_atou32(str, value, 0); + if (SPA_UNLIKELY(!success)) + pw_log_warn("Failed to parse \"%s\"=\"%s\" as int32", key, str); + + return success ? 0 : -EINVAL; +} + +/** Fetch a property as int32_t + * + * \param properties a \ref pw_properties + * \param key a key + * \param value set to the value of the property on success, otherwise left + * unmodified + * \return 0 on success or a negative errno otherwise + * \retval -ENOENT The property does not exist + * \retval -EINVAL The property is not in the expected format + */ +SPA_EXPORT +int pw_properties_fetch_int32(const struct pw_properties *properties, const char *key, + int32_t *value) +{ + const char *str = pw_properties_get(properties, key); + bool success; + + if (!str) + return -ENOENT; + + success = spa_atoi32(str, value, 0); + if (SPA_UNLIKELY(!success)) + pw_log_warn("Failed to parse \"%s\"=\"%s\" as int32", key, str); + + return success ? 0 : -EINVAL; +} + +/** Fetch a property as uint64_t. + * + * \param properties a \ref pw_properties + * \param key a key + * \param value set to the value of the property on success, otherwise left + * unmodified + * \return 0 on success or a negative errno otherwise + * \retval -ENOENT The property does not exist + * \retval -EINVAL The property is not in the expected format + */ +SPA_EXPORT +int pw_properties_fetch_uint64(const struct pw_properties *properties, const char *key, + uint64_t *value) +{ + const char *str = pw_properties_get(properties, key); + bool success; + + if (!str) + return -ENOENT; + + success = spa_atou64(str, value, 0); + if (SPA_UNLIKELY(!success)) + pw_log_warn("Failed to parse \"%s\"=\"%s\" as uint64", key, str); + + return success ? 0 : -EINVAL; +} + +/** Fetch a property as int64_t + * + * \param properties a \ref pw_properties + * \param key a key + * \param value set to the value of the property on success, otherwise left + * unmodified + * \return 0 on success or a negative errno otherwise + * \retval -ENOENT The property does not exist + * \retval -EINVAL The property is not in the expected format + */ +SPA_EXPORT +int pw_properties_fetch_int64(const struct pw_properties *properties, const char *key, + int64_t *value) +{ + const char *str = pw_properties_get(properties, key); + bool success; + + if (!str) + return -ENOENT; + + success = spa_atoi64(str, value, 0); + if (SPA_UNLIKELY(!success)) + pw_log_warn("Failed to parse \"%s\"=\"%s\" as int64", key, str); + + return success ? 0 : -EINVAL; +} + +/** Fetch a property as boolean value + * + * \param properties a \ref pw_properties + * \param key a key + * \param value set to the value of the property on success, otherwise left + * unmodified + * \return 0 on success or a negative errno otherwise + * \retval -ENOENT The property does not exist + * \retval -EINVAL The property is not in the expected format + */ +SPA_EXPORT +int pw_properties_fetch_bool(const struct pw_properties *properties, const char *key, + bool *value) +{ + const char *str = pw_properties_get(properties, key); + + if (!str) + return -ENOENT; + + *value = spa_atob(str); + return 0; +} + +/** Iterate property values + * + * \param properties a \ref pw_properties + * \param state state + * \return The next key or NULL when there are no more keys to iterate. + * + * Iterate over \a properties, returning each key in turn. \a state should point + * to a pointer holding NULL to get the first element and will be updated + * after each iteration. When NULL is returned, all elements have been + * iterated. + */ +SPA_EXPORT +const char *pw_properties_iterate(const struct pw_properties *properties, void **state) +{ + struct properties *impl = SPA_CONTAINER_OF(properties, struct properties, this); + uint32_t index; + + if (*state == NULL) + index = 0; + else + index = SPA_PTR_TO_INT(*state); + + if (!pw_array_check_index(&impl->items, index, struct spa_dict_item)) + return NULL; + + *state = SPA_INT_TO_PTR(index + 1); + + return pw_array_get_unchecked(&impl->items, index, struct spa_dict_item)->key; +} + +static int encode_string(FILE *f, const char *val) +{ + int len = 0; + len += fprintf(f, "\""); + while (*val) { + switch (*val) { + case '\n': + len += fprintf(f, "\\n"); + break; + case '\r': + len += fprintf(f, "\\r"); + break; + case '\b': + len += fprintf(f, "\\b"); + break; + case '\t': + len += fprintf(f, "\\t"); + break; + case '\f': + len += fprintf(f, "\\f"); + break; + case '\\': + case '"': + len += fprintf(f, "\\%c", *val); + break; + default: + if (*val > 0 && *val < 0x20) + len += fprintf(f, "\\u%04x", *val); + else + len += fprintf(f, "%c", *val); + break; + } + val++; + } + len += fprintf(f, "\""); + return len-1; +} + +SPA_EXPORT +int pw_properties_serialize_dict(FILE *f, const struct spa_dict *dict, uint32_t flags) +{ + const struct spa_dict_item *it; + int count = 0; + char key[1024]; + + spa_dict_for_each(it, dict) { + size_t len = it->value ? strlen(it->value) : 0; + + if (spa_json_encode_string(key, sizeof(key)-1, it->key) >= (int)sizeof(key)-1) + continue; + + fprintf(f, "%s%s %s: ", + count == 0 ? "" : ",", + flags & PW_PROPERTIES_FLAG_NL ? "\n" : "", + key); + + if (it->value == NULL) { + fprintf(f, "null"); + } else if (spa_json_is_null(it->value, len) || + spa_json_is_float(it->value, len) || + spa_json_is_bool(it->value, len) || + spa_json_is_container(it->value, len) || + spa_json_is_string(it->value, len)) { + fprintf(f, "%s", it->value); + } else { + encode_string(f, it->value); + } + count++; + } + return count; +} diff --git a/src/pipewire/properties.h b/src/pipewire/properties.h new file mode 100644 index 0000000..e91fee5 --- /dev/null +++ b/src/pipewire/properties.h @@ -0,0 +1,199 @@ +/* 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. + */ + +#ifndef PIPEWIRE_PROPERTIES_H +#define PIPEWIRE_PROPERTIES_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <stdarg.h> + +#include <spa/utils/dict.h> +#include <spa/utils/string.h> + +/** \defgroup pw_properties Properties + * + * Properties are used to pass around arbitrary key/value pairs. + * Both keys and values are strings which keeps things simple. + * Encoding of arbitrary values should be done by using a string + * serialization such as base64 for binary blobs. + */ + +/** + * \addtogroup pw_properties + * \{ + */ +struct pw_properties { + struct spa_dict dict; /**< dictionary of key/values */ + uint32_t flags; /**< extra flags */ +}; + +struct pw_properties * +pw_properties_new(const char *key, ...) SPA_SENTINEL; + +struct pw_properties * +pw_properties_new_dict(const struct spa_dict *dict); + +struct pw_properties * +pw_properties_new_string(const char *args); + +struct pw_properties * +pw_properties_copy(const struct pw_properties *properties); + +int pw_properties_update_keys(struct pw_properties *props, + const struct spa_dict *dict, const char * const keys[]); +int pw_properties_update_ignore(struct pw_properties *props, + const struct spa_dict *dict, const char * const ignore[]); + +/* Update props with all key/value pairs from dict */ +int pw_properties_update(struct pw_properties *props, + const struct spa_dict *dict); +/* Update props with all key/value pairs from str */ +int pw_properties_update_string(struct pw_properties *props, + const char *str, size_t size); + +int pw_properties_add(struct pw_properties *oldprops, + const struct spa_dict *dict); +int pw_properties_add_keys(struct pw_properties *oldprops, + const struct spa_dict *dict, const char * const keys[]); + +void pw_properties_clear(struct pw_properties *properties); + +void +pw_properties_free(struct pw_properties *properties); + +int +pw_properties_set(struct pw_properties *properties, const char *key, const char *value); + +int +pw_properties_setf(struct pw_properties *properties, + const char *key, const char *format, ...) SPA_PRINTF_FUNC(3, 4); +int +pw_properties_setva(struct pw_properties *properties, + const char *key, const char *format, va_list args) SPA_PRINTF_FUNC(3,0); +const char * +pw_properties_get(const struct pw_properties *properties, const char *key); + +int +pw_properties_fetch_uint32(const struct pw_properties *properties, const char *key, uint32_t *value); + +int +pw_properties_fetch_int32(const struct pw_properties *properties, const char *key, int32_t *value); + +int +pw_properties_fetch_uint64(const struct pw_properties *properties, const char *key, uint64_t *value); + +int +pw_properties_fetch_int64(const struct pw_properties *properties, const char *key, int64_t *value); + +int +pw_properties_fetch_bool(const struct pw_properties *properties, const char *key, bool *value); + +static inline uint32_t +pw_properties_get_uint32(const struct pw_properties *properties, const char *key, uint32_t deflt) +{ + uint32_t val = deflt; + pw_properties_fetch_uint32(properties, key, &val); + return val; +} + +static inline int32_t +pw_properties_get_int32(const struct pw_properties *properties, const char *key, int32_t deflt) +{ + int32_t val = deflt; + pw_properties_fetch_int32(properties, key, &val); + return val; +} + +static inline uint64_t +pw_properties_get_uint64(const struct pw_properties *properties, const char *key, uint64_t deflt) +{ + uint64_t val = deflt; + pw_properties_fetch_uint64(properties, key, &val); + return val; +} + +static inline int64_t +pw_properties_get_int64(const struct pw_properties *properties, const char *key, int64_t deflt) +{ + int64_t val = deflt; + pw_properties_fetch_int64(properties, key, &val); + return val; +} + + +static inline bool +pw_properties_get_bool(const struct pw_properties *properties, const char *key, bool deflt) +{ + bool val = deflt; + pw_properties_fetch_bool(properties, key, &val); + return val; +} + +const char * +pw_properties_iterate(const struct pw_properties *properties, void **state); + +#define PW_PROPERTIES_FLAG_NL (1<<0) +int pw_properties_serialize_dict(FILE *f, const struct spa_dict *dict, uint32_t flags); + +static inline bool pw_properties_parse_bool(const char *value) { + return spa_atob(value); +} + +static inline int pw_properties_parse_int(const char *value) { + int v; + return spa_atoi32(value, &v, 0) ? v: 0; +} + +static inline int64_t pw_properties_parse_int64(const char *value) { + int64_t v; + return spa_atoi64(value, &v, 0) ? v : 0; +} + +static inline uint64_t pw_properties_parse_uint64(const char *value) { + uint64_t v; + return spa_atou64(value, &v, 0) ? v : 0; +} + +static inline float pw_properties_parse_float(const char *value) { + float v; + return spa_atof(value, &v) ? v : 0.0f; +} + +static inline double pw_properties_parse_double(const char *value) { + double v; + return spa_atod(value, &v) ? v : 0.0; +} + +/** + * \} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* PIPEWIRE_PROPERTIES_H */ diff --git a/src/pipewire/protocol.c b/src/pipewire/protocol.c new file mode 100644 index 0000000..3092f2c --- /dev/null +++ b/src/pipewire/protocol.c @@ -0,0 +1,188 @@ +/* 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 <errno.h> + +#include <spa/debug/types.h> +#include <spa/utils/string.h> + +#include <pipewire/protocol.h> +#include <pipewire/private.h> +#include <pipewire/type.h> + +PW_LOG_TOPIC_EXTERN(log_protocol); +#define PW_LOG_TOPIC_DEFAULT log_protocol + +/** \cond */ +struct impl { + struct pw_protocol this; +}; + +struct marshal { + struct spa_list link; + const struct pw_protocol_marshal *marshal; +}; +/** \endcond */ + +SPA_EXPORT +struct pw_protocol *pw_protocol_new(struct pw_context *context, + const char *name, + size_t user_data_size) +{ + struct pw_protocol *protocol; + + protocol = calloc(1, sizeof(struct impl) + user_data_size); + if (protocol == NULL) + return NULL; + + protocol->context = context; + protocol->name = strdup(name); + + spa_list_init(&protocol->marshal_list); + spa_list_init(&protocol->server_list); + spa_list_init(&protocol->client_list); + spa_hook_list_init(&protocol->listener_list); + + if (user_data_size > 0) + protocol->user_data = SPA_PTROFF(protocol, sizeof(struct impl), void); + + spa_list_append(&context->protocol_list, &protocol->link); + + pw_log_debug("%p: Created protocol %s", protocol, name); + + return protocol; +} + +SPA_EXPORT +struct pw_context *pw_protocol_get_context(struct pw_protocol *protocol) +{ + return protocol->context; +} + +SPA_EXPORT +void *pw_protocol_get_user_data(struct pw_protocol *protocol) +{ + return protocol->user_data; +} + +SPA_EXPORT +const struct pw_protocol_implementation * +pw_protocol_get_implementation(struct pw_protocol *protocol) +{ + return protocol->implementation; +} + +SPA_EXPORT +const void * +pw_protocol_get_extension(struct pw_protocol *protocol) +{ + return protocol->extension; +} + +SPA_EXPORT +void pw_protocol_destroy(struct pw_protocol *protocol) +{ + struct impl *impl = SPA_CONTAINER_OF(protocol, struct impl, this); + struct marshal *marshal, *t1; + struct pw_protocol_server *server; + struct pw_protocol_client *client; + + pw_log_debug("%p: destroy", protocol); + pw_protocol_emit_destroy(protocol); + + spa_hook_list_clean(&protocol->listener_list); + + spa_list_remove(&protocol->link); + + spa_list_consume(server, &protocol->server_list, link) + pw_protocol_server_destroy(server); + + spa_list_consume(client, &protocol->client_list, link) + pw_protocol_client_destroy(client); + + spa_list_for_each_safe(marshal, t1, &protocol->marshal_list, link) + free(marshal); + + free(protocol->name); + + free(impl); +} + +SPA_EXPORT +void pw_protocol_add_listener(struct pw_protocol *protocol, + struct spa_hook *listener, + const struct pw_protocol_events *events, + void *data) +{ + spa_hook_list_append(&protocol->listener_list, listener, events, data); +} + +SPA_EXPORT +int +pw_protocol_add_marshal(struct pw_protocol *protocol, + const struct pw_protocol_marshal *marshal) +{ + struct marshal *impl; + + impl = calloc(1, sizeof(struct marshal)); + if (impl == NULL) + return -errno; + + impl->marshal = marshal; + + spa_list_append(&protocol->marshal_list, &impl->link); + + pw_log_debug("%p: Add marshal %s/%d to protocol %s", protocol, + marshal->type, marshal->version, protocol->name); + + return 0; +} + +SPA_EXPORT +const struct pw_protocol_marshal * +pw_protocol_get_marshal(struct pw_protocol *protocol, const char *type, uint32_t version, uint32_t flags) +{ + struct marshal *impl; + + spa_list_for_each(impl, &protocol->marshal_list, link) { + if (spa_streq(impl->marshal->type, type) && + (impl->marshal->flags & flags) == flags) + return impl->marshal; + } + pw_log_debug("%p: No marshal %s/%d for protocol %s", protocol, + type, version, protocol->name); + return NULL; +} + +SPA_EXPORT +struct pw_protocol *pw_context_find_protocol(struct pw_context *context, const char *name) +{ + struct pw_protocol *protocol; + + spa_list_for_each(protocol, &context->protocol_list, link) { + if (spa_streq(protocol->name, name)) + return protocol; + } + return NULL; +} diff --git a/src/pipewire/protocol.h b/src/pipewire/protocol.h new file mode 100644 index 0000000..bb97273 --- /dev/null +++ b/src/pipewire/protocol.h @@ -0,0 +1,162 @@ +/* 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. + */ + +#ifndef PIPEWIRE_PROTOCOL_H +#define PIPEWIRE_PROTOCOL_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <spa/utils/list.h> + +/** \defgroup pw_protocol Protocol + * + * \brief Manages protocols and their implementation + */ + +/** + * \addtogroup pw_protocol + * \{ + */ + +struct pw_protocol; + +#include <pipewire/context.h> +#include <pipewire/properties.h> +#include <pipewire/utils.h> + +#define PW_TYPE_INFO_Protocol "PipeWire:Protocol" +#define PW_TYPE_INFO_PROTOCOL_BASE PW_TYPE_INFO_Protocol ":" + +struct pw_protocol_client { + struct spa_list link; /**< link in protocol client_list */ + struct pw_protocol *protocol; /**< the owner protocol */ + + struct pw_core *core; + + int (*connect) (struct pw_protocol_client *client, + const struct spa_dict *props, + void (*done_callback) (void *data, int result), + void *data); + int (*connect_fd) (struct pw_protocol_client *client, int fd, bool close); + int (*steal_fd) (struct pw_protocol_client *client); + void (*disconnect) (struct pw_protocol_client *client); + void (*destroy) (struct pw_protocol_client *client); + int (*set_paused) (struct pw_protocol_client *client, bool paused); +}; + +#define pw_protocol_client_connect(c,p,cb,d) ((c)->connect(c,p,cb,d)) +#define pw_protocol_client_connect_fd(c,fd,cl) ((c)->connect_fd(c,fd,cl)) +#define pw_protocol_client_steal_fd(c) ((c)->steal_fd(c)) +#define pw_protocol_client_disconnect(c) ((c)->disconnect(c)) +#define pw_protocol_client_destroy(c) ((c)->destroy(c)) +#define pw_protocol_client_set_paused(c,p) ((c)->set_paused(c,p)) + +struct pw_protocol_server { + struct spa_list link; /**< link in protocol server_list */ + struct pw_protocol *protocol; /**< the owner protocol */ + + struct pw_impl_core *core; + + struct spa_list client_list; /**< list of clients of this protocol */ + + void (*destroy) (struct pw_protocol_server *listen); +}; + +#define pw_protocol_server_destroy(l) ((l)->destroy(l)) + +struct pw_protocol_marshal { + const char *type; /**< interface type */ + uint32_t version; /**< version */ +#define PW_PROTOCOL_MARSHAL_FLAG_IMPL (1 << 0) /**< marshal for implementations */ + uint32_t flags; /**< version */ + uint32_t n_client_methods; /**< number of client methods */ + uint32_t n_server_methods; /**< number of server methods */ + const void *client_marshal; + const void *server_demarshal; + const void *server_marshal; + const void *client_demarshal; +}; + +struct pw_protocol_implementation { +#define PW_VERSION_PROTOCOL_IMPLEMENTATION 0 + uint32_t version; + + struct pw_protocol_client * (*new_client) (struct pw_protocol *protocol, + struct pw_core *core, + const struct spa_dict *props); + struct pw_protocol_server * (*add_server) (struct pw_protocol *protocol, + struct pw_impl_core *core, + const struct spa_dict *props); +}; + +struct pw_protocol_events { +#define PW_VERSION_PROTOCOL_EVENTS 0 + uint32_t version; + + void (*destroy) (void *data); +}; + +#define pw_protocol_new_client(p,...) (pw_protocol_get_implementation(p)->new_client(p,__VA_ARGS__)) +#define pw_protocol_add_server(p,...) (pw_protocol_get_implementation(p)->add_server(p,__VA_ARGS__)) +#define pw_protocol_ext(p,type,method,...) (((type*)pw_protocol_get_extension(p))->method( __VA_ARGS__)) + +struct pw_protocol *pw_protocol_new(struct pw_context *context, const char *name, size_t user_data_size); + +void pw_protocol_destroy(struct pw_protocol *protocol); + +struct pw_context *pw_protocol_get_context(struct pw_protocol *protocol); + +void *pw_protocol_get_user_data(struct pw_protocol *protocol); + +const struct pw_protocol_implementation * +pw_protocol_get_implementation(struct pw_protocol *protocol); + +const void * +pw_protocol_get_extension(struct pw_protocol *protocol); + + +void pw_protocol_add_listener(struct pw_protocol *protocol, + struct spa_hook *listener, + const struct pw_protocol_events *events, + void *data); + +int pw_protocol_add_marshal(struct pw_protocol *protocol, + const struct pw_protocol_marshal *marshal); + +const struct pw_protocol_marshal * +pw_protocol_get_marshal(struct pw_protocol *protocol, const char *type, uint32_t version, uint32_t flags); + +struct pw_protocol * pw_context_find_protocol(struct pw_context *context, const char *name); + +/** + * \} + */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* PIPEWIRE_PROTOCOL_H */ diff --git a/src/pipewire/proxy.c b/src/pipewire/proxy.c new file mode 100644 index 0000000..b3eff72 --- /dev/null +++ b/src/pipewire/proxy.c @@ -0,0 +1,374 @@ +/* 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 <assert.h> + +#include <pipewire/log.h> +#include <pipewire/proxy.h> +#include <pipewire/core.h> +#include <pipewire/private.h> +#include <pipewire/type.h> + +#include <spa/debug/types.h> + +PW_LOG_TOPIC_EXTERN(log_proxy); +#define PW_LOG_TOPIC_DEFAULT log_proxy + +/** \cond */ +struct proxy { + struct pw_proxy this; +}; +/** \endcond */ + +int pw_proxy_init(struct pw_proxy *proxy, const char *type, uint32_t version) +{ + int res; + + proxy->refcount = 1; + proxy->type = type; + proxy->version = version; + proxy->bound_id = SPA_ID_INVALID; + + proxy->id = pw_map_insert_new(&proxy->core->objects, proxy); + if (proxy->id == SPA_ID_INVALID) { + res = -errno; + pw_log_error("%p: can't allocate new id: %m", proxy); + goto error; + } + + spa_hook_list_init(&proxy->listener_list); + spa_hook_list_init(&proxy->object_listener_list); + + if ((res = pw_proxy_install_marshal(proxy, false)) < 0) { + pw_log_error("%p: no marshal for type %s/%d: %s", proxy, + type, version, spa_strerror(res)); + goto error_clean; + } + proxy->in_map = true; + return 0; + +error_clean: + pw_map_remove(&proxy->core->objects, proxy->id); +error: + return res; +} + +/** Create a proxy object with a given id and type + * + * \param factory another proxy object that serves as a factory + * \param type Type of the proxy object + * \param version Interface version + * \param user_data_size size of user_data + * \return A newly allocated proxy object or NULL on failure + * + * This function creates a new proxy object with the supplied id and type. The + * proxy object will have an id assigned from the client id space. + * + * \sa pw_core + */ +SPA_EXPORT +struct pw_proxy *pw_proxy_new(struct pw_proxy *factory, + const char *type, uint32_t version, + size_t user_data_size) +{ + struct proxy *impl; + struct pw_proxy *this; + int res; + + impl = calloc(1, sizeof(struct proxy) + user_data_size); + if (impl == NULL) + return NULL; + + this = &impl->this; + this->core = factory->core; + + if ((res = pw_proxy_init(this, type, version)) < 0) + goto error_init; + + if (user_data_size > 0) + this->user_data = SPA_PTROFF(impl, sizeof(struct proxy), void); + + pw_log_debug("%p: new %u type %s/%d core-proxy:%p, marshal:%p", + this, this->id, type, version, this->core, this->marshal); + return this; + +error_init: + free(impl); + errno = -res; + return NULL; +} + +SPA_EXPORT +int pw_proxy_install_marshal(struct pw_proxy *this, bool implementor) +{ + struct pw_core *core = this->core; + const struct pw_protocol_marshal *marshal; + + if (core == NULL) + return -EIO; + + marshal = pw_protocol_get_marshal(core->conn->protocol, + this->type, this->version, + implementor ? PW_PROTOCOL_MARSHAL_FLAG_IMPL : 0); + if (marshal == NULL) + return -EPROTO; + + this->marshal = marshal; + this->type = marshal->type; + + this->impl = SPA_INTERFACE_INIT( + this->type, + this->marshal->version, + this->marshal->client_marshal, this); + return 0; +} + +SPA_EXPORT +void *pw_proxy_get_user_data(struct pw_proxy *proxy) +{ + return proxy->user_data; +} + +SPA_EXPORT +uint32_t pw_proxy_get_id(struct pw_proxy *proxy) +{ + return proxy->id; +} + +SPA_EXPORT +int pw_proxy_set_bound_id(struct pw_proxy *proxy, uint32_t global_id) +{ + proxy->bound_id = global_id; + pw_log_debug("%p: id:%d bound:%d", proxy, proxy->id, global_id); + pw_proxy_emit_bound(proxy, global_id); + return 0; +} + +SPA_EXPORT +uint32_t pw_proxy_get_bound_id(struct pw_proxy *proxy) +{ + return proxy->bound_id; +} + +SPA_EXPORT +const char *pw_proxy_get_type(struct pw_proxy *proxy, uint32_t *version) +{ + if (version) + *version = proxy->version; + return proxy->type; +} + +SPA_EXPORT +struct pw_core *pw_proxy_get_core(struct pw_proxy *proxy) +{ + return proxy->core; +} + +SPA_EXPORT +struct pw_protocol *pw_proxy_get_protocol(struct pw_proxy *proxy) +{ + if (proxy->core == NULL || proxy->core->conn == NULL) + return NULL; + return proxy->core->conn->protocol; +} + +SPA_EXPORT +void pw_proxy_add_listener(struct pw_proxy *proxy, + struct spa_hook *listener, + const struct pw_proxy_events *events, + void *data) +{ + spa_hook_list_append(&proxy->listener_list, listener, events, data); +} + +SPA_EXPORT +void pw_proxy_add_object_listener(struct pw_proxy *proxy, + struct spa_hook *listener, + const void *funcs, + void *data) +{ + spa_hook_list_append(&proxy->object_listener_list, listener, funcs, data); +} + +static inline void remove_from_map(struct pw_proxy *proxy) +{ + if (proxy->in_map) { + if (proxy->core) + pw_map_remove(&proxy->core->objects, proxy->id); + proxy->in_map = false; + } +} + +/** Destroy a proxy object + * + * \param proxy Proxy object to destroy + * + * \note This is normally called by \ref pw_core when the server + * decides to destroy the server side object + */ +SPA_EXPORT +void pw_proxy_destroy(struct pw_proxy *proxy) +{ + pw_log_debug("%p: destroy id:%u removed:%u zombie:%u ref:%d", proxy, + proxy->id, proxy->removed, proxy->zombie, proxy->refcount); + + assert(!proxy->destroyed); + proxy->destroyed = true; + + if (!proxy->removed) { + /* if the server did not remove this proxy, schedule a + * destroy if we can */ + if (proxy->core && !proxy->core->removed) { + pw_core_destroy(proxy->core, proxy); + proxy->refcount++; + } else { + proxy->removed = true; + } + } + if (proxy->removed) + remove_from_map(proxy); + + if (!proxy->zombie) { + /* mark zombie and emit destroyed. No more + * events will be emitted on zombie objects */ + proxy->zombie = true; + pw_proxy_emit_destroy(proxy); + } + + pw_proxy_unref(proxy); +} + +/** called when cleaning up or when the server removed the resource. Can + * be called multiple times */ +void pw_proxy_remove(struct pw_proxy *proxy) +{ + assert(proxy->refcount > 0); + + pw_log_debug("%p: remove id:%u removed:%u destroyed:%u zombie:%u ref:%d", proxy, + proxy->id, proxy->removed, proxy->destroyed, proxy->zombie, + proxy->refcount); + + if (!proxy->destroyed) + proxy->refcount++; + + if (!proxy->removed) { + /* mark removed and emit the removed signal only once and + * only when not already destroyed */ + proxy->removed = true; + if (!proxy->destroyed) + pw_proxy_emit_removed(proxy); + } + if (proxy->destroyed) + remove_from_map(proxy); + + pw_proxy_unref(proxy); +} + +SPA_EXPORT +void pw_proxy_unref(struct pw_proxy *proxy) +{ + assert(proxy->refcount > 0); + if (--proxy->refcount > 0) + return; + + pw_log_debug("%p: free %u", proxy, proxy->id); + /** client must explicitly destroy all proxies */ + assert(proxy->destroyed); + +#if DEBUG_LISTENERS + { + struct spa_hook *h; + spa_list_for_each(h, &proxy->object_listener_list.list, link) { + pw_log_warn("%p: proxy %u: leaked object listener %p", + proxy, proxy->id, h); + break; + } + spa_list_for_each(h, &proxy->listener_list.list, link) { + pw_log_warn("%p: proxy %u: leaked listener %p", + proxy, proxy->id, h); + break; + } + } +#endif + free(proxy); +} + +SPA_EXPORT +void pw_proxy_ref(struct pw_proxy *proxy) +{ + assert(proxy->refcount > 0); + proxy->refcount++; +} + +SPA_EXPORT +int pw_proxy_sync(struct pw_proxy *proxy, int seq) +{ + int res = -EIO; + struct pw_core *core = proxy->core; + + if (core && !core->removed) { + res = pw_core_sync(core, proxy->id, seq); + pw_log_debug("%p: %u seq:%d sync %u", proxy, proxy->id, seq, res); + } + return res; +} + +SPA_EXPORT +int pw_proxy_errorf(struct pw_proxy *proxy, int res, const char *error, ...) +{ + va_list ap; + int r = -EIO; + struct pw_core *core = proxy->core; + + va_start(ap, error); + if (core && !core->removed) + r = pw_core_errorv(core, proxy->id, + core->recv_seq, res, error, ap); + va_end(ap); + return r; +} + +SPA_EXPORT +int pw_proxy_error(struct pw_proxy *proxy, int res, const char *error) +{ + int r = -EIO; + struct pw_core *core = proxy->core; + + if (core && !core->removed) + r = pw_core_error(core, proxy->id, + core->recv_seq, res, error); + return r; +} + +SPA_EXPORT +struct spa_hook_list *pw_proxy_get_object_listeners(struct pw_proxy *proxy) +{ + return &proxy->object_listener_list; +} + +SPA_EXPORT +const struct pw_protocol_marshal *pw_proxy_get_marshal(struct pw_proxy *proxy) +{ + return proxy->marshal; +} diff --git a/src/pipewire/proxy.h b/src/pipewire/proxy.h new file mode 100644 index 0000000..1e15dcc --- /dev/null +++ b/src/pipewire/proxy.h @@ -0,0 +1,219 @@ +/* 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. + */ + +#ifndef PIPEWIRE_PROXY_H +#define PIPEWIRE_PROXY_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <spa/utils/hook.h> + +/** \page page_proxy Proxy + * + * \section sec_page_proxy_overview Overview + * + * The proxy object is a client side representation of a resource + * that lives on a remote PipeWire instance. + * + * It is used to communicate with the remote object. + * + * \section sec_page_proxy_core Core proxy + * + * A proxy for a remote core object can be obtained by making + * a remote connection with \ref pw_context_connect. + * See \ref pw_proxy + * + * Some methods on proxy object allow creation of more proxy objects or + * create a binding between a local proxy and global resource. + * + * \section sec_page_proxy_create Create + * + * A client first creates a new proxy object with pw_proxy_new(). A + * type must be provided for this object. + * + * The protocol of the context will usually install an interface to + * translate method calls and events to the wire format. + * + * The creator of the proxy will usually also install an event + * implementation of the particular object type. + * + * \section sec_page_proxy_bind Bind + * + * To actually use the proxy object, one needs to create a server + * side resource for it. This can be done by, for example, binding + * to a global object or by calling a method that creates and binds + * to a new remote object. In all cases, the local id is passed to + * the server and is used to create a resource with the same id. + * + * \section sec_page_proxy_methods Methods + * + * To call a method on the proxy use the interface methods. Calling + * any interface method will result in a request to the server to + * perform the requested action on the corresponding resource. + * + * \section sec_page_proxy_events Events + * + * Events send from the server to the proxy will be demarshalled by + * the protocol and will then result in a call to the installed + * implementation of the proxy. + * + * \section sec_page_proxy_destroy Destroy + * + * Use pw_proxy_destroy() to destroy the client side object. This + * is usually done automatically when the server removes the resource + * associated to the proxy. + */ + +/** \defgroup pw_proxy Proxy + * + * \brief Represents an object on the client side. + * + * A pw_proxy acts as a client side proxy to an object existing in a remote + * pipewire instance. The proxy is responsible for converting interface functions + * invoked by the client to PipeWire messages. Events will call the handlers + * set in listener. + * + * See \ref page_proxy + */ + +/** + * \addtogroup pw_proxy + * \{ + */ +struct pw_proxy; + +#include <pipewire/protocol.h> + +/** Proxy events, use \ref pw_proxy_add_listener */ +struct pw_proxy_events { +#define PW_VERSION_PROXY_EVENTS 0 + uint32_t version; + + /** The proxy is destroyed */ + void (*destroy) (void *data); + + /** a proxy is bound to a global id */ + void (*bound) (void *data, uint32_t global_id); + + /** a proxy is removed from the server. Use pw_proxy_destroy to + * free the proxy. */ + void (*removed) (void *data); + + /** a reply to a sync method completed */ + void (*done) (void *data, int seq); + + /** an error occurred on the proxy */ + void (*error) (void *data, int seq, int res, const char *message); +}; + +/* Make a new proxy object. The id can be used to bind to a remote object and + * can be retrieved with \ref pw_proxy_get_id . */ +struct pw_proxy * +pw_proxy_new(struct pw_proxy *factory, + const char *type, /* interface type */ + uint32_t version, /* interface version */ + size_t user_data_size /* size of user data */); + +/** Add an event listener to proxy */ +void pw_proxy_add_listener(struct pw_proxy *proxy, + struct spa_hook *listener, + const struct pw_proxy_events *events, + void *data); + +/** Add a listener for the events received from the remote object. The + * events depend on the type of the remote object type. */ +void pw_proxy_add_object_listener(struct pw_proxy *proxy, /**< the proxy */ + struct spa_hook *listener, /**< listener */ + const void *funcs, /**< proxied functions */ + void *data /**< data passed to events */); + +/** destroy a proxy */ +void pw_proxy_destroy(struct pw_proxy *proxy); + +void pw_proxy_ref(struct pw_proxy *proxy); +void pw_proxy_unref(struct pw_proxy *proxy); + +/** Get the user_data. The size was given in \ref pw_proxy_new */ +void *pw_proxy_get_user_data(struct pw_proxy *proxy); + +/** Get the local id of the proxy */ +uint32_t pw_proxy_get_id(struct pw_proxy *proxy); + +/** Get the type and version of the proxy */ +const char *pw_proxy_get_type(struct pw_proxy *proxy, uint32_t *version); + +/** Get the protocol used for the proxy */ +struct pw_protocol *pw_proxy_get_protocol(struct pw_proxy *proxy); + +/** Generate an sync method for a proxy. This will generate a done event + * with the same seq number of the reply. */ +int pw_proxy_sync(struct pw_proxy *proxy, int seq); + +/** Set the global id this proxy is bound to. This is usually used internally + * and will also emit the bound event */ +int pw_proxy_set_bound_id(struct pw_proxy *proxy, uint32_t global_id); +/** Get the global id bound to this proxy of SPA_ID_INVALID when not bound + * to a global */ +uint32_t pw_proxy_get_bound_id(struct pw_proxy *proxy); + +/** Generate an error for a proxy */ +int pw_proxy_error(struct pw_proxy *proxy, int res, const char *error); +int pw_proxy_errorf(struct pw_proxy *proxy, int res, const char *error, ...) SPA_PRINTF_FUNC(3, 4); + +/** Get the listener of proxy */ +struct spa_hook_list *pw_proxy_get_object_listeners(struct pw_proxy *proxy); + +/** Get the marshal functions for the proxy */ +const struct pw_protocol_marshal *pw_proxy_get_marshal(struct pw_proxy *proxy); + +/** Install a marshal function on a proxy */ +int pw_proxy_install_marshal(struct pw_proxy *proxy, bool implementor); + +#define pw_proxy_notify(p,type,event,version,...) \ + spa_hook_list_call(pw_proxy_get_object_listeners(p), \ + type, event, version, ## __VA_ARGS__) + +#define pw_proxy_call(p,type,method,version,...) \ + spa_interface_call((struct spa_interface*)p, \ + type, method, version, ##__VA_ARGS__) + +#define pw_proxy_call_res(p,type,method,version,...) \ +({ \ + int _res = -ENOTSUP; \ + spa_interface_call_res((struct spa_interface*)p, \ + type, _res, method, version, ##__VA_ARGS__); \ + _res; \ +}) + +/** + * \} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* PIPEWIRE_PROXY_H */ diff --git a/src/pipewire/resource.c b/src/pipewire/resource.c new file mode 100644 index 0000000..0a6231c --- /dev/null +++ b/src/pipewire/resource.c @@ -0,0 +1,351 @@ +/* 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 <string.h> +#include <assert.h> + +#include "pipewire/private.h" +#include "pipewire/protocol.h" +#include "pipewire/resource.h" +#include "pipewire/type.h" + +#include <spa/debug/types.h> + +PW_LOG_TOPIC_EXTERN(log_resource); +#define PW_LOG_TOPIC_DEFAULT log_resource + +/** \cond */ +struct impl { + struct pw_resource this; +}; +/** \endcond */ + +SPA_EXPORT +struct pw_resource *pw_resource_new(struct pw_impl_client *client, + uint32_t id, + uint32_t permissions, + const char *type, + uint32_t version, + size_t user_data_size) +{ + struct impl *impl; + struct pw_resource *this; + int res; + + impl = calloc(1, sizeof(struct impl) + user_data_size); + if (impl == NULL) + return NULL; + + this = &impl->this; + this->refcount = 1; + this->context = client->context; + this->client = client; + this->permissions = permissions; + this->type = type; + this->version = version; + this->bound_id = SPA_ID_INVALID; + + spa_hook_list_init(&this->listener_list); + spa_hook_list_init(&this->object_listener_list); + + if (id == SPA_ID_INVALID) { + res = -EINVAL; + goto error_clean; + } + + if ((res = pw_map_insert_at(&client->objects, id, this)) < 0) { + pw_log_error("%p: can't add id %u for client %p: %s", + this, id, client, spa_strerror(res)); + goto error_clean; + } + this->id = id; + + if ((res = pw_resource_install_marshal(this, false)) < 0) { + pw_log_error("%p: no marshal for type %s/%d: %s", this, + type, version, spa_strerror(res)); + goto error_clean; + } + + + if (user_data_size > 0) + this->user_data = SPA_PTROFF(impl, sizeof(struct impl), void); + + pw_log_debug("%p: new %u type %s/%d client:%p marshal:%p", + this, id, type, version, client, this->marshal); + + pw_impl_client_emit_resource_added(client, this); + + return this; + +error_clean: + free(impl); + errno = -res; + return NULL; +} + +SPA_EXPORT +int pw_resource_install_marshal(struct pw_resource *this, bool implementor) +{ + struct pw_impl_client *client = this->client; + const struct pw_protocol_marshal *marshal; + + marshal = pw_protocol_get_marshal(client->protocol, + this->type, this->version, + implementor ? PW_PROTOCOL_MARSHAL_FLAG_IMPL : 0); + if (marshal == NULL) + return -EPROTO; + + this->marshal = marshal; + this->type = marshal->type; + + this->impl = SPA_INTERFACE_INIT( + this->type, + this->marshal->version, + this->marshal->server_marshal, this); + return 0; +} + +SPA_EXPORT +struct pw_impl_client *pw_resource_get_client(struct pw_resource *resource) +{ + return resource->client; +} + +SPA_EXPORT +uint32_t pw_resource_get_id(struct pw_resource *resource) +{ + return resource->id; +} + +SPA_EXPORT +uint32_t pw_resource_get_permissions(struct pw_resource *resource) +{ + return resource->permissions; +} + +SPA_EXPORT +const char *pw_resource_get_type(struct pw_resource *resource, uint32_t *version) +{ + if (version) + *version = resource->version; + return resource->type; +} + +SPA_EXPORT +struct pw_protocol *pw_resource_get_protocol(struct pw_resource *resource) +{ + return resource->client->protocol; +} + +SPA_EXPORT +void *pw_resource_get_user_data(struct pw_resource *resource) +{ + return resource->user_data; +} + +SPA_EXPORT +void pw_resource_add_listener(struct pw_resource *resource, + struct spa_hook *listener, + const struct pw_resource_events *events, + void *data) +{ + spa_hook_list_append(&resource->listener_list, listener, events, data); +} + +SPA_EXPORT +void pw_resource_add_object_listener(struct pw_resource *resource, + struct spa_hook *listener, + const void *funcs, + void *data) +{ + spa_hook_list_append(&resource->object_listener_list, listener, funcs, data); +} + +SPA_EXPORT +struct spa_hook_list *pw_resource_get_object_listeners(struct pw_resource *resource) +{ + return &resource->object_listener_list; +} + +SPA_EXPORT +const struct pw_protocol_marshal *pw_resource_get_marshal(struct pw_resource *resource) +{ + return resource->marshal; +} + +SPA_EXPORT +int pw_resource_ping(struct pw_resource *resource, int seq) +{ + int res = -EIO; + struct pw_impl_client *client = resource->client; + + if (client->core_resource != NULL) { + pw_core_resource_ping(client->core_resource, resource->id, seq); + res = client->send_seq; + pw_log_debug("%p: %u seq:%d ping %d", resource, resource->id, seq, res); + } + return res; +} + +SPA_EXPORT +int pw_resource_set_bound_id(struct pw_resource *resource, uint32_t global_id) +{ + struct pw_impl_client *client = resource->client; + + resource->bound_id = global_id; + if (client->core_resource != NULL) { + pw_log_debug("%p: %u global_id:%u", resource, resource->id, global_id); + pw_core_resource_bound_id(client->core_resource, resource->id, global_id); + } + return 0; +} + +SPA_EXPORT +uint32_t pw_resource_get_bound_id(struct pw_resource *resource) +{ + return resource->bound_id; +} + +static void SPA_PRINTF_FUNC(4, 0) +pw_resource_errorv_id(struct pw_resource *resource, uint32_t id, int res, const char *error, va_list ap) +{ + struct pw_impl_client *client; + + if (resource) { + client = resource->client; + if (client->core_resource != NULL) + pw_core_resource_errorv(client->core_resource, + id, client->recv_seq, res, error, ap); + } else { + pw_logtv(SPA_LOG_LEVEL_ERROR, PW_LOG_TOPIC_DEFAULT, error, ap); + } +} + +SPA_EXPORT +void pw_resource_errorf(struct pw_resource *resource, int res, const char *error, ...) +{ + va_list ap; + va_start(ap, error); + if (resource) + pw_resource_errorv_id(resource, resource->id, res, error, ap); + else + pw_logtv(SPA_LOG_LEVEL_ERROR, PW_LOG_TOPIC_DEFAULT, error, ap); + va_end(ap); +} + +SPA_EXPORT +void pw_resource_errorf_id(struct pw_resource *resource, uint32_t id, int res, const char *error, ...) +{ + va_list ap; + va_start(ap, error); + if (resource) + pw_resource_errorv_id(resource, id, res, error, ap); + else + pw_logtv(SPA_LOG_LEVEL_ERROR, PW_LOG_TOPIC_DEFAULT, error, ap); + va_end(ap); +} + +SPA_EXPORT +void pw_resource_error(struct pw_resource *resource, int res, const char *error) +{ + struct pw_impl_client *client; + if (resource) { + client = resource->client; + if (client->core_resource != NULL) + pw_core_resource_error(client->core_resource, + resource->id, client->recv_seq, res, error); + } else { + pw_log_error("%s: %s", error, spa_strerror(res)); + } +} + +SPA_EXPORT +void pw_resource_ref(struct pw_resource *resource) +{ + assert(resource->refcount > 0); + resource->refcount++; +} + +SPA_EXPORT +void pw_resource_unref(struct pw_resource *resource) +{ + assert(resource->refcount > 0); + if (--resource->refcount > 0) + return; + + pw_log_debug("%p: free %u", resource, resource->id); + assert(resource->destroyed); + +#if DEBUG_LISTENERS + { + struct spa_hook *h; + spa_list_for_each(h, &resource->object_listener_list.list, link) { + pw_log_warn("%p: resource %u: leaked object listener %p", + resource, resource->id, h); + break; + } + spa_list_for_each(h, &resource->listener_list.list, link) { + pw_log_warn("%p: resource %u: leaked listener %p", + resource, resource->id, h); + break; + } + } +#endif + spa_hook_list_clean(&resource->listener_list); + spa_hook_list_clean(&resource->object_listener_list); + + free(resource); +} + +SPA_EXPORT +void pw_resource_destroy(struct pw_resource *resource) +{ + struct pw_impl_client *client = resource->client; + + pw_log_debug("%p: destroy %u", resource, resource->id); + assert(!resource->destroyed); + resource->destroyed = true; + + if (resource->global) { + spa_list_remove(&resource->link); + resource->global = NULL; + } + + pw_resource_emit_destroy(resource); + + pw_map_insert_at(&client->objects, resource->id, NULL); + pw_impl_client_emit_resource_removed(client, resource); + + if (client->core_resource && !resource->removed) + pw_core_resource_remove_id(client->core_resource, resource->id); + + pw_resource_unref(resource); +} + +SPA_EXPORT +void pw_resource_remove(struct pw_resource *resource) +{ + resource->removed = true; + pw_resource_destroy(resource); +} diff --git a/src/pipewire/resource.h b/src/pipewire/resource.h new file mode 100644 index 0000000..24d458c --- /dev/null +++ b/src/pipewire/resource.h @@ -0,0 +1,174 @@ +/* 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. + */ + +#ifndef PIPEWIRE_RESOURCE_H +#define PIPEWIRE_RESOURCE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <spa/utils/hook.h> + +/** \defgroup pw_resource Resource + * + * \brief Client owned objects + * + * Resources represent objects owned by a \ref pw_impl_client. They are + * the result of binding to a global resource or by calling API that + * creates client owned objects. + * + * The client usually has a proxy object associated with the resource + * that it can use to communicate with the resource. See \ref page_proxy. + * + * Resources are destroyed when the client or the bound object is + * destroyed. + * + */ + + +/** + * \addtogroup pw_resource + * \{ + */ +struct pw_resource; + +#include <pipewire/impl-client.h> + +/** Resource events */ +struct pw_resource_events { +#define PW_VERSION_RESOURCE_EVENTS 0 + uint32_t version; + + /** The resource is destroyed */ + void (*destroy) (void *data); + + /** a reply to a ping event completed */ + void (*pong) (void *data, int seq); + + /** an error occurred on the resource */ + void (*error) (void *data, int seq, int res, const char *message); +}; + +/** Make a new resource for client */ +struct pw_resource * +pw_resource_new(struct pw_impl_client *client, /**< the client owning the resource */ + uint32_t id, /**< the remote per client id */ + uint32_t permissions, /**< permissions on this resource */ + const char *type, /**< interface of the resource */ + uint32_t version, /**< requested interface version */ + size_t user_data_size /**< extra user data size */); + +/** Destroy a resource */ +void pw_resource_destroy(struct pw_resource *resource); + +/** Remove a resource, like pw_resource_destroy but without sending a + * remove_id message to the client */ +void pw_resource_remove(struct pw_resource *resource); + +/** Get the client owning this resource */ +struct pw_impl_client *pw_resource_get_client(struct pw_resource *resource); + +/** Get the unique id of this resource */ +uint32_t pw_resource_get_id(struct pw_resource *resource); + +/** Get the permissions of this resource */ +uint32_t pw_resource_get_permissions(struct pw_resource *resource); + +/** Get the type and optionally the version of this resource */ +const char *pw_resource_get_type(struct pw_resource *resource, uint32_t *version); + +/** Get the protocol used for this resource */ +struct pw_protocol *pw_resource_get_protocol(struct pw_resource *resource); + +/** Get the user data for the resource, the size was given in \ref pw_resource_new */ +void *pw_resource_get_user_data(struct pw_resource *resource); + +/** Add an event listener */ +void pw_resource_add_listener(struct pw_resource *resource, + struct spa_hook *listener, + const struct pw_resource_events *events, + void *data); + +/** Set the resource implementation. */ +void pw_resource_add_object_listener(struct pw_resource *resource, + struct spa_hook *listener, + const void *funcs, + void *data); + +/** Generate an ping event for a resource. This will generate a pong event + * with the same \a sequence number in the return value. */ +int pw_resource_ping(struct pw_resource *resource, int seq); + +/** ref/unref a resource, Since 0.3.52 */ +void pw_resource_ref(struct pw_resource *resource); +void pw_resource_unref(struct pw_resource *resource); + +/** Notify global id this resource is bound to */ +int pw_resource_set_bound_id(struct pw_resource *resource, uint32_t global_id); + +/** Get the global id this resource is bound to or SPA_ID_INVALID when not bound */ +uint32_t pw_resource_get_bound_id(struct pw_resource *resource); + +/** Generate an error for a resource */ +void pw_resource_error(struct pw_resource *resource, int res, const char *error); +void pw_resource_errorf(struct pw_resource *resource, int res, const char *error, ...) SPA_PRINTF_FUNC(3, 4); +void pw_resource_errorf_id(struct pw_resource *resource, uint32_t id, int res, const char *error, ...) SPA_PRINTF_FUNC(4, 5); + +/** Get the list of object listeners from a resource */ +struct spa_hook_list *pw_resource_get_object_listeners(struct pw_resource *resource); + +/** Get the marshal functions for the resource */ +const struct pw_protocol_marshal *pw_resource_get_marshal(struct pw_resource *resource); + +/** install a marshal function on a resource */ +int pw_resource_install_marshal(struct pw_resource *resource, bool implementor); + +#define pw_resource_notify(r,type,event,version,...) \ + spa_hook_list_call(pw_resource_get_object_listeners(r), \ + type, event, version, ## __VA_ARGS__) + +#define pw_resource_call(r,type,method,version,...) \ + spa_interface_call((struct spa_interface*)r, \ + type, method, version, ##__VA_ARGS__) + +#define pw_resource_call_res(r,type,method,version,...) \ +({ \ + int _res = -ENOTSUP; \ + spa_interface_call_res((struct spa_interface*)r, \ + type, _res, method, version, ##__VA_ARGS__); \ + _res; \ +}) + + +/** + * \} + */ + + +#ifdef __cplusplus +} +#endif + +#endif /* PIPEWIRE_RESOURCE_H */ diff --git a/src/pipewire/settings.c b/src/pipewire/settings.c new file mode 100644 index 0000000..c512e96 --- /dev/null +++ b/src/pipewire/settings.c @@ -0,0 +1,337 @@ +/* PipeWire + * + * Copyright © 2021 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 <spa/utils/result.h> +#include <spa/utils/string.h> +#include <spa/monitor/device.h> +#include <spa/monitor/utils.h> +#include <spa/pod/filter.h> +#include <spa/utils/json.h> + +#include <pipewire/impl.h> +#include <pipewire/private.h> +#include "pipewire/array.h" +#include "pipewire/core.h" + +#include <pipewire/extensions/metadata.h> + +#define NAME "settings" + +#define DEFAULT_CLOCK_RATE 48000u +#define DEFAULT_CLOCK_RATES "[ 48000 ]" +#define DEFAULT_CLOCK_QUANTUM 1024u +#define DEFAULT_CLOCK_MIN_QUANTUM 32u +#define DEFAULT_CLOCK_MAX_QUANTUM 2048u +#define DEFAULT_CLOCK_QUANTUM_LIMIT 8192u +#define DEFAULT_CLOCK_POWER_OF_TWO_QUANTUM true +#define DEFAULT_VIDEO_WIDTH 640 +#define DEFAULT_VIDEO_HEIGHT 480 +#define DEFAULT_VIDEO_RATE_NUM 25u +#define DEFAULT_VIDEO_RATE_DENOM 1u +#define DEFAULT_LINK_MAX_BUFFERS 64u +#define DEFAULT_MEM_WARN_MLOCK false +#define DEFAULT_MEM_ALLOW_MLOCK true +#define DEFAULT_CHECK_QUANTUM false +#define DEFAULT_CHECK_RATE false + +struct impl { + struct pw_context *context; + struct pw_impl_metadata *metadata; + + struct spa_hook metadata_listener; +}; + +static void metadata_destroy(void *data) +{ + struct impl *impl = data; + spa_hook_remove(&impl->metadata_listener); + impl->metadata = NULL; +} + +static uint32_t get_default_int(struct pw_properties *properties, const char *name, uint32_t def) +{ + uint32_t val; + const char *str; + if ((str = pw_properties_get(properties, name)) != NULL) + val = atoi(str); + else { + val = def; + pw_properties_setf(properties, name, "%d", val); + } + return val; +} + +static bool get_default_bool(struct pw_properties *properties, const char *name, bool def) +{ + bool val; + const char *str; + if ((str = pw_properties_get(properties, name)) != NULL) + val = pw_properties_parse_bool(str); + else { + val = def; + pw_properties_set(properties, name, val ? "true" : "false"); + } + return val; +} + +static bool uint32_array_contains(uint32_t *vals, uint32_t n_vals, uint32_t val) +{ + uint32_t i; + for (i = 0; i < n_vals; i++) + if (vals[i] == val) + return true; + return false; +} + +static uint32_t parse_uint32_array(const char *str, uint32_t *vals, uint32_t max, uint32_t def) +{ + uint32_t count = 0, r; + struct spa_json it[2]; + char v[256]; + + spa_json_init(&it[0], str, strlen(str)); + if (spa_json_enter_array(&it[0], &it[1]) <= 0) + spa_json_init(&it[1], str, strlen(str)); + + while (spa_json_get_string(&it[1], v, sizeof(v)) > 0 && + count < max) { + if (spa_atou32(v, &r, 0)) + vals[count++] = r; + } + if (!uint32_array_contains(vals, count, def)) + count = 0; + return count; +} + +static uint32_t parse_clock_rate(struct pw_properties *properties, const char *name, + uint32_t *rates, const char *def_rates, uint32_t def) +{ + const char *str; + uint32_t count = 0; + + if ((str = pw_properties_get(properties, name)) == NULL) + str = def_rates; + + count = parse_uint32_array(str, rates, MAX_RATES, def); + if (count == 0) + count = parse_uint32_array(def_rates, rates, MAX_RATES, def); + if (count == 0) + goto fallback; + + return count; +fallback: + rates[0] = def; + pw_properties_setf(properties, name, "[ %u ]", def); + return 1; +} + +static int metadata_property(void *data, uint32_t subject, const char *key, + const char *type, const char *value) +{ + struct impl *impl = data; + struct pw_context *context = impl->context; + struct settings *d = &context->defaults; + struct settings *s = &context->settings; + uint32_t v; + bool recalc = false; + + if (subject != PW_ID_CORE) + return 0; + + if (spa_streq(key, "log.level")) { + v = value ? atoi(value) : 3; + pw_log_set_level(v); + } else if (spa_streq(key, "clock.rate")) { + v = value ? atoi(value) : 0; + s->clock_rate = v == 0 ? d->clock_rate : v; + recalc = true; + } else if (spa_streq(key, "clock.allowed-rates")) { + s->n_clock_rates = parse_uint32_array(value, + s->clock_rates, MAX_RATES, s->clock_rate); + if (s->n_clock_rates == 0) { + s->n_clock_rates = d->n_clock_rates; + memcpy(s->clock_rates, d->clock_rates, MAX_RATES * sizeof(uint32_t)); + } + recalc = true; + } else if (spa_streq(key, "clock.quantum")) { + v = value ? atoi(value) : 0; + s->clock_quantum = v == 0 ? d->clock_quantum : v; + recalc = true; + } else if (spa_streq(key, "clock.min-quantum")) { + v = value ? atoi(value) : 0; + s->clock_min_quantum = v == 0 ? d->clock_min_quantum : v; + recalc = true; + } else if (spa_streq(key, "clock.max-quantum")) { + v = value ? atoi(value) : 0; + s->clock_max_quantum = v == 0 ? d->clock_max_quantum : v; + recalc = true; + } else if (spa_streq(key, "clock.force-rate")) { + v = value ? atoi(value) : 0; + if (v != 0 && s->check_rate && + !uint32_array_contains(s->clock_rates, s->n_clock_rates, v)) { + pw_log_info("invalid %s: %d not in allowed rates", key, v); + } else { + s->clock_force_rate = v; + recalc = true; + } + } else if (spa_streq(key, "clock.force-quantum")) { + v = value ? atoi(value) : 0; + if (v != 0 && s->check_quantum && + (v < s->clock_min_quantum || v > s->clock_max_quantum)) { + pw_log_info("invalid %s: %d not in (%d-%d)", key, v, + s->clock_min_quantum, s->clock_max_quantum); + } else { + s->clock_force_quantum = v; + recalc = true; + } + } + if (recalc) + pw_context_recalc_graph(context, "settings changed"); + + return 0; +} + +static const struct pw_impl_metadata_events metadata_events = { + PW_VERSION_IMPL_METADATA_EVENTS, + .destroy = metadata_destroy, + .property = metadata_property, +}; + +void pw_settings_init(struct pw_context *this) +{ + struct pw_properties *p = this->properties; + struct settings *d = &this->defaults; + + d->clock_rate = get_default_int(p, "default.clock.rate", DEFAULT_CLOCK_RATE); + d->n_clock_rates = parse_clock_rate(p, "default.clock.allowed-rates", d->clock_rates, + DEFAULT_CLOCK_RATES, d->clock_rate); + d->clock_quantum = get_default_int(p, "default.clock.quantum", DEFAULT_CLOCK_QUANTUM); + d->clock_min_quantum = get_default_int(p, "default.clock.min-quantum", DEFAULT_CLOCK_MIN_QUANTUM); + d->clock_max_quantum = get_default_int(p, "default.clock.max-quantum", DEFAULT_CLOCK_MAX_QUANTUM); + d->clock_quantum_limit = get_default_int(p, "default.clock.quantum-limit", DEFAULT_CLOCK_QUANTUM_LIMIT); + d->video_size.width = get_default_int(p, "default.video.width", DEFAULT_VIDEO_WIDTH); + d->video_size.height = get_default_int(p, "default.video.height", DEFAULT_VIDEO_HEIGHT); + d->video_rate.num = get_default_int(p, "default.video.rate.num", DEFAULT_VIDEO_RATE_NUM); + d->video_rate.denom = get_default_int(p, "default.video.rate.denom", DEFAULT_VIDEO_RATE_DENOM); + + d->log_level = get_default_int(p, "log.level", pw_log_level); + d->clock_power_of_two_quantum = get_default_bool(p, "clock.power-of-two-quantum", + DEFAULT_CLOCK_POWER_OF_TWO_QUANTUM); + d->link_max_buffers = get_default_int(p, "link.max-buffers", DEFAULT_LINK_MAX_BUFFERS); + d->mem_warn_mlock = get_default_bool(p, "mem.warn-mlock", DEFAULT_MEM_WARN_MLOCK); + d->mem_allow_mlock = get_default_bool(p, "mem.allow-mlock", DEFAULT_MEM_ALLOW_MLOCK); + + d->check_quantum = get_default_bool(p, "settings.check-quantum", DEFAULT_CHECK_QUANTUM); + d->check_rate = get_default_bool(p, "settings.check-rate", DEFAULT_CHECK_RATE); + + d->clock_quantum_limit = SPA_CLAMP(d->clock_quantum_limit, + CLOCK_MIN_QUANTUM, CLOCK_MAX_QUANTUM); + d->clock_max_quantum = SPA_CLAMP(d->clock_max_quantum, + CLOCK_MIN_QUANTUM, d->clock_quantum_limit); + d->clock_min_quantum = SPA_CLAMP(d->clock_min_quantum, + CLOCK_MIN_QUANTUM, d->clock_max_quantum); + d->clock_quantum = SPA_CLAMP(d->clock_quantum, + d->clock_min_quantum, d->clock_max_quantum); +} + +static void expose_settings(struct pw_context *context, struct pw_impl_metadata *metadata) +{ + struct settings *s = &context->settings; + uint32_t i, o; + char rates[MAX_RATES*16] = ""; + + pw_impl_metadata_set_propertyf(metadata, + PW_ID_CORE, "log.level", "", "%d", s->log_level); + pw_impl_metadata_set_propertyf(metadata, + PW_ID_CORE, "clock.rate", "", "%d", s->clock_rate); + for (i = 0, o = 0; i < s->n_clock_rates; i++) { + int r = snprintf(rates+o, sizeof(rates)-o, "%s%d", i == 0 ? "" : ", ", + s->clock_rates[i]); + if (r < 0 || o + r >= (int)sizeof(rates)) { + snprintf(rates, sizeof(rates), "%d", s->clock_rate); + break; + } + o += r; + } + if (s->n_clock_rates == 0) + snprintf(rates, sizeof(rates), "%d", s->clock_rate); + + pw_impl_metadata_set_propertyf(metadata, + PW_ID_CORE, "clock.allowed-rates", "", "[ %s ]", rates); + pw_impl_metadata_set_propertyf(metadata, + PW_ID_CORE, "clock.quantum", "", "%d", s->clock_quantum); + pw_impl_metadata_set_propertyf(metadata, + PW_ID_CORE, "clock.min-quantum", "", "%d", s->clock_min_quantum); + pw_impl_metadata_set_propertyf(metadata, + PW_ID_CORE, "clock.max-quantum", "", "%d", s->clock_max_quantum); + pw_impl_metadata_set_propertyf(metadata, + PW_ID_CORE, "clock.force-quantum", "", "%d", s->clock_force_quantum); + pw_impl_metadata_set_propertyf(metadata, + PW_ID_CORE, "clock.force-rate", "", "%d", s->clock_force_rate); +} + +int pw_settings_expose(struct pw_context *context) +{ + struct impl *impl; + + impl = calloc(1, sizeof(*impl)); + if (impl == NULL) + return -errno; + + impl->context = context; + impl->metadata = pw_context_create_metadata(context, "settings", NULL, 0); + if (impl->metadata == NULL) + goto error_free; + + expose_settings(context, impl->metadata); + + pw_impl_metadata_add_listener(impl->metadata, + &impl->metadata_listener, + &metadata_events, impl); + + pw_impl_metadata_register(impl->metadata, NULL); + + context->settings_impl = impl; + + return 0; + +error_free: + free(impl); + return -errno; +} + +void pw_settings_clean(struct pw_context *context) +{ + struct impl *impl = context->settings_impl; + + if (impl == NULL) + return; + + context->settings_impl = NULL; + if (impl->metadata != NULL) + pw_impl_metadata_destroy(impl->metadata); + free(impl); +} diff --git a/src/pipewire/stream.c b/src/pipewire/stream.c new file mode 100644 index 0000000..da49b66 --- /dev/null +++ b/src/pipewire/stream.c @@ -0,0 +1,2414 @@ +/* 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 <errno.h> +#include <stdio.h> +#include <math.h> +#include <sys/mman.h> +#include <time.h> + +#include <spa/buffer/alloc.h> +#include <spa/param/props.h> +#include <spa/param/format-utils.h> +#include <spa/node/io.h> +#include <spa/node/utils.h> +#include <spa/utils/ringbuffer.h> +#include <spa/pod/filter.h> +#include <spa/pod/dynamic.h> +#include <spa/debug/types.h> + +#define PW_ENABLE_DEPRECATED + +#include "pipewire/pipewire.h" +#include "pipewire/stream.h" +#include "pipewire/private.h" + +PW_LOG_TOPIC_EXTERN(log_stream); +#define PW_LOG_TOPIC_DEFAULT log_stream + +#define MAX_BUFFERS 64 + +#define MASK_BUFFERS (MAX_BUFFERS-1) + +static bool mlock_warned = false; + +static uint32_t mappable_dataTypes = (1<<SPA_DATA_MemFd); + +struct buffer { + struct pw_buffer this; + uint32_t id; +#define BUFFER_FLAG_MAPPED (1 << 0) +#define BUFFER_FLAG_QUEUED (1 << 1) +#define BUFFER_FLAG_ADDED (1 << 2) + uint32_t flags; + struct spa_meta_busy *busy; +}; + +struct queue { + uint32_t ids[MAX_BUFFERS]; + struct spa_ringbuffer ring; + uint64_t incount; + uint64_t outcount; +}; + +struct data { + struct pw_context *context; + struct spa_hook stream_listener; +}; + +struct param { + uint32_t id; +#define PARAM_FLAG_LOCKED (1 << 0) + uint32_t flags; + struct spa_list link; + struct spa_pod *param; +}; + +struct control { + uint32_t id; + uint32_t type; + uint32_t container; + struct spa_list link; + struct pw_stream_control control; + struct spa_pod *info; + unsigned int emitted:1; + float values[64]; +}; + +struct stream { + struct pw_stream this; + + const char *path; + + struct pw_context *context; + struct spa_hook context_listener; + + enum spa_direction direction; + enum pw_stream_flags flags; + + struct pw_impl_node *node; + + struct spa_node impl_node; + struct spa_node_methods node_methods; + struct spa_hook_list hooks; + struct spa_callbacks callbacks; + + struct spa_io_clock *clock; + struct spa_io_position *position; + struct spa_io_buffers *io; + struct spa_io_rate_match *rate_match; + uint32_t rate_queued; + struct { + struct spa_io_position *position; + } rt; + + uint32_t port_change_mask_all; + struct spa_port_info port_info; + struct pw_properties *port_props; +#define PORT_EnumFormat 0 +#define PORT_Meta 1 +#define PORT_IO 2 +#define PORT_Format 3 +#define PORT_Buffers 4 +#define PORT_Latency 5 +#define N_PORT_PARAMS 6 + struct spa_param_info port_params[N_PORT_PARAMS]; + + struct spa_list param_list; + + uint32_t change_mask_all; + struct spa_node_info info; +#define NODE_PropInfo 0 +#define NODE_Props 1 +#define NODE_EnumFormat 2 +#define NODE_Format 3 +#define N_NODE_PARAMS 4 + struct spa_param_info params[N_NODE_PARAMS]; + + uint32_t media_type; + uint32_t media_subtype; + + struct buffer buffers[MAX_BUFFERS]; + uint32_t n_buffers; + + struct queue dequeued; + struct queue queued; + + struct data data; + uintptr_t seq; + struct pw_time time; + uint64_t base_pos; + uint32_t clock_id; + struct spa_latency_info latency; + uint64_t quantum; + + struct spa_callbacks rt_callbacks; + + unsigned int disconnecting:1; + unsigned int disconnect_core:1; + unsigned int draining:1; + unsigned int drained:1; + unsigned int allow_mlock:1; + unsigned int warn_mlock:1; + unsigned int process_rt:1; + unsigned int driving:1; + unsigned int using_trigger:1; + unsigned int trigger:1; + int in_set_control; +}; + +static int get_param_index(uint32_t id) +{ + switch (id) { + case SPA_PARAM_PropInfo: + return NODE_PropInfo; + case SPA_PARAM_Props: + return NODE_Props; + case SPA_PARAM_EnumFormat: + return NODE_EnumFormat; + case SPA_PARAM_Format: + return NODE_Format; + default: + return -1; + } +} + +static int get_port_param_index(uint32_t id) +{ + switch (id) { + case SPA_PARAM_EnumFormat: + return PORT_EnumFormat; + case SPA_PARAM_Meta: + return PORT_Meta; + case SPA_PARAM_IO: + return PORT_IO; + case SPA_PARAM_Format: + return PORT_Format; + case SPA_PARAM_Buffers: + return PORT_Buffers; + case SPA_PARAM_Latency: + return PORT_Latency; + default: + return -1; + } +} + +static void fix_datatype(const struct spa_pod *param) +{ + const struct spa_pod_prop *pod_param; + const struct spa_pod *vals; + uint32_t dataType, n_vals, choice; + + pod_param = spa_pod_find_prop(param, NULL, SPA_PARAM_BUFFERS_dataType); + if (pod_param == NULL) + return; + + vals = spa_pod_get_values(&pod_param->value, &n_vals, &choice); + if (n_vals == 0) + return; + + if (spa_pod_get_int(&vals[0], (int32_t*)&dataType) < 0) + return; + + pw_log_debug("dataType: %u", dataType); + if (dataType & (1u << SPA_DATA_MemPtr)) { + SPA_POD_VALUE(struct spa_pod_int, &vals[0]) = + dataType | mappable_dataTypes; + pw_log_debug("Change dataType: %u -> %u", dataType, + SPA_POD_VALUE(struct spa_pod_int, &vals[0])); + } +} + +static struct param *add_param(struct stream *impl, + uint32_t id, uint32_t flags, const struct spa_pod *param) +{ + struct param *p; + int idx; + + if (param == NULL || !spa_pod_is_object(param)) { + errno = EINVAL; + return NULL; + } + if (id == SPA_ID_INVALID) + id = SPA_POD_OBJECT_ID(param); + + p = malloc(sizeof(struct param) + SPA_POD_SIZE(param)); + if (p == NULL) + return NULL; + + if (id == SPA_PARAM_Buffers && + SPA_FLAG_IS_SET(impl->flags, PW_STREAM_FLAG_MAP_BUFFERS) && + impl->direction == SPA_DIRECTION_INPUT) + fix_datatype(param); + + p->id = id; + p->flags = flags; + p->param = SPA_PTROFF(p, sizeof(struct param), struct spa_pod); + memcpy(p->param, param, SPA_POD_SIZE(param)); + SPA_POD_OBJECT_ID(p->param) = id; + + spa_list_append(&impl->param_list, &p->link); + + if ((idx = get_param_index(id)) != -1) { + impl->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; + impl->params[idx].flags |= SPA_PARAM_INFO_READ; + impl->params[idx].user++; + } + if ((idx = get_port_param_index(id)) != -1) { + impl->port_info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; + impl->port_params[idx].flags |= SPA_PARAM_INFO_READ; + impl->port_params[idx].user++; + } + return p; +} + +static void clear_params(struct stream *impl, uint32_t id) +{ + struct param *p, *t; + + spa_list_for_each_safe(p, t, &impl->param_list, link) { + if (id == SPA_ID_INVALID || + (p->id == id && !(p->flags & PARAM_FLAG_LOCKED))) { + spa_list_remove(&p->link); + free(p); + } + } +} + +static int update_params(struct stream *impl, uint32_t id, + const struct spa_pod **params, uint32_t n_params) +{ + uint32_t i; + int res = 0; + + if (id != SPA_ID_INVALID) { + clear_params(impl, id); + } else { + for (i = 0; i < n_params; i++) { + if (params[i] == NULL || !spa_pod_is_object(params[i])) + continue; + clear_params(impl, SPA_POD_OBJECT_ID(params[i])); + } + } + for (i = 0; i < n_params; i++) { + if (add_param(impl, id, 0, params[i]) == NULL) { + res = -errno; + break; + } + } + return res; +} + + +static inline int queue_push(struct stream *stream, struct queue *queue, struct buffer *buffer) +{ + uint32_t index; + + if (SPA_FLAG_IS_SET(buffer->flags, BUFFER_FLAG_QUEUED) || + buffer->id >= stream->n_buffers) + return -EINVAL; + + SPA_FLAG_SET(buffer->flags, BUFFER_FLAG_QUEUED); + queue->incount += buffer->this.size; + + spa_ringbuffer_get_write_index(&queue->ring, &index); + queue->ids[index & MASK_BUFFERS] = buffer->id; + spa_ringbuffer_write_update(&queue->ring, index + 1); + + return 0; +} + +static inline bool queue_is_empty(struct stream *stream, struct queue *queue) +{ + uint32_t index; + return spa_ringbuffer_get_read_index(&queue->ring, &index) < 1; +} + +static inline struct buffer *queue_pop(struct stream *stream, struct queue *queue) +{ + uint32_t index, id; + struct buffer *buffer; + + if (spa_ringbuffer_get_read_index(&queue->ring, &index) < 1) { + errno = EPIPE; + return NULL; + } + + id = queue->ids[index & MASK_BUFFERS]; + spa_ringbuffer_read_update(&queue->ring, index + 1); + + buffer = &stream->buffers[id]; + queue->outcount += buffer->this.size; + SPA_FLAG_CLEAR(buffer->flags, BUFFER_FLAG_QUEUED); + + return buffer; +} +static inline void clear_queue(struct stream *stream, struct queue *queue) +{ + spa_ringbuffer_init(&queue->ring); + queue->incount = queue->outcount; +} + +static bool stream_set_state(struct pw_stream *stream, enum pw_stream_state state, const char *error) +{ + enum pw_stream_state old = stream->state; + bool res = old != state; + + if (res) { + free(stream->error); + stream->error = error ? strdup(error) : NULL; + + pw_log_debug("%p: update state from %s -> %s (%s)", stream, + pw_stream_state_as_string(old), + pw_stream_state_as_string(state), stream->error); + + if (state == PW_STREAM_STATE_ERROR) + pw_log_error("%p: error %s", stream, error); + + stream->state = state; + pw_stream_emit_state_changed(stream, old, state, error); + } + return res; +} + +static struct buffer *get_buffer(struct pw_stream *stream, uint32_t id) +{ + struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this); + if (id < impl->n_buffers) + return &impl->buffers[id]; + + errno = EINVAL; + return NULL; +} + +static inline uint32_t update_requested(struct stream *impl) +{ + uint32_t index, id, res = 0; + struct buffer *buffer; + struct spa_io_rate_match *r = impl->rate_match; + + if (spa_ringbuffer_get_read_index(&impl->dequeued.ring, &index) < 1) + return 0; + + id = impl->dequeued.ids[index & MASK_BUFFERS]; + buffer = &impl->buffers[id]; + if (r) { + buffer->this.requested = r->size; + res = r->size > 0 ? 1 : 0; + } else { + buffer->this.requested = impl->quantum; + res = 1; + } + pw_log_trace_fp("%p: update buffer:%u size:%"PRIu64, impl, id, buffer->this.requested); + return res; +} + +static int +do_call_process(struct spa_loop *loop, + bool async, uint32_t seq, const void *data, size_t size, void *user_data) +{ + struct stream *impl = user_data; + struct pw_stream *stream = &impl->this; + pw_log_trace_fp("%p: do process", stream); + pw_stream_emit_process(stream); + return 0; +} + +static inline void call_process(struct stream *impl) +{ + pw_log_trace_fp("%p: call process rt:%u", impl, impl->process_rt); + if (impl->direction == SPA_DIRECTION_OUTPUT && update_requested(impl) <= 0) + return; + if (impl->process_rt) + spa_callbacks_call(&impl->rt_callbacks, struct pw_stream_events, process, 0); + else + pw_loop_invoke(impl->context->main_loop, + do_call_process, 1, NULL, 0, false, impl); +} + +static int +do_call_drained(struct spa_loop *loop, + bool async, uint32_t seq, const void *data, size_t size, void *user_data) +{ + struct stream *impl = user_data; + struct pw_stream *stream = &impl->this; + pw_log_trace_fp("%p: drained", stream); + pw_stream_emit_drained(stream); + return 0; +} + +static void call_drained(struct stream *impl) +{ + pw_loop_invoke(impl->context->main_loop, + do_call_drained, 1, NULL, 0, false, impl); +} + +static int +do_call_trigger_done(struct spa_loop *loop, + bool async, uint32_t seq, const void *data, size_t size, void *user_data) +{ + struct stream *impl = user_data; + struct pw_stream *stream = &impl->this; + pw_log_trace_fp("%p: trigger_done", stream); + pw_stream_emit_trigger_done(stream); + return 0; +} + +static void call_trigger_done(struct stream *impl) +{ + pw_loop_invoke(impl->context->main_loop, + do_call_trigger_done, 1, NULL, 0, false, impl); +} + +static int +do_set_position(struct spa_loop *loop, + bool async, uint32_t seq, const void *data, size_t size, void *user_data) +{ + struct stream *impl = user_data; + impl->rt.position = impl->position; + return 0; +} + +static int impl_set_io(void *object, uint32_t id, void *data, size_t size) +{ + struct stream *impl = object; + struct pw_stream *stream = &impl->this; + + pw_log_debug("%p: set io id %d (%s) %p %zd", impl, id, + spa_debug_type_find_name(spa_type_io, id), data, size); + + switch(id) { + case SPA_IO_Clock: + if (data && size >= sizeof(struct spa_io_clock)) + impl->clock = data; + else + impl->clock = NULL; + break; + case SPA_IO_Position: + if (data && size >= sizeof(struct spa_io_position)) + impl->position = data; + else + impl->position = NULL; + + pw_loop_invoke(impl->context->data_loop, + do_set_position, 1, NULL, 0, true, impl); + break; + default: + break; + } + impl->driving = impl->clock && impl->position && impl->position->clock.id == impl->clock->id; + pw_stream_emit_io_changed(stream, id, data, size); + + return 0; +} + +static int enum_params(void *object, bool is_port, int seq, uint32_t id, uint32_t start, uint32_t num, + const struct spa_pod *filter) +{ + struct stream *d = object; + struct spa_result_node_params result; + uint8_t buffer[1024]; + struct spa_pod_dynamic_builder b; + uint32_t count = 0; + struct param *p; + bool found = false; + + spa_return_val_if_fail(num != 0, -EINVAL); + + result.id = id; + result.next = 0; + + pw_log_debug("%p: param id %d (%s) start:%d num:%d", d, id, + spa_debug_type_find_name(spa_type_param, id), + start, num); + + spa_list_for_each(p, &d->param_list, link) { + struct spa_pod *param; + + param = p->param; + if (param == NULL || p->id != id) + continue; + + found = true; + + result.index = result.next++; + if (result.index < start) + continue; + + spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 4096); + if (spa_pod_filter(&b.b, &result.param, param, filter) == 0) { + spa_node_emit_result(&d->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); + count++; + } + spa_pod_dynamic_builder_clean(&b); + + if (count == num) + break; + } + return found ? 0 : -ENOENT; +} + +static int impl_enum_params(void *object, int seq, uint32_t id, uint32_t start, uint32_t num, + const struct spa_pod *filter) +{ + return enum_params(object, false, seq, id, start, num, filter); +} + +static int impl_set_param(void *object, uint32_t id, uint32_t flags, const struct spa_pod *param) +{ + struct stream *impl = object; + struct pw_stream *stream = &impl->this; + + if (id != SPA_PARAM_Props) + return -ENOTSUP; + + if (impl->in_set_control == 0) + pw_stream_emit_param_changed(stream, id, param); + + return 0; +} + +static inline void copy_position(struct stream *impl, int64_t queued) +{ + struct spa_io_position *p = impl->rt.position; + + SEQ_WRITE(impl->seq); + if (SPA_LIKELY(p != NULL)) { + impl->time.now = p->clock.nsec; + impl->time.rate = p->clock.rate; + if (SPA_UNLIKELY(impl->clock_id != p->clock.id)) { + impl->base_pos = p->clock.position - impl->time.ticks; + impl->clock_id = p->clock.id; + } + impl->time.ticks = p->clock.position - impl->base_pos; + impl->time.delay = 0; + impl->time.queued = queued; + impl->quantum = p->clock.duration; + } + if (SPA_LIKELY(impl->rate_match != NULL)) + impl->rate_queued = impl->rate_match->delay; + SEQ_WRITE(impl->seq); +} + +static int impl_send_command(void *object, const struct spa_command *command) +{ + struct stream *impl = object; + struct pw_stream *stream = &impl->this; + uint32_t id = SPA_NODE_COMMAND_ID(command); + + pw_log_info("%p: command %s", impl, + spa_debug_type_find_name(spa_type_node_command_id, id)); + + switch (id) { + case SPA_NODE_COMMAND_Suspend: + case SPA_NODE_COMMAND_Flush: + case SPA_NODE_COMMAND_Pause: + pw_loop_invoke(impl->context->main_loop, + NULL, 0, NULL, 0, false, impl); + if (stream->state == PW_STREAM_STATE_STREAMING) { + + pw_log_debug("%p: pause", stream); + stream_set_state(stream, PW_STREAM_STATE_PAUSED, NULL); + } + break; + case SPA_NODE_COMMAND_Start: + if (stream->state == PW_STREAM_STATE_PAUSED) { + pw_log_debug("%p: start %d", stream, impl->direction); + + if (impl->direction == SPA_DIRECTION_INPUT) { + if (impl->io != NULL) + impl->io->status = SPA_STATUS_NEED_DATA; + } + else if (!impl->process_rt && !impl->driving) { + copy_position(impl, impl->queued.incount); + call_process(impl); + } + + stream_set_state(stream, PW_STREAM_STATE_STREAMING, NULL); + } + break; + default: + break; + } + pw_stream_emit_command(stream, command); + return 0; +} + +static void emit_node_info(struct stream *d, bool full) +{ + uint32_t i; + uint64_t old = full ? d->info.change_mask : 0; + if (full) + d->info.change_mask = d->change_mask_all; + if (d->info.change_mask != 0) { + if (d->info.change_mask & SPA_NODE_CHANGE_MASK_PARAMS) { + for (i = 0; i < d->info.n_params; i++) { + if (d->params[i].user > 0) { + d->params[i].flags ^= SPA_PARAM_INFO_SERIAL; + d->params[i].user = 0; + } + } + } + spa_node_emit_info(&d->hooks, &d->info); + } + d->info.change_mask = old; +} + +static void emit_port_info(struct stream *d, bool full) +{ + uint32_t i; + uint64_t old = full ? d->port_info.change_mask : 0; + if (full) + d->port_info.change_mask = d->port_change_mask_all; + if (d->port_info.change_mask != 0) { + if (d->port_info.change_mask & SPA_PORT_CHANGE_MASK_PARAMS) { + for (i = 0; i < d->port_info.n_params; i++) { + if (d->port_params[i].user > 0) { + d->port_params[i].flags ^= SPA_PARAM_INFO_SERIAL; + d->port_params[i].user = 0; + } + } + } + spa_node_emit_port_info(&d->hooks, d->direction, 0, &d->port_info); + } + d->port_info.change_mask = old; +} + +static int impl_add_listener(void *object, + struct spa_hook *listener, + const struct spa_node_events *events, + void *data) +{ + struct stream *d = object; + struct spa_hook_list save; + + spa_hook_list_isolate(&d->hooks, &save, listener, events, data); + + emit_node_info(d, true); + emit_port_info(d, true); + + spa_hook_list_join(&d->hooks, &save); + + return 0; +} + +static int impl_set_callbacks(void *object, + const struct spa_node_callbacks *callbacks, void *data) +{ + struct stream *d = object; + + d->callbacks = SPA_CALLBACKS_INIT(callbacks, data); + + return 0; +} + +static int impl_port_set_io(void *object, enum spa_direction direction, uint32_t port_id, + uint32_t id, void *data, size_t size) +{ + struct stream *impl = object; + struct pw_stream *stream = &impl->this; + + pw_log_debug("%p: id:%d (%s) %p %zd", impl, id, + spa_debug_type_find_name(spa_type_io, id), data, size); + + switch (id) { + case SPA_IO_Buffers: + if (data && size >= sizeof(struct spa_io_buffers)) + impl->io = data; + else + impl->io = NULL; + break; + case SPA_IO_RateMatch: + if (data && size >= sizeof(struct spa_io_rate_match)) + impl->rate_match = data; + else + impl->rate_match = NULL; + break; + } + pw_stream_emit_io_changed(stream, id, data, size); + + return 0; +} + +static int impl_port_enum_params(void *object, int seq, + enum spa_direction direction, uint32_t port_id, + uint32_t id, uint32_t start, uint32_t num, + const struct spa_pod *filter) +{ + return enum_params(object, true, seq, id, start, num, filter); +} + +static int map_data(struct stream *impl, struct spa_data *data, int prot) +{ + void *ptr; + struct pw_map_range range; + + pw_map_range_init(&range, data->mapoffset, data->maxsize, impl->context->sc_pagesize); + + ptr = mmap(NULL, range.size, prot, MAP_SHARED, data->fd, range.offset); + if (ptr == MAP_FAILED) { + pw_log_error("%p: failed to mmap buffer mem: %m", impl); + return -errno; + } + + data->data = SPA_PTROFF(ptr, range.start, void); + pw_log_debug("%p: fd %"PRIi64" mapped %d %d %p", impl, data->fd, + range.offset, range.size, data->data); + + if (impl->allow_mlock && mlock(data->data, data->maxsize) < 0) { + if (errno != ENOMEM || !mlock_warned) { + pw_log(impl->warn_mlock ? SPA_LOG_LEVEL_WARN : SPA_LOG_LEVEL_DEBUG, + "%p: Failed to mlock memory %p %u: %s", impl, + data->data, data->maxsize, + errno == ENOMEM ? + "This is not a problem but for best performance, " + "consider increasing RLIMIT_MEMLOCK" : strerror(errno)); + mlock_warned |= errno == ENOMEM; + } + } + return 0; +} + +static int unmap_data(struct stream *impl, struct spa_data *data) +{ + struct pw_map_range range; + + pw_map_range_init(&range, data->mapoffset, data->maxsize, impl->context->sc_pagesize); + + if (munmap(SPA_PTROFF(data->data, -range.start, void), range.size) < 0) + pw_log_warn("%p: failed to unmap: %m", impl); + + pw_log_debug("%p: fd %"PRIi64" unmapped", impl, data->fd); + return 0; +} + +static void clear_buffers(struct pw_stream *stream) +{ + struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this); + uint32_t i, j; + + pw_log_debug("%p: clear buffers %d", stream, impl->n_buffers); + + for (i = 0; i < impl->n_buffers; i++) { + struct buffer *b = &impl->buffers[i]; + + if (SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_ADDED)) + pw_stream_emit_remove_buffer(stream, &b->this); + + if (SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_MAPPED)) { + for (j = 0; j < b->this.buffer->n_datas; j++) { + struct spa_data *d = &b->this.buffer->datas[j]; + pw_log_debug("%p: clear buffer %d mem", + stream, b->id); + unmap_data(impl, d); + } + } + } + impl->n_buffers = 0; + if (impl->direction == SPA_DIRECTION_INPUT) { + struct buffer *b; + + while ((b = queue_pop(impl, &impl->dequeued))) { + if (b->busy) + ATOMIC_DEC(b->busy->count); + } + } else + clear_queue(impl, &impl->dequeued); + clear_queue(impl, &impl->queued); +} + +static int parse_latency(struct pw_stream *stream, const struct spa_pod *param) +{ + struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this); + struct spa_latency_info info; + int res; + + if (param == NULL) + return 0; + + if ((res = spa_latency_parse(param, &info)) < 0) + return res; + + pw_log_info("stream %p: set %s latency %f-%f %d-%d %"PRIu64"-%"PRIu64, stream, + info.direction == SPA_DIRECTION_INPUT ? "input" : "output", + info.min_quantum, info.max_quantum, + info.min_rate, info.max_rate, + info.min_ns, info.max_ns); + + if (info.direction == impl->direction) + return 0; + + impl->latency = info; + return 0; +} + +static int impl_port_set_param(void *object, + enum spa_direction direction, uint32_t port_id, + uint32_t id, uint32_t flags, + const struct spa_pod *param) +{ + struct stream *impl = object; + struct pw_stream *stream = &impl->this; + int res; + + pw_log_debug("%p: port:%d.%d id:%d (%s) param:%p disconnecting:%d", impl, + direction, port_id, id, + spa_debug_type_find_name(spa_type_param, id), param, + impl->disconnecting); + + if (impl->disconnecting && param != NULL) + return -EIO; + + if (param) + pw_log_pod(SPA_LOG_LEVEL_DEBUG, param); + + if ((res = update_params(impl, id, ¶m, param ? 1 : 0)) < 0) + return res; + + switch (id) { + case SPA_PARAM_Format: + clear_buffers(stream); + break; + case SPA_PARAM_Latency: + parse_latency(stream, param); + break; + default: + break; + } + + pw_stream_emit_param_changed(stream, id, param); + + if (stream->state == PW_STREAM_STATE_ERROR) + return -EIO; + + emit_node_info(impl, false); + emit_port_info(impl, false); + + return 0; +} + +static int impl_port_use_buffers(void *object, + enum spa_direction direction, uint32_t port_id, + uint32_t flags, + struct spa_buffer **buffers, uint32_t n_buffers) +{ + struct stream *impl = object; + struct pw_stream *stream = &impl->this; + uint32_t i, j, impl_flags = impl->flags; + int prot, res; + int size = 0; + + pw_log_debug("%p: port:%d.%d buffers:%u disconnecting:%d", impl, + direction, port_id, n_buffers, impl->disconnecting); + + if (impl->disconnecting && n_buffers > 0) + return -EIO; + + prot = PROT_READ | (direction == SPA_DIRECTION_OUTPUT ? PROT_WRITE : 0); + + clear_buffers(stream); + + if (n_buffers > MAX_BUFFERS) + return -ENOSPC; + + for (i = 0; i < n_buffers; i++) { + int buf_size = 0; + struct buffer *b = &impl->buffers[i]; + + b->flags = 0; + b->id = i; + + if (SPA_FLAG_IS_SET(impl_flags, PW_STREAM_FLAG_MAP_BUFFERS)) { + for (j = 0; j < buffers[i]->n_datas; j++) { + struct spa_data *d = &buffers[i]->datas[j]; + if ((mappable_dataTypes & (1<<d->type)) > 0) { + if ((res = map_data(impl, d, prot)) < 0) + return res; + SPA_FLAG_SET(b->flags, BUFFER_FLAG_MAPPED); + } + else if (d->type == SPA_DATA_MemPtr && d->data == NULL) { + pw_log_error("%p: invalid buffer mem", stream); + return -EINVAL; + } + buf_size += d->maxsize; + } + + if (size > 0 && buf_size != size) { + pw_log_error("%p: invalid buffer size %d", stream, buf_size); + return -EINVAL; + } else + size = buf_size; + } + pw_log_debug("%p: got buffer id:%d datas:%d, mapped size %d", stream, i, + buffers[i]->n_datas, size); + } + impl->n_buffers = n_buffers; + + for (i = 0; i < n_buffers; i++) { + struct buffer *b = &impl->buffers[i]; + + b->this.buffer = buffers[i]; + b->busy = spa_buffer_find_meta_data(buffers[i], SPA_META_Busy, sizeof(*b->busy)); + + if (impl->direction == SPA_DIRECTION_OUTPUT) { + pw_log_trace("%p: recycle buffer %d", stream, b->id); + queue_push(impl, &impl->dequeued, b); + } + + SPA_FLAG_SET(b->flags, BUFFER_FLAG_ADDED); + + pw_stream_emit_add_buffer(stream, &b->this); + } + return 0; +} + +static int impl_port_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id) +{ + struct stream *d = object; + pw_log_trace("%p: recycle buffer %d", d, buffer_id); + if (buffer_id < d->n_buffers) + queue_push(d, &d->queued, &d->buffers[buffer_id]); + return 0; +} + +static int impl_node_process_input(void *object) +{ + struct stream *impl = object; + struct pw_stream *stream = &impl->this; + struct spa_io_buffers *io = impl->io; + struct buffer *b; + + if (io == NULL) + return -EIO; + + pw_log_trace_fp("%p: process in status:%d id:%d ticks:%"PRIu64" delay:%"PRIi64, + stream, io->status, io->buffer_id, impl->time.ticks, impl->time.delay); + + if (io->status == SPA_STATUS_HAVE_DATA && + (b = get_buffer(stream, io->buffer_id)) != NULL) { + /* push new buffer */ + pw_log_trace_fp("%p: push %d %p", stream, b->id, io); + if (queue_push(impl, &impl->dequeued, b) == 0) { + copy_position(impl, impl->dequeued.incount); + if (b->busy) + ATOMIC_INC(b->busy->count); + call_process(impl); + } + } + if (io->status != SPA_STATUS_NEED_DATA || io->buffer_id == SPA_ID_INVALID) { + /* pop buffer to recycle */ + if ((b = queue_pop(impl, &impl->queued))) { + pw_log_trace_fp("%p: recycle buffer %d", stream, b->id); + io->buffer_id = b->id; + } else { + pw_log_trace_fp("%p: no buffers to recycle", stream); + io->buffer_id = SPA_ID_INVALID; + } + io->status = SPA_STATUS_NEED_DATA; + } + if (impl->driving && impl->using_trigger) + call_trigger_done(impl); + + return SPA_STATUS_NEED_DATA | SPA_STATUS_HAVE_DATA; +} + +static int impl_node_process_output(void *object) +{ + struct stream *impl = object; + struct pw_stream *stream = &impl->this; + struct spa_io_buffers *io = impl->io; + struct buffer *b; + int res; + bool ask_more; + + if (io == NULL) + return -EIO; + +again: + pw_log_trace_fp("%p: process out status:%d id:%d", stream, + io->status, io->buffer_id); + + ask_more = false; + if ((res = io->status) != SPA_STATUS_HAVE_DATA) { + /* recycle old buffer */ + if ((b = get_buffer(stream, io->buffer_id)) != NULL) { + pw_log_trace_fp("%p: recycle buffer %d", stream, b->id); + queue_push(impl, &impl->dequeued, b); + } + + /* pop new buffer */ + if ((b = queue_pop(impl, &impl->queued)) != NULL) { + impl->drained = false; + io->buffer_id = b->id; + res = io->status = SPA_STATUS_HAVE_DATA; + pw_log_trace_fp("%p: pop %d %p", stream, b->id, io); + /* we have a buffer, if we are not rt and don't follow + * any rate matching and there are no more + * buffers queued and there is a buffer to dequeue, ask for + * more buffers so that we have one in the next round. + * If we are using rate matching we need to wait until the + * rate matching node (audioconvert) has been scheduled to + * update the values. */ + ask_more = !impl->process_rt && impl->rate_match == NULL && + queue_is_empty(impl, &impl->queued) && + !queue_is_empty(impl, &impl->dequeued); + } else if (impl->draining || impl->drained) { + impl->draining = true; + impl->drained = true; + io->buffer_id = SPA_ID_INVALID; + res = io->status = SPA_STATUS_DRAINED; + pw_log_trace_fp("%p: draining", stream); + } else { + io->buffer_id = SPA_ID_INVALID; + res = io->status = SPA_STATUS_NEED_DATA; + pw_log_trace_fp("%p: no more buffers %p", stream, io); + ask_more = true; + } + } else { + ask_more = !impl->process_rt && + queue_is_empty(impl, &impl->queued) && + !queue_is_empty(impl, &impl->dequeued); + } + + copy_position(impl, impl->queued.outcount); + + if (!impl->draining && !impl->driving) { + /* we're not draining, not a driver check if we need to get + * more buffers */ + if (ask_more) { + call_process(impl); + /* realtime, we can try again now if there is something. + * non-realtime, we will have to try in the next round */ + if (impl->process_rt && + (impl->draining || !queue_is_empty(impl, &impl->queued))) + goto again; + } + } + + pw_log_trace_fp("%p: res %d", stream, res); + + if (impl->driving && impl->using_trigger && res != SPA_STATUS_HAVE_DATA) + call_trigger_done(impl); + + return res; +} + +static const struct spa_node_methods impl_node = { + SPA_VERSION_NODE_METHODS, + .add_listener = impl_add_listener, + .set_callbacks = impl_set_callbacks, + .enum_params = impl_enum_params, + .set_param = impl_set_param, + .set_io = impl_set_io, + .send_command = impl_send_command, + .port_set_io = impl_port_set_io, + .port_enum_params = impl_port_enum_params, + .port_set_param = impl_port_set_param, + .port_use_buffers = impl_port_use_buffers, + .port_reuse_buffer = impl_port_reuse_buffer, +}; + +static void proxy_removed(void *_data) +{ + struct pw_stream *stream = _data; + pw_log_debug("%p: removed", stream); + spa_hook_remove(&stream->proxy_listener); + stream->node_id = SPA_ID_INVALID; + stream_set_state(stream, PW_STREAM_STATE_UNCONNECTED, NULL); +} + +static void proxy_destroy(void *_data) +{ + struct pw_stream *stream = _data; + pw_log_debug("%p: destroy", stream); + proxy_removed(_data); +} + +static void proxy_error(void *_data, int seq, int res, const char *message) +{ + struct pw_stream *stream = _data; + /* we just emit the state change here to inform the application. + * If this is supposed to be a permanent error, the app should + * do a pw_stream_set_error() */ + pw_stream_emit_state_changed(stream, stream->state, + PW_STREAM_STATE_ERROR, message); +} + +static void proxy_bound(void *data, uint32_t global_id) +{ + struct pw_stream *stream = data; + stream->node_id = global_id; + stream_set_state(stream, PW_STREAM_STATE_PAUSED, NULL); +} + +static const struct pw_proxy_events proxy_events = { + PW_VERSION_PROXY_EVENTS, + .removed = proxy_removed, + .destroy = proxy_destroy, + .error = proxy_error, + .bound = proxy_bound, +}; + +static struct control *find_control(struct pw_stream *stream, uint32_t id) +{ + struct control *c; + spa_list_for_each(c, &stream->controls, link) { + if (c->id == id) + return c; + } + return NULL; +} + +static int node_event_param(void *object, int seq, + uint32_t id, uint32_t index, uint32_t next, + struct spa_pod *param) +{ + struct pw_stream *stream = object; + + switch (id) { + case SPA_PARAM_PropInfo: + { + struct control *c; + const struct spa_pod *type, *pod; + uint32_t iid, choice, n_vals, container = SPA_ID_INVALID; + float *vals, bool_range[3] = { 1.0f, 0.0f, 1.0f }, dbl[3]; + + if (spa_pod_parse_object(param, + SPA_TYPE_OBJECT_PropInfo, NULL, + SPA_PROP_INFO_id, SPA_POD_Id(&iid)) < 0) + return -EINVAL; + + c = find_control(stream, iid); + if (c != NULL) + return 0; + + c = calloc(1, sizeof(*c) + SPA_POD_SIZE(param)); + c->info = SPA_PTROFF(c, sizeof(*c), struct spa_pod); + memcpy(c->info, param, SPA_POD_SIZE(param)); + c->control.n_values = 0; + c->control.max_values = 0; + c->control.values = c->values; + + if (spa_pod_parse_object(c->info, + SPA_TYPE_OBJECT_PropInfo, NULL, + SPA_PROP_INFO_description, SPA_POD_OPT_String(&c->control.name), + SPA_PROP_INFO_type, SPA_POD_PodChoice(&type), + SPA_PROP_INFO_container, SPA_POD_OPT_Id(&container)) < 0) { + free(c); + return -EINVAL; + } + + pod = spa_pod_get_values(type, &n_vals, &choice); + + c->type = SPA_POD_TYPE(pod); + if (spa_pod_is_float(pod)) + vals = SPA_POD_BODY(pod); + else if (spa_pod_is_double(pod)) { + double *v = SPA_POD_BODY(pod); + dbl[0] = v[0]; + if (n_vals > 1) + dbl[1] = v[1]; + if (n_vals > 2) + dbl[2] = v[2]; + vals = dbl; + } + else if (spa_pod_is_bool(pod) && n_vals > 0) { + choice = SPA_CHOICE_Range; + vals = bool_range; + vals[0] = SPA_POD_VALUE(struct spa_pod_bool, pod); + n_vals = 3; + } + else { + free(c); + return -ENOTSUP; + } + + c->container = container != SPA_ID_INVALID ? container : c->type; + + switch (choice) { + case SPA_CHOICE_None: + if (n_vals < 1) { + free(c); + return -EINVAL; + } + c->control.n_values = 1; + c->control.max_values = 1; + c->control.values[0] = c->control.def = c->control.min = c->control.max = vals[0]; + break; + case SPA_CHOICE_Range: + if (n_vals < 3) { + free(c); + return -EINVAL; + } + c->control.n_values = 1; + c->control.max_values = 1; + c->control.values[0] = vals[0]; + c->control.def = vals[0]; + c->control.min = vals[1]; + c->control.max = vals[2]; + break; + default: + free(c); + return -ENOTSUP; + } + + c->id = iid; + spa_list_append(&stream->controls, &c->link); + pw_log_debug("%p: add control %d (%s) container:%d (def:%f min:%f max:%f)", + stream, c->id, c->control.name, c->container, + c->control.def, c->control.min, c->control.max); + break; + } + case SPA_PARAM_Props: + { + struct spa_pod_prop *prop; + struct spa_pod_object *obj = (struct spa_pod_object *) param; + float value_f; + double value_d; + bool value_b; + float *values; + uint32_t i, n_values; + + SPA_POD_OBJECT_FOREACH(obj, prop) { + struct control *c; + + c = find_control(stream, prop->key); + if (c == NULL) + continue; + + switch (c->container) { + case SPA_TYPE_Float: + if (spa_pod_get_float(&prop->value, &value_f) < 0) + continue; + n_values = 1; + values = &value_f; + break; + case SPA_TYPE_Double: + if (spa_pod_get_double(&prop->value, &value_d) < 0) + continue; + n_values = 1; + value_f = value_d; + values = &value_f; + break; + case SPA_TYPE_Bool: + if (spa_pod_get_bool(&prop->value, &value_b) < 0) + continue; + value_f = value_b ? 1.0f : 0.0f; + n_values = 1; + values = &value_f; + break; + case SPA_TYPE_Array: + if ((values = spa_pod_get_array(&prop->value, &n_values)) == NULL || + !spa_pod_is_float(SPA_POD_ARRAY_CHILD(&prop->value))) + continue; + break; + default: + continue; + } + + if (c->emitted && c->control.n_values == n_values && + memcmp(c->control.values, values, sizeof(float) * n_values) == 0) + continue; + + memcpy(c->control.values, values, sizeof(float) * n_values); + c->control.n_values = n_values; + c->emitted = true; + + pw_log_debug("%p: control %d (%s) changed %d:", stream, + prop->key, c->control.name, n_values); + for (i = 0; i < n_values; i++) + pw_log_debug("%p: value %d %f", stream, i, values[i]); + + pw_stream_emit_control_info(stream, prop->key, &c->control); + } + break; + } + default: + break; + } + return 0; +} + +static void node_event_info(void *data, const struct pw_node_info *info) +{ + struct pw_stream *stream = data; + struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this); + uint32_t i; + + if (info->change_mask & PW_NODE_CHANGE_MASK_PARAMS) { + for (i = 0; i < info->n_params; i++) { + switch (info->params[i].id) { + case SPA_PARAM_PropInfo: + case SPA_PARAM_Props: + pw_impl_node_for_each_param(impl->node, + 0, info->params[i].id, + 0, UINT32_MAX, + NULL, + node_event_param, + stream); + break; + default: + break; + } + } + } +} + +static const struct pw_impl_node_events node_events = { + PW_VERSION_IMPL_NODE_EVENTS, + .info_changed = node_event_info, +}; + +static void on_core_error(void *data, uint32_t id, int seq, int res, const char *message) +{ + struct pw_stream *stream = data; + + pw_log_debug("%p: error id:%u seq:%d res:%d (%s): %s", stream, + id, seq, res, spa_strerror(res), message); + + if (id == PW_ID_CORE && res == -EPIPE) { + stream_set_state(stream, PW_STREAM_STATE_UNCONNECTED, message); + } +} + +static const struct pw_core_events core_events = { + PW_VERSION_CORE_EVENTS, + .error = on_core_error, +}; + +static void context_drained(void *data, struct pw_impl_node *node) +{ + struct stream *impl = data; + if (impl->node != node) + return; + if (impl->draining && impl->drained) { + impl->draining = false; + if (impl->io != NULL) + impl->io->status = SPA_STATUS_NEED_DATA; + call_drained(impl); + } +} + +static const struct pw_context_driver_events context_events = { + PW_VERSION_CONTEXT_DRIVER_EVENTS, + .drained = context_drained, +}; + +struct match { + struct pw_stream *stream; + int count; +}; +#define MATCH_INIT(s) ((struct match){ .stream = (s) }) + +static int execute_match(void *data, const char *location, const char *action, + const char *val, size_t len) +{ + struct match *match = data; + struct pw_stream *this = match->stream; + if (spa_streq(action, "update-props")) + match->count += pw_properties_update_string(this->properties, val, len); + return 1; +} + +static struct stream * +stream_new(struct pw_context *context, const char *name, + struct pw_properties *props, const struct pw_properties *extra) +{ + struct stream *impl; + struct pw_stream *this; + const char *str; + struct match match; + int res; + + impl = calloc(1, sizeof(struct stream)); + if (impl == NULL) { + res = -errno; + goto error_cleanup; + } + impl->port_props = pw_properties_new(NULL, NULL); + if (impl->port_props == NULL) { + res = -errno; + goto error_properties; + } + + this = &impl->this; + pw_log_debug("%p: new \"%s\"", impl, name); + + if (props == NULL) { + props = pw_properties_new(PW_KEY_MEDIA_NAME, name, NULL); + } else if (pw_properties_get(props, PW_KEY_MEDIA_NAME) == NULL) { + pw_properties_set(props, PW_KEY_MEDIA_NAME, name); + } + if (props == NULL) { + res = -errno; + goto error_properties; + } + spa_hook_list_init(&impl->hooks); + this->properties = props; + + pw_context_conf_update_props(context, "stream.properties", props); + + match = MATCH_INIT(this); + pw_context_conf_section_match_rules(context, "stream.rules", + &this->properties->dict, execute_match, &match); + + if ((str = getenv("PIPEWIRE_PROPS")) != NULL) + pw_properties_update_string(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(props, PW_KEY_NODE_RATE, + "1/%u", q.denom); + pw_properties_setf(props, PW_KEY_NODE_LATENCY, + "%u/%u", q.num, q.denom); + } + } + if ((str = getenv("PIPEWIRE_LATENCY")) != NULL) + pw_properties_set(props, PW_KEY_NODE_LATENCY, str); + if ((str = getenv("PIPEWIRE_RATE")) != NULL) + pw_properties_set(props, PW_KEY_NODE_RATE, str); + + if (pw_properties_get(props, PW_KEY_STREAM_IS_LIVE) == NULL) + pw_properties_set(props, PW_KEY_STREAM_IS_LIVE, "true"); + if (pw_properties_get(props, PW_KEY_NODE_NAME) == NULL && extra) { + str = pw_properties_get(extra, PW_KEY_APP_NAME); + if (str == NULL) + str = pw_properties_get(extra, PW_KEY_APP_PROCESS_BINARY); + if (str == NULL) + str = name; + pw_properties_set(props, PW_KEY_NODE_NAME, str); + } + + this->name = name ? strdup(name) : NULL; + this->node_id = SPA_ID_INVALID; + + spa_ringbuffer_init(&impl->dequeued.ring); + spa_ringbuffer_init(&impl->queued.ring); + spa_list_init(&impl->param_list); + + spa_hook_list_init(&this->listener_list); + spa_list_init(&this->controls); + + this->state = PW_STREAM_STATE_UNCONNECTED; + + impl->context = context; + impl->allow_mlock = context->settings.mem_allow_mlock; + impl->warn_mlock = context->settings.mem_warn_mlock; + + spa_hook_list_append(&impl->context->driver_listener_list, + &impl->context_listener, + &context_events, impl); + return impl; + +error_properties: + pw_properties_free(impl->port_props); + free(impl); +error_cleanup: + pw_properties_free(props); + errno = -res; + return NULL; +} + +SPA_EXPORT +struct pw_stream * pw_stream_new(struct pw_core *core, const char *name, + struct pw_properties *props) +{ + struct stream *impl; + struct pw_stream *this; + struct pw_context *context = core->context; + + impl = stream_new(context, name, props, core->properties); + if (impl == NULL) + return NULL; + + this = &impl->this; + this->core = core; + spa_list_append(&core->stream_list, &this->link); + pw_core_add_listener(core, + &this->core_listener, &core_events, this); + + return this; +} + +SPA_EXPORT +struct pw_stream * +pw_stream_new_simple(struct pw_loop *loop, + const char *name, + struct pw_properties *props, + const struct pw_stream_events *events, + void *data) +{ + struct pw_stream *this; + struct stream *impl; + struct pw_context *context; + int res; + + if (props == NULL) + props = pw_properties_new(NULL, NULL); + if (props == NULL) + return NULL; + + context = pw_context_new(loop, NULL, 0); + if (context == NULL) { + res = -errno; + goto error_cleanup; + } + + impl = stream_new(context, name, props, NULL); + if (impl == NULL) { + res = -errno; + props = NULL; + goto error_cleanup; + } + + this = &impl->this; + impl->data.context = context; + pw_stream_add_listener(this, &impl->data.stream_listener, events, data); + + return this; + +error_cleanup: + if (context) + pw_context_destroy(context); + pw_properties_free(props); + errno = -res; + return NULL; +} + +SPA_EXPORT +const char *pw_stream_state_as_string(enum pw_stream_state state) +{ + switch (state) { + case PW_STREAM_STATE_ERROR: + return "error"; + case PW_STREAM_STATE_UNCONNECTED: + return "unconnected"; + case PW_STREAM_STATE_CONNECTING: + return "connecting"; + case PW_STREAM_STATE_PAUSED: + return "paused"; + case PW_STREAM_STATE_STREAMING: + return "streaming"; + } + return "invalid-state"; +} + +SPA_EXPORT +void pw_stream_destroy(struct pw_stream *stream) +{ + struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this); + struct control *c; + + pw_log_debug("%p: destroy", stream); + + pw_stream_emit_destroy(stream); + + if (!impl->disconnecting) + pw_stream_disconnect(stream); + + if (stream->core) { + spa_hook_remove(&stream->core_listener); + spa_list_remove(&stream->link); + stream->core = NULL; + } + + clear_params(impl, SPA_ID_INVALID); + + pw_log_debug("%p: free", stream); + free(stream->error); + + pw_properties_free(stream->properties); + + free(stream->name); + + spa_list_consume(c, &stream->controls, link) { + spa_list_remove(&c->link); + free(c); + } + + spa_hook_list_clean(&impl->hooks); + spa_hook_list_clean(&stream->listener_list); + + spa_hook_remove(&impl->context_listener); + + if (impl->data.context) + pw_context_destroy(impl->data.context); + + pw_properties_free(impl->port_props); + free(impl); +} + +static void hook_removed(struct spa_hook *hook) +{ + struct stream *impl = hook->priv; + spa_zero(impl->rt_callbacks); + hook->priv = NULL; + hook->removed = NULL; +} + +SPA_EXPORT +void pw_stream_add_listener(struct pw_stream *stream, + struct spa_hook *listener, + const struct pw_stream_events *events, + void *data) +{ + struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this); + spa_hook_list_append(&stream->listener_list, listener, events, data); + + if (events->process && impl->rt_callbacks.funcs == NULL) { + impl->rt_callbacks = SPA_CALLBACKS_INIT(events, data); + listener->removed = hook_removed; + listener->priv = impl; + } +} + +SPA_EXPORT +enum pw_stream_state pw_stream_get_state(struct pw_stream *stream, const char **error) +{ + if (error) + *error = stream->error; + return stream->state; +} + +SPA_EXPORT +const char *pw_stream_get_name(struct pw_stream *stream) +{ + return stream->name; +} + +SPA_EXPORT +const struct pw_properties *pw_stream_get_properties(struct pw_stream *stream) +{ + return stream->properties; +} + +SPA_EXPORT +int pw_stream_update_properties(struct pw_stream *stream, const struct spa_dict *dict) +{ + struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this); + int changed, res = 0; + struct match match; + + changed = pw_properties_update(stream->properties, dict); + if (!changed) + return 0; + + match = MATCH_INIT(stream); + pw_context_conf_section_match_rules(impl->context, "stream.rules", + &stream->properties->dict, execute_match, &match); + + if (impl->node) + res = pw_impl_node_update_properties(impl->node, + match.count == 0 ? + dict : + &stream->properties->dict); + + return res; +} + +SPA_EXPORT +struct pw_core *pw_stream_get_core(struct pw_stream *stream) +{ + return stream->core; +} + +static void add_params(struct stream *impl) +{ + uint8_t buffer[4096]; + struct spa_pod_builder b; + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + + add_param(impl, SPA_PARAM_IO, PARAM_FLAG_LOCKED, + 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)))); + + add_param(impl, SPA_PARAM_Meta, PARAM_FLAG_LOCKED, + spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, + SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Busy), + SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_busy)))); +} + +static int find_format(struct stream *impl, enum pw_direction direction, + uint32_t *media_type, uint32_t *media_subtype) +{ + uint32_t state = 0; + uint8_t buffer[4096]; + struct spa_pod_builder b; + int res; + struct spa_pod *format; + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + if (spa_node_port_enum_params_sync(&impl->impl_node, + impl->direction, 0, + SPA_PARAM_EnumFormat, &state, + NULL, &format, &b) != 1) { + pw_log_warn("%p: no format given", impl); + return 0; + } + + if ((res = spa_format_parse(format, media_type, media_subtype)) < 0) + return res; + + pw_log_debug("%p: %s/%s", impl, + spa_debug_type_find_name(spa_type_media_type, *media_type), + spa_debug_type_find_name(spa_type_media_subtype, *media_subtype)); + return 0; +} + +static const char *get_media_class(struct stream *impl) +{ + switch (impl->media_type) { + case SPA_MEDIA_TYPE_audio: + return "Audio"; + case SPA_MEDIA_TYPE_video: + return "Video"; + case SPA_MEDIA_TYPE_application: + switch(impl->media_subtype) { + case SPA_MEDIA_SUBTYPE_control: + return "Midi"; + } + return "Data"; + case SPA_MEDIA_TYPE_stream: + switch(impl->media_subtype) { + case SPA_MEDIA_SUBTYPE_midi: + return "Midi"; + } + return "Data"; + default: + return "Unknown"; + } +} + +SPA_EXPORT +int +pw_stream_connect(struct pw_stream *stream, + enum pw_direction direction, + uint32_t target_id, + enum pw_stream_flags flags, + const struct spa_pod **params, + uint32_t n_params) +{ + struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this); + struct pw_impl_factory *factory; + struct pw_properties *props = NULL; + const char *str; + uint32_t i; + int res; + + pw_log_debug("%p: connect target:%d", stream, target_id); + impl->direction = + direction == PW_DIRECTION_INPUT ? SPA_DIRECTION_INPUT : SPA_DIRECTION_OUTPUT; + impl->flags = flags; + impl->node_methods = impl_node; + + if (impl->direction == SPA_DIRECTION_INPUT) + impl->node_methods.process = impl_node_process_input; + else + impl->node_methods.process = impl_node_process_output; + + impl->process_rt = SPA_FLAG_IS_SET(flags, PW_STREAM_FLAG_RT_PROCESS); + + impl->impl_node.iface = SPA_INTERFACE_INIT( + SPA_TYPE_INTERFACE_Node, + SPA_VERSION_NODE, + &impl->node_methods, impl); + + impl->change_mask_all = + SPA_NODE_CHANGE_MASK_FLAGS | + SPA_NODE_CHANGE_MASK_PROPS | + SPA_NODE_CHANGE_MASK_PARAMS; + + impl->info = SPA_NODE_INFO_INIT(); + if (impl->direction == SPA_DIRECTION_INPUT) { + impl->info.max_input_ports = 1; + impl->info.max_output_ports = 0; + } else { + impl->info.max_input_ports = 0; + impl->info.max_output_ports = 1; + } + /* we're always RT safe, if the stream was marked RT_PROCESS, + * the callback must be RT safe */ + impl->info.flags = SPA_NODE_FLAG_RT; + /* if the callback was not marked RT_PROCESS, we will offload + * the process callback in the main thread and we are ASYNC */ + if (!impl->process_rt) + impl->info.flags |= SPA_NODE_FLAG_ASYNC; + impl->info.props = &stream->properties->dict; + impl->params[NODE_PropInfo] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, 0); + impl->params[NODE_Props] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_WRITE); + impl->params[NODE_EnumFormat] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, 0); + impl->params[NODE_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); + impl->info.params = impl->params; + impl->info.n_params = N_NODE_PARAMS; + impl->info.change_mask = impl->change_mask_all; + + impl->port_change_mask_all = + SPA_PORT_CHANGE_MASK_FLAGS | + SPA_PORT_CHANGE_MASK_PROPS | + SPA_PORT_CHANGE_MASK_PARAMS; + + impl->port_info = SPA_PORT_INFO_INIT(); + impl->port_info.change_mask = impl->port_change_mask_all; + impl->port_info.flags = 0; + if (SPA_FLAG_IS_SET(flags, PW_STREAM_FLAG_ALLOC_BUFFERS)) + impl->port_info.flags |= SPA_PORT_FLAG_CAN_ALLOC_BUFFERS; + impl->port_params[PORT_EnumFormat] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, 0); + impl->port_params[PORT_Meta] = SPA_PARAM_INFO(SPA_PARAM_Meta, 0); + impl->port_params[PORT_IO] = SPA_PARAM_INFO(SPA_PARAM_IO, 0); + impl->port_params[PORT_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); + impl->port_params[PORT_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); + impl->port_params[PORT_Latency] = SPA_PARAM_INFO(SPA_PARAM_Latency, SPA_PARAM_INFO_WRITE); + impl->port_info.props = &impl->port_props->dict; + impl->port_info.params = impl->port_params; + impl->port_info.n_params = N_PORT_PARAMS; + + clear_params(impl, SPA_ID_INVALID); + for (i = 0; i < n_params; i++) + add_param(impl, SPA_ID_INVALID, 0, params[i]); + + add_params(impl); + + if ((res = find_format(impl, direction, &impl->media_type, &impl->media_subtype)) < 0) + return res; + + impl->disconnecting = false; + stream_set_state(stream, PW_STREAM_STATE_CONNECTING, NULL); + + if ((str = getenv("PIPEWIRE_NODE")) != NULL) + pw_properties_set(stream->properties, PW_KEY_TARGET_OBJECT, str); + else if (target_id != PW_ID_ANY) + /* XXX this is deprecated but still used by the portal and its apps */ + pw_properties_setf(stream->properties, PW_KEY_NODE_TARGET, "%d", target_id); + + if ((flags & PW_STREAM_FLAG_AUTOCONNECT) && + pw_properties_get(stream->properties, PW_KEY_NODE_AUTOCONNECT) == NULL) { + str = getenv("PIPEWIRE_AUTOCONNECT"); + pw_properties_set(stream->properties, PW_KEY_NODE_AUTOCONNECT, str ? str : "true"); + } + if (flags & PW_STREAM_FLAG_DRIVER) + pw_properties_set(stream->properties, PW_KEY_NODE_DRIVER, "true"); + if ((pw_properties_get(stream->properties, PW_KEY_NODE_WANT_DRIVER) == NULL)) + pw_properties_set(stream->properties, PW_KEY_NODE_WANT_DRIVER, "true"); + + if (flags & PW_STREAM_FLAG_EXCLUSIVE) + pw_properties_set(stream->properties, PW_KEY_NODE_EXCLUSIVE, "true"); + if (flags & PW_STREAM_FLAG_DONT_RECONNECT) + pw_properties_set(stream->properties, PW_KEY_NODE_DONT_RECONNECT, "true"); + if (flags & PW_STREAM_FLAG_TRIGGER) { + pw_properties_set(stream->properties, PW_KEY_NODE_TRIGGER, "true"); + impl->trigger = true; + } + + if ((str = pw_properties_get(stream->properties, "mem.warn-mlock")) != NULL) + impl->warn_mlock = pw_properties_parse_bool(str); + + if ((pw_properties_get(stream->properties, PW_KEY_MEDIA_CLASS) == NULL)) { + const char *media_type = pw_properties_get(stream->properties, PW_KEY_MEDIA_TYPE); + pw_properties_setf(stream->properties, PW_KEY_MEDIA_CLASS, "Stream/%s/%s", + direction == PW_DIRECTION_INPUT ? "Input" : "Output", + media_type ? media_type : get_media_class(impl)); + } + + if ((str = pw_properties_get(stream->properties, PW_KEY_FORMAT_DSP)) != NULL) + pw_properties_set(impl->port_props, PW_KEY_FORMAT_DSP, str); + else if (impl->media_type == SPA_MEDIA_TYPE_application && + impl->media_subtype == SPA_MEDIA_SUBTYPE_control) + pw_properties_set(impl->port_props, PW_KEY_FORMAT_DSP, "8 bit raw midi"); + + impl->port_info.props = &impl->port_props->dict; + + if (stream->core == NULL) { + stream->core = pw_context_connect(impl->context, + pw_properties_copy(stream->properties), 0); + if (stream->core == NULL) { + res = -errno; + goto error_connect; + } + spa_list_append(&stream->core->stream_list, &stream->link); + pw_core_add_listener(stream->core, + &stream->core_listener, &core_events, stream); + impl->disconnect_core = true; + } + + pw_log_debug("%p: creating node", stream); + props = pw_properties_copy(stream->properties); + if (props == NULL) { + res = -errno; + goto error_node; + } + + if ((str = pw_properties_get(props, PW_KEY_STREAM_MONITOR)) && + pw_properties_parse_bool(str)) { + pw_properties_set(props, "resample.peaks", "true"); + pw_properties_set(props, "channelmix.normalize", "true"); + } + + if (impl->media_type == SPA_MEDIA_TYPE_audio) { + factory = pw_context_find_factory(impl->context, "adapter"); + if (factory == NULL) { + pw_log_error("%p: no adapter factory found", stream); + res = -ENOENT; + goto error_node; + } + pw_properties_setf(props, "adapt.follower.spa-node", "pointer:%p", + &impl->impl_node); + pw_properties_set(props, "object.register", "false"); + impl->node = pw_impl_factory_create_object(factory, + NULL, + PW_TYPE_INTERFACE_Node, + PW_VERSION_NODE, + props, + 0); + props = NULL; + if (impl->node == NULL) { + res = -errno; + goto error_node; + } + } else { + impl->node = pw_context_create_node(impl->context, props, 0); + props = NULL; + if (impl->node == NULL) { + res = -errno; + goto error_node; + } + pw_impl_node_set_implementation(impl->node, &impl->impl_node); + } + pw_impl_node_set_active(impl->node, + !SPA_FLAG_IS_SET(impl->flags, PW_STREAM_FLAG_INACTIVE)); + + pw_log_debug("%p: export node %p", stream, impl->node); + stream->proxy = pw_core_export(stream->core, + PW_TYPE_INTERFACE_Node, NULL, impl->node, 0); + if (stream->proxy == NULL) { + res = -errno; + goto error_proxy; + } + + pw_proxy_add_listener(stream->proxy, &stream->proxy_listener, &proxy_events, stream); + + pw_impl_node_add_listener(impl->node, &stream->node_listener, &node_events, stream); + + return 0; + +error_connect: + pw_log_error("%p: can't connect: %s", stream, spa_strerror(res)); + goto exit_cleanup; +error_node: + pw_log_error("%p: can't make node: %s", stream, spa_strerror(res)); + goto exit_cleanup; +error_proxy: + pw_log_error("%p: can't make proxy: %s", stream, spa_strerror(res)); + goto exit_cleanup; + +exit_cleanup: + pw_properties_free(props); + return res; +} + +SPA_EXPORT +uint32_t pw_stream_get_node_id(struct pw_stream *stream) +{ + return stream->node_id; +} + +SPA_EXPORT +int pw_stream_disconnect(struct pw_stream *stream) +{ + struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this); + + pw_log_debug("%p: disconnect", stream); + + if (impl->disconnecting) + return 0; + + impl->disconnecting = true; + + if (impl->node) + pw_impl_node_set_active(impl->node, false); + + if (stream->proxy) { + pw_proxy_destroy(stream->proxy); + stream->proxy = NULL; + } + + if (impl->node) { + pw_impl_node_destroy(impl->node); + impl->node = NULL; + } + if (impl->disconnect_core) { + impl->disconnect_core = false; + spa_hook_remove(&stream->core_listener); + spa_list_remove(&stream->link); + pw_core_disconnect(stream->core); + stream->core = NULL; + } + return 0; +} + +SPA_EXPORT +int pw_stream_set_error(struct pw_stream *stream, + int res, const char *error, ...) +{ + if (res < 0) { + va_list args; + char *value; + int r; + + va_start(args, error); + r = vasprintf(&value, error, args); + va_end(args); + if (r < 0) + return -errno; + + if (stream->proxy) + pw_proxy_error(stream->proxy, res, value); + stream_set_state(stream, PW_STREAM_STATE_ERROR, value); + + free(value); + } + return res; +} + +SPA_EXPORT +int pw_stream_update_params(struct pw_stream *stream, + const struct spa_pod **params, + uint32_t n_params) +{ + struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this); + int res; + + pw_log_debug("%p: update params", stream); + if ((res = update_params(impl, SPA_ID_INVALID, params, n_params)) < 0) + return res; + + emit_node_info(impl, false); + emit_port_info(impl, false); + + return res; +} + +SPA_EXPORT +int pw_stream_set_control(struct pw_stream *stream, uint32_t id, uint32_t n_values, float *values, ...) +{ + struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this); + va_list varargs; + char buf[1024]; + struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buf, sizeof(buf)); + struct spa_pod_frame f[1]; + struct spa_pod *pod; + struct control *c; + + if (impl->node == NULL) + return -EIO; + + va_start(varargs, values); + + spa_pod_builder_push_object(&b, &f[0], SPA_TYPE_OBJECT_Props, SPA_PARAM_Props); + while (1) { + pw_log_debug("%p: set control %d %d %f", stream, id, n_values, values[0]); + + if ((c = find_control(stream, id))) { + spa_pod_builder_prop(&b, id, 0); + switch (c->container) { + case SPA_TYPE_Float: + spa_pod_builder_float(&b, values[0]); + break; + case SPA_TYPE_Double: + spa_pod_builder_double(&b, values[0]); + break; + case SPA_TYPE_Bool: + spa_pod_builder_bool(&b, values[0] < 0.5 ? false : true); + break; + case SPA_TYPE_Array: + spa_pod_builder_array(&b, + sizeof(float), SPA_TYPE_Float, + n_values, values); + break; + default: + spa_pod_builder_none(&b); + break; + } + } else { + pw_log_warn("%p: unknown control with id %d", stream, id); + } + if ((id = va_arg(varargs, uint32_t)) == 0) + break; + n_values = va_arg(varargs, uint32_t); + values = va_arg(varargs, float *); + } + pod = spa_pod_builder_pop(&b, &f[0]); + + va_end(varargs); + + impl->in_set_control++; + pw_impl_node_set_param(impl->node, SPA_PARAM_Props, 0, pod); + impl->in_set_control--; + + return 0; +} + +SPA_EXPORT +const struct pw_stream_control *pw_stream_get_control(struct pw_stream *stream, uint32_t id) +{ + struct control *c; + + if (id == 0) + return NULL; + + if ((c = find_control(stream, id))) + return &c->control; + + return NULL; +} + +SPA_EXPORT +int pw_stream_set_active(struct pw_stream *stream, bool active) +{ + struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this); + pw_log_debug("%p: active:%d", stream, active); + if (impl->node) + pw_impl_node_set_active(impl->node, active); + + if (!active || impl->drained) + impl->drained = impl->draining = false; + return 0; +} + +struct old_time { + int64_t now; + struct spa_fraction rate; + uint64_t ticks; + int64_t delay; + uint64_t queued; +}; + +SPA_EXPORT +int pw_stream_get_time(struct pw_stream *stream, struct pw_time *time) +{ + return pw_stream_get_time_n(stream, time, sizeof(struct old_time)); +} + +SPA_EXPORT +int pw_stream_get_time_n(struct pw_stream *stream, struct pw_time *time, size_t size) +{ + struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this); + uintptr_t seq1, seq2; + uint32_t buffered, quantum, index; + + do { + seq1 = SEQ_READ(impl->seq); + memcpy(time, &impl->time, SPA_MIN(size, sizeof(struct pw_time))); + buffered = impl->rate_queued; + quantum = impl->quantum; + seq2 = SEQ_READ(impl->seq); + } while (!SEQ_READ_SUCCESS(seq1, seq2)); + + if (impl->direction == SPA_DIRECTION_INPUT) + time->queued = (int64_t)(time->queued - impl->dequeued.outcount); + else + time->queued = (int64_t)(impl->queued.incount - time->queued); + + time->delay += ((impl->latency.min_quantum + impl->latency.max_quantum) / 2) * quantum; + time->delay += (impl->latency.min_rate + impl->latency.max_rate) / 2; + time->delay += ((impl->latency.min_ns + impl->latency.max_ns) / 2) * time->rate.denom / SPA_NSEC_PER_SEC; + + if (size >= offsetof(struct pw_time, queued_buffers)) + time->buffered = buffered; + if (size >= offsetof(struct pw_time, avail_buffers)) + time->queued_buffers = spa_ringbuffer_get_read_index(&impl->queued.ring, &index); + if (size >= sizeof(struct pw_time)) + time->avail_buffers = spa_ringbuffer_get_read_index(&impl->dequeued.ring, &index); + + pw_log_trace_fp("%p: %"PRIi64" %"PRIi64" %"PRIu64" %d/%d %"PRIu64" %" + PRIu64" %"PRIu64" %"PRIu64" %"PRIu64, stream, + time->now, time->delay, time->ticks, + time->rate.num, time->rate.denom, time->queued, + impl->dequeued.outcount, impl->dequeued.incount, + impl->queued.outcount, impl->queued.incount); + return 0; +} + +static int +do_trigger_deprecated(struct spa_loop *loop, + bool async, uint32_t seq, const void *data, size_t size, void *user_data) +{ + struct stream *impl = user_data; + int res = impl->node_methods.process(impl); + return spa_node_call_ready(&impl->callbacks, res); +} + +SPA_EXPORT +struct pw_buffer *pw_stream_dequeue_buffer(struct pw_stream *stream) +{ + struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this); + struct buffer *b; + int res; + + if ((b = queue_pop(impl, &impl->dequeued)) == NULL) { + res = -errno; + pw_log_trace_fp("%p: no more buffers: %m", stream); + errno = -res; + return NULL; + } + pw_log_trace_fp("%p: dequeue buffer %d size:%"PRIu64, stream, b->id, b->this.size); + + if (b->busy && impl->direction == SPA_DIRECTION_OUTPUT) { + if (ATOMIC_INC(b->busy->count) > 1) { + ATOMIC_DEC(b->busy->count); + queue_push(impl, &impl->dequeued, b); + pw_log_trace_fp("%p: buffer busy", stream); + errno = EBUSY; + return NULL; + } + } + return &b->this; +} + +SPA_EXPORT +int pw_stream_queue_buffer(struct pw_stream *stream, struct pw_buffer *buffer) +{ + struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this); + struct buffer *b = SPA_CONTAINER_OF(buffer, struct buffer, this); + int res; + + if (b->busy) + ATOMIC_DEC(b->busy->count); + + pw_log_trace_fp("%p: queue buffer %d", stream, b->id); + if ((res = queue_push(impl, &impl->queued, b)) < 0) + return res; + + if (impl->direction == SPA_DIRECTION_OUTPUT && + impl->driving && !impl->using_trigger) { + pw_log_debug("deprecated: use pw_stream_trigger_process() to drive the stream."); + res = pw_loop_invoke(impl->context->data_loop, + do_trigger_deprecated, 1, NULL, 0, false, impl); + } + return res; +} + +static int +do_flush(struct spa_loop *loop, + bool async, uint32_t seq, const void *data, size_t size, void *user_data) +{ + struct stream *impl = user_data; + struct buffer *b; + + pw_log_trace_fp("%p: flush", impl); + do { + b = queue_pop(impl, &impl->queued); + if (b != NULL) + queue_push(impl, &impl->dequeued, b); + } + while (b); + + impl->queued.outcount = impl->dequeued.incount = + impl->dequeued.outcount = impl->queued.incount = 0; + + return 0; +} +static int +do_drain(struct spa_loop *loop, + bool async, uint32_t seq, const void *data, size_t size, void *user_data) +{ + struct stream *impl = user_data; + pw_log_trace_fp("%p", impl); + impl->draining = true; + impl->drained = false; + return 0; +} + +SPA_EXPORT +int pw_stream_flush(struct pw_stream *stream, bool drain) +{ + struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this); + pw_loop_invoke(impl->context->data_loop, + drain ? do_drain : do_flush, 1, NULL, 0, true, impl); + if (!drain && impl->node != NULL) + spa_node_send_command(impl->node->node, + &SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_Flush)); + return 0; +} + +SPA_EXPORT +bool pw_stream_is_driving(struct pw_stream *stream) +{ + struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this); + return impl->driving; +} + +static int +do_trigger_process(struct spa_loop *loop, + bool async, uint32_t seq, const void *data, size_t size, void *user_data) +{ + struct stream *impl = user_data; + int res; + if (impl->direction == SPA_DIRECTION_OUTPUT) { + if (impl->process_rt) + call_process(impl); + res = impl->node_methods.process(impl); + } else { + res = SPA_STATUS_NEED_DATA; + } + return spa_node_call_ready(&impl->callbacks, res); +} + +static int trigger_request_process(struct stream *impl) +{ + uint8_t buffer[1024]; + struct spa_pod_builder b = { 0 }; + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + spa_node_emit_event(&impl->hooks, + spa_pod_builder_add_object(&b, + SPA_TYPE_EVENT_Node, SPA_NODE_EVENT_RequestProcess)); + return 0; +} + +SPA_EXPORT +int pw_stream_trigger_process(struct pw_stream *stream) +{ + struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this); + int res = 0; + + pw_log_trace_fp("%p", impl); + + /* flag to check for old or new behaviour */ + impl->using_trigger = true; + + if (!impl->driving && !impl->trigger) { + res = trigger_request_process(impl); + } else { + if (!impl->process_rt) + call_process(impl); + + res = pw_loop_invoke(impl->context->data_loop, + do_trigger_process, 1, NULL, 0, false, impl); + } + return res; +} diff --git a/src/pipewire/stream.h b/src/pipewire/stream.h new file mode 100644 index 0000000..bf08c44 --- /dev/null +++ b/src/pipewire/stream.h @@ -0,0 +1,529 @@ +/* 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. + */ + +#ifndef PIPEWIRE_STREAM_H +#define PIPEWIRE_STREAM_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** \page page_streams Streams + * + * \section sec_overview Overview + * + * \ref pw_stream "Streams" are used to exchange data with the + * PipeWire server. A stream is a wrapper around a proxy for a pw_client_node + * with an adapter. This means the stream will automatically do conversion + * to the type required by the server. + * + * Streams can be used to: + * + * \li Consume a stream from PipeWire. This is a PW_DIRECTION_INPUT stream. + * \li Produce a stream to PipeWire. This is a PW_DIRECTION_OUTPUT stream + * + * You can connect the stream port to a specific server port or let PipeWire + * choose a port for you. + * + * For more complicated nodes such as filters or ports with multiple + * inputs and/or outputs you will need to use the pw_filter or make + * a pw_node yourself and export it with \ref pw_core_export. + * + * Streams can also be used to: + * + * \li Implement a Sink in PipeWire. This is a PW_DIRECTION_INPUT stream. + * \li Implement a Source in PipeWire. This is a PW_DIRECTION_OUTPUT stream + * + * In this case, the PW_KEY_MEDIA_CLASS property needs to be set to + * "Audio/Sink" or "Audio/Source" respectively. + * + * \section sec_create Create + * + * Make a new stream with \ref pw_stream_new(). You will need to specify + * a name for the stream and extra properties. The basic set of properties + * each stream must provide is filled in automatically. + * + * Once the stream is created, the state_changed event should be used to + * track the state of the stream. + * + * \section sec_connect Connect + * + * The stream is initially unconnected. To connect the stream, use + * \ref pw_stream_connect(). Pass the desired direction as an argument. + * + * The direction is: + + * \li PW_DIRECTION_INPUT for a stream that *consumes* data. This can be a + * stream that captures from a Source or a when the stream is used to + * implement a Sink. + * + * \li PW_DIRECTION_OUTPUT for a stream that *produces* data. This can be a + * stream that plays to a Sink or when the stream is used to implement + * a Source. + * + * \subsection ssec_stream_target Stream target + * + * To make the newly connected stream automatically connect to an existing + * PipeWire node, use the \ref PW_STREAM_FLAG_AUTOCONNECT and set the + * PW_KEY_OBJECT_SERIAL or the PW_KEY_NODE_NAME value of the target node + * in the PW_KEY_TARGET_OBJECT property before connecting. + * + * \subsection ssec_stream_formats Stream formats + * + * An array of possible formats that this stream can consume or provide + * must be specified. + * + * \section sec_format Format negotiation + * + * After connecting the stream, the server will want to configure some + * parameters on the stream. You will be notified of these changes + * with the param_changed event. + * + * When a format param change is emitted, the client should now prepare + * itself to deal with the format and complete the negotiation procedure + * with a call to \ref pw_stream_update_params(). + * + * As arguments to \ref pw_stream_update_params() an array of spa_param + * structures must be given. They contain parameters such as buffer size, + * number of buffers, required metadata and other parameters for the + * media buffers. + * + * \section sec_buffers Buffer negotiation + * + * After completing the format negotiation, PipeWire will allocate and + * notify the stream of the buffers that will be used to exchange data + * between client and server. + * + * With the add_buffer event, a stream will be notified of a new buffer + * that can be used for data transport. You can attach user_data to these + * buffers. The buffers can only be used with the stream that emitted + * the add_buffer event. + * + * After the buffers are negotiated, the stream will transition to the + * \ref PW_STREAM_STATE_PAUSED state. + * + * \section sec_streaming Streaming + * + * From the \ref PW_STREAM_STATE_PAUSED state, the stream can be set to + * the \ref PW_STREAM_STATE_STREAMING state by the PipeWire server when + * data transport is started. + * + * Depending on how the stream was connected it will need to Produce or + * Consume data for/from PipeWire as explained in the following + * subsections. + * + * \subsection ssec_consume Consume data + * + * The process event is emitted for each new buffer that can be + * consumed. + * + * \ref pw_stream_dequeue_buffer() should be used to get the data and + * metadata of the buffer. + * + * The buffer is owned by the stream and stays alive until the + * remove_buffer event is emitted or the stream is destroyed. + * + * When the buffer has been processed, call \ref pw_stream_queue_buffer() + * to let PipeWire reuse the buffer. + * + * \subsection ssec_produce Produce data + * + * \ref pw_stream_dequeue_buffer() gives an empty buffer that can be filled. + * + * The buffer is owned by the stream and stays alive until the + * remove_buffer event is emitted or the stream is destroyed. + * + * Filled buffers should be queued with \ref pw_stream_queue_buffer(). + * + * The process event is emitted when PipeWire has emptied a buffer that + * can now be refilled. + * + * \section sec_stream_disconnect Disconnect + * + * Use \ref pw_stream_disconnect() to disconnect a stream after use. + * + * \section sec_stream_configuration Configuration + * + * \subsection ssec_config_properties Stream Properties + * + * \subsection ssec_config_rules Stream Rules + * + * \section sec_stream_environment Environment Variables + * + */ +/** \defgroup pw_stream Stream + * + * \brief PipeWire stream objects + * + * The stream object provides a convenient way to send and + * receive data streams from/to PipeWire. + * + * See also \ref page_streams and \ref api_pw_core + */ + +/** + * \addtogroup pw_stream + * \{ + */ +struct pw_stream; + +#include <spa/buffer/buffer.h> +#include <spa/param/param.h> +#include <spa/pod/command.h> + +/** \enum pw_stream_state The state of a stream */ +enum pw_stream_state { + PW_STREAM_STATE_ERROR = -1, /**< the stream is in error */ + PW_STREAM_STATE_UNCONNECTED = 0, /**< unconnected */ + PW_STREAM_STATE_CONNECTING = 1, /**< connection is in progress */ + PW_STREAM_STATE_PAUSED = 2, /**< paused */ + PW_STREAM_STATE_STREAMING = 3 /**< streaming */ +}; + +/** a buffer structure obtained from pw_stream_dequeue_buffer(). The size of this + * structure can grow as more field are added in the future */ +struct pw_buffer { + struct spa_buffer *buffer; /**< the spa buffer */ + void *user_data; /**< user data attached to the buffer */ + uint64_t size; /**< This field is set by the user and the sum of + * all queued buffer is returned in the time info. + * For audio, it is advised to use the number of + * samples in the buffer for this field. */ + uint64_t requested; /**< For playback streams, this field contains the + * suggested amount of data to provide. For audio + * streams this will be the amount of samples + * required by the resampler. This field is 0 + * when no suggestion is provided. Since 0.3.49 */ +}; + +struct pw_stream_control { + const char *name; /**< name of the control */ + uint32_t flags; /**< extra flags (unused) */ + float def; /**< default value */ + float min; /**< min value */ + float max; /**< max value */ + float *values; /**< array of values */ + uint32_t n_values; /**< number of values in array */ + uint32_t max_values; /**< max values that can be set on this control */ +}; + +/** A time structure. + * + * Use pw_stream_get_time_n() to get an updated time snapshot of the stream. + * The time snapshot can give information about the time in the driver of the + * graph, the delay to the edge of the graph and the internal queuing in the + * stream. + * + * pw_time.ticks gives a monotonic increasing counter of the time in the graph + * driver. I can be used to generate a timetime to schedule samples as well + * as detect discontinuities in the timeline caused by xruns. + * + * pw_time.delay is expressed as pw_time.rate, the time domain of the graph. This + * value, and pw_time.ticks, were captured at pw_time.now and can be extrapolated + * to the current time like this: + * + * struct timespec ts; + * clock_gettime(CLOCK_MONOTONIC, &ts); + * int64_t diff = SPA_TIMESPEC_TO_NSEC(&ts) - pw_time.now; + * int64_t elapsed = (pw_time.rate.denom * diff) / (pw_time.rate.num * SPA_NSEC_PER_SEC); + * + * pw_time.delay contains the total delay that a signal will travel through the + * graph. This includes the delay caused by filters in the graph as well as delays + * caused by the hardware. The delay is usually quite stable and should only change when + * the topology, quantum or samplerate of the graph changes. + * + * pw_time.queued and pw_time.buffered is expressed in the time domain of the stream, + * or the format that is used for the buffers of this stream. + * + * pw_time.queued is the sum of all the pw_buffer.size fields of the buffers that are + * currently queued in the stream but not yet processed. The application can choose + * the units of this value, for example, time, samples or bytes (below expressed + * as app.rate). + * + * pw_time.buffered is format dependent, for audio/raw it contains the number of samples + * that are buffered inside the resampler/converter. + * + * The total delay of data in a stream is the sum of the queued and buffered data + * (not yet processed data) and the delay to the edge of the graph, usually a + * playback or capture device. + * + * For an audio playback stream, if you were to queue a buffer, the total delay + * in milliseconds for the first sample in the newly queued buffer to be played + * by the hardware can be calculated as: + * + * (pw_time.buffered * 1000 / stream.samplerate) + + * (pw_time.queued * 1000 / app.rate) + + * ((pw_time.delay - elapsed) * 1000 * pw_time.rate.num / pw_time.rate.denom) + * + * The current extrapolated time (in ms) in the source or sink can be calculated as: + * + * (pw_time.ticks + elapsed) * 1000 * pw_time.rate.num / pw_time.rate.denom + * + * + * stream time domain graph time domain + * /-----------------------\/-----------------------------\ + * + * queue +-+ +-+ +-----------+ +--------+ + * ----> | | | |->| converter | -> graph -> | kernel | -> speaker + * <---- +-+ +-+ +-----------+ +--------+ + * dequeue buffers \-------------------/\--------/ + * graph internal + * latency latency + * \--------/\-------------/\-----------------------------/ + * queued buffered delay + */ +struct pw_time { + int64_t now; /**< the monotonic time in nanoseconds. This is the time + * when this time report was updated. It is usually + * updated every graph cycle. You can use the current + * monotonic time to calculate the elapsed time between + * this report and the current state and calculate + * updated ticks and delay values. */ + struct spa_fraction rate; /**< the rate of \a ticks and delay. This is usually + * expressed in 1/<samplerate>. */ + uint64_t ticks; /**< the ticks at \a now. This is the current time that + * the remote end is reading/writing. This is monotonicaly + * increasing. */ + int64_t delay; /**< delay to device. This is the time it will take for + * the next output sample of the stream to be presented by + * the playback device or the time a sample traveled + * from the capture device. This delay includes the + * delay introduced by all filters on the path between + * the stream and the device. The delay is normally + * constant in a graph and can change when the topology + * of the graph or the quantum changes. This delay does + * not include the delay caused by queued buffers. */ + uint64_t queued; /**< data queued in the stream, this is the sum + * of the size fields in the pw_buffer that are + * currently queued */ + uint64_t buffered; /**< for audio/raw streams, this contains the extra + * number of samples buffered in the resampler. + * Since 0.3.50. */ + uint32_t queued_buffers; /**< The number of buffers that are queued. Since 0.3.50 */ + uint32_t avail_buffers; /**< The number of buffers that can be dequeued. Since 0.3.50 */ +}; + +#include <pipewire/port.h> + +/** Events for a stream. These events are always called from the mainloop + * unless explicitly documented otherwise. */ +struct pw_stream_events { +#define PW_VERSION_STREAM_EVENTS 2 + uint32_t version; + + void (*destroy) (void *data); + /** when the stream state changes */ + void (*state_changed) (void *data, enum pw_stream_state old, + enum pw_stream_state state, const char *error); + + /** Notify information about a control. */ + void (*control_info) (void *data, uint32_t id, const struct pw_stream_control *control); + + /** when io changed on the stream. */ + void (*io_changed) (void *data, uint32_t id, void *area, uint32_t size); + /** when a parameter changed */ + void (*param_changed) (void *data, uint32_t id, const struct spa_pod *param); + + /** when a new buffer was created for this stream */ + void (*add_buffer) (void *data, struct pw_buffer *buffer); + /** when a buffer was destroyed for this stream */ + void (*remove_buffer) (void *data, struct pw_buffer *buffer); + + /** when a buffer can be queued (for playback streams) or + * dequeued (for capture streams). This is normally called from the + * mainloop but can also be called directly from the realtime data + * thread if the user is prepared to deal with this. */ + void (*process) (void *data); + + /** The stream is drained */ + void (*drained) (void *data); + + /** A command notify, Since 0.3.39:1 */ + void (*command) (void *data, const struct spa_command *command); + + /** a trigger_process completed. Since version 0.3.40:2 */ + void (*trigger_done) (void *data); +}; + +/** Convert a stream state to a readable string */ +const char * pw_stream_state_as_string(enum pw_stream_state state); + +/** \enum pw_stream_flags Extra flags that can be used in \ref pw_stream_connect() */ +enum pw_stream_flags { + PW_STREAM_FLAG_NONE = 0, /**< no flags */ + PW_STREAM_FLAG_AUTOCONNECT = (1 << 0), /**< try to automatically connect + * this stream */ + PW_STREAM_FLAG_INACTIVE = (1 << 1), /**< start the stream inactive, + * pw_stream_set_active() needs to be + * called explicitly */ + PW_STREAM_FLAG_MAP_BUFFERS = (1 << 2), /**< mmap the buffers except DmaBuf */ + PW_STREAM_FLAG_DRIVER = (1 << 3), /**< be a driver */ + PW_STREAM_FLAG_RT_PROCESS = (1 << 4), /**< call process from the realtime + * thread. You MUST use RT safe functions + * in the process callback. */ + PW_STREAM_FLAG_NO_CONVERT = (1 << 5), /**< don't convert format */ + PW_STREAM_FLAG_EXCLUSIVE = (1 << 6), /**< require exclusive access to the + * device */ + PW_STREAM_FLAG_DONT_RECONNECT = (1 << 7), /**< don't try to reconnect this stream + * when the sink/source is removed */ + PW_STREAM_FLAG_ALLOC_BUFFERS = (1 << 8), /**< the application will allocate buffer + * memory. In the add_buffer event, the + * data of the buffer should be set */ + PW_STREAM_FLAG_TRIGGER = (1 << 9), /**< the output stream will not be scheduled + * automatically but _trigger_process() + * needs to be called. This can be used + * when the output of the stream depends + * on input from other streams. */ +}; + +/** Create a new unconneced \ref pw_stream + * \return a newly allocated \ref pw_stream */ +struct pw_stream * +pw_stream_new(struct pw_core *core, /**< a \ref pw_core */ + const char *name, /**< a stream media name */ + struct pw_properties *props /**< stream properties, ownership is taken */); + +struct pw_stream * +pw_stream_new_simple(struct pw_loop *loop, /**< a \ref pw_loop to use */ + const char *name, /**< a stream media name */ + struct pw_properties *props,/**< stream properties, ownership is taken */ + const struct pw_stream_events *events, /**< stream events */ + void *data /**< data passed to events */); + +/** Destroy a stream */ +void pw_stream_destroy(struct pw_stream *stream); + +void pw_stream_add_listener(struct pw_stream *stream, + struct spa_hook *listener, + const struct pw_stream_events *events, + void *data); + +enum pw_stream_state pw_stream_get_state(struct pw_stream *stream, const char **error); + +const char *pw_stream_get_name(struct pw_stream *stream); + +struct pw_core *pw_stream_get_core(struct pw_stream *stream); + +const struct pw_properties *pw_stream_get_properties(struct pw_stream *stream); + +int pw_stream_update_properties(struct pw_stream *stream, const struct spa_dict *dict); + +/** Connect a stream for input or output on \a port_path. + * \return 0 on success < 0 on error. + * + * You should connect to the process event and use pw_stream_dequeue_buffer() + * to get the latest metadata and data. */ +int +pw_stream_connect(struct pw_stream *stream, /**< a \ref pw_stream */ + enum pw_direction direction, /**< the stream direction */ + uint32_t target_id, /**< should have the value PW_ID_ANY. + * To select a specific target + * node, specify the + * PW_KEY_OBJECT_SERIAL or the + * PW_KEY_NODE_NAME value of the target + * node in the PW_KEY_TARGET_OBJECT + * property of the stream. + * Specifying target nodes by + * their id is deprecated. + */ + enum pw_stream_flags flags, /**< stream flags */ + const struct spa_pod **params, /**< an array with params. The params + * should ideally contain supported + * formats. */ + uint32_t n_params /**< number of items in \a params */); + +/** Get the node ID of the stream. + * \return node ID. */ +uint32_t +pw_stream_get_node_id(struct pw_stream *stream); + +/** Disconnect \a stream */ +int pw_stream_disconnect(struct pw_stream *stream); + +/** Set the stream in error state */ +int pw_stream_set_error(struct pw_stream *stream, /**< a \ref pw_stream */ + int res, /**< a result code */ + const char *error, /**< an error message */ + ...) SPA_PRINTF_FUNC(3, 4); + +/** Complete the negotiation process with result code \a res + * + * This function should be called after notification of the format. + + * When \a res indicates success, \a params contain the parameters for the + * allocation state. */ +int +pw_stream_update_params(struct pw_stream *stream, /**< a \ref pw_stream */ + const struct spa_pod **params, /**< an array of params. The params should + * ideally contain parameters for doing + * buffer allocation. */ + uint32_t n_params /**< number of elements in \a params */); + +/** Get control values */ +const struct pw_stream_control *pw_stream_get_control(struct pw_stream *stream, uint32_t id); + +/** Set control values */ +int pw_stream_set_control(struct pw_stream *stream, uint32_t id, uint32_t n_values, float *values, ...); + +/** Query the time on the stream */ +int pw_stream_get_time_n(struct pw_stream *stream, struct pw_time *time, size_t size); + +/** Query the time on the stream, deprecated since 0.3.50, + * use pw_stream_get_time_n() to get the fields added since 0.3.50. */ +SPA_DEPRECATED +int pw_stream_get_time(struct pw_stream *stream, struct pw_time *time); + +/** Get a buffer that can be filled for playback streams or consumed + * for capture streams. */ +struct pw_buffer *pw_stream_dequeue_buffer(struct pw_stream *stream); + +/** Submit a buffer for playback or recycle a buffer for capture. */ +int pw_stream_queue_buffer(struct pw_stream *stream, struct pw_buffer *buffer); + +/** Activate or deactivate the stream */ +int pw_stream_set_active(struct pw_stream *stream, bool active); + +/** Flush a stream. When \a drain is true, the drained callback will + * be called when all data is played or recorded */ +int pw_stream_flush(struct pw_stream *stream, bool drain); + +/** Check if the stream is driving. The stream needs to have the + * PW_STREAM_FLAG_DRIVER set. When the stream is driving, + * pw_stream_trigger_process() needs to be called when data is + * available (output) or needed (input). Since 0.3.34 */ +bool pw_stream_is_driving(struct pw_stream *stream); + +/** Trigger a push/pull on the stream. One iteration of the graph will + * scheduled and process() will be called. Since 0.3.34 */ +int pw_stream_trigger_process(struct pw_stream *stream); + +/** + * \} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* PIPEWIRE_STREAM_H */ diff --git a/src/pipewire/thread-loop.c b/src/pipewire/thread-loop.c new file mode 100644 index 0000000..2fdd50e --- /dev/null +++ b/src/pipewire/thread-loop.c @@ -0,0 +1,469 @@ +/* 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 <pthread.h> +#include <errno.h> +#include <sys/time.h> + +#include <spa/support/thread.h> +#include <spa/utils/result.h> + +#include "log.h" +#include "thread.h" +#include "thread-loop.h" + +PW_LOG_TOPIC_EXTERN(log_thread_loop); +#define PW_LOG_TOPIC_DEFAULT log_thread_loop + + +#define pw_thread_loop_events_emit(o,m,v,...) spa_hook_list_call(&o->listener_list, struct pw_thread_loop_events, m, v, ##__VA_ARGS__) +#define pw_thread_loop_events_destroy(o) pw_thread_loop_events_emit(o, destroy, 0) + +/** \cond */ +struct pw_thread_loop { + struct pw_loop *loop; + char name[16]; + + struct spa_hook_list listener_list; + + pthread_mutex_t lock; + pthread_cond_t cond; + pthread_cond_t accept_cond; + + pthread_t thread; + + struct spa_hook hook; + + struct spa_source *event; + + int n_waiting; + int n_waiting_for_accept; + unsigned int created:1; + unsigned int running:1; +}; +/** \endcond */ + +static void before(void *data) +{ + struct pw_thread_loop *this = data; + pthread_mutex_unlock(&this->lock); +} + +static void after(void *data) +{ + struct pw_thread_loop *this = data; + pthread_mutex_lock(&this->lock); +} + +static const struct spa_loop_control_hooks impl_hooks = { + SPA_VERSION_LOOP_CONTROL_HOOKS, + before, + after, +}; + +static void do_stop(void *data, uint64_t count) +{ + struct pw_thread_loop *this = data; + pw_log_debug("stopping"); + this->running = false; +} + +#define CHECK(expression,label) \ +do { \ + if ((errno = (expression)) != 0) { \ + res = -errno; \ + pw_log_error(#expression ": %s", strerror(errno)); \ + goto label; \ + } \ +} while(false); + +static struct pw_thread_loop *loop_new(struct pw_loop *loop, + const char *name, + const struct spa_dict *props) +{ + struct pw_thread_loop *this; + pthread_mutexattr_t attr; + pthread_condattr_t cattr; + int res; + + this = calloc(1, sizeof(struct pw_thread_loop)); + if (this == NULL) + return NULL; + + pw_log_debug("%p: new name:%s", this, name); + + if (loop == NULL) { + loop = pw_loop_new(props); + this->created = true; + } + if (loop == NULL) { + res = -errno; + goto clean_this; + } + this->loop = loop; + snprintf(this->name, sizeof(this->name), "%s", name ? name : "pw-thread-loop"); + + spa_hook_list_init(&this->listener_list); + + CHECK(pthread_mutexattr_init(&attr), clean_this); + CHECK(pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE), clean_this); + CHECK(pthread_mutex_init(&this->lock, &attr), clean_this); + + CHECK(pthread_condattr_init(&cattr), clean_lock); + CHECK(pthread_condattr_setclock(&cattr, CLOCK_REALTIME), clean_lock); + + CHECK(pthread_cond_init(&this->cond, &cattr), clean_lock); + CHECK(pthread_cond_init(&this->accept_cond, &cattr), clean_cond); + + if ((this->event = pw_loop_add_event(this->loop, do_stop, this)) == NULL) { + res = -errno; + goto clean_acceptcond; + } + + pw_loop_add_hook(loop, &this->hook, &impl_hooks, this); + + return this; + +clean_acceptcond: + pthread_cond_destroy(&this->accept_cond); +clean_cond: + pthread_cond_destroy(&this->cond); +clean_lock: + pthread_mutex_destroy(&this->lock); +clean_this: + if (this->created && this->loop) + pw_loop_destroy(this->loop); + free(this); + errno = -res; + return NULL; +} + +/** Create a new \ref pw_thread_loop + * + * \param name the name of the thread or NULL + * \param props a dict of properties for the thread loop + * \return a newly allocated \ref pw_thread_loop + * + * Make a new \ref pw_thread_loop that will run in + * a thread with \a name. + * + * After this function you should probably call pw_thread_loop_start() to + * actually start the thread + * + */ +SPA_EXPORT +struct pw_thread_loop *pw_thread_loop_new(const char *name, + const struct spa_dict *props) +{ + return loop_new(NULL, name, props); +} + +/** Create a new \ref pw_thread_loop + * + * \param loop the loop to wrap + * \param name the name of the thread or NULL + * \param props a dict of properties for the thread loop + * \return a newly allocated \ref pw_thread_loop + * + * Make a new \ref pw_thread_loop that will run \a loop in + * a thread with \a name. + * + * After this function you should probably call pw_thread_loop_start() to + * actually start the thread + * + */ +SPA_EXPORT +struct pw_thread_loop *pw_thread_loop_new_full(struct pw_loop *loop, + const char *name, const struct spa_dict *props) +{ + return loop_new(loop, name, props); +} + +/** Destroy a threaded loop */ +SPA_EXPORT +void pw_thread_loop_destroy(struct pw_thread_loop *loop) +{ + pw_thread_loop_events_destroy(loop); + + pw_thread_loop_stop(loop); + + spa_hook_remove(&loop->hook); + + spa_hook_list_clean(&loop->listener_list); + + pw_loop_destroy_source(loop->loop, loop->event); + + if (loop->created) + pw_loop_destroy(loop->loop); + + pthread_cond_destroy(&loop->accept_cond); + pthread_cond_destroy(&loop->cond); + pthread_mutex_destroy(&loop->lock); + + free(loop); +} + +SPA_EXPORT +void pw_thread_loop_add_listener(struct pw_thread_loop *loop, + struct spa_hook *listener, + const struct pw_thread_loop_events *events, + void *data) +{ + spa_hook_list_append(&loop->listener_list, listener, events, data); +} + +SPA_EXPORT +struct pw_loop * +pw_thread_loop_get_loop(struct pw_thread_loop *loop) +{ + return loop->loop; +} + +static void *do_loop(void *user_data) +{ + struct pw_thread_loop *this = user_data; + int res; + + pthread_mutex_lock(&this->lock); + pw_log_debug("%p: enter thread", this); + pw_loop_enter(this->loop); + + while (this->running) { + if ((res = pw_loop_iterate(this->loop, -1)) < 0) { + if (res == -EINTR) + continue; + pw_log_warn("%p: iterate error %d (%s)", + this, res, spa_strerror(res)); + } + } + pw_log_debug("%p: leave thread", this); + pw_loop_leave(this->loop); + pthread_mutex_unlock(&this->lock); + + return NULL; +} + +/** Start the thread to handle \a loop + * + * \param loop a \ref pw_thread_loop + * \return 0 on success + * + */ +SPA_EXPORT +int pw_thread_loop_start(struct pw_thread_loop *loop) +{ + int err; + + if (!loop->running) { + struct spa_thread *thr; + struct spa_dict_item items[1]; + + loop->running = true; + + items[0] = SPA_DICT_ITEM_INIT(SPA_KEY_THREAD_NAME, loop->name); + thr = pw_thread_utils_create(&SPA_DICT_INIT_ARRAY(items), do_loop, loop); + if (thr == NULL) + goto error; + + loop->thread = (pthread_t)thr; + } + return 0; + +error: + err = errno; + pw_log_warn("%p: can't create thread: %s", loop, + strerror(err)); + loop->running = false; + return -err; +} + +/** Quit the loop and stop its thread + * + * \param loop a \ref pw_thread_loop + * + */ +SPA_EXPORT +void pw_thread_loop_stop(struct pw_thread_loop *loop) +{ + pw_log_debug("%p stopping %d", loop, loop->running); + if (loop->running) { + pw_log_debug("%p signal", loop); + pw_loop_signal_event(loop->loop, loop->event); + pw_log_debug("%p join", loop); + pthread_join(loop->thread, NULL); + pw_log_debug("%p joined", loop); + loop->running = false; + } + pw_log_debug("%p stopped", loop); +} + +/** Lock the mutex associated with \a loop + * + * \param loop a \ref pw_thread_loop + * + */ +SPA_EXPORT +void pw_thread_loop_lock(struct pw_thread_loop *loop) +{ + pthread_mutex_lock(&loop->lock); + pw_log_trace("%p", loop); +} + +/** Unlock the mutex associated with \a loop + * + * \param loop a \ref pw_thread_loop + * + */ +SPA_EXPORT +void pw_thread_loop_unlock(struct pw_thread_loop *loop) +{ + pw_log_trace("%p", loop); + pthread_mutex_unlock(&loop->lock); +} + +/** Signal the thread + * + * \param loop a \ref pw_thread_loop to signal + * \param wait_for_accept if we need to wait for accept + * + * Signal the thread of \a loop. If \a wait_for_accept is true, + * this function waits until \ref pw_thread_loop_accept() is called. + * + */ +SPA_EXPORT +void pw_thread_loop_signal(struct pw_thread_loop *loop, bool wait_for_accept) +{ + pw_log_trace("%p, waiting:%d accept:%d", + loop, loop->n_waiting, wait_for_accept); + if (loop->n_waiting > 0) + pthread_cond_broadcast(&loop->cond); + + if (wait_for_accept) { + loop->n_waiting_for_accept++; + + while (loop->n_waiting_for_accept > 0) + pthread_cond_wait(&loop->accept_cond, &loop->lock); + } +} + +/** Wait for the loop thread to call \ref pw_thread_loop_signal() + * + * \param loop a \ref pw_thread_loop to signal + * + */ +SPA_EXPORT +void pw_thread_loop_wait(struct pw_thread_loop *loop) +{ + pw_log_trace("%p, waiting %d", loop, loop->n_waiting); + loop->n_waiting++; + pthread_cond_wait(&loop->cond, &loop->lock); + loop->n_waiting--; + pw_log_trace("%p, waiting done %d", loop, loop->n_waiting); +} + +/** Wait for the loop thread to call \ref pw_thread_loop_signal() + * or time out. + * + * \param loop a \ref pw_thread_loop to signal + * \param wait_max_sec the maximum number of seconds to wait for a \ref pw_thread_loop_signal() + * \return 0 on success or ETIMEDOUT on timeout or a negative errno value. + * + */ +SPA_EXPORT +int pw_thread_loop_timed_wait(struct pw_thread_loop *loop, int wait_max_sec) +{ + struct timespec timeout; + int ret = 0; + if ((ret = pw_thread_loop_get_time(loop, + &timeout, wait_max_sec * SPA_NSEC_PER_SEC)) < 0) + return ret; + ret = pw_thread_loop_timed_wait_full(loop, &timeout); + return ret == -ETIMEDOUT ? ETIMEDOUT : ret; +} + +/** Get the current time of the loop + timeout. This can be used in + * pw_thread_loop_timed_wait_full(). + * + * \param loop a \ref pw_thread_loop + * \param abstime the result struct timesspec + * \param timeout the time in nanoseconds to add to \a tp + * \return 0 on success or a negative errno value on error. + * + */ +SPA_EXPORT +int pw_thread_loop_get_time(struct pw_thread_loop *loop, struct timespec *abstime, int64_t timeout) +{ + if (clock_gettime(CLOCK_REALTIME, abstime) < 0) + return -errno; + + abstime->tv_sec += timeout / SPA_NSEC_PER_SEC; + abstime->tv_nsec += timeout % SPA_NSEC_PER_SEC; + if (abstime->tv_nsec >= SPA_NSEC_PER_SEC) { + abstime->tv_sec++; + abstime->tv_nsec -= SPA_NSEC_PER_SEC; + } + return 0; +} + +/** Wait for the loop thread to call \ref pw_thread_loop_signal() + * or time out. + * + * \param loop a \ref pw_thread_loop to signal + * \param abstime the absolute time to wait for a \ref pw_thread_loop_signal() + * \return 0 on success or -ETIMEDOUT on timeout or a negative error value + * + */ +SPA_EXPORT +int pw_thread_loop_timed_wait_full(struct pw_thread_loop *loop, struct timespec *abstime) +{ + int ret; + loop->n_waiting++; + ret = pthread_cond_timedwait(&loop->cond, &loop->lock, abstime); + loop->n_waiting--; + return -ret; +} + +/** Signal the loop thread waiting for accept with \ref pw_thread_loop_signal() + * + * \param loop a \ref pw_thread_loop to signal + * + */ +SPA_EXPORT +void pw_thread_loop_accept(struct pw_thread_loop *loop) +{ + loop->n_waiting_for_accept--; + pthread_cond_signal(&loop->accept_cond); +} + +/** Check if we are inside the thread of the loop + * + * \param loop a \ref pw_thread_loop to signal + * \return true when called inside the thread of \a loop. + * + */ +SPA_EXPORT +bool pw_thread_loop_in_thread(struct pw_thread_loop *loop) +{ + return loop->running && pthread_self() == loop->thread; +} diff --git a/src/pipewire/thread-loop.h b/src/pipewire/thread-loop.h new file mode 100644 index 0000000..13e8532 --- /dev/null +++ b/src/pipewire/thread-loop.h @@ -0,0 +1,175 @@ +/* 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. + */ + +#ifndef PIPEWIRE_THREAD_LOOP_H +#define PIPEWIRE_THREAD_LOOP_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <pipewire/loop.h> + +/** \page page_thread_loop Thread Loop + * + * \section sec_thread_loop_overview Overview + * + * The threaded loop implementation is a special wrapper around the + * regular \ref pw_loop implementation. + * + * The added feature in the threaded loop is that it spawns a new thread + * that runs the wrapped loop. This allows a synchronous application to use + * the asynchronous API without risking to stall the PipeWire library. + * + * \section sec_thread_loop_create Creation + * + * A \ref pw_thread_loop object is created using pw_thread_loop_new(). + * The \ref pw_loop to wrap must be given as an argument along with the name + * for the thread that will be spawned. + * + * After allocating the object, the thread must be started with + * pw_thread_loop_start() + * + * \section sec_thread_loop_destruction Destruction + * + * When the PipeWire connection has been terminated, the thread must be + * stopped and the resources freed. Stopping the thread is done using + * pw_thread_loop_stop(), which must be called without the lock (see + * below) held. When that function returns, the thread is stopped and the + * \ref pw_thread_loop object can be freed using pw_thread_loop_destroy(). + * + * \section sec_thread_loop_locking Locking + * + * Since the PipeWire API doesn't allow concurrent accesses to objects, + * a locking scheme must be used to guarantee safe usage. The threaded + * loop API provides such a scheme through the functions + * pw_thread_loop_lock() and pw_thread_loop_unlock(). + * + * The lock is recursive, so it's safe to use it multiple times from the same + * thread. Just make sure you call pw_thread_loop_unlock() the same + * number of times you called pw_thread_loop_lock(). + * + * The lock needs to be held whenever you call any PipeWire function that + * uses an object associated with this loop. Make sure you do not hold + * on to the lock more than necessary though, as the threaded loop stops + * while the lock is held. + * + * \section sec_thread_loop_events Events and Callbacks + * + * All events and callbacks are called with the thread lock held. + * + */ +/** \defgroup pw_thread_loop Thread Loop + * + * The threaded loop object runs a \ref pw_loop in a separate thread + * and ensures proper locking is done. + * + * All of the loop callbacks will be executed with the loop + * lock held. + * + * See also \ref page_thread_loop + */ + +/** + * \addtogroup pw_thread_loop + * \{ + */ +struct pw_thread_loop; + +/** Thread loop events */ +struct pw_thread_loop_events { +#define PW_VERSION_THREAD_LOOP_EVENTS 0 + uint32_t version; + + /** the loop is destroyed */ + void (*destroy) (void *data); +}; + +/** Make a new thread loop with the given name and optional properties. */ +struct pw_thread_loop * +pw_thread_loop_new(const char *name, const struct spa_dict *props); + +/** Make a new thread loop with the given loop, name and optional properties. + * When \a loop is NULL, a new loop will be created. */ +struct pw_thread_loop * +pw_thread_loop_new_full(struct pw_loop *loop, const char *name, const struct spa_dict *props); + +/** Destroy a thread loop */ +void pw_thread_loop_destroy(struct pw_thread_loop *loop); + +/** Add an event listener */ +void pw_thread_loop_add_listener(struct pw_thread_loop *loop, + struct spa_hook *listener, + const struct pw_thread_loop_events *events, + void *data); + +/** Get the loop implementation of the thread loop */ +struct pw_loop * pw_thread_loop_get_loop(struct pw_thread_loop *loop); + +/** Start the thread loop */ +int pw_thread_loop_start(struct pw_thread_loop *loop); + +/** Stop the thread loop */ +void pw_thread_loop_stop(struct pw_thread_loop *loop); + +/** Lock the loop. This ensures exclusive ownership of the loop */ +void pw_thread_loop_lock(struct pw_thread_loop *loop); + +/** Unlock the loop */ +void pw_thread_loop_unlock(struct pw_thread_loop *loop); + +/** Release the lock and wait until some thread calls \ref pw_thread_loop_signal */ +void pw_thread_loop_wait(struct pw_thread_loop *loop); + +/** Release the lock and wait a maximum of 'wait_max_sec' seconds + * until some thread calls \ref pw_thread_loop_signal or time out */ +int pw_thread_loop_timed_wait(struct pw_thread_loop *loop, int wait_max_sec); + +/** Get a struct timespec suitable for \ref pw_thread_loop_timed_wait_full. + * Since: 0.3.7 */ +int pw_thread_loop_get_time(struct pw_thread_loop *loop, struct timespec *abstime, int64_t timeout); + +/** Release the lock and wait up to \a abstime until some thread calls + * \ref pw_thread_loop_signal. Use \ref pw_thread_loop_get_time to make a timeout. + * Since: 0.3.7 */ +int pw_thread_loop_timed_wait_full(struct pw_thread_loop *loop, struct timespec *abstime); + +/** Signal all threads waiting with \ref pw_thread_loop_wait */ +void pw_thread_loop_signal(struct pw_thread_loop *loop, bool wait_for_accept); + +/** Signal all threads executing \ref pw_thread_loop_signal with wait_for_accept */ +void pw_thread_loop_accept(struct pw_thread_loop *loop); + +/** Check if inside the thread */ +bool pw_thread_loop_in_thread(struct pw_thread_loop *loop); + +/** + * \} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* PIPEWIRE_THREAD_LOOP_H */ diff --git a/src/pipewire/thread.c b/src/pipewire/thread.c new file mode 100644 index 0000000..02b8b9e --- /dev/null +++ b/src/pipewire/thread.c @@ -0,0 +1,160 @@ +/* PipeWire + * + * Copyright © 2021 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 <stdlib.h> +#include <unistd.h> +#include <sys/types.h> +#include <pthread.h> + +#include <spa/utils/dict.h> +#include <spa/utils/defs.h> +#include <spa/utils/list.h> + +#include <pipewire/log.h> + +#include "thread.h" + +#define CHECK(expression,label) \ +do { \ + if ((errno = (expression)) != 0) { \ + res = -errno; \ + pw_log_error(#expression ": %s", strerror(errno)); \ + goto label; \ + } \ +} while(false); + +SPA_EXPORT +pthread_attr_t *pw_thread_fill_attr(const struct spa_dict *props, pthread_attr_t *attr) +{ + const char *str; + int res; + + if (props == NULL) + return NULL; + + pthread_attr_init(attr); + if ((str = spa_dict_lookup(props, SPA_KEY_THREAD_STACK_SIZE)) != NULL) + CHECK(pthread_attr_setstacksize(attr, atoi(str)), error); + return attr; +error: + errno = -res; + return NULL; +} + +#if defined(__FreeBSD__) || defined(__MidnightBSD__) +#include <sys/param.h> +#if __FreeBSD_version < 1202000 || defined(__MidnightBSD__) +int pthread_setname_np(pthread_t thread, const char *name) +{ + pthread_set_name_np(thread, name); + return 0; +} +#endif +#endif + +static struct spa_thread *impl_create(void *object, + const struct spa_dict *props, + void *(*start)(void*), void *arg) +{ + pthread_t pt; + pthread_attr_t *attr = NULL, attributes; + const char *str; + int err; + + attr = pw_thread_fill_attr(props, &attributes); + + err = pthread_create(&pt, attr, start, arg); + + if (attr) + pthread_attr_destroy(attr); + + if (err != 0) { + errno = err; + return NULL; + } + if (props) { + if ((str = spa_dict_lookup(props, SPA_KEY_THREAD_NAME)) != NULL && + (err = pthread_setname_np(pt, str)) != 0) + pw_log_warn("pthread_setname error: %s", strerror(err)); + } + return (struct spa_thread*)pt; +} + +static int impl_join(void *object, struct spa_thread *thread, void **retval) +{ + pthread_t pt = (pthread_t)thread; + return pthread_join(pt, retval); +} + +static int impl_get_rt_range(void *object, const struct spa_dict *props, + int *min, int *max) +{ + if (min) + *min = sched_get_priority_min(SCHED_OTHER); + if (max) + *max = sched_get_priority_max(SCHED_OTHER); + return 0; +} +static int impl_acquire_rt(void *object, struct spa_thread *thread, int priority) +{ + pw_log_warn("acquire_rt thread:%p prio:%d not implemented", thread, priority); + return -ENOTSUP; +} + +static int impl_drop_rt(void *object, struct spa_thread *thread) +{ + pw_log_warn("drop_rt thread:%p not implemented", thread); + return -ENOTSUP; +} + +static struct { + struct spa_thread_utils utils; + struct spa_thread_utils_methods methods; +} default_impl = { + { { SPA_TYPE_INTERFACE_ThreadUtils, + SPA_VERSION_THREAD_UTILS, + SPA_CALLBACKS_INIT(&default_impl.methods, + &default_impl) } }, + { SPA_VERSION_THREAD_UTILS_METHODS, + .create = impl_create, + .join = impl_join, + .get_rt_range = impl_get_rt_range, + .acquire_rt = impl_acquire_rt, + .drop_rt = impl_drop_rt, + } +}; + +static struct spa_thread_utils *global_impl = &default_impl.utils; + +SPA_EXPORT +void pw_thread_utils_set(struct spa_thread_utils *impl) +{ + pw_log_warn("pw_thread_utils_set is deprecated and does nothing anymore"); +} + +SPA_EXPORT +struct spa_thread_utils *pw_thread_utils_get(void) +{ + return global_impl; +} diff --git a/src/pipewire/thread.h b/src/pipewire/thread.h new file mode 100644 index 0000000..ba84a5d --- /dev/null +++ b/src/pipewire/thread.h @@ -0,0 +1,65 @@ +/* PipeWire + * + * Copyright © 2021 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_THREAD_H +#define PIPEWIRE_THREAD_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <string.h> +#include <errno.h> + +#include <spa/support/thread.h> + +/** \defgroup pw_thread Thread + * + * \brief functions to manipulate threads + */ + +/** + * \addtogroup pw_thread + * \{ + */ + +SPA_DEPRECATED +void pw_thread_utils_set(struct spa_thread_utils *impl); +struct spa_thread_utils *pw_thread_utils_get(void); + +#define pw_thread_utils_create(...) spa_thread_utils_create(pw_thread_utils_get(), ##__VA_ARGS__) +#define pw_thread_utils_join(...) spa_thread_utils_join(pw_thread_utils_get(), ##__VA_ARGS__) +#define pw_thread_utils_get_rt_range(...) spa_thread_utils_get_rt_range(pw_thread_utils_get(), ##__VA_ARGS__) +#define pw_thread_utils_acquire_rt(...) spa_thread_utils_acquire_rt(pw_thread_utils_get(), ##__VA_ARGS__) +#define pw_thread_utils_drop_rt(...) spa_thread_utils_drop_rt(pw_thread_utils_get(), ##__VA_ARGS__) + +/** + * \} + */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* PIPEWIRE_THREAD_H */ diff --git a/src/pipewire/type.h b/src/pipewire/type.h new file mode 100644 index 0000000..3572531 --- /dev/null +++ b/src/pipewire/type.h @@ -0,0 +1,65 @@ +/* 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. + */ + +#ifndef PIPEWIRE_TYPE_H +#define PIPEWIRE_TYPE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <spa/utils/type.h> + +/** \defgroup pw_type Type info + * Type information + */ + +/** + * \addtogroup pw_type + * \{ + */ + +enum { + PW_TYPE_FIRST = SPA_TYPE_VENDOR_PipeWire, +}; + +#define PW_TYPE_INFO_BASE "PipeWire:" + +#define PW_TYPE_INFO_Object PW_TYPE_INFO_BASE "Object" +#define PW_TYPE_INFO_OBJECT_BASE PW_TYPE_INFO_Object ":" + +#define PW_TYPE_INFO_Interface PW_TYPE_INFO_BASE "Interface" +#define PW_TYPE_INFO_INTERFACE_BASE PW_TYPE_INFO_Interface ":" + +const struct spa_type_info * pw_type_info(void); + +/** + * \} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* PIPEWIRE_TYPE_H */ diff --git a/src/pipewire/utils.c b/src/pipewire/utils.c new file mode 100644 index 0000000..1d02714 --- /dev/null +++ b/src/pipewire/utils.c @@ -0,0 +1,214 @@ +/* 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 <fcntl.h> +#include <unistd.h> +#include <errno.h> +#ifdef HAVE_SYS_RANDOM_H +#include <sys/random.h> +#endif +#include <string.h> + +#include <pipewire/array.h> +#include <pipewire/log.h> +#include <pipewire/utils.h> + +/** Split a string based on delimiters + * \param str a string to split + * \param delimiter delimiter characters to split on + * \param[out] len the length of the current string + * \param[in,out] state a state variable + * \return a string or NULL when the end is reached + * + * Repeatedly call this function to split \a str into all substrings + * delimited by \a delimiter. \a state should be set to NULL on the first + * invocation and passed to the function until NULL is returned. + */ +SPA_EXPORT +const char *pw_split_walk(const char *str, const char *delimiter, size_t * len, const char **state) +{ + const char *s = *state ? *state : str; + + s += strspn(s, delimiter); + if (*s == '\0') + return NULL; + + *len = strcspn(s, delimiter); + *state = s + *len; + + return s; +} + +/** Split a string based on delimiters + * \param str a string to split + * \param delimiter delimiter characters to split on + * \param max_tokens the max number of tokens to split + * \param[out] n_tokens the number of tokens + * \return a NULL terminated array of strings that should be + * freed with \ref pw_free_strv. + */ +SPA_EXPORT +char **pw_split_strv(const char *str, const char *delimiter, int max_tokens, int *n_tokens) +{ + const char *state = NULL, *s = NULL; + struct pw_array arr; + size_t len; + int n = 0; + + pw_array_init(&arr, 16); + + s = pw_split_walk(str, delimiter, &len, &state); + while (s && n + 1 < max_tokens) { + pw_array_add_ptr(&arr, strndup(s, len)); + s = pw_split_walk(str, delimiter, &len, &state); + n++; + } + if (s) { + pw_array_add_ptr(&arr, strdup(s)); + n++; + } + pw_array_add_ptr(&arr, NULL); + + *n_tokens = n; + + return arr.data; +} + +/** Split a string in-place based on delimiters + * \param str a string to split + * \param delimiter delimiter characters to split on + * \param max_tokens the max number of tokens to split + * \param[out] tokens an array to hold up to \a max_tokens of strings + * \return the number of tokens in \a tokens + * + * \a str will be modified in-place so that \a tokens will contain zero terminated + * strings split at \a delimiter characters. + */ +SPA_EXPORT +int pw_split_ip(char *str, const char *delimiter, int max_tokens, char *tokens[]) +{ + const char *state = NULL; + char *s, *t; + size_t len, l2; + int n = 0; + + s = (char *)pw_split_walk(str, delimiter, &len, &state); + while (s && n + 1 < max_tokens) { + t = (char*)pw_split_walk(str, delimiter, &l2, &state); + s[len] = '\0'; + tokens[n++] = s; + s = t; + len = l2; + } + if (s) + tokens[n++] = s; + return n; +} + +/** Free a NULL terminated array of strings + * \param str a NULL terminated array of string + * + * Free all the strings in the array and the array + */ +SPA_EXPORT +void pw_free_strv(char **str) +{ + int i; + + if (str == NULL) + return; + + for (i = 0; str[i]; i++) + free(str[i]); + free(str); +} + +/** Strip all whitespace before and after a string + * \param str a string to strip + * \param whitespace characters to strip + * \return the stripped part of \a str + * + * Strip whitespace before and after \a str. \a str will be + * modified. + */ +SPA_EXPORT +char *pw_strip(char *str, const char *whitespace) +{ + char *e, *l = NULL; + + str += strspn(str, whitespace); + + for (e = str; *e; e++) + if (!strchr(whitespace, *e)) + l = e; + + if (l) + *(l + 1) = '\0'; + else + *str = '\0'; + + return str; +} + +/** Fill a buffer with random data + * \param buf a buffer to fill + * \param buflen the number of bytes to fill + * \param flags optional flags + * \return the number of bytes filled + * + * Fill \a buf with \a buflen random bytes. + */ +SPA_EXPORT +ssize_t pw_getrandom(void *buf, size_t buflen, unsigned int flags) +{ + ssize_t bytes; + int read_errno; + +#ifdef HAVE_GETRANDOM + bytes = getrandom(buf, buflen, flags); + if (!(bytes == -1 && errno == ENOSYS)) + return bytes; +#endif + + int fd = open("/dev/urandom", O_CLOEXEC); + if (fd < 0) + return -1; + bytes = read(fd, buf, buflen); + read_errno = errno; + close(fd); + errno = read_errno; + return bytes; +} + +SPA_EXPORT +void* pw_reallocarray(void *ptr, size_t nmemb, size_t size) +{ +#ifdef HAVE_REALLOCARRAY + return reallocarray(ptr, nmemb, size); +#else + return realloc(ptr, nmemb * size); +#endif +} diff --git a/src/pipewire/utils.h b/src/pipewire/utils.h new file mode 100644 index 0000000..d8d45e0 --- /dev/null +++ b/src/pipewire/utils.h @@ -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. + */ + +#ifndef PIPEWIRE_UTILS_H +#define PIPEWIRE_UTILS_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <stdlib.h> +#include <string.h> +#include <sys/un.h> +#ifndef _POSIX_C_SOURCE +# include <sys/mount.h> +#endif + +#include <spa/utils/defs.h> +#include <spa/pod/pod.h> + +/** \defgroup pw_utils Utilities + * + * Various utility functions + */ + +/** + * \addtogroup pw_utils + * \{ + */ + +/** a function to destroy an item */ +typedef void (*pw_destroy_t) (void *object); + +const char * +pw_split_walk(const char *str, const char *delimiter, size_t *len, const char **state); + +char ** +pw_split_strv(const char *str, const char *delimiter, int max_tokens, int *n_tokens); + +int +pw_split_ip(char *str, const char *delimiter, int max_tokens, char *tokens[]); + +void +pw_free_strv(char **str); + +char * +pw_strip(char *str, const char *whitespace); + +#if !defined(strndupa) +# define strndupa(s, n) \ + ({ \ + const char *__old = (s); \ + size_t __len = strnlen(__old, (n)); \ + char *__new = (char *) __builtin_alloca(__len + 1); \ + memcpy(__new, __old, __len); \ + __new[__len] = '\0'; \ + __new; \ + }) +#endif + +#if !defined(strdupa) +# define strdupa(s) \ + ({ \ + const char *__old = (s); \ + size_t __len = strlen(__old) + 1; \ + char *__new = (char *) alloca(__len); \ + (char *) memcpy(__new, __old, __len); \ + }) +#endif + +SPA_WARN_UNUSED_RESULT +ssize_t pw_getrandom(void *buf, size_t buflen, unsigned int flags); + +void* pw_reallocarray(void *ptr, size_t nmemb, size_t size); + +#ifdef PW_ENABLE_DEPRECATED +#define PW_DEPRECATED(v) (v) +#else +#define PW_DEPRECATED(v) ({ __typeof__(v) _v SPA_DEPRECATED = (v); (void)_v; (v); }) +#endif /* PW_ENABLE_DEPRECATED */ + +/** + * \} + */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* PIPEWIRE_UTILS_H */ diff --git a/src/pipewire/version.h.in b/src/pipewire/version.h.in new file mode 100644 index 0000000..55604d0 --- /dev/null +++ b/src/pipewire/version.h.in @@ -0,0 +1,68 @@ +/* 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. + */ + +#ifndef PIPEWIRE_VERSION_H +#define PIPEWIRE_VERSION_H + +/* WARNING: Make sure to edit the real source file version.h.in! */ + +#ifdef __cplusplus +extern "C" { +#endif + +/** Return the version of the header files. Keep in mind that this is +a macro and not a function, so it is impossible to get the pointer of +it. */ +#define pw_get_headers_version() ("@PIPEWIRE_VERSION_MAJOR@.@PIPEWIRE_VERSION_MINOR@.@PIPEWIRE_VERSION_MICRO@") + +/** Return the version of the library the current application is + * linked to. */ +const char* pw_get_library_version(void); + +/** The current API version. Versions prior to 0.2.0 have + * PW_API_VERSION undefined. Please note that this is only ever + * increased on incompatible API changes! */ +#define PW_API_VERSION @PIPEWIRE_API_VERSION@ + +/** The major version of PipeWire. \since 0.2.0 */ +#define PW_MAJOR @PIPEWIRE_VERSION_MAJOR@ + +/** The minor version of PipeWire. \since 0.2.0 */ +#define PW_MINOR @PIPEWIRE_VERSION_MINOR@ + +/** The micro version of PipeWire. \since 0.2.0 */ +#define PW_MICRO @PIPEWIRE_VERSION_MICRO@ + +/** Evaluates to TRUE if the PipeWire library version is equal or + * newer than the specified. \since 0.2.0 */ +#define PW_CHECK_VERSION(major,minor,micro) \ + ((PW_MAJOR > (major)) || \ + (PW_MAJOR == (major) && PW_MINOR > (minor)) || \ + (PW_MAJOR == (major) && PW_MINOR == (minor) && PW_MICRO >= (micro))) + +#ifdef __cplusplus +} +#endif + +#endif /* PIPEWIRE_VERSION_H */ diff --git a/src/pipewire/work-queue.c b/src/pipewire/work-queue.c new file mode 100644 index 0000000..190d29f --- /dev/null +++ b/src/pipewire/work-queue.c @@ -0,0 +1,269 @@ +/* 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 <errno.h> +#include <stdio.h> +#include <string.h> + +#include <spa/utils/type.h> +#include <spa/utils/result.h> + +#include "pipewire/log.h" +#include "pipewire/work-queue.h" + +PW_LOG_TOPIC_EXTERN(log_work_queue); +#define PW_LOG_TOPIC_DEFAULT log_work_queue + +/** \cond */ +struct work_item { + void *obj; + uint32_t id; + uint32_t seq; + pw_work_func_t func; + void *data; + struct spa_list link; + int res; +}; + +struct pw_work_queue { + struct pw_loop *loop; + + struct spa_source *wakeup; + + struct spa_list work_list; + struct spa_list free_list; + uint32_t counter; + uint32_t n_queued; +}; +/** \endcond */ + +static void process_work_queue(void *data, uint64_t count) +{ + struct pw_work_queue *this = data; + struct work_item *item, *tmp; + + spa_list_for_each_safe(item, tmp, &this->work_list, link) { + if (item->seq != SPA_ID_INVALID) { + pw_log_debug("%p: n_queued:%d waiting for item %p seq:%d id:%u", this, + this->n_queued, item->obj, item->seq, item->id); + continue; + } + + if (item->res == -EBUSY && + item != spa_list_first(&this->work_list, struct work_item, link)) { + pw_log_debug("%p: n_queued:%d sync item %p not head id:%u", this, + this->n_queued, item->obj, item->id); + continue; + } + + spa_list_remove(&item->link); + this->n_queued--; + + if (item->func) { + pw_log_debug("%p: n_queued:%d process work item %p seq:%d res:%d id:%u", + this, this->n_queued, item->obj, item->seq, item->res, + item->id); + item->func(item->obj, item->data, item->res, item->id); + } + spa_list_append(&this->free_list, &item->link); + } +} + +/** Create a new \ref pw_work_queue + * + * \param loop the loop to use + * \return a newly allocated work queue + * + */ +struct pw_work_queue *pw_work_queue_new(struct pw_loop *loop) +{ + struct pw_work_queue *this; + int res; + + this = calloc(1, sizeof(struct pw_work_queue)); + if (this == NULL) + return NULL; + + pw_log_debug("%p: new", this); + + this->loop = loop; + + this->wakeup = pw_loop_add_event(this->loop, process_work_queue, this); + if (this->wakeup == NULL) { + res = -errno; + goto error_free; + } + + spa_list_init(&this->work_list); + spa_list_init(&this->free_list); + + return this; + +error_free: + free(this); + errno = -res; + return NULL; +} + +/** Destroy a work queue + * \param queue the work queue to destroy + * + */ +void pw_work_queue_destroy(struct pw_work_queue *queue) +{ + struct work_item *item, *tmp; + + pw_log_debug("%p: destroy", queue); + + pw_loop_destroy_source(queue->loop, queue->wakeup); + + spa_list_for_each_safe(item, tmp, &queue->work_list, link) { + pw_log_debug("%p: cancel work item %p seq:%d res:%d id:%u", + queue, item->obj, item->seq, item->res, item->id); + free(item); + } + spa_list_for_each_safe(item, tmp, &queue->free_list, link) + free(item); + + free(queue); +} + +/** Add an item to the work queue + * + * \param queue the work queue + * \param obj the object owning the work item + * \param res a result code + * \param func a work function + * \param data passed to \a func + * + */ +SPA_EXPORT +uint32_t +pw_work_queue_add(struct pw_work_queue *queue, void *obj, int res, pw_work_func_t func, void *data) +{ + struct work_item *item; + bool have_work = false; + + if (!spa_list_is_empty(&queue->free_list)) { + item = spa_list_first(&queue->free_list, struct work_item, link); + spa_list_remove(&item->link); + } else { + item = malloc(sizeof(struct work_item)); + if (item == NULL) + return SPA_ID_INVALID; + } + item->id = ++queue->counter; + if (item->id == SPA_ID_INVALID) + item->id = ++queue->counter; + + item->obj = obj; + item->func = func; + item->data = data; + + if (SPA_RESULT_IS_ASYNC(res)) { + item->seq = SPA_RESULT_ASYNC_SEQ(res); + item->res = res; + pw_log_debug("%p: defer async %d for object %p id:%d", + queue, item->seq, obj, item->id); + } else if (res == -EBUSY) { + pw_log_debug("%p: wait sync object %p id:%u", + queue, obj, item->id); + item->seq = SPA_ID_INVALID; + item->res = res; + have_work = true; + } else { + item->seq = SPA_ID_INVALID; + item->res = res; + have_work = true; + pw_log_debug("%p: defer object %p id:%u", queue, obj, item->id); + } + spa_list_append(&queue->work_list, &item->link); + queue->n_queued++; + + if (have_work) + pw_loop_signal_event(queue->loop, queue->wakeup); + + return item->id; +} + +/** Cancel a work item + * \param queue the work queue + * \param obj the owner object + * \param id the wotk id to cancel + * + */ +SPA_EXPORT +int pw_work_queue_cancel(struct pw_work_queue *queue, void *obj, uint32_t id) +{ + bool have_work = false; + struct work_item *item; + + spa_list_for_each(item, &queue->work_list, link) { + if ((id == SPA_ID_INVALID || item->id == id) && (obj == NULL || item->obj == obj)) { + pw_log_debug("%p: cancel defer %d for object %p id:%u", queue, + item->seq, item->obj, id); + item->seq = SPA_ID_INVALID; + item->func = NULL; + have_work = true; + } + } + if (!have_work) { + pw_log_debug("%p: no deferred found for object %p id:%u", queue, obj, id); + return -EINVAL; + } + + pw_loop_signal_event(queue->loop, queue->wakeup); + return 0; +} + +/** Complete a work item + * \param queue the work queue + * \param obj the owner object + * \param seq the sequence number that completed + * \param res 0 if the item was found, < 0 on error + * + */ +SPA_EXPORT +int pw_work_queue_complete(struct pw_work_queue *queue, void *obj, uint32_t seq, int res) +{ + struct work_item *item; + bool have_work = false; + + spa_list_for_each(item, &queue->work_list, link) { + if (item->obj == obj && item->seq == seq) { + pw_log_debug("%p: found deferred %d for object %p res:%d id:%u", + queue, seq, obj, res, item->id); + item->seq = SPA_ID_INVALID; + item->res = res; + have_work = true; + } + } + if (!have_work) { + pw_log_trace("%p: no deferred %d found for object %p", queue, seq, obj); + return -EINVAL; + } + + pw_loop_signal_event(queue->loop, queue->wakeup); + return 0; +} diff --git a/src/pipewire/work-queue.h b/src/pipewire/work-queue.h new file mode 100644 index 0000000..299f8f1 --- /dev/null +++ b/src/pipewire/work-queue.h @@ -0,0 +1,71 @@ +/* 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. + */ + +#ifndef PIPEWIRE_WORK_QUEUE_H +#define PIPEWIRE_WORK_QUEUE_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** \defgroup pw_work_queue Work Queue + * Queued processing of work items. + */ + +/** + * \addtogroup pw_work_queue + * \{ + */ +struct pw_work_queue; + +#include <pipewire/loop.h> + +typedef void (*pw_work_func_t) (void *obj, void *data, int res, uint32_t id); + +struct pw_work_queue * +pw_work_queue_new(struct pw_loop *loop); + +void +pw_work_queue_destroy(struct pw_work_queue *queue); + +uint32_t +pw_work_queue_add(struct pw_work_queue *queue, + void *obj, int res, + pw_work_func_t func, void *data); + +int +pw_work_queue_cancel(struct pw_work_queue *queue, void *obj, uint32_t id); + +int +pw_work_queue_complete(struct pw_work_queue *queue, void *obj, uint32_t seq, int res); + +/** + * \} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* PIPEWIRE_WORK_QUEUE_H */ |