ALSA: poll instead of async handler

This commit is contained in:
Andrew Kelley 2015-07-21 21:21:00 -07:00
parent f6684a0585
commit eca4bc6074
3 changed files with 157 additions and 27 deletions

View file

@ -261,10 +261,13 @@ view `coverage/index.html` in a browser.
specify how much to peek() and if you don't peek all of it, save the specify how much to peek() and if you don't peek all of it, save the
unused to a buffer for you. unused to a buffer for you.
0. add len arguments to APIs that have char * 0. add len arguments to APIs that have char *
0. custom allocator support
0. Test in an app that needs to synchronize video to test the 0. Test in an app that needs to synchronize video to test the
latency/synchronization API. latency/synchronization API.
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. mlock memory which is accessed in the real time path
0. instead of `void *backend_data` use a union for better cache locality
and smaller mlock requirements
## Planned Uses for libsoundio ## Planned Uses for libsoundio

View file

@ -16,6 +16,15 @@
static snd_pcm_stream_t stream_types[] = {SND_PCM_STREAM_PLAYBACK, SND_PCM_STREAM_CAPTURE}; static snd_pcm_stream_t stream_types[] = {SND_PCM_STREAM_PLAYBACK, SND_PCM_STREAM_CAPTURE};
static snd_pcm_access_t prioritized_access_types[] = {
SND_PCM_ACCESS_MMAP_INTERLEAVED,
SND_PCM_ACCESS_MMAP_NONINTERLEAVED,
SND_PCM_ACCESS_MMAP_COMPLEX,
SND_PCM_ACCESS_RW_INTERLEAVED,
SND_PCM_ACCESS_RW_NONINTERLEAVED,
};
struct SoundIoAlsa { struct SoundIoAlsa {
SoundIoOsMutex *mutex; SoundIoOsMutex *mutex;
SoundIoOsCond *cond; SoundIoOsCond *cond;
@ -35,9 +44,17 @@ struct SoundIoOutStreamAlsa {
snd_pcm_t *handle; snd_pcm_t *handle;
snd_pcm_chmap_t *chmap; snd_pcm_chmap_t *chmap;
int chmap_size; int chmap_size;
snd_async_handler_t *ahandler;
snd_pcm_uframes_t offset; snd_pcm_uframes_t offset;
SoundIoChannelArea areas[SOUNDIO_MAX_CHANNELS]; SoundIoChannelArea areas[SOUNDIO_MAX_CHANNELS];
snd_pcm_access_t access;
int sample_buffer_size;
char *sample_buffer;
int alsa_areas_size;
snd_pcm_channel_area_t *alsa_areas;
int poll_fd_count;
struct pollfd *poll_fds;
SoundIoOsThread *thread;
atomic_flag thread_exit_flag;
}; };
struct SoundIoInStreamAlsa { struct SoundIoInStreamAlsa {
@ -260,6 +277,19 @@ static void test_fmt_mask(SoundIoDevice *device, const snd_pcm_format_mask_t *fm
} }
} }
static int set_access(snd_pcm_t *handle, snd_pcm_hw_params_t *hwparams, snd_pcm_access_t *out_access) {
for (int i = 0; i < array_length(prioritized_access_types); i += 1) {
snd_pcm_access_t access = prioritized_access_types[i];
int err = snd_pcm_hw_params_set_access(handle, hwparams, access);
if (err >= 0) {
if (out_access)
*out_access = access;
return 0;
}
}
return SoundIoErrorOpeningDevice;
}
// this function does not override device->formats, so if you want it to, deallocate and set it to NULL // this function does not override device->formats, so if you want it to, deallocate and set it to NULL
static int probe_open_device(SoundIoDevice *device, snd_pcm_t *handle, int resample) { static int probe_open_device(SoundIoDevice *device, snd_pcm_t *handle, int resample) {
int err; int err;
@ -273,13 +303,8 @@ static int probe_open_device(SoundIoDevice *device, snd_pcm_t *handle, int resam
if ((err = snd_pcm_hw_params_set_rate_resample(handle, hwparams, resample)) < 0) if ((err = snd_pcm_hw_params_set_rate_resample(handle, hwparams, resample)) < 0)
return SoundIoErrorOpeningDevice; return SoundIoErrorOpeningDevice;
if ((err = snd_pcm_hw_params_set_access(handle, hwparams, SND_PCM_ACCESS_MMAP_INTERLEAVED)) < 0) { if ((err = set_access(handle, hwparams, nullptr)))
if ((err = snd_pcm_hw_params_set_access(handle, hwparams, SND_PCM_ACCESS_MMAP_NONINTERLEAVED)) < 0) { return err;
if ((err = snd_pcm_hw_params_set_access(handle, hwparams, SND_PCM_ACCESS_MMAP_COMPLEX)) < 0) {
return SoundIoErrorIncompatibleDevice;
}
}
}
unsigned int channel_count; unsigned int channel_count;
if ((err = snd_pcm_hw_params_set_channels_last(handle, hwparams, &channel_count)) < 0) if ((err = snd_pcm_hw_params_set_channels_last(handle, hwparams, &channel_count)) < 0)
@ -840,11 +865,22 @@ static void outstream_destroy_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate *
if (!osa) if (!osa)
return; return;
deallocate(osa->chmap, osa->chmap_size); if (osa->thread) {
osa->thread_exit_flag.clear();
// TODO wake up poll
soundio_os_thread_destroy(osa->thread);
}
if (osa->handle) if (osa->handle)
snd_pcm_close(osa->handle); snd_pcm_close(osa->handle);
deallocate(osa->poll_fds, osa->poll_fd_count);
deallocate(osa->chmap, osa->chmap_size);
deallocate(osa->alsa_areas, osa->alsa_areas_size);
deallocate(osa->sample_buffer, osa->sample_buffer_size);
destroy(osa); destroy(osa);
os->backend_data = nullptr; os->backend_data = nullptr;
} }
@ -867,14 +903,40 @@ static int xrun_recovery(SoundIoOutStreamPrivate *os, int err) {
return err; return err;
} }
static void async_direct_callback(snd_async_handler_t *ahandler) { static int wait_for_poll(SoundIoOutStreamAlsa *osa) {
SoundIoOutStreamPrivate *os = (SoundIoOutStreamPrivate *)snd_async_handler_get_callback_private(ahandler); int err;
unsigned short revents;
for (;;) {
if ((err = poll(osa->poll_fds, osa->poll_fd_count, -1)) < 0)
return err;
if ((err = snd_pcm_poll_descriptors_revents(osa->handle,
osa->poll_fds, osa->poll_fd_count, &revents)) < 0)
{
return err;
}
if (revents & POLLERR)
return -EIO;
if (revents & POLLOUT)
return 0;
}
}
void outstream_thread_run(void *arg) {
SoundIoOutStreamPrivate *os = (SoundIoOutStreamPrivate *) arg;
SoundIoOutStream *outstream = &os->pub; SoundIoOutStream *outstream = &os->pub;
SoundIoOutStreamAlsa *osa = (SoundIoOutStreamAlsa *) os->backend_data; SoundIoOutStreamAlsa *osa = (SoundIoOutStreamAlsa *) os->backend_data;
int err; int err;
for (;;) { for (;;) {
if ((err = wait_for_poll(osa)) < 0) {
if (!osa->thread_exit_flag.test_and_set())
return;
outstream->error_callback(outstream, SoundIoErrorStreaming);
return;
}
if (!osa->thread_exit_flag.test_and_set())
return;
snd_pcm_state_t state = snd_pcm_state(osa->handle); snd_pcm_state_t state = snd_pcm_state(osa->handle);
switch (state) { switch (state) {
case SND_PCM_STATE_OPEN: case SND_PCM_STATE_OPEN:
@ -903,7 +965,7 @@ static void async_direct_callback(snd_async_handler_t *ahandler) {
} }
outstream->write_callback(outstream, avail); outstream->write_callback(outstream, avail);
return; continue;
} }
case SND_PCM_STATE_XRUN: case SND_PCM_STATE_XRUN:
if ((err = xrun_recovery(os, -EPIPE)) < 0) { if ((err = xrun_recovery(os, -EPIPE)) < 0) {
@ -945,7 +1007,9 @@ static int outstream_open_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os)
} }
os->backend_data = osa; os->backend_data = osa;
osa->chmap_size = sizeof(int) + sizeof(int) * outstream->layout.channel_count; int ch_count = outstream->layout.channel_count;
osa->chmap_size = sizeof(int) + sizeof(int) * ch_count;
osa->chmap = (snd_pcm_chmap_t *)allocate<char>(osa->chmap_size); osa->chmap = (snd_pcm_chmap_t *)allocate<char>(osa->chmap_size);
if (!osa->chmap) { if (!osa->chmap) {
outstream_destroy_alsa(si, os); outstream_destroy_alsa(si, os);
@ -975,16 +1039,12 @@ static int outstream_open_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os)
return SoundIoErrorOpeningDevice; return SoundIoErrorOpeningDevice;
} }
if ((err = snd_pcm_hw_params_set_access(osa->handle, hwparams, SND_PCM_ACCESS_MMAP_INTERLEAVED)) < 0) { if ((err = set_access(osa->handle, hwparams, &osa->access))) {
if ((err = snd_pcm_hw_params_set_access(osa->handle, hwparams, SND_PCM_ACCESS_MMAP_NONINTERLEAVED)) < 0) {
if ((err = snd_pcm_hw_params_set_access(osa->handle, hwparams, SND_PCM_ACCESS_MMAP_COMPLEX)) < 0) {
outstream_destroy_alsa(si, os); outstream_destroy_alsa(si, os);
return SoundIoErrorIncompatibleDevice; return err;
}
}
} }
if ((err = snd_pcm_hw_params_set_channels(osa->handle, hwparams, outstream->layout.channel_count)) < 0) { if ((err = snd_pcm_hw_params_set_channels(osa->handle, hwparams, ch_count)) < 0) {
outstream_destroy_alsa(si, os); outstream_destroy_alsa(si, os);
return SoundIoErrorOpeningDevice; return SoundIoErrorOpeningDevice;
} }
@ -994,7 +1054,14 @@ static int outstream_open_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os)
return SoundIoErrorOpeningDevice; return SoundIoErrorOpeningDevice;
} }
if ((err = snd_pcm_hw_params_set_format(osa->handle, hwparams, to_alsa_fmt(outstream->format))) < 0) { snd_pcm_format_t format = to_alsa_fmt(outstream->format);
int phys_bits_per_sample = snd_pcm_format_physical_width(format);
if (phys_bits_per_sample % 8 != 0) {
outstream_destroy_alsa(si, os);
return SoundIoErrorIncompatibleDevice;
}
int phys_bytes_per_sample = phys_bits_per_sample / 8;
if ((err = snd_pcm_hw_params_set_format(osa->handle, hwparams, format)) < 0) {
outstream_destroy_alsa(si, os); outstream_destroy_alsa(si, os);
return SoundIoErrorOpeningDevice; return SoundIoErrorOpeningDevice;
} }
@ -1030,8 +1097,8 @@ static int outstream_open_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os)
} }
// set channel map // set channel map
osa->chmap->channels = outstream->layout.channel_count; osa->chmap->channels = ch_count;
for (int i = 0; i < outstream->layout.channel_count; i += 1) { for (int i = 0; i < ch_count; i += 1) {
osa->chmap->pos[i] = to_alsa_chmap_pos(outstream->layout.channels[i]); osa->chmap->pos[i] = to_alsa_chmap_pos(outstream->layout.channels[i]);
} }
if (snd_pcm_set_chmap(osa->handle, osa->chmap) < 0) { if (snd_pcm_set_chmap(osa->handle, osa->chmap) < 0) {
@ -1064,7 +1131,49 @@ static int outstream_open_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os)
return (err == -EINVAL) ? SoundIoErrorIncompatibleDevice : SoundIoErrorOpeningDevice; return (err == -EINVAL) ? SoundIoErrorIncompatibleDevice : SoundIoErrorOpeningDevice;
} }
if ((err = snd_async_add_pcm_handler(&osa->ahandler, osa->handle, async_direct_callback, os)) < 0) { if (osa->access == SND_PCM_ACCESS_RW_INTERLEAVED || osa->access == SND_PCM_ACCESS_RW_NONINTERLEAVED) {
osa->sample_buffer_size = ch_count * period_size * phys_bytes_per_sample;
osa->sample_buffer = allocate_nonzero<char>(osa->sample_buffer_size);
if (!osa->sample_buffer) {
outstream_destroy_alsa(si, os);
return SoundIoErrorNoMem;
}
osa->alsa_areas_size = ch_count;
osa->alsa_areas = allocate<snd_pcm_channel_area_t>(osa->alsa_areas_size);
if (!osa->alsa_areas) {
outstream_destroy_alsa(si, os);
return SoundIoErrorNoMem;
}
if (osa->access == SND_PCM_ACCESS_RW_INTERLEAVED) {
for (int ch = 0; ch < ch_count; ch += 1) {
osa->alsa_areas[ch].addr = osa->sample_buffer;
osa->alsa_areas[ch].first = ch * phys_bits_per_sample;
osa->alsa_areas[ch].step = ch_count * phys_bits_per_sample;
}
} else {
for (int ch = 0; ch < ch_count; ch += 1) {
osa->alsa_areas[ch].addr = osa->sample_buffer;
osa->alsa_areas[ch].first = ch * phys_bits_per_sample * period_size;
osa->alsa_areas[ch].step = phys_bits_per_sample;
}
}
}
osa->poll_fd_count = snd_pcm_poll_descriptors_count(osa->handle);
if (osa->poll_fd_count <= 0) {
outstream_destroy_alsa(si, os);
return SoundIoErrorOpeningDevice;
}
osa->poll_fds = allocate<struct pollfd>(osa->poll_fd_count);
if (!osa->poll_fds) {
outstream_destroy_alsa(si, os);
return SoundIoErrorNoMem;
}
if ((err = snd_pcm_poll_descriptors(osa->handle, osa->poll_fds, osa->poll_fd_count)) < 0) {
outstream_destroy_alsa(si, os); outstream_destroy_alsa(si, os);
return SoundIoErrorOpeningDevice; return SoundIoErrorOpeningDevice;
} }
@ -1075,7 +1184,14 @@ static int outstream_open_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os)
static int outstream_start_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) { static int outstream_start_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) {
SoundIoOutStreamAlsa *osa = (SoundIoOutStreamAlsa *) os->backend_data; SoundIoOutStreamAlsa *osa = (SoundIoOutStreamAlsa *) os->backend_data;
async_direct_callback(osa->ahandler); assert(!osa->thread);
osa->thread_exit_flag.test_and_set();
int err;
if ((err = soundio_os_thread_create(outstream_thread_run, os, true, &osa->thread))) {
outstream_destroy_alsa(si, os);
return err;
}
return 0; return 0;
} }

View file

@ -111,3 +111,14 @@
fun:snd_pcm_get_chmap fun:snd_pcm_get_chmap
fun:snd_pcm_set_chmap fun:snd_pcm_set_chmap
} }
{
<insert_a_suppression_name_here>
Memcheck:Cond
fun:snd_interval_floor
fun:snd_pcm_plug_hw_refine_cchange
fun:snd1_pcm_hw_refine_slave
fun:snd_pcm_plug_hw_refine
fun:snd_pcm_hw_refine
fun:snd1_pcm_hw_param_set_last
...
}