2 * module-agl-audio -- PulseAudio module for providing audio routing support
3 * (forked from "module-murphy-ivi" - https://github.com/otcshare )
4 * Copyright (c) 2012, Intel Corporation.
5 * Copyright (c) 2016, IoT.bzh
7 * This program is free software; you can redistribute it and/or modify it
8 * under the terms and conditions of the GNU Lesser General Public License,
9 * version 2.1, as published by the Free Software Foundation.
11 * This program is distributed in the hope it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.
14 * See the GNU Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program; if not, write to the
18 * Free Software Foundation, Inc., 51 Franklin St - Fifth Floor, Boston,
22 #include <pulsecore/pulsecore-config.h> /* required for "core-util.h" */
23 #include <pulsecore/core-util.h> /* requred for "pa_streq" */
24 #include <pulsecore/device-port.h> /* required for "card.h" */
25 #include <pulsecore/card.h> /* for "struct pa_card", "struct pa_card_profile" */
33 #define MAX_CARD_TARGET 4 /* max number of managed sinks/sources per card */
34 #define MAX_NAME_LENGTH 256 /* max sink/source name length */
36 static void handle_alsa_card (struct userdata *, pa_card *);
37 static void handle_alsa_card_sinks_and_sources (struct userdata *, pa_card *, agl_node *, const char *);
38 static void get_sinks_and_sources_from_profile (pa_card_profile *, char **, char **, char *, int);
39 static char *get_sink_or_source_from_str (char **, int);
40 static void handle_card_ports (struct userdata *, agl_node *, pa_card *, pa_card_profile *);
41 static const char *node_key (struct userdata *, agl_direction,
42 void *, pa_device_port *, char *, size_t);
43 static agl_node *create_node (struct userdata *, agl_node *, bool *);
45 struct agl_discover *agl_discover_init (struct userdata *u)
47 agl_discover *discover = pa_xnew0 (agl_discover, 1);
50 discover->selected = true;
51 discover->nodes.byname = pa_hashmap_new (pa_idxset_string_hash_func,
52 pa_idxset_string_compare_func);
53 discover->nodes.byptr = pa_hashmap_new (pa_idxset_trivial_hash_func,
54 pa_idxset_trivial_compare_func);
59 void agl_discover_done (struct userdata *u)
61 agl_discover *discover;
65 if (u && (discover = u->discover)) {
66 /*PA_HASHMAP_FOREACH(node, discover->nodes.byname, state) {
67 agl_node_destroy(u, node);
69 pa_hashmap_free (discover->nodes.byname);
70 pa_hashmap_free (discover->nodes.byptr);
76 void agl_discover_add_card (struct userdata *u, pa_card *card)
83 if (!(bus = agl_utils_get_card_bus (card))) {
84 pa_log_debug ("ignoring card '%s' due to lack of '%s' property",
85 agl_utils_get_card_name (card), PA_PROP_DEVICE_BUS);
89 if (pa_streq(bus, "pci") || pa_streq(bus, "usb") || pa_streq(bus, "platform")) {
90 pa_log_debug ("adding card '%s' thanks to its '%s' property",
91 agl_utils_get_card_name (card), PA_PROP_DEVICE_BUS);
92 pa_log_debug ("card type is '%s'", bus);
93 handle_alsa_card (u, card);
96 /* this never happens, because "agl_utils_get_card_bus()" never returns "bluetooth",
97 * but have this here as a reminder */
99 else if (pa_streq(bus, "bluetooth")) {
100 handle_bluetooth_card(u, card);
105 pa_log_debug ("ignoring card '%s' due to unsupported bus type '%s'",
106 agl_utils_get_card_name (card), bus);
109 void agl_discover_remove_card (struct userdata *u, pa_card *card)
112 agl_discover *discover;
118 pa_assert_se (discover = u->discover);
120 if (!(bus = agl_utils_get_card_bus(card)))
123 /*PA_HASHMAP_FOREACH(node, discover->nodes.byname, state) {
124 if (node->implement == agl_device &&
125 node->pacard.index == card->index)
127 if (pa_streq(bus, "pci") || pa_streq(bus, "usb") || pa_streq(bus, "platform"))
128 agl_constrain_destroy (u, node->paname);
130 destroy_node(u, node);
133 /* this never happens, because "agl_utils_get_card_bus()" never returns "bluetooth",
134 * but have this here as a reminder */
136 if (pa_streq(bus, "bluetooth"))
137 agl_constrain_destroy(u, card->name);
141 void agl_discover_add_sink (struct userdata *u, pa_sink *sink, bool route)
146 agl_discover *discover;
150 pa_source *null_source;
154 pa_assert_se (core = u->core);
155 pa_assert_se (discover = u->discover);
157 module = sink->module;
161 /* helper function verifying that sink direction is input/output */
162 key = node_key (u, agl_output, sink, NULL, kbf, sizeof(kbf));
164 pa_log_debug ("Sink key: %s", key);
165 node = agl_discover_find_node_by_key (u, key);
166 if (!node) { /* ALWAYS NULL, IF CALL "handle_card_ports" FROM "handle_alsa_card" !!! */
167 if (u->state.profile)
168 pa_log_debug ("can't find node for sink (key '%s')", key);
169 else { /* how do we get here ? not in initial setup */
170 u->state.sink = sink->index;
171 pa_log_debug ("BUG ! Did you call handle_card_ports() ?");
175 pa_log_debug("node for '%s' found (key %s). Updating with sink data",
176 node->paname, node->key);
177 node->paidx = sink->index;
178 node->available = true;
179 agl_discover_add_node_to_ptr_hash (u, sink, node);
182 /* loopback part : it is a device node, use "module-loopback" to make its */
183 if (node->implement == agl_device) {
184 null_source = agl_utils_get_null_source (u);
186 pa_log ("Can't load loopback module: no initial null source");
195 static void handle_alsa_card (struct userdata *u, pa_card *card)
198 const char *cnam; /* PulseAudio name */
199 const char *cid; /* short PulseAudio name (with "alsa_name." stripped) */
200 const char *alsanam; /* ALSA name */
201 const char *udd; /* managed by udev ("1" = yes) */
203 memset (&data, 0, sizeof(data));
204 data.zone = agl_utils_get_zone (card->proplist, NULL);
206 data.amid = AM_ID_INVALID;
207 data.implement = agl_device; /* this node is a physical device */
208 data.paidx = PA_IDXSET_INVALID;
209 data.stamp = agl_utils_get_stamp (); /* each time incremented by one */
211 cnam = agl_utils_get_card_name (card); /* PulseAudio name */
212 cid = cnam + 10; /* PulseAudio short name, with "alsa_card." prefix removed */
213 alsanam = pa_proplist_gets (card->proplist, "alsa.card_name"); /* ALSA name */
214 udd = pa_proplist_gets (card->proplist, "module-udev-detect.discovered");
216 data.amdescr = (char *)alsanam;
217 data.pacard.index = card->index;
219 pa_log_debug ("Sound card zone: %s", data.zone);
220 pa_log_debug ("Sound card stamp: %d", data.stamp);
221 pa_log_debug ("PulseAudio card name: %s", cnam);
222 pa_log_debug ("PulseAudio short card name: %s", cid);
223 pa_log_debug ("ALSA card name: %s", alsanam);
225 pa_log_debug ("ALSA card detected by udev: %s", udd);
227 /* WITH THE TIZEN MODULE, ONLY UDEV-MANAGED CARDS ARE ACCEPTED
228 * NO MATTER, TREAT STATIC CARDS THE SAME WAY HERE.. */
229 /*if (!udd || (udd && !pa_streq(udd, "1"))
230 pa_log_debug ("Card not accepted, not managed by udev\n");*/
232 handle_alsa_card_sinks_and_sources (u, card, &data, cid);
235 static void handle_alsa_card_sinks_and_sources (struct userdata *u, pa_card *card, agl_node *data, const char *cardid)
237 agl_discover *discover; /* discovery restrictions (max channels...) */
238 pa_card_profile *prof;
240 char *sinks[MAX_CARD_TARGET+1]; /* discovered sinks array */
241 char *sources[MAX_CARD_TARGET+1]; /* discovered sources array */
242 char namebuf[MAX_NAME_LENGTH+1]; /* discovered sink/source name buf.*/
244 char paname[MAX_NAME_LENGTH+1];
245 char amname[MAX_NAME_LENGTH+1];
249 pa_assert (card->profiles);
250 pa_assert_se (discover = u->discover);
252 alsanam = pa_proplist_gets (card->proplist, "alsa.card_name");
253 data->paname = paname;
254 data->amname = amname;
255 data->amdescr = (char *)alsanam;
256 data->pacard.index = card->index;
258 PA_HASHMAP_FOREACH(prof, card->profiles, state) {
259 /* TODO : skip selected profile here */
261 /* skip profiles withoutqx sinks/sources */
262 if (!prof->n_sinks && !prof->n_sources)
264 /* skip profiles with too few/many channels */
266 (prof->max_sink_channels < discover->chmin ||
267 prof->max_sink_channels > discover->chmax))
269 if (prof->n_sources &&
270 (prof->max_source_channels < discover->chmin ||
271 prof->max_source_channels > discover->chmax))
274 /* VALID PROFILE, STORE IT */
275 pa_log_debug ("Discovered valid profile: %s", prof->name);
276 data->pacard.profile = prof->name;
277 /* NOW FILLING SINKS/SOURCE ARRAYS WITH PROFILE DATA */
278 get_sinks_and_sources_from_profile (prof, sinks, sources, namebuf, sizeof(namebuf));
280 /* OUTPUT DIRECTION, SINKS */
281 data->direction = agl_output;
282 data->channels = prof->max_sink_channels;
283 for (i = 0; sinks[i]; i++) {
284 pa_log_debug ("Discovered valid sink #%s on card %s", sinks[i], cardid);
285 snprintf(paname, sizeof(paname), "alsa_output.%s.%s", cardid, sinks[i]);
286 handle_card_ports(u, data, card, prof);
289 /* INPUT DIRECTION, SOURCES */
290 data->direction = agl_input;
291 data->channels = prof->max_source_channels;
292 for (i = 0; sources[i]; i++) {
293 pa_log_debug ("Discovered valid source #%s on card %s", sources[i], cardid);
294 snprintf(paname, sizeof(paname), "alsa_input.%s.%s", cardid, sinks[i]);
295 handle_card_ports(u, data, card, prof);
300 static void get_sinks_and_sources_from_profile (pa_card_profile *prof, char **sinks, char **sources, char *buf, int buflen)
306 pa_assert (prof->name);
308 strncpy (buf, prof->name, (size_t)buflen);
309 buf[buflen-1] = '\0';
311 memset (sinks, 0, sizeof(char *) * (MAX_CARD_TARGET+1));
312 memset (sources, 0, sizeof(char *) * (MAX_CARD_TARGET+1));
315 if (!strncmp (p, "output:", 7)) {
316 if (i >= MAX_CARD_TARGET) {
317 pa_log_debug ("number of outputs exeeds the maximum %d in "
318 "profile name '%s'", MAX_CARD_TARGET, prof->name);
321 sinks[i++] = get_sink_or_source_from_str (&p, 7);
322 } else if (!strncmp (p, "input:", 6)) {
323 if (j >= MAX_CARD_TARGET) {
324 pa_log_debug ("number of inputs exeeds the maximum %d in "
325 "profile name '%s'", MAX_CARD_TARGET, prof->name);
328 sources[j++] = get_sink_or_source_from_str (&p, 6);
330 pa_log ("%s: failed to parse profile name '%s'",
331 __FILE__, prof->name);
337 static char *get_sink_or_source_from_str (char **string_ptr, int offs)
341 name = *string_ptr + offs;
343 for (end = name; (c = *end); end++) {
355 static void handle_card_ports (struct userdata *u, agl_node *data, pa_card *card, pa_card_profile *prof) {
356 agl_node *node = NULL;
357 pa_device_port *port;
360 bool have_ports = false;
361 char key[MAX_NAME_LENGTH+1];
362 const char *amname = data->amname;
370 PA_HASHMAP_FOREACH (port, card->ports, state) {
371 if (port->profiles &&
372 pa_hashmap_get (port->profiles, prof->name) &&
373 ((port->direction == PA_DIRECTION_INPUT && data->direction == agl_input)||
374 (port->direction == PA_DIRECTION_OUTPUT && data->direction == agl_output))) {
376 snprintf (key, sizeof(key), "%s@%s", data->paname, port->name);
379 data->available = (port->available != PA_AVAILABLE_NO);
381 data->amname = amname;
382 data->paport = port->name;
384 printf ("Key : %s\n", key);
386 /* this is needed to fill the "port->type" field */
387 //agl_classify_node_by_card (data, card, prof, port);
389 /* this is needed to complete the "pa_discover_add_sink" first pass */
390 node = create_node (u, data, &created);
396 data->amname = amname;
399 static const char *node_key (struct userdata *u, agl_direction direction,
400 void *data, pa_device_port *port, char *buf, size_t len) {
401 char *type; /* "sink" or "source" */
402 const char *name; /* sink or source name */
404 pa_card_profile *profile;
405 const char *profile_name;
412 pa_assert (direction == agl_input || direction == agl_output);
414 if (direction == agl_output) {
415 pa_sink *sink = data;
416 type = pa_xstrdup ("sink");
417 name = agl_utils_get_sink_name (sink);
420 port = sink->active_port;
422 pa_source *source = data;
423 type = pa_xstrdup ("source");
424 name = agl_utils_get_source_name (source);
427 port = source->active_port;
430 pa_log_debug ("Node type (sink/source): %s", type);
431 pa_log_debug ("Node name: %s", name);
436 pa_assert_se (profile = card->active_profile);
437 if (!u->state.profile) {
438 pa_log_debug ("profile is now '%s'", profile->name);
439 profile_name = profile->name;
441 pa_log_debug ("state.profile is not null. '%s' supresses '%s'",
442 u->state.profile, profile->name);
443 profile_name = u->state.profile;
446 if (!(bus = agl_utils_get_card_bus (card))) {
447 pa_log_debug ("ignoring card '%s' due to lack of '%s' property",
448 agl_utils_get_card_name (card), PA_PROP_DEVICE_BUS);
452 if (pa_streq(bus, "pci") || pa_streq(bus, "usb") || pa_streq(bus, "platform")) {
457 snprintf (buf, len, "%s@%s", name, port->name);
460 /* we do not handle Bluetooth yet, and the function never returns it */
461 /*else if (pa_streq(bus, "bluetooth")) {
464 return (const char *)key;
467 agl_node *agl_discover_find_node_by_key (struct userdata *u, const char *key)
469 agl_discover *discover;
473 pa_assert_se (discover = u->discover);
476 node = pa_hashmap_get (discover->nodes.byname, key);
483 void agl_discover_add_node_to_ptr_hash (struct userdata *u, void *ptr, agl_node *node)
485 agl_discover *discover;
490 pa_assert_se (discover = u->discover);
492 pa_hashmap_put (discover->nodes.byptr, ptr, node);
495 static agl_node *create_node (struct userdata *u, agl_node *data, bool *created_ret)
497 agl_discover *discover;
503 pa_assert (data->key);
504 pa_assert (data->paname);
505 pa_assert_se (discover = u->discover);
507 if ((node = pa_hashmap_get (discover->nodes.byname, data->key))) {
508 pa_log_debug ("No need to create this node");
511 pa_log_debug ("Creating new node");
513 node = agl_node_create (u, data);
514 pa_hashmap_put (discover->nodes.byname, node->key, node);
516 /* TODO: registering the new node to the Optional router daemon */
517 /* if (node->available)
518 pa_audiomgr_register_node (u, node); */
524 *created_ret = created;
529 void agl_discover_add_source (struct userdata *u, pa_source *source)
531 static agl_nodeset_resdef def_resdef = {0, {0, 0}};
536 agl_discover *discover;
543 pa_assert_se (core = u->core);
544 pa_assert_se (discover = u->discover);
546 module = source->module;
550 /* helper function verifying that sink direction is input/output */
551 key = node_key (u, agl_input, source, NULL, kbf, sizeof(kbf));
553 pa_log_debug ("Source key: %s", key);
554 node = agl_discover_find_node_by_key (u, key);
555 if (!node) { /* VERIFY IF THIS WORKS */
556 if (u->state.profile)
557 pa_log_debug ("can't find node for source (key '%s')", key);
558 else { /* how do we get here ? not in initial setup */
559 u->state.source = source->index;
560 pa_log_debug ("BUG !");
564 pa_log_debug("node for '%s' found (key %s). Updating with source data",
565 node->paname, node->key);
566 node->paidx = source->index;
567 node->available = true;
568 agl_discover_add_node_to_ptr_hash (u, source, node);
572 bool agl_discover_preroute_sink_input(struct userdata *u, pa_sink_input_new_data *data)
577 agl_discover *discover;
578 agl_nodeset *nodeset;
584 pa_assert_se (core = u->core);
585 pa_assert_se (discover = u->discover);
586 pa_assert_se (nodeset = u->nodeset);
587 pa_assert_se (pl = data->proplist);
589 /* is this a valid sink input ? */
593 /* is there an existing matching node ? */
594 node = agl_node_get_from_data (u, agl_input, data);
598 node = agl_node_create (u, NULL);
599 node->direction = agl_input;
600 node->implement = agl_stream;
601 node->type = agl_classify_guess_stream_node_type (u, pl);
602 node->visible = true;
603 node->available = true;
604 node->ignore = true; /* gets ignored initially */
605 node->paname = pa_proplist_gets (pl, PA_PROP_APPLICATION_NAME);
606 node->client = data->client;
607 /* add to global nodeset */
608 pa_idxset_put (nodeset->nodes, node, &node->index);
611 /* create NULL sink */
613 node->nullsink = agl_utils_create_null_sink (u, u->nsnam);
617 /* redirect sink input to NULL sink */
618 sink = agl_utils_get_null_sink (u, node->nullsink);
620 if (pa_sink_input_new_data_set_sink (data, sink, false))
621 pa_log_debug ("set sink %u for new sink-input", sink->index);
623 pa_log ("can't set sink %u for new sink-input", sink->index);
628 void agl_discover_register_sink_input (struct userdata *u, pa_sink_input *sinp)
631 pa_proplist *pl, *client_proplist;
632 agl_discover *discover;
637 agl_node node_data, *node;
643 pa_assert_se (core = u->core);
644 pa_assert_se (discover = u->discover);
645 pa_assert_se (pl = sinp->proplist);
647 media = pa_proplist_gets (sinp->proplist, PA_PROP_MEDIA_NAME);
649 /* special treatment for combine/loopback streams */
650 pa_log_debug ("Stream may me combine/loopback, in this case we should ignore it, but use it for now");
654 name = agl_utils_get_sink_input_name (sinp);
655 client_proplist = sinp->client ? sinp->client->proplist : NULL;
657 pa_log_debug ("registering input stream '%s'", name);
659 /* we could autodetect sink type by using:
660 * - PA_PROP_APPLICATION_PROCESS_ID;
661 * - PA_PROP_APPLICATION_PROCESS_BINARY;
662 * - PA_PROP_APPLICATION_NAME;
664 but let us assume "agl_player" for now */
667 /* this sets our routing properties on the sink, which are :
668 * #define PA_PROP_ROUTING_CLASS_NAME "routing.class.name"
669 * #define PA_PROP_ROUTING_CLASS_ID "routing.class.id"
670 * #define PA_PROP_ROUTING_METHOD "routing.method" */
671 agl_utils_set_stream_routing_properties (pl, type, NULL);
673 /* we now create a new node for this sink */
674 memset (&node_data, 0, sizeof(node_data));
676 node_data.direction = agl_input;
677 node_data.implement = agl_stream;
678 node_data.channels = sinp->channel_map.channels;
679 node_data.type = type;
680 node_data.zone = agl_utils_get_zone (sinp->proplist, client_proplist);
681 node_data.visible = true;
682 node_data.available = true;
683 node_data.amname = pa_proplist_gets (pl, "resource.set.appid");
684 node_data.amdescr = pa_proplist_gets (pl, PA_PROP_MEDIA_NAME);
685 node_data.amid = AM_ID_INVALID;
686 node_data.paname = (char *)name;
687 node_data.paidx = sinp->index;
688 /*node_data.rset.id = pa_utils_get_rsetid (pl, idbuf, sizeof(idbuf));*/
690 role = pa_proplist_gets (sinp->proplist, PA_PROP_MEDIA_ROLE);
691 /* MAIN PREROUTING DONE HERE ! (this was failing in the samples) */
692 /*sink = mir_router_make_prerouting (u, &data, &sinp->channel_map, role, &target);*/
694 node = create_node (u, &node_data, NULL);
696 agl_discover_add_node_to_ptr_hash (u, sinp, node);
698 if (sink && target) {
699 pa_log_debug ("move stream to sink %u (%s)", sink->index, sink->name);
701 if (pa_sink_input_move_to (sinp, sink, false) < 0)
702 pa_log ("failed to route '%s' => '%s'",node->amname,target->amname);
704 pa_audiomgr_add_default_route (u, node, target);
708 void agl_discover_register_source_output (struct userdata *u, pa_source_output *sout)
711 pa_proplist *pl, *client_proplist;
712 agl_discover *discover;
717 agl_node node_data, *node;
723 pa_assert_se (core = u->core);
724 pa_assert_se (discover = u->discover);
725 pa_assert_se (pl = sout->proplist);
727 media = pa_proplist_gets (sout->proplist, PA_PROP_MEDIA_NAME);
729 /* special treatment for loopback streams (not combine as with sinks !) */
730 pa_log_debug ("Stream may me loopback, in this case we should ignore it, but use it for now");
734 /* TODO : contrary to "agl_discover_register_sink_input", this function is always called
735 * even if we do not find PA_PROP_MEDIA_NAME (see above). */
736 //agl_utils_set_stream_routing_properties (pl, type, NULL);
738 name = agl_utils_get_source_output_name (sout);
739 client_proplist = sout->client ? sout->client->proplist : NULL;
741 pa_log_debug("registering output stream '%s'", name);
744 void agl_discover_add_sink_input (struct userdata *u, pa_sink_input *sinp)
751 pa_assert_se (core = u->core);
756 /* is there an existing matching node ? */
757 node = agl_node_get_from_client (u, sinp->client);
761 agl_router_register_node (u, node);
764 void agl_discover_remove_sink_input (struct userdata *u, pa_sink_input *sinp)
771 pa_assert_se (core = u->core);
776 /* is there an existing matching node ? */
777 node = agl_node_get_from_client (u, sinp->client);
781 agl_router_unregister_node (u, node);