Julius 4.2
libsent/src/adin/adin_mic_linux_alsa.c
説明を見る。
00001 
00050 /*
00051  * Copyright (c) 1991-2011 Kawahara Lab., Kyoto University
00052  * Copyright (c) 2000-2005 Shikano Lab., Nara Institute of Science and Technology
00053  * Copyright (c) 2005-2011 Julius project team, Nagoya Institute of Technology
00054  * All rights reserved
00055  */
00056 
00057 #include <sent/stddefs.h>
00058 #include <sent/adin.h>
00059 #include <sys/ioctl.h>
00060 #include <sys/types.h>
00061 #include <sys/stat.h>
00062 #include <fcntl.h>
00063 
00064 #ifdef HAS_ALSA
00065 
00066 #if defined(HAVE_ALSA_ASOUNDLIB_H)
00067 #include <alsa/asoundlib.h>
00068 #elif defined(HAVE_SYS_ASOUNDLIB_H)
00069 #include <sys/asoundlib.h>
00070 #endif
00071 
00072 static int srate;               
00073 static snd_pcm_t *handle;       
00074 static char pcm_name[MAXPATHLEN]; 
00075 static int latency = 32;        
00076 static boolean need_swap;       
00077 
00078 #if (SND_LIB_MAJOR == 0)
00079 static struct pollfd *ufds;     
00080 static int count;               
00081 #endif
00082 
00083 #define MAXPOLLINTERVAL 300     ///< Read timeout in msec.
00084 
00085 #endif /* HAS_ALSA */
00086 
00087 #ifdef HAS_ALSA
00088 
00095 static void
00096 output_card_info(char *pcm_name, snd_pcm_t *handle)
00097 {
00098   int err;
00099   snd_ctl_t *ctl;
00100   snd_ctl_card_info_t *info;
00101   snd_pcm_info_t *pcminfo;
00102   snd_ctl_card_info_alloca(&info);
00103   snd_pcm_info_alloca(&pcminfo);
00104   char ctlname[30];
00105   int card;
00106   
00107   /* get PCM information to set current device and subdevice name */
00108   if ((err = snd_pcm_info(handle, pcminfo)) < 0) {
00109     jlog("Warning: adin_alsa: failed to obtain pcm info\n");
00110     jlog("Warning: adin_alsa: skip output of detailed audio device info\n");
00111     return;
00112   }
00113   /* open control associated with the pcm device name */
00114   card = snd_pcm_info_get_card(pcminfo);
00115   if (card < 0) {
00116     strcpy(ctlname, "default");
00117   } else {
00118     snprintf(ctlname, 30, "hw:%d", card);
00119   }
00120   if ((err = snd_ctl_open(&ctl, ctlname, 0)) < 0) {
00121     jlog("Warning: adin_alsa: failed to open control device \"%s\", \n", ctlname);
00122     jlog("Warning: adin_alsa: skip output of detailed audio device info\n");
00123     return;
00124   }
00125   /* get its card info */
00126   if ((err = snd_ctl_card_info(ctl, info)) < 0) {
00127     jlog("Warning: adin_alsa: unable to get card info for %s\n", ctlname);
00128     jlog("Warning: adin_alsa: skip output of detailed audio device info\n");
00129     snd_ctl_close(ctl);
00130     return;
00131   }
00132 
00133   /* get detailed PCM information of current device from control */
00134   if ((err = snd_ctl_pcm_info(ctl, pcminfo)) < 0) {
00135     jlog("Error: adin_alsa: unable to get pcm info from card control\n");
00136     jlog("Warning: adin_alsa: skip output of detailed audio device info\n");
00137     snd_ctl_close(ctl);
00138     return;
00139   }
00140   /* output */
00141   jlog("Stat: \"%s\": %s [%s] device %s [%s] %s\n",
00142        pcm_name,
00143        snd_ctl_card_info_get_id(info),
00144        snd_ctl_card_info_get_name(info),
00145        snd_pcm_info_get_id(pcminfo),
00146        snd_pcm_info_get_name(pcminfo),
00147        snd_pcm_info_get_subdevice_name(pcminfo));
00148 
00149   /* close controller */
00150   snd_ctl_close(ctl);
00151 
00152 }
00153 #endif /* HAS_ALSA */
00154 
00163 boolean
00164 adin_alsa_standby(int sfreq, void *dummy)
00165 {
00166 #ifndef HAS_ALSA
00167   jlog("Error: ALSA not compiled in\n");
00168   return FALSE;
00169 #else
00170   /* store required sampling rate for checking after opening device */
00171   srate = sfreq;
00172   return TRUE;
00173 #endif
00174 }
00175 
00176 
00184 static boolean
00185 adin_alsa_open(char *devstr)
00186 {
00187 #ifndef HAS_ALSA
00188   jlog("Error: ALSA not compiled in\n");
00189   return FALSE;
00190 #else
00191   int err;
00192   snd_pcm_hw_params_t *hwparams; 
00193 #if (SND_LIB_MAJOR == 0)
00194   int actual_rate;              /* sample rate returned by hardware */
00195 #else
00196   unsigned int actual_rate;             /* sample rate returned by hardware */
00197 #endif
00198   int dir = 0;                  /* comparison result of exact rate and given rate */
00199 
00200   /* open the device in non-block mode) */
00201   if ((err = snd_pcm_open(&handle, devstr, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK)) < 0) {
00202     jlog("Error: adin_alsa: cannot open PCM device \"%s\" (%s)\n", devstr, snd_strerror(err));
00203     return(FALSE);
00204   }
00205   /* set device to non-block mode */
00206   if ((err = snd_pcm_nonblock(handle, 1)) < 0) {
00207     jlog("Error: adin_alsa: cannot set PCM device to non-blocking mode\n");
00208     return(FALSE);
00209   }
00210 
00211   /* allocate hwparam structure */
00212   snd_pcm_hw_params_alloca(&hwparams);
00213 
00214   /* initialize hwparam structure */
00215   if ((err = snd_pcm_hw_params_any(handle, hwparams)) < 0) {
00216     jlog("Error: adin_alsa: cannot initialize PCM device parameter structure (%s)\n", snd_strerror(err));
00217     return(FALSE);
00218   }
00219 
00220   /* set interleaved read/write format */
00221   if ((err = snd_pcm_hw_params_set_access(handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
00222     jlog("Error: adin_alsa: cannot set PCM device access mode (%s)\n", snd_strerror(err));
00223     return(FALSE);
00224   }
00225 
00226   /* set sample format */
00227 #ifdef WORDS_BIGENDIAN
00228   /* try big endian, then little endian with byte swap */
00229   if ((err = snd_pcm_hw_params_set_format(handle, hwparams, SND_PCM_FORMAT_S16_BE)) >= 0) {
00230     need_swap = FALSE;
00231   } else if ((err = snd_pcm_hw_params_set_format(handle, hwparams, SND_PCM_FORMAT_S16_LE)) >= 0) {
00232     need_swap = TRUE;
00233   } else {
00234     jlog("Error: adin_alsa: cannot set PCM device format to signed 16bit (%s)\n", snd_strerror(err));
00235     return(FALSE);
00236   }
00237 #else  /* LITTLE ENDIAN */
00238   /* try little endian, then big endian with byte swap */
00239   if ((err = snd_pcm_hw_params_set_format(handle, hwparams, SND_PCM_FORMAT_S16_LE)) >= 0) {
00240     need_swap = FALSE;
00241   } else if ((err = snd_pcm_hw_params_set_format(handle, hwparams, SND_PCM_FORMAT_S16_BE)) >= 0) {
00242     need_swap = TRUE;
00243   } else {
00244     jlog("Error: adin_alsa: cannot set PCM device format to signed 16bit (%s)\n", snd_strerror(err));
00245     return(FALSE);
00246   }
00247 #endif
00248   /* set number of channels */
00249   if ((err = snd_pcm_hw_params_set_channels(handle, hwparams, 1)) < 0) {
00250     jlog("Error: adin_alsa: cannot set PCM channel to %d (%s)\n", 1, snd_strerror(err));
00251     return(FALSE);
00252   }
00253   
00254   /* set sample rate (if the exact rate is not supported by the hardware, use nearest possible rate */
00255 #if (SND_LIB_MAJOR == 0)
00256   actual_rate = snd_pcm_hw_params_set_rate_near(handle, hwparams, srate, &dir);
00257   if (actual_rate < 0) {
00258     jlog("Error: adin_alsa: cannot set PCM device sample rate to %d (%s)\n", srate, snd_strerror(actual_rate));
00259     return(FALSE);
00260   }
00261 #else
00262   actual_rate = srate;
00263   err = snd_pcm_hw_params_set_rate_near(handle, hwparams, &actual_rate, &dir);
00264   if (err < 0) {
00265     jlog("Error: adin_alsa: cannot set PCM device sample rate to %d (%s)\n", srate, snd_strerror(err));
00266     return(FALSE);
00267   }
00268 #endif
00269   if (actual_rate != srate) {
00270     jlog("Warning: adin_alsa: the exact rate %d Hz is not available by your PCM hardware.\n", srate);
00271     jlog("Warning: adin_alsa: using %d Hz instead.\n", actual_rate);
00272   }
00273   jlog("Stat: capture audio at %dHz\n", actual_rate);
00274 
00275   /* set period size */
00276   {
00277 #if (SND_LIB_MAJOR == 0)
00278     int periodsize;             /* period size (bytes) */
00279     int actual_size;
00280     int maxsize, minsize;
00281 #else
00282     unsigned int period_time, period_time_current;
00283     snd_pcm_uframes_t chunk_size;
00284     boolean has_current_period;
00285 #endif
00286     boolean force = FALSE;
00287     char *p;
00288     
00289     /* set apropriate period size */
00290     if ((p = getenv("LATENCY_MSEC")) != NULL) {
00291       latency = atoi(p);
00292       jlog("Stat: adin_alsa: trying to set latency to %d msec from LATENCY_MSEC)\n", latency);
00293       force = TRUE;
00294     }
00295 
00296     /* get hardware max/min size */
00297 #if (SND_LIB_MAJOR == 0)
00298     if ((maxsize = snd_pcm_hw_params_get_period_size_max(hwparams, &dir)) < 0) {
00299       jlog("Error: adin_alsa: cannot get maximum period size\n");
00300       return(FALSE);
00301     }
00302     if ((minsize = snd_pcm_hw_params_get_period_size_min(hwparams, &dir)) < 0) {
00303       jlog("Error: adin_alsa: cannot get minimum period size\n");
00304       return(FALSE);
00305     }
00306 #else    
00307     has_current_period = TRUE;
00308     if ((err = snd_pcm_hw_params_get_period_time(hwparams, &period_time_current, &dir)) < 0) {
00309       has_current_period = FALSE;
00310     }
00311     if (has_current_period) {
00312       jlog("Stat: adin_alsa: current latency time: %d msec\n", period_time_current / 1000);
00313     }
00314 #endif
00315 
00316     /* set period time (near value will be used) */
00317 #if (SND_LIB_MAJOR == 0)
00318     periodsize = actual_rate * latency / 1000 * sizeof(SP16);
00319     if (periodsize < minsize) {
00320       jlog("Stat: adin_alsa: PCM latency of %d ms (%d bytes) too small, use device minimum %d bytes\n", latency, periodsize, minsize);
00321       periodsize = minsize;
00322     } else if (periodsize > maxsize) {
00323       jlog("Stat: adin_alsa: PCM latency of %d ms (%d bytes) too large, use device maximum %d bytes\n", latency, periodsize, maxsize);
00324       periodsize = maxsize;
00325     }
00326     actual_size = snd_pcm_hw_params_set_period_size_near(handle, hwparams, periodsize, &dir);
00327     if (actual_size < 0) {
00328       jlog("Error: adin_alsa: cannot set PCM record period size to %d (%s)\n", periodsize, snd_strerror(actual_size));
00329       return(FALSE);
00330     }
00331     if (actual_size != periodsize) {
00332       jlog("Stat: adin_alsa: PCM period size: %d bytes (%dms) -> %d bytes\n", periodsize, latency, actual_size);
00333     }
00334     jlog("Stat: Audio I/O Latency = %d msec (data fragment = %d frames)\n", actual_size * 1000 / (actual_rate * sizeof(SP16)), actual_size / sizeof(SP16));
00335 #else
00336     period_time = latency * 1000;
00337     if (!force && has_current_period && period_time > period_time_current) {
00338         jlog("Stat: adin_alsa: current latency (%dms) is shorter than %dms, leave it\n", period_time_current / 1000, latency);
00339         period_time = period_time_current;
00340     } else {
00341       if ((err = snd_pcm_hw_params_set_period_time_near(handle, hwparams, &period_time, 0)) < 0) {
00342         jlog("Error: adin_alsa: cannot set PCM record period time to %d msec (%s)\n", period_time / 1000, snd_strerror(err));
00343         return(FALSE);
00344       }
00345       snd_pcm_hw_params_get_period_size(hwparams, &chunk_size, 0);
00346       jlog("Stat: adin_alsa: latency set to %d msec (chunk = %d bytes)\n", period_time / 1000, chunk_size);
00347     }
00348 #endif
00349 
00350 #if (SND_LIB_MAJOR == 0)
00351     /* set number of periods ( = 2) */
00352     if ((err = snd_pcm_hw_params_set_periods(handle, hwparams, sizeof(SP16), 0)) < 0) {
00353       jlog("Error: adin_alsa: cannot set PCM number of periods to %d (%s)\n", sizeof(SP16), snd_strerror(err));
00354       return(FALSE);
00355     }
00356 #endif
00357   }
00358 
00359   /* apply the configuration to the PCM device */
00360   if ((err = snd_pcm_hw_params(handle, hwparams)) < 0) {
00361     jlog("Error: adin_alsa: cannot set PCM hardware parameters (%s)\n", snd_strerror(err));
00362     return(FALSE);
00363   }
00364 
00365   /* prepare for recording */
00366   if ((err = snd_pcm_prepare(handle)) < 0) {
00367     jlog("Error: adin_alsa: failed to prepare audio interface (%s)\n", snd_strerror(err));
00368   }
00369 
00370 #if (SND_LIB_MAJOR == 0)
00371   /* prepare for polling */
00372   count = snd_pcm_poll_descriptors_count(handle);
00373   if (count <= 0) {
00374     jlog("Error: adin_alsa: invalid PCM poll descriptors count\n");
00375     return(FALSE);
00376   }
00377   ufds = mymalloc(sizeof(struct pollfd) * count);
00378   if ((err = snd_pcm_poll_descriptors(handle, ufds, count)) < 0) {
00379     jlog("Error: adin_alsa: unable to obtain poll descriptors for PCM recording (%s)\n", snd_strerror(err));
00380     return(FALSE);
00381   }
00382 #endif
00383 
00384   /* output status */
00385   output_card_info(devstr, handle);
00386 
00387   return(TRUE);
00388 #endif /* HAS_ALSA */
00389 }
00390 
00391 #ifdef HAS_ALSA
00392 
00400 static int
00401 xrun_recovery(snd_pcm_t *handle, int err)
00402 {
00403   if (err == -EPIPE) {    /* under-run */
00404     err = snd_pcm_prepare(handle);
00405     if (err < 0)
00406       jlog("Error: adin_alsa: can't recovery from PCM buffer underrun, prepare failed: %s\n", snd_strerror(err));
00407     return 0;
00408   } else if (err == -ESTRPIPE) {
00409     while ((err = snd_pcm_resume(handle)) == -EAGAIN)
00410       sleep(1);       /* wait until the suspend flag is released */
00411     if (err < 0) {
00412       err = snd_pcm_prepare(handle);
00413       if (err < 0)
00414         jlog("Error: adin_alsa: can't recovery from PCM buffer suspend, prepare failed: %s\n", snd_strerror(err));
00415     }
00416     return 0;
00417   }
00418   return err;
00419 }
00420 #endif /* HAS_ALSA */
00421 
00429 boolean
00430 adin_alsa_begin(char *pathname)
00431 {
00432 #ifndef HAS_ALSA
00433   return FALSE;
00434 #else
00435   int err;
00436   snd_pcm_state_t status;
00437   char *p;
00438 
00439   /* set device name to open to pcm_name */
00440   if (pathname != NULL) {
00441     strncpy(pcm_name, pathname, MAXPATHLEN);
00442     jlog("Stat: adin_alsa: device name from argument: \"%s\"\n", pcm_name);
00443   } else if ((p = getenv("ALSADEV")) != NULL) {
00444     strncpy(pcm_name, p, MAXPATHLEN);
00445     jlog("Stat: adin_alsa: device name from ALSADEV: \"%s\"\n", pcm_name);
00446   } else {
00447     strcpy(pcm_name, "default");
00448   }
00449   /* open the device */
00450   if (adin_alsa_open(pcm_name) == FALSE) {
00451     return FALSE;
00452   }
00453 
00454   /* check hardware status */
00455   while(1) {                    /* wait till prepared */
00456     status = snd_pcm_state(handle);
00457     switch(status) {
00458     case SND_PCM_STATE_PREPARED: /* prepared for operation */
00459       if ((err = snd_pcm_start(handle)) < 0) {
00460         jlog("Error: adin_alsa: cannot start PCM (%s)\n", snd_strerror(err));
00461         return (FALSE);
00462       }
00463       return(TRUE);
00464       break;
00465     case SND_PCM_STATE_RUNNING: /* capturing the samples of other application */
00466       if ((err = snd_pcm_drop(handle)) < 0) { /* discard the existing samples */
00467         jlog("Error: adin_alsa: cannot drop PCM (%s)\n", snd_strerror(err));
00468         return (FALSE);
00469       }
00470       break;
00471     case SND_PCM_STATE_XRUN:    /* buffer overrun */
00472       if ((err = xrun_recovery(handle, -EPIPE)) < 0) {
00473         jlog("Error: adin_alsa: PCM XRUN recovery failed (%s)\n", snd_strerror(err));
00474         return(FALSE);
00475       }
00476       break;
00477     case SND_PCM_STATE_SUSPENDED:       /* suspended by power management system */
00478       if ((err = xrun_recovery(handle, -ESTRPIPE)) < 0) {
00479         jlog("Error: adin_alsa: PCM XRUN recovery failed (%s)\n", snd_strerror(err));
00480         return(FALSE);
00481       }
00482       break;
00483     default:
00484       /* do nothing */
00485       break;
00486     }
00487   }
00488 
00489   return(TRUE);
00490 #endif /* HAS_ALSA */
00491 }
00492   
00498 boolean
00499 adin_alsa_end()
00500 {
00501   int err;
00502 
00503   if ((err = snd_pcm_close(handle)) < 0) {
00504     jlog("Error: adin_alsa: cannot close PCM device (%s)\n", snd_strerror(err));
00505     return(FALSE);
00506   }
00507   return(TRUE);
00508 }
00509 
00522 int
00523 adin_alsa_read(SP16 *buf, int sampnum)
00524 {
00525 #ifndef HAS_ALSA
00526   return -2;
00527 #else
00528   int cnt;
00529 
00530 #if (SND_LIB_MAJOR == 0)
00531 
00532   snd_pcm_sframes_t avail;
00533 
00534   while ((avail = snd_pcm_avail_update(handle)) <= 0) {
00535     usleep(latency * 1000);
00536   }
00537   if (avail < sampnum) {
00538     cnt = snd_pcm_readi(handle, buf, avail);
00539   } else {
00540     cnt = snd_pcm_readi(handle, buf, sampnum);
00541   }
00542 
00543 #else
00544 
00545   int ret;
00546   snd_pcm_status_t *status;
00547   int res;
00548   struct timeval now, diff, tstamp;
00549 
00550   ret = snd_pcm_wait(handle, MAXPOLLINTERVAL);
00551   switch (ret) {
00552   case 0:                       /* timeout */
00553     jlog("Warning: adin_alsa: no data fragment after %d msec?\n", MAXPOLLINTERVAL);
00554     cnt = 0;
00555     break;
00556   case 1:                       /* has data */
00557     cnt = snd_pcm_readi(handle, buf, sampnum); /* read available (non-block) */
00558     break;
00559   case -EPIPE:                  /* pipe error */
00560     /* try to recover the broken pipe */
00561     snd_pcm_status_alloca(&status);
00562     if ((res = snd_pcm_status(handle, status))<0) {
00563       jlog("Error: adin_alsa: broken pipe: status error (%s)\n", snd_strerror(res));
00564       return -2;
00565     }
00566     if (snd_pcm_status_get_state(status) == SND_PCM_STATE_XRUN) {
00567       gettimeofday(&now, 0);
00568       snd_pcm_status_get_trigger_tstamp(status, &tstamp);
00569       timersub(&now, &tstamp, &diff);
00570       jlog("Warning: adin_alsa: overrun!!! (at least %.3f ms long)\n",
00571            diff.tv_sec * 1000 + diff.tv_usec / 1000.0);
00572       if ((res = snd_pcm_prepare(handle))<0) {
00573         jlog("Error: adin_alsa: overrun: prepare error (%s)", snd_strerror(res));
00574         return -2;
00575       }
00576       break;         /* ok, data should be accepted again */
00577     } else if (snd_pcm_status_get_state(status) == SND_PCM_STATE_DRAINING) {
00578       jlog("Warning: adin_alsa: draining: capture stream format change? attempting recover...\n");
00579       if ((res = snd_pcm_prepare(handle))<0) {
00580         jlog("Error: adin_alsa: draining: prepare error (%s)", snd_strerror(res));
00581         return -2;
00582       }
00583       break;
00584     }
00585     jlog("Error: adin_alsa: error in snd_pcm_wait() (%s)\n", snd_pcm_state_name(snd_pcm_status_get_state(status)));
00586     return -2;
00587 
00588   default:                      /* other poll error */
00589     jlog("Error: adin_alsa: error in snd_pcm_wait() (%s)\n", snd_strerror(ret));
00590     return(-2);                 /* error */
00591   }
00592 #endif
00593   if (cnt < 0) {
00594     jlog("Error: adin_alsa: failed to read PCM (%s)\n", snd_strerror(cnt));
00595     return(-2);
00596   }
00597   if (need_swap) {
00598     swap_sample_bytes(buf, cnt);
00599   }
00600 
00601   return(cnt);
00602 #endif /* HAS_ALSA */
00603 }
00604 
00612 char *
00613 adin_alsa_input_name()
00614 {
00615 #ifndef HAS_ALSA
00616   return NULL;
00617 #else
00618   return(pcm_name);
00619 #endif
00620 }
00621 
00622 /* end of file */