SphinxBase  0.6
src/libsphinxad/ad_alsa.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 /* -*- mode:c; indent-tabs-mode:t; c-basic-offset:4; comment-column:40 -*-
00038  *
00039  * Sphinx II libad (Linux)
00040  * ^^^^^^^^^^^^^^^^^^^^^^^
00041  * $Id: ad_alsa.c,v 1.6 2001/12/11 00:24:48 lenzo Exp $
00042  *
00043  * John G. Dorsey (jd5q+@andrew.cmu.edu)
00044  * Engineering Design Research Center
00045  * Carnegie Mellon University
00046  * ***************************************************************************
00047  * 
00048  * REVISION HISTORY
00049  *
00050  * 18-Mar-2006  David Huggins-Daines <dhuggins@cs.cmu.edu>
00051  *              Update this to the ALSA 1.0 API.
00052  *
00053  * 12-Dec-2000  David Huggins-Daines <dhd@cepstral.com> at Cepstral LLC
00054  *              Make this at least compile with the new ALSA API.
00055  *
00056  * 05-Nov-1999  Sean Levy (snl@stalphonsos.com) at St. Alphonsos, LLC.
00057  *              Ported to ALSA so I can actually get working full-duplex.
00058  *
00059  * 09-Aug-1999  Kevin Lenzo (lenzo@cs.cmu.edu) at Cernegie Mellon University.
00060  *              Incorporated nickr@cs.cmu.edu's changes (marked below) and
00061  *              SPS_EPSILON to allow for sample rates that are "close enough".
00062  * 
00063  * 15-Jun-1999  M. K. Ravishankar (rkm@cs.cmu.edu) at Carnegie Mellon Univ.
00064  *              Consolidated all ad functions into
00065  *              this one file.  Added ad_open_sps().
00066  *              Other cosmetic changes for consistency (e.g., use of err.h).
00067  * 
00068  * 18-May-1999  Kevin Lenzo (lenzo@cs.cmu.edu) added <errno.h>.
00069  */
00070 
00071 
00072 #include <fcntl.h>
00073 #include <stdio.h>
00074 #include <stdlib.h>
00075 #include <string.h>
00076 #include <alsa/asoundlib.h>
00077 #include <errno.h>
00078 #include <config.h>
00079 #include <unistd.h>
00080 
00081 #include "prim_type.h"
00082 #include "ad.h"
00083 
00084 
00085 #define AUDIO_FORMAT SND_PCM_SFMT_S16_LE        /* 16-bit signed, little endian */
00086 #define INPUT_GAIN   (85)
00087 #define SPS_EPSILON   200
00088 
00089 static int
00090 setparams(int32 sps, snd_pcm_t * handle)
00091 {
00092     snd_pcm_hw_params_t *hwparams;
00093     unsigned int out_sps, buffer_time, period_time;
00094     int err;
00095 
00096     snd_pcm_hw_params_alloca(&hwparams);
00097     err = snd_pcm_hw_params_any(handle, hwparams);
00098     if (err < 0) {
00099         fprintf(stderr, "Can not configure this PCM device: %s\n",
00100                 snd_strerror(err));
00101         return -1;
00102     }
00103 
00104     err =
00105         snd_pcm_hw_params_set_access(handle, hwparams,
00106                                      SND_PCM_ACCESS_RW_INTERLEAVED);
00107     if (err < 0) {
00108         fprintf(stderr,
00109                 "Failed to set PCM device to interleaved: %s\n",
00110                 snd_strerror(err));
00111         return -1;
00112     }
00113 
00114     err =
00115         snd_pcm_hw_params_set_format(handle, hwparams, SND_PCM_FORMAT_S16);
00116     if (err < 0) {
00117         fprintf(stderr,
00118                 "Failed to set PCM device to 16-bit signed PCM: %s\n",
00119                 snd_strerror(err));
00120         return -1;
00121     }
00122 
00123     err = snd_pcm_hw_params_set_channels(handle, hwparams, 1);
00124     if (err < 0) {
00125         fprintf(stderr, "Failed to set PCM device to mono: %s\n",
00126                 snd_strerror(err));
00127         return -1;
00128     }
00129 
00130     out_sps = sps;
00131     err =
00132         snd_pcm_hw_params_set_rate_near(handle, hwparams, &out_sps, NULL);
00133     if (err < 0) {
00134         fprintf(stderr, "Failed to set sampling rate: %s\n",
00135                 snd_strerror(err));
00136         return -1;
00137     }
00138     if (abs(out_sps - sps) > SPS_EPSILON) {
00139         fprintf(stderr,
00140                 "Available samping rate %d is too far from requested %d\n",
00141                 out_sps, sps);
00142         return -1;
00143     }
00144 
00145     /* Set buffer time to the maximum. */
00146     err = snd_pcm_hw_params_get_buffer_time_max(hwparams, &buffer_time, 0);
00147     period_time = buffer_time / 4;
00148     err = snd_pcm_hw_params_set_period_time_near(handle, hwparams,
00149                                                  &period_time, 0);
00150     if (err < 0) {
00151         fprintf(stderr, "Failed to set period time to %u: %s\n",
00152                 period_time, snd_strerror(err));
00153         return -1;
00154     }
00155     err = snd_pcm_hw_params_set_buffer_time_near(handle, hwparams,
00156                                                  &buffer_time, 0);
00157     if (err < 0) {
00158         fprintf(stderr, "Failed to set buffer time to %u: %s\n",
00159                 buffer_time, snd_strerror(err));
00160         return -1;
00161     }
00162 
00163     err = snd_pcm_hw_params(handle, hwparams);
00164     if (err < 0) {
00165         fprintf(stderr, "Failed to set hwparams: %s\n", snd_strerror(err));
00166         return -1;
00167     }
00168 
00169     err = snd_pcm_nonblock(handle, 1);
00170     if (err < 0) {
00171         fprintf(stderr, "Failed to set non-blocking mode: %s\n",
00172                 snd_strerror(err));
00173         return -1;
00174     }
00175     return 0;
00176 }
00177 
00178 static int
00179 setlevels(const char *dev)
00180 {
00181     snd_mixer_t *handle;
00182     snd_mixer_selem_id_t *sid;
00183     snd_mixer_elem_t *elem;
00184     int err;
00185     char *mixer_dev, *c;
00186 
00187     /* Basically we just want to turn on Mic capture. */
00188     if ((err = snd_mixer_open(&handle, 0)) < 0) {
00189         fprintf(stderr, "Mixer open failed: %s\n", snd_strerror(err));
00190         return -1;
00191     }
00192 
00193     mixer_dev = strdup(dev);
00194     if (strncmp(mixer_dev, "plug", 4) == 0)
00195         memmove(mixer_dev, mixer_dev + 4, strlen(mixer_dev) - 4 + 1);
00196     if ((c = strchr(mixer_dev, ',')))
00197         *c = '\0';
00198     if ((err = snd_mixer_attach(handle, mixer_dev)) < 0) {
00199         fprintf(stderr, "Mixer attach to %s failed: %s\n",
00200                 mixer_dev, snd_strerror(err));
00201         free(mixer_dev);
00202         snd_mixer_close(handle);
00203         return -1;
00204     }
00205     free(mixer_dev);
00206     if ((err = snd_mixer_selem_register(handle, NULL, NULL)) < 0) {
00207         fprintf(stderr, "Mixer register failed: %s\n", snd_strerror(err));
00208         snd_mixer_close(handle);
00209         return -1;
00210     }
00211     if ((err = snd_mixer_load(handle)) < 0) {
00212         fprintf(stderr, "Mixer load failed: %s\n", snd_strerror(err));
00213         snd_mixer_close(handle);
00214         return -1;
00215     }
00216     snd_mixer_selem_id_alloca(&sid);
00217     snd_mixer_selem_id_set_name(sid, "Mic");
00218     if ((elem = snd_mixer_find_selem(handle, sid)) == NULL) {
00219         fprintf(stderr, "Warning: Could not find Mic element\n");
00220     }
00221     else {
00222         if (snd_mixer_selem_has_capture_switch(elem)) {
00223             if ((err = snd_mixer_selem_set_capture_switch_all(elem, 1)) < 0) { 
00224                 fprintf(stderr,
00225                         "Failed to enable microphone capture: %s\n",
00226                         snd_strerror(err));
00227                 snd_mixer_close(handle); 
00228                 return -1;
00229             }
00230         }
00231     }
00232     snd_mixer_selem_id_set_name(sid, "Capture");
00233     if ((elem = snd_mixer_find_selem(handle, sid)) == NULL) {
00234         fprintf(stderr, "Warning: Could not find Capture element\n");
00235     }
00236     else {
00237         if (snd_mixer_selem_has_capture_switch(elem)) {
00238             if ((err = snd_mixer_selem_set_capture_switch_all(elem, 1)) < 0) {
00239                 fprintf(stderr,
00240                         "Failed to enable microphone capture: %s\n",
00241                         snd_strerror(err));
00242                 snd_mixer_close(handle);
00243                 return -1;
00244             }
00245         }
00246     }
00247 
00248     return 0;
00249 }
00250 
00251 ad_rec_t *
00252 ad_open_dev(const char *dev, int32 sps)
00253 {
00254     ad_rec_t *handle;
00255     snd_pcm_t *dspH;
00256 
00257     int err;
00258 
00259     if (dev == NULL)
00260         dev = DEFAULT_DEVICE;
00261 
00262     err = snd_pcm_open(&dspH, dev, SND_PCM_STREAM_CAPTURE, 0);
00263     if (err < 0) {
00264         fprintf(stderr,
00265                 "Error opening audio device %s for capture: %s\n",
00266                 dev, snd_strerror(err));
00267         return NULL;
00268     }
00269 
00270     if (setparams(sps, dspH) < 0) {
00271         return NULL;
00272     }
00273     if (setlevels(dev) < 0) {
00274         return NULL;
00275     }
00276     if ((handle = (ad_rec_t *) calloc(1, sizeof(ad_rec_t))) == NULL) {
00277         fprintf(stderr, "calloc(%d) failed\n", (int)sizeof(ad_rec_t));
00278         abort();
00279     }
00280 
00281     handle->dspH = dspH;
00282     handle->recording = 0;
00283     handle->sps = sps;
00284     handle->bps = sizeof(int16);
00285 
00286     return (handle);
00287 }
00288 
00289 ad_rec_t *
00290 ad_open_sps(int32 sps)
00291 {
00292     return ad_open_dev(DEFAULT_DEVICE, sps);
00293 }
00294 
00295 ad_rec_t *
00296 ad_open(void)
00297 {
00298     return ad_open_sps(DEFAULT_SAMPLES_PER_SEC);
00299 }
00300 
00301 
00302 int32
00303 ad_close(ad_rec_t * handle)
00304 {
00305     if (handle->dspH == NULL)
00306         return AD_ERR_NOT_OPEN;
00307 
00308     if (handle->recording) {
00309         if (ad_stop_rec(handle) < 0)
00310             return AD_ERR_GEN;
00311     }
00312     snd_pcm_close(handle->dspH);
00313     free(handle);
00314 
00315     return (0);
00316 }
00317 
00318 
00319 int32
00320 ad_start_rec(ad_rec_t * handle)
00321 {
00322     int err;
00323 
00324     if (handle->dspH == NULL)
00325         return AD_ERR_NOT_OPEN;
00326 
00327     if (handle->recording)
00328         return AD_ERR_GEN;
00329 
00330     err = snd_pcm_prepare(handle->dspH);
00331     if (err < 0) {
00332         fprintf(stderr, "snd_pcm_prepare failed: %s\n", snd_strerror(err));
00333         return AD_ERR_GEN;
00334     }
00335     err = snd_pcm_start(handle->dspH);
00336     if (err < 0) {
00337         fprintf(stderr, "snd_pcm_start failed: %s\n", snd_strerror(err));
00338         return AD_ERR_GEN;
00339     }
00340     handle->recording = 1;
00341 
00342     return (0);
00343 }
00344 
00345 
00346 int32
00347 ad_stop_rec(ad_rec_t * handle)
00348 {
00349     int err;
00350 
00351     if (handle->dspH == NULL)
00352         return AD_ERR_NOT_OPEN;
00353 
00354     if (!handle->recording)
00355         return AD_ERR_GEN;
00356 
00357     err = snd_pcm_drop(handle->dspH);
00358     if (err < 0) {
00359         fprintf(stderr, "snd_pcm_drop failed: %s\n", snd_strerror(err));
00360         return AD_ERR_GEN;
00361     }
00362     handle->recording = 0;
00363 
00364     return (0);
00365 }
00366 
00367 
00368 int32
00369 ad_read(ad_rec_t * handle, int16 * buf, int32 max)
00370 {
00371     int32 length, err;
00372 
00373     if (!handle->recording)
00374         return AD_EOF;
00375 
00376     length = snd_pcm_readi(handle->dspH, buf, max);
00377     if (length == -EAGAIN) {
00378         length = 0;
00379     }
00380     else if (length == -EPIPE) {
00381         fprintf(stderr, "Input overrun, read calls are too rare (non-fatal)\n");
00382         err = snd_pcm_prepare(handle->dspH);
00383         if (err < 0) {
00384                 fprintf(stderr, "Can't recover from underrun: %s\n",
00385                         snd_strerror(err));
00386                 return AD_ERR_GEN;
00387         }
00388         length = 0;
00389     }
00390     else if (length == -ESTRPIPE) {
00391         fprintf(stderr, "Resuming sound driver (non-fatal)\n");
00392         while ((err = snd_pcm_resume(handle->dspH)) == -EAGAIN)
00393                 usleep(10000); /* Wait for the driver to wake up */
00394         if (err < 0) {
00395                 err = snd_pcm_prepare(handle->dspH);
00396                 if (err < 0) {
00397                         fprintf(stderr, "Can't recover from underrun: %s\n",
00398                                 snd_strerror(err));
00399                         return AD_ERR_GEN;
00400                 }
00401         }
00402         length = 0;
00403     }
00404     else if (length < 0) {
00405         fprintf(stderr, "Audio read error: %s\n",
00406                 snd_strerror(length));
00407         return AD_ERR_GEN;
00408     }
00409     return length;
00410 }