Blob Blame History Raw
From 8f9d27a2c48b8e913669344d2af59947357ba830 Mon Sep 17 00:00:00 2001
From: Christian Spielberger <c.spielberger@commend.com>
Date: Thu, 2 Feb 2023 08:52:54 +0100
Subject: [PATCH 01/11] pipewire: add pipewire module

---
 cmake/FindPIPEWIRE.cmake        |  10 ++
 cmake/modules.cmake             |   4 +
 modules/pipewire/CMakeLists.txt |  17 ++++
 modules/pipewire/capture.c      | 163 ++++++++++++++++++++++++++++++++
 modules/pipewire/pipewire.c     | 161 +++++++++++++++++++++++++++++++
 modules/pipewire/pipewire.h     |  17 ++++
 modules/pipewire/playback.c     | 163 ++++++++++++++++++++++++++++++++
 7 files changed, 535 insertions(+)
 create mode 100644 cmake/FindPIPEWIRE.cmake
 create mode 100644 modules/pipewire/CMakeLists.txt
 create mode 100644 modules/pipewire/capture.c
 create mode 100644 modules/pipewire/pipewire.c
 create mode 100644 modules/pipewire/pipewire.h
 create mode 100644 modules/pipewire/playback.c

diff --git a/cmake/FindPIPEWIRE.cmake b/cmake/FindPIPEWIRE.cmake
new file mode 100644
index 000000000..bd9b90181
--- /dev/null
+++ b/cmake/FindPIPEWIRE.cmake
@@ -0,0 +1,10 @@
+# Find the system's pipewire includes and library
+#
+#  PIPEWIRE_INCLUDE_DIRS - where to find pipewire.h
+#  PIPEWIRE_LIBRARIES    - List of libraries when using pipewire
+#  PIPEWIRE_FOUND        - True if pipewire found
+
+if(NOT WIN32)
+    find_package(PkgConfig)
+    pkg_search_module(PIPEWIRE libpipewire-0.3)
+endif()
diff --git a/cmake/modules.cmake b/cmake/modules.cmake
index ed99bf835..e68835595 100644
--- a/cmake/modules.cmake
+++ b/cmake/modules.cmake
@@ -20,6 +20,7 @@ find_package(OPUS)
 find_package(PNG)
 find_package(PORTAUDIO)
 find_package(PULSE)
+find_package(PIPEWIRE)
 find_package(SDL)
 find_package(SNDFILE)
 find_package(SPANDSP)
@@ -140,6 +141,9 @@ endif()
 if(PULSE_FOUND)
   list(APPEND MODULES pulse)
 endif()
+if(PIPEWIRE_FOUND)
+  list(APPEND MODULES pipewire)
+endif()
 if(SDL_FOUND)
   list(APPEND MODULES sdl)
 endif()
diff --git a/modules/pipewire/CMakeLists.txt b/modules/pipewire/CMakeLists.txt
new file mode 100644
index 000000000..8673873af
--- /dev/null
+++ b/modules/pipewire/CMakeLists.txt
@@ -0,0 +1,17 @@
+project(pipewire)
+
+set(SRCS pipewire.c playback.c capture.c)
+
+if(STATIC)
+    add_library(${PROJECT_NAME} OBJECT ${SRCS})
+else()
+    add_library(${PROJECT_NAME} MODULE ${SRCS})
+endif()
+
+target_include_directories(${PROJECT_NAME} PRIVATE ${PIPEWIRE_INCLUDE_DIRS})
+target_link_directories(${PROJECT_NAME} PRIVATE ${PIPEWIRE_LIBRARY_DIRS})
+target_link_libraries(${PROJECT_NAME} PRIVATE ${PIPEWIRE_LIBRARIES})
+target_compile_options(${PROJECT_NAME} PRIVATE
+  -Wno-pedantic
+  -Wno-bad-function-cast
+)
diff --git a/modules/pipewire/capture.c b/modules/pipewire/capture.c
new file mode 100644
index 000000000..3e92d131c
--- /dev/null
+++ b/modules/pipewire/capture.c
@@ -0,0 +1,163 @@
+/**
+ * @file capture.c  Pipewire sound driver - capture
+ *
+ * Copyright (C) 2023 Commend.com - c.spielberger@commend.com
+ */
+
+#include <string.h>
+#include <errno.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include <spa/param/audio/format-utils.h>
+#include <pipewire/pipewire.h>
+
+#include "pipewire.h"
+
+
+struct ausrc_st {
+	struct pw_stream *stream;
+
+	struct ausrc_prm prm;
+	ausrc_read_h *rh;
+	struct spa_hook listener;
+	ausrc_error_h *errh;
+
+	size_t sampsz;
+	uint64_t samps;
+
+	void *arg;
+};
+
+
+static void on_process(void *arg);
+
+static const struct pw_stream_events stream_events = {
+	PW_VERSION_STREAM_EVENTS,
+	.process = on_process,
+};
+
+
+static void ausrc_destructor(void *arg)
+{
+	struct ausrc_st *st = arg;
+
+	st->rh = NULL;
+	st->errh = NULL;
+	pw_stream_destroy(st->stream);
+}
+
+
+int pw_capture_alloc(struct ausrc_st **stp, const struct ausrc *as,
+	struct ausrc_prm *prm, const char *dev, ausrc_read_h *rh,
+	ausrc_error_h *errh, void *arg)
+{
+	struct ausrc_st *st;
+	const struct spa_pod *params[1];
+	uint8_t buffer[1024];
+	struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer,
+							sizeof(buffer));
+	const char name[] = "baresip-capture";
+	int err = 0;
+
+	if (!stp || !as || !prm || !rh)
+		return EINVAL;
+
+	info ("pipewire: opening capture(%u Hz, %d channels,"
+	      "device '%s')\n", prm->srate, prm->ch, dev);
+
+	st = mem_zalloc(sizeof(*st), ausrc_destructor);
+	if (!st)
+		return ENOMEM;
+
+	st->prm.srate = prm->srate;
+	st->prm.ch    = prm->ch;
+	st->prm.ptime = prm->ptime;
+	st->prm.fmt   = prm->fmt;
+
+	st->sampsz = aufmt_sample_size(prm->fmt);
+	st->samps  = 0;
+
+	st->rh   = rh;
+	st->errh = errh;
+	st->arg  = arg;
+
+	pw_thread_loop_lock (pw_loop_instance());
+	st->stream = pw_stream_new(pw_core_instance(), name,
+			   pw_properties_new(
+				     PW_KEY_MEDIA_TYPE, "Audio",
+				     PW_KEY_MEDIA_CATEGORY, "Capture",
+				     PW_KEY_MEDIA_ROLE, "Communication",
+				     NULL));
+	if (!st->stream) {
+		err = errno;
+		goto out;
+	}
+
+	pw_stream_add_listener(st->stream, &st->listener, &stream_events, st);
+	params[0] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat,
+					&SPA_AUDIO_INFO_RAW_INIT(
+					.format = aufmt_to_pw_format(prm->fmt),
+					.channels = prm->ch,
+					.rate = prm->srate ));
+	if (!params[0])
+		goto out;
+
+	err = pw_stream_connect(st->stream,
+				PW_DIRECTION_INPUT,
+				PW_ID_ANY,
+				PW_STREAM_FLAG_AUTOCONNECT |
+				PW_STREAM_FLAG_MAP_BUFFERS |
+				PW_STREAM_FLAG_RT_PROCESS,
+				params, 1);
+
+	pw_thread_loop_unlock(pw_loop_instance());
+
+	info ("pipewire: stream %s started (%m)\n", name, err);
+
+  out:
+	if (err)
+		mem_deref(st);
+	else
+		*stp = st;
+
+	return err;
+}
+
+
+/**
+ * Pipewire process callback
+ *
+ * @param arg Argument (ausrc_st object)
+ */
+static void on_process(void *arg)
+{
+	struct ausrc_st *st = arg;
+	struct pw_buffer *b;
+	struct spa_buffer *buf;
+	struct auframe af;
+
+	void *sampv;
+	size_t sampc;
+
+	b = pw_stream_dequeue_buffer(st->stream);
+	if (!b)
+		warning("pipewire: out of buffers (%m)\n", errno);
+
+	buf = b->buffer;
+	sampv = buf->datas[0].data;
+	if (!sampv)
+		return;
+
+	sampc = buf->datas[0].chunk->size / st->sampsz;
+
+	auframe_init(&af, st->prm.fmt, sampv, sampc,
+		     st->prm.srate, st->prm.ch);
+
+	af.timestamp = st->samps * AUDIO_TIMEBASE /
+		       (st->prm.srate * st->prm.ch);
+	st->samps += sampc;
+	st->rh(&af, st->arg);
+
+	pw_stream_queue_buffer(st->stream, b);
+}
diff --git a/modules/pipewire/pipewire.c b/modules/pipewire/pipewire.c
new file mode 100644
index 000000000..c9e6aaff4
--- /dev/null
+++ b/modules/pipewire/pipewire.c
@@ -0,0 +1,161 @@
+/**
+ * @file pipewire.c  Pipewire sound driver
+ *
+ * Copyright (C) 2023 Commend.com - c.spielberger@commend.com
+ */
+
+#include <string.h>
+#include <errno.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include <spa/param/audio/raw.h>
+#include <pipewire/pipewire.h>
+
+#include "pipewire.h"
+
+/**
+ * @defgroup pipewire pipewire
+ *
+ * Audio driver module for Pipewire
+ *
+ */
+
+enum {
+	RECONN_DELAY = 1500,
+};
+
+
+struct pw_stat {
+	struct pw_thread_loop *loop;
+	struct pw_context *context;
+	struct pw_core *core;
+};
+
+
+static struct pw_stat *d = NULL;
+
+static struct auplay *auplay = NULL;
+static struct ausrc *ausrc = NULL;
+
+
+static void destructor(void *arg)
+{
+	struct pw_stat *pw = arg;
+
+	if (pw->core)
+		pw_core_disconnect(pw->core);
+
+	if (pw->context)
+		pw_context_destroy(pw->context);
+
+	if (pw->loop) {
+		pw_thread_loop_stop(pw->loop);
+		pw_thread_loop_destroy(pw->loop);
+	}
+}
+
+
+static struct pw_stat *pw_stat_alloc(void)
+{
+	struct pw_stat *pw;
+	int err;
+
+	pw = mem_zalloc(sizeof(*pw), destructor);
+
+	pw->loop = pw_thread_loop_new("baresip pipewire", NULL);
+	if (!pw->loop)
+		goto errout;
+
+	err = pw_thread_loop_start(pw->loop);
+	if (err)
+		goto errout;
+
+	pw->context = pw_context_new(pw_thread_loop_get_loop(pw->loop),
+				     NULL /* properties */,
+				     0 /* user_data size */);
+	if (!pw->context)
+		goto errout;
+
+	pw->core = pw_context_connect(pw->context,
+				      NULL /* properties */,
+				      0 /* user_data size */);
+	if (!pw->core)
+		goto errout;
+
+	info("pipewire: connected to pipewire\n");
+	return pw;
+
+errout:
+	warning("pipewire: could not connect to pipewire\n");
+	mem_deref(pw);
+	return NULL;
+}
+
+
+struct pw_core *pw_core_instance(void)
+{
+	if (!d)
+		return NULL;
+
+	return d->core;
+}
+
+
+struct pw_thread_loop *pw_loop_instance(void)
+{
+	if (!d)
+		return NULL;
+
+	return d->loop;
+}
+
+
+int aufmt_to_pw_format(enum aufmt fmt)
+{
+	switch (fmt) {
+		case AUFMT_S16LE:  return SPA_AUDIO_FORMAT_S16_LE;
+		case AUFMT_FLOAT:  return SPA_AUDIO_FORMAT_F32;
+		default: return 0;
+	}
+}
+
+
+static int module_init(void)
+{
+	int err = 0;
+
+	pw_init(NULL, NULL);
+	info("pipewire: headers %s library %s \n",
+	     pw_get_headers_version(), pw_get_library_version());
+
+	d = pw_stat_alloc();
+	if (!d)
+		return errno;
+
+	err  = auplay_register(&auplay, baresip_auplayl(),
+			       "pipewire", pw_playback_alloc);
+	err |= ausrc_register(&ausrc, baresip_ausrcl(),
+			      "pipewire", pw_capture_alloc);
+
+	return err;
+}
+
+
+static int module_close(void)
+{
+	auplay = mem_deref(auplay);
+	ausrc  = mem_deref(ausrc);
+
+	d = mem_deref(d);
+	pw_deinit();
+	return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(pipewire) = {
+	"pipewire",
+	"audio",
+	module_init,
+	module_close,
+};
diff --git a/modules/pipewire/pipewire.h b/modules/pipewire/pipewire.h
new file mode 100644
index 000000000..0a93aaf52
--- /dev/null
+++ b/modules/pipewire/pipewire.h
@@ -0,0 +1,17 @@
+/**
+ * @file pipewire.h  Pipewire sound driver - internal API
+ *
+ * Copyright (C) 2023 Commend.com - c.spielberger@commend.com
+ */
+
+int aufmt_to_pw_format(enum aufmt fmt);
+struct pw_core *pw_core_instance(void);
+struct pw_thread_loop *pw_loop_instance(void);
+
+int pw_playback_alloc(struct auplay_st **stp,
+		      const struct auplay *ap,
+		      struct auplay_prm *prm, const char *dev,
+		      auplay_write_h *wh, void *arg);
+int pw_capture_alloc(struct ausrc_st **stp, const struct ausrc *as,
+		     struct ausrc_prm *prm, const char *dev, ausrc_read_h *rh,
+		     ausrc_error_h *errh, void *arg);
diff --git a/modules/pipewire/playback.c b/modules/pipewire/playback.c
new file mode 100644
index 000000000..94fceb1b0
--- /dev/null
+++ b/modules/pipewire/playback.c
@@ -0,0 +1,163 @@
+/**
+ * @file playback.c  Pipewire sound driver - playback
+ *
+ * Copyright (C) 2023 Commend.com - c.spielberger@commend.com
+ */
+
+#include <string.h>
+#include <errno.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include <spa/param/audio/format-utils.h>
+#include <pipewire/pipewire.h>
+
+#include "pipewire.h"
+
+struct auplay_st {
+	struct pw_stream *stream;
+
+	struct auplay_prm prm;
+	auplay_write_h *wh;
+	struct spa_hook listener;
+
+	size_t sampc;
+	size_t nbytes;
+	int32_t stride;
+
+	void *arg;
+};
+
+static void on_process(void *arg);
+
+static const struct pw_stream_events stream_events = {
+	PW_VERSION_STREAM_EVENTS,
+	.process = on_process,
+};
+
+
+static void auplay_destructor(void *arg)
+{
+	struct auplay_st *st = arg;
+
+	st->wh = NULL;
+	pw_stream_destroy(st->stream);
+}
+
+
+int pw_playback_alloc(struct auplay_st **stp, const struct auplay *ap,
+	struct auplay_prm *prm, const char *dev, auplay_write_h *wh, void *arg)
+{
+	struct auplay_st *st;
+	const struct spa_pod *params[1];
+	uint8_t buffer[1024];
+	struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer,
+							sizeof(buffer));
+	const char name[] = "baresip-playback";
+	size_t sampsz;
+	int err = 0;
+
+	if (!stp || !ap || !prm || !wh)
+		return EINVAL;
+
+	info ("pipewire: opening playback (%u Hz, %d channels, device %s, "
+		"ptime %u)\n", prm->srate, prm->ch, dev, prm->ptime);
+
+	st = mem_zalloc(sizeof(*st), auplay_destructor);
+	if (!st)
+		return ENOMEM;
+
+	st->prm.srate = prm->srate;
+	st->prm.ch    = prm->ch;
+	st->prm.ptime = prm->ptime;
+	st->prm.fmt   = prm->fmt;
+
+	sampsz = aufmt_sample_size(prm->fmt);
+	st->sampc  = st->prm.ptime * st->prm.ch * st->prm.srate / 1000;
+	st->nbytes = st->sampc * sampsz;
+	st->stride = sampsz * prm->ch;
+
+	st->wh  = wh;
+	st->arg = arg;
+
+	pw_thread_loop_lock (pw_loop_instance());
+	st->stream = pw_stream_new(pw_core_instance(), name,
+			   pw_properties_new(
+				     PW_KEY_MEDIA_TYPE, "Audio",
+				     PW_KEY_MEDIA_CATEGORY, "Playback",
+				     PW_KEY_MEDIA_ROLE, "Communication",
+				     NULL));
+	if (!st->stream) {
+		err = errno;
+		goto out;
+	}
+
+	pw_stream_add_listener(st->stream, &st->listener, &stream_events, st);
+	params[0] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat,
+					&SPA_AUDIO_INFO_RAW_INIT(
+					.format = aufmt_to_pw_format(prm->fmt),
+					.channels = prm->ch,
+					.rate = prm->srate ));
+	if (!params[0])
+		goto out;
+
+	err = pw_stream_connect(st->stream,
+				PW_DIRECTION_OUTPUT,
+				PW_ID_ANY,
+				PW_STREAM_FLAG_AUTOCONNECT |
+				PW_STREAM_FLAG_MAP_BUFFERS |
+				PW_STREAM_FLAG_RT_PROCESS,
+				params, 1);
+
+	pw_thread_loop_unlock(pw_loop_instance());
+
+	info ("pipewire: stream %s started (%m)\n", name, err);
+
+  out:
+	if (err)
+		mem_deref(st);
+	else
+		*stp = st;
+
+	return err;
+}
+
+
+/**
+ * Pipewire process callback
+ *
+ * @param arg Argument (auplay_st object)
+ */
+static void on_process(void *arg)
+{
+	struct auplay_st *st = arg;
+	struct pw_buffer *b;
+	struct spa_buffer *buf;
+	struct auframe af;
+	void *sampv;
+
+	b = pw_stream_dequeue_buffer(st->stream);
+	if (!b)
+		warning("pipewire: out of buffers (%m)\n", errno);
+
+	buf = b->buffer;
+	sampv = buf->datas[0].data;
+	if (!sampv)
+		return;
+
+	if (buf->datas[0].maxsize < st->nbytes) {
+		warning("pipewire: buffer to small\n");
+		return;
+	}
+
+	auframe_init(&af, st->prm.fmt, sampv, st->sampc,
+		     st->prm.srate, st->prm.ch);
+
+	st->wh(&af, st->arg);
+
+	buf->datas[0].chunk->offset = 0;
+	buf->datas[0].chunk->stride = st->stride;
+	buf->datas[0].chunk->size   = auframe_size(&af);
+
+	pw_stream_queue_buffer(st->stream, b);
+}

From a1a3e596da6f4fc0e11a4de77f9bf468a7ccb351 Mon Sep 17 00:00:00 2001
From: Christian Spielberger <c.spielberger@commend.com>
Date: Thu, 2 Feb 2023 10:57:15 +0100
Subject: [PATCH 02/11] cmake: repair FindPIPEWIRE.cmake

---
 cmake/FindPIPEWIRE.cmake | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/cmake/FindPIPEWIRE.cmake b/cmake/FindPIPEWIRE.cmake
index bd9b90181..20fa46a6f 100644
--- a/cmake/FindPIPEWIRE.cmake
+++ b/cmake/FindPIPEWIRE.cmake
@@ -8,3 +8,7 @@ if(NOT WIN32)
     find_package(PkgConfig)
     pkg_search_module(PIPEWIRE libpipewire-0.3)
 endif()
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(PIPEWIRE DEFAULT_MSG PIPEWIRE_INCLUDE_DIRS
+    PIPEWIRE_LIBRARIES)

From fe94c10108cb2beb9dbf72374ffbf9f7b6651907 Mon Sep 17 00:00:00 2001
From: Christian Spielberger <c.spielberger@commend.com>
Date: Thu, 2 Feb 2023 14:19:41 +0100
Subject: [PATCH 03/11] pipewire: set node latency and use chunk offset

---
 modules/pipewire/capture.c | 16 +++++++++++++---
 1 file changed, 13 insertions(+), 3 deletions(-)

diff --git a/modules/pipewire/capture.c b/modules/pipewire/capture.c
index 3e92d131c..697845e7d 100644
--- a/modules/pipewire/capture.c
+++ b/modules/pipewire/capture.c
@@ -58,6 +58,7 @@ int pw_capture_alloc(struct ausrc_st **stp, const struct ausrc *as,
 	struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer,
 							sizeof(buffer));
 	const char name[] = "baresip-capture";
+	char nlat[10];
 	int err = 0;
 
 	if (!stp || !as || !prm || !rh)
@@ -81,6 +82,7 @@ int pw_capture_alloc(struct ausrc_st **stp, const struct ausrc *as,
 	st->rh   = rh;
 	st->errh = errh;
 	st->arg  = arg;
+	re_snprintf(nlat, sizeof(nlat), "%u/1000", prm->ptime);
 
 	pw_thread_loop_lock (pw_loop_instance());
 	st->stream = pw_stream_new(pw_core_instance(), name,
@@ -88,6 +90,7 @@ int pw_capture_alloc(struct ausrc_st **stp, const struct ausrc *as,
 				     PW_KEY_MEDIA_TYPE, "Audio",
 				     PW_KEY_MEDIA_CATEGORY, "Capture",
 				     PW_KEY_MEDIA_ROLE, "Communication",
+				     PW_KEY_NODE_LATENCY, nlat,
 				     NULL));
 	if (!st->stream) {
 		err = errno;
@@ -135,7 +138,10 @@ static void on_process(void *arg)
 	struct ausrc_st *st = arg;
 	struct pw_buffer *b;
 	struct spa_buffer *buf;
+	struct spa_data *d;
 	struct auframe af;
+	uint32_t offs;
+	uint32_t size;
 
 	void *sampv;
 	size_t sampc;
@@ -145,11 +151,15 @@ static void on_process(void *arg)
 		warning("pipewire: out of buffers (%m)\n", errno);
 
 	buf = b->buffer;
-	sampv = buf->datas[0].data;
-	if (!sampv)
+	d = &buf->datas[0];
+
+	if (!d->data)
 		return;
 
-	sampc = buf->datas[0].chunk->size / st->sampsz;
+	offs  = SPA_MIN(d->chunk->offset, d->maxsize);
+	size  = SPA_MIN(d->chunk->size, d->maxsize - offs);
+	sampv = SPA_PTROFF(d->data, offs, void);
+	sampc = size / st->sampsz;
 
 	auframe_init(&af, st->prm.fmt, sampv, sampc,
 		     st->prm.srate, st->prm.ch);

From c3ae03b803a742d2816ab19825af578d2da7c290 Mon Sep 17 00:00:00 2001
From: Christian Spielberger <c.spielberger@commend.com>
Date: Thu, 2 Feb 2023 14:20:30 +0100
Subject: [PATCH 04/11] pipewire: playback cleanup

---
 modules/pipewire/playback.c | 15 +++++++++------
 1 file changed, 9 insertions(+), 6 deletions(-)

diff --git a/modules/pipewire/playback.c b/modules/pipewire/playback.c
index 94fceb1b0..012731570 100644
--- a/modules/pipewire/playback.c
+++ b/modules/pipewire/playback.c
@@ -133,6 +133,7 @@ static void on_process(void *arg)
 	struct auplay_st *st = arg;
 	struct pw_buffer *b;
 	struct spa_buffer *buf;
+	struct spa_data *d;
 	struct auframe af;
 	void *sampv;
 
@@ -141,11 +142,13 @@ static void on_process(void *arg)
 		warning("pipewire: out of buffers (%m)\n", errno);
 
 	buf = b->buffer;
-	sampv = buf->datas[0].data;
-	if (!sampv)
+	d = &buf->datas[0];
+
+	if (!d->data)
 		return;
 
-	if (buf->datas[0].maxsize < st->nbytes) {
+	sampv = d->data;
+	if (d->maxsize < st->nbytes) {
 		warning("pipewire: buffer to small\n");
 		return;
 	}
@@ -155,9 +158,9 @@ static void on_process(void *arg)
 
 	st->wh(&af, st->arg);
 
-	buf->datas[0].chunk->offset = 0;
-	buf->datas[0].chunk->stride = st->stride;
-	buf->datas[0].chunk->size   = auframe_size(&af);
+	d->chunk->offset = 0;
+	d->chunk->stride = st->stride;
+	d->chunk->size   = auframe_size(&af);
 
 	pw_stream_queue_buffer(st->stream, b);
 }

From 074b37357fbe98314094d2a37f226baa81e7d0b2 Mon Sep 17 00:00:00 2001
From: Christian Spielberger <c.spielberger@commend.com>
Date: Fri, 3 Feb 2023 08:19:30 +0100
Subject: [PATCH 05/11] pipewire: device selection

---
 modules/pipewire/capture.c  |   3 +-
 modules/pipewire/pipewire.c | 155 +++++++++++++++++++++++++++++++++---
 modules/pipewire/pipewire.h |   1 +
 modules/pipewire/playback.c |   3 +-
 4 files changed, 151 insertions(+), 11 deletions(-)

diff --git a/modules/pipewire/capture.c b/modules/pipewire/capture.c
index 697845e7d..36f6d5d90 100644
--- a/modules/pipewire/capture.c
+++ b/modules/pipewire/capture.c
@@ -90,6 +90,7 @@ int pw_capture_alloc(struct ausrc_st **stp, const struct ausrc *as,
 				     PW_KEY_MEDIA_TYPE, "Audio",
 				     PW_KEY_MEDIA_CATEGORY, "Capture",
 				     PW_KEY_MEDIA_ROLE, "Communication",
+				     PW_KEY_TARGET_OBJECT, dev,
 				     PW_KEY_NODE_LATENCY, nlat,
 				     NULL));
 	if (!st->stream) {
@@ -108,7 +109,7 @@ int pw_capture_alloc(struct ausrc_st **stp, const struct ausrc *as,
 
 	err = pw_stream_connect(st->stream,
 				PW_DIRECTION_INPUT,
-				PW_ID_ANY,
+				pw_device_id(dev),
 				PW_STREAM_FLAG_AUTOCONNECT |
 				PW_STREAM_FLAG_MAP_BUFFERS |
 				PW_STREAM_FLAG_RT_PROCESS,
diff --git a/modules/pipewire/pipewire.c b/modules/pipewire/pipewire.c
index c9e6aaff4..182420f8e 100644
--- a/modules/pipewire/pipewire.c
+++ b/modules/pipewire/pipewire.c
@@ -22,7 +22,16 @@
  */
 
 enum {
-	RECONN_DELAY = 1500,
+	RECONN_DELAY  = 1500,
+	DEV_HASH_SIZE = 16,
+};
+
+
+struct pw_dev {
+	struct le he;
+
+	char *node_name;
+	uint32_t id;
 };
 
 
@@ -30,19 +39,27 @@ struct pw_stat {
 	struct pw_thread_loop *loop;
 	struct pw_context *context;
 	struct pw_core *core;
+	struct pw_registry *registry;
+	struct spa_hook registry_listener;
+
+	struct auplay *auplay;
+	struct ausrc *ausrc;
+	struct hash *devices;
 };
 
 
 static struct pw_stat *d = NULL;
 
-static struct auplay *auplay = NULL;
-static struct ausrc *ausrc = NULL;
-
 
 static void destructor(void *arg)
 {
 	struct pw_stat *pw = arg;
 
+	mem_deref(pw->auplay);
+	mem_deref(pw->ausrc);
+	hash_flush(pw->devices);
+	mem_deref(pw->devices);
+
 	if (pw->core)
 		pw_core_disconnect(pw->core);
 
@@ -56,6 +73,105 @@ static void destructor(void *arg)
 }
 
 
+static void pw_dev_destructor(void *arg)
+{
+	struct pw_dev *pwd = arg;
+
+	mem_deref(pwd->node_name);
+}
+
+
+static int pw_dev_add(uint32_t id, const char *node_name)
+{
+	struct pw_dev *pwd;
+	int err;
+
+	pwd = mem_zalloc(sizeof(*pwd), pw_dev_destructor);
+	if (!pwd)
+		return ENOMEM;
+
+	pwd->id = id;
+	err = str_dup(&pwd->node_name, node_name);
+	if (err) {
+		mem_deref(pwd);
+		return ENOMEM;
+	}
+
+	hash_append(d->devices, hash_joaat_str(node_name), &pwd->he, pwd);
+	return 0;
+}
+
+
+static void registry_event_global(void *arg, uint32_t id,
+		uint32_t permissions, const char *type, uint32_t version,
+		const struct spa_dict *props)
+{
+	struct pw_stat *pw = arg;
+	const char *media_class;
+	const char *node_name;
+	(void)permissions;
+	(void)version;
+
+	if (str_cmp(type, PW_TYPE_INTERFACE_Node))
+		return;
+
+	media_class = spa_dict_lookup(props, PW_KEY_MEDIA_CLASS);
+	node_name = spa_dict_lookup(props, PW_KEY_NODE_NAME);
+	if (!str_cmp(media_class, "Audio/Source") && str_isset(node_name)) {
+		debug("pipewire: adding (%u) %s: \"%s\"\n",
+		      id, media_class, node_name);
+		mediadev_add(&pw->ausrc->dev_list, node_name);
+		(void)pw_dev_add(id, node_name);
+	}
+
+	if (!str_cmp(media_class, "Audio/Sink") && str_isset(node_name)) {
+		debug("pipewire: adding (%u) %s: \"%s\"\n",
+		      id, media_class, node_name);
+		mediadev_add(&pw->auplay->dev_list, node_name);
+		(void)pw_dev_add(id, node_name);
+	}
+}
+
+
+static const struct pw_registry_events registry_events = {
+	PW_VERSION_REGISTRY_EVENTS,
+	.global = registry_event_global,
+};
+
+
+struct pwd_cmp {
+	const char *node_name;
+};
+
+
+static bool pw_dev_cmp(struct le *le, void *arg)
+{
+	const struct pwd_cmp *cmp = arg;
+	const struct pw_dev *pwd = le->data;
+
+	return !str_cmp(pwd->node_name, cmp->node_name);
+}
+
+
+int pw_device_id(const char *node_name)
+{
+	struct le *le;
+	struct pw_dev *pwd;
+	struct pwd_cmp cmp;
+
+	cmp.node_name = node_name;
+
+	le = hash_lookup(d->devices, hash_joaat_str(node_name),
+			  pw_dev_cmp, &cmp);
+
+	if (!le || !le->data)
+		return PW_ID_ANY;
+
+	pwd = le->data;
+	return pwd->id;
+}
+
+
 static struct pw_stat *pw_stat_alloc(void)
 {
 	struct pw_stat *pw;
@@ -93,6 +209,29 @@ static struct pw_stat *pw_stat_alloc(void)
 }
 
 
+static int pw_start_registry_scan(struct pw_stat *pw)
+{
+	int err;
+
+	pw->registry = pw_core_get_registry(pw->core, PW_VERSION_REGISTRY,
+					0 /* user_data size */);
+
+	if (!pw->registry)
+		return errno;
+
+	err  = hash_alloc(&pw->devices, DEV_HASH_SIZE);
+	if (err)
+		return err;
+
+	pw_thread_loop_lock (pw_loop_instance());
+	spa_zero(pw->registry_listener);
+	pw_registry_add_listener(pw->registry, &pw->registry_listener,
+				 &registry_events, pw);
+	pw_thread_loop_unlock(pw_loop_instance());
+	return 0;
+}
+
+
 struct pw_core *pw_core_instance(void)
 {
 	if (!d)
@@ -133,20 +272,18 @@ static int module_init(void)
 	if (!d)
 		return errno;
 
-	err  = auplay_register(&auplay, baresip_auplayl(),
+	err  = auplay_register(&d->auplay, baresip_auplayl(),
 			       "pipewire", pw_playback_alloc);
-	err |= ausrc_register(&ausrc, baresip_ausrcl(),
+	err |= ausrc_register(&d->ausrc, baresip_ausrcl(),
 			      "pipewire", pw_capture_alloc);
 
+	err |= pw_start_registry_scan(d);
 	return err;
 }
 
 
 static int module_close(void)
 {
-	auplay = mem_deref(auplay);
-	ausrc  = mem_deref(ausrc);
-
 	d = mem_deref(d);
 	pw_deinit();
 	return 0;
diff --git a/modules/pipewire/pipewire.h b/modules/pipewire/pipewire.h
index 0a93aaf52..4d4c9787b 100644
--- a/modules/pipewire/pipewire.h
+++ b/modules/pipewire/pipewire.h
@@ -7,6 +7,7 @@
 int aufmt_to_pw_format(enum aufmt fmt);
 struct pw_core *pw_core_instance(void);
 struct pw_thread_loop *pw_loop_instance(void);
+int pw_device_id(const char *node_name);
 
 int pw_playback_alloc(struct auplay_st **stp,
 		      const struct auplay *ap,
diff --git a/modules/pipewire/playback.c b/modules/pipewire/playback.c
index 012731570..24b4f2c3a 100644
--- a/modules/pipewire/playback.c
+++ b/modules/pipewire/playback.c
@@ -86,6 +86,7 @@ int pw_playback_alloc(struct auplay_st **stp, const struct auplay *ap,
 				     PW_KEY_MEDIA_TYPE, "Audio",
 				     PW_KEY_MEDIA_CATEGORY, "Playback",
 				     PW_KEY_MEDIA_ROLE, "Communication",
+				     PW_KEY_TARGET_OBJECT, dev,
 				     NULL));
 	if (!st->stream) {
 		err = errno;
@@ -103,7 +104,7 @@ int pw_playback_alloc(struct auplay_st **stp, const struct auplay *ap,
 
 	err = pw_stream_connect(st->stream,
 				PW_DIRECTION_OUTPUT,
-				PW_ID_ANY,
+				pw_device_id(dev),
 				PW_STREAM_FLAG_AUTOCONNECT |
 				PW_STREAM_FLAG_MAP_BUFFERS |
 				PW_STREAM_FLAG_RT_PROCESS,

From 018d8113bcc3029a7e0ec71a5c722c6d2e3b3615 Mon Sep 17 00:00:00 2001
From: Christian Spielberger <c.spielberger@commend.com>
Date: Wed, 1 Mar 2023 09:30:21 +0100
Subject: [PATCH 06/11] pipewire: set _XOPEN_SOURCE=700

---
 modules/pipewire/CMakeLists.txt | 1 +
 1 file changed, 1 insertion(+)

diff --git a/modules/pipewire/CMakeLists.txt b/modules/pipewire/CMakeLists.txt
index 8673873af..bb2ad8f2a 100644
--- a/modules/pipewire/CMakeLists.txt
+++ b/modules/pipewire/CMakeLists.txt
@@ -14,4 +14,5 @@ target_link_libraries(${PROJECT_NAME} PRIVATE ${PIPEWIRE_LIBRARIES})
 target_compile_options(${PROJECT_NAME} PRIVATE
   -Wno-pedantic
   -Wno-bad-function-cast
+  -D_XOPEN_SOURCE=700
 )

From 978787dee5b3fd8dc50100224ff0ef34a4ef7ce6 Mon Sep 17 00:00:00 2001
From: Christian Spielberger <c.spielberger@commend.com>
Date: Wed, 1 Mar 2023 09:34:19 +0100
Subject: [PATCH 07/11] pipewire: replace _XOPEN_SOURCE by _GNU_SOURCE

---
 modules/pipewire/CMakeLists.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/modules/pipewire/CMakeLists.txt b/modules/pipewire/CMakeLists.txt
index bb2ad8f2a..50a4befef 100644
--- a/modules/pipewire/CMakeLists.txt
+++ b/modules/pipewire/CMakeLists.txt
@@ -14,5 +14,5 @@ target_link_libraries(${PROJECT_NAME} PRIVATE ${PIPEWIRE_LIBRARIES})
 target_compile_options(${PROJECT_NAME} PRIVATE
   -Wno-pedantic
   -Wno-bad-function-cast
-  -D_XOPEN_SOURCE=700
+  -D_GNU_SOURCE
 )

From 87b11d9ba47832865d2fb906f50467d6c7ce75f4 Mon Sep 17 00:00:00 2001
From: Christian Spielberger <c.spielberger@commend.com>
Date: Wed, 1 Mar 2023 10:28:30 +0100
Subject: [PATCH 08/11] pipewire: thread safe stream termination

---
 modules/pipewire/capture.c  | 5 ++++-
 modules/pipewire/playback.c | 5 ++++-
 2 files changed, 8 insertions(+), 2 deletions(-)

diff --git a/modules/pipewire/capture.c b/modules/pipewire/capture.c
index 36f6d5d90..8d20eec52 100644
--- a/modules/pipewire/capture.c
+++ b/modules/pipewire/capture.c
@@ -42,9 +42,11 @@ static void ausrc_destructor(void *arg)
 {
 	struct ausrc_st *st = arg;
 
+	pw_thread_loop_lock (pw_loop_instance());
 	st->rh = NULL;
 	st->errh = NULL;
 	pw_stream_destroy(st->stream);
+	pw_thread_loop_unlock(pw_loop_instance());
 }
 
 
@@ -168,7 +170,8 @@ static void on_process(void *arg)
 	af.timestamp = st->samps * AUDIO_TIMEBASE /
 		       (st->prm.srate * st->prm.ch);
 	st->samps += sampc;
-	st->rh(&af, st->arg);
+	if (st->rh)
+		st->rh(&af, st->arg);
 
 	pw_stream_queue_buffer(st->stream, b);
 }
diff --git a/modules/pipewire/playback.c b/modules/pipewire/playback.c
index 24b4f2c3a..55aff42c6 100644
--- a/modules/pipewire/playback.c
+++ b/modules/pipewire/playback.c
@@ -40,8 +40,10 @@ static void auplay_destructor(void *arg)
 {
 	struct auplay_st *st = arg;
 
+	pw_thread_loop_lock (pw_loop_instance());
 	st->wh = NULL;
 	pw_stream_destroy(st->stream);
+	pw_thread_loop_unlock(pw_loop_instance());
 }
 
 
@@ -157,7 +159,8 @@ static void on_process(void *arg)
 	auframe_init(&af, st->prm.fmt, sampv, st->sampc,
 		     st->prm.srate, st->prm.ch);
 
-	st->wh(&af, st->arg);
+	if (st->wh)
+		st->wh(&af, st->arg);
 
 	d->chunk->offset = 0;
 	d->chunk->stride = st->stride;

From eab7291993107826adb4756b08009e0188a1d89a Mon Sep 17 00:00:00 2001
From: Christian Spielberger <c.spielberger@commend.com>
Date: Wed, 1 Mar 2023 10:29:15 +0100
Subject: [PATCH 09/11] pipewire: remove unused errh

---
 modules/pipewire/capture.c | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/modules/pipewire/capture.c b/modules/pipewire/capture.c
index 8d20eec52..00f6f5e99 100644
--- a/modules/pipewire/capture.c
+++ b/modules/pipewire/capture.c
@@ -21,7 +21,6 @@ struct ausrc_st {
 	struct ausrc_prm prm;
 	ausrc_read_h *rh;
 	struct spa_hook listener;
-	ausrc_error_h *errh;
 
 	size_t sampsz;
 	uint64_t samps;
@@ -44,7 +43,6 @@ static void ausrc_destructor(void *arg)
 
 	pw_thread_loop_lock (pw_loop_instance());
 	st->rh = NULL;
-	st->errh = NULL;
 	pw_stream_destroy(st->stream);
 	pw_thread_loop_unlock(pw_loop_instance());
 }
@@ -62,6 +60,7 @@ int pw_capture_alloc(struct ausrc_st **stp, const struct ausrc *as,
 	const char name[] = "baresip-capture";
 	char nlat[10];
 	int err = 0;
+	(void)errh;
 
 	if (!stp || !as || !prm || !rh)
 		return EINVAL;
@@ -82,7 +81,6 @@ int pw_capture_alloc(struct ausrc_st **stp, const struct ausrc *as,
 	st->samps  = 0;
 
 	st->rh   = rh;
-	st->errh = errh;
 	st->arg  = arg;
 	re_snprintf(nlat, sizeof(nlat), "%u/1000", prm->ptime);
 

From 62f397052a2da2c967cd9100e6df66bb34a68c25 Mon Sep 17 00:00:00 2001
From: Christian Spielberger <c.spielberger@commend.com>
Date: Thu, 2 Mar 2023 07:18:30 +0100
Subject: [PATCH 10/11] config: add pipewire as option for DEFAULT_AUDIO_DEVICE

---
 docs/examples/config | 1 +
 src/config.c         | 9 ++++++++-
 2 files changed, 9 insertions(+), 1 deletion(-)

diff --git a/docs/examples/config b/docs/examples/config
index 153dc81ec..19ab2174b 100644
--- a/docs/examples/config
+++ b/docs/examples/config
@@ -118,6 +118,7 @@ module			auresamp.so
 # Audio driver Modules
 module			alsa.so
 #module			pulse.so
+#module			pipewire.so
 #module			jack.so
 #module			portaudio.so
 #module			aubridge.so
diff --git a/src/config.c b/src/config.c
index a9e332b9e..ca66fd880 100644
--- a/src/config.c
+++ b/src/config.c
@@ -1001,13 +1001,20 @@ int config_write_template(const char *file, const struct config *cfg)
 #elif defined (WIN32)
 	(void)re_fprintf(f, "module\t\t\t" "winwave" MOD_EXT "\n");
 #else
-	if (!strncmp(default_audio_device(), "pulse", 5)) {
+	if (!strncmp(default_audio_device(), "pipewire", 8)) {
+		(void)re_fprintf(f, "#module\t\t\t" "alsa" MOD_EXT "\n");
+		(void)re_fprintf(f, "#module\t\t\t" "pulse" MOD_EXT "\n");
+		(void)re_fprintf(f, "module\t\t\t" "pipewire" MOD_EXT "\n");
+	}
+	else if (!strncmp(default_audio_device(), "pulse", 5)) {
 		(void)re_fprintf(f, "#module\t\t\t" "alsa" MOD_EXT "\n");
 		(void)re_fprintf(f, "module\t\t\t" "pulse" MOD_EXT "\n");
+		(void)re_fprintf(f, "#module\t\t\t" "pipewire" MOD_EXT "\n");
 	}
 	else {
 		(void)re_fprintf(f, "module\t\t\t" "alsa" MOD_EXT "\n");
 		(void)re_fprintf(f, "#module\t\t\t" "pulse" MOD_EXT"\n");
+		(void)re_fprintf(f, "#module\t\t\t" "pipewire" MOD_EXT "\n");
 	}
 #endif
 	(void)re_fprintf(f, "#module\t\t\t" "jack" MOD_EXT "\n");

From 7a72698b35b97ddd78ed2370920b818fdddd2120 Mon Sep 17 00:00:00 2001
From: Christian Spielberger <c.spielberger@commend.com>
Date: Mon, 17 Apr 2023 16:09:15 +0200
Subject: [PATCH 11/11] pipewire: disable stderr buffering after pw_init()

---
 modules/pipewire/pipewire.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/modules/pipewire/pipewire.c b/modules/pipewire/pipewire.c
index 182420f8e..eb80ad16b 100644
--- a/modules/pipewire/pipewire.c
+++ b/modules/pipewire/pipewire.c
@@ -265,6 +265,7 @@ static int module_init(void)
 	int err = 0;
 
 	pw_init(NULL, NULL);
+	setvbuf(stderr, NULL, _IONBF, 0);
 	info("pipewire: headers %s library %s \n",
 	     pw_get_headers_version(), pw_get_library_version());