diff --git a/README.md b/README.md index 99ffd97..29af981 100644 --- a/README.md +++ b/README.md @@ -233,12 +233,16 @@ view `coverage/index.html` in a browser. ## Roadmap 0. implement JACK backend, get examples working + 0. Steal PulseAudio's default channel maps per channel count + 0. Ability to parse PulseAudio's "front-left" "front-right" channel label strings + 0. When two soundio clients are talking to each other, use port names to + negotiate channel maps. 0. why does pulseaudio microphone use up all the CPU? 0. merge in/out stream structures and functions? 0. implement CoreAudio (OSX) backend, get examples working 0. implement WASAPI (Windows) backend, get examples working 0. implement ASIO (Windows) backend, get examples working - 0. Avoid calling `panic` in PulseAudio. + 0. Avoid calling `soundio_panic` in PulseAudio. 0. Figure out a way to test prebuf. I suspect prebuf not working for ALSA which is why we have to pre-fill the ring buffer with silence for the microphone example. @@ -257,6 +261,9 @@ view `coverage/index.html` in a browser. 0. Allow calling functions from outside the callbacks as long as they first call lock and then unlock when done. 0. Should pause/resume be callable from outside the callbacks? + 0. device.name -> device.id, device.description -> device.name + 0. PulseAudio: when opening a device start it corked that way we can get + accurate buffer readings 0. clean up API and improve documentation - make sure every function which can return an error documents which errors it can return diff --git a/src/alsa.cpp b/src/alsa.cpp index 15e91a1..3f89687 100644 --- a/src/alsa.cpp +++ b/src/alsa.cpp @@ -7,8 +7,6 @@ #include "alsa.hpp" #include "soundio.hpp" -#include "os.hpp" -#include "atomics.hpp" #include #include @@ -25,21 +23,6 @@ static snd_pcm_access_t prioritized_access_types[] = { }; -struct SoundIoAlsa { - SoundIoOsMutex *mutex; - SoundIoOsCond *cond; - - struct SoundIoOsThread *thread; - atomic_flag abort_flag; - int notify_fd; - int notify_wd; - atomic_bool have_devices_flag; - int notify_pipe_fd[2]; - - // this one is ready to be read with flush_events. protected by mutex - struct SoundIoDevicesInfo *ready_devices_info; -}; - struct SoundIoOutStreamAlsa { snd_pcm_t *handle; snd_pcm_chmap_t *chmap; @@ -85,9 +68,7 @@ static void wakeup_device_poll(SoundIoAlsa *sia) { } static void destroy_alsa(SoundIoPrivate *si) { - SoundIoAlsa *sia = (SoundIoAlsa *)si->backend_data; - if (!sia) - return; + SoundIoAlsa *sia = &si->backend_data.alsa; if (sia->thread) { sia->abort_flag.clear(); @@ -108,9 +89,6 @@ static void destroy_alsa(SoundIoPrivate *si) { close(sia->notify_pipe_fd[0]); close(sia->notify_pipe_fd[1]); close(sia->notify_fd); - - destroy(sia); - si->backend_data = nullptr; } static char * str_partition_on_char(char *str, char c) { @@ -517,7 +495,7 @@ static inline bool str_has_prefix(const char *big_str, const char *prefix) { static int refresh_devices(SoundIoPrivate *si) { SoundIo *soundio = &si->pub; - SoundIoAlsa *sia = (SoundIoAlsa *)si->backend_data; + SoundIoAlsa *sia = &si->backend_data.alsa; SoundIoDevicesInfo *devices_info = create(); if (!devices_info) @@ -761,7 +739,7 @@ static int refresh_devices(SoundIoPrivate *si) { static void device_thread_run(void *arg) { SoundIoPrivate *si = (SoundIoPrivate *)arg; - SoundIoAlsa *sia = (SoundIoAlsa *)si->backend_data; + SoundIoAlsa *sia = &si->backend_data.alsa; // Some systems cannot read integer variables if they are not // properly aligned. On other systems, incorrect alignment may @@ -864,7 +842,7 @@ static void block_until_have_devices(SoundIoAlsa *sia) { static void flush_events(SoundIoPrivate *si) { SoundIo *soundio = &si->pub; - SoundIoAlsa *sia = (SoundIoAlsa *)si->backend_data; + SoundIoAlsa *sia = &si->backend_data.alsa; block_until_have_devices(sia); bool change = false; @@ -888,7 +866,7 @@ static void flush_events(SoundIoPrivate *si) { } static void wait_events(SoundIoPrivate *si) { - SoundIoAlsa *sia = (SoundIoAlsa *)si->backend_data; + SoundIoAlsa *sia = &si->backend_data.alsa; flush_events(si); soundio_os_mutex_lock(sia->mutex); soundio_os_cond_wait(sia->cond, sia->mutex); @@ -896,7 +874,7 @@ static void wait_events(SoundIoPrivate *si) { } static void wakeup(SoundIoPrivate *si) { - SoundIoAlsa *sia = (SoundIoAlsa *)si->backend_data; + SoundIoAlsa *sia = &si->backend_data.alsa; soundio_os_mutex_lock(sia->mutex); soundio_os_cond_signal(sia->cond, sia->mutex); soundio_os_mutex_unlock(sia->mutex); @@ -1695,15 +1673,9 @@ static int instream_pause_alsa(struct SoundIoPrivate *si, struct SoundIoInStream } int soundio_alsa_init(SoundIoPrivate *si) { + SoundIoAlsa *sia = &si->backend_data.alsa; int err; - assert(!si->backend_data); - SoundIoAlsa *sia = create(); - if (!sia) { - destroy_alsa(si); - return SoundIoErrorNoMem; - } - si->backend_data = sia; sia->notify_fd = -1; sia->notify_wd = -1; sia->have_devices_flag.store(false); diff --git a/src/alsa.hpp b/src/alsa.hpp index e753d55..0311d84 100644 --- a/src/alsa.hpp +++ b/src/alsa.hpp @@ -8,7 +8,28 @@ #ifndef SOUNDIO_ALSA_HPP #define SOUNDIO_ALSA_HPP +#include "os.hpp" +#include "atomics.hpp" + int soundio_alsa_init(struct SoundIoPrivate *si); -#endif +struct SoundIoDeviceAlsa { +}; + +struct SoundIoAlsa { + SoundIoOsMutex *mutex; + SoundIoOsCond *cond; + + struct SoundIoOsThread *thread; + atomic_flag abort_flag; + int notify_fd; + int notify_wd; + atomic_bool have_devices_flag; + int notify_pipe_fd[2]; + + // this one is ready to be read with flush_events. protected by mutex + struct SoundIoDevicesInfo *ready_devices_info; +}; + +#endif diff --git a/src/dummy.cpp b/src/dummy.cpp index 410642f..9cad6b6 100644 --- a/src/dummy.cpp +++ b/src/dummy.cpp @@ -7,7 +7,6 @@ #include "dummy.hpp" #include "soundio.hpp" -#include "os.hpp" #include "atomics.hpp" #include "ring_buffer.hpp" @@ -39,12 +38,6 @@ struct SoundIoInStreamDummy { SoundIoChannelArea areas[SOUNDIO_MAX_CHANNELS]; }; -struct SoundIoDummy { - SoundIoOsMutex *mutex; - SoundIoOsCond *cond; - bool devices_emitted; -}; - static void playback_thread_run(void *arg) { SoundIoOutStreamPrivate *os = (SoundIoOutStreamPrivate *)arg; SoundIoOutStream *outstream = &os->pub; @@ -127,23 +120,18 @@ static void capture_thread_run(void *arg) { } static void destroy_dummy(SoundIoPrivate *si) { - SoundIoDummy *sid = (SoundIoDummy *)si->backend_data; - if (!sid) - return; + SoundIoDummy *sid = &si->backend_data.dummy; if (sid->cond) soundio_os_cond_destroy(sid->cond); if (sid->mutex) soundio_os_mutex_destroy(sid->mutex); - - destroy(sid); - si->backend_data = nullptr; } static void flush_events(SoundIoPrivate *si) { SoundIo *soundio = &si->pub; - SoundIoDummy *sid = (SoundIoDummy *)si->backend_data; + SoundIoDummy *sid = &si->backend_data.dummy; if (sid->devices_emitted) return; sid->devices_emitted = true; @@ -151,13 +139,13 @@ static void flush_events(SoundIoPrivate *si) { } static void wait_events(SoundIoPrivate *si) { - SoundIoDummy *sid = (SoundIoDummy *)si->backend_data; + SoundIoDummy *sid = &si->backend_data.dummy; flush_events(si); soundio_os_cond_wait(sid->cond, nullptr); } static void wakeup(SoundIoPrivate *si) { - SoundIoDummy *sid = (SoundIoDummy *)si->backend_data; + SoundIoDummy *sid = &si->backend_data.dummy; soundio_os_cond_signal(sid->cond, nullptr); } @@ -468,13 +456,7 @@ static int set_all_device_channel_layouts(SoundIoDevice *device) { int soundio_dummy_init(SoundIoPrivate *si) { SoundIo *soundio = &si->pub; - assert(!si->backend_data); - SoundIoDummy *sid = create(); - if (!sid) { - destroy_dummy(si); - return SoundIoErrorNoMem; - } - si->backend_data = sid; + SoundIoDummy *sid = &si->backend_data.dummy; sid->mutex = soundio_os_mutex_create(); if (!sid->mutex) { diff --git a/src/dummy.hpp b/src/dummy.hpp index f6f7637..f10827d 100644 --- a/src/dummy.hpp +++ b/src/dummy.hpp @@ -8,6 +8,18 @@ #ifndef SOUNDIO_DUMMY_HPP #define SOUNDIO_DUMMY_HPP +#include "os.hpp" + int soundio_dummy_init(struct SoundIoPrivate *si); +struct SoundIoDummy { + SoundIoOsMutex *mutex; + SoundIoOsCond *cond; + bool devices_emitted; +}; + +struct SoundIoDeviceDummy { + +}; + #endif diff --git a/src/jack.cpp b/src/jack.cpp index 9da9dfa..fdfbcd3 100644 --- a/src/jack.cpp +++ b/src/jack.cpp @@ -8,23 +8,15 @@ #include "jack.hpp" #include "soundio.hpp" #include "atomics.hpp" -#include "os.hpp" #include "list.hpp" -#include #include static atomic_flag global_msg_callback_flag = ATOMIC_FLAG_INIT; -struct SoundIoJack { +struct SoundIoOutStreamJack { 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; + jack_port_t *ports[SOUNDIO_MAX_CHANNELS]; }; struct SoundIoJackPort { @@ -37,12 +29,13 @@ struct SoundIoJackClient { int name_len; bool is_physical; SoundIoDevicePurpose purpose; - SoundIoList ports; + SoundIoJackPort ports[SOUNDIO_MAX_CHANNELS]; + int port_count; }; static void flush_events_jack(struct SoundIoPrivate *si) { SoundIo *soundio = &si->pub; - SoundIoJack *sij = (SoundIoJack *)si->backend_data; + SoundIoJack *sij = &si->backend_data.jack; bool change = false; SoundIoDevicesInfo *old_devices_info = nullptr; @@ -65,7 +58,7 @@ static void flush_events_jack(struct SoundIoPrivate *si) { } static void wait_events_jack(struct SoundIoPrivate *si) { - SoundIoJack *sij = (SoundIoJack *)si->backend_data; + SoundIoJack *sij = &si->backend_data.jack; flush_events_jack(si); soundio_os_mutex_lock(sij->mutex); soundio_os_cond_wait(sij->cond, sij->mutex); @@ -73,69 +66,150 @@ static void wait_events_jack(struct SoundIoPrivate *si) { } static void wakeup_jack(struct SoundIoPrivate *si) { - SoundIoJack *sij = (SoundIoJack *)si->backend_data; + SoundIoJack *sij = &si->backend_data.jack; soundio_os_mutex_lock(sij->mutex); soundio_os_cond_signal(sij->cond, sij->mutex); soundio_os_mutex_unlock(sij->mutex); } - -static int outstream_open_jack(struct SoundIoPrivate *, struct SoundIoOutStreamPrivate *) { - soundio_panic("TODO"); +static int outstream_process_callback(jack_nframes_t nframes, void *arg) { + SoundIoOutStreamPrivate *os = (SoundIoOutStreamPrivate *)arg; + SoundIoOutStream *outstream = &os->pub; + outstream->write_callback(outstream, nframes); + return 0; } -static void outstream_destroy_jack(struct SoundIoPrivate *, struct SoundIoOutStreamPrivate *) { - soundio_panic("TODO"); +static void outstream_destroy_jack(struct SoundIoPrivate *is, struct SoundIoOutStreamPrivate *os) { + SoundIoOutStreamJack *osj = (SoundIoOutStreamJack *) os->backend_data; + if (!osj) + return; + + jack_client_close(osj->client); + + destroy(osj); + os->backend_data = nullptr; } -static int outstream_start_jack(struct SoundIoPrivate *, struct SoundIoOutStreamPrivate *) { - soundio_panic("TODO"); +static int outstream_open_jack(struct SoundIoPrivate *si, struct SoundIoOutStreamPrivate *os) { + SoundIoOutStream *outstream = &os->pub; + //TODO SoundIoDevice *device = outstream->device; + + outstream->buffer_duration = 0.0; // TODO + outstream->period_duration = 0.0; // TODO + outstream->prebuf_duration = 0.0; // TODO + + SoundIoOutStreamJack *osj = create(); + if (!osj) { + outstream_destroy_jack(si, os); + return SoundIoErrorNoMem; + } + os->backend_data = osj; + + outstream->layout_error = SoundIoErrorIncompatibleBackend; + + jack_status_t status; + osj->client = jack_client_open(outstream->name, JackNoStartServer, &status); + if (!osj->client) { + outstream_destroy_jack(si, os); + assert(!(status & JackInvalidOption)); + if (status & JackShmFailure) + return SoundIoErrorSystemResources; + if (status & JackNoSuchClient) + return SoundIoErrorNoSuchClient; + return SoundIoErrorOpeningDevice; + } + + int err; + if ((err = jack_set_process_callback(osj->client, outstream_process_callback, os))) { + outstream_destroy_jack(si, os); + return SoundIoErrorOpeningDevice; + } + // TODO register the other callbacks and emit a stream error if they're called + + + // register ports + for (int ch = 0; ch < outstream->layout.channel_count; ch += 1) { + const char *channel_name = soundio_get_channel_name(outstream->layout.channels[ch]); + unsigned long flags = JackPortIsOutput; + if (!outstream->non_terminal_hint) + flags |= JackPortIsTerminal; + jack_port_t *port = jack_port_register(osj->client, channel_name, JACK_DEFAULT_AUDIO_TYPE, flags, 0); + if (!port) { + outstream_destroy_jack(si, os); + return SoundIoErrorOpeningDevice; + } + osj->ports[ch] = port; + } + + return 0; +} + +static int outstream_pause_jack(struct SoundIoPrivate *si, struct SoundIoOutStreamPrivate *os, bool pause) { + SoundIoOutStreamJack *osj = (SoundIoOutStreamJack *) os->backend_data; + SoundIoOutStream *outstream = &os->pub; + int err; + if (pause) { + if ((err = jack_deactivate(osj->client))) + return SoundIoErrorStreaming; + } else { + if ((err = jack_activate(osj->client))) + return SoundIoErrorStreaming; + + for (int ch = 0; ch < outstream->layout.channel_count; ch += 1) { + // TODO figure out source port name and dest port name + //if ((err = jack_connect(osj->client, source_port, dest_port))) + // return SoundIoErrorStreaming; + } + } + + return 0; +} + +static int outstream_start_jack(struct SoundIoPrivate *si, struct SoundIoOutStreamPrivate *os) { + return outstream_pause_jack(si, os, false); } static int outstream_begin_write_jack(struct SoundIoPrivate *, struct SoundIoOutStreamPrivate *, SoundIoChannelArea **out_areas, int *frame_count) { - soundio_panic("TODO"); + soundio_panic("TODO begin write"); } static int outstream_end_write_jack(struct SoundIoPrivate *, struct SoundIoOutStreamPrivate *, int frame_count) { - soundio_panic("TODO"); + soundio_panic("TODO end write"); } static int outstream_clear_buffer_jack(struct SoundIoPrivate *, struct SoundIoOutStreamPrivate *) { - soundio_panic("TODO"); + soundio_panic("TODO clear buffer"); } -static int outstream_pause_jack(struct SoundIoPrivate *, struct SoundIoOutStreamPrivate *, bool pause) { - soundio_panic("TODO"); -} static int instream_open_jack(struct SoundIoPrivate *, struct SoundIoInStreamPrivate *) { - soundio_panic("TODO"); + soundio_panic("TODO open instream"); } static void instream_destroy_jack(struct SoundIoPrivate *, struct SoundIoInStreamPrivate *) { - soundio_panic("TODO"); + soundio_panic("TODO destroy instream"); } static int instream_start_jack(struct SoundIoPrivate *, struct SoundIoInStreamPrivate *) { - soundio_panic("TODO"); + soundio_panic("TODO start instream"); } static int instream_begin_read_jack(struct SoundIoPrivate *, struct SoundIoInStreamPrivate *, SoundIoChannelArea **out_areas, int *frame_count) { - soundio_panic("TODO"); + soundio_panic("TODO begin read"); } static int instream_end_read_jack(struct SoundIoPrivate *, struct SoundIoInStreamPrivate *) { - soundio_panic("TODO"); + soundio_panic("TODO end read"); } static int instream_pause_jack(struct SoundIoPrivate *, struct SoundIoInStreamPrivate *, bool pause) { - soundio_panic("TODO"); + soundio_panic("TODO pause"); } static void split_str(const char *input_str, int input_str_len, char c, @@ -179,6 +253,7 @@ static SoundIoJackClient *find_or_create_client(SoundIoList * client->purpose = purpose; client->name = client_name; client->name_len = client_name_len; + client->port_count = 0; return client; } @@ -193,7 +268,7 @@ static char *dupe_str(const char *str, int str_len) { static int refresh_devices(SoundIoPrivate *si) { SoundIo *soundio = &si->pub; - SoundIoJack *sij = (SoundIoJack *)si->backend_data; + SoundIoJack *sij = &si->backend_data.jack; SoundIoDevicesInfo *devices_info = create(); if (!devices_info) @@ -208,13 +283,18 @@ static int refresh_devices(SoundIoPrivate *si) { } SoundIoList clients; - int err; - const char **port_name_ptr = port_names; while (*port_name_ptr) { const char *client_and_port_name = *port_name_ptr; jack_port_t *jport = jack_port_by_name(sij->client, client_and_port_name); 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; + } + SoundIoDevicePurpose purpose = (flags & JackPortIsInput) ? SoundIoDevicePurposeOutput : SoundIoDevicePurposeInput; bool is_physical = flags & JackPortIsPhysical; @@ -236,12 +316,11 @@ static int refresh_devices(SoundIoPrivate *si) { destroy(devices_info); return SoundIoErrorNoMem; } - if ((err = client->ports.add_one())) { - jack_free(port_names); - destroy(devices_info); - return SoundIoErrorNoMem; + if (client->port_count >= SOUNDIO_MAX_CHANNELS) { + // we hit the channel limit, skip the leftovers + continue; } - SoundIoJackPort *port = &client->ports.last(); + SoundIoJackPort *port = &client->ports[client->port_count++]; port->name = port_name; port->name_len = port_name_len; @@ -251,7 +330,7 @@ static int refresh_devices(SoundIoPrivate *si) { for (int i = 0; i < clients.length; i += 1) { SoundIoJackClient *client = &clients.at(i); - if (client->ports.length <= 0) + if (client->port_count <= 0) continue; SoundIoDevice *device = create(); @@ -260,9 +339,9 @@ static int refresh_devices(SoundIoPrivate *si) { destroy(devices_info); return SoundIoErrorNoMem; } - int description_len = client->name_len + 3 + 2 * client->ports.length; - for (int port_index = 0; port_index < client->ports.length; port_index += 1) { - SoundIoJackPort *port = &client->ports.at(port_index); + 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; } @@ -294,21 +373,21 @@ static int refresh_devices(SoundIoPrivate *si) { memcpy(device->description, client->name, client->name_len); memcpy(&device->description[client->name_len], ": ", 2); int index = client->name_len + 2; - for (int port_index = 0; port_index < client->ports.length; port_index += 1) { - SoundIoJackPort *port = &client->ports.at(port_index); + for (int port_index = 0; port_index < client->port_count; port_index += 1) { + SoundIoJackPort *port = &client->ports[port_index]; memcpy(&device->description[index], port->name, port->name_len); index += port->name_len; - if (port_index + 1 < client->ports.length) { + if (port_index + 1 < client->port_count) { memcpy(&device->description[index], ", ", 2); index += 2; } } - const struct SoundIoChannelLayout *layout = soundio_channel_layout_get_default(client->ports.length); + const struct SoundIoChannelLayout *layout = soundio_channel_layout_get_default(client->port_count); if (layout) { device->current_layout = *layout; } else { - for (int port_index = 0; port_index < client->ports.length; port_index += 1) + for (int port_index = 0; port_index < client->port_count; port_index += 1) device->current_layout.channels[port_index] = SoundIoChannelIdInvalid; } @@ -355,7 +434,7 @@ static int process_callback(jack_nframes_t nframes, void *arg) { static int buffer_size_callback(jack_nframes_t nframes, void *arg) { SoundIoPrivate *si = (SoundIoPrivate *)arg; - SoundIoJack *sij = (SoundIoJack *)si->backend_data; + SoundIoJack *sij = &si->backend_data.jack; sij->buffer_size = nframes; if (sij->initialized) refresh_devices(si); @@ -364,7 +443,7 @@ static int buffer_size_callback(jack_nframes_t nframes, void *arg) { static int sample_rate_callback(jack_nframes_t nframes, void *arg) { SoundIoPrivate *si = (SoundIoPrivate *)arg; - SoundIoJack *sij = (SoundIoJack *)si->backend_data; + SoundIoJack *sij = &si->backend_data.jack; sij->sample_rate = nframes; if (sij->initialized) refresh_devices(si); @@ -379,7 +458,7 @@ static int xrun_callback(void *arg) { static void port_registration_callback(jack_port_id_t port_id, int reg, void *arg) { SoundIoPrivate *si = (SoundIoPrivate *)arg; - SoundIoJack *sij = (SoundIoJack *)si->backend_data; + SoundIoJack *sij = &si->backend_data.jack; if (sij->initialized) refresh_devices(si); } @@ -388,7 +467,7 @@ 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; - SoundIoJack *sij = (SoundIoJack *)si->backend_data; + SoundIoJack *sij = &si->backend_data.jack; if (sij->initialized) refresh_devices(si); return 0; @@ -400,9 +479,7 @@ static void shutdown_callback(void *arg) { } static void destroy_jack(SoundIoPrivate *si) { - SoundIoJack *sij = (SoundIoJack *)si->backend_data; - if (!sij) - return; + SoundIoJack *sij = &si->backend_data.jack; if (sij->client) jack_client_close(sij->client); @@ -414,12 +491,10 @@ static void destroy_jack(SoundIoPrivate *si) { soundio_os_mutex_destroy(sij->mutex); soundio_destroy_devices_info(sij->ready_devices_info); - - destroy(sij); - si->backend_data = nullptr; } int soundio_jack_init(struct SoundIoPrivate *si) { + SoundIoJack *sij = &si->backend_data.jack; SoundIo *soundio = &si->pub; if (!global_msg_callback_flag.test_and_set()) { @@ -430,14 +505,6 @@ int soundio_jack_init(struct SoundIoPrivate *si) { global_msg_callback_flag.clear(); } - assert(!si->backend_data); - SoundIoJack *sij = create(); - if (!sij) { - destroy_jack(si); - return SoundIoErrorNoMem; - } - si->backend_data = sij; - sij->mutex = soundio_os_mutex_create(); if (!sij->mutex) { destroy_jack(si); diff --git a/src/jack.hpp b/src/jack.hpp index 7d48696..52a8f97 100644 --- a/src/jack.hpp +++ b/src/jack.hpp @@ -8,7 +8,26 @@ #ifndef SOUNDIO_JACK_HPP #define SOUNDIO_JACK_HPP +#include "os.hpp" + +#include + int soundio_jack_init(struct SoundIoPrivate *si); +struct SoundIoDeviceJack { + +}; + +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; +}; + #endif diff --git a/src/pulseaudio.cpp b/src/pulseaudio.cpp index b6d6325..84e82e4 100644 --- a/src/pulseaudio.cpp +++ b/src/pulseaudio.cpp @@ -7,13 +7,11 @@ #include "pulseaudio.hpp" #include "soundio.hpp" -#include "atomics.hpp" #include #include #include -#include struct SoundIoOutStreamPulseAudio { pa_stream *stream; @@ -30,43 +28,17 @@ struct SoundIoInStreamPulseAudio { SoundIoChannelArea areas[SOUNDIO_MAX_CHANNELS]; }; -struct SoundIoPulseAudio { - bool connection_refused; - - pa_context *pulse_context; - atomic_bool device_scan_queued; - - // the one that we're working on building - struct SoundIoDevicesInfo *current_devices_info; - char * default_sink_name; - char * default_source_name; - - // this one is ready to be read with flush_events. protected by mutex - struct SoundIoDevicesInfo *ready_devices_info; - - bool have_sink_list; - bool have_source_list; - bool have_default_sink; - - atomic_bool ready_flag; - atomic_bool have_devices_flag; - - pa_threaded_mainloop *main_loop; - pa_proplist *props; -}; - - static void subscribe_callback(pa_context *context, pa_subscription_event_type_t event_bits, uint32_t index, void *userdata) { SoundIoPrivate *si = (SoundIoPrivate *)userdata; - SoundIoPulseAudio *sipa = (SoundIoPulseAudio *)si->backend_data; + SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio; sipa->device_scan_queued = true; pa_threaded_mainloop_signal(sipa->main_loop, 0); } static void subscribe_to_events(SoundIoPrivate *si) { - SoundIoPulseAudio *sipa = (SoundIoPulseAudio *)si->backend_data; + SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio; pa_subscription_mask_t events = (pa_subscription_mask_t)( PA_SUBSCRIPTION_MASK_SINK|PA_SUBSCRIPTION_MASK_SOURCE|PA_SUBSCRIPTION_MASK_SERVER); pa_operation *subscribe_op = pa_context_subscribe(sipa->pulse_context, @@ -78,7 +50,7 @@ static void subscribe_to_events(SoundIoPrivate *si) { static void context_state_callback(pa_context *context, void *userdata) { SoundIoPrivate *si = (SoundIoPrivate *)userdata; - SoundIoPulseAudio *sipa = (SoundIoPulseAudio *)si->backend_data; + SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio; switch (pa_context_get_state(context)) { case PA_CONTEXT_UNCONNECTED: // The context hasn't been connected yet. @@ -112,9 +84,7 @@ static void context_state_callback(pa_context *context, void *userdata) { } static void destroy_pa(SoundIoPrivate *si) { - SoundIoPulseAudio *sipa = (SoundIoPulseAudio *)si->backend_data; - if (!sipa) - return; + SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio; if (sipa->main_loop) pa_threaded_mainloop_stop(sipa->main_loop); @@ -133,9 +103,6 @@ static void destroy_pa(SoundIoPrivate *si) { free(sipa->default_sink_name); free(sipa->default_source_name); - - destroy(sipa); - si->backend_data = nullptr; } static SoundIoFormat from_pulseaudio_format(pa_sample_spec sample_spec) { @@ -232,7 +199,7 @@ static int set_all_device_formats(SoundIoDevice *device) { } static int perform_operation(SoundIoPrivate *si, pa_operation *op) { - SoundIoPulseAudio *sipa = (SoundIoPulseAudio *)si->backend_data; + SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio; for (;;) { switch (pa_operation_get_state(op)) { case PA_OPERATION_RUNNING: @@ -250,7 +217,7 @@ static int perform_operation(SoundIoPrivate *si, pa_operation *op) { static void finish_device_query(SoundIoPrivate *si) { SoundIo *soundio = &si->pub; - SoundIoPulseAudio *sipa = (SoundIoPulseAudio *)si->backend_data; + SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio; if (!sipa->have_sink_list || !sipa->have_source_list || @@ -298,7 +265,7 @@ static void finish_device_query(SoundIoPrivate *si) { static void sink_info_callback(pa_context *pulse_context, const pa_sink_info *info, int eol, void *userdata) { SoundIoPrivate *si = (SoundIoPrivate *)userdata; SoundIo *soundio = &si->pub; - SoundIoPulseAudio *sipa = (SoundIoPulseAudio *)si->backend_data; + SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio; int err; if (eol) { sipa->have_sink_list = true; @@ -348,7 +315,7 @@ static void sink_info_callback(pa_context *pulse_context, const pa_sink_info *in static void source_info_callback(pa_context *pulse_context, const pa_source_info *info, int eol, void *userdata) { SoundIoPrivate *si = (SoundIoPrivate *)userdata; SoundIo *soundio = &si->pub; - SoundIoPulseAudio *sipa = (SoundIoPulseAudio *)si->backend_data; + SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio; int err; if (eol) { sipa->have_source_list = true; @@ -399,7 +366,7 @@ static void source_info_callback(pa_context *pulse_context, const pa_source_info static void server_info_callback(pa_context *pulse_context, const pa_server_info *info, void *userdata) { SoundIoPrivate *si = (SoundIoPrivate *)userdata; assert(si); - SoundIoPulseAudio *sipa = (SoundIoPulseAudio *)si->backend_data; + SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio; free(sipa->default_sink_name); free(sipa->default_source_name); @@ -416,7 +383,7 @@ static void server_info_callback(pa_context *pulse_context, const pa_server_info } static void scan_devices(SoundIoPrivate *si) { - SoundIoPulseAudio *sipa = (SoundIoPulseAudio *)si->backend_data; + SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio; sipa->have_sink_list = false; sipa->have_default_sink = false; @@ -449,7 +416,7 @@ static void scan_devices(SoundIoPrivate *si) { } static void block_until_have_devices(SoundIoPrivate *si) { - SoundIoPulseAudio *sipa = (SoundIoPulseAudio *)si->backend_data; + SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio; if (sipa->have_devices_flag) return; pa_threaded_mainloop_lock(sipa->main_loop); @@ -460,7 +427,7 @@ static void block_until_have_devices(SoundIoPrivate *si) { } static void block_until_ready(SoundIoPrivate *si) { - SoundIoPulseAudio *sipa = (SoundIoPulseAudio *)si->backend_data; + SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio; if (sipa->ready_flag) return; pa_threaded_mainloop_lock(sipa->main_loop); @@ -474,7 +441,7 @@ static void flush_events(SoundIoPrivate *si) { SoundIo *soundio = &si->pub; block_until_ready(si); - SoundIoPulseAudio *sipa = (SoundIoPulseAudio *)si->backend_data; + SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio; if (sipa->device_scan_queued) { sipa->device_scan_queued = false; @@ -504,13 +471,13 @@ static void flush_events(SoundIoPrivate *si) { } static void wait_events(SoundIoPrivate *si) { - SoundIoPulseAudio *sipa = (SoundIoPulseAudio *)si->backend_data; + SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio; flush_events(si); pa_threaded_mainloop_wait(sipa->main_loop); } static void wakeup(SoundIoPrivate *si) { - SoundIoPulseAudio *sipa = (SoundIoPulseAudio *)si->backend_data; + SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio; pa_threaded_mainloop_signal(sipa->main_loop, 0); } @@ -602,7 +569,7 @@ static void playback_stream_state_callback(pa_stream *stream, void *userdata) { SoundIoOutStream *outstream = &os->pub; SoundIo *soundio = outstream->device->soundio; SoundIoPrivate *si = (SoundIoPrivate *)soundio; - SoundIoPulseAudio *sipa = (SoundIoPulseAudio *)si->backend_data; + SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio; SoundIoOutStreamPulseAudio *ospa = (SoundIoOutStreamPulseAudio *)os->backend_data; switch (pa_stream_get_state(stream)) { case PA_STREAM_UNCONNECTED: @@ -635,7 +602,7 @@ static void outstream_destroy_pa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os if (!ospa) return; - SoundIoPulseAudio *sipa = (SoundIoPulseAudio *)si->backend_data; + SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio; pa_stream *stream = ospa->stream; if (stream) { pa_threaded_mainloop_lock(sipa->main_loop); @@ -666,7 +633,7 @@ static int outstream_open_pa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) { } os->backend_data = ospa; - SoundIoPulseAudio *sipa = (SoundIoPulseAudio *)si->backend_data; + SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio; ospa->stream_ready = false; assert(sipa->pulse_context); @@ -718,7 +685,7 @@ static int outstream_open_pa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) { static int outstream_start_pa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) { SoundIoOutStream *outstream = &os->pub; - SoundIoPulseAudio *sipa = (SoundIoPulseAudio *)si->backend_data; + SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio; SoundIoOutStreamPulseAudio *ospa = (SoundIoOutStreamPulseAudio *)os->backend_data; pa_threaded_mainloop_lock(sipa->main_loop); @@ -786,7 +753,7 @@ static int outstream_clear_buffer_pa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) { SoundIoOutStreamPulseAudio *ospa = (SoundIoOutStreamPulseAudio *)os->backend_data; - SoundIoPulseAudio *sipa = (SoundIoPulseAudio *)si->backend_data; + SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio; pa_stream *stream = ospa->stream; pa_threaded_mainloop_lock(sipa->main_loop); pa_operation *op = pa_stream_flush(stream, NULL, NULL); @@ -799,7 +766,7 @@ static int outstream_clear_buffer_pa(SoundIoPrivate *si, static int outstream_pause_pa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os, bool pause) { SoundIoOutStreamPulseAudio *ospa = (SoundIoOutStreamPulseAudio *)os->backend_data; - SoundIoPulseAudio *sipa = (SoundIoPulseAudio *)si->backend_data; + SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio; pa_threaded_mainloop_lock(sipa->main_loop); @@ -821,7 +788,7 @@ static void recording_stream_state_callback(pa_stream *stream, void *userdata) { SoundIoInStream *instream = &is->pub; SoundIo *soundio = instream->device->soundio; SoundIoPrivate *si = (SoundIoPrivate *)soundio; - SoundIoPulseAudio *sipa = (SoundIoPulseAudio *)si->backend_data; + SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio; switch (pa_stream_get_state(stream)) { case PA_STREAM_UNCONNECTED: case PA_STREAM_CREATING: @@ -852,7 +819,7 @@ static void instream_destroy_pa(SoundIoPrivate *si, SoundIoInStreamPrivate *inst if (!ispa) return; - SoundIoPulseAudio *sipa = (SoundIoPulseAudio *)si->backend_data; + SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio; pa_stream *stream = ispa->stream; if (stream) { pa_threaded_mainloop_lock(sipa->main_loop); @@ -878,7 +845,7 @@ static int instream_open_pa(SoundIoPrivate *si, SoundIoInStreamPrivate *is) { } is->backend_data = ispa; - SoundIoPulseAudio *sipa = (SoundIoPulseAudio *)si->backend_data; + SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio; ispa->stream_ready = false; pa_threaded_mainloop_lock(sipa->main_loop); @@ -923,7 +890,7 @@ static int instream_open_pa(SoundIoPrivate *si, SoundIoInStreamPrivate *is) { static int instream_start_pa(SoundIoPrivate *si, SoundIoInStreamPrivate *is) { SoundIoInStream *instream = &is->pub; SoundIoInStreamPulseAudio *ispa = (SoundIoInStreamPulseAudio *)is->backend_data; - SoundIoPulseAudio *sipa = (SoundIoPulseAudio *)si->backend_data; + SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio; pa_threaded_mainloop_lock(sipa->main_loop); pa_stream_flags_t flags = (instream->period_duration > 0.0) ? PA_STREAM_ADJUST_LATENCY : PA_STREAM_NOFLAGS; @@ -991,7 +958,7 @@ static int instream_end_read_pa(SoundIoPrivate *si, SoundIoInStreamPrivate *is) static int instream_pause_pa(SoundIoPrivate *si, SoundIoInStreamPrivate *is, bool pause) { SoundIoInStreamPulseAudio *ispa = (SoundIoInStreamPulseAudio *)is->backend_data; - SoundIoPulseAudio *sipa = (SoundIoPulseAudio *)si->backend_data; + SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio; pa_threaded_mainloop_lock(sipa->main_loop); @@ -1009,14 +976,7 @@ static int instream_pause_pa(SoundIoPrivate *si, SoundIoInStreamPrivate *is, boo int soundio_pulseaudio_init(SoundIoPrivate *si) { SoundIo *soundio = &si->pub; - - assert(!si->backend_data); - SoundIoPulseAudio *sipa = create(); - if (!sipa) { - destroy_pa(si); - return SoundIoErrorNoMem; - } - si->backend_data = sipa; + SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio; sipa->connection_refused = false; sipa->device_scan_queued = false; diff --git a/src/pulseaudio.hpp b/src/pulseaudio.hpp index dbeb4b3..986fe07 100644 --- a/src/pulseaudio.hpp +++ b/src/pulseaudio.hpp @@ -8,6 +8,39 @@ #ifndef SOUNDIO_PULSEAUDIO_HPP #define SOUNDIO_PULSEAUDIO_HPP +#include "atomics.hpp" + +#include + int soundio_pulseaudio_init(struct SoundIoPrivate *si); +struct SoundIoDevicePulseAudio { + +}; + +struct SoundIoPulseAudio { + bool connection_refused; + + pa_context *pulse_context; + atomic_bool device_scan_queued; + + // the one that we're working on building + struct SoundIoDevicesInfo *current_devices_info; + char *default_sink_name; + char *default_source_name; + + // this one is ready to be read with flush_events. protected by mutex + struct SoundIoDevicesInfo *ready_devices_info; + + bool have_sink_list; + bool have_source_list; + bool have_default_sink; + + atomic_bool ready_flag; + atomic_bool have_devices_flag; + + pa_threaded_mainloop *main_loop; + pa_proplist *props; +}; + #endif diff --git a/src/soundio.cpp b/src/soundio.cpp index 83a5703..d89875f 100644 --- a/src/soundio.cpp +++ b/src/soundio.cpp @@ -7,22 +7,9 @@ #include "soundio.hpp" #include "util.hpp" -#include "dummy.hpp" #include "os.hpp" #include "config.h" -#ifdef SOUNDIO_HAVE_JACK -#include "jack.hpp" -#endif - -#ifdef SOUNDIO_HAVE_PULSEAUDIO -#include "pulseaudio.hpp" -#endif - -#ifdef SOUNDIO_HAVE_ALSA -#include "alsa.hpp" -#endif - #include #include @@ -71,6 +58,7 @@ const char *soundio_strerror(int error) { case SoundIoErrorStreaming: return "unrecoverable streaming failure"; case SoundIoErrorIncompatibleDevice: return "incompatible device"; case SoundIoErrorNoSuchClient: return "no such client"; + case SoundIoErrorIncompatibleBackend: return "incompatible backend"; } soundio_panic("invalid error enum value: %d", error); } @@ -198,6 +186,8 @@ int soundio_connect_backend(SoundIo *soundio, SoundIoBackend backend) { if (!fn) return SoundIoErrorBackendUnavailable; + memset(&si->backend_data, 0, sizeof(SoundIoBackendData)); + int err; if ((err = backend_init_fns[backend](si))) { soundio_disconnect(soundio); @@ -211,7 +201,6 @@ void soundio_disconnect(struct SoundIo *soundio) { if (si->destroy) si->destroy(si); - assert(!si->backend_data); soundio->current_backend = SoundIoBackendNone; diff --git a/src/soundio.h b/src/soundio.h index 6c7b2a7..64fee69 100644 --- a/src/soundio.h +++ b/src/soundio.h @@ -27,6 +27,7 @@ enum SoundIoError { SoundIoErrorStreaming, SoundIoErrorIncompatibleDevice, SoundIoErrorNoSuchClient, + SoundIoErrorIncompatibleBackend, }; enum SoundIoChannelId { @@ -210,6 +211,7 @@ struct SoundIo { // Optional: Application name. // PulseAudio uses this for "application name". // JACK uses this for `client_name`. + // Must not contain a colon (":"). const char *app_name; // Optional: JACK info and error callbacks. @@ -371,8 +373,14 @@ struct 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. + // Must not contain a colon (":"). const char *name; + // Optional: Hint that this output stream is nonterminal. This is used by + // JACK and it means that the output stream data originates from an input + // stream. Defaults to `false`. + bool non_terminal_hint; + // computed automatically when you call soundio_outstream_open int bytes_per_frame; @@ -425,6 +433,7 @@ struct 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. + // Must not contain a colon (":"). const char *name; // computed automatically when you call soundio_instream_open diff --git a/src/soundio.hpp b/src/soundio.hpp index 4a207f1..9c545a0 100644 --- a/src/soundio.hpp +++ b/src/soundio.hpp @@ -11,6 +11,46 @@ #include "soundio.h" #include "list.hpp" +#ifdef SOUNDIO_HAVE_JACK +#include "jack.hpp" +#endif + +#ifdef SOUNDIO_HAVE_PULSEAUDIO +#include "pulseaudio.hpp" +#endif + +#ifdef SOUNDIO_HAVE_ALSA +#include "alsa.hpp" +#endif + +#include "dummy.hpp" + +union SoundIoBackendData { +#ifdef SOUNDIO_HAVE_JACK + SoundIoJack jack; +#endif +#ifdef SOUNDIO_HAVE_PULSEAUDIO + SoundIoPulseAudio pulseaudio; +#endif +#ifdef SOUNDIO_HAVE_ALSA + SoundIoAlsa alsa; +#endif + SoundIoDummy dummy; +}; + +union SoundIoDeviceBackendData { +#ifdef SOUNDIO_HAVE_JACK + SoundIoDeviceJack jack; +#endif +#ifdef SOUNDIO_HAVE_PULSEAUDIO + SoundIoDevicePulseAudio pulseaudio; +#endif +#ifdef SOUNDIO_HAVE_ALSA + SoundIoDeviceAlsa alsa; +#endif + SoundIoDeviceDummy dummy; +}; + struct SoundIoDevicesInfo { SoundIoList input_devices; SoundIoList output_devices; @@ -35,7 +75,7 @@ struct SoundIoPrivate { // Safe to read from a single thread without a mutex. struct SoundIoDevicesInfo *safe_devices_info; - void *backend_data; + SoundIoBackendData backend_data; void (*destroy)(struct SoundIoPrivate *); void (*flush_events)(struct SoundIoPrivate *); void (*wait_events)(struct SoundIoPrivate *); @@ -60,6 +100,11 @@ struct SoundIoPrivate { int (*instream_pause)(struct SoundIoPrivate *, struct SoundIoInStreamPrivate *, bool pause); }; +struct SoundIoDevicePrivate { + SoundIoDevice pub; + SoundIoDeviceBackendData backend_data; +}; + void soundio_destroy_devices_info(struct SoundIoDevicesInfo *devices_info);