1 /* 2 * PortAudio Portable Real-Time Audio Library 3 * Latest Version at: http://www.softsynth.com/portaudio/ 4 * DirectSound Implementation 5 * 6 * Copyright (c) 1999-2000 Phil Burk 7 * 8 * Permission is hereby granted, free of charge, to any person obtaining 9 * a copy of this software and associated documentation files 10 * (the "Software"), to deal in the Software without restriction, 11 * including without limitation the rights to use, copy, modify, merge, 12 * publish, distribute, sublicense, and/or sell copies of the Software, 13 * and to permit persons to whom the Software is furnished to do so, 14 * subject to the following conditions: 15 * 16 * The above copyright notice and this permission notice shall be 17 * included in all copies or substantial portions of the Software. 18 * 19 * Any person wishing to distribute modifications to the Software is 20 * requested to send the modifications to the original developer so that 21 * they can be incorporated into the canonical version. 22 * 23 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 24 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 25 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 26 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR 27 * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 28 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 29 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 30 * 31 */ 32 /* Modifications 33 * 7/19/01 Mike Berry - casts for compiling with __MWERKS__ CodeWarrior 34 * 9/27/01 Phil Burk - use number of frames instead of real-time for CPULoad calculation. 35 */ 36 /* Compiler flags: 37 SUPPORT_AUDIO_CAPTURE - define this flag if you want to SUPPORT_AUDIO_CAPTURE 38 */ 39 #include <stdio.h> 40 #include <stdlib.h> 41 #ifndef __MWERKS__ 42 #include <malloc.h> 43 #include <memory.h> 44 #endif //__MWERKS__ 45 #include <math.h> 46 #if 0 47 #include "portaudio.h" 48 #include "pa_host.h" 49 #include "pa_trace.h" 50 #include "dsound_wrapper.h" 51 #endif 52 #define PRINT(x) { printf x; fflush(stdout); } 53 #define ERR_RPT(x) PRINT(x) 54 #define DBUG(x) /* PRINT(x) */ 55 #define DBUGX(x) /* PRINT(x) */ 56 #define PA_USE_HIGH_LATENCY (0) 57 #if PA_USE_HIGH_LATENCY 58 #define PA_WIN_9X_LATENCY (500) 59 #define PA_WIN_NT_LATENCY (600) 60 #else 61 #define PA_WIN_9X_LATENCY (140) 62 #define PA_WIN_NT_LATENCY (280) 63 #endif 64 /* Trigger an underflow for testing purposes. Should normally be (0). */ 65 #define PA_SIMULATE_UNDERFLOW (0) 66 #if PA_SIMULATE_UNDERFLOW 67 static gUnderCallbackCounter = 0; 68 #define UNDER_START_GAP (10) 69 #define UNDER_STOP_GAP (UNDER_START_GAP + 4) 70 #endif 71 /************************************************* Definitions ********/ 72 typedef struct internalPortAudioStream internalPortAudioStream; 73 typedef struct internalPortAudioDevice 74 { 75 GUID pad_GUID; 76 GUID *pad_lpGUID; 77 double pad_SampleRates[10]; /* for pointing to from pad_Info FIXME?!*/ 78 PaDeviceInfo pad_Info; 79 } internalPortAudioDevice; 80 /* Define structure to contain all DirectSound and Windows specific data. */ 81 typedef struct PaHostSoundControl 82 { 83 DSoundWrapper pahsc_DSoundWrapper; 84 MMRESULT pahsc_TimerID; 85 BOOL pahsc_IfInsideCallback; /* Test for reentrancy. */ 86 short *pahsc_NativeBuffer; 87 unsigned int pahsc_BytesPerBuffer; /* native buffer size in bytes */ 88 double pahsc_ValidFramesWritten; 89 int pahsc_FramesPerDSBuffer; 90 /* For measuring CPU utilization. */ 91 LARGE_INTEGER pahsc_EntryCount; 92 double pahsc_InverseTicksPerUserBuffer; 93 } PaHostSoundControl; 94 /************************************************* Shared Data ********/ 95 /* FIXME - put Mutex around this shared data. */ 96 static int sNumDevices = 0; 97 static int sDeviceIndex = 0; 98 static internalPortAudioDevice *sDevices = NULL; 99 static int sDefaultInputDeviceID = paNoDevice; 100 static int sDefaultOutputDeviceID = paNoDevice; 101 static int sEnumerationError; 102 static int sPaHostError = 0; 103 /************************************************* Prototypes **********/ 104 static internalPortAudioDevice *Pa_GetInternalDevice( PaDeviceID id ); 105 static BOOL CALLBACK Pa_EnumProc(LPGUID lpGUID, 106 LPCTSTR lpszDesc, 107 LPCTSTR lpszDrvName, 108 LPVOID lpContext ); 109 static BOOL CALLBACK Pa_CountDevProc(LPGUID lpGUID, 110 LPCTSTR lpszDesc, 111 LPCTSTR lpszDrvName, 112 LPVOID lpContext ); 113 static Pa_QueryDevices( void ); 114 static void CALLBACK Pa_TimerCallback(UINT uID, UINT uMsg, 115 DWORD dwUser, DWORD dw1, DWORD dw2); 116 /********************************* BEGIN CPU UTILIZATION MEASUREMENT ****/ 117 static void Pa_StartUsageCalculation( internalPortAudioStream *past ) 118 { 119 PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; 120 if( pahsc == NULL ) return; 121 /* Query system timer for usage analysis and to prevent overuse of CPU. */ 122 QueryPerformanceCounter( &pahsc->pahsc_EntryCount ); 123 } 124 static void Pa_EndUsageCalculation( internalPortAudioStream *past ) 125 { 126 LARGE_INTEGER CurrentCount = { 0, 0 }; 127 PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; 128 if( pahsc == NULL ) return; 129 /* 130 ** Measure CPU utilization during this callback. Note that this calculation 131 ** assumes that we had the processor the whole time. 132 */ 133 #define LOWPASS_COEFFICIENT_0 (0.9) 134 #define LOWPASS_COEFFICIENT_1 (0.99999 - LOWPASS_COEFFICIENT_0) 135 if( QueryPerformanceCounter( &CurrentCount ) ) 136 { 137 LONGLONG InsideCount = CurrentCount.QuadPart - pahsc->pahsc_EntryCount.QuadPart; 138 double newUsage = InsideCount * pahsc->pahsc_InverseTicksPerUserBuffer; 139 past->past_Usage = (LOWPASS_COEFFICIENT_0 * past->past_Usage) + 140 (LOWPASS_COEFFICIENT_1 * newUsage); 141 } 142 } 143 /****************************************** END CPU UTILIZATION *******/ 144 static PaError Pa_QueryDevices( void ) 145 { 146 int numBytes; 147 sDefaultInputDeviceID = paNoDevice; 148 sDefaultOutputDeviceID = paNoDevice; 149 /* Enumerate once just to count devices. */ 150 sNumDevices = 0; // for default device 151 DirectSoundEnumerate( (LPDSENUMCALLBACK)Pa_CountDevProc, NULL ); 152 #if SUPPORT_AUDIO_CAPTURE 153 DirectSoundCaptureEnumerate( (LPDSENUMCALLBACK)Pa_CountDevProc, NULL ); 154 #endif /* SUPPORT_AUDIO_CAPTURE */ 155 /* Allocate structures to hold device info. */ 156 numBytes = sNumDevices * sizeof(internalPortAudioDevice); 157 sDevices = (internalPortAudioDevice *)PaHost_AllocateFastMemory( numBytes ); /* MEM */ 158 if( sDevices == NULL ) return paInsufficientMemory; 159 /* Enumerate again to fill in structures. */ 160 sDeviceIndex = 0; 161 sEnumerationError = 0; 162 DirectSoundEnumerate( (LPDSENUMCALLBACK)Pa_EnumProc, (void *)0 ); 163 #if SUPPORT_AUDIO_CAPTURE 164 if( sEnumerationError != paNoError ) return sEnumerationError; 165 sEnumerationError = 0; 166 DirectSoundCaptureEnumerate( (LPDSENUMCALLBACK)Pa_EnumProc, (void *)1 ); 167 #endif /* SUPPORT_AUDIO_CAPTURE */ 168 return sEnumerationError; 169 } 170 /************************************************************************************/ 171 long Pa_GetHostError() 172 { 173 return sPaHostError; 174 } 175 /************************************************************************************ 176 ** Just count devices so we know how much memory to allocate. 177 */ 178 static BOOL CALLBACK Pa_CountDevProc(LPGUID lpGUID, 179 LPCTSTR lpszDesc, 180 LPCTSTR lpszDrvName, 181 LPVOID lpContext ) 182 { 183 sNumDevices++; 184 return TRUE; 185 } 186 /************************************************************************************ 187 ** Extract capabilities info from each device. 188 */ 189 static BOOL CALLBACK Pa_EnumProc(LPGUID lpGUID, 190 LPCTSTR lpszDesc, 191 LPCTSTR lpszDrvName, 192 LPVOID lpContext ) 193 { 194 HRESULT hr; 195 LPDIRECTSOUND lpDirectSound; 196 #if SUPPORT_AUDIO_CAPTURE 197 LPDIRECTSOUNDCAPTURE lpDirectSoundCapture; 198 #endif /* SUPPORT_AUDIO_CAPTURE */ 199 int isInput = (int) lpContext; /* Passed from Pa_CountDevices() */ 200 internalPortAudioDevice *pad; 201 202 if( sDeviceIndex >= sNumDevices ) 203 { 204 sEnumerationError = paInternalError; 205 return FALSE; 206 } 207 pad = &sDevices[sDeviceIndex]; 208 /* Copy GUID to static array. Set pointer. */ 209 if( lpGUID == NULL ) 210 { 211 pad->pad_lpGUID = NULL; 212 } 213 else 214 { 215 memcpy( &pad->pad_GUID, lpGUID, sizeof(GUID) ); 216 pad->pad_lpGUID = &pad->pad_GUID; 217 } 218 pad->pad_Info.sampleRates = pad->pad_SampleRates; /* Point to array. */ 219 /* Allocate room for descriptive name. */ 220 if( lpszDesc != NULL ) 221 { 222 int len = strlen(lpszDesc); 223 pad->pad_Info.name = (char *)malloc( len+1 ); 224 if( pad->pad_Info.name == NULL ) 225 { 226 sEnumerationError = paInsufficientMemory; 227 return FALSE; 228 } 229 memcpy( (void *) pad->pad_Info.name, lpszDesc, len+1 ); 230 } 231 #if SUPPORT_AUDIO_CAPTURE 232 if( isInput ) 233 { 234 /********** Input ******************************/ 235 DSCCAPS caps; 236 if( lpGUID == NULL ) sDefaultInputDeviceID = sDeviceIndex; 237 hr = DirectSoundCaptureCreate( lpGUID, &lpDirectSoundCapture, NULL ); 238 if( hr != DS_OK ) 239 { 240 pad->pad_Info.maxInputChannels = 0; 241 DBUG(("Cannot create Capture for %s. Result = 0x%x\n", lpszDesc, hr )); 242 } 243 else 244 { 245 /* Query device characteristics. */ 246 caps.dwSize = sizeof(caps); 247 IDirectSoundCapture_GetCaps( lpDirectSoundCapture, &caps ); 248 /* printf("caps.dwFormats = 0x%x\n", caps.dwFormats ); */ 249 pad->pad_Info.maxInputChannels = caps.dwChannels; 250 /* Determine sample rates from flags. */ 251 if( caps.dwChannels == 2 ) 252 { 253 int index = 0; 254 if( caps.dwFormats & WAVE_FORMAT_1S16) pad->pad_SampleRates[index++] = 11025.0; 255 if( caps.dwFormats & WAVE_FORMAT_2S16) pad->pad_SampleRates[index++] = 22050.0; 256 if( caps.dwFormats & WAVE_FORMAT_4S16) pad->pad_SampleRates[index++] = 44100.0; 257 pad->pad_Info.numSampleRates = index; 258 } 259 else if( caps.dwChannels == 1 ) 260 { 261 int index = 0; 262 if( caps.dwFormats & WAVE_FORMAT_1M16) pad->pad_SampleRates[index++] = 11025.0; 263 if( caps.dwFormats & WAVE_FORMAT_2M16) pad->pad_SampleRates[index++] = 22050.0; 264 if( caps.dwFormats & WAVE_FORMAT_4M16) pad->pad_SampleRates[index++] = 44100.0; 265 pad->pad_Info.numSampleRates = index; 266 } 267 else pad->pad_Info.numSampleRates = 0; 268 IDirectSoundCapture_Release( lpDirectSoundCapture ); 269 } 270 } 271 else 272 #endif /* SUPPORT_AUDIO_CAPTURE */ 273 { 274 /********** Output ******************************/ 275 DSCAPS caps; 276 if( lpGUID == NULL ) sDefaultOutputDeviceID = sDeviceIndex; 277 /* Create interfaces for each object. */ 278 hr = DirectSoundCreate( lpGUID, &lpDirectSound, NULL ); 279 if( hr != DS_OK ) 280 { 281 pad->pad_Info.maxOutputChannels = 0; 282 DBUG(("Cannot create dsound for %s. Result = 0x%x\n", lpszDesc, hr )); 283 } 284 else 285 { 286 /* Query device characteristics. */ 287 caps.dwSize = sizeof(caps); 288 IDirectSound_GetCaps( lpDirectSound, &caps ); 289 pad->pad_Info.maxOutputChannels = ( caps.dwFlags & DSCAPS_PRIMARYSTEREO ) ? 2 : 1; 290 /* Get sample rates. */ 291 pad->pad_SampleRates[0] = (double) caps.dwMinSecondarySampleRate; 292 pad->pad_SampleRates[1] = (double) caps.dwMaxSecondarySampleRate; 293 if( caps.dwFlags & DSCAPS_CONTINUOUSRATE ) pad->pad_Info.numSampleRates = -1; 294 else if( caps.dwMinSecondarySampleRate == caps.dwMaxSecondarySampleRate ) 295 { 296 if( caps.dwMinSecondarySampleRate == 0 ) 297 { 298 /* 299 ** On my Thinkpad 380Z, DirectSoundV6 returns min-max=0 !! 300 ** But it supports continuous sampling. 301 ** So fake range of rates, and hope it really supports it. 302 */ 303 pad->pad_SampleRates[0] = 11025.0f; 304 pad->pad_SampleRates[1] = 48000.0f; 305 pad->pad_Info.numSampleRates = -1; /* continuous range */ 306 307 DBUG(("PA - Reported rates both zero. Setting to fake values for device #%d\n", sDeviceIndex )); 308 } 309 else 310 { 311 pad->pad_Info.numSampleRates = 1; 312 } 313 } 314 else if( (caps.dwMinSecondarySampleRate < 1000.0) && (caps.dwMaxSecondarySampleRate > 50000.0) ) 315 { 316 /* The EWS88MT drivers lie, lie, lie. The say they only support two rates, 100 & 100000. 317 ** But we know that they really support a range of rates! 318 ** So when we see a ridiculous set of rates, assume it is a range. 319 */ 320 pad->pad_Info.numSampleRates = -1; 321 DBUG(("PA - Sample rate range used instead of two odd values for device #%d\n", sDeviceIndex )); 322 } 323 else pad->pad_Info.numSampleRates = 2; 324 IDirectSound_Release( lpDirectSound ); 325 } 326 } 327 pad->pad_Info.nativeSampleFormats = paInt16; 328 sDeviceIndex++; 329 return( TRUE ); 330 } 331 /*************************************************************************/ 332 int Pa_CountDevices() 333 { 334 if( sNumDevices <= 0 ) Pa_Initialize(); 335 return sNumDevices; 336 } 337 static internalPortAudioDevice *Pa_GetInternalDevice( PaDeviceID id ) 338 { 339 if( (id < 0) || ( id >= Pa_CountDevices()) ) return NULL; 340 return &sDevices[id]; 341 } 342 /*************************************************************************/ 343 const PaDeviceInfo* Pa_GetDeviceInfo( PaDeviceID id ) 344 { 345 internalPortAudioDevice *pad; 346 if( (id < 0) || ( id >= Pa_CountDevices()) ) return NULL; 347 pad = Pa_GetInternalDevice( id ); 348 return &pad->pad_Info ; 349 } 350 static PaError Pa_MaybeQueryDevices( void ) 351 { 352 if( sNumDevices == 0 ) 353 { 354 return Pa_QueryDevices(); 355 } 356 return 0; 357 } 358 /************************************************************************* 359 ** Returns recommended device ID. 360 ** On the PC, the recommended device can be specified by the user by 361 ** setting an environment variable. For example, to use device #1. 362 ** 363 ** set PA_RECOMMENDED_OUTPUT_DEVICE=1 364 ** 365 ** The user should first determine the available device ID by using 366 ** the supplied application "pa_devs". 367 */ 368 #define PA_ENV_BUF_SIZE (32) 369 #define PA_REC_IN_DEV_ENV_NAME ("PA_RECOMMENDED_INPUT_DEVICE") 370 #define PA_REC_OUT_DEV_ENV_NAME ("PA_RECOMMENDED_OUTPUT_DEVICE") 371 static PaDeviceID PaHost_GetEnvDefaultDeviceID( char *envName ) 372 { 373 DWORD hresult; 374 char envbuf[PA_ENV_BUF_SIZE]; 375 PaDeviceID recommendedID = paNoDevice; 376 /* Let user determine default device by setting environment variable. */ 377 hresult = GetEnvironmentVariable( envName, envbuf, PA_ENV_BUF_SIZE ); 378 if( (hresult > 0) && (hresult < PA_ENV_BUF_SIZE) ) 379 { 380 recommendedID = atoi( envbuf ); 381 } 382 return recommendedID; 383 } 384 PaDeviceID Pa_GetDefaultInputDeviceID( void ) 385 { 386 PaError result; 387 result = PaHost_GetEnvDefaultDeviceID( PA_REC_IN_DEV_ENV_NAME ); 388 if( result < 0 ) 389 { 390 result = Pa_MaybeQueryDevices(); 391 if( result < 0 ) return result; 392 result = sDefaultInputDeviceID; 393 } 394 return result; 395 } 396 PaDeviceID Pa_GetDefaultOutputDeviceID( void ) 397 { 398 PaError result; 399 result = PaHost_GetEnvDefaultDeviceID( PA_REC_OUT_DEV_ENV_NAME ); 400 if( result < 0 ) 401 { 402 result = Pa_MaybeQueryDevices(); 403 if( result < 0 ) return result; 404 result = sDefaultOutputDeviceID; 405 } 406 return result; 407 } 408 /********************************************************************** 409 ** Make sure that we have queried the device capabilities. 410 */ 411 PaError PaHost_Init( void ) 412 { 413 #if PA_SIMULATE_UNDERFLOW 414 PRINT(("WARNING - Underflow Simulation Enabled - Expect a Big Glitch!!!\n")); 415 #endif 416 return Pa_MaybeQueryDevices(); 417 } 418 static PaError Pa_TimeSlice( internalPortAudioStream *past ) 419 { 420 PaError result = 0; 421 long bytesEmpty = 0; 422 long bytesFilled = 0; 423 long bytesToXfer = 0; 424 long numChunks; 425 HRESULT hresult; 426 PaHostSoundControl *pahsc; 427 short *nativeBufPtr; 428 past->past_NumCallbacks += 1; 429 pahsc = (PaHostSoundControl *) past->past_DeviceData; 430 if( pahsc == NULL ) return paInternalError; 431 /* How much input data is available? */ 432 #if SUPPORT_AUDIO_CAPTURE 433 if( past->past_NumInputChannels > 0 ) 434 { 435 DSW_QueryInputFilled( &pahsc->pahsc_DSoundWrapper, &bytesFilled ); 436 bytesToXfer = bytesFilled; 437 } 438 #endif /* SUPPORT_AUDIO_CAPTURE */ 439 /* How much output room is available? */ 440 if( past->past_NumOutputChannels > 0 ) 441 { 442 DSW_QueryOutputSpace( &pahsc->pahsc_DSoundWrapper, &bytesEmpty ); 443 bytesToXfer = bytesEmpty; 444 } 445 AddTraceMessage( "bytesEmpty ", bytesEmpty ); 446 /* Choose smallest value if both are active. */ 447 if( (past->past_NumInputChannels > 0) && (past->past_NumOutputChannels > 0) ) 448 { 449 bytesToXfer = ( bytesFilled < bytesEmpty ) ? bytesFilled : bytesEmpty; 450 } 451 /* printf("bytesFilled = %d, bytesEmpty = %d, bytesToXfer = %d\n", 452 bytesFilled, bytesEmpty, bytesToXfer); 453 */ 454 /* Quantize to multiples of a buffer. */ 455 numChunks = bytesToXfer / pahsc->pahsc_BytesPerBuffer; 456 if( numChunks > (long)(past->past_NumUserBuffers/2) ) 457 { 458 numChunks = (long)past->past_NumUserBuffers/2; 459 } 460 else if( numChunks < 0 ) 461 { 462 numChunks = 0; 463 } 464 AddTraceMessage( "numChunks ", numChunks ); 465 nativeBufPtr = pahsc->pahsc_NativeBuffer; 466 if( numChunks > 0 ) 467 { 468 while( numChunks-- > 0 ) 469 { 470 /* Measure usage based on time to process one user buffer. */ 471 Pa_StartUsageCalculation( past ); 472 #if SUPPORT_AUDIO_CAPTURE 473 /* Get native data from DirectSound. */ 474 if( past->past_NumInputChannels > 0 ) 475 { 476 hresult = DSW_ReadBlock( &pahsc->pahsc_DSoundWrapper, (char *) nativeBufPtr, pahsc->pahsc_BytesPerBuffer ); 477 if( hresult < 0 ) 478 { 479 ERR_RPT(("DirectSound ReadBlock failed, hresult = 0x%x\n",hresult)); 480 sPaHostError = hresult; 481 break; 482 } 483 } 484 #endif /* SUPPORT_AUDIO_CAPTURE */ 485 /* Convert 16 bit native data to user data and call user routine. */ 486 result = Pa_CallConvertInt16( past, nativeBufPtr, nativeBufPtr ); 487 if( result != 0) break; 488 /* Pass native data to DirectSound. */ 489 if( past->past_NumOutputChannels > 0 ) 490 { 491 /* static short DEBUGHACK = 0; 492 DEBUGHACK += 0x0049; 493 nativeBufPtr[0] = DEBUGHACK; /* Make buzz to see if DirectSound still running. */ 494 hresult = DSW_WriteBlock( &pahsc->pahsc_DSoundWrapper, (char *) nativeBufPtr, pahsc->pahsc_BytesPerBuffer ); 495 if( hresult < 0 ) 496 { 497 ERR_RPT(("DirectSound WriteBlock failed, result = 0x%x\n",hresult)); 498 sPaHostError = hresult; 499 break; 500 } 501 } 502 Pa_EndUsageCalculation( past ); 503 } 504 } 505 return result; 506 } 507 /*******************************************************************/ 508 static void CALLBACK Pa_TimerCallback(UINT uID, UINT uMsg, DWORD dwUser, DWORD dw1, DWORD dw2) 509 { 510 internalPortAudioStream *past; 511 PaHostSoundControl *pahsc; 512 #if PA_SIMULATE_UNDERFLOW 513 gUnderCallbackCounter++; 514 if( (gUnderCallbackCounter >= UNDER_START_GAP) && 515 (gUnderCallbackCounter <= UNDER_STOP_GAP) ) 516 { 517 if( gUnderCallbackCounter == UNDER_START_GAP) 518 { 519 AddTraceMessage("Begin stall: gUnderCallbackCounter =======", gUnderCallbackCounter ); 520 } 521 if( gUnderCallbackCounter == UNDER_STOP_GAP) 522 { 523 AddTraceMessage("End stall: gUnderCallbackCounter =======", gUnderCallbackCounter ); 524 } 525 return; 526 } 527 #endif 528 past = (internalPortAudioStream *) dwUser; 529 if( past == NULL ) return; 530 pahsc = (PaHostSoundControl *) past->past_DeviceData; 531 if( pahsc == NULL ) return; 532 if( !pahsc->pahsc_IfInsideCallback && past->past_IsActive ) 533 { 534 if( past->past_StopNow ) 535 { 536 past->past_IsActive = 0; 537 } 538 else if( past->past_StopSoon ) 539 { 540 DSoundWrapper *dsw = &pahsc->pahsc_DSoundWrapper; 541 if( past->past_NumOutputChannels > 0 ) 542 { 543 DSW_ZeroEmptySpace( dsw ); 544 AddTraceMessage("Pa_TimerCallback: waiting - written ", (int) dsw->dsw_FramesWritten ); 545 AddTraceMessage("Pa_TimerCallback: waiting - played ", (int) dsw->dsw_FramesPlayed ); 546 /* clear past_IsActive when all sound played */ 547 if( dsw->dsw_FramesPlayed >= past->past_FrameCount ) 548 { 549 past->past_IsActive = 0; 550 } 551 } 552 else 553 { 554 past->past_IsActive = 0; 555 } 556 } 557 else 558 { 559 pahsc->pahsc_IfInsideCallback = 1; 560 if( Pa_TimeSlice( past ) != 0) /* Call time slice independant of timing method. */ 561 { 562 past->past_StopSoon = 1; 563 } 564 pahsc->pahsc_IfInsideCallback = 0; 565 } 566 } 567 } 568 /*******************************************************************/ 569 PaError PaHost_OpenStream( internalPortAudioStream *past ) 570 { 571 HRESULT hr; 572 PaError result = paNoError; 573 PaHostSoundControl *pahsc; 574 int numBytes, maxChannels; 575 unsigned int minNumBuffers; 576 internalPortAudioDevice *pad; 577 DSoundWrapper *dsw; 578 /* Allocate and initialize host data. */ 579 pahsc = (PaHostSoundControl *) PaHost_AllocateFastMemory(sizeof(PaHostSoundControl)); /* MEM */ 580 if( pahsc == NULL ) 581 { 582 result = paInsufficientMemory; 583 goto error; 584 } 585 memset( pahsc, 0, sizeof(PaHostSoundControl) ); 586 past->past_DeviceData = (void *) pahsc; 587 pahsc->pahsc_TimerID = 0; 588 dsw = &pahsc->pahsc_DSoundWrapper; 589 DSW_Init( dsw ); 590 /* Allocate native buffer. */ 591 maxChannels = ( past->past_NumOutputChannels > past->past_NumInputChannels ) ? 592 past->past_NumOutputChannels : past->past_NumInputChannels; 593 pahsc->pahsc_BytesPerBuffer = past->past_FramesPerUserBuffer * maxChannels * sizeof(short); 594 if( maxChannels > 0 ) 595 { 596 pahsc->pahsc_NativeBuffer = (short *) PaHost_AllocateFastMemory(pahsc->pahsc_BytesPerBuffer); /* MEM */ 597 if( pahsc->pahsc_NativeBuffer == NULL ) 598 { 599 result = paInsufficientMemory; 600 goto error; 601 } 602 } 603 else 604 { 605 result = paInvalidChannelCount; 606 goto error; 607 } 608 609 DBUG(("PaHost_OpenStream: pahsc_MinFramesPerHostBuffer = %d\n", pahsc->pahsc_MinFramesPerHostBuffer )); 610 minNumBuffers = Pa_GetMinNumBuffers( past->past_FramesPerUserBuffer, past->past_SampleRate ); 611 past->past_NumUserBuffers = ( minNumBuffers > past->past_NumUserBuffers ) ? minNumBuffers : past->past_NumUserBuffers; 612 numBytes = pahsc->pahsc_BytesPerBuffer * past->past_NumUserBuffers; 613 if( numBytes < DSBSIZE_MIN ) 614 { 615 result = paBufferTooSmall; 616 goto error; 617 } 618 if( numBytes > DSBSIZE_MAX ) 619 { 620 result = paBufferTooBig; 621 goto error; 622 } 623 pahsc->pahsc_FramesPerDSBuffer = past->past_FramesPerUserBuffer * past->past_NumUserBuffers; 624 { 625 int msecLatency = (int) ((pahsc->pahsc_FramesPerDSBuffer * 1000) / past->past_SampleRate); 626 PRINT(("PortAudio on DirectSound - Latency = %d frames, %d msec\n", pahsc->pahsc_FramesPerDSBuffer, msecLatency )); 627 } 628 /* ------------------ OUTPUT */ 629 if( (past->past_OutputDeviceID >= 0) && (past->past_NumOutputChannels > 0) ) 630 { 631 DBUG(("PaHost_OpenStream: deviceID = 0x%x\n", past->past_OutputDeviceID)); 632 pad = Pa_GetInternalDevice( past->past_OutputDeviceID ); 633 hr = DirectSoundCreate( pad->pad_lpGUID, &dsw->dsw_pDirectSound, NULL ); 634 /* If this fails, then try each output device until we find one that works. */ 635 if( hr != DS_OK ) 636 { 637 int i; 638 ERR_RPT(("Creation of requested Audio Output device '%s' failed.\n", 639 ((pad->pad_lpGUID == NULL) ? "Default" : pad->pad_Info.name) )); 640 for( i=0; i<Pa_CountDevices(); i++ ) 641 { 642 pad = Pa_GetInternalDevice( i ); 643 if( pad->pad_Info.maxOutputChannels >= past->past_NumOutputChannels ) 644 { 645 DBUG(("Try device '%s' instead.\n", pad->pad_Info.name )); 646 hr = DirectSoundCreate( pad->pad_lpGUID, &dsw->dsw_pDirectSound, NULL ); 647 if( hr == DS_OK ) 648 { 649 ERR_RPT(("Using device '%s' instead.\n", pad->pad_Info.name )); 650 break; 651 } 652 } 653 } 654 } 655 if( hr != DS_OK ) 656 { 657 ERR_RPT(("PortAudio: DirectSoundCreate() failed!\n")); 658 result = paHostError; 659 sPaHostError = hr; 660 goto error; 661 } 662 hr = DSW_InitOutputBuffer( dsw, 663 (unsigned long) (past->past_SampleRate + 0.5), 664 past->past_NumOutputChannels, numBytes ); 665 DBUG(("DSW_InitOutputBuffer() returns %x\n", hr)); 666 if( hr != DS_OK ) 667 { 668 result = paHostError; 669 sPaHostError = hr; 670 goto error; 671 } 672 past->past_FrameCount = pahsc->pahsc_DSoundWrapper.dsw_FramesWritten; 673 } 674 #if SUPPORT_AUDIO_CAPTURE 675 /* ------------------ INPUT */ 676 if( (past->past_InputDeviceID >= 0) && (past->past_NumInputChannels > 0) ) 677 { 678 pad = Pa_GetInternalDevice( past->past_InputDeviceID ); 679 hr = DirectSoundCaptureCreate( pad->pad_lpGUID, &dsw->dsw_pDirectSoundCapture, NULL ); 680 /* If this fails, then try each input device until we find one that works. */ 681 if( hr != DS_OK ) 682 { 683 int i; 684 ERR_RPT(("Creation of requested Audio Capture device '%s' failed.\n", 685 ((pad->pad_lpGUID == NULL) ? "Default" : pad->pad_Info.name) )); 686 for( i=0; i<Pa_CountDevices(); i++ ) 687 { 688 pad = Pa_GetInternalDevice( i ); 689 if( pad->pad_Info.maxInputChannels >= past->past_NumInputChannels ) 690 { 691 PRINT(("Try device '%s' instead.\n", pad->pad_Info.name )); 692 hr = DirectSoundCaptureCreate( pad->pad_lpGUID, &dsw->dsw_pDirectSoundCapture, NULL ); 693 if( hr == DS_OK ) break; 694 } 695 } 696 } 697 if( hr != DS_OK ) 698 { 699 ERR_RPT(("PortAudio: DirectSoundCaptureCreate() failed!\n")); 700 result = paHostError; 701 sPaHostError = hr; 702 goto error; 703 } 704 hr = DSW_InitInputBuffer( dsw, 705 (unsigned long) (past->past_SampleRate + 0.5), 706 past->past_NumInputChannels, numBytes ); 707 DBUG(("DSW_InitInputBuffer() returns %x\n", hr)); 708 if( hr != DS_OK ) 709 { 710 ERR_RPT(("PortAudio: DSW_InitInputBuffer() returns %x\n", hr)); 711 result = paHostError; 712 sPaHostError = hr; 713 goto error; 714 } 715 } 716 #endif /* SUPPORT_AUDIO_CAPTURE */ 717 /* Calculate scalar used in CPULoad calculation. */ 718 { 719 LARGE_INTEGER frequency; 720 if( QueryPerformanceFrequency( &frequency ) == 0 ) 721 { 722 pahsc->pahsc_InverseTicksPerUserBuffer = 0.0; 723 } 724 else 725 { 726 pahsc->pahsc_InverseTicksPerUserBuffer = past->past_SampleRate / 727 ( (double)frequency.QuadPart * past->past_FramesPerUserBuffer ); 728 DBUG(("pahsc_InverseTicksPerUserBuffer = %g\n", pahsc->pahsc_InverseTicksPerUserBuffer )); 729 } 730 } 731 return result; 732 error: 733 PaHost_CloseStream( past ); 734 return result; 735 } 736 /*************************************************************************/ 737 PaError PaHost_StartOutput( internalPortAudioStream *past ) 738 { 739 HRESULT hr; 740 PaHostSoundControl *pahsc; 741 PaError result = paNoError; 742 pahsc = (PaHostSoundControl *) past->past_DeviceData; 743 /* Give user callback a chance to pre-fill buffer. */ 744 result = Pa_TimeSlice( past ); 745 if( result != paNoError ) return result; // FIXME - what if finished? 746 hr = DSW_StartOutput( &pahsc->pahsc_DSoundWrapper ); 747 DBUG(("PaHost_StartOutput: DSW_StartOutput returned = 0x%X.\n", hr)); 748 if( hr != DS_OK ) 749 { 750 result = paHostError; 751 sPaHostError = hr; 752 goto error; 753 } 754 error: 755 return result; 756 } 757 /*************************************************************************/ 758 PaError PaHost_StartInput( internalPortAudioStream *past ) 759 { 760 PaError result = paNoError; 761 #if SUPPORT_AUDIO_CAPTURE 762 HRESULT hr; 763 PaHostSoundControl *pahsc; 764 pahsc = (PaHostSoundControl *) past->past_DeviceData; 765 hr = DSW_StartInput( &pahsc->pahsc_DSoundWrapper ); 766 DBUG(("Pa_StartStream: DSW_StartInput returned = 0x%X.\n", hr)); 767 if( hr != DS_OK ) 768 { 769 result = paHostError; 770 sPaHostError = hr; 771 goto error; 772 } 773 error: 774 #endif /* SUPPORT_AUDIO_CAPTURE */ 775 return result; 776 } 777 /*************************************************************************/ 778 PaError PaHost_StartEngine( internalPortAudioStream *past ) 779 { 780 PaHostSoundControl *pahsc; 781 PaError result = paNoError; 782 pahsc = (PaHostSoundControl *) past->past_DeviceData; 783 past->past_StopNow = 0; 784 past->past_StopSoon = 0; 785 past->past_IsActive = 1; 786 /* Create timer that will wake us up so we can fill the DSound buffer. */ 787 { 788 int msecPerBuffer; 789 int resolution; 790 int bufsPerInterrupt = past->past_NumUserBuffers/4; 791 if( bufsPerInterrupt < 1 ) bufsPerInterrupt = 1; 792 msecPerBuffer = 1000 * (bufsPerInterrupt * past->past_FramesPerUserBuffer) / (int) past->past_SampleRate; 793 if( msecPerBuffer < 10 ) msecPerBuffer = 10; 794 else if( msecPerBuffer > 100 ) msecPerBuffer = 100; 795 resolution = msecPerBuffer/4; 796 pahsc->pahsc_TimerID = timeSetEvent( msecPerBuffer, resolution, (LPTIMECALLBACK) Pa_TimerCallback, 797 (DWORD) past, TIME_PERIODIC ); 798 } 799 if( pahsc->pahsc_TimerID == 0 ) 800 { 801 past->past_IsActive = 0; 802 result = paHostError; 803 sPaHostError = 0; 804 goto error; 805 } 806 error: 807 return result; 808 } 809 /*************************************************************************/ 810 PaError PaHost_StopEngine( internalPortAudioStream *past, int abort ) 811 { 812 int timeoutMsec; 813 PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; 814 if( pahsc == NULL ) return paNoError; 815 if( abort ) past->past_StopNow = 1; 816 past->past_StopSoon = 1; 817 /* Set timeout at 20% beyond maximum time we might wait. */ 818 timeoutMsec = (int) (1200.0 * pahsc->pahsc_FramesPerDSBuffer / past->past_SampleRate); 819 while( past->past_IsActive && (timeoutMsec > 0) ) 820 { 821 Sleep(10); 822 timeoutMsec -= 10; 823 } 824 if( pahsc->pahsc_TimerID != 0 ) 825 { 826 timeKillEvent(pahsc->pahsc_TimerID); /* Stop callback timer. */ 827 pahsc->pahsc_TimerID = 0; 828 } 829 return paNoError; 830 } 831 /*************************************************************************/ 832 PaError PaHost_StopInput( internalPortAudioStream *past, int abort ) 833 { 834 #if SUPPORT_AUDIO_CAPTURE 835 HRESULT hr; 836 PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; 837 if( pahsc == NULL ) return paNoError; 838 (void) abort; 839 hr = DSW_StopInput( &pahsc->pahsc_DSoundWrapper ); 840 DBUG(("DSW_StopInput() result is %x\n", hr)); 841 #endif /* SUPPORT_AUDIO_CAPTURE */ 842 return paNoError; 843 } 844 /*************************************************************************/ 845 PaError PaHost_StopOutput( internalPortAudioStream *past, int abort ) 846 { 847 HRESULT hr; 848 PaHostSoundControl *pahsc; 849 pahsc = (PaHostSoundControl *) past->past_DeviceData; 850 if( pahsc == NULL ) return paNoError; 851 (void) abort; 852 hr = DSW_StopOutput( &pahsc->pahsc_DSoundWrapper ); 853 DBUG(("DSW_StopOutput() result is %x\n", hr)); 854 return paNoError; 855 } 856 /*******************************************************************/ 857 PaError PaHost_CloseStream( internalPortAudioStream *past ) 858 { 859 PaHostSoundControl *pahsc; 860 if( past == NULL ) return paBadStreamPtr; 861 pahsc = (PaHostSoundControl *) past->past_DeviceData; 862 if( pahsc == NULL ) return paNoError; 863 DSW_Term( &pahsc->pahsc_DSoundWrapper ); 864 if( pahsc->pahsc_NativeBuffer ) 865 { 866 PaHost_FreeFastMemory( pahsc->pahsc_NativeBuffer, pahsc->pahsc_BytesPerBuffer ); /* MEM */ 867 pahsc->pahsc_NativeBuffer = NULL; 868 } 869 PaHost_FreeFastMemory( pahsc, sizeof(PaHostSoundControl) ); /* MEM */ 870 past->past_DeviceData = NULL; 871 return paNoError; 872 } 873 /************************************************************************* 874 ** Determine minimum number of buffers required for this host based 875 ** on minimum latency. Latency can be optionally set by user by setting 876 ** an environment variable. For example, to set latency to 200 msec, put: 877 ** 878 ** set PA_MIN_LATENCY_MSEC=200 879 ** 880 ** in the AUTOEXEC.BAT file and reboot. 881 ** If the environment variable is not set, then the latency will be determined 882 ** based on the OS. Windows NT has higher latency than Win95. 883 */ 884 #define PA_LATENCY_ENV_NAME ("PA_MIN_LATENCY_MSEC") 885 int Pa_GetMinNumBuffers( int framesPerBuffer, double sampleRate ) 886 { 887 char envbuf[PA_ENV_BUF_SIZE]; 888 DWORD hostVersion; 889 DWORD hresult; 890 int minLatencyMsec = 0; 891 double msecPerBuffer = (1000.0 * framesPerBuffer) / sampleRate; 892 int minBuffers; 893 /* Let user determine minimal latency by setting environment variable. */ 894 hresult = GetEnvironmentVariable( PA_LATENCY_ENV_NAME, envbuf, PA_ENV_BUF_SIZE ); 895 if( (hresult > 0) && (hresult < PA_ENV_BUF_SIZE) ) 896 { 897 minLatencyMsec = atoi( envbuf ); 898 } 899 else 900 { 901 /* Set minimal latency based on whether NT or Win95. 902 * NT has higher latency. 903 */ 904 hostVersion = GetVersion(); 905 /* High bit clear if NT */ 906 minLatencyMsec = ( (hostVersion & 0x80000000) == 0 ) ? PA_WIN_NT_LATENCY : PA_WIN_9X_LATENCY ; 907 #if PA_USE_HIGH_LATENCY 908 PRINT(("PA - Minimum Latency set to %d msec!\n", minLatencyMsec )); 909 #endif 910 } 911 minBuffers = (int) (1.0 + ((double)minLatencyMsec / msecPerBuffer)); 912 if( minBuffers < 2 ) minBuffers = 2; 913 return minBuffers; 914 } 915 /*************************************************************************/ 916 PaError PaHost_Term( void ) 917 { 918 int i; 919 /* Free names allocated during enumeration. */ 920 for( i=0; i<sNumDevices; i++ ) 921 { 922 if( sDevices[i].pad_Info.name != NULL ) 923 { 924 free( (void *) sDevices[i].pad_Info.name ); 925 sDevices[i].pad_Info.name = NULL; 926 } 927 } 928 if( sDevices != NULL ) 929 { 930 PaHost_FreeFastMemory( sDevices, sNumDevices * sizeof(internalPortAudioDevice) ); /* MEM */ 931 sDevices = NULL; 932 sNumDevices = 0; 933 } 934 return 0; 935 } 936 void Pa_Sleep( long msec ) 937 { 938 Sleep( msec ); 939 } 940 /************************************************************************* 941 * Allocate memory that can be accessed in real-time. 942 * This may need to be held in physical memory so that it is not 943 * paged to virtual memory. 944 * This call MUST be balanced with a call to PaHost_FreeFastMemory(). 945 * Memory will be set to zero. 946 */ 947 void *PaHost_AllocateFastMemory( long numBytes ) 948 { 949 void *addr = GlobalAlloc( GPTR, numBytes ); /* FIXME - do we need physical memory? Use VirtualLock() */ /* MEM */ 950 return addr; 951 } 952 /************************************************************************* 953 * Free memory that could be accessed in real-time. 954 * This call MUST be balanced with a call to PaHost_AllocateFastMemory(). 955 */ 956 void PaHost_FreeFastMemory( void *addr, long numBytes ) 957 { 958 if( addr != NULL ) GlobalFree( addr ); /* MEM */ 959 } 960 /***********************************************************************/ 961 PaError PaHost_StreamActive( internalPortAudioStream *past ) 962 { 963 PaHostSoundControl *pahsc; 964 if( past == NULL ) return paBadStreamPtr; 965 pahsc = (PaHostSoundControl *) past->past_DeviceData; 966 if( pahsc == NULL ) return paInternalError; 967 return (PaError) (past->past_IsActive); 968 } 969 /*************************************************************************/ 970 PaTimestamp Pa_StreamTime( PortAudioStream *stream ) 971 { 972 DSoundWrapper *dsw; 973 internalPortAudioStream *past = (internalPortAudioStream *) stream; 974 PaHostSoundControl *pahsc; 975 if( past == NULL ) return paBadStreamPtr; 976 pahsc = (PaHostSoundControl *) past->past_DeviceData; 977 dsw = &pahsc->pahsc_DSoundWrapper; 978 return dsw->dsw_FramesPlayed; 979 } 980