JACK emits on_backend_shutdown event

This commit is contained in:
Andrew Kelley 2015-07-28 13:36:31 -07:00
parent 6df84096f3
commit eeae08e1a3
5 changed files with 135 additions and 50 deletions

View file

@ -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. 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 0. When two soundio clients are talking to each other, use port names to
negotiate channel maps. negotiate channel maps.
0. JACK: implement prebuffering
0. why does pulseaudio microphone use up all the CPU? 0. why does pulseaudio microphone use up all the CPU?
0. merge in/out stream structures and functions? 0. merge in/out stream structures and functions?
0. implement CoreAudio (OSX) backend, get examples working 0. implement CoreAudio (OSX) backend, get examples working
@ -248,7 +249,11 @@ view `coverage/index.html` in a browser.
the microphone example. the microphone example.
0. PulseAudio: when prebuf gets set to 0 need to pass `PA_STREAM_START_CORKED`. 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. 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 pausing and resuming input and output streams.
0. Create a test for the latency / synchronization API. 0. Create a test for the latency / synchronization API.
- Input is an audio file and some events indexed at particular frame - when - Input is an audio file and some events indexed at particular frame - when

View file

@ -35,7 +35,8 @@ static void flush_events_jack(struct SoundIoPrivate *si) {
SoundIo *soundio = &si->pub; SoundIo *soundio = &si->pub;
SoundIoJack *sij = &si->backend_data.jack; SoundIoJack *sij = &si->backend_data.jack;
bool change = false; bool change_devices = false;
bool cb_shutdown = false;
SoundIoDevicesInfo *old_devices_info = nullptr; SoundIoDevicesInfo *old_devices_info = nullptr;
soundio_os_mutex_lock(sij->mutex); 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; old_devices_info = si->safe_devices_info;
si->safe_devices_info = sij->ready_devices_info; si->safe_devices_info = sij->ready_devices_info;
sij->ready_devices_info = nullptr; 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); soundio_os_mutex_unlock(sij->mutex);
if (change) if (change_devices)
soundio->on_devices_change(soundio); soundio->on_devices_change(soundio);
soundio_destroy_devices_info(old_devices_info); soundio_destroy_devices_info(old_devices_info);
if (cb_shutdown)
soundio->on_backend_disconnect(soundio);
} }
static void wait_events_jack(struct SoundIoPrivate *si) { static void wait_events_jack(struct SoundIoPrivate *si) {
@ -96,17 +104,58 @@ static SoundIoDeviceJackPort *find_port_matching_channel(SoundIoDevice *device,
return nullptr; 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) { static int outstream_open_jack(struct SoundIoPrivate *si, struct SoundIoOutStreamPrivate *os) {
SoundIoJack *sij = &si->backend_data.jack;
SoundIoOutStreamJack *osj = &os->backend_data.jack; SoundIoOutStreamJack *osj = &os->backend_data.jack;
SoundIoOutStream *outstream = &os->pub; SoundIoOutStream *outstream = &os->pub;
SoundIoDevice *device = outstream->device; SoundIoDevice *device = outstream->device;
SoundIoDevicePrivate *dev = (SoundIoDevicePrivate *)device; SoundIoDevicePrivate *dev = (SoundIoDevicePrivate *)device;
SoundIoDeviceJack *dj = &dev->backend_data.jack; SoundIoDeviceJack *dj = &dev->backend_data.jack;
if (sij->is_shutdown)
return SoundIoErrorBackendDisconnected;
outstream->buffer_duration = 0.0; // TODO outstream->buffer_duration = device->buffer_duration_current;
outstream->period_duration = 0.0; // TODO outstream->period_duration = 0.0;
outstream->prebuf_duration = 0.0; // TODO outstream->prebuf_duration = 0.0;
jack_status_t status; jack_status_t status;
osj->client = jack_client_open(outstream->name, JackNoStartServer, &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); outstream_destroy_jack(si, os);
return SoundIoErrorOpeningDevice; 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 // 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) { static int outstream_pause_jack(struct SoundIoPrivate *si, struct SoundIoOutStreamPrivate *os, bool pause) {
SoundIoOutStreamJack *osj = &os->backend_data.jack; SoundIoOutStreamJack *osj = &os->backend_data.jack;
SoundIoOutStream *outstream = &os->pub; SoundIoOutStream *outstream = &os->pub;
// TODO SoundIoDevice *device = outstream->device; SoundIoJack *sij = &si->backend_data.jack;
// TODO SoundIoDevicePrivate *dev = (SoundIoDevicePrivate *)device; if (sij->is_shutdown)
// TODO SoundIoDeviceJack *dj = &dev->backend_data.jack; return SoundIoErrorBackendDisconnected;
int err; int err;
if (pause) { if (pause) {
if ((err = jack_deactivate(osj->client))) 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 *) { 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 *) { static void instream_destroy_jack(struct SoundIoPrivate *, struct SoundIoInStreamPrivate *) {
soundio_panic("TODO destroy instream"); 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"); 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) SoundIoChannelArea **out_areas, int *frame_count)
{ {
soundio_panic("TODO begin read"); 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"); 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"); soundio_panic("TODO pause");
} }
@ -326,6 +403,10 @@ static int refresh_devices(SoundIoPrivate *si) {
SoundIo *soundio = &si->pub; SoundIo *soundio = &si->pub;
SoundIoJack *sij = &si->backend_data.jack; SoundIoJack *sij = &si->backend_data.jack;
if (sij->is_shutdown)
return SoundIoErrorBackendDisconnected;
SoundIoDevicesInfo *devices_info = create<SoundIoDevicesInfo>(); SoundIoDevicesInfo *devices_info = create<SoundIoDevicesInfo>();
if (!devices_info) if (!devices_info)
return SoundIoErrorNoMem; return SoundIoErrorNoMem;
@ -513,12 +594,6 @@ static int refresh_devices(SoundIoPrivate *si) {
return 0; 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) { static int buffer_size_callback(jack_nframes_t nframes, void *arg) {
SoundIoPrivate *si = (SoundIoPrivate *)arg; SoundIoPrivate *si = (SoundIoPrivate *)arg;
SoundIoJack *sij = &si->backend_data.jack; SoundIoJack *sij = &si->backend_data.jack;
@ -537,12 +612,6 @@ static int sample_rate_callback(jack_nframes_t nframes, void *arg) {
return 0; 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) { static void port_registration_callback(jack_port_id_t port_id, int reg, void *arg) {
SoundIoPrivate *si = (SoundIoPrivate *)arg; SoundIoPrivate *si = (SoundIoPrivate *)arg;
SoundIoJack *sij = &si->backend_data.jack; 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) { static void shutdown_callback(void *arg) {
//SoundIoPrivate *si = (SoundIoPrivate *)arg; SoundIoPrivate *si = (SoundIoPrivate *)arg;
soundio_panic("TODO shutdown callback"); 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) { static void destroy_jack(SoundIoPrivate *si) {
@ -620,10 +694,6 @@ int soundio_jack_init(struct SoundIoPrivate *si) {
} }
int err; 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))) { if ((err = jack_set_buffer_size_callback(sij->client, buffer_size_callback, si))) {
destroy_jack(si); destroy_jack(si);
return SoundIoErrorInitAudioBackend; return SoundIoErrorInitAudioBackend;
@ -632,12 +702,6 @@ int soundio_jack_init(struct SoundIoPrivate *si) {
destroy_jack(si); destroy_jack(si);
return SoundIoErrorInitAudioBackend; 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))) { if ((err = jack_set_port_registration_callback(sij->client, port_registration_callback, si))) {
destroy_jack(si); destroy_jack(si);
return SoundIoErrorInitAudioBackend; return SoundIoErrorInitAudioBackend;
@ -648,6 +712,9 @@ int soundio_jack_init(struct SoundIoPrivate *si) {
} }
jack_on_shutdown(sij->client, shutdown_callback, 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))) { if ((err = jack_activate(sij->client))) {
destroy_jack(si); destroy_jack(si);
return SoundIoErrorInitAudioBackend; return SoundIoErrorInitAudioBackend;

View file

@ -35,6 +35,8 @@ struct SoundIoJack {
bool initialized; bool initialized;
int sample_rate; int sample_rate;
int buffer_size; int buffer_size;
bool is_shutdown;
bool emitted_shutdown_cb;
}; };
struct SoundIoOutStreamJackPort { struct SoundIoOutStreamJackPort {

View file

@ -59,6 +59,7 @@ const char *soundio_strerror(int error) {
case SoundIoErrorIncompatibleDevice: return "incompatible device"; case SoundIoErrorIncompatibleDevice: return "incompatible device";
case SoundIoErrorNoSuchClient: return "no such client"; case SoundIoErrorNoSuchClient: return "no such client";
case SoundIoErrorIncompatibleBackend: return "incompatible backend"; case SoundIoErrorIncompatibleBackend: return "incompatible backend";
case SoundIoErrorBackendDisconnected: return "backend disconnected";
} }
soundio_panic("invalid error enum value: %d", error); soundio_panic("invalid error enum value: %d", error);
} }
@ -139,8 +140,7 @@ void soundio_destroy(struct SoundIo *soundio) {
destroy(si); destroy(si);
} }
static void default_on_devices_change(struct SoundIo *) { } static void do_nothing_cb(struct SoundIo *) { }
static void default_on_events_signal(struct SoundIo *) { }
static void default_msg_callback(const char *msg) { } static void default_msg_callback(const char *msg) { }
struct SoundIo * soundio_create(void) { struct SoundIo * soundio_create(void) {
@ -149,8 +149,9 @@ struct SoundIo * soundio_create(void) {
if (!si) if (!si)
return NULL; return NULL;
SoundIo *soundio = &si->pub; SoundIo *soundio = &si->pub;
soundio->on_devices_change = default_on_devices_change; soundio->on_devices_change = do_nothing_cb;
soundio->on_events_signal = default_on_events_signal; soundio->on_backend_disconnect = do_nothing_cb;
soundio->on_events_signal = do_nothing_cb;
soundio->app_name = "SoundIo"; soundio->app_name = "SoundIo";
soundio->jack_info_callback = default_msg_callback; soundio->jack_info_callback = default_msg_callback;
soundio->jack_error_callback = default_msg_callback; soundio->jack_error_callback = default_msg_callback;

View file

@ -28,6 +28,7 @@ enum SoundIoError {
SoundIoErrorIncompatibleDevice, SoundIoErrorIncompatibleDevice,
SoundIoErrorNoSuchClient, SoundIoErrorNoSuchClient,
SoundIoErrorIncompatibleBackend, SoundIoErrorIncompatibleBackend,
SoundIoErrorBackendDisconnected,
}; };
enum SoundIoChannelId { enum SoundIoChannelId {
@ -203,6 +204,12 @@ struct SoundIo {
// Optional callback. Called when the list of devices change. Only called // Optional callback. Called when the list of devices change. Only called
// during a call to soundio_flush_events or soundio_wait_events. // during a call to soundio_flush_events or soundio_wait_events.
void (*on_devices_change)(struct SoundIo *); 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 // 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 // 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. // 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. // How many seconds need to be in the buffer before playback will commence.
// If a buffer underflow occurs, this prebuffering will be again enabled. // If a buffer underflow occurs, this prebuffering will be again enabled.
// This value defaults to being the same as `buffer_duration`. // 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; double prebuf_duration;
// Defaults to NULL. Put whatever you want here. // Defaults to NULL. Put whatever you want here.