| /* |
| * $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 maintanance 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 */ |
| |
| #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 |
| |
| /* 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 havn't observed significant stability degredation |
| 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 TCHAR string to explicit char string using UTF8 encoding */ |
| static char *CopyTCharStringToUtf8CString(char *destination, size_t destLengthBytes, const TCHAR *source) |
| { |
| #if !defined(_UNICODE) && !defined(UNICODE) |
| return strcpy(destination, source); |
| #else |
| /* 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; |
| #endif |
| } |
| |
| /* returns required length (in bytes) of destination buffer when |
| converting TCHAR string to UTF8 bytes, not including the terminating null. */ |
| static size_t TCharStringLen(const TCHAR *str) |
| { |
| #if !defined(_UNICODE) && !defined(UNICODE) |
| return strlen(str); |
| #else |
| return WideCharToMultiByte(CP_UTF8, 0, str, -1, NULL, 0, NULL, NULL); |
| #endif |
| } |
| |
| /********************************************************************/ |
| |
| 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 ScanDeviceInfos( struct PaUtilHostApiRepresentation *hostApi, PaHostApiIndex index, void **newDeviceInfos, int *newDeviceCount ); |
| static PaError CommitDeviceInfos( struct PaUtilHostApiRepresentation *hostApi, PaHostApiIndex index, void *deviceInfos, int deviceCount ); |
| static PaError DisposeDeviceInfos( struct PaUtilHostApiRepresentation *hostApi, void *deviceInfos, int deviceCount ); |
| 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 */ |
| |
| #ifdef UNICODE |
| |
| #define PA_MME_SET_LAST_WAVEIN_ERROR( mmresult ) \ |
| { \ |
| wchar_t mmeErrorTextWide[ MAXERRORLENGTH ]; \ |
| char mmeErrorText[ MAXERRORLENGTH ]; \ |
| waveInGetErrorText( mmresult, mmeErrorTextWide, MAXERRORLENGTH ); \ |
| WideCharToMultiByte( CP_ACP, WC_COMPOSITECHECK | WC_DEFAULTCHAR,\ |
| mmeErrorTextWide, -1, mmeErrorText, MAXERRORLENGTH, NULL, NULL ); \ |
| PaUtil_SetLastHostErrorInfo( paMME, mmresult, mmeErrorText ); \ |
| } |
| |
| #define PA_MME_SET_LAST_WAVEOUT_ERROR( mmresult ) \ |
| { \ |
| wchar_t mmeErrorTextWide[ MAXERRORLENGTH ]; \ |
| char mmeErrorText[ MAXERRORLENGTH ]; \ |
| waveOutGetErrorText( mmresult, mmeErrorTextWide, MAXERRORLENGTH ); \ |
| WideCharToMultiByte( CP_ACP, WC_COMPOSITECHECK | WC_DEFAULTCHAR,\ |
| mmeErrorTextWide, -1, mmeErrorText, MAXERRORLENGTH, NULL, NULL ); \ |
| PaUtil_SetLastHostErrorInfo( paMME, mmresult, mmeErrorText ); \ |
| } |
| |
| #else /* !UNICODE */ |
| |
| #define PA_MME_SET_LAST_WAVEIN_ERROR( mmresult ) \ |
| { \ |
| char mmeErrorText[ MAXERRORLENGTH ]; \ |
| waveInGetErrorText( mmresult, mmeErrorText, MAXERRORLENGTH ); \ |
| PaUtil_SetLastHostErrorInfo( paMME, mmresult, mmeErrorText ); \ |
| } |
| |
| #define PA_MME_SET_LAST_WAVEOUT_ERROR( mmresult ) \ |
| { \ |
| char mmeErrorText[ MAXERRORLENGTH ]; \ |
| waveOutGetErrorText( mmresult, mmeErrorText, MAXERRORLENGTH ); \ |
| PaUtil_SetLastHostErrorInfo( paMME, mmresult, mmeErrorText ); \ |
| } |
| |
| #endif /* UNICODE */ |
| |
| |
| static void PaMme_SetLastSystemError( DWORD errorCode ) |
| { |
| 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; |
| } |
| PaWinMmeHostApiRepresentation; |
| |
| |
| typedef struct |
| { |
| PaDeviceInfo inheritedDeviceInfo; |
| UINT winWmmeDeviceId; /*<< Windows MME device id for this device */ |
| LPWSTR deviceInterfaceName; /*<< A null-terminated Unicode string containing the device-interface name returned by DRV_QUERYDEVICEINTERFACE. |
| see: https://msdn.microsoft.com/en-us/library/windows/hardware/ff536363(v=vs.85).aspx |
| We use this for connectionId impl because it works on Windows XP (DRV_QUERYFUNCTIONINSTANCEID is supported only on Vista and later) */ |
| 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; |
| |
| typedef struct |
| { /* used for transferring device infos during scanning / rescanning */ |
| PaDeviceInfo **deviceInfos; |
| PaDeviceIndex defaultInputDevice; |
| PaDeviceIndex defaultOutputDevice; |
| int inputDeviceCount, outputDeviceCount; |
| } |
| PaWinMmeScanDeviceInfosResults; |
| |
| /************************************************************************* |
| * 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 ) |
| { |
| PaWinMmeDeviceInfo *winWmmeDeviceInfo; |
| |
| assert( device >= 0 && device < hostApi->inputDeviceCount + hostApi->outputDeviceCount ); |
| winWmmeDeviceInfo = (PaWinMmeDeviceInfo *)hostApi->inheritedHostApiRep.deviceInfos[device]; |
| return winWmmeDeviceInfo->winWmmeDeviceId; |
| } |
| |
| |
| 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; |
| } |
| } |
| } |
| |
| |
| /* On error, *resultInterfaceName will be NULL */ |
| static PaError QueryInputDeviceInterfaceName( PaUtilAllocationGroup *allocations, UINT waveInDeviceId, LPWSTR *resultInterfaceName ) |
| { |
| LPWSTR resultString = NULL; |
| DWORD stringSize = 0; |
| |
| *resultInterfaceName = NULL; |
| |
| if( waveInMessage( (HWAVEIN)waveInDeviceId, DRV_QUERYDEVICEINTERFACESIZE, |
| (DWORD_PTR)&stringSize, 0 ) != MMSYSERR_NOERROR ) |
| return paUnanticipatedHostError; |
| |
| if( stringSize == 0 ) |
| return paNoError; |
| |
| resultString = (LPWSTR)PaUtil_GroupAllocateMemory( allocations, stringSize ); |
| if( !resultString ) |
| return paInsufficientMemory; |
| |
| if( waveInMessage( (HWAVEIN)waveInDeviceId, DRV_QUERYDEVICEINTERFACE, |
| (DWORD_PTR)resultString, stringSize ) == MMSYSERR_NOERROR ) |
| { |
| *resultInterfaceName = resultString; |
| return paNoError; |
| } |
| else |
| { |
| PaUtil_GroupFreeMemory( allocations, resultString ); |
| return paUnanticipatedHostError; |
| } |
| } |
| |
| |
| static PaError InitializeInputDeviceInfo( PaWinMmeHostApiRepresentation *winMmeHostApi, |
| PaWinMmeDeviceInfo *winMmeDeviceInfo, UINT winMmeInputDeviceId, int *success ) |
| { |
| PaError result = paNoError; |
| char *deviceName = NULL; /* non-const ptr */ |
| LPWSTR deviceInterfaceName = NULL; |
| MMRESULT mmresult; |
| WAVEINCAPS wic; |
| PaDeviceInfo *deviceInfo = &winMmeDeviceInfo->inheritedDeviceInfo; |
| size_t len; |
| |
| *success = 0; |
| |
| mmresult = waveInGetDevCaps( winMmeInputDeviceId, &wic, sizeof( WAVEINCAPS ) ); |
| if( mmresult == MMSYSERR_NOMEM ) |
| { |
| result = paInsufficientMemory; |
| goto error; |
| } |
| else if( mmresult != MMSYSERR_NOERROR ) |
| { |
| /* instead of returning paUnanticipatedHostError we return |
| paNoError, but leave success set as 0. This allows |
| Pa_Initialize to just ignore this device, without failing |
| the entire initialisation process. |
| */ |
| return paNoError; |
| } |
| |
| /* 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 = TCharStringLen( 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; |
| } |
| CopyTCharStringToUtf8CString( deviceName, len, wic.szPname ); |
| strcat( deviceName, constInputMapperSuffix_ ); |
| } |
| else |
| { |
| len = TCharStringLen( wic.szPname ) + 1; |
| deviceName = (char*)PaUtil_GroupAllocateMemory( |
| winMmeHostApi->allocations, |
| (long)len ); |
| if( !deviceName ) |
| { |
| result = paInsufficientMemory; |
| goto error; |
| } |
| CopyTCharStringToUtf8CString( deviceName, len, wic.szPname ); |
| } |
| deviceInfo->name = deviceName; |
| |
| QueryInputDeviceInterfaceName( winMmeHostApi->allocations, |
| winMmeInputDeviceId, &deviceInterfaceName ); |
| winMmeDeviceInfo->deviceInterfaceName = deviceInterfaceName; |
| |
| 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 |
| if( deviceInterfaceName != NULL ) |
| { |
| /* apparently DRV_QUERYDEVICEINTERFACE returns a unicode interface path |
| that we can use with WDM/KS, although this seems to be undocumented */ |
| int count = PaWin_WDMKS_QueryFilterMaximumChannelCount( deviceInterfaceName, /* isInput= */ 1 ); |
| if( count > 0 ) |
| { |
| deviceInfo->maxInputChannels = count; |
| winMmeDeviceInfo->deviceInputChannelCountIsKnown = 1; |
| } |
| } |
| #endif /* PAWIN_USE_WDMKS_DEVICE_INFO */ |
| |
| winMmeDeviceInfo->dwFormats = wic.dwFormats; |
| |
| DetectDefaultSampleRate( winMmeDeviceInfo, winMmeInputDeviceId, |
| QueryInputWaveFormatEx, deviceInfo->maxInputChannels ); |
| |
| winMmeDeviceInfo->winWmmeDeviceId = winMmeInputDeviceId; |
| |
| *success = 1; |
| return result; |
| |
| error: |
| PaUtil_GroupFreeMemory( winMmeHostApi->allocations, deviceName ); |
| PaUtil_GroupFreeMemory( winMmeHostApi->allocations, deviceInterfaceName ); |
| return result; |
| } |
| |
| |
| /* resultInterfaceName will be NULL if there's an error */ |
| static PaError QueryOutputDeviceInterfaceName( PaUtilAllocationGroup *allocations, UINT waveOutDeviceId, LPWSTR *resultInterfaceName ) |
| { |
| LPWSTR resultString = NULL; |
| DWORD stringSize = 0; |
| |
| *resultInterfaceName = NULL; |
| |
| if( waveOutMessage( (HWAVEOUT)waveOutDeviceId, DRV_QUERYDEVICEINTERFACESIZE, |
| (DWORD_PTR)&stringSize, 0 ) != MMSYSERR_NOERROR ) |
| return paInternalError; |
| |
| if( stringSize == 0 ) |
| return paNoError; |
| |
| resultString = (LPWSTR)PaUtil_GroupAllocateMemory( allocations, stringSize ); |
| if( !resultString ) |
| return paInsufficientMemory; |
| |
| if( waveOutMessage( (HWAVEOUT)waveOutDeviceId, DRV_QUERYDEVICEINTERFACE, |
| (DWORD_PTR)resultString, stringSize ) == MMSYSERR_NOERROR ) |
| { |
| *resultInterfaceName = resultString; |
| return paNoError; |
| } |
| else |
| { |
| PaUtil_GroupFreeMemory( allocations, resultString ); |
| return paInternalError; |
| } |
| } |
| |
| |
| static PaError InitializeOutputDeviceInfo( PaWinMmeHostApiRepresentation *winMmeHostApi, |
| PaWinMmeDeviceInfo *winMmeDeviceInfo, UINT winMmeOutputDeviceId, int *success ) |
| { |
| PaError result = paNoError; |
| char *deviceName = NULL; /* non-const ptr */ |
| LPWSTR deviceInterfaceName = NULL; |
| MMRESULT mmresult; |
| WAVEOUTCAPS woc; |
| PaDeviceInfo *deviceInfo = &winMmeDeviceInfo->inheritedDeviceInfo; |
| size_t len; |
| |
| *success = 0; |
| |
| mmresult = waveOutGetDevCaps( winMmeOutputDeviceId, &woc, sizeof( WAVEOUTCAPS ) ); |
| if( mmresult == MMSYSERR_NOMEM ) |
| { |
| result = paInsufficientMemory; |
| goto error; |
| } |
| else if( mmresult != MMSYSERR_NOERROR ) |
| { |
| /* instead of returning paUnanticipatedHostError we return |
| paNoError, but leave success set as 0. This allows |
| Pa_Initialize to just ignore this device, without failing |
| the entire initialisation process. |
| */ |
| return paNoError; |
| } |
| |
| /* 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 = TCharStringLen( woc.szPname ) + 1 + sizeof(constOutputMapperSuffix_); |
| deviceName = (char*)PaUtil_GroupAllocateMemory( |
| winMmeHostApi->allocations, |
| (long)len ); |
| if( !deviceName ) |
| { |
| result = paInsufficientMemory; |
| goto error; |
| } |
| CopyTCharStringToUtf8CString( deviceName, len, woc.szPname ); |
| strcat( deviceName, constOutputMapperSuffix_ ); |
| } |
| else |
| { |
| len = TCharStringLen( woc.szPname ) + 1; |
| deviceName = (char*)PaUtil_GroupAllocateMemory( |
| winMmeHostApi->allocations, |
| (long)len ); |
| if( !deviceName ) |
| { |
| result = paInsufficientMemory; |
| goto error; |
| } |
| CopyTCharStringToUtf8CString( deviceName, len, woc.szPname ); |
| } |
| deviceInfo->name = deviceName; |
| |
| QueryOutputDeviceInterfaceName( winMmeHostApi->allocations, |
| winMmeOutputDeviceId, &deviceInterfaceName ); |
| winMmeDeviceInfo->deviceInterfaceName = deviceInterfaceName; |
| |
| 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 |
| if( deviceInterfaceName != NULL ) |
| { |
| /* apparently DRV_QUERYDEVICEINTERFACE returns a unicode interface path |
| that we can use with WDM/KS, although this seems to be undocumented */ |
| int count = PaWin_WDMKS_QueryFilterMaximumChannelCount( deviceInterfaceName, /* isInput= */ 0 ); |
| if( count > 0 ) |
| { |
| deviceInfo->maxOutputChannels = count; |
| winMmeDeviceInfo->deviceOutputChannelCountIsKnown = 1; |
| } |
| } |
| #endif /* PAWIN_USE_WDMKS_DEVICE_INFO */ |
| |
| winMmeDeviceInfo->dwFormats = woc.dwFormats; |
| |
| DetectDefaultSampleRate( winMmeDeviceInfo, winMmeOutputDeviceId, |
| QueryOutputWaveFormatEx, deviceInfo->maxOutputChannels ); |
| |
| winMmeDeviceInfo->winWmmeDeviceId = winMmeOutputDeviceId; |
| |
| *success = 1; |
| return result; |
| |
| error: |
| PaUtil_GroupFreeMemory( winMmeHostApi->allocations, deviceName ); |
| PaUtil_GroupFreeMemory( winMmeHostApi->allocations, deviceInterfaceName ); |
| 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 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; |
| PaWinMmeHostApiRepresentation *winMmeHostApi; |
| void *scanResults = 0; |
| int deviceCount; |
| |
| 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"; |
| |
| /* Initialize device counts and default devices under the assumption that |
| there are no devices. These values are incremented if and when |
| devices are successfully initialized. |
| |
| Note: the following are all updated by CommitDeviceInfos(). |
| */ |
| (*hostApi)->info.deviceCount = 0; |
| (*hostApi)->info.defaultInputDevice = paNoDevice; |
| (*hostApi)->info.defaultOutputDevice = paNoDevice; |
| winMmeHostApi->inputDeviceCount = 0; |
| winMmeHostApi->outputDeviceCount = 0; |
| |
| result = ScanDeviceInfos( &winMmeHostApi->inheritedHostApiRep, hostApiIndex, &scanResults, &deviceCount ); |
| if( result != paNoError ) |
| goto error; |
| |
| /* Ignore the result of CommitDeviceInfos(), it is a non-failing operation */ |
| CommitDeviceInfos( &winMmeHostApi->inheritedHostApiRep, hostApiIndex, scanResults, deviceCount ); |
| |
| InitializeDefaultDeviceIdsFromEnv( winMmeHostApi ); /* uses the active device list. must be called after CommitDeviceInfos */ |
| |
| (*hostApi)->Terminate = Terminate; |
| (*hostApi)->OpenStream = OpenStream; |
| (*hostApi)->IsFormatSupported = IsFormatSupported; |
| (*hostApi)->ScanDeviceInfos = ScanDeviceInfos; |
| (*hostApi)->CommitDeviceInfos = CommitDeviceInfos; |
| (*hostApi)->DisposeDeviceInfos = DisposeDeviceInfos; |
| |
| 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 const PaDeviceInfo* FindDeviceByConnectionId( |
| const PaDeviceInfo **deviceInfos, int deviceCount, |
| PaDeviceConnectionId connectionId ) |
| { |
| int i; |
| |
| #ifndef NDEBUG |
| if( deviceCount ) |
| { |
| assert( deviceInfos != NULL ); |
| assert( deviceInfos[0] != NULL ); |
| } |
| #endif |
| |
| for( i = 0; i < deviceCount; ++i ) |
| { |
| const PaDeviceInfo *deviceInfo = deviceInfos[i]; |
| if( deviceInfo->connectionId == connectionId ) |
| return deviceInfo; |
| } |
| |
| return NULL; |
| } |
| |
| /* used for looking up existing devices to reuse their connectionId */ |
| static const PaDeviceInfo* FindDeviceInfoByInterfaceName( |
| const PaDeviceInfo **deviceInfos, int deviceCount, |
| LPCWSTR deviceInterfaceName, int isInput ) |
| { |
| int i; |
| |
| #ifndef NDEBUG |
| if( deviceCount ) |
| { |
| assert( deviceInfos != NULL ); |
| assert( deviceInfos[0] != NULL ); |
| } |
| #endif |
| |
| for( i = 0; i < deviceCount; ++i ) |
| { |
| const PaDeviceInfo *deviceInfo = deviceInfos[i]; |
| const PaWinMmeDeviceInfo *wmmeDeviceInfo = (const PaWinMmeDeviceInfo*)deviceInfo; |
| if( ((isInput && deviceInfo->maxInputChannels > 0) |
| || (!isInput && deviceInfo->maxOutputChannels > 0)) |
| && |
| /* require both interface names to be non-NULL, because they can be NULL due to errors. */ |
| (deviceInterfaceName != NULL && wmmeDeviceInfo->deviceInterfaceName != NULL |
| && (wcscmp(deviceInterfaceName, wmmeDeviceInfo->deviceInterfaceName) == 0)) ) |
| { |
| return deviceInfo; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| /* correlate or allocate device connection id */ |
| static PaDeviceConnectionId AssignDeviceConnectionId( |
| const PaDeviceInfo **activeDeviceInfos, int activeDeviceCount, |
| const PaDeviceInfo **newDeviceInfos, int newDeviceCount, /* this is the new list being built. it contains infos added so far. */ |
| LPCWSTR deviceInterfaceName, int isInput ) |
| { |
| const PaDeviceInfo *currentDeviceInfo; |
| |
| currentDeviceInfo = FindDeviceInfoByInterfaceName( |
| activeDeviceInfos, activeDeviceCount, /* search active device list for deviceInterfaceName */ |
| deviceInterfaceName, isInput ); |
| |
| if( currentDeviceInfo && FindDeviceByConnectionId( |
| newDeviceInfos, newDeviceCount, /* search new device list connectionId */ |
| currentDeviceInfo->connectionId ) == NULL ) |
| { |
| /* deviceInterfaceName matches, and its connectionId hasn't already been reused */ |
| return currentDeviceInfo->connectionId; |
| } |
| |
| return PaUtil_MakeDeviceConnectionId(); |
| } |
| |
| |
| static void FreeDeviceInfos( PaUtilAllocationGroup *allocations, |
| PaDeviceInfo **deviceInfos, int deviceCount ) |
| { |
| if( deviceInfos ) |
| { |
| if( deviceInfos[0] ) |
| { |
| int i; |
| for( i = 0; i < deviceCount; ++i ) |
| { |
| PaWinMmeDeviceInfo *wmmeDeviceInfo = (PaWinMmeDeviceInfo*)deviceInfos[ i ]; |
| PaUtil_GroupFreeMemory( allocations, wmmeDeviceInfo->deviceInterfaceName ); |
| } |
| |
| /* all device info structs are allocated in a block so we can destroy them like this */ |
| PaUtil_GroupFreeMemory( allocations, deviceInfos[0] ); |
| } |
| |
| PaUtil_GroupFreeMemory( allocations, deviceInfos ); |
| } |
| } |
| |
| static PaError ScanDeviceInfos( struct PaUtilHostApiRepresentation *hostApi, |
| PaHostApiIndex hostApiIndex, |
| void **scanResults_OUT, int *newDeviceCount_OUT ) |
| { |
| PaWinMmeHostApiRepresentation *winMmeHostApi = (PaWinMmeHostApiRepresentation*)hostApi; |
| PaError result = paNoError; |
| int i; |
| int waveInDeviceCount, waveOutDeviceCount; |
| int maximumPossibleDeviceCount; |
| PaWinMmeScanDeviceInfosResults *winMmeScanResults; |
| PaWinMmeDeviceInfo *deviceInfoArray; |
| int initializedInfosCount; |
| |
| /* Check preconditions */ |
| if( scanResults_OUT == NULL || newDeviceCount_OUT == NULL ) |
| return paInternalError; |
| |
| /* initialize the OUT params */ |
| *scanResults_OUT = NULL; |
| *newDeviceCount_OUT = 0; |
| |
| winMmeScanResults = NULL; |
| initializedInfosCount = 0; /* incremented after successfully appending to deviceInfoArray */ |
| |
| maximumPossibleDeviceCount = 0; |
| |
| #ifdef PAWIN_WMME_NO_WAVE_MAPPER |
| #define PAWIN_WMME_POSSIBLE_WAVE_MAPPER_COUNT_ 0 |
| #else |
| #define PAWIN_WMME_POSSIBLE_WAVE_MAPPER_COUNT_ 1 |
| #endif |
| |
| waveInDeviceCount = waveInGetNumDevs(); |
| if( waveInDeviceCount > 0 ) |
| maximumPossibleDeviceCount += waveInDeviceCount + PAWIN_WMME_POSSIBLE_WAVE_MAPPER_COUNT_; |
| |
| waveOutDeviceCount = waveOutGetNumDevs(); |
| if( waveOutDeviceCount > 0 ) |
| maximumPossibleDeviceCount += waveOutDeviceCount + PAWIN_WMME_POSSIBLE_WAVE_MAPPER_COUNT_; |
| |
| if( maximumPossibleDeviceCount > 0 ) |
| { |
| PaTime defaultLowLatency, defaultHighLatency; |
| |
| /* allocate scan results -- it will contain all info needed to update the device list */ |
| winMmeScanResults = (PaWinMmeScanDeviceInfosResults *) PaUtil_GroupAllocateMemory( |
| winMmeHostApi->allocations, sizeof(PaWinMmeScanDeviceInfosResults) ); |
| if( !winMmeScanResults ) |
| { |
| result = paInsufficientMemory; |
| goto error; |
| } |
| |
| /* allocate array for pointers to PaDeviceInfo structs */ |
| winMmeScanResults->deviceInfos = (PaDeviceInfo**)PaUtil_GroupAllocateMemory( |
| winMmeHostApi->allocations, sizeof(PaDeviceInfo*) * maximumPossibleDeviceCount ); |
| if( !winMmeScanResults->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; |
| } |
| |
| winMmeScanResults->defaultInputDevice = paNoDevice; |
| winMmeScanResults->defaultOutputDevice = paNoDevice; |
| winMmeScanResults->inputDeviceCount = 0; |
| winMmeScanResults->outputDeviceCount = 0; |
| |
| GetDefaultLatencies( &defaultLowLatency, &defaultHighLatency ); |
| |
| /* populate device infos set default input and output devices */ |
| |
| /* Default device selection logic: |
| Query for the system preferred input and output devices using |
| DRVM_MAPPER_PREFERRED_GET; on error default to WAVE_MAPPER. Preferred |
| devices are stored in wave{In, Out}PreferredDevice. |
| |
| When enumerating devices, set PA default devices to the MME devices |
| with ids matching wave{In, Out}PreferredDevice. If such devices are not |
| encountered during enumeration, the first device enumerated for each of |
| {input, output} will be selected as the PA default device. |
| |
| Note that if PAWIN_WMME_NO_WAVE_MAPPER is defined, the WAVE_MAPPER |
| devices will never be enumerated, and will never be made a PA |
| default device, irrespective of the wave{In, Out}PreferredDevice values. |
| */ |
| |
| if( waveInDeviceCount > 0 ){ |
| /* get preferred input device -- see comment about default devices above */ |
| DWORD preferredDeviceStatusFlags = 0; |
| DWORD waveInPreferredDevice = WAVE_MAPPER; |
| if( waveInMessage( (HWAVEIN)WAVE_MAPPER, DRVM_MAPPER_PREFERRED_GET, |
| (DWORD_PTR)&waveInPreferredDevice, (DWORD_PTR)&preferredDeviceStatusFlags ) != MMSYSERR_NOERROR ) |
| waveInPreferredDevice = WAVE_MAPPER; |
| |
| /* append input devices to deviceInfoArray */ |
| #ifdef PAWIN_WMME_NO_WAVE_MAPPER |
| for( i = 0; i < waveInDeviceCount; ++i ){ |
| UINT winMmeDeviceId = (UINT)i; |
| #else |
| /* -1 enumerates the WAVE_MAPPER */ |
| for( i = -1; i < waveInDeviceCount; ++i ){ |
| UINT winMmeDeviceId = (UINT)((i==-1) ? WAVE_MAPPER : i); |
| #endif |
| int deviceInfoInitializationSucceeded = 0; |
| int deviceIndex = initializedInfosCount; /* host-api local index. append at end of deviceInfoArray */ |
| PaWinMmeDeviceInfo *wmmeDeviceInfo = &deviceInfoArray[ deviceIndex ]; |
| PaDeviceInfo *deviceInfo = &wmmeDeviceInfo->inheritedDeviceInfo; |
| deviceInfo->structVersion = 3; |
| 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 ) |
| continue; /* ignore error results here and just skip the device. */ |
| /* N.B. Earlier WMME versions failed scanning with an error here. */ |
| |
| if( deviceInfoInitializationSucceeded ){ |
| /* NOTE: wmmeDeviceInfo->deviceInterfaceName is now allocated and would need to be freed on error. */ |
| |
| if( winMmeScanResults->defaultInputDevice == paNoDevice ) |
| { |
| /* if there is currently no default device, use the first one available */ |
| winMmeScanResults->defaultInputDevice = deviceIndex; |
| } |
| else if( winMmeDeviceId == waveInPreferredDevice ) |
| { |
| /* set the default device to the system preferred device */ |
| winMmeScanResults->defaultInputDevice = deviceIndex; |
| } |
| |
| deviceInfo->connectionId = AssignDeviceConnectionId( |
| hostApi->deviceInfos, hostApi->info.deviceCount, |
| winMmeScanResults->deviceInfos, initializedInfosCount, |
| wmmeDeviceInfo->deviceInterfaceName, /* isInput= */ 1 ); |
| |
| winMmeScanResults->deviceInfos[ deviceIndex ] = deviceInfo; |
| |
| winMmeScanResults->inputDeviceCount++; |
| initializedInfosCount++; |
| } |
| } |
| } |
| |
| if( waveOutDeviceCount > 0 ){ |
| /* get preferred output device -- see comment about default devices above */ |
| DWORD preferredDeviceStatusFlags = 0; |
| DWORD waveOutPreferredDevice = WAVE_MAPPER; |
| if( waveOutMessage( (HWAVEOUT)WAVE_MAPPER, DRVM_MAPPER_PREFERRED_GET, |
| (DWORD_PTR)&waveOutPreferredDevice, (DWORD_PTR)&preferredDeviceStatusFlags ) != MMSYSERR_NOERROR ) |
| waveOutPreferredDevice = WAVE_MAPPER; |
| |
| /* append output devices to deviceInfoArray */ |
| #ifdef PAWIN_WMME_NO_WAVE_MAPPER |
| for( i = 0; i < waveOutDeviceCount; ++i ){ |
| UINT winMmeDeviceId = (UINT)i; |
| #else |
| /* -1 enumerates the WAVE_MAPPER */ |
| for( i = -1; i < waveOutDeviceCount; ++i ){ |
| UINT winMmeDeviceId = (UINT)((i==-1) ? WAVE_MAPPER : i); |
| #endif |
| int deviceInfoInitializationSucceeded = 0; |
| int deviceIndex = initializedInfosCount; /* host-api local index. append at end of deviceInfoArray */ |
| PaWinMmeDeviceInfo *wmmeDeviceInfo = &deviceInfoArray[ deviceIndex ]; |
| PaDeviceInfo *deviceInfo = &wmmeDeviceInfo->inheritedDeviceInfo; |
| deviceInfo->structVersion = 3; |
| 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 ) |
| continue; /* ignore error results here and just skip the device. */ |
| /* N.B. Earlier WMME versions failed scanning with an error here. */ |
| |
| if( deviceInfoInitializationSucceeded ){ |
| /* NOTE: wmmeDeviceInfo->deviceInterfaceName is now allocated and would need to be freed on error. */ |
| |
| if( winMmeScanResults->defaultOutputDevice == paNoDevice ) |
| { |
| /* if there is currently no default device, use the first one available */ |
| winMmeScanResults->defaultOutputDevice = deviceIndex; |
| } |
| else if( winMmeDeviceId == waveOutPreferredDevice ) |
| { |
| /* set the default device to the system preferred device */ |
| winMmeScanResults->defaultOutputDevice = deviceIndex; |
| } |
| |
| deviceInfo->connectionId = AssignDeviceConnectionId( |
| hostApi->deviceInfos, hostApi->info.deviceCount, |
| winMmeScanResults->deviceInfos, initializedInfosCount, |
| wmmeDeviceInfo->deviceInterfaceName, /* isInput= */ 0 ); |
| |
| winMmeScanResults->deviceInfos[ deviceIndex ] = deviceInfo; |
| |
| winMmeScanResults->outputDeviceCount++; |
| initializedInfosCount++; |
| } |
| } |
| } |
| } |
| |
| *scanResults_OUT = winMmeScanResults; |
| *newDeviceCount_OUT = initializedInfosCount; |
| return result; |
| |
| error: |
| if( winMmeScanResults ) |
| { |
| FreeDeviceInfos( winMmeHostApi->allocations, winMmeScanResults->deviceInfos, initializedInfosCount ); |
| } |
| |
| return result; |
| } |
| |
| static PaError CommitDeviceInfos( |
| struct PaUtilHostApiRepresentation *hostApi, PaHostApiIndex hostApiIndex, |
| void *scanResults, int deviceCount ) |
| { |
| PaWinMmeHostApiRepresentation *winMmeHostApi = (PaWinMmeHostApiRepresentation*)hostApi; |
| PaError result = paNoError; |
| |
| (void)hostApiIndex; /* unused parameter */ |
| |
| /* Free any old memory which might be in the device info */ |
| if( hostApi->deviceInfos ) |
| { |
| FreeDeviceInfos( winMmeHostApi->allocations, hostApi->deviceInfos, hostApi->info.deviceCount ); |
| hostApi->deviceInfos = NULL; |
| } |
| hostApi->info.deviceCount = 0; |
| hostApi->info.defaultInputDevice = paNoDevice; |
| hostApi->info.defaultOutputDevice = paNoDevice; |
| winMmeHostApi->inputDeviceCount = 0; |
| winMmeHostApi->outputDeviceCount = 0; |
| |
| if( scanResults != NULL ) |
| { |
| PaWinMmeScanDeviceInfosResults *scanDeviceInfosResults = ( PaWinMmeScanDeviceInfosResults * ) scanResults; |
| |
| if( deviceCount > 0 ) |
| { |
| /* use the array allocated in ScanDeviceInfos() as our deviceInfos */ |
| hostApi->deviceInfos = scanDeviceInfosResults->deviceInfos; |
| hostApi->info.deviceCount = deviceCount; |
| hostApi->info.defaultInputDevice = scanDeviceInfosResults->defaultInputDevice; |
| hostApi->info.defaultOutputDevice = scanDeviceInfosResults->defaultOutputDevice; |
| winMmeHostApi->inputDeviceCount = scanDeviceInfosResults->inputDeviceCount; |
| winMmeHostApi->outputDeviceCount = scanDeviceInfosResults->outputDeviceCount; |
| } |
| |
| PaUtil_GroupFreeMemory( winMmeHostApi->allocations, scanDeviceInfosResults ); |
| } |
| |
| return result; |
| } |
| |
| static PaError DisposeDeviceInfos( struct PaUtilHostApiRepresentation *hostApi, |
| void *scanResults, int deviceCount ) |
| { |
| PaWinMmeHostApiRepresentation *winMmeHostApi = (PaWinMmeHostApiRepresentation*)hostApi; |
| |
| if( scanResults != NULL ) |
| { |
| PaWinMmeScanDeviceInfosResults *scanDeviceInfosResults = ( PaWinMmeScanDeviceInfosResults * ) scanResults; |
| |
| if( scanDeviceInfosResults->deviceInfos ) |
| { |
| FreeDeviceInfos( winMmeHostApi->allocations, hostApi->deviceInfos, deviceCount ); |
| } |
| |
| PaUtil_GroupFreeMemory( winMmeHostApi->allocations, scanDeviceInfosResults ); |
| } |
| |
| return paNoError; |
| } |
| |
| /***********************************************************************************/ |
| |
| 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, |
| 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, |
| 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 throtte, 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 = 0; |
| 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; |
| inputChannelMask = 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.; |
| outputChannelMask = 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 quater 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, sampleRate, |
| inputDevices, inputDeviceCount, inputChannelMask, 1 /* isInput */ ); |
| if( result != paNoError ) goto error; |
| } |
| |
| if( outputParameters ) |
| { |
| result = InitializeWaveHandles( winMmeHostApi, &stream->output, |
| winMmeSpecificOutputFlags, 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 = (DWORD)paUnanticipatedHostError; |
| /** @todo FIXME/REVIEW: can't return host error info from an asyncronous 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 occured 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 (accross 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; |
| DWORD timeoutMs, totalTimeoutMs; |
| DWORD waitStartTime, elapsedMs; |
| DWORD waitResult; |
| MMRESULT mmresult; |
| signed int hostOutputBufferIndex; |
| unsigned int channel, waitCount, i; |
| unsigned int maxWaitCount; |
| |
| /** @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 */ |
| |
| /* First-chance timeout is for draining the the buffer cleanly. Use a time longer than to total buffers duration. */ |
| timeoutMs = (DWORD)(stream->allBuffersDurationMs * 1.5) + 1; |
| |
| /* Tell processing thread to stop generating more data and to let current data play out. */ |
| stream->stopProcessing = 1; |
| |
| PA_DEBUG(("WinMME StopStream: waiting for background thread.\n")); |
| |
| waitResult = WaitForSingleObject( stream->processingThread, timeoutMs ); |
| if( waitResult == WAIT_TIMEOUT ) |
| { |
| /* try to abort */ |
| |
| /* Last-chance timeout results in no more than PA_MME_MIN_TIMEOUT_MSEC_ of additional waiting. */ |
| if( timeoutMs > PA_MME_MIN_TIMEOUT_MSEC_ ) |
| timeoutMs = PA_MME_MIN_TIMEOUT_MSEC_; |
| |
| stream->abortProcessing = 1; |
| SetEvent( stream->abortEvent ); |
| waitResult = WaitForSingleObject( stream->processingThread, timeoutMs ); |
| 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 ); |
| } |
| |
| |
| /* |
| The purpose of the following wait/poll loop is to wait for |
| the output to play out (all queued buffers to be returned). |
| We expect all queued buffers to be returned to us within |
| allBuffersDurationMs (plus some margin to avoid cutting off |
| the tail). |
| |
| When functioning as intended, WaitForSingleObject will wake |
| after each buffer completes, taking at most bufferCount |
| loop iterations. We're done when NoBuffersAreQueued() returns true. |
| |
| When not functioning as intended, we want to bound the |
| duration of the wait, but also poll frequently enough to |
| pick up an early-exit from NoBuffersAreQueued. |
| |
| Two mechanisms are used to ensure that this loop does not hang: |
| |
| - The total elapsed wait time is limited to totalTimeoutMs. |
| - The loop is limited to maxWaitCount iterations, and the |
| timeout for each iteration equals totalTimeoutMs/maxWaitCount. |
| |
| This combination should cover cases where the timers or |
| timeout times are unreliable (e.g. if WFSO takes longer |
| than timeoutMs to return). |
| */ |
| |
| totalTimeoutMs = (DWORD)(1.5 * stream->allBuffersDurationMs); |
| |
| /* poll every 1.5 buffer durations. */ |
| timeoutMs = (DWORD)((1.5 * stream->allBuffersDurationMs) / stream->output.bufferCount) + 1; |
| /* for a total of maxWaitCount iterations, duration: maxWaitCount*timeoutMs = 1.5 * stream->allBuffersDurationMs */ |
| maxWaitCount = stream->output.bufferCount; |
| |
| waitStartTime = GetTickCount(); |
| waitCount = 0; |
| while( !NoBuffersAreQueued( &stream->output ) && waitCount <= maxWaitCount ) |
| { |
| /* wait for MME to signal that a buffer is available */ |
| waitResult = WaitForSingleObject( stream->output.bufferEvent, timeoutMs ); |
| if( waitResult == WAIT_FAILED ) |
| { |
| break; |
| } |
| else if( waitResult == WAIT_TIMEOUT ) |
| { |
| elapsedMs = GetTickCount() - waitStartTime; |
| if( elapsedMs >= totalTimeoutMs ) |
| { |
| result = paTimedOut; |
| break; |
| } |
| } |
| |
| ++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; |
| unsigned int channel, i; |
| |
| /* timeout variables. see StopStream for a discussion. */ |
| DWORD totalTimeoutMs = (DWORD)(stream->allBuffersDurationMs * 1.5); |
| /* poll every 1.5 buffer durations. */ |
| DWORD timeoutMs = (DWORD)((1.5 * stream->allBuffersDurationMs) / stream->output.bufferCount) + 1; |
| /* for a total of maxWaitCount iterations, duration: maxWaitCount*timeoutMs = 1.5 * stream->allBuffersDurationMs */ |
| unsigned int maxWaitCount = stream->output.bufferCount; |
| |
| unsigned int waitCount; |
| DWORD waitStartTime = 0; |
| DWORD elapsedMs; |
| |
| 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]; |
| } |
| |
| waitCount = 0; |
| |
| do{ |
| if( CurrentInputBuffersAreDone( stream ) ) |
| { |
| waitCount = 0; |
| |
| 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{ |
| |
| if( waitCount == 0 ) |
| waitStartTime = GetTickCount(); |
| ++waitCount; |
| |
| /* wait for MME to signal that a buffer is available */ |
| waitResult = WaitForSingleObject( stream->input.bufferEvent, timeoutMs ); |
| if( waitResult == WAIT_FAILED ) |
| { |
| result = paUnanticipatedHostError; |
| break; |
| } |
| else if( waitResult == WAIT_TIMEOUT ) |
| { |
| /* |
| Repeated timeouts with no forward progress on done buffers |
| has been reported to happen after disconnecting a device (Jitsi). |
| |
| Limit the number and duration of waits, then time out. |
| */ |
| if( waitCount >= maxWaitCount ) |
| { |
| result = paTimedOut; |
| break; |
| } |
| |
| elapsedMs = GetTickCount() - waitStartTime; |
| if( elapsedMs >= totalTimeoutMs ) |
| { |
| result = paTimedOut; |
| break; |
| } |
| } |
| } |
| }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; |
| unsigned int channel, i; |
| |
| /* timeout variables. see StopStream for a discussion. */ |
| DWORD totalTimeoutMs = (DWORD)(stream->allBuffersDurationMs * 1.5); |
| /* poll every 1.5 buffer durations. */ |
| DWORD timeoutMs = (DWORD)((1.5 * stream->allBuffersDurationMs) / stream->output.bufferCount) + 1; |
| /* for a total of maxWaitCount iterations, duration: maxWaitCount*timeoutMs = 1.5 * stream->allBuffersDurationMs */ |
| unsigned int maxWaitCount = stream->output.bufferCount; |
| |
| unsigned int waitCount; |
| DWORD waitStartTime = 0; |
| DWORD elapsedMs; |
| |
| 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]; |
| } |
| |
| waitCount = 0; |
| |
| do{ |
| if( CurrentOutputBuffersAreDone( stream ) ) |
| { |
| waitCount = 0; |
| |
| 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 |
| { |
| if( waitCount == 0 ) |
| waitStartTime = GetTickCount(); |
| ++waitCount; |
| |
| /* wait for MME to signal that a buffer is available */ |
| waitResult = WaitForSingleObject( stream->output.bufferEvent, timeoutMs ); |
| if( waitResult == WAIT_FAILED ) |
| { |
| result = paUnanticipatedHostError; |
| break; |
| } |
| else if( waitResult == WAIT_TIMEOUT ) |
| { |
| /* |
| Repeated timeouts with no forward progress on done buffers |
| has been reported to happen after disconnecting a device (Jitsi). |
| |
| Limit the number and duration of waits, then time out. |
| */ |
| if( waitCount >= maxWaitCount ) |
| { |
| result = paTimedOut; |
| break; |
| } |
| |
| elapsedMs = GetTickCount() - waitStartTime; |
| if( elapsedMs >= totalTimeoutMs ) |
| { |
| result = paTimedOut; |
| break; |
| } |
| } |
| } |
| }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; |
| } |