aboutsummaryrefslogtreecommitdiffstats
path: root/src/output/alsa_plugin.c
diff options
context:
space:
mode:
authorMax Kellermann <max@duempel.org>2009-11-09 22:22:31 +0100
committerMax Kellermann <max@duempel.org>2009-11-09 22:22:31 +0100
commit54033c74e4393f2d6650539dc41dde7cecffc7a8 (patch)
tree58ea9c0049afbea8b3bdc525242a4761808d83a1 /src/output/alsa_plugin.c
parent8420f1420fba1254b26d812ea90fef59a5cbd867 (diff)
downloadmpd-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 'src/output/alsa_plugin.c')
-rw-r--r--src/output/alsa_plugin.c50
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(&params);
+ 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) {