From fac80e76d20f06800631863e47fff1d71631eea2 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 12 Jan 2021 20:38:47 +0100 Subject: [PATCH] media-session: add conf file for v4l2 and bluez as well --- src/daemon/media-session.d/alsa-monitor.conf | 2 +- src/daemon/media-session.d/bluez-monitor.conf | 48 ++++++ src/daemon/media-session.d/v4l2-monitor.conf | 45 ++++++ src/examples/media-session/alsa-monitor.c | 117 +-------------- src/examples/media-session/bluez-monitor.c | 29 +++- src/examples/media-session/match-rules.c | 139 ++++++++++++++++++ src/examples/media-session/media-session.h | 3 + src/examples/media-session/v4l2-monitor.c | 24 ++- src/examples/meson.build | 1 + 9 files changed, 292 insertions(+), 116 deletions(-) create mode 100644 src/daemon/media-session.d/bluez-monitor.conf create mode 100644 src/daemon/media-session.d/v4l2-monitor.conf create mode 100644 src/examples/media-session/match-rules.c diff --git a/src/daemon/media-session.d/alsa-monitor.conf b/src/daemon/media-session.d/alsa-monitor.conf index c55c5c045..f53cb740f 100644 --- a/src/daemon/media-session.d/alsa-monitor.conf +++ b/src/daemon/media-session.d/alsa-monitor.conf @@ -46,6 +46,7 @@ rules = [ #node.nick = null #priority.driver = 100 #priority.session = 100 + #node.pause-on-idle = true #resample.quality = 4 #channelmix.normalize = false #channelmix.mix-lfe = false @@ -56,4 +57,3 @@ rules = [ } } ] - diff --git a/src/daemon/media-session.d/bluez-monitor.conf b/src/daemon/media-session.d/bluez-monitor.conf new file mode 100644 index 000000000..f483d903e --- /dev/null +++ b/src/daemon/media-session.d/bluez-monitor.conf @@ -0,0 +1,48 @@ +# bluez-monitor config file +properties = { +} + +rules = [ + # an array of matches/actions to evaluate + { + # rules for matching a device or node. It is an array of + # properties that all need to match the regexp. If any of the + # matches work, the actions are executed for the object. + matches = [ + { + # this matches all cards + device.name = ~bluez_card.* + } + ] + actions = { + # actions can update properties on the matched object. + update-props = { + #device.nick = "My Device" + } + } + } + { + matches = [ + { + # matches all sinks + node.name = ~bluez_input.* + } + { + # matches all sources + node.name = ~bluez_output.* + } + ] + actions = { + update-props = { + #node.nick = "My Node" + #node.nick = null + #priority.driver = 100 + #priority.session = 100 + #node.pause-on-idle = true + #resample.quality = 4 + #channelmix.normalize = false + #channelmix.mix-lfe = false + } + } + } +] diff --git a/src/daemon/media-session.d/v4l2-monitor.conf b/src/daemon/media-session.d/v4l2-monitor.conf new file mode 100644 index 000000000..89aa09cf5 --- /dev/null +++ b/src/daemon/media-session.d/v4l2-monitor.conf @@ -0,0 +1,45 @@ +# v4l2-monitor config file +properties = { +} + +rules = [ + # an array of matches/actions to evaluate + { + # rules for matching a device or node. It is an array of + # properties that all need to match the regexp. If any of the + # matches work, the actions are executed for the object. + matches = [ + { + # this matches all devices + device.name = ~v4l2_device.* + } + ] + actions = { + # actions can update properties on the matched object. + update-props = { + #device.nick = "My Device" + } + } + } + { + matches = [ + { + # matches all sinks + node.name = ~api.v4l2.sink.* + } + { + # matches all sources + node.name = ~api.v4l2.source.* + } + ] + actions = { + update-props = { + #node.nick = "My Node" + #node.nick = null + #priority.driver = 100 + #priority.session = 100 + #node.pause-on-idle = true + } + } + } +] diff --git a/src/examples/media-session/alsa-monitor.c b/src/examples/media-session/alsa-monitor.c index 31a3ad3d4..139044521 100644 --- a/src/examples/media-session/alsa-monitor.c +++ b/src/examples/media-session/alsa-monitor.c @@ -202,118 +202,13 @@ static const struct sm_object_methods node_methods = { .release = node_release, }; -static bool find_match(struct spa_json *arr, struct pw_properties *props) -{ - struct spa_json it[1]; - - while (spa_json_enter_object(arr, &it[0]) > 0) { - char key[256], val[1024]; - const char *str, *value; - int match = 0, fail = 0; - int len; - - while (spa_json_get_string(&it[0], key, sizeof(key)-1) > 0) { - bool success = false; - - if ((len = spa_json_next(&it[0], &value)) <= 0) - break; - - if (key[0] == '#') - continue; - - str = pw_properties_get(props, key); - - if (spa_json_is_null(value, len)) { - success = str == NULL; - } else { - spa_json_parse_string(value, SPA_MIN(len, 1024), val); - value = val; - len = strlen(val); - } - if (str != NULL) { - if (value[0] == '~') { - regex_t preg; - if (regcomp(&preg, value+1, REG_EXTENDED | REG_NOSUB) == 0) { - if (regexec(&preg, str, 0, NULL, 0) == 0) - success = true; - regfree(&preg); - } - } else if (strncmp(str, value, len) == 0) { - success = true; - } - } - if (success) { - match++; - pw_log_debug("'%s' match '%s' < > '%.*s'", key, str, len, value); - } - else - fail++; - } - if (match > 0 && fail == 0) - return true; - } - return false; -} - -static int apply_matches(struct impl *impl, struct pw_properties *props) -{ - const char *rules, *val; - struct spa_json it[4], actions;; - - if ((rules = pw_properties_get(impl->conf, "rules")) == NULL) - return 0; - - spa_json_init(&it[0], rules, strlen(rules)); - if (spa_json_enter_array(&it[0], &it[1]) < 0) - return 0; - - while (spa_json_enter_object(&it[1], &it[2]) > 0) { - char key[64]; - bool have_match = false, have_actions = false; - - while (spa_json_get_string(&it[2], key, sizeof(key)-1) > 0) { - if (strcmp(key, "matches") == 0) { - if (spa_json_enter_array(&it[2], &it[3]) < 0) - break; - - have_match = find_match(&it[3], props); - } - else if (strcmp(key, "actions") == 0) { - if (spa_json_enter_object(&it[2], &actions) > 0) - have_actions = true; - } - else if (spa_json_next(&it[2], &val) <= 0) - break; - } - if (!have_match || !have_actions) - continue; - - while (spa_json_get_string(&actions, key, sizeof(key)-1) > 0) { - int len; - pw_log_debug("action %s", key); - if (strcmp(key, "update-props") == 0) { - if ((len = spa_json_next(&actions, &val)) <= 0) - continue; - if (!spa_json_is_object(val, len)) - continue; - len = spa_json_container_len(&actions, val, len); - - pw_properties_update_string(props, val, len); - } - else if (spa_json_next(&actions, &val) <= 0) - break; - } - } - return 1; -} - static struct node *alsa_create_node(struct device *device, uint32_t id, const struct spa_device_object_info *info) { struct node *node; struct impl *impl = device->impl; int res; - const char *dev, *subdev, *stream, *profile, *profile_desc; + const char *dev, *subdev, *stream, *profile, *profile_desc, *rules; int i, priority; pw_log_debug("new node %u", id); @@ -443,7 +338,8 @@ static struct node *alsa_create_node(struct device *device, uint32_t id, node->device = device; node->id = id; - apply_matches(impl, node->props); + if ((rules = pw_properties_get(impl->conf, "rules")) != NULL) + sm_media_session_match_rules(rules, strlen(rules), node->props); node->snode = sm_media_session_create_node(impl->session, "adapter", @@ -946,7 +842,7 @@ static struct device *alsa_create_device(struct impl *impl, uint32_t id, { struct device *device; int res; - const char *str, *card; + const char *str, *card, *rules; pw_log_debug("new device %u", id); @@ -971,7 +867,8 @@ static struct device *alsa_create_device(struct impl *impl, uint32_t id, device->pending_profile = 1; spa_list_append(&impl->device_list, &device->link); - apply_matches(impl, device->props); + if ((rules = pw_properties_get(impl->conf, "rules")) != NULL) + sm_media_session_match_rules(rules, strlen(rules), device->props); str = pw_properties_get(device->props, "api.alsa.use-acp"); device->use_acp = str ? pw_properties_parse_bool(str) : true; @@ -1101,7 +998,7 @@ int sm_alsa_monitor_start(struct sm_media_session *session) impl->conf = pw_properties_new(NULL, NULL); if (impl->conf == NULL) { free(impl); - return -ENOMEM; + return -errno; } if ((res = sm_media_session_load_conf(impl->session, diff --git a/src/examples/media-session/bluez-monitor.c b/src/examples/media-session/bluez-monitor.c index 46154b2e6..d583d170c 100644 --- a/src/examples/media-session/bluez-monitor.c +++ b/src/examples/media-session/bluez-monitor.c @@ -47,7 +47,8 @@ #include "pipewire/impl.h" #include "media-session.h" -#define NAME "bluez5-monitor" +#define NAME "bluez5-monitor" +#define SESSION_CONF "bluez-monitor.conf" struct device; @@ -91,6 +92,8 @@ struct impl { struct sm_media_session *session; struct spa_hook session_listener; + struct pw_properties *conf; + struct spa_handle *handle; struct spa_device *monitor; @@ -127,7 +130,7 @@ static struct node *bluez5_create_node(struct device *device, uint32_t id, struct pw_context *context = impl->session->context; struct pw_impl_factory *factory; int res; - const char *prefix, *str, *profile; + const char *prefix, *str, *profile, *rules; pw_log_debug("new node %u", id); @@ -164,9 +167,9 @@ static struct node *bluez5_create_node(struct device *device, uint32_t id, str = pw_properties_get(device->props, SPA_KEY_DEVICE_NAME); if (strstr(info->factory_name, "sink") != NULL) - prefix = "bluez_sink"; + prefix = "bluez_input"; else if (strstr(info->factory_name, "source") != NULL) - prefix = "bluez_source"; + prefix = "bluez_output"; else prefix = info->factory_name; @@ -177,6 +180,9 @@ static struct node *bluez5_create_node(struct device *device, uint32_t id, node->device = device; node->id = id; + if ((rules = pw_properties_get(impl->conf, "rules")) != NULL) + sm_media_session_match_rules(rules, strlen(rules), node->props); + factory = pw_context_find_factory(context, "adapter"); if (factory == NULL) { pw_log_error("no adapter factory found"); @@ -370,6 +376,7 @@ static struct device *bluez5_create_device(struct impl *impl, uint32_t id, struct spa_handle *handle; int res; void *iface; + const char *rules; pw_log_debug("new device %u", id); @@ -412,6 +419,8 @@ static struct device *bluez5_create_device(struct impl *impl, uint32_t id, spa_list_init(&device->node_list); + if ((rules = pw_properties_get(impl->conf, "rules")) != NULL) + sm_media_session_match_rules(rules, strlen(rules), device->props); sm_object_add_listener(&device->sdevice->obj, &device->listener, @@ -482,6 +491,7 @@ static void session_destroy(void *data) spa_hook_remove(&impl->session_listener); spa_hook_remove(&impl->listener); pw_unload_spa_handle(impl->handle); + pw_properties_free(impl->conf); free(impl); } @@ -515,12 +525,21 @@ int sm_bluez5_monitor_start(struct sm_media_session *session) res = -errno; goto out_unload; } + impl->conf = pw_properties_new(NULL, NULL); + if (impl->conf == NULL) { + res = -errno; + goto out_free; + } impl->session = session; impl->handle = handle; impl->monitor = iface; spa_list_init(&impl->device_list); + if ((res = sm_media_session_load_conf(impl->session, + SESSION_CONF, impl->conf)) < 0) + pw_log_info("can't load "SESSION_CONF" config: %s", spa_strerror(res)); + spa_device_add_listener(impl->monitor, &impl->listener, &bluez5_enum_callbacks, impl); @@ -529,6 +548,8 @@ int sm_bluez5_monitor_start(struct sm_media_session *session) return 0; +out_free: + free(impl); out_unload: pw_unload_spa_handle(handle); out: diff --git a/src/examples/media-session/match-rules.c b/src/examples/media-session/match-rules.c new file mode 100644 index 000000000..c6a382ede --- /dev/null +++ b/src/examples/media-session/match-rules.c @@ -0,0 +1,139 @@ +/* PipeWire + * + * Copyright © 2020 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include + +#include "config.h" + +#include + +#include +#include "media-session.h" + +static bool find_match(struct spa_json *arr, struct pw_properties *props) +{ + struct spa_json it[1]; + + while (spa_json_enter_object(arr, &it[0]) > 0) { + char key[256], val[1024]; + const char *str, *value; + int match = 0, fail = 0; + int len; + + while (spa_json_get_string(&it[0], key, sizeof(key)-1) > 0) { + bool success = false; + + if ((len = spa_json_next(&it[0], &value)) <= 0) + break; + + if (key[0] == '#') + continue; + + str = pw_properties_get(props, key); + + if (spa_json_is_null(value, len)) { + success = str == NULL; + } else { + spa_json_parse_string(value, SPA_MIN(len, 1024), val); + value = val; + len = strlen(val); + } + if (str != NULL) { + if (value[0] == '~') { + regex_t preg; + if (regcomp(&preg, value+1, REG_EXTENDED | REG_NOSUB) == 0) { + if (regexec(&preg, str, 0, NULL, 0) == 0) + success = true; + regfree(&preg); + } + } else if (strncmp(str, value, len) == 0) { + success = true; + } + } + if (success) { + match++; + pw_log_debug("'%s' match '%s' < > '%.*s'", key, str, len, value); + } + else + fail++; + } + if (match > 0 && fail == 0) + return true; + } + return false; +} + +int sm_media_session_match_rules(const char *rules, size_t size, struct pw_properties *props) +{ + const char *val; + struct spa_json it[4], actions;; + + spa_json_init(&it[0], rules, size); + if (spa_json_enter_array(&it[0], &it[1]) < 0) + return 0; + + while (spa_json_enter_object(&it[1], &it[2]) > 0) { + char key[64]; + bool have_match = false, have_actions = false; + + while (spa_json_get_string(&it[2], key, sizeof(key)-1) > 0) { + if (strcmp(key, "matches") == 0) { + if (spa_json_enter_array(&it[2], &it[3]) < 0) + break; + + have_match = find_match(&it[3], props); + } + else if (strcmp(key, "actions") == 0) { + if (spa_json_enter_object(&it[2], &actions) > 0) + have_actions = true; + } + else if (spa_json_next(&it[2], &val) <= 0) + break; + } + if (!have_match || !have_actions) + continue; + + while (spa_json_get_string(&actions, key, sizeof(key)-1) > 0) { + int len; + pw_log_debug("action %s", key); + if (strcmp(key, "update-props") == 0) { + if ((len = spa_json_next(&actions, &val)) <= 0) + continue; + if (!spa_json_is_object(val, len)) + continue; + len = spa_json_container_len(&actions, val, len); + + pw_properties_update_string(props, val, len); + } + else if (spa_json_next(&actions, &val) <= 0) + break; + } + } + return 1; +} diff --git a/src/examples/media-session/media-session.h b/src/examples/media-session/media-session.h index f1bb1a2e9..154a1f57f 100644 --- a/src/examples/media-session/media-session.h +++ b/src/examples/media-session/media-session.h @@ -297,6 +297,9 @@ int sm_media_session_load_state(struct sm_media_session *sess, int sm_media_session_save_state(struct sm_media_session *sess, const char *name, const char *prefix, const struct pw_properties *props); +int sm_media_session_match_rules(const char *rules, size_t size, + struct pw_properties *props); + #ifdef __cplusplus } #endif diff --git a/src/examples/media-session/v4l2-monitor.c b/src/examples/media-session/v4l2-monitor.c index 3e0fe9170..559fa35a2 100644 --- a/src/examples/media-session/v4l2-monitor.c +++ b/src/examples/media-session/v4l2-monitor.c @@ -43,6 +43,8 @@ #include "media-session.h" +#define SESSION_CONF "v4l2-monitor.conf" + struct device; struct node { @@ -83,6 +85,8 @@ struct impl { struct sm_media_session *session; struct spa_hook session_listener; + struct pw_properties *conf; + struct spa_handle *handle; struct spa_device *monitor; struct spa_hook listener; @@ -123,7 +127,7 @@ static struct node *v4l2_create_node(struct device *dev, uint32_t id, struct node *node; struct impl *impl = dev->impl; int i, res; - const char *str, *d; + const char *str, *d, *rules; pw_log_debug("new node %u", id); @@ -168,6 +172,9 @@ static struct node *v4l2_create_node(struct device *dev, uint32_t id, pw_properties_set(node->props, PW_KEY_FACTORY_NAME, info->factory_name); + if ((rules = pw_properties_get(impl->conf, "rules")) != NULL) + sm_media_session_match_rules(rules, strlen(rules), dev->props); + node->impl = impl; node->device = dev; node->id = id; @@ -383,6 +390,7 @@ static struct device *v4l2_create_device(struct impl *impl, uint32_t id, struct spa_handle *handle; int res; void *iface; + const char *rules; pw_log_debug("new device %u", id); @@ -418,6 +426,9 @@ static struct device *v4l2_create_device(struct impl *impl, uint32_t id, dev->props = pw_properties_new_dict(info->props); v4l2_update_device_props(dev); + if ((rules = pw_properties_get(impl->conf, "rules")) != NULL) + sm_media_session_match_rules(rules, strlen(rules), dev->props); + dev->sdevice = sm_media_session_export_device(impl->session, &dev->props->dict, dev->device); @@ -485,6 +496,7 @@ static void session_destroy(void *data) spa_hook_remove(&impl->session_listener); spa_hook_remove(&impl->listener); pw_unload_spa_handle(impl->handle); + pw_properties_free(impl->conf); free(impl); } @@ -504,6 +516,11 @@ int sm_v4l2_monitor_start(struct sm_media_session *sess) if (impl == NULL) return -errno; + impl->conf = pw_properties_new(NULL, NULL); + if (impl->conf == NULL) { + res = -errno; + goto out_free; + } impl->session = sess; impl->handle = pw_context_load_spa_handle(context, SPA_NAME_API_V4L2_ENUM_UDEV, NULL); @@ -517,9 +534,14 @@ int sm_v4l2_monitor_start(struct sm_media_session *sess) goto out_unload; } + impl->monitor = iface; spa_list_init(&impl->device_list); + if ((res = sm_media_session_load_conf(impl->session, + SESSION_CONF, impl->conf)) < 0) + pw_log_info("can't load "SESSION_CONF" config: %s", spa_strerror(res)); + spa_device_add_listener(impl->monitor, &impl->listener, &v4l2_udev_callbacks, impl); diff --git a/src/examples/meson.build b/src/examples/meson.build index 7e196ae6e..84fae1c94 100644 --- a/src/examples/meson.build +++ b/src/examples/meson.build @@ -79,6 +79,7 @@ if alsa_dep.found() 'media-session/default-routes.c', 'media-session/media-session.c', 'media-session/session-manager.c', + 'media-session/match-rules.c', 'media-session/metadata.c', 'media-session/stream-endpoint.c', 'media-session/restore-stream.c',