From dd6b7003d9508877ba30912e11e7c5b59e953e98 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 27 Aug 2015 22:58:17 -0700 Subject: [PATCH] ALSA: better handling of raw devices * Recover from xruns * Better period size choice --- README.md | 1 - src/alsa.cpp | 52 +++++++++++++++++++++++++------------------------ src/soundio.cpp | 3 +++ 3 files changed, 30 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 3b526be..3089537 100644 --- a/README.md +++ b/README.md @@ -274,7 +274,6 @@ Then look at `html/index.html` in a browser. ## Roadmap - 0. `sio_microphone` with ALSA backend in raw mode quickly causes unrecoverable streaming failure 0. Create a test for the latency / synchronization API. - Input is an audio file and some events indexed at particular frame - when listening the events should line up exactly with a beat or visual diff --git a/src/alsa.cpp b/src/alsa.cpp index d75de3e..307df65 100644 --- a/src/alsa.cpp +++ b/src/alsa.cpp @@ -934,7 +934,7 @@ static void outstream_destroy_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate * free(osa->sample_buffer); } -static int os_xrun_recovery(SoundIoOutStreamPrivate *os, int err) { +static int outstream_xrun_recovery(SoundIoOutStreamPrivate *os, int err) { SoundIoOutStream *outstream = &os->pub; SoundIoOutStreamAlsa *osa = &os->backend_data.alsa; if (err == -EPIPE) { @@ -974,37 +974,43 @@ static int instream_xrun_recovery(SoundIoInStreamPrivate *is, int err) { return err; } -static int wait_for_poll(SoundIoOutStreamAlsa *osa) { +static int outstream_wait_for_poll(SoundIoOutStreamPrivate *os) { + SoundIoOutStreamAlsa *osa = &os->backend_data.alsa; int err; unsigned short revents; for (;;) { - if ((err = poll(osa->poll_fds, osa->poll_fd_count, -1)) < 0) + 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 & (POLLERR|POLLNVAL|POLLHUP)) { + return 0; + } if (revents & POLLOUT) return 0; } } -static int instream_wait_for_poll(SoundIoInStreamAlsa *isa) { +static int instream_wait_for_poll(SoundIoInStreamPrivate *is) { + SoundIoInStreamAlsa *isa = &is->backend_data.alsa; int err; unsigned short revents; for (;;) { - if ((err = poll(isa->poll_fds, isa->poll_fd_count, -1)) < 0) + if ((err = poll(isa->poll_fds, isa->poll_fd_count, -1)) < 0) { return err; + } if ((err = snd_pcm_poll_descriptors_revents(isa->handle, isa->poll_fds, isa->poll_fd_count, &revents)) < 0) { return err; } - if (revents & POLLERR) - return -EIO; + if (revents & (POLLERR|POLLNVAL|POLLHUP)) { + return 0; + } if (revents & POLLIN) return 0; } @@ -1049,7 +1055,7 @@ void outstream_thread_run(void *arg) { } case SND_PCM_STATE_RUNNING: { - if ((err = wait_for_poll(osa)) < 0) { + if ((err = outstream_wait_for_poll(os)) < 0) { if (!osa->thread_exit_flag.test_and_set()) return; outstream->error_callback(outstream, SoundIoErrorStreaming); @@ -1060,7 +1066,7 @@ void outstream_thread_run(void *arg) { snd_pcm_sframes_t avail = snd_pcm_avail_update(osa->handle); if (avail < 0) { - if ((err = os_xrun_recovery(os, avail)) < 0) { + if ((err = outstream_xrun_recovery(os, avail)) < 0) { outstream->error_callback(outstream, SoundIoErrorStreaming); return; } @@ -1071,13 +1077,13 @@ void outstream_thread_run(void *arg) { continue; } case SND_PCM_STATE_XRUN: - if ((err = os_xrun_recovery(os, -EPIPE)) < 0) { + if ((err = outstream_xrun_recovery(os, -EPIPE)) < 0) { outstream->error_callback(outstream, SoundIoErrorStreaming); return; } continue; case SND_PCM_STATE_SUSPENDED: - if ((err = os_xrun_recovery(os, -ESTRPIPE)) < 0) { + if ((err = outstream_xrun_recovery(os, -ESTRPIPE)) < 0) { outstream->error_callback(outstream, SoundIoErrorStreaming); return; } @@ -1116,7 +1122,7 @@ static void instream_thread_run(void *arg) { continue; case SND_PCM_STATE_RUNNING: { - if ((err = instream_wait_for_poll(isa)) < 0) { + if ((err = instream_wait_for_poll(is)) < 0) { if (!isa->thread_exit_flag.test_and_set()) return; instream->error_callback(instream, SoundIoErrorStreaming); @@ -1126,6 +1132,7 @@ static void instream_thread_run(void *arg) { return; snd_pcm_sframes_t avail = snd_pcm_avail_update(isa->handle); + if (avail < 0) { if ((err = instream_xrun_recovery(is, avail)) < 0) { instream->error_callback(instream, SoundIoErrorStreaming); @@ -1134,7 +1141,8 @@ static void instream_thread_run(void *arg) { continue; } - instream->read_callback(instream, 0, avail); + if (avail > 0) + instream->read_callback(instream, 0, avail); continue; } case SND_PCM_STATE_XRUN: @@ -1235,8 +1243,8 @@ static int outstream_open_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) outstream->software_latency = ((double)osa->buffer_size_frames) / (double)outstream->sample_rate; if (device->is_raw) { - unsigned int microseconds; - if ((err = snd_pcm_hw_params_set_period_time_first(osa->handle, hwparams, µseconds, nullptr)) < 0) { + unsigned int microseconds = 0.25 * outstream->software_latency * 1000000.0; + if ((err = snd_pcm_hw_params_set_period_time_near(osa->handle, hwparams, µseconds, nullptr)) < 0) { outstream_destroy_alsa(si, os); return SoundIoErrorOpeningDevice; } @@ -1518,12 +1526,13 @@ static int instream_open_alsa(SoundIoPrivate *si, SoundIoInStreamPrivate *is) { return SoundIoErrorOpeningDevice; } - snd_pcm_uframes_t period_frames = ceil(instream->software_latency * (double)instream->sample_rate); + snd_pcm_uframes_t period_frames = ceil(0.5 * instream->software_latency * (double)instream->sample_rate); if ((err = snd_pcm_hw_params_set_period_size_near(isa->handle, hwparams, &period_frames, nullptr)) < 0) { instream_destroy_alsa(si, is); return SoundIoErrorOpeningDevice; } instream->software_latency = ((double)period_frames) / (double)instream->sample_rate; + isa->period_size = period_frames; snd_pcm_uframes_t buffer_size_frames; @@ -1532,13 +1541,6 @@ static int instream_open_alsa(SoundIoPrivate *si, SoundIoInStreamPrivate *is) { return SoundIoErrorOpeningDevice; } - snd_pcm_uframes_t period_size; - if ((snd_pcm_hw_params_get_period_size(hwparams, &period_size, nullptr)) < 0) { - instream_destroy_alsa(si, is); - return SoundIoErrorOpeningDevice; - } - isa->period_size = period_size; - // write the hardware parameters to device if ((err = snd_pcm_hw_params(isa->handle, hwparams)) < 0) { instream_destroy_alsa(si, is); diff --git a/src/soundio.cpp b/src/soundio.cpp index ce47e76..d070826 100644 --- a/src/soundio.cpp +++ b/src/soundio.cpp @@ -541,6 +541,8 @@ static void default_instream_error_callback(struct SoundIoInStream *is, int err) soundio_panic("libsoundio: %s", soundio_strerror(err)); } +static void default_overflow_callback(struct SoundIoInStream *instream) { } + struct SoundIoInStream *soundio_instream_create(struct SoundIoDevice *device) { SoundIoInStreamPrivate *is = allocate(1); SoundIoInStream *instream = &is->pub; @@ -554,6 +556,7 @@ struct SoundIoInStream *soundio_instream_create(struct SoundIoDevice *device) { soundio_device_ref(device); instream->error_callback = default_instream_error_callback; + instream->overflow_callback = default_overflow_callback; return instream; }