summaryrefslogtreecommitdiffstats
path: root/meta-agl-devel/meta-pipewire
diff options
context:
space:
mode:
Diffstat (limited to 'meta-agl-devel/meta-pipewire')
-rw-r--r--meta-agl-devel/meta-pipewire/conf/include/agl-pipewire.inc3
-rw-r--r--meta-agl-devel/meta-pipewire/conf/layer.conf12
-rw-r--r--meta-agl-devel/meta-pipewire/recipes-connectivity/bluez-alsa/bluez-alsa/0001-utils-add-a-gstreamer-helper-application-for-interco.patch464
-rw-r--r--meta-agl-devel/meta-pipewire/recipes-connectivity/bluez-alsa/bluez-alsa/bluealsa-gst-helper@.service18
-rw-r--r--meta-agl-devel/meta-pipewire/recipes-connectivity/bluez-alsa/bluez-alsa_git.bbappend35
-rw-r--r--meta-agl-devel/meta-pipewire/recipes-core/packagegroups/packagegroup-pipewire.bb17
-rw-r--r--meta-agl-devel/meta-pipewire/recipes-multimedia/agl-service-audiomixer/agl-service-audiomixer_git.bb16
-rw-r--r--meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire-conf-agl/client.env10
-rw-r--r--meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire-conf-agl/pipewire.conf10
-rw-r--r--meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire-conf-agl/server.env12
-rw-r--r--meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire-conf-agl_git.bb40
-rw-r--r--meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire.inc117
-rw-r--r--meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/0001-spa-include-install-missing-headers.patch41
-rw-r--r--meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/0002-extensions-implement-Endpoint-ClientEndpoint-interfa.patch1563
-rw-r--r--meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/0003-pipewire-cli-add-support-for-printing-endpoint-info-.patch149
-rw-r--r--meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/0004-pipewire-cli-add-command-to-modify-endpoint-control-.patch124
-rw-r--r--meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/0005-arm-build-with-mno-unaligned-access.patch30
-rw-r--r--meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/0006-logger-print-timestamps-on-logged-messages.patch52
-rw-r--r--meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/0007-alsa-make-corrections-on-the-timeout-based-on-how-fa.patch130
-rw-r--r--meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/0008-audio-dsp-allow-mode-to-be-set-with-a-property.patch45
-rw-r--r--meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/0009-audioconvert-do-setup-internal-links-and-buffers-als.patch55
-rw-r--r--meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/0010-gst-Implement-new-pwaudio-src-sink-elements-based-on.patch1249
-rw-r--r--meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/0011-gst-pwaudioringbuffer-make-the-buffer-size-sensitive.patch60
-rw-r--r--meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/0012-gst-pwaudioringbuffer-request-pause-play-on-the-appr.patch76
-rw-r--r--meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/0013-gst-pwaudioringbuffer-wait-only-for-STREAM_STATE_CON.patch35
-rw-r--r--meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/0014-gst-pwaudiosink-set-the-default-latency-time-buffer-.patch37
-rw-r--r--meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/0015-audioconvert-fmtconvert-assume-F32-on-the-other-port.patch44
-rw-r--r--meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/0016-gst-pwaudioringbuffer-set-node.latency-to-get-schedu.patch41
-rw-r--r--meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/0017-connection-move-remaining-data-and-fds.patch61
-rw-r--r--meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/0018-protocol-improve-flushing.patch222
-rw-r--r--meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/pipewire@.service24
-rw-r--r--meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/pipewire@.socket19
-rw-r--r--meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/smack-pipewire8
-rw-r--r--meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire_git.bb29
-rw-r--r--meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire_git.bbappend30
-rw-r--r--meta-agl-devel/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/wireplumber.conf.in55
-rw-r--r--meta-agl-devel/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl_git.bb69
-rw-r--r--meta-agl-devel/meta-pipewire/recipes-multimedia/wireplumber/wireplumber_git.bb36
-rw-r--r--meta-agl-devel/meta-pipewire/recipes-security/security-manager/security-manager/0001-Adapt-smack-rules-to-allow-connections-to-pipewire.patch25
-rw-r--r--meta-agl-devel/meta-pipewire/recipes-security/security-manager/security-manager_%.bbappend2
40 files changed, 5065 insertions, 0 deletions
diff --git a/meta-agl-devel/meta-pipewire/conf/include/agl-pipewire.inc b/meta-agl-devel/meta-pipewire/conf/include/agl-pipewire.inc
new file mode 100644
index 00000000..edd89311
--- /dev/null
+++ b/meta-agl-devel/meta-pipewire/conf/include/agl-pipewire.inc
@@ -0,0 +1,3 @@
+DISTRO_FEATURES_append = " pipewire"
+PREFERRED_RPROVIDER_virtual/pipewire-config = "pipewire-conf-agl"
+PREFERRED_RPROVIDER_virtual/wireplumber-config = "wireplumber-board-config-agl"
diff --git a/meta-agl-devel/meta-pipewire/conf/layer.conf b/meta-agl-devel/meta-pipewire/conf/layer.conf
new file mode 100644
index 00000000..8ad06e3e
--- /dev/null
+++ b/meta-agl-devel/meta-pipewire/conf/layer.conf
@@ -0,0 +1,12 @@
+# We have a conf and classes directory, add to BBPATH
+BBPATH .= ":${LAYERDIR}"
+
+# We have recipes-* directories, add to BBFILES
+BBFILES += "${LAYERDIR}/recipes-*/*/*.bb \
+ ${LAYERDIR}/recipes-*/*/*.bbappend"
+
+BBFILE_COLLECTIONS += "meta-pipewire"
+BBFILE_PATTERN_meta-pipewire = "^${LAYERDIR}/"
+BBFILE_PRIORITY_meta-pipewire = "71"
+
+LAYERSERIES_COMPAT_meta-pipewire = "thud"
diff --git a/meta-agl-devel/meta-pipewire/recipes-connectivity/bluez-alsa/bluez-alsa/0001-utils-add-a-gstreamer-helper-application-for-interco.patch b/meta-agl-devel/meta-pipewire/recipes-connectivity/bluez-alsa/bluez-alsa/0001-utils-add-a-gstreamer-helper-application-for-interco.patch
new file mode 100644
index 00000000..37c03218
--- /dev/null
+++ b/meta-agl-devel/meta-pipewire/recipes-connectivity/bluez-alsa/bluez-alsa/0001-utils-add-a-gstreamer-helper-application-for-interco.patch
@@ -0,0 +1,464 @@
+From 33555a493af67f3acc2129764a1b093aec6254d8 Mon Sep 17 00:00:00 2001
+From: George Kiagiadakis <george.kiagiadakis@collabora.com>
+Date: Fri, 4 Oct 2019 20:51:24 +0300
+Subject: [PATCH] utils: add a gstreamer helper application for interconnection
+ with pipewire
+
+Unfortunately, the bluez-alsa PCM plugin does not work correctly
+when it is used through pipewire (or gstreamer, or anywhere really...).
+
+Thanfully, the bluez-alsa PCM plugin is only a simple client that
+reads/writes on a file descriptor that was opened by bluealsa.
+This allows us to use bluealsa without the PCM plugin, just like it
+is done in the aplay.c util.
+
+This one uses GStreamer to implement the plumbing between pipewire
+and the file descriptor. On the reading side we are also doing some
+tricks to ensure a smooth stream, which is not the case for the
+stream that is coming out of bluealsa.
+
+This helper is implemented as a patch to bluez-alsa so that it can
+use its internal private API. In the future this needs some re-thinking.
+
+Upstream-Status: Inappropriate
+---
+ configure.ac | 7 +
+ utils/Makefile.am | 20 +++
+ utils/gst-helper.c | 379 +++++++++++++++++++++++++++++++++++++++++++++
+ 3 files changed, 406 insertions(+)
+ create mode 100644 utils/gst-helper.c
+
+diff --git a/configure.ac b/configure.ac
+index 4825afa..9125871 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -141,6 +141,13 @@ AM_COND_IF([ENABLE_HCITOP], [
+ PKG_CHECK_MODULES([NCURSES], [ncurses])
+ ])
+
++AC_ARG_ENABLE([gsthelper],
++ [AS_HELP_STRING([--enable-gsthelper], [enable building of gsthelper tool])])
++AM_CONDITIONAL([ENABLE_GSTHELPER], [test "x$enable_gsthelper" = "xyes"])
++AM_COND_IF([ENABLE_GSTHELPER], [
++ PKG_CHECK_MODULES([GST], [gstreamer-1.0 glib-2.0])
++])
++
+ AC_ARG_ENABLE([test],
+ [AS_HELP_STRING([--enable-test], [enable unit test])])
+ AM_CONDITIONAL([ENABLE_TEST], [test "x$enable_test" = "xyes"])
+diff --git a/utils/Makefile.am b/utils/Makefile.am
+index 9057f2c..9790474 100644
+--- a/utils/Makefile.am
++++ b/utils/Makefile.am
+@@ -47,3 +47,23 @@ hcitop_LDADD = \
+ @LIBBSD_LIBS@ \
+ @NCURSES_LIBS@
+ endif
++
++if ENABLE_GSTHELPER
++bin_PROGRAMS += bluealsa-gst-helper
++bluealsa_gst_helper_SOURCES = \
++ ../src/shared/dbus-client.c \
++ ../src/shared/ffb.c \
++ ../src/shared/log.c \
++ gst-helper.c
++bluealsa_gst_helper_CFLAGS = \
++ -I$(top_srcdir)/src \
++ @ALSA_CFLAGS@ \
++ @BLUEZ_CFLAGS@ \
++ @DBUS1_CFLAGS@ \
++ @GST_CFLAGS@
++bluealsa_gst_helper_LDADD = \
++ @ALSA_LIBS@ \
++ @BLUEZ_LIBS@ \
++ @DBUS1_LIBS@ \
++ @GST_LIBS@
++endif
+diff --git a/utils/gst-helper.c b/utils/gst-helper.c
+new file mode 100644
+index 0000000..1b021ee
+--- /dev/null
++++ b/utils/gst-helper.c
+@@ -0,0 +1,379 @@
++/* Bluez-Alsa PipeWire integration GStreamer helper
++ *
++ * Copyright © 2016-2019 Arkadiusz Bokowy
++ * Copyright © 2019 Collabora Ltd.
++ * @author George Kiagiadakis <george.kiagiadakis@collabora.com>
++ *
++ * SPDX-License-Identifier: MIT
++ */
++
++#if HAVE_CONFIG_H
++# include <config.h>
++#endif
++
++#include <errno.h>
++#include <getopt.h>
++#include <poll.h>
++#include <pthread.h>
++#include <signal.h>
++#include <stdbool.h>
++#include <stdint.h>
++#include <stdio.h>
++#include <stdlib.h>
++#include <string.h>
++#include <unistd.h>
++
++#include <bluetooth/bluetooth.h>
++#include <dbus/dbus.h>
++#include <gst/gst.h>
++
++#include "shared/dbus-client.h"
++#include "shared/defs.h"
++#include "shared/ffb.h"
++#include "shared/log.h"
++
++struct worker {
++ /* used BlueALSA PCM device */
++ struct ba_pcm ba_pcm;
++ /* file descriptor of PCM FIFO */
++ int ba_pcm_fd;
++ /* file descriptor of PCM control */
++ int ba_pcm_ctrl_fd;
++ /* the gstreamer pipelines (sink & source) */
++ GstElement *pipeline[2];
++};
++
++static struct ba_dbus_ctx dbus_ctx;
++static GHashTable *workers;
++static bool main_loop_on = true;
++
++static void
++main_loop_stop(int sig)
++{
++ /* Call to this handler restores the default action, so on the
++ * second call the program will be forcefully terminated. */
++
++ struct sigaction sigact = { .sa_handler = SIG_DFL };
++ sigaction(sig, &sigact, NULL);
++
++ main_loop_on = false;
++}
++
++static int
++worker_start_pipeline(struct worker *w, int id, int mode, int profile)
++{
++ GError *gerr = NULL;
++ DBusError err = DBUS_ERROR_INIT;
++
++ if (w->pipeline[id])
++ return 0;
++
++ if (!bluealsa_dbus_pcm_open(&dbus_ctx, w->ba_pcm.pcm_path, mode,
++ &w->ba_pcm_fd, &w->ba_pcm_ctrl_fd, &err)) {
++ error("Couldn't open PCM: %s", err.message);
++ dbus_error_free(&err);
++ goto fail;
++ }
++
++ if (mode == BA_PCM_FLAG_SINK) {
++ debug("sink start");
++ w->pipeline[id] = gst_parse_launch(
++ /* add a silent live source to ensure a perfect live stream on the
++ output, even when the bt device is not sending or has gaps;
++ this also effectively changes the clock to be the system clock,
++ which is the same clock used by bluez-alsa on the sending side */
++ "audiotestsrc is-live=true wave=silence ! capsfilter name=capsf "
++ "! audiomixer name=m "
++ /* mix the input from bluez-alsa using fdsrc; rawaudioparse
++ is necessary to convert bytes to time and align the buffers */
++ "fdsrc name=fdelem do-timestamp=true ! capsfilter name=capsf2 "
++ "! rawaudioparse use-sink-caps=true ! m. "
++ /* take the mixer output, convert and push to pipewire */
++ "m.src ! capsfilter name=capsf3 ! audioconvert ! audioresample "
++ "! audio/x-raw,format=F32LE,rate=48000 ! pwaudiosink name=pwelem",
++ &gerr);
++ } else if (mode == BA_PCM_FLAG_SOURCE && profile == BA_PCM_FLAG_PROFILE_SCO) {
++ debug("source start");
++ w->pipeline[id] = gst_parse_launch(
++ /* read from pipewire and put the buffers on a leaky queue, which
++ will essentially allow pwaudiosrc to continue working while
++ the fdsink is blocked (when there is no phone call in progress).
++ 9600 bytes = 50ms @ F32LE/1ch/48000
++ */
++ "pwaudiosrc name=pwelem ! audio/x-raw,format=F32LE,rate=48000 "
++ "! queue leaky=downstream max-size-time=0 max-size-buffers=0 max-size-bytes=9600 "
++ "! audioconvert ! audioresample ! capsfilter name=capsf "
++ "! fdsink name=fdelem", &gerr);
++ }
++
++ if (gerr) {
++ error("Failed to start pipeline: %s", gerr->message);
++ g_error_free(gerr);
++ goto fail;
++ }
++
++ if (w->pipeline[id]) {
++ g_autofree gchar *capsstr = NULL;
++ g_autoptr (GstElement) fdelem = gst_bin_get_by_name(GST_BIN(w->pipeline[id]), "fdelem");
++ g_autoptr (GstElement) pwelem = gst_bin_get_by_name(GST_BIN(w->pipeline[id]), "pwelem");
++ g_autoptr (GstElement) capsf = gst_bin_get_by_name(GST_BIN(w->pipeline[id]), "capsf");
++ g_autoptr (GstElement) capsf2 = gst_bin_get_by_name(GST_BIN(w->pipeline[id]), "capsf2");
++ g_autoptr (GstElement) capsf3 = gst_bin_get_by_name(GST_BIN(w->pipeline[id]), "capsf3");
++ g_autoptr (GstCaps) caps = gst_caps_new_simple("audio/x-raw",
++ "format", G_TYPE_STRING, "S16LE",
++ "layout", G_TYPE_STRING, "interleaved",
++ "channels", G_TYPE_INT, w->ba_pcm.channels,
++ "rate", G_TYPE_INT, w->ba_pcm.sampling,
++ NULL);
++ g_autoptr (GstStructure) stream_props = gst_structure_new("props",
++ "media.role", G_TYPE_STRING, "Communication",
++ "wireplumber.keep-linked", G_TYPE_STRING, "1",
++ NULL);
++
++ g_object_set(capsf, "caps", caps, NULL);
++ if (capsf2)
++ g_object_set(capsf2, "caps", caps, NULL);
++ if (capsf3)
++ g_object_set(capsf3, "caps", caps, NULL);
++
++ capsstr = gst_caps_to_string (caps);
++ debug(" caps: %s", capsstr);
++
++ g_object_set(fdelem, "fd", w->ba_pcm_fd, NULL);
++ g_object_set(pwelem, "stream-properties", stream_props, NULL);
++
++ gst_element_set_state(w->pipeline[id], GST_STATE_PLAYING);
++ }
++
++ return 0;
++fail:
++ g_clear_object(&w->pipeline[id]);
++ return -1;
++}
++
++static int
++worker_start(struct worker *w)
++{
++ int mode = w->ba_pcm.flags & (BA_PCM_FLAG_SOURCE | BA_PCM_FLAG_SINK);
++ int profile = w->ba_pcm.flags & (BA_PCM_FLAG_PROFILE_A2DP | BA_PCM_FLAG_PROFILE_SCO);
++ /* human-readable BT address */
++ char addr[18];
++
++ g_return_val_if_fail (profile != 0 && profile != (BA_PCM_FLAG_PROFILE_A2DP | BA_PCM_FLAG_PROFILE_SCO), -1);
++
++ ba2str(&w->ba_pcm.addr, addr);
++ debug("%p: worker start addr:%s, mode:0x%x, profile:0x%x", w, addr, mode, profile);
++
++ if (mode & BA_PCM_FLAG_SINK)
++ worker_start_pipeline(w, 0, BA_PCM_FLAG_SINK, profile);
++ if (mode & BA_PCM_FLAG_SOURCE)
++ worker_start_pipeline(w, 1, BA_PCM_FLAG_SOURCE, profile);
++}
++
++static int
++worker_stop(struct worker *w)
++{
++ debug("stop worker %p", w);
++ if (w->pipeline[0]) {
++ gst_element_set_state(w->pipeline[0], GST_STATE_NULL);
++ g_clear_object(&w->pipeline[0]);
++ }
++ if (w->pipeline[1]) {
++ gst_element_set_state(w->pipeline[1], GST_STATE_NULL);
++ g_clear_object(&w->pipeline[1]);
++ }
++ if (w->ba_pcm_fd != -1) {
++ close(w->ba_pcm_fd);
++ w->ba_pcm_fd = -1;
++ }
++ if (w->ba_pcm_ctrl_fd != -1) {
++ close(w->ba_pcm_ctrl_fd);
++ w->ba_pcm_ctrl_fd = -1;
++ }
++ return 0;
++}
++
++static int
++supervise_pcm_worker(struct worker *worker)
++{
++ if (worker == NULL)
++ return -1;
++
++ /* no mode? */
++ if (worker->ba_pcm.flags & (BA_PCM_FLAG_SOURCE | BA_PCM_FLAG_SINK) == 0)
++ goto stop;
++
++ /* no profile? */
++ if (worker->ba_pcm.flags & (BA_PCM_FLAG_PROFILE_A2DP | BA_PCM_FLAG_PROFILE_SCO) == 0)
++ goto stop;
++
++ /* check whether SCO has selected codec */
++ if (worker->ba_pcm.flags & BA_PCM_FLAG_PROFILE_SCO &&
++ worker->ba_pcm.codec == 0) {
++ debug("Skipping SCO with codec not selected");
++ goto stop;
++ }
++
++start:
++ return worker_start(worker);
++stop:
++ return worker_stop(worker);
++}
++
++static void
++worker_new(struct ba_pcm *pcm)
++{
++ struct worker *w = g_slice_new0 (struct worker);
++ memcpy(&w->ba_pcm, pcm, sizeof(struct ba_pcm));
++ w->ba_pcm_fd = -1;
++ w->ba_pcm_ctrl_fd = -1;
++ g_hash_table_insert(workers, w->ba_pcm.pcm_path, w);
++ supervise_pcm_worker(w);
++}
++
++static DBusHandlerResult
++dbus_signal_handler(DBusConnection *conn, DBusMessage *message, void *data)
++{
++ (void)conn;
++ (void)data;
++
++ const char *path = dbus_message_get_path(message);
++ const char *interface = dbus_message_get_interface(message);
++ const char *signal = dbus_message_get_member(message);
++
++ DBusMessageIter iter;
++ struct worker *worker;
++
++ if (strcmp(interface, BLUEALSA_INTERFACE_MANAGER) == 0) {
++
++ if (strcmp(signal, "PCMAdded") == 0) {
++ struct ba_pcm pcm;
++ if (!dbus_message_iter_init(message, &iter) ||
++ !bluealsa_dbus_message_iter_get_pcm(&iter, NULL, &pcm)) {
++ error("Couldn't add new PCM: %s", "Invalid signal signature");
++ goto fail;
++ }
++ worker_new(&pcm);
++ return DBUS_HANDLER_RESULT_HANDLED;
++ }
++
++ if (strcmp(signal, "PCMRemoved") == 0) {
++ if (!dbus_message_iter_init(message, &iter) ||
++ dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_OBJECT_PATH) {
++ error("Couldn't remove PCM: %s", "Invalid signal signature");
++ goto fail;
++ }
++ dbus_message_iter_get_basic(&iter, &path);
++ g_hash_table_remove(workers, path);
++ return DBUS_HANDLER_RESULT_HANDLED;
++ }
++
++ }
++
++ if (strcmp(interface, DBUS_INTERFACE_PROPERTIES) == 0) {
++ worker = g_hash_table_lookup(workers, path);
++ if (!worker)
++ goto fail;
++ if (!dbus_message_iter_init(message, &iter) ||
++ dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) {
++ error("Couldn't update PCM: %s", "Invalid signal signature");
++ goto fail;
++ }
++ dbus_message_iter_get_basic(&iter, &interface);
++ dbus_message_iter_next(&iter);
++ if (!bluealsa_dbus_message_iter_get_pcm_props(&iter, NULL, &worker->ba_pcm))
++ goto fail;
++ supervise_pcm_worker(worker);
++ return DBUS_HANDLER_RESULT_HANDLED;
++ }
++
++fail:
++ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
++}
++
++static void
++destroy_worker(void *worker)
++{
++ struct worker *w = worker;
++ worker_stop(w);
++ g_slice_free(struct worker, w);
++}
++
++int
++main(int argc, char *argv[])
++{
++ int ret = EXIT_SUCCESS;
++
++ log_open(argv[0], false, false);
++ gst_init(&argc, &argv);
++ dbus_threads_init_default();
++
++ DBusError err = DBUS_ERROR_INIT;
++ if (!bluealsa_dbus_connection_ctx_init(&dbus_ctx, BLUEALSA_SERVICE, &err)) {
++ error("Couldn't initialize D-Bus context: %s", err.message);
++ return EXIT_FAILURE;
++ }
++
++ bluealsa_dbus_connection_signal_match_add(&dbus_ctx,
++ BLUEALSA_SERVICE, NULL, BLUEALSA_INTERFACE_MANAGER, "PCMAdded", NULL);
++ bluealsa_dbus_connection_signal_match_add(&dbus_ctx,
++ BLUEALSA_SERVICE, NULL, BLUEALSA_INTERFACE_MANAGER, "PCMRemoved", NULL);
++ bluealsa_dbus_connection_signal_match_add(&dbus_ctx,
++ BLUEALSA_SERVICE, NULL, DBUS_INTERFACE_PROPERTIES, "PropertiesChanged",
++ "arg0='"BLUEALSA_INTERFACE_PCM"'");
++
++ if (!dbus_connection_add_filter(dbus_ctx.conn, dbus_signal_handler, NULL, NULL)) {
++ error("Couldn't add D-Bus filter: %s", err.message);
++ return EXIT_FAILURE;
++ }
++
++ workers = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, destroy_worker);
++
++ {
++ struct ba_pcm *pcms = NULL;
++ size_t pcms_count = 0, i;
++
++ if (!bluealsa_dbus_get_pcms(&dbus_ctx, &pcms, &pcms_count, &err))
++ warn("Couldn't get BlueALSA PCM list: %s", err.message);
++
++ for (i = 0; i < pcms_count; i++) {
++ worker_new(&pcms[i]);
++ }
++
++ free(pcms);
++ }
++
++ struct sigaction sigact = { .sa_handler = main_loop_stop };
++ sigaction(SIGTERM, &sigact, NULL);
++ sigaction(SIGINT, &sigact, NULL);
++
++ /* Ignore SIGPIPE, which may be received when writing to the bluealsa
++ socket when it is closed on the remote end */
++ signal(SIGPIPE, SIG_IGN);
++
++ debug("Starting main loop");
++ while (main_loop_on) {
++
++ struct pollfd pfds[10];
++ nfds_t pfds_len = ARRAYSIZE(pfds);
++
++ if (!bluealsa_dbus_connection_poll_fds(&dbus_ctx, pfds, &pfds_len)) {
++ error("Couldn't get D-Bus connection file descriptors");
++ ret = EXIT_FAILURE;
++ goto out;
++ }
++
++ if (poll(pfds, pfds_len, -1) == -1 &&
++ errno == EINTR)
++ continue;
++
++ if (bluealsa_dbus_connection_poll_dispatch(&dbus_ctx, pfds, pfds_len))
++ while (dbus_connection_dispatch(dbus_ctx.conn) == DBUS_DISPATCH_DATA_REMAINS)
++ continue;
++
++ }
++
++out:
++ g_hash_table_unref(workers);
++ return ret;
++}
+--
+2.23.0
+
diff --git a/meta-agl-devel/meta-pipewire/recipes-connectivity/bluez-alsa/bluez-alsa/bluealsa-gst-helper@.service b/meta-agl-devel/meta-pipewire/recipes-connectivity/bluez-alsa/bluez-alsa/bluealsa-gst-helper@.service
new file mode 100644
index 00000000..495ab622
--- /dev/null
+++ b/meta-agl-devel/meta-pipewire/recipes-connectivity/bluez-alsa/bluez-alsa/bluealsa-gst-helper@.service
@@ -0,0 +1,18 @@
+[Unit]
+Description=Bluetooth audio helper for user %i
+Requires=pipewire@%i.socket bluez-alsa.service
+After=pipewire@%i.socket bluez-alsa.service
+
+[Service]
+Type=simple
+Restart=on-failure
+ExecStart=/usr/bin/bluealsa-gst-helper
+
+Environment=XDG_RUNTIME_DIR=/run/user/%i
+Environment=DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/%i/bus
+
+User=%i
+Slice=user-%i.slice
+SupplementaryGroups=audio
+UMask=0077
+CapabilityBoundingSet=
diff --git a/meta-agl-devel/meta-pipewire/recipes-connectivity/bluez-alsa/bluez-alsa_git.bbappend b/meta-agl-devel/meta-pipewire/recipes-connectivity/bluez-alsa/bluez-alsa_git.bbappend
new file mode 100644
index 00000000..2f9699a8
--- /dev/null
+++ b/meta-agl-devel/meta-pipewire/recipes-connectivity/bluez-alsa/bluez-alsa_git.bbappend
@@ -0,0 +1,35 @@
+FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:"
+
+SRC_URI += "\
+ file://0001-utils-add-a-gstreamer-helper-application-for-interco.patch \
+ file://bluealsa-gst-helper@.service \
+ "
+
+PACKAGECONFIG += "gsthelper"
+PACKAGECONFIG[gsthelper] = "--enable-gsthelper, --disable-gsthelper, gstreamer1.0"
+
+do_install_append() {
+ if ${@bb.utils.contains('DISTRO_FEATURES', 'systemd', 'true', 'false', d)}; then
+ # install the service file
+ mkdir -p ${D}${systemd_system_unitdir}/
+ install -m 0644 ${WORKDIR}/bluealsa-gst-helper@.service ${D}${systemd_system_unitdir}/bluealsa-gst-helper@.service
+
+ # enable the helper to start together with afm-user-session
+ mkdir -p ${D}${systemd_system_unitdir}/afm-user-session@.target.wants
+ ln -sf ../bluealsa-gst-helper@.service ${D}${systemd_system_unitdir}/afm-user-session@.target.wants/bluealsa-gst-helper@.service
+ fi
+}
+
+PACKAGES =+ "${PN}-pipewire"
+
+FILES_${PN}-pipewire = "\
+ ${bindir}/bluealsa-gst-helper \
+ ${systemd_system_unitdir}/bluealsa-gst-helper@.service \
+ ${systemd_system_unitdir}/afm-user-session@.target.wants/bluealsa-gst-helper@.service \
+ "
+RDEPENDS_${PN}-pipewire += "\
+ bluez-alsa \
+ pipewire \
+ gstreamer1.0-plugins-base \
+ gstreamer1.0-pipewire \
+ "
diff --git a/meta-agl-devel/meta-pipewire/recipes-core/packagegroups/packagegroup-pipewire.bb b/meta-agl-devel/meta-pipewire/recipes-core/packagegroups/packagegroup-pipewire.bb
new file mode 100644
index 00000000..4020f1e2
--- /dev/null
+++ b/meta-agl-devel/meta-pipewire/recipes-core/packagegroups/packagegroup-pipewire.bb
@@ -0,0 +1,17 @@
+SUMMARY = "PipeWire Media Server"
+DESCRIPTION = "The set of packages required to use PipeWire in AGL"
+LICENSE = "MIT & LGPL-2.1"
+
+inherit packagegroup
+
+PACKAGES = "\
+ packagegroup-pipewire \
+ "
+
+RDEPENDS_${PN} += "\
+ agl-service-audiomixer \
+ pipewire \
+ pipewire-alsa \
+ gstreamer1.0-pipewire \
+ bluez-alsa-pipewire \
+"
diff --git a/meta-agl-devel/meta-pipewire/recipes-multimedia/agl-service-audiomixer/agl-service-audiomixer_git.bb b/meta-agl-devel/meta-pipewire/recipes-multimedia/agl-service-audiomixer/agl-service-audiomixer_git.bb
new file mode 100644
index 00000000..c4de73e7
--- /dev/null
+++ b/meta-agl-devel/meta-pipewire/recipes-multimedia/agl-service-audiomixer/agl-service-audiomixer_git.bb
@@ -0,0 +1,16 @@
+SUMMARY = "Audio Mixer Service Binding"
+DESCRIPTION = "AGL Audio Mixer Service Binding"
+SECTION = "apps"
+LICENSE = "MIT"
+LIC_FILES_CHKSUM = "file://LICENSE;beginline=3;md5=e8ad01a5182f2c1b3a2640e9ea268264"
+
+PV = "0.1+git${SRCPV}"
+
+SRC_URI = "git://gerrit.automotivelinux.org/gerrit/apps/agl-service-audiomixer.git;protocol=https;branch=${AGL_BRANCH}"
+SRCREV = "${AGL_APP_REVISION}"
+
+S = "${WORKDIR}/git"
+
+inherit cmake aglwgt pkgconfig
+
+DEPENDS += "pipewire"
diff --git a/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire-conf-agl/client.env b/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire-conf-agl/client.env
new file mode 100644
index 00000000..9b44cee0
--- /dev/null
+++ b/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire-conf-agl/client.env
@@ -0,0 +1,10 @@
+# This file contains environment variables that will apply
+# to pipewire clients started by the application framework
+
+# libpipewire by default tries to obtain real-time scheduling
+# for the streaming thread. This is only useful on the desktop, disable here.
+DISABLE_RTKIT=1
+
+# Uncomment to enable libpipewire debug for clients
+# 1=error, 2=warning, 3=info, 4=debug, 5=trace
+#PIPEWIRE_DEBUG=4
diff --git a/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire-conf-agl/pipewire.conf b/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire-conf-agl/pipewire.conf
new file mode 100644
index 00000000..d09ee8ed
--- /dev/null
+++ b/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire-conf-agl/pipewire.conf
@@ -0,0 +1,10 @@
+# daemon config file for PipeWire version "0.2.9"
+# distributed by Automotive Grade Linux
+load-module libpipewire-module-protocol-native
+load-module libpipewire-module-spa-monitor alsa/libspa-alsa alsa-monitor alsa
+load-module libpipewire-module-client-node
+load-module libpipewire-module-access
+load-module libpipewire-module-audio-dsp
+load-module libpipewire-module-link-factory
+load-module libpipewire-module-endpoint
+exec /usr/bin/wireplumber
diff --git a/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire-conf-agl/server.env b/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire-conf-agl/server.env
new file mode 100644
index 00000000..c74b941d
--- /dev/null
+++ b/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire-conf-agl/server.env
@@ -0,0 +1,12 @@
+# This file contains environment variables that will apply
+# to the pipewire daemon as well as its session manager
+
+# Disable rtkit for wireplumber, which is also a client
+DISABLE_RTKIT=1
+
+# Uncomment to enable wireplumber debug
+#G_MESSAGES_DEBUG=all
+
+# Uncomment to enable pipewire debug
+# 1=error, 2=warning, 3=info, 4=debug, 5=trace
+#PIPEWIRE_DEBUG=4
diff --git a/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire-conf-agl_git.bb b/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire-conf-agl_git.bb
new file mode 100644
index 00000000..2bb76f9d
--- /dev/null
+++ b/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire-conf-agl_git.bb
@@ -0,0 +1,40 @@
+SUMMARY = "AGL configuration file for pipewire"
+HOMEPAGE = "https://pipewire.org"
+BUGTRACKER = "https://jira.automotivelinux.org"
+AUTHOR = "George Kiagiadakis <george.kiagiadakis@collabora.com>"
+SECTION = "multimedia"
+
+LICENSE = "MIT"
+LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"
+
+SRC_URI = " \
+ file://pipewire.conf \
+ file://client.env \
+ file://server.env \
+ "
+
+do_configure[noexec] = "1"
+do_compile[noexec] = "1"
+
+do_install_append() {
+ # if we are distributing our own configuration file,
+ # replace the one installed by pipewire
+ install -d ${D}/${sysconfdir}/pipewire/
+ install -m 0644 ${WORKDIR}/pipewire.conf ${D}${sysconfdir}/pipewire/pipewire.conf
+
+ # install environment variable files
+ install -d ${D}/${sysconfdir}/afm/unit.env.d/
+ install -m 0644 ${WORKDIR}/client.env ${D}/${sysconfdir}/afm/unit.env.d/pipewire
+ install -m 0644 ${WORKDIR}/server.env ${D}${sysconfdir}/pipewire/environment
+}
+
+FILES_${PN} = "\
+ ${sysconfdir}/pipewire/* \
+ ${sysconfdir}/afm/unit.env.d/* \
+"
+CONFFILES_${PN} += "\
+ ${sysconfdir}/pipewire/* \
+ ${sysconfdir}/afm/unit.env.d/* \
+"
+
+RPROVIDES_${PN} += "virtual/pipewire-config"
diff --git a/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire.inc b/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire.inc
new file mode 100644
index 00000000..e9046e8e
--- /dev/null
+++ b/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire.inc
@@ -0,0 +1,117 @@
+SUMMARY = "Multimedia processing server for Linux"
+HOMEPAGE = "https://pipewire.org"
+BUGTRACKER = "https://github.com/PipeWire/pipewire/issues"
+AUTHOR = "Wim Taymans <wtaymans@redhat.com>"
+SECTION = "multimedia"
+
+LICENSE = "MIT & LGPL-2.1"
+LIC_FILES_CHKSUM = "\
+ file://COPYING;beginline=3;md5=b3adc775ca6ee80056383a5ae814cc75 \
+ file://pipewire-alsa/LICENSE;md5=fc178bcd425090939a8b634d1d6a9594 \
+ file://pipewire-jack/LICENSE;md5=fc178bcd425090939a8b634d1d6a9594 \
+ file://pipewire-pulseaudio/LICENSE;md5=fc178bcd425090939a8b634d1d6a9594 \
+"
+
+inherit meson pkgconfig systemd manpages
+
+DEPENDS = "dbus"
+
+PACKAGECONFIG ??= "\
+ ${@bb.utils.filter('DISTRO_FEATURES', 'systemd', d)} \
+ ${@bb.utils.filter('DISTRO_FEATURES', 'bluez5', d)} \
+ alsa audioconvert \
+ pipewire-alsa \
+ gstreamer \
+"
+
+GST_VER = "1.0"
+
+# systemd integration
+PACKAGECONFIG[systemd] = "-Dsystemd=true,-Dsystemd=false,systemd"
+
+# SPA plugins
+PACKAGECONFIG[alsa] = "-Dalsa=true,-Dalsa=false,udev alsa-lib"
+PACKAGECONFIG[audioconvert] = "-Daudioconvert=true,-Daudioconvert=false,speexdsp"
+PACKAGECONFIG[audiotestsrc] = "-Daudiotestsrc=true,-Daudiotestsrc=false, "
+PACKAGECONFIG[bluez5] = "-Dbluez5=true,-Dbluez5=false,bluez5 sbc"
+PACKAGECONFIG[v4l2] = "-Dv4l2=true,-Dv4l2=false,udev v4l-utils"
+PACKAGECONFIG[videotestsrc] = "-Dvideotestsrc=true,-Dvideotestsrc=false, "
+
+# alsa plugin to redirect audio to pipewire
+PACKAGECONFIG[pipewire-alsa] = "-Dpipewire-alsa=true,-Dpipewire-alsa=false,alsa-lib"
+# pulseaudio drop-in replacement library
+PACKAGECONFIG[pipewire-pulseaudio] = "-Dpipewire-pulseaudio=true,-Dpipewire-pulseaudio=false,pulseaudio glib-2.0"
+# jack drop-in replacement library
+PACKAGECONFIG[pipewire-jack] = "-Dpipewire-jack=true,-Dpipewire-jack=false,jack"
+
+# GStreamer plugins
+PACKAGECONFIG[gstreamer] = "-Dgstreamer=true,-Dgstreamer=false,glib-2.0 gstreamer${GST_VER} gstreamer${GST_VER}-plugins-base"
+
+# man pages
+PACKAGECONFIG[manpages] = "-Dman=true,-Dman=false,libxml-parser-perl-native"
+
+do_install_append() {
+ # only install the alsa config file if the alsa-lib plugin has been built
+ # this avoids creating the pipewire-alsa package when the pipewire-alsa
+ # feature is not enabled
+ if [ -d ${D}${libdir}/alsa-lib ]
+ then
+ mkdir -p ${D}${datadir}/alsa/alsa.conf.d
+ install -m 0644 ${S}/pipewire-alsa/conf/50-pipewire.conf ${D}${datadir}/alsa/alsa.conf.d/50-pipewire.conf
+ fi
+}
+
+PACKAGES =+ "\
+ ${PN}-spa-plugins \
+ ${PN}-alsa \
+ ${PN}-pulseaudio \
+ ${PN}-jack \
+ ${PN}-config \
+ gstreamer${GST_VER}-${PN} \
+ lib${PN} \
+ lib${PN}-modules \
+"
+
+FILES_${PN} = "\
+ ${bindir}/pipewire* \
+ ${systemd_user_unitdir}/* \
+"
+
+FILES_lib${PN} = "\
+ ${libdir}/libpipewire-*.so.* \
+"
+
+FILES_lib${PN}-modules = "\
+ ${libdir}/pipewire-*/* \
+"
+
+FILES_${PN}-spa-plugins = "\
+ ${bindir}/spa-* \
+ ${libdir}/spa/* \
+"
+
+FILES_${PN}-alsa = "\
+ ${libdir}/alsa-lib/* \
+ ${datadir}/alsa/alsa.conf.d/50-pipewire.conf \
+"
+
+FILES_${PN}-pulseaudio = "\
+ ${libdir}/libpulse*.so.* \
+"
+
+FILES_gstreamer${GST_VER}-${PN} = "\
+ ${libdir}/gstreamer-${GST_VER}/* \
+"
+
+RDEPENDS_lib${PN} += "lib${PN}-modules ${PN}-spa-plugins"
+
+# The default pipewire config.
+# Replace in your own package using
+# "virtual/pipewire-config"
+FILES_${PN}-config = "\
+ ${sysconfdir}/pipewire/pipewire.conf \
+"
+CONFFILES_${PN}-config += "\
+ ${sysconfdir}/pipewire/pipewire.conf \
+"
+RPROVIDES_${PN}-config += "virtual/pipewire-config"
diff --git a/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/0001-spa-include-install-missing-headers.patch b/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/0001-spa-include-install-missing-headers.patch
new file mode 100644
index 00000000..5b928117
--- /dev/null
+++ b/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/0001-spa-include-install-missing-headers.patch
@@ -0,0 +1,41 @@
+From dbb6e10df8c2ba9b874eb9350d4cb93c62dba5a9 Mon Sep 17 00:00:00 2001
+From: George Kiagiadakis <george.kiagiadakis@collabora.com>
+Date: Wed, 29 May 2019 12:09:13 +0300
+Subject: [PATCH] spa/include: install missing headers
+
+Upstream-Status: Accepted
+---
+ spa/include/spa/meson.build | 3 +++
+ 1 file changed, 3 insertions(+)
+
+diff --git a/spa/include/spa/meson.build b/spa/include/spa/meson.build
+index c9d07659..c079a1a2 100644
+--- a/spa/include/spa/meson.build
++++ b/spa/include/spa/meson.build
+@@ -39,6 +39,7 @@ spa_monitor_headers = [
+ 'monitor/device.h',
+ 'monitor/monitor.h',
+ 'monitor/type-info.h',
++ 'monitor/utils.h',
+ ]
+
+ install_headers(spa_monitor_headers,
+@@ -50,6 +51,7 @@ spa_node_headers = [
+ 'node/io.h',
+ 'node/node.h',
+ 'node/type-info.h',
++ 'node/utils.h',
+ ]
+
+ install_headers(spa_node_headers,
+@@ -97,6 +99,7 @@ spa_utils_headers = [
+ 'utils/dict.h',
+ 'utils/hook.h',
+ 'utils/list.h',
++ 'utils/result.h',
+ 'utils/ringbuffer.h',
+ 'utils/type.h',
+ 'utils/type-info.h',
+--
+2.20.1
+
diff --git a/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/0002-extensions-implement-Endpoint-ClientEndpoint-interfa.patch b/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/0002-extensions-implement-Endpoint-ClientEndpoint-interfa.patch
new file mode 100644
index 00000000..e49edf49
--- /dev/null
+++ b/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/0002-extensions-implement-Endpoint-ClientEndpoint-interfa.patch
@@ -0,0 +1,1563 @@
+From 5afe82a430642c2f7e116941709a624b8fd73011 Mon Sep 17 00:00:00 2001
+From: George Kiagiadakis <george.kiagiadakis@collabora.com>
+Date: Thu, 23 May 2019 18:59:05 +0300
+Subject: [PATCH] extensions: implement Endpoint & ClientEndpoint interfaces
+
+The ClientEndpoint interface allows session managers to register
+endpoint objects on pipewire.
+The Endpoint interface allows other clients to interact with
+endpoints provided by the session manager.
+
+Upstream-Status: Pending
+---
+ src/extensions/client-endpoint.h | 106 ++++
+ src/extensions/endpoint.h | 237 +++++++++
+ src/extensions/meson.build | 2 +
+ src/modules/meson.build | 12 +
+ src/modules/module-endpoint.c | 137 +++++
+ src/modules/module-endpoint/endpoint-impl.c | 428 ++++++++++++++++
+ src/modules/module-endpoint/endpoint-impl.h | 52 ++
+ src/modules/module-endpoint/protocol-native.c | 472 ++++++++++++++++++
+ src/pipewire/pipewire.c | 2 +
+ src/pipewire/type.h | 3 +-
+ 10 files changed, 1450 insertions(+), 1 deletion(-)
+ create mode 100644 src/extensions/client-endpoint.h
+ create mode 100644 src/extensions/endpoint.h
+ create mode 100644 src/modules/module-endpoint.c
+ create mode 100644 src/modules/module-endpoint/endpoint-impl.c
+ create mode 100644 src/modules/module-endpoint/endpoint-impl.h
+ create mode 100644 src/modules/module-endpoint/protocol-native.c
+
+diff --git a/src/extensions/client-endpoint.h b/src/extensions/client-endpoint.h
+new file mode 100644
+index 00000000..0389893c
+--- /dev/null
++++ b/src/extensions/client-endpoint.h
+@@ -0,0 +1,106 @@
++/* PipeWire
++ *
++ * Copyright © 2019 Collabora Ltd.
++ * @author George Kiagiadakis <george.kiagiadakis@collabora.com>
++ *
++ * Permission is hereby granted, free of charge, to any person obtaining a
++ * copy of this software and associated documentation files (the "Software"),
++ * to deal in the Software without restriction, including without limitation
++ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
++ * and/or sell copies of the Software, and to permit persons to whom the
++ * Software is furnished to do so, subject to the following conditions:
++ *
++ * The above copyright notice and this permission notice (including the next
++ * paragraph) shall be included in all copies or substantial portions of the
++ * Software.
++ *
++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
++ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
++ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
++ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
++ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
++ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
++ * DEALINGS IN THE SOFTWARE.
++ */
++
++#ifndef PIPEWIRE_EXT_CLIENT_ENDPOINT_H
++#define PIPEWIRE_EXT_CLIENT_ENDPOINT_H
++
++#include "endpoint.h"
++
++#ifdef __cplusplus
++extern "C" {
++#endif
++
++struct pw_client_endpoint_proxy;
++
++#define PW_VERSION_CLIENT_ENDPOINT 0
++#define PW_EXTENSION_MODULE_CLIENT_ENDPOINT PIPEWIRE_MODULE_PREFIX "module-endpoint"
++
++#define PW_CLIENT_ENDPOINT_PROXY_METHOD_UPDATE 0
++#define PW_CLIENT_ENDPOINT_PROXY_METHOD_NUM 1
++
++struct pw_client_endpoint_proxy_methods {
++#define PW_VERSION_CLIENT_ENDPOINT_PROXY_METHODS 0
++ uint32_t version;
++
++ /**
++ * Update endpoint info
++ */
++ int (*update) (void *object,
++#define PW_CLIENT_ENDPOINT_UPDATE_PARAMS (1 << 0)
++#define PW_CLIENT_ENDPOINT_UPDATE_PARAMS_INCREMENTAL (1 << 1)
++#define PW_CLIENT_ENDPOINT_UPDATE_INFO (1 << 2)
++ uint32_t change_mask,
++ uint32_t n_params,
++ const struct spa_pod **params,
++ const struct pw_endpoint_info *info);
++};
++
++static inline int
++pw_client_endpoint_proxy_update(struct pw_client_endpoint_proxy *p,
++ uint32_t change_mask,
++ uint32_t n_params,
++ const struct spa_pod **params,
++ struct pw_endpoint_info *info)
++{
++ return pw_proxy_do((struct pw_proxy*)p,
++ struct pw_client_endpoint_proxy_methods, update,
++ change_mask, n_params, params, info);
++}
++
++#define PW_CLIENT_ENDPOINT_PROXY_EVENT_SET_PARAM 0
++#define PW_CLIENT_ENDPOINT_PROXY_EVENT_NUM 1
++
++struct pw_client_endpoint_proxy_events {
++#define PW_VERSION_CLIENT_ENDPOINT_PROXY_EVENTS 0
++ uint32_t version;
++
++ /**
++ * Set a parameter on the endpoint
++ *
++ * \param id the parameter id to set
++ * \param flags extra parameter flags
++ * \param param the parameter to set
++ */
++ void (*set_param) (void *object, uint32_t id, uint32_t flags,
++ const struct spa_pod *param);
++};
++
++static inline void
++pw_client_endpoint_proxy_add_listener(struct pw_client_endpoint_proxy *p,
++ struct spa_hook *listener,
++ const struct pw_client_endpoint_proxy_events *events,
++ void *data)
++{
++ pw_proxy_add_proxy_listener((struct pw_proxy*)p, listener, events, data);
++}
++
++#define pw_client_endpoint_resource_set_param(r,...) \
++ pw_resource_notify(r,struct pw_client_endpoint_proxy_events,set_param,__VA_ARGS__)
++
++#ifdef __cplusplus
++} /* extern "C" */
++#endif
++
++#endif /* PIPEWIRE_EXT_CLIENT_ENDPOINT_H */
+diff --git a/src/extensions/endpoint.h b/src/extensions/endpoint.h
+new file mode 100644
+index 00000000..211c0895
+--- /dev/null
++++ b/src/extensions/endpoint.h
+@@ -0,0 +1,237 @@
++/* PipeWire
++ *
++ * Copyright © 2019 Collabora Ltd.
++ * @author George Kiagiadakis <george.kiagiadakis@collabora.com>
++ *
++ * Permission is hereby granted, free of charge, to any person obtaining a
++ * copy of this software and associated documentation files (the "Software"),
++ * to deal in the Software without restriction, including without limitation
++ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
++ * and/or sell copies of the Software, and to permit persons to whom the
++ * Software is furnished to do so, subject to the following conditions:
++ *
++ * The above copyright notice and this permission notice (including the next
++ * paragraph) shall be included in all copies or substantial portions of the
++ * Software.
++ *
++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
++ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
++ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
++ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
++ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
++ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
++ * DEALINGS IN THE SOFTWARE.
++ */
++
++#ifndef PIPEWIRE_EXT_ENDPOINT_H
++#define PIPEWIRE_EXT_ENDPOINT_H
++
++#include <spa/utils/defs.h>
++#include <spa/utils/type-info.h>
++#include <pipewire/proxy.h>
++
++#ifdef __cplusplus
++extern "C" {
++#endif
++
++struct pw_endpoint_proxy;
++
++#define PW_VERSION_ENDPOINT 0
++#define PW_EXTENSION_MODULE_ENDPOINT PIPEWIRE_MODULE_PREFIX "module-endpoint"
++
++/* extending enum spa_param_type */
++enum endpoint_param_type {
++ PW_ENDPOINT_PARAM_EnumControl = 0x1000,
++ PW_ENDPOINT_PARAM_Control,
++ PW_ENDPOINT_PARAM_EnumStream,
++};
++
++enum endpoint_param_object_type {
++ PW_ENDPOINT_OBJECT_ParamControl = PW_TYPE_FIRST + SPA_TYPE_OBJECT_START + 0x1001,
++ PW_ENDPOINT_OBJECT_ParamStream,
++};
++
++/** properties for PW_ENDPOINT_OBJECT_ParamControl */
++enum endpoint_param_control {
++ PW_ENDPOINT_PARAM_CONTROL_START, /**< object id, one of enum endpoint_param_type */
++ PW_ENDPOINT_PARAM_CONTROL_id, /**< control id (Int) */
++ PW_ENDPOINT_PARAM_CONTROL_stream_id, /**< stream id (Int) */
++ PW_ENDPOINT_PARAM_CONTROL_name, /**< control name (String) */
++ PW_ENDPOINT_PARAM_CONTROL_type, /**< control type (Range) */
++ PW_ENDPOINT_PARAM_CONTROL_value, /**< control value */
++};
++
++/** properties for PW_ENDPOINT_OBJECT_ParamStream */
++enum endpoint_param_stream {
++ PW_ENDPOINT_PARAM_STREAM_START, /**< object id, one of enum endpoint_param_type */
++ PW_ENDPOINT_PARAM_STREAM_id, /**< stream id (Int) */
++ PW_ENDPOINT_PARAM_STREAM_name, /**< stream name (String) */
++};
++
++static const struct spa_type_info endpoint_param_type_info[] = {
++ { PW_ENDPOINT_PARAM_EnumControl, SPA_TYPE_Int, SPA_TYPE_INFO_PARAM_ID_BASE "EnumControl", NULL },
++ { PW_ENDPOINT_PARAM_Control, SPA_TYPE_Int, SPA_TYPE_INFO_PARAM_ID_BASE "Control", NULL },
++ { PW_ENDPOINT_PARAM_EnumStream, SPA_TYPE_Int, SPA_TYPE_INFO_PARAM_ID_BASE "EnumStream", NULL },
++ { 0, 0, NULL, NULL },
++};
++
++#define PW_ENDPOINT_TYPE_INFO_ParamControl SPA_TYPE_INFO_PARAM_BASE "ParamControl"
++#define PW_ENDPOINT_TYPE_INFO_PARAM_CONTROL_BASE PW_ENDPOINT_TYPE_INFO_ParamControl ":"
++
++static const struct spa_type_info endpoint_param_control_info[] = {
++ { PW_ENDPOINT_PARAM_CONTROL_START, SPA_TYPE_Id, PW_ENDPOINT_TYPE_INFO_PARAM_CONTROL_BASE, spa_type_param, },
++ { PW_ENDPOINT_PARAM_CONTROL_id, SPA_TYPE_Int, PW_ENDPOINT_TYPE_INFO_PARAM_CONTROL_BASE "id", NULL },
++ { PW_ENDPOINT_PARAM_CONTROL_stream_id, SPA_TYPE_Int, PW_ENDPOINT_TYPE_INFO_PARAM_CONTROL_BASE "streamId", NULL },
++ { PW_ENDPOINT_PARAM_CONTROL_name, SPA_TYPE_String, PW_ENDPOINT_TYPE_INFO_PARAM_CONTROL_BASE "name", NULL },
++ { PW_ENDPOINT_PARAM_CONTROL_type, SPA_TYPE_Pod, PW_ENDPOINT_TYPE_INFO_PARAM_CONTROL_BASE "type", NULL },
++ { PW_ENDPOINT_PARAM_CONTROL_value, SPA_TYPE_Struct, PW_ENDPOINT_TYPE_INFO_PARAM_CONTROL_BASE "value", NULL },
++ { 0, 0, NULL, NULL },
++};
++
++#define PW_ENDPOINT_TYPE_INFO_ParamStream SPA_TYPE_INFO_PARAM_BASE "ParamStream"
++#define PW_ENDPOINT_TYPE_INFO_PARAM_STREAM_BASE PW_ENDPOINT_TYPE_INFO_ParamStream ":"
++
++static const struct spa_type_info endpoint_param_stream_info[] = {
++ { PW_ENDPOINT_PARAM_STREAM_START, SPA_TYPE_Id, PW_ENDPOINT_TYPE_INFO_PARAM_STREAM_BASE, spa_type_param, },
++ { PW_ENDPOINT_PARAM_STREAM_id, SPA_TYPE_Int, PW_ENDPOINT_TYPE_INFO_PARAM_STREAM_BASE "id", NULL },
++ { PW_ENDPOINT_PARAM_STREAM_name, SPA_TYPE_String, PW_ENDPOINT_TYPE_INFO_PARAM_STREAM_BASE "name", NULL },
++ { 0, 0, NULL, NULL },
++};
++
++static const struct spa_type_info endpoint_param_object_type_info[] = {
++ { PW_ENDPOINT_OBJECT_ParamControl, SPA_TYPE_Object, SPA_TYPE_INFO_OBJECT_BASE "ParamControl", endpoint_param_control_info, },
++ { PW_ENDPOINT_OBJECT_ParamStream, SPA_TYPE_Object, SPA_TYPE_INFO_OBJECT_BASE "ParamStream", endpoint_param_stream_info },
++ { 0, 0, NULL, NULL },
++};
++
++struct pw_endpoint_info {
++ uint32_t id; /**< id of the global */
++#define PW_ENDPOINT_CHANGE_MASK_PARAMS (1 << 0)
++#define PW_ENDPOINT_CHANGE_MASK_PROPS (1 << 1)
++ uint32_t change_mask; /**< bitfield of changed fields since last call */
++ uint32_t n_params; /**< number of items in \a params */
++ struct spa_param_info *params; /**< parameters */
++ struct spa_dict *props; /**< extra properties */
++};
++
++#define PW_ENDPOINT_PROXY_METHOD_SUBSCRIBE_PARAMS 0
++#define PW_ENDPOINT_PROXY_METHOD_ENUM_PARAMS 1
++#define PW_ENDPOINT_PROXY_METHOD_SET_PARAM 2
++#define PW_ENDPOINT_PROXY_METHOD_NUM 3
++
++struct pw_endpoint_proxy_methods {
++#define PW_VERSION_ENDPOINT_PROXY_METHODS 0
++ uint32_t version;
++
++ /**
++ * Subscribe to parameter changes
++ *
++ * Automatically emit param events for the given ids when
++ * they are changed.
++ *
++ * \param ids an array of param ids
++ * \param n_ids the number of ids in \a ids
++ */
++ int (*subscribe_params) (void *object, uint32_t *ids, uint32_t n_ids);
++
++ /**
++ * Enumerate endpoint parameters
++ *
++ * Start enumeration of endpoint parameters. For each param, a
++ * param event will be emited.
++ *
++ * \param seq a sequence number to place in the reply
++ * \param id the parameter id to enum or SPA_ID_INVALID for all
++ * \param start the start index or 0 for the first param
++ * \param num the maximum number of params to retrieve
++ * \param filter a param filter or NULL
++ */
++ int (*enum_params) (void *object, int seq,
++ uint32_t id, uint32_t start, uint32_t num,
++ const struct spa_pod *filter);
++
++ /**
++ * Set a parameter on the endpoint
++ *
++ * \param id the parameter id to set
++ * \param flags extra parameter flags
++ * \param param the parameter to set
++ */
++ int (*set_param) (void *object, uint32_t id, uint32_t flags,
++ const struct spa_pod *param);
++};
++
++static inline int
++pw_endpoint_proxy_subscribe_params(struct pw_endpoint_proxy *p, uint32_t *ids, uint32_t n_ids)
++{
++ return pw_proxy_do((struct pw_proxy*)p, struct pw_endpoint_proxy_methods,
++ subscribe_params, ids, n_ids);
++}
++
++static inline int
++pw_endpoint_proxy_enum_params(struct pw_endpoint_proxy *p, int seq,
++ uint32_t id, uint32_t start, uint32_t num,
++ const struct spa_pod *filter)
++{
++ return pw_proxy_do((struct pw_proxy*)p, struct pw_endpoint_proxy_methods,
++ enum_params, seq, id, start, num, filter);
++}
++
++static inline int
++pw_endpoint_proxy_set_param(struct pw_endpoint_proxy *p, uint32_t id,
++ uint32_t flags, const struct spa_pod *param)
++{
++ return pw_proxy_do((struct pw_proxy*)p, struct pw_endpoint_proxy_methods,
++ set_param, id, flags, param);
++}
++
++#define PW_ENDPOINT_PROXY_EVENT_INFO 0
++#define PW_ENDPOINT_PROXY_EVENT_PARAM 1
++#define PW_ENDPOINT_PROXY_EVENT_NUM 2
++
++struct pw_endpoint_proxy_events {
++#define PW_VERSION_ENDPOINT_PROXY_EVENTS 0
++ uint32_t version;
++
++ /**
++ * Notify endpoint info
++ *
++ * \param info info about the endpoint
++ */
++ void (*info) (void *object, const struct pw_endpoint_info * info);
++
++ /**
++ * Notify an endpoint param
++ *
++ * Event emited as a result of the enum_params method.
++ *
++ * \param seq the sequence number of the request
++ * \param id the param id
++ * \param index the param index
++ * \param next the param index of the next param
++ * \param param the parameter
++ */
++ void (*param) (void *object, int seq, uint32_t id,
++ uint32_t index, uint32_t next,
++ const struct spa_pod *param);
++};
++
++static inline void
++pw_endpoint_proxy_add_listener(struct pw_endpoint_proxy *p,
++ struct spa_hook *listener,
++ const struct pw_endpoint_proxy_events *events,
++ void *data)
++{
++ pw_proxy_add_proxy_listener((struct pw_proxy*)p, listener, events, data);
++}
++
++#define pw_endpoint_resource_info(r,...) \
++ pw_resource_notify(r,struct pw_endpoint_proxy_events,info,__VA_ARGS__)
++#define pw_endpoint_resource_param(r,...) \
++ pw_resource_notify(r,struct pw_endpoint_proxy_events,param,__VA_ARGS__)
++
++#ifdef __cplusplus
++} /* extern "C" */
++#endif
++
++#endif /* PIPEWIRE_EXT_ENDPOINT_H */
+diff --git a/src/extensions/meson.build b/src/extensions/meson.build
+index a7f5d3cb..9f690caf 100644
+--- a/src/extensions/meson.build
++++ b/src/extensions/meson.build
+@@ -1,5 +1,7 @@
+ pipewire_ext_headers = [
++ 'client-endpoint.h',
+ 'client-node.h',
++ 'endpoint.h',
+ 'protocol-native.h',
+ ]
+
+diff --git a/src/modules/meson.build b/src/modules/meson.build
+index 98bc3864..572f1b6b 100644
+--- a/src/modules/meson.build
++++ b/src/modules/meson.build
+@@ -37,6 +37,18 @@ pipewire_module_client_node = shared_library('pipewire-module-client-node',
+ dependencies : [mathlib, dl_lib, pipewire_dep],
+ )
+
++pipewire_module_endpoint = shared_library('pipewire-module-endpoint',
++ [ 'module-endpoint.c',
++ 'module-endpoint/endpoint-impl.c',
++ 'module-endpoint/protocol-native.c',
++ ],
++ c_args : pipewire_module_c_args,
++ include_directories : [configinc, spa_inc],
++ install : true,
++ install_dir : modules_install_dir,
++ dependencies : [mathlib, dl_lib, pipewire_dep],
++)
++
+ pipewire_module_link_factory = shared_library('pipewire-module-link-factory',
+ [ 'module-link-factory.c' ],
+ c_args : pipewire_module_c_args,
+diff --git a/src/modules/module-endpoint.c b/src/modules/module-endpoint.c
+new file mode 100644
+index 00000000..d830de1b
+--- /dev/null
++++ b/src/modules/module-endpoint.c
+@@ -0,0 +1,137 @@
++/* PipeWire
++ *
++ * Copyright © 2019 Collabora Ltd.
++ * @author George Kiagiadakis <george.kiagiadakis@collabora.com>
++ *
++ * Permission is hereby granted, free of charge, to any person obtaining a
++ * copy of this software and associated documentation files (the "Software"),
++ * to deal in the Software without restriction, including without limitation
++ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
++ * and/or sell copies of the Software, and to permit persons to whom the
++ * Software is furnished to do so, subject to the following conditions:
++ *
++ * The above copyright notice and this permission notice (including the next
++ * paragraph) shall be included in all copies or substantial portions of the
++ * Software.
++ *
++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
++ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
++ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
++ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
++ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
++ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
++ * DEALINGS IN THE SOFTWARE.
++ */
++
++#include "config.h"
++
++#include "module-endpoint/endpoint-impl.h"
++
++struct pw_protocol *pw_protocol_native_ext_endpoint_init(struct pw_core *core);
++
++static const struct spa_dict_item module_props[] = {
++ { PW_MODULE_PROP_AUTHOR, "George Kiagiadakis <george.kiagiadakis@collabora.com>" },
++ { PW_MODULE_PROP_DESCRIPTION, "Allows clients to interract with session manager endpoints" },
++ { PW_MODULE_PROP_VERSION, PACKAGE_VERSION },
++};
++
++struct factory_data {
++ struct pw_factory *this;
++ struct pw_properties *properties;
++
++ struct pw_module *module;
++ struct spa_hook module_listener;
++};
++
++static void *create_object(void *_data,
++ struct pw_resource *resource,
++ uint32_t type,
++ uint32_t version,
++ struct pw_properties *properties,
++ uint32_t new_id)
++{
++ void *result;
++ struct pw_resource *endpoint_resource;
++ struct pw_global *parent;
++ struct pw_client *client = pw_resource_get_client(resource);
++
++ endpoint_resource = pw_resource_new(client, new_id, PW_PERM_RWX, type, version, 0);
++ if (endpoint_resource == NULL)
++ goto no_mem;
++
++ parent = pw_client_get_global(client);
++
++ result = pw_client_endpoint_new(endpoint_resource, parent, properties);
++ if (result == NULL)
++ goto no_mem;
++
++ return result;
++
++ no_mem:
++ pw_log_error("can't create endpoint");
++ pw_resource_error(resource, -ENOMEM, "can't create endpoint: no memory");
++ if (properties)
++ pw_properties_free(properties);
++ return NULL;
++}
++
++static const struct pw_factory_implementation impl_factory = {
++ PW_VERSION_FACTORY_IMPLEMENTATION,
++ .create_object = create_object,
++};
++
++static void module_destroy(void *data)
++{
++ struct factory_data *d = data;
++
++ spa_hook_remove(&d->module_listener);
++
++ if (d->properties)
++ pw_properties_free(d->properties);
++
++ pw_factory_destroy(d->this);
++}
++
++static const struct pw_module_events module_events = {
++ PW_VERSION_MODULE_EVENTS,
++ .destroy = module_destroy,
++};
++
++static int module_init(struct pw_module *module, struct pw_properties *properties)
++{
++ struct pw_core *core = pw_module_get_core(module);
++ struct pw_factory *factory;
++ struct factory_data *data;
++
++ factory = pw_factory_new(core,
++ "client-endpoint",
++ PW_TYPE_INTERFACE_ClientEndpoint,
++ PW_VERSION_CLIENT_ENDPOINT,
++ NULL,
++ sizeof(*data));
++ if (factory == NULL)
++ return -ENOMEM;
++
++ data = pw_factory_get_user_data(factory);
++ data->this = factory;
++ data->module = module;
++ data->properties = properties;
++
++ pw_log_debug("module-endpoint %p: new", module);
++
++ pw_factory_set_implementation(factory, &impl_factory, data);
++ pw_factory_register(factory, NULL, pw_module_get_global(module), NULL);
++
++ pw_protocol_native_ext_endpoint_init(core);
++
++ pw_module_add_listener(module, &data->module_listener, &module_events, data);
++ pw_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_props));
++
++ return 0;
++}
++
++SPA_EXPORT
++int pipewire__module_init(struct pw_module *module, const char *args)
++{
++ return module_init(module, NULL);
++}
+diff --git a/src/modules/module-endpoint/endpoint-impl.c b/src/modules/module-endpoint/endpoint-impl.c
+new file mode 100644
+index 00000000..252eeca1
+--- /dev/null
++++ b/src/modules/module-endpoint/endpoint-impl.c
+@@ -0,0 +1,428 @@
++/* PipeWire
++ *
++ * Copyright © 2019 Collabora Ltd.
++ * @author George Kiagiadakis <george.kiagiadakis@collabora.com>
++ *
++ * Permission is hereby granted, free of charge, to any person obtaining a
++ * copy of this software and associated documentation files (the "Software"),
++ * to deal in the Software without restriction, including without limitation
++ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
++ * and/or sell copies of the Software, and to permit persons to whom the
++ * Software is furnished to do so, subject to the following conditions:
++ *
++ * The above copyright notice and this permission notice (including the next
++ * paragraph) shall be included in all copies or substantial portions of the
++ * Software.
++ *
++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
++ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
++ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
++ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
++ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
++ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
++ * DEALINGS IN THE SOFTWARE.
++ */
++
++#include "endpoint-impl.h"
++#include <pipewire/private.h>
++#include <spa/pod/filter.h>
++#include <spa/pod/compare.h>
++
++struct pw_endpoint {
++ struct pw_core *core;
++ struct pw_global *global;
++ struct pw_global *parent;
++
++ struct pw_client_endpoint *client_ep;
++
++ uint32_t n_params;
++ struct spa_pod **params;
++
++ struct pw_endpoint_info info;
++ struct pw_properties *props;
++};
++
++struct pw_client_endpoint {
++ struct pw_resource *owner_resource;
++ struct spa_hook owner_resource_listener;
++
++ struct pw_endpoint endpoint;
++};
++
++struct resource_data {
++ struct pw_endpoint *endpoint;
++ struct pw_client_endpoint *client_ep;
++
++ struct spa_hook resource_listener;
++
++ uint32_t n_subscribe_ids;
++ uint32_t subscribe_ids[32];
++};
++
++static int
++endpoint_enum_params (void *object, int seq,
++ uint32_t id, uint32_t start, uint32_t num,
++ const struct spa_pod *filter)
++{
++ struct pw_resource *resource = object;
++ struct resource_data *data = pw_resource_get_user_data(resource);
++ struct pw_endpoint *this = data->endpoint;
++ struct spa_pod *result;
++ struct spa_pod *param;
++ uint8_t buffer[1024];
++ struct spa_pod_builder b = { 0 };
++ uint32_t index;
++ uint32_t next = start;
++ uint32_t count = 0;
++
++ while (true) {
++ index = next++;
++ if (index >= this->n_params)
++ break;
++
++ param = this->params[index];
++
++ if (param == NULL || !spa_pod_is_object_id(param, id))
++ continue;
++
++ spa_pod_builder_init(&b, buffer, sizeof(buffer));
++ if (spa_pod_filter(&b, &result, param, filter) != 0)
++ continue;
++
++ pw_log_debug("endpoint %p: %d param %u", this, seq, index);
++
++ pw_endpoint_resource_param(resource, seq, id, index, next, result);
++
++ if (++count == num)
++ break;
++ }
++ return 0;
++}
++
++static int
++endpoint_subscribe_params (void *object, uint32_t *ids, uint32_t n_ids)
++{
++ struct pw_resource *resource = object;
++ struct resource_data *data = pw_resource_get_user_data(resource);
++ uint32_t i;
++
++ n_ids = SPA_MIN(n_ids, SPA_N_ELEMENTS(data->subscribe_ids));
++ data->n_subscribe_ids = n_ids;
++
++ for (i = 0; i < n_ids; i++) {
++ data->subscribe_ids[i] = ids[i];
++ pw_log_debug("endpoint %p: resource %d subscribe param %u",
++ data->endpoint, resource->id, ids[i]);
++ endpoint_enum_params(resource, 1, ids[i], 0, UINT32_MAX, NULL);
++ }
++ return 0;
++}
++
++static int
++endpoint_set_param (void *object, uint32_t id, uint32_t flags,
++ const struct spa_pod *param)
++{
++ struct pw_resource *resource = object;
++ struct resource_data *data = pw_resource_get_user_data(resource);
++ struct pw_client_endpoint *client_ep = data->client_ep;
++
++ pw_client_endpoint_resource_set_param(client_ep->owner_resource,
++ id, flags, param);
++
++ return 0;
++}
++
++static const struct pw_endpoint_proxy_methods endpoint_methods = {
++ PW_VERSION_ENDPOINT_PROXY_METHODS,
++ .subscribe_params = endpoint_subscribe_params,
++ .enum_params = endpoint_enum_params,
++ .set_param = endpoint_set_param,
++};
++
++static void
++endpoint_unbind(void *data)
++{
++ struct pw_resource *resource = data;
++ spa_list_remove(&resource->link);
++}
++
++static const struct pw_resource_events resource_events = {
++ PW_VERSION_RESOURCE_EVENTS,
++ .destroy = endpoint_unbind,
++};
++
++static int
++endpoint_bind(void *_data, struct pw_client *client, uint32_t permissions,
++ uint32_t version, uint32_t id)
++{
++ struct pw_endpoint *this = _data;
++ struct pw_global *global = this->global;
++ struct pw_resource *resource;
++ struct resource_data *data;
++
++ resource = pw_resource_new(client, id, permissions, global->type, version, sizeof(*data));
++ if (resource == NULL)
++ goto no_mem;
++
++ data = pw_resource_get_user_data(resource);
++ data->endpoint = this;
++ data->client_ep = this->client_ep;
++ pw_resource_add_listener(resource, &data->resource_listener, &resource_events, resource);
++
++ pw_resource_set_implementation(resource, &endpoint_methods, resource);
++
++ pw_log_debug("endpoint %p: bound to %d", this, resource->id);
++
++ spa_list_append(&global->resource_list, &resource->link);
++
++ this->info.change_mask = PW_ENDPOINT_CHANGE_MASK_PARAMS |
++ PW_ENDPOINT_CHANGE_MASK_PROPS;
++ pw_endpoint_resource_info(resource, &this->info);
++ this->info.change_mask = 0;
++
++ return 0;
++
++ no_mem:
++ pw_log_error("can't create node resource");
++ return -ENOMEM;
++}
++
++static int
++pw_endpoint_init(struct pw_endpoint *this,
++ struct pw_core *core,
++ struct pw_client *owner,
++ struct pw_global *parent,
++ struct pw_properties *properties)
++{
++ struct pw_properties *props = NULL;
++
++ pw_log_debug("endpoint %p: new", this);
++
++ this->core = core;
++ this->parent = parent;
++
++ props = properties ? properties : pw_properties_new(NULL, NULL);
++ if (!props)
++ goto no_mem;
++
++ this->props = pw_properties_copy (props);
++ if (!this->props)
++ goto no_mem;
++
++ this->global = pw_global_new (core,
++ PW_TYPE_INTERFACE_Endpoint,
++ PW_VERSION_ENDPOINT,
++ props, endpoint_bind, this);
++ if (!this->global)
++ goto no_mem;
++
++ this->info.id = this->global->id;
++ this->info.props = &this->props->dict;
++
++ return pw_global_register(this->global, owner, parent);
++
++ no_mem:
++ pw_log_error("can't create endpoint - out of memory");
++ if (props && !properties)
++ pw_properties_free(props);
++ if (this->props)
++ pw_properties_free(this->props);
++ return -ENOMEM;
++}
++
++static void
++pw_endpoint_clear(struct pw_endpoint *this)
++{
++ uint32_t i;
++
++ pw_log_debug("endpoint %p: destroy", this);
++
++ pw_global_destroy(this->global);
++
++ for (i = 0; i < this->n_params; i++)
++ free(this->params[i]);
++ free(this->params);
++
++ free(this->info.params);
++
++ if (this->props)
++ pw_properties_free(this->props);
++}
++
++static void
++endpoint_notify_subscribed(struct pw_endpoint *this,
++ uint32_t index, uint32_t next)
++{
++ struct pw_global *global = this->global;
++ struct pw_resource *resource;
++ struct resource_data *data;
++ struct spa_pod *param = this->params[index];
++ uint32_t id;
++ uint32_t i;
++
++ if (!param || !spa_pod_is_object (param))
++ return;
++
++ id = SPA_POD_OBJECT_ID (param);
++
++ spa_list_for_each(resource, &global->resource_list, link) {
++ data = pw_resource_get_user_data(resource);
++ for (i = 0; i < data->n_subscribe_ids; i++) {
++ if (data->subscribe_ids[i] == id) {
++ pw_endpoint_resource_param(resource, 1, id,
++ index, next, param);
++ }
++ }
++ }
++}
++
++static int
++client_endpoint_update(void *object,
++ uint32_t change_mask,
++ uint32_t n_params,
++ const struct spa_pod **params,
++ const struct pw_endpoint_info *info)
++{
++ struct pw_client_endpoint *cliep = object;
++ struct pw_endpoint *this = &cliep->endpoint;
++
++ if (change_mask & PW_CLIENT_ENDPOINT_UPDATE_PARAMS) {
++ uint32_t i;
++
++ pw_log_debug("endpoint %p: update %d params", this, n_params);
++
++ for (i = 0; i < this->n_params; i++)
++ free(this->params[i]);
++ this->n_params = n_params;
++ this->params = realloc(this->params, this->n_params * sizeof(struct spa_pod *));
++
++ for (i = 0; i < this->n_params; i++) {
++ this->params[i] = params[i] ? spa_pod_copy(params[i]) : NULL;
++ endpoint_notify_subscribed(this, i, i+1);
++ }
++ }
++ else if (change_mask & PW_CLIENT_ENDPOINT_UPDATE_PARAMS_INCREMENTAL) {
++ uint32_t i, j;
++ const struct spa_pod_prop *pold, *pnew;
++
++ pw_log_debug("endpoint %p: update %d params incremental", this, n_params);
++
++ for (i = 0; i < this->n_params; i++) {
++ /* we only support incremental updates for controls */
++ if (!spa_pod_is_object_id (this->params[i], PW_ENDPOINT_PARAM_Control))
++ continue;
++
++ for (j = 0; j < n_params; j++) {
++ if (!spa_pod_is_object_id (params[j], PW_ENDPOINT_PARAM_Control)) {
++ pw_log_warn ("endpoint %p: ignoring incremental update "
++ "on non-control param", this);
++ continue;
++ }
++
++ pold = spa_pod_object_find_prop (
++ (const struct spa_pod_object *) this->params[i],
++ NULL, PW_ENDPOINT_PARAM_CONTROL_id);
++ pnew = spa_pod_object_find_prop (
++ (const struct spa_pod_object *) params[j],
++ NULL, PW_ENDPOINT_PARAM_CONTROL_id);
++
++ if (pold && pnew && spa_pod_compare (&pold->value, &pnew->value) == 0) {
++ free (this->params[i]);
++ this->params[i] = spa_pod_copy (params[j]);
++ endpoint_notify_subscribed(this, i, UINT32_MAX);
++ }
++ }
++ }
++ }
++
++ if (change_mask & PW_CLIENT_ENDPOINT_UPDATE_INFO) {
++ struct pw_global *global = this->global;
++ struct pw_resource *resource;
++
++ if (info->change_mask & PW_ENDPOINT_CHANGE_MASK_PARAMS) {
++ size_t size = info->n_params * sizeof(struct spa_param_info);
++ free(this->info.params);
++ this->info.params = malloc(size);
++ this->info.n_params = info->n_params;
++ memcpy(this->info.params, info->params, size);
++ }
++
++ if (info->change_mask & PW_ENDPOINT_CHANGE_MASK_PROPS) {
++ pw_properties_update(this->props, info->props);
++ }
++
++ this->info.change_mask = info->change_mask;
++ spa_list_for_each(resource, &global->resource_list, link) {
++ pw_endpoint_resource_info(resource, &this->info);
++ }
++ this->info.change_mask = 0;
++ }
++
++ return 0;
++}
++
++static struct pw_client_endpoint_proxy_methods client_endpoint_methods = {
++ PW_VERSION_CLIENT_ENDPOINT_PROXY_METHODS,
++ .update = client_endpoint_update,
++};
++
++static void
++client_endpoint_resource_destroy(void *data)
++{
++ struct pw_client_endpoint *this = data;
++
++ pw_log_debug("client-endpoint %p: destroy", this);
++
++ pw_endpoint_clear(&this->endpoint);
++
++ this->owner_resource = NULL;
++ spa_hook_remove(&this->owner_resource_listener);
++ free(this);
++}
++
++static const struct pw_resource_events owner_resource_events = {
++ PW_VERSION_RESOURCE_EVENTS,
++ .destroy = client_endpoint_resource_destroy,
++};
++
++struct pw_client_endpoint *
++pw_client_endpoint_new(struct pw_resource *owner_resource,
++ struct pw_global *parent,
++ struct pw_properties *properties)
++{
++ struct pw_client_endpoint *this;
++ struct pw_client *owner = pw_resource_get_client(owner_resource);
++ struct pw_core *core = pw_client_get_core(owner);
++
++ this = calloc(1, sizeof(struct pw_client_endpoint));
++ if (this == NULL)
++ return NULL;
++
++ pw_log_debug("client-endpoint %p: new", this);
++
++ if (pw_endpoint_init(&this->endpoint, core, owner, parent, properties) < 0)
++ goto error_no_endpoint;
++ this->endpoint.client_ep = this;
++
++ this->owner_resource = owner_resource;
++ pw_resource_add_listener(this->owner_resource,
++ &this->owner_resource_listener,
++ &owner_resource_events,
++ this);
++ pw_resource_set_implementation(this->owner_resource,
++ &client_endpoint_methods,
++ this);
++
++ return this;
++
++ error_no_endpoint:
++ pw_resource_destroy(owner_resource);
++ free(this);
++ return NULL;
++}
++
++void
++pw_client_endpoint_destroy(struct pw_client_endpoint *this)
++{
++ pw_resource_destroy(this->owner_resource);
++}
+diff --git a/src/modules/module-endpoint/endpoint-impl.h b/src/modules/module-endpoint/endpoint-impl.h
+new file mode 100644
+index 00000000..059aa904
+--- /dev/null
++++ b/src/modules/module-endpoint/endpoint-impl.h
+@@ -0,0 +1,52 @@
++/* PipeWire
++ *
++ * Copyright © 2019 Collabora Ltd.
++ * @author George Kiagiadakis <george.kiagiadakis@collabora.com>
++ *
++ * Permission is hereby granted, free of charge, to any person obtaining a
++ * copy of this software and associated documentation files (the "Software"),
++ * to deal in the Software without restriction, including without limitation
++ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
++ * and/or sell copies of the Software, and to permit persons to whom the
++ * Software is furnished to do so, subject to the following conditions:
++ *
++ * The above copyright notice and this permission notice (including the next
++ * paragraph) shall be included in all copies or substantial portions of the
++ * Software.
++ *
++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
++ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
++ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
++ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
++ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
++ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
++ * DEALINGS IN THE SOFTWARE.
++ */
++
++#ifndef PIPEWIRE_ENDPOINT_IMPL_H
++#define PIPEWIRE_ENDPOINT_IMPL_H
++
++#include <pipewire/pipewire.h>
++#include <extensions/endpoint.h>
++#include <extensions/client-endpoint.h>
++
++#ifdef __cplusplus
++extern "C" {
++#endif
++
++struct pw_endpoint;
++struct pw_client_endpoint;
++
++struct pw_client_endpoint *
++pw_client_endpoint_new(struct pw_resource *resource,
++ struct pw_global *parent,
++ struct pw_properties *properties);
++
++void
++pw_client_endpoint_destroy(struct pw_client_endpoint *endpoint);
++
++#ifdef __cplusplus
++}
++#endif
++
++#endif /* PIPEWIRE_ENDPOINT_IMPL_H */
+diff --git a/src/modules/module-endpoint/protocol-native.c b/src/modules/module-endpoint/protocol-native.c
+new file mode 100644
+index 00000000..a41d3119
+--- /dev/null
++++ b/src/modules/module-endpoint/protocol-native.c
+@@ -0,0 +1,472 @@
++/* PipeWire
++ *
++ * Copyright © 2019 Collabora Ltd.
++ * @author George Kiagiadakis <george.kiagiadakis@collabora.com>
++ *
++ * Permission is hereby granted, free of charge, to any person obtaining a
++ * copy of this software and associated documentation files (the "Software"),
++ * to deal in the Software without restriction, including without limitation
++ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
++ * and/or sell copies of the Software, and to permit persons to whom the
++ * Software is furnished to do so, subject to the following conditions:
++ *
++ * The above copyright notice and this permission notice (including the next
++ * paragraph) shall be included in all copies or substantial portions of the
++ * Software.
++ *
++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
++ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
++ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
++ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
++ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
++ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
++ * DEALINGS IN THE SOFTWARE.
++ */
++
++#include <pipewire/pipewire.h>
++#include <spa/pod/parser.h>
++
++#include <extensions/client-endpoint.h>
++#include <extensions/endpoint.h>
++#include <extensions/protocol-native.h>
++
++static void
++serialize_pw_endpoint_info(struct spa_pod_builder *b,
++ const struct pw_endpoint_info *info)
++{
++ struct spa_pod_frame f;
++ uint32_t i, n_props;
++
++ n_props = info->props ? info->props->n_items : 0;
++
++ spa_pod_builder_push_struct(b, &f);
++ spa_pod_builder_add(b,
++ SPA_POD_Id(info->id),
++ SPA_POD_Int(info->change_mask),
++ SPA_POD_Int(info->n_params),
++ SPA_POD_Int(n_props),
++ NULL);
++
++ for (i = 0; i < info->n_params; i++) {
++ spa_pod_builder_add(b,
++ SPA_POD_Id(info->params[i].id),
++ SPA_POD_Int(info->params[i].flags), NULL);
++ }
++
++ for (i = 0; i < n_props; i++) {
++ spa_pod_builder_add(b,
++ SPA_POD_String(info->props->items[i].key),
++ SPA_POD_String(info->props->items[i].value),
++ NULL);
++ }
++
++ spa_pod_builder_pop(b, &f);
++}
++
++/* macro because of alloca() */
++#define deserialize_pw_endpoint_info(p, f, info) \
++do { \
++ if (spa_pod_parser_push_struct(p, f) < 0 || \
++ spa_pod_parser_get(p, \
++ SPA_POD_Id(&(info)->id), \
++ SPA_POD_Int(&(info)->change_mask), \
++ SPA_POD_Int(&(info)->n_params), \
++ SPA_POD_Int(&(info)->props->n_items), \
++ NULL) < 0) \
++ return -EINVAL; \
++ \
++ if ((info)->n_params > 0) \
++ (info)->params = alloca((info)->n_params * sizeof(struct spa_param_info)); \
++ if ((info)->props->n_items > 0) \
++ (info)->props->items = alloca((info)->props->n_items * sizeof(struct spa_dict_item)); \
++ \
++ for (i = 0; i < (info)->n_params; i++) { \
++ if (spa_pod_parser_get(p, \
++ SPA_POD_Id(&(info)->params[i].id), \
++ SPA_POD_Int(&(info)->params[i].flags), \
++ NULL) < 0) \
++ return -EINVAL; \
++ } \
++ \
++ for (i = 0; i < (info)->props->n_items; i++) { \
++ if (spa_pod_parser_get(p, \
++ SPA_POD_String(&(info)->props->items[i].key), \
++ SPA_POD_String(&(info)->props->items[i].value), \
++ NULL) < 0) \
++ return -EINVAL; \
++ } \
++ \
++ spa_pod_parser_pop(p, f); \
++} while(0)
++
++static int
++endpoint_marshal_subscribe_params(void *object, uint32_t *ids, uint32_t n_ids)
++{
++ struct pw_proxy *proxy = object;
++ struct spa_pod_builder *b;
++
++ b = pw_protocol_native_begin_proxy(proxy,
++ PW_ENDPOINT_PROXY_METHOD_SUBSCRIBE_PARAMS, NULL);
++
++ spa_pod_builder_add_struct(b,
++ SPA_POD_Array(sizeof(uint32_t), SPA_TYPE_Id, n_ids, ids));
++
++ return pw_protocol_native_end_proxy(proxy, b);
++}
++
++static int
++endpoint_demarshal_subscribe_params(void *object, const struct pw_protocol_native_message *msg)
++{
++ struct pw_resource *resource = object;
++ struct spa_pod_parser prs;
++ uint32_t csize, ctype, n_ids;
++ uint32_t *ids;
++
++ spa_pod_parser_init(&prs, msg->data, msg->size);
++ if (spa_pod_parser_get_struct(&prs,
++ SPA_POD_Array(&csize, &ctype, &n_ids, &ids)) < 0)
++ return -EINVAL;
++
++ if (ctype != SPA_TYPE_Id)
++ return -EINVAL;
++
++ return pw_resource_do(resource, struct pw_endpoint_proxy_methods,
++ subscribe_params, 0, ids, n_ids);
++}
++
++static int
++endpoint_marshal_enum_params(void *object, int seq, uint32_t id,
++ uint32_t index, uint32_t num, const struct spa_pod *filter)
++{
++ struct pw_proxy *proxy = object;
++ struct spa_pod_builder *b;
++
++ b = pw_protocol_native_begin_proxy(proxy,
++ PW_ENDPOINT_PROXY_METHOD_ENUM_PARAMS, NULL);
++
++ spa_pod_builder_add_struct(b,
++ SPA_POD_Int(seq),
++ SPA_POD_Id(id),
++ SPA_POD_Int(index),
++ SPA_POD_Int(num),
++ SPA_POD_Pod(filter));
++
++ return pw_protocol_native_end_proxy(proxy, b);
++}
++
++static int
++endpoint_demarshal_enum_params(void *object, const struct pw_protocol_native_message *msg)
++{
++ struct pw_resource *resource = object;
++ struct spa_pod_parser prs;
++ uint32_t id, index, num;
++ int seq;
++ struct spa_pod *filter;
++
++ spa_pod_parser_init(&prs, msg->data, msg->size);
++ if (spa_pod_parser_get_struct(&prs,
++ SPA_POD_Int(&seq),
++ SPA_POD_Id(&id),
++ SPA_POD_Int(&index),
++ SPA_POD_Int(&num),
++ SPA_POD_Pod(&filter)) < 0)
++ return -EINVAL;
++
++ return pw_resource_do(resource, struct pw_endpoint_proxy_methods,
++ enum_params, 0, seq, id, index, num, filter);
++}
++
++static int
++endpoint_marshal_set_param(void *object, uint32_t id, uint32_t flags,
++ const struct spa_pod *param)
++{
++ struct pw_proxy *proxy = object;
++ struct spa_pod_builder *b;
++
++ b = pw_protocol_native_begin_proxy(proxy,
++ PW_ENDPOINT_PROXY_METHOD_SET_PARAM, NULL);
++
++ spa_pod_builder_add_struct(b,
++ SPA_POD_Id(id),
++ SPA_POD_Int(flags),
++ SPA_POD_Pod(param));
++ return pw_protocol_native_end_proxy(proxy, b);
++}
++
++static int
++endpoint_demarshal_set_param(void *object, const struct pw_protocol_native_message *msg)
++{
++ struct pw_resource *resource = object;
++ struct spa_pod_parser prs;
++ uint32_t id, flags;
++ struct spa_pod *param;
++
++ spa_pod_parser_init(&prs, msg->data, msg->size);
++ if (spa_pod_parser_get_struct(&prs,
++ SPA_POD_Id(&id),
++ SPA_POD_Int(&flags),
++ SPA_POD_Pod(&param)) < 0)
++ return -EINVAL;
++
++ return pw_resource_do(resource, struct pw_endpoint_proxy_methods,
++ set_param, 0, id, flags, param);
++}
++
++static void
++endpoint_marshal_info(void *object, const struct pw_endpoint_info *info)
++{
++ struct pw_resource *resource = object;
++ struct spa_pod_builder *b;
++
++ b = pw_protocol_native_begin_resource(resource,
++ PW_ENDPOINT_PROXY_EVENT_INFO, NULL);
++ serialize_pw_endpoint_info (b, info);
++ pw_protocol_native_end_resource(resource, b);
++}
++
++static int
++endpoint_demarshal_info(void *object, const struct pw_protocol_native_message *msg)
++{
++ struct pw_proxy *proxy = object;
++ struct spa_pod_parser prs;
++ struct spa_pod_frame f;
++ struct spa_dict props = SPA_DICT_INIT(NULL, 0);
++ struct pw_endpoint_info info = { .props = &props };
++ uint32_t i;
++
++ spa_pod_parser_init(&prs, msg->data, msg->size);
++
++ deserialize_pw_endpoint_info(&prs, &f, &info);
++
++ return pw_proxy_notify(proxy, struct pw_endpoint_proxy_events,
++ info, 0, &info);
++}
++
++static void
++endpoint_marshal_param(void *object, int seq, uint32_t id,
++ uint32_t index, uint32_t next, const struct spa_pod *param)
++{
++ struct pw_resource *resource = object;
++ struct spa_pod_builder *b;
++
++ b = pw_protocol_native_begin_resource(resource,
++ PW_ENDPOINT_PROXY_EVENT_PARAM, NULL);
++
++ spa_pod_builder_add_struct(b,
++ SPA_POD_Int(seq),
++ SPA_POD_Id(id),
++ SPA_POD_Int(index),
++ SPA_POD_Int(next),
++ SPA_POD_Pod(param));
++
++ pw_protocol_native_end_resource(resource, b);
++}
++
++static int
++endpoint_demarshal_param(void *object, const struct pw_protocol_native_message *msg)
++{
++ struct pw_proxy *proxy = object;
++ struct spa_pod_parser prs;
++ uint32_t id, index, next;
++ int seq;
++ struct spa_pod *param;
++
++ spa_pod_parser_init(&prs, msg->data, msg->size);
++ if (spa_pod_parser_get_struct(&prs,
++ SPA_POD_Int(&seq),
++ SPA_POD_Id(&id),
++ SPA_POD_Int(&index),
++ SPA_POD_Int(&next),
++ SPA_POD_Pod(&param)) < 0)
++ return -EINVAL;
++
++ return pw_proxy_notify(proxy, struct pw_endpoint_proxy_events, param, 0,
++ seq, id, index, next, param);
++}
++
++static const struct pw_endpoint_proxy_methods pw_protocol_native_endpoint_method_marshal = {
++ PW_VERSION_ENDPOINT_PROXY_METHODS,
++ &endpoint_marshal_subscribe_params,
++ &endpoint_marshal_enum_params,
++ &endpoint_marshal_set_param,
++};
++
++static const struct pw_protocol_native_demarshal pw_protocol_native_endpoint_method_demarshal[] = {
++ { &endpoint_demarshal_subscribe_params, 0 },
++ { &endpoint_demarshal_enum_params, 0 },
++ { &endpoint_demarshal_set_param, 0 }
++};
++
++static const struct pw_endpoint_proxy_events pw_protocol_native_endpoint_event_marshal = {
++ PW_VERSION_ENDPOINT_PROXY_EVENTS,
++ &endpoint_marshal_info,
++ &endpoint_marshal_param,
++};
++
++static const struct pw_protocol_native_demarshal pw_protocol_native_endpoint_event_demarshal[] = {
++ { &endpoint_demarshal_info, 0 },
++ { &endpoint_demarshal_param, 0 }
++};
++
++static const struct pw_protocol_marshal pw_protocol_native_endpoint_marshal = {
++ PW_TYPE_INTERFACE_Endpoint,
++ PW_VERSION_ENDPOINT,
++ PW_ENDPOINT_PROXY_METHOD_NUM,
++ PW_ENDPOINT_PROXY_EVENT_NUM,
++ &pw_protocol_native_endpoint_method_marshal,
++ &pw_protocol_native_endpoint_method_demarshal,
++ &pw_protocol_native_endpoint_event_marshal,
++ &pw_protocol_native_endpoint_event_demarshal,
++};
++
++
++static int
++client_endpoint_marshal_update(
++ void *object,
++ uint32_t change_mask,
++ uint32_t n_params,
++ const struct spa_pod **params,
++ const struct pw_endpoint_info *info)
++{
++ struct pw_proxy *proxy = object;
++ struct spa_pod_builder *b;
++ struct spa_pod_frame f;
++ uint32_t i;
++
++ b = pw_protocol_native_begin_proxy(proxy,
++ PW_CLIENT_ENDPOINT_PROXY_METHOD_UPDATE, NULL);
++
++ spa_pod_builder_push_struct(b, &f);
++ spa_pod_builder_add(b,
++ SPA_POD_Int(change_mask),
++ SPA_POD_Int(n_params), NULL);
++
++ for (i = 0; i < n_params; i++)
++ spa_pod_builder_add(b, SPA_POD_Pod(params[i]), NULL);
++
++ if (change_mask & PW_CLIENT_ENDPOINT_UPDATE_INFO)
++ serialize_pw_endpoint_info(b, info);
++
++ spa_pod_builder_pop(b, &f);
++
++ return pw_protocol_native_end_proxy(proxy, b);
++}
++
++static int
++client_endpoint_demarshal_update(void *object,
++ const struct pw_protocol_native_message *msg)
++{
++ struct pw_resource *resource = object;
++ struct spa_pod_parser prs;
++ struct spa_pod_frame f[2];
++ uint32_t change_mask, n_params;
++ const struct spa_pod **params = NULL;
++ struct spa_dict props = SPA_DICT_INIT(NULL, 0);
++ struct pw_endpoint_info info = { .props = &props };
++ uint32_t i;
++
++ spa_pod_parser_init(&prs, msg->data, msg->size);
++ if (spa_pod_parser_push_struct(&prs, &f[0]) < 0 ||
++ spa_pod_parser_get(&prs,
++ SPA_POD_Int(&change_mask),
++ SPA_POD_Int(&n_params), NULL) < 0)
++ return -EINVAL;
++
++ if (n_params > 0)
++ params = alloca(n_params * sizeof(struct spa_pod *));
++ for (i = 0; i < n_params; i++)
++ if (spa_pod_parser_get(&prs,
++ SPA_POD_PodObject(&params[i]), NULL) < 0)
++ return -EINVAL;
++
++ if (change_mask & PW_CLIENT_ENDPOINT_UPDATE_INFO)
++ deserialize_pw_endpoint_info(&prs, &f[1], &info);
++
++ pw_resource_do(resource, struct pw_client_endpoint_proxy_methods,
++ update, 0, change_mask, n_params, params, &info);
++ return 0;
++}
++
++static void
++client_endpoint_marshal_set_param (void *object,
++ uint32_t id, uint32_t flags,
++ const struct spa_pod *param)
++{
++ struct pw_resource *resource = object;
++ struct spa_pod_builder *b;
++
++ b = pw_protocol_native_begin_resource(resource,
++ PW_CLIENT_ENDPOINT_PROXY_EVENT_SET_PARAM, NULL);
++
++ spa_pod_builder_add_struct(b,
++ SPA_POD_Id(id),
++ SPA_POD_Int(flags),
++ SPA_POD_Pod(param));
++
++ pw_protocol_native_end_resource(resource, b);
++}
++
++static int
++client_endpoint_demarshal_set_param(void *object,
++ const struct pw_protocol_native_message *msg)
++{
++ struct pw_proxy *proxy = object;
++ struct spa_pod_parser prs;
++ uint32_t id, flags;
++ const struct spa_pod *param = NULL;
++
++ spa_pod_parser_init(&prs, msg->data, msg->size);
++ if (spa_pod_parser_get_struct(&prs,
++ SPA_POD_Id(&id),
++ SPA_POD_Int(&flags),
++ SPA_POD_PodObject(&param)) < 0)
++ return -EINVAL;
++
++ pw_proxy_notify(proxy, struct pw_client_endpoint_proxy_events,
++ set_param, 0, id, flags, param);
++ return 0;
++}
++
++static const struct pw_client_endpoint_proxy_methods pw_protocol_native_client_endpoint_method_marshal = {
++ PW_VERSION_CLIENT_ENDPOINT_PROXY_METHODS,
++ &client_endpoint_marshal_update,
++};
++
++static const struct pw_protocol_native_demarshal pw_protocol_native_client_endpoint_method_demarshal[] = {
++ { &client_endpoint_demarshal_update, 0 }
++};
++
++static const struct pw_client_endpoint_proxy_events pw_protocol_native_client_endpoint_event_marshal = {
++ PW_VERSION_CLIENT_ENDPOINT_PROXY_EVENTS,
++ &client_endpoint_marshal_set_param,
++};
++
++static const struct pw_protocol_native_demarshal pw_protocol_native_client_endpoint_event_demarshal[] = {
++ { &client_endpoint_demarshal_set_param, 0 }
++};
++
++static const struct pw_protocol_marshal pw_protocol_native_client_endpoint_marshal = {
++ PW_TYPE_INTERFACE_ClientEndpoint,
++ PW_VERSION_CLIENT_ENDPOINT,
++ PW_CLIENT_ENDPOINT_PROXY_METHOD_NUM,
++ PW_CLIENT_ENDPOINT_PROXY_EVENT_NUM,
++ &pw_protocol_native_client_endpoint_method_marshal,
++ &pw_protocol_native_client_endpoint_method_demarshal,
++ &pw_protocol_native_client_endpoint_event_marshal,
++ &pw_protocol_native_client_endpoint_event_demarshal,
++};
++
++struct pw_protocol *pw_protocol_native_ext_endpoint_init(struct pw_core *core)
++{
++ struct pw_protocol *protocol;
++
++ protocol = pw_core_find_protocol(core, PW_TYPE_INFO_PROTOCOL_Native);
++
++ if (protocol == NULL)
++ return NULL;
++
++ pw_protocol_add_marshal(protocol, &pw_protocol_native_client_endpoint_marshal);
++ pw_protocol_add_marshal(protocol, &pw_protocol_native_endpoint_marshal);
++
++ return protocol;
++}
+diff --git a/src/pipewire/pipewire.c b/src/pipewire/pipewire.c
+index a8752438..bbbf9420 100644
+--- a/src/pipewire/pipewire.c
++++ b/src/pipewire/pipewire.c
+@@ -647,6 +647,8 @@ static const struct spa_type_info type_info[] = {
+ { PW_TYPE_INTERFACE_Module, SPA_TYPE_Pointer, PW_TYPE_INFO_INTERFACE_BASE "Module", NULL },
+ { PW_TYPE_INTERFACE_ClientNode, SPA_TYPE_Pointer, PW_TYPE_INFO_INTERFACE_BASE "ClientNode", NULL },
+ { PW_TYPE_INTERFACE_Device, SPA_TYPE_Pointer, PW_TYPE_INFO_INTERFACE_BASE "Device", NULL },
++ { PW_TYPE_INTERFACE_Endpoint, SPA_TYPE_Pointer, PW_TYPE_INFO_INTERFACE_BASE "Endpoint", NULL},
++ { PW_TYPE_INTERFACE_ClientEndpoint, SPA_TYPE_Pointer, PW_TYPE_INFO_INTERFACE_BASE "ClientEndpoint", NULL},
+ { SPA_ID_INVALID, SPA_ID_INVALID, "spa_types", spa_types },
+ { 0, 0, NULL, NULL },
+ };
+diff --git a/src/pipewire/type.h b/src/pipewire/type.h
+index a1b205f7..39544913 100644
+--- a/src/pipewire/type.h
++++ b/src/pipewire/type.h
+@@ -48,7 +48,8 @@ enum {
+ /* extensions */
+ PW_TYPE_INTERFACE_EXTENSIONS = PW_TYPE_INTERFACE_START + 0x1000,
+ PW_TYPE_INTERFACE_ClientNode,
+-
++ PW_TYPE_INTERFACE_Endpoint,
++ PW_TYPE_INTERFACE_ClientEndpoint,
+ };
+
+ #define PW_TYPE_INFO_BASE "PipeWire:"
+--
+2.20.1
+
diff --git a/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/0003-pipewire-cli-add-support-for-printing-endpoint-info-.patch b/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/0003-pipewire-cli-add-support-for-printing-endpoint-info-.patch
new file mode 100644
index 00000000..a709abdf
--- /dev/null
+++ b/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/0003-pipewire-cli-add-support-for-printing-endpoint-info-.patch
@@ -0,0 +1,149 @@
+From 0e9fe3cfb19c751655f16ce4b8c6100269f23ff2 Mon Sep 17 00:00:00 2001
+From: George Kiagiadakis <george.kiagiadakis@collabora.com>
+Date: Tue, 28 May 2019 11:46:36 +0300
+Subject: [PATCH] pipewire-cli: add support for printing endpoint info & params
+
+Note that you have to run:
+ load-module libpipewire-module-endpoint
+before trying to query any endpoints
+
+Upstream-Status: Pending
+---
+ src/tools/pipewire-cli.c | 78 +++++++++++++++++++++++++++++++++++++++-
+ 1 file changed, 77 insertions(+), 1 deletion(-)
+
+diff --git a/src/tools/pipewire-cli.c b/src/tools/pipewire-cli.c
+index 521739f6..9511db82 100644
+--- a/src/tools/pipewire-cli.c
++++ b/src/tools/pipewire-cli.c
+@@ -38,6 +38,8 @@
+ #include <pipewire/type.h>
+ #include <pipewire/permission.h>
+
++#include <extensions/endpoint.h>
++
+ static const char WHITESPACE[] = " \t";
+
+ struct remote_data;
+@@ -176,8 +178,12 @@ static void print_params(struct spa_param_info *params, uint32_t n_params, char
+ return;
+ }
+ for (i = 0; i < n_params; i++) {
++ const struct spa_type_info *type_info = spa_type_param;
++ if (params[i].id >= PW_ENDPOINT_PARAM_EnumControl)
++ type_info = endpoint_param_type_info;
++
+ fprintf(stdout, "%c\t %d (%s) %c%c\n", mark, params[i].id,
+- spa_debug_type_find_name(spa_type_param, params[i].id),
++ spa_debug_type_find_name(type_info, params[i].id),
+ params[i].flags & SPA_PARAM_INFO_READ ? 'r' : '-',
+ params[i].flags & SPA_PARAM_INFO_WRITE ? 'w' : '-');
+ }
+@@ -641,6 +647,16 @@ static void info_device(struct proxy_data *pd)
+ info->change_mask = 0;
+ }
+
++static void info_endpoint(struct proxy_data *pd)
++{
++ struct pw_endpoint_info *info = pd->info;
++
++ info_global(pd);
++ print_properties(info->props, MARK_CHANGE(1), true);
++ print_params(info->params, info->n_params, MARK_CHANGE(0), true);
++ info->change_mask = 0;
++}
++
+ static void core_event_info(void *object, const struct pw_core_info *info)
+ {
+ struct proxy_data *pd = object;
+@@ -708,6 +724,9 @@ static void event_param(void *object, int seq, uint32_t id,
+
+ if (spa_pod_is_object_type(param, SPA_TYPE_OBJECT_Format))
+ spa_debug_format(2, NULL, param);
++ else if (spa_pod_is_object_type(param, PW_ENDPOINT_OBJECT_ParamControl) ||
++ spa_pod_is_object_type(param, PW_ENDPOINT_OBJECT_ParamStream))
++ spa_debug_pod(2, endpoint_param_object_type_info, param);
+ else
+ spa_debug_pod(2, NULL, param);
+ }
+@@ -842,6 +861,53 @@ static const struct pw_device_proxy_events device_events = {
+ .param = event_param
+ };
+
++static void endpoint_info_free(struct pw_endpoint_info *info)
++{
++ free(info->params);
++ if (info->props)
++ pw_properties_free ((struct pw_properties *)info->props);
++ free(info);
++}
++
++static void endpoint_event_info(void *object,
++ const struct pw_endpoint_info *update)
++{
++ struct proxy_data *pd = object;
++ struct remote_data *rd = pd->rd;
++ struct pw_endpoint_info *info = pd->info;
++
++ if (!info) {
++ info = pd->info = calloc(1, sizeof(*info));
++ info->id = update->id;
++ }
++ if (update->change_mask & PW_ENDPOINT_CHANGE_MASK_PARAMS) {
++ info->n_params = update->n_params;
++ free(info->params);
++ info->params = malloc(info->n_params * sizeof(struct spa_param_info));
++ memcpy(info->params, update->params,
++ info->n_params * sizeof(struct spa_param_info));
++ }
++ if (update->change_mask & PW_ENDPOINT_CHANGE_MASK_PROPS) {
++ if (info->props)
++ pw_properties_free ((struct pw_properties *)info->props);
++ info->props =
++ (struct spa_dict *) pw_properties_new_dict (update->props);
++ }
++
++ if (pd->global == NULL)
++ pd->global = pw_map_lookup(&rd->globals, info->id);
++ if (pd->global && pd->global->info_pending) {
++ info_endpoint(pd);
++ pd->global->info_pending = false;
++ }
++}
++
++static const struct pw_endpoint_proxy_events endpoint_events = {
++ PW_VERSION_ENDPOINT_PROXY_EVENTS,
++ .info = endpoint_event_info,
++ .param = event_param
++};
++
+ static void
+ destroy_proxy (void *data)
+ {
+@@ -928,6 +994,12 @@ static bool bind_global(struct remote_data *rd, struct global *global, char **er
+ destroy = (pw_destroy_t) pw_link_info_free;
+ info_func = info_link;
+ break;
++ case PW_TYPE_INTERFACE_Endpoint:
++ events = &endpoint_events;
++ client_version = PW_VERSION_ENDPOINT;
++ destroy = (pw_destroy_t) endpoint_info_free;
++ info_func = info_endpoint;
++ break;
+ default:
+ asprintf(error, "unsupported type %s", spa_debug_type_find_name(pw_type_info(), global->type));
+ return false;
+@@ -1201,6 +1273,10 @@ static bool do_enum_params(struct data *data, const char *cmd, char *args, char
+ pw_device_proxy_enum_params((struct pw_device_proxy*)global->proxy, 0,
+ param_id, 0, 0, NULL);
+ break;
++ case PW_TYPE_INTERFACE_Endpoint:
++ pw_endpoint_proxy_enum_params((struct pw_endpoint_proxy*)global->proxy, 0,
++ param_id, 0, 0, NULL);
++ break;
+ default:
+ asprintf(error, "enum-params not implemented on object %d", atoi(a[0]));
+ return false;
+--
+2.20.1
+
diff --git a/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/0004-pipewire-cli-add-command-to-modify-endpoint-control-.patch b/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/0004-pipewire-cli-add-command-to-modify-endpoint-control-.patch
new file mode 100644
index 00000000..4394d60d
--- /dev/null
+++ b/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/0004-pipewire-cli-add-command-to-modify-endpoint-control-.patch
@@ -0,0 +1,124 @@
+From 824c8abf88e9ee82567c177145798b619298ab91 Mon Sep 17 00:00:00 2001
+From: George Kiagiadakis <george.kiagiadakis@collabora.com>
+Date: Wed, 5 Jun 2019 14:57:37 +0300
+Subject: [PATCH] pipewire-cli: add command to modify endpoint control values
+
+Upstream-Status: Pending
+---
+ src/tools/pipewire-cli.c | 86 ++++++++++++++++++++++++++++++++++++++++
+ 1 file changed, 86 insertions(+)
+
+diff --git a/src/tools/pipewire-cli.c b/src/tools/pipewire-cli.c
+index 9511db82..b52ab100 100644
+--- a/src/tools/pipewire-cli.c
++++ b/src/tools/pipewire-cli.c
+@@ -210,6 +210,7 @@ static bool do_export_node(struct data *data, const char *cmd, char *args, char
+ static bool do_enum_params(struct data *data, const char *cmd, char *args, char **error);
+ static bool do_permissions(struct data *data, const char *cmd, char *args, char **error);
+ static bool do_get_permissions(struct data *data, const char *cmd, char *args, char **error);
++static bool do_endpoint_control(struct data *data, const char *cmd, char *args, char **error);
+
+ static struct command command_list[] = {
+ { "help", "Show this help", do_help },
+@@ -228,6 +229,7 @@ static struct command command_list[] = {
+ { "enum-params", "Enumerate params of an object <object-id> [<param-id-name>]", do_enum_params },
+ { "permissions", "Set permissions for a client <client-id> <permissions>", do_permissions },
+ { "get-permissions", "Get permissions of a client <client-id>", do_get_permissions },
++ { "endpoint-control", "Set control value on an endpoint <object-id> <control-id> <type: b|i|l|d> <value>", do_endpoint_control },
+ };
+
+ static bool do_help(struct data *data, const char *cmd, char *args, char **error)
+@@ -1357,6 +1359,90 @@ static bool do_get_permissions(struct data *data, const char *cmd, char *args, c
+ return true;
+ }
+
++static bool do_endpoint_control(struct data *data, const char *cmd, char *args, char **error)
++{
++ struct remote_data *rd = data->current;
++ int n;
++ char *a[4];
++ uint32_t id, control_id;
++ struct global *global;
++ char buffer[1024];
++ struct spa_pod_builder b;
++ struct spa_pod_frame f;
++ struct spa_pod *param;
++
++ n = pw_split_ip(args, WHITESPACE, 4, a);
++ if (n < 4) {
++ asprintf(error, "%s <object-id> <control-id> <type: b|i|l|d> <value>", cmd);
++ return false;
++ }
++
++ id = atoi(a[0]);
++ global = pw_map_lookup(&rd->globals, id);
++ if (global == NULL) {
++ asprintf(error, "%s: unknown global %d", cmd, id);
++ return false;
++ }
++ if (global->type != PW_TYPE_INTERFACE_Endpoint) {
++ asprintf(error, "object %d is not an endpoint", atoi(a[0]));
++ return false;
++ }
++ if (global->proxy == NULL) {
++ if (!bind_global(rd, global, error))
++ return false;
++ }
++
++ control_id = atoi(a[1]);
++
++ spa_pod_builder_init(&b, buffer, 1024);
++ spa_pod_builder_push_object (&b, &f,
++ PW_ENDPOINT_OBJECT_ParamControl, PW_ENDPOINT_PARAM_Control);
++ spa_pod_builder_add(&b,
++ PW_ENDPOINT_PARAM_CONTROL_id, SPA_POD_Int(control_id),
++ NULL);
++
++ switch (*a[2]) {
++ case 'b': {
++ bool val = atoi(a[3]);
++ spa_pod_builder_add(&b,
++ PW_ENDPOINT_PARAM_CONTROL_value, SPA_POD_Bool(val),
++ NULL);
++ break;
++ }
++ case 'i': {
++ int val = atoi(a[3]);
++ spa_pod_builder_add(&b,
++ PW_ENDPOINT_PARAM_CONTROL_value, SPA_POD_Int(val),
++ NULL);
++ break;
++ }
++ case 'l': {
++ int64_t val = strtoll(a[3], NULL, 10);
++ spa_pod_builder_add(&b,
++ PW_ENDPOINT_PARAM_CONTROL_value, SPA_POD_Long(val),
++ NULL);
++ break;
++ }
++ case 'd': {
++ double val = strtod(a[3], NULL);
++ spa_pod_builder_add(&b,
++ PW_ENDPOINT_PARAM_CONTROL_value, SPA_POD_Double(val),
++ NULL);
++ break;
++ }
++ default:
++ asprintf(error, "%s: unknown value type %s", cmd, a[2]);
++ return false;
++ }
++
++ param = spa_pod_builder_pop(&b, &f);
++
++ pw_endpoint_proxy_set_param((struct pw_endpoint_proxy *) global->proxy,
++ PW_ENDPOINT_PARAM_Control, 0, param);
++
++ return true;
++}
++
+ static bool parse(struct data *data, char *buf, size_t size, char **error)
+ {
+ char *a[2];
+--
+2.20.1
+
diff --git a/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/0005-arm-build-with-mno-unaligned-access.patch b/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/0005-arm-build-with-mno-unaligned-access.patch
new file mode 100644
index 00000000..a670e7ff
--- /dev/null
+++ b/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/0005-arm-build-with-mno-unaligned-access.patch
@@ -0,0 +1,30 @@
+From 2016605938f02835c75928648e99b25f7248aa5b Mon Sep 17 00:00:00 2001
+From: George Kiagiadakis <george.kiagiadakis@collabora.com>
+Date: Mon, 24 Jun 2019 12:19:20 +0300
+Subject: [PATCH] arm: build with -mno-unaligned-access
+
+Upstream-Status: Inappropriate [workaround]
+See also https://github.com/PipeWire/pipewire/issues/161
+---
+ meson.build | 5 +++++
+ 1 file changed, 5 insertions(+)
+
+diff --git a/meson.build b/meson.build
+index 81303d27..f3cc6030 100644
+--- a/meson.build
++++ b/meson.build
+@@ -50,6 +50,11 @@ if cc.get_id() == 'gcc'
+ language : 'c')
+ endif
+
++if host_machine.cpu_family() == 'arm'
++ add_global_arguments('-mno-unaligned-access',
++ language: 'c')
++endif
++
+ sse_args = '-msse'
+ sse2_args = '-msse2'
+ ssse3_args = '-mssse3'
+--
+2.20.1
+
diff --git a/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/0006-logger-print-timestamps-on-logged-messages.patch b/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/0006-logger-print-timestamps-on-logged-messages.patch
new file mode 100644
index 00000000..4d9117f0
--- /dev/null
+++ b/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/0006-logger-print-timestamps-on-logged-messages.patch
@@ -0,0 +1,52 @@
+From 352c58357e5922b21d664c1f5a0b89a74f864f41 Mon Sep 17 00:00:00 2001
+From: George Kiagiadakis <george.kiagiadakis@collabora.com>
+Date: Wed, 3 Jul 2019 17:47:46 +0300
+Subject: [PATCH] logger: print timestamps on logged messages
+
+Timestamps have usec precision and the seconds are limited
+to 9 digits. Usually what matters in these messages is to spot
+delays between printouts and not really what is the absolute
+time of the system.
+
+Upstream-Status: Submitted [https://github.com/PipeWire/pipewire/pull/164]
+---
+ spa/plugins/support/logger.c | 9 +++++++--
+ 1 file changed, 7 insertions(+), 2 deletions(-)
+
+diff --git a/spa/plugins/support/logger.c b/spa/plugins/support/logger.c
+index 87ba3c21..2976601c 100644
+--- a/spa/plugins/support/logger.c
++++ b/spa/plugins/support/logger.c
+@@ -27,6 +27,7 @@
+ #include <string.h>
+ #include <errno.h>
+ #include <stdio.h>
++#include <time.h>
+ #include <sys/eventfd.h>
+
+ #include <spa/support/log.h>
+@@ -70,6 +71,9 @@ impl_log_logv(struct spa_log *log,
+ const char *prefix = "", *suffix = "";
+ int size;
+ bool do_trace;
++ struct timespec now;
++
++ clock_gettime(CLOCK_MONOTONIC_RAW, &now);
+
+ if ((do_trace = (level == SPA_LOG_LEVEL_TRACE && impl->have_source)))
+ level++;
+@@ -86,8 +90,9 @@ impl_log_logv(struct spa_log *log,
+ }
+
+ vsnprintf(text, sizeof(text), fmt, args);
+- size = snprintf(location, sizeof(location), "%s[%s][%s:%i %s()] %s%s\n",
+- prefix, levels[level], strrchr(file, '/') + 1, line, func, text, suffix);
++ size = snprintf(location, sizeof(location), "%s[%s][%09lu.%06lu][%s:%i %s()] %s%s\n",
++ prefix, levels[level], now.tv_sec & 0x1FFFFFFF, now.tv_nsec / 1000,
++ strrchr(file, '/') + 1, line, func, text, suffix);
+
+ if (SPA_UNLIKELY(do_trace)) {
+ uint32_t index;
+--
+2.20.1
+
diff --git a/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/0007-alsa-make-corrections-on-the-timeout-based-on-how-fa.patch b/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/0007-alsa-make-corrections-on-the-timeout-based-on-how-fa.patch
new file mode 100644
index 00000000..86495014
--- /dev/null
+++ b/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/0007-alsa-make-corrections-on-the-timeout-based-on-how-fa.patch
@@ -0,0 +1,130 @@
+From 05a3a926df4906cdf60f7978843c637bbf37714a Mon Sep 17 00:00:00 2001
+From: George Kiagiadakis <george.kiagiadakis@collabora.com>
+Date: Tue, 9 Jul 2019 18:06:18 +0300
+Subject: [PATCH] alsa: make corrections on the timeout based on how fast ALSA
+ consumes samples
+
+This feels a bit hacky, but it actually makes huge difference when pipewire is
+running in qemu.
+
+The idea is that it keeps track of how much samples are in the device
+(fill level) and calculates how many are consumed when a timeout occurs.
+Then it converts that into a time based on the sample rate and compares it to
+the system clock time that elapsed since the last write to the device.
+The division between the two gives a rate (drift) that can be used to shorten
+the timeout window.
+
+So for instance, if the timeout window was 21.3 ms, but the device actually
+consumed an equivalent of 28 ms in samples, the drift will be 21.3/28 = 0.76
+and the next timeout window will be approximately 21.3 * 0.76 = 16.1 ms
+
+To avoid making things worse, the drift is clamped between 0.6 and 1.0.
+Min 0.6 was arbitrarily chosen, but sometimes alsa does report strange numbers,
+causing the drift to be very low, which in turn causes an early wakeup.
+Max 1.0 basically means that we don't care if the device is consuming samples
+slower. In that case, the early wakeup mechanism will throttle pipewire.
+
+Fixes #163
+
+Upstream-Status: Submitted [https://github.com/PipeWire/pipewire/pull/166]
+---
+ spa/plugins/alsa/alsa-utils.c | 31 ++++++++++++++++++++++++++-----
+ spa/plugins/alsa/alsa-utils.h | 2 ++
+ 2 files changed, 28 insertions(+), 5 deletions(-)
+
+diff --git a/spa/plugins/alsa/alsa-utils.c b/spa/plugins/alsa/alsa-utils.c
+index 87582c1c..872969bf 100644
+--- a/spa/plugins/alsa/alsa-utils.c
++++ b/spa/plugins/alsa/alsa-utils.c
+@@ -593,7 +593,21 @@ static int get_status(struct state *state, snd_pcm_sframes_t *delay)
+
+ static int update_time(struct state *state, uint64_t nsec, snd_pcm_sframes_t delay, bool slave)
+ {
+- double err, corr;
++ double err, corr, drift;
++ snd_pcm_sframes_t consumed;
++
++ consumed = state->fill_level - delay;
++ if (state->alsa_started && consumed > 0) {
++ double sysclk_diff = nsec - state->last_time;
++ double devclk_diff = ((double) consumed) * 1e9 / state->rate;
++ drift = sysclk_diff / devclk_diff;
++ drift = SPA_CLAMP(drift, 0.6, 1.0);
++
++ spa_log_trace_fp(state->log, "cons:%ld sclk:%f dclk:%f drift:%f",
++ consumed, sysclk_diff, devclk_diff, drift);
++ } else {
++ drift = 1.0;
++ }
+
+ if (state->stream == SND_PCM_STREAM_PLAYBACK)
+ err = delay - state->last_threshold;
+@@ -650,11 +664,11 @@ static int update_time(struct state *state, uint64_t nsec, snd_pcm_sframes_t del
+ state->clock->rate_diff = corr;
+ }
+
+- spa_log_trace_fp(state->log, "slave:%d %"PRIu64" %f %ld %f %f %d", slave, nsec,
+- corr, delay, err, state->threshold * corr,
++ spa_log_trace_fp(state->log, "slave:%d %"PRIu64" %f %ld %f %f %f %d", slave, nsec,
++ corr, delay, err, state->threshold * corr, drift,
+ state->threshold);
+
+- state->next_time += state->threshold / corr * 1e9 / state->rate;
++ state->next_time += state->threshold / corr * drift * 1e9 / state->rate;
+ state->last_threshold = state->threshold;
+
+ return 0;
+@@ -783,6 +797,10 @@ again:
+ goto again;
+
+ state->sample_count += total_written;
++ state->fill_level += total_written;
++
++ clock_gettime(CLOCK_MONOTONIC, &state->now);
++ state->last_time = SPA_TIMESPEC_TO_NSEC (&state->now);
+
+ if (!state->alsa_started && total_written > 0) {
+ spa_log_trace(state->log, "snd_pcm_start %lu", written);
+@@ -935,7 +953,7 @@ static int handle_play(struct state *state, uint64_t nsec, snd_pcm_sframes_t del
+ {
+ int res;
+
+- if (delay >= state->last_threshold * 2) {
++ if (delay > state->last_threshold * 2) {
+ spa_log_trace(state->log, "early wakeup %ld %d", delay, state->threshold);
+ state->next_time = nsec + (delay - state->last_threshold) * SPA_NSEC_PER_SEC / state->rate;
+ return -EAGAIN;
+@@ -944,6 +962,8 @@ static int handle_play(struct state *state, uint64_t nsec, snd_pcm_sframes_t del
+ if ((res = update_time(state, nsec, delay, false)) < 0)
+ return res;
+
++ state->fill_level = delay;
++
+ if (spa_list_is_empty(&state->ready)) {
+ struct spa_io_buffers *io = state->io;
+
+@@ -1072,6 +1092,7 @@ int spa_alsa_start(struct state *state)
+
+ state->slaved = is_slaved(state);
+ state->last_threshold = state->threshold;
++ state->fill_level = 0;
+
+ init_loop(state);
+ state->safety = 0.0;
+diff --git a/spa/plugins/alsa/alsa-utils.h b/spa/plugins/alsa/alsa-utils.h
+index a862873f..b53890b5 100644
+--- a/spa/plugins/alsa/alsa-utils.h
++++ b/spa/plugins/alsa/alsa-utils.h
+@@ -134,7 +134,9 @@ struct state {
+ int64_t sample_time;
+ uint64_t next_time;
+ uint64_t base_time;
++ uint64_t last_time;
+
++ snd_pcm_uframes_t fill_level;
+ uint64_t underrun;
+ double safety;
+
+--
+2.20.1
+
diff --git a/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/0008-audio-dsp-allow-mode-to-be-set-with-a-property.patch b/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/0008-audio-dsp-allow-mode-to-be-set-with-a-property.patch
new file mode 100644
index 00000000..681bae69
--- /dev/null
+++ b/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/0008-audio-dsp-allow-mode-to-be-set-with-a-property.patch
@@ -0,0 +1,45 @@
+From 0c85e1110e32720785f861c7a85cf0636394eee4 Mon Sep 17 00:00:00 2001
+From: Julian Bouzas <julian.bouzas@collabora.com>
+Date: Thu, 27 Jun 2019 12:44:39 -0400
+Subject: [PATCH] audio-dsp: allow mode to be set with a property
+
+Upstream-Status: Submitted [https://github.com/PipeWire/pipewire/pull/167]
+---
+ src/modules/module-audio-dsp/audio-dsp.c | 7 +++++--
+ 1 file changed, 5 insertions(+), 2 deletions(-)
+
+diff --git a/src/modules/module-audio-dsp/audio-dsp.c b/src/modules/module-audio-dsp/audio-dsp.c
+index be8a7592..68249307 100644
+--- a/src/modules/module-audio-dsp/audio-dsp.c
++++ b/src/modules/module-audio-dsp/audio-dsp.c
+@@ -264,7 +264,7 @@ struct pw_node *pw_audio_dsp_new(struct pw_core *core,
+ {
+ struct pw_node *node;
+ struct node *n;
+- const char *api, *alias, *str, *factory;
++ const char *api, *alias, *str, *factory, *mode;
+ char node_name[128];
+ struct pw_properties *pr;
+ int i;
+@@ -279,6 +279,7 @@ struct pw_node *pw_audio_dsp_new(struct pw_core *core,
+ pw_log_error("missing audio-dsp.name property");
+ goto error;
+ }
++ mode = pw_properties_get(pr, "audio-dsp.mode");
+
+ snprintf(node_name, sizeof(node_name), "system_%s", alias);
+ for (i = 0; node_name[i]; i++) {
+@@ -296,7 +297,9 @@ struct pw_node *pw_audio_dsp_new(struct pw_core *core,
+ if ((str = pw_properties_get(pr, "node.id")) != NULL)
+ pw_properties_set(pr, "node.session", str);
+
+- if (direction == PW_DIRECTION_OUTPUT) {
++ if (mode) {
++ factory = mode;
++ } else if (direction == PW_DIRECTION_OUTPUT) {
+ pw_properties_set(pr, "merger.monitor", "1");
+ factory = "merge";
+ } else {
+--
+2.20.1
+
diff --git a/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/0009-audioconvert-do-setup-internal-links-and-buffers-als.patch b/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/0009-audioconvert-do-setup-internal-links-and-buffers-als.patch
new file mode 100644
index 00000000..87141e91
--- /dev/null
+++ b/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/0009-audioconvert-do-setup-internal-links-and-buffers-als.patch
@@ -0,0 +1,55 @@
+From ddcda9fa6ec49168c5ddd9fbeda748c5fad18fce Mon Sep 17 00:00:00 2001
+From: George Kiagiadakis <george.kiagiadakis@collabora.com>
+Date: Wed, 10 Jul 2019 14:53:15 +0300
+Subject: [PATCH] audioconvert: do setup internal links and buffers also in
+ convert mode
+
+Upstream-Status: Submitted [https://github.com/PipeWire/pipewire/pull/167]
+---
+ spa/plugins/audioconvert/audioconvert.c | 10 ++++++----
+ 1 file changed, 6 insertions(+), 4 deletions(-)
+
+diff --git a/spa/plugins/audioconvert/audioconvert.c b/spa/plugins/audioconvert/audioconvert.c
+index fa8dec97..0af0732d 100644
+--- a/spa/plugins/audioconvert/audioconvert.c
++++ b/spa/plugins/audioconvert/audioconvert.c
+@@ -79,6 +79,8 @@ struct impl {
+ #define MODE_MERGE 1
+ #define MODE_CONVERT 2
+ int mode;
++ bool fmt_is_set[2];
++ bool buffers_set[2];
+ bool started;
+
+ struct spa_handle *hnd_fmt[2];
+@@ -791,11 +793,11 @@ impl_node_port_set_param(struct spa_node *node,
+ if (id == SPA_PARAM_Format) {
+ if (param == NULL)
+ clean_convert(this);
+- else if ((direction == SPA_DIRECTION_OUTPUT && this->mode == MODE_MERGE) ||
+- (direction == SPA_DIRECTION_INPUT && this->mode == MODE_SPLIT)) {
++ else if (this->fmt_is_set[SPA_DIRECTION_REVERSE(direction)]) {
+ if ((res = setup_convert(this)) < 0)
+ return res;
+ }
++ this->fmt_is_set[direction] = (param != NULL);
+ }
+ return res;
+ }
+@@ -824,11 +826,11 @@ impl_node_port_use_buffers(struct spa_node *node,
+ direction, port_id, buffers, n_buffers)) < 0)
+ return res;
+
+- if ((direction == SPA_DIRECTION_OUTPUT && this->mode == MODE_MERGE) ||
+- (direction == SPA_DIRECTION_INPUT && this->mode == MODE_SPLIT)) {
++ if (buffers && this->buffers_set[SPA_DIRECTION_REVERSE(direction)]) {
+ if ((res = setup_buffers(this, SPA_DIRECTION_INPUT)) < 0)
+ return res;
+ }
++ this->buffers_set[direction] = (buffers != NULL);
+ return res;
+ }
+
+--
+2.20.1
+
diff --git a/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/0010-gst-Implement-new-pwaudio-src-sink-elements-based-on.patch b/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/0010-gst-Implement-new-pwaudio-src-sink-elements-based-on.patch
new file mode 100644
index 00000000..6b1a6441
--- /dev/null
+++ b/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/0010-gst-Implement-new-pwaudio-src-sink-elements-based-on.patch
@@ -0,0 +1,1249 @@
+From bbc875ec4268a88bf2465244e089b119011e7479 Mon Sep 17 00:00:00 2001
+From: George Kiagiadakis <george.kiagiadakis@collabora.com>
+Date: Tue, 19 Feb 2019 18:23:19 +0200
+Subject: [PATCH] gst: Implement new pwaudio{src,sink} elements, based on
+ GstAudioBase{Src,Sink}
+
+These are much more reliable elements to use for audio data.
+* GstAudioBaseSink provides a reliable clock implementation based
+ on the number of samples read/written
+* on the pipewire side we make sure to dequeue, fill and enqueue
+ a single buffer inside the process() function, which avoids
+ underruns
+
+Both elements share a common ringbuffer that actually implements
+the pipewire integration.
+
+Upstream-Status: Submitted [https://github.com/PipeWire/pipewire/pull/140]
+---
+ src/gst/gstpipewire.c | 8 +-
+ src/gst/gstpwaudioringbuffer.c | 542 +++++++++++++++++++++++++++++++++
+ src/gst/gstpwaudioringbuffer.h | 83 +++++
+ src/gst/gstpwaudiosink.c | 200 ++++++++++++
+ src/gst/gstpwaudiosink.h | 48 +++
+ src/gst/gstpwaudiosrc.c | 200 ++++++++++++
+ src/gst/gstpwaudiosrc.h | 48 +++
+ src/gst/meson.build | 6 +
+ 8 files changed, 1134 insertions(+), 1 deletion(-)
+ create mode 100644 src/gst/gstpwaudioringbuffer.c
+ create mode 100644 src/gst/gstpwaudioringbuffer.h
+ create mode 100644 src/gst/gstpwaudiosink.c
+ create mode 100644 src/gst/gstpwaudiosink.h
+ create mode 100644 src/gst/gstpwaudiosrc.c
+ create mode 100644 src/gst/gstpwaudiosrc.h
+
+diff --git a/src/gst/gstpipewire.c b/src/gst/gstpipewire.c
+index 4040264b..68fd446f 100644
+--- a/src/gst/gstpipewire.c
++++ b/src/gst/gstpipewire.c
+@@ -40,6 +40,8 @@
+ #include "gstpipewiresrc.h"
+ #include "gstpipewiresink.h"
+ #include "gstpipewiredeviceprovider.h"
++#include "gstpwaudiosrc.h"
++#include "gstpwaudiosink.h"
+
+ GST_DEBUG_CATEGORY (pipewire_debug);
+
+@@ -52,12 +54,16 @@ plugin_init (GstPlugin *plugin)
+ GST_TYPE_PIPEWIRE_SRC);
+ gst_element_register (plugin, "pipewiresink", GST_RANK_NONE,
+ GST_TYPE_PIPEWIRE_SINK);
++ gst_element_register (plugin, "pwaudiosrc", GST_RANK_NONE,
++ GST_TYPE_PW_AUDIO_SRC);
++ gst_element_register (plugin, "pwaudiosink", GST_RANK_NONE,
++ GST_TYPE_PW_AUDIO_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");
++ GST_DEBUG_CATEGORY_INIT (pipewire_debug, "pipewire", 0, "PipeWire elements");
+
+ return TRUE;
+ }
+diff --git a/src/gst/gstpwaudioringbuffer.c b/src/gst/gstpwaudioringbuffer.c
+new file mode 100644
+index 00000000..989b2cd7
+--- /dev/null
++++ b/src/gst/gstpwaudioringbuffer.c
+@@ -0,0 +1,542 @@
++/* PipeWire
++ *
++ * Copyright © 2018 Wim Taymans
++ * Copyright © 2019 Collabora Ltd.
++ * @author George Kiagiadakis <george.kiagiadakis@collabora.com>
++ *
++ * Permission is hereby granted, free of charge, to any person obtaining a
++ * copy of this software and associated documentation files (the "Software"),
++ * to deal in the Software without restriction, including without limitation
++ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
++ * and/or sell copies of the Software, and to permit persons to whom the
++ * Software is furnished to do so, subject to the following conditions:
++ *
++ * The above copyright notice and this permission notice (including the next
++ * paragraph) shall be included in all copies or substantial portions of the
++ * Software.
++ *
++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
++ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
++ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
++ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
++ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
++ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
++ * DEALINGS IN THE SOFTWARE.
++ */
++
++#ifdef HAVE_CONFIG_H
++#include "config.h"
++#endif
++
++#include "gstpwaudioringbuffer.h"
++
++#include <spa/param/audio/format-utils.h>
++#include <spa/pod/builder.h>
++
++GST_DEBUG_CATEGORY_STATIC (pw_audio_ring_buffer_debug);
++#define GST_CAT_DEFAULT pw_audio_ring_buffer_debug
++
++#define gst_pw_audio_ring_buffer_parent_class parent_class
++G_DEFINE_TYPE (GstPwAudioRingBuffer, gst_pw_audio_ring_buffer, GST_TYPE_AUDIO_RING_BUFFER);
++
++enum
++{
++ PROP_0,
++ PROP_ELEMENT,
++ PROP_DIRECTION,
++ PROP_PROPS
++};
++
++static void
++gst_pw_audio_ring_buffer_init (GstPwAudioRingBuffer * self)
++{
++ self->loop = pw_loop_new (NULL);
++ self->main_loop = pw_thread_loop_new (self->loop, "pw-audioringbuffer-loop");
++ self->core = pw_core_new (self->loop, NULL, 0);
++}
++
++static void
++gst_pw_audio_ring_buffer_finalize (GObject * object)
++{
++ GstPwAudioRingBuffer *self = GST_PW_AUDIO_RING_BUFFER (object);
++
++ pw_core_destroy (self->core);
++ pw_thread_loop_destroy (self->main_loop);
++ pw_loop_destroy (self->loop);
++}
++
++static void
++gst_pw_audio_ring_buffer_set_property (GObject * object, guint prop_id,
++ const GValue * value, GParamSpec * pspec)
++{
++ GstPwAudioRingBuffer *self = GST_PW_AUDIO_RING_BUFFER (object);
++
++ switch (prop_id) {
++ case PROP_ELEMENT:
++ self->elem = g_value_get_object (value);
++ break;
++
++ case PROP_DIRECTION:
++ self->direction = g_value_get_int (value);
++ break;
++
++ case PROP_PROPS:
++ self->props = g_value_get_pointer (value);
++ break;
++
++ default:
++ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
++ break;
++ }
++}
++
++static void
++on_remote_state_changed (void *data, enum pw_remote_state old,
++ enum pw_remote_state state, const char *error)
++{
++ GstPwAudioRingBuffer *self = GST_PW_AUDIO_RING_BUFFER (data);
++
++ GST_DEBUG_OBJECT (self->elem, "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 (self->elem, RESOURCE, FAILED,
++ ("remote error: %s", error), (NULL));
++ break;
++ }
++ pw_thread_loop_signal (self->main_loop, FALSE);
++}
++
++static const struct pw_remote_events remote_events = {
++ PW_VERSION_REMOTE_EVENTS,
++ .state_changed = on_remote_state_changed,
++};
++
++static gboolean
++wait_for_remote_state (GstPwAudioRingBuffer *self,
++ enum pw_remote_state target)
++{
++ while (TRUE) {
++ enum pw_remote_state state = pw_remote_get_state (self->remote, NULL);
++ if (state == target)
++ return TRUE;
++ if (state == PW_REMOTE_STATE_ERROR)
++ return FALSE;
++ pw_thread_loop_wait (self->main_loop);
++ }
++}
++
++static gboolean
++gst_pw_audio_ring_buffer_open_device (GstAudioRingBuffer *buf)
++{
++ GstPwAudioRingBuffer *self = GST_PW_AUDIO_RING_BUFFER (buf);
++
++ GST_DEBUG_OBJECT (self->elem, "open device");
++
++ if (pw_thread_loop_start (self->main_loop) < 0)
++ goto mainloop_error;
++
++ pw_thread_loop_lock (self->main_loop);
++
++ self->remote = pw_remote_new (self->core, NULL, 0);
++ pw_remote_add_listener (self->remote, &self->remote_listener, &remote_events,
++ self);
++
++ if (self->props->fd == -1)
++ pw_remote_connect (self->remote);
++ else
++ pw_remote_connect_fd (self->remote, self->props->fd);
++
++ GST_DEBUG_OBJECT (self->elem, "waiting for connection");
++
++ if (!wait_for_remote_state (self, PW_REMOTE_STATE_CONNECTED))
++ goto connect_error;
++
++ pw_thread_loop_unlock (self->main_loop);
++
++ return TRUE;
++
++ /* ERRORS */
++mainloop_error:
++ {
++ GST_ELEMENT_ERROR (self->elem, RESOURCE, FAILED,
++ ("Failed to start mainloop"), (NULL));
++ return FALSE;
++ }
++connect_error:
++ {
++ pw_thread_loop_unlock (self->main_loop);
++ return FALSE;
++ }
++}
++
++static gboolean
++gst_pw_audio_ring_buffer_close_device (GstAudioRingBuffer *buf)
++{
++ GstPwAudioRingBuffer *self = GST_PW_AUDIO_RING_BUFFER (buf);
++
++ GST_DEBUG_OBJECT (self->elem, "closing device");
++
++ pw_thread_loop_lock (self->main_loop);
++ if (self->remote) {
++ pw_remote_disconnect (self->remote);
++ wait_for_remote_state (self, PW_REMOTE_STATE_UNCONNECTED);
++ }
++ pw_thread_loop_unlock (self->main_loop);
++
++ pw_thread_loop_stop (self->main_loop);
++
++ if (self->remote) {
++ pw_remote_destroy (self->remote);
++ self->remote = NULL;
++ }
++ return TRUE;
++}
++
++static void
++on_stream_state_changed (void *data, enum pw_stream_state old,
++ enum pw_stream_state state, const char *error)
++{
++ GstPwAudioRingBuffer *self = GST_PW_AUDIO_RING_BUFFER (data);
++
++ GST_DEBUG_OBJECT (self->elem, "got stream state: %s",
++ pw_stream_state_as_string (state));
++
++ switch (state) {
++ case PW_STREAM_STATE_UNCONNECTED:
++ GST_ELEMENT_ERROR (self->elem, RESOURCE, FAILED,
++ ("stream disconnected unexpectedly"), (NULL));
++ break;
++ 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 (self->elem, RESOURCE, FAILED,
++ ("stream error: %s", error), (NULL));
++ break;
++ }
++ pw_thread_loop_signal (self->main_loop, FALSE);
++}
++
++static gboolean
++wait_for_stream_state (GstPwAudioRingBuffer *self,
++ enum pw_stream_state target)
++{
++ while (TRUE) {
++ enum pw_stream_state state = pw_stream_get_state (self->stream, NULL);
++ if (state >= target)
++ return TRUE;
++ if (state == PW_STREAM_STATE_ERROR || state == PW_STREAM_STATE_UNCONNECTED)
++ return FALSE;
++ pw_thread_loop_wait (self->main_loop);
++ }
++}
++
++static void
++on_stream_format_changed (void *data, const struct spa_pod *format)
++{
++ GstPwAudioRingBuffer *self = GST_PW_AUDIO_RING_BUFFER (data);
++ const struct spa_pod *params[1];
++ struct spa_pod_builder b = { NULL };
++ uint8_t buffer[512];
++
++ spa_pod_builder_init (&b, buffer, sizeof (buffer));
++ params[0] = spa_pod_builder_add_object (&b,
++ SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers,
++ SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(16, 1, INT32_MAX),
++ SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1),
++ SPA_PARAM_BUFFERS_size, SPA_POD_Int(self->segsize),
++ SPA_PARAM_BUFFERS_stride, SPA_POD_Int(self->bpf),
++ SPA_PARAM_BUFFERS_align, SPA_POD_Int(16));
++
++ GST_DEBUG_OBJECT (self->elem, "doing finish format, buffer size:%d", self->segsize);
++ pw_stream_finish_format (self->stream, 0, params, 1);
++}
++
++static void
++on_stream_process (void *data)
++{
++ GstPwAudioRingBuffer *self = GST_PW_AUDIO_RING_BUFFER (data);
++ GstAudioRingBuffer *buf = GST_AUDIO_RING_BUFFER (data);
++ struct pw_buffer *b;
++ struct spa_data *d;
++ gint size; /*< size to read/write from/to the spa buffer */
++ gint offset; /*< offset to read/write from/to in the spa buffer */
++ gint segment; /*< the current segment number in the ringbuffer */
++ guint8 *ringptr; /*< pointer to the beginning of the current segment */
++ gint segsize; /*< the size of one segment in the ringbuffer */
++ gint copy_size; /*< the bytes to copy in one memcpy() invocation */
++ gint remain; /*< remainder of bytes available in the spa buffer */
++
++ if (g_atomic_int_get (&buf->state) != GST_AUDIO_RING_BUFFER_STATE_STARTED) {
++ GST_LOG_OBJECT (self->elem, "ring buffer is not started");
++ return;
++ }
++
++ b = pw_stream_dequeue_buffer (self->stream);
++ if (!b) {
++ GST_WARNING_OBJECT (self->elem, "no pipewire buffer available");
++ return;
++ }
++
++ d = &b->buffer->datas[0];
++
++ if (self->direction == PW_DIRECTION_OUTPUT) {
++ /* in output mode, always fill the entire spa buffer */
++ offset = d->chunk->offset = 0;
++ size = d->chunk->size = d->maxsize;
++ b->size = size / self->bpf;
++ } else {
++ offset = SPA_MIN (d->chunk->offset, d->maxsize);
++ size = SPA_MIN (d->chunk->size, d->maxsize - offset);
++ }
++
++ do {
++ gst_audio_ring_buffer_prepare_read (buf, &segment, &ringptr, &segsize);
++
++ /* in INPUT (src) mode, it is possible that the skew algorithm
++ * advances the ringbuffer behind our back */
++ if (self->segoffset > 0 && self->cur_segment != segment)
++ self->segoffset = 0;
++
++ copy_size = SPA_MIN (size, segsize - self->segoffset);
++
++ if (self->direction == PW_DIRECTION_OUTPUT) {
++ memcpy (((guint8*) d->data) + offset, ringptr + self->segoffset,
++ copy_size);
++ } else {
++ memcpy (ringptr + self->segoffset, ((guint8*) d->data) + offset,
++ copy_size);
++ }
++
++ remain = size - (segsize - self->segoffset);
++
++ GST_TRACE_OBJECT (self->elem,
++ "seg %d: %s %d bytes remained:%d offset:%d segoffset:%d", segment,
++ self->direction == PW_DIRECTION_INPUT ? "INPUT" : "OUTPUT",
++ copy_size, remain, offset, self->segoffset);
++
++ if (remain >= 0) {
++ offset += (segsize - self->segoffset);
++ size = remain;
++
++ /* write silence on the segment we just read */
++ if (self->direction == PW_DIRECTION_OUTPUT)
++ gst_audio_ring_buffer_clear (buf, segment);
++
++ /* notify that we have read a complete segment */
++ gst_audio_ring_buffer_advance (buf, 1);
++ self->segoffset = 0;
++ } else {
++ self->segoffset += size;
++ self->cur_segment = segment;
++ }
++ } while (remain > 0);
++
++ pw_stream_queue_buffer (self->stream, b);
++}
++
++static const struct pw_stream_events stream_events = {
++ PW_VERSION_STREAM_EVENTS,
++ .state_changed = on_stream_state_changed,
++ .format_changed = on_stream_format_changed,
++ .process = on_stream_process,
++};
++
++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_pw_audio_ring_buffer_acquire (GstAudioRingBuffer *buf,
++ GstAudioRingBufferSpec *spec)
++{
++ GstPwAudioRingBuffer *self = GST_PW_AUDIO_RING_BUFFER (buf);
++ struct pw_properties *props;
++ struct spa_pod_builder b = { NULL };
++ uint8_t buffer[512];
++ const struct spa_pod *params[1];
++
++ g_return_val_if_fail (spec, FALSE);
++ g_return_val_if_fail (GST_AUDIO_INFO_IS_VALID (&spec->info), FALSE);
++ g_return_val_if_fail (!self->stream, TRUE); /* already acquired */
++
++ g_return_val_if_fail (spec->type == GST_AUDIO_RING_BUFFER_FORMAT_TYPE_RAW, FALSE);
++ g_return_val_if_fail (GST_AUDIO_INFO_IS_FLOAT (&spec->info), FALSE);
++
++ GST_DEBUG_OBJECT (self->elem, "acquire");
++
++ /* construct param & props objects */
++
++ if (self->props->properties) {
++ props = pw_properties_new (NULL, NULL);
++ gst_structure_foreach (self->props->properties, copy_properties, props);
++ } else {
++ props = NULL;
++ }
++
++ spa_pod_builder_init (&b, buffer, sizeof (buffer));
++ params[0] = spa_pod_builder_add_object (&b,
++ SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat,
++ SPA_FORMAT_mediaType, SPA_POD_Id (SPA_MEDIA_TYPE_audio),
++ SPA_FORMAT_mediaSubtype, SPA_POD_Id (SPA_MEDIA_SUBTYPE_raw),
++ SPA_FORMAT_AUDIO_format, SPA_POD_Id (SPA_AUDIO_FORMAT_F32),
++ SPA_FORMAT_AUDIO_rate, SPA_POD_Int (GST_AUDIO_INFO_RATE (&spec->info)),
++ SPA_FORMAT_AUDIO_channels, SPA_POD_Int (GST_AUDIO_INFO_CHANNELS (&spec->info)));
++
++ self->segsize = spec->segsize;
++ self->bpf = GST_AUDIO_INFO_BPF (&spec->info);
++ self->rate = GST_AUDIO_INFO_RATE (&spec->info);
++ self->segoffset = 0;
++
++ /* connect stream */
++
++ pw_thread_loop_lock (self->main_loop);
++
++ GST_DEBUG_OBJECT (self->elem, "creating stream");
++
++ self->stream = pw_stream_new (self->remote, self->props->client_name, props);
++ pw_stream_add_listener(self->stream, &self->stream_listener, &stream_events,
++ self);
++
++ if (pw_stream_connect (self->stream,
++ self->direction,
++ self->props->path ? (uint32_t)atoi(self->props->path) : SPA_ID_INVALID,
++ PW_STREAM_FLAG_AUTOCONNECT |
++ PW_STREAM_FLAG_MAP_BUFFERS |
++ PW_STREAM_FLAG_RT_PROCESS,
++ params, 1) < 0)
++ goto start_error;
++
++ GST_DEBUG_OBJECT (self->elem, "waiting for stream READY");
++
++ if (!wait_for_stream_state (self, PW_STREAM_STATE_READY))
++ goto start_error;
++
++ pw_thread_loop_unlock (self->main_loop);
++
++ /* allocate the internal ringbuffer */
++
++ spec->seglatency = spec->segtotal + 1;
++ buf->size = spec->segtotal * spec->segsize;
++ buf->memory = g_malloc (buf->size);
++
++ gst_audio_format_fill_silence (buf->spec.info.finfo, buf->memory,
++ buf->size);
++
++ GST_DEBUG_OBJECT (self->elem, "acquire done");
++
++ return TRUE;
++
++start_error:
++ {
++ GST_ERROR_OBJECT (self->elem, "could not start stream");
++ pw_stream_destroy (self->stream);
++ self->stream = NULL;
++ pw_thread_loop_unlock (self->main_loop);
++ return FALSE;
++ }
++}
++
++static gboolean
++gst_pw_audio_ring_buffer_release (GstAudioRingBuffer *buf)
++{
++ GstPwAudioRingBuffer *self = GST_PW_AUDIO_RING_BUFFER (buf);
++
++ GST_DEBUG_OBJECT (self->elem, "release");
++
++ pw_thread_loop_lock (self->main_loop);
++ if (self->stream) {
++ spa_hook_remove (&self->stream_listener);
++ pw_stream_disconnect (self->stream);
++ pw_stream_destroy (self->stream);
++ self->stream = NULL;
++ }
++ pw_thread_loop_unlock (self->main_loop);
++
++ /* free the buffer */
++ g_free (buf->memory);
++ buf->memory = NULL;
++
++ return TRUE;
++}
++
++static guint
++gst_pw_audio_ring_buffer_delay (GstAudioRingBuffer *buf)
++{
++ GstPwAudioRingBuffer *self = GST_PW_AUDIO_RING_BUFFER (buf);
++ struct pw_time t;
++
++ if (!self->stream || pw_stream_get_time (self->stream, &t) < 0)
++ return 0;
++
++ if (self->direction == PW_DIRECTION_OUTPUT) {
++ /* on output streams, we set the pw_buffer.size in frames,
++ so no conversion is necessary */
++ return t.queued;
++ } else {
++ /* on input streams, pw_buffer.size is set by pw_stream in ticks,
++ so we need to convert it to frames and also add segoffset, which
++ is the number of bytes we have read but not advertised yet, as
++ the segment is incomplete */
++ if (t.rate.denom > 0)
++ return
++ gst_util_uint64_scale (t.queued, self->rate * t.rate.num, t.rate.denom)
++ + self->segoffset / self->bpf;
++ else
++ return self->segoffset / self->bpf;
++ }
++
++ return 0;
++}
++
++static void
++gst_pw_audio_ring_buffer_class_init (GstPwAudioRingBufferClass * klass)
++{
++ GObjectClass *gobject_class;
++ GstAudioRingBufferClass *gstaudiorbuf_class;
++
++ gobject_class = (GObjectClass *) klass;
++ gstaudiorbuf_class = (GstAudioRingBufferClass *) klass;
++
++ gobject_class->finalize = gst_pw_audio_ring_buffer_finalize;
++ gobject_class->set_property = gst_pw_audio_ring_buffer_set_property;
++
++ gstaudiorbuf_class->open_device = gst_pw_audio_ring_buffer_open_device;
++ gstaudiorbuf_class->acquire = gst_pw_audio_ring_buffer_acquire;
++ gstaudiorbuf_class->release = gst_pw_audio_ring_buffer_release;
++ gstaudiorbuf_class->close_device = gst_pw_audio_ring_buffer_close_device;
++ gstaudiorbuf_class->delay = gst_pw_audio_ring_buffer_delay;
++
++ g_object_class_install_property (gobject_class, PROP_ELEMENT,
++ g_param_spec_object ("element", "Element", "The audio source or sink",
++ GST_TYPE_ELEMENT,
++ G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
++
++ g_object_class_install_property (gobject_class, PROP_DIRECTION,
++ g_param_spec_int ("direction", "Direction", "The stream direction",
++ PW_DIRECTION_INPUT, PW_DIRECTION_OUTPUT, PW_DIRECTION_INPUT,
++ G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
++
++ g_object_class_install_property (gobject_class, PROP_PROPS,
++ g_param_spec_pointer ("props", "Properties", "The properties struct",
++ G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
++
++ GST_DEBUG_CATEGORY_INIT (pw_audio_ring_buffer_debug, "pwaudioringbuffer", 0,
++ "PipeWire Audio Ring Buffer");
++}
+diff --git a/src/gst/gstpwaudioringbuffer.h b/src/gst/gstpwaudioringbuffer.h
+new file mode 100644
+index 00000000..f47f668a
+--- /dev/null
++++ b/src/gst/gstpwaudioringbuffer.h
+@@ -0,0 +1,83 @@
++/* PipeWire
++ *
++ * Copyright © 2018 Wim Taymans
++ * Copyright © 2019 Collabora Ltd.
++ * @author George Kiagiadakis <george.kiagiadakis@collabora.com>
++ *
++ * Permission is hereby granted, free of charge, to any person obtaining a
++ * copy of this software and associated documentation files (the "Software"),
++ * to deal in the Software without restriction, including without limitation
++ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
++ * and/or sell copies of the Software, and to permit persons to whom the
++ * Software is furnished to do so, subject to the following conditions:
++ *
++ * The above copyright notice and this permission notice (including the next
++ * paragraph) shall be included in all copies or substantial portions of the
++ * Software.
++ *
++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
++ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
++ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
++ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
++ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
++ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
++ * DEALINGS IN THE SOFTWARE.
++ */
++
++#ifndef __GST_PW_AUDIO_RING_BUFFER_H__
++#define __GST_PW_AUDIO_RING_BUFFER_H__
++
++#include <gst/gst.h>
++#include <gst/audio/audio.h>
++#include <pipewire/pipewire.h>
++
++G_BEGIN_DECLS
++
++#define GST_TYPE_PW_AUDIO_RING_BUFFER \
++ (gst_pw_audio_ring_buffer_get_type ())
++
++G_DECLARE_FINAL_TYPE(GstPwAudioRingBuffer, gst_pw_audio_ring_buffer,
++ GST, PW_AUDIO_RING_BUFFER, GstAudioRingBuffer);
++
++typedef struct _GstPwAudioRingBufferProps GstPwAudioRingBufferProps;
++
++struct _GstPwAudioRingBuffer
++{
++ GstAudioRingBuffer parent;
++
++ /* properties */
++ GstElement *elem;
++ enum pw_direction direction;
++ GstPwAudioRingBufferProps *props;
++
++ /* internal */
++ struct pw_loop *loop;
++ struct pw_thread_loop *main_loop;
++
++ struct pw_core *core;
++ struct pw_remote *remote;
++ struct spa_hook remote_listener;
++
++ struct pw_stream *stream;
++ struct spa_hook stream_listener;
++
++ gint segsize;
++ gint bpf;
++ gint rate;
++
++ /* on_stream_process() state */
++ gint segoffset;
++ gint cur_segment;
++};
++
++struct _GstPwAudioRingBufferProps
++{
++ gchar *path;
++ gchar *client_name;
++ GstStructure *properties;
++ int fd;
++};
++
++G_END_DECLS
++
++#endif
+diff --git a/src/gst/gstpwaudiosink.c b/src/gst/gstpwaudiosink.c
+new file mode 100644
+index 00000000..6cb71385
+--- /dev/null
++++ b/src/gst/gstpwaudiosink.c
+@@ -0,0 +1,200 @@
++/* PipeWire
++ *
++ * Copyright © 2018 Wim Taymans
++ * Copyright © 2019 Collabora Ltd.
++ * @author George Kiagiadakis <george.kiagiadakis@collabora.com>
++ *
++ * Permission is hereby granted, free of charge, to any person obtaining a
++ * copy of this software and associated documentation files (the "Software"),
++ * to deal in the Software without restriction, including without limitation
++ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
++ * and/or sell copies of the Software, and to permit persons to whom the
++ * Software is furnished to do so, subject to the following conditions:
++ *
++ * The above copyright notice and this permission notice (including the next
++ * paragraph) shall be included in all copies or substantial portions of the
++ * Software.
++ *
++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
++ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
++ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
++ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
++ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
++ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
++ * DEALINGS IN THE SOFTWARE.
++ */
++
++#ifdef HAVE_CONFIG_H
++#include "config.h"
++#endif
++
++#include "gstpwaudiosink.h"
++
++GST_DEBUG_CATEGORY_STATIC (pw_audio_sink_debug);
++#define GST_CAT_DEFAULT pw_audio_sink_debug
++
++G_DEFINE_TYPE (GstPwAudioSink, gst_pw_audio_sink, GST_TYPE_AUDIO_BASE_SINK);
++
++enum
++{
++ PROP_0,
++ PROP_PATH,
++ PROP_CLIENT_NAME,
++ PROP_STREAM_PROPERTIES,
++ PROP_FD
++};
++
++static GstStaticPadTemplate gst_pw_audio_sink_template =
++GST_STATIC_PAD_TEMPLATE ("sink",
++ GST_PAD_SINK,
++ GST_PAD_ALWAYS,
++ GST_STATIC_CAPS (GST_AUDIO_CAPS_MAKE (GST_AUDIO_NE (F32))
++ ", layout = (string)\"interleaved\"")
++);
++
++
++static void
++gst_pw_audio_sink_init (GstPwAudioSink * self)
++{
++ self->props.fd = -1;
++}
++
++static void
++gst_pw_audio_sink_finalize (GObject * object)
++{
++ GstPwAudioSink *pwsink = GST_PW_AUDIO_SINK (object);
++
++ g_free (pwsink->props.path);
++ g_free (pwsink->props.client_name);
++ if (pwsink->props.properties)
++ gst_structure_free (pwsink->props.properties);
++}
++
++static void
++gst_pw_audio_sink_set_property (GObject * object, guint prop_id,
++ const GValue * value, GParamSpec * pspec)
++{
++ GstPwAudioSink *pwsink = GST_PW_AUDIO_SINK (object);
++
++ switch (prop_id) {
++ case PROP_PATH:
++ g_free (pwsink->props.path);
++ pwsink->props.path = g_value_dup_string (value);
++ break;
++
++ case PROP_CLIENT_NAME:
++ g_free (pwsink->props.client_name);
++ pwsink->props.client_name = g_value_dup_string (value);
++ break;
++
++ case PROP_STREAM_PROPERTIES:
++ if (pwsink->props.properties)
++ gst_structure_free (pwsink->props.properties);
++ pwsink->props.properties =
++ gst_structure_copy (gst_value_get_structure (value));
++ break;
++
++ case PROP_FD:
++ pwsink->props.fd = g_value_get_int (value);
++ break;
++
++ default:
++ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
++ break;
++ }
++}
++
++static void
++gst_pw_audio_sink_get_property (GObject * object, guint prop_id,
++ GValue * value, GParamSpec * pspec)
++{
++ GstPwAudioSink *pwsink = GST_PW_AUDIO_SINK (object);
++
++ switch (prop_id) {
++ case PROP_PATH:
++ g_value_set_string (value, pwsink->props.path);
++ break;
++
++ case PROP_CLIENT_NAME:
++ g_value_set_string (value, pwsink->props.client_name);
++ break;
++
++ case PROP_STREAM_PROPERTIES:
++ gst_value_set_structure (value, pwsink->props.properties);
++ break;
++
++ case PROP_FD:
++ g_value_set_int (value, pwsink->props.fd);
++ break;
++
++ default:
++ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
++ break;
++ }
++}
++
++static GstAudioRingBuffer *
++gst_pw_audio_sink_create_ringbuffer (GstAudioBaseSink * sink)
++{
++ GstPwAudioSink *self = GST_PW_AUDIO_SINK (sink);
++ GstAudioRingBuffer *buffer;
++
++ GST_DEBUG_OBJECT (sink, "creating ringbuffer");
++ buffer = g_object_new (GST_TYPE_PW_AUDIO_RING_BUFFER,
++ "element", sink,
++ "direction", PW_DIRECTION_OUTPUT,
++ "props", &self->props,
++ NULL);
++ GST_DEBUG_OBJECT (sink, "created ringbuffer @%p", buffer);
++
++ return buffer;
++}
++
++static void
++gst_pw_audio_sink_class_init (GstPwAudioSinkClass * klass)
++{
++ GObjectClass *gobject_class;
++ GstElementClass *gstelement_class;
++ GstAudioBaseSinkClass *gstaudiobsink_class;
++
++ gobject_class = (GObjectClass *) klass;
++ gstelement_class = (GstElementClass *) klass;
++ gstaudiobsink_class = (GstAudioBaseSinkClass *) klass;
++
++ gobject_class->finalize = gst_pw_audio_sink_finalize;
++ gobject_class->set_property = gst_pw_audio_sink_set_property;
++ gobject_class->get_property = gst_pw_audio_sink_get_property;
++
++ gstaudiobsink_class->create_ringbuffer = gst_pw_audio_sink_create_ringbuffer;
++
++ 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_FD,
++ g_param_spec_int ("fd", "Fd", "The fd to connect with", -1, G_MAXINT, -1,
++ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
++
++ gst_element_class_set_static_metadata (gstelement_class,
++ "PipeWire Audio sink", "Sink/Audio",
++ "Send audio to PipeWire",
++ "George Kiagiadakis <george.kiagiadakis@collabora.com>");
++
++ gst_element_class_add_pad_template (gstelement_class,
++ gst_static_pad_template_get (&gst_pw_audio_sink_template));
++
++ GST_DEBUG_CATEGORY_INIT (pw_audio_sink_debug, "pwaudiosink", 0,
++ "PipeWire Audio Sink");
++}
++
+diff --git a/src/gst/gstpwaudiosink.h b/src/gst/gstpwaudiosink.h
+new file mode 100644
+index 00000000..7ed0de7b
+--- /dev/null
++++ b/src/gst/gstpwaudiosink.h
+@@ -0,0 +1,48 @@
++/* PipeWire
++ *
++ * Copyright © 2018 Wim Taymans
++ * Copyright © 2019 Collabora Ltd.
++ * @author George Kiagiadakis <george.kiagiadakis@collabora.com>
++ *
++ * Permission is hereby granted, free of charge, to any person obtaining a
++ * copy of this software and associated documentation files (the "Software"),
++ * to deal in the Software without restriction, including without limitation
++ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
++ * and/or sell copies of the Software, and to permit persons to whom the
++ * Software is furnished to do so, subject to the following conditions:
++ *
++ * The above copyright notice and this permission notice (including the next
++ * paragraph) shall be included in all copies or substantial portions of the
++ * Software.
++ *
++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
++ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
++ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
++ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
++ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
++ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
++ * DEALINGS IN THE SOFTWARE.
++ */
++
++#ifndef __GST_PW_AUDIO_SINK_H__
++#define __GST_PW_AUDIO_SINK_H__
++
++#include "gstpwaudioringbuffer.h"
++
++G_BEGIN_DECLS
++
++#define GST_TYPE_PW_AUDIO_SINK \
++ (gst_pw_audio_sink_get_type ())
++
++G_DECLARE_FINAL_TYPE(GstPwAudioSink, gst_pw_audio_sink,
++ GST, PW_AUDIO_SINK, GstAudioBaseSink);
++
++struct _GstPwAudioSink
++{
++ GstAudioBaseSink parent;
++ GstPwAudioRingBufferProps props;
++};
++
++G_END_DECLS
++
++#endif
+diff --git a/src/gst/gstpwaudiosrc.c b/src/gst/gstpwaudiosrc.c
+new file mode 100644
+index 00000000..6c522982
+--- /dev/null
++++ b/src/gst/gstpwaudiosrc.c
+@@ -0,0 +1,200 @@
++/* PipeWire
++ *
++ * Copyright © 2018 Wim Taymans
++ * Copyright © 2019 Collabora Ltd.
++ * @author George Kiagiadakis <george.kiagiadakis@collabora.com>
++ *
++ * Permission is hereby granted, free of charge, to any person obtaining a
++ * copy of this software and associated documentation files (the "Software"),
++ * to deal in the Software without restriction, including without limitation
++ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
++ * and/or sell copies of the Software, and to permit persons to whom the
++ * Software is furnished to do so, subject to the following conditions:
++ *
++ * The above copyright notice and this permission notice (including the next
++ * paragraph) shall be included in all copies or substantial portions of the
++ * Software.
++ *
++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
++ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
++ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
++ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
++ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
++ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
++ * DEALINGS IN THE SOFTWARE.
++ */
++
++#ifdef HAVE_CONFIG_H
++#include "config.h"
++#endif
++
++#include "gstpwaudiosrc.h"
++
++GST_DEBUG_CATEGORY_STATIC (pw_audio_src_debug);
++#define GST_CAT_DEFAULT pw_audio_src_debug
++
++G_DEFINE_TYPE (GstPwAudioSrc, gst_pw_audio_src, GST_TYPE_AUDIO_BASE_SRC);
++
++enum
++{
++ PROP_0,
++ PROP_PATH,
++ PROP_CLIENT_NAME,
++ PROP_STREAM_PROPERTIES,
++ PROP_FD
++};
++
++static GstStaticPadTemplate gst_pw_audio_src_template =
++GST_STATIC_PAD_TEMPLATE ("src",
++ GST_PAD_SRC,
++ GST_PAD_ALWAYS,
++ GST_STATIC_CAPS (GST_AUDIO_CAPS_MAKE (GST_AUDIO_NE (F32))
++ ", layout = (string)\"interleaved\"")
++);
++
++
++static void
++gst_pw_audio_src_init (GstPwAudioSrc * self)
++{
++ self->props.fd = -1;
++}
++
++static void
++gst_pw_audio_src_finalize (GObject * object)
++{
++ GstPwAudioSrc *self = GST_PW_AUDIO_SRC (object);
++
++ g_free (self->props.path);
++ g_free (self->props.client_name);
++ if (self->props.properties)
++ gst_structure_free (self->props.properties);
++}
++
++static void
++gst_pw_audio_src_set_property (GObject * object, guint prop_id,
++ const GValue * value, GParamSpec * pspec)
++{
++ GstPwAudioSrc *self = GST_PW_AUDIO_SRC (object);
++
++ switch (prop_id) {
++ case PROP_PATH:
++ g_free (self->props.path);
++ self->props.path = g_value_dup_string (value);
++ break;
++
++ case PROP_CLIENT_NAME:
++ g_free (self->props.client_name);
++ self->props.client_name = g_value_dup_string (value);
++ break;
++
++ case PROP_STREAM_PROPERTIES:
++ if (self->props.properties)
++ gst_structure_free (self->props.properties);
++ self->props.properties =
++ gst_structure_copy (gst_value_get_structure (value));
++ break;
++
++ case PROP_FD:
++ self->props.fd = g_value_get_int (value);
++ break;
++
++ default:
++ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
++ break;
++ }
++}
++
++static void
++gst_pw_audio_src_get_property (GObject * object, guint prop_id,
++ GValue * value, GParamSpec * pspec)
++{
++ GstPwAudioSrc *self = GST_PW_AUDIO_SRC (object);
++
++ switch (prop_id) {
++ case PROP_PATH:
++ g_value_set_string (value, self->props.path);
++ break;
++
++ case PROP_CLIENT_NAME:
++ g_value_set_string (value, self->props.client_name);
++ break;
++
++ case PROP_STREAM_PROPERTIES:
++ gst_value_set_structure (value, self->props.properties);
++ break;
++
++ case PROP_FD:
++ g_value_set_int (value, self->props.fd);
++ break;
++
++ default:
++ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
++ break;
++ }
++}
++
++static GstAudioRingBuffer *
++gst_pw_audio_src_create_ringbuffer (GstAudioBaseSrc * sink)
++{
++ GstPwAudioSrc *self = GST_PW_AUDIO_SRC (sink);
++ GstAudioRingBuffer *buffer;
++
++ GST_DEBUG_OBJECT (sink, "creating ringbuffer");
++ buffer = g_object_new (GST_TYPE_PW_AUDIO_RING_BUFFER,
++ "element", sink,
++ "direction", PW_DIRECTION_INPUT,
++ "props", &self->props,
++ NULL);
++ GST_DEBUG_OBJECT (sink, "created ringbuffer @%p", buffer);
++
++ return buffer;
++}
++
++static void
++gst_pw_audio_src_class_init (GstPwAudioSrcClass * klass)
++{
++ GObjectClass *gobject_class;
++ GstElementClass *gstelement_class;
++ GstAudioBaseSrcClass *gstaudiobsrc_class;
++
++ gobject_class = (GObjectClass *) klass;
++ gstelement_class = (GstElementClass *) klass;
++ gstaudiobsrc_class = (GstAudioBaseSrcClass *) klass;
++
++ gobject_class->finalize = gst_pw_audio_src_finalize;
++ gobject_class->set_property = gst_pw_audio_src_set_property;
++ gobject_class->get_property = gst_pw_audio_src_get_property;
++
++ gstaudiobsrc_class->create_ringbuffer = gst_pw_audio_src_create_ringbuffer;
++
++ 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_FD,
++ g_param_spec_int ("fd", "Fd", "The fd to connect with", -1, G_MAXINT, -1,
++ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
++
++ gst_element_class_set_static_metadata (gstelement_class,
++ "PipeWire Audio source", "Source/Audio",
++ "Receive audio from PipeWire",
++ "George Kiagiadakis <george.kiagiadakis@collabora.com>");
++
++ gst_element_class_add_pad_template (gstelement_class,
++ gst_static_pad_template_get (&gst_pw_audio_src_template));
++
++ GST_DEBUG_CATEGORY_INIT (pw_audio_src_debug, "pwaudiosrc", 0,
++ "PipeWire Audio Src");
++}
++
+diff --git a/src/gst/gstpwaudiosrc.h b/src/gst/gstpwaudiosrc.h
+new file mode 100644
+index 00000000..c46e644c
+--- /dev/null
++++ b/src/gst/gstpwaudiosrc.h
+@@ -0,0 +1,48 @@
++/* PipeWire
++ *
++ * Copyright © 2018 Wim Taymans
++ * Copyright © 2019 Collabora Ltd.
++ * @author George Kiagiadakis <george.kiagiadakis@collabora.com>
++ *
++ * Permission is hereby granted, free of charge, to any person obtaining a
++ * copy of this software and associated documentation files (the "Software"),
++ * to deal in the Software without restriction, including without limitation
++ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
++ * and/or sell copies of the Software, and to permit persons to whom the
++ * Software is furnished to do so, subject to the following conditions:
++ *
++ * The above copyright notice and this permission notice (including the next
++ * paragraph) shall be included in all copies or substantial portions of the
++ * Software.
++ *
++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
++ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
++ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
++ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
++ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
++ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
++ * DEALINGS IN THE SOFTWARE.
++ */
++
++#ifndef __GST_PW_AUDIO_SRC_H__
++#define __GST_PW_AUDIO_SRC_H__
++
++#include "gstpwaudioringbuffer.h"
++
++G_BEGIN_DECLS
++
++#define GST_TYPE_PW_AUDIO_SRC \
++ (gst_pw_audio_src_get_type ())
++
++G_DECLARE_FINAL_TYPE(GstPwAudioSrc, gst_pw_audio_src,
++ GST, PW_AUDIO_SRC, GstAudioBaseSrc);
++
++struct _GstPwAudioSrc
++{
++ GstAudioBaseSrc parent;
++ GstPwAudioRingBufferProps props;
++};
++
++G_END_DECLS
++
++#endif
+diff --git a/src/gst/meson.build b/src/gst/meson.build
+index ad0e0801..0e922347 100644
+--- a/src/gst/meson.build
++++ b/src/gst/meson.build
+@@ -6,6 +6,9 @@ pipewire_gst_sources = [
+ 'gstpipewirepool.c',
+ 'gstpipewiresink.c',
+ 'gstpipewiresrc.c',
++ 'gstpwaudioringbuffer.c',
++ 'gstpwaudiosink.c',
++ 'gstpwaudiosrc.c',
+ ]
+
+ pipewire_gst_headers = [
+@@ -15,6 +18,9 @@ pipewire_gst_headers = [
+ 'gstpipewirepool.h',
+ 'gstpipewiresink.h',
+ 'gstpipewiresrc.h',
++ 'gstpwaudioringbuffer.h',
++ 'gstpwaudiosink.h',
++ 'gstpwaudiosrc.h',
+ ]
+
+ pipewire_gst_c_args = [
+--
+2.20.1
+
diff --git a/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/0011-gst-pwaudioringbuffer-make-the-buffer-size-sensitive.patch b/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/0011-gst-pwaudioringbuffer-make-the-buffer-size-sensitive.patch
new file mode 100644
index 00000000..5ffabb6d
--- /dev/null
+++ b/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/0011-gst-pwaudioringbuffer-make-the-buffer-size-sensitive.patch
@@ -0,0 +1,60 @@
+From 6e289d0058d71bc433d1918a8bbf3305f3e4f517 Mon Sep 17 00:00:00 2001
+From: Julian Bouzas <julian.bouzas@collabora.com>
+Date: Tue, 7 May 2019 10:36:35 -0400
+Subject: [PATCH] gst/pwaudioringbuffer: make the buffer size sensitive to the
+ number of channels
+
+Upstream-Status: Submitted [https://github.com/PipeWire/pipewire/pull/140]
+---
+ src/gst/gstpwaudioringbuffer.c | 6 ++++--
+ src/gst/gstpwaudioringbuffer.h | 1 +
+ 2 files changed, 5 insertions(+), 2 deletions(-)
+
+diff --git a/src/gst/gstpwaudioringbuffer.c b/src/gst/gstpwaudioringbuffer.c
+index 989b2cd7..181304e8 100644
+--- a/src/gst/gstpwaudioringbuffer.c
++++ b/src/gst/gstpwaudioringbuffer.c
+@@ -246,17 +246,18 @@ on_stream_format_changed (void *data, const struct spa_pod *format)
+ const struct spa_pod *params[1];
+ struct spa_pod_builder b = { NULL };
+ uint8_t buffer[512];
++ const gint b_size = self->segsize * self->channels;
+
+ spa_pod_builder_init (&b, buffer, sizeof (buffer));
+ params[0] = spa_pod_builder_add_object (&b,
+ SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers,
+ SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(16, 1, INT32_MAX),
+ SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1),
+- SPA_PARAM_BUFFERS_size, SPA_POD_Int(self->segsize),
++ SPA_PARAM_BUFFERS_size, SPA_POD_Int(b_size),
+ SPA_PARAM_BUFFERS_stride, SPA_POD_Int(self->bpf),
+ SPA_PARAM_BUFFERS_align, SPA_POD_Int(16));
+
+- GST_DEBUG_OBJECT (self->elem, "doing finish format, buffer size:%d", self->segsize);
++ GST_DEBUG_OBJECT (self->elem, "doing finish format, buffer size:%d", b_size);
+ pw_stream_finish_format (self->stream, 0, params, 1);
+ }
+
+@@ -402,6 +403,7 @@ gst_pw_audio_ring_buffer_acquire (GstAudioRingBuffer *buf,
+ self->segsize = spec->segsize;
+ self->bpf = GST_AUDIO_INFO_BPF (&spec->info);
+ self->rate = GST_AUDIO_INFO_RATE (&spec->info);
++ self->channels = GST_AUDIO_INFO_CHANNELS (&spec->info);
+ self->segoffset = 0;
+
+ /* connect stream */
+diff --git a/src/gst/gstpwaudioringbuffer.h b/src/gst/gstpwaudioringbuffer.h
+index f47f668a..f600f012 100644
+--- a/src/gst/gstpwaudioringbuffer.h
++++ b/src/gst/gstpwaudioringbuffer.h
+@@ -64,6 +64,7 @@ struct _GstPwAudioRingBuffer
+ gint segsize;
+ gint bpf;
+ gint rate;
++ gint channels;
+
+ /* on_stream_process() state */
+ gint segoffset;
+--
+2.20.1
+
diff --git a/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/0012-gst-pwaudioringbuffer-request-pause-play-on-the-appr.patch b/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/0012-gst-pwaudioringbuffer-request-pause-play-on-the-appr.patch
new file mode 100644
index 00000000..3680cc35
--- /dev/null
+++ b/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/0012-gst-pwaudioringbuffer-request-pause-play-on-the-appr.patch
@@ -0,0 +1,76 @@
+From 1eb1e3a839f97ad4aa43c289f702c587a068a333 Mon Sep 17 00:00:00 2001
+From: George Kiagiadakis <george.kiagiadakis@collabora.com>
+Date: Thu, 11 Jul 2019 16:21:17 +0300
+Subject: [PATCH] gst/pwaudioringbuffer: request pause/play on the appropriate
+ stream state changes
+
+This allows the client to properly go to PAUSED when the session manager
+unlinks the stream and go again to PLAYING when the sm re-links it.
+This allows the session manager to implement policies without letting
+the client pipeline freeze (in the absence of a running audio clock)
+when it is unlinked. Note that in case the client doesn't handle the
+request, there is still no issue. Like in pulseaudio, the clock just
+freezes, so the pipeline stops progressing.
+
+This is similar to the pulseaudio cork/uncork mechanism.
+
+Upstream-Status: Submitted [https://github.com/PipeWire/pipewire/pull/140]
+---
+ src/gst/gstpwaudioringbuffer.c | 27 +++++++++++++++++++++++----
+ 1 file changed, 23 insertions(+), 4 deletions(-)
+
+diff --git a/src/gst/gstpwaudioringbuffer.c b/src/gst/gstpwaudioringbuffer.c
+index 181304e8..04259927 100644
+--- a/src/gst/gstpwaudioringbuffer.c
++++ b/src/gst/gstpwaudioringbuffer.c
+@@ -202,11 +202,16 @@ on_stream_state_changed (void *data, enum pw_stream_state old,
+ enum pw_stream_state state, const char *error)
+ {
+ GstPwAudioRingBuffer *self = GST_PW_AUDIO_RING_BUFFER (data);
++ GstMessage *msg;
+
+ GST_DEBUG_OBJECT (self->elem, "got stream state: %s",
+ pw_stream_state_as_string (state));
+
+ switch (state) {
++ case PW_STREAM_STATE_ERROR:
++ GST_ELEMENT_ERROR (self->elem, RESOURCE, FAILED,
++ ("stream error: %s", error), (NULL));
++ break;
+ case PW_STREAM_STATE_UNCONNECTED:
+ GST_ELEMENT_ERROR (self->elem, RESOURCE, FAILED,
+ ("stream disconnected unexpectedly"), (NULL));
+@@ -214,12 +219,26 @@ on_stream_state_changed (void *data, enum pw_stream_state old,
+ case PW_STREAM_STATE_CONNECTING:
+ case PW_STREAM_STATE_CONFIGURE:
+ case PW_STREAM_STATE_READY:
++ break;
+ case PW_STREAM_STATE_PAUSED:
+- case PW_STREAM_STATE_STREAMING:
++ if (old == PW_STREAM_STATE_STREAMING) {
++ if (GST_STATE (self->elem) != GST_STATE_PAUSED &&
++ GST_STATE_TARGET (self->elem) != GST_STATE_PAUSED) {
++ GST_DEBUG_OBJECT (self->elem, "requesting GST_STATE_PAUSED");
++ msg = gst_message_new_request_state (GST_OBJECT (self->elem),
++ GST_STATE_PAUSED);
++ gst_element_post_message (self->elem, msg);
++ }
++ }
+ break;
+- case PW_STREAM_STATE_ERROR:
+- GST_ELEMENT_ERROR (self->elem, RESOURCE, FAILED,
+- ("stream error: %s", error), (NULL));
++ case PW_STREAM_STATE_STREAMING:
++ if (GST_STATE (self->elem) != GST_STATE_PLAYING &&
++ GST_STATE_TARGET (self->elem) != GST_STATE_PLAYING) {
++ GST_DEBUG_OBJECT (self->elem, "requesting GST_STATE_PLAYING");
++ msg = gst_message_new_request_state (GST_OBJECT (self->elem),
++ GST_STATE_PLAYING);
++ gst_element_post_message (self->elem, msg);
++ }
+ break;
+ }
+ pw_thread_loop_signal (self->main_loop, FALSE);
+--
+2.20.1
+
diff --git a/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/0013-gst-pwaudioringbuffer-wait-only-for-STREAM_STATE_CON.patch b/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/0013-gst-pwaudioringbuffer-wait-only-for-STREAM_STATE_CON.patch
new file mode 100644
index 00000000..539e3a5e
--- /dev/null
+++ b/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/0013-gst-pwaudioringbuffer-wait-only-for-STREAM_STATE_CON.patch
@@ -0,0 +1,35 @@
+From 1b2bf0f435f2912c32fbd7a6118ed9bfb41f031c Mon Sep 17 00:00:00 2001
+From: George Kiagiadakis <george.kiagiadakis@collabora.com>
+Date: Thu, 11 Jul 2019 16:34:35 +0300
+Subject: [PATCH] gst/pwaudioringbuffer: wait only for STREAM_STATE_CONFIGURE
+ when starting
+
+The CONFIGURE state is reached when the pw_client_node is exported,
+while the READY state requires the session manager to try and link
+the stream. If the SM does not want to link the stream due to policy,
+the client should not hang there forever.
+
+Upstream-Status: Submitted [https://github.com/PipeWire/pipewire/pull/140]
+---
+ src/gst/gstpwaudioringbuffer.c | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/src/gst/gstpwaudioringbuffer.c b/src/gst/gstpwaudioringbuffer.c
+index 04259927..b92b5feb 100644
+--- a/src/gst/gstpwaudioringbuffer.c
++++ b/src/gst/gstpwaudioringbuffer.c
+@@ -444,9 +444,9 @@ gst_pw_audio_ring_buffer_acquire (GstAudioRingBuffer *buf,
+ params, 1) < 0)
+ goto start_error;
+
+- GST_DEBUG_OBJECT (self->elem, "waiting for stream READY");
++ GST_DEBUG_OBJECT (self->elem, "waiting for stream CONFIGURE");
+
+- if (!wait_for_stream_state (self, PW_STREAM_STATE_READY))
++ if (!wait_for_stream_state (self, PW_STREAM_STATE_CONFIGURE))
+ goto start_error;
+
+ pw_thread_loop_unlock (self->main_loop);
+--
+2.20.1
+
diff --git a/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/0014-gst-pwaudiosink-set-the-default-latency-time-buffer-.patch b/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/0014-gst-pwaudiosink-set-the-default-latency-time-buffer-.patch
new file mode 100644
index 00000000..6f15b7f7
--- /dev/null
+++ b/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/0014-gst-pwaudiosink-set-the-default-latency-time-buffer-.patch
@@ -0,0 +1,37 @@
+From 460ce06c9cc6fd7b0106e0ce8a265bbeff4ae406 Mon Sep 17 00:00:00 2001
+From: George Kiagiadakis <george.kiagiadakis@collabora.com>
+Date: Thu, 11 Jul 2019 17:07:15 +0300
+Subject: [PATCH] gst/pwaudiosink: set the default latency time (buffer size)
+ to be 21.3ms
+
+This is to solve underrun issues that seem to appear with the default
+10ms latency that GstBaseAudioSink has.
+Hopefully in the future we will have a better mechanism to pick
+the appropriate latency instead of hardcoding it here.
+
+Upstream-Status: Submitted [https://github.com/PipeWire/pipewire/pull/140]
+---
+ src/gst/gstpwaudiosink.c | 7 +++++++
+ 1 file changed, 7 insertions(+)
+
+diff --git a/src/gst/gstpwaudiosink.c b/src/gst/gstpwaudiosink.c
+index 6cb71385..069996c3 100644
+--- a/src/gst/gstpwaudiosink.c
++++ b/src/gst/gstpwaudiosink.c
+@@ -57,6 +57,13 @@ static void
+ gst_pw_audio_sink_init (GstPwAudioSink * self)
+ {
+ self->props.fd = -1;
++
++ /* Bump the default buffer size up to 21.3 ms, which is the default on most
++ * sound cards, in hope to match the alsa buffer size on the pipewire server.
++ * This may not always happen, but it still sounds better than the 10ms
++ * default latency. This is temporary until we have a better mechanism to
++ * select the appropriate latency */
++ GST_AUDIO_BASE_SINK (self)->latency_time = 21333;
+ }
+
+ static void
+--
+2.20.1
+
diff --git a/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/0015-audioconvert-fmtconvert-assume-F32-on-the-other-port.patch b/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/0015-audioconvert-fmtconvert-assume-F32-on-the-other-port.patch
new file mode 100644
index 00000000..ed3c1b06
--- /dev/null
+++ b/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/0015-audioconvert-fmtconvert-assume-F32-on-the-other-port.patch
@@ -0,0 +1,44 @@
+From aa5de0cfc31df9cd8fb6d24367d2852dbbc8dcb9 Mon Sep 17 00:00:00 2001
+From: George Kiagiadakis <george.kiagiadakis@collabora.com>
+Date: Mon, 29 Jul 2019 16:12:45 +0300
+Subject: [PATCH] audioconvert/fmtconvert: assume F32 on the other port when
+ listing formats
+
+This allows picking F32LE as the default format on links that have
+no restriction and it avoids failing negotiation when the restricted
+end cannot handle S16/F32/F32P
+
+For instance this pipeline would previously fail:
+
+ audio-dsp mode=merge ! audio-dsp mode=convert ! alsa-sink
+old negotiation: S16LE S24_32LE
+new negotiation: F32LE S24_32LE
+
+The link between the audio-dsp nodes has no restriction, so previously
+it would negotiate S16LE, which would then fail to negotiate with alsa-sink
+because fmtconvert does not know how to convert S16LE to S24_32LE directly.
+
+With this change, the middle link negotiates to F32LE, which can be
+converted to anything.
+
+Upstream-Status: Submitted [https://github.com/PipeWire/pipewire/pull/169]
+---
+ spa/plugins/audioconvert/fmtconvert.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/spa/plugins/audioconvert/fmtconvert.c b/spa/plugins/audioconvert/fmtconvert.c
+index df4ffb6b..a330f68f 100644
+--- a/spa/plugins/audioconvert/fmtconvert.c
++++ b/spa/plugins/audioconvert/fmtconvert.c
+@@ -338,7 +338,7 @@ static int port_enum_formats(struct spa_node *node,
+ if (other->have_format)
+ info = other->format;
+ else
+- info.info.raw.format = SPA_AUDIO_FORMAT_S16;
++ info.info.raw.format = SPA_AUDIO_FORMAT_F32;
+
+ if (info.info.raw.format == SPA_AUDIO_FORMAT_F32P ||
+ info.info.raw.format == SPA_AUDIO_FORMAT_F32) {
+--
+2.20.1
+
diff --git a/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/0016-gst-pwaudioringbuffer-set-node.latency-to-get-schedu.patch b/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/0016-gst-pwaudioringbuffer-set-node.latency-to-get-schedu.patch
new file mode 100644
index 00000000..b97e21ff
--- /dev/null
+++ b/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/0016-gst-pwaudioringbuffer-set-node.latency-to-get-schedu.patch
@@ -0,0 +1,41 @@
+From 3af64cf4e1d33c33a9757c0f30c7de1068202540 Mon Sep 17 00:00:00 2001
+From: George Kiagiadakis <george.kiagiadakis@collabora.com>
+Date: Tue, 20 Aug 2019 18:33:35 +0300
+Subject: [PATCH] gst: pwaudioringbuffer: set node.latency to get scheduled
+ correctly in capture mode
+
+Upstream-Status: Pending
+---
+ src/gst/gstpwaudioringbuffer.c | 7 ++++---
+ 1 file changed, 4 insertions(+), 3 deletions(-)
+
+diff --git a/src/gst/gstpwaudioringbuffer.c b/src/gst/gstpwaudioringbuffer.c
+index b92b5feb..2314dd77 100644
+--- a/src/gst/gstpwaudioringbuffer.c
++++ b/src/gst/gstpwaudioringbuffer.c
+@@ -403,11 +403,9 @@ gst_pw_audio_ring_buffer_acquire (GstAudioRingBuffer *buf,
+
+ /* construct param & props objects */
+
++ props = pw_properties_new (NULL, NULL);
+ if (self->props->properties) {
+- props = pw_properties_new (NULL, NULL);
+ gst_structure_foreach (self->props->properties, copy_properties, props);
+- } else {
+- props = NULL;
+ }
+
+ spa_pod_builder_init (&b, buffer, sizeof (buffer));
+@@ -425,6 +423,9 @@ gst_pw_audio_ring_buffer_acquire (GstAudioRingBuffer *buf,
+ self->channels = GST_AUDIO_INFO_CHANNELS (&spec->info);
+ self->segoffset = 0;
+
++ pw_properties_setf(props, "node.latency", "%u/%u",
++ self->segsize / self->bpf, self->rate);
++
+ /* connect stream */
+
+ pw_thread_loop_lock (self->main_loop);
+--
+2.23.0
+
diff --git a/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/0017-connection-move-remaining-data-and-fds.patch b/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/0017-connection-move-remaining-data-and-fds.patch
new file mode 100644
index 00000000..be5ac5ea
--- /dev/null
+++ b/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/0017-connection-move-remaining-data-and-fds.patch
@@ -0,0 +1,61 @@
+From 75247f77eae2c473b18cf8d7e117216f73d2e127 Mon Sep 17 00:00:00 2001
+From: Wim Taymans <wtaymans@redhat.com>
+Date: Tue, 1 Oct 2019 10:43:48 +0200
+Subject: [PATCH] connection: move remaining data and fds
+
+If we can't send all of the data, move the remaining data to the
+start of the buffer so that we can send it again later.
+
+See #111
+
+Upstream-Status: Backport [3d48ba8394396fc8d8cadb1bff3514217ddd70e6]
+---
+ .../module-protocol-native/connection.c | 23 +++++++++++--------
+ 1 file changed, 14 insertions(+), 9 deletions(-)
+
+diff --git a/src/modules/module-protocol-native/connection.c b/src/modules/module-protocol-native/connection.c
+index dbb6a3cf..cb592e41 100644
+--- a/src/modules/module-protocol-native/connection.c
++++ b/src/modules/module-protocol-native/connection.c
+@@ -491,8 +491,12 @@ int pw_protocol_native_connection_flush(struct pw_protocol_native_connection *co
+ if (sent < 0) {
+ if (errno == EINTR)
+ continue;
+- else
+- goto send_error;
++ else {
++ res = -errno;
++ pw_log_error("could not sendmsg on fd:%d n_fds:%d: %s",
++ conn->fd, n_fds, spa_strerror(res));
++ goto exit;
++ }
+ }
+ break;
+ }
+@@ -504,15 +508,16 @@ int pw_protocol_native_connection_flush(struct pw_protocol_native_connection *co
+ n_fds -= outfds;
+ fds += outfds;
+ }
+- buf->buffer_size = size;
+- buf->n_fds = n_fds;
+
+- return 0;
++ res = 0;
+
+- /* ERRORS */
+- send_error:
+- res = -errno;
+- pw_log_error("could not sendmsg: %s", strerror(errno));
++exit:
++ if (size > 0)
++ memmove(buf->buffer_data, data, size);
++ buf->buffer_size = size;
++ if (n_fds > 0)
++ memmove(buf->fds, fds, n_fds * sizeof(int));
++ buf->n_fds = n_fds;
+ return res;
+ }
+
+--
+2.23.0
+
diff --git a/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/0018-protocol-improve-flushing.patch b/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/0018-protocol-improve-flushing.patch
new file mode 100644
index 00000000..e027765e
--- /dev/null
+++ b/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/0018-protocol-improve-flushing.patch
@@ -0,0 +1,222 @@
+From 7e885c029a6cc66ce1881f6f2c50d5f76a7d3372 Mon Sep 17 00:00:00 2001
+From: Wim Taymans <wtaymans@redhat.com>
+Date: Tue, 1 Oct 2019 12:53:56 +0200
+Subject: [PATCH] protocol: improve flushing
+
+Use the IO_OUT flag to schedule flushing instead of a flush_event.
+
+Handle EGAIN and wait for IO_OUT to try again.
+
+Fixes #111
+
+Upstream-Status: Backport [cc8e992cd155b4f19312a5036c7b744fc547410f]
+---
+ src/modules/module-protocol-native.c | 89 +++++++++++++------
+ .../module-protocol-native/connection.c | 2 -
+ 2 files changed, 62 insertions(+), 29 deletions(-)
+
+diff --git a/src/modules/module-protocol-native.c b/src/modules/module-protocol-native.c
+index 411bad6c..b7cd3140 100644
+--- a/src/modules/module-protocol-native.c
++++ b/src/modules/module-protocol-native.c
+@@ -83,8 +83,7 @@ struct client {
+ struct spa_hook conn_listener;
+
+ bool disconnecting;
+- bool flush_signaled;
+- struct spa_source *flush_event;
++ bool flushing;
+ };
+
+ struct server {
+@@ -106,6 +105,7 @@ struct client_data {
+ struct spa_source *source;
+ struct pw_protocol_native_connection *connection;
+ bool busy;
++ bool need_flush;
+ };
+
+ static void
+@@ -194,12 +194,14 @@ client_busy_changed(void *data, bool busy)
+ {
+ struct client_data *c = data;
+ struct pw_client *client = c->client;
+- enum spa_io mask = SPA_IO_ERR | SPA_IO_HUP;
++ uint32_t mask = c->source->mask;
+
+ c->busy = busy;
+
+- if (!busy)
+- mask |= SPA_IO_IN;
++ if (busy)
++ SPA_FLAG_UNSET(mask, SPA_IO_IN);
++ else
++ SPA_FLAG_SET(mask, SPA_IO_IN);
+
+ pw_log_debug("protocol-native %p: busy changed %d", client->protocol, busy);
+ pw_loop_update_io(client->core->main_loop, c->source, mask);
+@@ -214,13 +216,32 @@ connection_data(void *data, int fd, enum spa_io mask)
+ {
+ struct client_data *this = data;
+ struct pw_client *client = this->client;
++ int res;
+
+- if (mask & (SPA_IO_ERR | SPA_IO_HUP)) {
++ if (mask & SPA_IO_HUP) {
+ pw_log_info("protocol-native %p: client %p disconnected", client->protocol, client);
+ pw_client_destroy(client);
+ return;
+ }
+-
++ if (mask & SPA_IO_ERR) {
++ pw_log_error("protocol-native %p: client %p error", client->protocol, client);
++ pw_client_destroy(client);
++ return;
++ }
++ if (mask & SPA_IO_OUT) {
++ res = pw_protocol_native_connection_flush(this->connection);
++ if (res >= 0) {
++ int mask = this->source->mask;
++ SPA_FLAG_UNSET(mask, SPA_IO_OUT);
++ pw_loop_update_io(client->protocol->core->main_loop,
++ this->source, mask);
++ } else if (res != EAGAIN) {
++ pw_log_error("client %p: could not flush: %s",
++ client, spa_strerror(res));
++ pw_client_destroy(client);
++ return;
++ }
++ }
+ if (mask & SPA_IO_IN)
+ process_messages(this);
+ }
+@@ -288,7 +309,8 @@ static struct pw_client *client_new(struct server *s, int fd)
+
+ this->client = client;
+ this->source = pw_loop_add_io(pw_core_get_main_loop(core),
+- fd, SPA_IO_ERR | SPA_IO_HUP, true, connection_data, this);
++ fd, SPA_IO_ERR | SPA_IO_HUP, true,
++ connection_data, this);
+ if (this->source == NULL)
+ goto cleanup_client;
+
+@@ -396,7 +418,7 @@ socket_data(void *data, int fd, enum spa_io mask)
+
+ if (!client->busy)
+ pw_loop_update_io(client->protocol->core->main_loop,
+- c->source, SPA_IO_IN | SPA_IO_ERR | SPA_IO_HUP);
++ c->source, c->source->mask | SPA_IO_IN);
+ }
+
+ static bool add_socket(struct pw_protocol *protocol, struct server *s)
+@@ -479,6 +501,17 @@ on_remote_data(void *data, int fd, enum spa_io mask)
+ res = -EPIPE;
+ goto error;
+ }
++ if (mask & SPA_IO_OUT) {
++ res = pw_protocol_native_connection_flush(conn);
++ if (res >= 0) {
++ int mask = impl->source->mask;
++ SPA_FLAG_UNSET(mask, SPA_IO_OUT);
++ pw_loop_update_io(core->main_loop,
++ impl->source, mask);
++ impl->flushing = false;
++ } else if (res != EAGAIN)
++ goto error;
++ }
+
+ if (mask & SPA_IO_IN) {
+ const struct pw_protocol_native_message *msg;
+@@ -545,23 +578,17 @@ error:
+ }
+
+
+-static void do_flush_event(void *data, uint64_t count)
+-{
+- struct client *impl = data;
+- impl->flush_signaled = false;
+- if (impl->connection)
+- if (pw_protocol_native_connection_flush(impl->connection) < 0)
+- impl->this.disconnect(&impl->this);
+-}
+-
+ static void on_need_flush(void *data)
+ {
+ struct client *impl = data;
+ struct pw_remote *remote = impl->this.remote;
+
+- if (!impl->flush_signaled) {
+- impl->flush_signaled = true;
+- pw_loop_signal_event(remote->core->main_loop, impl->flush_event);
++ if (!impl->flushing) {
++ int mask = impl->source->mask;
++ impl->flushing = true;
++ SPA_FLAG_SET(mask, SPA_IO_OUT);
++ pw_loop_update_io(remote->core->main_loop,
++ impl->source, mask);
+ }
+ }
+
+@@ -619,12 +646,9 @@ static void impl_disconnect(struct pw_protocol_client *client)
+ static void impl_destroy(struct pw_protocol_client *client)
+ {
+ struct client *impl = SPA_CONTAINER_OF(client, struct client, this);
+- struct pw_remote *remote = client->remote;
+
+ impl_disconnect(client);
+
+- pw_loop_destroy_source(remote->core->main_loop, impl->flush_event);
+-
+ if (impl->properties)
+ pw_properties_free(impl->properties);
+
+@@ -665,8 +689,6 @@ impl_new_client(struct pw_protocol *protocol,
+ this->disconnect = impl_disconnect;
+ this->destroy = impl_destroy;
+
+- impl->flush_event = pw_loop_add_event(remote->core->main_loop, do_flush_event, impl);
+-
+ spa_list_append(&protocol->client_list, &this->link);
+
+ return this;
+@@ -701,10 +723,23 @@ static void on_before_hook(void *_data)
+ struct pw_protocol_server *this = &server->this;
+ struct pw_client *client, *tmp;
+ struct client_data *data;
++ int res;
+
+ spa_list_for_each_safe(client, tmp, &this->client_list, protocol_link) {
+ data = client->user_data;
+- pw_protocol_native_connection_flush(data->connection);
++
++ res = pw_protocol_native_connection_flush(data->connection);
++ if (res == -EAGAIN) {
++ int mask = data->source->mask;
++ SPA_FLAG_SET(mask, SPA_IO_OUT);
++ pw_loop_update_io(client->protocol->core->main_loop,
++ data->source, mask);
++ } else if (res < 0) {
++ pw_log_warn("client %p: could not flush: %s",
++ data->client, spa_strerror(res));
++ pw_client_destroy(client);
++ }
++
+ }
+ }
+
+diff --git a/src/modules/module-protocol-native/connection.c b/src/modules/module-protocol-native/connection.c
+index cb592e41..8b9e919d 100644
+--- a/src/modules/module-protocol-native/connection.c
++++ b/src/modules/module-protocol-native/connection.c
+@@ -493,8 +493,6 @@ int pw_protocol_native_connection_flush(struct pw_protocol_native_connection *co
+ continue;
+ else {
+ res = -errno;
+- pw_log_error("could not sendmsg on fd:%d n_fds:%d: %s",
+- conn->fd, n_fds, spa_strerror(res));
+ goto exit;
+ }
+ }
+--
+2.23.0
+
diff --git a/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/pipewire@.service b/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/pipewire@.service
new file mode 100644
index 00000000..e116dc1f
--- /dev/null
+++ b/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/pipewire@.service
@@ -0,0 +1,24 @@
+[Unit]
+Description=Multimedia Service for user %i
+Requires=pipewire@%i.socket
+
+[Install]
+Also=pipewire@%i.socket
+
+[Service]
+Type=simple
+Restart=on-failure
+ExecStart=/usr/bin/pipewire
+
+Environment=XDG_RUNTIME_DIR=/run/user/%i
+Environment=DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/%i/bus
+EnvironmentFile=-/etc/pipewire/environment
+
+User=%i
+Slice=user-%i.slice
+SmackProcessLabel=System::Pipewire
+SupplementaryGroups=audio
+UMask=0077
+CapabilityBoundingSet=
+SystemCallFilter=@basic-io @file-system @io-event @ipc \
+ @memlock @network-io @process @resources @signal
diff --git a/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/pipewire@.socket b/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/pipewire@.socket
new file mode 100644
index 00000000..10cb3227
--- /dev/null
+++ b/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/pipewire@.socket
@@ -0,0 +1,19 @@
+[Unit]
+Description=Multimedia Service socket for user %i
+Requires=afm-user-setup@%i.service
+After=afm-user-setup@%i.service
+
+[Socket]
+Priority=6
+Backlog=5
+ListenStream=/run/user/%i/pipewire-0
+Service=pipewire@%i.service
+SmackLabel=*
+SmackLabelIPIn=System
+SmackLabelIPOut=System
+SocketUser=%i
+SocketGroup=%i
+SocketMode=0660
+
+[Install]
+WantedBy=afm-user-session@%i.target
diff --git a/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/smack-pipewire b/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/smack-pipewire
new file mode 100644
index 00000000..8d5b541f
--- /dev/null
+++ b/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire/smack-pipewire
@@ -0,0 +1,8 @@
+System System::Pipewire rwxa--
+System::Pipewire System -wx---
+System::Pipewire System::Shared r-x---
+System::Pipewire System::Run rwxat-
+System::Pipewire System::Log rwxa--
+System::Pipewire _ r-x--l
+System::Pipewire User::Home r-x--l
+System::Pipewire User::App-Shared rwxat-
diff --git a/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire_git.bb b/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire_git.bb
new file mode 100644
index 00000000..823da421
--- /dev/null
+++ b/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire_git.bb
@@ -0,0 +1,29 @@
+require pipewire.inc
+
+SRC_URI = "gitsm://github.com/PipeWire/pipewire;protocol=https;branch=work \
+ file://0001-spa-include-install-missing-headers.patch \
+ file://0002-extensions-implement-Endpoint-ClientEndpoint-interfa.patch \
+ file://0003-pipewire-cli-add-support-for-printing-endpoint-info-.patch \
+ file://0004-pipewire-cli-add-command-to-modify-endpoint-control-.patch \
+ file://0005-arm-build-with-mno-unaligned-access.patch \
+ file://0006-logger-print-timestamps-on-logged-messages.patch \
+ file://0007-alsa-make-corrections-on-the-timeout-based-on-how-fa.patch \
+ file://0008-audio-dsp-allow-mode-to-be-set-with-a-property.patch \
+ file://0009-audioconvert-do-setup-internal-links-and-buffers-als.patch \
+ file://0010-gst-Implement-new-pwaudio-src-sink-elements-based-on.patch \
+ file://0011-gst-pwaudioringbuffer-make-the-buffer-size-sensitive.patch \
+ file://0012-gst-pwaudioringbuffer-request-pause-play-on-the-appr.patch \
+ file://0013-gst-pwaudioringbuffer-wait-only-for-STREAM_STATE_CON.patch \
+ file://0014-gst-pwaudiosink-set-the-default-latency-time-buffer-.patch \
+ file://0015-audioconvert-fmtconvert-assume-F32-on-the-other-port.patch \
+ file://0016-gst-pwaudioringbuffer-set-node.latency-to-get-schedu.patch \
+ file://0017-connection-move-remaining-data-and-fds.patch \
+ file://0018-protocol-improve-flushing.patch \
+ "
+
+SRCREV = "4be788962e60891237f1f018627bf709ae3981e6"
+
+PV = "0.2.90+git${SRCPV}+3"
+S = "${WORKDIR}/git"
+
+RDEPENDS_${PN} += "virtual/pipewire-sessionmanager virtual/pipewire-config"
diff --git a/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire_git.bbappend b/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire_git.bbappend
new file mode 100644
index 00000000..8a0b0741
--- /dev/null
+++ b/meta-agl-devel/meta-pipewire/recipes-multimedia/pipewire/pipewire_git.bbappend
@@ -0,0 +1,30 @@
+SRC_URI += "\
+ file://pipewire@.service \
+ file://pipewire@.socket \
+ file://smack-pipewire \
+ "
+
+do_install_append() {
+ if ${@bb.utils.contains('DISTRO_FEATURES', 'systemd', 'true', 'false', d)}; then
+ # remove the original user unit files shipped by pipewire
+ rm -rf ${D}${systemd_unitdir}
+
+ # install our own system-level templates
+ mkdir -p ${D}${systemd_system_unitdir}/
+ install -m 0644 ${WORKDIR}/pipewire@.service ${D}${systemd_system_unitdir}/pipewire@.service
+ install -m 0644 ${WORKDIR}/pipewire@.socket ${D}${systemd_system_unitdir}/pipewire@.socket
+
+ # enable the socket to start together with afm-user-session
+ mkdir -p ${D}${systemd_system_unitdir}/afm-user-session@.target.wants
+ ln -sf ../pipewire@.socket ${D}${systemd_system_unitdir}/afm-user-session@.target.wants/pipewire@.socket
+
+ # install smack rules
+ mkdir -p ${D}${sysconfdir}/smack/accesses.d
+ install -m 0644 ${WORKDIR}/smack-pipewire ${D}${sysconfdir}/smack/accesses.d/pipewire
+ fi
+}
+
+FILES_${PN} += "\
+ ${systemd_system_unitdir}/* \
+ ${sysconfdir}/smack/accesses.d/* \
+"
diff --git a/meta-agl-devel/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/wireplumber.conf.in b/meta-agl-devel/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/wireplumber.conf.in
new file mode 100644
index 00000000..76a57419
--- /dev/null
+++ b/meta-agl-devel/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/wireplumber.conf.in
@@ -0,0 +1,55 @@
+# Basic pipewire integration - do not remove
+load-module C libwireplumber-module-pipewire
+
+# Grants access to security confined clients
+load-module C libwireplumber-module-client-permissions
+
+# Endpoint implementation for standard audio devices
+# using software conversions, mixing and volume controls
+load-module C libwireplumber-module-pw-audio-softdsp-endpoint
+
+# Endpoint that provides high-level volume controls for the AGL mixer
+# The streams specified here are the ones that will appear in the mixer.
+# They must match the stream names in the alsa-udev module,
+# except for "Master", which is treated specially.
+load-module C libwireplumber-module-mixer {
+ "streams": <["Master", "Multimedia", "Speech-Low", "Custom-Low",
+ "Navigation", "Speech-High", "Custom-High",
+ "Communication", "Emergency"]>
+}
+
+# Monitors the ALSA devices that are discovered via udev
+# and creates softdsp-endopints for each one of them
+# The streams specified here are the ones that will be available for linking
+# clients. Currently, they are matched against the client's role string.
+load-module C libwireplumber-module-pw-alsa-udev {
+ "streams": <["Multimedia", "Speech-Low", "Custom-Low",
+ "Navigation", "Speech-High", "Custom-High",
+ "Communication", "Emergency"]>
+}
+
+# Monitors the Audio clients that are discovered via pipewire
+# and creates simple-endpoints for each one of them
+load-module C libwireplumber-module-pw-audio-client
+
+# Implements linking clients to devices and maintains
+# information about the devices to be used.
+# Notes:
+# - Devices must be specified in hw:X,Y format, where X and Y are integers.
+# Things like hw:Intel,0 or paths are not understood.
+# - Roles and priorities can be arbitrary strings and arbitrary numbers
+# - Roles are matched against the stream names specified in the modules above.
+load-module C libwireplumber-module-simple-policy {
+ "default-playback-device": <"PLAYBACK">,
+ "default-capture-device": <"CAPTURE">,
+ "role-priorities": <{
+ "Multimedia": 1,
+ "Speech-Low": 2,
+ "Custom-Low": 3,
+ "Navigation": 5,
+ "Speech-High:": 7,
+ "Custom-High": 8,
+ "Communication": 9,
+ "Emergency": 10
+ }>
+}
diff --git a/meta-agl-devel/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl_git.bb b/meta-agl-devel/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl_git.bb
new file mode 100644
index 00000000..7ed9ea1a
--- /dev/null
+++ b/meta-agl-devel/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl_git.bb
@@ -0,0 +1,69 @@
+SUMMARY = "AGL configuration file for wireplumber"
+HOMEPAGE = "https://gitlab.freedesktop.org/gkiagia/wireplumber"
+BUGTRACKER = "https://jira.automotivelinux.org"
+AUTHOR = "George Kiagiadakis <george.kiagiadakis@collabora.com>"
+SECTION = "multimedia"
+
+LICENSE = "MIT"
+LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"
+
+SRC_URI = "file://wireplumber.conf.in"
+
+PACKAGE_ARCH = "${MACHINE_ARCH}"
+
+do_configure[noexec] = "1"
+do_compile[noexec] = "1"
+
+#
+# For device names, any unique substring of the "endpoint" name is valid.
+# To list all endpoints:
+# export XDG_RUNTIME_DIR=/run/user/1001
+# pipewire-cli
+# > connect pipewire-0
+# > list-objects
+# ... and look for objects of type "PipeWire:Interface:Endpoint/0"
+#
+# For instance:
+# id 269, parent 40, type PipeWire:Interface:Endpoint/0
+# media.name = "USB Audio on WD15 Dock (hw:1,0 / node 5)"
+# media.class = "Audio/Sink"
+# id 270, parent 40, type PipeWire:Interface:Endpoint/0
+# media.name = "USB Audio on WD15 Dock (hw:1,0 / node 7)"
+# media.class = "Audio/Source"
+#
+# Audio/Sink endpoints are valid for playback
+# Audio/Source endpoints are valid for capture
+#
+# Wireplumber will first filter endpoints based on the media.class, depending
+# on whether the client is doing playback or capture and then it will look
+# for a sub-string match in the media.name
+#
+DEV_PLAYBACK = "hw:0,0"
+DEV_CAPTURE = "hw:0,0"
+
+DEV_PLAYBACK_dra7xx-evm = "DRA7xx-EVM"
+DEV_CAPTURE_dra7xx-evm = "DRA7xx-EVM"
+
+DEV_PLAYBACK_m3ulcb = "ak4613"
+DEV_CAPTURE_m3ulcb = "ak4613"
+
+DEV_PLAYBACK_h3ulcb = "ak4613"
+DEV_CAPTURE_h3ulcb = "ak4613"
+
+DEV_PLAYBACK_raspberrypi3 = "bcm2835 ALSA on bcm2835 ALSA"
+DEV_CAPTURE_raspberrypi3 = "hw:0,0"
+
+do_install_append() {
+ sed -e "s/PLAYBACK/${DEV_PLAYBACK}/" -e "s/CAPTURE/${DEV_CAPTURE}/" ${WORKDIR}/wireplumber.conf.in > ${WORKDIR}/wireplumber.conf
+ install -d ${D}/${sysconfdir}/wireplumber/
+ install -m 644 ${WORKDIR}/wireplumber.conf ${D}/${sysconfdir}/wireplumber/wireplumber.conf
+}
+
+FILES_${PN} += "\
+ ${sysconfdir}/wireplumber/* \
+"
+CONFFILES_${PN} += "\
+ ${sysconfdir}/wireplumber/* \
+"
+
+RPROVIDES_${PN} += "virtual/wireplumber-config"
diff --git a/meta-agl-devel/meta-pipewire/recipes-multimedia/wireplumber/wireplumber_git.bb b/meta-agl-devel/meta-pipewire/recipes-multimedia/wireplumber/wireplumber_git.bb
new file mode 100644
index 00000000..2979d7e5
--- /dev/null
+++ b/meta-agl-devel/meta-pipewire/recipes-multimedia/wireplumber/wireplumber_git.bb
@@ -0,0 +1,36 @@
+SUMMARY = "Session / Policy Manager for PipeWire"
+HOMEPAGE = "https://gitlab.freedesktop.org/gkiagia/wireplumber"
+BUGTRACKER = "https://gitlab.freedesktop.org/gkiagia/wireplumber/issues"
+AUTHOR = "George Kiagiadakis <george.kiagiadakis@collabora.com>"
+SECTION = "multimedia"
+
+LICENSE = "MIT"
+LIC_FILES_CHKSUM = "file://LICENSE;beginline=3;md5=e8ad01a5182f2c1b3a2640e9ea268264"
+
+inherit meson pkgconfig gobject-introspection
+
+DEPENDS = "glib-2.0 glib-2.0-native pipewire"
+
+SRC_URI = "git://gitlab.freedesktop.org/gkiagia/wireplumber;protocol=https;branch=0.1"
+SRCREV = "50db6a5930d01e9f34dda6952654ee4cb7926405"
+
+PV = "0.1.1+git${SRCPV}"
+S = "${WORKDIR}/git"
+
+PACKAGES =+ "${PN}-config"
+
+FILES_${PN} += "\
+ ${libdir}/wireplumber-*/* \
+"
+RPROVIDES_${PN} += "virtual/pipewire-sessionmanager"
+RDEPENDS_${PN} += "virtual/wireplumber-config"
+
+
+FILES_${PN}-config += "\
+ ${sysconfdir}/wireplumber/* \
+"
+CONFFILES_${PN}-config += "\
+ ${sysconfdir}/wireplumber/* \
+"
+
+RPROVIDES_${PN}-config += "virtual/wireplumber-config"
diff --git a/meta-agl-devel/meta-pipewire/recipes-security/security-manager/security-manager/0001-Adapt-smack-rules-to-allow-connections-to-pipewire.patch b/meta-agl-devel/meta-pipewire/recipes-security/security-manager/security-manager/0001-Adapt-smack-rules-to-allow-connections-to-pipewire.patch
new file mode 100644
index 00000000..821c1e1d
--- /dev/null
+++ b/meta-agl-devel/meta-pipewire/recipes-security/security-manager/security-manager/0001-Adapt-smack-rules-to-allow-connections-to-pipewire.patch
@@ -0,0 +1,25 @@
+From cc5cbaddad6fe559e9e482467266fb18fb00c6a7 Mon Sep 17 00:00:00 2001
+From: George Kiagiadakis <george.kiagiadakis@collabora.com>
+Date: Wed, 26 Jun 2019 16:02:13 +0300
+Subject: [PATCH] Adapt smack rules to allow connections to pipewire
+
+Signed-off-by: George Kiagiadakis <george.kiagiadakis@collabora.com>
+---
+ policy/app-rules-template.smack | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/policy/app-rules-template.smack b/policy/app-rules-template.smack
+index 910f40c..78b75de 100644
+--- a/policy/app-rules-template.smack
++++ b/policy/app-rules-template.smack
+@@ -4,6 +4,7 @@ System ~PKG~ rwxat
+ ~APP~ System::Shared rx
+ ~APP~ System::Run rwxat
+ ~APP~ System::Log rwxa
++~APP~ System::Pipewire rw
+ ~APP~ _ l
+ ~APP~ User::Home rxl
+ ~APP~ User::App-Shared rwxat
+--
+2.20.1
+
diff --git a/meta-agl-devel/meta-pipewire/recipes-security/security-manager/security-manager_%.bbappend b/meta-agl-devel/meta-pipewire/recipes-security/security-manager/security-manager_%.bbappend
new file mode 100644
index 00000000..319a27d6
--- /dev/null
+++ b/meta-agl-devel/meta-pipewire/recipes-security/security-manager/security-manager_%.bbappend
@@ -0,0 +1,2 @@
+FILESEXTRAPATHS_prepend := "${THISDIR}/security-manager:"
+SRC_URI += "file://0001-Adapt-smack-rules-to-allow-connections-to-pipewire.patch"