/* -*- c-basic-offset: 4; indent-tabs-mode: nil -*- */ /* ==================================================================== * Copyright (c) 1999-2001 Carnegie Mellon University. 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. * * This work was supported in part by funding from the Defense Advanced * Research Projects Agency and the National Science Foundation of the * United States of America, and the CMU Sphinx Speech Consortium. * * THIS SOFTWARE IS PROVIDED BY CARNEGIE MELLON UNIVERSITY ``AS IS'' AND * ANY EXPRESSED 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 CARNEGIE MELLON UNIVERSITY * NOR ITS EMPLOYEES 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. * * ==================================================================== * */ /* * rec.c -- low level audio recording for Windows NT/95. * * HISTORY * * 19-Jan-1999 M K Ravishankar (rkm@cs.cmu.edu) at Carnegie Mellon University * Added AD_ return codes. Added ad_open_sps_bufsize(), and * ad_rec_t.n_buf. * * 07-Mar-98 M K Ravishankar (rkm@cs.cmu.edu) at Carnegie Mellon University * Added ad_open_sps(), and made ad_open() call it. * * 10-Jun-96 M K Ravishankar (rkm@cs.cmu.edu) at Carnegie Mellon University * Added ad_rec_t type to all calls. * * 03-Jun-96 M K Ravishankar (rkm@cs.cmu.edu) at Carnegie Mellon University * Created. */ #include #include #include #include "sphinxbase/prim_type.h" #include "sphinxbase/ad.h" #if defined (__CYGWIN__) #include #include #elif (defined(_WIN32) && !defined(GNUWINCE)) || defined(_WIN32_WCE) #include #include #endif typedef struct { HGLOBAL h_whdr; LPWAVEHDR p_whdr; HGLOBAL h_buf; LPSTR p_buf; } ad_wbuf_t; /** * Audio recording structure. */ struct ad_rec_s { HWAVEIN h_wavein; /* "HANDLE" to the audio input device */ ad_wbuf_t *wi_buf; /* Recording buffers provided to system */ int32 n_buf; /* #Recording buffers provided to system */ int32 opened; /* Flag; A/D opened for recording */ int32 recording; int32 curbuf; /* Current buffer with data for application */ int32 curoff; /* Start of data for application in curbuf */ int32 curlen; /* #samples of data from curoff in curbuf */ int32 lastbuf; /* Last buffer containing data after recording stopped */ int32 sps; /* Samples/sec */ int32 bps; /* Bytes/sample */ }; #define DEFAULT_N_WI_BUF 32 /* #Recording bufs */ #define WI_BUFSIZE 2500 /* Samples/buf (Why this specific value?? So that at reasonable sampling rates data is returned frequently enough.) */ #ifdef _WIN32_WCE #include "sphinxbase/ckd_alloc.h" static void wavein_error(char *src, int32 ret) { TCHAR errbuf[512]; wchar_t* werrbuf; size_t len; waveOutGetErrorText(ret, errbuf, sizeof(errbuf)); len = mbstowcs(NULL, errbuf, 0) + 1; werrbuf = ckd_calloc(len, sizeof(*werrbuf)); mbstowcs(werrbuf, errbuf, len); OutputDebugStringW(werrbuf); } #else static void wavein_error(char *src, int32 ret) { char errbuf[1024]; waveInGetErrorText(ret, errbuf, sizeof(errbuf)); fprintf(stderr, "%s error %d: %s\n", src, ret, errbuf); } #endif static void wavein_free_buf(ad_wbuf_t * b) { GlobalUnlock(b->h_whdr); GlobalFree(b->h_whdr); GlobalUnlock(b->h_buf); GlobalFree(b->h_buf); } static int32 wavein_alloc_buf(ad_wbuf_t * b, int32 samples_per_buf) { HGLOBAL h_buf; /* handle to data buffer */ LPSTR p_buf; /* pointer to data buffer */ HGLOBAL h_whdr; /* handle to header */ LPWAVEHDR p_whdr; /* pointer to header */ /* Allocate data buffer */ h_buf = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, samples_per_buf * sizeof(int16)); if (!h_buf) { fprintf(stderr, "GlobalAlloc failed\n"); return -1; } if ((p_buf = GlobalLock(h_buf)) == NULL) { GlobalFree(h_buf); fprintf(stderr, "GlobalLock failed\n"); return -1; } /* Allocate WAVEHDR structure */ h_whdr = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, sizeof(WAVEHDR)); if (h_whdr == NULL) { GlobalUnlock(h_buf); GlobalFree(h_buf); fprintf(stderr, "GlobalAlloc failed\n"); return -1; } if ((p_whdr = GlobalLock(h_whdr)) == NULL) { GlobalUnlock(h_buf); GlobalFree(h_buf); GlobalFree(h_whdr); fprintf(stderr, "GlobalLock failed\n"); return -1; } b->h_buf = h_buf; b->p_buf = p_buf; b->h_whdr = h_whdr; b->p_whdr = p_whdr; p_whdr->lpData = p_buf; p_whdr->dwBufferLength = samples_per_buf * sizeof(int16); p_whdr->dwUser = 0L; p_whdr->dwFlags = 0L; p_whdr->dwLoops = 0L; return 0; } static int32 wavein_enqueue_buf(HWAVEIN h, LPWAVEHDR whdr) { int32 st; if ((st = waveInPrepareHeader(h, whdr, sizeof(WAVEHDR))) != 0) { wavein_error("waveInPrepareHeader", st); return -1; } if ((st = waveInAddBuffer(h, whdr, sizeof(WAVEHDR))) != 0) { wavein_error("waveInAddBuffer", st); return -1; } return 0; } static HWAVEIN wavein_open(int32 samples_per_sec, int32 bytes_per_sample, unsigned int device_id) { WAVEFORMATEX wfmt; int32 st; HWAVEIN h; if (bytes_per_sample != sizeof(int16)) { fprintf(stderr, "bytes/sample != %d\n", sizeof(int16)); return NULL; } wfmt.wFormatTag = WAVE_FORMAT_PCM; wfmt.nChannels = 1; wfmt.nSamplesPerSec = samples_per_sec; wfmt.nAvgBytesPerSec = samples_per_sec * bytes_per_sample; wfmt.nBlockAlign = bytes_per_sample; wfmt.wBitsPerSample = 8 * bytes_per_sample; /* There should be a check here for a device of the desired type; later... */ st = waveInOpen((LPHWAVEIN) & h, device_id, (LPWAVEFORMATEX) & wfmt, (DWORD) 0L, 0L, (DWORD) CALLBACK_NULL); if (st != 0) { wavein_error("waveInOpen", st); return NULL; } return h; } static int32 wavein_close(ad_rec_t * r) { int32 i, st; /* Unprepare all buffers; multiple unprepares of the same buffer are benign */ for (i = 0; i < r->n_buf; i++) { /* Unpreparing an unprepared buffer, on the other hand, fails on Win98/WinME, though this is not documented - dhuggins@cs, 2004-07-14 */ if (!(r->wi_buf[i].p_whdr->dwFlags & WHDR_PREPARED)) continue; st = waveInUnprepareHeader(r->h_wavein, r->wi_buf[i].p_whdr, sizeof(WAVEHDR)); if (st != 0) { wavein_error("waveInUnprepareHeader", st); return -1; } } /* Free buffers */ for (i = 0; i < r->n_buf; i++) wavein_free_buf(&(r->wi_buf[i])); free(r->wi_buf); if ((st = waveInClose(r->h_wavein)) != 0) { wavein_error("waveInClose", st); return -1; } free(r); return 0; } ad_rec_t * ad_open_sps_bufsize(int32 sps, int32 bufsize_msec, unsigned int device_id) { ad_rec_t *r; int32 i, j; HWAVEIN h; if ((h = wavein_open(sps, sizeof(int16), device_id)) == NULL) return NULL; if ((r = (ad_rec_t *) malloc(sizeof(ad_rec_t))) == NULL) { fprintf(stderr, "malloc(%d) failed\n", sizeof(ad_rec_t)); waveInClose(h); return NULL; } r->n_buf = ((sps * bufsize_msec) / 1000) / WI_BUFSIZE; if (r->n_buf < DEFAULT_N_WI_BUF) r->n_buf = DEFAULT_N_WI_BUF; printf("Allocating %d buffers of %d samples each\n", r->n_buf, WI_BUFSIZE); if ((r->wi_buf = (ad_wbuf_t *) calloc(r->n_buf, sizeof(ad_wbuf_t))) == NULL) { fprintf(stderr, "calloc(%d,%d) failed\n", r->n_buf, sizeof(ad_wbuf_t)); free(r); waveInClose(h); return NULL; } for (i = 0; i < r->n_buf; i++) { if (wavein_alloc_buf(&(r->wi_buf[i]), WI_BUFSIZE) < 0) { for (j = 0; j < i; j++) wavein_free_buf(&(r->wi_buf[j])); free(r->wi_buf); free(r); waveInClose(h); return NULL; } } r->h_wavein = h; r->opened = 1; r->recording = 0; r->curbuf = r->n_buf - 1; /* current buffer with data for application */ r->curlen = 0; /* #samples in curbuf remaining to be consumed */ r->lastbuf = r->curbuf; r->sps = sps; r->bps = sizeof(int16); /* HACK!! Hardwired value for bytes/sec */ return r; } ad_rec_t * ad_open_dev(const char *dev, int32 sps) { unsigned int device_num = WAVE_MAPPER; /* Convert given deviceId parameter to int */ if (dev != NULL && sscanf(dev, "%d", &device_num) != EOF) { if (device_num >= waveInGetNumDevs()) { device_num = WAVE_MAPPER; } } return (ad_open_sps_bufsize (sps, WI_BUFSIZE * DEFAULT_N_WI_BUF * 1000 / sps, device_num)); } ad_rec_t * ad_open_sps(int32 sps) { return (ad_open_sps_bufsize (sps, WI_BUFSIZE * DEFAULT_N_WI_BUF * 1000 / sps, WAVE_MAPPER)); } ad_rec_t * ad_open(void) { return (ad_open_sps(DEFAULT_SAMPLES_PER_SEC)); /* HACK!! Rename this constant */ } int32 ad_close(ad_rec_t * r) { if (!r->opened) return AD_ERR_NOT_OPEN; if (r->recording) if (ad_stop_rec(r) < 0) return AD_ERR_WAVE; if (wavein_close(r) < 0) return AD_ERR_WAVE; return 0; } int32 ad_start_rec(ad_rec_t * r) { int32 i; if ((!r->opened) || r->recording) return -1; for (i = 0; i < r->n_buf; i++) if (wavein_enqueue_buf(r->h_wavein, r->wi_buf[i].p_whdr) < 0) return AD_ERR_WAVE; r->curbuf = r->n_buf - 1; /* current buffer with data for application */ r->curlen = 0; /* #samples in curbuf remaining to be consumed */ if (waveInStart(r->h_wavein) != 0) return AD_ERR_WAVE; r->recording = 1; return 0; } int32 ad_stop_rec(ad_rec_t * r) { int32 i, st; if ((!r->opened) || (!r->recording)) return -1; if (waveInStop(r->h_wavein) != 0) return AD_ERR_WAVE; if ((st = waveInReset(r->h_wavein)) != 0) { wavein_error("waveInReset", st); return AD_ERR_WAVE; } /* Wait until all buffers marked done */ for (i = 0; i < r->n_buf; i++) while (!(r->wi_buf[i].p_whdr->dwFlags & WHDR_DONE)); if ((r->lastbuf = r->curbuf - 1) < 0) r->lastbuf = r->n_buf - 1; r->recording = 0; return 0; } int32 ad_read(ad_rec_t * r, int16 * buf, int32 max) { int32 t, st, len; LPWAVEHDR whdr; int16 *sysbufp; if (!r->opened) return AD_ERR_NOT_OPEN; /* Check if all recorded data exhausted */ if ((!r->recording) && (r->curbuf == r->lastbuf) && (r->curlen == 0)) return AD_EOF; len = 0; while (max > 0) { /* Look for next buffer with recording data */ if (r->curlen == 0) { /* No current buffer with data; get next buffer in sequence if available */ t = r->curbuf + 1; if (t >= r->n_buf) t = 0; if (!(r->wi_buf[t].p_whdr->dwFlags & WHDR_DONE)) return len; r->curbuf = t; r->curlen = r->wi_buf[t].p_whdr->dwBytesRecorded >> 1; r->curoff = 0; } /* Copy data from curbuf to buf */ whdr = r->wi_buf[r->curbuf].p_whdr; t = (max < r->curlen) ? max : r->curlen; /* #Samples to copy */ if (t > 0) { sysbufp = (int16 *) (whdr->lpData); memcpy(buf, sysbufp + r->curoff, t * sizeof(int16)); buf += t; max -= t; r->curoff += t; r->curlen -= t; len += t; } /* If curbuf empty recycle it to system if still recording */ if (r->curlen == 0) { if (r->recording) { /* Return empty buffer to system */ st = waveInUnprepareHeader(r->h_wavein, whdr, sizeof(WAVEHDR)); if (st != 0) { wavein_error("waveInUnprepareHeader", st); return AD_ERR_WAVE; } if (wavein_enqueue_buf(r->h_wavein, whdr) < 0) return AD_ERR_WAVE; } else if (r->curbuf == r->lastbuf) { return len; } } } return len; }