diff --git a/src/modules/module-raop-discover.c b/src/modules/module-raop-discover.c index 38f8176ca..5fdf29251 100644 --- a/src/modules/module-raop-discover.c +++ b/src/modules/module-raop-discover.c @@ -31,19 +31,56 @@ * Automatically creates RAOP (Airplay) sink devices based on zeroconf * information. * - * This module will load module-raop-sink for each discovered sink - * with the right parameters. + * This module will load module-raop-sink for each announced stream that matches + * the rule with the create-stream action. + * + * If no stream.rules are given, it will create a sink for all announced + * streams. * * ## Module Options * - * This module has no options. + * Options specific to the behavior of this module + * + * - `stream.rules` = : match rules, use create-stream actions. See + * \ref page_module_raop_sink for module properties. * * ## Example configuration * *\code{.unparsed} * context.modules = [ * { name = libpipewire-raop-discover - * args = { } + * args = { + * stream.rules = [ + * { matches = [ + * { raop.ip = "~.*" + * #raop.ip.version = 4 | 6 + * #raop.ip.version = 4 + * #raop.port = 1000 + * #raop.name = "" + * #raop.hostname = "" + * #raop.domain = "" + * #raop.device = "" + * #raop.transport = "udp" | "tcp" + * #raop.encryption.type = "RSA" | "auth_setup" | "none" + * #raop.audio.codec = "PCM" | "ALAC" | "AAC" | "AAC-ELD" + * #audio.channels = 2 + * #audio.format = "S16" | "S24" | "S32" + * #audio.rate = 44100 + * #device.model = "" + * } + * ] + * actions = { + * create-stream = { + * #raop.password = "" + * stream.props = { + * #target.object = "" + * #media.class = "Audio/Sink" + * } + * } + * } + * } + * ] + * } * } * ] *\endcode @@ -58,7 +95,10 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); #define PW_LOG_TOPIC_DEFAULT mod_topic -#define MODULE_USAGE " " +#define MODULE_USAGE "stream.rules=, use create-stream actions " + +#define DEFAULT_CREATE_RULES \ + "[ { matches = [ { raop.ip = \"~.*\" } ] actions = { create-stream = { } } } ] " static const struct spa_dict_item module_props[] = { { PW_KEY_MODULE_AUTHOR, "Wim Taymans " }, @@ -88,6 +128,7 @@ struct tunnel_info { AvahiIfIndex interface; AvahiProtocol protocol; const char *name; + const char *host_name; const char *type; const char *domain; }; @@ -114,6 +155,7 @@ static struct tunnel *make_tunnel(struct impl *impl, const struct tunnel_info *i t->info.interface = info->interface; t->info.protocol = info->protocol; t->info.name = strdup(info->name); + t->info.host_name = strdup(info->host_name); t->info.type = strdup(info->type); t->info.domain = strdup(info->domain); spa_list_append(&impl->tunnel_list, &t->link); @@ -255,6 +297,7 @@ static void submodule_destroy(void *data) spa_hook_remove(&t->module_listener); free((char *) t->info.name); + free((char *) t->info.host_name); free((char *) t->info.type); free((char *) t->info.domain); @@ -266,20 +309,84 @@ static const struct pw_impl_module_events submodule_events = { .destroy = submodule_destroy, }; +struct match_info { + struct impl *impl; + struct pw_properties *props; + struct tunnel_info *tinfo; + bool matched; +}; + +static int create_stream(struct impl *impl, struct pw_properties *props, + struct tunnel_info *tinfo) +{ + FILE *f; + char *args; + size_t size; + int res = 0; + struct pw_impl_module *mod; + struct tunnel *t; + + if ((f = open_memstream(&args, &size)) == NULL) { + res = -errno; + pw_log_error("Can't open memstream: %m"); + goto done; + } + + fprintf(f, "{"); + pw_properties_serialize_dict(f, &props->dict, 0); + fprintf(f, "}"); + fclose(f); + + pw_log_info("loading module args:'%s'", args); + mod = pw_context_load_module(impl->context, + "libpipewire-module-raop-sink", + args, NULL); + free(args); + + if (mod == NULL) { + res = -errno; + pw_log_error("Can't load module: %m"); + goto done; + } + + t = make_tunnel(impl, tinfo); + if (t == NULL) { + res = -errno; + pw_log_error("Can't make tunnel: %m"); + pw_impl_module_destroy(mod); + goto done; + } + + pw_impl_module_add_listener(mod, &t->module_listener, &submodule_events, t); + + t->module = mod; +done: + return res; +} + +static int rule_matched(void *data, const char *location, const char *action, + const char *str, size_t len) +{ + struct match_info *i = data; + int res = 0; + + i->matched = true; + if (spa_streq(action, "create-stream")) { + pw_properties_update_string(i->props, str, len); + create_stream(i->impl, i->props, i->tinfo); + } + return res; +} + static void resolver_cb(AvahiServiceResolver *r, AvahiIfIndex interface, AvahiProtocol protocol, AvahiResolverEvent event, const char *name, const char *type, const char *domain, const char *host_name, const AvahiAddress *a, uint16_t port, AvahiStringList *txt, AvahiLookupResultFlags flags, void *userdata) { struct impl *impl = userdata; - struct tunnel *t; struct tunnel_info tinfo; const char *str; AvahiStringList *l; - FILE *f; - char *args; - size_t size; - struct pw_impl_module *mod; struct pw_properties *props = NULL; char at[AVAHI_ADDRESS_STR_MAX]; int ipv; @@ -291,6 +398,7 @@ static void resolver_cb(AvahiServiceResolver *r, AvahiIfIndex interface, AvahiPr } tinfo = TUNNEL_INFO(.interface = interface, .protocol = protocol, + .host_name = host_name, .name = name, .type = type, .domain = domain); @@ -302,21 +410,14 @@ static void resolver_cb(AvahiServiceResolver *r, AvahiIfIndex interface, AvahiPr } avahi_address_snprint(at, sizeof(at), a); + ipv = protocol == AVAHI_PROTO_INET ? 4 : 6; pw_properties_setf(props, "raop.ip", "%s", at); + pw_properties_setf(props, "raop.ip.version", "%d", ipv); pw_properties_setf(props, "raop.port", "%u", port); + pw_properties_setf(props, "raop.name", "%s", name); pw_properties_setf(props, "raop.hostname", "%s", host_name); - - ipv = protocol == AVAHI_PROTO_INET ? 4 : 6; - if ((str = strstr(name, "@"))) { - str++; - if (strlen(str) > 0) - name = str; - } - pw_properties_setf(props, PW_KEY_NODE_DESCRIPTION, - "RAOP on %s (IPv%d)", name, ipv); - pw_properties_setf(props, PW_KEY_NODE_NAME, "raop_sink.%s.%s.ipv%d", - name, host_name, ipv); + pw_properties_setf(props, "raop.domain", "%s", domain); for (l = txt; l; l = l->next) { char *key, *value; @@ -329,45 +430,24 @@ static void resolver_cb(AvahiServiceResolver *r, AvahiIfIndex interface, AvahiPr avahi_free(value); } + if ((str = pw_properties_get(impl->properties, "stream.rules")) == NULL) + str = DEFAULT_CREATE_RULES; + if (str != NULL) { + struct match_info minfo = { + .impl = impl, + .props = props, + .tinfo = &tinfo, + }; + pw_conf_match_rules(str, strlen(str), NAME, &props->dict, + rule_matched, &minfo); - if ((f = open_memstream(&args, &size)) == NULL) { - pw_log_error("Can't open memstream: %m"); - goto done; + if (!minfo.matched) + pw_log_info("unmatched service found %s", str); } - fprintf(f, "{"); - pw_properties_serialize_dict(f, &props->dict, 0); - fprintf(f, " stream.props = {"); - fprintf(f, " }"); - fprintf(f, "}"); - fclose(f); - - pw_properties_free(props); - - pw_log_info("loading module args:'%s'", args); - mod = pw_context_load_module(impl->context, - "libpipewire-module-raop-sink", - args, NULL); - free(args); - - if (mod == NULL) { - pw_log_error("Can't load module: %m"); - goto done; - } - - t = make_tunnel(impl, &tinfo); - if (t == NULL) { - pw_log_error("Can't make tunnel: %m"); - pw_impl_module_destroy(mod); - goto done; - } - - pw_impl_module_add_listener(mod, &t->module_listener, &submodule_events, t); - - t->module = mod; - done: avahi_service_resolver_free(r); + pw_properties_free(props); } diff --git a/src/modules/module-raop-sink.c b/src/modules/module-raop-sink.c index c99376596..f614aba69 100644 --- a/src/modules/module-raop-sink.c +++ b/src/modules/module-raop-sink.c @@ -57,8 +57,9 @@ * Options specific to the behavior of this module * * - `raop.ip`: The ip address of the remote end. - * - `raop.hostname`: The hostname of the remote end. * - `raop.port`: The port of the remote end. + * - `raop.name`: The name of the remote end. + * - `raop.hostname`: The hostname of the remote end. * - `raop.transport`: The data transport to use, one of "udp" or "tcp". Defaults * to "udp". * - `raop.encryption.type`: The encryption type to use. One of "none", "RSA" or @@ -89,8 +90,9 @@ * args = { * # Set the remote address to tunnel to * raop.ip = "127.0.0.1" - * raop.hostname = "my-raop-device" * raop.port = 8190 + * raop.name = "my-raop-device" + * raop.hostname = "My Service" * #raop.transport = "udp" * raop.encryption.type = "RSA" * #raop.audio.codec = "PCM" @@ -144,8 +146,9 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); #define DEFAULT_LATENCY 22050 #define MODULE_USAGE "[ raop.ip= ] " \ - "[ raop.hostname= ] " \ "[ raop.port= ] " \ + "[ raop.name= ] " \ + "[ raop.hostname= ] " \ "[ raop.transport= ] " \ "[ raop.encryption.type= ] " \ "[ raop.audio.codec=PCM ] " \ @@ -1522,8 +1525,7 @@ static int rtsp_do_teardown(struct impl *impl) if (!impl->ready) return 0; - return pw_rtsp_client_send(impl->rtsp, "TEARDOWN", NULL, - NULL, NULL, rtsp_teardown_reply, impl); + return rtsp_send(impl, "TEARDOWN", NULL, NULL, rtsp_teardown_reply); } static void stream_param_changed(void *data, uint32_t id, const struct spa_pod *param) @@ -1759,7 +1761,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) struct pw_context *context = pw_impl_module_get_context(module); struct pw_properties *props = NULL; struct impl *impl; - const char *str; + const char *str, *name, *hostname, *ipv; int res; PW_LOG_TOPIC_INIT(mod_topic); @@ -1806,13 +1808,25 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) if (pw_properties_get(props, PW_KEY_MEDIA_CLASS) == NULL) pw_properties_set(props, PW_KEY_MEDIA_CLASS, "Audio/Sink"); - if (pw_properties_get(props, PW_KEY_NODE_NAME) == NULL) - pw_properties_setf(props, PW_KEY_NODE_NAME, "raop_output.%s", - pw_properties_get(props, "raop.hostname")); + if ((name = pw_properties_get(props, "raop.name")) == NULL) + name = "RAOP"; + + if ((str = strstr(name, "@"))) { + str++; + if (strlen(str) > 0) + name = str; + } + if ((ipv = pw_properties_get(props, "raop.ip.version")) == NULL) + ipv = "4"; + if ((hostname = pw_properties_get(props, "raop.hostname")) == NULL) + hostname = name; if (pw_properties_get(props, PW_KEY_NODE_DESCRIPTION) == NULL) - pw_properties_set(props, PW_KEY_NODE_DESCRIPTION, - pw_properties_get(props, PW_KEY_NODE_NAME)); + pw_properties_setf(props, PW_KEY_NODE_DESCRIPTION, + "%s (IPv%s)", name, ipv); + if (pw_properties_get(props, PW_KEY_NODE_NAME) == NULL) + pw_properties_setf(props, PW_KEY_NODE_NAME, "raop_sink.%s.ipv%s", + hostname, ipv); if (pw_properties_get(props, PW_KEY_NODE_LATENCY) == NULL) pw_properties_set(props, PW_KEY_NODE_LATENCY, "352/44100");