static void destroy_pa(struct SoundIoPrivate *si) { struct SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio; if (sipa->main_loop) pa_threaded_mainloop_stop(sipa->main_loop); pa_context_disconnect(sipa->pulse_context); pa_context_unref(sipa->pulse_context); soundio_destroy_devices_info(sipa->current_devices_info); soundio_destroy_devices_info(sipa->ready_devices_info); if (sipa->main_loop) pa_threaded_mainloop_free(sipa->main_loop); if (sipa->props) pa_proplist_free(sipa->props); free(sipa->default_sink_name); free(sipa->default_source_name); }
// always called even when refresh_devices succeeds static void cleanup_refresh_devices(struct SoundIoPrivate *si) { struct SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio; soundio_destroy_devices_info(sipa->current_devices_info); sipa->current_devices_info = NULL; free(sipa->default_sink_name); sipa->default_sink_name = NULL; free(sipa->default_source_name); sipa->default_source_name = NULL; }
static void my_flush_events(struct SoundIoPrivate *si, bool wait) { struct SoundIo *soundio = &si->pub; struct SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio; bool change = false; bool cb_shutdown = false; struct SoundIoDevicesInfo *old_devices_info = NULL; pa_threaded_mainloop_lock(sipa->main_loop); if (wait) pa_threaded_mainloop_wait(sipa->main_loop); if (sipa->device_scan_queued && !sipa->connection_err) { sipa->device_scan_queued = false; sipa->connection_err = refresh_devices(si); cleanup_refresh_devices(si); } if (sipa->connection_err && !sipa->emitted_shutdown_cb) { sipa->emitted_shutdown_cb = true; cb_shutdown = true; } else if (sipa->ready_devices_info) { old_devices_info = si->safe_devices_info; si->safe_devices_info = sipa->ready_devices_info; sipa->ready_devices_info = NULL; change = true; } pa_threaded_mainloop_unlock(sipa->main_loop); if (cb_shutdown) soundio->on_backend_disconnect(soundio, sipa->connection_err); else if (change) soundio->on_devices_change(soundio); soundio_destroy_devices_info(old_devices_info); }
void soundio_disconnect(struct SoundIo *soundio) { SoundIoPrivate *si = (SoundIoPrivate *)soundio; if (!si) return; if (si->destroy) si->destroy(si); memset(&si->backend_data, 0, sizeof(SoundIoBackendData)); soundio->current_backend = SoundIoBackendNone; soundio_destroy_devices_info(si->safe_devices_info); si->safe_devices_info = nullptr; si->destroy = nullptr; si->flush_events = nullptr; si->wait_events = nullptr; si->wakeup = nullptr; si->force_device_scan = nullptr; si->outstream_open = nullptr; si->outstream_destroy = nullptr; si->outstream_start = nullptr; si->outstream_begin_write = nullptr; si->outstream_end_write = nullptr; si->outstream_clear_buffer = nullptr; si->outstream_pause = nullptr; si->outstream_get_latency = nullptr; si->instream_open = nullptr; si->instream_destroy = nullptr; si->instream_start = nullptr; si->instream_begin_read = nullptr; si->instream_end_read = nullptr; si->instream_pause = nullptr; si->instream_get_latency = nullptr; }
static int refresh_devices_bare(SoundIoPrivate *si) { SoundIo *soundio = &si->pub; SoundIoJack *sij = &si->backend_data.jack; if (sij->is_shutdown) return SoundIoErrorBackendDisconnected; SoundIoDevicesInfo *devices_info = allocate<SoundIoDevicesInfo>(1); if (!devices_info) return SoundIoErrorNoMem; devices_info->default_output_index = -1; devices_info->default_input_index = -1; const char **port_names = jack_get_ports(sij->client, nullptr, nullptr, 0); if (!port_names) { soundio_destroy_devices_info(devices_info); return SoundIoErrorNoMem; } SoundIoList<SoundIoJackClient> clients = {0}; const char **port_name_ptr = port_names; for (; *port_name_ptr; port_name_ptr += 1) { const char *client_and_port_name = *port_name_ptr; int client_and_port_name_len = strlen(client_and_port_name); jack_port_t *jport = jack_port_by_name(sij->client, client_and_port_name); if (!jport) { // This refresh devices scan is already outdated. Just give up and // let refresh_devices be called again. jack_free(port_names); soundio_destroy_devices_info(devices_info); return SoundIoErrorInterrupted; } int flags = jack_port_flags(jport); const char *port_type = jack_port_type(jport); if (strcmp(port_type, JACK_DEFAULT_AUDIO_TYPE) != 0) { // we don't know how to support such a port continue; } SoundIoDeviceAim aim = (flags & JackPortIsInput) ? SoundIoDeviceAimOutput : SoundIoDeviceAimInput; bool is_physical = flags & JackPortIsPhysical; const char *client_name = nullptr; const char *port_name = nullptr; int client_name_len; int port_name_len; split_str(client_and_port_name, client_and_port_name_len, ':', &client_name, &client_name_len, &port_name, &port_name_len); if (!client_name || !port_name) { // device does not have colon, skip it continue; } SoundIoJackClient *client = find_or_create_client(&clients, aim, is_physical, client_name, client_name_len); if (!client) { jack_free(port_names); soundio_destroy_devices_info(devices_info); return SoundIoErrorNoMem; } if (client->port_count >= SOUNDIO_MAX_CHANNELS) { // we hit the channel limit, skip the leftovers continue; } SoundIoJackPort *port = &client->ports[client->port_count++]; port->full_name = client_and_port_name; port->full_name_len = client_and_port_name_len; port->name = port_name; port->name_len = port_name_len; port->channel_id = soundio_parse_channel_id(port_name, port_name_len); jack_latency_callback_mode_t latency_mode = (aim == SoundIoDeviceAimOutput) ? JackPlaybackLatency : JackCaptureLatency; jack_port_get_latency_range(jport, latency_mode, &port->latency_range); } for (int i = 0; i < clients.length; i += 1) { SoundIoJackClient *client = &clients.at(i); if (client->port_count <= 0) continue; SoundIoDevicePrivate *dev = allocate<SoundIoDevicePrivate>(1); if (!dev) { jack_free(port_names); soundio_destroy_devices_info(devices_info); return SoundIoErrorNoMem; } SoundIoDevice *device = &dev->pub; SoundIoDeviceJack *dj = &dev->backend_data.jack; int description_len = client->name_len + 3 + 2 * client->port_count; for (int port_index = 0; port_index < client->port_count; port_index += 1) { SoundIoJackPort *port = &client->ports[port_index]; description_len += port->name_len; } dev->destruct = destruct_device; device->ref_count = 1; device->soundio = soundio; device->is_raw = false; device->aim = client->aim; device->id = soundio_str_dupe(client->name, client->name_len); device->name = allocate<char>(description_len); device->current_format = SoundIoFormatFloat32NE; device->sample_rate_count = 1; device->sample_rates = &dev->prealloc_sample_rate_range; device->sample_rates[0].min = sij->sample_rate; device->sample_rates[0].max = sij->sample_rate; device->sample_rate_current = sij->sample_rate; device->software_latency_current = sij->period_size / (double) sij->sample_rate; device->software_latency_min = sij->period_size / (double) sij->sample_rate; device->software_latency_max = sij->period_size / (double) sij->sample_rate; dj->port_count = client->port_count; dj->ports = allocate<SoundIoDeviceJackPort>(dj->port_count); if (!device->id || !device->name || !dj->ports) { jack_free(port_names); soundio_device_unref(device); soundio_destroy_devices_info(devices_info); return SoundIoErrorNoMem; } for (int port_index = 0; port_index < client->port_count; port_index += 1) { SoundIoJackPort *port = &client->ports[port_index]; SoundIoDeviceJackPort *djp = &dj->ports[port_index]; djp->full_name = soundio_str_dupe(port->full_name, port->full_name_len); djp->full_name_len = port->full_name_len; djp->channel_id = port->channel_id; djp->latency_range = port->latency_range; if (!djp->full_name) { jack_free(port_names); soundio_device_unref(device); soundio_destroy_devices_info(devices_info); return SoundIoErrorNoMem; } } memcpy(device->name, client->name, client->name_len); memcpy(&device->name[client->name_len], ": ", 2); int index = client->name_len + 2; for (int port_index = 0; port_index < client->port_count; port_index += 1) { SoundIoJackPort *port = &client->ports[port_index]; memcpy(&device->name[index], port->name, port->name_len); index += port->name_len; if (port_index + 1 < client->port_count) { memcpy(&device->name[index], ", ", 2); index += 2; } } device->current_layout.channel_count = client->port_count; bool any_invalid = false; for (int port_index = 0; port_index < client->port_count; port_index += 1) { SoundIoJackPort *port = &client->ports[port_index]; device->current_layout.channels[port_index] = port->channel_id; any_invalid = any_invalid || (port->channel_id == SoundIoChannelIdInvalid); } if (any_invalid) { const struct SoundIoChannelLayout *layout = soundio_channel_layout_get_default(client->port_count); if (layout) device->current_layout = *layout; } else { soundio_channel_layout_detect_builtin(&device->current_layout); } device->layout_count = 1; device->layouts = &device->current_layout; device->format_count = 1; device->formats = &dev->prealloc_format; device->formats[0] = device->current_format; SoundIoList<SoundIoDevice *> *device_list; if (device->aim == SoundIoDeviceAimOutput) { device_list = &devices_info->output_devices; if (devices_info->default_output_index < 0 && client->is_physical) devices_info->default_output_index = device_list->length; } else { assert(device->aim == SoundIoDeviceAimInput); device_list = &devices_info->input_devices; if (devices_info->default_input_index < 0 && client->is_physical) devices_info->default_input_index = device_list->length; } if (device_list->append(device)) { soundio_device_unref(device); soundio_destroy_devices_info(devices_info); return SoundIoErrorNoMem; } } jack_free(port_names); soundio_destroy_devices_info(si->safe_devices_info); si->safe_devices_info = devices_info; return 0; }
// call this while holding the main loop lock static int refresh_devices(struct SoundIoPrivate *si) { struct SoundIo *soundio = &si->pub; struct SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio; assert(!sipa->current_devices_info); sipa->current_devices_info = ALLOCATE(struct SoundIoDevicesInfo, 1); if (!sipa->current_devices_info) return SoundIoErrorNoMem; pa_operation *list_sink_op = pa_context_get_sink_info_list(sipa->pulse_context, sink_info_callback, si); pa_operation *list_source_op = pa_context_get_source_info_list(sipa->pulse_context, source_info_callback, si); pa_operation *server_info_op = pa_context_get_server_info(sipa->pulse_context, server_info_callback, si); int err; if ((err = perform_operation(si, list_sink_op))) { return err; } if ((err = perform_operation(si, list_source_op))) { return err; } if ((err = perform_operation(si, server_info_op))) { return err; } if (sipa->device_query_err) { return sipa->device_query_err; } // based on the default sink name, figure out the default output index // if the name doesn't match just pick the first one. if there are no // devices then we need to set it to -1. sipa->current_devices_info->default_output_index = -1; sipa->current_devices_info->default_input_index = -1; if (sipa->current_devices_info->input_devices.length > 0) { sipa->current_devices_info->default_input_index = 0; for (int i = 0; i < sipa->current_devices_info->input_devices.length; i += 1) { struct SoundIoDevice *device = SoundIoListDevicePtr_val_at( &sipa->current_devices_info->input_devices, i); assert(device->aim == SoundIoDeviceAimInput); if (strcmp(device->id, sipa->default_source_name) == 0) { sipa->current_devices_info->default_input_index = i; } } } if (sipa->current_devices_info->output_devices.length > 0) { sipa->current_devices_info->default_output_index = 0; for (int i = 0; i < sipa->current_devices_info->output_devices.length; i += 1) { struct SoundIoDevice *device = SoundIoListDevicePtr_val_at( &sipa->current_devices_info->output_devices, i); assert(device->aim == SoundIoDeviceAimOutput); if (strcmp(device->id, sipa->default_sink_name) == 0) { sipa->current_devices_info->default_output_index = i; } } } soundio_destroy_devices_info(sipa->ready_devices_info); sipa->ready_devices_info = sipa->current_devices_info; sipa->current_devices_info = NULL; pa_threaded_mainloop_signal(sipa->main_loop, 0); soundio->on_events_signal(soundio); return 0; }