2
* $Id: pa_win_ds.c 1433 2009-12-09 01:42:59Z rossb $
3
* Portable Audio I/O Library DirectSound implementation
5
* Authors: Phil Burk, Robert Marsanyi & Ross Bencina
6
* Based on the Open Source API proposed by Ross Bencina
7
* Copyright (c) 1999-2007 Ross Bencina, Phil Burk, Robert Marsanyi
9
* Permission is hereby granted, free of charge, to any person obtaining
10
* a copy of this software and associated documentation files
11
* (the "Software"), to deal in the Software without restriction,
12
* including without limitation the rights to use, copy, modify, merge,
13
* publish, distribute, sublicense, and/or sell copies of the Software,
14
* and to permit persons to whom the Software is furnished to do so,
15
* subject to the following conditions:
17
* The above copyright notice and this permission notice shall be
18
* included in all copies or substantial portions of the Software.
20
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
23
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
24
* ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
25
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30
* The text above constitutes the entire PortAudio license; however,
31
* the PortAudio community also makes the following non-binding requests:
33
* Any person wishing to distribute modifications to the Software is
34
* requested to send the modifications to the original developer so that
35
* they can be incorporated into the canonical version. It is also
36
* requested that these non-binding requests be included along with the
43
@todo implement paInputOverflow callback status flag
45
@todo implement paNeverDropInput.
47
@todo implement host api specific extension to set i/o buffer sizes in frames
49
@todo implement initialisation of PaDeviceInfo default*Latency fields (currently set to 0.)
51
@todo implement ReadStream, WriteStream, GetStreamReadAvailable, GetStreamWriteAvailable
53
@todo audit handling of DirectSound result codes - in many cases we could convert a HRESULT into
54
a native portaudio error code. Standard DirectSound result codes are documented at msdn.
56
@todo implement IsFormatSupported
58
@todo call PaUtil_SetLastHostErrorInfo with a specific error string (currently just "DSound error").
60
@todo make sure all buffers have been played before stopping the stream
61
when the stream callback returns paComplete
63
@todo retrieve default devices using the DRVM_MAPPER_PREFERRED_GET functions used in the wmme api
64
these wave device ids can be aligned with the directsound devices either by retrieving
65
the system interface device name using DRV_QUERYDEVICEINTERFACE or by using the wave device
66
id retrieved in KsPropertySetEnumerateCallback.
68
old TODOs from phil, need to work out if these have been done:
69
O- fix "patest_stop.c"
74
#include <string.h> /* strlen() */
79
We are only using DX3 in here, no need to polute the namespace - davidv
81
#define DIRECTSOUND_VERSION 0x0300
83
#ifdef PAWIN_USE_WDMKS_DEVICE_INFO
85
#endif /* PAWIN_USE_WDMKS_DEVICE_INFO */
88
#include "pa_allocation.h"
89
#include "pa_hostapi.h"
90
#include "pa_stream.h"
91
#include "pa_cpuload.h"
92
#include "pa_process.h"
93
#include "pa_debugprint.h"
95
#include "pa_win_ds.h"
96
#include "pa_win_ds_dynlink.h"
97
#include "pa_win_waveformat.h"
98
#include "pa_win_wdmks_utils.h"
101
#if (defined(WIN32) && (defined(_MSC_VER) && (_MSC_VER >= 1200))) /* MSC version 6 and above */
102
#pragma comment( lib, "dsound.lib" )
103
#pragma comment( lib, "winmm.lib" )
107
provided in newer platform sdks and x64
111
#define DWORD_PTR unsigned __int64
113
#define DWORD_PTR unsigned long
117
#define PRINT(x) PA_DEBUG(x);
118
#define ERR_RPT(x) PRINT(x)
119
#define DBUG(x) PRINT(x)
120
#define DBUGX(x) PRINT(x)
122
#define PA_USE_HIGH_LATENCY (0)
123
#if PA_USE_HIGH_LATENCY
124
#define PA_WIN_9X_LATENCY (500)
125
#define PA_WIN_NT_LATENCY (600)
127
#define PA_WIN_9X_LATENCY (140)
128
#define PA_WIN_NT_LATENCY (280)
131
#define PA_WIN_WDM_LATENCY (120)
133
#define SECONDS_PER_MSEC (0.001)
134
#define MSEC_PER_SECOND (1000)
136
/* prototypes for functions declared in this file */
141
#endif /* __cplusplus */
143
PaError PaWinDs_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index );
147
#endif /* __cplusplus */
149
static void Terminate( struct PaUtilHostApiRepresentation *hostApi );
150
static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
152
const PaStreamParameters *inputParameters,
153
const PaStreamParameters *outputParameters,
155
unsigned long framesPerBuffer,
156
PaStreamFlags streamFlags,
157
PaStreamCallback *streamCallback,
159
static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi,
160
const PaStreamParameters *inputParameters,
161
const PaStreamParameters *outputParameters,
163
static PaError CloseStream( PaStream* stream );
164
static PaError StartStream( PaStream *stream );
165
static PaError StopStream( PaStream *stream );
166
static PaError AbortStream( PaStream *stream );
167
static PaError IsStreamStopped( PaStream *s );
168
static PaError IsStreamActive( PaStream *stream );
169
static PaTime GetStreamTime( PaStream *stream );
170
static double GetStreamCpuLoad( PaStream* stream );
171
static PaError ReadStream( PaStream* stream, void *buffer, unsigned long frames );
172
static PaError WriteStream( PaStream* stream, const void *buffer, unsigned long frames );
173
static signed long GetStreamReadAvailable( PaStream* stream );
174
static signed long GetStreamWriteAvailable( PaStream* stream );
177
/* FIXME: should convert hr to a string */
178
#define PA_DS_SET_LAST_DIRECTSOUND_ERROR( hr ) \
179
PaUtil_SetLastHostErrorInfo( paDirectSound, hr, "DirectSound error" )
181
/************************************************* DX Prototypes **********/
182
static BOOL CALLBACK CollectGUIDsProc(LPGUID lpGUID,
187
/************************************************************************************/
188
/********************** Structures **************************************************/
189
/************************************************************************************/
190
/* PaWinDsHostApiRepresentation - host api datastructure specific to this implementation */
192
typedef struct PaWinDsDeviceInfo
194
PaDeviceInfo inheritedDeviceInfo;
197
double sampleRates[3];
198
char deviceInputChannelCountIsKnown; /**<< if the system returns 0xFFFF then we don't really know the number of supported channels (1=>known, 0=>unknown)*/
199
char deviceOutputChannelCountIsKnown; /**<< if the system returns 0xFFFF then we don't really know the number of supported channels (1=>known, 0=>unknown)*/
204
PaUtilHostApiRepresentation inheritedHostApiRep;
205
PaUtilStreamInterface callbackStreamInterface;
206
PaUtilStreamInterface blockingStreamInterface;
208
PaUtilAllocationGroup *allocations;
210
/* implementation specific data goes here */
212
char comWasInitialized;
214
} PaWinDsHostApiRepresentation;
217
/* PaWinDsStream - a stream data structure specifically for this implementation */
219
typedef struct PaWinDsStream
221
PaUtilStreamRepresentation streamRepresentation;
222
PaUtilCpuLoadMeasurer cpuLoadMeasurer;
223
PaUtilBufferProcessor bufferProcessor;
225
/* DirectSound specific data. */
228
LPDIRECTSOUND pDirectSound;
229
LPDIRECTSOUNDBUFFER pDirectSoundOutputBuffer;
230
DWORD outputBufferWriteOffsetBytes; /* last write position */
231
INT outputBufferSizeBytes;
232
INT bytesPerOutputFrame;
233
/* Try to detect play buffer underflows. */
234
LARGE_INTEGER perfCounterTicksPerBuffer; /* counter ticks it should take to play a full buffer */
235
LARGE_INTEGER previousPlayTime;
236
UINT previousPlayCursor;
237
UINT outputUnderflowCount;
238
BOOL outputIsRunning;
239
/* use double which lets us can play for several thousand years with enough precision */
240
double dsw_framesWritten;
243
LPDIRECTSOUNDCAPTURE pDirectSoundCapture;
244
LPDIRECTSOUNDCAPTUREBUFFER pDirectSoundInputBuffer;
245
INT bytesPerInputFrame;
246
UINT readOffset; /* last read position */
251
int framesPerDSBuffer;
252
double framesWritten;
253
double secondsPerHostByte; /* Used to optimize latency calculation for outTime */
255
PaStreamCallbackFlags callbackFlags;
257
/* FIXME - move all below to PaUtilStreamRepresentation */
258
volatile int isStarted;
259
volatile int isActive;
260
volatile int stopProcessing; /* stop thread once existing buffers have been returned */
261
volatile int abortProcessing; /* stop thread immediately */
265
/************************************************************************************
266
** Duplicate the input string using the allocations allocator.
267
** A NULL string is converted to a zero length string.
268
** If memory cannot be allocated, NULL is returned.
270
static char *DuplicateDeviceNameString( PaUtilAllocationGroup *allocations, const char* src )
276
size_t len = strlen(src);
277
result = (char*)PaUtil_GroupAllocateMemory( allocations, (long)(len + 1) );
279
memcpy( (void *) result, src, len+1 );
283
result = (char*)PaUtil_GroupAllocateMemory( allocations, 1 );
291
/************************************************************************************
292
** DSDeviceNameAndGUID, DSDeviceNameAndGUIDVector used for collecting preliminary
293
** information during device enumeration.
295
typedef struct DSDeviceNameAndGUID{
296
char *name; // allocated from parent's allocations, never deleted by this structure
299
void *pnpInterface; // wchar_t* interface path, allocated using the DS host api's allocation group
300
} DSDeviceNameAndGUID;
302
typedef struct DSDeviceNameAndGUIDVector{
303
PaUtilAllocationGroup *allocations;
304
PaError enumerationError;
308
DSDeviceNameAndGUID *items; // Allocated using LocalAlloc()
309
} DSDeviceNameAndGUIDVector;
311
typedef struct DSDeviceNamesAndGUIDs{
312
PaWinDsHostApiRepresentation *winDsHostApi;
313
DSDeviceNameAndGUIDVector inputNamesAndGUIDs;
314
DSDeviceNameAndGUIDVector outputNamesAndGUIDs;
315
} DSDeviceNamesAndGUIDs;
317
static PaError InitializeDSDeviceNameAndGUIDVector(
318
DSDeviceNameAndGUIDVector *guidVector, PaUtilAllocationGroup *allocations )
320
PaError result = paNoError;
322
guidVector->allocations = allocations;
323
guidVector->enumerationError = paNoError;
325
guidVector->count = 0;
326
guidVector->free = 8;
327
guidVector->items = (DSDeviceNameAndGUID*)LocalAlloc( LMEM_FIXED, sizeof(DSDeviceNameAndGUID) * guidVector->free );
328
if( guidVector->items == NULL )
329
result = paInsufficientMemory;
334
static PaError ExpandDSDeviceNameAndGUIDVector( DSDeviceNameAndGUIDVector *guidVector )
336
PaError result = paNoError;
337
DSDeviceNameAndGUID *newItems;
340
/* double size of vector */
341
int size = guidVector->count + guidVector->free;
342
guidVector->free += size;
344
newItems = (DSDeviceNameAndGUID*)LocalAlloc( LMEM_FIXED, sizeof(DSDeviceNameAndGUID) * size * 2 );
345
if( newItems == NULL )
347
result = paInsufficientMemory;
351
for( i=0; i < guidVector->count; ++i )
353
newItems[i].name = guidVector->items[i].name;
354
if( guidVector->items[i].lpGUID == NULL )
356
newItems[i].lpGUID = NULL;
360
newItems[i].lpGUID = &newItems[i].guid;
361
memcpy( &newItems[i].guid, guidVector->items[i].lpGUID, sizeof(GUID) );;
363
newItems[i].pnpInterface = guidVector->items[i].pnpInterface;
366
LocalFree( guidVector->items );
367
guidVector->items = newItems;
374
it's safe to call DSDeviceNameAndGUIDVector multiple times
376
static PaError TerminateDSDeviceNameAndGUIDVector( DSDeviceNameAndGUIDVector *guidVector )
378
PaError result = paNoError;
380
if( guidVector->items != NULL )
382
if( LocalFree( guidVector->items ) != NULL )
383
result = paInsufficientMemory; /** @todo this isn't the correct error to return from a deallocation failure */
385
guidVector->items = NULL;
391
/************************************************************************************
392
** Collect preliminary device information during DirectSound enumeration
394
static BOOL CALLBACK CollectGUIDsProc(LPGUID lpGUID,
399
DSDeviceNameAndGUIDVector *namesAndGUIDs = (DSDeviceNameAndGUIDVector*)lpContext;
402
(void) lpszDrvName; /* unused variable */
404
if( namesAndGUIDs->free == 0 )
406
error = ExpandDSDeviceNameAndGUIDVector( namesAndGUIDs );
407
if( error != paNoError )
409
namesAndGUIDs->enumerationError = error;
414
/* Set GUID pointer, copy GUID to storage in DSDeviceNameAndGUIDVector. */
417
namesAndGUIDs->items[namesAndGUIDs->count].lpGUID = NULL;
421
namesAndGUIDs->items[namesAndGUIDs->count].lpGUID =
422
&namesAndGUIDs->items[namesAndGUIDs->count].guid;
424
memcpy( &namesAndGUIDs->items[namesAndGUIDs->count].guid, lpGUID, sizeof(GUID) );
427
namesAndGUIDs->items[namesAndGUIDs->count].name =
428
DuplicateDeviceNameString( namesAndGUIDs->allocations, lpszDesc );
429
if( namesAndGUIDs->items[namesAndGUIDs->count].name == NULL )
431
namesAndGUIDs->enumerationError = paInsufficientMemory;
435
namesAndGUIDs->items[namesAndGUIDs->count].pnpInterface = 0;
437
++namesAndGUIDs->count;
438
--namesAndGUIDs->free;
443
#ifdef PAWIN_USE_WDMKS_DEVICE_INFO
445
static void *DuplicateWCharString( PaUtilAllocationGroup *allocations, wchar_t *source )
450
len = wcslen( source );
451
result = (wchar_t*)PaUtil_GroupAllocateMemory( allocations, (long) ((len+1) * sizeof(wchar_t)) );
452
wcscpy( result, source );
456
static BOOL CALLBACK KsPropertySetEnumerateCallback( PDSPROPERTY_DIRECTSOUNDDEVICE_DESCRIPTION_W_DATA data, LPVOID context )
459
DSDeviceNamesAndGUIDs *deviceNamesAndGUIDs = (DSDeviceNamesAndGUIDs*)context;
461
if( data->DataFlow == DIRECTSOUNDDEVICE_DATAFLOW_RENDER )
463
for( i=0; i < deviceNamesAndGUIDs->outputNamesAndGUIDs.count; ++i )
465
if( deviceNamesAndGUIDs->outputNamesAndGUIDs.items[i].lpGUID
466
&& memcmp( &data->DeviceId, deviceNamesAndGUIDs->outputNamesAndGUIDs.items[i].lpGUID, sizeof(GUID) ) == 0 )
468
deviceNamesAndGUIDs->outputNamesAndGUIDs.items[i].pnpInterface =
469
(char*)DuplicateWCharString( deviceNamesAndGUIDs->winDsHostApi->allocations, data->Interface );
474
else if( data->DataFlow == DIRECTSOUNDDEVICE_DATAFLOW_CAPTURE )
476
for( i=0; i < deviceNamesAndGUIDs->inputNamesAndGUIDs.count; ++i )
478
if( deviceNamesAndGUIDs->inputNamesAndGUIDs.items[i].lpGUID
479
&& memcmp( &data->DeviceId, deviceNamesAndGUIDs->inputNamesAndGUIDs.items[i].lpGUID, sizeof(GUID) ) == 0 )
481
deviceNamesAndGUIDs->inputNamesAndGUIDs.items[i].pnpInterface =
482
(char*)DuplicateWCharString( deviceNamesAndGUIDs->winDsHostApi->allocations, data->Interface );
492
static GUID pawin_CLSID_DirectSoundPrivate =
493
{ 0x11ab3ec0, 0x25ec, 0x11d1, 0xa4, 0xd8, 0x00, 0xc0, 0x4f, 0xc2, 0x8a, 0xca };
495
static GUID pawin_DSPROPSETID_DirectSoundDevice =
496
{ 0x84624f82, 0x25ec, 0x11d1, 0xa4, 0xd8, 0x00, 0xc0, 0x4f, 0xc2, 0x8a, 0xca };
498
static GUID pawin_IID_IKsPropertySet =
499
{ 0x31efac30, 0x515c, 0x11d0, 0xa9, 0xaa, 0x00, 0xaa, 0x00, 0x61, 0xbe, 0x93 };
503
FindDevicePnpInterfaces fills in the pnpInterface fields in deviceNamesAndGUIDs
504
with UNICODE file paths to the devices. The DS documentation mentions
505
at least two techniques by which these Interface paths can be found using IKsPropertySet on
506
the DirectSound class object. One is using the DSPROPERTY_DIRECTSOUNDDEVICE_DESCRIPTION
507
property, and the other is using DSPROPERTY_DIRECTSOUNDDEVICE_ENUMERATE.
508
I tried both methods and only the second worked. I found two postings on the
509
net from people who had the same problem with the first method, so I think the method used here is
510
more common/likely to work. The probem is that IKsPropertySet_Get returns S_OK
511
but the fields of the device description are not filled in.
513
The mechanism we use works by registering an enumeration callback which is called for
514
every DSound device. Our callback searches for a device in our deviceNamesAndGUIDs list
515
with the matching GUID and copies the pointer to the Interface path.
516
Note that we could have used this enumeration callback to perform the original
517
device enumeration, however we choose not to so we can disable this step easily.
519
Apparently the IKsPropertySet mechanism was added in DirectSound 9c 2004
520
http://www.tech-archive.net/Archive/Development/microsoft.public.win32.programmer.mmedia/2004-12/0099.html
524
static void FindDevicePnpInterfaces( DSDeviceNamesAndGUIDs *deviceNamesAndGUIDs )
526
IClassFactory *pClassFactory;
528
if( paWinDsDSoundEntryPoints.DllGetClassObject(&pawin_CLSID_DirectSoundPrivate, &IID_IClassFactory, (PVOID *) &pClassFactory) == S_OK ){
529
IKsPropertySet *pPropertySet;
530
if( pClassFactory->lpVtbl->CreateInstance( pClassFactory, NULL, &pawin_IID_IKsPropertySet, (PVOID *) &pPropertySet) == S_OK ){
532
DSPROPERTY_DIRECTSOUNDDEVICE_ENUMERATE_W_DATA data;
535
data.Callback = KsPropertySetEnumerateCallback;
536
data.Context = deviceNamesAndGUIDs;
538
IKsPropertySet_Get( pPropertySet,
539
&pawin_DSPROPSETID_DirectSoundDevice,
540
DSPROPERTY_DIRECTSOUNDDEVICE_ENUMERATE_W,
548
IKsPropertySet_Release( pPropertySet );
550
pClassFactory->lpVtbl->Release( pClassFactory );
554
The following code fragment, which I chose not to use, queries for the
555
device interface for a device with a specific GUID:
558
DSPROPERTY_DIRECTSOUNDDEVICE_DESCRIPTION_W_DATA Property;
560
memset (&Property, 0, sizeof(Property));
561
Property.DataFlow = DIRECTSOUNDDEVICE_DATAFLOW_RENDER;
562
Property.DeviceId = *lpGUID;
564
hr = IKsPropertySet_Get( pPropertySet,
565
&pawin_DSPROPSETID_DirectSoundDevice,
566
DSPROPERTY_DIRECTSOUNDDEVICE_DESCRIPTION_W,
576
//pnpInterface = Property.Interface;
580
#endif /* PAWIN_USE_WDMKS_DEVICE_INFO */
584
GUIDs for emulated devices which we blacklist below.
585
are there more than two of them??
588
GUID IID_IRolandVSCEmulated1 = {0xc2ad1800, 0xb243, 0x11ce, 0xa8, 0xa4, 0x00, 0xaa, 0x00, 0x6c, 0x45, 0x01};
589
GUID IID_IRolandVSCEmulated2 = {0xc2ad1800, 0xb243, 0x11ce, 0xa8, 0xa4, 0x00, 0xaa, 0x00, 0x6c, 0x45, 0x02};
592
#define PA_DEFAULTSAMPLERATESEARCHORDER_COUNT_ (13) /* must match array length below */
593
static double defaultSampleRateSearchOrder_[] =
594
{ 44100.0, 48000.0, 32000.0, 24000.0, 22050.0, 88200.0, 96000.0, 192000.0,
595
16000.0, 12000.0, 11025.0, 9600.0, 8000.0 };
597
/************************************************************************************
598
** Extract capabilities from an output device, and add it to the device info list
599
** if successful. This function assumes that there is enough room in the
600
** device info list to accomodate all entries.
602
** The device will not be added to the device list if any errors are encountered.
604
static PaError AddOutputDeviceInfoFromDirectSound(
605
PaWinDsHostApiRepresentation *winDsHostApi, char *name, LPGUID lpGUID, char *pnpInterface )
607
PaUtilHostApiRepresentation *hostApi = &winDsHostApi->inheritedHostApiRep;
608
PaWinDsDeviceInfo *winDsDeviceInfo = (PaWinDsDeviceInfo*) hostApi->deviceInfos[hostApi->info.deviceCount];
609
PaDeviceInfo *deviceInfo = &winDsDeviceInfo->inheritedDeviceInfo;
611
LPDIRECTSOUND lpDirectSound;
614
PaError result = paNoError;
617
/* Copy GUID to the device info structure. Set pointer. */
620
winDsDeviceInfo->lpGUID = NULL;
624
memcpy( &winDsDeviceInfo->guid, lpGUID, sizeof(GUID) );
625
winDsDeviceInfo->lpGUID = &winDsDeviceInfo->guid;
630
if (IsEqualGUID (&IID_IRolandVSCEmulated1,lpGUID) ||
631
IsEqualGUID (&IID_IRolandVSCEmulated2,lpGUID) )
633
PA_DEBUG(("BLACKLISTED: %s \n",name));
638
/* Create a DirectSound object for the specified GUID
639
Note that using CoCreateInstance doesn't work on windows CE.
641
hr = paWinDsDSoundEntryPoints.DirectSoundCreate( lpGUID, &lpDirectSound, NULL );
643
/** try using CoCreateInstance because DirectSoundCreate was hanging under
644
some circumstances - note this was probably related to the
645
#define BOOL short bug which has now been fixed
646
@todo delete this comment and the following code once we've ensured
650
hr = CoCreateInstance( &CLSID_DirectSound, NULL, CLSCTX_INPROC_SERVER,
651
&IID_IDirectSound, (void**)&lpDirectSound );
655
hr = IDirectSound_Initialize( lpDirectSound, lpGUID );
661
if (hr == DSERR_ALLOCATED)
662
PA_DEBUG(("AddOutputDeviceInfoFromDirectSound %s DSERR_ALLOCATED\n",name));
663
DBUG(("Cannot create DirectSound for %s. Result = 0x%x\n", name, hr ));
665
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",
683
/* Query device characteristics. */
684
memset( &caps, 0, sizeof(caps) );
685
caps.dwSize = sizeof(caps);
686
hr = IDirectSound_GetCaps( lpDirectSound, &caps );
689
DBUG(("Cannot GetCaps() for DirectSound device %s. Result = 0x%x\n", name, hr ));
696
if( caps.dwFlags & DSCAPS_EMULDRIVER )
698
/* If WMME supported, then reject Emulated drivers because they are lousy. */
705
deviceInfo->maxInputChannels = 0;
706
winDsDeviceInfo->deviceInputChannelCountIsKnown = 1;
708
/* DS output capabilities only indicate supported number of channels
709
using two flags which indicate mono and/or stereo.
710
We assume that stereo devices may support more than 2 channels
711
(as is the case with 5.1 devices for example) and so
712
set deviceOutputChannelCountIsKnown to 0 (unknown).
713
In this case OpenStream will try to open the device
714
when the user requests more than 2 channels, rather than
717
if( caps.dwFlags & DSCAPS_PRIMARYSTEREO )
719
deviceInfo->maxOutputChannels = 2;
720
winDsDeviceInfo->deviceOutputChannelCountIsKnown = 0;
724
deviceInfo->maxOutputChannels = 1;
725
winDsDeviceInfo->deviceOutputChannelCountIsKnown = 1;
728
#ifdef PAWIN_USE_WDMKS_DEVICE_INFO
731
int count = PaWin_WDMKS_QueryFilterMaximumChannelCount( pnpInterface, /* isInput= */ 0 );
734
deviceInfo->maxOutputChannels = count;
735
winDsDeviceInfo->deviceOutputChannelCountIsKnown = 1;
738
#endif /* PAWIN_USE_WDMKS_DEVICE_INFO */
740
deviceInfo->defaultLowInputLatency = 0.; /** @todo IMPLEMENT ME */
741
deviceInfo->defaultLowOutputLatency = 0.; /** @todo IMPLEMENT ME */
742
deviceInfo->defaultHighInputLatency = 0.; /** @todo IMPLEMENT ME */
743
deviceInfo->defaultHighOutputLatency = 0.; /** @todo IMPLEMENT ME */
745
/* initialize defaultSampleRate */
747
if( caps.dwFlags & DSCAPS_CONTINUOUSRATE )
749
/* initialize to caps.dwMaxSecondarySampleRate incase none of the standard rates match */
750
deviceInfo->defaultSampleRate = caps.dwMaxSecondarySampleRate;
752
for( i = 0; i < PA_DEFAULTSAMPLERATESEARCHORDER_COUNT_; ++i )
754
if( defaultSampleRateSearchOrder_[i] >= caps.dwMinSecondarySampleRate
755
&& defaultSampleRateSearchOrder_[i] <= caps.dwMaxSecondarySampleRate )
757
deviceInfo->defaultSampleRate = defaultSampleRateSearchOrder_[i];
762
else if( caps.dwMinSecondarySampleRate == caps.dwMaxSecondarySampleRate )
764
if( caps.dwMinSecondarySampleRate == 0 )
767
** On my Thinkpad 380Z, DirectSoundV6 returns min-max=0 !!
768
** But it supports continuous sampling.
769
** So fake range of rates, and hope it really supports it.
771
deviceInfo->defaultSampleRate = 44100.0f;
773
DBUG(("PA - Reported rates both zero. Setting to fake values for device #%s\n", name ));
777
deviceInfo->defaultSampleRate = caps.dwMaxSecondarySampleRate;
780
else if( (caps.dwMinSecondarySampleRate < 1000.0) && (caps.dwMaxSecondarySampleRate > 50000.0) )
782
/* The EWS88MT drivers lie, lie, lie. The say they only support two rates, 100 & 100000.
783
** But we know that they really support a range of rates!
784
** So when we see a ridiculous set of rates, assume it is a range.
786
deviceInfo->defaultSampleRate = 44100.0f;
787
DBUG(("PA - Sample rate range used instead of two odd values for device #%s\n", name ));
789
else deviceInfo->defaultSampleRate = caps.dwMaxSecondarySampleRate;
792
//printf( "min %d max %d\n", caps.dwMinSecondarySampleRate, caps.dwMaxSecondarySampleRate );
793
// dwFlags | DSCAPS_CONTINUOUSRATE
797
IDirectSound_Release( lpDirectSound );
802
deviceInfo->name = name;
805
hostApi->info.defaultOutputDevice = hostApi->info.deviceCount;
807
hostApi->info.deviceCount++;
814
/************************************************************************************
815
** Extract capabilities from an input device, and add it to the device info list
816
** if successful. This function assumes that there is enough room in the
817
** device info list to accomodate all entries.
819
** The device will not be added to the device list if any errors are encountered.
821
static PaError AddInputDeviceInfoFromDirectSoundCapture(
822
PaWinDsHostApiRepresentation *winDsHostApi, char *name, LPGUID lpGUID, char *pnpInterface )
824
PaUtilHostApiRepresentation *hostApi = &winDsHostApi->inheritedHostApiRep;
825
PaWinDsDeviceInfo *winDsDeviceInfo = (PaWinDsDeviceInfo*) hostApi->deviceInfos[hostApi->info.deviceCount];
826
PaDeviceInfo *deviceInfo = &winDsDeviceInfo->inheritedDeviceInfo;
828
LPDIRECTSOUNDCAPTURE lpDirectSoundCapture;
831
PaError result = paNoError;
833
/* Copy GUID to the device info structure. Set pointer. */
836
winDsDeviceInfo->lpGUID = NULL;
840
winDsDeviceInfo->lpGUID = &winDsDeviceInfo->guid;
841
memcpy( &winDsDeviceInfo->guid, lpGUID, sizeof(GUID) );
844
hr = paWinDsDSoundEntryPoints.DirectSoundCaptureCreate( lpGUID, &lpDirectSoundCapture, NULL );
846
/** try using CoCreateInstance because DirectSoundCreate was hanging under
847
some circumstances - note this was probably related to the
848
#define BOOL short bug which has now been fixed
849
@todo delete this comment and the following code once we've ensured
853
hr = CoCreateInstance( &CLSID_DirectSoundCapture, NULL, CLSCTX_INPROC_SERVER,
854
&IID_IDirectSoundCapture, (void**)&lpDirectSoundCapture );
858
DBUG(("Cannot create Capture for %s. Result = 0x%x\n", name, hr ));
863
/* Query device characteristics. */
864
memset( &caps, 0, sizeof(caps) );
865
caps.dwSize = sizeof(caps);
866
hr = IDirectSoundCapture_GetCaps( lpDirectSoundCapture, &caps );
869
DBUG(("Cannot GetCaps() for Capture device %s. Result = 0x%x\n", name, hr ));
875
if( caps.dwFlags & DSCAPS_EMULDRIVER )
877
/* If WMME supported, then reject Emulated drivers because they are lousy. */
884
deviceInfo->maxInputChannels = caps.dwChannels;
885
winDsDeviceInfo->deviceInputChannelCountIsKnown = 1;
887
deviceInfo->maxOutputChannels = 0;
888
winDsDeviceInfo->deviceOutputChannelCountIsKnown = 1;
890
#ifdef PAWIN_USE_WDMKS_DEVICE_INFO
893
int count = PaWin_WDMKS_QueryFilterMaximumChannelCount( pnpInterface, /* isInput= */ 1 );
896
deviceInfo->maxInputChannels = count;
897
winDsDeviceInfo->deviceInputChannelCountIsKnown = 1;
900
#endif /* PAWIN_USE_WDMKS_DEVICE_INFO */
902
deviceInfo->defaultLowInputLatency = 0.; /** @todo IMPLEMENT ME */
903
deviceInfo->defaultLowOutputLatency = 0.; /** @todo IMPLEMENT ME */
904
deviceInfo->defaultHighInputLatency = 0.; /** @todo IMPLEMENT ME */
905
deviceInfo->defaultHighOutputLatency = 0.; /** @todo IMPLEMENT ME */
907
/* constants from a WINE patch by Francois Gouget, see:
908
http://www.winehq.com/hypermail/wine-patches/2003/01/0290.html
911
Date: Fri, 14 May 2004 10:38:12 +0200 (CEST)
912
From: Francois Gouget <fgouget@ ... .fr>
913
To: Ross Bencina <rbencina@ ... .au>
914
Subject: Re: Permission to use wine 48/96 wave patch in BSD licensed library
918
I give you permission to use the patch below under the BSD license.
919
http://www.winehq.com/hypermail/wine-patches/2003/01/0290.html
923
#ifndef WAVE_FORMAT_48M08
924
#define WAVE_FORMAT_48M08 0x00001000 /* 48 kHz, Mono, 8-bit */
925
#define WAVE_FORMAT_48S08 0x00002000 /* 48 kHz, Stereo, 8-bit */
926
#define WAVE_FORMAT_48M16 0x00004000 /* 48 kHz, Mono, 16-bit */
927
#define WAVE_FORMAT_48S16 0x00008000 /* 48 kHz, Stereo, 16-bit */
928
#define WAVE_FORMAT_96M08 0x00010000 /* 96 kHz, Mono, 8-bit */
929
#define WAVE_FORMAT_96S08 0x00020000 /* 96 kHz, Stereo, 8-bit */
930
#define WAVE_FORMAT_96M16 0x00040000 /* 96 kHz, Mono, 16-bit */
931
#define WAVE_FORMAT_96S16 0x00080000 /* 96 kHz, Stereo, 16-bit */
934
/* defaultSampleRate */
935
if( caps.dwChannels == 2 )
937
if( caps.dwFormats & WAVE_FORMAT_4S16 )
938
deviceInfo->defaultSampleRate = 44100.0;
939
else if( caps.dwFormats & WAVE_FORMAT_48S16 )
940
deviceInfo->defaultSampleRate = 48000.0;
941
else if( caps.dwFormats & WAVE_FORMAT_2S16 )
942
deviceInfo->defaultSampleRate = 22050.0;
943
else if( caps.dwFormats & WAVE_FORMAT_1S16 )
944
deviceInfo->defaultSampleRate = 11025.0;
945
else if( caps.dwFormats & WAVE_FORMAT_96S16 )
946
deviceInfo->defaultSampleRate = 96000.0;
948
deviceInfo->defaultSampleRate = 0.;
950
else if( caps.dwChannels == 1 )
952
if( caps.dwFormats & WAVE_FORMAT_4M16 )
953
deviceInfo->defaultSampleRate = 44100.0;
954
else if( caps.dwFormats & WAVE_FORMAT_48M16 )
955
deviceInfo->defaultSampleRate = 48000.0;
956
else if( caps.dwFormats & WAVE_FORMAT_2M16 )
957
deviceInfo->defaultSampleRate = 22050.0;
958
else if( caps.dwFormats & WAVE_FORMAT_1M16 )
959
deviceInfo->defaultSampleRate = 11025.0;
960
else if( caps.dwFormats & WAVE_FORMAT_96M16 )
961
deviceInfo->defaultSampleRate = 96000.0;
963
deviceInfo->defaultSampleRate = 0.;
965
else deviceInfo->defaultSampleRate = 0.;
969
IDirectSoundCapture_Release( lpDirectSoundCapture );
974
deviceInfo->name = name;
977
hostApi->info.defaultInputDevice = hostApi->info.deviceCount;
979
hostApi->info.deviceCount++;
986
/***********************************************************************************/
987
PaError PaWinDs_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex hostApiIndex )
989
PaError result = paNoError;
991
PaWinDsHostApiRepresentation *winDsHostApi;
992
DSDeviceNamesAndGUIDs deviceNamesAndGUIDs;
994
PaWinDsDeviceInfo *deviceInfoArray;
995
char comWasInitialized = 0;
998
If COM is already initialized CoInitialize will either return
999
FALSE, or RPC_E_CHANGED_MODE if it was initialised in a different
1000
threading mode. In either case we shouldn't consider it an error
1001
but we need to be careful to not call CoUninitialize() if
1002
RPC_E_CHANGED_MODE was returned.
1005
HRESULT hr = CoInitialize(NULL);
1006
if( FAILED(hr) && hr != RPC_E_CHANGED_MODE )
1007
return paUnanticipatedHostError;
1009
if( hr != RPC_E_CHANGED_MODE )
1010
comWasInitialized = 1;
1012
/* initialise guid vectors so they can be safely deleted on error */
1013
deviceNamesAndGUIDs.winDsHostApi = NULL;
1014
deviceNamesAndGUIDs.inputNamesAndGUIDs.items = NULL;
1015
deviceNamesAndGUIDs.outputNamesAndGUIDs.items = NULL;
1017
PaWinDs_InitializeDSoundEntryPoints();
1019
winDsHostApi = (PaWinDsHostApiRepresentation*)PaUtil_AllocateMemory( sizeof(PaWinDsHostApiRepresentation) );
1022
result = paInsufficientMemory;
1026
winDsHostApi->comWasInitialized = comWasInitialized;
1028
winDsHostApi->allocations = PaUtil_CreateAllocationGroup();
1029
if( !winDsHostApi->allocations )
1031
result = paInsufficientMemory;
1035
*hostApi = &winDsHostApi->inheritedHostApiRep;
1036
(*hostApi)->info.structVersion = 1;
1037
(*hostApi)->info.type = paDirectSound;
1038
(*hostApi)->info.name = "Windows DirectSound";
1040
(*hostApi)->info.deviceCount = 0;
1041
(*hostApi)->info.defaultInputDevice = paNoDevice;
1042
(*hostApi)->info.defaultOutputDevice = paNoDevice;
1045
/* DSound - enumerate devices to count them and to gather their GUIDs */
1047
result = InitializeDSDeviceNameAndGUIDVector( &deviceNamesAndGUIDs.inputNamesAndGUIDs, winDsHostApi->allocations );
1048
if( result != paNoError )
1051
result = InitializeDSDeviceNameAndGUIDVector( &deviceNamesAndGUIDs.outputNamesAndGUIDs, winDsHostApi->allocations );
1052
if( result != paNoError )
1055
paWinDsDSoundEntryPoints.DirectSoundCaptureEnumerate( (LPDSENUMCALLBACK)CollectGUIDsProc, (void *)&deviceNamesAndGUIDs.inputNamesAndGUIDs );
1057
paWinDsDSoundEntryPoints.DirectSoundEnumerate( (LPDSENUMCALLBACK)CollectGUIDsProc, (void *)&deviceNamesAndGUIDs.outputNamesAndGUIDs );
1059
if( deviceNamesAndGUIDs.inputNamesAndGUIDs.enumerationError != paNoError )
1061
result = deviceNamesAndGUIDs.inputNamesAndGUIDs.enumerationError;
1065
if( deviceNamesAndGUIDs.outputNamesAndGUIDs.enumerationError != paNoError )
1067
result = deviceNamesAndGUIDs.outputNamesAndGUIDs.enumerationError;
1071
deviceCount = deviceNamesAndGUIDs.inputNamesAndGUIDs.count + deviceNamesAndGUIDs.outputNamesAndGUIDs.count;
1073
#ifdef PAWIN_USE_WDMKS_DEVICE_INFO
1074
if( deviceCount > 0 )
1076
deviceNamesAndGUIDs.winDsHostApi = winDsHostApi;
1077
FindDevicePnpInterfaces( &deviceNamesAndGUIDs );
1079
#endif /* PAWIN_USE_WDMKS_DEVICE_INFO */
1081
if( deviceCount > 0 )
1083
/* allocate array for pointers to PaDeviceInfo structs */
1084
(*hostApi)->deviceInfos = (PaDeviceInfo**)PaUtil_GroupAllocateMemory(
1085
winDsHostApi->allocations, sizeof(PaDeviceInfo*) * deviceCount );
1086
if( !(*hostApi)->deviceInfos )
1088
result = paInsufficientMemory;
1092
/* allocate all PaDeviceInfo structs in a contiguous block */
1093
deviceInfoArray = (PaWinDsDeviceInfo*)PaUtil_GroupAllocateMemory(
1094
winDsHostApi->allocations, sizeof(PaWinDsDeviceInfo) * deviceCount );
1095
if( !deviceInfoArray )
1097
result = paInsufficientMemory;
1101
for( i=0; i < deviceCount; ++i )
1103
PaDeviceInfo *deviceInfo = &deviceInfoArray[i].inheritedDeviceInfo;
1104
deviceInfo->structVersion = 2;
1105
deviceInfo->hostApi = hostApiIndex;
1106
deviceInfo->name = 0;
1107
(*hostApi)->deviceInfos[i] = deviceInfo;
1110
for( i=0; i < deviceNamesAndGUIDs.inputNamesAndGUIDs.count; ++i )
1112
result = AddInputDeviceInfoFromDirectSoundCapture( winDsHostApi,
1113
deviceNamesAndGUIDs.inputNamesAndGUIDs.items[i].name,
1114
deviceNamesAndGUIDs.inputNamesAndGUIDs.items[i].lpGUID,
1115
deviceNamesAndGUIDs.inputNamesAndGUIDs.items[i].pnpInterface );
1116
if( result != paNoError )
1120
for( i=0; i < deviceNamesAndGUIDs.outputNamesAndGUIDs.count; ++i )
1122
result = AddOutputDeviceInfoFromDirectSound( winDsHostApi,
1123
deviceNamesAndGUIDs.outputNamesAndGUIDs.items[i].name,
1124
deviceNamesAndGUIDs.outputNamesAndGUIDs.items[i].lpGUID,
1125
deviceNamesAndGUIDs.outputNamesAndGUIDs.items[i].pnpInterface );
1126
if( result != paNoError )
1131
result = TerminateDSDeviceNameAndGUIDVector( &deviceNamesAndGUIDs.inputNamesAndGUIDs );
1132
if( result != paNoError )
1135
result = TerminateDSDeviceNameAndGUIDVector( &deviceNamesAndGUIDs.outputNamesAndGUIDs );
1136
if( result != paNoError )
1140
(*hostApi)->Terminate = Terminate;
1141
(*hostApi)->OpenStream = OpenStream;
1142
(*hostApi)->IsFormatSupported = IsFormatSupported;
1144
PaUtil_InitializeStreamInterface( &winDsHostApi->callbackStreamInterface, CloseStream, StartStream,
1145
StopStream, AbortStream, IsStreamStopped, IsStreamActive,
1146
GetStreamTime, GetStreamCpuLoad,
1147
PaUtil_DummyRead, PaUtil_DummyWrite,
1148
PaUtil_DummyGetReadAvailable, PaUtil_DummyGetWriteAvailable );
1150
PaUtil_InitializeStreamInterface( &winDsHostApi->blockingStreamInterface, CloseStream, StartStream,
1151
StopStream, AbortStream, IsStreamStopped, IsStreamActive,
1152
GetStreamTime, PaUtil_DummyGetCpuLoad,
1153
ReadStream, WriteStream, GetStreamReadAvailable, GetStreamWriteAvailable );
1160
if( winDsHostApi->allocations )
1162
PaUtil_FreeAllAllocations( winDsHostApi->allocations );
1163
PaUtil_DestroyAllocationGroup( winDsHostApi->allocations );
1166
PaUtil_FreeMemory( winDsHostApi );
1169
TerminateDSDeviceNameAndGUIDVector( &deviceNamesAndGUIDs.inputNamesAndGUIDs );
1170
TerminateDSDeviceNameAndGUIDVector( &deviceNamesAndGUIDs.outputNamesAndGUIDs );
1172
if( comWasInitialized )
1179
/***********************************************************************************/
1180
static void Terminate( struct PaUtilHostApiRepresentation *hostApi )
1182
PaWinDsHostApiRepresentation *winDsHostApi = (PaWinDsHostApiRepresentation*)hostApi;
1183
char comWasInitialized = winDsHostApi->comWasInitialized;
1187
- clean up any resources not handled by the allocation group
1190
if( winDsHostApi->allocations )
1192
PaUtil_FreeAllAllocations( winDsHostApi->allocations );
1193
PaUtil_DestroyAllocationGroup( winDsHostApi->allocations );
1196
PaUtil_FreeMemory( winDsHostApi );
1198
PaWinDs_TerminateDSoundEntryPoints();
1200
if( comWasInitialized )
1205
/* Set minimal latency based on whether NT or Win95.
1206
* NT has higher latency.
1208
static int PaWinDS_GetMinSystemLatency( void )
1211
/* Set minimal latency based on whether NT or other OS.
1212
* NT has higher latency.
1215
osvi.dwOSVersionInfoSize = sizeof( osvi );
1216
GetVersionEx( &osvi );
1217
DBUG(("PA - PlatformId = 0x%x\n", osvi.dwPlatformId ));
1218
DBUG(("PA - MajorVersion = 0x%x\n", osvi.dwMajorVersion ));
1219
DBUG(("PA - MinorVersion = 0x%x\n", osvi.dwMinorVersion ));
1221
if( (osvi.dwMajorVersion == 4) && (osvi.dwPlatformId == 2) )
1223
minLatencyMsec = PA_WIN_NT_LATENCY;
1225
else if(osvi.dwMajorVersion >= 5)
1227
minLatencyMsec = PA_WIN_WDM_LATENCY;
1231
minLatencyMsec = PA_WIN_9X_LATENCY;
1233
return minLatencyMsec;
1236
static PaError ValidateWinDirectSoundSpecificStreamInfo(
1237
const PaStreamParameters *streamParameters,
1238
const PaWinDirectSoundStreamInfo *streamInfo )
1242
if( streamInfo->size != sizeof( PaWinDirectSoundStreamInfo )
1243
|| streamInfo->version != 1 )
1245
return paIncompatibleHostApiSpecificStreamInfo;
1252
/***********************************************************************************/
1253
static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi,
1254
const PaStreamParameters *inputParameters,
1255
const PaStreamParameters *outputParameters,
1259
PaWinDsDeviceInfo *inputWinDsDeviceInfo, *outputWinDsDeviceInfo;
1260
PaDeviceInfo *inputDeviceInfo, *outputDeviceInfo;
1261
int inputChannelCount, outputChannelCount;
1262
PaSampleFormat inputSampleFormat, outputSampleFormat;
1263
PaWinDirectSoundStreamInfo *inputStreamInfo, *outputStreamInfo;
1265
if( inputParameters )
1267
inputWinDsDeviceInfo = (PaWinDsDeviceInfo*) hostApi->deviceInfos[ inputParameters->device ];
1268
inputDeviceInfo = &inputWinDsDeviceInfo->inheritedDeviceInfo;
1270
inputChannelCount = inputParameters->channelCount;
1271
inputSampleFormat = inputParameters->sampleFormat;
1273
/* unless alternate device specification is supported, reject the use of
1274
paUseHostApiSpecificDeviceSpecification */
1276
if( inputParameters->device == paUseHostApiSpecificDeviceSpecification )
1277
return paInvalidDevice;
1279
/* check that input device can support inputChannelCount */
1280
if( inputWinDsDeviceInfo->deviceInputChannelCountIsKnown
1281
&& inputChannelCount > inputDeviceInfo->maxInputChannels )
1282
return paInvalidChannelCount;
1284
/* validate inputStreamInfo */
1285
inputStreamInfo = (PaWinDirectSoundStreamInfo*)inputParameters->hostApiSpecificStreamInfo;
1286
result = ValidateWinDirectSoundSpecificStreamInfo( inputParameters, inputStreamInfo );
1287
if( result != paNoError ) return result;
1291
inputChannelCount = 0;
1294
if( outputParameters )
1296
outputWinDsDeviceInfo = (PaWinDsDeviceInfo*) hostApi->deviceInfos[ outputParameters->device ];
1297
outputDeviceInfo = &outputWinDsDeviceInfo->inheritedDeviceInfo;
1299
outputChannelCount = outputParameters->channelCount;
1300
outputSampleFormat = outputParameters->sampleFormat;
1302
/* unless alternate device specification is supported, reject the use of
1303
paUseHostApiSpecificDeviceSpecification */
1305
if( outputParameters->device == paUseHostApiSpecificDeviceSpecification )
1306
return paInvalidDevice;
1308
/* check that output device can support inputChannelCount */
1309
if( outputWinDsDeviceInfo->deviceOutputChannelCountIsKnown
1310
&& outputChannelCount > outputDeviceInfo->maxOutputChannels )
1311
return paInvalidChannelCount;
1313
/* validate outputStreamInfo */
1314
outputStreamInfo = (PaWinDirectSoundStreamInfo*)outputParameters->hostApiSpecificStreamInfo;
1315
result = ValidateWinDirectSoundSpecificStreamInfo( outputParameters, outputStreamInfo );
1316
if( result != paNoError ) return result;
1320
outputChannelCount = 0;
1326
- if a full duplex stream is requested, check that the combination
1327
of input and output parameters is supported if necessary
1329
- check that the device supports sampleRate
1331
Because the buffer adapter handles conversion between all standard
1332
sample formats, the following checks are only required if paCustomFormat
1333
is implemented, or under some other unusual conditions.
1335
- check that input device can support inputSampleFormat, or that
1336
we have the capability to convert from outputSampleFormat to
1339
- check that output device can support outputSampleFormat, or that
1340
we have the capability to convert from outputSampleFormat to
1344
return paFormatIsSupported;
1348
/*************************************************************************
1349
** Determine minimum number of buffers required for this host based
1350
** on minimum latency. Latency can be optionally set by user by setting
1351
** an environment variable. For example, to set latency to 200 msec, put:
1353
** set PA_MIN_LATENCY_MSEC=200
1355
** in the AUTOEXEC.BAT file and reboot.
1356
** If the environment variable is not set, then the latency will be determined
1357
** based on the OS. Windows NT has higher latency than Win95.
1359
#define PA_LATENCY_ENV_NAME ("PA_MIN_LATENCY_MSEC")
1360
#define PA_ENV_BUF_SIZE (32)
1362
static int PaWinDs_GetMinLatencyFrames( double sampleRate )
1364
char envbuf[PA_ENV_BUF_SIZE];
1366
int minLatencyMsec = 0;
1368
/* Let user determine minimal latency by setting environment variable. */
1369
hresult = GetEnvironmentVariable( PA_LATENCY_ENV_NAME, envbuf, PA_ENV_BUF_SIZE );
1370
if( (hresult > 0) && (hresult < PA_ENV_BUF_SIZE) )
1372
minLatencyMsec = atoi( envbuf );
1376
minLatencyMsec = PaWinDS_GetMinSystemLatency();
1377
#if PA_USE_HIGH_LATENCY
1378
PRINT(("PA - Minimum Latency set to %d msec!\n", minLatencyMsec ));
1383
return (int) (minLatencyMsec * sampleRate * SECONDS_PER_MSEC);
1387
static HRESULT InitInputBuffer( PaWinDsStream *stream, PaSampleFormat sampleFormat, unsigned long nFrameRate, WORD nChannels, int bytesPerBuffer, PaWinWaveFormatChannelMask channelMask )
1389
DSCBUFFERDESC captureDesc;
1390
PaWinWaveFormat waveFormat;
1393
stream->bytesPerInputFrame = nChannels * Pa_GetSampleSize(sampleFormat);
1395
stream->inputSize = bytesPerBuffer;
1396
// ----------------------------------------------------------------------
1397
// Setup the secondary buffer description
1398
ZeroMemory(&captureDesc, sizeof(DSCBUFFERDESC));
1399
captureDesc.dwSize = sizeof(DSCBUFFERDESC);
1400
captureDesc.dwFlags = 0;
1401
captureDesc.dwBufferBytes = bytesPerBuffer;
1402
captureDesc.lpwfxFormat = (WAVEFORMATEX*)&waveFormat;
1404
// Create the capture buffer
1406
// first try WAVEFORMATEXTENSIBLE. if this fails, fall back to WAVEFORMATEX
1407
PaWin_InitializeWaveFormatExtensible( &waveFormat, nChannels,
1408
sampleFormat, PaWin_SampleFormatToLinearWaveFormatTag( sampleFormat ),
1409
nFrameRate, channelMask );
1411
if( IDirectSoundCapture_CreateCaptureBuffer( stream->pDirectSoundCapture,
1412
&captureDesc, &stream->pDirectSoundInputBuffer, NULL) != DS_OK )
1414
PaWin_InitializeWaveFormatEx( &waveFormat, nChannels, sampleFormat,
1415
PaWin_SampleFormatToLinearWaveFormatTag( sampleFormat ), nFrameRate );
1417
if ((result = IDirectSoundCapture_CreateCaptureBuffer( stream->pDirectSoundCapture,
1418
&captureDesc, &stream->pDirectSoundInputBuffer, NULL)) != DS_OK) return result;
1421
stream->readOffset = 0; // reset last read position to start of buffer
1426
static HRESULT InitOutputBuffer( PaWinDsStream *stream, PaSampleFormat sampleFormat, unsigned long nFrameRate, WORD nChannels, int bytesPerBuffer, PaWinWaveFormatChannelMask channelMask )
1428
/** @todo FIXME: if InitOutputBuffer returns an error I'm not sure it frees all resources cleanly */
1433
LPDIRECTSOUNDBUFFER pPrimaryBuffer;
1436
PaWinWaveFormat waveFormat;
1437
DSBUFFERDESC primaryDesc;
1438
DSBUFFERDESC secondaryDesc;
1439
unsigned char* pDSBuffData;
1440
LARGE_INTEGER counterFrequency;
1441
int bytesPerSample = Pa_GetSampleSize(sampleFormat);
1443
stream->outputBufferSizeBytes = bytesPerBuffer;
1444
stream->outputIsRunning = FALSE;
1445
stream->outputUnderflowCount = 0;
1446
stream->dsw_framesWritten = 0;
1447
stream->bytesPerOutputFrame = nChannels * bytesPerSample;
1449
// We were using getForegroundWindow() but sometimes the ForegroundWindow may not be the
1450
// applications's window. Also if that window is closed before the Buffer is closed
1451
// then DirectSound can crash. (Thanks for Scott Patterson for reporting this.)
1452
// So we will use GetDesktopWindow() which was suggested by Miller Puckette.
1453
// hWnd = GetForegroundWindow();
1455
// FIXME: The example code I have on the net creates a hidden window that
1456
// is managed by our code - I think we should do that - one hidden
1457
// window for the whole of Pa_DS
1459
hWnd = GetDesktopWindow();
1461
// Set cooperative level to DSSCL_EXCLUSIVE so that we can get 16 bit output, 44.1 KHz.
1462
// Exclusize also prevents unexpected sounds from other apps during a performance.
1463
if ((hr = IDirectSound_SetCooperativeLevel( stream->pDirectSound,
1464
hWnd, DSSCL_EXCLUSIVE)) != DS_OK)
1469
// -----------------------------------------------------------------------
1470
// Create primary buffer and set format just so we can specify our custom format.
1471
// Otherwise we would be stuck with the default which might be 8 bit or 22050 Hz.
1472
// Setup the primary buffer description
1473
ZeroMemory(&primaryDesc, sizeof(DSBUFFERDESC));
1474
primaryDesc.dwSize = sizeof(DSBUFFERDESC);
1475
primaryDesc.dwFlags = DSBCAPS_PRIMARYBUFFER; // all panning, mixing, etc done by synth
1476
primaryDesc.dwBufferBytes = 0;
1477
primaryDesc.lpwfxFormat = NULL;
1478
// Create the buffer
1479
if ((result = IDirectSound_CreateSoundBuffer( stream->pDirectSound,
1480
&primaryDesc, &pPrimaryBuffer, NULL)) != DS_OK) return result;
1482
// Set the primary buffer's format
1484
// first try WAVEFORMATEXTENSIBLE. if this fails, fall back to WAVEFORMATEX
1485
PaWin_InitializeWaveFormatExtensible( &waveFormat, nChannels,
1486
sampleFormat, PaWin_SampleFormatToLinearWaveFormatTag( sampleFormat ),
1487
nFrameRate, channelMask );
1489
if( IDirectSoundBuffer_SetFormat( pPrimaryBuffer, (WAVEFORMATEX*)&waveFormat) != DS_OK )
1491
PaWin_InitializeWaveFormatEx( &waveFormat, nChannels, sampleFormat,
1492
PaWin_SampleFormatToLinearWaveFormatTag( sampleFormat ), nFrameRate );
1494
if((result = IDirectSoundBuffer_SetFormat( pPrimaryBuffer, (WAVEFORMATEX*)&waveFormat)) != DS_OK) return result;
1497
// ----------------------------------------------------------------------
1498
// Setup the secondary buffer description
1499
ZeroMemory(&secondaryDesc, sizeof(DSBUFFERDESC));
1500
secondaryDesc.dwSize = sizeof(DSBUFFERDESC);
1501
secondaryDesc.dwFlags = DSBCAPS_GLOBALFOCUS | DSBCAPS_GETCURRENTPOSITION2;
1502
secondaryDesc.dwBufferBytes = bytesPerBuffer;
1503
secondaryDesc.lpwfxFormat = (WAVEFORMATEX*)&waveFormat;
1504
// Create the secondary buffer
1505
if ((result = IDirectSound_CreateSoundBuffer( stream->pDirectSound,
1506
&secondaryDesc, &stream->pDirectSoundOutputBuffer, NULL)) != DS_OK) return result;
1507
// Lock the DS buffer
1508
if ((result = IDirectSoundBuffer_Lock( stream->pDirectSoundOutputBuffer, 0, stream->outputBufferSizeBytes, (LPVOID*)&pDSBuffData,
1509
&dwDataLen, NULL, 0, 0)) != DS_OK) return result;
1510
// Zero the DS buffer
1511
ZeroMemory(pDSBuffData, dwDataLen);
1512
// Unlock the DS buffer
1513
if ((result = IDirectSoundBuffer_Unlock( stream->pDirectSoundOutputBuffer, pDSBuffData, dwDataLen, NULL, 0)) != DS_OK) return result;
1514
if( QueryPerformanceFrequency( &counterFrequency ) )
1516
int framesInBuffer = bytesPerBuffer / (nChannels * bytesPerSample);
1517
stream->perfCounterTicksPerBuffer.QuadPart = (counterFrequency.QuadPart * framesInBuffer) / nFrameRate;
1521
stream->perfCounterTicksPerBuffer.QuadPart = 0;
1523
// Let DSound set the starting write position because if we set it to zero, it looks like the
1524
// buffer is full to begin with. This causes a long pause before sound starts when using large buffers.
1525
hr = IDirectSoundBuffer_GetCurrentPosition( stream->pDirectSoundOutputBuffer,
1526
&playCursor, &stream->outputBufferWriteOffsetBytes );
1531
stream->dsw_framesWritten = stream->outputBufferWriteOffsetBytes / stream->bytesPerOutputFrame;
1532
/* printf("DSW_InitOutputBuffer: playCursor = %d, writeCursor = %d\n", playCursor, dsw->dsw_WriteOffset ); */
1536
/***********************************************************************************/
1537
/* see pa_hostapi.h for a list of validity guarantees made about OpenStream parameters */
1539
static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
1541
const PaStreamParameters *inputParameters,
1542
const PaStreamParameters *outputParameters,
1544
unsigned long framesPerBuffer,
1545
PaStreamFlags streamFlags,
1546
PaStreamCallback *streamCallback,
1549
PaError result = paNoError;
1550
PaWinDsHostApiRepresentation *winDsHostApi = (PaWinDsHostApiRepresentation*)hostApi;
1551
PaWinDsStream *stream = 0;
1552
PaWinDsDeviceInfo *inputWinDsDeviceInfo, *outputWinDsDeviceInfo;
1553
PaDeviceInfo *inputDeviceInfo, *outputDeviceInfo;
1554
int inputChannelCount, outputChannelCount;
1555
PaSampleFormat inputSampleFormat, outputSampleFormat;
1556
PaSampleFormat hostInputSampleFormat, hostOutputSampleFormat;
1557
unsigned long suggestedInputLatencyFrames, suggestedOutputLatencyFrames;
1558
PaWinDirectSoundStreamInfo *inputStreamInfo, *outputStreamInfo;
1559
PaWinWaveFormatChannelMask inputChannelMask, outputChannelMask;
1561
if( inputParameters )
1563
inputWinDsDeviceInfo = (PaWinDsDeviceInfo*) hostApi->deviceInfos[ inputParameters->device ];
1564
inputDeviceInfo = &inputWinDsDeviceInfo->inheritedDeviceInfo;
1566
inputChannelCount = inputParameters->channelCount;
1567
inputSampleFormat = inputParameters->sampleFormat;
1568
suggestedInputLatencyFrames = (unsigned long)(inputParameters->suggestedLatency * sampleRate);
1570
/* IDEA: the following 3 checks could be performed by default by pa_front
1571
unless some flag indicated otherwise */
1573
/* unless alternate device specification is supported, reject the use of
1574
paUseHostApiSpecificDeviceSpecification */
1575
if( inputParameters->device == paUseHostApiSpecificDeviceSpecification )
1576
return paInvalidDevice;
1578
/* check that input device can support inputChannelCount */
1579
if( inputWinDsDeviceInfo->deviceInputChannelCountIsKnown
1580
&& inputChannelCount > inputDeviceInfo->maxInputChannels )
1581
return paInvalidChannelCount;
1583
/* validate hostApiSpecificStreamInfo */
1584
inputStreamInfo = (PaWinDirectSoundStreamInfo*)inputParameters->hostApiSpecificStreamInfo;
1585
result = ValidateWinDirectSoundSpecificStreamInfo( inputParameters, inputStreamInfo );
1586
if( result != paNoError ) return result;
1588
if( inputStreamInfo && inputStreamInfo->flags & paWinDirectSoundUseChannelMask )
1589
inputChannelMask = inputStreamInfo->channelMask;
1591
inputChannelMask = PaWin_DefaultChannelMask( inputChannelCount );
1595
inputChannelCount = 0;
1596
inputSampleFormat = 0;
1597
suggestedInputLatencyFrames = 0;
1601
if( outputParameters )
1603
outputWinDsDeviceInfo = (PaWinDsDeviceInfo*) hostApi->deviceInfos[ outputParameters->device ];
1604
outputDeviceInfo = &outputWinDsDeviceInfo->inheritedDeviceInfo;
1606
outputChannelCount = outputParameters->channelCount;
1607
outputSampleFormat = outputParameters->sampleFormat;
1608
suggestedOutputLatencyFrames = (unsigned long)(outputParameters->suggestedLatency * sampleRate);
1610
/* unless alternate device specification is supported, reject the use of
1611
paUseHostApiSpecificDeviceSpecification */
1612
if( outputParameters->device == paUseHostApiSpecificDeviceSpecification )
1613
return paInvalidDevice;
1615
/* check that output device can support outputChannelCount */
1616
if( outputWinDsDeviceInfo->deviceOutputChannelCountIsKnown
1617
&& outputChannelCount > outputDeviceInfo->maxOutputChannels )
1618
return paInvalidChannelCount;
1620
/* validate hostApiSpecificStreamInfo */
1621
outputStreamInfo = (PaWinDirectSoundStreamInfo*)outputParameters->hostApiSpecificStreamInfo;
1622
result = ValidateWinDirectSoundSpecificStreamInfo( outputParameters, outputStreamInfo );
1623
if( result != paNoError ) return result;
1625
if( outputStreamInfo && outputStreamInfo->flags & paWinDirectSoundUseChannelMask )
1626
outputChannelMask = outputStreamInfo->channelMask;
1628
outputChannelMask = PaWin_DefaultChannelMask( outputChannelCount );
1632
outputChannelCount = 0;
1633
outputSampleFormat = 0;
1634
suggestedOutputLatencyFrames = 0;
1641
( the following two checks are taken care of by PaUtil_InitializeBufferProcessor() )
1643
- check that input device can support inputSampleFormat, or that
1644
we have the capability to convert from outputSampleFormat to
1647
- check that output device can support outputSampleFormat, or that
1648
we have the capability to convert from outputSampleFormat to
1651
- if a full duplex stream is requested, check that the combination
1652
of input and output parameters is supported
1654
- check that the device supports sampleRate
1656
- alter sampleRate to a close allowable rate if possible / necessary
1658
- validate suggestedInputLatency and suggestedOutputLatency parameters,
1659
use default values where necessary
1663
/* validate platform specific flags */
1664
if( (streamFlags & paPlatformSpecificFlags) != 0 )
1665
return paInvalidFlag; /* unexpected platform specific flag */
1668
stream = (PaWinDsStream*)PaUtil_AllocateMemory( sizeof(PaWinDsStream) );
1671
result = paInsufficientMemory;
1675
memset( stream, 0, sizeof(PaWinDsStream) ); /* initialize all stream variables to 0 */
1677
if( streamCallback )
1679
PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation,
1680
&winDsHostApi->callbackStreamInterface, streamCallback, userData );
1684
PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation,
1685
&winDsHostApi->blockingStreamInterface, streamCallback, userData );
1688
PaUtil_InitializeCpuLoadMeasurer( &stream->cpuLoadMeasurer, sampleRate );
1691
if( inputParameters )
1693
/* IMPLEMENT ME - establish which host formats are available */
1694
PaSampleFormat nativeInputFormats = paInt16;
1695
//PaSampleFormat nativeFormats = paUInt8 | paInt16 | paInt24 | paInt32 | paFloat32;
1697
hostInputSampleFormat =
1698
PaUtil_SelectClosestAvailableFormat( nativeInputFormats, inputParameters->sampleFormat );
1702
hostInputSampleFormat = 0;
1705
if( outputParameters )
1707
/* IMPLEMENT ME - establish which host formats are available */
1708
PaSampleFormat nativeOutputFormats = paInt16;
1709
//PaSampleFormat nativeOutputFormats = paUInt8 | paInt16 | paInt24 | paInt32 | paFloat32;
1711
hostOutputSampleFormat =
1712
PaUtil_SelectClosestAvailableFormat( nativeOutputFormats, outputParameters->sampleFormat );
1716
hostOutputSampleFormat = 0;
1719
result = PaUtil_InitializeBufferProcessor( &stream->bufferProcessor,
1720
inputChannelCount, inputSampleFormat, hostInputSampleFormat,
1721
outputChannelCount, outputSampleFormat, hostOutputSampleFormat,
1722
sampleRate, streamFlags, framesPerBuffer,
1723
framesPerBuffer, /* ignored in paUtilVariableHostBufferSizePartialUsageAllowed mode. */
1724
/* This next mode is required because DS can split the host buffer when it wraps around. */
1725
paUtilVariableHostBufferSizePartialUsageAllowed,
1726
streamCallback, userData );
1727
if( result != paNoError )
1731
stream->streamRepresentation.streamInfo.inputLatency =
1732
PaUtil_GetBufferProcessorInputLatency(&stream->bufferProcessor); /* FIXME: not initialised anywhere else */
1733
stream->streamRepresentation.streamInfo.outputLatency =
1734
PaUtil_GetBufferProcessorOutputLatency(&stream->bufferProcessor); /* FIXME: not initialised anywhere else */
1735
stream->streamRepresentation.streamInfo.sampleRate = sampleRate;
1738
/* DirectSound specific initialization */
1741
int bytesPerDirectSoundBuffer;
1742
int userLatencyFrames;
1743
int minLatencyFrames;
1745
stream->timerID = 0;
1747
/* Get system minimum latency. */
1748
minLatencyFrames = PaWinDs_GetMinLatencyFrames( sampleRate );
1750
/* Let user override latency by passing latency parameter. */
1751
userLatencyFrames = (suggestedInputLatencyFrames > suggestedOutputLatencyFrames)
1752
? suggestedInputLatencyFrames
1753
: suggestedOutputLatencyFrames;
1754
if( userLatencyFrames > 0 ) minLatencyFrames = userLatencyFrames;
1756
/* Calculate stream->framesPerDSBuffer depending on framesPerBuffer */
1757
if( framesPerBuffer == paFramesPerBufferUnspecified )
1759
/* App support variable framesPerBuffer */
1760
stream->framesPerDSBuffer = minLatencyFrames;
1762
stream->streamRepresentation.streamInfo.outputLatency = (double)(minLatencyFrames - 1) / sampleRate;
1766
/* Round up to number of buffers needed to guarantee that latency. */
1767
int numUserBuffers = (minLatencyFrames + framesPerBuffer - 1) / framesPerBuffer;
1768
if( numUserBuffers < 1 ) numUserBuffers = 1;
1769
numUserBuffers += 1; /* So we have latency worth of buffers ahead of current buffer. */
1770
stream->framesPerDSBuffer = framesPerBuffer * numUserBuffers;
1772
stream->streamRepresentation.streamInfo.outputLatency = (double)(framesPerBuffer * (numUserBuffers-1)) / sampleRate;
1776
/** @todo REVIEW: this calculation seems incorrect to me - rossb. */
1777
int msecLatency = (int) ((stream->framesPerDSBuffer * MSEC_PER_SECOND) / sampleRate);
1778
PRINT(("PortAudio on DirectSound - Latency = %d frames, %d msec\n", stream->framesPerDSBuffer, msecLatency ));
1782
/* ------------------ OUTPUT */
1783
if( outputParameters )
1786
PaDeviceInfo *deviceInfo = hostApi->deviceInfos[ outputParameters->device ];
1787
DBUG(("PaHost_OpenStream: deviceID = 0x%x\n", outputParameters->device));
1790
int bytesPerSample = Pa_GetSampleSize(hostOutputSampleFormat);
1791
bytesPerDirectSoundBuffer = stream->framesPerDSBuffer * outputParameters->channelCount * bytesPerSample;
1792
if( bytesPerDirectSoundBuffer < DSBSIZE_MIN )
1794
result = paBufferTooSmall;
1797
else if( bytesPerDirectSoundBuffer > DSBSIZE_MAX )
1799
result = paBufferTooBig;
1803
hr = paWinDsDSoundEntryPoints.DirectSoundCreate(
1804
((PaWinDsDeviceInfo*)hostApi->deviceInfos[outputParameters->device])->lpGUID,
1805
&stream->pDirectSound, NULL );
1809
ERR_RPT(("PortAudio: DirectSoundCreate() failed!\n"));
1810
result = paUnanticipatedHostError;
1811
PA_DS_SET_LAST_DIRECTSOUND_ERROR( hr );
1814
hr = InitOutputBuffer( stream,
1815
hostOutputSampleFormat,
1816
(unsigned long) (sampleRate + 0.5),
1817
(WORD)outputParameters->channelCount, bytesPerDirectSoundBuffer,
1818
outputChannelMask );
1819
DBUG(("InitOutputBuffer() returns %x\n", hr));
1822
result = paUnanticipatedHostError;
1823
PA_DS_SET_LAST_DIRECTSOUND_ERROR( hr );
1826
/* Calculate value used in latency calculation to avoid real-time divides. */
1827
stream->secondsPerHostByte = 1.0 /
1828
(stream->bufferProcessor.bytesPerHostOutputSample *
1829
outputChannelCount * sampleRate);
1832
/* ------------------ INPUT */
1833
if( inputParameters )
1836
PaDeviceInfo *deviceInfo = hostApi->deviceInfos[ inputParameters->device ];
1837
DBUG(("PaHost_OpenStream: deviceID = 0x%x\n", inputParameters->device));
1840
int bytesPerSample = Pa_GetSampleSize(hostInputSampleFormat);
1841
bytesPerDirectSoundBuffer = stream->framesPerDSBuffer * inputParameters->channelCount * bytesPerSample;
1842
if( bytesPerDirectSoundBuffer < DSBSIZE_MIN )
1844
result = paBufferTooSmall;
1847
else if( bytesPerDirectSoundBuffer > DSBSIZE_MAX )
1849
result = paBufferTooBig;
1853
hr = paWinDsDSoundEntryPoints.DirectSoundCaptureCreate(
1854
((PaWinDsDeviceInfo*)hostApi->deviceInfos[inputParameters->device])->lpGUID,
1855
&stream->pDirectSoundCapture, NULL );
1858
ERR_RPT(("PortAudio: DirectSoundCaptureCreate() failed!\n"));
1859
result = paUnanticipatedHostError;
1860
PA_DS_SET_LAST_DIRECTSOUND_ERROR( hr );
1863
hr = InitInputBuffer( stream,
1864
hostInputSampleFormat,
1865
(unsigned long) (sampleRate + 0.5),
1866
(WORD)inputParameters->channelCount, bytesPerDirectSoundBuffer,
1868
DBUG(("InitInputBuffer() returns %x\n", hr));
1871
ERR_RPT(("PortAudio: DSW_InitInputBuffer() returns %x\n", hr));
1872
result = paUnanticipatedHostError;
1873
PA_DS_SET_LAST_DIRECTSOUND_ERROR( hr );
1880
*s = (PaStream*)stream;
1886
PaUtil_FreeMemory( stream );
1892
/************************************************************************************
1893
* Determine how much space can be safely written to in DS buffer.
1894
* Detect underflows and overflows.
1895
* Does not allow writing into safety gap maintained by DirectSound.
1897
static HRESULT QueryOutputSpace( PaWinDsStream *stream, long *bytesEmpty )
1904
// Query to see how much room is in buffer.
1905
hr = IDirectSoundBuffer_GetCurrentPosition( stream->pDirectSoundOutputBuffer,
1906
&playCursor, &writeCursor );
1911
// Determine size of gap between playIndex and WriteIndex that we cannot write into.
1912
playWriteGap = writeCursor - playCursor;
1913
if( playWriteGap < 0 ) playWriteGap += stream->outputBufferSizeBytes; // unwrap
1914
/* DirectSound doesn't have a large enough playCursor so we cannot detect wrap-around. */
1915
/* Attempt to detect playCursor wrap-around and correct it. */
1916
if( stream->outputIsRunning && (stream->perfCounterTicksPerBuffer.QuadPart != 0) )
1918
/* How much time has elapsed since last check. */
1919
LARGE_INTEGER currentTime;
1920
LARGE_INTEGER elapsedTime;
1923
long buffersWrapped;
1925
QueryPerformanceCounter( ¤tTime );
1926
elapsedTime.QuadPart = currentTime.QuadPart - stream->previousPlayTime.QuadPart;
1927
stream->previousPlayTime = currentTime;
1929
/* How many bytes does DirectSound say have been played. */
1930
bytesPlayed = playCursor - stream->previousPlayCursor;
1931
if( bytesPlayed < 0 ) bytesPlayed += stream->outputBufferSizeBytes; // unwrap
1932
stream->previousPlayCursor = playCursor;
1934
/* Calculate how many bytes we would have expected to been played by now. */
1935
bytesExpected = (long) ((elapsedTime.QuadPart * stream->outputBufferSizeBytes) / stream->perfCounterTicksPerBuffer.QuadPart);
1936
buffersWrapped = (bytesExpected - bytesPlayed) / stream->outputBufferSizeBytes;
1937
if( buffersWrapped > 0 )
1939
playCursor += (buffersWrapped * stream->outputBufferSizeBytes);
1940
bytesPlayed += (buffersWrapped * stream->outputBufferSizeBytes);
1942
/* Maintain frame output cursor. */
1943
stream->framesPlayed += (bytesPlayed / stream->bytesPerOutputFrame);
1945
numBytesEmpty = playCursor - stream->outputBufferWriteOffsetBytes;
1946
if( numBytesEmpty < 0 ) numBytesEmpty += stream->outputBufferSizeBytes; // unwrap offset
1948
/* Have we underflowed? */
1949
if( numBytesEmpty > (stream->outputBufferSizeBytes - playWriteGap) )
1951
if( stream->outputIsRunning )
1953
stream->outputUnderflowCount += 1;
1958
The write cursor indicates the position at which it is safe
1959
to write new data to the buffer. The write cursor always leads the
1960
play cursor, typically by about 15 milliseconds' worth of audio
1962
It is always safe to change data that is behind the position
1963
indicated by the lpdwCurrentPlayCursor parameter.
1966
stream->outputBufferWriteOffsetBytes = writeCursor;
1967
numBytesEmpty = stream->outputBufferSizeBytes - playWriteGap;
1969
*bytesEmpty = numBytesEmpty;
1973
/***********************************************************************************/
1974
static PaError Pa_TimeSlice( PaWinDsStream *stream )
1976
PaError result = 0; /* FIXME: this should be declared int and this function should also return that type (same as stream callback return type)*/
1978
long bytesEmpty = 0;
1979
long bytesFilled = 0;
1980
long bytesToXfer = 0;
1981
long framesToXfer = 0;
1982
long numInFramesReady = 0;
1983
long numOutFramesReady = 0;
1984
long bytesProcessed;
1986
double outputLatency = 0;
1987
PaStreamCallbackTimeInfo timeInfo = {0,0,0}; /** @todo implement inputBufferAdcTime */
1990
LPBYTE lpInBuf1 = NULL;
1991
LPBYTE lpInBuf2 = NULL;
1992
DWORD dwInSize1 = 0;
1993
DWORD dwInSize2 = 0;
1995
LPBYTE lpOutBuf1 = NULL;
1996
LPBYTE lpOutBuf2 = NULL;
1997
DWORD dwOutSize1 = 0;
1998
DWORD dwOutSize2 = 0;
2000
/* How much input data is available? */
2001
if( stream->bufferProcessor.inputChannelCount > 0 )
2007
// Query to see how much data is in buffer.
2008
// We don't need the capture position but sometimes DirectSound doesn't handle NULLS correctly
2009
// so let's pass a pointer just to be safe.
2010
hr = IDirectSoundCaptureBuffer_GetCurrentPosition( stream->pDirectSoundInputBuffer, &capturePos, &readPos );
2013
filled = readPos - stream->readOffset;
2014
if( filled < 0 ) filled += stream->inputSize; // unwrap offset
2015
bytesFilled = filled;
2017
// FIXME: what happens if IDirectSoundCaptureBuffer_GetCurrentPosition fails?
2019
framesToXfer = numInFramesReady = bytesFilled / stream->bytesPerInputFrame;
2020
outputLatency = ((double)bytesFilled) * stream->secondsPerHostByte;
2022
/** @todo Check for overflow */
2025
/* How much output room is available? */
2026
if( stream->bufferProcessor.outputChannelCount > 0 )
2028
UINT previousUnderflowCount = stream->outputUnderflowCount;
2029
QueryOutputSpace( stream, &bytesEmpty );
2030
framesToXfer = numOutFramesReady = bytesEmpty / stream->bytesPerOutputFrame;
2032
/* Check for underflow */
2033
if( stream->outputUnderflowCount != previousUnderflowCount )
2034
stream->callbackFlags |= paOutputUnderflow;
2037
if( (numInFramesReady > 0) && (numOutFramesReady > 0) )
2039
framesToXfer = (numOutFramesReady < numInFramesReady) ? numOutFramesReady : numInFramesReady;
2042
if( framesToXfer > 0 )
2045
PaUtil_BeginCpuLoadMeasurement( &stream->cpuLoadMeasurer );
2047
/* The outputBufferDacTime parameter should indicates the time at which
2048
the first sample of the output buffer is heard at the DACs. */
2049
timeInfo.currentTime = PaUtil_GetTime();
2050
timeInfo.outputBufferDacTime = timeInfo.currentTime + outputLatency;
2053
PaUtil_BeginBufferProcessing( &stream->bufferProcessor, &timeInfo, stream->callbackFlags );
2054
stream->callbackFlags = 0;
2057
if( stream->bufferProcessor.inputChannelCount > 0 )
2059
bytesToXfer = framesToXfer * stream->bytesPerInputFrame;
2060
hresult = IDirectSoundCaptureBuffer_Lock ( stream->pDirectSoundInputBuffer,
2061
stream->readOffset, bytesToXfer,
2062
(void **) &lpInBuf1, &dwInSize1,
2063
(void **) &lpInBuf2, &dwInSize2, 0);
2064
if (hresult != DS_OK)
2066
ERR_RPT(("DirectSound IDirectSoundCaptureBuffer_Lock failed, hresult = 0x%x\n",hresult));
2067
result = paUnanticipatedHostError;
2068
PA_DS_SET_LAST_DIRECTSOUND_ERROR( hresult );
2072
numFrames = dwInSize1 / stream->bytesPerInputFrame;
2073
PaUtil_SetInputFrameCount( &stream->bufferProcessor, numFrames );
2074
PaUtil_SetInterleavedInputChannels( &stream->bufferProcessor, 0, lpInBuf1, 0 );
2075
/* Is input split into two regions. */
2078
numFrames = dwInSize2 / stream->bytesPerInputFrame;
2079
PaUtil_Set2ndInputFrameCount( &stream->bufferProcessor, numFrames );
2080
PaUtil_Set2ndInterleavedInputChannels( &stream->bufferProcessor, 0, lpInBuf2, 0 );
2085
if( stream->bufferProcessor.outputChannelCount > 0 )
2087
bytesToXfer = framesToXfer * stream->bytesPerOutputFrame;
2088
hresult = IDirectSoundBuffer_Lock ( stream->pDirectSoundOutputBuffer,
2089
stream->outputBufferWriteOffsetBytes, bytesToXfer,
2090
(void **) &lpOutBuf1, &dwOutSize1,
2091
(void **) &lpOutBuf2, &dwOutSize2, 0);
2092
if (hresult != DS_OK)
2094
ERR_RPT(("DirectSound IDirectSoundBuffer_Lock failed, hresult = 0x%x\n",hresult));
2095
result = paUnanticipatedHostError;
2096
PA_DS_SET_LAST_DIRECTSOUND_ERROR( hresult );
2100
numFrames = dwOutSize1 / stream->bytesPerOutputFrame;
2101
PaUtil_SetOutputFrameCount( &stream->bufferProcessor, numFrames );
2102
PaUtil_SetInterleavedOutputChannels( &stream->bufferProcessor, 0, lpOutBuf1, 0 );
2104
/* Is output split into two regions. */
2105
if( dwOutSize2 > 0 )
2107
numFrames = dwOutSize2 / stream->bytesPerOutputFrame;
2108
PaUtil_Set2ndOutputFrameCount( &stream->bufferProcessor, numFrames );
2109
PaUtil_Set2ndInterleavedOutputChannels( &stream->bufferProcessor, 0, lpOutBuf2, 0 );
2113
result = paContinue;
2114
numFrames = PaUtil_EndBufferProcessing( &stream->bufferProcessor, &result );
2115
stream->framesWritten += numFrames;
2117
if( stream->bufferProcessor.outputChannelCount > 0 )
2119
/* FIXME: an underflow could happen here */
2121
/* Update our buffer offset and unlock sound buffer */
2122
bytesProcessed = numFrames * stream->bytesPerOutputFrame;
2123
stream->outputBufferWriteOffsetBytes = (stream->outputBufferWriteOffsetBytes + bytesProcessed) % stream->outputBufferSizeBytes;
2124
IDirectSoundBuffer_Unlock( stream->pDirectSoundOutputBuffer, lpOutBuf1, dwOutSize1, lpOutBuf2, dwOutSize2);
2125
stream->dsw_framesWritten += numFrames;
2129
if( stream->bufferProcessor.inputChannelCount > 0 )
2131
/* FIXME: an overflow could happen here */
2133
/* Update our buffer offset and unlock sound buffer */
2134
bytesProcessed = numFrames * stream->bytesPerInputFrame;
2135
stream->readOffset = (stream->readOffset + bytesProcessed) % stream->inputSize;
2136
IDirectSoundCaptureBuffer_Unlock( stream->pDirectSoundInputBuffer, lpInBuf1, dwInSize1, lpInBuf2, dwInSize2);
2140
PaUtil_EndCpuLoadMeasurement( &stream->cpuLoadMeasurer, numFrames );
2146
/*******************************************************************/
2148
static HRESULT ZeroAvailableOutputSpace( PaWinDsStream *stream )
2151
LPBYTE lpbuf1 = NULL;
2152
LPBYTE lpbuf2 = NULL;
2156
hr = QueryOutputSpace( stream, &bytesEmpty ); // updates framesPlayed
2157
if (hr != DS_OK) return hr;
2158
if( bytesEmpty == 0 ) return DS_OK;
2159
// Lock free space in the DS
2160
hr = IDirectSoundBuffer_Lock( stream->pDirectSoundOutputBuffer, stream->outputBufferWriteOffsetBytes,
2161
bytesEmpty, (void **) &lpbuf1, &dwsize1,
2162
(void **) &lpbuf2, &dwsize2, 0);
2165
// Copy the buffer into the DS
2166
ZeroMemory(lpbuf1, dwsize1);
2169
ZeroMemory(lpbuf2, dwsize2);
2171
// Update our buffer offset and unlock sound buffer
2172
stream->outputBufferWriteOffsetBytes = (stream->outputBufferWriteOffsetBytes + dwsize1 + dwsize2) % stream->outputBufferSizeBytes;
2173
IDirectSoundBuffer_Unlock( stream->pDirectSoundOutputBuffer, lpbuf1, dwsize1, lpbuf2, dwsize2);
2174
stream->dsw_framesWritten += bytesEmpty / stream->bytesPerOutputFrame;
2180
static void CALLBACK Pa_TimerCallback(UINT uID, UINT uMsg, DWORD_PTR dwUser, DWORD dw1, DWORD dw2)
2182
PaWinDsStream *stream;
2184
/* suppress unused variable warnings */
2190
stream = (PaWinDsStream *) dwUser;
2191
if( stream == NULL ) return;
2193
if( stream->isActive )
2195
if( stream->abortProcessing )
2197
stream->isActive = 0;
2199
else if( stream->stopProcessing )
2201
if( stream->bufferProcessor.outputChannelCount > 0 )
2203
ZeroAvailableOutputSpace( stream );
2204
/* clear isActive when all sound played */
2205
if( stream->framesPlayed >= stream->framesWritten )
2207
stream->isActive = 0;
2212
stream->isActive = 0;
2217
if( Pa_TimeSlice( stream ) != 0) /* Call time slice independant of timing method. */
2219
/* FIXME implement handling of paComplete and paAbort if possible */
2220
stream->stopProcessing = 1;
2224
if( !stream->isActive )
2226
if( stream->streamRepresentation.streamFinishedCallback != 0 )
2227
stream->streamRepresentation.streamFinishedCallback( stream->streamRepresentation.userData );
2232
/***********************************************************************************
2233
When CloseStream() is called, the multi-api layer ensures that
2234
the stream has already been stopped or aborted.
2236
static PaError CloseStream( PaStream* s )
2238
PaError result = paNoError;
2239
PaWinDsStream *stream = (PaWinDsStream*)s;
2241
// Cleanup the sound buffers
2242
if( stream->pDirectSoundOutputBuffer )
2244
IDirectSoundBuffer_Stop( stream->pDirectSoundOutputBuffer );
2245
IDirectSoundBuffer_Release( stream->pDirectSoundOutputBuffer );
2246
stream->pDirectSoundOutputBuffer = NULL;
2249
if( stream->pDirectSoundInputBuffer )
2251
IDirectSoundCaptureBuffer_Stop( stream->pDirectSoundInputBuffer );
2252
IDirectSoundCaptureBuffer_Release( stream->pDirectSoundInputBuffer );
2253
stream->pDirectSoundInputBuffer = NULL;
2256
if( stream->pDirectSoundCapture )
2258
IDirectSoundCapture_Release( stream->pDirectSoundCapture );
2259
stream->pDirectSoundCapture = NULL;
2262
if( stream->pDirectSound )
2264
IDirectSound_Release( stream->pDirectSound );
2265
stream->pDirectSound = NULL;
2268
PaUtil_TerminateBufferProcessor( &stream->bufferProcessor );
2269
PaUtil_TerminateStreamRepresentation( &stream->streamRepresentation );
2270
PaUtil_FreeMemory( stream );
2275
/***********************************************************************************/
2276
static PaError StartStream( PaStream *s )
2278
PaError result = paNoError;
2279
PaWinDsStream *stream = (PaWinDsStream*)s;
2282
PaUtil_ResetBufferProcessor( &stream->bufferProcessor );
2284
if( stream->bufferProcessor.inputChannelCount > 0 )
2286
// Start the buffer playback
2287
if( stream->pDirectSoundInputBuffer != NULL ) // FIXME: not sure this check is necessary
2289
hr = IDirectSoundCaptureBuffer_Start( stream->pDirectSoundInputBuffer, DSCBSTART_LOOPING );
2292
DBUG(("StartStream: DSW_StartInput returned = 0x%X.\n", hr));
2295
result = paUnanticipatedHostError;
2296
PA_DS_SET_LAST_DIRECTSOUND_ERROR( hr );
2301
stream->framesWritten = 0;
2302
stream->callbackFlags = 0;
2304
stream->abortProcessing = 0;
2305
stream->stopProcessing = 0;
2306
stream->isActive = 1;
2308
if( stream->bufferProcessor.outputChannelCount > 0 )
2310
/* Give user callback a chance to pre-fill buffer. REVIEW - i thought we weren't pre-filling, rb. */
2311
result = Pa_TimeSlice( stream );
2312
if( result != paNoError ) return result; // FIXME - what if finished?
2314
QueryPerformanceCounter( &stream->previousPlayTime );
2315
stream->previousPlayCursor = 0;
2316
stream->framesPlayed = 0;
2317
hr = IDirectSoundBuffer_SetCurrentPosition( stream->pDirectSoundOutputBuffer, 0 );
2318
DBUG(("PaHost_StartOutput: IDirectSoundBuffer_SetCurrentPosition returned = 0x%X.\n", hr));
2321
result = paUnanticipatedHostError;
2322
PA_DS_SET_LAST_DIRECTSOUND_ERROR( hr );
2326
// Start the buffer playback in a loop.
2327
if( stream->pDirectSoundOutputBuffer != NULL ) // FIXME: not sure this needs to be checked here
2329
hr = IDirectSoundBuffer_Play( stream->pDirectSoundOutputBuffer, 0, 0, DSBPLAY_LOOPING );
2330
DBUG(("PaHost_StartOutput: IDirectSoundBuffer_Play returned = 0x%X.\n", hr));
2333
result = paUnanticipatedHostError;
2334
PA_DS_SET_LAST_DIRECTSOUND_ERROR( hr );
2337
stream->outputIsRunning = TRUE;
2342
/* Create timer that will wake us up so we can fill the DSound buffer. */
2345
int framesPerWakeup = stream->framesPerDSBuffer / 4;
2346
int msecPerWakeup = MSEC_PER_SECOND * framesPerWakeup / (int) stream->streamRepresentation.streamInfo.sampleRate;
2347
if( msecPerWakeup < 10 ) msecPerWakeup = 10;
2348
else if( msecPerWakeup > 100 ) msecPerWakeup = 100;
2349
resolution = msecPerWakeup/4;
2350
stream->timerID = timeSetEvent( msecPerWakeup, resolution, (LPTIMECALLBACK) Pa_TimerCallback,
2351
(DWORD_PTR) stream, TIME_PERIODIC );
2353
if( stream->timerID == 0 )
2355
stream->isActive = 0;
2356
result = paUnanticipatedHostError;
2357
PA_DS_SET_LAST_DIRECTSOUND_ERROR( hr );
2361
stream->isStarted = TRUE;
2368
/***********************************************************************************/
2369
static PaError StopStream( PaStream *s )
2371
PaError result = paNoError;
2372
PaWinDsStream *stream = (PaWinDsStream*)s;
2376
stream->stopProcessing = 1;
2377
/* Set timeout at 20% beyond maximum time we might wait. */
2378
timeoutMsec = (int) (1200.0 * stream->framesPerDSBuffer / stream->streamRepresentation.streamInfo.sampleRate);
2379
while( stream->isActive && (timeoutMsec > 0) )
2384
if( stream->timerID != 0 )
2386
timeKillEvent(stream->timerID); /* Stop callback timer. */
2387
stream->timerID = 0;
2391
if( stream->bufferProcessor.outputChannelCount > 0 )
2393
// Stop the buffer playback
2394
if( stream->pDirectSoundOutputBuffer != NULL )
2396
stream->outputIsRunning = FALSE;
2397
// FIXME: what happens if IDirectSoundBuffer_Stop returns an error?
2398
hr = IDirectSoundBuffer_Stop( stream->pDirectSoundOutputBuffer );
2402
if( stream->bufferProcessor.inputChannelCount > 0 )
2404
// Stop the buffer capture
2405
if( stream->pDirectSoundInputBuffer != NULL )
2407
// FIXME: what happens if IDirectSoundCaptureBuffer_Stop returns an error?
2408
hr = IDirectSoundCaptureBuffer_Stop( stream->pDirectSoundInputBuffer );
2412
stream->isStarted = FALSE;
2418
/***********************************************************************************/
2419
static PaError AbortStream( PaStream *s )
2421
PaWinDsStream *stream = (PaWinDsStream*)s;
2423
stream->abortProcessing = 1;
2424
return StopStream( s );
2428
/***********************************************************************************/
2429
static PaError IsStreamStopped( PaStream *s )
2431
PaWinDsStream *stream = (PaWinDsStream*)s;
2433
return !stream->isStarted;
2437
/***********************************************************************************/
2438
static PaError IsStreamActive( PaStream *s )
2440
PaWinDsStream *stream = (PaWinDsStream*)s;
2442
return stream->isActive;
2445
/***********************************************************************************/
2446
static PaTime GetStreamTime( PaStream *s )
2448
/* suppress unused variable warnings */
2451
return PaUtil_GetTime();
2455
/***********************************************************************************/
2456
static double GetStreamCpuLoad( PaStream* s )
2458
PaWinDsStream *stream = (PaWinDsStream*)s;
2460
return PaUtil_GetCpuLoad( &stream->cpuLoadMeasurer );
2464
/***********************************************************************************
2465
As separate stream interfaces are used for blocking and callback
2466
streams, the following functions can be guaranteed to only be called
2467
for blocking streams.
2470
static PaError ReadStream( PaStream* s,
2472
unsigned long frames )
2474
PaWinDsStream *stream = (PaWinDsStream*)s;
2476
/* suppress unused variable warnings */
2481
/* IMPLEMENT ME, see portaudio.h for required behavior*/
2487
/***********************************************************************************/
2488
static PaError WriteStream( PaStream* s,
2490
unsigned long frames )
2492
PaWinDsStream *stream = (PaWinDsStream*)s;
2494
/* suppress unused variable warnings */
2499
/* IMPLEMENT ME, see portaudio.h for required behavior*/
2505
/***********************************************************************************/
2506
static signed long GetStreamReadAvailable( PaStream* s )
2508
PaWinDsStream *stream = (PaWinDsStream*)s;
2510
/* suppress unused variable warnings */
2513
/* IMPLEMENT ME, see portaudio.h for required behavior*/
2519
/***********************************************************************************/
2520
static signed long GetStreamWriteAvailable( PaStream* s )
2522
PaWinDsStream *stream = (PaWinDsStream*)s;
2524
/* suppress unused variable warnings */
2527
/* IMPLEMENT ME, see portaudio.h for required behavior*/