aboutsummaryrefslogtreecommitdiffstats
path: root/meta-pipewire
diff options
context:
space:
mode:
Diffstat (limited to 'meta-pipewire')
-rw-r--r--meta-pipewire/conf/include/agl-pipewire.inc3
-rw-r--r--meta-pipewire/conf/layer.conf12
-rw-r--r--meta-pipewire/recipes-connectivity/bluez-alsa/bluez-alsa/0001-utils-add-a-gstreamer-helper-application-for-interco.patch517
-rw-r--r--meta-pipewire/recipes-connectivity/bluez-alsa/bluez-alsa/bluealsa-gst-helper@.service18
-rw-r--r--meta-pipewire/recipes-connectivity/bluez-alsa/bluez-alsa_git.bbappend35
-rw-r--r--meta-pipewire/recipes-core/packagegroups/packagegroup-pipewire.bb17
-rw-r--r--meta-pipewire/recipes-multimedia/agl-service-audiomixer/agl-service-audiomixer_git.bb17
-rw-r--r--meta-pipewire/recipes-multimedia/pipewire/pipewire-conf-agl/client.env10
-rw-r--r--meta-pipewire/recipes-multimedia/pipewire/pipewire-conf-agl/pipewire.conf.in17
-rw-r--r--meta-pipewire/recipes-multimedia/pipewire/pipewire-conf-agl/server.env12
-rw-r--r--meta-pipewire/recipes-multimedia/pipewire/pipewire-conf-agl_git.bb43
-rw-r--r--meta-pipewire/recipes-multimedia/pipewire/pipewire.inc120
-rw-r--r--meta-pipewire/recipes-multimedia/pipewire/pipewire/0001-meson-revert-version-check-to-require-meson-0.47-not.patch30
-rw-r--r--meta-pipewire/recipes-multimedia/pipewire/pipewire/0002-arm-build-with-mno-unaligned-access.patch30
-rw-r--r--meta-pipewire/recipes-multimedia/pipewire/pipewire/0003-gst-Implement-new-pwaudio-src-sink-elements-based-on.patch1280
-rw-r--r--meta-pipewire/recipes-multimedia/pipewire/pipewire/0004-audioconvert-always-assume-that-output-ports-are-NOT.patch35
-rw-r--r--meta-pipewire/recipes-multimedia/pipewire/pipewire/0005-module-access-add-same-sec-label-mode.patch94
-rw-r--r--meta-pipewire/recipes-multimedia/pipewire/pipewire/0006-alsa-pcm-call-reuse_buffers-when-resetting-the-state.patch30
-rw-r--r--meta-pipewire/recipes-multimedia/pipewire/pipewire/pipewire@.service24
-rw-r--r--meta-pipewire/recipes-multimedia/pipewire/pipewire/pipewire@.socket19
-rw-r--r--meta-pipewire/recipes-multimedia/pipewire/pipewire/smack-pipewire8
-rw-r--r--meta-pipewire/recipes-multimedia/pipewire/pipewire_git.bb17
-rw-r--r--meta-pipewire/recipes-multimedia/pipewire/pipewire_git.bbappend30
-rw-r--r--meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/00-audio-sink.endpoint10
-rw-r--r--meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/00-audio-source.endpoint10
-rw-r--r--meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/00-default-input-audio.endpoint-link7
-rw-r--r--meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/00-default-output-audio.endpoint-link7
-rw-r--r--meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/00-stream-input-audio.endpoint9
-rw-r--r--meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/00-stream-output-audio.endpoint9
-rw-r--r--meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/01-hw00-audio-sink.endpoint12
-rw-r--r--meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/01-hw00-audio-source.endpoint12
-rw-r--r--meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/30-ak4613-audio-sink.endpoint12
-rw-r--r--meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/30-ak4613-audio-source.endpoint12
-rw-r--r--meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/30-dra7xx-audio-sink.endpoint12
-rw-r--r--meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/30-dra7xx-audio-source.endpoint12
-rw-r--r--meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/30-rcarsound-audio-sink.endpoint12
-rw-r--r--meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/30-rcarsound-audio-source.endpoint12
-rw-r--r--meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/30-rpi3-audio-sink.endpoint13
-rw-r--r--meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/40-fiberdyne-amp.endpoint12
-rw-r--r--meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/40-microchip-mic.endpoint12
-rw-r--r--meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/70-usb-audio-sink.endpoint12
-rw-r--r--meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/70-usb-audio-source.endpoint12
-rw-r--r--meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/bluealsa-input-audio.endpoint-link11
-rw-r--r--meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/bluealsa-output-audio.endpoint-link11
-rw-r--r--meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/capture.streams3
-rw-r--r--meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/playback.streams31
-rw-r--r--meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/wireplumber.conf30
-rw-r--r--meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl_git.bb57
-rw-r--r--meta-pipewire/recipes-multimedia/wireplumber/wireplumber/0001-Build-cpptoml-without-a-cmake-subproject.patch28
-rw-r--r--meta-pipewire/recipes-multimedia/wireplumber/wireplumber_git.bb46
-rw-r--r--meta-pipewire/recipes-security/cynagora/cynagora_%.bbappend5
-rw-r--r--meta-pipewire/recipes-security/security-manager/security-manager_%.bbappend4
52 files changed, 2853 insertions, 0 deletions
diff --git a/meta-pipewire/conf/include/agl-pipewire.inc b/meta-pipewire/conf/include/agl-pipewire.inc
new file mode 100644
index 000000000..edd893115
--- /dev/null
+++ b/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-pipewire/conf/layer.conf b/meta-pipewire/conf/layer.conf
new file mode 100644
index 000000000..68113221d
--- /dev/null
+++ b/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 = "dunfell"
diff --git a/meta-pipewire/recipes-connectivity/bluez-alsa/bluez-alsa/0001-utils-add-a-gstreamer-helper-application-for-interco.patch b/meta-pipewire/recipes-connectivity/bluez-alsa/bluez-alsa/0001-utils-add-a-gstreamer-helper-application-for-interco.patch
new file mode 100644
index 000000000..6c9a388c8
--- /dev/null
+++ b/meta-pipewire/recipes-connectivity/bluez-alsa/bluez-alsa/0001-utils-add-a-gstreamer-helper-application-for-interco.patch
@@ -0,0 +1,517 @@
+From f2e6a0a324106b40195f88953e55a355875d2b1b 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 | 432 +++++++++++++++++++++++++++++++++++++++++++++
+ 3 files changed, 459 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..de1d47c
+--- /dev/null
++++ b/utils/gst-helper.c
+@@ -0,0 +1,432 @@
++/* 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];
++ /* the queue & pwaudiosink of the sink pipeline */
++ GstElement *queue;
++ GstElement *pwelem;
++};
++
++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 GstBusSyncReply
++bus_sync_handler(GstBus *bus, GstMessage *message, gpointer user_data)
++{
++ struct worker *w = user_data;
++ GstState s;
++
++ switch (GST_MESSAGE_TYPE (message)) {
++ case GST_MESSAGE_REQUEST_STATE:
++ gst_message_parse_request_state (message, &s);
++
++ debug ("corked: %d", (s == GST_STATE_PAUSED));
++
++ /* drop queue data when corked */
++ g_object_set (w->queue,
++ "leaky", (s == GST_STATE_PAUSED) ? 2 /* downstream */ : 0 /* no */,
++ NULL);
++ gst_element_set_state (w->pwelem, s);
++
++ /* flush the queue when resuming */
++ if (s == GST_STATE_PLAYING) {
++ gst_element_send_event (w->queue, gst_event_new_flush_start ());
++ gst_element_send_event (w->queue, gst_event_new_flush_stop (FALSE));
++ }
++ break;
++ default:
++ break;
++ }
++
++ gst_message_unref (message);
++ return GST_BUS_DROP;
++}
++
++static int
++worker_start_pipeline(struct worker *w, int id, int mode, int profile)
++{
++ GError *gerr = NULL;
++ DBusError err = DBUS_ERROR_INIT;
++ const gchar * role = NULL;
++
++ 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 ! identity sync=true "
++ "! queue name=queue leaky=no max-size-time=0 max-size-buffers=0 max-size-bytes=192000 "
++ "! pwaudiosink name=pwelem",
++ &gerr);
++
++ /* a2dp is for music, sco is for calls */
++ role = (profile == BA_PCM_FLAG_PROFILE_A2DP) ? "Multimedia" : "Communication";
++ }
++ 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 name=queue leaky=downstream max-size-time=0 max-size-buffers=0 max-size-bytes=9600 "
++ "! audioconvert ! audioresample ! capsfilter name=capsf "
++ "! fdsink name=fdelem", &gerr);
++
++ role = "Communication";
++ }
++
++ 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) queue = gst_bin_get_by_name(GST_BIN(w->pipeline[id]), "queue");
++ 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, role,
++ "bluealsa.profile", G_TYPE_STRING,
++ (profile == BA_PCM_FLAG_PROFILE_SCO) ? "sco" : "a2dp",
++ 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);
++
++ if (mode == BA_PCM_FLAG_SINK) {
++ g_autoptr (GstBus) bus = gst_pipeline_get_bus(GST_PIPELINE(w->pipeline[id]));
++ gst_bus_set_sync_handler(bus, bus_sync_handler, w, NULL);
++ w->queue = queue;
++ w->pwelem = pwelem;
++ }
++
++ 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.24.0
+
diff --git a/meta-pipewire/recipes-connectivity/bluez-alsa/bluez-alsa/bluealsa-gst-helper@.service b/meta-pipewire/recipes-connectivity/bluez-alsa/bluez-alsa/bluealsa-gst-helper@.service
new file mode 100644
index 000000000..495ab6222
--- /dev/null
+++ b/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-pipewire/recipes-connectivity/bluez-alsa/bluez-alsa_git.bbappend b/meta-pipewire/recipes-connectivity/bluez-alsa/bluez-alsa_git.bbappend
new file mode 100644
index 000000000..2f9699a83
--- /dev/null
+++ b/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-pipewire/recipes-core/packagegroups/packagegroup-pipewire.bb b/meta-pipewire/recipes-core/packagegroups/packagegroup-pipewire.bb
new file mode 100644
index 000000000..4020f1e24
--- /dev/null
+++ b/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-pipewire/recipes-multimedia/agl-service-audiomixer/agl-service-audiomixer_git.bb b/meta-pipewire/recipes-multimedia/agl-service-audiomixer/agl-service-audiomixer_git.bb
new file mode 100644
index 000000000..2a8261195
--- /dev/null
+++ b/meta-pipewire/recipes-multimedia/agl-service-audiomixer/agl-service-audiomixer_git.bb
@@ -0,0 +1,17 @@
+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 wireplumber json-c"
+RDEPENDS_${PN} = "agl-service-signal-composer"
diff --git a/meta-pipewire/recipes-multimedia/pipewire/pipewire-conf-agl/client.env b/meta-pipewire/recipes-multimedia/pipewire/pipewire-conf-agl/client.env
new file mode 100644
index 000000000..9b44cee01
--- /dev/null
+++ b/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-pipewire/recipes-multimedia/pipewire/pipewire-conf-agl/pipewire.conf.in b/meta-pipewire/recipes-multimedia/pipewire/pipewire-conf-agl/pipewire.conf.in
new file mode 100644
index 000000000..6c055bcff
--- /dev/null
+++ b/meta-pipewire/recipes-multimedia/pipewire/pipewire-conf-agl/pipewire.conf.in
@@ -0,0 +1,17 @@
+# daemon config file for PipeWire version "0.2.9"
+# distributed by Automotive Grade Linux
+
+add-spa-lib audio.convert* audioconvert/libspa-audioconvert
+add-spa-lib api.alsa.* alsa/libspa-alsa
+add-spa-lib api.v4l2.* v4l2/libspa-v4l2
+add-spa-lib api.bluez5.* bluez5/libspa-bluez5
+
+load-module libpipewire-module-protocol-native
+load-module libpipewire-module-spa-node-factory
+load-module libpipewire-module-client-node
+load-module libpipewire-module-client-device
+load-module libpipewire-module-access same-sec-label-mode=1
+load-module libpipewire-module-adapter
+load-module libpipewire-module-link-factory
+load-module libpipewire-module-session-manager
+exec /usr/bin/wireplumber
diff --git a/meta-pipewire/recipes-multimedia/pipewire/pipewire-conf-agl/server.env b/meta-pipewire/recipes-multimedia/pipewire/pipewire-conf-agl/server.env
new file mode 100644
index 000000000..c74b941d6
--- /dev/null
+++ b/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-pipewire/recipes-multimedia/pipewire/pipewire-conf-agl_git.bb b/meta-pipewire/recipes-multimedia/pipewire/pipewire-conf-agl_git.bb
new file mode 100644
index 000000000..a28c6534e
--- /dev/null
+++ b/meta-pipewire/recipes-multimedia/pipewire/pipewire-conf-agl_git.bb
@@ -0,0 +1,43 @@
+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.in \
+ file://client.env \
+ file://server.env \
+ "
+
+do_configure[noexec] = "1"
+do_compile[noexec] = "1"
+
+do_install_append() {
+ # enable optional features in the config
+ BLUEZ5=${@bb.utils.contains('DISTRO_FEATURES', 'bluez5', '', '#', d)}
+ sed -e "s/#IF_BLUEZ5 /${BLUEZ5}/" ${WORKDIR}/pipewire.conf.in > ${WORKDIR}/pipewire.conf
+
+ # install our custom config
+ 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-pipewire/recipes-multimedia/pipewire/pipewire.inc b/meta-pipewire/recipes-multimedia/pipewire/pipewire.inc
new file mode 100644
index 000000000..b3081ca43
--- /dev/null
+++ b/meta-pipewire/recipes-multimedia/pipewire/pipewire.inc
@@ -0,0 +1,120 @@
+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 audiomixer \
+ 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,"
+PACKAGECONFIG[audiomixer] = "-Daudiomixer=true,-Daudiomixer=false,"
+PACKAGECONFIG[audiotestsrc] = "-Daudiotestsrc=true,-Daudiotestsrc=false, "
+PACKAGECONFIG[bluez5] = "-Dbluez5=true,-Dbluez5=false,bluez5 sbc"
+PACKAGECONFIG[jack] = "-Djack=true,-Djack=false,jack"
+PACKAGECONFIG[v4l2] = "-Dv4l2=true,-Dv4l2=false,udev v4l-utils"
+PACKAGECONFIG[videotestsrc] = "-Dvideotestsrc=true,-Dvideotestsrc=false, "
+PACKAGECONFIG[vulkan] = "-Dvulkan=true,-Dvulkan=false,vulkan"
+
+# 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-pipewire/recipes-multimedia/pipewire/pipewire/0001-meson-revert-version-check-to-require-meson-0.47-not.patch b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0001-meson-revert-version-check-to-require-meson-0.47-not.patch
new file mode 100644
index 000000000..4e7bb0d4f
--- /dev/null
+++ b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0001-meson-revert-version-check-to-require-meson-0.47-not.patch
@@ -0,0 +1,30 @@
+From 5a249321aa84cd74e3d83bcd555c85fba3cd682d Mon Sep 17 00:00:00 2001
+From: George Kiagiadakis <george.kiagiadakis@collabora.com>
+Date: Sun, 22 Sep 2019 17:59:19 +0300
+Subject: [PATCH] meson: revert version check to require meson 0.47, not 0.50
+
+meson 0.50 is not really needed, but there are some strange warnings
+if you require an older version; in any case, AGL does not have 0.50
+yet, so let's not fail compilation because of that...
+
+Upstream-Status: Inappropriate [workaround]
+---
+ meson.build | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/meson.build b/meson.build
+index 2734b0d2..c9da6b4d 100644
+--- a/meson.build
++++ b/meson.build
+@@ -1,7 +1,7 @@
+ project('pipewire', ['c' ],
+ version : '0.2.9',
+ license : 'MIT',
+- meson_version : '>= 0.50.0',
++ meson_version : '>= 0.47.0',
+ default_options : [ 'warning_level=1',
+ 'c_std=gnu99',
+ 'buildtype=debugoptimized' ])
+--
+2.24.0
+
diff --git a/meta-pipewire/recipes-multimedia/pipewire/pipewire/0002-arm-build-with-mno-unaligned-access.patch b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0002-arm-build-with-mno-unaligned-access.patch
new file mode 100644
index 000000000..2077af63d
--- /dev/null
+++ b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0002-arm-build-with-mno-unaligned-access.patch
@@ -0,0 +1,30 @@
+From e4b81946baf2d8c08de87088c01a1d87ae4f03d9 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://gitlab.freedesktop.org/pipewire/pipewire/issues/161
+---
+ meson.build | 5 +++++
+ 1 file changed, 5 insertions(+)
+
+diff --git a/meson.build b/meson.build
+index c9da6b4d..5c121339 100644
+--- a/meson.build
++++ b/meson.build
+@@ -52,6 +52,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.24.0
+
diff --git a/meta-pipewire/recipes-multimedia/pipewire/pipewire/0003-gst-Implement-new-pwaudio-src-sink-elements-based-on.patch b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0003-gst-Implement-new-pwaudio-src-sink-elements-based-on.patch
new file mode 100644
index 000000000..b3eba21f7
--- /dev/null
+++ b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0003-gst-Implement-new-pwaudio-src-sink-elements-based-on.patch
@@ -0,0 +1,1280 @@
+From 1b1f884a165ed7b2147affbdddf85a641d4cf180 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: Denied
+See https://gitlab.freedesktop.org/pipewire/pipewire/merge_requests/140
+---
+ src/gst/gstpipewire.c | 8 +-
+ src/gst/gstpwaudioringbuffer.c | 565 +++++++++++++++++++++++++++++++++
+ src/gst/gstpwaudioringbuffer.h | 83 +++++
+ src/gst/gstpwaudiosink.c | 207 ++++++++++++
+ src/gst/gstpwaudiosink.h | 48 +++
+ src/gst/gstpwaudiosrc.c | 200 ++++++++++++
+ src/gst/gstpwaudiosrc.h | 48 +++
+ src/gst/meson.build | 6 +
+ 8 files changed, 1164 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..babf2d83
+--- /dev/null
++++ b/src/gst/gstpwaudioringbuffer.c
+@@ -0,0 +1,565 @@
++/* 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);
++ 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));
++ break;
++ case PW_STREAM_STATE_CONNECTING:
++ break;
++ case PW_STREAM_STATE_PAUSED:
++ 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_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);
++}
++
++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_param_changed (void *data, uint32_t id, 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];
++
++ if (format == NULL || id != SPA_PARAM_Format)
++ return;
++
++ 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_update_params (self->stream, 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_INFO_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,
++ .param_changed = on_stream_param_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 */
++
++ props = pw_properties_new (NULL, NULL);
++ if (self->props->properties) {
++ gst_structure_foreach (self->props->properties, copy_properties, props);
++ }
++
++ 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;
++
++ pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%u/%u",
++ self->segsize / self->bpf, self->rate);
++ GST_DEBUG_OBJECT (self->elem, "segsize:%u, bpf:%u, node.latency = %s",
++ self->segsize, self->bpf, pw_properties_get (props, PW_KEY_NODE_LATENCY));
++
++ /* 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 CONFIGURE");
++
++ if (!wait_for_stream_state (self, PW_STREAM_STATE_PAUSED))
++ 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..069996c3
+--- /dev/null
++++ b/src/gst/gstpwaudiosink.c
+@@ -0,0 +1,207 @@
++/* 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;
++
++ /* 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
++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.24.0
+
diff --git a/meta-pipewire/recipes-multimedia/pipewire/pipewire/0004-audioconvert-always-assume-that-output-ports-are-NOT.patch b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0004-audioconvert-always-assume-that-output-ports-are-NOT.patch
new file mode 100644
index 000000000..beb878390
--- /dev/null
+++ b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0004-audioconvert-always-assume-that-output-ports-are-NOT.patch
@@ -0,0 +1,35 @@
+From ce155eb0073fba84556782633f79bb7d03492c07 Mon Sep 17 00:00:00 2001
+From: George Kiagiadakis <george.kiagiadakis@collabora.com>
+Date: Wed, 2 Oct 2019 21:40:34 +0300
+Subject: [PATCH] audioconvert: always assume that output ports are NOT monitor
+ ports
+
+Otherwise, when we setup audioconvert in merge+split mode,
+it assumes that the splitter's ports are monitor ports and
+belong to the merger.
+
+Upstream-Status: Inappropriate [workaround]
+---
+ spa/plugins/audioconvert/audioconvert.c | 4 ++++
+ 1 file changed, 4 insertions(+)
+
+diff --git a/spa/plugins/audioconvert/audioconvert.c b/spa/plugins/audioconvert/audioconvert.c
+index 74a62a35..72da37d1 100644
+--- a/spa/plugins/audioconvert/audioconvert.c
++++ b/spa/plugins/audioconvert/audioconvert.c
+@@ -113,8 +113,12 @@ struct impl {
+ unsigned int add_listener:1;
+ };
+
++#if 0
+ #define IS_MONITOR_PORT(this,dir,port_id) (dir == SPA_DIRECTION_OUTPUT && port_id > 0 && \
+ this->mode[SPA_DIRECTION_INPUT] == SPA_PARAM_PORT_CONFIG_MODE_dsp)
++#else
++#define IS_MONITOR_PORT(this,dir,port_id) (false)
++#endif
+
+ static void emit_node_info(struct impl *this, bool full)
+ {
+--
+2.24.0
+
diff --git a/meta-pipewire/recipes-multimedia/pipewire/pipewire/0005-module-access-add-same-sec-label-mode.patch b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0005-module-access-add-same-sec-label-mode.patch
new file mode 100644
index 000000000..07a1ec114
--- /dev/null
+++ b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0005-module-access-add-same-sec-label-mode.patch
@@ -0,0 +1,94 @@
+From 19fad1a4fa8bdc4f02aac4e169e7ff9cab18bdcd Mon Sep 17 00:00:00 2001
+From: George Kiagiadakis <george.kiagiadakis@collabora.com>
+Date: Tue, 19 Nov 2019 17:09:07 +0200
+Subject: [PATCH] module-access: add same-sec-label-mode
+
+This is a mode where the access module allows all clients that have
+the same security label as the pipewire daemon, and every other
+client is put on the restricted state.
+
+In systems that use SMACK security labels, such as AGL, this allows
+the session manager (which is spawned by pipewire, inheriting the
+same smack label) to have full access to all objects, while every
+other client is restricted and the session manager must decide
+what to do with it
+
+Note that while this option is configurable, there is no loss of
+security if this option is not set in the configuration. Clients
+that don't have the same security context will be considered to
+be flatpak clients because pipewire will not be able to open
+/proc/pid/cmdline. This however results in some unwanted error
+messages that may be confusing.
+
+Upstream-Status: Inappropriate [agl/smack specific]
+---
+ src/modules/module-access.c | 45 ++++++++++++++++++++++++++++++++++++-
+ 1 file changed, 44 insertions(+), 1 deletion(-)
+
+diff --git a/src/modules/module-access.c b/src/modules/module-access.c
+index 09dafa43..f75306d9 100644
+--- a/src/modules/module-access.c
++++ b/src/modules/module-access.c
+@@ -50,6 +50,30 @@ struct impl {
+ struct spa_hook module_listener;
+ };
+
++static int check_seclabel(const char *str)
++{
++ char attr[1024];
++ int fd, len;
++
++ fd = open("/proc/self/attr/current", O_RDONLY);
++ if (fd < 0)
++ return -errno;
++
++ if ((len = read(fd, attr, 1024)) <= 0) {
++ close(fd);
++ return -EIO;
++ }
++ attr[len] = '\0';
++
++ if (strcmp(attr, str) == 0) {
++ close(fd);
++ return 1;
++ }
++
++ close(fd);
++ return 0;
++}
++
+ static int check_cmdline(struct pw_client *client, int pid, const char *str)
+ {
+ char path[2048];
+@@ -121,8 +145,27 @@ core_check_access(void *data, struct pw_client *client)
+ const char *str;
+ int pid, res;
+
++ props = pw_client_get_properties(client);
++
++ if (impl->properties &&
++ (str = pw_properties_get(impl->properties, "same-sec-label-mode")) != NULL &&
++ strcmp(str, "1") == 0) {
++ if (props && (str = pw_properties_get(props, PW_KEY_SEC_LABEL)) != NULL) {
++ res = check_seclabel(str);
++ if (res == 1)
++ goto granted;
++ else if (res < 0)
++ pw_log_warn("module %p: client %p seclabel check failed: %s",
++ impl, client, spa_strerror(res));
++ }
++ pw_log_debug("module %p: seclabel restricted client %p added",
++ impl, client);
++ items[0] = SPA_DICT_ITEM_INIT(PW_KEY_ACCESS, "restricted");
++ goto wait_permissions;
++ }
++
+ pid = -EINVAL;
+- if ((props = pw_client_get_properties(client)) != NULL) {
++ if (props != NULL) {
+ if ((str = pw_properties_get(props, PW_KEY_SEC_PID)) != NULL)
+ pid = atoi(str);
+ }
+--
+2.24.0
+
diff --git a/meta-pipewire/recipes-multimedia/pipewire/pipewire/0006-alsa-pcm-call-reuse_buffers-when-resetting-the-state.patch b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0006-alsa-pcm-call-reuse_buffers-when-resetting-the-state.patch
new file mode 100644
index 000000000..cae4d70f6
--- /dev/null
+++ b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0006-alsa-pcm-call-reuse_buffers-when-resetting-the-state.patch
@@ -0,0 +1,30 @@
+From 5946fbd2ca3a7f892b4ebc10090f62df6bb1ec88 Mon Sep 17 00:00:00 2001
+From: George Kiagiadakis <george.kiagiadakis@collabora.com>
+Date: Thu, 9 Jan 2020 19:27:23 +0200
+Subject: [PATCH] alsa-pcm: call reuse_buffers when resetting the state of the
+ buffers
+
+This allows the upstream node to put buffers back to its pool in case
+they were left around in the ready list locally when the alsa-pcm-sink
+was last paused.
+
+Fixes #203
+---
+ spa/plugins/alsa/alsa-pcm.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/spa/plugins/alsa/alsa-pcm.c b/spa/plugins/alsa/alsa-pcm.c
+index 63d75549..a6f22cf0 100644
+--- a/spa/plugins/alsa/alsa-pcm.c
++++ b/spa/plugins/alsa/alsa-pcm.c
+@@ -1115,6 +1115,7 @@ static void reset_buffers(struct state *this)
+ struct buffer *b = &this->buffers[i];
+ if (this->stream == SND_PCM_STREAM_PLAYBACK) {
+ SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUT);
++ spa_node_call_reuse_buffer(&this->callbacks, 0, b->id);
+ } else {
+ spa_list_append(&this->free, &b->link);
+ SPA_FLAG_CLEAR(b->flags, BUFFER_FLAG_OUT);
+--
+2.24.1
+
diff --git a/meta-pipewire/recipes-multimedia/pipewire/pipewire/pipewire@.service b/meta-pipewire/recipes-multimedia/pipewire/pipewire/pipewire@.service
new file mode 100644
index 000000000..e116dc1fa
--- /dev/null
+++ b/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-pipewire/recipes-multimedia/pipewire/pipewire/pipewire@.socket b/meta-pipewire/recipes-multimedia/pipewire/pipewire/pipewire@.socket
new file mode 100644
index 000000000..10cb32276
--- /dev/null
+++ b/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-pipewire/recipes-multimedia/pipewire/pipewire/smack-pipewire b/meta-pipewire/recipes-multimedia/pipewire/pipewire/smack-pipewire
new file mode 100644
index 000000000..8d5b541ff
--- /dev/null
+++ b/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-pipewire/recipes-multimedia/pipewire/pipewire_git.bb b/meta-pipewire/recipes-multimedia/pipewire/pipewire_git.bb
new file mode 100644
index 000000000..1a4e4eb97
--- /dev/null
+++ b/meta-pipewire/recipes-multimedia/pipewire/pipewire_git.bb
@@ -0,0 +1,17 @@
+require pipewire.inc
+
+SRC_URI = "git://gitlab.freedesktop.org/pipewire/pipewire.git;protocol=https;branch=master \
+ file://0001-meson-revert-version-check-to-require-meson-0.47-not.patch \
+ file://0002-arm-build-with-mno-unaligned-access.patch \
+ file://0003-gst-Implement-new-pwaudio-src-sink-elements-based-on.patch \
+ file://0004-audioconvert-always-assume-that-output-ports-are-NOT.patch \
+ file://0005-module-access-add-same-sec-label-mode.patch \
+ file://0006-alsa-pcm-call-reuse_buffers-when-resetting-the-state.patch \
+ "
+
+SRCREV = "b0932e687fc47e0872ca291531f2291d99042d70"
+
+PV = "0.2.91+git${SRCPV}+2"
+S = "${WORKDIR}/git"
+
+RDEPENDS_${PN} += "virtual/pipewire-sessionmanager virtual/pipewire-config"
diff --git a/meta-pipewire/recipes-multimedia/pipewire/pipewire_git.bbappend b/meta-pipewire/recipes-multimedia/pipewire/pipewire_git.bbappend
new file mode 100644
index 000000000..8a0b0741f
--- /dev/null
+++ b/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-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/00-audio-sink.endpoint b/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/00-audio-sink.endpoint
new file mode 100644
index 000000000..4bc435742
--- /dev/null
+++ b/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/00-audio-sink.endpoint
@@ -0,0 +1,10 @@
+[match-node]
+priority = 0
+properties = [
+ { name = "media.class", value = "Audio/Sink" },
+]
+
+[endpoint]
+type = "pw-audio-softdsp-endpoint"
+direction = "sink"
+streams = "playback.streams"
diff --git a/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/00-audio-source.endpoint b/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/00-audio-source.endpoint
new file mode 100644
index 000000000..7657f6f40
--- /dev/null
+++ b/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/00-audio-source.endpoint
@@ -0,0 +1,10 @@
+[match-node]
+priority = 0
+properties = [
+ { name = "media.class", value = "Audio/Source" },
+]
+
+[endpoint]
+type = "pw-audio-softdsp-endpoint"
+direction = "source"
+streams = "capture.streams"
diff --git a/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/00-default-input-audio.endpoint-link b/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/00-default-input-audio.endpoint-link
new file mode 100644
index 000000000..4b70dc89f
--- /dev/null
+++ b/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/00-default-input-audio.endpoint-link
@@ -0,0 +1,7 @@
+[match-endpoint]
+priority = 0
+direction = "sink"
+media_class = "Stream/Input/Audio"
+
+[endpoint-link]
+keep = false
diff --git a/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/00-default-output-audio.endpoint-link b/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/00-default-output-audio.endpoint-link
new file mode 100644
index 000000000..5d6428f94
--- /dev/null
+++ b/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/00-default-output-audio.endpoint-link
@@ -0,0 +1,7 @@
+[match-endpoint]
+priority = 0
+direction = "source"
+media_class = "Stream/Output/Audio"
+
+[endpoint-link]
+keep = false
diff --git a/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/00-stream-input-audio.endpoint b/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/00-stream-input-audio.endpoint
new file mode 100644
index 000000000..2993f3e44
--- /dev/null
+++ b/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/00-stream-input-audio.endpoint
@@ -0,0 +1,9 @@
+[match-node]
+priority = 0
+properties = [
+ { name = "media.class", value = "Stream/Input/Audio" },
+]
+
+[endpoint]
+direction = "sink"
+type = "pw-audio-softdsp-endpoint"
diff --git a/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/00-stream-output-audio.endpoint b/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/00-stream-output-audio.endpoint
new file mode 100644
index 000000000..1cf82ea02
--- /dev/null
+++ b/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/00-stream-output-audio.endpoint
@@ -0,0 +1,9 @@
+[match-node]
+priority = 0
+properties = [
+ { name = "media.class", value = "Stream/Output/Audio" },
+]
+
+[endpoint]
+direction = "source"
+type = "pw-audio-softdsp-endpoint"
diff --git a/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/01-hw00-audio-sink.endpoint b/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/01-hw00-audio-sink.endpoint
new file mode 100644
index 000000000..85a9b5117
--- /dev/null
+++ b/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/01-hw00-audio-sink.endpoint
@@ -0,0 +1,12 @@
+[match-node]
+priority = 1
+properties = [
+ { name = "media.class", value = "Audio/Sink" },
+ { name = "api.alsa.path", value = "hw:0,0" },
+]
+
+[endpoint]
+type = "pw-audio-softdsp-endpoint"
+direction = "sink"
+streams = "playback.streams"
+priority = 1
diff --git a/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/01-hw00-audio-source.endpoint b/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/01-hw00-audio-source.endpoint
new file mode 100644
index 000000000..c77701c0d
--- /dev/null
+++ b/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/01-hw00-audio-source.endpoint
@@ -0,0 +1,12 @@
+[match-node]
+priority = 1
+properties = [
+ { name = "media.class", value = "Audio/Source" },
+ { name = "api.alsa.path", value = "hw:0,0" },
+]
+
+[endpoint]
+type = "pw-audio-softdsp-endpoint"
+direction = "source"
+streams = "capture.streams"
+priority = 1
diff --git a/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/30-ak4613-audio-sink.endpoint b/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/30-ak4613-audio-sink.endpoint
new file mode 100644
index 000000000..53f9d0df7
--- /dev/null
+++ b/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/30-ak4613-audio-sink.endpoint
@@ -0,0 +1,12 @@
+[match-node]
+priority = 30
+properties = [
+ { name = "media.class", value = "Audio/Sink" },
+ { name = "api.alsa.card.id", value = "rcarsound" },
+]
+
+[endpoint]
+type = "pw-audio-softdsp-endpoint"
+direction = "sink"
+streams = "playback.streams"
+priority = 30
diff --git a/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/30-ak4613-audio-source.endpoint b/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/30-ak4613-audio-source.endpoint
new file mode 100644
index 000000000..d72d7e31c
--- /dev/null
+++ b/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/30-ak4613-audio-source.endpoint
@@ -0,0 +1,12 @@
+[match-node]
+priority = 30
+properties = [
+ { name = "media.class", value = "Audio/Source" },
+ { name = "api.alsa.card.id", value = "rcarsound" },
+]
+
+[endpoint]
+type = "pw-audio-softdsp-endpoint"
+direction = "source"
+streams = "capture.streams"
+priority = 30
diff --git a/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/30-dra7xx-audio-sink.endpoint b/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/30-dra7xx-audio-sink.endpoint
new file mode 100644
index 000000000..becd21e2e
--- /dev/null
+++ b/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/30-dra7xx-audio-sink.endpoint
@@ -0,0 +1,12 @@
+[match-node]
+priority = 30
+properties = [
+ { name = "media.class", value = "Audio/Sink" },
+ { name = "api.alsa.card.id", value = "DRA7xx-EVM" },
+]
+
+[endpoint]
+type = "pw-audio-softdsp-endpoint"
+direction = "sink"
+streams = "playback.streams"
+priority = 30
diff --git a/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/30-dra7xx-audio-source.endpoint b/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/30-dra7xx-audio-source.endpoint
new file mode 100644
index 000000000..72ef46770
--- /dev/null
+++ b/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/30-dra7xx-audio-source.endpoint
@@ -0,0 +1,12 @@
+[match-node]
+priority = 30
+properties = [
+ { name = "media.class", value = "Audio/Source" },
+ { name = "api.alsa.card.id", value = "DRA7xx-EVM" },
+]
+
+[endpoint]
+type = "pw-audio-softdsp-endpoint"
+direction = "source"
+streams = "capture.streams"
+priority = 30
diff --git a/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/30-rcarsound-audio-sink.endpoint b/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/30-rcarsound-audio-sink.endpoint
new file mode 100644
index 000000000..53f9d0df7
--- /dev/null
+++ b/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/30-rcarsound-audio-sink.endpoint
@@ -0,0 +1,12 @@
+[match-node]
+priority = 30
+properties = [
+ { name = "media.class", value = "Audio/Sink" },
+ { name = "api.alsa.card.id", value = "rcarsound" },
+]
+
+[endpoint]
+type = "pw-audio-softdsp-endpoint"
+direction = "sink"
+streams = "playback.streams"
+priority = 30
diff --git a/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/30-rcarsound-audio-source.endpoint b/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/30-rcarsound-audio-source.endpoint
new file mode 100644
index 000000000..d72d7e31c
--- /dev/null
+++ b/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/30-rcarsound-audio-source.endpoint
@@ -0,0 +1,12 @@
+[match-node]
+priority = 30
+properties = [
+ { name = "media.class", value = "Audio/Source" },
+ { name = "api.alsa.card.id", value = "rcarsound" },
+]
+
+[endpoint]
+type = "pw-audio-softdsp-endpoint"
+direction = "source"
+streams = "capture.streams"
+priority = 30
diff --git a/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/30-rpi3-audio-sink.endpoint b/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/30-rpi3-audio-sink.endpoint
new file mode 100644
index 000000000..74e4d62e6
--- /dev/null
+++ b/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/30-rpi3-audio-sink.endpoint
@@ -0,0 +1,13 @@
+[match-node]
+priority = 30
+properties = [
+ { name = "media.class", value = "Audio/Sink" },
+ { name = "api.alsa.card.name", value = "bcm2835 ALSA" },
+ { name = "api.alsa.pcm.name", value = "bcm2835 ALSA" },
+]
+
+[endpoint]
+type = "pw-audio-softdsp-endpoint"
+direction = "sink"
+streams = "playback.streams"
+priority = 30
diff --git a/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/40-fiberdyne-amp.endpoint b/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/40-fiberdyne-amp.endpoint
new file mode 100644
index 000000000..807ad4688
--- /dev/null
+++ b/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/40-fiberdyne-amp.endpoint
@@ -0,0 +1,12 @@
+[match-node]
+priority = 40
+properties = [
+ { name = "media.class", value = "Audio/Sink" },
+ { name = "api.alsa.card.id", value = "ep016ch" },
+]
+
+[endpoint]
+type = "pw-audio-softdsp-endpoint"
+direction = "sink"
+streams = "playback.streams"
+priority = 40
diff --git a/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/40-microchip-mic.endpoint b/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/40-microchip-mic.endpoint
new file mode 100644
index 000000000..bbfcd43a5
--- /dev/null
+++ b/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/40-microchip-mic.endpoint
@@ -0,0 +1,12 @@
+[match-node]
+priority = 40
+properties = [
+ { name = "media.class", value = "Audio/Source" },
+ { name = "api.alsa.card.id", value = "ep811ch" },
+]
+
+[endpoint]
+type = "pw-audio-softdsp-endpoint"
+direction = "source"
+streams = "capture.streams"
+priority = 40
diff --git a/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/70-usb-audio-sink.endpoint b/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/70-usb-audio-sink.endpoint
new file mode 100644
index 000000000..62e279090
--- /dev/null
+++ b/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/70-usb-audio-sink.endpoint
@@ -0,0 +1,12 @@
+[match-node]
+priority = 70
+properties = [
+ { name = "media.class", value = "Audio/Sink" },
+ { name = "api.alsa.card.driver", value = "USB-Audio" },
+]
+
+[endpoint]
+type = "pw-audio-softdsp-endpoint"
+direction = "sink"
+streams = "playback.streams"
+priority = 70
diff --git a/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/70-usb-audio-source.endpoint b/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/70-usb-audio-source.endpoint
new file mode 100644
index 000000000..505ae8d81
--- /dev/null
+++ b/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/70-usb-audio-source.endpoint
@@ -0,0 +1,12 @@
+[match-node]
+priority = 70
+properties = [
+ { name = "media.class", value = "Audio/Source" },
+ { name = "api.alsa.card.driver", value = "USB-Audio" },
+]
+
+[endpoint]
+type = "pw-audio-softdsp-endpoint"
+direction = "source"
+streams = "capture.streams"
+priority = 70
diff --git a/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/bluealsa-input-audio.endpoint-link b/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/bluealsa-input-audio.endpoint-link
new file mode 100644
index 000000000..b5753a102
--- /dev/null
+++ b/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/bluealsa-input-audio.endpoint-link
@@ -0,0 +1,11 @@
+[match-endpoint]
+priority = 75
+direction = "sink"
+name = "bluealsa*"
+media_class = "Stream/Input/Audio"
+properties = [
+ { name = "bluealsa.profile", value = "sco" },
+]
+
+[endpoint-link]
+keep = true
diff --git a/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/bluealsa-output-audio.endpoint-link b/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/bluealsa-output-audio.endpoint-link
new file mode 100644
index 000000000..d1b3cec07
--- /dev/null
+++ b/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/bluealsa-output-audio.endpoint-link
@@ -0,0 +1,11 @@
+[match-endpoint]
+priority = 75
+direction = "source"
+name = "bluealsa*"
+media_class = "Stream/Output/Audio"
+properties = [
+ { name = "bluealsa.profile", value = "sco" },
+]
+
+[endpoint-link]
+keep = true
diff --git a/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/capture.streams b/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/capture.streams
new file mode 100644
index 000000000..e7ce36f6a
--- /dev/null
+++ b/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/capture.streams
@@ -0,0 +1,3 @@
+[[streams]]
+name = "Default"
+priority = 25
diff --git a/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/playback.streams b/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/playback.streams
new file mode 100644
index 000000000..c645416ad
--- /dev/null
+++ b/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/playback.streams
@@ -0,0 +1,31 @@
+[[streams]]
+name = "Multimedia"
+priority = 25
+
+[[streams]]
+name = "Speech-Low"
+priority = 30
+
+[[streams]]
+name = "Custom-Low"
+priority = 35
+
+[[streams]]
+name = "Navigation"
+priority = 50
+
+[[streams]]
+name = "Speech-High"
+priority = 60
+
+[[streams]]
+name = "Custom-High"
+priority = 65
+
+[[streams]]
+name = "Communication"
+priority = 75
+
+[[streams]]
+name = "Emergency"
+priority = 99
diff --git a/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/wireplumber.conf b/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/wireplumber.conf
new file mode 100644
index 000000000..e0975a81f
--- /dev/null
+++ b/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl/wireplumber.conf
@@ -0,0 +1,30 @@
+# Register well-known SPA factories
+# These do not need to exist on the system to be registered
+add-spa-lib audio.convert* audioconvert/libspa-audioconvert
+add-spa-lib api.alsa.* alsa/libspa-alsa
+add-spa-lib api.v4l2.* v4l2/libspa-v4l2
+add-spa-lib api.bluez5.* bluez5/libspa-bluez5
+
+# the client-device pipewire module is needed for libwireplumber-module-monitor
+load-pipewire-module libpipewire-module-client-device
+
+# Session object implementation
+# This keeps track of the default input & output device endpoint
+load-module C libwireplumber-module-session
+
+# 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
+
+load-module C libwireplumber-module-monitor {
+ "factory": <"api.alsa.enum.udev">,
+ "flags": <["use-adapter", "activate-devices"]>
+}
+
+# Implements endpoint creation based on TOML configuration files
+load-module C libwireplumber-module-config-endpoint
+
+# Implements linking clients to devices based on TOML configuration files
+load-module C libwireplumber-module-config-policy
diff --git a/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl_git.bb b/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl_git.bb
new file mode 100644
index 000000000..7df90907a
--- /dev/null
+++ b/meta-pipewire/recipes-multimedia/wireplumber/wireplumber-board-config-agl_git.bb
@@ -0,0 +1,57 @@
+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 \
+ file://00-audio-sink.endpoint \
+ file://00-audio-source.endpoint \
+ file://00-default-input-audio.endpoint-link \
+ file://00-default-output-audio.endpoint-link \
+ file://00-stream-input-audio.endpoint \
+ file://00-stream-output-audio.endpoint \
+ file://01-hw00-audio-sink.endpoint \
+ file://01-hw00-audio-source.endpoint \
+ file://30-ak4613-audio-sink.endpoint \
+ file://30-ak4613-audio-source.endpoint \
+ file://30-rcarsound-audio-sink.endpoint \
+ file://30-rcarsound-audio-source.endpoint \
+ file://30-dra7xx-audio-sink.endpoint \
+ file://30-dra7xx-audio-source.endpoint \
+ file://30-rpi3-audio-sink.endpoint \
+ file://40-fiberdyne-amp.endpoint \
+ file://40-microchip-mic.endpoint \
+ file://70-usb-audio-sink.endpoint \
+ file://70-usb-audio-source.endpoint \
+ file://bluealsa-input-audio.endpoint-link \
+ file://bluealsa-output-audio.endpoint-link \
+ file://capture.streams \
+ file://playback.streams \
+"
+
+PACKAGE_ARCH = "${MACHINE_ARCH}"
+
+do_configure[noexec] = "1"
+do_compile[noexec] = "1"
+
+do_install_append() {
+ install -d ${D}/${sysconfdir}/wireplumber/
+ install -m 644 ${WORKDIR}/wireplumber.conf ${D}/${sysconfdir}/wireplumber/wireplumber.conf
+ install -m 644 ${WORKDIR}/*.endpoint ${D}/${sysconfdir}/wireplumber/
+ install -m 644 ${WORKDIR}/*.endpoint-link ${D}/${sysconfdir}/wireplumber/
+ install -m 644 ${WORKDIR}/*.streams ${D}/${sysconfdir}/wireplumber/
+}
+
+FILES_${PN} += "\
+ ${sysconfdir}/wireplumber/* \
+"
+CONFFILES_${PN} += "\
+ ${sysconfdir}/wireplumber/* \
+"
+
+RPROVIDES_${PN} += "virtual/wireplumber-config"
diff --git a/meta-pipewire/recipes-multimedia/wireplumber/wireplumber/0001-Build-cpptoml-without-a-cmake-subproject.patch b/meta-pipewire/recipes-multimedia/wireplumber/wireplumber/0001-Build-cpptoml-without-a-cmake-subproject.patch
new file mode 100644
index 000000000..726b35e74
--- /dev/null
+++ b/meta-pipewire/recipes-multimedia/wireplumber/wireplumber/0001-Build-cpptoml-without-a-cmake-subproject.patch
@@ -0,0 +1,28 @@
+From e5efe3d4f0abc28251dac245ce0cf4124e7e2a12 Mon Sep 17 00:00:00 2001
+From: George Kiagiadakis <george.kiagiadakis@collabora.com>
+Date: Thu, 5 Dec 2019 17:59:44 +0200
+Subject: [PATCH] Build cpptoml without a cmake subproject
+
+Upstream-Status: Inappropriate
+---
+ meson.build | 4 +---
+ 1 file changed, 1 insertion(+), 3 deletions(-)
+
+diff --git a/meson.build b/meson.build
+index 5a75d96..0b21377 100644
+--- a/meson.build
++++ b/meson.build
+@@ -24,9 +24,7 @@ else
+ wireplumber_config_dir = join_paths(get_option('prefix'), get_option('sysconfdir'), 'wireplumber')
+ endif
+
+-cmake = import('cmake')
+-cpptoml = cmake.subproject('cpptoml')
+-cpptoml_dep = cpptoml.dependency('cpptoml')
++cpptoml_dep = declare_dependency(include_directories: include_directories('subprojects/cpptoml'))
+
+ gobject_dep = dependency('gobject-2.0')
+ gmodule_dep = dependency('gmodule-2.0')
+--
+2.24.0
+
diff --git a/meta-pipewire/recipes-multimedia/wireplumber/wireplumber_git.bb b/meta-pipewire/recipes-multimedia/wireplumber/wireplumber_git.bb
new file mode 100644
index 000000000..0e810b375
--- /dev/null
+++ b/meta-pipewire/recipes-multimedia/wireplumber/wireplumber_git.bb
@@ -0,0 +1,46 @@
+SUMMARY = "Session / Policy Manager for PipeWire"
+HOMEPAGE = "https://gitlab.freedesktop.org/pipewire/wireplumber"
+BUGTRACKER = "https://gitlab.freedesktop.org/pipewire/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/pipewire/wireplumber.git;protocol=https;branch=master \
+ https://raw.githubusercontent.com/skystrife/cpptoml/fededad7169e538ca47e11a9ee9251bc361a9a65/include/cpptoml.h \
+ file://0001-Build-cpptoml-without-a-cmake-subproject.patch \
+"
+SRCREV = "0e98e4150b73d0bed9b72bf8d3eba49671962991"
+SRC_URI[sha256sum] = "3e4e1d315fa1229921c7a4297ead08775b5bb1220c18a7eac62db9ca7e79df0d"
+
+PV = "0.1.90+git${SRCPV}"
+S = "${WORKDIR}/git"
+
+do_configure_prepend() {
+ mkdir -p ${WORKDIR}/git/subprojects/cpptoml/include
+ cp -f ${WORKDIR}/cpptoml.h ${WORKDIR}/git/subprojects/cpptoml/include/
+}
+
+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-pipewire/recipes-security/cynagora/cynagora_%.bbappend b/meta-pipewire/recipes-security/cynagora/cynagora_%.bbappend
new file mode 100644
index 000000000..9395c90c7
--- /dev/null
+++ b/meta-pipewire/recipes-security/cynagora/cynagora_%.bbappend
@@ -0,0 +1,5 @@
+
+do_install_append() {
+ echo "System::Pipewire * * http://tizen.org/privilege/internal/dbus yes forever" >> ${D}${sysconfdir}/security/cynagora.initial
+}
+
diff --git a/meta-pipewire/recipes-security/security-manager/security-manager_%.bbappend b/meta-pipewire/recipes-security/security-manager/security-manager_%.bbappend
new file mode 100644
index 000000000..594494463
--- /dev/null
+++ b/meta-pipewire/recipes-security/security-manager/security-manager_%.bbappend
@@ -0,0 +1,4 @@
+
+do_install_append() {
+ echo "~APP~ System::Pipewire rw" >> ${D}${datadir}/security-manager/policy/app-rules-template.smack
+}