sine example works with JACK

This commit is contained in:
Andrew Kelley 2015-07-28 11:28:07 -07:00
parent 70decb39f5
commit 6df84096f3
11 changed files with 185 additions and 45 deletions

View file

@ -232,7 +232,7 @@ view `coverage/index.html` in a browser.
## Roadmap ## Roadmap
0. implement JACK backend, get examples working 0. JACK: input
0. Steal PulseAudio's default channel maps per channel count 0. Steal PulseAudio's default channel maps per channel count
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
@ -280,10 +280,10 @@ view `coverage/index.html` in a browser.
0. Support PulseAudio proplist properties for main context and streams 0. Support PulseAudio proplist properties for main context and streams
0. custom allocator support 0. custom allocator support
0. mlock memory which is accessed in the real time path 0. mlock memory which is accessed in the real time path
0. Consider testing on FreeBSD
0. make rtprio warning a callback and have existing behavior be the default callback 0. make rtprio warning a callback and have existing behavior be the default callback
0. write detailed docs on buffer underflows explaining when they occur, what state 0. write detailed docs on buffer underflows explaining when they occur, what state
changes are related to them, and how to recover from them. changes are related to them, and how to recover from them.
0. Consider testing on FreeBSD
## Planned Uses for libsoundio ## Planned Uses for libsoundio

View file

@ -109,7 +109,7 @@ int main(int argc, char **argv) {
if (!device) if (!device)
panic("out of memory"); panic("out of memory");
fprintf(stderr, "Output device: %s: %s\n", device->name, device->description); fprintf(stderr, "Output device: %s\n", device->description);
struct SoundIoOutStream *outstream = soundio_outstream_create(device); struct SoundIoOutStream *outstream = soundio_outstream_create(device);
outstream->format = SoundIoFormatFloat32NE; outstream->format = SoundIoFormatFloat32NE;

View file

@ -530,14 +530,15 @@ static int refresh_devices(SoundIoPrivate *si) {
} }
SoundIoDevice *device = create<SoundIoDevice>(); SoundIoDevicePrivate *dev = create<SoundIoDevicePrivate>();
if (!device) { if (!dev) {
free(name); free(name);
free(descr); free(descr);
destroy(devices_info); destroy(devices_info);
snd_device_name_free_hint(hints); snd_device_name_free_hint(hints);
return SoundIoErrorNoMem; return SoundIoErrorNoMem;
} }
SoundIoDevice *device = &dev->pub;
device->ref_count = 1; device->ref_count = 1;
device->soundio = soundio; device->soundio = soundio;
device->is_raw = false; device->is_raw = false;
@ -647,12 +648,13 @@ static int refresh_devices(SoundIoPrivate *si) {
const char *device_name = snd_pcm_info_get_name(pcm_info); const char *device_name = snd_pcm_info_get_name(pcm_info);
SoundIoDevice *device = create<SoundIoDevice>(); SoundIoDevicePrivate *dev = create<SoundIoDevicePrivate>();
if (!device) { if (!dev) {
snd_ctl_close(handle); snd_ctl_close(handle);
destroy(devices_info); destroy(devices_info);
return SoundIoErrorNoMem; return SoundIoErrorNoMem;
} }
SoundIoDevice *device = &dev->pub;
device->ref_count = 1; device->ref_count = 1;
device->soundio = soundio; device->soundio = soundio;
device->name = soundio_alloc_sprintf(nullptr, "hw:%d,%d", card_index, device_index); device->name = soundio_alloc_sprintf(nullptr, "hw:%d,%d", card_index, device_index);

View file

@ -410,3 +410,8 @@ const struct SoundIoChannelLayout *soundio_channel_layout_get_default(int channe
} }
return nullptr; return nullptr;
} }
enum SoundIoChannelId soundio_parse_channel_id(const char *str, int str_len) {
// TODO actually parse
return SoundIoChannelIdInvalid;
}

View file

@ -434,11 +434,12 @@ int soundio_dummy_init(SoundIoPrivate *si) {
// create output device // create output device
{ {
SoundIoDevice *device = create<SoundIoDevice>(); SoundIoDevicePrivate *dev = create<SoundIoDevicePrivate>();
if (!device) { if (!dev) {
destroy_dummy(si); destroy_dummy(si);
return SoundIoErrorNoMem; return SoundIoErrorNoMem;
} }
SoundIoDevice *device = &dev->pub;
device->ref_count = 1; device->ref_count = 1;
device->soundio = soundio; device->soundio = soundio;
@ -482,11 +483,12 @@ int soundio_dummy_init(SoundIoPrivate *si) {
// create input device // create input device
{ {
SoundIoDevice *device = create<SoundIoDevice>(); SoundIoDevicePrivate *dev = create<SoundIoDevicePrivate>();
if (!device) { if (!dev) {
destroy_dummy(si); destroy_dummy(si);
return SoundIoErrorNoMem; return SoundIoErrorNoMem;
} }
SoundIoDevice *device = &dev->pub;
device->ref_count = 1; device->ref_count = 1;
device->soundio = soundio; device->soundio = soundio;

View file

@ -15,8 +15,11 @@
static atomic_flag global_msg_callback_flag = ATOMIC_FLAG_INIT; static atomic_flag global_msg_callback_flag = ATOMIC_FLAG_INIT;
struct SoundIoJackPort { struct SoundIoJackPort {
const char *full_name;
int full_name_len;
const char *name; const char *name;
int name_len; int name_len;
SoundIoChannelId channel_id;
}; };
struct SoundIoJackClient { struct SoundIoJackClient {
@ -24,8 +27,8 @@ struct SoundIoJackClient {
int name_len; int name_len;
bool is_physical; bool is_physical;
SoundIoDevicePurpose purpose; SoundIoDevicePurpose purpose;
SoundIoJackPort ports[SOUNDIO_MAX_CHANNELS];
int port_count; int port_count;
SoundIoJackPort ports[SOUNDIO_MAX_CHANNELS];
}; };
static void flush_events_jack(struct SoundIoPrivate *si) { static void flush_events_jack(struct SoundIoPrivate *si) {
@ -80,17 +83,31 @@ static void outstream_destroy_jack(struct SoundIoPrivate *is, struct SoundIoOutS
jack_client_close(osj->client); jack_client_close(osj->client);
} }
static SoundIoDeviceJackPort *find_port_matching_channel(SoundIoDevice *device, SoundIoChannelId id) {
SoundIoDevicePrivate *dev = (SoundIoDevicePrivate *)device;
SoundIoDeviceJack *dj = &dev->backend_data.jack;
for (int ch = 0; ch < device->current_layout.channel_count; ch += 1) {
SoundIoChannelId chan_id = device->current_layout.channels[ch];
if (chan_id == id)
return &dj->ports[ch];
}
return nullptr;
}
static int outstream_open_jack(struct SoundIoPrivate *si, struct SoundIoOutStreamPrivate *os) { static int outstream_open_jack(struct SoundIoPrivate *si, struct SoundIoOutStreamPrivate *os) {
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; SoundIoDevice *device = outstream->device;
SoundIoDevicePrivate *dev = (SoundIoDevicePrivate *)device;
SoundIoDeviceJack *dj = &dev->backend_data.jack;
outstream->buffer_duration = 0.0; // TODO outstream->buffer_duration = 0.0; // TODO
outstream->period_duration = 0.0; // TODO outstream->period_duration = 0.0; // TODO
outstream->prebuf_duration = 0.0; // TODO outstream->prebuf_duration = 0.0; // TODO
outstream->layout_error = SoundIoErrorIncompatibleBackend;
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);
if (!osj->client) { if (!osj->client) {
@ -111,18 +128,41 @@ static int outstream_open_jack(struct SoundIoPrivate *si, struct SoundIoOutStrea
// TODO register the other callbacks and emit a stream error if they're called // TODO register the other callbacks and emit a stream error if they're called
// register ports // register ports and map channels
int connected_count = 0;
for (int ch = 0; ch < outstream->layout.channel_count; ch += 1) { for (int ch = 0; ch < outstream->layout.channel_count; ch += 1) {
const char *channel_name = soundio_get_channel_name(outstream->layout.channels[ch]); SoundIoChannelId my_channel_id = outstream->layout.channels[ch];
const char *channel_name = soundio_get_channel_name(my_channel_id);
unsigned long flags = JackPortIsOutput; unsigned long flags = JackPortIsOutput;
if (!outstream->non_terminal_hint) if (!outstream->non_terminal_hint)
flags |= JackPortIsTerminal; flags |= JackPortIsTerminal;
jack_port_t *port = jack_port_register(osj->client, channel_name, JACK_DEFAULT_AUDIO_TYPE, flags, 0); jack_port_t *jport = jack_port_register(osj->client, channel_name, JACK_DEFAULT_AUDIO_TYPE, flags, 0);
if (!port) { if (!jport) {
outstream_destroy_jack(si, os); outstream_destroy_jack(si, os);
return SoundIoErrorOpeningDevice; return SoundIoErrorOpeningDevice;
} }
osj->ports[ch] = port; SoundIoOutStreamJackPort *osjp = &osj->ports[ch];
osjp->source_port = jport;
// figure out which dest port this connects to
SoundIoDeviceJackPort *djp = find_port_matching_channel(device, my_channel_id);
if (djp) {
osjp->dest_port_name = djp->full_name;
osjp->dest_port_name_len = djp->full_name_len;
connected_count += 1;
}
}
// If nothing got connected, channel layouts aren't working. Just send the
// data in the order of the ports.
if (connected_count == 0) {
outstream->layout_error = SoundIoErrorIncompatibleDevice;
int ch_count = min(outstream->layout.channel_count, dj->port_count);
for (int ch = 0; ch < ch_count; ch += 1) {
SoundIoOutStreamJackPort *osjp = &osj->ports[ch];
SoundIoDeviceJackPort *djp = &dj->ports[ch];
osjp->dest_port_name = djp->full_name;
osjp->dest_port_name_len = djp->full_name_len;
}
} }
return 0; return 0;
@ -131,6 +171,9 @@ 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;
// TODO SoundIoDevicePrivate *dev = (SoundIoDevicePrivate *)device;
// TODO SoundIoDeviceJack *dj = &dev->backend_data.jack;
int err; int err;
if (pause) { if (pause) {
if ((err = jack_deactivate(osj->client))) if ((err = jack_deactivate(osj->client)))
@ -140,9 +183,14 @@ static int outstream_pause_jack(struct SoundIoPrivate *si, struct SoundIoOutStre
return SoundIoErrorStreaming; return SoundIoErrorStreaming;
for (int ch = 0; ch < outstream->layout.channel_count; ch += 1) { for (int ch = 0; ch < outstream->layout.channel_count; ch += 1) {
// TODO figure out source port name and dest port name SoundIoOutStreamJackPort *osjp = &osj->ports[ch];
//if ((err = jack_connect(osj->client, source_port, dest_port))) const char *dest_port_name = osjp->dest_port_name;
// return SoundIoErrorStreaming; // allow unconnected ports
if (!dest_port_name)
continue;
const char *source_port_name = jack_port_name(osjp->source_port);
if ((err = jack_connect(osj->client, source_port_name, dest_port_name)))
return SoundIoErrorStreaming;
} }
} }
@ -153,14 +201,29 @@ static int outstream_start_jack(struct SoundIoPrivate *si, struct SoundIoOutStre
return outstream_pause_jack(si, os, false); return outstream_pause_jack(si, os, false);
} }
static int outstream_begin_write_jack(struct SoundIoPrivate *, struct SoundIoOutStreamPrivate *, static int outstream_begin_write_jack(struct SoundIoPrivate *si, struct SoundIoOutStreamPrivate *os,
SoundIoChannelArea **out_areas, int *frame_count) SoundIoChannelArea **out_areas, int *frame_count)
{ {
soundio_panic("TODO begin write"); SoundIoOutStream *outstream = &os->pub;
SoundIoOutStreamJack *osj = &os->backend_data.jack;
SoundIoJack *sij = &si->backend_data.jack;
assert(*frame_count <= sij->buffer_size);
for (int ch = 0; ch < outstream->layout.channel_count; ch += 1) {
SoundIoOutStreamJackPort *osjp = &osj->ports[ch];
if (!(osj->areas[ch].ptr = (char*)jack_port_get_buffer(osjp->source_port, *frame_count)))
return SoundIoErrorStreaming;
osj->areas[ch].step = outstream->bytes_per_sample;
}
*out_areas = osj->areas;
return 0;
} }
static int outstream_end_write_jack(struct SoundIoPrivate *, struct SoundIoOutStreamPrivate *, int frame_count) { static int outstream_end_write_jack(struct SoundIoPrivate *, struct SoundIoOutStreamPrivate *, int frame_count) {
soundio_panic("TODO end write"); return 0;
} }
static int outstream_clear_buffer_jack(struct SoundIoPrivate *, struct SoundIoOutStreamPrivate *) { static int outstream_clear_buffer_jack(struct SoundIoPrivate *, struct SoundIoOutStreamPrivate *) {
@ -250,6 +313,15 @@ static char *dupe_str(const char *str, int str_len) {
return out; return out;
} }
static void destruct_device(SoundIoDevicePrivate *dp) {
SoundIoDeviceJack *dj = &dp->backend_data.jack;
for (int i = 0; i < dj->port_count; i += 1) {
SoundIoDeviceJackPort *djp = &dj->ports[i];
destroy(djp->full_name);
}
deallocate(dj->ports, dj->port_count);
}
static int refresh_devices(SoundIoPrivate *si) { 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;
@ -270,6 +342,7 @@ static int refresh_devices(SoundIoPrivate *si) {
const char **port_name_ptr = port_names; const char **port_name_ptr = port_names;
while (*port_name_ptr) { while (*port_name_ptr) {
const char *client_and_port_name = *port_name_ptr; 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); jack_port_t *jport = jack_port_by_name(sij->client, client_and_port_name);
int flags = jack_port_flags(jport); int flags = jack_port_flags(jport);
@ -287,7 +360,7 @@ static int refresh_devices(SoundIoPrivate *si) {
const char *port_name = nullptr; const char *port_name = nullptr;
int client_name_len; int client_name_len;
int port_name_len; int port_name_len;
split_str(client_and_port_name, strlen(client_and_port_name), ':', split_str(client_and_port_name, client_and_port_name_len, ':',
&client_name, &client_name_len, &port_name, &port_name_len); &client_name, &client_name_len, &port_name, &port_name_len);
if (!client_name || !port_name) { if (!client_name || !port_name) {
// device does not have colon, skip it // device does not have colon, skip it
@ -305,9 +378,11 @@ static int refresh_devices(SoundIoPrivate *si) {
continue; continue;
} }
SoundIoJackPort *port = &client->ports[client->port_count++]; 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 = port_name;
port->name_len = port_name_len; port->name_len = port_name_len;
port->channel_id = soundio_parse_channel_id(port_name, port_name_len);
port_name_ptr += 1; port_name_ptr += 1;
} }
@ -317,18 +392,22 @@ static int refresh_devices(SoundIoPrivate *si) {
if (client->port_count <= 0) if (client->port_count <= 0)
continue; continue;
SoundIoDevice *device = create<SoundIoDevice>(); SoundIoDevicePrivate *dev = create<SoundIoDevicePrivate>();
if (!device) { if (!dev) {
jack_free(port_names); jack_free(port_names);
destroy(devices_info); destroy(devices_info);
return SoundIoErrorNoMem; return SoundIoErrorNoMem;
} }
SoundIoDevice *device = &dev->pub;
SoundIoDeviceJack *dj = &dev->backend_data.jack;
int description_len = client->name_len + 3 + 2 * client->port_count; int description_len = client->name_len + 3 + 2 * client->port_count;
for (int port_index = 0; port_index < client->port_count; port_index += 1) { for (int port_index = 0; port_index < client->port_count; port_index += 1) {
SoundIoJackPort *port = &client->ports[port_index]; SoundIoJackPort *port = &client->ports[port_index];
description_len += port->name_len; description_len += port->name_len;
} }
dev->destruct = destruct_device;
device->ref_count = 1; device->ref_count = 1;
device->soundio = soundio; device->soundio = soundio;
device->is_raw = false; device->is_raw = false;
@ -346,14 +425,31 @@ static int refresh_devices(SoundIoPrivate *si) {
device->buffer_duration_min = sij->buffer_size / (double) 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_max = device->buffer_duration_min;
device->buffer_duration_current = device->buffer_duration_min; device->buffer_duration_current = device->buffer_duration_min;
dj->port_count = client->port_count;
dj->ports = allocate<SoundIoDeviceJackPort>(dj->port_count);
if (!device->name || !device->description || !device->layouts || !device->formats) { if (!device->name || !device->description || !device->layouts || !device->formats || !dj->ports) {
jack_free(port_names); jack_free(port_names);
soundio_device_unref(device); soundio_device_unref(device);
destroy(devices_info); destroy(devices_info);
return SoundIoErrorNoMem; 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 = dupe_str(port->full_name, port->full_name_len);
djp->full_name_len = port->full_name_len;
djp->channel_id = port->channel_id;
if (!djp->full_name) {
jack_free(port_names);
soundio_device_unref(device);
destroy(devices_info);
return SoundIoErrorNoMem;
}
}
memcpy(device->description, client->name, client->name_len); memcpy(device->description, client->name, client->name_len);
memcpy(&device->description[client->name_len], ": ", 2); memcpy(&device->description[client->name_len], ": ", 2);
int index = client->name_len + 2; int index = client->name_len + 2;
@ -367,12 +463,19 @@ static int refresh_devices(SoundIoPrivate *si) {
} }
} }
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); const struct SoundIoChannelLayout *layout = soundio_channel_layout_get_default(client->port_count);
if (layout) { if (layout)
device->current_layout = *layout; device->current_layout = *layout;
} else { } else {
for (int port_index = 0; port_index < client->port_count; port_index += 1) soundio_channel_layout_detect_builtin(&device->current_layout);
device->current_layout.channels[port_index] = SoundIoChannelIdInvalid;
} }
device->layouts[0] = device->current_layout; device->layouts[0] = device->current_layout;
@ -434,11 +537,11 @@ static int sample_rate_callback(jack_nframes_t nframes, void *arg) {
return 0; return 0;
} }
/* TODO
static int xrun_callback(void *arg) { static int xrun_callback(void *arg) {
//SoundIoPrivate *si = (SoundIoPrivate *)arg;
soundio_panic("TODO xrun callback");
return 0; 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;
@ -529,10 +632,12 @@ 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))) { if ((err = jack_set_xrun_callback(sij->client, xrun_callback, si))) {
destroy_jack(si); destroy_jack(si);
return SoundIoErrorInitAudioBackend; 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;

View file

@ -15,8 +15,15 @@
int soundio_jack_init(struct SoundIoPrivate *si); int soundio_jack_init(struct SoundIoPrivate *si);
struct SoundIoDeviceJack { struct SoundIoDeviceJackPort {
char *full_name;
int full_name_len;
SoundIoChannelId channel_id;
};
struct SoundIoDeviceJack {
int port_count;
SoundIoDeviceJackPort *ports;
}; };
struct SoundIoJack { struct SoundIoJack {
@ -30,9 +37,16 @@ struct SoundIoJack {
int buffer_size; int buffer_size;
}; };
struct SoundIoOutStreamJackPort {
jack_port_t *source_port;
const char *dest_port_name;
int dest_port_name_len;
};
struct SoundIoOutStreamJack { struct SoundIoOutStreamJack {
jack_client_t *client; jack_client_t *client;
jack_port_t *ports[SOUNDIO_MAX_CHANNELS]; SoundIoOutStreamJackPort ports[SOUNDIO_MAX_CHANNELS];
SoundIoChannelArea areas[SOUNDIO_MAX_CHANNELS];
}; };
struct SoundIoInStreamJack { struct SoundIoInStreamJack {

View file

@ -256,9 +256,10 @@ static void sink_info_callback(pa_context *pulse_context, const pa_sink_info *in
sipa->have_sink_list = true; sipa->have_sink_list = true;
finish_device_query(si); finish_device_query(si);
} else { } else {
SoundIoDevice *device = create<SoundIoDevice>(); SoundIoDevicePrivate *dev = create<SoundIoDevicePrivate>();
if (!device) if (!dev)
soundio_panic("out of memory"); soundio_panic("out of memory");
SoundIoDevice *device = &dev->pub;
device->ref_count = 1; device->ref_count = 1;
device->soundio = soundio; device->soundio = soundio;
@ -306,9 +307,10 @@ static void source_info_callback(pa_context *pulse_context, const pa_source_info
sipa->have_source_list = true; sipa->have_source_list = true;
finish_device_query(si); finish_device_query(si);
} else { } else {
SoundIoDevice *device = create<SoundIoDevice>(); SoundIoDevicePrivate *dev = create<SoundIoDevicePrivate>();
if (!device) if (!dev)
soundio_panic("out of memory"); soundio_panic("out of memory");
SoundIoDevice *device = &dev->pub;
device->ref_count = 1; device->ref_count = 1;
device->soundio = soundio; device->soundio = soundio;

View file

@ -295,11 +295,16 @@ void soundio_device_unref(struct SoundIoDevice *device) {
assert(device->ref_count >= 0); assert(device->ref_count >= 0);
if (device->ref_count == 0) { if (device->ref_count == 0) {
SoundIoDevicePrivate *dev = (SoundIoDevicePrivate *)device;
if (dev->destruct)
dev->destruct(dev);
free(device->name); free(device->name);
free(device->description); free(device->description);
deallocate(device->formats, device->format_count); deallocate(device->formats, device->format_count);
deallocate(device->layouts, device->layout_count); deallocate(device->layouts, device->layout_count);
destroy(device);
destroy(dev);
} }
} }

View file

@ -494,6 +494,10 @@ bool soundio_channel_layout_equal(
const struct SoundIoChannelLayout *b); const struct SoundIoChannelLayout *b);
const char *soundio_get_channel_name(enum SoundIoChannelId id); const char *soundio_get_channel_name(enum SoundIoChannelId id);
// Given UTF-8 encoded text which is the name of a channel such as
// "Front Left", "FL", or "front-left", return the corresponding
// SoundIoChannelId. Returns SoundIoChannelIdInvalid for no match.
enum SoundIoChannelId soundio_parse_channel_id(const char *str, int str_len);
int soundio_channel_layout_builtin_count(void); int soundio_channel_layout_builtin_count(void);
const struct SoundIoChannelLayout *soundio_channel_layout_get_builtin(int index); const struct SoundIoChannelLayout *soundio_channel_layout_get_builtin(int index);

View file

@ -129,6 +129,7 @@ struct SoundIoPrivate {
struct SoundIoDevicePrivate { struct SoundIoDevicePrivate {
SoundIoDevice pub; SoundIoDevice pub;
SoundIoDeviceBackendData backend_data; SoundIoDeviceBackendData backend_data;
void (*destruct)(SoundIoDevicePrivate *);
}; };
void soundio_destroy_devices_info(struct SoundIoDevicesInfo *devices_info); void soundio_destroy_devices_info(struct SoundIoDevicesInfo *devices_info);