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