/* * * ao_wmm.c * * Copyright (C) Benjamin Gerard - March 2007 * * This file is part of libao, a cross-platform library. See * README for a history of this source code. * * libao is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * libao is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Make; see the file COPYING. If not, write to * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. * */ //#define PREPARE_EACH #define _CRT_SECURE_NO_DEPRECATE #include #include #include #include #include #include #include #include "ao/ao.h" /* #include "ao/plugin.h" */ #define GALLOC_WVHD_TYPE (GHND) #define GALLOC_DATA_TYPE (GHND) static int debug_flag = 1; static void debug(const char * fmt, ...) { if (debug_flag) { va_list list; va_start(list,fmt); vfprintf(stderr,fmt,list); va_end(list); } } static const char * mmerror(MMRESULT mmrError) { static char mmbuffer[1024]; int len; sprintf(mmbuffer,"mm:%d ",(int)mmrError); len = (int)strlen(mmbuffer); waveOutGetErrorText(mmrError, mmbuffer+len, sizeof(mmbuffer)-len); mmbuffer[sizeof(mmbuffer)-1] = 0; return mmbuffer; } static char * ao_wmm_options[] = {"dev","id"}; static ao_info ao_wmm_info = { /* type */ AO_TYPE_LIVE, /* name */ "WMM audio driver output ", /* short-name */ "wmm", /* author */ "Benjamin Gerard ", /* comment */ "Outputs audio to the Windows MultiMedia driver.", /* prefered format */ AO_FMT_LITTLE, /* priority */ 20, /* options */ ao_wmm_options, /* # of options */ sizeof(ao_wmm_options)/sizeof(*ao_wmm_options) }; typedef struct { WAVEHDR wh; /* waveheader */ char * data; /* sample data ptr */ int idx; /* index of this header */ int count; /* current byte count */ int length; /* size of data */ int sent; /* set when header is sent to device */ } myWH_t; typedef struct ao_wmm_internal { UINT id; /* device id */ HWAVEOUT hwo; /* waveout handler */ WAVEOUTCAPS caps; /* device caps */ WAVEFORMATEX wavefmt; /* sample format */ int opened; /* device has been opened */ int prepared; /* waveheaders have been prepared */ int blocks; /* number of blocks (wave headers) */ int splPerBlock; /* sample per blocks. */ int msPerBlock; /* millisecond per block (approx.) */ void * bigbuffer; /* Allocated buffer for waveheaders and sound data */ myWH_t * wh; /* Pointer to waveheaders in bigbuffer */ BYTE * spl; /* Pointer to sound data in bigbuffer */ int sent_blocks; /* Number of waveheader sent (not ack). */ int full_blocks; /* Number of waveheader full (ready to send). */ int widx; /* Index to the block being currently filled. */ int ridx; /* Index to the block being sent. */ } ao_wmm_internal; int ao_wmm_test(void) { debug("ao_wmm_test() {} => [success]\n"); return 1; /* This plugin works in default mode */ } ao_info *ao_wmm_driver_info(void) { debug("ao_wmm_driver_info() {} => [success]\n"); return &ao_wmm_info; } int ao_wmm_set_option(ao_device *device, const char *key, const char *value) { ao_wmm_internal *internal = (ao_wmm_internal *) device->internal; int res = 0; debug("ao_wmm_set_option(%s,%s) {\n", key, value); if (!strcmp(key, "dev")) { if (!strcmp(value,"default")) { key = "id"; value = "0"; } else { WAVEOUTCAPS caps; int i, max = waveOutGetNumDevs(); debug("ao_wmm_set_option: search device %s among %d\n", value, max); for (i=0; i [%sfound]\n",i,caps.szPname, caps.vDriverVersion>>8,caps.vDriverVersion&255, res?"":"not "); if (res) { internal->id = i; internal->caps = caps; break; } } else { debug("ao_wmm_set_option: waveOutGetDevCaps(%d) => [error]\n",i); debug(" => %s\n", mmerror(mmres)); } } goto finish; } } if (!strcmp(key,"id")) { MMRESULT mmres; WAVEOUTCAPS caps; int id = strtol(value,0,0); int max = waveOutGetNumDevs(); debug("ao_wmm_set_option: search device %d among %d\n", id, max); if (id >= 0 && id <= max) { if (id-- == 0) { debug("ao_wmm_set_option: set default wavemapper\n"); id = WAVE_MAPPER; } mmres = waveOutGetDevCaps(id, &caps, sizeof(caps)); if (mmres == MMSYSERR_NOERROR) { res = 1; debug("ao_wmm_set_option:\n" " id : %d\n" " name : %s\n" " ver : %d.%d\n", id,caps.szPname,caps.vDriverVersion>>8,caps.vDriverVersion&255); internal->id = id; internal->caps = caps; } else { debug(" waveOutGetDevCaps(%d) => %s\n",id, mmerror(mmres)); } } } finish: debug("} ao_wmm_set_option() => [%s]\n", res?"success":"error"); return res; } int ao_wmm_device_init(ao_device *device) { ao_wmm_internal *internal; int res; debug("ao_wmm_device_init() {\n"); internal = (ao_wmm_internal *) malloc(sizeof(ao_wmm_internal)); device->internal = internal; if (internal != NULL) { memset(internal,0,sizeof(ao_wmm_internal)); internal->id = WAVE_MAPPER; internal->blocks = 32; internal->splPerBlock = 512; /* set default device */ ao_wmm_set_option(device,"id","0"); } res = internal != NULL; debug("} ao_wmm_device_init() => [%s]\n",res?"success":"error" ); return res; } #if 0 static void CALLBACK waveOutProc(HWAVEOUT hwo, UINT uMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2) { ao_device *device = (ao_device *) dwInstance; ao_wmm_internal *internal = (ao_wmm_internal *) device->internal; switch (uMsg) { case WOM_OPEN: /* Sent when the device is opened using the waveOutOpen function. */ { debug("WOM_OPEN\n"); } break; case WOM_CLOSE: /* Sent when the device is closed using the waveOutClose function. */ { debug("WOM_CLOSE\n"); } break; case WOM_DONE: /* Sent when the device driver is finished with a data block sent using the waveOutWrite function. */ { LPWAVEHDR lpwvhdr = (LPWAVEHDR) dwParam1; int me = lpwvhdr - internal->wh; debug("WOM_DONE #%d\n",me); lpwvhdr->dwBytesRecorded = 0; } break; default: { debug("WOM_???\n"); } break; } } #endif static int _ao_open_device(ao_device *device) { ao_wmm_internal *internal = (ao_wmm_internal *) device->internal; int res; MMRESULT mmres; debug("_ao_open_device() {\n"); mmres = waveOutOpen(&internal->hwo, internal->id, &internal->wavefmt, (DWORD_PTR)0/* waveOutProc */, (DWORD_PTR)device, CALLBACK_NULL/* |WAVE_FORMAT_DIRECT */|WAVE_ALLOWSYNC); debug("_ao_open_device: waveOutOpen\n" " id : %d\n" " channels : %d\n" " bits : %d\n" " rate : %d => [%s]\n", internal->id, internal->wavefmt.nChannels, internal->wavefmt.wBitsPerSample, internal->wavefmt.nSamplesPerSec, mmres == MMSYSERR_NOERROR?"success":"error"); if (mmres == MMSYSERR_NOERROR) { UINT id; if (MMSYSERR_NOERROR == waveOutGetID(internal->hwo,&id)) { debug("_ao_open_device: waveOutGetID() => [%d]\n",id); internal->id = id; } } else { debug(" waveOutOpen(%d)\n => %s\n", internal->id, mmerror(mmres)); } res = (mmres == MMSYSERR_NOERROR); debug("} _ao_open_device() => [%s]\n",res?"success":"error"); return res; } static int _ao_close_device(ao_device *device) { ao_wmm_internal * internal = (ao_wmm_internal *) device->internal; int res; MMRESULT mmres; debug("_ao_close_device() {\n"); mmres = waveOutClose(internal->hwo); if (mmres != MMSYSERR_NOERROR) { debug(" waveOutClose(%d)\n => %s\n", internal->id, mmerror(mmres)); } res = (mmres == MMSYSERR_NOERROR); debug("} _ao_close_device() => [%s]\n",res?"success":"error"); return res; } static int _ao_alloc_wave_headers(ao_device *device) { ao_wmm_internal *internal = (ao_wmm_internal *) device->internal; int bytesPerBlock = internal->wavefmt.nBlockAlign * internal->splPerBlock; /* int bytes = internal->blocks * (sizeof(WAVEHDR) + bytesPerBlock); */ int bytes = internal->blocks * (sizeof(*internal->wh) + bytesPerBlock); int res; MMRESULT mmres; debug("_ao_alloc_wave_headers() {\n" " blocks : %d\n" " bytes/blocks : %d\n" " total : %d\n",internal->blocks,bytesPerBlock,bytes); internal->bigbuffer = malloc(bytes); if (internal->bigbuffer != NULL) { int i; BYTE * b; memset(internal->bigbuffer,0,bytes); internal->wh = internal->bigbuffer; internal->spl = (LPBYTE) (internal->wh+internal->blocks); for (i=0, b=internal->spl; iblocks; ++i, b+=bytesPerBlock) { internal->wh[i].data = b; internal->wh[i].wh.lpData = internal->wh[i].data; internal->wh[i].length = bytesPerBlock; internal->wh[i].wh.dwBufferLength = internal->wh[i].length; internal->wh[i].wh.dwUser = (DWORD_PTR)device; mmres = waveOutPrepareHeader(internal->hwo, &internal->wh[i].wh,sizeof(WAVEHDR)); if (MMSYSERR_NOERROR != mmres) { debug("_ao_alloc_wave_headers:" " waveOutPrepareHeader(%d) => [error]\n",i); debug(" => %s\n", mmerror(mmres)); break; } } if (iblocks) { while (--i >= 0) { waveOutUnprepareHeader(internal->hwo, &internal->wh[i].wh,sizeof(WAVEHDR)); } free(internal->bigbuffer); internal->wh = 0; internal->spl = 0; internal->bigbuffer = 0; } else { /* all ok ! */ } } else { debug("_ao_alloc_wave_headers: malloc() => [error]\n"); } res = (internal->bigbuffer != NULL); debug("} _ao_alloc_wave_headers() => [%s]\n", res?"success":"error"); return res; } static int _ao_get_free_block(ao_device * device); static int _ao_wait_wave_headers(ao_device *device, int wait_all) { ao_wmm_internal *internal = (ao_wmm_internal *) device->internal; int res = 1; debug("_ao_wait_wave_headers: wait for %d blocks (%swait all)\n", internal->sent_blocks,wait_all?"":"not "); while (internal->sent_blocks > 0) { int n; _ao_get_free_block(device); n = internal->sent_blocks; if (n > 0) { unsigned int ms = (internal->msPerBlock>>1)+1; if (wait_all) ms *= n; debug("_ao_wait_wave_headers: sleep for %ums wait on %d blocks\n",ms, internal->sent_blocks); Sleep(ms); } } res &= !internal->sent_blocks; debug("_ao_wait_wave_headers: => [%s]\n",res?"success":"error"); return res; } static int _ao_free_wave_headers(ao_device *device) { ao_wmm_internal *internal = (ao_wmm_internal *) device->internal; MMRESULT mmres; int res = 1; debug("_ao_free_wave_headers() {\n"); if (internal->wh) { int i; /* Reset so we dont need to wait ... Just a satefy net * since _ao_wait_wave_headers() has been called once before. */ mmres = waveOutReset(internal->hwo); debug(" waveOutReset(%d) => %s\n", internal->id, mmerror(mmres)); /* Wait again to be sure reseted waveheaders has been released. */ _ao_wait_wave_headers(device,0); for (i=internal->blocks; --i>=0; ) { mmres = waveOutUnprepareHeader(internal->hwo, &internal->wh[i].wh,sizeof(WAVEHDR)); if (mmres != MMSYSERR_NOERROR) { debug("_ao_free_wave_headers:" " waveOutUnprepareHeader(%d)\n",i); debug(" => %s\n", mmerror(mmres)); } res &= mmres == MMSYSERR_NOERROR; } internal->wh = 0; internal->spl = 0; } debug("} _ao_alloc_wave_headers() => [%s]\n", res?"success":"error"); return res; } /* * open the audio device for writing to */ int ao_wmm_open(ao_device * device, ao_sample_format * format) { ao_wmm_internal *internal = (ao_wmm_internal *) device->internal; int res = 0; WAVEFORMATEX wavefmt; debug("ao_wmm_open(%p,%p) {\n",device,format); debug("ao_wmm_open:\n" " channels : %d\n" " bits : %d\n" " rate : %d\n" " format : %d(%s)\n", format->channels,format->bits,format->rate,format->byte_format, format->byte_format==AO_FMT_LITTLE ?"little" :(format->byte_format==AO_FMT_NATIVE ?"native" :(format->byte_format==AO_FMT_BIG?"big":"unknown"))); if(internal->opened) { debug("ao_wmm_open: already opened\n"); goto error_no_close; } /* Force LITTLE as specified by WIN32 API */ format->byte_format = AO_FMT_LITTLE; device->driver_byte_format = AO_FMT_LITTLE; if (! (format->channels == 1 || format->channels == 2) || ! (format->bits == 8 || format->bits == 16) || ! (format->rate >= 6000 && format->rate <= 50000) ) { debug("ao_wmm_open: weird format\n"); goto error; } /* $$$ WMM 8 bit samples are unsigned... Not sure for ao ... */ /* Make sample format */ memset(&wavefmt,0,sizeof(wavefmt)); wavefmt.wFormatTag = WAVE_FORMAT_PCM; wavefmt.nChannels = format->channels; wavefmt.wBitsPerSample = format->bits; wavefmt.nSamplesPerSec = format->rate; wavefmt.nBlockAlign = (wavefmt.wBitsPerSample>>3)*wavefmt.nChannels; wavefmt.nAvgBytesPerSec = wavefmt.nSamplesPerSec*wavefmt.nBlockAlign; wavefmt.cbSize = 0; internal->wavefmt = wavefmt; /* $$$ later this should be optionnal parms */ internal->blocks = 64; internal->splPerBlock = 512; internal->msPerBlock = (internal->splPerBlock * 1000 + format->rate - 1) / format->rate; /* Open device */ if(!_ao_open_device(device)) { debug("ao_wmm_open: _ao_open_device() => [error]\n"); goto error; } internal->opened = 1; /* Allocate buffers */ if (!_ao_alloc_wave_headers(device)) { debug("ao_wmm_open: _ao_alloc_wave_headers() => [error]\n"); goto error; } internal->prepared = 1; res = 1; error: if (!res) { if (internal->prepared) { _ao_free_wave_headers(device); internal->prepared = 0; } if (internal->opened) { _ao_close_device(device); internal->opened = 0; } } error_no_close: debug("} ao_wmm_open() => [%s]\n",res?"success":"error"); return res; } /* Send a block to audio hardware */ static int _ao_send_block(ao_device *device, const int idx) { ao_wmm_internal * internal = (ao_wmm_internal *) device->internal; MMRESULT mmres; /* Satanity checks */ if (internal->wh[idx].sent) { debug("_ao_send_block: block %d marked SENT\n",idx); return 0; } if (!!(internal->wh[idx].wh.dwFlags & WHDR_DONE)) { debug("_ao_send_block: block %d marked DONE\n",idx); return 0; } /* count <= 0, just pretend it's been sent */ if (internal->wh[idx].count <= 0) { internal->wh[idx].sent = 2; /* set with 2 so we can track these special cases */ internal->wh[idx].wh.dwFlags |= WHDR_DONE; ++internal->sent_blocks; return 1; } internal->wh[idx].wh.dwBufferLength = internal->wh[idx].count; internal->wh[idx].count = 0; mmres = waveOutWrite(internal->hwo, &internal->wh[idx].wh, sizeof(WAVEHDR)); internal->wh[idx].sent = (mmres == MMSYSERR_NOERROR); /*&& !(internal->wh[idx].wh.dwFlags & WHDR_DONE);*/ internal->sent_blocks += internal->wh[idx].sent; if (mmres != MMSYSERR_NOERROR) { debug("_ao_send_block:: waveOutWrite(%d)\n",idx); debug(" => %s\n",mmerror(mmres)); } return mmres == MMSYSERR_NOERROR; } /* Get idx of next free block. */ static int _ao_get_free_block(ao_device * device) { ao_wmm_internal * internal = (ao_wmm_internal *) device->internal; const int idx = internal->widx; int ridx = internal->ridx; while (internal->wh[ridx].sent && !!(internal->wh[ridx].wh.dwFlags & WHDR_DONE)) { /* block successfully sent to hardware, release it */ /*debug("_ao_get_free_block: release block %d\n",ridx);*/ internal->wh[ridx].sent = 0; internal->wh[ridx].wh.dwFlags &= ~WHDR_DONE; --internal->full_blocks; if (internal->full_blocks<0) { debug("_ao_get_free_block: internal error with full block counter\n"); internal->full_blocks = 0; } --internal->sent_blocks; if (internal->sent_blocks<0) { debug("_ao_get_free_block: internal error with sent block counter\n"); internal->sent_blocks = 0; } if (++ridx >= internal->blocks) ridx = 0; } internal->ridx = ridx; return internal->wh[idx].sent ? -1 : idx; } /* * play the sample to the already opened file descriptor */ int ao_wmm_play(ao_device *device, const char *output_samples, uint_32 num_bytes) { int ret = 1; ao_wmm_internal *internal = (ao_wmm_internal *) device->internal; while(ret && num_bytes > 0) { int n; const int idx = _ao_get_free_block(device); if (idx == -1) { /* debug("sleep %dms, rem %d bytes\n",internal->msPerBlock,num_bytes); */ Sleep(internal->msPerBlock); continue; } /* Get free bytes in the block */ n = internal->wh[idx].wh.dwBufferLength - internal->wh[idx].count; /* debug("free in block %d : %d/%d\n", */ /* idx,n,internal->wh[idx].dwBufferLength); */ /* Get amount to copy */ if (n > (int)num_bytes) { n = num_bytes; } /* debug("copy = %d\n",n); */ /* Do copy */ CopyMemory((char*)internal->wh[idx].wh.lpData + internal->wh[idx].count, output_samples, n); /* Updates pointers and counters */ output_samples += n; num_bytes -= n; /* debug("rem = %d\n",num_bytes); */ internal->wh[idx].count += n; /* Is this block full ? */ if (internal->wh[idx].count == internal->wh[idx].wh.dwBufferLength) { ++internal->full_blocks; /* debug("blocks %d full, total:%d\n",internal->widx,internal->full_blocks); */ if (++internal->widx == internal->blocks) { internal->widx = 0; } ret = _ao_send_block(device,idx); } } /* debug("ao_wmm_play => %d rem => [%s]\n",num_bytes,ret?"success":"error"); */ return ret; } int ao_wmm_close(ao_device *device) { ao_wmm_internal *internal = (ao_wmm_internal *) device->internal; int ret = 0; debug("ao_wmm_close() {\n"); if (internal->opened && internal->prepared) { _ao_wait_wave_headers(device, 1); } if (internal->prepared) { ret = _ao_free_wave_headers(device); internal->prepared = 0; } if (internal->opened) { ret = _ao_close_device(device); internal->opened = 0; } debug("} ao_wmm_close() => [%s]\n",ret?"success":"error"); return ret; } void ao_wmm_device_clear(ao_device *device) { ao_wmm_internal *internal = (ao_wmm_internal *) device->internal; debug("ao_wmm_device_clear() {\n"); if (internal->bigbuffer) { free(internal->bigbuffer); internal->bigbuffer = NULL; } free(internal); debug("} ao_wmm_device_clear()\n"); } ao_functions ao_wmm = { ao_wmm_test, ao_wmm_driver_info, ao_wmm_device_init, ao_wmm_set_option, ao_wmm_open, ao_wmm_play, ao_wmm_close, ao_wmm_device_clear };