From 4ce8411bb9c47a59d67ffe431f43cc4bc408dce1 Mon Sep 17 00:00:00 2001 From: Jan-Simon Möller Date: Thu, 25 Jun 2020 19:47:24 +0200 Subject: Move meta-agl-profile-graphical-html5 and meta-pipewire into the main layer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reasoning: - the profile-graphical-html5 belongs in the main layer - meta-pipewire is enabled by default For reference - the original location is: https://gerrit.automotivelinux.org/gerrit/gitweb?p=AGL%2Fmeta-agl-devel.git;a=commit;h=8479cd0c37e5e5e207a16f21c864610103e4f20f Bug-AGL: SPEC-3453 Signed-off-by: Jan-Simon Möller Change-Id: Ib475331bb6f84437020728fbb3f17b4acaab0106 Reviewed-on: https://gerrit.automotivelinux.org/gerrit/c/AGL/meta-agl/+/24875 Reviewed-by: Scott Murray --- .../pipewire/pipewire-conf-agl/client.env | 10 + .../pipewire/pipewire-conf-agl/pipewire.conf.in | 17 + .../pipewire/pipewire-conf-agl/server.env | 12 + .../pipewire/pipewire-conf-agl_git.bb | 43 + .../recipes-multimedia/pipewire/pipewire.inc | 120 ++ ...t-version-check-to-require-meson-0.47-not.patch | 30 + .../0002-arm-build-with-mno-unaligned-access.patch | 30 + ...nt-new-pwaudio-src-sink-elements-based-on.patch | 1280 ++++++++++++++++++++ ...t-always-assume-that-output-ports-are-NOT.patch | 35 + ...005-module-access-add-same-sec-label-mode.patch | 94 ++ ...ll-reuse_buffers-when-resetting-the-state.patch | 30 + .../pipewire/pipewire/pipewire@.service | 24 + .../pipewire/pipewire/pipewire@.socket | 19 + .../pipewire/pipewire/smack-pipewire | 8 + .../recipes-multimedia/pipewire/pipewire_git.bb | 17 + .../pipewire/pipewire_git.bbappend | 30 + 16 files changed, 1799 insertions(+) create mode 100644 meta-pipewire/recipes-multimedia/pipewire/pipewire-conf-agl/client.env create mode 100644 meta-pipewire/recipes-multimedia/pipewire/pipewire-conf-agl/pipewire.conf.in create mode 100644 meta-pipewire/recipes-multimedia/pipewire/pipewire-conf-agl/server.env create mode 100644 meta-pipewire/recipes-multimedia/pipewire/pipewire-conf-agl_git.bb create mode 100644 meta-pipewire/recipes-multimedia/pipewire/pipewire.inc create mode 100644 meta-pipewire/recipes-multimedia/pipewire/pipewire/0001-meson-revert-version-check-to-require-meson-0.47-not.patch create mode 100644 meta-pipewire/recipes-multimedia/pipewire/pipewire/0002-arm-build-with-mno-unaligned-access.patch create mode 100644 meta-pipewire/recipes-multimedia/pipewire/pipewire/0003-gst-Implement-new-pwaudio-src-sink-elements-based-on.patch create mode 100644 meta-pipewire/recipes-multimedia/pipewire/pipewire/0004-audioconvert-always-assume-that-output-ports-are-NOT.patch create mode 100644 meta-pipewire/recipes-multimedia/pipewire/pipewire/0005-module-access-add-same-sec-label-mode.patch create mode 100644 meta-pipewire/recipes-multimedia/pipewire/pipewire/0006-alsa-pcm-call-reuse_buffers-when-resetting-the-state.patch create mode 100644 meta-pipewire/recipes-multimedia/pipewire/pipewire/pipewire@.service create mode 100644 meta-pipewire/recipes-multimedia/pipewire/pipewire/pipewire@.socket create mode 100644 meta-pipewire/recipes-multimedia/pipewire/pipewire/smack-pipewire create mode 100644 meta-pipewire/recipes-multimedia/pipewire/pipewire_git.bb create mode 100644 meta-pipewire/recipes-multimedia/pipewire/pipewire_git.bbappend (limited to 'meta-pipewire/recipes-multimedia/pipewire') diff --git a/meta-pipewire/recipes-multimedia/pipewire/pipewire-conf-agl/client.env b/meta-pipewire/recipes-multimedia/pipewire/pipewire-conf-agl/client.env new file mode 100644 index 000000000..9b44cee01 --- /dev/null +++ b/meta-pipewire/recipes-multimedia/pipewire/pipewire-conf-agl/client.env @@ -0,0 +1,10 @@ +# This file contains environment variables that will apply +# to pipewire clients started by the application framework + +# libpipewire by default tries to obtain real-time scheduling +# for the streaming thread. This is only useful on the desktop, disable here. +DISABLE_RTKIT=1 + +# Uncomment to enable libpipewire debug for clients +# 1=error, 2=warning, 3=info, 4=debug, 5=trace +#PIPEWIRE_DEBUG=4 diff --git a/meta-pipewire/recipes-multimedia/pipewire/pipewire-conf-agl/pipewire.conf.in b/meta-pipewire/recipes-multimedia/pipewire/pipewire-conf-agl/pipewire.conf.in new file mode 100644 index 000000000..6c055bcff --- /dev/null +++ b/meta-pipewire/recipes-multimedia/pipewire/pipewire-conf-agl/pipewire.conf.in @@ -0,0 +1,17 @@ +# daemon config file for PipeWire version "0.2.9" +# distributed by Automotive Grade Linux + +add-spa-lib audio.convert* audioconvert/libspa-audioconvert +add-spa-lib api.alsa.* alsa/libspa-alsa +add-spa-lib api.v4l2.* v4l2/libspa-v4l2 +add-spa-lib api.bluez5.* bluez5/libspa-bluez5 + +load-module libpipewire-module-protocol-native +load-module libpipewire-module-spa-node-factory +load-module libpipewire-module-client-node +load-module libpipewire-module-client-device +load-module libpipewire-module-access same-sec-label-mode=1 +load-module libpipewire-module-adapter +load-module libpipewire-module-link-factory +load-module libpipewire-module-session-manager +exec /usr/bin/wireplumber diff --git a/meta-pipewire/recipes-multimedia/pipewire/pipewire-conf-agl/server.env b/meta-pipewire/recipes-multimedia/pipewire/pipewire-conf-agl/server.env new file mode 100644 index 000000000..c74b941d6 --- /dev/null +++ b/meta-pipewire/recipes-multimedia/pipewire/pipewire-conf-agl/server.env @@ -0,0 +1,12 @@ +# This file contains environment variables that will apply +# to the pipewire daemon as well as its session manager + +# Disable rtkit for wireplumber, which is also a client +DISABLE_RTKIT=1 + +# Uncomment to enable wireplumber debug +#G_MESSAGES_DEBUG=all + +# Uncomment to enable pipewire debug +# 1=error, 2=warning, 3=info, 4=debug, 5=trace +#PIPEWIRE_DEBUG=4 diff --git a/meta-pipewire/recipes-multimedia/pipewire/pipewire-conf-agl_git.bb b/meta-pipewire/recipes-multimedia/pipewire/pipewire-conf-agl_git.bb new file mode 100644 index 000000000..a28c6534e --- /dev/null +++ b/meta-pipewire/recipes-multimedia/pipewire/pipewire-conf-agl_git.bb @@ -0,0 +1,43 @@ +SUMMARY = "AGL configuration file for pipewire" +HOMEPAGE = "https://pipewire.org" +BUGTRACKER = "https://jira.automotivelinux.org" +AUTHOR = "George Kiagiadakis " +SECTION = "multimedia" + +LICENSE = "MIT" +LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302" + +SRC_URI = " \ + file://pipewire.conf.in \ + file://client.env \ + file://server.env \ + " + +do_configure[noexec] = "1" +do_compile[noexec] = "1" + +do_install_append() { + # enable optional features in the config + BLUEZ5=${@bb.utils.contains('DISTRO_FEATURES', 'bluez5', '', '#', d)} + sed -e "s/#IF_BLUEZ5 /${BLUEZ5}/" ${WORKDIR}/pipewire.conf.in > ${WORKDIR}/pipewire.conf + + # install our custom config + install -d ${D}/${sysconfdir}/pipewire/ + install -m 0644 ${WORKDIR}/pipewire.conf ${D}${sysconfdir}/pipewire/pipewire.conf + + # install environment variable files + install -d ${D}/${sysconfdir}/afm/unit.env.d/ + install -m 0644 ${WORKDIR}/client.env ${D}/${sysconfdir}/afm/unit.env.d/pipewire + install -m 0644 ${WORKDIR}/server.env ${D}${sysconfdir}/pipewire/environment +} + +FILES_${PN} = "\ + ${sysconfdir}/pipewire/* \ + ${sysconfdir}/afm/unit.env.d/* \ +" +CONFFILES_${PN} += "\ + ${sysconfdir}/pipewire/* \ + ${sysconfdir}/afm/unit.env.d/* \ +" + +RPROVIDES_${PN} += "virtual/pipewire-config" diff --git a/meta-pipewire/recipes-multimedia/pipewire/pipewire.inc b/meta-pipewire/recipes-multimedia/pipewire/pipewire.inc new file mode 100644 index 000000000..b3081ca43 --- /dev/null +++ b/meta-pipewire/recipes-multimedia/pipewire/pipewire.inc @@ -0,0 +1,120 @@ +SUMMARY = "Multimedia processing server for Linux" +HOMEPAGE = "https://pipewire.org" +BUGTRACKER = "https://github.com/PipeWire/pipewire/issues" +AUTHOR = "Wim Taymans " +SECTION = "multimedia" + +LICENSE = "MIT & LGPL-2.1" +LIC_FILES_CHKSUM = "\ + file://COPYING;beginline=3;md5=b3adc775ca6ee80056383a5ae814cc75 \ + file://pipewire-alsa/LICENSE;md5=fc178bcd425090939a8b634d1d6a9594 \ + file://pipewire-jack/LICENSE;md5=fc178bcd425090939a8b634d1d6a9594 \ + file://pipewire-pulseaudio/LICENSE;md5=fc178bcd425090939a8b634d1d6a9594 \ +" + +inherit meson pkgconfig systemd manpages + +DEPENDS = "dbus" + +PACKAGECONFIG ??= "\ + ${@bb.utils.filter('DISTRO_FEATURES', 'systemd', d)} \ + ${@bb.utils.filter('DISTRO_FEATURES', 'bluez5', d)} \ + alsa audioconvert audiomixer \ + pipewire-alsa \ + gstreamer \ +" + +GST_VER = "1.0" + +# systemd integration +PACKAGECONFIG[systemd] = "-Dsystemd=true,-Dsystemd=false,systemd" + +# SPA plugins +PACKAGECONFIG[alsa] = "-Dalsa=true,-Dalsa=false,udev alsa-lib" +PACKAGECONFIG[audioconvert] = "-Daudioconvert=true,-Daudioconvert=false," +PACKAGECONFIG[audiomixer] = "-Daudiomixer=true,-Daudiomixer=false," +PACKAGECONFIG[audiotestsrc] = "-Daudiotestsrc=true,-Daudiotestsrc=false, " +PACKAGECONFIG[bluez5] = "-Dbluez5=true,-Dbluez5=false,bluez5 sbc" +PACKAGECONFIG[jack] = "-Djack=true,-Djack=false,jack" +PACKAGECONFIG[v4l2] = "-Dv4l2=true,-Dv4l2=false,udev v4l-utils" +PACKAGECONFIG[videotestsrc] = "-Dvideotestsrc=true,-Dvideotestsrc=false, " +PACKAGECONFIG[vulkan] = "-Dvulkan=true,-Dvulkan=false,vulkan" + +# alsa plugin to redirect audio to pipewire +PACKAGECONFIG[pipewire-alsa] = "-Dpipewire-alsa=true,-Dpipewire-alsa=false,alsa-lib" +# pulseaudio drop-in replacement library +PACKAGECONFIG[pipewire-pulseaudio] = "-Dpipewire-pulseaudio=true,-Dpipewire-pulseaudio=false,pulseaudio glib-2.0" +# jack drop-in replacement library +PACKAGECONFIG[pipewire-jack] = "-Dpipewire-jack=true,-Dpipewire-jack=false,jack" + +# GStreamer plugins +PACKAGECONFIG[gstreamer] = "-Dgstreamer=true,-Dgstreamer=false,glib-2.0 gstreamer${GST_VER} gstreamer${GST_VER}-plugins-base" + +# man pages +PACKAGECONFIG[manpages] = "-Dman=true,-Dman=false,libxml-parser-perl-native" + +do_install_append() { + # only install the alsa config file if the alsa-lib plugin has been built + # this avoids creating the pipewire-alsa package when the pipewire-alsa + # feature is not enabled + if [ -d ${D}${libdir}/alsa-lib ] + then + mkdir -p ${D}${datadir}/alsa/alsa.conf.d + install -m 0644 ${S}/pipewire-alsa/conf/50-pipewire.conf ${D}${datadir}/alsa/alsa.conf.d/50-pipewire.conf + fi +} + +PACKAGES =+ "\ + ${PN}-spa-plugins \ + ${PN}-alsa \ + ${PN}-pulseaudio \ + ${PN}-jack \ + ${PN}-config \ + gstreamer${GST_VER}-${PN} \ + lib${PN} \ + lib${PN}-modules \ +" + +FILES_${PN} = "\ + ${bindir}/pipewire* \ + ${systemd_user_unitdir}/* \ +" + +FILES_lib${PN} = "\ + ${libdir}/libpipewire-*.so.* \ +" + +FILES_lib${PN}-modules = "\ + ${libdir}/pipewire-*/* \ +" + +FILES_${PN}-spa-plugins = "\ + ${bindir}/spa-* \ + ${libdir}/spa/* \ +" + +FILES_${PN}-alsa = "\ + ${libdir}/alsa-lib/* \ + ${datadir}/alsa/alsa.conf.d/50-pipewire.conf \ +" + +FILES_${PN}-pulseaudio = "\ + ${libdir}/libpulse*.so.* \ +" + +FILES_gstreamer${GST_VER}-${PN} = "\ + ${libdir}/gstreamer-${GST_VER}/* \ +" + +RDEPENDS_lib${PN} += "lib${PN}-modules ${PN}-spa-plugins" + +# The default pipewire config. +# Replace in your own package using +# "virtual/pipewire-config" +FILES_${PN}-config = "\ + ${sysconfdir}/pipewire/pipewire.conf \ +" +CONFFILES_${PN}-config += "\ + ${sysconfdir}/pipewire/pipewire.conf \ +" +RPROVIDES_${PN}-config += "virtual/pipewire-config" diff --git a/meta-pipewire/recipes-multimedia/pipewire/pipewire/0001-meson-revert-version-check-to-require-meson-0.47-not.patch b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0001-meson-revert-version-check-to-require-meson-0.47-not.patch new file mode 100644 index 000000000..4e7bb0d4f --- /dev/null +++ b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0001-meson-revert-version-check-to-require-meson-0.47-not.patch @@ -0,0 +1,30 @@ +From 5a249321aa84cd74e3d83bcd555c85fba3cd682d Mon Sep 17 00:00:00 2001 +From: George Kiagiadakis +Date: Sun, 22 Sep 2019 17:59:19 +0300 +Subject: [PATCH] meson: revert version check to require meson 0.47, not 0.50 + +meson 0.50 is not really needed, but there are some strange warnings +if you require an older version; in any case, AGL does not have 0.50 +yet, so let's not fail compilation because of that... + +Upstream-Status: Inappropriate [workaround] +--- + meson.build | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/meson.build b/meson.build +index 2734b0d2..c9da6b4d 100644 +--- a/meson.build ++++ b/meson.build +@@ -1,7 +1,7 @@ + project('pipewire', ['c' ], + version : '0.2.9', + license : 'MIT', +- meson_version : '>= 0.50.0', ++ meson_version : '>= 0.47.0', + default_options : [ 'warning_level=1', + 'c_std=gnu99', + 'buildtype=debugoptimized' ]) +-- +2.24.0 + diff --git a/meta-pipewire/recipes-multimedia/pipewire/pipewire/0002-arm-build-with-mno-unaligned-access.patch b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0002-arm-build-with-mno-unaligned-access.patch new file mode 100644 index 000000000..2077af63d --- /dev/null +++ b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0002-arm-build-with-mno-unaligned-access.patch @@ -0,0 +1,30 @@ +From e4b81946baf2d8c08de87088c01a1d87ae4f03d9 Mon Sep 17 00:00:00 2001 +From: George Kiagiadakis +Date: Mon, 24 Jun 2019 12:19:20 +0300 +Subject: [PATCH] arm: build with -mno-unaligned-access + +Upstream-Status: Inappropriate [workaround] +See also https://gitlab.freedesktop.org/pipewire/pipewire/issues/161 +--- + meson.build | 5 +++++ + 1 file changed, 5 insertions(+) + +diff --git a/meson.build b/meson.build +index c9da6b4d..5c121339 100644 +--- a/meson.build ++++ b/meson.build +@@ -52,6 +52,11 @@ if cc.get_id() == 'gcc' + language : 'c') + endif + ++if host_machine.cpu_family() == 'arm' ++ add_global_arguments('-mno-unaligned-access', ++ language: 'c') ++endif ++ + sse_args = '-msse' + sse2_args = '-msse2' + ssse3_args = '-mssse3' +-- +2.24.0 + diff --git a/meta-pipewire/recipes-multimedia/pipewire/pipewire/0003-gst-Implement-new-pwaudio-src-sink-elements-based-on.patch b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0003-gst-Implement-new-pwaudio-src-sink-elements-based-on.patch new file mode 100644 index 000000000..b3eba21f7 --- /dev/null +++ b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0003-gst-Implement-new-pwaudio-src-sink-elements-based-on.patch @@ -0,0 +1,1280 @@ +From 1b1f884a165ed7b2147affbdddf85a641d4cf180 Mon Sep 17 00:00:00 2001 +From: George Kiagiadakis +Date: Tue, 19 Feb 2019 18:23:19 +0200 +Subject: [PATCH] gst: Implement new pwaudio{src,sink} elements, based on + GstAudioBase{Src,Sink} + +These are much more reliable elements to use for audio data. +* GstAudioBaseSink provides a reliable clock implementation based + on the number of samples read/written +* on the pipewire side we make sure to dequeue, fill and enqueue + a single buffer inside the process() function, which avoids + underruns + +Both elements share a common ringbuffer that actually implements +the pipewire integration. + +Upstream-Status: Denied +See https://gitlab.freedesktop.org/pipewire/pipewire/merge_requests/140 +--- + src/gst/gstpipewire.c | 8 +- + src/gst/gstpwaudioringbuffer.c | 565 +++++++++++++++++++++++++++++++++ + src/gst/gstpwaudioringbuffer.h | 83 +++++ + src/gst/gstpwaudiosink.c | 207 ++++++++++++ + src/gst/gstpwaudiosink.h | 48 +++ + src/gst/gstpwaudiosrc.c | 200 ++++++++++++ + src/gst/gstpwaudiosrc.h | 48 +++ + src/gst/meson.build | 6 + + 8 files changed, 1164 insertions(+), 1 deletion(-) + create mode 100644 src/gst/gstpwaudioringbuffer.c + create mode 100644 src/gst/gstpwaudioringbuffer.h + create mode 100644 src/gst/gstpwaudiosink.c + create mode 100644 src/gst/gstpwaudiosink.h + create mode 100644 src/gst/gstpwaudiosrc.c + create mode 100644 src/gst/gstpwaudiosrc.h + +diff --git a/src/gst/gstpipewire.c b/src/gst/gstpipewire.c +index 4040264b..68fd446f 100644 +--- a/src/gst/gstpipewire.c ++++ b/src/gst/gstpipewire.c +@@ -40,6 +40,8 @@ + #include "gstpipewiresrc.h" + #include "gstpipewiresink.h" + #include "gstpipewiredeviceprovider.h" ++#include "gstpwaudiosrc.h" ++#include "gstpwaudiosink.h" + + GST_DEBUG_CATEGORY (pipewire_debug); + +@@ -52,12 +54,16 @@ plugin_init (GstPlugin *plugin) + GST_TYPE_PIPEWIRE_SRC); + gst_element_register (plugin, "pipewiresink", GST_RANK_NONE, + GST_TYPE_PIPEWIRE_SINK); ++ gst_element_register (plugin, "pwaudiosrc", GST_RANK_NONE, ++ GST_TYPE_PW_AUDIO_SRC); ++ gst_element_register (plugin, "pwaudiosink", GST_RANK_NONE, ++ GST_TYPE_PW_AUDIO_SINK); + + if (!gst_device_provider_register (plugin, "pipewiredeviceprovider", + GST_RANK_PRIMARY + 1, GST_TYPE_PIPEWIRE_DEVICE_PROVIDER)) + return FALSE; + +- GST_DEBUG_CATEGORY_INIT (pipewire_debug, "pipewire", 0, "PipeWirie elements"); ++ GST_DEBUG_CATEGORY_INIT (pipewire_debug, "pipewire", 0, "PipeWire elements"); + + return TRUE; + } +diff --git a/src/gst/gstpwaudioringbuffer.c b/src/gst/gstpwaudioringbuffer.c +new file mode 100644 +index 00000000..babf2d83 +--- /dev/null ++++ b/src/gst/gstpwaudioringbuffer.c +@@ -0,0 +1,565 @@ ++/* PipeWire ++ * ++ * Copyright © 2018 Wim Taymans ++ * Copyright © 2019 Collabora Ltd. ++ * @author George Kiagiadakis ++ * ++ * 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 ++#include ++ ++GST_DEBUG_CATEGORY_STATIC (pw_audio_ring_buffer_debug); ++#define GST_CAT_DEFAULT pw_audio_ring_buffer_debug ++ ++#define gst_pw_audio_ring_buffer_parent_class parent_class ++G_DEFINE_TYPE (GstPwAudioRingBuffer, gst_pw_audio_ring_buffer, GST_TYPE_AUDIO_RING_BUFFER); ++ ++enum ++{ ++ PROP_0, ++ PROP_ELEMENT, ++ PROP_DIRECTION, ++ PROP_PROPS ++}; ++ ++static void ++gst_pw_audio_ring_buffer_init (GstPwAudioRingBuffer * self) ++{ ++ self->loop = pw_loop_new (NULL); ++ self->main_loop = pw_thread_loop_new (self->loop, "pw-audioringbuffer-loop"); ++ self->core = pw_core_new (self->loop, NULL, 0); ++} ++ ++static void ++gst_pw_audio_ring_buffer_finalize (GObject * object) ++{ ++ GstPwAudioRingBuffer *self = GST_PW_AUDIO_RING_BUFFER (object); ++ ++ pw_core_destroy (self->core); ++ pw_thread_loop_destroy (self->main_loop); ++ pw_loop_destroy (self->loop); ++} ++ ++static void ++gst_pw_audio_ring_buffer_set_property (GObject * object, guint prop_id, ++ const GValue * value, GParamSpec * pspec) ++{ ++ GstPwAudioRingBuffer *self = GST_PW_AUDIO_RING_BUFFER (object); ++ ++ switch (prop_id) { ++ case PROP_ELEMENT: ++ self->elem = g_value_get_object (value); ++ break; ++ ++ case PROP_DIRECTION: ++ self->direction = g_value_get_int (value); ++ break; ++ ++ case PROP_PROPS: ++ self->props = g_value_get_pointer (value); ++ break; ++ ++ default: ++ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); ++ break; ++ } ++} ++ ++static void ++on_remote_state_changed (void *data, enum pw_remote_state old, ++ enum pw_remote_state state, const char *error) ++{ ++ GstPwAudioRingBuffer *self = GST_PW_AUDIO_RING_BUFFER (data); ++ ++ GST_DEBUG_OBJECT (self->elem, "got remote state %d", state); ++ ++ switch (state) { ++ case PW_REMOTE_STATE_UNCONNECTED: ++ case PW_REMOTE_STATE_CONNECTING: ++ case PW_REMOTE_STATE_CONNECTED: ++ break; ++ case PW_REMOTE_STATE_ERROR: ++ GST_ELEMENT_ERROR (self->elem, RESOURCE, FAILED, ++ ("remote error: %s", error), (NULL)); ++ break; ++ } ++ pw_thread_loop_signal (self->main_loop, FALSE); ++} ++ ++static const struct pw_remote_events remote_events = { ++ PW_VERSION_REMOTE_EVENTS, ++ .state_changed = on_remote_state_changed, ++}; ++ ++static gboolean ++wait_for_remote_state (GstPwAudioRingBuffer *self, ++ enum pw_remote_state target) ++{ ++ while (TRUE) { ++ enum pw_remote_state state = pw_remote_get_state (self->remote, NULL); ++ if (state == target) ++ return TRUE; ++ if (state == PW_REMOTE_STATE_ERROR) ++ return FALSE; ++ pw_thread_loop_wait (self->main_loop); ++ } ++} ++ ++static gboolean ++gst_pw_audio_ring_buffer_open_device (GstAudioRingBuffer *buf) ++{ ++ GstPwAudioRingBuffer *self = GST_PW_AUDIO_RING_BUFFER (buf); ++ ++ GST_DEBUG_OBJECT (self->elem, "open device"); ++ ++ if (pw_thread_loop_start (self->main_loop) < 0) ++ goto mainloop_error; ++ ++ pw_thread_loop_lock (self->main_loop); ++ ++ self->remote = pw_remote_new (self->core, NULL, 0); ++ pw_remote_add_listener (self->remote, &self->remote_listener, &remote_events, ++ self); ++ ++ if (self->props->fd == -1) ++ pw_remote_connect (self->remote); ++ else ++ pw_remote_connect_fd (self->remote, self->props->fd); ++ ++ GST_DEBUG_OBJECT (self->elem, "waiting for connection"); ++ ++ if (!wait_for_remote_state (self, PW_REMOTE_STATE_CONNECTED)) ++ goto connect_error; ++ ++ pw_thread_loop_unlock (self->main_loop); ++ ++ return TRUE; ++ ++ /* ERRORS */ ++mainloop_error: ++ { ++ GST_ELEMENT_ERROR (self->elem, RESOURCE, FAILED, ++ ("Failed to start mainloop"), (NULL)); ++ return FALSE; ++ } ++connect_error: ++ { ++ pw_thread_loop_unlock (self->main_loop); ++ return FALSE; ++ } ++} ++ ++static gboolean ++gst_pw_audio_ring_buffer_close_device (GstAudioRingBuffer *buf) ++{ ++ GstPwAudioRingBuffer *self = GST_PW_AUDIO_RING_BUFFER (buf); ++ ++ GST_DEBUG_OBJECT (self->elem, "closing device"); ++ ++ pw_thread_loop_lock (self->main_loop); ++ if (self->remote) { ++ pw_remote_disconnect (self->remote); ++ wait_for_remote_state (self, PW_REMOTE_STATE_UNCONNECTED); ++ } ++ pw_thread_loop_unlock (self->main_loop); ++ ++ pw_thread_loop_stop (self->main_loop); ++ ++ if (self->remote) { ++ pw_remote_destroy (self->remote); ++ self->remote = NULL; ++ } ++ return TRUE; ++} ++ ++static void ++on_stream_state_changed (void *data, enum pw_stream_state old, ++ enum pw_stream_state state, const char *error) ++{ ++ GstPwAudioRingBuffer *self = GST_PW_AUDIO_RING_BUFFER (data); ++ GstMessage *msg; ++ ++ GST_DEBUG_OBJECT (self->elem, "got stream state: %s", ++ pw_stream_state_as_string (state)); ++ ++ switch (state) { ++ case PW_STREAM_STATE_ERROR: ++ GST_ELEMENT_ERROR (self->elem, RESOURCE, FAILED, ++ ("stream error: %s", error), (NULL)); ++ break; ++ case PW_STREAM_STATE_UNCONNECTED: ++ GST_ELEMENT_ERROR (self->elem, RESOURCE, FAILED, ++ ("stream disconnected unexpectedly"), (NULL)); ++ break; ++ case PW_STREAM_STATE_CONNECTING: ++ break; ++ case PW_STREAM_STATE_PAUSED: ++ if (old == PW_STREAM_STATE_STREAMING) { ++ if (GST_STATE (self->elem) != GST_STATE_PAUSED && ++ GST_STATE_TARGET (self->elem) != GST_STATE_PAUSED) { ++ GST_DEBUG_OBJECT (self->elem, "requesting GST_STATE_PAUSED"); ++ msg = gst_message_new_request_state (GST_OBJECT (self->elem), ++ GST_STATE_PAUSED); ++ gst_element_post_message (self->elem, msg); ++ } ++ } ++ break; ++ case PW_STREAM_STATE_STREAMING: ++ if (GST_STATE (self->elem) != GST_STATE_PLAYING && ++ GST_STATE_TARGET (self->elem) != GST_STATE_PLAYING) { ++ GST_DEBUG_OBJECT (self->elem, "requesting GST_STATE_PLAYING"); ++ msg = gst_message_new_request_state (GST_OBJECT (self->elem), ++ GST_STATE_PLAYING); ++ gst_element_post_message (self->elem, msg); ++ } ++ break; ++ } ++ pw_thread_loop_signal (self->main_loop, FALSE); ++} ++ ++static gboolean ++wait_for_stream_state (GstPwAudioRingBuffer *self, ++ enum pw_stream_state target) ++{ ++ while (TRUE) { ++ enum pw_stream_state state = pw_stream_get_state (self->stream, NULL); ++ if (state >= target) ++ return TRUE; ++ if (state == PW_STREAM_STATE_ERROR || state == PW_STREAM_STATE_UNCONNECTED) ++ return FALSE; ++ pw_thread_loop_wait (self->main_loop); ++ } ++} ++ ++static void ++on_stream_param_changed (void *data, uint32_t id, const struct spa_pod *format) ++{ ++ GstPwAudioRingBuffer *self = GST_PW_AUDIO_RING_BUFFER (data); ++ const struct spa_pod *params[1]; ++ struct spa_pod_builder b = { NULL }; ++ uint8_t buffer[512]; ++ ++ if (format == NULL || id != SPA_PARAM_Format) ++ return; ++ ++ spa_pod_builder_init (&b, buffer, sizeof (buffer)); ++ params[0] = spa_pod_builder_add_object (&b, ++ SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, ++ SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(16, 1, INT32_MAX), ++ SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), ++ SPA_PARAM_BUFFERS_size, SPA_POD_Int(self->segsize), ++ SPA_PARAM_BUFFERS_stride, SPA_POD_Int(self->bpf), ++ SPA_PARAM_BUFFERS_align, SPA_POD_Int(16)); ++ ++ GST_DEBUG_OBJECT (self->elem, "doing finish format, buffer size:%d", self->segsize); ++ pw_stream_update_params (self->stream, params, 1); ++} ++ ++static void ++on_stream_process (void *data) ++{ ++ GstPwAudioRingBuffer *self = GST_PW_AUDIO_RING_BUFFER (data); ++ GstAudioRingBuffer *buf = GST_AUDIO_RING_BUFFER (data); ++ struct pw_buffer *b; ++ struct spa_data *d; ++ gint size; /*< size to read/write from/to the spa buffer */ ++ gint offset; /*< offset to read/write from/to in the spa buffer */ ++ gint segment; /*< the current segment number in the ringbuffer */ ++ guint8 *ringptr; /*< pointer to the beginning of the current segment */ ++ gint segsize; /*< the size of one segment in the ringbuffer */ ++ gint copy_size; /*< the bytes to copy in one memcpy() invocation */ ++ gint remain; /*< remainder of bytes available in the spa buffer */ ++ ++ if (g_atomic_int_get (&buf->state) != GST_AUDIO_RING_BUFFER_STATE_STARTED) { ++ GST_LOG_OBJECT (self->elem, "ring buffer is not started"); ++ return; ++ } ++ ++ b = pw_stream_dequeue_buffer (self->stream); ++ if (!b) { ++ GST_INFO_OBJECT (self->elem, "no pipewire buffer available"); ++ return; ++ } ++ ++ d = &b->buffer->datas[0]; ++ ++ if (self->direction == PW_DIRECTION_OUTPUT) { ++ /* in output mode, always fill the entire spa buffer */ ++ offset = d->chunk->offset = 0; ++ size = d->chunk->size = d->maxsize; ++ b->size = size / self->bpf; ++ } else { ++ offset = SPA_MIN (d->chunk->offset, d->maxsize); ++ size = SPA_MIN (d->chunk->size, d->maxsize - offset); ++ } ++ ++ do { ++ gst_audio_ring_buffer_prepare_read (buf, &segment, &ringptr, &segsize); ++ ++ /* in INPUT (src) mode, it is possible that the skew algorithm ++ * advances the ringbuffer behind our back */ ++ if (self->segoffset > 0 && self->cur_segment != segment) ++ self->segoffset = 0; ++ ++ copy_size = SPA_MIN (size, segsize - self->segoffset); ++ ++ if (self->direction == PW_DIRECTION_OUTPUT) { ++ memcpy (((guint8*) d->data) + offset, ringptr + self->segoffset, ++ copy_size); ++ } else { ++ memcpy (ringptr + self->segoffset, ((guint8*) d->data) + offset, ++ copy_size); ++ } ++ ++ remain = size - (segsize - self->segoffset); ++ ++ GST_TRACE_OBJECT (self->elem, ++ "seg %d: %s %d bytes remained:%d offset:%d segoffset:%d", segment, ++ self->direction == PW_DIRECTION_INPUT ? "INPUT" : "OUTPUT", ++ copy_size, remain, offset, self->segoffset); ++ ++ if (remain >= 0) { ++ offset += (segsize - self->segoffset); ++ size = remain; ++ ++ /* write silence on the segment we just read */ ++ if (self->direction == PW_DIRECTION_OUTPUT) ++ gst_audio_ring_buffer_clear (buf, segment); ++ ++ /* notify that we have read a complete segment */ ++ gst_audio_ring_buffer_advance (buf, 1); ++ self->segoffset = 0; ++ } else { ++ self->segoffset += size; ++ self->cur_segment = segment; ++ } ++ } while (remain > 0); ++ ++ pw_stream_queue_buffer (self->stream, b); ++} ++ ++static const struct pw_stream_events stream_events = { ++ PW_VERSION_STREAM_EVENTS, ++ .state_changed = on_stream_state_changed, ++ .param_changed = on_stream_param_changed, ++ .process = on_stream_process, ++}; ++ ++static gboolean ++copy_properties (GQuark field_id, const GValue *value, gpointer user_data) ++{ ++ struct pw_properties *properties = user_data; ++ ++ if (G_VALUE_HOLDS_STRING (value)) ++ pw_properties_set (properties, ++ g_quark_to_string (field_id), ++ g_value_get_string (value)); ++ return TRUE; ++} ++ ++static gboolean ++gst_pw_audio_ring_buffer_acquire (GstAudioRingBuffer *buf, ++ GstAudioRingBufferSpec *spec) ++{ ++ GstPwAudioRingBuffer *self = GST_PW_AUDIO_RING_BUFFER (buf); ++ struct pw_properties *props; ++ struct spa_pod_builder b = { NULL }; ++ uint8_t buffer[512]; ++ const struct spa_pod *params[1]; ++ ++ g_return_val_if_fail (spec, FALSE); ++ g_return_val_if_fail (GST_AUDIO_INFO_IS_VALID (&spec->info), FALSE); ++ g_return_val_if_fail (!self->stream, TRUE); /* already acquired */ ++ ++ g_return_val_if_fail (spec->type == GST_AUDIO_RING_BUFFER_FORMAT_TYPE_RAW, FALSE); ++ g_return_val_if_fail (GST_AUDIO_INFO_IS_FLOAT (&spec->info), FALSE); ++ ++ GST_DEBUG_OBJECT (self->elem, "acquire"); ++ ++ /* construct param & props objects */ ++ ++ props = pw_properties_new (NULL, NULL); ++ if (self->props->properties) { ++ gst_structure_foreach (self->props->properties, copy_properties, props); ++ } ++ ++ spa_pod_builder_init (&b, buffer, sizeof (buffer)); ++ params[0] = spa_pod_builder_add_object (&b, ++ SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, ++ SPA_FORMAT_mediaType, SPA_POD_Id (SPA_MEDIA_TYPE_audio), ++ SPA_FORMAT_mediaSubtype, SPA_POD_Id (SPA_MEDIA_SUBTYPE_raw), ++ SPA_FORMAT_AUDIO_format, SPA_POD_Id (SPA_AUDIO_FORMAT_F32), ++ SPA_FORMAT_AUDIO_rate, SPA_POD_Int (GST_AUDIO_INFO_RATE (&spec->info)), ++ SPA_FORMAT_AUDIO_channels, SPA_POD_Int (GST_AUDIO_INFO_CHANNELS (&spec->info))); ++ ++ self->segsize = spec->segsize; ++ self->bpf = GST_AUDIO_INFO_BPF (&spec->info); ++ self->rate = GST_AUDIO_INFO_RATE (&spec->info); ++ self->segoffset = 0; ++ ++ pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%u/%u", ++ self->segsize / self->bpf, self->rate); ++ GST_DEBUG_OBJECT (self->elem, "segsize:%u, bpf:%u, node.latency = %s", ++ self->segsize, self->bpf, pw_properties_get (props, PW_KEY_NODE_LATENCY)); ++ ++ /* connect stream */ ++ ++ pw_thread_loop_lock (self->main_loop); ++ ++ GST_DEBUG_OBJECT (self->elem, "creating stream"); ++ ++ self->stream = pw_stream_new (self->remote, self->props->client_name, props); ++ pw_stream_add_listener(self->stream, &self->stream_listener, &stream_events, ++ self); ++ ++ if (pw_stream_connect (self->stream, ++ self->direction, ++ self->props->path ? (uint32_t)atoi(self->props->path) : SPA_ID_INVALID, ++ PW_STREAM_FLAG_AUTOCONNECT | ++ PW_STREAM_FLAG_MAP_BUFFERS | ++ PW_STREAM_FLAG_RT_PROCESS, ++ params, 1) < 0) ++ goto start_error; ++ ++ GST_DEBUG_OBJECT (self->elem, "waiting for stream CONFIGURE"); ++ ++ if (!wait_for_stream_state (self, PW_STREAM_STATE_PAUSED)) ++ goto start_error; ++ ++ pw_thread_loop_unlock (self->main_loop); ++ ++ /* allocate the internal ringbuffer */ ++ ++ spec->seglatency = spec->segtotal + 1; ++ buf->size = spec->segtotal * spec->segsize; ++ buf->memory = g_malloc (buf->size); ++ ++ gst_audio_format_fill_silence (buf->spec.info.finfo, buf->memory, ++ buf->size); ++ ++ GST_DEBUG_OBJECT (self->elem, "acquire done"); ++ ++ return TRUE; ++ ++start_error: ++ { ++ GST_ERROR_OBJECT (self->elem, "could not start stream"); ++ pw_stream_destroy (self->stream); ++ self->stream = NULL; ++ pw_thread_loop_unlock (self->main_loop); ++ return FALSE; ++ } ++} ++ ++static gboolean ++gst_pw_audio_ring_buffer_release (GstAudioRingBuffer *buf) ++{ ++ GstPwAudioRingBuffer *self = GST_PW_AUDIO_RING_BUFFER (buf); ++ ++ GST_DEBUG_OBJECT (self->elem, "release"); ++ ++ pw_thread_loop_lock (self->main_loop); ++ if (self->stream) { ++ spa_hook_remove (&self->stream_listener); ++ pw_stream_disconnect (self->stream); ++ pw_stream_destroy (self->stream); ++ self->stream = NULL; ++ } ++ pw_thread_loop_unlock (self->main_loop); ++ ++ /* free the buffer */ ++ g_free (buf->memory); ++ buf->memory = NULL; ++ ++ return TRUE; ++} ++ ++static guint ++gst_pw_audio_ring_buffer_delay (GstAudioRingBuffer *buf) ++{ ++ GstPwAudioRingBuffer *self = GST_PW_AUDIO_RING_BUFFER (buf); ++ struct pw_time t; ++ ++ if (!self->stream || pw_stream_get_time (self->stream, &t) < 0) ++ return 0; ++ ++ if (self->direction == PW_DIRECTION_OUTPUT) { ++ /* on output streams, we set the pw_buffer.size in frames, ++ so no conversion is necessary */ ++ return t.queued; ++ } else { ++ /* on input streams, pw_buffer.size is set by pw_stream in ticks, ++ so we need to convert it to frames and also add segoffset, which ++ is the number of bytes we have read but not advertised yet, as ++ the segment is incomplete */ ++ if (t.rate.denom > 0) ++ return ++ gst_util_uint64_scale (t.queued, self->rate * t.rate.num, t.rate.denom) ++ + self->segoffset / self->bpf; ++ else ++ return self->segoffset / self->bpf; ++ } ++ ++ return 0; ++} ++ ++static void ++gst_pw_audio_ring_buffer_class_init (GstPwAudioRingBufferClass * klass) ++{ ++ GObjectClass *gobject_class; ++ GstAudioRingBufferClass *gstaudiorbuf_class; ++ ++ gobject_class = (GObjectClass *) klass; ++ gstaudiorbuf_class = (GstAudioRingBufferClass *) klass; ++ ++ gobject_class->finalize = gst_pw_audio_ring_buffer_finalize; ++ gobject_class->set_property = gst_pw_audio_ring_buffer_set_property; ++ ++ gstaudiorbuf_class->open_device = gst_pw_audio_ring_buffer_open_device; ++ gstaudiorbuf_class->acquire = gst_pw_audio_ring_buffer_acquire; ++ gstaudiorbuf_class->release = gst_pw_audio_ring_buffer_release; ++ gstaudiorbuf_class->close_device = gst_pw_audio_ring_buffer_close_device; ++ gstaudiorbuf_class->delay = gst_pw_audio_ring_buffer_delay; ++ ++ g_object_class_install_property (gobject_class, PROP_ELEMENT, ++ g_param_spec_object ("element", "Element", "The audio source or sink", ++ GST_TYPE_ELEMENT, ++ G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); ++ ++ g_object_class_install_property (gobject_class, PROP_DIRECTION, ++ g_param_spec_int ("direction", "Direction", "The stream direction", ++ PW_DIRECTION_INPUT, PW_DIRECTION_OUTPUT, PW_DIRECTION_INPUT, ++ G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); ++ ++ g_object_class_install_property (gobject_class, PROP_PROPS, ++ g_param_spec_pointer ("props", "Properties", "The properties struct", ++ G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); ++ ++ GST_DEBUG_CATEGORY_INIT (pw_audio_ring_buffer_debug, "pwaudioringbuffer", 0, ++ "PipeWire Audio Ring Buffer"); ++} +diff --git a/src/gst/gstpwaudioringbuffer.h b/src/gst/gstpwaudioringbuffer.h +new file mode 100644 +index 00000000..f47f668a +--- /dev/null ++++ b/src/gst/gstpwaudioringbuffer.h +@@ -0,0 +1,83 @@ ++/* PipeWire ++ * ++ * Copyright © 2018 Wim Taymans ++ * Copyright © 2019 Collabora Ltd. ++ * @author George Kiagiadakis ++ * ++ * 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 ++#include ++#include ++ ++G_BEGIN_DECLS ++ ++#define GST_TYPE_PW_AUDIO_RING_BUFFER \ ++ (gst_pw_audio_ring_buffer_get_type ()) ++ ++G_DECLARE_FINAL_TYPE(GstPwAudioRingBuffer, gst_pw_audio_ring_buffer, ++ GST, PW_AUDIO_RING_BUFFER, GstAudioRingBuffer); ++ ++typedef struct _GstPwAudioRingBufferProps GstPwAudioRingBufferProps; ++ ++struct _GstPwAudioRingBuffer ++{ ++ GstAudioRingBuffer parent; ++ ++ /* properties */ ++ GstElement *elem; ++ enum pw_direction direction; ++ GstPwAudioRingBufferProps *props; ++ ++ /* internal */ ++ struct pw_loop *loop; ++ struct pw_thread_loop *main_loop; ++ ++ struct pw_core *core; ++ struct pw_remote *remote; ++ struct spa_hook remote_listener; ++ ++ struct pw_stream *stream; ++ struct spa_hook stream_listener; ++ ++ gint segsize; ++ gint bpf; ++ gint rate; ++ ++ /* on_stream_process() state */ ++ gint segoffset; ++ gint cur_segment; ++}; ++ ++struct _GstPwAudioRingBufferProps ++{ ++ gchar *path; ++ gchar *client_name; ++ GstStructure *properties; ++ int fd; ++}; ++ ++G_END_DECLS ++ ++#endif +diff --git a/src/gst/gstpwaudiosink.c b/src/gst/gstpwaudiosink.c +new file mode 100644 +index 00000000..069996c3 +--- /dev/null ++++ b/src/gst/gstpwaudiosink.c +@@ -0,0 +1,207 @@ ++/* PipeWire ++ * ++ * Copyright © 2018 Wim Taymans ++ * Copyright © 2019 Collabora Ltd. ++ * @author George Kiagiadakis ++ * ++ * Permission is hereby granted, free of charge, to any person obtaining a ++ * copy of this software and associated documentation files (the "Software"), ++ * to deal in the Software without restriction, including without limitation ++ * the rights to use, copy, modify, merge, publish, distribute, sublicense, ++ * and/or sell copies of the Software, and to permit persons to whom the ++ * Software is furnished to do so, subject to the following conditions: ++ * ++ * The above copyright notice and this permission notice (including the next ++ * paragraph) shall be included in all copies or substantial portions of the ++ * Software. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, ++ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL ++ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER ++ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING ++ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER ++ * DEALINGS IN THE SOFTWARE. ++ */ ++ ++#ifdef HAVE_CONFIG_H ++#include "config.h" ++#endif ++ ++#include "gstpwaudiosink.h" ++ ++GST_DEBUG_CATEGORY_STATIC (pw_audio_sink_debug); ++#define GST_CAT_DEFAULT pw_audio_sink_debug ++ ++G_DEFINE_TYPE (GstPwAudioSink, gst_pw_audio_sink, GST_TYPE_AUDIO_BASE_SINK); ++ ++enum ++{ ++ PROP_0, ++ PROP_PATH, ++ PROP_CLIENT_NAME, ++ PROP_STREAM_PROPERTIES, ++ PROP_FD ++}; ++ ++static GstStaticPadTemplate gst_pw_audio_sink_template = ++GST_STATIC_PAD_TEMPLATE ("sink", ++ GST_PAD_SINK, ++ GST_PAD_ALWAYS, ++ GST_STATIC_CAPS (GST_AUDIO_CAPS_MAKE (GST_AUDIO_NE (F32)) ++ ", layout = (string)\"interleaved\"") ++); ++ ++ ++static void ++gst_pw_audio_sink_init (GstPwAudioSink * self) ++{ ++ self->props.fd = -1; ++ ++ /* Bump the default buffer size up to 21.3 ms, which is the default on most ++ * sound cards, in hope to match the alsa buffer size on the pipewire server. ++ * This may not always happen, but it still sounds better than the 10ms ++ * default latency. This is temporary until we have a better mechanism to ++ * select the appropriate latency */ ++ GST_AUDIO_BASE_SINK (self)->latency_time = 21333; ++} ++ ++static void ++gst_pw_audio_sink_finalize (GObject * object) ++{ ++ GstPwAudioSink *pwsink = GST_PW_AUDIO_SINK (object); ++ ++ g_free (pwsink->props.path); ++ g_free (pwsink->props.client_name); ++ if (pwsink->props.properties) ++ gst_structure_free (pwsink->props.properties); ++} ++ ++static void ++gst_pw_audio_sink_set_property (GObject * object, guint prop_id, ++ const GValue * value, GParamSpec * pspec) ++{ ++ GstPwAudioSink *pwsink = GST_PW_AUDIO_SINK (object); ++ ++ switch (prop_id) { ++ case PROP_PATH: ++ g_free (pwsink->props.path); ++ pwsink->props.path = g_value_dup_string (value); ++ break; ++ ++ case PROP_CLIENT_NAME: ++ g_free (pwsink->props.client_name); ++ pwsink->props.client_name = g_value_dup_string (value); ++ break; ++ ++ case PROP_STREAM_PROPERTIES: ++ if (pwsink->props.properties) ++ gst_structure_free (pwsink->props.properties); ++ pwsink->props.properties = ++ gst_structure_copy (gst_value_get_structure (value)); ++ break; ++ ++ case PROP_FD: ++ pwsink->props.fd = g_value_get_int (value); ++ break; ++ ++ default: ++ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); ++ break; ++ } ++} ++ ++static void ++gst_pw_audio_sink_get_property (GObject * object, guint prop_id, ++ GValue * value, GParamSpec * pspec) ++{ ++ GstPwAudioSink *pwsink = GST_PW_AUDIO_SINK (object); ++ ++ switch (prop_id) { ++ case PROP_PATH: ++ g_value_set_string (value, pwsink->props.path); ++ break; ++ ++ case PROP_CLIENT_NAME: ++ g_value_set_string (value, pwsink->props.client_name); ++ break; ++ ++ case PROP_STREAM_PROPERTIES: ++ gst_value_set_structure (value, pwsink->props.properties); ++ break; ++ ++ case PROP_FD: ++ g_value_set_int (value, pwsink->props.fd); ++ break; ++ ++ default: ++ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); ++ break; ++ } ++} ++ ++static GstAudioRingBuffer * ++gst_pw_audio_sink_create_ringbuffer (GstAudioBaseSink * sink) ++{ ++ GstPwAudioSink *self = GST_PW_AUDIO_SINK (sink); ++ GstAudioRingBuffer *buffer; ++ ++ GST_DEBUG_OBJECT (sink, "creating ringbuffer"); ++ buffer = g_object_new (GST_TYPE_PW_AUDIO_RING_BUFFER, ++ "element", sink, ++ "direction", PW_DIRECTION_OUTPUT, ++ "props", &self->props, ++ NULL); ++ GST_DEBUG_OBJECT (sink, "created ringbuffer @%p", buffer); ++ ++ return buffer; ++} ++ ++static void ++gst_pw_audio_sink_class_init (GstPwAudioSinkClass * klass) ++{ ++ GObjectClass *gobject_class; ++ GstElementClass *gstelement_class; ++ GstAudioBaseSinkClass *gstaudiobsink_class; ++ ++ gobject_class = (GObjectClass *) klass; ++ gstelement_class = (GstElementClass *) klass; ++ gstaudiobsink_class = (GstAudioBaseSinkClass *) klass; ++ ++ gobject_class->finalize = gst_pw_audio_sink_finalize; ++ gobject_class->set_property = gst_pw_audio_sink_set_property; ++ gobject_class->get_property = gst_pw_audio_sink_get_property; ++ ++ gstaudiobsink_class->create_ringbuffer = gst_pw_audio_sink_create_ringbuffer; ++ ++ g_object_class_install_property (gobject_class, PROP_PATH, ++ g_param_spec_string ("path", "Path", ++ "The sink path to connect to (NULL = default)", NULL, ++ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); ++ ++ g_object_class_install_property (gobject_class, PROP_CLIENT_NAME, ++ g_param_spec_string ("client-name", "Client Name", ++ "The client name to use (NULL = default)", NULL, ++ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); ++ ++ g_object_class_install_property (gobject_class, PROP_STREAM_PROPERTIES, ++ g_param_spec_boxed ("stream-properties", "Stream properties", ++ "List of PipeWire stream properties", GST_TYPE_STRUCTURE, ++ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); ++ ++ g_object_class_install_property (gobject_class, PROP_FD, ++ g_param_spec_int ("fd", "Fd", "The fd to connect with", -1, G_MAXINT, -1, ++ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); ++ ++ gst_element_class_set_static_metadata (gstelement_class, ++ "PipeWire Audio sink", "Sink/Audio", ++ "Send audio to PipeWire", ++ "George Kiagiadakis "); ++ ++ 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 ++ * ++ * 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 ++ * ++ * 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 "); ++ ++ 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 ++ * ++ * Permission is hereby granted, free of charge, to any person obtaining a ++ * copy of this software and associated documentation files (the "Software"), ++ * to deal in the Software without restriction, including without limitation ++ * the rights to use, copy, modify, merge, publish, distribute, sublicense, ++ * and/or sell copies of the Software, and to permit persons to whom the ++ * Software is furnished to do so, subject to the following conditions: ++ * ++ * The above copyright notice and this permission notice (including the next ++ * paragraph) shall be included in all copies or substantial portions of the ++ * Software. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, ++ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL ++ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER ++ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING ++ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER ++ * DEALINGS IN THE SOFTWARE. ++ */ ++ ++#ifndef __GST_PW_AUDIO_SRC_H__ ++#define __GST_PW_AUDIO_SRC_H__ ++ ++#include "gstpwaudioringbuffer.h" ++ ++G_BEGIN_DECLS ++ ++#define GST_TYPE_PW_AUDIO_SRC \ ++ (gst_pw_audio_src_get_type ()) ++ ++G_DECLARE_FINAL_TYPE(GstPwAudioSrc, gst_pw_audio_src, ++ GST, PW_AUDIO_SRC, GstAudioBaseSrc); ++ ++struct _GstPwAudioSrc ++{ ++ GstAudioBaseSrc parent; ++ GstPwAudioRingBufferProps props; ++}; ++ ++G_END_DECLS ++ ++#endif +diff --git a/src/gst/meson.build b/src/gst/meson.build +index ad0e0801..0e922347 100644 +--- a/src/gst/meson.build ++++ b/src/gst/meson.build +@@ -6,6 +6,9 @@ pipewire_gst_sources = [ + 'gstpipewirepool.c', + 'gstpipewiresink.c', + 'gstpipewiresrc.c', ++ 'gstpwaudioringbuffer.c', ++ 'gstpwaudiosink.c', ++ 'gstpwaudiosrc.c', + ] + + pipewire_gst_headers = [ +@@ -15,6 +18,9 @@ pipewire_gst_headers = [ + 'gstpipewirepool.h', + 'gstpipewiresink.h', + 'gstpipewiresrc.h', ++ 'gstpwaudioringbuffer.h', ++ 'gstpwaudiosink.h', ++ 'gstpwaudiosrc.h', + ] + + pipewire_gst_c_args = [ +-- +2.24.0 + diff --git a/meta-pipewire/recipes-multimedia/pipewire/pipewire/0004-audioconvert-always-assume-that-output-ports-are-NOT.patch b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0004-audioconvert-always-assume-that-output-ports-are-NOT.patch new file mode 100644 index 000000000..beb878390 --- /dev/null +++ b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0004-audioconvert-always-assume-that-output-ports-are-NOT.patch @@ -0,0 +1,35 @@ +From ce155eb0073fba84556782633f79bb7d03492c07 Mon Sep 17 00:00:00 2001 +From: George Kiagiadakis +Date: Wed, 2 Oct 2019 21:40:34 +0300 +Subject: [PATCH] audioconvert: always assume that output ports are NOT monitor + ports + +Otherwise, when we setup audioconvert in merge+split mode, +it assumes that the splitter's ports are monitor ports and +belong to the merger. + +Upstream-Status: Inappropriate [workaround] +--- + spa/plugins/audioconvert/audioconvert.c | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/spa/plugins/audioconvert/audioconvert.c b/spa/plugins/audioconvert/audioconvert.c +index 74a62a35..72da37d1 100644 +--- a/spa/plugins/audioconvert/audioconvert.c ++++ b/spa/plugins/audioconvert/audioconvert.c +@@ -113,8 +113,12 @@ struct impl { + unsigned int add_listener:1; + }; + ++#if 0 + #define IS_MONITOR_PORT(this,dir,port_id) (dir == SPA_DIRECTION_OUTPUT && port_id > 0 && \ + this->mode[SPA_DIRECTION_INPUT] == SPA_PARAM_PORT_CONFIG_MODE_dsp) ++#else ++#define IS_MONITOR_PORT(this,dir,port_id) (false) ++#endif + + static void emit_node_info(struct impl *this, bool full) + { +-- +2.24.0 + diff --git a/meta-pipewire/recipes-multimedia/pipewire/pipewire/0005-module-access-add-same-sec-label-mode.patch b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0005-module-access-add-same-sec-label-mode.patch new file mode 100644 index 000000000..07a1ec114 --- /dev/null +++ b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0005-module-access-add-same-sec-label-mode.patch @@ -0,0 +1,94 @@ +From 19fad1a4fa8bdc4f02aac4e169e7ff9cab18bdcd Mon Sep 17 00:00:00 2001 +From: George Kiagiadakis +Date: Tue, 19 Nov 2019 17:09:07 +0200 +Subject: [PATCH] module-access: add same-sec-label-mode + +This is a mode where the access module allows all clients that have +the same security label as the pipewire daemon, and every other +client is put on the restricted state. + +In systems that use SMACK security labels, such as AGL, this allows +the session manager (which is spawned by pipewire, inheriting the +same smack label) to have full access to all objects, while every +other client is restricted and the session manager must decide +what to do with it + +Note that while this option is configurable, there is no loss of +security if this option is not set in the configuration. Clients +that don't have the same security context will be considered to +be flatpak clients because pipewire will not be able to open +/proc/pid/cmdline. This however results in some unwanted error +messages that may be confusing. + +Upstream-Status: Inappropriate [agl/smack specific] +--- + src/modules/module-access.c | 45 ++++++++++++++++++++++++++++++++++++- + 1 file changed, 44 insertions(+), 1 deletion(-) + +diff --git a/src/modules/module-access.c b/src/modules/module-access.c +index 09dafa43..f75306d9 100644 +--- a/src/modules/module-access.c ++++ b/src/modules/module-access.c +@@ -50,6 +50,30 @@ struct impl { + struct spa_hook module_listener; + }; + ++static int check_seclabel(const char *str) ++{ ++ char attr[1024]; ++ int fd, len; ++ ++ fd = open("/proc/self/attr/current", O_RDONLY); ++ if (fd < 0) ++ return -errno; ++ ++ if ((len = read(fd, attr, 1024)) <= 0) { ++ close(fd); ++ return -EIO; ++ } ++ attr[len] = '\0'; ++ ++ if (strcmp(attr, str) == 0) { ++ close(fd); ++ return 1; ++ } ++ ++ close(fd); ++ return 0; ++} ++ + static int check_cmdline(struct pw_client *client, int pid, const char *str) + { + char path[2048]; +@@ -121,8 +145,27 @@ core_check_access(void *data, struct pw_client *client) + const char *str; + int pid, res; + ++ props = pw_client_get_properties(client); ++ ++ if (impl->properties && ++ (str = pw_properties_get(impl->properties, "same-sec-label-mode")) != NULL && ++ strcmp(str, "1") == 0) { ++ if (props && (str = pw_properties_get(props, PW_KEY_SEC_LABEL)) != NULL) { ++ res = check_seclabel(str); ++ if (res == 1) ++ goto granted; ++ else if (res < 0) ++ pw_log_warn("module %p: client %p seclabel check failed: %s", ++ impl, client, spa_strerror(res)); ++ } ++ pw_log_debug("module %p: seclabel restricted client %p added", ++ impl, client); ++ items[0] = SPA_DICT_ITEM_INIT(PW_KEY_ACCESS, "restricted"); ++ goto wait_permissions; ++ } ++ + pid = -EINVAL; +- if ((props = pw_client_get_properties(client)) != NULL) { ++ if (props != NULL) { + if ((str = pw_properties_get(props, PW_KEY_SEC_PID)) != NULL) + pid = atoi(str); + } +-- +2.24.0 + diff --git a/meta-pipewire/recipes-multimedia/pipewire/pipewire/0006-alsa-pcm-call-reuse_buffers-when-resetting-the-state.patch b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0006-alsa-pcm-call-reuse_buffers-when-resetting-the-state.patch new file mode 100644 index 000000000..cae4d70f6 --- /dev/null +++ b/meta-pipewire/recipes-multimedia/pipewire/pipewire/0006-alsa-pcm-call-reuse_buffers-when-resetting-the-state.patch @@ -0,0 +1,30 @@ +From 5946fbd2ca3a7f892b4ebc10090f62df6bb1ec88 Mon Sep 17 00:00:00 2001 +From: George Kiagiadakis +Date: Thu, 9 Jan 2020 19:27:23 +0200 +Subject: [PATCH] alsa-pcm: call reuse_buffers when resetting the state of the + buffers + +This allows the upstream node to put buffers back to its pool in case +they were left around in the ready list locally when the alsa-pcm-sink +was last paused. + +Fixes #203 +--- + spa/plugins/alsa/alsa-pcm.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/spa/plugins/alsa/alsa-pcm.c b/spa/plugins/alsa/alsa-pcm.c +index 63d75549..a6f22cf0 100644 +--- a/spa/plugins/alsa/alsa-pcm.c ++++ b/spa/plugins/alsa/alsa-pcm.c +@@ -1115,6 +1115,7 @@ static void reset_buffers(struct state *this) + struct buffer *b = &this->buffers[i]; + if (this->stream == SND_PCM_STREAM_PLAYBACK) { + SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUT); ++ spa_node_call_reuse_buffer(&this->callbacks, 0, b->id); + } else { + spa_list_append(&this->free, &b->link); + SPA_FLAG_CLEAR(b->flags, BUFFER_FLAG_OUT); +-- +2.24.1 + diff --git a/meta-pipewire/recipes-multimedia/pipewire/pipewire/pipewire@.service b/meta-pipewire/recipes-multimedia/pipewire/pipewire/pipewire@.service new file mode 100644 index 000000000..e116dc1fa --- /dev/null +++ b/meta-pipewire/recipes-multimedia/pipewire/pipewire/pipewire@.service @@ -0,0 +1,24 @@ +[Unit] +Description=Multimedia Service for user %i +Requires=pipewire@%i.socket + +[Install] +Also=pipewire@%i.socket + +[Service] +Type=simple +Restart=on-failure +ExecStart=/usr/bin/pipewire + +Environment=XDG_RUNTIME_DIR=/run/user/%i +Environment=DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/%i/bus +EnvironmentFile=-/etc/pipewire/environment + +User=%i +Slice=user-%i.slice +SmackProcessLabel=System::Pipewire +SupplementaryGroups=audio +UMask=0077 +CapabilityBoundingSet= +SystemCallFilter=@basic-io @file-system @io-event @ipc \ + @memlock @network-io @process @resources @signal diff --git a/meta-pipewire/recipes-multimedia/pipewire/pipewire/pipewire@.socket b/meta-pipewire/recipes-multimedia/pipewire/pipewire/pipewire@.socket new file mode 100644 index 000000000..10cb32276 --- /dev/null +++ b/meta-pipewire/recipes-multimedia/pipewire/pipewire/pipewire@.socket @@ -0,0 +1,19 @@ +[Unit] +Description=Multimedia Service socket for user %i +Requires=afm-user-setup@%i.service +After=afm-user-setup@%i.service + +[Socket] +Priority=6 +Backlog=5 +ListenStream=/run/user/%i/pipewire-0 +Service=pipewire@%i.service +SmackLabel=* +SmackLabelIPIn=System +SmackLabelIPOut=System +SocketUser=%i +SocketGroup=%i +SocketMode=0660 + +[Install] +WantedBy=afm-user-session@%i.target diff --git a/meta-pipewire/recipes-multimedia/pipewire/pipewire/smack-pipewire b/meta-pipewire/recipes-multimedia/pipewire/pipewire/smack-pipewire new file mode 100644 index 000000000..8d5b541ff --- /dev/null +++ b/meta-pipewire/recipes-multimedia/pipewire/pipewire/smack-pipewire @@ -0,0 +1,8 @@ +System System::Pipewire rwxa-- +System::Pipewire System -wx--- +System::Pipewire System::Shared r-x--- +System::Pipewire System::Run rwxat- +System::Pipewire System::Log rwxa-- +System::Pipewire _ r-x--l +System::Pipewire User::Home r-x--l +System::Pipewire User::App-Shared rwxat- diff --git a/meta-pipewire/recipes-multimedia/pipewire/pipewire_git.bb b/meta-pipewire/recipes-multimedia/pipewire/pipewire_git.bb new file mode 100644 index 000000000..1a4e4eb97 --- /dev/null +++ b/meta-pipewire/recipes-multimedia/pipewire/pipewire_git.bb @@ -0,0 +1,17 @@ +require pipewire.inc + +SRC_URI = "git://gitlab.freedesktop.org/pipewire/pipewire.git;protocol=https;branch=master \ + file://0001-meson-revert-version-check-to-require-meson-0.47-not.patch \ + file://0002-arm-build-with-mno-unaligned-access.patch \ + file://0003-gst-Implement-new-pwaudio-src-sink-elements-based-on.patch \ + file://0004-audioconvert-always-assume-that-output-ports-are-NOT.patch \ + file://0005-module-access-add-same-sec-label-mode.patch \ + file://0006-alsa-pcm-call-reuse_buffers-when-resetting-the-state.patch \ + " + +SRCREV = "b0932e687fc47e0872ca291531f2291d99042d70" + +PV = "0.2.91+git${SRCPV}+2" +S = "${WORKDIR}/git" + +RDEPENDS_${PN} += "virtual/pipewire-sessionmanager virtual/pipewire-config" diff --git a/meta-pipewire/recipes-multimedia/pipewire/pipewire_git.bbappend b/meta-pipewire/recipes-multimedia/pipewire/pipewire_git.bbappend new file mode 100644 index 000000000..8a0b0741f --- /dev/null +++ b/meta-pipewire/recipes-multimedia/pipewire/pipewire_git.bbappend @@ -0,0 +1,30 @@ +SRC_URI += "\ + file://pipewire@.service \ + file://pipewire@.socket \ + file://smack-pipewire \ + " + +do_install_append() { + if ${@bb.utils.contains('DISTRO_FEATURES', 'systemd', 'true', 'false', d)}; then + # remove the original user unit files shipped by pipewire + rm -rf ${D}${systemd_unitdir} + + # install our own system-level templates + mkdir -p ${D}${systemd_system_unitdir}/ + install -m 0644 ${WORKDIR}/pipewire@.service ${D}${systemd_system_unitdir}/pipewire@.service + install -m 0644 ${WORKDIR}/pipewire@.socket ${D}${systemd_system_unitdir}/pipewire@.socket + + # enable the socket to start together with afm-user-session + mkdir -p ${D}${systemd_system_unitdir}/afm-user-session@.target.wants + ln -sf ../pipewire@.socket ${D}${systemd_system_unitdir}/afm-user-session@.target.wants/pipewire@.socket + + # install smack rules + mkdir -p ${D}${sysconfdir}/smack/accesses.d + install -m 0644 ${WORKDIR}/smack-pipewire ${D}${sysconfdir}/smack/accesses.d/pipewire + fi +} + +FILES_${PN} += "\ + ${systemd_system_unitdir}/* \ + ${sysconfdir}/smack/accesses.d/* \ +" -- cgit 1.2.3-korg