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().
This commit is contained in:
Max Kellermann 2009-11-09 22:22:31 +01:00
parent 8420f1420f
commit 54033c74e4
2 changed files with 48 additions and 3 deletions

1
NEWS
View File

@ -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"

View File

@ -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) {