mirror of
https://github.com/Ryujinx/libsoundio.git
synced 2025-01-03 14:05:38 +00:00
ALSA: recover from underflow gracefully
This commit is contained in:
parent
2900616e9b
commit
5503072fc8
|
@ -241,10 +241,10 @@ view `coverage/index.html` in a browser.
|
||||||
|
|
||||||
## Roadmap
|
## Roadmap
|
||||||
|
|
||||||
0. Integrate into libgroove and test with Groove Basin
|
|
||||||
0. implement CoreAudio (OSX) backend, get examples working
|
0. implement CoreAudio (OSX) backend, get examples working
|
||||||
0. implement WASAPI (Windows) backend, get examples working
|
0. implement WASAPI (Windows) backend, get examples working
|
||||||
0. implement ASIO (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. Avoid calling `soundio_panic` in PulseAudio.
|
||||||
0. PulseAudio: when prebuf gets set to 0 need to pass `PA_STREAM_START_CORKED`.
|
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
|
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.
|
`pa_stream_flush`, then uncork the stream.
|
||||||
0. Detect PulseAudio server going offline and emit `on_backend_disconnect`.
|
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. 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 pausing and resuming input and output streams.
|
||||||
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
|
||||||
|
|
|
@ -30,7 +30,6 @@ static int usage(char *exe) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static const float PI = 3.1415926535f;
|
static const float PI = 3.1415926535f;
|
||||||
static float seconds_offset = 0.0f;
|
static float seconds_offset = 0.0f;
|
||||||
static void write_callback(struct SoundIoOutStream *outstream, int requested_frame_count) {
|
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;
|
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));
|
panic("%s", soundio_strerror(err));
|
||||||
|
}
|
||||||
|
|
||||||
requested_frame_count -= frame_count;
|
requested_frame_count -= frame_count;
|
||||||
if (requested_frame_count <= 0)
|
if (requested_frame_count <= 0)
|
||||||
|
|
27
src/alsa.cpp
27
src/alsa.cpp
|
@ -867,7 +867,7 @@ static void outstream_destroy_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate *
|
||||||
deallocate(osa->sample_buffer, osa->sample_buffer_size);
|
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;
|
SoundIoOutStream *outstream = &os->pub;
|
||||||
SoundIoOutStreamAlsa *osa = &os->backend_data.alsa;
|
SoundIoOutStreamAlsa *osa = &os->backend_data.alsa;
|
||||||
if (err == -EPIPE) {
|
if (err == -EPIPE) {
|
||||||
|
@ -950,11 +950,13 @@ void outstream_thread_run(void *arg) {
|
||||||
snd_pcm_state_t state = snd_pcm_state(osa->handle);
|
snd_pcm_state_t state = snd_pcm_state(osa->handle);
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case SND_PCM_STATE_SETUP:
|
case SND_PCM_STATE_SETUP:
|
||||||
|
{
|
||||||
if ((err = snd_pcm_prepare(osa->handle)) < 0) {
|
if ((err = snd_pcm_prepare(osa->handle)) < 0) {
|
||||||
outstream->error_callback(outstream, SoundIoErrorStreaming);
|
outstream->error_callback(outstream, SoundIoErrorStreaming);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
case SND_PCM_STATE_PREPARED:
|
case SND_PCM_STATE_PREPARED:
|
||||||
{
|
{
|
||||||
if ((err = snd_pcm_start(osa->handle)) < 0) {
|
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);
|
snd_pcm_sframes_t avail = snd_pcm_avail_update(osa->handle);
|
||||||
if (avail < 0) {
|
if (avail < 0) {
|
||||||
if ((err = xrun_recovery(os, avail)) < 0) {
|
if ((err = os_xrun_recovery(os, avail)) < 0) {
|
||||||
outstream->error_callback(outstream, SoundIoErrorStreaming);
|
outstream->error_callback(outstream, SoundIoErrorStreaming);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -987,13 +989,13 @@ void outstream_thread_run(void *arg) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
case SND_PCM_STATE_XRUN:
|
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);
|
outstream->error_callback(outstream, SoundIoErrorStreaming);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
case SND_PCM_STATE_SUSPENDED:
|
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);
|
outstream->error_callback(outstream, SoundIoErrorStreaming);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1240,20 +1242,11 @@ static int outstream_open_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os)
|
||||||
}
|
}
|
||||||
|
|
||||||
static int outstream_start_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) {
|
static int outstream_start_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) {
|
||||||
SoundIoOutStream *outstream = &os->pub;
|
|
||||||
SoundIoOutStreamAlsa *osa = &os->backend_data.alsa;
|
SoundIoOutStreamAlsa *osa = &os->backend_data.alsa;
|
||||||
|
|
||||||
assert(!osa->thread);
|
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;
|
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();
|
osa->thread_exit_flag.test_and_set();
|
||||||
if ((err = soundio_os_thread_create(outstream_thread_run, os, true, &osa->thread)))
|
if ((err = soundio_os_thread_create(outstream_thread_run, os, true, &osa->thread)))
|
||||||
return err;
|
return err;
|
||||||
|
@ -1288,7 +1281,9 @@ int outstream_begin_write_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os,
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
if ((err = snd_pcm_mmap_begin(osa->handle, &areas, &osa->offset, &frames)) < 0) {
|
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;
|
return SoundIoErrorStreaming;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1326,7 +1321,9 @@ static int outstream_end_write_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate
|
||||||
|
|
||||||
if (commitres < 0 || commitres != frame_count) {
|
if (commitres < 0 || commitres != frame_count) {
|
||||||
int err = (commitres >= 0) ? -EPIPE : commitres;
|
int err = (commitres >= 0) ? -EPIPE : commitres;
|
||||||
if ((err = xrun_recovery(os, err)) < 0)
|
if (err == -EPIPE || err == -ESTRPIPE)
|
||||||
|
return SoundIoErrorUnderflow;
|
||||||
|
else
|
||||||
return SoundIoErrorStreaming;
|
return SoundIoErrorStreaming;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -61,6 +61,7 @@ const char *soundio_strerror(int error) {
|
||||||
case SoundIoErrorIncompatibleBackend: return "incompatible backend";
|
case SoundIoErrorIncompatibleBackend: return "incompatible backend";
|
||||||
case SoundIoErrorBackendDisconnected: return "backend disconnected";
|
case SoundIoErrorBackendDisconnected: return "backend disconnected";
|
||||||
case SoundIoErrorInterrupted: return "interrupted; try again";
|
case SoundIoErrorInterrupted: return "interrupted; try again";
|
||||||
|
case SoundIoErrorUnderflow: return "buffer underflow";
|
||||||
}
|
}
|
||||||
soundio_panic("invalid error enum value: %d", error);
|
soundio_panic("invalid error enum value: %d", error);
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,7 @@ enum SoundIoError {
|
||||||
SoundIoErrorIncompatibleBackend,
|
SoundIoErrorIncompatibleBackend,
|
||||||
SoundIoErrorBackendDisconnected,
|
SoundIoErrorBackendDisconnected,
|
||||||
SoundIoErrorInterrupted,
|
SoundIoErrorInterrupted,
|
||||||
|
SoundIoErrorUnderflow,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum SoundIoChannelId {
|
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`.
|
// Commits the write that you began with `soundio_outstream_begin_write`.
|
||||||
// You must call this function only from the `write_callback` thread context.
|
// 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);
|
int soundio_outstream_end_write(struct SoundIoOutStream *outstream, int frame_count);
|
||||||
|
|
||||||
// Clears the output stream buffer.
|
// Clears the output stream buffer.
|
||||||
|
|
Loading…
Reference in a new issue