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

audio-hp.cc

/*
 * Copyright (c) 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.
 */

#ifndef lint
static const char rcsid[] =
    "@(#) $Header: audio-hp.cc,v 1.9 96/03/18 02:54:57 van Exp $ (LBL)";
#endif

#include "config.h"
#include <sys/socket.h>
#include <sys/audio.h>
#include <fcntl.h>
#include "audio.h"

#define ABUFSIZE 4096
#define ABUFLEN (ABUFSIZE/sizeof(short))

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

class HPAudio : public Audio {
    public:
      HPAudio();
      virtual int FrameReady();
      virtual u_char* Read();
      virtual     void Write(u_char *);
      virtual void SetRGain(int);
      virtual void SetPGain(int);
      virtual void InputPort(int);
      virtual void OutputPort(int);
      virtual void Obtain();
    protected:
      friend class HPAudioMatcher;
      int lastmean[4];

      short* readptr;
      short* readbufend;
      short* readbuf;

      u_char* ubufptr;
      u_char* ubufend;
      u_char* ubuf;

      short* writeptr;
      short* writebufend;
      short* writebuf;

      float rgain_scale;
      float rgain_min;
      float pgain_scale;
      float pgain_min;
};

class HP2160Audio : public HPAudio {
    public:
      HP2160Audio();
      virtual int FrameReady();
      virtual     void Write(u_char *);
      virtual void Obtain();
};

static class HPAudioMatcher : public Matcher {
    public:
      HPAudioMatcher() : Matcher("audio") {}
      TclObject* match(const char* fmt);
} hpaudio_matcher;

TclObject* HPAudioMatcher::match(const char* fmt)
{
      if (strcasecmp(fmt, "hp") != 0)
            return (0);

      int cfd = open("/dev/audioCtl", O_RDONLY, 0);
      if (cfd < 0) {
            perror("/dev/audioCtl");
            exit(1);
      }

      /* get gain & channel config */
      audio_describe ad;
      if (ioctl(cfd, AUDIO_DESCRIBE, &ad) < 0) {
            perror("AUDIO_DESCRIBE");
            exit(1);
      }
      close(cfd);
      HPAudio* aud;
      if (ad.audio_id == AUDIO_ID_PSB2160)
            aud = new HP2160Audio;
      else
            aud = new HPAudio;

      aud->rgain_scale = float(ad.max_receive_gain-ad.min_receive_gain)/255.;
      aud->rgain_min = ad.min_receive_gain;
      aud->pgain_scale = float(ad.max_transmit_gain-ad.min_transmit_gain)/255.;
      aud->pgain_min = ad.min_transmit_gain;
      return (aud);
}

HPAudio::HPAudio()
{
      iports = 2;
      oports = 3;

      lastmean[0] = 0;
      lastmean[1] = 0;
      lastmean[2] = 0;
      lastmean[3] = 0;

      readbuf = new short[ABUFLEN];
      readptr = readbufend = readbuf + ABUFLEN;

      writeptr = writebuf = new short[ABUFLEN];
      writebufend = writebuf + ABUFLEN;

      ubufptr = ubuf = new u_char[blksize];
      ubufend = ubuf + blksize;
}

void HPAudio::Obtain()
{
      if (HaveAudio())
            abort();

      fd = open("/dev/audio", O_RDWR, 0);
      if (fd >= 0) {
            /*
             * To get around the
             * problem that the hardware can only do 4K read/writes
             * (half a second for 8KHz ulaw), we run in 16bit linear
             * stereo mode to get the latency down to `only' 125ms.
             * After setting up the format (which has to be done
             * before the driver buffers are allocated),
             * make sure we do non-blocking reads then allocate
             * about a second of buffer space.
             */
            if (ioctl(fd, AUDIO_SET_DATA_FORMAT, AUDIO_FORMAT_LINEAR16BIT) < 0) {
                  perror("AUDIO_SET_DATA_FORMAT");
                  exit(1);
            }
            if (ioctl(fd, AUDIO_SET_CHANNELS, 2) < 0) {
                  perror("AUDIO_SET_CHANNELS");
                  exit(1);
            }
            /*
             * the default sample rate is supposed to be 8khz but
             * there's a kernel bug that keeps it from being set so
             * we inherit whatever the last used.  so set it to what
             * we need.
             */
            if (ioctl(fd, AUDIO_SET_SAMPLE_RATE, 8000) < 0) {
                  perror("AUDIO_SET_SAMPLE_RATE");
                  exit(1);
            }
            readptr = readbufend;
            writeptr = writebuf;
            SetRGain(rgain);
            SetPGain(pgain);
            OutputPort(oport);
            InputPort(iport);
            if (fcntl(fd, F_SETFL, O_NONBLOCK) < 0) {
                  perror("audio O_NONBLOCK");
                  exit(1);
            }
            if (ioctl(fd, AUDIO_SET_RXBUFSIZE, 32768) < 0) {
                  perror("AUDIO_SET_RXBUFSIZE");
                  exit(1);
            }
            if (ioctl(fd, AUDIO_SET_TXBUFSIZE, 32768) < 0) {
                  perror("AUDIO_SET_TXBUFSIZE");
                  exit(1);
            }
            Audio::Obtain();
      }
}

void HPAudio::Write(u_char *cp)
{
      if (HaveAudio()) {
            register u_char *cpend = cp + blksize;
            register short *wbuf = writeptr;
            register short *wend = writebufend;
            for ( ; cp < cpend; cp += 4) {
                  wbuf[1] = wbuf[0] = mulawtolin[cp[0]];
                  wbuf[3] = wbuf[2] = mulawtolin[cp[1]];
                  wbuf[5] = wbuf[4] = mulawtolin[cp[2]];
                  wbuf[7] = wbuf[6] = mulawtolin[cp[3]];
                  wbuf += 8;
                  if (wbuf >= wend) {
                        wbuf = writebuf;
                        if (write(fd, (char*)wbuf, ABUFSIZE) != ABUFSIZE)
                              perror("aud write");
                  }
            }
            writeptr = wbuf;
      }
}

int HPAudio::FrameReady()
{
      register u_char* cp = ubufptr;
      register u_char* cpend = ubufend;
      register short* rbuf = readptr;
      register short* rend = readbufend;
      register int smean = lastmean[iport];

      if (!HaveAudio())
            return (0);

      for ( ; cp < cpend; cp += 4) {
            if (rbuf >= rend) {
                  rbuf = readbuf;
                  int cc = read(fd, (char*)rbuf, ABUFSIZE);
                  if (cc <= 0) {
                        /*
                         * The audio seems to occasionally
                         * lock up.  My guess is that
                         * scheduling delays occasionally
                         * cause us to get behind & the
                         * kernel audio input buffer
                         * overflows so the driver
                         * goes into 'pause' (a really
                         * stupid design decision on hp's
                         * part) and we have to manually
                         * unpause it or we get continuous
                         * EIO errors.
                         */
                        ubufptr = cp;
                        readbufend = rbuf;
                        if (cc == -1 && errno != EAGAIN) {
                              if (errno == EIO) {
                                    struct audio_status as;
                                    ioctl(fd, AUDIO_GET_STATUS,
                                          &as);
                                    if (as.receive_status ==
                                        AUDIO_PAUSE) {
                                          ioctl(fd, AUDIO_RESUME,
                                                AUDIO_RECEIVE);
                                    }
                              } else {
                                    Release();
                                    Obtain();
                              }
                        }
                        return (0);
                  }
                  readbufend = rend = (short*)((u_char*)rbuf + cc);
            }
            /* 
             * there's probably a dc offset due to the phantom
             * power for the mike.  this has to be filtered out
             * so we can do power calculations so we estimate the
             * dc bias via an ~1HZ lowpass filter & subtract it out.
             */
            register int mean, dif, res;

            register int r0 = (int(rbuf[0]) + int(rbuf[1])) >> 1;
            mean = smean >> 13;
            dif = r0 - mean;
            smean += dif;
              res = lintomulawX[dif & 0x1ffff] << 24;

            register int r1 = (int(rbuf[2]) + int(rbuf[3])) >> 1;
            mean = smean >> 13;
            dif = r1 - mean;
            smean += dif;
              res |= lintomulawX[dif & 0x1ffff] << 16;

            register int r2 = (int(rbuf[4]) + int(rbuf[5])) >> 1;
            mean = smean >> 13;
            dif = r2 - mean;
            smean += dif;
              res |= lintomulawX[dif & 0x1ffff] << 8;

            register int r3 = (int(rbuf[6]) + int(rbuf[7])) >> 1;
            mean = smean >> 13;
            dif = r3 - mean;
            smean += dif;
              res |= lintomulawX[dif & 0x1ffff];

            *(int*)cp = res;
            rbuf += 8;
      }
      lastmean[iport] = smean;
      readptr = rbuf;
      return (1);
}

u_char* HPAudio::Read()
{
      return (ubufptr = ubuf);
}

void HPAudio::SetRGain(int level)
{
      rgain = level;
      if (HaveAudio()) {
            struct audio_gain gain;
            if (ioctl(fd, AUDIO_GET_GAINS, &gain) < 0) {
                  perror("AUDIO_GET_GAINS");
                  exit(1);
            }
            int g = int(float(rgain) * rgain_scale + rgain_min);
            gain.cgain[0].receive_gain = g;
            gain.cgain[1].receive_gain = g;
            if (ioctl(fd, AUDIO_SET_GAINS, &gain) < 0) {
                  perror("AUDIO_SET_GAINS");
                  exit(1);
            }
      }
}

void HPAudio::SetPGain(int level)
{

      pgain = level;
      if (HaveAudio()) {
            struct audio_gain gain;
            if (ioctl(fd, AUDIO_GET_GAINS, &gain) < 0) {
                  perror("AUDIO_GET_GAINS");
                  exit(1);
            }
            int g = int(float(pgain) * pgain_scale + pgain_min);
            gain.cgain[0].transmit_gain = g;
            gain.cgain[1].transmit_gain = g;
            if (ioctl(fd, AUDIO_SET_GAINS, &gain) < 0) {
                  perror("AUDIO_SET_GAINS");
                  exit(1);
            }
      }
}

void HPAudio::InputPort(int p)
{
      iport = p;
      if (HaveAudio()) {
            ioctl(fd, AUDIO_SET_INPUT,
                  iport? AUDIO_IN_LINE : AUDIO_IN_MIKE);
      }
}

void HPAudio::OutputPort(int p)
{
      oport = p;
      if (HaveAudio()) {
            ioctl(fd, AUDIO_SET_OUTPUT,
                  oport == 0? AUDIO_OUT_SPEAKER :
                          oport == 1? AUDIO_OUT_EXTERNAL :
                                    AUDIO_OUT_LINE);
      }
}

HP2160Audio::HP2160Audio()
{
      iports = 1;
      oports = 2;
}

void HP2160Audio::Obtain()
{
      if (HaveAudio())
            return;

      fd = open("/dev/audio", O_RDWR, 0);
      if (fd >= 0) {
            struct audio_select_thresholds selt;
            selt.write_threshold = selt.read_threshold = blksize * 4;
            if (ioctl(fd, AUDIO_SET_SEL_THRESHOLD, &selt) < 0) {
                  perror("AUDIO_SET_SEL_THRESHOLD");
                  exit(1);
            }
            if (ioctl(fd, AUDIO_SET_DATA_FORMAT, AUDIO_FORMAT_LINEAR16BIT) < 0) {
                  perror("AUDIO_SET_DATA_FORMAT");
                  exit(1);
            }
            if (ioctl(fd, AUDIO_SET_CHANNELS, 1) < 0) {
                  perror("AUDIO_SET_CHANNELS");
                  exit(1);
            }
            /*
             * the default sample rate is supposed to be 8khz but
             * there's a kernel bug that keeps it from being set so
             * we inherit whatever the last used.  so set it to what
             * we need.
             */
            if (ioctl(fd, AUDIO_SET_SAMPLE_RATE, 8000) < 0) {
                  perror("AUDIO_SET_SAMPLE_RATE");
                  exit(1);
            }
            readptr = readbufend;
            writeptr = writebuf;
            SetRGain(rgain);
            SetPGain(pgain);
            OutputPort(oport);
            InputPort(iport);
            if (fcntl(fd, F_SETFL, O_NONBLOCK) < 0) {
                  perror("audio O_NONBLOCK");
                  exit(1);
            }
            if (ioctl(fd, AUDIO_SET_RXBUFSIZE, 32768) < 0) {
                  perror("AUDIO_SET_RXBUFSIZE");
                  exit(1);
            }
            if (ioctl(fd, AUDIO_SET_TXBUFSIZE, 32768) < 0) {
                  perror("AUDIO_SET_TXBUFSIZE");
                  exit(1);
            }
            notify();
      }
}

void HP2160Audio::Write(u_char *cp)
{
      if (HaveAudio()) {
            register u_char *cpend = cp + blksize;
            register short *wbuf = writebuf;
            for ( ; cp < cpend; cp += 4) {
                  wbuf[0] = mulawtolin[cp[0]];
                  wbuf[1] = mulawtolin[cp[1]];
                  wbuf[2] = mulawtolin[cp[2]];
                  wbuf[3] = mulawtolin[cp[3]];
                  wbuf += 4;
            }
            int cc = write(fd, (char *)writebuf, wbuf - writebuf);
            if (cc < 0 && errno != EPERM)
                  perror("audio write");
      }
}

int HP2160Audio::FrameReady()
{
      register u_char* cp = ubufptr;
      register u_char* cpend = ubufend;
      register short* rbuf = readptr;
      register short* rend = readbufend;
      register int smean = lastmean[iport];

      for ( ; cp < cpend; cp += 4) {
            if (rbuf >= rend) {
                  rbuf = readbuf;
                  int cc = read(fd, (char*)rbuf, ABUFSIZE);
                  if (cc < 0) {
                        if (errno == EINVAL)
                              /* probably wrapped file pos. */
                              lseek(fd, 0, SEEK_SET);
                        ubufptr = cp;
                        readbufend = rbuf;
                        return (0);
                  }
                  readbufend = rend = (short*)((u_char*)rbuf + cc);
            }
            /* 
             * there's probably a dc offset due to the phantom
             * power for the mike.  this has to be filtered out
             * so we can do power calculations so we estimate the
             * dc bias via an ~1HZ lowpass filter & subtract it out.
             */
            register int mean, dif, res;

            register int r0 = int(rbuf[0]);
            mean = smean >> 13;
            dif = r0 - mean;
            smean += dif;
              res = lintomulawX[dif & 0x1ffff] << 24;

            register int r1 = int(rbuf[1]);
            mean = smean >> 13;
            dif = r1 - mean;
            smean += dif;
              res |= lintomulawX[dif & 0x1ffff] << 16;

            register int r2 = int(rbuf[2]);
            mean = smean >> 13;
            dif = r2 - mean;
            smean += dif;
              res |= lintomulawX[dif & 0x1ffff] << 8;

            register int r3 = int(rbuf[3]);
            mean = smean >> 13;
            dif = r3 - mean;
            smean += dif;
              res |= lintomulawX[dif & 0x1ffff];

            *(int*)cp = res;
            rbuf += 4;
      }
      lastmean[iport] = smean;
      readptr = rbuf;
      return (1);
}

Generated by  Doxygen 1.6.0   Back to index