diff --git a/src/daemon/media-session.d/media-session.conf b/src/daemon/media-session.d/media-session.conf index 9d6d9ce92..1b8bfcdb1 100644 --- a/src/daemon/media-session.d/media-session.conf +++ b/src/daemon/media-session.d/media-session.conf @@ -30,6 +30,7 @@ modules = { #default-nodes # restore default nodes #default-profile # restore default profiles #default-routes # restore default route + #streams-follow-default # move streams when default changes #alsa-seq # alsa seq midi support #alsa-monitor # alsa udev detection #bluez5 # bluetooth support @@ -53,5 +54,6 @@ modules = { with-audio bluez5 restore-stream + streams-follow-default ] } diff --git a/src/examples/media-session/media-session.c b/src/examples/media-session/media-session.c index b82116922..0dcf7204d 100644 --- a/src/examples/media-session/media-session.c +++ b/src/examples/media-session/media-session.c @@ -84,6 +84,7 @@ int sm_default_nodes_start(struct sm_media_session *sess); int sm_default_profile_start(struct sm_media_session *sess); int sm_default_routes_start(struct sm_media_session *sess); int sm_restore_stream_start(struct sm_media_session *sess); +int sm_streams_follow_default_start(struct sm_media_session *sess); int sm_alsa_midi_start(struct sm_media_session *sess); int sm_v4l2_monitor_start(struct sm_media_session *sess); int sm_libcamera_monitor_start(struct sm_media_session *sess); @@ -2227,6 +2228,7 @@ static const struct { { "default-profile", "restore default profiles", sm_default_profile_start, NULL }, { "default-routes", "restore default route", sm_default_routes_start, NULL }, { "restore-stream", "restore stream settings", sm_restore_stream_start, NULL }, + { "streams-follow-default", "move streams when default changes", sm_streams_follow_default_start, NULL }, { "alsa-seq", "alsa seq midi support", sm_alsa_midi_start, NULL }, { "alsa-monitor", "alsa card udev detection", sm_alsa_monitor_start, NULL }, { "v4l2", "video for linux udev detection", sm_v4l2_monitor_start, NULL }, diff --git a/src/examples/media-session/policy-node.c b/src/examples/media-session/policy-node.c index ed2466438..4b28e197d 100644 --- a/src/examples/media-session/policy-node.c +++ b/src/examples/media-session/policy-node.c @@ -64,6 +64,8 @@ struct impl { uint32_t default_audio_sink; uint32_t default_audio_source; uint32_t default_video_source; + + bool streams_follow_default; }; struct node { @@ -560,6 +562,7 @@ static int rescan_node(struct impl *impl, struct node *n) struct node *peer; struct sm_object *obj; uint32_t path_id; + bool follows_default; if (!n->active) { pw_log_debug(NAME " %p: node %d is not active", impl, n->id); @@ -580,14 +583,23 @@ static int rescan_node(struct impl *impl, struct node *n) return 0; } - if (n->peer != NULL) { + info = n->obj->info; + props = info->props; + + str = spa_dict_lookup(props, PW_KEY_NODE_DONT_RECONNECT); + reconnect = str ? !pw_properties_parse_bool(str) : true; + + follows_default = (impl->streams_follow_default && + n->type == NODE_TYPE_STREAM && + reconnect && + n->obj->target_node == NULL && + spa_dict_lookup(props, PW_KEY_NODE_TARGET) == NULL); + + if (n->peer != NULL && !follows_default) { pw_log_debug(NAME " %p: node %d is already linked", impl, n->id); return 0; } - info = n->obj->info; - props = info->props; - if ((str = spa_dict_lookup(props, PW_KEY_STREAM_DONT_REMIX)) != NULL) n->dont_remix = pw_properties_parse_bool(str); @@ -627,9 +639,6 @@ static int rescan_node(struct impl *impl, struct node *n) find.target = n; find.exclusive = exclusive; - str = spa_dict_lookup(props, PW_KEY_NODE_DONT_RECONNECT); - reconnect = str ? !pw_properties_parse_bool(str) : true; - /* we always honour the target node asked for by the client */ path_id = SPA_ID_INVALID; if ((str = spa_dict_lookup(props, PW_KEY_NODE_TARGET)) != NULL) @@ -637,8 +646,22 @@ static int rescan_node(struct impl *impl, struct node *n) if (path_id == SPA_ID_INVALID && n->obj->target_node != NULL) path_id = find_device_for_name(impl, n->obj->target_node); - pw_log_info("trying to link node %d exclusive:%d reconnect:%d target:%d", n->id, - exclusive, reconnect, path_id); + pw_log_info("trying to link node %d exclusive:%d reconnect:%d target:%d follows-default:%d", n->id, + exclusive, reconnect, path_id, follows_default); + + if (n->peer != NULL) { + spa_list_for_each(peer, &impl->node_list, link) + find_node(&find, peer); + + if (follows_default && find.node != NULL && find.node != n->peer) { + pw_log_debug(NAME " %p: node %d follows default, changed (%d -> %d)", impl, n->id, + n->peer->id, find.node->id); + unlink_nodes(n, n->peer); + } else { + pw_log_debug(NAME " %p: node %d already linked (not changing)", impl, n->id); + return 0; + } + } if (path_id != SPA_ID_INVALID) { pw_log_debug(NAME " %p: target:%d", impl, path_id); @@ -660,8 +683,11 @@ static int rescan_node(struct impl *impl, struct node *n) path_id, reconnect); } if (path_id == SPA_ID_INVALID && (reconnect || n->connect_count == 0)) { - spa_list_for_each(peer, &impl->node_list, link) - find_node(&find, peer); + if (find.node == NULL) + spa_list_for_each(peer, &impl->node_list, link) + find_node(&find, peer); + } else { + find.node = NULL; } if (find.node == NULL) { @@ -751,44 +777,6 @@ static int do_move_node(struct node *n, struct node *src, struct node *dst) return 0; } -static int move_node(struct impl *impl, uint32_t source, uint32_t target) -{ - struct node *n, *src_node, *dst_node; - const char *str; - - if (source == SPA_ID_INVALID || target == SPA_ID_INVALID) - return 0; - - /* find source and dest node */ - if ((src_node = find_node_by_id(impl, source)) == NULL) - return -ENOENT; - if ((dst_node = find_node_by_id(impl, target)) == NULL) - return -ENOENT; - - if (src_node == dst_node) - return 0; - - pw_log_info("move %d -> %d", src_node->id, dst_node->id); - - /* unlink all nodes from source and link to target */ - spa_list_for_each(n, &impl->node_list, link) { - struct pw_node_info *info; - - if (n->peer != src_node) - continue; - - if ((info = n->obj->info) == NULL) - continue; - - if ((str = spa_dict_lookup(info->props, PW_KEY_NODE_DONT_RECONNECT)) != NULL && - pw_properties_parse_bool(str)) - continue; - - do_move_node(n, src_node, dst_node); - } - return 0; -} - static int handle_move(struct impl *impl, struct node *src_node, struct node *dst_node) { const char *str; @@ -825,18 +813,26 @@ static int metadata_property(void *object, uint32_t subject, uint32_t val = (key && value) ? (uint32_t)atoi(value) : SPA_ID_INVALID; if (subject == PW_ID_CORE) { + bool changed = false; + if (key == NULL || strcmp(key, "default.audio.sink") == 0) { - move_node(impl, impl->default_audio_sink, val); + if (impl->default_audio_sink != val) + changed = true; impl->default_audio_sink = val; } if (key == NULL || strcmp(key, "default.audio.source") == 0) { - move_node(impl, impl->default_audio_source, val); + if (impl->default_audio_source != val) + changed = true; impl->default_audio_source = val; } if (key == NULL || strcmp(key, "default.video.source") == 0) { - move_node(impl, impl->default_video_source, val); + if (impl->default_video_source != val) + changed = true; impl->default_video_source = val; } + + if (changed && impl->streams_follow_default) + sm_media_session_schedule_rescan(impl->session); } else { if (val != SPA_ID_INVALID && strcmp(key, "target.node") == 0) { struct node *src_node, *dst_node; @@ -859,6 +855,7 @@ static const struct pw_metadata_events metadata_events = { int sm_policy_node_start(struct sm_media_session *session) { struct impl *impl; + const char *flag; impl = calloc(1, sizeof(struct impl)); if (impl == NULL) @@ -872,6 +869,9 @@ int sm_policy_node_start(struct sm_media_session *session) impl->default_audio_source = SPA_ID_INVALID; impl->default_video_source = SPA_ID_INVALID; + flag = pw_properties_get(session->props, NAME ".streams-follow-default"); + impl->streams_follow_default = (flag != NULL && pw_properties_parse_bool(flag)); + spa_list_init(&impl->node_list); sm_media_session_add_listener(impl->session, diff --git a/src/examples/media-session/streams-follow-default.c b/src/examples/media-session/streams-follow-default.c new file mode 100644 index 000000000..471143932 --- /dev/null +++ b/src/examples/media-session/streams-follow-default.c @@ -0,0 +1,46 @@ +/* PipeWire + * + * Copyright © 2021 Pauli Virtanen + * + * 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. + */ + +/* + * Instruct policy-node to move streams when the default sink/sources (either explicitly set + * via metadata, or determined from priority) changes, and the stream does not have an + * explicitly specified target node. + * + * This is done by just setting a session property flag, and policy-node does the rest. + */ + +#include "config.h" + +#include "pipewire/pipewire.h" +#include "extensions/metadata.h" + +#include "media-session.h" + +#define KEY_NAME "policy-node.streams-follow-default" + +int sm_streams_follow_default_start(struct sm_media_session *session) +{ + pw_properties_set(session->props, KEY_NAME, "true"); + return 0; +} diff --git a/src/examples/meson.build b/src/examples/meson.build index 84fae1c94..409ad8fbf 100644 --- a/src/examples/meson.build +++ b/src/examples/meson.build @@ -85,6 +85,7 @@ if alsa_dep.found() 'media-session/restore-stream.c', 'media-session/policy-ep.c', 'media-session/policy-node.c', + 'media-session/streams-follow-default.c', 'media-session/v4l2-monitor.c', 'media-session/v4l2-endpoint.c', 'media-session/libcamera-monitor.c',