ALSA: better handling of raw devices

* Recover from xruns
 * Better period size choice
This commit is contained in:
Andrew Kelley 2015-08-27 22:58:17 -07:00
parent 7a714298c1
commit dd6b7003d9
3 changed files with 30 additions and 26 deletions

View file

@ -274,7 +274,6 @@ Then look at `html/index.html` in a browser.
## Roadmap ## Roadmap
0. `sio_microphone` with ALSA backend in raw mode quickly causes unrecoverable streaming failure
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
listening the events should line up exactly with a beat or visual listening the events should line up exactly with a beat or visual

View file

@ -934,7 +934,7 @@ static void outstream_destroy_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate *
free(osa->sample_buffer); 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; SoundIoOutStream *outstream = &os->pub;
SoundIoOutStreamAlsa *osa = &os->backend_data.alsa; SoundIoOutStreamAlsa *osa = &os->backend_data.alsa;
if (err == -EPIPE) { if (err == -EPIPE) {
@ -974,37 +974,43 @@ static int instream_xrun_recovery(SoundIoInStreamPrivate *is, int err) {
return 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; int err;
unsigned short revents; unsigned short revents;
for (;;) { 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; return err;
}
if ((err = snd_pcm_poll_descriptors_revents(osa->handle, if ((err = snd_pcm_poll_descriptors_revents(osa->handle,
osa->poll_fds, osa->poll_fd_count, &revents)) < 0) osa->poll_fds, osa->poll_fd_count, &revents)) < 0)
{ {
return err; return err;
} }
if (revents & POLLERR) if (revents & (POLLERR|POLLNVAL|POLLHUP)) {
return -EIO; return 0;
}
if (revents & POLLOUT) if (revents & POLLOUT)
return 0; 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; int err;
unsigned short revents; unsigned short revents;
for (;;) { 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; return err;
}
if ((err = snd_pcm_poll_descriptors_revents(isa->handle, if ((err = snd_pcm_poll_descriptors_revents(isa->handle,
isa->poll_fds, isa->poll_fd_count, &revents)) < 0) isa->poll_fds, isa->poll_fd_count, &revents)) < 0)
{ {
return err; return err;
} }
if (revents & POLLERR) if (revents & (POLLERR|POLLNVAL|POLLHUP)) {
return -EIO; return 0;
}
if (revents & POLLIN) if (revents & POLLIN)
return 0; return 0;
} }
@ -1049,7 +1055,7 @@ void outstream_thread_run(void *arg) {
} }
case SND_PCM_STATE_RUNNING: 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()) if (!osa->thread_exit_flag.test_and_set())
return; return;
outstream->error_callback(outstream, SoundIoErrorStreaming); 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); snd_pcm_sframes_t avail = snd_pcm_avail_update(osa->handle);
if (avail < 0) { if (avail < 0) {
if ((err = os_xrun_recovery(os, avail)) < 0) { if ((err = outstream_xrun_recovery(os, avail)) < 0) {
outstream->error_callback(outstream, SoundIoErrorStreaming); outstream->error_callback(outstream, SoundIoErrorStreaming);
return; return;
} }
@ -1071,13 +1077,13 @@ void outstream_thread_run(void *arg) {
continue; continue;
} }
case SND_PCM_STATE_XRUN: 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); outstream->error_callback(outstream, SoundIoErrorStreaming);
return; return;
} }
continue; continue;
case SND_PCM_STATE_SUSPENDED: 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); outstream->error_callback(outstream, SoundIoErrorStreaming);
return; return;
} }
@ -1116,7 +1122,7 @@ static void instream_thread_run(void *arg) {
continue; continue;
case SND_PCM_STATE_RUNNING: 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()) if (!isa->thread_exit_flag.test_and_set())
return; return;
instream->error_callback(instream, SoundIoErrorStreaming); instream->error_callback(instream, SoundIoErrorStreaming);
@ -1126,6 +1132,7 @@ static void instream_thread_run(void *arg) {
return; return;
snd_pcm_sframes_t avail = snd_pcm_avail_update(isa->handle); snd_pcm_sframes_t avail = snd_pcm_avail_update(isa->handle);
if (avail < 0) { if (avail < 0) {
if ((err = instream_xrun_recovery(is, avail)) < 0) { if ((err = instream_xrun_recovery(is, avail)) < 0) {
instream->error_callback(instream, SoundIoErrorStreaming); instream->error_callback(instream, SoundIoErrorStreaming);
@ -1134,7 +1141,8 @@ static void instream_thread_run(void *arg) {
continue; continue;
} }
instream->read_callback(instream, 0, avail); if (avail > 0)
instream->read_callback(instream, 0, avail);
continue; continue;
} }
case SND_PCM_STATE_XRUN: 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; outstream->software_latency = ((double)osa->buffer_size_frames) / (double)outstream->sample_rate;
if (device->is_raw) { if (device->is_raw) {
unsigned int microseconds; unsigned int microseconds = 0.25 * outstream->software_latency * 1000000.0;
if ((err = snd_pcm_hw_params_set_period_time_first(osa->handle, hwparams, &microseconds, nullptr)) < 0) { if ((err = snd_pcm_hw_params_set_period_time_near(osa->handle, hwparams, &microseconds, nullptr)) < 0) {
outstream_destroy_alsa(si, os); outstream_destroy_alsa(si, os);
return SoundIoErrorOpeningDevice; return SoundIoErrorOpeningDevice;
} }
@ -1518,12 +1526,13 @@ static int instream_open_alsa(SoundIoPrivate *si, SoundIoInStreamPrivate *is) {
return SoundIoErrorOpeningDevice; 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) { if ((err = snd_pcm_hw_params_set_period_size_near(isa->handle, hwparams, &period_frames, nullptr)) < 0) {
instream_destroy_alsa(si, is); instream_destroy_alsa(si, is);
return SoundIoErrorOpeningDevice; return SoundIoErrorOpeningDevice;
} }
instream->software_latency = ((double)period_frames) / (double)instream->sample_rate; instream->software_latency = ((double)period_frames) / (double)instream->sample_rate;
isa->period_size = period_frames;
snd_pcm_uframes_t buffer_size_frames; snd_pcm_uframes_t buffer_size_frames;
@ -1532,13 +1541,6 @@ static int instream_open_alsa(SoundIoPrivate *si, SoundIoInStreamPrivate *is) {
return SoundIoErrorOpeningDevice; 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 // write the hardware parameters to device
if ((err = snd_pcm_hw_params(isa->handle, hwparams)) < 0) { if ((err = snd_pcm_hw_params(isa->handle, hwparams)) < 0) {
instream_destroy_alsa(si, is); instream_destroy_alsa(si, is);

View file

@ -541,6 +541,8 @@ static void default_instream_error_callback(struct SoundIoInStream *is, int err)
soundio_panic("libsoundio: %s", soundio_strerror(err)); soundio_panic("libsoundio: %s", soundio_strerror(err));
} }
static void default_overflow_callback(struct SoundIoInStream *instream) { }
struct SoundIoInStream *soundio_instream_create(struct SoundIoDevice *device) { struct SoundIoInStream *soundio_instream_create(struct SoundIoDevice *device) {
SoundIoInStreamPrivate *is = allocate<SoundIoInStreamPrivate>(1); SoundIoInStreamPrivate *is = allocate<SoundIoInStreamPrivate>(1);
SoundIoInStream *instream = &is->pub; SoundIoInStream *instream = &is->pub;
@ -554,6 +556,7 @@ struct SoundIoInStream *soundio_instream_create(struct SoundIoDevice *device) {
soundio_device_ref(device); soundio_device_ref(device);
instream->error_callback = default_instream_error_callback; instream->error_callback = default_instream_error_callback;
instream->overflow_callback = default_overflow_callback;
return instream; return instream;
} }