From 9c0e19a3be2288db79e2502e5fa450c3e20a668d Mon Sep 17 00:00:00 2001 From: Guenter Geiger Date: Fri, 9 May 2003 16:04:00 +0000 Subject: This commit was generated by cvs2svn to compensate for changes in r610, which included commits to RCS files with non-trunk default branches. svn path=/trunk/; revision=611 --- pd/portaudio/pa_win_wmme/pa_win_wmme.c | 2672 ++++++++++++++++++++++++++++++++ 1 file changed, 2672 insertions(+) create mode 100644 pd/portaudio/pa_win_wmme/pa_win_wmme.c (limited to 'pd/portaudio/pa_win_wmme/pa_win_wmme.c') diff --git a/pd/portaudio/pa_win_wmme/pa_win_wmme.c b/pd/portaudio/pa_win_wmme/pa_win_wmme.c new file mode 100644 index 00000000..2b1cc57d --- /dev/null +++ b/pd/portaudio/pa_win_wmme/pa_win_wmme.c @@ -0,0 +1,2672 @@ +/* + * $Id: pa_win_wmme.c,v 1.6.2.44 2003/03/18 14:27:58 rossbencina Exp $ + * pa_win_wmme.c + * Implementation of PortAudio for Windows MultiMedia Extensions (WMME) + * + * PortAudio Portable Real-Time Audio Library + * Latest Version at: http://www.portaudio.com + * + * Authors: Ross Bencina and Phil Burk + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +/* Modification History: + PLB = Phil Burk + JM = Julien Maillard + RDB = Ross Bencina + PLB20010402 - sDevicePtrs now allocates based on sizeof(pointer) + PLB20010413 - check for excessive numbers of channels + PLB20010422 - apply Mike Berry's changes for CodeWarrior on PC + including conditional inclusion of memory.h, + and explicit typecasting on memory allocation + PLB20010802 - use GlobalAlloc for sDevicesPtr instead of PaHost_AllocFastMemory + PLB20010816 - pass process instead of thread to SetPriorityClass() + PLB20010927 - use number of frames instead of real-time for CPULoad calculation. + JM20020118 - prevent hung thread when buffers underflow. + PLB20020321 - detect Win XP versus NT, 9x; fix DBUG typo; removed init of CurrentCount + RDB20020411 - various renaming cleanups, factored streamData alloc and cpu usage init + RDB20020417 - stopped counting WAVE_MAPPER when there were no real devices + refactoring, renaming and fixed a few edge case bugs + RDB20020531 - converted to V19 framework + ** NOTE maintanance history is now stored in CVS ** +*/ + +/** @file + + @todo Handle case where user supplied full duplex buffer sizes are not compatible + (must be common multiples) + + @todo Fix buffer catch up code, can sometimes get stuck + + @todo Implement "close sample rate matching" if needed - is this really needed + in mme? + + @todo Investigate supporting host buffer formats > 16 bits + + @todo Implement buffer size and number of buffers code, + this code should generate defaults the way the old code did + + @todo implement underflow/overflow streamCallback statusFlags, paNeverDropInput. + + @todo Fix fixmes + + @todo implement initialisation of PaDeviceInfo default*Latency fields (currently set to 0.) + + @todo implement ReadStream, WriteStream, GetStreamReadAvailable, GetStreamWriteAvailable + + @todo implement IsFormatSupported + + @todo implement PaDeviceInfo.defaultSampleRate; + + @todo define UNICODE and _UNICODE in the project settings and see what breaks +*/ + +#include +#include +#include +#include +#include +#include +#include +/* PLB20010422 - "memory.h" doesn't work on CodeWarrior for PC. Thanks Mike Berry for the mod. */ +#ifndef __MWERKS__ +#include +#include +#endif /* __MWERKS__ */ + +#include "portaudio.h" +#include "pa_trace.h" +#include "pa_util.h" +#include "pa_allocation.h" +#include "pa_hostapi.h" +#include "pa_stream.h" +#include "pa_cpuload.h" +#include "pa_process.h" + +#include "pa_win_wmme.h" + +/************************************************* Constants ********/ + +/* Switches for debugging. */ +#define PA_SIMULATE_UNDERFLOW_ (0) /* Set to one to force an underflow of the output buffer. */ + +#define PA_USE_HIGH_LATENCY_ (0) /* For debugging glitches. */ + +#if PA_USE_HIGH_LATENCY_ + #define PA_MIN_MSEC_PER_HOST_BUFFER_ (100) + #define PA_MAX_MSEC_PER_HOST_BUFFER_ (300) /* Do not exceed unless user buffer exceeds */ + #define PA_MIN_NUM_HOST_BUFFERS_ (4) + #define PA_MAX_NUM_HOST_BUFFERS_ (16) /* OK to exceed if necessary */ + #define PA_WIN_9X_LATENCY_ (400) +#else + #define PA_MIN_MSEC_PER_HOST_BUFFER_ (10) + #define PA_MAX_MSEC_PER_HOST_BUFFER_ (100) /* Do not exceed unless user buffer exceeds */ + #define PA_MIN_NUM_HOST_BUFFERS_ (3) + #define PA_MAX_NUM_HOST_BUFFERS_ (16) /* OK to exceed if necessary */ + #define PA_WIN_9X_LATENCY_ (200) +#endif + +#define PA_MIN_TIMEOUT_MSEC_ (1000) + +/* +** Use higher latency for NT because it is even worse at real-time +** operation than Win9x. +*/ +#define PA_WIN_NT_LATENCY_ (PA_WIN_9X_LATENCY_ * 2) +#define PA_WIN_WDM_LATENCY_ (PA_WIN_9X_LATENCY_) + + +static const char constInputMapperSuffix_[] = " - Input"; +static const char constOutputMapperSuffix_[] = " - Output"; + + +typedef struct PaWinMmeStream PaWinMmeStream; /* forward reference */ + +/* prototypes for functions declared in this file */ + +PaError PaWinMme_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex hostApiIndex ); +static void Terminate( struct PaUtilHostApiRepresentation *hostApi ); +static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, + PaStream** stream, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamFlags streamFlags, + PaStreamCallback *streamCallback, + void *userData ); +static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate ); +static PaError CloseStream( PaStream* stream ); +static PaError StartStream( PaStream *stream ); +static PaError StopStream( PaStream *stream ); +static PaError AbortStream( PaStream *stream ); +static PaError IsStreamStopped( PaStream *s ); +static PaError IsStreamActive( PaStream *stream ); +static PaTime GetStreamTime( PaStream *stream ); +static double GetStreamCpuLoad( PaStream* stream ); +static PaError ReadStream( PaStream* stream, void *buffer, unsigned long frames ); +static PaError WriteStream( PaStream* stream, void *buffer, unsigned long frames ); +static signed long GetStreamReadAvailable( PaStream* stream ); +static signed long GetStreamWriteAvailable( PaStream* stream ); + +static PaError UpdateStreamTime( PaWinMmeStream *stream ); + +/* macros for setting last host error information */ + +#ifdef UNICODE + +#define PA_MME_SET_LAST_WAVEIN_ERROR( mmresult ) \ + { \ + wchar_t mmeErrorTextWide[ MAXERRORLENGTH ]; \ + char mmeErrorText[ MAXERRORLENGTH ]; \ + waveInGetErrorText( mmresult, mmeErrorTextWide, MAXERRORLENGTH ); \ + WideCharToMultiByte( CP_ACP, WC_COMPOSITECHECK | WC_DEFAULTCHAR,\ + mmeErrorTextWide, -1, mmeErrorText, MAXERRORLENGTH, NULL, NULL ); \ + PaUtil_SetLastHostErrorInfo( paMME, mmresult, mmeErrorText ); \ + } + +#define PA_MME_SET_LAST_WAVEOUT_ERROR( mmresult ) \ + { \ + wchar_t mmeErrorTextWide[ MAXERRORLENGTH ]; \ + char mmeErrorText[ MAXERRORLENGTH ]; \ + waveOutGetErrorText( mmresult, mmeErrorTextWide, MAXERRORLENGTH ); \ + WideCharToMultiByte( CP_ACP, WC_COMPOSITECHECK | WC_DEFAULTCHAR,\ + mmeErrorTextWide, -1, mmeErrorText, MAXERRORLENGTH, NULL, NULL ); \ + PaUtil_SetLastHostErrorInfo( paMME, mmresult, mmeErrorText ); \ + } + +#else /* !UNICODE */ + +#define PA_MME_SET_LAST_WAVEIN_ERROR( mmresult ) \ + { \ + char mmeErrorText[ MAXERRORLENGTH ]; \ + waveInGetErrorText( mmresult, mmeErrorText, MAXERRORLENGTH ); \ + PaUtil_SetLastHostErrorInfo( paMME, mmresult, mmeErrorText ); \ + } + +#define PA_MME_SET_LAST_WAVEOUT_ERROR( mmresult ) \ + { \ + char mmeErrorText[ MAXERRORLENGTH ]; \ + waveOutGetErrorText( mmresult, mmeErrorText, MAXERRORLENGTH ); \ + PaUtil_SetLastHostErrorInfo( paMME, mmresult, mmeErrorText ); \ + } + +#endif /* UNICODE */ + + +static void PaMme_SetLastSystemError( DWORD errorCode ) +{ + LPVOID lpMsgBuf; + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, + NULL, + errorCode, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR) &lpMsgBuf, + 0, + NULL + ); + PaUtil_SetLastHostErrorInfo( paMME, errorCode, lpMsgBuf ); + LocalFree( lpMsgBuf ); +} + +#define PA_MME_SET_LAST_SYSTEM_ERROR( errorCode ) \ + PaMme_SetLastSystemError( errorCode ) + + +/* PaWinMmeHostApiRepresentation - host api datastructure specific to this implementation */ + +typedef struct +{ + PaUtilHostApiRepresentation inheritedHostApiRep; + PaUtilStreamInterface callbackStreamInterface; + PaUtilStreamInterface blockingStreamInterface; + + PaUtilAllocationGroup *allocations; + + int numInputDevices, numOutputDevices; + + /** winMmeDeviceIds is an array of WinMme device ids. + fields in the range [0, numInputDevices) are input device ids, + and [numInputDevices, numInputDevices + numOutputDevices) are output + device ids. + */ + int *winMmeDeviceIds; +} +PaWinMmeHostApiRepresentation; + + +/************************************************************************* + * Returns recommended device ID. + * On the PC, the recommended device can be specified by the user by + * setting an environment variable. For example, to use device #1. + * + * set PA_RECOMMENDED_OUTPUT_DEVICE=1 + * + * The user should first determine the available device ID by using + * the supplied application "pa_devs". + */ +#define PA_ENV_BUF_SIZE_ (32) +#define PA_REC_IN_DEV_ENV_NAME_ ("PA_RECOMMENDED_INPUT_DEVICE") +#define PA_REC_OUT_DEV_ENV_NAME_ ("PA_RECOMMENDED_OUTPUT_DEVICE") +static PaDeviceIndex GetEnvDefaultDeviceID( char *envName ) +{ + PaDeviceIndex recommendedIndex = paNoDevice; + DWORD hresult; + char envbuf[PA_ENV_BUF_SIZE_]; + +#ifndef WIN32_PLATFORM_PSPC /* no GetEnvironmentVariable on PocketPC */ + + /* Let user determine default device by setting environment variable. */ + hresult = GetEnvironmentVariable( envName, envbuf, PA_ENV_BUF_SIZE_ ); + if( (hresult > 0) && (hresult < PA_ENV_BUF_SIZE_) ) + { + recommendedIndex = atoi( envbuf ); + } +#endif + + return recommendedIndex; +} + +static void InitializeDefaultDeviceIdsFromEnv( PaWinMmeHostApiRepresentation *hostApi ) +{ + PaDeviceIndex device; + + /* input */ + device = GetEnvDefaultDeviceID( PA_REC_IN_DEV_ENV_NAME_ ); + if( device != paNoDevice && + ( device >= 0 && device < hostApi->inheritedHostApiRep.info.deviceCount ) && + hostApi->inheritedHostApiRep.deviceInfos[ device ]->maxInputChannels > 0 ) + { + hostApi->inheritedHostApiRep.info.defaultInputDevice = device; + } + + /* output */ + device = GetEnvDefaultDeviceID( PA_REC_OUT_DEV_ENV_NAME_ ); + if( device != paNoDevice && + ( device >= 0 && device < hostApi->inheritedHostApiRep.info.deviceCount ) && + hostApi->inheritedHostApiRep.deviceInfos[ device ]->maxOutputChannels > 0 ) + { + hostApi->inheritedHostApiRep.info.defaultOutputDevice = device; + } +} + + +/** Convert external PA ID to a windows multimedia device ID +*/ +static int LocalDeviceIndexToWinMmeDeviceId( PaWinMmeHostApiRepresentation *hostApi, PaDeviceIndex device ) +{ + assert( device >= 0 && device < hostApi->numInputDevices + hostApi->numOutputDevices ); + + return hostApi->winMmeDeviceIds[ device ]; +} + + +static PaError InitializeInputDeviceInfo( PaWinMmeHostApiRepresentation *winMmeHostApi, + PaDeviceInfo *deviceInfo, int winMmeInputDeviceId, int *success ) +{ + PaError result = paNoError; + char *deviceName; /* non-const ptr */ + MMRESULT mmresult; + WAVEINCAPS wic; + + *success = 0; + + mmresult = waveInGetDevCaps( winMmeInputDeviceId, &wic, sizeof( WAVEINCAPS ) ); + if( mmresult == MMSYSERR_NOMEM ) + { + result = paInsufficientMemory; + goto error; + } + else if( mmresult != MMSYSERR_NOERROR ) + { + /* instead of returning paUnanticipatedHostError we return + paNoError, but leave success set as 0. This allows + Pa_Initialize to just ignore this device, without failing + the entire initialisation process. + */ + return paNoError; + } + + if( winMmeInputDeviceId == WAVE_MAPPER ) + { + /* Append I/O suffix to WAVE_MAPPER device. */ + deviceName = (char *)PaUtil_GroupAllocateMemory( + winMmeHostApi->allocations, strlen( wic.szPname ) + 1 + sizeof(constInputMapperSuffix_) ); + if( !deviceName ) + { + result = paInsufficientMemory; + goto error; + } + strcpy( deviceName, wic.szPname ); + strcat( deviceName, constInputMapperSuffix_ ); + } + else + { + deviceName = (char*)PaUtil_GroupAllocateMemory( + winMmeHostApi->allocations, strlen( wic.szPname ) + 1 ); + if( !deviceName ) + { + result = paInsufficientMemory; + goto error; + } + strcpy( deviceName, wic.szPname ); + } + deviceInfo->name = deviceName; + + deviceInfo->maxInputChannels = wic.wChannels; + /* Sometimes a device can return a rediculously large number of channels. + * This happened with an SBLive card on a Windows ME box. + * If that happens, then force it to 2 channels. PLB20010413 + */ + if( (deviceInfo->maxInputChannels < 1) || (deviceInfo->maxInputChannels > 256) ) + { + PA_DEBUG(("Pa_GetDeviceInfo: Num input channels reported as %d! Changed to 2.\n", deviceInfo->maxInputChannels )); + deviceInfo->maxInputChannels = 2; + } + + deviceInfo->defaultSampleRate = 0.; /* @todo IMPLEMENT ME */ + + *success = 1; + +error: + return result; +} + + +static PaError InitializeOutputDeviceInfo( PaWinMmeHostApiRepresentation *winMmeHostApi, + PaDeviceInfo *deviceInfo, int winMmeOutputDeviceId, int *success ) +{ + PaError result = paNoError; + char *deviceName; /* non-const ptr */ + MMRESULT mmresult; + WAVEOUTCAPS woc; + + *success = 0; + + mmresult = waveOutGetDevCaps( winMmeOutputDeviceId, &woc, sizeof( WAVEOUTCAPS ) ); + if( mmresult == MMSYSERR_NOMEM ) + { + result = paInsufficientMemory; + goto error; + } + else if( mmresult != MMSYSERR_NOERROR ) + { + /* instead of returning paUnanticipatedHostError we return + paNoError, but leave success set as 0. This allows + Pa_Initialize to just ignore this device, without failing + the entire initialisation process. + */ + return paNoError; + } + + if( winMmeOutputDeviceId == WAVE_MAPPER ) + { + /* Append I/O suffix to WAVE_MAPPER device. */ + deviceName = (char *)PaUtil_GroupAllocateMemory( + winMmeHostApi->allocations, strlen( woc.szPname ) + 1 + sizeof(constOutputMapperSuffix_) ); + if( !deviceName ) + { + result = paInsufficientMemory; + goto error; + } + strcpy( deviceName, woc.szPname ); + strcat( deviceName, constOutputMapperSuffix_ ); + } + else + { + deviceName = (char*)PaUtil_GroupAllocateMemory( + winMmeHostApi->allocations, strlen( woc.szPname ) + 1 ); + if( !deviceName ) + { + result = paInsufficientMemory; + goto error; + } + strcpy( deviceName, woc.szPname ); + } + deviceInfo->name = deviceName; + + deviceInfo->maxOutputChannels = woc.wChannels; + /* Sometimes a device can return a rediculously large number of channels. + * This happened with an SBLive card on a Windows ME box. + * It also happens on Win XP! + */ + if( (deviceInfo->maxOutputChannels < 1) || (deviceInfo->maxOutputChannels > 256) ) + { + PA_DEBUG(("Pa_GetDeviceInfo: Num output channels reported as %d! Changed to 2.\n", deviceInfo->maxOutputChannels )); + deviceInfo->maxOutputChannels = 2; + } + + deviceInfo->defaultSampleRate = 0.; /* @todo IMPLEMENT ME */ + + *success = 1; + +error: + return result; +} + + +PaError PaWinMme_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex hostApiIndex ) +{ + PaError result = paNoError; + int i; + PaWinMmeHostApiRepresentation *winMmeHostApi; + int numInputDevices, numOutputDevices, maximumPossibleNumDevices; + PaDeviceInfo *deviceInfoArray; + int deviceInfoInitializationSucceeded; + + winMmeHostApi = (PaWinMmeHostApiRepresentation*)PaUtil_AllocateMemory( sizeof(PaWinMmeHostApiRepresentation) ); + if( !winMmeHostApi ) + { + result = paInsufficientMemory; + goto error; + } + + winMmeHostApi->allocations = PaUtil_CreateAllocationGroup(); + if( !winMmeHostApi->allocations ) + { + result = paInsufficientMemory; + goto error; + } + + *hostApi = &winMmeHostApi->inheritedHostApiRep; + (*hostApi)->info.structVersion = 1; + (*hostApi)->info.type = paMME; + (*hostApi)->info.name = "MME"; + + + /* initialise device counts and default devices under the assumption that + there are no devices. These values are incremented below if and when + devices are successfully initialized. + */ + (*hostApi)->info.deviceCount = 0; + (*hostApi)->info.defaultInputDevice = paNoDevice; + (*hostApi)->info.defaultOutputDevice = paNoDevice; + winMmeHostApi->numInputDevices = 0; + winMmeHostApi->numOutputDevices = 0; + + + maximumPossibleNumDevices = 0; + + numInputDevices = waveInGetNumDevs(); + if( numInputDevices > 0 ) + maximumPossibleNumDevices += numInputDevices + 1; /* assume there is a WAVE_MAPPER */ + + numOutputDevices = waveOutGetNumDevs(); + if( numOutputDevices > 0 ) + maximumPossibleNumDevices += numOutputDevices + 1; /* assume there is a WAVE_MAPPER */ + + + if( maximumPossibleNumDevices > 0 ){ + + (*hostApi)->deviceInfos = (PaDeviceInfo**)PaUtil_GroupAllocateMemory( + winMmeHostApi->allocations, sizeof(PaDeviceInfo*) * maximumPossibleNumDevices ); + if( !(*hostApi)->deviceInfos ) + { + result = paInsufficientMemory; + goto error; + } + + /* allocate all device info structs in a contiguous block */ + deviceInfoArray = (PaDeviceInfo*)PaUtil_GroupAllocateMemory( + winMmeHostApi->allocations, sizeof(PaDeviceInfo) * maximumPossibleNumDevices ); + if( !deviceInfoArray ) + { + result = paInsufficientMemory; + goto error; + } + + winMmeHostApi->winMmeDeviceIds = (int*)PaUtil_GroupAllocateMemory( + winMmeHostApi->allocations, sizeof(int) * maximumPossibleNumDevices ); + if( !winMmeHostApi->winMmeDeviceIds ) + { + result = paInsufficientMemory; + goto error; + } + + if( numInputDevices > 0 ){ + // -1 is the WAVE_MAPPER + for( i = -1; i < numInputDevices; ++i ){ + PaDeviceInfo *deviceInfo = &deviceInfoArray[ (*hostApi)->info.deviceCount ]; + deviceInfo->structVersion = 2; + deviceInfo->hostApi = hostApiIndex; + + deviceInfo->maxInputChannels = 0; + deviceInfo->maxOutputChannels = 0; + + deviceInfo->defaultLowInputLatency = 0.; /* @todo IMPLEMENT ME */ + deviceInfo->defaultLowOutputLatency = 0.; /* @todo IMPLEMENT ME */ + deviceInfo->defaultHighInputLatency = 0.; /* @todo IMPLEMENT ME */ + deviceInfo->defaultHighOutputLatency = 0.; /* @todo IMPLEMENT ME */ + + result = InitializeInputDeviceInfo( winMmeHostApi, deviceInfo, i, &deviceInfoInitializationSucceeded ); + if( result != paNoError ) + goto error; + + if( deviceInfoInitializationSucceeded ){ + if( (*hostApi)->info.defaultInputDevice == paNoDevice ) + (*hostApi)->info.defaultInputDevice = (*hostApi)->info.deviceCount; + + winMmeHostApi->winMmeDeviceIds[ (*hostApi)->info.deviceCount ] = i; + (*hostApi)->deviceInfos[ (*hostApi)->info.deviceCount ] = deviceInfo; + + winMmeHostApi->numInputDevices++; + (*hostApi)->info.deviceCount++; + } + } + } + + if( numOutputDevices > 0 ){ + // -1 is the WAVE_MAPPER + for( i = -1; i < numOutputDevices; ++i ){ + PaDeviceInfo *deviceInfo = &deviceInfoArray[ (*hostApi)->info.deviceCount ]; + deviceInfo->structVersion = 2; + deviceInfo->hostApi = hostApiIndex; + + deviceInfo->maxInputChannels = 0; + deviceInfo->maxOutputChannels = 0; + + deviceInfo->defaultLowInputLatency = 0.; /* @todo IMPLEMENT ME */ + deviceInfo->defaultLowOutputLatency = 0.; /* @todo IMPLEMENT ME */ + deviceInfo->defaultHighInputLatency = 0.; /* @todo IMPLEMENT ME */ + deviceInfo->defaultHighOutputLatency = 0.; /* @todo IMPLEMENT ME */ + + result = InitializeOutputDeviceInfo( winMmeHostApi, deviceInfo, i, &deviceInfoInitializationSucceeded ); + if( result != paNoError ) + goto error; + + if( deviceInfoInitializationSucceeded ){ + if( (*hostApi)->info.defaultOutputDevice == paNoDevice ) + (*hostApi)->info.defaultOutputDevice = (*hostApi)->info.deviceCount; + + winMmeHostApi->winMmeDeviceIds[ (*hostApi)->info.deviceCount ] = i; + (*hostApi)->deviceInfos[ (*hostApi)->info.deviceCount ] = deviceInfo; + + winMmeHostApi->numOutputDevices++; + (*hostApi)->info.deviceCount++; + } + } + } + } + + + InitializeDefaultDeviceIdsFromEnv( winMmeHostApi ); + + (*hostApi)->Terminate = Terminate; + (*hostApi)->OpenStream = OpenStream; + (*hostApi)->IsFormatSupported = IsFormatSupported; + + PaUtil_InitializeStreamInterface( &winMmeHostApi->callbackStreamInterface, CloseStream, StartStream, + StopStream, AbortStream, IsStreamStopped, IsStreamActive, + GetStreamTime, GetStreamCpuLoad, + PaUtil_DummyReadWrite, PaUtil_DummyReadWrite, PaUtil_DummyGetAvailable, PaUtil_DummyGetAvailable ); + + PaUtil_InitializeStreamInterface( &winMmeHostApi->blockingStreamInterface, CloseStream, StartStream, + StopStream, AbortStream, IsStreamStopped, IsStreamActive, + GetStreamTime, PaUtil_DummyGetCpuLoad, + ReadStream, WriteStream, GetStreamReadAvailable, GetStreamWriteAvailable ); + + return result; + +error: + if( winMmeHostApi ) + { + if( winMmeHostApi->allocations ) + { + PaUtil_FreeAllAllocations( winMmeHostApi->allocations ); + PaUtil_DestroyAllocationGroup( winMmeHostApi->allocations ); + } + + PaUtil_FreeMemory( winMmeHostApi ); + } + + return result; +} + + +static void Terminate( struct PaUtilHostApiRepresentation *hostApi ) +{ + PaWinMmeHostApiRepresentation *winMmeHostApi = (PaWinMmeHostApiRepresentation*)hostApi; + + if( winMmeHostApi->allocations ) + { + PaUtil_FreeAllAllocations( winMmeHostApi->allocations ); + PaUtil_DestroyAllocationGroup( winMmeHostApi->allocations ); + } + + PaUtil_FreeMemory( winMmeHostApi ); +} + + +static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate ) +{ + int inputChannelCount, outputChannelCount; + PaSampleFormat inputSampleFormat, outputSampleFormat; + + if( inputParameters ) + { + inputChannelCount = inputParameters->channelCount; + inputSampleFormat = inputParameters->sampleFormat; + + /* unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification */ + + if( inputParameters->device == paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; + + /* check that input device can support inputChannelCount */ + if( inputChannelCount > hostApi->deviceInfos[ inputParameters->device ]->maxInputChannels ) + return paInvalidChannelCount; + + /* validate inputStreamInfo */ + if( inputParameters->hostApiSpecificStreamInfo ) + return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + } + else + { + inputChannelCount = 0; + } + + if( outputParameters ) + { + outputChannelCount = outputParameters->channelCount; + outputSampleFormat = outputParameters->sampleFormat; + + /* unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification */ + + if( outputParameters->device == paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; + + /* check that output device can support inputChannelCount */ + if( outputChannelCount > hostApi->deviceInfos[ outputParameters->device ]->maxOutputChannels ) + return paInvalidChannelCount; + + /* validate outputStreamInfo */ + if( outputParameters->hostApiSpecificStreamInfo ) + return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + } + else + { + outputChannelCount = 0; + } + + /* + IMPLEMENT ME: + - check that input device can support inputSampleFormat, or that + we have the capability to convert from outputSampleFormat to + a native format + + - check that output device can support outputSampleFormat, or that + we have the capability to convert from outputSampleFormat to + a native format + + - if a full duplex stream is requested, check that the combination + of input and output parameters is supported + + - check that the device supports sampleRate + */ + + return paFormatIsSupported; +} + + +/* CalculateBufferSettings() fills the framesPerHostInputBuffer, numHostInputBuffers, + framesPerHostOutputBuffer and numHostOutputBuffers parameters based on the values + of the other parameters. + +*/ + +static PaError CalculateBufferSettings( + unsigned long *framesPerHostInputBuffer, unsigned long *numHostInputBuffers, + unsigned long *framesPerHostOutputBuffer, unsigned long *numHostOutputBuffers, + int inputChannelCount, PaSampleFormat hostInputSampleFormat, + PaTime suggestedInputLatency, PaWinMmeStreamInfo *inputStreamInfo, + int outputChannelCount, PaSampleFormat hostOutputSampleFormat, + PaTime suggestedOutputLatency, PaWinMmeStreamInfo *outputStreamInfo, + double sampleRate, unsigned long framesPerBuffer ) +{ + PaError result = paNoError; + + if( inputChannelCount > 0 ) + { + if( inputStreamInfo ) + { + if( inputStreamInfo->flags & PaWinMmeUseLowLevelLatencyParameters ) + { + if( inputStreamInfo->numBuffers <= 0 + || inputStreamInfo->framesPerBuffer <= 0 ) + { + result = paIncompatibleHostApiSpecificStreamInfo; + goto error; + } + + *framesPerHostInputBuffer = inputStreamInfo->framesPerBuffer; + *numHostInputBuffers = inputStreamInfo->numBuffers; + } + } + else + { + /* hardwire for now, FIXME */ + /* don't forget that there will be one more buffer than the number required to achieve the requested latency */ + *framesPerHostInputBuffer = 4096; + *numHostInputBuffers = 4; + + /* + Need to determine the right heuristic for mapping latency in + seconds to buffer sizes and number of buffers. + + - for output don't allocate less than 1+1 buffers + + - for input don't allocate less than 2+1 buffers + (less than 1+1 if input only) + + - don't allocate buffers smaller than framesPerBuffer + + - if the client doesn't care about the buffer size use a power + of two buffer size. otherwise use a multiple of the user + buffer size. if the user buffer size is a power of 2, it + might be wise to make the host buffer size a power of 2 too. + + - there probably shouldn't be too many buffers ( 3 to 10 seems + reasonable). + + - aside from a limit on what constitutes a "reasonable" number + of buffers, there should be as many buffers as possible, + because this will place a less bursty load on CPU resources + + - the host buffers should be as big as practical (ie multiple + user buffers per host buffer). + + + . One way to achieve the above is to say: Try to have 8 + host buffers, and host buffers cannot be larger than 32k + unless the user buffer size requires it" + I say 32k because buffers larger than this are known to + crash some drivers (Turtle Beach for example.) + + just some idle rambling: + + if( framesPerBuffer == 0 ){ + // use a power of two buffer size + + }else{ + latencySamples = ceil(requestedLatency * sampleRate) + + numBuffers = ceil(latencySamples / framesPerBuffer); + + bufferSize = framesPerBuffer; + + minBuffers = ( inputChannelCount > 0 && outputChannelCount > 0 ) ? 2 : 1; + + if( numBuffers <= minBuffers ){ + numBuffers = minBuffers; + }else{ + make buffer size a multiple of framesPerBuffer until numBuffers is + greater than 4 and less than 10. + } + } + */ + } + } + else + { + *framesPerHostInputBuffer = 0; + *numHostInputBuffers = 0; + } + + if( outputChannelCount > 0 ) + { + if( outputStreamInfo ) + { + if( outputStreamInfo->flags & PaWinMmeUseLowLevelLatencyParameters ) + { + if( outputStreamInfo->numBuffers <= 0 + || outputStreamInfo->framesPerBuffer <= 0 ) + { + result = paIncompatibleHostApiSpecificStreamInfo; + goto error; + } + + *framesPerHostOutputBuffer = outputStreamInfo->framesPerBuffer; + *numHostOutputBuffers = outputStreamInfo->numBuffers; + } + } + else + { + /* hardwire for now, FIXME */ + /* don't forget that there will be one more buffer than the number required to achieve the requested latency */ + *framesPerHostOutputBuffer = 4096; + *numHostOutputBuffers = 4; + } + } + else + { + *framesPerHostOutputBuffer = 0; + *numHostOutputBuffers = 0; + } + +error: + return result; +} + + + +typedef HWAVEIN MmeHandle; + +static PaError InitializeBufferSet( WAVEHDR **bufferSet, int numBuffers, int bufferBytes, + int isInput, /* if 0, then output */ + MmeHandle mmeWaveHandle, int numDeviceChannels ) +{ + PaError result = paNoError; + MMRESULT mmresult; + int i; + + *bufferSet = 0; + + /* Allocate an array to hold the buffer pointers. */ + *bufferSet = (WAVEHDR *) PaUtil_AllocateMemory( sizeof(WAVEHDR)*numBuffers ); + if( !*bufferSet ) + { + result = paInsufficientMemory; + goto error; + } + + for( i=0; i don't throtte, non-0 -> throttle */ + int processingThreadPriority; + int highThreadPriority; + int throttledThreadPriority; + + volatile int isActive; + volatile int stopProcessing; /* stop thread once existing buffers have been returned */ + volatile int abortProcessing; /* stop thread immediately */ + + DWORD allBuffersDurationMs; /* used to calculate timeouts */ + + /* @todo FIXME: we no longer need the following for GetStreamTime support */ + /* GetStreamTime() support ------------- */ + + PaTime streamPosition; + long previousStreamPosition; /* used to track frames played. */ +} +PaWinMmeStream; + + +/* the following macros are intended to improve the readability of the following code */ +#define PA_IS_INPUT_STREAM_( stream ) ( stream ->hWaveIns ) +#define PA_IS_OUTPUT_STREAM_( stream ) ( stream ->hWaveOuts ) +#define PA_IS_FULL_DUPLEX_STREAM_( stream ) ( stream ->hWaveIns && stream ->hWaveOuts ) + + +static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, + PaStream** s, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamFlags streamFlags, + PaStreamCallback *streamCallback, + void *userData ) +{ + PaError result = paNoError; + PaWinMmeHostApiRepresentation *winMmeHostApi = (PaWinMmeHostApiRepresentation*)hostApi; + PaWinMmeStream *stream = 0; + PaSampleFormat hostInputSampleFormat, hostOutputSampleFormat; + int inputChannelCount, outputChannelCount; + PaSampleFormat inputSampleFormat, outputSampleFormat; + double suggestedInputLatency, suggestedOutputLatency; + PaWinMmeStreamInfo *inputStreamInfo, *outputStreamInfo; + unsigned long bytesPerInputFrame, bytesPerOutputFrame; + unsigned long framesPerHostInputBuffer; + unsigned long numHostInputBuffers; + unsigned long framesPerHostOutputBuffer; + unsigned long numHostOutputBuffers; + unsigned long framesPerBufferProcessorCall; + int lockInited = 0; + int bufferEventInited = 0; + int abortEventInited = 0; + WAVEFORMATEX wfx; + MMRESULT mmresult; + unsigned int i; + int channelCount; + PaWinMmeDeviceAndChannelCount *inputDevices = 0; + unsigned long numInputDevices = (inputParameters) ? 1 : 0; + PaWinMmeDeviceAndChannelCount *outputDevices = 0; + unsigned long numOutputDevices = (outputParameters) ? 1 : 0; + char noHighPriorityProcessClass = 0; + char useTimeCriticalProcessingThreadPriority = 0; + char throttleProcessingThreadOnOverload = 1; + + + if( inputParameters ) + { + inputChannelCount = inputParameters->channelCount; + inputSampleFormat = inputParameters->sampleFormat; + suggestedInputLatency = inputParameters->suggestedLatency; + + /* check that input device can support inputChannelCount */ + if( (inputParameters->device != paUseHostApiSpecificDeviceSpecification) && + (inputChannelCount > hostApi->deviceInfos[ inputParameters->device ]->maxInputChannels) ) + return paInvalidChannelCount; + + + /* validate input hostApiSpecificStreamInfo */ + inputStreamInfo = (PaWinMmeStreamInfo*)inputParameters->hostApiSpecificStreamInfo; + if( inputStreamInfo ) + { + if( inputStreamInfo->size != sizeof( PaWinMmeStreamInfo ) + || inputStreamInfo->version != 1 ) + { + return paIncompatibleHostApiSpecificStreamInfo; + } + + if( inputStreamInfo->flags & PaWinMmeNoHighPriorityProcessClass ) + noHighPriorityProcessClass = 1; + if( inputStreamInfo->flags & PaWinMmeDontThrottleOverloadedProcessingThread ) + throttleProcessingThreadOnOverload = 0; + if( inputStreamInfo->flags & PaWinMmeUseTimeCriticalThreadPriority ) + useTimeCriticalProcessingThreadPriority = 1; + + /* validate multidevice fields */ + + if( inputStreamInfo->flags & PaWinMmeUseMultipleDevices ) + { + int totalChannels = 0; + for( i=0; i< inputStreamInfo->deviceCount; ++i ) + { + /* validate that the device number is within range, and that + the number of channels is legal */ + PaDeviceIndex hostApiDevice; + + if( inputParameters->device != paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; + + channelCount = inputStreamInfo->devices[i].channelCount; + + result = PaUtil_DeviceIndexToHostApiDeviceIndex( &hostApiDevice, + inputStreamInfo->devices[i].device, hostApi ); + if( result != paNoError ) + return result; + + if( channelCount < 1 || channelCount > hostApi->deviceInfos[ hostApiDevice ]->maxInputChannels ) + return paInvalidChannelCount; + + /* FIXME this validation might be easier and better if there was a pautil + function which performed the validation in pa_front:ValidateOpenStreamParameters() */ + + totalChannels += channelCount; + } + + if( totalChannels != inputChannelCount ) + { + /* inputChannelCount must match total channels specified by multiple devices */ + return paInvalidChannelCount; /* REVIEW use of this error code */ + } + + inputDevices = inputStreamInfo->devices; + numInputDevices = inputStreamInfo->deviceCount; + } + } + + /* FIXME: establish which host formats are available */ + hostInputSampleFormat = + PaUtil_SelectClosestAvailableFormat( paInt16 /* native formats */, inputSampleFormat ); + + } + else + { + inputChannelCount = 0; + inputSampleFormat = -1; + suggestedInputLatency = 0.; + inputStreamInfo = 0; + hostInputSampleFormat = -1; + } + + + if( outputParameters ) + { + outputChannelCount = outputParameters->channelCount; + outputSampleFormat = outputParameters->sampleFormat; + suggestedOutputLatency = outputParameters->suggestedLatency; + + /* check that input device can support inputChannelCount */ + if( (outputParameters->device != paUseHostApiSpecificDeviceSpecification) && + (inputChannelCount > hostApi->deviceInfos[ outputParameters->device ]->maxOutputChannels) ) + return paInvalidChannelCount; + + + /* validate input hostApiSpecificStreamInfo */ + outputStreamInfo = (PaWinMmeStreamInfo*)outputParameters->hostApiSpecificStreamInfo; + if( outputStreamInfo ) + { + if( outputStreamInfo->size != sizeof( PaWinMmeStreamInfo ) + || outputStreamInfo->version != 1 ) + { + return paIncompatibleHostApiSpecificStreamInfo; + } + + if( outputStreamInfo->flags & PaWinMmeNoHighPriorityProcessClass ) + noHighPriorityProcessClass = 1; + if( outputStreamInfo->flags & PaWinMmeDontThrottleOverloadedProcessingThread ) + throttleProcessingThreadOnOverload = 0; + if( outputStreamInfo->flags & PaWinMmeUseTimeCriticalThreadPriority ) + useTimeCriticalProcessingThreadPriority = 1; + + /* validate multidevice fields */ + + if( outputStreamInfo->flags & PaWinMmeUseMultipleDevices ) + { + int totalChannels = 0; + for( i=0; i< outputStreamInfo->deviceCount; ++i ) + { + /* validate that the device number is within range, and that + the number of channels is legal */ + PaDeviceIndex hostApiDevice; + + if( outputParameters->device != paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; + + channelCount = outputStreamInfo->devices[i].channelCount; + + result = PaUtil_DeviceIndexToHostApiDeviceIndex( &hostApiDevice, + outputStreamInfo->devices[i].device, + hostApi ); + if( result != paNoError ) + return result; + + if( channelCount < 1 || channelCount > hostApi->deviceInfos[ hostApiDevice ]->maxOutputChannels ) + return paInvalidChannelCount; + + /* FIXME this validation might be easier and better if there was a pautil + function which performed the validation in pa_front:ValidateOpenStreamParameters() */ + + totalChannels += channelCount; + } + + if( totalChannels != outputChannelCount ) + { + /* outputChannelCount must match total channels specified by multiple devices */ + return paInvalidChannelCount; /* REVIEW use of this error code */ + } + + outputDevices = outputStreamInfo->devices; + numOutputDevices = outputStreamInfo->deviceCount; + } + } + + /* FIXME: establish which host formats are available */ + hostOutputSampleFormat = + PaUtil_SelectClosestAvailableFormat( paInt16 /* native formats */, outputSampleFormat ); + } + else + { + outputChannelCount = 0; + outputSampleFormat = -1; + outputStreamInfo = 0; + hostOutputSampleFormat = -1; + suggestedOutputLatency = 0.; + } + + + /* + IMPLEMENT ME: + - alter sampleRate to a close allowable rate if possible / necessary + */ + + + /* validate platform specific flags */ + if( (streamFlags & paPlatformSpecificFlags) != 0 ) + return paInvalidFlag; /* unexpected platform specific flag */ + + + result = CalculateBufferSettings( &framesPerHostInputBuffer, &numHostInputBuffers, + &framesPerHostOutputBuffer, &numHostOutputBuffers, + inputChannelCount, hostInputSampleFormat, suggestedInputLatency, inputStreamInfo, + outputChannelCount, hostOutputSampleFormat, suggestedOutputLatency, outputStreamInfo, + sampleRate, framesPerBuffer ); + if( result != paNoError ) + goto error; + + + stream = (PaWinMmeStream*)PaUtil_AllocateMemory( sizeof(PaWinMmeStream) ); + if( !stream ) + { + result = paInsufficientMemory; + goto error; + } + + stream->hWaveIns = 0; + stream->inputBuffers = 0; + stream->hWaveOuts = 0; + stream->outputBuffers = 0; + stream->processingThread = 0; + + stream->noHighPriorityProcessClass = noHighPriorityProcessClass; + stream->useTimeCriticalProcessingThreadPriority = useTimeCriticalProcessingThreadPriority; + stream->throttleProcessingThreadOnOverload = throttleProcessingThreadOnOverload; + + PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation, + &winMmeHostApi->callbackStreamInterface, streamCallback, userData ); + + stream->streamRepresentation.streamInfo.inputLatency = (double)(framesPerHostInputBuffer * (numHostInputBuffers-1)) / sampleRate; + stream->streamRepresentation.streamInfo.outputLatency = (double)(framesPerHostOutputBuffer * (numHostOutputBuffers-1)) / sampleRate; + stream->streamRepresentation.streamInfo.sampleRate = sampleRate; + + PaUtil_InitializeCpuLoadMeasurer( &stream->cpuLoadMeasurer, sampleRate ); + + + if( inputParameters && outputParameters ) /* full duplex */ + { + /* + either host input and output buffers must be the same size, or the + larger one must be an integer multiple of the smaller one. + FIXME: should this return an error if the host specific latency + settings don't fulfill these constraints? rb: probably + */ + + if( framesPerHostInputBuffer < framesPerHostOutputBuffer ) + { + assert( (framesPerHostOutputBuffer % framesPerHostInputBuffer) == 0 ); + + framesPerBufferProcessorCall = framesPerHostInputBuffer; + } + else + { + assert( (framesPerHostInputBuffer % framesPerHostOutputBuffer) == 0 ); + + framesPerBufferProcessorCall = framesPerHostOutputBuffer; + } + } + else if( inputParameters ) + { + framesPerBufferProcessorCall = framesPerHostInputBuffer; + } + else if( outputParameters ) + { + framesPerBufferProcessorCall = framesPerHostOutputBuffer; + } + + stream->framesPerInputBuffer = framesPerHostInputBuffer; + stream->framesPerOutputBuffer = framesPerHostOutputBuffer; + + result = PaUtil_InitializeBufferProcessor( &stream->bufferProcessor, + inputChannelCount, inputSampleFormat, hostInputSampleFormat, + outputChannelCount, outputSampleFormat, hostOutputSampleFormat, + sampleRate, streamFlags, framesPerBuffer, + framesPerBufferProcessorCall, paUtilFixedHostBufferSize, + streamCallback, userData ); + if( result != paNoError ) + goto error; + + stream->isActive = 0; + + stream->streamPosition = 0.; + stream->previousStreamPosition = 0; + + + stream->bufferEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); + if( stream->bufferEvent == NULL ) + { + result = paUnanticipatedHostError; + PA_MME_SET_LAST_SYSTEM_ERROR( GetLastError() ); + goto error; + } + bufferEventInited = 1; + + if( inputParameters ) + { + wfx.wFormatTag = WAVE_FORMAT_PCM; + wfx.nSamplesPerSec = (DWORD) sampleRate; + wfx.cbSize = 0; + + stream->numInputDevices = numInputDevices; + stream->hWaveIns = (HWAVEIN*)PaUtil_AllocateMemory( sizeof(HWAVEIN) * stream->numInputDevices ); + if( !stream->hWaveIns ) + { + result = paInsufficientMemory; + goto error; + } + + for( i = 0; i < stream->numInputDevices; ++i ) + stream->hWaveIns[i] = 0; + + for( i = 0; i < stream->numInputDevices; ++i ) + { + int inputWinMmeId; + + if( inputDevices ) + { + PaDeviceIndex hostApiDevice; + + result = PaUtil_DeviceIndexToHostApiDeviceIndex( &hostApiDevice, + inputDevices[i].device, hostApi ); + if( result != paNoError ) + return result; + + inputWinMmeId = LocalDeviceIndexToWinMmeDeviceId( winMmeHostApi, hostApiDevice ); + wfx.nChannels = (WORD) inputDevices[i].channelCount; + } + else + { + inputWinMmeId = LocalDeviceIndexToWinMmeDeviceId( winMmeHostApi, inputParameters->device ); + wfx.nChannels = (WORD) inputChannelCount; + } + + bytesPerInputFrame = wfx.nChannels * stream->bufferProcessor.bytesPerHostInputSample; + + wfx.nAvgBytesPerSec = (DWORD)(bytesPerInputFrame * sampleRate); + wfx.nBlockAlign = (WORD)bytesPerInputFrame; + wfx.wBitsPerSample = (WORD)((bytesPerInputFrame/wfx.nChannels) * 8); + + /* REVIEW: consider not firing an event for input when a full duplex stream is being used */ + + mmresult = waveInOpen( &stream->hWaveIns[i], inputWinMmeId, &wfx, + (DWORD)stream->bufferEvent, (DWORD) stream, CALLBACK_EVENT ); + if( mmresult != MMSYSERR_NOERROR ) + { + switch( mmresult ) + { + case MMSYSERR_ALLOCATED: /* Specified resource is already allocated. */ + result = paDeviceUnavailable; + break; + case MMSYSERR_BADDEVICEID: /* Specified device identifier is out of range. */ + result = paInternalError; /* portaudio should ensure that only good device ids are used */ + break; + case MMSYSERR_NODRIVER: /* No device driver is present. */ + result = paDeviceUnavailable; + break; + case MMSYSERR_NOMEM: /* Unable to allocate or lock memory. */ + result = paInsufficientMemory; + break; + case WAVERR_BADFORMAT: /* Attempted to open with an unsupported waveform-audio format. */ + result = paInternalError; /* REVIEW: port audio shouldn't get this far without using compatible format info */ + break; + default: + result = paUnanticipatedHostError; + PA_MME_SET_LAST_WAVEIN_ERROR( mmresult ); + } + goto error; + } + } + } + + if( outputParameters ) + { + wfx.wFormatTag = WAVE_FORMAT_PCM; + wfx.nSamplesPerSec = (DWORD) sampleRate; + wfx.cbSize = 0; + + stream->numOutputDevices = numOutputDevices; + stream->hWaveOuts = (HWAVEOUT*)PaUtil_AllocateMemory( sizeof(HWAVEOUT) * stream->numOutputDevices ); + if( !stream->hWaveOuts ) + { + result = paInsufficientMemory; + goto error; + } + + for( i = 0; i < stream->numOutputDevices; ++i ) + stream->hWaveOuts[i] = 0; + + for( i = 0; i < stream->numOutputDevices; ++i ) + { + int outputWinMmeId; + + if( outputDevices ) + { + PaDeviceIndex hostApiDevice; + + result = PaUtil_DeviceIndexToHostApiDeviceIndex( &hostApiDevice, + outputDevices[i].device, hostApi ); + if( result != paNoError ) + return result; + + outputWinMmeId = LocalDeviceIndexToWinMmeDeviceId( winMmeHostApi, hostApiDevice ); + wfx.nChannels = (WORD) outputDevices[i].channelCount; + } + else + { + outputWinMmeId = LocalDeviceIndexToWinMmeDeviceId( winMmeHostApi, outputParameters->device ); + wfx.nChannels = (WORD) outputChannelCount; + } + + bytesPerOutputFrame = wfx.nChannels * stream->bufferProcessor.bytesPerHostOutputSample; + + wfx.nAvgBytesPerSec = (DWORD)(bytesPerOutputFrame * sampleRate); + wfx.nBlockAlign = (WORD)bytesPerOutputFrame; + wfx.wBitsPerSample = (WORD)((bytesPerOutputFrame/wfx.nChannels) * 8); + + mmresult = waveOutOpen( &stream->hWaveOuts[i], outputWinMmeId, &wfx, + (DWORD)stream->bufferEvent, (DWORD) stream, CALLBACK_EVENT ); + if( mmresult != MMSYSERR_NOERROR ) + { + switch( mmresult ) + { + case MMSYSERR_ALLOCATED: /* Specified resource is already allocated. */ + result = paDeviceUnavailable; + break; + case MMSYSERR_BADDEVICEID: /* Specified device identifier is out of range. */ + result = paInternalError; /* portaudio should ensure that only good device ids are used */ + break; + case MMSYSERR_NODRIVER: /* No device driver is present. */ + result = paDeviceUnavailable; + break; + case MMSYSERR_NOMEM: /* Unable to allocate or lock memory. */ + result = paInsufficientMemory; + break; + case WAVERR_BADFORMAT: /* Attempted to open with an unsupported waveform-audio format. */ + result = paInternalError; /* REVIEW: port audio shouldn't get this far without using compatible format info */ + break; + default: + result = paUnanticipatedHostError; + PA_MME_SET_LAST_WAVEOUT_ERROR( mmresult ); + } + goto error; + } + } + } + + if( PA_IS_INPUT_STREAM_(stream) ) + { + stream->inputBuffers = (WAVEHDR**)PaUtil_AllocateMemory( sizeof(WAVEHDR*) * stream->numInputDevices ); + if( stream->inputBuffers == 0 ) + { + result = paInsufficientMemory; + goto error; + } + + for( i =0; i < stream->numInputDevices; ++i ) + stream->inputBuffers[i] = 0; + + stream->numInputBuffers = numHostInputBuffers; + + for( i =0; i < stream->numInputDevices; ++i ) + { + int hostInputBufferBytes = Pa_GetSampleSize( hostInputSampleFormat ) * + framesPerHostInputBuffer * + ((inputDevices) ? inputDevices[i].channelCount : inputChannelCount); + if( hostInputBufferBytes < 0 ) + { + result = paInternalError; + goto error; + } + + result = InitializeBufferSet( &stream->inputBuffers[i], numHostInputBuffers, hostInputBufferBytes, + 1 /* isInput */, + (MmeHandle)stream->hWaveIns[i], + ((inputDevices) ? inputDevices[i].channelCount : inputChannelCount) ); + + if( result != paNoError ) + goto error; + } + } + + if( PA_IS_OUTPUT_STREAM_(stream) ) + { + stream->outputBuffers = (WAVEHDR**)PaUtil_AllocateMemory( sizeof(WAVEHDR*) * stream->numOutputDevices ); + if( stream->outputBuffers == 0 ) + { + result = paInsufficientMemory; + goto error; + } + + for( i =0; i < stream->numOutputDevices; ++i ) + stream->outputBuffers[i] = 0; + + stream->numOutputBuffers = numHostOutputBuffers; + + for( i=0; i < stream->numOutputDevices; ++i ) + { + int hostOutputBufferBytes = Pa_GetSampleSize( hostOutputSampleFormat ) * + framesPerHostOutputBuffer * + ((outputDevices) ? outputDevices[i].channelCount : outputChannelCount); + if( hostOutputBufferBytes < 0 ) + { + result = paInternalError; + goto error; + } + + result = InitializeBufferSet( &stream->outputBuffers[i], numHostOutputBuffers, hostOutputBufferBytes, + 0 /* not isInput */, + (MmeHandle)stream->hWaveOuts[i], + ((outputDevices) ? outputDevices[i].channelCount : outputChannelCount) ); + + if( result != paNoError ) + goto error; + } + } + + stream->abortEvent = CreateEvent( NULL, TRUE, FALSE, NULL ); + if( stream->abortEvent == NULL ) + { + result = paUnanticipatedHostError; + PA_MME_SET_LAST_SYSTEM_ERROR( GetLastError() ); + goto error; + } + abortEventInited = 1; + + InitializeCriticalSection( &stream->lock ); + lockInited = 1; + + if( PA_IS_OUTPUT_STREAM_(stream) ) + stream->allBuffersDurationMs = (DWORD) (1000.0 * (framesPerHostOutputBuffer * stream->numOutputBuffers) / sampleRate); + else + stream->allBuffersDurationMs = (DWORD) (1000.0 * (framesPerHostInputBuffer * stream->numInputBuffers) / sampleRate); + + + *s = (PaStream*)stream; + + return result; + +error: + if( lockInited ) + DeleteCriticalSection( &stream->lock ); + + if( abortEventInited ) + CloseHandle( stream->abortEvent ); + + + if( stream->inputBuffers ) + { + for( i =0 ; i< stream->numInputDevices; ++i ) + { + if( stream->inputBuffers[i] ) + { + TerminateBufferSet( &stream->inputBuffers[i], stream->numInputBuffers, + 1 /* isInput */, (MmeHandle)stream->hWaveIns[i] ); + } + } + + PaUtil_FreeMemory( stream->inputBuffers ); + } + + if( stream->outputBuffers ) + { + for( i =0 ; i< stream->numOutputDevices; ++i ) + { + if( stream->outputBuffers[i] ) + { + TerminateBufferSet( &stream->outputBuffers[i], stream->numOutputBuffers, + 0 /* not isInput */, (MmeHandle)stream->hWaveOuts[i] ); + } + } + + PaUtil_FreeMemory( stream->outputBuffers ); + } + + if( stream->hWaveIns ) + { + for( i =0 ; i< stream->numInputDevices; ++i ) + { + if( stream->hWaveIns[i] ) + waveInClose( stream->hWaveIns[i] ); + } + + PaUtil_FreeMemory( stream->hWaveIns ); + } + + if( stream->hWaveOuts ) + { + for( i =0 ; i< stream->numOutputDevices; ++i ) + { + if( stream->hWaveOuts[i] ) + waveOutClose( stream->hWaveOuts[i] ); + } + + PaUtil_FreeMemory( stream->hWaveOuts ); + } + + if( bufferEventInited ) + CloseHandle( stream->bufferEvent ); + + if( stream ) + PaUtil_FreeMemory( stream ); + + return result; +} + + +/* return non-zero if any output buffers are queued */ +static int OutputBuffersAreQueued( PaWinMmeStream *stream ) +{ + int result = 0; + unsigned int i, j; + + if( PA_IS_OUTPUT_STREAM_( stream ) ) + { + for( i=0; inumOutputBuffers; ++i ) + { + for( j=0; j < stream->numOutputDevices; ++j ) + { + if( !( stream->outputBuffers[ j ][ i ].dwFlags & WHDR_DONE) ) + { + result++; + } + } + } + } + + return result; +} + + +static PaError AdvanceToNextInputBuffer( PaWinMmeStream *stream ) +{ + PaError result = paNoError; + MMRESULT mmresult; + unsigned int i; + + for( i=0; i< stream->numInputDevices; ++i ) + { + mmresult = waveInAddBuffer( stream->hWaveIns[i], + &stream->inputBuffers[i][ stream->currentInputBufferIndex ], + sizeof(WAVEHDR) ); + if( mmresult != MMSYSERR_NOERROR ) + { + result = paUnanticipatedHostError; + PA_MME_SET_LAST_WAVEIN_ERROR( mmresult ); + } + } + stream->currentInputBufferIndex = (stream->currentInputBufferIndex+1 >= stream->numInputBuffers) ? + 0 : stream->currentInputBufferIndex+1; + + stream->framesUsedInCurrentInputBuffer = 0; + + return result; +} + + +static PaError AdvanceToNextOutputBuffer( PaWinMmeStream *stream ) +{ + PaError result = paNoError; + MMRESULT mmresult; + unsigned int i; + + for( i=0; i< stream->numOutputDevices; ++i ) + { + mmresult = waveOutWrite( stream->hWaveOuts[i], + &stream->outputBuffers[i][ stream->currentOutputBufferIndex ], + sizeof(WAVEHDR) ); + if( mmresult != MMSYSERR_NOERROR ) + { + result = paUnanticipatedHostError; + PA_MME_SET_LAST_WAVEOUT_ERROR( mmresult ); + } + } + + stream->currentOutputBufferIndex = (stream->currentOutputBufferIndex+1 >= stream->numOutputBuffers) ? + 0 : stream->currentOutputBufferIndex+1; + + stream->framesUsedInCurrentOutputBuffer = 0; + + return result; +} + + +static DWORD WINAPI ProcessingThreadProc( void *pArg ) +{ + PaWinMmeStream *stream = (PaWinMmeStream *)pArg; + HANDLE events[2]; + int numEvents = 0; + DWORD result = paNoError; + DWORD waitResult; +/** @todo: +Gordon Gidluck: +> function: ProcessingThreadProc() +> line #1665 DWORD timeout = stream->allBuffersDurationMs * 0.5; +> conversion from 'double ' to 'unsigned long ', possible loss of data +*/ + DWORD timeout = stream->allBuffersDurationMs * 0.5; + DWORD numTimeouts = 0; + int hostBuffersAvailable; + signed int hostInputBufferIndex, hostOutputBufferIndex; + int callbackResult; + int done = 0; + unsigned int channel, i, j; + unsigned long framesProcessed; + + /* prepare event array for call to WaitForMultipleObjects() */ + events[numEvents++] = stream->bufferEvent; + events[numEvents++] = stream->abortEvent; + + /* loop until something causes us to stop */ + while( !done ) + { + /* wait for MME to signal that a buffer is available, or for + the PA abort event to be signaled */ + waitResult = WaitForMultipleObjects( numEvents, events, FALSE, timeout ); + if( waitResult == WAIT_FAILED ) + { + result = paUnanticipatedHostError; + /* FIXME/REVIEW: can't return host error info from an asyncronous thread */ + done = 1; + } + else if( waitResult == WAIT_TIMEOUT ) + { + /* if a timeout is encountered, continue */ + numTimeouts += 1; + } + + if( stream->abortProcessing ) + { + /* Pa_AbortStream() has been called, stop processing immediately */ + done = 1; + } + else if( stream->stopProcessing ) + { + /* Pa_StopStream() has been called or the user callback returned + non-zero, processing will continue until all output buffers + are marked as done. The stream will stop immediately if it + is input-only. + */ + + if( !OutputBuffersAreQueued( stream ) ) + { + done = 1; /* Will cause thread to return. */ + } + } + else + { + hostBuffersAvailable = 1; + + /* process all available host buffers */ + do + { + hostInputBufferIndex = -1; + hostOutputBufferIndex = -1; + + if( PA_IS_INPUT_STREAM_(stream)) + { + hostInputBufferIndex = stream->currentInputBufferIndex; + for( i=0; inumInputDevices; ++i ) + { + if( !(stream->inputBuffers[i][ stream->currentInputBufferIndex ].dwFlags & WHDR_DONE) ) + { + hostInputBufferIndex = -1; + break; + } + } + + if( hostInputBufferIndex != -1 ) + { + /* if all of the other buffers are also ready then we dicard all but the + most recent. */ + int inputCatchUp = 1; + + for( i=0; i < stream->numInputBuffers && inputCatchUp == 1; ++i ) + { + for( j=0; jnumInputDevices; ++j ) + { + if( !(stream->inputBuffers[ j ][ i ].dwFlags & WHDR_DONE) ) + { + inputCatchUp = 0; + break; + } + } + } + + if( inputCatchUp ) + { + for( i=0; i < stream->numInputBuffers - 1; ++i ) + { + result = AdvanceToNextInputBuffer( stream ); + if( result != paNoError ) + done = 1; + } + } + } + } + + if( PA_IS_OUTPUT_STREAM_(stream) ) + { + hostOutputBufferIndex = stream->currentOutputBufferIndex; + for( i=0; inumOutputDevices; ++i ) + { + if( !(stream->outputBuffers[i][ stream->currentOutputBufferIndex ].dwFlags & WHDR_DONE) ) + { + hostOutputBufferIndex = -1; + break; + } + } + + if( hostOutputBufferIndex != - 1 ) + { + /* if all of the other buffers are also ready, catch up by copying + the most recently generated buffer into all but one of the output + buffers */ + int outputCatchUp = 1; + + for( i=0; i < stream->numOutputBuffers && outputCatchUp == 1; ++i ) + { + for( j=0; jnumOutputDevices; ++j ) + { + if( !(stream->outputBuffers[ j ][ i ].dwFlags & WHDR_DONE) ) + { + outputCatchUp = 0; + break; + } + } + } + + if( outputCatchUp ) + { + /* FIXME: this is an output underflow buffer slip and should be flagged as such */ + unsigned int previousBufferIndex = (stream->currentOutputBufferIndex==0) + ? stream->numOutputBuffers - 1 + : stream->currentOutputBufferIndex - 1; + + for( i=0; i < stream->numOutputBuffers - 1; ++i ) + { + for( j=0; jnumOutputDevices; ++j ) + { + if( stream->outputBuffers[j][ stream->currentOutputBufferIndex ].lpData + != stream->outputBuffers[j][ previousBufferIndex ].lpData ) + { + CopyMemory( stream->outputBuffers[j][ stream->currentOutputBufferIndex ].lpData, + stream->outputBuffers[j][ previousBufferIndex ].lpData, + stream->outputBuffers[j][ stream->currentOutputBufferIndex ].dwBufferLength ); + } + } + + result = AdvanceToNextOutputBuffer( stream ); + if( result != paNoError ) + done = 1; + } + } + } + } + + + if( (PA_IS_FULL_DUPLEX_STREAM_(stream) && hostInputBufferIndex != -1 && hostOutputBufferIndex != -1) || + (!PA_IS_FULL_DUPLEX_STREAM_(stream) && ( hostInputBufferIndex != -1 || hostOutputBufferIndex != -1 ) ) ) + { + PaStreamCallbackTimeInfo timeInfo = {0,0,0}; /* @todo implement inputBufferAdcTime and currentTime */ + + + if( hostOutputBufferIndex != -1 ){ + MMTIME time; + double now; + long totalRingFrames; + long ringPosition; + long playbackPosition; + + time.wType = TIME_SAMPLES; + waveOutGetPosition( stream->hWaveOuts[0], &time, sizeof(MMTIME) ); + now = PaUtil_GetTime(); + + totalRingFrames = stream->numOutputBuffers * stream->bufferProcessor.framesPerHostBuffer; + + ringPosition = stream->currentOutputBufferIndex * stream->bufferProcessor.framesPerHostBuffer; + + playbackPosition = time.u.sample % totalRingFrames; + + if( playbackPosition >= ringPosition ){ + timeInfo.outputBufferDacTime = + now + ((double)( ringPosition + (totalRingFrames - playbackPosition) ) * stream->bufferProcessor.samplePeriod ); + }else{ + timeInfo.outputBufferDacTime = + now + ((double)( ringPosition - playbackPosition ) * stream->bufferProcessor.samplePeriod ); + } + } + + + PaUtil_BeginCpuLoadMeasurement( &stream->cpuLoadMeasurer ); + + PaUtil_BeginBufferProcessing( &stream->bufferProcessor, &timeInfo ); + + if( hostInputBufferIndex != -1 ) + { + PaUtil_SetInputFrameCount( &stream->bufferProcessor, 0 /* default to host buffer size */ ); + + channel = 0; + for( i=0; inumInputDevices; ++i ) + { + /* we have stored the number of channels in the buffer in dwUser */ + int channelCount = stream->inputBuffers[i][ hostInputBufferIndex ].dwUser; + + PaUtil_SetInterleavedInputChannels( &stream->bufferProcessor, channel, + stream->inputBuffers[i][ hostInputBufferIndex ].lpData + + stream->framesUsedInCurrentInputBuffer * channelCount * + stream->bufferProcessor.bytesPerHostInputSample, + channelCount ); + + + channel += channelCount; + } + } + + if( hostOutputBufferIndex != -1 ) + { + PaUtil_SetOutputFrameCount( &stream->bufferProcessor, 0 /* default to host buffer size */ ); + + channel = 0; + for( i=0; inumOutputDevices; ++i ) + { + /* we have stored the number of channels in the buffer in dwUser */ + int channelCount = stream->outputBuffers[i][ hostOutputBufferIndex ].dwUser; + + PaUtil_SetInterleavedOutputChannels( &stream->bufferProcessor, channel, + stream->outputBuffers[i][ hostOutputBufferIndex ].lpData + + stream->framesUsedInCurrentOutputBuffer * channelCount * + stream->bufferProcessor.bytesPerHostOutputSample, + channelCount ); + + /* we have stored the number of channels in the buffer in dwUser */ + channel += channelCount; + } + } + + framesProcessed = PaUtil_EndBufferProcessing( &stream->bufferProcessor, &callbackResult ); + + stream->framesUsedInCurrentInputBuffer += framesProcessed; + stream->framesUsedInCurrentOutputBuffer += framesProcessed; + + PaUtil_EndCpuLoadMeasurement( &stream->cpuLoadMeasurer, framesProcessed ); + + if( callbackResult == paContinue ) + { + /* nothing special to do */ + } + else if( callbackResult == paAbort ) + { + stream->abortProcessing = 1; + done = 1; + /* FIXME: should probably do a reset here */ + result = paNoError; + } + else + { + /* User cllback has asked us to stop with paComplete or other non-zero value */ + stream->stopProcessing = 1; /* stop once currently queued audio has finished */ + result = paNoError; + } + + /* + FIXME: the following code is incorrect, because stopProcessing should + still queue the current buffer. + */ + if( stream->stopProcessing == 0 && stream->abortProcessing == 0 ) + { + if( stream->throttleProcessingThreadOnOverload != 0 ) + { + if( PaUtil_GetCpuLoad( &stream->cpuLoadMeasurer ) > 1. ) + { + if( stream->processingThreadPriority != stream->throttledThreadPriority ) + { + SetThreadPriority( stream->processingThread, stream->throttledThreadPriority ); + stream->processingThreadPriority = stream->throttledThreadPriority; + } + +/** @todo: +Gordon Gidluck: +> function: ProcessingThreadProc() +> line #1947/1948 Sleep( stream->bufferProcessor.framesPerHostBuffer * +> stream->bufferProcessor.samplePeriod * .25 ); +> conversion from 'double ' to 'unsigned long ', possible loss of data +> integral size mismatch in argument; conversion supplied +*/ + /* sleep for a quater of a buffer's duration to give other processes a go */ + Sleep( stream->bufferProcessor.framesPerHostBuffer * + stream->bufferProcessor.samplePeriod * .25 ); + } + else + { + if( stream->processingThreadPriority != stream->highThreadPriority ) + { + SetThreadPriority( stream->processingThread, stream->highThreadPriority ); + stream->processingThreadPriority = stream->highThreadPriority; + } + } + } + + if( PA_IS_INPUT_STREAM_(stream) && + stream->framesUsedInCurrentInputBuffer == stream->framesPerInputBuffer ) + { + result = AdvanceToNextInputBuffer( stream ); + if( result != paNoError ) + done = 1; + } + + if( PA_IS_OUTPUT_STREAM_(stream) && + stream->framesUsedInCurrentOutputBuffer == stream->framesPerOutputBuffer ) + { + result = AdvanceToNextOutputBuffer( stream ); + if( result != paNoError ) + done = 1; + } + } + } + else + { + hostBuffersAvailable = 0; + } + } + while( hostBuffersAvailable && + stream->stopProcessing == 0 && + stream->abortProcessing == 0 && + !done ); + } + + result = UpdateStreamTime( stream ); + if( result != paNoError ) + done = 1; + } + + stream->isActive = 0; + + if( stream->streamRepresentation.streamFinishedCallback != 0 ) + stream->streamRepresentation.streamFinishedCallback( stream->streamRepresentation.userData ); + + return result; +} + + +/* + When CloseStream() is called, the multi-api layer ensures that + the stream has already been stopped or aborted. +*/ +static PaError CloseStream( PaStream* s ) +{ + PaError result = paNoError; + PaWinMmeStream *stream = (PaWinMmeStream*)s; + MMRESULT mmresult; + unsigned int i; + + if( PA_IS_INPUT_STREAM_(stream) ) + { + for( i=0; inumInputDevices; ++i ) + { + TerminateBufferSet( &stream->inputBuffers[i], stream->numInputBuffers, + 1 /* isInput */, (MmeHandle)stream->hWaveIns[i] ); + } + + PaUtil_FreeMemory( stream->inputBuffers ); + } + + if( PA_IS_OUTPUT_STREAM_(stream) ) + { + for( i=0; inumOutputDevices; ++i ) + { + TerminateBufferSet( &stream->outputBuffers[i], stream->numOutputBuffers, + 0 /* not isInput */, (MmeHandle)stream->hWaveOuts[i] ); + } + + PaUtil_FreeMemory( stream->outputBuffers ); + } + + + if( PA_IS_INPUT_STREAM_(stream) ) + { + for( i=0; inumInputDevices; ++i ) + { + mmresult = waveInClose( stream->hWaveIns[i] ); + if( mmresult != MMSYSERR_NOERROR ) + { + result = paUnanticipatedHostError; + PA_MME_SET_LAST_WAVEIN_ERROR( mmresult ); + goto error; + } + } + + PaUtil_FreeMemory( stream->hWaveIns ); + } + + if( PA_IS_OUTPUT_STREAM_(stream) ) + { + for( i=0; inumOutputDevices; ++i ) + { + mmresult = waveOutClose( stream->hWaveOuts[i] ); + if( mmresult != MMSYSERR_NOERROR ) + { + result = paUnanticipatedHostError; + PA_MME_SET_LAST_WAVEOUT_ERROR( mmresult ); + goto error; + } + } + + PaUtil_FreeMemory( stream->hWaveOuts ); + } + + if( CloseHandle( stream->bufferEvent ) == 0 ) + { + result = paUnanticipatedHostError; + PA_MME_SET_LAST_SYSTEM_ERROR( GetLastError() ); + goto error; + } + + if( CloseHandle( stream->abortEvent ) == 0 ) + { + result = paUnanticipatedHostError; + PA_MME_SET_LAST_SYSTEM_ERROR( GetLastError() ); + goto error; + } + + DeleteCriticalSection( &stream->lock ); + + PaUtil_TerminateBufferProcessor( &stream->bufferProcessor ); + PaUtil_TerminateStreamRepresentation( &stream->streamRepresentation ); + PaUtil_FreeMemory( stream ); + +error: + /* FIXME: consider how to best clean up on failure */ + return result; +} + + +static PaError StartStream( PaStream *s ) +{ + PaError result = paNoError; + PaWinMmeStream *stream = (PaWinMmeStream*)s; + MMRESULT mmresult; + unsigned int i, j; + + if( PA_IS_INPUT_STREAM_(stream) ) + { + for( i=0; inumInputBuffers; ++i ) + { + for( j=0; jnumInputDevices; ++j ) + { + mmresult = waveInAddBuffer( stream->hWaveIns[j], &stream->inputBuffers[j][i], sizeof(WAVEHDR) ); + if( mmresult != MMSYSERR_NOERROR ) + { + result = paUnanticipatedHostError; + PA_MME_SET_LAST_WAVEIN_ERROR( mmresult ); + goto error; + } + } + } + stream->currentInputBufferIndex = 0; + stream->framesUsedInCurrentInputBuffer = 0; + } + + if( PA_IS_OUTPUT_STREAM_(stream) ) + { + for( i=0; inumOutputDevices; ++i ) + { + if( (mmresult = waveOutPause( stream->hWaveOuts[i] )) != MMSYSERR_NOERROR ) + { + result = paUnanticipatedHostError; + PA_MME_SET_LAST_WAVEOUT_ERROR( mmresult ); + goto error; + } + } + + for( i=0; inumOutputBuffers; ++i ) + { + for( j=0; jnumOutputDevices; ++j ) + { + ZeroMemory( stream->outputBuffers[j][i].lpData, stream->outputBuffers[j][i].dwBufferLength ); + mmresult = waveOutWrite( stream->hWaveOuts[j], &stream->outputBuffers[j][i], sizeof(WAVEHDR) ); + if( mmresult != MMSYSERR_NOERROR ) + { + result = paUnanticipatedHostError; + PA_MME_SET_LAST_WAVEOUT_ERROR( mmresult ); + goto error; + } + } + } + stream->currentOutputBufferIndex = 0; + stream->framesUsedInCurrentOutputBuffer = 0; + } + + stream->streamPosition = 0.; + stream->previousStreamPosition = 0; + + stream->isActive = 1; + stream->stopProcessing = 0; + stream->abortProcessing = 0; + + if( ResetEvent( stream->bufferEvent ) == 0 ) + { + result = paUnanticipatedHostError; + PA_MME_SET_LAST_SYSTEM_ERROR( GetLastError() ); + goto error; + } + + if( ResetEvent( stream->abortEvent ) == 0 ) + { + result = paUnanticipatedHostError; + PA_MME_SET_LAST_SYSTEM_ERROR( GetLastError() ); + goto error; + } + + /* Create thread that waits for audio buffers to be ready for processing. */ + stream->processingThread = CreateThread( 0, 0, ProcessingThreadProc, stream, 0, &stream->processingThreadId ); + if( !stream->processingThread ) + { + result = paUnanticipatedHostError; + PA_MME_SET_LAST_SYSTEM_ERROR( GetLastError() ); + goto error; + } + + /* I used to pass the thread which was failing. I now pass GetCurrentProcess(). + * This fix could improve latency for some applications. It could also result in CPU + * starvation if the callback did too much processing. + * I also added result checks, so we might see more failures at initialization. + * Thanks to Alberto di Bene for spotting this. + */ + /* REVIEW: should we reset the priority class when the stream has stopped? + - would be best to refcount priority boosts incase more than one + stream is open + */ + + if( !stream->noHighPriorityProcessClass ) + { +#ifndef WIN32_PLATFORM_PSPC /* no SetPriorityClass or HIGH_PRIORITY_CLASS on PocketPC */ + + if( !SetPriorityClass( GetCurrentProcess(), HIGH_PRIORITY_CLASS ) ) /* PLB20010816 */ + { + result = paUnanticipatedHostError; + PA_MME_SET_LAST_SYSTEM_ERROR( GetLastError() ); + goto error; + } +#endif + } + + if( stream->useTimeCriticalProcessingThreadPriority ) + stream->highThreadPriority = THREAD_PRIORITY_TIME_CRITICAL; + else + stream->highThreadPriority = THREAD_PRIORITY_HIGHEST; + + stream->throttledThreadPriority = THREAD_PRIORITY_NORMAL; + + if( !SetThreadPriority( stream->processingThread, stream->highThreadPriority ) ) + { + result = paUnanticipatedHostError; + PA_MME_SET_LAST_SYSTEM_ERROR( GetLastError() ); + goto error; + } + stream->processingThreadPriority = stream->highThreadPriority; + + + if( PA_IS_INPUT_STREAM_(stream) ) + { + for( i=0; i < stream->numInputDevices; ++i ) + { + mmresult = waveInStart( stream->hWaveIns[i] ); + PA_DEBUG(("Pa_StartStream: waveInStart returned = 0x%X.\n", mmresult)); + if( mmresult != MMSYSERR_NOERROR ) + { + result = paUnanticipatedHostError; + PA_MME_SET_LAST_WAVEIN_ERROR( mmresult ); + goto error; + } + } + } + + if( PA_IS_OUTPUT_STREAM_(stream) ) + { + for( i=0; i < stream->numOutputDevices; ++i ) + { + if( (mmresult = waveOutRestart( stream->hWaveOuts[i] )) != MMSYSERR_NOERROR ) + { + result = paUnanticipatedHostError; + PA_MME_SET_LAST_WAVEOUT_ERROR( mmresult ); + goto error; + } + } + } + + return result; + +error: + /* FIXME: implement recovery as best we can + This should involve rolling back to a state as-if this function had never been called + */ + return result; +} + + +static PaError StopStream( PaStream *s ) +{ + PaError result = paNoError; + PaWinMmeStream *stream = (PaWinMmeStream*)s; + int timeout; + DWORD waitResult; + MMRESULT mmresult; + unsigned int i; + + /* + FIXME: the error checking in this function needs review. the basic + idea is to return from this function in a known state - for example + there is no point avoiding calling waveInReset just because + the thread times out. + */ + + + /* Tell processing thread to stop generating more data and to let current data play out. */ + stream->stopProcessing = 1; + + /* Calculate timeOut longer than longest time it could take to return all buffers. */ + timeout = stream->allBuffersDurationMs * 1.5; + if( timeout < PA_MIN_TIMEOUT_MSEC_ ) + timeout = PA_MIN_TIMEOUT_MSEC_; + + PA_DEBUG(("WinMME StopStream: waiting for background thread.\n")); + + waitResult = WaitForSingleObject( stream->processingThread, timeout ); + if( waitResult == WAIT_TIMEOUT ) + { + /* try to abort */ + stream->abortProcessing = 1; + SetEvent( stream->abortEvent ); + waitResult = WaitForSingleObject( stream->processingThread, timeout ); + if( waitResult == WAIT_TIMEOUT ) + { + PA_DEBUG(("WinMME StopStream: timed out while waiting for background thread to finish.\n")); + result = paTimedOut; + } + } + + CloseHandle( stream->processingThread ); + stream->processingThread = NULL; + + if( PA_IS_OUTPUT_STREAM_(stream) ) + { + for( i =0; i < stream->numOutputDevices; ++i ) + { + mmresult = waveOutReset( stream->hWaveOuts[i] ); + if( mmresult != MMSYSERR_NOERROR ) + { + result = paUnanticipatedHostError; + PA_MME_SET_LAST_WAVEOUT_ERROR( mmresult ); + } + } + } + + if( PA_IS_INPUT_STREAM_(stream) ) + { + for( i=0; i < stream->numInputDevices; ++i ) + { + mmresult = waveInReset( stream->hWaveIns[i] ); + if( mmresult != MMSYSERR_NOERROR ) + { + result = paUnanticipatedHostError; + PA_MME_SET_LAST_WAVEIN_ERROR( mmresult ); + } + } + } + + stream->isActive = 0; + + return result; +} + + +static PaError AbortStream( PaStream *s ) +{ + PaError result = paNoError; + PaWinMmeStream *stream = (PaWinMmeStream*)s; + int timeout; + DWORD waitResult; + MMRESULT mmresult; + unsigned int i; + + /* + FIXME: the error checking in this function needs review. the basic + idea is to return from this function in a known state - for example + there is no point avoiding calling waveInReset just because + the thread times out. + */ + + /* Tell processing thread to abort immediately */ + stream->abortProcessing = 1; + SetEvent( stream->abortEvent ); + + /* Calculate timeOut longer than longest time it could take to return all buffers. */ + timeout = stream->allBuffersDurationMs * 1.5; + if( timeout < PA_MIN_TIMEOUT_MSEC_ ) + timeout = PA_MIN_TIMEOUT_MSEC_; + + if( PA_IS_OUTPUT_STREAM_(stream) ) + { + for( i =0; i < stream->numOutputDevices; ++i ) + { + mmresult = waveOutReset( stream->hWaveOuts[i] ); + if( mmresult != MMSYSERR_NOERROR ) + { + PA_MME_SET_LAST_WAVEOUT_ERROR( mmresult ); + return paUnanticipatedHostError; + } + } + } + + if( PA_IS_INPUT_STREAM_(stream) ) + { + for( i=0; i < stream->numInputDevices; ++i ) + { + mmresult = waveInReset( stream->hWaveIns[i] ); + if( mmresult != MMSYSERR_NOERROR ) + { + PA_MME_SET_LAST_WAVEIN_ERROR( mmresult ); + return paUnanticipatedHostError; + } + } + } + + + PA_DEBUG(("WinMME AbortStream: waiting for background thread.\n")); + + waitResult = WaitForSingleObject( stream->processingThread, timeout ); + if( waitResult == WAIT_TIMEOUT ) + { + PA_DEBUG(("WinMME AbortStream: timed out while waiting for background thread to finish.\n")); + return paTimedOut; + } + + CloseHandle( stream->processingThread ); + stream->processingThread = NULL; + + stream->isActive = 0; + + return result; +} + + +static PaError IsStreamStopped( PaStream *s ) +{ + PaWinMmeStream *stream = (PaWinMmeStream*)s; + + return ( stream->processingThread == NULL ); +} + + +static PaError IsStreamActive( PaStream *s ) +{ + PaWinMmeStream *stream = (PaWinMmeStream*)s; + + return stream->isActive; +} + + +/* UpdateStreamTime() must be called periodically because mmtime.u.sample + is a DWORD and can wrap and lose sync after a few hours. + */ +static PaError UpdateStreamTime( PaWinMmeStream *stream ) +{ + MMRESULT mmresult; + MMTIME mmtime; + mmtime.wType = TIME_SAMPLES; + + if( stream->hWaveOuts ) + { + /* assume that all devices have the same position */ + mmresult = waveOutGetPosition( stream->hWaveOuts[0], &mmtime, sizeof(mmtime) ); + + if( mmresult != MMSYSERR_NOERROR ) + { + PA_MME_SET_LAST_WAVEOUT_ERROR( mmresult ); + return paUnanticipatedHostError; + } + } + else + { + /* assume that all devices have the same position */ + mmresult = waveInGetPosition( stream->hWaveIns[0], &mmtime, sizeof(mmtime) ); + + if( mmresult != MMSYSERR_NOERROR ) + { + PA_MME_SET_LAST_WAVEIN_ERROR( mmresult ); + return paUnanticipatedHostError; + } + } + + + /* This data has two variables and is shared by foreground and background. + * So we need to make it thread safe. */ + EnterCriticalSection( &stream->lock ); + stream->streamPosition += ((long)mmtime.u.sample) - stream->previousStreamPosition; + stream->previousStreamPosition = (long)mmtime.u.sample; + LeaveCriticalSection( &stream->lock ); + + return paNoError; +} + + +static PaTime GetStreamTime( PaStream *s ) +{ +/* + new behavior for GetStreamTime is to return a stream based seconds clock + used for the outTime parameter to the callback. + FIXME: delete this comment when the other unnecessary related code has + been cleaned from this file. + + PaWinMmeStream *stream = (PaWinMmeStream*)s; + PaError error = UpdateStreamTime( stream ); + + if( error == paNoError ) + return stream->streamPosition; + else + return 0; +*/ + (void) s; /* unused parameter */ + return PaUtil_GetTime(); +} + + +static double GetStreamCpuLoad( PaStream* s ) +{ + PaWinMmeStream *stream = (PaWinMmeStream*)s; + + return PaUtil_GetCpuLoad( &stream->cpuLoadMeasurer ); +} + + +/* + As separate stream interfaces are used for blocking and callback + streams, the following functions can be guaranteed to only be called + for blocking streams. +*/ + +static PaError ReadStream( PaStream* s, + void *buffer, + unsigned long frames ) +{ + PaWinMmeStream *stream = (PaWinMmeStream*)s; + + /* IMPLEMENT ME, see portaudio.h for required behavior*/ + (void) stream; /* unused parameters */ + (void) buffer; + (void) frames; + + return paNoError; +} + + +static PaError WriteStream( PaStream* s, + void *buffer, + unsigned long frames ) +{ + PaWinMmeStream *stream = (PaWinMmeStream*)s; + + /* IMPLEMENT ME, see portaudio.h for required behavior*/ + (void) stream; /* unused parameters */ + (void) buffer; + (void) frames; + + return paNoError; +} + + +static signed long GetStreamReadAvailable( PaStream* s ) +{ + PaWinMmeStream *stream = (PaWinMmeStream*)s; + + /* IMPLEMENT ME, see portaudio.h for required behavior*/ + (void) stream; /* unused parameter */ + + return 0; +} + + +static signed long GetStreamWriteAvailable( PaStream* s ) +{ + PaWinMmeStream *stream = (PaWinMmeStream*)s; + + /* IMPLEMENT ME, see portaudio.h for required behavior*/ + (void) stream; /* unused parameter */ + + return 0; +} + + + + -- cgit v1.2.1