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