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