Audio Information

/dev/audio

/dev/audio is vaguely standard, except that it requires fills of DMA buffer size (4608 bytes) and is locked at 44.1khz stereo. The buffer fill bit is a bug, but it's likely to stay locked at that rate - we can't actually change the rate (the player does interpolation & flash stuff for lower bitrates).

The mixer calls are pretty standard I believe, though there are lots of other bits for controlling the DSP - many of which we simply can't document as we're under NDA with the docs on the DSP.

Contributed by Hugo Fiennes

Example

Here is a program which reads pairs of signed 16-bit little-endian PCM samples from standard input and plays them at 44.1kHz stereo using /dev/audio. It takes care always to write 4608 bytes at a time.

Some of the ioctl()s probably aren't strictly necessary on the empeg but they illustrate the API.

pcmplay.c


# include <stdio.h>
# include <string.h>
# include <errno.h>
# include <fcntl.h>
# include <unistd.h>

# include <sys/ioctl.h>
# include <sys/soundcard.h>

# define AUDIO_DEVICE	"/dev/audio"
# define AUDIO_FILLSZ	4608

static
int output(int fd, void const *buf, unsigned int len)
{
  char const *ptr = buf;
  int wrote;

  while (len) {
    wrote = write(fd, ptr, len);
    if (wrote == -1) {
      if (errno == EINTR)
	continue;
      else
	return -1;
    }

    ptr += wrote;
    len -= wrote;
  }

  return 0;
}

static
int audio_buffer(int fd, void const *buf, unsigned int len)
{
  char const *ptr = buf;
  static char hold[AUDIO_FILLSZ];
  static unsigned int held;
  unsigned int left, grab;

  if (len == 0) {
    if (held) {
      memset(&hold[held], 0, &hold[AUDIO_FILLSZ] - &hold[held]);
      held = 0;

      return output(fd, hold, AUDIO_FILLSZ);
    }

    return 0;
  }

  if (held == 0 && len == AUDIO_FILLSZ)
    return output(fd, ptr, len);

  left = AUDIO_FILLSZ - held;

  while (len) {
    grab = len < left ? len : left;

    memcpy(&hold[held], ptr, grab);
    held += grab;
    left -= grab;

    ptr  += grab;
    len  -= grab;

    if (left == 0) {
      if (output(fd, hold, AUDIO_FILLSZ) == -1)
	return -1;

      held = 0;
      left = AUDIO_FILLSZ;
    }
  }

  return 0;
}

# define audio_flush(fd)  audio_buffer((fd), 0, 0)

static
int audio_init(int fd)
{
  int format, stereo, speed;

  format = AFMT_S16_LE;
  if (ioctl(fd, SNDCTL_DSP_SETFMT, &format) == -1) {
    perror("ioctl(SNDCTL_DSP_SETFMT)");
    return -1;
  }
  if (format != AFMT_S16_LE) {
    fprintf(stderr, "AFMT_S16_LE not available\n");
    return -1;
  }

  stereo = 1;
  if (ioctl(fd, SNDCTL_DSP_STEREO, &stereo) == -1) {
    perror("ioctl(SNDCTL_DSP_STEREO)");
    return -1;
  }
  if (!stereo) {
    fprintf(stderr, "stereo selection failed\n");
    return -1;
  }

  speed = 44100;
  if (ioctl(fd, SNDCTL_DSP_SPEED, &speed) == -1) {
    perror("ioctl(SNDCTL_DSP_SPEED)");
    return -1;
  }
  if (speed != 44100) {
    fprintf(stderr, "sample speed 44100 not available (closest %u)\n", speed);
    return -1;
  }

  return 0;
}

int main(int argc, char *argv[])
{
  static char buffer[AUDIO_FILLSZ];
  int fd;
  unsigned int len;

  fd = open(AUDIO_DEVICE, O_WRONLY);
  if (fd == -1) {
    perror(AUDIO_DEVICE);
    return 1;
  }

  if (audio_init(fd) == -1) {
    close(fd);
    return 2;
  }

  while ((len = fread(buffer, 4, AUDIO_FILLSZ / 4, stdin))) {
    if (audio_buffer(fd, buffer, 4 * len) == -1) {
      perror("write");
      return 3;
    }
  }

  if (ferror(stdin)) {
    perror("read");
    return 4;
  }

  if (audio_flush(fd) == -1) {
    perror("write");
    return 3;
  }

  if (close(fd) == -1) {
    perror("close");
    return 5;
  }

  return 0;
}

You will need a source of PCM data to try this program. If you have a 44.1kHz stereo WAV file, you can usually generate an appropriate PCM file by stripping away the 44-byte RIFF header:

dd if=$file.wav of=$file.pcm ibs=44 skip=1
Contributed by Rob Leslie