| /* |
| * $Id$ |
| * Portable Audio I/O Library DirectSound implementation |
| * |
| * Authors: Phil Burk, Robert Marsanyi & Ross Bencina |
| * Based on the Open Source API proposed by Ross Bencina |
| * Copyright (c) 1999-2007 Ross Bencina, Phil Burk, Robert Marsanyi |
| * |
| * 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. |
| */ |
| |
| /** @file |
| @ingroup hostapi_src |
| */ |
| |
| /* Until May 2011 PA/DS has used a multimedia timer to perform the callback. |
| We're replacing this with a new implementation using a thread and a different timer mechanim. |
| Defining PA_WIN_DS_USE_WMME_TIMER uses the old (pre-May 2011) behavior. |
| */ |
| //#define PA_WIN_DS_USE_WMME_TIMER |
| |
| #include <assert.h> |
| #include <stdio.h> |
| #include <string.h> /* strlen() */ |
| |
| #define _WIN32_WINNT 0x0400 /* required to get waitable timer APIs */ |
| #include <initguid.h> /* make sure ds guids get defined */ |
| #include <windows.h> |
| #include <objbase.h> |
| |
| |
| /* |
| Use the earliest version of DX required, no need to polute the namespace |
| */ |
| #ifdef PAWIN_USE_DIRECTSOUNDFULLDUPLEXCREATE |
| #define DIRECTSOUND_VERSION 0x0800 |
| #else |
| #define DIRECTSOUND_VERSION 0x0300 |
| #endif |
| #include <dsound.h> |
| #ifdef PAWIN_USE_WDMKS_DEVICE_INFO |
| #include <dsconf.h> |
| #endif /* PAWIN_USE_WDMKS_DEVICE_INFO */ |
| #ifndef PA_WIN_DS_USE_WMME_TIMER |
| #ifndef UNDER_CE |
| #include <process.h> |
| #endif |
| #endif |
| |
| #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_ds.h" |
| #include "pa_win_ds_dynlink.h" |
| #include "pa_win_waveformat.h" |
| #include "pa_win_wdmks_utils.h" |
| #include "pa_win_coinitialize.h" |
| |
| #if (defined(WIN32) && (defined(_MSC_VER) && (_MSC_VER >= 1200))) /* MSC version 6 and above */ |
| #pragma comment( lib, "dsound.lib" ) |
| #pragma comment( lib, "winmm.lib" ) |
| #pragma comment( lib, "kernel32.lib" ) |
| #endif |
| |
| /* use CreateThread for CYGWIN, _beginthreadex for all others */ |
| #ifndef PA_WIN_DS_USE_WMME_TIMER |
| |
| #if !defined(__CYGWIN__) && !defined(UNDER_CE) |
| #define CREATE_THREAD (HANDLE)_beginthreadex |
| #undef CLOSE_THREAD_HANDLE /* as per documentation we don't call CloseHandle on a thread created with _beginthreadex */ |
| #define PA_THREAD_FUNC static unsigned WINAPI |
| #define PA_THREAD_ID unsigned |
| #else |
| #define CREATE_THREAD CreateThread |
| #define CLOSE_THREAD_HANDLE CloseHandle |
| #define PA_THREAD_FUNC static DWORD WINAPI |
| #define PA_THREAD_ID DWORD |
| #endif |
| |
| #if (defined(UNDER_CE)) |
| #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 |
| |
| PA_THREAD_FUNC ProcessingThreadProc( void *pArg ); |
| |
| #if !defined(UNDER_CE) |
| #define PA_WIN_DS_USE_WAITABLE_TIMER_OBJECT /* use waitable timer where possible, otherwise we use a WaitForSingleObject timeout */ |
| #endif |
| |
| #endif /* !PA_WIN_DS_USE_WMME_TIMER */ |
| |
| |
| /* |
| provided in newer platform sdks and x64 |
| */ |
| #ifndef DWORD_PTR |
| #if defined(_WIN64) |
| #define DWORD_PTR unsigned __int64 |
| #else |
| #define DWORD_PTR unsigned long |
| #endif |
| #endif |
| |
| #define PRINT(x) PA_DEBUG(x); |
| #define ERR_RPT(x) PRINT(x) |
| #define DBUG(x) PRINT(x) |
| #define DBUGX(x) PRINT(x) |
| |
| #define PA_USE_HIGH_LATENCY (0) |
| #if PA_USE_HIGH_LATENCY |
| #define PA_DS_WIN_9X_DEFAULT_LATENCY_ (.500) |
| #define PA_DS_WIN_NT_DEFAULT_LATENCY_ (.600) |
| #else |
| #define PA_DS_WIN_9X_DEFAULT_LATENCY_ (.140) |
| #define PA_DS_WIN_NT_DEFAULT_LATENCY_ (.280) |
| #endif |
| |
| #define PA_DS_WIN_WDM_DEFAULT_LATENCY_ (.120) |
| |
| /* we allow the polling period to range between 1 and 100ms. |
| prior to August 2011 we limited the minimum polling period to 10ms. |
| */ |
| #define PA_DS_MINIMUM_POLLING_PERIOD_SECONDS (0.001) /* 1ms */ |
| #define PA_DS_MAXIMUM_POLLING_PERIOD_SECONDS (0.100) /* 100ms */ |
| #define PA_DS_POLLING_JITTER_SECONDS (0.001) /* 1ms */ |
| |
| #define SECONDS_PER_MSEC (0.001) |
| #define MSECS_PER_SECOND (1000) |
| |
| /* prototypes for functions declared in this file */ |
| |
| #ifdef __cplusplus |
| extern "C" |
| { |
| #endif /* __cplusplus */ |
| |
| PaError PaWinDs_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index ); |
| |
| #ifdef __cplusplus |
| } |
| #endif /* __cplusplus */ |
| |
| static void Terminate( struct PaUtilHostApiRepresentation *hostApi ); |
| 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 ); |
| 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 ); |
| |
| |
| /* FIXME: should convert hr to a string */ |
| #define PA_DS_SET_LAST_DIRECTSOUND_ERROR( hr ) \ |
| PaUtil_SetLastHostErrorInfo( paDirectSound, hr, "DirectSound error" ) |
| |
| /************************************************* DX Prototypes **********/ |
| static BOOL CALLBACK CollectGUIDsProcW(LPGUID lpGUID, |
| LPCWSTR lpszDesc, |
| LPCWSTR lpszDrvName, |
| LPVOID lpContext ); |
| |
| /************************************************************************************/ |
| /********************** Structures **************************************************/ |
| /************************************************************************************/ |
| /* PaWinDsHostApiRepresentation - host api datastructure specific to this implementation */ |
| |
| typedef struct PaWinDsDeviceInfo |
| { |
| PaDeviceInfo inheritedDeviceInfo; |
| GUID guid; |
| GUID *lpGUID; |
| double sampleRates[3]; |
| 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)*/ |
| } PaWinDsDeviceInfo; |
| |
| typedef struct |
| { |
| PaUtilHostApiRepresentation inheritedHostApiRep; |
| PaUtilStreamInterface callbackStreamInterface; |
| PaUtilStreamInterface blockingStreamInterface; |
| |
| PaUtilAllocationGroup *allocations; |
| |
| /* implementation specific data goes here */ |
| |
| PaWinUtilComInitializationResult comInitializationResult; |
| |
| } PaWinDsHostApiRepresentation; |
| |
| |
| /* PaWinDsStream - a stream data structure specifically for this implementation */ |
| |
| typedef struct PaWinDsStream |
| { |
| PaUtilStreamRepresentation streamRepresentation; |
| PaUtilCpuLoadMeasurer cpuLoadMeasurer; |
| PaUtilBufferProcessor bufferProcessor; |
| |
| /* DirectSound specific data. */ |
| #ifdef PAWIN_USE_DIRECTSOUNDFULLDUPLEXCREATE |
| LPDIRECTSOUNDFULLDUPLEX8 pDirectSoundFullDuplex8; |
| #endif |
| |
| /* Output */ |
| LPDIRECTSOUND pDirectSound; |
| LPDIRECTSOUNDBUFFER pDirectSoundPrimaryBuffer; |
| LPDIRECTSOUNDBUFFER pDirectSoundOutputBuffer; |
| DWORD outputBufferWriteOffsetBytes; /* last write position */ |
| INT outputBufferSizeBytes; |
| INT outputFrameSizeBytes; |
| /* Try to detect play buffer underflows. */ |
| LARGE_INTEGER perfCounterTicksPerBuffer; /* counter ticks it should take to play a full buffer */ |
| LARGE_INTEGER previousPlayTime; |
| DWORD previousPlayCursor; |
| UINT outputUnderflowCount; |
| BOOL outputIsRunning; |
| INT finalZeroBytesWritten; /* used to determine when we've flushed the whole buffer */ |
| |
| /* Input */ |
| LPDIRECTSOUNDCAPTURE pDirectSoundCapture; |
| LPDIRECTSOUNDCAPTUREBUFFER pDirectSoundInputBuffer; |
| INT inputFrameSizeBytes; |
| UINT readOffset; /* last read position */ |
| UINT inputBufferSizeBytes; |
| |
| |
| int hostBufferSizeFrames; /* input and output host ringbuffers have the same number of frames */ |
| double framesWritten; |
| double secondsPerHostByte; /* Used to optimize latency calculation for outTime */ |
| double pollingPeriodSeconds; |
| |
| PaStreamCallbackFlags callbackFlags; |
| |
| PaStreamFlags streamFlags; |
| int callbackResult; |
| HANDLE processingCompleted; |
| |
| /* FIXME - move all below to PaUtilStreamRepresentation */ |
| volatile int isStarted; |
| volatile int isActive; |
| volatile int stopProcessing; /* stop thread once existing buffers have been returned */ |
| volatile int abortProcessing; /* stop thread immediately */ |
| |
| UINT systemTimerResolutionPeriodMs; /* set to 0 if we were unable to set the timer period */ |
| |
| #ifdef PA_WIN_DS_USE_WMME_TIMER |
| MMRESULT timerID; |
| #else |
| |
| #ifdef PA_WIN_DS_USE_WAITABLE_TIMER_OBJECT |
| HANDLE waitableTimer; |
| #endif |
| HANDLE processingThread; |
| PA_THREAD_ID processingThreadId; |
| HANDLE processingThreadCompleted; |
| #endif |
| |
| } PaWinDsStream; |
| |
| typedef struct PaWinDsScanDeviceInfosResults |
| { |
| PaDeviceInfo **deviceInfos; |
| PaDeviceIndex defaultInputDevice; |
| PaDeviceIndex defaultOutputDevice; |
| } PaWinDsScanDeviceInfosResults; |
| |
| |
| /* Set minimal latency based on the current OS version. |
| * NT has higher latency. |
| */ |
| static double PaWinDS_GetMinSystemLatencySeconds( void ) |
| { |
| /* |
| 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 */ |
| |
| double minLatencySeconds; |
| /* Set minimal latency based on whether NT or other OS. |
| * NT has higher latency. |
| */ |
| |
| OSVERSIONINFO osvi; |
| osvi.dwOSVersionInfoSize = sizeof( osvi ); |
| GetVersionEx( &osvi ); |
| DBUG(("PA - PlatformId = 0x%x\n", osvi.dwPlatformId )); |
| DBUG(("PA - MajorVersion = 0x%x\n", osvi.dwMajorVersion )); |
| DBUG(("PA - MinorVersion = 0x%x\n", osvi.dwMinorVersion )); |
| /* Check for NT */ |
| if( (osvi.dwMajorVersion == 4) && (osvi.dwPlatformId == 2) ) |
| { |
| minLatencySeconds = PA_DS_WIN_NT_DEFAULT_LATENCY_; |
| } |
| else if(osvi.dwMajorVersion >= 5) |
| { |
| minLatencySeconds = PA_DS_WIN_WDM_DEFAULT_LATENCY_; |
| } |
| else |
| { |
| minLatencySeconds = PA_DS_WIN_9X_DEFAULT_LATENCY_; |
| } |
| return minLatencySeconds; |
| |
| #pragma warning (default : 4996) |
| } |
| |
| |
| /************************************************************************* |
| ** Return minimum workable latency required for this host. This is returned |
| ** As the default stream latency in PaDeviceInfo. |
| ** Latency can be optionally set by user by setting an environment variable. |
| ** For example, to set latency to 200 msec, put: |
| ** |
| ** set PA_MIN_LATENCY_MSEC=200 |
| ** |
| ** in the AUTOEXEC.BAT file and reboot. |
| ** If the environment variable is not set, then the latency will be determined |
| ** based on the OS. Windows NT has higher latency than Win95. |
| */ |
| #define PA_LATENCY_ENV_NAME ("PA_MIN_LATENCY_MSEC") |
| #define PA_ENV_BUF_SIZE (32) |
| |
| static double PaWinDs_GetMinLatencySeconds( double sampleRate ) |
| { |
| char envbuf[PA_ENV_BUF_SIZE]; |
| DWORD hresult; |
| double minLatencySeconds = 0; |
| |
| /* Let user determine minimal latency by setting environment variable. */ |
| hresult = GetEnvironmentVariableA( PA_LATENCY_ENV_NAME, envbuf, PA_ENV_BUF_SIZE ); |
| if( (hresult > 0) && (hresult < PA_ENV_BUF_SIZE) ) |
| { |
| minLatencySeconds = atoi( envbuf ) * SECONDS_PER_MSEC; |
| } |
| else |
| { |
| minLatencySeconds = PaWinDS_GetMinSystemLatencySeconds(); |
| #if PA_USE_HIGH_LATENCY |
| PRINT(("PA - Minimum Latency set to %f msec!\n", minLatencySeconds * MSECS_PER_SECOND )); |
| #endif |
| } |
| |
| return minLatencySeconds; |
| } |
| |
| |
| /************************************************************************************ |
| ** Duplicate and convert the input string using the group allocations allocator. |
| ** A NULL string is converted to a zero length string. |
| ** If memory cannot be allocated, NULL is returned. |
| **/ |
| static char *DuplicateDeviceNameString( PaUtilAllocationGroup *allocations, const wchar_t* src ) |
| { |
| char *result = 0; |
| |
| if( src != NULL ) |
| { |
| #if !defined(_UNICODE) && !defined(UNICODE) |
| size_t len = WideCharToMultiByte(CP_ACP, 0, src, -1, NULL, 0, NULL, NULL); |
| |
| result = (char*)PaUtil_GroupAllocateMemory( allocations, (long)(len + 1) ); |
| if( result ) { |
| if (WideCharToMultiByte(CP_ACP, 0, src, -1, result, (int)len, NULL, NULL) == 0) { |
| result = 0; |
| } |
| } |
| #else |
| size_t len = WideCharToMultiByte(CP_UTF8, 0, src, -1, NULL, 0, NULL, NULL); |
| |
| result = (char*)PaUtil_GroupAllocateMemory( allocations, (long)(len + 1) ); |
| if( result ) { |
| if (WideCharToMultiByte(CP_UTF8, 0, src, -1, result, (int)len, NULL, NULL) == 0) { |
| result = 0; |
| } |
| } |
| #endif |
| } |
| else |
| { |
| result = (char*)PaUtil_GroupAllocateMemory( allocations, 1 ); |
| if( result ) |
| result[0] = '\0'; |
| } |
| |
| return result; |
| } |
| |
| /************************************************************************************ |
| ** DSDeviceNameAndGUID, DSDeviceNameAndGUIDVector used for collecting preliminary |
| ** information during device enumeration. |
| */ |
| typedef struct DSDeviceNameAndGUID{ |
| char *name; // allocated from parent's allocations, never deleted by this structure |
| GUID guid; |
| LPGUID lpGUID; |
| void *pnpInterface; // wchar_t* interface path, allocated using the DS host api's allocation group |
| } DSDeviceNameAndGUID; |
| |
| typedef struct DSDeviceNameAndGUIDVector{ |
| PaUtilAllocationGroup *allocations; |
| PaError enumerationError; |
| |
| int count; |
| int free; |
| DSDeviceNameAndGUID *items; /* Allocated using LocalAlloc() */ |
| } DSDeviceNameAndGUIDVector; |
| |
| typedef struct DSDeviceNamesAndGUIDs{ |
| PaWinDsHostApiRepresentation *winDsHostApi; |
| DSDeviceNameAndGUIDVector inputNamesAndGUIDs; |
| DSDeviceNameAndGUIDVector outputNamesAndGUIDs; |
| } DSDeviceNamesAndGUIDs; |
| |
| static PaError InitializeDSDeviceNameAndGUIDVector( |
| DSDeviceNameAndGUIDVector *guidVector, PaUtilAllocationGroup *allocations ) |
| { |
| PaError result = paNoError; |
| |
| guidVector->allocations = allocations; |
| guidVector->enumerationError = paNoError; |
| |
| guidVector->count = 0; |
| guidVector->free = 8; |
| guidVector->items = (DSDeviceNameAndGUID*)LocalAlloc( LMEM_FIXED, sizeof(DSDeviceNameAndGUID) * guidVector->free ); |
| if( guidVector->items == NULL ) |
| result = paInsufficientMemory; |
| |
| return result; |
| } |
| |
| static PaError ExpandDSDeviceNameAndGUIDVector( DSDeviceNameAndGUIDVector *guidVector ) |
| { |
| PaError result = paNoError; |
| DSDeviceNameAndGUID *newItems; |
| int i; |
| |
| /* double size of vector */ |
| int size = guidVector->count + guidVector->free; |
| guidVector->free += size; |
| |
| newItems = (DSDeviceNameAndGUID*)LocalAlloc( LMEM_FIXED, sizeof(DSDeviceNameAndGUID) * size * 2 ); |
| if( newItems == NULL ) |
| { |
| result = paInsufficientMemory; |
| } |
| else |
| { |
| for( i=0; i < guidVector->count; ++i ) |
| { |
| newItems[i].name = guidVector->items[i].name; |
| if( guidVector->items[i].lpGUID == NULL ) |
| { |
| newItems[i].lpGUID = NULL; |
| } |
| else |
| { |
| newItems[i].lpGUID = &newItems[i].guid; |
| memcpy( &newItems[i].guid, guidVector->items[i].lpGUID, sizeof(GUID) ); |
| } |
| newItems[i].pnpInterface = guidVector->items[i].pnpInterface; |
| } |
| |
| LocalFree( guidVector->items ); |
| guidVector->items = newItems; |
| } |
| |
| return result; |
| } |
| |
| /* |
| it's safe to call DSDeviceNameAndGUIDVector multiple times |
| */ |
| static PaError TerminateDSDeviceNameAndGUIDVector( DSDeviceNameAndGUIDVector *guidVector ) |
| { |
| PaError result = paNoError; |
| |
| if( guidVector->items != NULL ) |
| { |
| if( LocalFree( guidVector->items ) != NULL ) |
| result = paInsufficientMemory; /** @todo this isn't the correct error to return from a deallocation failure */ |
| |
| guidVector->items = NULL; |
| } |
| |
| return result; |
| } |
| |
| /************************************************************************************ |
| ** Collect preliminary device information during DirectSound enumeration |
| */ |
| static BOOL CALLBACK CollectGUIDsProcW(LPGUID lpGUID, |
| LPCWSTR lpszDesc, |
| LPCWSTR lpszDrvName, |
| LPVOID lpContext ) |
| { |
| DSDeviceNameAndGUIDVector *namesAndGUIDs = (DSDeviceNameAndGUIDVector*)lpContext; |
| PaError error; |
| |
| (void) lpszDrvName; /* unused variable */ |
| |
| if( namesAndGUIDs->free == 0 ) |
| { |
| error = ExpandDSDeviceNameAndGUIDVector( namesAndGUIDs ); |
| if( error != paNoError ) |
| { |
| namesAndGUIDs->enumerationError = error; |
| return FALSE; |
| } |
| } |
| |
| /* Set GUID pointer, copy GUID to storage in DSDeviceNameAndGUIDVector. */ |
| if( lpGUID == NULL ) |
| { |
| namesAndGUIDs->items[namesAndGUIDs->count].lpGUID = NULL; |
| } |
| else |
| { |
| namesAndGUIDs->items[namesAndGUIDs->count].lpGUID = |
| &namesAndGUIDs->items[namesAndGUIDs->count].guid; |
| |
| memcpy( &namesAndGUIDs->items[namesAndGUIDs->count].guid, lpGUID, sizeof(GUID) ); |
| } |
| |
| namesAndGUIDs->items[namesAndGUIDs->count].name = |
| DuplicateDeviceNameString( namesAndGUIDs->allocations, lpszDesc ); |
| if( namesAndGUIDs->items[namesAndGUIDs->count].name == NULL ) |
| { |
| namesAndGUIDs->enumerationError = paInsufficientMemory; |
| return FALSE; |
| } |
| |
| namesAndGUIDs->items[namesAndGUIDs->count].pnpInterface = 0; |
| |
| ++namesAndGUIDs->count; |
| --namesAndGUIDs->free; |
| |
| return TRUE; |
| } |
| |
| |
| #ifdef PAWIN_USE_WDMKS_DEVICE_INFO |
| |
| static void *DuplicateWCharString( PaUtilAllocationGroup *allocations, wchar_t *source ) |
| { |
| size_t len; |
| wchar_t *result; |
| |
| len = wcslen( source ); |
| result = (wchar_t*)PaUtil_GroupAllocateMemory( allocations, (long) ((len+1) * sizeof(wchar_t)) ); |
| wcscpy( result, source ); |
| return result; |
| } |
| |
| static BOOL CALLBACK KsPropertySetEnumerateCallback( PDSPROPERTY_DIRECTSOUNDDEVICE_DESCRIPTION_W_DATA data, LPVOID context ) |
| { |
| int i; |
| DSDeviceNamesAndGUIDs *deviceNamesAndGUIDs = (DSDeviceNamesAndGUIDs*)context; |
| |
| /* |
| Apparently data->Interface can be NULL in some cases. |
| Possibly virtual devices without hardware. |
| So we check for NULLs now. See mailing list message November 10, 2012: |
| "[Portaudio] portaudio initialization crash in KsPropertySetEnumerateCallback(pa_win_ds.c)" |
| */ |
| if( data->Interface ) |
| { |
| if( data->DataFlow == DIRECTSOUNDDEVICE_DATAFLOW_RENDER ) |
| { |
| for( i=0; i < deviceNamesAndGUIDs->outputNamesAndGUIDs.count; ++i ) |
| { |
| if( deviceNamesAndGUIDs->outputNamesAndGUIDs.items[i].lpGUID |
| && memcmp( &data->DeviceId, deviceNamesAndGUIDs->outputNamesAndGUIDs.items[i].lpGUID, sizeof(GUID) ) == 0 ) |
| { |
| deviceNamesAndGUIDs->outputNamesAndGUIDs.items[i].pnpInterface = |
| (char*)DuplicateWCharString( deviceNamesAndGUIDs->winDsHostApi->allocations, data->Interface ); |
| break; |
| } |
| } |
| } |
| else if( data->DataFlow == DIRECTSOUNDDEVICE_DATAFLOW_CAPTURE ) |
| { |
| for( i=0; i < deviceNamesAndGUIDs->inputNamesAndGUIDs.count; ++i ) |
| { |
| if( deviceNamesAndGUIDs->inputNamesAndGUIDs.items[i].lpGUID |
| && memcmp( &data->DeviceId, deviceNamesAndGUIDs->inputNamesAndGUIDs.items[i].lpGUID, sizeof(GUID) ) == 0 ) |
| { |
| deviceNamesAndGUIDs->inputNamesAndGUIDs.items[i].pnpInterface = |
| (char*)DuplicateWCharString( deviceNamesAndGUIDs->winDsHostApi->allocations, data->Interface ); |
| break; |
| } |
| } |
| } |
| } |
| |
| return TRUE; |
| } |
| |
| |
| static GUID pawin_CLSID_DirectSoundPrivate = |
| { 0x11ab3ec0, 0x25ec, 0x11d1, 0xa4, 0xd8, 0x00, 0xc0, 0x4f, 0xc2, 0x8a, 0xca }; |
| |
| static GUID pawin_DSPROPSETID_DirectSoundDevice = |
| { 0x84624f82, 0x25ec, 0x11d1, 0xa4, 0xd8, 0x00, 0xc0, 0x4f, 0xc2, 0x8a, 0xca }; |
| |
| static GUID pawin_IID_IKsPropertySet = |
| { 0x31efac30, 0x515c, 0x11d0, 0xa9, 0xaa, 0x00, 0xaa, 0x00, 0x61, 0xbe, 0x93 }; |
| |
| |
| /* |
| FindDevicePnpInterfaces fills in the pnpInterface fields in deviceNamesAndGUIDs |
| with UNICODE file paths to the devices. The DS documentation mentions |
| at least two techniques by which these Interface paths can be found using IKsPropertySet on |
| the DirectSound class object. One is using the DSPROPERTY_DIRECTSOUNDDEVICE_DESCRIPTION |
| property, and the other is using DSPROPERTY_DIRECTSOUNDDEVICE_ENUMERATE. |
| I tried both methods and only the second worked. I found two postings on the |
| net from people who had the same problem with the first method, so I think the method used here is |
| more common/likely to work. The problem is that IKsPropertySet_Get returns S_OK |
| but the fields of the device description are not filled in. |
| |
| The mechanism we use works by registering an enumeration callback which is called for |
| every DSound device. Our callback searches for a device in our deviceNamesAndGUIDs list |
| with the matching GUID and copies the pointer to the Interface path. |
| Note that we could have used this enumeration callback to perform the original |
| device enumeration, however we choose not to so we can disable this step easily. |
| |
| Apparently the IKsPropertySet mechanism was added in DirectSound 9c 2004 |
| http://www.tech-archive.net/Archive/Development/microsoft.public.win32.programmer.mmedia/2004-12/0099.html |
| |
| -- rossb |
| */ |
| static void FindDevicePnpInterfaces( DSDeviceNamesAndGUIDs *deviceNamesAndGUIDs ) |
| { |
| IClassFactory *pClassFactory; |
| |
| if( paWinDsDSoundEntryPoints.DllGetClassObject(&pawin_CLSID_DirectSoundPrivate, &IID_IClassFactory, (PVOID *) &pClassFactory) == S_OK ){ |
| IKsPropertySet *pPropertySet; |
| if( pClassFactory->lpVtbl->CreateInstance( pClassFactory, NULL, &pawin_IID_IKsPropertySet, (PVOID *) &pPropertySet) == S_OK ){ |
| |
| DSPROPERTY_DIRECTSOUNDDEVICE_ENUMERATE_W_DATA data; |
| ULONG bytesReturned; |
| |
| data.Callback = KsPropertySetEnumerateCallback; |
| data.Context = deviceNamesAndGUIDs; |
| |
| IKsPropertySet_Get( pPropertySet, |
| &pawin_DSPROPSETID_DirectSoundDevice, |
| DSPROPERTY_DIRECTSOUNDDEVICE_ENUMERATE_W, |
| NULL, |
| 0, |
| &data, |
| sizeof(data), |
| &bytesReturned |
| ); |
| |
| IKsPropertySet_Release( pPropertySet ); |
| } |
| pClassFactory->lpVtbl->Release( pClassFactory ); |
| } |
| |
| /* |
| The following code fragment, which I chose not to use, queries for the |
| device interface for a device with a specific GUID: |
| |
| ULONG BytesReturned; |
| DSPROPERTY_DIRECTSOUNDDEVICE_DESCRIPTION_W_DATA Property; |
| |
| memset (&Property, 0, sizeof(Property)); |
| Property.DataFlow = DIRECTSOUNDDEVICE_DATAFLOW_RENDER; |
| Property.DeviceId = *lpGUID; |
| |
| hr = IKsPropertySet_Get( pPropertySet, |
| &pawin_DSPROPSETID_DirectSoundDevice, |
| DSPROPERTY_DIRECTSOUNDDEVICE_DESCRIPTION_W, |
| NULL, |
| 0, |
| &Property, |
| sizeof(Property), |
| &BytesReturned |
| ); |
| |
| if( hr == S_OK ) |
| { |
| //pnpInterface = Property.Interface; |
| } |
| */ |
| } |
| #endif /* PAWIN_USE_WDMKS_DEVICE_INFO */ |
| |
| |
| /* |
| GUIDs for emulated devices which we blacklist below. |
| are there more than two of them?? |
| */ |
| |
| GUID IID_IRolandVSCEmulated1 = {0xc2ad1800, 0xb243, 0x11ce, 0xa8, 0xa4, 0x00, 0xaa, 0x00, 0x6c, 0x45, 0x01}; |
| GUID IID_IRolandVSCEmulated2 = {0xc2ad1800, 0xb243, 0x11ce, 0xa8, 0xa4, 0x00, 0xaa, 0x00, 0x6c, 0x45, 0x02}; |
| |
| |
| #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 }; |
| |
| /************************************************************************************ |
| ** Extract capabilities from an output device, and add it to the device info list |
| ** if successful. This function assumes that there is enough room in the |
| ** device info list to accomodate all entries. |
| ** |
| ** The device will not be added to the device list if any errors are encountered. |
| */ |
| static PaError AddOutputDeviceInfoFromDirectSound( |
| PaWinDsDeviceInfo *winDsDeviceInfo, char *name, LPGUID lpGUID, char *pnpInterface ) |
| { |
| PaDeviceInfo *deviceInfo = &winDsDeviceInfo->inheritedDeviceInfo; |
| HRESULT hr; |
| LPDIRECTSOUND lpDirectSound = NULL; |
| DSCAPS caps; |
| PaError result = paNoError; |
| int i; |
| |
| |
| /* Create a DirectSound object for the specified GUID |
| Note that using CoCreateInstance doesn't work on windows CE. |
| */ |
| hr = paWinDsDSoundEntryPoints.DirectSoundCreate( lpGUID, &lpDirectSound, NULL ); |
| |
| /** try using CoCreateInstance because DirectSoundCreate was hanging under |
| some circumstances - note this was probably related to the |
| #define BOOL short bug which has now been fixed |
| @todo delete this comment and the following code once we've ensured |
| there is no bug. |
| */ |
| /* |
| hr = CoCreateInstance( &CLSID_DirectSound, NULL, CLSCTX_INPROC_SERVER, |
| &IID_IDirectSound, (void**)&lpDirectSound ); |
| |
| if( hr == S_OK ) |
| { |
| hr = IDirectSound_Initialize( lpDirectSound, lpGUID ); |
| } |
| */ |
| |
| if( hr != DS_OK ) |
| { |
| if (hr == DSERR_ALLOCATED) |
| PA_DEBUG(("AddOutputDeviceInfoFromDirectSound %s DSERR_ALLOCATED\n",name)); |
| DBUG(("Cannot create DirectSound for %s. Result = 0x%x\n", name, hr )); |
| if (lpGUID) |
| DBUG(("%s's GUID: {0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x, 0x%x} \n", |
| name, |
| lpGUID->Data1, |
| lpGUID->Data2, |
| lpGUID->Data3, |
| lpGUID->Data4[0], |
| lpGUID->Data4[1], |
| lpGUID->Data4[2], |
| lpGUID->Data4[3], |
| lpGUID->Data4[4], |
| lpGUID->Data4[5], |
| lpGUID->Data4[6], |
| lpGUID->Data4[7])); |
| |
| result = paUnanticipatedHostError; |
| goto error; |
| } |
| else |
| { |
| /* Query device characteristics. */ |
| memset( &caps, 0, sizeof(caps) ); |
| caps.dwSize = sizeof(caps); |
| hr = IDirectSound_GetCaps( lpDirectSound, &caps ); |
| if( hr != DS_OK ) |
| { |
| DBUG(("Cannot GetCaps() for DirectSound device %s. Result = 0x%x\n", name, hr )); |
| |
| result = paUnanticipatedHostError; |
| goto error; |
| } |
| else |
| { |
| |
| #if PA_USE_WMME |
| if( caps.dwFlags & DSCAPS_EMULDRIVER ) |
| { |
| /* If WMME supported, then reject Emulated drivers because they are lousy. */ |
| result = paInvalidDevice; |
| goto error; |
| } |
| #endif |
| |
| deviceInfo->maxInputChannels = 0; |
| winDsDeviceInfo->deviceInputChannelCountIsKnown = 1; |
| |
| /* DS output capabilities only indicate supported number of channels |
| using two flags which indicate mono and/or stereo. |
| We assume that stereo devices may support more than 2 channels |
| (as is the case with 5.1 devices for example) and so |
| set deviceOutputChannelCountIsKnown to 0 (unknown). |
| In this case OpenStream will try to open the device |
| when the user requests more than 2 channels, rather than |
| returning an error. |
| */ |
| if( caps.dwFlags & DSCAPS_PRIMARYSTEREO ) |
| { |
| deviceInfo->maxOutputChannels = 2; |
| winDsDeviceInfo->deviceOutputChannelCountIsKnown = 0; |
| } |
| else |
| { |
| deviceInfo->maxOutputChannels = 1; |
| winDsDeviceInfo->deviceOutputChannelCountIsKnown = 1; |
| } |
| |
| /* Guess channels count from speaker configuration. We do it only when |
| pnpInterface is NULL or when PAWIN_USE_WDMKS_DEVICE_INFO is undefined. |
| */ |
| #ifdef PAWIN_USE_WDMKS_DEVICE_INFO |
| if( !pnpInterface ) |
| #endif |
| { |
| DWORD spkrcfg; |
| if( SUCCEEDED(IDirectSound_GetSpeakerConfig( lpDirectSound, &spkrcfg )) ) |
| { |
| int count = 0; |
| switch (DSSPEAKER_CONFIG(spkrcfg)) |
| { |
| case DSSPEAKER_HEADPHONE: count = 2; break; |
| case DSSPEAKER_MONO: count = 1; break; |
| case DSSPEAKER_QUAD: count = 4; break; |
| case DSSPEAKER_STEREO: count = 2; break; |
| case DSSPEAKER_SURROUND: count = 4; break; |
| case DSSPEAKER_5POINT1: count = 6; break; |
| case DSSPEAKER_7POINT1: count = 8; break; |
| #ifndef DSSPEAKER_7POINT1_SURROUND |
| #define DSSPEAKER_7POINT1_SURROUND 0x00000008 |
| #endif |
| case DSSPEAKER_7POINT1_SURROUND: count = 8; break; |
| #ifndef DSSPEAKER_5POINT1_SURROUND |
| #define DSSPEAKER_5POINT1_SURROUND 0x00000009 |
| #endif |
| case DSSPEAKER_5POINT1_SURROUND: count = 6; break; |
| } |
| if( count ) |
| { |
| deviceInfo->maxOutputChannels = count; |
| winDsDeviceInfo->deviceOutputChannelCountIsKnown = 1; |
| } |
| } |
| } |
| |
| #ifdef PAWIN_USE_WDMKS_DEVICE_INFO |
| if( pnpInterface ) |
| { |
| int count = PaWin_WDMKS_QueryFilterMaximumChannelCount( pnpInterface, /* isInput= */ 0 ); |
| if( count > 0 ) |
| { |
| deviceInfo->maxOutputChannels = count; |
| winDsDeviceInfo->deviceOutputChannelCountIsKnown = 1; |
| } |
| } |
| #endif /* PAWIN_USE_WDMKS_DEVICE_INFO */ |
| |
| /* initialize defaultSampleRate */ |
| |
| if( caps.dwFlags & DSCAPS_CONTINUOUSRATE ) |
| { |
| /* initialize to caps.dwMaxSecondarySampleRate incase none of the standard rates match */ |
| deviceInfo->defaultSampleRate = caps.dwMaxSecondarySampleRate; |
| |
| for( i = 0; i < PA_DEFAULTSAMPLERATESEARCHORDER_COUNT_; ++i ) |
| { |
| if( defaultSampleRateSearchOrder_[i] >= caps.dwMinSecondarySampleRate |
| && defaultSampleRateSearchOrder_[i] <= caps.dwMaxSecondarySampleRate ) |
| { |
| deviceInfo->defaultSampleRate = defaultSampleRateSearchOrder_[i]; |
| break; |
| } |
| } |
| } |
| else if( caps.dwMinSecondarySampleRate == caps.dwMaxSecondarySampleRate ) |
| { |
| if( caps.dwMinSecondarySampleRate == 0 ) |
| { |
| /* |
| ** On my Thinkpad 380Z, DirectSoundV6 returns min-max=0 !! |
| ** But it supports continuous sampling. |
| ** So fake range of rates, and hope it really supports it. |
| */ |
| deviceInfo->defaultSampleRate = 48000.0f; /* assume 48000 as the default */ |
| |
| DBUG(("PA - Reported rates both zero. Setting to fake values for device #%s\n", name )); |
| } |
| else |
| { |
| deviceInfo->defaultSampleRate = caps.dwMaxSecondarySampleRate; |
| } |
| } |
| else if( (caps.dwMinSecondarySampleRate < 1000.0) && (caps.dwMaxSecondarySampleRate > 50000.0) ) |
| { |
| /* The EWS88MT drivers lie, lie, lie. The say they only support two rates, 100 & 100000. |
| ** But we know that they really support a range of rates! |
| ** So when we see a ridiculous set of rates, assume it is a range. |
| */ |
| deviceInfo->defaultSampleRate = 48000.0f; /* assume 48000 as the default */ |
| DBUG(("PA - Sample rate range used instead of two odd values for device #%s\n", name )); |
| } |
| else deviceInfo->defaultSampleRate = caps.dwMaxSecondarySampleRate; |
| |
| //printf( "min %d max %d\n", caps.dwMinSecondarySampleRate, caps.dwMaxSecondarySampleRate ); |
| // dwFlags | DSCAPS_CONTINUOUSRATE |
| |
| deviceInfo->defaultLowInputLatency = 0.; |
| deviceInfo->defaultHighInputLatency = 0.; |
| |
| deviceInfo->defaultLowOutputLatency = PaWinDs_GetMinLatencySeconds( deviceInfo->defaultSampleRate ); |
| deviceInfo->defaultHighOutputLatency = deviceInfo->defaultLowOutputLatency * 2; |
| } |
| |
| IDirectSound_Release( lpDirectSound ); |
| } |
| |
| /* Copy GUID to the device info structure. Set pointer. */ |
| if( lpGUID == NULL ) |
| { |
| winDsDeviceInfo->lpGUID = NULL; |
| } |
| else |
| { |
| memcpy( &winDsDeviceInfo->guid, lpGUID, sizeof(GUID) ); |
| winDsDeviceInfo->lpGUID = &winDsDeviceInfo->guid; |
| } |
| |
| deviceInfo->name = name; |
| deviceInfo->connectionId = -1; /* initialized by caller */ |
| |
| return result; |
| |
| error: |
| if( lpDirectSound ) |
| IDirectSound_Release( lpDirectSound ); |
| |
| return result; |
| } |
| |
| |
| /************************************************************************************ |
| ** Extract capabilities from an input device, and add it to the device info list |
| ** if successful. This function assumes that there is enough room in the |
| ** device info list to accomodate all entries. |
| ** |
| ** The device will not be added to the device list if any errors are encountered. |
| */ |
| static PaError AddInputDeviceInfoFromDirectSoundCapture( |
| PaWinDsDeviceInfo *winDsDeviceInfo, char *name, LPGUID lpGUID, char *pnpInterface ) |
| { |
| PaDeviceInfo *deviceInfo = &winDsDeviceInfo->inheritedDeviceInfo; |
| HRESULT hr; |
| LPDIRECTSOUNDCAPTURE lpDirectSoundCapture = NULL; |
| DSCCAPS caps; |
| PaError result = paNoError; |
| |
| |
| hr = paWinDsDSoundEntryPoints.DirectSoundCaptureCreate( lpGUID, &lpDirectSoundCapture, NULL ); |
| |
| /** try using CoCreateInstance because DirectSoundCreate was hanging under |
| some circumstances - note this was probably related to the |
| #define BOOL short bug which has now been fixed |
| @todo delete this comment and the following code once we've ensured |
| there is no bug. |
| */ |
| /* |
| hr = CoCreateInstance( &CLSID_DirectSoundCapture, NULL, CLSCTX_INPROC_SERVER, |
| &IID_IDirectSoundCapture, (void**)&lpDirectSoundCapture ); |
| */ |
| if( hr != DS_OK ) |
| { |
| DBUG(("Cannot create Capture for %s. Result = 0x%x\n", name, hr )); |
| result = paUnanticipatedHostError; |
| goto error; |
| } |
| else |
| { |
| /* Query device characteristics. */ |
| memset( &caps, 0, sizeof(caps) ); |
| caps.dwSize = sizeof(caps); |
| hr = IDirectSoundCapture_GetCaps( lpDirectSoundCapture, &caps ); |
| if( hr != DS_OK ) |
| { |
| DBUG(("Cannot GetCaps() for Capture device %s. Result = 0x%x\n", name, hr )); |
| result = paUnanticipatedHostError; |
| goto error; |
| } |
| else |
| { |
| #if PA_USE_WMME |
| if( caps.dwFlags & DSCAPS_EMULDRIVER ) |
| { |
| /* If WMME supported, then reject Emulated drivers because they are lousy. */ |
| result = paInvalidDevice; |
| goto error; |
| } |
| #endif |
| |
| deviceInfo->maxInputChannels = caps.dwChannels; |
| winDsDeviceInfo->deviceInputChannelCountIsKnown = 1; |
| |
| deviceInfo->maxOutputChannels = 0; |
| winDsDeviceInfo->deviceOutputChannelCountIsKnown = 1; |
| |
| #ifdef PAWIN_USE_WDMKS_DEVICE_INFO |
| if( pnpInterface ) |
| { |
| int count = PaWin_WDMKS_QueryFilterMaximumChannelCount( pnpInterface, /* isInput= */ 1 ); |
| if( count > 0 ) |
| { |
| deviceInfo->maxInputChannels = count; |
| winDsDeviceInfo->deviceInputChannelCountIsKnown = 1; |
| } |
| } |
| #endif /* PAWIN_USE_WDMKS_DEVICE_INFO */ |
| |
| /* constants from a WINE patch by Francois Gouget, see: |
| http://www.winehq.com/hypermail/wine-patches/2003/01/0290.html |
| |
| --- |
| Date: Fri, 14 May 2004 10:38:12 +0200 (CEST) |
| From: Francois Gouget <fgouget@ ... .fr> |
| To: Ross Bencina <rbencina@ ... .au> |
| Subject: Re: Permission to use wine 48/96 wave patch in BSD licensed library |
| |
| [snip] |
| |
| I give you permission to use the patch below under the BSD license. |
| http://www.winehq.com/hypermail/wine-patches/2003/01/0290.html |
| |
| [snip] |
| */ |
| #ifndef WAVE_FORMAT_48M08 |
| #define WAVE_FORMAT_48M08 0x00001000 /* 48 kHz, Mono, 8-bit */ |
| #define WAVE_FORMAT_48S08 0x00002000 /* 48 kHz, Stereo, 8-bit */ |
| #define WAVE_FORMAT_48M16 0x00004000 /* 48 kHz, Mono, 16-bit */ |
| #define WAVE_FORMAT_48S16 0x00008000 /* 48 kHz, Stereo, 16-bit */ |
| #define WAVE_FORMAT_96M08 0x00010000 /* 96 kHz, Mono, 8-bit */ |
| #define WAVE_FORMAT_96S08 0x00020000 /* 96 kHz, Stereo, 8-bit */ |
| #define WAVE_FORMAT_96M16 0x00040000 /* 96 kHz, Mono, 16-bit */ |
| #define WAVE_FORMAT_96S16 0x00080000 /* 96 kHz, Stereo, 16-bit */ |
| #endif |
| |
| /* defaultSampleRate */ |
| if( caps.dwChannels == 2 ) |
| { |
| if( caps.dwFormats & WAVE_FORMAT_4S16 ) |
| deviceInfo->defaultSampleRate = 44100.0; |
| else if( caps.dwFormats & WAVE_FORMAT_48S16 ) |
| deviceInfo->defaultSampleRate = 48000.0; |
| else if( caps.dwFormats & WAVE_FORMAT_2S16 ) |
| deviceInfo->defaultSampleRate = 22050.0; |
| else if( caps.dwFormats & WAVE_FORMAT_1S16 ) |
| deviceInfo->defaultSampleRate = 11025.0; |
| else if( caps.dwFormats & WAVE_FORMAT_96S16 ) |
| deviceInfo->defaultSampleRate = 96000.0; |
| else |
| deviceInfo->defaultSampleRate = 48000.0; /* assume 48000 as the default */ |
| } |
| else if( caps.dwChannels == 1 ) |
| { |
| if( caps.dwFormats & WAVE_FORMAT_4M16 ) |
| deviceInfo->defaultSampleRate = 44100.0; |
| else if( caps.dwFormats & WAVE_FORMAT_48M16 ) |
| deviceInfo->defaultSampleRate = 48000.0; |
| else if( caps.dwFormats & WAVE_FORMAT_2M16 ) |
| deviceInfo->defaultSampleRate = 22050.0; |
| else if( caps.dwFormats & WAVE_FORMAT_1M16 ) |
| deviceInfo->defaultSampleRate = 11025.0; |
| else if( caps.dwFormats & WAVE_FORMAT_96M16 ) |
| deviceInfo->defaultSampleRate = 96000.0; |
| else |
| deviceInfo->defaultSampleRate = 48000.0; /* assume 48000 as the default */ |
| } |
| else deviceInfo->defaultSampleRate = 48000.0; /* assume 48000 as the default */ |
| |
| deviceInfo->defaultLowInputLatency = PaWinDs_GetMinLatencySeconds( deviceInfo->defaultSampleRate ); |
| deviceInfo->defaultHighInputLatency = deviceInfo->defaultLowInputLatency * 2; |
| |
| deviceInfo->defaultLowOutputLatency = 0.; |
| deviceInfo->defaultHighOutputLatency = 0.; |
| } |
| |
| IDirectSoundCapture_Release( lpDirectSoundCapture ); |
| } |
| |
| /* Copy GUID to the device info structure. Set pointer. */ |
| if( lpGUID == NULL ) |
| { |
| winDsDeviceInfo->lpGUID = NULL; |
| } |
| else |
| { |
| winDsDeviceInfo->lpGUID = &winDsDeviceInfo->guid; |
| memcpy( &winDsDeviceInfo->guid, lpGUID, sizeof(GUID) ); |
| } |
| |
| deviceInfo->name = name; |
| deviceInfo->connectionId = -1; /* initialized by caller */ |
| |
| return result; |
| |
| error: |
| if( lpDirectSoundCapture ) |
| IDirectSoundCapture_Release( lpDirectSoundCapture ); |
| |
| return result; |
| } |
| |
| |
| /***********************************************************************************/ |
| PaError PaWinDs_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex hostApiIndex ) |
| { |
| PaError result = paNoError; |
| PaWinDsHostApiRepresentation *winDsHostApi; |
| int deviceCount; |
| void *scanResults = 0; |
| |
| PaWinDs_InitializeDSoundEntryPoints(); |
| |
| winDsHostApi = (PaWinDsHostApiRepresentation*)PaUtil_AllocateMemory( sizeof(PaWinDsHostApiRepresentation) ); |
| if( !winDsHostApi ) |
| { |
| result = paInsufficientMemory; |
| goto error; |
| } |
| |
| memset( winDsHostApi, 0, sizeof(PaWinDsHostApiRepresentation) ); /* ensure all fields are zeroed. especially winDsHostApi->allocations */ |
| |
| result = PaWinUtil_CoInitialize( paDirectSound, &winDsHostApi->comInitializationResult ); |
| if( result != paNoError ) |
| { |
| goto error; |
| } |
| |
| winDsHostApi->allocations = PaUtil_CreateAllocationGroup(); |
| if( !winDsHostApi->allocations ) |
| { |
| result = paInsufficientMemory; |
| goto error; |
| } |
| |
| *hostApi = &winDsHostApi->inheritedHostApiRep; |
| (*hostApi)->info.structVersion = 1; |
| (*hostApi)->info.type = paDirectSound; |
| (*hostApi)->info.name = "Windows DirectSound"; |
| |
| /* these are all updated by CommitDeviceInfos() */ |
| (*hostApi)->info.deviceCount = 0; |
| (*hostApi)->info.defaultInputDevice = paNoDevice; |
| (*hostApi)->info.defaultOutputDevice = paNoDevice; |
| (*hostApi)->deviceInfos = 0; |
| |
| result = ScanDeviceInfos( &winDsHostApi->inheritedHostApiRep, hostApiIndex, &scanResults, &deviceCount ); |
| if( result != paNoError ) |
| goto error; |
| |
| CommitDeviceInfos( &winDsHostApi->inheritedHostApiRep, hostApiIndex, scanResults, deviceCount ); |
| |
| (*hostApi)->Terminate = Terminate; |
| (*hostApi)->OpenStream = OpenStream; |
| (*hostApi)->IsFormatSupported = IsFormatSupported; |
| (*hostApi)->ScanDeviceInfos = ScanDeviceInfos; |
| (*hostApi)->CommitDeviceInfos = CommitDeviceInfos; |
| (*hostApi)->DisposeDeviceInfos = DisposeDeviceInfos; |
| |
| PaUtil_InitializeStreamInterface( &winDsHostApi->callbackStreamInterface, CloseStream, StartStream, |
| StopStream, AbortStream, IsStreamStopped, IsStreamActive, |
| GetStreamTime, GetStreamCpuLoad, |
| PaUtil_DummyRead, PaUtil_DummyWrite, |
| PaUtil_DummyGetReadAvailable, PaUtil_DummyGetWriteAvailable ); |
| |
| PaUtil_InitializeStreamInterface( &winDsHostApi->blockingStreamInterface, CloseStream, StartStream, |
| StopStream, AbortStream, IsStreamStopped, IsStreamActive, |
| GetStreamTime, PaUtil_DummyGetCpuLoad, |
| ReadStream, WriteStream, GetStreamReadAvailable, GetStreamWriteAvailable ); |
| |
| return result; |
| |
| error: |
| Terminate( (struct PaUtilHostApiRepresentation *)winDsHostApi ); |
| |
| return result; |
| } |
| |
| |
| /***********************************************************************************/ |
| static void Terminate( struct PaUtilHostApiRepresentation *hostApi ) |
| { |
| PaWinDsHostApiRepresentation *winDsHostApi = (PaWinDsHostApiRepresentation*)hostApi; |
| |
| if( winDsHostApi ){ |
| if( winDsHostApi->allocations ) |
| { |
| PaUtil_FreeAllAllocations( winDsHostApi->allocations ); |
| PaUtil_DestroyAllocationGroup( winDsHostApi->allocations ); |
| } |
| |
| PaWinUtil_CoUninitialize( paDirectSound, &winDsHostApi->comInitializationResult ); |
| |
| PaUtil_FreeMemory( winDsHostApi ); |
| } |
| |
| PaWinDs_TerminateDSoundEntryPoints(); |
| } |
| |
| static PaError ValidateWinDirectSoundSpecificStreamInfo( |
| const PaStreamParameters *streamParameters, |
| const PaWinDirectSoundStreamInfo *streamInfo ) |
| { |
| if( streamInfo ) |
| { |
| if( streamInfo->size != sizeof( PaWinDirectSoundStreamInfo ) |
| || streamInfo->version != 2 ) |
| { |
| return paIncompatibleHostApiSpecificStreamInfo; |
| } |
| |
| if( streamInfo->flags & paWinDirectSoundUseLowLevelLatencyParameters ) |
| { |
| if( streamInfo->framesPerBuffer <= 0 ) |
| return paIncompatibleHostApiSpecificStreamInfo; |
| |
| } |
| } |
| |
| return paNoError; |
| } |
| |
| /***********************************************************************************/ |
| static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, |
| const PaStreamParameters *inputParameters, |
| const PaStreamParameters *outputParameters, |
| double sampleRate ) |
| { |
| PaError result; |
| PaWinDsDeviceInfo *inputWinDsDeviceInfo, *outputWinDsDeviceInfo; |
| PaDeviceInfo *inputDeviceInfo, *outputDeviceInfo; |
| int inputChannelCount, outputChannelCount; |
| PaSampleFormat inputSampleFormat, outputSampleFormat; |
| PaWinDirectSoundStreamInfo *inputStreamInfo, *outputStreamInfo; |
| |
| if( inputParameters ) |
| { |
| inputWinDsDeviceInfo = (PaWinDsDeviceInfo*) hostApi->deviceInfos[ inputParameters->device ]; |
| inputDeviceInfo = &inputWinDsDeviceInfo->inheritedDeviceInfo; |
| |
| inputChannelCount = inputParameters->channelCount; |
| inputSampleFormat = inputParameters->sampleFormat; |
| |
| /* unless alternate device specification is supported, reject the use of |
| paUseHostApiSpecificDeviceSpecification */ |
| |
| if( inputParameters->device == paUseHostApiSpecificDeviceSpecification ) |
| return paInvalidDevice; |
| |
| /* check that input device can support inputChannelCount */ |
| if( inputWinDsDeviceInfo->deviceInputChannelCountIsKnown |
| && inputChannelCount > inputDeviceInfo->maxInputChannels ) |
| return paInvalidChannelCount; |
| |
| /* validate inputStreamInfo */ |
| inputStreamInfo = (PaWinDirectSoundStreamInfo*)inputParameters->hostApiSpecificStreamInfo; |
| result = ValidateWinDirectSoundSpecificStreamInfo( inputParameters, inputStreamInfo ); |
| if( result != paNoError ) return result; |
| } |
| else |
| { |
| inputChannelCount = 0; |
| } |
| |
| if( outputParameters ) |
| { |
| outputWinDsDeviceInfo = (PaWinDsDeviceInfo*) hostApi->deviceInfos[ outputParameters->device ]; |
| outputDeviceInfo = &outputWinDsDeviceInfo->inheritedDeviceInfo; |
| |
| outputChannelCount = outputParameters->channelCount; |
| outputSampleFormat = outputParameters->sampleFormat; |
| |
| /* unless alternate device specification is supported, reject the use of |
| paUseHostApiSpecificDeviceSpecification */ |
| |
| if( outputParameters->device == paUseHostApiSpecificDeviceSpecification ) |
| return paInvalidDevice; |
| |
| /* check that output device can support inputChannelCount */ |
| if( outputWinDsDeviceInfo->deviceOutputChannelCountIsKnown |
| && outputChannelCount > outputDeviceInfo->maxOutputChannels ) |
| return paInvalidChannelCount; |
| |
| /* validate outputStreamInfo */ |
| outputStreamInfo = (PaWinDirectSoundStreamInfo*)outputParameters->hostApiSpecificStreamInfo; |
| result = ValidateWinDirectSoundSpecificStreamInfo( outputParameters, outputStreamInfo ); |
| if( result != paNoError ) return result; |
| } |
| else |
| { |
| outputChannelCount = 0; |
| } |
| |
| /* |
| IMPLEMENT ME: |
| |
| - if a full duplex stream is requested, check that the combination |
| of input and output parameters is supported if necessary |
| |
| - check that the device supports sampleRate |
| |
| Because the buffer adapter handles conversion between all standard |
| sample formats, the following checks are only required if paCustomFormat |
| is implemented, or under some other unusual conditions. |
| |
| - check that input device can support inputSampleFormat, or that |
| we have the capability to convert from outputSampleFormat to |
| a native format |
| |
| - check that output device can support outputSampleFormat, or that |
| we have the capability to convert from outputSampleFormat to |
| a native format |
| */ |
| |
| return paFormatIsSupported; |
| } |
| |
| /***********************************************************************************/ |
| |
| /* used for looking up existing devices to reuse their connectionId */ |
| static const PaDeviceInfo* FindDeviceInfo( |
| const PaDeviceInfo **deviceInfos, int deviceCount, |
| LPGUID lpGUID, 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 PaWinDsDeviceInfo *dsDeviceInfo = (const PaWinDsDeviceInfo*)deviceInfo; |
| if( ((isInput && deviceInfo->maxInputChannels > 0) |
| || (!isInput && deviceInfo->maxOutputChannels > 0)) |
| && ((lpGUID == NULL && dsDeviceInfo->lpGUID == NULL) |
| || (lpGUID != NULL && dsDeviceInfo->lpGUID != NULL && (memcmp(lpGUID, dsDeviceInfo->lpGUID, sizeof(GUID)) == 0)))) |
| { |
| return deviceInfo; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| static void FreeDeviceInfos( PaUtilAllocationGroup *allocations, PaDeviceInfo **deviceInfos ) |
| { |
| if( deviceInfos ) |
| { |
| if( deviceInfos[0] ) |
| { |
| /* 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, int *newDeviceCount ) |
| { |
| PaWinDsHostApiRepresentation *winDsHostApi = (PaWinDsHostApiRepresentation*)hostApi; |
| PaWinDsDeviceInfo *deviceInfoArray; |
| PaError result = paNoError; |
| PaWinDsScanDeviceInfosResults *outArgument = 0; |
| DSDeviceNamesAndGUIDs deviceNamesAndGUIDs; |
| int i = 0; |
| int maximumNewDeviceCount = 0; |
| |
| /* Check preconditions */ |
| if( scanResults == NULL || newDeviceCount == NULL ) |
| return paInternalError; |
| |
| /* initialize the out params */ |
| *scanResults = NULL; |
| *newDeviceCount = 0; |
| |
| /* initialise guid vectors so they can be safely deleted on error */ |
| deviceNamesAndGUIDs.winDsHostApi = NULL; |
| deviceNamesAndGUIDs.inputNamesAndGUIDs.items = NULL; |
| deviceNamesAndGUIDs.outputNamesAndGUIDs.items = NULL; |
| |
| result = InitializeDSDeviceNameAndGUIDVector( &deviceNamesAndGUIDs.inputNamesAndGUIDs, winDsHostApi->allocations ); |
| if( result != paNoError ) |
| goto error; |
| |
| result = InitializeDSDeviceNameAndGUIDVector( &deviceNamesAndGUIDs.outputNamesAndGUIDs, winDsHostApi->allocations ); |
| if( result != paNoError ) |
| goto error; |
| |
| deviceNamesAndGUIDs.winDsHostApi = winDsHostApi; |
| |
| /* DSound - enumerate devices to count them and to gather their GUIDs and device names */ |
| |
| paWinDsDSoundEntryPoints.DirectSoundCaptureEnumerateW( (LPDSENUMCALLBACKW)CollectGUIDsProcW, (void *)&deviceNamesAndGUIDs.inputNamesAndGUIDs ); |
| |
| if( deviceNamesAndGUIDs.inputNamesAndGUIDs.enumerationError != paNoError ) |
| { |
| result = deviceNamesAndGUIDs.inputNamesAndGUIDs.enumerationError; |
| goto error; |
| } |
| |
| paWinDsDSoundEntryPoints.DirectSoundEnumerateW( (LPDSENUMCALLBACKW)CollectGUIDsProcW, (void *)&deviceNamesAndGUIDs.outputNamesAndGUIDs ); |
| |
| if( deviceNamesAndGUIDs.outputNamesAndGUIDs.enumerationError != paNoError ) |
| { |
| result = deviceNamesAndGUIDs.outputNamesAndGUIDs.enumerationError; |
| goto error; |
| } |
| |
| maximumNewDeviceCount = deviceNamesAndGUIDs.inputNamesAndGUIDs.count + |
| deviceNamesAndGUIDs.outputNamesAndGUIDs.count; |
| |
| #ifdef PAWIN_USE_WDMKS_DEVICE_INFO |
| if( maximumNewDeviceCount > 0 ) |
| { |
| FindDevicePnpInterfaces( &deviceNamesAndGUIDs ); |
| } |
| #endif /* PAWIN_USE_WDMKS_DEVICE_INFO */ |
| |
| if( maximumNewDeviceCount > 0 ) |
| { |
| /* Allocate the out param for all the info we need */ |
| outArgument = (PaWinDsScanDeviceInfosResults *) PaUtil_GroupAllocateMemory( |
| winDsHostApi->allocations, sizeof(PaWinDsScanDeviceInfosResults) ); |
| if( !outArgument ) |
| { |
| result = paInsufficientMemory; |
| goto error; |
| } |
| |
| /* allocate array for pointers to PaDeviceInfo structs */ |
| outArgument->deviceInfos = (PaDeviceInfo**)PaUtil_GroupAllocateMemory( |
| winDsHostApi->allocations, sizeof(PaDeviceInfo*) * maximumNewDeviceCount ); |
| if( !outArgument->deviceInfos ) |
| { |
| result = paInsufficientMemory; |
| goto error; |
| } |
| |
| /* allocate all PaDeviceInfo structs in a contiguous block */ |
| deviceInfoArray = (PaWinDsDeviceInfo*)PaUtil_GroupAllocateMemory( |
| winDsHostApi->allocations, sizeof(PaWinDsDeviceInfo) * maximumNewDeviceCount ); |
| if( !deviceInfoArray ) |
| { |
| result = paInsufficientMemory; |
| goto error; |
| } |
| |
| for( i = 0 ; i < maximumNewDeviceCount; ++i ) |
| { |
| PaDeviceInfo *deviceInfo = &deviceInfoArray[i].inheritedDeviceInfo; |
| deviceInfo->structVersion = 3; |
| deviceInfo->hostApi = hostApiIndex; |
| deviceInfo->name = 0; |
| |
| outArgument->deviceInfos[ i ] = deviceInfo; |
| } |
| |
| for( i = 0 ; i < deviceNamesAndGUIDs.inputNamesAndGUIDs.count ; ++i ) |
| { |
| PaWinDsDeviceInfo *winDsDeviceInfo = (PaWinDsDeviceInfo*)outArgument->deviceInfos[*newDeviceCount]; |
| const PaDeviceInfo *currentDeviceInfo = FindDeviceInfo( |
| hostApi->deviceInfos, hostApi->info.deviceCount, |
| deviceNamesAndGUIDs.inputNamesAndGUIDs.items[i].lpGUID, /*isInput=*/1 ); |
| |
| /* |
| FIXME REVIEW |
| - we could also use deviceNamesAndGUIDs.inputNamesAndGUIDs.items[i].pnpInterface as parameter to FindDeviceInfo. I'm not sure whether that adds anything |
| - if currentDeviceInfo is non-NULL we could just copy the device info over instead of calling AddInputDeviceInfoFromDirectSoundCapture |
| => make an assesment about whether any device info is likely to change |
| - Channel count could change if speaker configuration changes |
| - if we're going to leave it the current way, then move call to FindDeviceInfo to only if AddInputDeviceInfoFromDirectSoundCapture succeeds |
| */ |
| |
| result = AddInputDeviceInfoFromDirectSoundCapture( winDsDeviceInfo, |
| deviceNamesAndGUIDs.inputNamesAndGUIDs.items[i].name, |
| deviceNamesAndGUIDs.inputNamesAndGUIDs.items[i].lpGUID, |
| deviceNamesAndGUIDs.inputNamesAndGUIDs.items[i].pnpInterface ); |
| if( result == paNoError ) |
| { |
| if (currentDeviceInfo) |
| winDsDeviceInfo->inheritedDeviceInfo.connectionId = currentDeviceInfo->connectionId; |
| else |
| winDsDeviceInfo->inheritedDeviceInfo.connectionId = PaUtil_MakeDeviceConnectionId(); |
| |
| if( deviceNamesAndGUIDs.inputNamesAndGUIDs.items[i].lpGUID == NULL ) |
| outArgument->defaultInputDevice = *newDeviceCount; |
| (*newDeviceCount)++; |
| } |
| /* ignore error results here and just skip the device */ |
| } |
| |
| for( i = 0 ; i < deviceNamesAndGUIDs.outputNamesAndGUIDs.count ; ++i ) |
| { |
| PaWinDsDeviceInfo *winDsDeviceInfo = (PaWinDsDeviceInfo*)outArgument->deviceInfos[*newDeviceCount]; |
| const PaDeviceInfo *currentDeviceInfo = FindDeviceInfo( |
| hostApi->deviceInfos, hostApi->info.deviceCount, |
| deviceNamesAndGUIDs.outputNamesAndGUIDs.items[i].lpGUID, /*isInput=*/0 ); |
| |
| result = AddOutputDeviceInfoFromDirectSound( winDsDeviceInfo, |
| deviceNamesAndGUIDs.outputNamesAndGUIDs.items[i].name, |
| deviceNamesAndGUIDs.outputNamesAndGUIDs.items[i].lpGUID, |
| deviceNamesAndGUIDs.outputNamesAndGUIDs.items[i].pnpInterface ); |
| if( result == paNoError ) |
| { |
| if (currentDeviceInfo) |
| winDsDeviceInfo->inheritedDeviceInfo.connectionId = currentDeviceInfo->connectionId; |
| else |
| winDsDeviceInfo->inheritedDeviceInfo.connectionId = PaUtil_MakeDeviceConnectionId(); |
| |
| if( deviceNamesAndGUIDs.outputNamesAndGUIDs.items[i].lpGUID == NULL ) |
| outArgument->defaultOutputDevice = *newDeviceCount; |
| (*newDeviceCount)++; |
| } |
| /* ignore error results here and just skip the device */ |
| } |
| } |
| |
| result = TerminateDSDeviceNameAndGUIDVector( &deviceNamesAndGUIDs.inputNamesAndGUIDs ); |
| if( result != paNoError ) |
| goto error; |
| |
| result = TerminateDSDeviceNameAndGUIDVector( &deviceNamesAndGUIDs.outputNamesAndGUIDs ); |
| if( result != paNoError ) |
| goto error; |
| |
| *scanResults = outArgument; |
| return result; |
| |
| error: |
| TerminateDSDeviceNameAndGUIDVector( &deviceNamesAndGUIDs.inputNamesAndGUIDs ); |
| TerminateDSDeviceNameAndGUIDVector( &deviceNamesAndGUIDs.outputNamesAndGUIDs ); |
| |
| if( outArgument ) |
| { |
| FreeDeviceInfos( winDsHostApi->allocations, outArgument->deviceInfos ); |
| } |
| *newDeviceCount = 0; |
| |
| return result; |
| } |
| |
| static PaError CommitDeviceInfos( struct PaUtilHostApiRepresentation *hostApi, PaHostApiIndex index, void *scanResults, int deviceCount ) |
| { |
| PaWinDsHostApiRepresentation *winDsHostApi = (PaWinDsHostApiRepresentation*)hostApi; |
| |
| hostApi->info.deviceCount = 0; |
| hostApi->info.defaultInputDevice = paNoDevice; |
| hostApi->info.defaultOutputDevice = paNoDevice; |
| |
| /* Free any old memory which might be in the device info */ |
| if( hostApi->deviceInfos ) |
| { |
| FreeDeviceInfos( winDsHostApi->allocations, hostApi->deviceInfos ); |
| hostApi->deviceInfos = NULL; |
| } |
| |
| if( scanResults != NULL ) |
| { |
| PaWinDsScanDeviceInfosResults *scanDeviceInfosResults = ( PaWinDsScanDeviceInfosResults * ) scanResults; |
| |
| if( deviceCount > 0 ) |
| { |
| /* use the array allocated in ScanDeviceInfos() as our deviceInfos */ |
| hostApi->deviceInfos = scanDeviceInfosResults->deviceInfos; |
| |
| hostApi->info.defaultInputDevice = scanDeviceInfosResults->defaultInputDevice; |
| hostApi->info.defaultOutputDevice = scanDeviceInfosResults->defaultOutputDevice; |
| |
| hostApi->info.deviceCount = deviceCount; |
| } |
| |
| PaUtil_GroupFreeMemory( winDsHostApi->allocations, scanDeviceInfosResults ); |
| } |
| |
| return paNoError; |
| } |
| |
| static PaError DisposeDeviceInfos( struct PaUtilHostApiRepresentation *hostApi, void *scanResults, int deviceCount ) |
| { |
| PaWinDsHostApiRepresentation *winDsHostApi = (PaWinDsHostApiRepresentation*)hostApi; |
| |
| if( scanResults != NULL ) |
| { |
| PaWinDsScanDeviceInfosResults *scanDeviceInfosResults = ( PaWinDsScanDeviceInfosResults * ) scanResults; |
| |
| if( scanDeviceInfosResults->deviceInfos ) |
| { |
| FreeDeviceInfos( winDsHostApi->allocations, hostApi->deviceInfos ); |
| } |
| |
| PaUtil_GroupFreeMemory( winDsHostApi->allocations, scanDeviceInfosResults ); |
| } |
| |
| return paNoError; |
| } |
| |
| /***********************************************************************************/ |
| |
| #ifdef PAWIN_USE_DIRECTSOUNDFULLDUPLEXCREATE |
| static HRESULT InitFullDuplexInputOutputBuffers( PaWinDsStream *stream, |
| PaWinDsDeviceInfo *inputDevice, |
| PaSampleFormat hostInputSampleFormat, |
| WORD inputChannelCount, |
| int bytesPerInputBuffer, |
| PaWinWaveFormatChannelMask inputChannelMask, |
| PaWinDsDeviceInfo *outputDevice, |
| PaSampleFormat hostOutputSampleFormat, |
| WORD outputChannelCount, |
| int bytesPerOutputBuffer, |
| PaWinWaveFormatChannelMask outputChannelMask, |
| unsigned long nFrameRate |
| ) |
| { |
| HRESULT hr; |
| DSCBUFFERDESC captureDesc; |
| PaWinWaveFormat captureWaveFormat; |
| DSBUFFERDESC secondaryRenderDesc; |
| PaWinWaveFormat renderWaveFormat; |
| LPDIRECTSOUNDBUFFER8 pRenderBuffer8; |
| LPDIRECTSOUNDCAPTUREBUFFER8 pCaptureBuffer8; |
| |
| // capture buffer description |
| |
| // only try wave format extensible. assume it's available on all ds 8 systems |
| PaWin_InitializeWaveFormatExtensible( &captureWaveFormat, inputChannelCount, |
| hostInputSampleFormat, PaWin_SampleFormatToLinearWaveFormatTag( hostInputSampleFormat ), |
| nFrameRate, inputChannelMask ); |
| |
| ZeroMemory(&captureDesc, sizeof(DSCBUFFERDESC)); |
| captureDesc.dwSize = sizeof(DSCBUFFERDESC); |
| captureDesc.dwFlags = 0; |
| captureDesc.dwBufferBytes = bytesPerInputBuffer; |
| captureDesc.lpwfxFormat = (WAVEFORMATEX*)&captureWaveFormat; |
| |
| // render buffer description |
| |
| PaWin_InitializeWaveFormatExtensible( &renderWaveFormat, outputChannelCount, |
| hostOutputSampleFormat, PaWin_SampleFormatToLinearWaveFormatTag( hostOutputSampleFormat ), |
| nFrameRate, outputChannelMask ); |
| |
| ZeroMemory(&secondaryRenderDesc, sizeof(DSBUFFERDESC)); |
| secondaryRenderDesc.dwSize = sizeof(DSBUFFERDESC); |
| secondaryRenderDesc.dwFlags = DSBCAPS_GLOBALFOCUS | DSBCAPS_GETCURRENTPOSITION2; |
| secondaryRenderDesc.dwBufferBytes = bytesPerOutputBuffer; |
| secondaryRenderDesc.lpwfxFormat = (WAVEFORMATEX*)&renderWaveFormat; |
| |
| /* note that we don't create a primary buffer here at all */ |
| |
| hr = paWinDsDSoundEntryPoints.DirectSoundFullDuplexCreate8( |
| inputDevice->lpGUID, outputDevice->lpGUID, |
| &captureDesc, &secondaryRenderDesc, |
| GetDesktopWindow(), /* see InitOutputBuffer() for a discussion of whether this is a good idea */ |
| DSSCL_EXCLUSIVE, |
| &stream->pDirectSoundFullDuplex8, |
| &pCaptureBuffer8, |
| &pRenderBuffer8, |
| NULL /* pUnkOuter must be NULL */ |
| ); |
| |
| if( hr == DS_OK ) |
| { |
| PA_DEBUG(("DirectSoundFullDuplexCreate succeeded!\n")); |
| |
| /* retrieve the pre ds 8 buffer interfaces which are used by the rest of the code */ |
| |
| hr = IUnknown_QueryInterface( pCaptureBuffer8, &IID_IDirectSoundCaptureBuffer, (LPVOID *)&stream->pDirectSoundInputBuffer ); |
| |
| if( hr == DS_OK ) |
| hr = IUnknown_QueryInterface( pRenderBuffer8, &IID_IDirectSoundBuffer, (LPVOID *)&stream->pDirectSoundOutputBuffer ); |
| |
| /* release the ds 8 interfaces, we don't need them */ |
| IUnknown_Release( pCaptureBuffer8 ); |
| IUnknown_Release( pRenderBuffer8 ); |
| |
| if( !stream->pDirectSoundInputBuffer || !stream->pDirectSoundOutputBuffer ){ |
| /* couldn't get pre ds 8 interfaces for some reason. clean up. */ |
| if( stream->pDirectSoundInputBuffer ) |
| { |
| IUnknown_Release( stream->pDirectSoundInputBuffer ); |
| stream->pDirectSoundInputBuffer = NULL; |
| } |
| |
| if( stream->pDirectSoundOutputBuffer ) |
| { |
| IUnknown_Release( stream->pDirectSoundOutputBuffer ); |
| stream->pDirectSoundOutputBuffer = NULL; |
| } |
| |
| IUnknown_Release( stream->pDirectSoundFullDuplex8 ); |
| stream->pDirectSoundFullDuplex8 = NULL; |
| } |
| } |
| else |
| { |
| PA_DEBUG(("DirectSoundFullDuplexCreate failed. hr=%d\n", hr)); |
| } |
| |
| return hr; |
| } |
| #endif /* PAWIN_USE_DIRECTSOUNDFULLDUPLEXCREATE */ |
| |
| |
| static HRESULT InitInputBuffer( PaWinDsStream *stream, |
| PaWinDsDeviceInfo *device, |
| PaSampleFormat sampleFormat, |
| unsigned long nFrameRate, |
| WORD nChannels, |
| int bytesPerBuffer, |
| PaWinWaveFormatChannelMask channelMask ) |
| { |
| DSCBUFFERDESC captureDesc; |
| PaWinWaveFormat waveFormat; |
| HRESULT result; |
| |
| if( (result = paWinDsDSoundEntryPoints.DirectSoundCaptureCreate( |
| device->lpGUID, &stream->pDirectSoundCapture, NULL) ) != DS_OK ){ |
| ERR_RPT(("PortAudio: DirectSoundCaptureCreate() failed!\n")); |
| return result; |
| } |
| |
| // Setup the secondary buffer description |
| ZeroMemory(&captureDesc, sizeof(DSCBUFFERDESC)); |
| captureDesc.dwSize = sizeof(DSCBUFFERDESC); |
| captureDesc.dwFlags = 0; |
| captureDesc.dwBufferBytes = bytesPerBuffer; |
| captureDesc.lpwfxFormat = (WAVEFORMATEX*)&waveFormat; |
| |
| // Create the capture buffer |
| |
| // first try WAVEFORMATEXTENSIBLE. if this fails, fall back to WAVEFORMATEX |
| PaWin_InitializeWaveFormatExtensible( &waveFormat, nChannels, |
| sampleFormat, PaWin_SampleFormatToLinearWaveFormatTag( sampleFormat ), |
| nFrameRate, channelMask ); |
| |
| if( IDirectSoundCapture_CreateCaptureBuffer( stream->pDirectSoundCapture, |
| &captureDesc, &stream->pDirectSoundInputBuffer, NULL) != DS_OK ) |
| { |
| PaWin_InitializeWaveFormatEx( &waveFormat, nChannels, sampleFormat, |
| PaWin_SampleFormatToLinearWaveFormatTag( sampleFormat ), nFrameRate ); |
| |
| if ((result = IDirectSoundCapture_CreateCaptureBuffer( stream->pDirectSoundCapture, |
| &captureDesc, &stream->pDirectSoundInputBuffer, NULL)) != DS_OK) return result; |
| } |
| |
| stream->readOffset = 0; // reset last read position to start of buffer |
| return DS_OK; |
| } |
| |
| |
| static HRESULT InitOutputBuffer( PaWinDsStream *stream, PaWinDsDeviceInfo *device, |
| PaSampleFormat sampleFormat, unsigned long nFrameRate, |
| WORD nChannels, int bytesPerBuffer, |
| PaWinWaveFormatChannelMask channelMask ) |
| { |
| HRESULT result; |
| HWND hWnd; |
| HRESULT hr; |
| PaWinWaveFormat waveFormat; |
| DSBUFFERDESC primaryDesc; |
| DSBUFFERDESC secondaryDesc; |
| |
| if( (hr = paWinDsDSoundEntryPoints.DirectSoundCreate( |
| device->lpGUID, &stream->pDirectSound, NULL )) != DS_OK ){ |
| ERR_RPT(("PortAudio: DirectSoundCreate() failed!\n")); |
| return hr; |
| } |
| |
| // We were using getForegroundWindow() but sometimes the ForegroundWindow may not be the |
| // applications's window. Also if that window is closed before the Buffer is closed |
| // then DirectSound can crash. (Thanks for Scott Patterson for reporting this.) |
| // So we will use GetDesktopWindow() which was suggested by Miller Puckette. |
| // hWnd = GetForegroundWindow(); |
| // |
| // FIXME: The example code I have on the net creates a hidden window that |
| // is managed by our code - I think we should do that - one hidden |
| // window for the whole of Pa_DS |
| // |
| hWnd = GetDesktopWindow(); |
| |
| // Set cooperative level to DSSCL_EXCLUSIVE so that we can get 16 bit output, 44.1 KHz. |
| // exclusive also prevents unexpected sounds from other apps during a performance. |
| if ((hr = IDirectSound_SetCooperativeLevel( stream->pDirectSound, |
| hWnd, DSSCL_EXCLUSIVE)) != DS_OK) |
| { |
| return hr; |
| } |
| |
| // ----------------------------------------------------------------------- |
| // Create primary buffer and set format just so we can specify our custom format. |
| // Otherwise we would be stuck with the default which might be 8 bit or 22050 Hz. |
| // Setup the primary buffer description |
| ZeroMemory(&primaryDesc, sizeof(DSBUFFERDESC)); |
| primaryDesc.dwSize = sizeof(DSBUFFERDESC); |
| primaryDesc.dwFlags = DSBCAPS_PRIMARYBUFFER; // all panning, mixing, etc done by synth |
| primaryDesc.dwBufferBytes = 0; |
| primaryDesc.lpwfxFormat = NULL; |
| // Create the buffer |
| if ((result = IDirectSound_CreateSoundBuffer( stream->pDirectSound, |
| &primaryDesc, &stream->pDirectSoundPrimaryBuffer, NULL)) != DS_OK) |
| goto error; |
| |
| // Set the primary buffer's format |
| |
| // first try WAVEFORMATEXTENSIBLE. if this fails, fall back to WAVEFORMATEX |
| PaWin_InitializeWaveFormatExtensible( &waveFormat, nChannels, |
| sampleFormat, PaWin_SampleFormatToLinearWaveFormatTag( sampleFormat ), |
| nFrameRate, channelMask ); |
| |
| if( IDirectSoundBuffer_SetFormat( stream->pDirectSoundPrimaryBuffer, (WAVEFORMATEX*)&waveFormat) != DS_OK ) |
| { |
| PaWin_InitializeWaveFormatEx( &waveFormat, nChannels, sampleFormat, |
| PaWin_SampleFormatToLinearWaveFormatTag( sampleFormat ), nFrameRate ); |
| |
| if((result = IDirectSoundBuffer_SetFormat( stream->pDirectSoundPrimaryBuffer, (WAVEFORMATEX*)&waveFormat)) != DS_OK) |
| goto error; |
| } |
| |
| // ---------------------------------------------------------------------- |
| // Setup the secondary buffer description |
| ZeroMemory(&secondaryDesc, sizeof(DSBUFFERDESC)); |
| secondaryDesc.dwSize = sizeof(DSBUFFERDESC); |
| secondaryDesc.dwFlags = DSBCAPS_GLOBALFOCUS | DSBCAPS_GETCURRENTPOSITION2; |
| secondaryDesc.dwBufferBytes = bytesPerBuffer; |
| secondaryDesc.lpwfxFormat = (WAVEFORMATEX*)&waveFormat; /* waveFormat contains whatever format was negotiated for the primary buffer above */ |
| // Create the secondary buffer |
| if ((result = IDirectSound_CreateSoundBuffer( stream->pDirectSound, |
| &secondaryDesc, &stream->pDirectSoundOutputBuffer, NULL)) != DS_OK) |
| goto error; |
| |
| return DS_OK; |
| |
| error: |
| |
| if( stream->pDirectSoundPrimaryBuffer ) |
| { |
| IDirectSoundBuffer_Release( stream->pDirectSoundPrimaryBuffer ); |
| stream->pDirectSoundPrimaryBuffer = NULL; |
| } |
| |
| return result; |
| } |
| |
| |
| static void CalculateBufferSettings( unsigned long *hostBufferSizeFrames, |
| unsigned long *pollingPeriodFrames, |
| int isFullDuplex, |
| unsigned long suggestedInputLatencyFrames, |
| unsigned long suggestedOutputLatencyFrames, |
| double sampleRate, unsigned long userFramesPerBuffer ) |
| { |
| unsigned long minimumPollingPeriodFrames = (unsigned long)(sampleRate * PA_DS_MINIMUM_POLLING_PERIOD_SECONDS); |
| unsigned long maximumPollingPeriodFrames = (unsigned long)(sampleRate * PA_DS_MAXIMUM_POLLING_PERIOD_SECONDS); |
| unsigned long pollingJitterFrames = (unsigned long)(sampleRate * PA_DS_POLLING_JITTER_SECONDS); |
| |
| if( userFramesPerBuffer == paFramesPerBufferUnspecified ) |
| { |
| unsigned long targetBufferingLatencyFrames = max( suggestedInputLatencyFrames, suggestedOutputLatencyFrames ); |
| |
| *pollingPeriodFrames = targetBufferingLatencyFrames / 4; |
| if( *pollingPeriodFrames < minimumPollingPeriodFrames ) |
| { |
| *pollingPeriodFrames = minimumPollingPeriodFrames; |
| } |
| else if( *pollingPeriodFrames > maximumPollingPeriodFrames ) |
| { |
| *pollingPeriodFrames = maximumPollingPeriodFrames; |
| } |
| |
| *hostBufferSizeFrames = *pollingPeriodFrames |
| + max( *pollingPeriodFrames + pollingJitterFrames, targetBufferingLatencyFrames); |
| } |
| else |
| { |
| unsigned long targetBufferingLatencyFrames = suggestedInputLatencyFrames; |
| if( isFullDuplex ) |
| { |
| /* In full duplex streams we know that the buffer adapter adds userFramesPerBuffer |
| extra fixed latency. so we subtract it here as a fixed latency before computing |
| the buffer size. being careful not to produce an unrepresentable negative result. |
| |
| Note: this only works as expected if output latency is greater than input latency. |
| Otherwise we use input latency anyway since we do max(in,out). |
| */ |
| |
| if( userFramesPerBuffer < suggestedOutputLatencyFrames ) |
| { |
| unsigned long adjustedSuggestedOutputLatencyFrames = |
| suggestedOutputLatencyFrames - userFramesPerBuffer; |
| |
| /* maximum of input and adjusted output suggested latency */ |
| if( adjustedSuggestedOutputLatencyFrames > targetBufferingLatencyFrames ) |
| targetBufferingLatencyFrames = adjustedSuggestedOutputLatencyFrames; |
| } |
| } |
| else |
| { |
| /* maximum of input and output suggested latency */ |
| if( suggestedOutputLatencyFrames > suggestedInputLatencyFrames ) |
| targetBufferingLatencyFrames = suggestedOutputLatencyFrames; |
| } |
| |
| *hostBufferSizeFrames = userFramesPerBuffer |
| + max( userFramesPerBuffer + pollingJitterFrames, targetBufferingLatencyFrames); |
| |
| *pollingPeriodFrames = max( max(1, userFramesPerBuffer / 4), targetBufferingLatencyFrames / 16 ); |
| |
| if( *pollingPeriodFrames > maximumPollingPeriodFrames ) |
| { |
| *pollingPeriodFrames = maximumPollingPeriodFrames; |
| } |
| } |
| } |
| |
| |
| static void CalculatePollingPeriodFrames( unsigned long hostBufferSizeFrames, |
| unsigned long *pollingPeriodFrames, |
| double sampleRate, unsigned long userFramesPerBuffer ) |
| { |
| unsigned long minimumPollingPeriodFrames = (unsigned long)(sampleRate * PA_DS_MINIMUM_POLLING_PERIOD_SECONDS); |
| unsigned long maximumPollingPeriodFrames = (unsigned long)(sampleRate * PA_DS_MAXIMUM_POLLING_PERIOD_SECONDS); |
| unsigned long pollingJitterFrames = (unsigned long)(sampleRate * PA_DS_POLLING_JITTER_SECONDS); |
| |
| *pollingPeriodFrames = max( max(1, userFramesPerBuffer / 4), hostBufferSizeFrames / 16 ); |
| |
| if( *pollingPeriodFrames > maximumPollingPeriodFrames ) |
| { |
| *pollingPeriodFrames = maximumPollingPeriodFrames; |
| } |
| } |
| |
| |
| static void SetStreamInfoLatencies( PaWinDsStream *stream, |
| unsigned long userFramesPerBuffer, |
| unsigned long pollingPeriodFrames, |
| double sampleRate ) |
| { |
| /* compute the stream info actual latencies based on framesPerBuffer, polling period, hostBufferSizeFrames, |
| and the configuration of the buffer processor */ |
| |
| unsigned long effectiveFramesPerBuffer = (userFramesPerBuffer == paFramesPerBufferUnspecified) |
| ? pollingPeriodFrames |
| : userFramesPerBuffer; |
| |
| if( stream->bufferProcessor.inputChannelCount > 0 ) |
| { |
| /* 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) |
| + effectiveFramesPerBuffer) / sampleRate; |
| } |
| else |
| { |
| stream->streamRepresentation.streamInfo.inputLatency = 0; |
| } |
| |
| if( stream->bufferProcessor.outputChannelCount > 0 ) |
| { |
| stream->streamRepresentation.streamInfo.outputLatency = |
| (double)(PaUtil_GetBufferProcessorOutputLatencyFrames(&stream->bufferProcessor) |
| + (stream->hostBufferSizeFrames - effectiveFramesPerBuffer)) / sampleRate; |
| } |
| else |
| { |
| stream->streamRepresentation.streamInfo.outputLatency = 0; |
| } |
| } |
| |
| |
| /***********************************************************************************/ |
| /* see pa_hostapi.h for a list of validity guarantees made about OpenStream parameters */ |
| |
| static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, |
| PaStream** s, |
| const PaStreamParameters *inputParameters, |
| const PaStreamParameters *outputParameters, |
| double sampleRate, |
| unsigned long framesPerBuffer, |
| PaStreamFlags streamFlags, |
| PaStreamCallback *streamCallback, |
| void *userData ) |
| { |
| PaError result = paNoError; |
| PaWinDsHostApiRepresentation *winDsHostApi = (PaWinDsHostApiRepresentation*)hostApi; |
| PaWinDsStream *stream = 0; |
| int bufferProcessorIsInitialized = 0; |
| int streamRepresentationIsInitialized = 0; |
| PaWinDsDeviceInfo *inputWinDsDeviceInfo, *outputWinDsDeviceInfo; |
| PaDeviceInfo *inputDeviceInfo, *outputDeviceInfo; |
| int inputChannelCount, outputChannelCount; |
| PaSampleFormat inputSampleFormat, outputSampleFormat; |
| PaSampleFormat hostInputSampleFormat, hostOutputSampleFormat; |
| int userRequestedHostInputBufferSizeFrames = 0; |
| int userRequestedHostOutputBufferSizeFrames = 0; |
| unsigned long suggestedInputLatencyFrames, suggestedOutputLatencyFrames; |
| PaWinDirectSoundStreamInfo *inputStreamInfo, *outputStreamInfo; |
| PaWinWaveFormatChannelMask inputChannelMask, outputChannelMask; |
| unsigned long pollingPeriodFrames = 0; |
| |
| if( inputParameters ) |
| { |
| inputWinDsDeviceInfo = (PaWinDsDeviceInfo*) hostApi->deviceInfos[ inputParameters->device ]; |
| inputDeviceInfo = &inputWinDsDeviceInfo->inheritedDeviceInfo; |
| |
| inputChannelCount = inputParameters->channelCount; |
| inputSampleFormat = inputParameters->sampleFormat; |
| suggestedInputLatencyFrames = (unsigned long)(inputParameters->suggestedLatency * sampleRate); |
| |
| /* IDEA: the following 3 checks could be performed by default by pa_front |
| unless some flag indicated otherwise */ |
| |
| /* unless alternate device specification is supported, reject the use of |
| paUseHostApiSpecificDeviceSpecification */ |
| if( inputParameters->device == paUseHostApiSpecificDeviceSpecification ) |
| return paInvalidDevice; |
| |
| /* check that input device can support inputChannelCount */ |
| if( inputWinDsDeviceInfo->deviceInputChannelCountIsKnown |
| && inputChannelCount > inputDeviceInfo->maxInputChannels ) |
| return paInvalidChannelCount; |
| |
| /* validate hostApiSpecificStreamInfo */ |
| inputStreamInfo = (PaWinDirectSoundStreamInfo*)inputParameters->hostApiSpecificStreamInfo; |
| result = ValidateWinDirectSoundSpecificStreamInfo( inputParameters, inputStreamInfo ); |
| if( result != paNoError ) return result; |
| |
| if( inputStreamInfo && inputStreamInfo->flags & paWinDirectSoundUseLowLevelLatencyParameters ) |
| userRequestedHostInputBufferSizeFrames = inputStreamInfo->framesPerBuffer; |
| |
| if( inputStreamInfo && inputStreamInfo->flags & paWinDirectSoundUseChannelMask ) |
| inputChannelMask = inputStreamInfo->channelMask; |
| else |
| inputChannelMask = PaWin_DefaultChannelMask( inputChannelCount ); |
| } |
| else |
| { |
| inputChannelCount = 0; |
| inputSampleFormat = 0; |
| suggestedInputLatencyFrames = 0; |
| } |
| |
| |
| if( outputParameters ) |
| { |
| outputWinDsDeviceInfo = (PaWinDsDeviceInfo*) hostApi->deviceInfos[ outputParameters->device ]; |
| outputDeviceInfo = &outputWinDsDeviceInfo->inheritedDeviceInfo; |
| |
| outputChannelCount = outputParameters->channelCount; |
| outputSampleFormat = outputParameters->sampleFormat; |
| suggestedOutputLatencyFrames = (unsigned long)(outputParameters->suggestedLatency * sampleRate); |
| |
| /* unless alternate device specification is supported, reject the use of |
| paUseHostApiSpecificDeviceSpecification */ |
| if( outputParameters->device == paUseHostApiSpecificDeviceSpecification ) |
| return paInvalidDevice; |
| |
| /* check that output device can support outputChannelCount */ |
| if( outputWinDsDeviceInfo->deviceOutputChannelCountIsKnown |
| && outputChannelCount > outputDeviceInfo->maxOutputChannels ) |
| return paInvalidChannelCount; |
| |
| /* validate hostApiSpecificStreamInfo */ |
| outputStreamInfo = (PaWinDirectSoundStreamInfo*)outputParameters->hostApiSpecificStreamInfo; |
| result = ValidateWinDirectSoundSpecificStreamInfo( outputParameters, outputStreamInfo ); |
| if( result != paNoError ) return result; |
| |
| if( outputStreamInfo && outputStreamInfo->flags & paWinDirectSoundUseLowLevelLatencyParameters ) |
| userRequestedHostOutputBufferSizeFrames = outputStreamInfo->framesPerBuffer; |
| |
| if( outputStreamInfo && outputStreamInfo->flags & paWinDirectSoundUseChannelMask ) |
| outputChannelMask = outputStreamInfo->channelMask; |
| else |
| outputChannelMask = PaWin_DefaultChannelMask( outputChannelCount ); |
| } |
| else |
| { |
| outputChannelCount = 0; |
| outputSampleFormat = 0; |
| suggestedOutputLatencyFrames = 0; |
| } |
| |
| /* |
| If low level host buffer size is specified for both input and output |
| the current code requires the sizes to match. |
| */ |
| |
| if( (userRequestedHostInputBufferSizeFrames > 0 && userRequestedHostOutputBufferSizeFrames > 0) |
| && userRequestedHostInputBufferSizeFrames != userRequestedHostOutputBufferSizeFrames ) |
| return paIncompatibleHostApiSpecificStreamInfo; |
| |
| |
| |
| /* |
| IMPLEMENT ME: |
| |
| ( the following two checks are taken care of by PaUtil_InitializeBufferProcessor() ) |
| |
| - check that input device can support inputSampleFormat, or that |
| we have the capability to convert from outputSampleFormat to |
| a native format |
| |
| - check that output device can support outputSampleFormat, or that |
| we have the capability to convert from outputSampleFormat to |
| a native format |
| |
| - if a full duplex stream is requested, check that the combination |
| of input and output parameters is supported |
| |
| - check that the device supports sampleRate |
| |
| - alter sampleRate to a close allowable rate if possible / necessary |
| |
| - validate suggestedInputLatency and suggestedOutputLatency parameters, |
| use default values where necessary |
| */ |
| |
| |
| /* validate platform specific flags */ |
| if( (streamFlags & paPlatformSpecificFlags) != 0 ) |
| return paInvalidFlag; /* unexpected platform specific flag */ |
| |
| |
| stream = (PaWinDsStream*)PaUtil_AllocateMemory( sizeof(PaWinDsStream) ); |
| if( !stream ) |
| { |
| result = paInsufficientMemory; |
| goto error; |
| } |
| |
| memset( stream, 0, sizeof(PaWinDsStream) ); /* initialize all stream variables to 0 */ |
| |
| if( streamCallback ) |
| { |
| PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation, |
| &winDsHostApi->callbackStreamInterface, streamCallback, userData ); |
| } |
| else |
| { |
| PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation, |
| &winDsHostApi->blockingStreamInterface, streamCallback, userData ); |
| } |
| |
| streamRepresentationIsInitialized = 1; |
| |
| stream->streamFlags = streamFlags; |
| |
| PaUtil_InitializeCpuLoadMeasurer( &stream->cpuLoadMeasurer, sampleRate ); |
| |
| |
| if( inputParameters ) |
| { |
| /* IMPLEMENT ME - establish which host formats are available */ |
| PaSampleFormat nativeInputFormats = paInt16; |
| /* PaSampleFormat nativeFormats = paUInt8 | paInt16 | paInt24 | paInt32 | paFloat32; */ |
| |
| hostInputSampleFormat = |
| PaUtil_SelectClosestAvailableFormat( nativeInputFormats, inputParameters->sampleFormat ); |
| } |
| else |
| { |
| hostInputSampleFormat = 0; |
| } |
| |
| if( outputParameters ) |
| { |
| /* IMPLEMENT ME - establish which host formats are available */ |
| PaSampleFormat nativeOutputFormats = paInt16; |
| /* PaSampleFormat nativeOutputFormats = paUInt8 | paInt16 | paInt24 | paInt32 | paFloat32; */ |
| |
| hostOutputSampleFormat = |
| PaUtil_SelectClosestAvailableFormat( nativeOutputFormats, outputParameters->sampleFormat ); |
| } |
| else |
| { |
| hostOutputSampleFormat = 0; |
| } |
| |
| result = PaUtil_InitializeBufferProcessor( &stream->bufferProcessor, |
| inputChannelCount, inputSampleFormat, hostInputSampleFormat, |
| outputChannelCount, outputSampleFormat, hostOutputSampleFormat, |
| sampleRate, streamFlags, framesPerBuffer, |
| 0, /* ignored in paUtilVariableHostBufferSizePartialUsageAllowed mode. */ |
| /* This next mode is required because DS can split the host buffer when it wraps around. */ |
| paUtilVariableHostBufferSizePartialUsageAllowed, |
| streamCallback, userData ); |
| if( result != paNoError ) |
| goto error; |
| |
| bufferProcessorIsInitialized = 1; |
| |
| |
| /* DirectSound specific initialization */ |
| { |
| HRESULT hr; |
| unsigned long integerSampleRate = (unsigned long) (sampleRate + 0.5); |
| |
| stream->processingCompleted = CreateEvent( NULL, /* bManualReset = */ TRUE, /* bInitialState = */ FALSE, NULL ); |
| if( stream->processingCompleted == NULL ) |
| { |
| result = paInsufficientMemory; |
| goto error; |
| } |
| |
| #ifdef PA_WIN_DS_USE_WMME_TIMER |
| stream->timerID = 0; |
| #endif |
| |
| #ifdef PA_WIN_DS_USE_WAITABLE_TIMER_OBJECT |
| stream->waitableTimer = (HANDLE)CreateWaitableTimer( 0, FALSE, NULL ); |
| if( stream->waitableTimer == NULL ) |
| { |
| result = paUnanticipatedHostError; |
| PA_DS_SET_LAST_DIRECTSOUND_ERROR( GetLastError() ); |
| goto error; |
| } |
| #endif |
| |
| #ifndef PA_WIN_DS_USE_WMME_TIMER |
| stream->processingThreadCompleted = CreateEvent( NULL, /* bManualReset = */ TRUE, /* bInitialState = */ FALSE, NULL ); |
| if( stream->processingThreadCompleted == NULL ) |
| { |
| result = paUnanticipatedHostError; |
| PA_DS_SET_LAST_DIRECTSOUND_ERROR( GetLastError() ); |
| goto error; |
| } |
| #endif |
| |
| /* set up i/o parameters */ |
| |
| if( userRequestedHostInputBufferSizeFrames > 0 || userRequestedHostOutputBufferSizeFrames > 0 ) |
| { |
| /* use low level parameters */ |
| |
| /* since we use the same host buffer size for input and output |
| we choose the highest user specified value. |
| */ |
| stream->hostBufferSizeFrames = max( userRequestedHostInputBufferSizeFrames, userRequestedHostOutputBufferSizeFrames ); |
| |
| CalculatePollingPeriodFrames( |
| stream->hostBufferSizeFrames, &pollingPeriodFrames, |
| sampleRate, framesPerBuffer ); |
| } |
| else |
| { |
| CalculateBufferSettings( &stream->hostBufferSizeFrames, &pollingPeriodFrames, |
| /* isFullDuplex = */ (inputParameters && outputParameters), |
| suggestedInputLatencyFrames, |
| suggestedOutputLatencyFrames, |
| sampleRate, framesPerBuffer ); |
| } |
| |
| stream->pollingPeriodSeconds = pollingPeriodFrames / sampleRate; |
| |
| DBUG(("DirectSound host buffer size frames: %d, polling period seconds: %f, @ sr: %f\n", |
| stream->hostBufferSizeFrames, stream->pollingPeriodSeconds, sampleRate )); |
| |
| |
| /* ------------------ OUTPUT */ |
| if( outputParameters ) |
| { |
| LARGE_INTEGER counterFrequency; |
| |
| /* |
| PaDeviceInfo *deviceInfo = hostApi->deviceInfos[ outputParameters->device ]; |
| DBUG(("PaHost_OpenStream: deviceID = 0x%x\n", outputParameters->device)); |
| */ |
| |
| int sampleSizeBytes = Pa_GetSampleSize(hostOutputSampleFormat); |
| stream->outputFrameSizeBytes = outputParameters->channelCount * sampleSizeBytes; |
| |
| stream->outputBufferSizeBytes = stream->hostBufferSizeFrames * stream->outputFrameSizeBytes; |
| if( stream->outputBufferSizeBytes < DSBSIZE_MIN ) |
| { |
| result = paBufferTooSmall; |
| goto error; |
| } |
| else if( stream->outputBufferSizeBytes > DSBSIZE_MAX ) |
| { |
| result = paBufferTooBig; |
| goto error; |
| } |
| |
| /* Calculate value used in latency calculation to avoid real-time divides. */ |
| stream->secondsPerHostByte = 1.0 / |
| (stream->bufferProcessor.bytesPerHostOutputSample * |
| outputChannelCount * sampleRate); |
| |
| stream->outputIsRunning = FALSE; |
| stream->outputUnderflowCount = 0; |
| |
| /* perfCounterTicksPerBuffer is used by QueryOutputSpace for overflow detection */ |
| if( QueryPerformanceFrequency( &counterFrequency ) ) |
| { |
| stream->perfCounterTicksPerBuffer.QuadPart = (counterFrequency.QuadPart * stream->hostBufferSizeFrames) / integerSampleRate; |
| } |
| else |
| { |
| stream->perfCounterTicksPerBuffer.QuadPart = 0; |
| } |
| } |
| |
| /* ------------------ INPUT */ |
| if( inputParameters ) |
| { |
| /* |
| PaDeviceInfo *deviceInfo = hostApi->deviceInfos[ inputParameters->device ]; |
| DBUG(("PaHost_OpenStream: deviceID = 0x%x\n", inputParameters->device)); |
| */ |
| |
| int sampleSizeBytes = Pa_GetSampleSize(hostInputSampleFormat); |
| stream->inputFrameSizeBytes = inputParameters->channelCount * sampleSizeBytes; |
| |
| stream->inputBufferSizeBytes = stream->hostBufferSizeFrames * stream->inputFrameSizeBytes; |
| if( stream->inputBufferSizeBytes < DSBSIZE_MIN ) |
| { |
| result = paBufferTooSmall; |
| goto error; |
| } |
| else if( stream->inputBufferSizeBytes > DSBSIZE_MAX ) |
| { |
| result = paBufferTooBig; |
| goto error; |
| } |
| } |
| |
| /* open/create the DirectSound buffers */ |
| |
| /* interface ptrs should be zeroed when stream is zeroed. */ |
| assert( stream->pDirectSoundCapture == NULL ); |
| assert( stream->pDirectSoundInputBuffer == NULL ); |
| assert( stream->pDirectSound == NULL ); |
| assert( stream->pDirectSoundPrimaryBuffer == NULL ); |
| assert( stream->pDirectSoundOutputBuffer == NULL ); |
| |
| |
| if( inputParameters && outputParameters ) |
| { |
| #ifdef PAWIN_USE_DIRECTSOUNDFULLDUPLEXCREATE |
| /* try to use the full-duplex DX8 API to create the buffers. |
| if that fails we fall back to the half-duplex API below */ |
| |
| hr = InitFullDuplexInputOutputBuffers( stream, |
| (PaWinDsDeviceInfo*)hostApi->deviceInfos[inputParameters->device], |
| hostInputSampleFormat, |
| (WORD)inputParameters->channelCount, stream->inputBufferSizeBytes, |
| inputChannelMask, |
| (PaWinDsDeviceInfo*)hostApi->deviceInfos[outputParameters->device], |
| hostOutputSampleFormat, |
| (WORD)outputParameters->channelCount, stream->outputBufferSizeBytes, |
| outputChannelMask, |
| integerSampleRate |
| ); |
| DBUG(("InitFullDuplexInputOutputBuffers() returns %x\n", hr)); |
| /* ignore any error returned by InitFullDuplexInputOutputBuffers. |
| we retry opening the buffers below */ |
| #endif /* PAWIN_USE_DIRECTSOUNDFULLDUPLEXCREATE */ |
| } |
| |
| /* create half duplex buffers. also used for full-duplex streams which didn't |
| succeed when using the full duplex API. that could happen because |
| DX8 or greater isnt installed, the i/o devices aren't the same |
| physical device. etc. |
| */ |
| |
| if( outputParameters && !stream->pDirectSoundOutputBuffer ) |
| { |
| hr = InitOutputBuffer( stream, |
| (PaWinDsDeviceInfo*)hostApi->deviceInfos[outputParameters->device], |
| hostOutputSampleFormat, |
| integerSampleRate, |
| (WORD)outputParameters->channelCount, stream->outputBufferSizeBytes, |
| outputChannelMask ); |
| DBUG(("InitOutputBuffer() returns %x\n", hr)); |
| if( hr != DS_OK ) |
| { |
| result = paUnanticipatedHostError; |
| PA_DS_SET_LAST_DIRECTSOUND_ERROR( hr ); |
| goto error; |
| } |
| } |
| |
| if( inputParameters && !stream->pDirectSoundInputBuffer ) |
| { |
| hr = InitInputBuffer( stream, |
| (PaWinDsDeviceInfo*)hostApi->deviceInfos[inputParameters->device], |
| hostInputSampleFormat, |
| integerSampleRate, |
| (WORD)inputParameters->channelCount, stream->inputBufferSizeBytes, |
| inputChannelMask ); |
| DBUG(("InitInputBuffer() returns %x\n", hr)); |
| if( hr != DS_OK ) |
| { |
| ERR_RPT(("PortAudio: DSW_InitInputBuffer() returns %x\n", hr)); |
| result = paUnanticipatedHostError; |
| PA_DS_SET_LAST_DIRECTSOUND_ERROR( hr ); |
| goto error; |
| } |
| } |
| } |
| |
| SetStreamInfoLatencies( stream, framesPerBuffer, pollingPeriodFrames, sampleRate ); |
| |
| stream->streamRepresentation.streamInfo.sampleRate = sampleRate; |
| |
| *s = (PaStream*)stream; |
| |
| return result; |
| |
| error: |
| if( stream ) |
| { |
| if( stream->processingCompleted != NULL ) |
| CloseHandle( stream->processingCompleted ); |
| |
| #ifdef PA_WIN_DS_USE_WAITABLE_TIMER_OBJECT |
| if( stream->waitableTimer != NULL ) |
| CloseHandle( stream->waitableTimer ); |
| #endif |
| |
| #ifndef PA_WIN_DS_USE_WMME_TIMER |
| if( stream->processingThreadCompleted != NULL ) |
| CloseHandle( stream->processingThreadCompleted ); |
| #endif |
| |
| if( stream->pDirectSoundOutputBuffer ) |
| { |
| IDirectSoundBuffer_Stop( stream->pDirectSoundOutputBuffer ); |
| IDirectSoundBuffer_Release( stream->pDirectSoundOutputBuffer ); |
| stream->pDirectSoundOutputBuffer = NULL; |
| } |
| |
| if( stream->pDirectSoundPrimaryBuffer ) |
| { |
| IDirectSoundBuffer_Release( stream->pDirectSoundPrimaryBuffer ); |
| stream->pDirectSoundPrimaryBuffer = NULL; |
| } |
| |
| if( stream->pDirectSoundInputBuffer ) |
| { |
| IDirectSoundCaptureBuffer_Stop( stream->pDirectSoundInputBuffer ); |
| IDirectSoundCaptureBuffer_Release( stream->pDirectSoundInputBuffer ); |
| stream->pDirectSoundInputBuffer = NULL; |
| } |
| |
| if( stream->pDirectSoundCapture ) |
| { |
| IDirectSoundCapture_Release( stream->pDirectSoundCapture ); |
| stream->pDirectSoundCapture = NULL; |
| } |
| |
| if( stream->pDirectSound ) |
| { |
| IDirectSound_Release( stream->pDirectSound ); |
| stream->pDirectSound = NULL; |
| } |
| |
| #ifdef PAWIN_USE_DIRECTSOUNDFULLDUPLEXCREATE |
| if( stream->pDirectSoundFullDuplex8 ) |
| { |
| IDirectSoundFullDuplex_Release( stream->pDirectSoundFullDuplex8 ); |
| stream->pDirectSoundFullDuplex8 = NULL; |
| } |
| #endif |
| if( bufferProcessorIsInitialized ) |
| PaUtil_TerminateBufferProcessor( &stream->bufferProcessor ); |
| |
| if( streamRepresentationIsInitialized ) |
| PaUtil_TerminateStreamRepresentation( &stream->streamRepresentation ); |
| |
| PaUtil_FreeMemory( stream ); |
| } |
| |
| return result; |
| } |
| |
| |
| /************************************************************************************ |
| * Determine how much space can be safely written to in DS buffer. |
| * Detect underflows and overflows. |
| * Does not allow writing into safety gap maintained by DirectSound. |
| */ |
| static HRESULT QueryOutputSpace( PaWinDsStream *stream, long *bytesEmpty ) |
| { |
| HRESULT hr; |
| DWORD playCursor; |
| DWORD writeCursor; |
| long numBytesEmpty; |
| long playWriteGap; |
| // Query to see how much room is in buffer. |
| hr = IDirectSoundBuffer_GetCurrentPosition( stream->pDirectSoundOutputBuffer, |
| &playCursor, &writeCursor ); |
| if( hr != DS_OK ) |
| { |
| return hr; |
| } |
| |
| // Determine size of gap between playIndex and WriteIndex that we cannot write into. |
| playWriteGap = writeCursor - playCursor; |
| if( playWriteGap < 0 ) playWriteGap += stream->outputBufferSizeBytes; // unwrap |
| |
| /* DirectSound doesn't have a large enough playCursor so we cannot detect wrap-around. */ |
| /* Attempt to detect playCursor wrap-around and correct it. */ |
| if( stream->outputIsRunning && (stream->perfCounterTicksPerBuffer.QuadPart != 0) ) |
| { |
| /* How much time has elapsed since last check. */ |
| LARGE_INTEGER currentTime; |
| LARGE_INTEGER elapsedTime; |
| long bytesPlayed; |
| long bytesExpected; |
| long buffersWrapped; |
| |
| QueryPerformanceCounter( ¤tTime ); |
| elapsedTime.QuadPart = currentTime.QuadPart - stream->previousPlayTime.QuadPart; |
| stream->previousPlayTime = currentTime; |
| |
| /* How many bytes does DirectSound say have been played. */ |
| bytesPlayed = playCursor - stream->previousPlayCursor; |
| if( bytesPlayed < 0 ) bytesPlayed += stream->outputBufferSizeBytes; // unwrap |
| stream->previousPlayCursor = playCursor; |
| |
| /* Calculate how many bytes we would have expected to been played by now. */ |
| bytesExpected = (long) ((elapsedTime.QuadPart * stream->outputBufferSizeBytes) / stream->perfCounterTicksPerBuffer.QuadPart); |
| buffersWrapped = (bytesExpected - bytesPlayed) / stream->outputBufferSizeBytes; |
| if( buffersWrapped > 0 ) |
| { |
| playCursor += (buffersWrapped * stream->outputBufferSizeBytes); |
| bytesPlayed += (buffersWrapped * stream->outputBufferSizeBytes); |
| } |
| } |
| numBytesEmpty = playCursor - stream->outputBufferWriteOffsetBytes; |
| if( numBytesEmpty < 0 ) numBytesEmpty += stream->outputBufferSizeBytes; // unwrap offset |
| |
| /* Have we underflowed? */ |
| if( numBytesEmpty > (stream->outputBufferSizeBytes - playWriteGap) ) |
| { |
| if( stream->outputIsRunning ) |
| { |
| stream->outputUnderflowCount += 1; |
| } |
| |
| /* |
| From MSDN: |
| The write cursor indicates the position at which it is safe |
| to write new data to the buffer. The write cursor always leads the |
| play cursor, typically by about 15 milliseconds' worth of audio |
| data. |
| It is always safe to change data that is behind the position |
| indicated by the lpdwCurrentPlayCursor parameter. |
| */ |
| |
| stream->outputBufferWriteOffsetBytes = writeCursor; |
| numBytesEmpty = stream->outputBufferSizeBytes - playWriteGap; |
| } |
| *bytesEmpty = numBytesEmpty; |
| return hr; |
| } |
| |
| /***********************************************************************************/ |
| static int TimeSlice( PaWinDsStream *stream ) |
| { |
| long numFrames = 0; |
| long bytesEmpty = 0; |
| long bytesFilled = 0; |
| long bytesToXfer = 0; |
| long framesToXfer = 0; /* the number of frames we'll process this tick */ |
| long numInFramesReady = 0; |
| long numOutFramesReady = 0; |
| long bytesProcessed; |
| HRESULT hresult; |
| double outputLatency = 0; |
| double inputLatency = 0; |
| PaStreamCallbackTimeInfo timeInfo = {0,0,0}; |
| |
| /* Input */ |
| LPBYTE lpInBuf1 = NULL; |
| LPBYTE lpInBuf2 = NULL; |
| DWORD dwInSize1 = 0; |
| DWORD dwInSize2 = 0; |
| /* Output */ |
| LPBYTE lpOutBuf1 = NULL; |
| LPBYTE lpOutBuf2 = NULL; |
| DWORD dwOutSize1 = 0; |
| DWORD dwOutSize2 = 0; |
| |
| /* How much input data is available? */ |
| if( stream->bufferProcessor.inputChannelCount > 0 ) |
| { |
| HRESULT hr; |
| DWORD capturePos; |
| DWORD readPos; |
| long filled = 0; |
| // Query to see how much data is in buffer. |
| // We don't need the capture position but sometimes DirectSound doesn't handle NULLS correctly |
| // so let's pass a pointer just to be safe. |
| hr = IDirectSoundCaptureBuffer_GetCurrentPosition( stream->pDirectSoundInputBuffer, &capturePos, &readPos ); |
| if( hr == DS_OK ) |
| { |
| filled = readPos - stream->readOffset; |
| if( filled < 0 ) filled += stream->inputBufferSizeBytes; // unwrap offset |
| bytesFilled = filled; |
| |
| inputLatency = ((double)bytesFilled) * stream->secondsPerHostByte; |
| } |
| // FIXME: what happens if IDirectSoundCaptureBuffer_GetCurrentPosition fails? |
| |
| framesToXfer = numInFramesReady = bytesFilled / stream->inputFrameSizeBytes; |
| |
| /** @todo Check for overflow */ |
| } |
| |
| /* How much output room is available? */ |
| if( stream->bufferProcessor.outputChannelCount > 0 ) |
| { |
| UINT previousUnderflowCount = stream->outputUnderflowCount; |
| QueryOutputSpace( stream, &bytesEmpty ); |
| framesToXfer = numOutFramesReady = bytesEmpty / stream->outputFrameSizeBytes; |
| |
| /* Check for underflow */ |
| /* FIXME QueryOutputSpace should not adjust underflow count as a side effect. |
| A query function should be a const operator on the stream and return a flag on underflow. */ |
| if( stream->outputUnderflowCount != previousUnderflowCount ) |
| stream->callbackFlags |= paOutputUnderflow; |
| |
| /* We are about to compute audio into the first byte of empty space in the output buffer. |
| This audio will reach the DAC after all of the current (non-empty) audio |
| in the buffer has played. Therefore the output time is the current time |
| plus the time it takes to play the non-empty bytes in the buffer, |
| computed here: |
| */ |
| outputLatency = ((double)(stream->outputBufferSizeBytes - bytesEmpty)) * stream->secondsPerHostByte; |
| } |
| |
| /* if it's a full duplex stream, set framesToXfer to the minimum of input and output frames ready */ |
| if( stream->bufferProcessor.inputChannelCount > 0 && stream->bufferProcessor.outputChannelCount > 0 ) |
| { |
| framesToXfer = (numOutFramesReady < numInFramesReady) ? numOutFramesReady : numInFramesReady; |
| } |
| |
| if( framesToXfer > 0 ) |
| { |
| PaUtil_BeginCpuLoadMeasurement( &stream->cpuLoadMeasurer ); |
| |
| /* The outputBufferDacTime parameter should indicates the time at which |
| the first sample of the output buffer is heard at the DACs. */ |
| timeInfo.currentTime = PaUtil_GetTime(); |
| |
| PaUtil_BeginBufferProcessing( &stream->bufferProcessor, &timeInfo, stream->callbackFlags ); |
| stream->callbackFlags = 0; |
| |
| /* Input */ |
| if( stream->bufferProcessor.inputChannelCount > 0 ) |
| { |
| timeInfo.inputBufferAdcTime = timeInfo.currentTime - inputLatency; |
| |
| bytesToXfer = framesToXfer * stream->inputFrameSizeBytes; |
| hresult = IDirectSoundCaptureBuffer_Lock ( stream->pDirectSoundInputBuffer, |
| stream->readOffset, bytesToXfer, |
| (void **) &lpInBuf1, &dwInSize1, |
| (void **) &lpInBuf2, &dwInSize2, 0); |
| if (hresult != DS_OK) |
| { |
| ERR_RPT(("DirectSound IDirectSoundCaptureBuffer_Lock failed, hresult = 0x%x\n",hresult)); |
| /* PA_DS_SET_LAST_DIRECTSOUND_ERROR( hresult ); */ |
| PaUtil_ResetBufferProcessor( &stream->bufferProcessor ); /* flush the buffer processor */ |
| stream->callbackResult = paComplete; |
| goto error2; |
| } |
| |
| numFrames = dwInSize1 / stream->inputFrameSizeBytes; |
| PaUtil_SetInputFrameCount( &stream->bufferProcessor, numFrames ); |
| PaUtil_SetInterleavedInputChannels( &stream->bufferProcessor, 0, lpInBuf1, 0 ); |
| /* Is input split into two regions. */ |
| if( dwInSize2 > 0 ) |
| { |
| numFrames = dwInSize2 / stream->inputFrameSizeBytes; |
| PaUtil_Set2ndInputFrameCount( &stream->bufferProcessor, numFrames ); |
| PaUtil_Set2ndInterleavedInputChannels( &stream->bufferProcessor, 0, lpInBuf2, 0 ); |
| } |
| } |
| |
| /* Output */ |
| if( stream->bufferProcessor.outputChannelCount > 0 ) |
| { |
| /* |
| We don't currently add outputLatency here because it appears to produce worse |
| results than not adding it. Need to do more testing to verify this. |
| */ |
| /* timeInfo.outputBufferDacTime = timeInfo.currentTime + outputLatency; */ |
| timeInfo.outputBufferDacTime = timeInfo.currentTime; |
| |
| bytesToXfer = framesToXfer * stream->outputFrameSizeBytes; |
| hresult = IDirectSoundBuffer_Lock ( stream->pDirectSoundOutputBuffer, |
| stream->outputBufferWriteOffsetBytes, bytesToXfer, |
| (void **) &lpOutBuf1, &dwOutSize1, |
| (void **) &lpOutBuf2, &dwOutSize2, 0); |
| if (hresult != DS_OK) |
| { |
| ERR_RPT(("DirectSound IDirectSoundBuffer_Lock failed, hresult = 0x%x\n",hresult)); |
| /* PA_DS_SET_LAST_DIRECTSOUND_ERROR( hresult ); */ |
| PaUtil_ResetBufferProcessor( &stream->bufferProcessor ); /* flush the buffer processor */ |
| stream->callbackResult = paComplete; |
| goto error1; |
| } |
| |
| numFrames = dwOutSize1 / stream->outputFrameSizeBytes; |
| PaUtil_SetOutputFrameCount( &stream->bufferProcessor, numFrames ); |
| PaUtil_SetInterleavedOutputChannels( &stream->bufferProcessor, 0, lpOutBuf1, 0 ); |
| |
| /* Is output split into two regions. */ |
| if( dwOutSize2 > 0 ) |
| { |
| numFrames = dwOutSize2 / stream->outputFrameSizeBytes; |
| PaUtil_Set2ndOutputFrameCount( &stream->bufferProcessor, numFrames ); |
| PaUtil_Set2ndInterleavedOutputChannels( &stream->bufferProcessor, 0, lpOutBuf2, 0 ); |
| } |
| } |
| |
| numFrames = PaUtil_EndBufferProcessing( &stream->bufferProcessor, &stream->callbackResult ); |
| stream->framesWritten += numFrames; |
| |
| if( stream->bufferProcessor.outputChannelCount > 0 ) |
| { |
| /* FIXME: an underflow could happen here */ |
| |
| /* Update our buffer offset and unlock sound buffer */ |
| bytesProcessed = numFrames * stream->outputFrameSizeBytes; |
| stream->outputBufferWriteOffsetBytes = (stream->outputBufferWriteOffsetBytes + bytesProcessed) % stream->outputBufferSizeBytes; |
| IDirectSoundBuffer_Unlock( stream->pDirectSoundOutputBuffer, lpOutBuf1, dwOutSize1, lpOutBuf2, dwOutSize2); |
| } |
| |
| error1: |
| if( stream->bufferProcessor.inputChannelCount > 0 ) |
| { |
| /* FIXME: an overflow could happen here */ |
| |
| /* Update our buffer offset and unlock sound buffer */ |
| bytesProcessed = numFrames * stream->inputFrameSizeBytes; |
| stream->readOffset = (stream->readOffset + bytesProcessed) % stream->inputBufferSizeBytes; |
| IDirectSoundCaptureBuffer_Unlock( stream->pDirectSoundInputBuffer, lpInBuf1, dwInSize1, lpInBuf2, dwInSize2); |
| } |
| error2: |
| |
| PaUtil_EndCpuLoadMeasurement( &stream->cpuLoadMeasurer, numFrames ); |
| } |
| |
| if( stream->callbackResult == paComplete && !PaUtil_IsBufferProcessorOutputEmpty( &stream->bufferProcessor ) ) |
| { |
| /* don't return completed until the buffer processor has been drained */ |
| return paContinue; |
| } |
| else |
| { |
| return stream->callbackResult; |
| } |
| } |
| /*******************************************************************/ |
| |
| static HRESULT ZeroAvailableOutputSpace( PaWinDsStream *stream ) |
| { |
| HRESULT hr; |
| LPBYTE lpbuf1 = NULL; |
| LPBYTE lpbuf2 = NULL; |
| DWORD dwsize1 = 0; |
| DWORD dwsize2 = 0; |
| long bytesEmpty; |
| hr = QueryOutputSpace( stream, &bytesEmpty ); |
| if (hr != DS_OK) return hr; |
| if( bytesEmpty == 0 ) return DS_OK; |
| // Lock free space in the DS |
| hr = IDirectSoundBuffer_Lock( stream->pDirectSoundOutputBuffer, stream->outputBufferWriteOffsetBytes, |
| bytesEmpty, (void **) &lpbuf1, &dwsize1, |
| (void **) &lpbuf2, &dwsize2, 0); |
| if (hr == DS_OK) |
| { |
| // Copy the buffer into the DS |
| ZeroMemory(lpbuf1, dwsize1); |
| if(lpbuf2 != NULL) |
| { |
| ZeroMemory(lpbuf2, dwsize2); |
| } |
| // Update our buffer offset and unlock sound buffer |
| stream->outputBufferWriteOffsetBytes = (stream->outputBufferWriteOffsetBytes + dwsize1 + dwsize2) % stream->outputBufferSizeBytes; |
| IDirectSoundBuffer_Unlock( stream->pDirectSoundOutputBuffer, lpbuf1, dwsize1, lpbuf2, dwsize2); |
| |
| stream->finalZeroBytesWritten += dwsize1 + dwsize2; |
| } |
| return hr; |
| } |
| |
| |
| static void CALLBACK TimerCallback(UINT uID, UINT uMsg, DWORD_PTR dwUser, DWORD dw1, DWORD dw2) |
| { |
| PaWinDsStream *stream; |
| int isFinished = 0; |
| |
| /* suppress unused variable warnings */ |
| (void) uID; |
| (void) uMsg; |
| (void) dw1; |
| (void) dw2; |
| |
| stream = (PaWinDsStream *) dwUser; |
| if( stream == NULL ) return; |
| |
| if( stream->isActive ) |
| { |
| if( stream->abortProcessing ) |
| { |
| isFinished = 1; |
| } |
| else if( stream->stopProcessing ) |
| { |
| if( stream->bufferProcessor.outputChannelCount > 0 ) |
| { |
| ZeroAvailableOutputSpace( stream ); |
| if( stream->finalZeroBytesWritten >= stream->outputBufferSizeBytes ) |
| { |
| /* once we've flushed the whole output buffer with zeros we know all data has been played */ |
| isFinished = 1; |
| } |
| } |
| else |
| { |
| isFinished = 1; |
| } |
| } |
| else |
| { |
| int callbackResult = TimeSlice( stream ); |
| if( callbackResult != paContinue ) |
| { |
| /* FIXME implement handling of paComplete and paAbort if possible |
| At the moment this should behave as if paComplete was called and |
| flush the buffer. |
| */ |
| |
| stream->stopProcessing = 1; |
| } |
| } |
| |
| if( isFinished ) |
| { |
| if( stream->streamRepresentation.streamFinishedCallback != 0 ) |
| stream->streamRepresentation.streamFinishedCallback( stream->streamRepresentation.userData ); |
| |
| stream->isActive = 0; /* don't set this until the stream really is inactive */ |
| SetEvent( stream->processingCompleted ); |
| } |
| } |
| } |
| |
| #ifndef PA_WIN_DS_USE_WMME_TIMER |
| |
| #ifdef PA_WIN_DS_USE_WAITABLE_TIMER_OBJECT |
| |
| static void CALLBACK WaitableTimerAPCProc( |
| LPVOID lpArg, // Data value |
| DWORD dwTimerLowValue, // Timer low value |
| DWORD dwTimerHighValue ) // Timer high value |
| |
| { |
| (void)dwTimerLowValue; |
| (void)dwTimerHighValue; |
| |
| TimerCallback( 0, 0, (DWORD_PTR)lpArg, 0, 0 ); |
| } |
| |
| #endif /* PA_WIN_DS_USE_WAITABLE_TIMER_OBJECT */ |
| |
| |
| PA_THREAD_FUNC ProcessingThreadProc( void *pArg ) |
| { |
| PaWinDsStream *stream = (PaWinDsStream *)pArg; |
| LARGE_INTEGER dueTime; |
| int timerPeriodMs; |
| |
| timerPeriodMs = (int)(stream->pollingPeriodSeconds * MSECS_PER_SECOND); |
| if( timerPeriodMs < 1 ) |
| timerPeriodMs = 1; |
| |
| #ifdef PA_WIN_DS_USE_WAITABLE_TIMER_OBJECT |
| assert( stream->waitableTimer != NULL ); |
| |
| /* invoke first timeout immediately */ |
| dueTime.LowPart = timerPeriodMs * 1000 * 10; |
| dueTime.HighPart = 0; |
| |
| /* tick using waitable timer */ |
| if( SetWaitableTimer( stream->waitableTimer, &dueTime, timerPeriodMs, WaitableTimerAPCProc, pArg, FALSE ) != 0 ) |
| { |
| DWORD wfsoResult = 0; |
| do |
| { |
| /* wait for processingCompleted to be signaled or our timer APC to be called */ |
| wfsoResult = WaitForSingleObjectEx( stream->processingCompleted, timerPeriodMs * 10, /* alertable = */ TRUE ); |
| |
| }while( wfsoResult == WAIT_TIMEOUT || wfsoResult == WAIT_IO_COMPLETION ); |
| } |
| |
| CancelWaitableTimer( stream->waitableTimer ); |
| |
| #else |
| |
| /* tick using WaitForSingleObject timout */ |
| while ( WaitForSingleObject( stream->processingCompleted, timerPeriodMs ) == WAIT_TIMEOUT ) |
| { |
| TimerCallback( 0, 0, (DWORD_PTR)pArg, 0, 0 ); |
| } |
| #endif /* PA_WIN_DS_USE_WAITABLE_TIMER_OBJECT */ |
| |
| SetEvent( stream->processingThreadCompleted ); |
| |
| return 0; |
| } |
| |
| #endif /* !PA_WIN_DS_USE_WMME_TIMER */ |
| |
| /*********************************************************************************** |
| When CloseStream() is called, the multi-api layer ensures that |
| the stream has already been stopped or aborted. |
| */ |
| static PaError CloseStream( PaStream* s ) |
| { |
| PaError result = paNoError; |
| PaWinDsStream *stream = (PaWinDsStream*)s; |
| |
| CloseHandle( stream->processingCompleted ); |
| |
| #ifdef PA_WIN_DS_USE_WAITABLE_TIMER_OBJECT |
| if( stream->waitableTimer != NULL ) |
| CloseHandle( stream->waitableTimer ); |
| #endif |
| |
| #ifndef PA_WIN_DS_USE_WMME_TIMER |
| CloseHandle( stream->processingThreadCompleted ); |
| #endif |
| |
| // Cleanup the sound buffers |
| if( stream->pDirectSoundOutputBuffer ) |
| { |
| IDirectSoundBuffer_Stop( stream->pDirectSoundOutputBuffer ); |
| IDirectSoundBuffer_Release( stream->pDirectSoundOutputBuffer ); |
| stream->pDirectSoundOutputBuffer = NULL; |
| } |
| |
| if( stream->pDirectSoundPrimaryBuffer ) |
| { |
| IDirectSoundBuffer_Release( stream->pDirectSoundPrimaryBuffer ); |
| stream->pDirectSoundPrimaryBuffer = NULL; |
| } |
| |
| if( stream->pDirectSoundInputBuffer ) |
| { |
| IDirectSoundCaptureBuffer_Stop( stream->pDirectSoundInputBuffer ); |
| IDirectSoundCaptureBuffer_Release( stream->pDirectSoundInputBuffer ); |
| stream->pDirectSoundInputBuffer = NULL; |
| } |
| |
| if( stream->pDirectSoundCapture ) |
| { |
| IDirectSoundCapture_Release( stream->pDirectSoundCapture ); |
| stream->pDirectSoundCapture = NULL; |
| } |
| |
| if( stream->pDirectSound ) |
| { |
| IDirectSound_Release( stream->pDirectSound ); |
| stream->pDirectSound = NULL; |
| } |
| |
| #ifdef PAWIN_USE_DIRECTSOUNDFULLDUPLEXCREATE |
| if( stream->pDirectSoundFullDuplex8 ) |
| { |
| IDirectSoundFullDuplex_Release( stream->pDirectSoundFullDuplex8 ); |
| stream->pDirectSoundFullDuplex8 = NULL; |
| } |
| #endif |
| |
| PaUtil_TerminateBufferProcessor( &stream->bufferProcessor ); |
| PaUtil_TerminateStreamRepresentation( &stream->streamRepresentation ); |
| PaUtil_FreeMemory( stream ); |
| |
| return result; |
| } |
| |
| /***********************************************************************************/ |
| static HRESULT ClearOutputBuffer( PaWinDsStream *stream ) |
| { |
| PaError result = paNoError; |
| unsigned char* pDSBuffData; |
| DWORD dwDataLen; |
| HRESULT hr; |
| |
| hr = IDirectSoundBuffer_SetCurrentPosition( stream->pDirectSoundOutputBuffer, 0 ); |
| DBUG(("PaHost_ClearOutputBuffer: IDirectSoundBuffer_SetCurrentPosition returned = 0x%X.\n", hr)); |
| if( hr != DS_OK ) |
| return hr; |
| |
| // Lock the DS buffer |
| if ((hr = IDirectSoundBuffer_Lock( stream->pDirectSoundOutputBuffer, 0, stream->outputBufferSizeBytes, (LPVOID*)&pDSBuffData, |
| &dwDataLen, NULL, 0, 0)) != DS_OK ) |
| return hr; |
| |
| // Zero the DS buffer |
| ZeroMemory(pDSBuffData, dwDataLen); |
| // Unlock the DS buffer |
| if ((hr = IDirectSoundBuffer_Unlock( stream->pDirectSoundOutputBuffer, pDSBuffData, dwDataLen, NULL, 0)) != DS_OK) |
| return hr; |
| |
| // Let DSound set the starting write position because if we set it to zero, it looks like the |
| // buffer is full to begin with. This causes a long pause before sound starts when using large buffers. |
| if ((hr = IDirectSoundBuffer_GetCurrentPosition( stream->pDirectSoundOutputBuffer, |
| &stream->previousPlayCursor, &stream->outputBufferWriteOffsetBytes )) != DS_OK) |
| return hr; |
| |
| /* printf("DSW_InitOutputBuffer: playCursor = %d, writeCursor = %d\n", playCursor, dsw->dsw_WriteOffset ); */ |
| |
| return DS_OK; |
| } |
| |
| static PaError StartStream( PaStream *s ) |
| { |
| PaError result = paNoError; |
| PaWinDsStream *stream = (PaWinDsStream*)s; |
| HRESULT hr; |
| |
| stream->callbackResult = paContinue; |
| PaUtil_ResetBufferProcessor( &stream->bufferProcessor ); |
| |
| ResetEvent( stream->processingCompleted ); |
| |
| #ifndef PA_WIN_DS_USE_WMME_TIMER |
| ResetEvent( stream->processingThreadCompleted ); |
| #endif |
| |
| if( stream->bufferProcessor.inputChannelCount > 0 ) |
| { |
| // Start the buffer capture |
| if( stream->pDirectSoundInputBuffer != NULL ) // FIXME: not sure this check is necessary |
| { |
| hr = IDirectSoundCaptureBuffer_Start( stream->pDirectSoundInputBuffer, DSCBSTART_LOOPING ); |
| } |
| |
| DBUG(("StartStream: DSW_StartInput returned = 0x%X.\n", hr)); |
| if( hr != DS_OK ) |
| { |
| result = paUnanticipatedHostError; |
| PA_DS_SET_LAST_DIRECTSOUND_ERROR( hr ); |
| goto error; |
| } |
| } |
| |
| stream->framesWritten = 0; |
| stream->callbackFlags = 0; |
| |
| stream->abortProcessing = 0; |
| stream->stopProcessing = 0; |
| |
| if( stream->bufferProcessor.outputChannelCount > 0 ) |
| { |
| QueryPerformanceCounter( &stream->previousPlayTime ); |
| stream->finalZeroBytesWritten = 0; |
| |
| hr = ClearOutputBuffer( stream ); |
| if( hr != DS_OK ) |
| { |
| result = paUnanticipatedHostError; |
| PA_DS_SET_LAST_DIRECTSOUND_ERROR( hr ); |
| goto error; |
| } |
| |
| if( stream->streamRepresentation.streamCallback && (stream->streamFlags & paPrimeOutputBuffersUsingStreamCallback) ) |
| { |
| stream->callbackFlags = paPrimingOutput; |
| |
| TimeSlice( stream ); |
| /* we ignore the return value from TimeSlice here and start the stream as usual. |
| The first timer callback will detect if the callback has completed. */ |
| |
| stream->callbackFlags = 0; |
| } |
| |
| // Start the buffer playback in a loop. |
| if( stream->pDirectSoundOutputBuffer != NULL ) // FIXME: not sure this needs to be checked here |
| { |
| hr = IDirectSoundBuffer_Play( stream->pDirectSoundOutputBuffer, 0, 0, DSBPLAY_LOOPING ); |
| DBUG(("PaHost_StartOutput: IDirectSoundBuffer_Play returned = 0x%X.\n", hr)); |
| if( hr != DS_OK ) |
| { |
| result = paUnanticipatedHostError; |
| PA_DS_SET_LAST_DIRECTSOUND_ERROR( hr ); |
| goto error; |
| } |
| stream->outputIsRunning = TRUE; |
| } |
| } |
| |
| if( stream->streamRepresentation.streamCallback ) |
| { |
| TIMECAPS timecaps; |
| int timerPeriodMs = (int)(stream->pollingPeriodSeconds * MSECS_PER_SECOND); |
| if( timerPeriodMs < 1 ) |
| timerPeriodMs = 1; |
| |
| /* set windows scheduler granularity only as fine as needed, no finer */ |
| /* Although this is not fully documented by MS, it appears that |
| timeBeginPeriod() affects the scheduling granulatity of all timers |
| including Waitable Timer Objects. So we always call timeBeginPeriod, whether |
| we're using an MM timer callback via timeSetEvent or not. |
| */ |
| assert( stream->systemTimerResolutionPeriodMs == 0 ); |
| if( timeGetDevCaps( &timecaps, sizeof(TIMECAPS) ) == MMSYSERR_NOERROR && timecaps.wPeriodMin > 0 ) |
| { |
| /* aim for resolution 4 times higher than polling rate */ |
| stream->systemTimerResolutionPeriodMs = (UINT)((stream->pollingPeriodSeconds * MSECS_PER_SECOND) * .25); |
| if( stream->systemTimerResolutionPeriodMs < timecaps.wPeriodMin ) |
| stream->systemTimerResolutionPeriodMs = timecaps.wPeriodMin; |
| if( stream->systemTimerResolutionPeriodMs > timecaps.wPeriodMax ) |
| stream->systemTimerResolutionPeriodMs = timecaps.wPeriodMax; |
| |
| if( timeBeginPeriod( stream->systemTimerResolutionPeriodMs ) != MMSYSERR_NOERROR ) |
| stream->systemTimerResolutionPeriodMs = 0; /* timeBeginPeriod failed, so we don't need to call timeEndPeriod() later */ |
| } |
| |
| |
| #ifdef PA_WIN_DS_USE_WMME_TIMER |
| /* Create timer that will wake us up so we can fill the DSound buffer. */ |
| /* We have deprecated timeSetEvent because all MM timer callbacks |
| are serialised onto a single thread. Which creates problems with multiple |
| PA streams, or when also using timers for other time critical tasks |
| */ |
| stream->timerID = timeSetEvent( timerPeriodMs, stream->systemTimerResolutionPeriodMs, (LPTIMECALLBACK) TimerCallback, |
| (DWORD_PTR) stream, TIME_PERIODIC | TIME_KILL_SYNCHRONOUS ); |
| |
| if( stream->timerID == 0 ) |
| { |
| stream->isActive = 0; |
| result = paUnanticipatedHostError; |
| PA_DS_SET_LAST_DIRECTSOUND_ERROR( GetLastError() ); |
| goto error; |
| } |
| #else |
| /* Create processing thread which calls TimerCallback */ |
| |
| stream->processingThread = CREATE_THREAD( 0, 0, ProcessingThreadProc, stream, 0, &stream->processingThreadId ); |
| if( !stream->processingThread ) |
| { |
| result = paUnanticipatedHostError; |
| PA_DS_SET_LAST_DIRECTSOUND_ERROR( GetLastError() ); |
| goto error; |
| } |
| |
| if( !SetThreadPriority( stream->processingThread, THREAD_PRIORITY_TIME_CRITICAL ) ) |
| { |
| result = paUnanticipatedHostError; |
| PA_DS_SET_LAST_DIRECTSOUND_ERROR( GetLastError() ); |
| goto error; |
| } |
| #endif |
| } |
| |
| stream->isActive = 1; |
| stream->isStarted = 1; |
| |
| assert( result == paNoError ); |
| return result; |
| |
| error: |
| |
| if( stream->pDirectSoundOutputBuffer != NULL && stream->outputIsRunning ) |
| IDirectSoundBuffer_Stop( stream->pDirectSoundOutputBuffer ); |
| stream->outputIsRunning = FALSE; |
| |
| #ifndef PA_WIN_DS_USE_WMME_TIMER |
| if( stream->processingThread ) |
| { |
| #ifdef CLOSE_THREAD_HANDLE |
| CLOSE_THREAD_HANDLE( stream->processingThread ); /* Delete thread. */ |
| #endif |
| stream->processingThread = NULL; |
| } |
| #endif |
| |
| return result; |
| } |
| |
| |
| /***********************************************************************************/ |
| static PaError StopStream( PaStream *s ) |
| { |
| PaError result = paNoError; |
| PaWinDsStream *stream = (PaWinDsStream*)s; |
| HRESULT hr; |
| int timeoutMsec; |
| |
| if( stream->streamRepresentation.streamCallback ) |
| { |
| stream->stopProcessing = 1; |
| |
| /* Set timeout at 4 times maximum time we might wait. */ |
| timeoutMsec = (int) (4 * MSECS_PER_SECOND * (stream->hostBufferSizeFrames / stream->streamRepresentation.streamInfo.sampleRate)); |
| |
| WaitForSingleObject( stream->processingCompleted, timeoutMsec ); |
| } |
| |
| #ifdef PA_WIN_DS_USE_WMME_TIMER |
| if( stream->timerID != 0 ) |
| { |
| timeKillEvent(stream->timerID); /* Stop callback timer. */ |
| stream->timerID = 0; |
| } |
| #else |
| if( stream->processingThread ) |
| { |
| if( WaitForSingleObject( stream->processingThreadCompleted, 30*100 ) == WAIT_TIMEOUT ) |
| return paUnanticipatedHostError; |
| |
| #ifdef CLOSE_THREAD_HANDLE |
| CloseHandle( stream->processingThread ); /* Delete thread. */ |
| stream->processingThread = NULL; |
| #endif |
| |
| } |
| #endif |
| |
| if( stream->systemTimerResolutionPeriodMs > 0 ){ |
| timeEndPeriod( stream->systemTimerResolutionPeriodMs ); |
| stream->systemTimerResolutionPeriodMs = 0; |
| } |
| |
| if( stream->bufferProcessor.outputChannelCount > 0 ) |
| { |
| // Stop the buffer playback |
| if( stream->pDirectSoundOutputBuffer != NULL ) |
| { |
| stream->outputIsRunning = FALSE; |
| // FIXME: what happens if IDirectSoundBuffer_Stop returns an error? |
| hr = IDirectSoundBuffer_Stop( stream->pDirectSoundOutputBuffer ); |
| |
| if( stream->pDirectSoundPrimaryBuffer ) |
| IDirectSoundBuffer_Stop( stream->pDirectSoundPrimaryBuffer ); /* FIXME we never started the primary buffer so I'm not sure we need to stop it */ |
| } |
| } |
| |
| if( stream->bufferProcessor.inputChannelCount > 0 ) |
| { |
| // Stop the buffer capture |
| if( stream->pDirectSoundInputBuffer != NULL ) |
| { |
| // FIXME: what happens if IDirectSoundCaptureBuffer_Stop returns an error? |
| hr = IDirectSoundCaptureBuffer_Stop( stream->pDirectSoundInputBuffer ); |
| } |
| } |
| |
| stream->isStarted = 0; |
| |
| return result; |
| } |
| |
| |
| /***********************************************************************************/ |
| static PaError AbortStream( PaStream *s ) |
| { |
| PaWinDsStream *stream = (PaWinDsStream*)s; |
| |
| stream->abortProcessing = 1; |
| return StopStream( s ); |
| } |
| |
| |
| /***********************************************************************************/ |
| static PaError IsStreamStopped( PaStream *s ) |
| { |
| PaWinDsStream *stream = (PaWinDsStream*)s; |
| |
| return !stream->isStarted; |
| } |
| |
| |
| /***********************************************************************************/ |
| static PaError IsStreamActive( PaStream *s ) |
| { |
| PaWinDsStream *stream = (PaWinDsStream*)s; |
| |
| return stream->isActive; |
| } |
| |
| /***********************************************************************************/ |
| static PaTime GetStreamTime( PaStream *s ) |
| { |
| /* suppress unused variable warnings */ |
| (void) s; |
| |
| return PaUtil_GetTime(); |
| } |
| |
| |
| /***********************************************************************************/ |
| static double GetStreamCpuLoad( PaStream* s ) |
| { |
| PaWinDsStream *stream = (PaWinDsStream*)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 ) |
| { |
| PaWinDsStream *stream = (PaWinDsStream*)s; |
| |
| /* suppress unused variable warnings */ |
| (void) buffer; |
| (void) frames; |
| (void) stream; |
| |
| /* IMPLEMENT ME, see portaudio.h for required behavior*/ |
| |
| return paNoError; |
| } |
| |
| |
| /***********************************************************************************/ |
| static PaError WriteStream( PaStream* s, |
| const void *buffer, |
| unsigned long frames ) |
| { |
| PaWinDsStream *stream = (PaWinDsStream*)s; |
| |
| /* suppress unused variable warnings */ |
| (void) buffer; |
| (void) frames; |
| (void) stream; |
| |
| /* IMPLEMENT ME, see portaudio.h for required behavior*/ |
| |
| return paNoError; |
| } |
| |
| |
| /***********************************************************************************/ |
| static signed long GetStreamReadAvailable( PaStream* s ) |
| { |
| PaWinDsStream *stream = (PaWinDsStream*)s; |
| |
| /* suppress unused variable warnings */ |
| (void) stream; |
| |
| /* IMPLEMENT ME, see portaudio.h for required behavior*/ |
| |
| return 0; |
| } |
| |
| |
| /***********************************************************************************/ |
| static signed long GetStreamWriteAvailable( PaStream* s ) |
| { |
| PaWinDsStream *stream = (PaWinDsStream*)s; |
| |
| /* suppress unused variable warnings */ |
| (void) stream; |
| |
| /* IMPLEMENT ME, see portaudio.h for required behavior*/ |
| |
| return 0; |
| } |
| |