summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorWim Taymans <wtaymans@redhat.com>2017-07-11 15:57:20 +0200
committerWim Taymans <wtaymans@redhat.com>2017-07-11 16:08:22 +0200
commitd1655196c3a54de9dad08cde7ecde17d9c0acf8b (patch)
treefa3c48546d7c400882b18aba81a35d6fa4c0e85f /src
parent847cef83b665fcdeb7d50fe02ea51df460c857c4 (diff)
move things around
Diffstat (limited to 'src')
-rw-r--r--src/.gitignore0
-rw-r--r--src/daemon/.gitignore1
-rw-r--r--src/daemon/daemon-config.c200
-rw-r--r--src/daemon/daemon-config.h53
-rw-r--r--src/daemon/main.c56
-rw-r--r--src/daemon/meson.build30
-rw-r--r--src/daemon/pipewire-system.conf31
-rw-r--r--src/daemon/pipewire.conf.in11
-rw-r--r--src/daemon/pipewire.desktop.in11
-rw-r--r--src/examples/local-v4l2.c531
-rw-r--r--src/examples/meson.build18
-rw-r--r--src/examples/video-play.c415
-rw-r--r--src/examples/video-src.c296
-rw-r--r--src/extensions/client-node.h299
-rw-r--r--src/extensions/meson.build5
-rw-r--r--src/gst/gstpipewire.c64
-rw-r--r--src/gst/gstpipewireclock.c92
-rw-r--r--src/gst/gstpipewireclock.h62
-rw-r--r--src/gst/gstpipewiredeviceprovider.c647
-rw-r--r--src/gst/gstpipewiredeviceprovider.h105
-rw-r--r--src/gst/gstpipewireformat.c852
-rw-r--r--src/gst/gstpipewireformat.h37
-rw-r--r--src/gst/gstpipewirepool.c178
-rw-r--r--src/gst/gstpipewirepool.h66
-rw-r--r--src/gst/gstpipewiresink.c941
-rw-r--r--src/gst/gstpipewiresink.h114
-rw-r--r--src/gst/gstpipewiresrc.c1154
-rw-r--r--src/gst/gstpipewiresrc.h97
-rw-r--r--src/gst/meson.build31
-rw-r--r--src/meson.build8
-rw-r--r--src/modules/meson.build91
-rw-r--r--src/modules/module-autolink.c329
-rw-r--r--src/modules/module-client-node.c97
-rw-r--r--src/modules/module-client-node/client-node.c1242
-rw-r--r--src/modules/module-client-node/client-node.h57
-rw-r--r--src/modules/module-client-node/protocol-native.c873
-rw-r--r--src/modules/module-flatpak.c712
-rw-r--r--src/modules/module-jack.c754
-rw-r--r--src/modules/module-jack/defs.h143
-rw-r--r--src/modules/module-jack/server.h54
-rw-r--r--src/modules/module-jack/shared.h601
-rw-r--r--src/modules/module-jack/shm.c1303
-rw-r--r--src/modules/module-jack/shm.h216
-rw-r--r--src/modules/module-jack/synchro.h48
-rw-r--r--src/modules/module-mixer.c183
-rw-r--r--src/modules/module-protocol-dbus.c632
-rw-r--r--src/modules/module-protocol-native.c684
-rw-r--r--src/modules/module-protocol-native/connection.c529
-rw-r--r--src/modules/module-protocol-native/connection.h89
-rw-r--r--src/modules/module-protocol-native/protocol-native.c1077
-rw-r--r--src/modules/module-suspend-on-idle.c195
-rw-r--r--src/modules/spa/meson.build34
-rw-r--r--src/modules/spa/module-monitor.c63
-rw-r--r--src/modules/spa/module-node-factory.c118
-rw-r--r--src/modules/spa/module-node.c74
-rw-r--r--src/modules/spa/spa-monitor.c315
-rw-r--r--src/modules/spa/spa-monitor.h54
-rw-r--r--src/modules/spa/spa-node.c588
-rw-r--r--src/modules/spa/spa-node.h51
-rw-r--r--src/pipewire/.gitignore2
-rw-r--r--src/pipewire/array.h134
-rw-r--r--src/pipewire/client.c204
-rw-r--r--src/pipewire/client.h142
-rw-r--r--src/pipewire/command.c160
-rw-r--r--src/pipewire/command.h54
-rw-r--r--src/pipewire/core.c716
-rw-r--r--src/pipewire/core.h243
-rw-r--r--src/pipewire/data-loop.c214
-rw-r--r--src/pipewire/data-loop.h59
-rw-r--r--src/pipewire/interfaces.h347
-rw-r--r--src/pipewire/introspect.c390
-rw-r--r--src/pipewire/introspect.h184
-rw-r--r--src/pipewire/link.c1139
-rw-r--r--src/pipewire/link.h118
-rw-r--r--src/pipewire/log.c137
-rw-r--r--src/pipewire/log.h103
-rw-r--r--src/pipewire/loop.c127
-rw-r--r--src/pipewire/loop.h78
-rw-r--r--src/pipewire/main-loop.c112
-rw-r--r--src/pipewire/main-loop.h60
-rw-r--r--src/pipewire/map.h184
-rw-r--r--src/pipewire/mem.c221
-rw-r--r--src/pipewire/mem.h64
-rw-r--r--src/pipewire/memfd-wrappers.h60
-rw-r--r--src/pipewire/meson.build86
-rw-r--r--src/pipewire/module.c235
-rw-r--r--src/pipewire/module.h73
-rw-r--r--src/pipewire/node-factory.c21
-rw-r--r--src/pipewire/node-factory.h63
-rw-r--r--src/pipewire/node.c617
-rw-r--r--src/pipewire/node.h179
-rw-r--r--src/pipewire/pipewire.c346
-rw-r--r--src/pipewire/pipewire.h132
-rw-r--r--src/pipewire/port.c393
-rw-r--r--src/pipewire/port.h174
-rw-r--r--src/pipewire/properties.c318
-rw-r--r--src/pipewire/properties.h74
-rw-r--r--src/pipewire/protocol.c96
-rw-r--r--src/pipewire/protocol.h94
-rw-r--r--src/pipewire/proxy.c122
-rw-r--r--src/pipewire/proxy.h132
-rw-r--r--src/pipewire/remote.c293
-rw-r--r--src/pipewire/remote.h181
-rw-r--r--src/pipewire/resource.c111
-rw-r--r--src/pipewire/resource.h107
-rw-r--r--src/pipewire/rtkit.c378
-rw-r--r--src/pipewire/rtkit.h93
-rw-r--r--src/pipewire/sig.h76
-rw-r--r--src/pipewire/stream.c1062
-rw-r--r--src/pipewire/stream.h315
-rw-r--r--src/pipewire/thread-loop.c297
-rw-r--r--src/pipewire/thread-loop.h133
-rw-r--r--src/pipewire/transport.c309
-rw-r--r--src/pipewire/transport.h137
-rw-r--r--src/pipewire/type.c125
-rw-r--r--src/pipewire/type.h126
-rw-r--r--src/pipewire/utils.c131
-rw-r--r--src/pipewire/utils.h65
-rw-r--r--src/pipewire/work-queue.c243
-rw-r--r--src/pipewire/work-queue.h63
-rw-r--r--src/server/meson.build53
-rw-r--r--src/tools/meson.build5
-rw-r--r--src/tools/pipewire-monitor.c356
123 files changed, 30677 insertions, 0 deletions
diff --git a/src/.gitignore b/src/.gitignore
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/.gitignore
diff --git a/src/daemon/.gitignore b/src/daemon/.gitignore
new file mode 100644
index 00000000..5347568e
--- /dev/null
+++ b/src/daemon/.gitignore
@@ -0,0 +1 @@
+pipewire.desktop
diff --git a/src/daemon/daemon-config.c b/src/daemon/daemon-config.c
new file mode 100644
index 00000000..290639a3
--- /dev/null
+++ b/src/daemon/daemon-config.c
@@ -0,0 +1,200 @@
+/* PipeWire
+ * Copyright (C) 2016 Axis Communications <dev-gstreamer@axis.com>
+ * @author Linus Svensson <linus.svensson@axis.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <string.h>
+#include <errno.h>
+
+#include <pipewire/pipewire.h>
+#include <pipewire/command.h>
+
+#include "daemon/daemon-config.h"
+
+#define DEFAULT_CONFIG_FILE PIPEWIRE_CONFIG_DIR "/pipewire.conf"
+
+static bool
+parse_line(struct pw_daemon_config *config,
+ const char *filename, char *line, unsigned int lineno, char **err)
+{
+ struct pw_command *command = NULL;
+ char *p;
+ bool ret = true;
+ char *local_err = NULL;
+
+ /* search for comments */
+ if ((p = strchr(line, '#')))
+ *p = '\0';
+
+ /* remove whitespaces */
+ pw_strip(line, "\n\r \t");
+
+ if (*line == '\0') /* empty line */
+ return true;
+
+ if ((command = pw_command_parse(line, &local_err)) == NULL) {
+ asprintf(err, "%s:%u: %s", filename, lineno, local_err);
+ free(local_err);
+ ret = false;
+ } else {
+ spa_list_insert(config->commands.prev, &command->link);
+ }
+
+ return ret;
+}
+
+/**
+ * pw_daemon_config_new:
+ *
+ * Returns a new empty #struct pw_daemon_config.
+ */
+struct pw_daemon_config *pw_daemon_config_new(void)
+{
+ struct pw_daemon_config *config;
+
+ config = calloc(1, sizeof(struct pw_daemon_config));
+ spa_list_init(&config->commands);
+
+ return config;
+}
+
+/**
+ * pw_daemon_config_free:
+ * @config: A #struct pw_daemon_config
+ *
+ * Free all resources associated to @config.
+ */
+void pw_daemon_config_free(struct pw_daemon_config *config)
+{
+ struct pw_command *cmd, *tmp;
+
+ spa_list_for_each_safe(cmd, tmp, &config->commands, link)
+ pw_command_free(cmd);
+
+ free(config);
+}
+
+/**
+ * pw_daemon_config_load_file:
+ * @config: A #struct pw_daemon_config
+ * @filename: A filename
+ * @err: Return location for an error string
+ *
+ * Loads PipeWire config from @filename.
+ *
+ * Returns: %true on success, otherwise %false and @err is set.
+ */
+bool pw_daemon_config_load_file(struct pw_daemon_config *config, const char *filename, char **err)
+{
+ unsigned int line;
+ FILE *f;
+ char buf[4096];
+
+ pw_log_debug("deamon-config %p: loading configuration file '%s'", config, filename);
+
+ if ((f = fopen(filename, "r")) == NULL) {
+ asprintf(err, "failed to open configuration file '%s': %s", filename,
+ strerror(errno));
+ goto open_error;
+ }
+
+ line = 0;
+
+ while (!feof(f)) {
+ if (!fgets(buf, sizeof(buf), f)) {
+ if (feof(f))
+ break;
+
+ asprintf(err, "failed to read configuration file '%s': %s",
+ filename, strerror(errno));
+ goto read_error;
+ }
+
+ line++;
+
+ if (!parse_line(config, filename, buf, line, err))
+ goto parse_failed;
+ }
+ fclose(f);
+
+ return true;
+
+ parse_failed:
+ read_error:
+ fclose(f);
+ open_error:
+ return false;
+}
+
+/**
+ * pw_daemon_config_load:
+ * @config: A #struct pw_daemon_config
+ * @err: Return location for a #GError, or %NULL
+ *
+ * Loads the default config file for PipeWire. The filename can be overridden with
+ * an evironment variable PIPEWIRE_CONFIG_FILE.
+ *
+ * Return: %true on success, otherwise %false and @err is set.
+ */
+bool pw_daemon_config_load(struct pw_daemon_config *config, char **err)
+{
+ const char *filename;
+
+ filename = getenv("PIPEWIRE_CONFIG_FILE");
+ if (filename != NULL && *filename != '\0') {
+ pw_log_debug("PIPEWIRE_CONFIG_FILE set to: %s", filename);
+ } else {
+ filename = DEFAULT_CONFIG_FILE;
+ }
+ return pw_daemon_config_load_file(config, filename, err);
+}
+
+/**
+ * pw_daemon_config_run_commands:
+ * @config: A #struct pw_daemon_config
+ * @core: A #struct pw_core
+ *
+ * Run all commands that have been parsed. The list of commands will be cleared
+ * when this function has been called.
+ *
+ * Returns: %true if all commands where executed with success, otherwise %false.
+ */
+bool pw_daemon_config_run_commands(struct pw_daemon_config *config, struct pw_core *core)
+{
+ char *err = NULL;
+ bool ret = true;
+ struct pw_command *command, *tmp;
+
+ spa_list_for_each(command, &config->commands, link) {
+ if (!pw_command_run(command, core, &err)) {
+ pw_log_warn("could not run command %s: %s", command->name, err);
+ free(err);
+ ret = false;
+ }
+ }
+
+ spa_list_for_each_safe(command, tmp, &config->commands, link) {
+ pw_command_free(command);
+ }
+
+ return ret;
+}
diff --git a/src/daemon/daemon-config.h b/src/daemon/daemon-config.h
new file mode 100644
index 00000000..c5f7bdc2
--- /dev/null
+++ b/src/daemon/daemon-config.h
@@ -0,0 +1,53 @@
+/* PipeWire
+ * Copyright (C) 2016 Axis Communications <dev-gstreamer@axis.com>
+ * @author Linus Svensson <linus.svensson@axis.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __PIPEWIRE_DAEMON_CONFIG_H__
+#define __PIPEWIRE_DAEMON_CONFIG_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <pipewire/core.h>
+
+struct pw_daemon_config {
+ struct spa_list commands;
+};
+
+struct pw_daemon_config *
+pw_daemon_config_new(void);
+
+void
+pw_daemon_config_free(struct pw_daemon_config *config);
+
+bool
+pw_daemon_config_load_file(struct pw_daemon_config *config, const char *filename, char **err);
+
+bool
+pw_daemon_config_load(struct pw_daemon_config *config, char **err);
+
+bool
+pw_daemon_config_run_commands(struct pw_daemon_config *config, struct pw_core *core);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __PIPEWIRE_DAEMON_CONFIG_H__ */
diff --git a/src/daemon/main.c b/src/daemon/main.c
new file mode 100644
index 00000000..d8c566ce
--- /dev/null
+++ b/src/daemon/main.c
@@ -0,0 +1,56 @@
+/* PipeWire
+ * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <pipewire/pipewire.h>
+#include <pipewire/core.h>
+#include <pipewire/module.h>
+
+#include "daemon-config.h"
+
+int main(int argc, char *argv[])
+{
+ struct pw_core *core;
+ struct pw_main_loop *loop;
+ struct pw_daemon_config *config;
+ char *err = NULL;
+
+ pw_init(&argc, &argv);
+
+ /* parse configuration */
+ config = pw_daemon_config_new();
+ if (!pw_daemon_config_load(config, &err)) {
+ pw_log_error("failed to parse config: %s", err);
+ free(err);
+ return -1;
+ }
+
+ loop = pw_main_loop_new();
+
+ core = pw_core_new(loop->loop, NULL);
+
+ pw_daemon_config_run_commands(config, core);
+
+ pw_main_loop_run(loop);
+
+ pw_main_loop_destroy(loop);
+
+ pw_core_destroy(core);
+
+ return 0;
+}
diff --git a/src/daemon/meson.build b/src/daemon/meson.build
new file mode 100644
index 00000000..6bd4f31c
--- /dev/null
+++ b/src/daemon/meson.build
@@ -0,0 +1,30 @@
+pipewire_sources = [
+ 'main.c',
+ 'daemon-config.c',
+]
+
+pipewire_headers = [
+ 'daemon-config.h',
+]
+
+pipewire_c_args = [
+ '-DHAVE_CONFIG_H',
+ '-D_GNU_SOURCE',
+ '-DG_LOG_DOMAIN=g_log_domain_pipewire',
+]
+
+conf_config = configuration_data()
+conf_install_dir = '@0@/pipewire'.format(get_option('sysconfdir'))
+
+configure_file(input : 'pipewire.conf.in',
+ output : 'pipewire.conf',
+ configuration : conf_config,
+ install_dir : conf_install_dir)
+
+executable('pipewire',
+ pipewire_sources,
+ install: true,
+ c_args : pipewire_c_args,
+ include_directories : [configinc, spa_inc],
+ dependencies : [pipewire_dep],
+)
diff --git a/src/daemon/pipewire-system.conf b/src/daemon/pipewire-system.conf
new file mode 100644
index 00000000..9ea2adec
--- /dev/null
+++ b/src/daemon/pipewire-system.conf
@@ -0,0 +1,31 @@
+<?xml version="1.0"?><!--*-nxml-*-->
+<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
+ "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
+
+<!--
+This file is part of PipeWire.
+
+PipeWire is free software; you can redistribute it and/or modify it
+under the terms of the GNU Lesser General Public License as
+published by the Free Software Foundation; either version 2.1 of the
+License, or (at your option) any later version.
+
+PipeWire is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
+Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with PipeWire; if not, see <http://www.gnu.org/licenses/>.
+-->
+
+<busconfig>
+
+ <!-- System-wide PipeWire runs as 'pipewire' user. This fragment is
+ not necessary for user PipeWire instances. -->
+
+ <policy user="pipewire">
+ <allow own="org.pipewire"/>
+ </policy>
+
+</busconfig>
diff --git a/src/daemon/pipewire.conf.in b/src/daemon/pipewire.conf.in
new file mode 100644
index 00000000..dd547514
--- /dev/null
+++ b/src/daemon/pipewire.conf.in
@@ -0,0 +1,11 @@
+#load-module libpipewire-module-protocol-dbus
+load-module libpipewire-module-protocol-native
+load-module libpipewire-module-suspend-on-idle
+#load-module libpipewire-module-spa-monitor alsa/libspa-alsa alsa-monitor alsa
+load-module libpipewire-module-spa-monitor v4l2/libspa-v4l2 v4l2-monitor v4l2
+#load-module libpipewire-module-spa-node videotestsrc/libspa-videotestsrc videotestsrc videotestsrc media.class=Video/Source Spa:POD:Object:Props:patternType=Spa:POD:Object:Props:patternType:snow
+load-module libpipewire-module-autolink
+#load-module libpipewire-module-mixer
+load-module libpipewire-module-client-node
+load-module libpipewire-module-flatpak
+#load-module libpipewire-module-jack
diff --git a/src/daemon/pipewire.desktop.in b/src/daemon/pipewire.desktop.in
new file mode 100644
index 00000000..7ff72c55
--- /dev/null
+++ b/src/daemon/pipewire.desktop.in
@@ -0,0 +1,11 @@
+[Desktop Entry]
+Version=1.0
+_Name=PipeWire Media System
+_Comment=Start the PipeWire Media System
+Exec=pipewire
+Terminal=false
+Type=Application
+Categories=
+GenericName=
+X-GNOME-Autostart-Phase=Initialization
+X-KDE-autostart-phase=1
diff --git a/src/examples/local-v4l2.c b/src/examples/local-v4l2.c
new file mode 100644
index 00000000..5dc3569c
--- /dev/null
+++ b/src/examples/local-v4l2.c
@@ -0,0 +1,531 @@
+/* PipeWire
+ * Copyright (C) 2017 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <stdio.h>
+#include <sys/mman.h>
+
+#include <SDL2/SDL.h>
+
+#include <spa/type-map.h>
+#include <spa/format-utils.h>
+#include <spa/video/format-utils.h>
+#include <spa/format-builder.h>
+#include <spa/props.h>
+#include <spa/lib/debug.h>
+
+#include <pipewire/pipewire.h>
+#include <pipewire/sig.h>
+#include <pipewire/module.h>
+#include <pipewire/node-factory.h>
+
+struct type {
+ uint32_t format;
+ uint32_t props;
+ struct spa_type_meta meta;
+ struct spa_type_data data;
+ struct spa_type_media_type media_type;
+ struct spa_type_media_subtype media_subtype;
+ struct spa_type_format_video format_video;
+ struct spa_type_video_format video_format;
+};
+
+static inline void init_type(struct type *type, struct spa_type_map *map)
+{
+ type->format = spa_type_map_get_id(map, SPA_TYPE__Format);
+ type->props = spa_type_map_get_id(map, SPA_TYPE__Props);
+ spa_type_meta_map(map, &type->meta);
+ spa_type_data_map(map, &type->data);
+ spa_type_media_type_map(map, &type->media_type);
+ spa_type_media_subtype_map(map, &type->media_subtype);
+ spa_type_format_video_map(map, &type->format_video);
+ spa_type_video_format_map(map, &type->video_format);
+}
+
+#define WIDTH 640
+#define HEIGHT 480
+#define BPP 3
+
+struct data {
+ struct type type;
+
+ const char *path;
+
+ SDL_Renderer *renderer;
+ SDL_Window *window;
+ SDL_Texture *texture;
+
+ bool running;
+ struct pw_loop *loop;
+ struct spa_source *timer;
+
+ struct pw_core *core;
+ struct pw_node *node;
+ struct pw_port *port;
+ struct spa_port_info port_info;
+
+ struct pw_node *v4l2;
+
+ struct pw_link *link;
+
+ uint8_t buffer[1024];
+
+ struct spa_video_info_raw format;
+ int32_t stride;
+
+ uint8_t params_buffer[1024];
+ struct spa_param *params[2];
+
+ struct spa_buffer *buffers[32];
+ int n_buffers;
+};
+
+static void handle_events(struct data *data)
+{
+ SDL_Event event;
+ while (SDL_PollEvent(&event)) {
+ switch (event.type) {
+ case SDL_QUIT:
+ exit(0);
+ break;
+ }
+ }
+}
+
+static struct {
+ Uint32 format;
+ uint32_t id;
+} video_formats[] = {
+ {
+ SDL_PIXELFORMAT_UNKNOWN, offsetof(struct spa_type_video_format, UNKNOWN),}, {
+ SDL_PIXELFORMAT_INDEX1LSB, offsetof(struct spa_type_video_format, UNKNOWN),}, {
+ SDL_PIXELFORMAT_UNKNOWN, offsetof(struct spa_type_video_format, UNKNOWN),}, {
+ SDL_PIXELFORMAT_INDEX1LSB, offsetof(struct spa_type_video_format, UNKNOWN),}, {
+ SDL_PIXELFORMAT_INDEX1MSB, offsetof(struct spa_type_video_format, UNKNOWN),}, {
+ SDL_PIXELFORMAT_INDEX4LSB, offsetof(struct spa_type_video_format, UNKNOWN),}, {
+ SDL_PIXELFORMAT_INDEX4MSB, offsetof(struct spa_type_video_format, UNKNOWN),}, {
+ SDL_PIXELFORMAT_INDEX8, offsetof(struct spa_type_video_format, UNKNOWN),}, {
+ SDL_PIXELFORMAT_RGB332, offsetof(struct spa_type_video_format, UNKNOWN),}, {
+ SDL_PIXELFORMAT_RGB444, offsetof(struct spa_type_video_format, UNKNOWN),}, {
+ SDL_PIXELFORMAT_RGB555, offsetof(struct spa_type_video_format, UNKNOWN),}, {
+ SDL_PIXELFORMAT_BGR555, offsetof(struct spa_type_video_format, UNKNOWN),}, {
+ SDL_PIXELFORMAT_ARGB4444, offsetof(struct spa_type_video_format, UNKNOWN),}, {
+ SDL_PIXELFORMAT_RGBA4444, offsetof(struct spa_type_video_format, UNKNOWN),}, {
+ SDL_PIXELFORMAT_ABGR4444, offsetof(struct spa_type_video_format, UNKNOWN),}, {
+ SDL_PIXELFORMAT_BGRA4444, offsetof(struct spa_type_video_format, UNKNOWN),}, {
+ SDL_PIXELFORMAT_ARGB1555, offsetof(struct spa_type_video_format, UNKNOWN),}, {
+ SDL_PIXELFORMAT_RGBA5551, offsetof(struct spa_type_video_format, UNKNOWN),}, {
+ SDL_PIXELFORMAT_ABGR1555, offsetof(struct spa_type_video_format, UNKNOWN),}, {
+ SDL_PIXELFORMAT_BGRA5551, offsetof(struct spa_type_video_format, UNKNOWN),}, {
+ SDL_PIXELFORMAT_RGB565, offsetof(struct spa_type_video_format, UNKNOWN),}, {
+ SDL_PIXELFORMAT_BGR565, offsetof(struct spa_type_video_format, UNKNOWN),}, {
+ SDL_PIXELFORMAT_RGB24, offsetof(struct spa_type_video_format, RGB),}, {
+ SDL_PIXELFORMAT_RGB888, offsetof(struct spa_type_video_format, RGB),}, {
+ SDL_PIXELFORMAT_RGBX8888, offsetof(struct spa_type_video_format, RGBx),}, {
+ SDL_PIXELFORMAT_BGR24, offsetof(struct spa_type_video_format, BGR),}, {
+ SDL_PIXELFORMAT_BGR888, offsetof(struct spa_type_video_format, BGR),}, {
+ SDL_PIXELFORMAT_BGRX8888, offsetof(struct spa_type_video_format, BGRx),}, {
+ SDL_PIXELFORMAT_ARGB2101010, offsetof(struct spa_type_video_format, UNKNOWN),}, {
+ SDL_PIXELFORMAT_RGBA8888, offsetof(struct spa_type_video_format, RGBA),}, {
+ SDL_PIXELFORMAT_ARGB8888, offsetof(struct spa_type_video_format, ARGB),}, {
+ SDL_PIXELFORMAT_BGRA8888, offsetof(struct spa_type_video_format, BGRA),}, {
+ SDL_PIXELFORMAT_ABGR8888, offsetof(struct spa_type_video_format, ABGR),}, {
+ SDL_PIXELFORMAT_YV12, offsetof(struct spa_type_video_format, YV12),}, {
+ SDL_PIXELFORMAT_IYUV, offsetof(struct spa_type_video_format, I420),}, {
+ SDL_PIXELFORMAT_YUY2, offsetof(struct spa_type_video_format, YUY2),}, {
+ SDL_PIXELFORMAT_UYVY, offsetof(struct spa_type_video_format, UYVY),}, {
+ SDL_PIXELFORMAT_YVYU, offsetof(struct spa_type_video_format, YVYU),}, {
+ SDL_PIXELFORMAT_NV12, offsetof(struct spa_type_video_format, NV12),}, {
+SDL_PIXELFORMAT_NV21, offsetof(struct spa_type_video_format, NV21),}};
+
+static uint32_t sdl_format_to_id(struct data *data, Uint32 format)
+{
+ int i;
+
+ for (i = 0; i < SPA_N_ELEMENTS(video_formats); i++) {
+ if (video_formats[i].format == format)
+ return *SPA_MEMBER(&data->type.video_format, video_formats[i].id, uint32_t);
+ }
+ return data->type.video_format.UNKNOWN;
+}
+
+static Uint32 id_to_sdl_format(struct data *data, uint32_t id)
+{
+ int i;
+
+ for (i = 0; i < SPA_N_ELEMENTS(video_formats); i++) {
+ if (*SPA_MEMBER(&data->type.video_format, video_formats[i].id, uint32_t) == id)
+ return video_formats[i].format;
+ }
+ return SDL_PIXELFORMAT_UNKNOWN;
+}
+
+#define PROP(f,key,type,...) \
+ SPA_POD_PROP (f,key,0,type,1,__VA_ARGS__)
+#define PROP_U_MM(f,key,type,...) \
+ SPA_POD_PROP (f,key,SPA_POD_PROP_FLAG_UNSET | \
+ SPA_POD_PROP_RANGE_MIN_MAX,type,3,__VA_ARGS__)
+
+static int impl_port_enum_formats(struct pw_port *port,
+ struct spa_format **format,
+ const struct spa_format *filter,
+ int32_t index)
+{
+ struct data *data = port->user_data;
+ const struct spa_format *formats[1];
+ struct spa_pod_builder b = SPA_POD_BUILDER_INIT(data->buffer, sizeof(data->buffer));
+ struct spa_pod_frame f[2];
+ SDL_RendererInfo info;
+ int i, c;
+
+ if (index != 0)
+ return SPA_RESULT_ENUM_END;
+
+ SDL_GetRendererInfo(data->renderer, &info);
+
+ spa_pod_builder_push_format(&b, &f[0], data->type.format,
+ data->type.media_type.video,
+ data->type.media_subtype.raw);
+
+ spa_pod_builder_push_prop(&b, &f[1], data->type.format_video.format,
+ SPA_POD_PROP_FLAG_UNSET |
+ SPA_POD_PROP_RANGE_ENUM);
+ for (i = 0, c = 0; i < info.num_texture_formats; i++) {
+ uint32_t id = sdl_format_to_id(data, info.texture_formats[i]);
+ if (id == 0)
+ continue;
+ if (c++ == 0)
+ spa_pod_builder_id(&b, id);
+ spa_pod_builder_id(&b, id);
+ }
+ for (i = 0; i < SPA_N_ELEMENTS(video_formats); i++) {
+ uint32_t id =
+ *SPA_MEMBER(&data->type.video_format, video_formats[i].id,
+ uint32_t);
+ if (id != data->type.video_format.UNKNOWN)
+ spa_pod_builder_id(&b, id);
+ }
+ spa_pod_builder_pop(&b, &f[1]);
+ spa_pod_builder_add(&b,
+ PROP_U_MM(&f[1], data->type.format_video.size, SPA_POD_TYPE_RECTANGLE,
+ WIDTH, HEIGHT,
+ 1, 1, info.max_texture_width, info.max_texture_height),
+ PROP_U_MM(&f[1], data->type.format_video.framerate, SPA_POD_TYPE_FRACTION,
+ 25, 1,
+ 0, 1, 30, 1),
+ 0);
+ spa_pod_builder_pop(&b, &f[0]);
+ formats[0] = SPA_POD_BUILDER_DEREF(&b, f[0].ref, struct spa_format);
+
+ spa_debug_format(formats[0]);
+
+ *format = (struct spa_format *)formats[0];
+
+ return SPA_RESULT_OK;
+}
+
+static int impl_port_set_format(struct pw_port *port, uint32_t flags, struct spa_format *format)
+{
+ struct data *data = port->user_data;
+ struct pw_core *core = data->core;
+ struct spa_pod_builder b = { NULL };
+ struct spa_pod_frame f[2];
+ Uint32 sdl_format;
+ void *d;
+
+ if (format == NULL) {
+ return SPA_RESULT_OK;
+ }
+
+ spa_debug_format(format);
+
+ spa_format_video_raw_parse(format, &data->format, &data->type.format_video);
+
+ sdl_format = id_to_sdl_format(data, data->format.format);
+ if (sdl_format == SDL_PIXELFORMAT_UNKNOWN)
+ return SPA_RESULT_ERROR;
+
+ data->texture = SDL_CreateTexture(data->renderer,
+ sdl_format,
+ SDL_TEXTUREACCESS_STREAMING,
+ data->format.size.width,
+ data->format.size.height);
+ SDL_LockTexture(data->texture, NULL, &d, &data->stride);
+ SDL_UnlockTexture(data->texture);
+
+ spa_pod_builder_init(&b, data->params_buffer, sizeof(data->params_buffer));
+ spa_pod_builder_object(&b, &f[0], 0, core->type.param_alloc_buffers.Buffers,
+ PROP(&f[1], core->type.param_alloc_buffers.size, SPA_POD_TYPE_INT,
+ data->stride * data->format.size.height),
+ PROP(&f[1], core->type.param_alloc_buffers.stride, SPA_POD_TYPE_INT,
+ data->stride),
+ PROP_U_MM(&f[1], core->type.param_alloc_buffers.buffers, SPA_POD_TYPE_INT,
+ 32,
+ 2, 32),
+ PROP(&f[1], core->type.param_alloc_buffers.align, SPA_POD_TYPE_INT,
+ 16));
+ data->params[0] = SPA_POD_BUILDER_DEREF(&b, f[0].ref, struct spa_param);
+
+ spa_pod_builder_object(&b, &f[0], 0, core->type.param_alloc_meta_enable.MetaEnable,
+ PROP(&f[1], core->type.param_alloc_meta_enable.type, SPA_POD_TYPE_ID,
+ core->type.meta.Header),
+ PROP(&f[1], core->type.param_alloc_meta_enable.size, SPA_POD_TYPE_INT,
+ sizeof(struct spa_meta_header)));
+ data->params[1] = SPA_POD_BUILDER_DEREF(&b, f[0].ref, struct spa_param);
+
+ return SPA_RESULT_OK;
+}
+
+static int impl_port_get_format(struct pw_port *port, const struct spa_format **format)
+{
+ return SPA_RESULT_NOT_IMPLEMENTED;
+}
+
+static int impl_port_get_info(struct pw_port *port, const struct spa_port_info **info)
+{
+ struct data *data = port->user_data;
+
+ data->port_info.flags = SPA_PORT_INFO_FLAG_CAN_USE_BUFFERS;
+ data->port_info.rate = 0;
+ data->port_info.props = NULL;
+
+ *info = &data->port_info;
+
+ return SPA_RESULT_OK;
+}
+
+static int impl_port_enum_params(struct pw_port *port, uint32_t index, struct spa_param **param)
+{
+ struct data *data = port->user_data;
+
+ if (index >= 2)
+ return SPA_RESULT_ENUM_END;
+
+ *param = data->params[index];
+
+ return SPA_RESULT_OK;
+}
+
+static int impl_port_set_param(struct pw_port *port, struct spa_param *param)
+{
+ return SPA_RESULT_NOT_IMPLEMENTED;
+}
+
+static int impl_port_use_buffers(struct pw_port *port, struct spa_buffer **buffers, uint32_t n_buffers)
+{
+ struct data *data = port->user_data;
+ int i;
+ for (i = 0; i < n_buffers; i++)
+ data->buffers[i] = buffers[i];
+ data->n_buffers = n_buffers;
+ return SPA_RESULT_OK;
+}
+
+static int impl_port_alloc_buffers(struct pw_port *port,
+ struct spa_param **params, uint32_t n_params,
+ struct spa_buffer **buffers, uint32_t *n_buffers)
+{
+ return SPA_RESULT_NOT_IMPLEMENTED;
+}
+
+static int impl_port_reuse_buffer(struct pw_port *port, uint32_t buffer_id)
+{
+ return SPA_RESULT_NOT_IMPLEMENTED;
+}
+
+static int impl_port_send_command(struct pw_port *port, struct spa_command *command)
+{
+ return SPA_RESULT_NOT_IMPLEMENTED;
+}
+
+static const struct pw_port_implementation impl_port = {
+ PW_VERSION_PORT_IMPLEMENTATION,
+ impl_port_enum_formats,
+ impl_port_set_format,
+ impl_port_get_format,
+ impl_port_get_info,
+ impl_port_enum_params,
+ impl_port_set_param,
+ impl_port_use_buffers,
+ impl_port_alloc_buffers,
+ impl_port_reuse_buffer,
+ impl_port_send_command,
+};
+
+static int impl_node_get_props(struct pw_node *node, struct spa_props **props)
+{
+ return SPA_RESULT_NOT_IMPLEMENTED;
+}
+
+static int impl_node_set_props(struct pw_node *node, const struct spa_props *props)
+{
+ return SPA_RESULT_NOT_IMPLEMENTED;
+}
+
+static int impl_node_send_command(struct pw_node *node,
+ struct spa_command *command)
+{
+ return SPA_RESULT_OK;
+}
+
+static struct pw_port* impl_node_add_port(struct pw_node *node,
+ enum pw_direction direction,
+ uint32_t port_id)
+{
+ return NULL;
+}
+
+static int impl_node_process_input(struct pw_node *node)
+{
+ struct data *data = node->user_data;
+ struct pw_port *port = data->port;
+ struct spa_buffer *buf;
+ uint8_t *map;
+ void *sdata, *ddata;
+ int sstride, dstride, ostride;
+ int i;
+ uint8_t *src, *dst;
+
+ buf = port->buffers[port->io.buffer_id];
+
+ if (buf->datas[0].type == data->type.data.MemFd ||
+ buf->datas[0].type == data->type.data.DmaBuf) {
+ map = mmap(NULL, buf->datas[0].maxsize + buf->datas[0].mapoffset, PROT_READ,
+ MAP_PRIVATE, buf->datas[0].fd, 0);
+ sdata = SPA_MEMBER(map, buf->datas[0].mapoffset, uint8_t);
+ } else if (buf->datas[0].type == data->type.data.MemPtr) {
+ map = NULL;
+ sdata = buf->datas[0].data;
+ } else
+ return SPA_RESULT_ERROR;
+
+ if (SDL_LockTexture(data->texture, NULL, &ddata, &dstride) < 0) {
+ fprintf(stderr, "Couldn't lock texture: %s\n", SDL_GetError());
+ return SPA_RESULT_ERROR;
+ }
+ sstride = buf->datas[0].chunk->stride;
+ ostride = SPA_MIN(sstride, dstride);
+
+ src = sdata;
+ dst = ddata;
+ for (i = 0; i < data->format.size.height; i++) {
+ memcpy(dst, src, ostride);
+ src += sstride;
+ dst += dstride;
+ }
+ SDL_UnlockTexture(data->texture);
+
+ SDL_RenderClear(data->renderer);
+ SDL_RenderCopy(data->renderer, data->texture, NULL, NULL);
+ SDL_RenderPresent(data->renderer);
+
+ if (map)
+ munmap(map, buf->datas[0].maxsize);
+
+ handle_events(data);
+
+ port->io.status = SPA_RESULT_NEED_BUFFER;
+
+ return SPA_RESULT_NEED_BUFFER;
+}
+
+static int impl_node_process_output(struct pw_node *node)
+{
+ return SPA_RESULT_NOT_IMPLEMENTED;
+}
+
+static const struct pw_node_implementation impl_node = {
+ PW_VERSION_NODE_IMPLEMENTATION,
+ impl_node_get_props,
+ impl_node_set_props,
+ impl_node_send_command,
+ impl_node_add_port,
+ impl_node_process_input,
+ impl_node_process_output,
+};
+
+static void make_nodes(struct data *data)
+{
+ struct pw_node_factory *factory;
+ struct pw_properties *props;
+
+ data->node = pw_node_new(data->core, NULL, "SDL-sink", NULL, 0);
+ data->node->user_data = data;
+ data->node->implementation = &impl_node;
+
+ data->port = pw_port_new(PW_DIRECTION_INPUT, 0, 0);
+ data->port->user_data = data;
+ data->port->implementation = &impl_port;
+ pw_port_add(data->port, data->node);
+ pw_node_export(data->node);
+
+ factory = pw_core_find_node_factory(data->core, "spa-node-factory");
+ props = pw_properties_new("spa.library.name", "v4l2/libspa-v4l2",
+ "spa.factory.name", "v4l2-source", NULL);
+ data->v4l2 = pw_node_factory_create_node(factory, NULL, "v4l2-source", props);
+
+ data->link = pw_link_new(data->core,
+ pw_node_get_free_port(data->v4l2, PW_DIRECTION_OUTPUT),
+ data->port,
+ NULL,
+ NULL,
+ NULL);
+ pw_link_activate(data->link);
+}
+
+int main(int argc, char *argv[])
+{
+ struct data data = { 0, };
+ char *err;
+
+ pw_init(&argc, &argv);
+
+ data.loop = pw_loop_new();
+ data.running = true;
+ data.core = pw_core_new(data.loop, NULL);
+ data.path = argc > 1 ? argv[1] : NULL;
+
+ pw_module_load(data.core, "libpipewire-module-spa-node-factory", NULL, &err);
+
+ init_type(&data.type, data.core->type.map);
+
+ spa_debug_set_type_map(data.core->type.map);
+
+ if (SDL_Init(SDL_INIT_VIDEO) < 0) {
+ printf("can't initialize SDL: %s\n", SDL_GetError());
+ return -1;
+ }
+
+ if (SDL_CreateWindowAndRenderer
+ (WIDTH, HEIGHT, SDL_WINDOW_RESIZABLE, &data.window, &data.renderer)) {
+ printf("can't create window: %s\n", SDL_GetError());
+ return -1;
+ }
+
+ make_nodes(&data);
+
+ pw_loop_enter(data.loop);
+ while (data.running) {
+ pw_loop_iterate(data.loop, -1);
+ }
+ pw_loop_leave(data.loop);
+
+ pw_loop_destroy(data.loop);
+
+ return 0;
+}
diff --git a/src/examples/meson.build b/src/examples/meson.build
new file mode 100644
index 00000000..e087f98d
--- /dev/null
+++ b/src/examples/meson.build
@@ -0,0 +1,18 @@
+executable('video-src',
+ 'video-src.c',
+ install: false,
+ dependencies : [pipewire_dep],
+)
+
+if sdl_dep.found()
+ executable('video-play',
+ 'video-play.c',
+ install: false,
+ dependencies : [pipewire_dep, sdl_dep],
+ )
+ executable('local-v4l2',
+ 'local-v4l2.c',
+ install: false,
+ dependencies : [pipewire_dep, sdl_dep],
+ )
+endif
diff --git a/src/examples/video-play.c b/src/examples/video-play.c
new file mode 100644
index 00000000..e47d8986
--- /dev/null
+++ b/src/examples/video-play.c
@@ -0,0 +1,415 @@
+/* PipeWire
+ * Copyright (C) 2017 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <stdio.h>
+#include <sys/mman.h>
+
+#include <SDL2/SDL.h>
+
+#include <spa/type-map.h>
+#include <spa/format-utils.h>
+#include <spa/video/format-utils.h>
+#include <spa/format-builder.h>
+#include <spa/props.h>
+#include <spa/lib/debug.h>
+
+#include <pipewire/pipewire.h>
+#include <pipewire/sig.h>
+
+struct type {
+ uint32_t format;
+ uint32_t props;
+ struct spa_type_meta meta;
+ struct spa_type_data data;
+ struct spa_type_media_type media_type;
+ struct spa_type_media_subtype media_subtype;
+ struct spa_type_format_video format_video;
+ struct spa_type_video_format video_format;
+};
+
+static inline void init_type(struct type *type, struct spa_type_map *map)
+{
+ type->format = spa_type_map_get_id(map, SPA_TYPE__Format);
+ type->props = spa_type_map_get_id(map, SPA_TYPE__Props);
+ spa_type_meta_map(map, &type->meta);
+ spa_type_data_map(map, &type->data);
+ spa_type_media_type_map(map, &type->media_type);
+ spa_type_media_subtype_map(map, &type->media_subtype);
+ spa_type_format_video_map(map, &type->format_video);
+ spa_type_video_format_map(map, &type->video_format);
+}
+
+#define WIDTH 640
+#define HEIGHT 480
+#define BPP 3
+
+struct data {
+ struct type type;
+
+ const char *path;
+
+ SDL_Renderer *renderer;
+ SDL_Window *window;
+ SDL_Texture *texture;
+
+ bool running;
+ struct pw_loop *loop;
+ struct spa_source *timer;
+
+ struct pw_core *core;
+ struct pw_remote *remote;
+ struct pw_listener on_state_changed;
+
+ struct pw_stream *stream;
+ struct pw_listener on_stream_state_changed;
+ struct pw_listener on_stream_format_changed;
+ struct pw_listener on_stream_new_buffer;
+
+ struct spa_video_info_raw format;
+ int32_t stride;
+
+ uint8_t params_buffer[1024];
+ int counter;
+};
+
+static void handle_events(struct data *data)
+{
+ SDL_Event event;
+ while (SDL_PollEvent(&event)) {
+ switch (event.type) {
+ case SDL_QUIT:
+ exit(0);
+ break;
+ }
+ }
+}
+
+static void
+on_stream_new_buffer(struct pw_listener *listener, struct pw_stream *stream, uint32_t id)
+{
+ struct data *data = SPA_CONTAINER_OF(listener, struct data, on_stream_new_buffer);
+ struct spa_buffer *buf;
+ uint8_t *map;
+ void *sdata, *ddata;
+ int sstride, dstride, ostride;
+ int i;
+ uint8_t *src, *dst;
+
+ buf = pw_stream_peek_buffer(data->stream, id);
+
+ if (buf->datas[0].type == data->type.data.MemFd) {
+ map = mmap(NULL, buf->datas[0].maxsize + buf->datas[0].mapoffset, PROT_READ,
+ MAP_PRIVATE, buf->datas[0].fd, 0);
+ sdata = SPA_MEMBER(map, buf->datas[0].mapoffset, uint8_t);
+ } else if (buf->datas[0].type == data->type.data.MemPtr) {
+ map = NULL;
+ sdata = buf->datas[0].data;
+ } else
+ return;
+
+ if (SDL_LockTexture(data->texture, NULL, &ddata, &dstride) < 0) {
+ fprintf(stderr, "Couldn't lock texture: %s\n", SDL_GetError());
+ return;
+ }
+ sstride = buf->datas[0].chunk->stride;
+ ostride = SPA_MIN(sstride, dstride);
+
+ src = sdata;
+ dst = ddata;
+ for (i = 0; i < data->format.size.height; i++) {
+ memcpy(dst, src, ostride);
+ src += sstride;
+ dst += dstride;
+ }
+ SDL_UnlockTexture(data->texture);
+
+ SDL_RenderClear(data->renderer);
+ SDL_RenderCopy(data->renderer, data->texture, NULL, NULL);
+ SDL_RenderPresent(data->renderer);
+
+ if (map)
+ munmap(map, buf->datas[0].maxsize);
+
+ pw_stream_recycle_buffer(data->stream, id);
+
+ handle_events(data);
+}
+
+static void on_stream_state_changed(struct pw_listener *listener, struct pw_stream *stream)
+{
+ printf("stream state: \"%s\"\n", pw_stream_state_as_string(stream->state));
+}
+
+static struct {
+ Uint32 format;
+ uint32_t id;
+} video_formats[] = {
+ {
+ SDL_PIXELFORMAT_UNKNOWN, offsetof(struct spa_type_video_format, UNKNOWN),}, {
+ SDL_PIXELFORMAT_INDEX1LSB, offsetof(struct spa_type_video_format, UNKNOWN),}, {
+ SDL_PIXELFORMAT_UNKNOWN, offsetof(struct spa_type_video_format, UNKNOWN),}, {
+ SDL_PIXELFORMAT_INDEX1LSB, offsetof(struct spa_type_video_format, UNKNOWN),}, {
+ SDL_PIXELFORMAT_INDEX1MSB, offsetof(struct spa_type_video_format, UNKNOWN),}, {
+ SDL_PIXELFORMAT_INDEX4LSB, offsetof(struct spa_type_video_format, UNKNOWN),}, {
+ SDL_PIXELFORMAT_INDEX4MSB, offsetof(struct spa_type_video_format, UNKNOWN),}, {
+ SDL_PIXELFORMAT_INDEX8, offsetof(struct spa_type_video_format, UNKNOWN),}, {
+ SDL_PIXELFORMAT_RGB332, offsetof(struct spa_type_video_format, UNKNOWN),}, {
+ SDL_PIXELFORMAT_RGB444, offsetof(struct spa_type_video_format, UNKNOWN),}, {
+ SDL_PIXELFORMAT_RGB555, offsetof(struct spa_type_video_format, UNKNOWN),}, {
+ SDL_PIXELFORMAT_BGR555, offsetof(struct spa_type_video_format, UNKNOWN),}, {
+ SDL_PIXELFORMAT_ARGB4444, offsetof(struct spa_type_video_format, UNKNOWN),}, {
+ SDL_PIXELFORMAT_RGBA4444, offsetof(struct spa_type_video_format, UNKNOWN),}, {
+ SDL_PIXELFORMAT_ABGR4444, offsetof(struct spa_type_video_format, UNKNOWN),}, {
+ SDL_PIXELFORMAT_BGRA4444, offsetof(struct spa_type_video_format, UNKNOWN),}, {
+ SDL_PIXELFORMAT_ARGB1555, offsetof(struct spa_type_video_format, UNKNOWN),}, {
+ SDL_PIXELFORMAT_RGBA5551, offsetof(struct spa_type_video_format, UNKNOWN),}, {
+ SDL_PIXELFORMAT_ABGR1555, offsetof(struct spa_type_video_format, UNKNOWN),}, {
+ SDL_PIXELFORMAT_BGRA5551, offsetof(struct spa_type_video_format, UNKNOWN),}, {
+ SDL_PIXELFORMAT_RGB565, offsetof(struct spa_type_video_format, UNKNOWN),}, {
+ SDL_PIXELFORMAT_BGR565, offsetof(struct spa_type_video_format, UNKNOWN),}, {
+ SDL_PIXELFORMAT_RGB24, offsetof(struct spa_type_video_format, RGB),}, {
+ SDL_PIXELFORMAT_RGB888, offsetof(struct spa_type_video_format, RGB),}, {
+ SDL_PIXELFORMAT_RGBX8888, offsetof(struct spa_type_video_format, RGBx),}, {
+ SDL_PIXELFORMAT_BGR24, offsetof(struct spa_type_video_format, BGR),}, {
+ SDL_PIXELFORMAT_BGR888, offsetof(struct spa_type_video_format, BGR),}, {
+ SDL_PIXELFORMAT_BGRX8888, offsetof(struct spa_type_video_format, BGRx),}, {
+ SDL_PIXELFORMAT_ARGB2101010, offsetof(struct spa_type_video_format, UNKNOWN),}, {
+ SDL_PIXELFORMAT_RGBA8888, offsetof(struct spa_type_video_format, RGBA),}, {
+ SDL_PIXELFORMAT_ARGB8888, offsetof(struct spa_type_video_format, ARGB),}, {
+ SDL_PIXELFORMAT_BGRA8888, offsetof(struct spa_type_video_format, BGRA),}, {
+ SDL_PIXELFORMAT_ABGR8888, offsetof(struct spa_type_video_format, ABGR),}, {
+ SDL_PIXELFORMAT_YV12, offsetof(struct spa_type_video_format, YV12),}, {
+ SDL_PIXELFORMAT_IYUV, offsetof(struct spa_type_video_format, I420),}, {
+ SDL_PIXELFORMAT_YUY2, offsetof(struct spa_type_video_format, YUY2),}, {
+ SDL_PIXELFORMAT_UYVY, offsetof(struct spa_type_video_format, UYVY),}, {
+ SDL_PIXELFORMAT_YVYU, offsetof(struct spa_type_video_format, YVYU),}, {
+ SDL_PIXELFORMAT_NV12, offsetof(struct spa_type_video_format, NV12),}, {
+SDL_PIXELFORMAT_NV21, offsetof(struct spa_type_video_format, NV21),}};
+
+static uint32_t sdl_format_to_id(struct data *data, Uint32 format)
+{
+ int i;
+
+ for (i = 0; i < SPA_N_ELEMENTS(video_formats); i++) {
+ if (video_formats[i].format == format)
+ return *SPA_MEMBER(&data->type.video_format, video_formats[i].id, uint32_t);
+ }
+ return data->type.video_format.UNKNOWN;
+}
+
+static Uint32 id_to_sdl_format(struct data *data, uint32_t id)
+{
+ int i;
+
+ for (i = 0; i < SPA_N_ELEMENTS(video_formats); i++) {
+ if (*SPA_MEMBER(&data->type.video_format, video_formats[i].id, uint32_t) == id)
+ return video_formats[i].format;
+ }
+ return SDL_PIXELFORMAT_UNKNOWN;
+}
+
+#define PROP(f,key,type,...) \
+ SPA_POD_PROP (f,key,0,type,1,__VA_ARGS__)
+#define PROP_U_MM(f,key,type,...) \
+ SPA_POD_PROP (f,key,SPA_POD_PROP_FLAG_UNSET | \
+ SPA_POD_PROP_RANGE_MIN_MAX,type,3,__VA_ARGS__)
+
+static void
+on_stream_format_changed(struct pw_listener *listener,
+ struct pw_stream *stream, struct spa_format *format)
+{
+ struct data *data = SPA_CONTAINER_OF(listener, struct data, on_stream_format_changed);
+ struct pw_remote *remote = stream->remote;
+ struct pw_core *core = remote->core;
+ struct spa_pod_builder b = { NULL };
+ struct spa_pod_frame f[2];
+ struct spa_param *params[2];
+ Uint32 sdl_format;
+ void *d;
+
+ if (format == NULL) {
+ pw_stream_finish_format(stream, SPA_RESULT_OK, NULL, 0);
+ return;
+ }
+
+ spa_debug_format(format);
+
+ spa_format_video_raw_parse(format, &data->format, &data->type.format_video);
+
+ sdl_format = id_to_sdl_format(data, data->format.format);
+ if (sdl_format == SDL_PIXELFORMAT_UNKNOWN) {
+ pw_stream_finish_format(stream, SPA_RESULT_ERROR, NULL, 0);
+ return;
+ }
+
+ data->texture = SDL_CreateTexture(data->renderer,
+ sdl_format,
+ SDL_TEXTUREACCESS_STREAMING,
+ data->format.size.width,
+ data->format.size.height);
+ SDL_LockTexture(data->texture, NULL, &d, &data->stride);
+ SDL_UnlockTexture(data->texture);
+
+ spa_pod_builder_init(&b, data->params_buffer, sizeof(data->params_buffer));
+ spa_pod_builder_object(&b, &f[0], 0, core->type.param_alloc_buffers.Buffers,
+ PROP(&f[1], core->type.param_alloc_buffers.size, SPA_POD_TYPE_INT,
+ data->stride * data->format.size.height),
+ PROP(&f[1], core->type.param_alloc_buffers.stride, SPA_POD_TYPE_INT,
+ data->stride),
+ PROP_U_MM(&f[1], core->type.param_alloc_buffers.buffers, SPA_POD_TYPE_INT,
+ 32,
+ 2, 32),
+ PROP(&f[1], core->type.param_alloc_buffers.align, SPA_POD_TYPE_INT,
+ 16));
+ params[0] = SPA_POD_BUILDER_DEREF(&b, f[0].ref, struct spa_param);
+
+ spa_pod_builder_object(&b, &f[0], 0, core->type.param_alloc_meta_enable.MetaEnable,
+ PROP(&f[1], core->type.param_alloc_meta_enable.type, SPA_POD_TYPE_ID,
+ core->type.meta.Header),
+ PROP(&f[1], core->type.param_alloc_meta_enable.size, SPA_POD_TYPE_INT,
+ sizeof(struct spa_meta_header)));
+ params[1] = SPA_POD_BUILDER_DEREF(&b, f[0].ref, struct spa_param);
+
+ pw_stream_finish_format(stream, SPA_RESULT_OK, params, 2);
+}
+
+static void on_state_changed(struct pw_listener *listener, struct pw_remote *remote)
+{
+ struct data *data = SPA_CONTAINER_OF(listener, struct data, on_state_changed);
+
+ switch (remote->state) {
+ case PW_REMOTE_STATE_ERROR:
+ printf("remote error: %s\n", remote->error);
+ data->running = false;
+ break;
+
+ case PW_REMOTE_STATE_CONNECTED:
+ {
+ const struct spa_format *formats[1];
+ uint8_t buffer[1024];
+ struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
+ struct spa_pod_frame f[2];
+ SDL_RendererInfo info;
+ int i, c;
+
+ printf("remote state: \"%s\"\n",
+ pw_remote_state_as_string(remote->state));
+
+ data->stream = pw_stream_new(remote, "video-play", NULL);
+
+ SDL_GetRendererInfo(data->renderer, &info);
+
+ spa_pod_builder_push_format(&b, &f[0], data->type.format,
+ data->type.media_type.video,
+ data->type.media_subtype.raw);
+
+ spa_pod_builder_push_prop(&b, &f[1], data->type.format_video.format,
+ SPA_POD_PROP_FLAG_UNSET |
+ SPA_POD_PROP_RANGE_ENUM);
+ for (i = 0, c = 0; i < info.num_texture_formats; i++) {
+ uint32_t id = sdl_format_to_id(data, info.texture_formats[i]);
+ if (id == 0)
+ continue;
+ if (c++ == 0)
+ spa_pod_builder_id(&b, id);
+ spa_pod_builder_id(&b, id);
+ }
+ for (i = 0; i < SPA_N_ELEMENTS(video_formats); i++) {
+ uint32_t id =
+ *SPA_MEMBER(&data->type.video_format, video_formats[i].id,
+ uint32_t);
+ if (id != data->type.video_format.UNKNOWN)
+ spa_pod_builder_id(&b, id);
+ }
+ spa_pod_builder_pop(&b, &f[1]);
+ spa_pod_builder_add(&b,
+ PROP_U_MM(&f[1], data->type.format_video.size, SPA_POD_TYPE_RECTANGLE,
+ WIDTH, HEIGHT,
+ 1, 1, info.max_texture_width, info.max_texture_height),
+ PROP_U_MM(&f[1], data->type.format_video.framerate, SPA_POD_TYPE_FRACTION,
+ 25, 1,
+ 0, 1, 30, 1),
+ 0);
+ spa_pod_builder_pop(&b, &f[0]);
+ formats[0] = SPA_POD_BUILDER_DEREF(&b, f[0].ref, struct spa_format);
+
+ printf("supported formats:\n");
+ spa_debug_format(formats[0]);
+
+ pw_signal_add(&data->stream->state_changed,
+ &data->on_stream_state_changed, on_stream_state_changed);
+ pw_signal_add(&data->stream->format_changed,
+ &data->on_stream_format_changed, on_stream_format_changed);
+ pw_signal_add(&data->stream->new_buffer,
+ &data->on_stream_new_buffer, on_stream_new_buffer);
+
+ pw_stream_connect(data->stream,
+ PW_DIRECTION_INPUT,
+ PW_STREAM_MODE_BUFFER,
+ data->path, PW_STREAM_FLAG_AUTOCONNECT, 1, formats);
+ break;
+ }
+ default:
+ printf("remote state: \"%s\"\n", pw_remote_state_as_string(remote->state));
+ break;
+ }
+}
+
+int main(int argc, char *argv[])
+{
+ struct data data = { 0, };
+
+ pw_init(&argc, &argv);
+
+ data.loop = pw_loop_new();
+ data.running = true;
+ data.core = pw_core_new(data.loop, NULL);
+ data.remote = pw_remote_new(data.core, NULL);
+ data.path = argc > 1 ? argv[1] : NULL;
+
+ init_type(&data.type, data.core->type.map);
+
+ spa_debug_set_type_map(data.core->type.map);
+
+ if (SDL_Init(SDL_INIT_VIDEO) < 0) {
+ printf("can't initialize SDL: %s\n", SDL_GetError());
+ return -1;
+ }
+
+ if (SDL_CreateWindowAndRenderer
+ (WIDTH, HEIGHT, SDL_WINDOW_RESIZABLE, &data.window, &data.renderer)) {
+ printf("can't create window: %s\n", SDL_GetError());
+ return -1;
+ }
+
+ pw_signal_add(&data.remote->state_changed, &data.on_state_changed, on_state_changed);
+
+ pw_remote_connect(data.remote);
+
+ pw_loop_enter(data.loop);
+ while (data.running) {
+ pw_loop_iterate(data.loop, -1);
+ }
+ pw_loop_leave(data.loop);
+
+ pw_remote_destroy(data.remote);
+ pw_loop_destroy(data.loop);
+
+ return 0;
+}
diff --git a/src/examples/video-src.c b/src/examples/video-src.c
new file mode 100644
index 00000000..a4d7234c
--- /dev/null
+++ b/src/examples/video-src.c
@@ -0,0 +1,296 @@
+/* PipeWire
+ * Copyright (C) 2017 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <stdio.h>
+#include <errno.h>
+#include <time.h>
+#include <sys/mman.h>
+
+#include <spa/type-map.h>
+#include <spa/format-utils.h>
+#include <spa/video/format-utils.h>
+#include <spa/format-builder.h>
+#include <spa/props.h>
+#include <spa/lib/debug.h>
+
+#include <pipewire/pipewire.h>
+#include <pipewire/sig.h>
+
+struct type {
+ uint32_t format;
+ uint32_t props;
+ struct spa_type_meta meta;
+ struct spa_type_data data;
+ struct spa_type_media_type media_type;
+ struct spa_type_media_subtype media_subtype;
+ struct spa_type_format_video format_video;
+ struct spa_type_video_format video_format;
+};
+
+static inline void init_type(struct type *type, struct spa_type_map *map)
+{
+ type->format = spa_type_map_get_id(map, SPA_TYPE__Format);
+ type->props = spa_type_map_get_id(map, SPA_TYPE__Props);
+ spa_type_meta_map(map, &type->meta);
+ spa_type_data_map(map, &type->data);
+ spa_type_media_type_map(map, &type->media_type);
+ spa_type_media_subtype_map(map, &type->media_subtype);
+ spa_type_format_video_map(map, &type->format_video);
+ spa_type_video_format_map(map, &type->video_format);
+}
+
+#define BPP 3
+
+struct data {
+ struct type type;
+
+ bool running;
+ struct pw_loop *loop;
+ struct spa_source *timer;
+
+ struct pw_core *core;
+ struct pw_remote *remote;
+ struct pw_listener on_state_changed;
+
+ struct pw_stream *stream;
+ struct pw_listener on_stream_state_changed;
+ struct pw_listener on_stream_format_changed;
+
+ struct spa_video_info_raw format;
+ int32_t stride;
+
+ uint8_t params_buffer[1024];
+ int counter;
+ uint32_t seq;
+};
+
+static void on_timeout(struct spa_loop_utils *utils, struct spa_source *source, void *userdata)
+{
+ struct data *data = userdata;
+ uint32_t id;
+ struct spa_buffer *buf;
+ int i, j;
+ uint8_t *p, *map;
+ struct spa_meta_header *h;
+
+ id = pw_stream_get_empty_buffer(data->stream);
+ if (id == SPA_ID_INVALID)
+ return;
+
+ buf = pw_stream_peek_buffer(data->stream, id);
+
+ if (buf->datas[0].type == data->type.data.MemFd) {
+ map =
+ mmap(NULL, buf->datas[0].maxsize + buf->datas[0].mapoffset,
+ PROT_READ | PROT_WRITE, MAP_SHARED, buf->datas[0].fd, 0);
+ if (map == MAP_FAILED) {
+ printf("failed to mmap: %s\n", strerror(errno));
+ return;
+ }
+ p = SPA_MEMBER(map, buf->datas[0].mapoffset, uint8_t);
+ } else if (buf->datas[0].type == data->type.data.MemPtr) {
+ map = NULL;
+ p = buf->datas[0].data;
+ } else
+ return;
+
+ if ((h = spa_buffer_find_meta(buf, data->type.meta.Header))) {
+#if 0
+ struct timespec now;
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ h->pts = SPA_TIMESPEC_TO_TIME(&now);
+#else
+ h->pts = -1;
+#endif
+ h->flags = 0;
+ h->seq = data->seq++;
+ h->dts_offset = 0;
+ }
+
+ for (i = 0; i < data->format.size.height; i++) {
+ for (j = 0; j < data->format.size.width * BPP; j++) {
+ p[j] = data->counter + j * i;
+ }
+ p += buf->datas[0].chunk->stride;
+ data->counter += 13;
+ }
+
+ if (map)
+ munmap(map, buf->datas[0].maxsize + buf->datas[0].mapoffset);
+
+ pw_stream_send_buffer(data->stream, id);
+}
+
+static void on_stream_state_changed(struct pw_listener *listener, struct pw_stream *stream)
+{
+ struct data *data = SPA_CONTAINER_OF(listener, struct data, on_stream_state_changed);
+
+ printf("stream state: \"%s\"\n", pw_stream_state_as_string(stream->state));
+
+ switch (stream->state) {
+ case PW_STREAM_STATE_PAUSED:
+ pw_loop_update_timer(data->loop, data->timer, NULL, NULL, false);
+ break;
+
+ case PW_STREAM_STATE_STREAMING:
+ {
+ struct timespec timeout, interval;
+
+ timeout.tv_sec = 0;
+ timeout.tv_nsec = 1;
+ interval.tv_sec = 0;
+ interval.tv_nsec = 40 * SPA_NSEC_PER_MSEC;
+
+ pw_loop_update_timer(data->loop, data->timer, &timeout, &interval, false);
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+#define PROP(f,key,type,...) \
+ SPA_POD_PROP (f,key,0,type,1,__VA_ARGS__)
+#define PROP_U_MM(f,key,type,...) \
+ SPA_POD_PROP (f,key,SPA_POD_PROP_FLAG_UNSET | \
+ SPA_POD_PROP_RANGE_MIN_MAX,type,3,__VA_ARGS__)
+
+static void
+on_stream_format_changed(struct pw_listener *listener,
+ struct pw_stream *stream, struct spa_format *format)
+{
+ struct data *data = SPA_CONTAINER_OF(listener, struct data, on_stream_format_changed);
+ struct pw_remote *remote = stream->remote;
+ struct pw_core *core = remote->core;
+ struct spa_pod_builder b = { NULL };
+ struct spa_pod_frame f[2];
+ struct spa_param *params[2];
+
+ if (format == NULL) {
+ pw_stream_finish_format(stream, SPA_RESULT_OK, NULL, 0);
+ return;
+ }
+ spa_format_video_raw_parse(format, &data->format, &data->type.format_video);
+
+ data->stride = SPA_ROUND_UP_N(data->format.size.width * BPP, 4);
+
+ spa_pod_builder_init(&b, data->params_buffer, sizeof(data->params_buffer));
+ spa_pod_builder_object(&b, &f[0], 0, core->type.param_alloc_buffers.Buffers,
+ PROP(&f[1], core->type.param_alloc_buffers.size, SPA_POD_TYPE_INT,
+ data->stride * data->format.size.height),
+ PROP(&f[1], core->type.param_alloc_buffers.stride, SPA_POD_TYPE_INT,
+ data->stride),
+ PROP_U_MM(&f[1], core->type.param_alloc_buffers.buffers, SPA_POD_TYPE_INT,
+ 32,
+ 2, 32),
+ PROP(&f[1], core->type.param_alloc_buffers.align, SPA_POD_TYPE_INT,
+ 16));
+ params[0] = SPA_POD_BUILDER_DEREF(&b, f[0].ref, struct spa_param);
+
+ spa_pod_builder_object(&b, &f[0], 0, core->type.param_alloc_meta_enable.MetaEnable,
+ PROP(&f[1], core->type.param_alloc_meta_enable.type, SPA_POD_TYPE_ID,
+ core->type.meta.Header),
+ PROP(&f[1], core->type.param_alloc_meta_enable.size, SPA_POD_TYPE_INT,
+ sizeof(struct spa_meta_header)));
+ params[1] = SPA_POD_BUILDER_DEREF(&b, f[0].ref, struct spa_param);
+
+ pw_stream_finish_format(stream, SPA_RESULT_OK, params, 2);
+}
+
+static void on_state_changed(struct pw_listener *listener, struct pw_remote *remote)
+{
+ struct data *data = SPA_CONTAINER_OF(listener, struct data, on_state_changed);
+
+ switch (remote->state) {
+ case PW_REMOTE_STATE_ERROR:
+ printf("remote error: %s\n", remote->error);
+ data->running = false;
+ break;
+
+ case PW_REMOTE_STATE_CONNECTED:
+ {
+ const struct spa_format *formats[1];
+ uint8_t buffer[1024];
+ struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
+ struct spa_pod_frame f[2];
+
+ printf("remote state: \"%s\"\n",
+ pw_remote_state_as_string(remote->state));
+
+ data->stream = pw_stream_new(remote, "video-src", NULL);
+
+ spa_pod_builder_format(&b, &f[0], data->type.format,
+ data->type.media_type.video,
+ data->type.media_subtype.raw,
+ PROP(&f[1], data->type.format_video.format, SPA_POD_TYPE_ID,
+ data->type.video_format.RGB),
+ PROP_U_MM(&f[1], data->type.format_video.size, SPA_POD_TYPE_RECTANGLE,
+ 320, 240,
+ 1, 1, 4096, 4096),
+ PROP(&f[1], data->type.format_video.framerate, SPA_POD_TYPE_FRACTION,
+ 25, 1));
+ formats[0] = SPA_POD_BUILDER_DEREF(&b, f[0].ref, struct spa_format);
+
+ pw_signal_add(&data->stream->state_changed,
+ &data->on_stream_state_changed, on_stream_state_changed);
+ pw_signal_add(&data->stream->format_changed,
+ &data->on_stream_format_changed, on_stream_format_changed);
+
+ pw_stream_connect(data->stream,
+ PW_DIRECTION_OUTPUT,
+ PW_STREAM_MODE_BUFFER,
+ NULL, PW_STREAM_FLAG_NONE, 1, formats);
+ break;
+ }
+ default:
+ printf("remote state: \"%s\"\n", pw_remote_state_as_string(remote->state));
+ break;
+ }
+}
+
+int main(int argc, char *argv[])
+{
+ struct data data = { 0, };
+
+ pw_init(&argc, &argv);
+
+ data.loop = pw_loop_new();
+ data.running = true;
+ data.core = pw_core_new(data.loop, NULL);
+ data.remote = pw_remote_new(data.core, NULL);
+
+ init_type(&data.type, data.core->type.map);
+
+ data.timer = pw_loop_add_timer(data.loop, on_timeout, &data);
+
+ pw_signal_add(&data.remote->state_changed, &data.on_state_changed, on_state_changed);
+
+ pw_remote_connect(data.remote);
+
+ pw_loop_enter(data.loop);
+ while (data.running) {
+ pw_loop_iterate(data.loop, -1);
+ }
+ pw_loop_leave(data.loop);
+
+ pw_remote_destroy(data.remote);
+ pw_loop_destroy(data.loop);
+
+ return 0;
+}
diff --git a/src/extensions/client-node.h b/src/extensions/client-node.h
new file mode 100644
index 00000000..445d9de3
--- /dev/null
+++ b/src/extensions/client-node.h
@@ -0,0 +1,299 @@
+/* PipeWire
+ * Copyright (C) 2016 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __PIPEWIRE_EXT_CLIENT_NODE_H__
+#define __PIPEWIRE_EXT_CLIENT_NODE_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/defs.h>
+#include <spa/props.h>
+#include <spa/format.h>
+#include <spa/param-alloc.h>
+#include <spa/node.h>
+
+#define PIPEWIRE_TYPE__ClientNode PIPEWIRE_TYPE_NODE_BASE "Client"
+#define PIPEWIRE_TYPE_CLIENT_NODE_BASE PIPEWIRE_TYPE__ClientNode ":"
+
+/** 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_VERSION_CLIENT_NODE 0
+
+#define PW_CLIENT_NODE_METHOD_DONE 0
+#define PW_CLIENT_NODE_METHOD_UPDATE 1
+#define PW_CLIENT_NODE_METHOD_PORT_UPDATE 2
+#define PW_CLIENT_NODE_METHOD_EVENT 3
+#define PW_CLIENT_NODE_METHOD_DESTROY 4
+#define PW_CLIENT_NODE_METHOD_NUM 5
+
+/** \ref pw_client_node methods */
+struct pw_client_node_methods {
+ /** Complete an async operation */
+ void (*done) (void *object, int seq, int res);
+
+ /**
+ * Update the node ports and properties
+ *
+ * Update the maximum number of ports and the properties 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 props new properties
+ */
+ void (*update) (void *object,
+#define PW_CLIENT_NODE_UPDATE_MAX_INPUTS (1 << 0)
+#define PW_CLIENT_NODE_UPDATE_MAX_OUTPUTS (1 << 1)
+#define PW_CLIENT_NODE_UPDATE_PROPS (1 << 2)
+ uint32_t change_mask,
+ uint32_t max_input_ports,
+ uint32_t max_output_ports,
+ const struct spa_props *props);
+
+ /**
+ * 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_possible_formats number of possible formats
+ * \param possible_formats array of possible formats on the port
+ * \param format the current format on the port
+ * \param n_params number of port parameters
+ * \param params array of port parameters
+ * \param info port information
+ */
+ void (*port_update) (void *object,
+ enum spa_direction direction,
+ uint32_t port_id,
+#define PW_CLIENT_NODE_PORT_UPDATE_POSSIBLE_FORMATS (1 << 0)
+#define PW_CLIENT_NODE_PORT_UPDATE_FORMAT (1 << 1)
+#define PW_CLIENT_NODE_PORT_UPDATE_PARAMS (1 << 2)
+#define PW_CLIENT_NODE_PORT_UPDATE_INFO (1 << 3)
+ uint32_t change_mask,
+ uint32_t n_possible_formats,
+ const struct spa_format **possible_formats,
+ const struct spa_format *format,
+ uint32_t n_params,
+ const struct spa_param **params,
+ const struct spa_port_info *info);
+ /**
+ * Send an event to the node
+ * \param event the event to send
+ */
+ void (*event) (void *object, struct spa_event *event);
+ /**
+ * Destroy the client_node
+ */
+ void (*destroy) (void *object);
+};
+
+#define pw_client_node_do_done(p,...) pw_proxy_do(p,struct pw_client_node_methods,done,__VA_ARGS__)
+#define pw_client_node_do_update(p,...) pw_proxy_do(p,struct pw_client_node_methods,update,__VA_ARGS__)
+#define pw_client_node_do_port_update(p,...) pw_proxy_do(p,struct pw_client_node_methods,port_update,__VA_ARGS__)
+#define pw_client_node_do_event(p,...) pw_proxy_do(p,struct pw_client_node_methods,event,__VA_ARGS__)
+#define pw_client_node_do_destroy(p) pw_proxy_do_na(p,struct pw_client_node_methods,destroy)
+
+#define PW_CLIENT_NODE_EVENT_TRANSPORT 0
+#define PW_CLIENT_NODE_EVENT_SET_PROPS 1
+#define PW_CLIENT_NODE_EVENT_EVENT 2
+#define PW_CLIENT_NODE_EVENT_ADD_PORT 3
+#define PW_CLIENT_NODE_EVENT_REMOVE_PORT 4
+#define PW_CLIENT_NODE_EVENT_SET_FORMAT 5
+#define PW_CLIENT_NODE_EVENT_SET_PARAM 6
+#define PW_CLIENT_NODE_EVENT_ADD_MEM 7
+#define PW_CLIENT_NODE_EVENT_USE_BUFFERS 8
+#define PW_CLIENT_NODE_EVENT_NODE_COMMAND 9
+#define PW_CLIENT_NODE_EVENT_PORT_COMMAND 10
+#define PW_CLIENT_NODE_EVENT_NUM 11
+
+/** \ref pw_client_node events */
+struct pw_client_node_events {
+ /**
+ * Notify of a new transport area
+ *
+ * The transport area is used to exchange real-time commands between
+ * the client and the server.
+ *
+ * \param node_id the node id created for this client node
+ * \param readfd fd for signal data can be read
+ * \param writefd fd for signal data can be written
+ * \param memfd the memory fd of the area
+ * \param offset the offset to map
+ * \param size the size to map
+ */
+ void (*transport) (void *object,
+ uint32_t node_id,
+ int readfd,
+ int writefd,
+ int memfd,
+ 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 seq a sequence number
+ * \param props the props to set
+ */
+ void (*set_props) (void *object,
+ uint32_t seq,
+ const struct spa_props *props);
+ /**
+ * Receive an event from the client node
+ * \param event the received event */
+ void (*event) (void *object, const struct spa_event *event);
+ /**
+ * 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 seq a sequence number
+ * \param direction the direction of the port
+ * \param port_id the new port id
+ */
+ void (*add_port) (void *object,
+ uint32_t seq,
+ enum spa_direction direction,
+ uint32_t port_id);
+ /**
+ * A port was removed from the node
+ *
+ * \param seq a sequence number
+ * \param direction a port direction
+ * \param port_id the remove port id
+ */
+ void (*remove_port) (void *object,
+ uint32_t seq,
+ enum spa_direction direction,
+ uint32_t port_id);
+ /**
+ * A format was configured on the port
+ *
+ * \param seq a sequence number
+ * \param direction a port direction
+ * \param port_id the port id
+ * \param flags flags used when setting the format
+ * \param format the new format
+ */
+ void (*set_format) (void *object,
+ uint32_t seq,
+ enum spa_direction direction,
+ uint32_t port_id,
+ uint32_t flags,
+ const struct spa_format *format);
+ /**
+ * A parameter was configured on the port
+ *
+ * \param seq a sequence number
+ * \param direction a port direction
+ * \param port_id the port id
+ * \param param the new param
+ */
+ void (*set_param) (void *object,
+ uint32_t seq,
+ enum spa_direction direction,
+ uint32_t port_id,
+ const struct spa_param *param);
+ /**
+ * Memory was added for a port
+ *
+ * \param direction a port direction
+ * \param port_id the port id
+ * \param mem_id the id of the memory
+ * \param type the memory type
+ * \param memfd the fd of the memory
+ * \param flags flags for the \a memfd
+ * \param offset valid offset of mapped memory from \a memfd
+ * \param size valid size of mapped memory from \a memfd
+ */
+ void (*add_mem) (void *object,
+ enum spa_direction direction,
+ uint32_t port_id,
+ uint32_t mem_id,
+ uint32_t type,
+ int memfd,
+ uint32_t flags,
+ uint32_t offset,
+ uint32_t size);
+ /**
+ * Notify the port of buffers
+ *
+ * \param seq a sequence number
+ * \param direction a port direction
+ * \param port_id the port id
+ * \param n_buffer the number of buffers
+ * \param buffers and array of buffer descriptions
+ */
+ void (*use_buffers) (void *object,
+ uint32_t seq,
+ enum spa_direction direction,
+ uint32_t port_id,
+ uint32_t n_buffers,
+ struct pw_client_node_buffer *buffers);
+ /**
+ * Notify of a new node command
+ *
+ * \param seq a sequence number
+ * \param command the command
+ */
+ void (*node_command) (void *object, uint32_t seq, const struct spa_command *command);
+ /**
+ * Notify of a new port command
+ *
+ * \param direction a port direction
+ * \param port_id the port id
+ * \param command the command
+ */
+ void (*port_command) (void *object,
+ enum spa_direction direction,
+ uint32_t port_id,
+ const struct spa_command *command);
+
+};
+
+#define pw_client_node_notify_transport(r,...) pw_resource_notify(r,struct pw_client_node_events,transport,__VA_ARGS__)
+#define pw_client_node_notify_set_props(r,...) pw_resource_notify(r,struct pw_client_node_events,props,__VA_ARGS__)
+#define pw_client_node_notify_event(r,...) pw_resource_notify(r,struct pw_client_node_events,event,__VA_ARGS__)
+#define pw_client_node_notify_add_port(r,...) pw_resource_notify(r,struct pw_client_node_events,add_port,__VA_ARGS__)
+#define pw_client_node_notify_remove_port(r,...) pw_resource_notify(r,struct pw_client_node_events,remove_port,__VA_ARGS__)
+#define pw_client_node_notify_set_format(r,...) pw_resource_notify(r,struct pw_client_node_events,set_format,__VA_ARGS__)
+#define pw_client_node_notify_set_param(r,...) pw_resource_notify(r,struct pw_client_node_events,set_param,__VA_ARGS__)
+#define pw_client_node_notify_add_mem(r,...) pw_resource_notify(r,struct pw_client_node_events,add_mem,__VA_ARGS__)
+#define pw_client_node_notify_use_buffers(r,...) pw_resource_notify(r,struct pw_client_node_events,use_buffers,__VA_ARGS__)
+#define pw_client_node_notify_node_command(r,...) pw_resource_notify(r,struct pw_client_node_events,node_command,__VA_ARGS__)
+#define pw_client_node_notify_port_command(r,...) pw_resource_notify(r,struct pw_client_node_events,port_command,__VA_ARGS__)
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* __PIPEWIRE_EXT_CLIENT_NODE_H__ */
diff --git a/src/extensions/meson.build b/src/extensions/meson.build
new file mode 100644
index 00000000..ca784be2
--- /dev/null
+++ b/src/extensions/meson.build
@@ -0,0 +1,5 @@
+pipewire_ext_headers = [
+ 'client-node.h',
+]
+
+install_headers(pipewire_ext_headers, subdir : 'pipewire/extensions')
diff --git a/src/gst/gstpipewire.c b/src/gst/gstpipewire.c
new file mode 100644
index 00000000..b77640fd
--- /dev/null
+++ b/src/gst/gstpipewire.c
@@ -0,0 +1,64 @@
+/* GStreamer
+ * Copyright (C) <2015> Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/**
+ * SECTION:element-pipewiresrc
+ *
+ * <refsect2>
+ * <title>Example launch line</title>
+ * |[
+ * gst-launch -v pipewiresrc ! ximagesink
+ * ]| Shows PipeWire output in an X window.
+ * </refsect2>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gstpipewiresrc.h"
+#include "gstpipewiresink.h"
+#include "gstpipewiredeviceprovider.h"
+
+GST_DEBUG_CATEGORY (pipewire_debug);
+
+static gboolean
+plugin_init (GstPlugin *plugin)
+{
+ pw_init (NULL, NULL);
+
+ gst_element_register (plugin, "pipewiresrc", GST_RANK_PRIMARY + 1,
+ GST_TYPE_PIPEWIRE_SRC);
+ gst_element_register (plugin, "pipewiresink", GST_RANK_NONE,
+ GST_TYPE_PIPEWIRE_SINK);
+
+ if (!gst_device_provider_register (plugin, "pipewiredeviceprovider",
+ GST_RANK_PRIMARY + 1, GST_TYPE_PIPEWIRE_DEVICE_PROVIDER))
+ return FALSE;
+
+ GST_DEBUG_CATEGORY_INIT (pipewire_debug, "pipewire", 0, "PipeWirie elements");
+
+ return TRUE;
+}
+
+GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
+ GST_VERSION_MINOR,
+ pipewire,
+ "Uses PipeWire to handle media streams",
+ plugin_init, VERSION, "LGPL", "pipewire", "pipewire.org")
diff --git a/src/gst/gstpipewireclock.c b/src/gst/gstpipewireclock.c
new file mode 100644
index 00000000..a82631ef
--- /dev/null
+++ b/src/gst/gstpipewireclock.c
@@ -0,0 +1,92 @@
+/* GStreamer
+ * Copyright (C) 2016 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Suite 500,
+ * Boston, MA 02110-1335, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <gst/gst.h>
+
+#include "gstpipewireclock.h"
+
+GST_DEBUG_CATEGORY_STATIC (gst_pipewire_clock_debug_category);
+#define GST_CAT_DEFAULT gst_pipewire_clock_debug_category
+
+G_DEFINE_TYPE (GstPipeWireClock, gst_pipewire_clock, GST_TYPE_SYSTEM_CLOCK);
+
+GstClock *
+gst_pipewire_clock_new (struct pw_stream *stream)
+{
+ GstPipeWireClock *clock;
+
+ clock = g_object_new (GST_TYPE_PIPEWIRE_CLOCK, NULL);
+ clock->stream = stream;
+
+ return GST_CLOCK_CAST (clock);
+}
+
+static GstClockTime
+gst_pipewire_clock_get_internal_time (GstClock * clock)
+{
+ GstPipeWireClock *pclock = (GstPipeWireClock *) clock;
+ GstClockTime result;
+ struct pw_time t;
+
+ pw_stream_get_time (pclock->stream, &t);
+
+ if (t.rate)
+ result = gst_util_uint64_scale_int (t.ticks, GST_SECOND, t.rate);
+ else
+ result = GST_CLOCK_TIME_NONE;
+
+ GST_DEBUG ("%"PRId64", %d %"PRId64, t.ticks, t.rate, result);
+
+ return result;
+}
+
+
+static void
+gst_pipewire_clock_finalize (GObject * object)
+{
+ GstPipeWireClock *clock = GST_PIPEWIRE_CLOCK (object);
+
+ GST_DEBUG_OBJECT (clock, "finalize");
+
+ G_OBJECT_CLASS (gst_pipewire_clock_parent_class)->finalize (object);
+}
+
+static void
+gst_pipewire_clock_class_init (GstPipeWireClockClass * klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ GstClockClass *gstclock_class = GST_CLOCK_CLASS (klass);
+
+ gobject_class->finalize = gst_pipewire_clock_finalize;
+
+ gstclock_class->get_internal_time = gst_pipewire_clock_get_internal_time;
+
+ GST_DEBUG_CATEGORY_INIT (gst_pipewire_clock_debug_category, "pipewireclock", 0,
+ "debug category for pipewireclock object");
+}
+
+static void
+gst_pipewire_clock_init (GstPipeWireClock * clock)
+{
+ GST_OBJECT_FLAG_SET (clock, GST_CLOCK_FLAG_CAN_SET_MASTER);
+}
diff --git a/src/gst/gstpipewireclock.h b/src/gst/gstpipewireclock.h
new file mode 100644
index 00000000..3020c83c
--- /dev/null
+++ b/src/gst/gstpipewireclock.h
@@ -0,0 +1,62 @@
+/* GStreamer
+ * Copyright (C) <2016> Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __GST_PIPEWIRE_CLOCK_H__
+#define __GST_PIPEWIRE_CLOCK_H__
+
+#include <gst/gst.h>
+
+#include <pipewire/pipewire.h>
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_PIPEWIRE_CLOCK \
+ (gst_pipewire_clock_get_type())
+#define GST_PIPEWIRE_CLOCK(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_PIPEWIRE_CLOCK,GstPipeWireClock))
+#define GST_PIPEWIRE_CLOCK_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_PIPEWIRE_CLOCK,GstPipeWireClockClass))
+#define GST_IS_PIPEWIRE_CLOCK(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_PIPEWIRE_CLOCK))
+#define GST_IS_PIPEWIRE_CLOCK_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_PIPEWIRE_CLOCK))
+#define GST_PIPEWIRE_CLOCK_GET_CLASS(klass) \
+ (G_TYPE_INSTANCE_GET_CLASS ((klass), GST_TYPE_PIPEWIRE_CLOCK, GstPipeWireClockClass))
+
+typedef struct _GstPipeWireClock GstPipeWireClock;
+typedef struct _GstPipeWireClockClass GstPipeWireClockClass;
+
+struct _GstPipeWireClock {
+ GstSystemClock parent;
+
+ struct pw_stream *stream;
+};
+
+struct _GstPipeWireClockClass {
+ GstSystemClockClass parent_class;
+};
+
+GType gst_pipewire_clock_get_type (void);
+
+GstClock * gst_pipewire_clock_new (struct pw_stream *stream);
+
+
+G_END_DECLS
+
+#endif /* __GST_PIPEWIRE_CLOCK_H__ */
diff --git a/src/gst/gstpipewiredeviceprovider.c b/src/gst/gstpipewiredeviceprovider.c
new file mode 100644
index 00000000..1af78463
--- /dev/null
+++ b/src/gst/gstpipewiredeviceprovider.c
@@ -0,0 +1,647 @@
+/* GStreamer
+ * Copyright (C) 2012 Olivier Crete <olivier.crete@collabora.com>
+ * (C) 2015 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * pipewiredeviceprovider.c: PipeWire device probing and monitoring
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <string.h>
+
+#include <gst/gst.h>
+
+#include "gstpipewireformat.h"
+#include "gstpipewiredeviceprovider.h"
+#include "gstpipewiresrc.h"
+#include "gstpipewiresink.h"
+
+GST_DEBUG_CATEGORY_EXTERN (pipewire_debug);
+#define GST_CAT_DEFAULT pipewire_debug
+
+G_DEFINE_TYPE (GstPipeWireDevice, gst_pipewire_device, GST_TYPE_DEVICE);
+
+enum
+{
+ PROP_ID = 1,
+};
+
+static GstDevice *
+gst_pipewire_device_new (uint32_t id, const gchar * device_name,
+ GstCaps * caps, const gchar *klass,
+ GstPipeWireDeviceType type, GstStructure *props)
+{
+ GstPipeWireDevice *gstdev;
+ const gchar *element = NULL;
+
+ g_return_val_if_fail (device_name, NULL);
+ g_return_val_if_fail (caps, NULL);
+
+ switch (type) {
+ case GST_PIPEWIRE_DEVICE_TYPE_SOURCE:
+ element = "pipewiresrc";
+ break;
+ case GST_PIPEWIRE_DEVICE_TYPE_SINK:
+ element = "pipewiresink";
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ gstdev = g_object_new (GST_TYPE_PIPEWIRE_DEVICE,
+ "display-name", device_name, "caps", caps, "device-class", klass,
+ "id", id, "properties", props, NULL);
+
+ gstdev->id = id;
+ gstdev->type = type;
+ gstdev->element = element;
+
+ return GST_DEVICE (gstdev);
+}
+
+static GstElement *
+gst_pipewire_device_create_element (GstDevice * device, const gchar * name)
+{
+ GstPipeWireDevice *pipewire_dev = GST_PIPEWIRE_DEVICE (device);
+ GstElement *elem;
+ gchar *str;
+
+ elem = gst_element_factory_make (pipewire_dev->element, name);
+ str = g_strdup_printf ("%u", pipewire_dev->id);
+ g_object_set (elem, "path", str, NULL);
+ g_free (str);
+
+ return elem;
+}
+
+static gboolean
+gst_pipewire_device_reconfigure_element (GstDevice * device, GstElement * element)
+{
+ GstPipeWireDevice *pipewire_dev = GST_PIPEWIRE_DEVICE (device);
+ gchar *str;
+
+ if (!strcmp (pipewire_dev->element, "pipewiresrc")) {
+ if (!GST_IS_PIPEWIRE_SRC (element))
+ return FALSE;
+ } else if (!strcmp (pipewire_dev->element, "pipewiresink")) {
+ if (!GST_IS_PIPEWIRE_SINK (element))
+ return FALSE;
+ } else {
+ g_assert_not_reached ();
+ }
+
+ str = g_strdup_printf ("%u", pipewire_dev->id);
+ g_object_set (element, "path", str, NULL);
+ g_free (str);
+
+ return TRUE;
+}
+
+
+static void
+gst_pipewire_device_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec)
+{
+ GstPipeWireDevice *device;
+
+ device = GST_PIPEWIRE_DEVICE_CAST (object);
+
+ switch (prop_id) {
+ case PROP_ID:
+ g_value_set_uint (value, device->id);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_pipewire_device_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ GstPipeWireDevice *device;
+
+ device = GST_PIPEWIRE_DEVICE_CAST (object);
+
+ switch (prop_id) {
+ case PROP_ID:
+ device->id = g_value_get_uint (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_pipewire_device_finalize (GObject * object)
+{
+ G_OBJECT_CLASS (gst_pipewire_device_parent_class)->finalize (object);
+}
+
+static void
+gst_pipewire_device_class_init (GstPipeWireDeviceClass * klass)
+{
+ GstDeviceClass *dev_class = GST_DEVICE_CLASS (klass);
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ dev_class->create_element = gst_pipewire_device_create_element;
+ dev_class->reconfigure_element = gst_pipewire_device_reconfigure_element;
+
+ object_class->get_property = gst_pipewire_device_get_property;
+ object_class->set_property = gst_pipewire_device_set_property;
+ object_class->finalize = gst_pipewire_device_finalize;
+
+ g_object_class_install_property (object_class, PROP_ID,
+ g_param_spec_uint ("id", "Id",
+ "The internal id of the PipeWire device", 0, G_MAXUINT32, SPA_ID_INVALID,
+ G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+gst_pipewire_device_init (GstPipeWireDevice * device)
+{
+}
+
+G_DEFINE_TYPE (GstPipeWireDeviceProvider, gst_pipewire_device_provider,
+ GST_TYPE_DEVICE_PROVIDER);
+
+enum
+{
+ PROP_0,
+ PROP_CLIENT_NAME,
+ PROP_LAST
+};
+
+static GstDevice *
+new_node (GstPipeWireDeviceProvider *self, const struct pw_node_info *info)
+{
+ GstCaps *caps = NULL;
+ GstStructure *props;
+ const gchar *klass = NULL;
+ struct spa_dict_item *item;
+ GstPipeWireDeviceType type;
+ int i;
+
+ caps = gst_caps_new_empty ();
+ if (info->max_input_ports > 0 && info->max_output_ports == 0) {
+ type = GST_PIPEWIRE_DEVICE_TYPE_SINK;
+
+ for (i = 0; i < info->n_input_formats; i++) {
+ GstCaps *c1 = gst_caps_from_format (info->input_formats[i], self->core->type.map);
+ if (c1)
+ gst_caps_append (caps, c1);
+ }
+ }
+ else if (info->max_output_ports > 0 && info->max_input_ports == 0) {
+ type = GST_PIPEWIRE_DEVICE_TYPE_SOURCE;
+ for (i = 0; i < info->n_output_formats; i++) {
+ GstCaps *c1 = gst_caps_from_format (info->output_formats[i], self->core->type.map);
+ if (c1)
+ gst_caps_append (caps, c1);
+ }
+ } else {
+ gst_caps_unref(caps);
+ return NULL;
+ }
+
+ props = gst_structure_new_empty ("pipewire-proplist");
+ if (info->props) {
+ spa_dict_for_each (item, info->props)
+ gst_structure_set (props, item->key, G_TYPE_STRING, item->value, NULL);
+
+ klass = spa_dict_lookup (info->props, "media.class");
+ }
+ if (klass == NULL)
+ klass = "unknown/unknown";
+
+ return gst_pipewire_device_new (info->id,
+ info->name,
+ caps,
+ klass,
+ type,
+ props);
+}
+
+static GstPipeWireDevice *
+find_device (GstDeviceProvider *provider, uint32_t id)
+{
+ GList *item;
+ GstPipeWireDevice *dev = NULL;
+
+ GST_OBJECT_LOCK (provider);
+ for (item = provider->devices; item; item = item->next) {
+ dev = item->data;
+ if (dev->id == id) {
+ gst_object_ref (dev);
+ break;
+ }
+ dev = NULL;
+ }
+ GST_OBJECT_UNLOCK (provider);
+
+ return dev;
+}
+
+static void
+get_core_info (struct pw_remote *remote,
+ void *user_data)
+{
+ GstDeviceProvider *provider = user_data;
+ struct pw_core_info *info = remote->info;
+ const gchar *value;
+
+ if (info == NULL || info->props == NULL)
+ return;
+
+ value = spa_dict_lookup (info->props, "monitors");
+ if (value) {
+ gchar **monitors = g_strsplit (value, ",", -1);
+ gint i;
+
+ GST_DEBUG_OBJECT (provider, "have hidden providers: %s", value);
+
+ for (i = 0; monitors[i]; i++) {
+ if (strcmp (monitors[i], "v4l2") == 0)
+ gst_device_provider_hide_provider (provider, "v4l2deviceprovider");
+ else if (strcmp (monitors[i], "alsa") == 0)
+ gst_device_provider_hide_provider (provider, "pulsedeviceprovider");
+ }
+ g_strfreev (monitors);
+ }
+}
+
+static void
+on_sync_reply (struct pw_listener *listener, struct pw_remote *remote, uint32_t seq)
+{
+ GstPipeWireDeviceProvider *self = SPA_CONTAINER_OF (listener, GstPipeWireDeviceProvider, on_sync_reply);
+ if (seq == 1)
+ pw_core_do_sync(self->registry->remote->core_proxy, 2);
+ else if (seq == 2)
+ self->end = true;
+}
+
+static void node_event_info(void *object, struct pw_node_info *info)
+{
+ struct pw_proxy *proxy = object;
+ GstPipeWireDeviceProvider *self = proxy->object;
+ GstDevice *dev;
+
+ dev = new_node (self, info);
+ if (dev) {
+ if(self->list_only)
+ *self->devices = g_list_prepend (*self->devices, gst_object_ref_sink (dev));
+ else
+ gst_device_provider_device_add (GST_DEVICE_PROVIDER (self), dev);
+ }
+}
+
+static const struct pw_node_events node_events = {
+ &node_event_info
+};
+
+static void registry_event_global(void *object, uint32_t id, const char *type, uint32_t version)
+{
+ struct pw_proxy *registry = object;
+ GstPipeWireDeviceProvider *self = registry->object;
+ struct pw_remote *remote = registry->remote;
+ struct pw_core *core = remote->core;
+ struct pw_proxy *proxy = NULL;
+
+ if (strcmp(type, PIPEWIRE_TYPE__Node))
+ return;
+
+ proxy = pw_proxy_new(remote, SPA_ID_INVALID, core->type.node, 0);
+ if (proxy == NULL)
+ goto no_mem;
+
+ pw_proxy_set_implementation(proxy, self, PW_VERSION_NODE, &node_events, NULL);
+ pw_registry_do_bind(registry, id, version, proxy->id);
+ return;
+
+no_mem:
+ GST_ERROR_OBJECT(self, "failed to create proxy");
+ return;
+}
+
+static void registry_event_global_remove(void *object, uint32_t id)
+{
+ struct pw_proxy *registry = object;
+ GstPipeWireDeviceProvider *self = registry->object;
+ GstDeviceProvider *provider = GST_DEVICE_PROVIDER (self);
+ GstPipeWireDevice *dev;
+
+ dev = find_device (provider, id);
+ if (dev != NULL) {
+ gst_device_provider_device_remove (provider, GST_DEVICE (dev));
+ gst_object_unref (dev);
+ }
+}
+
+static const struct pw_registry_events registry_events = {
+ registry_event_global,
+ registry_event_global_remove,
+};
+
+static GList *
+gst_pipewire_device_provider_probe (GstDeviceProvider * provider)
+{
+ GstPipeWireDeviceProvider *self = GST_PIPEWIRE_DEVICE_PROVIDER (provider);
+ struct pw_loop *l = NULL;
+ struct pw_core *c = NULL;
+ struct pw_remote *r = NULL;
+ struct pw_proxy *reg = NULL;
+
+ GST_DEBUG_OBJECT (self, "starting probe");
+
+ if (!(l = pw_loop_new ()))
+ return NULL;
+
+ if (!(c = pw_core_new (l, NULL)))
+ return NULL;
+
+ if (!(r = pw_remote_new (c, NULL)))
+ goto failed;
+
+ pw_signal_add(&r->sync_reply, &self->on_sync_reply, on_sync_reply);
+
+ pw_remote_connect (r);
+
+ for (;;) {
+ enum pw_remote_state state;
+
+ state = r->state;
+
+ if (state <= 0) {
+ GST_ERROR_OBJECT (self, "Failed to connect: %s", r->error);
+ goto failed;
+ }
+
+ if (state == PW_REMOTE_STATE_CONNECTED)
+ break;
+
+ /* Wait until something happens */
+ pw_loop_iterate (l, -1);
+ }
+ GST_DEBUG_OBJECT (self, "connected");
+
+ get_core_info (r, self);
+
+ self->end = FALSE;
+ self->list_only = TRUE;
+ self->devices = NULL;
+
+ reg = pw_proxy_new(r, SPA_ID_INVALID, c->type.registry, 0);
+ pw_proxy_set_implementation(reg, self, PW_VERSION_REGISTRY, &registry_events, NULL);
+ pw_core_do_get_registry(r->core_proxy, reg->id);
+ pw_core_do_sync(r->core_proxy, 1);
+
+ for (;;) {
+ if (r->state <= 0)
+ break;
+ if (self->end)
+ break;
+ pw_loop_iterate (l, -1);
+ }
+
+ pw_remote_disconnect (r);
+ pw_remote_destroy (r);
+ pw_core_destroy (c);
+ pw_loop_destroy (l);
+
+ return *self->devices;
+
+failed:
+ pw_loop_destroy (l);
+ return NULL;
+}
+
+static void
+on_remote_state_changed (struct pw_listener *listener,
+ struct pw_remote *remote)
+{
+ GstPipeWireDeviceProvider *self = SPA_CONTAINER_OF (listener, GstPipeWireDeviceProvider, remote_state_changed);
+ enum pw_remote_state state;
+
+ state= remote->state;
+
+ GST_DEBUG ("got remote state %d", state);
+
+ switch (state) {
+ case PW_REMOTE_STATE_CONNECTING:
+ break;
+ case PW_REMOTE_STATE_UNCONNECTED:
+ case PW_REMOTE_STATE_CONNECTED:
+ break;
+ case PW_REMOTE_STATE_ERROR:
+ GST_ERROR_OBJECT (self, "remote error: %s", remote->error);
+ break;
+ }
+ pw_thread_loop_signal (self->main_loop, FALSE);
+}
+
+static gboolean
+gst_pipewire_device_provider_start (GstDeviceProvider * provider)
+{
+ GstPipeWireDeviceProvider *self = GST_PIPEWIRE_DEVICE_PROVIDER (provider);
+
+ GST_DEBUG_OBJECT (self, "starting provider");
+
+ self->loop = pw_loop_new ();
+ self->list_only = FALSE;
+
+ if (!(self->main_loop = pw_thread_loop_new (self->loop, "pipewire-device-monitor"))) {
+ GST_ERROR_OBJECT (self, "Could not create PipeWire mainloop");
+ goto failed_main_loop;
+ }
+
+ if (!(self->core = pw_core_new (self->loop, NULL))) {
+ GST_ERROR_OBJECT (self, "Could not create PipeWire core");
+ goto failed_core;
+ }
+
+ if (pw_thread_loop_start (self->main_loop) != SPA_RESULT_OK) {
+ GST_ERROR_OBJECT (self, "Could not start PipeWire mainloop");
+ goto failed_start;
+ }
+
+ pw_thread_loop_lock (self->main_loop);
+
+ if (!(self->remote = pw_remote_new (self->core, NULL))) {
+ GST_ERROR_OBJECT (self, "Failed to create remote");
+ goto failed_remote;
+ }
+
+ pw_signal_add (&self->remote->state_changed,
+ &self->remote_state_changed,
+ on_remote_state_changed);
+
+ pw_remote_connect (self->remote);
+ for (;;) {
+ enum pw_remote_state state;
+
+ state = self->remote->state;
+
+ if (state <= 0) {
+ GST_WARNING_OBJECT (self, "Failed to connect: %s", self->remote->error);
+ goto not_running;
+ }
+
+ if (state == PW_REMOTE_STATE_CONNECTED)
+ break;
+
+ /* Wait until something happens */
+ pw_thread_loop_wait (self->main_loop);
+ }
+ GST_DEBUG_OBJECT (self, "connected");
+ get_core_info (self->remote, self);
+
+ self->registry = pw_proxy_new(self->remote, SPA_ID_INVALID, self->core->type.registry, 0);
+ pw_proxy_set_implementation(self->registry, self, PW_VERSION_REGISTRY, &registry_events, NULL);
+ pw_core_do_get_registry(self->remote->core_proxy, self->registry->id);
+ pw_core_do_sync(self->remote->core_proxy, 1);
+
+ pw_thread_loop_unlock (self->main_loop);
+
+ return TRUE;
+
+not_running:
+ pw_remote_destroy (self->remote);
+ self->remote = NULL;
+failed_remote:
+ pw_thread_loop_unlock (self->main_loop);
+failed_start:
+ pw_core_destroy (self->core);
+ self->core = NULL;
+failed_core:
+ pw_thread_loop_destroy (self->main_loop);
+ self->main_loop = NULL;
+failed_main_loop:
+ pw_loop_destroy (self->loop);
+ self->loop = NULL;
+ return TRUE;
+}
+
+static void
+gst_pipewire_device_provider_stop (GstDeviceProvider * provider)
+{
+ GstPipeWireDeviceProvider *self = GST_PIPEWIRE_DEVICE_PROVIDER (provider);
+
+ if (self->remote) {
+ pw_remote_disconnect (self->remote);
+ pw_remote_destroy (self->remote);
+ self->remote = NULL;
+ }
+ if (self->main_loop) {
+ pw_thread_loop_destroy (self->main_loop);
+ self->main_loop = NULL;
+ }
+ if (self->loop) {
+ pw_loop_destroy (self->loop);
+ self->loop = NULL;
+ }
+}
+
+static void
+gst_pipewire_device_provider_set_property (GObject * object,
+ guint prop_id, const GValue * value, GParamSpec * pspec)
+{
+ GstPipeWireDeviceProvider *self = GST_PIPEWIRE_DEVICE_PROVIDER (object);
+
+ switch (prop_id) {
+ case PROP_CLIENT_NAME:
+ g_free (self->client_name);
+ if (!g_value_get_string (value)) {
+ GST_WARNING_OBJECT (self,
+ "Empty PipeWire client name not allowed. "
+ "Resetting to default value");
+ self->client_name = pw_get_client_name ();
+ } else
+ self->client_name = g_value_dup_string (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_pipewire_device_provider_get_property (GObject * object,
+ guint prop_id, GValue * value, GParamSpec * pspec)
+{
+ GstPipeWireDeviceProvider *self = GST_PIPEWIRE_DEVICE_PROVIDER (object);
+
+ switch (prop_id) {
+ case PROP_CLIENT_NAME:
+ g_value_set_string (value, self->client_name);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_pipewire_device_provider_finalize (GObject * object)
+{
+ GstPipeWireDeviceProvider *self = GST_PIPEWIRE_DEVICE_PROVIDER (object);
+
+ g_free (self->client_name);
+
+ G_OBJECT_CLASS (gst_pipewire_device_provider_parent_class)->finalize (object);
+}
+
+static void
+gst_pipewire_device_provider_class_init (GstPipeWireDeviceProviderClass * klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ GstDeviceProviderClass *dm_class = GST_DEVICE_PROVIDER_CLASS (klass);
+ gchar *client_name;
+
+ gobject_class->set_property = gst_pipewire_device_provider_set_property;
+ gobject_class->get_property = gst_pipewire_device_provider_get_property;
+ gobject_class->finalize = gst_pipewire_device_provider_finalize;
+
+ dm_class->probe = gst_pipewire_device_provider_probe;
+ dm_class->start = gst_pipewire_device_provider_start;
+ dm_class->stop = gst_pipewire_device_provider_stop;
+
+ client_name = pw_get_client_name ();
+ g_object_class_install_property (gobject_class,
+ PROP_CLIENT_NAME,
+ g_param_spec_string ("client-name", "Client Name",
+ "The PipeWire client_name_to_use", client_name,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
+ GST_PARAM_MUTABLE_READY));
+ g_free (client_name);
+
+ gst_device_provider_class_set_static_metadata (dm_class,
+ "PipeWire Device Provider", "Sink/Source/Audio/Video",
+ "List and provide PipeWire source and sink devices",
+ "Wim Taymans <wim.taymans@gmail.com>");
+}
+
+static void
+gst_pipewire_device_provider_init (GstPipeWireDeviceProvider * self)
+{
+ self->client_name = pw_get_client_name ();
+}
diff --git a/src/gst/gstpipewiredeviceprovider.h b/src/gst/gstpipewiredeviceprovider.h
new file mode 100644
index 00000000..462d98c3
--- /dev/null
+++ b/src/gst/gstpipewiredeviceprovider.h
@@ -0,0 +1,105 @@
+/* GStreamer
+ * Copyright (C) 2012 Olivier Crete <olivier.crete@collabora.com>
+ * (C) 2015 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * pipewiredeviceprovider.h: Device probing and monitoring
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+#ifndef __GST_PIPEWIRE_DEVICE_PROVIDER_H__
+#define __GST_PIPEWIRE_DEVICE_PROVIDER_H__
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <pipewire/pipewire.h>
+
+#include <gst/gst.h>
+
+G_BEGIN_DECLS
+
+typedef struct _GstPipeWireDevice GstPipeWireDevice;
+typedef struct _GstPipeWireDeviceClass GstPipeWireDeviceClass;
+
+#define GST_TYPE_PIPEWIRE_DEVICE (gst_pipewire_device_get_type())
+#define GST_IS_PIPEWIRE_DEVICE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_PIPEWIRE_DEVICE))
+#define GST_IS_PIPEWIRE_DEVICE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_PIPEWIRE_DEVICE))
+#define GST_PIPEWIRE_DEVICE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_PIPEWIRE_DEVICE, GstPipeWireDeviceClass))
+#define GST_PIPEWIRE_DEVICE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_PIPEWIRE_DEVICE, GstPipeWireDevice))
+#define GST_PIPEWIRE_DEVICE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_DEVICE, GstPipeWireDeviceClass))
+#define GST_PIPEWIRE_DEVICE_CAST(obj) ((GstPipeWireDevice *)(obj))
+
+typedef enum {
+ GST_PIPEWIRE_DEVICE_TYPE_UNKNOWN,
+ GST_PIPEWIRE_DEVICE_TYPE_SOURCE,
+ GST_PIPEWIRE_DEVICE_TYPE_SINK,
+} GstPipeWireDeviceType;
+
+struct _GstPipeWireDevice {
+ GstDevice parent;
+
+ GstPipeWireDeviceType type;
+ uint32_t id;
+ const gchar *element;
+};
+
+struct _GstPipeWireDeviceClass {
+ GstDeviceClass parent_class;
+};
+
+GType gst_pipewire_device_get_type (void);
+
+typedef struct _GstPipeWireDeviceProvider GstPipeWireDeviceProvider;
+typedef struct _GstPipeWireDeviceProviderClass GstPipeWireDeviceProviderClass;
+
+#define GST_TYPE_PIPEWIRE_DEVICE_PROVIDER (gst_pipewire_device_provider_get_type())
+#define GST_IS_PIPEWIRE_DEVICE_PROVIDER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_PIPEWIRE_DEVICE_PROVIDER))
+#define GST_IS_PIPEWIRE_DEVICE_PROVIDER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_PIPEWIRE_DEVICE_PROVIDER))
+#define GST_PIPEWIRE_DEVICE_PROVIDER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_PIPEWIRE_DEVICE_PROVIDER, GstPipeWireDeviceProviderClass))
+#define GST_PIPEWIRE_DEVICE_PROVIDER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_PIPEWIRE_DEVICE_PROVIDER, GstPipeWireDeviceProvider))
+#define GST_PIPEWIRE_DEVICE_PROVIDER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_DEVICE_PROVIDER, GstPipeWireDeviceProviderClass))
+#define GST_PIPEWIRE_DEVICE_PROVIDER_CAST(obj) ((GstPipeWireDeviceProvider *)(obj))
+
+struct _GstPipeWireDeviceProvider {
+ GstDeviceProvider parent;
+
+ gchar *client_name;
+
+ struct pw_loop *loop;
+ struct pw_thread_loop *main_loop;
+
+ struct pw_core *core;
+ struct pw_remote *remote;
+ struct pw_proxy *registry;
+ gboolean end;
+ gboolean list_only;
+ GList **devices;
+ struct pw_listener remote_state_changed;
+ struct pw_listener on_sync_reply;
+};
+
+struct _GstPipeWireDeviceProviderClass {
+ GstDeviceProviderClass parent_class;
+};
+
+GType gst_pipewire_device_provider_get_type (void);
+
+G_END_DECLS
+
+#endif /* __GST_PIPEWIRE_DEVICE_PROVIDER_H__ */
diff --git a/src/gst/gstpipewireformat.c b/src/gst/gstpipewireformat.c
new file mode 100644
index 00000000..346d35cb
--- /dev/null
+++ b/src/gst/gstpipewireformat.c
@@ -0,0 +1,852 @@
+/* GStreamer
+ * Copyright (C) 2016 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#define _GNU_SOURCE
+#include <stdio.h>
+
+#include <gst/gst.h>
+#include <gst/video/video.h>
+#include <gst/audio/audio.h>
+
+#include <spa/format-builder.h>
+#include <spa/video/format-utils.h>
+#include <spa/audio/format-utils.h>
+
+#include "gstpipewireformat.h"
+
+struct media_type {
+ const char *name;
+ uint32_t *media_type;
+ uint32_t *media_subtype;
+};
+
+static struct {
+ struct spa_type_map *map;
+ uint32_t format;
+ struct spa_type_media_type media_type;
+ struct spa_type_media_subtype media_subtype;
+ struct spa_type_media_subtype_video media_subtype_video;
+ struct spa_type_media_subtype_audio media_subtype_audio;
+ struct spa_type_format_video format_video;
+ struct spa_type_format_audio format_audio;
+ struct spa_type_video_format video_format;
+ struct spa_type_audio_format audio_format;
+} type = { NULL, };
+
+static void
+ensure_types (struct spa_type_map *map)
+{
+ type.map = map;
+
+ type.format = spa_type_map_get_id (map, SPA_TYPE__Format);
+ spa_type_media_type_map (map, &type.media_type);
+ spa_type_media_subtype_map (map, &type.media_subtype);
+ spa_type_media_subtype_video_map (map, &type.media_subtype_video);
+ spa_type_media_subtype_audio_map (map, &type.media_subtype_audio);
+ spa_type_format_video_map (map, &type.format_video);
+ spa_type_format_audio_map (map, &type.format_audio);
+ spa_type_video_format_map (map, &type.video_format);
+ spa_type_audio_format_map (map, &type.audio_format);
+}
+
+static const struct media_type media_type_map[] = {
+ { "video/x-raw", &type.media_type.video, &type.media_subtype.raw },
+ { "audio/x-raw", &type.media_type.audio, &type.media_subtype.raw },
+ { "image/jpeg", &type.media_type.video, &type.media_subtype_video.mjpg },
+ { "video/x-h264", &type.media_type.video, &type.media_subtype_video.h264 },
+ { NULL, }
+};
+
+static const uint32_t *video_format_map[] = {
+ &type.video_format.UNKNOWN,
+ &type.video_format.ENCODED,
+ &type.video_format.I420,
+ &type.video_format.YV12,
+ &type.video_format.YUY2,
+ &type.video_format.UYVY,
+ &type.video_format.AYUV,
+ &type.video_format.RGBx,
+ &type.video_format.BGRx,
+ &type.video_format.xRGB,
+ &type.video_format.xBGR,
+ &type.video_format.RGBA,
+ &type.video_format.BGRA,
+ &type.video_format.ARGB,
+ &type.video_format.ABGR,
+ &type.video_format.RGB,
+ &type.video_format.BGR,
+ &type.video_format.Y41B,
+ &type.video_format.Y42B,
+ &type.video_format.YVYU,
+ &type.video_format.Y444,
+ &type.video_format.v210,
+ &type.video_format.v216,
+ &type.video_format.NV12,
+ &type.video_format.NV21,
+ &type.video_format.GRAY8,
+ &type.video_format.GRAY16_BE,
+ &type.video_format.GRAY16_LE,
+ &type.video_format.v308,
+ &type.video_format.RGB16,
+ &type.video_format.BGR16,
+ &type.video_format.RGB15,
+ &type.video_format.BGR15,
+ &type.video_format.UYVP,
+ &type.video_format.A420,
+ &type.video_format.RGB8P,
+ &type.video_format.YUV9,
+ &type.video_format.YVU9,
+ &type.video_format.IYU1,
+ &type.video_format.ARGB64,
+ &type.video_format.AYUV64,
+ &type.video_format.r210,
+ &type.video_format.I420_10BE,
+ &type.video_format.I420_10LE,
+ &type.video_format.I422_10BE,
+ &type.video_format.I422_10LE,
+ &type.video_format.Y444_10BE,
+ &type.video_format.Y444_10LE,
+ &type.video_format.GBR,
+ &type.video_format.GBR_10BE,
+ &type.video_format.GBR_10LE,
+ &type.video_format.NV16,
+ &type.video_format.NV24,
+ &type.video_format.NV12_64Z32,
+ &type.video_format.A420_10BE,
+ &type.video_format.A420_10LE,
+ &type.video_format.A422_10BE,
+ &type.video_format.A422_10LE,
+ &type.video_format.A444_10BE,
+ &type.video_format.A444_10LE,
+ &type.video_format.NV61,
+ &type.video_format.P010_10BE,
+ &type.video_format.P010_10LE,
+ &type.video_format.IYU2,
+ &type.video_format.VYUY,
+ &type.video_format.GBRA,
+ &type.video_format.GBRA_10BE,
+ &type.video_format.GBRA_10LE,
+ &type.video_format.GBR_12BE,
+ &type.video_format.GBR_12LE,
+ &type.video_format.GBRA_12BE,
+ &type.video_format.GBRA_12LE,
+ &type.video_format.I420_12BE,
+ &type.video_format.I420_12LE,
+ &type.video_format.I422_12BE,
+ &type.video_format.I422_12LE,
+ &type.video_format.Y444_12BE,
+ &type.video_format.Y444_12LE,
+};
+
+#if __BYTE_ORDER == __BIG_ENDIAN
+#define _FORMAT_LE(fmt) &type.audio_format. fmt ## _OE
+#define _FORMAT_BE(fmt) &type.audio_format. fmt
+#elif __BYTE_ORDER == __LITTLE_ENDIAN
+#define _FORMAT_LE(fmt) &type.audio_format. fmt
+#define _FORMAT_BE(fmt) &type.audio_format. fmt ## _OE
+#endif
+
+static const uint32_t *audio_format_map[] = {
+ &type.audio_format.UNKNOWN,
+ &type.audio_format.ENCODED,
+ &type.audio_format.S8,
+ &type.audio_format.U8,
+ _FORMAT_LE (S16),
+ _FORMAT_BE (S16),
+ _FORMAT_LE (U16),
+ _FORMAT_BE (U16),
+ _FORMAT_LE (S24_32),
+ _FORMAT_BE (S24_32),
+ _FORMAT_LE (U24_32),
+ _FORMAT_BE (U24_32),
+ _FORMAT_LE (S32),
+ _FORMAT_BE (S32),
+ _FORMAT_LE (U32),
+ _FORMAT_BE (U32),
+ _FORMAT_LE (S24),
+ _FORMAT_BE (S24),
+ _FORMAT_LE (U24),
+ _FORMAT_BE (U24),
+ _FORMAT_LE (S20),
+ _FORMAT_BE (S20),
+ _FORMAT_LE (U20),
+ _FORMAT_BE (U20),
+ _FORMAT_LE (S18),
+ _FORMAT_BE (S18),
+ _FORMAT_LE (U18),
+ _FORMAT_BE (U18),
+ _FORMAT_LE (F32),
+ _FORMAT_BE (F32),
+ _FORMAT_LE (F64),
+ _FORMAT_BE (F64),
+};
+
+typedef struct {
+ struct spa_pod_builder b;
+ const struct media_type *type;
+ const GstCapsFeatures *cf;
+ const GstStructure *cs;
+} ConvertData;
+
+static const struct media_type *
+find_media_types (const char *name)
+{
+ int i;
+ for (i = 0; media_type_map[i].name; i++) {
+ if (!strcmp (media_type_map[i].name, name))
+ return &media_type_map[i];
+ }
+ return NULL;
+}
+
+static const char *
+get_nth_string (const GValue *val, int idx)
+{
+ const GValue *v = NULL;
+ GType type = G_VALUE_TYPE (val);
+
+ if (type == G_TYPE_STRING && idx == 0)
+ v = val;
+ else if (type == GST_TYPE_LIST) {
+ GArray *array = g_value_peek_pointer (val);
+ if (idx < array->len + 1) {
+ v = &g_array_index (array, GValue, SPA_MAX (idx - 1, 0));
+ }
+ }
+ if (v)
+ return g_value_get_string (v);
+
+ return NULL;
+}
+
+static bool
+get_nth_int (const GValue *val, int idx, int *res)
+{
+ const GValue *v = NULL;
+ GType type = G_VALUE_TYPE (val);
+
+ if (type == G_TYPE_INT && idx == 0) {
+ v = val;
+ } else if (type == GST_TYPE_INT_RANGE) {
+ if (idx == 0 || idx == 1) {
+ *res = gst_value_get_int_range_min (val);
+ return true;
+ } else if (idx == 2) {
+ *res = gst_value_get_int_range_max (val);
+ return true;
+ }
+ } else if (type == GST_TYPE_LIST) {
+ GArray *array = g_value_peek_pointer (val);
+ if (idx < array->len + 1) {
+ v = &g_array_index (array, GValue, SPA_MAX (idx - 1, 0));
+ }
+ }
+ if (v) {
+ *res = g_value_get_int (v);
+ return true;
+ }
+ return false;
+}
+
+static gboolean
+get_nth_fraction (const GValue *val, int idx, struct spa_fraction *f)
+{
+ const GValue *v = NULL;
+ GType type = G_VALUE_TYPE (val);
+
+ if (type == GST_TYPE_FRACTION && idx == 0) {
+ v = val;
+ } else if (type == GST_TYPE_FRACTION_RANGE) {
+ if (idx == 0 || idx == 1) {
+ v = gst_value_get_fraction_range_min (val);
+ } else if (idx == 2) {
+ v = gst_value_get_fraction_range_max (val);
+ }
+ } else if (type == GST_TYPE_LIST) {
+ GArray *array = g_value_peek_pointer (val);
+ if (idx < array->len + 1) {
+ v = &g_array_index (array, GValue, SPA_MAX (idx-1, 0));
+ }
+ }
+ if (v) {
+ f->num = gst_value_get_fraction_numerator (v);
+ f->denom = gst_value_get_fraction_denominator (v);
+ return true;
+ }
+ return false;
+}
+
+static gboolean
+get_nth_rectangle (const GValue *width, const GValue *height, int idx, struct spa_rectangle *r)
+{
+ const GValue *w = NULL, *h = NULL;
+ GType wt = G_VALUE_TYPE (width);
+ GType ht = G_VALUE_TYPE (height);
+
+ if (wt == G_TYPE_INT && ht == G_TYPE_INT && idx == 0) {
+ w = width;
+ h = height;
+ } else if (wt == GST_TYPE_INT_RANGE && ht == GST_TYPE_INT_RANGE) {
+ if (idx == 0 || idx == 1) {
+ r->width = gst_value_get_int_range_min (width);
+ r->height = gst_value_get_int_range_min (height);
+ return true;
+ } else if (idx == 2) {
+ r->width = gst_value_get_int_range_max (width);
+ r->height = gst_value_get_int_range_max (height);
+ return true;
+ }
+ } else if (wt == GST_TYPE_LIST && ht == GST_TYPE_LIST) {
+ GArray *wa = g_value_peek_pointer (width);
+ GArray *ha = g_value_peek_pointer (height);
+ if (idx < wa->len + 1)
+ w = &g_array_index (wa, GValue, SPA_MAX (idx-1, 0));
+ if (idx < ha->len + 1)
+ h = &g_array_index (ha, GValue, SPA_MAX (idx-1, 0));
+ }
+ if (w && h) {
+ r->width = g_value_get_int (w);
+ r->height = g_value_get_int (h);
+ return true;
+ }
+ return false;
+}
+
+static const uint32_t
+get_range_type (const GValue *val)
+{
+ GType type = G_VALUE_TYPE (val);
+
+ if (type == GST_TYPE_LIST)
+ return SPA_POD_PROP_RANGE_ENUM;
+ if (type == GST_TYPE_DOUBLE_RANGE || type == GST_TYPE_FRACTION_RANGE)
+ return SPA_POD_PROP_RANGE_MIN_MAX;
+ if (type == GST_TYPE_INT_RANGE) {
+ if (gst_value_get_int_range_step (val) == 1)
+ return SPA_POD_PROP_RANGE_MIN_MAX;
+ else
+ return SPA_POD_PROP_RANGE_STEP;
+ }
+ if (type == GST_TYPE_INT64_RANGE) {
+ if (gst_value_get_int64_range_step (val) == 1)
+ return SPA_POD_PROP_RANGE_MIN_MAX;
+ else
+ return SPA_POD_PROP_RANGE_STEP;
+ }
+ return SPA_POD_PROP_RANGE_NONE;
+}
+
+static const uint32_t
+get_range_type2 (const GValue *v1, const GValue *v2)
+{
+ uint32_t r1, r2;
+
+ r1 = get_range_type (v1);
+ r2 = get_range_type (v2);
+
+ if (r1 == r2)
+ return r1;
+ if (r1 == SPA_POD_PROP_RANGE_STEP || r2 == SPA_POD_PROP_RANGE_STEP)
+ return SPA_POD_PROP_RANGE_STEP;
+ if (r1 == SPA_POD_PROP_RANGE_MIN_MAX || r2 == SPA_POD_PROP_RANGE_MIN_MAX)
+ return SPA_POD_PROP_RANGE_MIN_MAX;
+ return SPA_POD_PROP_RANGE_MIN_MAX;
+}
+
+static gboolean
+handle_video_fields (ConvertData *d)
+{
+ struct spa_pod_frame f;
+ const GValue *value, *value2;
+ int i;
+
+ value = gst_structure_get_value (d->cs, "format");
+ if (value) {
+ const char *v;
+ int idx;
+ for (i = 0; (v = get_nth_string (value, i)); i++) {
+ if (i == 0)
+ spa_pod_builder_push_prop (&d->b, &f,
+ type.format_video.format,
+ get_range_type (value));
+
+ idx = gst_video_format_from_string (v);
+ if (idx < SPA_N_ELEMENTS (video_format_map))
+ spa_pod_builder_id (&d->b, *video_format_map[idx]);
+ }
+ if (i > 1)
+ SPA_POD_BUILDER_DEREF (&d->b, f.ref, struct spa_pod_prop)->body.flags |= SPA_POD_PROP_FLAG_UNSET;
+ spa_pod_builder_pop (&d->b, &f);
+ }
+ value = gst_structure_get_value (d->cs, "width");
+ value2 = gst_structure_get_value (d->cs, "height");
+ if (value || value2) {
+ struct spa_rectangle v;
+ for (i = 0; get_nth_rectangle (value, value2, i, &v); i++) {
+ if (i == 0)
+ spa_pod_builder_push_prop (&d->b, &f,
+ type.format_video.size,
+ get_range_type2 (value, value2));
+
+ spa_pod_builder_rectangle (&d->b, v.width, v.height);
+ }
+ if (i > 1)
+ SPA_POD_BUILDER_DEREF (&d->b, f.ref, struct spa_pod_prop)->body.flags |= SPA_POD_PROP_FLAG_UNSET;
+ spa_pod_builder_pop (&d->b, &f);
+ }
+
+ value = gst_structure_get_value (d->cs, "framerate");
+ if (value) {
+ struct spa_fraction v;
+ for (i = 0; get_nth_fraction (value, i, &v); i++) {
+ if (i == 0)
+ spa_pod_builder_push_prop (&d->b, &f,
+ type.format_video.framerate,
+ get_range_type (value));
+
+ spa_pod_builder_fraction (&d->b, v.num, v.denom);
+ }
+ if (i > 1)
+ SPA_POD_BUILDER_DEREF (&d->b, f.ref, struct spa_pod_prop)->body.flags |= SPA_POD_PROP_FLAG_UNSET;
+ spa_pod_builder_pop (&d->b, &f);
+ }
+
+ value = gst_structure_get_value (d->cs, "max-framerate");
+ if (value) {
+ struct spa_fraction v;
+ for (i = 0; get_nth_fraction (value, i, &v); i++) {
+ if (i == 0)
+ spa_pod_builder_push_prop (&d->b, &f,
+ type.format_video.max_framerate,
+ get_range_type (value));
+
+ spa_pod_builder_fraction (&d->b, v.num, v.denom);
+ }
+ if (i > 1)
+ SPA_POD_BUILDER_DEREF (&d->b, f.ref, struct spa_pod_prop)->body.flags |= SPA_POD_PROP_FLAG_UNSET;
+ spa_pod_builder_pop (&d->b, &f);
+ }
+ return TRUE;
+}
+
+static gboolean
+handle_audio_fields (ConvertData *d)
+{
+ struct spa_pod_frame f;
+ const GValue *value;
+ int i = 0;
+
+ value = gst_structure_get_value (d->cs, "format");
+ if (value) {
+ const char *v;
+ int idx;
+ for (i = 0; (v = get_nth_string (value, i)); i++) {
+ if (i == 0)
+ spa_pod_builder_push_prop (&d->b, &f,
+ type.format_audio.format,
+ get_range_type (value));
+
+ idx = gst_audio_format_from_string (v);
+ if (idx < SPA_N_ELEMENTS (audio_format_map))
+ spa_pod_builder_id (&d->b, *audio_format_map[idx]);
+ }
+ if (i > 1)
+ SPA_POD_BUILDER_DEREF (&d->b, f.ref, struct spa_pod_prop)->body.flags |= SPA_POD_PROP_FLAG_UNSET;
+ spa_pod_builder_pop (&d->b, &f);
+ }
+
+ value = gst_structure_get_value (d->cs, "layout");
+ if (value) {
+ const char *v;
+ for (i = 0; (v = get_nth_string (value, i)); i++) {
+ enum spa_audio_layout layout;
+
+ if (!strcmp (v, "interleaved"))
+ layout = SPA_AUDIO_LAYOUT_INTERLEAVED;
+ else if (!strcmp (v, "non-interleaved"))
+ layout = SPA_AUDIO_LAYOUT_NON_INTERLEAVED;
+ else
+ break;
+
+ if (i == 0)
+ spa_pod_builder_push_prop (&d->b, &f,
+ type.format_audio.layout,
+ get_range_type (value));
+
+ spa_pod_builder_int (&d->b, layout);
+ }
+ if (i > 1)
+ SPA_POD_BUILDER_DEREF (&d->b, f.ref, struct spa_pod_prop)->body.flags |= SPA_POD_PROP_FLAG_UNSET;
+ spa_pod_builder_pop (&d->b, &f);
+ }
+ value = gst_structure_get_value (d->cs, "rate");
+ if (value) {
+ int v;
+ for (i = 0; get_nth_int (value, i, &v); i++) {
+ if (i == 0)
+ spa_pod_builder_push_prop (&d->b, &f,
+ type.format_audio.rate,
+ get_range_type (value));
+
+ spa_pod_builder_int (&d->b, v);
+ }
+ if (i > 1)
+ SPA_POD_BUILDER_DEREF (&d->b, f.ref, struct spa_pod_prop)->body.flags |= SPA_POD_PROP_FLAG_UNSET;
+ spa_pod_builder_pop (&d->b, &f);
+ }
+ value = gst_structure_get_value (d->cs, "channels");
+ if (value) {
+ int v;
+ for (i = 0; get_nth_int (value, i, &v); i++) {
+ if (i == 0)
+ spa_pod_builder_push_prop (&d->b, &f,
+ type.format_audio.channels,
+ get_range_type (value));
+
+ spa_pod_builder_int (&d->b, v);
+ }
+ if (i > 1)
+ SPA_POD_BUILDER_DEREF (&d->b, f.ref, struct spa_pod_prop)->body.flags |= SPA_POD_PROP_FLAG_UNSET;
+ spa_pod_builder_pop (&d->b, &f);
+ }
+ return TRUE;
+}
+
+static uint32_t
+write_pod (struct spa_pod_builder *b, uint32_t ref, const void *data, uint32_t size)
+{
+ if (ref == -1)
+ ref = b->offset;
+
+ if (b->size <= b->offset) {
+ b->size = SPA_ROUND_UP_N (b->offset + size, 512);
+ b->data = realloc (b->data, b->size);
+ }
+ memcpy (b->data + ref, data, size);
+ return ref;
+}
+
+static struct spa_format *
+convert_1 (GstCapsFeatures *cf, GstStructure *cs)
+{
+ ConvertData d;
+ struct spa_pod_frame f;
+
+ spa_zero (d);
+ d.cf = cf;
+ d.cs = cs;
+
+ if (!(d.type = find_media_types (gst_structure_get_name (cs))))
+ return NULL;
+
+ d.b.write = write_pod;
+
+ spa_pod_builder_push_format (&d.b, &f, type.format,
+ *d.type->media_type,
+ *d.type->media_subtype);
+
+ if (*d.type->media_type == type.media_type.video)
+ handle_video_fields (&d);
+ else if (*d.type->media_type == type.media_type.audio)
+ handle_audio_fields (&d);
+
+ spa_pod_builder_pop (&d.b, &f);
+
+ return SPA_MEMBER (d.b.data, 0, struct spa_format);
+}
+
+struct spa_format *
+gst_caps_to_format (GstCaps *caps, guint index, struct spa_type_map *map)
+{
+ GstCapsFeatures *f;
+ GstStructure *s;
+ struct spa_format *res;
+
+ g_return_val_if_fail (GST_IS_CAPS (caps), NULL);
+ g_return_val_if_fail (gst_caps_is_fixed (caps), NULL);
+
+ ensure_types(map);
+
+ f = gst_caps_get_features (caps, index);
+ s = gst_caps_get_structure (caps, index);
+
+ res = convert_1 (f, s);
+
+ return res;
+}
+
+static gboolean
+foreach_func (GstCapsFeatures *features,
+ GstStructure *structure,
+ GPtrArray *array)
+{
+ struct spa_format *fmt;
+
+ if ((fmt = convert_1 (features, structure)))
+ g_ptr_array_insert (array, -1, fmt);
+
+ return TRUE;
+}
+
+
+GPtrArray *
+gst_caps_to_format_all (GstCaps *caps, struct spa_type_map *map)
+{
+ GPtrArray *res;
+
+ ensure_types(map);
+
+ res = g_ptr_array_new_full (gst_caps_get_size (caps), (GDestroyNotify)g_free);
+ gst_caps_foreach (caps, (GstCapsForeachFunc) foreach_func, res);
+
+ return res;
+}
+
+static void
+handle_id_prop (struct spa_pod_prop *prop, const char *key, GstCaps *res)
+{
+ const char * str;
+ uint32_t *id = SPA_POD_CONTENTS (struct spa_pod_prop, prop);
+ uint32_t i, n_items = SPA_POD_PROP_N_VALUES (prop);
+ uint32_t flags;
+
+ flags = prop->body.flags;
+ if (!(flags & SPA_POD_PROP_FLAG_UNSET))
+ flags &= ~SPA_POD_PROP_RANGE_MASK;
+
+ switch (flags & SPA_POD_PROP_RANGE_MASK) {
+ case SPA_POD_PROP_RANGE_NONE:
+ if (!(str = spa_type_map_get_type (type.map, id[0])))
+ return;
+ gst_caps_set_simple (res, key, G_TYPE_STRING, rindex (str, ':') + 1, NULL);
+ break;
+ case SPA_POD_PROP_RANGE_ENUM:
+ {
+ GValue list = { 0 }, v = { 0 };
+
+ g_value_init (&list, GST_TYPE_LIST);
+ for (i = 1; i < n_items; i++) {
+ if (!(str = spa_type_map_get_type (type.map, id[i])))
+ continue;
+
+ g_value_init (&v, G_TYPE_STRING);
+ g_value_set_string (&v, rindex (str, ':') + 1);
+ gst_value_list_append_and_take_value (&list, &v);
+ }
+ gst_caps_set_value (res, key, &list);
+ g_value_unset (&list);
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+static void
+handle_int_prop (struct spa_pod_prop *prop, const char *key, GstCaps *res)
+{
+ uint32_t *val = SPA_POD_CONTENTS (struct spa_pod_prop, prop);
+ uint32_t i, n_items = SPA_POD_PROP_N_VALUES (prop);
+ uint32_t flags;
+
+ flags = prop->body.flags;
+ if (!(flags & SPA_POD_PROP_FLAG_UNSET))
+ flags &= ~SPA_POD_PROP_RANGE_MASK;
+
+ switch (flags & SPA_POD_PROP_RANGE_MASK) {
+ case SPA_POD_PROP_RANGE_NONE:
+ gst_caps_set_simple (res, key, G_TYPE_INT, val[0], NULL);
+ break;
+ case SPA_POD_PROP_RANGE_MIN_MAX:
+ case SPA_POD_PROP_RANGE_STEP:
+ {
+ if (n_items < 3)
+ return;
+ gst_caps_set_simple (res, key, GST_TYPE_INT_RANGE, val[1], val[2], NULL);
+ break;
+ }
+ case SPA_POD_PROP_RANGE_ENUM:
+ {
+ GValue list = { 0 }, v = { 0 };
+
+ g_value_init (&list, GST_TYPE_LIST);
+ for (i = 1; i < n_items; i++) {
+ g_value_init (&v, G_TYPE_INT);
+ g_value_set_int (&v, val[i]);
+ gst_value_list_append_and_take_value (&list, &v);
+ }
+ gst_caps_set_value (res, key, &list);
+ g_value_unset (&list);
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+static void
+handle_rect_prop (struct spa_pod_prop *prop, const char *width, const char *height, GstCaps *res)
+{
+ struct spa_rectangle *rect = SPA_POD_CONTENTS (struct spa_pod_prop, prop);
+ uint32_t i, n_items = SPA_POD_PROP_N_VALUES (prop);
+ uint32_t flags;
+
+ flags = prop->body.flags;
+ if (!(flags & SPA_POD_PROP_FLAG_UNSET))
+ flags &= ~SPA_POD_PROP_RANGE_MASK;
+
+ switch (flags & SPA_POD_PROP_RANGE_MASK) {
+ case SPA_POD_PROP_RANGE_NONE:
+ gst_caps_set_simple (res, width, G_TYPE_INT, rect[0].width,
+ height, G_TYPE_INT, rect[0].height, NULL);
+ break;
+ case SPA_POD_PROP_RANGE_MIN_MAX:
+ case SPA_POD_PROP_RANGE_STEP:
+ {
+ if (n_items < 3)
+ return;
+ gst_caps_set_simple (res, width, GST_TYPE_INT_RANGE, rect[1].width, rect[2].width,
+ height, GST_TYPE_INT_RANGE, rect[1].height, rect[2].height, NULL);
+ break;
+ }
+ case SPA_POD_PROP_RANGE_ENUM:
+ {
+ GValue l1 = { 0 }, l2 = { 0 }, v1 = { 0 }, v2 = { 0 };
+
+ g_value_init (&l1, GST_TYPE_LIST);
+ g_value_init (&l2, GST_TYPE_LIST);
+ for (i = 1; i < n_items; i++) {
+ g_value_init (&v1, G_TYPE_INT);
+ g_value_set_int (&v1, rect[i].width);
+ gst_value_list_append_and_take_value (&l1, &v1);
+
+ g_value_init (&v2, G_TYPE_INT);
+ g_value_set_int (&v2, rect[i].height);
+ gst_value_list_append_and_take_value (&l2, &v2);
+ }
+ gst_caps_set_value (res, width, &l1);
+ gst_caps_set_value (res, height, &l2);
+ g_value_unset (&l1);
+ g_value_unset (&l2);
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+static void
+handle_fraction_prop (struct spa_pod_prop *prop, const char *key, GstCaps *res)
+{
+ struct spa_fraction *fract = SPA_POD_CONTENTS (struct spa_pod_prop, prop);
+ uint32_t i, n_items = SPA_POD_PROP_N_VALUES (prop);
+ uint32_t flags;
+
+ flags = prop->body.flags;
+ if (!(flags & SPA_POD_PROP_FLAG_UNSET))
+ flags &= ~SPA_POD_PROP_RANGE_MASK;
+
+ switch (flags & SPA_POD_PROP_RANGE_MASK) {
+ case SPA_POD_PROP_RANGE_NONE:
+ gst_caps_set_simple (res, key, GST_TYPE_FRACTION, fract[0].num, fract[0].denom, NULL);
+ break;
+ case SPA_POD_PROP_RANGE_MIN_MAX:
+ case SPA_POD_PROP_RANGE_STEP:
+ {
+ if (n_items < 3)
+ return;
+ gst_caps_set_simple (res, key, GST_TYPE_FRACTION_RANGE, fract[1].num, fract[1].denom,
+ fract[2].num, fract[2].denom, NULL);
+ break;
+ }
+ case SPA_POD_PROP_RANGE_ENUM:
+ {
+ GValue l1 = { 0 }, v1 = { 0 };
+
+ g_value_init (&l1, GST_TYPE_LIST);
+ for (i = 1; i < n_items; i++) {
+ g_value_init (&v1, GST_TYPE_FRACTION);
+ gst_value_set_fraction (&v1, fract[i].num, fract[i].denom);
+ gst_value_list_append_and_take_value (&l1, &v1);
+ }
+ gst_caps_set_value (res, key, &l1);
+ g_value_unset (&l1);
+ break;
+ }
+ default:
+ break;
+ }
+}
+GstCaps *
+gst_caps_from_format (const struct spa_format *format, struct spa_type_map *map)
+{
+ GstCaps *res = NULL;
+ uint32_t media_type, media_subtype;
+ struct spa_pod_prop *prop;
+
+ ensure_types(map);
+
+ media_type = SPA_FORMAT_MEDIA_TYPE (format);
+ media_subtype = SPA_FORMAT_MEDIA_SUBTYPE (format);
+
+ if (media_type == type.media_type.video) {
+ if (media_subtype == type.media_subtype.raw) {
+ res = gst_caps_new_empty_simple ("video/x-raw");
+ if ((prop = spa_format_find_prop (format, type.format_video.format))) {
+ handle_id_prop (prop, "format", res);
+ }
+ }
+ else if (media_subtype == type.media_subtype_video.mjpg) {
+ res = gst_caps_new_empty_simple ("image/jpeg");
+ }
+ else if (media_subtype == type.media_subtype_video.h264) {
+ res = gst_caps_new_simple ("video/x-h264",
+ "stream-format", G_TYPE_STRING, "byte-stream",
+ "alignment", G_TYPE_STRING, "au",
+ NULL);
+ }
+ if ((prop = spa_format_find_prop (format, type.format_video.size))) {
+ handle_rect_prop (prop, "width", "height", res);
+ }
+ if ((prop = spa_format_find_prop (format, type.format_video.framerate))) {
+ handle_fraction_prop (prop, "framerate", res);
+ }
+ if ((prop = spa_format_find_prop (format, type.format_video.max_framerate))) {
+ handle_fraction_prop (prop, "max-framerate", res);
+ }
+ } else if (media_type == type.media_type.audio) {
+ if (media_subtype == type.media_subtype.raw) {
+ res = gst_caps_new_simple ("audio/x-raw",
+ "layout", G_TYPE_STRING, "interleaved",
+ NULL);
+ if ((prop = spa_format_find_prop (format, type.format_audio.format))) {
+ handle_id_prop (prop, "format", res);
+ }
+ if ((prop = spa_format_find_prop (format, type.format_audio.rate))) {
+ handle_int_prop (prop, "rate", res);
+ }
+ if ((prop = spa_format_find_prop (format, type.format_audio.channels))) {
+ handle_int_prop (prop, "channels", res);
+ }
+ }
+ else if (media_subtype == type.media_subtype_audio.aac) {
+ }
+ }
+ return res;
+}
diff --git a/src/gst/gstpipewireformat.h b/src/gst/gstpipewireformat.h
new file mode 100644
index 00000000..e5cc0776
--- /dev/null
+++ b/src/gst/gstpipewireformat.h
@@ -0,0 +1,37 @@
+/* GStreamer
+ * Copyright (C) 2016 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef _GST_PIPEWIRE_FORMAT_H_
+#define _GST_PIPEWIRE_FORMAT_H_
+
+#include <gst/gst.h>
+
+#include <spa/type-map.h>
+#include <spa/format.h>
+
+G_BEGIN_DECLS
+
+struct spa_format * gst_caps_to_format (GstCaps *caps, guint index, struct spa_type_map *map);
+GPtrArray * gst_caps_to_format_all (GstCaps *caps, struct spa_type_map *map);
+
+GstCaps * gst_caps_from_format (const struct spa_format *format, struct spa_type_map *map);
+
+G_END_DECLS
+
+#endif
diff --git a/src/gst/gstpipewirepool.c b/src/gst/gstpipewirepool.c
new file mode 100644
index 00000000..eb36f957
--- /dev/null
+++ b/src/gst/gstpipewirepool.c
@@ -0,0 +1,178 @@
+/* GStreamer
+ * Copyright (C) 2016 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Suite 500,
+ * Boston, MA 02110-1335, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <gst/gst.h>
+
+#include "gstpipewirepool.h"
+
+GST_DEBUG_CATEGORY_STATIC (gst_pipewire_pool_debug_category);
+#define GST_CAT_DEFAULT gst_pipewire_pool_debug_category
+
+G_DEFINE_TYPE (GstPipeWirePool, gst_pipewire_pool, GST_TYPE_BUFFER_POOL);
+
+enum
+{
+ ACTIVATED,
+ /* FILL ME */
+ LAST_SIGNAL
+};
+
+
+static guint pool_signals[LAST_SIGNAL] = { 0 };
+
+GstPipeWirePool *
+gst_pipewire_pool_new (void)
+{
+ GstPipeWirePool *pool;
+
+ pool = g_object_new (GST_TYPE_PIPEWIRE_POOL, NULL);
+
+ return pool;
+}
+
+gboolean
+gst_pipewire_pool_add_buffer (GstPipeWirePool *pool, GstBuffer *buffer)
+{
+ g_return_val_if_fail (GST_IS_PIPEWIRE_POOL (pool), FALSE);
+ g_return_val_if_fail (GST_IS_BUFFER (buffer), FALSE);
+
+ GST_OBJECT_LOCK (pool);
+ g_queue_push_tail (&pool->available, buffer);
+ g_cond_signal (&pool->cond);
+ GST_OBJECT_UNLOCK (pool);
+
+ return TRUE;
+}
+
+gboolean
+gst_pipewire_pool_remove_buffer (GstPipeWirePool *pool, GstBuffer *buffer)
+{
+ gboolean res;
+
+ g_return_val_if_fail (GST_IS_PIPEWIRE_POOL (pool), FALSE);
+ g_return_val_if_fail (GST_IS_BUFFER (buffer), FALSE);
+
+ GST_OBJECT_LOCK (pool);
+ res = g_queue_remove (&pool->available, buffer);
+ GST_OBJECT_UNLOCK (pool);
+
+ return res;
+}
+
+static GstFlowReturn
+acquire_buffer (GstBufferPool * pool, GstBuffer ** buffer,
+ GstBufferPoolAcquireParams * params)
+{
+ GstPipeWirePool *p = GST_PIPEWIRE_POOL (pool);
+
+ GST_OBJECT_LOCK (pool);
+ while (TRUE) {
+ if (G_UNLIKELY (GST_BUFFER_POOL_IS_FLUSHING (pool)))
+ goto flushing;
+
+ if (p->available.length > 0)
+ break;
+
+ GST_WARNING ("queue empty");
+ g_cond_wait (&p->cond, GST_OBJECT_GET_LOCK (pool));
+ }
+ *buffer = g_queue_pop_head (&p->available);
+ GST_OBJECT_UNLOCK (pool);
+ GST_DEBUG ("acquire buffer %p", *buffer);
+
+ return GST_FLOW_OK;
+
+flushing:
+ {
+ GST_OBJECT_UNLOCK (pool);
+ return GST_FLOW_FLUSHING;
+ }
+}
+
+static void
+flush_start (GstBufferPool * pool)
+{
+ GstPipeWirePool *p = GST_PIPEWIRE_POOL (pool);
+
+ GST_DEBUG ("flush start");
+ GST_OBJECT_LOCK (pool);
+ g_cond_signal (&p->cond);
+ GST_OBJECT_UNLOCK (pool);
+}
+
+static void
+release_buffer (GstBufferPool * pool, GstBuffer *buffer)
+{
+ GstPipeWirePool *p = GST_PIPEWIRE_POOL (pool);
+
+ GST_DEBUG ("release buffer %p", buffer);
+ GST_OBJECT_LOCK (pool);
+ g_queue_push_tail (&p->available, buffer);
+ g_cond_signal (&p->cond);
+ GST_OBJECT_UNLOCK (pool);
+}
+
+static gboolean
+do_start (GstBufferPool * pool)
+{
+ g_signal_emit (pool, pool_signals[ACTIVATED], 0, NULL);
+ return TRUE;
+}
+
+static void
+gst_pipewire_pool_finalize (GObject * object)
+{
+ GstPipeWirePool *pool = GST_PIPEWIRE_POOL (object);
+
+ GST_DEBUG_OBJECT (pool, "finalize");
+
+ G_OBJECT_CLASS (gst_pipewire_pool_parent_class)->finalize (object);
+}
+
+static void
+gst_pipewire_pool_class_init (GstPipeWirePoolClass * klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ GstBufferPoolClass *bufferpool_class = GST_BUFFER_POOL_CLASS (klass);
+
+ gobject_class->finalize = gst_pipewire_pool_finalize;
+
+ bufferpool_class->start = do_start;
+ bufferpool_class->flush_start = flush_start;
+ bufferpool_class->acquire_buffer = acquire_buffer;
+ bufferpool_class->release_buffer = release_buffer;
+
+ pool_signals[ACTIVATED] =
+ g_signal_new ("activated", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 0, G_TYPE_NONE);
+
+ GST_DEBUG_CATEGORY_INIT (gst_pipewire_pool_debug_category, "pipewirepool", 0,
+ "debug category for pipewirepool object");
+}
+
+static void
+gst_pipewire_pool_init (GstPipeWirePool * pool)
+{
+ g_cond_init (&pool->cond);
+ g_queue_init (&pool->available);
+}
diff --git a/src/gst/gstpipewirepool.h b/src/gst/gstpipewirepool.h
new file mode 100644
index 00000000..80188f71
--- /dev/null
+++ b/src/gst/gstpipewirepool.h
@@ -0,0 +1,66 @@
+/* GStreamer
+ * Copyright (C) <2016> Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __GST_PIPEWIRE_POOL_H__
+#define __GST_PIPEWIRE_POOL_H__
+
+#include <gst/gst.h>
+
+#include <pipewire/pipewire.h>
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_PIPEWIRE_POOL \
+ (gst_pipewire_pool_get_type())
+#define GST_PIPEWIRE_POOL(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_PIPEWIRE_POOL,GstPipeWirePool))
+#define GST_PIPEWIRE_POOL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_PIPEWIRE_POOL,GstPipeWirePoolClass))
+#define GST_IS_PIPEWIRE_POOL(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_PIPEWIRE_POOL))
+#define GST_IS_PIPEWIRE_POOL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_PIPEWIRE_POOL))
+#define GST_PIPEWIRE_POOL_GET_CLASS(klass) \
+ (G_TYPE_INSTANCE_GET_CLASS ((klass), GST_TYPE_PIPEWIRE_POOL, GstPipeWirePoolClass))
+
+typedef struct _GstPipeWirePool GstPipeWirePool;
+typedef struct _GstPipeWirePoolClass GstPipeWirePoolClass;
+
+struct _GstPipeWirePool {
+ GstBufferPool parent;
+
+ struct pw_stream *stream;
+ GQueue available;
+ GCond cond;
+};
+
+struct _GstPipeWirePoolClass {
+ GstBufferPoolClass parent_class;
+};
+
+GType gst_pipewire_pool_get_type (void);
+
+GstPipeWirePool * gst_pipewire_pool_new (void);
+
+gboolean gst_pipewire_pool_add_buffer (GstPipeWirePool *pool, GstBuffer *buffer);
+gboolean gst_pipewire_pool_remove_buffer (GstPipeWirePool *pool, GstBuffer *buffer);
+
+G_END_DECLS
+
+#endif /* __GST_PIPEWIRE_POOL_H__ */
diff --git a/src/gst/gstpipewiresink.c b/src/gst/gstpipewiresink.c
new file mode 100644
index 00000000..2d61fe8d
--- /dev/null
+++ b/src/gst/gstpipewiresink.c
@@ -0,0 +1,941 @@
+/* GStreamer
+ * Copyright (C) <2015> Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/**
+ * SECTION:element-pipewiresink
+ *
+ * <refsect2>
+ * <title>Example launch line</title>
+ * |[
+ * gst-launch -v videotestsrc ! pipewiresink
+ * ]| Sends a test video source to PipeWire
+ * </refsect2>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include "gstpipewiresink.h"
+
+#include <string.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#include <gio/gunixfdmessage.h>
+#include <gst/allocators/gstfdmemory.h>
+#include <gst/video/video.h>
+
+#include <spa/buffer.h>
+
+#include "gstpipewireformat.h"
+
+static GQuark process_mem_data_quark;
+
+GST_DEBUG_CATEGORY_STATIC (pipewire_sink_debug);
+#define GST_CAT_DEFAULT pipewire_sink_debug
+
+#define DEFAULT_PROP_MODE GST_PIPEWIRE_SINK_MODE_DEFAULT
+
+enum
+{
+ PROP_0,
+ PROP_PATH,
+ PROP_CLIENT_NAME,
+ PROP_STREAM_PROPERTIES,
+ PROP_MODE
+};
+
+GType
+gst_pipewire_sink_mode_get_type (void)
+{
+ static volatile gsize mode_type = 0;
+ static const GEnumValue mode[] = {
+ {GST_PIPEWIRE_SINK_MODE_DEFAULT, "GST_PIPEWIRE_SINK_MODE_DEFAULT", "default"},
+ {GST_PIPEWIRE_SINK_MODE_RENDER, "GST_PIPEWIRE_SINK_MODE_RENDER", "render"},
+ {GST_PIPEWIRE_SINK_MODE_PROVIDE, "GST_PIPEWIRE_SINK_MODE_PROVIDE", "provide"},
+ {0, NULL, NULL},
+ };
+
+ if (g_once_init_enter (&mode_type)) {
+ GType tmp =
+ g_enum_register_static ("GstPipeWireSinkMode", mode);
+ g_once_init_leave (&mode_type, tmp);
+ }
+
+ return (GType) mode_type;
+}
+
+
+static GstStaticPadTemplate gst_pipewire_sink_template =
+GST_STATIC_PAD_TEMPLATE ("sink",
+ GST_PAD_SINK,
+ GST_PAD_ALWAYS,
+ GST_STATIC_CAPS_ANY
+ );
+
+#define gst_pipewire_sink_parent_class parent_class
+G_DEFINE_TYPE (GstPipeWireSink, gst_pipewire_sink, GST_TYPE_BASE_SINK);
+
+static void gst_pipewire_sink_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec);
+static void gst_pipewire_sink_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec);
+
+static GstStateChangeReturn
+gst_pipewire_sink_change_state (GstElement * element, GstStateChange transition);
+
+static gboolean gst_pipewire_sink_setcaps (GstBaseSink * bsink, GstCaps * caps);
+static GstCaps *gst_pipewire_sink_sink_fixate (GstBaseSink * bsink,
+ GstCaps * caps);
+
+static GstFlowReturn gst_pipewire_sink_render (GstBaseSink * psink,
+ GstBuffer * buffer);
+static gboolean gst_pipewire_sink_start (GstBaseSink * basesink);
+static gboolean gst_pipewire_sink_stop (GstBaseSink * basesink);
+
+static void
+gst_pipewire_sink_finalize (GObject * object)
+{
+ GstPipeWireSink *pwsink = GST_PIPEWIRE_SINK (object);
+
+ g_object_unref (pwsink->pool);
+
+ pw_thread_loop_destroy (pwsink->main_loop);
+ pwsink->main_loop = NULL;
+
+ pw_loop_destroy (pwsink->loop);
+ pwsink->loop = NULL;
+
+ if (pwsink->properties)
+ gst_structure_free (pwsink->properties);
+ g_object_unref (pwsink->allocator);
+ g_free (pwsink->path);
+ g_free (pwsink->client_name);
+ g_hash_table_unref (pwsink->buf_ids);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static gboolean
+gst_pipewire_sink_propose_allocation (GstBaseSink * bsink, GstQuery * query)
+{
+ GstPipeWireSink *pwsink = GST_PIPEWIRE_SINK (bsink);
+
+ gst_query_add_allocation_pool (query, GST_BUFFER_POOL_CAST (pwsink->pool), 0, 0, 0);
+ return TRUE;
+}
+
+static void
+gst_pipewire_sink_class_init (GstPipeWireSinkClass * klass)
+{
+ GObjectClass *gobject_class;
+ GstElementClass *gstelement_class;
+ GstBaseSinkClass *gstbasesink_class;
+
+ gobject_class = (GObjectClass *) klass;
+ gstelement_class = (GstElementClass *) klass;
+ gstbasesink_class = (GstBaseSinkClass *) klass;
+
+ gobject_class->finalize = gst_pipewire_sink_finalize;
+ gobject_class->set_property = gst_pipewire_sink_set_property;
+ gobject_class->get_property = gst_pipewire_sink_get_property;
+
+ g_object_class_install_property (gobject_class,
+ PROP_PATH,
+ g_param_spec_string ("path",
+ "Path",
+ "The sink path to connect to (NULL = default)",
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class,
+ PROP_CLIENT_NAME,
+ g_param_spec_string ("client-name",
+ "Client Name",
+ "The client name to use (NULL = default)",
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class,
+ PROP_STREAM_PROPERTIES,
+ g_param_spec_boxed ("stream-properties",
+ "Stream properties",
+ "List of PipeWire stream properties",
+ GST_TYPE_STRUCTURE,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class,
+ PROP_MODE,
+ g_param_spec_enum ("mode",
+ "Mode",
+ "The mode to operate in",
+ GST_TYPE_PIPEWIRE_SINK_MODE,
+ DEFAULT_PROP_MODE,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ gstelement_class->change_state = gst_pipewire_sink_change_state;
+
+ gst_element_class_set_static_metadata (gstelement_class,
+ "PipeWire sink", "Sink/Video",
+ "Send video to PipeWire", "Wim Taymans <wim.taymans@gmail.com>");
+
+ gst_element_class_add_pad_template (gstelement_class,
+ gst_static_pad_template_get (&gst_pipewire_sink_template));
+
+ gstbasesink_class->set_caps = gst_pipewire_sink_setcaps;
+ gstbasesink_class->fixate = gst_pipewire_sink_sink_fixate;
+ gstbasesink_class->propose_allocation = gst_pipewire_sink_propose_allocation;
+ gstbasesink_class->start = gst_pipewire_sink_start;
+ gstbasesink_class->stop = gst_pipewire_sink_stop;
+ gstbasesink_class->render = gst_pipewire_sink_render;
+
+ GST_DEBUG_CATEGORY_INIT (pipewire_sink_debug, "pipewiresink", 0,
+ "PipeWire Sink");
+
+ process_mem_data_quark = g_quark_from_static_string ("GstPipeWireSinkProcessMemQuark");
+}
+
+
+#define PROP(f,key,type,...) \
+ SPA_POD_PROP (f,key,0,type,1,__VA_ARGS__)
+#define PROP_R(f,key,type,...) \
+ SPA_POD_PROP (f,key,SPA_POD_PROP_FLAG_READONLY,type,1,__VA_ARGS__)
+#define PROP_MM(f,key,type,...) \
+ SPA_POD_PROP (f,key,SPA_POD_PROP_RANGE_MIN_MAX,type,3,__VA_ARGS__)
+#define PROP_U_MM(f,key,type,...) \
+ SPA_POD_PROP (f,key,SPA_POD_PROP_FLAG_UNSET | \
+ SPA_POD_PROP_RANGE_MIN_MAX,type,3,__VA_ARGS__)
+#define PROP_EN(f,key,type,n,...) \
+ SPA_POD_PROP (f,key,SPA_POD_PROP_RANGE_ENUM,type,n,__VA_ARGS__)
+#define PROP_U_EN(f,key,type,n,...) \
+ SPA_POD_PROP (f,key,SPA_POD_PROP_FLAG_UNSET | \
+ SPA_POD_PROP_RANGE_ENUM,type,n,__VA_ARGS__)
+static void
+pool_activated (GstPipeWirePool *pool, GstPipeWireSink *sink)
+{
+ struct pw_remote *remote = sink->stream->remote;
+ struct pw_core *core = remote->core;
+ GstStructure *config;
+ GstCaps *caps;
+ guint size;
+ guint min_buffers;
+ guint max_buffers;
+ struct spa_param *port_params[3];
+ struct spa_pod_builder b = { NULL };
+ uint8_t buffer[1024];
+ struct spa_pod_frame f[2];
+
+ config = gst_buffer_pool_get_config (GST_BUFFER_POOL (pool));
+ gst_buffer_pool_config_get_params (config, &caps, &size, &min_buffers, &max_buffers);
+
+ spa_pod_builder_init (&b, buffer, sizeof (buffer));
+ spa_pod_builder_push_object (&b, &f[0], 0, core->type.param_alloc_buffers.Buffers);
+ if (size == 0)
+ spa_pod_builder_add (&b,
+ PROP_U_MM (&f[1], core->type.param_alloc_buffers.size, SPA_POD_TYPE_INT, 0, 0, INT32_MAX), 0);
+ else
+ spa_pod_builder_add (&b,
+ PROP_MM (&f[1], core->type.param_alloc_buffers.size, SPA_POD_TYPE_INT, size, size, INT32_MAX), 0);
+
+ spa_pod_builder_add (&b,
+ PROP_MM (&f[1], core->type.param_alloc_buffers.stride, SPA_POD_TYPE_INT, 0, 0, INT32_MAX),
+ PROP_U_MM (&f[1], core->type.param_alloc_buffers.buffers, SPA_POD_TYPE_INT, min_buffers, min_buffers, max_buffers ? max_buffers : INT32_MAX),
+ PROP (&f[1], core->type.param_alloc_buffers.align, SPA_POD_TYPE_INT, 16),
+ 0);
+ spa_pod_builder_pop (&b, &f[0]);
+ port_params[0] = SPA_POD_BUILDER_DEREF (&b, f[0].ref, struct spa_param);
+
+ spa_pod_builder_object (&b, &f[0], 0, core->type.param_alloc_meta_enable.MetaEnable,
+ PROP (&f[1], core->type.param_alloc_meta_enable.type, SPA_POD_TYPE_ID, core->type.meta.Header),
+ PROP (&f[1], core->type.param_alloc_meta_enable.size, SPA_POD_TYPE_INT, sizeof (struct spa_meta_header)));
+ port_params[1] = SPA_POD_BUILDER_DEREF (&b, f[0].ref, struct spa_param);
+
+ spa_pod_builder_object (&b, &f[0], 0, core->type.param_alloc_meta_enable.MetaEnable,
+ PROP (&f[1], core->type.param_alloc_meta_enable.type, SPA_POD_TYPE_ID, core->type.meta.Ringbuffer),
+ PROP (&f[1], core->type.param_alloc_meta_enable.size, SPA_POD_TYPE_INT, sizeof (struct spa_meta_ringbuffer)),
+ PROP (&f[1], core->type.param_alloc_meta_enable.ringbufferSize, SPA_POD_TYPE_INT,
+ size * SPA_MAX (4,
+ SPA_MAX (min_buffers, max_buffers))),
+ PROP (&f[1], core->type.param_alloc_meta_enable.ringbufferStride, SPA_POD_TYPE_INT, 0),
+ PROP (&f[1], core->type.param_alloc_meta_enable.ringbufferBlocks, SPA_POD_TYPE_INT, 1),
+ PROP (&f[1], core->type.param_alloc_meta_enable.ringbufferAlign, SPA_POD_TYPE_INT, 16));
+ port_params[2] = SPA_POD_BUILDER_DEREF (&b, f[0].ref, struct spa_param);
+
+ pw_thread_loop_lock (sink->main_loop);
+ pw_stream_finish_format (sink->stream, SPA_RESULT_OK, port_params, 2);
+ pw_thread_loop_unlock (sink->main_loop);
+}
+
+static void
+gst_pipewire_sink_init (GstPipeWireSink * sink)
+{
+ sink->allocator = gst_fd_allocator_new ();
+ sink->pool = gst_pipewire_pool_new ();
+ sink->client_name = pw_get_client_name();
+ sink->mode = DEFAULT_PROP_MODE;
+
+ g_signal_connect (sink->pool, "activated", G_CALLBACK (pool_activated), sink);
+
+ sink->buf_ids = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL,
+ (GDestroyNotify) gst_buffer_unref);
+
+ g_queue_init (&sink->queue);
+
+ sink->loop = pw_loop_new ();
+ sink->main_loop = pw_thread_loop_new (sink->loop, "pipewire-sink-loop");
+ sink->core = pw_core_new (sink->loop, NULL);
+ GST_DEBUG ("loop %p %p", sink->loop, sink->main_loop);
+}
+
+static GstCaps *
+gst_pipewire_sink_sink_fixate (GstBaseSink * bsink, GstCaps * caps)
+{
+ GstStructure *structure;
+
+ caps = gst_caps_make_writable (caps);
+
+ structure = gst_caps_get_structure (caps, 0);
+
+ if (gst_structure_has_name (structure, "video/x-raw")) {
+ gst_structure_fixate_field_nearest_int (structure, "width", 320);
+ gst_structure_fixate_field_nearest_int (structure, "height", 240);
+ gst_structure_fixate_field_nearest_fraction (structure, "framerate", 30, 1);
+
+ if (gst_structure_has_field (structure, "pixel-aspect-ratio"))
+ gst_structure_fixate_field_nearest_fraction (structure,
+ "pixel-aspect-ratio", 1, 1);
+ else
+ gst_structure_set (structure, "pixel-aspect-ratio", GST_TYPE_FRACTION, 1, 1,
+ NULL);
+
+ if (gst_structure_has_field (structure, "colorimetry"))
+ gst_structure_fixate_field_string (structure, "colorimetry", "bt601");
+ if (gst_structure_has_field (structure, "chroma-site"))
+ gst_structure_fixate_field_string (structure, "chroma-site", "mpeg2");
+
+ if (gst_structure_has_field (structure, "interlace-mode"))
+ gst_structure_fixate_field_string (structure, "interlace-mode",
+ "progressive");
+ else
+ gst_structure_set (structure, "interlace-mode", G_TYPE_STRING,
+ "progressive", NULL);
+ } else if (gst_structure_has_name (structure, "audio/x-raw")) {
+ gst_structure_fixate_field_string (structure, "format", "S16LE");
+ gst_structure_fixate_field_nearest_int (structure, "channels", 2);
+ gst_structure_fixate_field_nearest_int (structure, "rate", 44100);
+ }
+
+ caps = GST_BASE_SINK_CLASS (parent_class)->fixate (bsink, caps);
+
+ return caps;
+}
+
+static void
+gst_pipewire_sink_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ GstPipeWireSink *pwsink = GST_PIPEWIRE_SINK (object);
+
+ switch (prop_id) {
+ case PROP_PATH:
+ g_free (pwsink->path);
+ pwsink->path = g_value_dup_string (value);
+ break;
+
+ case PROP_CLIENT_NAME:
+ g_free (pwsink->client_name);
+ pwsink->client_name = g_value_dup_string (value);
+ break;
+
+ case PROP_STREAM_PROPERTIES:
+ if (pwsink->properties)
+ gst_structure_free (pwsink->properties);
+ pwsink->properties =
+ gst_structure_copy (gst_value_get_structure (value));
+ break;
+
+ case PROP_MODE:
+ pwsink->mode = g_value_get_enum (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_pipewire_sink_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec)
+{
+ GstPipeWireSink *pwsink = GST_PIPEWIRE_SINK (object);
+
+ switch (prop_id) {
+ case PROP_PATH:
+ g_value_set_string (value, pwsink->path);
+ break;
+
+ case PROP_CLIENT_NAME:
+ g_value_set_string (value, pwsink->client_name);
+ break;
+
+ case PROP_STREAM_PROPERTIES:
+ gst_value_set_structure (value, pwsink->properties);
+ break;
+
+ case PROP_MODE:
+ g_value_set_enum (value, pwsink->mode);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+typedef struct {
+ GstPipeWireSink *sink;
+ guint id;
+ struct spa_buffer *buf;
+ struct spa_meta_header *header;
+ guint flags;
+ goffset offset;
+} ProcessMemData;
+
+static void
+process_mem_data_destroy (gpointer user_data)
+{
+ ProcessMemData *data = user_data;
+
+ gst_object_unref (data->sink);
+ g_slice_free (ProcessMemData, data);
+}
+
+static void
+on_add_buffer (struct pw_listener *listener,
+ struct pw_stream *stream,
+ uint32_t id)
+{
+ GstPipeWireSink *pwsink = SPA_CONTAINER_OF (listener, GstPipeWireSink, stream_add_buffer);
+ struct spa_buffer *b;
+ GstBuffer *buf;
+ uint32_t i;
+ ProcessMemData data;
+ struct pw_core *core = pwsink->remote->core;
+
+ GST_LOG_OBJECT (pwsink, "add buffer");
+
+ if (!(b = pw_stream_peek_buffer (pwsink->stream, id))) {
+ g_warning ("failed to peek buffer");
+ return;
+ }
+
+ buf = gst_buffer_new ();
+
+ data.sink = gst_object_ref (pwsink);
+ data.id = id;
+ data.buf = b;
+ data.header = spa_buffer_find_meta (b, core->type.meta.Header);
+
+ for (i = 0; i < b->n_datas; i++) {
+ struct spa_data *d = &b->datas[i];
+ GstMemory *gmem = NULL;
+
+ if (d->type == core->type.data.MemFd ||
+ d->type == core->type.data.DmaBuf) {
+ gmem = gst_fd_allocator_alloc (pwsink->allocator, dup (d->fd),
+ d->mapoffset + d->maxsize, GST_FD_MEMORY_FLAG_NONE);
+ gst_memory_resize (gmem, d->chunk->offset + d->mapoffset, d->chunk->size);
+ data.offset = d->mapoffset;
+ }
+ else if (d->type == core->type.data.MemPtr) {
+ gmem = gst_memory_new_wrapped (0, d->data, d->maxsize, d->chunk->offset,
+ d->chunk->size, NULL, NULL);
+ data.offset = 0;
+ }
+ if (gmem)
+ gst_buffer_append_memory (buf, gmem);
+ }
+ data.flags = GST_BUFFER_FLAGS (buf);
+ gst_mini_object_set_qdata (GST_MINI_OBJECT_CAST (buf),
+ process_mem_data_quark,
+ g_slice_dup (ProcessMemData, &data),
+ process_mem_data_destroy);
+
+ gst_pipewire_pool_add_buffer (pwsink->pool, buf);
+ g_hash_table_insert (pwsink->buf_ids, GINT_TO_POINTER (id), buf);
+
+ pw_thread_loop_signal (pwsink->main_loop, FALSE);
+}
+
+static void
+on_remove_buffer (struct pw_listener *listener,
+ struct pw_stream *stream,
+ uint32_t id)
+{
+ GstPipeWireSink *pwsink = SPA_CONTAINER_OF (listener, GstPipeWireSink, stream_remove_buffer);
+ GstBuffer *buf;
+
+ GST_LOG_OBJECT (pwsink, "remove buffer");
+ buf = g_hash_table_lookup (pwsink->buf_ids, GINT_TO_POINTER (id));
+ if (buf) {
+ GST_MINI_OBJECT_CAST (buf)->dispose = NULL;
+ if (!gst_pipewire_pool_remove_buffer (pwsink->pool, buf))
+ gst_buffer_ref (buf);
+ if (g_queue_remove (&pwsink->queue, buf))
+ gst_buffer_unref (buf);
+ g_hash_table_remove (pwsink->buf_ids, GINT_TO_POINTER (id));
+ }
+}
+
+static void
+on_new_buffer (struct pw_listener *listener,
+ struct pw_stream *stream,
+ uint32_t id)
+{
+ GstPipeWireSink *pwsink = SPA_CONTAINER_OF (listener, GstPipeWireSink, stream_new_buffer);
+ GstBuffer *buf;
+
+ GST_LOG_OBJECT (pwsink, "got new buffer %u", id);
+ if (pwsink->stream == NULL) {
+ GST_LOG_OBJECT (pwsink, "no stream");
+ return;
+ }
+ buf = g_hash_table_lookup (pwsink->buf_ids, GINT_TO_POINTER (id));
+
+ if (buf) {
+ gst_buffer_unref (buf);
+ pw_thread_loop_signal (pwsink->main_loop, FALSE);
+ }
+}
+
+static void
+do_send_buffer (GstPipeWireSink *pwsink)
+{
+ GstBuffer *buffer;
+ ProcessMemData *data;
+ gboolean res;
+ guint i;
+
+ buffer = g_queue_pop_head (&pwsink->queue);
+ if (buffer == NULL) {
+ GST_WARNING ("out of buffers");
+ return;
+ }
+
+ data = gst_mini_object_get_qdata (GST_MINI_OBJECT_CAST (buffer),
+ process_mem_data_quark);
+
+ if (data->header) {
+ data->header->seq = GST_BUFFER_OFFSET (buffer);
+ data->header->pts = GST_BUFFER_PTS (buffer);
+ data->header->dts_offset = GST_BUFFER_DTS (buffer);
+ }
+ for (i = 0; i < data->buf->n_datas; i++) {
+ struct spa_data *d = &data->buf->datas[i];
+ GstMemory *mem = gst_buffer_peek_memory (buffer, i);
+ d->chunk->offset = mem->offset - data->offset;
+ d->chunk->size = mem->size;
+ }
+
+ if (!(res = pw_stream_send_buffer (pwsink->stream, data->id))) {
+ g_warning ("can't send buffer");
+ pw_thread_loop_signal (pwsink->main_loop, FALSE);
+ } else
+ pwsink->need_ready--;
+}
+
+
+static void
+on_need_buffer (struct pw_listener *listener,
+ struct pw_stream *stream)
+{
+ GstPipeWireSink *pwsink = SPA_CONTAINER_OF (listener, GstPipeWireSink, stream_need_buffer);
+
+ pwsink->need_ready++;
+ GST_DEBUG ("need buffer %u", pwsink->need_ready);
+ do_send_buffer (pwsink);
+}
+
+static void
+on_state_changed (struct pw_listener *listener,
+ struct pw_stream *stream)
+{
+ GstPipeWireSink *pwsink = SPA_CONTAINER_OF (listener, GstPipeWireSink, stream_state_changed);
+ enum pw_stream_state state;
+
+ state = stream->state;
+ GST_DEBUG ("got stream state %d", state);
+
+ switch (state) {
+ case PW_STREAM_STATE_UNCONNECTED:
+ case PW_STREAM_STATE_CONNECTING:
+ case PW_STREAM_STATE_CONFIGURE:
+ case PW_STREAM_STATE_READY:
+ case PW_STREAM_STATE_PAUSED:
+ case PW_STREAM_STATE_STREAMING:
+ break;
+ case PW_STREAM_STATE_ERROR:
+ GST_ELEMENT_ERROR (pwsink, RESOURCE, FAILED,
+ ("stream error: %s", stream->error), (NULL));
+ break;
+ }
+ pw_thread_loop_signal (pwsink->main_loop, FALSE);
+}
+
+static void
+on_format_changed (struct pw_listener *listener,
+ struct pw_stream *stream,
+ struct spa_format *format)
+{
+ GstPipeWireSink *pwsink = SPA_CONTAINER_OF (listener, GstPipeWireSink, stream_format_changed);
+
+ if (gst_buffer_pool_is_active (GST_BUFFER_POOL_CAST (pwsink->pool)))
+ pool_activated (pwsink->pool, pwsink);
+}
+
+static gboolean
+gst_pipewire_sink_setcaps (GstBaseSink * bsink, GstCaps * caps)
+{
+ GstPipeWireSink *pwsink;
+ GPtrArray *possible;
+ enum pw_stream_state state;
+ gboolean res = FALSE;
+
+ pwsink = GST_PIPEWIRE_SINK (bsink);
+
+ possible = gst_caps_to_format_all (caps, pwsink->remote->core->type.map);
+
+ pw_thread_loop_lock (pwsink->main_loop);
+ state = pwsink->stream->state;
+
+ if (state == PW_STREAM_STATE_ERROR)
+ goto start_error;
+
+ if (state == PW_STREAM_STATE_UNCONNECTED) {
+ enum pw_stream_flags flags = 0;
+
+ if (pwsink->mode != GST_PIPEWIRE_SINK_MODE_PROVIDE)
+ flags |= PW_STREAM_FLAG_AUTOCONNECT;
+
+ pw_stream_connect (pwsink->stream,
+ PW_DIRECTION_OUTPUT,
+ PW_STREAM_MODE_BUFFER,
+ pwsink->path,
+ flags,
+ possible->len,
+ (const struct spa_format **) possible->pdata);
+
+ while (TRUE) {
+ state = pwsink->stream->state;
+
+ if (state == PW_STREAM_STATE_READY)
+ break;
+
+ if (state == PW_STREAM_STATE_ERROR)
+ goto start_error;
+
+ pw_thread_loop_wait (pwsink->main_loop);
+ }
+ }
+ res = TRUE;
+
+ pw_thread_loop_unlock (pwsink->main_loop);
+
+ pwsink->negotiated = res;
+
+ return res;
+
+start_error:
+ {
+ GST_ERROR ("could not start stream");
+ pw_thread_loop_unlock (pwsink->main_loop);
+ g_ptr_array_unref (possible);
+ return FALSE;
+ }
+}
+
+static GstFlowReturn
+gst_pipewire_sink_render (GstBaseSink * bsink, GstBuffer * buffer)
+{
+ GstPipeWireSink *pwsink;
+ GstFlowReturn res = GST_FLOW_OK;
+
+ pwsink = GST_PIPEWIRE_SINK (bsink);
+
+ if (!pwsink->negotiated)
+ goto not_negotiated;
+
+ pw_thread_loop_lock (pwsink->main_loop);
+ if (pwsink->stream->state != PW_STREAM_STATE_STREAMING)
+ goto done;
+
+ if (buffer->pool != GST_BUFFER_POOL_CAST (pwsink->pool)) {
+ GstBuffer *b = NULL;
+ GstMapInfo info = { 0, };
+
+ if (!gst_buffer_pool_is_active (GST_BUFFER_POOL_CAST (pwsink->pool)))
+ gst_buffer_pool_set_active (GST_BUFFER_POOL_CAST (pwsink->pool), TRUE);
+
+ if ((res = gst_buffer_pool_acquire_buffer (GST_BUFFER_POOL_CAST (pwsink->pool), &b, NULL)) != GST_FLOW_OK)
+ goto done;
+
+ gst_buffer_map (b, &info, GST_MAP_WRITE);
+ gst_buffer_extract (buffer, 0, info.data, info.size);
+ gst_buffer_unmap (b, &info);
+ gst_buffer_resize (b, 0, gst_buffer_get_size (buffer));
+ buffer = b;
+ } else {
+ gst_buffer_ref (buffer);
+ }
+
+ GST_DEBUG ("push buffer in queue");
+ g_queue_push_tail (&pwsink->queue, buffer);
+
+ if (pwsink->need_ready && pwsink->mode == GST_PIPEWIRE_SINK_MODE_PROVIDE)
+ do_send_buffer (pwsink);
+
+done:
+ pw_thread_loop_unlock (pwsink->main_loop);
+
+ return res;
+
+not_negotiated:
+ {
+ return GST_FLOW_NOT_NEGOTIATED;
+ }
+}
+
+static gboolean
+copy_properties (GQuark field_id,
+ const GValue *value,
+ gpointer user_data)
+{
+ struct pw_properties *properties = user_data;
+
+ if (G_VALUE_HOLDS_STRING (value))
+ pw_properties_set (properties,
+ g_quark_to_string (field_id),
+ g_value_get_string (value));
+ return TRUE;
+}
+
+static gboolean
+gst_pipewire_sink_start (GstBaseSink * basesink)
+{
+ GstPipeWireSink *pwsink = GST_PIPEWIRE_SINK (basesink);
+ struct pw_properties *props;
+
+ pwsink->negotiated = FALSE;
+
+ if (pwsink->properties) {
+ props = pw_properties_new (NULL, NULL);
+ gst_structure_foreach (pwsink->properties, copy_properties, props);
+ } else {
+ props = NULL;
+ }
+
+ pw_thread_loop_lock (pwsink->main_loop);
+ pwsink->stream = pw_stream_new (pwsink->remote, pwsink->client_name, props);
+ pwsink->pool->stream = pwsink->stream;
+
+ pw_signal_add (&pwsink->stream->state_changed, &pwsink->stream_state_changed, on_state_changed);
+ pw_signal_add (&pwsink->stream->format_changed, &pwsink->stream_format_changed, on_format_changed);
+ pw_signal_add (&pwsink->stream->add_buffer, &pwsink->stream_add_buffer, on_add_buffer);
+ pw_signal_add (&pwsink->stream->remove_buffer, &pwsink->stream_remove_buffer, on_remove_buffer);
+ pw_signal_add (&pwsink->stream->new_buffer, &pwsink->stream_new_buffer, on_new_buffer);
+ pw_signal_add (&pwsink->stream->need_buffer, &pwsink->stream_need_buffer, on_need_buffer);
+ pw_thread_loop_unlock (pwsink->main_loop);
+
+ return TRUE;
+}
+
+static gboolean
+gst_pipewire_sink_stop (GstBaseSink * basesink)
+{
+ GstPipeWireSink *pwsink = GST_PIPEWIRE_SINK (basesink);
+
+ pw_thread_loop_lock (pwsink->main_loop);
+ if (pwsink->stream) {
+ pw_stream_disconnect (pwsink->stream);
+ pw_stream_destroy (pwsink->stream);
+ pwsink->stream = NULL;
+ pwsink->pool->stream = NULL;
+ }
+ pw_thread_loop_unlock (pwsink->main_loop);
+
+ pwsink->negotiated = FALSE;
+
+ return TRUE;
+}
+
+static void
+on_remote_state_changed (struct pw_listener *listener,
+ struct pw_remote *remote)
+{
+ GstPipeWireSink *pwsink = SPA_CONTAINER_OF (listener, GstPipeWireSink, remote_state_changed);
+ enum pw_remote_state state;
+
+ state = remote->state;
+ GST_DEBUG ("got remote state %d", state);
+
+ switch (state) {
+ case PW_REMOTE_STATE_UNCONNECTED:
+ case PW_REMOTE_STATE_CONNECTING:
+ case PW_REMOTE_STATE_CONNECTED:
+ break;
+ case PW_REMOTE_STATE_ERROR:
+ GST_ELEMENT_ERROR (pwsink, RESOURCE, FAILED,
+ ("remote error: %s", remote->error), (NULL));
+ break;
+ }
+ pw_thread_loop_signal (pwsink->main_loop, FALSE);
+}
+
+static gboolean
+gst_pipewire_sink_open (GstPipeWireSink * pwsink)
+{
+ if (pw_thread_loop_start (pwsink->main_loop) != SPA_RESULT_OK)
+ goto mainloop_error;
+
+ pw_thread_loop_lock (pwsink->main_loop);
+ pwsink->remote = pw_remote_new (pwsink->core, NULL);
+
+ pw_signal_add (&pwsink->remote->state_changed, &pwsink->remote_state_changed, on_remote_state_changed);
+
+ pw_remote_connect (pwsink->remote);
+
+ while (TRUE) {
+ enum pw_remote_state state = pwsink->remote->state;
+
+ if (state == PW_REMOTE_STATE_CONNECTED)
+ break;
+
+ if (state == PW_REMOTE_STATE_ERROR)
+ goto connect_error;
+
+ pw_thread_loop_wait (pwsink->main_loop);
+ }
+ pw_thread_loop_unlock (pwsink->main_loop);
+
+ return TRUE;
+
+ /* ERRORS */
+mainloop_error:
+ {
+ GST_ELEMENT_ERROR (pwsink, RESOURCE, FAILED,
+ ("Failed to start mainloop"), (NULL));
+ return FALSE;
+ }
+connect_error:
+ {
+ pw_thread_loop_unlock (pwsink->main_loop);
+ return FALSE;
+ }
+}
+
+static gboolean
+gst_pipewire_sink_close (GstPipeWireSink * pwsink)
+{
+ pw_thread_loop_lock (pwsink->main_loop);
+ if (pwsink->stream) {
+ pw_stream_disconnect (pwsink->stream);
+ }
+ if (pwsink->remote) {
+ pw_remote_disconnect (pwsink->remote);
+
+ while (TRUE) {
+ enum pw_remote_state state = pwsink->remote->state;
+
+ if (state == PW_REMOTE_STATE_UNCONNECTED)
+ break;
+
+ if (state == PW_REMOTE_STATE_ERROR)
+ break;
+
+ pw_thread_loop_wait (pwsink->main_loop);
+ }
+ }
+ pw_thread_loop_unlock (pwsink->main_loop);
+
+ pw_thread_loop_stop (pwsink->main_loop);
+
+ if (pwsink->stream) {
+ pw_stream_destroy (pwsink->stream);
+ pwsink->stream = NULL;
+ }
+
+ if (pwsink->remote) {
+ pw_remote_destroy (pwsink->remote);
+ pwsink->remote = NULL;
+ }
+
+ return TRUE;
+}
+
+static GstStateChangeReturn
+gst_pipewire_sink_change_state (GstElement * element, GstStateChange transition)
+{
+ GstStateChangeReturn ret;
+ GstPipeWireSink *this = GST_PIPEWIRE_SINK_CAST (element);
+
+ switch (transition) {
+ case GST_STATE_CHANGE_NULL_TO_READY:
+ if (!gst_pipewire_sink_open (this))
+ goto open_failed;
+ break;
+ case GST_STATE_CHANGE_READY_TO_PAUSED:
+ break;
+ case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
+ /* uncork and start recording */
+ break;
+ case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
+ /* stop recording ASAP by corking */
+ break;
+ default:
+ break;
+ }
+
+ ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
+
+ switch (transition) {
+ case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
+ break;
+ case GST_STATE_CHANGE_PAUSED_TO_READY:
+ break;
+ case GST_STATE_CHANGE_READY_TO_NULL:
+ gst_pipewire_sink_close (this);
+ break;
+ default:
+ break;
+ }
+ return ret;
+
+ /* ERRORS */
+open_failed:
+ {
+ return GST_STATE_CHANGE_FAILURE;
+ }
+}
diff --git a/src/gst/gstpipewiresink.h b/src/gst/gstpipewiresink.h
new file mode 100644
index 00000000..d4287d6f
--- /dev/null
+++ b/src/gst/gstpipewiresink.h
@@ -0,0 +1,114 @@
+/* GStreamer
+ * Copyright (C) <2015> Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __GST_PIPEWIRE_SINK_H__
+#define __GST_PIPEWIRE_SINK_H__
+
+#include <gst/gst.h>
+#include <gst/base/gstbasesink.h>
+
+#include <pipewire/pipewire.h>
+#include <gst/gstpipewirepool.h>
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_PIPEWIRE_SINK \
+ (gst_pipewire_sink_get_type())
+#define GST_PIPEWIRE_SINK(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_PIPEWIRE_SINK,GstPipeWireSink))
+#define GST_PIPEWIRE_SINK_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_PIPEWIRE_SINK,GstPipeWireSinkClass))
+#define GST_IS_PIPEWIRE_SINK(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_PIPEWIRE_SINK))
+#define GST_IS_PIPEWIRE_SINK_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_PIPEWIRE_SINK))
+#define GST_PIPEWIRE_SINK_CAST(obj) \
+ ((GstPipeWireSink *) (obj))
+
+typedef struct _GstPipeWireSink GstPipeWireSink;
+typedef struct _GstPipeWireSinkClass GstPipeWireSinkClass;
+
+
+/**
+ * GstPipeWireSinkMode:
+ * @GST_PIPEWIRE_SINK_MODE_DEFAULT: the default mode as configured in the server
+ * @GST_PIPEWIRE_SINK_MODE_RENDER: try to render the media
+ * @GST_PIPEWIRE_SINK_MODE_PROVIDE: provide the media
+ *
+ * Different modes of operation.
+ */
+typedef enum
+{
+ GST_PIPEWIRE_SINK_MODE_DEFAULT,
+ GST_PIPEWIRE_SINK_MODE_RENDER,
+ GST_PIPEWIRE_SINK_MODE_PROVIDE,
+} GstPipeWireSinkMode;
+
+#define GST_TYPE_PIPEWIRE_SINK_MODE (gst_pipewire_sink_mode_get_type ())
+
+/**
+ * GstPipeWireSink:
+ *
+ * Opaque data structure.
+ */
+struct _GstPipeWireSink {
+ GstBaseSink element;
+
+ /*< private >*/
+ gchar *path;
+ gchar *client_name;
+
+ /* video state */
+ gboolean negotiated;
+
+ struct pw_loop *loop;
+ struct pw_thread_loop *main_loop;
+
+ struct pw_core *core;
+ struct pw_remote *remote;
+ struct pw_listener remote_state_changed;
+
+ struct pw_stream *stream;
+ struct pw_listener stream_state_changed;
+ struct pw_listener stream_format_changed;
+ struct pw_listener stream_add_buffer;
+ struct pw_listener stream_remove_buffer;
+ struct pw_listener stream_new_buffer;
+ struct pw_listener stream_need_buffer;
+
+ GstAllocator *allocator;
+ GstStructure *properties;
+ GstPipeWireSinkMode mode;
+
+ GstPipeWirePool *pool;
+ GHashTable *buf_ids;
+ GQueue queue;
+ guint need_ready;
+};
+
+struct _GstPipeWireSinkClass {
+ GstBaseSinkClass parent_class;
+};
+
+GType gst_pipewire_sink_get_type (void);
+GType gst_pipewire_sink_mode_get_type (void);
+
+G_END_DECLS
+
+#endif /* __GST_PIPEWIRE_SINK_H__ */
diff --git a/src/gst/gstpipewiresrc.c b/src/gst/gstpipewiresrc.c
new file mode 100644
index 00000000..0395b19f
--- /dev/null
+++ b/src/gst/gstpipewiresrc.c
@@ -0,0 +1,1154 @@
+/* GStreamer
+ * Copyright (C) <2015> Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/**
+ * SECTION:element-pipewiresrc
+ *
+ * <refsect2>
+ * <title>Example launch line</title>
+ * |[
+ * gst-launch -v pipewiresrc ! videoconvert ! ximagesink
+ * ]| Shows pipewire output in an X window.
+ * </refsect2>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include "gstpipewiresrc.h"
+#include "gstpipewireformat.h"
+
+#include <string.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#include <gio/gunixfdmessage.h>
+#include <gst/net/gstnetclientclock.h>
+#include <gst/allocators/gstfdmemory.h>
+#include <gst/video/video.h>
+
+#include <spa/buffer.h>
+
+#include "gstpipewireclock.h"
+
+static GQuark process_mem_data_quark;
+
+GST_DEBUG_CATEGORY_STATIC (pipewire_src_debug);
+#define GST_CAT_DEFAULT pipewire_src_debug
+
+#define DEFAULT_ALWAYS_COPY false
+
+enum
+{
+ PROP_0,
+ PROP_PATH,
+ PROP_CLIENT_NAME,
+ PROP_STREAM_PROPERTIES,
+ PROP_ALWAYS_COPY,
+};
+
+
+static GstStaticPadTemplate gst_pipewire_src_template =
+GST_STATIC_PAD_TEMPLATE ("src",
+ GST_PAD_SRC,
+ GST_PAD_ALWAYS,
+ GST_STATIC_CAPS_ANY
+ );
+
+#define gst_pipewire_src_parent_class parent_class
+G_DEFINE_TYPE (GstPipeWireSrc, gst_pipewire_src, GST_TYPE_PUSH_SRC);
+
+static GstStateChangeReturn
+gst_pipewire_src_change_state (GstElement * element, GstStateChange transition);
+
+static gboolean gst_pipewire_src_negotiate (GstBaseSrc * basesrc);
+
+static GstFlowReturn gst_pipewire_src_create (GstPushSrc * psrc,
+ GstBuffer ** buffer);
+static gboolean gst_pipewire_src_unlock (GstBaseSrc * basesrc);
+static gboolean gst_pipewire_src_unlock_stop (GstBaseSrc * basesrc);
+static gboolean gst_pipewire_src_start (GstBaseSrc * basesrc);
+static gboolean gst_pipewire_src_stop (GstBaseSrc * basesrc);
+static gboolean gst_pipewire_src_event (GstBaseSrc * src, GstEvent * event);
+static gboolean gst_pipewire_src_query (GstBaseSrc * src, GstQuery * query);
+
+static void
+gst_pipewire_src_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ GstPipeWireSrc *pwsrc = GST_PIPEWIRE_SRC (object);
+
+ switch (prop_id) {
+ case PROP_PATH:
+ g_free (pwsrc->path);
+ pwsrc->path = g_value_dup_string (value);
+ break;
+
+ case PROP_CLIENT_NAME:
+ g_free (pwsrc->client_name);
+ pwsrc->client_name = g_value_dup_string (value);
+ break;
+
+ case PROP_STREAM_PROPERTIES:
+ if (pwsrc->properties)
+ gst_structure_free (pwsrc->properties);
+ pwsrc->properties =
+ gst_structure_copy (gst_value_get_structure (value));
+ break;
+
+ case PROP_ALWAYS_COPY:
+ pwsrc->always_copy = g_value_get_boolean (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_pipewire_src_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec)
+{
+ GstPipeWireSrc *pwsrc = GST_PIPEWIRE_SRC (object);
+
+ switch (prop_id) {
+ case PROP_PATH:
+ g_value_set_string (value, pwsrc->path);
+ break;
+
+ case PROP_CLIENT_NAME:
+ g_value_set_string (value, pwsrc->client_name);
+ break;
+
+ case PROP_STREAM_PROPERTIES:
+ gst_value_set_structure (value, pwsrc->properties);
+ break;
+
+ case PROP_ALWAYS_COPY:
+ g_value_set_boolean (value, pwsrc->always_copy);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static GstClock *
+gst_pipewire_src_provide_clock (GstElement * elem)
+{
+ GstPipeWireSrc *pwsrc = GST_PIPEWIRE_SRC (elem);
+ GstClock *clock;
+
+ GST_OBJECT_LOCK (pwsrc);
+ if (!GST_OBJECT_FLAG_IS_SET (pwsrc, GST_ELEMENT_FLAG_PROVIDE_CLOCK))
+ goto clock_disabled;
+
+ if (pwsrc->clock && pwsrc->is_live)
+ clock = GST_CLOCK_CAST (gst_object_ref (pwsrc->clock));
+ else
+ clock = NULL;
+ GST_OBJECT_UNLOCK (pwsrc);
+
+ return clock;
+
+ /* ERRORS */
+clock_disabled:
+ {
+ GST_DEBUG_OBJECT (pwsrc, "clock provide disabled");
+ GST_OBJECT_UNLOCK (pwsrc);
+ return NULL;
+ }
+}
+
+static void
+clear_queue (GstPipeWireSrc *pwsrc)
+{
+ g_queue_foreach (&pwsrc->queue, (GFunc) gst_mini_object_unref, NULL);
+ g_queue_clear (&pwsrc->queue);
+}
+
+static void
+gst_pipewire_src_finalize (GObject * object)
+{
+ GstPipeWireSrc *pwsrc = GST_PIPEWIRE_SRC (object);
+
+ clear_queue (pwsrc);
+
+ pw_thread_loop_destroy (pwsrc->main_loop);
+ pwsrc->main_loop = NULL;
+ pw_loop_destroy (pwsrc->loop);
+ pwsrc->loop = NULL;
+
+ if (pwsrc->properties)
+ gst_structure_free (pwsrc->properties);
+ g_object_unref (pwsrc->fd_allocator);
+ if (pwsrc->clock)
+ gst_object_unref (pwsrc->clock);
+ g_free (pwsrc->path);
+ g_free (pwsrc->client_name);
+ g_hash_table_unref (pwsrc->buf_ids);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gst_pipewire_src_class_init (GstPipeWireSrcClass * klass)
+{
+ GObjectClass *gobject_class;
+ GstElementClass *gstelement_class;
+ GstBaseSrcClass *gstbasesrc_class;
+ GstPushSrcClass *gstpushsrc_class;
+
+ gobject_class = (GObjectClass *) klass;
+ gstelement_class = (GstElementClass *) klass;
+ gstbasesrc_class = (GstBaseSrcClass *) klass;
+ gstpushsrc_class = (GstPushSrcClass *) klass;
+
+ gobject_class->finalize = gst_pipewire_src_finalize;
+ gobject_class->set_property = gst_pipewire_src_set_property;
+ gobject_class->get_property = gst_pipewire_src_get_property;
+
+ g_object_class_install_property (gobject_class,
+ PROP_PATH,
+ g_param_spec_string ("path",
+ "Path",
+ "The source path to connect to (NULL = default)",
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class,
+ PROP_CLIENT_NAME,
+ g_param_spec_string ("client-name",
+ "Client Name",
+ "The client name to use (NULL = default)",
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class,
+ PROP_STREAM_PROPERTIES,
+ g_param_spec_boxed ("stream-properties",
+ "stream properties",
+ "list of PipeWire stream properties",
+ GST_TYPE_STRUCTURE,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class,
+ PROP_ALWAYS_COPY,
+ g_param_spec_boolean ("always-copy",
+ "Always copy",
+ "Always copy the buffer and data",
+ DEFAULT_ALWAYS_COPY,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ gstelement_class->provide_clock = gst_pipewire_src_provide_clock;
+ gstelement_class->change_state = gst_pipewire_src_change_state;
+
+ gst_element_class_set_static_metadata (gstelement_class,
+ "PipeWire source", "Source/Video",
+ "Uses PipeWire to create video", "Wim Taymans <wim.taymans@gmail.com>");
+
+ gst_element_class_add_pad_template (gstelement_class,
+ gst_static_pad_template_get (&gst_pipewire_src_template));
+
+ gstbasesrc_class->negotiate = gst_pipewire_src_negotiate;
+ gstbasesrc_class->unlock = gst_pipewire_src_unlock;
+ gstbasesrc_class->unlock_stop = gst_pipewire_src_unlock_stop;
+ gstbasesrc_class->start = gst_pipewire_src_start;
+ gstbasesrc_class->stop = gst_pipewire_src_stop;
+ gstbasesrc_class->event = gst_pipewire_src_event;
+ gstbasesrc_class->query = gst_pipewire_src_query;
+ gstpushsrc_class->create = gst_pipewire_src_create;
+
+ GST_DEBUG_CATEGORY_INIT (pipewire_src_debug, "pipewiresrc", 0,
+ "PipeWire Source");
+
+ process_mem_data_quark = g_quark_from_static_string ("GstPipeWireSrcProcessMemQuark");
+}
+
+static void
+gst_pipewire_src_init (GstPipeWireSrc * src)
+{
+ /* we operate in time */
+ gst_base_src_set_format (GST_BASE_SRC (src), GST_FORMAT_TIME);
+
+ GST_OBJECT_FLAG_SET (src, GST_ELEMENT_FLAG_PROVIDE_CLOCK);
+
+ src->always_copy = DEFAULT_ALWAYS_COPY;
+
+ g_queue_init (&src->queue);
+
+ src->fd_allocator = gst_fd_allocator_new ();
+ src->client_name = pw_get_client_name ();
+ src->buf_ids = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, (GDestroyNotify) gst_buffer_unref);
+
+ src->loop = pw_loop_new ();
+ src->main_loop = pw_thread_loop_new (src->loop, "pipewire-main-loop");
+ src->core = pw_core_new (src->loop, NULL);
+ GST_DEBUG ("loop %p, mainloop %p", src->loop, src->main_loop);
+
+}
+
+typedef struct {
+ GstPipeWireSrc *src;
+ guint id;
+ struct spa_buffer *buf;
+ struct spa_meta_header *header;
+ guint flags;
+ goffset offset;
+} ProcessMemData;
+
+static void
+process_mem_data_destroy (gpointer user_data)
+{
+ ProcessMemData *data = user_data;
+
+ gst_object_unref (data->src);
+ g_slice_free (ProcessMemData, data);
+}
+
+static gboolean
+buffer_recycle (GstMiniObject *obj)
+{
+ ProcessMemData *data;
+ GstPipeWireSrc *src;
+
+ gst_mini_object_ref (obj);
+ data = gst_mini_object_get_qdata (obj,
+ process_mem_data_quark);
+ GST_BUFFER_FLAGS (obj) = data->flags;
+ src = data->src;
+
+ GST_LOG_OBJECT (obj, "recycle buffer");
+ pw_thread_loop_lock (src->main_loop);
+ pw_stream_recycle_buffer (src->stream, data->id);
+ pw_thread_loop_unlock (src->main_loop);
+
+ return FALSE;
+}
+
+static void
+on_add_buffer (struct pw_listener *listener,
+ struct pw_stream *stream,
+ guint id)
+{
+ GstPipeWireSrc *pwsrc = SPA_CONTAINER_OF (listener, GstPipeWireSrc, stream_add_buffer);
+ struct spa_buffer *b;
+ GstBuffer *buf;
+ uint32_t i;
+ ProcessMemData data;
+ struct pw_remote *remote = pwsrc->stream->remote;
+ struct pw_core *core = remote->core;
+
+ GST_LOG_OBJECT (pwsrc, "add buffer");
+
+ if (!(b = pw_stream_peek_buffer (pwsrc->stream, id))) {
+ g_warning ("failed to peek buffer");
+ return;
+ }
+
+ buf = gst_buffer_new ();
+ GST_MINI_OBJECT_CAST (buf)->dispose = buffer_recycle;
+
+ data.src = gst_object_ref (pwsrc);
+ data.id = id;
+ data.buf = b;
+ data.header = spa_buffer_find_meta (b, core->type.meta.Header);
+
+ for (i = 0; i < b->n_datas; i++) {
+ struct spa_data *d = &b->datas[i];
+ GstMemory *gmem = NULL;
+
+ if (d->type == core->type.data.MemFd || d->type == core->type.data.DmaBuf) {
+ gmem = gst_fd_allocator_alloc (pwsrc->fd_allocator, dup (d->fd),
+ d->mapoffset + d->maxsize, GST_FD_MEMORY_FLAG_NONE);
+ gst_memory_resize (gmem, d->chunk->offset + d->mapoffset, d->chunk->size);
+ data.offset = d->mapoffset;
+ }
+ else if (d->type == core->type.data.MemPtr) {
+ gmem = gst_memory_new_wrapped (0, d->data, d->maxsize, d->chunk->offset + d->mapoffset,
+ d->chunk->size, NULL, NULL);
+ data.offset = 0;
+ }
+ if (gmem)
+ gst_buffer_append_memory (buf, gmem);
+ }
+ data.flags = GST_BUFFER_FLAGS (buf);
+ gst_mini_object_set_qdata (GST_MINI_OBJECT_CAST (buf),
+ process_mem_data_quark,
+ g_slice_dup (ProcessMemData, &data),
+ process_mem_data_destroy);
+
+ g_hash_table_insert (pwsrc->buf_ids, GINT_TO_POINTER (id), buf);
+}
+
+static void
+on_remove_buffer (struct pw_listener *listener,
+ struct pw_stream *stream,
+ guint id)
+{
+ GstPipeWireSrc *pwsrc = SPA_CONTAINER_OF (listener, GstPipeWireSrc, stream_remove_buffer);
+ GstBuffer *buf;
+
+ GST_LOG_OBJECT (pwsrc, "remove buffer");
+ buf = g_hash_table_lookup (pwsrc->buf_ids, GINT_TO_POINTER (id));
+ if (buf) {
+ GList *walk;
+
+ GST_MINI_OBJECT_CAST (buf)->dispose = NULL;
+
+ walk = pwsrc->queue.head;
+ while (walk) {
+ GList *next = walk->next;
+
+ if (walk->data == buf) {
+ gst_buffer_unref (buf);
+ g_queue_delete_link (&pwsrc->queue, walk);
+ }
+ walk = next;
+ }
+ g_hash_table_remove (pwsrc->buf_ids, GINT_TO_POINTER (id));
+ }
+}
+
+static void
+on_new_buffer (struct pw_listener *listener,
+ struct pw_stream *stream,
+ guint id)
+{
+ GstPipeWireSrc *pwsrc = SPA_CONTAINER_OF (listener, GstPipeWireSrc, stream_new_buffer);
+ GstBuffer *buf;
+ ProcessMemData *data;
+ struct spa_meta_header *h;
+ guint i;
+
+ buf = g_hash_table_lookup (pwsrc->buf_ids, GINT_TO_POINTER (id));
+ if (buf == NULL) {
+ g_warning ("unknown buffer %d", id);
+ return;
+ }
+ GST_LOG_OBJECT (pwsrc, "got new buffer %p", buf);
+
+ data = gst_mini_object_get_qdata (GST_MINI_OBJECT_CAST (buf),
+ process_mem_data_quark);
+ h = data->header;
+ if (h) {
+ GST_INFO ("pts %" G_GUINT64_FORMAT ", dts_offset %"G_GUINT64_FORMAT, h->pts, h->dts_offset);
+
+ if (GST_CLOCK_TIME_IS_VALID (h->pts)) {
+ GST_BUFFER_PTS (buf) = h->pts;
+ if (GST_BUFFER_PTS (buf) + h->dts_offset > 0)
+ GST_BUFFER_DTS (buf) = GST_BUFFER_PTS (buf) + h->dts_offset;
+ }
+ GST_BUFFER_OFFSET (buf) = h->seq;
+ }
+ for (i = 0; i < data->buf->n_datas; i++) {
+ struct spa_data *d = &data->buf->datas[i];
+ GstMemory *mem = gst_buffer_peek_memory (buf, i);
+ mem->offset = d->chunk->offset + data->offset;
+ mem->size = d->chunk->size;
+ }
+
+ if (pwsrc->always_copy)
+ buf = gst_buffer_copy_deep (buf);
+ else
+ gst_buffer_ref (buf);
+
+ g_queue_push_tail (&pwsrc->queue, buf);
+
+ pw_thread_loop_signal (pwsrc->main_loop, FALSE);
+ return;
+}
+
+static void
+on_state_changed (struct pw_listener *listener,
+ struct pw_stream *stream)
+{
+ GstPipeWireSrc *pwsrc = SPA_CONTAINER_OF (listener, GstPipeWireSrc, stream_state_changed);
+ enum pw_stream_state state = stream->state;
+
+ GST_DEBUG ("got stream state %s", pw_stream_state_as_string (state));
+
+ switch (state) {
+ case PW_STREAM_STATE_UNCONNECTED:
+ case PW_STREAM_STATE_CONNECTING:
+ case PW_STREAM_STATE_CONFIGURE:
+ case PW_STREAM_STATE_READY:
+ case PW_STREAM_STATE_PAUSED:
+ case PW_STREAM_STATE_STREAMING:
+ break;
+ case PW_STREAM_STATE_ERROR:
+ GST_ELEMENT_ERROR (pwsrc, RESOURCE, FAILED,
+ ("stream error: %s", stream->error), (NULL));
+ break;
+ }
+ pw_thread_loop_signal (pwsrc->main_loop, FALSE);
+}
+
+static void
+parse_stream_properties (GstPipeWireSrc *pwsrc, struct pw_properties *props)
+{
+ const gchar *var;
+ gboolean is_live;
+
+ GST_OBJECT_LOCK (pwsrc);
+ var = pw_properties_get (props, "pipewire.latency.is-live");
+ is_live = pwsrc->is_live = var ? (atoi (var) == 1) : FALSE;
+
+ var = pw_properties_get (props, "pipewire.latency.min");
+ pwsrc->min_latency = var ? (GstClockTime) atoi (var) : 0;
+
+ var = pw_properties_get (props, "pipewire.latency.max");
+ pwsrc->max_latency = var ? (GstClockTime) atoi (var) : GST_CLOCK_TIME_NONE;
+ GST_OBJECT_UNLOCK (pwsrc);
+
+ GST_DEBUG_OBJECT (pwsrc, "live %d", is_live);
+
+ gst_base_src_set_live (GST_BASE_SRC (pwsrc), is_live);
+}
+
+static gboolean
+gst_pipewire_src_stream_start (GstPipeWireSrc *pwsrc)
+{
+ pw_thread_loop_lock (pwsrc->main_loop);
+ GST_DEBUG_OBJECT (pwsrc, "doing stream start");
+ while (TRUE) {
+ enum pw_stream_state state = pwsrc->stream->state;
+
+ GST_DEBUG_OBJECT (pwsrc, "waiting for STREAMING, now %s", pw_stream_state_as_string (state));
+ if (state == PW_STREAM_STATE_STREAMING)
+ break;
+
+ if (state == PW_STREAM_STATE_ERROR)
+ goto start_error;
+
+ if (pwsrc->remote->state == PW_REMOTE_STATE_ERROR)
+ goto start_error;
+
+ pw_thread_loop_wait (pwsrc->main_loop);
+ }
+
+ parse_stream_properties (pwsrc, pwsrc->stream->properties);
+ pw_thread_loop_unlock (pwsrc->main_loop);
+
+ pw_thread_loop_lock (pwsrc->main_loop);
+ GST_DEBUG_OBJECT (pwsrc, "signal started");
+ pwsrc->started = TRUE;
+ pw_thread_loop_signal (pwsrc->main_loop, FALSE);
+ pw_thread_loop_unlock (pwsrc->main_loop);
+
+ return TRUE;
+
+start_error:
+ {
+ GST_DEBUG_OBJECT (pwsrc, "error starting stream");
+ pw_thread_loop_unlock (pwsrc->main_loop);
+ return FALSE;
+ }
+}
+
+static enum pw_stream_state
+wait_negotiated (GstPipeWireSrc *this)
+{
+ enum pw_stream_state state;
+
+ pw_thread_loop_lock (this->main_loop);
+ while (TRUE) {
+ state = this->stream->state;
+
+ GST_DEBUG_OBJECT (this, "waiting for started signal, state now %s",
+ pw_stream_state_as_string (state));
+
+ if (state == PW_STREAM_STATE_ERROR)
+ break;
+
+ if (this->remote->state == PW_REMOTE_STATE_ERROR)
+ break;
+
+ if (this->started)
+ break;
+
+ pw_thread_loop_wait (this->main_loop);
+ }
+ GST_DEBUG_OBJECT (this, "got started signal");
+ pw_thread_loop_unlock (this->main_loop);
+
+ return state;
+}
+
+static gboolean
+gst_pipewire_src_negotiate (GstBaseSrc * basesrc)
+{
+ GstPipeWireSrc *pwsrc = GST_PIPEWIRE_SRC (basesrc);
+ GstCaps *thiscaps;
+ GstCaps *caps = NULL;
+ GstCaps *peercaps = NULL;
+ gboolean result = FALSE;
+ GPtrArray *possible;
+
+ /* first see what is possible on our source pad */
+ thiscaps = gst_pad_query_caps (GST_BASE_SRC_PAD (basesrc), NULL);
+ GST_DEBUG_OBJECT (basesrc, "caps of src: %" GST_PTR_FORMAT, thiscaps);
+ /* nothing or anything is allowed, we're done */
+ if (thiscaps == NULL)
+ goto no_nego_needed;
+
+ if (G_UNLIKELY (gst_caps_is_empty (thiscaps)))
+ goto no_caps;
+
+ /* get the peer caps */
+ peercaps = gst_pad_peer_query_caps (GST_BASE_SRC_PAD (basesrc), thiscaps);
+ GST_DEBUG_OBJECT (basesrc, "caps of peer: %" GST_PTR_FORMAT, peercaps);
+ if (peercaps) {
+ /* The result is already a subset of our caps */
+ caps = peercaps;
+ gst_caps_unref (thiscaps);
+ } else {
+ /* no peer, work with our own caps then */
+ caps = thiscaps;
+ }
+ if (caps == NULL || gst_caps_is_empty (caps))
+ goto no_common_caps;
+
+ GST_DEBUG_OBJECT (basesrc, "have common caps: %" GST_PTR_FORMAT, caps);
+
+ /* open a connection with these caps */
+ possible = gst_caps_to_format_all (caps, pwsrc->remote->core->type.map);
+ gst_caps_unref (caps);
+
+ /* first disconnect */
+ pw_thread_loop_lock (pwsrc->main_loop);
+ if (pwsrc->stream->state != PW_STREAM_STATE_UNCONNECTED) {
+ GST_DEBUG_OBJECT (basesrc, "disconnect capture");
+ pw_stream_disconnect (pwsrc->stream);
+ while (TRUE) {
+ enum pw_stream_state state = pwsrc->stream->state;
+
+ GST_DEBUG_OBJECT (basesrc, "waiting for UNCONNECTED, now %s", pw_stream_state_as_string (state));
+ if (state == PW_STREAM_STATE_UNCONNECTED)
+ break;
+
+ if (state == PW_STREAM_STATE_ERROR) {
+ g_ptr_array_unref (possible);
+ goto connect_error;
+ }
+
+ pw_thread_loop_wait (pwsrc->main_loop);
+ }
+ }
+
+ GST_DEBUG_OBJECT (basesrc, "connect capture with path %s", pwsrc->path);
+ pw_stream_connect (pwsrc->stream,
+ PW_DIRECTION_INPUT,
+ PW_STREAM_MODE_BUFFER,
+ pwsrc->path,
+ PW_STREAM_FLAG_AUTOCONNECT,
+ possible->len,
+ (const struct spa_format **)possible->pdata);
+ g_ptr_array_free (possible, TRUE);
+
+ while (TRUE) {
+ enum pw_stream_state state = pwsrc->stream->state;
+
+ GST_DEBUG_OBJECT (basesrc, "waiting for PAUSED, now %s", pw_stream_state_as_string (state));
+ if (state == PW_STREAM_STATE_PAUSED ||
+ state == PW_STREAM_STATE_STREAMING)
+ break;
+
+ if (state == PW_STREAM_STATE_ERROR)
+ goto connect_error;
+
+ if (pwsrc->remote->state == PW_REMOTE_STATE_ERROR)
+ goto connect_error;
+
+ pw_thread_loop_wait (pwsrc->main_loop);
+ }
+ pw_thread_loop_unlock (pwsrc->main_loop);
+
+ result = gst_pipewire_src_stream_start (pwsrc);
+
+ pwsrc->negotiated = result;
+
+ return result;
+
+no_nego_needed:
+ {
+ GST_DEBUG_OBJECT (basesrc, "no negotiation needed");
+ if (thiscaps)
+ gst_caps_unref (thiscaps);
+ return TRUE;
+ }
+no_caps:
+ {
+ GST_ELEMENT_ERROR (basesrc, STREAM, FORMAT,
+ ("No supported formats found"),
+ ("This element did not produce valid caps"));
+ if (thiscaps)
+ gst_caps_unref (thiscaps);
+ return FALSE;
+ }
+no_common_caps:
+ {
+ GST_ELEMENT_ERROR (basesrc, STREAM, FORMAT,
+ ("No supported formats found"),
+ ("This element does not have formats in common with the peer"));
+ if (caps)
+ gst_caps_unref (caps);
+ return FALSE;
+ }
+connect_error:
+ {
+ pw_thread_loop_unlock (pwsrc->main_loop);
+ return FALSE;
+ }
+}
+
+#define PROP(f,key,type,...) \
+ SPA_POD_PROP (f,key,0,type,1,__VA_ARGS__)
+#define PROP_U_MM(f,key,type,...) \
+ SPA_POD_PROP (f,key,SPA_POD_PROP_FLAG_UNSET | \
+ SPA_POD_PROP_RANGE_MIN_MAX,type,3,__VA_ARGS__)
+
+static void
+on_format_changed (struct pw_listener *listener,
+ struct pw_stream *stream,
+ struct spa_format *format)
+{
+ GstPipeWireSrc *pwsrc = SPA_CONTAINER_OF (listener, GstPipeWireSrc, stream_format_changed);
+ GstCaps *caps;
+ gboolean res;
+ struct pw_remote *remote = stream->remote;
+ struct pw_core *core = remote->core;
+
+ if (format == NULL) {
+ GST_DEBUG_OBJECT (pwsrc, "clear format");
+ pw_stream_finish_format (pwsrc->stream, SPA_RESULT_OK, NULL, 0);
+ return;
+ }
+
+ caps = gst_caps_from_format (format, core->type.map);
+ GST_DEBUG_OBJECT (pwsrc, "we got format %" GST_PTR_FORMAT, caps);
+ res = gst_base_src_set_caps (GST_BASE_SRC (pwsrc), caps);
+ gst_caps_unref (caps);
+
+ if (res) {
+ struct spa_param *params[2];
+ struct spa_pod_builder b = { NULL };
+ uint8_t buffer[512];
+ struct spa_pod_frame f[2];
+
+ spa_pod_builder_init (&b, buffer, sizeof (buffer));
+ spa_pod_builder_object (&b, &f[0], 0, core->type.param_alloc_buffers.Buffers,
+ PROP_U_MM (&f[1], core->type.param_alloc_buffers.size, SPA_POD_TYPE_INT, 0, 0, INT32_MAX),
+ PROP_U_MM (&f[1], core->type.param_alloc_buffers.stride, SPA_POD_TYPE_INT, 0, 0, INT32_MAX),
+ PROP_U_MM (&f[1], core->type.param_alloc_buffers.buffers, SPA_POD_TYPE_INT, 16, 0, INT32_MAX),
+ PROP (&f[1], core->type.param_alloc_buffers.align, SPA_POD_TYPE_INT, 16));
+ params[0] = SPA_POD_BUILDER_DEREF (&b, f[0].ref, struct spa_param);
+
+ spa_pod_builder_object (&b, &f[0], 0, core->type.param_alloc_meta_enable.MetaEnable,
+ PROP (&f[1], core->type.param_alloc_meta_enable.type, SPA_POD_TYPE_ID, core->type.meta.Header),
+ PROP (&f[1], core->type.param_alloc_meta_enable.size, SPA_POD_TYPE_INT, sizeof (struct spa_meta_header)));
+ params[1] = SPA_POD_BUILDER_DEREF (&b, f[0].ref, struct spa_param);
+
+ GST_DEBUG_OBJECT (pwsrc, "doing finish format");
+ pw_stream_finish_format (pwsrc->stream, SPA_RESULT_OK, params, 2);
+ } else {
+ GST_WARNING_OBJECT (pwsrc, "finish format with error");
+ pw_stream_finish_format (pwsrc->stream, SPA_RESULT_INVALID_MEDIA_TYPE, NULL, 0);
+ }
+}
+
+static gboolean
+gst_pipewire_src_unlock (GstBaseSrc * basesrc)
+{
+ GstPipeWireSrc *pwsrc = GST_PIPEWIRE_SRC (basesrc);
+
+ pw_thread_loop_lock (pwsrc->main_loop);
+ GST_DEBUG_OBJECT (pwsrc, "setting flushing");
+ pwsrc->flushing = TRUE;
+ pw_thread_loop_signal (pwsrc->main_loop, FALSE);
+ pw_thread_loop_unlock (pwsrc->main_loop);
+
+ return TRUE;
+}
+
+static gboolean
+gst_pipewire_src_unlock_stop (GstBaseSrc * basesrc)
+{
+ GstPipeWireSrc *pwsrc = GST_PIPEWIRE_SRC (basesrc);
+
+ pw_thread_loop_lock (pwsrc->main_loop);
+ GST_DEBUG_OBJECT (pwsrc, "unsetting flushing");
+ pwsrc->flushing = FALSE;
+ pw_thread_loop_unlock (pwsrc->main_loop);
+
+ return TRUE;
+}
+
+static gboolean
+gst_pipewire_src_event (GstBaseSrc * src, GstEvent * event)
+{
+ gboolean res = FALSE;
+
+ switch (GST_EVENT_TYPE (event)) {
+ case GST_EVENT_CUSTOM_UPSTREAM:
+ if (gst_video_event_is_force_key_unit (event)) {
+ GstClockTime running_time;
+ gboolean all_headers;
+ guint count;
+
+ gst_video_event_parse_upstream_force_key_unit (event,
+ &running_time, &all_headers, &count);
+
+#if 0
+ pw_buffer_builder_init (&b);
+
+ refresh.last_id = 0;
+ refresh.request_type = all_headers ? 1 : 0;
+ refresh.pts = running_time;
+ pw_buffer_builder_add_refresh_request (&b, &refresh);
+
+ pw_buffer_builder_end (&b, &pbuf);
+
+ GST_OBJECT_LOCK (pwsrc);
+ if (pwsrc->stream_state == PW_STREAM_STATE_STREAMING) {
+ GST_DEBUG_OBJECT (pwsrc, "send refresh request");
+ pw_stream_send_buffer (pwsrc->stream, &pbuf);
+ }
+ GST_OBJECT_UNLOCK (pwsrc);
+
+ pw_buffer_unref (&pbuf);
+#endif
+ res = TRUE;
+ } else {
+ res = GST_BASE_SRC_CLASS (parent_class)->event (src, event);
+ }
+ break;
+ default:
+ res = GST_BASE_SRC_CLASS (parent_class)->event (src, event);
+ break;
+ }
+ return res;
+}
+
+static gboolean
+gst_pipewire_src_query (GstBaseSrc * src, GstQuery * query)
+{
+ gboolean res = FALSE;
+ GstPipeWireSrc *pwsrc;
+
+ pwsrc = GST_PIPEWIRE_SRC (src);
+
+ switch (GST_QUERY_TYPE (query)) {
+ case GST_QUERY_LATENCY:
+ GST_OBJECT_LOCK (pwsrc);
+ pwsrc->min_latency = 10000000;
+ pwsrc->max_latency = GST_CLOCK_TIME_NONE;
+ gst_query_set_latency (query, pwsrc->is_live, pwsrc->min_latency, pwsrc->max_latency);
+ GST_OBJECT_UNLOCK (pwsrc);
+ res = TRUE;
+ break;
+ default:
+ res = GST_BASE_SRC_CLASS (parent_class)->query (src, query);
+ break;
+ }
+ return res;
+}
+
+static GstFlowReturn
+gst_pipewire_src_create (GstPushSrc * psrc, GstBuffer ** buffer)
+{
+ GstPipeWireSrc *pwsrc;
+ GstClockTime pts, dts, base_time;
+
+ pwsrc = GST_PIPEWIRE_SRC (psrc);
+
+ if (!pwsrc->negotiated)
+ goto not_negotiated;
+
+ pw_thread_loop_lock (pwsrc->main_loop);
+ while (TRUE) {
+ enum pw_stream_state state;
+
+ if (pwsrc->flushing)
+ goto streaming_stopped;
+
+ state = pwsrc->stream->state;
+ if (state == PW_STREAM_STATE_ERROR)
+ goto streaming_error;
+
+ if (state != PW_STREAM_STATE_STREAMING)
+ goto streaming_stopped;
+
+ *buffer = g_queue_pop_head (&pwsrc->queue);
+ GST_DEBUG ("popped buffer %p", *buffer);
+ if (*buffer != NULL)
+ break;
+
+ pw_thread_loop_wait (pwsrc->main_loop);
+ }
+ pw_thread_loop_unlock (pwsrc->main_loop);
+
+ if (pwsrc->is_live)
+ base_time = GST_ELEMENT_CAST (psrc)->base_time;
+ else
+ base_time = 0;
+
+ pts = GST_BUFFER_PTS (*buffer);
+ dts = GST_BUFFER_DTS (*buffer);
+
+ if (GST_CLOCK_TIME_IS_VALID (pts))
+ pts = (pts >= base_time ? pts - base_time : 0);
+ if (GST_CLOCK_TIME_IS_VALID (dts))
+ dts = (dts >= base_time ? dts - base_time : 0);
+
+ GST_INFO ("pts %" G_GUINT64_FORMAT ", dts %"G_GUINT64_FORMAT
+ ", base-time %"GST_TIME_FORMAT" -> %"GST_TIME_FORMAT", %"GST_TIME_FORMAT,
+ GST_BUFFER_PTS (*buffer), GST_BUFFER_DTS (*buffer), GST_TIME_ARGS (base_time),
+ GST_TIME_ARGS (pts), GST_TIME_ARGS (dts));
+
+ GST_BUFFER_PTS (*buffer) = pts;
+ GST_BUFFER_DTS (*buffer) = dts;
+
+ return GST_FLOW_OK;
+
+not_negotiated:
+ {
+ return GST_FLOW_NOT_NEGOTIATED;
+ }
+streaming_error:
+ {
+ pw_thread_loop_unlock (pwsrc->main_loop);
+ return GST_FLOW_ERROR;
+ }
+streaming_stopped:
+ {
+ pw_thread_loop_unlock (pwsrc->main_loop);
+ return GST_FLOW_FLUSHING;
+ }
+}
+
+static gboolean
+gst_pipewire_src_start (GstBaseSrc * basesrc)
+{
+ return TRUE;
+}
+
+static gboolean
+gst_pipewire_src_stop (GstBaseSrc * basesrc)
+{
+ GstPipeWireSrc *pwsrc;
+
+ pwsrc = GST_PIPEWIRE_SRC (basesrc);
+
+ pw_thread_loop_lock (pwsrc->main_loop);
+ clear_queue (pwsrc);
+ pw_thread_loop_unlock (pwsrc->main_loop);
+
+ return TRUE;
+}
+
+static void
+on_remote_state_changed (struct pw_listener *listener,
+ struct pw_remote *remote)
+{
+ GstPipeWireSrc *pwsrc = SPA_CONTAINER_OF (listener, GstPipeWireSrc, remote_state_changed);
+ enum pw_remote_state state = remote->state;
+
+ GST_DEBUG ("got remote state %s", pw_remote_state_as_string (state));
+
+ switch (state) {
+ case PW_REMOTE_STATE_UNCONNECTED:
+ case PW_REMOTE_STATE_CONNECTING:
+ case PW_REMOTE_STATE_CONNECTED:
+ break;
+ case PW_REMOTE_STATE_ERROR:
+ GST_ELEMENT_ERROR (pwsrc, RESOURCE, FAILED,
+ ("remote error: %s", remote->error), (NULL));
+ break;
+ }
+ pw_thread_loop_signal (pwsrc->main_loop, FALSE);
+}
+
+static gboolean
+copy_properties (GQuark field_id,
+ const GValue *value,
+ gpointer user_data)
+{
+ struct pw_properties *properties = user_data;
+
+ if (G_VALUE_HOLDS_STRING (value))
+ pw_properties_set (properties,
+ g_quark_to_string (field_id),
+ g_value_get_string (value));
+ return TRUE;
+}
+
+static gboolean
+gst_pipewire_src_open (GstPipeWireSrc * pwsrc)
+{
+ struct pw_properties *props;
+
+ if (pw_thread_loop_start (pwsrc->main_loop) != SPA_RESULT_OK)
+ goto mainloop_failed;
+
+ pw_thread_loop_lock (pwsrc->main_loop);
+ if ((pwsrc->remote = pw_remote_new (pwsrc->core, NULL)) == NULL)
+ goto no_remote;
+
+ pw_signal_add (&pwsrc->remote->state_changed, &pwsrc->remote_state_changed, on_remote_state_changed);
+
+ pw_remote_connect (pwsrc->remote);
+
+ while (TRUE) {
+ enum pw_remote_state state = pwsrc->remote->state;
+
+ GST_DEBUG ("waiting for CONNECTED, now %s", pw_remote_state_as_string (state));
+ if (state == PW_REMOTE_STATE_CONNECTED)
+ break;
+
+ if (state == PW_REMOTE_STATE_ERROR)
+ goto connect_error;
+
+ pw_thread_loop_wait (pwsrc->main_loop);
+ }
+
+ if (pwsrc->properties) {
+ props = pw_properties_new (NULL, NULL);
+ gst_structure_foreach (pwsrc->properties, copy_properties, props);
+ } else {
+ props = NULL;
+ }
+
+ if ((pwsrc->stream = pw_stream_new (pwsrc->remote, pwsrc->client_name, props)) == NULL)
+ goto no_stream;
+
+ pw_signal_add (&pwsrc->stream->state_changed, &pwsrc->stream_state_changed, on_state_changed);
+ pw_signal_add (&pwsrc->stream->format_changed, &pwsrc->stream_format_changed, on_format_changed);
+ pw_signal_add (&pwsrc->stream->add_buffer, &pwsrc->stream_add_buffer, on_add_buffer);
+ pw_signal_add (&pwsrc->stream->remove_buffer, &pwsrc->stream_remove_buffer, on_remove_buffer);
+ pw_signal_add (&pwsrc->stream->new_buffer, &pwsrc->stream_new_buffer, on_new_buffer);
+
+ pwsrc->clock = gst_pipewire_clock_new (pwsrc->stream);
+ pw_thread_loop_unlock (pwsrc->main_loop);
+
+ return TRUE;
+
+ /* ERRORS */
+mainloop_failed:
+ {
+ GST_ELEMENT_ERROR (pwsrc, RESOURCE, FAILED, ("error starting mainloop"), (NULL));
+ return FALSE;
+ }
+no_remote:
+ {
+ GST_ELEMENT_ERROR (pwsrc, RESOURCE, FAILED, ("can't create remote"), (NULL));
+ pw_thread_loop_unlock (pwsrc->main_loop);
+ return FALSE;
+ }
+connect_error:
+ {
+ pw_thread_loop_unlock (pwsrc->main_loop);
+ return FALSE;
+ }
+no_stream:
+ {
+ GST_ELEMENT_ERROR (pwsrc, RESOURCE, FAILED, ("can't create stream"), (NULL));
+ pw_thread_loop_unlock (pwsrc->main_loop);
+ return FALSE;
+ }
+}
+
+static void
+gst_pipewire_src_close (GstPipeWireSrc * pwsrc)
+{
+ clear_queue (pwsrc);
+
+ pw_thread_loop_stop (pwsrc->main_loop);
+
+ pw_stream_destroy (pwsrc->stream);
+ pwsrc->stream = NULL;
+
+ pw_remote_destroy (pwsrc->remote);
+ pwsrc->remote = NULL;
+
+ GST_OBJECT_LOCK (pwsrc);
+ g_clear_object (&pwsrc->clock);
+ GST_OBJECT_UNLOCK (pwsrc);
+}
+
+static GstStateChangeReturn
+gst_pipewire_src_change_state (GstElement * element, GstStateChange transition)
+{
+ GstStateChangeReturn ret;
+ GstPipeWireSrc *this = GST_PIPEWIRE_SRC_CAST (element);
+
+ switch (transition) {
+ case GST_STATE_CHANGE_NULL_TO_READY:
+ if (!gst_pipewire_src_open (this))
+ goto open_failed;
+ break;
+ case GST_STATE_CHANGE_READY_TO_PAUSED:
+ break;
+ case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
+ /* uncork and start recording */
+ break;
+ case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
+ /* stop recording ASAP by corking */
+ break;
+ default:
+ break;
+ }
+
+ ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
+
+ switch (transition) {
+ case GST_STATE_CHANGE_READY_TO_PAUSED:
+ if (wait_negotiated (this) == PW_STREAM_STATE_ERROR)
+ goto open_failed;
+
+ if (gst_base_src_is_live (GST_BASE_SRC (element)))
+ ret = GST_STATE_CHANGE_NO_PREROLL;
+ break;
+ case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
+ break;
+ case GST_STATE_CHANGE_PAUSED_TO_READY:
+ this->negotiated = FALSE;
+ break;
+ case GST_STATE_CHANGE_READY_TO_NULL:
+ gst_pipewire_src_close (this);
+ break;
+ default:
+ break;
+ }
+ return ret;
+
+ /* ERRORS */
+open_failed:
+ {
+ return GST_STATE_CHANGE_FAILURE;
+ }
+}
diff --git a/src/gst/gstpipewiresrc.h b/src/gst/gstpipewiresrc.h
new file mode 100644
index 00000000..f888bcca
--- /dev/null
+++ b/src/gst/gstpipewiresrc.h
@@ -0,0 +1,97 @@
+/* GStreamer
+ * Copyright (C) <2015> Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __GST_PIPEWIRE_SRC_H__
+#define __GST_PIPEWIRE_SRC_H__
+
+#include <gst/gst.h>
+#include <gst/base/gstpushsrc.h>
+
+#include <pipewire/pipewire.h>
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_PIPEWIRE_SRC \
+ (gst_pipewire_src_get_type())
+#define GST_PIPEWIRE_SRC(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_PIPEWIRE_SRC,GstPipeWireSrc))
+#define GST_PIPEWIRE_SRC_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_PIPEWIRE_SRC,GstPipeWireSrcClass))
+#define GST_IS_PIPEWIRE_SRC(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_PIPEWIRE_SRC))
+#define GST_IS_PIPEWIRE_SRC_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_PIPEWIRE_SRC))
+#define GST_PIPEWIRE_SRC_CAST(obj) \
+ ((GstPipeWireSrc *) (obj))
+
+typedef struct _GstPipeWireSrc GstPipeWireSrc;
+typedef struct _GstPipeWireSrcClass GstPipeWireSrcClass;
+
+/**
+ * GstPipeWireSrc:
+ *
+ * Opaque data structure.
+ */
+struct _GstPipeWireSrc {
+ GstPushSrc element;
+
+ /*< private >*/
+ gchar *path;
+ gchar *client_name;
+ gboolean always_copy;
+
+ gboolean negotiated;
+ gboolean flushing;
+ gboolean started;
+
+ gboolean is_live;
+ GstClockTime min_latency;
+ GstClockTime max_latency;
+
+ struct pw_loop *loop;
+ struct pw_thread_loop *main_loop;
+
+ struct pw_core *core;
+ struct pw_remote *remote;
+ struct pw_listener remote_state_changed;
+
+ struct pw_stream *stream;
+ struct pw_listener stream_state_changed;
+ struct pw_listener stream_format_changed;
+ struct pw_listener stream_add_buffer;
+ struct pw_listener stream_remove_buffer;
+ struct pw_listener stream_new_buffer;
+
+ GstAllocator *fd_allocator;
+ GstStructure *properties;
+
+ GHashTable *buf_ids;
+ GQueue queue;
+ GstClock *clock;
+};
+
+struct _GstPipeWireSrcClass {
+ GstPushSrcClass parent_class;
+};
+
+GType gst_pipewire_src_get_type (void);
+
+G_END_DECLS
+
+#endif /* __GST_PIPEWIRE_SRC_H__ */
diff --git a/src/gst/meson.build b/src/gst/meson.build
new file mode 100644
index 00000000..cfc604d6
--- /dev/null
+++ b/src/gst/meson.build
@@ -0,0 +1,31 @@
+pipewire_gst_sources = [
+ 'gstpipewire.c',
+ 'gstpipewireclock.c',
+ 'gstpipewiredeviceprovider.c',
+ 'gstpipewireformat.c',
+ 'gstpipewirepool.c',
+ 'gstpipewiresink.c',
+ 'gstpipewiresrc.c',
+]
+
+pipewire_gst_headers = [
+ 'gstpipewireclock.h',
+ 'gstpipewiredeviceprovider.h',
+ 'gstpipewireformat.h',
+ 'gstpipewirepool.h',
+ 'gstpipewiresink.h',
+ 'gstpipewiresrc.h',
+]
+
+pipewire_gst_c_args = [
+ '-DHAVE_CONFIG_H',
+]
+
+pipewire_gst = shared_library('gstpipewire',
+ pipewire_gst_sources,
+ c_args : pipewire_gst_c_args,
+ include_directories : [configinc, spa_inc],
+ dependencies : [gobject_dep, glib_dep, gio_dep, gst_dep, pipewire_dep],
+ install : true,
+ install_dir : '@0@/gstreamer-1.0'.format(get_option('libdir')),
+)
diff --git a/src/meson.build b/src/meson.build
new file mode 100644
index 00000000..c9032de4
--- /dev/null
+++ b/src/meson.build
@@ -0,0 +1,8 @@
+
+subdir('pipewire')
+subdir('extensions')
+subdir('daemon')
+subdir('tools')
+subdir('modules')
+subdir('gst')
+subdir('examples')
diff --git a/src/modules/meson.build b/src/modules/meson.build
new file mode 100644
index 00000000..de3be9de
--- /dev/null
+++ b/src/modules/meson.build
@@ -0,0 +1,91 @@
+subdir('spa')
+
+pipewire_module_c_args = [
+ '-DHAVE_CONFIG_H',
+ '-D_GNU_SOURCE',
+]
+
+pipewire_module_flatpak = shared_library('pipewire-module-flatpak', [ 'module-flatpak.c' ],
+ c_args : pipewire_module_c_args,
+ include_directories : [configinc, spa_inc],
+ link_with : spalib,
+ install : true,
+ install_dir : modules_install_dir,
+ dependencies : [dbus_dep, mathlib, dl_lib, pipewire_dep],
+)
+
+pipewire_module_autolink = shared_library('pipewire-module-autolink', [ 'module-autolink.c' ],
+ c_args : pipewire_module_c_args,
+ include_directories : [configinc, spa_inc],
+ link_with : spalib,
+ install : true,
+ install_dir : modules_install_dir,
+ dependencies : [mathlib, dl_lib, pipewire_dep],
+)
+
+pipewire_module_mixer = shared_library('pipewire-module-mixer',
+ [ 'module-mixer.c', 'spa/spa-node.c' ],
+ c_args : pipewire_module_c_args,
+ include_directories : [configinc, spa_inc],
+ link_with : spalib,
+ install : true,
+ install_dir : modules_install_dir,
+ dependencies : [mathlib, dl_lib, pipewire_dep],
+)
+
+pipewire_module_client_node = shared_library('pipewire-module-client-node',
+ [ 'module-client-node.c',
+ 'module-client-node/client-node.c',
+ 'module-client-node/protocol-native.c',
+ 'module-protocol-native/connection.c',
+ 'spa/spa-node.c', ],
+ c_args : pipewire_module_c_args,
+ include_directories : [configinc, spa_inc],
+ link_with : spalib,
+ install : true,
+ install_dir : modules_install_dir,
+ dependencies : [mathlib, dl_lib, pipewire_dep],
+)
+
+#pipewire_module_protocol_dbus = shared_library('pipewire-module-protocol-dbus', [ 'module-protocol-dbus.c', gdbus_target ],
+# c_args : pipewire_module_c_args,
+# include_directories : [configinc, spa_inc],
+# link_with : spalib,
+# install : true,
+# install_dir : modules_install_dir,
+# dependencies : [glib_dep, gio_dep, mathlib, dl_lib, pipewire_dep],
+#)
+
+pipewire_module_protocol_native = shared_library('pipewire-module-protocol-native',
+ [ 'module-protocol-native.c',
+ 'module-protocol-native/protocol-native.c',
+ 'module-protocol-native/connection.c' ],
+ c_args : pipewire_module_c_args,
+ include_directories : [configinc, spa_inc],
+ link_with : spalib,
+ install : true,
+ install_dir : modules_install_dir,
+ dependencies : [mathlib, dl_lib, pipewire_dep],
+)
+
+if jack_dep.found()
+pipewire_module_jack = shared_library('pipewire-module-jack',
+ [ 'module-jack.c',
+ 'module-jack/shm.c' ],
+ c_args : pipewire_module_c_args,
+ include_directories : [configinc, spa_inc],
+ link_with : spalib,
+ install : true,
+ install_dir : modules_install_dir,
+ dependencies : [jack_dep, mathlib, dl_lib, rt_lib, pipewire_dep],
+)
+endif
+
+pipewire_module_suspend_on_idle = shared_library('pipewire-module-suspend-on-idle', [ 'module-suspend-on-idle.c' ],
+ c_args : pipewire_module_c_args,
+ include_directories : [configinc, spa_inc],
+ link_with : spalib,
+ install : true,
+ install_dir : modules_install_dir,
+ dependencies : [mathlib, dl_lib, pipewire_dep],
+)
diff --git a/src/modules/module-autolink.c b/src/modules/module-autolink.c
new file mode 100644
index 00000000..edcb145b
--- /dev/null
+++ b/src/modules/module-autolink.c
@@ -0,0 +1,329 @@
+/* PipeWire
+ * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+
+#include "config.h"
+
+#include "pipewire/interfaces.h"
+#include "pipewire/core.h"
+#include "pipewire/module.h"
+
+struct impl {
+ struct pw_core *core;
+ struct pw_properties *properties;
+
+ struct pw_listener global_added;
+ struct pw_listener global_removed;
+
+ struct spa_list node_list;
+};
+
+struct node_info {
+ struct impl *impl;
+ struct pw_node *node;
+ struct spa_list link;
+ struct pw_listener state_changed;
+ struct pw_listener port_added;
+ struct pw_listener port_removed;
+ struct pw_listener port_unlinked;
+ struct pw_listener link_state_changed;
+ struct pw_listener link_destroy;
+};
+
+static struct node_info *find_node_info(struct impl *impl, struct pw_node *node)
+{
+ struct node_info *info;
+
+ spa_list_for_each(info, &impl->node_list, link) {
+ if (info->node == node)
+ return info;
+ }
+ return NULL;
+}
+
+static void node_info_free(struct node_info *info)
+{
+ spa_list_remove(&info->link);
+ pw_signal_remove(&info->state_changed);
+ pw_signal_remove(&info->port_added);
+ pw_signal_remove(&info->port_removed);
+ pw_signal_remove(&info->port_unlinked);
+ pw_signal_remove(&info->link_destroy);
+ pw_signal_remove(&info->link_state_changed);
+ free(info);
+}
+
+static void try_link_port(struct pw_node *node, struct pw_port *port, struct node_info *info);
+
+static void
+on_link_port_unlinked(struct pw_listener *listener, struct pw_link *link, struct pw_port *port)
+{
+ struct node_info *info = SPA_CONTAINER_OF(listener, struct node_info, port_unlinked);
+ struct impl *impl = info->impl;
+
+ pw_log_debug("module %p: link %p: port %p unlinked", impl, link, port);
+ if (port->direction == PW_DIRECTION_OUTPUT && link->input)
+ try_link_port(link->input->node, link->input, info);
+}
+
+static void
+on_link_state_changed(struct pw_listener *listener,
+ struct pw_link *link, enum pw_link_state old, enum pw_link_state state)
+{
+ struct node_info *info = SPA_CONTAINER_OF(listener, struct node_info, link_state_changed);
+ struct impl *impl = info->impl;
+
+ switch (state) {
+ case PW_LINK_STATE_ERROR:
+ {
+ struct pw_resource *resource;
+
+ pw_log_debug("module %p: link %p: state error: %s", impl, link,
+ link->error);
+
+ spa_list_for_each(resource, &link->resource_list, link) {
+ pw_core_notify_error(resource->client->core_resource,
+ resource->id, SPA_RESULT_ERROR, link->error);
+ }
+ if (info->node->owner) {
+ pw_core_notify_error(info->node->owner->client->core_resource,
+ info->node->owner->id,
+ SPA_RESULT_ERROR, link->error);
+ }
+ break;
+ }
+
+
+ case PW_LINK_STATE_UNLINKED:
+ pw_log_debug("module %p: link %p: unlinked", impl, link);
+ break;
+
+ case PW_LINK_STATE_INIT:
+ case PW_LINK_STATE_NEGOTIATING:
+ case PW_LINK_STATE_ALLOCATING:
+ case PW_LINK_STATE_PAUSED:
+ case PW_LINK_STATE_RUNNING:
+ break;
+ }
+}
+
+static void
+on_link_destroy(struct pw_listener *listener, struct pw_link *link)
+{
+ struct node_info *info = SPA_CONTAINER_OF(listener, struct node_info, link_destroy);
+ struct impl *impl = info->impl;
+
+ pw_log_debug("module %p: link %p destroyed", impl, link);
+ pw_signal_remove(&info->port_unlinked);
+ pw_signal_remove(&info->link_state_changed);
+ pw_signal_remove(&info->link_destroy);
+ spa_list_init(&info->port_unlinked.link);
+ spa_list_init(&info->link_state_changed.link);
+ spa_list_init(&info->link_destroy.link);
+}
+
+static void try_link_port(struct pw_node *node, struct pw_port *port, struct node_info *info)
+{
+ struct impl *impl = info->impl;
+ struct pw_properties *props;
+ const char *str;
+ uint32_t path_id;
+ char *error = NULL;
+ struct pw_link *link;
+ struct pw_port *target;
+
+ props = node->properties;
+ if (props == NULL) {
+ pw_log_debug("module %p: node has no properties", impl);
+ return;
+ }
+
+ str = pw_properties_get(props, "pipewire.target.node");
+ if (str != NULL)
+ path_id = atoi(str);
+ else {
+ str = pw_properties_get(props, "pipewire.autoconnect");
+ if (str == NULL || atoi(str) == 0) {
+ pw_log_debug("module %p: node does not need autoconnect", impl);
+ return;
+ }
+ path_id = SPA_ID_INVALID;
+ }
+
+ pw_log_debug("module %p: try to find and link to node '%d'", impl, path_id);
+
+ target = pw_core_find_port(impl->core, port, path_id, NULL, 0, NULL, &error);
+ if (target == NULL)
+ goto error;
+
+ if (port->direction == PW_DIRECTION_OUTPUT)
+ link = pw_link_new(impl->core, port, target, NULL, NULL, &error);
+ else
+ link = pw_link_new(impl->core, target, port, NULL, NULL, &error);
+
+ if (link == NULL)
+ goto error;
+
+ pw_signal_add(&link->port_unlinked, &info->port_unlinked, on_link_port_unlinked);
+ pw_signal_add(&link->state_changed, &info->link_state_changed, on_link_state_changed);
+ pw_signal_add(&link->destroy_signal, &info->link_destroy, on_link_destroy);
+
+ pw_link_activate(link);
+
+ return;
+
+ error:
+ pw_log_error("module %p: can't link node '%s'", impl, error);
+ if (info->node->owner && info->node->owner->client->core_resource) {
+ pw_core_notify_error(info->node->owner->client->core_resource,
+ info->node->owner->id, SPA_RESULT_ERROR, error);
+ }
+ free(error);
+ return;
+}
+
+static void on_port_added(struct pw_listener *listener, struct pw_node *node, struct pw_port *port)
+{
+ struct node_info *info = SPA_CONTAINER_OF(listener, struct node_info, port_added);
+
+ try_link_port(node, port, info);
+}
+
+static void
+on_port_removed(struct pw_listener *listener, struct pw_node *node, struct pw_port *port)
+{
+}
+
+static void on_node_created(struct pw_node *node, struct node_info *info)
+{
+ struct pw_port *port;
+
+ spa_list_for_each(port, &node->input_ports, link)
+ on_port_added(&info->port_added, node, port);
+
+ spa_list_for_each(port, &node->output_ports, link)
+ on_port_added(&info->port_added, node, port);
+}
+
+static void
+on_state_changed(struct pw_listener *listener,
+ struct pw_node *node, enum pw_node_state old, enum pw_node_state state)
+{
+ struct node_info *info = SPA_CONTAINER_OF(listener, struct node_info, state_changed);
+
+ if (old == PW_NODE_STATE_CREATING && state == PW_NODE_STATE_SUSPENDED)
+ on_node_created(node, info);
+}
+
+static void
+on_global_added(struct pw_listener *listener, struct pw_core *core, struct pw_global *global)
+{
+ struct impl *impl = SPA_CONTAINER_OF(listener, struct impl, global_added);
+
+ if (global->type == impl->core->type.node) {
+ struct pw_node *node = global->object;
+ struct node_info *ninfo;
+
+ ninfo = calloc(1, sizeof(struct node_info));
+ ninfo->impl = impl;
+ ninfo->node = node;
+ spa_list_insert(impl->node_list.prev, &ninfo->link);
+ spa_list_init(&ninfo->port_unlinked.link);
+ spa_list_init(&ninfo->link_state_changed.link);
+ spa_list_init(&ninfo->link_destroy.link);
+
+ pw_signal_add(&node->port_added, &ninfo->port_added, on_port_added);
+ pw_signal_add(&node->port_removed, &ninfo->port_removed, on_port_removed);
+ pw_signal_add(&node->state_changed, &ninfo->state_changed, on_state_changed);
+
+ pw_log_debug("module %p: node %p added", impl, node);
+
+ if (node->info.state > PW_NODE_STATE_CREATING)
+ on_node_created(node, ninfo);
+ }
+}
+
+static void
+on_global_removed(struct pw_listener *listener, struct pw_core *core, struct pw_global *global)
+{
+ struct impl *impl = SPA_CONTAINER_OF(listener, struct impl, global_removed);
+
+ if (global->type == impl->core->type.node) {
+ struct pw_node *node = global->object;
+ struct node_info *ninfo;
+
+ if ((ninfo = find_node_info(impl, node)))
+ node_info_free(ninfo);
+
+ pw_log_debug("module %p: node %p removed", impl, node);
+ }
+}
+
+/**
+ * module_new:
+ * @core: #struct pw_core
+ * @properties: #struct pw_properties
+ *
+ * Make a new #struct impl object with given @properties
+ *
+ * Returns: a new #struct impl
+ */
+static struct impl *module_new(struct pw_core *core, struct pw_properties *properties)
+{
+ struct impl *impl;
+
+ impl = calloc(1, sizeof(struct impl));
+ pw_log_debug("module %p: new", impl);
+
+ impl->core = core;
+ impl->properties = properties;
+
+ spa_list_init(&impl->node_list);
+
+ pw_signal_add(&core->global_added, &impl->global_added, on_global_added);
+ pw_signal_add(&core->global_removed, &impl->global_removed, on_global_removed);
+
+ return impl;
+}
+
+#if 0
+static void module_destroy(struct impl *impl)
+{
+ pw_log_debug("module %p: destroy", impl);
+
+ pw_global_destroy(impl->global);
+
+ pw_signal_remove(&impl->global_added);
+ pw_signal_remove(&impl->global_removed);
+ pw_signal_remove(&impl->port_added);
+ pw_signal_remove(&impl->port_removed);
+ pw_signal_remove(&impl->port_unlinked);
+ pw_signal_remove(&impl->link_state_changed);
+ free(impl);
+}
+#endif
+
+bool pipewire__module_init(struct pw_module *module, const char *args)
+{
+ module->user_data = module_new(module->core, NULL);
+ return true;
+}
diff --git a/src/modules/module-client-node.c b/src/modules/module-client-node.c
new file mode 100644
index 00000000..d1505068
--- /dev/null
+++ b/src/modules/module-client-node.c
@@ -0,0 +1,97 @@
+/* PipeWire
+ * Copyright (C) 2017 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <dlfcn.h>
+
+#include "config.h"
+
+#include "pipewire/interfaces.h"
+#include "pipewire/core.h"
+#include "pipewire/module.h"
+
+#include "module-client-node/client-node.h"
+
+struct pw_protocol *pw_protocol_native_ext_client_node_init(void);
+
+struct impl {
+ struct pw_node_factory this;
+ struct pw_properties *properties;
+};
+
+static struct pw_node *create_node(struct pw_node_factory *factory,
+ struct pw_resource *resource,
+ const char *name,
+ struct pw_properties *properties)
+{
+ struct pw_client_node *node;
+
+ node = pw_client_node_new(resource, name, properties);
+ if (node == NULL)
+ goto no_mem;
+
+ return node->node;
+
+ no_mem:
+ pw_log_error("can't create node");
+ pw_core_notify_error(resource->client->core_resource,
+ resource->client->core_resource->id, SPA_RESULT_NO_MEMORY, "no memory");
+ return NULL;
+}
+
+static struct impl *module_new(struct pw_core *core, struct pw_properties *properties)
+{
+ struct impl *impl;
+
+ impl = calloc(1, sizeof(struct impl));
+ pw_log_debug("module %p: new", impl);
+
+ impl->properties = properties;
+
+ impl->this.core = core;
+ impl->this.name = "client-node";
+ impl->this.type = spa_type_map_get_id(core->type.map, PIPEWIRE_TYPE__ClientNode);
+ pw_signal_init(&impl->this.destroy_signal);
+ impl->this.create_node = create_node;
+
+ pw_protocol_native_ext_client_node_init();
+
+ spa_list_insert(core->node_factory_list.prev, &impl->this.link);
+
+ pw_core_add_global(core, NULL, core->type.node_factory, 0, impl, NULL, &impl->this.global);
+
+ return impl;
+}
+
+#if 0
+static void module_destroy(struct impl *impl)
+{
+ pw_log_debug("module %p: destroy", impl);
+
+ free(impl);
+}
+#endif
+
+bool pipewire__module_init(struct pw_module *module, const char *args)
+{
+ module_new(module->core, NULL);
+ return true;
+}
diff --git a/src/modules/module-client-node/client-node.c b/src/modules/module-client-node/client-node.c
new file mode 100644
index 00000000..2db5c53b
--- /dev/null
+++ b/src/modules/module-client-node/client-node.c
@@ -0,0 +1,1242 @@
+/* PipeWire
+ * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <string.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <dlfcn.h>
+#include <sys/socket.h>
+#include <sys/mman.h>
+#include <sys/eventfd.h>
+
+#include "spa/node.h"
+#include "spa/format-builder.h"
+#include "spa/lib/format.h"
+
+#include "pipewire/pipewire.h"
+#include "pipewire/interfaces.h"
+#include "pipewire/transport.h"
+
+#include "pipewire/core.h"
+#include "modules/spa/spa-node.h"
+#include "client-node.h"
+
+/** \cond */
+
+#define MAX_INPUTS 64
+#define MAX_OUTPUTS 64
+
+#define MAX_BUFFERS 64
+
+#define CHECK_IN_PORT_ID(this,d,p) ((d) == SPA_DIRECTION_INPUT && (p) < MAX_INPUTS)
+#define CHECK_OUT_PORT_ID(this,d,p) ((d) == SPA_DIRECTION_OUTPUT && (p) < MAX_OUTPUTS)
+#define CHECK_PORT_ID(this,d,p) (CHECK_IN_PORT_ID(this,d,p) || CHECK_OUT_PORT_ID(this,d,p))
+#define CHECK_FREE_IN_PORT(this,d,p) (CHECK_IN_PORT_ID(this,d,p) && !(this)->in_ports[p].valid)
+#define CHECK_FREE_OUT_PORT(this,d,p) (CHECK_OUT_PORT_ID(this,d,p) && !(this)->out_ports[p].valid)
+#define CHECK_FREE_PORT(this,d,p) (CHECK_FREE_IN_PORT (this,d,p) || CHECK_FREE_OUT_PORT (this,d,p))
+#define CHECK_IN_PORT(this,d,p) (CHECK_IN_PORT_ID(this,d,p) && (this)->in_ports[p].valid)
+#define CHECK_OUT_PORT(this,d,p) (CHECK_OUT_PORT_ID(this,d,p) && (this)->out_ports[p].valid)
+#define CHECK_PORT(this,d,p) (CHECK_IN_PORT (this,d,p) || CHECK_OUT_PORT (this,d,p))
+
+#define CHECK_PORT_BUFFER(this,b,p) (b < p->n_buffers)
+
+struct proxy_buffer {
+ struct spa_buffer *outbuf;
+ struct spa_buffer buffer;
+ struct spa_meta metas[4];
+ struct spa_data datas[4];
+ off_t offset;
+ size_t size;
+ bool outstanding;
+};
+
+struct proxy_port {
+ bool valid;
+ struct spa_port_info info;
+ struct spa_format *format;
+ uint32_t n_formats;
+ struct spa_format **formats;
+ uint32_t n_params;
+ struct spa_param **params;
+ struct spa_port_io *io;
+
+ uint32_t n_buffers;
+ struct proxy_buffer buffers[MAX_BUFFERS];
+};
+
+struct proxy {
+ struct spa_node node;
+
+ struct impl *impl;
+
+ struct spa_type_map *map;
+ struct spa_log *log;
+ struct spa_loop *data_loop;
+
+ const struct spa_node_callbacks *callbacks;
+ void *user_data;
+
+ struct pw_resource *resource;
+
+ struct spa_source data_source;
+ int writefd;
+
+ uint32_t max_inputs;
+ uint32_t n_inputs;
+ uint32_t max_outputs;
+ uint32_t n_outputs;
+ struct proxy_port in_ports[MAX_INPUTS];
+ struct proxy_port out_ports[MAX_OUTPUTS];
+
+ uint8_t format_buffer[1024];
+ uint32_t seq;
+};
+
+struct impl {
+ struct pw_client_node this;
+
+ uint32_t type_client_node;
+
+ struct pw_core *core;
+
+ struct proxy proxy;
+
+ struct pw_transport *transport;
+
+ struct pw_listener node_free;
+ struct pw_listener initialized;
+ struct pw_listener global_added;
+
+ int fds[2];
+ int other_fds[2];
+};
+
+/** \endcond */
+
+static int clear_buffers(struct proxy *this, struct proxy_port *port)
+{
+ if (port->n_buffers) {
+ spa_log_info(this->log, "proxy %p: clear buffers", this);
+ port->n_buffers = 0;
+ }
+ return SPA_RESULT_OK;
+}
+
+static int spa_proxy_node_get_props(struct spa_node *node, struct spa_props **props)
+{
+ return SPA_RESULT_NOT_IMPLEMENTED;
+}
+
+static int spa_proxy_node_set_props(struct spa_node *node, const struct spa_props *props)
+{
+ return SPA_RESULT_NOT_IMPLEMENTED;
+}
+
+static inline void do_flush(struct proxy *this)
+{
+ uint64_t cmd = 1;
+ if (write(this->writefd, &cmd, 8) != 8)
+ spa_log_warn(this->log, "proxy %p: error writing event: %s", this, strerror(errno));
+
+}
+
+static inline void send_need_input(struct proxy *this)
+{
+ struct impl *impl = SPA_CONTAINER_OF(this, struct impl, proxy);
+
+ pw_transport_add_event(impl->transport,
+ &SPA_EVENT_INIT(impl->core->type.event_transport.NeedInput));
+ do_flush(this);
+}
+
+static inline void send_have_output(struct proxy *this)
+{
+ struct impl *impl = SPA_CONTAINER_OF(this, struct impl, proxy);
+
+ pw_transport_add_event(impl->transport,
+ &SPA_EVENT_INIT(impl->core->type.event_transport.HaveOutput));
+ do_flush(this);
+}
+
+static int spa_proxy_node_send_command(struct spa_node *node, struct spa_command *command)
+{
+ struct proxy *this;
+ int res = SPA_RESULT_OK;
+ struct pw_core *core;
+
+ if (node == NULL || command == NULL)
+ return SPA_RESULT_INVALID_ARGUMENTS;
+
+ this = SPA_CONTAINER_OF(node, struct proxy, node);
+
+ if (this->resource == NULL)
+ return SPA_RESULT_OK;
+
+ core = this->impl->core;
+
+ if (SPA_COMMAND_TYPE(command) == core->type.command_node.ClockUpdate) {
+ pw_client_node_notify_node_command(this->resource, this->seq++, command);
+ } else {
+ /* send start */
+ pw_client_node_notify_node_command(this->resource, this->seq, command);
+ if (SPA_COMMAND_TYPE(command) == core->type.command_node.Start)
+ send_need_input(this);
+
+ res = SPA_RESULT_RETURN_ASYNC(this->seq++);
+ }
+ return res;
+}
+
+static int
+spa_proxy_node_set_callbacks(struct spa_node *node,
+ const struct spa_node_callbacks *callbacks,
+ void *user_data)
+{
+ struct proxy *this;
+
+ if (node == NULL)
+ return SPA_RESULT_INVALID_ARGUMENTS;
+
+ this = SPA_CONTAINER_OF(node, struct proxy, node);
+ this->callbacks = callbacks;
+ this->user_data = user_data;
+
+ return SPA_RESULT_OK;
+}
+
+static int
+spa_proxy_node_get_n_ports(struct spa_node *node,
+ uint32_t *n_input_ports,
+ uint32_t *max_input_ports,
+ uint32_t *n_output_ports,
+ uint32_t *max_output_ports)
+{
+ struct proxy *this;
+
+ if (node == NULL)
+ return SPA_RESULT_INVALID_ARGUMENTS;
+
+ this = SPA_CONTAINER_OF(node, struct proxy, node);
+
+ if (n_input_ports)
+ *n_input_ports = this->n_inputs;
+ if (max_input_ports)
+ *max_input_ports = this->max_inputs;
+ if (n_output_ports)
+ *n_output_ports = this->n_outputs;
+ if (max_output_ports)
+ *max_output_ports = this->max_outputs;
+
+ return SPA_RESULT_OK;
+}
+
+static int
+spa_proxy_node_get_port_ids(struct spa_node *node,
+ uint32_t n_input_ports,
+ uint32_t *input_ids,
+ uint32_t n_output_ports,
+ uint32_t *output_ids)
+{
+ struct proxy *this;
+ int c, i;
+
+ if (node == NULL)
+ return SPA_RESULT_INVALID_ARGUMENTS;
+
+ this = SPA_CONTAINER_OF(node, struct proxy, node);
+
+ if (input_ids) {
+ for (c = 0, i = 0; i < MAX_INPUTS && c < n_input_ports; i++) {
+ if (this->in_ports[i].valid)
+ input_ids[c++] = i;
+ }
+ }
+ if (output_ids) {
+ for (c = 0, i = 0; i < MAX_OUTPUTS && c < n_output_ports; i++) {
+ if (this->out_ports[i].valid)
+ output_ids[c++] = i;
+ }
+ }
+ return SPA_RESULT_OK;
+}
+
+static void
+do_update_port(struct proxy *this,
+ enum spa_direction direction,
+ uint32_t port_id,
+ uint32_t change_mask,
+ uint32_t n_possible_formats,
+ const struct spa_format **possible_formats,
+ const struct spa_format *format,
+ uint32_t n_params,
+ const struct spa_param **params,
+ const struct spa_port_info *info)
+{
+ struct proxy_port *port;
+ uint32_t i;
+
+ if (direction == SPA_DIRECTION_INPUT) {
+ port = &this->in_ports[port_id];
+ } else {
+ port = &this->out_ports[port_id];
+ }
+
+ if (change_mask & PW_CLIENT_NODE_PORT_UPDATE_POSSIBLE_FORMATS) {
+ for (i = 0; i < port->n_formats; i++)
+ free(port->formats[i]);
+ port->n_formats = n_possible_formats;
+ port->formats =
+ realloc(port->formats, port->n_formats * sizeof(struct spa_format *));
+ for (i = 0; i < port->n_formats; i++)
+ port->formats[i] = spa_format_copy(possible_formats[i]);
+ }
+ if (change_mask & PW_CLIENT_NODE_PORT_UPDATE_FORMAT) {
+ if (port->format)
+ free(port->format);
+ port->format = spa_format_copy(format);
+ }
+
+ if (change_mask & PW_CLIENT_NODE_PORT_UPDATE_PARAMS) {
+ for (i = 0; i < port->n_params; i++)
+ free(port->params[i]);
+ port->n_params = n_params;
+ port->params = realloc(port->params, port->n_params * sizeof(struct spa_param *));
+ for (i = 0; i < port->n_params; i++)
+ port->params[i] = spa_param_copy(params[i]);
+ }
+
+ if (change_mask & PW_CLIENT_NODE_PORT_UPDATE_INFO && info)
+ port->info = *info;
+
+ if (!port->valid) {
+ spa_log_info(this->log, "proxy %p: adding port %d", this, port_id);
+ port->format = NULL;
+ port->valid = true;
+
+ if (direction == SPA_DIRECTION_INPUT)
+ this->n_inputs++;
+ else
+ this->n_outputs++;
+ }
+}
+
+static void
+clear_port(struct proxy *this,
+ struct proxy_port *port, enum spa_direction direction, uint32_t port_id)
+{
+ do_update_port(this,
+ direction,
+ port_id,
+ PW_CLIENT_NODE_PORT_UPDATE_POSSIBLE_FORMATS |
+ PW_CLIENT_NODE_PORT_UPDATE_FORMAT |
+ PW_CLIENT_NODE_PORT_UPDATE_PARAMS |
+ PW_CLIENT_NODE_PORT_UPDATE_INFO, 0, NULL, NULL, 0, NULL, NULL);
+ clear_buffers(this, port);
+}
+
+static void do_uninit_port(struct proxy *this, enum spa_direction direction, uint32_t port_id)
+{
+ struct proxy_port *port;
+
+ spa_log_info(this->log, "proxy %p: removing port %d", this, port_id);
+ if (direction == SPA_DIRECTION_INPUT) {
+ port = &this->in_ports[port_id];
+ this->n_inputs--;
+ } else {
+ port = &this->out_ports[port_id];
+ this->n_outputs--;
+ }
+ clear_port(this, port, direction, port_id);
+ port->valid = false;
+}
+
+static int
+spa_proxy_node_add_port(struct spa_node *node, enum spa_direction direction, uint32_t port_id)
+{
+ struct proxy *this;
+ struct proxy_port *port;
+
+ if (node == NULL)
+ return SPA_RESULT_INVALID_ARGUMENTS;
+
+ this = SPA_CONTAINER_OF(node, struct proxy, node);
+
+ if (!CHECK_FREE_PORT(this, direction, port_id))
+ return SPA_RESULT_INVALID_PORT;
+
+ port =
+ direction == SPA_DIRECTION_INPUT ? &this->in_ports[port_id] : &this->out_ports[port_id];
+ clear_port(this, port, direction, port_id);
+
+ return SPA_RESULT_OK;
+}
+
+static int
+spa_proxy_node_remove_port(struct spa_node *node, enum spa_direction direction, uint32_t port_id)
+{
+ struct proxy *this;
+
+ if (node == NULL)
+ return SPA_RESULT_INVALID_ARGUMENTS;
+
+ this = SPA_CONTAINER_OF(node, struct proxy, node);
+
+ if (!CHECK_PORT(this, direction, port_id))
+ return SPA_RESULT_INVALID_PORT;
+
+ do_uninit_port(this, direction, port_id);
+
+ return SPA_RESULT_OK;
+}
+
+static int
+spa_proxy_node_port_enum_formats(struct spa_node *node,
+ enum spa_direction direction,
+ uint32_t port_id,
+ struct spa_format **format,
+ const struct spa_format *filter,
+ uint32_t index)
+{
+ struct proxy *this;
+ struct proxy_port *port;
+ struct spa_format *fmt;
+ struct spa_pod_builder b = { NULL, };
+ int res;
+ uint32_t count, match = 0;
+
+ if (node == NULL || format == NULL)
+ return SPA_RESULT_INVALID_ARGUMENTS;
+
+ this = SPA_CONTAINER_OF(node, struct proxy, node);
+
+ if (!CHECK_PORT(this, direction, port_id))
+ return SPA_RESULT_INVALID_PORT;
+
+ port =
+ direction == SPA_DIRECTION_INPUT ? &this->in_ports[port_id] : &this->out_ports[port_id];
+
+ count = match = filter ? 0 : index;
+
+ next:
+ if (count >= port->n_formats)
+ return SPA_RESULT_ENUM_END;
+
+ fmt = port->formats[count++];
+
+ spa_pod_builder_init(&b, this->format_buffer, sizeof(this->format_buffer));
+
+ if ((res = spa_format_filter(fmt, filter, &b)) != SPA_RESULT_OK || match++ != index)
+ goto next;
+
+ *format = SPA_POD_BUILDER_DEREF(&b, 0, struct spa_format);
+
+ return SPA_RESULT_OK;
+}
+
+static int
+spa_proxy_node_port_set_format(struct spa_node *node,
+ enum spa_direction direction,
+ uint32_t port_id, uint32_t flags, const struct spa_format *format)
+{
+ struct proxy *this;
+
+ if (node == NULL)
+ return SPA_RESULT_INVALID_ARGUMENTS;
+
+ this = SPA_CONTAINER_OF(node, struct proxy, node);
+
+ if (!CHECK_PORT(this, direction, port_id))
+ return SPA_RESULT_INVALID_PORT;
+
+ if (this->resource == NULL)
+ return SPA_RESULT_OK;
+
+ pw_client_node_notify_set_format(this->resource,
+ this->seq, direction, port_id, flags, format);
+
+ return SPA_RESULT_RETURN_ASYNC(this->seq++);
+}
+
+static int
+spa_proxy_node_port_get_format(struct spa_node *node,
+ enum spa_direction direction,
+ uint32_t port_id, const struct spa_format **format)
+{
+ struct proxy *this;
+ struct proxy_port *port;
+
+ if (node == NULL || format == NULL)
+ return SPA_RESULT_INVALID_ARGUMENTS;
+
+ this = SPA_CONTAINER_OF(node, struct proxy, node);
+
+ if (!CHECK_PORT(this, direction, port_id))
+ return SPA_RESULT_INVALID_PORT;
+
+ port =
+ direction == SPA_DIRECTION_INPUT ? &this->in_ports[port_id] : &this->out_ports[port_id];
+
+ if (!port->format)
+ return SPA_RESULT_NO_FORMAT;
+
+ *format = port->format;
+
+ return SPA_RESULT_OK;
+}
+
+static int
+spa_proxy_node_port_get_info(struct spa_node *node,
+ enum spa_direction direction,
+ uint32_t port_id, const struct spa_port_info **info)
+{
+ struct proxy *this;
+ struct proxy_port *port;
+
+ if (node == NULL || info == NULL)
+ return SPA_RESULT_INVALID_ARGUMENTS;
+
+ this = SPA_CONTAINER_OF(node, struct proxy, node);
+
+ if (!CHECK_PORT(this, direction, port_id))
+ return SPA_RESULT_INVALID_PORT;
+
+ port =
+ direction == SPA_DIRECTION_INPUT ? &this->in_ports[port_id] : &this->out_ports[port_id];
+
+ *info = &port->info;
+
+ return SPA_RESULT_OK;
+}
+
+static int
+spa_proxy_node_port_enum_params(struct spa_node *node,
+ enum spa_direction direction,
+ uint32_t port_id, uint32_t index, struct spa_param **param)
+{
+ struct proxy *this;
+ struct proxy_port *port;
+
+ spa_return_val_if_fail(node != NULL, SPA_RESULT_INVALID_ARGUMENTS);
+ spa_return_val_if_fail(param != NULL, SPA_RESULT_INVALID_ARGUMENTS);
+
+ this = SPA_CONTAINER_OF(node, struct proxy, node);
+
+ spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), SPA_RESULT_INVALID_PORT);
+
+ port =
+ direction == SPA_DIRECTION_INPUT ? &this->in_ports[port_id] : &this->out_ports[port_id];
+
+ if (index >= port->n_params)
+ return SPA_RESULT_ENUM_END;
+
+ *param = port->params[index];
+
+ return SPA_RESULT_OK;
+}
+
+static int
+spa_proxy_node_port_set_param(struct spa_node *node,
+ enum spa_direction direction,
+ uint32_t port_id, const struct spa_param *param)
+{
+ return SPA_RESULT_NOT_IMPLEMENTED;
+}
+
+static int
+spa_proxy_node_port_set_io(struct spa_node *node,
+ enum spa_direction direction, uint32_t port_id, struct spa_port_io *io)
+{
+ struct proxy *this;
+ struct proxy_port *port;
+
+ if (node == NULL)
+ return SPA_RESULT_INVALID_ARGUMENTS;
+
+ this = SPA_CONTAINER_OF(node, struct proxy, node);
+
+ if (!CHECK_PORT(this, direction, port_id))
+ return SPA_RESULT_INVALID_PORT;
+
+ port =
+ direction == SPA_DIRECTION_INPUT ? &this->in_ports[port_id] : &this->out_ports[port_id];
+ port->io = io;
+
+ return SPA_RESULT_OK;
+}
+
+static int
+spa_proxy_node_port_use_buffers(struct spa_node *node,
+ enum spa_direction direction,
+ uint32_t port_id,
+ struct spa_buffer **buffers,
+ uint32_t n_buffers)
+{
+ struct proxy *this;
+ struct impl *impl;
+ struct proxy_port *port;
+ uint32_t i, j;
+ size_t n_mem;
+ struct pw_client_node_buffer *mb;
+ struct spa_meta_shared *msh;
+
+ this = SPA_CONTAINER_OF(node, struct proxy, node);
+ impl = this->impl;
+ spa_log_info(this->log, "proxy %p: use buffers %p %u", this, buffers, n_buffers);
+
+ if (!CHECK_PORT(this, direction, port_id))
+ return SPA_RESULT_INVALID_PORT;
+
+ port =
+ direction == SPA_DIRECTION_INPUT ? &this->in_ports[port_id] : &this->out_ports[port_id];
+
+ if (!port->format)
+ return SPA_RESULT_NO_FORMAT;
+
+ clear_buffers(this, port);
+
+ if (n_buffers > 0) {
+ mb = alloca(n_buffers * sizeof(struct pw_client_node_buffer));
+ } else {
+ mb = NULL;
+ }
+
+ port->n_buffers = n_buffers;
+
+ if (this->resource == NULL)
+ return SPA_RESULT_OK;
+
+ n_mem = 0;
+ for (i = 0; i < n_buffers; i++) {
+ struct proxy_buffer *b = &port->buffers[i];
+
+ msh = spa_buffer_find_meta(buffers[i], impl->core->type.meta.Shared);
+ if (msh == NULL) {
+ spa_log_error(this->log, "missing shared metadata on buffer %d", i);
+ return SPA_RESULT_ERROR;
+ }
+
+ b->outbuf = buffers[i];
+ memcpy(&b->buffer, buffers[i], sizeof(struct spa_buffer));
+ b->buffer.datas = b->datas;
+ b->buffer.metas = b->metas;
+
+ mb[i].buffer = &b->buffer;
+ mb[i].mem_id = n_mem++;
+ mb[i].offset = 0;
+ mb[i].size = msh->size;
+
+ pw_client_node_notify_add_mem(this->resource,
+ direction,
+ port_id,
+ mb[i].mem_id,
+ impl->core->type.data.MemFd,
+ msh->fd, msh->flags, msh->offset, msh->size);
+
+ for (j = 0; j < buffers[i]->n_metas; j++) {
+ memcpy(&b->buffer.metas[j], &buffers[i]->metas[j], sizeof(struct spa_meta));
+ }
+
+ for (j = 0; j < buffers[i]->n_datas; j++) {
+ struct spa_data *d = &buffers[i]->datas[j];
+
+ memcpy(&b->buffer.datas[j], d, sizeof(struct spa_data));
+
+ if (d->type == impl->core->type.data.DmaBuf ||
+ d->type == impl->core->type.data.MemFd) {
+ pw_client_node_notify_add_mem(this->resource,
+ direction,
+ port_id,
+ n_mem,
+ d->type,
+ d->fd,
+ d->flags, d->mapoffset, d->maxsize);
+ b->buffer.datas[j].type = impl->core->type.data.Id;
+ b->buffer.datas[j].data = SPA_UINT32_TO_PTR(n_mem);
+ n_mem++;
+ } else if (d->type == impl->core->type.data.MemPtr) {
+ b->buffer.datas[j].data = SPA_INT_TO_PTR(b->size);
+ b->size += d->maxsize;
+ } else {
+ b->buffer.datas[j].type = SPA_ID_INVALID;
+ b->buffer.datas[j].data = 0;
+ spa_log_error(this->log, "invalid memory type %d", d->type);
+ }
+ }
+ }
+
+ pw_client_node_notify_use_buffers(this->resource,
+ this->seq, direction, port_id, n_buffers, mb);
+
+ return SPA_RESULT_RETURN_ASYNC(this->seq++);
+}
+
+static int
+spa_proxy_node_port_alloc_buffers(struct spa_node *node,
+ enum spa_direction direction,
+ uint32_t port_id,
+ struct spa_param **params,
+ uint32_t n_params,
+ struct spa_buffer **buffers,
+ uint32_t *n_buffers)
+{
+ struct proxy *this;
+ struct proxy_port *port;
+
+ if (node == NULL || buffers == NULL)
+ return SPA_RESULT_INVALID_ARGUMENTS;
+
+ this = SPA_CONTAINER_OF(node, struct proxy, node);
+
+ if (!CHECK_PORT(this, direction, port_id))
+ return SPA_RESULT_INVALID_PORT;
+
+ port =
+ direction == SPA_DIRECTION_INPUT ? &this->in_ports[port_id] : &this->out_ports[port_id];
+
+ if (!port->format)
+ return SPA_RESULT_NO_FORMAT;
+
+ return SPA_RESULT_NOT_IMPLEMENTED;
+}
+
+static int
+spa_proxy_node_port_reuse_buffer(struct spa_node *node, uint32_t port_id, uint32_t buffer_id)
+{
+ struct proxy *this;
+ struct impl *impl;
+
+ if (node == NULL)
+ return SPA_RESULT_INVALID_ARGUMENTS;
+
+ this = SPA_CONTAINER_OF(node, struct proxy, node);
+ impl = this->impl;
+
+ if (!CHECK_OUT_PORT(this, SPA_DIRECTION_OUTPUT, port_id))
+ return SPA_RESULT_INVALID_PORT;
+
+ spa_log_trace(this->log, "reuse buffer %d", buffer_id);
+ {
+ struct pw_event_transport_reuse_buffer rb = PW_EVENT_TRANSPORT_REUSE_BUFFER_INIT
+ (impl->core->type.event_transport.ReuseBuffer, port_id, buffer_id);
+ pw_transport_add_event(impl->transport, (struct spa_event *) &rb);
+ }
+
+ return SPA_RESULT_OK;
+}
+
+static int
+spa_proxy_node_port_send_command(struct spa_node *node,
+ enum spa_direction direction,
+ uint32_t port_id, struct spa_command *command)
+{
+ struct proxy *this;
+
+ if (node == NULL || command == NULL)
+ return SPA_RESULT_INVALID_ARGUMENTS;
+
+ this = SPA_CONTAINER_OF(node, struct proxy, node);
+
+ spa_log_warn(this->log, "unhandled command %d", SPA_COMMAND_TYPE(command));
+ return SPA_RESULT_NOT_IMPLEMENTED;
+}
+
+static int spa_proxy_node_process_input(struct spa_node *node)
+{
+ struct impl *impl;
+ struct proxy *this;
+ int i;
+
+ if (node == NULL)
+ return SPA_RESULT_INVALID_ARGUMENTS;
+
+ this = SPA_CONTAINER_OF(node, struct proxy, node);
+ impl = this->impl;
+
+ for (i = 0; i < MAX_INPUTS; i++) {
+ struct spa_port_io *io = this->in_ports[i].io;
+
+ if (!io)
+ continue;
+
+ pw_log_trace("%d %d", io->status, io->buffer_id);
+
+ impl->transport->inputs[i] = *io;
+ io->status = SPA_RESULT_OK;
+ }
+ send_have_output(this);
+
+ if (this->callbacks->need_input)
+ return SPA_RESULT_OK;
+ else
+ return SPA_RESULT_NEED_BUFFER;
+}
+
+static int spa_proxy_node_process_output(struct spa_node *node)
+{
+ struct proxy *this;
+ struct impl *impl;
+ int i;
+ bool send_need = false, flush = false;
+
+ this = SPA_CONTAINER_OF(node, struct proxy, node);
+ impl = this->impl;
+
+ for (i = 0; i < MAX_OUTPUTS; i++) {
+ struct spa_port_io *io = this->out_ports[i].io, tmp;
+
+ if (!io)
+ continue;
+
+ if (io->buffer_id != SPA_ID_INVALID) {
+ struct pw_event_transport_reuse_buffer rb =
+ PW_EVENT_TRANSPORT_REUSE_BUFFER_INIT(impl->core->type.event_transport.
+ ReuseBuffer, i, io->buffer_id);
+
+ spa_log_trace(this->log, "reuse buffer %d", io->buffer_id);
+
+ pw_transport_add_event(impl->transport, (struct spa_event *) &rb);
+ io->buffer_id = SPA_ID_INVALID;
+ flush = true;
+ }
+
+ tmp = impl->transport->outputs[i];
+ impl->transport->outputs[i] = *io;
+
+ pw_log_trace("%d %d %d %d", io->status, io->buffer_id, tmp.status, tmp.buffer_id);
+
+ if (io->status == SPA_RESULT_NEED_BUFFER)
+ send_need = true;
+
+ *io = tmp;
+ }
+ if (send_need)
+ send_need_input(this);
+ else if (flush)
+ do_flush(this);
+
+ return SPA_RESULT_HAVE_BUFFER;
+}
+
+static int handle_node_event(struct proxy *this, struct spa_event *event)
+{
+ struct impl *impl = SPA_CONTAINER_OF(this, struct impl, proxy);
+ int i;
+
+ if (SPA_EVENT_TYPE(event) == impl->core->type.event_transport.HaveOutput) {
+ for (i = 0; i < MAX_OUTPUTS; i++) {
+ struct spa_port_io *io = this->out_ports[i].io;
+
+ if (!io)
+ continue;
+
+ *io = impl->transport->outputs[i];
+ pw_log_trace("%d %d", io->status, io->buffer_id);
+ }
+ this->callbacks->have_output(&this->node, this->user_data);
+ } else if (SPA_EVENT_TYPE(event) == impl->core->type.event_transport.NeedInput) {
+ this->callbacks->need_input(&this->node, this->user_data);
+ } else if (SPA_EVENT_TYPE(event) == impl->core->type.event_transport.ReuseBuffer) {
+ struct pw_event_transport_reuse_buffer *p =
+ (struct pw_event_transport_reuse_buffer *) event;
+ this->callbacks->reuse_buffer(&this->node, p->body.port_id.value,
+ p->body.buffer_id.value, this->user_data);
+ }
+ return SPA_RESULT_OK;
+}
+
+static void
+client_node_done(void *object, int seq, int res)
+{
+ struct pw_resource *resource = object;
+ struct pw_client_node *node = resource->object;
+ struct impl *impl = SPA_CONTAINER_OF(node, struct impl, this);
+ struct proxy *this = &impl->proxy;
+
+ this->callbacks->done(&this->node, seq, res, this->user_data);
+}
+
+
+static void
+client_node_update(void *object,
+ uint32_t change_mask,
+ uint32_t max_input_ports,
+ uint32_t max_output_ports, const struct spa_props *props)
+{
+ struct pw_resource *resource = object;
+ struct pw_client_node *node = resource->object;
+ struct impl *impl = SPA_CONTAINER_OF(node, struct impl, this);
+ struct proxy *this = &impl->proxy;
+
+ if (change_mask & PW_CLIENT_NODE_UPDATE_MAX_INPUTS)
+ this->max_inputs = max_input_ports;
+ if (change_mask & PW_CLIENT_NODE_UPDATE_MAX_OUTPUTS)
+ this->max_outputs = max_output_ports;
+
+ spa_log_info(this->log, "proxy %p: got node update max_in %u, max_out %u", this,
+ this->max_inputs, this->max_outputs);
+}
+
+static void
+client_node_port_update(void *object,
+ enum spa_direction direction,
+ uint32_t port_id,
+ uint32_t change_mask,
+ uint32_t n_possible_formats,
+ const struct spa_format **possible_formats,
+ const struct spa_format *format,
+ uint32_t n_params,
+ const struct spa_param **params, const struct spa_port_info *info)
+{
+ struct pw_resource *resource = object;
+ struct pw_client_node *node = resource->object;
+ struct impl *impl = SPA_CONTAINER_OF(node, struct impl, this);
+ struct proxy *this = &impl->proxy;
+ bool remove;
+
+ spa_log_info(this->log, "proxy %p: got port update", this);
+ if (!CHECK_PORT_ID(this, direction, port_id))
+ return;
+
+ remove = (change_mask == 0);
+
+ if (remove) {
+ do_uninit_port(this, direction, port_id);
+ } else {
+ do_update_port(this,
+ direction,
+ port_id,
+ change_mask,
+ n_possible_formats,
+ possible_formats, format, n_params, params, info);
+ }
+}
+
+static void client_node_event(void *object, struct spa_event *event)
+{
+ struct pw_resource *resource = object;
+ struct pw_client_node *node = resource->object;
+ struct impl *impl = SPA_CONTAINER_OF(node, struct impl, this);
+ struct proxy *this = &impl->proxy;
+
+ this->callbacks->event(&this->node, event, this->user_data);
+}
+
+static void client_node_destroy(void *object)
+{
+ struct pw_resource *resource = object;
+ struct pw_client_node *node = resource->object;
+ pw_client_node_destroy(node);
+}
+
+static struct pw_client_node_methods client_node_methods = {
+ &client_node_done,
+ &client_node_update,
+ &client_node_port_update,
+ &client_node_event,
+ &client_node_destroy,
+};
+
+static void proxy_on_data_fd_events(struct spa_source *source)
+{
+ struct proxy *this = source->data;
+ struct impl *impl = this->impl;
+
+ if (source->rmask & (SPA_IO_ERR | SPA_IO_HUP)) {
+ spa_log_warn(this->log, "proxy %p: got error", this);
+ return;
+ }
+
+ if (source->rmask & SPA_IO_IN) {
+ struct spa_event event;
+ uint64_t cmd;
+
+ if (read(this->data_source.fd, &cmd, 8) != 8)
+ spa_log_warn(this->log, "proxy %p: error reading event: %s",
+ this, strerror(errno));
+
+ while (pw_transport_next_event(impl->transport, &event) == SPA_RESULT_OK) {
+ struct spa_event *ev = alloca(SPA_POD_SIZE(&event));
+ pw_transport_parse_event(impl->transport, ev);
+ handle_node_event(this, ev);
+ }
+ }
+}
+
+static const struct spa_node proxy_node = {
+ SPA_VERSION_NODE,
+ NULL,
+ spa_proxy_node_get_props,
+ spa_proxy_node_set_props,
+ spa_proxy_node_send_command,
+ spa_proxy_node_set_callbacks,
+ spa_proxy_node_get_n_ports,
+ spa_proxy_node_get_port_ids,
+ spa_proxy_node_add_port,
+ spa_proxy_node_remove_port,
+ spa_proxy_node_port_enum_formats,
+ spa_proxy_node_port_set_format,
+ spa_proxy_node_port_get_format,
+ spa_proxy_node_port_get_info,
+ spa_proxy_node_port_enum_params,
+ spa_proxy_node_port_set_param,
+ spa_proxy_node_port_use_buffers,
+ spa_proxy_node_port_alloc_buffers,
+ spa_proxy_node_port_set_io,
+ spa_proxy_node_port_reuse_buffer,
+ spa_proxy_node_port_send_command,
+ spa_proxy_node_process_input,
+ spa_proxy_node_process_output,
+};
+
+static int
+proxy_init(struct proxy *this,
+ struct spa_dict *info,
+ const struct spa_support *support,
+ uint32_t n_support)
+{
+ uint32_t i;
+
+ for (i = 0; i < n_support; i++) {
+ if (strcmp(support[i].type, SPA_TYPE__Log) == 0)
+ this->log = support[i].data;
+ else if (strcmp(support[i].type, SPA_TYPE_LOOP__DataLoop) == 0)
+ this->data_loop = support[i].data;
+ }
+ if (this->data_loop == NULL) {
+ spa_log_error(this->log, "a data-loop is needed");
+ }
+
+ this->node = proxy_node;
+
+ this->data_source.func = proxy_on_data_fd_events;
+ this->data_source.data = this;
+ this->data_source.fd = -1;
+ this->data_source.mask = SPA_IO_IN | SPA_IO_ERR | SPA_IO_HUP;
+ this->data_source.rmask = 0;
+
+ return SPA_RESULT_RETURN_ASYNC(this->seq++);
+}
+
+static void on_initialized(struct pw_listener *listener, struct pw_node *node)
+{
+ struct impl *impl = SPA_CONTAINER_OF(listener, struct impl, initialized);
+ struct pw_client_node *this = &impl->this;
+ struct pw_transport_info info;
+ int readfd, writefd;
+
+ if (this->resource == NULL)
+ return;
+
+ impl->transport = pw_transport_new(node->info.max_input_ports, node->info.max_output_ports);
+ impl->transport->area->n_input_ports = node->info.n_input_ports;
+ impl->transport->area->n_output_ports = node->info.n_output_ports;
+
+ pw_client_node_get_fds(this, &readfd, &writefd);
+ pw_transport_get_info(impl->transport, &info);
+
+ pw_client_node_notify_transport(this->resource, node->global->id,
+ readfd, writefd, info.memfd, info.offset, info.size);
+}
+
+static void
+on_global_added(struct pw_listener *listener, struct pw_core *core, struct pw_global *global)
+{
+ struct impl *impl = SPA_CONTAINER_OF(listener, struct impl, global_added);
+
+ if (global->object == impl->this.node)
+ global->owner = impl->this.resource;
+}
+
+static int proxy_clear(struct proxy *this)
+{
+ uint32_t i;
+
+ for (i = 0; i < MAX_INPUTS; i++) {
+ if (this->in_ports[i].valid)
+ clear_port(this, &this->in_ports[i], SPA_DIRECTION_INPUT, i);
+ }
+ for (i = 0; i < MAX_OUTPUTS; i++) {
+ if (this->out_ports[i].valid)
+ clear_port(this, &this->out_ports[i], SPA_DIRECTION_OUTPUT, i);
+ }
+
+ return SPA_RESULT_OK;
+}
+
+static void client_node_resource_destroy(struct pw_resource *resource)
+{
+ struct pw_client_node *this = resource->object;
+ struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this);
+ struct proxy *proxy = &impl->proxy;
+
+ pw_log_debug("client-node %p: destroy", impl);
+ pw_signal_emit(&this->destroy_signal, this);
+
+ impl->proxy.resource = this->resource = NULL;
+
+ pw_signal_remove(&impl->global_added);
+ pw_signal_remove(&impl->initialized);
+
+ if (proxy->data_source.fd != -1)
+ spa_loop_remove_source(proxy->data_loop, &proxy->data_source);
+
+ pw_node_destroy(this->node);
+}
+
+static void on_node_free(struct pw_listener *listener, struct pw_node *node)
+{
+ struct impl *impl = SPA_CONTAINER_OF(listener, struct impl, node_free);
+
+ pw_log_debug("client-node %p: free", &impl->this);
+ proxy_clear(&impl->proxy);
+
+ pw_signal_remove(&impl->node_free);
+
+ if (impl->transport)
+ pw_transport_destroy(impl->transport);
+
+ if (impl->fds[0] != -1)
+ close(impl->fds[0]);
+ if (impl->fds[1] != -1)
+ close(impl->fds[1]);
+ free(impl);
+}
+
+/** Create a new client node
+ * \param client an owner \ref pw_client
+ * \param id an id
+ * \param name a name
+ * \param properties extra properties
+ * \return a newly allocated client node
+ *
+ * Create a new \ref pw_node.
+ *
+ * \memberof pw_client_node
+ */
+struct pw_client_node *pw_client_node_new(struct pw_resource *resource,
+ const char *name,
+ struct pw_properties *properties)
+{
+ struct impl *impl;
+ struct pw_client_node *this;
+ struct pw_core *core = resource->client->core;
+
+ impl = calloc(1, sizeof(struct impl));
+ if (impl == NULL)
+ return NULL;
+
+ this = &impl->this;
+
+ impl->core = core;
+ impl->fds[0] = impl->fds[1] = -1;
+ pw_log_debug("client-node %p: new", impl);
+
+ impl->type_client_node = spa_type_map_get_id(core->type.map, PIPEWIRE_TYPE__ClientNode);
+
+ pw_signal_init(&this->destroy_signal);
+
+ proxy_init(&impl->proxy, NULL, core->support, core->n_support);
+ impl->proxy.impl = impl;
+
+ this->resource = resource;
+ this->node = pw_spa_node_new(core,
+ this->resource,
+ name,
+ true,
+ &impl->proxy.node,
+ NULL,
+ properties);
+ if (this->node == NULL)
+ goto error_no_node;
+
+ pw_resource_set_implementation(this->resource,
+ this,
+ PW_VERSION_CLIENT_NODE,
+ &client_node_methods,
+ (pw_destroy_t) client_node_resource_destroy);
+
+ impl->proxy.resource = this->resource;
+
+ pw_signal_add(&this->node->free_signal, &impl->node_free, on_node_free);
+ pw_signal_add(&this->node->initialized, &impl->initialized, on_initialized);
+ pw_signal_add(&impl->core->global_added, &impl->global_added, on_global_added);
+
+ return this;
+
+ error_no_node:
+ pw_resource_destroy(this->resource);
+ proxy_clear(&impl->proxy);
+ free(impl);
+ return NULL;
+}
+
+/** Destroy a client node
+ * \param node the client node to destroy
+ * \memberof pw_client_node
+ */
+void pw_client_node_destroy(struct pw_client_node *node)
+{
+ pw_resource_destroy(node->resource);
+}
+
+/** Get the set of fds for this \ref pw_client_node
+ *
+ * \param node a \ref pw_client_node
+ * \param[out] readfd an fd for reading
+ * \param[out] writefd an fd for writing
+ * \return 0 on success < 0 on error
+ *
+ * Create or return a previously created set of fds for \a node.
+ *
+ * \memberof pw_client_node
+ */
+int pw_client_node_get_fds(struct pw_client_node *node, int *readfd, int *writefd)
+{
+ struct impl *impl = SPA_CONTAINER_OF(node, struct impl, this);
+
+ if (impl->fds[0] == -1) {
+#if 0
+ if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, impl->fds) !=
+ 0)
+ return SPA_RESULT_ERRNO;
+
+ impl->proxy.data_source.fd = impl->fds[0];
+ impl->proxy.writefd = impl->fds[0];
+ impl->other_fds[0] = impl->fds[1];
+ impl->other_fds[1] = impl->fds[1];
+#else
+ impl->fds[0] = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK);
+ impl->fds[1] = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK);
+ impl->proxy.data_source.fd = impl->fds[0];
+ impl->proxy.writefd = impl->fds[1];
+ impl->other_fds[0] = impl->fds[1];
+ impl->other_fds[1] = impl->fds[0];
+#endif
+
+ spa_loop_add_source(impl->proxy.data_loop, &impl->proxy.data_source);
+ pw_log_debug("client-node %p: add data fd %d", node, impl->proxy.data_source.fd);
+ }
+ *readfd = impl->other_fds[0];
+ *writefd = impl->other_fds[1];
+
+ return SPA_RESULT_OK;
+}
diff --git a/src/modules/module-client-node/client-node.h b/src/modules/module-client-node/client-node.h
new file mode 100644
index 00000000..1dcaec8c
--- /dev/null
+++ b/src/modules/module-client-node/client-node.h
@@ -0,0 +1,57 @@
+/* PipeWire
+ * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __PIPEWIRE_CLIENT_NODE_H__
+#define __PIPEWIRE_CLIENT_NODE_H__
+
+#include <pipewire/node.h>
+#include <extensions/client-node.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** \class pw_client_node
+ *
+ * PipeWire client node interface
+ */
+struct pw_client_node {
+ struct pw_node *node;
+
+ struct pw_resource *resource;
+
+ PW_SIGNAL(destroy_signal, (struct pw_listener *listener, struct pw_client_node *node));
+};
+
+struct pw_client_node *
+pw_client_node_new(struct pw_resource *resource,
+ const char *name,
+ struct pw_properties *properties);
+
+void
+pw_client_node_destroy(struct pw_client_node *node);
+
+int
+pw_client_node_get_fds(struct pw_client_node *node, int *readfd, int *writefd);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __PIPEWIRE_CLIENT_NODE_H__ */
diff --git a/src/modules/module-client-node/protocol-native.c b/src/modules/module-client-node/protocol-native.c
new file mode 100644
index 00000000..d7933f77
--- /dev/null
+++ b/src/modules/module-client-node/protocol-native.c
@@ -0,0 +1,873 @@
+/* PipeWire
+ * Copyright (C) 2017 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <errno.h>
+
+#include "spa/pod-iter.h"
+
+#include "pipewire/pipewire.h"
+#include "pipewire/interfaces.h"
+#include "pipewire/protocol.h"
+#include "pipewire/client.h"
+#include "extensions/client-node.h"
+
+#include "modules/module-protocol-native/connection.h"
+
+/** \cond */
+
+typedef bool(*demarshal_func_t) (void *object, void *data, size_t size);
+
+/** \endcond */
+
+static void
+client_node_marshal_done(void *object, int seq, int res)
+{
+ struct pw_proxy *proxy = object;
+ struct pw_connection *connection = proxy->remote->protocol_private;
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f;
+
+ b = pw_connection_begin_write_proxy(connection, proxy, PW_CLIENT_NODE_METHOD_DONE);
+
+ spa_pod_builder_struct(b, &f,
+ SPA_POD_TYPE_INT, seq,
+ SPA_POD_TYPE_INT, res);
+
+ pw_connection_end_write(connection, b);
+}
+
+
+static void
+client_node_marshal_update(void *object,
+ uint32_t change_mask,
+ uint32_t max_input_ports,
+ uint32_t max_output_ports, const struct spa_props *props)
+{
+ struct pw_proxy *proxy = object;
+ struct pw_connection *connection = proxy->remote->protocol_private;
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f;
+
+ b = pw_connection_begin_write_proxy(connection, proxy, PW_CLIENT_NODE_METHOD_UPDATE);
+
+ spa_pod_builder_struct(b, &f,
+ SPA_POD_TYPE_INT, change_mask,
+ SPA_POD_TYPE_INT, max_input_ports,
+ SPA_POD_TYPE_INT, max_output_ports, SPA_POD_TYPE_POD, props);
+
+ pw_connection_end_write(connection, b);
+}
+
+static void
+client_node_marshal_port_update(void *object,
+ enum spa_direction direction,
+ uint32_t port_id,
+ uint32_t change_mask,
+ uint32_t n_possible_formats,
+ const struct spa_format **possible_formats,
+ const struct spa_format *format,
+ uint32_t n_params,
+ const struct spa_param **params, const struct spa_port_info *info)
+{
+ struct pw_proxy *proxy = object;
+ struct pw_connection *connection = proxy->remote->protocol_private;
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f[2];
+ int i;
+
+ b = pw_connection_begin_write_proxy(connection, proxy, PW_CLIENT_NODE_METHOD_PORT_UPDATE);
+
+ spa_pod_builder_add(b,
+ SPA_POD_TYPE_STRUCT, &f[0],
+ SPA_POD_TYPE_INT, direction,
+ SPA_POD_TYPE_INT, port_id,
+ SPA_POD_TYPE_INT, change_mask, SPA_POD_TYPE_INT, n_possible_formats, 0);
+
+ for (i = 0; i < n_possible_formats; i++)
+ spa_pod_builder_add(b, SPA_POD_TYPE_POD, possible_formats[i], 0);
+
+ spa_pod_builder_add(b, SPA_POD_TYPE_POD, format, SPA_POD_TYPE_INT, n_params, 0);
+
+ for (i = 0; i < n_params; i++) {
+ const struct spa_param *p = params[i];
+ spa_pod_builder_add(b, SPA_POD_TYPE_POD, p, 0);
+ }
+
+ if (info) {
+ spa_pod_builder_add(b,
+ SPA_POD_TYPE_STRUCT, &f[1],
+ SPA_POD_TYPE_INT, info->flags, SPA_POD_TYPE_INT, info->rate, 0);
+ spa_pod_builder_add(b, -SPA_POD_TYPE_STRUCT, &f[1], 0);
+ } else {
+ spa_pod_builder_add(b, SPA_POD_TYPE_POD, NULL, 0);
+ }
+ spa_pod_builder_add(b, -SPA_POD_TYPE_STRUCT, &f[0], 0);
+
+ pw_connection_end_write(connection, b);
+}
+
+static void client_node_marshal_event_method(void *object, struct spa_event *event)
+{
+ struct pw_proxy *proxy = object;
+ struct pw_connection *connection = proxy->remote->protocol_private;
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f;
+
+ b = pw_connection_begin_write_proxy(connection, proxy, PW_CLIENT_NODE_METHOD_EVENT);
+
+ spa_pod_builder_struct(b, &f, SPA_POD_TYPE_POD, event);
+
+ pw_connection_end_write(connection, b);
+}
+
+static void client_node_marshal_destroy(void *object)
+{
+ struct pw_proxy *proxy = object;
+ struct pw_connection *connection = proxy->remote->protocol_private;
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f;
+
+ b = pw_connection_begin_write_proxy(connection, proxy, PW_CLIENT_NODE_METHOD_DESTROY);
+
+ spa_pod_builder_struct(b, &f, 0);
+
+ pw_connection_end_write(connection, b);
+}
+
+static bool client_node_demarshal_set_props(void *object, void *data, size_t size)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_iter it;
+ uint32_t seq;
+ const struct spa_props *props = NULL;
+
+ if (!spa_pod_iter_struct(&it, data, size) ||
+ !spa_pod_iter_get(&it,
+ SPA_POD_TYPE_INT, &seq,
+ -SPA_POD_TYPE_OBJECT, &props, 0))
+ return false;
+
+ ((struct pw_client_node_events *) proxy->implementation)->set_props(proxy, seq, props);
+ return true;
+}
+
+static bool client_node_demarshal_event_event(void *object, void *data, size_t size)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_iter it;
+ const struct spa_event *event;
+
+ if (!spa_pod_iter_struct(&it, data, size) ||
+ !pw_pod_remap_data(SPA_POD_TYPE_STRUCT, data, size, &proxy->remote->types) ||
+ !spa_pod_iter_get(&it, SPA_POD_TYPE_OBJECT, &event, 0))
+ return false;
+
+ ((struct pw_client_node_events *) proxy->implementation)->event(proxy, event);
+ return true;
+}
+
+static bool client_node_demarshal_add_port(void *object, void *data, size_t size)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_iter it;
+ int32_t seq, direction, port_id;
+
+ if (!spa_pod_iter_struct(&it, data, size) ||
+ !spa_pod_iter_get(&it,
+ SPA_POD_TYPE_INT, &seq,
+ SPA_POD_TYPE_INT, &direction, SPA_POD_TYPE_INT, &port_id, 0))
+ return false;
+
+ ((struct pw_client_node_events *) proxy->implementation)->add_port(proxy, seq, direction,
+ port_id);
+ return true;
+}
+
+static bool client_node_demarshal_remove_port(void *object, void *data, size_t size)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_iter it;
+ int32_t seq, direction, port_id;
+
+ if (!spa_pod_iter_struct(&it, data, size) ||
+ !spa_pod_iter_get(&it,
+ SPA_POD_TYPE_INT, &seq,
+ SPA_POD_TYPE_INT, &direction, SPA_POD_TYPE_INT, &port_id, 0))
+ return false;
+
+ ((struct pw_client_node_events *) proxy->implementation)->remove_port(proxy, seq, direction,
+ port_id);
+ return true;
+}
+
+static bool client_node_demarshal_set_format(void *object, void *data, size_t size)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_iter it;
+ uint32_t seq, direction, port_id, flags;
+ const struct spa_format *format = NULL;
+
+ if (!spa_pod_iter_struct(&it, data, size) ||
+ !pw_pod_remap_data(SPA_POD_TYPE_STRUCT, data, size, &proxy->remote->types) ||
+ !spa_pod_iter_get(&it,
+ SPA_POD_TYPE_INT, &seq,
+ SPA_POD_TYPE_INT, &direction,
+ SPA_POD_TYPE_INT, &port_id,
+ SPA_POD_TYPE_INT, &flags,
+ -SPA_POD_TYPE_OBJECT, &format, 0))
+ return false;
+
+ ((struct pw_client_node_events *) proxy->implementation)->set_format(proxy, seq, direction,
+ port_id, flags,
+ format);
+ return true;
+}
+
+static bool client_node_demarshal_set_param(void *object, void *data, size_t size)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_iter it;
+ uint32_t seq, direction, port_id;
+ const struct spa_param *param = NULL;
+
+ if (!spa_pod_iter_struct(&it, data, size) ||
+ !pw_pod_remap_data(SPA_POD_TYPE_STRUCT, data, size, &proxy->remote->types) ||
+ !spa_pod_iter_get(&it,
+ SPA_POD_TYPE_INT, &seq,
+ SPA_POD_TYPE_INT, &direction,
+ SPA_POD_TYPE_INT, &port_id,
+ -SPA_POD_TYPE_OBJECT, &param, 0))
+ return false;
+
+ ((struct pw_client_node_events *) proxy->implementation)->set_param(proxy, seq, direction,
+ port_id, param);
+ return true;
+}
+
+static bool client_node_demarshal_add_mem(void *object, void *data, size_t size)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_iter it;
+ struct pw_connection *connection = proxy->remote->protocol_private;
+ uint32_t direction, port_id, mem_id, type, memfd_idx, flags, offset, sz;
+ int memfd;
+
+ if (!spa_pod_iter_struct(&it, data, size) ||
+ !pw_pod_remap_data(SPA_POD_TYPE_STRUCT, data, size, &proxy->remote->types) ||
+ !spa_pod_iter_get(&it,
+ SPA_POD_TYPE_INT, &direction,
+ SPA_POD_TYPE_INT, &port_id,
+ SPA_POD_TYPE_INT, &mem_id,
+ SPA_POD_TYPE_ID, &type,
+ SPA_POD_TYPE_INT, &memfd_idx,
+ SPA_POD_TYPE_INT, &flags,
+ SPA_POD_TYPE_INT, &offset, SPA_POD_TYPE_INT, &sz, 0))
+ return false;
+
+ memfd = pw_connection_get_fd(connection, memfd_idx);
+
+ ((struct pw_client_node_events *) proxy->implementation)->add_mem(proxy,
+ direction,
+ port_id,
+ mem_id,
+ type,
+ memfd, flags, offset, sz);
+ return true;
+}
+
+static bool client_node_demarshal_use_buffers(void *object, void *data, size_t size)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_iter it;
+ uint32_t seq, direction, port_id, n_buffers, data_id;
+ struct pw_client_node_buffer *buffers;
+ int i, j;
+
+ if (!spa_pod_iter_struct(&it, data, size) ||
+ !pw_pod_remap_data(SPA_POD_TYPE_STRUCT, data, size, &proxy->remote->types) ||
+ !spa_pod_iter_get(&it,
+ SPA_POD_TYPE_INT, &seq,
+ SPA_POD_TYPE_INT, &direction,
+ SPA_POD_TYPE_INT, &port_id, SPA_POD_TYPE_INT, &n_buffers, 0))
+ return false;
+
+ buffers = alloca(sizeof(struct pw_client_node_buffer) * n_buffers);
+ for (i = 0; i < n_buffers; i++) {
+ struct spa_buffer *buf = buffers[i].buffer = alloca(sizeof(struct spa_buffer));
+
+ if (!spa_pod_iter_get(&it,
+ SPA_POD_TYPE_INT, &buffers[i].mem_id,
+ SPA_POD_TYPE_INT, &buffers[i].offset,
+ SPA_POD_TYPE_INT, &buffers[i].size,
+ SPA_POD_TYPE_INT, &buf->id,
+ SPA_POD_TYPE_INT, &buf->n_metas, 0))
+ return false;
+
+ buf->metas = alloca(sizeof(struct spa_meta) * buf->n_metas);
+ for (j = 0; j < buf->n_metas; j++) {
+ struct spa_meta *m = &buf->metas[j];
+
+ if (!spa_pod_iter_get(&it,
+ SPA_POD_TYPE_ID, &m->type,
+ SPA_POD_TYPE_INT, &m->size, 0))
+ return false;
+ }
+ if (!spa_pod_iter_get(&it, SPA_POD_TYPE_INT, &buf->n_datas, 0))
+ return false;
+
+ buf->datas = alloca(sizeof(struct spa_data) * buf->n_datas);
+ for (j = 0; j < buf->n_datas; j++) {
+ struct spa_data *d = &buf->datas[j];
+
+ if (!spa_pod_iter_get(&it,
+ SPA_POD_TYPE_ID, &d->type,
+ SPA_POD_TYPE_INT, &data_id,
+ SPA_POD_TYPE_INT, &d->flags,
+ SPA_POD_TYPE_INT, &d->mapoffset,
+ SPA_POD_TYPE_INT, &d->maxsize, 0))
+ return false;
+
+ d->data = SPA_UINT32_TO_PTR(data_id);
+ }
+ }
+ ((struct pw_client_node_events *) proxy->implementation)->use_buffers(proxy,
+ seq,
+ direction,
+ port_id,
+ n_buffers, buffers);
+ return true;
+}
+
+static bool client_node_demarshal_node_command(void *object, void *data, size_t size)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_iter it;
+ const struct spa_command *command;
+ uint32_t seq;
+
+ if (!spa_pod_iter_struct(&it, data, size) ||
+ !pw_pod_remap_data(SPA_POD_TYPE_STRUCT, data, size, &proxy->remote->types) ||
+ !spa_pod_iter_get(&it, SPA_POD_TYPE_INT, &seq, SPA_POD_TYPE_OBJECT, &command, 0))
+ return false;
+
+ ((struct pw_client_node_events *) proxy->implementation)->node_command(proxy, seq, command);
+ return true;
+}
+
+static bool client_node_demarshal_port_command(void *object, void *data, size_t size)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_iter it;
+ const struct spa_command *command;
+ uint32_t direction, port_id;
+
+ if (!spa_pod_iter_struct(&it, data, size) ||
+ !pw_pod_remap_data(SPA_POD_TYPE_STRUCT, data, size, &proxy->remote->types) ||
+ !spa_pod_iter_get(&it,
+ SPA_POD_TYPE_INT, &direction,
+ SPA_POD_TYPE_INT, &port_id,
+ SPA_POD_TYPE_OBJECT, &command, 0))
+ return false;
+
+ ((struct pw_client_node_events *) proxy->implementation)->port_command(proxy,
+ direction,
+ port_id,
+ command);
+ return true;
+}
+
+static bool client_node_demarshal_transport(void *object, void *data, size_t size)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_iter it;
+ struct pw_connection *connection = proxy->remote->protocol_private;
+ uint32_t node_id, ridx, widx, memfd_idx, offset, sz;
+ int readfd, writefd, memfd;
+
+ if (!spa_pod_iter_struct(&it, data, size) ||
+ !spa_pod_iter_get(&it,
+ SPA_POD_TYPE_INT, &node_id,
+ SPA_POD_TYPE_INT, &ridx,
+ SPA_POD_TYPE_INT, &widx,
+ SPA_POD_TYPE_INT, &memfd_idx,
+ SPA_POD_TYPE_INT, &offset,
+ SPA_POD_TYPE_INT, &sz, 0))
+ return false;
+
+ readfd = pw_connection_get_fd(connection, ridx);
+ writefd = pw_connection_get_fd(connection, widx);
+ memfd = pw_connection_get_fd(connection, memfd_idx);
+ if (readfd == -1 || writefd == -1 || memfd_idx == -1)
+ return false;
+
+ ((struct pw_client_node_events *) proxy->implementation)->transport(proxy, node_id,
+ readfd, writefd,
+ memfd, offset, sz);
+ return true;
+}
+
+static void
+client_node_marshal_set_props(void *object, uint32_t seq, const struct spa_props *props)
+{
+ struct pw_resource *resource = object;
+ struct pw_connection *connection = resource->client->protocol_private;
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f;
+
+ b = pw_connection_begin_write_resource(connection, resource, PW_CLIENT_NODE_EVENT_SET_PROPS);
+
+ spa_pod_builder_struct(b, &f,
+ SPA_POD_TYPE_INT, seq,
+ SPA_POD_TYPE_POD, props);
+
+ pw_connection_end_write(connection, b);
+}
+
+static void client_node_marshal_event_event(void *object, const struct spa_event *event)
+{
+ struct pw_resource *resource = object;
+ struct pw_connection *connection = resource->client->protocol_private;
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f;
+
+ b = pw_connection_begin_write_resource(connection, resource, PW_CLIENT_NODE_EVENT_EVENT);
+
+ spa_pod_builder_struct(b, &f, SPA_POD_TYPE_POD, event);
+
+ pw_connection_end_write(connection, b);
+}
+
+static void
+client_node_marshal_add_port(void *object,
+ uint32_t seq, enum spa_direction direction, uint32_t port_id)
+{
+ struct pw_resource *resource = object;
+ struct pw_connection *connection = resource->client->protocol_private;
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f;
+
+ b = pw_connection_begin_write_resource(connection, resource, PW_CLIENT_NODE_EVENT_ADD_PORT);
+
+ spa_pod_builder_struct(b, &f,
+ SPA_POD_TYPE_INT, seq,
+ SPA_POD_TYPE_INT, direction, SPA_POD_TYPE_INT, port_id);
+
+ pw_connection_end_write(connection, b);
+}
+
+static void
+client_node_marshal_remove_port(void *object,
+ uint32_t seq, enum spa_direction direction, uint32_t port_id)
+{
+ struct pw_resource *resource = object;
+ struct pw_connection *connection = resource->client->protocol_private;
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f;
+
+ b = pw_connection_begin_write_resource(connection, resource, PW_CLIENT_NODE_EVENT_REMOVE_PORT);
+
+ spa_pod_builder_struct(b, &f,
+ SPA_POD_TYPE_INT, seq,
+ SPA_POD_TYPE_INT, direction, SPA_POD_TYPE_INT, port_id);
+
+ pw_connection_end_write(connection, b);
+}
+
+static void
+client_node_marshal_set_format(void *object,
+ uint32_t seq,
+ enum spa_direction direction,
+ uint32_t port_id,
+ uint32_t flags,
+ const struct spa_format *format)
+{
+ struct pw_resource *resource = object;
+ struct pw_connection *connection = resource->client->protocol_private;
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f;
+
+ b = pw_connection_begin_write_resource(connection, resource, PW_CLIENT_NODE_EVENT_SET_FORMAT);
+
+ spa_pod_builder_struct(b, &f,
+ SPA_POD_TYPE_INT, seq,
+ SPA_POD_TYPE_INT, direction,
+ SPA_POD_TYPE_INT, port_id,
+ SPA_POD_TYPE_INT, flags,
+ SPA_POD_TYPE_POD, format);
+
+ pw_connection_end_write(connection, b);
+}
+
+static void
+client_node_marshal_set_param(void *object,
+ uint32_t seq,
+ enum spa_direction direction,
+ uint32_t port_id,
+ const struct spa_param *param)
+{
+ struct pw_resource *resource = object;
+ struct pw_connection *connection = resource->client->protocol_private;
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f;
+
+ b = pw_connection_begin_write_resource(connection, resource, PW_CLIENT_NODE_EVENT_SET_PARAM);
+
+ spa_pod_builder_struct(b, &f,
+ SPA_POD_TYPE_INT, seq,
+ SPA_POD_TYPE_INT, direction,
+ SPA_POD_TYPE_INT, port_id,
+ SPA_POD_TYPE_POD, param);
+
+ pw_connection_end_write(connection, b);
+}
+
+static void
+client_node_marshal_add_mem(void *object,
+ enum spa_direction direction,
+ uint32_t port_id,
+ uint32_t mem_id,
+ uint32_t type,
+ int memfd, uint32_t flags, uint32_t offset, uint32_t size)
+{
+ struct pw_resource *resource = object;
+ struct pw_connection *connection = resource->client->protocol_private;
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f;
+
+ b = pw_connection_begin_write_resource(connection, resource, PW_CLIENT_NODE_EVENT_ADD_MEM);
+
+ spa_pod_builder_struct(b, &f,
+ SPA_POD_TYPE_INT, direction,
+ SPA_POD_TYPE_INT, port_id,
+ SPA_POD_TYPE_INT, mem_id,
+ SPA_POD_TYPE_ID, type,
+ SPA_POD_TYPE_INT, pw_connection_add_fd(connection, memfd),
+ SPA_POD_TYPE_INT, flags,
+ SPA_POD_TYPE_INT, offset, SPA_POD_TYPE_INT, size);
+
+ pw_connection_end_write(connection, b);
+}
+
+static void
+client_node_marshal_use_buffers(void *object,
+ uint32_t seq,
+ enum spa_direction direction,
+ uint32_t port_id,
+ uint32_t n_buffers, struct pw_client_node_buffer *buffers)
+{
+ struct pw_resource *resource = object;
+ struct pw_connection *connection = resource->client->protocol_private;
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f;
+ uint32_t i, j;
+
+ b = pw_connection_begin_write_resource(connection, resource, PW_CLIENT_NODE_EVENT_USE_BUFFERS);
+
+ spa_pod_builder_add(b,
+ SPA_POD_TYPE_STRUCT, &f,
+ SPA_POD_TYPE_INT, seq,
+ SPA_POD_TYPE_INT, direction,
+ SPA_POD_TYPE_INT, port_id, SPA_POD_TYPE_INT, n_buffers, 0);
+
+ for (i = 0; i < n_buffers; i++) {
+ struct spa_buffer *buf = buffers[i].buffer;
+
+ spa_pod_builder_add(b,
+ SPA_POD_TYPE_INT, buffers[i].mem_id,
+ SPA_POD_TYPE_INT, buffers[i].offset,
+ SPA_POD_TYPE_INT, buffers[i].size,
+ SPA_POD_TYPE_INT, buf->id, SPA_POD_TYPE_INT, buf->n_metas, 0);
+
+ for (j = 0; j < buf->n_metas; j++) {
+ struct spa_meta *m = &buf->metas[j];
+ spa_pod_builder_add(b,
+ SPA_POD_TYPE_ID, m->type, SPA_POD_TYPE_INT, m->size, 0);
+ }
+ spa_pod_builder_add(b, SPA_POD_TYPE_INT, buf->n_datas, 0);
+ for (j = 0; j < buf->n_datas; j++) {
+ struct spa_data *d = &buf->datas[j];
+ spa_pod_builder_add(b,
+ SPA_POD_TYPE_ID, d->type,
+ SPA_POD_TYPE_INT, SPA_PTR_TO_UINT32(d->data),
+ SPA_POD_TYPE_INT, d->flags,
+ SPA_POD_TYPE_INT, d->mapoffset,
+ SPA_POD_TYPE_INT, d->maxsize, 0);
+ }
+ }
+ spa_pod_builder_add(b, -SPA_POD_TYPE_STRUCT, &f, 0);
+
+ pw_connection_end_write(connection, b);
+}
+
+static void
+client_node_marshal_node_command(void *object, uint32_t seq, const struct spa_command *command)
+{
+ struct pw_resource *resource = object;
+ struct pw_connection *connection = resource->client->protocol_private;
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f;
+
+ b = pw_connection_begin_write_resource(connection, resource, PW_CLIENT_NODE_EVENT_NODE_COMMAND);
+
+ spa_pod_builder_struct(b, &f, SPA_POD_TYPE_INT, seq, SPA_POD_TYPE_POD, command);
+
+ pw_connection_end_write(connection, b);
+}
+
+static void
+client_node_marshal_port_command(void *object,
+ uint32_t direction,
+ uint32_t port_id,
+ const struct spa_command *command)
+{
+ struct pw_resource *resource = object;
+ struct pw_connection *connection = resource->client->protocol_private;
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f;
+
+ b = pw_connection_begin_write_resource(connection, resource, PW_CLIENT_NODE_EVENT_PORT_COMMAND);
+
+ spa_pod_builder_struct(b, &f,
+ SPA_POD_TYPE_INT, direction,
+ SPA_POD_TYPE_INT, port_id,
+ SPA_POD_TYPE_POD, command);
+
+ pw_connection_end_write(connection, b);
+}
+
+static void client_node_marshal_transport(void *object, uint32_t node_id, int readfd, int writefd,
+ int memfd, uint32_t offset, uint32_t size)
+{
+ struct pw_resource *resource = object;
+ struct pw_connection *connection = resource->client->protocol_private;
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f;
+
+ b = pw_connection_begin_write_resource(connection, resource, PW_CLIENT_NODE_EVENT_TRANSPORT);
+
+ spa_pod_builder_struct(b, &f,
+ SPA_POD_TYPE_INT, node_id,
+ SPA_POD_TYPE_INT, pw_connection_add_fd(connection, readfd),
+ SPA_POD_TYPE_INT, pw_connection_add_fd(connection, writefd),
+ SPA_POD_TYPE_INT, pw_connection_add_fd(connection, memfd),
+ SPA_POD_TYPE_INT, offset, SPA_POD_TYPE_INT, size);
+
+ pw_connection_end_write(connection, b);
+}
+
+
+static bool client_node_demarshal_done(void *object, void *data, size_t size)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_iter it;
+ uint32_t seq, res;
+
+ if (!spa_pod_iter_struct(&it, data, size) ||
+ !spa_pod_iter_get(&it,
+ SPA_POD_TYPE_INT, &seq,
+ SPA_POD_TYPE_INT, &res, 0))
+ return false;
+
+ ((struct pw_client_node_methods *) resource->implementation)->done(resource, seq, res);
+ return true;
+}
+
+static bool client_node_demarshal_update(void *object, void *data, size_t size)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_iter it;
+ uint32_t change_mask, max_input_ports, max_output_ports;
+ const struct spa_props *props;
+
+ if (!spa_pod_iter_struct(&it, data, size) ||
+ !pw_pod_remap_data(SPA_POD_TYPE_STRUCT, data, size, &resource->client->types) ||
+ !spa_pod_iter_get(&it,
+ SPA_POD_TYPE_INT, &change_mask,
+ SPA_POD_TYPE_INT, &max_input_ports,
+ SPA_POD_TYPE_INT, &max_output_ports, -SPA_POD_TYPE_OBJECT, &props, 0))
+ return false;
+
+ ((struct pw_client_node_methods *) resource->implementation)->update(resource, change_mask,
+ max_input_ports,
+ max_output_ports,
+ props);
+ return true;
+}
+
+static bool client_node_demarshal_port_update(void *object, void *data, size_t size)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_iter it;
+ uint32_t i, direction, port_id, change_mask, n_possible_formats, n_params;
+ const struct spa_param **params = NULL;
+ const struct spa_format **possible_formats = NULL, *format = NULL;
+ struct spa_port_info info, *infop = NULL;
+ struct spa_pod *ipod;
+
+ if (!spa_pod_iter_struct(&it, data, size) ||
+ !pw_pod_remap_data(SPA_POD_TYPE_STRUCT, data, size, &resource->client->types) ||
+ !spa_pod_iter_get(&it,
+ SPA_POD_TYPE_INT, &direction,
+ SPA_POD_TYPE_INT, &port_id,
+ SPA_POD_TYPE_INT, &change_mask,
+ SPA_POD_TYPE_INT, &n_possible_formats, 0))
+ return false;
+
+ possible_formats = alloca(n_possible_formats * sizeof(struct spa_format *));
+ for (i = 0; i < n_possible_formats; i++)
+ if (!spa_pod_iter_get(&it, SPA_POD_TYPE_OBJECT, &possible_formats[i], 0))
+ return false;
+
+ if (!spa_pod_iter_get(&it, -SPA_POD_TYPE_OBJECT, &format, SPA_POD_TYPE_INT, &n_params, 0))
+ return false;
+
+ params = alloca(n_params * sizeof(struct spa_param *));
+ for (i = 0; i < n_params; i++)
+ if (!spa_pod_iter_get(&it, SPA_POD_TYPE_OBJECT, &params[i], 0))
+ return false;
+
+ if (!spa_pod_iter_get(&it, -SPA_POD_TYPE_STRUCT, &ipod, 0))
+ return false;
+
+ if (ipod) {
+ struct spa_pod_iter it2;
+ infop = &info;
+
+ if (!spa_pod_iter_pod(&it2, ipod) ||
+ !spa_pod_iter_get(&it2,
+ SPA_POD_TYPE_INT, &info.flags,
+ SPA_POD_TYPE_INT, &info.rate, 0))
+ return false;
+ }
+
+ ((struct pw_client_node_methods *) resource->implementation)->port_update(resource,
+ direction,
+ port_id,
+ change_mask,
+ n_possible_formats,
+ possible_formats,
+ format,
+ n_params,
+ params, infop);
+ return true;
+}
+
+static bool client_node_demarshal_event_method(void *object, void *data, size_t size)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_iter it;
+ struct spa_event *event;
+
+ if (!spa_pod_iter_struct(&it, data, size) ||
+ !pw_pod_remap_data(SPA_POD_TYPE_STRUCT, data, size, &resource->client->types) ||
+ !spa_pod_iter_get(&it, SPA_POD_TYPE_OBJECT, &event, 0))
+ return false;
+
+ ((struct pw_client_node_methods *) resource->implementation)->event(resource, event);
+ return true;
+}
+
+static bool client_node_demarshal_destroy(void *object, void *data, size_t size)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_iter it;
+
+ if (!spa_pod_iter_struct(&it, data, size))
+ return false;
+
+ ((struct pw_client_node_methods *) resource->implementation)->destroy(resource);
+ return true;
+}
+
+static const struct pw_client_node_methods pw_protocol_native_client_client_node_methods = {
+ &client_node_marshal_done,
+ &client_node_marshal_update,
+ &client_node_marshal_port_update,
+ &client_node_marshal_event_method,
+ &client_node_marshal_destroy
+};
+
+static const demarshal_func_t pw_protocol_native_client_client_node_demarshal[] = {
+ &client_node_demarshal_transport,
+ &client_node_demarshal_set_props,
+ &client_node_demarshal_event_event,
+ &client_node_demarshal_add_port,
+ &client_node_demarshal_remove_port,
+ &client_node_demarshal_set_format,
+ &client_node_demarshal_set_param,
+ &client_node_demarshal_add_mem,
+ &client_node_demarshal_use_buffers,
+ &client_node_demarshal_node_command,
+ &client_node_demarshal_port_command,
+};
+
+static const struct pw_interface pw_protocol_native_client_client_node_interface = {
+ PIPEWIRE_TYPE_NODE_BASE "Client",
+ PW_VERSION_CLIENT_NODE,
+ PW_CLIENT_NODE_METHOD_NUM, &pw_protocol_native_client_client_node_methods,
+ PW_CLIENT_NODE_EVENT_NUM, pw_protocol_native_client_client_node_demarshal,
+};
+
+static const demarshal_func_t pw_protocol_native_server_client_node_demarshal[] = {
+ &client_node_demarshal_done,
+ &client_node_demarshal_update,
+ &client_node_demarshal_port_update,
+ &client_node_demarshal_event_method,
+ &client_node_demarshal_destroy,
+};
+
+static const struct pw_client_node_events pw_protocol_native_server_client_node_events = {
+ &client_node_marshal_transport,
+ &client_node_marshal_set_props,
+ &client_node_marshal_event_event,
+ &client_node_marshal_add_port,
+ &client_node_marshal_remove_port,
+ &client_node_marshal_set_format,
+ &client_node_marshal_set_param,
+ &client_node_marshal_add_mem,
+ &client_node_marshal_use_buffers,
+ &client_node_marshal_node_command,
+ &client_node_marshal_port_command,
+};
+
+const struct pw_interface pw_protocol_native_server_client_node_interface = {
+ PIPEWIRE_TYPE_NODE_BASE "Client",
+ PW_VERSION_CLIENT_NODE,
+ PW_CLIENT_NODE_METHOD_NUM, &pw_protocol_native_server_client_node_demarshal,
+ PW_CLIENT_NODE_EVENT_NUM, &pw_protocol_native_server_client_node_events,
+};
+
+struct pw_protocol *pw_protocol_native_ext_client_node_init(void)
+{
+ static bool init = false;
+ struct pw_protocol *protocol;
+
+ protocol = pw_protocol_get(PW_TYPE_PROTOCOL__Native);
+
+ if (init)
+ return protocol;
+
+ pw_protocol_add_interfaces(protocol,
+ &pw_protocol_native_client_client_node_interface,
+ &pw_protocol_native_server_client_node_interface);
+
+ init = true;
+
+ return protocol;
+}
diff --git a/src/modules/module-flatpak.c b/src/modules/module-flatpak.c
new file mode 100644
index 00000000..86d7f7de
--- /dev/null
+++ b/src/modules/module-flatpak.c
@@ -0,0 +1,712 @@
+/* PipeWire
+ * Copyright (C) 2016 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "config.h"
+
+#include <dbus/dbus.h>
+
+#include "pipewire/interfaces.h"
+#include "pipewire/utils.h"
+
+#include "pipewire/core.h"
+#include "pipewire/module.h"
+
+struct impl {
+ struct pw_core *core;
+ struct pw_properties *properties;
+
+ DBusConnection *bus;
+
+ struct pw_listener global_added;
+ struct pw_listener global_removed;
+
+ struct spa_list client_list;
+
+ struct spa_source *dispatch_event;
+};
+
+struct client_info {
+ struct impl *impl;
+ struct spa_list link;
+ struct pw_client *client;
+ bool is_sandboxed;
+ const struct pw_core_methods *old_methods;
+ struct pw_core_methods core_methods;
+ struct spa_list async_pending;
+ struct pw_listener resource_impl;
+ struct pw_listener resource_removed;
+};
+
+struct async_pending {
+ struct spa_list link;
+ struct client_info *info;
+ bool handled;
+ char *handle;
+ struct pw_resource *resource;
+ char *factory_name;
+ char *name;
+ struct pw_properties *properties;
+ uint32_t new_id;
+};
+
+static struct client_info *find_client_info(struct impl *impl, struct pw_client *client)
+{
+ struct client_info *info;
+
+ spa_list_for_each(info, &impl->client_list, link) {
+ if (info->client == client)
+ return info;
+ }
+ return NULL;
+}
+
+static void close_request(struct async_pending *p)
+{
+ DBusMessage *m = NULL;
+ struct impl *impl = p->info->impl;
+
+ pw_log_debug("pending %p: handle %s", p, p->handle);
+
+ if (!(m = dbus_message_new_method_call("org.freedesktop.portal.Request",
+ p->handle,
+ "org.freedesktop.portal.Request", "Close"))) {
+ pw_log_error("Failed to create message");
+ return;
+ }
+
+ if (!dbus_connection_send(impl->bus, m, NULL))
+ pw_log_error("Failed to send message");
+
+ dbus_message_unref(m);
+}
+
+static struct async_pending *find_pending(struct client_info *cinfo, const char *handle)
+{
+ struct async_pending *p;
+
+ spa_list_for_each(p, &cinfo->async_pending, link) {
+ if (strcmp(p->handle, handle) == 0)
+ return p;
+ }
+ return NULL;
+}
+
+static void free_pending(struct async_pending *p)
+{
+ if (!p->handled)
+ close_request(p);
+
+ pw_log_debug("pending %p: handle %s", p, p->handle);
+ spa_list_remove(&p->link);
+ free(p->handle);
+ free(p->factory_name);
+ free(p->name);
+ if (p->properties)
+ pw_properties_free(p->properties);
+ free(p);
+}
+
+static void client_info_free(struct client_info *cinfo)
+{
+ struct async_pending *p, *tmp;
+
+ spa_list_for_each_safe(p, tmp, &cinfo->async_pending, link)
+ free_pending(p);
+
+ spa_list_remove(&cinfo->link);
+ free(cinfo);
+}
+
+static bool client_is_sandboxed(struct pw_client *cl)
+{
+ char data[2048], *ptr;
+ size_t n, size;
+ const char *state = NULL;
+ const char *current;
+ bool result;
+ int fd;
+ pid_t pid;
+
+ if (cl->ucred_valid) {
+ pw_log_info("client has trusted pid %d", cl->ucred.pid);
+ } else {
+ pw_log_info("no trusted pid found, assuming not sandboxed\n");
+ return false;
+ }
+
+ pid = cl->ucred.pid;
+
+ sprintf(data, "/proc/%u/cgroup", pid);
+ fd = open(data, O_RDONLY | O_CLOEXEC, 0);
+ if (fd == -1)
+ return false;
+
+ spa_zero(data);
+ size = sizeof(data);
+ ptr = data;
+
+ while (size > 0) {
+ int r;
+
+ if ((r = read(fd, data, size)) < 0) {
+ if (errno == EINTR)
+ continue;
+ else
+ break;
+ }
+ if (r == 0)
+ break;
+
+ ptr += r;
+ size -= r;
+ }
+ close(fd);
+
+ result = false;
+ while ((current = pw_split_walk(data, "\n", &n, &state)) != NULL) {
+ if (strncmp(current, "1:name=systemd:", strlen("1:name=systemd:")) == 0) {
+ const char *p = strstr(current, "flatpak-");
+ if (p && p - current < n) {
+ pw_log_info("found a flatpak cgroup, assuming sandboxed\n");
+ result = true;
+ break;
+ }
+ }
+ }
+ return result;
+}
+
+static bool
+check_global_owner(struct pw_core *core, struct pw_client *client, struct pw_global *global)
+{
+ if (global == NULL)
+ return false;
+
+ if (global->owner == NULL)
+ return true;
+
+ if (global->owner->client->ucred.uid == client->ucred.uid)
+ return true;
+
+ return false;
+}
+
+static bool
+do_global_filter(struct pw_global *global, struct pw_client *client, void *data)
+{
+ if (global->type == client->core->type.link) {
+ struct pw_link *link = global->object;
+
+ /* we must be able to see both nodes */
+ if (link->output
+ && !check_global_owner(client->core, client, link->output->node->global))
+ return false;
+
+ if (link->input
+ && !check_global_owner(client->core, client, link->input->node->global))
+ return false;
+ } else if (!check_global_owner(client->core, client, global))
+ return false;
+
+ return true;
+}
+
+static DBusHandlerResult
+portal_response(DBusConnection *connection, DBusMessage *msg, void *user_data)
+{
+ struct client_info *cinfo = user_data;
+
+ if (dbus_message_is_signal(msg, "org.freedesktop.portal.Request", "Response")) {
+ uint32_t response = 2;
+ DBusError error;
+ struct async_pending *p;
+
+ dbus_error_init(&error);
+
+ dbus_connection_remove_filter(connection, portal_response, cinfo);
+
+ if (!dbus_message_get_args
+ (msg, &error, DBUS_TYPE_UINT32, &response, DBUS_TYPE_INVALID)) {
+ pw_log_error("failed to parse Response: %s", error.message);
+ dbus_error_free(&error);
+ }
+
+ p = find_pending(cinfo, dbus_message_get_path(msg));
+ if (p == NULL)
+ return DBUS_HANDLER_RESULT_HANDLED;
+
+ p->handled = true;
+
+ pw_log_debug("portal check result: %d", response);
+
+ if (response == 0) {
+ cinfo->old_methods->create_node (p->resource,
+ p->factory_name,
+ p->name,
+ &p->properties->dict,
+ p->new_id);
+ } else {
+ pw_core_notify_error(cinfo->client->core_resource,
+ p->resource->id, SPA_RESULT_NO_PERMISSION, "not allowed");
+
+ }
+ free_pending(p);
+ pw_client_set_busy(cinfo->client, false);
+
+ return DBUS_HANDLER_RESULT_HANDLED;
+ }
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+
+static void do_create_node(void *object,
+ const char *factory_name,
+ const char *name,
+ const struct spa_dict *props,
+ uint32_t new_id)
+{
+ struct pw_resource *resource = object;
+ struct client_info *cinfo = resource->access_private;
+ struct impl *impl = cinfo->impl;
+ struct pw_client *client = resource->client;
+ DBusMessage *m = NULL, *r = NULL;
+ DBusError error;
+ pid_t pid;
+ DBusMessageIter msg_iter;
+ DBusMessageIter dict_iter;
+ const char *handle;
+ const char *device;
+ struct async_pending *p;
+
+ if (!cinfo->is_sandboxed) {
+ cinfo->old_methods->create_node (object, factory_name, name, props, new_id);
+ return;
+ }
+ if (strcmp(factory_name, "client-node") != 0) {
+ pw_log_error("can only allow client-node");
+ goto not_allowed;
+ }
+
+ pw_log_info("ask portal for client %p", cinfo->client);
+
+ dbus_error_init(&error);
+
+ if (!(m = dbus_message_new_method_call("org.freedesktop.portal.Desktop",
+ "/org/freedesktop/portal/desktop",
+ "org.freedesktop.portal.Device", "AccessDevice")))
+ goto no_method_call;
+
+ device = "camera";
+
+ pid = cinfo->client->ucred.pid;
+ if (!dbus_message_append_args(m, DBUS_TYPE_UINT32, &pid, DBUS_TYPE_INVALID))
+ goto message_failed;
+
+ dbus_message_iter_init_append(m, &msg_iter);
+ dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "s", &dict_iter);
+ dbus_message_iter_append_basic(&dict_iter, DBUS_TYPE_STRING, &device);
+ dbus_message_iter_close_container(&msg_iter, &dict_iter);
+
+ dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter);
+ dbus_message_iter_close_container(&msg_iter, &dict_iter);
+
+ if (!(r = dbus_connection_send_with_reply_and_block(impl->bus, m, -1, &error)))
+ goto send_failed;
+
+ dbus_message_unref(m);
+
+ if (!dbus_message_get_args(r, &error, DBUS_TYPE_OBJECT_PATH, &handle, DBUS_TYPE_INVALID))
+ goto parse_failed;
+
+ dbus_message_unref(r);
+
+ dbus_bus_add_match(impl->bus,
+ "type='signal',interface='org.freedesktop.portal.Request'", &error);
+ dbus_connection_flush(impl->bus);
+ if (dbus_error_is_set(&error))
+ goto subscribe_failed;
+
+ dbus_connection_add_filter(impl->bus, portal_response, cinfo, NULL);
+
+ p = calloc(1, sizeof(struct async_pending));
+ p->info = cinfo;
+ p->handle = strdup(handle);
+ p->handled = false;
+ p->resource = resource;
+ p->factory_name = strdup(factory_name);
+ p->name = strdup(name);
+ p->properties = props ? pw_properties_new_dict(props) : NULL;
+ p->new_id = new_id;
+ pw_client_set_busy(client, true);
+
+ pw_log_debug("pending %p: handle %s", p, handle);
+ spa_list_insert(cinfo->async_pending.prev, &p->link);
+
+ return;
+
+ no_method_call:
+ pw_log_error("Failed to create message");
+ goto not_allowed;
+ message_failed:
+ dbus_message_unref(m);
+ goto not_allowed;
+ send_failed:
+ pw_log_error("Failed to call portal: %s", error.message);
+ dbus_error_free(&error);
+ dbus_message_unref(m);
+ goto not_allowed;
+ parse_failed:
+ pw_log_error("Failed to parse AccessDevice result: %s", error.message);
+ dbus_error_free(&error);
+ dbus_message_unref(r);
+ goto not_allowed;
+ subscribe_failed:
+ pw_log_error("Failed to subscribe to Request signal: %s", error.message);
+ dbus_error_free(&error);
+ goto not_allowed;
+ not_allowed:
+ pw_core_notify_error(client->core_resource,
+ resource->id, SPA_RESULT_NO_PERMISSION, "not allowed");
+ return;
+}
+
+static void
+do_create_link(void *object,
+ uint32_t output_node_id,
+ uint32_t output_port_id,
+ uint32_t input_node_id,
+ uint32_t input_port_id,
+ const struct spa_format *filter,
+ const struct spa_dict *props,
+ uint32_t new_id)
+{
+ struct pw_resource *resource = object;
+ struct client_info *cinfo = resource->access_private;
+ struct pw_client *client = resource->client;
+
+ if (cinfo->is_sandboxed) {
+ pw_core_notify_error(client->core_resource,
+ resource->id, SPA_RESULT_NO_PERMISSION, "not allowed");
+ return;
+ }
+ cinfo->old_methods->create_link (object,
+ output_node_id,
+ output_port_id,
+ input_node_id,
+ input_port_id,
+ filter,
+ props,
+ new_id);
+}
+
+static void on_resource_impl(struct pw_listener *listener,
+ struct pw_client *client,
+ struct pw_resource *resource)
+{
+ struct client_info *cinfo = SPA_CONTAINER_OF(listener, struct client_info, resource_impl);
+ struct impl *impl = cinfo->impl;
+
+ if (resource->type == impl->core->type.core) {
+ cinfo->old_methods = resource->implementation;
+ cinfo->core_methods = *cinfo->old_methods;
+ resource->implementation = &cinfo->core_methods;
+ resource->access_private = cinfo;
+ cinfo->core_methods.create_node = do_create_node;
+ cinfo->core_methods.create_link = do_create_link;
+ }
+}
+
+static void
+on_global_added(struct pw_listener *listener, struct pw_core *core, struct pw_global *global)
+{
+ struct impl *impl = SPA_CONTAINER_OF(listener, struct impl, global_added);
+
+ if (global->type == impl->core->type.client) {
+ struct pw_client *client = global->object;
+ struct client_info *cinfo;
+
+ cinfo = calloc(1, sizeof(struct client_info));
+ cinfo->impl = impl;
+ cinfo->client = client;
+ cinfo->is_sandboxed = client_is_sandboxed(client);
+ spa_list_init(&cinfo->async_pending);
+
+ pw_signal_add(&client->resource_impl, &cinfo->resource_impl, on_resource_impl);
+
+ spa_list_insert(impl->client_list.prev, &cinfo->link);
+
+ pw_log_debug("module %p: client %p added", impl, client);
+ }
+}
+
+static void
+on_global_removed(struct pw_listener *listener, struct pw_core *core, struct pw_global *global)
+{
+ struct impl *impl = SPA_CONTAINER_OF(listener, struct impl, global_removed);
+
+ if (global->type == impl->core->type.client) {
+ struct pw_client *client = global->object;
+ struct client_info *cinfo;
+
+ if ((cinfo = find_client_info(impl, client)))
+ client_info_free(cinfo);
+
+ pw_log_debug("module %p: client %p removed", impl, client);
+ }
+}
+
+static void dispatch_cb(struct spa_loop_utils *utils, struct spa_source *source, void *userdata)
+{
+ struct impl *impl = userdata;
+
+ if (dbus_connection_dispatch(impl->bus) == DBUS_DISPATCH_COMPLETE)
+ pw_loop_enable_idle(impl->core->main_loop, source, false);
+}
+
+static void dispatch_status(DBusConnection *conn, DBusDispatchStatus status, void *userdata)
+{
+ struct impl *impl = userdata;
+
+ pw_loop_enable_idle(impl->core->main_loop,
+ impl->dispatch_event, status == DBUS_DISPATCH_COMPLETE ? false : true);
+}
+
+static inline enum spa_io dbus_to_io(DBusWatch *watch)
+{
+ enum spa_io mask;
+ unsigned int flags;
+
+ /* no watch flags for disabled watches */
+ if (!dbus_watch_get_enabled(watch))
+ return 0;
+
+ flags = dbus_watch_get_flags(watch);
+ mask = SPA_IO_HUP | SPA_IO_ERR;
+
+ if (flags & DBUS_WATCH_READABLE)
+ mask |= SPA_IO_IN;
+ if (flags & DBUS_WATCH_WRITABLE)
+ mask |= SPA_IO_OUT;
+
+ return mask;
+}
+
+static inline unsigned int io_to_dbus(enum spa_io mask)
+{
+ unsigned int flags = 0;
+
+ if (mask & SPA_IO_IN)
+ flags |= DBUS_WATCH_READABLE;
+ if (mask & SPA_IO_OUT)
+ flags |= DBUS_WATCH_WRITABLE;
+ if (mask & SPA_IO_HUP)
+ flags |= DBUS_WATCH_HANGUP;
+ if (mask & SPA_IO_ERR)
+ flags |= DBUS_WATCH_ERROR;
+ return flags;
+}
+
+static void
+handle_io_event(struct spa_loop_utils *utils,
+ struct spa_source *source, int fd, enum spa_io mask, void *userdata)
+{
+ DBusWatch *watch = userdata;
+
+ if (!dbus_watch_get_enabled(watch)) {
+ pw_log_warn("Asked to handle disabled watch: %p %i", (void *) watch, fd);
+ return;
+ }
+ dbus_watch_handle(watch, io_to_dbus(mask));
+}
+
+static dbus_bool_t add_watch(DBusWatch *watch, void *userdata)
+{
+ struct impl *impl = userdata;
+ struct spa_source *source;
+
+ pw_log_debug("add watch %p %d", watch, dbus_watch_get_unix_fd(watch));
+
+ /* we dup because dbus tends to add the same fd multiple times and our epoll
+ * implementation does not like that */
+ source = pw_loop_add_io(impl->core->main_loop,
+ dup(dbus_watch_get_unix_fd(watch)),
+ dbus_to_io(watch), true, handle_io_event, watch);
+
+ dbus_watch_set_data(watch, source, NULL);
+ return TRUE;
+}
+
+static void remove_watch(DBusWatch *watch, void *userdata)
+{
+ struct impl *impl = userdata;
+ struct spa_source *source;
+
+ if ((source = dbus_watch_get_data(watch)))
+ pw_loop_destroy_source(impl->core->main_loop, source);
+}
+
+static void toggle_watch(DBusWatch *watch, void *userdata)
+{
+ struct impl *impl = userdata;
+ struct spa_source *source;
+
+ source = dbus_watch_get_data(watch);
+
+ pw_loop_update_io(impl->core->main_loop, source, dbus_to_io(watch));
+}
+
+static void
+handle_timer_event(struct spa_loop_utils *utils, struct spa_source *source, void *userdata)
+{
+ DBusTimeout *timeout = userdata;
+ uint64_t t;
+ struct timespec ts;
+
+ if (dbus_timeout_get_enabled(timeout)) {
+ t = dbus_timeout_get_interval(timeout) * SPA_NSEC_PER_MSEC;
+ ts.tv_sec = t / SPA_NSEC_PER_SEC;
+ ts.tv_nsec = t % SPA_NSEC_PER_SEC;
+ spa_loop_utils_update_timer(utils, source, &ts, NULL, false);
+
+ dbus_timeout_handle(timeout);
+ }
+}
+
+static dbus_bool_t add_timeout(DBusTimeout *timeout, void *userdata)
+{
+ struct impl *impl = userdata;
+ struct spa_source *source;
+ struct timespec ts;
+ uint64_t t;
+
+ if (!dbus_timeout_get_enabled(timeout))
+ return FALSE;
+
+ source = pw_loop_add_timer(impl->core->main_loop, handle_timer_event, timeout);
+
+ dbus_timeout_set_data(timeout, source, NULL);
+
+ t = dbus_timeout_get_interval(timeout) * SPA_NSEC_PER_MSEC;
+ ts.tv_sec = t / SPA_NSEC_PER_SEC;
+ ts.tv_nsec = t % SPA_NSEC_PER_SEC;
+ pw_loop_update_timer(impl->core->main_loop, source, &ts, NULL, false);
+ return TRUE;
+}
+
+static void remove_timeout(DBusTimeout *timeout, void *userdata)
+{
+ struct impl *impl = userdata;
+ struct spa_source *source;
+
+ if ((source = dbus_timeout_get_data(timeout)))
+ pw_loop_destroy_source(impl->core->main_loop, source);
+}
+
+static void toggle_timeout(DBusTimeout *timeout, void *userdata)
+{
+ struct impl *impl = userdata;
+ struct spa_source *source;
+ struct timespec ts, *tsp;
+
+ source = dbus_timeout_get_data(timeout);
+
+ if (dbus_timeout_get_enabled(timeout)) {
+ uint64_t t = dbus_timeout_get_interval(timeout) * SPA_NSEC_PER_MSEC;
+ ts.tv_sec = t / SPA_NSEC_PER_SEC;
+ ts.tv_nsec = t % SPA_NSEC_PER_SEC;
+ tsp = &ts;
+ } else {
+ tsp = NULL;
+ }
+ pw_loop_update_timer(impl->core->main_loop, source, tsp, NULL, false);
+}
+
+static void wakeup_main(void *userdata)
+{
+ struct impl *impl = userdata;
+
+ pw_loop_enable_idle(impl->core->main_loop, impl->dispatch_event, true);
+}
+
+static struct impl *module_new(struct pw_core *core, struct pw_properties *properties)
+{
+ struct impl *impl;
+ DBusError error;
+
+ dbus_error_init(&error);
+
+ impl = calloc(1, sizeof(struct impl));
+ pw_log_debug("module %p: new", impl);
+
+ impl->core = core;
+ impl->properties = properties;
+
+ impl->bus = dbus_bus_get_private(DBUS_BUS_SESSION, &error);
+ if (impl->bus == NULL)
+ goto error;
+
+ impl->dispatch_event = pw_loop_add_idle(core->main_loop, false, dispatch_cb, impl);
+
+ dbus_connection_set_exit_on_disconnect(impl->bus, false);
+ dbus_connection_set_dispatch_status_function(impl->bus, dispatch_status, impl, NULL);
+ dbus_connection_set_watch_functions(impl->bus, add_watch, remove_watch, toggle_watch, impl,
+ NULL);
+ dbus_connection_set_timeout_functions(impl->bus, add_timeout, remove_timeout,
+ toggle_timeout, impl, NULL);
+ dbus_connection_set_wakeup_main_function(impl->bus, wakeup_main, impl, NULL);
+
+ spa_list_init(&impl->client_list);
+
+ pw_signal_add(&core->global_added, &impl->global_added, on_global_added);
+ pw_signal_add(&core->global_removed, &impl->global_removed, on_global_removed);
+ core->global_filter = do_global_filter;
+ core->global_filter_data = impl;
+
+ return impl;
+
+ error:
+ pw_log_error("Failed to connect to system bus: %s", error.message);
+ dbus_error_free(&error);
+ return NULL;
+}
+
+#if 0
+static void module_destroy(struct impl *impl)
+{
+ pw_log_debug("module %p: destroy", impl);
+
+ dbus_connection_close(impl->bus);
+ dbus_connection_unref(impl->bus);
+ free(impl);
+}
+#endif
+
+bool pipewire__module_init(struct pw_module *module, const char *args)
+{
+ module_new(module->core, NULL);
+ return true;
+}
diff --git a/src/modules/module-jack.c b/src/modules/module-jack.c
new file mode 100644
index 00000000..49100beb
--- /dev/null
+++ b/src/modules/module-jack.c
@@ -0,0 +1,754 @@
+/* PipeWire
+ * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/file.h>
+#include <sys/mman.h>
+#include <semaphore.h>
+
+#include <jack/jack.h>
+#include <jack/session.h>
+
+#include "config.h"
+
+#include "pipewire/pipewire.h"
+#include "pipewire/log.h"
+#include "pipewire/interfaces.h"
+
+#include "pipewire/core.h"
+#include "pipewire/node.h"
+#include "pipewire/module.h"
+#include "pipewire/client.h"
+#include "pipewire/resource.h"
+#include "pipewire/link.h"
+#include "pipewire/node-factory.h"
+#include "pipewire/data-loop.h"
+#include "pipewire/main-loop.h"
+
+#include "modules/module-jack/defs.h"
+#include "modules/module-jack/shm.h"
+#include "modules/module-jack/shared.h"
+#include "modules/module-jack/synchro.h"
+#include "modules/module-jack/server.h"
+
+#ifndef UNIX_PATH_MAX
+#define UNIX_PATH_MAX 108
+#endif
+
+#define LOCK_SUFFIX ".lock"
+#define LOCK_SUFFIXLEN 5
+
+int segment_num = 0;
+
+typedef bool(*demarshal_func_t) (void *object, void *data, size_t size);
+
+struct socket {
+ int fd;
+ struct sockaddr_un addr;
+ char lock_addr[UNIX_PATH_MAX + LOCK_SUFFIXLEN];
+
+ struct pw_loop *loop;
+ struct spa_source *source;
+ char *core_name;
+ struct spa_list link;
+};
+
+struct impl {
+ struct pw_core *core;
+ struct spa_list link;
+
+ struct pw_properties *properties;
+
+ struct spa_list socket_list;
+ struct spa_list client_list;
+
+ struct spa_loop_control_hooks hooks;
+
+ struct jack_server server;
+};
+
+struct client {
+ struct impl *impl;
+ struct spa_list link;
+ struct pw_client *client;
+ int fd;
+ struct spa_source *source;
+ struct pw_listener busy_changed;
+};
+
+static int process_messages(struct client *client);
+
+static void client_destroy(void *data)
+{
+ struct pw_client *client = data;
+ struct client *this = client->user_data;
+
+ pw_loop_destroy_source(this->impl->core->main_loop, this->source);
+ spa_list_remove(&this->link);
+
+ close(this->fd);
+}
+
+static int
+handle_register_port(struct client *client)
+{
+ int result = 0;
+ int ref_num;
+ char name[JACK_PORT_NAME_SIZE + 1];
+ char port_type[JACK_PORT_TYPE_SIZE + 1];
+ unsigned int flags;
+ unsigned int buffer_size;
+ static jack_port_id_t port_index = 0;
+
+ CheckSize(kRegisterPort_size);
+ CheckRead(&ref_num, sizeof(int));
+ CheckRead(name, sizeof(name));
+ CheckRead(port_type, sizeof(port_type));
+ CheckRead(&flags, sizeof(unsigned int));
+ CheckRead(&buffer_size, sizeof(unsigned int));
+
+ pw_log_error("protocol-jack %p: kRegisterPort %d %s %s %u %u", client->impl,
+ ref_num, name, port_type, flags, buffer_size);
+ port_index++;
+
+ CheckWrite(&result, sizeof(int));
+ CheckWrite(&port_index, sizeof(jack_port_id_t));
+ return 0;
+}
+
+static int
+handle_activate_client(struct client *client)
+{
+ int result = 0;
+ int ref_num;
+ int is_real_time;
+
+ CheckSize(kActivateClient_size);
+ CheckRead(&ref_num, sizeof(int));
+ CheckRead(&is_real_time, sizeof(int));
+
+ pw_log_error("protocol-jack %p: kActivateClient %d %d", client->impl,
+ ref_num, is_real_time);
+
+ CheckWrite(&result, sizeof(int));
+ return 0;
+}
+
+static int
+handle_deactivate_client(struct client *client)
+{
+ int result = 0;
+ int ref_num;
+
+ CheckSize(kDeactivateClient_size);
+ CheckRead(&ref_num, sizeof(int));
+
+ pw_log_error("protocol-jack %p: kDeactivateClient %d", client->impl,
+ ref_num);
+
+ CheckWrite(&result, sizeof(int));
+ return 0;
+}
+
+static int
+handle_client_check(struct client *client)
+{
+ char name[JACK_CLIENT_NAME_SIZE+1];
+ int protocol;
+ int options;
+ int UUID;
+ int open;
+ int result = 0;
+ int status;
+
+ CheckSize(kClientCheck_size);
+ CheckRead(name, sizeof(name));
+ CheckRead(&protocol, sizeof(int));
+ CheckRead(&options, sizeof(int));
+ CheckRead(&UUID, sizeof(int));
+ CheckRead(&open, sizeof(int));
+
+ pw_log_error("protocol-jack %p: kClientCheck %s %d %d %d %d", client->impl,
+ name, protocol, options, UUID, open);
+
+ status = 0;
+ if (protocol != JACK_PROTOCOL_VERSION) {
+ status |= (JackFailure | JackVersionError);
+ pw_log_error("protocol-jack: protocol mismatch (%d vs %d)", protocol, JACK_PROTOCOL_VERSION);
+ result = -1;
+ goto reply;
+ }
+ /* TODO check client name and uuid */
+
+ reply:
+ CheckWrite(&result, sizeof(int));
+ CheckWrite(name, sizeof(name));
+ CheckWrite(&status, sizeof(int));
+
+ if (open)
+ return process_messages(client);
+
+ return 0;
+}
+
+static int
+handle_client_open(struct client *client)
+{
+ struct impl *impl = client->impl;
+ struct jack_server *server = &impl->server;
+ int PID, UUID;
+ char name[JACK_CLIENT_NAME_SIZE+1];
+ int result, ref_num, shared_engine, shared_client, shared_graph;
+ struct jack_client *jc;
+
+ CheckSize(kClientOpen_size);
+ CheckRead(&PID, sizeof(int));
+ CheckRead(&UUID, sizeof(int));
+ CheckRead(name, sizeof(name));
+
+ ref_num = jack_server_allocate_ref_num(server);
+ if (ref_num == -1) {
+ result = -1;
+ goto reply;
+ }
+
+ jc = calloc(1,sizeof(struct jack_client));
+ jc->owner = client;
+ jc->ref_num = ref_num;
+
+ if (jack_synchro_init(&server->synchro_table[ref_num],
+ name,
+ server->engine_control->server_name,
+ 0,
+ false,
+ server->promiscuous) < 0) {
+ result = -1;
+ goto reply;
+ }
+
+ jc->control = jack_client_control_alloc(name, client->client->ucred.pid, ref_num, -1);
+ if (jc->control == NULL) {
+ result = -1;
+ goto reply;
+ }
+
+ server->client_table[ref_num] = jc;
+
+ result = 0;
+ shared_engine = impl->server.engine_control->info.index;
+ shared_client = jc->control->info.index;
+ shared_graph = impl->server.graph_manager->info.index;
+
+ reply:
+ CheckWrite(&result, sizeof(int));
+ CheckWrite(&shared_engine, sizeof(int));
+ CheckWrite(&shared_client, sizeof(int));
+ CheckWrite(&shared_graph, sizeof(int));
+
+ return 0;
+}
+
+static int
+handle_client_close(struct client *client)
+{
+ int ref_num;
+ CheckSize(kClientClose_size);
+ CheckRead(&ref_num, sizeof(int));
+ int result = 0;
+
+ CheckWrite(&result, sizeof(int));
+ return 0;
+}
+
+static int
+handle_connect_name_ports(struct client *client)
+{
+ int ref_num;
+ char src[REAL_JACK_PORT_NAME_SIZE+1];
+ char dst[REAL_JACK_PORT_NAME_SIZE+1];
+ int result = 0;
+
+ CheckSize(kConnectNamePorts_size);
+ CheckRead(&ref_num, sizeof(int));
+ CheckRead(src, sizeof(src));
+ CheckRead(dst, sizeof(dst));
+
+ CheckWrite(&result, sizeof(int));
+ return 0;
+}
+
+static int
+handle_get_UUID_by_client(struct client *client)
+{
+ char name[JACK_CLIENT_NAME_SIZE+1];
+ char UUID[JACK_UUID_SIZE];
+ int result = 0;
+
+ CheckSize(kGetUUIDByClient_size);
+ CheckRead(name, sizeof(name));
+
+ CheckWrite(&result, sizeof(int));
+ CheckWrite(UUID, sizeof(UUID));
+
+ return 0;
+}
+
+static int
+process_messages(struct client *client)
+{
+ struct pw_client *c = client->client;
+ int type, res = -1;
+
+ if (read(client->fd, &type, sizeof(enum jack_request_type)) != sizeof(enum jack_request_type)) {
+ pw_log_error("protocol-jack %p: failed to read type", client->impl);
+ goto error;
+ }
+ pw_log_info("protocol-jack %p: got type %d", client->impl, type);
+
+ switch(type) {
+ case jack_request_RegisterPort:
+ res = handle_register_port(client);
+ break;
+ case jack_request_UnRegisterPort:
+ case jack_request_ConnectPorts:
+ case jack_request_DisconnectPorts:
+ case jack_request_SetTimeBaseClient:
+ case jack_request_ActivateClient:
+ res = handle_activate_client(client);
+ break;
+ case jack_request_DeactivateClient:
+ res = handle_deactivate_client(client);
+ break;
+ case jack_request_DisconnectPort:
+ case jack_request_SetClientCapabilities:
+ case jack_request_GetPortConnections:
+ case jack_request_GetPortNConnections:
+ case jack_request_ReleaseTimebase:
+ case jack_request_SetTimebaseCallback:
+ case jack_request_SetBufferSize:
+ case jack_request_SetFreeWheel:
+ break;
+ case jack_request_ClientCheck:
+ res = handle_client_check(client);
+ break;
+ case jack_request_ClientOpen:
+ res = handle_client_open(client);
+ break;
+ case jack_request_ClientClose:
+ res = handle_client_close(client);
+ break;
+ case jack_request_ConnectNamePorts:
+ res = handle_connect_name_ports(client);
+ break;
+ case jack_request_DisconnectNamePorts:
+ case jack_request_GetInternalClientName:
+ case jack_request_InternalClientHandle:
+ case jack_request_InternalClientLoad:
+ case jack_request_InternalClientUnload:
+ case jack_request_PortRename:
+ case jack_request_Notification:
+ case jack_request_SessionNotify:
+ case jack_request_SessionReply:
+ case jack_request_GetClientByUUID:
+ case jack_request_ReserveClientName:
+ break;
+ case jack_request_GetUUIDByClient:
+ res = handle_get_UUID_by_client(client);
+ break;
+ case jack_request_ClientHasSessionCallback:
+ case jack_request_ComputeTotalLatencies:
+ break;
+ default:
+ pw_log_error("protocol-jack %p: invalid type %d", client->impl, type);
+ goto error;
+ }
+ if (res != 0)
+ goto error;
+
+ return res;
+
+ error:
+ pw_log_error("protocol-jack %p: error handling type %d", client->impl, type);
+ pw_client_destroy(c);
+ return -1;
+
+}
+
+static void
+on_busy_changed(struct pw_listener *listener,
+ struct pw_client *client)
+{
+ struct client *c = SPA_CONTAINER_OF(listener, struct client, busy_changed);
+ enum spa_io mask = SPA_IO_ERR | SPA_IO_HUP;
+
+ if (!client->busy)
+ mask |= SPA_IO_IN;
+
+ pw_loop_update_io(c->impl->core->main_loop, c->source, mask);
+
+ if (!client->busy)
+ process_messages(c);
+}
+
+static void
+connection_data(struct spa_loop_utils *utils,
+ struct spa_source *source, int fd, enum spa_io mask, void *data)
+{
+ struct client *client = data;
+
+ if (mask & (SPA_IO_ERR | SPA_IO_HUP)) {
+ pw_log_error("protocol-native %p: got connection error", client->impl);
+ pw_client_destroy(client->client);
+ return;
+ }
+
+ if (mask & SPA_IO_IN)
+ process_messages(client);
+}
+
+static struct client *client_new(struct impl *impl, int fd)
+{
+ struct client *this;
+ struct pw_client *client;
+ socklen_t len;
+ struct ucred ucred, *ucredp;
+
+ len = sizeof(ucred);
+ if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &ucred, &len) < 0) {
+ pw_log_error("no peercred: %m");
+ ucredp = NULL;
+ } else {
+ ucredp = &ucred;
+ }
+
+ client = pw_client_new(impl->core, ucredp, NULL, sizeof(struct client));
+ if (client == NULL)
+ goto no_client;
+
+ client->destroy = client_destroy;
+
+ this = client->user_data;
+ this->impl = impl;
+ this->fd = fd;
+ this->source = pw_loop_add_io(impl->core->main_loop,
+ this->fd,
+ SPA_IO_ERR | SPA_IO_HUP, false, connection_data, this);
+ if (this->source == NULL)
+ goto no_source;
+
+ this->client = client;
+
+ spa_list_insert(impl->client_list.prev, &this->link);
+
+ pw_signal_add(&client->busy_changed, &this->busy_changed, on_busy_changed);
+
+ pw_log_error("module-jack %p: added new client", impl);
+
+ return this;
+
+ no_source:
+ free(this);
+ no_client:
+ return NULL;
+}
+
+static int
+make_int_client(struct impl *impl, struct pw_node *node)
+{
+ struct jack_server *server = &impl->server;
+ struct jack_connection_manager *conn;
+ int ref_num;
+ struct jack_client *jc;
+ jack_port_id_t port_id;
+
+ ref_num = jack_server_allocate_ref_num(server);
+ if (ref_num == -1)
+ return -1;
+
+ if (jack_synchro_init(&server->synchro_table[ref_num],
+ "system",
+ server->engine_control->server_name,
+ 0,
+ false,
+ server->promiscuous) < 0) {
+ return -1;
+ }
+
+ jc = calloc(1,sizeof(struct jack_client));
+ jc->ref_num = ref_num;
+ jc->node = node;
+ jc->control = jack_client_control_alloc("system", -1, ref_num, -1);
+ jc->control->active = true;
+
+ server->client_table[ref_num] = jc;
+
+ impl->server.engine_control->driver_num++;
+
+ conn = jack_graph_manager_next_start(server->graph_manager);
+
+ jack_connection_manager_init_ref_num(conn, ref_num);
+
+ port_id = jack_graph_manager_allocate_port(server->graph_manager,
+ ref_num, "system:playback_1", 2,
+ JackPortIsInput | JackPortIsPhysical | JackPortIsTerminal);
+
+ jack_connection_manager_add_port(conn, true, ref_num, port_id);
+
+ jack_graph_manager_next_stop(server->graph_manager);
+
+ return 0;
+}
+
+static bool init_nodes(struct impl *impl)
+{
+ struct pw_core *core = impl->core;
+ struct pw_node *n;
+
+ spa_list_for_each(n, &core->node_list, link) {
+ const char *str;
+
+ if (n->global == NULL)
+ continue;
+
+ if (n->properties == NULL)
+ continue;
+
+ if ((str = pw_properties_get(n->properties, "media.class")) == NULL)
+ continue;
+
+ if (strcmp(str, "Audio/Sink") != 0)
+ continue;
+
+ make_int_client(impl, n);
+ }
+ return true;
+}
+
+static struct socket *create_socket(void)
+{
+ struct socket *s;
+
+ if ((s = calloc(1, sizeof(struct socket))) == NULL)
+ return NULL;
+
+ s->fd = -1;
+ return s;
+}
+
+static void destroy_socket(struct socket *s)
+{
+ if (s->source)
+ pw_loop_destroy_source(s->loop, s->source);
+ if (s->addr.sun_path[0])
+ unlink(s->addr.sun_path);
+ if (s->fd >= 0)
+ close(s->fd);
+ if (s->lock_addr[0])
+ unlink(s->lock_addr);
+ free(s);
+}
+
+static bool init_socket_name(struct socket *s, const char *name, bool promiscuous, int which)
+{
+ int name_size;
+ const char *runtime_dir;
+
+ runtime_dir = JACK_SOCKET_DIR;
+
+ s->addr.sun_family = AF_UNIX;
+ if (promiscuous) {
+ name_size = snprintf(s->addr.sun_path, sizeof(s->addr.sun_path),
+ "%s/jack_%s_%d", runtime_dir, name, which) + 1;
+ } else {
+ name_size = snprintf(s->addr.sun_path, sizeof(s->addr.sun_path),
+ "%s/jack_%s_%d_%d", runtime_dir, name, getuid(), which) + 1;
+ }
+
+ s->core_name = (s->addr.sun_path + name_size - 1) - strlen(name);
+
+ if (name_size > (int) sizeof(s->addr.sun_path)) {
+ pw_log_error("socket path \"%s/%s\" plus null terminator exceeds 108 bytes",
+ runtime_dir, name);
+ *s->addr.sun_path = 0;
+ return false;
+ }
+ return true;
+}
+
+static void
+socket_data(struct spa_loop_utils *utils,
+ struct spa_source *source, int fd, enum spa_io mask, void *data)
+{
+ struct impl *impl = data;
+ struct client *client;
+ struct sockaddr_un name;
+ socklen_t length;
+ int client_fd;
+
+ length = sizeof(name);
+ client_fd = accept4(fd, (struct sockaddr *) &name, &length, SOCK_CLOEXEC);
+ if (client_fd < 0) {
+ pw_log_error("failed to accept: %m");
+ return;
+ }
+
+ client = client_new(impl, client_fd);
+ if (client == NULL) {
+ pw_log_error("failed to create client");
+ close(client_fd);
+ return;
+ }
+
+ pw_loop_update_io(impl->core->main_loop,
+ client->source, SPA_IO_IN | SPA_IO_ERR | SPA_IO_HUP);
+}
+
+static bool add_socket(struct impl *impl, struct socket *s)
+{
+ socklen_t size;
+
+ if ((s->fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)) < 0)
+ return false;
+
+ size = offsetof(struct sockaddr_un, sun_path) + strlen(s->addr.sun_path);
+ if (bind(s->fd, (struct sockaddr *) &s->addr, size) < 0) {
+ pw_log_error("bind() failed with error: %m");
+ return false;
+ }
+
+ if (listen(s->fd, 100) < 0) {
+ pw_log_error("listen() failed with error: %m");
+ return false;
+ }
+
+ s->loop = impl->core->main_loop;
+ s->source = pw_loop_add_io(s->loop, s->fd, SPA_IO_IN, false, socket_data, impl);
+ if (s->source == NULL)
+ return false;
+
+ spa_list_insert(impl->socket_list.prev, &s->link);
+
+ return true;
+}
+
+static int init_server(struct impl *impl, const char *name, bool promiscuous)
+{
+ struct jack_server *server = &impl->server;
+ int i;
+ struct socket *s;
+
+ pthread_mutex_init(&server->lock, NULL);
+
+ if (jack_register_server(name, 1) != 0)
+ return -1;
+
+ jack_cleanup_shm();
+
+ /* graph manager */
+ server->graph_manager = jack_graph_manager_alloc(2048);
+
+ /* engine control */
+ server->engine_control = jack_engine_control_alloc(name);
+
+ for (i = 0; i < CLIENT_NUM; i++)
+ server->synchro_table[i] = JACK_SYNCHRO_INIT;
+
+ if (!init_nodes(impl))
+ return -1;
+
+ s = create_socket();
+
+ if (!init_socket_name(s, name, promiscuous, 0))
+ goto error;
+
+ if (!add_socket(impl, s))
+ goto error;
+
+ return 0;
+
+ error:
+ destroy_socket(s);
+ return -1;
+}
+
+
+static struct impl *module_new(struct pw_core *core, struct pw_properties *properties)
+{
+ struct impl *impl;
+ const char *name, *str;
+ bool promiscuous;
+
+ impl = calloc(1, sizeof(struct impl));
+ pw_log_debug("protocol-jack %p: new", impl);
+
+ impl->core = core;
+ impl->properties = properties;
+
+ spa_list_init(&impl->socket_list);
+ spa_list_init(&impl->client_list);
+
+ str = NULL;
+ if (impl->properties)
+ str = pw_properties_get(impl->properties, "jack.default.server");
+ if (str == NULL)
+ str = getenv("JACK_DEFAULT_SERVER");
+
+ name = str ? str : JACK_DEFAULT_SERVER_NAME;
+
+ str = NULL;
+ if (impl->properties)
+ str = pw_properties_get(impl->properties, "jack.promiscuous.server");
+ if (str == NULL)
+ str = getenv("JACK_PROMISCUOUS_SERVER");
+
+ promiscuous = str ? atoi(str) != 0 : false;
+
+ if (init_server(impl, name, promiscuous) < 0)
+ goto error;
+
+ return impl;
+
+ error:
+ free(impl);
+ return NULL;
+}
+
+#if 0
+static void module_destroy(struct impl *impl)
+{
+ struct impl *object, *tmp;
+
+ pw_log_debug("module %p: destroy", impl);
+
+ free(impl);
+}
+#endif
+
+bool pipewire__module_init(struct pw_module *module, const char *args)
+{
+ module_new(module->core, NULL);
+ return true;
+}
diff --git a/src/modules/module-jack/defs.h b/src/modules/module-jack/defs.h
new file mode 100644
index 00000000..5ecb9724
--- /dev/null
+++ b/src/modules/module-jack/defs.h
@@ -0,0 +1,143 @@
+/* PipeWire
+ * Copyright (C) 2017 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include "pipewire/log.h"
+
+#define USE_POSIX_SHM
+#undef JACK_MONITOR
+
+#define JACK_DEFAULT_SERVER_NAME "default"
+#define JACK_SOCKET_DIR "/dev/shm"
+#define JACK_SHM_DIR "/dev/shm"
+#define JACK_SERVER_NAME_SIZE 256
+#define JACK_CLIENT_NAME_SIZE 64
+#define JACK_PORT_NAME_SIZE 256
+#define JACK_PORT_TYPE_SIZE 32
+#define JACK_PROTOCOL_VERSION 8
+
+#define PORT_NUM_MAX 4096
+#define PORT_NUM_FOR_CLIENT 2048
+#define CONNECTION_NUM_FOR_PORT PORT_NUM_FOR_CLIENT
+
+#define REAL_JACK_PORT_NAME_SIZE JACK_CLIENT_NAME_SIZE + JACK_PORT_NAME_SIZE
+
+#define BUFFER_SIZE_MAX 8192
+
+#define CLIENT_NUM 256
+
+#define JACK_ENGINE_ROLLING_COUNT 32
+#define JACK_ENGINE_ROLLING_INTERVAL 1024
+
+#define TIME_POINTS 100000
+#define FAILURE_TIME_POINTS 10000
+#define FAILURE_WINDOW 10
+#define MEASURED_CLIENTS 32
+
+#define SYNC_MAX_NAME_SIZE 256
+
+#define JACK_UUID_SIZE 36
+#define JACK_UUID_STRING_SIZE (JACK_UUID_SIZE+1)
+
+#define JACK_SESSION_COMMAND_SIZE 256
+
+#define NO_PORT 0xFFFE
+#define EMPTY 0xFFFD
+#define FREE 0xFFFC
+
+
+
+typedef enum {
+ JACK_TIMER_SYSTEM_CLOCK,
+ JACK_TIMER_HPET,
+} jack_timer_type_t;
+
+enum jack_request_type {
+ jack_request_RegisterPort = 1,
+ jack_request_UnRegisterPort = 2,
+ jack_request_ConnectPorts = 3,
+ jack_request_DisconnectPorts = 4,
+ jack_request_SetTimeBaseClient = 5,
+ jack_request_ActivateClient = 6,
+ jack_request_DeactivateClient = 7,
+ jack_request_DisconnectPort = 8,
+ jack_request_SetClientCapabilities = 9,
+ jack_request_GetPortConnections = 10,
+ jack_request_GetPortNConnections = 11,
+ jack_request_ReleaseTimebase = 12,
+ jack_request_SetTimebaseCallback = 13,
+ jack_request_SetBufferSize = 20,
+ jack_request_SetFreeWheel = 21,
+ jack_request_ClientCheck = 22,
+ jack_request_ClientOpen = 23,
+ jack_request_ClientClose = 24,
+ jack_request_ConnectNamePorts = 25,
+ jack_request_DisconnectNamePorts = 26,
+ jack_request_GetInternalClientName = 27,
+ jack_request_InternalClientHandle = 28,
+ jack_request_InternalClientLoad = 29,
+ jack_request_InternalClientUnload = 30,
+ jack_request_PortRename = 31,
+ jack_request_Notification = 32,
+ jack_request_SessionNotify = 33,
+ jack_request_SessionReply = 34,
+ jack_request_GetClientByUUID = 35,
+ jack_request_ReserveClientName = 36,
+ jack_request_GetUUIDByClient = 37,
+ jack_request_ClientHasSessionCallback = 38,
+ jack_request_ComputeTotalLatencies = 39
+};
+
+enum jack_notification_type {
+ jack_notify_AddClient = 0,
+ jack_notify_RemoveClient = 1,
+ jack_notify_ActivateClient = 2,
+ jack_notify_XRunCallback = 3,
+ jack_notify_GraphOrderCallback = 4,
+ jack_notify_BufferSizeCallback = 5,
+ jack_notify_SampleRateCallback = 6,
+ jack_notify_StartFreewheelCallback = 7,
+ jack_notify_StopFreewheelCallback = 8,
+ jack_notify_PortRegistrationOnCallback = 9,
+ jack_notify_PortRegistrationOffCallback = 10,
+ jack_notify_PortConnectCallback = 11,
+ jack_notify_PortDisconnectCallback = 12,
+ jack_notify_PortRenameCallback = 13,
+ jack_notify_RealTimeCallback = 14,
+ jack_notify_ShutDownCallback = 15,
+ jack_notify_QUIT = 16,
+ jack_notify_SessionCallback = 17,
+ jack_notify_LatencyCallback = 18,
+ jack_notify_max = 64 // To keep some room in JackClientControl fCallback table
+};
+
+#define kActivateClient_size (2*sizeof(int))
+#define kDeactivateClient_size (sizeof(int))
+#define kRegisterPort_size (sizeof(int) + JACK_PORT_NAME_SIZE+1 + JACK_PORT_TYPE_SIZE+1 + 2*sizeof(unsigned int))
+#define kClientCheck_size (JACK_CLIENT_NAME_SIZE+1 + 4 * sizeof(int))
+#define kClientOpen_size (JACK_CLIENT_NAME_SIZE+1 + 2 * sizeof(int))
+#define kClientClose_size (sizeof(int))
+#define kConnectNamePorts_size (sizeof(int) + REAL_JACK_PORT_NAME_SIZE+1 + REAL_JACK_PORT_NAME_SIZE+1)
+#define kGetUUIDByClient_size (JACK_CLIENT_NAME_SIZE+1)
+
+#define CheckRead(var,size) if(read(client->fd,var,size)!=size) {pw_log_error("read error"); return -1; }
+#define CheckWrite(var,size) if(write(client->fd,var,size)!=size) {pw_log_error("write error"); return -1; }
+#define CheckSize(expected) { int __size; CheckRead(&__size, sizeof(int)); if (__size != expected) { pw_log_error("CheckSize error size %d != %d", __size, (int)expected); return -1; } }
+
+#define jack_error pw_log_error
+#define jack_log pw_log_info
diff --git a/src/modules/module-jack/server.h b/src/modules/module-jack/server.h
new file mode 100644
index 00000000..c5f6c0c9
--- /dev/null
+++ b/src/modules/module-jack/server.h
@@ -0,0 +1,54 @@
+/* PipeWire
+ * Copyright (C) 2017 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+struct jack_client {
+ int ref_num;
+ struct client *owner;
+ struct jack_client_control *control;
+ struct pw_node *node;
+};
+
+struct jack_server {
+ pthread_mutex_t lock;
+
+ bool promiscuous;
+
+ struct jack_graph_manager *graph_manager;
+ struct jack_engine_control *engine_control;
+
+ struct jack_client* client_table[CLIENT_NUM];
+ struct jack_synchro synchro_table[CLIENT_NUM];
+};
+
+static inline int
+jack_server_allocate_ref_num(struct jack_server *server)
+{
+ int i;
+
+ for (i = 0; i < CLIENT_NUM; i++)
+ if (server->client_table[i] == NULL)
+ return i;
+ return -1;
+}
+
+static inline void
+jack_server_free_ref_num(struct jack_server *server, int ref_num)
+{
+ server->client_table[ref_num] = NULL;
+}
diff --git a/src/modules/module-jack/shared.h b/src/modules/module-jack/shared.h
new file mode 100644
index 00000000..2d4cfaf8
--- /dev/null
+++ b/src/modules/module-jack/shared.h
@@ -0,0 +1,601 @@
+/* PipeWire
+ * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <math.h>
+
+extern int segment_num;
+
+static inline int jack_shm_alloc(size_t size, jack_shm_info_t *info, int num)
+{
+ char name[64];
+
+ snprintf(name, sizeof(name), "/jack_shared%d", num);
+
+ if (jack_shmalloc(name, size, info)) {
+ pw_log_error("Cannot create shared memory segment of size = %zd (%s)", size, strerror(errno));
+ return -1;
+ }
+
+ if (jack_attach_shm(info)) {
+ jack_error("Cannot attach shared memory segment name = %s err = %s", name, strerror(errno));
+ jack_destroy_shm(info);
+ return -1;
+ }
+ info->size = size;
+ return 0;
+}
+
+typedef uint16_t jack_int_t; // Internal type for ports and refnum
+
+typedef enum {
+ NotTriggered,
+ Triggered,
+ Running,
+ Finished,
+} jack_client_state_t;
+
+PRE_PACKED_STRUCTURE
+struct jack_client_timing {
+ jack_time_t signaled_at;
+ jack_time_t awake_at;
+ jack_time_t finished_at;
+ jack_client_state_t status;
+} POST_PACKED_STRUCTURE;
+
+#define JACK_CLIENT_TIMING_INIT (struct jack_client_timing) { 0, 0, 0, NotTriggered }
+
+PRE_PACKED_STRUCTURE
+struct jack_port {
+ int type_id;
+ enum JackPortFlags flags;
+ char name[REAL_JACK_PORT_NAME_SIZE];
+ char alias1[REAL_JACK_PORT_NAME_SIZE];
+ char alias2[REAL_JACK_PORT_NAME_SIZE];
+ int ref_num;
+
+ jack_nframes_t latency;
+ jack_nframes_t total_latency;
+ jack_latency_range_t playback_latency;
+ jack_latency_range_t capture_latency;
+ uint8_t monitor_requests;
+
+ bool in_use;
+ jack_port_id_t tied;
+ jack_default_audio_sample_t buffer[BUFFER_SIZE_MAX + 8];
+} POST_PACKED_STRUCTURE;
+
+static inline void jack_port_init(struct jack_port *port, int ref_num,
+ const char* port_name, int type_id, enum JackPortFlags flags)
+{
+ port->type_id = type_id;
+ port->flags = flags;
+ strcpy(port->name, port_name);
+ port->alias1[0] = '\0';
+ port->alias2[0] = '\0';
+ port->ref_num = ref_num;
+ port->latency = 0;
+ port->total_latency = 0;
+ port->playback_latency.min = port->playback_latency.max = 0;
+ port->capture_latency.min = port->capture_latency.max = 0;
+ port->monitor_requests = 0;
+ port->in_use = true;
+ port->tied = NO_PORT;
+}
+
+#define MAKE_FIXED_ARRAY(size) \
+PRE_PACKED_STRUCTURE \
+struct { \
+ jack_int_t table[size]; \
+ uint32_t counter; \
+} POST_PACKED_STRUCTURE
+
+#define INIT_FIXED_ARRAY(arr) ({ \
+ int i; \
+ for (i = 0; i < SPA_N_ELEMENTS(arr.table); i++) \
+ arr.table[i] = EMPTY; \
+ arr.counter = 0; \
+})
+
+#define ADD_FIXED_ARRAY(arr,item) ({ \
+ int i,ret = -1; \
+ for (i = 0; i < SPA_N_ELEMENTS(arr.table); i++) { \
+ if (arr.table[i] == EMPTY) { \
+ arr.table[i] = item; \
+ arr.counter++; \
+ ret = 0; \
+ break; \
+ } \
+ } \
+ ret; \
+})
+
+#define MAKE_FIXED_ARRAY1(size) \
+PRE_PACKED_STRUCTURE \
+struct { \
+ MAKE_FIXED_ARRAY(size) array; \
+ bool used; \
+} POST_PACKED_STRUCTURE
+
+#define INIT_FIXED_ARRAY1(arr) ({ \
+ INIT_FIXED_ARRAY(arr.array); \
+ arr.used = false; \
+})
+
+#define ADD_FIXED_ARRAY1(arr,item) ADD_FIXED_ARRAY(arr.array,item)
+
+#define MAKE_FIXED_MATRIX(size) \
+PRE_PACKED_STRUCTURE \
+struct { \
+ jack_int_t table[size][size]; \
+} POST_PACKED_STRUCTURE
+
+#define INIT_FIXED_MATRIX(mat,idx) ({ \
+ int i; \
+ for (i = 0; i < SPA_N_ELEMENTS(mat.table[0]); i++){ \
+ mat.table[idx][i] = 0; \
+ mat.table[i][idx] = 0; \
+ } \
+})
+
+PRE_PACKED_STRUCTURE
+struct jack_activation_count {
+ int32_t value;
+ int32_t count;
+} POST_PACKED_STRUCTURE;
+
+static inline void jack_activation_count_set_value(struct jack_activation_count *cnt, int32_t val) {
+ cnt->value = val;
+}
+
+#define MAKE_LOOP_FEEDBACK(size) \
+PRE_PACKED_STRUCTURE \
+struct { \
+ int table[size][3]; \
+} POST_PACKED_STRUCTURE
+
+#define INIT_LOOP_FEEDBACK(arr,size) ({ \
+ int i; \
+ for (i = 0; i < size; i++) { \
+ arr.table[i][0] = EMPTY; \
+ arr.table[i][1] = EMPTY; \
+ arr.table[i][2] = 0; \
+ } \
+})
+
+PRE_PACKED_STRUCTURE
+struct jack_connection_manager {
+ MAKE_FIXED_ARRAY(CONNECTION_NUM_FOR_PORT) connection[PORT_NUM_MAX];
+ MAKE_FIXED_ARRAY1(PORT_NUM_FOR_CLIENT) input_port[CLIENT_NUM];
+ MAKE_FIXED_ARRAY(PORT_NUM_FOR_CLIENT) output_port[CLIENT_NUM];
+ MAKE_FIXED_MATRIX(CLIENT_NUM) connection_ref;
+ struct jack_activation_count input_counter[CLIENT_NUM];
+ MAKE_LOOP_FEEDBACK(CONNECTION_NUM_FOR_PORT) loop_feedback;
+} POST_PACKED_STRUCTURE;
+
+static inline void
+jack_connection_manager_init_ref_num(struct jack_connection_manager *conn, int ref_num)
+{
+ INIT_FIXED_ARRAY1(conn->input_port[ref_num]);
+ INIT_FIXED_ARRAY(conn->output_port[ref_num]);
+ INIT_FIXED_MATRIX(conn->connection_ref, ref_num);
+ jack_activation_count_set_value (&conn->input_counter[ref_num], 0);
+}
+
+static inline void
+jack_connection_manager_init(struct jack_connection_manager *conn)
+{
+ int i;
+ for (i = 0; i < PORT_NUM_MAX; i++)
+ INIT_FIXED_ARRAY(conn->connection[i]);
+
+ INIT_LOOP_FEEDBACK(conn->loop_feedback, CONNECTION_NUM_FOR_PORT);
+
+ for (i = 0; i < CLIENT_NUM; i++)
+ jack_connection_manager_init_ref_num(conn, i);
+}
+
+static inline int
+jack_connection_manager_add_port(struct jack_connection_manager *conn, bool input,
+ int ref_num, jack_port_id_t port_id)
+{
+ if (input)
+ return ADD_FIXED_ARRAY1(conn->input_port[ref_num], port_id);
+ else
+ return ADD_FIXED_ARRAY(conn->output_port[ref_num], port_id);
+}
+
+PRE_PACKED_STRUCTURE
+struct jack_atomic_counter {
+ union {
+ struct {
+ uint16_t short_val1; // Cur
+ uint16_t short_val2; // Next
+ } scounter;
+ uint32_t long_val;
+ } info;
+} POST_PACKED_STRUCTURE;
+
+#define Counter(e) (e).info.long_val
+#define CurIndex(e) (e).info.scounter.short_val1
+#define NextIndex(e) (e).info.scounter.short_val2
+
+#define CurArrayIndex(e) (CurIndex(e) & 0x0001)
+#define NextArrayIndex(e) ((CurIndex(e) + 1) & 0x0001)
+
+#define MAKE_ATOMIC_STATE(type) \
+PRE_PACKED_STRUCTURE \
+struct { \
+ type state[2]; \
+ volatile struct jack_atomic_counter counter; \
+ int32_t call_write_counter; \
+} POST_PACKED_STRUCTURE
+
+PRE_PACKED_STRUCTURE
+struct jack_atomic_array_counter {
+ union {
+ struct {
+ unsigned char byte_val[4];
+ } scounter;
+ uint32_t long_val;
+ } info;
+} POST_PACKED_STRUCTURE;
+
+#define MAKE_ATOMIC_ARRAY_STATE(type) \
+PRE_PACKED_STRUCTURE \
+struct { \
+ type state[3]; \
+ volatile struct jack_atomic_array_counter counter; \
+} POST_PACKED_STRUCTURE
+
+PRE_PACKED_STRUCTURE
+struct jack_graph_manager {
+ jack_shm_info_t info;
+ MAKE_ATOMIC_STATE(struct jack_connection_manager) state;
+ unsigned int port_max;
+ struct jack_client_timing client_timing[CLIENT_NUM];
+ struct jack_port port_array[0];
+} POST_PACKED_STRUCTURE;
+
+static inline struct jack_graph_manager *
+jack_graph_manager_alloc(int port_max)
+{
+ struct jack_graph_manager *mgr;
+ jack_shm_info_t info;
+ size_t i, size;
+
+ size = sizeof(struct jack_graph_manager) + port_max * sizeof(struct jack_port);
+
+ if (jack_shm_alloc(size, &info, segment_num++) < 0)
+ return NULL;
+
+ mgr = (struct jack_graph_manager *)jack_shm_addr(&info);
+ mgr->info = info;
+
+ jack_connection_manager_init(&mgr->state.state[0]);
+ jack_connection_manager_init(&mgr->state.state[1]);
+ mgr->port_max = port_max;
+
+ for (i = 0; i < port_max; i++) {
+ mgr->port_array[i].in_use = false;
+ mgr->port_array[i].ref_num = -1;
+ }
+ return mgr;
+}
+
+static inline jack_port_id_t
+jack_graph_manager_allocate_port(struct jack_graph_manager *mgr,
+ int ref_num, const char* port_name, int type_id,
+ enum JackPortFlags flags)
+{
+ int i;
+ for (i = 1; i < mgr->port_max; i++) {
+ if (!mgr->port_array[i].in_use) {
+ jack_port_init(&mgr->port_array[i], ref_num, port_name, type_id, flags);
+ return i;
+ }
+ }
+ return NO_PORT;
+}
+
+
+static inline struct jack_connection_manager *
+jack_graph_manager_next_start(struct jack_graph_manager *manager)
+{
+ uint32_t next_index;
+
+ if (manager->state.call_write_counter++ == 0) {
+ struct jack_atomic_counter old_val;
+ struct jack_atomic_counter new_val;
+ uint32_t cur_index;
+ bool need_copy;
+ do {
+ old_val = manager->state.counter;
+ new_val = old_val;
+ cur_index = CurArrayIndex(new_val);
+ next_index = NextArrayIndex(new_val);
+ need_copy = (CurIndex(new_val) == NextIndex(new_val));
+ NextIndex(new_val) = CurIndex(new_val); // Invalidate next index
+ }
+ while (!__atomic_compare_exchange_n((uint32_t*)&manager->state.counter,
+ (uint32_t*)&Counter(old_val),
+ Counter(new_val),
+ false,
+ __ATOMIC_SEQ_CST,
+ __ATOMIC_SEQ_CST));
+
+ if (need_copy)
+ memcpy(&manager->state.state[next_index],
+ &manager->state.state[cur_index],
+ sizeof(struct jack_connection_manager));
+ }
+ else {
+ next_index = NextArrayIndex(manager->state.counter);
+ }
+ return &manager->state.state[next_index];
+}
+
+static inline void
+jack_graph_manager_next_stop(struct jack_graph_manager *manager)
+{
+ if (--manager->state.call_write_counter == 0) {
+ struct jack_atomic_counter old_val;
+ struct jack_atomic_counter new_val;
+ do {
+ old_val = manager->state.counter;
+ new_val = old_val;
+ NextIndex(new_val)++; // Set next index
+ }
+ while (!__atomic_compare_exchange_n((uint32_t*)&manager->state.counter,
+ (uint32_t*)&Counter(old_val),
+ Counter(new_val),
+ false,
+ __ATOMIC_SEQ_CST,
+ __ATOMIC_SEQ_CST));
+ }
+}
+
+typedef enum {
+ TransportCommandNone = 0,
+ TransportCommandStart = 1,
+ TransportCommandStop = 2,
+} transport_command_t;
+
+PRE_PACKED_STRUCTURE
+struct jack_transport_engine {
+ MAKE_ATOMIC_ARRAY_STATE(jack_position_t) state;
+ jack_transport_state_t transport_state;
+ volatile transport_command_t transport_cmd;
+ transport_command_t previous_cmd; /* previous transport_cmd */
+ jack_time_t sync_timeout;
+ int sync_time_left;
+ int time_base_master;
+ bool pending_pos;
+ bool network_sync;
+ bool conditionnal;
+ int32_t write_counter;
+} POST_PACKED_STRUCTURE;
+
+PRE_PACKED_STRUCTURE
+struct jack_timer {
+ jack_nframes_t frames;
+ jack_time_t current_wakeup;
+ jack_time_t current_callback;
+ jack_time_t next_wakeup;
+ float period_usecs;
+ float filter_omega; /* set once, never altered */
+ bool initialized;
+} POST_PACKED_STRUCTURE;
+
+PRE_PACKED_STRUCTURE
+struct jack_frame_timer {
+ MAKE_ATOMIC_STATE(struct jack_timer) state;
+ bool first_wakeup;
+} POST_PACKED_STRUCTURE;
+
+#ifdef JACK_MONITOR
+PRE_PACKED_STRUCTURE
+struct jack_timing_measure_client {
+ int ref_num;
+ jack_time_t signaled_at;
+ jack_time_t awake_at;
+ jack_time_t finished_at;
+ jack_client_state_t status;
+} POST_PACKED_STRUCTURE;
+
+PRE_PACKED_STRUCTURE
+struct jack_timing_client_interval {
+ int ref_num;
+ char name[JACK_CLIENT_NAME_SIZE+1];
+ int begin_interval;
+ int end_interval;
+} POST_PACKED_STRUCTURE;
+
+PRE_PACKED_STRUCTURE
+struct jack_timing_measure {
+ unsigned int audio_cycle;
+ jack_time_t period_usecs;
+ jack_time_t cur_cycle_begin;
+ jack_time_t prev_cycle_end;
+ struct jack_timing_measure_client client_table[CLIENT_NUM];
+} POST_PACKED_STRUCTURE;
+
+PRE_PACKED_STRUCTURE
+struct jack_engine_profiling {
+ struct jack_timing_measure profile_table[TIME_POINTS];
+ struct jack_timing_client_interval interval_table[MEASURED_CLIENTS];
+
+ unsigned int audio_cycle;
+ unsigned int measured_client;
+} POST_PACKED_STRUCTURE;
+#endif
+
+PRE_PACKED_STRUCTURE
+struct jack_engine_control {
+ jack_shm_info_t info;
+ jack_nframes_t buffer_size;
+ jack_nframes_t sample_rate;
+ bool sync_mode;
+ bool temporary;
+ jack_time_t period_usecs;
+ jack_time_t timeout_usecs;
+ float max_delayed_usecs;
+ float xrun_delayed_usecs;
+ bool timeout;
+ bool real_time;
+ bool saved_real_time;
+ int server_priority;
+ int client_priority;
+ int max_client_priority;
+ char server_name[JACK_SERVER_NAME_SIZE];
+ struct jack_transport_engine transport;
+ jack_timer_type_t clock_source;
+ int driver_num;
+ bool verbose;
+
+ jack_time_t prev_cycle_time;
+ jack_time_t cur_cycle_time;
+ jack_time_t spare_usecs;
+ jack_time_t max_usecs;
+ jack_time_t rolling_client_usecs[JACK_ENGINE_ROLLING_COUNT];
+ unsigned int rolling_client_usecs_cnt;
+ int rolling_client_usecs_index;
+ int rolling_interval;
+ float CPU_load;
+
+ uint64_t period;
+ uint64_t computation;
+ uint64_t constraint;
+
+ struct jack_frame_timer frame_timer;
+
+#ifdef JACK_MONITOR
+ struct jack_engine_profiling profiler;
+#endif
+} POST_PACKED_STRUCTURE;
+
+static inline void
+jack_engine_control_reset_rolling_usecs(struct jack_engine_control *ctrl)
+{
+ memset(ctrl->rolling_client_usecs, 0, sizeof(ctrl->rolling_client_usecs));
+ ctrl->rolling_client_usecs_index = 0;
+ ctrl->rolling_client_usecs_cnt = 0;
+ ctrl->spare_usecs = 0;
+ ctrl->rolling_interval = floor((JACK_ENGINE_ROLLING_INTERVAL * 1000.f) / ctrl->period_usecs);
+}
+
+static inline struct jack_engine_control *
+jack_engine_control_alloc(const char* name)
+{
+ struct jack_engine_control *ctrl;
+ jack_shm_info_t info;
+ size_t size;
+
+ size = sizeof(struct jack_engine_control);
+ if (jack_shm_alloc(size, &info, segment_num++) < 0)
+ return NULL;
+
+ ctrl = (struct jack_engine_control *)jack_shm_addr(&info);
+ ctrl->info = info;
+
+ ctrl->buffer_size = 512;
+ ctrl->sample_rate = 48000;
+ ctrl->sync_mode = false;
+ ctrl->temporary = false;
+ ctrl->period_usecs = 1000000.f / ctrl->sample_rate * ctrl->buffer_size;
+ ctrl->timeout_usecs = 0;
+ ctrl->max_delayed_usecs = 0.f;
+ ctrl->xrun_delayed_usecs = 0.f;
+ ctrl->timeout = false;
+ ctrl->real_time = true;
+ ctrl->saved_real_time = false;
+ ctrl->server_priority = 20;
+ ctrl->client_priority = 15;
+ ctrl->max_client_priority = 19;
+ strcpy(ctrl->server_name, name);
+ ctrl->clock_source = 0;
+ ctrl->driver_num = 0;
+ ctrl->verbose = true;
+
+ ctrl->prev_cycle_time = 0;
+ ctrl->cur_cycle_time = 0;
+ ctrl->spare_usecs = 0;
+ ctrl->max_usecs = 0;
+ jack_engine_control_reset_rolling_usecs(ctrl);
+ ctrl->CPU_load = 0.f;
+
+ ctrl->period = 0;
+ ctrl->computation = 0;
+ ctrl->constraint = 0;
+
+ return ctrl;
+}
+
+PRE_PACKED_STRUCTURE
+struct jack_client_control {
+ jack_shm_info_t info;
+ char name[JACK_CLIENT_NAME_SIZE+1];
+ bool callback[jack_notify_max];
+ volatile jack_transport_state_t transport_state;
+ volatile bool transport_sync;
+ volatile bool transport_timebase;
+ int ref_num;
+ int PID;
+ bool active;
+
+ int session_ID;
+ char session_command[JACK_SESSION_COMMAND_SIZE];
+ jack_session_flags_t session_flags;
+} POST_PACKED_STRUCTURE;
+
+static inline struct jack_client_control *
+jack_client_control_alloc(const char* name, int pid, int ref_num, int uuid)
+{
+ struct jack_client_control *ctrl;
+ jack_shm_info_t info;
+ size_t size;
+
+ size = sizeof(struct jack_client_control);
+ if (jack_shm_alloc(size, &info, segment_num++) < 0)
+ return NULL;
+
+ ctrl = (struct jack_client_control *)jack_shm_addr(&info);
+ ctrl->info = info;
+
+ strcpy(ctrl->name, name);
+ for (int i = 0; i < jack_notify_max; i++)
+ ctrl->callback[i] = false;
+
+ // Always activated
+ ctrl->callback[jack_notify_AddClient] = true;
+ ctrl->callback[jack_notify_RemoveClient] = true;
+ ctrl->callback[jack_notify_ActivateClient] = true;
+ ctrl->callback[jack_notify_LatencyCallback] = true;
+ // So that driver synchro are correctly setup in "flush" or "normal" mode
+ ctrl->callback[jack_notify_StartFreewheelCallback] = true;
+ ctrl->callback[jack_notify_StopFreewheelCallback] = true;
+ ctrl->ref_num = ref_num;
+ ctrl->PID = pid;
+ ctrl->transport_state = JackTransportStopped;
+ ctrl->transport_sync = false;
+ ctrl->transport_timebase = false;
+ ctrl->active = false;
+ ctrl->session_ID = uuid;
+
+ return ctrl;
+}
diff --git a/src/modules/module-jack/shm.c b/src/modules/module-jack/shm.c
new file mode 100644
index 00000000..c3989bcf
--- /dev/null
+++ b/src/modules/module-jack/shm.c
@@ -0,0 +1,1303 @@
+/* This module provides a set of abstract shared memory interfaces
+ * with support using both System V and POSIX shared memory
+ * implementations. The code is divided into three sections:
+ *
+ * - common (interface-independent) code
+ * - POSIX implementation
+ * - System V implementation
+ * - Windows implementation
+ *
+ * The implementation used is determined by whether USE_POSIX_SHM was
+ * set in the ./configure step.
+ */
+
+/*
+ Copyright (C) 2001-2003 Paul Davis
+ Copyright (C) 2005-2012 Grame
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+ */
+
+//#include "JackConstants.h"
+
+#ifdef WIN32
+#include <process.h>
+#include <stdio.h>
+#else
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <signal.h>
+#include <limits.h>
+#include <errno.h>
+#include <dirent.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/shm.h>
+#include <sys/sem.h>
+#include <stdlib.h>
+
+#endif
+
+#include "defs.h"
+#include "shm.h"
+
+static int GetUID()
+{
+#ifdef WIN32
+ return _getpid();
+ //#error "No getuid function available"
+#else
+ return getuid();
+#endif
+}
+
+static int GetPID()
+{
+#ifdef WIN32
+ return _getpid();
+#else
+ return getpid();
+#endif
+}
+
+#ifdef USE_POSIX_SHM
+static jack_shmtype_t jack_shmtype = shm_POSIX;
+#elif WIN32
+static jack_shmtype_t jack_shmtype = shm_WIN32;
+#else
+static jack_shmtype_t jack_shmtype = shm_SYSV;
+#endif
+
+/* interface-dependent forward declarations */
+static int jack_access_registry (jack_shm_info_t *ri);
+static int jack_create_registry (jack_shm_info_t *ri);
+static void jack_remove_shm (jack_shm_id_t *id);
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * common interface-independent section
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+/* The JACK SHM registry is a chunk of memory for keeping track of the
+ * shared memory used by each active JACK server. This allows the
+ * server to clean up shared memory when it exits. To avoid memory
+ * leakage due to kill -9, crashes or debugger-driven exits, this
+ * cleanup is also done when a new instance of that server starts.
+ */
+
+/* per-process global data for the SHM interfaces */
+static jack_shm_id_t registry_id; /* SHM id for the registry */
+
+#ifdef WIN32
+static jack_shm_info_t registry_info = {/* SHM info for the registry */
+ JACK_SHM_NULL_INDEX,
+ NULL
+};
+#else
+static jack_shm_info_t registry_info = { /* SHM info for the registry */
+ .index = JACK_SHM_NULL_INDEX,
+ .ptr.attached_at = MAP_FAILED
+};
+#endif
+
+/* pointers to registry header and array */
+static jack_shm_header_t *jack_shm_header = NULL;
+static jack_shm_registry_t *jack_shm_registry = NULL;
+static char jack_shm_server_prefix[JACK_SERVER_NAME_SIZE+1] = "";
+
+/* jack_shm_lock_registry() serializes updates to the shared memory
+ * segment JACK uses to keep track of the SHM segments allocated to
+ * all its processes, including multiple servers.
+ *
+ * This is not a high-contention lock, but it does need to work across
+ * multiple processes. High transaction rates and realtime safety are
+ * not required. Any solution needs to at least be portable to POSIX
+ * and POSIX-like systems.
+ *
+ * We must be particularly careful to ensure that the lock be released
+ * if the owning process terminates abnormally. Otherwise, a segfault
+ * or kill -9 at the wrong moment could prevent JACK from ever running
+ * again on that machine until after a reboot.
+ */
+
+#define JACK_SEMAPHORE_KEY 0x282929
+#ifndef USE_POSIX_SHM
+#define JACK_SHM_REGISTRY_KEY JACK_SEMAPHORE_KEY
+#endif
+
+static int semid = -1;
+
+#ifdef WIN32
+
+#include <psapi.h>
+#include <lmcons.h>
+
+static BOOL check_process_running(DWORD process_id)
+{
+ DWORD aProcesses[2048], cbNeeded, cProcesses;
+ unsigned int i;
+
+ // Enumerate all processes
+ if (!EnumProcesses(aProcesses, sizeof(aProcesses), &cbNeeded)) {
+ return FALSE;
+ }
+
+ // Calculate how many process identifiers were returned.
+ cProcesses = cbNeeded / sizeof(DWORD);
+
+ for (i = 0; i < cProcesses; i++) {
+ if (aProcesses[i] == process_id) {
+ // Process process_id is running...
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+static int
+semaphore_init () {return 0;}
+
+static int
+semaphore_add (int value) {return 0;}
+
+#else
+/* all semaphore errors are fatal -- issue message, but do not return */
+static void
+semaphore_error (char *msg)
+{
+ jack_error ("JACK semaphore error: %s (%s)",
+ msg, strerror (errno));
+}
+
+static int
+semaphore_init ()
+{
+ key_t semkey = JACK_SEMAPHORE_KEY;
+ struct sembuf sbuf;
+ int create_flags = IPC_CREAT | IPC_EXCL
+ | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
+
+ /* Get semaphore ID associated with this key. */
+ if ((semid = semget(semkey, 0, 0)) == -1) {
+
+ /* Semaphore does not exist - Create. */
+ if ((semid = semget(semkey, 1, create_flags)) != -1) {
+
+ /* Initialize the semaphore, allow one owner. */
+ sbuf.sem_num = 0;
+ sbuf.sem_op = 1;
+ sbuf.sem_flg = 0;
+ if (semop(semid, &sbuf, 1) == -1) {
+ semaphore_error ("semop");
+ return -1;
+ }
+
+ } else if (errno == EEXIST) {
+ if ((semid = semget(semkey, 0, 0)) == -1) {
+ semaphore_error ("semget");
+ return -1;
+ }
+
+ } else {
+ semaphore_error ("semget creation");
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static inline int
+semaphore_add (int value)
+{
+ struct sembuf sbuf;
+
+ sbuf.sem_num = 0;
+ sbuf.sem_op = value;
+ sbuf.sem_flg = SEM_UNDO;
+
+ if (semop(semid, &sbuf, 1) == -1) {
+ semaphore_error ("semop");
+ return -1;
+ }
+
+ return 0;
+}
+
+#endif
+
+static int
+jack_shm_lock_registry (void)
+{
+ if (semid == -1) {
+ if (semaphore_init () < 0)
+ return -1;
+ }
+
+ return semaphore_add (-1);
+}
+
+static void
+jack_shm_unlock_registry (void)
+{
+ semaphore_add (1);
+}
+
+static void
+jack_shm_init_registry ()
+{
+ /* registry must be locked */
+ int i;
+
+ memset (jack_shm_header, 0, JACK_SHM_REGISTRY_SIZE);
+
+ jack_shm_header->magic = JACK_SHM_MAGIC;
+ //jack_shm_header->protocol = JACK_PROTOCOL_VERSION;
+ jack_shm_header->type = jack_shmtype;
+ jack_shm_header->size = JACK_SHM_REGISTRY_SIZE;
+ jack_shm_header->hdr_len = sizeof (jack_shm_header_t);
+ jack_shm_header->entry_len = sizeof (jack_shm_registry_t);
+
+ for (i = 0; i < MAX_SHM_ID; ++i) {
+ jack_shm_registry[i].index = i;
+ }
+}
+
+static int
+jack_shm_validate_registry ()
+{
+ /* registry must be locked */
+
+ if ((jack_shm_header->magic == JACK_SHM_MAGIC)
+ //&& (jack_shm_header->protocol == JACK_PROTOCOL_VERSION)
+ && (jack_shm_header->type == jack_shmtype)
+ && (jack_shm_header->size == JACK_SHM_REGISTRY_SIZE)
+ && (jack_shm_header->hdr_len == sizeof (jack_shm_header_t))
+ && (jack_shm_header->entry_len == sizeof (jack_shm_registry_t))) {
+
+ return 0; /* registry OK */
+ }
+
+ return -1;
+}
+
+/* set a unique per-user, per-server shm prefix string
+ *
+ * According to the POSIX standard:
+ *
+ * "The name argument conforms to the construction rules for a
+ * pathname. If name begins with the slash character, then processes
+ * calling shm_open() with the same value of name refer to the same
+ * shared memory object, as long as that name has not been
+ * removed. If name does not begin with the slash character, the
+ * effect is implementation-defined. The interpretation of slash
+ * characters other than the leading slash character in name is
+ * implementation-defined."
+ *
+ * Since the Linux implementation does not allow slashes *within* the
+ * name, in the interest of portability we use colons instead.
+ */
+static void
+jack_set_server_prefix (const char *server_name)
+{
+#ifdef WIN32
+ char buffer[UNLEN+1]={0};
+ DWORD len = UNLEN+1;
+ GetUserName(buffer, &len);
+ snprintf (jack_shm_server_prefix, sizeof (jack_shm_server_prefix),
+ "jack-%s:%s:", buffer, server_name);
+#else
+ snprintf (jack_shm_server_prefix, sizeof (jack_shm_server_prefix),
+ "jack-%d:%s:", GetUID(), server_name);
+#endif
+}
+
+/* gain server addressability to shared memory registration segment
+ *
+ * returns: 0 if successful
+ */
+static int
+jack_server_initialize_shm (int new_registry)
+{
+ int rc;
+
+ if (jack_shm_header)
+ return 0; /* already initialized */
+
+ if (jack_shm_lock_registry () < 0) {
+ jack_error ("jack_shm_lock_registry fails...");
+ return -1;
+ }
+
+ rc = jack_access_registry (&registry_info);
+
+ if (new_registry) {
+ jack_remove_shm (&registry_id);
+ rc = ENOENT;
+ }
+
+ switch (rc) {
+ case ENOENT: /* registry does not exist */
+ rc = jack_create_registry (&registry_info);
+ break;
+ case 0: /* existing registry */
+ if (jack_shm_validate_registry () == 0)
+ break;
+ /* else it was invalid, so fall through */
+ case EINVAL: /* bad registry */
+ /* Apparently, this registry was created by an older
+ * JACK version. Delete it so we can try again. */
+ jack_release_shm (&registry_info);
+ jack_remove_shm (&registry_id);
+ if ((rc = jack_create_registry (&registry_info)) != 0) {
+ jack_error ("incompatible shm registry (%s)",
+ strerror (errno));
+#ifndef USE_POSIX_SHM
+ jack_error ("to delete, use `ipcrm -M 0x%8x'",
+ JACK_SHM_REGISTRY_KEY);
+#endif
+ }
+ break;
+ default: /* failure return code */
+ break;
+ }
+
+ jack_shm_unlock_registry ();
+ return rc;
+}
+
+/* gain client addressability to shared memory registration segment
+ *
+ * NOTE: this function is no longer used for server initialization,
+ * instead it calls jack_register_server().
+ *
+ * returns: 0 if successful
+ */
+int
+jack_initialize_shm (const char *server_name)
+{
+ int rc;
+
+ if (jack_shm_header)
+ return 0; /* already initialized */
+
+ jack_set_server_prefix (server_name);
+
+ if (jack_shm_lock_registry () < 0) {
+ jack_error ("jack_shm_lock_registry fails...");
+ return -1;
+ }
+
+ if ((rc = jack_access_registry (&registry_info)) == 0) {
+ if ((rc = jack_shm_validate_registry ()) != 0) {
+ jack_error ("Incompatible shm registry, "
+ "are jackd and libjack in sync?");
+ }
+ }
+ jack_shm_unlock_registry ();
+
+ return rc;
+}
+
+
+char* jack_shm_addr (jack_shm_info_t* si)
+{
+ return (char*)si->ptr.attached_at;
+}
+
+void
+jack_destroy_shm (jack_shm_info_t* si)
+{
+ /* must NOT have the registry locked */
+ if (si->index == JACK_SHM_NULL_INDEX)
+ return; /* segment not allocated */
+
+ jack_remove_shm (&jack_shm_registry[si->index].id);
+ jack_release_shm_info (si->index);
+}
+
+jack_shm_registry_t *
+jack_get_free_shm_info ()
+{
+ /* registry must be locked */
+ jack_shm_registry_t* si = NULL;
+ int i;
+
+ for (i = 0; i < MAX_SHM_ID; ++i) {
+ if (jack_shm_registry[i].size == 0) {
+ break;
+ }
+ }
+
+ if (i < MAX_SHM_ID) {
+ si = &jack_shm_registry[i];
+ }
+
+ return si;
+}
+
+static void
+jack_release_shm_entry (jack_shm_registry_index_t index)
+{
+ /* the registry must be locked */
+ jack_shm_registry[index].size = 0;
+ jack_shm_registry[index].allocator = 0;
+ memset (&jack_shm_registry[index].id, 0,
+ sizeof (jack_shm_registry[index].id));
+}
+
+int
+jack_release_shm_info (jack_shm_registry_index_t index)
+{
+ /* must NOT have the registry locked */
+ if (jack_shm_registry[index].allocator == GetPID()) {
+ if (jack_shm_lock_registry () < 0) {
+ jack_error ("jack_shm_lock_registry fails...");
+ return -1;
+ }
+ jack_release_shm_entry (index);
+ jack_shm_unlock_registry ();
+ }
+
+ return 0;
+}
+
+/* Claim server_name for this process.
+ *
+ * returns 0 if successful
+ * EEXIST if server_name was already active for this user
+ * ENOSPC if server registration limit reached
+ * ENOMEM if unable to access shared memory registry
+ */
+int
+jack_register_server (const char *server_name, int new_registry)
+{
+ int i, res = 0;
+
+ jack_set_server_prefix (server_name);
+
+ if (jack_server_initialize_shm (new_registry))
+ return ENOMEM;
+
+ if (jack_shm_lock_registry () < 0) {
+ jack_error ("jack_shm_lock_registry fails...");
+ return -1;
+ }
+
+ /* See if server_name already registered. Since server names
+ * are per-user, we register the unique server prefix string.
+ */
+ for (i = 0; i < MAX_SERVERS; i++) {
+
+ if (strncmp (jack_shm_header->server[i].name,
+ jack_shm_server_prefix,
+ JACK_SERVER_NAME_SIZE) != 0)
+ continue; /* no match */
+
+ if (jack_shm_header->server[i].pid == GetPID()){
+ res = 0; /* it's me */
+ goto unlock;
+ }
+
+ /* see if server still exists */
+ #ifdef WIN32
+ if (check_process_running(jack_shm_header->server[i].pid)) {
+ res = EEXIST; /* other server running */
+ goto unlock;
+ }
+ #else
+ if (kill (jack_shm_header->server[i].pid, 0) == 0) {
+ res = EEXIST; /* other server running */
+ goto unlock;
+ }
+ #endif
+
+ /* it's gone, reclaim this entry */
+ memset (&jack_shm_header->server[i], 0,
+ sizeof (jack_shm_server_t));
+ }
+
+ /* find a free entry */
+ for (i = 0; i < MAX_SERVERS; i++) {
+ if (jack_shm_header->server[i].pid == 0)
+ break;
+ }
+
+ if (i >= MAX_SERVERS){
+ res = ENOSPC; /* out of space */
+ goto unlock;
+ }
+
+ /* claim it */
+ jack_shm_header->server[i].pid = GetPID();
+ strncpy (jack_shm_header->server[i].name,
+ jack_shm_server_prefix,
+ JACK_SERVER_NAME_SIZE);
+
+ unlock:
+ jack_shm_unlock_registry ();
+ return res;
+}
+
+/* release server_name registration */
+int
+jack_unregister_server (const char *server_name /* unused */)
+{
+ int i;
+ if (jack_shm_lock_registry () < 0) {
+ jack_error ("jack_shm_lock_registry fails...");
+ return -1;
+ }
+
+ for (i = 0; i < MAX_SERVERS; i++) {
+ if (jack_shm_header->server[i].pid == GetPID()) {
+ memset (&jack_shm_header->server[i], 0,
+ sizeof (jack_shm_server_t));
+ }
+ }
+
+ jack_shm_unlock_registry ();
+ return 0;
+}
+
+/* called for server startup and termination */
+int
+jack_cleanup_shm ()
+{
+ int i;
+ int destroy;
+ jack_shm_info_t copy;
+
+ if (jack_shm_lock_registry () < 0) {
+ jack_error ("jack_shm_lock_registry fails...");
+ return -1;
+ }
+
+ for (i = 0; i < MAX_SHM_ID; i++) {
+ jack_shm_registry_t* r;
+
+ r = &jack_shm_registry[i];
+ memcpy (&copy, r, sizeof (jack_shm_info_t));
+ destroy = FALSE;
+
+ /* ignore unused entries */
+ if (r->allocator == 0)
+ continue;
+
+ /* is this my shm segment? */
+ if (r->allocator == GetPID()) {
+
+ /* allocated by this process, so unattach
+ and destroy. */
+ jack_release_shm (&copy);
+ destroy = TRUE;
+
+ } else {
+
+ /* see if allocator still exists */
+ #ifdef WIN32
+ //jack_info("TODO: kill API not available !!");
+ #else
+ if (kill (r->allocator, 0)) {
+ if (errno == ESRCH) {
+ /* allocator no longer exists,
+ * so destroy */
+ destroy = TRUE;
+ }
+ }
+ #endif
+ }
+
+ if (destroy) {
+
+ int index = copy.index;
+
+ if ((index >= 0) && (index < MAX_SHM_ID)) {
+ jack_remove_shm (&jack_shm_registry[index].id);
+ jack_release_shm_entry (index);
+ }
+ r->size = 0;
+ r->allocator = 0;
+ }
+ }
+
+ jack_shm_unlock_registry ();
+ return TRUE;
+}
+
+/* resize a shared memory segment
+ *
+ * There is no way to resize a System V shm segment. Resizing is
+ * possible with POSIX shm, but not with the non-conformant Mac OS X
+ * implementation. Since POSIX shm is mainly used on that platform,
+ * it's simpler to treat them both the same.
+ *
+ * So, we always resize by deleting and reallocating. This is
+ * tricky, because the old segment will not disappear until
+ * all the clients have released it. We only do what we can
+ * from here.
+ *
+ * This is not done under a single lock. I don't even want to think
+ * about all the things that could possibly go wrong if multple
+ * processes tried to resize the same segment concurrently. That
+ * probably doesn't happen.
+ */
+int
+jack_resize_shm (jack_shm_info_t* si, jack_shmsize_t size)
+{
+ jack_shm_id_t id;
+
+ /* The underlying type of `id' differs for SYSV and POSIX */
+ memcpy (&id, &jack_shm_registry[si->index].id, sizeof (id));
+
+ jack_release_shm (si);
+ jack_destroy_shm (si);
+
+ if (jack_shmalloc ((char *) id, size, si)) {
+ return -1;
+ }
+
+ return jack_attach_shm (si);
+}
+
+int
+jack_attach_lib_shm (jack_shm_info_t* si)
+{
+ int res = jack_attach_shm(si);
+ if (res == 0)
+ si->size = jack_shm_registry[si->index].size; // Keep size in si struct
+ return res;
+}
+
+int
+jack_attach_lib_shm_read (jack_shm_info_t* si)
+{
+ int res = jack_attach_shm_read(si);
+ if (res == 0)
+ si->size = jack_shm_registry[si->index].size; // Keep size in si struct
+ return res;
+}
+
+#ifdef USE_POSIX_SHM
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * POSIX interface-dependent functions
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+/* gain addressability to existing SHM registry segment
+ *
+ * sets up global registry pointers, if successful
+ *
+ * returns: 0 if existing registry accessed successfully
+ * ENOENT if registry does not exist
+ * EINVAL if registry exists, but has the wrong size
+ */
+static int
+jack_access_registry (jack_shm_info_t *ri)
+{
+ /* registry must be locked */
+ int shm_fd;
+
+ strncpy (registry_id, "/jack-shm-registry", sizeof (registry_id));
+
+ /* try to open an existing segment */
+ if ((shm_fd = shm_open (registry_id, O_RDWR, 0666)) < 0) {
+ int rc = errno;
+ if (errno != ENOENT) {
+ jack_error ("Cannot open existing shm registry segment"
+ " (%s)", strerror (errno));
+ }
+ close (shm_fd);
+ return rc;
+ }
+
+ if ((ri->ptr.attached_at = mmap (0, JACK_SHM_REGISTRY_SIZE,
+ PROT_READ|PROT_WRITE,
+ MAP_SHARED, shm_fd, 0)) == MAP_FAILED) {
+ jack_error ("Cannot mmap shm registry segment (%s)",
+ strerror (errno));
+ close (shm_fd);
+ return EINVAL;
+ }
+
+ /* set up global pointers */
+ ri->index = JACK_SHM_REGISTRY_INDEX;
+ jack_shm_header = ri->ptr.attached_at;
+ jack_shm_registry = (jack_shm_registry_t *) (jack_shm_header + 1);
+
+ close (shm_fd);
+ return 0;
+}
+
+/* create a new SHM registry segment
+ *
+ * sets up global registry pointers, if successful
+ *
+ * returns: 0 if registry created successfully
+ * nonzero error code if unable to allocate a new registry
+ */
+static int
+jack_create_registry (jack_shm_info_t *ri)
+{
+ /* registry must be locked */
+ int shm_fd;
+
+ strncpy (registry_id, "/jack-shm-registry", sizeof (registry_id));
+
+ if ((shm_fd = shm_open (registry_id, O_RDWR|O_CREAT, 0666)) < 0) {
+ int rc = errno;
+ jack_error ("Cannot create shm registry segment (%s)",
+ strerror (errno));
+ return rc;
+ }
+
+ /* Previous shm_open result depends of the actual value of umask, force correct file permisssion here */
+ if (fchmod(shm_fd, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH) < 0) {
+ jack_log("Cannot chmod jack-shm-registry (%s)", strerror (errno));
+ }
+
+ /* Set the desired segment size. NOTE: the non-conformant Mac
+ * OS X POSIX shm only allows ftruncate() on segment creation.
+ */
+ if (ftruncate (shm_fd, JACK_SHM_REGISTRY_SIZE) < 0) {
+ int rc = errno;
+ jack_error ("Cannot set registry size (%s)", strerror (errno));
+ jack_remove_shm (&registry_id);
+ close (shm_fd);
+ return rc;
+ }
+
+ if ((ri->ptr.attached_at = mmap (0, JACK_SHM_REGISTRY_SIZE,
+ PROT_READ|PROT_WRITE,
+ MAP_SHARED, shm_fd, 0)) == MAP_FAILED) {
+ jack_error ("Cannot mmap shm registry segment (%s)",
+ strerror (errno));
+ jack_remove_shm (&registry_id);
+ close (shm_fd);
+ return EINVAL;
+ }
+
+ /* set up global pointers */
+ ri->index = JACK_SHM_REGISTRY_INDEX;
+ jack_shm_header = ri->ptr.attached_at;
+ jack_shm_registry = (jack_shm_registry_t *) (jack_shm_header + 1);
+
+ /* initialize registry contents */
+ jack_shm_init_registry ();
+ close (shm_fd);
+ return 0;
+}
+
+static void
+jack_remove_shm (jack_shm_id_t *id)
+{
+ /* registry may or may not be locked */
+ shm_unlink ((char *) id);
+}
+
+void
+jack_release_shm (jack_shm_info_t* si)
+{
+ /* registry may or may not be locked */
+ if (si->ptr.attached_at != MAP_FAILED) {
+ munmap (si->ptr.attached_at, jack_shm_registry[si->index].size);
+ }
+}
+
+void
+jack_release_lib_shm (jack_shm_info_t* si)
+{
+ /* registry may or may not be locked */
+ if (si->ptr.attached_at != MAP_FAILED) {
+ munmap (si->ptr.attached_at, si->size);
+ }
+}
+
+/* allocate a POSIX shared memory segment */
+int
+jack_shmalloc (const char *shm_name, jack_shmsize_t size, jack_shm_info_t* si)
+{
+ jack_shm_registry_t* registry;
+ int shm_fd;
+ int rc = -1;
+ char name[SHM_NAME_MAX+1];
+
+ if (jack_shm_lock_registry () < 0) {
+ jack_error ("jack_shm_lock_registry fails...");
+ return -1;
+ }
+
+ if ((registry = jack_get_free_shm_info ()) == NULL) {
+ jack_error ("shm registry full");
+ goto unlock;
+ }
+
+ /* On Mac OS X, the maximum length of a shared memory segment
+ * name is SHM_NAME_MAX (instead of NAME_MAX or PATH_MAX as
+ * defined by the standard). Unfortunately, Apple sets this
+ * value so small (about 31 bytes) that it is useless for
+ * actual names. So, we construct a short name from the
+ * registry index for uniqueness and ignore the shm_name
+ * parameter. Bah!
+ */
+ snprintf (name, sizeof (name), "/jack-%d-%d", GetUID(), registry->index);
+
+ if (strlen (name) >= sizeof (registry->id)) {
+ jack_error ("shm segment name too long %s", name);
+ goto unlock;
+ }
+
+ if ((shm_fd = shm_open (name, O_RDWR|O_CREAT, 0666)) < 0) {
+ jack_error ("Cannot create shm segment %s (%s)",
+ name, strerror (errno));
+ goto unlock;
+ }
+
+ if (ftruncate (shm_fd, size) < 0) {
+ jack_error ("Cannot set size of engine shm "
+ "registry 0 (%s)",
+ strerror (errno));
+ close (shm_fd);
+ goto unlock;
+ }
+
+ close (shm_fd);
+ registry->size = size;
+ strncpy (registry->id, name, sizeof (registry->id));
+ registry->allocator = GetPID();
+ si->index = registry->index;
+ si->ptr.attached_at = MAP_FAILED; /* not attached */
+ rc = 0; /* success */
+
+ unlock:
+ jack_shm_unlock_registry ();
+ return rc;
+}
+
+int
+jack_attach_shm (jack_shm_info_t* si)
+{
+ int shm_fd;
+ jack_shm_registry_t *registry = &jack_shm_registry[si->index];
+
+ if ((shm_fd = shm_open (registry->id,
+ O_RDWR, 0666)) < 0) {
+ jack_error ("Cannot open shm segment %s (%s)", registry->id,
+ strerror (errno));
+ return -1;
+ }
+
+ if ((si->ptr.attached_at = mmap (0, registry->size, PROT_READ|PROT_WRITE,
+ MAP_SHARED, shm_fd, 0)) == MAP_FAILED) {
+ jack_error ("Cannot mmap shm segment %s (%s)",
+ registry->id,
+ strerror (errno));
+ close (shm_fd);
+ return -1;
+ }
+
+ close (shm_fd);
+ return 0;
+}
+
+int
+jack_attach_shm_read (jack_shm_info_t* si)
+{
+ int shm_fd;
+ jack_shm_registry_t *registry = &jack_shm_registry[si->index];
+
+ if ((shm_fd = shm_open (registry->id,
+ O_RDONLY, 0666)) < 0) {
+ jack_error ("Cannot open shm segment %s (%s)", registry->id,
+ strerror (errno));
+ return -1;
+ }
+
+ if ((si->ptr.attached_at = mmap (0, registry->size, PROT_READ,
+ MAP_SHARED, shm_fd, 0)) == MAP_FAILED) {
+ jack_error ("Cannot mmap shm segment %s (%s)",
+ registry->id,
+ strerror (errno));
+ close (shm_fd);
+ return -1;
+ }
+
+ close (shm_fd);
+ return 0;
+}
+
+#elif WIN32
+
+static int
+jack_access_registry (jack_shm_info_t *ri)
+{
+ /* registry must be locked */
+ HANDLE shm_fd;
+ strncpy (registry_id, "jack-shm-registry", sizeof (registry_id));
+
+ /* try to open an existing segment */
+
+ if ((shm_fd = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, registry_id)) == NULL) {
+ int rc = GetLastError();
+ if (rc != ERROR_FILE_NOT_FOUND) {
+ jack_error ("Cannot open existing shm registry segment (%ld)", rc);
+ }
+ return rc;
+ }
+
+ if ((ri->ptr.attached_at = MapViewOfFile (shm_fd, FILE_MAP_ALL_ACCESS, 0, 0, JACK_SHM_REGISTRY_SIZE)) == NULL) {
+ jack_error ("Cannot mmap shm registry segment (%ld)", GetLastError());
+ jack_remove_shm (&registry_id);
+ CloseHandle (shm_fd);
+ return EINVAL;
+ }
+
+ /* set up global pointers */
+ ri->index = JACK_SHM_REGISTRY_INDEX;
+ jack_shm_header = ri->ptr.attached_at;
+ jack_shm_registry = (jack_shm_registry_t *) (jack_shm_header + 1);
+
+ //CloseHandle(shm_fd); // TO CHECK
+ return 0;
+}
+
+static int
+jack_create_registry (jack_shm_info_t *ri)
+{
+ /* registry must be locked */
+ HANDLE shm_fd;
+
+ strncpy (registry_id, "jack-shm-registry", sizeof (registry_id));
+
+ if ((shm_fd = CreateFileMapping(INVALID_HANDLE_VALUE,
+ 0, PAGE_READWRITE,
+ 0, JACK_SHM_REGISTRY_SIZE,
+ registry_id)) == NULL || (shm_fd == INVALID_HANDLE_VALUE)) {
+ int rc = GetLastError();
+ jack_error ("Cannot create shm registry segment (%ld)", rc);
+ return rc;
+ }
+
+ if ((ri->ptr.attached_at = MapViewOfFile (shm_fd, FILE_MAP_ALL_ACCESS, 0, 0, JACK_SHM_REGISTRY_SIZE)) == NULL) {
+ jack_error ("Cannot mmap shm registry segment (%ld)", GetLastError());
+ jack_remove_shm (&registry_id);
+ CloseHandle (shm_fd);
+ return EINVAL;
+ }
+
+ /* set up global pointers */
+ ri->index = JACK_SHM_REGISTRY_INDEX;
+ jack_shm_header = ri->ptr.attached_at;
+ jack_shm_registry = (jack_shm_registry_t *) (jack_shm_header + 1);
+
+ /* initialize registry contents */
+ jack_shm_init_registry ();
+
+ //CloseHandle(shm_fd); // TO CHECK
+ return 0;
+}
+
+static void
+jack_remove_shm (jack_shm_id_t *id)
+{
+ /* nothing to do */
+}
+
+void
+jack_release_shm (jack_shm_info_t* si)
+{
+ /* registry may or may not be locked */
+ if (si->ptr.attached_at != NULL) {
+ UnmapViewOfFile (si->ptr.attached_at);
+ }
+}
+
+void
+jack_release_lib_shm (jack_shm_info_t* si)
+{
+ jack_release_shm(si);
+}
+
+int
+jack_shmalloc (const char *shm_name, jack_shmsize_t size, jack_shm_info_t* si)
+{
+ jack_shm_registry_t* registry;
+ HANDLE shm_fd;
+ int rc = -1;
+ char name[SHM_NAME_MAX+1];
+
+ if (jack_shm_lock_registry () < 0) {
+ jack_error ("jack_shm_lock_registry fails...");
+ return -1;
+ }
+
+ if ((registry = jack_get_free_shm_info ()) == NULL) {
+ jack_error ("shm registry full");
+ goto unlock;
+ }
+
+ snprintf (name, sizeof (name), "jack-%d-%d", GetUID(), registry->index);
+
+ if (strlen (name) >= sizeof (registry->id)) {
+ jack_error ("shm segment name too long %s", name);
+ goto unlock;
+ }
+
+ if ((shm_fd = CreateFileMapping(INVALID_HANDLE_VALUE,
+ 0, PAGE_READWRITE,
+ 0, size,
+ name)) == NULL || (shm_fd == INVALID_HANDLE_VALUE)) {
+ int rc = GetLastError();
+ jack_error ("Cannot create shm segment (%ld)",rc);
+ goto unlock;
+ }
+
+ //CloseHandle (shm_fd); // TO CHECK
+
+ registry->size = size;
+ strncpy (registry->id, name, sizeof (registry->id));
+ registry->allocator = _getpid();
+ si->index = registry->index;
+ si->ptr.attached_at = NULL; /* not attached */
+ rc = 0; /* success */
+
+ unlock:
+ jack_shm_unlock_registry ();
+ return rc;
+}
+
+int
+jack_attach_shm (jack_shm_info_t* si)
+{
+ HANDLE shm_fd;
+ jack_shm_registry_t *registry = &jack_shm_registry[si->index];
+
+ if ((shm_fd = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, registry->id)) == NULL) {
+ int rc = GetLastError();
+ jack_error ("Cannot open shm segment (%ld)",rc);
+ return -1;
+ }
+
+ if ((si->ptr.attached_at = MapViewOfFile (shm_fd, FILE_MAP_ALL_ACCESS, 0, 0, registry->size)) == NULL) {
+ jack_error ("Cannot mmap shm segment (%ld)", GetLastError());
+ jack_remove_shm (&registry_id);
+ CloseHandle (shm_fd);
+ return -1;
+ }
+
+ //CloseHandle (shm_fd); // TO CHECK
+ return 0;
+}
+
+int
+jack_attach_shm_read (jack_shm_info_t* si)
+{
+ HANDLE shm_fd;
+ jack_shm_registry_t *registry = &jack_shm_registry[si->index];
+
+ if ((shm_fd = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, registry->id)) == NULL) {
+ int rc = GetLastError();
+ jack_error ("Cannot open shm segment (%ld)",rc);
+ return -1;
+ }
+
+ if ((si->ptr.attached_at = MapViewOfFile (shm_fd, FILE_MAP_READ, 0, 0, registry->size)) == NULL) {
+ jack_error("Cannot mmap shm segment (%ld)", GetLastError());
+ jack_remove_shm(&registry_id);
+ CloseHandle(shm_fd);
+ return -1;
+ }
+
+ //CloseHandle (shm_fd); // TO CHECK
+ return 0;
+}
+
+#else
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * System V interface-dependent functions
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+/* gain addressability to existing SHM registry segment
+ *
+ * sets up global registry pointers, if successful
+ *
+ * returns: 0 if existing registry accessed successfully
+ * ENOENT if registry does not exist
+ * EINVAL if registry exists, but has the wrong size
+ * other nonzero error code if unable to access registry
+ */
+static int
+jack_access_registry (jack_shm_info_t *ri)
+{
+ /* registry must be locked */
+
+ /* try without IPC_CREAT to get existing segment */
+ if ((registry_id = shmget (JACK_SHM_REGISTRY_KEY,
+ JACK_SHM_REGISTRY_SIZE, 0666)) < 0) {
+
+ switch (errno) {
+
+ case ENOENT: /* segment does not exist */
+ return ENOENT;
+
+ case EINVAL: /* segment exists, but too small */
+ /* attempt minimum size access */
+ registry_id = shmget (JACK_SHM_REGISTRY_KEY, 1, 0666);
+ return EINVAL;
+
+ default: /* or other error */
+ jack_error ("unable to access shm registry (%s)",
+ strerror (errno));
+ return errno;
+ }
+ }
+
+ if ((ri->ptr.attached_at = shmat (registry_id, 0, 0)) < 0) {
+ jack_error ("Cannot attach shm registry segment (%s)",
+ strerror (errno));
+ return EINVAL;
+ }
+
+ /* set up global pointers */
+ ri->index = JACK_SHM_REGISTRY_INDEX;
+ jack_shm_header = ri->ptr.attached_at;
+ jack_shm_registry = (jack_shm_registry_t *) (jack_shm_header + 1);
+ return 0;
+}
+
+/* create a new SHM registry segment
+ *
+ * sets up global registry pointers, if successful
+ *
+ * returns: 0 if registry created successfully
+ * nonzero error code if unable to allocate a new registry
+ */
+static int
+jack_create_registry (jack_shm_info_t *ri)
+{
+ /* registry must be locked */
+ if ((registry_id = shmget (JACK_SHM_REGISTRY_KEY,
+ JACK_SHM_REGISTRY_SIZE,
+ 0666|IPC_CREAT)) < 0) {
+ jack_error ("Cannot create shm registry segment (%s)",
+ strerror (errno));
+ return errno;
+ }
+
+ if ((ri->ptr.attached_at = shmat (registry_id, 0, 0)) < 0) {
+ jack_error ("Cannot attach shm registry segment (%s)",
+ strerror (errno));
+ return EINVAL;
+ }
+
+ /* set up global pointers */
+ ri->index = JACK_SHM_REGISTRY_INDEX;
+ jack_shm_header = ri->ptr.attached_at;
+ jack_shm_registry = (jack_shm_registry_t *) (jack_shm_header + 1);
+
+ /* initialize registry contents */
+ jack_shm_init_registry ();
+ return 0;
+}
+
+static void
+jack_remove_shm (jack_shm_id_t *id)
+{
+ /* registry may or may not be locked */
+ shmctl (*id, IPC_RMID, NULL);
+}
+
+void
+jack_release_shm (jack_shm_info_t* si)
+{
+ /* registry may or may not be locked */
+ if (si->ptr.attached_at != MAP_FAILED) {
+ shmdt (si->ptr.attached_at);
+ }
+}
+
+void
+jack_release_lib_shm (jack_shm_info_t* si)
+{
+ jack_release_shm(si);
+}
+
+int
+jack_shmalloc (const char* name_not_used, jack_shmsize_t size,
+ jack_shm_info_t* si)
+{
+ int shmflags;
+ int shmid;
+ int rc = -1;
+ jack_shm_registry_t* registry;
+
+ if (jack_shm_lock_registry () < 0) {
+ jack_error ("jack_shm_lock_registry fails...");
+ return -1;
+ }
+
+ if ((registry = jack_get_free_shm_info ())) {
+
+ shmflags = 0666 | IPC_CREAT | IPC_EXCL;
+
+ if ((shmid = shmget (IPC_PRIVATE, size, shmflags)) >= 0) {
+
+ registry->size = size;
+ registry->id = shmid;
+ registry->allocator = getpid();
+ si->index = registry->index;
+ si->ptr.attached_at = MAP_FAILED; /* not attached */
+ rc = 0;
+
+ } else {
+ jack_error ("Cannot create shm segment %s (%s)",
+ name_not_used, strerror (errno));
+ }
+ }
+
+ jack_shm_unlock_registry ();
+ return rc;
+}
+
+int
+jack_attach_shm (jack_shm_info_t* si)
+{
+ if ((si->ptr.attached_at = shmat (jack_shm_registry[si->index].id, 0, 0)) < 0) {
+ jack_error ("Cannot attach shm segment (%s)",
+ strerror (errno));
+ jack_release_shm_info (si->index);
+ return -1;
+ }
+ return 0;
+}
+
+int
+jack_attach_shm_read (jack_shm_info_t* si)
+{
+ if ((si->ptr.attached_at = shmat (jack_shm_registry[si->index].id, 0, SHM_RDONLY)) < 0) {
+ jack_error ("Cannot attach shm segment (%s)",
+ strerror (errno));
+ jack_release_shm_info (si->index);
+ return -1;
+ }
+ return 0;
+}
+
+#endif /* !USE_POSIX_SHM */
diff --git a/src/modules/module-jack/shm.h b/src/modules/module-jack/shm.h
new file mode 100644
index 00000000..90336eea
--- /dev/null
+++ b/src/modules/module-jack/shm.h
@@ -0,0 +1,216 @@
+/* This module provides a set of abstract shared memory interfaces
+ * with support using both System V and POSIX shared memory
+ * implementations. The code is divided into three sections:
+ *
+ * - common (interface-independent) code
+ * - POSIX implementation
+ * - System V implementation
+ * - Windows implementation
+ *
+ * The implementation used is determined by whether USE_POSIX_SHM was
+ * set in the ./configure step.
+ */
+
+/*
+ Copyright (C) 2001-2003 Paul Davis
+ Copyright (C) 2005-2012 Grame
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+ */
+
+#ifndef __jack_shm_h__
+#define __jack_shm_h__
+
+#include <limits.h>
+#include <sys/types.h>
+
+#include <jack/types.h>
+
+#define TRUE 1
+#define FALSE 0
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#define MAX_SERVERS 8 /* maximum concurrent servers */
+#define MAX_SHM_ID 256 /* generally about 16 per server */
+#define JACK_SHM_MAGIC 0x4a41434b /* shm magic number: "JACK" */
+#define JACK_SHM_NULL_INDEX -1 /* NULL SHM index */
+#define JACK_SHM_REGISTRY_INDEX -2 /* pseudo SHM index for registry */
+
+
+ /* On Mac OS X, SHM_NAME_MAX is the maximum length of a shared memory
+ * segment name (instead of NAME_MAX or PATH_MAX as defined by the
+ * standard).
+ */
+#ifdef USE_POSIX_SHM
+
+#ifndef NAME_MAX
+#define NAME_MAX 255
+#endif
+
+#ifndef SHM_NAME_MAX
+#define SHM_NAME_MAX NAME_MAX
+#endif
+ typedef char shm_name_t[SHM_NAME_MAX];
+ typedef shm_name_t jack_shm_id_t;
+
+#elif WIN32
+#define NAME_MAX 255
+#ifndef SHM_NAME_MAX
+#define SHM_NAME_MAX NAME_MAX
+#endif
+ typedef char shm_name_t[SHM_NAME_MAX];
+ typedef shm_name_t jack_shm_id_t;
+
+#elif __ANDROID__
+
+#ifndef NAME_MAX
+#define NAME_MAX 255
+#endif
+
+#ifndef SHM_NAME_MAX
+#define SHM_NAME_MAX NAME_MAX
+#endif
+ typedef char shm_name_t[SHM_NAME_MAX];
+ typedef shm_name_t jack_shm_id_t;
+ typedef int jack_shm_fd_t;
+
+#else
+ /* System V SHM */
+ typedef int jack_shm_id_t;
+#endif /* SHM type */
+
+ /* shared memory type */
+ typedef enum {
+ shm_POSIX = 1, /* POSIX shared memory */
+ shm_SYSV = 2, /* System V shared memory */
+ shm_WIN32 = 3, /* Windows 32 shared memory */
+ shm_ANDROID = 4 /* Android shared memory */
+ } jack_shmtype_t;
+
+ typedef int16_t jack_shm_registry_index_t;
+
+ /**
+ * A structure holding information about shared memory allocated by
+ * JACK. this persists across invocations of JACK, and can be used by
+ * multiple JACK servers. It contains no pointers and is valid across
+ * address spaces.
+ *
+ * The registry consists of two parts: a header including an array of
+ * server names, followed by an array of segment registry entries.
+ */
+ typedef struct _jack_shm_server {
+#ifdef WIN32
+ int pid; /* process ID */
+#else
+ pid_t pid; /* process ID */
+#endif
+
+ char name[JACK_SERVER_NAME_SIZE];
+ }
+ jack_shm_server_t;
+
+ typedef struct _jack_shm_header {
+ uint32_t magic; /* magic number */
+ uint16_t protocol; /* JACK protocol version */
+ jack_shmtype_t type; /* shm type */
+ jack_shmsize_t size; /* total registry segment size */
+ jack_shmsize_t hdr_len; /* size of header */
+ jack_shmsize_t entry_len; /* size of registry entry */
+ jack_shm_server_t server[MAX_SERVERS]; /* current server array */
+ }
+ jack_shm_header_t;
+
+ typedef struct _jack_shm_registry {
+ jack_shm_registry_index_t index; /* offset into the registry */
+
+#ifdef WIN32
+ int allocator; /* PID that created shm segment */
+#else
+ pid_t allocator; /* PID that created shm segment */
+#endif
+
+ jack_shmsize_t size; /* for POSIX unattach */
+ jack_shm_id_t id; /* API specific, see above */
+#ifdef __ANDROID__
+ jack_shm_fd_t fd;
+#endif
+ }
+ jack_shm_registry_t;
+
+#define JACK_SHM_REGISTRY_SIZE (sizeof (jack_shm_header_t) \
+ + sizeof (jack_shm_registry_t) * MAX_SHM_ID)
+
+ /**
+ * a structure holding information about shared memory
+ * allocated by JACK. this version is valid only
+ * for a given address space. It contains a pointer
+ * indicating where the shared memory has been
+ * attached to the address space.
+ */
+
+ PRE_PACKED_STRUCTURE
+ struct _jack_shm_info {
+ jack_shm_registry_index_t index; /* offset into the registry */
+ uint32_t size;
+#ifdef __ANDROID__
+ jack_shm_fd_t fd;
+#endif
+ union {
+ void *attached_at; /* address where attached */
+ char ptr_size[8];
+ } ptr; /* a "pointer" that has the same 8 bytes size when compling in 32 or 64 bits */
+ } POST_PACKED_STRUCTURE;
+
+ typedef struct _jack_shm_info jack_shm_info_t;
+
+ /* utility functions used only within JACK */
+
+ void jack_shm_copy_from_registry (jack_shm_info_t*,
+ jack_shm_registry_index_t);
+ void jack_shm_copy_to_registry (jack_shm_info_t*,
+ jack_shm_registry_index_t*);
+ int jack_release_shm_info (jack_shm_registry_index_t);
+ char* jack_shm_addr (jack_shm_info_t* si);
+
+ // here begin the API
+ int jack_register_server (const char *server_name, int new_registry);
+ int jack_unregister_server (const char *server_name);
+
+ int jack_initialize_shm (const char *server_name);
+ int jack_initialize_shm_server (void);
+ int jack_initialize_shm_client (void);
+ int jack_cleanup_shm (void);
+
+ int jack_shmalloc (const char *shm_name, jack_shmsize_t size,
+ jack_shm_info_t* result);
+ void jack_release_shm (jack_shm_info_t*);
+ void jack_release_lib_shm (jack_shm_info_t*);
+ void jack_destroy_shm (jack_shm_info_t*);
+ int jack_attach_shm (jack_shm_info_t*);
+ int jack_attach_lib_shm (jack_shm_info_t*);
+ int jack_attach_shm_read (jack_shm_info_t*);
+ int jack_attach_lib_shm_read (jack_shm_info_t*);
+ int jack_resize_shm (jack_shm_info_t*, jack_shmsize_t size);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __jack_shm_h__ */
diff --git a/src/modules/module-jack/synchro.h b/src/modules/module-jack/synchro.h
new file mode 100644
index 00000000..e58a368d
--- /dev/null
+++ b/src/modules/module-jack/synchro.h
@@ -0,0 +1,48 @@
+/* PipeWire
+ * Copyright (C) 2017 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+struct jack_synchro {
+ char name[SYNC_MAX_NAME_SIZE];
+ bool flush;
+ sem_t *semaphore;
+};
+
+#define JACK_SYNCHRO_INIT (struct jack_synchro) { { 0, }, false, NULL }
+
+static inline int
+jack_synchro_init(struct jack_synchro *synchro,
+ const char *client_name,
+ const char *server_name,
+ int value, bool internal,
+ bool promiscuous)
+{
+ if (promiscuous)
+ snprintf(synchro->name, sizeof(synchro->name),
+ "jack_sem.%s_%s", server_name, client_name);
+ else
+ snprintf(synchro->name, sizeof(synchro->name),
+ "jack_sem.%d_%s_%s", getuid(), server_name, client_name);
+
+ synchro->flush = false;
+ if ((synchro->semaphore = sem_open(synchro->name, O_CREAT | O_RDWR, 0777, value)) == (sem_t*)SEM_FAILED) {
+ pw_log_error("can't check in named semaphore name = %s err = %s", synchro->name, strerror(errno));
+ return -1;
+ }
+ return 0;
+}
diff --git a/src/modules/module-mixer.c b/src/modules/module-mixer.c
new file mode 100644
index 00000000..6812c2f5
--- /dev/null
+++ b/src/modules/module-mixer.c
@@ -0,0 +1,183 @@
+/* PipeWire
+ * Copyright (C) 2017 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <dlfcn.h>
+
+#include "config.h"
+
+#include "pipewire/core.h"
+#include "pipewire/module.h"
+#include "modules/spa/spa-node.h"
+
+#define AUDIOMIXER_LIB "audiomixer/libspa-audiomixer"
+
+struct impl {
+ struct pw_core *core;
+ struct pw_properties *properties;
+
+ void *hnd;
+ const struct spa_handle_factory *factory;
+};
+
+static const struct spa_handle_factory *find_factory(struct impl *impl)
+{
+ spa_handle_factory_enum_func_t enum_func;
+ uint32_t index;
+ const struct spa_handle_factory *factory = NULL;
+ int res;
+ char *filename;
+ const char *dir;
+
+ if ((dir = getenv("SPA_PLUGIN_DIR")) == NULL)
+ dir = PLUGINDIR;
+
+ asprintf(&filename, "%s/%s.so", dir, AUDIOMIXER_LIB);
+
+ if ((impl->hnd = dlopen(filename, RTLD_NOW)) == NULL) {
+ pw_log_error("can't load %s: %s", AUDIOMIXER_LIB, dlerror());
+ goto open_failed;
+ }
+ if ((enum_func = dlsym(impl->hnd, SPA_HANDLE_FACTORY_ENUM_FUNC_NAME)) == NULL) {
+ pw_log_error("can't find enum function");
+ goto no_symbol;
+ }
+
+ for (index = 0;; index++) {
+ if ((res = enum_func(&factory, index)) < 0) {
+ if (res != SPA_RESULT_ENUM_END)
+ pw_log_error("can't enumerate factories: %d", res);
+ goto enum_failed;
+ }
+ if (strcmp(factory->name, "audiomixer") == 0)
+ break;
+ }
+ free(filename);
+ return factory;
+
+ enum_failed:
+ no_symbol:
+ dlclose(impl->hnd);
+ impl->hnd = NULL;
+ open_failed:
+ free(filename);
+ return NULL;
+}
+
+static struct pw_node *make_node(struct impl *impl)
+{
+ struct spa_handle *handle;
+ int res;
+ void *iface;
+ struct spa_node *spa_node;
+ struct spa_clock *spa_clock;
+ struct pw_node *node;
+
+ handle = calloc(1, impl->factory->size);
+ if ((res = spa_handle_factory_init(impl->factory,
+ handle,
+ NULL, impl->core->support, impl->core->n_support)) < 0) {
+ pw_log_error("can't make factory instance: %d", res);
+ goto init_failed;
+ }
+ if ((res = spa_handle_get_interface(handle, impl->core->type.spa_node, &iface)) < 0) {
+ pw_log_error("can't get interface %d", res);
+ goto interface_failed;
+ }
+ spa_node = iface;
+
+ if ((res = spa_handle_get_interface(handle, impl->core->type.spa_clock, &iface)) < 0) {
+ iface = NULL;
+ }
+ spa_clock = iface;
+
+ node = pw_spa_node_new(impl->core, NULL, "audiomixer", false, spa_node, spa_clock, NULL);
+
+ return node;
+
+ interface_failed:
+ spa_handle_clear(handle);
+ init_failed:
+ free(handle);
+ return NULL;
+}
+
+static struct impl *module_new(struct pw_core *core, struct pw_properties *properties)
+{
+ struct impl *impl;
+ struct pw_node *n;
+
+ impl = calloc(1, sizeof(struct impl));
+ pw_log_debug("module %p: new", impl);
+
+ impl->core = core;
+ impl->properties = properties;
+
+ impl->factory = find_factory(impl);
+
+ spa_list_for_each(n, &core->node_list, link) {
+ const char *str;
+ char *error;
+ struct pw_node *node;
+ struct pw_port *ip, *op;
+
+ if (n->global == NULL)
+ continue;
+
+ if (n->properties == NULL)
+ continue;
+
+ if ((str = pw_properties_get(n->properties, "media.class")) == NULL)
+ continue;
+
+ if (strcmp(str, "Audio/Sink") != 0)
+ continue;
+
+ if ((ip = pw_node_get_free_port(n, PW_DIRECTION_INPUT)) == NULL)
+ continue;
+
+ node = make_node(impl);
+ op = pw_node_get_free_port(node, PW_DIRECTION_OUTPUT);
+ if (op == NULL)
+ continue;
+
+ n->idle_used_input_links++;
+ node->idle_used_output_links++;
+
+ pw_link_new(core, op, ip, NULL, NULL, &error);
+ }
+ return impl;
+}
+
+#if 0
+static void module_destroy(struct impl *impl)
+{
+ pw_log_debug("module %p: destroy", impl);
+
+ free(impl);
+}
+#endif
+
+bool pipewire__module_init(struct pw_module *module, const char *args)
+{
+ module_new(module->core, NULL);
+ return true;
+}
diff --git a/src/modules/module-protocol-dbus.c b/src/modules/module-protocol-dbus.c
new file mode 100644
index 00000000..2134aa04
--- /dev/null
+++ b/src/modules/module-protocol-dbus.c
@@ -0,0 +1,632 @@
+/* PipeWire
+ * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <sys/socket.h>
+
+#include <gio/gio.h>
+#include <gio/gunixfdlist.h>
+
+#include "config.h"
+
+#include "pipewire/client/pipewire.h"
+#include "pipewire/client/log.h"
+
+#include "pipewire/server/core.h"
+#include "pipewire/server/node.h"
+#include "pipewire/server/module.h"
+#include "pipewire/server/client-node.h"
+#include "pipewire/server/client.h"
+#include "pipewire/server/resource.h"
+#include "pipewire/server/link.h"
+#include "pipewire/server/node-factory.h"
+#include "pipewire/server/data-loop.h"
+#include "pipewire/server/main-loop.h"
+
+#include "pipewire/dbus/org-pipewire.h"
+
+#define PIPEWIRE_DBUS_SERVICE "org.pipewire"
+#define PIPEWIRE_DBUS_OBJECT_PREFIX "/org/pipewire"
+#define PIPEWIRE_DBUS_OBJECT_SERVER PIPEWIRE_DBUS_OBJECT_PREFIX "/server"
+#define PIPEWIRE_DBUS_OBJECT_CLIENT PIPEWIRE_DBUS_OBJECT_PREFIX "/client"
+#define PIPEWIRE_DBUS_OBJECT_NODE PIPEWIRE_DBUS_OBJECT_PREFIX "/node"
+#define PIPEWIRE_DBUS_OBJECT_LINK PIPEWIRE_DBUS_OBJECT_PREFIX "/link"
+
+struct impl {
+ struct pw_core *core;
+ struct spa_list link;
+
+ struct pw_properties *properties;
+
+ GDBusConnection *connection;
+ GDBusObjectManagerServer *server_manager;
+
+ struct spa_list client_list;
+ struct spa_list object_list;
+
+ struct pw_listener global_added;
+ struct pw_listener global_removed;
+};
+
+struct object {
+ struct impl *impl;
+ struct spa_list link;
+ struct pw_global *global;
+ void *iface;
+ PipeWireObjectSkeleton *skel;
+ const gchar *object_path;
+ pw_destroy_t destroy;
+};
+
+struct server {
+ struct object parent;
+ struct spa_list link;
+ guint id;
+};
+
+struct client {
+ struct object parent;
+ struct spa_list link;
+ gchar *sender;
+ guint id;
+};
+
+struct node {
+ struct object parent;
+ struct pw_listener state_changed;
+};
+
+static void object_export(struct object *this)
+{
+ g_dbus_object_manager_server_export(this->impl->server_manager,
+ G_DBUS_OBJECT_SKELETON(this->skel));
+ this->object_path = g_dbus_object_get_object_path(G_DBUS_OBJECT(this->skel));
+ pw_log_debug("protocol-dbus %p: export object %s", this->impl, this->object_path);
+}
+
+static void object_unexport(struct object *this)
+{
+ if (this->object_path)
+ g_dbus_object_manager_server_unexport(this->impl->server_manager,
+ this->object_path);
+}
+
+static void *object_new(size_t size,
+ struct impl *impl,
+ struct pw_global *global,
+ void *iface,
+ PipeWireObjectSkeleton * skel, bool export, pw_destroy_t destroy)
+{
+ struct object *this;
+
+ this = calloc(1, size);
+ this->impl = impl;
+ this->global = global;
+ this->iface = iface;
+ this->skel = skel;
+ this->destroy = destroy;
+
+ spa_list_insert(impl->object_list.prev, &this->link);
+
+ if (export)
+ object_export(this);
+
+ return this;
+}
+
+static void object_destroy(struct object *this)
+{
+ spa_list_remove(&this->link);
+
+ if (this->destroy)
+ this->destroy(this);
+
+ object_unexport(this);
+ g_clear_object(&this->iface);
+ g_clear_object(&this->skel);
+ free(this);
+}
+
+static struct object *find_object(struct impl *impl, void *object)
+{
+ struct object *obj;
+ spa_list_for_each(obj, &impl->object_list, link) {
+ if (obj->global->object == object)
+ return obj;
+ }
+ return NULL;
+}
+
+#if 0
+struct _struct pw_properties {
+ GHashTable *hashtable;
+};
+
+static void add_to_variant(const gchar * key, const gchar * value, GVariantBuilder * b)
+{
+ g_variant_builder_add(b, "{sv}", key, g_variant_new_string(value));
+}
+#endif
+
+static void pw_properties_init_builder(struct pw_properties *properties, GVariantBuilder * builder)
+{
+ g_variant_builder_init(builder, G_VARIANT_TYPE("a{sv}"));
+// g_hash_table_foreach (properties->hashtable, (GHFunc) add_to_variant, builder);
+}
+
+static GVariant *pw_properties_to_variant(struct pw_properties *properties)
+{
+ GVariantBuilder builder;
+ pw_properties_init_builder(properties, &builder);
+ return g_variant_builder_end(&builder);
+}
+
+static struct pw_properties *pw_properties_from_variant(GVariant * variant)
+{
+ struct pw_properties *props;
+ GVariantIter iter;
+ //GVariant *value;
+ //gchar *key;
+
+ props = pw_properties_new(NULL, NULL);
+
+ g_variant_iter_init(&iter, variant);
+// while (g_variant_iter_loop (&iter, "{sv}", &key, &value))
+ //g_hash_table_replace (props->hashtable,
+ // g_strdup (key),
+ // g_variant_dup_string (value, NULL));
+
+ return props;
+}
+
+static void
+client_name_appeared_handler(GDBusConnection * connection,
+ const gchar * name, const gchar * name_owner, gpointer user_data)
+{
+ struct client *this = user_data;
+ pw_log_debug("client %p: appeared %s %s", this, name, name_owner);
+ object_export(&this->parent);
+}
+
+static void client_destroy(struct client *this)
+{
+ if (this->sender) {
+ spa_list_remove(&this->link);
+ free(this->sender);
+ }
+}
+
+static void
+client_name_vanished_handler(GDBusConnection * connection, const gchar * name, gpointer user_data)
+{
+ struct client *this = user_data;
+ pw_log_debug("client %p: vanished %s", this, name);
+ g_bus_unwatch_name(this->id);
+ /* destroying the client here will trigger the global_removed, which
+ * will then destroy our wrapper */
+ pw_client_destroy(this->parent.global->object);
+}
+
+
+static struct client *client_new(struct impl *impl, const char *sender)
+{
+ struct client *this;
+ struct pw_client *client;
+
+ client = pw_client_new(impl->core, NULL, NULL);
+
+ if ((this = (struct client *) find_object(impl, client))) {
+ pipewire_client1_set_sender(this->parent.iface, sender);
+
+ this->sender = strdup(sender);
+ this->id = g_bus_watch_name_on_connection(impl->connection,
+ this->sender,
+ G_BUS_NAME_WATCHER_FLAGS_NONE,
+ client_name_appeared_handler,
+ client_name_vanished_handler, this, NULL);
+
+ spa_list_insert(impl->client_list.prev, &this->link);
+ }
+ return this;
+}
+
+static struct pw_client *sender_get_client(struct impl *impl, const char *sender, bool create)
+{
+ struct client *client;
+
+ spa_list_for_each(client, &impl->client_list, link) {
+ if (strcmp(client->sender, sender) == 0)
+ return client->parent.global->object;
+ }
+ if (!create)
+ return NULL;
+
+ client = client_new(impl, sender);
+
+ return client->parent.global->object;
+}
+
+static bool
+handle_create_node(PipeWireDaemon1 * interface,
+ GDBusMethodInvocation * invocation,
+ const char *arg_factory_name,
+ const char *arg_name, GVariant * arg_properties, gpointer user_data)
+{
+ struct impl *impl = user_data;
+ struct pw_node_factory *factory;
+ struct pw_node *node;
+ struct pw_client *client;
+ const char *sender, *object_path;
+ struct pw_properties *props;
+ struct object *object;
+
+ sender = g_dbus_method_invocation_get_sender(invocation);
+ client = sender_get_client(impl, sender, TRUE);
+
+ pw_log_debug("protocol-dbus %p: create node: %s", impl, sender);
+
+ props = pw_properties_from_variant(arg_properties);
+
+ factory = pw_core_find_node_factory(impl->core, arg_factory_name);
+ if (factory == NULL)
+ goto no_factory;
+
+ node = pw_node_factory_create_node(factory, client, arg_name, props);
+ pw_properties_free(props);
+
+ if (node == NULL)
+ goto no_node;
+
+ object = find_object(impl, node);
+ if (object == NULL)
+ goto object_failed;
+
+ pw_resource_new(client,
+ SPA_ID_INVALID,
+ impl->core->type.node, node, (pw_destroy_t) pw_node_destroy);
+
+ object_path = object->object_path;
+ pw_log_debug("protocol-dbus %p: added node %p with path %s", impl, node, object_path);
+ g_dbus_method_invocation_return_value(invocation, g_variant_new("(o)", object_path));
+ return TRUE;
+
+ /* ERRORS */
+ no_factory:
+ pw_log_debug("protocol-dbus %p: could not find factory named %s", impl,
+ arg_factory_name);
+ g_dbus_method_invocation_return_dbus_error(invocation, "org.pipewire.Error",
+ "can't find factory");
+ return TRUE;
+ no_node:
+ pw_log_debug("protocol-dbus %p: could not create node named %s from factory %s",
+ impl, arg_name, arg_factory_name);
+ g_dbus_method_invocation_return_dbus_error(invocation, "org.pipewire.Error",
+ "can't create node");
+ return TRUE;
+ object_failed:
+ pw_log_debug("protocol-dbus %p: could not create dbus object", impl);
+ g_dbus_method_invocation_return_dbus_error(invocation,
+ "org.pipewire.Error",
+ "can't create object");
+ return TRUE;
+}
+
+static void
+on_node_state_changed(struct pw_listener *listener,
+ struct pw_node *node, enum pw_node_state old, enum pw_node_state state)
+{
+ struct node *object = SPA_CONTAINER_OF(listener, struct node, state_changed);
+
+ pw_log_debug("protocol-dbus %p: node %p state change %s -> %s", object->parent.impl, node,
+ pw_node_state_as_string(old), pw_node_state_as_string(state));
+
+ pipewire_node1_set_state(object->parent.iface, node->state);
+}
+
+static bool
+handle_create_client_node(PipeWireDaemon1 * interface,
+ GDBusMethodInvocation * invocation,
+ const char *arg_name, GVariant * arg_properties, gpointer user_data)
+{
+ struct impl *impl = user_data;
+ struct pw_client_node *node;
+ struct pw_client *client;
+ int res;
+ const char *sender, *object_path, *target_node;
+ struct pw_properties *props;
+ GError *error = NULL;
+ GUnixFDList *fdlist;
+ int ctrl_fd, data_rfd, data_wfd;
+ int ctrl_idx, data_ridx, data_widx;
+ struct object *object;
+ int fd[2];
+
+ sender = g_dbus_method_invocation_get_sender(invocation);
+ client = sender_get_client(impl, sender, TRUE);
+
+ pw_log_debug("protocol-dbus %p: create client-node: %s", impl, sender);
+ props = pw_properties_from_variant(arg_properties);
+
+ target_node = pw_properties_get(props, "pipewire.target.node");
+ if (target_node) {
+ if (strncmp(target_node, "/org/pipewire/node_", strlen("/org/pipewire/node_")) == 0) {
+ pw_properties_setf(props, "pipewire.target.node", "%s",
+ target_node + strlen("/org/pipewire/node_"));
+ }
+ }
+
+ if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, fd) != 0)
+ goto no_socket_pair;
+
+ ctrl_fd = fd[1];
+
+ node = pw_client_node_new(client, SPA_ID_INVALID, arg_name, props);
+
+ object = find_object(impl, node->node);
+ if (object == NULL)
+ goto object_failed;
+
+ if ((res = pw_client_node_get_fds(node, &data_rfd, &data_wfd)) < 0)
+ goto no_socket;
+
+ object_path = object->object_path;
+ pw_log_debug("protocol-dbus %p: add client-node %p, %s", impl, node, object_path);
+
+ fdlist = g_unix_fd_list_new();
+ ctrl_idx = g_unix_fd_list_append(fdlist, ctrl_fd, &error);
+ data_ridx = g_unix_fd_list_append(fdlist, data_rfd, &error);
+ data_widx = g_unix_fd_list_append(fdlist, data_wfd, &error);
+
+ g_dbus_method_invocation_return_value_with_unix_fd_list(invocation,
+ g_variant_new("(ohhh)", object_path,
+ ctrl_idx, data_ridx,
+ data_widx), fdlist);
+ g_object_unref(fdlist);
+
+ return TRUE;
+
+ object_failed:
+ pw_log_debug("protocol-dbus %p: could not create object", impl);
+ goto exit_error;
+ no_socket_pair:
+ pw_log_debug("protocol-dbus %p: could not create socketpair: %s", impl,
+ strerror(errno));
+ goto exit_error;
+ no_socket:
+ pw_log_debug("protocol-dbus %p: could not create socket: %s", impl,
+ strerror(errno));
+ pw_client_node_destroy(node);
+ goto exit_error;
+ exit_error:
+ g_dbus_method_invocation_return_gerror(invocation, error);
+ g_clear_error(&error);
+ return TRUE;
+}
+
+static void bus_acquired_handler(GDBusConnection * connection, const char *name, gpointer user_data)
+{
+ struct impl *impl = user_data;
+ GDBusObjectManagerServer *manager = impl->server_manager;
+
+ impl->connection = connection;
+ g_dbus_object_manager_server_set_connection(manager, connection);
+}
+
+static void
+name_acquired_handler(GDBusConnection * connection, const char *name, gpointer user_data)
+{
+}
+
+static void name_lost_handler(GDBusConnection * connection, const char *name, gpointer user_data)
+{
+ struct impl *impl = user_data;
+ GDBusObjectManagerServer *manager = impl->server_manager;
+
+ g_dbus_object_manager_server_set_connection(manager, connection);
+ impl->connection = connection;
+}
+
+static bool
+handle_node_remove(PipeWireNode1 * interface,
+ GDBusMethodInvocation * invocation, gpointer user_data)
+{
+ struct pw_node *this = user_data;
+
+ pw_log_debug("node %p: remove", this);
+
+ g_dbus_method_invocation_return_value(invocation, g_variant_new("()"));
+ return true;
+}
+
+static void
+on_global_added(struct pw_listener *listener, struct pw_core *core, struct pw_global *global)
+{
+ struct impl *impl = SPA_CONTAINER_OF(listener, struct impl, global_added);
+ PipeWireObjectSkeleton *skel;
+
+ if (global->type == impl->core->type.client) {
+ PipeWireClient1 *iface;
+ struct pw_client *client = global->object;
+ struct pw_properties *props = client->properties;
+ char *path;
+
+ asprintf(&path, "%s_%u", PIPEWIRE_DBUS_OBJECT_CLIENT, global->id);
+ skel = pipewire_object_skeleton_new(path);
+ free(path);
+
+ iface = pipewire_client1_skeleton_new();
+ pipewire_client1_set_properties(iface,
+ props ? pw_properties_to_variant(props) : NULL);
+ pipewire_object_skeleton_set_client1(skel, iface);
+
+ object_new(sizeof(struct client),
+ impl, global, iface, skel, false, (pw_destroy_t) client_destroy);
+
+ } else if (global->type == impl->core->type.node) {
+ PipeWireNode1 *iface;
+ struct pw_node *node = global->object;
+ struct pw_properties *props = node->properties;
+ char *path;
+ struct node *obj;
+
+ asprintf(&path, "%s_%u", PIPEWIRE_DBUS_OBJECT_NODE, global->id);
+ skel = pipewire_object_skeleton_new(path);
+ free(path);
+
+ iface = pipewire_node1_skeleton_new();
+ g_signal_connect(iface, "handle-remove", (GCallback) handle_node_remove, node);
+ pipewire_node1_set_state(iface, node->state);
+ pipewire_node1_set_owner(iface, "/");
+ pipewire_node1_set_name(iface, node->name);
+ pipewire_node1_set_properties(iface,
+ props ? pw_properties_to_variant(props) : NULL);
+ pipewire_object_skeleton_set_node1(skel, iface);
+
+ obj = object_new(sizeof(struct node), impl, global, iface, skel, true, NULL);
+ pw_signal_add(&node->state_changed, &obj->state_changed, on_node_state_changed);
+ } else if (global->object == impl) {
+ struct impl *proto = global->object;
+ struct server *server;
+ PipeWireDaemon1 *iface;
+ char *path;
+
+ iface = pipewire_daemon1_skeleton_new();
+ g_signal_connect(iface, "handle-create-node", (GCallback) handle_create_node,
+ proto);
+ g_signal_connect(iface, "handle-create-client-node",
+ (GCallback) handle_create_client_node, proto);
+
+ asprintf(&path, "%s_%u", PIPEWIRE_DBUS_OBJECT_SERVER, global->id);
+ skel = pipewire_object_skeleton_new(path);
+ free(path);
+
+ pipewire_daemon1_set_user_name(iface, g_get_user_name());
+ pipewire_daemon1_set_host_name(iface, g_get_host_name());
+ pipewire_daemon1_set_version(iface, PACKAGE_VERSION);
+ pipewire_daemon1_set_name(iface, PACKAGE_NAME);
+ pipewire_daemon1_set_cookie(iface, g_random_int());
+ pipewire_daemon1_set_properties(iface, proto->properties ?
+ pw_properties_to_variant(proto->properties) : NULL);
+ pipewire_object_skeleton_set_daemon1(skel, iface);
+
+ server = object_new(sizeof(struct server), impl, global, iface, skel, true, NULL);
+ server->id = g_bus_own_name(G_BUS_TYPE_SESSION,
+ PIPEWIRE_DBUS_SERVICE,
+ G_BUS_NAME_OWNER_FLAGS_REPLACE,
+ bus_acquired_handler,
+ name_acquired_handler, name_lost_handler, proto, NULL);
+ } else if (global->type == impl->core->type.link) {
+ PipeWireLink1 *iface;
+ struct pw_link *link = global->object;
+ struct object *obj;
+ char *path;
+
+ asprintf(&path, "%s_%u", PIPEWIRE_DBUS_OBJECT_LINK, global->id);
+ skel = pipewire_object_skeleton_new(path);
+ free(path);
+
+ iface = pipewire_link1_skeleton_new();
+
+ obj = link->output ? find_object(impl, link->output->node) : NULL;
+ if (obj) {
+ pipewire_link1_set_output_node(iface, obj->object_path);
+ pipewire_link1_set_output_port(iface, link->output->port_id);
+ } else {
+ pipewire_link1_set_output_node(iface, "/");
+ pipewire_link1_set_output_port(iface, SPA_ID_INVALID);
+ }
+ obj = link->input ? find_object(impl, link->input->node) : NULL;
+ if (obj) {
+ pipewire_link1_set_input_node(iface, obj->object_path);
+ pipewire_link1_set_input_port(iface, link->input->port_id);
+ } else {
+ pipewire_link1_set_output_node(iface, "/");
+ pipewire_link1_set_output_port(iface, SPA_ID_INVALID);
+ }
+ pipewire_object_skeleton_set_link1(skel, iface);
+
+ object_new(sizeof(struct object), impl, global, iface, skel, true, NULL);
+ }
+}
+
+static void
+on_global_removed(struct pw_listener *listener, struct pw_core *core, struct pw_global *global)
+{
+ struct impl *impl = SPA_CONTAINER_OF(listener, struct impl, global_removed);
+ struct object *object;
+
+ if ((object = find_object(impl, global->object)))
+ object_destroy(object);
+}
+
+static struct impl *pw_protocol_dbus_new(struct pw_core *core, struct pw_properties *properties)
+{
+ struct impl *impl;
+
+ impl = calloc(1, sizeof(struct impl));
+ pw_log_debug("protocol-dbus %p: new", impl);
+
+ impl->core = core;
+ impl->properties = properties;
+
+ spa_list_init(&impl->client_list);
+ spa_list_init(&impl->object_list);
+
+ pw_signal_add(&core->global_added, &impl->global_added, on_global_added);
+ pw_signal_add(&core->global_removed, &impl->global_removed, on_global_removed);
+
+ impl->server_manager = g_dbus_object_manager_server_new(PIPEWIRE_DBUS_OBJECT_PREFIX);
+
+ return impl;
+}
+
+#if 0
+static void pw_protocol_dbus_destroy(struct impl *impl)
+{
+ struct object *object, *tmp;
+
+ pw_log_debug("protocol-dbus %p: destroy", impl);
+
+ pw_global_destroy(impl->global);
+
+ spa_list_for_each_safe(object, tmp, &impl->object_list, link)
+ object_destroy(object);
+
+#if 0
+ if (impl->id != 0)
+ g_bus_unown_name(impl->id);
+#endif
+
+ pw_signal_remove(&impl->global_added);
+ pw_signal_remove(&impl->global_removed);
+ pw_signal_remove(&impl->node_state_changed);
+
+ g_clear_object(&impl->server_manager);
+
+ free(impl);
+}
+#endif
+
+bool pipewire__module_init(struct pw_module *module, const char *args)
+{
+ pw_protocol_dbus_new(module->core, NULL);
+ return TRUE;
+}
diff --git a/src/modules/module-protocol-native.c b/src/modules/module-protocol-native.c
new file mode 100644
index 00000000..6e9b5d20
--- /dev/null
+++ b/src/modules/module-protocol-native.c
@@ -0,0 +1,684 @@
+/* PipeWire
+ * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/file.h>
+
+#include "config.h"
+
+#include "pipewire/pipewire.h"
+#include "pipewire/log.h"
+#include "pipewire/interfaces.h"
+#include "pipewire/core.h"
+#include "pipewire/node.h"
+#include "pipewire/module.h"
+#include "pipewire/client.h"
+#include "pipewire/resource.h"
+#include "pipewire/link.h"
+#include "pipewire/node-factory.h"
+#include "pipewire/data-loop.h"
+#include "pipewire/main-loop.h"
+
+#include "modules/module-protocol-native/connection.h"
+
+#ifndef UNIX_PATH_MAX
+#define UNIX_PATH_MAX 108
+#endif
+
+#define LOCK_SUFFIX ".lock"
+#define LOCK_SUFFIXLEN 5
+
+struct pw_protocol *pw_protocol_native_init(void);
+
+typedef bool(*demarshal_func_t) (void *object, void *data, size_t size);
+
+struct connection {
+ struct pw_protocol_connection this;
+
+ int fd;
+
+ struct spa_source *source;
+ struct pw_connection *connection;
+
+ bool disconnecting;
+ struct pw_listener need_flush;
+ struct spa_source *flush_event;
+};
+
+struct listener {
+ struct pw_protocol_listener this;
+
+ int fd;
+ int fd_lock;
+ struct sockaddr_un addr;
+ char lock_addr[UNIX_PATH_MAX + LOCK_SUFFIXLEN];
+
+ struct pw_loop *loop;
+ struct spa_source *source;
+};
+
+struct impl {
+ struct pw_core *core;
+ struct spa_list link;
+
+ struct pw_protocol *protocol;
+ struct pw_properties *properties;
+
+ struct spa_list client_list;
+
+ struct spa_loop_control_hooks hooks;
+};
+
+struct native_client {
+ struct impl *impl;
+ struct spa_list link;
+ struct pw_client *client;
+ int fd;
+ struct spa_source *source;
+ struct pw_connection *connection;
+ struct pw_listener busy_changed;
+};
+
+static void client_destroy(void *data)
+{
+ struct pw_client *client = data;
+ struct native_client *this = client->user_data;
+
+ pw_loop_destroy_source(this->impl->core->main_loop, this->source);
+ spa_list_remove(&this->link);
+
+ pw_connection_destroy(this->connection);
+ close(this->fd);
+}
+
+static void
+process_messages(struct native_client *client)
+{
+ struct pw_connection *conn = client->connection;
+ uint8_t opcode;
+ uint32_t id;
+ uint32_t size;
+ struct pw_client *c = client->client;
+ void *message;
+
+ while (pw_connection_get_next(conn, &opcode, &id, &message, &size)) {
+ struct pw_resource *resource;
+ const demarshal_func_t *demarshal;
+
+ pw_log_trace("protocol-native %p: got message %d from %u", client->impl,
+ opcode, id);
+
+ resource = pw_map_lookup(&c->objects, id);
+ if (resource == NULL) {
+ pw_log_error("protocol-native %p: unknown resource %u",
+ client->impl, id);
+ continue;
+ }
+ if (opcode >= resource->iface->n_methods) {
+ pw_log_error("protocol-native %p: invalid method %u %u", client->impl,
+ id, opcode);
+ pw_client_destroy(c);
+ break;
+ }
+ demarshal = resource->iface->methods;
+ if (!demarshal[opcode] || !demarshal[opcode] (resource, message, size)) {
+ pw_log_error("protocol-native %p: invalid message received %u %u",
+ client->impl, id, opcode);
+ pw_client_destroy(c);
+ break;
+ }
+ if (c->busy) {
+ break;
+ }
+ }
+}
+
+static void
+on_busy_changed(struct pw_listener *listener,
+ struct pw_client *client)
+{
+ struct native_client *c = SPA_CONTAINER_OF(listener, struct native_client, busy_changed);
+ enum spa_io mask = SPA_IO_ERR | SPA_IO_HUP;
+
+ if (!client->busy)
+ mask |= SPA_IO_IN;
+
+ pw_loop_update_io(c->impl->core->main_loop, c->source, mask);
+
+ if (!client->busy)
+ process_messages(c);
+
+}
+
+static void on_before_hook(const struct spa_loop_control_hooks *hooks)
+{
+ struct impl *this = SPA_CONTAINER_OF(hooks, struct impl, hooks);
+ struct native_client *client, *tmp;
+
+ spa_list_for_each_safe(client, tmp, &this->client_list, link)
+ pw_connection_flush(client->connection);
+}
+
+static void
+connection_data(struct spa_loop_utils *utils,
+ struct spa_source *source, int fd, enum spa_io mask, void *data)
+{
+ struct native_client *client = data;
+
+ if (mask & (SPA_IO_ERR | SPA_IO_HUP)) {
+ pw_log_error("protocol-native %p: got connection error", client->impl);
+ pw_client_destroy(client->client);
+ return;
+ }
+
+ if (mask & SPA_IO_IN)
+ process_messages(client);
+}
+
+static struct native_client *client_new(struct impl *impl, int fd)
+{
+ struct native_client *this;
+ struct pw_client *client;
+ socklen_t len;
+ struct ucred ucred, *ucredp;
+
+ len = sizeof(ucred);
+ if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &ucred, &len) < 0) {
+ pw_log_error("no peercred: %m");
+ ucredp = NULL;
+ } else {
+ ucredp = &ucred;
+ }
+
+ client = pw_client_new(impl->core, ucredp, NULL, sizeof(struct native_client));
+ if (client == NULL)
+ goto no_client;
+
+ client->destroy = client_destroy;
+
+ this = client->user_data;
+ this->impl = impl;
+ this->fd = fd;
+ this->source = pw_loop_add_io(impl->core->main_loop,
+ this->fd,
+ SPA_IO_ERR | SPA_IO_HUP, false, connection_data, this);
+ if (this->source == NULL)
+ goto no_source;
+
+ this->connection = pw_connection_new(fd);
+ if (this->connection == NULL)
+ goto no_connection;
+
+ client->protocol = impl->protocol;
+ client->protocol_private = this->connection;
+
+ this->client = client;
+
+ spa_list_insert(impl->client_list.prev, &this->link);
+
+ pw_signal_add(&client->busy_changed, &this->busy_changed, on_busy_changed);
+
+ pw_global_bind(impl->core->global, client, 0, 0);
+
+ return this;
+
+ no_connection:
+ pw_loop_destroy_source(impl->core->main_loop, this->source);
+ no_source:
+ free(this);
+ no_client:
+ return NULL;
+}
+
+static void destroy_listener(struct listener *l)
+{
+ if (l->source)
+ pw_loop_destroy_source(l->loop, l->source);
+ if (l->addr.sun_path[0])
+ unlink(l->addr.sun_path);
+ if (l->fd >= 0)
+ close(l->fd);
+ if (l->lock_addr[0])
+ unlink(l->lock_addr);
+ if (l->fd_lock >= 0)
+ close(l->fd_lock);
+ free(l);
+}
+
+static bool init_socket_name(struct listener *l, const char *name)
+{
+ int name_size;
+ const char *runtime_dir;
+
+ if ((runtime_dir = getenv("XDG_RUNTIME_DIR")) == NULL) {
+ pw_log_error("XDG_RUNTIME_DIR not set in the environment");
+ return false;
+ }
+
+ l->addr.sun_family = AF_LOCAL;
+ name_size = snprintf(l->addr.sun_path, sizeof(l->addr.sun_path),
+ "%s/%s", runtime_dir, name) + 1;
+
+ if (name_size > (int) sizeof(l->addr.sun_path)) {
+ pw_log_error("socket path \"%s/%s\" plus null terminator exceeds 108 bytes",
+ runtime_dir, name);
+ *l->addr.sun_path = 0;
+ return false;
+ }
+ return true;
+}
+
+static bool lock_socket(struct listener *l)
+{
+ struct stat socket_stat;
+
+ snprintf(l->lock_addr, sizeof(l->lock_addr), "%s%s", l->addr.sun_path, LOCK_SUFFIX);
+
+ l->fd_lock = open(l->lock_addr, O_CREAT | O_CLOEXEC,
+ (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP));
+
+ if (l->fd_lock < 0) {
+ pw_log_error("unable to open lockfile %s check permissions", l->lock_addr);
+ goto err;
+ }
+
+ if (flock(l->fd_lock, LOCK_EX | LOCK_NB) < 0) {
+ pw_log_error("unable to lock lockfile %s, maybe another daemon is running",
+ l->lock_addr);
+ goto err_fd;
+ }
+
+ if (stat(l->addr.sun_path, &socket_stat) < 0) {
+ if (errno != ENOENT) {
+ pw_log_error("did not manage to stat file %s\n", l->addr.sun_path);
+ goto err_fd;
+ }
+ } else if (socket_stat.st_mode & S_IWUSR || socket_stat.st_mode & S_IWGRP) {
+ unlink(l->addr.sun_path);
+ }
+ return true;
+
+ err_fd:
+ close(l->fd_lock);
+ l->fd_lock = -1;
+ err:
+ *l->lock_addr = 0;
+ *l->addr.sun_path = 0;
+ return false;
+}
+
+static void
+socket_data(struct spa_loop_utils *utils,
+ struct spa_source *source, int fd, enum spa_io mask, void *data)
+{
+ struct impl *impl = data;
+ struct native_client *client;
+ struct sockaddr_un name;
+ socklen_t length;
+ int client_fd;
+
+ length = sizeof(name);
+ client_fd = accept4(fd, (struct sockaddr *) &name, &length, SOCK_CLOEXEC);
+ if (client_fd < 0) {
+ pw_log_error("failed to accept: %m");
+ return;
+ }
+
+ client = client_new(impl, client_fd);
+ if (client == NULL) {
+ pw_log_error("failed to create client");
+ close(client_fd);
+ return;
+ }
+
+ pw_loop_update_io(impl->core->main_loop,
+ client->source, SPA_IO_IN | SPA_IO_ERR | SPA_IO_HUP);
+}
+
+static bool add_socket(struct impl *impl, struct listener *l)
+{
+ socklen_t size;
+
+ if ((l->fd = socket(PF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)) < 0)
+ return false;
+
+ size = offsetof(struct sockaddr_un, sun_path) + strlen(l->addr.sun_path);
+ if (bind(l->fd, (struct sockaddr *) &l->addr, size) < 0) {
+ pw_log_error("bind() failed with error: %m");
+ return false;
+ }
+
+ if (listen(l->fd, 128) < 0) {
+ pw_log_error("listen() failed with error: %m");
+ return false;
+ }
+
+ l->loop = impl->core->main_loop;
+ l->source = pw_loop_add_io(l->loop, l->fd, SPA_IO_IN, false, socket_data, impl);
+ if (l->source == NULL)
+ return false;
+
+ return true;
+}
+
+static const char *
+get_name(struct pw_properties *properties)
+{
+ const char *name = NULL;
+
+ if (properties)
+ name = pw_properties_get(properties, "pipewire.core.name");
+ if (name == NULL)
+ name = getenv("PIPEWIRE_CORE");
+ if (name == NULL)
+ name = "pipewire-0";
+
+ return name;
+}
+
+static int impl_connect(struct pw_protocol_connection *conn)
+{
+ struct sockaddr_un addr;
+ socklen_t size;
+ const char *runtime_dir, *name = NULL;
+ int name_size, fd;
+
+ if ((runtime_dir = getenv("XDG_RUNTIME_DIR")) == NULL) {
+ pw_log_error("connect failed: XDG_RUNTIME_DIR not set in the environment");
+ return -1;
+ }
+
+ if (name == NULL)
+ name = getenv("PIPEWIRE_CORE");
+ if (name == NULL)
+ name = "pipewire-0";
+
+ if ((fd = socket(PF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)) < 0)
+ return -1;
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sun_family = AF_LOCAL;
+ name_size = snprintf(addr.sun_path, sizeof(addr.sun_path), "%s/%s", runtime_dir, name) + 1;
+
+ if (name_size > (int) sizeof addr.sun_path) {
+ pw_log_error("socket path \"%s/%s\" plus null terminator exceeds 108 bytes",
+ runtime_dir, name);
+ goto error_close;
+ };
+
+ size = offsetof(struct sockaddr_un, sun_path) + name_size;
+
+ if (connect(fd, (struct sockaddr *) &addr, size) < 0) {
+ goto error_close;
+ }
+
+ return conn->connect_fd(conn, fd);
+
+ error_close:
+ close(fd);
+ return -1;
+}
+
+
+static void
+on_remote_data(struct spa_loop_utils *utils,
+ struct spa_source *source, int fd, enum spa_io mask, void *data)
+{
+ struct connection *impl = data;
+ struct pw_remote *this = impl->this.remote;
+ struct pw_connection *conn = impl->connection;
+
+ if (mask & (SPA_IO_ERR | SPA_IO_HUP)) {
+ return;
+ }
+
+ if (mask & SPA_IO_IN) {
+ uint8_t opcode;
+ uint32_t id;
+ uint32_t size;
+ void *message;
+
+ while (!impl->disconnecting
+ && pw_connection_get_next(conn, &opcode, &id, &message, &size)) {
+ struct pw_proxy *proxy;
+ const demarshal_func_t *demarshal;
+
+ pw_log_trace("protocol-native %p: got message %d from %u", this, opcode, id);
+
+ proxy = pw_map_lookup(&this->objects, id);
+ if (proxy == NULL) {
+ pw_log_error("protocol-native %p: could not find proxy %u", this, id);
+ continue;
+ }
+ if (opcode >= proxy->iface->n_events) {
+ pw_log_error("protocol-native %p: invalid method %u for %u", this, opcode,
+ id);
+ continue;
+ }
+
+ demarshal = proxy->iface->events;
+ if (demarshal[opcode]) {
+ if (!demarshal[opcode] (proxy, message, size))
+ pw_log_error
+ ("protocol-native %p: invalid message received %u for %u", this,
+ opcode, id);
+ } else
+ pw_log_error("protocol-native %p: function %d not implemented on %u", this,
+ opcode, id);
+
+ }
+ }
+}
+
+
+static void do_flush_event(struct spa_loop_utils *utils, struct spa_source *source, void *data)
+{
+ struct connection *impl = data;
+ if (impl->connection)
+ if (!pw_connection_flush(impl->connection))
+ impl->this.disconnect(&impl->this);
+}
+
+static void on_need_flush(struct pw_listener *listener, struct pw_connection *connection)
+{
+ struct connection *impl = SPA_CONTAINER_OF(listener, struct connection, need_flush);
+ struct pw_remote *remote = impl->this.remote;
+ pw_loop_signal_event(remote->core->main_loop, impl->flush_event);
+}
+
+static int impl_connect_fd(struct pw_protocol_connection *conn, int fd)
+{
+ struct connection *impl = SPA_CONTAINER_OF(conn, struct connection, this);
+ struct pw_remote *remote = impl->this.remote;
+
+ impl->connection = pw_connection_new(fd);
+ if (impl->connection == NULL)
+ goto error_close;
+
+ conn->remote->protocol_private = impl->connection;
+
+ pw_signal_add(&impl->connection->need_flush, &impl->need_flush, on_need_flush);
+
+ impl->fd = fd;
+ impl->source = pw_loop_add_io(remote->core->main_loop,
+ fd,
+ SPA_IO_IN | SPA_IO_HUP | SPA_IO_ERR,
+ false, on_remote_data, impl);
+
+ return 0;
+
+ error_close:
+ close(fd);
+ return -1;
+}
+
+static int impl_disconnect(struct pw_protocol_connection *conn)
+{
+ struct connection *impl = SPA_CONTAINER_OF(conn, struct connection, this);
+ struct pw_remote *remote = impl->this.remote;
+
+ impl->disconnecting = true;
+
+ if (impl->source)
+ pw_loop_destroy_source(remote->core->main_loop, impl->source);
+ impl->source = NULL;
+
+ if (impl->connection)
+ pw_connection_destroy(impl->connection);
+ impl->connection = NULL;
+
+ if (impl->fd != -1)
+ close(impl->fd);
+ impl->fd = -1;
+
+ return 0;
+}
+
+static int impl_destroy(struct pw_protocol_connection *conn)
+{
+ struct connection *impl = SPA_CONTAINER_OF(conn, struct connection, this);
+ struct pw_remote *remote = conn->remote;
+
+ pw_loop_destroy_source(remote->core->main_loop, impl->flush_event);
+
+ spa_list_remove(&conn->link);
+ free(impl);
+
+ return 0;
+}
+
+static struct pw_protocol_connection *
+impl_new_connection(struct pw_protocol *protocol,
+ struct pw_remote *remote,
+ struct pw_properties *properties)
+{
+ struct impl *impl = protocol->protocol_private;
+ struct connection *c;
+ struct pw_protocol_connection *this;
+
+ if ((c = calloc(1, sizeof(struct connection))) == NULL)
+ return NULL;
+
+ this = &c->this;
+ this->remote = remote;
+
+ this->connect = impl_connect;
+ this->connect_fd = impl_connect_fd;
+ this->disconnect = impl_disconnect;
+ this->destroy = impl_destroy;
+
+ c->flush_event = pw_loop_add_event(remote->core->main_loop, do_flush_event, c);
+
+ spa_list_insert(impl->protocol->connection_list.prev, &c->this.link);
+
+ return this;
+}
+
+static struct pw_protocol_listener *
+impl_add_listener(struct pw_protocol *protocol,
+ struct pw_core *core,
+ struct pw_properties *properties)
+{
+ struct impl *impl = protocol->protocol_private;
+ struct listener *l;
+ const char *name;
+
+ if ((l = calloc(1, sizeof(struct listener))) == NULL)
+ return NULL;
+
+ l->fd = -1;
+ l->fd_lock = -1;
+
+ name = get_name(properties);
+
+ if (!init_socket_name(l, name))
+ goto error;
+
+ if (!lock_socket(l))
+ goto error;
+
+ if (!add_socket(impl, l))
+ goto error;
+
+ spa_list_insert(impl->protocol->listener_list.prev, &l->this.link);
+
+ impl->hooks.before = on_before_hook;
+ pw_loop_add_hooks(impl->core->main_loop, &impl->hooks);
+
+ pw_log_info("protocol-native %p: Added listener", protocol);
+
+ return &l->this;
+
+ error:
+ destroy_listener(l);
+ return NULL;
+}
+
+
+static struct impl *pw_protocol_native_new(struct pw_core *core, struct pw_properties *properties)
+{
+ struct impl *impl;
+
+ impl = calloc(1, sizeof(struct impl));
+
+ impl->core = core;
+ impl->properties = properties;
+ impl->protocol = pw_protocol_native_init();
+ impl->protocol->new_connection = impl_new_connection;
+ impl->protocol->add_listener = impl_add_listener;
+ impl->protocol->protocol_private = impl;
+ pw_log_debug("protocol-native %p: new %p", impl, impl->protocol);
+
+ spa_list_init(&impl->client_list);
+
+ impl_add_listener(impl->protocol, core, properties);
+
+ return impl;
+}
+
+#if 0
+static void pw_protocol_native_destroy(struct impl *impl)
+{
+ struct impl *object, *tmp;
+
+ pw_log_debug("protocol-native %p: destroy", impl);
+
+ pw_signal_remove(&impl->before_iterate);
+
+ pw_global_destroy(impl->global);
+
+ spa_list_for_each_safe(object, tmp, &impl->object_list, link)
+ object_destroy(object);
+
+ free(impl);
+}
+#endif
+
+bool pipewire__module_init(struct pw_module *module, const char *args)
+{
+ pw_protocol_native_new(module->core, NULL);
+ return true;
+}
diff --git a/src/modules/module-protocol-native/connection.c b/src/modules/module-protocol-native/connection.c
new file mode 100644
index 00000000..073cc836
--- /dev/null
+++ b/src/modules/module-protocol-native/connection.c
@@ -0,0 +1,529 @@
+/* PipeWire
+ * Copyright (C) 2016 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <stdint.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <sys/socket.h>
+
+#include <spa/lib/debug.h>
+
+#include <pipewire/pipewire.h>
+
+#include "connection.h"
+
+/** \cond */
+
+#define MAX_BUFFER_SIZE (1024 * 32)
+#define MAX_FDS 28
+
+static bool debug_messages = 0;
+
+struct buffer {
+ uint8_t *buffer_data;
+ size_t buffer_size;
+ size_t buffer_maxsize;
+ int fds[MAX_FDS];
+ uint32_t n_fds;
+
+ off_t offset;
+ void *data;
+ size_t size;
+
+ bool update;
+};
+
+struct impl {
+ struct pw_connection this;
+
+ struct buffer in, out;
+
+ uint32_t dest_id;
+ uint8_t opcode;
+ struct spa_pod_builder builder;
+};
+
+/** \endcond */
+
+/** Get an fd from a connection
+ *
+ * \param conn the connection
+ * \param index the index of the fd to get
+ * \return the fd at \a index or -1 when no such fd exists
+ *
+ * \memberof pw_connection
+ */
+int pw_connection_get_fd(struct pw_connection *conn, uint32_t index)
+{
+ struct impl *impl = SPA_CONTAINER_OF(conn, struct impl, this);
+
+ if (index < 0 || index >= impl->in.n_fds)
+ return -1;
+
+ return impl->in.fds[index];
+}
+
+/** Add an fd to a connection
+ *
+ * \param conn the connection
+ * \param fd the fd to add
+ * \return the index of the fd or -1 when an error occured
+ *
+ * \memberof pw_connection
+ */
+uint32_t pw_connection_add_fd(struct pw_connection *conn, int fd)
+{
+ struct impl *impl = SPA_CONTAINER_OF(conn, struct impl, this);
+ uint32_t index, i;
+
+ for (i = 0; i < impl->out.n_fds; i++) {
+ if (impl->out.fds[i] == fd)
+ return i;
+ }
+
+ index = impl->out.n_fds;
+ if (index >= MAX_FDS) {
+ pw_log_error("connection %p: too many fds", conn);
+ return -1;
+ }
+
+ impl->out.fds[index] = fd;
+ impl->out.n_fds++;
+
+ return index;
+}
+
+static void *connection_ensure_size(struct pw_connection *conn, struct buffer *buf, size_t size)
+{
+ if (buf->buffer_size + size > buf->buffer_maxsize) {
+ buf->buffer_maxsize = SPA_ROUND_UP_N(buf->buffer_size + size, MAX_BUFFER_SIZE);
+ buf->buffer_data = realloc(buf->buffer_data, buf->buffer_maxsize);
+
+ pw_log_warn("connection %p: resize buffer to %zd %zd %zd",
+ conn, buf->buffer_size, size, buf->buffer_maxsize);
+ }
+ return (uint8_t *) buf->buffer_data + buf->buffer_size;
+}
+
+static bool refill_buffer(struct pw_connection *conn, struct buffer *buf)
+{
+ ssize_t len;
+ struct cmsghdr *cmsg;
+ struct msghdr msg = { 0 };
+ struct iovec iov[1];
+ char cmsgbuf[CMSG_SPACE(MAX_FDS * sizeof(int))];
+
+ iov[0].iov_base = buf->buffer_data + buf->buffer_size;
+ iov[0].iov_len = buf->buffer_maxsize - buf->buffer_size;
+ msg.msg_iov = iov;
+ msg.msg_iovlen = 1;
+ msg.msg_control = cmsgbuf;
+ msg.msg_controllen = sizeof(cmsgbuf);
+ msg.msg_flags = MSG_CMSG_CLOEXEC;
+
+ while (true) {
+ len = recvmsg(conn->fd, &msg, msg.msg_flags);
+ if (len < 0) {
+ if (errno == EINTR)
+ continue;
+ else
+ goto recv_error;
+ }
+ break;
+ }
+
+ buf->buffer_size += len;
+
+ /* handle control messages */
+ for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
+ if (cmsg->cmsg_level != SOL_SOCKET || cmsg->cmsg_type != SCM_RIGHTS)
+ continue;
+
+ buf->n_fds =
+ (cmsg->cmsg_len - ((char *) CMSG_DATA(cmsg) - (char *) cmsg)) / sizeof(int);
+ memcpy(buf->fds, CMSG_DATA(cmsg), buf->n_fds * sizeof(int));
+ }
+ pw_log_trace("connection %p: %d read %zd bytes and %d fds", conn, conn->fd, len,
+ buf->n_fds);
+
+ return true;
+
+ /* ERRORS */
+ recv_error:
+ pw_log_error("could not recvmsg on fd %d: %s", conn->fd, strerror(errno));
+ return false;
+}
+
+static void clear_buffer(struct buffer *buf)
+{
+ buf->n_fds = 0;
+ buf->offset = 0;
+ buf->size = 0;
+ buf->buffer_size = 0;
+}
+
+/** Make a new connection object for the given socket
+ *
+ * \param fd the socket
+ * \returns a newly allocated connection object
+ *
+ * \memberof pw_connection
+ */
+struct pw_connection *pw_connection_new(int fd)
+{
+ struct impl *impl;
+ struct pw_connection *this;
+
+ impl = calloc(1, sizeof(struct impl));
+ if (impl == NULL)
+ return NULL;
+
+ debug_messages = pw_debug_is_category_enabled("connection");
+
+ this = &impl->this;
+
+ pw_log_debug("connection %p: new", this);
+
+ this->fd = fd;
+ pw_signal_init(&this->need_flush);
+ pw_signal_init(&this->destroy_signal);
+
+ impl->out.buffer_data = malloc(MAX_BUFFER_SIZE);
+ impl->out.buffer_maxsize = MAX_BUFFER_SIZE;
+ impl->in.buffer_data = malloc(MAX_BUFFER_SIZE);
+ impl->in.buffer_maxsize = MAX_BUFFER_SIZE;
+ impl->in.update = true;
+
+ if (impl->out.buffer_data == NULL || impl->in.buffer_data == NULL)
+ goto no_mem;
+
+ return this;
+
+ no_mem:
+ free(impl->out.buffer_data);
+ free(impl->in.buffer_data);
+ free(impl);
+ return NULL;
+}
+
+/** Destroy a connection
+ *
+ * \param conn the connection to destroy
+ *
+ * \memberof pw_connection
+ */
+void pw_connection_destroy(struct pw_connection *conn)
+{
+ struct impl *impl = SPA_CONTAINER_OF(conn, struct impl, this);
+
+ pw_log_debug("connection %p: destroy", conn);
+
+ pw_signal_emit(&conn->destroy_signal, conn);
+
+ free(impl->out.buffer_data);
+ free(impl->in.buffer_data);
+ free(impl);
+}
+
+/** Move to the next packet in the connection
+ *
+ * \param conn the connection
+ * \param opcode addres of result opcode
+ * \param dest_id addres of result destination id
+ * \param dt pointer to packet data
+ * \param sz size of packet data
+ * \return true on success
+ *
+ * Get the next packet in \a conn and store the opcode and destination
+ * id as well as the packet data and size.
+ *
+ * \memberof pw_connection
+ */
+bool
+pw_connection_get_next(struct pw_connection *conn,
+ uint8_t *opcode,
+ uint32_t *dest_id,
+ void **dt,
+ uint32_t *sz)
+{
+ struct impl *impl = SPA_CONTAINER_OF(conn, struct impl, this);
+ size_t len, size;
+ uint8_t *data;
+ struct buffer *buf;
+ uint32_t *p;
+
+ buf = &impl->in;
+
+ /* move to next packet */
+ buf->offset += buf->size;
+
+ again:
+ if (buf->update) {
+ if (!refill_buffer(conn, buf))
+ return false;
+ buf->update = false;
+ }
+
+ /* now read packet */
+ data = buf->buffer_data;
+ size = buf->buffer_size;
+
+ if (buf->offset >= size) {
+ clear_buffer(buf);
+ buf->update = true;
+ return false;
+ }
+
+ data += buf->offset;
+ size -= buf->offset;
+
+ if (size < 8) {
+ connection_ensure_size(conn, buf, 8);
+ buf->update = true;
+ goto again;
+ }
+ p = (uint32_t *) data;
+ data += 8;
+ size -= 8;
+
+ *dest_id = p[0];
+ *opcode = p[1] >> 24;
+ len = p[1] & 0xffffff;
+
+ if (len > size) {
+ connection_ensure_size(conn, buf, len);
+ buf->update = true;
+ goto again;
+ }
+ buf->size = len;
+ buf->data = data;
+ buf->offset += 8;
+
+ *dt = buf->data;
+ *sz = buf->size;
+
+
+ if (debug_messages) {
+ printf("<<<<<<<<< in:\n");
+ spa_debug_pod((struct spa_pod *)data);
+ }
+
+ return true;
+}
+
+static inline void *begin_write(struct pw_connection *conn, uint32_t size)
+{
+ struct impl *impl = SPA_CONTAINER_OF(conn, struct impl, this);
+ uint32_t *p;
+ struct buffer *buf = &impl->out;
+ /* 4 for dest_id, 1 for opcode, 3 for size and size for payload */
+ p = connection_ensure_size(conn, buf, 8 + size);
+ return p + 2;
+}
+
+static uint32_t write_pod(struct spa_pod_builder *b, uint32_t ref, const void *data, uint32_t size)
+{
+ struct impl *impl = SPA_CONTAINER_OF(b, struct impl, builder);
+
+ if (ref == -1)
+ ref = b->offset;
+
+ if (b->size <= b->offset) {
+ b->size = SPA_ROUND_UP_N(b->offset + size, 4096);
+ b->data = begin_write(&impl->this, b->size);
+ }
+ memcpy(b->data + ref, data, size);
+
+ return ref;
+}
+
+struct spa_pod_builder *
+pw_connection_begin_write_resource(struct pw_connection *conn,
+ struct pw_resource *resource,
+ uint8_t opcode)
+{
+ struct impl *impl = SPA_CONTAINER_OF(conn, struct impl, this);
+ uint32_t diff, base, i, b;
+ struct pw_client *client = resource->client;
+ struct pw_core *core = client->core;
+ const char **types;
+
+ base = client->n_types;
+ diff = spa_type_map_get_size(core->type.map) - base;
+ if (diff > 0) {
+ types = alloca(diff * sizeof(char *));
+ for (i = 0, b = base; i < diff; i++, b++)
+ types[i] = spa_type_map_get_type(core->type.map, b);
+
+ client->n_types += diff;
+ pw_core_notify_update_types(client->core_resource, base, diff, types);
+ }
+
+ impl->dest_id = resource->id;
+ impl->opcode = opcode;
+ impl->builder = (struct spa_pod_builder) { NULL, 0, 0, NULL, write_pod };
+
+ return &impl->builder;
+}
+
+struct spa_pod_builder *
+pw_connection_begin_write_proxy(struct pw_connection *conn,
+ struct pw_proxy *proxy,
+ uint8_t opcode)
+{
+ struct impl *impl = SPA_CONTAINER_OF(conn, struct impl, this);
+ uint32_t diff, base, i, b;
+ const char **types;
+ struct pw_remote *remote = proxy->remote;
+ struct pw_core *core = remote->core;
+
+ base = remote->n_types;
+ diff = spa_type_map_get_size(core->type.map) - base;
+ if (diff > 0) {
+ types = alloca(diff * sizeof(char *));
+ for (i = 0, b = base; i < diff; i++, b++)
+ types[i] = spa_type_map_get_type(core->type.map, b);
+
+ remote->n_types += diff;
+ pw_core_do_update_types(remote->core_proxy, base, diff, types);
+ }
+
+ impl->dest_id = proxy->id;
+ impl->opcode = opcode;
+ impl->builder = (struct spa_pod_builder) { NULL, 0, 0, NULL, write_pod };
+
+ return &impl->builder;
+}
+
+void
+pw_connection_end_write(struct pw_connection *conn,
+ struct spa_pod_builder *builder)
+{
+ struct impl *impl = SPA_CONTAINER_OF(conn, struct impl, this);
+ uint32_t *p, size = builder->offset;
+ struct buffer *buf = &impl->out;
+
+ p = connection_ensure_size(conn, buf, 8 + size);
+ *p++ = impl->dest_id;
+ *p++ = (impl->opcode << 24) | (size & 0xffffff);
+
+ buf->buffer_size += 8 + size;
+
+ if (debug_messages) {
+ printf(">>>>>>>>> out:\n");
+ spa_debug_pod((struct spa_pod *)p);
+ }
+
+ pw_signal_emit(&conn->need_flush, conn);
+}
+
+/** Flush the connection object
+ *
+ * \param conn the connection object
+ * \return true on success
+ *
+ * Write the queued messages on the connection to the socket
+ *
+ * \memberof pw_connection
+ */
+bool pw_connection_flush(struct pw_connection *conn)
+{
+ struct impl *impl = SPA_CONTAINER_OF(conn, struct impl, this);
+ ssize_t len;
+ struct msghdr msg = { 0 };
+ struct iovec iov[1];
+ struct cmsghdr *cmsg;
+ char cmsgbuf[CMSG_SPACE(MAX_FDS * sizeof(int))];
+ int *cm, i, fds_len;
+ struct buffer *buf;
+
+ buf = &impl->out;
+
+ if (buf->buffer_size == 0)
+ return true;
+
+ fds_len = buf->n_fds * sizeof(int);
+
+ iov[0].iov_base = buf->buffer_data;
+ iov[0].iov_len = buf->buffer_size;
+ msg.msg_iov = iov;
+ msg.msg_iovlen = 1;
+
+ if (buf->n_fds > 0) {
+ msg.msg_control = cmsgbuf;
+ msg.msg_controllen = CMSG_SPACE(fds_len);
+ cmsg = CMSG_FIRSTHDR(&msg);
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = SCM_RIGHTS;
+ cmsg->cmsg_len = CMSG_LEN(fds_len);
+ cm = (int *) CMSG_DATA(cmsg);
+ for (i = 0; i < buf->n_fds; i++)
+ cm[i] = buf->fds[i] > 0 ? buf->fds[i] : -buf->fds[i];
+ msg.msg_controllen = cmsg->cmsg_len;
+ } else {
+ msg.msg_control = NULL;
+ msg.msg_controllen = 0;
+ }
+
+ while (true) {
+ len = sendmsg(conn->fd, &msg, MSG_NOSIGNAL);
+ if (len < 0) {
+ if (errno == EINTR)
+ continue;
+ else
+ goto send_error;
+ }
+ break;
+ }
+ pw_log_trace("connection %p: %d written %zd bytes and %u fds", conn, conn->fd, len,
+ buf->n_fds);
+
+ buf->buffer_size -= len;
+ buf->n_fds = 0;
+
+ return true;
+
+ /* ERRORS */
+ send_error:
+ pw_log_error("could not sendmsg: %s", strerror(errno));
+ return false;
+}
+
+/** Clear the connection object
+ *
+ * \param conn the connection object
+ * \return true on success
+ *
+ * Remove all queued messages from \a conn
+ *
+ * \memberof pw_connection
+ */
+bool pw_connection_clear(struct pw_connection *conn)
+{
+ struct impl *impl = SPA_CONTAINER_OF(conn, struct impl, this);
+
+ clear_buffer(&impl->out);
+ clear_buffer(&impl->in);
+ impl->in.update = true;
+
+ return true;
+}
diff --git a/src/modules/module-protocol-native/connection.h b/src/modules/module-protocol-native/connection.h
new file mode 100644
index 00000000..7f213449
--- /dev/null
+++ b/src/modules/module-protocol-native/connection.h
@@ -0,0 +1,89 @@
+/* PipeWire
+ * Copyright (C) 2016 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __PIPEWIRE_CONNECTION_H__
+#define __PIPEWIRE_CONNECTION_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/defs.h>
+#include <pipewire/sig.h>
+
+/** \class pw_connection
+ *
+ * \brief Manages the connection between client and server
+ *
+ * The \ref pw_connection handles the connection between client
+ * and server on a given socket.
+ */
+struct pw_connection {
+ int fd; /**< the socket */
+
+ /** Emited when data has been written that needs to be flushed */
+ PW_SIGNAL(need_flush, (struct pw_listener *listener, struct pw_connection *conn));
+ /** Emited when the connection is destroyed */
+ PW_SIGNAL(destroy_signal, (struct pw_listener *listener, struct pw_connection *conn));
+};
+
+struct pw_connection *
+pw_connection_new(int fd);
+
+void
+pw_connection_destroy(struct pw_connection *conn);
+
+uint32_t
+pw_connection_add_fd(struct pw_connection *conn, int fd);
+
+int
+pw_connection_get_fd(struct pw_connection *conn, uint32_t index);
+
+bool
+pw_connection_get_next(struct pw_connection *conn,
+ uint8_t *opcode,
+ uint32_t *dest_id,
+ void **data, uint32_t *size);
+
+struct spa_pod_builder *
+pw_connection_begin_write_resource(struct pw_connection *conn,
+ struct pw_resource *resource,
+ uint8_t opcode);
+
+struct spa_pod_builder *
+pw_connection_begin_write_proxy(struct pw_connection *conn,
+ struct pw_proxy *proxy,
+ uint8_t opcode);
+
+
+void
+pw_connection_end_write(struct pw_connection *conn,
+ struct spa_pod_builder *builder);
+
+bool
+pw_connection_flush(struct pw_connection *conn);
+
+bool
+pw_connection_clear(struct pw_connection *conn);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* __PIPEWIRE_CONNECTION_H__ */
diff --git a/src/modules/module-protocol-native/protocol-native.c b/src/modules/module-protocol-native/protocol-native.c
new file mode 100644
index 00000000..225fcb4d
--- /dev/null
+++ b/src/modules/module-protocol-native/protocol-native.c
@@ -0,0 +1,1077 @@
+/* PipeWire
+ * Copyright (C) 2017 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <errno.h>
+
+#include "spa/pod-iter.h"
+
+#include "pipewire/pipewire.h"
+#include "pipewire/protocol.h"
+#include "pipewire/interfaces.h"
+#include "pipewire/resource.h"
+
+#include "connection.h"
+
+/** \cond */
+
+typedef bool(*demarshal_func_t) (void *object, void *data, size_t size);
+
+/** \endcond */
+
+static void core_marshal_client_update(void *object, const struct spa_dict *props)
+{
+ struct pw_proxy *proxy = object;
+ struct pw_connection *connection = proxy->remote->protocol_private;
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f;
+ int i, n_items;
+
+ b = pw_connection_begin_write_proxy(connection, proxy, PW_CORE_METHOD_CLIENT_UPDATE);
+
+ n_items = props ? props->n_items : 0;
+
+ spa_pod_builder_add(b, SPA_POD_TYPE_STRUCT, &f, SPA_POD_TYPE_INT, n_items, 0);
+
+ for (i = 0; i < n_items; i++) {
+ spa_pod_builder_add(b,
+ SPA_POD_TYPE_STRING, props->items[i].key,
+ SPA_POD_TYPE_STRING, props->items[i].value, 0);
+ }
+ spa_pod_builder_add(b, -SPA_POD_TYPE_STRUCT, &f, 0);
+
+ pw_connection_end_write(connection, b);
+}
+
+static void core_marshal_sync(void *object, uint32_t seq)
+{
+ struct pw_proxy *proxy = object;
+ struct pw_connection *connection = proxy->remote->protocol_private;
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f;
+
+ b = pw_connection_begin_write_proxy(connection, proxy, PW_CORE_METHOD_SYNC);
+
+ spa_pod_builder_struct(b, &f, SPA_POD_TYPE_INT, seq);
+
+ pw_connection_end_write(connection, b);
+}
+
+static void core_marshal_get_registry(void *object, uint32_t new_id)
+{
+ struct pw_proxy *proxy = object;
+ struct pw_connection *connection = proxy->remote->protocol_private;
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f;
+
+ b = pw_connection_begin_write_proxy(connection, proxy, PW_CORE_METHOD_GET_REGISTRY);
+
+ spa_pod_builder_struct(b, &f, SPA_POD_TYPE_INT, new_id);
+
+ pw_connection_end_write(connection, b);
+}
+
+static void
+core_marshal_create_node(void *object,
+ const char *factory_name,
+ const char *name, const struct spa_dict *props, uint32_t new_id)
+{
+ struct pw_proxy *proxy = object;
+ struct pw_connection *connection = proxy->remote->protocol_private;
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f;
+ uint32_t i, n_items;
+
+ b = pw_connection_begin_write_proxy(connection, proxy, PW_CORE_METHOD_CREATE_NODE);
+
+ n_items = props ? props->n_items : 0;
+
+ spa_pod_builder_add(b,
+ SPA_POD_TYPE_STRUCT, &f,
+ SPA_POD_TYPE_STRING, factory_name,
+ SPA_POD_TYPE_STRING, name, SPA_POD_TYPE_INT, n_items, 0);
+
+ for (i = 0; i < n_items; i++) {
+ spa_pod_builder_add(b,
+ SPA_POD_TYPE_STRING, props->items[i].key,
+ SPA_POD_TYPE_STRING, props->items[i].value, 0);
+ }
+ spa_pod_builder_add(b, SPA_POD_TYPE_INT, new_id, -SPA_POD_TYPE_STRUCT, &f, 0);
+
+ pw_connection_end_write(connection, b);
+}
+
+static void
+core_marshal_create_link(void *object,
+ uint32_t output_node_id,
+ uint32_t output_port_id,
+ uint32_t input_node_id,
+ uint32_t input_port_id,
+ const struct spa_format *filter,
+ const struct spa_dict *props,
+ uint32_t new_id)
+{
+ struct pw_proxy *proxy = object;
+ struct pw_connection *connection = proxy->remote->protocol_private;
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f;
+ uint32_t i, n_items;
+
+ b = pw_connection_begin_write_proxy(connection, proxy, PW_CORE_METHOD_CREATE_LINK);
+
+ n_items = props ? props->n_items : 0;
+
+ spa_pod_builder_add(b,
+ SPA_POD_TYPE_STRUCT, &f,
+ SPA_POD_TYPE_INT, output_node_id,
+ SPA_POD_TYPE_INT, output_port_id,
+ SPA_POD_TYPE_INT, input_node_id,
+ SPA_POD_TYPE_INT, input_port_id,
+ SPA_POD_TYPE_POD, filter,
+ SPA_POD_TYPE_INT, n_items, 0);
+
+ for (i = 0; i < n_items; i++) {
+ spa_pod_builder_add(b,
+ SPA_POD_TYPE_STRING, props->items[i].key,
+ SPA_POD_TYPE_STRING, props->items[i].value, 0);
+ }
+ spa_pod_builder_add(b,
+ SPA_POD_TYPE_INT, new_id,
+ -SPA_POD_TYPE_STRUCT, &f, 0);
+
+ pw_connection_end_write(connection, b);
+}
+
+static void
+core_marshal_update_types_client(void *object, uint32_t first_id, uint32_t n_types, const char **types)
+{
+ struct pw_proxy *proxy = object;
+ struct pw_connection *connection = proxy->remote->protocol_private;
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f;
+ uint32_t i;
+
+ b = pw_connection_begin_write_proxy(connection, proxy, PW_CORE_METHOD_UPDATE_TYPES);
+
+ spa_pod_builder_add(b,
+ SPA_POD_TYPE_STRUCT, &f,
+ SPA_POD_TYPE_INT, first_id, SPA_POD_TYPE_INT, n_types, 0);
+
+ for (i = 0; i < n_types; i++) {
+ spa_pod_builder_add(b, SPA_POD_TYPE_STRING, types[i], 0);
+ }
+ spa_pod_builder_add(b, -SPA_POD_TYPE_STRUCT, &f, 0);
+
+ pw_connection_end_write(connection, b);
+}
+
+static bool core_demarshal_info(void *object, void *data, size_t size)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_dict props;
+ struct pw_core_info info;
+ struct spa_pod_iter it;
+ int i;
+
+ if (!spa_pod_iter_struct(&it, data, size) ||
+ !spa_pod_iter_get(&it,
+ SPA_POD_TYPE_INT, &info.id,
+ SPA_POD_TYPE_LONG, &info.change_mask,
+ SPA_POD_TYPE_STRING, &info.user_name,
+ SPA_POD_TYPE_STRING, &info.host_name,
+ SPA_POD_TYPE_STRING, &info.version,
+ SPA_POD_TYPE_STRING, &info.name,
+ SPA_POD_TYPE_INT, &info.cookie, SPA_POD_TYPE_INT, &props.n_items, 0))
+ return false;
+
+ info.props = &props;
+ props.items = alloca(props.n_items * sizeof(struct spa_dict_item));
+ for (i = 0; i < props.n_items; i++) {
+ if (!spa_pod_iter_get(&it,
+ SPA_POD_TYPE_STRING, &props.items[i].key,
+ SPA_POD_TYPE_STRING, &props.items[i].value, 0))
+ return false;
+ }
+ pw_proxy_notify(proxy, struct pw_core_events, info, &info);
+ return true;
+}
+
+static bool core_demarshal_done(void *object, void *data, size_t size)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_iter it;
+ uint32_t seq;
+
+ if (!spa_pod_iter_struct(&it, data, size) ||
+ !spa_pod_iter_get(&it, SPA_POD_TYPE_INT, &seq, 0))
+ return false;
+
+ pw_proxy_notify(proxy, struct pw_core_events, done, seq);
+ return true;
+}
+
+static bool core_demarshal_error(void *object, void *data, size_t size)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_iter it;
+ uint32_t id, res;
+ const char *error;
+
+ if (!spa_pod_iter_struct(&it, data, size) ||
+ !spa_pod_iter_get(&it,
+ SPA_POD_TYPE_INT, &id,
+ SPA_POD_TYPE_INT, &res, SPA_POD_TYPE_STRING, &error, 0))
+ return false;
+
+ pw_proxy_notify(proxy, struct pw_core_events, error, id, res, error);
+ return true;
+}
+
+static bool core_demarshal_remove_id(void *object, void *data, size_t size)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_iter it;
+ uint32_t id;
+
+ if (!spa_pod_iter_struct(&it, data, size) ||
+ !spa_pod_iter_get(&it, SPA_POD_TYPE_INT, &id, 0))
+ return false;
+
+ pw_proxy_notify(proxy, struct pw_core_events, remove_id, id);
+ return true;
+}
+
+static bool core_demarshal_update_types_client(void *object, void *data, size_t size)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_iter it;
+ uint32_t first_id, n_types;
+ const char **types;
+ int i;
+
+ if (!spa_pod_iter_struct(&it, data, size) ||
+ !spa_pod_iter_get(&it, SPA_POD_TYPE_INT, &first_id, SPA_POD_TYPE_INT, &n_types, 0))
+ return false;
+
+ types = alloca(n_types * sizeof(char *));
+ for (i = 0; i < n_types; i++) {
+ if (!spa_pod_iter_get(&it, SPA_POD_TYPE_STRING, &types[i], 0))
+ return false;
+ }
+ pw_proxy_notify(proxy, struct pw_core_events, update_types, first_id, n_types, types);
+ return true;
+}
+
+static void core_marshal_info(void *object, struct pw_core_info *info)
+{
+ struct pw_resource *resource = object;
+ struct pw_connection *connection = resource->client->protocol_private;
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f;
+ uint32_t i, n_items;
+
+ b = pw_connection_begin_write_resource(connection, resource, PW_CORE_EVENT_INFO);
+
+ n_items = info->props ? info->props->n_items : 0;
+
+ spa_pod_builder_add(b,
+ SPA_POD_TYPE_STRUCT, &f,
+ SPA_POD_TYPE_INT, info->id,
+ SPA_POD_TYPE_LONG, info->change_mask,
+ SPA_POD_TYPE_STRING, info->user_name,
+ SPA_POD_TYPE_STRING, info->host_name,
+ SPA_POD_TYPE_STRING, info->version,
+ SPA_POD_TYPE_STRING, info->name,
+ SPA_POD_TYPE_INT, info->cookie, SPA_POD_TYPE_INT, n_items, 0);
+
+ for (i = 0; i < n_items; i++) {
+ spa_pod_builder_add(b,
+ SPA_POD_TYPE_STRING, info->props->items[i].key,
+ SPA_POD_TYPE_STRING, info->props->items[i].value, 0);
+ }
+ spa_pod_builder_add(b, -SPA_POD_TYPE_STRUCT, &f, 0);
+
+ pw_connection_end_write(connection, b);
+}
+
+static void core_marshal_done(void *object, uint32_t seq)
+{
+ struct pw_resource *resource = object;
+ struct pw_connection *connection = resource->client->protocol_private;
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f;
+
+ b = pw_connection_begin_write_resource(connection, resource, PW_CORE_EVENT_DONE);
+
+ spa_pod_builder_struct(b, &f, SPA_POD_TYPE_INT, seq);
+
+ pw_connection_end_write(connection, b);
+}
+
+static void core_marshal_error(void *object, uint32_t id, int res, const char *error, ...)
+{
+ struct pw_resource *resource = object;
+ struct pw_connection *connection = resource->client->protocol_private;
+ char buffer[128];
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f;
+ va_list ap;
+
+ b = pw_connection_begin_write_resource(connection, resource, PW_CORE_EVENT_ERROR);
+
+ va_start(ap, error);
+ vsnprintf(buffer, sizeof(buffer), error, ap);
+ va_end(ap);
+
+ spa_pod_builder_struct(b, &f,
+ SPA_POD_TYPE_INT, id,
+ SPA_POD_TYPE_INT, res, SPA_POD_TYPE_STRING, buffer);
+
+ pw_connection_end_write(connection, b);
+}
+
+static void core_marshal_remove_id(void *object, uint32_t id)
+{
+ struct pw_resource *resource = object;
+ struct pw_connection *connection = resource->client->protocol_private;
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f;
+
+ b = pw_connection_begin_write_resource(connection, resource, PW_CORE_EVENT_REMOVE_ID);
+
+ spa_pod_builder_struct(b, &f, SPA_POD_TYPE_INT, id);
+
+ pw_connection_end_write(connection, b);
+}
+
+static void
+core_marshal_update_types_server(void *object, uint32_t first_id, uint32_t n_types, const char **types)
+{
+ struct pw_resource *resource = object;
+ struct pw_connection *connection = resource->client->protocol_private;
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f;
+ uint32_t i;
+
+ b = pw_connection_begin_write_resource(connection, resource, PW_CORE_EVENT_UPDATE_TYPES);
+
+ spa_pod_builder_add(b,
+ SPA_POD_TYPE_STRUCT, &f,
+ SPA_POD_TYPE_INT, first_id, SPA_POD_TYPE_INT, n_types, 0);
+
+ for (i = 0; i < n_types; i++) {
+ spa_pod_builder_add(b, SPA_POD_TYPE_STRING, types[i], 0);
+ }
+ spa_pod_builder_add(b, -SPA_POD_TYPE_STRUCT, &f, 0);
+
+ pw_connection_end_write(connection, b);
+}
+
+static bool core_demarshal_client_update(void *object, void *data, size_t size)
+{
+ struct pw_resource *resource = object;
+ struct spa_dict props;
+ struct spa_pod_iter it;
+ uint32_t i;
+
+ if (!spa_pod_iter_struct(&it, data, size) ||
+ !spa_pod_iter_get(&it, SPA_POD_TYPE_INT, &props.n_items, 0))
+ return false;
+
+ props.items = alloca(props.n_items * sizeof(struct spa_dict_item));
+ for (i = 0; i < props.n_items; i++) {
+ if (!spa_pod_iter_get(&it,
+ SPA_POD_TYPE_STRING, &props.items[i].key,
+ SPA_POD_TYPE_STRING, &props.items[i].value, 0))
+ return false;
+ }
+ pw_resource_do(resource, struct pw_core_methods, client_update, &props);
+ return true;
+}
+
+static bool core_demarshal_sync(void *object, void *data, size_t size)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_iter it;
+ uint32_t seq;
+
+ if (!spa_pod_iter_struct(&it, data, size) ||
+ !spa_pod_iter_get(&it, SPA_POD_TYPE_INT, &seq, 0))
+ return false;
+
+ pw_resource_do(resource, struct pw_core_methods, sync, seq);
+ return true;
+}
+
+static bool core_demarshal_get_registry(void *object, void *data, size_t size)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_iter it;
+ int32_t new_id;
+
+ if (!spa_pod_iter_struct(&it, data, size) ||
+ !spa_pod_iter_get(&it, SPA_POD_TYPE_INT, &new_id, 0))
+ return false;
+
+ pw_resource_do(resource, struct pw_core_methods, get_registry, new_id);
+ return true;
+}
+
+static bool core_demarshal_create_node(void *object, void *data, size_t size)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_iter it;
+ uint32_t new_id, i;
+ const char *factory_name, *name;
+ struct spa_dict props;
+
+ if (!spa_pod_iter_struct(&it, data, size) ||
+ !spa_pod_iter_get(&it,
+ SPA_POD_TYPE_STRING, &factory_name,
+ SPA_POD_TYPE_STRING, &name, SPA_POD_TYPE_INT, &props.n_items, 0))
+ return false;
+
+ props.items = alloca(props.n_items * sizeof(struct spa_dict_item));
+ for (i = 0; i < props.n_items; i++) {
+ if (!spa_pod_iter_get(&it,
+ SPA_POD_TYPE_STRING, &props.items[i].key,
+ SPA_POD_TYPE_STRING, &props.items[i].value, 0))
+ return false;
+ }
+ if (!spa_pod_iter_get(&it, SPA_POD_TYPE_INT, &new_id, 0))
+ return false;
+
+ pw_resource_do(resource, struct pw_core_methods, create_node, factory_name,
+ name, &props, new_id);
+ return true;
+}
+
+static bool core_demarshal_create_link(void *object, void *data, size_t size)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_iter it;
+ uint32_t new_id, i;
+ uint32_t output_node_id, output_port_id, input_node_id, input_port_id;
+ struct spa_format *filter = NULL;
+ struct spa_dict props;
+
+ if (!spa_pod_iter_struct(&it, data, size) ||
+ !pw_pod_remap_data(SPA_POD_TYPE_STRUCT, data, size, &resource->client->types) ||
+ !spa_pod_iter_get(&it,
+ SPA_POD_TYPE_INT, &output_node_id,
+ SPA_POD_TYPE_INT, &output_port_id,
+ SPA_POD_TYPE_INT, &input_node_id,
+ SPA_POD_TYPE_INT, &input_port_id,
+ -SPA_POD_TYPE_OBJECT, &filter,
+ SPA_POD_TYPE_INT, &props.n_items, 0))
+ return false;
+
+ props.items = alloca(props.n_items * sizeof(struct spa_dict_item));
+ for (i = 0; i < props.n_items; i++) {
+ if (!spa_pod_iter_get(&it,
+ SPA_POD_TYPE_STRING, &props.items[i].key,
+ SPA_POD_TYPE_STRING, &props.items[i].value, 0))
+ return false;
+ }
+ if (!spa_pod_iter_get(&it, SPA_POD_TYPE_INT, &new_id, 0))
+ return false;
+
+ pw_resource_do(resource, struct pw_core_methods, create_link, output_node_id,
+ output_port_id,
+ input_node_id,
+ input_port_id,
+ filter,
+ &props,
+ new_id);
+ return true;
+}
+
+static bool core_demarshal_update_types_server(void *object, void *data, size_t size)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_iter it;
+ uint32_t first_id, n_types;
+ const char **types;
+ int i;
+
+ if (!spa_pod_iter_struct(&it, data, size) ||
+ !spa_pod_iter_get(&it, SPA_POD_TYPE_INT, &first_id, SPA_POD_TYPE_INT, &n_types, 0))
+ return false;
+
+ types = alloca(n_types * sizeof(char *));
+ for (i = 0; i < n_types; i++) {
+ if (!spa_pod_iter_get(&it, SPA_POD_TYPE_STRING, &types[i], 0))
+ return false;
+ }
+ pw_resource_do(resource, struct pw_core_methods, update_types, first_id, n_types, types);
+ return true;
+}
+
+static void registry_marshal_global(void *object, uint32_t id, const char *type, uint32_t version)
+{
+ struct pw_resource *resource = object;
+ struct pw_connection *connection = resource->client->protocol_private;
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f;
+
+ b = pw_connection_begin_write_resource(connection, resource, PW_REGISTRY_EVENT_GLOBAL);
+
+ spa_pod_builder_struct(b, &f,
+ SPA_POD_TYPE_INT, id,
+ SPA_POD_TYPE_STRING, type,
+ SPA_POD_TYPE_INT, version);
+
+ pw_connection_end_write(connection, b);
+}
+
+static void registry_marshal_global_remove(void *object, uint32_t id)
+{
+ struct pw_resource *resource = object;
+ struct pw_connection *connection = resource->client->protocol_private;
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f;
+
+ b = pw_connection_begin_write_resource(connection, resource, PW_REGISTRY_EVENT_GLOBAL_REMOVE);
+
+ spa_pod_builder_struct(b, &f, SPA_POD_TYPE_INT, id);
+
+ pw_connection_end_write(connection, b);
+}
+
+static bool registry_demarshal_bind(void *object, void *data, size_t size)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_iter it;
+ uint32_t id, version, new_id;
+
+ if (!spa_pod_iter_struct(&it, data, size) ||
+ !spa_pod_iter_get(&it,
+ SPA_POD_TYPE_INT, &id,
+ SPA_POD_TYPE_INT, &version,
+ SPA_POD_TYPE_INT, &new_id, 0))
+ return false;
+
+ pw_resource_do(resource, struct pw_registry_methods, bind, id, version, new_id);
+ return true;
+}
+
+static void module_marshal_info(void *object, struct pw_module_info *info)
+{
+ struct pw_resource *resource = object;
+ struct pw_connection *connection = resource->client->protocol_private;
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f;
+ uint32_t i, n_items;
+
+ b = pw_connection_begin_write_resource(connection, resource, PW_MODULE_EVENT_INFO);
+
+ n_items = info->props ? info->props->n_items : 0;
+
+ spa_pod_builder_add(b,
+ SPA_POD_TYPE_STRUCT, &f,
+ SPA_POD_TYPE_INT, info->id,
+ SPA_POD_TYPE_LONG, info->change_mask,
+ SPA_POD_TYPE_STRING, info->name,
+ SPA_POD_TYPE_STRING, info->filename,
+ SPA_POD_TYPE_STRING, info->args, SPA_POD_TYPE_INT, n_items, 0);
+
+ for (i = 0; i < n_items; i++) {
+ spa_pod_builder_add(b,
+ SPA_POD_TYPE_STRING, info->props->items[i].key,
+ SPA_POD_TYPE_STRING, info->props->items[i].value, 0);
+ }
+ spa_pod_builder_add(b, -SPA_POD_TYPE_STRUCT, &f, 0);
+
+ pw_connection_end_write(connection, b);
+}
+
+static bool module_demarshal_info(void *object, void *data, size_t size)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_iter it;
+ struct spa_dict props;
+ struct pw_module_info info;
+ int i;
+
+ if (!spa_pod_iter_struct(&it, data, size) ||
+ !spa_pod_iter_get(&it,
+ SPA_POD_TYPE_INT, &info.id,
+ SPA_POD_TYPE_LONG, &info.change_mask,
+ SPA_POD_TYPE_STRING, &info.name,
+ SPA_POD_TYPE_STRING, &info.filename,
+ SPA_POD_TYPE_STRING, &info.args, SPA_POD_TYPE_INT, &props.n_items, 0))
+ return false;
+
+ info.props = &props;
+ props.items = alloca(props.n_items * sizeof(struct spa_dict_item));
+ for (i = 0; i < props.n_items; i++) {
+ if (!spa_pod_iter_get(&it,
+ SPA_POD_TYPE_STRING, &props.items[i].key,
+ SPA_POD_TYPE_STRING, &props.items[i].value, 0))
+ return false;
+ }
+ pw_proxy_notify(proxy, struct pw_module_events, info, &info);
+ return true;
+}
+
+static void node_marshal_info(void *object, struct pw_node_info *info)
+{
+ struct pw_resource *resource = object;
+ struct pw_connection *connection = resource->client->protocol_private;
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f;
+ uint32_t i, n_items;
+
+ b = pw_connection_begin_write_resource(connection, resource, PW_NODE_EVENT_INFO);
+
+ spa_pod_builder_add(b,
+ SPA_POD_TYPE_STRUCT, &f,
+ SPA_POD_TYPE_INT, info->id,
+ SPA_POD_TYPE_LONG, info->change_mask,
+ SPA_POD_TYPE_STRING, info->name,
+ SPA_POD_TYPE_INT, info->max_input_ports,
+ SPA_POD_TYPE_INT, info->n_input_ports,
+ SPA_POD_TYPE_INT, info->n_input_formats, 0);
+
+ for (i = 0; i < info->n_input_formats; i++)
+ spa_pod_builder_add(b, SPA_POD_TYPE_POD, info->input_formats[i], 0);
+
+ spa_pod_builder_add(b,
+ SPA_POD_TYPE_INT, info->max_output_ports,
+ SPA_POD_TYPE_INT, info->n_output_ports,
+ SPA_POD_TYPE_INT, info->n_output_formats, 0);
+
+ for (i = 0; i < info->n_output_formats; i++)
+ spa_pod_builder_add(b, SPA_POD_TYPE_POD, info->output_formats[i], 0);
+
+ n_items = info->props ? info->props->n_items : 0;
+
+ spa_pod_builder_add(b,
+ SPA_POD_TYPE_INT, info->state,
+ SPA_POD_TYPE_STRING, info->error, SPA_POD_TYPE_INT, n_items, 0);
+
+ for (i = 0; i < n_items; i++) {
+ spa_pod_builder_add(b,
+ SPA_POD_TYPE_STRING, info->props->items[i].key,
+ SPA_POD_TYPE_STRING, info->props->items[i].value, 0);
+ }
+ spa_pod_builder_add(b, -SPA_POD_TYPE_STRUCT, &f, 0);
+
+ pw_connection_end_write(connection, b);
+}
+
+static bool node_demarshal_info(void *object, void *data, size_t size)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_iter it;
+ struct spa_dict props;
+ struct pw_node_info info;
+ int i;
+
+ if (!spa_pod_iter_struct(&it, data, size) ||
+ !pw_pod_remap_data(SPA_POD_TYPE_STRUCT, data, size, &proxy->remote->types) ||
+ !spa_pod_iter_get(&it,
+ SPA_POD_TYPE_INT, &info.id,
+ SPA_POD_TYPE_LONG, &info.change_mask,
+ SPA_POD_TYPE_STRING, &info.name,
+ SPA_POD_TYPE_INT, &info.max_input_ports,
+ SPA_POD_TYPE_INT, &info.n_input_ports,
+ SPA_POD_TYPE_INT, &info.n_input_formats, 0))
+ return false;
+
+ info.input_formats = alloca(info.n_input_formats * sizeof(struct spa_format *));
+ for (i = 0; i < info.n_input_formats; i++)
+ if (!spa_pod_iter_get(&it, SPA_POD_TYPE_OBJECT, &info.input_formats[i], 0))
+ return false;
+
+ if (!spa_pod_iter_get(&it,
+ SPA_POD_TYPE_INT, &info.max_output_ports,
+ SPA_POD_TYPE_INT, &info.n_output_ports,
+ SPA_POD_TYPE_INT, &info.n_output_formats, 0))
+ return false;
+
+ info.output_formats = alloca(info.n_output_formats * sizeof(struct spa_format *));
+ for (i = 0; i < info.n_output_formats; i++)
+ if (!spa_pod_iter_get(&it, SPA_POD_TYPE_OBJECT, &info.output_formats[i], 0))
+ return false;
+
+ if (!spa_pod_iter_get(&it,
+ SPA_POD_TYPE_INT, &info.state,
+ SPA_POD_TYPE_STRING, &info.error,
+ SPA_POD_TYPE_INT, &props.n_items, 0))
+ return false;
+
+ info.props = &props;
+ props.items = alloca(props.n_items * sizeof(struct spa_dict_item));
+ for (i = 0; i < props.n_items; i++) {
+ if (!spa_pod_iter_get(&it,
+ SPA_POD_TYPE_STRING, &props.items[i].key,
+ SPA_POD_TYPE_STRING, &props.items[i].value, 0))
+ return false;
+ }
+ pw_proxy_notify(proxy, struct pw_node_events, info, &info);
+ return true;
+}
+
+static void client_marshal_info(void *object, struct pw_client_info *info)
+{
+ struct pw_resource *resource = object;
+ struct pw_connection *connection = resource->client->protocol_private;
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f;
+ uint32_t i, n_items;
+
+ b = pw_connection_begin_write_resource(connection, resource, PW_CLIENT_EVENT_INFO);
+
+ n_items = info->props ? info->props->n_items : 0;
+
+ spa_pod_builder_add(b,
+ SPA_POD_TYPE_STRUCT, &f,
+ SPA_POD_TYPE_INT, info->id,
+ SPA_POD_TYPE_LONG, info->change_mask, SPA_POD_TYPE_INT, n_items, 0);
+
+ for (i = 0; i < n_items; i++) {
+ spa_pod_builder_add(b,
+ SPA_POD_TYPE_STRING, info->props->items[i].key,
+ SPA_POD_TYPE_STRING, info->props->items[i].value, 0);
+ }
+ spa_pod_builder_add(b, -SPA_POD_TYPE_STRUCT, &f, 0);
+
+ pw_connection_end_write(connection, b);
+}
+
+static bool client_demarshal_info(void *object, void *data, size_t size)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_iter it;
+ struct spa_dict props;
+ struct pw_client_info info;
+ uint32_t i;
+
+ if (!spa_pod_iter_struct(&it, data, size) ||
+ !spa_pod_iter_get(&it,
+ SPA_POD_TYPE_INT, &info.id,
+ SPA_POD_TYPE_LONG, &info.change_mask,
+ SPA_POD_TYPE_INT, &props.n_items, 0))
+ return false;
+
+ info.props = &props;
+ props.items = alloca(props.n_items * sizeof(struct spa_dict_item));
+ for (i = 0; i < props.n_items; i++) {
+ if (!spa_pod_iter_get(&it,
+ SPA_POD_TYPE_STRING, &props.items[i].key,
+ SPA_POD_TYPE_STRING, &props.items[i].value, 0))
+ return false;
+ }
+ pw_proxy_notify(proxy, struct pw_client_events, info, &info);
+ return true;
+}
+
+static void link_marshal_info(void *object, struct pw_link_info *info)
+{
+ struct pw_resource *resource = object;
+ struct pw_connection *connection = resource->client->protocol_private;
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f;
+
+ b = pw_connection_begin_write_resource(connection, resource, PW_LINK_EVENT_INFO);
+
+ spa_pod_builder_struct(b, &f,
+ SPA_POD_TYPE_INT, info->id,
+ SPA_POD_TYPE_LONG, info->change_mask,
+ SPA_POD_TYPE_INT, info->output_node_id,
+ SPA_POD_TYPE_INT, info->output_port_id,
+ SPA_POD_TYPE_INT, info->input_node_id,
+ SPA_POD_TYPE_INT, info->input_port_id,
+ SPA_POD_TYPE_POD, info->format);
+
+ pw_connection_end_write(connection, b);
+}
+
+static bool link_demarshal_info(void *object, void *data, size_t size)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_iter it;
+ struct pw_link_info info = { 0, };
+
+ if (!spa_pod_iter_struct(&it, data, size) ||
+ !pw_pod_remap_data(SPA_POD_TYPE_STRUCT, data, size, &proxy->remote->types) ||
+ !spa_pod_iter_get(&it,
+ SPA_POD_TYPE_INT, &info.id,
+ SPA_POD_TYPE_LONG, &info.change_mask,
+ SPA_POD_TYPE_INT, &info.output_node_id,
+ SPA_POD_TYPE_INT, &info.output_port_id,
+ SPA_POD_TYPE_INT, &info.input_node_id,
+ SPA_POD_TYPE_INT, &info.input_port_id,
+ -SPA_POD_TYPE_OBJECT, &info.format, 0))
+ return false;
+
+ pw_proxy_notify(proxy, struct pw_link_events, info, &info);
+ return true;
+}
+
+static bool registry_demarshal_global(void *object, void *data, size_t size)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_iter it;
+ uint32_t id, version;
+ const char *type;
+
+ if (!spa_pod_iter_struct(&it, data, size) ||
+ !spa_pod_iter_get(&it,
+ SPA_POD_TYPE_INT, &id,
+ SPA_POD_TYPE_STRING, &type,
+ SPA_POD_TYPE_INT, &version, 0))
+ return false;
+
+ pw_proxy_notify(proxy, struct pw_registry_events, global, id, type, version);
+ return true;
+}
+
+static bool registry_demarshal_global_remove(void *object, void *data, size_t size)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_iter it;
+ uint32_t id;
+
+ if (!spa_pod_iter_struct(&it, data, size) ||
+ !spa_pod_iter_get(&it, SPA_POD_TYPE_INT, &id, 0))
+ return false;
+
+ pw_proxy_notify(proxy, struct pw_registry_events, global_remove, id);
+ return true;
+}
+
+static void registry_marshal_bind(void *object, uint32_t id, uint32_t version, uint32_t new_id)
+{
+ struct pw_proxy *proxy = object;
+ struct pw_connection *connection = proxy->remote->protocol_private;
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f;
+
+ b = pw_connection_begin_write_proxy(connection, proxy, PW_REGISTRY_METHOD_BIND);
+
+ spa_pod_builder_struct(b, &f,
+ SPA_POD_TYPE_INT, id,
+ SPA_POD_TYPE_INT, version,
+ SPA_POD_TYPE_INT, new_id);
+
+ pw_connection_end_write(connection, b);
+}
+
+static const struct pw_core_methods pw_protocol_native_client_core_methods = {
+ &core_marshal_update_types_client,
+ &core_marshal_sync,
+ &core_marshal_get_registry,
+ &core_marshal_client_update,
+ &core_marshal_create_node,
+ &core_marshal_create_link
+};
+
+static const demarshal_func_t pw_protocol_native_client_core_demarshal[PW_CORE_EVENT_NUM] = {
+ &core_demarshal_update_types_client,
+ &core_demarshal_done,
+ &core_demarshal_error,
+ &core_demarshal_remove_id,
+ &core_demarshal_info
+};
+
+static const struct pw_interface pw_protocol_native_client_core_interface = {
+ PIPEWIRE_TYPE__Core,
+ PW_VERSION_CORE,
+ PW_CORE_METHOD_NUM, &pw_protocol_native_client_core_methods,
+ PW_CORE_EVENT_NUM, pw_protocol_native_client_core_demarshal
+};
+
+static const struct pw_registry_methods pw_protocol_native_client_registry_methods = {
+ &registry_marshal_bind
+};
+
+static const demarshal_func_t pw_protocol_native_client_registry_demarshal[] = {
+ &registry_demarshal_global,
+ &registry_demarshal_global_remove,
+};
+
+static const struct pw_interface pw_protocol_native_client_registry_interface = {
+ PIPEWIRE_TYPE__Registry,
+ PW_VERSION_REGISTRY,
+ PW_REGISTRY_METHOD_NUM, &pw_protocol_native_client_registry_methods,
+ PW_REGISTRY_EVENT_NUM, pw_protocol_native_client_registry_demarshal,
+};
+
+static const demarshal_func_t pw_protocol_native_client_module_demarshal[] = {
+ &module_demarshal_info,
+};
+
+static const struct pw_interface pw_protocol_native_client_module_interface = {
+ PIPEWIRE_TYPE__Module,
+ PW_VERSION_MODULE,
+ 0, NULL,
+ PW_MODULE_EVENT_NUM, pw_protocol_native_client_module_demarshal,
+};
+
+static const demarshal_func_t pw_protocol_native_client_node_demarshal[] = {
+ &node_demarshal_info,
+};
+
+static const struct pw_interface pw_protocol_native_client_node_interface = {
+ PIPEWIRE_TYPE__Node,
+ PW_VERSION_NODE,
+ 0, NULL,
+ PW_NODE_EVENT_NUM, pw_protocol_native_client_node_demarshal,
+};
+
+static const demarshal_func_t pw_protocol_native_client_client_demarshal[] = {
+ &client_demarshal_info,
+};
+
+static const struct pw_interface pw_protocol_native_client_client_interface = {
+ PIPEWIRE_TYPE__Client,
+ PW_VERSION_CLIENT,
+ 0, NULL,
+ PW_CLIENT_EVENT_NUM, pw_protocol_native_client_client_demarshal,
+};
+
+static const demarshal_func_t pw_protocol_native_client_link_demarshal[] = {
+ &link_demarshal_info,
+};
+
+static const struct pw_interface pw_protocol_native_client_link_interface = {
+ PIPEWIRE_TYPE__Link,
+ PW_VERSION_LINK,
+ 0, NULL,
+ PW_LINK_EVENT_NUM, pw_protocol_native_client_link_demarshal,
+};
+
+static const demarshal_func_t pw_protocol_native_server_core_demarshal[PW_CORE_METHOD_NUM] = {
+ &core_demarshal_update_types_server,
+ &core_demarshal_sync,
+ &core_demarshal_get_registry,
+ &core_demarshal_client_update,
+ &core_demarshal_create_node,
+ &core_demarshal_create_link
+};
+
+static const struct pw_core_events pw_protocol_native_server_core_events = {
+ &core_marshal_update_types_server,
+ &core_marshal_done,
+ &core_marshal_error,
+ &core_marshal_remove_id,
+ &core_marshal_info
+};
+
+const struct pw_interface pw_protocol_native_server_core_interface = {
+ PIPEWIRE_TYPE__Core,
+ PW_VERSION_CORE,
+ PW_CORE_METHOD_NUM, pw_protocol_native_server_core_demarshal,
+ PW_CORE_EVENT_NUM, &pw_protocol_native_server_core_events,
+};
+
+static const demarshal_func_t pw_protocol_native_server_registry_demarshal[] = {
+ &registry_demarshal_bind,
+};
+
+static const struct pw_registry_events pw_protocol_native_server_registry_events = {
+ &registry_marshal_global,
+ &registry_marshal_global_remove,
+};
+
+const struct pw_interface pw_protocol_native_server_registry_interface = {
+ PIPEWIRE_TYPE__Registry,
+ PW_VERSION_REGISTRY,
+ PW_REGISTRY_METHOD_NUM, pw_protocol_native_server_registry_demarshal,
+ PW_REGISTRY_EVENT_NUM, &pw_protocol_native_server_registry_events,
+};
+
+static const struct pw_module_events pw_protocol_native_server_module_events = {
+ &module_marshal_info,
+};
+
+const struct pw_interface pw_protocol_native_server_module_interface = {
+ PIPEWIRE_TYPE__Module,
+ PW_VERSION_MODULE,
+ 0, NULL,
+ PW_MODULE_EVENT_NUM, &pw_protocol_native_server_module_events,
+};
+
+static const struct pw_node_events pw_protocol_native_server_node_events = {
+ &node_marshal_info,
+};
+
+const struct pw_interface pw_protocol_native_server_node_interface = {
+ PIPEWIRE_TYPE__Node,
+ PW_VERSION_NODE,
+ 0, NULL,
+ PW_NODE_EVENT_NUM, &pw_protocol_native_server_node_events,
+};
+
+static const struct pw_client_events pw_protocol_native_server_client_events = {
+ &client_marshal_info,
+};
+
+const struct pw_interface pw_protocol_native_server_client_interface = {
+ PIPEWIRE_TYPE__Client,
+ PW_VERSION_CLIENT,
+ 0, NULL,
+ PW_CLIENT_EVENT_NUM, &pw_protocol_native_server_client_events,
+};
+
+static const struct pw_link_events pw_protocol_native_server_link_events = {
+ &link_marshal_info,
+};
+
+const struct pw_interface pw_protocol_native_server_link_interface = {
+ PIPEWIRE_TYPE__Link,
+ PW_VERSION_LINK,
+ 0, NULL,
+ PW_LINK_EVENT_NUM, &pw_protocol_native_server_link_events,
+};
+
+struct pw_protocol *pw_protocol_native_init(void)
+{
+ static bool init = false;
+ struct pw_protocol *protocol;
+
+ protocol = pw_protocol_get(PW_TYPE_PROTOCOL__Native);
+
+ if (init)
+ return protocol;
+
+ pw_protocol_add_interfaces(protocol,
+ &pw_protocol_native_client_core_interface,
+ &pw_protocol_native_server_core_interface);
+ pw_protocol_add_interfaces(protocol,
+ &pw_protocol_native_client_registry_interface,
+ &pw_protocol_native_server_registry_interface);
+ pw_protocol_add_interfaces(protocol,
+ &pw_protocol_native_client_module_interface,
+ &pw_protocol_native_server_module_interface);
+ pw_protocol_add_interfaces(protocol,
+ &pw_protocol_native_client_node_interface,
+ &pw_protocol_native_server_node_interface);
+ pw_protocol_add_interfaces(protocol,
+ &pw_protocol_native_client_client_interface,
+ &pw_protocol_native_server_client_interface);
+ pw_protocol_add_interfaces(protocol,
+ &pw_protocol_native_client_link_interface,
+ &pw_protocol_native_server_link_interface);
+
+ init = true;
+
+ return protocol;
+}
diff --git a/src/modules/module-suspend-on-idle.c b/src/modules/module-suspend-on-idle.c
new file mode 100644
index 00000000..208a3b5e
--- /dev/null
+++ b/src/modules/module-suspend-on-idle.c
@@ -0,0 +1,195 @@
+/* PipeWire
+ * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+
+#include "config.h"
+
+#include "pipewire/core.h"
+#include "pipewire/module.h"
+
+struct impl {
+ struct pw_core *core;
+ struct pw_properties *properties;
+
+ struct pw_listener global_added;
+ struct pw_listener global_removed;
+
+ struct spa_list node_list;
+};
+
+struct node_info {
+ struct impl *impl;
+ struct pw_node *node;
+ struct spa_list link;
+ struct pw_listener node_state_request;
+ struct pw_listener node_state_changed;
+ struct spa_source *idle_timeout;
+};
+
+static struct node_info *find_node_info(struct impl *impl, struct pw_node *node)
+{
+ struct node_info *info;
+
+ spa_list_for_each(info, &impl->node_list, link) {
+ if (info->node == node)
+ return info;
+ }
+ return NULL;
+}
+
+static void remove_idle_timeout(struct node_info *info)
+{
+ if (info->idle_timeout) {
+ pw_loop_destroy_source(info->impl->core->main_loop, info->idle_timeout);
+ info->idle_timeout = NULL;
+ }
+}
+
+static void node_info_free(struct node_info *info)
+{
+ spa_list_remove(&info->link);
+ remove_idle_timeout(info);
+ pw_signal_remove(&info->node_state_request);
+ pw_signal_remove(&info->node_state_changed);
+ free(info);
+}
+
+static void idle_timeout(struct spa_loop_utils *utils, struct spa_source *source, void *data)
+{
+ struct node_info *info = data;
+
+ pw_log_debug("module %p: node %p idle timeout", info->impl, info->node);
+ remove_idle_timeout(info);
+ pw_node_set_state(info->node, PW_NODE_STATE_SUSPENDED);
+}
+
+static void
+on_node_state_request(struct pw_listener *listener, struct pw_node *node, enum pw_node_state state)
+{
+ struct node_info *info = SPA_CONTAINER_OF(listener, struct node_info, node_state_request);
+ remove_idle_timeout(info);
+}
+
+static void
+on_node_state_changed(struct pw_listener *listener,
+ struct pw_node *node, enum pw_node_state old, enum pw_node_state state)
+{
+ struct node_info *info = SPA_CONTAINER_OF(listener, struct node_info, node_state_changed);
+ struct impl *impl = info->impl;
+
+ if (state != PW_NODE_STATE_IDLE) {
+ remove_idle_timeout(info);
+ } else {
+ struct timespec value;
+
+ pw_log_debug("module %p: node %p became idle", impl, node);
+ info->idle_timeout = pw_loop_add_timer(impl->core->main_loop,
+ idle_timeout, info);
+ value.tv_sec = 3;
+ value.tv_nsec = 0;
+ pw_loop_update_timer(impl->core->main_loop,
+ info->idle_timeout, &value, NULL, false);
+ }
+}
+
+static void
+on_global_added(struct pw_listener *listener, struct pw_core *core, struct pw_global *global)
+{
+ struct impl *impl = SPA_CONTAINER_OF(listener, struct impl, global_added);
+
+ if (global->type == impl->core->type.node) {
+ struct pw_node *node = global->object;
+ struct node_info *info;
+
+ info = calloc(1, sizeof(struct node_info));
+ info->impl = impl;
+ info->node = node;
+ spa_list_insert(impl->node_list.prev, &info->link);
+ pw_signal_add(&node->state_request, &info->node_state_request,
+ on_node_state_request);
+ pw_signal_add(&node->state_changed, &info->node_state_changed,
+ on_node_state_changed);
+
+ pw_log_debug("module %p: node %p added", impl, node);
+ }
+}
+
+static void
+on_global_removed(struct pw_listener *listener, struct pw_core *core, struct pw_global *global)
+{
+ struct impl *impl = SPA_CONTAINER_OF(listener, struct impl, global_removed);
+
+ if (global->type == impl->core->type.node) {
+ struct pw_node *node = global->object;
+ struct node_info *info;
+
+ if ((info = find_node_info(impl, node)))
+ node_info_free(info);
+
+ pw_log_debug("module %p: node %p removed", impl, node);
+ }
+}
+
+
+/**
+ * module_new:
+ * @core: #struct pw_core
+ * @properties: #struct pw_properties
+ *
+ * Make a new #struct impl object with given @properties
+ *
+ * Returns: a new #struct impl
+ */
+static struct impl *module_new(struct pw_core *core, struct pw_properties *properties)
+{
+ struct impl *impl;
+
+ impl = calloc(1, sizeof(struct impl));
+ pw_log_debug("module %p: new", impl);
+
+ impl->core = core;
+ impl->properties = properties;
+
+ spa_list_init(&impl->node_list);
+
+ pw_signal_add(&core->global_added, &impl->global_added, on_global_added);
+ pw_signal_add(&core->global_removed, &impl->global_removed, on_global_removed);
+
+ return impl;
+}
+
+#if 0
+static void module_destroy(struct impl *impl)
+{
+ pw_log_debug("module %p: destroy", impl);
+
+ pw_global_destroy(impl->global);
+
+ free(impl);
+}
+#endif
+
+bool pipewire__module_init(struct pw_module *module, const char *args)
+{
+ module_new(module->core, NULL);
+ return true;
+}
diff --git a/src/modules/spa/meson.build b/src/modules/spa/meson.build
new file mode 100644
index 00000000..af49555f
--- /dev/null
+++ b/src/modules/spa/meson.build
@@ -0,0 +1,34 @@
+pipewire_module_spa_c_args = [
+ '-DHAVE_CONFIG_H',
+ '-D_GNU_SOURCE',
+]
+
+pipewire_module_spa_monitor = shared_library('pipewire-module-spa-monitor',
+ [ 'module-monitor.c', 'spa-monitor.c', 'spa-node.c' ],
+ c_args : pipewire_module_spa_c_args,
+ include_directories : [configinc, spa_inc],
+ link_with : spalib,
+ install : true,
+ install_dir : '@0@/pipewire-0.1'.format(get_option('libdir')),
+ dependencies : [mathlib, dl_lib, pipewire_dep],
+)
+
+pipewire_module_spa_node = shared_library('pipewire-module-spa-node',
+ [ 'module-node.c', 'spa-node.c' ],
+ c_args : pipewire_module_spa_c_args,
+ include_directories : [configinc, spa_inc],
+ link_with : spalib,
+ install : true,
+ install_dir : '@0@/pipewire-0.1'.format(get_option('libdir')),
+ dependencies : [mathlib, dl_lib, pipewire_dep],
+)
+
+pipewire_module_spa_node_factory = shared_library('pipewire-module-spa-node-factory',
+ [ 'module-node-factory.c', 'spa-node.c' ],
+ c_args : pipewire_module_spa_c_args,
+ include_directories : [configinc, spa_inc],
+ link_with : spalib,
+ install : true,
+ install_dir : '@0@/pipewire-0.1'.format(get_option('libdir')),
+ dependencies : [mathlib, dl_lib, pipewire_dep],
+)
diff --git a/src/modules/spa/module-monitor.c b/src/modules/spa/module-monitor.c
new file mode 100644
index 00000000..f3f2b248
--- /dev/null
+++ b/src/modules/spa/module-monitor.c
@@ -0,0 +1,63 @@
+/* PipeWire
+ * Copyright (C) 2016 Axis Communications <dev-gstreamer@axis.com>
+ * @author Linus Svensson <linus.svensson@axis.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <getopt.h>
+#include <limits.h>
+
+#include <spa/lib/props.h>
+
+#include <pipewire/utils.h>
+#include <pipewire/core.h>
+#include <pipewire/module.h>
+
+#include "spa-monitor.h"
+
+bool pipewire__module_init(struct pw_module *module, const char *args)
+{
+ const char *dir;
+ char **argv;
+ int n_tokens;
+
+ if (args == NULL)
+ goto wrong_arguments;
+
+ argv = pw_split_strv(args, " \t", INT_MAX, &n_tokens);
+ if (n_tokens < 3)
+ goto not_enough_arguments;
+
+ if ((dir = getenv("SPA_PLUGIN_DIR")) == NULL)
+ dir = PLUGINDIR;
+
+ pw_spa_monitor_load(module->core, dir, argv[0], argv[1], argv[2]);
+
+ pw_free_strv(argv);
+
+ return true;
+
+ not_enough_arguments:
+ pw_free_strv(argv);
+ wrong_arguments:
+ pw_log_error("usage: module-spa-monitor <plugin> <factory> <name>");
+ return false;
+}
diff --git a/src/modules/spa/module-node-factory.c b/src/modules/spa/module-node-factory.c
new file mode 100644
index 00000000..29a50f2b
--- /dev/null
+++ b/src/modules/spa/module-node-factory.c
@@ -0,0 +1,118 @@
+/* PipeWire
+ * Copyright (C) 2017 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <dlfcn.h>
+
+#include "config.h"
+
+#include "pipewire/interfaces.h"
+#include "pipewire/core.h"
+#include "pipewire/module.h"
+
+#include "spa-node.h"
+
+struct impl {
+ struct pw_node_factory this;
+ struct pw_properties *properties;
+};
+
+static struct pw_node *create_node(struct pw_node_factory *factory,
+ struct pw_resource *resource,
+ const char *name,
+ struct pw_properties *properties)
+{
+ struct pw_node *node;
+ const char *lib, *factory_name;
+
+ if (properties == NULL)
+ goto no_properties;
+
+ lib = pw_properties_get(properties, "spa.library.name");
+ factory_name = pw_properties_get(properties, "spa.factory.name");
+
+ if(lib == NULL || factory_name == NULL)
+ goto no_properties;
+
+ node = pw_spa_node_load(factory->core,
+ NULL,
+ lib,
+ factory_name,
+ name,
+ properties);
+ if (node == NULL)
+ goto no_mem;
+
+ return node;
+
+ no_properties:
+ pw_log_error("missing properties");
+ if (resource) {
+ pw_core_notify_error(resource->client->core_resource,
+ resource->client->core_resource->id,
+ SPA_RESULT_INVALID_ARGUMENTS, "missing properties");
+ }
+ return NULL;
+ no_mem:
+ pw_log_error("can't create node");
+ if (resource) {
+ pw_core_notify_error(resource->client->core_resource,
+ resource->client->core_resource->id,
+ SPA_RESULT_NO_MEMORY, "no memory");
+ }
+ return NULL;
+}
+
+static struct impl *module_new(struct pw_core *core, struct pw_properties *properties)
+{
+ struct impl *impl;
+
+ impl = calloc(1, sizeof(struct impl));
+ pw_log_debug("module %p: new", impl);
+
+ impl->properties = properties;
+
+ impl->this.core = core;
+ impl->this.name = "spa-node-factory";
+ pw_signal_init(&impl->this.destroy_signal);
+ impl->this.create_node = create_node;
+
+ spa_list_insert(core->node_factory_list.prev, &impl->this.link);
+
+ pw_core_add_global(core, NULL, core->type.node_factory, 0, impl, NULL, &impl->this.global);
+
+ return impl;
+}
+
+#if 0
+static void module_destroy(struct impl *impl)
+{
+ pw_log_debug("module %p: destroy", impl);
+
+ free(impl);
+}
+#endif
+
+bool pipewire__module_init(struct pw_module *module, const char *args)
+{
+ module_new(module->core, NULL);
+ return true;
+}
diff --git a/src/modules/spa/module-node.c b/src/modules/spa/module-node.c
new file mode 100644
index 00000000..d44fd1ba
--- /dev/null
+++ b/src/modules/spa/module-node.c
@@ -0,0 +1,74 @@
+/* PipeWire
+ * Copyright (C) 2016 Axis Communications <dev-gstreamer@axis.com>
+ * @author Linus Svensson <linus.svensson@axis.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <getopt.h>
+#include <limits.h>
+
+#include <spa/lib/props.h>
+
+#include <pipewire/utils.h>
+#include <pipewire/core.h>
+#include <pipewire/module.h>
+
+#include "spa-monitor.h"
+#include "spa-node.h"
+
+bool pipewire__module_init(struct pw_module *module, const char *args)
+{
+ struct pw_properties *props = NULL;
+ char **argv;
+ int i, n_tokens;
+
+ if (args == NULL)
+ goto wrong_arguments;
+
+ argv = pw_split_strv(args, " \t", INT_MAX, &n_tokens);
+ if (n_tokens < 3)
+ goto not_enough_arguments;
+
+ props = pw_properties_new(NULL, NULL);
+
+ for (i = 3; i < n_tokens; i++) {
+ char **prop;
+ int n_props;
+
+ prop = pw_split_strv(argv[i], "=", INT_MAX, &n_props);
+ if (n_props >= 2)
+ pw_properties_set(props, prop[0], prop[1]);
+
+ pw_free_strv(prop);
+ }
+
+ pw_spa_node_load(module->core, NULL, argv[0], argv[1], argv[2], props);
+
+ pw_free_strv(argv);
+
+ return true;
+
+ not_enough_arguments:
+ pw_free_strv(argv);
+ wrong_arguments:
+ pw_log_error("usage: module-spa-node <plugin> <factory> <name> [key=value ...]");
+ return false;
+}
diff --git a/src/modules/spa/spa-monitor.c b/src/modules/spa/spa-monitor.c
new file mode 100644
index 00000000..e8850a6c
--- /dev/null
+++ b/src/modules/spa/spa-monitor.c
@@ -0,0 +1,315 @@
+/* PipeWire
+ * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <dlfcn.h>
+#include <errno.h>
+#include <poll.h>
+#include <string.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/mman.h>
+
+#include <spa/node.h>
+#include <spa/monitor.h>
+#include <spa/pod-iter.h>
+
+#include <pipewire/log.h>
+#include <pipewire/node.h>
+
+#include "spa-monitor.h"
+#include "spa-node.h"
+
+struct monitor_item {
+ char *id;
+ struct spa_list link;
+ struct pw_node *node;
+};
+
+struct impl {
+ struct pw_spa_monitor this;
+
+ struct pw_core *core;
+
+ void *hnd;
+
+ struct spa_list item_list;
+};
+
+static void add_item(struct pw_spa_monitor *this, struct spa_monitor_item *item)
+{
+ struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this);
+ int res;
+ struct spa_handle *handle;
+ struct monitor_item *mitem;
+ void *node_iface;
+ void *clock_iface;
+ struct pw_properties *props = NULL;
+ const char *name, *id, *klass;
+ struct spa_handle_factory *factory;
+ struct spa_pod *info = NULL;
+
+ spa_pod_object_query(&item->object,
+ impl->core->type.monitor.name, SPA_POD_TYPE_STRING, &name,
+ impl->core->type.monitor.id, SPA_POD_TYPE_STRING, &id,
+ impl->core->type.monitor.klass, SPA_POD_TYPE_STRING, &klass,
+ impl->core->type.monitor.factory, SPA_POD_TYPE_POINTER, &factory,
+ impl->core->type.monitor.info, SPA_POD_TYPE_STRUCT, &info, 0);
+
+ pw_log_debug("monitor %p: add: \"%s\" (%s)", this, name, id);
+
+ props = pw_properties_new(NULL, NULL);
+
+ if (info) {
+ struct spa_pod_iter it;
+
+ spa_pod_iter_pod(&it, info);
+ while (true) {
+ const char *key, *val;
+ if (!spa_pod_iter_get
+ (&it, SPA_POD_TYPE_STRING, &key, SPA_POD_TYPE_STRING, &val, 0))
+ break;
+ pw_properties_set(props, key, val);
+ }
+ }
+ pw_properties_set(props, "media.class", klass);
+
+ handle = calloc(1, factory->size);
+ if ((res = spa_handle_factory_init(factory,
+ handle,
+ &props->dict,
+ impl->core->support, impl->core->n_support)) < 0) {
+ pw_log_error("can't make factory instance: %d", res);
+ return;
+ }
+ if ((res = spa_handle_get_interface(handle, impl->core->type.spa_node, &node_iface)) < 0) {
+ pw_log_error("can't get NODE interface: %d", res);
+ return;
+ }
+ if ((res = spa_handle_get_interface(handle, impl->core->type.spa_clock, &clock_iface)) < 0) {
+ pw_log_info("no CLOCK interface: %d", res);
+ }
+
+
+ mitem = calloc(1, sizeof(struct monitor_item));
+ mitem->id = strdup(id);
+ mitem->node = pw_spa_node_new(impl->core, NULL, name, false, node_iface, clock_iface, props);
+
+ spa_list_insert(impl->item_list.prev, &mitem->link);
+}
+
+static struct monitor_item *find_item(struct pw_spa_monitor *this, const char *id)
+{
+ struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this);
+ struct monitor_item *mitem;
+
+ spa_list_for_each(mitem, &impl->item_list, link) {
+ if (strcmp(mitem->id, id) == 0) {
+ return mitem;
+ }
+ }
+ return NULL;
+}
+
+void destroy_item(struct monitor_item *mitem)
+{
+ pw_node_destroy(mitem->node);
+ spa_list_remove(&mitem->link);
+ free(mitem->id);
+ free(mitem);
+}
+
+static void remove_item(struct pw_spa_monitor *this, struct spa_monitor_item *item)
+{
+ struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this);
+ struct monitor_item *mitem;
+ const char *name, *id;
+
+ spa_pod_object_query(&item->object,
+ impl->core->type.monitor.name, SPA_POD_TYPE_STRING, &name,
+ impl->core->type.monitor.id, SPA_POD_TYPE_STRING, &id, 0);
+
+ pw_log_debug("monitor %p: remove: \"%s\" (%s)", this, name, id);
+ mitem = find_item(this, id);
+ if (mitem)
+ destroy_item(mitem);
+}
+
+static void on_monitor_event(struct spa_monitor *monitor, struct spa_event *event, void *user_data)
+{
+ struct impl *impl = user_data;
+ struct pw_spa_monitor *this = &impl->this;
+
+ if (SPA_EVENT_TYPE(event) == impl->core->type.monitor.Added) {
+ struct spa_monitor_item *item = SPA_POD_CONTENTS(struct spa_event, event);
+ add_item(this, item);
+ } else if (SPA_EVENT_TYPE(event) == impl->core->type.monitor.Removed) {
+ struct spa_monitor_item *item = SPA_POD_CONTENTS(struct spa_event, event);
+ remove_item(this, item);
+ } else if (SPA_EVENT_TYPE(event) == impl->core->type.monitor.Changed) {
+ struct spa_monitor_item *item = SPA_POD_CONTENTS(struct spa_event, event);
+ const char *name;
+
+ spa_pod_object_query(&item->object,
+ impl->core->type.monitor.name, SPA_POD_TYPE_STRING, &name, 0);
+
+ pw_log_debug("monitor %p: changed: \"%s\"", this, name);
+ }
+}
+
+static void update_monitor(struct pw_core *core, const char *name)
+{
+ const char *monitors;
+ struct spa_dict_item item;
+ struct spa_dict dict = SPA_DICT_INIT(1, &item);
+
+ if (core->properties)
+ monitors = pw_properties_get(core->properties, "monitors");
+ else
+ monitors = NULL;
+
+ item.key = "monitors";
+ if (monitors == NULL)
+ item.value = name;
+ else
+ asprintf((char **) &item.value, "%s,%s", monitors, name);
+
+ pw_core_update_properties(core, &dict);
+
+ if (monitors != NULL)
+ free((void *) item.value);
+}
+
+static const struct spa_monitor_callbacks callbacks = {
+ SPA_VERSION_MONITOR_CALLBACKS,
+ on_monitor_event,
+};
+
+struct pw_spa_monitor *pw_spa_monitor_load(struct pw_core *core,
+ const char *dir,
+ const char *lib,
+ const char *factory_name, const char *system_name)
+{
+ struct impl *impl;
+ struct pw_spa_monitor *this;
+ struct spa_handle *handle;
+ int res;
+ void *iface;
+ void *hnd;
+ uint32_t index;
+ spa_handle_factory_enum_func_t enum_func;
+ const struct spa_handle_factory *factory;
+ char *filename;
+
+ asprintf(&filename, "%s/%s.so", dir, lib);
+
+ if ((hnd = dlopen(filename, RTLD_NOW)) == NULL) {
+ pw_log_error("can't load %s: %s", filename, dlerror());
+ goto open_failed;
+ }
+ if ((enum_func = dlsym(hnd, SPA_HANDLE_FACTORY_ENUM_FUNC_NAME)) == NULL) {
+ pw_log_error("can't find enum function");
+ goto no_symbol;
+ }
+
+ for (index = 0;; index++) {
+ if ((res = enum_func(&factory, index)) < 0) {
+ if (res != SPA_RESULT_ENUM_END)
+ pw_log_error("can't enumerate factories: %d", res);
+ goto enum_failed;
+ }
+ if (strcmp(factory->name, factory_name) == 0)
+ break;
+ }
+ handle = calloc(1, factory->size);
+ if ((res = spa_handle_factory_init(factory,
+ handle, NULL, core->support, core->n_support)) < 0) {
+ pw_log_error("can't make factory instance: %d", res);
+ goto init_failed;
+ }
+ if ((res = spa_handle_get_interface(handle, core->type.spa_monitor, &iface)) < 0) {
+ free(handle);
+ pw_log_error("can't get MONITOR interface: %d", res);
+ goto interface_failed;
+ }
+
+ impl = calloc(1, sizeof(struct impl));
+ impl->core = core;
+ impl->hnd = hnd;
+
+ this = &impl->this;
+ pw_signal_init(&this->destroy_signal);
+ this->monitor = iface;
+ this->lib = filename;
+ this->factory_name = strdup(factory_name);
+ this->system_name = strdup(system_name);
+ this->handle = handle;
+
+ update_monitor(core, this->system_name);
+
+ spa_list_init(&impl->item_list);
+
+ for (index = 0;; index++) {
+ struct spa_monitor_item *item;
+ int res;
+
+ if ((res = spa_monitor_enum_items(this->monitor, &item, index)) < 0) {
+ if (res != SPA_RESULT_ENUM_END)
+ pw_log_debug("spa_monitor_enum_items: got error %d\n", res);
+ break;
+ }
+ add_item(this, item);
+ }
+ spa_monitor_set_callbacks(this->monitor, &callbacks, impl);
+
+ return this;
+
+ interface_failed:
+ spa_handle_clear(handle);
+ init_failed:
+ free(handle);
+ enum_failed:
+ no_symbol:
+ dlclose(hnd);
+ open_failed:
+ free(filename);
+ return NULL;
+
+}
+
+void pw_spa_monitor_destroy(struct pw_spa_monitor *monitor)
+{
+ struct impl *impl = SPA_CONTAINER_OF(monitor, struct impl, this);
+ struct monitor_item *mitem, *tmp;
+
+ pw_log_debug("spa-monitor %p: dispose", impl);
+ pw_signal_emit(&monitor->destroy_signal, monitor);
+
+ spa_list_for_each_safe(mitem, tmp, &impl->item_list, link)
+ destroy_item(mitem);
+
+ spa_handle_clear(monitor->handle);
+ free(monitor->handle);
+ free(monitor->lib);
+ free(monitor->factory_name);
+ free(monitor->system_name);
+
+ dlclose(impl->hnd);
+ free(impl);
+}
diff --git a/src/modules/spa/spa-monitor.h b/src/modules/spa/spa-monitor.h
new file mode 100644
index 00000000..846d16fc
--- /dev/null
+++ b/src/modules/spa/spa-monitor.h
@@ -0,0 +1,54 @@
+/* PipeWire
+ * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __PIPEWIRE_SPA_MONITOR_H__
+#define __PIPEWIRE_SPA_MONITOR_H__
+
+#include <spa/monitor.h>
+
+#include <pipewire/core.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct pw_spa_monitor {
+ struct spa_monitor *monitor;
+
+ char *lib;
+ char *factory_name;
+ char *system_name;
+ struct spa_handle *handle;
+
+ PW_SIGNAL(destroy_signal, (struct pw_listener *listener, struct pw_spa_monitor *monitor));
+};
+
+struct pw_spa_monitor *
+pw_spa_monitor_load(struct pw_core *core,
+ const char *dir,
+ const char *lib,
+ const char *factory_name, const char *system_name);
+void
+pw_spa_monitor_destroy(struct pw_spa_monitor *monitor);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __PIPEWIRE_SPA_MONITOR_H__ */
diff --git a/src/modules/spa/spa-node.c b/src/modules/spa/spa-node.c
new file mode 100644
index 00000000..a3aaf5c1
--- /dev/null
+++ b/src/modules/spa/spa-node.c
@@ -0,0 +1,588 @@
+/* PipeWire
+ * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <spa/graph-scheduler3.h>
+
+#include <string.h>
+#include <stdio.h>
+#include <dlfcn.h>
+
+#include <spa/node.h>
+
+#include "spa-node.h"
+
+struct impl {
+ struct pw_node *this;
+
+ bool async_init;
+
+ void *hnd;
+ struct spa_handle *handle;
+ struct spa_node *node; /**< handle to SPA node */
+ char *lib;
+ char *factory_name;
+};
+
+struct port {
+ struct pw_port *port;
+
+ struct spa_node *node;
+};
+
+
+static int port_impl_enum_formats(struct pw_port *port,
+ struct spa_format **format,
+ const struct spa_format *filter,
+ int32_t index)
+{
+ struct port *p = port->user_data;
+ return spa_node_port_enum_formats(p->node, port->direction, port->port_id, format, filter, index);
+}
+
+static int port_impl_set_format(struct pw_port *port, uint32_t flags, struct spa_format *format)
+{
+ struct port *p = port->user_data;
+ return spa_node_port_set_format(p->node, port->direction, port->port_id, flags, format);
+}
+
+static int port_impl_get_format(struct pw_port *port, const struct spa_format **format)
+{
+ struct port *p = port->user_data;
+ return spa_node_port_get_format(p->node, port->direction, port->port_id, format);
+}
+
+static int port_impl_get_info(struct pw_port *port, const struct spa_port_info **info)
+{
+ struct port *p = port->user_data;
+ return spa_node_port_get_info(p->node, port->direction, port->port_id, info);
+}
+
+static int port_impl_enum_params(struct pw_port *port, uint32_t index, struct spa_param **param)
+{
+ struct port *p = port->user_data;
+ return spa_node_port_enum_params(p->node, port->direction, port->port_id, index, param);
+}
+
+static int port_impl_set_param(struct pw_port *port, struct spa_param *param)
+{
+ struct port *p = port->user_data;
+ return spa_node_port_set_param(p->node, port->direction, port->port_id, param);
+}
+
+static int port_impl_use_buffers(struct pw_port *port, struct spa_buffer **buffers, uint32_t n_buffers)
+{
+ struct port *p = port->user_data;
+ return spa_node_port_use_buffers(p->node, port->direction, port->port_id, buffers, n_buffers);
+}
+
+static int port_impl_alloc_buffers(struct pw_port *port,
+ struct spa_param **params, uint32_t n_params,
+ struct spa_buffer **buffers, uint32_t *n_buffers)
+{
+ struct port *p = port->user_data;
+ return spa_node_port_alloc_buffers(p->node, port->direction, port->port_id,
+ params, n_params, buffers, n_buffers);
+}
+
+static int port_impl_reuse_buffer(struct pw_port *port, uint32_t buffer_id)
+{
+ struct port *p = port->user_data;
+ return spa_node_port_reuse_buffer(p->node, port->port_id, buffer_id);
+}
+
+static int port_impl_send_command(struct pw_port *port, struct spa_command *command)
+{
+ struct port *p = port->user_data;
+ return spa_node_port_send_command(p->node,
+ port->direction,
+ port->port_id,
+ command);
+}
+
+const struct pw_port_implementation port_impl = {
+ PW_VERSION_PORT_IMPLEMENTATION,
+ port_impl_enum_formats,
+ port_impl_set_format,
+ port_impl_get_format,
+ port_impl_get_info,
+ port_impl_enum_params,
+ port_impl_set_param,
+ port_impl_use_buffers,
+ port_impl_alloc_buffers,
+ port_impl_reuse_buffer,
+ port_impl_send_command,
+};
+
+static struct pw_port *
+make_port(struct pw_node *node, enum pw_direction direction, uint32_t port_id)
+{
+ struct impl *impl = node->user_data;
+ struct pw_port *port;
+ struct port *p;
+
+ port = pw_port_new(direction, port_id, sizeof(struct port));
+ if (port == NULL)
+ return NULL;
+
+ p = port->user_data;
+ p->node = impl->node;
+
+ port->implementation = &port_impl;
+
+ spa_node_port_set_io(impl->node, direction, port_id, &port->io);
+
+ pw_port_add(port, node);
+
+ return port;
+}
+
+static void update_port_ids(struct impl *impl)
+{
+ struct pw_node *this = impl->this;
+ uint32_t *input_port_ids, *output_port_ids;
+ uint32_t n_input_ports, n_output_ports, max_input_ports, max_output_ports;
+ uint32_t i;
+ struct spa_list *ports;
+
+ spa_node_get_n_ports(impl->node,
+ &n_input_ports, &max_input_ports, &n_output_ports, &max_output_ports);
+
+ this->info.max_input_ports = max_input_ports;
+ this->info.max_output_ports = max_output_ports;
+
+ input_port_ids = alloca(sizeof(uint32_t) * n_input_ports);
+ output_port_ids = alloca(sizeof(uint32_t) * n_output_ports);
+
+ spa_node_get_port_ids(impl->node,
+ max_input_ports, input_port_ids, max_output_ports, output_port_ids);
+
+ pw_log_debug("node %p: update_port ids %u/%u, %u/%u", this,
+ n_input_ports, max_input_ports, n_output_ports, max_output_ports);
+
+ i = 0;
+ ports = &this->input_ports;
+ while (true) {
+ struct pw_port *p = (ports == &this->input_ports) ? NULL :
+ SPA_CONTAINER_OF(ports, struct pw_port, link);
+
+ if (p && i < n_input_ports && p->port_id == input_port_ids[i]) {
+ pw_log_debug("node %p: exiting input port %d", this, input_port_ids[i]);
+ i++;
+ ports = ports->next;
+ } else if ((p && i < n_input_ports && input_port_ids[i] < p->port_id)
+ || i < n_input_ports) {
+ struct pw_port *np;
+ pw_log_debug("node %p: input port added %d", this, input_port_ids[i]);
+ np = make_port(this, PW_DIRECTION_INPUT, input_port_ids[i]);
+
+ ports = np->link.next;
+ i++;
+ } else if (p) {
+ ports = ports->next;
+ pw_log_debug("node %p: input port removed %d", this, p->port_id);
+ pw_port_destroy(p);
+ } else {
+ pw_log_debug("node %p: no more input ports", this);
+ break;
+ }
+ }
+
+ i = 0;
+ ports = &this->output_ports;
+ while (true) {
+ struct pw_port *p = (ports == &this->output_ports) ? NULL :
+ SPA_CONTAINER_OF(ports, struct pw_port, link);
+
+ if (p && i < n_output_ports && p->port_id == output_port_ids[i]) {
+ pw_log_debug("node %p: exiting output port %d", this, output_port_ids[i]);
+ i++;
+ ports = ports->next;
+ } else if ((p && i < n_output_ports && output_port_ids[i] < p->port_id)
+ || i < n_output_ports) {
+ struct pw_port *np;
+ pw_log_debug("node %p: output port added %d", this, output_port_ids[i]);
+ np = make_port(this, PW_DIRECTION_OUTPUT, output_port_ids[i]);
+ ports = np->link.next;
+ i++;
+ } else if (p) {
+ ports = ports->next;
+ pw_log_debug("node %p: output port removed %d", this, p->port_id);
+ pw_port_destroy(p);
+ } else {
+ pw_log_debug("node %p: no more output ports", this);
+ break;
+ }
+ }
+}
+
+
+static int node_impl_get_props(struct pw_node *node, struct spa_props **props)
+{
+ struct impl *impl = node->user_data;
+ return spa_node_get_props(impl->node, props);
+}
+
+static int node_impl_set_props(struct pw_node *node, const struct spa_props *props)
+{
+ struct impl *impl = node->user_data;
+ return spa_node_set_props(impl->node, props);
+}
+
+static int node_impl_send_command(struct pw_node *node, struct spa_command *command)
+{
+ struct impl *impl = node->user_data;
+ return spa_node_send_command(impl->node, command);
+}
+
+static struct pw_port*
+node_impl_add_port(struct pw_node *node,
+ enum pw_direction direction,
+ uint32_t port_id)
+{
+ struct impl *impl = node->user_data;
+ int res;
+
+ if ((res = spa_node_add_port(impl->node, direction, port_id)) < 0) {
+ pw_log_error("node %p: could not add port %d %d", node, port_id, res);
+ return NULL;
+ }
+
+ return make_port(node, direction, port_id);
+}
+
+static int node_impl_schedule_input(struct pw_node *node)
+{
+ struct impl *impl = node->user_data;
+ return spa_node_process_input(impl->node);
+}
+
+static int node_impl_schedule_output(struct pw_node *node)
+{
+ struct impl *impl = node->user_data;
+ return spa_node_process_output(impl->node);
+}
+
+static const struct pw_node_implementation node_impl = {
+ PW_VERSION_NODE_IMPLEMENTATION,
+ node_impl_get_props,
+ node_impl_set_props,
+ node_impl_send_command,
+ node_impl_add_port,
+ node_impl_schedule_input,
+ node_impl_schedule_output,
+};
+
+static void pw_spa_node_destroy(void *object)
+{
+ struct pw_node *node = object;
+ struct impl *impl = node->user_data;
+
+ pw_log_debug("spa-node %p: destroy", node);
+
+ if (impl->handle) {
+ spa_handle_clear(impl->handle);
+ free(impl->handle);
+ }
+ free(impl->lib);
+ free(impl->factory_name);
+ if (impl->hnd)
+ dlclose(impl->hnd);
+}
+
+static void complete_init(struct impl *impl)
+{
+ struct pw_node *this = impl->this;
+ update_port_ids(impl);
+ pw_node_export(this);
+}
+
+static void on_node_done(struct spa_node *node, int seq, int res, void *user_data)
+{
+ struct impl *impl = user_data;
+ struct pw_node *this = impl->this;
+
+ if (impl->async_init) {
+ complete_init(impl);
+ impl->async_init = false;
+ }
+
+ pw_log_debug("spa-node %p: async complete event %d %d", this, seq, res);
+ pw_signal_emit(&this->async_complete, this, seq, res);
+}
+
+static void on_node_event(struct spa_node *node, struct spa_event *event, void *user_data)
+{
+ struct impl *impl = user_data;
+ struct pw_node *this = impl->this;
+
+ pw_signal_emit(&this->event, this, event);
+}
+
+static void on_node_need_input(struct spa_node *node, void *user_data)
+{
+ struct impl *impl = user_data;
+ struct pw_node *this = impl->this;
+
+ spa_graph_scheduler_pull(this->rt.sched, &this->rt.node);
+ while (spa_graph_scheduler_iterate(this->rt.sched));
+}
+
+static void on_node_have_output(struct spa_node *node, void *user_data)
+{
+ struct impl *impl = user_data;
+ struct pw_node *this = impl->this;
+
+ spa_graph_scheduler_push(this->rt.sched, &this->rt.node);
+ while (spa_graph_scheduler_iterate(this->rt.sched));
+}
+
+static void
+on_node_reuse_buffer(struct spa_node *node, uint32_t port_id, uint32_t buffer_id, void *user_data)
+{
+ struct impl *impl = user_data;
+ struct pw_node *this = impl->this;
+ struct spa_graph_port *p, *pp;
+
+ spa_list_for_each(p, &this->rt.node.ports[SPA_DIRECTION_INPUT], link) {
+ if (p->port_id != port_id)
+ continue;
+
+ pp = p->peer;
+ if (pp && pp->methods->reuse_buffer)
+ pp->methods->reuse_buffer(pp, buffer_id, pp->user_data);
+
+ break;
+ }
+}
+
+static const struct spa_node_callbacks node_callbacks = {
+ SPA_VERSION_NODE_CALLBACKS,
+ &on_node_done,
+ &on_node_event,
+ &on_node_need_input,
+ &on_node_have_output,
+ &on_node_reuse_buffer,
+};
+
+struct pw_node *
+pw_spa_node_new(struct pw_core *core,
+ struct pw_resource *owner,
+ const char *name,
+ bool async,
+ struct spa_node *node,
+ struct spa_clock *clock,
+ struct pw_properties *properties)
+{
+ struct pw_node *this;
+ struct impl *impl;
+
+ if (node->info) {
+ uint32_t i;
+
+ if (properties == NULL)
+ properties = pw_properties_new(NULL, NULL);
+
+ if (properties)
+ return NULL;
+
+ for (i = 0; i < node->info->n_items; i++)
+ pw_properties_set(properties,
+ node->info->items[i].key,
+ node->info->items[i].value);
+ }
+
+ this = pw_node_new(core, owner, name, properties, sizeof(struct impl));
+ if (this == NULL)
+ return NULL;
+
+ this->destroy = pw_spa_node_destroy;
+ this->implementation = &node_impl;
+ this->clock = clock;
+
+ impl = this->user_data;
+ impl->this = this;
+ impl->node = node;
+ impl->async_init = async;
+
+ if (spa_node_set_callbacks(impl->node, &node_callbacks, impl) < 0)
+ pw_log_warn("spa-node %p: error setting callback", this);
+
+ if (!async) {
+ complete_init(impl);
+ }
+
+ return this;
+}
+
+static int
+setup_props(struct pw_core *core, struct spa_node *spa_node, struct pw_properties *pw_props)
+{
+ int res;
+ struct spa_props *props;
+ void *state = NULL;
+ const char *key;
+
+ if ((res = spa_node_get_props(spa_node, &props)) != SPA_RESULT_OK) {
+ pw_log_debug("spa_node_get_props failed: %d", res);
+ return SPA_RESULT_ERROR;
+ }
+
+ while ((key = pw_properties_iterate(pw_props, &state))) {
+ struct spa_pod_prop *prop;
+ uint32_t id;
+
+ if (!spa_type_is_a(key, SPA_TYPE_PROPS_BASE))
+ continue;
+
+ id = spa_type_map_get_id(core->type.map, key);
+ if (id == SPA_ID_INVALID)
+ continue;
+
+ if ((prop = spa_pod_object_find_prop(&props->object, id))) {
+ const char *value = pw_properties_get(pw_props, key);
+
+ pw_log_info("configure prop %s", key);
+
+ switch(prop->body.value.type) {
+ case SPA_POD_TYPE_ID:
+ SPA_POD_VALUE(struct spa_pod_id, &prop->body.value) =
+ spa_type_map_get_id(core->type.map, value);
+ break;
+ case SPA_POD_TYPE_INT:
+ SPA_POD_VALUE(struct spa_pod_int, &prop->body.value) = atoi(value);
+ break;
+ case SPA_POD_TYPE_LONG:
+ SPA_POD_VALUE(struct spa_pod_long, &prop->body.value) = atoi(value);
+ break;
+ case SPA_POD_TYPE_FLOAT:
+ SPA_POD_VALUE(struct spa_pod_float, &prop->body.value) = atof(value);
+ break;
+ case SPA_POD_TYPE_DOUBLE:
+ SPA_POD_VALUE(struct spa_pod_double, &prop->body.value) = atof(value);
+ break;
+ case SPA_POD_TYPE_STRING:
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ if ((res = spa_node_set_props(spa_node, props)) != SPA_RESULT_OK) {
+ pw_log_debug("spa_node_set_props failed: %d", res);
+ return SPA_RESULT_ERROR;
+ }
+ return SPA_RESULT_OK;
+}
+
+
+struct pw_node *pw_spa_node_load(struct pw_core *core,
+ struct pw_resource *owner,
+ const char *lib,
+ const char *factory_name,
+ const char *name,
+ struct pw_properties *properties)
+{
+ struct pw_node *this;
+ struct impl *impl;
+ struct spa_node *spa_node;
+ struct spa_clock *spa_clock;
+ int res;
+ struct spa_handle *handle;
+ void *hnd;
+ uint32_t index;
+ spa_handle_factory_enum_func_t enum_func;
+ const struct spa_handle_factory *factory;
+ void *iface;
+ char *filename;
+ const char *dir;
+ bool async;
+
+ if ((dir = getenv("SPA_PLUGIN_DIR")) == NULL)
+ dir = PLUGINDIR;
+
+ asprintf(&filename, "%s/%s.so", dir, lib);
+
+ if ((hnd = dlopen(filename, RTLD_NOW)) == NULL) {
+ pw_log_error("can't load %s: %s", filename, dlerror());
+ goto open_failed;
+ }
+ if ((enum_func = dlsym(hnd, SPA_HANDLE_FACTORY_ENUM_FUNC_NAME)) == NULL) {
+ pw_log_error("can't find enum function");
+ goto no_symbol;
+ }
+
+ for (index = 0;; index++) {
+ if ((res = enum_func(&factory, index)) < 0) {
+ if (res != SPA_RESULT_ENUM_END)
+ pw_log_error("can't enumerate factories: %d", res);
+ goto enum_failed;
+ }
+ if (strcmp(factory->name, factory_name) == 0)
+ break;
+ }
+
+ handle = calloc(1, factory->size);
+ if ((res = spa_handle_factory_init(factory,
+ handle, NULL, core->support, core->n_support)) < 0) {
+ pw_log_error("can't make factory instance: %d", res);
+ goto init_failed;
+ }
+ async = SPA_RESULT_IS_ASYNC(res);
+
+ if ((res = spa_handle_get_interface(handle, core->type.spa_node, &iface)) < 0) {
+ pw_log_error("can't get node interface %d", res);
+ goto interface_failed;
+ }
+ spa_node = iface;
+
+ if ((res = spa_handle_get_interface(handle, core->type.spa_clock, &iface)) < 0) {
+ iface = NULL;
+ }
+ spa_clock = iface;
+
+ if (properties != NULL) {
+ if (setup_props(core, spa_node, properties) != SPA_RESULT_OK) {
+ pw_log_debug("Unrecognized properties");
+ }
+ }
+
+ this = pw_spa_node_new(core, owner, name, async, spa_node, spa_clock, properties);
+ impl->hnd = hnd;
+ impl->handle = handle;
+ impl->lib = filename;
+ impl->factory_name = strdup(factory_name);
+
+ return this;
+
+ interface_failed:
+ spa_handle_clear(handle);
+ init_failed:
+ free(handle);
+ enum_failed:
+ no_symbol:
+ dlclose(hnd);
+ open_failed:
+ free(filename);
+ return NULL;
+}
diff --git a/src/modules/spa/spa-node.h b/src/modules/spa/spa-node.h
new file mode 100644
index 00000000..35eba6a2
--- /dev/null
+++ b/src/modules/spa/spa-node.h
@@ -0,0 +1,51 @@
+/* PipeWire
+ * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __PIPEWIRE_SPA_NODE_H__
+#define __PIPEWIRE_SPA_NODE_H__
+
+#include <pipewire/core.h>
+#include <pipewire/node.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct pw_node *
+pw_spa_node_new(struct pw_core *core,
+ struct pw_resource *owner, /**< optional owner */
+ const char *name,
+ bool async,
+ struct spa_node *node,
+ struct spa_clock *clock,
+ struct pw_properties *properties);
+
+struct pw_node *
+pw_spa_node_load(struct pw_core *core,
+ struct pw_resource *owner, /**< optional owner */
+ const char *lib,
+ const char *factory_name,
+ const char *name,
+ struct pw_properties *properties);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __PIPEWIRE_SPA_NODE_H__ */
diff --git a/src/pipewire/.gitignore b/src/pipewire/.gitignore
new file mode 100644
index 00000000..7ed96236
--- /dev/null
+++ b/src/pipewire/.gitignore
@@ -0,0 +1,2 @@
+enumtypes.c
+enumtypes.h
diff --git a/src/pipewire/array.h b/src/pipewire/array.h
new file mode 100644
index 00000000..1f761161
--- /dev/null
+++ b/src/pipewire/array.h
@@ -0,0 +1,134 @@
+/* PipeWire
+ * Copyright (C) 2016 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __PIPEWIRE_ARRAY_H__
+#define __PIPEWIRE_ARRAY_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/defs.h>
+
+/** \class pw_array
+ *
+ * \brief An array object
+ *
+ * The array is a dynamically resizable data structure that can
+ * hold items of the same size.
+ */
+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_MEMBER((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 \memberof pw_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 \memberof pw_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 \memberof pw_array */
+#define pw_array_check_index(a,idx,t) pw_array_check_index_s(a,idx,sizeof(t))
+
+#define pw_array_for_each(pos, array) \
+ for (pos = (array)->data; \
+ (const uint8_t *) pos < ((const uint8_t *) (array)->data + (array)->size); \
+ (pos)++)
+
+/** Initialize the array with given extend \memberof pw_array */
+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);
+}
+
+/** Make sure \a size bytes can be added to the array \memberof pw_array */
+static inline bool 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);
+ while (alloc < need)
+ alloc *= 2;
+ if (SPA_UNLIKELY((data = realloc(arr->data, alloc)) == NULL))
+ return false;
+ arr->data = data;
+ arr->alloc = alloc;
+ }
+ return true;
+}
+
+/** Add \a ref size bytes to \a arr. A pointer to memory that can
+ * hold at least \a size bytes is returned \memberof pw_array */
+static inline void *pw_array_add(struct pw_array *arr, size_t size)
+{
+ void *p;
+
+ if (!pw_array_ensure_size(arr, size))
+ return NULL;
+
+ p = arr->data + arr->size;
+ 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 \memberof pw_array */
+static inline void *pw_array_add_fixed(struct pw_array *arr, size_t size)
+{
+ void *p;
+
+ if (SPA_UNLIKELY(arr->alloc < arr->size + size))
+ return NULL;
+
+ p = arr->data + arr->size;
+ arr->size += size;
+
+ return p;
+}
+
+/** Add a pointer to array \memberof pw_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/client.c b/src/pipewire/client.c
new file mode 100644
index 00000000..3a509d3b
--- /dev/null
+++ b/src/pipewire/client.c
@@ -0,0 +1,204 @@
+/* PipeWire
+ * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <string.h>
+
+#include "pipewire/pipewire.h"
+#include "pipewire/interfaces.h"
+
+#include "pipewire/client.h"
+#include "pipewire/resource.h"
+
+/** \cond */
+struct impl {
+ struct pw_client this;
+};
+/** \endcond */
+
+static void client_unbind_func(void *data)
+{
+ struct pw_resource *resource = data;
+ spa_list_remove(&resource->link);
+}
+
+static int
+client_bind_func(struct pw_global *global, struct pw_client *client, uint32_t version, uint32_t id)
+{
+ struct pw_client *this = global->object;
+ struct pw_resource *resource;
+
+ resource = pw_resource_new(client, id, global->type, 0);
+ if (resource == NULL)
+ goto no_mem;
+
+ pw_resource_set_implementation(resource, global->object, PW_VERSION_CLIENT, NULL, client_unbind_func);
+
+ pw_log_debug("client %p: bound to %d", global->object, resource->id);
+
+ spa_list_insert(this->resource_list.prev, &resource->link);
+
+ this->info.change_mask = ~0;
+ pw_client_notify_info(resource, &this->info);
+
+ return SPA_RESULT_OK;
+
+ no_mem:
+ pw_log_error("can't create client resource");
+ pw_core_notify_error(client->core_resource,
+ client->core_resource->id, SPA_RESULT_NO_MEMORY, "no memory");
+ return SPA_RESULT_NO_MEMORY;
+}
+
+/** Make a new client object
+ *
+ * \param core a \ref pw_core object to register the client with
+ * \param ucred a ucred structure or NULL when unknown
+ * \param properties optional client properties, ownership is taken
+ * \return a newly allocated client object
+ *
+ * \memberof pw_client
+ */
+struct pw_client *pw_client_new(struct pw_core *core,
+ struct ucred *ucred,
+ struct pw_properties *properties,
+ size_t user_data_size)
+{
+ struct pw_client *this;
+ struct impl *impl;
+
+ impl = calloc(1, sizeof(struct impl) + user_data_size);
+ if (impl == NULL)
+ return NULL;
+
+ pw_log_debug("client %p: new", impl);
+
+ this = &impl->this;
+ this->core = core;
+ if ((this->ucred_valid = (ucred != NULL)))
+ this->ucred = *ucred;
+ this->properties = properties;
+
+ if (user_data_size > 0)
+ this->user_data = SPA_MEMBER(impl, sizeof(struct impl), void);
+
+ spa_list_init(&this->resource_list);
+ pw_signal_init(&this->properties_changed);
+ pw_signal_init(&this->resource_added);
+ pw_signal_init(&this->resource_impl);
+ pw_signal_init(&this->resource_removed);
+ pw_signal_init(&this->busy_changed);
+
+ pw_map_init(&this->objects, 0, 32);
+ pw_map_init(&this->types, 0, 32);
+ pw_signal_init(&this->destroy_signal);
+
+ spa_list_insert(core->client_list.prev, &this->link);
+
+ pw_core_add_global(core, NULL, core->type.client, 0, this, client_bind_func, &this->global);
+
+ this->info.id = this->global->id;
+ this->info.props = this->properties ? &this->properties->dict : NULL;
+
+ return this;
+}
+
+static void destroy_resource(void *object, void *data)
+{
+ pw_resource_destroy(object);
+}
+
+/** Destroy a client object
+ *
+ * \param client the client to destroy
+ *
+ * \memberof pw_client
+ */
+void pw_client_destroy(struct pw_client *client)
+{
+ struct pw_resource *resource, *tmp;
+ struct impl *impl = SPA_CONTAINER_OF(client, struct impl, this);
+
+ pw_log_debug("client %p: destroy", client);
+ pw_signal_emit(&client->destroy_signal, client);
+
+ spa_list_remove(&client->link);
+ pw_global_destroy(client->global);
+
+ spa_list_for_each_safe(resource, tmp, &client->resource_list, link)
+ pw_resource_destroy(resource);
+
+ pw_map_for_each(&client->objects, destroy_resource, client);
+
+ pw_log_debug("client %p: free", impl);
+
+ if (client->destroy)
+ client->destroy(client);
+
+ pw_map_clear(&client->objects);
+ pw_map_clear(&client->types);
+
+ if (client->properties)
+ pw_properties_free(client->properties);
+
+ free(impl);
+}
+
+/** Update client properties
+ *
+ * \param client the client
+ * \param dict a \ref 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.
+ *
+ * \memberof pw_client
+ */
+void pw_client_update_properties(struct pw_client *client, const struct spa_dict *dict)
+{
+ struct pw_resource *resource;
+
+ if (client->properties == NULL) {
+ if (dict)
+ client->properties = pw_properties_new_dict(dict);
+ } else {
+ uint32_t i;
+
+ for (i = 0; i < dict->n_items; i++)
+ pw_properties_set(client->properties,
+ dict->items[i].key, dict->items[i].value);
+ }
+
+ client->info.change_mask = 1 << 0;
+ client->info.props = client->properties ? &client->properties->dict : NULL;
+
+ pw_signal_emit(&client->properties_changed, client);
+
+ spa_list_for_each(resource, &client->resource_list, link) {
+ pw_client_notify_info(resource, &client->info);
+ }
+}
+
+void pw_client_set_busy(struct pw_client *client, bool busy)
+{
+ if (client->busy != busy) {
+ client->busy = busy;
+ pw_signal_emit(&client->busy_changed, client);
+ }
+}
diff --git a/src/pipewire/client.h b/src/pipewire/client.h
new file mode 100644
index 00000000..9de1388d
--- /dev/null
+++ b/src/pipewire/client.h
@@ -0,0 +1,142 @@
+/* PipeWire
+ * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __PIPEWIRE_CLIENT_H__
+#define __PIPEWIRE_CLIENT_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef __USE_GNU
+#define __USE_GNU
+#endif
+
+#include <sys/socket.h>
+
+#include <pipewire/core.h>
+#include <pipewire/introspect.h>
+#include <pipewire/properties.h>
+#include <pipewire/sig.h>
+#include <pipewire/resource.h>
+
+/** \page page_client Client
+ *
+ * \section sec_page_client_overview Overview
+ *
+ * The \ref pw_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_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. See also \ref page_access
+ *
+ * \section sec_page_client_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. See also \ref page_types.
+ *
+ * \section sec_page_client_resources Resources
+ *
+ * When a client binds to core 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 page_resource
+ */
+
+/** \class pw_client
+ *
+ * \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.
+ */
+struct pw_client {
+ struct pw_core *core; /**< core object */
+ struct spa_list link; /**< link in core object client list */
+ struct pw_global *global; /**< global object created for this client */
+
+ struct pw_properties *properties; /**< Client properties */
+ /** Emited when the properties changed */
+ PW_SIGNAL(properties_changed, (struct pw_listener *listener, struct pw_client *client));
+
+ struct pw_client_info info; /**< client info */
+ bool ucred_valid; /**< if the ucred member is valid */
+ struct ucred ucred; /**< ucred information */
+
+ struct pw_resource *core_resource; /**< core resource object */
+
+ struct pw_map objects; /**< list of resource objects */
+ uint32_t n_types; /**< number of client types */
+ struct pw_map types; /**< map of client types */
+
+ struct spa_list resource_list; /**< The list of resources of this client */
+ /** Emited when a resource is added */
+ PW_SIGNAL(resource_added, (struct pw_listener *listener,
+ struct pw_client *client, struct pw_resource *resource));
+ /** Emited when a resource implementation is set */
+ PW_SIGNAL(resource_impl, (struct pw_listener *listener,
+ struct pw_client *client, struct pw_resource *resource));
+ /** Emited when a resource is removed */
+ PW_SIGNAL(resource_removed, (struct pw_listener *listener,
+ struct pw_client *client, struct pw_resource *resource));
+
+ bool busy;
+ /** Emited when the client starts/stops an async operation that should
+ * block/resume all methods for this client */
+ PW_SIGNAL(busy_changed, (struct pw_listener *listener, struct pw_client *client));
+
+ /** Emited when the client is destroyed */
+ PW_SIGNAL(destroy_signal, (struct pw_listener *listener, struct pw_client *client));
+
+ struct pw_protocol *protocol; /**< protocol in use */
+ void *protocol_private; /**< private data for the protocol */
+
+ void *user_data; /**< extra user data */
+ pw_destroy_t destroy; /**< function to clean up the object */
+};
+
+struct pw_client *
+pw_client_new(struct pw_core *core,
+ struct ucred *ucred,
+ struct pw_properties *properties,
+ size_t user_data_size);
+
+void
+pw_client_destroy(struct pw_client *client);
+
+void
+pw_client_update_properties(struct pw_client *client, const struct spa_dict *dict);
+
+void pw_client_set_busy(struct pw_client *client, bool busy);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __PIPEWIRE_CLIENT_H__ */
diff --git a/src/pipewire/command.c b/src/pipewire/command.c
new file mode 100644
index 00000000..a3b6d835
--- /dev/null
+++ b/src/pipewire/command.c
@@ -0,0 +1,160 @@
+/* PipeWire
+ * Copyright (C) 2016 Axis Communications <dev-gstreamer@axis.com>
+ * @author Linus Svensson <linus.svensson@axis.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <string.h>
+
+#include <pipewire/pipewire.h>
+#include <pipewire/utils.h>
+#include <pipewire/module.h>
+
+#include "command.h"
+
+/** \cond */
+typedef bool(*pw_command_func_t) (struct pw_command *command, struct pw_core *core, char **err);
+
+static bool execute_command_module_load(struct pw_command *command,
+ struct pw_core *core, char **err);
+
+typedef struct pw_command *(*pw_command_parse_func_t) (const char *line, char **err);
+
+static struct pw_command *parse_command_module_load(const char *line, char **err);
+
+struct impl {
+ struct pw_command this;
+
+ pw_command_func_t func;
+ char **args;
+ int n_args;
+};
+
+struct command_parse {
+ const char *name;
+ pw_command_parse_func_t func;
+};
+
+static const struct command_parse parsers[] = {
+ {"load-module", parse_command_module_load},
+ {NULL, NULL}
+};
+
+static const char whitespace[] = " \t";
+/** \endcond */
+
+static struct pw_command *parse_command_module_load(const char *line, char **err)
+{
+ struct impl *impl;
+
+ impl = calloc(1, sizeof(struct impl));
+ if (impl == NULL)
+ goto no_mem;
+
+ impl->func = execute_command_module_load;
+ impl->args = pw_split_strv(line, whitespace, 3, &impl->n_args);
+
+ if (impl->args[1] == NULL)
+ goto no_module;
+
+ impl->this.name = impl->args[0];
+
+ return &impl->this;
+
+ no_module:
+ asprintf(err, "%s requires a module name", impl->args[0]);
+ pw_free_strv(impl->args);
+ return NULL;
+ no_mem:
+ asprintf(err, "no memory");
+ return NULL;
+}
+
+static bool
+execute_command_module_load(struct pw_command *command, struct pw_core *core, char **err)
+{
+ struct impl *impl = SPA_CONTAINER_OF(command, struct impl, this);
+
+ return pw_module_load(core, impl->args[1], impl->args[2], err) != NULL;
+}
+
+/** Free command
+ *
+ * \param command a command to free
+ *
+ * Free all resources assicated with \a command.
+ *
+ * \memberof pw_command
+ */
+void pw_command_free(struct pw_command *command)
+{
+ struct impl *impl = SPA_CONTAINER_OF(command, struct impl, this);
+
+ spa_list_remove(&command->link);
+ pw_free_strv(impl->args);
+ free(impl);
+}
+
+/** Parses a command line
+ * \param line command line to parse
+ * \param[out] err Return location for an error
+ * \return The command or NULL when \a err is set.
+ *
+ * Parses a command line, \a line, and return the parsed command.
+ * A command can later be executed with \ref pw_command_run()
+ *
+ * \memberof pw_command
+ */
+struct pw_command *pw_command_parse(const char *line, char **err)
+{
+ struct pw_command *command = NULL;
+ const struct command_parse *parse;
+ char *name;
+ size_t len;
+
+ len = strcspn(line, whitespace);
+
+ name = strndup(line, len);
+
+ for (parse = parsers; parse->name != NULL; parse++) {
+ if (strcmp(name, parse->name) == 0) {
+ command = parse->func(line, err);
+ goto out;
+ }
+ }
+
+ asprintf(err, "Command \"%s\" does not exist", name);
+ out:
+ free(name);
+ return command;
+}
+
+/** Run a command
+ *
+ * \param command: A \ref pw_command
+ * \param core: A \ref pw_core
+ * \param err: Return location for an error string, or NULL
+ * \return true if \a command was executed successfully, false otherwise.
+ *
+ * \memberof pw_command
+ */
+bool pw_command_run(struct pw_command *command, struct pw_core *core, char **err)
+{
+ struct impl *impl = SPA_CONTAINER_OF(command, struct impl, this);
+
+ return impl->func(command, core, err);
+}
diff --git a/src/pipewire/command.h b/src/pipewire/command.h
new file mode 100644
index 00000000..d64f3c69
--- /dev/null
+++ b/src/pipewire/command.h
@@ -0,0 +1,54 @@
+/* PipeWire
+ * Copyright (C) 2016 Axis Communications <dev-gstreamer@axis.com>
+ * @author Linus Svensson <linus.svensson@axis.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __PIPEWIRE_COMMAND_H__
+#define __PIPEWIRE_COMMAND_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <pipewire/core.h>
+
+/** \class pw_command
+ *
+ * A configuration command
+ */
+struct pw_command {
+ struct spa_list link; /**< link in list of commands */
+
+ const char *name; /**< command name */
+};
+
+
+struct pw_command *
+pw_command_parse(const char *line, char **err);
+
+void
+pw_command_free(struct pw_command *command);
+
+bool
+pw_command_run(struct pw_command *command, struct pw_core *core, char **err);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __PIPEWIRE_COMMAND_H__ */
diff --git a/src/pipewire/core.c b/src/pipewire/core.c
new file mode 100644
index 00000000..c654fa6f
--- /dev/null
+++ b/src/pipewire/core.c
@@ -0,0 +1,716 @@
+/* PipeWire
+ * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+#include <time.h>
+
+#include <spa/lib/debug.h>
+#include <spa/format-utils.h>
+
+#include <pipewire/pipewire.h>
+#include <pipewire/interfaces.h>
+#include <pipewire/core.h>
+#include <pipewire/data-loop.h>
+
+/** \cond */
+struct global_impl {
+ struct pw_global this;
+ pw_bind_func_t bind;
+};
+
+struct impl {
+ struct pw_core this;
+
+ struct spa_support support[4];
+ struct pw_data_loop *data_loop;
+};
+
+/** \endcond */
+
+static bool pw_global_is_visible(struct pw_global *global,
+ struct pw_client *client)
+{
+ struct pw_core *core = client->core;
+
+ return (core->global_filter == NULL ||
+ core->global_filter(global, client, core->global_filter_data));
+}
+
+static void registry_bind(void *object, uint32_t id, uint32_t version, uint32_t new_id)
+{
+ struct pw_resource *resource = object;
+ struct pw_client *client = resource->client;
+ struct pw_core *core = resource->core;
+ struct pw_global *global;
+
+ spa_list_for_each(global, &core->global_list, link) {
+ if (global->id == id)
+ break;
+ }
+ if (&global->link == &core->global_list)
+ goto no_id;
+
+ if (!pw_global_is_visible(global, client))
+ goto no_id;
+
+ pw_log_debug("global %p: bind object id %d to %d", global, id, new_id);
+ pw_global_bind(global, client, version, new_id);
+
+ return;
+
+ no_id:
+ pw_log_debug("registry %p: no global with id %u to bind to %u", resource, id, new_id);
+ /* 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_notify_remove_id(client->core_resource, new_id);
+ return;
+}
+
+static struct pw_registry_methods registry_methods = {
+ &registry_bind
+};
+
+static void destroy_registry_resource(void *object)
+{
+ struct pw_resource *resource = object;
+ spa_list_remove(&resource->link);
+}
+
+static void core_client_update(void *object, const struct spa_dict *props)
+{
+ struct pw_resource *resource = object;
+
+ pw_client_update_properties(resource->client, props);
+}
+
+static void core_sync(void *object, uint32_t seq)
+{
+ struct pw_resource *resource = object;
+
+ pw_core_notify_done(resource, seq);
+}
+
+static void core_get_registry(void *object, uint32_t new_id)
+{
+ struct pw_resource *resource = object;
+ struct pw_client *client = resource->client;
+ struct pw_core *this = resource->core;
+ struct pw_global *global;
+ struct pw_resource *registry_resource;
+
+ registry_resource = pw_resource_new(client,
+ new_id,
+ this->type.registry,
+ 0);
+ if (registry_resource == NULL)
+ goto no_mem;
+
+ pw_resource_set_implementation(registry_resource,
+ this,
+ PW_VERSION_REGISTRY,
+ &registry_methods,
+ destroy_registry_resource);
+
+ spa_list_insert(this->registry_resource_list.prev, &registry_resource->link);
+
+ spa_list_for_each(global, &this->global_list, link) {
+ if (pw_global_is_visible(global, client))
+ pw_registry_notify_global(registry_resource,
+ global->id,
+ spa_type_map_get_type(this->type.map,
+ global->type),
+ global->version);
+ }
+
+ return;
+
+ no_mem:
+ pw_log_error("can't create registry resource");
+ pw_core_notify_error(client->core_resource,
+ resource->id, SPA_RESULT_NO_MEMORY, "no memory");
+}
+
+static void
+core_create_node(void *object,
+ const char *factory_name,
+ const char *name,
+ const struct spa_dict *props,
+ uint32_t new_id)
+{
+ struct pw_resource *resource = object;
+ struct pw_resource *node_resource;
+ struct pw_client *client = resource->client;
+ struct pw_node_factory *factory;
+ struct pw_properties *properties;
+
+ factory = pw_core_find_node_factory(client->core, factory_name);
+ if (factory == NULL)
+ goto no_factory;
+
+ node_resource = pw_resource_new(client,
+ new_id,
+ factory->type,
+ 0);
+ if (node_resource == NULL)
+ goto no_resource;
+
+ if (props) {
+ properties = pw_properties_new_dict(props);
+ if (properties == NULL)
+ goto no_properties;
+ } else
+ properties = NULL;
+
+ /* error will be posted */
+ pw_node_factory_create_node(factory, node_resource, name, properties);
+ properties = NULL;
+
+ done:
+ return;
+
+ no_factory:
+ pw_log_error("can't find node factory");
+ pw_core_notify_error(client->core_resource,
+ resource->id, SPA_RESULT_INVALID_ARGUMENTS, "unknown factory name");
+ goto done;
+
+ no_resource:
+ pw_log_error("can't create resource");
+ goto no_mem;
+ no_properties:
+ pw_log_error("can't create properties");
+ pw_resource_destroy(node_resource);
+ goto no_mem;
+ no_mem:
+ pw_core_notify_error(client->core_resource,
+ resource->id, SPA_RESULT_NO_MEMORY, "no memory");
+ goto done;
+}
+
+static void
+core_create_link(void *object,
+ uint32_t output_node_id,
+ uint32_t output_port_id,
+ uint32_t input_node_id,
+ uint32_t input_port_id,
+ const struct spa_format *filter,
+ const struct spa_dict *props,
+ uint32_t new_id)
+{
+ struct pw_resource *resource = object;
+ struct pw_client *client = resource->client;
+
+ pw_log_error("can't create link");
+ pw_core_notify_error(client->core_resource,
+ resource->id, SPA_RESULT_NOT_IMPLEMENTED, "not implemented");
+}
+
+static void core_update_types(void *object, uint32_t first_id, uint32_t n_types, const char **types)
+{
+ struct pw_resource *resource = object;
+ struct pw_core *this = resource->core;
+ struct pw_client *client = resource->client;
+ int i;
+
+ for (i = 0; i < n_types; i++, first_id++) {
+ uint32_t this_id = spa_type_map_get_id(this->type.map, types[i]);
+ if (!pw_map_insert_at(&client->types, first_id, PW_MAP_ID_TO_PTR(this_id)))
+ pw_log_error("can't add type for client");
+ }
+}
+
+static const struct pw_core_methods core_methods = {
+ &core_update_types,
+ &core_sync,
+ &core_get_registry,
+ &core_client_update,
+ &core_create_node,
+ &core_create_link
+};
+
+static void core_unbind_func(void *data)
+{
+ struct pw_resource *resource = data;
+ resource->client->core_resource = NULL;
+ spa_list_remove(&resource->link);
+}
+
+static int
+core_bind_func(struct pw_global *global, struct pw_client *client, uint32_t version, uint32_t id)
+{
+ struct pw_core *this = global->object;
+ struct pw_resource *resource;
+
+ resource = pw_resource_new(client, id, global->type, 0);
+ if (resource == NULL)
+ goto no_mem;
+
+ pw_resource_set_implementation(resource, global->object,
+ PW_VERSION_CORE, &core_methods, core_unbind_func);
+
+ spa_list_insert(this->resource_list.prev, &resource->link);
+ client->core_resource = resource;
+
+ pw_log_debug("core %p: bound to %d", global->object, resource->id);
+
+ this->info.change_mask = PW_CORE_CHANGE_MASK_ALL;
+ pw_core_notify_info(resource, &this->info);
+
+ return SPA_RESULT_OK;
+
+ no_mem:
+ pw_log_error("can't create core resource");
+ return SPA_RESULT_NO_MEMORY;
+}
+
+/** Create a new core object
+ *
+ * \param main_loop the main loop to use
+ * \param properties extra properties for the core, ownership it taken
+ * \return a newly allocated core object
+ *
+ * \memberof pw_core
+ */
+struct pw_core *pw_core_new(struct pw_loop *main_loop, struct pw_properties *properties)
+{
+ struct impl *impl;
+ struct pw_core *this;
+
+ impl = calloc(1, sizeof(struct impl));
+ if (impl == NULL)
+ return NULL;
+
+ this = &impl->this;
+ impl->data_loop = pw_data_loop_new();
+ if (impl->data_loop == NULL)
+ goto no_data_loop;
+
+ this->data_loop = impl->data_loop->loop;
+ this->main_loop = main_loop;
+ this->properties = properties;
+
+ pw_type_init(&this->type);
+ pw_map_init(&this->objects, 128, 32);
+
+ spa_graph_init(&this->rt.graph);
+ spa_graph_scheduler_init(&this->rt.sched, &this->rt.graph);
+
+ spa_debug_set_type_map(this->type.map);
+
+ impl->support[0] = SPA_SUPPORT_INIT(SPA_TYPE__TypeMap, this->type.map);
+ impl->support[1] = SPA_SUPPORT_INIT(SPA_TYPE_LOOP__DataLoop, this->data_loop->loop);
+ impl->support[2] = SPA_SUPPORT_INIT(SPA_TYPE_LOOP__MainLoop, this->main_loop->loop);
+ impl->support[3] = SPA_SUPPORT_INIT(SPA_TYPE__Log, pw_log_get());
+ this->support = impl->support;
+ this->n_support = 4;
+
+ pw_data_loop_start(impl->data_loop);
+
+ spa_list_init(&this->remote_list);
+ spa_list_init(&this->resource_list);
+ spa_list_init(&this->registry_resource_list);
+ spa_list_init(&this->global_list);
+ spa_list_init(&this->client_list);
+ spa_list_init(&this->node_list);
+ spa_list_init(&this->node_factory_list);
+ spa_list_init(&this->link_list);
+ pw_signal_init(&this->info_changed);
+ pw_signal_init(&this->destroy_signal);
+ pw_signal_init(&this->global_added);
+ pw_signal_init(&this->global_removed);
+
+ pw_core_add_global(this, NULL, this->type.core, 0, this, core_bind_func, &this->global);
+
+ this->info.id = this->global->id;
+ this->info.change_mask = 0;
+ this->info.user_name = pw_get_user_name();
+ this->info.host_name = pw_get_host_name();
+ this->info.version = "0";
+ this->info.name = "pipewire-0";
+ srandom(time(NULL));
+ this->info.cookie = random();
+ this->info.props = this->properties ? &this->properties->dict : NULL;
+
+ return this;
+
+ no_data_loop:
+ free(impl);
+ return NULL;
+}
+
+/** Destroy a core object
+ *
+ * \param core a core to destroy
+ *
+ * \memberof pw_core
+ */
+void pw_core_destroy(struct pw_core *core)
+{
+ struct impl *impl = SPA_CONTAINER_OF(core, struct impl, this);
+
+ pw_log_debug("core %p: destroy", core);
+ pw_signal_emit(&core->destroy_signal, core);
+
+ pw_data_loop_destroy(impl->data_loop);
+
+ pw_map_clear(&core->objects);
+
+ pw_log_debug("core %p: free", core);
+ free(impl);
+}
+
+/** Create and add a new global to the core
+ *
+ * \param core a core
+ * \param owner an optional owner of the global
+ * \param type the type of the global
+ * \param version the version
+ * \param object the associated object
+ * \param bind a function to bind to this global
+ * \param[out] global a result global
+ * \return true on success
+ *
+ * \memberof pw_core
+ */
+bool
+pw_core_add_global(struct pw_core *core,
+ struct pw_resource *owner,
+ uint32_t type,
+ uint32_t version,
+ void *object,
+ pw_bind_func_t bind,
+ struct pw_global **global)
+{
+ struct global_impl *impl;
+ struct pw_global *this;
+ struct pw_resource *registry;
+ const char *type_name;
+
+ impl = calloc(1, sizeof(struct global_impl));
+ if (impl == NULL)
+ return false;
+
+ this = &impl->this;
+ impl->bind = bind;
+
+ this->core = core;
+ this->owner = owner;
+ this->type = type;
+ this->version = version;
+ this->object = object;
+ *global = this;
+
+ pw_signal_init(&this->destroy_signal);
+
+ this->id = pw_map_insert_new(&core->objects, this);
+
+ spa_list_insert(core->global_list.prev, &this->link);
+ pw_signal_emit(&core->global_added, core, this);
+
+ type_name = spa_type_map_get_type(core->type.map, this->type);
+ pw_log_debug("global %p: new %u %s, owner %p", this, this->id, type_name, owner);
+
+ spa_list_for_each(registry, &core->registry_resource_list, link)
+ if (pw_global_is_visible(this, registry->client))
+ pw_registry_notify_global(registry, this->id, type_name, this->version);
+
+ return true;
+}
+
+/** Bind to a global
+ *
+ * \param global the global to bind to
+ * \param client the client that binds
+ * \param version the version
+ * \param id the id
+ *
+ * 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.
+ *
+ * \memberof pw_global
+ */
+int
+pw_global_bind(struct pw_global *global, struct pw_client *client, uint32_t version, uint32_t id)
+{
+ int res;
+ struct global_impl *impl = SPA_CONTAINER_OF(global, struct global_impl, this);
+
+ if (impl->bind) {
+ res = impl->bind(global, client, version, id);
+ } else {
+ res = SPA_RESULT_NOT_IMPLEMENTED;
+ pw_core_notify_error(client->core_resource,
+ client->core_resource->id, res, "can't bind object id %d", id);
+ }
+ return res;
+}
+
+/** Destroy a global
+ *
+ * \param global a global to destroy
+ *
+ * \memberof pw_global
+ */
+void pw_global_destroy(struct pw_global *global)
+{
+ struct pw_core *core = global->core;
+ struct pw_resource *registry;
+
+ pw_log_debug("global %p: destroy %u", global, global->id);
+ pw_signal_emit(&global->destroy_signal, global);
+
+ spa_list_for_each(registry, &core->registry_resource_list, link)
+ if (pw_global_is_visible(global, registry->client))
+ pw_registry_notify_global_remove(registry, global->id);
+
+ pw_map_remove(&core->objects, global->id);
+
+ spa_list_remove(&global->link);
+ pw_signal_emit(&core->global_removed, core, global);
+
+ pw_log_debug("global %p: free", global);
+ free(global);
+}
+
+/** Update core properties
+ *
+ * \param core a core
+ * \param dict properties to update
+ *
+ * Update the core object with the given properties
+ *
+ * \memberof pw_core
+ */
+void pw_core_update_properties(struct pw_core *core, const struct spa_dict *dict)
+{
+ struct pw_resource *resource;
+
+ if (core->properties == NULL) {
+ if (dict)
+ core->properties = pw_properties_new_dict(dict);
+ } else if (dict != &core->properties->dict) {
+ uint32_t i;
+
+ for (i = 0; i < dict->n_items; i++)
+ pw_properties_set(core->properties,
+ dict->items[i].key, dict->items[i].value);
+ }
+
+ core->info.change_mask = PW_CORE_CHANGE_MASK_PROPS;
+ core->info.props = core->properties ? &core->properties->dict : NULL;
+
+ pw_signal_emit(&core->info_changed, core);
+
+ spa_list_for_each(resource, &core->resource_list, link) {
+ pw_core_notify_info(resource, &core->info);
+ }
+}
+
+/** Find a port to link with
+ *
+ * \param core a core
+ * \param other_port a port to find a link with
+ * \param id the id of a port or SPA_ID_INVALID
+ * \param props extra properties
+ * \param n_format_filters number of filters
+ * \param format_filters array of format filters
+ * \param[out] error an error when something is wrong
+ * \return a port that can be used to link to \a otherport or NULL on error
+ *
+ * \memberof pw_core
+ */
+struct pw_port *pw_core_find_port(struct pw_core *core,
+ struct pw_port *other_port,
+ uint32_t id,
+ struct pw_properties *props,
+ uint32_t n_format_filters,
+ struct spa_format **format_filters,
+ char **error)
+{
+ struct pw_port *best = NULL;
+ bool have_id;
+ struct pw_node *n;
+
+ have_id = id != SPA_ID_INVALID;
+
+ pw_log_debug("id \"%u\", %d", id, have_id);
+
+ spa_list_for_each(n, &core->node_list, link) {
+ if (n->global == NULL)
+ continue;
+
+ pw_log_debug("node id \"%d\"", n->global->id);
+
+ if (have_id) {
+ if (n->global->id == id) {
+ pw_log_debug("id \"%u\" matches node %p", id, n);
+
+ best =
+ pw_node_get_free_port(n,
+ pw_direction_reverse(other_port->
+ direction));
+ if (best)
+ break;
+ }
+ } else {
+ struct pw_port *p, *pin, *pout;
+
+ p = pw_node_get_free_port(n, pw_direction_reverse(other_port->direction));
+ if (p == NULL)
+ continue;
+
+ if (p->direction == PW_DIRECTION_OUTPUT) {
+ pin = other_port;
+ pout = p;
+ } else {
+ pin = p;
+ pout = other_port;
+ }
+
+ if (pw_core_find_format(core,
+ pout,
+ pin,
+ props,
+ n_format_filters, format_filters, error) == NULL)
+ continue;
+
+ best = p;
+ }
+ }
+ if (best == NULL) {
+ asprintf(error, "No matching Node found");
+ }
+ return best;
+}
+
+/** Find a common format between two ports
+ *
+ * \param core a core 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] 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.
+ *
+ * \memberof pw_core
+ */
+struct spa_format *pw_core_find_format(struct pw_core *core,
+ struct pw_port *output,
+ struct pw_port *input,
+ struct pw_properties *props,
+ uint32_t n_format_filters,
+ struct spa_format **format_filters,
+ char **error)
+{
+ uint32_t out_state, in_state;
+ int res;
+ struct spa_format *filter = NULL, *format;
+ uint32_t iidx = 0, oidx = 0;
+
+ out_state = output->state;
+ in_state = input->state;
+
+ pw_log_debug("core %p: finding best format %d %d", core, 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_PORT_STATE_CONFIGURE && output->node->info.state == PW_NODE_STATE_IDLE)
+ out_state = PW_PORT_STATE_CONFIGURE;
+ if (in_state > PW_PORT_STATE_CONFIGURE && input->node->info.state == PW_NODE_STATE_IDLE)
+ in_state = PW_PORT_STATE_CONFIGURE;
+
+ if (in_state == PW_PORT_STATE_CONFIGURE && out_state > PW_PORT_STATE_CONFIGURE) {
+ /* only input needs format */
+ if ((res = pw_port_get_format(output, (const struct spa_format **) &format)) < 0) {
+ asprintf(error, "error get output format: %d", res);
+ goto error;
+ }
+ } else if (out_state == PW_PORT_STATE_CONFIGURE && in_state > PW_PORT_STATE_CONFIGURE) {
+ /* only output needs format */
+ if ((res = pw_port_get_format(input, (const struct spa_format **) &format)) < 0) {
+ asprintf(error, "error get input format: %d", res);
+ goto error;
+ }
+ } else if (in_state == PW_PORT_STATE_CONFIGURE && out_state == PW_PORT_STATE_CONFIGURE) {
+ again:
+ /* both ports need a format */
+ pw_log_debug("core %p: finding best format", core);
+ if ((res = pw_port_enum_formats(input, &filter, NULL, iidx)) < 0) {
+ if (res == SPA_RESULT_ENUM_END && iidx != 0) {
+ asprintf(error, "error input enum formats: %d", res);
+ goto error;
+ }
+ }
+ pw_log_debug("Try filter: %p", filter);
+ if (pw_log_level_enabled(SPA_LOG_LEVEL_DEBUG))
+ spa_debug_format(filter);
+
+ if ((res = pw_port_enum_formats(output, &format, filter, oidx)) < 0) {
+ if (res == SPA_RESULT_ENUM_END) {
+ oidx = 0;
+ iidx++;
+ goto again;
+ }
+ asprintf(error, "error output enum formats: %d", res);
+ goto error;
+ }
+ pw_log_debug("Got filtered:");
+ if (pw_log_level_enabled(SPA_LOG_LEVEL_DEBUG))
+ spa_debug_format(format);
+
+ spa_format_fixate(format);
+ } else {
+ asprintf(error, "error node state");
+ goto error;
+ }
+ if (format == NULL) {
+ asprintf(error, "error get format");
+ goto error;
+ }
+ return format;
+
+ error:
+ return NULL;
+}
+
+/** Find a node by name
+ *
+ * \param core the core object
+ * \param name the name of the node to find
+ *
+ * Find in the list of nodes registered in \a core for one with
+ * the given \a name.
+ *
+ * \memberof pw_core
+ */
+struct pw_node_factory *pw_core_find_node_factory(struct pw_core *core, const char *name)
+{
+ struct pw_node_factory *factory;
+
+ spa_list_for_each(factory, &core->node_factory_list, link) {
+ if (strcmp(factory->name, name) == 0)
+ return factory;
+ }
+ return NULL;
+}
diff --git a/src/pipewire/core.h b/src/pipewire/core.h
new file mode 100644
index 00000000..0cccc764
--- /dev/null
+++ b/src/pipewire/core.h
@@ -0,0 +1,243 @@
+/* PipeWire
+ * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __PIPEWIRE_CORE_H__
+#define __PIPEWIRE_CORE_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/log.h>
+#include <spa/graph-scheduler3.h>
+
+struct pw_global;
+
+#include <pipewire/type.h>
+#include <pipewire/interfaces.h>
+
+#include <pipewire/main-loop.h>
+#include <pipewire/data-loop.h>
+#include <pipewire/resource.h>
+#include <pipewire/node.h>
+#include <pipewire/link.h>
+#include <pipewire/node-factory.h>
+
+/** \page page_server_api Server API
+ *
+ * \section page_server_overview Overview
+ *
+ * \subpage page_core
+ *
+ * \subpage page_registry
+ *
+ * \subpage page_global
+ *
+ * \subpage page_client
+ *
+ * \subpage page_resource
+ *
+ * \subpage page_node
+ *
+ * \subpage page_port
+ *
+ * \subpage page_link
+ */
+
+/** \page page_core Core
+ *
+ * \section page_core_overview Overview
+ *
+ * The core object is a singleton object that manages the state and
+ * resources of the PipeWire server.
+ */
+/** \page page_registry Registry
+ *
+ * \section page_registry_overview Overview
+ *
+ * The registry object is a singleton object that keeps track of
+ * global objects on the PipeWire server. See also \ref page_global.
+ *
+ * Global objects typically represent an actual object in the
+ * server (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.
+ */
+typedef int (*pw_bind_func_t) (struct pw_global *global,
+ struct pw_client *client, uint32_t version, uint32_t id);
+
+typedef bool (*pw_global_filter_func_t) (struct pw_global *global,
+ struct pw_client *client, void *data);
+
+/** \page page_global Global
+ *
+ * Global objects represent resources that are available on the server and
+ * accessible to clients.
+ * Globals come and go when devices or other resources become available for
+ * clients.
+ *
+ * The client receives a list of globals when it binds to the registry
+ * object. See \ref page_registry.
+ *
+ * A client can bind to a global to send methods or receive events from
+ * the global.
+ */
+/** \class pw_global
+ *
+ * \brief A global object visible to all clients
+ *
+ * A global object is visible to all clients and represents a resource
+ * that can be used or inspected.
+ *
+ * See \ref page_server_api
+ */
+struct pw_global {
+ struct pw_core *core; /**< the core */
+ struct pw_resource *owner; /**< the owner of this object, NULL when the
+ * PipeWire server is the owner */
+
+ struct spa_list link; /**< link in core list of globals */
+ uint32_t id; /**< server id of the object */
+ uint32_t type; /**< type of the object */
+ uint32_t version; /**< version of the object */
+ void *object; /**< object associated with the global */
+
+ /** Emited when the global is destroyed */
+ PW_SIGNAL(destroy_signal, (struct pw_listener *listener, struct pw_global *global));
+};
+
+/** \class pw_core
+ *
+ * \brief the core PipeWire object
+ *
+ * The server core object manages all resources available on the
+ * server.
+ *
+ * See \ref page_server_api
+ */
+struct pw_core {
+ struct pw_global *global; /**< the global of the core */
+
+ struct pw_core_info info; /**< info about the core */
+ /** Emited when the core info is updated */
+ PW_SIGNAL(info_changed, (struct pw_listener *listener, struct pw_core *core));
+
+ struct pw_properties *properties; /**< properties of the core */
+
+ struct pw_type type; /**< type map and common types */
+
+ pw_global_filter_func_t global_filter;
+ void *global_filter_data;
+
+ struct pw_map objects; /**< map of known objects */
+
+ struct spa_list remote_list; /**< list of remote connections */
+ struct spa_list resource_list; /**< list of core resources */
+ struct spa_list registry_resource_list; /**< list of registry resources */
+ 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 node_factory_list; /**< list of node factories */
+ struct spa_list link_list; /**< list of links */
+
+ struct pw_loop *main_loop; /**< main loop for control */
+ struct pw_loop *data_loop; /**< data loop for data passing */
+
+ struct spa_support *support; /**< support for spa plugins */
+ uint32_t n_support; /**< number of support items */
+
+ /** Emited when the core is destroyed */
+ PW_SIGNAL(destroy_signal, (struct pw_listener *listener, struct pw_core *core));
+
+ /** Emited when a global is added */
+ PW_SIGNAL(global_added, (struct pw_listener *listener,
+ struct pw_core *core, struct pw_global *global));
+ /** Emited when a global is removed */
+ PW_SIGNAL(global_removed, (struct pw_listener *listener,
+ struct pw_core *core, struct pw_global *global));
+
+ struct {
+ struct spa_graph_scheduler sched;
+ struct spa_graph graph;
+ } rt;
+};
+
+struct pw_core *
+pw_core_new(struct pw_loop *main_loop, struct pw_properties *props);
+
+void
+pw_core_destroy(struct pw_core *core);
+
+void
+pw_core_update_properties(struct pw_core *core, const struct spa_dict *dict);
+
+bool
+pw_core_add_global(struct pw_core *core,
+ struct pw_resource *owner,
+ uint32_t type,
+ uint32_t version,
+ void *object, pw_bind_func_t bind,
+ struct pw_global **global);
+
+int
+pw_global_bind(struct pw_global *global,
+ struct pw_client *client, uint32_t version, uint32_t id);
+
+void
+pw_global_destroy(struct pw_global *global);
+
+struct spa_format *
+pw_core_find_format(struct pw_core *core,
+ struct pw_port *output,
+ struct pw_port *input,
+ struct pw_properties *props,
+ uint32_t n_format_filters,
+ struct spa_format **format_filters,
+ char **error);
+
+struct pw_port *
+pw_core_find_port(struct pw_core *core,
+ struct pw_port *other_port,
+ uint32_t id,
+ struct pw_properties *props,
+ uint32_t n_format_filters,
+ struct spa_format **format_filters,
+ char **error);
+
+struct pw_node_factory *
+pw_core_find_node_factory(struct pw_core *core, const char *name);
+
+#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 00000000..83da38a6
--- /dev/null
+++ b/src/pipewire/data-loop.c
@@ -0,0 +1,214 @@
+/* PipeWire
+ * Copyright (C) 2016 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <pthread.h>
+#include <errno.h>
+#include <sys/resource.h>
+
+#include "pipewire/log.h"
+#include "pipewire/rtkit.h"
+#include "pipewire/data-loop.h"
+
+/** \cond */
+struct impl {
+ struct pw_data_loop this;
+
+ struct spa_source *event;
+
+ bool running;
+ pthread_t thread;
+};
+/** \endcond */
+
+static void make_realtime(struct pw_data_loop *this)
+{
+ struct sched_param sp;
+ struct pw_rtkit_bus *system_bus;
+ struct rlimit rl;
+ int r, rtprio;
+ long long rttime;
+
+ rtprio = 20;
+ rttime = 20000;
+
+ spa_zero(sp);
+ sp.sched_priority = rtprio;
+
+ if (pthread_setschedparam(pthread_self(), SCHED_OTHER | SCHED_RESET_ON_FORK, &sp) == 0) {
+ pw_log_debug("SCHED_OTHER|SCHED_RESET_ON_FORK worked.");
+ return;
+ }
+ system_bus = pw_rtkit_bus_get_system();
+
+ rl.rlim_cur = rl.rlim_max = rttime;
+ if ((r = setrlimit(RLIMIT_RTTIME, &rl)) < 0)
+ pw_log_debug("setrlimit() failed: %s", strerror(errno));
+
+ if (rttime >= 0) {
+ r = getrlimit(RLIMIT_RTTIME, &rl);
+ if (r >= 0 && (long long) rl.rlim_max > rttime) {
+ pw_log_debug("Clamping rlimit-rttime to %lld for RealtimeKit", rttime);
+ rl.rlim_cur = rl.rlim_max = rttime;
+
+ if ((r = setrlimit(RLIMIT_RTTIME, &rl)) < 0)
+ pw_log_debug("setrlimit() failed: %s", strerror(errno));
+ }
+ }
+
+ if ((r = pw_rtkit_make_realtime(system_bus, 0, rtprio)) < 0) {
+ pw_log_debug("could not make thread realtime: %s", strerror(r));
+ } else {
+ pw_log_debug("thread made realtime");
+ }
+ pw_rtkit_bus_free(system_bus);
+}
+
+static void *do_loop(void *user_data)
+{
+ struct impl *impl = user_data;
+ struct pw_data_loop *this = &impl->this;
+ int res;
+
+ make_realtime(this);
+
+ pw_log_debug("data-loop %p: enter thread", this);
+ pw_loop_enter(impl->this.loop);
+
+ while (impl->running) {
+ if ((res = pw_loop_iterate(this->loop, -1)) < 0)
+ pw_log_warn("data-loop %p: iterate error %d", this, res);
+ }
+ pw_log_debug("data-loop %p: leave thread", this);
+ pw_loop_leave(impl->this.loop);
+
+ return NULL;
+}
+
+
+static void do_stop(struct spa_loop_utils *utils, struct spa_source *source, void *data)
+{
+ struct impl *impl = data;
+ impl->running = false;
+}
+
+/** Create a new \ref pw_data_loop.
+ * \return a newly allocated data loop
+ *
+ * \memberof pw_data_loop
+ */
+struct pw_data_loop *pw_data_loop_new(void)
+{
+ struct impl *impl;
+ struct pw_data_loop *this;
+
+ impl = calloc(1, sizeof(struct impl));
+ if (impl == NULL)
+ return NULL;
+
+ pw_log_debug("data-loop %p: new", impl);
+
+ this = &impl->this;
+ this->loop = pw_loop_new();
+ if (this->loop == NULL)
+ goto no_loop;
+
+ pw_signal_init(&this->destroy_signal);
+
+ impl->event = pw_loop_add_event(this->loop, do_stop, impl);
+ return this;
+
+ no_loop:
+ free(impl);
+ return NULL;
+}
+
+/** Destroy a data loop
+ * \param loop the data loop to destroy
+ * \memberof pw_data_loop
+ */
+void pw_data_loop_destroy(struct pw_data_loop *loop)
+{
+ struct impl *impl = SPA_CONTAINER_OF(loop, struct impl, this);
+
+ pw_log_debug("data-loop %p: destroy", impl);
+ pw_signal_emit(&loop->destroy_signal, loop);
+
+ pw_data_loop_stop(loop);
+
+ pw_loop_destroy_source(loop->loop, impl->event);
+ pw_loop_destroy(loop->loop);
+ free(impl);
+}
+
+/** 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.
+ *
+ * \memberof pw_data_loop
+ */
+int pw_data_loop_start(struct pw_data_loop *loop)
+{
+ struct impl *impl = SPA_CONTAINER_OF(loop, struct impl, this);
+
+ if (!impl->running) {
+ int err;
+
+ impl->running = true;
+ if ((err = pthread_create(&impl->thread, NULL, do_loop, impl)) != 0) {
+ pw_log_warn("data-loop %p: can't create thread: %s", impl, strerror(err));
+ impl->running = false;
+ return SPA_RESULT_ERROR;
+ }
+ }
+ return SPA_RESULT_OK;
+}
+
+/** Stop a data loop
+ * \param loop the data loop to Stop
+ * \return \ref SPA_RESULT_OK
+ *
+ * This will stop and join the realtime thread that manages the loop.
+ *
+ * \memberof pw_data_loop
+ */
+int pw_data_loop_stop(struct pw_data_loop *loop)
+{
+ struct impl *impl = SPA_CONTAINER_OF(loop, struct impl, this);
+
+ if (impl->running) {
+ pw_loop_signal_event(impl->this.loop, impl->event);
+
+ pthread_join(impl->thread, NULL);
+ }
+ return SPA_RESULT_OK;
+}
+
+/** 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
+ *
+ * \memberof pw_data_loop
+ */
+bool pw_data_loop_in_thread(struct pw_data_loop * loop)
+{
+ struct impl *impl = SPA_CONTAINER_OF(loop, struct impl, this);
+ return pthread_equal(impl->thread, pthread_self());
+}
diff --git a/src/pipewire/data-loop.h b/src/pipewire/data-loop.h
new file mode 100644
index 00000000..64a4c241
--- /dev/null
+++ b/src/pipewire/data-loop.h
@@ -0,0 +1,59 @@
+/* PipeWire
+ * Copyright (C) 2016 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __PIPEWIRE_DATA_LOOP_H__
+#define __PIPEWIRE_DATA_LOOP_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <pipewire/loop.h>
+
+/** \class pw_data_loop
+ *
+ * PipeWire rt-loop object.
+ */
+struct pw_data_loop {
+ struct pw_loop *loop; /**< wrapped loop object */
+
+ /** Emited when the data loop is destroyed */
+ PW_SIGNAL(destroy_signal, (struct pw_listener *listener, struct pw_data_loop *loop));
+};
+
+struct pw_data_loop *
+pw_data_loop_new(void);
+
+void
+pw_data_loop_destroy(struct pw_data_loop *loop);
+
+int
+pw_data_loop_start(struct pw_data_loop *loop);
+
+int
+pw_data_loop_stop(struct pw_data_loop *loop);
+
+bool
+pw_data_loop_in_thread(struct pw_data_loop *loop);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __PIPEWIRE_DATA_LOOP_H__ */
diff --git a/src/pipewire/interfaces.h b/src/pipewire/interfaces.h
new file mode 100644
index 00000000..76aa767b
--- /dev/null
+++ b/src/pipewire/interfaces.h
@@ -0,0 +1,347 @@
+/* PipeWire
+ * Copyright (C) 2016 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __PIPEWIRE_INTERFACES_H__
+#define __PIPEWIRE_INTERFACES_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/defs.h>
+#include <spa/props.h>
+#include <spa/format.h>
+#include <spa/param-alloc.h>
+#include <spa/node.h>
+
+#include <pipewire/introspect.h>
+
+/**
+ * \page page_pipewire_protocol The PipeWire protocol
+ * \section page_ifaces_pipewire Interfaces
+ * - \subpage page_iface_pw_core - core global object
+ * - \subpage page_iface_pw_registry - global registry object
+ */
+
+/**
+ * \page page_iface_pw_core pw_core
+ * \section page_iface_pw_core_desc Description
+ *
+ * The core global object. This is a special singleton object. It
+ * is used for internal Wayland protocol features.
+ * \section page_iface_pw_core API
+ */
+
+#define PW_VERSION_CORE 0
+
+#define PW_CORE_METHOD_UPDATE_TYPES 0
+#define PW_CORE_METHOD_SYNC 1
+#define PW_CORE_METHOD_GET_REGISTRY 2
+#define PW_CORE_METHOD_CLIENT_UPDATE 3
+#define PW_CORE_METHOD_CREATE_NODE 4
+#define PW_CORE_METHOD_CREATE_LINK 5
+#define PW_CORE_METHOD_NUM 6
+
+/**
+ * \struct pw_core_methods
+ * \brief Core methods
+ *
+ * The core global object. This is a singleton object used for
+ * creating new objects in the PipeWire server. It is also used
+ * for internal features.
+ */
+struct pw_core_methods {
+ /**
+ * Update the type map
+ *
+ * Send a type map update to the PipeWire server. The server uses this
+ * information to keep a mapping between client types and the server types.
+ * \param first_id the id of the first type
+ * \param n_types the number of types
+ * \param types the types as a string
+ */
+ void (*update_types) (void *object,
+ uint32_t first_id,
+ uint32_t n_types,
+ const char **types);
+ /**
+ * Do server roundtrip
+ *
+ * Ask the server to emit the 'done' event with \a id.
+ * 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 sequence number passed to the done event
+ */
+ void (*sync) (void *object, uint32_t seq);
+ /**
+ * 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 id the client proxy id
+ */
+ void (*get_registry) (void *object, uint32_t new_id);
+ /**
+ * Update the client properties
+ * \param props the new client properties
+ */
+ void (*client_update) (void *object, const struct spa_dict *props);
+ /**
+ * Create a new node on the PipeWire server from a factory.
+ * Use a \a fectory_name of "client-node" to create a
+ * \ref pw_client_node.
+ *
+ * \param factory_name the factory name to use
+ * \param name the node name
+ * \param props extra properties
+ * \param new_id the client proxy id
+ */
+ void (*create_node) (void *object,
+ const char *factory_name,
+ const char *name,
+ const struct spa_dict *props,
+ uint32_t new_id);
+ /**
+ * Create a new link between two node ports
+ *
+ * \param output_node_id the global id of the output node
+ * \param output_port_id the id of the output port
+ * \param input_node_id the global id of the input node
+ * \param input_port_id the id of the input port
+ * \param filter an optional format filter
+ * \param props optional properties
+ * \param new_id the client proxy id
+ */
+ void (*create_link) (void *object,
+ uint32_t output_node_id,
+ uint32_t output_port_id,
+ uint32_t input_node_id,
+ uint32_t input_port_id,
+ const struct spa_format *filter,
+ const struct spa_dict *props,
+ uint32_t new_id);
+};
+
+#define pw_core_do_update_types(p,...) pw_proxy_do(p,struct pw_core_methods,update_types,__VA_ARGS__)
+#define pw_core_do_sync(p,...) pw_proxy_do(p,struct pw_core_methods,sync,__VA_ARGS__)
+#define pw_core_do_get_registry(p,...) pw_proxy_do(p,struct pw_core_methods,get_registry,__VA_ARGS__)
+#define pw_core_do_client_update(p,...) pw_proxy_do(p,struct pw_core_methods,client_update,__VA_ARGS__)
+#define pw_core_do_create_node(p,...) pw_proxy_do(p,struct pw_core_methods,create_node,__VA_ARGS__)
+#define pw_core_do_create_link(p,...) pw_proxy_do(p,struct pw_core_methods,create_link,__VA_ARGS__)
+
+#define PW_CORE_EVENT_UPDATE_TYPES 0
+#define PW_CORE_EVENT_DONE 1
+#define PW_CORE_EVENT_ERROR 2
+#define PW_CORE_EVENT_REMOVE_ID 3
+#define PW_CORE_EVENT_INFO 4
+#define PW_CORE_EVENT_NUM 5
+
+/** \struct pw_core_events
+ * \brief Core events
+ * \ingroup pw_core_interface The pw_core interface
+ */
+struct pw_core_events {
+ /**
+ * Update the type map
+ *
+ * Send a type map update to the client. The client uses this
+ * information to keep a mapping between server types and the client types.
+ * \param first_id the id of the first type
+ * \param n_types the number of types
+ * \param types the types as a string
+ */
+ void (*update_types) (void *object,
+ uint32_t first_id,
+ uint32_t n_types,
+ const char **types);
+ /**
+ * Emit a done event
+ *
+ * The done event is emited as a result of a sync method with the
+ * same sequence number.
+ * \param seq the sequence number passed to the sync method call
+ */
+ void (*done) (void *object, uint32_t seq);
+ /**
+ * Fatal error event
+ *
+ * The error event is sent out when a fatal (non-recoverable)
+ * error has occurred. The id argument is the 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.
+ * \param id object where the error occurred
+ * \param res error code
+ * \param error error description
+ */
+ void (*error) (void *object, uint32_t id, int res, const char *error, ...);
+ /**
+ * 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 *object, uint32_t id);
+ /**
+ * Notify new core info
+ *
+ * \param info new core info
+ */
+ void (*info) (void *object, struct pw_core_info *info);
+};
+
+#define pw_core_notify_update_types(r,...) pw_resource_notify(r,struct pw_core_events,update_types,__VA_ARGS__)
+#define pw_core_notify_done(r,...) pw_resource_notify(r,struct pw_core_events,done,__VA_ARGS__)
+#define pw_core_notify_error(r,...) pw_resource_notify(r,struct pw_core_events,error,__VA_ARGS__)
+#define pw_core_notify_remove_id(r,...) pw_resource_notify(r,struct pw_core_events,remove_id,__VA_ARGS__)
+#define pw_core_notify_info(r,...) pw_resource_notify(r,struct pw_core_events,info,__VA_ARGS__)
+
+#define PW_VERSION_REGISTRY 0
+
+#define PW_REGISTRY_METHOD_BIND 0
+#define PW_REGISTRY_METHOD_NUM 1
+
+/** Registry methods */
+struct pw_registry_methods {
+ /**
+ * 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 version the version to use
+ * \param new_id the client proxy to use
+ */
+ void (*bind) (void *object, uint32_t id, uint32_t version, uint32_t new_id);
+};
+
+#define pw_registry_do_bind(p,...) pw_proxy_do(p,struct pw_registry_methods,bind,__VA_ARGS__)
+
+#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 {
+ /**
+ * 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 type the type of the object
+ * \param version the version of the object
+ */
+ void (*global) (void *object, uint32_t id, const char *type, uint32_t version);
+ /**
+ * Notify of a global object removal
+ *
+ * Emited 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 *object, uint32_t id);
+};
+
+#define pw_registry_notify_global(r,...) pw_resource_notify(r,struct pw_registry_events,global,__VA_ARGS__)
+#define pw_registry_notify_global_remove(r,...) pw_resource_notify(r,struct pw_registry_events,global_remove,__VA_ARGS__)
+
+#define PW_VERSION_MODULE 0
+
+#define PW_MODULE_EVENT_INFO 0
+#define PW_MODULE_EVENT_NUM 1
+
+/** Module events */
+struct pw_module_events {
+ /**
+ * Notify module info
+ *
+ * \param info info about the module
+ */
+ void (*info) (void *object, struct pw_module_info *info);
+};
+
+#define pw_module_notify_info(r,...) pw_resource_notify(r,struct pw_module_events,info,__VA_ARGS__)
+
+#define PW_VERSION_NODE 0
+
+#define PW_NODE_EVENT_INFO 0
+#define PW_NODE_EVENT_NUM 1
+
+/** Node events */
+struct pw_node_events {
+ /**
+ * Notify node info
+ *
+ * \param info info about the node
+ */
+ void (*info) (void *object, struct pw_node_info *info);
+};
+
+#define pw_node_notify_info(r,...) pw_resource_notify(r,struct pw_node_events,info,__VA_ARGS__)
+
+#define PW_VERSION_CLIENT 0
+
+#define PW_CLIENT_EVENT_INFO 0
+#define PW_CLIENT_EVENT_NUM 1
+
+/** Client events */
+struct pw_client_events {
+ /**
+ * Notify client info
+ *
+ * \param info info about the client
+ */
+ void (*info) (void *object, struct pw_client_info *info);
+};
+
+#define pw_client_notify_info(r,...) pw_resource_notify(r,struct pw_client_events,info,__VA_ARGS__)
+
+#define PW_VERSION_LINK 0
+
+#define PW_LINK_EVENT_INFO 0
+#define PW_LINK_EVENT_NUM 1
+
+/** Link events */
+struct pw_link_events {
+ /**
+ * Notify link info
+ *
+ * \param info info about the link
+ */
+ void (*info) (void *object, struct pw_link_info *info);
+};
+
+#define pw_link_notify_info(r,...) pw_resource_notify(r,struct pw_link_events,info,__VA_ARGS__)
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* __PIPEWIRE_INTERFACES_H__ */
diff --git a/src/pipewire/introspect.c b/src/pipewire/introspect.c
new file mode 100644
index 00000000..02e4b811
--- /dev/null
+++ b/src/pipewire/introspect.c
@@ -0,0 +1,390 @@
+/* PipeWire
+ * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <string.h>
+
+#include "pipewire/pipewire.h"
+
+#include "pipewire/remote.h"
+
+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";
+}
+
+const char *pw_direction_as_string(enum pw_direction direction)
+{
+ switch (direction) {
+ case PW_DIRECTION_INPUT:
+ return "input";
+ case PW_DIRECTION_OUTPUT:
+ return "output";
+ default:
+ return "invalid";
+ }
+ return "invalid-direction";
+}
+
+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_RUNNING:
+ return "running";
+ }
+ return "invalid-state";
+}
+
+static void pw_spa_dict_destroy(struct spa_dict *dict)
+{
+ struct spa_dict_item *item;
+
+ spa_dict_for_each(item, dict) {
+ free((void *) item->key);
+ free((void *) item->value);
+ }
+ free(dict->items);
+ free(dict);
+}
+
+static struct spa_dict *pw_spa_dict_copy(struct spa_dict *dict)
+{
+ struct spa_dict *copy;
+ uint32_t i;
+
+ if (dict == NULL)
+ return NULL;
+
+ copy = calloc(1, sizeof(struct spa_dict));
+ if (copy == NULL)
+ goto no_mem;
+ copy->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++) {
+ copy->items[i].key = strdup(dict->items[i].key);
+ copy->items[i].value = strdup(dict->items[i].value);
+ }
+ return copy;
+
+ no_items:
+ free(copy);
+ no_mem:
+ return NULL;
+}
+
+struct pw_core_info *pw_core_info_update(struct pw_core_info *info,
+ const struct pw_core_info *update)
+{
+ 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->change_mask = update->change_mask;
+
+ if (update->change_mask & (1 << 0)) {
+ if (info->user_name)
+ free((void *) info->user_name);
+ info->user_name = update->user_name ? strdup(update->user_name) : NULL;
+ }
+ if (update->change_mask & (1 << 1)) {
+ if (info->host_name)
+ free((void *) info->host_name);
+ info->host_name = update->host_name ? strdup(update->host_name) : NULL;
+ }
+ if (update->change_mask & (1 << 2)) {
+ if (info->version)
+ free((void *) info->version);
+ info->version = update->version ? strdup(update->version) : NULL;
+ }
+ if (update->change_mask & (1 << 3)) {
+ if (info->name)
+ free((void *) info->name);
+ info->name = update->name ? strdup(update->name) : NULL;
+ }
+ if (update->change_mask & (1 << 4))
+ info->cookie = update->cookie;
+ if (update->change_mask & (1 << 5)) {
+ if (info->props)
+ pw_spa_dict_destroy(info->props);
+ info->props = pw_spa_dict_copy(update->props);
+ }
+ return info;
+}
+
+void pw_core_info_free(struct pw_core_info *info)
+{
+ if (info->user_name)
+ free((void *) info->user_name);
+ if (info->host_name)
+ free((void *) info->host_name);
+ if (info->version)
+ free((void *) info->version);
+ if (info->name)
+ free((void *) info->name);
+ if (info->props)
+ pw_spa_dict_destroy(info->props);
+ free(info);
+}
+
+struct pw_node_info *pw_node_info_update(struct pw_node_info *info,
+ const struct pw_node_info *update)
+{
+ int i;
+
+ 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->change_mask = update->change_mask;
+
+ if (update->change_mask & (1 << 0)) {
+ if (info->name)
+ free((void *) info->name);
+ info->name = update->name ? strdup(update->name) : NULL;
+ }
+ if (update->change_mask & (1 << 1)) {
+ info->max_input_ports = update->max_input_ports;
+ info->n_input_ports = update->n_input_ports;
+ }
+ if (update->change_mask & (1 << 2)) {
+ for (i = 0; i < info->n_input_formats; i++)
+ free(info->input_formats[i]);
+ info->n_input_formats = update->n_input_formats;
+ if (info->n_input_formats)
+ info->input_formats =
+ realloc(info->input_formats,
+ info->n_input_formats * sizeof(struct spa_format *));
+ else {
+ free(info->input_formats);
+ info->input_formats = NULL;
+ }
+ for (i = 0; i < info->n_input_formats; i++) {
+ info->input_formats[i] = spa_format_copy(update->input_formats[i]);
+ }
+ }
+ if (update->change_mask & (1 << 3)) {
+ info->max_output_ports = update->max_output_ports;
+ info->n_output_ports = update->n_output_ports;
+ }
+ if (update->change_mask & (1 << 4)) {
+ for (i = 0; i < info->n_output_formats; i++)
+ free(info->output_formats[i]);
+ info->n_output_formats = update->n_output_formats;
+ if (info->n_output_formats)
+ info->output_formats =
+ realloc(info->output_formats,
+ info->n_output_formats * sizeof(struct spa_format *));
+ else {
+ free(info->output_formats);
+ info->output_formats = NULL;
+ }
+ for (i = 0; i < info->n_output_formats; i++) {
+ info->output_formats[i] = spa_format_copy(update->output_formats[i]);
+ }
+ }
+
+ if (update->change_mask & (1 << 5)) {
+ info->state = update->state;
+ if (info->error)
+ free((void *) info->error);
+ info->error = update->error ? strdup(update->error) : NULL;
+ }
+ if (update->change_mask & (1 << 6)) {
+ if (info->props)
+ pw_spa_dict_destroy(info->props);
+ info->props = pw_spa_dict_copy(update->props);
+ }
+ return info;
+}
+
+void pw_node_info_free(struct pw_node_info *info)
+{
+ int i;
+
+ if (info->name)
+ free((void *) info->name);
+ if (info->input_formats) {
+ for (i = 0; i < info->n_input_formats; i++)
+ free(info->input_formats[i]);
+ free(info->input_formats);
+ }
+ if (info->output_formats) {
+ for (i = 0; i < info->n_output_formats; i++)
+ free(info->output_formats[i]);
+ free(info->output_formats);
+ }
+ if (info->error)
+ free((void *) info->error);
+ if (info->props)
+ pw_spa_dict_destroy(info->props);
+ free(info);
+}
+
+struct pw_module_info *pw_module_info_update(struct pw_module_info *info,
+ const struct pw_module_info *update)
+{
+ 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->change_mask = update->change_mask;
+
+ if (update->change_mask & (1 << 0)) {
+ if (info->name)
+ free((void *) info->name);
+ info->name = update->name ? strdup(update->name) : NULL;
+ }
+ if (update->change_mask & (1 << 1)) {
+ if (info->filename)
+ free((void *) info->filename);
+ info->filename = update->filename ? strdup(update->filename) : NULL;
+ }
+ if (update->change_mask & (1 << 2)) {
+ if (info->args)
+ free((void *) info->args);
+ info->args = update->args ? strdup(update->args) : NULL;
+ }
+ if (update->change_mask & (1 << 3)) {
+ if (info->props)
+ pw_spa_dict_destroy(info->props);
+ info->props = pw_spa_dict_copy(update->props);
+ }
+ return info;
+}
+
+void pw_module_info_free(struct pw_module_info *info)
+{
+ if (info->name)
+ free((void *) info->name);
+ if (info->filename)
+ free((void *) info->filename);
+ if (info->args)
+ free((void *) info->args);
+ if (info->props)
+ pw_spa_dict_destroy(info->props);
+ free(info);
+}
+
+
+struct pw_client_info *pw_client_info_update(struct pw_client_info *info,
+ const struct pw_client_info *update)
+{
+ 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;
+ info->change_mask = update->change_mask;
+
+ if (update->change_mask & (1 << 0)) {
+ if (info->props)
+ pw_spa_dict_destroy(info->props);
+ info->props = pw_spa_dict_copy(update->props);
+ }
+ return info;
+}
+
+void pw_client_info_free(struct pw_client_info *info)
+{
+ if (info->props)
+ pw_spa_dict_destroy(info->props);
+ free(info);
+}
+
+struct pw_link_info *pw_link_info_update(struct pw_link_info *info,
+ const struct pw_link_info *update)
+{
+ 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->change_mask = update->change_mask;
+
+ if (update->change_mask & (1 << 0))
+ info->output_node_id = update->output_node_id;
+ if (update->change_mask & (1 << 1))
+ info->output_port_id = update->output_port_id;
+ if (update->change_mask & (1 << 2))
+ info->input_node_id = update->input_node_id;
+ if (update->change_mask & (1 << 3))
+ info->input_port_id = update->input_port_id;
+ if (update->change_mask & (1 << 4)) {
+ if (info->format)
+ free(info->format);
+ info->format = spa_format_copy(update->format);
+ }
+ return info;
+}
+
+void pw_link_info_free(struct pw_link_info *info)
+{
+ if (info->format)
+ free(info->format);
+ free(info);
+}
diff --git a/src/pipewire/introspect.h b/src/pipewire/introspect.h
new file mode 100644
index 00000000..dd0d772e
--- /dev/null
+++ b/src/pipewire/introspect.h
@@ -0,0 +1,184 @@
+/* PipeWire
+ * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __PIPEWIRE_INTROSPECT_H__
+#define __PIPEWIRE_INTROSPECT_H__
+
+#include <spa/defs.h>
+#include <spa/format.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <pipewire/properties.h>
+
+/** \enum pw_node_state The different node states \memberof pw_node */
+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 \memberof pw_node */
+const char * pw_node_state_as_string(enum pw_node_state state);
+
+/** \enum pw_direction The direction of a port \memberof pw_introspect */
+enum pw_direction {
+ PW_DIRECTION_INPUT = SPA_DIRECTION_INPUT, /**< an input port direction */
+ PW_DIRECTION_OUTPUT = SPA_DIRECTION_OUTPUT /**< an output port direction */
+};
+
+/** Convert a \ref pw_direction to a readable string \memberof pw_introspect */
+const char * pw_direction_as_string(enum pw_direction direction);
+
+/** \enum pw_link_state The different link states \memberof pw_link */
+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_RUNNING = 4, /**< the link is running */
+};
+
+/** Convert a \ref pw_link_state to a readable string \memberof pw_link */
+const char * pw_link_state_as_string(enum pw_link_state state);
+
+/** \class pw_introspect
+ *
+ * The introspection methods and structures are used to get information
+ * about the object in the PipeWire server
+ */
+
+/** The core information. Extra information can be added in later versions \memberof pw_introspect */
+struct pw_core_info {
+ uint32_t id; /**< server side id of the core */
+#define PW_CORE_CHANGE_MASK_USER_NAME (1 << 0)
+#define PW_CORE_CHANGE_MASK_HOST_NAME (1 << 1)
+#define PW_CORE_CHANGE_MASK_VERSION (1 << 2)
+#define PW_CORE_CHANGE_MASK_NAME (1 << 3)
+#define PW_CORE_CHANGE_MASK_COOKIE (1 << 4)
+#define PW_CORE_CHANGE_MASK_PROPS (1 << 5)
+#define PW_CORE_CHANGE_MASK_ALL (~0)
+ uint64_t change_mask; /**< bitfield of changed fields since last call */
+ 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 */
+ uint32_t cookie; /**< a random cookie for identifying this instance of PipeWire */
+ struct spa_dict *props; /**< extra properties */
+};
+
+/** Update and existing \ref pw_core_info with \a update \memberof pw_introspect */
+struct pw_core_info *
+pw_core_info_update(struct pw_core_info *info,
+ const struct pw_core_info *update);
+
+/** Free a \ref pw_core_info \memberof pw_introspect */
+void pw_core_info_free(struct pw_core_info *info);
+
+/** The module information. Extra information can be added in later versions \memberof pw_introspect */
+struct pw_module_info {
+ uint32_t id; /**< server side id of the module */
+ uint64_t change_mask; /**< bitfield of changed fields since last call */
+ const char *name; /**< name of the module */
+ const char *filename; /**< filename of the module */
+ const char *args; /**< arguments passed to the module */
+ struct spa_dict *props; /**< extra properties */
+};
+
+/** Update and existing \ref pw_module_info with \a update \memberof pw_introspect */
+struct pw_module_info *
+pw_module_info_update(struct pw_module_info *info,
+ const struct pw_module_info *update);
+
+/** Free a \ref pw_module_info \memberof pw_introspect */
+void pw_module_info_free(struct pw_module_info *info);
+
+/** The client information. Extra information can be added in later versions \memberof pw_introspect */
+struct pw_client_info {
+ uint32_t id; /**< server side id of the client */
+ uint64_t change_mask; /**< bitfield of changed fields since last call */
+ struct spa_dict *props; /**< extra properties */
+};
+
+/** Update and existing \ref pw_client_info with \a update \memberof pw_introspect */
+struct pw_client_info *
+pw_client_info_update(struct pw_client_info *info,
+ const struct pw_client_info *update);
+
+/** Free a \ref pw_client_info \memberof pw_introspect */
+void pw_client_info_free(struct pw_client_info *info);
+
+
+/** The node information. Extra information can be added in later versions \memberof pw_introspect */
+struct pw_node_info {
+ uint32_t id; /**< server side id of the node */
+ uint64_t change_mask; /**< bitfield of changed fields since last call */
+ const char *name; /**< name the node, suitable for display */
+ uint32_t max_input_ports; /**< maximum number of inputs */
+ uint32_t n_input_ports; /**< number of inputs */
+ uint32_t n_input_formats; /**< number of input formats */
+ struct spa_format **input_formats; /**< array of input formats */
+ uint32_t max_output_ports; /**< maximum number of outputs */
+ uint32_t n_output_ports; /**< number of outputs */
+ uint32_t n_output_formats; /**< number of output formats */
+ struct spa_format **output_formats; /**< array of output formats */
+ 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 pw_node_info *
+pw_node_info_update(struct pw_node_info *info,
+ const struct pw_node_info *update);
+
+void
+pw_node_info_free(struct pw_node_info *info);
+
+
+/** The link information. Extra information can be added in later versions \memberof pw_introspect */
+struct pw_link_info {
+ uint32_t id; /**< server side id of the link */
+ uint64_t change_mask; /**< bitfield of changed fields since last call */
+ 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 */
+ struct spa_format *format; /**< format over link */
+};
+
+struct pw_link_info *
+pw_link_info_update(struct pw_link_info *info,
+ const struct pw_link_info *update);
+
+void
+pw_link_info_free(struct pw_link_info *info);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __PIPEWIRE_INTROSPECT_H__ */
diff --git a/src/pipewire/link.c b/src/pipewire/link.c
new file mode 100644
index 00000000..8ac6ab2a
--- /dev/null
+++ b/src/pipewire/link.c
@@ -0,0 +1,1139 @@
+/* PipeWire
+ * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <string.h>
+
+#include <spa/lib/debug.h>
+#include <spa/video/format.h>
+#include <spa/pod-utils.h>
+
+#include <spa/lib/format.h>
+#include <spa/lib/props.h>
+
+#include "pipewire.h"
+#include "interfaces.h"
+#include "link.h"
+#include "work-queue.h"
+
+#define MAX_BUFFERS 16
+
+/** \cond */
+struct impl {
+ struct pw_link this;
+
+ bool active;
+
+ struct pw_work_queue *work;
+
+ struct spa_format *format_filter;
+ struct pw_properties *properties;
+
+ struct pw_listener input_port_destroy;
+ struct pw_listener input_async_complete;
+ struct pw_listener output_port_destroy;
+ struct pw_listener output_async_complete;
+
+ void *buffer_owner;
+ struct pw_memblock buffer_mem;
+ struct spa_buffer **buffers;
+ uint32_t n_buffers;
+};
+
+/** \endcond */
+
+static void pw_link_update_state(struct pw_link *link, enum pw_link_state state, char *error)
+{
+ enum pw_link_state old = link->state;
+
+ if (state != old) {
+ pw_log_debug("link %p: update state %s -> %s (%s)", link,
+ pw_link_state_as_string(old), pw_link_state_as_string(state), error);
+
+ link->state = state;
+ if (link->error)
+ free(link->error);
+ link->error = error;
+
+ pw_signal_emit(&link->state_changed, link, old, state);
+ }
+}
+
+static void complete_ready(void *obj, void *data, int res, uint32_t id)
+{
+ struct pw_port *port = data;
+ if (SPA_RESULT_IS_OK(res)) {
+ port->state = PW_PORT_STATE_READY;
+ pw_log_debug("port %p: state READY", port);
+ } else
+ pw_log_warn("port %p: failed to go to READY", port);
+}
+
+static void complete_paused(void *obj, void *data, int res, uint32_t id)
+{
+ struct pw_port *port = data;
+ if (SPA_RESULT_IS_OK(res)) {
+ port->state = PW_PORT_STATE_PAUSED;
+ pw_log_debug("port %p: state PAUSED", port);
+ } else
+ pw_log_warn("port %p: failed to go to PAUSED", port);
+}
+
+static void complete_streaming(void *obj, void *data, int res, uint32_t id)
+{
+ struct pw_port *port = data;
+ if (SPA_RESULT_IS_OK(res)) {
+ port->state = PW_PORT_STATE_STREAMING;
+ pw_log_debug("port %p: state STREAMING", port);
+ } else
+ pw_log_warn("port %p: failed to go to STREAMING", port);
+}
+
+static int do_negotiate(struct pw_link *this, uint32_t in_state, uint32_t out_state)
+{
+ struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this);
+ int res = SPA_RESULT_ERROR, res2;
+ struct spa_format *format, *current;
+ char *error = NULL;
+
+ if (in_state != PW_PORT_STATE_CONFIGURE && out_state != PW_PORT_STATE_CONFIGURE)
+ return SPA_RESULT_OK;
+
+ pw_link_update_state(this, PW_LINK_STATE_NEGOTIATING, NULL);
+
+ format = pw_core_find_format(this->core, this->output, this->input, NULL, 0, NULL, &error);
+ if (format == NULL)
+ goto error;
+
+ format = spa_format_copy(format);
+
+ if (out_state > PW_PORT_STATE_CONFIGURE && this->output->node->info.state == PW_NODE_STATE_IDLE) {
+ if ((res = pw_port_get_format(this->output,
+ (const struct spa_format **) &current)) < 0) {
+ asprintf(&error, "error get output format: %d", res);
+ goto error;
+ }
+ if (spa_format_compare(current, format) < 0) {
+ pw_log_debug("link %p: output format change, renegotiate", this);
+ pw_node_set_state(this->output->node, PW_NODE_STATE_SUSPENDED);
+ out_state = PW_PORT_STATE_CONFIGURE;
+ }
+ else
+ pw_node_update_state(this->output->node, PW_NODE_STATE_RUNNING, NULL);
+ }
+ if (in_state > PW_PORT_STATE_CONFIGURE && this->input->node->info.state == PW_NODE_STATE_IDLE) {
+ if ((res = pw_port_get_format(this->input,
+ (const struct spa_format **) &current)) < 0) {
+ asprintf(&error, "error get input format: %d", res);
+ goto error;
+ }
+ if (spa_format_compare(current, format) < 0) {
+ pw_log_debug("link %p: input format change, renegotiate", this);
+ pw_node_set_state(this->input->node, PW_NODE_STATE_SUSPENDED);
+ in_state = PW_PORT_STATE_CONFIGURE;
+ }
+ else
+ pw_node_update_state(this->input->node, PW_NODE_STATE_RUNNING, NULL);
+ }
+
+ pw_log_debug("link %p: doing set format", this);
+ if (pw_log_level_enabled(SPA_LOG_LEVEL_DEBUG))
+ spa_debug_format(format);
+
+ if (out_state == PW_PORT_STATE_CONFIGURE) {
+ pw_log_debug("link %p: doing set format on output", this);
+ if ((res = pw_port_set_format(this->output, SPA_PORT_FORMAT_FLAG_NEAREST, format)) < 0) {
+ asprintf(&error, "error set output format: %d", res);
+ goto error;
+ }
+ if (SPA_RESULT_IS_ASYNC(res))
+ pw_work_queue_add(impl->work, this->output->node, res, complete_ready,
+ this->output);
+ }
+ if (in_state == PW_PORT_STATE_CONFIGURE) {
+ pw_log_debug("link %p: doing set format on input", this);
+ if ((res2 = pw_port_set_format(this->input, SPA_PORT_FORMAT_FLAG_NEAREST, format)) < 0) {
+ asprintf(&error, "error set input format: %d", res2);
+ goto error;
+ }
+ if (SPA_RESULT_IS_ASYNC(res2))
+ pw_work_queue_add(impl->work, this->input->node, res2, complete_ready, this->input);
+ }
+
+ if (this->info.format)
+ free(this->info.format);
+ this->info.format = format;
+
+ return SPA_RESULT_OK;
+
+ error:
+ pw_link_update_state(this, PW_LINK_STATE_ERROR, error);
+ if (format)
+ free(format);
+ return res;
+}
+
+static struct spa_param *find_param(struct spa_param **params, int n_params, uint32_t type)
+{
+ uint32_t i;
+
+ for (i = 0; i < n_params; i++) {
+ if (spa_pod_is_object_type(&params[i]->object.pod, type))
+ return params[i];
+ }
+ return NULL;
+}
+
+static struct spa_param *find_meta_enable(struct pw_core *core, struct spa_param **params,
+ int n_params, uint32_t type)
+{
+ uint32_t i;
+
+ for (i = 0; i < n_params; i++) {
+ if (spa_pod_is_object_type
+ (&params[i]->object.pod, core->type.param_alloc_meta_enable.MetaEnable)) {
+ uint32_t qtype;
+
+ if (spa_param_query(params[i],
+ core->type.param_alloc_meta_enable.type,
+ SPA_POD_TYPE_ID, &qtype, 0) != 1)
+ continue;
+
+ if (qtype == type)
+ return params[i];
+ }
+ }
+ return NULL;
+}
+
+static struct spa_buffer **alloc_buffers(struct pw_link *this,
+ uint32_t n_buffers,
+ uint32_t n_params,
+ struct spa_param **params,
+ uint32_t n_datas,
+ size_t *data_sizes,
+ ssize_t *data_strides,
+ struct pw_memblock *mem)
+{
+ struct spa_buffer **buffers, *bp;
+ uint32_t i;
+ size_t skel_size, data_size, meta_size;
+ struct spa_chunk *cdp;
+ void *ddp;
+ uint32_t n_metas;
+ struct spa_meta *metas;
+
+ n_metas = data_size = meta_size = 0;
+
+ /* each buffer */
+ skel_size = sizeof(struct spa_buffer);
+
+ metas = alloca(sizeof(struct spa_meta) * n_params + 1);
+
+ /* add shared metadata */
+ metas[n_metas].type = this->core->type.meta.Shared;
+ metas[n_metas].size = sizeof(struct spa_meta_shared);
+ meta_size += metas[n_metas].size;
+ n_metas++;
+ skel_size += sizeof(struct spa_meta);
+
+ /* collect metadata */
+ for (i = 0; i < n_params; i++) {
+ if (spa_pod_is_object_type
+ (&params[i]->object.pod, this->core->type.param_alloc_meta_enable.MetaEnable)) {
+ uint32_t type, size;
+
+ if (spa_param_query(params[i],
+ this->core->type.param_alloc_meta_enable.type,
+ SPA_POD_TYPE_ID, &type,
+ this->core->type.param_alloc_meta_enable.size,
+ SPA_POD_TYPE_INT, &size, 0) != 2)
+ continue;
+
+ pw_log_debug("link %p: enable meta %d %d", this, type, size);
+
+ metas[n_metas].type = type;
+ metas[n_metas].size = size;
+ meta_size += metas[n_metas].size;
+ n_metas++;
+ skel_size += sizeof(struct spa_meta);
+ }
+ }
+ data_size += meta_size;
+
+ /* data */
+ for (i = 0; i < n_datas; i++) {
+ data_size += sizeof(struct spa_chunk);
+ data_size += data_sizes[i];
+ skel_size += sizeof(struct spa_data);
+ }
+
+ buffers = calloc(n_buffers, skel_size + sizeof(struct spa_buffer *));
+ /* pointer to buffer structures */
+ bp = SPA_MEMBER(buffers, n_buffers * sizeof(struct spa_buffer *), struct spa_buffer);
+
+ pw_memblock_alloc(PW_MEMBLOCK_FLAG_WITH_FD |
+ PW_MEMBLOCK_FLAG_MAP_READWRITE |
+ PW_MEMBLOCK_FLAG_SEAL, n_buffers * data_size, mem);
+
+ for (i = 0; i < n_buffers; i++) {
+ int j;
+ struct spa_buffer *b;
+ void *p;
+
+ buffers[i] = b = SPA_MEMBER(bp, skel_size * i, struct spa_buffer);
+
+ p = SPA_MEMBER(mem->ptr, data_size * i, void);
+
+ b->id = i;
+ b->n_metas = n_metas;
+ b->metas = SPA_MEMBER(b, sizeof(struct spa_buffer), struct spa_meta);
+ for (j = 0; j < n_metas; j++) {
+ struct spa_meta *m = &b->metas[j];
+
+ m->type = metas[j].type;
+ m->data = p;
+ m->size = metas[j].size;
+
+ if (m->type == this->core->type.meta.Shared) {
+ struct spa_meta_shared *msh = p;
+
+ msh->flags = 0;
+ msh->fd = mem->fd;
+ msh->offset = data_size * i;
+ msh->size = data_size;
+ } else if (m->type == this->core->type.meta.Ringbuffer) {
+ struct spa_meta_ringbuffer *rb = p;
+ spa_ringbuffer_init(&rb->ringbuffer, data_sizes[0]);
+ }
+ p += m->size;
+ }
+ /* pointer to data structure */
+ b->n_datas = n_datas;
+ b->datas = SPA_MEMBER(b->metas, n_metas * sizeof(struct spa_meta), struct spa_data);
+
+ cdp = p;
+ ddp = SPA_MEMBER(cdp, sizeof(struct spa_chunk) * n_datas, void);
+
+ for (j = 0; j < n_datas; j++) {
+ struct spa_data *d = &b->datas[j];
+
+ d->chunk = &cdp[j];
+ if (data_sizes[j] > 0) {
+ d->type = this->core->type.data.MemFd;
+ d->flags = 0;
+ d->fd = mem->fd;
+ d->mapoffset = SPA_PTRDIFF(ddp, mem->ptr);
+ d->maxsize = data_sizes[j];
+ d->data = SPA_MEMBER(mem->ptr, d->mapoffset, void);
+ d->chunk->offset = 0;
+ d->chunk->size = data_sizes[j];
+ d->chunk->stride = data_strides[j];
+ ddp += data_sizes[j];
+ } else {
+ d->type = SPA_ID_INVALID;
+ d->data = NULL;
+ }
+ }
+ }
+ return buffers;
+}
+
+static int
+param_filter(struct pw_link *this,
+ struct pw_port *in_port,
+ struct pw_port *out_port,
+ struct spa_pod_builder *result)
+{
+ int res;
+ struct spa_param *oparam, *iparam;
+ int iidx, oidx, num = 0;
+
+ for (iidx = 0;; iidx++) {
+ if (pw_port_enum_params(in_port, iidx, &iparam)
+ < 0)
+ break;
+
+ if (pw_log_level_enabled(SPA_LOG_LEVEL_DEBUG))
+ spa_debug_param(iparam);
+
+ for (oidx = 0;; oidx++) {
+ struct spa_pod_frame f;
+ uint32_t offset;
+
+ if (pw_port_enum_params(out_port, oidx, &oparam) < 0)
+ break;
+
+ if (pw_log_level_enabled(SPA_LOG_LEVEL_DEBUG))
+ spa_debug_param(oparam);
+
+ if (iparam->object.body.type != oparam->object.body.type)
+ continue;
+
+ offset = result->offset;
+ spa_pod_builder_push_object(result, &f, 0, iparam->object.body.type);
+ if ((res = spa_props_filter(result,
+ SPA_POD_CONTENTS(struct spa_param, iparam),
+ SPA_POD_CONTENTS_SIZE(struct spa_param, iparam),
+ SPA_POD_CONTENTS(struct spa_param, oparam),
+ SPA_POD_CONTENTS_SIZE(struct spa_param, oparam))) < 0) {
+ result->offset = offset;
+ result->stack = NULL;
+ continue;
+ }
+ spa_pod_builder_pop(result, &f);
+ num++;
+ }
+ }
+ return num;
+}
+
+static int do_allocation(struct pw_link *this, uint32_t in_state, uint32_t out_state)
+{
+ struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this);
+ int res;
+ const struct spa_port_info *iinfo, *oinfo;
+ uint32_t in_flags, out_flags;
+ char *error = NULL;
+
+ if (in_state != PW_PORT_STATE_READY && out_state != PW_PORT_STATE_READY)
+ return SPA_RESULT_OK;
+
+ pw_link_update_state(this, PW_LINK_STATE_ALLOCATING, NULL);
+
+ pw_log_debug("link %p: doing alloc buffers %p %p", this, this->output->node,
+ this->input->node);
+ /* find out what's possible */
+ if ((res = pw_port_get_info(this->output, &oinfo)) < 0) {
+ asprintf(&error, "error get output port info: %d", res);
+ goto error;
+ }
+ if ((res = pw_port_get_info(this->input, &iinfo)) < 0) {
+ asprintf(&error, "error get input port info: %d", res);
+ goto error;
+ }
+
+ in_flags = iinfo->flags;
+ out_flags = oinfo->flags;
+
+ if (out_flags & SPA_PORT_INFO_FLAG_LIVE) {
+ pw_log_debug("setting link as live");
+ this->output->node->live = true;
+ this->input->node->live = true;
+ }
+
+ if (in_state == PW_PORT_STATE_READY && out_state == PW_PORT_STATE_READY) {
+ if ((out_flags & SPA_PORT_INFO_FLAG_CAN_ALLOC_BUFFERS) &&
+ (in_flags & SPA_PORT_INFO_FLAG_CAN_USE_BUFFERS)) {
+ out_flags = SPA_PORT_INFO_FLAG_CAN_ALLOC_BUFFERS;
+ in_flags = SPA_PORT_INFO_FLAG_CAN_USE_BUFFERS;
+ } else if ((out_flags & SPA_PORT_INFO_FLAG_CAN_USE_BUFFERS) &&
+ (in_flags & SPA_PORT_INFO_FLAG_CAN_ALLOC_BUFFERS)) {
+ out_flags = SPA_PORT_INFO_FLAG_CAN_USE_BUFFERS;
+ in_flags = SPA_PORT_INFO_FLAG_CAN_ALLOC_BUFFERS;
+ } else if ((out_flags & SPA_PORT_INFO_FLAG_CAN_USE_BUFFERS) &&
+ (in_flags & SPA_PORT_INFO_FLAG_CAN_USE_BUFFERS)) {
+ out_flags = SPA_PORT_INFO_FLAG_CAN_USE_BUFFERS;
+ in_flags = SPA_PORT_INFO_FLAG_CAN_USE_BUFFERS;
+ } else if ((out_flags & SPA_PORT_INFO_FLAG_CAN_ALLOC_BUFFERS) &&
+ (in_flags & SPA_PORT_INFO_FLAG_CAN_ALLOC_BUFFERS)) {
+ out_flags = SPA_PORT_INFO_FLAG_CAN_ALLOC_BUFFERS;
+ in_flags = SPA_PORT_INFO_FLAG_CAN_ALLOC_BUFFERS;
+ } else {
+ asprintf(&error, "no common buffer alloc found");
+ res = SPA_RESULT_ERROR;
+ goto error;
+ }
+ } else if (in_state == PW_PORT_STATE_READY && out_state > PW_PORT_STATE_READY) {
+ out_flags &= ~SPA_PORT_INFO_FLAG_CAN_USE_BUFFERS;
+ in_flags &= ~SPA_PORT_INFO_FLAG_CAN_ALLOC_BUFFERS;
+ } else if (out_state == PW_PORT_STATE_READY && in_state > PW_PORT_STATE_READY) {
+ in_flags &= ~SPA_PORT_INFO_FLAG_CAN_USE_BUFFERS;
+ out_flags &= ~SPA_PORT_INFO_FLAG_CAN_ALLOC_BUFFERS;
+ } else {
+ pw_log_debug("link %p: delay allocation, state %d %d", this, in_state, out_state);
+ return SPA_RESULT_OK;
+ }
+
+ if (pw_log_level_enabled(SPA_LOG_LEVEL_DEBUG)) {
+ spa_debug_port_info(oinfo);
+ spa_debug_port_info(iinfo);
+ }
+
+ if (impl->buffers == NULL) {
+ struct spa_param **params, *param;
+ uint8_t buffer[4096];
+ struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
+ int i, offset, n_params;
+ uint32_t max_buffers;
+ size_t minsize = 1024, stride = 0;
+
+ n_params = param_filter(this, this->input, this->output, &b);
+
+ params = alloca(n_params * sizeof(struct spa_param *));
+ for (i = 0, offset = 0; i < n_params; i++) {
+ params[i] = SPA_MEMBER(buffer, offset, struct spa_param);
+ spa_param_fixate(params[i]);
+ if (pw_log_level_enabled(SPA_LOG_LEVEL_DEBUG))
+ spa_debug_param(params[i]);
+ offset += SPA_ROUND_UP_N(SPA_POD_SIZE(params[i]), 8);
+ }
+
+ param = find_meta_enable(this->core, params, n_params,
+ this->core->type.meta.Ringbuffer);
+ if (param) {
+ uint32_t ms, s;
+ max_buffers = 1;
+
+ if (spa_param_query(param,
+ this->core->type.param_alloc_meta_enable.ringbufferSize,
+ SPA_POD_TYPE_INT, &ms,
+ this->core->type.param_alloc_meta_enable.
+ ringbufferStride, SPA_POD_TYPE_INT, &s, 0) == 2) {
+ minsize = ms;
+ stride = s;
+ }
+ } else {
+ max_buffers = MAX_BUFFERS;
+ minsize = stride = 0;
+ param = find_param(params, n_params,
+ this->core->type.param_alloc_buffers.Buffers);
+ if (param) {
+ uint32_t qmax_buffers = max_buffers,
+ qminsize = minsize, qstride = stride;
+
+ spa_param_query(param,
+ this->core->type.param_alloc_buffers.size,
+ SPA_POD_TYPE_INT, &qminsize,
+ this->core->type.param_alloc_buffers.stride,
+ SPA_POD_TYPE_INT, &qstride,
+ this->core->type.param_alloc_buffers.buffers,
+ SPA_POD_TYPE_INT, &qmax_buffers, 0);
+
+ max_buffers =
+ qmax_buffers == 0 ? max_buffers : SPA_MIN(qmax_buffers,
+ max_buffers);
+ minsize = SPA_MAX(minsize, qminsize);
+ stride = SPA_MAX(stride, qstride);
+ } else {
+ minsize = 4096;
+ }
+ }
+
+ if ((in_flags & SPA_PORT_INFO_FLAG_CAN_ALLOC_BUFFERS) ||
+ (out_flags & SPA_PORT_INFO_FLAG_CAN_ALLOC_BUFFERS))
+ minsize = 0;
+
+ if (this->output->n_buffers) {
+ out_flags = 0;
+ in_flags = SPA_PORT_INFO_FLAG_CAN_USE_BUFFERS;
+ impl->n_buffers = this->output->n_buffers;
+ impl->buffers = this->output->buffers;
+ impl->buffer_owner = this->output;
+ pw_log_debug("reusing %d output buffers %p", impl->n_buffers,
+ impl->buffers);
+ } else if (this->input->n_buffers) {
+ out_flags = SPA_PORT_INFO_FLAG_CAN_USE_BUFFERS;
+ in_flags = 0;
+ impl->n_buffers = this->input->n_buffers;
+ impl->buffers = this->input->buffers;
+ impl->buffer_owner = this->input;
+ pw_log_debug("reusing %d input buffers %p", impl->n_buffers, impl->buffers);
+ } else {
+ size_t data_sizes[1];
+ ssize_t data_strides[1];
+
+ data_sizes[0] = minsize;
+ data_strides[0] = stride;
+
+ impl->buffer_owner = this;
+ impl->n_buffers = max_buffers;
+ impl->buffers = alloc_buffers(this,
+ impl->n_buffers,
+ n_params,
+ params,
+ 1,
+ data_sizes, data_strides, &impl->buffer_mem);
+
+ pw_log_debug("allocating %d input buffers %p %zd %zd", impl->n_buffers,
+ impl->buffers, minsize, stride);
+ }
+
+ if (out_flags & SPA_PORT_INFO_FLAG_CAN_ALLOC_BUFFERS) {
+ if ((res = pw_port_alloc_buffers(this->output,
+ params, n_params,
+ impl->buffers, &impl->n_buffers)) < 0) {
+ asprintf(&error, "error alloc output buffers: %d", res);
+ goto error;
+ }
+ if (SPA_RESULT_IS_ASYNC(res))
+ pw_work_queue_add(impl->work, this->output->node, res, complete_paused,
+ this->output);
+ this->output->buffer_mem = impl->buffer_mem;
+ impl->buffer_owner = this->output;
+ pw_log_debug("allocated %d buffers %p from output port", impl->n_buffers,
+ impl->buffers);
+ } else if (in_flags & SPA_PORT_INFO_FLAG_CAN_ALLOC_BUFFERS) {
+ if ((res = pw_port_alloc_buffers(this->input,
+ params, n_params,
+ impl->buffers, &impl->n_buffers)) < 0) {
+ asprintf(&error, "error alloc input buffers: %d", res);
+ goto error;
+ }
+ if (SPA_RESULT_IS_ASYNC(res))
+ pw_work_queue_add(impl->work, this->input->node, res, complete_paused,
+ this->input);
+ this->input->buffer_mem = impl->buffer_mem;
+ impl->buffer_owner = this->input;
+ pw_log_debug("allocated %d buffers %p from input port", impl->n_buffers,
+ impl->buffers);
+ }
+ }
+
+ if (in_flags & SPA_PORT_INFO_FLAG_CAN_USE_BUFFERS) {
+ pw_log_debug("using %d buffers %p on input port", impl->n_buffers, impl->buffers);
+ if ((res = pw_port_use_buffers(this->input,
+ impl->buffers, impl->n_buffers)) < 0) {
+ asprintf(&error, "error use input buffers: %d", res);
+ goto error;
+ }
+ if (SPA_RESULT_IS_ASYNC(res))
+ pw_work_queue_add(impl->work, this->input->node, res, complete_paused, this->input);
+ } else if (out_flags & SPA_PORT_INFO_FLAG_CAN_USE_BUFFERS) {
+ pw_log_debug("using %d buffers %p on output port", impl->n_buffers, impl->buffers);
+ if ((res = pw_port_use_buffers(this->output,
+ impl->buffers, impl->n_buffers)) < 0) {
+ asprintf(&error, "error use output buffers: %d", res);
+ goto error;
+ }
+ if (SPA_RESULT_IS_ASYNC(res))
+ pw_work_queue_add(impl->work, this->output->node, res, complete_paused, this->output);
+ } else {
+ asprintf(&error, "no common buffer alloc found");
+ goto error;
+ }
+
+ return SPA_RESULT_OK;
+
+ error:
+ this->output->buffers = NULL;
+ this->output->n_buffers = 0;
+ this->output->allocated = false;
+ this->input->buffers = NULL;
+ this->input->n_buffers = 0;
+ this->input->allocated = false;
+ pw_link_update_state(this, PW_LINK_STATE_ERROR, error);
+ return res;
+}
+
+static int do_start(struct pw_link *this, uint32_t in_state, uint32_t out_state)
+{
+ struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this);
+ char *error = NULL;
+ int res;
+
+ if (in_state < PW_PORT_STATE_PAUSED || out_state < PW_PORT_STATE_PAUSED)
+ return SPA_RESULT_OK;
+
+ pw_link_update_state(this, PW_LINK_STATE_PAUSED, NULL);
+
+ if (in_state == PW_PORT_STATE_PAUSED) {
+ if ((res = pw_node_set_state(this->input->node, PW_NODE_STATE_RUNNING)) < 0) {
+ asprintf(&error, "error starting input node: %d", res);
+ goto error;
+ }
+
+ if (SPA_RESULT_IS_ASYNC(res))
+ pw_work_queue_add(impl->work, this->input->node, res, complete_streaming,
+ this->input);
+ else
+ complete_streaming(this->input->node, this->input, res, 0);
+ }
+ if (out_state == PW_PORT_STATE_PAUSED) {
+ if ((res = pw_node_set_state(this->output->node, PW_NODE_STATE_RUNNING)) < 0) {
+ asprintf(&error, "error starting output node: %d", res);
+ goto error;
+ }
+
+ if (SPA_RESULT_IS_ASYNC(res))
+ pw_work_queue_add(impl->work, this->output->node, res, complete_streaming,
+ this->output);
+ else
+ complete_streaming(this->output->node, this->output, res, 0);
+ }
+ return SPA_RESULT_OK;
+
+ error:
+ pw_link_update_state(this, PW_LINK_STATE_ERROR, error);
+ return res;
+}
+
+static int check_states(struct pw_link *this, void *user_data, int res)
+{
+ struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this);
+ uint32_t in_state, out_state;
+
+ if (this->state == PW_LINK_STATE_ERROR)
+ return SPA_RESULT_ERROR;
+
+ if (this->input == NULL || this->output == NULL)
+ return SPA_RESULT_OK;
+
+ if (this->input->node->info.state == PW_NODE_STATE_ERROR ||
+ this->output->node->info.state == PW_NODE_STATE_ERROR)
+ return SPA_RESULT_ERROR;
+
+ in_state = this->input->state;
+ out_state = this->output->state;
+
+ pw_log_debug("link %p: input state %d, output state %d", this, in_state, out_state);
+
+ if (in_state == PW_PORT_STATE_STREAMING && out_state == PW_PORT_STATE_STREAMING) {
+ pw_link_update_state(this, PW_LINK_STATE_RUNNING, NULL);
+ return SPA_RESULT_OK;
+ }
+
+ if ((res = do_negotiate(this, in_state, out_state)) != SPA_RESULT_OK)
+ goto exit;
+
+ if ((res = do_allocation(this, in_state, out_state)) != SPA_RESULT_OK)
+ goto exit;
+
+ if ((res = do_start(this, in_state, out_state)) != SPA_RESULT_OK)
+ goto exit;
+
+ exit:
+ if (SPA_RESULT_IS_ERROR(res)) {
+ pw_log_debug("link %p: got error result %d", this, res);
+ return res;
+ }
+
+ pw_work_queue_add(impl->work,
+ this, SPA_RESULT_WAIT_SYNC, (pw_work_func_t) check_states, this);
+ return res;
+}
+
+static void
+on_input_async_complete_notify(struct pw_listener *listener,
+ struct pw_node *node, uint32_t seq, int res)
+{
+ struct impl *impl = SPA_CONTAINER_OF(listener, struct impl, input_async_complete);
+
+ pw_log_debug("link %p: node %p async complete %d %d", impl, node, seq, res);
+ pw_work_queue_complete(impl->work, node, seq, res);
+}
+
+static void
+on_output_async_complete_notify(struct pw_listener *listener,
+ struct pw_node *node, uint32_t seq, int res)
+{
+ struct impl *impl = SPA_CONTAINER_OF(listener, struct impl, output_async_complete);
+
+ pw_log_debug("link %p: node %p async complete %d %d", impl, node, seq, res);
+ pw_work_queue_complete(impl->work, node, seq, res);
+}
+
+static void clear_port_buffers(struct pw_link *link, struct pw_port *port)
+{
+ struct impl *impl = SPA_CONTAINER_OF(link, struct impl, this);
+
+ if (impl->buffer_owner != port)
+ pw_port_use_buffers(port, NULL, 0);
+}
+
+static int
+do_remove_input(struct spa_loop *loop,
+ bool async, uint32_t seq, size_t size, void *data, void *user_data)
+{
+ struct pw_link *this = user_data;
+ spa_graph_port_remove(&this->rt.in_port);
+ return SPA_RESULT_OK;
+}
+
+static void input_remove(struct pw_link *this, struct pw_port *port)
+{
+ struct impl *impl = (struct impl *) this;
+
+ pw_log_debug("link %p: remove input port %p", this, port);
+ pw_signal_remove(&impl->input_port_destroy);
+ pw_signal_remove(&impl->input_async_complete);
+
+ pw_loop_invoke(port->node->data_loop,
+ do_remove_input, 1, 0, NULL, true, this);
+
+ clear_port_buffers(this, this->input);
+}
+
+static int
+do_remove_output(struct spa_loop *loop,
+ bool async, uint32_t seq, size_t size, void *data, void *user_data)
+{
+ struct pw_link *this = user_data;
+ spa_graph_port_remove(&this->rt.out_port);
+ return SPA_RESULT_OK;
+}
+
+static void output_remove(struct pw_link *this, struct pw_port *port)
+{
+ struct impl *impl = (struct impl *) this;
+
+ pw_log_debug("link %p: remove output port %p", this, port);
+ pw_signal_remove(&impl->output_port_destroy);
+ pw_signal_remove(&impl->output_async_complete);
+
+ pw_loop_invoke(port->node->data_loop,
+ do_remove_output, 1, 0, NULL, true, this);
+
+ clear_port_buffers(this, this->output);
+}
+
+static void on_port_destroy(struct pw_link *this, struct pw_port *port)
+{
+ struct impl *impl = (struct impl *) this;
+ struct pw_port *other;
+
+ if (port == this->input) {
+ input_remove(this, port);
+ other = this->output;
+ } else if (port == this->output) {
+ output_remove(this, port);
+ other = this->input;
+ } else
+ return;
+
+ if (impl->buffer_owner == port) {
+ impl->buffers = NULL;
+ impl->n_buffers = 0;
+
+ pw_log_debug("link %p: clear allocated buffers on port %p", this, other);
+ pw_port_use_buffers(other, NULL, 0);
+ impl->buffer_owner = NULL;
+ }
+
+ pw_signal_emit(&this->port_unlinked, this, port);
+
+ pw_link_update_state(this, PW_LINK_STATE_UNLINKED, NULL);
+ pw_link_destroy(this);
+}
+
+static void on_input_port_destroy(struct pw_listener *listener, struct pw_port *port)
+{
+ struct impl *impl = SPA_CONTAINER_OF(listener, struct impl, input_port_destroy);
+
+ on_port_destroy(&impl->this, port);
+}
+
+static void on_output_port_destroy(struct pw_listener *listener, struct pw_port *port)
+{
+ struct impl *impl = SPA_CONTAINER_OF(listener, struct impl, output_port_destroy);
+
+ on_port_destroy(&impl->this, port);
+}
+
+static int
+do_activate_link(struct spa_loop *loop,
+ bool async, uint32_t seq, size_t size, void *data, void *user_data)
+{
+ struct pw_link *this = user_data;
+ spa_graph_port_link(&this->rt.out_port, &this->rt.in_port);
+ return SPA_RESULT_OK;
+}
+
+bool pw_link_activate(struct pw_link *this)
+{
+ struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this);
+
+ if (impl->active)
+ return true;
+
+ impl->active = true;
+
+ pw_log_debug("link %p: activate", this);
+ pw_loop_invoke(this->output->node->data_loop,
+ do_activate_link, SPA_ID_INVALID, 0, NULL, false, this);
+
+ this->output->node->n_used_output_links++;
+ this->input->node->n_used_input_links++;
+
+ pw_work_queue_add(impl->work,
+ this, SPA_RESULT_WAIT_SYNC, (pw_work_func_t) check_states, this);
+
+ return true;
+}
+
+static int
+do_deactivate_link(struct spa_loop *loop,
+ bool async, uint32_t seq, size_t size, void *data, void *user_data)
+{
+ struct pw_link *this = user_data;
+ spa_graph_port_unlink(&this->rt.out_port);
+ return SPA_RESULT_OK;
+}
+
+bool pw_link_deactivate(struct pw_link *this)
+{
+ struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this);
+ struct pw_node *input_node, *output_node;
+
+ if (!impl->active)
+ return true;
+
+ impl->active = false;
+ pw_log_debug("link %p: deactivate", this);
+ pw_loop_invoke(this->output->node->data_loop,
+ do_deactivate_link, SPA_ID_INVALID, 0, NULL, true, this);
+
+ input_node = this->input->node;
+ output_node = this->output->node;
+
+ input_node->n_used_input_links--;
+ output_node->n_used_output_links--;
+
+ pw_log_debug("link %p: in %d %d, out %d %d, %d %d %d %d", this,
+ input_node->n_used_input_links,
+ input_node->n_used_output_links,
+ output_node->n_used_input_links,
+ output_node->n_used_output_links,
+ input_node->idle_used_input_links,
+ input_node->idle_used_output_links,
+ output_node->idle_used_input_links,
+ output_node->idle_used_output_links);
+
+ if (input_node->n_used_input_links <= input_node->idle_used_input_links &&
+ input_node->n_used_output_links <= input_node->idle_used_output_links &&
+ input_node->info.state > PW_NODE_STATE_IDLE) {
+ pw_node_update_state(input_node, PW_NODE_STATE_IDLE, NULL);
+ this->input->state = PW_PORT_STATE_PAUSED;
+ }
+
+ if (output_node->n_used_input_links <= output_node->idle_used_input_links &&
+ output_node->n_used_output_links <= output_node->idle_used_output_links &&
+ output_node->info.state > PW_NODE_STATE_IDLE) {
+ pw_node_update_state(output_node, PW_NODE_STATE_IDLE, NULL);
+ this->output->state = PW_PORT_STATE_PAUSED;
+ }
+
+ return true;
+}
+
+static void link_unbind_func(void *data)
+{
+ struct pw_resource *resource = data;
+ spa_list_remove(&resource->link);
+}
+
+static int
+link_bind_func(struct pw_global *global, struct pw_client *client, uint32_t version, uint32_t id)
+{
+ struct pw_link *this = global->object;
+ struct pw_resource *resource;
+
+ resource = pw_resource_new(client, id, global->type, 0);
+
+ if (resource == NULL)
+ goto no_mem;
+
+ pw_resource_set_implementation(resource, global->object, PW_VERSION_LINK, NULL, link_unbind_func);
+
+ pw_log_debug("link %p: bound to %d", global->object, resource->id);
+
+ spa_list_insert(this->resource_list.prev, &resource->link);
+
+ this->info.change_mask = ~0;
+ pw_link_notify_info(resource, &this->info);
+
+ return SPA_RESULT_OK;
+
+ no_mem:
+ pw_log_error("can't create link resource");
+ pw_core_notify_error(client->core_resource,
+ client->core_resource->id, SPA_RESULT_NO_MEMORY, "no memory");
+ return SPA_RESULT_NO_MEMORY;
+}
+
+static int
+do_add_link(struct spa_loop *loop,
+ bool async, uint32_t seq, size_t size, void *data, void *user_data)
+{
+ struct pw_link *this = user_data;
+ struct pw_port *port = ((struct pw_port **) data)[0];
+
+ if (port->direction == PW_DIRECTION_OUTPUT) {
+ spa_graph_port_add(&port->rt.mix_node, &this->rt.out_port);
+ } else {
+ spa_graph_port_add(&port->rt.mix_node, &this->rt.in_port);
+ }
+
+ return SPA_RESULT_OK;
+}
+
+struct pw_link *pw_link_new(struct pw_core *core,
+ struct pw_port *output,
+ struct pw_port *input,
+ struct spa_format *format_filter,
+ struct pw_properties *properties,
+ char **error)
+{
+ struct impl *impl;
+ struct pw_link *this;
+ struct pw_node *input_node, *output_node;
+
+ if (output == input)
+ goto same_ports;
+
+ if (pw_link_find(output, input))
+ goto link_exists;
+
+ impl = calloc(1, sizeof(struct impl));
+ if (impl == NULL)
+ goto no_mem;
+
+ this = &impl->this;
+ pw_log_debug("link %p: new", this);
+
+ impl->work = pw_work_queue_new(core->main_loop);
+
+ this->core = core;
+ this->properties = properties;
+ this->state = PW_LINK_STATE_INIT;
+
+ this->input = input;
+ this->output = output;
+
+ input_node = input->node;
+ output_node = output->node;
+
+ spa_list_init(&this->resource_list);
+ pw_signal_init(&this->port_unlinked);
+ pw_signal_init(&this->state_changed);
+ pw_signal_init(&this->destroy_signal);
+
+ impl->format_filter = format_filter;
+
+ pw_signal_add(&input->destroy_signal,
+ &impl->input_port_destroy, on_input_port_destroy);
+
+ pw_signal_add(&input_node->async_complete,
+ &impl->input_async_complete, on_input_async_complete_notify);
+
+ pw_signal_add(&output->destroy_signal,
+ &impl->output_port_destroy, on_output_port_destroy);
+
+ pw_signal_add(&output_node->async_complete,
+ &impl->output_async_complete, on_output_async_complete_notify);
+
+ pw_log_debug("link %p: constructed %p:%d -> %p:%d", impl,
+ output_node, output->port_id, input_node, input->port_id);
+
+ input_node->live = output_node->live;
+ if (output_node->clock)
+ input_node->clock = output_node->clock;
+
+ pw_log_debug("link %p: output node %p clock %p, live %d", this, output_node, output_node->clock,
+ output_node->live);
+
+ spa_list_insert(output->links.prev, &this->output_link);
+ spa_list_insert(input->links.prev, &this->input_link);
+
+ spa_list_insert(core->link_list.prev, &this->link);
+
+ pw_core_add_global(core, NULL, core->type.link, 0, this, link_bind_func, &this->global);
+
+ this->info.id = this->global->id;
+ this->info.output_node_id = output ? output_node->global->id : -1;
+ this->info.output_port_id = output ? output->port_id : -1;
+ this->info.input_node_id = input ? input_node->global->id : -1;
+ this->info.input_port_id = input ? input->port_id : -1;
+ this->info.format = NULL;
+
+ spa_graph_port_init(&this->rt.out_port,
+ PW_DIRECTION_OUTPUT,
+ this->rt.out_port.port_id,
+ 0,
+ &this->io);
+ spa_graph_port_init(&this->rt.in_port,
+ PW_DIRECTION_INPUT,
+ this->rt.in_port.port_id,
+ 0,
+ &this->io);
+
+ pw_loop_invoke(output_node->data_loop,
+ do_add_link,
+ SPA_ID_INVALID, sizeof(struct pw_port *), &output, false, this);
+ pw_loop_invoke(input_node->data_loop,
+ do_add_link,
+ SPA_ID_INVALID, sizeof(struct pw_port *), &input, false, this);
+
+ return this;
+
+ same_ports:
+ asprintf(error, "can't link the same ports");
+ return NULL;
+ link_exists:
+ asprintf(error, "link already exists");
+ return NULL;
+ no_mem:
+ asprintf(error, "no memory");
+ return NULL;
+}
+
+void pw_link_destroy(struct pw_link *link)
+{
+ struct impl *impl = SPA_CONTAINER_OF(link, struct impl, this);
+ struct pw_resource *resource, *tmp;
+
+ pw_log_debug("link %p: destroy", impl);
+ pw_signal_emit(&link->destroy_signal, link);
+
+ pw_link_deactivate(link);
+
+ pw_global_destroy(link->global);
+ spa_list_remove(&link->link);
+
+ spa_list_for_each_safe(resource, tmp, &link->resource_list, link)
+ pw_resource_destroy(resource);
+
+ input_remove(link, link->input);
+ spa_list_remove(&link->input_link);
+ link->input = NULL;
+
+ output_remove(link, link->output);
+ spa_list_remove(&link->output_link);
+ link->output = NULL;
+
+ pw_work_queue_destroy(impl->work);
+
+ if (link->info.format)
+ free(link->info.format);
+
+ if (impl->buffer_owner == link)
+ pw_memblock_free(&impl->buffer_mem);
+
+ free(impl);
+}
+
+struct pw_link *pw_link_find(struct pw_port *output_port, struct pw_port *input_port)
+{
+ struct pw_link *pl;
+
+ spa_list_for_each(pl, &output_port->links, output_link) {
+ if (pl->input == input_port)
+ return pl;
+ }
+ return NULL;
+}
diff --git a/src/pipewire/link.h b/src/pipewire/link.h
new file mode 100644
index 00000000..276e5376
--- /dev/null
+++ b/src/pipewire/link.h
@@ -0,0 +1,118 @@
+/* PipeWire
+ * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __PIPEWIRE_LINK_H__
+#define __PIPEWIRE_LINK_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/ringbuffer.h>
+
+#include <pipewire/mem.h>
+#include <pipewire/introspect.h>
+
+#include <pipewire/core.h>
+#include <pipewire/port.h>
+#include <pipewire/main-loop.h>
+
+/** \page page_link Link
+ *
+ * \section page_link_overview Overview
+ *
+ * A link is the connection between 2 nodes (\ref page_node). Nodes are
+ * linked together on ports.
+ *
+ * The link is responsible for negotiating the format and buffers for
+ * the nodes.
+ */
+
+/** \class pw_link
+ *
+ * PipeWire link interface.
+ */
+struct pw_link {
+ struct pw_core *core; /**< core object */
+ struct spa_list link; /**< link in core link_list */
+ struct pw_global *global; /**< global for this link */
+
+ struct pw_properties *properties; /**< extra link properties */
+
+ struct pw_link_info info; /**< introspectable link info */
+
+ enum pw_link_state state; /**< link state */
+ char *error; /**< error message when state error */
+ /** Emited when the link state changed */
+ PW_SIGNAL(state_changed, (struct pw_listener *listener,
+ struct pw_link *link,
+ enum pw_link_state old, enum pw_link_state state));
+
+ /** Emited when the link is destroyed */
+ PW_SIGNAL(destroy_signal, (struct pw_listener *, struct pw_link *));
+
+ struct spa_list resource_list; /**< list of bound resources */
+
+ struct spa_port_io io; /**< link io area */
+
+ struct pw_port *output; /**< output port */
+ struct spa_list output_link; /**< link in output port links */
+ struct pw_port *input; /**< input port */
+ struct spa_list input_link; /**< link in input port links */
+
+ /** Emited when the port is unlinked */
+ PW_SIGNAL(port_unlinked, (struct pw_listener *listener,
+ struct pw_link *link, struct pw_port *port));
+
+ struct {
+ struct spa_graph_port out_port;
+ struct spa_graph_port in_port;
+ } rt;
+};
+
+
+/** Make a new link between two ports \memberof pw_link
+ * \return a newly allocated link */
+struct pw_link *
+pw_link_new(struct pw_core *core, /**< the core object */
+ struct pw_port *output, /**< an output port */
+ struct pw_port *input, /**< an input port */
+ struct spa_format *format_filter, /**< an optional format filter */
+ struct pw_properties *properties /**< extra properties */,
+ char **error /**< error string */);
+
+/** Destroy a link \memberof pw_link */
+void pw_link_destroy(struct pw_link *link);
+
+/** Find the link between 2 ports \memberof pw_link */
+struct pw_link * pw_link_find(struct pw_port *output, struct pw_port *input);
+
+/** Activate a link \memberof pw_link
+ * Starts the negotiation of formats and buffers on \a link and then
+ * starts data streaming */
+bool pw_link_activate(struct pw_link *link);
+
+/** Deactivate a link \memberof pw_link */
+bool pw_link_deactivate(struct pw_link *link);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __PIPEWIRE_LINK_H__ */
diff --git a/src/pipewire/log.c b/src/pipewire/log.c
new file mode 100644
index 00000000..80835281
--- /dev/null
+++ b/src/pipewire/log.c
@@ -0,0 +1,137 @@
+/* PipeWire
+ * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <pipewire/log.h>
+
+#define DEFAULT_LOG_LEVEL SPA_LOG_LEVEL_ERROR
+
+enum spa_log_level pw_log_level = DEFAULT_LOG_LEVEL;
+
+static struct spa_log *global_log = NULL;
+
+/** Set the global log interface
+ * \param log the global log to set
+ * \memberof pw_log
+ */
+void pw_log_set(struct spa_log *log)
+{
+ global_log = log;
+ if (global_log)
+ global_log->level = pw_log_level;
+}
+
+/** Get the global log interface
+ * \return the global log
+ * \memberof pw_log
+ */
+struct spa_log *pw_log_get(void)
+{
+ return global_log;
+}
+
+/** Set the global log level
+ * \param level the new log level
+ * \memberof pw_log
+ */
+void pw_log_set_level(enum spa_log_level level)
+{
+ pw_log_level = level;
+ if (global_log)
+ global_log->level = level;
+}
+
+/** Log a message
+ * \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
+ *
+ * \memberof pw_log
+ */
+void
+pw_log_log(enum spa_log_level level,
+ const char *file,
+ int line,
+ const char *func,
+ const char *fmt, ...)
+{
+ if (SPA_UNLIKELY(pw_log_level_enabled(level) && global_log)) {
+ va_list args;
+ va_start(args, fmt);
+ global_log->logv(global_log, level, file, line, func, fmt, args);
+ va_end(args);
+ }
+}
+
+/** Log a message 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
+ *
+ * \memberof pw_log
+ */
+void
+pw_log_logv(enum spa_log_level level,
+ const char *file,
+ int line,
+ const char *func,
+ const char *fmt,
+ va_list args)
+{
+ if (SPA_UNLIKELY(pw_log_level_enabled(level) && global_log)) {
+ global_log->logv(global_log, level, file, line, func, fmt, args);
+ }
+}
+
+/** \fn void pw_log_error (const char *format, ...)
+ * Log an error message
+ * \param format a printf style format
+ * \param ... printf style arguments
+ * \memberof pw_log
+ */
+/** \fn void pw_log_warn (const char *format, ...)
+ * Log a warning message
+ * \param format a printf style format
+ * \param ... printf style arguments
+ * \memberof pw_log
+ */
+/** \fn void pw_log_info (const char *format, ...)
+ * Log an info message
+ * \param format a printf style format
+ * \param ... printf style arguments
+ * \memberof pw_log
+ */
+/** \fn void pw_log_debug (const char *format, ...)
+ * Log a debug message
+ * \param format a printf style format
+ * \param ... printf style arguments
+ * \memberof pw_log
+ */
+/** \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
+ * \memberof pw_log
+ */
diff --git a/src/pipewire/log.h b/src/pipewire/log.h
new file mode 100644
index 00000000..719dbbe6
--- /dev/null
+++ b/src/pipewire/log.h
@@ -0,0 +1,103 @@
+/* PipeWire
+ * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __PIPEWIRE_LOG_H__
+#define __PIPEWIRE_LOG_H__
+
+#include <spa/log.h>
+#include <spa/loop.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** \class pw_log
+ *
+ * Logging functions of PipeWire
+ *
+ * Loggin 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.
+ */
+
+/** The global log level */
+extern enum spa_log_level pw_log_level;
+
+void pw_log_set(struct spa_log *log);
+struct spa_log *pw_log_get(void);
+
+void
+pw_log_set_level(enum spa_log_level level);
+
+
+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);
+
+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);
+
+
+/** Check if a loglevel is enabled \memberof pw_log */
+#define pw_log_level_enabled(lev) (pw_log_level >= (lev))
+
+#if __STDC_VERSION__ >= 199901L
+
+#define pw_log_logc(lev,...) \
+ if (SPA_UNLIKELY(pw_log_level_enabled (lev))) \
+ pw_log_log(lev,__VA_ARGS__)
+
+#define pw_log_error(...) pw_log_logc(SPA_LOG_LEVEL_ERROR,__FILE__,__LINE__,__func__,__VA_ARGS__)
+#define pw_log_warn(...) pw_log_logc(SPA_LOG_LEVEL_WARN,__FILE__,__LINE__,__func__,__VA_ARGS__)
+#define pw_log_info(...) pw_log_logc(SPA_LOG_LEVEL_INFO,__FILE__,__LINE__,__func__,__VA_ARGS__)
+#define pw_log_debug(...) pw_log_logc(SPA_LOG_LEVEL_DEBUG,__FILE__,__LINE__,__func__,__VA_ARGS__)
+#define pw_log_trace(...) pw_log_logc(SPA_LOG_LEVEL_TRACE,__FILE__,__LINE__,__func__,__VA_ARGS__)
+
+#else
+
+#include <stdarg.h>
+
+#define PW_LOG_FUNC(name,lev) \
+static inline void pw_log_##name (const char *format, ...) SPA_PRINTF_FUNC(1, 0); \
+{ \
+ if (SPA_UNLIKELY(pw_log_level_enabled(lev))) { \
+ va_list varargs; \
+ va_start(varargs, format); \
+ pw_log_logv(lev,__FILE__,__LINE__,__func__,format,varargs); \
+ va_end(varargs); \
+ } \
+}
+
+PW_LOG_FUNC(error, SPA_LOG_LEVEL_ERROR)
+PW_LOG_FUNC(warn, SPA_LOG_LEVEL_WARN)
+PW_LOG_FUNC(info, SPA_LOG_LEVEL_INFO)
+PW_LOG_FUNC(debug, SPA_LOG_LEVEL_DEBUG)
+PW_LOG_FUNC(trace, SPA_LOG_LEVEL_TRACE)
+
+#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 00000000..4fd3c8b5
--- /dev/null
+++ b/src/pipewire/loop.c
@@ -0,0 +1,127 @@
+/* PipeWire
+ * Copyright (C) 2016 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <spa/loop.h>
+#include <spa/type-map.h>
+
+#include <pipewire/pipewire.h>
+#include <pipewire/loop.h>
+#include <pipewire/log.h>
+
+#define DATAS_SIZE (4096 * 8)
+
+/** \cond */
+
+struct impl {
+ struct pw_loop this;
+
+ struct spa_handle *handle;
+};
+/** \endcond */
+
+/** Create a new loop
+ * \returns a newly allocated loop
+ * \memberof pw_loop
+ */
+struct pw_loop *pw_loop_new(void)
+{
+ int res;
+ struct impl *impl;
+ struct pw_loop *this;
+ const struct spa_handle_factory *factory;
+ struct spa_type_map *map;
+ void *iface;
+ const struct spa_support *support;
+ uint32_t n_support;
+
+ support = pw_get_support(&n_support);
+ if (support == NULL)
+ return NULL;
+
+ map = spa_support_find(support, n_support, SPA_TYPE__TypeMap);
+ if (map == NULL)
+ return NULL;
+
+ factory = pw_get_support_factory("loop");
+ if (factory == NULL)
+ return NULL;
+
+ impl = calloc(1, sizeof(struct impl) + factory->size);
+ if (impl == NULL)
+ return NULL;
+
+ impl->handle = SPA_MEMBER(impl, sizeof(struct impl), struct spa_handle);
+
+ this = &impl->this;
+
+ pw_signal_init(&this->destroy_signal);
+
+ if ((res = spa_handle_factory_init(factory,
+ impl->handle,
+ NULL,
+ support,
+ n_support)) < 0) {
+ fprintf(stderr, "can't make factory instance: %d\n", res);
+ goto failed;
+ }
+
+ if ((res = spa_handle_get_interface(impl->handle,
+ spa_type_map_get_id(map, SPA_TYPE__Loop),
+ &iface)) < 0) {
+ fprintf(stderr, "can't get %s interface %d\n", SPA_TYPE__Loop, res);
+ goto failed;
+ }
+ this->loop = iface;
+
+ if ((res = spa_handle_get_interface(impl->handle,
+ spa_type_map_get_id(map, SPA_TYPE__LoopControl),
+ &iface)) < 0) {
+ fprintf(stderr, "can't get %s interface %d\n", SPA_TYPE__LoopControl, res);
+ goto failed;
+ }
+ this->control = iface;
+
+ if ((res = spa_handle_get_interface(impl->handle,
+ spa_type_map_get_id(map, SPA_TYPE__LoopUtils),
+ &iface)) < 0) {
+ fprintf(stderr, "can't get %s interface %d\n", SPA_TYPE__LoopUtils, res);
+ goto failed;
+ }
+ this->utils = iface;
+
+ return this;
+
+ failed:
+ free(impl);
+ return NULL;
+}
+
+/** Destroy a loop
+ * \param loop a loop to destroy
+ * \memberof pw_loop
+ */
+void pw_loop_destroy(struct pw_loop *loop)
+{
+ struct impl *impl = SPA_CONTAINER_OF(loop, struct impl, this);
+
+ pw_signal_emit(&loop->destroy_signal, loop);
+
+ spa_handle_clear(impl->handle);
+ free(impl);
+}
diff --git a/src/pipewire/loop.h b/src/pipewire/loop.h
new file mode 100644
index 00000000..dceb4cfe
--- /dev/null
+++ b/src/pipewire/loop.h
@@ -0,0 +1,78 @@
+/* PipeWire
+ * Copyright (C) 2016 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __PIPEWIRE_LOOP_H__
+#define __PIPEWIRE_LOOP_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/loop.h>
+
+#include <pipewire/sig.h>
+
+/** \class pw_loop
+ *
+ * PipeWire loop interface provides an implementation of
+ * the spa loop interfaces. It can be used to implement various
+ * event loops.
+ */
+struct pw_loop {
+ struct spa_loop *loop; /**< wrapped loop */
+ struct spa_loop_control *control; /**< loop control */
+ struct spa_loop_utils *utils; /**< loop utils */
+
+ /** Emited when the loop is destroyed */
+ PW_SIGNAL(destroy_signal, (struct pw_listener *listener, struct pw_loop *loop));
+};
+
+struct pw_loop *
+pw_loop_new(void);
+
+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(__VA_ARGS__)
+#define pw_loop_remove_source(l,...) spa_loop_remove_source(__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_hooks(l,...) spa_loop_control_add_hooks((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 00000000..f1aca16c
--- /dev/null
+++ b/src/pipewire/main-loop.c
@@ -0,0 +1,112 @@
+/* PipeWire
+ * Copyright (C) 2016 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include "pipewire/log.h"
+#include "pipewire/main-loop.h"
+
+/** \cond */
+struct impl {
+ struct pw_main_loop this;
+
+ bool running;
+};
+/** \endcond */
+
+/** Create a new new main loop
+ * \return a newly allocated \ref pw_main_loop
+ *
+ * \memberof pw_main_loop
+ */
+struct pw_main_loop *pw_main_loop_new(void)
+{
+ struct impl *impl;
+ struct pw_main_loop *this;
+
+ impl = calloc(1, sizeof(struct impl));
+ if (impl == NULL)
+ return NULL;
+
+ pw_log_debug("main-loop %p: new", impl);
+ this = &impl->this;
+
+ this->loop = pw_loop_new();
+ if (this->loop == NULL)
+ goto no_loop;
+
+ pw_signal_init(&this->destroy_signal);
+
+ return this;
+
+ no_loop:
+ free(impl);
+ return NULL;
+}
+
+/** Destroy a main loop
+ * \param loop the main loop to destroy
+ *
+ * \memberof pw_main_loop
+ */
+void pw_main_loop_destroy(struct pw_main_loop *loop)
+{
+ struct impl *impl = SPA_CONTAINER_OF(loop, struct impl, this);
+
+ pw_log_debug("main-loop %p: destroy", impl);
+ pw_signal_emit(&loop->destroy_signal, loop);
+
+ pw_loop_destroy(loop->loop);
+
+ free(impl);
+}
+
+/** Stop a main loop
+ * \param loop a \ref pw_main_loop to stop
+ *
+ * The call to \ref pw_main_loop_run() will return
+ *
+ * \memberof pw_main_loop
+ */
+void pw_main_loop_quit(struct pw_main_loop *loop)
+{
+ struct impl *impl = SPA_CONTAINER_OF(loop, struct impl, this);
+ pw_log_debug("main-loop %p: quit", impl);
+ impl->running = false;
+}
+
+/** 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
+ *
+ * \memberof pw_main_loop
+ */
+void pw_main_loop_run(struct pw_main_loop *loop)
+{
+ struct impl *impl = SPA_CONTAINER_OF(loop, struct impl, this);
+
+ pw_log_debug("main-loop %p: run", impl);
+
+ impl->running = true;
+ pw_loop_enter(loop->loop);
+ while (impl->running) {
+ pw_loop_iterate(loop->loop, -1);
+ }
+ pw_loop_leave(loop->loop);
+}
diff --git a/src/pipewire/main-loop.h b/src/pipewire/main-loop.h
new file mode 100644
index 00000000..be5e279d
--- /dev/null
+++ b/src/pipewire/main-loop.h
@@ -0,0 +1,60 @@
+/* PipeWire
+ * Copyright (C) 2016 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __PIPEWIRE_MAIN_LOOP_H__
+#define __PIPEWIRE_MAIN_LOOP_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <pipewire/loop.h>
+
+/** \class pw_main_loop
+ *
+ * \brief PipeWire main-loop interface.
+ *
+ * A main loop object
+ */
+
+/** A main loop object \memberof pw_main_loop */
+struct pw_main_loop {
+ struct pw_loop *loop; /**< the wrapped loop */
+
+ /** Emited when the loop is destroyed */
+ PW_SIGNAL(destroy_signal, (struct pw_listener *listener, struct pw_main_loop *loop));
+};
+
+struct pw_main_loop *
+pw_main_loop_new(void);
+
+void
+pw_main_loop_destroy(struct pw_main_loop *loop);
+
+void
+pw_main_loop_run(struct pw_main_loop *loop);
+
+void
+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 00000000..0f98c7e6
--- /dev/null
+++ b/src/pipewire/map.h
@@ -0,0 +1,184 @@
+/* PipeWire
+ * Copyright (C) 2016 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __PIPEWIRE_MAP_H__
+#define __PIPEWIRE_MAP_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <string.h>
+#include <stdio.h>
+
+#include <spa/defs.h>
+#include <pipewire/array.h>
+#include <pipewire/log.h>
+
+/** \class pw_map
+ *
+ * A map that holds objects indexed by id
+ */
+
+/** An entry in the map \memberof pw_map */
+union pw_map_item {
+ uint32_t next; /**< next free index */
+ void *data; /**< data of this item, must be an even address */
+};
+
+/** A map \memberof pw_map */
+struct pw_map {
+ struct pw_array items; /**< an array with the map items */
+ uint32_t free_list; /**< the free items */
+};
+
+#define PW_MAP_INIT(extend) { PW_ARRAY_INIT(extend), 0 }
+
+#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)))
+#define pw_map_check_id(m,id) ((id) < pw_map_get_size(m))
+#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 \memberof pw_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 \memberof pw_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
+ * \memberof pw_map
+ */
+static inline void pw_map_init(struct pw_map *map, size_t size, size_t extend)
+{
+ pw_array_init(&map->items, extend);
+ pw_array_ensure_size(&map->items, size * sizeof(union pw_map_item));
+ map->free_list = 0;
+}
+
+/** Clear a map
+ * \param map the map to clear
+ * \memberof pw_map
+ */
+static inline void pw_map_clear(struct pw_map *map)
+{
+ pw_array_clear(&map->items);
+}
+
+/** Insert data in the map
+ * \param map the map to insert into
+ * \param data the item to add
+ * \return the id where the item was inserted
+ * \memberof pw_map
+ */
+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) {
+ start = map->items.data;
+ item = &start[map->free_list >> 1];
+ map->free_list = item->next;
+ } else {
+ item = pw_array_add(&map->items, sizeof(union pw_map_item));
+ if (!item)
+ return SPA_ID_INVALID;
+ start = map->items.data;
+ }
+ item->data = data;
+ id = (item - start);
+ return id;
+}
+
+/** Insert data in the map at an index
+ * \param map the map to inser into
+ * \param id the index to insert at
+ * \param data the data to insert
+ * \return true on success, false when the index is invalid
+ * \memberof pw_map
+ */
+static inline bool 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 false;
+ else if (id == size)
+ item = pw_array_add(&map->items, sizeof(union pw_map_item));
+ else
+ item = pw_map_get_item(map, id);
+
+ item->data = data;
+ return true;
+}
+
+/** Remove and item at index
+ * \param map the map to remove from
+ * \param id the index to remove
+ * \memberof pw_map
+ */
+static inline void pw_map_remove(struct pw_map *map, uint32_t id)
+{
+ 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
+ * \memberof pw_map
+ */
+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
+ * \param data data to pass to \a func
+ * \memberof pw_map
+ */
+static inline void pw_map_for_each(struct pw_map *map, void (*func) (void *, void *), void *data)
+{
+ union pw_map_item *item;
+
+ pw_array_for_each(item, &map->items) {
+ if (item->data && !pw_map_item_is_free(item))
+ func(item->data, data);
+ }
+}
+
+#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 00000000..10d42d33
--- /dev/null
+++ b/src/pipewire/mem.c
@@ -0,0 +1,221 @@
+/* PipeWire
+ * Copyright (C) 2016 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#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 <pipewire/log.h>
+#include <pipewire/mem.h>
+
+/*
+ * 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);
+}
+
+/* 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
+
+
+#undef USE_MEMFD
+
+/** Map a memblock
+ * \param mem a memblock
+ * \return 0 on success, < 0 on error
+ * \memberof pw_memblock
+ */
+int pw_memblock_map(struct pw_memblock *mem)
+{
+ if (mem->ptr != NULL)
+ return SPA_RESULT_OK;
+
+ 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;
+
+ mem->ptr =
+ mmap(NULL, mem->size << 1, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1,
+ 0);
+ if (mem->ptr == MAP_FAILED)
+ return SPA_RESULT_NO_MEMORY;
+
+ 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 SPA_RESULT_NO_MEMORY;
+ }
+
+ ptr =
+ mmap(mem->ptr + mem->size, mem->size, prot, MAP_FIXED | MAP_SHARED,
+ mem->fd, mem->offset);
+ if (ptr != mem->ptr + mem->size) {
+ munmap(mem->ptr, mem->size << 1);
+ return SPA_RESULT_NO_MEMORY;
+ }
+ } else {
+ mem->ptr = mmap(NULL, mem->size, prot, MAP_SHARED, mem->fd, 0);
+ if (mem->ptr == MAP_FAILED)
+ return SPA_RESULT_NO_MEMORY;
+ }
+ } else {
+ mem->ptr = NULL;
+ }
+ return SPA_RESULT_OK;
+}
+
+/** Create a new memblock
+ * \param flags memblock flags
+ * \param size size to allocate
+ * \param[out] mem memblock structure to fill
+ * \return 0 on success, < 0 on error
+ * \memberof pw_memblock
+ */
+int pw_memblock_alloc(enum pw_memblock_flags flags, size_t size, struct pw_memblock *mem)
+{
+ bool use_fd;
+
+ if (mem == NULL || size == 0)
+ return SPA_RESULT_INVALID_ARGUMENTS;
+
+ mem->offset = 0;
+ mem->flags = flags;
+ mem->size = size;
+ mem->ptr = NULL;
+
+ use_fd = ! !(flags & (PW_MEMBLOCK_FLAG_MAP_TWICE | PW_MEMBLOCK_FLAG_WITH_FD));
+
+ if (use_fd) {
+#ifdef USE_MEMFD
+ mem->fd = memfd_create("pipewire-memfd", MFD_CLOEXEC | MFD_ALLOW_SEALING);
+ if (mem->fd == -1) {
+ pw_log_error("Failed to create memfd: %s\n", strerror(errno));
+ return SPA_RESULT_ERRNO;
+ }
+#else
+ char filename[] = "/dev/shm/pipewire-tmpfile.XXXXXX";
+ mem->fd = mkostemp(filename, O_CLOEXEC);
+ if (mem->fd == -1) {
+ pw_log_error("Failed to create temporary file: %s\n", strerror(errno));
+ return SPA_RESULT_ERRNO;
+ }
+ unlink(filename);
+#endif
+
+ if (ftruncate(mem->fd, size) < 0) {
+ pw_log_warn("Failed to truncate temporary file: %s", strerror(errno));
+ close(mem->fd);
+ return SPA_RESULT_ERRNO;
+ }
+#ifdef USE_MEMFD
+ if (flags & PW_MEMBLOCK_FLAG_SEAL) {
+ unsigned int seals = F_SEAL_GROW | F_SEAL_SHRINK | F_SEAL_SEAL;
+ if (fcntl(mem->fd, F_ADD_SEALS, seals) == -1) {
+ pw_log_warn("Failed to add seals: %s", strerror(errno));
+ }
+ }
+#endif
+ if (pw_memblock_map(mem) != SPA_RESULT_OK)
+ goto mmap_failed;
+ } else {
+ mem->ptr = malloc(size);
+ if (mem->ptr == NULL)
+ return SPA_RESULT_NO_MEMORY;
+ mem->fd = -1;
+ }
+ if (!(flags & PW_MEMBLOCK_FLAG_WITH_FD) && mem->fd != -1) {
+ close(mem->fd);
+ mem->fd = -1;
+ }
+ return SPA_RESULT_OK;
+
+ mmap_failed:
+ close(mem->fd);
+ return SPA_RESULT_NO_MEMORY;
+}
+
+/** Free a memblock
+ * \param mem a memblock
+ * \memberof pw_memblock
+ */
+void pw_memblock_free(struct pw_memblock *mem)
+{
+ if (mem == NULL)
+ return;
+
+ if (mem->flags & PW_MEMBLOCK_FLAG_WITH_FD) {
+ if (mem->ptr)
+ munmap(mem->ptr, mem->size);
+ if (mem->fd != -1)
+ close(mem->fd);
+ } else {
+ free(mem->ptr);
+ }
+ mem->ptr = NULL;
+ mem->fd = -1;
+}
diff --git a/src/pipewire/mem.h b/src/pipewire/mem.h
new file mode 100644
index 00000000..0e5240e2
--- /dev/null
+++ b/src/pipewire/mem.h
@@ -0,0 +1,64 @@
+/* PipeWire
+ * Copyright (C) 2016 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __PIPEWIRE_MEM_H__
+#define __PIPEWIRE_MEM_H__
+
+#include <spa/defs.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** Flags passed to \ref pw_memblock_alloc() \memberof pw_memblock */
+enum pw_memblock_flags {
+ PW_MEMBLOCK_FLAG_NONE = 0,
+ PW_MEMBLOCK_FLAG_WITH_FD = (1 << 0),
+ PW_MEMBLOCK_FLAG_SEAL = (1 << 1),
+ PW_MEMBLOCK_FLAG_MAP_READ = (1 << 2),
+ PW_MEMBLOCK_FLAG_MAP_WRITE = (1 << 3),
+ PW_MEMBLOCK_FLAG_MAP_TWICE = (1 << 4),
+};
+
+#define PW_MEMBLOCK_FLAG_MAP_READWRITE (PW_MEMBLOCK_FLAG_MAP_READ | PW_MEMBLOCK_FLAG_MAP_WRITE)
+
+/** \class pw_memblock
+ * Memory block structure */
+struct pw_memblock {
+ enum pw_memblock_flags flags; /**< flags used when allocating */
+ int fd; /**< memfd if any */
+ off_t offset; /**< offset of mappable memory */
+ void *ptr; /**< ptr to mapped memory */
+ size_t size; /**< size of mapped memory */
+};
+
+int
+pw_memblock_alloc(enum pw_memblock_flags flags, size_t size, struct pw_memblock *mem);
+
+int
+pw_memblock_map(struct pw_memblock *mem);
+
+void
+pw_memblock_free(struct pw_memblock *mem);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __PIPEWIRE_MEM_H__ */
diff --git a/src/pipewire/memfd-wrappers.h b/src/pipewire/memfd-wrappers.h
new file mode 100644
index 00000000..05958fe4
--- /dev/null
+++ b/src/pipewire/memfd-wrappers.h
@@ -0,0 +1,60 @@
+/* PipeWire
+ * Copyright (C) 2016 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <sys/syscall.h>
+#include <fcntl.h>
+
+/*
+ * 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);
+}
+
+/* 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
diff --git a/src/pipewire/meson.build b/src/pipewire/meson.build
new file mode 100644
index 00000000..782bc1f8
--- /dev/null
+++ b/src/pipewire/meson.build
@@ -0,0 +1,86 @@
+pipewire_headers = [
+ 'array.h',
+ 'client.h',
+ 'command.h',
+ 'core.h',
+ 'data-loop.h',
+ 'interfaces.h',
+ 'introspect.h',
+ 'link.h',
+ 'log.h',
+ 'loop.h',
+ 'main-loop.h',
+ 'map.h',
+ 'mem.h',
+ 'module.h',
+ 'node.h',
+ 'node-factory.h',
+ 'pipewire.h',
+ 'port.h',
+ 'properties.h',
+ 'protocol.h',
+ 'proxy.h',
+ 'remote.h',
+ 'resource.h',
+ 'rtkit.h',
+ 'sig.h',
+ 'stream.h',
+ 'thread-loop.h',
+ 'transport.h',
+ 'type.h',
+ 'utils.h',
+ 'work-queue.h',
+]
+
+pipewire_sources = [
+ 'client.c',
+ 'command.c',
+ 'core.c',
+ 'data-loop.c',
+ 'introspect.c',
+ 'link.c',
+ 'log.c',
+ 'loop.c',
+ 'main-loop.c',
+ 'mem.c',
+ 'module.c',
+ 'node.c',
+ 'node-factory.c',
+ 'pipewire.c',
+ 'port.c',
+ 'properties.c',
+ 'protocol.c',
+ 'proxy.c',
+ 'remote.c',
+ 'resource.c',
+ 'stream.c',
+ 'rtkit.c',
+ 'thread-loop.c',
+ 'transport.c',
+ 'type.c',
+ 'utils.c',
+ 'work-queue.c',
+]
+
+install_headers(pipewire_headers, subdir : 'pipewire')
+
+libpipewire_c_args = [
+ '-DHAVE_CONFIG_H',
+ '-D_GNU_SOURCE',
+ '-D_POSIX_C_SOURCE',
+]
+
+libpipewire = shared_library('pipewire-@0@'.format(apiversion), pipewire_sources,
+ version : libversion,
+ soversion : soversion,
+ c_args : libpipewire_c_args,
+ include_directories : [pipewire_inc, configinc, spa_inc],
+ link_with : spalib,
+ install : true,
+ dependencies : [dbus_dep, dl_lib, mathlib, pthread_lib],
+)
+
+pipewire_dep = declare_dependency(link_with : libpipewire,
+ include_directories : [pipewire_inc, configinc, spa_inc],
+ dependencies : [pthread_lib,spalib_dep],
+)
diff --git a/src/pipewire/module.c b/src/pipewire/module.c
new file mode 100644
index 00000000..c3d156b6
--- /dev/null
+++ b/src/pipewire/module.c
@@ -0,0 +1,235 @@
+/* PipeWire
+ * Copyright (C) 2016 Axis Communications <dev-gstreamer@axis.com>
+ * @author Linus Svensson <linus.svensson@axis.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <dlfcn.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include <errno.h>
+
+#include "pipewire/pipewire.h"
+#include "pipewire/interfaces.h"
+#include "pipewire/utils.h"
+#include "pipewire/module.h"
+
+/** \cond */
+struct impl {
+ struct pw_module this;
+ void *hnd;
+};
+/** \endcond */
+
+static char *find_module(const char *path, const char *name)
+{
+ char *filename;
+ struct dirent *entry;
+ struct stat s;
+ DIR *dir;
+
+ asprintf(&filename, "%s/%s.so", path, name);
+
+ 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 */
+
+ dir = opendir(path);
+ if (dir == NULL) {
+ pw_log_warn("could not open %s: %s", path, strerror(errno));
+ return NULL;
+ }
+
+ while ((entry = readdir(dir))) {
+ char *newpath;
+
+ if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
+ continue;
+
+ asprintf(&newpath, "%s/%s", path, entry->d_name);
+
+ if (stat(newpath, &s) == 0 && S_ISDIR(s.st_mode)) {
+ filename = find_module(newpath, name);
+ }
+ free(newpath);
+
+ if (filename != NULL)
+ break;
+ }
+
+ closedir(dir);
+
+ return filename;
+}
+
+static int
+module_bind_func(struct pw_global *global, struct pw_client *client, uint32_t version, uint32_t id)
+{
+ struct pw_module *this = global->object;
+ struct pw_resource *resource;
+
+ resource = pw_resource_new(client, id, global->type, 0);
+ if (resource == NULL)
+ goto no_mem;
+
+ pw_resource_set_implementation(resource, global->object, PW_VERSION_MODULE, NULL, NULL);
+
+ pw_log_debug("module %p: bound to %d", global->object, resource->id);
+
+ this->info.change_mask = ~0;
+ pw_module_notify_info(resource, &this->info);
+
+ return SPA_RESULT_OK;
+
+ no_mem:
+ pw_log_error("can't create module resource");
+ pw_core_notify_error(client->core_resource,
+ client->core_resource->id, SPA_RESULT_NO_MEMORY, "no memory");
+ return SPA_RESULT_NO_MEMORY;
+}
+
+/** Load a module
+ *
+ * \param core a \ref pw_core
+ * \param name name of the module to load
+ * \param args A string with arguments for the module
+ * \param[out] err Return location for an error string, or NULL
+ * \return A \ref pw_module if the module could be loaded, or NULL on failure.
+ *
+ * \memberof pw_module
+ */
+struct pw_module *pw_module_load(struct pw_core *core,
+ const char *name, const char *args, char **err)
+{
+ struct pw_module *this;
+ struct impl *impl;
+ void *hnd;
+ char *filename = NULL;
+ const char *module_dir;
+ pw_module_init_func_t init_func;
+
+ module_dir = getenv("PIPEWIRE_MODULE_DIR");
+ if (module_dir != NULL) {
+ char **l;
+ int i, n_paths;
+
+ pw_log_debug("PIPEWIRE_MODULE_DIR set to: %s", module_dir);
+
+ l = pw_split_strv(module_dir, "/", 0, &n_paths);
+ for (i = 0; l[i] != NULL; i++) {
+ filename = find_module(l[i], name);
+ if (filename != NULL)
+ break;
+ }
+ pw_free_strv(l);
+ } else {
+ pw_log_debug("moduledir set to: %s", MODULEDIR);
+
+ filename = find_module(MODULEDIR, name);
+ }
+
+ if (filename == NULL)
+ goto not_found;
+
+ pw_log_debug("trying to load module: %s (%s)", name, filename);
+
+ hnd = dlopen(filename, RTLD_NOW | RTLD_LOCAL);
+
+ if (hnd == NULL)
+ goto open_failed;
+
+ if ((init_func = dlsym(hnd, PIPEWIRE_SYMBOL_MODULE_INIT)) == NULL)
+ goto no_pw_module;
+
+ impl = calloc(1, sizeof(struct impl));
+ if (impl == NULL)
+ goto no_mem;
+
+ impl->hnd = hnd;
+
+ this = &impl->this;
+ this->core = core;
+
+ pw_signal_init(&this->destroy_signal);
+
+ if (!init_func(this, (char *) args))
+ goto init_failed;
+
+ pw_core_add_global(core, NULL, core->type.module, 0, impl, module_bind_func, &this->global);
+
+ this->info.id = this->global->id;
+ this->info.name = name ? strdup(name) : NULL;
+ this->info.filename = filename;
+ this->info.args = args ? strdup(args) : NULL;
+ this->info.props = NULL;
+
+ pw_log_debug("loaded module: %s", this->info.name);
+
+ return this;
+
+ not_found:
+ if (err)
+ asprintf(err, "No module \"%s\" was found", name);
+ return NULL;
+ open_failed:
+ if (err)
+ asprintf(err, "Failed to open module: \"%s\" %s", filename, dlerror());
+ free(filename);
+ return NULL;
+ no_mem:
+ no_pw_module:
+ if (err)
+ asprintf(err, "\"%s\" is not a pipewire module", name);
+ dlclose(hnd);
+ free(filename);
+ return NULL;
+ init_failed:
+ if (err)
+ asprintf(err, "\"%s\" failed to initialize", name);
+ pw_module_destroy(this);
+ return NULL;
+}
+
+/** Destroy a module
+ * \param module the module to destroy
+ * \memberof pw_module
+ */
+void pw_module_destroy(struct pw_module *module)
+{
+ struct impl *impl = SPA_CONTAINER_OF(module, struct impl, this);
+
+ pw_signal_emit(&module->destroy_signal, module);
+
+ if (module->info.name)
+ free((char *) module->info.name);
+ if (module->info.filename)
+ free((char *) module->info.filename);
+ if (module->info.args)
+ free((char *) module->info.args);
+ dlclose(impl->hnd);
+ free(impl);
+}
diff --git a/src/pipewire/module.h b/src/pipewire/module.h
new file mode 100644
index 00000000..fb390ca9
--- /dev/null
+++ b/src/pipewire/module.h
@@ -0,0 +1,73 @@
+/* PipeWire
+ * Copyright (C) 2016 Axis Communications <dev-gstreamer@axis.com>
+ * @author Linus Svensson <linus.svensson@axis.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __PIPEWIRE_MODULE_H__
+#define __PIPEWIRE_MODULE_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <pipewire/core.h>
+
+#define PIPEWIRE_SYMBOL_MODULE_INIT "pipewire__module_init"
+
+/** \class pw_module
+ *
+ * A dynamically loadable module
+ */
+struct pw_module {
+ struct pw_core *core; /**< the core object */
+ struct spa_list link; /**< link in the core module_list */
+ struct pw_global *global; /**< global object for this module */
+
+ struct pw_module_info info; /**< introspectable module info */
+
+ void *user_data; /**< module user_data */
+
+ /** Emited when the module is destroyed */
+ PW_SIGNAL(destroy_signal, (struct pw_listener *listener, struct pw_module *module));
+};
+
+/** Module init function signature
+ *
+ * \param module A \ref pw_module
+ * \param args Arguments to the module
+ * \return true on success, false otherwise
+ *
+ * A module should provide an init function with this signature. This function
+ * will be called when a module is loaded.
+ *
+ * \memberof pw_module
+ */
+typedef bool (*pw_module_init_func_t) (struct pw_module *module, char *args);
+
+struct pw_module *
+pw_module_load(struct pw_core *core,
+ const char *name, const char *args, char **err);
+
+void
+pw_module_destroy(struct pw_module *module);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __PIPEWIRE_MODULE_H__ */
diff --git a/src/pipewire/node-factory.c b/src/pipewire/node-factory.c
new file mode 100644
index 00000000..616384af
--- /dev/null
+++ b/src/pipewire/node-factory.c
@@ -0,0 +1,21 @@
+/* PipeWire
+ * Copyright (C) 2016 Axis Communications AB
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include "pipewire/pipewire.h"
+#include "pipewire/node-factory.h"
diff --git a/src/pipewire/node-factory.h b/src/pipewire/node-factory.h
new file mode 100644
index 00000000..20452b2c
--- /dev/null
+++ b/src/pipewire/node-factory.h
@@ -0,0 +1,63 @@
+/* PipeWire
+ * Copyright (C) 2016 Axis Communications AB
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __PIPEWIRE_NODE_FACTORY_H__
+#define __PIPEWIRE_NODE_FACTORY_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define PIPEWIRE_TYPE__NodeFactory "PipeWire:Object:NodeFactory"
+#define PIPEWIRE_TYPE_NODE_FACTORY_BASE PIPEWIRE_TYPE__NodeFactory ":"
+
+#include <pipewire/core.h>
+#include <pipewire/resource.h>
+
+/** \class pw_node_factory
+ *
+ * \brief PipeWire node factory interface.
+ *
+ * The factory object is used to make nodes on demand.
+ */
+struct pw_node_factory {
+ struct pw_core *core; /**< the core */
+ struct spa_list link; /**< link in core node_factory_list */
+ struct pw_global *global; /**< global for this factory */
+
+ const char *name; /**< the factory name */
+ uint32_t type; /**< type of the created nodes */
+
+ /** Emited when the factory is destroyed */
+ PW_SIGNAL(destroy_signal, (struct pw_listener *listener, struct pw_node_factory *object));
+
+ /** The function to create a node from this factory */
+ struct pw_node *(*create_node) (struct pw_node_factory *factory,
+ struct pw_resource *resource,
+ const char *name,
+ struct pw_properties *properties);
+};
+
+#define pw_node_factory_create_node(f,...) (f)->create_node((f),__VA_ARGS__)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __PIPEWIRE_NODE_FACTORY_H__ */
diff --git a/src/pipewire/node.c b/src/pipewire/node.c
new file mode 100644
index 00000000..0a573a00
--- /dev/null
+++ b/src/pipewire/node.c
@@ -0,0 +1,617 @@
+/* PipeWire
+ * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include "pipewire/pipewire.h"
+#include "pipewire/interfaces.h"
+
+#include "pipewire/node.h"
+#include "pipewire/data-loop.h"
+#include "pipewire/main-loop.h"
+#include "pipewire/work-queue.h"
+
+/** \cond */
+struct impl {
+ struct pw_node this;
+
+ struct pw_work_queue *work;
+ struct pw_listener on_async_complete;
+ struct pw_listener on_event;
+
+ bool exported;
+};
+
+/** \endcond */
+
+static int pause_node(struct pw_node *this)
+{
+ int res = SPA_RESULT_OK;
+
+ if (this->info.state <= PW_NODE_STATE_IDLE)
+ return SPA_RESULT_OK;
+
+ pw_log_debug("node %p: pause node", this);
+ if ((res = this->implementation->send_command(this,
+ &SPA_COMMAND_INIT(this->core->type.command_node.Pause))) < 0)
+ pw_log_debug("node %p: send command error %d", this, res);
+
+ return res;
+}
+
+static int start_node(struct pw_node *this)
+{
+ int res = SPA_RESULT_OK;
+
+ pw_log_debug("node %p: start node", this);
+ if ((res = this->implementation->send_command(this,
+ &SPA_COMMAND_INIT(this->core->type.command_node.Start))) < 0)
+ pw_log_debug("node %p: send command error %d", this, res);
+
+ return res;
+}
+
+static int suspend_node(struct pw_node *this)
+{
+ int res = SPA_RESULT_OK;
+ struct pw_port *p;
+
+ pw_log_debug("node %p: suspend node", this);
+
+ spa_list_for_each(p, &this->input_ports, link) {
+ if ((res = pw_port_set_format(p, 0, NULL)) < 0)
+ pw_log_warn("error unset format input: %d", res);
+ p->state = PW_PORT_STATE_CONFIGURE;
+ }
+
+ spa_list_for_each(p, &this->output_ports, link) {
+ if ((res = pw_port_set_format(p, 0, NULL)) < 0)
+ pw_log_warn("error unset format output: %d", res);
+ p->state = PW_PORT_STATE_CONFIGURE;
+ }
+ return res;
+}
+
+static void on_async_complete(struct pw_listener *listener,
+ struct pw_node *node, uint32_t seq, int res)
+{
+ struct impl *impl = SPA_CONTAINER_OF(listener, struct impl, on_async_complete);
+ struct pw_node *this = &impl->this;
+
+ pw_log_debug("node %p: async complete event %d %d", this, seq, res);
+ pw_work_queue_complete(impl->work, this, seq, res);
+}
+
+static void send_clock_update(struct pw_node *this)
+{
+ int res;
+ struct spa_command_node_clock_update cu =
+ SPA_COMMAND_NODE_CLOCK_UPDATE_INIT(this->core->type.command_node.ClockUpdate,
+ SPA_COMMAND_NODE_CLOCK_UPDATE_TIME |
+ SPA_COMMAND_NODE_CLOCK_UPDATE_SCALE |
+ SPA_COMMAND_NODE_CLOCK_UPDATE_STATE |
+ SPA_COMMAND_NODE_CLOCK_UPDATE_LATENCY, /* change_mask */
+ 1, /* rate */
+ 0, /* ticks */
+ 0, /* monotonic_time */
+ 0, /* offset */
+ (1 << 16) | 1, /* scale */
+ SPA_CLOCK_STATE_RUNNING, /* state */
+ 0, /* flags */
+ 0); /* latency */
+
+ if (this->clock && this->live) {
+ cu.body.flags.value = SPA_COMMAND_NODE_CLOCK_UPDATE_FLAG_LIVE;
+ res = spa_clock_get_time(this->clock,
+ &cu.body.rate.value,
+ &cu.body.ticks.value,
+ &cu.body.monotonic_time.value);
+ }
+ if ((res = this->implementation->send_command(this, (struct spa_command *) &cu)) < 0)
+ pw_log_debug("node %p: send clock update error %d", this, res);
+}
+
+static void on_event(struct pw_listener *listener,
+ struct pw_node *node, struct spa_event *event)
+{
+ struct impl *impl = SPA_CONTAINER_OF(listener, struct impl, on_event);
+ struct pw_node *this = &impl->this;
+
+ pw_log_trace("node %p: event %d", this, SPA_EVENT_TYPE(event));
+ if (SPA_EVENT_TYPE(event) == this->core->type.event_node.RequestClockUpdate) {
+ send_clock_update(this);
+ }
+}
+
+static void node_unbind_func(void *data)
+{
+ struct pw_resource *resource = data;
+ spa_list_remove(&resource->link);
+}
+
+static void
+update_info(struct pw_node *this)
+{
+ this->info.id = this->global->id;
+ this->info.input_formats = NULL;
+
+ if (!spa_list_is_empty(&this->input_ports)) {
+ struct pw_port *port = spa_list_first(&this->input_ports, struct pw_port, link);
+
+ for (this->info.n_input_formats = 0;; this->info.n_input_formats++) {
+ struct spa_format *fmt;
+
+ if (pw_port_enum_formats(port, &fmt, NULL, this->info.n_input_formats) < 0)
+ break;
+
+ this->info.input_formats =
+ realloc(this->info.input_formats,
+ sizeof(struct spa_format *) * (this->info.n_input_formats + 1));
+ this->info.input_formats[this->info.n_input_formats] = spa_format_copy(fmt);
+ }
+ }
+
+ this->info.output_formats = NULL;
+ if (!spa_list_is_empty(&this->output_ports)) {
+ struct pw_port *port = spa_list_first(&this->output_ports, struct pw_port, link);
+
+ for (this->info.n_output_formats = 0;; this->info.n_output_formats++) {
+ struct spa_format *fmt;
+
+ if (pw_port_enum_formats(port, &fmt, NULL, this->info.n_output_formats) < 0)
+ break;
+
+ this->info.output_formats =
+ realloc(this->info.output_formats,
+ sizeof(struct spa_format *) * (this->info.n_output_formats + 1));
+ this->info.output_formats[this->info.n_output_formats] = spa_format_copy(fmt);
+ }
+ }
+ this->info.props = this->properties ? &this->properties->dict : NULL;
+}
+
+static void
+clear_info(struct pw_node *this)
+{
+ int i;
+
+ free((char*)this->info.name);
+ if (this->info.input_formats) {
+ for (i = 0; i < this->info.n_input_formats; i++)
+ free(this->info.input_formats[i]);
+ free(this->info.input_formats);
+ }
+
+ if (this->info.output_formats) {
+ for (i = 0; i < this->info.n_output_formats; i++)
+ free(this->info.output_formats[i]);
+ free(this->info.output_formats);
+ }
+ free((char*)this->info.error);
+
+}
+
+static int
+node_bind_func(struct pw_global *global, struct pw_client *client, uint32_t version, uint32_t id)
+{
+ struct pw_node *this = global->object;
+ struct pw_resource *resource;
+
+ resource = pw_resource_new(client, id, global->type, 0);
+ if (resource == NULL)
+ goto no_mem;
+
+ pw_resource_set_implementation(resource, global->object, PW_VERSION_NODE, NULL, node_unbind_func);
+
+ pw_log_debug("node %p: bound to %d", this, resource->id);
+
+ spa_list_insert(this->resource_list.prev, &resource->link);
+
+ this->info.change_mask = ~0;
+ pw_node_notify_info(resource, &this->info);
+ this->info.change_mask = 0;
+
+ return SPA_RESULT_OK;
+
+ no_mem:
+ pw_log_error("can't create node resource");
+ pw_core_notify_error(client->core_resource,
+ client->core_resource->id, SPA_RESULT_NO_MEMORY, "no memory");
+ return SPA_RESULT_NO_MEMORY;
+}
+
+static int
+do_node_add(struct spa_loop *loop,
+ bool async, uint32_t seq, size_t size, void *data, void *user_data)
+{
+ struct pw_node *this = user_data;
+
+ spa_graph_node_add(this->rt.sched->graph, &this->rt.node);
+
+ return SPA_RESULT_OK;
+}
+
+void pw_node_export(struct pw_node *this)
+{
+ struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this);
+
+ pw_log_debug("node %p: export", this);
+
+ spa_list_insert(this->core->node_list.prev, &this->link);
+
+ pw_core_add_global(this->core,
+ this->owner,
+ this->core->type.node, 0, this, node_bind_func, &this->global);
+
+ pw_loop_invoke(this->data_loop, do_node_add, 1, 0, NULL, false, this);
+
+ update_info(this);
+
+ impl->exported = true;
+
+ pw_signal_emit(&this->initialized, this);
+
+ pw_node_update_state(this, PW_NODE_STATE_SUSPENDED, NULL);
+}
+
+static int
+graph_impl_process_input(struct spa_graph_node *node, void *user_data)
+{
+ struct pw_node *this = user_data;
+ return this->implementation->process_input(this);
+}
+
+static int
+graph_impl_process_output(struct spa_graph_node *node, void *user_data)
+{
+ struct pw_node *this = user_data;
+ return this->implementation->process_output(this);
+}
+
+static const struct spa_graph_node_methods graph_methods = {
+ SPA_VERSION_GRAPH_NODE_METHODS,
+ graph_impl_process_input,
+ graph_impl_process_output,
+};
+
+struct pw_node *pw_node_new(struct pw_core *core,
+ struct pw_resource *owner,
+ const char *name,
+ struct pw_properties *properties,
+ size_t user_data_size)
+{
+ struct impl *impl;
+ struct pw_node *this;
+
+ impl = calloc(1, sizeof(struct impl) + user_data_size);
+ if (impl == NULL)
+ return NULL;
+
+ this = &impl->this;
+ this->core = core;
+ this->owner = owner;
+ pw_log_debug("node %p: new, owner %p", this, owner);
+
+ if (user_data_size > 0)
+ this->user_data = SPA_MEMBER(impl, sizeof(struct impl), void);
+
+ impl->work = pw_work_queue_new(this->core->main_loop);
+
+ this->info.name = strdup(name);
+ this->properties = properties;
+
+ this->data_loop = core->data_loop;
+
+ this->rt.sched = &core->rt.sched;
+
+ spa_list_init(&this->resource_list);
+
+ pw_signal_init(&this->state_request);
+ pw_signal_init(&this->state_changed);
+ pw_signal_init(&this->initialized);
+ pw_signal_init(&this->port_added);
+ pw_signal_init(&this->port_removed);
+ pw_signal_init(&this->destroy_signal);
+ pw_signal_init(&this->free_signal);
+ pw_signal_init(&this->async_complete);
+ pw_signal_init(&this->event);
+
+ pw_signal_add(&this->async_complete, &impl->on_async_complete, on_async_complete);
+ pw_signal_add(&this->event, &impl->on_event, on_event);
+
+ this->info.state = PW_NODE_STATE_CREATING;
+
+ 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_graph_node_init(&this->rt.node);
+ spa_graph_node_set_methods(&this->rt.node,
+ &graph_methods,
+ this);
+
+ return this;
+}
+
+static int
+do_node_remove(struct spa_loop *loop,
+ bool async, uint32_t seq, size_t size, void *data, void *user_data)
+{
+ struct pw_node *this = user_data;
+
+ pause_node(this);
+
+ spa_graph_node_remove(&this->rt.node);
+
+ return SPA_RESULT_OK;
+}
+
+/** 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.
+ *
+ * \memberof pw_node
+ */
+void pw_node_destroy(struct pw_node *node)
+{
+ struct impl *impl = SPA_CONTAINER_OF(node, struct impl, this);
+ struct pw_resource *resource, *tmp;
+ struct pw_port *port, *tmpp;
+
+ pw_log_debug("node %p: destroy", impl);
+ pw_signal_emit(&node->destroy_signal, node);
+
+ pw_loop_invoke(node->data_loop, do_node_remove, 1, 0, NULL, true, node);
+
+ if (impl->exported) {
+ spa_list_remove(&node->link);
+ pw_global_destroy(node->global);
+ node->global = NULL;
+ }
+
+ spa_list_for_each_safe(resource, tmp, &node->resource_list, link)
+ pw_resource_destroy(resource);
+
+ pw_log_debug("node %p: destroy ports", node);
+ spa_list_for_each_safe(port, tmpp, &node->input_ports, link) {
+ pw_signal_emit(&node->port_removed, node, port);
+ pw_port_destroy(port);
+ }
+
+ spa_list_for_each_safe(port, tmpp, &node->output_ports, link) {
+ pw_signal_emit(&node->port_removed, node, port);
+ pw_port_destroy(port);
+ }
+
+ pw_log_debug("node %p: free", node);
+ pw_signal_emit(&node->free_signal, node);
+
+ if (node->destroy)
+ node->destroy(node);
+
+ pw_work_queue_destroy(impl->work);
+
+ pw_map_clear(&node->input_port_map);
+ pw_map_clear(&node->output_port_map);
+
+ if (node->properties)
+ pw_properties_free(node->properties);
+
+ clear_info(node);
+
+ free(impl);
+}
+
+/**
+ * pw_node_get_free_port:
+ * \param node a \ref pw_node
+ * \param direction a \ref pw_direction
+ * \return the new port or NULL on error
+ *
+ * Find a new unused port in \a node with \a direction
+ *
+ * \memberof pw_node
+ */
+struct pw_port *pw_node_get_free_port(struct pw_node *node, enum pw_direction direction)
+{
+ uint32_t n_ports, max_ports;
+ struct spa_list *ports;
+ struct pw_port *port = NULL, *p, *mixport = NULL;
+ struct pw_map *portmap;
+
+ if (direction == PW_DIRECTION_INPUT) {
+ max_ports = node->info.max_input_ports;
+ n_ports = node->info.n_input_ports;
+ ports = &node->input_ports;
+ portmap = &node->input_port_map;
+ } else {
+ max_ports = node->info.max_output_ports;
+ n_ports = node->info.n_output_ports;
+ ports = &node->output_ports;
+ portmap = &node->output_port_map;
+ }
+
+ pw_log_debug("node %p: direction %d max %u, n %u", node, direction, max_ports, n_ports);
+
+ /* first try to find an unlinked port */
+ spa_list_for_each(p, ports, link) {
+ if (spa_list_is_empty(&p->links))
+ return p;
+ /* for output we can reuse an existing port, for input only
+ * when there is a multiplex */
+ if (direction == PW_DIRECTION_OUTPUT || p->mix != NULL)
+ mixport = p;
+ }
+
+ /* no port, can we create one ? */
+ if (n_ports < max_ports) {
+ uint32_t port_id = pw_map_insert_new(portmap, NULL);
+
+ pw_log_debug("node %p: creating port direction %d %u", node, direction, port_id);
+
+ port = node->implementation->add_port(node, direction, port_id);
+ if (port == NULL)
+ goto no_mem;
+ } else {
+ port = mixport;
+ }
+ return port;
+
+ no_mem:
+ pw_log_error("node %p: can't create new port", node);
+ return NULL;
+}
+
+static void on_state_complete(struct pw_node *node, void *data, int res)
+{
+ enum pw_node_state state = SPA_PTR_TO_INT(data);
+ char *error = NULL;
+
+ pw_log_debug("node %p: state complete %d", node, res);
+ if (SPA_RESULT_IS_ERROR(res)) {
+ asprintf(&error, "error changing node state: %d", res);
+ state = PW_NODE_STATE_ERROR;
+ }
+ pw_node_update_state(node, state, error);
+}
+
+static void node_deactivate(struct pw_node *this)
+{
+ struct pw_port *port;
+
+ pw_log_debug("node %p: deactivate", this);
+ spa_list_for_each(port, &this->input_ports, link) {
+ struct pw_link *link;
+ spa_list_for_each(link, &port->links, input_link)
+ pw_link_deactivate(link);
+ }
+ spa_list_for_each(port, &this->output_ports, link) {
+ struct pw_link *link;
+ spa_list_for_each(link, &port->links, output_link)
+ pw_link_deactivate(link);
+ }
+}
+
+static void node_activate(struct pw_node *this)
+{
+ struct pw_port *port;
+
+ pw_log_debug("node %p: activate", this);
+ spa_list_for_each(port, &this->input_ports, link) {
+ struct pw_link *link;
+ spa_list_for_each(link, &port->links, input_link)
+ pw_link_activate(link);
+ }
+ spa_list_for_each(port, &this->output_ports, link) {
+ struct pw_link *link;
+ spa_list_for_each(link, &port->links, output_link)
+ pw_link_activate(link);
+ }
+}
+
+/** Set th node state
+ * \param node a \ref pw_node
+ * \param state a \ref pw_node_state
+ * \return 0 on success < 0 on error
+ *
+ * Set the state of \a node to \a state.
+ *
+ * \memberof pw_node
+ */
+int pw_node_set_state(struct pw_node *node, enum pw_node_state state)
+{
+ int res = SPA_RESULT_OK;
+ struct impl *impl = SPA_CONTAINER_OF(node, struct impl, this);
+
+ pw_signal_emit(&node->state_request, node, state);
+
+ pw_log_debug("node %p: set state %s", node, pw_node_state_as_string(state));
+
+ switch (state) {
+ case PW_NODE_STATE_CREATING:
+ return SPA_RESULT_ERROR;
+
+ case PW_NODE_STATE_SUSPENDED:
+ res = suspend_node(node);
+ break;
+
+ case PW_NODE_STATE_IDLE:
+ res = pause_node(node);
+ break;
+
+ case PW_NODE_STATE_RUNNING:
+ node_activate(node);
+ send_clock_update(node);
+ res = start_node(node);
+ break;
+
+ case PW_NODE_STATE_ERROR:
+ break;
+ }
+ if (SPA_RESULT_IS_ERROR(res))
+ return res;
+
+ pw_work_queue_add(impl->work,
+ node, res, (pw_work_func_t) on_state_complete, SPA_INT_TO_PTR(state));
+
+ return res;
+}
+
+/** Update the node state
+ * \param node a \ref pw_node
+ * \param state a \ref pw_node_state
+ * \param error error when \a state is \ref PW_NODE_STATE_ERROR
+ *
+ * Update the state of a node. This method is used from inside \a node
+ * itself.
+ *
+ * \memberof pw_node
+ */
+void pw_node_update_state(struct pw_node *node, enum pw_node_state state, char *error)
+{
+ enum pw_node_state old;
+
+ old = node->info.state;
+ if (old != state) {
+ struct pw_resource *resource;
+
+ pw_log_debug("node %p: update state from %s -> %s", node,
+ pw_node_state_as_string(old), pw_node_state_as_string(state));
+
+ if (node->info.error)
+ free((char*)node->info.error);
+ node->info.error = error;
+ node->info.state = state;
+
+ if (state == PW_NODE_STATE_IDLE)
+ node_deactivate(node);
+
+ pw_signal_emit(&node->state_changed, node, old, state);
+
+ node->info.change_mask |= 1 << 5;
+ spa_list_for_each(resource, &node->resource_list, link)
+ pw_node_notify_info(resource, &node->info);
+ node->info.change_mask = 0;
+ }
+}
diff --git a/src/pipewire/node.h b/src/pipewire/node.h
new file mode 100644
index 00000000..69eff6f8
--- /dev/null
+++ b/src/pipewire/node.h
@@ -0,0 +1,179 @@
+/* PipeWire
+ * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __PIPEWIRE_NODE_H__
+#define __PIPEWIRE_NODE_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define PIPEWIRE_TYPE__Node "PipeWire:Object:Node"
+#define PIPEWIRE_TYPE_NODE_BASE PIPEWIRE_TYPE__Node ":"
+
+#include <spa/clock.h>
+#include <spa/node.h>
+
+#include <pipewire/mem.h>
+#include <pipewire/introspect.h>
+#include <pipewire/transport.h>
+
+#include <pipewire/core.h>
+#include <pipewire/port.h>
+#include <pipewire/link.h>
+#include <pipewire/client.h>
+#include <pipewire/data-loop.h>
+
+struct pw_node;
+
+
+#define PW_VERSION_NODE_IMPLEMENTATION 0
+
+struct pw_node_implementation {
+ uint32_t version;
+
+ int (*get_props) (struct pw_node *node, struct spa_props **props);
+
+ int (*set_props) (struct pw_node *node, const struct spa_props *props);
+
+ int (*send_command) (struct pw_node *node,
+ struct spa_command *command);
+
+ struct pw_port* (*add_port) (struct pw_node *node,
+ enum pw_direction direction,
+ uint32_t port_id);
+
+ int (*process_input) (struct pw_node *node);
+
+ int (*process_output) (struct pw_node *node);
+};
+
+/** \page page_node Node
+ *
+ * \section page_node_overview Overview
+ *
+ * The node object processes data. The node has a list of
+ * input and output ports (\ref page_port) on which it
+ * will receive and send out buffers respectively.
+ *
+ * The node wraps an SPA node object.
+ */
+/** \class pw_node
+ *
+ * PipeWire node class.
+ */
+struct pw_node {
+ struct pw_core *core; /**< core object */
+ struct spa_list link; /**< link in core node_list */
+ struct pw_global *global; /**< global for this node */
+
+ struct pw_resource *owner; /**< owner resource if any */
+ struct pw_properties *properties; /**< properties of the node */
+
+ struct pw_node_info info; /**< introspectable node info */
+
+ /** Emited when a state change is started */
+ PW_SIGNAL(state_request, (struct pw_listener *listener,
+ struct pw_node *object, enum pw_node_state state));
+ /** Emited when a stat change is completed */
+ PW_SIGNAL(state_changed, (struct pw_listener *listener,
+ struct pw_node *object,
+ enum pw_node_state old, enum pw_node_state state));
+
+ bool live; /**< if the node is live */
+ struct spa_clock *clock; /**< handle to SPA clock if any */
+
+ struct spa_list resource_list; /**< list of resources for this node */
+
+ /** Implementation of core node functions */
+ const struct pw_node_implementation *implementation;
+
+ /** Emited when the node is initialized */
+ PW_SIGNAL(initialized, (struct pw_listener *listener, struct pw_node *object));
+
+ struct spa_list input_ports; /**< list of input ports */
+ struct pw_map input_port_map; /**< map from port_id to port */
+ uint32_t n_used_input_links; /**< number of active input links */
+ uint32_t idle_used_input_links; /**< number of active input to be idle */
+
+ struct spa_list output_ports; /**< list of output ports */
+ struct pw_map output_port_map; /**< map from port_id to port */
+ uint32_t n_used_output_links; /**< number of active output links */
+ uint32_t idle_used_output_links; /**< number of active output to be idle */
+
+ /** Emited when a new port is added */
+ PW_SIGNAL(port_added, (struct pw_listener *listener,
+ struct pw_node *node, struct pw_port *port));
+ /** Emited when a port is removed */
+ PW_SIGNAL(port_removed, (struct pw_listener *listener,
+ struct pw_node *node, struct pw_port *port));
+
+ /** Emited when the node is destroyed */
+ PW_SIGNAL(destroy_signal, (struct pw_listener *listener, struct pw_node *object));
+ /** Emited when the node is free */
+ PW_SIGNAL(free_signal, (struct pw_listener *listener, struct pw_node *object));
+
+ /** an async operation on the node completed */
+ PW_SIGNAL(async_complete, (struct pw_listener *listener,
+ struct pw_node *node, uint32_t seq, int res));
+
+ /** an event is emited */
+ PW_SIGNAL(event, (struct pw_listener *listener,
+ struct pw_node *node, struct spa_event *event));
+
+ struct pw_loop *data_loop; /**< the data loop for this node */
+
+ struct {
+ struct spa_graph_scheduler *sched;
+ struct spa_graph_node node;
+ } rt;
+
+ void *user_data; /**< extra user data */
+ pw_destroy_t destroy; /**< function to clean up the object */
+};
+
+/** Create a new node \memberof pw_node */
+struct pw_node *
+pw_node_new(struct pw_core *core, /**< the core */
+ struct pw_resource *owner, /**< optional owner */
+ const char *name, /**< node name */
+ struct pw_properties *properties, /**< extra properties */
+ size_t user_data_size /**< user data size */);
+
+/** Complete initialization of the node */
+void pw_node_export(struct pw_node *node);
+
+/** Destroy a node */
+void pw_node_destroy(struct pw_node *node);
+
+/** Get a free unused port from the node */
+struct pw_port *
+pw_node_get_free_port(struct pw_node *node, enum pw_direction direction);
+
+/** Change the state of the node */
+int pw_node_set_state(struct pw_node *node, enum pw_node_state state);
+
+/** Update the state of the node, mostly used by node implementations */
+void pw_node_update_state(struct pw_node *node, enum pw_node_state state, char *error);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __PIPEWIRE_NODE_H__ */
diff --git a/src/pipewire/pipewire.c b/src/pipewire/pipewire.c
new file mode 100644
index 00000000..5e5ae5f9
--- /dev/null
+++ b/src/pipewire/pipewire.c
@@ -0,0 +1,346 @@
+/* PipeWire
+ * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <unistd.h>
+#include <limits.h>
+#include <stdio.h>
+#include <sys/prctl.h>
+#include <pwd.h>
+#include <errno.h>
+#include <dlfcn.h>
+
+#include "pipewire/pipewire.h"
+
+static char **categories = NULL;
+
+static struct support_info {
+ void *hnd;
+ spa_handle_factory_enum_func_t enum_func;
+ struct spa_support support[4];
+ uint32_t n_support;
+} support_info;
+
+static bool
+open_support(const char *path,
+ const char *lib,
+ struct support_info *info)
+{
+ char *filename;
+
+ if (asprintf(&filename, "%s/%s.so", path, lib) < 0)
+ goto no_filename;
+
+ if ((info->hnd = dlopen(filename, RTLD_NOW)) == NULL) {
+ fprintf(stderr, "can't load %s: %s\n", filename, dlerror());
+ goto open_failed;
+ }
+ if ((info->enum_func = dlsym(info->hnd, SPA_HANDLE_FACTORY_ENUM_FUNC_NAME)) == NULL) {
+ fprintf(stderr, "can't find enum function\n");
+ goto no_symbol;
+ }
+ free(filename);
+ return true;
+
+ no_filename:
+ return false;
+ no_symbol:
+ dlclose(info->hnd);
+ open_failed:
+ free(filename);
+ return false;
+}
+
+static void *
+load_interface(struct support_info *info,
+ const char *factory_name,
+ const char *type)
+{
+ int res;
+ struct spa_handle *handle;
+ uint32_t type_id;
+ const struct spa_handle_factory *factory;
+ void *iface;
+ struct spa_type_map *map = NULL;
+
+ factory = pw_get_support_factory(factory_name);
+ if (factory == NULL)
+ goto not_found;
+
+ handle = calloc(1, factory->size);
+ if ((res = spa_handle_factory_init(factory,
+ handle, NULL, info->support, info->n_support)) < 0) {
+ fprintf(stderr, "can't make factory instance: %d\n", res);
+ goto init_failed;
+ }
+
+ map = pw_get_support_interface(SPA_TYPE__TypeMap);
+ type_id = map ? spa_type_map_get_id(map, type) : 0;
+
+ if ((res = spa_handle_get_interface(handle, type_id, &iface)) < 0) {
+ fprintf(stderr, "can't get %s interface %d\n", type, res);
+ goto interface_failed;
+ }
+ return iface;
+
+ interface_failed:
+ spa_handle_clear(handle);
+ init_failed:
+ free(handle);
+ not_found:
+ return NULL;
+}
+
+static void configure_debug(const char *str)
+{
+ char **level;
+ int n_tokens;
+
+ level = pw_split_strv(str, ":", INT_MAX, &n_tokens);
+ if (n_tokens > 0)
+ pw_log_set_level(atoi(level[0]));
+
+ if (n_tokens > 1)
+ categories = pw_split_strv(level[1], ",", INT_MAX, &n_tokens);
+}
+
+static void configure_support(struct support_info *info)
+{
+ void *iface;
+
+ iface = load_interface(info, "mapper", SPA_TYPE__TypeMap);
+ if (iface != NULL) {
+ info->support[info->n_support++] = SPA_SUPPORT_INIT(SPA_TYPE__TypeMap, iface);
+ }
+
+ iface = load_interface(info, "logger", SPA_TYPE__Log);
+ if (iface != NULL) {
+ info->support[info->n_support++] = SPA_SUPPORT_INIT(SPA_TYPE__Log, iface);
+ pw_log_set(iface);
+ }
+}
+
+/** Get a support interface
+ * \param type the interface type
+ * \return the interface or NULL when not configured
+ */
+void *pw_get_support_interface(const char *type)
+{
+ int i;
+
+ for (i = 0; i < support_info.n_support; i++) {
+ if (strcmp(support_info.support->type, type) == 0)
+ return support_info.support->data;
+ }
+ return NULL;
+}
+
+const struct spa_handle_factory *pw_get_support_factory(const char *factory_name)
+{
+ int res;
+ uint32_t index;
+ const struct spa_handle_factory *factory;
+
+ for (index = 0;; index++) {
+ if ((res = support_info.enum_func(&factory, index)) < 0) {
+ if (res != SPA_RESULT_ENUM_END)
+ fprintf(stderr, "can't enumerate factories: %d\n", res);
+ break;
+ }
+ if (strcmp(factory->name, factory_name) == 0)
+ return factory;
+ }
+ return NULL;
+}
+
+const struct spa_support *pw_get_support(uint32_t *n_support)
+{
+ *n_support = support_info.n_support;
+ return support_info.support;
+}
+
+/** 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.
+ *
+ * The environment variable \a PIPEWIRE_DEBUG
+ *
+ * \memberof pw_pipewire
+ */
+void pw_init(int *argc, char **argv[])
+{
+ const char *str;
+
+ if ((str = getenv("PIPEWIRE_DEBUG")))
+ configure_debug(str);
+
+ if ((str = getenv("SPA_PLUGIN_DIR")) == NULL)
+ str = PLUGINDIR;
+
+ if (open_support(str, "support/libspa-support", &support_info))
+ configure_support(&support_info);
+}
+
+/** 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
+ *
+ * \memberof pw_pipewire
+ */
+bool pw_debug_is_category_enabled(const char *name)
+{
+ int i;
+
+ if (categories == NULL)
+ return false;
+
+ for (i = 0; categories[i]; i++) {
+ if (strcmp (categories[i], name) == 0)
+ return true;
+ }
+ return false;
+}
+
+/** Get the application name \memberof pw_pipewire */
+const char *pw_get_application_name(void)
+{
+ return NULL;
+}
+
+/** Get the program name \memberof pw_pipewire */
+const char *pw_get_prgname(void)
+{
+ static char tcomm[16 + 1];
+ spa_zero(tcomm);
+
+ if (prctl(PR_GET_NAME, (unsigned long) tcomm, 0, 0, 0) == 0)
+ return tcomm;
+
+ return NULL;
+}
+
+/** Get the user name \memberof pw_pipewire */
+const char *pw_get_user_name(void)
+{
+ struct passwd *pw;
+
+ if ((pw = getpwuid(getuid())))
+ return pw->pw_name;
+
+ return NULL;
+}
+
+/** Get the host name \memberof pw_pipewire */
+const char *pw_get_host_name(void)
+{
+ static char hname[256];
+
+ if (gethostname(hname, 256) < 0)
+ return NULL;
+
+ hname[255] = 0;
+ return hname;
+}
+
+/** Get the client name
+ *
+ * Make a new PipeWire client name that can be used to construct a remote.
+ *
+ * \memberof pw_pipewire
+ */
+char *pw_get_client_name(void)
+{
+ char *c;
+ const char *cc;
+
+ if ((cc = pw_get_application_name()))
+ return strdup(cc);
+ else if ((cc = pw_get_prgname()))
+ return strdup(cc);
+ else {
+ if (asprintf(&c, "pipewire-pid-%zd", (size_t) getpid()) < 0)
+ return NULL;
+ return c;
+ }
+}
+
+/** Fill remote properties
+ * \param properties a \ref pw_properties
+ *
+ * Fill \a properties with a set of default remote properties.
+ *
+ * \memberof pw_pipewire
+ */
+void pw_fill_remote_properties(struct pw_properties *properties)
+{
+ if (!pw_properties_get(properties, "application.name"))
+ pw_properties_set(properties, "application.name", pw_get_application_name());
+
+ if (!pw_properties_get(properties, "application.prgname"))
+ pw_properties_set(properties, "application.prgname", pw_get_prgname());
+
+ if (!pw_properties_get(properties, "application.language")) {
+ pw_properties_set(properties, "application.language", getenv("LANG"));
+ }
+ if (!pw_properties_get(properties, "application.process.id")) {
+ pw_properties_setf(properties, "application.process.id", "%zd", (size_t) getpid());
+ }
+ if (!pw_properties_get(properties, "application.process.user"))
+ pw_properties_set(properties, "application.process.user", pw_get_user_name());
+
+ if (!pw_properties_get(properties, "application.process.host"))
+ pw_properties_set(properties, "application.process.host", pw_get_host_name());
+
+ if (!pw_properties_get(properties, "application.process.session_id")) {
+ pw_properties_set(properties, "application.process.session_id",
+ getenv("XDG_SESSION_ID"));
+ }
+}
+
+/** Fill stream properties
+ * \param properties a \ref pw_properties
+ *
+ * Fill \a properties with a set of default stream properties.
+ *
+ * \memberof pw_pipewire
+ */
+void pw_fill_stream_properties(struct pw_properties *properties)
+{
+}
+
+/** Reverse the direction \memberof pw_pipewire */
+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;
+}
diff --git a/src/pipewire/pipewire.h b/src/pipewire/pipewire.h
new file mode 100644
index 00000000..5668d9c5
--- /dev/null
+++ b/src/pipewire/pipewire.h
@@ -0,0 +1,132 @@
+/* PipeWire
+ * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __PIPEWIRE_H__
+#define __PIPEWIRE_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <pipewire/introspect.h>
+#include <pipewire/log.h>
+#include <pipewire/loop.h>
+#include <pipewire/mem.h>
+#include <pipewire/thread-loop.h>
+#include <pipewire/properties.h>
+#include <pipewire/stream.h>
+#include <pipewire/utils.h>
+
+#include <spa/type-map.h>
+
+/** \mainpage
+ *
+ * \section sec_intro Introduction
+ *
+ * This document describes the API for the PipeWire multimedia server.
+ * The API consists of two parts:
+ *
+ * \li The client side API (See \subpage page_client_api)
+ * \li The server side API and tools to build new modules (See
+ * \subpage page_server_api)
+ *
+ * \section sec_errors Error reporting
+ *
+ * Functions return either NULL or a negative int error code when an
+ * error occurs. Error codes are used from the SPA plugin library on
+ * which PipeWire is built.
+ *
+ * Some functions might return asynchronously. The error code for such
+ * functions is positive and SPA_RESULT_IS_ASYNC() will return true.
+ * SPA_RESULT_ASYNC_SEQ() can be used to get the unique sequence number
+ * associated with the async operation.
+ *
+ * The object returning the async result code will have some way to
+ * signal the completion of the async operation (with, for example, a
+ * callback). The sequence number can be used to see which operation
+ * completed.
+ *
+ * \section sec_logging Logging
+ *
+ * The 'PIPEWIRE_DEBUG' environment variable can be used to enable
+ * more debugging. The format is:
+ *
+ * &lt;level&gt;[:&lt;category&gt;,...]
+ *
+ * - &lt;level&gt;: specifies the log level:
+ * + `0`: no logging is enabled
+ * + `1`: Error logging is enabled
+ * + `2`: Warnings are enabled
+ * + `3`: Informational messages are enabled
+ * + `4`: Debug messages are enabled
+ * + `5`: Trace messages are enabled. These messages can be logged
+ * from the realtime threads.
+ *
+ * - &lt;category&gt;: Specifies a string category to enable. Many categories
+ * can be separated by commas. Current categories are:
+ * + `connection`: to log connection messages
+ */
+
+/** \class pw_pipewire
+ *
+ * \brief PipeWire initalization and infrasctructure functions
+ */
+void
+pw_init(int *argc, char **argv[]);
+
+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);
+
+char *
+pw_get_client_name(void);
+
+void
+pw_fill_remote_properties(struct pw_properties *properties);
+
+void
+pw_fill_stream_properties(struct pw_properties *properties);
+
+enum pw_direction
+pw_direction_reverse(enum pw_direction direction);
+
+void *
+pw_get_support_interface(const char *type);
+
+const struct spa_handle_factory *
+pw_get_support_factory(const char *factory_name);
+
+const struct spa_support *
+pw_get_support(uint32_t *n_support);
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* __PIPEWIRE_H__ */
diff --git a/src/pipewire/port.c b/src/pipewire/port.c
new file mode 100644
index 00000000..537747ad
--- /dev/null
+++ b/src/pipewire/port.c
@@ -0,0 +1,393 @@
+/* PipeWire
+ * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include "pipewire/pipewire.h"
+#include "pipewire/port.h"
+
+/** \cond */
+struct impl {
+ struct pw_port this;
+};
+/** \endcond */
+
+
+static void port_update_state(struct pw_port *port, enum pw_port_state state)
+{
+ if (port->state != state) {
+ pw_log_debug("port %p: state %d -> %d", port, port->state, state);
+ port->state = state;
+ pw_signal_emit(&port->state_changed, port);
+ }
+}
+
+static int schedule_tee_input(struct spa_graph_node *node, void *user_data)
+{
+ int res;
+ struct pw_port *this = user_data;
+ struct spa_graph_port *p;
+ struct spa_port_io *io = this->rt.mix_port.io;
+
+ if (spa_list_is_empty(&node->ports[SPA_DIRECTION_OUTPUT])) {
+ io->status = SPA_RESULT_NEED_BUFFER;
+ res = SPA_RESULT_NEED_BUFFER;
+ }
+ else {
+ spa_list_for_each(p, &node->ports[SPA_DIRECTION_OUTPUT], link)
+ *p->io = *io;
+ io->status = SPA_RESULT_OK;
+ io->buffer_id = SPA_ID_INVALID;
+ res = SPA_RESULT_HAVE_BUFFER;
+ }
+ return res;
+}
+static int schedule_tee_output(struct spa_graph_node *node, void *user_data)
+{
+ struct pw_port *this = user_data;
+ struct spa_graph_port *p;
+ struct spa_port_io *io = this->rt.mix_port.io;
+
+ spa_list_for_each(p, &node->ports[SPA_DIRECTION_OUTPUT], link)
+ *io = *p->io;
+ io->status = SPA_RESULT_NEED_BUFFER;
+
+ return SPA_RESULT_NEED_BUFFER;
+}
+
+static const struct spa_graph_node_methods schedule_tee_node = {
+ SPA_VERSION_GRAPH_NODE_METHODS,
+ schedule_tee_input,
+ schedule_tee_output,
+};
+
+static int schedule_tee_reuse_buffer(struct spa_graph_port *port, uint32_t buffer_id, void *user_data)
+{
+ return SPA_RESULT_OK;
+}
+
+static const struct spa_graph_port_methods schedule_tee_port = {
+ SPA_VERSION_GRAPH_PORT_METHODS,
+ schedule_tee_reuse_buffer,
+};
+
+static int schedule_mix_input(struct spa_graph_node *node, void *user_data)
+{
+ struct pw_port *this = user_data;
+ struct spa_graph_port *p;
+ struct spa_port_io *io = this->rt.mix_port.io;
+
+ spa_list_for_each(p, &node->ports[SPA_DIRECTION_INPUT], link) {
+ *io = *p->io;
+ p->io->status = SPA_RESULT_OK;
+ p->io->buffer_id = SPA_ID_INVALID;
+ }
+ return SPA_RESULT_HAVE_BUFFER;
+}
+
+static int schedule_mix_output(struct spa_graph_node *node, void *user_data)
+{
+ struct pw_port *this = user_data;
+ struct spa_graph_port *p;
+ struct spa_port_io *io = this->rt.mix_port.io;
+
+ io->status = SPA_RESULT_NEED_BUFFER;
+ spa_list_for_each(p, &node->ports[SPA_DIRECTION_INPUT], link)
+ *p->io = *io;
+ io->buffer_id = SPA_ID_INVALID;
+
+ return SPA_RESULT_NEED_BUFFER;
+}
+
+static const struct spa_graph_node_methods schedule_mix_node = {
+ SPA_VERSION_GRAPH_NODE_METHODS,
+ schedule_mix_input,
+ schedule_mix_output,
+};
+
+static int schedule_mix_reuse_buffer(struct spa_graph_port *port, uint32_t buffer_id, void *user_data)
+{
+ return SPA_RESULT_OK;
+}
+static const struct spa_graph_port_methods schedule_mix_port = {
+ SPA_VERSION_GRAPH_PORT_METHODS,
+ schedule_mix_reuse_buffer,
+};
+
+struct pw_port *pw_port_new(enum pw_direction direction,
+ uint32_t port_id,
+ size_t user_data_size)
+{
+ struct impl *impl;
+ struct pw_port *this;
+
+ impl = calloc(1, sizeof(struct impl) + user_data_size);
+ if (impl == NULL)
+ return NULL;
+
+ this = &impl->this;
+ pw_log_debug("port %p: new", this);
+
+ this->direction = direction;
+ this->port_id = port_id;
+ this->state = PW_PORT_STATE_INIT;
+ this->io.status = SPA_RESULT_OK;
+ this->io.buffer_id = SPA_ID_INVALID;
+
+ if (user_data_size > 0)
+ this->user_data = SPA_MEMBER(impl, sizeof(struct impl), void);
+
+ spa_list_init(&this->links);
+
+ pw_signal_init(&this->state_changed);
+ pw_signal_init(&this->destroy_signal);
+
+ spa_graph_port_init(&this->rt.port,
+ this->direction,
+ this->port_id,
+ 0,
+ &this->io);
+ spa_graph_node_init(&this->rt.mix_node);
+ spa_graph_node_set_methods(&this->rt.mix_node,
+ this->direction == PW_DIRECTION_INPUT ?
+ &schedule_mix_node :
+ &schedule_tee_node,
+ this);
+ spa_graph_port_init(&this->rt.mix_port,
+ pw_direction_reverse(this->direction),
+ 0,
+ 0,
+ &this->io);
+ spa_graph_port_set_methods(&this->rt.mix_port,
+ this->direction == PW_DIRECTION_INPUT ?
+ &schedule_mix_port :
+ &schedule_tee_port,
+ this);
+ return this;
+}
+
+static int do_add_port(struct spa_loop *loop,
+ bool async, uint32_t seq, size_t size, void *data, void *user_data)
+{
+ struct pw_port *this = user_data;
+
+ spa_graph_port_add(&this->node->rt.node, &this->rt.port);
+ spa_graph_node_add(this->rt.graph, &this->rt.mix_node);
+ spa_graph_port_add(&this->rt.mix_node, &this->rt.mix_port);
+ spa_graph_port_link(&this->rt.port, &this->rt.mix_port);
+
+ return SPA_RESULT_OK;
+}
+
+void pw_port_add(struct pw_port *port, struct pw_node *node)
+{
+ port->node = node;
+
+ pw_log_debug("port %p: add to node %p", port, node);
+ if (port->direction == PW_DIRECTION_INPUT) {
+ spa_list_insert(&node->input_ports, &port->link);
+ pw_map_insert_at(&node->input_port_map, port->port_id, port);
+ node->info.n_input_ports++;
+ node->info.change_mask |= 1 << 1;
+ }
+ else {
+ spa_list_insert(&node->output_ports, &port->link);
+ pw_map_insert_at(&node->output_port_map, port->port_id, port);
+ node->info.n_output_ports++;
+ node->info.change_mask |= 1 << 3;
+ }
+
+ port->rt.graph = node->rt.sched->graph;
+ pw_loop_invoke(node->data_loop, do_add_port, SPA_ID_INVALID, 0, NULL, false, port);
+
+ port_update_state(port, PW_PORT_STATE_CONFIGURE);
+
+ pw_signal_emit(&node->port_added, node, port);
+}
+
+static int do_remove_port(struct spa_loop *loop,
+ bool async, uint32_t seq, size_t size, void *data, void *user_data)
+{
+ struct pw_port *this = user_data;
+ struct spa_graph_port *p;
+
+ spa_graph_port_unlink(&this->rt.port);
+ spa_graph_port_remove(&this->rt.port);
+
+ spa_list_for_each(p, &this->rt.mix_node.ports[this->direction], link)
+ spa_graph_port_remove(p);
+
+ spa_graph_port_remove(&this->rt.mix_port);
+ spa_graph_node_remove(&this->rt.mix_node);
+
+ return SPA_RESULT_OK;
+}
+
+void pw_port_destroy(struct pw_port *port)
+{
+ struct pw_node *node = port->node;
+
+ pw_log_debug("port %p: destroy", port);
+
+ pw_signal_emit(&port->destroy_signal, port);
+
+ if (node) {
+ pw_loop_invoke(port->node->data_loop, do_remove_port, SPA_ID_INVALID, 0, NULL, true, port);
+
+ if (port->direction == PW_DIRECTION_INPUT) {
+ pw_map_remove(&node->input_port_map, port->port_id);
+ node->info.n_input_ports--;
+ }
+ else {
+ pw_map_remove(&node->output_port_map, port->port_id);
+ node->info.n_output_ports--;
+ }
+ spa_list_remove(&port->link);
+ pw_signal_emit(&node->port_removed, node, port);
+ }
+
+ if (port->destroy)
+ port->destroy(port);
+
+ free(port);
+}
+
+static int
+do_port_pause(struct spa_loop *loop,
+ bool async, uint32_t seq, size_t size, void *data, void *user_data)
+{
+ struct pw_port *port = user_data;
+
+ return port->implementation->send_command(port,
+ &SPA_COMMAND_INIT(port->node->core->type.command_node.Pause));
+}
+
+int pw_port_enum_formats(struct pw_port *port,
+ struct spa_format **format,
+ const struct spa_format *filter,
+ int32_t index)
+{
+ return port->implementation->enum_formats(port, format, filter, index);
+}
+
+int pw_port_set_format(struct pw_port *port, uint32_t flags, struct spa_format *format)
+{
+ int res;
+
+ res = port->implementation->set_format(port, flags, format);
+ pw_log_debug("port %p: set format %d", port, res);
+
+ if (!SPA_RESULT_IS_ASYNC(res)) {
+ if (format == NULL) {
+ port->buffers = NULL;
+ port->n_buffers = 0;
+ if (port->allocated)
+ pw_memblock_free(&port->buffer_mem);
+ port->allocated = false;
+ port_update_state (port, PW_PORT_STATE_CONFIGURE);
+ }
+ else {
+ port_update_state (port, PW_PORT_STATE_READY);
+ }
+ }
+ return res;
+}
+
+int pw_port_get_format(struct pw_port *port, const struct spa_format **format)
+{
+ return port->implementation->get_format(port, format);
+}
+
+int pw_port_get_info(struct pw_port *port, const struct spa_port_info **info)
+{
+ return port->implementation->get_info(port, info);
+}
+
+int pw_port_enum_params(struct pw_port *port, uint32_t index, struct spa_param **param)
+{
+ return port->implementation->enum_params(port, index, param);
+}
+
+int pw_port_set_param(struct pw_port *port, struct spa_param *param)
+{
+ return port->implementation->set_param(port, param);
+}
+
+int pw_port_use_buffers(struct pw_port *port, struct spa_buffer **buffers, uint32_t n_buffers)
+{
+ int res;
+
+ if (n_buffers == 0 && port->state <= PW_PORT_STATE_READY)
+ return SPA_RESULT_OK;
+
+ if (n_buffers > 0 && port->state < PW_PORT_STATE_READY)
+ return SPA_RESULT_NO_FORMAT;
+
+ if (port->state > PW_PORT_STATE_PAUSED) {
+ pw_loop_invoke(port->node->data_loop,
+ do_port_pause, 0, 0, NULL, true, port);
+ port_update_state (port, PW_PORT_STATE_PAUSED);
+ }
+
+ pw_log_debug("port %p: use %d buffers", port, n_buffers);
+ res = port->implementation->use_buffers(port, buffers, n_buffers);
+
+ port->buffers = buffers;
+ port->n_buffers = n_buffers;
+ if (port->allocated)
+ pw_memblock_free(&port->buffer_mem);
+ port->allocated = false;
+
+ if (port->n_buffers == 0)
+ port_update_state (port, PW_PORT_STATE_READY);
+ else if (!SPA_RESULT_IS_ASYNC(res))
+ port_update_state (port, PW_PORT_STATE_PAUSED);
+
+ return res;
+}
+
+int pw_port_alloc_buffers(struct pw_port *port,
+ struct spa_param **params, uint32_t n_params,
+ struct spa_buffer **buffers, uint32_t *n_buffers)
+{
+ int res;
+
+ if (port->state < PW_PORT_STATE_READY)
+ return SPA_RESULT_NO_FORMAT;
+
+ if (port->state > PW_PORT_STATE_PAUSED) {
+ pw_loop_invoke(port->node->data_loop,
+ do_port_pause, 0, 0, NULL, true, port);
+ port_update_state (port, PW_PORT_STATE_PAUSED);
+ }
+
+ pw_log_debug("port %p: alloc %d buffers", port, *n_buffers);
+ res = port->implementation->alloc_buffers(port, params, n_params, buffers, n_buffers);
+
+ port->buffers = buffers;
+ port->n_buffers = *n_buffers;
+ port->allocated = true;
+
+ if (!SPA_RESULT_IS_ASYNC(res))
+ port_update_state (port, PW_PORT_STATE_PAUSED);
+
+ return res;
+}
diff --git a/src/pipewire/port.h b/src/pipewire/port.h
new file mode 100644
index 00000000..b56b27f4
--- /dev/null
+++ b/src/pipewire/port.h
@@ -0,0 +1,174 @@
+/* PipeWire
+ * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __PIPEWIRE_PORT_H__
+#define __PIPEWIRE_PORT_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define PIPEWIRE_TYPE__Port "PipeWire:Object:Port"
+#define PIPEWIRE_TYPE_PORT_BASE PIPEWIRE_TYPE__Port ":"
+
+#include <spa/node.h>
+
+#include <pipewire/introspect.h>
+#include <pipewire/mem.h>
+
+#include <pipewire/core.h>
+#include <pipewire/link.h>
+
+enum pw_port_state {
+ PW_PORT_STATE_ERROR = -1,
+ PW_PORT_STATE_INIT = 0,
+ PW_PORT_STATE_CONFIGURE = 1,
+ PW_PORT_STATE_READY = 2,
+ PW_PORT_STATE_PAUSED = 3,
+ PW_PORT_STATE_STREAMING = 4,
+};
+
+struct pw_port;
+
+#define PW_VERSION_PORT_IMPLEMENTATION 0
+
+struct pw_port_implementation {
+ uint32_t version;
+
+ int (*enum_formats) (struct pw_port *port,
+ struct spa_format **format,
+ const struct spa_format *filter,
+ int32_t index);
+
+ int (*set_format) (struct pw_port *port, uint32_t flags, struct spa_format *format);
+
+ int (*get_format) (struct pw_port *port, const struct spa_format **format);
+
+ int (*get_info) (struct pw_port *port, const struct spa_port_info **info);
+
+ int (*enum_params) (struct pw_port *port, uint32_t index, struct spa_param **param);
+
+ int (*set_param) (struct pw_port *port, struct spa_param *param);
+
+ int (*use_buffers) (struct pw_port *port, struct spa_buffer **buffers, uint32_t n_buffers);
+
+ int (*alloc_buffers) (struct pw_port *port,
+ struct spa_param **params, uint32_t n_params,
+ struct spa_buffer **buffers, uint32_t *n_buffers);
+ int (*reuse_buffer) (struct pw_port *port, uint32_t buffer_id);
+
+ int (*send_command) (struct pw_port *port, struct spa_command *command);
+};
+
+/** \page page_port Port
+ *
+ * \section page_node_overview Overview
+ *
+ * A port can be used to link two nodes.
+ */
+/** \class pw_port
+ *
+ * The port object
+ */
+struct pw_port {
+ struct spa_list link; /**< link in node port_list */
+
+ PW_SIGNAL(destroy_signal, (struct pw_listener *listener, struct pw_port *));
+
+ struct pw_node *node; /**< owner node */
+
+ enum pw_direction direction; /**< port direction */
+ uint32_t port_id; /**< port id */
+
+ enum pw_port_state state; /**< state of the port */
+ /** Emited when the port state changes */
+ PW_SIGNAL(state_changed, (struct pw_listener *listener, struct pw_port *port));
+
+ const struct pw_port_implementation *implementation;
+
+ struct spa_port_io io; /**< io area of the port */
+
+ bool allocated; /**< if buffers are allocated */
+ struct pw_memblock buffer_mem; /**< allocated buffer memory */
+ struct spa_buffer **buffers; /**< port buffers */
+ uint32_t n_buffers; /**< number of port buffers */
+
+ struct spa_list links; /**< list of \ref pw_link */
+
+ void *mix; /**< optional port buffer mix/split */
+
+ struct {
+ struct spa_graph *graph;
+ struct spa_graph_port port;
+ struct spa_graph_port mix_port;
+ struct spa_graph_node mix_node;
+ } rt; /**< data only accessed from the data thread */
+
+ void *user_data; /**< extra user data */
+ pw_destroy_t destroy; /**< function to clean up the object */
+};
+
+/** Create a new port \memberof pw_port
+ * \return a newly allocated port */
+struct pw_port *
+pw_port_new(enum pw_direction direction,
+ uint32_t port_id,
+ size_t user_data_size);
+
+/** Add a port to a node \memberof pw_port */
+void pw_port_add(struct pw_port *port, struct pw_node *node);
+
+/** Destroy a port \memberof pw_port */
+void pw_port_destroy(struct pw_port *port);
+
+/** Get the current format on a port \memberof pw_port */
+int pw_port_enum_formats(struct pw_port *port,
+ struct spa_format **format,
+ const struct spa_format *filter,
+ int32_t index);
+
+/** Set a format on a port \memberof pw_port */
+int pw_port_set_format(struct pw_port *port, uint32_t flags, struct spa_format *format);
+
+/** Get the current format on a port \memberof pw_port */
+int pw_port_get_format(struct pw_port *port, const struct spa_format **format);
+
+/** Get the info on a port \memberof pw_port */
+int pw_port_get_info(struct pw_port *port, const struct spa_port_info **info);
+
+/** Enumerate the port parameters \memberof pw_port */
+int pw_port_enum_params(struct pw_port *port, uint32_t index, struct spa_param **param);
+
+/** Set a port parameter \memberof pw_port */
+int pw_port_set_param(struct pw_port *port, struct spa_param *param);
+
+/** Use buffers on a port \memberof pw_port */
+int pw_port_use_buffers(struct pw_port *port, struct spa_buffer **buffers, uint32_t n_buffers);
+
+/** Allocate memory for buffers on a port \memberof pw_port */
+int pw_port_alloc_buffers(struct pw_port *port,
+ struct spa_param **params, uint32_t n_params,
+ struct spa_buffer **buffers, uint32_t *n_buffers);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __PIPEWIRE_PORT_H__ */
diff --git a/src/pipewire/properties.c b/src/pipewire/properties.c
new file mode 100644
index 00000000..374d18e9
--- /dev/null
+++ b/src/pipewire/properties.c
@@ -0,0 +1,318 @@
+/* PipeWire
+ * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include "pipewire/pipewire.h"
+#include "pipewire/properties.h"
+
+/** \cond */
+struct properties {
+ struct pw_properties this;
+
+ struct pw_array items;
+};
+/** \endcond */
+
+static void 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));
+ item->key = key;
+ item->value = value;
+
+ this->dict.items = impl->items.data;
+ this->dict.n_items = pw_array_get_len(&impl->items, struct spa_dict_item);
+}
+
+static void clear_item(struct spa_dict_item *item)
+{
+ free((char *) item->key);
+ free((char *) item->value);
+}
+
+static int find_index(struct pw_properties *this, const char *key)
+{
+ struct properties *impl = SPA_CONTAINER_OF(this, struct properties, this);
+ int i, len = pw_array_get_len(&impl->items, struct spa_dict_item);
+
+ for (i = 0; i < len; i++) {
+ struct spa_dict_item *item =
+ pw_array_get_unchecked(&impl->items, i, struct spa_dict_item);
+ if (strcmp(item->key, key) == 0)
+ return i;
+ }
+ return -1;
+}
+
+/** Make a new properties object
+ *
+ * \param key a first key
+ * \param ... value and more keys NULL terminated
+ * \return a newly allocated properties object
+ *
+ * \memberof pw_properties
+ */
+struct pw_properties *pw_properties_new(const char *key, ...)
+{
+ struct properties *impl;
+ va_list varargs;
+ const char *value;
+
+ impl = calloc(1, sizeof(struct properties));
+ if (impl == NULL)
+ return NULL;
+
+ pw_array_init(&impl->items, 16);
+
+ va_start(varargs, key);
+ while (key != NULL) {
+ value = va_arg(varargs, char *);
+ 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
+ *
+ * \memberof pw_properties
+ */
+struct pw_properties *pw_properties_new_dict(const struct spa_dict *dict)
+{
+ uint32_t i;
+ struct properties *impl;
+
+ impl = calloc(1, sizeof(struct properties));
+ if (impl == NULL)
+ return NULL;
+
+ pw_array_init(&impl->items, 16);
+
+ for (i = 0; i < dict->n_items; i++)
+ add_func(&impl->this, strdup(dict->items[i].key), strdup(dict->items[i].value));
+
+ return &impl->this;
+}
+
+/** Copy a properties object
+ *
+ * \param properties properties to copy
+ * \return a new properties object
+ *
+ * \memberof pw_properties
+ */
+struct pw_properties *pw_properties_copy(struct pw_properties *properties)
+{
+ struct properties *impl = SPA_CONTAINER_OF(properties, struct properties, this);
+ struct pw_properties *copy;
+ struct spa_dict_item *item;
+
+ copy = pw_properties_new(NULL, NULL);
+ if (copy == NULL)
+ return NULL;
+
+ pw_array_for_each(item, &impl->items)
+ add_func(copy, strdup(item->key), strdup(item->value));
+
+ return copy;
+}
+
+/** Merge properties into one
+ *
+ * \param oldprops properties to merge into
+ * \param newprops properties to merge
+ * \return a newly allocated \ref pw_properties
+ *
+ * A new \ref pw_properties is allocated and the properties of
+ * \a oldprops and \a newprops are copied into it in that order.
+ *
+ * \memberof pw_properties
+ */
+struct pw_properties *pw_properties_merge(struct pw_properties *oldprops,
+ struct pw_properties *newprops)
+{
+ struct pw_properties *res = NULL;
+
+ if (oldprops == NULL) {
+ if (newprops == NULL)
+ res = NULL;
+ else
+ res = pw_properties_copy(newprops);
+ } else if (newprops == NULL) {
+ res = pw_properties_copy(oldprops);
+ } else {
+ const char *key;
+ void *state = NULL;
+
+ res = pw_properties_copy(oldprops);
+ if (res == NULL)
+ return NULL;
+
+ while ((key = pw_properties_iterate(newprops, &state))) {
+ pw_properties_set(res, key, pw_properties_get(newprops, key));
+ }
+ }
+ return res;
+}
+
+/** Free a properties object
+ *
+ * \param properties the properties to free
+ *
+ * \memberof pw_properties
+ */
+void pw_properties_free(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_clear(&impl->items);
+ free(impl);
+}
+
+static void do_replace(struct pw_properties *properties, char *key, char *value)
+{
+ struct properties *impl = SPA_CONTAINER_OF(properties, struct properties, this);
+ int index = find_index(properties, key);
+
+ if (index == -1) {
+ add_func(properties, key, value);
+ } else {
+ struct spa_dict_item *item =
+ pw_array_get_unchecked(&impl->items, index, struct spa_dict_item);
+
+ clear_item(item);
+ if (value == NULL) {
+ struct spa_dict_item *other = pw_array_get_unchecked(&impl->items,
+ pw_array_get_len
+ (&impl->items,
+ struct spa_dict_item)
+ - 1,
+ struct spa_dict_item);
+ item->key = other->key;
+ item->value = other->value;
+ impl->items.size -= sizeof(struct spa_dict_item);
+ } else {
+ item->key = key;
+ item->value = value;
+ }
+ }
+}
+
+/** Set a property value
+ *
+ * \param properties the properties to change
+ * \param key a key
+ * \param value a value or NULL to remove the key
+ *
+ * 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.
+ *
+ * \memberof pw_properties
+ */
+void pw_properties_set(struct pw_properties *properties, const char *key, const char *value)
+{
+ do_replace(properties, strdup(key), value ? strdup(value) : NULL);
+}
+
+/** Set a property value by format
+ *
+ * \param properties a \ref pw_properties
+ * \param key a key
+ * \param format a value
+ * \param ... extra arguments
+ *
+ * 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.
+ *
+ * \memberof pw_properties
+ */
+void pw_properties_setf(struct pw_properties *properties, const char *key, const char *format, ...)
+{
+ va_list varargs;
+ char *value;
+
+ va_start(varargs, format);
+ vasprintf(&value, format, varargs);
+ va_end(varargs);
+
+ do_replace(properties, strdup(key), value);
+}
+
+/** 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.
+ *
+ * \memberof pw_properties
+ */
+const char *pw_properties_get(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;
+}
+
+/** 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.
+ *
+ * \memberof pw_properties
+ */
+const char *pw_properties_iterate(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;
+}
diff --git a/src/pipewire/properties.h b/src/pipewire/properties.h
new file mode 100644
index 00000000..9d79e8a0
--- /dev/null
+++ b/src/pipewire/properties.h
@@ -0,0 +1,74 @@
+/* PipeWire
+ * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __PIPEWIRE_PROPERTIES_H__
+#define __PIPEWIRE_PROPERTIES_H__
+
+#ifdef __cplusplus
+//extern "C" {
+#endif
+
+#include <spa/dict.h>
+
+/** \class pw_properties
+ *
+ * \brief A collection of key/value pairs
+ *
+ * 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.
+ */
+struct pw_properties {
+ struct spa_dict dict;
+};
+
+struct pw_properties *
+pw_properties_new(const char *key, ...);
+
+struct pw_properties *
+pw_properties_new_dict(const struct spa_dict *dict);
+
+struct pw_properties *
+pw_properties_copy(struct pw_properties *properties);
+
+struct pw_properties *
+pw_properties_merge(struct pw_properties *oldprops,
+ struct pw_properties *newprops);
+
+void
+pw_properties_free(struct pw_properties *properties);
+
+void
+pw_properties_set(struct pw_properties *properties, const char *key, const char *value);
+
+void
+pw_properties_setf(struct pw_properties *properties,
+ const char *key, const char *format, ...) SPA_PRINTF_FUNC(3, 4);
+const char *
+pw_properties_get(struct pw_properties *properties, const char *key);
+
+const char *
+pw_properties_iterate(struct pw_properties *properties, void **state);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __PIPEWIRE_PROPERTIES_H__ */
diff --git a/src/pipewire/protocol.c b/src/pipewire/protocol.c
new file mode 100644
index 00000000..6fcc77d7
--- /dev/null
+++ b/src/pipewire/protocol.c
@@ -0,0 +1,96 @@
+/* PipeWire
+ * Copyright (C) 2016 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <pipewire/protocol.h>
+
+static struct impl {
+ bool init;
+ struct spa_list protocol_list;
+} protocols;
+
+static struct impl *get_impl(void)
+{
+ if (!protocols.init) {
+ spa_list_init(&protocols.protocol_list);
+ protocols.init = true;
+ }
+ return &protocols;
+}
+
+struct pw_protocol *pw_protocol_get(const char *name)
+{
+ struct impl *impl = get_impl();
+ struct pw_protocol *protocol;
+
+ spa_list_for_each(protocol, &impl->protocol_list, link) {
+ if (strcmp(protocol->name, name) == 0)
+ return protocol;
+ }
+
+ protocol = calloc(1, sizeof(struct pw_protocol));
+ protocol->name = name;
+ spa_list_init(&protocol->iface_list);
+ spa_list_init(&protocol->connection_list);
+ spa_list_init(&protocol->listener_list);
+ spa_list_insert(impl->protocol_list.prev, &protocol->link);
+
+ pw_log_info("Created protocol %s", name);
+
+ return protocol;
+}
+
+void
+pw_protocol_add_interfaces(struct pw_protocol *protocol,
+ const struct pw_interface *client_iface,
+ const struct pw_interface *server_iface)
+{
+ struct pw_protocol_iface *iface;
+ const char *type;
+ uint32_t version;
+
+ iface = calloc(1, sizeof(struct pw_protocol_iface));
+ iface->client_iface = client_iface;
+ iface->server_iface = server_iface;
+
+ spa_list_insert(protocol->iface_list.prev, &iface->link);
+
+ type = client_iface ? client_iface->type : server_iface->type;
+ version = client_iface ? client_iface->version : server_iface->version;
+
+ pw_log_info("Add iface %s:%d to protocol %s", type, version, protocol->name);
+}
+
+const struct pw_interface *
+pw_protocol_get_interface(struct pw_protocol *protocol,
+ const char *type,
+ bool server)
+{
+ struct pw_protocol_iface *protocol_iface;
+
+ if (protocol == NULL)
+ return NULL;
+
+ spa_list_for_each(protocol_iface, &protocol->iface_list, link) {
+ const struct pw_interface *iface = server ? protocol_iface->server_iface :
+ protocol_iface->client_iface;
+ if (strcmp(iface->type, type) == 0)
+ return iface;
+ }
+ return NULL;
+}
diff --git a/src/pipewire/protocol.h b/src/pipewire/protocol.h
new file mode 100644
index 00000000..2c79deb1
--- /dev/null
+++ b/src/pipewire/protocol.h
@@ -0,0 +1,94 @@
+/* PipeWire
+ * Copyright (C) 2016 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __PIPEWIRE_PROTOCOL_H__
+#define __PIPEWIRE_PROTOCOL_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/defs.h>
+#include <spa/list.h>
+
+#include <pipewire/type.h>
+#include <pipewire/properties.h>
+
+#define PW_TYPE_PROTOCOL__Native PW_TYPE_PROTOCOL_BASE "Native"
+
+struct pw_protocol_connection {
+ struct spa_list link;
+ struct pw_remote *remote;
+
+ int (*connect) (struct pw_protocol_connection *conn);
+ int (*connect_fd) (struct pw_protocol_connection *conn, int fd);
+ int (*disconnect) (struct pw_protocol_connection *conn);
+ int (*destroy) (struct pw_protocol_connection *conn);
+};
+
+struct pw_protocol_listener {
+ struct spa_list link;
+ struct pw_core *core;
+
+ int (*destroy) (struct pw_protocol_listener *listen);
+};
+
+struct pw_protocol_iface {
+ struct spa_list link;
+ const struct pw_interface *client_iface;
+ const struct pw_interface *server_iface;
+};
+
+struct pw_protocol {
+ struct spa_list link;
+ const char *name;
+ struct spa_list iface_list;
+ struct spa_list connection_list;
+ struct spa_list listener_list;
+
+ struct pw_protocol_connection * (*new_connection) (struct pw_protocol *protocol,
+ struct pw_remote *remote,
+ struct pw_properties *properties);
+ struct pw_protocol_listener * (*add_listener) (struct pw_protocol *protocol,
+ struct pw_core *core,
+ struct pw_properties *properties);
+ void *protocol_private;
+};
+
+struct pw_protocol *pw_protocol_get(const char *name);
+
+/** \class pw_protocol
+ *
+ * \brief Manages protocols and their implementation
+ */
+void
+pw_protocol_add_interfaces(struct pw_protocol *protocol,
+ const struct pw_interface *client_iface,
+ const struct pw_interface *server_iface);
+
+const struct pw_interface *
+pw_protocol_get_interface(struct pw_protocol *protocol,
+ const char *type,
+ bool server);
+
+#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 00000000..6687a879
--- /dev/null
+++ b/src/pipewire/proxy.c
@@ -0,0 +1,122 @@
+/* PipeWire
+ * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <pipewire/log.h>
+#include <pipewire/proxy.h>
+#include <pipewire/core.h>
+
+/** \cond */
+struct proxy {
+ struct pw_proxy this;
+};
+/** \endcond */
+
+/** Create a proxy object with a given id and type
+ *
+ * \param remote Remote object
+ * \param id Id of the new object, SPA_ID_INVALID will choose a new id
+ * \param type Type of the proxy object
+ * \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_remote
+ *
+ * \memberof pw_proxy
+ */
+struct pw_proxy *pw_proxy_new(struct pw_remote *remote,
+ uint32_t id, uint32_t type,
+ size_t user_data_size)
+{
+ struct proxy *impl;
+ struct pw_proxy *this;
+
+ impl = calloc(1, sizeof(struct proxy) + user_data_size);
+ if (impl == NULL)
+ return NULL;
+
+ this = &impl->this;
+ this->remote = remote;
+ this->type = type;
+
+ pw_signal_init(&this->destroy_signal);
+
+ if (id == SPA_ID_INVALID) {
+ id = pw_map_insert_new(&remote->objects, this);
+ } else if (!pw_map_insert_at(&remote->objects, id, this))
+ goto in_use;
+
+ this->id = id;
+
+ if (user_data_size > 0)
+ this->user_data = SPA_MEMBER(impl, sizeof(struct proxy), void);
+
+ this->iface = pw_protocol_get_interface(remote->protocol,
+ spa_type_map_get_type(remote->core->type.map, type),
+ false);
+
+ spa_list_insert(&this->remote->proxy_list, &this->link);
+
+ pw_log_debug("proxy %p: new %u, remote %p", this, this->id, remote);
+
+ return this;
+
+ in_use:
+ pw_log_error("proxy %p: id %u in use for remote %p", this, id, remote);
+ free(impl);
+ return NULL;
+}
+
+int pw_proxy_set_implementation(struct pw_proxy *proxy,
+ void *object,
+ uint32_t version,
+ const void *implementation,
+ pw_destroy_t destroy)
+{
+ proxy->object = object;
+ proxy->version = version;
+ proxy->implementation = implementation;
+ proxy->destroy = destroy;
+ return SPA_RESULT_OK;
+}
+
+/** Destroy a proxy object
+ *
+ * \param proxy Proxy object to destroy
+ *
+ * \note This is normally called by \ref pw_remote when the server
+ * decides to destroy the server side object
+ * \memberof pw_proxy
+ */
+void pw_proxy_destroy(struct pw_proxy *proxy)
+{
+ struct proxy *impl = SPA_CONTAINER_OF(proxy, struct proxy, this);
+
+ pw_log_debug("proxy %p: destroy %u", proxy, proxy->id);
+ pw_signal_emit(&proxy->destroy_signal, proxy);
+
+ pw_map_remove(&proxy->remote->objects, proxy->id);
+ spa_list_remove(&proxy->link);
+
+ if (proxy->destroy)
+ proxy->destroy(proxy);
+
+ free(impl);
+}
diff --git a/src/pipewire/proxy.h b/src/pipewire/proxy.h
new file mode 100644
index 00000000..66553576
--- /dev/null
+++ b/src/pipewire/proxy.h
@@ -0,0 +1,132 @@
+/* PipeWire
+ * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __PIPEWIRE_PROXY_H__
+#define __PIPEWIRE_PROXY_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <pipewire/type.h>
+#include <pipewire/utils.h>
+#include <pipewire/remote.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 the server.
+ *
+ * It is used to communicate with the server side object.
+ *
+ * \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.
+ */
+
+/** \class pw_proxy
+ *
+ * \brief Represents an object on the client side.
+ *
+ * A pw_proxy acts as a client side proxy to an object existing in the
+ * pipewire server. The proxy is responsible for converting interface functions
+ * invoked by the client to PipeWire messages. Events will call the handlers
+ * set in implementation.
+ *
+ * See \ref page_proxy
+ */
+struct pw_proxy {
+ struct pw_remote *remote; /**< the owner remote of this proxy */
+ struct spa_list link; /**< link in the remote */
+
+ uint32_t id; /**< client side id */
+ uint32_t type; /**< object type id */
+ const struct pw_interface *iface; /**< methods/events marshal/demarshal functions */
+
+ void *object; /**< client side object */
+ uint32_t version;
+ const void *implementation; /**< event handler implementation */
+ pw_destroy_t destroy; /**< optional destroy function to clean up the object */
+
+ /** destroy is emited when the proxy is destroyed */
+ PW_SIGNAL(destroy_signal, (struct pw_listener *listener, struct pw_proxy *proxy));
+
+ void *user_data; /**< extra user data */
+};
+
+struct pw_proxy *
+pw_proxy_new(struct pw_remote *remote,
+ uint32_t id,
+ uint32_t type,
+ size_t user_data_size);
+
+int
+pw_proxy_set_implementation(struct pw_proxy *proxy,
+ void *object,
+ uint32_t version,
+ const void *implementation,
+ pw_destroy_t destroy);
+
+void pw_proxy_destroy(struct pw_proxy *proxy);
+
+#define pw_proxy_notify(p,type,event,...) ((type*) (p)->implementation)->event(p, __VA_ARGS__)
+#define pw_proxy_notify_na(p,type,event) ((type*) (p)->implementation)->event(p)
+#define pw_proxy_do(p,type,method,...) ((type*) (p)->iface->methods)->method(p, __VA_ARGS__)
+#define pw_proxy_do_na(p,type,method) ((type*) (p)->iface->methods)->method(p)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __PIPEWIRE_PROXY_H__ */
diff --git a/src/pipewire/remote.c b/src/pipewire/remote.c
new file mode 100644
index 00000000..c49ec75c
--- /dev/null
+++ b/src/pipewire/remote.c
@@ -0,0 +1,293 @@
+/* PipeWire
+ * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <errno.h>
+
+#include "pipewire/pipewire.h"
+#include "pipewire/introspect.h"
+#include "pipewire/interfaces.h"
+#include "pipewire/remote.h"
+#include "pipewire/core.h"
+#include "pipewire/module.h"
+
+#include <spa/lib/debug.h>
+
+/** \cond */
+struct remote {
+ struct pw_remote this;
+};
+/** \endcond */
+
+const char *pw_remote_state_as_string(enum pw_remote_state state)
+{
+ switch (state) {
+ case PW_REMOTE_STATE_ERROR:
+ return "error";
+ case PW_REMOTE_STATE_UNCONNECTED:
+ return "unconnected";
+ case PW_REMOTE_STATE_CONNECTING:
+ return "connecting";
+ case PW_REMOTE_STATE_CONNECTED:
+ return "connected";
+ }
+ return "invalid-state";
+}
+
+static void
+remote_update_state(struct pw_remote *remote, enum pw_remote_state state, const char *fmt, ...)
+{
+ if (remote->state != state) {
+ if (remote->error)
+ free(remote->error);
+
+ if (fmt) {
+ va_list varargs;
+
+ va_start(varargs, fmt);
+ if (vasprintf(&remote->error, fmt, varargs) < 0) {
+ pw_log_debug("remote %p: error formating message: %m", remote);
+ remote->error = NULL;
+ }
+ va_end(varargs);
+ } else {
+ remote->error = NULL;
+ }
+ pw_log_debug("remote %p: update state from %s -> %s (%s)", remote,
+ pw_remote_state_as_string(remote->state),
+ pw_remote_state_as_string(state), remote->error);
+
+ remote->state = state;
+ pw_signal_emit(&remote->state_changed, remote);
+ }
+}
+
+static void core_event_info(void *object, struct pw_core_info *info)
+{
+ struct pw_proxy *proxy = object;
+ struct pw_remote *this = proxy->remote;
+
+ pw_log_debug("got core info");
+ this->info = pw_core_info_update(this->info, info);
+ pw_signal_emit(&this->info_changed, this);
+}
+
+static void core_event_done(void *object, uint32_t seq)
+{
+ struct pw_proxy *proxy = object;
+ struct pw_remote *this = proxy->remote;
+
+ pw_log_debug("core event done %d", seq);
+ if (seq == 0)
+ remote_update_state(this, PW_REMOTE_STATE_CONNECTED, NULL);
+
+ pw_signal_emit(&this->sync_reply, this, seq);
+}
+
+static void core_event_error(void *object, uint32_t id, int res, const char *error, ...)
+{
+ struct pw_proxy *proxy = object;
+ struct pw_remote *this = proxy->remote;
+ remote_update_state(this, PW_REMOTE_STATE_ERROR, error);
+}
+
+static void core_event_remove_id(void *object, uint32_t id)
+{
+ struct pw_proxy *core_proxy = object;
+ struct pw_remote *this = core_proxy->remote;
+ struct pw_proxy *proxy;
+
+ proxy = pw_map_lookup(&this->objects, id);
+ if (proxy) {
+ pw_log_debug("remote %p: object remove %u", this, id);
+ pw_proxy_destroy(proxy);
+ }
+}
+
+static void
+core_event_update_types(void *object, uint32_t first_id, uint32_t n_types, const char **types)
+{
+ struct pw_proxy *proxy = object;
+ struct pw_remote *this = proxy->remote;
+ int i;
+
+ for (i = 0; i < n_types; i++, first_id++) {
+ uint32_t this_id = spa_type_map_get_id(this->core->type.map, types[i]);
+ if (!pw_map_insert_at(&this->types, first_id, PW_MAP_ID_TO_PTR(this_id)))
+ pw_log_error("can't add type for client");
+ }
+}
+
+static const struct pw_core_events core_events = {
+ &core_event_update_types,
+ &core_event_done,
+ &core_event_error,
+ &core_event_remove_id,
+ &core_event_info,
+};
+
+struct pw_remote *pw_remote_new(struct pw_core *core,
+ struct pw_properties *properties)
+{
+ struct remote *impl;
+ struct pw_remote *this;
+
+ impl = calloc(1, sizeof(struct remote));
+ if (impl == NULL)
+ return NULL;
+
+ this = &impl->this;
+ pw_log_debug("remote %p: new", impl);
+
+ this->core = core;
+
+ if (properties == NULL)
+ properties = pw_properties_new(NULL, NULL);
+ if (properties == NULL)
+ goto no_mem;
+
+ pw_fill_remote_properties(properties);
+ this->properties = properties;
+
+ this->state = PW_REMOTE_STATE_UNCONNECTED;
+
+ pw_map_init(&this->objects, 64, 32);
+ pw_map_init(&this->types, 64, 32);
+
+ spa_list_init(&this->proxy_list);
+ spa_list_init(&this->stream_list);
+
+ pw_signal_init(&this->info_changed);
+ pw_signal_init(&this->sync_reply);
+ pw_signal_init(&this->state_changed);
+ pw_signal_init(&this->destroy_signal);
+
+ pw_module_load(core, "libpipewire-module-protocol-native", NULL, NULL);
+ pw_module_load(core, "libpipewire-module-client-node", NULL, NULL);
+ this->protocol = pw_protocol_get(PW_TYPE_PROTOCOL__Native);
+ if (this->protocol == NULL || this->protocol->new_connection == NULL)
+ goto no_protocol;
+
+ this->conn = this->protocol->new_connection(this->protocol, this, properties);
+ if (this->conn == NULL)
+ goto no_connection;
+
+ spa_list_insert(core->remote_list.prev, &this->link);
+
+ return this;
+
+ no_connection:
+ no_protocol:
+ pw_properties_free(properties);
+ no_mem:
+ free(impl);
+ return NULL;
+}
+
+void pw_remote_destroy(struct pw_remote *remote)
+{
+ struct remote *impl = SPA_CONTAINER_OF(remote, struct remote, this);
+ struct pw_proxy *proxy, *t2;
+
+ pw_log_debug("remote %p: destroy", remote);
+ pw_signal_emit(&remote->destroy_signal, remote);
+
+ if (remote->state != PW_REMOTE_STATE_UNCONNECTED)
+ pw_remote_disconnect(remote);
+
+ remote->conn->destroy(remote->conn);
+
+ spa_list_for_each_safe(proxy, t2, &remote->proxy_list, link)
+ pw_proxy_destroy(proxy);
+
+ pw_map_clear(&remote->objects);
+ pw_map_clear(&remote->types);
+
+ spa_list_remove(&remote->link);
+
+ if (remote->info)
+ pw_core_info_free (remote->info);
+
+ if (remote->properties)
+ pw_properties_free(remote->properties);
+ free(remote->error);
+ free(impl);
+}
+
+static int do_connect(struct pw_remote *remote)
+{
+ remote->core_proxy = pw_proxy_new(remote, 0, remote->core->type.core, 0);
+ if (remote->core_proxy == NULL)
+ goto no_proxy;
+
+ pw_proxy_set_implementation(remote->core_proxy, remote, PW_VERSION_CORE,
+ &core_events, NULL);
+
+ pw_core_do_client_update(remote->core_proxy, &remote->properties->dict);
+ pw_core_do_sync(remote->core_proxy, 0);
+
+ return 0;
+
+ no_proxy:
+ remote->conn->disconnect(remote->conn);
+ remote_update_state(remote, PW_REMOTE_STATE_ERROR, "can't connect: no memory");
+ return -1;
+}
+
+int pw_remote_connect(struct pw_remote *remote)
+{
+ int res;
+
+ remote_update_state(remote, PW_REMOTE_STATE_CONNECTING, NULL);
+
+ if ((res = remote->conn->connect(remote->conn)) < 0) {
+ remote_update_state(remote, PW_REMOTE_STATE_ERROR, "connect failed");
+ return res;
+ }
+
+ return do_connect(remote);
+}
+
+int pw_remote_connect_fd(struct pw_remote *remote, int fd)
+{
+ int res;
+
+ remote_update_state(remote, PW_REMOTE_STATE_CONNECTING, NULL);
+
+ if ((res = remote->conn->connect_fd(remote->conn, fd)) < 0) {
+ remote_update_state(remote, PW_REMOTE_STATE_ERROR, "connect_fd failed");
+ return res;
+ }
+
+ return do_connect(remote);
+}
+
+void pw_remote_disconnect(struct pw_remote *remote)
+{
+ remote->conn->disconnect(remote->conn);
+
+ if (remote->core_proxy)
+ pw_proxy_destroy(remote->core_proxy);
+ remote->core_proxy = NULL;
+
+ remote_update_state(remote, PW_REMOTE_STATE_UNCONNECTED, NULL);
+}
diff --git a/src/pipewire/remote.h b/src/pipewire/remote.h
new file mode 100644
index 00000000..5a11fc59
--- /dev/null
+++ b/src/pipewire/remote.h
@@ -0,0 +1,181 @@
+/* PipeWire
+ * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __PIPEWIRE_REMOTE_H__
+#define __PIPEWIRE_REMOTE_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct pw_remote;
+
+#include <pipewire/map.h>
+#include <pipewire/loop.h>
+#include <pipewire/properties.h>
+#include <pipewire/protocol.h>
+#include <pipewire/proxy.h>
+#include <pipewire/type.h>
+#include <pipewire/core.h>
+
+/** \page page_remote_api Remote API
+ *
+ * \section sec_remote_api_overview Overview
+ *
+ * The remote API allows you to connect to a remote PipeWire and
+ * perform actions on the PipeWire graph. This includes
+ *
+ * \li introspecting the objects on the server
+ * \li Creating nodes
+ * \li Linking nodes on their ports
+ * \li providing media to the server for playback or consumption
+ * \li retrieving media from the server
+ *
+ * \section sec_remote_api_loop Event Loop Abstraction
+ *
+ * Most API is asynchronous and based around an event loop. Methods will
+ * start an operation which will cause a state change of the \ref pw_context
+ * object. Connect to the state_changed signal to be notified of these
+ * state changes.
+ *
+ * The most convenient way to deal with the asynchronous calls is probably
+ * with the thread loop (See \subpage page_thread_loop for more details).
+ *
+ * \subsection ssec_remote_api_proxy Proxy
+ *
+ * Proxies are local representations of remote resources. They
+ * allow communication between local and remote objects.
+ *
+ * The \ref pw_remote maintains a list of all proxies, including a core
+ * proxy that is used to get access to other proxy objects.
+ *
+ * See also \subpage page_proxy
+ *
+ * \section sec_remote_api_remote Remote
+ *
+ * \subsection ssec_remote_create Create
+ *
+ * To create a new remote use pw_remote_new(). You will
+ * need to pass a \ref pw_core implementation for event and data loop.
+ *
+ * A typical loop would be created with pw_thread_loop_new() but
+ * other implementation are possible.
+ *
+ * You will also need to pass properties for the remote. Use
+ * pw_fill_remote_properties() to get a default set of properties.
+ *
+ * After creating the remote, you can track the state of the remote
+ * by listening for the state_changed signal.
+ *
+ * \subsection ssec_remote_api_remote_connect Connecting
+ *
+ * A remote must be connected to a server before any operation can be
+ * issued. Calling pw_remote_connect() will initiate the connection
+ * procedure.
+ *
+ * When connecting, the remote will automatically create a core
+ * proxy to get access to the registry and types.
+ *
+ * \subsection ssec_remote_api_remote_disconnect Disconnect
+ *
+ * Use pw_remote_disconnect() to disconnect from the remote.
+ */
+
+/** \enum pw_remote_state The state of a \ref pw_remote \memberof pw_remote */
+enum pw_remote_state {
+ PW_REMOTE_STATE_ERROR = -1, /**< remote is in error */
+ PW_REMOTE_STATE_UNCONNECTED = 0, /**< not connected */
+ PW_REMOTE_STATE_CONNECTING = 1, /**< connecting to remote PipeWire */
+ PW_REMOTE_STATE_CONNECTED = 2, /**< remote is connected and ready */
+};
+
+/** Convert a \ref pw_remote_state to a readable string \memberof pw_remote */
+const char *pw_remote_state_as_string(enum pw_remote_state state);
+
+/** \class pw_remote
+ *
+ * \brief Represents a connection with the PipeWire server
+ *
+ * a \ref pw_remote is created and used to connect to the server.
+ * A \ref pw_proxy for the core object will automatically be created
+ * when connecting.
+ *
+ * See also \ref page_client_api
+ */
+struct pw_remote {
+ struct pw_core *core; /**< core */
+ struct spa_list link; /**< link in core remote_list */
+ struct pw_properties *properties; /**< extra properties */
+
+ struct pw_proxy *core_proxy; /**< proxy for the core object */
+ struct pw_map objects; /**< map of client side proxy objects
+ * indexed with the client id */
+ struct pw_core_info *info; /**< info about the remote core */
+ /** Signal emited when the core info changed */
+ PW_SIGNAL(info_changed, (struct pw_listener *listener, struct pw_remote *remote));
+
+ /** Signal emited when a reply to a sync was received */
+ PW_SIGNAL(sync_reply, (struct pw_listener *listener, struct pw_remote *remote, uint32_t seq));
+
+ uint32_t n_types; /**< number of client types */
+ struct pw_map types; /**< client types */
+
+ struct spa_list proxy_list; /**< list of \ref pw_proxy objects */
+ struct spa_list stream_list; /**< list of \ref pw_proxy objects */
+
+ struct pw_protocol *protocol; /**< the protocol in use */
+ void *protocol_private; /**< private data for the protocol */
+ struct pw_protocol_connection *conn; /**< the protocol connection */
+
+ enum pw_remote_state state;
+ char *error;
+ /** Signal emited when the state changes */
+ PW_SIGNAL(state_changed, (struct pw_listener *listener, struct pw_remote *remote));
+
+ /** Signal emited when the remote is destroyed */
+ PW_SIGNAL(destroy_signal, (struct pw_listener *listener, struct pw_remote *remote));
+};
+
+/** Create a new unconnected remote \memberof pw_remote
+ * \return a new unconnected remote */
+struct pw_remote *
+pw_remote_new(struct pw_core *core, /**< a \ref pw_core */
+ struct pw_properties *properties /**< optional properties, ownership of
+ * the properties is taken.*/ );
+
+/** Destroy a remote \memberof pw_remote */
+void pw_remote_destroy(struct pw_remote *remote);
+
+/** Connect to a remote PipeWire \memberof pw_remote
+ * \return true on success. */
+int pw_remote_connect(struct pw_remote *remote);
+
+/** Connect to a remote PipeWire on the given socket \memberof pw_remote
+ * \param fd the connected socket to use
+ * \return true on success. */
+int pw_remote_connect_fd(struct pw_remote *remote, int fd);
+
+/** Disconnect from the remote PipeWire. \memberof pw_remote */
+void pw_remote_disconnect(struct pw_remote *remote);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __PIPEWIRE_REMOTE_H__ */
diff --git a/src/pipewire/resource.c b/src/pipewire/resource.c
new file mode 100644
index 00000000..f808b6cb
--- /dev/null
+++ b/src/pipewire/resource.c
@@ -0,0 +1,111 @@
+/* PipeWire
+ * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <string.h>
+
+#include "pipewire/interfaces.h"
+#include "pipewire/protocol.h"
+#include "pipewire/resource.h"
+
+/** \cond */
+struct impl {
+ struct pw_resource this;
+};
+/** \endcond */
+
+struct pw_resource *pw_resource_new(struct pw_client *client,
+ uint32_t id,
+ uint32_t type,
+ size_t user_data_size)
+{
+ struct impl *impl;
+ struct pw_resource *this;
+
+ impl = calloc(1, sizeof(struct impl) + user_data_size);
+ if (impl == NULL)
+ return NULL;
+
+ this = &impl->this;
+ this->core = client->core;
+ this->client = client;
+ this->type = type;
+
+ pw_signal_init(&this->destroy_signal);
+
+ if (id == SPA_ID_INVALID) {
+ id = pw_map_insert_new(&client->objects, this);
+ } else if (!pw_map_insert_at(&client->objects, id, this))
+ goto in_use;
+
+ this->id = id;
+
+ if (user_data_size > 0)
+ this->user_data = SPA_MEMBER(impl, sizeof(struct impl), void);
+
+ this->iface = pw_protocol_get_interface(client->protocol,
+ spa_type_map_get_type(client->core->type.map, type),
+ true);
+
+ pw_log_debug("resource %p: new for client %p id %u", this, client, id);
+ pw_signal_emit(&client->resource_added, client, this);
+
+ return this;
+
+ in_use:
+ pw_log_debug("resource %p: id %u in use for client %p", this, id, client);
+ free(impl);
+ return NULL;
+}
+
+int
+pw_resource_set_implementation(struct pw_resource *resource,
+ void *object,
+ uint32_t version,
+ const void *implementation,
+ pw_destroy_t destroy)
+{
+ struct pw_client *client = resource->client;
+
+ resource->object = object;
+ resource->version = version;
+ resource->implementation = implementation;
+ resource->destroy = destroy;
+ pw_signal_emit(&client->resource_impl, client, resource);
+
+ return SPA_RESULT_OK;
+}
+
+void pw_resource_destroy(struct pw_resource *resource)
+{
+ struct pw_client *client = resource->client;
+
+ pw_log_trace("resource %p: destroy %u", resource, resource->id);
+ pw_signal_emit(&resource->destroy_signal, resource);
+
+ pw_map_insert_at(&client->objects, resource->id, NULL);
+ pw_signal_emit(&client->resource_removed, client, resource);
+
+ if (resource->destroy)
+ resource->destroy(resource);
+
+ if (client->core_resource)
+ pw_core_notify_remove_id(client->core_resource, resource->id);
+
+ free(resource);
+}
diff --git a/src/pipewire/resource.h b/src/pipewire/resource.h
new file mode 100644
index 00000000..38cf6ea5
--- /dev/null
+++ b/src/pipewire/resource.h
@@ -0,0 +1,107 @@
+/* PipeWire
+ * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __PIPEWIRE_RESOURCE_H__
+#define __PIPEWIRE_RESOURCE_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define PIPEWIRE_TYPE__Resource "PipeWire:Object:Resource"
+#define PIPEWIRE_TYPE_RESOURCE_BASE PIPEWIRE_TYPE__Resource ":"
+
+#include <spa/list.h>
+
+#include <pipewire/sig.h>
+#include <pipewire/utils.h>
+#include <pipewire/core.h>
+
+/** \page page_resource Resource
+ *
+ * \section sec_page_resource Overview
+ *
+ * Resources represent objects owned by a \ref pw_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.
+ *
+ */
+/** \class pw_resource
+ *
+ * \brief Client owned objects
+ *
+ * Resources are objects owned by a client and are destroyed when the
+ * client disappears.
+ *
+ * See also \ref page_resource
+ */
+struct pw_resource {
+ struct pw_core *core; /**< the core object */
+ struct spa_list link; /**< link in object resource_list */
+
+ struct pw_client *client; /**< owner client */
+
+ uint32_t id; /**< per client unique id, index in client objects */
+ uint32_t type; /**< type id of the object */
+ const struct pw_interface *iface; /**< protocol specific interface functions */
+
+ void *object; /**< pointer to the object */
+ uint32_t version; /**< interface version */
+ const void *implementation; /**< method implementation */
+ pw_destroy_t destroy; /**< function to clean up the object */
+
+ /** Emited when the resource is destroyed */
+ PW_SIGNAL(destroy_signal, (struct pw_listener *listener, struct pw_resource *resource));
+
+ void *access_private; /**< private data for access control */
+ void *user_data; /**< extra user data */
+};
+
+struct pw_resource *
+pw_resource_new(struct pw_client *client,
+ uint32_t id,
+ uint32_t type,
+ size_t user_data_size);
+
+int
+pw_resource_set_implementation(struct pw_resource *resource,
+ void *object,
+ uint32_t version,
+ const void *implementation,
+ pw_destroy_t destroy);
+
+void
+pw_resource_destroy(struct pw_resource *resource);
+
+#define pw_resource_do(r,type,method,...) ((type*) r->implementation)->method(r, __VA_ARGS__)
+#define pw_resource_do_na(r,type,method) ((type*) r->implementation)->method(r)
+#define pw_resource_notify(r,type,event,...) ((type*) r->iface->events)->event(r, __VA_ARGS__)
+#define pw_resource_notify_na(r,type,event) ((type*) r->iface->events)->event(r)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __PIPEWIRE_RESOURCE_H__ */
diff --git a/src/pipewire/rtkit.c b/src/pipewire/rtkit.c
new file mode 100644
index 00000000..98fa9225
--- /dev/null
+++ b/src/pipewire/rtkit.c
@@ -0,0 +1,378 @@
+/* PipeWire
+ * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+/***
+ Copyright 2009 Lennart Poettering
+ Copyright 2010 David Henningsson <diwic@ubuntu.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 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 <stdlib.h>
+#include <stdbool.h>
+
+#include <dbus/dbus.h>
+
+#include <pipewire/log.h>
+
+#include "rtkit.h"
+
+/** \cond */
+struct pw_rtkit_bus {
+ DBusConnection *bus;
+};
+/** \endcond */
+
+struct pw_rtkit_bus *pw_rtkit_bus_get_system(void)
+{
+ struct pw_rtkit_bus *bus;
+ DBusError error;
+
+ dbus_error_init(&error);
+
+ bus = calloc(1, sizeof(struct pw_rtkit_bus));
+ if (bus == NULL)
+ return NULL;
+
+ bus->bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
+ if (bus->bus == NULL)
+ goto error;
+
+ dbus_connection_set_exit_on_disconnect(bus->bus, false);
+
+ return bus;
+
+ error:
+ pw_log_error("Failed to connect to system bus: %s", error.message);
+ dbus_error_free(&error);
+ return NULL;
+}
+
+void pw_rtkit_bus_free(struct pw_rtkit_bus *system_bus)
+{
+ dbus_connection_close(system_bus->bus);
+ dbus_connection_unref(system_bus->bus);
+ free(system_bus);
+}
+
+#if defined(__linux__) && !defined(__ANDROID__)
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/syscall.h>
+
+
+static pid_t _gettid(void)
+{
+ return (pid_t) syscall(SYS_gettid);
+}
+
+static int translate_error(const char *name)
+{
+ if (0 == strcmp(name, DBUS_ERROR_NO_MEMORY))
+ return -ENOMEM;
+ if (0 == strcmp(name, DBUS_ERROR_SERVICE_UNKNOWN) ||
+ 0 == strcmp(name, DBUS_ERROR_NAME_HAS_NO_OWNER))
+ return -ENOENT;
+ if (0 == strcmp(name, DBUS_ERROR_ACCESS_DENIED) ||
+ 0 == strcmp(name, DBUS_ERROR_AUTH_FAILED))
+ return -EACCES;
+
+ return -EIO;
+}
+
+static long long rtkit_get_int_property(struct pw_rtkit_bus *connection, const char *propname,
+ long long *propval)
+{
+ DBusMessage *m = NULL, *r = NULL;
+ DBusMessageIter iter, subiter;
+ dbus_int64_t i64;
+ dbus_int32_t i32;
+ DBusError error;
+ int current_type;
+ long long ret;
+ const char *interfacestr = "org.freedesktop.RealtimeKit1";
+
+ dbus_error_init(&error);
+
+ if (!(m = dbus_message_new_method_call(RTKIT_SERVICE_NAME,
+ RTKIT_OBJECT_PATH,
+ "org.freedesktop.DBus.Properties", "Get"))) {
+ ret = -ENOMEM;
+ goto finish;
+ }
+
+ if (!dbus_message_append_args(m,
+ DBUS_TYPE_STRING, &interfacestr,
+ DBUS_TYPE_STRING, &propname, DBUS_TYPE_INVALID)) {
+ ret = -ENOMEM;
+ goto finish;
+ }
+
+ if (!(r = dbus_connection_send_with_reply_and_block(connection->bus, m, -1, &error))) {
+ ret = translate_error(error.name);
+ goto finish;
+ }
+
+ if (dbus_set_error_from_message(&error, r)) {
+ ret = translate_error(error.name);
+ goto finish;
+ }
+
+ ret = -EBADMSG;
+ dbus_message_iter_init(r, &iter);
+ while ((current_type = dbus_message_iter_get_arg_type(&iter)) != DBUS_TYPE_INVALID) {
+
+ if (current_type == DBUS_TYPE_VARIANT) {
+ dbus_message_iter_recurse(&iter, &subiter);
+
+ while ((current_type =
+ dbus_message_iter_get_arg_type(&subiter)) != DBUS_TYPE_INVALID) {
+
+ if (current_type == DBUS_TYPE_INT32) {
+ dbus_message_iter_get_basic(&subiter, &i32);
+ *propval = i32;
+ ret = 0;
+ }
+
+ if (current_type == DBUS_TYPE_INT64) {
+ dbus_message_iter_get_basic(&subiter, &i64);
+ *propval = i64;
+ ret = 0;
+ }
+
+ dbus_message_iter_next(&subiter);
+ }
+ }
+ dbus_message_iter_next(&iter);
+ }
+
+ finish:
+
+ if (m)
+ dbus_message_unref(m);
+
+ if (r)
+ dbus_message_unref(r);
+
+ dbus_error_free(&error);
+
+ return ret;
+}
+
+int pw_rtkit_get_max_realtime_priority(struct pw_rtkit_bus *connection)
+{
+ long long retval;
+ int err;
+
+ err = rtkit_get_int_property(connection, "MaxRealtimePriority", &retval);
+ return err < 0 ? err : retval;
+}
+
+int pw_rtkit_get_min_nice_level(struct pw_rtkit_bus *connection, int *min_nice_level)
+{
+ long long retval;
+ int err;
+
+ err = rtkit_get_int_property(connection, "MinNiceLevel", &retval);
+ if (err >= 0)
+ *min_nice_level = retval;
+ return err;
+}
+
+long long pw_rtkit_get_rttime_usec_max(struct pw_rtkit_bus *connection)
+{
+ long long retval;
+ int err;
+
+ err = rtkit_get_int_property(connection, "RTTimeUSecMax", &retval);
+ return err < 0 ? err : retval;
+}
+
+int pw_rtkit_make_realtime(struct pw_rtkit_bus *connection, pid_t thread, int priority)
+{
+ DBusMessage *m = NULL, *r = NULL;
+ dbus_uint64_t u64;
+ dbus_uint32_t u32;
+ DBusError error;
+ int ret;
+
+ dbus_error_init(&error);
+
+ if (thread == 0)
+ thread = _gettid();
+
+ if (!(m = dbus_message_new_method_call(RTKIT_SERVICE_NAME,
+ RTKIT_OBJECT_PATH,
+ "org.freedesktop.RealtimeKit1",
+ "MakeThreadRealtime"))) {
+ ret = -ENOMEM;
+ goto finish;
+ }
+
+ u64 = (dbus_uint64_t) thread;
+ u32 = (dbus_uint32_t) priority;
+
+ if (!dbus_message_append_args(m,
+ DBUS_TYPE_UINT64, &u64,
+ DBUS_TYPE_UINT32, &u32, DBUS_TYPE_INVALID)) {
+ ret = -ENOMEM;
+ goto finish;
+ }
+
+ if (!(r = dbus_connection_send_with_reply_and_block(connection->bus, m, -1, &error))) {
+ ret = translate_error(error.name);
+ goto finish;
+ }
+
+
+ if (dbus_set_error_from_message(&error, r)) {
+ ret = translate_error(error.name);
+ goto finish;
+ }
+
+ ret = 0;
+
+ finish:
+
+ if (m)
+ dbus_message_unref(m);
+
+ if (r)
+ dbus_message_unref(r);
+
+ dbus_error_free(&error);
+
+ return ret;
+}
+
+int pw_rtkit_make_high_priority(struct pw_rtkit_bus *connection, pid_t thread, int nice_level)
+{
+ DBusMessage *m = NULL, *r = NULL;
+ dbus_uint64_t u64;
+ dbus_int32_t s32;
+ DBusError error;
+ int ret;
+
+ dbus_error_init(&error);
+
+ if (thread == 0)
+ thread = _gettid();
+
+ if (!(m = dbus_message_new_method_call(RTKIT_SERVICE_NAME,
+ RTKIT_OBJECT_PATH,
+ "org.freedesktop.RealtimeKit1",
+ "MakeThreadHighPriority"))) {
+ ret = -ENOMEM;
+ goto finish;
+ }
+
+ u64 = (dbus_uint64_t) thread;
+ s32 = (dbus_int32_t) nice_level;
+
+ if (!dbus_message_append_args(m,
+ DBUS_TYPE_UINT64, &u64,
+ DBUS_TYPE_INT32, &s32, DBUS_TYPE_INVALID)) {
+ ret = -ENOMEM;
+ goto finish;
+ }
+
+
+
+ if (!(r = dbus_connection_send_with_reply_and_block(connection->bus, m, -1, &error))) {
+ ret = translate_error(error.name);
+ goto finish;
+ }
+
+
+ if (dbus_set_error_from_message(&error, r)) {
+ ret = translate_error(error.name);
+ goto finish;
+ }
+
+ ret = 0;
+
+ finish:
+
+ if (m)
+ dbus_message_unref(m);
+
+ if (r)
+ dbus_message_unref(r);
+
+ dbus_error_free(&error);
+
+ return ret;
+}
+
+#else
+
+int pw_rtkit_make_realtime(struct pw_rtkit_bus *connection, pid_t thread, int priority)
+{
+ return -ENOTSUP;
+}
+
+int pw_rtkit_make_high_priority(struct pw_rtkit_bus *connection, pid_t thread, int nice_level)
+{
+ return -ENOTSUP;
+}
+
+int pw_rtkit_get_max_realtime_priority(struct pw_rtkit_bus *connection)
+{
+ return -ENOTSUP;
+}
+
+int pw_rtkit_get_min_nice_level(struct pw_rtkit_bus *connection, int *min_nice_level)
+{
+ return -ENOTSUP;
+}
+
+long long pw_rtkit_get_rttime_usec_max(struct pw_rtkit_bus *connection)
+{
+ return -ENOTSUP;
+}
+
+#endif
diff --git a/src/pipewire/rtkit.h b/src/pipewire/rtkit.h
new file mode 100644
index 00000000..a40e2d12
--- /dev/null
+++ b/src/pipewire/rtkit.h
@@ -0,0 +1,93 @@
+/* PipeWire
+ * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __PIPEWIRE_RTKIT_H__
+#define __PIPEWIRE_RTKIT_H__
+
+#include <sys/types.h>
+#include <unistd.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define RTKIT_SERVICE_NAME "org.freedesktop.RealtimeKit1"
+#define RTKIT_OBJECT_PATH "/org/freedesktop/RealtimeKit1"
+
+/** \class pw_rtkit
+ *
+ * RTKit helpers
+ */
+/** Get an RTKit bus \memberof pw_rtkit */
+struct pw_rtkit_bus *
+pw_rtkit_bus_get_system(void);
+
+/** Free an RTKit bus \memberof pw_rtkit */
+void
+pw_rtkit_bus_free(struct pw_rtkit_bus *system_bus);
+
+
+/** This is mostly equivalent to sched_setparam(thread, SCHED_RR, {
+ * .sched_priority = priority }). 'thread' needs to be a kernel thread
+ * id as returned by gettid(), not a pthread_t! If 'thread' is 0 the
+ * current thread is used. The returned value is a negative errno
+ * style error code, or 0 on success.
+ * \memberof pw_rtkit
+ */
+int
+pw_rtkit_make_realtime(struct pw_rtkit_bus *system_bus, pid_t thread, int priority);
+
+
+/** This is mostly equivalent to setpriority(PRIO_PROCESS, thread,
+ * nice_level). 'thread' needs to be a kernel thread id as returned by
+ * gettid(), not a pthread_t! If 'thread' is 0 the current thread is
+ * used. The returned value is a negative errno style error code, or
+ * 0 on success.
+ * \memberof pw_rtkit
+ */
+int
+pw_rtkit_make_high_priority(struct pw_rtkit_bus *system_bus, pid_t thread, int nice_level);
+
+/** Return the maximum value of realtime priority available. Realtime requests
+ * above this value will fail. A negative value is an errno style error code.
+ * \memberof pw_rtkit
+ */
+int
+pw_rtkit_get_max_realtime_priority(struct pw_rtkit_bus *system_bus);
+
+/** Retreive the minimum value of nice level available. High prio requests
+ * below this value will fail. The returned value is a negative errno
+ * style error code, or 0 on success.
+ * \memberof pw_rtkit
+ */
+int
+pw_rtkit_get_min_nice_level(struct pw_rtkit_bus *system_bus, int *min_nice_level);
+
+/** Return the maximum value of RLIMIT_RTTIME to set before attempting a
+ * realtime request. A negative value is an errno style error code.
+ * \memberof pw_rtkit
+ */
+long long
+pw_rtkit_get_rttime_usec_max(struct pw_rtkit_bus *system_bus);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __PIPEWIRE_RTKIT_H__ */
diff --git a/src/pipewire/sig.h b/src/pipewire/sig.h
new file mode 100644
index 00000000..ef850e5a
--- /dev/null
+++ b/src/pipewire/sig.h
@@ -0,0 +1,76 @@
+/* PipeWire
+ * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __PIPEWIRE_SIGNAL_H__
+#define __PIPEWIRE_SIGNAL_H__
+
+#include <spa/list.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** \class pw_signal
+ * Signal emission helpers
+ */
+
+/** A listener \memberof pw_signal */
+struct pw_listener {
+ struct spa_list link; /**< link in the signal listeners */
+ void (*notify) (void *); /**< notify function */
+};
+
+/** A signal definition \memberof pw_signal */
+#define PW_SIGNAL(name,func) \
+ union { \
+ struct spa_list listeners; \
+ void (*notify) func; \
+ } name;
+
+/** Initialize a signal \memberof pw_signal */
+#define pw_signal_init(signal) \
+ spa_list_init(&(signal)->listeners);
+
+/** Add a signal listener \memberof pw_signal */
+#define pw_signal_add(signal,listener,func) \
+do { \
+ __typeof__((signal)->notify) n = (func); \
+ (listener)->notify = (void(*)(void *)) n; \
+ spa_list_insert((signal)->listeners.prev, &(listener)->link); \
+} while (false);
+
+/** Remove a signal listener \memberof pw_signal */
+static inline void pw_signal_remove(struct pw_listener *listener)
+{
+ spa_list_remove(&listener->link);
+}
+
+/** Emit a signal \memberof pw_signal */
+#define pw_signal_emit(signal,...) \
+do { \
+ struct pw_listener *l, *next; \
+ spa_list_for_each_safe(l, next, &(signal)->listeners, link) \
+ ((__typeof__((signal)->notify))l->notify)(l,__VA_ARGS__); \
+} while (false);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __PIPEWIRE_SIGNAL_H__ */
diff --git a/src/pipewire/stream.c b/src/pipewire/stream.c
new file mode 100644
index 00000000..e7f5dcde
--- /dev/null
+++ b/src/pipewire/stream.c
@@ -0,0 +1,1062 @@
+/* PipeWire
+ * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <unistd.h>
+#include <sys/socket.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <errno.h>
+#include <time.h>
+
+#include "spa/lib/debug.h"
+
+#include "pipewire/pipewire.h"
+#include "pipewire/interfaces.h"
+#include "pipewire/array.h"
+#include "pipewire/stream.h"
+#include "pipewire/transport.h"
+#include "pipewire/utils.h"
+#include "pipewire/stream.h"
+#include "extensions/client-node.h"
+
+/** \cond */
+
+#define MAX_BUFFER_SIZE 4096
+#define MAX_FDS 32
+#define MAX_INPUTS 64
+#define MAX_OUTPUTS 64
+
+struct mem_id {
+ uint32_t id;
+ int fd;
+ uint32_t flags;
+ void *ptr;
+ uint32_t offset;
+ uint32_t size;
+};
+
+struct buffer_id {
+ struct spa_list link;
+ uint32_t id;
+ bool used;
+ void *buf_ptr;
+ struct spa_buffer *buf;
+};
+
+struct stream {
+ struct pw_stream this;
+
+ uint32_t type_client_node;
+
+ uint32_t n_possible_formats;
+ struct spa_format **possible_formats;
+
+ uint32_t n_params;
+ struct spa_param **params;
+
+ struct spa_format *format;
+ struct spa_port_info port_info;
+ enum spa_direction direction;
+ uint32_t port_id;
+ uint32_t pending_seq;
+
+ enum pw_stream_mode mode;
+
+ int rtreadfd;
+ int rtwritefd;
+ struct spa_source *rtsocket_source;
+
+ struct pw_proxy *node_proxy;
+ bool disconnecting;
+ struct pw_listener node_proxy_destroy;
+ struct pw_listener node_proxy_sync_done;
+
+ struct pw_transport *trans;
+
+ struct spa_source *timeout_source;
+
+ struct pw_array mem_ids;
+ struct pw_array buffer_ids;
+ bool in_order;
+
+ struct spa_list free;
+ bool in_need_buffer;
+
+ int64_t last_ticks;
+ int32_t last_rate;
+ int64_t last_monotonic;
+};
+/** \endcond */
+
+static void clear_memid(struct mem_id *mid)
+{
+ if (mid->ptr != NULL)
+ munmap(mid->ptr, mid->size + mid->offset);
+ mid->ptr = NULL;
+ close(mid->fd);
+}
+
+static void clear_mems(struct pw_stream *stream)
+{
+ struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this);
+ struct mem_id *mid;
+
+ pw_array_for_each(mid, &impl->mem_ids)
+ clear_memid(mid);
+ impl->mem_ids.size = 0;
+}
+
+static void clear_buffers(struct pw_stream *stream)
+{
+ struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this);
+ struct buffer_id *bid;
+
+ pw_log_debug("stream %p: clear buffers", stream);
+
+ pw_array_for_each(bid, &impl->buffer_ids) {
+ pw_signal_emit(&stream->remove_buffer, stream, bid->id);
+ free(bid->buf);
+ bid->buf = NULL;
+ bid->used = false;
+ }
+ impl->buffer_ids.size = 0;
+ impl->in_order = true;
+ spa_list_init(&impl->free);
+}
+
+static bool stream_set_state(struct pw_stream *stream, enum pw_stream_state state, char *error)
+{
+ bool res = stream->state != state;
+ if (res) {
+ if (stream->error)
+ free(stream->error);
+ stream->error = error;
+
+ pw_log_debug("stream %p: update state from %s -> %s (%s)", stream,
+ pw_stream_state_as_string(stream->state),
+ pw_stream_state_as_string(state), stream->error);
+
+ stream->state = state;
+ pw_signal_emit(&stream->state_changed, stream);
+ }
+ return res;
+}
+
+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_CONFIGURE:
+ return "configure";
+ case PW_STREAM_STATE_READY:
+ return "ready";
+ case PW_STREAM_STATE_PAUSED:
+ return "paused";
+ case PW_STREAM_STATE_STREAMING:
+ return "streaming";
+ }
+ return "invalid-state";
+}
+
+struct pw_stream *pw_stream_new(struct pw_remote *remote,
+ const char *name, struct pw_properties *props)
+{
+ struct stream *impl;
+ struct pw_stream *this;
+
+ impl = calloc(1, sizeof(struct stream));
+ if (impl == NULL)
+ return NULL;
+
+ this = &impl->this;
+ pw_log_debug("stream %p: new", impl);
+
+ if (props == NULL) {
+ props = pw_properties_new("media.name", name, NULL);
+ } else if (!pw_properties_get(props, "media.name")) {
+ pw_properties_set(props, "media.name", name);
+ }
+ if (props == NULL)
+ goto no_mem;
+
+ this->properties = props;
+
+ this->remote = remote;
+ this->name = strdup(name);
+ impl->type_client_node = spa_type_map_get_id(remote->core->type.map, PIPEWIRE_TYPE_NODE_BASE "Client");
+
+ pw_signal_init(&this->destroy_signal);
+ pw_signal_init(&this->state_changed);
+ pw_signal_init(&this->format_changed);
+ pw_signal_init(&this->add_buffer);
+ pw_signal_init(&this->remove_buffer);
+ pw_signal_init(&this->new_buffer);
+ pw_signal_init(&this->need_buffer);
+
+ this->state = PW_STREAM_STATE_UNCONNECTED;
+
+ pw_array_init(&impl->mem_ids, 64);
+ pw_array_ensure_size(&impl->mem_ids, sizeof(struct mem_id) * 64);
+ pw_array_init(&impl->buffer_ids, 32);
+ pw_array_ensure_size(&impl->buffer_ids, sizeof(struct buffer_id) * 64);
+ impl->pending_seq = SPA_ID_INVALID;
+ spa_list_init(&impl->free);
+
+ spa_list_insert(&remote->stream_list, &this->link);
+
+ return this;
+
+ no_mem:
+ free(impl);
+ return NULL;
+}
+
+static void unhandle_socket(struct pw_stream *stream)
+{
+ struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this);
+
+ if (impl->rtsocket_source) {
+ pw_loop_destroy_source(stream->remote->core->data_loop, impl->rtsocket_source);
+ impl->rtsocket_source = NULL;
+ }
+ if (impl->timeout_source) {
+ pw_loop_destroy_source(stream->remote->core->data_loop, impl->timeout_source);
+ impl->timeout_source = NULL;
+ }
+}
+
+static void
+set_possible_formats(struct pw_stream *stream,
+ int n_possible_formats, const struct spa_format **possible_formats)
+{
+ struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this);
+ int i;
+
+ if (impl->possible_formats) {
+ for (i = 0; i < impl->n_possible_formats; i++)
+ free(impl->possible_formats[i]);
+ free(impl->possible_formats);
+ impl->possible_formats = NULL;
+ }
+ impl->n_possible_formats = n_possible_formats;
+ if (n_possible_formats > 0) {
+ impl->possible_formats = malloc(n_possible_formats * sizeof(struct spa_format *));
+ for (i = 0; i < n_possible_formats; i++)
+ impl->possible_formats[i] = spa_format_copy(possible_formats[i]);
+ }
+}
+
+static void set_params(struct pw_stream *stream, int n_params, struct spa_param **params)
+{
+ struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this);
+ int i;
+
+ if (impl->params) {
+ for (i = 0; i < impl->n_params; i++)
+ free(impl->params[i]);
+ free(impl->params);
+ impl->params = NULL;
+ }
+ impl->n_params = n_params;
+ if (n_params > 0) {
+ impl->params = malloc(n_params * sizeof(struct spa_param *));
+ for (i = 0; i < n_params; i++)
+ impl->params[i] = spa_param_copy(params[i]);
+ }
+}
+
+void pw_stream_destroy(struct pw_stream *stream)
+{
+ struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this);
+
+ pw_log_debug("stream %p: destroy", stream);
+
+ pw_signal_emit(&stream->destroy_signal, stream);
+
+ unhandle_socket(stream);
+
+ spa_list_remove(&stream->link);
+
+ if (impl->node_proxy)
+ pw_signal_remove(&impl->node_proxy_destroy);
+
+ set_possible_formats(stream, 0, NULL);
+ set_params(stream, 0, NULL);
+
+ if (impl->format)
+ free(impl->format);
+
+ if (stream->error)
+ free(stream->error);
+
+ clear_buffers(stream);
+ pw_array_clear(&impl->buffer_ids);
+
+ clear_mems(stream);
+ pw_array_clear(&impl->mem_ids);
+
+ if (stream->properties)
+ pw_properties_free(stream->properties);
+
+ if (impl->trans)
+ pw_transport_destroy(impl->trans);
+
+ if (stream->name)
+ free(stream->name);
+
+ close(impl->rtwritefd);
+
+ free(impl);
+}
+
+static void add_node_update(struct pw_stream *stream, uint32_t change_mask)
+{
+ struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this);
+ uint32_t max_input_ports = 0, max_output_ports = 0;
+
+ if (change_mask & PW_CLIENT_NODE_UPDATE_MAX_INPUTS)
+ max_input_ports = impl->direction == SPA_DIRECTION_INPUT ? 1 : 0;
+ if (change_mask & PW_CLIENT_NODE_UPDATE_MAX_OUTPUTS)
+ max_output_ports = impl->direction == SPA_DIRECTION_OUTPUT ? 1 : 0;
+
+ pw_client_node_do_update(impl->node_proxy,
+ change_mask, max_input_ports, max_output_ports, NULL);
+}
+
+static void add_port_update(struct pw_stream *stream, uint32_t change_mask)
+{
+ struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this);
+
+
+ pw_client_node_do_port_update(impl->node_proxy,
+ impl->direction,
+ impl->port_id,
+ change_mask,
+ impl->n_possible_formats,
+ (const struct spa_format **) impl->possible_formats,
+ impl->format,
+ impl->n_params,
+ (const struct spa_param **) impl->params, &impl->port_info);
+}
+
+static inline void send_need_input(struct pw_stream *stream)
+{
+#if 0
+ struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this);
+ uint64_t cmd = 1;
+
+ pw_transport_add_event(impl->trans,
+ &SPA_EVENT_INIT(stream->remote->core->type.event_transport.NeedInput));
+ write(impl->rtwritefd, &cmd, 8);
+#endif
+}
+
+static inline void send_have_output(struct pw_stream *stream)
+{
+ struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this);
+ uint64_t cmd = 1;
+
+ pw_transport_add_event(impl->trans,
+ &SPA_EVENT_INIT(stream->remote->core->type.event_transport.HaveOutput));
+ write(impl->rtwritefd, &cmd, 8);
+}
+
+static void add_request_clock_update(struct pw_stream *stream)
+{
+ struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this);
+
+ pw_client_node_do_event(impl->node_proxy, (struct spa_event *)
+ &SPA_EVENT_NODE_REQUEST_CLOCK_UPDATE_INIT(stream->remote->core->type.
+ event_node.
+ RequestClockUpdate,
+ SPA_EVENT_NODE_REQUEST_CLOCK_UPDATE_TIME,
+ 0, 0));
+}
+
+static void add_async_complete(struct pw_stream *stream, uint32_t seq, int res)
+{
+ struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this);
+
+ pw_client_node_do_done(impl->node_proxy, seq, res);
+}
+
+static void do_node_init(struct pw_stream *stream)
+{
+ struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this);
+
+ add_node_update(stream, PW_CLIENT_NODE_UPDATE_MAX_INPUTS |
+ PW_CLIENT_NODE_UPDATE_MAX_OUTPUTS);
+
+ impl->port_info.flags = SPA_PORT_INFO_FLAG_CAN_USE_BUFFERS;
+ add_port_update(stream, PW_CLIENT_NODE_PORT_UPDATE_POSSIBLE_FORMATS |
+ PW_CLIENT_NODE_PORT_UPDATE_INFO);
+ add_async_complete(stream, 0, SPA_RESULT_OK);
+}
+
+static void on_timeout(struct spa_loop_utils *utils, struct spa_source *source, void *data)
+{
+ struct pw_stream *stream = data;
+ add_request_clock_update(stream);
+}
+
+static struct mem_id *find_mem(struct pw_stream *stream, uint32_t id)
+{
+ struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this);
+ struct mem_id *mid;
+
+ pw_array_for_each(mid, &impl->mem_ids) {
+ if (mid->id == id)
+ return mid;
+ }
+ return NULL;
+}
+
+static struct buffer_id *find_buffer(struct pw_stream *stream, uint32_t id)
+{
+ struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this);
+
+ if (impl->in_order && pw_array_check_index(&impl->buffer_ids, id, struct buffer_id)) {
+ return pw_array_get_unchecked(&impl->buffer_ids, id, struct buffer_id);
+ } else {
+ struct buffer_id *bid;
+
+ pw_array_for_each(bid, &impl->buffer_ids) {
+ if (bid->id == id)
+ return bid;
+ }
+ }
+ return NULL;
+}
+
+static inline void reuse_buffer(struct pw_stream *stream, uint32_t id)
+{
+ struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this);
+ struct buffer_id *bid;
+
+ if ((bid = find_buffer(stream, id)) && bid->used) {
+ pw_log_trace("stream %p: reuse buffer %u", stream, id);
+ bid->used = false;
+ spa_list_insert(impl->free.prev, &bid->link);
+ pw_signal_emit(&stream->new_buffer, stream, id);
+ }
+}
+
+static void handle_rtnode_event(struct pw_stream *stream, struct spa_event *event)
+{
+ struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this);
+ struct pw_remote *remote = impl->this.remote;
+
+ if (SPA_EVENT_TYPE(event) == remote->core->type.event_transport.HaveOutput) {
+ int i;
+
+ for (i = 0; i < impl->trans->area->n_input_ports; i++) {
+ struct spa_port_io *input = &impl->trans->inputs[i];
+
+ pw_log_trace("stream %p: have output %d %d", stream, input->status,
+ input->buffer_id);
+ if (input->buffer_id == SPA_ID_INVALID)
+ continue;
+
+ pw_signal_emit(&stream->new_buffer, stream, input->buffer_id);
+ input->buffer_id = SPA_ID_INVALID;
+ }
+ send_need_input(stream);
+ } else if (SPA_EVENT_TYPE(event) == remote->core->type.event_transport.NeedInput) {
+ int i;
+
+ for (i = 0; i < impl->trans->area->n_output_ports; i++) {
+ struct spa_port_io *output = &impl->trans->outputs[i];
+
+ if (output->buffer_id == SPA_ID_INVALID)
+ continue;
+
+ reuse_buffer(stream, output->buffer_id);
+ output->buffer_id = SPA_ID_INVALID;
+ }
+ pw_log_trace("stream %p: need input", stream);
+ impl->in_need_buffer = true;
+ pw_signal_emit(&stream->need_buffer, stream);
+ impl->in_need_buffer = false;
+ } else if (SPA_EVENT_TYPE(event) == remote->core->type.event_transport.ReuseBuffer) {
+ struct pw_event_transport_reuse_buffer *p =
+ (struct pw_event_transport_reuse_buffer *) event;
+
+ if (p->body.port_id.value != impl->port_id)
+ return;
+ if (impl->direction != SPA_DIRECTION_OUTPUT)
+ return;
+
+ reuse_buffer(stream, p->body.buffer_id.value);
+ } else {
+ pw_log_warn("unexpected node event %d", SPA_EVENT_TYPE(event));
+ }
+}
+
+static void
+on_rtsocket_condition(struct spa_loop_utils *utils,
+ struct spa_source *source, int fd, enum spa_io mask, void *data)
+{
+ struct pw_stream *stream = data;
+ struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this);
+
+ if (mask & (SPA_IO_ERR | SPA_IO_HUP)) {
+ pw_log_warn("got error");
+ unhandle_socket(stream);
+ return;
+ }
+
+ if (mask & SPA_IO_IN) {
+ struct spa_event event;
+ uint64_t cmd;
+
+ read(impl->rtreadfd, &cmd, 8);
+
+ while (pw_transport_next_event(impl->trans, &event) == SPA_RESULT_OK) {
+ struct spa_event *ev = alloca(SPA_POD_SIZE(&event));
+ pw_transport_parse_event(impl->trans, ev);
+ handle_rtnode_event(stream, ev);
+ }
+ }
+}
+
+static void handle_socket(struct pw_stream *stream, int rtreadfd, int rtwritefd)
+{
+ struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this);
+ struct timespec interval;
+
+ impl->rtreadfd = rtreadfd;
+ impl->rtwritefd = rtwritefd;
+ impl->rtsocket_source = pw_loop_add_io(stream->remote->core->data_loop,
+ impl->rtreadfd,
+ SPA_IO_ERR | SPA_IO_HUP,
+ true, on_rtsocket_condition, stream);
+
+ impl->timeout_source = pw_loop_add_timer(stream->remote->core->main_loop, on_timeout, stream);
+ interval.tv_sec = 0;
+ interval.tv_nsec = 100000000;
+ pw_loop_update_timer(stream->remote->core->main_loop, impl->timeout_source, NULL, &interval, false);
+ return;
+}
+
+static bool
+handle_node_command(struct pw_stream *stream, uint32_t seq, const struct spa_command *command)
+{
+ struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this);
+ struct pw_remote *remote = stream->remote;
+
+ if (SPA_COMMAND_TYPE(command) == remote->core->type.command_node.Pause) {
+ add_async_complete(stream, seq, SPA_RESULT_OK);
+
+ if (stream->state == PW_STREAM_STATE_STREAMING) {
+ pw_log_debug("stream %p: pause %d", stream, seq);
+
+ pw_loop_update_io(stream->remote->core->data_loop,
+ impl->rtsocket_source, SPA_IO_ERR | SPA_IO_HUP);
+
+ stream_set_state(stream, PW_STREAM_STATE_PAUSED, NULL);
+ }
+ } else if (SPA_COMMAND_TYPE(command) == remote->core->type.command_node.Start) {
+ add_async_complete(stream, seq, SPA_RESULT_OK);
+
+ if (stream->state == PW_STREAM_STATE_PAUSED) {
+ pw_log_debug("stream %p: start %d %d", stream, seq, impl->direction);
+
+ pw_loop_update_io(stream->remote->core->data_loop,
+ impl->rtsocket_source,
+ SPA_IO_IN | SPA_IO_ERR | SPA_IO_HUP);
+
+ if (impl->direction == SPA_DIRECTION_INPUT)
+ send_need_input(stream);
+ else {
+ impl->in_need_buffer = true;
+ pw_signal_emit(&stream->need_buffer, stream);
+ impl->in_need_buffer = false;
+ }
+ stream_set_state(stream, PW_STREAM_STATE_STREAMING, NULL);
+ }
+ } else if (SPA_COMMAND_TYPE(command) == remote->core->type.command_node.ClockUpdate) {
+ struct spa_command_node_clock_update *cu = (__typeof__(cu)) command;
+
+ if (cu->body.flags.value & SPA_COMMAND_NODE_CLOCK_UPDATE_FLAG_LIVE) {
+ pw_properties_set(stream->properties, "pipewire.latency.is-live", "1");
+ pw_properties_setf(stream->properties,
+ "pipewire.latency.min", "%" PRId64,
+ cu->body.latency.value);
+ }
+ impl->last_ticks = cu->body.ticks.value;
+ impl->last_rate = cu->body.rate.value;
+ impl->last_monotonic = cu->body.monotonic_time.value;
+ } else {
+ pw_log_warn("unhandled node command %d", SPA_COMMAND_TYPE(command));
+ add_async_complete(stream, seq, SPA_RESULT_NOT_IMPLEMENTED);
+ }
+ return true;
+}
+
+static void
+client_node_set_props(void *object, uint32_t seq, const struct spa_props *props)
+{
+ pw_log_warn("set property not implemented");
+}
+
+static void client_node_event(void *object, const struct spa_event *event)
+{
+ pw_log_warn("unhandled node event %d", SPA_EVENT_TYPE(event));
+}
+
+static void
+client_node_add_port(void *object, uint32_t seq, enum spa_direction direction, uint32_t port_id)
+{
+ pw_log_warn("add port not supported");
+}
+
+static void
+client_node_remove_port(void *object, uint32_t seq, enum spa_direction direction, uint32_t port_id)
+{
+ pw_log_warn("remove port not supported");
+}
+
+static void
+client_node_set_format(void *object,
+ uint32_t seq,
+ enum spa_direction direction,
+ uint32_t port_id, uint32_t flags, const struct spa_format *format)
+{
+ struct pw_proxy *proxy = object;
+ struct pw_stream *stream = proxy->object;
+ struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this);
+
+ pw_log_debug("stream %p: format changed %d", stream, seq);
+
+ if (impl->format)
+ free(impl->format);
+ impl->format = format ? spa_format_copy(format) : NULL;
+ impl->pending_seq = seq;
+
+ pw_signal_emit(&stream->format_changed, stream, impl->format);
+
+ if (format)
+ stream_set_state(stream, PW_STREAM_STATE_READY, NULL);
+ else
+ stream_set_state(stream, PW_STREAM_STATE_CONFIGURE, NULL);
+}
+
+static void
+client_node_set_param(void *object,
+ uint32_t seq,
+ enum spa_direction direction,
+ uint32_t port_id,
+ const struct spa_param *param)
+{
+ pw_log_warn("set param not implemented");
+}
+
+static void
+client_node_add_mem(void *object,
+ enum spa_direction direction,
+ uint32_t port_id,
+ uint32_t mem_id,
+ uint32_t type, int memfd, uint32_t flags, uint32_t offset, uint32_t size)
+{
+ struct pw_proxy *proxy = object;
+ struct pw_stream *stream = proxy->object;
+ struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this);
+ struct mem_id *m;
+
+ m = find_mem(stream, mem_id);
+ if (m) {
+ pw_log_debug("update mem %u, fd %d, flags %d, off %d, size %d",
+ mem_id, memfd, flags, offset, size);
+ clear_memid(m);
+ } else {
+ m = pw_array_add(&impl->mem_ids, sizeof(struct mem_id));
+ pw_log_debug("add mem %u, fd %d, flags %d, off %d, size %d",
+ mem_id, memfd, flags, offset, size);
+ }
+ m->id = mem_id;
+ m->fd = memfd;
+ m->flags = flags;
+ m->ptr = NULL;
+ m->offset = offset;
+ m->size = size;
+}
+
+static void
+client_node_use_buffers(void *object,
+ uint32_t seq,
+ enum spa_direction direction,
+ uint32_t port_id, uint32_t n_buffers, struct pw_client_node_buffer *buffers)
+{
+ struct pw_proxy *proxy = object;
+ struct pw_stream *stream = proxy->object;
+ struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this);
+ struct buffer_id *bid;
+ uint32_t i, j, len;
+ struct spa_buffer *b;
+
+ /* clear previous buffers */
+ clear_buffers(stream);
+
+ for (i = 0; i < n_buffers; i++) {
+ off_t offset;
+
+ struct mem_id *mid = find_mem(stream, buffers[i].mem_id);
+ if (mid == NULL) {
+ pw_log_warn("unknown memory id %u", buffers[i].mem_id);
+ continue;
+ }
+
+ if (mid->ptr == NULL) {
+ mid->ptr =
+ mmap(NULL, mid->size + mid->offset, PROT_READ | PROT_WRITE, MAP_SHARED,
+ mid->fd, 0);
+ if (mid->ptr == MAP_FAILED) {
+ mid->ptr = NULL;
+ pw_log_warn("Failed to mmap memory %d %p: %s", mid->size, mid,
+ strerror(errno));
+ continue;
+ }
+ }
+ len = pw_array_get_len(&impl->buffer_ids, struct buffer_id);
+ bid = pw_array_add(&impl->buffer_ids, sizeof(struct buffer_id));
+ if (impl->direction == SPA_DIRECTION_OUTPUT) {
+ bid->used = false;
+ spa_list_insert(impl->free.prev, &bid->link);
+ } else {
+ bid->used = true;
+ }
+
+ b = buffers[i].buffer;
+
+ bid->buf_ptr = SPA_MEMBER(mid->ptr, mid->offset + buffers[i].offset, void);
+ {
+ size_t size;
+
+ size = sizeof(struct spa_buffer);
+ for (j = 0; j < buffers[i].buffer->n_metas; j++)
+ size += sizeof(struct spa_meta);
+ for (j = 0; j < buffers[i].buffer->n_datas; j++)
+ size += sizeof(struct spa_data);
+
+ b = bid->buf = malloc(size);
+ memcpy(b, buffers[i].buffer, sizeof(struct spa_buffer));
+
+ b->metas = SPA_MEMBER(b, sizeof(struct spa_buffer), struct spa_meta);
+ b->datas =
+ SPA_MEMBER(b->metas, sizeof(struct spa_meta) * b->n_metas,
+ struct spa_data);
+ }
+ bid->id = b->id;
+
+ if (bid->id != len) {
+ pw_log_warn("unexpected id %u found, expected %u", bid->id, len);
+ impl->in_order = false;
+ }
+ pw_log_debug("add buffer %d %d %u", mid->id, bid->id, buffers[i].offset);
+
+ offset = 0;
+ for (j = 0; j < b->n_metas; j++) {
+ struct spa_meta *m = &b->metas[j];
+ memcpy(m, &buffers[i].buffer->metas[j], sizeof(struct spa_meta));
+ m->data = SPA_MEMBER(bid->buf_ptr, offset, void);
+ offset += m->size;
+ }
+
+ for (j = 0; j < b->n_datas; j++) {
+ struct spa_data *d = &b->datas[j];
+
+ memcpy(d, &buffers[i].buffer->datas[j], sizeof(struct spa_data));
+ d->chunk =
+ SPA_MEMBER(bid->buf_ptr, offset + sizeof(struct spa_chunk) * j,
+ struct spa_chunk);
+
+ if (d->type == stream->remote->core->type.data.Id) {
+ struct mem_id *bmid = find_mem(stream, SPA_PTR_TO_UINT32(d->data));
+ d->type = stream->remote->core->type.data.MemFd;
+ d->data = NULL;
+ d->fd = bmid->fd;
+ pw_log_debug(" data %d %u -> fd %d", j, bmid->id, bmid->fd);
+ } else if (d->type == stream->remote->core->type.data.MemPtr) {
+ d->data = SPA_MEMBER(bid->buf_ptr, SPA_PTR_TO_INT(d->data), void);
+ d->fd = -1;
+ pw_log_debug(" data %d %u -> mem %p", j, bid->id, d->data);
+ } else {
+ pw_log_warn("unknown buffer data type %d", d->type);
+ }
+ }
+ pw_signal_emit(&stream->add_buffer, stream, bid->id);
+ }
+
+ add_async_complete(stream, seq, SPA_RESULT_OK);
+
+ if (n_buffers)
+ stream_set_state(stream, PW_STREAM_STATE_PAUSED, NULL);
+ else {
+ clear_mems(stream);
+ stream_set_state(stream, PW_STREAM_STATE_READY, NULL);
+ }
+}
+
+static void client_node_node_command(void *object, uint32_t seq, const struct spa_command *command)
+{
+ struct pw_proxy *proxy = object;
+ struct pw_stream *stream = proxy->object;
+ handle_node_command(stream, seq, command);
+}
+
+static void
+client_node_port_command(void *object,
+ uint32_t direction,
+ uint32_t port_id,
+ const struct spa_command *command)
+{
+ pw_log_warn("port command not supported");
+}
+
+static void client_node_transport(void *object, uint32_t node_id,
+ int readfd, int writefd, int memfd, uint32_t offset, uint32_t size)
+{
+ struct pw_proxy *proxy = object;
+ struct pw_stream *stream = proxy->object;
+ struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this);
+ struct pw_transport_info info;
+
+ stream->node_id = node_id;
+
+ info.memfd = memfd;
+ if (info.memfd == -1)
+ return;
+ info.offset = offset;
+ info.size = size;
+
+ if (impl->trans)
+ pw_transport_destroy(impl->trans);
+ impl->trans = pw_transport_new_from_info(&info);
+
+ pw_log_info("stream %p: create client transport %p with fds %d %d for node %u",
+ stream, impl->trans, readfd, writefd, node_id);
+ handle_socket(stream, readfd, writefd);
+
+ stream_set_state(stream, PW_STREAM_STATE_CONFIGURE, NULL);
+}
+
+static const struct pw_client_node_events client_node_events = {
+ &client_node_transport,
+ &client_node_set_props,
+ &client_node_event,
+ &client_node_add_port,
+ &client_node_remove_port,
+ &client_node_set_format,
+ &client_node_set_param,
+ &client_node_add_mem,
+ &client_node_use_buffers,
+ &client_node_node_command,
+ &client_node_port_command,
+};
+
+static void on_node_proxy_destroy(struct pw_listener *listener, struct pw_proxy *proxy)
+{
+ struct stream *impl = SPA_CONTAINER_OF(listener, struct stream, node_proxy_destroy);
+ struct pw_stream *this = &impl->this;
+
+ impl->disconnecting = false;
+ impl->node_proxy = NULL;
+ pw_signal_remove(&impl->node_proxy_destroy);
+ stream_set_state(this, PW_STREAM_STATE_UNCONNECTED, NULL);
+}
+
+bool
+pw_stream_connect(struct pw_stream *stream,
+ enum pw_direction direction,
+ enum pw_stream_mode mode,
+ const char *port_path,
+ enum pw_stream_flags flags,
+ uint32_t n_possible_formats,
+ const struct spa_format **possible_formats)
+{
+ struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this);
+
+ impl->direction =
+ direction == PW_DIRECTION_INPUT ? SPA_DIRECTION_INPUT : SPA_DIRECTION_OUTPUT;
+ impl->port_id = 0;
+ impl->mode = mode;
+
+ set_possible_formats(stream, n_possible_formats, possible_formats);
+
+ stream_set_state(stream, PW_STREAM_STATE_CONNECTING, NULL);
+
+ if (stream->properties == NULL)
+ stream->properties = pw_properties_new(NULL, NULL);
+ if (port_path)
+ pw_properties_set(stream->properties, "pipewire.target.node", port_path);
+ if (flags & PW_STREAM_FLAG_AUTOCONNECT)
+ pw_properties_set(stream->properties, "pipewire.autoconnect", "1");
+
+ impl->node_proxy = pw_proxy_new(stream->remote,
+ SPA_ID_INVALID,
+ impl->type_client_node,
+ 0);
+
+ if (impl->node_proxy == NULL)
+ return false;
+
+ pw_proxy_set_implementation(impl->node_proxy, stream, PW_VERSION_CLIENT_NODE,
+ &client_node_events, NULL);
+
+ pw_signal_add(&impl->node_proxy->destroy_signal,
+ &impl->node_proxy_destroy, on_node_proxy_destroy);
+
+ pw_core_do_create_node(stream->remote->core_proxy,
+ "client-node",
+ "client-node",
+ &stream->properties->dict,
+ impl->node_proxy->id);
+
+ do_node_init(stream);
+
+ return true;
+}
+
+void
+pw_stream_finish_format(struct pw_stream *stream,
+ int res, struct spa_param **params, uint32_t n_params)
+{
+ struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this);
+
+ pw_log_debug("stream %p: finish format %d %d", stream, res, impl->pending_seq);
+
+ set_params(stream, n_params, params);
+
+ if (SPA_RESULT_IS_OK(res)) {
+ add_port_update(stream, (n_params ? PW_CLIENT_NODE_PORT_UPDATE_PARAMS : 0) |
+ PW_CLIENT_NODE_PORT_UPDATE_FORMAT);
+
+ if (!impl->format) {
+ clear_buffers(stream);
+ clear_mems(stream);
+ }
+ }
+ add_async_complete(stream, impl->pending_seq, res);
+
+ impl->pending_seq = SPA_ID_INVALID;
+}
+
+void pw_stream_disconnect(struct pw_stream *stream)
+{
+ struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this);
+
+ impl->disconnecting = true;
+
+ unhandle_socket(stream);
+
+ if (impl->node_proxy) {
+ pw_client_node_do_destroy(impl->node_proxy);
+ impl->node_proxy = NULL;
+ }
+}
+
+bool pw_stream_get_time(struct pw_stream *stream, struct pw_time *time)
+{
+ struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this);
+ int64_t elapsed;
+ struct timespec ts;
+
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+ time->now = SPA_TIMESPEC_TO_TIME(&ts);
+ elapsed = (time->now - impl->last_monotonic) / 1000;
+
+ time->ticks = impl->last_ticks + (elapsed * impl->last_rate) / SPA_USEC_PER_SEC;
+ time->rate = impl->last_rate;
+
+ return true;
+}
+
+uint32_t pw_stream_get_empty_buffer(struct pw_stream *stream)
+{
+ struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this);
+ struct buffer_id *bid;
+
+ if (spa_list_is_empty(&impl->free))
+ return SPA_ID_INVALID;
+
+ bid = spa_list_first(&impl->free, struct buffer_id, link);
+
+ return bid->id;
+}
+
+bool pw_stream_recycle_buffer(struct pw_stream *stream, uint32_t id)
+{
+ struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this);
+ struct pw_event_transport_reuse_buffer rb = PW_EVENT_TRANSPORT_REUSE_BUFFER_INIT
+ (stream->remote->core->type.event_transport.ReuseBuffer, impl->port_id, id);
+ struct buffer_id *bid;
+ uint64_t cmd = 1;
+
+ if ((bid = find_buffer(stream, id)) == NULL || !bid->used)
+ return false;
+
+ bid->used = false;
+ spa_list_insert(impl->free.prev, &bid->link);
+
+ pw_transport_add_event(impl->trans, (struct spa_event *) &rb);
+ write(impl->rtwritefd, &cmd, 8);
+
+ return true;
+}
+
+struct spa_buffer *pw_stream_peek_buffer(struct pw_stream *stream, uint32_t id)
+{
+ struct buffer_id *bid;
+
+ if ((bid = find_buffer(stream, id)))
+ return bid->buf;
+
+ return NULL;
+}
+
+bool pw_stream_send_buffer(struct pw_stream *stream, uint32_t id)
+{
+ struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this);
+ struct buffer_id *bid;
+
+ if (impl->trans->outputs[0].buffer_id != SPA_ID_INVALID) {
+ pw_log_debug("can't send %u, pending buffer %u", id,
+ impl->trans->outputs[0].buffer_id);
+ return false;
+ }
+
+ if ((bid = find_buffer(stream, id)) && !bid->used) {
+ bid->used = true;
+ spa_list_remove(&bid->link);
+ impl->trans->outputs[0].buffer_id = id;
+ impl->trans->outputs[0].status = SPA_RESULT_HAVE_BUFFER;
+ pw_log_trace("stream %p: send buffer %d", stream, id);
+ if (!impl->in_need_buffer)
+ send_have_output(stream);
+ } else {
+ pw_log_debug("stream %p: output %u was used", stream, id);
+ }
+
+ return true;
+}
diff --git a/src/pipewire/stream.h b/src/pipewire/stream.h
new file mode 100644
index 00000000..1463ff71
--- /dev/null
+++ b/src/pipewire/stream.h
@@ -0,0 +1,315 @@
+/* PipeWire
+ * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __PIPEWIRE_STREAM_H__
+#define __PIPEWIRE_STREAM_H__
+
+#include <spa/buffer.h>
+#include <spa/format.h>
+
+#include <pipewire/remote.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** \page page_streams Media Streams
+ *
+ * \section sec_overview Overview
+ *
+ * Media streams are used to exchange data with the PipeWire server. A
+ * stream is a wrapper around a proxy for a \ref pw_client_node with
+ * just one port.
+ *
+ * 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 manage the \ref pw_client_node proxy
+ * yourself.
+ *
+ * \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. You can use \ref
+ * pw_fill_stream_properties() to get a basic set of properties for the
+ * stream.
+ *
+ * Once the stream is created, the state_changed signal 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.
+ *
+ * \subsection ssec_stream_mode Stream modes
+ *
+ * The stream mode specifies how the data will be exchanged with PipeWire.
+ * The following stream modes are available
+ *
+ * \li \ref PW_STREAM_MODE_BUFFER: data is exchanged with fixed size
+ * buffers. This is ideal for video frames or equal sized audio
+ * frames.
+ * \li \ref PW_STREAM_MODE_RINGBUFFER: data is exhanged with a fixed
+ * size ringbuffer. This is ideal for variable sized audio packets
+ * or compressed media.
+ *
+ * \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 the port_path
+ * argument while 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, it will transition to the \ref
+ * PW_STREAM_STATE_CONFIGURE state. In this state the format will be
+ * negotiated by the PipeWire server.
+ *
+ * Once the format has been selected, the format_changed signal is
+ * emited with the configured format as a parameter.
+ *
+ * The client should now prepare itself to deal with the format and
+ * complete the negotiation procedure with a call to \ref
+ * pw_stream_finish_format().
+ *
+ * As arguments to \ref pw_stream_finish_format() 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 signal, a stream will be notified of a new buffer
+ * that can be used for data transport.
+ *
+ * Afer 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 new_buffer signal is emited for each new buffer can can be
+ * consumed.
+ *
+ * \ref pw_stream_peek_buffer() should be used to get the data and metadata
+ * of the buffer.
+ *
+ * When the buffer is no longer in use, call \ref pw_stream_recycle_buffer()
+ * to let PipeWire reuse the buffer.
+ *
+ * \subsection ssec_produce Produce data
+ *
+ * The need_buffer signal is emited when PipeWire needs a new buffer for this
+ * stream.
+ *
+ * \ref pw_stream_get_empty_buffer() gives the id of an empty buffer.
+ * Use \ref pw_stream_peek_buffer() to get the data and metadata that should
+ * be filled.
+ *
+ * To send the filled buffer, use \ref pw_stream_send_buffer().
+ *
+ * The new_buffer signal is emited when PipeWire no longer uses the buffer
+ * and it can be safely reused.
+ *
+ * \section sec_stream_disconnect Disconnect
+ *
+ * Use \ref pw_stream_disconnect() to disconnect a stream after use.
+ */
+
+/** \enum pw_stream_state The state of a stream \memberof pw_stream */
+enum pw_stream_state {
+ PW_STREAM_STATE_ERROR = -1, /**< the strean is in error */
+ PW_STREAM_STATE_UNCONNECTED = 0, /**< unconnected */
+ PW_STREAM_STATE_CONNECTING = 1, /**< connection is in progress */
+ PW_STREAM_STATE_CONFIGURE = 2, /**< stream is being configured */
+ PW_STREAM_STATE_READY = 3, /**< stream is ready */
+ PW_STREAM_STATE_PAUSED = 4, /**< paused, fully configured but not
+ * processing data yet */
+ PW_STREAM_STATE_STREAMING = 5 /**< streaming */
+};
+
+/** Convert a stream state to a readable string \memberof pw_stream */
+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() \memberof pw_stream */
+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_CLOCK_UPDATE = (1 << 1), /**< request periodic clock updates for
+ * this stream */
+};
+
+/** \enum pw_stream_mode The method for transfering data for a stream \memberof pw_stream */
+enum pw_stream_mode {
+ PW_STREAM_MODE_BUFFER = 0, /**< data is placed in buffers */
+ PW_STREAM_MODE_RINGBUFFER = 1, /**< a ringbuffer is used to exchange data */
+};
+
+/** A time structure \memberof pw_stream */
+struct pw_time {
+ int64_t now; /**< the monotonic time */
+ int64_t ticks; /**< the ticks at \a now */
+ int32_t rate; /**< the rate of \a ticks */
+};
+
+/** \class pw_stream
+ *
+ * \brief PipeWire stream object class
+ *
+ * The stream object provides a convenient way to send and
+ * receive data streams from/to PipeWire.
+ *
+ * See also \ref page_streams and \ref page_client_api
+ */
+struct pw_stream {
+ struct pw_remote *remote; /**< the owner remote */
+ struct spa_list link; /**< link in the remote */
+
+ char *name; /**< the name of the stream */
+ uint32_t node_id; /**< node id for remote node, available from
+ * CONFIGURE state and higher */
+ struct pw_properties *properties; /**< properties of the stream */
+
+ /** Emited when the stream is destroyed */
+ PW_SIGNAL(destroy_signal, (struct pw_listener *listener, struct pw_stream *stream));
+
+ enum pw_stream_state state; /**< stream state */
+ char *error; /**< error reason when state is in error */
+ /** Emited when the stream state changes */
+ PW_SIGNAL(state_changed, (struct pw_listener *listener, struct pw_stream *stream));
+
+ /** Emited when the format changed. The listener should call
+ * pw_stream_finish_format() from within this signal or later to complete
+ * the format negotiation */
+ PW_SIGNAL(format_changed, (struct pw_listener *listener,
+ struct pw_stream *stream, struct spa_format *format));
+
+ /** Emited when a new buffer was created for this stream */
+ PW_SIGNAL(add_buffer, (struct pw_listener *listener,
+ struct pw_stream *stream, uint32_t id));
+ /** Emited when a buffer was destroyed for this stream */
+ PW_SIGNAL(remove_buffer, (struct pw_listener *listener,
+ struct pw_stream *stream, uint32_t id));
+ /** Emited when a buffer can be reused (for playback streams) or
+ * is filled (for capture streams */
+ PW_SIGNAL(new_buffer, (struct pw_listener *listener,
+ struct pw_stream *stream, uint32_t id));
+ /** Emited when a buffer is needed (for playback streams) */
+ PW_SIGNAL(need_buffer, (struct pw_listener *listener, struct pw_stream *stream));
+};
+
+/** Create a new unconneced \ref pw_stream \memberof pw_stream
+ * \return a newly allocated \ref pw_stream */
+struct pw_stream *
+pw_stream_new(struct pw_remote *remote, /**< a \ref pw_remote */
+ const char *name, /**< a stream name */
+ struct pw_properties *props /**< stream properties, ownership is taken */);
+
+/** Destroy a stream \memberof pw_stream */
+void pw_stream_destroy(struct pw_stream *stream);
+
+/** Connect a stream for input or output on \a port_path. \memberof pw_stream
+ * \return true on success.
+ *
+ * When \a mode is \ref PW_STREAM_MODE_BUFFER, you should connect to the new-buffer
+ * signal and use pw_stream_peek_buffer() to get the latest metadata and
+ * data. */
+bool
+pw_stream_connect(struct pw_stream *stream, /**< a \ref pw_stream */
+ enum pw_direction direction, /**< the stream direction */
+ enum pw_stream_mode mode, /**< a \ref pw_stream_mode */
+ const char *port_path, /**< the port path to connect to or NULL
+ * to let the server choose a port */
+ enum pw_stream_flags flags, /**< stream flags */
+ uint32_t n_possible_formats, /**< number of items in \a possible_formats */
+ const struct spa_format **possible_formats /**< an array with possible accepted formats */);
+
+/** Disconnect \a stream \memberof pw_stream */
+void pw_stream_disconnect(struct pw_stream *stream);
+
+/** Complete the negotiation process with result code \a res \memberof pw_stream
+ *
+ * This function should be called after notification of the format.
+
+ * When \a res indicates success, \a params contain the parameters for the
+ * allocation state. */
+void
+pw_stream_finish_format(struct pw_stream *stream, /**< a \ref pw_stream */
+ int res, /**< a result code */
+ struct spa_param **params, /**< an array of pointers to \ref spa_param */
+ uint32_t n_params /**< number of elements in \a params */);
+
+/** Query the time on the stream \memberof pw_stream */
+bool pw_stream_get_time(struct pw_stream *stream, struct pw_time *time);
+
+/** Get the id of an empty buffer that can be filled \memberof pw_stream
+ * \return the id of an empty buffer or \ref SPA_ID_INVALID when no buffer is
+ * available. */
+uint32_t pw_stream_get_empty_buffer(struct pw_stream *stream);
+
+/** Recycle the buffer with \a id \memberof pw_stream
+ * \return true on success, false when \a id is invalid or not a used buffer
+ * Let the PipeWire server know that it can reuse the buffer with \a id. */
+bool pw_stream_recycle_buffer(struct pw_stream *stream, uint32_t id);
+
+/** Get the buffer with \a id from \a stream \memberof pw_stream
+ * \return a \ref spa_buffer or NULL when there is no buffer
+ *
+ * This function should be called from the new-buffer signal callback. */
+struct spa_buffer *
+pw_stream_peek_buffer(struct pw_stream *stream, uint32_t id);
+
+/** Send a buffer with \a id to \a stream \memberof pw_stream
+ * \return true when \a id was handled, false on error
+ *
+ * For provider or playback streams, this function should be called whenever
+ * there is a new buffer available. */
+bool pw_stream_send_buffer(struct pw_stream *stream, uint32_t id);
+
+#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 00000000..d2c153a6
--- /dev/null
+++ b/src/pipewire/thread-loop.c
@@ -0,0 +1,297 @@
+/* PipeWire
+ * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <pthread.h>
+
+#include "pipewire.h"
+#include "thread-loop.h"
+
+/** \cond */
+struct thread_loop {
+ struct pw_thread_loop this;
+
+ char *name;
+
+ pthread_mutex_t lock;
+ pthread_cond_t cond;
+ pthread_cond_t accept_cond;
+
+ bool running;
+ pthread_t thread;
+
+ struct spa_loop_control_hooks hooks;
+ const struct spa_loop_control_hooks *old;
+
+ struct spa_source *event;
+
+ int n_waiting;
+ int n_waiting_for_accept;
+};
+/** \endcond */
+
+static void before(const struct spa_loop_control_hooks *hooks)
+{
+ struct thread_loop *impl = SPA_CONTAINER_OF(hooks, struct thread_loop, hooks);
+ pthread_mutex_unlock(&impl->lock);
+}
+
+static void after(const struct spa_loop_control_hooks *hooks)
+{
+ struct thread_loop *impl = SPA_CONTAINER_OF(hooks, struct thread_loop, hooks);
+ pthread_mutex_lock(&impl->lock);
+}
+
+static const struct spa_loop_control_hooks impl_hooks = {
+ SPA_VERSION_LOOP_CONTROL_HOOKS,
+ { NULL, },
+ before,
+ after,
+};
+
+static void do_stop(struct spa_loop_utils *utils, struct spa_source *source, void *data)
+{
+ struct thread_loop *impl = data;
+ impl->running = false;
+}
+
+/** Create a new \ref pw_thread_loop
+ *
+ * \param loop the loop to wrap
+ * \param name the name of the thread or NULL
+ * \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
+ *
+ * \memberof pw_thread_loop
+ */
+struct pw_thread_loop *pw_thread_loop_new(struct pw_loop *loop, const char *name)
+{
+ struct thread_loop *impl;
+ struct pw_thread_loop *this;
+ pthread_mutexattr_t attr;
+
+ impl = calloc(1, sizeof(struct thread_loop));
+ if (impl == NULL)
+ return NULL;
+
+ this = &impl->this;
+ pw_log_debug("thread-loop %p: new", impl);
+
+ this->loop = loop;
+ this->name = name ? strdup(name) : NULL;
+
+ impl->hooks = impl_hooks;
+ pw_loop_add_hooks(loop, &impl->hooks);
+
+ pw_signal_init(&this->destroy_signal);
+
+ pthread_mutexattr_init(&attr);
+ pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
+ pthread_mutex_init(&impl->lock, &attr);
+ pthread_cond_init(&impl->cond, NULL);
+ pthread_cond_init(&impl->accept_cond, NULL);
+
+ impl->event = pw_loop_add_event(this->loop, do_stop, impl);
+
+ return this;
+}
+
+/** Destroy a threaded loop \memberof pw_thread_loop */
+void pw_thread_loop_destroy(struct pw_thread_loop *loop)
+{
+ struct thread_loop *impl = SPA_CONTAINER_OF(loop, struct thread_loop, this);
+
+ pw_signal_emit(&loop->destroy_signal, loop);
+
+ pw_thread_loop_stop(loop);
+
+ if (loop->name)
+ free(loop->name);
+ pthread_mutex_destroy(&impl->lock);
+ pthread_cond_destroy(&impl->cond);
+ pthread_cond_destroy(&impl->accept_cond);
+
+ spa_list_remove(&impl->hooks.link);
+
+ free(impl);
+}
+
+static void *do_loop(void *user_data)
+{
+ struct thread_loop *impl = user_data;
+ struct pw_thread_loop *this = &impl->this;
+ int res;
+
+ pthread_mutex_lock(&impl->lock);
+ pw_log_debug("thread-loop %p: enter thread", this);
+ pw_loop_enter(this->loop);
+
+ while (impl->running) {
+ if ((res = pw_loop_iterate(this->loop, -1)) < 0)
+ pw_log_warn("thread-loop %p: iterate error %d", this, res);
+ }
+ pw_log_debug("thread-loop %p: leave thread", this);
+ pw_loop_leave(this->loop);
+ pthread_mutex_unlock(&impl->lock);
+
+ return NULL;
+}
+
+/** Start the thread to handle \a loop
+ *
+ * \param loop a \ref pw_thread_loop
+ * \return \ref SPA_RESULT_OK on success
+ *
+ * \memberof pw_thread_loop
+ */
+int pw_thread_loop_start(struct pw_thread_loop *loop)
+{
+ struct thread_loop *impl = SPA_CONTAINER_OF(loop, struct thread_loop, this);
+
+ if (!impl->running) {
+ int err;
+
+ impl->running = true;
+ if ((err = pthread_create(&impl->thread, NULL, do_loop, impl)) != 0) {
+ pw_log_warn("thread-loop %p: can't create thread: %s", impl,
+ strerror(err));
+ impl->running = false;
+ return SPA_RESULT_ERROR;
+ }
+ }
+ return SPA_RESULT_OK;
+}
+
+/** Quit the loop and stop its thread
+ *
+ * \param loop a \ref pw_thread_loop
+ *
+ * \memberof pw_thread_loop
+ */
+void pw_thread_loop_stop(struct pw_thread_loop *loop)
+{
+ struct thread_loop *impl = SPA_CONTAINER_OF(loop, struct thread_loop, this);
+
+ pw_log_debug("thread-loop: %p stopping", impl);
+ if (impl->running) {
+ pw_log_debug("thread-loop: %p signal", impl);
+ pw_loop_signal_event(loop->loop, impl->event);
+ pw_log_debug("thread-loop: %p join", impl);
+ pthread_join(impl->thread, NULL);
+ pw_log_debug("thread-loop: %p joined", impl);
+ impl->running = false;
+ }
+ pw_log_debug("thread-loop: %p stopped", impl);
+}
+
+/** Lock the mutex associated with \a loop
+ *
+ * \param loop a \ref pw_thread_loop
+ *
+ * \memberof pw_thread_loop
+ */
+void pw_thread_loop_lock(struct pw_thread_loop *loop)
+{
+ struct thread_loop *impl = SPA_CONTAINER_OF(loop, struct thread_loop, this);
+ pthread_mutex_lock(&impl->lock);
+}
+
+/** Unlock the mutex associated with \a loop
+ *
+ * \param loop a \ref pw_thread_loop
+ *
+ * \memberof pw_thread_loop
+ */
+void pw_thread_loop_unlock(struct pw_thread_loop *loop)
+{
+ struct thread_loop *impl = SPA_CONTAINER_OF(loop, struct thread_loop, this);
+ pthread_mutex_unlock(&impl->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.
+ *
+ * \memberof pw_thread_loop
+ */
+void pw_thread_loop_signal(struct pw_thread_loop *loop, bool wait_for_accept)
+{
+ struct thread_loop *impl = SPA_CONTAINER_OF(loop, struct thread_loop, this);
+
+ if (impl->n_waiting > 0)
+ pthread_cond_broadcast(&impl->cond);
+
+ if (wait_for_accept) {
+ impl->n_waiting_for_accept++;
+
+ while (impl->n_waiting_for_accept > 0)
+ pthread_cond_wait(&impl->accept_cond, &impl->lock);
+ }
+}
+
+/** Wait for the loop thread to call \ref pw_thread_loop_signal()
+ *
+ * \param loop a \ref pw_thread_loop to signal
+ *
+ * \memberof pw_thread_loop
+ */
+void pw_thread_loop_wait(struct pw_thread_loop *loop)
+{
+ struct thread_loop *impl = SPA_CONTAINER_OF(loop, struct thread_loop, this);
+
+ impl->n_waiting++;
+
+ pthread_cond_wait(&impl->cond, &impl->lock);
+ impl->n_waiting--;
+}
+
+/** Signal the loop thread waiting for accept with \ref pw_thread_loop_signal()
+ *
+ * \param loop a \ref pw_thread_loop to signal
+ *
+ * \memberof pw_thread_loop
+ */
+void pw_thread_loop_accept(struct pw_thread_loop *loop)
+{
+ struct thread_loop *impl = SPA_CONTAINER_OF(loop, struct thread_loop, this);
+
+ impl->n_waiting_for_accept--;
+ pthread_cond_signal(&impl->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.
+ *
+ * \memberof pw_thread_loop
+ */
+bool pw_thread_loop_in_thread(struct pw_thread_loop *loop)
+{
+ struct thread_loop *impl = SPA_CONTAINER_OF(loop, struct thread_loop, this);
+ return pthread_self() == impl->thread;
+}
diff --git a/src/pipewire/thread-loop.h b/src/pipewire/thread-loop.h
new file mode 100644
index 00000000..954edcec
--- /dev/null
+++ b/src/pipewire/thread-loop.h
@@ -0,0 +1,133 @@
+/* PipeWire
+ * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __PIPEWIRE_THREAD_LOOP_H__
+#define __PIPEWIRE_THREAD_LOOP_H__
+
+#include <pipewire/loop.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** \page page_thread_loop Threaded 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_signals Signals and Callbacks
+ *
+ * All signals and callbacks are called with the thread lock held.
+ *
+ */
+/** \class pw_thread_loop
+ *
+ * \brief PipeWire threaded loop object
+ *
+ * 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
+ */
+struct pw_thread_loop {
+ struct pw_loop *loop; /**< the \ref pw_loop that is wrapped */
+ char *name; /**< the thread name */
+
+ /** Emited when the threaded loop is destroyed */
+ PW_SIGNAL(destroy_signal, (struct pw_listener *listener,
+ struct pw_thread_loop *loop));
+};
+
+struct pw_thread_loop *
+pw_thread_loop_new(struct pw_loop *loop, const char *name);
+
+void
+pw_thread_loop_destroy(struct pw_thread_loop *loop);
+
+int
+pw_thread_loop_start(struct pw_thread_loop *loop);
+
+void
+pw_thread_loop_stop(struct pw_thread_loop *loop);
+
+void
+pw_thread_loop_lock(struct pw_thread_loop *loop);
+
+void
+pw_thread_loop_unlock(struct pw_thread_loop *loop);
+
+void
+pw_thread_loop_wait(struct pw_thread_loop *loop);
+
+void
+pw_thread_loop_signal(struct pw_thread_loop *loop, bool wait_for_accept);
+
+void
+pw_thread_loop_accept(struct pw_thread_loop *loop);
+
+bool
+pw_thread_loop_in_thread(struct pw_thread_loop *loop);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __PIPEWIRE_THREAD_LOOP_H__ */
diff --git a/src/pipewire/transport.c b/src/pipewire/transport.c
new file mode 100644
index 00000000..e1054029
--- /dev/null
+++ b/src/pipewire/transport.c
@@ -0,0 +1,309 @@
+/* PipeWire
+ * Copyright (C) 2016 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <unistd.h>
+#include <errno.h>
+#include <sys/mman.h>
+
+#include <pipewire/log.h>
+#include <pipewire/transport.h>
+
+/** \cond */
+
+#define INPUT_BUFFER_SIZE (1<<12)
+#define OUTPUT_BUFFER_SIZE (1<<12)
+
+struct transport {
+ struct pw_transport trans;
+
+ struct pw_memblock mem;
+ size_t offset;
+
+ struct spa_event current;
+ uint32_t current_index;
+};
+/** \endcond */
+
+static size_t transport_area_get_size(struct pw_transport_area *area)
+{
+ size_t size;
+ size = sizeof(struct pw_transport_area);
+ size += area->max_input_ports * sizeof(struct spa_port_io);
+ size += area->max_output_ports * sizeof(struct spa_port_io);
+ size += sizeof(struct spa_ringbuffer);
+ size += INPUT_BUFFER_SIZE;
+ size += sizeof(struct spa_ringbuffer);
+ size += OUTPUT_BUFFER_SIZE;
+ return size;
+}
+
+static void transport_setup_area(void *p, struct pw_transport *trans)
+{
+ struct pw_transport_area *a;
+
+ trans->area = a = p;
+ p = SPA_MEMBER(p, sizeof(struct pw_transport_area), struct spa_port_io);
+
+ trans->inputs = p;
+ p = SPA_MEMBER(p, a->max_input_ports * sizeof(struct spa_port_io), void);
+
+ trans->outputs = p;
+ p = SPA_MEMBER(p, a->max_output_ports * sizeof(struct spa_port_io), void);
+
+ trans->input_buffer = p;
+ p = SPA_MEMBER(p, sizeof(struct spa_ringbuffer), void);
+
+ trans->input_data = p;
+ p = SPA_MEMBER(p, INPUT_BUFFER_SIZE, void);
+
+ trans->output_buffer = p;
+ p = SPA_MEMBER(p, sizeof(struct spa_ringbuffer), void);
+
+ trans->output_data = p;
+ p = SPA_MEMBER(p, OUTPUT_BUFFER_SIZE, void);
+}
+
+static void transport_reset_area(struct pw_transport *trans)
+{
+ int i;
+ struct pw_transport_area *a = trans->area;
+
+ for (i = 0; i < a->max_input_ports; i++) {
+ trans->inputs[i].status = SPA_RESULT_OK;
+ trans->inputs[i].buffer_id = SPA_ID_INVALID;
+ }
+ for (i = 0; i < a->max_output_ports; i++) {
+ trans->outputs[i].status = SPA_RESULT_OK;
+ trans->outputs[i].buffer_id = SPA_ID_INVALID;
+ }
+ spa_ringbuffer_init(trans->input_buffer, INPUT_BUFFER_SIZE);
+ spa_ringbuffer_init(trans->output_buffer, OUTPUT_BUFFER_SIZE);
+}
+
+/** Create a new transport
+ * \param max_input_ports maximum number of input_ports
+ * \param max_output_ports maximum number of output_ports
+ * \return a newly allocated \ref pw_transport
+ * \memberof pw_transport
+ */
+struct pw_transport *pw_transport_new(uint32_t max_input_ports, uint32_t max_output_ports)
+{
+ struct transport *impl;
+ struct pw_transport *trans;
+ struct pw_transport_area area;
+
+ area.max_input_ports = max_input_ports;
+ area.n_input_ports = 0;
+ area.max_output_ports = max_output_ports;
+ area.n_output_ports = 0;
+
+ impl = calloc(1, sizeof(struct transport));
+ if (impl == NULL)
+ return NULL;
+
+ impl->offset = 0;
+
+ trans = &impl->trans;
+ pw_signal_init(&trans->destroy_signal);
+
+ pw_memblock_alloc(PW_MEMBLOCK_FLAG_WITH_FD |
+ PW_MEMBLOCK_FLAG_MAP_READWRITE |
+ PW_MEMBLOCK_FLAG_SEAL, transport_area_get_size(&area), &impl->mem);
+
+ memcpy(impl->mem.ptr, &area, sizeof(struct pw_transport_area));
+ transport_setup_area(impl->mem.ptr, trans);
+ transport_reset_area(trans);
+
+ return trans;
+}
+
+struct pw_transport *pw_transport_new_from_info(struct pw_transport_info *info)
+{
+ struct transport *impl;
+ struct pw_transport *trans;
+ void *tmp;
+
+ impl = calloc(1, sizeof(struct transport));
+ if (impl == NULL)
+ return NULL;
+
+ trans = &impl->trans;
+ pw_signal_init(&trans->destroy_signal);
+
+ impl->mem.flags = PW_MEMBLOCK_FLAG_MAP_READWRITE | PW_MEMBLOCK_FLAG_WITH_FD;
+ impl->mem.fd = info->memfd;
+ impl->mem.offset = info->offset;
+ impl->mem.size = info->size;
+ if (pw_memblock_map(&impl->mem) != SPA_RESULT_OK) {
+ pw_log_warn("transport %p: failed to map fd %d: %s", impl, info->memfd,
+ strerror(errno));
+ goto mmap_failed;
+ }
+
+ impl->offset = info->offset;
+
+ transport_setup_area(impl->mem.ptr, trans);
+
+ tmp = trans->output_buffer;
+ trans->output_buffer = trans->input_buffer;
+ trans->input_buffer = tmp;
+
+ tmp = trans->output_data;
+ trans->output_data = trans->input_data;
+ trans->input_data = tmp;
+
+ return trans;
+
+ mmap_failed:
+ free(impl);
+ return NULL;
+}
+
+/** Destroy a transport
+ * \param trans a transport to destroy
+ * \memberof pw_transport
+ */
+void pw_transport_destroy(struct pw_transport *trans)
+{
+ struct transport *impl = (struct transport *) trans;
+
+ pw_log_debug("transport %p: destroy", trans);
+
+ pw_signal_emit(&trans->destroy_signal, trans);
+
+ pw_memblock_free(&impl->mem);
+ free(impl);
+}
+
+/** Get transport info
+ * \param trans the transport to get info of
+ * \param[out] info transport info
+ * \return 0 on success
+ *
+ * Fill \a info with the transport info of \a trans. This information can be
+ * passed to the client to set up the shared transport.
+ *
+ * \memberof pw_transport
+ */
+int pw_transport_get_info(struct pw_transport *trans, struct pw_transport_info *info)
+{
+ struct transport *impl = (struct transport *) trans;
+
+ info->memfd = impl->mem.fd;
+ info->offset = impl->offset;
+ info->size = impl->mem.size;
+
+ return SPA_RESULT_OK;
+}
+
+/** Add an event to the transport
+ * \param trans the transport to send the event on
+ * \param event the event to add
+ * \return 0 on success, < 0 on error
+ *
+ * Write \a event to the shared ringbuffer and signal the other side that
+ * new data can be read.
+ *
+ * \memberof pw_transport
+ */
+int pw_transport_add_event(struct pw_transport *trans, struct spa_event *event)
+{
+ struct transport *impl = (struct transport *) trans;
+ int32_t filled, avail;
+ uint32_t size, index;
+
+ if (impl == NULL || event == NULL)
+ return SPA_RESULT_INVALID_ARGUMENTS;
+
+ filled = spa_ringbuffer_get_write_index(trans->output_buffer, &index);
+ avail = trans->output_buffer->size - filled;
+ size = SPA_POD_SIZE(event);
+ if (avail < size)
+ return SPA_RESULT_ERROR;
+
+ spa_ringbuffer_write_data(trans->output_buffer,
+ trans->output_data,
+ index & trans->output_buffer->mask, event, size);
+ spa_ringbuffer_write_update(trans->output_buffer, index + size);
+
+ return SPA_RESULT_OK;
+}
+
+/** Get next event from a transport
+ * \param trans the transport to get the event of
+ * \param[out] event the event to read
+ * \return 0 on success, < 0 on error, SPA_RESULT_ENUM_END when no more events
+ * are available.
+ *
+ * Get the skeleton next event from \a trans into \a event. This function will
+ * only read the head and object body of the event.
+ *
+ * After the complete size of the event has been calculated, you should call
+ * \ref pw_transport_parse_event() to read the complete event contents.
+ *
+ * \memberof pw_transport
+ */
+int pw_transport_next_event(struct pw_transport *trans, struct spa_event *event)
+{
+ struct transport *impl = (struct transport *) trans;
+ int32_t avail;
+
+ if (impl == NULL || event == NULL)
+ return SPA_RESULT_INVALID_ARGUMENTS;
+
+ avail = spa_ringbuffer_get_read_index(trans->input_buffer, &impl->current_index);
+ if (avail < sizeof(struct spa_event))
+ return SPA_RESULT_ENUM_END;
+
+ spa_ringbuffer_read_data(trans->input_buffer,
+ trans->input_data,
+ impl->current_index & trans->input_buffer->mask,
+ &impl->current, sizeof(struct spa_event));
+
+ *event = impl->current;
+
+ return SPA_RESULT_OK;
+}
+
+/** Parse the complete event on transport
+ * \param trans the transport to read from
+ * \param[out] event memory that can hold the complete event
+ * \return 0 on success, < 0 on error
+ *
+ * Use this function after \ref pw_transport_next_event().
+ *
+ * \memberof pw_transport
+ */
+int pw_transport_parse_event(struct pw_transport *trans, void *event)
+{
+ struct transport *impl = (struct transport *) trans;
+ uint32_t size;
+
+ if (impl == NULL || event == NULL)
+ return SPA_RESULT_INVALID_ARGUMENTS;
+
+ size = SPA_POD_SIZE(&impl->current);
+
+ spa_ringbuffer_read_data(trans->input_buffer,
+ trans->input_data,
+ impl->current_index & trans->input_buffer->mask, event, size);
+ spa_ringbuffer_read_update(trans->input_buffer, impl->current_index + size);
+
+ return SPA_RESULT_OK;
+}
diff --git a/src/pipewire/transport.h b/src/pipewire/transport.h
new file mode 100644
index 00000000..402355c9
--- /dev/null
+++ b/src/pipewire/transport.h
@@ -0,0 +1,137 @@
+/* PipeWire
+ * Copyright (C) 2016 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __PIPEWIRE_TRANSPORT_H__
+#define __PIPEWIRE_TRANSPORT_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <string.h>
+
+#include <spa/defs.h>
+#include <spa/node.h>
+
+#include <pipewire/mem.h>
+#include <pipewire/sig.h>
+
+/** information about the transport region \memberof pw_transport */
+struct pw_transport_info {
+ int memfd; /**< the memfd of the transport area */
+ uint32_t offset; /**< offset to map \a memfd at */
+ uint32_t size; /**< size of memfd mapping */
+};
+
+/** Shared structure between client and server \memberof pw_transport */
+struct pw_transport_area {
+ uint32_t max_input_ports; /**< max input ports of the node */
+ uint32_t n_input_ports; /**< number of input ports of the node */
+ uint32_t max_output_ports; /**< max output ports of the node */
+ uint32_t n_output_ports; /**< number of output ports of the node */
+};
+
+/** \class pw_transport
+ *
+ * \brief Transport object
+ *
+ * The transport object contains shared data and ringbuffers to exchange
+ * events and data between the server and the client in a low-latency and
+ * lockfree way.
+ */
+struct pw_transport {
+ /** Emited when the transport is destroyed */
+ PW_SIGNAL(destroy_signal, (struct pw_listener *listener, struct pw_transport *trans));
+
+ struct pw_transport_area *area; /**< the transport area */
+ struct spa_port_io *inputs; /**< array of input port io */
+ struct spa_port_io *outputs; /**< array of output port io */
+ void *input_data; /**< input memory for ringbuffer */
+ struct spa_ringbuffer *input_buffer; /**< ringbuffer for input memory */
+ void *output_data; /**< output memory for ringbuffer */
+ struct spa_ringbuffer *output_buffer; /**< ringbuffer for output memory */
+};
+
+struct pw_transport *
+pw_transport_new(uint32_t max_input_ports, uint32_t max_output_ports);
+
+struct pw_transport *
+pw_transport_new_from_info(struct pw_transport_info *info);
+
+void
+pw_transport_destroy(struct pw_transport *trans);
+
+int
+pw_transport_get_info(struct pw_transport *trans, struct pw_transport_info *info);
+
+int
+pw_transport_add_event(struct pw_transport *trans, struct spa_event *event);
+
+int
+pw_transport_next_event(struct pw_transport *trans, struct spa_event *event);
+
+int
+pw_transport_parse_event(struct pw_transport *trans, void *event);
+
+#define PIPEWIRE_TYPE_EVENT__Transport SPA_TYPE_EVENT_BASE "Transport"
+#define PIPEWIRE_TYPE_EVENT_TRANSPORT_BASE PIPEWIRE_TYPE_EVENT__Transport ":"
+
+#define PIPEWIRE_TYPE_EVENT_TRANSPORT__HaveOutput PIPEWIRE_TYPE_EVENT_TRANSPORT_BASE "HaveOutput"
+#define PIPEWIRE_TYPE_EVENT_TRANSPORT__NeedInput PIPEWIRE_TYPE_EVENT_TRANSPORT_BASE "NeedInput"
+#define PIPEWIRE_TYPE_EVENT_TRANSPORT__ReuseBuffer PIPEWIRE_TYPE_EVENT_TRANSPORT_BASE "ReuseBuffer"
+
+struct pw_type_event_transport {
+ uint32_t HaveOutput;
+ uint32_t NeedInput;
+ uint32_t ReuseBuffer;
+};
+
+static inline void
+pw_type_event_transport_map(struct spa_type_map *map, struct pw_type_event_transport *type)
+{
+ if (type->HaveOutput == 0) {
+ type->HaveOutput = spa_type_map_get_id(map, PIPEWIRE_TYPE_EVENT_TRANSPORT__HaveOutput);
+ type->NeedInput = spa_type_map_get_id(map, PIPEWIRE_TYPE_EVENT_TRANSPORT__NeedInput);
+ type->ReuseBuffer = spa_type_map_get_id(map, PIPEWIRE_TYPE_EVENT_TRANSPORT__ReuseBuffer);
+ }
+}
+
+struct pw_event_transport_reuse_buffer_body {
+ struct spa_pod_object_body body;
+ struct spa_pod_int port_id;
+ struct spa_pod_int buffer_id;
+};
+
+struct pw_event_transport_reuse_buffer {
+ struct spa_pod pod;
+ struct pw_event_transport_reuse_buffer_body body;
+};
+
+#define PW_EVENT_TRANSPORT_REUSE_BUFFER_INIT(type,port_id,buffer_id) \
+ SPA_EVENT_INIT_COMPLEX(struct pw_event_transport_reuse_buffer, \
+ sizeof(struct pw_event_transport_reuse_buffer_body), type, \
+ SPA_POD_INT_INIT(port_id), \
+ SPA_POD_INT_INIT(buffer_id))
+
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* __PIPEWIRE_TRANSPORT_H__ */
diff --git a/src/pipewire/type.c b/src/pipewire/type.c
new file mode 100644
index 00000000..2ec057cf
--- /dev/null
+++ b/src/pipewire/type.c
@@ -0,0 +1,125 @@
+/* PipeWire
+ * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <string.h>
+
+#include "spa/defs.h"
+#include "spa/clock.h"
+#include "spa/type-map.h"
+#include "spa/monitor.h"
+
+#include "pipewire/pipewire.h"
+#include "pipewire/type.h"
+
+#include "pipewire/node-factory.h"
+
+
+/** Initializes the type system
+ * \param type a type structure
+ * \memberof pw_type
+ */
+void pw_type_init(struct pw_type *type)
+{
+ type->map = pw_get_support_interface(SPA_TYPE__TypeMap);
+
+ type->core = spa_type_map_get_id(type->map, PIPEWIRE_TYPE__Core);
+ type->registry = spa_type_map_get_id(type->map, PIPEWIRE_TYPE__Registry);
+ type->node = spa_type_map_get_id(type->map, PIPEWIRE_TYPE__Node);
+ type->node_factory = spa_type_map_get_id(type->map, PIPEWIRE_TYPE__NodeFactory);
+ type->link = spa_type_map_get_id(type->map, PIPEWIRE_TYPE__Link);
+ type->client = spa_type_map_get_id(type->map, PIPEWIRE_TYPE__Client);
+ type->module = spa_type_map_get_id(type->map, PIPEWIRE_TYPE__Module);
+
+ type->spa_log = spa_type_map_get_id(type->map, SPA_TYPE__Log);
+ type->spa_node = spa_type_map_get_id(type->map, SPA_TYPE__Node);
+ type->spa_clock = spa_type_map_get_id(type->map, SPA_TYPE__Clock);
+ type->spa_monitor = spa_type_map_get_id(type->map, SPA_TYPE__Monitor);
+ type->spa_format = spa_type_map_get_id(type->map, SPA_TYPE__Format);
+ type->spa_props = spa_type_map_get_id(type->map, SPA_TYPE__Props);
+
+ spa_type_meta_map(type->map, &type->meta);
+ spa_type_data_map(type->map, &type->data);
+ spa_type_event_node_map(type->map, &type->event_node);
+ spa_type_command_node_map(type->map, &type->command_node);
+ spa_type_monitor_map(type->map, &type->monitor);
+ spa_type_param_alloc_buffers_map(type->map, &type->param_alloc_buffers);
+ spa_type_param_alloc_meta_enable_map(type->map, &type->param_alloc_meta_enable);
+ spa_type_param_alloc_video_padding_map(type->map, &type->param_alloc_video_padding);
+
+ pw_type_event_transport_map(type->map, &type->event_transport);
+}
+
+bool pw_pod_remap_data(uint32_t type, void *body, uint32_t size, struct pw_map *types)
+{
+ void *t;
+ switch (type) {
+ case SPA_POD_TYPE_ID:
+ if ((t = pw_map_lookup(types, *(int32_t *) body)) == NULL)
+ return false;
+ *(int32_t *) body = PW_MAP_PTR_TO_ID(t);
+ break;
+
+ case SPA_POD_TYPE_PROP:
+ {
+ struct spa_pod_prop_body *b = body;
+
+ if ((t = pw_map_lookup(types, b->key)) == NULL)
+ return false;
+ b->key = PW_MAP_PTR_TO_ID(t);
+
+ if (b->value.type == SPA_POD_TYPE_ID) {
+ void *alt;
+ if (!pw_pod_remap_data
+ (b->value.type, SPA_POD_BODY(&b->value), b->value.size, types))
+ return false;
+
+ SPA_POD_PROP_ALTERNATIVE_FOREACH(b, size, alt)
+ if (!pw_pod_remap_data(b->value.type, alt, b->value.size, types))
+ return false;
+ }
+ break;
+ }
+ case SPA_POD_TYPE_OBJECT:
+ {
+ struct spa_pod_object_body *b = body;
+ struct spa_pod *p;
+
+ if ((t = pw_map_lookup(types, b->type)) == NULL)
+ return false;
+ b->type = PW_MAP_PTR_TO_ID(t);
+
+ SPA_POD_OBJECT_BODY_FOREACH(b, size, p)
+ if (!pw_pod_remap_data(p->type, SPA_POD_BODY(p), p->size, types))
+ return false;
+ break;
+ }
+ case SPA_POD_TYPE_STRUCT:
+ {
+ struct spa_pod *b = body, *p;
+
+ SPA_POD_FOREACH(b, size, p)
+ if (!pw_pod_remap_data(p->type, SPA_POD_BODY(p), p->size, types))
+ return false;
+ break;
+ }
+ default:
+ break;
+ }
+ return true;
+}
diff --git a/src/pipewire/type.h b/src/pipewire/type.h
new file mode 100644
index 00000000..3486343a
--- /dev/null
+++ b/src/pipewire/type.h
@@ -0,0 +1,126 @@
+/* PipeWire
+ * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __PIPEWIRE_TYPE_H__
+#define __PIPEWIRE_TYPE_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/type-map.h>
+#include <spa/event-node.h>
+#include <spa/command-node.h>
+#include <spa/monitor.h>
+#include <spa/param-alloc.h>
+
+#include <pipewire/map.h>
+#include <pipewire/transport.h>
+
+#define PIPEWIRE_TYPE__Core "PipeWire:Object:Core"
+#define PIPEWIRE_TYPE_CORE_BASE PIPEWIRE_TYPE__Core ":"
+
+#define PIPEWIRE_TYPE__Registry "PipeWire:Object:Registry"
+#define PIPEWIRE_TYPE_REGISYRY_BASE PIPEWIRE_TYPE__Registry ":"
+
+#define PIPEWIRE_TYPE__Node "PipeWire:Object:Node"
+#define PIPEWIRE_TYPE_NODE_BASE PIPEWIRE_TYPE__Node ":"
+
+#define PIPEWIRE_TYPE__Client "PipeWire:Object:Client"
+#define PIPEWIRE_TYPE_CLIENT_BASE PIPEWIRE_TYPE__Client ":"
+
+#define PIPEWIRE_TYPE__Link "PipeWire:Object:Link"
+#define PIPEWIRE_TYPE_LINK_BASE PIPEWIRE_TYPE__Link ":"
+
+#define PIPEWIRE_TYPE__Module "PipeWire:Object:Module"
+#define PIPEWIRE_TYPE_MODULE_BASE PIPEWIRE_TYPE__Module ":"
+
+#define PW_TYPE__Protocol "PipeWire:Protocol"
+#define PW_TYPE_PROTOCOL_BASE PW_TYPE__Protocol ":"
+
+/** \class pw_interface
+ * \brief The interface definition
+ *
+ * The interface implements the methods and events for a \ref
+ * pw_proxy. It typically implements marshal functions for the
+ * methods and calls the user installed implementation after
+ * demarshalling the events.
+ *
+ * \sa pw_proxy, pw_resource
+ */
+struct pw_interface {
+ const char *type; /**< interface type */
+ uint32_t version; /**< version */
+ uint32_t n_methods; /**< number of methods in the interface */
+ const void *methods; /**< method implementations of the interface */
+ uint32_t n_events; /**< number of events in the interface */
+ const void *events; /**< event implementations of the interface */
+};
+
+/** \class pw_type
+ * \brief PipeWire type support struct
+ *
+ * This structure contains some of the most common types
+ * and should be initialized with \ref pw_type_init() */
+struct pw_type {
+ struct spa_type_map *map; /**< the type mapper */
+
+ uint32_t core;
+ uint32_t registry;
+ uint32_t node;
+ uint32_t node_factory;
+ uint32_t link;
+ uint32_t client;
+ uint32_t module;
+
+ uint32_t spa_log;
+ uint32_t spa_node;
+ uint32_t spa_clock;
+ uint32_t spa_monitor;
+ uint32_t spa_format;
+ uint32_t spa_props;
+
+ struct spa_type_meta meta;
+ struct spa_type_data data;
+ struct spa_type_event_node event_node;
+ struct spa_type_command_node command_node;
+ struct spa_type_monitor monitor;
+ struct spa_type_param_alloc_buffers param_alloc_buffers;
+ struct spa_type_param_alloc_meta_enable param_alloc_meta_enable;
+ struct spa_type_param_alloc_video_padding param_alloc_video_padding;
+ struct pw_type_event_transport event_transport;
+};
+
+void
+pw_type_init(struct pw_type *type);
+
+bool
+pw_pod_remap_data(uint32_t type, void *body, uint32_t size, struct pw_map *types);
+
+static inline bool
+pw_pod_remap(struct spa_pod *pod, struct pw_map *types)
+{
+ return pw_pod_remap_data(pod->type, SPA_POD_BODY(pod), pod->size, types);
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __PIPEWIRE_TYPE_H__ */
diff --git a/src/pipewire/utils.c b/src/pipewire/utils.c
new file mode 100644
index 00000000..744997d2
--- /dev/null
+++ b/src/pipewire/utils.c
@@ -0,0 +1,131 @@
+/* PipeWire
+ * Copyright (C) 2016 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <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.
+ *
+ * \memberof pw_utils
+ */
+const char *pw_split_walk(const char *str, const char *delimiter, size_t * len, const char **state)
+{
+ const char *s = *state ? *state : str;
+
+ if (*s == '\0')
+ return NULL;
+
+ *len = strcspn(s, delimiter);
+
+ *state = s + *len;
+ *state += strspn(*state, delimiter);
+
+ 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.
+ *
+ * \memberof pw_utils
+ */
+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;
+}
+
+/** 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
+ *
+ * \memberof pw_utils
+ */
+void pw_free_strv(char **str)
+{
+ int i;
+ 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.
+ *
+ * \memberof pw_utils
+ */
+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;
+}
diff --git a/src/pipewire/utils.h b/src/pipewire/utils.h
new file mode 100644
index 00000000..a23b1882
--- /dev/null
+++ b/src/pipewire/utils.h
@@ -0,0 +1,65 @@
+/* PipeWire
+ * Copyright (C) 2016 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __PIPEWIRE_UTILS_H__
+#define __PIPEWIRE_UTILS_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/defs.h>
+#include <spa/pod-utils.h>
+
+/** \class pw_utils
+ *
+ * Various utility functions
+ */
+
+/** a function to destroy an item \memberof pw_utils */
+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);
+
+void
+pw_free_strv(char **str);
+
+char *
+pw_strip(char *str, const char *whitespace);
+
+/** Copy a pod structure \memberof pw_utils */
+static inline struct spa_pod *
+pw_spa_pod_copy(const struct spa_pod *pod)
+{
+ return pod ? memcpy(malloc(SPA_POD_SIZE(pod)), pod, SPA_POD_SIZE(pod)) : NULL;
+}
+
+#define spa_format_copy(f) ((struct spa_format*)pw_spa_pod_copy(&(f)->pod))
+#define spa_props_copy(p) ((struct spa_prop*)pw_spa_pod_copy(&(p)->object.pod))
+#define spa_param_copy(p) ((struct spa_param*)pw_spa_pod_copy(&(p)->object.pod))
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* __PIPEWIRE_UTILS_H__ */
diff --git a/src/pipewire/work-queue.c b/src/pipewire/work-queue.c
new file mode 100644
index 00000000..5c309ea8
--- /dev/null
+++ b/src/pipewire/work-queue.c
@@ -0,0 +1,243 @@
+/* PipeWire
+ * Copyright (C) 2016 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <stdio.h>
+#include <string.h>
+
+#include "pipewire/log.h"
+#include "pipewire/work-queue.h"
+
+/** \cond */
+struct work_item {
+ uint32_t id;
+ void *obj;
+ uint32_t seq;
+ int res;
+ pw_work_func_t func;
+ void *data;
+ struct spa_list link;
+};
+
+struct impl {
+ struct pw_work_queue this;
+
+ struct spa_source *wakeup;
+ uint32_t counter;
+
+ struct spa_list work_list;
+ struct spa_list free_list;
+ int n_queued;
+};
+/** \endcond */
+
+static void process_work_queue(struct spa_loop_utils *utils, struct spa_source *source, void *data)
+{
+ struct impl *impl = data;
+ struct pw_work_queue *this = &impl->this;
+ struct work_item *item, *tmp;
+
+ spa_list_for_each_safe(item, tmp, &impl->work_list, link) {
+ if (item->seq != SPA_ID_INVALID) {
+ pw_log_debug("work-queue %p: %d waiting for item %p %d", this,
+ impl->n_queued, item->obj, item->seq);
+ continue;
+ }
+
+ if (item->res == SPA_RESULT_WAIT_SYNC &&
+ item != spa_list_first(&impl->work_list, struct work_item, link)) {
+ pw_log_debug("work-queue %p: %d sync item %p not head", this,
+ impl->n_queued, item->obj);
+ continue;
+ }
+
+ spa_list_remove(&item->link);
+ impl->n_queued--;
+
+ if (item->func) {
+ pw_log_debug("work-queue %p: %d process work item %p %d %d", this,
+ impl->n_queued, item->obj, item->seq, item->res);
+ item->func(item->obj, item->data, item->res, item->id);
+ }
+ spa_list_insert(impl->free_list.prev, &item->link);
+ }
+}
+
+/** Create a new \ref pw_work_queue
+ *
+ * \param loop the loop to use
+ * \return a newly allocated work queue
+ *
+ * \memberof pw_work_queue
+ */
+struct pw_work_queue *pw_work_queue_new(struct pw_loop *loop)
+{
+ struct impl *impl;
+ struct pw_work_queue *this;
+
+ impl = calloc(1, sizeof(struct impl));
+ pw_log_debug("work-queue %p: new", impl);
+
+ this = &impl->this;
+ this->loop = loop;
+ pw_signal_init(&this->destroy_signal);
+
+ impl->wakeup = pw_loop_add_event(this->loop, process_work_queue, impl);
+
+ spa_list_init(&impl->work_list);
+ spa_list_init(&impl->free_list);
+
+ return this;
+}
+
+/** Destroy a work queue
+ * \param queue the work queue to destroy
+ *
+ * \memberof pw_work_queue
+ */
+void pw_work_queue_destroy(struct pw_work_queue *queue)
+{
+ struct impl *impl = SPA_CONTAINER_OF(queue, struct impl, this);
+ struct work_item *item, *tmp;
+
+ pw_log_debug("work-queue %p: destroy", impl);
+ pw_signal_emit(&queue->destroy_signal, queue);
+
+ pw_loop_destroy_source(queue->loop, impl->wakeup);
+
+ spa_list_for_each_safe(item, tmp, &impl->work_list, link) {
+ pw_log_warn("work-queue %p: cancel work item %p %d %d", queue,
+ item->obj, item->seq, item->res);
+ free(item);
+ }
+ spa_list_for_each_safe(item, tmp, &impl->free_list, link)
+ free(item);
+
+ free(impl);
+}
+
+/** 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
+ *
+ * \memberof pw_work_queue
+ */
+uint32_t
+pw_work_queue_add(struct pw_work_queue *queue, void *obj, int res, pw_work_func_t func, void *data)
+{
+ struct impl *impl = SPA_CONTAINER_OF(queue, struct impl, this);
+ struct work_item *item;
+ bool have_work = false;
+
+ if (!spa_list_is_empty(&impl->free_list)) {
+ item = spa_list_first(&impl->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 = ++impl->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("work-queue %p: defer async %d for object %p", queue, item->seq, obj);
+ } else if (res == SPA_RESULT_WAIT_SYNC) {
+ pw_log_debug("work-queue %p: wait sync object %p", queue, obj);
+ 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("work-queue %p: defer object %p", queue, obj);
+ }
+ spa_list_insert(impl->work_list.prev, &item->link);
+ impl->n_queued++;
+
+ if (have_work)
+ pw_loop_signal_event(impl->this.loop, impl->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
+ *
+ * \memberof pw_work_queue
+ */
+void pw_work_queue_cancel(struct pw_work_queue *queue, void *obj, uint32_t id)
+{
+ struct impl *impl = SPA_CONTAINER_OF(queue, struct impl, this);
+ bool have_work = false;
+ struct work_item *item;
+
+ spa_list_for_each(item, &impl->work_list, link) {
+ if ((id == SPA_ID_INVALID || item->id == id) && (obj == NULL || item->obj == obj)) {
+ pw_log_debug("work-queue %p: cancel defer %d for object %p", queue,
+ item->seq, item->obj);
+ item->seq = SPA_ID_INVALID;
+ item->func = NULL;
+ have_work = true;
+ }
+ }
+ if (have_work)
+ pw_loop_signal_event(impl->this.loop, impl->wakeup);
+}
+
+/** Complete a work item
+ * \param queue the work queue
+ * \param obj the owner object
+ * \param seq the sequence number that completed
+ * \param res the result of the completed work
+ *
+ * \memberof pw_work_queue
+ */
+bool pw_work_queue_complete(struct pw_work_queue *queue, void *obj, uint32_t seq, int res)
+{
+ struct work_item *item;
+ struct impl *impl = SPA_CONTAINER_OF(queue, struct impl, this);
+ bool have_work = false;
+
+ spa_list_for_each(item, &impl->work_list, link) {
+ if (item->obj == obj && item->seq == seq) {
+ pw_log_debug("work-queue %p: found defered %d for object %p", queue, seq,
+ obj);
+ item->seq = SPA_ID_INVALID;
+ item->res = res;
+ have_work = true;
+ }
+ }
+ if (!have_work) {
+ pw_log_debug("work-queue %p: no defered %d found for object %p", queue, seq, obj);
+ } else {
+ pw_loop_signal_event(impl->this.loop, impl->wakeup);
+ }
+ return have_work;
+}
diff --git a/src/pipewire/work-queue.h b/src/pipewire/work-queue.h
new file mode 100644
index 00000000..ccd8a5de
--- /dev/null
+++ b/src/pipewire/work-queue.h
@@ -0,0 +1,63 @@
+/* PipeWire
+ * Copyright (C) 2016 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __PIPEWIRE_WORK_QUEUE_H__
+#define __PIPEWIRE_WORK_QUEUE_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <pipewire/loop.h>
+
+typedef void (*pw_work_func_t) (void *obj, void *data, int res, uint32_t id);
+
+/** \class pw_work_queue
+ *
+ * PipeWire work queue object
+ */
+struct pw_work_queue {
+ struct pw_loop *loop; /**< the loop used for handling work */
+
+ /** Emited when the work queue is destroyed */
+ PW_SIGNAL(destroy_signal, (struct pw_listener *listener, struct pw_work_queue *queue));
+};
+
+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);
+
+void
+pw_work_queue_cancel(struct pw_work_queue *queue, void *obj, uint32_t id);
+
+bool
+pw_work_queue_complete(struct pw_work_queue *queue, void *obj, uint32_t seq, int res);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __PIPEWIRE_WORK_QUEUE_H__ */
diff --git a/src/server/meson.build b/src/server/meson.build
new file mode 100644
index 00000000..b0418eac
--- /dev/null
+++ b/src/server/meson.build
@@ -0,0 +1,53 @@
+pipewirecore_headers = [
+ 'client.h',
+ 'command.h',
+ 'core.h',
+ 'data-loop.h',
+ 'link.h',
+ 'main-loop.h',
+ 'module.h',
+ 'node.h',
+ 'node-factory.h',
+ 'port.h',
+ 'remote.h',
+ 'resource.h',
+ 'work-queue.h',
+]
+
+pipewirecore_sources = [
+ 'client.c',
+ 'command.c',
+ 'core.c',
+ 'data-loop.c',
+ 'link.c',
+ 'main-loop.c',
+ 'module.c',
+ 'node.c',
+ 'node-factory.c',
+ 'port.c',
+ 'remote.c',
+ 'resource.c',
+ 'work-queue.c',
+]
+
+install_headers(pipewirecore_headers, subdir : 'pipewire/server')
+
+libpipewirecore_c_args = [
+ '-DHAVE_CONFIG_H',
+ '-D_GNU_SOURCE',
+]
+
+libpipewirecore = shared_library('pipewirecore-@0@'.format(apiversion), pipewirecore_sources,
+ version : libversion,
+ soversion : soversion,
+ c_args : libpipewirecore_c_args,
+ include_directories : [configinc, spa_inc],
+ link_with : spalib,
+ install : true,
+ dependencies : [mathlib, dl_lib, pipewire_dep],
+)
+
+pipewirecore_dep = declare_dependency(link_with : libpipewirecore,
+ include_directories : [configinc, spa_inc],
+ dependencies : [pipewire_dep],
+)
diff --git a/src/tools/meson.build b/src/tools/meson.build
new file mode 100644
index 00000000..ca8e467f
--- /dev/null
+++ b/src/tools/meson.build
@@ -0,0 +1,5 @@
+executable('pipewire-monitor',
+ 'pipewire-monitor.c',
+ install: true,
+ dependencies : [pipewire_dep],
+)
diff --git a/src/tools/pipewire-monitor.c b/src/tools/pipewire-monitor.c
new file mode 100644
index 00000000..3c35be12
--- /dev/null
+++ b/src/tools/pipewire-monitor.c
@@ -0,0 +1,356 @@
+/* PipeWire
+ * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <stdio.h>
+
+#include <spa/lib/debug.h>
+
+#include <pipewire/pipewire.h>
+#include <pipewire/sig.h>
+
+struct data {
+ bool running;
+ struct pw_loop *loop;
+ struct pw_core *core;
+ struct pw_remote *remote;
+ struct pw_proxy *registry_proxy;
+
+ struct pw_listener on_info_changed;
+ struct pw_listener on_state_changed;
+};
+
+struct proxy_data {
+ void *info;
+};
+
+static void print_properties(struct spa_dict *props, char mark)
+{
+ struct spa_dict_item *item;
+
+ if (props == NULL)
+ return;
+
+ printf("%c\tproperties:\n", mark);
+ spa_dict_for_each(item, props) {
+ printf("%c\t\t%s = \"%s\"\n", mark, item->key, item->value);
+ }
+}
+
+#define MARK_CHANGE(f) ((print_mark && ((info)->change_mask & (1 << (f)))) ? '*' : ' ')
+
+static void on_info_changed(struct pw_listener *listener, struct pw_remote *remote)
+{
+ struct pw_core_info *info = remote->info;
+ bool print_all = true, print_mark = false;
+
+ printf("\tid: %u\n", info->id);
+ printf("\ttype: %s\n", PIPEWIRE_TYPE__Core);
+ if (print_all) {
+ printf("%c\tuser-name: \"%s\"\n", MARK_CHANGE(0), info->user_name);
+ printf("%c\thost-name: \"%s\"\n", MARK_CHANGE(1), info->host_name);
+ printf("%c\tversion: \"%s\"\n", MARK_CHANGE(2), info->version);
+ printf("%c\tname: \"%s\"\n", MARK_CHANGE(3), info->name);
+ printf("%c\tcookie: %u\n", MARK_CHANGE(4), info->cookie);
+ print_properties(info->props, MARK_CHANGE(5));
+ }
+}
+
+static void module_event_info(void *object, struct pw_module_info *info)
+{
+ struct pw_proxy *proxy = object;
+ struct proxy_data *data = proxy->user_data;
+ bool print_all, print_mark;
+
+ print_all = true;
+ if (data->info == NULL) {
+ printf("added:\n");
+ print_mark = false;
+ }
+ else {
+ printf("changed:\n");
+ print_mark = true;
+ }
+
+ info = data->info = pw_module_info_update(data->info, info);
+
+ printf("\tid: %u\n", info->id);
+ printf("\ttype: %s\n", PIPEWIRE_TYPE__Module);
+ if (print_all) {
+ printf("%c\tname: \"%s\"\n", MARK_CHANGE(0), info->name);
+ printf("%c\tfilename: \"%s\"\n", MARK_CHANGE(1), info->filename);
+ printf("%c\targs: \"%s\"\n", MARK_CHANGE(2), info->args);
+ print_properties(info->props, MARK_CHANGE(3));
+ }
+}
+
+static const struct pw_module_events module_events = {
+ &module_event_info,
+};
+
+static void node_event_info(void *object, struct pw_node_info *info)
+{
+ struct pw_proxy *proxy = object;
+ struct proxy_data *data = proxy->user_data;
+ bool print_all, print_mark;
+
+ print_all = true;
+ if (data->info == NULL) {
+ printf("added:\n");
+ print_mark = false;
+ }
+ else {
+ printf("changed:\n");
+ print_mark = true;
+ }
+
+ info = data->info = pw_node_info_update(data->info, info);
+
+ printf("\tid: %u\n", info->id);
+ printf("\ttype: %s\n", PIPEWIRE_TYPE__Node);
+ if (print_all) {
+ int i;
+
+ printf("%c\tname: \"%s\"\n", MARK_CHANGE(0), info->name);
+ printf("%c\tinput ports: %u/%u\n", MARK_CHANGE(1), info->n_input_ports, info->max_input_ports);
+ printf("%c\tinput formats:\n", MARK_CHANGE(2));
+ for (i = 0; i < info->n_input_formats; i++)
+ spa_debug_format(info->input_formats[i]);
+
+ printf("%c\toutput ports: %u/%u\n", MARK_CHANGE(3), info->n_output_ports, info->max_output_ports);
+ printf("%c\toutput formats:\n", MARK_CHANGE(4));
+ for (i = 0; i < info->n_output_formats; i++)
+ spa_debug_format(info->output_formats[i]);
+
+ printf("%c\tstate: \"%s\"", MARK_CHANGE(5), pw_node_state_as_string(info->state));
+ if (info->state == PW_NODE_STATE_ERROR && info->error)
+ printf(" \"%s\"\n", info->error);
+ else
+ printf("\n");
+ print_properties(info->props, MARK_CHANGE(6));
+ }
+}
+
+static const struct pw_node_events node_events = {
+ &node_event_info
+};
+
+static void client_event_info(void *object, struct pw_client_info *info)
+{
+ struct pw_proxy *proxy = object;
+ struct proxy_data *data = proxy->user_data;
+ bool print_all, print_mark;
+
+ print_all = true;
+ if (data->info == NULL) {
+ printf("added:\n");
+ print_mark = false;
+ }
+ else {
+ printf("changed:\n");
+ print_mark = true;
+ }
+
+ info = data->info = pw_client_info_update(data->info, info);
+
+ printf("\tid: %u\n", info->id);
+ printf("\ttype: %s\n", PIPEWIRE_TYPE__Client);
+ if (print_all) {
+ print_properties(info->props, MARK_CHANGE(0));
+ }
+}
+
+static const struct pw_client_events client_events = {
+ &client_event_info
+};
+
+static void link_event_info(void *object, struct pw_link_info *info)
+{
+ struct pw_proxy *proxy = object;
+ struct proxy_data *data = proxy->user_data;
+ bool print_all, print_mark;
+
+ print_all = true;
+ if (data->info == NULL) {
+ printf("added:\n");
+ print_mark = false;
+ }
+ else {
+ printf("changed:\n");
+ print_mark = true;
+ }
+
+ info = data->info = pw_link_info_update(data->info, info);
+
+ printf("\tid: %u\n", info->id);
+ printf("\ttype: %s\n", PIPEWIRE_TYPE__Link);
+ if (print_all) {
+ printf("%c\toutput-node-id: %u\n", MARK_CHANGE(0), info->output_node_id);
+ printf("%c\toutput-port-id: %u\n", MARK_CHANGE(1), info->output_port_id);
+ printf("%c\tinput-node-id: %u\n", MARK_CHANGE(2), info->input_node_id);
+ printf("%c\tinput-port-id: %u\n", MARK_CHANGE(3), info->input_port_id);
+ printf("%c\tformat:\n", MARK_CHANGE(4));
+ if (info->format)
+ spa_debug_format(info->format);
+ else
+ printf("\t none\n");
+ }
+}
+
+static const struct pw_link_events link_events = {
+ &link_event_info
+};
+
+static void
+destroy_proxy (void *data)
+{
+ struct pw_proxy *proxy = data;
+ struct proxy_data *user_data = proxy->user_data;
+ struct pw_core *core = proxy->remote->core;
+
+ if (user_data->info == NULL)
+ return;
+
+ if (proxy->type == core->type.core) {
+ pw_core_info_free (user_data->info);
+ } else if (proxy->type == core->type.node) {
+ pw_node_info_free (user_data->info);
+ } else if (proxy->type == core->type.module) {
+ pw_module_info_free (user_data->info);
+ } else if (proxy->type == core->type.client) {
+ pw_client_info_free (user_data->info);
+ } else if (proxy->type == core->type.link) {
+ pw_link_info_free (user_data->info);
+ }
+ user_data->info = NULL;
+}
+
+
+static void registry_event_global(void *object, uint32_t id, const char *type, uint32_t version)
+{
+ struct pw_proxy *registry_proxy = object;
+ struct data *data = registry_proxy->object;
+ struct pw_core *core = data->core;
+ struct pw_remote *remote = registry_proxy->remote;
+ struct pw_proxy *proxy = NULL;
+ uint32_t proxy_type, client_version;
+ const void *implementation;
+
+ if (!strcmp(type, PIPEWIRE_TYPE__Node)) {
+ proxy_type = core->type.node;
+ implementation = &node_events;
+ client_version = PW_VERSION_NODE;
+ } else if (!strcmp(type, PIPEWIRE_TYPE__Module)) {
+ proxy_type = core->type.module;
+ implementation = &module_events;
+ client_version = PW_VERSION_MODULE;
+ } else if (!strcmp(type, PIPEWIRE_TYPE__Client)) {
+ proxy_type = core->type.client;
+ implementation = &client_events;
+ client_version = PW_VERSION_CLIENT;
+ } else if (!strcmp(type, PIPEWIRE_TYPE__Link)) {
+ proxy_type = core->type.link;
+ implementation = &link_events;
+ client_version = PW_VERSION_LINK;
+ } else
+ return;
+
+ proxy = pw_proxy_new(remote, SPA_ID_INVALID, proxy_type, sizeof(struct proxy_data));
+ if (proxy == NULL)
+ goto no_mem;
+
+ pw_proxy_set_implementation(proxy, data, client_version, implementation, destroy_proxy);
+
+ pw_registry_do_bind(registry_proxy, id, version, proxy->id);
+
+ return;
+
+ no_mem:
+ printf("failed to create proxy");
+ return;
+}
+
+static void registry_event_global_remove(void *object, uint32_t id)
+{
+ printf("removed:\n");
+ printf("\tid: %u\n", id);
+}
+
+static const struct pw_registry_events registry_events = {
+ registry_event_global,
+ registry_event_global_remove,
+};
+
+static void on_state_changed(struct pw_listener *listener, struct pw_remote *remote)
+{
+ struct data *data = SPA_CONTAINER_OF(listener, struct data, on_state_changed);
+
+ switch (remote->state) {
+ case PW_REMOTE_STATE_ERROR:
+ printf("remote error: %s\n", remote->error);
+ data->running = false;
+ break;
+
+ case PW_REMOTE_STATE_CONNECTED:
+ printf("remote state: \"%s\"\n", pw_remote_state_as_string(remote->state));
+
+ data->registry_proxy = pw_proxy_new(data->remote,
+ SPA_ID_INVALID,
+ data->core->type.registry,
+ 0);
+ pw_proxy_set_implementation(data->registry_proxy, data, PW_VERSION_REGISTRY,
+ &registry_events, NULL);
+
+ pw_core_do_get_registry(data->remote->core_proxy,
+ data->registry_proxy->id);
+
+ break;
+
+ default:
+ printf("remote state: \"%s\"\n", pw_remote_state_as_string(remote->state));
+ break;
+ }
+}
+
+int main(int argc, char *argv[])
+{
+ struct data data = { 0 };
+
+ pw_init(&argc, &argv);
+
+ data.loop = pw_loop_new();
+ data.running = true;
+ data.core = pw_core_new(data.loop, NULL);
+ data.remote = pw_remote_new(data.core, NULL);
+
+ pw_signal_add(&data.remote->info_changed, &data.on_info_changed, on_info_changed);
+ pw_signal_add(&data.remote->state_changed, &data.on_state_changed, on_state_changed);
+
+ pw_remote_connect(data.remote);
+
+ pw_loop_enter(data.loop);
+ while (data.running) {
+ pw_loop_iterate(data.loop, -1);
+ }
+ pw_loop_leave(data.loop);
+
+ pw_remote_destroy(data.remote);
+ pw_loop_destroy(data.loop);
+
+ return 0;
+}