/**
* @file adin_portaudio.c
*
*
* @brief マイク入力 (portaudioライブラリ)
*
* portaudiooライブラリを使用したマイク入力のための低レベル関数です.
* 使用するには configure 時に "--with-mictype=portaudio" を指定して下さい.
* Linux および Win32 で使用可能です.Win32モードではこれが
* デフォルトとなります.
*
* 録音デバイスは WASAPI -> ASIO -> DirectSound -> MME の順で選択されます。
* 使用するデバイスを指定したい場合は、環境変数 PORTAUDIO_DEV でデバイス名
* (先頭から部分マッチ)を指定するか、PORTAUDIO_DEV_NUM でデバイス番号を
* 指定してください。使用可能なデバイス名とデバイス番号は起動時に出力されます。
*
* Juliusはミキサーデバイスの設定を一切行いません.録音デバイスの
* 選択(マイク/ライン)や録音ボリュームの調節はWindowsの
* 「ボリュームコントロール」 や Linux の xmixer など,他のツールで
* 行なって下さい.
*
* portaudio はフリーでクロスプラットホームのオーディオ入出力ライブラリ
* です.ソースは libsent/src/adin/pa/ に含まれています.このプログラムでは
* スレッドを利用したcallback を利用して入力音声をリングバッファに取り込んで
* います.
*
* @sa http://www.portaudio.com/
*
*
* @brief Microphone input using portaudio library
*
* Low level I/O functions for microphone input using portaudio library.
* To use, please specify "--with-mictype=portaudio" options
* to configure script. This function is currently available for Linux and
* Win32. On Windows, this is default.
*
* The audio API will be chosen in the following order: WASAPI, ASIO,
* DirectSound and MME. You can specify which audio capture device to use
* by setting the name (entire, or just the first part) to the
* environment variable "PORTAUDIO_DEV", or the ID number to
* "PORTAUDIO_DEV_NUM". The names and ID numbers of available devices will
* be scanned and listed at the initialization.
*
* Julius does not alter any mixer device setting at all. You should
* configure the mixer for recording source (mic/line) and recording volume
* correctly using other audio tool such as xmixer on Linux, or
* 'Volume Control' on Windows.
*
* Portaudio is a free, cross platform, open-source audio I/O library.
* The sources are included at libsent/src/adin/pa/. This program uses
* ring buffer to store captured samples in callback functions with threading.
*
* @sa http://www.portaudio.com/
*
*
* @author Akinobu LEE
* @date Mon Feb 14 12:03:48 2005
*
* $Revision: 1.19 $
*
*/
/*
* Copyright (c) 2004-2005 Shikano Lab., Nara Institute of Science and Technology
* Copyright (c) 2005-2012 Julius project team, Nagoya Institute of Technology
* All rights reserved
*/
#include
#include
#include
/* sound header */
#include
#ifndef paNonInterleaved
#define OLDVER
#endif
#undef DDEBUG
/**
* Define this to choose which audio device to open by querying the
* device API type in the following order in Windows:
*
* WASAPI -> ASIO -> DirectSound -> MME
*
* This will be effective when using portaudio library with multiple
* Host API support, in which case Pa_OpenDefaultStream() will open
* the first found one (not the one with the optimal performance)
*
* (not work on OLDVER)
*
*/
#ifndef OLDVER
#define CHOOSE_HOST_API
#endif
/**
* Maximum Data fragment Length in msec. Input can be delayed to this time.
* You can override this value by specifying environment valuable
* "LATENCY_MSEC".
*
* This is not used in the new V19, it uses default value given by the library.
* At the new V19, you can force latency by PA_MIN_LATENCY_MSEC instead of LATENCY_MSEC.
*
*/
#ifdef OLDVER
#define MAX_FRAGMENT_MSEC 128
#endif
/* temporal buffer */
static SP16 *speech; ///< cycle buffer for incoming speech data
static int current; ///< writing point
static int processed; ///< reading point
static boolean buffer_overflowed = FALSE; ///< TRUE if buffer overflowed
static int cycle_buffer_len; ///< length of cycle buffer based on INPUT_DELAY_SEC
/**
* PortAudio callback to store the incoming speech data into the cycle
* buffer.
*
* @param inbuf [in] portaudio input buffer
* @param outbuf [in] portaudio output buffer (not used)
* @param len [in] length of above
* @param outTime [in] output time (not used)
* @param userdata [in] user defined data (not used)
*
* @return 0 when no error, or 1 to terminate recording.
*/
static int
#ifdef OLDVER
Callback(void *inbuf, void *outbuf, unsigned long len, PaTimestamp outTime, void *userdata)
#else
Callback(const void *inbuf, void *outbuf, unsigned long len, const PaStreamCallbackTimeInfo *outTime, PaStreamCallbackFlags statusFlags, void *userdata)
#endif
{
#ifdef OLDVER
SP16 *now;
int avail;
#else
const SP16 *now;
unsigned long avail;
#endif
int processed_local;
int written;
now = inbuf;
processed_local = processed;
#ifdef DDEBUG
printf("callback-1: processed=%d, current=%d: recordlen=%d\n", processed_local, current, len);
#endif
/* check overflow */
if (processed_local > current) {
avail = processed_local - current;
} else {
avail = cycle_buffer_len + processed_local - current;
}
if (len > avail) {
#ifdef DDEBUG
printf("callback-*: buffer overflow!\n");
#endif
buffer_overflowed = TRUE;
len = avail;
}
/* store to buffer */
if (current + len <= cycle_buffer_len) {
memcpy(&(speech[current]), now, len * sizeof(SP16));
#ifdef DDEBUG
printf("callback-2: [%d..%d] %d samples written\n", current, current+len, len);
#endif
} else {
written = cycle_buffer_len - current;
memcpy(&(speech[current]), now, written * sizeof(SP16));
#ifdef DDEBUG
printf("callback-2-1: [%d..%d] %d samples written\n", current, current+written, written);
#endif
memcpy(&(speech[0]), &(now[written]), (len - written) * sizeof(SP16));
#ifdef DDEBUG
printf("callback-2-2: ->[%d..%d] %d samples written (total %d samples)\n", 0, len-written, len-written, len);
#endif
}
current += len;
if (current >= cycle_buffer_len) current -= cycle_buffer_len;
#ifdef DDEBUG
printf("callback-3: new current: %d\n", current);
#endif
return(0);
}
#ifdef OLDVER
static PortAudioStream *stream = NULL; ///< Stream information
#else
static PaStream *stream = NULL; ///< Stream information
#endif
static int srate; ///< Required sampling rate
#ifdef CHOOSE_HOST_API
// Get device list
// If first argument is NULL, return the maximum number of devices
int
get_device_list(int *devidlist, char **namelist, int maxstrlen, int maxnum)
{
PaDeviceIndex numDevice = Pa_GetDeviceCount(), i;
const PaDeviceInfo *deviceInfo;
const PaHostApiInfo *apiInfo;
static char buf[256];
int n;
n = 0;
for(i=0;imaxInputChannels <= 0) continue;
apiInfo = Pa_GetHostApiInfo(deviceInfo->hostApi);
if (!apiInfo) continue;
if (devidlist != NULL) {
if ( n >= maxnum ) break;
snprintf(buf, 255, "%s: %s", apiInfo->name, deviceInfo->name);
buf[255] = '\0';
devidlist[n] = i;
strncpy(namelist[n], buf, maxstrlen);
}
n++;
}
return n;
}
// Automatically choose a device to open
//
// 1. if the env value of "PORTAUDIO_DEV" is defined and matches any of
// "apiInfo->name: deviceInfo->name" string, use it.
// 2. if the env value "PORTAUDIO_DEV_NUM" is defined, use the (value-1)
// as device id.
// 3. if not yet, default device will be chosen:
// 3.1. in WIN32 environment, search for supported and available API in
// the following order: WASAPI, ASIO, DirectSound, MME.
// Note that only the APIs supported by the linked PortAudio
// library are available.
// 3.2 in other environment, use the default input device.
//
// store the device id to devId_ret and returns 0.
// if devId_ret is -1, tell caller to use default.
// returns -1 on error.
static int
auto_determine_device(int *devId_ret)
{
int devId;
PaDeviceIndex numDevice = Pa_GetDeviceCount(), i;
const PaDeviceInfo *deviceInfo;
const PaHostApiInfo *apiInfo;
char *devname;
static char buf[256];
#if defined(_WIN32) || defined(__CYGWIN__)
// at win32, force preference order: iWASAPI > ASIO > DirectSound > MME > Other
int iMME = -1, iDS = -1, iASIO = -1, iWASAPI = -1;
#endif
// if PORTAUDIO_DEV is specified, match it against available APIs
devname = getenv("PORTAUDIO_DEV");
devId = -1;
// get list of available capture devices
jlog("Stat: adin_portaudio: sound capture devices:\n");
for(i=0;imaxInputChannels <= 0) continue;
apiInfo = Pa_GetHostApiInfo(deviceInfo->hostApi);
if (!apiInfo) continue;
snprintf(buf, 255, "%s: %s", apiInfo->name, deviceInfo->name);
buf[255] = '\0';
jlog(" %d [%s]\n", i+1, buf);
if (devname && !strncmp(devname, buf, strlen(devname))) {
// device name (partially) matches PORTAUDIO_DEV
devId = i;
}
#if defined(_WIN32) || defined(__CYGWIN__)
// store the device ID for each API
switch(apiInfo->type) {
case paWASAPI: if (iWASAPI < 0) iWASAPI = i; break;
case paMME:if (iMME < 0) iMME = i; break;
case paDirectSound:if (iDS < 0) iDS = i; break;
case paASIO:if (iASIO < 0) iASIO = i; break;
}
#endif
}
if (devname) {
if (devId == -1) {
jlog("Error: adin_portaudio: PORTAUDIO_DEV=\"%s\", but no device matches it\n", devname);
return -1;
}
jlog(" --> #%d matches PORTAUDIO_DEV, use it\n", devId + 1);
*devId_ret = devId;
return 0;
}
if (getenv("PORTAUDIO_DEV_NUM")) {
devId = atoi(getenv("PORTAUDIO_DEV_NUM")) - 1;
if (devId < 0 || devId >= numDevice) {
jlog("Error: PORTAUDIO_DEV_NUM=%d, but device %d not exist\n", devId+1, devId+1);
return -1;
}
jlog(" --> use device %d, specified by PORTAUDIO_DEV_NUM\n", devId + 1);
*devId_ret = devId;
return 0;
}
#if defined(_WIN32) || defined(__CYGWIN__)
jlog("Stat: adin_portaudio: APIs:");
if (iWASAPI >= 0) jlog(" WASAPI");
if (iASIO >= 0) jlog(" ASIO");
if (iDS >= 0) jlog(" DirectSound");
if (iMME >= 0) jlog(" MME");
jlog("\n");
if (iWASAPI >= 0) {
jlog("Stat: adin_portaudio: -- WASAPI selected\n");
devId = iWASAPI;
} else if (iASIO >= 0) {
jlog("Stat: adin_portaudio: -- ASIO selected\n");
devId = iASIO;
} else if (iDS >= 0) {
jlog("Stat: adin_portaudio: -- DirectSound selected\n");
devId = iDS;
} else if (iMME >= 0) {
jlog("Stat: adin_portaudio: -- MME selected\n");
devId = iMME;
} else {
jlog("Error: adin_portaudio: no device available, try default\n");
devId = -1;
}
*devId_ret = devId;
#else
jlog("Stat: adin_portaudio: use default device\n");
*devId_ret = -1;
#endif
return 0;
}
#endif
/**
* Device initialization: check device capability and open for recording.
*
* @param sfreq [in] required sampling frequency.
* @param dummy [in] a dummy data
*
* @return TRUE on success, FALSE on failure.
*/
boolean
adin_mic_standby(int sfreq, void *dummy)
{
/* store required sampling rate for checking after opening device */
srate = sfreq;
return TRUE;
}
/**
* Open the portaudio device and check capability of the opening device.
*
* @param arg [in] argument: if number, use it as device ID
*
* @return TRUE on success, FALSE on failure.
*/
static boolean
adin_mic_open(char *arg)
{
int sfreq = srate;
PaError err;
#ifdef OLDVER
int frames_per_buffer;
int num_buffer;
#endif
int latency;
char *p;
int devId;
/* set cycle buffer length */
cycle_buffer_len = INPUT_DELAY_SEC * sfreq;
//jlog("Stat: adin_portaudio: INPUT_DELAY_SEC = %d\n", INPUT_DELAY_SEC);
jlog("Stat: adin_portaudio: audio cycle buffer length = %d bytes\n", cycle_buffer_len * sizeof(SP16));
#ifdef OLDVER
/* for safety... */
if (sizeof(SP16) != paInt16) {
jlog("Error: adin_portaudio: SP16 != paInt16 !!\n");
return FALSE;
}
/* set buffer parameter*/
frames_per_buffer = 256;
#endif
/* allocate and init */
current = processed = 0;
speech = (SP16 *)mymalloc(sizeof(SP16) * cycle_buffer_len);
buffer_overflowed = FALSE;
/* get user-specified latency parameter */
latency = 0;
if ((p = getenv("LATENCY_MSEC")) != NULL) {
latency = atoi(p);
jlog("Stat: adin_portaudio: setting latency to %d msec (obtained from LATENCY_MSEC)\n", latency);
}
#ifdef OLDVER
if (latency == 0) {
latency = MAX_FRAGMENT_MSEC;
jlog("Stat: adin_portaudio: setting latency to %d msec\n", latency);
}
num_buffer = sfreq * latency / (frames_per_buffer * 1000);
jlog("Stat: adin_portaudio: framesPerBuffer=%d, NumBuffers(guess)=%d\n",
frames_per_buffer, num_buffer);
jlog("Stat: adin_portaudio: audio I/O Latency = %d msec (data fragment = %d frames)\n",
(frames_per_buffer * num_buffer) * 1000 / sfreq,
(frames_per_buffer * num_buffer));
#endif
/* initialize device */
err = Pa_Initialize();
if (err != paNoError) {
jlog("Error: adin_portaudio: failed to initialize: %s\n", Pa_GetErrorText(err));
return(FALSE);
}
#ifdef CHOOSE_HOST_API
if (arg == NULL) {
// first try to determine the best device
if (auto_determine_device(&devId) == -1) {
jlog("Error: adin_portaudio: failed to choose the specified device\n");
return(FALSE);
}
if (devId == -1) {
// No device has been determined, use the default input device
devId = Pa_GetDefaultInputDevice();
if (devId == paNoDevice) {
jlog("Error: adin_portaudio: no default input device is available or an error was encountered\n");
return FALSE;
}
}
} else {
// use the given number as device id
devId = atoi(arg);
}
// output device information to use
{
const PaDeviceInfo *deviceInfo;
const PaHostApiInfo *apiInfo;
static char buf[256];
deviceInfo = Pa_GetDeviceInfo(devId);
if (deviceInfo == NULL) {
jlog("Error: adin_portaudio: failed to get info for device id %d\n", devId);
return FALSE;
}
apiInfo = Pa_GetHostApiInfo(deviceInfo->hostApi);
if (apiInfo == NULL) {
jlog("Error: adin_portaudio: failed to get API info for device id %d\n", devId);
return FALSE;
}
snprintf(buf, 255, "%s: %s", apiInfo->name, deviceInfo->name);
buf[255] = '\0';
jlog("Stat: adin_portaudio: [%s]\n", buf);
jlog("Stat: adin_portaudio: (you can specify device by \"PORTAUDIO_DEV_NUM=number\"\n");
}
// open the device
{
PaStreamParameters param;
memset( ¶m, 0, sizeof(param));
param.channelCount = 1;
param.device = devId;
param.sampleFormat = paInt16;
if (latency == 0) {
param.suggestedLatency = Pa_GetDeviceInfo(devId)->defaultLowInputLatency;
jlog("Stat: adin_portaudio: try to set default low latency from portaudio: %d msec\n", param.suggestedLatency * 1000.0);
} else {
param.suggestedLatency = latency / 1000.0;
jlog("Stat: adin_portaudio: try to set latency to %d msec\n", param.suggestedLatency * 1000.0);
}
err = Pa_OpenStream(&stream, ¶m, NULL, sfreq,
0, paNoFlag,
Callback, NULL);
if (err != paNoError) {
jlog("Error: adin_portaudio: error in opening stream: %s\n", Pa_GetErrorText(err));
return(FALSE);
}
}
{
const PaStreamInfo *stinfo;
stinfo = Pa_GetStreamInfo(stream);
jlog("Stat: adin_portaudio: latency was set to %f msec\n", stinfo->inputLatency * 1000.0);
}
#else
// Just open the default stream
err = Pa_OpenDefaultStream(&stream, 1, 0, paInt16, sfreq,
#ifdef OLDVER
frames_per_buffer,
num_buffer,
#else
0,
#endif
Callback, NULL);
if (err != paNoError) {
jlog("Error: adin_portaudio: error in opening stream: %s\n", Pa_GetErrorText(err));
return(FALSE);
}
#endif /* CHOOSE_HOST_API */
return(TRUE);
}
/**
* Start recording.
*
* @param pathname [in] path name to open or NULL for default
*
* @return TRUE on success, FALSE on failure.
*/
boolean
adin_mic_begin(char *arg)
{
PaError err;
/* initialize device and open stream */
if (adin_mic_open(arg) == FALSE) {
stream = NULL;
return(FALSE);
}
/* start stream */
err = Pa_StartStream(stream);
if (err != paNoError) {
jlog("Error: adin_portaudio: failed to begin stream: %s\n", Pa_GetErrorText(err));
stream = NULL;
return(FALSE);
}
return(TRUE);
}
/**
* Stop recording.
*
* @return TRUE on success, FALSE on failure.
*/
boolean
adin_mic_end()
{
PaError err;
if (stream == NULL) return(TRUE);
/* stop stream (do not wait callback and buffer flush, stop immediately) */
err = Pa_AbortStream(stream);
if (err != paNoError) {
jlog("Error: adin_portaudio: failed to stop stream: %s\n", Pa_GetErrorText(err));
return(FALSE);
}
/* close stream */
err = Pa_CloseStream(stream);
if (err != paNoError) {
jlog("Error: adin_portaudio: failed to close stream: %s\n", Pa_GetErrorText(err));
return(FALSE);
}
/* terminate library */
err = Pa_Terminate();
if (err != paNoError) {
jlog("Error: adin_portaudio: failed to terminate library: %s\n", Pa_GetErrorText(err));
return(FALSE);
}
stream = NULL;
return TRUE;
}
/**
* @brief Read samples from device
*
* Try to read @a sampnum samples and returns actual number of recorded
* samples currently available. This function will block until
* at least some samples are obtained.
*
* @param buf [out] samples obtained in this function
* @param sampnum [in] wanted number of samples to be read
*
* @return actural number of read samples, -2 if an error occured.
*/
int
adin_mic_read(SP16 *buf, int sampnum)
{
int current_local;
int avail;
int len;
if (buffer_overflowed) {
jlog("Error: adin_portaudio: input buffer OVERFLOW, increase INPUT_DELAY_SEC in sent/speech.h\n");
buffer_overflowed = FALSE;
}
while (current == processed) {
#ifdef DDEBUG
printf("process : current == processed: %d: wait\n", current);
#endif
Pa_Sleep(20); /* wait till some input comes */
if (stream == NULL) return(-1);
}
current_local = current;
#ifdef DDEBUG
printf("process-1: processed=%d, current=%d\n", processed, current_local);
#endif
if (processed < current_local) {
avail = current_local - processed;
if (avail > sampnum) avail = sampnum;
memcpy(buf, &(speech[processed]), avail * sizeof(SP16));
#ifdef DDEBUG
printf("process-2: [%d..%d] %d samples read\n", processed, processed+avail, avail);
#endif
len = avail;
processed += avail;
} else {
avail = cycle_buffer_len - processed;
if (avail > sampnum) avail = sampnum;
memcpy(buf, &(speech[processed]), avail * sizeof(SP16));
#ifdef DDEBUG
printf("process-2-1: [%d..%d] %d samples read\n", processed, processed+avail, avail);
#endif
len = avail;
processed += avail;
if (processed >= cycle_buffer_len) processed -= cycle_buffer_len;
if (sampnum - avail > 0) {
if (sampnum - avail < current_local) {
avail = sampnum - avail;
} else {
avail = current_local;
}
if (avail > 0) {
memcpy(&(buf[len]), &(speech[0]), avail * sizeof(SP16));
#ifdef DDEBUG
printf("process-2-2: [%d..%d] %d samples read (total %d)\n", 0, avail, avail, len + avail);
#endif
len += avail;
processed += avail;
if (processed >= cycle_buffer_len) processed -= cycle_buffer_len;
}
}
}
#ifdef DDEBUG
printf("process-3: new processed: %d\n", processed);
#endif
return len;
}
/**
*
* Function to return current input source device name
*
* @return string of current input device name.
*
*/
char *
adin_mic_input_name()
{
return("Portaudio default device");
}