From eeae08e1a36dd1713366fc3f088f8427c2721d38 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 28 Jul 2015 13:36:31 -0700 Subject: [PATCH] JACK emits on_backend_shutdown event --- README.md | 7 ++- src/jack.cpp | 157 ++++++++++++++++++++++++++++++++++-------------- src/jack.hpp | 2 + src/soundio.cpp | 9 +-- src/soundio.h | 10 +++ 5 files changed, 135 insertions(+), 50 deletions(-) diff --git a/README.md b/README.md index 162dbf3..6519ae5 100644 --- a/README.md +++ b/README.md @@ -237,6 +237,7 @@ view `coverage/index.html` in a browser. 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. JACK: implement prebuffering 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 @@ -248,7 +249,11 @@ view `coverage/index.html` in a browser. the microphone example. 0. PulseAudio: when prebuf gets set to 0 need to pass `PA_STREAM_START_CORKED`. 0. In ALSA do we need to wake up the poll when destroying the in or out stream? - 0. Create a test for clearing the playback buffer. + 0. Verify that JACK xrun callback context is the same as process callback. + If not, might need to hav xrun callback set a flag and have process callback + call the underflow callback. + 0. Detect PulseAudio server going offline and emit `on_backend_disconnect`. + 0. Create a test for clearing the playback buffer and prebuffering. 0. Create a test for pausing and resuming input and output streams. 0. Create a test for the latency / synchronization API. - Input is an audio file and some events indexed at particular frame - when diff --git a/src/jack.cpp b/src/jack.cpp index aa890ed..d2b37d7 100644 --- a/src/jack.cpp +++ b/src/jack.cpp @@ -35,7 +35,8 @@ static void flush_events_jack(struct SoundIoPrivate *si) { SoundIo *soundio = &si->pub; SoundIoJack *sij = &si->backend_data.jack; - bool change = false; + bool change_devices = false; + bool cb_shutdown = false; SoundIoDevicesInfo *old_devices_info = nullptr; soundio_os_mutex_lock(sij->mutex); @@ -44,15 +45,22 @@ static void flush_events_jack(struct SoundIoPrivate *si) { old_devices_info = si->safe_devices_info; si->safe_devices_info = sij->ready_devices_info; sij->ready_devices_info = nullptr; - change = true; + change_devices = true; + } + + if (sij->is_shutdown && !sij->emitted_shutdown_cb) { + sij->emitted_shutdown_cb = true; + cb_shutdown = true; } soundio_os_mutex_unlock(sij->mutex); - if (change) + if (change_devices) soundio->on_devices_change(soundio); - soundio_destroy_devices_info(old_devices_info); + + if (cb_shutdown) + soundio->on_backend_disconnect(soundio); } static void wait_events_jack(struct SoundIoPrivate *si) { @@ -96,17 +104,58 @@ static SoundIoDeviceJackPort *find_port_matching_channel(SoundIoDevice *device, return nullptr; } +static int outstream_xrun_callback(void *arg) { + SoundIoOutStreamPrivate *os = (SoundIoOutStreamPrivate *)arg; + SoundIoOutStream *outstream = &os->pub; + outstream->underflow_callback(outstream); + return 0; +} + +static int outstream_buffer_size_callback(jack_nframes_t nframes, void *arg) { + SoundIoOutStreamPrivate *os = (SoundIoOutStreamPrivate *)arg; + SoundIoOutStream *outstream = &os->pub; + SoundIo *soundio = outstream->device->soundio; + SoundIoPrivate *si = (SoundIoPrivate *)soundio; + SoundIoJack *sij = &si->backend_data.jack; + if ((jack_nframes_t)sij->buffer_size == nframes) { + return 0; + } else { + outstream->error_callback(outstream, SoundIoErrorStreaming); + return -1; + } +} + +static int outstream_sample_rate_callback(jack_nframes_t nframes, void *arg) { + SoundIoOutStreamPrivate *os = (SoundIoOutStreamPrivate *)arg; + SoundIoOutStream *outstream = &os->pub; + if (nframes == (jack_nframes_t)outstream->sample_rate) { + return 0; + } else { + outstream->error_callback(outstream, SoundIoErrorStreaming); + return -1; + } +} + +static void outstream_shutdown_callback(void *arg) { + SoundIoOutStreamPrivate *os = (SoundIoOutStreamPrivate *)arg; + SoundIoOutStream *outstream = &os->pub; + outstream->error_callback(outstream, SoundIoErrorStreaming); +} + static int outstream_open_jack(struct SoundIoPrivate *si, struct SoundIoOutStreamPrivate *os) { + SoundIoJack *sij = &si->backend_data.jack; SoundIoOutStreamJack *osj = &os->backend_data.jack; SoundIoOutStream *outstream = &os->pub; SoundIoDevice *device = outstream->device; SoundIoDevicePrivate *dev = (SoundIoDevicePrivate *)device; SoundIoDeviceJack *dj = &dev->backend_data.jack; + if (sij->is_shutdown) + return SoundIoErrorBackendDisconnected; - outstream->buffer_duration = 0.0; // TODO - outstream->period_duration = 0.0; // TODO - outstream->prebuf_duration = 0.0; // TODO + outstream->buffer_duration = device->buffer_duration_current; + outstream->period_duration = 0.0; + outstream->prebuf_duration = 0.0; jack_status_t status; osj->client = jack_client_open(outstream->name, JackNoStartServer, &status); @@ -125,7 +174,19 @@ static int outstream_open_jack(struct SoundIoPrivate *si, struct SoundIoOutStrea outstream_destroy_jack(si, os); return SoundIoErrorOpeningDevice; } - // TODO register the other callbacks and emit a stream error if they're called + if ((err = jack_set_buffer_size_callback(osj->client, outstream_buffer_size_callback, os))) { + outstream_destroy_jack(si, os); + return SoundIoErrorOpeningDevice; + } + if ((err = jack_set_sample_rate_callback(osj->client, outstream_sample_rate_callback, os))) { + outstream_destroy_jack(si, os); + return SoundIoErrorOpeningDevice; + } + if ((err = jack_set_xrun_callback(osj->client, outstream_xrun_callback, os))) { + outstream_destroy_jack(si, os); + return SoundIoErrorOpeningDevice; + } + jack_on_shutdown(osj->client, outstream_shutdown_callback, os); // register ports and map channels @@ -171,9 +232,10 @@ static int outstream_open_jack(struct SoundIoPrivate *si, struct SoundIoOutStrea static int outstream_pause_jack(struct SoundIoPrivate *si, struct SoundIoOutStreamPrivate *os, bool pause) { SoundIoOutStreamJack *osj = &os->backend_data.jack; SoundIoOutStream *outstream = &os->pub; - // TODO SoundIoDevice *device = outstream->device; - // TODO SoundIoDevicePrivate *dev = (SoundIoDevicePrivate *)device; - // TODO SoundIoDeviceJack *dj = &dev->backend_data.jack; + SoundIoJack *sij = &si->backend_data.jack; + if (sij->is_shutdown) + return SoundIoErrorBackendDisconnected; + int err; if (pause) { if ((err = jack_deactivate(osj->client))) @@ -227,35 +289,50 @@ static int outstream_end_write_jack(struct SoundIoPrivate *, struct SoundIoOutSt } static int outstream_clear_buffer_jack(struct SoundIoPrivate *, struct SoundIoOutStreamPrivate *) { - soundio_panic("TODO clear buffer"); + // JACK does not support `prebuf` which is the same as a `prebuf` value of 0, + // which means that clearing the buffer is always successful and does nothing. + return 0; } - -static int instream_open_jack(struct SoundIoPrivate *, struct SoundIoInStreamPrivate *) { - soundio_panic("TODO open instream"); -} - static void instream_destroy_jack(struct SoundIoPrivate *, struct SoundIoInStreamPrivate *) { soundio_panic("TODO destroy instream"); } -static int instream_start_jack(struct SoundIoPrivate *, struct SoundIoInStreamPrivate *) { +static int instream_open_jack(struct SoundIoPrivate *si, struct SoundIoInStreamPrivate *is) { + //SoundIoInStream *outstream = &is->pub; + //SoundIoInStreamJack *isj = &is->backend_data.jack; + SoundIoJack *sij = &si->backend_data.jack; + if (sij->is_shutdown) { + instream_destroy_jack(si, is); + return SoundIoErrorBackendDisconnected; + } + soundio_panic("TODO open instream"); +} + +static int instream_start_jack(struct SoundIoPrivate *si, struct SoundIoInStreamPrivate *is) { + SoundIoJack *sij = &si->backend_data.jack; + if (sij->is_shutdown) + return SoundIoErrorBackendDisconnected; + soundio_panic("TODO start instream"); } -static int instream_begin_read_jack(struct SoundIoPrivate *, struct SoundIoInStreamPrivate *, +static int instream_begin_read_jack(struct SoundIoPrivate *si, struct SoundIoInStreamPrivate *is, SoundIoChannelArea **out_areas, int *frame_count) { soundio_panic("TODO begin read"); } -static int instream_end_read_jack(struct SoundIoPrivate *, struct SoundIoInStreamPrivate *) { +static int instream_end_read_jack(struct SoundIoPrivate *si, struct SoundIoInStreamPrivate *is) { soundio_panic("TODO end read"); } -static int instream_pause_jack(struct SoundIoPrivate *, struct SoundIoInStreamPrivate *, bool pause) { +static int instream_pause_jack(struct SoundIoPrivate *si, struct SoundIoInStreamPrivate *is, bool pause) { + SoundIoJack *sij = &si->backend_data.jack; + if (sij->is_shutdown) + return SoundIoErrorBackendDisconnected; soundio_panic("TODO pause"); } @@ -326,6 +403,10 @@ static int refresh_devices(SoundIoPrivate *si) { SoundIo *soundio = &si->pub; SoundIoJack *sij = &si->backend_data.jack; + if (sij->is_shutdown) + return SoundIoErrorBackendDisconnected; + + SoundIoDevicesInfo *devices_info = create(); if (!devices_info) return SoundIoErrorNoMem; @@ -513,12 +594,6 @@ static int refresh_devices(SoundIoPrivate *si) { 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 = &si->backend_data.jack; @@ -537,12 +612,6 @@ static int sample_rate_callback(jack_nframes_t nframes, void *arg) { return 0; } -/* TODO -static int xrun_callback(void *arg) { - return 0; -} -*/ - static void port_registration_callback(jack_port_id_t port_id, int reg, void *arg) { SoundIoPrivate *si = (SoundIoPrivate *)arg; SoundIoJack *sij = &si->backend_data.jack; @@ -561,8 +630,13 @@ static int port_rename_calllback(jack_port_id_t port_id, } static void shutdown_callback(void *arg) { - //SoundIoPrivate *si = (SoundIoPrivate *)arg; - soundio_panic("TODO shutdown callback"); + SoundIoPrivate *si = (SoundIoPrivate *)arg; + SoundIo *soundio = &si->pub; + SoundIoJack *sij = &si->backend_data.jack; + soundio_os_mutex_lock(sij->mutex); + sij->is_shutdown = true; + soundio->on_events_signal(soundio); + soundio_os_mutex_unlock(sij->mutex); } static void destroy_jack(SoundIoPrivate *si) { @@ -620,10 +694,6 @@ int soundio_jack_init(struct SoundIoPrivate *si) { } 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; @@ -632,12 +702,6 @@ int soundio_jack_init(struct SoundIoPrivate *si) { destroy_jack(si); return SoundIoErrorInitAudioBackend; } - /* TODO - 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; @@ -648,6 +712,9 @@ int soundio_jack_init(struct SoundIoPrivate *si) { } jack_on_shutdown(sij->client, shutdown_callback, si); + sij->buffer_size = jack_get_buffer_size(sij->client); + sij->sample_rate = jack_get_sample_rate(sij->client); + if ((err = jack_activate(sij->client))) { destroy_jack(si); return SoundIoErrorInitAudioBackend; diff --git a/src/jack.hpp b/src/jack.hpp index 2bcf52a..8658ec7 100644 --- a/src/jack.hpp +++ b/src/jack.hpp @@ -35,6 +35,8 @@ struct SoundIoJack { bool initialized; int sample_rate; int buffer_size; + bool is_shutdown; + bool emitted_shutdown_cb; }; struct SoundIoOutStreamJackPort { diff --git a/src/soundio.cpp b/src/soundio.cpp index 97349f1..3305e87 100644 --- a/src/soundio.cpp +++ b/src/soundio.cpp @@ -59,6 +59,7 @@ const char *soundio_strerror(int error) { case SoundIoErrorIncompatibleDevice: return "incompatible device"; case SoundIoErrorNoSuchClient: return "no such client"; case SoundIoErrorIncompatibleBackend: return "incompatible backend"; + case SoundIoErrorBackendDisconnected: return "backend disconnected"; } soundio_panic("invalid error enum value: %d", error); } @@ -139,8 +140,7 @@ void soundio_destroy(struct SoundIo *soundio) { destroy(si); } -static void default_on_devices_change(struct SoundIo *) { } -static void default_on_events_signal(struct SoundIo *) { } +static void do_nothing_cb(struct SoundIo *) { } static void default_msg_callback(const char *msg) { } struct SoundIo * soundio_create(void) { @@ -149,8 +149,9 @@ struct SoundIo * soundio_create(void) { if (!si) return NULL; SoundIo *soundio = &si->pub; - soundio->on_devices_change = default_on_devices_change; - soundio->on_events_signal = default_on_events_signal; + soundio->on_devices_change = do_nothing_cb; + soundio->on_backend_disconnect = do_nothing_cb; + soundio->on_events_signal = do_nothing_cb; soundio->app_name = "SoundIo"; soundio->jack_info_callback = default_msg_callback; soundio->jack_error_callback = default_msg_callback; diff --git a/src/soundio.h b/src/soundio.h index 0124e77..d6a5d75 100644 --- a/src/soundio.h +++ b/src/soundio.h @@ -28,6 +28,7 @@ enum SoundIoError { SoundIoErrorIncompatibleDevice, SoundIoErrorNoSuchClient, SoundIoErrorIncompatibleBackend, + SoundIoErrorBackendDisconnected, }; enum SoundIoChannelId { @@ -203,6 +204,12 @@ struct SoundIo { // Optional callback. Called when the list of devices change. Only called // during a call to soundio_flush_events or soundio_wait_events. void (*on_devices_change)(struct SoundIo *); + // Optional callback. Called when the backend disconnects. For example, + // when the JACK server shuts down. When this happens, listing devices + // and opening streams will always fail with + // SoundIoErrorBackendDisconnected. This callback is only called during a + // call to soundio_flush_events or soundio_wait_events. + void (*on_backend_disconnect)(struct SoundIo *); // Optional callback. Called from an unknown thread that you should not use // to call any soundio functions. You may use this to signal a condition // variable to wake up. Called when soundio_wait_events would be woken up. @@ -349,6 +356,9 @@ struct SoundIoOutStream { // How many seconds need to be in the buffer before playback will commence. // If a buffer underflow occurs, this prebuffering will be again enabled. // This value defaults to being the same as `buffer_duration`. + // After you call `soundio_outstream_open` this value is replaced with the + // actual `prebuf_duration`, as near to this value as possible. + // JACK does not support prebuffering; `prebuf_duration` is effectively 0. double prebuf_duration; // Defaults to NULL. Put whatever you want here.