Bug#462753: libao2: need to clear the final period in alsa driver before closing it.

Morita Sho morita-pub-en-debian at inz.sakura.ne.jp
Sun Jan 27 10:06:52 UTC 2008


Package: libao2
Version: 0.8.8-3
Severity: normal
Tags: patch

Hi,

I found a problem in libao while investigating a bug in SoX.

When I play a sound clip with following command, I heard a noise at end of playback.

  $ sox /usr/share/sounds/KDE_Beep_Ahem.wav -t ao default

  (Note: This not always reproduce a problem.
         I made a program for reproduce a problem below.)

Other files does not cause a noise.

SoX supports many output drivers, e.g. libao, alsa, oss.
But the noise is occurred only with libao driver.
And the noise is disappeared when libao is configured to use other than "alsa".


First of all, I thought it is a bug on SoX.
I investigated the source code of SoX but can't find any problematic code.
To make sure it is not a bug on SoX, I made a small program that just plays KDE_Beep_Ahem.wav using libao.
And I confirmed a noise is appeared at end of playback on my program, too.
I decided that it is not a bug on SoX.

Secondly, I investigated the source code of libao,
especially its ALSA handling code (src/plugins/alsa09/ao_alsa09.c) since problem is only occurred when libao is configured to use "alsa".
I compared libao's ALSA handling to other softwares that uses ALSA.
And I noticed libao does not correctly closes ALSA device.

As I understand it, in ALSA, audio data is played as a unit of "period".
Once playing one period is finished, then next period will be played.
If period size is 1000 frames and audio data is 1001 frames,
1000 frames are written to first period, and remaining 1 frame are written to next period.
A period that contains only 1 frames, it means remaining 999 frames are undefined.
It will be a noise. Or sometimes audio data previously played.

Other softwares that use ALSA always fill whole period.
If actual audio data is smaller than period size, they will fills it with silence.
For example, alsa driver in SoX (sox/src/alsa.c) have following code:
  static int sox_alsastopwrite(sox_format_t * ft)
  {
    alsa_priv_t alsa = (alsa_priv_t)ft->priv;

    /* Append silence to fill the rest of the period, because alsa provides
     * whole periods to the hardware */
    snd_pcm_uframes_t frames_of_silence = alsa->period_size - alsa->frames_this_period;

    memset(alsa->buf, 0, frames_of_silence * ft->signal.size * ft->signal.channels);

And aplay in alsa-utils calls "snd_pcm_format_set_silence" if audio data is smaller than period size:
  static ssize_t pcm_write(u_char *data, size_t count)
  {
	ssize_t r;
	ssize_t result = 0;

	if (sleep_min == 0 &&
	    count < chunk_size) {  <== chunk_size holds period size
		snd_pcm_format_set_silence(hwparams.format, data + count * bits_per_frame / 8, (chunk_size - count) * hwparams.channels);
		count = chunk_size;
	}



A period size libao use is depend on the Sampling Rate of the audio to play.
The sampling rate of KDE_Beep_Ahem.wav is 22254 hz.
In this rate, libao uses 3709 frames for period size.
On the other hand, libao uses 245 frames on 44100 hz.
Bigger period size causes more noises than smaller.
I think it is a reason why any file except KDE_Beep_Ahem.wav doesn't causes a noise.

Note: I'm not sure the period size is constant for any environment because
      ALSA can change the period size to fit soundcard.
      Different soundcards may be different period size.
      Playing KDE_Beep_Ahem.wav causes a noise for me, but may not causes a noise for other environment.


I have added a code to src/plugins/alsa09/ao_alsa09.c 
to clear the final period with silence before closing audio device.
I have attached a patch to do this.

Unfortunately, this command:

  $ sox /usr/share/sounds/KDE_Beep_Ahem.wav -t ao default

can't be used to test whether the problem is resolved or not because
the uninitialized frames in the final period not always contain annoying noise.

To always reproduce the problem, I made a small program.
This program plays 500 hz sine wave about 0.4 seconds (period size * 2), and then 800 hz sine wave about 0.4 seconds (period size * 2 + 1).
The final period only contains 1 frame. Thus remaining 3708 frames are uninitialized.

Once executed this program, additional "500 hz sine wave about 0.1 seconds" will be played at end of playback.
You can change AO_DRIVER (defined inside reproduce_libao_bug.c) to "oss" to see sound is correctly played as expected.

Note: This program assumes that period size is 3709.

To compile:
  $ gcc -Wall -lao -ldl -lm -o reproduce_libao_bug reproduce_libao_bug.c 

Try this one if you can't reproduce the problem with sox.


I can confirm a noise at end of playback is gone with this patch applied.


Thanks,
Morita Sho

-- System Information:
Debian Release: lenny/sid
  APT prefers unstable
  APT policy: (500, 'unstable')
Architecture: i386 (i686)

Kernel: Linux 2.6.22-3-k7 (SMP w/1 CPU core)
Locale: LANG=ja_JP.UTF-8, LC_CTYPE=ja_JP.UTF-8 (charmap=UTF-8)
Shell: /bin/sh linked to /bin/bash

Versions of packages libao2 depends on:
ii  libc6                         2.7-6      GNU C Library: Shared libraries

libao2 recommends no packages.

-- no debconf information
-------------- next part --------------
A non-text attachment was scrubbed...
Name: libao_final_period_clear.patch
Type: text/x-c
Size: 2886 bytes
Desc: not available
Url : http://lists.alioth.debian.org/pipermail/pkg-xiph-maint/attachments/20080127/19af58ea/attachment.bin 


More information about the pkg-xiph-maint mailing list