ALSA: recover from underflow gracefully

This commit is contained in:
Andrew Kelley 2015-07-29 22:39:27 -07:00
parent 2900616e9b
commit 5503072fc8
5 changed files with 22 additions and 18 deletions

View file

@ -241,10 +241,10 @@ view `coverage/index.html` in a browser.
## Roadmap
0. Integrate into libgroove and test with Groove Basin
0. implement CoreAudio (OSX) backend, get examples working
0. implement WASAPI (Windows) backend, get examples working
0. implement ASIO (Windows) backend, get examples working
0. Integrate into libgroove and test with Groove Basin
0. Avoid calling `soundio_panic` in PulseAudio.
0. PulseAudio: when prebuf gets set to 0 need to pass `PA_STREAM_START_CORKED`.
0. clear buffer maybe could take an argument to say how many frames to not clear
@ -259,6 +259,8 @@ view `coverage/index.html` in a browser.
`pa_stream_flush`, then uncork the stream.
0. Detect PulseAudio server going offline and emit `on_backend_disconnect`.
0. Instead fo open(), start(), pause(), open() starts it and it starts paused.
0. Create a test for underflow handling. It just makes a sine wave for 5
seconds, then on next audio callback, sleep for 2 seconds.
0. Create a test for pausing and resuming input and output streams.
0. Create a test for the latency / synchronization API.
- Input is an audio file and some events indexed at particular frame - when

View file

@ -30,7 +30,6 @@ static int usage(char *exe) {
return 1;
}
static const float PI = 3.1415926535f;
static float seconds_offset = 0.0f;
static void write_callback(struct SoundIoOutStream *outstream, int requested_frame_count) {
@ -61,8 +60,11 @@ static void write_callback(struct SoundIoOutStream *outstream, int requested_fra
}
seconds_offset += seconds_per_frame * frame_count;
if ((err = soundio_outstream_end_write(outstream, frame_count)))
if ((err = soundio_outstream_end_write(outstream, frame_count))) {
if (err == SoundIoErrorUnderflow)
return;
panic("%s", soundio_strerror(err));
}
requested_frame_count -= frame_count;
if (requested_frame_count <= 0)

View file

@ -867,7 +867,7 @@ static void outstream_destroy_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate *
deallocate(osa->sample_buffer, osa->sample_buffer_size);
}
static int xrun_recovery(SoundIoOutStreamPrivate *os, int err) {
static int os_xrun_recovery(SoundIoOutStreamPrivate *os, int err) {
SoundIoOutStream *outstream = &os->pub;
SoundIoOutStreamAlsa *osa = &os->backend_data.alsa;
if (err == -EPIPE) {
@ -950,11 +950,13 @@ void outstream_thread_run(void *arg) {
snd_pcm_state_t state = snd_pcm_state(osa->handle);
switch (state) {
case SND_PCM_STATE_SETUP:
{
if ((err = snd_pcm_prepare(osa->handle)) < 0) {
outstream->error_callback(outstream, SoundIoErrorStreaming);
return;
}
continue;
}
case SND_PCM_STATE_PREPARED:
{
if ((err = snd_pcm_start(osa->handle)) < 0) {
@ -976,7 +978,7 @@ void outstream_thread_run(void *arg) {
snd_pcm_sframes_t avail = snd_pcm_avail_update(osa->handle);
if (avail < 0) {
if ((err = xrun_recovery(os, avail)) < 0) {
if ((err = os_xrun_recovery(os, avail)) < 0) {
outstream->error_callback(outstream, SoundIoErrorStreaming);
return;
}
@ -987,13 +989,13 @@ void outstream_thread_run(void *arg) {
continue;
}
case SND_PCM_STATE_XRUN:
if ((err = xrun_recovery(os, -EPIPE)) < 0) {
if ((err = os_xrun_recovery(os, -EPIPE)) < 0) {
outstream->error_callback(outstream, SoundIoErrorStreaming);
return;
}
continue;
case SND_PCM_STATE_SUSPENDED:
if ((err = xrun_recovery(os, -ESTRPIPE)) < 0) {
if ((err = os_xrun_recovery(os, -ESTRPIPE)) < 0) {
outstream->error_callback(outstream, SoundIoErrorStreaming);
return;
}
@ -1240,20 +1242,11 @@ static int outstream_open_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os)
}
static int outstream_start_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) {
SoundIoOutStream *outstream = &os->pub;
SoundIoOutStreamAlsa *osa = &os->backend_data.alsa;
assert(!osa->thread);
// Give the API user a chance to fill the buffer before playback commences.
snd_pcm_sframes_t avail = snd_pcm_avail_update(osa->handle);
int err;
if (avail < 0) {
if ((err = xrun_recovery(os, avail)) < 0)
return SoundIoErrorStreaming;
}
outstream->write_callback(outstream, avail);
osa->thread_exit_flag.test_and_set();
if ((err = soundio_os_thread_create(outstream_thread_run, os, true, &osa->thread)))
return err;
@ -1288,7 +1281,9 @@ int outstream_begin_write_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os,
int err;
if ((err = snd_pcm_mmap_begin(osa->handle, &areas, &osa->offset, &frames)) < 0) {
if ((err = xrun_recovery(os, err)) < 0)
if (err == -EPIPE || err == -ESTRPIPE)
return SoundIoErrorUnderflow;
else
return SoundIoErrorStreaming;
}
@ -1326,7 +1321,9 @@ static int outstream_end_write_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate
if (commitres < 0 || commitres != frame_count) {
int err = (commitres >= 0) ? -EPIPE : commitres;
if ((err = xrun_recovery(os, err)) < 0)
if (err == -EPIPE || err == -ESTRPIPE)
return SoundIoErrorUnderflow;
else
return SoundIoErrorStreaming;
}

View file

@ -61,6 +61,7 @@ const char *soundio_strerror(int error) {
case SoundIoErrorIncompatibleBackend: return "incompatible backend";
case SoundIoErrorBackendDisconnected: return "backend disconnected";
case SoundIoErrorInterrupted: return "interrupted; try again";
case SoundIoErrorUnderflow: return "buffer underflow";
}
soundio_panic("invalid error enum value: %d", error);
}

View file

@ -30,6 +30,7 @@ enum SoundIoError {
SoundIoErrorIncompatibleBackend,
SoundIoErrorBackendDisconnected,
SoundIoErrorInterrupted,
SoundIoErrorUnderflow,
};
enum SoundIoChannelId {
@ -653,6 +654,7 @@ int soundio_outstream_begin_write(struct SoundIoOutStream *outstream,
// Commits the write that you began with `soundio_outstream_begin_write`.
// You must call this function only from the `write_callback` thread context.
// This function might return `SoundIoErrorUnderflow` but don't count on it.
int soundio_outstream_end_write(struct SoundIoOutStream *outstream, int frame_count);
// Clears the output stream buffer.