diff options
Diffstat (limited to 'src/engine/external/portaudio/pa_win_ds.c')
| -rw-r--r-- | src/engine/external/portaudio/pa_win_ds.c | 2197 |
1 files changed, 2197 insertions, 0 deletions
diff --git a/src/engine/external/portaudio/pa_win_ds.c b/src/engine/external/portaudio/pa_win_ds.c new file mode 100644 index 00000000..09b29577 --- /dev/null +++ b/src/engine/external/portaudio/pa_win_ds.c @@ -0,0 +1,2197 @@ +/* + * $Id: pa_win_ds.c 1229 2007-06-15 16:11:11Z rossb $ + * 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-2006 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 hostaip_src + + @todo implement paInputOverflow callback status flag + + @todo implement paNeverDropInput. + + @todo implement host api specific extension to set i/o buffer sizes in frames + + @todo implement initialisation of PaDeviceInfo default*Latency fields (currently set to 0.) + + @todo implement ReadStream, WriteStream, GetStreamReadAvailable, GetStreamWriteAvailable + + @todo audit handling of DirectSound result codes - in many cases we could convert a HRESULT into + a native portaudio error code. Standard DirectSound result codes are documented at msdn. + + @todo implement IsFormatSupported + + @todo check that CoInitialize() CoUninitialize() are always correctly + paired, even in error cases. + + @todo call PaUtil_SetLastHostErrorInfo with a specific error string (currently just "DSound error"). + + @todo make sure all buffers have been played before stopping the stream + when the stream callback returns paComplete + + old TODOs from phil, need to work out if these have been done: + O- fix "patest_stop.c" +*/ + +#include <stdio.h> +#include <string.h> /* strlen() */ + +#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_dynlink.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" ) +#endif + +/* + provided in newer platform sdks and x64 + */ +#ifndef DWORD_PTR +#define DWORD_PTR DWORD +#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_WIN_9X_LATENCY (500) +#define PA_WIN_NT_LATENCY (600) +#else +#define PA_WIN_9X_LATENCY (140) +#define PA_WIN_NT_LATENCY (280) +#endif + +#define PA_WIN_WDM_LATENCY (120) + +#define SECONDS_PER_MSEC (0.001) +#define MSEC_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 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 CollectGUIDsProc(LPGUID lpGUID, + LPCTSTR lpszDesc, + LPCTSTR lpszDrvName, + LPVOID lpContext ); + +/************************************************************************************/ +/********************** Structures **************************************************/ +/************************************************************************************/ +/* PaWinDsHostApiRepresentation - host api datastructure specific to this implementation */ + +typedef struct PaWinDsDeviceInfo +{ + GUID guid; + GUID *lpGUID; + double sampleRates[3]; +} PaWinDsDeviceInfo; + +typedef struct +{ + PaUtilHostApiRepresentation inheritedHostApiRep; + PaUtilStreamInterface callbackStreamInterface; + PaUtilStreamInterface blockingStreamInterface; + + PaUtilAllocationGroup *allocations; + + /* implementation specific data goes here */ + PaWinDsDeviceInfo *winDsDeviceInfos; + +} PaWinDsHostApiRepresentation; + + +/* PaWinDsStream - a stream data structure specifically for this implementation */ + +typedef struct PaWinDsStream +{ + PaUtilStreamRepresentation streamRepresentation; + PaUtilCpuLoadMeasurer cpuLoadMeasurer; + PaUtilBufferProcessor bufferProcessor; + +/* DirectSound specific data. */ + +/* Output */ + LPDIRECTSOUND pDirectSound; + LPDIRECTSOUNDBUFFER pDirectSoundOutputBuffer; + DWORD outputBufferWriteOffsetBytes; /* last write position */ + INT outputBufferSizeBytes; + INT bytesPerOutputFrame; + /* Try to detect play buffer underflows. */ + LARGE_INTEGER perfCounterTicksPerBuffer; /* counter ticks it should take to play a full buffer */ + LARGE_INTEGER previousPlayTime; + UINT previousPlayCursor; + UINT outputUnderflowCount; + BOOL outputIsRunning; + /* use double which lets us can play for several thousand years with enough precision */ + double dsw_framesWritten; + double framesPlayed; +/* Input */ + LPDIRECTSOUNDCAPTURE pDirectSoundCapture; + LPDIRECTSOUNDCAPTUREBUFFER pDirectSoundInputBuffer; + INT bytesPerInputFrame; + UINT readOffset; /* last read position */ + UINT inputSize; + + + MMRESULT timerID; + int framesPerDSBuffer; + double framesWritten; + double secondsPerHostByte; /* Used to optimize latency calculation for outTime */ + + PaStreamCallbackFlags callbackFlags; + +/* 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 */ +} PaWinDsStream; + + +/************************************************************************************ +** Duplicate the input string using the 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 char* src ) +{ + char *result = 0; + + if( src != NULL ) + { + size_t len = strlen(src); + result = (char*)PaUtil_GroupAllocateMemory( allocations, (long)(len + 1) ); + if( result ) + memcpy( (void *) result, src, len+1 ); + } + 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; +} DSDeviceNameAndGUID; + +typedef struct DSDeviceNameAndGUIDVector{ + PaUtilAllocationGroup *allocations; + PaError enumerationError; + + int count; + int free; + DSDeviceNameAndGUID *items; // Allocated using LocalAlloc() +} DSDeviceNameAndGUIDVector; + +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) );; + } + } + + 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 CollectGUIDsProc(LPGUID lpGUID, + LPCTSTR lpszDesc, + LPCTSTR 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->count; + --namesAndGUIDs->free; + + return TRUE; +} + + +/* + 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( + PaWinDsHostApiRepresentation *winDsHostApi, char *name, LPGUID lpGUID ) +{ + PaUtilHostApiRepresentation *hostApi = &winDsHostApi->inheritedHostApiRep; + PaDeviceInfo *deviceInfo = hostApi->deviceInfos[hostApi->info.deviceCount]; + PaWinDsDeviceInfo *winDsDeviceInfo = &winDsHostApi->winDsDeviceInfos[hostApi->info.deviceCount]; + HRESULT hr; + LPDIRECTSOUND lpDirectSound; + DSCAPS caps; + int deviceOK = TRUE; + PaError result = paNoError; + int i; + + /* 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; + } + + + if( lpGUID ) + { + if (IsEqualGUID (&IID_IRolandVSCEmulated1,lpGUID) || + IsEqualGUID (&IID_IRolandVSCEmulated2,lpGUID) ) + { + PA_DEBUG(("BLACKLISTED: %s \n",name)); + return paNoError; + } + } + + /* 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])); + + deviceOK = FALSE; + } + 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 )); + deviceOK = FALSE; + } + else + { + +#ifndef PA_NO_WMME + if( caps.dwFlags & DSCAPS_EMULDRIVER ) + { + /* If WMME supported, then reject Emulated drivers because they are lousy. */ + deviceOK = FALSE; + } +#endif + + if( deviceOK ) + { + deviceInfo->maxInputChannels = 0; + /* Mono or stereo device? */ + deviceInfo->maxOutputChannels = ( caps.dwFlags & DSCAPS_PRIMARYSTEREO ) ? 2 : 1; + + deviceInfo->defaultLowInputLatency = 0.; /** @todo IMPLEMENT ME */ + deviceInfo->defaultLowOutputLatency = 0.; /** @todo IMPLEMENT ME */ + deviceInfo->defaultHighInputLatency = 0.; /** @todo IMPLEMENT ME */ + deviceInfo->defaultHighOutputLatency = 0.; /** @todo IMPLEMENT ME */ + + /* 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 = 44100.0f; + + 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 = 44100.0f; + 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 + } + } + + IDirectSound_Release( lpDirectSound ); + } + + if( deviceOK ) + { + deviceInfo->name = name; + + if( lpGUID == NULL ) + hostApi->info.defaultOutputDevice = hostApi->info.deviceCount; + + hostApi->info.deviceCount++; + } + + 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( + PaWinDsHostApiRepresentation *winDsHostApi, char *name, LPGUID lpGUID ) +{ + PaUtilHostApiRepresentation *hostApi = &winDsHostApi->inheritedHostApiRep; + PaDeviceInfo *deviceInfo = hostApi->deviceInfos[hostApi->info.deviceCount]; + PaWinDsDeviceInfo *winDsDeviceInfo = &winDsHostApi->winDsDeviceInfos[hostApi->info.deviceCount]; + HRESULT hr; + LPDIRECTSOUNDCAPTURE lpDirectSoundCapture; + DSCCAPS caps; + int deviceOK = TRUE; + PaError result = paNoError; + + /* 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) ); + } + + + 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 )); + deviceOK = FALSE; + } + 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 )); + deviceOK = FALSE; + } + else + { +#ifndef PA_NO_WMME + if( caps.dwFlags & DSCAPS_EMULDRIVER ) + { + /* If WMME supported, then reject Emulated drivers because they are lousy. */ + deviceOK = FALSE; + } +#endif + + if( deviceOK ) + { + deviceInfo->maxInputChannels = caps.dwChannels; + deviceInfo->maxOutputChannels = 0; + + deviceInfo->defaultLowInputLatency = 0.; /** @todo IMPLEMENT ME */ + deviceInfo->defaultLowOutputLatency = 0.; /** @todo IMPLEMENT ME */ + deviceInfo->defaultHighInputLatency = 0.; /** @todo IMPLEMENT ME */ + deviceInfo->defaultHighOutputLatency = 0.; /** @todo IMPLEMENT ME */ + +/* 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 = 0.; + } + 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 = 0.; + } + else deviceInfo->defaultSampleRate = 0.; + } + } + + IDirectSoundCapture_Release( lpDirectSoundCapture ); + } + + if( deviceOK ) + { + deviceInfo->name = name; + + if( lpGUID == NULL ) + hostApi->info.defaultInputDevice = hostApi->info.deviceCount; + + hostApi->info.deviceCount++; + } + + return result; +} + + +/***********************************************************************************/ +PaError PaWinDs_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex hostApiIndex ) +{ + PaError result = paNoError; + int i, deviceCount; + PaWinDsHostApiRepresentation *winDsHostApi; + DSDeviceNameAndGUIDVector inputNamesAndGUIDs, outputNamesAndGUIDs; + PaDeviceInfo *deviceInfoArray; + + HRESULT hr = CoInitialize(NULL); /** @todo: should uninitialize too */ + if( FAILED(hr) ){ + return paUnanticipatedHostError; + } + + /* initialise guid vectors so they can be safely deleted on error */ + inputNamesAndGUIDs.items = NULL; + outputNamesAndGUIDs.items = NULL; + + PaWinDs_InitializeDSoundEntryPoints(); + + winDsHostApi = (PaWinDsHostApiRepresentation*)PaUtil_AllocateMemory( sizeof(PaWinDsHostApiRepresentation) ); + if( !winDsHostApi ) + { + result = paInsufficientMemory; + 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"; + + (*hostApi)->info.deviceCount = 0; + (*hostApi)->info.defaultInputDevice = paNoDevice; + (*hostApi)->info.defaultOutputDevice = paNoDevice; + + +/* DSound - enumerate devices to count them and to gather their GUIDs */ + + + result = InitializeDSDeviceNameAndGUIDVector( &inputNamesAndGUIDs, winDsHostApi->allocations ); + if( result != paNoError ) + goto error; + + result = InitializeDSDeviceNameAndGUIDVector( &outputNamesAndGUIDs, winDsHostApi->allocations ); + if( result != paNoError ) + goto error; + + paWinDsDSoundEntryPoints.DirectSoundCaptureEnumerate( (LPDSENUMCALLBACK)CollectGUIDsProc, (void *)&inputNamesAndGUIDs ); + + paWinDsDSoundEntryPoints.DirectSoundEnumerate( (LPDSENUMCALLBACK)CollectGUIDsProc, (void *)&outputNamesAndGUIDs ); + + if( inputNamesAndGUIDs.enumerationError != paNoError ) + { + result = inputNamesAndGUIDs.enumerationError; + goto error; + } + + if( outputNamesAndGUIDs.enumerationError != paNoError ) + { + result = outputNamesAndGUIDs.enumerationError; + goto error; + } + + deviceCount = inputNamesAndGUIDs.count + outputNamesAndGUIDs.count; + + if( deviceCount > 0 ) + { + /* allocate array for pointers to PaDeviceInfo structs */ + (*hostApi)->deviceInfos = (PaDeviceInfo**)PaUtil_GroupAllocateMemory( + winDsHostApi->allocations, sizeof(PaDeviceInfo*) * deviceCount ); + if( !(*hostApi)->deviceInfos ) + { + result = paInsufficientMemory; + goto error; + } + + /* allocate all PaDeviceInfo structs in a contiguous block */ + deviceInfoArray = (PaDeviceInfo*)PaUtil_GroupAllocateMemory( + winDsHostApi->allocations, sizeof(PaDeviceInfo) * deviceCount ); + if( !deviceInfoArray ) + { + result = paInsufficientMemory; + goto error; + } + + /* allocate all DSound specific info structs in a contiguous block */ + winDsHostApi->winDsDeviceInfos = (PaWinDsDeviceInfo*)PaUtil_GroupAllocateMemory( + winDsHostApi->allocations, sizeof(PaWinDsDeviceInfo) * deviceCount ); + if( !winDsHostApi->winDsDeviceInfos ) + { + result = paInsufficientMemory; + goto error; + } + + for( i=0; i < deviceCount; ++i ) + { + PaDeviceInfo *deviceInfo = &deviceInfoArray[i]; + deviceInfo->structVersion = 2; + deviceInfo->hostApi = hostApiIndex; + deviceInfo->name = 0; + (*hostApi)->deviceInfos[i] = deviceInfo; + } + + for( i=0; i< inputNamesAndGUIDs.count; ++i ) + { + result = AddInputDeviceInfoFromDirectSoundCapture( winDsHostApi, + inputNamesAndGUIDs.items[i].name, + inputNamesAndGUIDs.items[i].lpGUID ); + if( result != paNoError ) + goto error; + } + + for( i=0; i< outputNamesAndGUIDs.count; ++i ) + { + result = AddOutputDeviceInfoFromDirectSound( winDsHostApi, + outputNamesAndGUIDs.items[i].name, + outputNamesAndGUIDs.items[i].lpGUID ); + if( result != paNoError ) + goto error; + } + } + + result = TerminateDSDeviceNameAndGUIDVector( &inputNamesAndGUIDs ); + if( result != paNoError ) + goto error; + + result = TerminateDSDeviceNameAndGUIDVector( &outputNamesAndGUIDs ); + if( result != paNoError ) + goto error; + + + (*hostApi)->Terminate = Terminate; + (*hostApi)->OpenStream = OpenStream; + (*hostApi)->IsFormatSupported = IsFormatSupported; + + 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: + if( winDsHostApi ) + { + if( winDsHostApi->allocations ) + { + PaUtil_FreeAllAllocations( winDsHostApi->allocations ); + PaUtil_DestroyAllocationGroup( winDsHostApi->allocations ); + } + + PaUtil_FreeMemory( winDsHostApi ); + } + + TerminateDSDeviceNameAndGUIDVector( &inputNamesAndGUIDs ); + TerminateDSDeviceNameAndGUIDVector( &outputNamesAndGUIDs ); + + return result; +} + + +/***********************************************************************************/ +static void Terminate( struct PaUtilHostApiRepresentation *hostApi ) +{ + PaWinDsHostApiRepresentation *winDsHostApi = (PaWinDsHostApiRepresentation*)hostApi; + + /* + IMPLEMENT ME: + - clean up any resources not handled by the allocation group + */ + + if( winDsHostApi->allocations ) + { + PaUtil_FreeAllAllocations( winDsHostApi->allocations ); + PaUtil_DestroyAllocationGroup( winDsHostApi->allocations ); + } + + PaUtil_FreeMemory( winDsHostApi ); + + PaWinDs_TerminateDSoundEntryPoints(); + + CoUninitialize(); +} + + +/* Set minimal latency based on whether NT or Win95. + * NT has higher latency. + */ +static int PaWinDS_GetMinSystemLatency( void ) +{ + int minLatencyMsec; + /* 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) ) + { + minLatencyMsec = PA_WIN_NT_LATENCY; + } + else if(osvi.dwMajorVersion >= 5) + { + minLatencyMsec = PA_WIN_WDM_LATENCY; + } + else + { + minLatencyMsec = PA_WIN_9X_LATENCY; + } + return minLatencyMsec; +} + +/***********************************************************************************/ +static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate ) +{ + int inputChannelCount, outputChannelCount; + PaSampleFormat inputSampleFormat, outputSampleFormat; + + if( inputParameters ) + { + inputChannelCount = inputParameters->channelCount; + inputSampleFormat = inputParameters->sampleFormat; + + /* unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification */ + + if( inputParameters->device == paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; + + /* check that input device can support inputChannelCount */ + if( inputChannelCount > hostApi->deviceInfos[ inputParameters->device ]->maxInputChannels ) + return paInvalidChannelCount; + + /* validate inputStreamInfo */ + if( inputParameters->hostApiSpecificStreamInfo ) + return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + } + else + { + inputChannelCount = 0; + } + + if( outputParameters ) + { + outputChannelCount = outputParameters->channelCount; + outputSampleFormat = outputParameters->sampleFormat; + + /* unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification */ + + if( outputParameters->device == paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; + + /* check that output device can support inputChannelCount */ + if( outputChannelCount > hostApi->deviceInfos[ outputParameters->device ]->maxOutputChannels ) + return paInvalidChannelCount; + + /* validate outputStreamInfo */ + if( outputParameters->hostApiSpecificStreamInfo ) + return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + } + else + { + outputChannelCount = 0; + } + + /* + IMPLEMENT ME: + + - 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; +} + + +/************************************************************************* +** Determine minimum number of buffers required for this host based +** on minimum latency. 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 int PaWinDs_GetMinLatencyFrames( double sampleRate ) +{ + char envbuf[PA_ENV_BUF_SIZE]; + DWORD hresult; + int minLatencyMsec = 0; + + /* Let user determine minimal latency by setting environment variable. */ + hresult = GetEnvironmentVariable( PA_LATENCY_ENV_NAME, envbuf, PA_ENV_BUF_SIZE ); + if( (hresult > 0) && (hresult < PA_ENV_BUF_SIZE) ) + { + minLatencyMsec = atoi( envbuf ); + } + else + { + minLatencyMsec = PaWinDS_GetMinSystemLatency(); +#if PA_USE_HIGH_LATENCY + PRINT(("PA - Minimum Latency set to %d msec!\n", minLatencyMsec )); +#endif + + } + + return (int) (minLatencyMsec * sampleRate * SECONDS_PER_MSEC); +} + + +static HRESULT InitInputBuffer( PaWinDsStream *stream, unsigned long nFrameRate, WORD nChannels, int bytesPerBuffer ) +{ + DSCBUFFERDESC captureDesc; + WAVEFORMATEX wfFormat; + HRESULT result; + + stream->bytesPerInputFrame = nChannels * sizeof(short); + + // Define the buffer format + wfFormat.wFormatTag = WAVE_FORMAT_PCM; + wfFormat.nChannels = nChannels; + wfFormat.nSamplesPerSec = nFrameRate; + wfFormat.wBitsPerSample = 8 * sizeof(short); + wfFormat.nBlockAlign = (WORD)(wfFormat.nChannels * (wfFormat.wBitsPerSample / 8)); + wfFormat.nAvgBytesPerSec = wfFormat.nSamplesPerSec * wfFormat.nBlockAlign; + wfFormat.cbSize = 0; /* No extended format info. */ + stream->inputSize = bytesPerBuffer; + // ---------------------------------------------------------------------- + // Setup the secondary buffer description + ZeroMemory(&captureDesc, sizeof(DSCBUFFERDESC)); + captureDesc.dwSize = sizeof(DSCBUFFERDESC); + captureDesc.dwFlags = 0; + captureDesc.dwBufferBytes = bytesPerBuffer; + captureDesc.lpwfxFormat = &wfFormat; + // Create the capture buffer + 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, unsigned long nFrameRate, WORD nChannels, int bytesPerBuffer ) +{ + DWORD dwDataLen; + DWORD playCursor; + HRESULT result; + LPDIRECTSOUNDBUFFER pPrimaryBuffer; + HWND hWnd; + HRESULT hr; + WAVEFORMATEX wfFormat; + DSBUFFERDESC primaryDesc; + DSBUFFERDESC secondaryDesc; + unsigned char* pDSBuffData; + LARGE_INTEGER counterFrequency; + + stream->outputBufferSizeBytes = bytesPerBuffer; + stream->outputIsRunning = FALSE; + stream->outputUnderflowCount = 0; + stream->dsw_framesWritten = 0; + stream->bytesPerOutputFrame = nChannels * sizeof(short); + + // 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. + // Exclusize 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, &pPrimaryBuffer, NULL)) != DS_OK) return result; + // Define the buffer format + wfFormat.wFormatTag = WAVE_FORMAT_PCM; + wfFormat.nChannels = nChannels; + wfFormat.nSamplesPerSec = nFrameRate; + wfFormat.wBitsPerSample = 8 * sizeof(short); + wfFormat.nBlockAlign = (WORD)(wfFormat.nChannels * (wfFormat.wBitsPerSample / 8)); + wfFormat.nAvgBytesPerSec = wfFormat.nSamplesPerSec * wfFormat.nBlockAlign; + wfFormat.cbSize = 0; /* No extended format info. */ + // Set the primary buffer's format + if((result = IDirectSoundBuffer_SetFormat( pPrimaryBuffer, &wfFormat)) != DS_OK) return result; + + // ---------------------------------------------------------------------- + // Setup the secondary buffer description + ZeroMemory(&secondaryDesc, sizeof(DSBUFFERDESC)); + secondaryDesc.dwSize = sizeof(DSBUFFERDESC); + secondaryDesc.dwFlags = DSBCAPS_GLOBALFOCUS | DSBCAPS_GETCURRENTPOSITION2; + secondaryDesc.dwBufferBytes = bytesPerBuffer; + secondaryDesc.lpwfxFormat = &wfFormat; + // Create the secondary buffer + if ((result = IDirectSound_CreateSoundBuffer( stream->pDirectSound, + &secondaryDesc, &stream->pDirectSoundOutputBuffer, NULL)) != DS_OK) return result; + // Lock the DS buffer + if ((result = IDirectSoundBuffer_Lock( stream->pDirectSoundOutputBuffer, 0, stream->outputBufferSizeBytes, (LPVOID*)&pDSBuffData, + &dwDataLen, NULL, 0, 0)) != DS_OK) return result; + // Zero the DS buffer + ZeroMemory(pDSBuffData, dwDataLen); + // Unlock the DS buffer + if ((result = IDirectSoundBuffer_Unlock( stream->pDirectSoundOutputBuffer, pDSBuffData, dwDataLen, NULL, 0)) != DS_OK) return result; + if( QueryPerformanceFrequency( &counterFrequency ) ) + { + int framesInBuffer = bytesPerBuffer / (nChannels * sizeof(short)); + stream->perfCounterTicksPerBuffer.QuadPart = (counterFrequency.QuadPart * framesInBuffer) / nFrameRate; + } + else + { + stream->perfCounterTicksPerBuffer.QuadPart = 0; + } + // 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. + hr = IDirectSoundBuffer_GetCurrentPosition( stream->pDirectSoundOutputBuffer, + &playCursor, &stream->outputBufferWriteOffsetBytes ); + if( hr != DS_OK ) + { + return hr; + } + stream->dsw_framesWritten = stream->outputBufferWriteOffsetBytes / stream->bytesPerOutputFrame; + /* printf("DSW_InitOutputBuffer: playCursor = %d, writeCursor = %d\n", playCursor, dsw->dsw_WriteOffset ); */ + return DS_OK; +} + + +/***********************************************************************************/ +/* 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 inputChannelCount, outputChannelCount; + PaSampleFormat inputSampleFormat, outputSampleFormat; + PaSampleFormat hostInputSampleFormat, hostOutputSampleFormat; + unsigned long suggestedInputLatencyFrames, suggestedOutputLatencyFrames; + + if( inputParameters ) + { + 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( inputChannelCount > hostApi->deviceInfos[ inputParameters->device ]->maxInputChannels ) + return paInvalidChannelCount; + + /* validate hostApiSpecificStreamInfo */ + if( inputParameters->hostApiSpecificStreamInfo ) + return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + } + else + { + inputChannelCount = 0; + inputSampleFormat = 0; + suggestedInputLatencyFrames = 0; + } + + + if( outputParameters ) + { + 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 inputChannelCount */ + if( outputChannelCount > hostApi->deviceInfos[ outputParameters->device ]->maxOutputChannels ) + return paInvalidChannelCount; + + /* validate hostApiSpecificStreamInfo */ + if( outputParameters->hostApiSpecificStreamInfo ) + return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + } + else + { + outputChannelCount = 0; + outputSampleFormat = 0; + suggestedOutputLatencyFrames = 0; + } + + + /* + 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 ); + } + + PaUtil_InitializeCpuLoadMeasurer( &stream->cpuLoadMeasurer, sampleRate ); + + + if( inputParameters ) + { + /* IMPLEMENT ME - establish which host formats are available */ + hostInputSampleFormat = + PaUtil_SelectClosestAvailableFormat( paInt16 /* native formats */, inputParameters->sampleFormat ); + } + else + { + hostInputSampleFormat = 0; + } + + if( outputParameters ) + { + /* IMPLEMENT ME - establish which host formats are available */ + hostOutputSampleFormat = + PaUtil_SelectClosestAvailableFormat( paInt16 /* native formats */, outputParameters->sampleFormat ); + } + else + { + hostOutputSampleFormat = 0; + } + + result = PaUtil_InitializeBufferProcessor( &stream->bufferProcessor, + inputChannelCount, inputSampleFormat, hostInputSampleFormat, + outputChannelCount, outputSampleFormat, hostOutputSampleFormat, + sampleRate, streamFlags, framesPerBuffer, + framesPerBuffer, /* 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; + + + stream->streamRepresentation.streamInfo.inputLatency = + PaUtil_GetBufferProcessorInputLatency(&stream->bufferProcessor); /* FIXME: not initialised anywhere else */ + stream->streamRepresentation.streamInfo.outputLatency = + PaUtil_GetBufferProcessorOutputLatency(&stream->bufferProcessor); /* FIXME: not initialised anywhere else */ + stream->streamRepresentation.streamInfo.sampleRate = sampleRate; + + +/* DirectSound specific initialization */ + { + HRESULT hr; + int bytesPerDirectSoundBuffer; + int userLatencyFrames; + int minLatencyFrames; + + stream->timerID = 0; + + /* Get system minimum latency. */ + minLatencyFrames = PaWinDs_GetMinLatencyFrames( sampleRate ); + + /* Let user override latency by passing latency parameter. */ + userLatencyFrames = (suggestedInputLatencyFrames > suggestedOutputLatencyFrames) + ? suggestedInputLatencyFrames + : suggestedOutputLatencyFrames; + if( userLatencyFrames > 0 ) minLatencyFrames = userLatencyFrames; + + /* Calculate stream->framesPerDSBuffer depending on framesPerBuffer */ + if( framesPerBuffer == paFramesPerBufferUnspecified ) + { + /* App support variable framesPerBuffer */ + stream->framesPerDSBuffer = minLatencyFrames; + + stream->streamRepresentation.streamInfo.outputLatency = (double)(minLatencyFrames - 1) / sampleRate; + } + else + { + /* Round up to number of buffers needed to guarantee that latency. */ + int numUserBuffers = (minLatencyFrames + framesPerBuffer - 1) / framesPerBuffer; + if( numUserBuffers < 1 ) numUserBuffers = 1; + numUserBuffers += 1; /* So we have latency worth of buffers ahead of current buffer. */ + stream->framesPerDSBuffer = framesPerBuffer * numUserBuffers; + + stream->streamRepresentation.streamInfo.outputLatency = (double)(framesPerBuffer * (numUserBuffers-1)) / sampleRate; + } + + { + /** @todo REVIEW: this calculation seems incorrect to me - rossb. */ + int msecLatency = (int) ((stream->framesPerDSBuffer * MSEC_PER_SECOND) / sampleRate); + PRINT(("PortAudio on DirectSound - Latency = %d frames, %d msec\n", stream->framesPerDSBuffer, msecLatency )); + } + + + /* ------------------ OUTPUT */ + if( outputParameters ) + { + /* + PaDeviceInfo *deviceInfo = hostApi->deviceInfos[ outputParameters->device ]; + DBUG(("PaHost_OpenStream: deviceID = 0x%x\n", outputParameters->device)); + */ + + bytesPerDirectSoundBuffer = stream->framesPerDSBuffer * outputParameters->channelCount * sizeof(short); + if( bytesPerDirectSoundBuffer < DSBSIZE_MIN ) + { + result = paBufferTooSmall; + goto error; + } + else if( bytesPerDirectSoundBuffer > DSBSIZE_MAX ) + { + result = paBufferTooBig; + goto error; + } + + + hr = paWinDsDSoundEntryPoints.DirectSoundCreate( winDsHostApi->winDsDeviceInfos[outputParameters->device].lpGUID, + &stream->pDirectSound, NULL ); + if( hr != DS_OK ) + { + ERR_RPT(("PortAudio: DirectSoundCreate() failed!\n")); + result = paUnanticipatedHostError; + PA_DS_SET_LAST_DIRECTSOUND_ERROR( hr ); + goto error; + } + hr = InitOutputBuffer( stream, + (unsigned long) (sampleRate + 0.5), + (WORD)outputParameters->channelCount, bytesPerDirectSoundBuffer ); + DBUG(("InitOutputBuffer() returns %x\n", hr)); + if( hr != DS_OK ) + { + result = paUnanticipatedHostError; + PA_DS_SET_LAST_DIRECTSOUND_ERROR( hr ); + goto error; + } + /* Calculate value used in latency calculation to avoid real-time divides. */ + stream->secondsPerHostByte = 1.0 / + (stream->bufferProcessor.bytesPerHostOutputSample * + outputChannelCount * sampleRate); + } + + /* ------------------ INPUT */ + if( inputParameters ) + { + /* + PaDeviceInfo *deviceInfo = hostApi->deviceInfos[ inputParameters->device ]; + DBUG(("PaHost_OpenStream: deviceID = 0x%x\n", inputParameters->device)); + */ + + bytesPerDirectSoundBuffer = stream->framesPerDSBuffer * inputParameters->channelCount * sizeof(short); + if( bytesPerDirectSoundBuffer < DSBSIZE_MIN ) + { + result = paBufferTooSmall; + goto error; + } + else if( bytesPerDirectSoundBuffer > DSBSIZE_MAX ) + { + result = paBufferTooBig; + goto error; + } + + hr = paWinDsDSoundEntryPoints.DirectSoundCaptureCreate( winDsHostApi->winDsDeviceInfos[inputParameters->device].lpGUID, + &stream->pDirectSoundCapture, NULL ); + if( hr != DS_OK ) + { + ERR_RPT(("PortAudio: DirectSoundCaptureCreate() failed!\n")); + result = paUnanticipatedHostError; + PA_DS_SET_LAST_DIRECTSOUND_ERROR( hr ); + goto error; + } + hr = InitInputBuffer( stream, + (unsigned long) (sampleRate + 0.5), + (WORD)inputParameters->channelCount, bytesPerDirectSoundBuffer ); + 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; + } + } + + } + + *s = (PaStream*)stream; + + return result; + +error: + if( stream ) + 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); + } + /* Maintain frame output cursor. */ + stream->framesPlayed += (bytesPlayed / stream->bytesPerOutputFrame); + } + 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; + } + stream->outputBufferWriteOffsetBytes = writeCursor; + numBytesEmpty = stream->outputBufferSizeBytes - playWriteGap; + } + *bytesEmpty = numBytesEmpty; + return hr; +} + +/***********************************************************************************/ +static PaError Pa_TimeSlice( PaWinDsStream *stream ) +{ + PaError result = 0; /* FIXME: this should be declared int and this function should also return that type (same as stream callback return type)*/ + long numFrames = 0; + long bytesEmpty = 0; + long bytesFilled = 0; + long bytesToXfer = 0; + long framesToXfer = 0; + long numInFramesReady = 0; + long numOutFramesReady = 0; + long bytesProcessed; + HRESULT hresult; + double outputLatency = 0; + PaStreamCallbackTimeInfo timeInfo = {0,0,0}; /** @todo implement inputBufferAdcTime */ + +/* 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->inputSize; // unwrap offset + bytesFilled = filled; + } + // FIXME: what happens if IDirectSoundCaptureBuffer_GetCurrentPosition fails? + + framesToXfer = numInFramesReady = bytesFilled / stream->bytesPerInputFrame; + outputLatency = ((double)bytesFilled) * stream->secondsPerHostByte; + + /** @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->bytesPerOutputFrame; + + /* Check for underflow */ + if( stream->outputUnderflowCount != previousUnderflowCount ) + stream->callbackFlags |= paOutputUnderflow; + } + + if( (numInFramesReady > 0) && (numOutFramesReady > 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(); + timeInfo.outputBufferDacTime = timeInfo.currentTime + outputLatency; + + + PaUtil_BeginBufferProcessing( &stream->bufferProcessor, &timeInfo, stream->callbackFlags ); + stream->callbackFlags = 0; + + /* Input */ + if( stream->bufferProcessor.inputChannelCount > 0 ) + { + bytesToXfer = framesToXfer * stream->bytesPerInputFrame; + 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)); + result = paUnanticipatedHostError; + PA_DS_SET_LAST_DIRECTSOUND_ERROR( hresult ); + goto error2; + } + + numFrames = dwInSize1 / stream->bytesPerInputFrame; + PaUtil_SetInputFrameCount( &stream->bufferProcessor, numFrames ); + PaUtil_SetInterleavedInputChannels( &stream->bufferProcessor, 0, lpInBuf1, 0 ); + /* Is input split into two regions. */ + if( dwInSize2 > 0 ) + { + numFrames = dwInSize2 / stream->bytesPerInputFrame; + PaUtil_Set2ndInputFrameCount( &stream->bufferProcessor, numFrames ); + PaUtil_Set2ndInterleavedInputChannels( &stream->bufferProcessor, 0, lpInBuf2, 0 ); + } + } + + /* Output */ + if( stream->bufferProcessor.outputChannelCount > 0 ) + { + bytesToXfer = framesToXfer * stream->bytesPerOutputFrame; + 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)); + result = paUnanticipatedHostError; + PA_DS_SET_LAST_DIRECTSOUND_ERROR( hresult ); + goto error1; + } + + numFrames = dwOutSize1 / stream->bytesPerOutputFrame; + PaUtil_SetOutputFrameCount( &stream->bufferProcessor, numFrames ); + PaUtil_SetInterleavedOutputChannels( &stream->bufferProcessor, 0, lpOutBuf1, 0 ); + + /* Is output split into two regions. */ + if( dwOutSize2 > 0 ) + { + numFrames = dwOutSize2 / stream->bytesPerOutputFrame; + PaUtil_Set2ndOutputFrameCount( &stream->bufferProcessor, numFrames ); + PaUtil_Set2ndInterleavedOutputChannels( &stream->bufferProcessor, 0, lpOutBuf2, 0 ); + } + } + + result = paContinue; + numFrames = PaUtil_EndBufferProcessing( &stream->bufferProcessor, &result ); + 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->bytesPerOutputFrame; + stream->outputBufferWriteOffsetBytes = (stream->outputBufferWriteOffsetBytes + bytesProcessed) % stream->outputBufferSizeBytes; + IDirectSoundBuffer_Unlock( stream->pDirectSoundOutputBuffer, lpOutBuf1, dwOutSize1, lpOutBuf2, dwOutSize2); + stream->dsw_framesWritten += numFrames; + } + +error1: + if( stream->bufferProcessor.inputChannelCount > 0 ) + { + /* FIXME: an overflow could happen here */ + + /* Update our buffer offset and unlock sound buffer */ + bytesProcessed = numFrames * stream->bytesPerInputFrame; + stream->readOffset = (stream->readOffset + bytesProcessed) % stream->inputSize; + IDirectSoundCaptureBuffer_Unlock( stream->pDirectSoundInputBuffer, lpInBuf1, dwInSize1, lpInBuf2, dwInSize2); + } +error2: + + PaUtil_EndCpuLoadMeasurement( &stream->cpuLoadMeasurer, numFrames ); + + } + + return result; +} +/*******************************************************************/ + +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 ); // updates framesPlayed + 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->dsw_framesWritten += bytesEmpty / stream->bytesPerOutputFrame; + } + return hr; +} + + +static void CALLBACK Pa_TimerCallback(UINT uID, UINT uMsg, DWORD_PTR dwUser, DWORD dw1, DWORD dw2) +{ + PaWinDsStream *stream; + + /* 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 ) + { + stream->isActive = 0; + } + else if( stream->stopProcessing ) + { + if( stream->bufferProcessor.outputChannelCount > 0 ) + { + ZeroAvailableOutputSpace( stream ); + /* clear isActive when all sound played */ + if( stream->framesPlayed >= stream->framesWritten ) + { + stream->isActive = 0; + } + } + else + { + stream->isActive = 0; + } + } + else + { + if( Pa_TimeSlice( stream ) != 0) /* Call time slice independant of timing method. */ + { + /* FIXME implement handling of paComplete and paAbort if possible */ + stream->stopProcessing = 1; + } + } + + if( !stream->isActive ){ + if( stream->streamRepresentation.streamFinishedCallback != 0 ) + stream->streamRepresentation.streamFinishedCallback( stream->streamRepresentation.userData ); + } + } +} + +/*********************************************************************************** + 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; + + // Cleanup the sound buffers + if( stream->pDirectSoundOutputBuffer ) + { + IDirectSoundBuffer_Stop( stream->pDirectSoundOutputBuffer ); + IDirectSoundBuffer_Release( stream->pDirectSoundOutputBuffer ); + stream->pDirectSoundOutputBuffer = 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; + } + + PaUtil_TerminateBufferProcessor( &stream->bufferProcessor ); + PaUtil_TerminateStreamRepresentation( &stream->streamRepresentation ); + PaUtil_FreeMemory( stream ); + + return result; +} + +/***********************************************************************************/ +static PaError StartStream( PaStream *s ) +{ + PaError result = paNoError; + PaWinDsStream *stream = (PaWinDsStream*)s; + HRESULT hr; + + PaUtil_ResetBufferProcessor( &stream->bufferProcessor ); + + if( stream->bufferProcessor.inputChannelCount > 0 ) + { + // Start the buffer playback + 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; + stream->isActive = 1; + + if( stream->bufferProcessor.outputChannelCount > 0 ) + { + /* Give user callback a chance to pre-fill buffer. REVIEW - i thought we weren't pre-filling, rb. */ + result = Pa_TimeSlice( stream ); + if( result != paNoError ) return result; // FIXME - what if finished? + + QueryPerformanceCounter( &stream->previousPlayTime ); + stream->previousPlayCursor = 0; + stream->framesPlayed = 0; + hr = IDirectSoundBuffer_SetCurrentPosition( stream->pDirectSoundOutputBuffer, 0 ); + DBUG(("PaHost_StartOutput: IDirectSoundBuffer_SetCurrentPosition returned = 0x%X.\n", hr)); + if( hr != DS_OK ) + { + result = paUnanticipatedHostError; + PA_DS_SET_LAST_DIRECTSOUND_ERROR( hr ); + goto error; + } + + // 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; + } + } + + + /* Create timer that will wake us up so we can fill the DSound buffer. */ + { + int resolution; + int framesPerWakeup = stream->framesPerDSBuffer / 4; + int msecPerWakeup = MSEC_PER_SECOND * framesPerWakeup / (int) stream->streamRepresentation.streamInfo.sampleRate; + if( msecPerWakeup < 10 ) msecPerWakeup = 10; + else if( msecPerWakeup > 100 ) msecPerWakeup = 100; + resolution = msecPerWakeup/4; + stream->timerID = timeSetEvent( msecPerWakeup, resolution, (LPTIMECALLBACK) Pa_TimerCallback, + (DWORD_PTR) stream, TIME_PERIODIC ); + } + if( stream->timerID == 0 ) + { + stream->isActive = 0; + result = paUnanticipatedHostError; + PA_DS_SET_LAST_DIRECTSOUND_ERROR( hr ); + goto error; + } + + stream->isStarted = TRUE; + +error: + return result; +} + + +/***********************************************************************************/ +static PaError StopStream( PaStream *s ) +{ + PaError result = paNoError; + PaWinDsStream *stream = (PaWinDsStream*)s; + HRESULT hr; + int timeoutMsec; + + stream->stopProcessing = 1; + /* Set timeout at 20% beyond maximum time we might wait. */ + timeoutMsec = (int) (1200.0 * stream->framesPerDSBuffer / stream->streamRepresentation.streamInfo.sampleRate); + while( stream->isActive && (timeoutMsec > 0) ) + { + Sleep(10); + timeoutMsec -= 10; + } + if( stream->timerID != 0 ) + { + timeKillEvent(stream->timerID); /* Stop callback timer. */ + stream->timerID = 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->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 = FALSE; + + 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; +} + + + |