summaryrefslogtreecommitdiff
path: root/portaudio/src/hostapi/wmme
diff options
context:
space:
mode:
authorsanine <sanine.not@pm.me>2022-08-25 14:54:53 -0500
committersanine <sanine.not@pm.me>2022-08-25 14:54:53 -0500
commit37c97e345d12f95dde44e1d1a4c2f2aadd4615bc (patch)
treee1bb25bc855883062bdd7847ff2c04290f71c840 /portaudio/src/hostapi/wmme
parent5634c7b04da619669f2f29f6798c03982be05180 (diff)
add initial structure
Diffstat (limited to 'portaudio/src/hostapi/wmme')
-rw-r--r--portaudio/src/hostapi/wmme/pa_win_wmme.c4033
1 files changed, 4033 insertions, 0 deletions
diff --git a/portaudio/src/hostapi/wmme/pa_win_wmme.c b/portaudio/src/hostapi/wmme/pa_win_wmme.c
new file mode 100644
index 0000000..f8b1d7e
--- /dev/null
+++ b/portaudio/src/hostapi/wmme/pa_win_wmme.c
@@ -0,0 +1,4033 @@
+/*
+ * $Id$
+ * 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.
+ *
+ * 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.
+ */
+
+/*
+ * The text above constitutes the entire PortAudio license; however,
+ * the PortAudio community also makes the following non-binding requests:
+ *
+ * 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. It is also
+ * requested that these non-binding requests be included along with the
+ * license above.
+ */
+
+/* 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 maintenance history is now stored in CVS **
+*/
+
+/** @file
+ @ingroup hostapi_src
+
+ @brief Win32 host API implementation for the Windows MultiMedia Extensions (WMME) audio API.
+*/
+
+/*
+ How it works:
+
+ For both callback and blocking read/write streams we open the MME devices
+ in CALLBACK_EVENT mode. In this mode, MME signals an Event object whenever
+ it has finished with a buffer (either filled it for input, or played it
+ for output). Where necessary, we block waiting for Event objects using
+ WaitMultipleObjects().
+
+ When implementing a PA callback stream, we set up a high priority thread
+ which waits on the MME buffer Events and drains/fills the buffers when
+ they are ready.
+
+ When implementing a PA blocking read/write stream, we simply wait on these
+ Events (when necessary) inside the ReadStream() and WriteStream() functions.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <math.h>
+#include <windows.h>
+#include <mmsystem.h>
+#ifndef UNDER_CE
+#include <process.h>
+#endif
+#include <assert.h>
+/* PLB20010422 - "memory.h" doesn't work on CodeWarrior for PC. Thanks Mike Berry for the mod. */
+#ifndef __MWERKS__
+#include <malloc.h>
+#include <memory.h>
+#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_debugprint.h"
+
+#include "pa_win_wmme.h"
+#include "pa_win_waveformat.h"
+
+#ifdef PAWIN_USE_WDMKS_DEVICE_INFO
+#include "pa_win_wdmks_utils.h"
+#ifndef DRV_QUERYDEVICEINTERFACE
+#define DRV_QUERYDEVICEINTERFACE (DRV_RESERVED + 12)
+#endif
+#ifndef DRV_QUERYDEVICEINTERFACESIZE
+#define DRV_QUERYDEVICEINTERFACESIZE (DRV_RESERVED + 13)
+#endif
+#endif /* PAWIN_USE_WDMKS_DEVICE_INFO */
+
+/* use CreateThread for CYGWIN, _beginthreadex for all others */
+#if !defined(__CYGWIN__) && !defined(_WIN32_WCE)
+#define CREATE_THREAD (HANDLE)_beginthreadex( 0, 0, ProcessingThreadProc, stream, 0, &stream->processingThreadId )
+#define PA_THREAD_FUNC static unsigned WINAPI
+#define PA_THREAD_ID unsigned
+#else
+#define CREATE_THREAD CreateThread( 0, 0, ProcessingThreadProc, stream, 0, &stream->processingThreadId )
+#define PA_THREAD_FUNC static DWORD WINAPI
+#define PA_THREAD_ID DWORD
+#endif
+#if (defined(_WIN32_WCE))
+#pragma comment(lib, "Coredll.lib")
+#elif (defined(WIN32) && (defined(_MSC_VER) && (_MSC_VER >= 1200))) /* MSC version 6 and above */
+#pragma comment(lib, "winmm.lib")
+#endif
+
+/*
+ provided in newer platform sdks
+ */
+#ifndef DWORD_PTR
+ #if defined(_WIN64)
+ #define DWORD_PTR unsigned __int64
+ #else
+ #define DWORD_PTR unsigned long
+ #endif
+#endif
+
+/************************************************* Constants ********/
+
+#define PA_MME_USE_HIGH_DEFAULT_LATENCY_ (0) /* For debugging glitches. */
+
+#if PA_MME_USE_HIGH_DEFAULT_LATENCY_
+ #define PA_MME_WIN_9X_DEFAULT_LATENCY_ (0.4)
+ #define PA_MME_MIN_HOST_OUTPUT_BUFFER_COUNT_ (4)
+ #define PA_MME_MIN_HOST_INPUT_BUFFER_COUNT_FULL_DUPLEX_ (4)
+ #define PA_MME_MIN_HOST_INPUT_BUFFER_COUNT_HALF_DUPLEX_ (4)
+ #define PA_MME_HOST_BUFFER_GRANULARITY_FRAMES_WHEN_UNSPECIFIED_ (16)
+ #define PA_MME_MAX_HOST_BUFFER_SECS_ (0.3) /* Do not exceed unless user buffer exceeds */
+ #define PA_MME_MAX_HOST_BUFFER_BYTES_ (32 * 1024) /* Has precedence over PA_MME_MAX_HOST_BUFFER_SECS_, some drivers are known to crash with buffer sizes > 32k */
+#else
+ #define PA_MME_WIN_9X_DEFAULT_LATENCY_ (0.2)
+ #define PA_MME_MIN_HOST_OUTPUT_BUFFER_COUNT_ (2)
+ #define PA_MME_MIN_HOST_INPUT_BUFFER_COUNT_FULL_DUPLEX_ (3) /* always use at least 3 input buffers for full duplex */
+ #define PA_MME_MIN_HOST_INPUT_BUFFER_COUNT_HALF_DUPLEX_ (2)
+ #define PA_MME_HOST_BUFFER_GRANULARITY_FRAMES_WHEN_UNSPECIFIED_ (16)
+ #define PA_MME_MAX_HOST_BUFFER_SECS_ (0.1) /* Do not exceed unless user buffer exceeds */
+ #define PA_MME_MAX_HOST_BUFFER_BYTES_ (32 * 1024) /* Has precedence over PA_MME_MAX_HOST_BUFFER_SECS_, some drivers are known to crash with buffer sizes > 32k */
+#endif
+
+/* Use higher latency for NT because it is even worse at real-time
+ operation than Win9x.
+*/
+#define PA_MME_WIN_NT_DEFAULT_LATENCY_ (0.4)
+
+/* Default low latency for WDM based systems. This is based on a rough
+ survey of workable latency settings using patest_wmme_find_best_latency_params.c.
+ See pdf attached to ticket 185 for a graph of the survey results:
+ http://www.portaudio.com/trac/ticket/185
+
+ Workable latencies varied between 40ms and ~80ms on different systems (different
+ combinations of hardware, 32 and 64 bit, WinXP, Vista and Win7. We didn't
+ get enough Vista results to know if Vista has systemically worse latency.
+ For now we choose a safe value across all Windows versions here.
+*/
+#define PA_MME_WIN_WDM_DEFAULT_LATENCY_ (0.090)
+
+
+/* When client suggestedLatency could result in many host buffers, we aim to have around 8,
+ based off Windows documentation that suggests that the kmixer uses 8 buffers. This choice
+ is somewhat arbitrary here, since we haven't observed significant stability degradation
+ with using either more, or less buffers.
+*/
+#define PA_MME_TARGET_HOST_BUFFER_COUNT_ 8
+
+#define PA_MME_MIN_TIMEOUT_MSEC_ (1000)
+
+static const char constInputMapperSuffix_[] = " - Input";
+static const char constOutputMapperSuffix_[] = " - Output";
+
+/********************************************************************/
+
+/* Copy null-terminated WCHAR string to explicit char string using UTF8 encoding */
+static char *CopyWCharStringToUtf8CString(char *destination, size_t destLengthBytes, const WCHAR *source)
+{
+ /* The cbMultiByte parameter ["destLengthBytes" below] is:
+ """
+ Size, in bytes, of the buffer indicated by lpMultiByteStr ["destination" below].
+ If this parameter is set to 0, the function returns the required buffer
+ size for lpMultiByteStr and makes no use of the output parameter itself.
+ """
+ Source: WideCharToMultiByte at MSDN:
+ http://msdn.microsoft.com/en-us/library/windows/desktop/dd374130(v=vs.85).aspx
+ */
+ int intDestLengthBytes; /* cbMultiByte */
+ /* intDestLengthBytes is an int, destLengthBytes is a size_t. Ensure that we don't overflow
+ intDestLengthBytes by only using at most INT_MAX bytes of destination buffer.
+ */
+ if (destLengthBytes < INT_MAX)
+ {
+#pragma warning (disable : 4267) /* "conversion from 'size_t' to 'int', possible loss of data" */
+ intDestLengthBytes = (int)destLengthBytes; /* destLengthBytes is guaranteed < INT_MAX here */
+#pragma warning (default : 4267)
+ }
+ else
+ {
+ intDestLengthBytes = INT_MAX;
+ }
+
+ if (WideCharToMultiByte(CP_UTF8, 0, source, -1, destination, /*cbMultiByte=*/intDestLengthBytes, NULL, NULL) == 0)
+ return NULL;
+ return destination;
+}
+
+/* returns required length (in bytes) of destination buffer when
+ converting WCHAR string to UTF8 bytes, not including the terminating null. */
+static size_t WCharStringLen(const WCHAR *str)
+{
+ return WideCharToMultiByte(CP_UTF8, 0, str, -1, NULL, 0, NULL, NULL);
+}
+
+/********************************************************************/
+
+typedef struct PaWinMmeStream PaWinMmeStream; /* forward declaration */
+
+/* prototypes for functions declared in this file */
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+PaError PaWinMme_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index );
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+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, const void *buffer, unsigned long frames );
+static signed long GetStreamReadAvailable( PaStream* stream );
+static signed long GetStreamWriteAvailable( PaStream* stream );
+
+
+/* macros for setting last host error information */
+
+#define PA_MME_SET_LAST_WAVEIN_ERROR( mmresult ) \
+ { \
+ wchar_t mmeErrorTextWide[ MAXERRORLENGTH ]; \
+ char mmeErrorText[ MAXERRORLENGTH ]; \
+ waveInGetErrorTextW( mmresult, mmeErrorTextWide, MAXERRORLENGTH ); \
+ WideCharToMultiByte( CP_UTF8, 0, 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 ]; \
+ waveOutGetErrorTextW( mmresult, mmeErrorTextWide, MAXERRORLENGTH ); \
+ WideCharToMultiByte( CP_UTF8, 0, mmeErrorTextWide, -1, \
+ mmeErrorText, MAXERRORLENGTH, NULL, NULL ); \
+ PaUtil_SetLastHostErrorInfo( paMME, mmresult, mmeErrorText ); \
+ }
+
+
+static void PaMme_SetLastSystemError( DWORD errorCode )
+{
+ char *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 )
+
+
+/* PaError returning wrappers for some commonly used win32 functions
+ note that we allow passing a null ptr to have no effect.
+*/
+
+static PaError CreateEventWithPaError( HANDLE *handle,
+ LPSECURITY_ATTRIBUTES lpEventAttributes,
+ BOOL bManualReset,
+ BOOL bInitialState,
+ LPCTSTR lpName )
+{
+ PaError result = paNoError;
+
+ *handle = NULL;
+
+ *handle = CreateEvent( lpEventAttributes, bManualReset, bInitialState, lpName );
+ if( *handle == NULL )
+ {
+ result = paUnanticipatedHostError;
+ PA_MME_SET_LAST_SYSTEM_ERROR( GetLastError() );
+ }
+
+ return result;
+}
+
+
+static PaError ResetEventWithPaError( HANDLE handle )
+{
+ PaError result = paNoError;
+
+ if( handle )
+ {
+ if( ResetEvent( handle ) == 0 )
+ {
+ result = paUnanticipatedHostError;
+ PA_MME_SET_LAST_SYSTEM_ERROR( GetLastError() );
+ }
+ }
+
+ return result;
+}
+
+
+static PaError CloseHandleWithPaError( HANDLE handle )
+{
+ PaError result = paNoError;
+
+ if( handle )
+ {
+ if( CloseHandle( handle ) == 0 )
+ {
+ result = paUnanticipatedHostError;
+ PA_MME_SET_LAST_SYSTEM_ERROR( GetLastError() );
+ }
+ }
+
+ return result;
+}
+
+
+/* PaWinMmeHostApiRepresentation - host api datastructure specific to this implementation */
+
+typedef struct
+{
+ PaUtilHostApiRepresentation inheritedHostApiRep;
+ PaUtilStreamInterface callbackStreamInterface;
+ PaUtilStreamInterface blockingStreamInterface;
+
+ PaUtilAllocationGroup *allocations;
+
+ int inputDeviceCount, outputDeviceCount;
+
+ /** winMmeDeviceIds is an array of WinMme device ids.
+ fields in the range [0, inputDeviceCount) are input device ids,
+ and [inputDeviceCount, inputDeviceCount + outputDeviceCount) are output
+ device ids.
+ */
+ UINT *winMmeDeviceIds;
+}
+PaWinMmeHostApiRepresentation;
+
+
+typedef struct
+{
+ PaDeviceInfo inheritedDeviceInfo;
+ DWORD dwFormats; /**<< standard formats bitmask from the WAVEINCAPS and WAVEOUTCAPS structures */
+ char deviceInputChannelCountIsKnown; /**<< if the system returns 0xFFFF then we don't really know the number of supported channels (1=>known, 0=>unknown)*/
+ char deviceOutputChannelCountIsKnown; /**<< if the system returns 0xFFFF then we don't really know the number of supported channels (1=>known, 0=>unknown)*/
+}
+PaWinMmeDeviceInfo;
+
+
+/*************************************************************************
+ * 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 = GetEnvironmentVariableA( 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 UINT LocalDeviceIndexToWinMmeDeviceId( PaWinMmeHostApiRepresentation *hostApi, PaDeviceIndex device )
+{
+ assert( device >= 0 && device < hostApi->inputDeviceCount + hostApi->outputDeviceCount );
+
+ return hostApi->winMmeDeviceIds[ device ];
+}
+
+
+static int SampleFormatAndWinWmmeSpecificFlagsToLinearWaveFormatTag( PaSampleFormat sampleFormat, unsigned long winMmeSpecificFlags )
+{
+ int waveFormatTag = 0;
+
+ if( winMmeSpecificFlags & paWinMmeWaveFormatDolbyAc3Spdif )
+ waveFormatTag = PAWIN_WAVE_FORMAT_DOLBY_AC3_SPDIF;
+ else if( winMmeSpecificFlags & paWinMmeWaveFormatWmaSpdif )
+ waveFormatTag = PAWIN_WAVE_FORMAT_WMA_SPDIF;
+ else
+ waveFormatTag = PaWin_SampleFormatToLinearWaveFormatTag( sampleFormat );
+
+ return waveFormatTag;
+}
+
+
+static PaError QueryInputWaveFormatEx( int deviceId, WAVEFORMATEX *waveFormatEx )
+{
+ MMRESULT mmresult;
+
+ switch( mmresult = waveInOpen( NULL, deviceId, waveFormatEx, 0, 0, WAVE_FORMAT_QUERY ) )
+ {
+ case MMSYSERR_NOERROR:
+ return paNoError;
+ case MMSYSERR_ALLOCATED: /* Specified resource is already allocated. */
+ return paDeviceUnavailable;
+ case MMSYSERR_NODRIVER: /* No device driver is present. */
+ return paDeviceUnavailable;
+ case MMSYSERR_NOMEM: /* Unable to allocate or lock memory. */
+ return paInsufficientMemory;
+ case WAVERR_BADFORMAT: /* Attempted to open with an unsupported waveform-audio format. */
+ return paSampleFormatNotSupported;
+
+ case MMSYSERR_BADDEVICEID: /* Specified device identifier is out of range. */
+ /* falls through */
+ default:
+ PA_MME_SET_LAST_WAVEIN_ERROR( mmresult );
+ return paUnanticipatedHostError;
+ }
+}
+
+
+static PaError QueryOutputWaveFormatEx( int deviceId, WAVEFORMATEX *waveFormatEx )
+{
+ MMRESULT mmresult;
+
+ switch( mmresult = waveOutOpen( NULL, deviceId, waveFormatEx, 0, 0, WAVE_FORMAT_QUERY ) )
+ {
+ case MMSYSERR_NOERROR:
+ return paNoError;
+ case MMSYSERR_ALLOCATED: /* Specified resource is already allocated. */
+ return paDeviceUnavailable;
+ case MMSYSERR_NODRIVER: /* No device driver is present. */
+ return paDeviceUnavailable;
+ case MMSYSERR_NOMEM: /* Unable to allocate or lock memory. */
+ return paInsufficientMemory;
+ case WAVERR_BADFORMAT: /* Attempted to open with an unsupported waveform-audio format. */
+ return paSampleFormatNotSupported;
+
+ case MMSYSERR_BADDEVICEID: /* Specified device identifier is out of range. */
+ /* falls through */
+ default:
+ PA_MME_SET_LAST_WAVEOUT_ERROR( mmresult );
+ return paUnanticipatedHostError;
+ }
+}
+
+
+static PaError QueryFormatSupported( PaDeviceInfo *deviceInfo,
+ PaError (*waveFormatExQueryFunction)(int, WAVEFORMATEX*),
+ int winMmeDeviceId, int channels, double sampleRate, unsigned long winMmeSpecificFlags )
+{
+ PaWinMmeDeviceInfo *winMmeDeviceInfo = (PaWinMmeDeviceInfo*)deviceInfo;
+ PaWinWaveFormat waveFormat;
+ PaSampleFormat sampleFormat;
+ int waveFormatTag;
+
+ /* @todo at the moment we only query with 16 bit sample format and directout speaker config*/
+
+ sampleFormat = paInt16;
+ waveFormatTag = SampleFormatAndWinWmmeSpecificFlagsToLinearWaveFormatTag( sampleFormat, winMmeSpecificFlags );
+
+ if( waveFormatTag == PaWin_SampleFormatToLinearWaveFormatTag( paInt16 ) ){
+
+ /* attempt bypass querying the device for linear formats */
+
+ if( sampleRate == 11025.0
+ && ( (channels == 1 && (winMmeDeviceInfo->dwFormats & WAVE_FORMAT_1M16))
+ || (channels == 2 && (winMmeDeviceInfo->dwFormats & WAVE_FORMAT_1S16)) ) ){
+
+ return paNoError;
+ }
+
+ if( sampleRate == 22050.0
+ && ( (channels == 1 && (winMmeDeviceInfo->dwFormats & WAVE_FORMAT_2M16))
+ || (channels == 2 && (winMmeDeviceInfo->dwFormats & WAVE_FORMAT_2S16)) ) ){
+
+ return paNoError;
+ }
+
+ if( sampleRate == 44100.0
+ && ( (channels == 1 && (winMmeDeviceInfo->dwFormats & WAVE_FORMAT_4M16))
+ || (channels == 2 && (winMmeDeviceInfo->dwFormats & WAVE_FORMAT_4S16)) ) ){
+
+ return paNoError;
+ }
+ }
+
+
+ /* first, attempt to query the device using WAVEFORMATEXTENSIBLE,
+ if this fails we fall back to WAVEFORMATEX */
+
+ PaWin_InitializeWaveFormatExtensible( &waveFormat, channels, sampleFormat, waveFormatTag,
+ sampleRate, PAWIN_SPEAKER_DIRECTOUT );
+
+ if( waveFormatExQueryFunction( winMmeDeviceId, (WAVEFORMATEX*)&waveFormat ) == paNoError )
+ return paNoError;
+
+ PaWin_InitializeWaveFormatEx( &waveFormat, channels, sampleFormat, waveFormatTag, sampleRate );
+
+ return waveFormatExQueryFunction( winMmeDeviceId, (WAVEFORMATEX*)&waveFormat );
+}
+
+
+#define PA_DEFAULTSAMPLERATESEARCHORDER_COUNT_ (13) /* must match array length below */
+static double defaultSampleRateSearchOrder_[] =
+ { 44100.0, 48000.0, 32000.0, 24000.0, 22050.0, 88200.0, 96000.0, 192000.0,
+ 16000.0, 12000.0, 11025.0, 9600.0, 8000.0 };
+
+static void DetectDefaultSampleRate( PaWinMmeDeviceInfo *winMmeDeviceInfo, int winMmeDeviceId,
+ PaError (*waveFormatExQueryFunction)(int, WAVEFORMATEX*), int maxChannels )
+{
+ PaDeviceInfo *deviceInfo = &winMmeDeviceInfo->inheritedDeviceInfo;
+ int i;
+
+ deviceInfo->defaultSampleRate = 0.;
+
+ for( i=0; i < PA_DEFAULTSAMPLERATESEARCHORDER_COUNT_; ++i )
+ {
+ double sampleRate = defaultSampleRateSearchOrder_[ i ];
+ PaError paerror = QueryFormatSupported( deviceInfo, waveFormatExQueryFunction, winMmeDeviceId, maxChannels, sampleRate, 0 );
+ if( paerror == paNoError )
+ {
+ deviceInfo->defaultSampleRate = sampleRate;
+ break;
+ }
+ }
+}
+
+
+#ifdef PAWIN_USE_WDMKS_DEVICE_INFO
+static int QueryWaveInKSFilterMaxChannels( UINT waveInDeviceId, int *maxChannels )
+{
+ void *devicePath;
+ DWORD devicePathSize;
+ int result = 0;
+
+ /* pass UINT ID via punned HWAVEIN, as per DRV_QUERYDEVICEINTERFACESIZE documentation */
+ HWAVEIN hDeviceId = (HWAVEIN)((UINT_PTR)waveInDeviceId);
+
+ if( waveInMessage(hDeviceId, DRV_QUERYDEVICEINTERFACESIZE,
+ (DWORD_PTR)&devicePathSize, 0 ) != MMSYSERR_NOERROR )
+ return 0;
+
+ devicePath = PaUtil_AllocateMemory( devicePathSize );
+ if( !devicePath )
+ return 0;
+
+ /* apparently DRV_QUERYDEVICEINTERFACE returns a unicode interface path, although this is undocumented */
+ if( waveInMessage(hDeviceId, DRV_QUERYDEVICEINTERFACE,
+ (DWORD_PTR)devicePath, devicePathSize ) == MMSYSERR_NOERROR )
+ {
+ int count = PaWin_WDMKS_QueryFilterMaximumChannelCount( devicePath, /* isInput= */ 1 );
+ if( count > 0 )
+ {
+ *maxChannels = count;
+ result = 1;
+ }
+ }
+
+ PaUtil_FreeMemory( devicePath );
+
+ return result;
+}
+#endif /* PAWIN_USE_WDMKS_DEVICE_INFO */
+
+
+static PaError InitializeInputDeviceInfo( PaWinMmeHostApiRepresentation *winMmeHostApi,
+ PaWinMmeDeviceInfo *winMmeDeviceInfo, UINT winMmeInputDeviceId, int *success )
+{
+ PaError result = paNoError;
+ char *deviceName; /* non-const ptr */
+ MMRESULT mmresult;
+ WAVEINCAPSW wic;
+ PaDeviceInfo *deviceInfo = &winMmeDeviceInfo->inheritedDeviceInfo;
+ size_t len;
+
+ *success = 0;
+
+ mmresult = waveInGetDevCapsW( winMmeInputDeviceId, &wic, sizeof( WAVEINCAPSW ) );
+ 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;
+ }
+
+ /* NOTE: the WAVEOUTCAPS.szPname is a null-terminated array of 32 characters,
+ so we are limited to displaying only the first 31 characters of the device name. */
+ if( winMmeInputDeviceId == WAVE_MAPPER )
+ {
+ len = WCharStringLen( wic.szPname ) + 1 + sizeof(constInputMapperSuffix_);
+ /* Append I/O suffix to WAVE_MAPPER device. */
+ deviceName = (char*)PaUtil_GroupAllocateMemory(
+ winMmeHostApi->allocations,
+ (long)len );
+ if( !deviceName )
+ {
+ result = paInsufficientMemory;
+ goto error;
+ }
+ CopyWCharStringToUtf8CString( deviceName, len, wic.szPname );
+ strcat( deviceName, constInputMapperSuffix_ );
+ }
+ else
+ {
+ len = WCharStringLen( wic.szPname ) + 1;
+ deviceName = (char*)PaUtil_GroupAllocateMemory(
+ winMmeHostApi->allocations,
+ (long)len );
+ if( !deviceName )
+ {
+ result = paInsufficientMemory;
+ goto error;
+ }
+ CopyWCharStringToUtf8CString( deviceName, len, wic.szPname );
+ }
+ deviceInfo->name = deviceName;
+
+ if( wic.wChannels == 0xFFFF || wic.wChannels < 1 || wic.wChannels > 255 ){
+ /* For Windows versions using WDM (possibly Windows 98 ME and later)
+ * the kernel mixer sits between the application and the driver. As a result,
+ * wave*GetDevCaps often kernel mixer channel counts, which are unlimited.
+ * When this happens we assume the device is stereo and set a flag
+ * so that other channel counts can be tried with OpenStream -- i.e. when
+ * device*ChannelCountIsKnown is false, OpenStream will try whatever
+ * channel count you supply.
+ * see also InitializeOutputDeviceInfo() below.
+ */
+
+ PA_DEBUG(("Pa_GetDeviceInfo: Num input channels reported as %d! Changed to 2.\n", wic.wChannels ));
+ deviceInfo->maxInputChannels = 2;
+ winMmeDeviceInfo->deviceInputChannelCountIsKnown = 0;
+ }else{
+ deviceInfo->maxInputChannels = wic.wChannels;
+ winMmeDeviceInfo->deviceInputChannelCountIsKnown = 1;
+ }
+
+#ifdef PAWIN_USE_WDMKS_DEVICE_INFO
+ winMmeDeviceInfo->deviceInputChannelCountIsKnown =
+ QueryWaveInKSFilterMaxChannels( winMmeInputDeviceId, &deviceInfo->maxInputChannels );
+#endif /* PAWIN_USE_WDMKS_DEVICE_INFO */
+
+ winMmeDeviceInfo->dwFormats = wic.dwFormats;
+
+ DetectDefaultSampleRate( winMmeDeviceInfo, winMmeInputDeviceId,
+ QueryInputWaveFormatEx, deviceInfo->maxInputChannels );
+
+ *success = 1;
+
+error:
+ return result;
+}
+
+
+#ifdef PAWIN_USE_WDMKS_DEVICE_INFO
+static int QueryWaveOutKSFilterMaxChannels( UINT waveOutDeviceId, int *maxChannels )
+{
+ void *devicePath;
+ DWORD devicePathSize;
+ int result = 0;
+
+ /* pass UINT ID via punned HWAVEOUT, as per DRV_QUERYDEVICEINTERFACESIZE documentation */
+ HWAVEOUT hDeviceId = (HWAVEOUT)((UINT_PTR)waveOutDeviceId);
+
+ if( waveOutMessage(hDeviceId, DRV_QUERYDEVICEINTERFACESIZE,
+ (DWORD_PTR)&devicePathSize, 0 ) != MMSYSERR_NOERROR )
+ return 0;
+
+ devicePath = PaUtil_AllocateMemory( devicePathSize );
+ if( !devicePath )
+ return 0;
+
+ /* apparently DRV_QUERYDEVICEINTERFACE returns a unicode interface path, although this is undocumented */
+ if( waveOutMessage(hDeviceId, DRV_QUERYDEVICEINTERFACE,
+ (DWORD_PTR)devicePath, devicePathSize ) == MMSYSERR_NOERROR )
+ {
+ int count = PaWin_WDMKS_QueryFilterMaximumChannelCount( devicePath, /* isInput= */ 0 );
+ if( count > 0 )
+ {
+ *maxChannels = count;
+ result = 1;
+ }
+ }
+
+ PaUtil_FreeMemory( devicePath );
+
+ return result;
+}
+#endif /* PAWIN_USE_WDMKS_DEVICE_INFO */
+
+
+static PaError InitializeOutputDeviceInfo( PaWinMmeHostApiRepresentation *winMmeHostApi,
+ PaWinMmeDeviceInfo *winMmeDeviceInfo, UINT winMmeOutputDeviceId, int *success )
+{
+ PaError result = paNoError;
+ char *deviceName; /* non-const ptr */
+ MMRESULT mmresult;
+ WAVEOUTCAPSW woc;
+ PaDeviceInfo *deviceInfo = &winMmeDeviceInfo->inheritedDeviceInfo;
+ size_t len;
+#ifdef PAWIN_USE_WDMKS_DEVICE_INFO
+ int wdmksDeviceOutputChannelCountIsKnown;
+#endif
+
+ *success = 0;
+
+ mmresult = waveOutGetDevCapsW( winMmeOutputDeviceId, &woc, sizeof( WAVEOUTCAPSW ) );
+ 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;
+ }
+
+ /* NOTE: the WAVEOUTCAPS.szPname is a null-terminated array of 32 characters,
+ so we are limited to displaying only the first 31 characters of the device name. */
+ if( winMmeOutputDeviceId == WAVE_MAPPER )
+ {
+ /* Append I/O suffix to WAVE_MAPPER device. */
+ len = WCharStringLen( woc.szPname ) + 1 + sizeof(constOutputMapperSuffix_);
+ deviceName = (char*)PaUtil_GroupAllocateMemory(
+ winMmeHostApi->allocations,
+ (long)len );
+ if( !deviceName )
+ {
+ result = paInsufficientMemory;
+ goto error;
+ }
+ CopyWCharStringToUtf8CString( deviceName, len, woc.szPname );
+ strcat( deviceName, constOutputMapperSuffix_ );
+ }
+ else
+ {
+ len = WCharStringLen( woc.szPname ) + 1;
+ deviceName = (char*)PaUtil_GroupAllocateMemory(
+ winMmeHostApi->allocations,
+ (long)len );
+ if( !deviceName )
+ {
+ result = paInsufficientMemory;
+ goto error;
+ }
+ CopyWCharStringToUtf8CString( deviceName, len, woc.szPname );
+ }
+ deviceInfo->name = deviceName;
+
+ if( woc.wChannels == 0xFFFF || woc.wChannels < 1 || woc.wChannels > 255 ){
+ /* For Windows versions using WDM (possibly Windows 98 ME and later)
+ * the kernel mixer sits between the application and the driver. As a result,
+ * wave*GetDevCaps often kernel mixer channel counts, which are unlimited.
+ * When this happens we assume the device is stereo and set a flag
+ * so that other channel counts can be tried with OpenStream -- i.e. when
+ * device*ChannelCountIsKnown is false, OpenStream will try whatever
+ * channel count you supply.
+ * see also InitializeInputDeviceInfo() above.
+ */
+
+ PA_DEBUG(("Pa_GetDeviceInfo: Num output channels reported as %d! Changed to 2.\n", woc.wChannels ));
+ deviceInfo->maxOutputChannels = 2;
+ winMmeDeviceInfo->deviceOutputChannelCountIsKnown = 0;
+ }else{
+ deviceInfo->maxOutputChannels = woc.wChannels;
+ winMmeDeviceInfo->deviceOutputChannelCountIsKnown = 1;
+ }
+
+#ifdef PAWIN_USE_WDMKS_DEVICE_INFO
+ wdmksDeviceOutputChannelCountIsKnown = QueryWaveOutKSFilterMaxChannels(
+ winMmeOutputDeviceId, &deviceInfo->maxOutputChannels );
+ if( wdmksDeviceOutputChannelCountIsKnown && !winMmeDeviceInfo->deviceOutputChannelCountIsKnown )
+ winMmeDeviceInfo->deviceOutputChannelCountIsKnown = 1;
+#endif /* PAWIN_USE_WDMKS_DEVICE_INFO */
+
+ winMmeDeviceInfo->dwFormats = woc.dwFormats;
+
+ DetectDefaultSampleRate( winMmeDeviceInfo, winMmeOutputDeviceId,
+ QueryOutputWaveFormatEx, deviceInfo->maxOutputChannels );
+
+ *success = 1;
+
+error:
+ return result;
+}
+
+
+static void GetDefaultLatencies( PaTime *defaultLowLatency, PaTime *defaultHighLatency )
+{
+/*
+NOTE: GetVersionEx() is deprecated as of Windows 8.1 and can not be used to reliably detect
+versions of Windows higher than Windows 8 (due to manifest requirements for reporting higher versions).
+Microsoft recommends switching to VerifyVersionInfo (available on Win 2k and later), however GetVersionEx
+is faster, for now we just disable the deprecation warning.
+See: https://msdn.microsoft.com/en-us/library/windows/desktop/ms724451(v=vs.85).aspx
+See: http://www.codeproject.com/Articles/678606/Part-Overcoming-Windows-s-deprecation-of-GetVe
+*/
+#pragma warning (disable : 4996) /* use of GetVersionEx */
+
+ OSVERSIONINFO osvi;
+ osvi.dwOSVersionInfoSize = sizeof( osvi );
+ GetVersionEx( &osvi );
+
+ /* Check for NT */
+ if( (osvi.dwMajorVersion == 4) && (osvi.dwPlatformId == 2) )
+ {
+ *defaultLowLatency = PA_MME_WIN_NT_DEFAULT_LATENCY_;
+ }
+ else if(osvi.dwMajorVersion >= 5)
+ {
+ *defaultLowLatency = PA_MME_WIN_WDM_DEFAULT_LATENCY_;
+ }
+ else
+ {
+ *defaultLowLatency = PA_MME_WIN_9X_DEFAULT_LATENCY_;
+ }
+
+ *defaultHighLatency = *defaultLowLatency * 2;
+
+#pragma warning (default : 4996)
+}
+
+
+PaError PaWinMme_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex hostApiIndex )
+{
+ PaError result = paNoError;
+ int i;
+ PaWinMmeHostApiRepresentation *winMmeHostApi;
+ int inputDeviceCount, outputDeviceCount, maximumPossibleDeviceCount;
+ PaWinMmeDeviceInfo *deviceInfoArray;
+ int deviceInfoInitializationSucceeded;
+ PaTime defaultLowLatency, defaultHighLatency;
+ DWORD waveInPreferredDevice, waveOutPreferredDevice;
+ DWORD preferredDeviceStatusFlags;
+
+ 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->inputDeviceCount = 0;
+ winMmeHostApi->outputDeviceCount = 0;
+
+#if !defined(DRVM_MAPPER_PREFERRED_GET)
+/* DRVM_MAPPER_PREFERRED_GET is defined in mmddk.h but we avoid a dependency on the DDK by defining it here */
+#define DRVM_MAPPER_PREFERRED_GET (0x2000+21)
+#endif
+
+ /* the following calls assume that if wave*Message fails the preferred device parameter won't be modified */
+ preferredDeviceStatusFlags = 0;
+ waveInPreferredDevice = -1;
+ waveInMessage( (HWAVEIN)((UINT_PTR)WAVE_MAPPER), DRVM_MAPPER_PREFERRED_GET, (DWORD_PTR)&waveInPreferredDevice, (DWORD_PTR)&preferredDeviceStatusFlags );
+
+ preferredDeviceStatusFlags = 0;
+ waveOutPreferredDevice = -1;
+ waveOutMessage( (HWAVEOUT)((UINT_PTR)WAVE_MAPPER), DRVM_MAPPER_PREFERRED_GET, (DWORD_PTR)&waveOutPreferredDevice, (DWORD_PTR)&preferredDeviceStatusFlags );
+
+ maximumPossibleDeviceCount = 0;
+
+ inputDeviceCount = waveInGetNumDevs();
+ if( inputDeviceCount > 0 )
+ maximumPossibleDeviceCount += inputDeviceCount + 1; /* assume there is a WAVE_MAPPER */
+
+ outputDeviceCount = waveOutGetNumDevs();
+ if( outputDeviceCount > 0 )
+ maximumPossibleDeviceCount += outputDeviceCount + 1; /* assume there is a WAVE_MAPPER */
+
+
+ if( maximumPossibleDeviceCount > 0 ){
+
+ (*hostApi)->deviceInfos = (PaDeviceInfo**)PaUtil_GroupAllocateMemory(
+ winMmeHostApi->allocations, sizeof(PaDeviceInfo*) * maximumPossibleDeviceCount );
+ if( !(*hostApi)->deviceInfos )
+ {
+ result = paInsufficientMemory;
+ goto error;
+ }
+
+ /* allocate all device info structs in a contiguous block */
+ deviceInfoArray = (PaWinMmeDeviceInfo*)PaUtil_GroupAllocateMemory(
+ winMmeHostApi->allocations, sizeof(PaWinMmeDeviceInfo) * maximumPossibleDeviceCount );
+ if( !deviceInfoArray )
+ {
+ result = paInsufficientMemory;
+ goto error;
+ }
+
+ winMmeHostApi->winMmeDeviceIds = (UINT*)PaUtil_GroupAllocateMemory(
+ winMmeHostApi->allocations, sizeof(int) * maximumPossibleDeviceCount );
+ if( !winMmeHostApi->winMmeDeviceIds )
+ {
+ result = paInsufficientMemory;
+ goto error;
+ }
+
+ GetDefaultLatencies( &defaultLowLatency, &defaultHighLatency );
+
+ if( inputDeviceCount > 0 ){
+ /* -1 is the WAVE_MAPPER */
+ for( i = -1; i < inputDeviceCount; ++i ){
+ UINT winMmeDeviceId = (UINT)((i==-1) ? WAVE_MAPPER : i);
+ PaWinMmeDeviceInfo *wmmeDeviceInfo = &deviceInfoArray[ (*hostApi)->info.deviceCount ];
+ PaDeviceInfo *deviceInfo = &wmmeDeviceInfo->inheritedDeviceInfo;
+ deviceInfo->structVersion = 2;
+ deviceInfo->hostApi = hostApiIndex;
+
+ deviceInfo->maxInputChannels = 0;
+ wmmeDeviceInfo->deviceInputChannelCountIsKnown = 1;
+ deviceInfo->maxOutputChannels = 0;
+ wmmeDeviceInfo->deviceOutputChannelCountIsKnown = 1;
+
+ deviceInfo->defaultLowInputLatency = defaultLowLatency;
+ deviceInfo->defaultLowOutputLatency = defaultLowLatency;
+ deviceInfo->defaultHighInputLatency = defaultHighLatency;
+ deviceInfo->defaultHighOutputLatency = defaultHighLatency;
+
+ result = InitializeInputDeviceInfo( winMmeHostApi, wmmeDeviceInfo,
+ winMmeDeviceId, &deviceInfoInitializationSucceeded );
+ if( result != paNoError )
+ goto error;
+
+ if( deviceInfoInitializationSucceeded ){
+ if( (*hostApi)->info.defaultInputDevice == paNoDevice ){
+ /* if there is currently no default device, use the first one available */
+ (*hostApi)->info.defaultInputDevice = (*hostApi)->info.deviceCount;
+
+ }else if( winMmeDeviceId == waveInPreferredDevice ){
+ /* set the default device to the system preferred device */
+ (*hostApi)->info.defaultInputDevice = (*hostApi)->info.deviceCount;
+ }
+
+ winMmeHostApi->winMmeDeviceIds[ (*hostApi)->info.deviceCount ] = winMmeDeviceId;
+ (*hostApi)->deviceInfos[ (*hostApi)->info.deviceCount ] = deviceInfo;
+
+ winMmeHostApi->inputDeviceCount++;
+ (*hostApi)->info.deviceCount++;
+ }
+ }
+ }
+
+ if( outputDeviceCount > 0 ){
+ /* -1 is the WAVE_MAPPER */
+ for( i = -1; i < outputDeviceCount; ++i ){
+ UINT winMmeDeviceId = (UINT)((i==-1) ? WAVE_MAPPER : i);
+ PaWinMmeDeviceInfo *wmmeDeviceInfo = &deviceInfoArray[ (*hostApi)->info.deviceCount ];
+ PaDeviceInfo *deviceInfo = &wmmeDeviceInfo->inheritedDeviceInfo;
+ deviceInfo->structVersion = 2;
+ deviceInfo->hostApi = hostApiIndex;
+
+ deviceInfo->maxInputChannels = 0;
+ wmmeDeviceInfo->deviceInputChannelCountIsKnown = 1;
+ deviceInfo->maxOutputChannels = 0;
+ wmmeDeviceInfo->deviceOutputChannelCountIsKnown = 1;
+
+ deviceInfo->defaultLowInputLatency = defaultLowLatency;
+ deviceInfo->defaultLowOutputLatency = defaultLowLatency;
+ deviceInfo->defaultHighInputLatency = defaultHighLatency;
+ deviceInfo->defaultHighOutputLatency = defaultHighLatency;
+
+ result = InitializeOutputDeviceInfo( winMmeHostApi, wmmeDeviceInfo,
+ winMmeDeviceId, &deviceInfoInitializationSucceeded );
+ if( result != paNoError )
+ goto error;
+
+ if( deviceInfoInitializationSucceeded ){
+ if( (*hostApi)->info.defaultOutputDevice == paNoDevice ){
+ /* if there is currently no default device, use the first one available */
+ (*hostApi)->info.defaultOutputDevice = (*hostApi)->info.deviceCount;
+
+ }else if( winMmeDeviceId == waveOutPreferredDevice ){
+ /* set the default device to the system preferred device */
+ (*hostApi)->info.defaultOutputDevice = (*hostApi)->info.deviceCount;
+ }
+
+ winMmeHostApi->winMmeDeviceIds[ (*hostApi)->info.deviceCount ] = winMmeDeviceId;
+ (*hostApi)->deviceInfos[ (*hostApi)->info.deviceCount ] = deviceInfo;
+
+ winMmeHostApi->outputDeviceCount++;
+ (*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_DummyRead, PaUtil_DummyWrite,
+ PaUtil_DummyGetReadAvailable, PaUtil_DummyGetWriteAvailable );
+
+ 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 IsInputChannelCountSupported( PaWinMmeDeviceInfo* deviceInfo, int channelCount )
+{
+ PaError result = paNoError;
+
+ if( channelCount > 0
+ && deviceInfo->deviceInputChannelCountIsKnown
+ && channelCount > deviceInfo->inheritedDeviceInfo.maxInputChannels ){
+
+ result = paInvalidChannelCount;
+ }
+
+ return result;
+}
+
+static PaError IsOutputChannelCountSupported( PaWinMmeDeviceInfo* deviceInfo, int channelCount )
+{
+ PaError result = paNoError;
+
+ if( channelCount > 0
+ && deviceInfo->deviceOutputChannelCountIsKnown
+ && channelCount > deviceInfo->inheritedDeviceInfo.maxOutputChannels ){
+
+ result = paInvalidChannelCount;
+ }
+
+ return result;
+}
+
+static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi,
+ const PaStreamParameters *inputParameters,
+ const PaStreamParameters *outputParameters,
+ double sampleRate )
+{
+ PaWinMmeHostApiRepresentation *winMmeHostApi = (PaWinMmeHostApiRepresentation*)hostApi;
+ PaDeviceInfo *inputDeviceInfo, *outputDeviceInfo;
+ int inputChannelCount, outputChannelCount;
+ int inputMultipleDeviceChannelCount, outputMultipleDeviceChannelCount;
+ PaSampleFormat inputSampleFormat, outputSampleFormat;
+ PaWinMmeStreamInfo *inputStreamInfo, *outputStreamInfo;
+ UINT winMmeInputDeviceId, winMmeOutputDeviceId;
+ unsigned int i;
+ PaError paerror;
+
+ /* The calls to QueryFormatSupported below are intended to detect invalid
+ sample rates. If we assume that the channel count and format are OK,
+ then the only thing that could fail is the sample rate. This isn't
+ strictly true, but I can't think of a better way to test that the
+ sample rate is valid.
+ */
+
+ if( inputParameters )
+ {
+ inputChannelCount = inputParameters->channelCount;
+ inputSampleFormat = inputParameters->sampleFormat;
+ inputStreamInfo = inputParameters->hostApiSpecificStreamInfo;
+
+ /* all standard sample formats are supported by the buffer adapter,
+ this implementation doesn't support any custom sample formats */
+ if( inputSampleFormat & paCustomFormat )
+ return paSampleFormatNotSupported;
+
+ if( inputParameters->device == paUseHostApiSpecificDeviceSpecification
+ && inputStreamInfo && (inputStreamInfo->flags & paWinMmeUseMultipleDevices) )
+ {
+ inputMultipleDeviceChannelCount = 0;
+ for( i=0; i< inputStreamInfo->deviceCount; ++i )
+ {
+ inputMultipleDeviceChannelCount += inputStreamInfo->devices[i].channelCount;
+
+ inputDeviceInfo = hostApi->deviceInfos[ inputStreamInfo->devices[i].device ];
+
+ /* check that input device can support inputChannelCount */
+ if( inputStreamInfo->devices[i].channelCount < 1 )
+ return paInvalidChannelCount;
+
+ paerror = IsInputChannelCountSupported( (PaWinMmeDeviceInfo*)inputDeviceInfo,
+ inputStreamInfo->devices[i].channelCount );
+ if( paerror != paNoError )
+ return paerror;
+
+ /* test for valid sample rate, see comment above */
+ winMmeInputDeviceId = LocalDeviceIndexToWinMmeDeviceId( winMmeHostApi, inputStreamInfo->devices[i].device );
+ paerror = QueryFormatSupported( inputDeviceInfo, QueryInputWaveFormatEx,
+ winMmeInputDeviceId, inputStreamInfo->devices[i].channelCount, sampleRate,
+ ((inputStreamInfo) ? inputStreamInfo->flags : 0) );
+ if( paerror != paNoError )
+ return paInvalidSampleRate;
+ }
+
+ if( inputMultipleDeviceChannelCount != inputChannelCount )
+ return paIncompatibleHostApiSpecificStreamInfo;
+ }
+ else
+ {
+ if( inputStreamInfo && (inputStreamInfo->flags & paWinMmeUseMultipleDevices) )
+ return paIncompatibleHostApiSpecificStreamInfo; /* paUseHostApiSpecificDeviceSpecification was not supplied as the input device */
+
+ inputDeviceInfo = hostApi->deviceInfos[ inputParameters->device ];
+
+ /* check that input device can support inputChannelCount */
+ paerror = IsInputChannelCountSupported( (PaWinMmeDeviceInfo*)inputDeviceInfo, inputChannelCount );
+ if( paerror != paNoError )
+ return paerror;
+
+ /* test for valid sample rate, see comment above */
+ winMmeInputDeviceId = LocalDeviceIndexToWinMmeDeviceId( winMmeHostApi, inputParameters->device );
+ paerror = QueryFormatSupported( inputDeviceInfo, QueryInputWaveFormatEx,
+ winMmeInputDeviceId, inputChannelCount, sampleRate,
+ ((inputStreamInfo) ? inputStreamInfo->flags : 0) );
+ if( paerror != paNoError )
+ return paInvalidSampleRate;
+ }
+ }
+
+ if( outputParameters )
+ {
+ outputChannelCount = outputParameters->channelCount;
+ outputSampleFormat = outputParameters->sampleFormat;
+ outputStreamInfo = outputParameters->hostApiSpecificStreamInfo;
+
+ /* all standard sample formats are supported by the buffer adapter,
+ this implementation doesn't support any custom sample formats */
+ if( outputSampleFormat & paCustomFormat )
+ return paSampleFormatNotSupported;
+
+ if( outputParameters->device == paUseHostApiSpecificDeviceSpecification
+ && outputStreamInfo && (outputStreamInfo->flags & paWinMmeUseMultipleDevices) )
+ {
+ outputMultipleDeviceChannelCount = 0;
+ for( i=0; i< outputStreamInfo->deviceCount; ++i )
+ {
+ outputMultipleDeviceChannelCount += outputStreamInfo->devices[i].channelCount;
+
+ outputDeviceInfo = hostApi->deviceInfos[ outputStreamInfo->devices[i].device ];
+
+ /* check that output device can support outputChannelCount */
+ if( outputStreamInfo->devices[i].channelCount < 1 )
+ return paInvalidChannelCount;
+
+ paerror = IsOutputChannelCountSupported( (PaWinMmeDeviceInfo*)outputDeviceInfo,
+ outputStreamInfo->devices[i].channelCount );
+ if( paerror != paNoError )
+ return paerror;
+
+ /* test for valid sample rate, see comment above */
+ winMmeOutputDeviceId = LocalDeviceIndexToWinMmeDeviceId( winMmeHostApi, outputStreamInfo->devices[i].device );
+ paerror = QueryFormatSupported( outputDeviceInfo, QueryOutputWaveFormatEx,
+ winMmeOutputDeviceId, outputStreamInfo->devices[i].channelCount, sampleRate,
+ ((outputStreamInfo) ? outputStreamInfo->flags : 0) );
+ if( paerror != paNoError )
+ return paInvalidSampleRate;
+ }
+
+ if( outputMultipleDeviceChannelCount != outputChannelCount )
+ return paIncompatibleHostApiSpecificStreamInfo;
+ }
+ else
+ {
+ if( outputStreamInfo && (outputStreamInfo->flags & paWinMmeUseMultipleDevices) )
+ return paIncompatibleHostApiSpecificStreamInfo; /* paUseHostApiSpecificDeviceSpecification was not supplied as the output device */
+
+ outputDeviceInfo = hostApi->deviceInfos[ outputParameters->device ];
+
+ /* check that output device can support outputChannelCount */
+ paerror = IsOutputChannelCountSupported( (PaWinMmeDeviceInfo*)outputDeviceInfo, outputChannelCount );
+ if( paerror != paNoError )
+ return paerror;
+
+ /* test for valid sample rate, see comment above */
+ winMmeOutputDeviceId = LocalDeviceIndexToWinMmeDeviceId( winMmeHostApi, outputParameters->device );
+ paerror = QueryFormatSupported( outputDeviceInfo, QueryOutputWaveFormatEx,
+ winMmeOutputDeviceId, outputChannelCount, sampleRate,
+ ((outputStreamInfo) ? outputStreamInfo->flags : 0) );
+ if( paerror != paNoError )
+ return paInvalidSampleRate;
+ }
+ }
+
+ /*
+ - if a full duplex stream is requested, check that the combination
+ of input and output parameters is supported
+
+ - check that the device supports sampleRate
+
+ for mme all we can do is test that the input and output devices
+ support the requested sample rate and number of channels. we
+ cannot test for full duplex compatibility.
+ */
+
+ return paFormatIsSupported;
+}
+
+
+static unsigned long ComputeHostBufferCountForFixedBufferSizeFrames(
+ unsigned long suggestedLatencyFrames,
+ unsigned long hostBufferSizeFrames,
+ unsigned long minimumBufferCount )
+{
+ /* Calculate the number of buffers of length hostFramesPerBuffer
+ that fit in suggestedLatencyFrames, rounding up to the next integer.
+
+ The value (hostBufferSizeFrames - 1) below is to ensure the buffer count is rounded up.
+ */
+ unsigned long resultBufferCount = ((suggestedLatencyFrames + (hostBufferSizeFrames - 1)) / hostBufferSizeFrames);
+
+ /* We always need one extra buffer for processing while the rest are queued/playing.
+ i.e. latency is framesPerBuffer * (bufferCount - 1)
+ */
+ resultBufferCount += 1;
+
+ if( resultBufferCount < minimumBufferCount ) /* clamp to minimum buffer count */
+ resultBufferCount = minimumBufferCount;
+
+ return resultBufferCount;
+}
+
+
+static unsigned long ComputeHostBufferSizeGivenHardUpperLimit(
+ unsigned long userFramesPerBuffer,
+ unsigned long absoluteMaximumBufferSizeFrames )
+{
+ static unsigned long primes_[] = { 2, 3, 5, 7, 11, 13, 17, 19, 23,
+ 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 0 }; /* zero terminated */
+
+ unsigned long result = userFramesPerBuffer;
+ int i;
+
+ assert( absoluteMaximumBufferSizeFrames > 67 ); /* assume maximum is large and we're only factoring by small primes */
+
+ /* search for the largest integer factor of userFramesPerBuffer less
+ than or equal to absoluteMaximumBufferSizeFrames */
+
+ /* repeatedly divide by smallest prime factors until a buffer size
+ smaller than absoluteMaximumBufferSizeFrames is found */
+ while( result > absoluteMaximumBufferSizeFrames ){
+
+ /* search for the smallest prime factor of result */
+ for( i=0; primes_[i] != 0; ++i )
+ {
+ unsigned long p = primes_[i];
+ unsigned long divided = result / p;
+ if( divided*p == result )
+ {
+ result = divided;
+ break; /* continue with outer while loop */
+ }
+ }
+ if( primes_[i] == 0 )
+ { /* loop failed to find a prime factor, return an approximate result */
+ unsigned long d = (userFramesPerBuffer + (absoluteMaximumBufferSizeFrames-1))
+ / absoluteMaximumBufferSizeFrames;
+ return userFramesPerBuffer / d;
+ }
+ }
+
+ return result;
+}
+
+
+static PaError SelectHostBufferSizeFramesAndHostBufferCount(
+ unsigned long suggestedLatencyFrames,
+ unsigned long userFramesPerBuffer,
+ unsigned long minimumBufferCount,
+ unsigned long preferredMaximumBufferSizeFrames, /* try not to exceed this. for example, don't exceed when coalescing buffers */
+ unsigned long absoluteMaximumBufferSizeFrames, /* never exceed this, a hard limit */
+ unsigned long *hostBufferSizeFrames,
+ unsigned long *hostBufferCount )
+{
+ unsigned long effectiveUserFramesPerBuffer;
+ unsigned long numberOfUserBuffersPerHostBuffer;
+
+
+ if( userFramesPerBuffer == paFramesPerBufferUnspecified ){
+
+ effectiveUserFramesPerBuffer = PA_MME_HOST_BUFFER_GRANULARITY_FRAMES_WHEN_UNSPECIFIED_;
+
+ }else{
+
+ if( userFramesPerBuffer > absoluteMaximumBufferSizeFrames ){
+
+ /* user has requested a user buffer that's larger than absoluteMaximumBufferSizeFrames.
+ try to choose a buffer size that is equal or smaller than absoluteMaximumBufferSizeFrames
+ but is also an integer factor of userFramesPerBuffer, so as to distribute computation evenly.
+ the buffer processor will handle the block adaption between host and user buffer sizes.
+ see http://www.portaudio.com/trac/ticket/189 for discussion.
+ */
+
+ effectiveUserFramesPerBuffer = ComputeHostBufferSizeGivenHardUpperLimit( userFramesPerBuffer, absoluteMaximumBufferSizeFrames );
+ assert( effectiveUserFramesPerBuffer <= absoluteMaximumBufferSizeFrames );
+
+ /* try to ensure that duration of host buffering is at least as
+ large as duration of user buffer. */
+ if( suggestedLatencyFrames < userFramesPerBuffer )
+ suggestedLatencyFrames = userFramesPerBuffer;
+
+ }else{
+
+ effectiveUserFramesPerBuffer = userFramesPerBuffer;
+ }
+ }
+
+ /* compute a host buffer count based on suggestedLatencyFrames and our granularity */
+
+ *hostBufferSizeFrames = effectiveUserFramesPerBuffer;
+
+ *hostBufferCount = ComputeHostBufferCountForFixedBufferSizeFrames(
+ suggestedLatencyFrames, *hostBufferSizeFrames, minimumBufferCount );
+
+ if( *hostBufferSizeFrames >= userFramesPerBuffer )
+ {
+ /*
+ If there are too many host buffers we would like to coalesce
+ them by packing an integer number of user buffers into each host buffer.
+ We try to coalesce such that hostBufferCount will lie between
+ PA_MME_TARGET_HOST_BUFFER_COUNT_ and (PA_MME_TARGET_HOST_BUFFER_COUNT_*2)-1.
+ We limit coalescing to avoid exceeding either absoluteMaximumBufferSizeFrames and
+ preferredMaximumBufferSizeFrames.
+
+ First, compute a coalescing factor: the number of user buffers per host buffer.
+ The goal is to achieve PA_MME_TARGET_HOST_BUFFER_COUNT_ total buffer count.
+ Since our latency is computed based on (*hostBufferCount - 1) we compute a
+ coalescing factor based on (*hostBufferCount - 1) and (PA_MME_TARGET_HOST_BUFFER_COUNT_-1).
+
+ The + (PA_MME_TARGET_HOST_BUFFER_COUNT_-2) term below is intended to round up.
+ */
+ numberOfUserBuffersPerHostBuffer = ((*hostBufferCount - 1) + (PA_MME_TARGET_HOST_BUFFER_COUNT_-2)) / (PA_MME_TARGET_HOST_BUFFER_COUNT_ - 1);
+
+ if( numberOfUserBuffersPerHostBuffer > 1 )
+ {
+ unsigned long maxCoalescedBufferSizeFrames = (absoluteMaximumBufferSizeFrames < preferredMaximumBufferSizeFrames) /* minimum of our limits */
+ ? absoluteMaximumBufferSizeFrames
+ : preferredMaximumBufferSizeFrames;
+
+ unsigned long maxUserBuffersPerHostBuffer = maxCoalescedBufferSizeFrames / effectiveUserFramesPerBuffer; /* don't coalesce more than this */
+
+ if( numberOfUserBuffersPerHostBuffer > maxUserBuffersPerHostBuffer )
+ numberOfUserBuffersPerHostBuffer = maxUserBuffersPerHostBuffer;
+
+ *hostBufferSizeFrames = effectiveUserFramesPerBuffer * numberOfUserBuffersPerHostBuffer;
+
+ /* recompute hostBufferCount to approximate suggestedLatencyFrames now that hostBufferSizeFrames is larger */
+ *hostBufferCount = ComputeHostBufferCountForFixedBufferSizeFrames(
+ suggestedLatencyFrames, *hostBufferSizeFrames, minimumBufferCount );
+ }
+ }
+
+ return paNoError;
+}
+
+
+static PaError CalculateMaxHostSampleFrameSizeBytes(
+ int channelCount,
+ PaSampleFormat hostSampleFormat,
+ const PaWinMmeStreamInfo *streamInfo,
+ int *hostSampleFrameSizeBytes )
+{
+ unsigned int i;
+ /* PA WMME streams may aggregate multiple WMME devices. When the stream addresses
+ more than one device in a single direction, maxDeviceChannelCount is the maximum
+ number of channels used by a single device.
+ */
+ int maxDeviceChannelCount = channelCount;
+ int hostSampleSizeBytes = Pa_GetSampleSize( hostSampleFormat );
+ if( hostSampleSizeBytes < 0 )
+ {
+ return hostSampleSizeBytes; /* the value of hostSampleSize here is an error code, not a sample size */
+ }
+
+ if( streamInfo && ( streamInfo->flags & paWinMmeUseMultipleDevices ) )
+ {
+ maxDeviceChannelCount = streamInfo->devices[0].channelCount;
+ for( i=1; i< streamInfo->deviceCount; ++i )
+ {
+ if( streamInfo->devices[i].channelCount > maxDeviceChannelCount )
+ maxDeviceChannelCount = streamInfo->devices[i].channelCount;
+ }
+ }
+
+ *hostSampleFrameSizeBytes = hostSampleSizeBytes * maxDeviceChannelCount;
+
+ return paNoError;
+}
+
+
+/* CalculateBufferSettings() fills the framesPerHostInputBuffer, hostInputBufferCount,
+ framesPerHostOutputBuffer and hostOutputBufferCount parameters based on the values
+ of the other parameters.
+*/
+
+static PaError CalculateBufferSettings(
+ unsigned long *hostFramesPerInputBuffer, unsigned long *hostInputBufferCount,
+ unsigned long *hostFramesPerOutputBuffer, unsigned long *hostOutputBufferCount,
+ int inputChannelCount, PaSampleFormat hostInputSampleFormat,
+ PaTime suggestedInputLatency, const PaWinMmeStreamInfo *inputStreamInfo,
+ int outputChannelCount, PaSampleFormat hostOutputSampleFormat,
+ PaTime suggestedOutputLatency, const PaWinMmeStreamInfo *outputStreamInfo,
+ double sampleRate, unsigned long userFramesPerBuffer )
+{
+ PaError result = paNoError;
+
+ if( inputChannelCount > 0 ) /* stream has input */
+ {
+ int hostInputFrameSizeBytes;
+ result = CalculateMaxHostSampleFrameSizeBytes(
+ inputChannelCount, hostInputSampleFormat, inputStreamInfo, &hostInputFrameSizeBytes );
+ if( result != paNoError )
+ goto error;
+
+ if( inputStreamInfo
+ && ( inputStreamInfo->flags & paWinMmeUseLowLevelLatencyParameters ) )
+ {
+ /* input - using low level latency parameters if provided */
+
+ if( inputStreamInfo->bufferCount <= 0
+ || inputStreamInfo->framesPerBuffer <= 0 )
+ {
+ result = paIncompatibleHostApiSpecificStreamInfo;
+ goto error;
+ }
+
+ *hostFramesPerInputBuffer = inputStreamInfo->framesPerBuffer;
+ *hostInputBufferCount = inputStreamInfo->bufferCount;
+ }
+ else
+ {
+ /* input - not using low level latency parameters, so compute
+ hostFramesPerInputBuffer and hostInputBufferCount
+ based on userFramesPerBuffer and suggestedInputLatency. */
+
+ unsigned long minimumBufferCount = (outputChannelCount > 0)
+ ? PA_MME_MIN_HOST_INPUT_BUFFER_COUNT_FULL_DUPLEX_
+ : PA_MME_MIN_HOST_INPUT_BUFFER_COUNT_HALF_DUPLEX_;
+
+ result = SelectHostBufferSizeFramesAndHostBufferCount(
+ (unsigned long)(suggestedInputLatency * sampleRate), /* (truncate) */
+ userFramesPerBuffer,
+ minimumBufferCount,
+ (unsigned long)(PA_MME_MAX_HOST_BUFFER_SECS_ * sampleRate), /* in frames. preferred maximum */
+ (PA_MME_MAX_HOST_BUFFER_BYTES_ / hostInputFrameSizeBytes), /* in frames. a hard limit. note truncation due to
+ division is intentional here to limit max bytes */
+ hostFramesPerInputBuffer,
+ hostInputBufferCount );
+ if( result != paNoError )
+ goto error;
+ }
+ }
+ else
+ {
+ *hostFramesPerInputBuffer = 0;
+ *hostInputBufferCount = 0;
+ }
+
+ if( outputChannelCount > 0 ) /* stream has output */
+ {
+ if( outputStreamInfo
+ && ( outputStreamInfo->flags & paWinMmeUseLowLevelLatencyParameters ) )
+ {
+ /* output - using low level latency parameters */
+
+ if( outputStreamInfo->bufferCount <= 0
+ || outputStreamInfo->framesPerBuffer <= 0 )
+ {
+ result = paIncompatibleHostApiSpecificStreamInfo;
+ goto error;
+ }
+
+ *hostFramesPerOutputBuffer = outputStreamInfo->framesPerBuffer;
+ *hostOutputBufferCount = outputStreamInfo->bufferCount;
+
+ if( inputChannelCount > 0 ) /* full duplex */
+ {
+ /* harmonize hostFramesPerInputBuffer and hostFramesPerOutputBuffer */
+
+ if( *hostFramesPerInputBuffer != *hostFramesPerOutputBuffer )
+ {
+ if( inputStreamInfo
+ && ( inputStreamInfo->flags & paWinMmeUseLowLevelLatencyParameters ) )
+ {
+ /* a custom StreamInfo was used for specifying both input
+ and output buffer sizes. We require that the larger buffer size
+ must be a multiple of the smaller buffer size */
+
+ if( *hostFramesPerInputBuffer < *hostFramesPerOutputBuffer )
+ {
+ if( *hostFramesPerOutputBuffer % *hostFramesPerInputBuffer != 0 )
+ {
+ result = paIncompatibleHostApiSpecificStreamInfo;
+ goto error;
+ }
+ }
+ else
+ {
+ assert( *hostFramesPerInputBuffer > *hostFramesPerOutputBuffer );
+ if( *hostFramesPerInputBuffer % *hostFramesPerOutputBuffer != 0 )
+ {
+ result = paIncompatibleHostApiSpecificStreamInfo;
+ goto error;
+ }
+ }
+ }
+ else
+ {
+ /* a custom StreamInfo was not used for specifying the input buffer size,
+ so use the output buffer size, and approximately the suggested input latency. */
+
+ *hostFramesPerInputBuffer = *hostFramesPerOutputBuffer;
+
+ *hostInputBufferCount = ComputeHostBufferCountForFixedBufferSizeFrames(
+ (unsigned long)(suggestedInputLatency * sampleRate),
+ *hostFramesPerInputBuffer,
+ PA_MME_MIN_HOST_INPUT_BUFFER_COUNT_FULL_DUPLEX_ );
+ }
+ }
+ }
+ }
+ else
+ {
+ /* output - no low level latency parameters, so compute hostFramesPerOutputBuffer and hostOutputBufferCount
+ based on userFramesPerBuffer and suggestedOutputLatency. */
+
+ int hostOutputFrameSizeBytes;
+ result = CalculateMaxHostSampleFrameSizeBytes(
+ outputChannelCount, hostOutputSampleFormat, outputStreamInfo, &hostOutputFrameSizeBytes );
+ if( result != paNoError )
+ goto error;
+
+ /* compute the output buffer size and count */
+
+ result = SelectHostBufferSizeFramesAndHostBufferCount(
+ (unsigned long)(suggestedOutputLatency * sampleRate), /* (truncate) */
+ userFramesPerBuffer,
+ PA_MME_MIN_HOST_OUTPUT_BUFFER_COUNT_,
+ (unsigned long)(PA_MME_MAX_HOST_BUFFER_SECS_ * sampleRate), /* in frames. preferred maximum */
+ (PA_MME_MAX_HOST_BUFFER_BYTES_ / hostOutputFrameSizeBytes), /* in frames. a hard limit. note truncation due to
+ division is intentional here to limit max bytes */
+ hostFramesPerOutputBuffer,
+ hostOutputBufferCount );
+ if( result != paNoError )
+ goto error;
+
+ if( inputChannelCount > 0 ) /* full duplex */
+ {
+ /* harmonize hostFramesPerInputBuffer and hostFramesPerOutputBuffer */
+
+ /* ensure that both input and output buffer sizes are the same.
+ if they don't match at this stage, choose the smallest one
+ and use that for input and output and recompute the corresponding
+ buffer count accordingly.
+ */
+
+ if( *hostFramesPerOutputBuffer != *hostFramesPerInputBuffer )
+ {
+ if( hostFramesPerInputBuffer < hostFramesPerOutputBuffer )
+ {
+ *hostFramesPerOutputBuffer = *hostFramesPerInputBuffer;
+
+ *hostOutputBufferCount = ComputeHostBufferCountForFixedBufferSizeFrames(
+ (unsigned long)(suggestedOutputLatency * sampleRate),
+ *hostOutputBufferCount,
+ PA_MME_MIN_HOST_OUTPUT_BUFFER_COUNT_ );
+ }
+ else
+ {
+ *hostFramesPerInputBuffer = *hostFramesPerOutputBuffer;
+
+ *hostInputBufferCount = ComputeHostBufferCountForFixedBufferSizeFrames(
+ (unsigned long)(suggestedInputLatency * sampleRate),
+ *hostFramesPerInputBuffer,
+ PA_MME_MIN_HOST_INPUT_BUFFER_COUNT_FULL_DUPLEX_ );
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ *hostFramesPerOutputBuffer = 0;
+ *hostOutputBufferCount = 0;
+ }
+
+error:
+ return result;
+}
+
+
+typedef struct
+{
+ HANDLE bufferEvent;
+ void *waveHandles;
+ unsigned int deviceCount;
+ /* unsigned int channelCount; */
+ WAVEHDR **waveHeaders; /* waveHeaders[device][buffer] */
+ unsigned int bufferCount;
+ unsigned int currentBufferIndex;
+ unsigned int framesPerBuffer;
+ unsigned int framesUsedInCurrentBuffer;
+}PaWinMmeSingleDirectionHandlesAndBuffers;
+
+/* prototypes for functions operating on PaWinMmeSingleDirectionHandlesAndBuffers */
+
+static void InitializeSingleDirectionHandlesAndBuffers( PaWinMmeSingleDirectionHandlesAndBuffers *handlesAndBuffers );
+static PaError InitializeWaveHandles( PaWinMmeHostApiRepresentation *winMmeHostApi,
+ PaWinMmeSingleDirectionHandlesAndBuffers *handlesAndBuffers,
+ unsigned long winMmeSpecificFlags,
+ unsigned long bytesPerHostSample,
+ double sampleRate, PaWinMmeDeviceAndChannelCount *devices,
+ unsigned int deviceCount, PaWinWaveFormatChannelMask channelMask, int isInput );
+static PaError TerminateWaveHandles( PaWinMmeSingleDirectionHandlesAndBuffers *handlesAndBuffers, int isInput, int currentlyProcessingAnError );
+static PaError InitializeWaveHeaders( PaWinMmeSingleDirectionHandlesAndBuffers *handlesAndBuffers,
+ unsigned long hostBufferCount,
+ PaSampleFormat hostSampleFormat,
+ unsigned long framesPerHostBuffer,
+ PaWinMmeDeviceAndChannelCount *devices,
+ int isInput );
+static void TerminateWaveHeaders( PaWinMmeSingleDirectionHandlesAndBuffers *handlesAndBuffers, int isInput );
+
+
+static void InitializeSingleDirectionHandlesAndBuffers( PaWinMmeSingleDirectionHandlesAndBuffers *handlesAndBuffers )
+{
+ handlesAndBuffers->bufferEvent = 0;
+ handlesAndBuffers->waveHandles = 0;
+ handlesAndBuffers->deviceCount = 0;
+ handlesAndBuffers->waveHeaders = 0;
+ handlesAndBuffers->bufferCount = 0;
+}
+
+static PaError InitializeWaveHandles( PaWinMmeHostApiRepresentation *winMmeHostApi,
+ PaWinMmeSingleDirectionHandlesAndBuffers *handlesAndBuffers,
+ unsigned long winMmeSpecificFlags,
+ unsigned long bytesPerHostSample,
+ double sampleRate, PaWinMmeDeviceAndChannelCount *devices,
+ unsigned int deviceCount, PaWinWaveFormatChannelMask channelMask, int isInput )
+{
+ PaError result;
+ MMRESULT mmresult;
+ signed int i, j;
+ PaSampleFormat sampleFormat;
+ int waveFormatTag;
+
+ /* for error cleanup we expect that InitializeSingleDirectionHandlesAndBuffers()
+ has already been called to zero some fields */
+
+ result = CreateEventWithPaError( &handlesAndBuffers->bufferEvent, NULL, FALSE, FALSE, NULL );
+ if( result != paNoError ) goto error;
+
+ if( isInput )
+ handlesAndBuffers->waveHandles = (void*)PaUtil_AllocateMemory( sizeof(HWAVEIN) * deviceCount );
+ else
+ handlesAndBuffers->waveHandles = (void*)PaUtil_AllocateMemory( sizeof(HWAVEOUT) * deviceCount );
+ if( !handlesAndBuffers->waveHandles )
+ {
+ result = paInsufficientMemory;
+ goto error;
+ }
+
+ handlesAndBuffers->deviceCount = deviceCount;
+
+ for( i = 0; i < (signed int)deviceCount; ++i )
+ {
+ if( isInput )
+ ((HWAVEIN*)handlesAndBuffers->waveHandles)[i] = 0;
+ else
+ ((HWAVEOUT*)handlesAndBuffers->waveHandles)[i] = 0;
+ }
+
+ /* @todo at the moment we only use 16 bit sample format */
+ sampleFormat = paInt16;
+ waveFormatTag = SampleFormatAndWinWmmeSpecificFlagsToLinearWaveFormatTag( sampleFormat, winMmeSpecificFlags );
+
+ for( i = 0; i < (signed int)deviceCount; ++i )
+ {
+ PaWinWaveFormat waveFormat;
+ UINT winMmeDeviceId = LocalDeviceIndexToWinMmeDeviceId( winMmeHostApi, devices[i].device );
+
+ /* @todo: consider providing a flag or #define to not try waveformat extensible
+ this could just initialize j to 1 the first time round. */
+
+ for( j = 0; j < 2; ++j )
+ {
+ switch(j){
+ case 0:
+ /* first, attempt to open the device using WAVEFORMATEXTENSIBLE,
+ if this fails we fall back to WAVEFORMATEX */
+
+ PaWin_InitializeWaveFormatExtensible( &waveFormat, devices[i].channelCount,
+ sampleFormat, waveFormatTag, sampleRate, channelMask );
+ break;
+
+ case 1:
+ /* retry with WAVEFORMATEX */
+
+ PaWin_InitializeWaveFormatEx( &waveFormat, devices[i].channelCount,
+ sampleFormat, waveFormatTag, sampleRate );
+ break;
+ }
+
+ /* REVIEW: consider not firing an event for input when a full duplex
+ stream is being used. this would probably depend on the
+ neverDropInput flag. */
+
+ if( isInput )
+ {
+ mmresult = waveInOpen( &((HWAVEIN*)handlesAndBuffers->waveHandles)[i], winMmeDeviceId,
+ (WAVEFORMATEX*)&waveFormat,
+ (DWORD_PTR)handlesAndBuffers->bufferEvent, (DWORD_PTR)0, CALLBACK_EVENT );
+ }
+ else
+ {
+ mmresult = waveOutOpen( &((HWAVEOUT*)handlesAndBuffers->waveHandles)[i], winMmeDeviceId,
+ (WAVEFORMATEX*)&waveFormat,
+ (DWORD_PTR)handlesAndBuffers->bufferEvent, (DWORD_PTR)0, CALLBACK_EVENT );
+ }
+
+ if( mmresult == MMSYSERR_NOERROR )
+ {
+ break; /* success */
+ }
+ else if( j == 0 )
+ {
+ continue; /* try again with WAVEFORMATEX */
+ }
+ else
+ {
+ switch( mmresult )
+ {
+ case MMSYSERR_ALLOCATED: /* Specified resource is already allocated. */
+ result = paDeviceUnavailable;
+ 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 MMSYSERR_BADDEVICEID: /* Specified device identifier is out of range. */
+ /* falls through */
+
+ case WAVERR_BADFORMAT: /* Attempted to open with an unsupported waveform-audio format. */
+ /* This can also occur if we try to open the device with an unsupported
+ * number of channels. This is attempted when device*ChannelCountIsKnown is
+ * set to 0.
+ */
+ /* falls through */
+ default:
+ result = paUnanticipatedHostError;
+ if( isInput )
+ {
+ PA_MME_SET_LAST_WAVEIN_ERROR( mmresult );
+ }
+ else
+ {
+ PA_MME_SET_LAST_WAVEOUT_ERROR( mmresult );
+ }
+ }
+ goto error;
+ }
+ }
+ }
+
+ return result;
+
+error:
+ TerminateWaveHandles( handlesAndBuffers, isInput, 1 /* currentlyProcessingAnError */ );
+
+ return result;
+}
+
+
+static PaError TerminateWaveHandles( PaWinMmeSingleDirectionHandlesAndBuffers *handlesAndBuffers, int isInput, int currentlyProcessingAnError )
+{
+ PaError result = paNoError;
+ MMRESULT mmresult;
+ signed int i;
+
+ if( handlesAndBuffers->waveHandles )
+ {
+ for( i = handlesAndBuffers->deviceCount-1; i >= 0; --i )
+ {
+ if( isInput )
+ {
+ if( ((HWAVEIN*)handlesAndBuffers->waveHandles)[i] )
+ mmresult = waveInClose( ((HWAVEIN*)handlesAndBuffers->waveHandles)[i] );
+ else
+ mmresult = MMSYSERR_NOERROR;
+ }
+ else
+ {
+ if( ((HWAVEOUT*)handlesAndBuffers->waveHandles)[i] )
+ mmresult = waveOutClose( ((HWAVEOUT*)handlesAndBuffers->waveHandles)[i] );
+ else
+ mmresult = MMSYSERR_NOERROR;
+ }
+
+ if( mmresult != MMSYSERR_NOERROR &&
+ !currentlyProcessingAnError ) /* don't update the error state if we're already processing an error */
+ {
+ result = paUnanticipatedHostError;
+ if( isInput )
+ {
+ PA_MME_SET_LAST_WAVEIN_ERROR( mmresult );
+ }
+ else
+ {
+ PA_MME_SET_LAST_WAVEOUT_ERROR( mmresult );
+ }
+ /* note that we don't break here, we try to continue closing devices */
+ }
+ }
+
+ PaUtil_FreeMemory( handlesAndBuffers->waveHandles );
+ handlesAndBuffers->waveHandles = 0;
+ }
+
+ if( handlesAndBuffers->bufferEvent )
+ {
+ result = CloseHandleWithPaError( handlesAndBuffers->bufferEvent );
+ handlesAndBuffers->bufferEvent = 0;
+ }
+
+ return result;
+}
+
+
+static PaError InitializeWaveHeaders( PaWinMmeSingleDirectionHandlesAndBuffers *handlesAndBuffers,
+ unsigned long hostBufferCount,
+ PaSampleFormat hostSampleFormat,
+ unsigned long framesPerHostBuffer,
+ PaWinMmeDeviceAndChannelCount *devices,
+ int isInput )
+{
+ PaError result = paNoError;
+ MMRESULT mmresult;
+ WAVEHDR *deviceWaveHeaders;
+ signed int i, j;
+
+ /* for error cleanup we expect that InitializeSingleDirectionHandlesAndBuffers()
+ has already been called to zero some fields */
+
+
+ /* allocate an array of pointers to arrays of wave headers, one array of
+ wave headers per device */
+ handlesAndBuffers->waveHeaders = (WAVEHDR**)PaUtil_AllocateMemory( sizeof(WAVEHDR*) * handlesAndBuffers->deviceCount );
+ if( !handlesAndBuffers->waveHeaders )
+ {
+ result = paInsufficientMemory;
+ goto error;
+ }
+
+ for( i = 0; i < (signed int)handlesAndBuffers->deviceCount; ++i )
+ handlesAndBuffers->waveHeaders[i] = 0;
+
+ handlesAndBuffers->bufferCount = hostBufferCount;
+
+ for( i = 0; i < (signed int)handlesAndBuffers->deviceCount; ++i )
+ {
+ int bufferBytes = Pa_GetSampleSize( hostSampleFormat ) *
+ framesPerHostBuffer * devices[i].channelCount;
+ if( bufferBytes < 0 )
+ {
+ result = paInternalError;
+ goto error;
+ }
+
+ /* Allocate an array of wave headers for device i */
+ deviceWaveHeaders = (WAVEHDR *) PaUtil_AllocateMemory( sizeof(WAVEHDR)*hostBufferCount );
+ if( !deviceWaveHeaders )
+ {
+ result = paInsufficientMemory;
+ goto error;
+ }
+
+ for( j=0; j < (signed int)hostBufferCount; ++j )
+ deviceWaveHeaders[j].lpData = 0;
+
+ handlesAndBuffers->waveHeaders[i] = deviceWaveHeaders;
+
+ /* Allocate a buffer for each wave header */
+ for( j=0; j < (signed int)hostBufferCount; ++j )
+ {
+ deviceWaveHeaders[j].lpData = (char *)PaUtil_AllocateMemory( bufferBytes );
+ if( !deviceWaveHeaders[j].lpData )
+ {
+ result = paInsufficientMemory;
+ goto error;
+ }
+ deviceWaveHeaders[j].dwBufferLength = bufferBytes;
+ deviceWaveHeaders[j].dwUser = 0xFFFFFFFF; /* indicates that *PrepareHeader() has not yet been called, for error clean up code */
+
+ if( isInput )
+ {
+ mmresult = waveInPrepareHeader( ((HWAVEIN*)handlesAndBuffers->waveHandles)[i], &deviceWaveHeaders[j], sizeof(WAVEHDR) );
+ if( mmresult != MMSYSERR_NOERROR )
+ {
+ result = paUnanticipatedHostError;
+ PA_MME_SET_LAST_WAVEIN_ERROR( mmresult );
+ goto error;
+ }
+ }
+ else /* output */
+ {
+ mmresult = waveOutPrepareHeader( ((HWAVEOUT*)handlesAndBuffers->waveHandles)[i], &deviceWaveHeaders[j], sizeof(WAVEHDR) );
+ if( mmresult != MMSYSERR_NOERROR )
+ {
+ result = paUnanticipatedHostError;
+ PA_MME_SET_LAST_WAVEIN_ERROR( mmresult );
+ goto error;
+ }
+ }
+ deviceWaveHeaders[j].dwUser = devices[i].channelCount;
+ }
+ }
+
+ return result;
+
+error:
+ TerminateWaveHeaders( handlesAndBuffers, isInput );
+
+ return result;
+}
+
+
+static void TerminateWaveHeaders( PaWinMmeSingleDirectionHandlesAndBuffers *handlesAndBuffers, int isInput )
+{
+ signed int i, j;
+ WAVEHDR *deviceWaveHeaders;
+
+ if( handlesAndBuffers->waveHeaders )
+ {
+ for( i = handlesAndBuffers->deviceCount-1; i >= 0 ; --i )
+ {
+ deviceWaveHeaders = handlesAndBuffers->waveHeaders[i]; /* wave headers for device i */
+ if( deviceWaveHeaders )
+ {
+ for( j = handlesAndBuffers->bufferCount-1; j >= 0; --j )
+ {
+ if( deviceWaveHeaders[j].lpData )
+ {
+ if( deviceWaveHeaders[j].dwUser != 0xFFFFFFFF )
+ {
+ if( isInput )
+ waveInUnprepareHeader( ((HWAVEIN*)handlesAndBuffers->waveHandles)[i], &deviceWaveHeaders[j], sizeof(WAVEHDR) );
+ else
+ waveOutUnprepareHeader( ((HWAVEOUT*)handlesAndBuffers->waveHandles)[i], &deviceWaveHeaders[j], sizeof(WAVEHDR) );
+ }
+
+ PaUtil_FreeMemory( deviceWaveHeaders[j].lpData );
+ }
+ }
+
+ PaUtil_FreeMemory( deviceWaveHeaders );
+ }
+ }
+
+ PaUtil_FreeMemory( handlesAndBuffers->waveHeaders );
+ handlesAndBuffers->waveHeaders = 0;
+ }
+}
+
+
+
+/* PaWinMmeStream - a stream data structure specifically for this implementation */
+/* note that struct PaWinMmeStream is typedeffed to PaWinMmeStream above. */
+struct PaWinMmeStream
+{
+ PaUtilStreamRepresentation streamRepresentation;
+ PaUtilCpuLoadMeasurer cpuLoadMeasurer;
+ PaUtilBufferProcessor bufferProcessor;
+
+ int primeStreamUsingCallback;
+
+ PaWinMmeSingleDirectionHandlesAndBuffers input;
+ PaWinMmeSingleDirectionHandlesAndBuffers output;
+
+ /* Processing thread management -------------- */
+ HANDLE abortEvent;
+ HANDLE processingThread;
+ PA_THREAD_ID processingThreadId;
+
+ char throttleProcessingThreadOnOverload; /* 0 -> don't throttle, non-0 -> throttle */
+ int processingThreadPriority;
+ int highThreadPriority;
+ int throttledThreadPriority;
+ unsigned long throttledSleepMsecs;
+
+ int isStopped;
+ 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 */
+};
+
+/* updates deviceCount if PaWinMmeUseMultipleDevices is used */
+
+static PaError ValidateWinMmeSpecificStreamInfo(
+ const PaStreamParameters *streamParameters,
+ const PaWinMmeStreamInfo *streamInfo,
+ unsigned long *winMmeSpecificFlags,
+ char *throttleProcessingThreadOnOverload,
+ unsigned long *deviceCount )
+{
+ if( streamInfo )
+ {
+ if( streamInfo->size != sizeof( PaWinMmeStreamInfo )
+ || streamInfo->version != 1 )
+ {
+ return paIncompatibleHostApiSpecificStreamInfo;
+ }
+
+ *winMmeSpecificFlags = streamInfo->flags;
+
+ if( streamInfo->flags & paWinMmeDontThrottleOverloadedProcessingThread )
+ *throttleProcessingThreadOnOverload = 0;
+
+ if( streamInfo->flags & paWinMmeUseMultipleDevices )
+ {
+ if( streamParameters->device != paUseHostApiSpecificDeviceSpecification )
+ return paInvalidDevice;
+
+ *deviceCount = streamInfo->deviceCount;
+ }
+ }
+
+ return paNoError;
+}
+
+static PaError RetrieveDevicesFromStreamParameters(
+ struct PaUtilHostApiRepresentation *hostApi,
+ const PaStreamParameters *streamParameters,
+ const PaWinMmeStreamInfo *streamInfo,
+ PaWinMmeDeviceAndChannelCount *devices,
+ unsigned long deviceCount )
+{
+ PaError result = paNoError;
+ unsigned int i;
+ int totalChannelCount;
+ PaDeviceIndex hostApiDevice;
+
+ if( streamInfo && streamInfo->flags & paWinMmeUseMultipleDevices )
+ {
+ totalChannelCount = 0;
+ for( i=0; i < deviceCount; ++i )
+ {
+ /* validate that the device number is within range */
+ result = PaUtil_DeviceIndexToHostApiDeviceIndex( &hostApiDevice,
+ streamInfo->devices[i].device, hostApi );
+ if( result != paNoError )
+ return result;
+
+ devices[i].device = hostApiDevice;
+ devices[i].channelCount = streamInfo->devices[i].channelCount;
+
+ totalChannelCount += devices[i].channelCount;
+ }
+
+ if( totalChannelCount != streamParameters->channelCount )
+ {
+ /* channelCount must match total channels specified by multiple devices */
+ return paInvalidChannelCount; /* REVIEW use of this error code */
+ }
+ }
+ else
+ {
+ devices[0].device = streamParameters->device;
+ devices[0].channelCount = streamParameters->channelCount;
+ }
+
+ return result;
+}
+
+static PaError ValidateInputChannelCounts(
+ struct PaUtilHostApiRepresentation *hostApi,
+ PaWinMmeDeviceAndChannelCount *devices,
+ unsigned long deviceCount )
+{
+ unsigned int i;
+ PaWinMmeDeviceInfo *inputDeviceInfo;
+ PaError paerror;
+
+ for( i=0; i < deviceCount; ++i )
+ {
+ if( devices[i].channelCount < 1 )
+ return paInvalidChannelCount;
+
+ inputDeviceInfo =
+ (PaWinMmeDeviceInfo*)hostApi->deviceInfos[ devices[i].device ];
+
+ paerror = IsInputChannelCountSupported( inputDeviceInfo, devices[i].channelCount );
+ if( paerror != paNoError )
+ return paerror;
+ }
+
+ return paNoError;
+}
+
+static PaError ValidateOutputChannelCounts(
+ struct PaUtilHostApiRepresentation *hostApi,
+ PaWinMmeDeviceAndChannelCount *devices,
+ unsigned long deviceCount )
+{
+ unsigned int i;
+ PaWinMmeDeviceInfo *outputDeviceInfo;
+ PaError paerror;
+
+ for( i=0; i < deviceCount; ++i )
+ {
+ if( devices[i].channelCount < 1 )
+ return paInvalidChannelCount;
+
+ outputDeviceInfo =
+ (PaWinMmeDeviceInfo*)hostApi->deviceInfos[ devices[i].device ];
+
+ paerror = IsOutputChannelCountSupported( outputDeviceInfo, devices[i].channelCount );
+ if( paerror != paNoError )
+ return paerror;
+ }
+
+ return paNoError;
+}
+
+
+/* the following macros are intended to improve the readability of the following code */
+#define PA_IS_INPUT_STREAM_( stream ) ( stream ->input.waveHandles )
+#define PA_IS_OUTPUT_STREAM_( stream ) ( stream ->output.waveHandles )
+#define PA_IS_FULL_DUPLEX_STREAM_( stream ) ( stream ->input.waveHandles && stream ->output.waveHandles )
+#define PA_IS_HALF_DUPLEX_STREAM_( stream ) ( !(stream ->input.waveHandles && stream ->output.waveHandles) )
+
+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;
+ PaWinMmeHostApiRepresentation *winMmeHostApi = (PaWinMmeHostApiRepresentation*)hostApi;
+ PaWinMmeStream *stream = 0;
+ int bufferProcessorIsInitialized = 0;
+ int streamRepresentationIsInitialized = 0;
+ PaSampleFormat hostInputSampleFormat, hostOutputSampleFormat;
+ int inputChannelCount, outputChannelCount;
+ PaSampleFormat inputSampleFormat, outputSampleFormat;
+ double suggestedInputLatency, suggestedOutputLatency;
+ PaWinMmeStreamInfo *inputStreamInfo, *outputStreamInfo;
+ PaWinWaveFormatChannelMask inputChannelMask, outputChannelMask;
+ unsigned long framesPerHostInputBuffer;
+ unsigned long hostInputBufferCount;
+ unsigned long framesPerHostOutputBuffer;
+ unsigned long hostOutputBufferCount;
+ unsigned long framesPerBufferProcessorCall;
+ PaWinMmeDeviceAndChannelCount *inputDevices = 0; /* contains all devices and channel counts as local host api ids, even when PaWinMmeUseMultipleDevices is not used */
+ unsigned long winMmeSpecificInputFlags = 0;
+ unsigned long inputDeviceCount = 0;
+ PaWinMmeDeviceAndChannelCount *outputDevices = 0;
+ unsigned long winMmeSpecificOutputFlags = 0;
+ unsigned long outputDeviceCount = 0; /* contains all devices and channel counts as local host api ids, even when PaWinMmeUseMultipleDevices is not used */
+ char throttleProcessingThreadOnOverload = 1;
+
+
+ if( inputParameters )
+ {
+ inputChannelCount = inputParameters->channelCount;
+ inputSampleFormat = inputParameters->sampleFormat;
+ suggestedInputLatency = inputParameters->suggestedLatency;
+
+ inputDeviceCount = 1;
+
+ /* validate input hostApiSpecificStreamInfo */
+ inputStreamInfo = (PaWinMmeStreamInfo*)inputParameters->hostApiSpecificStreamInfo;
+ result = ValidateWinMmeSpecificStreamInfo( inputParameters, inputStreamInfo,
+ &winMmeSpecificInputFlags,
+ &throttleProcessingThreadOnOverload,
+ &inputDeviceCount );
+ if( result != paNoError ) return result;
+
+ inputDevices = (PaWinMmeDeviceAndChannelCount*)alloca( sizeof(PaWinMmeDeviceAndChannelCount) * inputDeviceCount );
+ if( !inputDevices ) return paInsufficientMemory;
+
+ result = RetrieveDevicesFromStreamParameters( hostApi, inputParameters, inputStreamInfo, inputDevices, inputDeviceCount );
+ if( result != paNoError ) return result;
+
+ result = ValidateInputChannelCounts( hostApi, inputDevices, inputDeviceCount );
+ if( result != paNoError ) return result;
+
+ hostInputSampleFormat =
+ PaUtil_SelectClosestAvailableFormat( paInt16 /* native formats */, inputSampleFormat );
+
+ if( inputDeviceCount != 1 ){
+ /* always use direct speakers when using multi-device multichannel mode */
+ inputChannelMask = PAWIN_SPEAKER_DIRECTOUT;
+ }
+ else
+ {
+ if( inputStreamInfo && inputStreamInfo->flags & paWinMmeUseChannelMask )
+ inputChannelMask = inputStreamInfo->channelMask;
+ else
+ inputChannelMask = PaWin_DefaultChannelMask( inputDevices[0].channelCount );
+ }
+ }
+ else
+ {
+ inputChannelCount = 0;
+ inputSampleFormat = 0;
+ suggestedInputLatency = 0.;
+ inputStreamInfo = 0;
+ hostInputSampleFormat = 0;
+ }
+
+
+ if( outputParameters )
+ {
+ outputChannelCount = outputParameters->channelCount;
+ outputSampleFormat = outputParameters->sampleFormat;
+ suggestedOutputLatency = outputParameters->suggestedLatency;
+
+ outputDeviceCount = 1;
+
+ /* validate output hostApiSpecificStreamInfo */
+ outputStreamInfo = (PaWinMmeStreamInfo*)outputParameters->hostApiSpecificStreamInfo;
+ result = ValidateWinMmeSpecificStreamInfo( outputParameters, outputStreamInfo,
+ &winMmeSpecificOutputFlags,
+ &throttleProcessingThreadOnOverload,
+ &outputDeviceCount );
+ if( result != paNoError ) return result;
+
+ outputDevices = (PaWinMmeDeviceAndChannelCount*)alloca( sizeof(PaWinMmeDeviceAndChannelCount) * outputDeviceCount );
+ if( !outputDevices ) return paInsufficientMemory;
+
+ result = RetrieveDevicesFromStreamParameters( hostApi, outputParameters, outputStreamInfo, outputDevices, outputDeviceCount );
+ if( result != paNoError ) return result;
+
+ result = ValidateOutputChannelCounts( hostApi, outputDevices, outputDeviceCount );
+ if( result != paNoError ) return result;
+
+ hostOutputSampleFormat =
+ PaUtil_SelectClosestAvailableFormat( paInt16 /* native formats */, outputSampleFormat );
+
+ if( outputDeviceCount != 1 ){
+ /* always use direct speakers when using multi-device multichannel mode */
+ outputChannelMask = PAWIN_SPEAKER_DIRECTOUT;
+ }
+ else
+ {
+ if( outputStreamInfo && outputStreamInfo->flags & paWinMmeUseChannelMask )
+ outputChannelMask = outputStreamInfo->channelMask;
+ else
+ outputChannelMask = PaWin_DefaultChannelMask( outputDevices[0].channelCount );
+ }
+ }
+ else
+ {
+ outputChannelCount = 0;
+ outputSampleFormat = 0;
+ outputStreamInfo = 0;
+ hostOutputSampleFormat = 0;
+ 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 */
+
+
+ /* always disable clipping and dithering if we are outputting a raw spdif stream */
+ if( (winMmeSpecificOutputFlags & paWinMmeWaveFormatDolbyAc3Spdif)
+ || (winMmeSpecificOutputFlags & paWinMmeWaveFormatWmaSpdif) ){
+
+ streamFlags = streamFlags | paClipOff | paDitherOff;
+ }
+
+
+ result = CalculateBufferSettings( &framesPerHostInputBuffer, &hostInputBufferCount,
+ &framesPerHostOutputBuffer, &hostOutputBufferCount,
+ 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;
+ }
+
+ InitializeSingleDirectionHandlesAndBuffers( &stream->input );
+ InitializeSingleDirectionHandlesAndBuffers( &stream->output );
+
+ stream->abortEvent = 0;
+ stream->processingThread = 0;
+
+ stream->throttleProcessingThreadOnOverload = throttleProcessingThreadOnOverload;
+
+ PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation,
+ ( (streamCallback)
+ ? &winMmeHostApi->callbackStreamInterface
+ : &winMmeHostApi->blockingStreamInterface ),
+ streamCallback, userData );
+ streamRepresentationIsInitialized = 1;
+
+ PaUtil_InitializeCpuLoadMeasurer( &stream->cpuLoadMeasurer, sampleRate );
+
+
+ if( inputParameters && outputParameters ) /* full duplex */
+ {
+ if( framesPerHostInputBuffer < framesPerHostOutputBuffer )
+ {
+ assert( (framesPerHostOutputBuffer % framesPerHostInputBuffer) == 0 ); /* CalculateBufferSettings() should guarantee this condition */
+
+ framesPerBufferProcessorCall = framesPerHostInputBuffer;
+ }
+ else
+ {
+ assert( (framesPerHostInputBuffer % framesPerHostOutputBuffer) == 0 ); /* CalculateBufferSettings() should guarantee this condition */
+
+ framesPerBufferProcessorCall = framesPerHostOutputBuffer;
+ }
+ }
+ else if( inputParameters )
+ {
+ framesPerBufferProcessorCall = framesPerHostInputBuffer;
+ }
+ else if( outputParameters )
+ {
+ framesPerBufferProcessorCall = framesPerHostOutputBuffer;
+ }
+
+ stream->input.framesPerBuffer = framesPerHostInputBuffer;
+ stream->output.framesPerBuffer = framesPerHostOutputBuffer;
+
+ result = PaUtil_InitializeBufferProcessor( &stream->bufferProcessor,
+ inputChannelCount, inputSampleFormat, hostInputSampleFormat,
+ outputChannelCount, outputSampleFormat, hostOutputSampleFormat,
+ sampleRate, streamFlags, framesPerBuffer,
+ framesPerBufferProcessorCall, paUtilFixedHostBufferSize,
+ streamCallback, userData );
+ if( result != paNoError ) goto error;
+
+ bufferProcessorIsInitialized = 1;
+
+ /* stream info input latency is the minimum buffering latency (unlike suggested and default which are *maximums*) */
+ stream->streamRepresentation.streamInfo.inputLatency =
+ (double)(PaUtil_GetBufferProcessorInputLatencyFrames(&stream->bufferProcessor)
+ + framesPerHostInputBuffer) / sampleRate;
+ stream->streamRepresentation.streamInfo.outputLatency =
+ (double)(PaUtil_GetBufferProcessorOutputLatencyFrames(&stream->bufferProcessor)
+ + (framesPerHostOutputBuffer * (hostOutputBufferCount-1))) / sampleRate;
+ stream->streamRepresentation.streamInfo.sampleRate = sampleRate;
+
+ stream->primeStreamUsingCallback = ( (streamFlags&paPrimeOutputBuffersUsingStreamCallback) && streamCallback ) ? 1 : 0;
+
+ /* time to sleep when throttling due to >100% cpu usage.
+ -a quarter of a buffer's duration */
+ stream->throttledSleepMsecs =
+ (unsigned long)(stream->bufferProcessor.framesPerHostBuffer *
+ stream->bufferProcessor.samplePeriod * .25 * 1000);
+
+ stream->isStopped = 1;
+ stream->isActive = 0;
+
+
+ /* for maximum compatibility with multi-device multichannel drivers,
+ we first open all devices, then we prepare all buffers, finally
+ we start all devices ( in StartStream() ). teardown in reverse order.
+ */
+
+ if( inputParameters )
+ {
+ result = InitializeWaveHandles( winMmeHostApi, &stream->input,
+ winMmeSpecificInputFlags,
+ stream->bufferProcessor.bytesPerHostInputSample, sampleRate,
+ inputDevices, inputDeviceCount, inputChannelMask, 1 /* isInput */ );
+ if( result != paNoError ) goto error;
+ }
+
+ if( outputParameters )
+ {
+ result = InitializeWaveHandles( winMmeHostApi, &stream->output,
+ winMmeSpecificOutputFlags,
+ stream->bufferProcessor.bytesPerHostOutputSample, sampleRate,
+ outputDevices, outputDeviceCount, outputChannelMask, 0 /* isInput */ );
+ if( result != paNoError ) goto error;
+ }
+
+ if( inputParameters )
+ {
+ result = InitializeWaveHeaders( &stream->input, hostInputBufferCount,
+ hostInputSampleFormat, framesPerHostInputBuffer, inputDevices, 1 /* isInput */ );
+ if( result != paNoError ) goto error;
+ }
+
+ if( outputParameters )
+ {
+ result = InitializeWaveHeaders( &stream->output, hostOutputBufferCount,
+ hostOutputSampleFormat, framesPerHostOutputBuffer, outputDevices, 0 /* not isInput */ );
+ if( result != paNoError ) goto error;
+
+ stream->allBuffersDurationMs = (DWORD) (1000.0 * (framesPerHostOutputBuffer * stream->output.bufferCount) / sampleRate);
+ }
+ else
+ {
+ stream->allBuffersDurationMs = (DWORD) (1000.0 * (framesPerHostInputBuffer * stream->input.bufferCount) / sampleRate);
+ }
+
+
+ if( streamCallback )
+ {
+ /* abort event is only needed for callback streams */
+ result = CreateEventWithPaError( &stream->abortEvent, NULL, TRUE, FALSE, NULL );
+ if( result != paNoError ) goto error;
+ }
+
+ *s = (PaStream*)stream;
+
+ return result;
+
+error:
+
+ if( stream )
+ {
+ if( stream->abortEvent )
+ CloseHandle( stream->abortEvent );
+
+ TerminateWaveHeaders( &stream->output, 0 /* not isInput */ );
+ TerminateWaveHeaders( &stream->input, 1 /* isInput */ );
+
+ TerminateWaveHandles( &stream->output, 0 /* not isInput */, 1 /* currentlyProcessingAnError */ );
+ TerminateWaveHandles( &stream->input, 1 /* isInput */, 1 /* currentlyProcessingAnError */ );
+
+ if( bufferProcessorIsInitialized )
+ PaUtil_TerminateBufferProcessor( &stream->bufferProcessor );
+
+ if( streamRepresentationIsInitialized )
+ PaUtil_TerminateStreamRepresentation( &stream->streamRepresentation );
+
+ PaUtil_FreeMemory( stream );
+ }
+
+ return result;
+}
+
+
+/* return non-zero if all current buffers are done */
+static int BuffersAreDone( WAVEHDR **waveHeaders, unsigned int deviceCount, int bufferIndex )
+{
+ unsigned int i;
+
+ for( i=0; i < deviceCount; ++i )
+ {
+ if( !(waveHeaders[i][ bufferIndex ].dwFlags & WHDR_DONE) )
+ {
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+static int CurrentInputBuffersAreDone( PaWinMmeStream *stream )
+{
+ return BuffersAreDone( stream->input.waveHeaders, stream->input.deviceCount, stream->input.currentBufferIndex );
+}
+
+static int CurrentOutputBuffersAreDone( PaWinMmeStream *stream )
+{
+ return BuffersAreDone( stream->output.waveHeaders, stream->output.deviceCount, stream->output.currentBufferIndex );
+}
+
+
+/* return non-zero if any buffers are queued */
+static int NoBuffersAreQueued( PaWinMmeSingleDirectionHandlesAndBuffers *handlesAndBuffers )
+{
+ unsigned int i, j;
+
+ if( handlesAndBuffers->waveHandles )
+ {
+ for( i=0; i < handlesAndBuffers->bufferCount; ++i )
+ {
+ for( j=0; j < handlesAndBuffers->deviceCount; ++j )
+ {
+ if( !( handlesAndBuffers->waveHeaders[ j ][ i ].dwFlags & WHDR_DONE) )
+ {
+ return 0;
+ }
+ }
+ }
+ }
+
+ return 1;
+}
+
+
+#define PA_CIRCULAR_INCREMENT_( current, max )\
+ ( (((current) + 1) >= (max)) ? (0) : (current+1) )
+
+#define PA_CIRCULAR_DECREMENT_( current, max )\
+ ( ((current) == 0) ? ((max)-1) : (current-1) )
+
+
+static signed long GetAvailableFrames( PaWinMmeSingleDirectionHandlesAndBuffers *handlesAndBuffers )
+{
+ signed long result = 0;
+ unsigned int i;
+
+ if( BuffersAreDone( handlesAndBuffers->waveHeaders, handlesAndBuffers->deviceCount, handlesAndBuffers->currentBufferIndex ) )
+ {
+ /* we could calculate the following in O(1) if we kept track of the
+ last done buffer */
+ result = handlesAndBuffers->framesPerBuffer - handlesAndBuffers->framesUsedInCurrentBuffer;
+
+ i = PA_CIRCULAR_INCREMENT_( handlesAndBuffers->currentBufferIndex, handlesAndBuffers->bufferCount );
+ while( i != handlesAndBuffers->currentBufferIndex )
+ {
+ if( BuffersAreDone( handlesAndBuffers->waveHeaders, handlesAndBuffers->deviceCount, i ) )
+ {
+ result += handlesAndBuffers->framesPerBuffer;
+ i = PA_CIRCULAR_INCREMENT_( i, handlesAndBuffers->bufferCount );
+ }
+ else
+ break;
+ }
+ }
+
+ return result;
+}
+
+
+static PaError AdvanceToNextInputBuffer( PaWinMmeStream *stream )
+{
+ PaError result = paNoError;
+ MMRESULT mmresult;
+ unsigned int i;
+
+ for( i=0; i < stream->input.deviceCount; ++i )
+ {
+ stream->input.waveHeaders[i][ stream->input.currentBufferIndex ].dwFlags &= ~WHDR_DONE;
+ mmresult = waveInAddBuffer( ((HWAVEIN*)stream->input.waveHandles)[i],
+ &stream->input.waveHeaders[i][ stream->input.currentBufferIndex ],
+ sizeof(WAVEHDR) );
+ if( mmresult != MMSYSERR_NOERROR )
+ {
+ result = paUnanticipatedHostError;
+ PA_MME_SET_LAST_WAVEIN_ERROR( mmresult );
+ }
+ }
+
+ stream->input.currentBufferIndex =
+ PA_CIRCULAR_INCREMENT_( stream->input.currentBufferIndex, stream->input.bufferCount );
+
+ stream->input.framesUsedInCurrentBuffer = 0;
+
+ return result;
+}
+
+
+static PaError AdvanceToNextOutputBuffer( PaWinMmeStream *stream )
+{
+ PaError result = paNoError;
+ MMRESULT mmresult;
+ unsigned int i;
+
+ for( i=0; i < stream->output.deviceCount; ++i )
+ {
+ mmresult = waveOutWrite( ((HWAVEOUT*)stream->output.waveHandles)[i],
+ &stream->output.waveHeaders[i][ stream->output.currentBufferIndex ],
+ sizeof(WAVEHDR) );
+ if( mmresult != MMSYSERR_NOERROR )
+ {
+ result = paUnanticipatedHostError;
+ PA_MME_SET_LAST_WAVEOUT_ERROR( mmresult );
+ }
+ }
+
+ stream->output.currentBufferIndex =
+ PA_CIRCULAR_INCREMENT_( stream->output.currentBufferIndex, stream->output.bufferCount );
+
+ stream->output.framesUsedInCurrentBuffer = 0;
+
+ return result;
+}
+
+
+/* requeue all but the most recent input with the driver. Used for catching
+ up after a total input buffer underrun */
+static PaError CatchUpInputBuffers( PaWinMmeStream *stream )
+{
+ PaError result = paNoError;
+ unsigned int i;
+
+ for( i=0; i < stream->input.bufferCount - 1; ++i )
+ {
+ result = AdvanceToNextInputBuffer( stream );
+ if( result != paNoError )
+ break;
+ }
+
+ return result;
+}
+
+
+/* take the most recent output and duplicate it to all other output buffers
+ and requeue them. Used for catching up after a total output buffer underrun.
+*/
+static PaError CatchUpOutputBuffers( PaWinMmeStream *stream )
+{
+ PaError result = paNoError;
+ unsigned int i, j;
+ unsigned int previousBufferIndex =
+ PA_CIRCULAR_DECREMENT_( stream->output.currentBufferIndex, stream->output.bufferCount );
+
+ for( i=0; i < stream->output.bufferCount - 1; ++i )
+ {
+ for( j=0; j < stream->output.deviceCount; ++j )
+ {
+ if( stream->output.waveHeaders[j][ stream->output.currentBufferIndex ].lpData
+ != stream->output.waveHeaders[j][ previousBufferIndex ].lpData )
+ {
+ CopyMemory( stream->output.waveHeaders[j][ stream->output.currentBufferIndex ].lpData,
+ stream->output.waveHeaders[j][ previousBufferIndex ].lpData,
+ stream->output.waveHeaders[j][ stream->output.currentBufferIndex ].dwBufferLength );
+ }
+ }
+
+ result = AdvanceToNextOutputBuffer( stream );
+ if( result != paNoError )
+ break;
+ }
+
+ return result;
+}
+
+
+PA_THREAD_FUNC ProcessingThreadProc( void *pArg )
+{
+ PaWinMmeStream *stream = (PaWinMmeStream *)pArg;
+ HANDLE events[3];
+ int eventCount = 0;
+ DWORD result = paNoError;
+ DWORD waitResult;
+ DWORD timeout = (unsigned long)(stream->allBuffersDurationMs * 0.5);
+ int hostBuffersAvailable;
+ signed int hostInputBufferIndex, hostOutputBufferIndex;
+ PaStreamCallbackFlags statusFlags;
+ int callbackResult;
+ int done = 0;
+ unsigned int channel, i;
+ unsigned long framesProcessed;
+
+ /* prepare event array for call to WaitForMultipleObjects() */
+ if( stream->input.bufferEvent )
+ events[eventCount++] = stream->input.bufferEvent;
+ if( stream->output.bufferEvent )
+ events[eventCount++] = stream->output.bufferEvent;
+ events[eventCount++] = stream->abortEvent;
+
+ statusFlags = 0; /** @todo support paInputUnderflow, paOutputOverflow and paNeverDropInput */
+
+ /* loop until something causes us to stop */
+ do{
+ /* wait for MME to signal that a buffer is available, or for
+ the PA abort event to be signaled.
+
+ When this indicates that one or more buffers are available
+ NoBuffersAreQueued() and Current*BuffersAreDone are used below to
+ poll for additional done buffers. NoBuffersAreQueued() will fail
+ to identify an underrun/overflow if the driver doesn't mark all done
+ buffers prior to signalling the event. Some drivers do this
+ (eg RME Digi96, and others don't eg VIA PC 97 input). This isn't a
+ huge problem, it just means that we won't always be able to detect
+ underflow/overflow.
+ */
+ waitResult = WaitForMultipleObjects( eventCount, events, FALSE /* wait all = FALSE */, timeout );
+ if( waitResult == WAIT_FAILED )
+ {
+ result = paUnanticipatedHostError;
+ /** @todo FIXME/REVIEW: can't return host error info from an asynchronous thread. see http://www.portaudio.com/trac/ticket/143 */
+ done = 1;
+ }
+ else if( waitResult == WAIT_TIMEOUT )
+ {
+ /* if a timeout is encountered, continue */
+ }
+
+ 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( PA_IS_OUTPUT_STREAM_(stream) )
+ {
+ if( NoBuffersAreQueued( &stream->output ) )
+ done = 1; /* Will cause thread to return. */
+ }
+ else
+ {
+ /* input only 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) )
+ {
+ if( CurrentInputBuffersAreDone( stream ) )
+ {
+ if( NoBuffersAreQueued( &stream->input ) )
+ {
+ /** @todo
+ if all of the other buffers are also ready then
+ we discard all but the most recent. This is an
+ input buffer overflow. FIXME: these buffers should
+ be passed to the callback in a paNeverDropInput
+ stream. http://www.portaudio.com/trac/ticket/142
+
+ note that it is also possible for an input overflow
+ to happen while the callback is processing a buffer.
+ that is handled further down.
+ */
+ result = CatchUpInputBuffers( stream );
+ if( result != paNoError )
+ done = 1;
+
+ statusFlags |= paInputOverflow;
+ }
+
+ hostInputBufferIndex = stream->input.currentBufferIndex;
+ }
+ }
+
+ if( PA_IS_OUTPUT_STREAM_(stream) )
+ {
+ if( CurrentOutputBuffersAreDone( stream ) )
+ {
+ /* ok, we have an output buffer */
+
+ if( NoBuffersAreQueued( &stream->output ) )
+ {
+ /*
+ 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.
+
+ note that this catch up code only handles the case where all
+ buffers have been played out due to this thread not having
+ woken up at all. a more common case occurs when this thread
+ is woken up, processes one buffer, but takes too long, and as
+ a result all the other buffers have become un-queued. that
+ case is handled further down.
+ */
+
+ result = CatchUpOutputBuffers( stream );
+ if( result != paNoError )
+ done = 1;
+
+ statusFlags |= paOutputUnderflow;
+ }
+
+ hostOutputBufferIndex = stream->output.currentBufferIndex;
+ }
+ }
+
+
+ if( (PA_IS_FULL_DUPLEX_STREAM_(stream) && hostInputBufferIndex != -1 && hostOutputBufferIndex != -1) ||
+ (PA_IS_HALF_DUPLEX_STREAM_(stream) && ( hostInputBufferIndex != -1 || hostOutputBufferIndex != -1 ) ) )
+ {
+ PaStreamCallbackTimeInfo timeInfo = {0,0,0}; /** @todo implement inputBufferAdcTime */
+
+
+ if( PA_IS_OUTPUT_STREAM_(stream) )
+ {
+ /* set timeInfo.currentTime and calculate timeInfo.outputBufferDacTime
+ from the current wave out position */
+ MMTIME mmtime;
+ double timeBeforeGetPosition, timeAfterGetPosition;
+ double time;
+ long framesInBufferRing;
+ long writePosition;
+ long playbackPosition;
+ HWAVEOUT firstWaveOutDevice = ((HWAVEOUT*)stream->output.waveHandles)[0];
+
+ mmtime.wType = TIME_SAMPLES;
+ timeBeforeGetPosition = PaUtil_GetTime();
+ waveOutGetPosition( firstWaveOutDevice, &mmtime, sizeof(MMTIME) );
+ timeAfterGetPosition = PaUtil_GetTime();
+
+ timeInfo.currentTime = timeAfterGetPosition;
+
+ /* approximate time at which wave out position was measured
+ as half way between timeBeforeGetPosition and timeAfterGetPosition */
+ time = timeBeforeGetPosition + (timeAfterGetPosition - timeBeforeGetPosition) * .5;
+
+ framesInBufferRing = stream->output.bufferCount * stream->bufferProcessor.framesPerHostBuffer;
+ playbackPosition = mmtime.u.sample % framesInBufferRing;
+
+ writePosition = stream->output.currentBufferIndex * stream->bufferProcessor.framesPerHostBuffer
+ + stream->output.framesUsedInCurrentBuffer;
+
+ if( playbackPosition >= writePosition ){
+ timeInfo.outputBufferDacTime =
+ time + ((double)( writePosition + (framesInBufferRing - playbackPosition) ) * stream->bufferProcessor.samplePeriod );
+ }else{
+ timeInfo.outputBufferDacTime =
+ time + ((double)( writePosition - playbackPosition ) * stream->bufferProcessor.samplePeriod );
+ }
+ }
+
+
+ PaUtil_BeginCpuLoadMeasurement( &stream->cpuLoadMeasurer );
+
+ PaUtil_BeginBufferProcessing( &stream->bufferProcessor, &timeInfo, statusFlags );
+
+ /* reset status flags once they have been passed to the buffer processor */
+ statusFlags = 0;
+
+ if( PA_IS_INPUT_STREAM_(stream) )
+ {
+ PaUtil_SetInputFrameCount( &stream->bufferProcessor, 0 /* default to host buffer size */ );
+
+ channel = 0;
+ for( i=0; i<stream->input.deviceCount; ++i )
+ {
+ /* we have stored the number of channels in the buffer in dwUser */
+ int channelCount = (int)stream->input.waveHeaders[i][ hostInputBufferIndex ].dwUser;
+
+ PaUtil_SetInterleavedInputChannels( &stream->bufferProcessor, channel,
+ stream->input.waveHeaders[i][ hostInputBufferIndex ].lpData +
+ stream->input.framesUsedInCurrentBuffer * channelCount *
+ stream->bufferProcessor.bytesPerHostInputSample,
+ channelCount );
+
+
+ channel += channelCount;
+ }
+ }
+
+ if( PA_IS_OUTPUT_STREAM_(stream) )
+ {
+ PaUtil_SetOutputFrameCount( &stream->bufferProcessor, 0 /* default to host buffer size */ );
+
+ channel = 0;
+ for( i=0; i<stream->output.deviceCount; ++i )
+ {
+ /* we have stored the number of channels in the buffer in dwUser */
+ int channelCount = (int)stream->output.waveHeaders[i][ hostOutputBufferIndex ].dwUser;
+
+ PaUtil_SetInterleavedOutputChannels( &stream->bufferProcessor, channel,
+ stream->output.waveHeaders[i][ hostOutputBufferIndex ].lpData +
+ stream->output.framesUsedInCurrentBuffer * channelCount *
+ stream->bufferProcessor.bytesPerHostOutputSample,
+ channelCount );
+
+ channel += channelCount;
+ }
+ }
+
+ callbackResult = paContinue;
+ framesProcessed = PaUtil_EndBufferProcessing( &stream->bufferProcessor, &callbackResult );
+
+ stream->input.framesUsedInCurrentBuffer += framesProcessed;
+ stream->output.framesUsedInCurrentBuffer += framesProcessed;
+
+ PaUtil_EndCpuLoadMeasurement( &stream->cpuLoadMeasurer, framesProcessed );
+
+ if( callbackResult == paContinue )
+ {
+ /* nothing special to do */
+ }
+ else if( callbackResult == paAbort )
+ {
+ stream->abortProcessing = 1;
+ done = 1;
+ /** @todo FIXME: should probably reset the output device immediately once the callback returns paAbort
+ see: http://www.portaudio.com/trac/ticket/141
+ */
+ result = paNoError;
+ }
+ else
+ {
+ /* User callback has asked us to stop with paComplete or other non-zero value */
+ stream->stopProcessing = 1; /* stop once currently queued audio has finished */
+ result = paNoError;
+ }
+
+
+ if( PA_IS_INPUT_STREAM_(stream)
+ && stream->stopProcessing == 0 && stream->abortProcessing == 0
+ && stream->input.framesUsedInCurrentBuffer == stream->input.framesPerBuffer )
+ {
+ if( NoBuffersAreQueued( &stream->input ) )
+ {
+ /** @todo need to handle PaNeverDropInput here where necessary */
+ result = CatchUpInputBuffers( stream );
+ if( result != paNoError )
+ done = 1;
+
+ statusFlags |= paInputOverflow;
+ }
+
+ result = AdvanceToNextInputBuffer( stream );
+ if( result != paNoError )
+ done = 1;
+ }
+
+
+ if( PA_IS_OUTPUT_STREAM_(stream) && !stream->abortProcessing )
+ {
+ if( stream->stopProcessing &&
+ stream->output.framesUsedInCurrentBuffer < stream->output.framesPerBuffer )
+ {
+ /* zero remaining samples in output output buffer and flush */
+
+ stream->output.framesUsedInCurrentBuffer += PaUtil_ZeroOutput( &stream->bufferProcessor,
+ stream->output.framesPerBuffer - stream->output.framesUsedInCurrentBuffer );
+
+ /* we send the entire buffer to the output devices, but we could
+ just send a partial buffer, rather than zeroing the unused
+ samples.
+ */
+ }
+
+ if( stream->output.framesUsedInCurrentBuffer == stream->output.framesPerBuffer )
+ {
+ /* check for underflow before enquing the just-generated buffer,
+ but recover from underflow after enquing it. This ensures
+ that the most recent audio segment is repeated */
+ int outputUnderflow = NoBuffersAreQueued( &stream->output );
+
+ result = AdvanceToNextOutputBuffer( stream );
+ if( result != paNoError )
+ done = 1;
+
+ if( outputUnderflow && !done && !stream->stopProcessing )
+ {
+ /* Recover from underflow in the case where the
+ underflow occurred while processing the buffer
+ we just finished */
+
+ result = CatchUpOutputBuffers( stream );
+ if( result != paNoError )
+ done = 1;
+
+ statusFlags |= paOutputUnderflow;
+ }
+ }
+ }
+
+ if( stream->throttleProcessingThreadOnOverload != 0 )
+ {
+ if( stream->stopProcessing || stream->abortProcessing )
+ {
+ if( stream->processingThreadPriority != stream->highThreadPriority )
+ {
+ SetThreadPriority( stream->processingThread, stream->highThreadPriority );
+ stream->processingThreadPriority = stream->highThreadPriority;
+ }
+ }
+ else if( PaUtil_GetCpuLoad( &stream->cpuLoadMeasurer ) > 1. )
+ {
+ if( stream->processingThreadPriority != stream->throttledThreadPriority )
+ {
+ SetThreadPriority( stream->processingThread, stream->throttledThreadPriority );
+ stream->processingThreadPriority = stream->throttledThreadPriority;
+ }
+
+ /* sleep to give other processes a go */
+ Sleep( stream->throttledSleepMsecs );
+ }
+ else
+ {
+ if( stream->processingThreadPriority != stream->highThreadPriority )
+ {
+ SetThreadPriority( stream->processingThread, stream->highThreadPriority );
+ stream->processingThreadPriority = stream->highThreadPriority;
+ }
+ }
+ }
+ }
+ else
+ {
+ hostBuffersAvailable = 0;
+ }
+ }
+ while( hostBuffersAvailable &&
+ stream->stopProcessing == 0 &&
+ stream->abortProcessing == 0 &&
+ !done );
+ }
+ }
+ while( !done );
+
+ stream->isActive = 0;
+
+ if( stream->streamRepresentation.streamFinishedCallback != 0 )
+ stream->streamRepresentation.streamFinishedCallback( stream->streamRepresentation.userData );
+
+ PaUtil_ResetCpuLoadMeasurer( &stream->cpuLoadMeasurer );
+
+ 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;
+ PaWinMmeStream *stream = (PaWinMmeStream*)s;
+
+ result = CloseHandleWithPaError( stream->abortEvent );
+ if( result != paNoError ) goto error;
+
+ TerminateWaveHeaders( &stream->output, 0 /* not isInput */ );
+ TerminateWaveHeaders( &stream->input, 1 /* isInput */ );
+
+ TerminateWaveHandles( &stream->output, 0 /* not isInput */, 0 /* not currentlyProcessingAnError */ );
+ TerminateWaveHandles( &stream->input, 1 /* isInput */, 0 /* not currentlyProcessingAnError */ );
+
+ PaUtil_TerminateBufferProcessor( &stream->bufferProcessor );
+ PaUtil_TerminateStreamRepresentation( &stream->streamRepresentation );
+ PaUtil_FreeMemory( stream );
+
+error:
+ /** @todo REVIEW: what is the best way to clean up a stream if an error is detected? */
+ return result;
+}
+
+
+static PaError StartStream( PaStream *s )
+{
+ PaError result;
+ PaWinMmeStream *stream = (PaWinMmeStream*)s;
+ MMRESULT mmresult;
+ unsigned int i, j;
+ int callbackResult;
+ unsigned int channel;
+ unsigned long framesProcessed;
+ PaStreamCallbackTimeInfo timeInfo = {0,0,0}; /** @todo implement this for stream priming */
+
+ PaUtil_ResetBufferProcessor( &stream->bufferProcessor );
+
+ if( PA_IS_INPUT_STREAM_(stream) )
+ {
+ for( i=0; i<stream->input.bufferCount; ++i )
+ {
+ for( j=0; j<stream->input.deviceCount; ++j )
+ {
+ stream->input.waveHeaders[j][i].dwFlags &= ~WHDR_DONE;
+ mmresult = waveInAddBuffer( ((HWAVEIN*)stream->input.waveHandles)[j], &stream->input.waveHeaders[j][i], sizeof(WAVEHDR) );
+ if( mmresult != MMSYSERR_NOERROR )
+ {
+ result = paUnanticipatedHostError;
+ PA_MME_SET_LAST_WAVEIN_ERROR( mmresult );
+ goto error;
+ }
+ }
+ }
+ stream->input.currentBufferIndex = 0;
+ stream->input.framesUsedInCurrentBuffer = 0;
+ }
+
+ if( PA_IS_OUTPUT_STREAM_(stream) )
+ {
+ for( i=0; i<stream->output.deviceCount; ++i )
+ {
+ if( (mmresult = waveOutPause( ((HWAVEOUT*)stream->output.waveHandles)[i] )) != MMSYSERR_NOERROR )
+ {
+ result = paUnanticipatedHostError;
+ PA_MME_SET_LAST_WAVEOUT_ERROR( mmresult );
+ goto error;
+ }
+ }
+
+ for( i=0; i<stream->output.bufferCount; ++i )
+ {
+ if( stream->primeStreamUsingCallback )
+ {
+
+ stream->output.framesUsedInCurrentBuffer = 0;
+ do{
+
+ PaUtil_BeginBufferProcessing( &stream->bufferProcessor,
+ &timeInfo,
+ paPrimingOutput | ((stream->input.bufferCount > 0 ) ? paInputUnderflow : 0));
+
+ if( stream->input.bufferCount > 0 )
+ PaUtil_SetNoInput( &stream->bufferProcessor );
+
+ PaUtil_SetOutputFrameCount( &stream->bufferProcessor, 0 /* default to host buffer size */ );
+
+ channel = 0;
+ for( j=0; j<stream->output.deviceCount; ++j )
+ {
+ /* we have stored the number of channels in the buffer in dwUser */
+ int channelCount = (int)stream->output.waveHeaders[j][i].dwUser;
+
+ PaUtil_SetInterleavedOutputChannels( &stream->bufferProcessor, channel,
+ stream->output.waveHeaders[j][i].lpData +
+ stream->output.framesUsedInCurrentBuffer * channelCount *
+ stream->bufferProcessor.bytesPerHostOutputSample,
+ channelCount );
+
+ /* we have stored the number of channels in the buffer in dwUser */
+ channel += channelCount;
+ }
+
+ callbackResult = paContinue;
+ framesProcessed = PaUtil_EndBufferProcessing( &stream->bufferProcessor, &callbackResult );
+ stream->output.framesUsedInCurrentBuffer += framesProcessed;
+
+ if( callbackResult != paContinue )
+ {
+ /** @todo fix this, what do we do if callback result is non-zero during stream
+ priming?
+
+ for complete: play out primed waveHeaders as usual
+ for abort: clean up immediately.
+ */
+ }
+
+ }while( stream->output.framesUsedInCurrentBuffer != stream->output.framesPerBuffer );
+
+ }
+ else
+ {
+ for( j=0; j<stream->output.deviceCount; ++j )
+ {
+ ZeroMemory( stream->output.waveHeaders[j][i].lpData, stream->output.waveHeaders[j][i].dwBufferLength );
+ }
+ }
+
+ /* we queue all channels of a single buffer frame (across all
+ devices, because some multidevice multichannel drivers work
+ better this way */
+ for( j=0; j<stream->output.deviceCount; ++j )
+ {
+ mmresult = waveOutWrite( ((HWAVEOUT*)stream->output.waveHandles)[j], &stream->output.waveHeaders[j][i], sizeof(WAVEHDR) );
+ if( mmresult != MMSYSERR_NOERROR )
+ {
+ result = paUnanticipatedHostError;
+ PA_MME_SET_LAST_WAVEOUT_ERROR( mmresult );
+ goto error;
+ }
+ }
+ }
+ stream->output.currentBufferIndex = 0;
+ stream->output.framesUsedInCurrentBuffer = 0;
+ }
+
+
+ stream->isStopped = 0;
+ stream->isActive = 1;
+ stream->stopProcessing = 0;
+ stream->abortProcessing = 0;
+
+ result = ResetEventWithPaError( stream->input.bufferEvent );
+ if( result != paNoError ) goto error;
+
+ result = ResetEventWithPaError( stream->output.bufferEvent );
+ if( result != paNoError ) goto error;
+
+
+ if( stream->streamRepresentation.streamCallback )
+ {
+ /* callback stream */
+
+ result = ResetEventWithPaError( stream->abortEvent );
+ if( result != paNoError ) goto error;
+
+ /* Create thread that waits for audio buffers to be ready for processing. */
+ stream->processingThread = CREATE_THREAD;
+ if( !stream->processingThread )
+ {
+ result = paUnanticipatedHostError;
+ PA_MME_SET_LAST_SYSTEM_ERROR( GetLastError() );
+ goto error;
+ }
+
+ /** @todo could have mme specific stream parameters to allow the user
+ to set the callback thread priorities */
+ stream->highThreadPriority = THREAD_PRIORITY_TIME_CRITICAL;
+ 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;
+ }
+ else
+ {
+ /* blocking read/write stream */
+
+ }
+
+ if( PA_IS_INPUT_STREAM_(stream) )
+ {
+ for( i=0; i < stream->input.deviceCount; ++i )
+ {
+ mmresult = waveInStart( ((HWAVEIN*)stream->input.waveHandles)[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->output.deviceCount; ++i )
+ {
+ if( (mmresult = waveOutRestart( ((HWAVEOUT*)stream->output.waveHandles)[i] )) != MMSYSERR_NOERROR )
+ {
+ result = paUnanticipatedHostError;
+ PA_MME_SET_LAST_WAVEOUT_ERROR( mmresult );
+ goto error;
+ }
+ }
+ }
+
+ return result;
+
+error:
+ /** @todo 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;
+ signed int hostOutputBufferIndex;
+ unsigned int channel, waitCount, i;
+
+ /** @todo
+ REVIEW: 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.
+ */
+
+ if( stream->processingThread )
+ {
+ /* callback stream */
+
+ /* 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 = (int)(stream->allBuffersDurationMs * 1.5);
+ if( timeout < PA_MME_MIN_TIMEOUT_MSEC_ )
+ timeout = PA_MME_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;
+ }
+ else
+ {
+ /* blocking read / write stream */
+
+ if( PA_IS_OUTPUT_STREAM_(stream) )
+ {
+ if( stream->output.framesUsedInCurrentBuffer > 0 )
+ {
+ /* there are still unqueued frames in the current buffer, so flush them */
+
+ hostOutputBufferIndex = stream->output.currentBufferIndex;
+
+ PaUtil_SetOutputFrameCount( &stream->bufferProcessor,
+ stream->output.framesPerBuffer - stream->output.framesUsedInCurrentBuffer );
+
+ channel = 0;
+ for( i=0; i<stream->output.deviceCount; ++i )
+ {
+ /* we have stored the number of channels in the buffer in dwUser */
+ int channelCount = (int)stream->output.waveHeaders[i][ hostOutputBufferIndex ].dwUser;
+
+ PaUtil_SetInterleavedOutputChannels( &stream->bufferProcessor, channel,
+ stream->output.waveHeaders[i][ hostOutputBufferIndex ].lpData +
+ stream->output.framesUsedInCurrentBuffer * channelCount *
+ stream->bufferProcessor.bytesPerHostOutputSample,
+ channelCount );
+
+ channel += channelCount;
+ }
+
+ PaUtil_ZeroOutput( &stream->bufferProcessor,
+ stream->output.framesPerBuffer - stream->output.framesUsedInCurrentBuffer );
+
+ /* we send the entire buffer to the output devices, but we could
+ just send a partial buffer, rather than zeroing the unused
+ samples.
+ */
+ AdvanceToNextOutputBuffer( stream );
+ }
+
+
+ timeout = (stream->allBuffersDurationMs / stream->output.bufferCount) + 1;
+ if( timeout < PA_MME_MIN_TIMEOUT_MSEC_ )
+ timeout = PA_MME_MIN_TIMEOUT_MSEC_;
+
+ waitCount = 0;
+ while( !NoBuffersAreQueued( &stream->output ) && waitCount <= stream->output.bufferCount )
+ {
+ /* wait for MME to signal that a buffer is available */
+ waitResult = WaitForSingleObject( stream->output.bufferEvent, timeout );
+ if( waitResult == WAIT_FAILED )
+ {
+ break;
+ }
+ else if( waitResult == WAIT_TIMEOUT )
+ {
+ /* keep waiting */
+ }
+
+ ++waitCount;
+ }
+ }
+ }
+
+ if( PA_IS_OUTPUT_STREAM_(stream) )
+ {
+ for( i =0; i < stream->output.deviceCount; ++i )
+ {
+ mmresult = waveOutReset( ((HWAVEOUT*)stream->output.waveHandles)[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->input.deviceCount; ++i )
+ {
+ mmresult = waveInReset( ((HWAVEIN*)stream->input.waveHandles)[i] );
+ if( mmresult != MMSYSERR_NOERROR )
+ {
+ result = paUnanticipatedHostError;
+ PA_MME_SET_LAST_WAVEIN_ERROR( mmresult );
+ }
+ }
+ }
+
+ stream->isStopped = 1;
+ 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;
+
+ /** @todo
+ REVIEW: 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.
+ */
+
+ if( stream->processingThread )
+ {
+ /* callback stream */
+
+ /* Tell processing thread to abort immediately */
+ stream->abortProcessing = 1;
+ SetEvent( stream->abortEvent );
+ }
+
+
+ if( PA_IS_OUTPUT_STREAM_(stream) )
+ {
+ for( i =0; i < stream->output.deviceCount; ++i )
+ {
+ mmresult = waveOutReset( ((HWAVEOUT*)stream->output.waveHandles)[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->input.deviceCount; ++i )
+ {
+ mmresult = waveInReset( ((HWAVEIN*)stream->input.waveHandles)[i] );
+ if( mmresult != MMSYSERR_NOERROR )
+ {
+ PA_MME_SET_LAST_WAVEIN_ERROR( mmresult );
+ return paUnanticipatedHostError;
+ }
+ }
+ }
+
+
+ if( stream->processingThread )
+ {
+ /* callback stream */
+
+ PA_DEBUG(("WinMME AbortStream: waiting for background thread.\n"));
+
+ /* Calculate timeOut longer than longest time it could take to return all buffers. */
+ timeout = (int)(stream->allBuffersDurationMs * 1.5);
+ if( timeout < PA_MME_MIN_TIMEOUT_MSEC_ )
+ timeout = PA_MME_MIN_TIMEOUT_MSEC_;
+
+ 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->isStopped = 1;
+ stream->isActive = 0;
+
+ return result;
+}
+
+
+static PaError IsStreamStopped( PaStream *s )
+{
+ PaWinMmeStream *stream = (PaWinMmeStream*)s;
+
+ return stream->isStopped;
+}
+
+
+static PaError IsStreamActive( PaStream *s )
+{
+ PaWinMmeStream *stream = (PaWinMmeStream*)s;
+
+ return stream->isActive;
+}
+
+
+static PaTime GetStreamTime( PaStream *s )
+{
+ (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 )
+{
+ PaError result = paNoError;
+ PaWinMmeStream *stream = (PaWinMmeStream*)s;
+ void *userBuffer;
+ unsigned long framesRead = 0;
+ unsigned long framesProcessed;
+ signed int hostInputBufferIndex;
+ DWORD waitResult;
+ DWORD timeout = (unsigned long)(stream->allBuffersDurationMs * 0.5);
+ unsigned int channel, i;
+
+ if( PA_IS_INPUT_STREAM_(stream) )
+ {
+ /* make a local copy of the user buffer pointer(s). this is necessary
+ because PaUtil_CopyInput() advances these pointers every time
+ it is called.
+ */
+ if( stream->bufferProcessor.userInputIsInterleaved )
+ {
+ userBuffer = buffer;
+ }
+ else
+ {
+ userBuffer = (void*)alloca( sizeof(void*) * stream->bufferProcessor.inputChannelCount );
+ if( !userBuffer )
+ return paInsufficientMemory;
+ for( i = 0; i<stream->bufferProcessor.inputChannelCount; ++i )
+ ((void**)userBuffer)[i] = ((void**)buffer)[i];
+ }
+
+ do{
+ if( CurrentInputBuffersAreDone( stream ) )
+ {
+ if( NoBuffersAreQueued( &stream->input ) )
+ {
+ /** @todo REVIEW: consider what to do if the input overflows.
+ do we requeue all of the buffers? should we be running
+ a thread to make sure they are always queued?
+ see: http://www.portaudio.com/trac/ticket/117
+ */
+
+ result = paInputOverflowed;
+ }
+
+ hostInputBufferIndex = stream->input.currentBufferIndex;
+
+ PaUtil_SetInputFrameCount( &stream->bufferProcessor,
+ stream->input.framesPerBuffer - stream->input.framesUsedInCurrentBuffer );
+
+ channel = 0;
+ for( i=0; i<stream->input.deviceCount; ++i )
+ {
+ /* we have stored the number of channels in the buffer in dwUser */
+ int channelCount = (int)stream->input.waveHeaders[i][ hostInputBufferIndex ].dwUser;
+
+ PaUtil_SetInterleavedInputChannels( &stream->bufferProcessor, channel,
+ stream->input.waveHeaders[i][ hostInputBufferIndex ].lpData +
+ stream->input.framesUsedInCurrentBuffer * channelCount *
+ stream->bufferProcessor.bytesPerHostInputSample,
+ channelCount );
+
+ channel += channelCount;
+ }
+
+ framesProcessed = PaUtil_CopyInput( &stream->bufferProcessor, &userBuffer, frames - framesRead );
+
+ stream->input.framesUsedInCurrentBuffer += framesProcessed;
+ if( stream->input.framesUsedInCurrentBuffer == stream->input.framesPerBuffer )
+ {
+ result = AdvanceToNextInputBuffer( stream );
+ if( result != paNoError )
+ break;
+ }
+
+ framesRead += framesProcessed;
+
+ }else{
+ /* wait for MME to signal that a buffer is available */
+ waitResult = WaitForSingleObject( stream->input.bufferEvent, timeout );
+ if( waitResult == WAIT_FAILED )
+ {
+ result = paUnanticipatedHostError;
+ break;
+ }
+ else if( waitResult == WAIT_TIMEOUT )
+ {
+ /* if a timeout is encountered, continue,
+ perhaps we should give up eventually
+ */
+ }
+ }
+ }while( framesRead < frames );
+ }
+ else
+ {
+ result = paCanNotReadFromAnOutputOnlyStream;
+ }
+
+ return result;
+}
+
+
+static PaError WriteStream( PaStream* s,
+ const void *buffer,
+ unsigned long frames )
+{
+ PaError result = paNoError;
+ PaWinMmeStream *stream = (PaWinMmeStream*)s;
+ const void *userBuffer;
+ unsigned long framesWritten = 0;
+ unsigned long framesProcessed;
+ signed int hostOutputBufferIndex;
+ DWORD waitResult;
+ DWORD timeout = (unsigned long)(stream->allBuffersDurationMs * 0.5);
+ unsigned int channel, i;
+
+
+ if( PA_IS_OUTPUT_STREAM_(stream) )
+ {
+ /* make a local copy of the user buffer pointer(s). this is necessary
+ because PaUtil_CopyOutput() advances these pointers every time
+ it is called.
+ */
+ if( stream->bufferProcessor.userOutputIsInterleaved )
+ {
+ userBuffer = buffer;
+ }
+ else
+ {
+ userBuffer = (const void*)alloca( sizeof(void*) * stream->bufferProcessor.outputChannelCount );
+ if( !userBuffer )
+ return paInsufficientMemory;
+ for( i = 0; i<stream->bufferProcessor.outputChannelCount; ++i )
+ ((const void**)userBuffer)[i] = ((const void**)buffer)[i];
+ }
+
+ do{
+ if( CurrentOutputBuffersAreDone( stream ) )
+ {
+ if( NoBuffersAreQueued( &stream->output ) )
+ {
+ /** @todo REVIEW: consider what to do if the output
+ underflows. do we requeue all the existing buffers with
+ zeros? should we run a separate thread to keep the buffers
+ enqueued at all times?
+ see: http://www.portaudio.com/trac/ticket/117
+ */
+
+ result = paOutputUnderflowed;
+ }
+
+ hostOutputBufferIndex = stream->output.currentBufferIndex;
+
+ PaUtil_SetOutputFrameCount( &stream->bufferProcessor,
+ stream->output.framesPerBuffer - stream->output.framesUsedInCurrentBuffer );
+
+ channel = 0;
+ for( i=0; i<stream->output.deviceCount; ++i )
+ {
+ /* we have stored the number of channels in the buffer in dwUser */
+ int channelCount = (int)stream->output.waveHeaders[i][ hostOutputBufferIndex ].dwUser;
+
+ PaUtil_SetInterleavedOutputChannels( &stream->bufferProcessor, channel,
+ stream->output.waveHeaders[i][ hostOutputBufferIndex ].lpData +
+ stream->output.framesUsedInCurrentBuffer * channelCount *
+ stream->bufferProcessor.bytesPerHostOutputSample,
+ channelCount );
+
+ channel += channelCount;
+ }
+
+ framesProcessed = PaUtil_CopyOutput( &stream->bufferProcessor, &userBuffer, frames - framesWritten );
+
+ stream->output.framesUsedInCurrentBuffer += framesProcessed;
+ if( stream->output.framesUsedInCurrentBuffer == stream->output.framesPerBuffer )
+ {
+ result = AdvanceToNextOutputBuffer( stream );
+ if( result != paNoError )
+ break;
+ }
+
+ framesWritten += framesProcessed;
+ }
+ else
+ {
+ /* wait for MME to signal that a buffer is available */
+ waitResult = WaitForSingleObject( stream->output.bufferEvent, timeout );
+ if( waitResult == WAIT_FAILED )
+ {
+ result = paUnanticipatedHostError;
+ break;
+ }
+ else if( waitResult == WAIT_TIMEOUT )
+ {
+ /* if a timeout is encountered, continue,
+ perhaps we should give up eventually
+ */
+ }
+ }
+ }while( framesWritten < frames );
+ }
+ else
+ {
+ result = paCanNotWriteToAnInputOnlyStream;
+ }
+
+ return result;
+}
+
+
+static signed long GetStreamReadAvailable( PaStream* s )
+{
+ PaWinMmeStream *stream = (PaWinMmeStream*)s;
+
+ if( PA_IS_INPUT_STREAM_(stream) )
+ return GetAvailableFrames( &stream->input );
+ else
+ return paCanNotReadFromAnOutputOnlyStream;
+}
+
+
+static signed long GetStreamWriteAvailable( PaStream* s )
+{
+ PaWinMmeStream *stream = (PaWinMmeStream*)s;
+
+ if( PA_IS_OUTPUT_STREAM_(stream) )
+ return GetAvailableFrames( &stream->output );
+ else
+ return paCanNotWriteToAnInputOnlyStream;
+}
+
+
+/* NOTE: the following functions are MME-stream specific, and are called directly
+ by client code. We need to check for many more error conditions here because
+ we don't have the benefit of pa_front.c's parameter checking.
+*/
+
+static PaError GetWinMMEStreamPointer( PaWinMmeStream **stream, PaStream *s )
+{
+ PaError result;
+ PaUtilHostApiRepresentation *hostApi;
+ PaWinMmeHostApiRepresentation *winMmeHostApi;
+
+ result = PaUtil_ValidateStreamPointer( s );
+ if( result != paNoError )
+ return result;
+
+ result = PaUtil_GetHostApiRepresentation( &hostApi, paMME );
+ if( result != paNoError )
+ return result;
+
+ winMmeHostApi = (PaWinMmeHostApiRepresentation*)hostApi;
+
+ /* note, the following would be easier if there was a generic way of testing
+ that a stream belongs to a specific host API */
+
+ if( PA_STREAM_REP( s )->streamInterface == &winMmeHostApi->callbackStreamInterface
+ || PA_STREAM_REP( s )->streamInterface == &winMmeHostApi->blockingStreamInterface )
+ {
+ /* s is a WinMME stream */
+ *stream = (PaWinMmeStream *)s;
+ return paNoError;
+ }
+ else
+ {
+ return paIncompatibleStreamHostApi;
+ }
+}
+
+
+int PaWinMME_GetStreamInputHandleCount( PaStream* s )
+{
+ PaWinMmeStream *stream;
+ PaError result = GetWinMMEStreamPointer( &stream, s );
+
+ if( result == paNoError )
+ return (PA_IS_INPUT_STREAM_(stream)) ? stream->input.deviceCount : 0;
+ else
+ return result;
+}
+
+
+HWAVEIN PaWinMME_GetStreamInputHandle( PaStream* s, int handleIndex )
+{
+ PaWinMmeStream *stream;
+ PaError result = GetWinMMEStreamPointer( &stream, s );
+
+ if( result == paNoError
+ && PA_IS_INPUT_STREAM_(stream)
+ && handleIndex >= 0
+ && (unsigned int)handleIndex < stream->input.deviceCount )
+ return ((HWAVEIN*)stream->input.waveHandles)[handleIndex];
+ else
+ return 0;
+}
+
+
+int PaWinMME_GetStreamOutputHandleCount( PaStream* s)
+{
+ PaWinMmeStream *stream;
+ PaError result = GetWinMMEStreamPointer( &stream, s );
+
+ if( result == paNoError )
+ return (PA_IS_OUTPUT_STREAM_(stream)) ? stream->output.deviceCount : 0;
+ else
+ return result;
+}
+
+
+HWAVEOUT PaWinMME_GetStreamOutputHandle( PaStream* s, int handleIndex )
+{
+ PaWinMmeStream *stream;
+ PaError result = GetWinMMEStreamPointer( &stream, s );
+
+ if( result == paNoError
+ && PA_IS_OUTPUT_STREAM_(stream)
+ && handleIndex >= 0
+ && (unsigned int)handleIndex < stream->output.deviceCount )
+ return ((HWAVEOUT*)stream->output.waveHandles)[handleIndex];
+ else
+ return 0;
+}