1 #ifdef _WIN32
2 /*
3 * $Id$
4 * Portable Audio I/O Library DirectSound implementation
5 *
6 * Authors: Phil Burk, Robert Marsanyi & Ross Bencina
7 * Based on the Open Source API proposed by Ross Bencina
8 * Copyright (c) 1999-2007 Ross Bencina, Phil Burk, Robert Marsanyi
9 *
10 * Permission is hereby granted, free of charge, to any person obtaining
11 * a copy of this software and associated documentation files
12 * (the "Software"), to deal in the Software without restriction,
13 * including without limitation the rights to use, copy, modify, merge,
14 * publish, distribute, sublicense, and/or sell copies of the Software,
15 * and to permit persons to whom the Software is furnished to do so,
16 * subject to the following conditions:
17 *
18 * The above copyright notice and this permission notice shall be
19 * included in all copies or substantial portions of the Software.
20 *
21 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
24 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
25 * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
26 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28 */
29
30 /*
31 * The text above constitutes the entire PortAudio license; however,
32 * the PortAudio community also makes the following non-binding requests:
33 *
34 * Any person wishing to distribute modifications to the Software is
35 * requested to send the modifications to the original developer so that
36 * they can be incorporated into the canonical version. It is also
37 * requested that these non-binding requests be included along with the
38 * license above.
39 */
40
41 /** @file
42 @ingroup hostapi_src
43 */
44
45 /* Until May 2011 PA/DS has used a multimedia timer to perform the callback.
46 We're replacing this with a new implementation using a thread and a different timer mechanim.
47 Defining PA_WIN_DS_USE_WMME_TIMER uses the old (pre-May 2011) behavior.
48 */
49 //#define PA_WIN_DS_USE_WMME_TIMER
50
51 #include <assert.h>
52 #include <stdio.h>
53 #include <string.h> /* strlen() */
54
55 #define _WIN32_WINNT 0x0400 /* required to get waitable timer APIs */
56 #include <initguid.h> /* make sure ds guids get defined */
57 #include <windows.h>
58 #include <objbase.h>
59
60
61 /*
62 Use the earliest version of DX required, no need to polute the namespace
63 */
64 #ifdef PAWIN_USE_DIRECTSOUNDFULLDUPLEXCREATE
65 #define DIRECTSOUND_VERSION 0x0800
66 #else
67 #define DIRECTSOUND_VERSION 0x0300
68 #endif
69 #include <dsound.h>
70 #ifdef PAWIN_USE_WDMKS_DEVICE_INFO
71 #include <dsconf.h>
72 #endif /* PAWIN_USE_WDMKS_DEVICE_INFO */
73 #ifndef PA_WIN_DS_USE_WMME_TIMER
74 #ifndef UNDER_CE
75 #include <process.h>
76 #endif
77 #endif
78
79 #include "pa_util.h"
80 #include "pa_allocation.h"
81 #include "pa_hostapi.h"
82 #include "pa_stream.h"
83 #include "pa_cpuload.h"
84 #include "pa_process.h"
85 #include "pa_debugprint.h"
86
87 #include "pa_win_ds.h"
88 #include "pa_win_ds_dynlink.h"
89 #include "pa_win_waveformat.h"
90 #include "pa_win_wdmks_utils.h"
91 #include "pa_win_coinitialize.h"
92
93 #if (defined(WIN32) && (defined(_MSC_VER) && (_MSC_VER >= 1200))) /* MSC version 6 and above */
94 #pragma comment( lib, "dsound.lib" )
95 #pragma comment( lib, "winmm.lib" )
96 #pragma comment( lib, "kernel32.lib" )
97 #endif
98
99 /* use CreateThread for CYGWIN, _beginthreadex for all others */
100 #ifndef PA_WIN_DS_USE_WMME_TIMER
101
102 #if !defined(__CYGWIN__) && !defined(UNDER_CE)
103 #define CREATE_THREAD (HANDLE)_beginthreadex
104 #undef CLOSE_THREAD_HANDLE /* as per documentation we don't call CloseHandle on a thread created with _beginthreadex */
105 #define PA_THREAD_FUNC static unsigned WINAPI
106 #define PA_THREAD_ID unsigned
107 #else
108 #define CREATE_THREAD CreateThread
109 #define CLOSE_THREAD_HANDLE CloseHandle
110 #define PA_THREAD_FUNC static DWORD WINAPI
111 #define PA_THREAD_ID DWORD
112 #endif
113
114 #if (defined(UNDER_CE))
115 #pragma comment(lib, "Coredll.lib")
116 #elif (defined(WIN32) && (defined(_MSC_VER) && (_MSC_VER >= 1200))) /* MSC version 6 and above */
117 #pragma comment(lib, "winmm.lib")
118 #endif
119
120 PA_THREAD_FUNC ProcessingThreadProc( void *pArg );
121
122 #if !defined(UNDER_CE)
123 #define PA_WIN_DS_USE_WAITABLE_TIMER_OBJECT /* use waitable timer where possible, otherwise we use a WaitForSingleObject timeout */
124 #endif
125
126 #endif /* !PA_WIN_DS_USE_WMME_TIMER */
127
128
129 /*
130 provided in newer platform sdks and x64
131 */
132 #ifndef DWORD_PTR
133 #if defined(_WIN64)
134 #define DWORD_PTR unsigned __int64
135 #else
136 #define DWORD_PTR unsigned long
137 #endif
138 #endif
139
140 #define PRINT(x) PA_DEBUG(x);
141 #define ERR_RPT(x) PRINT(x)
142 #define DBUG(x) PRINT(x)
143 #define DBUGX(x) PRINT(x)
144
145 #define PA_USE_HIGH_LATENCY (0)
146 #if PA_USE_HIGH_LATENCY
147 #define PA_DS_WIN_9X_DEFAULT_LATENCY_ (.500)
148 #define PA_DS_WIN_NT_DEFAULT_LATENCY_ (.600)
149 #else
150 #define PA_DS_WIN_9X_DEFAULT_LATENCY_ (.140)
151 #define PA_DS_WIN_NT_DEFAULT_LATENCY_ (.280)
152 #endif
153
154 #define PA_DS_WIN_WDM_DEFAULT_LATENCY_ (.120)
155
156 /* we allow the polling period to range between 1 and 100ms.
157 prior to August 2011 we limited the minimum polling period to 10ms.
158 */
159 #define PA_DS_MINIMUM_POLLING_PERIOD_SECONDS (0.001) /* 1ms */
160 #define PA_DS_MAXIMUM_POLLING_PERIOD_SECONDS (0.100) /* 100ms */
161 #define PA_DS_POLLING_JITTER_SECONDS (0.001) /* 1ms */
162
163 #define SECONDS_PER_MSEC (0.001)
164 #define MSECS_PER_SECOND (1000)
165
166 /* prototypes for functions declared in this file */
167
168 #ifdef __cplusplus
169 extern "C"
170 {
171 #endif /* __cplusplus */
172
173 PaError PaWinDs_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index );
174
175 #ifdef __cplusplus
176 }
177 #endif /* __cplusplus */
178
179 static void Terminate( struct PaUtilHostApiRepresentation *hostApi );
180 static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
181 PaStream** s,
182 const PaStreamParameters *inputParameters,
183 const PaStreamParameters *outputParameters,
184 double sampleRate,
185 unsigned long framesPerBuffer,
186 PaStreamFlags streamFlags,
187 PaStreamCallback *streamCallback,
188 void *userData );
189 static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi,
190 const PaStreamParameters *inputParameters,
191 const PaStreamParameters *outputParameters,
192 double sampleRate );
193 static PaError CloseStream( PaStream* stream );
194 static PaError StartStream( PaStream *stream );
195 static PaError StopStream( PaStream *stream );
196 static PaError AbortStream( PaStream *stream );
197 static PaError IsStreamStopped( PaStream *s );
198 static PaError IsStreamActive( PaStream *stream );
199 static PaTime GetStreamTime( PaStream *stream );
200 static double GetStreamCpuLoad( PaStream* stream );
201 static PaError ReadStream( PaStream* stream, void *buffer, unsigned long frames );
202 static PaError WriteStream( PaStream* stream, const void *buffer, unsigned long frames );
203 static signed long GetStreamReadAvailable( PaStream* stream );
204 static signed long GetStreamWriteAvailable( PaStream* stream );
205
206
207 /* FIXME: should convert hr to a string */
208 #define PA_DS_SET_LAST_DIRECTSOUND_ERROR( hr ) \
209 PaUtil_SetLastHostErrorInfo( paDirectSound, hr, "DirectSound error" )
210
211 /************************************************* DX Prototypes **********/
212 static BOOL CALLBACK CollectGUIDsProcW(LPGUID lpGUID,
213 LPCWSTR lpszDesc,
214 LPCWSTR lpszDrvName,
215 LPVOID lpContext );
216
217 /************************************************************************************/
218 /********************** Structures **************************************************/
219 /************************************************************************************/
220 /* PaWinDsHostApiRepresentation - host api datastructure specific to this implementation */
221
222 typedef struct PaWinDsDeviceInfo
223 {
224 PaDeviceInfo inheritedDeviceInfo;
225 GUID guid;
226 GUID *lpGUID;
227 double sampleRates[3];
228 char deviceInputChannelCountIsKnown; /**<< if the system returns 0xFFFF then we don't really know the number of supported channels (1=>known, 0=>unknown)*/
229 char deviceOutputChannelCountIsKnown; /**<< if the system returns 0xFFFF then we don't really know the number of supported channels (1=>known, 0=>unknown)*/
230 } PaWinDsDeviceInfo;
231
232 typedef struct
233 {
234 PaUtilHostApiRepresentation inheritedHostApiRep;
235 PaUtilStreamInterface callbackStreamInterface;
236 PaUtilStreamInterface blockingStreamInterface;
237
238 PaUtilAllocationGroup *allocations;
239
240 /* implementation specific data goes here */
241
242 PaWinUtilComInitializationResult comInitializationResult;
243
244 } PaWinDsHostApiRepresentation;
245
246
247 /* PaWinDsStream - a stream data structure specifically for this implementation */
248
249 typedef struct PaWinDsStream
250 {
251 PaUtilStreamRepresentation streamRepresentation;
252 PaUtilCpuLoadMeasurer cpuLoadMeasurer;
253 PaUtilBufferProcessor bufferProcessor;
254
255 /* DirectSound specific data. */
256 #ifdef PAWIN_USE_DIRECTSOUNDFULLDUPLEXCREATE
257 LPDIRECTSOUNDFULLDUPLEX8 pDirectSoundFullDuplex8;
258 #endif
259
260 /* Output */
261 LPDIRECTSOUND pDirectSound;
262 LPDIRECTSOUNDBUFFER pDirectSoundPrimaryBuffer;
263 LPDIRECTSOUNDBUFFER pDirectSoundOutputBuffer;
264 DWORD outputBufferWriteOffsetBytes; /* last write position */
265 INT outputBufferSizeBytes;
266 INT outputFrameSizeBytes;
267 /* Try to detect play buffer underflows. */
268 LARGE_INTEGER perfCounterTicksPerBuffer; /* counter ticks it should take to play a full buffer */
269 LARGE_INTEGER previousPlayTime;
270 DWORD previousPlayCursor;
271 UINT outputUnderflowCount;
272 BOOL outputIsRunning;
273 INT finalZeroBytesWritten; /* used to determine when we've flushed the whole buffer */
274
275 /* Input */
276 LPDIRECTSOUNDCAPTURE pDirectSoundCapture;
277 LPDIRECTSOUNDCAPTUREBUFFER pDirectSoundInputBuffer;
278 INT inputFrameSizeBytes;
279 UINT readOffset; /* last read position */
280 UINT inputBufferSizeBytes;
281
282
283 int hostBufferSizeFrames; /* input and output host ringbuffers have the same number of frames */
284 double framesWritten;
285 double secondsPerHostByte; /* Used to optimize latency calculation for outTime */
286 double pollingPeriodSeconds;
287
288 PaStreamCallbackFlags callbackFlags;
289
290 PaStreamFlags streamFlags;
291 int callbackResult;
292 HANDLE processingCompleted;
293
294 /* FIXME - move all below to PaUtilStreamRepresentation */
295 volatile int isStarted;
296 volatile int isActive;
297 volatile int stopProcessing; /* stop thread once existing buffers have been returned */
298 volatile int abortProcessing; /* stop thread immediately */
299
300 UINT systemTimerResolutionPeriodMs; /* set to 0 if we were unable to set the timer period */
301
302 #ifdef PA_WIN_DS_USE_WMME_TIMER
303 MMRESULT timerID;
304 #else
305
306 #ifdef PA_WIN_DS_USE_WAITABLE_TIMER_OBJECT
307 HANDLE waitableTimer;
308 #endif
309 HANDLE processingThread;
310 PA_THREAD_ID processingThreadId;
311 HANDLE processingThreadCompleted;
312 #endif
313
314 } PaWinDsStream;
315
316
317 /* Set minimal latency based on the current OS version.
318 * NT has higher latency.
319 */
PaWinDS_GetMinSystemLatencySeconds(void)320 static double PaWinDS_GetMinSystemLatencySeconds( void )
321 {
322 /*
323 NOTE: GetVersionEx() is deprecated as of Windows 8.1 and can not be used to reliably detect
324 versions of Windows higher than Windows 8 (due to manifest requirements for reporting higher versions).
325 Microsoft recommends switching to VerifyVersionInfo (available on Win 2k and later), however GetVersionEx
326 is is faster, for now we just disable the deprecation warning.
327 See: https://msdn.microsoft.com/en-us/library/windows/desktop/ms724451(v=vs.85).aspx
328 See: http://www.codeproject.com/Articles/678606/Part-Overcoming-Windows-s-deprecation-of-GetVe
329 */
330 #pragma warning (disable : 4996) /* use of GetVersionEx */
331
332 double minLatencySeconds;
333 /* Set minimal latency based on whether NT or other OS.
334 * NT has higher latency.
335 */
336
337 OSVERSIONINFO osvi;
338 osvi.dwOSVersionInfoSize = sizeof( osvi );
339 GetVersionEx( &osvi );
340 DBUG(("PA - PlatformId = 0x%x\n", osvi.dwPlatformId ));
341 DBUG(("PA - MajorVersion = 0x%x\n", osvi.dwMajorVersion ));
342 DBUG(("PA - MinorVersion = 0x%x\n", osvi.dwMinorVersion ));
343 /* Check for NT */
344 if( (osvi.dwMajorVersion == 4) && (osvi.dwPlatformId == 2) )
345 {
346 minLatencySeconds = PA_DS_WIN_NT_DEFAULT_LATENCY_;
347 }
348 else if(osvi.dwMajorVersion >= 5)
349 {
350 minLatencySeconds = PA_DS_WIN_WDM_DEFAULT_LATENCY_;
351 }
352 else
353 {
354 minLatencySeconds = PA_DS_WIN_9X_DEFAULT_LATENCY_;
355 }
356 return minLatencySeconds;
357
358 #pragma warning (default : 4996)
359 }
360
361
362 /*************************************************************************
363 ** Return minimum workable latency required for this host. This is returned
364 ** As the default stream latency in PaDeviceInfo.
365 ** Latency can be optionally set by user by setting an environment variable.
366 ** For example, to set latency to 200 msec, put:
367 **
368 ** set PA_MIN_LATENCY_MSEC=200
369 **
370 ** in the AUTOEXEC.BAT file and reboot.
371 ** If the environment variable is not set, then the latency will be determined
372 ** based on the OS. Windows NT has higher latency than Win95.
373 */
374 #define PA_LATENCY_ENV_NAME ("PA_MIN_LATENCY_MSEC")
375 #define PA_ENV_BUF_SIZE (32)
376
PaWinDs_GetMinLatencySeconds(double sampleRate)377 static double PaWinDs_GetMinLatencySeconds( double sampleRate )
378 {
379 char envbuf[PA_ENV_BUF_SIZE];
380 DWORD hresult;
381 double minLatencySeconds = 0;
382
383 /* Let user determine minimal latency by setting environment variable. */
384 hresult = GetEnvironmentVariableA( PA_LATENCY_ENV_NAME, envbuf, PA_ENV_BUF_SIZE );
385 if( (hresult > 0) && (hresult < PA_ENV_BUF_SIZE) )
386 {
387 minLatencySeconds = atoi( envbuf ) * SECONDS_PER_MSEC;
388 }
389 else
390 {
391 minLatencySeconds = PaWinDS_GetMinSystemLatencySeconds();
392 #if PA_USE_HIGH_LATENCY
393 PRINT(("PA - Minimum Latency set to %f msec!\n", minLatencySeconds * MSECS_PER_SECOND ));
394 #endif
395 }
396
397 return minLatencySeconds;
398 }
399
400
401 /************************************************************************************
402 ** Duplicate and convert the input string using the group allocations allocator.
403 ** A NULL string is converted to a zero length string.
404 ** If memory cannot be allocated, NULL is returned.
405 **/
DuplicateDeviceNameString(PaUtilAllocationGroup * allocations,const wchar_t * src)406 static char *DuplicateDeviceNameString( PaUtilAllocationGroup *allocations, const wchar_t* src )
407 {
408 char *result = 0;
409
410 if( src != NULL )
411 {
412 #if !defined(_UNICODE) && !defined(UNICODE)
413 size_t len = WideCharToMultiByte(CP_ACP, 0, src, -1, NULL, 0, NULL, NULL);
414
415 result = (char*)PaUtil_GroupAllocateMemory( allocations, (long)(len + 1) );
416 if( result ) {
417 if (WideCharToMultiByte(CP_ACP, 0, src, -1, result, (int)len, NULL, NULL) == 0) {
418 result = 0;
419 }
420 }
421 #else
422 size_t len = WideCharToMultiByte(CP_UTF8, 0, src, -1, NULL, 0, NULL, NULL);
423
424 result = (char*)PaUtil_GroupAllocateMemory( allocations, (long)(len + 1) );
425 if( result ) {
426 if (WideCharToMultiByte(CP_UTF8, 0, src, -1, result, (int)len, NULL, NULL) == 0) {
427 result = 0;
428 }
429 }
430 #endif
431 }
432 else
433 {
434 result = (char*)PaUtil_GroupAllocateMemory( allocations, 1 );
435 if( result )
436 result[0] = '\0';
437 }
438
439 return result;
440 }
441
442 /************************************************************************************
443 ** DSDeviceNameAndGUID, DSDeviceNameAndGUIDVector used for collecting preliminary
444 ** information during device enumeration.
445 */
446 typedef struct DSDeviceNameAndGUID{
447 char *name; // allocated from parent's allocations, never deleted by this structure
448 GUID guid;
449 LPGUID lpGUID;
450 void *pnpInterface; // wchar_t* interface path, allocated using the DS host api's allocation group
451 } DSDeviceNameAndGUID;
452
453 typedef struct DSDeviceNameAndGUIDVector{
454 PaUtilAllocationGroup *allocations;
455 PaError enumerationError;
456
457 int count;
458 int free;
459 DSDeviceNameAndGUID *items; // Allocated using LocalAlloc()
460 } DSDeviceNameAndGUIDVector;
461
462 typedef struct DSDeviceNamesAndGUIDs{
463 PaWinDsHostApiRepresentation *winDsHostApi;
464 DSDeviceNameAndGUIDVector inputNamesAndGUIDs;
465 DSDeviceNameAndGUIDVector outputNamesAndGUIDs;
466 } DSDeviceNamesAndGUIDs;
467
InitializeDSDeviceNameAndGUIDVector(DSDeviceNameAndGUIDVector * guidVector,PaUtilAllocationGroup * allocations)468 static PaError InitializeDSDeviceNameAndGUIDVector(
469 DSDeviceNameAndGUIDVector *guidVector, PaUtilAllocationGroup *allocations )
470 {
471 PaError result = paNoError;
472
473 guidVector->allocations = allocations;
474 guidVector->enumerationError = paNoError;
475
476 guidVector->count = 0;
477 guidVector->free = 8;
478 guidVector->items = (DSDeviceNameAndGUID*)LocalAlloc( LMEM_FIXED, sizeof(DSDeviceNameAndGUID) * guidVector->free );
479 if( guidVector->items == NULL )
480 result = paInsufficientMemory;
481
482 return result;
483 }
484
ExpandDSDeviceNameAndGUIDVector(DSDeviceNameAndGUIDVector * guidVector)485 static PaError ExpandDSDeviceNameAndGUIDVector( DSDeviceNameAndGUIDVector *guidVector )
486 {
487 PaError result = paNoError;
488 DSDeviceNameAndGUID *newItems;
489 int i;
490
491 /* double size of vector */
492 int size = guidVector->count + guidVector->free;
493 guidVector->free += size;
494
495 newItems = (DSDeviceNameAndGUID*)LocalAlloc( LMEM_FIXED, sizeof(DSDeviceNameAndGUID) * size * 2 );
496 if( newItems == NULL )
497 {
498 result = paInsufficientMemory;
499 }
500 else
501 {
502 for( i=0; i < guidVector->count; ++i )
503 {
504 newItems[i].name = guidVector->items[i].name;
505 if( guidVector->items[i].lpGUID == NULL )
506 {
507 newItems[i].lpGUID = NULL;
508 }
509 else
510 {
511 newItems[i].lpGUID = &newItems[i].guid;
512 memcpy( &newItems[i].guid, guidVector->items[i].lpGUID, sizeof(GUID) );
513 }
514 newItems[i].pnpInterface = guidVector->items[i].pnpInterface;
515 }
516
517 LocalFree( guidVector->items );
518 guidVector->items = newItems;
519 }
520
521 return result;
522 }
523
524 /*
525 it's safe to call DSDeviceNameAndGUIDVector multiple times
526 */
TerminateDSDeviceNameAndGUIDVector(DSDeviceNameAndGUIDVector * guidVector)527 static PaError TerminateDSDeviceNameAndGUIDVector( DSDeviceNameAndGUIDVector *guidVector )
528 {
529 PaError result = paNoError;
530
531 if( guidVector->items != NULL )
532 {
533 if( LocalFree( guidVector->items ) != NULL )
534 result = paInsufficientMemory; /** @todo this isn't the correct error to return from a deallocation failure */
535
536 guidVector->items = NULL;
537 }
538
539 return result;
540 }
541
542 /************************************************************************************
543 ** Collect preliminary device information during DirectSound enumeration
544 */
CollectGUIDsProcW(LPGUID lpGUID,LPCWSTR lpszDesc,LPCWSTR lpszDrvName,LPVOID lpContext)545 static BOOL CALLBACK CollectGUIDsProcW(LPGUID lpGUID,
546 LPCWSTR lpszDesc,
547 LPCWSTR lpszDrvName,
548 LPVOID lpContext )
549 {
550 DSDeviceNameAndGUIDVector *namesAndGUIDs = (DSDeviceNameAndGUIDVector*)lpContext;
551 PaError error;
552
553 (void) lpszDrvName; /* unused variable */
554
555 if( namesAndGUIDs->free == 0 )
556 {
557 error = ExpandDSDeviceNameAndGUIDVector( namesAndGUIDs );
558 if( error != paNoError )
559 {
560 namesAndGUIDs->enumerationError = error;
561 return FALSE;
562 }
563 }
564
565 /* Set GUID pointer, copy GUID to storage in DSDeviceNameAndGUIDVector. */
566 if( lpGUID == NULL )
567 {
568 namesAndGUIDs->items[namesAndGUIDs->count].lpGUID = NULL;
569 }
570 else
571 {
572 namesAndGUIDs->items[namesAndGUIDs->count].lpGUID =
573 &namesAndGUIDs->items[namesAndGUIDs->count].guid;
574
575 memcpy( &namesAndGUIDs->items[namesAndGUIDs->count].guid, lpGUID, sizeof(GUID) );
576 }
577
578 namesAndGUIDs->items[namesAndGUIDs->count].name =
579 DuplicateDeviceNameString( namesAndGUIDs->allocations, lpszDesc );
580 if( namesAndGUIDs->items[namesAndGUIDs->count].name == NULL )
581 {
582 namesAndGUIDs->enumerationError = paInsufficientMemory;
583 return FALSE;
584 }
585
586 namesAndGUIDs->items[namesAndGUIDs->count].pnpInterface = 0;
587
588 ++namesAndGUIDs->count;
589 --namesAndGUIDs->free;
590
591 return TRUE;
592 }
593
594
595 #ifdef PAWIN_USE_WDMKS_DEVICE_INFO
596
DuplicateWCharString(PaUtilAllocationGroup * allocations,wchar_t * source)597 static void *DuplicateWCharString( PaUtilAllocationGroup *allocations, wchar_t *source )
598 {
599 size_t len;
600 wchar_t *result;
601
602 len = wcslen( source );
603 result = (wchar_t*)PaUtil_GroupAllocateMemory( allocations, (long) ((len+1) * sizeof(wchar_t)) );
604 wcscpy( result, source );
605 return result;
606 }
607
KsPropertySetEnumerateCallback(PDSPROPERTY_DIRECTSOUNDDEVICE_DESCRIPTION_W_DATA data,LPVOID context)608 static BOOL CALLBACK KsPropertySetEnumerateCallback( PDSPROPERTY_DIRECTSOUNDDEVICE_DESCRIPTION_W_DATA data, LPVOID context )
609 {
610 int i;
611 DSDeviceNamesAndGUIDs *deviceNamesAndGUIDs = (DSDeviceNamesAndGUIDs*)context;
612
613 /*
614 Apparently data->Interface can be NULL in some cases.
615 Possibly virtual devices without hardware.
616 So we check for NULLs now. See mailing list message November 10, 2012:
617 "[Portaudio] portaudio initialization crash in KsPropertySetEnumerateCallback(pa_win_ds.c)"
618 */
619 if( data->Interface )
620 {
621 if( data->DataFlow == DIRECTSOUNDDEVICE_DATAFLOW_RENDER )
622 {
623 for( i=0; i < deviceNamesAndGUIDs->outputNamesAndGUIDs.count; ++i )
624 {
625 if( deviceNamesAndGUIDs->outputNamesAndGUIDs.items[i].lpGUID
626 && memcmp( &data->DeviceId, deviceNamesAndGUIDs->outputNamesAndGUIDs.items[i].lpGUID, sizeof(GUID) ) == 0 )
627 {
628 deviceNamesAndGUIDs->outputNamesAndGUIDs.items[i].pnpInterface =
629 (char*)DuplicateWCharString( deviceNamesAndGUIDs->winDsHostApi->allocations, data->Interface );
630 break;
631 }
632 }
633 }
634 else if( data->DataFlow == DIRECTSOUNDDEVICE_DATAFLOW_CAPTURE )
635 {
636 for( i=0; i < deviceNamesAndGUIDs->inputNamesAndGUIDs.count; ++i )
637 {
638 if( deviceNamesAndGUIDs->inputNamesAndGUIDs.items[i].lpGUID
639 && memcmp( &data->DeviceId, deviceNamesAndGUIDs->inputNamesAndGUIDs.items[i].lpGUID, sizeof(GUID) ) == 0 )
640 {
641 deviceNamesAndGUIDs->inputNamesAndGUIDs.items[i].pnpInterface =
642 (char*)DuplicateWCharString( deviceNamesAndGUIDs->winDsHostApi->allocations, data->Interface );
643 break;
644 }
645 }
646 }
647 }
648
649 return TRUE;
650 }
651
652
653 static GUID pawin_CLSID_DirectSoundPrivate =
654 { 0x11ab3ec0, 0x25ec, 0x11d1, 0xa4, 0xd8, 0x00, 0xc0, 0x4f, 0xc2, 0x8a, 0xca };
655
656 static GUID pawin_DSPROPSETID_DirectSoundDevice =
657 { 0x84624f82, 0x25ec, 0x11d1, 0xa4, 0xd8, 0x00, 0xc0, 0x4f, 0xc2, 0x8a, 0xca };
658
659 static GUID pawin_IID_IKsPropertySet =
660 { 0x31efac30, 0x515c, 0x11d0, 0xa9, 0xaa, 0x00, 0xaa, 0x00, 0x61, 0xbe, 0x93 };
661
662
663 /*
664 FindDevicePnpInterfaces fills in the pnpInterface fields in deviceNamesAndGUIDs
665 with UNICODE file paths to the devices. The DS documentation mentions
666 at least two techniques by which these Interface paths can be found using IKsPropertySet on
667 the DirectSound class object. One is using the DSPROPERTY_DIRECTSOUNDDEVICE_DESCRIPTION
668 property, and the other is using DSPROPERTY_DIRECTSOUNDDEVICE_ENUMERATE.
669 I tried both methods and only the second worked. I found two postings on the
670 net from people who had the same problem with the first method, so I think the method used here is
671 more common/likely to work. The probem is that IKsPropertySet_Get returns S_OK
672 but the fields of the device description are not filled in.
673
674 The mechanism we use works by registering an enumeration callback which is called for
675 every DSound device. Our callback searches for a device in our deviceNamesAndGUIDs list
676 with the matching GUID and copies the pointer to the Interface path.
677 Note that we could have used this enumeration callback to perform the original
678 device enumeration, however we choose not to so we can disable this step easily.
679
680 Apparently the IKsPropertySet mechanism was added in DirectSound 9c 2004
681 http://www.tech-archive.net/Archive/Development/microsoft.public.win32.programmer.mmedia/2004-12/0099.html
682
683 -- rossb
684 */
FindDevicePnpInterfaces(DSDeviceNamesAndGUIDs * deviceNamesAndGUIDs)685 static void FindDevicePnpInterfaces( DSDeviceNamesAndGUIDs *deviceNamesAndGUIDs )
686 {
687 IClassFactory *pClassFactory;
688
689 if( paWinDsDSoundEntryPoints.DllGetClassObject(&pawin_CLSID_DirectSoundPrivate, &IID_IClassFactory, (PVOID *) &pClassFactory) == S_OK ){
690 IKsPropertySet *pPropertySet;
691 if( pClassFactory->lpVtbl->CreateInstance( pClassFactory, NULL, &pawin_IID_IKsPropertySet, (PVOID *) &pPropertySet) == S_OK ){
692
693 DSPROPERTY_DIRECTSOUNDDEVICE_ENUMERATE_W_DATA data;
694 ULONG bytesReturned;
695
696 data.Callback = KsPropertySetEnumerateCallback;
697 data.Context = deviceNamesAndGUIDs;
698
699 IKsPropertySet_Get( pPropertySet,
700 &pawin_DSPROPSETID_DirectSoundDevice,
701 DSPROPERTY_DIRECTSOUNDDEVICE_ENUMERATE_W,
702 NULL,
703 0,
704 &data,
705 sizeof(data),
706 &bytesReturned
707 );
708
709 IKsPropertySet_Release( pPropertySet );
710 }
711 pClassFactory->lpVtbl->Release( pClassFactory );
712 }
713
714 /*
715 The following code fragment, which I chose not to use, queries for the
716 device interface for a device with a specific GUID:
717
718 ULONG BytesReturned;
719 DSPROPERTY_DIRECTSOUNDDEVICE_DESCRIPTION_W_DATA Property;
720
721 memset (&Property, 0, sizeof(Property));
722 Property.DataFlow = DIRECTSOUNDDEVICE_DATAFLOW_RENDER;
723 Property.DeviceId = *lpGUID;
724
725 hr = IKsPropertySet_Get( pPropertySet,
726 &pawin_DSPROPSETID_DirectSoundDevice,
727 DSPROPERTY_DIRECTSOUNDDEVICE_DESCRIPTION_W,
728 NULL,
729 0,
730 &Property,
731 sizeof(Property),
732 &BytesReturned
733 );
734
735 if( hr == S_OK )
736 {
737 //pnpInterface = Property.Interface;
738 }
739 */
740 }
741 #endif /* PAWIN_USE_WDMKS_DEVICE_INFO */
742
743
744 /*
745 GUIDs for emulated devices which we blacklist below.
746 are there more than two of them??
747 */
748
749 GUID IID_IRolandVSCEmulated1 = {0xc2ad1800, 0xb243, 0x11ce, 0xa8, 0xa4, 0x00, 0xaa, 0x00, 0x6c, 0x45, 0x01};
750 GUID IID_IRolandVSCEmulated2 = {0xc2ad1800, 0xb243, 0x11ce, 0xa8, 0xa4, 0x00, 0xaa, 0x00, 0x6c, 0x45, 0x02};
751
752
753 #define PA_DEFAULTSAMPLERATESEARCHORDER_COUNT_ (13) /* must match array length below */
754 static double defaultSampleRateSearchOrder_[] =
755 { 44100.0, 48000.0, 32000.0, 24000.0, 22050.0, 88200.0, 96000.0, 192000.0,
756 16000.0, 12000.0, 11025.0, 9600.0, 8000.0 };
757
758 /************************************************************************************
759 ** Extract capabilities from an output device, and add it to the device info list
760 ** if successful. This function assumes that there is enough room in the
761 ** device info list to accomodate all entries.
762 **
763 ** The device will not be added to the device list if any errors are encountered.
764 */
AddOutputDeviceInfoFromDirectSound(PaWinDsHostApiRepresentation * winDsHostApi,char * name,LPGUID lpGUID,char * pnpInterface)765 static PaError AddOutputDeviceInfoFromDirectSound(
766 PaWinDsHostApiRepresentation *winDsHostApi, char *name, LPGUID lpGUID, char *pnpInterface )
767 {
768 PaUtilHostApiRepresentation *hostApi = &winDsHostApi->inheritedHostApiRep;
769 PaWinDsDeviceInfo *winDsDeviceInfo = (PaWinDsDeviceInfo*) hostApi->deviceInfos[hostApi->info.deviceCount];
770 PaDeviceInfo *deviceInfo = &winDsDeviceInfo->inheritedDeviceInfo;
771 HRESULT hr;
772 LPDIRECTSOUND lpDirectSound;
773 DSCAPS caps;
774 int deviceOK = TRUE;
775 PaError result = paNoError;
776 int i;
777
778 /* Copy GUID to the device info structure. Set pointer. */
779 if( lpGUID == NULL )
780 {
781 winDsDeviceInfo->lpGUID = NULL;
782 }
783 else
784 {
785 memcpy( &winDsDeviceInfo->guid, lpGUID, sizeof(GUID) );
786 winDsDeviceInfo->lpGUID = &winDsDeviceInfo->guid;
787 }
788
789 if( lpGUID )
790 {
791 if (IsEqualGUID (&IID_IRolandVSCEmulated1,lpGUID) ||
792 IsEqualGUID (&IID_IRolandVSCEmulated2,lpGUID) )
793 {
794 PA_DEBUG(("BLACKLISTED: %s \n",name));
795 return paNoError;
796 }
797 }
798
799 /* Create a DirectSound object for the specified GUID
800 Note that using CoCreateInstance doesn't work on windows CE.
801 */
802 hr = paWinDsDSoundEntryPoints.DirectSoundCreate( lpGUID, &lpDirectSound, NULL );
803
804 /** try using CoCreateInstance because DirectSoundCreate was hanging under
805 some circumstances - note this was probably related to the
806 #define BOOL short bug which has now been fixed
807 @todo delete this comment and the following code once we've ensured
808 there is no bug.
809 */
810 /*
811 hr = CoCreateInstance( &CLSID_DirectSound, NULL, CLSCTX_INPROC_SERVER,
812 &IID_IDirectSound, (void**)&lpDirectSound );
813
814 if( hr == S_OK )
815 {
816 hr = IDirectSound_Initialize( lpDirectSound, lpGUID );
817 }
818 */
819
820 if( hr != DS_OK )
821 {
822 if (hr == DSERR_ALLOCATED)
823 PA_DEBUG(("AddOutputDeviceInfoFromDirectSound %s DSERR_ALLOCATED\n",name));
824 DBUG(("Cannot create DirectSound for %s. Result = 0x%x\n", name, hr ));
825 if (lpGUID)
826 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",
827 name,
828 lpGUID->Data1,
829 lpGUID->Data2,
830 lpGUID->Data3,
831 lpGUID->Data4[0],
832 lpGUID->Data4[1],
833 lpGUID->Data4[2],
834 lpGUID->Data4[3],
835 lpGUID->Data4[4],
836 lpGUID->Data4[5],
837 lpGUID->Data4[6],
838 lpGUID->Data4[7]));
839
840 deviceOK = FALSE;
841 }
842 else
843 {
844 /* Query device characteristics. */
845 memset( &caps, 0, sizeof(caps) );
846 caps.dwSize = sizeof(caps);
847 hr = IDirectSound_GetCaps( lpDirectSound, &caps );
848 if( hr != DS_OK )
849 {
850 DBUG(("Cannot GetCaps() for DirectSound device %s. Result = 0x%x\n", name, hr ));
851 deviceOK = FALSE;
852 }
853 else
854 {
855
856 #if PA_USE_WMME
857 if( caps.dwFlags & DSCAPS_EMULDRIVER )
858 {
859 /* If WMME supported, then reject Emulated drivers because they are lousy. */
860 deviceOK = FALSE;
861 }
862 #endif
863
864 if( deviceOK )
865 {
866 deviceInfo->maxInputChannels = 0;
867 winDsDeviceInfo->deviceInputChannelCountIsKnown = 1;
868
869 /* DS output capabilities only indicate supported number of channels
870 using two flags which indicate mono and/or stereo.
871 We assume that stereo devices may support more than 2 channels
872 (as is the case with 5.1 devices for example) and so
873 set deviceOutputChannelCountIsKnown to 0 (unknown).
874 In this case OpenStream will try to open the device
875 when the user requests more than 2 channels, rather than
876 returning an error.
877 */
878 if( caps.dwFlags & DSCAPS_PRIMARYSTEREO )
879 {
880 deviceInfo->maxOutputChannels = 2;
881 winDsDeviceInfo->deviceOutputChannelCountIsKnown = 0;
882 }
883 else
884 {
885 deviceInfo->maxOutputChannels = 1;
886 winDsDeviceInfo->deviceOutputChannelCountIsKnown = 1;
887 }
888
889 /* Guess channels count from speaker configuration. We do it only when
890 pnpInterface is NULL or when PAWIN_USE_WDMKS_DEVICE_INFO is undefined.
891 */
892 #ifdef PAWIN_USE_WDMKS_DEVICE_INFO
893 if( !pnpInterface )
894 #endif
895 {
896 DWORD spkrcfg;
897 if( SUCCEEDED(IDirectSound_GetSpeakerConfig( lpDirectSound, &spkrcfg )) )
898 {
899 int count = 0;
900 switch (DSSPEAKER_CONFIG(spkrcfg))
901 {
902 case DSSPEAKER_HEADPHONE: count = 2; break;
903 case DSSPEAKER_MONO: count = 1; break;
904 case DSSPEAKER_QUAD: count = 4; break;
905 case DSSPEAKER_STEREO: count = 2; break;
906 case DSSPEAKER_SURROUND: count = 4; break;
907 case DSSPEAKER_5POINT1: count = 6; break;
908 #ifndef DSSPEAKER_7POINT1
909 #define DSSPEAKER_7POINT1 0x00000007
910 #endif
911 case DSSPEAKER_7POINT1: count = 8; break;
912 #ifndef DSSPEAKER_7POINT1_SURROUND
913 #define DSSPEAKER_7POINT1_SURROUND 0x00000008
914 #endif
915 case DSSPEAKER_7POINT1_SURROUND: count = 8; break;
916 #ifndef DSSPEAKER_5POINT1_SURROUND
917 #define DSSPEAKER_5POINT1_SURROUND 0x00000009
918 #endif
919 case DSSPEAKER_5POINT1_SURROUND: count = 6; break;
920 }
921 if( count )
922 {
923 deviceInfo->maxOutputChannels = count;
924 winDsDeviceInfo->deviceOutputChannelCountIsKnown = 1;
925 }
926 }
927 }
928
929 #ifdef PAWIN_USE_WDMKS_DEVICE_INFO
930 if( pnpInterface )
931 {
932 int count = PaWin_WDMKS_QueryFilterMaximumChannelCount( pnpInterface, /* isInput= */ 0 );
933 if( count > 0 )
934 {
935 deviceInfo->maxOutputChannels = count;
936 winDsDeviceInfo->deviceOutputChannelCountIsKnown = 1;
937 }
938 }
939 #endif /* PAWIN_USE_WDMKS_DEVICE_INFO */
940
941 /* initialize defaultSampleRate */
942
943 if( caps.dwFlags & DSCAPS_CONTINUOUSRATE )
944 {
945 /* initialize to caps.dwMaxSecondarySampleRate incase none of the standard rates match */
946 deviceInfo->defaultSampleRate = caps.dwMaxSecondarySampleRate;
947
948 for( i = 0; i < PA_DEFAULTSAMPLERATESEARCHORDER_COUNT_; ++i )
949 {
950 if( defaultSampleRateSearchOrder_[i] >= caps.dwMinSecondarySampleRate
951 && defaultSampleRateSearchOrder_[i] <= caps.dwMaxSecondarySampleRate )
952 {
953 deviceInfo->defaultSampleRate = defaultSampleRateSearchOrder_[i];
954 break;
955 }
956 }
957 }
958 else if( caps.dwMinSecondarySampleRate == caps.dwMaxSecondarySampleRate )
959 {
960 if( caps.dwMinSecondarySampleRate == 0 )
961 {
962 /*
963 ** On my Thinkpad 380Z, DirectSoundV6 returns min-max=0 !!
964 ** But it supports continuous sampling.
965 ** So fake range of rates, and hope it really supports it.
966 */
967 deviceInfo->defaultSampleRate = 48000.0f; /* assume 48000 as the default */
968
969 DBUG(("PA - Reported rates both zero. Setting to fake values for device #%s\n", name ));
970 }
971 else
972 {
973 deviceInfo->defaultSampleRate = caps.dwMaxSecondarySampleRate;
974 }
975 }
976 else if( (caps.dwMinSecondarySampleRate < 1000.0) && (caps.dwMaxSecondarySampleRate > 50000.0) )
977 {
978 /* The EWS88MT drivers lie, lie, lie. The say they only support two rates, 100 & 100000.
979 ** But we know that they really support a range of rates!
980 ** So when we see a ridiculous set of rates, assume it is a range.
981 */
982 deviceInfo->defaultSampleRate = 48000.0f; /* assume 48000 as the default */
983 DBUG(("PA - Sample rate range used instead of two odd values for device #%s\n", name ));
984 }
985 else deviceInfo->defaultSampleRate = caps.dwMaxSecondarySampleRate;
986
987 //printf( "min %d max %d\n", caps.dwMinSecondarySampleRate, caps.dwMaxSecondarySampleRate );
988 // dwFlags | DSCAPS_CONTINUOUSRATE
989
990 deviceInfo->defaultLowInputLatency = 0.;
991 deviceInfo->defaultHighInputLatency = 0.;
992
993 deviceInfo->defaultLowOutputLatency = PaWinDs_GetMinLatencySeconds( deviceInfo->defaultSampleRate );
994 deviceInfo->defaultHighOutputLatency = deviceInfo->defaultLowOutputLatency * 2;
995 }
996 }
997
998 IDirectSound_Release( lpDirectSound );
999 }
1000
1001 if( deviceOK )
1002 {
1003 deviceInfo->name = name;
1004
1005 if( lpGUID == NULL )
1006 hostApi->info.defaultOutputDevice = hostApi->info.deviceCount;
1007
1008 hostApi->info.deviceCount++;
1009 }
1010
1011 return result;
1012 }
1013
1014
1015 /************************************************************************************
1016 ** Extract capabilities from an input device, and add it to the device info list
1017 ** if successful. This function assumes that there is enough room in the
1018 ** device info list to accomodate all entries.
1019 **
1020 ** The device will not be added to the device list if any errors are encountered.
1021 */
AddInputDeviceInfoFromDirectSoundCapture(PaWinDsHostApiRepresentation * winDsHostApi,char * name,LPGUID lpGUID,char * pnpInterface)1022 static PaError AddInputDeviceInfoFromDirectSoundCapture(
1023 PaWinDsHostApiRepresentation *winDsHostApi, char *name, LPGUID lpGUID, char *pnpInterface )
1024 {
1025 PaUtilHostApiRepresentation *hostApi = &winDsHostApi->inheritedHostApiRep;
1026 PaWinDsDeviceInfo *winDsDeviceInfo = (PaWinDsDeviceInfo*) hostApi->deviceInfos[hostApi->info.deviceCount];
1027 PaDeviceInfo *deviceInfo = &winDsDeviceInfo->inheritedDeviceInfo;
1028 HRESULT hr;
1029 LPDIRECTSOUNDCAPTURE lpDirectSoundCapture;
1030 DSCCAPS caps;
1031 int deviceOK = TRUE;
1032 PaError result = paNoError;
1033
1034 /* Copy GUID to the device info structure. Set pointer. */
1035 if( lpGUID == NULL )
1036 {
1037 winDsDeviceInfo->lpGUID = NULL;
1038 }
1039 else
1040 {
1041 winDsDeviceInfo->lpGUID = &winDsDeviceInfo->guid;
1042 memcpy( &winDsDeviceInfo->guid, lpGUID, sizeof(GUID) );
1043 }
1044
1045 hr = paWinDsDSoundEntryPoints.DirectSoundCaptureCreate( lpGUID, &lpDirectSoundCapture, NULL );
1046
1047 /** try using CoCreateInstance because DirectSoundCreate was hanging under
1048 some circumstances - note this was probably related to the
1049 #define BOOL short bug which has now been fixed
1050 @todo delete this comment and the following code once we've ensured
1051 there is no bug.
1052 */
1053 /*
1054 hr = CoCreateInstance( &CLSID_DirectSoundCapture, NULL, CLSCTX_INPROC_SERVER,
1055 &IID_IDirectSoundCapture, (void**)&lpDirectSoundCapture );
1056 */
1057 if( hr != DS_OK )
1058 {
1059 DBUG(("Cannot create Capture for %s. Result = 0x%x\n", name, hr ));
1060 deviceOK = FALSE;
1061 }
1062 else
1063 {
1064 /* Query device characteristics. */
1065 memset( &caps, 0, sizeof(caps) );
1066 caps.dwSize = sizeof(caps);
1067 hr = IDirectSoundCapture_GetCaps( lpDirectSoundCapture, &caps );
1068 if( hr != DS_OK )
1069 {
1070 DBUG(("Cannot GetCaps() for Capture device %s. Result = 0x%x\n", name, hr ));
1071 deviceOK = FALSE;
1072 }
1073 else
1074 {
1075 #if PA_USE_WMME
1076 if( caps.dwFlags & DSCAPS_EMULDRIVER )
1077 {
1078 /* If WMME supported, then reject Emulated drivers because they are lousy. */
1079 deviceOK = FALSE;
1080 }
1081 #endif
1082
1083 if( deviceOK )
1084 {
1085 deviceInfo->maxInputChannels = caps.dwChannels;
1086 winDsDeviceInfo->deviceInputChannelCountIsKnown = 1;
1087
1088 deviceInfo->maxOutputChannels = 0;
1089 winDsDeviceInfo->deviceOutputChannelCountIsKnown = 1;
1090
1091 #ifdef PAWIN_USE_WDMKS_DEVICE_INFO
1092 if( pnpInterface )
1093 {
1094 int count = PaWin_WDMKS_QueryFilterMaximumChannelCount( pnpInterface, /* isInput= */ 1 );
1095 if( count > 0 )
1096 {
1097 deviceInfo->maxInputChannels = count;
1098 winDsDeviceInfo->deviceInputChannelCountIsKnown = 1;
1099 }
1100 }
1101 #endif /* PAWIN_USE_WDMKS_DEVICE_INFO */
1102
1103 /* constants from a WINE patch by Francois Gouget, see:
1104 http://www.winehq.com/hypermail/wine-patches/2003/01/0290.html
1105
1106 ---
1107 Date: Fri, 14 May 2004 10:38:12 +0200 (CEST)
1108 From: Francois Gouget <fgouget@ ... .fr>
1109 To: Ross Bencina <rbencina@ ... .au>
1110 Subject: Re: Permission to use wine 48/96 wave patch in BSD licensed library
1111
1112 [snip]
1113
1114 I give you permission to use the patch below under the BSD license.
1115 http://www.winehq.com/hypermail/wine-patches/2003/01/0290.html
1116
1117 [snip]
1118 */
1119 #ifndef WAVE_FORMAT_48M08
1120 #define WAVE_FORMAT_48M08 0x00001000 /* 48 kHz, Mono, 8-bit */
1121 #define WAVE_FORMAT_48S08 0x00002000 /* 48 kHz, Stereo, 8-bit */
1122 #define WAVE_FORMAT_48M16 0x00004000 /* 48 kHz, Mono, 16-bit */
1123 #define WAVE_FORMAT_48S16 0x00008000 /* 48 kHz, Stereo, 16-bit */
1124 #define WAVE_FORMAT_96M08 0x00010000 /* 96 kHz, Mono, 8-bit */
1125 #define WAVE_FORMAT_96S08 0x00020000 /* 96 kHz, Stereo, 8-bit */
1126 #define WAVE_FORMAT_96M16 0x00040000 /* 96 kHz, Mono, 16-bit */
1127 #define WAVE_FORMAT_96S16 0x00080000 /* 96 kHz, Stereo, 16-bit */
1128 #endif
1129
1130 /* defaultSampleRate */
1131 if( caps.dwChannels == 2 )
1132 {
1133 if( caps.dwFormats & WAVE_FORMAT_4S16 )
1134 deviceInfo->defaultSampleRate = 44100.0;
1135 else if( caps.dwFormats & WAVE_FORMAT_48S16 )
1136 deviceInfo->defaultSampleRate = 48000.0;
1137 else if( caps.dwFormats & WAVE_FORMAT_2S16 )
1138 deviceInfo->defaultSampleRate = 22050.0;
1139 else if( caps.dwFormats & WAVE_FORMAT_1S16 )
1140 deviceInfo->defaultSampleRate = 11025.0;
1141 else if( caps.dwFormats & WAVE_FORMAT_96S16 )
1142 deviceInfo->defaultSampleRate = 96000.0;
1143 else
1144 deviceInfo->defaultSampleRate = 48000.0; /* assume 48000 as the default */
1145 }
1146 else if( caps.dwChannels == 1 )
1147 {
1148 if( caps.dwFormats & WAVE_FORMAT_4M16 )
1149 deviceInfo->defaultSampleRate = 44100.0;
1150 else if( caps.dwFormats & WAVE_FORMAT_48M16 )
1151 deviceInfo->defaultSampleRate = 48000.0;
1152 else if( caps.dwFormats & WAVE_FORMAT_2M16 )
1153 deviceInfo->defaultSampleRate = 22050.0;
1154 else if( caps.dwFormats & WAVE_FORMAT_1M16 )
1155 deviceInfo->defaultSampleRate = 11025.0;
1156 else if( caps.dwFormats & WAVE_FORMAT_96M16 )
1157 deviceInfo->defaultSampleRate = 96000.0;
1158 else
1159 deviceInfo->defaultSampleRate = 48000.0; /* assume 48000 as the default */
1160 }
1161 else deviceInfo->defaultSampleRate = 48000.0; /* assume 48000 as the default */
1162
1163 deviceInfo->defaultLowInputLatency = PaWinDs_GetMinLatencySeconds( deviceInfo->defaultSampleRate );
1164 deviceInfo->defaultHighInputLatency = deviceInfo->defaultLowInputLatency * 2;
1165
1166 deviceInfo->defaultLowOutputLatency = 0.;
1167 deviceInfo->defaultHighOutputLatency = 0.;
1168 }
1169 }
1170
1171 IDirectSoundCapture_Release( lpDirectSoundCapture );
1172 }
1173
1174 if( deviceOK )
1175 {
1176 deviceInfo->name = name;
1177
1178 if( lpGUID == NULL )
1179 hostApi->info.defaultInputDevice = hostApi->info.deviceCount;
1180
1181 hostApi->info.deviceCount++;
1182 }
1183
1184 return result;
1185 }
1186
1187
1188 /***********************************************************************************/
PaWinDs_Initialize(PaUtilHostApiRepresentation ** hostApi,PaHostApiIndex hostApiIndex)1189 PaError PaWinDs_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex hostApiIndex )
1190 {
1191 PaError result = paNoError;
1192 int i, deviceCount;
1193 PaWinDsHostApiRepresentation *winDsHostApi;
1194 DSDeviceNamesAndGUIDs deviceNamesAndGUIDs;
1195 PaWinDsDeviceInfo *deviceInfoArray;
1196
1197 PaWinDs_InitializeDSoundEntryPoints();
1198
1199 /* initialise guid vectors so they can be safely deleted on error */
1200 deviceNamesAndGUIDs.winDsHostApi = NULL;
1201 deviceNamesAndGUIDs.inputNamesAndGUIDs.items = NULL;
1202 deviceNamesAndGUIDs.outputNamesAndGUIDs.items = NULL;
1203
1204 winDsHostApi = (PaWinDsHostApiRepresentation*)PaUtil_AllocateMemory( sizeof(PaWinDsHostApiRepresentation) );
1205 if( !winDsHostApi )
1206 {
1207 result = paInsufficientMemory;
1208 goto error;
1209 }
1210
1211 memset( winDsHostApi, 0, sizeof(PaWinDsHostApiRepresentation) ); /* ensure all fields are zeroed. especially winDsHostApi->allocations */
1212
1213 result = PaWinUtil_CoInitialize( paDirectSound, &winDsHostApi->comInitializationResult );
1214 if( result != paNoError )
1215 {
1216 goto error;
1217 }
1218
1219 winDsHostApi->allocations = PaUtil_CreateAllocationGroup();
1220 if( !winDsHostApi->allocations )
1221 {
1222 result = paInsufficientMemory;
1223 goto error;
1224 }
1225
1226 *hostApi = &winDsHostApi->inheritedHostApiRep;
1227 (*hostApi)->info.structVersion = 1;
1228 (*hostApi)->info.type = paDirectSound;
1229 (*hostApi)->info.name = "Windows DirectSound";
1230
1231 (*hostApi)->info.deviceCount = 0;
1232 (*hostApi)->info.defaultInputDevice = paNoDevice;
1233 (*hostApi)->info.defaultOutputDevice = paNoDevice;
1234
1235
1236 /* DSound - enumerate devices to count them and to gather their GUIDs */
1237
1238 result = InitializeDSDeviceNameAndGUIDVector( &deviceNamesAndGUIDs.inputNamesAndGUIDs, winDsHostApi->allocations );
1239 if( result != paNoError )
1240 goto error;
1241
1242 result = InitializeDSDeviceNameAndGUIDVector( &deviceNamesAndGUIDs.outputNamesAndGUIDs, winDsHostApi->allocations );
1243 if( result != paNoError )
1244 goto error;
1245
1246 paWinDsDSoundEntryPoints.DirectSoundCaptureEnumerateW( (LPDSENUMCALLBACKW)CollectGUIDsProcW, (void *)&deviceNamesAndGUIDs.inputNamesAndGUIDs );
1247
1248 paWinDsDSoundEntryPoints.DirectSoundEnumerateW( (LPDSENUMCALLBACKW)CollectGUIDsProcW, (void *)&deviceNamesAndGUIDs.outputNamesAndGUIDs );
1249
1250 if( deviceNamesAndGUIDs.inputNamesAndGUIDs.enumerationError != paNoError )
1251 {
1252 result = deviceNamesAndGUIDs.inputNamesAndGUIDs.enumerationError;
1253 goto error;
1254 }
1255
1256 if( deviceNamesAndGUIDs.outputNamesAndGUIDs.enumerationError != paNoError )
1257 {
1258 result = deviceNamesAndGUIDs.outputNamesAndGUIDs.enumerationError;
1259 goto error;
1260 }
1261
1262 deviceCount = deviceNamesAndGUIDs.inputNamesAndGUIDs.count + deviceNamesAndGUIDs.outputNamesAndGUIDs.count;
1263
1264 #ifdef PAWIN_USE_WDMKS_DEVICE_INFO
1265 if( deviceCount > 0 )
1266 {
1267 deviceNamesAndGUIDs.winDsHostApi = winDsHostApi;
1268 FindDevicePnpInterfaces( &deviceNamesAndGUIDs );
1269 }
1270 #endif /* PAWIN_USE_WDMKS_DEVICE_INFO */
1271
1272 if( deviceCount > 0 )
1273 {
1274 /* allocate array for pointers to PaDeviceInfo structs */
1275 (*hostApi)->deviceInfos = (PaDeviceInfo**)PaUtil_GroupAllocateMemory(
1276 winDsHostApi->allocations, sizeof(PaDeviceInfo*) * deviceCount );
1277 if( !(*hostApi)->deviceInfos )
1278 {
1279 result = paInsufficientMemory;
1280 goto error;
1281 }
1282
1283 /* allocate all PaDeviceInfo structs in a contiguous block */
1284 deviceInfoArray = (PaWinDsDeviceInfo*)PaUtil_GroupAllocateMemory(
1285 winDsHostApi->allocations, sizeof(PaWinDsDeviceInfo) * deviceCount );
1286 if( !deviceInfoArray )
1287 {
1288 result = paInsufficientMemory;
1289 goto error;
1290 }
1291
1292 for( i=0; i < deviceCount; ++i )
1293 {
1294 PaDeviceInfo *deviceInfo = &deviceInfoArray[i].inheritedDeviceInfo;
1295 deviceInfo->structVersion = 2;
1296 deviceInfo->hostApi = hostApiIndex;
1297 deviceInfo->name = 0;
1298 (*hostApi)->deviceInfos[i] = deviceInfo;
1299 }
1300
1301 for( i=0; i < deviceNamesAndGUIDs.inputNamesAndGUIDs.count; ++i )
1302 {
1303 result = AddInputDeviceInfoFromDirectSoundCapture( winDsHostApi,
1304 deviceNamesAndGUIDs.inputNamesAndGUIDs.items[i].name,
1305 deviceNamesAndGUIDs.inputNamesAndGUIDs.items[i].lpGUID,
1306 deviceNamesAndGUIDs.inputNamesAndGUIDs.items[i].pnpInterface );
1307 if( result != paNoError )
1308 goto error;
1309 }
1310
1311 for( i=0; i < deviceNamesAndGUIDs.outputNamesAndGUIDs.count; ++i )
1312 {
1313 result = AddOutputDeviceInfoFromDirectSound( winDsHostApi,
1314 deviceNamesAndGUIDs.outputNamesAndGUIDs.items[i].name,
1315 deviceNamesAndGUIDs.outputNamesAndGUIDs.items[i].lpGUID,
1316 deviceNamesAndGUIDs.outputNamesAndGUIDs.items[i].pnpInterface );
1317 if( result != paNoError )
1318 goto error;
1319 }
1320 }
1321
1322 result = TerminateDSDeviceNameAndGUIDVector( &deviceNamesAndGUIDs.inputNamesAndGUIDs );
1323 if( result != paNoError )
1324 goto error;
1325
1326 result = TerminateDSDeviceNameAndGUIDVector( &deviceNamesAndGUIDs.outputNamesAndGUIDs );
1327 if( result != paNoError )
1328 goto error;
1329
1330
1331 (*hostApi)->Terminate = Terminate;
1332 (*hostApi)->OpenStream = OpenStream;
1333 (*hostApi)->IsFormatSupported = IsFormatSupported;
1334
1335 PaUtil_InitializeStreamInterface( &winDsHostApi->callbackStreamInterface, CloseStream, StartStream,
1336 StopStream, AbortStream, IsStreamStopped, IsStreamActive,
1337 GetStreamTime, GetStreamCpuLoad,
1338 PaUtil_DummyRead, PaUtil_DummyWrite,
1339 PaUtil_DummyGetReadAvailable, PaUtil_DummyGetWriteAvailable );
1340
1341 PaUtil_InitializeStreamInterface( &winDsHostApi->blockingStreamInterface, CloseStream, StartStream,
1342 StopStream, AbortStream, IsStreamStopped, IsStreamActive,
1343 GetStreamTime, PaUtil_DummyGetCpuLoad,
1344 ReadStream, WriteStream, GetStreamReadAvailable, GetStreamWriteAvailable );
1345
1346 return result;
1347
1348 error:
1349 TerminateDSDeviceNameAndGUIDVector( &deviceNamesAndGUIDs.inputNamesAndGUIDs );
1350 TerminateDSDeviceNameAndGUIDVector( &deviceNamesAndGUIDs.outputNamesAndGUIDs );
1351
1352 Terminate( (struct PaUtilHostApiRepresentation *)winDsHostApi );
1353
1354 return result;
1355 }
1356
1357
1358 /***********************************************************************************/
Terminate(struct PaUtilHostApiRepresentation * hostApi)1359 static void Terminate( struct PaUtilHostApiRepresentation *hostApi )
1360 {
1361 PaWinDsHostApiRepresentation *winDsHostApi = (PaWinDsHostApiRepresentation*)hostApi;
1362
1363 if( winDsHostApi ){
1364 if( winDsHostApi->allocations )
1365 {
1366 PaUtil_FreeAllAllocations( winDsHostApi->allocations );
1367 PaUtil_DestroyAllocationGroup( winDsHostApi->allocations );
1368 }
1369
1370 PaWinUtil_CoUninitialize( paDirectSound, &winDsHostApi->comInitializationResult );
1371
1372 PaUtil_FreeMemory( winDsHostApi );
1373 }
1374
1375 PaWinDs_TerminateDSoundEntryPoints();
1376 }
1377
ValidateWinDirectSoundSpecificStreamInfo(const PaStreamParameters * streamParameters,const PaWinDirectSoundStreamInfo * streamInfo)1378 static PaError ValidateWinDirectSoundSpecificStreamInfo(
1379 const PaStreamParameters *streamParameters,
1380 const PaWinDirectSoundStreamInfo *streamInfo )
1381 {
1382 if( streamInfo )
1383 {
1384 if( streamInfo->size != sizeof( PaWinDirectSoundStreamInfo )
1385 || streamInfo->version != 2 )
1386 {
1387 return paIncompatibleHostApiSpecificStreamInfo;
1388 }
1389
1390 if( streamInfo->flags & paWinDirectSoundUseLowLevelLatencyParameters )
1391 {
1392 if( streamInfo->framesPerBuffer <= 0 )
1393 return paIncompatibleHostApiSpecificStreamInfo;
1394
1395 }
1396 }
1397
1398 return paNoError;
1399 }
1400
1401 /***********************************************************************************/
IsFormatSupported(struct PaUtilHostApiRepresentation * hostApi,const PaStreamParameters * inputParameters,const PaStreamParameters * outputParameters,double sampleRate)1402 static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi,
1403 const PaStreamParameters *inputParameters,
1404 const PaStreamParameters *outputParameters,
1405 double sampleRate )
1406 {
1407 PaError result;
1408 PaWinDsDeviceInfo *inputWinDsDeviceInfo, *outputWinDsDeviceInfo;
1409 PaDeviceInfo *inputDeviceInfo, *outputDeviceInfo;
1410 int inputChannelCount, outputChannelCount;
1411 PaSampleFormat inputSampleFormat, outputSampleFormat;
1412 PaWinDirectSoundStreamInfo *inputStreamInfo, *outputStreamInfo;
1413
1414 if( inputParameters )
1415 {
1416 inputWinDsDeviceInfo = (PaWinDsDeviceInfo*) hostApi->deviceInfos[ inputParameters->device ];
1417 inputDeviceInfo = &inputWinDsDeviceInfo->inheritedDeviceInfo;
1418
1419 inputChannelCount = inputParameters->channelCount;
1420 inputSampleFormat = inputParameters->sampleFormat;
1421
1422 /* unless alternate device specification is supported, reject the use of
1423 paUseHostApiSpecificDeviceSpecification */
1424
1425 if( inputParameters->device == paUseHostApiSpecificDeviceSpecification )
1426 return paInvalidDevice;
1427
1428 /* check that input device can support inputChannelCount */
1429 if( inputWinDsDeviceInfo->deviceInputChannelCountIsKnown
1430 && inputChannelCount > inputDeviceInfo->maxInputChannels )
1431 return paInvalidChannelCount;
1432
1433 /* validate inputStreamInfo */
1434 inputStreamInfo = (PaWinDirectSoundStreamInfo*)inputParameters->hostApiSpecificStreamInfo;
1435 result = ValidateWinDirectSoundSpecificStreamInfo( inputParameters, inputStreamInfo );
1436 if( result != paNoError ) return result;
1437 }
1438 else
1439 {
1440 inputChannelCount = 0;
1441 }
1442
1443 if( outputParameters )
1444 {
1445 outputWinDsDeviceInfo = (PaWinDsDeviceInfo*) hostApi->deviceInfos[ outputParameters->device ];
1446 outputDeviceInfo = &outputWinDsDeviceInfo->inheritedDeviceInfo;
1447
1448 outputChannelCount = outputParameters->channelCount;
1449 outputSampleFormat = outputParameters->sampleFormat;
1450
1451 /* unless alternate device specification is supported, reject the use of
1452 paUseHostApiSpecificDeviceSpecification */
1453
1454 if( outputParameters->device == paUseHostApiSpecificDeviceSpecification )
1455 return paInvalidDevice;
1456
1457 /* check that output device can support inputChannelCount */
1458 if( outputWinDsDeviceInfo->deviceOutputChannelCountIsKnown
1459 && outputChannelCount > outputDeviceInfo->maxOutputChannels )
1460 return paInvalidChannelCount;
1461
1462 /* validate outputStreamInfo */
1463 outputStreamInfo = (PaWinDirectSoundStreamInfo*)outputParameters->hostApiSpecificStreamInfo;
1464 result = ValidateWinDirectSoundSpecificStreamInfo( outputParameters, outputStreamInfo );
1465 if( result != paNoError ) return result;
1466 }
1467 else
1468 {
1469 outputChannelCount = 0;
1470 }
1471
1472 /*
1473 IMPLEMENT ME:
1474
1475 - if a full duplex stream is requested, check that the combination
1476 of input and output parameters is supported if necessary
1477
1478 - check that the device supports sampleRate
1479
1480 Because the buffer adapter handles conversion between all standard
1481 sample formats, the following checks are only required if paCustomFormat
1482 is implemented, or under some other unusual conditions.
1483
1484 - check that input device can support inputSampleFormat, or that
1485 we have the capability to convert from outputSampleFormat to
1486 a native format
1487
1488 - check that output device can support outputSampleFormat, or that
1489 we have the capability to convert from outputSampleFormat to
1490 a native format
1491 */
1492
1493 return paFormatIsSupported;
1494 }
1495
1496
1497 #ifdef PAWIN_USE_DIRECTSOUNDFULLDUPLEXCREATE
InitFullDuplexInputOutputBuffers(PaWinDsStream * stream,PaWinDsDeviceInfo * inputDevice,PaSampleFormat hostInputSampleFormat,WORD inputChannelCount,int bytesPerInputBuffer,PaWinWaveFormatChannelMask inputChannelMask,PaWinDsDeviceInfo * outputDevice,PaSampleFormat hostOutputSampleFormat,WORD outputChannelCount,int bytesPerOutputBuffer,PaWinWaveFormatChannelMask outputChannelMask,unsigned long nFrameRate)1498 static HRESULT InitFullDuplexInputOutputBuffers( PaWinDsStream *stream,
1499 PaWinDsDeviceInfo *inputDevice,
1500 PaSampleFormat hostInputSampleFormat,
1501 WORD inputChannelCount,
1502 int bytesPerInputBuffer,
1503 PaWinWaveFormatChannelMask inputChannelMask,
1504 PaWinDsDeviceInfo *outputDevice,
1505 PaSampleFormat hostOutputSampleFormat,
1506 WORD outputChannelCount,
1507 int bytesPerOutputBuffer,
1508 PaWinWaveFormatChannelMask outputChannelMask,
1509 unsigned long nFrameRate
1510 )
1511 {
1512 HRESULT hr;
1513 DSCBUFFERDESC captureDesc;
1514 PaWinWaveFormat captureWaveFormat;
1515 DSBUFFERDESC secondaryRenderDesc;
1516 PaWinWaveFormat renderWaveFormat;
1517 LPDIRECTSOUNDBUFFER8 pRenderBuffer8;
1518 LPDIRECTSOUNDCAPTUREBUFFER8 pCaptureBuffer8;
1519
1520 // capture buffer description
1521
1522 // only try wave format extensible. assume it's available on all ds 8 systems
1523 PaWin_InitializeWaveFormatExtensible( &captureWaveFormat, inputChannelCount,
1524 hostInputSampleFormat, PaWin_SampleFormatToLinearWaveFormatTag( hostInputSampleFormat ),
1525 nFrameRate, inputChannelMask );
1526
1527 ZeroMemory(&captureDesc, sizeof(DSCBUFFERDESC));
1528 captureDesc.dwSize = sizeof(DSCBUFFERDESC);
1529 captureDesc.dwFlags = 0;
1530 captureDesc.dwBufferBytes = bytesPerInputBuffer;
1531 captureDesc.lpwfxFormat = (WAVEFORMATEX*)&captureWaveFormat;
1532
1533 // render buffer description
1534
1535 PaWin_InitializeWaveFormatExtensible( &renderWaveFormat, outputChannelCount,
1536 hostOutputSampleFormat, PaWin_SampleFormatToLinearWaveFormatTag( hostOutputSampleFormat ),
1537 nFrameRate, outputChannelMask );
1538
1539 ZeroMemory(&secondaryRenderDesc, sizeof(DSBUFFERDESC));
1540 secondaryRenderDesc.dwSize = sizeof(DSBUFFERDESC);
1541 secondaryRenderDesc.dwFlags = DSBCAPS_GLOBALFOCUS | DSBCAPS_GETCURRENTPOSITION2;
1542 secondaryRenderDesc.dwBufferBytes = bytesPerOutputBuffer;
1543 secondaryRenderDesc.lpwfxFormat = (WAVEFORMATEX*)&renderWaveFormat;
1544
1545 /* note that we don't create a primary buffer here at all */
1546
1547 hr = paWinDsDSoundEntryPoints.DirectSoundFullDuplexCreate8(
1548 inputDevice->lpGUID, outputDevice->lpGUID,
1549 &captureDesc, &secondaryRenderDesc,
1550 GetDesktopWindow(), /* see InitOutputBuffer() for a discussion of whether this is a good idea */
1551 DSSCL_EXCLUSIVE,
1552 &stream->pDirectSoundFullDuplex8,
1553 &pCaptureBuffer8,
1554 &pRenderBuffer8,
1555 NULL /* pUnkOuter must be NULL */
1556 );
1557
1558 if( hr == DS_OK )
1559 {
1560 PA_DEBUG(("DirectSoundFullDuplexCreate succeeded!\n"));
1561
1562 /* retrieve the pre ds 8 buffer interfaces which are used by the rest of the code */
1563
1564 hr = IUnknown_QueryInterface( pCaptureBuffer8, &IID_IDirectSoundCaptureBuffer, (LPVOID *)&stream->pDirectSoundInputBuffer );
1565
1566 if( hr == DS_OK )
1567 hr = IUnknown_QueryInterface( pRenderBuffer8, &IID_IDirectSoundBuffer, (LPVOID *)&stream->pDirectSoundOutputBuffer );
1568
1569 /* release the ds 8 interfaces, we don't need them */
1570 IUnknown_Release( pCaptureBuffer8 );
1571 IUnknown_Release( pRenderBuffer8 );
1572
1573 if( !stream->pDirectSoundInputBuffer || !stream->pDirectSoundOutputBuffer ){
1574 /* couldn't get pre ds 8 interfaces for some reason. clean up. */
1575 if( stream->pDirectSoundInputBuffer )
1576 {
1577 IUnknown_Release( stream->pDirectSoundInputBuffer );
1578 stream->pDirectSoundInputBuffer = NULL;
1579 }
1580
1581 if( stream->pDirectSoundOutputBuffer )
1582 {
1583 IUnknown_Release( stream->pDirectSoundOutputBuffer );
1584 stream->pDirectSoundOutputBuffer = NULL;
1585 }
1586
1587 IUnknown_Release( stream->pDirectSoundFullDuplex8 );
1588 stream->pDirectSoundFullDuplex8 = NULL;
1589 }
1590 }
1591 else
1592 {
1593 PA_DEBUG(("DirectSoundFullDuplexCreate failed. hr=%d\n", hr));
1594 }
1595
1596 return hr;
1597 }
1598 #endif /* PAWIN_USE_DIRECTSOUNDFULLDUPLEXCREATE */
1599
1600
InitInputBuffer(PaWinDsStream * stream,PaWinDsDeviceInfo * device,PaSampleFormat sampleFormat,unsigned long nFrameRate,WORD nChannels,int bytesPerBuffer,PaWinWaveFormatChannelMask channelMask)1601 static HRESULT InitInputBuffer( PaWinDsStream *stream,
1602 PaWinDsDeviceInfo *device,
1603 PaSampleFormat sampleFormat,
1604 unsigned long nFrameRate,
1605 WORD nChannels,
1606 int bytesPerBuffer,
1607 PaWinWaveFormatChannelMask channelMask )
1608 {
1609 DSCBUFFERDESC captureDesc;
1610 PaWinWaveFormat waveFormat;
1611 HRESULT result;
1612
1613 if( (result = paWinDsDSoundEntryPoints.DirectSoundCaptureCreate(
1614 device->lpGUID, &stream->pDirectSoundCapture, NULL) ) != DS_OK ){
1615 ERR_RPT(("PortAudio: DirectSoundCaptureCreate() failed!\n"));
1616 return result;
1617 }
1618
1619 // Setup the secondary buffer description
1620 ZeroMemory(&captureDesc, sizeof(DSCBUFFERDESC));
1621 captureDesc.dwSize = sizeof(DSCBUFFERDESC);
1622 captureDesc.dwFlags = 0;
1623 captureDesc.dwBufferBytes = bytesPerBuffer;
1624 captureDesc.lpwfxFormat = (WAVEFORMATEX*)&waveFormat;
1625
1626 // Create the capture buffer
1627
1628 // first try WAVEFORMATEXTENSIBLE. if this fails, fall back to WAVEFORMATEX
1629 PaWin_InitializeWaveFormatExtensible( &waveFormat, nChannels,
1630 sampleFormat, PaWin_SampleFormatToLinearWaveFormatTag( sampleFormat ),
1631 nFrameRate, channelMask );
1632
1633 if( IDirectSoundCapture_CreateCaptureBuffer( stream->pDirectSoundCapture,
1634 &captureDesc, &stream->pDirectSoundInputBuffer, NULL) != DS_OK )
1635 {
1636 PaWin_InitializeWaveFormatEx( &waveFormat, nChannels, sampleFormat,
1637 PaWin_SampleFormatToLinearWaveFormatTag( sampleFormat ), nFrameRate );
1638
1639 if ((result = IDirectSoundCapture_CreateCaptureBuffer( stream->pDirectSoundCapture,
1640 &captureDesc, &stream->pDirectSoundInputBuffer, NULL)) != DS_OK) return result;
1641 }
1642
1643 stream->readOffset = 0; // reset last read position to start of buffer
1644 return DS_OK;
1645 }
1646
1647
InitOutputBuffer(PaWinDsStream * stream,PaWinDsDeviceInfo * device,PaSampleFormat sampleFormat,unsigned long nFrameRate,WORD nChannels,int bytesPerBuffer,PaWinWaveFormatChannelMask channelMask)1648 static HRESULT InitOutputBuffer( PaWinDsStream *stream, PaWinDsDeviceInfo *device,
1649 PaSampleFormat sampleFormat, unsigned long nFrameRate,
1650 WORD nChannels, int bytesPerBuffer,
1651 PaWinWaveFormatChannelMask channelMask )
1652 {
1653 HRESULT result;
1654 HWND hWnd;
1655 HRESULT hr;
1656 PaWinWaveFormat waveFormat;
1657 DSBUFFERDESC primaryDesc;
1658 DSBUFFERDESC secondaryDesc;
1659
1660 if( (hr = paWinDsDSoundEntryPoints.DirectSoundCreate(
1661 device->lpGUID, &stream->pDirectSound, NULL )) != DS_OK ){
1662 ERR_RPT(("PortAudio: DirectSoundCreate() failed!\n"));
1663 return hr;
1664 }
1665
1666 // We were using getForegroundWindow() but sometimes the ForegroundWindow may not be the
1667 // applications's window. Also if that window is closed before the Buffer is closed
1668 // then DirectSound can crash. (Thanks for Scott Patterson for reporting this.)
1669 // So we will use GetDesktopWindow() which was suggested by Miller Puckette.
1670 // hWnd = GetForegroundWindow();
1671 //
1672 // FIXME: The example code I have on the net creates a hidden window that
1673 // is managed by our code - I think we should do that - one hidden
1674 // window for the whole of Pa_DS
1675 //
1676 hWnd = GetDesktopWindow();
1677
1678 // Set cooperative level to DSSCL_EXCLUSIVE so that we can get 16 bit output, 44.1 KHz.
1679 // exclusive also prevents unexpected sounds from other apps during a performance.
1680 if ((hr = IDirectSound_SetCooperativeLevel( stream->pDirectSound,
1681 hWnd, DSSCL_EXCLUSIVE)) != DS_OK)
1682 {
1683 return hr;
1684 }
1685
1686 // -----------------------------------------------------------------------
1687 // Create primary buffer and set format just so we can specify our custom format.
1688 // Otherwise we would be stuck with the default which might be 8 bit or 22050 Hz.
1689 // Setup the primary buffer description
1690 ZeroMemory(&primaryDesc, sizeof(DSBUFFERDESC));
1691 primaryDesc.dwSize = sizeof(DSBUFFERDESC);
1692 primaryDesc.dwFlags = DSBCAPS_PRIMARYBUFFER; // all panning, mixing, etc done by synth
1693 primaryDesc.dwBufferBytes = 0;
1694 primaryDesc.lpwfxFormat = NULL;
1695 // Create the buffer
1696 if ((result = IDirectSound_CreateSoundBuffer( stream->pDirectSound,
1697 &primaryDesc, &stream->pDirectSoundPrimaryBuffer, NULL)) != DS_OK)
1698 goto error;
1699
1700 // Set the primary buffer's format
1701
1702 // first try WAVEFORMATEXTENSIBLE. if this fails, fall back to WAVEFORMATEX
1703 PaWin_InitializeWaveFormatExtensible( &waveFormat, nChannels,
1704 sampleFormat, PaWin_SampleFormatToLinearWaveFormatTag( sampleFormat ),
1705 nFrameRate, channelMask );
1706
1707 if( IDirectSoundBuffer_SetFormat( stream->pDirectSoundPrimaryBuffer, (WAVEFORMATEX*)&waveFormat) != DS_OK )
1708 {
1709 PaWin_InitializeWaveFormatEx( &waveFormat, nChannels, sampleFormat,
1710 PaWin_SampleFormatToLinearWaveFormatTag( sampleFormat ), nFrameRate );
1711
1712 if((result = IDirectSoundBuffer_SetFormat( stream->pDirectSoundPrimaryBuffer, (WAVEFORMATEX*)&waveFormat)) != DS_OK)
1713 goto error;
1714 }
1715
1716 // ----------------------------------------------------------------------
1717 // Setup the secondary buffer description
1718 ZeroMemory(&secondaryDesc, sizeof(DSBUFFERDESC));
1719 secondaryDesc.dwSize = sizeof(DSBUFFERDESC);
1720 secondaryDesc.dwFlags = DSBCAPS_GLOBALFOCUS | DSBCAPS_GETCURRENTPOSITION2;
1721 secondaryDesc.dwBufferBytes = bytesPerBuffer;
1722 secondaryDesc.lpwfxFormat = (WAVEFORMATEX*)&waveFormat; /* waveFormat contains whatever format was negotiated for the primary buffer above */
1723 // Create the secondary buffer
1724 if ((result = IDirectSound_CreateSoundBuffer( stream->pDirectSound,
1725 &secondaryDesc, &stream->pDirectSoundOutputBuffer, NULL)) != DS_OK)
1726 goto error;
1727
1728 return DS_OK;
1729
1730 error:
1731
1732 if( stream->pDirectSoundPrimaryBuffer )
1733 {
1734 IDirectSoundBuffer_Release( stream->pDirectSoundPrimaryBuffer );
1735 stream->pDirectSoundPrimaryBuffer = NULL;
1736 }
1737
1738 return result;
1739 }
1740
1741
CalculateBufferSettings(unsigned long * hostBufferSizeFrames,unsigned long * pollingPeriodFrames,int isFullDuplex,unsigned long suggestedInputLatencyFrames,unsigned long suggestedOutputLatencyFrames,double sampleRate,unsigned long userFramesPerBuffer)1742 static void CalculateBufferSettings( unsigned long *hostBufferSizeFrames,
1743 unsigned long *pollingPeriodFrames,
1744 int isFullDuplex,
1745 unsigned long suggestedInputLatencyFrames,
1746 unsigned long suggestedOutputLatencyFrames,
1747 double sampleRate, unsigned long userFramesPerBuffer )
1748 {
1749 unsigned long minimumPollingPeriodFrames = (unsigned long)(sampleRate * PA_DS_MINIMUM_POLLING_PERIOD_SECONDS);
1750 unsigned long maximumPollingPeriodFrames = (unsigned long)(sampleRate * PA_DS_MAXIMUM_POLLING_PERIOD_SECONDS);
1751 unsigned long pollingJitterFrames = (unsigned long)(sampleRate * PA_DS_POLLING_JITTER_SECONDS);
1752
1753 if( userFramesPerBuffer == paFramesPerBufferUnspecified )
1754 {
1755 unsigned long targetBufferingLatencyFrames = max( suggestedInputLatencyFrames, suggestedOutputLatencyFrames );
1756
1757 *pollingPeriodFrames = targetBufferingLatencyFrames / 4;
1758 if( *pollingPeriodFrames < minimumPollingPeriodFrames )
1759 {
1760 *pollingPeriodFrames = minimumPollingPeriodFrames;
1761 }
1762 else if( *pollingPeriodFrames > maximumPollingPeriodFrames )
1763 {
1764 *pollingPeriodFrames = maximumPollingPeriodFrames;
1765 }
1766
1767 *hostBufferSizeFrames = *pollingPeriodFrames
1768 + max( *pollingPeriodFrames + pollingJitterFrames, targetBufferingLatencyFrames);
1769 }
1770 else
1771 {
1772 unsigned long targetBufferingLatencyFrames = suggestedInputLatencyFrames;
1773 if( isFullDuplex )
1774 {
1775 /* In full duplex streams we know that the buffer adapter adds userFramesPerBuffer
1776 extra fixed latency. so we subtract it here as a fixed latency before computing
1777 the buffer size. being careful not to produce an unrepresentable negative result.
1778
1779 Note: this only works as expected if output latency is greater than input latency.
1780 Otherwise we use input latency anyway since we do max(in,out).
1781 */
1782
1783 if( userFramesPerBuffer < suggestedOutputLatencyFrames )
1784 {
1785 unsigned long adjustedSuggestedOutputLatencyFrames =
1786 suggestedOutputLatencyFrames - userFramesPerBuffer;
1787
1788 /* maximum of input and adjusted output suggested latency */
1789 if( adjustedSuggestedOutputLatencyFrames > targetBufferingLatencyFrames )
1790 targetBufferingLatencyFrames = adjustedSuggestedOutputLatencyFrames;
1791 }
1792 }
1793 else
1794 {
1795 /* maximum of input and output suggested latency */
1796 if( suggestedOutputLatencyFrames > suggestedInputLatencyFrames )
1797 targetBufferingLatencyFrames = suggestedOutputLatencyFrames;
1798 }
1799
1800 *hostBufferSizeFrames = userFramesPerBuffer
1801 + max( userFramesPerBuffer + pollingJitterFrames, targetBufferingLatencyFrames);
1802
1803 *pollingPeriodFrames = max( max(1, userFramesPerBuffer / 4), targetBufferingLatencyFrames / 16 );
1804
1805 if( *pollingPeriodFrames > maximumPollingPeriodFrames )
1806 {
1807 *pollingPeriodFrames = maximumPollingPeriodFrames;
1808 }
1809 }
1810 }
1811
1812
CalculatePollingPeriodFrames(unsigned long hostBufferSizeFrames,unsigned long * pollingPeriodFrames,double sampleRate,unsigned long userFramesPerBuffer)1813 static void CalculatePollingPeriodFrames( unsigned long hostBufferSizeFrames,
1814 unsigned long *pollingPeriodFrames,
1815 double sampleRate, unsigned long userFramesPerBuffer )
1816 {
1817 unsigned long minimumPollingPeriodFrames = (unsigned long)(sampleRate * PA_DS_MINIMUM_POLLING_PERIOD_SECONDS);
1818 unsigned long maximumPollingPeriodFrames = (unsigned long)(sampleRate * PA_DS_MAXIMUM_POLLING_PERIOD_SECONDS);
1819 unsigned long pollingJitterFrames = (unsigned long)(sampleRate * PA_DS_POLLING_JITTER_SECONDS);
1820
1821 *pollingPeriodFrames = max( max(1, userFramesPerBuffer / 4), hostBufferSizeFrames / 16 );
1822
1823 if( *pollingPeriodFrames > maximumPollingPeriodFrames )
1824 {
1825 *pollingPeriodFrames = maximumPollingPeriodFrames;
1826 }
1827 }
1828
1829
SetStreamInfoLatencies(PaWinDsStream * stream,unsigned long userFramesPerBuffer,unsigned long pollingPeriodFrames,double sampleRate)1830 static void SetStreamInfoLatencies( PaWinDsStream *stream,
1831 unsigned long userFramesPerBuffer,
1832 unsigned long pollingPeriodFrames,
1833 double sampleRate )
1834 {
1835 /* compute the stream info actual latencies based on framesPerBuffer, polling period, hostBufferSizeFrames,
1836 and the configuration of the buffer processor */
1837
1838 unsigned long effectiveFramesPerBuffer = (userFramesPerBuffer == paFramesPerBufferUnspecified)
1839 ? pollingPeriodFrames
1840 : userFramesPerBuffer;
1841
1842 if( stream->bufferProcessor.inputChannelCount > 0 )
1843 {
1844 /* stream info input latency is the minimum buffering latency
1845 (unlike suggested and default which are *maximums*) */
1846 stream->streamRepresentation.streamInfo.inputLatency =
1847 (double)(PaUtil_GetBufferProcessorInputLatencyFrames(&stream->bufferProcessor)
1848 + effectiveFramesPerBuffer) / sampleRate;
1849 }
1850 else
1851 {
1852 stream->streamRepresentation.streamInfo.inputLatency = 0;
1853 }
1854
1855 if( stream->bufferProcessor.outputChannelCount > 0 )
1856 {
1857 stream->streamRepresentation.streamInfo.outputLatency =
1858 (double)(PaUtil_GetBufferProcessorOutputLatencyFrames(&stream->bufferProcessor)
1859 + (stream->hostBufferSizeFrames - effectiveFramesPerBuffer)) / sampleRate;
1860 }
1861 else
1862 {
1863 stream->streamRepresentation.streamInfo.outputLatency = 0;
1864 }
1865 }
1866
1867
1868 /***********************************************************************************/
1869 /* see pa_hostapi.h for a list of validity guarantees made about OpenStream parameters */
1870
OpenStream(struct PaUtilHostApiRepresentation * hostApi,PaStream ** s,const PaStreamParameters * inputParameters,const PaStreamParameters * outputParameters,double sampleRate,unsigned long framesPerBuffer,PaStreamFlags streamFlags,PaStreamCallback * streamCallback,void * userData)1871 static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
1872 PaStream** s,
1873 const PaStreamParameters *inputParameters,
1874 const PaStreamParameters *outputParameters,
1875 double sampleRate,
1876 unsigned long framesPerBuffer,
1877 PaStreamFlags streamFlags,
1878 PaStreamCallback *streamCallback,
1879 void *userData )
1880 {
1881 PaError result = paNoError;
1882 PaWinDsHostApiRepresentation *winDsHostApi = (PaWinDsHostApiRepresentation*)hostApi;
1883 PaWinDsStream *stream = 0;
1884 int bufferProcessorIsInitialized = 0;
1885 int streamRepresentationIsInitialized = 0;
1886 PaWinDsDeviceInfo *inputWinDsDeviceInfo, *outputWinDsDeviceInfo;
1887 PaDeviceInfo *inputDeviceInfo, *outputDeviceInfo;
1888 int inputChannelCount, outputChannelCount;
1889 PaSampleFormat inputSampleFormat, outputSampleFormat;
1890 PaSampleFormat hostInputSampleFormat, hostOutputSampleFormat;
1891 int userRequestedHostInputBufferSizeFrames = 0;
1892 int userRequestedHostOutputBufferSizeFrames = 0;
1893 unsigned long suggestedInputLatencyFrames, suggestedOutputLatencyFrames;
1894 PaWinDirectSoundStreamInfo *inputStreamInfo, *outputStreamInfo;
1895 PaWinWaveFormatChannelMask inputChannelMask, outputChannelMask;
1896 unsigned long pollingPeriodFrames = 0;
1897
1898 if( inputParameters )
1899 {
1900 inputWinDsDeviceInfo = (PaWinDsDeviceInfo*) hostApi->deviceInfos[ inputParameters->device ];
1901 inputDeviceInfo = &inputWinDsDeviceInfo->inheritedDeviceInfo;
1902
1903 inputChannelCount = inputParameters->channelCount;
1904 inputSampleFormat = inputParameters->sampleFormat;
1905 suggestedInputLatencyFrames = (unsigned long)(inputParameters->suggestedLatency * sampleRate);
1906
1907 /* IDEA: the following 3 checks could be performed by default by pa_front
1908 unless some flag indicated otherwise */
1909
1910 /* unless alternate device specification is supported, reject the use of
1911 paUseHostApiSpecificDeviceSpecification */
1912 if( inputParameters->device == paUseHostApiSpecificDeviceSpecification )
1913 return paInvalidDevice;
1914
1915 /* check that input device can support inputChannelCount */
1916 if( inputWinDsDeviceInfo->deviceInputChannelCountIsKnown
1917 && inputChannelCount > inputDeviceInfo->maxInputChannels )
1918 return paInvalidChannelCount;
1919
1920 /* validate hostApiSpecificStreamInfo */
1921 inputStreamInfo = (PaWinDirectSoundStreamInfo*)inputParameters->hostApiSpecificStreamInfo;
1922 result = ValidateWinDirectSoundSpecificStreamInfo( inputParameters, inputStreamInfo );
1923 if( result != paNoError ) return result;
1924
1925 if( inputStreamInfo && inputStreamInfo->flags & paWinDirectSoundUseLowLevelLatencyParameters )
1926 userRequestedHostInputBufferSizeFrames = inputStreamInfo->framesPerBuffer;
1927
1928 if( inputStreamInfo && inputStreamInfo->flags & paWinDirectSoundUseChannelMask )
1929 inputChannelMask = inputStreamInfo->channelMask;
1930 else
1931 inputChannelMask = PaWin_DefaultChannelMask( inputChannelCount );
1932 }
1933 else
1934 {
1935 inputChannelCount = 0;
1936 inputSampleFormat = 0;
1937 suggestedInputLatencyFrames = 0;
1938 }
1939
1940
1941 if( outputParameters )
1942 {
1943 outputWinDsDeviceInfo = (PaWinDsDeviceInfo*) hostApi->deviceInfos[ outputParameters->device ];
1944 outputDeviceInfo = &outputWinDsDeviceInfo->inheritedDeviceInfo;
1945
1946 outputChannelCount = outputParameters->channelCount;
1947 outputSampleFormat = outputParameters->sampleFormat;
1948 suggestedOutputLatencyFrames = (unsigned long)(outputParameters->suggestedLatency * sampleRate);
1949
1950 /* unless alternate device specification is supported, reject the use of
1951 paUseHostApiSpecificDeviceSpecification */
1952 if( outputParameters->device == paUseHostApiSpecificDeviceSpecification )
1953 return paInvalidDevice;
1954
1955 /* check that output device can support outputChannelCount */
1956 if( outputWinDsDeviceInfo->deviceOutputChannelCountIsKnown
1957 && outputChannelCount > outputDeviceInfo->maxOutputChannels )
1958 return paInvalidChannelCount;
1959
1960 /* validate hostApiSpecificStreamInfo */
1961 outputStreamInfo = (PaWinDirectSoundStreamInfo*)outputParameters->hostApiSpecificStreamInfo;
1962 result = ValidateWinDirectSoundSpecificStreamInfo( outputParameters, outputStreamInfo );
1963 if( result != paNoError ) return result;
1964
1965 if( outputStreamInfo && outputStreamInfo->flags & paWinDirectSoundUseLowLevelLatencyParameters )
1966 userRequestedHostOutputBufferSizeFrames = outputStreamInfo->framesPerBuffer;
1967
1968 if( outputStreamInfo && outputStreamInfo->flags & paWinDirectSoundUseChannelMask )
1969 outputChannelMask = outputStreamInfo->channelMask;
1970 else
1971 outputChannelMask = PaWin_DefaultChannelMask( outputChannelCount );
1972 }
1973 else
1974 {
1975 outputChannelCount = 0;
1976 outputSampleFormat = 0;
1977 suggestedOutputLatencyFrames = 0;
1978 }
1979
1980 /*
1981 If low level host buffer size is specified for both input and output
1982 the current code requires the sizes to match.
1983 */
1984
1985 if( (userRequestedHostInputBufferSizeFrames > 0 && userRequestedHostOutputBufferSizeFrames > 0)
1986 && userRequestedHostInputBufferSizeFrames != userRequestedHostOutputBufferSizeFrames )
1987 return paIncompatibleHostApiSpecificStreamInfo;
1988
1989
1990
1991 /*
1992 IMPLEMENT ME:
1993
1994 ( the following two checks are taken care of by PaUtil_InitializeBufferProcessor() )
1995
1996 - check that input device can support inputSampleFormat, or that
1997 we have the capability to convert from outputSampleFormat to
1998 a native format
1999
2000 - check that output device can support outputSampleFormat, or that
2001 we have the capability to convert from outputSampleFormat to
2002 a native format
2003
2004 - if a full duplex stream is requested, check that the combination
2005 of input and output parameters is supported
2006
2007 - check that the device supports sampleRate
2008
2009 - alter sampleRate to a close allowable rate if possible / necessary
2010
2011 - validate suggestedInputLatency and suggestedOutputLatency parameters,
2012 use default values where necessary
2013 */
2014
2015
2016 /* validate platform specific flags */
2017 if( (streamFlags & paPlatformSpecificFlags) != 0 )
2018 return paInvalidFlag; /* unexpected platform specific flag */
2019
2020
2021 stream = (PaWinDsStream*)PaUtil_AllocateMemory( sizeof(PaWinDsStream) );
2022 if( !stream )
2023 {
2024 result = paInsufficientMemory;
2025 goto error;
2026 }
2027
2028 memset( stream, 0, sizeof(PaWinDsStream) ); /* initialize all stream variables to 0 */
2029
2030 if( streamCallback )
2031 {
2032 PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation,
2033 &winDsHostApi->callbackStreamInterface, streamCallback, userData );
2034 }
2035 else
2036 {
2037 PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation,
2038 &winDsHostApi->blockingStreamInterface, streamCallback, userData );
2039 }
2040
2041 streamRepresentationIsInitialized = 1;
2042
2043 stream->streamFlags = streamFlags;
2044
2045 PaUtil_InitializeCpuLoadMeasurer( &stream->cpuLoadMeasurer, sampleRate );
2046
2047
2048 if( inputParameters )
2049 {
2050 /* IMPLEMENT ME - establish which host formats are available */
2051 PaSampleFormat nativeInputFormats = paInt16;
2052 /* PaSampleFormat nativeFormats = paUInt8 | paInt16 | paInt24 | paInt32 | paFloat32; */
2053
2054 hostInputSampleFormat =
2055 PaUtil_SelectClosestAvailableFormat( nativeInputFormats, inputParameters->sampleFormat );
2056 }
2057 else
2058 {
2059 hostInputSampleFormat = 0;
2060 }
2061
2062 if( outputParameters )
2063 {
2064 /* IMPLEMENT ME - establish which host formats are available */
2065 PaSampleFormat nativeOutputFormats = paInt16;
2066 /* PaSampleFormat nativeOutputFormats = paUInt8 | paInt16 | paInt24 | paInt32 | paFloat32; */
2067
2068 hostOutputSampleFormat =
2069 PaUtil_SelectClosestAvailableFormat( nativeOutputFormats, outputParameters->sampleFormat );
2070 }
2071 else
2072 {
2073 hostOutputSampleFormat = 0;
2074 }
2075
2076 result = PaUtil_InitializeBufferProcessor( &stream->bufferProcessor,
2077 inputChannelCount, inputSampleFormat, hostInputSampleFormat,
2078 outputChannelCount, outputSampleFormat, hostOutputSampleFormat,
2079 sampleRate, streamFlags, framesPerBuffer,
2080 0, /* ignored in paUtilVariableHostBufferSizePartialUsageAllowed mode. */
2081 /* This next mode is required because DS can split the host buffer when it wraps around. */
2082 paUtilVariableHostBufferSizePartialUsageAllowed,
2083 streamCallback, userData );
2084 if( result != paNoError )
2085 goto error;
2086
2087 bufferProcessorIsInitialized = 1;
2088
2089
2090 /* DirectSound specific initialization */
2091 {
2092 HRESULT hr;
2093 unsigned long integerSampleRate = (unsigned long) (sampleRate + 0.5);
2094
2095 stream->processingCompleted = CreateEvent( NULL, /* bManualReset = */ TRUE, /* bInitialState = */ FALSE, NULL );
2096 if( stream->processingCompleted == NULL )
2097 {
2098 result = paInsufficientMemory;
2099 goto error;
2100 }
2101
2102 #ifdef PA_WIN_DS_USE_WMME_TIMER
2103 stream->timerID = 0;
2104 #endif
2105
2106 #ifdef PA_WIN_DS_USE_WAITABLE_TIMER_OBJECT
2107 stream->waitableTimer = (HANDLE)CreateWaitableTimer( 0, FALSE, NULL );
2108 if( stream->waitableTimer == NULL )
2109 {
2110 result = paUnanticipatedHostError;
2111 PA_DS_SET_LAST_DIRECTSOUND_ERROR( GetLastError() );
2112 goto error;
2113 }
2114 #endif
2115
2116 #ifndef PA_WIN_DS_USE_WMME_TIMER
2117 stream->processingThreadCompleted = CreateEvent( NULL, /* bManualReset = */ TRUE, /* bInitialState = */ FALSE, NULL );
2118 if( stream->processingThreadCompleted == NULL )
2119 {
2120 result = paUnanticipatedHostError;
2121 PA_DS_SET_LAST_DIRECTSOUND_ERROR( GetLastError() );
2122 goto error;
2123 }
2124 #endif
2125
2126 /* set up i/o parameters */
2127
2128 if( userRequestedHostInputBufferSizeFrames > 0 || userRequestedHostOutputBufferSizeFrames > 0 )
2129 {
2130 /* use low level parameters */
2131
2132 /* since we use the same host buffer size for input and output
2133 we choose the highest user specified value.
2134 */
2135 stream->hostBufferSizeFrames = max( userRequestedHostInputBufferSizeFrames, userRequestedHostOutputBufferSizeFrames );
2136
2137 CalculatePollingPeriodFrames(
2138 stream->hostBufferSizeFrames, &pollingPeriodFrames,
2139 sampleRate, framesPerBuffer );
2140 }
2141 else
2142 {
2143 CalculateBufferSettings( (unsigned long*)&stream->hostBufferSizeFrames, &pollingPeriodFrames,
2144 /* isFullDuplex = */ (inputParameters && outputParameters),
2145 suggestedInputLatencyFrames,
2146 suggestedOutputLatencyFrames,
2147 sampleRate, framesPerBuffer );
2148 }
2149
2150 stream->pollingPeriodSeconds = pollingPeriodFrames / sampleRate;
2151
2152 DBUG(("DirectSound host buffer size frames: %d, polling period seconds: %f, @ sr: %f\n",
2153 stream->hostBufferSizeFrames, stream->pollingPeriodSeconds, sampleRate ));
2154
2155
2156 /* ------------------ OUTPUT */
2157 if( outputParameters )
2158 {
2159 LARGE_INTEGER counterFrequency;
2160
2161 /*
2162 PaDeviceInfo *deviceInfo = hostApi->deviceInfos[ outputParameters->device ];
2163 DBUG(("PaHost_OpenStream: deviceID = 0x%x\n", outputParameters->device));
2164 */
2165
2166 int sampleSizeBytes = Pa_GetSampleSize(hostOutputSampleFormat);
2167 stream->outputFrameSizeBytes = outputParameters->channelCount * sampleSizeBytes;
2168
2169 stream->outputBufferSizeBytes = stream->hostBufferSizeFrames * stream->outputFrameSizeBytes;
2170 if( stream->outputBufferSizeBytes < DSBSIZE_MIN )
2171 {
2172 result = paBufferTooSmall;
2173 goto error;
2174 }
2175 else if( stream->outputBufferSizeBytes > DSBSIZE_MAX )
2176 {
2177 result = paBufferTooBig;
2178 goto error;
2179 }
2180
2181 /* Calculate value used in latency calculation to avoid real-time divides. */
2182 stream->secondsPerHostByte = 1.0 /
2183 (stream->bufferProcessor.bytesPerHostOutputSample *
2184 outputChannelCount * sampleRate);
2185
2186 stream->outputIsRunning = FALSE;
2187 stream->outputUnderflowCount = 0;
2188
2189 /* perfCounterTicksPerBuffer is used by QueryOutputSpace for overflow detection */
2190 if( QueryPerformanceFrequency( &counterFrequency ) )
2191 {
2192 stream->perfCounterTicksPerBuffer.QuadPart = (counterFrequency.QuadPart * stream->hostBufferSizeFrames) / integerSampleRate;
2193 }
2194 else
2195 {
2196 stream->perfCounterTicksPerBuffer.QuadPart = 0;
2197 }
2198 }
2199
2200 /* ------------------ INPUT */
2201 if( inputParameters )
2202 {
2203 /*
2204 PaDeviceInfo *deviceInfo = hostApi->deviceInfos[ inputParameters->device ];
2205 DBUG(("PaHost_OpenStream: deviceID = 0x%x\n", inputParameters->device));
2206 */
2207
2208 int sampleSizeBytes = Pa_GetSampleSize(hostInputSampleFormat);
2209 stream->inputFrameSizeBytes = inputParameters->channelCount * sampleSizeBytes;
2210
2211 stream->inputBufferSizeBytes = stream->hostBufferSizeFrames * stream->inputFrameSizeBytes;
2212 if( stream->inputBufferSizeBytes < DSBSIZE_MIN )
2213 {
2214 result = paBufferTooSmall;
2215 goto error;
2216 }
2217 else if( stream->inputBufferSizeBytes > DSBSIZE_MAX )
2218 {
2219 result = paBufferTooBig;
2220 goto error;
2221 }
2222 }
2223
2224 /* open/create the DirectSound buffers */
2225
2226 /* interface ptrs should be zeroed when stream is zeroed. */
2227 assert( stream->pDirectSoundCapture == NULL );
2228 assert( stream->pDirectSoundInputBuffer == NULL );
2229 assert( stream->pDirectSound == NULL );
2230 assert( stream->pDirectSoundPrimaryBuffer == NULL );
2231 assert( stream->pDirectSoundOutputBuffer == NULL );
2232
2233
2234 if( inputParameters && outputParameters )
2235 {
2236 #ifdef PAWIN_USE_DIRECTSOUNDFULLDUPLEXCREATE
2237 /* try to use the full-duplex DX8 API to create the buffers.
2238 if that fails we fall back to the half-duplex API below */
2239
2240 hr = InitFullDuplexInputOutputBuffers( stream,
2241 (PaWinDsDeviceInfo*)hostApi->deviceInfos[inputParameters->device],
2242 hostInputSampleFormat,
2243 (WORD)inputParameters->channelCount, stream->inputBufferSizeBytes,
2244 inputChannelMask,
2245 (PaWinDsDeviceInfo*)hostApi->deviceInfos[outputParameters->device],
2246 hostOutputSampleFormat,
2247 (WORD)outputParameters->channelCount, stream->outputBufferSizeBytes,
2248 outputChannelMask,
2249 integerSampleRate
2250 );
2251 DBUG(("InitFullDuplexInputOutputBuffers() returns %x\n", hr));
2252 /* ignore any error returned by InitFullDuplexInputOutputBuffers.
2253 we retry opening the buffers below */
2254 #endif /* PAWIN_USE_DIRECTSOUNDFULLDUPLEXCREATE */
2255 }
2256
2257 /* create half duplex buffers. also used for full-duplex streams which didn't
2258 succeed when using the full duplex API. that could happen because
2259 DX8 or greater isnt installed, the i/o devices aren't the same
2260 physical device. etc.
2261 */
2262
2263 if( outputParameters && !stream->pDirectSoundOutputBuffer )
2264 {
2265 hr = InitOutputBuffer( stream,
2266 (PaWinDsDeviceInfo*)hostApi->deviceInfos[outputParameters->device],
2267 hostOutputSampleFormat,
2268 integerSampleRate,
2269 (WORD)outputParameters->channelCount, stream->outputBufferSizeBytes,
2270 outputChannelMask );
2271 DBUG(("InitOutputBuffer() returns %x\n", hr));
2272 if( hr != DS_OK )
2273 {
2274 result = paUnanticipatedHostError;
2275 PA_DS_SET_LAST_DIRECTSOUND_ERROR( hr );
2276 goto error;
2277 }
2278 }
2279
2280 if( inputParameters && !stream->pDirectSoundInputBuffer )
2281 {
2282 hr = InitInputBuffer( stream,
2283 (PaWinDsDeviceInfo*)hostApi->deviceInfos[inputParameters->device],
2284 hostInputSampleFormat,
2285 integerSampleRate,
2286 (WORD)inputParameters->channelCount, stream->inputBufferSizeBytes,
2287 inputChannelMask );
2288 DBUG(("InitInputBuffer() returns %x\n", hr));
2289 if( hr != DS_OK )
2290 {
2291 ERR_RPT(("PortAudio: DSW_InitInputBuffer() returns %x\n", hr));
2292 result = paUnanticipatedHostError;
2293 PA_DS_SET_LAST_DIRECTSOUND_ERROR( hr );
2294 goto error;
2295 }
2296 }
2297 }
2298
2299 SetStreamInfoLatencies( stream, framesPerBuffer, pollingPeriodFrames, sampleRate );
2300
2301 stream->streamRepresentation.streamInfo.sampleRate = sampleRate;
2302
2303 *s = (PaStream*)stream;
2304
2305 return result;
2306
2307 error:
2308 if( stream )
2309 {
2310 if( stream->processingCompleted != NULL )
2311 CloseHandle( stream->processingCompleted );
2312
2313 #ifdef PA_WIN_DS_USE_WAITABLE_TIMER_OBJECT
2314 if( stream->waitableTimer != NULL )
2315 CloseHandle( stream->waitableTimer );
2316 #endif
2317
2318 #ifndef PA_WIN_DS_USE_WMME_TIMER
2319 if( stream->processingThreadCompleted != NULL )
2320 CloseHandle( stream->processingThreadCompleted );
2321 #endif
2322
2323 if( stream->pDirectSoundOutputBuffer )
2324 {
2325 IDirectSoundBuffer_Stop( stream->pDirectSoundOutputBuffer );
2326 IDirectSoundBuffer_Release( stream->pDirectSoundOutputBuffer );
2327 stream->pDirectSoundOutputBuffer = NULL;
2328 }
2329
2330 if( stream->pDirectSoundPrimaryBuffer )
2331 {
2332 IDirectSoundBuffer_Release( stream->pDirectSoundPrimaryBuffer );
2333 stream->pDirectSoundPrimaryBuffer = NULL;
2334 }
2335
2336 if( stream->pDirectSoundInputBuffer )
2337 {
2338 IDirectSoundCaptureBuffer_Stop( stream->pDirectSoundInputBuffer );
2339 IDirectSoundCaptureBuffer_Release( stream->pDirectSoundInputBuffer );
2340 stream->pDirectSoundInputBuffer = NULL;
2341 }
2342
2343 if( stream->pDirectSoundCapture )
2344 {
2345 IDirectSoundCapture_Release( stream->pDirectSoundCapture );
2346 stream->pDirectSoundCapture = NULL;
2347 }
2348
2349 if( stream->pDirectSound )
2350 {
2351 IDirectSound_Release( stream->pDirectSound );
2352 stream->pDirectSound = NULL;
2353 }
2354
2355 #ifdef PAWIN_USE_DIRECTSOUNDFULLDUPLEXCREATE
2356 if( stream->pDirectSoundFullDuplex8 )
2357 {
2358 IDirectSoundFullDuplex_Release( stream->pDirectSoundFullDuplex8 );
2359 stream->pDirectSoundFullDuplex8 = NULL;
2360 }
2361 #endif
2362 if( bufferProcessorIsInitialized )
2363 PaUtil_TerminateBufferProcessor( &stream->bufferProcessor );
2364
2365 if( streamRepresentationIsInitialized )
2366 PaUtil_TerminateStreamRepresentation( &stream->streamRepresentation );
2367
2368 PaUtil_FreeMemory( stream );
2369 }
2370
2371 return result;
2372 }
2373
2374
2375 /************************************************************************************
2376 * Determine how much space can be safely written to in DS buffer.
2377 * Detect underflows and overflows.
2378 * Does not allow writing into safety gap maintained by DirectSound.
2379 */
QueryOutputSpace(PaWinDsStream * stream,long * bytesEmpty)2380 static HRESULT QueryOutputSpace( PaWinDsStream *stream, long *bytesEmpty )
2381 {
2382 HRESULT hr;
2383 DWORD playCursor;
2384 DWORD writeCursor;
2385 long numBytesEmpty;
2386 long playWriteGap;
2387 // Query to see how much room is in buffer.
2388 hr = IDirectSoundBuffer_GetCurrentPosition( stream->pDirectSoundOutputBuffer,
2389 &playCursor, &writeCursor );
2390 if( hr != DS_OK )
2391 {
2392 return hr;
2393 }
2394
2395 // Determine size of gap between playIndex and WriteIndex that we cannot write into.
2396 playWriteGap = writeCursor - playCursor;
2397 if( playWriteGap < 0 ) playWriteGap += stream->outputBufferSizeBytes; // unwrap
2398
2399 /* DirectSound doesn't have a large enough playCursor so we cannot detect wrap-around. */
2400 /* Attempt to detect playCursor wrap-around and correct it. */
2401 if( stream->outputIsRunning && (stream->perfCounterTicksPerBuffer.QuadPart != 0) )
2402 {
2403 /* How much time has elapsed since last check. */
2404 LARGE_INTEGER currentTime;
2405 LARGE_INTEGER elapsedTime;
2406 long bytesPlayed;
2407 long bytesExpected;
2408 long buffersWrapped;
2409
2410 QueryPerformanceCounter( ¤tTime );
2411 elapsedTime.QuadPart = currentTime.QuadPart - stream->previousPlayTime.QuadPart;
2412 stream->previousPlayTime = currentTime;
2413
2414 /* How many bytes does DirectSound say have been played. */
2415 bytesPlayed = playCursor - stream->previousPlayCursor;
2416 if( bytesPlayed < 0 ) bytesPlayed += stream->outputBufferSizeBytes; // unwrap
2417 stream->previousPlayCursor = playCursor;
2418
2419 /* Calculate how many bytes we would have expected to been played by now. */
2420 bytesExpected = (long) ((elapsedTime.QuadPart * stream->outputBufferSizeBytes) / stream->perfCounterTicksPerBuffer.QuadPart);
2421 buffersWrapped = (bytesExpected - bytesPlayed) / stream->outputBufferSizeBytes;
2422 if( buffersWrapped > 0 )
2423 {
2424 playCursor += (buffersWrapped * stream->outputBufferSizeBytes);
2425 bytesPlayed += (buffersWrapped * stream->outputBufferSizeBytes);
2426 }
2427 }
2428 numBytesEmpty = playCursor - stream->outputBufferWriteOffsetBytes;
2429 if( numBytesEmpty < 0 ) numBytesEmpty += stream->outputBufferSizeBytes; // unwrap offset
2430
2431 /* Have we underflowed? */
2432 if( numBytesEmpty > (stream->outputBufferSizeBytes - playWriteGap) )
2433 {
2434 if( stream->outputIsRunning )
2435 {
2436 stream->outputUnderflowCount += 1;
2437 }
2438
2439 /*
2440 From MSDN:
2441 The write cursor indicates the position at which it is safe
2442 to write new data to the buffer. The write cursor always leads the
2443 play cursor, typically by about 15 milliseconds' worth of audio
2444 data.
2445 It is always safe to change data that is behind the position
2446 indicated by the lpdwCurrentPlayCursor parameter.
2447 */
2448
2449 stream->outputBufferWriteOffsetBytes = writeCursor;
2450 numBytesEmpty = stream->outputBufferSizeBytes - playWriteGap;
2451 }
2452 *bytesEmpty = numBytesEmpty;
2453 return hr;
2454 }
2455
2456 /***********************************************************************************/
TimeSlice(PaWinDsStream * stream)2457 static int TimeSlice( PaWinDsStream *stream )
2458 {
2459 long numFrames = 0;
2460 long bytesEmpty = 0;
2461 long bytesFilled = 0;
2462 long bytesToXfer = 0;
2463 long framesToXfer = 0; /* the number of frames we'll process this tick */
2464 long numInFramesReady = 0;
2465 long numOutFramesReady = 0;
2466 long bytesProcessed;
2467 HRESULT hresult;
2468 double outputLatency = 0;
2469 double inputLatency = 0;
2470 PaStreamCallbackTimeInfo timeInfo = {0,0,0};
2471
2472 /* Input */
2473 LPBYTE lpInBuf1 = NULL;
2474 LPBYTE lpInBuf2 = NULL;
2475 DWORD dwInSize1 = 0;
2476 DWORD dwInSize2 = 0;
2477 /* Output */
2478 LPBYTE lpOutBuf1 = NULL;
2479 LPBYTE lpOutBuf2 = NULL;
2480 DWORD dwOutSize1 = 0;
2481 DWORD dwOutSize2 = 0;
2482
2483 /* How much input data is available? */
2484 if( stream->bufferProcessor.inputChannelCount > 0 )
2485 {
2486 HRESULT hr;
2487 DWORD capturePos;
2488 DWORD readPos;
2489 long filled = 0;
2490 // Query to see how much data is in buffer.
2491 // We don't need the capture position but sometimes DirectSound doesn't handle NULLS correctly
2492 // so let's pass a pointer just to be safe.
2493 hr = IDirectSoundCaptureBuffer_GetCurrentPosition( stream->pDirectSoundInputBuffer, &capturePos, &readPos );
2494 if( hr == DS_OK )
2495 {
2496 filled = readPos - stream->readOffset;
2497 if( filled < 0 ) filled += stream->inputBufferSizeBytes; // unwrap offset
2498 bytesFilled = filled;
2499
2500 inputLatency = ((double)bytesFilled) * stream->secondsPerHostByte;
2501 }
2502 // FIXME: what happens if IDirectSoundCaptureBuffer_GetCurrentPosition fails?
2503
2504 framesToXfer = numInFramesReady = bytesFilled / stream->inputFrameSizeBytes;
2505
2506 /** @todo Check for overflow */
2507 }
2508
2509 /* How much output room is available? */
2510 if( stream->bufferProcessor.outputChannelCount > 0 )
2511 {
2512 UINT previousUnderflowCount = stream->outputUnderflowCount;
2513 QueryOutputSpace( stream, &bytesEmpty );
2514 framesToXfer = numOutFramesReady = bytesEmpty / stream->outputFrameSizeBytes;
2515
2516 /* Check for underflow */
2517 /* FIXME QueryOutputSpace should not adjust underflow count as a side effect.
2518 A query function should be a const operator on the stream and return a flag on underflow. */
2519 if( stream->outputUnderflowCount != previousUnderflowCount )
2520 stream->callbackFlags |= paOutputUnderflow;
2521
2522 /* We are about to compute audio into the first byte of empty space in the output buffer.
2523 This audio will reach the DAC after all of the current (non-empty) audio
2524 in the buffer has played. Therefore the output time is the current time
2525 plus the time it takes to play the non-empty bytes in the buffer,
2526 computed here:
2527 */
2528 outputLatency = ((double)(stream->outputBufferSizeBytes - bytesEmpty)) * stream->secondsPerHostByte;
2529 }
2530
2531 /* if it's a full duplex stream, set framesToXfer to the minimum of input and output frames ready */
2532 if( stream->bufferProcessor.inputChannelCount > 0 && stream->bufferProcessor.outputChannelCount > 0 )
2533 {
2534 framesToXfer = (numOutFramesReady < numInFramesReady) ? numOutFramesReady : numInFramesReady;
2535 }
2536
2537 if( framesToXfer > 0 )
2538 {
2539 PaUtil_BeginCpuLoadMeasurement( &stream->cpuLoadMeasurer );
2540
2541 /* The outputBufferDacTime parameter should indicates the time at which
2542 the first sample of the output buffer is heard at the DACs. */
2543 timeInfo.currentTime = PaUtil_GetTime();
2544
2545 PaUtil_BeginBufferProcessing( &stream->bufferProcessor, &timeInfo, stream->callbackFlags );
2546 stream->callbackFlags = 0;
2547
2548 /* Input */
2549 if( stream->bufferProcessor.inputChannelCount > 0 )
2550 {
2551 timeInfo.inputBufferAdcTime = timeInfo.currentTime - inputLatency;
2552
2553 bytesToXfer = framesToXfer * stream->inputFrameSizeBytes;
2554 hresult = IDirectSoundCaptureBuffer_Lock ( stream->pDirectSoundInputBuffer,
2555 stream->readOffset, bytesToXfer,
2556 (void **) &lpInBuf1, &dwInSize1,
2557 (void **) &lpInBuf2, &dwInSize2, 0);
2558 if (hresult != DS_OK)
2559 {
2560 ERR_RPT(("DirectSound IDirectSoundCaptureBuffer_Lock failed, hresult = 0x%x\n",hresult));
2561 /* PA_DS_SET_LAST_DIRECTSOUND_ERROR( hresult ); */
2562 PaUtil_ResetBufferProcessor( &stream->bufferProcessor ); /* flush the buffer processor */
2563 stream->callbackResult = paComplete;
2564 goto error2;
2565 }
2566
2567 numFrames = dwInSize1 / stream->inputFrameSizeBytes;
2568 PaUtil_SetInputFrameCount( &stream->bufferProcessor, numFrames );
2569 PaUtil_SetInterleavedInputChannels( &stream->bufferProcessor, 0, lpInBuf1, 0 );
2570 /* Is input split into two regions. */
2571 if( dwInSize2 > 0 )
2572 {
2573 numFrames = dwInSize2 / stream->inputFrameSizeBytes;
2574 PaUtil_Set2ndInputFrameCount( &stream->bufferProcessor, numFrames );
2575 PaUtil_Set2ndInterleavedInputChannels( &stream->bufferProcessor, 0, lpInBuf2, 0 );
2576 }
2577 }
2578
2579 /* Output */
2580 if( stream->bufferProcessor.outputChannelCount > 0 )
2581 {
2582 /*
2583 We don't currently add outputLatency here because it appears to produce worse
2584 results than not adding it. Need to do more testing to verify this.
2585 */
2586 /* timeInfo.outputBufferDacTime = timeInfo.currentTime + outputLatency; */
2587 timeInfo.outputBufferDacTime = timeInfo.currentTime;
2588
2589 bytesToXfer = framesToXfer * stream->outputFrameSizeBytes;
2590 hresult = IDirectSoundBuffer_Lock ( stream->pDirectSoundOutputBuffer,
2591 stream->outputBufferWriteOffsetBytes, bytesToXfer,
2592 (void **) &lpOutBuf1, &dwOutSize1,
2593 (void **) &lpOutBuf2, &dwOutSize2, 0);
2594 if (hresult != DS_OK)
2595 {
2596 ERR_RPT(("DirectSound IDirectSoundBuffer_Lock failed, hresult = 0x%x\n",hresult));
2597 /* PA_DS_SET_LAST_DIRECTSOUND_ERROR( hresult ); */
2598 PaUtil_ResetBufferProcessor( &stream->bufferProcessor ); /* flush the buffer processor */
2599 stream->callbackResult = paComplete;
2600 goto error1;
2601 }
2602
2603 numFrames = dwOutSize1 / stream->outputFrameSizeBytes;
2604 PaUtil_SetOutputFrameCount( &stream->bufferProcessor, numFrames );
2605 PaUtil_SetInterleavedOutputChannels( &stream->bufferProcessor, 0, lpOutBuf1, 0 );
2606
2607 /* Is output split into two regions. */
2608 if( dwOutSize2 > 0 )
2609 {
2610 numFrames = dwOutSize2 / stream->outputFrameSizeBytes;
2611 PaUtil_Set2ndOutputFrameCount( &stream->bufferProcessor, numFrames );
2612 PaUtil_Set2ndInterleavedOutputChannels( &stream->bufferProcessor, 0, lpOutBuf2, 0 );
2613 }
2614 }
2615
2616 numFrames = PaUtil_EndBufferProcessing( &stream->bufferProcessor, &stream->callbackResult );
2617 stream->framesWritten += numFrames;
2618
2619 if( stream->bufferProcessor.outputChannelCount > 0 )
2620 {
2621 /* FIXME: an underflow could happen here */
2622
2623 /* Update our buffer offset and unlock sound buffer */
2624 bytesProcessed = numFrames * stream->outputFrameSizeBytes;
2625 stream->outputBufferWriteOffsetBytes = (stream->outputBufferWriteOffsetBytes + bytesProcessed) % stream->outputBufferSizeBytes;
2626 IDirectSoundBuffer_Unlock( stream->pDirectSoundOutputBuffer, lpOutBuf1, dwOutSize1, lpOutBuf2, dwOutSize2);
2627 }
2628
2629 error1:
2630 if( stream->bufferProcessor.inputChannelCount > 0 )
2631 {
2632 /* FIXME: an overflow could happen here */
2633
2634 /* Update our buffer offset and unlock sound buffer */
2635 bytesProcessed = numFrames * stream->inputFrameSizeBytes;
2636 stream->readOffset = (stream->readOffset + bytesProcessed) % stream->inputBufferSizeBytes;
2637 IDirectSoundCaptureBuffer_Unlock( stream->pDirectSoundInputBuffer, lpInBuf1, dwInSize1, lpInBuf2, dwInSize2);
2638 }
2639 error2:
2640
2641 PaUtil_EndCpuLoadMeasurement( &stream->cpuLoadMeasurer, numFrames );
2642 }
2643
2644 if( stream->callbackResult == paComplete && !PaUtil_IsBufferProcessorOutputEmpty( &stream->bufferProcessor ) )
2645 {
2646 /* don't return completed until the buffer processor has been drained */
2647 return paContinue;
2648 }
2649 else
2650 {
2651 return stream->callbackResult;
2652 }
2653 }
2654 /*******************************************************************/
2655
ZeroAvailableOutputSpace(PaWinDsStream * stream)2656 static HRESULT ZeroAvailableOutputSpace( PaWinDsStream *stream )
2657 {
2658 HRESULT hr;
2659 LPBYTE lpbuf1 = NULL;
2660 LPBYTE lpbuf2 = NULL;
2661 DWORD dwsize1 = 0;
2662 DWORD dwsize2 = 0;
2663 long bytesEmpty;
2664 hr = QueryOutputSpace( stream, &bytesEmpty );
2665 if (hr != DS_OK) return hr;
2666 if( bytesEmpty == 0 ) return DS_OK;
2667 // Lock free space in the DS
2668 hr = IDirectSoundBuffer_Lock( stream->pDirectSoundOutputBuffer, stream->outputBufferWriteOffsetBytes,
2669 bytesEmpty, (void **) &lpbuf1, &dwsize1,
2670 (void **) &lpbuf2, &dwsize2, 0);
2671 if (hr == DS_OK)
2672 {
2673 // Copy the buffer into the DS
2674 ZeroMemory(lpbuf1, dwsize1);
2675 if(lpbuf2 != NULL)
2676 {
2677 ZeroMemory(lpbuf2, dwsize2);
2678 }
2679 // Update our buffer offset and unlock sound buffer
2680 stream->outputBufferWriteOffsetBytes = (stream->outputBufferWriteOffsetBytes + dwsize1 + dwsize2) % stream->outputBufferSizeBytes;
2681 IDirectSoundBuffer_Unlock( stream->pDirectSoundOutputBuffer, lpbuf1, dwsize1, lpbuf2, dwsize2);
2682
2683 stream->finalZeroBytesWritten += dwsize1 + dwsize2;
2684 }
2685 return hr;
2686 }
2687
2688
TimerCallback(UINT uID,UINT uMsg,DWORD_PTR dwUser,DWORD dw1,DWORD dw2)2689 static void CALLBACK TimerCallback(UINT uID, UINT uMsg, DWORD_PTR dwUser, DWORD dw1, DWORD dw2)
2690 {
2691 PaWinDsStream *stream;
2692 int isFinished = 0;
2693
2694 /* suppress unused variable warnings */
2695 (void) uID;
2696 (void) uMsg;
2697 (void) dw1;
2698 (void) dw2;
2699
2700 stream = (PaWinDsStream *) dwUser;
2701 if( stream == NULL ) return;
2702
2703 if( stream->isActive )
2704 {
2705 if( stream->abortProcessing )
2706 {
2707 isFinished = 1;
2708 }
2709 else if( stream->stopProcessing )
2710 {
2711 if( stream->bufferProcessor.outputChannelCount > 0 )
2712 {
2713 ZeroAvailableOutputSpace( stream );
2714 if( stream->finalZeroBytesWritten >= stream->outputBufferSizeBytes )
2715 {
2716 /* once we've flushed the whole output buffer with zeros we know all data has been played */
2717 isFinished = 1;
2718 }
2719 }
2720 else
2721 {
2722 isFinished = 1;
2723 }
2724 }
2725 else
2726 {
2727 int callbackResult = TimeSlice( stream );
2728 if( callbackResult != paContinue )
2729 {
2730 /* FIXME implement handling of paComplete and paAbort if possible
2731 At the moment this should behave as if paComplete was called and
2732 flush the buffer.
2733 */
2734
2735 stream->stopProcessing = 1;
2736 }
2737 }
2738
2739 if( isFinished )
2740 {
2741 if( stream->streamRepresentation.streamFinishedCallback != 0 )
2742 stream->streamRepresentation.streamFinishedCallback( stream->streamRepresentation.userData );
2743
2744 stream->isActive = 0; /* don't set this until the stream really is inactive */
2745 SetEvent( stream->processingCompleted );
2746 }
2747 }
2748 }
2749
2750 #ifndef PA_WIN_DS_USE_WMME_TIMER
2751
2752 #ifdef PA_WIN_DS_USE_WAITABLE_TIMER_OBJECT
2753
WaitableTimerAPCProc(LPVOID lpArg,DWORD dwTimerLowValue,DWORD dwTimerHighValue)2754 static void CALLBACK WaitableTimerAPCProc(
2755 LPVOID lpArg, // Data value
2756 DWORD dwTimerLowValue, // Timer low value
2757 DWORD dwTimerHighValue ) // Timer high value
2758
2759 {
2760 (void)dwTimerLowValue;
2761 (void)dwTimerHighValue;
2762
2763 TimerCallback( 0, 0, (DWORD_PTR)lpArg, 0, 0 );
2764 }
2765
2766 #endif /* PA_WIN_DS_USE_WAITABLE_TIMER_OBJECT */
2767
2768
ProcessingThreadProc(void * pArg)2769 PA_THREAD_FUNC ProcessingThreadProc( void *pArg )
2770 {
2771 PaWinDsStream *stream = (PaWinDsStream *)pArg;
2772 LARGE_INTEGER dueTime;
2773 int timerPeriodMs;
2774
2775 timerPeriodMs = (int)(stream->pollingPeriodSeconds * MSECS_PER_SECOND);
2776 if( timerPeriodMs < 1 )
2777 timerPeriodMs = 1;
2778
2779 #ifdef PA_WIN_DS_USE_WAITABLE_TIMER_OBJECT
2780 assert( stream->waitableTimer != NULL );
2781
2782 /* invoke first timeout immediately */
2783 dueTime.LowPart = timerPeriodMs * 1000 * 10;
2784 dueTime.HighPart = 0;
2785
2786 /* tick using waitable timer */
2787 if( SetWaitableTimer( stream->waitableTimer, &dueTime, timerPeriodMs, WaitableTimerAPCProc, pArg, FALSE ) != 0 )
2788 {
2789 DWORD wfsoResult = 0;
2790 do
2791 {
2792 /* wait for processingCompleted to be signaled or our timer APC to be called */
2793 wfsoResult = WaitForSingleObjectEx( stream->processingCompleted, timerPeriodMs * 10, /* alertable = */ TRUE );
2794
2795 }while( wfsoResult == WAIT_TIMEOUT || wfsoResult == WAIT_IO_COMPLETION );
2796 }
2797
2798 CancelWaitableTimer( stream->waitableTimer );
2799
2800 #else
2801
2802 /* tick using WaitForSingleObject timout */
2803 while ( WaitForSingleObject( stream->processingCompleted, timerPeriodMs ) == WAIT_TIMEOUT )
2804 {
2805 TimerCallback( 0, 0, (DWORD_PTR)pArg, 0, 0 );
2806 }
2807 #endif /* PA_WIN_DS_USE_WAITABLE_TIMER_OBJECT */
2808
2809 SetEvent( stream->processingThreadCompleted );
2810
2811 return 0;
2812 }
2813
2814 #endif /* !PA_WIN_DS_USE_WMME_TIMER */
2815
2816 /***********************************************************************************
2817 When CloseStream() is called, the multi-api layer ensures that
2818 the stream has already been stopped or aborted.
2819 */
CloseStream(PaStream * s)2820 static PaError CloseStream( PaStream* s )
2821 {
2822 PaError result = paNoError;
2823 PaWinDsStream *stream = (PaWinDsStream*)s;
2824
2825 CloseHandle( stream->processingCompleted );
2826
2827 #ifdef PA_WIN_DS_USE_WAITABLE_TIMER_OBJECT
2828 if( stream->waitableTimer != NULL )
2829 CloseHandle( stream->waitableTimer );
2830 #endif
2831
2832 #ifndef PA_WIN_DS_USE_WMME_TIMER
2833 CloseHandle( stream->processingThreadCompleted );
2834 #endif
2835
2836 // Cleanup the sound buffers
2837 if( stream->pDirectSoundOutputBuffer )
2838 {
2839 IDirectSoundBuffer_Stop( stream->pDirectSoundOutputBuffer );
2840 IDirectSoundBuffer_Release( stream->pDirectSoundOutputBuffer );
2841 stream->pDirectSoundOutputBuffer = NULL;
2842 }
2843
2844 if( stream->pDirectSoundPrimaryBuffer )
2845 {
2846 IDirectSoundBuffer_Release( stream->pDirectSoundPrimaryBuffer );
2847 stream->pDirectSoundPrimaryBuffer = NULL;
2848 }
2849
2850 if( stream->pDirectSoundInputBuffer )
2851 {
2852 IDirectSoundCaptureBuffer_Stop( stream->pDirectSoundInputBuffer );
2853 IDirectSoundCaptureBuffer_Release( stream->pDirectSoundInputBuffer );
2854 stream->pDirectSoundInputBuffer = NULL;
2855 }
2856
2857 if( stream->pDirectSoundCapture )
2858 {
2859 IDirectSoundCapture_Release( stream->pDirectSoundCapture );
2860 stream->pDirectSoundCapture = NULL;
2861 }
2862
2863 if( stream->pDirectSound )
2864 {
2865 IDirectSound_Release( stream->pDirectSound );
2866 stream->pDirectSound = NULL;
2867 }
2868
2869 #ifdef PAWIN_USE_DIRECTSOUNDFULLDUPLEXCREATE
2870 if( stream->pDirectSoundFullDuplex8 )
2871 {
2872 IDirectSoundFullDuplex_Release( stream->pDirectSoundFullDuplex8 );
2873 stream->pDirectSoundFullDuplex8 = NULL;
2874 }
2875 #endif
2876
2877 PaUtil_TerminateBufferProcessor( &stream->bufferProcessor );
2878 PaUtil_TerminateStreamRepresentation( &stream->streamRepresentation );
2879 PaUtil_FreeMemory( stream );
2880
2881 return result;
2882 }
2883
2884 /***********************************************************************************/
ClearOutputBuffer(PaWinDsStream * stream)2885 static HRESULT ClearOutputBuffer( PaWinDsStream *stream )
2886 {
2887 PaError result = paNoError;
2888 unsigned char* pDSBuffData;
2889 DWORD dwDataLen;
2890 HRESULT hr;
2891
2892 hr = IDirectSoundBuffer_SetCurrentPosition( stream->pDirectSoundOutputBuffer, 0 );
2893 DBUG(("PaHost_ClearOutputBuffer: IDirectSoundBuffer_SetCurrentPosition returned = 0x%X.\n", hr));
2894 if( hr != DS_OK )
2895 return hr;
2896
2897 // Lock the DS buffer
2898 if ((hr = IDirectSoundBuffer_Lock( stream->pDirectSoundOutputBuffer, 0, stream->outputBufferSizeBytes, (LPVOID*)&pDSBuffData,
2899 &dwDataLen, NULL, 0, 0)) != DS_OK )
2900 return hr;
2901
2902 // Zero the DS buffer
2903 ZeroMemory(pDSBuffData, dwDataLen);
2904 // Unlock the DS buffer
2905 if ((hr = IDirectSoundBuffer_Unlock( stream->pDirectSoundOutputBuffer, pDSBuffData, dwDataLen, NULL, 0)) != DS_OK)
2906 return hr;
2907
2908 // Let DSound set the starting write position because if we set it to zero, it looks like the
2909 // buffer is full to begin with. This causes a long pause before sound starts when using large buffers.
2910 if ((hr = IDirectSoundBuffer_GetCurrentPosition( stream->pDirectSoundOutputBuffer,
2911 &stream->previousPlayCursor, &stream->outputBufferWriteOffsetBytes )) != DS_OK)
2912 return hr;
2913
2914 /* printf("DSW_InitOutputBuffer: playCursor = %d, writeCursor = %d\n", playCursor, dsw->dsw_WriteOffset ); */
2915
2916 return DS_OK;
2917 }
2918
StartStream(PaStream * s)2919 static PaError StartStream( PaStream *s )
2920 {
2921 PaError result = paNoError;
2922 PaWinDsStream *stream = (PaWinDsStream*)s;
2923 HRESULT hr;
2924
2925 stream->callbackResult = paContinue;
2926 PaUtil_ResetBufferProcessor( &stream->bufferProcessor );
2927
2928 ResetEvent( stream->processingCompleted );
2929
2930 #ifndef PA_WIN_DS_USE_WMME_TIMER
2931 ResetEvent( stream->processingThreadCompleted );
2932 #endif
2933
2934 if( stream->bufferProcessor.inputChannelCount > 0 )
2935 {
2936 // Start the buffer capture
2937 if( stream->pDirectSoundInputBuffer != NULL ) // FIXME: not sure this check is necessary
2938 {
2939 hr = IDirectSoundCaptureBuffer_Start( stream->pDirectSoundInputBuffer, DSCBSTART_LOOPING );
2940 }
2941
2942 DBUG(("StartStream: DSW_StartInput returned = 0x%X.\n", hr));
2943 if( hr != DS_OK )
2944 {
2945 result = paUnanticipatedHostError;
2946 PA_DS_SET_LAST_DIRECTSOUND_ERROR( hr );
2947 goto error;
2948 }
2949 }
2950
2951 stream->framesWritten = 0;
2952 stream->callbackFlags = 0;
2953
2954 stream->abortProcessing = 0;
2955 stream->stopProcessing = 0;
2956
2957 if( stream->bufferProcessor.outputChannelCount > 0 )
2958 {
2959 QueryPerformanceCounter( &stream->previousPlayTime );
2960 stream->finalZeroBytesWritten = 0;
2961
2962 hr = ClearOutputBuffer( stream );
2963 if( hr != DS_OK )
2964 {
2965 result = paUnanticipatedHostError;
2966 PA_DS_SET_LAST_DIRECTSOUND_ERROR( hr );
2967 goto error;
2968 }
2969
2970 if( stream->streamRepresentation.streamCallback && (stream->streamFlags & paPrimeOutputBuffersUsingStreamCallback) )
2971 {
2972 stream->callbackFlags = paPrimingOutput;
2973
2974 TimeSlice( stream );
2975 /* we ignore the return value from TimeSlice here and start the stream as usual.
2976 The first timer callback will detect if the callback has completed. */
2977
2978 stream->callbackFlags = 0;
2979 }
2980
2981 // Start the buffer playback in a loop.
2982 if( stream->pDirectSoundOutputBuffer != NULL ) // FIXME: not sure this needs to be checked here
2983 {
2984 hr = IDirectSoundBuffer_Play( stream->pDirectSoundOutputBuffer, 0, 0, DSBPLAY_LOOPING );
2985 DBUG(("PaHost_StartOutput: IDirectSoundBuffer_Play returned = 0x%X.\n", hr));
2986 if( hr != DS_OK )
2987 {
2988 result = paUnanticipatedHostError;
2989 PA_DS_SET_LAST_DIRECTSOUND_ERROR( hr );
2990 goto error;
2991 }
2992 stream->outputIsRunning = TRUE;
2993 }
2994 }
2995
2996 if( stream->streamRepresentation.streamCallback )
2997 {
2998 TIMECAPS timecaps;
2999 int timerPeriodMs = (int)(stream->pollingPeriodSeconds * MSECS_PER_SECOND);
3000 if( timerPeriodMs < 1 )
3001 timerPeriodMs = 1;
3002
3003 /* set windows scheduler granularity only as fine as needed, no finer */
3004 /* Although this is not fully documented by MS, it appears that
3005 timeBeginPeriod() affects the scheduling granulatity of all timers
3006 including Waitable Timer Objects. So we always call timeBeginPeriod, whether
3007 we're using an MM timer callback via timeSetEvent or not.
3008 */
3009 assert( stream->systemTimerResolutionPeriodMs == 0 );
3010 if( timeGetDevCaps( &timecaps, sizeof(TIMECAPS) ) == MMSYSERR_NOERROR && timecaps.wPeriodMin > 0 )
3011 {
3012 /* aim for resolution 4 times higher than polling rate */
3013 stream->systemTimerResolutionPeriodMs = (UINT)((stream->pollingPeriodSeconds * MSECS_PER_SECOND) * .25);
3014 if( stream->systemTimerResolutionPeriodMs < timecaps.wPeriodMin )
3015 stream->systemTimerResolutionPeriodMs = timecaps.wPeriodMin;
3016 if( stream->systemTimerResolutionPeriodMs > timecaps.wPeriodMax )
3017 stream->systemTimerResolutionPeriodMs = timecaps.wPeriodMax;
3018
3019 if( timeBeginPeriod( stream->systemTimerResolutionPeriodMs ) != MMSYSERR_NOERROR )
3020 stream->systemTimerResolutionPeriodMs = 0; /* timeBeginPeriod failed, so we don't need to call timeEndPeriod() later */
3021 }
3022
3023
3024 #ifdef PA_WIN_DS_USE_WMME_TIMER
3025 /* Create timer that will wake us up so we can fill the DSound buffer. */
3026 /* We have deprecated timeSetEvent because all MM timer callbacks
3027 are serialised onto a single thread. Which creates problems with multiple
3028 PA streams, or when also using timers for other time critical tasks
3029 */
3030 stream->timerID = timeSetEvent( timerPeriodMs, stream->systemTimerResolutionPeriodMs, (LPTIMECALLBACK) TimerCallback,
3031 (DWORD_PTR) stream, TIME_PERIODIC | TIME_KILL_SYNCHRONOUS );
3032
3033 if( stream->timerID == 0 )
3034 {
3035 stream->isActive = 0;
3036 result = paUnanticipatedHostError;
3037 PA_DS_SET_LAST_DIRECTSOUND_ERROR( GetLastError() );
3038 goto error;
3039 }
3040 #else
3041 /* Create processing thread which calls TimerCallback */
3042
3043 stream->processingThread = CREATE_THREAD( 0, 0, ProcessingThreadProc, stream, 0, &stream->processingThreadId );
3044 if( !stream->processingThread )
3045 {
3046 result = paUnanticipatedHostError;
3047 PA_DS_SET_LAST_DIRECTSOUND_ERROR( GetLastError() );
3048 goto error;
3049 }
3050
3051 if( !SetThreadPriority( stream->processingThread, THREAD_PRIORITY_TIME_CRITICAL ) )
3052 {
3053 result = paUnanticipatedHostError;
3054 PA_DS_SET_LAST_DIRECTSOUND_ERROR( GetLastError() );
3055 goto error;
3056 }
3057 #endif
3058 }
3059
3060 stream->isActive = 1;
3061 stream->isStarted = 1;
3062
3063 assert( result == paNoError );
3064 return result;
3065
3066 error:
3067
3068 if( stream->pDirectSoundOutputBuffer != NULL && stream->outputIsRunning )
3069 IDirectSoundBuffer_Stop( stream->pDirectSoundOutputBuffer );
3070 stream->outputIsRunning = FALSE;
3071
3072 #ifndef PA_WIN_DS_USE_WMME_TIMER
3073 if( stream->processingThread )
3074 {
3075 #ifdef CLOSE_THREAD_HANDLE
3076 CLOSE_THREAD_HANDLE( stream->processingThread ); /* Delete thread. */
3077 #endif
3078 stream->processingThread = NULL;
3079 }
3080 #endif
3081
3082 return result;
3083 }
3084
3085
3086 /***********************************************************************************/
StopStream(PaStream * s)3087 static PaError StopStream( PaStream *s )
3088 {
3089 PaError result = paNoError;
3090 PaWinDsStream *stream = (PaWinDsStream*)s;
3091 HRESULT hr;
3092 int timeoutMsec;
3093
3094 if( stream->streamRepresentation.streamCallback )
3095 {
3096 stream->stopProcessing = 1;
3097
3098 /* Set timeout at 4 times maximum time we might wait. */
3099 timeoutMsec = (int) (4 * MSECS_PER_SECOND * (stream->hostBufferSizeFrames / stream->streamRepresentation.streamInfo.sampleRate));
3100
3101 WaitForSingleObject( stream->processingCompleted, timeoutMsec );
3102 }
3103
3104 #ifdef PA_WIN_DS_USE_WMME_TIMER
3105 if( stream->timerID != 0 )
3106 {
3107 timeKillEvent(stream->timerID); /* Stop callback timer. */
3108 stream->timerID = 0;
3109 }
3110 #else
3111 if( stream->processingThread )
3112 {
3113 if( WaitForSingleObject( stream->processingThreadCompleted, 30*100 ) == WAIT_TIMEOUT )
3114 return paUnanticipatedHostError;
3115
3116 #ifdef CLOSE_THREAD_HANDLE
3117 CloseHandle( stream->processingThread ); /* Delete thread. */
3118 stream->processingThread = NULL;
3119 #endif
3120
3121 }
3122 #endif
3123
3124 if( stream->systemTimerResolutionPeriodMs > 0 ){
3125 timeEndPeriod( stream->systemTimerResolutionPeriodMs );
3126 stream->systemTimerResolutionPeriodMs = 0;
3127 }
3128
3129 if( stream->bufferProcessor.outputChannelCount > 0 )
3130 {
3131 // Stop the buffer playback
3132 if( stream->pDirectSoundOutputBuffer != NULL )
3133 {
3134 stream->outputIsRunning = FALSE;
3135 // FIXME: what happens if IDirectSoundBuffer_Stop returns an error?
3136 hr = IDirectSoundBuffer_Stop( stream->pDirectSoundOutputBuffer );
3137
3138 if( stream->pDirectSoundPrimaryBuffer )
3139 IDirectSoundBuffer_Stop( stream->pDirectSoundPrimaryBuffer ); /* FIXME we never started the primary buffer so I'm not sure we need to stop it */
3140 }
3141 }
3142
3143 if( stream->bufferProcessor.inputChannelCount > 0 )
3144 {
3145 // Stop the buffer capture
3146 if( stream->pDirectSoundInputBuffer != NULL )
3147 {
3148 // FIXME: what happens if IDirectSoundCaptureBuffer_Stop returns an error?
3149 hr = IDirectSoundCaptureBuffer_Stop( stream->pDirectSoundInputBuffer );
3150 }
3151 }
3152
3153 stream->isStarted = 0;
3154
3155 return result;
3156 }
3157
3158
3159 /***********************************************************************************/
AbortStream(PaStream * s)3160 static PaError AbortStream( PaStream *s )
3161 {
3162 PaWinDsStream *stream = (PaWinDsStream*)s;
3163
3164 stream->abortProcessing = 1;
3165 return StopStream( s );
3166 }
3167
3168
3169 /***********************************************************************************/
IsStreamStopped(PaStream * s)3170 static PaError IsStreamStopped( PaStream *s )
3171 {
3172 PaWinDsStream *stream = (PaWinDsStream*)s;
3173
3174 return !stream->isStarted;
3175 }
3176
3177
3178 /***********************************************************************************/
IsStreamActive(PaStream * s)3179 static PaError IsStreamActive( PaStream *s )
3180 {
3181 PaWinDsStream *stream = (PaWinDsStream*)s;
3182
3183 return stream->isActive;
3184 }
3185
3186 /***********************************************************************************/
GetStreamTime(PaStream * s)3187 static PaTime GetStreamTime( PaStream *s )
3188 {
3189 /* suppress unused variable warnings */
3190 (void) s;
3191
3192 return PaUtil_GetTime();
3193 }
3194
3195
3196 /***********************************************************************************/
GetStreamCpuLoad(PaStream * s)3197 static double GetStreamCpuLoad( PaStream* s )
3198 {
3199 PaWinDsStream *stream = (PaWinDsStream*)s;
3200
3201 return PaUtil_GetCpuLoad( &stream->cpuLoadMeasurer );
3202 }
3203
3204
3205 /***********************************************************************************
3206 As separate stream interfaces are used for blocking and callback
3207 streams, the following functions can be guaranteed to only be called
3208 for blocking streams.
3209 */
3210
ReadStream(PaStream * s,void * buffer,unsigned long frames)3211 static PaError ReadStream( PaStream* s,
3212 void *buffer,
3213 unsigned long frames )
3214 {
3215 PaWinDsStream *stream = (PaWinDsStream*)s;
3216
3217 /* suppress unused variable warnings */
3218 (void) buffer;
3219 (void) frames;
3220 (void) stream;
3221
3222 /* IMPLEMENT ME, see portaudio.h for required behavior*/
3223
3224 return paNoError;
3225 }
3226
3227
3228 /***********************************************************************************/
WriteStream(PaStream * s,const void * buffer,unsigned long frames)3229 static PaError WriteStream( PaStream* s,
3230 const void *buffer,
3231 unsigned long frames )
3232 {
3233 PaWinDsStream *stream = (PaWinDsStream*)s;
3234
3235 /* suppress unused variable warnings */
3236 (void) buffer;
3237 (void) frames;
3238 (void) stream;
3239
3240 /* IMPLEMENT ME, see portaudio.h for required behavior*/
3241
3242 return paNoError;
3243 }
3244
3245
3246 /***********************************************************************************/
GetStreamReadAvailable(PaStream * s)3247 static signed long GetStreamReadAvailable( PaStream* s )
3248 {
3249 PaWinDsStream *stream = (PaWinDsStream*)s;
3250
3251 /* suppress unused variable warnings */
3252 (void) stream;
3253
3254 /* IMPLEMENT ME, see portaudio.h for required behavior*/
3255
3256 return 0;
3257 }
3258
3259
3260 /***********************************************************************************/
GetStreamWriteAvailable(PaStream * s)3261 static signed long GetStreamWriteAvailable( PaStream* s )
3262 {
3263 PaWinDsStream *stream = (PaWinDsStream*)s;
3264
3265 /* suppress unused variable warnings */
3266 (void) stream;
3267
3268 /* IMPLEMENT ME, see portaudio.h for required behavior*/
3269
3270 return 0;
3271 }
3272
3273 #endif
3274