diff --git a/README.md b/README.md index ef395a4..6c691b1 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/example/sine.c b/example/sine.c index a4bc2db..2ca5fcc 100644 --- a/example/sine.c +++ b/example/sine.c @@ -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) diff --git a/src/alsa.cpp b/src/alsa.cpp index 4783bae..02145c6 100644 --- a/src/alsa.cpp +++ b/src/alsa.cpp @@ -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; } diff --git a/src/soundio.cpp b/src/soundio.cpp index f2f3a28..68394c8 100644 --- a/src/soundio.cpp +++ b/src/soundio.cpp @@ -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); } diff --git a/src/soundio.h b/src/soundio.h index b930835..41150be 100644 --- a/src/soundio.h +++ b/src/soundio.h @@ -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.