diff options
author | Max Kellermann <max@duempel.org> | 2009-11-09 22:22:31 +0100 |
---|---|---|
committer | Max Kellermann <max@duempel.org> | 2009-11-09 22:22:31 +0100 |
commit | 54033c74e4393f2d6650539dc41dde7cecffc7a8 (patch) | |
tree | 58ea9c0049afbea8b3bdc525242a4761808d83a1 /src/output/alsa_plugin.c | |
parent | 8420f1420fba1254b26d812ea90fef59a5cbd867 (diff) | |
download | mpd-54033c74e4393f2d6650539dc41dde7cecffc7a8.tar.gz mpd-54033c74e4393f2d6650539dc41dde7cecffc7a8.tar.xz mpd-54033c74e4393f2d6650539dc41dde7cecffc7a8.zip |
output/alsa: fill period buffer with silence before draining
ALSA passes full period buffers to the hardware. If an application
doesn't finish writing a period, libasound will nonetheless send the
partial buffer (with undefined trailing data). This causes noise at
the end of playback. This patch attempts to track the current
position within the period buffer, and generates silence at the end,
before calling snd_pcm_drain().
Diffstat (limited to '')
-rw-r--r-- | src/output/alsa_plugin.c | 50 |
1 files changed, 47 insertions, 3 deletions
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) { |