git stults online alsa-signals / master snd_dev.c
master

Tree @master (Download .tar.gz)

snd_dev.c @masterraw · history · blame

#include "snd_dev.h"

static inline void snd_error (int err)
{
  if (err < 0)
    {
      fprintf (stderr, "error: %s\n", snd_strerror(err));
      exit (1);
    }
  return;
}

snd_dev_t *snd_dev_create (const char *device, unsigned int stream,
			   unsigned int access, unsigned int format,
			   unsigned int channels, unsigned int rate, 
			   unsigned int frames)
{
  int               dir;
  int               val;
  ssize_t           bytesper;
  snd_dev_t        *sd;
  snd_pcm_stream_t _stream;
  snd_pcm_access_t _access;
  snd_pcm_format_t _format;
  snd_pcm_uframes_t _frames;

  if ((sd = calloc (1, sizeof(snd_dev_t))) == NULL)
    return NULL;

  /* open device (need stream type for this) */
  switch (stream)
    {
    case SND_DEV_READ:
      _stream = SND_PCM_STREAM_CAPTURE;
      break;
    case SND_DEV_WRITE:
      _stream = SND_PCM_STREAM_PLAYBACK;
      break;
    default:
      free (sd);
      return NULL;
    }
  snd_error (snd_pcm_open (&sd->handle, device, _stream, 0));

  /* create param structure */
  snd_error (snd_pcm_hw_params_malloc (&sd->params));

  /* set params to default values */
  snd_error (snd_pcm_hw_params_any (sd->handle, sd->params));

  /* now set a few of the params */

  /* access mode */
  switch (access)
    {
    case SND_DEV_INTER:
      _access = SND_PCM_ACCESS_RW_INTERLEAVED;
      break;
    case SND_DEV_NINTER:
      _access = SND_PCM_ACCESS_RW_NONINTERLEAVED;
      break;
    default:
      snd_pcm_hw_params_free (sd->params);
      free (sd);
      return NULL;
    }
  snd_error (snd_pcm_hw_params_set_access(sd->handle, sd->params, _access));

  /* set format */
  switch (format)
    {
    case SND_DEV_UINT8:
      _format = SND_PCM_FORMAT_U8;
      break;
    case SND_DEV_UINT16:
      _format = SND_PCM_FORMAT_U16_LE;
      break;
    case SND_DEV_UINT32:
      _format = SND_PCM_FORMAT_U32_LE;
      break;
    case SND_DEV_INT8:
      _format = SND_PCM_FORMAT_S8;
      break;
    case SND_DEV_INT16:
      _format = SND_PCM_FORMAT_S16_LE;
      break;
    case SND_DEV_INT32:
      _format = SND_PCM_FORMAT_S32_LE;
      break;
    case SND_DEV_FLOAT32:
      _format = SND_PCM_FORMAT_FLOAT_LE;
      break;
    case SND_DEV_FLOAT64:
      _format = SND_PCM_FORMAT_FLOAT64_LE;
      break;
    case SND_DEV_MU_LAW:
      _format = SND_PCM_FORMAT_MU_LAW;
      break;
    case SND_DEV_A_LAW:
      _format = SND_PCM_FORMAT_A_LAW;
      break;
    default:
      snd_pcm_hw_params_free (sd->params);
      free (sd);
      return NULL;
    }
  snd_error (snd_pcm_hw_params_set_format(sd->handle, sd->params, _format));

  /* channels */
  snd_error (snd_pcm_hw_params_set_channels(sd->handle, sd->params, channels));

  /* will try to set the rate as near to this as possible; if not
     equal dir is set accordingly; we don't check dir currently */
  snd_error (snd_pcm_hw_params_set_rate_near (sd->handle, sd->params, &rate, &dir));

  /* set period size to n frames */
  _frames = (snd_pcm_uframes_t)frames;
  snd_error (snd_pcm_hw_params_set_period_size_near (sd->handle, sd->params, &_frames, &dir));

  /* Write the parameters to the driver */
  snd_error (snd_pcm_hw_params (sd->handle, sd->params));

  /* deal with internal buffer in terms of period */

  /* want a buffer large enough to hold one period */
  /* this will be frames/period * bytes/sample * channels */
  snd_error (snd_pcm_hw_params_get_period_size (sd->params, &_frames, &dir));
  snd_error ((bytesper = snd_pcm_format_size (_format, 1))); /* bytes/1samp */
  sd->bflen = _frames * bytesper * channels;
  if ((sd->bf = calloc (sd->bflen, sizeof(char))) == NULL)
    {
      snd_pcm_hw_params_free (sd->params);
      snd_pcm_close (sd->handle);
      free (sd);
      return NULL;
    }

  /* find out how long one period takes (usecs) */
  snd_error (snd_pcm_hw_params_get_period_time (sd->params, &val, &dir));

  /* TODO error check this */

  /* how many descriptors does handle have */
  sd->nfds = snd_pcm_poll_descriptors_count (sd->handle);

  /* allocate an array for that many */
  sd->cardfd = calloc (sd->nfds, sizeof(struct pollfd));

  /* fill the array with appropriate data */
  snd_pcm_poll_descriptors (sd->handle, sd->cardfd, sd->nfds);

  /* set convenience values */
  sd->kill   = 0;
  sd->byps   = (unsigned int)bytesper;
  sd->chans  = channels;
  sd->rate   = rate;
  sd->frames = (unsigned int)_frames;
  sd->ptime  = (unsigned int)val;
  sd->read   = (!strncmp (snd_pcm_stream_name (_stream), "CAPTURE", 1024)) ? 1 : 0;
  sd->write  = (!strncmp (snd_pcm_stream_name (_stream), "PLAYBACK", 1024)) ? 1 : 0;
  return sd;
}

void snd_dev_destroy (snd_dev_t *sd)
{
  if (sd == NULL)
    return;
  snd_pcm_hw_params_free (sd->params);
  snd_pcm_drain (sd->handle);
  snd_pcm_close (sd->handle);
  free (sd->bf);
  free (sd->cardfd);
  free (sd);
  sd = NULL;
  return;
}

void snd_dev_transfer (snd_dev_t *sd, void *data, snd_pcm_uframes_t frames)
{
  int rc;
  
  if ((sd == NULL) || (data == NULL) || (frames == 0))
    return;
  
  if (sd->read)
    rc = snd_pcm_readi (sd->handle, data, frames);
  else if (sd->write)
    rc = snd_pcm_writei (sd->handle, data, frames);
  else
    return;

  if (rc == -EPIPE)
    {
      /* EPIPE means xrun */
      fprintf(stderr, "xrun occurred\n");
      snd_pcm_prepare (sd->handle);
    } 
  else if (rc < 0)
    fprintf(stderr, "transfer error: %s\n", snd_strerror(rc));
  else if (rc != (int)frames)
    fprintf(stderr, "short transfer, tried %d frames but transferred only %d frames\n", (int)frames, rc);
  return;  

}

/* TODO a lot of overhead for a poll ? */
int snd_dev_poll (snd_dev_t *sd)
{
  /* hardware interrupts on card are generated every n frames, as
     specified in initialization parameters; this is called a period. to
     not block on a read/write call to/from card, it is advised to poll
     it--the poll will return once per period, and the card will have
     one period's length of frames available (presumably) at that
     time. */

  int            rc;
  unsigned short revents;

  if (sd == NULL)
    return -1;
  
  /* if == 0 timeout kicked; if < 0 err; > 0 number of fds thato
     logged events. wait for at most a second before timeout */
  rc = poll (sd->cardfd, sd->nfds, 1000);
  
  /* timeout occurred */
  if (rc == 0)
    return 0;
  /* an error occurred */
  else if (rc < 0)
    return -1;
  
  /* else we have a lock */
  
  /* special demangle because alsa messes up the pollfd internals */
  snd_pcm_poll_descriptors_revents (sd->handle, sd->cardfd, sd->nfds, &revents);
  
  /* mask to find what happened */

  /* there was an error */
  if (revents & POLLERR)
    return -1;
  /* ready for reading/writing */
  else if ((revents & POLLIN) || (revents & POLLOUT))
    return 1;
  /* TODO something ? */
  else
    return -1;
}

int snd_dev_get_rate      (snd_dev_t *sd)
{
  if (sd == NULL)
    return -1;

  return sd->rate;
}

int snd_dev_get_channels  (snd_dev_t *sd)
{
  if (sd == NULL)
    return -1;

  return sd->chans;
}

int snd_dev_get_period    (snd_dev_t *sd)
{
  if (sd == NULL)
    return -1;

  return sd->frames;
}

int snd_dev_get_frame     (snd_dev_t *sd)
{
  if (sd == NULL)
    return -1;

  return sd->byps * sd->chans;
}

int snd_dev_get_sample    (snd_dev_t *sd)
{
  if (sd == NULL)
    return -1;

  return sd->byps;
}

int snd_dev_get_format    (snd_dev_t *sd)
{
  if (sd == NULL)
    return -1;

  /* TODO */
}

int snd_dev_get_ptime     (snd_dev_t *sd)
{
  if (sd == NULL)
    return -1;

  return sd->ptime;
}