summaryrefslogtreecommitdiffstats
path: root/src/pipewire
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/pipewire/array.h176
-rw-r--r--src/pipewire/buffers.c368
-rw-r--r--src/pipewire/buffers.h75
-rw-r--r--src/pipewire/client.h186
-rw-r--r--src/pipewire/conf.c1186
-rw-r--r--src/pipewire/conf.h49
-rw-r--r--src/pipewire/context.c1461
-rw-r--r--src/pipewire/context.h195
-rw-r--r--src/pipewire/control.c267
-rw-r--r--src/pipewire/control.h82
-rw-r--r--src/pipewire/core.c495
-rw-r--r--src/pipewire/core.h629
-rw-r--r--src/pipewire/data-loop.c292
-rw-r--r--src/pipewire/data-loop.h113
-rw-r--r--src/pipewire/device.h178
-rw-r--r--src/pipewire/extensions/client-node.h357
-rw-r--r--src/pipewire/extensions/meson.build21
-rw-r--r--src/pipewire/extensions/metadata.h112
-rw-r--r--src/pipewire/extensions/profiler.h95
-rw-r--r--src/pipewire/extensions/protocol-native.h105
-rw-r--r--src/pipewire/extensions/session-manager.h47
-rw-r--r--src/pipewire/extensions/session-manager/impl-interfaces.h298
-rw-r--r--src/pipewire/extensions/session-manager/interfaces.h479
-rw-r--r--src/pipewire/extensions/session-manager/introspect-funcs.h323
-rw-r--r--src/pipewire/extensions/session-manager/introspect.h130
-rw-r--r--src/pipewire/extensions/session-manager/keys.h67
-rw-r--r--src/pipewire/factory.h123
-rw-r--r--src/pipewire/filter.c1962
-rw-r--r--src/pipewire/filter.h250
-rw-r--r--src/pipewire/global.c430
-rw-r--r--src/pipewire/global.h165
-rw-r--r--src/pipewire/i18n.h56
-rw-r--r--src/pipewire/impl-client.c780
-rw-r--r--src/pipewire/impl-client.h185
-rw-r--r--src/pipewire/impl-core.c673
-rw-r--r--src/pipewire/impl-core.h104
-rw-r--r--src/pipewire/impl-device.c935
-rw-r--r--src/pipewire/impl-device.h116
-rw-r--r--src/pipewire/impl-factory.c301
-rw-r--r--src/pipewire/impl-factory.h131
-rw-r--r--src/pipewire/impl-link.c1508
-rw-r--r--src/pipewire/impl-link.h126
-rw-r--r--src/pipewire/impl-metadata.c627
-rw-r--r--src/pipewire/impl-metadata.h114
-rw-r--r--src/pipewire/impl-module.c427
-rw-r--r--src/pipewire/impl-module.h120
-rw-r--r--src/pipewire/impl-node.c2316
-rw-r--r--src/pipewire/impl-node.h190
-rw-r--r--src/pipewire/impl-port.c1614
-rw-r--r--src/pipewire/impl-port.h144
-rw-r--r--src/pipewire/impl.h62
-rw-r--r--src/pipewire/introspect.c590
-rw-r--r--src/pipewire/keys.h356
-rw-r--r--src/pipewire/link.h148
-rw-r--r--src/pipewire/log.c291
-rw-r--r--src/pipewire/log.h182
-rw-r--r--src/pipewire/loop.c162
-rw-r--r--src/pipewire/loop.h90
-rw-r--r--src/pipewire/main-loop.c157
-rw-r--r--src/pipewire/main-loop.h86
-rw-r--r--src/pipewire/map.h252
-rw-r--r--src/pipewire/mem.c808
-rw-r--r--src/pipewire/mem.h212
-rw-r--r--src/pipewire/meson.build138
-rw-r--r--src/pipewire/module.h121
-rw-r--r--src/pipewire/node.h216
-rw-r--r--src/pipewire/permission.h86
-rw-r--r--src/pipewire/pipewire.c871
-rw-r--r--src/pipewire/pipewire.h123
-rw-r--r--src/pipewire/port.h179
-rw-r--r--src/pipewire/private.h1310
-rw-r--r--src/pipewire/properties.c733
-rw-r--r--src/pipewire/properties.h199
-rw-r--r--src/pipewire/protocol.c188
-rw-r--r--src/pipewire/protocol.h162
-rw-r--r--src/pipewire/proxy.c374
-rw-r--r--src/pipewire/proxy.h219
-rw-r--r--src/pipewire/resource.c351
-rw-r--r--src/pipewire/resource.h174
-rw-r--r--src/pipewire/settings.c337
-rw-r--r--src/pipewire/stream.c2414
-rw-r--r--src/pipewire/stream.h529
-rw-r--r--src/pipewire/thread-loop.c469
-rw-r--r--src/pipewire/thread-loop.h175
-rw-r--r--src/pipewire/thread.c160
-rw-r--r--src/pipewire/thread.h65
-rw-r--r--src/pipewire/type.h65
-rw-r--r--src/pipewire/utils.c214
-rw-r--r--src/pipewire/utils.h111
-rw-r--r--src/pipewire/version.h.in68
-rw-r--r--src/pipewire/work-queue.c269
-rw-r--r--src/pipewire/work-queue.h71
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, &param, &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,
+ &registry_methods,
+ data);
+
+ spa_list_append(&context->registry_resource_list, &registry_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, &current, &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, &current, &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, &param, &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, &registry->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(&registry->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, &registry->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, &registry->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, &param, 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 */