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