SphinxBase  0.6
src/libsphinxad/ad_oss.c
00001 /* -*- c-basic-offset: 4; indent-tabs-mode: nil -*- */
00002 /* ====================================================================
00003  * Copyright (c) 1999-2001 Carnegie Mellon University.  All rights
00004  * reserved.
00005  *
00006  * Redistribution and use in source and binary forms, with or without
00007  * modification, are permitted provided that the following conditions
00008  * are met:
00009  *
00010  * 1. Redistributions of source code must retain the above copyright
00011  *    notice, this list of conditions and the following disclaimer. 
00012  *
00013  * 2. Redistributions in binary form must reproduce the above copyright
00014  *    notice, this list of conditions and the following disclaimer in
00015  *    the documentation and/or other materials provided with the
00016  *    distribution.
00017  *
00018  * This work was supported in part by funding from the Defense Advanced 
00019  * Research Projects Agency and the National Science Foundation of the 
00020  * United States of America, and the CMU Sphinx Speech Consortium.
00021  *
00022  * THIS SOFTWARE IS PROVIDED BY CARNEGIE MELLON UNIVERSITY ``AS IS'' AND 
00023  * ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 
00024  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
00025  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY
00026  * NOR ITS EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
00027  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 
00028  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
00029  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 
00030  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
00031  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
00032  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
00033  *
00034  * ====================================================================
00035  *
00036  */
00037 /* Sphinx II libad (Linux)
00038  * ^^^^^^^^^^^^^^^^^^^^^^^
00039  * $Id: ad_oss.c,v 1.9 2004/07/16 00:57:12 egouvea Exp $
00040  *
00041  * John G. Dorsey (jd5q+@andrew.cmu.edu)
00042  * Engineering Design Research Center
00043  * Carnegie Mellon University
00044  * ********************************************************************
00045  * 
00046  * REVISION HISTORY
00047  *
00048  * 09-Aug-1999  Kevin Lenzo (lenzo@cs.cmu.edu) at Cernegie Mellon University.
00049  *              Incorporated nickr@cs.cmu.edu's changes (marked below) and
00050  *              SPS_EPSILON to allow for sample rates that are "close enough".
00051  * 
00052  * 15-Jun-1999  M. K. Ravishankar (rkm@cs.cmu.edu) Consolidated all ad functions into
00053  *              this one file.  Added ad_open_sps().
00054  *              Other cosmetic changes for consistency (e.g., use of err.h).
00055  * 
00056  * 18-May-1999  Kevin Lenzo (lenzo@cs.cmu.edu) added <errno.h>.
00057  */
00058 
00059 #include <fcntl.h>
00060 #include <stdio.h>
00061 #include <stdlib.h>
00062 #include <string.h>
00063 #include <sys/soundcard.h>
00064 #include <sys/ioctl.h>
00065 #include <errno.h>
00066 #include <unistd.h>
00067 #include <config.h>
00068 
00069 #include "prim_type.h"
00070 #include "ad.h"
00071 
00072 #define AUDIO_FORMAT AFMT_S16_LE        /* 16-bit signed, little endian */
00073 #define INPUT_GAIN   (80)
00074 
00075 #define SPS_EPSILON   200
00076 #define SAMPLERATE_TOLERANCE 0.01
00077 
00078 ad_rec_t *
00079 ad_open_dev(const char *dev, int32 sps)
00080 {
00081     ad_rec_t *handle;
00082     int32 dspFD, mixerFD;
00083     int32 nonBlocking = 1, sourceMic = SOUND_MASK_MIC, inputGain =
00084         INPUT_GAIN, devMask = 0;
00085     int32 audioFormat = AUDIO_FORMAT;
00086     int32 dspCaps = 0;
00087     int32 sampleRate;
00088     int32 numberChannels = 1;
00089 
00090     sampleRate = sps;
00091 
00092     if (dev == NULL)
00093         dev = DEFAULT_DEVICE;
00094 
00095     /* Used to have O_NDELAY. */
00096     if ((dspFD = open(dev, O_RDONLY)) < 0) {
00097         if (errno == EBUSY)
00098             fprintf(stderr, "%s(%d): Audio device(%s) busy\n",
00099                     __FILE__, __LINE__, dev);
00100         else
00101             fprintf(stderr,
00102                     "%s(%d): Failed to open audio device(%s): %s\n",
00103                     __FILE__, __LINE__, dev, strerror(errno));
00104         return NULL;
00105     }
00106 
00107     if (ioctl(dspFD, SNDCTL_DSP_SYNC, 0) < 0) {
00108         fprintf(stderr, "Audio ioctl(SYNC) failed: %s\n", strerror(errno));
00109         close(dspFD);
00110         return NULL;
00111     }
00112 
00113     if (ioctl(dspFD, SNDCTL_DSP_RESET, 0) < 0) {
00114         fprintf(stderr, "Audio ioctl(RESET) failed: %s\n",
00115                 strerror(errno));
00116         close(dspFD);
00117         return NULL;
00118     }
00119 
00120     if (ioctl(dspFD, SNDCTL_DSP_SETFMT, &audioFormat) < 0) {
00121         fprintf(stderr, "Audio ioctl(SETFMT 0x%x) failed: %s\n",
00122                 audioFormat, strerror(errno));
00123         close(dspFD);
00124         return NULL;
00125     }
00126     if (audioFormat != AUDIO_FORMAT) {
00127         fprintf(stderr,
00128                 "Audio ioctl(SETFMT): 0x%x, expected: 0x%x\n",
00129                 audioFormat, AUDIO_FORMAT);
00130         close(dspFD);
00131         return NULL;
00132     }
00133 
00134     if (ioctl(dspFD, SNDCTL_DSP_SPEED, &sampleRate) < 0) {
00135         fprintf(stderr, "Audio ioctl(SPEED %d) failed %s\n",
00136                 sampleRate, strerror(errno));
00137         close(dspFD);
00138         return NULL;
00139     }
00140     if (sampleRate != sps) {
00141         if (abs(sampleRate - sps) <= (sampleRate * SAMPLERATE_TOLERANCE)) {
00142             fprintf(stderr,
00143                     "Audio ioctl(SPEED) not perfect, but is acceptable. "
00144                     "(Wanted %d, but got %d)\n", sampleRate, sps);
00145         }
00146         else {
00147             fprintf(stderr,
00148                     "Audio ioctl(SPEED): %d, expected: %d\n",
00149                     sampleRate, sps);
00150             close(dspFD);
00151             return NULL;
00152         }
00153     }
00154 
00155     if (ioctl(dspFD, SNDCTL_DSP_CHANNELS, &numberChannels) < 0) {
00156         fprintf(stderr, "Audio ioctl(CHANNELS %d) failed %s\n",
00157                 numberChannels, strerror(errno));
00158         close(dspFD);
00159         return NULL;
00160     }
00161 
00162     if (ioctl(dspFD, SNDCTL_DSP_NONBLOCK, &nonBlocking) < 0) {
00163         fprintf(stderr, "ioctl(NONBLOCK) failed: %s\n", strerror(errno));
00164         close(dspFD);
00165         return NULL;
00166     }
00167 
00168     if (ioctl(dspFD, SNDCTL_DSP_GETCAPS, &dspCaps) < 0) {
00169         fprintf(stderr, "ioctl(GETCAPS) failed: %s\n", strerror(errno));
00170         close(dspFD);
00171         return NULL;
00172     }
00173 #if 0
00174     printf("DSP Revision %d:\n", dspCaps & DSP_CAP_REVISION);
00175     printf("DSP %s duplex capability.\n",
00176            (dspCaps & DSP_CAP_DUPLEX) ? "has" : "does not have");
00177     printf("DSP %s real time capability.\n",
00178            (dspCaps & DSP_CAP_REALTIME) ? "has" : "does not have");
00179     printf("DSP %s batch capability.\n",
00180            (dspCaps & DSP_CAP_BATCH) ? "has" : "does not have");
00181     printf("DSP %s coprocessor capability.\n",
00182            (dspCaps & DSP_CAP_COPROC) ? "has" : "does not have");
00183     printf("DSP %s trigger capability.\n",
00184            (dspCaps & DSP_CAP_TRIGGER) ? "has" : "does not have");
00185     printf("DSP %s memory map capability.\n",
00186            (dspCaps & DSP_CAP_MMAP) ? "has" : "does not have");
00187 #endif
00188 
00189     if ((dspCaps & DSP_CAP_DUPLEX)
00190         && (ioctl(dspFD, SNDCTL_DSP_SETDUPLEX, 0) < 0))
00191         fprintf(stderr, "ioctl(SETDUPLEX) failed: %s\n", strerror(errno));
00192 
00193     /* Patched by N. Roy (nickr@ri.cmu.edu), 99/7/23. 
00194        Previously, mixer was set through dspFD. This is incorrect. Should
00195        be set through mixerFD, /dev/mixer. 
00196        Also, only the left channel volume was being set.
00197      */
00198 
00199     if ((mixerFD = open("/dev/mixer", O_RDONLY)) < 0) {
00200         if (errno == EBUSY) {
00201             fprintf(stderr, "%s %d: mixer device busy.\n",
00202                     __FILE__, __LINE__);
00203             fprintf(stderr, "%s %d: Using current setting.\n",
00204                     __FILE__, __LINE__);
00205         }
00206         else {
00207             fprintf(stderr, "%s %d: %s\n", __FILE__, __LINE__,
00208                     strerror(errno));
00209             exit(1);
00210         }
00211     }
00212 
00213     if (mixerFD >= 0) {
00214         if (ioctl(mixerFD, SOUND_MIXER_WRITE_RECSRC, &sourceMic) < 0) {
00215             if (errno == ENXIO)
00216                 fprintf(stderr,
00217                         "%s %d: can't set mic source for this device.\n",
00218                         __FILE__, __LINE__);
00219             else {
00220                 fprintf(stderr,
00221                         "%s %d: mixer set to mic: %s\n",
00222                         __FILE__, __LINE__, strerror(errno));
00223                 exit(1);
00224             }
00225         }
00226 
00227         /* Set the same gain for left and right channels. */
00228         inputGain = inputGain << 8 | inputGain;
00229 
00230         /* Some OSS devices have no input gain control, but do have a
00231            recording level control.  Find out if this is one of them and
00232            adjust accordingly. */
00233         if (ioctl(mixerFD, SOUND_MIXER_READ_DEVMASK, &devMask) < 0) {
00234             fprintf(stderr,
00235                     "%s %d: failed to read device mask: %s\n",
00236                     __FILE__, __LINE__, strerror(errno));
00237             exit(1);            /* FIXME: not a well-behaved-library thing to do! */
00238         }
00239         if (devMask & SOUND_MASK_IGAIN) {
00240             if (ioctl(mixerFD, SOUND_MIXER_WRITE_IGAIN, &inputGain) < 0) {
00241                 fprintf(stderr,
00242                         "%s %d: mixer input gain to %d: %s\n",
00243                         __FILE__, __LINE__, inputGain, strerror(errno));
00244                 exit(1);
00245             }
00246         }
00247         else if (devMask & SOUND_MASK_RECLEV) {
00248             if (ioctl(mixerFD, SOUND_MIXER_WRITE_RECLEV, &inputGain) < 0) {
00249                 fprintf(stderr,
00250                         "%s %d: mixer record level to %d: %s\n",
00251                         __FILE__, __LINE__, inputGain, strerror(errno));
00252                 exit(1);
00253             }
00254         }
00255         else {
00256             fprintf(stderr,
00257                     "%s %d: can't set input gain/recording level for this device.\n",
00258                     __FILE__, __LINE__);
00259         }
00260 
00261         close(mixerFD);
00262     }
00263 
00264     if ((handle = (ad_rec_t *) calloc(1, sizeof(ad_rec_t))) == NULL) {
00265         fprintf(stderr, "calloc(%ld) failed\n", sizeof(ad_rec_t));
00266         abort();
00267     }
00268 
00269     handle->dspFD = dspFD;
00270     handle->recording = 0;
00271     handle->sps = sps;
00272     handle->bps = sizeof(int16);
00273 
00274     return (handle);
00275 }
00276 
00277 ad_rec_t *
00278 ad_open_sps(int32 sps)
00279 {
00280     return ad_open_dev(DEFAULT_DEVICE, sps);
00281 }
00282 
00283 ad_rec_t *
00284 ad_open(void)
00285 {
00286     return ad_open_sps(DEFAULT_SAMPLES_PER_SEC);
00287 }
00288 
00289 int32
00290 ad_close(ad_rec_t * handle)
00291 {
00292     if (handle->dspFD < 0)
00293         return AD_ERR_NOT_OPEN;
00294 
00295     if (handle->recording) {
00296         if (ad_stop_rec(handle) < 0)
00297             return AD_ERR_GEN;
00298     }
00299 
00300     close(handle->dspFD);
00301     free(handle);
00302 
00303     return (0);
00304 }
00305 
00306 int32
00307 ad_start_rec(ad_rec_t * handle)
00308 {
00309     if (handle->dspFD < 0)
00310         return AD_ERR_NOT_OPEN;
00311 
00312     if (handle->recording)
00313         return AD_ERR_GEN;
00314 
00315     /* Sample rate, format, input mix settings, &c. are configured
00316      * with ioctl(2) calls under Linux. It makes more sense to handle
00317      * these at device open time and consider the state of the device
00318      * to be fixed until closed.
00319      */
00320 
00321     handle->recording = 1;
00322 
00323     /* rkm@cs: This doesn't actually do anything.  How do we turn recording on/off? */
00324 
00325     return (0);
00326 }
00327 
00328 int32
00329 ad_stop_rec(ad_rec_t * handle)
00330 {
00331     if (handle->dspFD < 0)
00332         return AD_ERR_NOT_OPEN;
00333 
00334     if (!handle->recording)
00335         return AD_ERR_GEN;
00336 
00337     if (ioctl(handle->dspFD, SNDCTL_DSP_SYNC, 0) < 0) {
00338         fprintf(stderr, "Audio ioctl(SYNC) failed: %s\n", strerror(errno));
00339         return AD_ERR_GEN;
00340     }
00341 
00342     handle->recording = 0;
00343 
00344     return (0);
00345 }
00346 
00347 int32
00348 ad_read(ad_rec_t * handle, int16 * buf, int32 max)
00349 {
00350     int32 length;
00351 
00352     length = max * handle->bps; /* #samples -> #bytes */
00353 
00354     if ((length = read(handle->dspFD, buf, length)) > 0) {
00355 #if 0
00356         if ((length % handle->bps) != 0)
00357             fprintf(stderr,
00358                     "Audio read returned non-integral #sample bytes (%d)\n",
00359                     length);
00360 #endif
00361         length /= handle->bps;
00362     }
00363 
00364     if (length < 0) {
00365         if (errno != EAGAIN) {
00366             fprintf(stderr, "Audio read error");
00367             return AD_ERR_GEN;
00368         }
00369         else {
00370             length = 0;
00371         }
00372     }
00373 
00374     if ((length == 0) && (!handle->recording))
00375         return AD_EOF;
00376 
00377     return length;
00378 }