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

audio-sun.cc

/*
 * Copyright (c) 1991-1993 Regents of the University of California.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *    This product includes software developed by the Computer Systems
 *    Engineering Group at Lawrence Berkeley Laboratory.
 * 4. Neither the name of the University nor of the Laboratory may be used
 *    to endorse or promote products derived from this software without
 *    specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */
static const char rcsid[] =
    "@(#) $Header: audio-sun.cc,v 1.24 96/05/03 01:23:30 van Exp $ (LBL)";

#undef resource_h

#ifdef __svr4__
#include <sys/audioio.h>
#include <sys/filio.h>
#else
#include <sun/audioio.h>
#endif
#include <unistd.h>
#include <stropts.h>
#include <errno.h>
#include <fcntl.h>
#include "audio-sun.h"

class SUNDBRIAudio : public SUNAudio {
    public:
      SUNDBRIAudio(const char* device);
      virtual u_char* Read();
    protected:

      int lastmean_[4];
};

class SUNXAudio : public SUNDBRIAudio {
    public:
      SUNXAudio(const char* device);
      virtual u_char* Read();
      virtual void Write(u_char*);
    protected:
      virtual void setup_device();

      u_char* ubuf;
};

static class SunAudioMatcher : public Matcher {
    public:
      SunAudioMatcher() : Matcher("audio") {}
      TclObject* match(const char* id) {
            if (strcasecmp(id, "sun") == 0)
                  return create();
            return (0);
      }
      Audio* create();
} sunaudio_matcher;

Audio* SunAudioMatcher::create()
{
      Tcl& tcl = Tcl::instance();
      const char* device = tcl.attr("audioFileName");
      int i = 0;
      if (strcmp(device, "/dev/audio") == 0 &&
          (i = open("/dev/audioctl", O_RDONLY, 0)) >= 0) {
            audio_info_t info;
            AUDIO_INITINFO(&info);
            int j = ioctl(i, AUDIO_GETINFO, &info);
            close(i);
            if (j != -1 &&
                info.play.avail_ports > 3 &&
                info.play.avail_ports < 32) {
                  /*
                   * if the driver is advanced enough to let us set
                   * the blocksize, use the 'sunx' driver which reads
                   * in linear to avoid the ulaw quantization noise
                   * from the dc offset on the mike & line inputs.
                   * If we can't set the blocksize, use the 'dbri'
                   * driver which just reads in ulaw (if we can't
                   * set the blocksize & switch to linear, sun's
                   * braindead driver will supply a weird, huge
                   * blocksize).
                   */
#ifndef __svr4__
                  int bs = info.record._xxx[2];
#else
                  int bs = info.record.buffer_size;
#endif
                  return ((bs != -1 && bs != 0)? 
                          new SUNXAudio(device) :
                          new SUNDBRIAudio(device));
            }
      }
      return (new SUNAudio(device));
}

SUNAudio::SUNAudio(const char* device)
{
      iports = 1;
      oports = 2;

      audio_info_t* i = new audio_info_t;
      state = i;
      AUDIO_INITINFO(i);

      rgain = 0;
      rbalance = 32;
      pgain = 0;
      pbalance = 32;
      mgain = 0;

      bufcur = buf = new u_char[blksize];
      bufend = buf + blksize;
      device_ = new char[strlen(device) + 1];
      strcpy(device_, device);
}

int SUNAudio::command(int argc, const char*const* argv)
{
      Tcl& tcl = Tcl::instance();
      if (argc == 3) {
            if (strcmp(argv[1], "input") == 0) {
                  if (strcmp(argv[2], "balance") == 0) {
                        sprintf(tcl.result(), "%s %d",
                              IPortToStr(iport), rbalance);
                        return (TCL_OK);
                  }
            } else if (strcmp(argv[1], "output") == 0) {
                  if (strcmp(argv[2], "balance") == 0) {
                        sprintf(tcl.result(), "%s %d",
                              OPortToStr(oport), pbalance);
                        return (TCL_OK);
                  }
            } else if (strcmp(argv[1], "monitor") == 0) {
                  if (strcmp(argv[2], "gain") == 0) {
                        sprintf(tcl.result(), "monitor %d", mgain);
                        return (TCL_OK);
                  }
            }
      } else if (argc == 4) {
            if (strcmp(argv[1], "input") == 0) {
                  if (strcmp(argv[2], "balance") == 0) {
                        SetRBalance(atoi(argv[3]));
                        return (TCL_OK);
                  }
            } else if (strcmp(argv[1], "output") == 0) {
                  if (strcmp(argv[2], "balance") == 0) {
                        SetPBalance(atoi(argv[3]));
                        return (TCL_OK);
                  }
            } else if (strcmp(argv[1], "monitor") == 0) {
                  if (strcmp(argv[2], "gain") == 0) {
                        SetMGain(atoi(argv[3]));
                        return (TCL_OK);
                  }
            }
      }
      return (Audio::command(argc, argv));
}

int SUNAudio::OPort(int p)
{
      switch(p) {
      default:
      case output_speaker:
            return (AUDIO_SPEAKER);
      case output_phones:
            return (AUDIO_HEADPHONE);
      case output_line:
            return (AUDIO_LINE_OUT);
      }
}

int SUNAudio::IPort(int p)
{
      switch(p) {
      default:
      case input_mike:
            return (AUDIO_MICROPHONE);
      case input_line:
            return (AUDIO_LINE_IN);
      }
}

void SUNAudio::Obtain()
{
      if (!HaveAudio()) {
            /*
             * try to save the current audio state (we have to
             * do this before opening /dev/audio because the open
             * will wipe out the sample rate & encoding).
             */
            int ctl = open("/dev/audioctl", O_RDONLY, 0);
            if (ctl >= 0) {
                  audio_info_t* i = (audio_info_t*)state;
                  AUDIO_INITINFO(i);
                  ioctl(ctl, AUDIO_GETINFO, i);
                  close(ctl);
            }
            fd = open(device_, O_RDWR|O_NDELAY);
            if (fd >= 0) {
                  setup_device();
                  Audio::Obtain();
            }
      }
}

void SUNAudio::Release()
{
      if (HaveAudio()) {
            /* restore the audio state to what it was on entry */
            setinfo((audio_info_t*)state);
            Audio::Release();
      }
}

void SUNAudio::Write(u_char *cp)
{
      register int len = blksize;
      int cc = write(fd, (char *)cp, len);
      if ((len -= cc) != 0) {
            do {
                  if (cc < 0) {
                        if (errno != EPERM)
                              perror("audio write");
                        return;
                  }
                  cp += cc;
                  cc = write(fd, (char *)cp, len);
                  len -= cc;
            } while (len > 0);
      }
}

int SUNAudio::FrameReady()
{
      register int len = bufend - bufcur;
      while (len > 0) {
            int cc = read(fd, (char *)bufcur, len);
            if (cc < 0) {
                  switch (errno) {
                  case EINVAL:
                        /* probably wrapped file pos. */
                        lseek(fd, 0, SEEK_SET);
                        cc = 0;
                        break;

                  case EPERM:
                        /* probably lost audio */
                        Release();
                        Obtain();
                        goto out;

#ifndef __svr4__
                  case EWOULDBLOCK:
#endif
                  case EAGAIN:
                        /* should warn about audio_bsize here */
                        goto out;

                  default:
                        perror("audio read");
                        Release();
                        Obtain();
                        return (0);
                  }
            }
            bufcur += cc;
            len -= cc;
      }
    out:
      return (bufcur >= bufend);
}

u_char* SUNAudio::Read()
{
      bufcur = buf;
      return (buf);
}

void SUNAudio::setup_device()
{
      audio_info_t info;
      if (fcntl(fd, F_SETFL, O_NONBLOCK|O_NDELAY) < 0)
            perror("audio O_NONBLOCK");
      int on = 1;
      ioctl(fd, FIONBIO, &on);
#ifndef __svr4__
      int bsddrvr = 0;
      AUDIO_INITINFO(&info);
      getinfo(&info);
      if (info._yyy[0] == 0)
            bsddrvr = 1;
#endif
      AUDIO_INITINFO(&info);
      info.play.sample_rate = 8000; /*XXX*/
      info.play.channels = 1; /*XXX*/
      info.play.precision = 8; /*XXX*/
      info.play.encoding = AUDIO_ENCODING_ULAW; /*XXX*/
      info.play.gain = pgain;
      info.play.port = OPort(oport);
      info.play.balance = pbalance;

      info.record.sample_rate = 8000; /*XXX*/
      info.record.channels = 1; /*XXX*/
      info.record.precision = 8; /*XXX*/
      info.record.encoding = AUDIO_ENCODING_ULAW; /*XXX*/
      info.record.gain = rgain;
      info.record.port = IPort(iport);
      info.record.balance = rbalance;
#ifndef __svr4__
      /* solaris 2.2 hack: set buffer size.
       * NB- this changes to _xxx[3] for os<4.1.3 */
      info.record._xxx[2] = blksize;
      if (bsddrvr) {
            /* bsd driver hack - set buffer size */
            info._yyy[0] = blksize;
      } else {
            /* 4.1.3 bug workaround: pause then resume */
            info.record.pause = 1;
            setinfo(&info);
            info.record.pause = 0;
      }
#else
      info.record.buffer_size = blksize;
#endif
      info.monitor_gain = mgain;
      setinfo(&info);
      /* flush input to get rid of any data fragments */
      ioctl(fd, I_FLUSH, FLUSHR);
      bufcur = buf;
}


int SUNAudio::GainClip(int level)
{
        if (level < AUDIO_MIN_GAIN)
                return AUDIO_MIN_GAIN;
        else if (level > AUDIO_MAX_GAIN)
                return AUDIO_MAX_GAIN;
        else
                return level;
}

int SUNAudio::getinfo(audio_info_t* info)
{
      int sts;
      if (fd < 0)
            sts = 0;
      else
            sts = ioctl(fd, AUDIO_GETINFO, info);
      return (sts);
}

int SUNAudio::setinfo(audio_info_t* info)
{
      int sts;
      if (fd < 0)
            sts = 0;
      else
            sts = ioctl(fd, AUDIO_SETINFO, info);
      return (sts);
}

void SUNAudio::SetRGain(int level)
{
      audio_info_t info;

      rgain = GainClip(level);
      AUDIO_INITINFO(&info);
      info.record.gain = rgain;
      setinfo(&info);
}

void SUNAudio::SetPGain(int level)
{
      audio_info_t info;

      pgain = GainClip(level);
      AUDIO_INITINFO(&info);
      info.play.gain = pgain;
      setinfo(&info);
}

void SUNAudio::SetRBalance(int level)
{
      audio_info_t info;

      rbalance = GainClip(level);
      AUDIO_INITINFO(&info);
      info.record.balance = rbalance;
      setinfo(&info);
}

void SUNAudio::SetMGain(int level)
{
      audio_info_t info;

      mgain = GainClip(level);
      AUDIO_INITINFO(&info);
      info.monitor_gain = mgain;
      setinfo(&info);
}

void SUNAudio::SetPBalance(int level)
{
      audio_info_t info;

      pbalance = GainClip(level);
      AUDIO_INITINFO(&info);
      info.play.balance = pbalance;
      setinfo(&info);
}

void SUNAudio::OutputPort(int p)
{
      audio_info_t info;

      oport = p;
      AUDIO_INITINFO(&info);
      info.play.port = OPort(p);
      setinfo(&info);
}

void SUNAudio::InputPort(int p)
{
      audio_info_t info;

      iport = p;
      AUDIO_INITINFO(&info);
      info.record.port = IPort(p);
      setinfo(&info);
}


extern const short mulawtolin[];
extern const unsigned char lintomulaw[];
extern const unsigned char lintomulawX[];

SUNDBRIAudio::SUNDBRIAudio(const char* device) : SUNAudio(device)
{
      /* XXX - hardwired for dbri speakerbox */
      iports = 2;
      oports = 3;
      lastmean_[0] = 0;
      lastmean_[1] = 0;
      lastmean_[2] = 0;
      lastmean_[3] = 0;
}

u_char* SUNDBRIAudio::Read()
{
      register u_char* cp = buf;

      /*
       * for some reason, SUN didn't bother to filter out the
       * mike 'phantom power' DC signal so we end up with a
       * large DC offset that screws up the lin-to-mu conversion
       * and the speakerphone power calculations.  So, all the
       * hair in the following loop is a low-pass filter with ~1Hz
       * passband to estimate the DC offset.
       */
      register u_int* ip = (u_int*)cp;
      register u_int* ep = (u_int*)(cp + blksize);
      register int smean = lastmean_[iport];
      register const short* u2l = mulawtolin;
      register const u_char* l2u = lintomulaw;
      if (iport != 0) {
            while (ip < ep) {
                  register int mean, dif;
                  register u_int res;
                  register u_int dat = *ip;
                  register int s0 = u2l[dat >> 24];
                  register int s1 = u2l[(dat >> 16) & 0xff];
                  register int s2 = u2l[(dat >> 8) & 0xff];
                  register int s3 = u2l[dat & 0xff];

                  mean = smean >> 13;
                  dif = s0 - mean;
                  smean += dif;
                  res = l2u[dif & 0xffff] << 24;

                  mean = smean >> 13;
                  dif = s1 - mean;
                  smean += dif;
                  res |= l2u[dif & 0xffff] << 16;

                  mean = smean >> 13;
                  dif = s2 - mean;
                  smean += dif;
                  res |= l2u[dif & 0xffff] << 8;

                  mean = smean >> 13;
                  dif = s3 - mean;
                  smean += dif;
                  res |= l2u[dif & 0xffff];

                  *ip++ = res;
            }
      } else {
            while (ip < ep) {
                  register int mean, dif;
                  register u_int res;
                  register u_int dat = *ip;
                  register int s0 = u2l[dat >> 24];
                  register int s1 = u2l[(dat >> 16) & 0xff];
                  register int s2 = u2l[(dat >> 8) & 0xff];
                  register int s3 = u2l[dat & 0xff];

                  mean = smean >> 13;
                  dif = s0 - mean;
                  smean += dif;
                  res = l2u[(dif >> 1) & 0xffff] << 24;

                  mean = smean >> 13;
                  dif = s1 - mean;
                  smean += dif;
                  res |= l2u[(dif >> 1) & 0xffff] << 16;

                  mean = smean >> 13;
                  dif = s2 - mean;
                  smean += dif;
                  res |= l2u[(dif >> 1) & 0xffff] << 8;

                  mean = smean >> 13;
                  dif = s3 - mean;
                  smean += dif;
                  res |= l2u[(dif >> 1) & 0xffff];

                  *ip++ = res;
            }
      }
      lastmean_[iport] = smean;
      bufcur = cp;
      return (cp);
}


SUNXAudio::SUNXAudio(const char* device) : SUNDBRIAudio(device)
{
      ubuf = bufcur;
      bufcur = buf = new u_char[blksize << 1];
      bufend = buf + (blksize << 1);
}

u_char* SUNXAudio::Read()
{
      /*
       * for some reason, SUN didn't bother to filter out the
       * mike 'phantom power' DC signal so we end up with a
       * large DC offset that screws up the lin-to-mu conversion
       * and the speakerphone power calculations.  So, all the
       * hair in the following loop is a low-pass filter with ~1Hz
       * passband to estimate the DC offset.  To avoid the
       * quantization noise from unbiasing a mulaw signal, we
       * have to read & write 8KHz linear.
       */
      register short* ip = (short*)buf;
      register short* ep = ip + blksize;
      register u_int* op = (u_int*)ubuf;
      register int smean = lastmean_[iport];
      register const u_char* l2u = lintomulawX;
      while (ip < ep) {
            register int mean, dif;
            register u_int res;
            register int s0 = ip[0];
            register int s1 = ip[1];
            register int s2 = ip[2];
            register int s3 = ip[3];

            mean = smean >> 13;
            dif = s0 - mean;
            smean += dif;
            res = l2u[dif & 0x1ffff] << 24;

            mean = smean >> 13;
            dif = s1 - mean;
            smean += dif;
            res |= l2u[dif & 0x1ffff] << 16;

            mean = smean >> 13;
            dif = s2 - mean;
            smean += dif;
            res |= l2u[dif & 0x1ffff] << 8;

            mean = smean >> 13;
            dif = s3 - mean;
            smean += dif;
            res |= l2u[dif & 0x1ffff];

            *op++ = res;
            ip += 4;
      }
      lastmean_[iport] = smean;
      bufcur = buf;
      return (ubuf);
}


void SUNXAudio::Write(u_char *cp)
{
      register u_int len = blksize;
      u_int samps[MAXAUDIOSIZE/2];
      register u_int* sp = samps;
      register u_int* ep = sp + len / 2;
      register const u_short* u2l = (u_short*)mulawtolin;
      register u_int* ip = (u_int*)cp;
      for ( ; sp < ep; sp += 4) {
            register u_int s = *ip++;
            sp[0] = (u2l[(s >> 24) & 0xff] << 16) | u2l[(s >> 16) & 0xff];
            sp[1] = (u2l[(s >> 8) & 0xff] << 16) | u2l[s & 0xff];
            s = *ip++;
            sp[2] = (u2l[(s >> 24) & 0xff] << 16) | u2l[(s >> 16) & 0xff];
            sp[3] = (u2l[(s >> 8) & 0xff] << 16) | u2l[s & 0xff];
      }
      write(fd, (char *)samps, len << 1);
}

void SUNXAudio::setup_device()
{
      SUNAudio::setup_device();

      audio_info_t info;
      AUDIO_INITINFO(&info);
      info.record.precision = 16;
      info.record.encoding  = AUDIO_ENCODING_LINEAR;
#ifndef __svr4__
      info.record._xxx[2]   = blksize << 1;
#else
      info.record.buffer_size = blksize << 1;
#endif
      info.play.precision   = 16;
      info.play.encoding    = AUDIO_ENCODING_LINEAR;
      setinfo(&info);
      /* flush input again to get rid of any data with wrong encoding */
      ioctl(fd, I_FLUSH, FLUSHR);
      bufcur = buf;
}

Generated by  Doxygen 1.6.0   Back to index