mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2025-11-02 09:01:50 -05:00
module-raop: add match rules for discover
Useful for selecting only ip4 streams or for setting up the password.
This commit is contained in:
parent
9e56fae236
commit
8167e1b9be
2 changed files with 160 additions and 66 deletions
|
|
@ -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` = <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=<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 <wim.taymans@gmail.com>" },
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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=<ip address of host> ] " \
|
||||
"[ raop.hostname=<name of host> ] " \
|
||||
"[ raop.port=<remote port> ] " \
|
||||
"[ raop.name=<name of host> ] " \
|
||||
"[ raop.hostname=<hostname of host> ] " \
|
||||
"[ raop.transport=<transport, default:udp> ] " \
|
||||
"[ raop.encryption.type=<encryption, default:none> ] " \
|
||||
"[ 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");
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue