From 42961553d80f47bf77e93ecc7423782ea26b6cec Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 24 Jul 2015 22:00:33 -0700 Subject: [PATCH] JACK: ability to list devices --- src/jack.cpp | 245 ++++++++++++++++++++++++++++++++++++++++++++++-- src/soundio.cpp | 9 +- src/soundio.h | 29 ++++-- src/soundio.hpp | 2 - 4 files changed, 261 insertions(+), 24 deletions(-) diff --git a/src/jack.cpp b/src/jack.cpp index 95686ce..bd3e5aa 100644 --- a/src/jack.cpp +++ b/src/jack.cpp @@ -8,6 +8,7 @@ #include "jack.hpp" #include "soundio.hpp" #include "atomics.hpp" +#include "os.hpp" #include #include @@ -16,18 +17,52 @@ static atomic_flag global_msg_callback_flag = ATOMIC_FLAG_INIT; struct SoundIoJack { jack_client_t *client; + SoundIoOsMutex *mutex; + SoundIoOsCond *cond; + // this one is ready to be read with flush_events. protected by mutex + struct SoundIoDevicesInfo *ready_devices_info; + bool initialized; + int sample_rate; + int buffer_size; }; -static void flush_events_jack(struct SoundIoPrivate *) { - soundio_panic("TODO"); +static void flush_events_jack(struct SoundIoPrivate *si) { + SoundIo *soundio = &si->pub; + SoundIoJack *sij = (SoundIoJack *)si->backend_data; + + bool change = false; + SoundIoDevicesInfo *old_devices_info = nullptr; + + soundio_os_mutex_lock(sij->mutex); + + if (sij->ready_devices_info) { + old_devices_info = si->safe_devices_info; + si->safe_devices_info = sij->ready_devices_info; + sij->ready_devices_info = nullptr; + change = true; + } + + soundio_os_mutex_unlock(sij->mutex); + + if (change) + soundio->on_devices_change(soundio); + + soundio_destroy_devices_info(old_devices_info); } -static void wait_events_jack(struct SoundIoPrivate *) { - soundio_panic("TODO"); +static void wait_events_jack(struct SoundIoPrivate *si) { + SoundIoJack *sij = (SoundIoJack *)si->backend_data; + flush_events_jack(si); + soundio_os_mutex_lock(sij->mutex); + soundio_os_cond_wait(sij->cond, sij->mutex); + soundio_os_mutex_unlock(sij->mutex); } -static void wakeup_jack(struct SoundIoPrivate *) { - soundio_panic("TODO"); +static void wakeup_jack(struct SoundIoPrivate *si) { + SoundIoJack *sij = (SoundIoJack *)si->backend_data; + soundio_os_mutex_lock(sij->mutex); + soundio_os_cond_signal(sij->cond, sij->mutex); + soundio_os_mutex_unlock(sij->mutex); } @@ -89,12 +124,159 @@ static int instream_pause_jack(struct SoundIoPrivate *, struct SoundIoInStreamPr soundio_panic("TODO"); } +static int refresh_devices(SoundIoPrivate *si) { + SoundIo *soundio = &si->pub; + SoundIoJack *sij = (SoundIoJack *)si->backend_data; + + SoundIoDevicesInfo *devices_info = create(); + 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) { + destroy(devices_info); + return SoundIoErrorNoMem; + } + + const char **port_name_ptr = port_names; + while (*port_name_ptr) { + const char *port_name = *port_name_ptr; + jack_port_t *port = jack_port_by_name(sij->client, port_name); + int flags = jack_port_flags(port); + + SoundIoDevice *device = create(); + if (!device) { + jack_free(port_names); + destroy(devices_info); + return SoundIoErrorNoMem; + } + device->ref_count = 1; + device->soundio = soundio; + device->is_raw = false; + device->name = strdup(port_name); + device->description = strdup(port_name); + device->layout_count = 1; + device->layouts = create(); + device->format_count = 1; + device->formats = create(); + + if (!device->name || !device->description || !device->layouts || !device->formats) { + jack_free(port_names); + soundio_device_unref(device); + destroy(devices_info); + return SoundIoErrorNoMem; + } + + // TODO figure out how jack does channel layout + device->layouts[0] = *soundio_channel_layout_get_builtin(SoundIoChannelLayoutIdMono); + device->current_layout = *soundio_channel_layout_get_builtin(SoundIoChannelLayoutIdMono); + + device->formats[0] = SoundIoFormatFloat32NE; + device->current_format = SoundIoFormatFloat32NE; + + device->sample_rate_min = sij->sample_rate; + device->sample_rate_max = sij->sample_rate; + device->sample_rate_current = sij->sample_rate; + + device->buffer_duration_min = sij->buffer_size / (double) sij->sample_rate; + device->buffer_duration_max = device->buffer_duration_min; + device->buffer_duration_current = device->buffer_duration_min; + + + SoundIoList *device_list; + if (flags & JackPortIsInput) { + device->purpose = SoundIoDevicePurposeOutput; + device_list = &devices_info->output_devices; + if (devices_info->default_output_index < 0 && (flags & JackPortIsPhysical)) + devices_info->default_output_index = device_list->length; + } else { + assert(flags & JackPortIsOutput); + device->purpose = SoundIoDevicePurposeInput; + device_list = &devices_info->input_devices; + if (devices_info->default_input_index < 0 && (flags & JackPortIsPhysical)) + devices_info->default_input_index = device_list->length; + } + + if (device_list->append(device)) { + jack_free(port_names); + soundio_device_unref(device); + destroy(devices_info); + return SoundIoErrorNoMem; + } + + port_name_ptr += 1; + } + + jack_free(port_names); + + soundio_os_mutex_lock(sij->mutex); + soundio_destroy_devices_info(sij->ready_devices_info); + sij->ready_devices_info = devices_info; + soundio_os_cond_signal(sij->cond, sij->mutex); + soundio->on_events_signal(soundio); + soundio_os_mutex_unlock(sij->mutex); + + return 0; +} + +static int process_callback(jack_nframes_t nframes, void *arg) { + ////SoundIoPrivate *si = (SoundIoPrivate *)arg; + //soundio_panic("TODO process callback"); + return 0; +} + +static int buffer_size_callback(jack_nframes_t nframes, void *arg) { + SoundIoPrivate *si = (SoundIoPrivate *)arg; + SoundIoJack *sij = (SoundIoJack *)si->backend_data; + sij->buffer_size = nframes; + if (sij->initialized) + refresh_devices(si); + return 0; +} + +static int sample_rate_callback(jack_nframes_t nframes, void *arg) { + SoundIoPrivate *si = (SoundIoPrivate *)arg; + SoundIoJack *sij = (SoundIoJack *)si->backend_data; + sij->sample_rate = nframes; + if (sij->initialized) + refresh_devices(si); + return 0; +} + +static int xrun_callback(void *arg) { + //SoundIoPrivate *si = (SoundIoPrivate *)arg; + soundio_panic("TODO xrun callback"); + return 0; +} + +static void port_registration_callback(jack_port_id_t port_id, int reg, void *arg) { + //SoundIoPrivate *si = (SoundIoPrivate *)arg; + soundio_panic("TODO port registration callback"); +} + +static int port_rename_calllback(jack_port_id_t port_id, + const char *old_name, const char *new_name, void *arg) +{ + //SoundIoPrivate *si = (SoundIoPrivate *)arg; + soundio_panic("TODO port rename callback"); + return 0; +} + +static void shutdown_callback(void *arg) { + //SoundIoPrivate *si = (SoundIoPrivate *)arg; + soundio_panic("TODO shutdown callback"); +} static void destroy_jack(SoundIoPrivate *si) { SoundIoJack *sij = (SoundIoJack *)si->backend_data; if (!sij) return; + if (sij->client) + jack_client_close(sij->client); + destroy(sij); si->backend_data = nullptr; } @@ -118,13 +300,23 @@ int soundio_jack_init(struct SoundIoPrivate *si) { } si->backend_data = sij; + sij->mutex = soundio_os_mutex_create(); + if (!sij->mutex) { + destroy_jack(si); + return SoundIoErrorNoMem; + } + + sij->cond = soundio_os_cond_create(); + if (!sij->cond) { + destroy_jack(si); + return SoundIoErrorNoMem; + } + jack_status_t status; sij->client = jack_client_open(soundio->app_name, JackNoStartServer, &status); if (!sij->client) { destroy_jack(si); assert(!(status & JackInvalidOption)); - if (status & JackNameNotUnique) - return SoundIoErrorNameNotUnique; if (status & JackShmFailure) return SoundIoErrorSystemResources; if (status & JackNoSuchClient) @@ -133,6 +325,43 @@ int soundio_jack_init(struct SoundIoPrivate *si) { return SoundIoErrorInitAudioBackend; } + int err; + if ((err = jack_set_process_callback(sij->client, process_callback, si))) { + destroy_jack(si); + return SoundIoErrorInitAudioBackend; + } + if ((err = jack_set_buffer_size_callback(sij->client, buffer_size_callback, si))) { + destroy_jack(si); + return SoundIoErrorInitAudioBackend; + } + if ((err = jack_set_sample_rate_callback(sij->client, sample_rate_callback, si))) { + destroy_jack(si); + return SoundIoErrorInitAudioBackend; + } + if ((err = jack_set_xrun_callback(sij->client, xrun_callback, si))) { + destroy_jack(si); + return SoundIoErrorInitAudioBackend; + } + if ((err = jack_set_port_registration_callback(sij->client, port_registration_callback, si))) { + destroy_jack(si); + return SoundIoErrorInitAudioBackend; + } + if ((err = jack_set_port_rename_callback(sij->client, port_rename_calllback, si))) { + destroy_jack(si); + return SoundIoErrorInitAudioBackend; + } + jack_on_shutdown(sij->client, shutdown_callback, si); + + if ((err = jack_activate(sij->client))) { + destroy_jack(si); + return SoundIoErrorInitAudioBackend; + } + + sij->initialized = true; + if ((err = refresh_devices(si))) { + destroy_jack(si); + return err; + } si->destroy = destroy_jack; si->flush_events = flush_events_jack; diff --git a/src/soundio.cpp b/src/soundio.cpp index f49a0a4..83a5703 100644 --- a/src/soundio.cpp +++ b/src/soundio.cpp @@ -70,7 +70,6 @@ const char *soundio_strerror(int error) { case SoundIoErrorBackendUnavailable: return "backend unavailable"; case SoundIoErrorStreaming: return "unrecoverable streaming failure"; case SoundIoErrorIncompatibleDevice: return "incompatible device"; - case SoundIoErrorNameNotUnique: return "name not unique"; case SoundIoErrorNoSuchClient: return "no such client"; } soundio_panic("invalid error enum value: %d", error); @@ -188,7 +187,7 @@ int soundio_connect(struct SoundIo *soundio) { int soundio_connect_backend(SoundIo *soundio, SoundIoBackend backend) { SoundIoPrivate *si = (SoundIoPrivate *)soundio; - if (si->current_backend) + if (soundio->current_backend) return SoundIoErrorInvalid; if (backend <= 0 || backend > SoundIoBackendDummy) @@ -214,7 +213,7 @@ void soundio_disconnect(struct SoundIo *soundio) { si->destroy(si); assert(!si->backend_data); - si->current_backend = SoundIoBackendNone; + soundio->current_backend = SoundIoBackendNone; soundio_destroy_devices_info(si->safe_devices_info); si->safe_devices_info = nullptr; @@ -390,7 +389,7 @@ int soundio_outstream_open(struct SoundIoOutStream *outstream) { outstream->sample_rate = clamp(device->sample_rate_min, 48000, device->sample_rate_max); if (!outstream->name) - outstream->name = "SoundIo"; + outstream->name = "SoundIoOutStream"; SoundIoOutStreamPrivate *os = (SoundIoOutStreamPrivate *)outstream; outstream->bytes_per_frame = soundio_get_bytes_per_frame(outstream->format, outstream->layout.channel_count); @@ -471,7 +470,7 @@ int soundio_instream_open(struct SoundIoInStream *instream) { instream->sample_rate = clamp(device->sample_rate_min, 48000, device->sample_rate_max); if (!instream->name) - instream->name = "SoundIo"; + instream->name = "SoundIoInStream"; instream->bytes_per_frame = soundio_get_bytes_per_frame(instream->format, instream->layout.channel_count); diff --git a/src/soundio.h b/src/soundio.h index 399302a..d06119a 100644 --- a/src/soundio.h +++ b/src/soundio.h @@ -26,7 +26,6 @@ enum SoundIoError { SoundIoErrorBackendUnavailable, SoundIoErrorStreaming, SoundIoErrorIncompatibleDevice, - SoundIoErrorNameNotUnique, SoundIoErrorNoSuchClient, }; @@ -198,7 +197,7 @@ struct SoundIoChannelArea { // The size of this struct is not part of the API or ABI. struct SoundIo { - // Defaults to NULL. Put whatever you want here. + // Optional. Put whatever you want here. Defaults to NULL. void *userdata; // Optional callback. Called when the list of devices change. Only called // during a call to soundio_flush_events or soundio_wait_events. @@ -216,9 +215,15 @@ struct SoundIo { // Optional: JACK info and error callbacks. // By default, libsoundio sets these to empty functions in order to // silence stdio messages from JACK. You may override the behavior by - // setting these to `NULL` or providing your own function. + // setting these to `NULL` or providing your own function. These are + // registered with JACK regardless of whether `soundio_connect_backend` + // succeeds. void (*jack_info_callback)(const char *msg); void (*jack_error_callback)(const char *msg); + + // Read-only. After calling `soundio_connect` or `soundio_connect_backend`, + // this field tells which backend is currently connected. + enum SoundIoBackend current_backend; }; // The size of this struct is not part of the API or ABI. @@ -285,9 +290,9 @@ struct SoundIoDevice { // Tells whether this device is an input device or an output device. enum SoundIoDevicePurpose purpose; - // raw means that you are directly opening the hardware device and not - // going through a proxy such as dmix or PulseAudio. When you open a raw - // device, other applications on the computer are not able to + // Raw means that you are directly opening the hardware device and not + // going through a proxy such as dmix, PulseAudio, or JACK. When you open a + // raw device, other applications on the computer are not able to // simultaneously access the device. Raw devices do not perform automatic // resampling and thus tend to have fewer formats available. bool is_raw; @@ -359,10 +364,13 @@ struct SoundIoOutStream { // invalid state and must be destroyed. // If you do not supply `error_callback`, the default callback will print // a message to stderr and then call `abort`. - // This is called fram the `write_callback` thread context. + // This is called from the `write_callback` thread context. void (*error_callback)(struct SoundIoOutStream *, int err); - // Name of the stream. This is used by PulseAudio. Defaults to "SoundIo". + // Optional: Name of the stream. Defaults to "SoundIoOutStream" + // PulseAudio uses this for the stream name. + // JACK uses this for the client name of the client that connects when you + // open the stream. const char *name; @@ -413,7 +421,10 @@ struct SoundIoInStream { // This is called from the `read_callback` thread context. void (*error_callback)(struct SoundIoInStream *, int err); - // Name of the stream. This is used by PulseAudio. Defaults to "SoundIo". + // Optional: Name of the stream. Defaults to "SoundIoInStream"; + // PulseAudio uses this for the stream name. + // JACK uses this for the client name of the client that connects when you + // open the stream. const char *name; // computed automatically when you call soundio_instream_open diff --git a/src/soundio.hpp b/src/soundio.hpp index 0ce1ac5..4a207f1 100644 --- a/src/soundio.hpp +++ b/src/soundio.hpp @@ -32,8 +32,6 @@ struct SoundIoInStreamPrivate { struct SoundIoPrivate { struct SoundIo pub; - enum SoundIoBackend current_backend; - // Safe to read from a single thread without a mutex. struct SoundIoDevicesInfo *safe_devices_info;