summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorThomas Weißschuh <thomas@t-8ch.de>2021-12-13 00:10:11 +0100
committerPhilip Langdale <github.philipl@overt.org>2022-02-06 22:44:40 -0800
commitb7a71ea70671f3cc991f5d90a411f017caf573c7 (patch)
tree69d8316c8cb402b9130da731859c981fba308ae1
parentc72b897e9cb60a3dd4a4fd35442ba21ea7ef6163 (diff)
downloadmpv-b7a71ea70671f3cc991f5d90a411f017caf573c7.tar.bz2
mpv-b7a71ea70671f3cc991f5d90a411f017caf573c7.tar.xz
ao_pipewire: add support for device selection
-rw-r--r--audio/out/ao_pipewire.c202
1 files changed, 188 insertions, 14 deletions
diff --git a/audio/out/ao_pipewire.c b/audio/out/ao_pipewire.c
index 9621bc5229..6749270e18 100644
--- a/audio/out/ao_pipewire.c
+++ b/audio/out/ao_pipewire.c
@@ -21,6 +21,7 @@
*/
#include <pipewire/pipewire.h>
+#include <pipewire/global.h>
#include <spa/param/audio/format-utils.h>
#include <spa/param/props.h>
#include <math.h>
@@ -44,6 +45,8 @@
struct priv {
struct pw_thread_loop *loop;
struct pw_stream *stream;
+ struct pw_core *core;
+ struct spa_hook stream_listener;
int buffer_msec;
bool muted;
@@ -104,6 +107,7 @@ static enum spa_audio_channel mp_speaker_id_to_spa(struct ao *ao, enum mp_speake
};
}
+
static void on_process(void *userdata)
{
struct ao *ao = userdata;
@@ -227,12 +231,141 @@ static void uninit(struct ao *ao)
if (p->stream)
pw_stream_destroy(p->stream);
p->stream = NULL;
+ if (p->core)
+ pw_core_disconnect(p->core);
+ p->core = NULL;
if (p->loop)
pw_thread_loop_destroy(p->loop);
p->loop = NULL;
pw_deinit();
}
+struct registry_event_global_ctx {
+ struct ao *ao;
+ void (*sink_cb) (struct ao *ao, uint32_t id, const struct spa_dict *props, void *sink_cb_ctx);
+ void *sink_cb_ctx;
+};
+
+static void for_each_sink_registry_event_global(void *data, uint32_t id,
+ uint32_t permissions, const
+ char *type, uint32_t version,
+ const struct spa_dict *props)
+{
+ struct registry_event_global_ctx *ctx = data;
+
+ if (strcmp(type, PW_TYPE_INTERFACE_Node) != 0)
+ return;
+
+ if (!props)
+ return;
+
+ const char *class = spa_dict_lookup(props, PW_KEY_MEDIA_CLASS);
+ if (!class || strcmp(class, "Audio/Sink") != 0)
+ return;
+
+ ctx->sink_cb(ctx->ao, id, props, ctx->sink_cb_ctx);
+}
+
+
+static const struct pw_registry_events for_each_sink_registry_events = {
+ .version = PW_VERSION_REGISTRY_EVENTS,
+ .global = for_each_sink_registry_event_global,
+};
+
+static void for_each_sink_done(void *data, uint32_t it, int seq)
+{
+ struct pw_thread_loop *loop = data;
+ pw_thread_loop_signal(loop, false);
+}
+
+static const struct pw_core_events for_each_sink_core_events = {
+ .version = PW_VERSION_CORE_EVENTS,
+ .done = for_each_sink_done,
+};
+
+static void for_each_sink(struct ao *ao, void (cb) (struct ao *ao, uint32_t id,
+ const struct spa_dict *props, void *ctx), void *cb_ctx)
+{
+ struct priv *priv = ao->priv;
+ struct pw_registry *registry;
+ struct spa_hook core_listener;
+
+ pw_thread_loop_lock(priv->loop);
+
+ pw_core_add_listener(priv->core, &core_listener, &for_each_sink_core_events, priv->loop);
+ registry = pw_core_get_registry(priv->core, PW_VERSION_REGISTRY, 0);
+ pw_core_sync(priv->core, 0, 0);
+
+ struct spa_hook registry_listener;
+ struct registry_event_global_ctx revents_ctx = {
+ .ao = ao,
+ .sink_cb = cb,
+ .sink_cb_ctx = cb_ctx,
+ };
+ pw_registry_add_listener(registry, &registry_listener, &for_each_sink_registry_events, &revents_ctx);
+ pw_thread_loop_wait(priv->loop);
+
+
+ spa_hook_remove(&core_listener);
+ spa_hook_remove(&registry_listener);
+ pw_proxy_destroy((struct pw_proxy *)registry);
+
+ pw_thread_loop_unlock(priv->loop);
+}
+
+
+static void get_target_id_cb(struct ao *ao, uint32_t id, const struct spa_dict *props, void *ctx)
+{
+ int32_t *target_id = ctx;
+
+ const char *name = spa_dict_lookup(props, PW_KEY_NODE_NAME);
+ if (!name)
+ return;
+
+ if (strcmp(name, ao->device) == 0) {
+ *target_id = id;
+ }
+}
+
+static uint32_t get_target_id(struct ao *ao)
+{
+ uint32_t target_id = 0;
+
+ if (ao->device == NULL)
+ return PW_ID_ANY;
+
+ for_each_sink(ao, get_target_id_cb, &target_id);
+
+ return target_id;
+}
+
+static int pipewire_init_boilerplate(struct ao *ao)
+{
+ struct priv *p = ao->priv;
+ struct pw_context *context;
+
+ pw_init(NULL, NULL);
+
+
+ p->loop = pw_thread_loop_new("ao-pipewire", NULL);
+ if (p->loop == NULL)
+ return -1;
+
+ if (pw_thread_loop_start(p->loop) < 0)
+ return -1;
+
+ context = pw_context_new(pw_thread_loop_get_loop(p->loop), NULL, 0);
+ if (!context)
+ return -1;
+
+ p->core = pw_context_connect(context, NULL, 0);
+ if (!p->core)
+ return -1;
+
+ return 0;
+}
+
+
static int init(struct ao *ao)
{
struct priv *p = ao->priv;
@@ -252,6 +385,9 @@ static int init(struct ao *ao)
NULL
);
+ if (pipewire_init_boilerplate(ao) < 0)
+ goto error;
+
ao->device_buffer = p->buffer_msec * ao->samplerate / 1000;
pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%d/%d", ao->device_buffer, ao->samplerate);
@@ -282,33 +418,42 @@ static int init(struct ao *ao)
ao->sstride = ao->channels.num * af_fmt_to_bytes(ao->format);
}
- pw_init(NULL, NULL);
+ pw_thread_loop_lock(p->loop);
- p->loop = pw_thread_loop_new("ao-pipewire", NULL);
- if (p->loop == NULL)
+ p->stream = pw_stream_new(
+ p->core,
+ "audio-src",
+ props);
+ if (p->stream == NULL) {
+ pw_thread_loop_unlock(p->loop);
goto error;
+ }
- p->stream = pw_stream_new_simple(
- pw_thread_loop_get_loop(p->loop),
- "audio-src",
- props,
- &stream_events,
- ao);
- if (p->stream == NULL)
+ pw_stream_add_listener(p->stream,
+ &p->stream_listener,
+ &stream_events, ao);
+
+ pw_thread_loop_unlock(p->loop);
+
+ uint32_t target_id = get_target_id(ao);
+ if (target_id == 0)
goto error;
+ pw_thread_loop_lock(p->loop);
+
if (pw_stream_connect(p->stream,
PW_DIRECTION_OUTPUT,
- PW_ID_ANY,
+ target_id,
PW_STREAM_FLAG_AUTOCONNECT |
PW_STREAM_FLAG_INACTIVE |
PW_STREAM_FLAG_MAP_BUFFERS |
PW_STREAM_FLAG_RT_PROCESS,
- params, 1) < 0)
+ params, 1) < 0) {
+ pw_thread_loop_unlock(p->loop);
goto error;
+ }
- if (pw_thread_loop_start(p->loop) < 0)
- goto error;
+ pw_thread_loop_unlock(p->loop);
return 0;
@@ -389,6 +534,33 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg)
}
}
+static void add_device_to_list(struct ao *ao, uint32_t id, const struct spa_dict *props, void *ctx)
+{
+ struct ao_device_list *list = ctx;
+ const char *name = spa_dict_lookup(props, PW_KEY_NODE_NAME);
+
+ if (!name)
+ return;
+
+ const char *description = spa_dict_lookup(props, PW_KEY_NODE_DESCRIPTION);
+
+ ao_device_list_add(list, ao, &(struct ao_device_desc){name, description});
+}
+
+static void list_devs(struct ao *ao, struct ao_device_list *list)
+{
+ // we are not using hotplug_{,un}init() because the AO core will only call
+ // the hotplug functions of a single AO. That will probably be ao_pulse.
+ if (pipewire_init_boilerplate(ao) < 0)
+ return;
+
+ ao_device_list_add(list, ao, &(struct ao_device_desc){});
+
+ for_each_sink(ao, add_device_to_list, list);
+
+ uninit(ao);
+}
+
#define OPT_BASE_STRUCT struct priv
const struct ao_driver audio_out_pipewire = {
@@ -402,6 +574,8 @@ const struct ao_driver audio_out_pipewire = {
.control = control,
+ .list_devs = list_devs,
+
.priv_size = sizeof(struct priv),
.priv_defaults = &(const struct priv)
{