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
unused to a buffer for you.
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
latency/synchronization API.
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

View file

@ -16,6 +16,15 @@
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 {
SoundIoOsMutex *mutex;
SoundIoOsCond *cond;
@ -35,9 +44,17 @@ struct SoundIoOutStreamAlsa {
snd_pcm_t *handle;
snd_pcm_chmap_t *chmap;
int chmap_size;
snd_async_handler_t *ahandler;
snd_pcm_uframes_t offset;
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 {
@ -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
static int probe_open_device(SoundIoDevice *device, snd_pcm_t *handle, int resample) {
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)
return SoundIoErrorOpeningDevice;
if ((err = snd_pcm_hw_params_set_access(handle, hwparams, SND_PCM_ACCESS_MMAP_INTERLEAVED)) < 0) {
if ((err = snd_pcm_hw_params_set_access(handle, hwparams, SND_PCM_ACCESS_MMAP_NONINTERLEAVED)) < 0) {
if ((err = snd_pcm_hw_params_set_access(handle, hwparams, SND_PCM_ACCESS_MMAP_COMPLEX)) < 0) {
return SoundIoErrorIncompatibleDevice;
}
}
}
if ((err = set_access(handle, hwparams, nullptr)))
return err;
unsigned int channel_count;
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)
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)
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);
os->backend_data = nullptr;
}
@ -867,14 +903,40 @@ static int xrun_recovery(SoundIoOutStreamPrivate *os, int err) {
return err;
}
static void async_direct_callback(snd_async_handler_t *ahandler) {
SoundIoOutStreamPrivate *os = (SoundIoOutStreamPrivate *)snd_async_handler_get_callback_private(ahandler);
static int wait_for_poll(SoundIoOutStreamAlsa *osa) {
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;
SoundIoOutStreamAlsa *osa = (SoundIoOutStreamAlsa *) os->backend_data;
int err;
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);
switch (state) {
case SND_PCM_STATE_OPEN:
@ -903,7 +965,7 @@ static void async_direct_callback(snd_async_handler_t *ahandler) {
}
outstream->write_callback(outstream, avail);
return;
continue;
}
case SND_PCM_STATE_XRUN:
if ((err = xrun_recovery(os, -EPIPE)) < 0) {
@ -945,7 +1007,9 @@ static int outstream_open_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os)
}
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);
if (!osa->chmap) {
outstream_destroy_alsa(si, os);
@ -975,16 +1039,12 @@ static int outstream_open_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os)
return SoundIoErrorOpeningDevice;
}
if ((err = snd_pcm_hw_params_set_access(osa->handle, hwparams, SND_PCM_ACCESS_MMAP_INTERLEAVED)) < 0) {
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) {
if ((err = set_access(osa->handle, hwparams, &osa->access))) {
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);
return SoundIoErrorOpeningDevice;
}
@ -994,7 +1054,14 @@ static int outstream_open_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os)
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);
return SoundIoErrorOpeningDevice;
}
@ -1030,8 +1097,8 @@ static int outstream_open_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os)
}
// set channel map
osa->chmap->channels = outstream->layout.channel_count;
for (int i = 0; i < outstream->layout.channel_count; i += 1) {
osa->chmap->channels = ch_count;
for (int i = 0; i < ch_count; i += 1) {
osa->chmap->pos[i] = to_alsa_chmap_pos(outstream->layout.channels[i]);
}
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;
}
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);
return SoundIoErrorOpeningDevice;
}
@ -1075,7 +1184,14 @@ static int outstream_open_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os)
static int outstream_start_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) {
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;
}

View file

@ -111,3 +111,14 @@
fun:snd_pcm_get_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
...
}