2 * module-murphy-ivi -- PulseAudio module for providing audio routing support
3 * Copyright (c) 2012, Intel Corporation.
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms and conditions of the GNU Lesser General Public License,
7 * version 2.1, as published by the Free Software Foundation.
9 * This program is distributed in the hope it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.
12 * See the GNU Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public License
15 * along with this program; if not, write to the
16 * Free Software Foundation, Inc., 51 Franklin St - Fifth Floor, Boston,
22 #include <pulsecore/pulsecore-config.h>
24 #include <pulsecore/hashmap.h>
25 #include <pulsecore/idxset.h>
26 #include <pulsecore/client.h>
27 #include <pulsecore/core-util.h>
28 #include <pulsecore/log.h>
29 #include <pulsecore/card.h>
30 #include <pulsecore/device-port.h>
31 #include <pulsecore/sink-input.h>
32 #include <pulsecore/source-output.h>
33 #include <pulsecore/strbuf.h>
40 #include "constrain.h"
41 #include "multiplex.h"
47 #define MAX_CARD_TARGET 4
48 #define MAX_NAME_LENGTH 256
50 #define ACTIVE_PORT NULL
52 /* Bluetooth service class */
53 #define BIT(x) (1U << (x))
55 #define BT_SERVICE_MASK 0xffe
56 #define BT_SERVICE_INFORMATION BIT(23) /**< WEB-server, WAP-server, etc */
57 #define BT_SERVICE_TELEPHONY BIT(22) /**< Modem, Headset, etc*/
58 #define BT_SERVICE_AUDIO BIT(21) /**< Speaker, Microphone, Headset */
59 #define BT_SERVICE_OBJECT_XFER BIT(20) /**< v-Inbox, v-Folder, etc */
60 #define BT_SERVICE_CAPTURING BIT(19) /**< Scanner, Microphone, etc */
61 #define BT_SERVICE_RENDERING BIT(18) /**< Printing, Speaker, etc */
62 #define BT_SERVICE_NETWORKING BIT(17) /**< LAN, Ad hoc, etc */
63 #define BT_SERVICE_POSITIONING BIT(16) /**< Location identification */
82 static const char combine_pattern[] = "Simultaneous output on ";
83 static const char loopback_outpatrn[] = "Loopback from ";
84 static const char loopback_inpatrn[] = "Loopback to ";
86 static void handle_alsa_card(struct userdata *, pa_card *);
87 static void handle_bluetooth_card(struct userdata *, pa_card *);
89 static void handle_udev_loaded_card(struct userdata *, pa_card *,
91 static void handle_card_ports(struct userdata *, mir_node *,
92 pa_card *, pa_card_profile *);
94 static mir_node *create_node(struct userdata *, mir_node *, pa_bool_t *);
95 static void destroy_node(struct userdata *, mir_node *);
96 static pa_bool_t update_node_availability(struct userdata *, mir_direction,
97 void *, pa_device_port *, pa_bool_t);
99 static void parse_profile_name(pa_card_profile *,
100 char **, char **, char *, int);
102 static char *node_key(struct userdata *, mir_direction,
103 void *, pa_device_port *, char *, size_t);
105 static pa_sink *make_output_prerouting(struct userdata *, mir_node *,
106 pa_channel_map *, const char *,
108 static pa_source *make_input_prerouting(struct userdata *, mir_node *,
109 const char *, mir_node **);
111 static mir_node_type get_stream_routing_class(pa_proplist *);
114 static void schedule_deferred_routing(struct userdata *);
115 static void schedule_card_check(struct userdata *, pa_card *);
116 static void schedule_source_cleanup(struct userdata *, mir_node *);
117 static void schedule_stream_uncorking(struct userdata *, pa_sink *);
119 static void pa_hashmap_node_free(void *node, void *u)
121 mir_node_destroy(u, node);
125 struct pa_discover *pa_discover_init(struct userdata *u)
127 pa_discover *discover = pa_xnew0(pa_discover, 1);
131 discover->selected = TRUE;
133 discover->nodes.byname = pa_hashmap_new(pa_idxset_string_hash_func,
134 pa_idxset_string_compare_func);
135 discover->nodes.byptr = pa_hashmap_new(pa_idxset_trivial_hash_func,
136 pa_idxset_trivial_compare_func);
140 void pa_discover_done(struct userdata *u)
142 pa_discover *discover;
144 if (u && (discover = u->discover)) {
145 pa_hashmap_free(discover->nodes.byname, pa_hashmap_node_free,u);
146 pa_hashmap_free(discover->nodes.byptr, NULL,NULL);
152 void pa_discover_domain_up(struct userdata *u)
154 pa_discover *discover;
159 pa_assert_se((discover = u->discover));
161 PA_HASHMAP_FOREACH(node, discover->nodes.byname, state) {
162 node->amid = AM_ID_INVALID;
164 if (node->visible && node->available)
165 pa_audiomgr_register_node(u, node);
169 void pa_discover_domain_down(struct userdata *u)
173 void pa_discover_add_card(struct userdata *u, pa_card *card)
180 if (!(bus = pa_proplist_gets(card->proplist, PA_PROP_DEVICE_BUS))) {
181 pa_log_debug("ignoring card '%s' due to lack of '%s' property",
182 pa_utils_get_card_name(card), PA_PROP_DEVICE_BUS);
186 if (pa_streq(bus, "pci") || pa_streq(bus, "usb")) {
187 handle_alsa_card(u, card);
190 else if (pa_streq(bus, "bluetooth")) {
191 handle_bluetooth_card(u, card);
195 pa_log_debug("ignoring card '%s' due to unsupported bus type '%s'",
196 pa_utils_get_card_name(card), bus);
199 void pa_discover_remove_card(struct userdata *u, pa_card *card)
202 pa_discover *discover;
208 pa_assert_se((discover = u->discover));
210 if (!(bus = pa_proplist_gets(card->proplist, PA_PROP_DEVICE_BUS)))
214 PA_HASHMAP_FOREACH(node, discover->nodes.byname, state) {
215 if (node->implement == mir_device &&
216 node->pacard.index == card->index)
218 if (pa_streq(bus, "pci") || pa_streq(bus, "usb"))
219 mir_constrain_destroy(u, node->paname);
221 destroy_node(u, node);
225 if (pa_streq(bus, "bluetooth"))
226 mir_constrain_destroy(u, card->name);
229 void pa_discover_profile_changed(struct userdata *u, pa_card *card)
232 pa_card_profile *prof;
235 pa_discover *discover;
247 pa_assert_se((core = u->core));
248 pa_assert_se((discover = u->discover));
251 if ((bus = pa_proplist_gets(card->proplist, PA_PROP_DEVICE_BUS)) == NULL) {
252 pa_log_debug("ignoring profile change on card '%s' due to lack of '%s'"
253 "property", pa_utils_get_card_name(card),
258 pci = pa_streq(bus, "pci");
259 usb = pa_streq(bus, "usb");
260 bluetooth = pa_streq(bus, "bluetooth");
262 if (!pci && !usb && !bluetooth) {
263 pa_log_debug("ignoring profile change on card '%s' due to unsupported "
264 "bus type '%s'", pa_utils_get_card_name(card), bus);
265 u->state.sink = u->state.source = PA_IDXSET_INVALID;
269 if ((index = u->state.sink) != PA_IDXSET_INVALID) {
270 if ((sink = pa_idxset_get_by_index(core->sinks, index)))
271 pa_discover_add_sink(u, sink, TRUE);
273 pa_log_debug("sink.%u is gone", index);
274 u->state.sink = PA_IDXSET_INVALID;
277 if ((index = u->state.source) != PA_IDXSET_INVALID) {
278 if ((source = pa_idxset_get_by_index(core->sources, index)))
279 pa_discover_add_source(u, source);
281 pa_log_debug("source.%u is gone", index);
282 u->state.source = PA_IDXSET_INVALID;
286 pa_assert_se((prof = card->active_profile));
288 pa_log_debug("bluetooth profile changed to '%s' on card '%s'",
289 prof->name, card->name);
291 if (!prof->n_sinks && !prof->n_sources) {
292 /* switched of but not unloaded yet */
293 PA_HASHMAP_FOREACH(node, discover->nodes.byname, state) {
294 if (node->implement == mir_device &&
295 node->pacard.index == card->index)
297 node->available = FALSE;
303 pa_log_debug("alsa profile changed to '%s' on card '%s'",
304 card->active_profile->name, card->name);
306 stamp = pa_utils_get_stamp();
308 handle_alsa_card(u, card);
310 PA_HASHMAP_FOREACH(node, discover->nodes.byname, state) {
311 if (node->implement == mir_device &&
312 node->pacard.index == card->index &&
315 destroy_node(u, node);
322 void pa_discover_port_available_changed(struct userdata *u,
323 pa_device_port *port)
335 pa_assert_se((core = u->core));
337 switch (port->available) {
338 case PA_PORT_AVAILABLE_NO: state = "not "; available = FALSE; break;
339 case PA_PORT_AVAILABLE_YES: state = ""; available = TRUE; break;
340 default: /* do nothing */ return;
343 pa_log_debug("port '%s' availabilty changed to %savailable. Updating",
348 if (port->is_output) {
349 PA_IDXSET_FOREACH(sink, core->sinks, idx) {
351 if (port == pa_hashmap_get(sink->ports, port->name)) {
352 pa_log_debug(" sink '%s'", sink->name);
353 route |= update_node_availability(u, mir_output,sink,port,
360 if (port->is_input) {
361 PA_IDXSET_FOREACH(source, core->sources, idx) {
363 if (port == pa_hashmap_get(source->ports, port->name)) {
364 pa_log_debug(" source '%s'", source->name);
365 route |= update_node_availability(u, mir_input,source,port,
373 mir_router_make_routing(u);
376 void pa_discover_add_sink(struct userdata *u, pa_sink *sink, pa_bool_t route)
379 pa_discover *discover;
386 const char *loopback_role;
393 pa_assert_se((core = u->core));
394 pa_assert_se((discover = u->discover));
396 module = sink->module;
398 if ((card = sink->card)) {
399 if (!(key = node_key(u, mir_output,sink,ACTIVE_PORT, kbf,sizeof(kbf))))
401 if (!(node = pa_discover_find_node_by_key(u, key))) {
402 if (u->state.profile)
403 pa_log_debug("can't find node for sink (key '%s')", key);
405 u->state.sink = sink->index;
408 pa_log_debug("node for '%s' found (key %s). Updating with sink data",
409 node->paname, node->key);
410 node->paidx = sink->index;
411 node->available = TRUE;
412 pa_discover_add_node_to_ptr_hash(u, sink, node);
414 if ((loopback_role = pa_classify_loopback_stream(node))) {
415 if (!(ns = pa_utils_get_null_source(u))) {
416 pa_log("Can't load loopback module: no initial null source");
419 node->loop = pa_loopback_create(u->loopback, core, node->index,
420 ns->index, sink->index,
423 mir_node_print(node, nbf, sizeof(nbf));
424 pa_log_debug("updated node:\n%s", nbf);
430 if (type != mir_bluetooth_a2dp && type != mir_bluetooth_sco)
431 mir_router_make_routing(u);
433 if (!u->state.profile)
434 schedule_deferred_routing(u);
438 else if (!module || !pa_streq(module->name, "module-combine-sink")) {
439 memset(&data, 0, sizeof(data));
440 data.key = pa_xstrdup(sink->name);
441 data.direction = mir_output;
442 data.implement = mir_device;
443 data.channels = sink->channel_map.channels;
445 if (sink == pa_utils_get_null_sink(u)) {
446 data.visible = FALSE;
447 data.available = TRUE;
448 data.type = mir_null;
449 data.amname = pa_xstrdup("Silent");
450 data.amid = AM_ID_INVALID;
451 data.paname = pa_xstrdup(sink->name);
452 data.paidx = sink->index;
455 pa_xfree(data.key); /* for now */
456 pa_log_info("currently we do not support "
457 "statically loaded sinks");
461 create_node(u, &data, NULL);
466 void pa_discover_remove_sink(struct userdata *u, pa_sink *sink)
468 pa_discover *discover;
475 pa_assert_se((discover = u->discover));
477 name = pa_utils_get_sink_name(sink);
479 if (!(node = pa_hashmap_get(discover->nodes.byptr, sink)))
480 pa_log_debug("can't find node for sink (name '%s')", name);
482 pa_log_debug("node found for '%s'. Reseting sink data", name);
483 schedule_source_cleanup(u, node);
484 node->paidx = PA_IDXSET_INVALID;
485 pa_hashmap_remove(discover->nodes.byptr, sink);
490 if (type != mir_bluetooth_a2dp && type != mir_bluetooth_sco)
491 node->available = FALSE;
493 if (!u->state.profile)
494 schedule_deferred_routing(u);
498 pa_log_info("currently we do not support statically loaded sinks");
504 void pa_discover_add_source(struct userdata *u, pa_source *source)
507 pa_discover *discover;
513 const char *loopback_role;
520 pa_assert_se((core = u->core));
521 pa_assert_se((discover = u->discover));
523 if ((card = source->card)) {
524 if (!(key = node_key(u,mir_input,source,ACTIVE_PORT,kbf,sizeof(kbf))))
526 if (!(node = pa_discover_find_node_by_key(u, key))) {
527 if (u->state.profile)
528 pa_log_debug("can't find node for source (key '%s')", key);
530 u->state.source = source->index;
533 pa_log_debug("node for '%s' found. Updating with source data",
535 node->paidx = source->index;
536 node->available = TRUE;
537 pa_discover_add_node_to_ptr_hash(u, source, node);
538 if ((loopback_role = pa_classify_loopback_stream(node))) {
539 if (!(ns = pa_utils_get_null_sink(u))) {
540 pa_log("Can't load loopback module: no initial null sink");
543 node->loop = pa_loopback_create(u->loopback, core, node->index,
544 source->index, ns->index,
547 sink_index = pa_loopback_get_sink_index(core, node->loop);
548 node->mux = pa_multiplex_find(u->multiplex, sink_index);
551 mir_node_print(node, nbf, sizeof(nbf));
552 pa_log_debug("updated node:\n%s", nbf);
556 memset(&data, 0, sizeof(data));
557 data.key = pa_xstrdup(source->name);
558 data.direction = mir_input;
559 data.implement = mir_device;
560 data.channels = source->channel_map.channels;
562 if (source == pa_utils_get_null_source(u)) {
563 data.visible = FALSE;
564 data.available = TRUE;
565 data.type = mir_null;
566 data.amname = pa_xstrdup("Silent");
567 data.amid = AM_ID_INVALID;
568 data.paname = pa_xstrdup(source->name);
569 data.paidx = source->index;
572 pa_xfree(data.key); /* for now */
573 pa_log_info("currently we do not support "
574 "statically loaded sources");
578 create_node(u, &data, NULL);
583 void pa_discover_remove_source(struct userdata *u, pa_source *source)
585 pa_discover *discover;
592 pa_assert_se((discover = u->discover));
594 name = pa_utils_get_source_name(source);
596 if (!(node = pa_hashmap_get(discover->nodes.byptr, source)))
597 pa_log_debug("can't find node for source (name '%s')", name);
599 pa_log_debug("node found. Reseting source data");
600 schedule_source_cleanup(u, node);
601 node->paidx = PA_IDXSET_INVALID;
602 pa_hashmap_remove(discover->nodes.byptr, source);
607 if (type != mir_bluetooth_sco)
608 node->available = FALSE;
610 if (!u->state.profile)
611 schedule_deferred_routing(u);
615 pa_log_info("currently we do not support statically "
622 void pa_discover_register_sink_input(struct userdata *u, pa_sink_input *sinp)
625 pa_discover *discover;
639 pa_assert_se((core = u->core));
640 pa_assert_se((discover = u->discover));
641 pa_assert_se((pl = sinp->proplist));
643 if ((media = pa_proplist_gets(sinp->proplist, PA_PROP_MEDIA_NAME))) {
644 if (!strncmp(media, combine_pattern, sizeof(combine_pattern)-1)) {
645 pa_log_debug("Seems to be a combine stream. Nothing to do ...");
648 if (!strncmp(media, loopback_outpatrn, sizeof(loopback_outpatrn)-1)) {
649 pa_log_debug("Seems to be a loopback stream. Nothing to do ...");
654 name = pa_utils_get_sink_input_name(sinp);
656 pa_log_debug("registering input stream '%s'", name);
658 if (!(type = pa_classify_guess_stream_node_type(pl))) {
659 pa_log_debug("cant find stream class for '%s'. "
660 "Leaving it alone", name);
664 pa_utils_set_stream_routing_properties(pl, type, NULL);
666 snprintf(key, sizeof(key), "stream_input.%d", sinp->index);
668 memset(&data, 0, sizeof(data));
670 data.direction = mir_input;
671 data.implement = mir_stream;
672 data.channels = sinp->channel_map.channels;
675 data.available = TRUE;
677 data.amdescr = (char *)pa_proplist_gets(pl, PA_PROP_MEDIA_NAME);
678 data.amid = AM_ID_INVALID;
680 data.paidx = sinp->index;
683 * here we can't guess whether the application requested an explicit
684 * route by sepcifying the target sink @ stream creation time.
686 * the brute force solution: we make a default route for this stream
687 * possibly overwiriting the orginal app request :(
689 /* this will set data.mux */
690 role = pa_proplist_gets(sinp->proplist, PA_PROP_MEDIA_ROLE);
691 sink = make_output_prerouting(u, &data, &sinp->channel_map, role, &target);
693 node = create_node(u, &data, NULL);
695 pa_discover_add_node_to_ptr_hash(u, sinp, node);
697 if (sink && target) {
698 pa_log_debug("move stream to sink %u (%s)", sink->index, sink->name);
700 if (pa_sink_input_move_to(sinp, sink, FALSE) < 0)
701 pa_log("failed to route '%s' => '%s'",node->amname,target->amname);
703 pa_log_debug("register route '%s' => '%s'",
704 node->amname, target->amname);
705 /* FIXME: and actually do it ... */
710 void pa_discover_preroute_sink_input(struct userdata *u,
711 pa_sink_input_new_data *data)
716 pa_discover *discover;
726 pa_assert_se((core = u->core));
727 pa_assert_se((discover = u->discover));
728 pa_assert_se((pl = data->proplist));
730 mnam = (m = data->module) ? m->name : "";
732 if (pa_streq(mnam, "module-combine-sink"))
733 type = mir_node_type_unknown;
735 if (pa_streq(mnam, "module-loopback")) {
737 if (!(node = pa_utils_get_node_from_data(u, mir_input, data))) {
738 pa_log_debug("can't find loopback node for sink-input");
742 if (node->direction == mir_output) {
743 pa_log_debug("refuse to preroute loopback sink-input "
744 "(current route: sink %u @ %p)", data->sink ?
745 data->sink->index : PA_IDXSET_INVALID, sink);
751 type = pa_classify_guess_stream_node_type(pl);
752 pa_utils_set_stream_routing_properties(pl, type, data->sink);
756 memset(&fake, 0, sizeof(fake));
757 fake.direction = mir_input;
758 fake.implement = mir_stream;
759 fake.channels = data->channel_map.channels;
762 fake.available = TRUE;
763 fake.amname = "<preroute sink-input>";
765 role = pa_proplist_gets(data->proplist, PA_PROP_MEDIA_ROLE);
766 sink = make_output_prerouting(u, &fake, &data->channel_map, role,NULL);
770 if (fake.mux && !(data->flags & PA_SINK_INPUT_START_CORKED)) {
771 data->flags |= PA_SINK_INPUT_START_CORKED;
772 schedule_stream_uncorking(u, sink);
776 if (pa_sink_input_new_data_set_sink(data, sink, FALSE))
777 pa_log_debug("set sink %u for new sink-input", sink->index);
779 pa_log("can't set sink %u for new sink-input", sink->index);
785 void pa_discover_add_sink_input(struct userdata *u, pa_sink_input *sinp)
789 pa_sink_input *csinp;
791 pa_discover *discover;
803 pa_assert_se((core = u->core));
804 pa_assert_se((discover = u->discover));
805 pa_assert_se((pl = sinp->proplist));
807 if (!(media = pa_proplist_gets(sinp->proplist, PA_PROP_MEDIA_NAME)))
810 if (!strncmp(media, combine_pattern, sizeof(combine_pattern)-1)) {
811 pa_log_debug("New stream is a combine stream. Nothing to do ...");
813 } else if (!strncmp(media, loopback_outpatrn,sizeof(loopback_outpatrn)-1)){
814 pa_log_debug("New stream is a loopback output stream");
816 if ((node = pa_utils_get_node_from_stream(u, mir_input, sinp))) {
817 if (node->direction == mir_input)
818 pa_log_debug("loopback stream node '%s' found", node->amname);
820 pa_log_debug("ignoring it");
825 pa_log_debug("can't find node for the loopback stream");
832 name = pa_utils_get_sink_input_name(sinp);
834 pa_log_debug("dealing with new input stream '%s'", name);
836 if ((type = get_stream_routing_class(pl)) == mir_node_type_unknown) {
837 if (!(type = pa_classify_guess_stream_node_type(pl))) {
838 pa_log_debug("cant find stream class for '%s'. "
839 "Leaving it alone", name);
843 pa_utils_set_stream_routing_properties(pl, type, NULL);
845 /* if needed, make some post-routing here */
848 /* we need to add this to main hashmap as that is used for loop
849 through on all nodes. */
850 snprintf(key, sizeof(key), "stream_input.%d", sinp->index);
852 memset(&data, 0, sizeof(data));
854 data.direction = mir_input;
855 data.implement = mir_stream;
856 data.channels = sinp->channel_map.channels;
859 data.available = TRUE;
861 data.amdescr = (char *)pa_proplist_gets(pl, PA_PROP_MEDIA_NAME);
862 data.amid = AM_ID_INVALID;
864 data.paidx = sinp->index;
865 data.mux = pa_multiplex_find(u->multiplex, sinp->sink->index);
867 node = create_node(u, &data, &created);
872 pa_log("%s: confused with stream. '%s' did exists",
873 __FILE__, node->amname);
877 pa_discover_add_node_to_ptr_hash(u, sinp, node);
882 csinp = pa_idxset_get_by_index(core->sink_inputs,
883 data.mux->defstream_index);
884 s = csinp ? csinp->sink : NULL;
889 pa_log_debug("routing target candidate is %u (%s)", s->index, s->name);
891 if (!s || !(snod = pa_hashmap_get(discover->nodes.byptr, s)))
892 pa_log_debug("can't figure out where this stream is routed");
894 pa_log_debug("register route '%s' => '%s'",
895 node->amname, snod->amname);
896 /* FIXME: and actually do it ... */
898 pa_fader_apply_volume_limits(u, pa_utils_get_stamp());
903 void pa_discover_remove_sink_input(struct userdata *u, pa_sink_input *sinp)
905 pa_discover *discover;
913 pa_assert_se((discover = u->discover));
915 name = pa_utils_get_sink_input_name(sinp);
917 pa_log("sink-input '%s' going to be destroyed", name);
919 if (!(node = pa_discover_remove_node_from_ptr_hash(u, sinp)))
920 pa_log_debug("can't find node for sink-input (name '%s')", name);
922 pa_log_debug("node found for '%s'. After clearing routes "
923 "it will be destroyed", name);
925 if (!(sinknod = pa_hashmap_get(discover->nodes.byptr, sinp->sink)))
926 pa_log_debug("can't figure out where this stream is routed");
928 pa_log_debug("clear route '%s' => '%s'",
929 node->amname, sinknod->amname);
931 /* FIXME: and actually do it ... */
935 destroy_node(u, node);
937 mir_router_make_routing(u);
942 void pa_discover_register_source_output(struct userdata *u,
943 pa_source_output *sout)
946 pa_discover *discover;
960 pa_assert_se((core = u->core));
961 pa_assert_se((discover = u->discover));
962 pa_assert_se((pl = sout->proplist));
964 if ((media = pa_proplist_gets(sout->proplist, PA_PROP_MEDIA_NAME))) {
965 if (!strncmp(media, loopback_inpatrn, sizeof(loopback_inpatrn)-1)) {
966 pa_log_debug("Seems to be a loopback stream. Nothing to do ...");
971 name = pa_utils_get_source_output_name(sout);
973 pa_log_debug("registering output stream '%s'", name);
975 if (!(type = pa_classify_guess_stream_node_type(pl))) {
976 pa_log_debug("cant find stream class for '%s'. "
977 "Leaving it alone", name);
981 pa_utils_set_stream_routing_properties(pl, type, NULL);
983 snprintf(key, sizeof(key), "stream_output.%d", sout->index);
985 memset(&data, 0, sizeof(data));
987 data.direction = mir_output;
988 data.implement = mir_stream;
989 data.channels = sout->channel_map.channels;
992 data.available = TRUE;
994 data.amdescr = (char *)pa_proplist_gets(pl, PA_PROP_MEDIA_NAME);
995 data.amid = AM_ID_INVALID;
997 data.paidx = sout->index;
1000 * here we can't guess whether the application requested an explicit
1001 * route by sepcifying the target source @ stream creation time.
1003 * the brute force solution: we make a default route for this stream
1004 * possibly overwiriting the orginal app request :(
1006 role = pa_proplist_gets(sout->proplist, PA_PROP_MEDIA_ROLE);
1007 source = make_input_prerouting(u, &data, role, &target);
1009 node = create_node(u, &data, NULL);
1011 pa_discover_add_node_to_ptr_hash(u, sout, node);
1013 if (source && target) {
1014 pa_log_debug("move stream to source %u (%s)",
1015 source->index, source->name);
1017 if (pa_source_output_move_to(sout, source, FALSE) < 0)
1018 pa_log("failed to route '%s' => '%s'",node->amname,target->amname);
1020 pa_log_debug("register route '%s' => '%s'",
1021 node->amname, target->amname);
1022 /* FIXME: and actually do it ... */
1027 void pa_discover_preroute_source_output(struct userdata *u,
1028 pa_source_output_new_data *data)
1033 pa_discover *discover;
1043 pa_assert_se((core = u->core));
1044 pa_assert_se((discover = u->discover));
1045 pa_assert_se((pl = data->proplist));
1047 mnam = (m = data->module) ? m->name : "";
1049 if (pa_streq(mnam, "module-loopback")) {
1050 if (!(node = pa_utils_get_node_from_data(u, mir_output, data))) {
1051 pa_log_debug("can't find loopback node for source-output");
1055 if (node->direction == mir_input) {
1056 pa_log_debug("refuse to preroute loopback source-output "
1057 "(current route: source %u @ %p)", data->source ?
1058 data->source->index : PA_IDXSET_INVALID, source);
1062 data->source = NULL;
1064 type = pa_classify_guess_stream_node_type(pl);
1065 pa_utils_set_stream_routing_properties(pl, type, data->source);
1067 if (!data->source) {
1068 memset(&fake, 0, sizeof(fake));
1069 fake.direction = mir_output;
1070 fake.implement = mir_stream;
1071 fake.channels = data->channel_map.channels;
1073 fake.visible = TRUE;
1074 fake.available = TRUE;
1075 fake.amname = "<preroute source-output>";
1077 role = pa_proplist_gets(data->proplist, PA_PROP_MEDIA_ROLE);
1078 source = make_input_prerouting(u, &fake, role, NULL);
1081 if (pa_source_output_new_data_set_source(data, source, FALSE))
1082 pa_log_debug("set source %u for new source-output");
1084 pa_log("can't set source %u for new source-output",
1092 void pa_discover_add_source_output(struct userdata *u, pa_source_output *sout)
1097 pa_discover *discover;
1109 pa_assert_se((core = u->core));
1110 pa_assert_se((discover = u->discover));
1111 pa_assert_se((pl = sout->proplist));
1113 if (!(media = pa_proplist_gets(sout->proplist, PA_PROP_MEDIA_NAME)))
1114 media = "<unknown>";
1116 if (!strncmp(media, loopback_inpatrn, sizeof(loopback_inpatrn)-1)) {
1117 pa_log_debug("New stream is a loopback input stream");
1119 if ((node = pa_utils_get_node_from_stream(u, mir_output, sout))) {
1120 if (node->direction == mir_output)
1121 pa_log_debug("loopback stream node '%s' found", node->amname);
1123 pa_log_debug("ignoring it");
1128 pa_log_debug("can't find node for the loopback stream");
1133 name = pa_utils_get_source_output_name(sout);
1135 pa_log_debug("dealing with new output stream '%s'", name);
1137 if ((type = get_stream_routing_class(pl)) == mir_node_type_unknown) {
1138 if (!(type = pa_classify_guess_stream_node_type(pl))) {
1139 pa_log_debug("cant find stream class for '%s'. "
1140 "Leaving it alone", name);
1144 pa_utils_set_stream_routing_properties(pl, type, NULL);
1146 /* if needed, make some post-routing here */
1149 /* we need to add this to main hashmap as that is used for loop
1150 through on all nodes. */
1151 snprintf(key, sizeof(key), "stream_output.%d", sout->index);
1153 memset(&data, 0, sizeof(data));
1155 data.direction = mir_output;
1156 data.implement = mir_stream;
1157 data.channels = sout->channel_map.channels;
1159 data.visible = TRUE;
1160 data.available = TRUE;
1162 data.amdescr = (char *)pa_proplist_gets(pl, PA_PROP_MEDIA_NAME);
1163 data.amid = AM_ID_INVALID;
1165 data.paidx = sout->index;
1167 node = create_node(u, &data, &created);
1172 pa_log("%s: confused with stream. '%s' did exists",
1173 __FILE__, node->amname);
1177 pa_discover_add_node_to_ptr_hash(u, sout, node);
1180 if ((s = sout->source))
1181 pa_log_debug("routing target candidate is %u (%s)", s->index, s->name);
1183 if (!s || !(snod = pa_hashmap_get(discover->nodes.byptr, s)))
1184 pa_log_debug("can't figure out where this stream is routed");
1186 pa_log_debug("register route '%s' => '%s'",
1187 snod->amname, node->amname);
1188 /* FIXME: and actually do it ... */
1194 void pa_discover_remove_source_output(struct userdata *u,
1195 pa_source_output *sout)
1197 pa_discover *discover;
1205 pa_assert_se((discover = u->discover));
1207 name = pa_utils_get_source_output_name(sout);
1209 pa_log("source-output '%s' going to be destroyed", name);
1211 if (!(node = pa_discover_remove_node_from_ptr_hash(u, sout)))
1212 pa_log_debug("can't find node for source-output (name '%s')", name);
1214 pa_log_debug("node found for '%s'. After clearing routes "
1215 "it will be destroyed", name);
1217 if (!(srcnod = pa_hashmap_get(discover->nodes.byptr, sout->source)))
1218 pa_log_debug("can't figure out where this stream is routed");
1220 pa_log_debug("clear route '%s' => '%s'",
1221 node->amname, srcnod->amname);
1223 /* FIXME: and actually do it ... */
1227 destroy_node(u, node);
1229 mir_router_make_routing(u);
1234 mir_node *pa_discover_find_node_by_key(struct userdata *u, const char *key)
1236 pa_discover *discover;
1240 pa_assert_se((discover = u->discover));
1243 node = pa_hashmap_get(discover->nodes.byname, key);
1250 mir_node *pa_discover_find_node_by_ptr(struct userdata *u, void *ptr)
1252 pa_discover *discover;
1256 pa_assert_se((discover = u->discover));
1259 node = pa_hashmap_get(discover->nodes.byptr, ptr);
1266 void pa_discover_add_node_to_ptr_hash(struct userdata *u,
1270 pa_discover *discover;
1275 pa_assert_se((discover = u->discover));
1277 pa_hashmap_put(discover->nodes.byptr, ptr, node);
1280 mir_node *pa_discover_remove_node_from_ptr_hash(struct userdata *u, void *ptr)
1282 pa_discover *discover;
1286 pa_assert_se((discover = u->discover));
1288 return pa_hashmap_remove(discover->nodes.byptr, ptr);
1292 static void handle_alsa_card(struct userdata *u, pa_card *card)
1299 memset(&data, 0, sizeof(data));
1300 data.visible = TRUE;
1301 data.amid = AM_ID_INVALID;
1302 data.implement = mir_device;
1303 data.paidx = PA_IDXSET_INVALID;
1304 data.stamp = pa_utils_get_stamp();
1306 cnam = pa_utils_get_card_name(card);
1307 udd = pa_proplist_gets(card->proplist, "module-udev-detect.discovered");
1309 if (udd && pa_streq(udd, "1")) {
1310 /* udev loaded alsa card */
1311 if (!strncmp(cnam, "alsa_card.", 10)) {
1313 handle_udev_loaded_card(u, card, &data, cid);
1318 /* statically loaded pci or usb card */
1321 pa_log_debug("ignoring unrecognized pci card '%s'", cnam);
1325 static void handle_bluetooth_card(struct userdata *u, pa_card *card)
1327 pa_discover *discover;
1328 pa_card_profile *prof;
1336 char paname[MAX_NAME_LENGTH+1];
1337 char amname[MAX_NAME_LENGTH+1];
1338 char key[MAX_NAME_LENGTH+1];
1341 pa_assert_se((discover = u->discover));
1343 cdescr = pa_proplist_gets(card->proplist, PA_PROP_DEVICE_DESCRIPTION);
1346 memset(paname, 0, sizeof(paname));
1347 memset(amname, 0, sizeof(amname));
1348 memset(key , 0, sizeof(key) );
1350 memset(&data, 0, sizeof(data));
1352 data.visible = TRUE;
1353 data.amid = AM_ID_INVALID;
1354 data.implement = mir_device;
1355 data.paidx = PA_IDXSET_INVALID;
1356 data.paname = paname;
1357 data.amname = amname;
1358 data.amdescr = (char *)cdescr;
1359 data.pacard.index = card->index;
1360 data.stamp = pa_utils_get_stamp();
1362 cnam = pa_utils_get_card_name(card);
1364 if (!strncmp(cnam, "bluez_card.", 11)) {
1367 cd = mir_constrain_create(u, "profile", mir_constrain_profile, cnam);
1369 PA_HASHMAP_FOREACH(prof, card->profiles, state) {
1370 data.available = TRUE;
1371 data.pacard.profile = prof->name;
1373 if (prof->n_sinks > 0) {
1374 data.direction = mir_output;
1375 data.channels = prof->max_sink_channels;
1376 data.amname = amname;
1378 snprintf(paname, sizeof(paname), "bluez_sink.%s", cid);
1379 snprintf(key, sizeof(key), "%s@%s", paname, prof->name);
1380 pa_classify_node_by_card(&data, card, prof, NULL);
1381 node = create_node(u, &data, NULL);
1383 cd = mir_constrain_create(u, "profile", mir_constrain_profile,
1386 mir_constrain_add_node(u, cd, node);
1389 if (prof->n_sources > 0) {
1390 data.direction = mir_input;
1391 data.channels = prof->max_source_channels;
1392 data.amname = amname;
1394 snprintf(paname, sizeof(paname), "bluez_source.%s", cid);
1395 snprintf(key, sizeof(key), "%s@%s", paname, prof->name);
1396 pa_classify_node_by_card(&data, card, prof, NULL);
1397 node = create_node(u, &data, NULL);
1399 cd = mir_constrain_create(u, "profile", mir_constrain_profile,
1402 mir_constrain_add_node(u, cd, node);
1406 if (!(prof = card->active_profile))
1407 pa_log("card '%s' has no active profile", card->name);
1409 pa_log_debug("card '%s' default profile '%s'",
1410 card->name, prof->name);
1413 schedule_card_check(u, card);
1418 static void handle_udev_loaded_card(struct userdata *u, pa_card *card,
1419 mir_node *data, char *cardid)
1421 pa_discover *discover;
1422 pa_card_profile *prof;
1423 pa_card_profile *active;
1425 const char *alsanam;
1427 char *sinks[MAX_CARD_TARGET+1];
1428 char *sources[MAX_CARD_TARGET+1];
1429 char buf[MAX_NAME_LENGTH+1];
1430 char paname[MAX_NAME_LENGTH+1];
1431 char amname[MAX_NAME_LENGTH+1];
1435 pa_assert(card->profiles);
1436 pa_assert_se((discover = u->discover));
1438 alsanam = pa_proplist_gets(card->proplist, "alsa.card_name");
1440 memset(amname, 0, sizeof(amname));
1442 data->paname = paname;
1443 data->amname = amname;
1444 data->amdescr = (char *)alsanam;
1446 data->pacard.index = card->index;
1448 active = card->active_profile;
1450 PA_HASHMAP_FOREACH(prof, card->profiles, state) {
1451 /* filtering: deal with selected profiles if requested so */
1452 if (discover->selected && (!active || (active && prof != active)))
1455 /* filtering: skip the 'off' profiles */
1456 if (!prof->n_sinks && !prof->n_sources)
1459 /* filtering: consider sinks with suitable amount channels */
1460 if (prof->n_sinks &&
1461 (prof->max_sink_channels < discover->chmin ||
1462 prof->max_sink_channels > discover->chmax ))
1465 /* filtering: consider sources with suitable amount channels */
1466 if (prof->n_sources &&
1467 (prof->max_source_channels < discover->chmin ||
1468 prof->max_source_channels > discover->chmax ))
1471 data->pacard.profile = prof->name;
1473 parse_profile_name(prof, sinks,sources, buf,sizeof(buf));
1475 data->direction = mir_output;
1476 data->channels = prof->max_sink_channels;
1477 for (i = 0; (sid = sinks[i]); i++) {
1478 snprintf(paname, sizeof(paname), "alsa_output.%s.%s", cardid, sid);
1479 handle_card_ports(u, data, card, prof);
1482 data->direction = mir_input;
1483 data->channels = prof->max_source_channels;
1484 for (i = 0; (sid = sources[i]); i++) {
1485 snprintf(paname, sizeof(paname), "alsa_input.%s.%s", cardid, sid);
1486 handle_card_ports(u, data, card, prof);
1492 static void handle_card_ports(struct userdata *u, mir_node *data,
1493 pa_card *card, pa_card_profile *prof)
1495 mir_node *node = NULL;
1496 pa_bool_t have_ports = FALSE;
1497 mir_constr_def *cd = NULL;
1498 char *amname = data->amname;
1499 pa_device_port *port;
1502 char key[MAX_NAME_LENGTH+1];
1510 PA_HASHMAP_FOREACH(port, card->ports, state) {
1512 * if this port did not belong to any profile
1513 * (ie. prof->profiles == NULL) we assume that this port
1514 * does works with all the profiles
1516 if (port->profiles && pa_hashmap_get(port->profiles, prof->name) &&
1517 ((port->is_input && data->direction == mir_input)||
1518 (port->is_output && data->direction == mir_output)))
1523 snprintf(key, sizeof(key), "%s@%s", data->paname, port->name);
1526 data->available = (port->available != PA_PORT_AVAILABLE_NO);
1528 data->amname = amname;
1529 data->paport = port->name;
1531 pa_classify_node_by_card(data, card, prof, port);
1533 node = create_node(u, data, &created);
1536 node->stamp = data->stamp;
1538 cd = mir_constrain_create(u, "port", mir_constrain_port,
1540 mir_constrain_add_node(u, cd, node);
1547 data->key = data->paname;
1548 data->available = TRUE;
1550 pa_classify_node_by_card(data, card, prof, NULL);
1552 node = create_node(u, data, &created);
1555 node->stamp = data->stamp;
1558 data->amname = amname;
1563 static mir_node *create_node(struct userdata *u, mir_node *data,
1564 pa_bool_t *created_ret)
1566 pa_discover *discover;
1573 pa_assert(data->key);
1574 pa_assert(data->paname);
1575 pa_assert_se((discover = u->discover));
1577 if ((node = pa_hashmap_get(discover->nodes.byname, data->key)))
1582 node = mir_node_create(u, data);
1583 pa_hashmap_put(discover->nodes.byname, node->key, node);
1585 mir_node_print(node, buf, sizeof(buf));
1586 pa_log_debug("new node:\n%s", buf);
1588 if (node->available)
1589 pa_audiomgr_register_node(u, node);
1593 *created_ret = created;
1598 static void destroy_node(struct userdata *u, mir_node *node)
1600 pa_discover *discover;
1604 pa_assert_se((discover = u->discover));
1607 removed = pa_hashmap_remove(discover->nodes.byname, node->key);
1609 if (node != removed) {
1611 pa_log("%s: confused with data structures: key mismatch. "
1612 " attempted to destroy '%s'; actually destroyed '%s'",
1613 __FILE__, node->key, removed->key);
1615 pa_log("%s: confused with data structures: node '%s' "
1616 "is not in the hash table", __FILE__, node->key);
1620 pa_log_debug("destroying node: %s / '%s'", node->key, node->amname);
1622 if (node->implement == mir_stream) {
1623 if (node->direction == mir_input) {
1625 pa_log_debug("removing multiplexer");
1630 pa_audiomgr_unregister_node(u, node);
1632 mir_constrain_remove_node(u, node);
1634 pa_loopback_destroy(u->loopback, u->core, node->loop);
1635 pa_multiplex_destroy(u->multiplex, u->core, node->mux);
1637 mir_node_destroy(u, node);
1641 static pa_bool_t update_node_availability(struct userdata *u,
1642 mir_direction direction,
1643 void *data, pa_device_port *port,
1644 pa_bool_t available)
1653 pa_assert(direction == mir_input || direction == mir_output);
1655 if ((key = node_key(u, direction, data, port, buf, sizeof(buf)))) {
1656 if (!(node = pa_discover_find_node_by_key(u, key)))
1657 pa_log_debug(" can't find node for sink (key '%s')", key);
1659 pa_log_debug(" node for '%s' found (key %s)",
1660 node->paname, node->key);
1661 if ((!available && node->available) ||
1662 ( available && !node->available) )
1664 node->available = available;
1667 pa_audiomgr_register_node(u, node);
1669 pa_audiomgr_unregister_node(u, node);
1671 return TRUE; /* routing needed */
1676 return FALSE; /* no routing needed */
1679 static char *get_name(char **string_ptr, int offs)
1681 char c, *name, *end;
1683 name = *string_ptr + offs;
1685 for (end = name; (c = *end); end++) {
1697 static void parse_profile_name(pa_card_profile *prof,
1707 pa_assert(prof->name);
1709 strncpy(buf, prof->name, buflen);
1710 buf[buflen-1] = '\0';
1712 memset(sinks, 0, sizeof(char *) * (MAX_CARD_TARGET+1));
1713 memset(sources, 0, sizeof(char *) * (MAX_CARD_TARGET+1));
1716 if (!strncmp(p, "output:", 7)) {
1717 if (i >= MAX_CARD_TARGET) {
1718 pa_log_debug("number of outputs exeeds the maximum %d in "
1719 "profile name '%s'", MAX_CARD_TARGET, prof->name);
1722 sinks[i++] = get_name(&p, 7);
1724 else if (!strncmp(p, "input:", 6)) {
1725 if (j >= MAX_CARD_TARGET) {
1726 pa_log_debug("number of inputs exeeds the maximum %d in "
1727 "profile name '%s'", MAX_CARD_TARGET, prof->name);
1730 sources[j++] = get_name(&p, 6);
1733 pa_log("%s: failed to parse profile name '%s'",
1734 __FILE__, prof->name);
1741 static char *node_key(struct userdata *u, mir_direction direction,
1742 void *data, pa_device_port *port, char *buf, size_t len)
1745 pa_card_profile *profile;
1749 pa_bool_t bluetooth;
1752 const char *profile_name;
1757 pa_assert(direction == mir_input || direction == mir_output);
1759 if (direction == mir_input) {
1760 pa_sink *sink = data;
1762 name = pa_utils_get_sink_name(sink);
1765 port = sink->active_port;
1768 pa_source *source = data;
1770 name = pa_utils_get_source_name(source);
1771 card = source->card;
1773 port = source->active_port;
1779 pa_assert_se((profile = card->active_profile));
1781 if (!u->state.profile)
1782 profile_name = profile->name;
1784 pa_log_debug("state.profile is not null. '%s' supresses '%s'",
1785 u->state.profile, profile->name);
1786 profile_name = u->state.profile;
1790 if (!(bus = pa_proplist_gets(card->proplist, PA_PROP_DEVICE_BUS))) {
1791 pa_log_debug("ignoring %s '%s' due to lack of '%s' property "
1792 "on its card", type, name, PA_PROP_DEVICE_BUS);
1796 pci = pa_streq(bus, "pci");
1797 usb = pa_streq(bus, "usb");
1798 bluetooth = pa_streq(bus, "bluetooth");
1800 if (!pci && !usb && !bluetooth) {
1801 pa_log_debug("ignoring %s '%s' due to unsupported bus type '%s' "
1802 "of its card", type, name, bus);
1808 snprintf(buf, len, "%s@%s", name, profile_name);
1815 snprintf(buf, len, "%s@%s", name, port->name);
1822 static pa_sink *make_output_prerouting(struct userdata *u,
1824 pa_channel_map *chmap,
1825 const char *media_role,
1826 mir_node **target_ret)
1830 pa_sink *sink = NULL;
1835 pa_assert_se((core = u->core));
1838 target = mir_router_make_prerouting(u, data);
1841 pa_log("there is no default route for the stream '%s'", data->amname);
1842 else if (target->paidx == PA_IDXSET_INVALID)
1843 pa_log("can't route to default '%s': no sink", target->amname);
1845 if (!(sink = pa_idxset_get_by_index(core->sinks, target->paidx)))
1846 pa_log("no route to default '%s': sink is gone", target->amname);
1848 if (pa_classify_multiplex_stream(data)) {
1849 data->mux = pa_multiplex_create(u->multiplex, core,
1850 sink->index, chmap, NULL,
1851 media_role, data->type);
1853 sink = pa_idxset_get_by_index(core->sinks,
1854 data->mux->sink_index);
1862 *target_ret = target;
1868 static pa_source *make_input_prerouting(struct userdata *u,
1870 const char *media_role,
1871 mir_node **target_ret)
1875 pa_source *source = NULL;
1879 pa_assert_se((core = u->core));
1881 target = mir_router_make_prerouting(u, data);
1884 pa_log("there is no default route for the stream '%s'", data->amname);
1885 else if (target->paidx == PA_IDXSET_INVALID)
1886 pa_log("can't route to default '%s': no source", target->amname);
1888 if (!(source = pa_idxset_get_by_index(core->sources, target->paidx)))
1889 pa_log("no route to default '%s': source is gone",target->amname);
1893 *target_ret = target;
1898 static mir_node_type get_stream_routing_class(pa_proplist *pl)
1906 if ((clid = pa_proplist_gets(pl, PA_PROP_ROUTING_CLASS_ID))) {
1907 type = strtol(clid, &e, 10);
1910 if (type >= mir_application_class_begin &&
1911 type < mir_application_class_end)
1918 return mir_node_type_unknown;
1923 static void deferred_routing_cb(pa_mainloop_api *m, void *d)
1925 struct userdata *u = d;
1931 pa_log_debug("deferred routing starts");
1933 mir_router_make_routing(u);
1937 static void schedule_deferred_routing(struct userdata *u)
1942 pa_assert_se((core = u->core));
1944 pa_log_debug("scheduling deferred routing");
1946 pa_mainloop_api_once(core->mainloop, deferred_routing_cb, u);
1950 static void card_check_cb(pa_mainloop_api *m, void *d)
1952 card_check_t *cc = d;
1958 int n_sink, n_source;
1964 pa_assert((u = cc->u));
1965 pa_assert((core = u->core));
1967 pa_log_debug("card check starts");
1969 if (!(card = pa_idxset_get_by_index(core->cards, cc->index)))
1970 pa_log_debug("card %u is gone", cc->index);
1972 n_sink = n_source = 0;
1974 PA_IDXSET_FOREACH(sink, core->sinks, idx) {
1975 if ((sink->card) && sink->card->index == card->index)
1979 PA_IDXSET_FOREACH(source, core->sources, idx) {
1980 if ((source->card) && source->card->index == card->index)
1984 if (n_sink || n_source) {
1985 pa_log_debug("found %u sinks and %u sources belonging to "
1986 "'%s' card", n_sink, n_source, card->name);
1987 pa_log_debug("nothing to do");
1990 pa_log_debug("card '%s' has no sinks/sources. Do routing ...",
1992 mir_router_make_routing(u);
2000 static void schedule_card_check(struct userdata *u, pa_card *card)
2007 pa_assert_se((core = u->core));
2009 pa_log_debug("scheduling card check");
2011 cc = pa_xnew0(card_check_t, 1);
2013 cc->index = card->index;
2015 pa_mainloop_api_once(core->mainloop, card_check_cb, cc);
2019 static void source_cleanup_cb(pa_mainloop_api *m, void *d)
2021 source_cleanup_t *sc = d;
2028 pa_assert((u = sc->u));
2029 pa_assert((core = u->core));
2031 pa_log_debug("source cleanup starts");
2033 pa_loopback_destroy(u->loopback, u->core, sc->loop);
2034 pa_multiplex_destroy(u->multiplex, u->core, sc->mux);
2036 pa_log_debug("source cleanup ends");
2042 static void schedule_source_cleanup(struct userdata *u, mir_node *node)
2045 source_cleanup_t *sc;
2049 pa_assert_se((core = u->core));
2051 pa_log_debug("scheduling source cleanup");
2053 sc = pa_xnew0(source_cleanup_t, 1);
2055 sc->mux = node->mux;
2056 sc->loop = node->loop;
2061 pa_mainloop_api_once(core->mainloop, source_cleanup_cb, sc);
2065 static void stream_uncork_cb(pa_mainloop_api *m, void *d)
2067 stream_uncork_t *suc = d;
2071 pa_sink_input *sinp;
2077 pa_assert((u = suc->u));
2078 pa_assert((core = u->core));
2080 pa_log_debug("start uncorking stream");
2082 if (!(sink = pa_idxset_get_by_index(core->sinks, suc->index))) {
2083 pa_log_debug("sink.%d gone", suc->index);
2087 if (!(sinp = pa_idxset_first(core->sink_inputs, &index))) {
2088 pa_log_debug("sink_input is gone");
2092 pa_sink_input_cork(sinp, FALSE);
2094 pa_log_debug("stream.%u uncorked", sinp->index);
2102 static void schedule_stream_uncorking(struct userdata *u, pa_sink *sink)
2105 stream_uncork_t *suc;
2109 pa_assert_se((core = u->core));
2111 pa_log_debug("scheduling stream uncorking");
2113 suc = pa_xnew0(stream_uncork_t, 1);
2115 suc->index = sink->index;
2117 pa_mainloop_api_once(core->mainloop, stream_uncork_cb, suc);
2124 * indent-tabs-mode: nil