static void signal_callback(pa_mainloop_api*m, pa_signal_event *e, int sig, void *userdata) { pa_log_info(_("Got signal %s."), pa_sig2str(sig)); switch (sig) { #ifdef SIGUSR1 case SIGUSR1: pa_module_load(userdata, "module-cli", NULL); break; #endif #ifdef SIGUSR2 case SIGUSR2: pa_module_load(userdata, "module-cli-protocol-unix", NULL); break; #endif #ifdef SIGHUP case SIGHUP: { char *c = pa_full_status_string(userdata); pa_log_notice("%s", c); pa_xfree(c); return; } #endif case SIGINT: case SIGTERM: default: pa_log_info(_("Exiting.")); m->quit(m, 1); break; } }
static int detect_oss(pa_core *c, int just_one) { FILE *f; int n = 0, b = 0; if (!(f = pa_fopen_cloexec("/dev/sndstat", "r")) && !(f = pa_fopen_cloexec("/proc/sndstat", "r")) && !(f = pa_fopen_cloexec("/proc/asound/oss/sndstat", "r"))) { if (errno != ENOENT) pa_log_error("failed to open OSS sndstat device: %s", pa_cstrerror(errno)); return -1; } while (!feof(f)) { char line[256], args[64]; unsigned device; if (!fgets(line, sizeof(line), f)) break; line[strcspn(line, "\r\n")] = 0; if (!b) { b = pa_streq(line, "Audio devices:") || pa_streq(line, "Installed devices:"); continue; } if (line[0] == 0) break; if (sscanf(line, "%u: ", &device) == 1) { if (device == 0) pa_snprintf(args, sizeof(args), "device=/dev/dsp"); else pa_snprintf(args, sizeof(args), "device=/dev/dsp%u", device); if (!pa_module_load(c, "module-oss", args)) continue; } else if (sscanf(line, "pcm%u: ", &device) == 1) { /* FreeBSD support, the devices are named /dev/dsp0.0, dsp0.1 and so on */ pa_snprintf(args, sizeof(args), "device=/dev/dsp%u.0", device); if (!pa_module_load(c, "module-oss", args)) continue; } n++; if (just_one) break; } fclose(f); return n; }
/* When a source is created, loopback it to default sink */ static pa_hook_result_t source_put_hook_callback(pa_core *c, pa_source *source, void* userdata) { const char *s; const char *role; char *args; pa_assert(c); pa_assert(source); /* Only consider bluetooth sinks and sources */ s = pa_proplist_gets(source->proplist, PA_PROP_DEVICE_BUS); if (!s) return PA_HOOK_OK; if (!pa_streq(s, "bluetooth")) return PA_HOOK_OK; /* Restrict to A2DP profile (sink role) */ s = pa_proplist_gets(source->proplist, "bluetooth.protocol"); if (!s) return PA_HOOK_OK; if (pa_streq(s, "a2dp_source")) role = "music"; else { pa_log_debug("Profile %s cannot be selected for loopback", s); return PA_HOOK_OK; } /* Load module-loopback */ args = pa_sprintf_malloc("source=\"%s\" source_dont_move=\"true\" sink_input_properties=\"media.role=%s\"", source->name, role); (void) pa_module_load(c, "module-loopback", args); pa_xfree(args); return PA_HOOK_OK; }
static int detect_solaris(pa_core *c, int just_one) { struct stat s; const char *dev; char args[64]; dev = getenv("AUDIODEV"); if (!dev) dev = "/dev/audio"; if (stat(dev, &s) < 0) { if (errno != ENOENT) pa_log_error("failed to open device %s: %s", dev, pa_cstrerror(errno)); return -1; } if (!S_ISCHR(s.st_mode)) return 0; pa_snprintf(args, sizeof(args), "device=%s", dev); if (!pa_module_load(c, "module-solaris", args)) return 0; return 1; }
static void load_null_sink_if_needed(pa_core *c, pa_sink *sink, struct userdata* u) { pa_sink *target; uint32_t idx; char *t; pa_module *m; pa_assert(c); pa_assert(u); pa_assert(u->null_module == PA_INVALID_INDEX); /* Loop through all sinks and check to see if we have *any* * sinks. Ignore the sink passed in (if it's not null) */ for (target = pa_idxset_first(c->sinks, &idx); target; target = pa_idxset_next(c->sinks, &idx)) if (!sink || target != sink) break; if (target) return; pa_log_debug("Autoloading null-sink as no other sinks detected."); u->ignore = TRUE; t = pa_sprintf_malloc("sink_name=%s sink_properties='device.description=\"%s\"'", u->sink_name, _("Dummy Output")); m = pa_module_load(c, "module-null-sink", t); u->null_module = m ? m->index : PA_INVALID_INDEX; pa_xfree(t); u->ignore = FALSE; if (!m) pa_log_warn("Unable to load module-null-sink"); }
static int detect_alsa(pa_core *c, int just_one) { FILE *f; int n = 0, n_sink = 0, n_source = 0; if (!(f = pa_fopen_cloexec("/proc/asound/devices", "r"))) { if (errno != ENOENT) pa_log_error("open(\"/proc/asound/devices\") failed: %s", pa_cstrerror(errno)); return -1; } while (!feof(f)) { char line[64], args[64]; unsigned device, subdevice; int is_sink; if (!fgets(line, sizeof(line), f)) break; line[strcspn(line, "\r\n")] = 0; if (pa_endswith(line, "digital audio playback")) is_sink = 1; else if (pa_endswith(line, "digital audio capture")) is_sink = 0; else continue; if (just_one && is_sink && n_sink >= 1) continue; if (just_one && !is_sink && n_source >= 1) continue; if (sscanf(line, " %*i: [%u- %u]: ", &device, &subdevice) != 2) continue; /* Only one sink per device */ if (subdevice != 0) continue; pa_snprintf(args, sizeof(args), "device_id=%u", device); if (!pa_module_load(c, is_sink ? "module-alsa-sink" : "module-alsa-source", args)) continue; n++; if (is_sink) n_sink++; else n_source++; } fclose(f); return n; }
static int detect_waveout(pa_core *c, int just_one) { /* * FIXME: No point in enumerating devices until the plugin supports * selecting anything but the first. */ if (!pa_module_load(c, "module-waveout", "")) return 0; return 1; }
void pa_autoload_request(pa_core *c, const char *name, pa_namereg_type_t type) { pa_autoload_entry *e; pa_module *m; pa_assert(c); pa_assert(name); if (!c->autoload_hashmap || !(e = pa_hashmap_get(c->autoload_hashmap, name)) || (e->type != type)) return; if (e->in_action) return; e->in_action = 1; if (type == PA_NAMEREG_SINK || type == PA_NAMEREG_SOURCE) { if ((m = pa_module_load(c, e->module, e->argument))) m->auto_unload = 1; } e->in_action = 0; }
void load_module( struct pa_module_info *m, unsigned i, const char *name, const char *args, bool is_new) { struct userdata *u; pa_module *mod; pa_assert(m); pa_assert(name); pa_assert(args); u = m->userdata; if (!is_new) { if (m->items[i].index != PA_INVALID_INDEX && pa_streq(m->items[i].name, name) && pa_streq(m->items[i].args, args)) return; unload_one_module(m, i); } pa_log_debug("Loading module '%s' with args '%s' due to GConf/GSettings configuration.", name, args); m->items[i].name = pa_xstrdup(name); m->items[i].args = pa_xstrdup(args); m->items[i].index = PA_INVALID_INDEX; if (pa_module_load(&mod, u->core, name, args) < 0) { pa_log("pa_module_load() failed"); return; } m->items[i].index = mod->index; }
int pa__init(pa_module*m) { pa_modargs *ma = NULL; bool restore_device = true, restore_volume = true; pa_module *n; char *t; pa_assert(m); if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { pa_log("Failed to parse module arguments"); goto fail; } if (pa_modargs_get_value_boolean(ma, "restore_device", &restore_device) < 0 || pa_modargs_get_value_boolean(ma, "restore_volume", &restore_volume) < 0) { pa_log("restore_volume= and restore_device= expect boolean arguments"); goto fail; } pa_log_warn("We will now load module-stream-restore. Please make sure to remove module-volume-restore from your configuration."); t = pa_sprintf_malloc("restore_volume=%s restore_device=%s", pa_yes_no(restore_volume), pa_yes_no(restore_device)); n = pa_module_load(m->core, "module-stream-restore", t); pa_xfree(t); if (n) pa_module_unload_request(m, true); pa_modargs_free(ma); return n ? 0 : -1; fail: if (ma) pa_modargs_free(ma); return -1; }
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 userdata *u = userdata; struct tunnel *tnl; pa_assert(u); tnl = tunnel_new(interface, protocol, name, type, domain); if (event != AVAHI_RESOLVER_FOUND) pa_log("Resolving of '%s' failed: %s", name, avahi_strerror(avahi_client_errno(u->client))); else { char *device = NULL, *dname, *module_name, *args; const char *t; char at[AVAHI_ADDRESS_STR_MAX], cmt[PA_CHANNEL_MAP_SNPRINT_MAX]; pa_sample_spec ss; pa_channel_map cm; AvahiStringList *l; pa_bool_t channel_map_set = FALSE; pa_module *m; ss = u->core->default_sample_spec; cm = u->core->default_channel_map; for (l = txt; l; l = l->next) { char *key, *value; pa_assert_se(avahi_string_list_get_pair(l, &key, &value, NULL) == 0); if (pa_streq(key, "device")) { pa_xfree(device); device = value; value = NULL; } else if (pa_streq(key, "rate")) ss.rate = (uint32_t) atoi(value); else if (pa_streq(key, "channels")) ss.channels = (uint8_t) atoi(value); else if (pa_streq(key, "format")) ss.format = pa_parse_sample_format(value); else if (pa_streq(key, "channel_map")) { pa_channel_map_parse(&cm, value); channel_map_set = TRUE; } avahi_free(key); avahi_free(value); } if (!channel_map_set && cm.channels != ss.channels) pa_channel_map_init_extend(&cm, ss.channels, PA_CHANNEL_MAP_DEFAULT); if (!pa_sample_spec_valid(&ss)) { pa_log("Service '%s' contains an invalid sample specification.", name); avahi_free(device); goto finish; } if (!pa_channel_map_valid(&cm) || cm.channels != ss.channels) { pa_log("Service '%s' contains an invalid channel map.", name); avahi_free(device); goto finish; } if (device) dname = pa_sprintf_malloc("tunnel.%s.%s", host_name, device); else dname = pa_sprintf_malloc("tunnel.%s", host_name); if (!pa_namereg_is_valid_name(dname)) { pa_log("Cannot construct valid device name from credentials of service '%s'.", dname); avahi_free(device); pa_xfree(dname); goto finish; } t = strstr(type, "sink") ? "sink" : "source"; module_name = pa_sprintf_malloc("module-tunnel-%s", t); args = pa_sprintf_malloc("server=[%s]:%u " "%s=%s " "format=%s " "channels=%u " "rate=%u " "%s_name=%s " "channel_map=%s", avahi_address_snprint(at, sizeof(at), a), port, t, device, pa_sample_format_to_string(ss.format), ss.channels, ss.rate, t, dname, pa_channel_map_snprint(cmt, sizeof(cmt), &cm)); pa_log_debug("Loading %s with arguments '%s'", module_name, args); if ((m = pa_module_load(u->core, module_name, args))) { tnl->module_index = m->index; pa_hashmap_put(u->tunnels, tnl, tnl); tnl = NULL; } pa_xfree(module_name); pa_xfree(dname); pa_xfree(args); avahi_free(device); } finish: avahi_service_resolver_free(r); if (tnl) tunnel_free(tnl); }
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 userdata *u = userdata; struct tunnel *tnl; char *device = NULL, *nicename, *dname, *vname, *args; char *tp = NULL, *et = NULL, *cn = NULL; char *ch = NULL, *ss = NULL, *sr = NULL; char *t = NULL; char at[AVAHI_ADDRESS_STR_MAX]; AvahiStringList *l; pa_module *m; pa_assert(u); tnl = tunnel_new(interface, protocol, name, type, domain); if (event != AVAHI_RESOLVER_FOUND) { pa_log("Resolving of '%s' failed: %s", name, avahi_strerror(avahi_client_errno(u->client))); goto finish; } if ((nicename = strstr(name, "@"))) { ++nicename; if (strlen(nicename) > 0) { pa_log_debug("Found RAOP: %s", nicename); nicename = pa_escape(nicename, "\"'"); } else nicename = NULL; } for (l = txt; l; l = l->next) { char *key, *value; pa_assert_se(avahi_string_list_get_pair(l, &key, &value, NULL) == 0); pa_log_debug("Found key: '%s' with value: '%s'", key, value); if (pa_streq(key, "device")) { device = value; value = NULL; } else if (pa_streq(key, "tp")) { /* Transport protocol: * - TCP = only TCP, * - UDP = only UDP, * - TCP,UDP = both supported (UDP should be prefered) */ pa_xfree(tp); if (pa_str_in_list(value, ",", "UDP")) tp = pa_xstrdup("UDP"); else if (pa_str_in_list(value, ",", "TCP")) tp = pa_xstrdup("TCP"); else tp = pa_xstrdup(value); } else if (pa_streq(key, "et")) { /* Supported encryption types: * - 0 = none, * - 1 = RSA, * - 2 = FairPlay, * - 3 = MFiSAP, * - 4 = FairPlay SAPv2.5. */ pa_xfree(et); if (pa_str_in_list(value, ",", "1")) et = pa_xstrdup("RSA"); else et = pa_xstrdup("none"); } else if (pa_streq(key, "cn")) { /* Suported audio codecs: * - 0 = PCM, * - 1 = ALAC, * - 2 = AAC, * - 3 = AAC ELD. */ pa_xfree(cn); if (pa_str_in_list(value, ",", "1")) cn = pa_xstrdup("ALAC"); else cn = pa_xstrdup("PCM"); } else if (pa_streq(key, "md")) { /* Supported metadata types: * - 0 = text, * - 1 = artwork, * - 2 = progress. */ } else if (pa_streq(key, "pw")) { /* Requires password ? (true/false) */ } else if (pa_streq(key, "ch")) { /* Number of channels */ pa_xfree(ch); ch = pa_xstrdup(value); } else if (pa_streq(key, "ss")) { /* Sample size */ pa_xfree(ss); ss = pa_xstrdup(value); } else if (pa_streq(key, "sr")) { /* Sample rate */ pa_xfree(sr); sr = pa_xstrdup(value); } avahi_free(key); avahi_free(value); } if (device) dname = pa_sprintf_malloc("raop_output.%s.%s", host_name, device); else dname = pa_sprintf_malloc("raop_output.%s", host_name); if (!(vname = pa_namereg_make_valid_name(dname))) { pa_log("Cannot construct valid device name from '%s'.", dname); avahi_free(device); pa_xfree(dname); pa_xfree(tp); pa_xfree(et); pa_xfree(cn); pa_xfree(ch); pa_xfree(ss); pa_xfree(sr); goto finish; } avahi_free(device); pa_xfree(dname); avahi_address_snprint(at, sizeof(at), a); if (nicename) { args = pa_sprintf_malloc("server=[%s]:%u " "sink_name=%s " "sink_properties='device.description=\"%s (%s:%u)\"'", at, port, vname, nicename, at, port); pa_xfree(nicename); } else { args = pa_sprintf_malloc("server=[%s]:%u " "sink_name=%s" "sink_properties='device.description=\"%s:%u\"'", at, port, vname, at, port); } if (tp != NULL) { t = args; args = pa_sprintf_malloc("%s protocol=%s", args, tp); pa_xfree(tp); pa_xfree(t); } if (et != NULL) { t = args; args = pa_sprintf_malloc("%s encryption=%s", args, et); pa_xfree(et); pa_xfree(t); } if (cn != NULL) { t = args; args = pa_sprintf_malloc("%s codec=%s", args, cn); pa_xfree(cn); pa_xfree(t); } if (ch != NULL) { t = args; args = pa_sprintf_malloc("%s channels=%s", args, ch); pa_xfree(ch); pa_xfree(t); } if (ss != NULL) { t = args; args = pa_sprintf_malloc("%s format=%s", args, ss); pa_xfree(ss); pa_xfree(t); } if (sr != NULL) { t = args; args = pa_sprintf_malloc("%s rate=%s", args, sr); pa_xfree(sr); pa_xfree(t); } pa_log_debug("Loading module-raop-sink with arguments '%s'", args); if ((m = pa_module_load(u->core, "module-raop-sink", args))) { tnl->module_index = m->index; pa_hashmap_put(u->tunnels, tnl, tnl); tnl = NULL; } pa_xfree(vname); pa_xfree(args); finish: avahi_service_resolver_free(r); if (tnl) tunnel_free(tnl); }
static void verify_access(struct userdata *u, struct device *d) { char *cd; pa_card *card; bool accessible; pa_assert(u); pa_assert(d); cd = pa_sprintf_malloc("/dev/snd/controlC%s", path_get_card_id(d->path)); accessible = access(cd, R_OK|W_OK) >= 0; pa_log_debug("%s is accessible: %s", cd, pa_yes_no(accessible)); pa_xfree(cd); if (d->module == PA_INVALID_INDEX) { /* If we are not loaded, try to load */ if (accessible) { pa_module *m; bool busy; /* Check if any of the PCM devices that belong to this * card are currently busy. If they are, don't try to load * right now, to make sure the probing phase can * successfully complete. When the current user of the * device closes it we will get another notification via * inotify and can then recheck. */ busy = is_card_busy(path_get_card_id(d->path)); pa_log_debug("%s is busy: %s", d->path, pa_yes_no(busy)); if (!busy) { /* So, why do we rate limit here? It's certainly ugly, * but there seems to be no other way. Problem is * this: if we are unable to configure/probe an audio * device after opening it we will close it again and * the module initialization will fail. This will then * cause an inotify event on the device node which * will be forwarded to us. We then try to reopen the * audio device again, practically entering a busy * loop. * * A clean fix would be if we would be able to ignore * our own inotify close events. However, inotify * lacks such functionality. Also, during probing of * the device we cannot really distinguish between * other processes causing EBUSY or ourselves, which * means we have no way to figure out if the probing * during opening was canceled by a "try again" * failure or a "fatal" failure. */ if (pa_ratelimit_test(&d->ratelimit, PA_LOG_DEBUG)) { pa_log_debug("Loading module-alsa-card with arguments '%s'", d->args); m = pa_module_load(u->core, "module-alsa-card", d->args); if (m) { d->module = m->index; pa_log_info("Card %s (%s) module loaded.", d->path, d->card_name); } else pa_log_info("Card %s (%s) failed to load module.", d->path, d->card_name); } else pa_log_warn("Tried to configure %s (%s) more often than %u times in %llus", d->path, d->card_name, d->ratelimit.burst, (long long unsigned) (d->ratelimit.interval / PA_USEC_PER_SEC)); } } } else { /* If we are already loaded update suspend status with * accessible boolean */ if ((card = pa_namereg_get(u->core, d->card_name, PA_NAMEREG_CARD))) { pa_log_debug("%s all sinks and sources of card %s.", accessible ? "Resuming" : "Suspending", d->card_name); pa_card_suspend(card, !accessible, PA_SUSPEND_SESSION); } } }
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 userdata *u = userdata; struct tunnel *tnl; pa_assert(u); tnl = tunnel_new(interface, protocol, name, type, domain); if (event != AVAHI_RESOLVER_FOUND) pa_log("Resolving of '%s' failed: %s", name, avahi_strerror(avahi_client_errno(u->client))); else { char *device = NULL, *nicename, *dname, *vname, *args; char at[AVAHI_ADDRESS_STR_MAX]; AvahiStringList *l; pa_module *m; if ((nicename = strstr(name, "@"))) { ++nicename; if (strlen(nicename) > 0) { pa_log_debug("Found RAOP: %s", nicename); nicename = pa_escape(nicename, "\"'"); } else nicename = NULL; } for (l = txt; l; l = l->next) { char *key, *value; pa_assert_se(avahi_string_list_get_pair(l, &key, &value, NULL) == 0); pa_log_debug("Found key: '%s' with value: '%s'", key, value); if (pa_streq(key, "device")) { pa_xfree(device); device = value; value = NULL; } avahi_free(key); avahi_free(value); } if (device) dname = pa_sprintf_malloc("raop.%s.%s", host_name, device); else dname = pa_sprintf_malloc("raop.%s", host_name); if (!(vname = pa_namereg_make_valid_name(dname))) { pa_log("Cannot construct valid device name from '%s'.", dname); avahi_free(device); pa_xfree(dname); goto finish; } pa_xfree(dname); if (nicename) { args = pa_sprintf_malloc("server=[%s]:%u " "sink_name=%s " "sink_properties='device.description=\"%s\"'", avahi_address_snprint(at, sizeof(at), a), port, vname, nicename); pa_xfree(nicename); } else { args = pa_sprintf_malloc("server=[%s]:%u " "sink_name=%s", avahi_address_snprint(at, sizeof(at), a), port, vname); } pa_log_debug("Loading module-raop-sink with arguments '%s'", args); if ((m = pa_module_load(u->core, "module-raop-sink", args))) { tnl->module_index = m->index; pa_hashmap_put(u->tunnels, tnl, tnl); tnl = NULL; } pa_xfree(vname); pa_xfree(args); avahi_free(device); } finish: avahi_service_resolver_free(r); if (tnl) tunnel_free(tnl); }
static void on_set_debug(pa_core* c, pa_proplist* p, int debug_sink_id, const char* ext_args) { const char* device_name = pa_proplist_gets(p, PROPLIST_KEY_DEVICE); if (!device_name) { pa_log_error("device not specific!"); return; } int device_id = atoi(device_name); pa_sink* sink = NULL; pa_source* source = NULL; sink = find_sink(c, device_id); if (!sink) { source = find_source(c, device_id); } if (!sink && !source) { pa_log_error("sink/source of device id %s not found!", device_name); return; } pa_sample_spec ss; pa_channel_map map; if (sink) { ss = sink->sample_spec; map = sink->channel_map; } else { ss = source->sample_spec; map = source->channel_map; } if (debug_sink_id == EAUDIO_STREAM_DEVICE_VIRTUALOUPUT_REMOTE) { ss.rate = 8000; ss.format = PA_SAMPLE_U8; ss.channels = 1; map.channels = 1; } char sink_name[64]; GET_PLUGIN_NAME(sink_name, debug_sink_id); pa_module* link_module = NULL; const char* str_func = pa_proplist_gets(p, PROPLIST_VALUE_FUNC); bool func_on = true; if (str_func) { func_on = !strcmp(str_func, PROPLIST_VALUE_TRUE); } if (sink) { link_module = sink->module; } else if (source) { link_module = source->module; } if (func_on) { pa_pre_load_func_t f = pa_get_user_data(PA_USER_SINK_PRELOAD_FUNC); if (f) { f(sink_name, c, &ss, &map, ext_args); } pa_sink* remote_sink = pa_namereg_get(c, sink_name, PA_NAMEREG_SINK); if (!remote_sink) { pa_log_error("remote-sink load failed"); return; } char args[256] = { 0 }; char loopback_name[128] = { 0 }; const char* i_name = NULL; const char* o_name = NULL; if (sink) { i_name = sink->monitor_source->name; o_name = remote_sink->name; } else if (source) { i_name = source->name; o_name = remote_sink->name; } pa_snprintf(loopback_name, sizeof(loopback_name), "Loopback(%s->%s)", i_name, o_name); pa_snprintf(args, sizeof(args), "name=%s source=%s sink=%s token=%u %s", loopback_name, i_name, o_name, MAGIC_TOKEN_ID, ext_args ? ext_args : ""); pa_log_info("load-module module-loopback args: %s", args); pa_module* m = pa_module_load(c, "module-loopback", args); if (!m) { pa_log_error("on_set_debug module load err."); return; } if (link_module) { pa_proplist_sets(link_module->proplist, PA_PROP_LINK_LOOPBACK_ID, loopback_name); } } else { pa_sink* remote_sink = find_sink(c, debug_sink_id); if (remote_sink) { pa_log_info("unloading remote_sink %d", debug_sink_id); pa_module_unload_request(remote_sink->module, true); } } }