Patch for ALSA

This patch changes the file sound/core/oss/pcm_oss.c in the ALSA kernel modules.  This patch needs to be applied against the kernel sources, not the sources that can be downloaded from www.alsa-project.org. 

This patch causes the older OSS (Open Sound System) layer, that was added to ALSA for backwards compatibility, to restart a recording audio stream after it was stopped due to an overrun of the recording buffer.  An overrun (also called xrun) occurs when the application doesn't read the opened audio device (i.e. "/dev/dsp") for a short time, causing the input buffer of the driver to run full.  After a stream is stopped, subsequential data from the soundcard is ignored.  When the application starts reading again however, the stream should be restarted; but this is not the case at the moment (the patch is offered to the ALSA development team at 13 July 2003).

You can test if your drivers are fixed by running the following test code:

testcode.c (download):

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <sys/soundcard.h>
#include <time.h>
#include <stdlib.h>

int main(void)
{
  int fd;
  int res = 0x7fff0009;
  audio_buf_info info;
  int prev_size = 0;
  do
  {
    ++res;
    close(fd);
    fd = open("/dev/dsp", O_RDONLY);
    if (fd == -1) { perror("open"); exit(127); }
    if (ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &res) == -1) {
      perror("ioctl"); exit(127); }
    if (ioctl(fd, SNDCTL_DSP_GETISPACE, &info) == -1) {
      perror("read"); exit(127); }
    printf("    Allocated %d buffers of %d bytes.\n", info.fragstotal, info.fragsize);
    if (prev_size == info.fragsize * info.fragstotal)
    {
      printf("    It seems impossible to set a recording buffer with a\n"
             "    total size of at least 8192 bytes.  This is not going\n"
             "    to work with ViaVoice.  Sorry.\n");
      exit(126);
    }
    prev_size = info.fragsize * info.fragstotal;
  }
  while (prev_size < 8192);
  printf("    Successfully allocated a buffer that is large enough.\n");
  res = AFMT_S16_LE;
  if (ioctl(fd, SNDCTL_DSP_SETFMT, &res) == -1) {
    perror("ioctl"); exit(127); }
  res = 0;
  if (ioctl(fd, SNDCTL_DSP_STEREO, &res) == -1) {
    perror("ioctl"); exit(127); }
  res = 22050;
  if (ioctl(fd, SOUND_PCM_READ_RATE, &res) == -1) {
    perror("ioctl"); exit(127); }
  char buf[1024];
  if (read(fd, buf, sizeof(buf)) < 0) { perror("read"); exit(127); }
  static struct timespec naptime = { 0, 100000000 };
  int count = 0;
  do {
    if (ioctl(fd, SNDCTL_DSP_GETISPACE, &info) == -1) {
      perror("read"); exit(127); }
    printf("    Available bytes: %d\n", info.bytes);
    nanosleep(&naptime, 0);
    if (++count == 200) { printf("    Success: No overrun occurs.\n"); exit(0); }
  } while(info.bytes < info.fragsize * info.fragstotal);
  printf("    Successfully caused an xrun.\n");
  printf("    non-blocking fragments: %d\n", info.fragments);
  printf("    non-blocking bytes: %d\n", info.bytes);
  ssize_t bufsize = info.bytes;
  if (ioctl(fd, SNDCTL_DSP_GETISPACE, &info) == -1) {
    perror("read"); exit(127); }
  printf("    Available bytes in buffer: %d\n", info.bytes);
  ssize_t trlen = 0;
  int nf = 0;
  for (;;)
  {
    if (info.fragments > 0) {
      ssize_t rlen;
      if ((rlen = read(fd, buf, sizeof(buf))) < 0)
      { perror("read"); exit(127); }
      printf("    Additionally read %d bytes.\n", rlen);
      trlen += rlen;
      if (trlen > bufsize) {
        printf("    Read %d bytes: stream successfully restarted.\n", trlen);
        break;
      }
      nf = 0;
    }
    else if (++nf > 10) {
      printf("    Stream is not restarted after xrun.\n");
      exit(1);
    }
    if (ioctl(fd, SNDCTL_DSP_GETISPACE, &info) == -1) {
      perror("read"); exit(127); }
  }
  close(fd);
  return 0;
}

Running the test code

To run this test download testcode.c and execute it.  For example:

$ gcc testcode.c
$ ./a.out
    Allocated 2 buffers of 1024 bytes.
    Allocated 2 buffers of 2048 bytes.
    Allocated 2 buffers of 4096 bytes.
    Successfully allocated a buffer that is large enough.
    Available bytes: 3072
    Available bytes: 4704
    Available bytes: 6336
    Available bytes: 8000
    Available bytes: 9664
    Successfully caused an xrun.
    non-blocking fragments: 2
    non-blocking bytes: 9664
    Available bytes in buffer: 7232
    Additionally read 1024 bytes.
    Additionally read 1024 bytes.
    Additionally read 1024 bytes.
    Additionally read 1024 bytes.
    Stream is not restarted after xrun.

Applying the patch

Here is an example of how to apply the patch to your kernel.  Please note that this only works if you use kernel modules for your sound.  You also need at recent 2.5 development kernel of course (otherwise just use the OSS drivers, not ALSA).

This assumes you are already running the kernel 2.5.74 which you configured and compiled yourself:

$ su
$ rmmod `lsmod | grep '^snd' | cut -d \  -f 1`
$ cd /usr/src/linux-2.5.74
$ patch -p0 < /path/to/patch/ossfix.patch
$ make modules
$ RELEASE=`grep UTS_RELEASE include/linux/version.h | sed -e 's/[^"]*"\([^"]*\)"/\1/'`
$ for f in `find sound -name '*.ko'`; do cp $f /lib/modules/$RELEASE/kernel/$f; done
$ modprobe snd

Notes:

If you don't have any customized modules, then you can also just run make modules_install.  Just keep in mind that it will remove every modules that is not part of the kernel.

The trick with the RELEASE only works if you actually compiled the kernel before (ie, did a make bzImage.

Make sure you don't run any sound applications that will reload the sound modules before you can reload them.  If the last modprobe snd says something like "FATAL: Module snd already in kernel." then it's time to kill sound applications and re-run the rmmod line.

Now running the test program again will yield:

Download