Logo Search packages:      
Sourcecode: rat version File versions  Download package

auddev_newpcm.c

/*
 * FILE: auddev_newpcm.c - Sound interface for newpcm FreeBSD driver.
 *
 * Modified to support newpcm (July 2000).
 *
 * Copyright (c) 1996-2001 University College London
 * All rights reserved.
 */
 
#ifndef HIDE_SOURCE_STRINGS
static const char cvsid[] = 
"$Id: auddev_newpcm.c,v 1.18 2002/02/24 21:13:47 ucacoxh Exp $";
#endif /* HIDE_SOURCE_STRINGS */

#include "config_unix.h"
#include "config_win32.h"
#include "audio_types.h"
#include "audio_fmt.h"
#include "auddev_newpcm.h"
#include "memory.h"
#include "debug.h"

#include <machine/soundcard.h>
#include <sys/types.h>

#include <dirent.h>
#include <errno.h>

/* #define DEBUG_JUST_NEWPCM if not using debug-enable and want err msgs */
#ifdef DEBUG_JUST_NEWPCM
#undef debug_msg
#define debug_msg(x...) fprintf(stderr, x)
#endif /* DEBUG_JUST_NEWPCM */

static char *port_names[] = SOUND_DEVICE_LABELS;
static int  iport, oport, loop;
static snd_chan_param pa;
static struct snd_size sz;
static int audio_fd = -1;
static int mixer_fd = -1;

#define RAT_TO_DEVICE(x) ((x) * 100 / MAX_AMP)
#define DEVICE_TO_RAT(x) ((x) * MAX_AMP / 100)

#define MIXER_CHECK0(fd)      if ((fd) < 0) {   \
            debug_msg("Failed mixer checked\n"); \
            return; \
      }

#define MIXER_CHECK1(fd, err) if ((fd) < 0) {   \
            debug_msg("Failed mixer checked\n"); \
            return (err); \
      }

#define NEWPCM_AUDIO_IOCTL(fd, cmd, val) \
      newpcm_error = 0; \
      if (ioctl((fd), (cmd), (val)) < 0) { \
            debug_msg("Failed %s - line %d\n",#cmd, __LINE__); \
            newpcm_error = __LINE__; \
      }

#define IN_RANGE(x,l,u) ((x) >= (l) && (x) <= (u))

#define NEWPCM_MAX_AUDIO_NAME_LEN 32
#define NEWPCM_MAX_AUDIO_DEVICES  10

static char names[NEWPCM_MAX_AUDIO_DEVICES][NEWPCM_MAX_AUDIO_NAME_LEN];
static int ndev = 0;
static int newpcm_error;
static audio_format *input_format, *output_format, *tmp_format;
static snd_capabilities soundcaps[NEWPCM_MAX_AUDIO_DEVICES];

static int  newpcm_mixer_open(const char* audiodev);
static void newpcm_mixer_save(int fd);
static void newpcm_mixer_restore(int fd);
static void newpcm_mixer_init(int fd);
static void newpcm_audio_loopback_config(int gain);

int 
newpcm_audio_open(audio_desc_t ad, audio_format *ifmt, audio_format *ofmt)
{
      audio_buf_info    abi;
      const char  *thedev;
      char        kick_off[64]; /* initial read buffer */
      int         frag;
 
      assert(ad >= 0 && ad < ndev); 
      thedev = names[ad];

      debug_msg("Opening \"%s\" device index %d\n", thedev, ad);

      audio_fd = open(thedev, O_RDWR);
      if (audio_fd >= 0) {
            /* Ignore any earlier errors */
            newpcm_error = 0;

            NEWPCM_AUDIO_IOCTL(audio_fd, AIOGCAP, &soundcaps[ad]);
            debug_msg("soundcaps[%d].rate_min = %ld\n", ad, soundcaps[ad].rate_min);
            debug_msg("soundcaps[%d].rate_max = %ld\n", ad, soundcaps[ad].rate_max);
            debug_msg("soundcaps[%d].formats  = 0x%08lx\n", ad, soundcaps[ad].formats);
            debug_msg("soundcaps[%d].bufsize  = %ld\n", ad, soundcaps[ad].bufsize);
            debug_msg("soundcaps[%d].mixers   = 0x%08lx\n", ad, soundcaps[ad].mixers);
            debug_msg("soundcaps[%d].inputs   = 0x%08lx\n", ad, soundcaps[ad].inputs);
            debug_msg("soundcaps[%d].left     = 0x%04x\n", ad, soundcaps[ad].left);
            debug_msg("soundcaps[%d].right    = 0x%04x\n", ad, soundcaps[ad].right);

            /* Setup input and output format settings */
            assert(ofmt->channels == ifmt->channels);
            memset(&pa, 0, sizeof(pa));
            if (ifmt->channels == 2) {
                  if (!soundcaps[ad].formats & AFMT_STEREO) {
                        fprintf(stderr,"Driver does not support stereo for this soundcard\n");
                        newpcm_audio_close(ad);
                        return FALSE;
                  }
                  pa.rec_format  = AFMT_STEREO;
                  pa.play_format = AFMT_STEREO;
            }

            switch(ifmt->encoding) {
            case DEV_PCMU: pa.rec_format |= AFMT_MU_LAW; break;
            case DEV_PCMA: pa.rec_format |= AFMT_A_LAW;  break;
            case DEV_S8:   pa.rec_format |= AFMT_S8;     break;
            case DEV_S16:  pa.rec_format |= AFMT_S16_LE; break;
            case DEV_U8:   pa.rec_format |= AFMT_U8;     break;
            }

            switch(ofmt->encoding) {
            case DEV_PCMU: pa.play_format |= AFMT_MU_LAW; break;
            case DEV_PCMA: pa.play_format |= AFMT_A_LAW;  break;
            case DEV_S8:   pa.play_format |= AFMT_S8;     break;
            case DEV_S16:  pa.play_format |= AFMT_S16_LE; break;
            case DEV_U8:   pa.play_format |= AFMT_U8;     break;
            }

            /* Check rate is supported (driver does not and
             * appears to mis-report actual rate) */
            if ((IN_RANGE((uint32_t)ofmt->sample_rate, 
                        soundcaps[ad].rate_min, 
                        soundcaps[ad].rate_max) == 0) || 
                (IN_RANGE((uint32_t)ifmt->sample_rate, 
                        soundcaps[ad].rate_min, 
                        soundcaps[ad].rate_max) == 0)) {
                  debug_msg("(%d or %d) out of range %ld -- %ld Hz\n",
                          ofmt->sample_rate,
                          ifmt->sample_rate,
                          soundcaps[ad].rate_min,
                          soundcaps[ad].rate_max);
                  if (ofmt->sample_rate == 8000 || ifmt->sample_rate == 8000) {
                        fprintf(stderr, "8000Hz sampling not supported by soundcard.  This is the default rate\nfor RTP voice applications.\n");
                  }
                  newpcm_audio_close(ad);
                  return FALSE;
            }

            /* Now set rate */
            pa.play_rate = ofmt->sample_rate;
            pa.rec_rate = ifmt->sample_rate;
            NEWPCM_AUDIO_IOCTL(audio_fd, AIOSFMT, &pa);

            /* Device buffer allocation - use
             * SNDCTL_DSP_SETFRAGMENT because it creates as more
             * frags for secondary buffer, making it possible to
             * achieve fill levels set by cushion.  */

            /* fragsz [15:0] = log2(fragsz) */
            frag = 2;
            while((1 << frag) < ifmt->bytes_per_block)
                  frag ++;
            frag --; /* Round down to nearest less than blocksize */
            
            /* fragsz [31:16] = number of frags (just lots, okay :-) */
            frag |= 0x00ff0000;

            NEWPCM_AUDIO_IOCTL(audio_fd, SNDCTL_DSP_SETFRAGMENT, &frag);
            NEWPCM_AUDIO_IOCTL(audio_fd, SNDCTL_DSP_GETOSPACE, &abi);
            debug_msg("fragments %d fragstotal %d fragsize %d bytes %d\n",
                    abi.fragments, abi.fragstotal, 
                    abi.fragsize, abi.bytes);

            /* sz is global and used throughout fn's */
            NEWPCM_AUDIO_IOCTL(audio_fd, AIOGSIZE, &sz);
            debug_msg("rec size %d, play size %d bytes\n",
                    sz.rec_size, sz.play_size);

            if (newpcm_error != 0) {
                  /* Failed somewhere in initialization - reset error and exit*/
                  newpcm_audio_close(ad);
                  newpcm_error = 0;
                  return FALSE;
            }

            /* Store format in case we have to re-open device because
             * of driver bug.  Careful with freeing format as input format
             * could be static input_format if device reset during write.
             */
            tmp_format = audio_format_dup(ifmt);
            if (input_format != NULL) {
                  audio_format_free(&input_format);
            }
            input_format = tmp_format;

            tmp_format = audio_format_dup(ofmt);
            if (output_format != NULL) {
                  audio_format_free(&output_format);
            }
            output_format = tmp_format;

            mixer_fd = newpcm_mixer_open(thedev);
            newpcm_mixer_save(mixer_fd);
            newpcm_mixer_init(mixer_fd);
            /* Turn off loopback from input to output... not fatal so
             * after error check.
             */
            newpcm_audio_loopback(ad, 0);

            read(audio_fd, kick_off, sizeof(kick_off)/sizeof(kick_off[0]));
            return TRUE;
      } else {
            fprintf(stderr, 
                  "Could not open device: \"%s\" (half-duplex?)\n", 
                  names[ad]);
            perror("newpcm_audio_open");
            newpcm_audio_close(ad);
            return FALSE;
      }
}

/* Close the audio device */
void
newpcm_audio_close(audio_desc_t ad)
{
      UNUSED(ad);
      
      if (audio_fd < 0) {
            debug_msg("Device already closed!\n");
            return;
      }
      if (input_format != NULL) {
            audio_format_free(&input_format);
      }
      if (output_format != NULL) {
            audio_format_free(&output_format);
      }

      newpcm_audio_drain(audio_fd);
      close(audio_fd);
      audio_fd = -1;

      newpcm_mixer_restore(mixer_fd);
      close(mixer_fd);
      mixer_fd = -1;
}

/* Flush input buffer */
void
newpcm_audio_drain(audio_desc_t ad)
{
      u_char buf[4];
      int pre, post;
       
      assert(audio_fd > 0);

      NEWPCM_AUDIO_IOCTL(audio_fd, FIONREAD, &pre);
      NEWPCM_AUDIO_IOCTL(audio_fd, SNDCTL_DSP_RESET, 0);
      NEWPCM_AUDIO_IOCTL(audio_fd, FIONREAD, &post);
      debug_msg("audio drain: %d -> %d\n", pre, post);
      read(audio_fd, buf, sizeof(buf));

      UNUSED(ad);
}

int
newpcm_audio_duplex(audio_desc_t ad)
{
      /* We only ever open device full duplex! */
      UNUSED(ad);
      return TRUE;
}

int
newpcm_audio_read(audio_desc_t ad, u_char *buf, int read_bytes)
{
      int done, this_read;
      int len;

      UNUSED(ad); assert(audio_fd > 0);

      done = 0;
      len = min(read_bytes, sz.rec_size);
      do {
            this_read = read(audio_fd, buf + done, len);
            if (this_read == -1) break;
            done += this_read;
      } while (this_read == len && (done + this_read < read_bytes));

      return done;
}

int
newpcm_audio_write(audio_desc_t ad, u_char *buf, int write_bytes)
{
      int done, wrote;

      UNUSED(ad); assert(audio_fd > 0);

      done = 0;
      while (done < write_bytes) {
            wrote = write(audio_fd, 
                        buf + done, 
                        min(sz.play_size, write_bytes - done));
            if (wrote == -1) break;
            done += wrote;
      }
      return done;
}

/* Set ops on audio device to be non-blocking */
void
newpcm_audio_non_block(audio_desc_t ad)
{
      int         frag = 1;

      UNUSED(ad); assert(audio_fd != -1);

      NEWPCM_AUDIO_IOCTL(audio_fd, SNDCTL_DSP_NONBLOCK, &frag);
}

/* Set ops on audio device to be blocking */
void
newpcm_audio_block(audio_desc_t ad)
{
      int         frag = 0;
       
      UNUSED(ad); assert(audio_fd > 0);
       
      NEWPCM_AUDIO_IOCTL(audio_fd, SNDCTL_DSP_NONBLOCK, &frag);
} 


static int recmask, playmask;

static void
newpcm_mixer_init(int fd) 
{
      int devmask;

      NEWPCM_AUDIO_IOCTL(fd, SOUND_MIXER_READ_RECMASK, &recmask);

      /* Remove Vol from Rec mask - it is a play control! */
      recmask = recmask & ~SOUND_MASK_VOLUME;
      if (recmask & SOUND_MASK_MIC) {
            iport = SOUND_MASK_MIC;
      } else {
            iport = 1;
            while ((iport & recmask) == 0) {
                  iport <<= 1;
            }
      }

      NEWPCM_AUDIO_IOCTL(fd, SOUND_MIXER_READ_DEVMASK, &devmask);
      playmask = devmask & ~recmask & ~SOUND_MASK_RECLEV ;
      debug_msg("devmask 0x%08x recmask 0x%08x playmask 0x%08x\n",
              devmask,
              recmask,
              playmask);
}

static int
newpcm_count_ports(int mask) 
{
      int n = 0, m = mask;

      while (m > 0) {
            n += (m & 0x01);
            m >>= 1;
      }

      return n;
}

static int
newpcm_get_nth_port_mask(int mask, int n)
{
      static int lgmask;

      lgmask = -1;
      do {
            lgmask ++;
            if ((1 << lgmask) & mask) {
                  n--;
            }
      } while (n >= 0);

      assert((1 << lgmask) & mask);
      return lgmask;
}

/* Gain and volume values are in the range 0 - MAX_AMP */
void
newpcm_audio_set_ogain(audio_desc_t ad, int vol)
{
      int volume, lgport, op;

      MIXER_CHECK0(mixer_fd);
            
      UNUSED(ad);
      vol = RAT_TO_DEVICE(vol);
      volume = vol << 8 | vol;
      lgport = -1;
      op = oport;
      while (op > 0) {
            op >>= 1;
            lgport ++;
      }

      NEWPCM_AUDIO_IOCTL(mixer_fd, MIXER_WRITE(lgport), &volume);
}

int
newpcm_audio_get_ogain(audio_desc_t ad)
{
      int volume, lgport, op;

      UNUSED(ad); 
      MIXER_CHECK1(mixer_fd, 0);

      lgport = -1;
      op     = oport;
      while (op > 0) {
            op >>= 1;
            lgport ++;
      }

      NEWPCM_AUDIO_IOCTL(mixer_fd, MIXER_READ(lgport), &volume);
      volume = DEVICE_TO_RAT(volume & 0xff);
      if (volume > 100 || volume < 0) {
            debug_msg("gain out of bounds (%08x %d--%d)" \
                    "mixer entry not implemented?", volume, 0, 100);  
            volume = 100;
      } 

      return volume;
}

void
newpcm_audio_oport_set(audio_desc_t ad, audio_port_t port)
{
      UNUSED(ad);
      oport = port;
      return;
}

audio_port_t
newpcm_audio_oport_get(audio_desc_t ad)
{
      UNUSED(ad);
      return oport;
}

int
newpcm_audio_oport_count(audio_desc_t ad)
{
      UNUSED(ad);
      return newpcm_count_ports(playmask);
}

const audio_port_details_t*
newpcm_audio_oport_details(audio_desc_t ad, int idx)
{
      static audio_port_details_t ap;
      int lgmask;

      UNUSED(ad);

      lgmask = newpcm_get_nth_port_mask(playmask, idx);
      ap.port = 1 << lgmask;
      sprintf(ap.name, "%s", port_names[lgmask]);

      return &ap;
}

/* newpcm_audio_{get,set}_igain are used to control input gain.  There
 * are 2 key mixer types for newpcm: those based on ac97 and those
 * not.  AC97 mixers control input gain using RECLEV, whereas non-AC97
 * set gain on current input port and do not (usually) have a RECLEV
 * mixer line. */

void
newpcm_audio_set_igain(audio_desc_t ad, int gain)
{
      int volume = RAT_TO_DEVICE(gain);
      volume |= (volume << 8);

      UNUSED(ad); 
      MIXER_CHECK0(mixer_fd);

      newpcm_audio_loopback_config(gain);
      /* Try AC97 */
      NEWPCM_AUDIO_IOCTL(mixer_fd, SOUND_MIXER_WRITE_RECLEV, &volume);

      if (newpcm_error != 0 && iport != 0) { 
            /* Fallback to non-ac97 */
            int idx = 1;
            while ((1 << idx) != iport)
                  idx++;
            NEWPCM_AUDIO_IOCTL(mixer_fd, MIXER_WRITE(idx), &volume);
      }
}

int
newpcm_audio_get_igain(audio_desc_t ad)
{
      int volume = 0;

      UNUSED(ad);
      MIXER_CHECK1(mixer_fd, 0);

      /* Try AC97 */
      NEWPCM_AUDIO_IOCTL(mixer_fd, SOUND_MIXER_READ_RECLEV, &volume);
      if (newpcm_error != 0 && iport != 0) {
            /* Fallback to non-ac97 */
            int idx = 1;
            while ((1 << idx) != iport)
                  idx++;
            NEWPCM_AUDIO_IOCTL(mixer_fd, MIXER_READ(idx), &volume);
      }
      volume = DEVICE_TO_RAT(volume & 0xff); 
      if (volume > 100 || volume < 0) {
            debug_msg("gain out of bounds (%d %d--%d)" \
                    "mixer entry not implemented?", volume, 0, 100);  
            volume = 100;
      } 
      return volume;
}

void
newpcm_audio_iport_set(audio_desc_t ad, audio_port_t port)
{
      /* Check port is in record mask */
      int gain;

      MIXER_CHECK0(mixer_fd);

      debug_msg("port 0x%08x recmask 0x%08x\n", port, recmask);

      if ((port & recmask) == 0) {
            debug_msg("Port 0x%08d not in recmask 0x%08d\n",
                    port, recmask);
            return;
      }

      if (ioctl(mixer_fd, SOUND_MIXER_WRITE_RECSRC, &port) < 0) {
            perror("Unable to write record mask\n");
            return;
      }
      iport = port;
      gain = newpcm_audio_get_igain(ad);
      newpcm_audio_loopback_config(gain);
      UNUSED(ad);
}

audio_port_t
newpcm_audio_iport_get(audio_desc_t ad)
{
      UNUSED(ad); assert(audio_fd > 0);
      return iport;
}

int
newpcm_audio_iport_count(audio_desc_t ad)
{
      UNUSED(ad);
      return newpcm_count_ports(recmask);
}

const audio_port_details_t *
newpcm_audio_iport_details(audio_desc_t ad, int idx)
{
      static audio_port_details_t ap;
      int lgmask;

      UNUSED(ad);

      lgmask = newpcm_get_nth_port_mask(recmask, idx);
      ap.port = 1 << lgmask;
      sprintf(ap.name, "%s", port_names[lgmask]);

      return &ap;
}

void
newpcm_audio_loopback(audio_desc_t ad, int gain)
{
      UNUSED(ad); assert(audio_fd > 0);
      loop = gain;
}

static void
newpcm_audio_loopback_config(int gain) 
{
      int lgport, vol;

      MIXER_CHECK0(mixer_fd);

      /* Find current input port id */
      lgport = newpcm_get_nth_port_mask(iport, 0);

      if (loop) {
            vol = RAT_TO_DEVICE(gain) << 8 | RAT_TO_DEVICE(gain);
      } else {
            vol = 0;
      }

      NEWPCM_AUDIO_IOCTL(mixer_fd, MIXER_WRITE(lgport), &vol);
}

void
newpcm_audio_wait_for(audio_desc_t ad, int delay_ms)
{
      struct timeval    timeout;
      fd_set            fds;

      timeout.tv_sec    = 0;
      timeout.tv_usec   = 1000 * delay_ms;
      
      FD_ZERO(&fds);
      FD_SET(audio_fd, &fds);
      select(audio_fd + 1, &fds, 0, 0, &timeout);
      UNUSED(ad);
}

int 
newpcm_audio_is_ready(audio_desc_t ad)
{
      int avail;

      UNUSED(ad);

      NEWPCM_AUDIO_IOCTL(audio_fd, FIONREAD, &avail);

      return (avail >= sz.rec_size);
}

int 
newpcm_audio_supports(audio_desc_t ad, audio_format *fmt)
{
      snd_capabilities s;

      UNUSED(ad);

      NEWPCM_AUDIO_IOCTL(audio_fd, AIOGCAP, &s);
      if (!newpcm_error) {
            if ((unsigned)fmt->sample_rate < s.rate_min || (unsigned)fmt->sample_rate > s.rate_max) return FALSE;
            if (fmt->channels == 1) 
                  return TRUE;      /* Always supports mono */
            assert(fmt->channels == 2);
            if (s.formats & AFMT_STEREO) 
                  return TRUE;
      }
      return FALSE;
}

static int
newpcm_is_driver() {
      FILE  *f;
      char  buf[128], *p;
      int   newpcm = FALSE;

      f = fopen("/dev/sndstat", "r");
      if (f == NULL) return FALSE;

      while(!feof(f)) {
            p = fgets(buf, 128, f);       
            if (p && strstr(buf, "newpcm")) {
                  newpcm = TRUE;
                  break;
            }
      }
      fclose(f);
      return newpcm;
}

int
newpcm_audio_query_devices()
{

      DIR         *d;
      struct dirent     *de;
      int         tfd;

      if (newpcm_is_driver() == 0)
            return 0;

      if (ndev)
            return ndev;

      d = opendir("/dev");
      if (d == NULL) {
            perror("opendir /dev");
            return 0;
      }

      while ((de = readdir(d)) != NULL && ndev < NEWPCM_MAX_AUDIO_DEVICES) {
            if (de->d_type != DT_CHR) continue;

            if (strncmp(de->d_name, "audio", 5) != 0) continue;

            sprintf(names[ndev], "/dev/%s", de->d_name); 
            tfd = open(names[ndev], O_RDWR);
            if (tfd < 0) {
            /* If device is busy it's an audio device, otherwise 
               it's (probably) an invalid device description */   
                  if (errno != EBUSY) 
                        continue;
            } else {
                  close(tfd);
            }
            ndev++;
      }
      closedir(d);
      return (ndev);
}

int
newpcm_get_device_count()
{
      return ndev;
}

char *
newpcm_get_device_name(audio_desc_t idx)
{
      if (idx >=0 && idx < ndev) {
            /* XXX this fn used to return (const char*) - grrr! */
            static char dummy[NEWPCM_MAX_AUDIO_NAME_LEN];
            strcpy(dummy, names[idx]);
            return dummy;
      }     
      return NULL;
}

/* Mixer open / close related */

static int
newpcm_mixer_device(const char* audiodev)
{
      const char* p = audiodev;
      int devno = 0;

      /* 
       * Audio device looks like "/dev/fooN" or "dev/foo/N.n"
       * and we want "N"
       */
      while (p && !isnumber(*p))
            p++;
      while (p && isnumber(*p)) {
            devno = devno * 10 + (*p - '0');
            p++;
      }
      assert(devno < 20);
      return devno;
}

static int
newpcm_mixer_open(const char* audiodev)
{
      char mixer_name[32] = "/dev/mixerXXX";
      int m;

#define END_OF_DEV_MIXER 10
      sprintf(mixer_name + END_OF_DEV_MIXER, 
            "%d", newpcm_mixer_device(audiodev));

      m = open(mixer_name, O_RDWR);
      if (m < 0) {
            fprintf(stderr, "Could not open %s (%s): "
                  "mixer operations will not work.\n",
                  mixer_name, strerror(errno));
            return 0;
      }
      return m;
}

/* Functions to save and restore recording source and mixer levels */

static int saved_rec_mask, saved_gain_values[SOUND_MIXER_NRDEVICES];

static void
newpcm_mixer_save(int fd)
{
      int devmask, i;
      NEWPCM_AUDIO_IOCTL(fd, SOUND_MIXER_READ_RECSRC, &saved_rec_mask); 
      NEWPCM_AUDIO_IOCTL(fd, SOUND_MIXER_READ_DEVMASK, &devmask);
      for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) {
            if ((1 << i) & devmask) {
                  NEWPCM_AUDIO_IOCTL(fd, MIXER_READ(i), &saved_gain_values[i]);
            } else {
                  saved_gain_values[i] = 0;
            }
      }
}

static void
newpcm_mixer_restore(int fd)
{
      int devmask, i;

      NEWPCM_AUDIO_IOCTL(fd, SOUND_MIXER_WRITE_RECSRC, &saved_rec_mask); 
      NEWPCM_AUDIO_IOCTL(fd, SOUND_MIXER_READ_DEVMASK, &devmask);
      for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) {
            if ((1 << i) & devmask) {
                  NEWPCM_AUDIO_IOCTL(fd, MIXER_WRITE(i), &saved_gain_values[i]);
            }
      }
}

Generated by  Doxygen 1.6.0   Back to index