diff options
-rw-r--r-- | NEWS | 1 | ||||
-rw-r--r-- | src/output/alsa_plugin.c | 50 |
2 files changed, 48 insertions, 3 deletions
@@ -29,6 +29,7 @@ ver 0.16 (20??/??/??) * output: - recorder: new output plugin for recording radio streams - alsa: don't recover on CANCEL + - alsa: fill period buffer with silence before draining - openal: new output plugin - pulse: announce "media.role=music" - pulse: renamed context to "Music Player Daemon" diff --git a/src/output/alsa_plugin.c b/src/output/alsa_plugin.c index 48a40fb9b..64a8127ba 100644 --- a/src/output/alsa_plugin.c +++ b/src/output/alsa_plugin.c @@ -69,6 +69,16 @@ struct alsa_data { /** the size of one audio frame */ size_t frame_size; + + /** + * The size of one period, in number of frames. + */ + snd_pcm_uframes_t period_frames; + + /** + * The number of frames written in the current period. + */ + snd_pcm_uframes_t period_position; }; /** @@ -413,6 +423,9 @@ configure_hw: g_debug("buffer_size=%u period_size=%u", (unsigned)alsa_buffer_size, (unsigned)alsa_period_size); + ad->period_frames = alsa_period_size; + ad->period_position = 0; + return true; error: @@ -479,6 +492,7 @@ alsa_recover(struct alsa_data *ad, int err) /* fall-through to snd_pcm_prepare: */ case SND_PCM_STATE_SETUP: case SND_PCM_STATE_XRUN: + ad->period_position = 0; err = snd_pcm_prepare(ad->pcm); break; case SND_PCM_STATE_DISCONNECTED: @@ -500,8 +514,33 @@ alsa_drain(void *data) { struct alsa_data *ad = data; - if (snd_pcm_state(ad->pcm) == SND_PCM_STATE_RUNNING) - snd_pcm_drain(ad->pcm); + if (snd_pcm_state(ad->pcm) != SND_PCM_STATE_RUNNING) + return; + + if (ad->period_position > 0) { + /* generate some silence to finish the partial + period */ + snd_pcm_uframes_t nframes = + ad->period_frames - ad->period_position; + size_t nbytes = nframes * ad->frame_size; + void *buffer = g_malloc(nbytes); + snd_pcm_hw_params_t *params; + snd_pcm_format_t format; + unsigned channels; + + snd_pcm_hw_params_alloca(¶ms); + snd_pcm_hw_params_current(ad->pcm, params); + snd_pcm_hw_params_get_format(params, &format); + snd_pcm_hw_params_get_channels(params, &channels); + + snd_pcm_format_set_silence(format, buffer, nframes * channels); + ad->writei(ad->pcm, buffer, nframes); + g_free(buffer); + } + + snd_pcm_drain(ad->pcm); + + ad->period_position = 0; } static void @@ -509,6 +548,8 @@ alsa_cancel(void *data) { struct alsa_data *ad = data; + ad->period_position = 0; + snd_pcm_drop(ad->pcm); } @@ -529,8 +570,11 @@ alsa_play(void *data, const void *chunk, size_t size, GError **error) while (true) { snd_pcm_sframes_t ret = ad->writei(ad->pcm, chunk, size); - if (ret > 0) + if (ret > 0) { + ad->period_position = (ad->period_position + ret) + % ad->period_frames; return ret * ad->frame_size; + } if (ret < 0 && ret != -EAGAIN && ret != -EINTR && alsa_recover(ad, ret) < 0) { |