1 /*
2 * $Id: pa_win_wmme.c,v 1.1.1.1 2007/09/28 02:50:56 sumomo Exp $
3 * pa_win_wmme.c
4 * Implementation of PortAudio for Windows MultiMedia Extensions (WMME)
5 *
6 * PortAudio Portable Real-Time Audio Library
7 * Latest Version at: http://www.portaudio.com
8 *
9 * Authors: Ross Bencina and Phil Burk
10 * Copyright (c) 1999-2000 Ross Bencina and Phil Burk
11 *
12 * Permission is hereby granted, free of charge, to any person obtaining
13 * a copy of this software and associated documentation files
14 * (the "Software"), to deal in the Software without restriction,
15 * including without limitation the rights to use, copy, modify, merge,
16 * publish, distribute, sublicense, and/or sell copies of the Software,
17 * and to permit persons to whom the Software is furnished to do so,
18 * subject to the following conditions:
19 *
20 * The above copyright notice and this permission notice shall be
21 * included in all copies or substantial portions of the Software.
22 *
23 * Any person wishing to distribute modifications to the Software is
24 * requested to send the modifications to the original developer so that
25 * they can be incorporated into the canonical version.
26 *
27 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
28 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
29 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
30 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
31 * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
32 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
33 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
34 *
35 */
36 /*
37 All memory allocations and frees are marked with MEM for quick review.
38 */
39
40 /* Modification History:
41 PLB = Phil Burk
42 JM = Julien Maillard
43 RDB = Ross Bencina
44 PLB20010402 - sDevicePtrs now allocates based on sizeof(pointer)
45 PLB20010413 - check for excessive numbers of channels
46 PLB20010422 - apply Mike Berry's changes for CodeWarrior on PC
47 including condition including of memory.h,
48 and explicit typecasting on memory allocation
49 PLB20010802 - use GlobalAlloc for sDevicesPtr instead of PaHost_AllocFastMemory
50 PLB20010816 - pass process instead of thread to SetPriorityClass()
51 PLB20010927 - use number of frames instead of real-time for CPULoad calculation.
52 JM20020118 - prevent hung thread when buffers underflow.
53 PLB20020321 - detect Win XP versus NT, 9x; fix DBUG typo; removed init of CurrentCount
54 RDB20020411 - various renaming cleanups, factored streamData alloc and cpu usage init
55 RDB20020417 - stopped counting WAVE_MAPPER when there were no real devices
56 refactoring, renaming and fixed a few edge case bugs
57 PLB20020612 - added 8000.0 Hz to custom sampling rates array
58 */
59 #pragma warning (disable: 4115)
60 #include <stdio.h>
61 #include <stdlib.h>
62 #include <math.h>
63 #include <windows.h>
64 #include <mmsystem.h>
65 #include <process.h>
66 /* PLB20010422 - "memory.h" doesn't work on CodeWarrior for PC. Thanks Mike Berry for the mod. */
67 #ifndef __MWERKS__
68 #include <malloc.h>
69 #include <memory.h>
70 #endif /* __MWERKS__ */
71 #include "portaudio.h"
72 #include "pa_host.h"
73 #include "pa_trace.h"
74
75 /************************************************* Constants ********/
76 #define PA_TRACK_MEMORY (0)
77
78 #define PA_USE_TIMER_CALLBACK (0) /* Select between two options for background task. 0=thread, 1=timer */
79 /* Switches for debugging. */
80 #define PA_SIMULATE_UNDERFLOW (0) /* Set to one to force an underflow of the output buffer. */
81
82 /* To trace program, enable TRACE_REALTIME_EVENTS in pa_trace.h */
83 #define PA_TRACE_RUN (0)
84 #define PA_TRACE_START_STOP (1)
85
86 #define PA_USE_HIGH_LATENCY (0) /* For debugging glitches. */
87
88 #if PA_USE_HIGH_LATENCY
89 #define PA_MIN_MSEC_PER_HOST_BUFFER (100)
90 #define PA_MAX_MSEC_PER_HOST_BUFFER (300) /* Do not exceed unless user buffer exceeds */
91 #define PA_MIN_NUM_HOST_BUFFERS (4)
92 #define PA_MAX_NUM_HOST_BUFFERS (16) /* OK to exceed if necessary */
93 #define PA_WIN_9X_LATENCY (400)
94 #else
95 #define PA_MIN_MSEC_PER_HOST_BUFFER (10)
96 #define PA_MAX_MSEC_PER_HOST_BUFFER (100) /* Do not exceed unless user buffer exceeds */
97 #define PA_MIN_NUM_HOST_BUFFERS (3)
98 #define PA_MAX_NUM_HOST_BUFFERS (16) /* OK to exceed if necessary */
99 #define PA_WIN_9X_LATENCY (200)
100 #endif
101 #define MIN_TIMEOUT_MSEC (1000)
102
103 /*
104 ** Use higher latency for NT because it is even worse at real-time
105 ** operation than Win9x.
106 */
107 #define PA_WIN_NT_LATENCY (PA_WIN_9X_LATENCY * 2)
108 #define PA_WIN_WDM_LATENCY (PA_WIN_9X_LATENCY)
109
110 #if PA_SIMULATE_UNDERFLOW
111 static gUnderCallbackCounter = 0;
112 #define UNDER_SLEEP_AT (40)
113 #define UNDER_SLEEP_FOR (500)
114 #endif
115
116 #define PRINT(x) { printf x; fflush(stdout); }
117 #define ERR_RPT(x) PRINT(x)
118 #define DBUG(x) /* PRINT(x) */
119 #define DBUGX(x) /* PRINT(x) */
120 /************************************************* Definitions ********/
121 /**************************************************************
122 * Structure for internal host specific stream data.
123 * This is allocated on a per stream basis.
124 */
125 typedef struct PaWMMEStreamData
126 {
127 /* Input -------------- */
128 HWAVEIN hWaveIn;
129 WAVEHDR *inputBuffers;
130 int currentInputBuffer;
131 int bytesPerHostInputBuffer;
132 int bytesPerUserInputBuffer; /* native buffer size in bytes */
133 /* Output -------------- */
134 HWAVEOUT hWaveOut;
135 WAVEHDR *outputBuffers;
136 int currentOutputBuffer;
137 int bytesPerHostOutputBuffer;
138 int bytesPerUserOutputBuffer; /* native buffer size in bytes */
139 /* Run Time -------------- */
140 PaTimestamp framesPlayed;
141 long lastPosition; /* used to track frames played. */
142 /* For measuring CPU utilization. */
143 LARGE_INTEGER entryCount;
144 double inverseTicksPerHostBuffer;
145 /* Init Time -------------- */
146 int numHostBuffers;
147 int framesPerHostBuffer;
148 int userBuffersPerHostBuffer;
149 CRITICAL_SECTION streamLock; /* Mutext to prevent threads from colliding. */
150 INT streamLockInited;
151 #if PA_USE_TIMER_CALLBACK
152 BOOL ifInsideCallback; /* Test for reentrancy. */
153 MMRESULT timerID;
154 #else
155 HANDLE abortEvent;
156 int abortEventInited;
157 HANDLE bufferEvent;
158 int bufferEventInited;
159 HANDLE engineThread;
160 DWORD engineThreadID;
161 #endif
162 }
163 PaWMMEStreamData;
164 /************************************************* Shared Data ********/
165 /* FIXME - put Mutex around this shared data. */
166 static int sNumInputDevices = 0;
167 static int sNumOutputDevices = 0;
168 static int sNumDevices = 0;
169 static PaDeviceInfo **sDevicePtrs = NULL;
170 static int sDefaultInputDeviceID = paNoDevice;
171 static int sDefaultOutputDeviceID = paNoDevice;
172 static int sPaHostError = 0;
173 static const char sMapperSuffixInput[] = " - Input";
174 static const char sMapperSuffixOutput[] = " - Output";
175
176 #if PA_TRACK_MEMORY
177 static int sNumAllocations = 0;
178 #endif
179
180 /************************************************* Macros ********/
181 /* Convert external PA ID to an internal ID that includes WAVE_MAPPER */
182 #define PaDeviceIdToWinId(id) (((id) < sNumInputDevices) ? (id - 1) : (id - sNumInputDevices - 1))
183 /************************************************* Prototypes **********/
184
185 void Pa_InitializeNumDevices( void );
186 PaError Pa_AllocateDevicePtrs( void );
187
188 static void CALLBACK Pa_TimerCallback(UINT uID, UINT uMsg,
189 DWORD dwUser, DWORD dw1, DWORD dw2);
190 PaError PaHost_GetTotalBufferFrames( internalPortAudioStream *past );
191 static PaError PaHost_UpdateStreamTime( PaWMMEStreamData *wmmeStreamData );
192 static PaError PaHost_BackgroundManager( internalPortAudioStream *past );
193
194 static void *PaHost_AllocateTrackedMemory( long numBytes );
195 static void PaHost_FreeTrackedMemory( void *addr );
196
197 /*******************************************************************/
PaHost_AllocateWMMEStreamData(internalPortAudioStream * stream)198 static PaError PaHost_AllocateWMMEStreamData( internalPortAudioStream *stream )
199 {
200 PaError result = paNoError;
201 PaWMMEStreamData *wmmeStreamData;
202
203 wmmeStreamData = (PaWMMEStreamData *) PaHost_AllocateFastMemory(sizeof(PaWMMEStreamData)); /* MEM */
204 if( wmmeStreamData == NULL )
205 {
206 result = paInsufficientMemory;
207 goto error;
208 }
209 memset( wmmeStreamData, 0, sizeof(PaWMMEStreamData) );
210 stream->past_DeviceData = (void *) wmmeStreamData;
211
212 return result;
213
214 error:
215 return result;
216 }
217
218 /*******************************************************************/
PaHost_FreeWMMEStreamData(internalPortAudioStream * internalStream)219 static void PaHost_FreeWMMEStreamData( internalPortAudioStream *internalStream )
220 {
221 PaWMMEStreamData *wmmeStreamData = (PaWMMEStreamData *) internalStream->past_DeviceData;
222
223 PaHost_FreeFastMemory( wmmeStreamData, sizeof(PaWMMEStreamData) ); /* MEM */
224 internalStream->past_DeviceData = NULL;
225 }
226 /*************************************************************************/
PaHost_GetWMMEStreamData(internalPortAudioStream * internalStream)227 static PaWMMEStreamData* PaHost_GetWMMEStreamData( internalPortAudioStream* internalStream )
228 {
229 PaWMMEStreamData *result = NULL;
230
231 if( internalStream != NULL )
232 {
233 result = (PaWMMEStreamData *) internalStream->past_DeviceData;
234 }
235 return result;
236 }
237 /********************************* BEGIN CPU UTILIZATION MEASUREMENT ****/
238 /* FIXME: the cpu usage code should be factored out into a common module */
Pa_InitializeCpuUsageScalar(internalPortAudioStream * stream)239 static void Pa_InitializeCpuUsageScalar( internalPortAudioStream *stream )
240 {
241 PaWMMEStreamData *wmmeStreamData = (PaWMMEStreamData *) stream->past_DeviceData;
242
243 LARGE_INTEGER frequency;
244 if( QueryPerformanceFrequency( &frequency ) == 0 )
245 {
246 wmmeStreamData->inverseTicksPerHostBuffer = 0.0;
247 }
248 else
249 {
250 wmmeStreamData->inverseTicksPerHostBuffer = stream->past_SampleRate /
251 ( (double)frequency.QuadPart * stream->past_FramesPerUserBuffer * wmmeStreamData->userBuffersPerHostBuffer );
252 DBUG(("inverseTicksPerHostBuffer = %g\n", wmmeStreamData->inverseTicksPerHostBuffer ));
253 }
254 }
Pa_StartUsageCalculation(internalPortAudioStream * stream)255 static void Pa_StartUsageCalculation( internalPortAudioStream *stream )
256 {
257 PaWMMEStreamData *wmmeStreamData = (PaWMMEStreamData *) stream->past_DeviceData;
258
259 if( wmmeStreamData == NULL ) return;
260 /* Query system timer for usage analysis and to prevent overuse of CPU. */
261 QueryPerformanceCounter( &wmmeStreamData->entryCount );
262 }
Pa_EndUsageCalculation(internalPortAudioStream * stream)263 static void Pa_EndUsageCalculation( internalPortAudioStream *stream )
264 {
265 LARGE_INTEGER CurrentCount;
266 PaWMMEStreamData *wmmeStreamData = (PaWMMEStreamData *) stream->past_DeviceData;
267
268 if( wmmeStreamData == NULL ) return;
269 /*
270 * Measure CPU utilization during this callback. Note that this calculation
271 * assumes that we had the processor the whole time.
272 */
273 #define LOWPASS_COEFFICIENT_0 (0.9)
274 #define LOWPASS_COEFFICIENT_1 (0.99999 - LOWPASS_COEFFICIENT_0)
275 if( QueryPerformanceCounter( &CurrentCount ) )
276 {
277 LONGLONG InsideCount = CurrentCount.QuadPart - wmmeStreamData->entryCount.QuadPart;
278 double newUsage = InsideCount * wmmeStreamData->inverseTicksPerHostBuffer;
279 stream->past_Usage = (LOWPASS_COEFFICIENT_0 * stream->past_Usage) +
280 (LOWPASS_COEFFICIENT_1 * newUsage);
281 }
282 }
283 /****************************************** END CPU UTILIZATION *******/
284
Pa_InitializeNumDevices(void)285 static void Pa_InitializeNumDevices( void )
286 {
287 sNumInputDevices = waveInGetNumDevs();
288 if( sNumInputDevices > 0 )
289 {
290 sNumInputDevices += 1; /* add one extra for the WAVE_MAPPER */
291 sDefaultInputDeviceID = 0;
292 }
293 else
294 {
295 sDefaultInputDeviceID = paNoDevice;
296 }
297
298 sNumOutputDevices = waveOutGetNumDevs();
299 if( sNumOutputDevices > 0 )
300 {
301 sNumOutputDevices += 1; /* add one extra for the WAVE_MAPPER */
302 sDefaultOutputDeviceID = sNumInputDevices;
303 }
304 else
305 {
306 sDefaultOutputDeviceID = paNoDevice;
307 }
308
309 sNumDevices = sNumInputDevices + sNumOutputDevices;
310 }
311
Pa_AllocateDevicePtrs(void)312 static PaError Pa_AllocateDevicePtrs( void )
313 {
314 int numBytes;
315 int i;
316
317 /* Allocate structures to hold device info. */
318 /* PLB20010402 - was allocating too much memory. */
319 /* numBytes = sNumDevices * sizeof(PaDeviceInfo); // PLB20010402 */
320
321 if( sNumDevices > 0 )
322 {
323 numBytes = sNumDevices * sizeof(PaDeviceInfo *); /* PLB20010402 */
324 sDevicePtrs = (PaDeviceInfo **) PaHost_AllocateTrackedMemory( numBytes ); /* MEM */
325 if( sDevicePtrs == NULL ) return paInsufficientMemory;
326
327 for( i = 0; i < sNumDevices; i++ )
328 sDevicePtrs[i] = NULL; /* RDB20020417 explicitly set each ptr to NULL */
329 }
330 else
331 {
332 sDevicePtrs = NULL;
333 }
334
335 return paNoError;
336 }
337 /*************************************************************************/
Pa_GetHostError()338 long Pa_GetHostError()
339 {
340 return sPaHostError;
341 }
342 /*************************************************************************/
Pa_CountDevices()343 int Pa_CountDevices()
344 {
345 if( PaHost_IsInitialized() )
346 return sNumDevices;
347 else
348 return 0;
349 }
350 /*************************************************************************
351 * If a PaDeviceInfo structure has not already been created,
352 * then allocate one and fill it in for the selected device.
353 *
354 * We create one extra input and one extra output device for the WAVE_MAPPER.
355 * [Does anyone know how to query the default device and get its name?]
356 */
Pa_GetDeviceInfo(PaDeviceID id)357 const PaDeviceInfo* Pa_GetDeviceInfo( PaDeviceID id )
358 {
359 #define NUM_STANDARDSAMPLINGRATES 3 /* 11025, 22050, 44100 */
360 static DWORD customSamplingRates[] = { 8000, 32000, 48000, 64000, 88200, 96000 };
361 #define NUM_CUSTOMSAMPLINGRATES (sizeof(customSamplingRates)/sizeof(DWORD))
362 #define MAX_NUMSAMPLINGRATES (NUM_STANDARDSAMPLINGRATES+NUM_CUSTOMSAMPLINGRATES)
363
364 PaDeviceInfo *deviceInfo;
365 double *sampleRates; /* non-const ptr */
366 int i;
367 char *s;
368
369 DBUG(( "Pa_GetDeviceInfo( %d )\n", id ));
370 if( id < 0 || id >= sNumDevices )
371 return NULL;
372 if( sDevicePtrs[ id ] != NULL )
373 {
374 return sDevicePtrs[ id ];
375 }
376 deviceInfo = (PaDeviceInfo *)PaHost_AllocateTrackedMemory( sizeof(PaDeviceInfo) ); /* MEM */
377 if( deviceInfo == NULL ) return NULL;
378 deviceInfo->structVersion = 1;
379 deviceInfo->maxInputChannels = 0;
380 deviceInfo->maxOutputChannels = 0;
381 deviceInfo->numSampleRates = 0;
382 sampleRates = (double*)PaHost_AllocateTrackedMemory( MAX_NUMSAMPLINGRATES * sizeof(double) ); /* MEM */
383 deviceInfo->sampleRates = sampleRates;
384 deviceInfo->nativeSampleFormats = paInt16; /* should query for higher bit depths below */
385 if( id < sNumInputDevices )
386 {
387 /* input device */
388 int inputMmID = PaDeviceIdToWinId(id);
389 WAVEINCAPS wic;
390 if( waveInGetDevCaps( inputMmID, &wic, sizeof( WAVEINCAPS ) ) != MMSYSERR_NOERROR )
391 goto error;
392
393 /* Append I/O suffix to WAVE_MAPPER device. */
394 if( inputMmID == WAVE_MAPPER )
395 {
396 s = (char *) PaHost_AllocateTrackedMemory( strlen( wic.szPname ) + 1 + sizeof(sMapperSuffixInput) ); /* MEM */
397 strcpy( s, wic.szPname );
398 strcat( s, sMapperSuffixInput );
399 }
400 else
401 {
402 s = (char *) PaHost_AllocateTrackedMemory( strlen( wic.szPname ) + 1 ); /* MEM */
403 strcpy( s, wic.szPname );
404 }
405 deviceInfo->name = s;
406 deviceInfo->maxInputChannels = wic.wChannels;
407 DBUG(( "Pa_GetDeviceInfo: input %s, maxChannels = %d\n", deviceInfo->name, deviceInfo->maxInputChannels ));
408 /* Sometimes a device can return a rediculously large number of channels.
409 * This happened with an SBLive card on a Windows ME box.
410 * If that happens, then force it to 2 channels. PLB20010413
411 */
412 if( (deviceInfo->maxInputChannels < 1) || (deviceInfo->maxInputChannels > 256) )
413 {
414 ERR_RPT(("Pa_GetDeviceInfo: Num input channels reported as %d! Changed to 2.\n", deviceInfo->maxOutputChannels ));
415 deviceInfo->maxInputChannels = 2;
416 }
417 /* Add a sample rate to the list if we can do stereo 16 bit at that rate
418 * based on the format flags. */
419 if( wic.dwFormats & WAVE_FORMAT_1M16 ||wic.dwFormats & WAVE_FORMAT_1S16 )
420 sampleRates[ deviceInfo->numSampleRates++ ] = 11025.;
421 if( wic.dwFormats & WAVE_FORMAT_2M16 ||wic.dwFormats & WAVE_FORMAT_2S16 )
422 sampleRates[ deviceInfo->numSampleRates++ ] = 22050.;
423 if( wic.dwFormats & WAVE_FORMAT_4M16 ||wic.dwFormats & WAVE_FORMAT_4S16 )
424 sampleRates[ deviceInfo->numSampleRates++ ] = 44100.;
425 /* Add a sample rate to the list if we can do stereo 16 bit at that rate
426 * based on opening the device successfully. */
427 for( i=0; i < NUM_CUSTOMSAMPLINGRATES; i++ )
428 {
429 WAVEFORMATEX wfx;
430 wfx.wFormatTag = WAVE_FORMAT_PCM;
431 wfx.nSamplesPerSec = customSamplingRates[i];
432 wfx.wBitsPerSample = 16;
433 wfx.cbSize = 0; /* ignored */
434 wfx.nChannels = (WORD)deviceInfo->maxInputChannels;
435 wfx.nAvgBytesPerSec = wfx.nChannels * wfx.nSamplesPerSec * sizeof(short);
436 wfx.nBlockAlign = (WORD)(wfx.nChannels * sizeof(short));
437 if( waveInOpen( NULL, inputMmID, &wfx, 0, 0, WAVE_FORMAT_QUERY ) == MMSYSERR_NOERROR )
438 {
439 sampleRates[ deviceInfo->numSampleRates++ ] = customSamplingRates[i];
440 }
441 }
442
443 }
444 else if( id - sNumInputDevices < sNumOutputDevices )
445 {
446 /* output device */
447 int outputMmID = PaDeviceIdToWinId(id);
448 WAVEOUTCAPS woc;
449 if( waveOutGetDevCaps( outputMmID, &woc, sizeof( WAVEOUTCAPS ) ) != MMSYSERR_NOERROR )
450 goto error;
451 /* Append I/O suffix to WAVE_MAPPER device. */
452 if( outputMmID == WAVE_MAPPER )
453 {
454 s = (char *) PaHost_AllocateTrackedMemory( strlen( woc.szPname ) + 1 + sizeof(sMapperSuffixOutput) ); /* MEM */
455 strcpy( s, woc.szPname );
456 strcat( s, sMapperSuffixOutput );
457 }
458 else
459 {
460 s = (char *) PaHost_AllocateTrackedMemory( strlen( woc.szPname ) + 1 ); /* MEM */
461 strcpy( s, woc.szPname );
462 }
463 deviceInfo->name = s;
464 deviceInfo->maxOutputChannels = woc.wChannels;
465 DBUG(( "Pa_GetDeviceInfo: output %s, maxChannels = %d\n", deviceInfo->name, deviceInfo->maxOutputChannels ));
466 /* Sometimes a device can return a rediculously large number of channels.
467 * This happened with an SBLive card on a Windows ME box.
468 * It also happens on Win XP!
469 */
470 if( (deviceInfo->maxOutputChannels < 1) || (deviceInfo->maxOutputChannels > 256) )
471 {
472 #if 1
473 deviceInfo->maxOutputChannels = 2;
474 #else
475 /* If channel max is goofy, then query for max channels. PLB20020228
476 * This doesn't seem to help. Disable code for now. Remove it later.
477 */
478 ERR_RPT(("Pa_GetDeviceInfo: Num output channels reported as %d!", deviceInfo->maxOutputChannels ));
479 deviceInfo->maxOutputChannels = 0;
480 /* Attempt to find the correct maximum by querying the device. */
481 for( i=2; i<16; i += 2 )
482 {
483 WAVEFORMATEX wfx;
484 wfx.wFormatTag = WAVE_FORMAT_PCM;
485 wfx.nSamplesPerSec = 44100;
486 wfx.wBitsPerSample = 16;
487 wfx.cbSize = 0; /* ignored */
488 wfx.nChannels = (WORD) i;
489 wfx.nAvgBytesPerSec = wfx.nChannels * wfx.nSamplesPerSec * sizeof(short);
490 wfx.nBlockAlign = (WORD)(wfx.nChannels * sizeof(short));
491 if( waveOutOpen( NULL, outputMmID, &wfx, 0, 0, WAVE_FORMAT_QUERY ) == MMSYSERR_NOERROR )
492 {
493 deviceInfo->maxOutputChannels = i;
494 }
495 else
496 {
497 break;
498 }
499 }
500 #endif
501 ERR_RPT((" Changed to %d.\n", deviceInfo->maxOutputChannels ));
502 }
503
504 /* Add a sample rate to the list if we can do stereo 16 bit at that rate
505 * based on the format flags. */
506 if( woc.dwFormats & WAVE_FORMAT_1M16 ||woc.dwFormats & WAVE_FORMAT_1S16 )
507 sampleRates[ deviceInfo->numSampleRates++ ] = 11025.;
508 if( woc.dwFormats & WAVE_FORMAT_2M16 ||woc.dwFormats & WAVE_FORMAT_2S16 )
509 sampleRates[ deviceInfo->numSampleRates++ ] = 22050.;
510 if( woc.dwFormats & WAVE_FORMAT_4M16 ||woc.dwFormats & WAVE_FORMAT_4S16 )
511 sampleRates[ deviceInfo->numSampleRates++ ] = 44100.;
512
513 /* Add a sample rate to the list if we can do stereo 16 bit at that rate
514 * based on opening the device successfully. */
515 for( i=0; i < NUM_CUSTOMSAMPLINGRATES; i++ )
516 {
517 WAVEFORMATEX wfx;
518 wfx.wFormatTag = WAVE_FORMAT_PCM;
519 wfx.nSamplesPerSec = customSamplingRates[i];
520 wfx.wBitsPerSample = 16;
521 wfx.cbSize = 0; /* ignored */
522 wfx.nChannels = (WORD)deviceInfo->maxOutputChannels;
523 wfx.nAvgBytesPerSec = wfx.nChannels * wfx.nSamplesPerSec * sizeof(short);
524 wfx.nBlockAlign = (WORD)(wfx.nChannels * sizeof(short));
525 DBUG(( "Pa_GetDeviceInfo: waveOutOpen( ... WAVE_FORMAT_QUERY at SR = %d\n", customSamplingRates[i] ));
526 if( waveOutOpen( NULL, outputMmID, &wfx, 0, 0, WAVE_FORMAT_QUERY ) == MMSYSERR_NOERROR )
527 {
528 sampleRates[ deviceInfo->numSampleRates++ ] = customSamplingRates[i];
529 }
530 }
531 }
532 DBUG(( "Pa_GetDeviceInfo: done.\n" ));
533 sDevicePtrs[ id ] = deviceInfo;
534 return deviceInfo;
535
536 error:
537 PaHost_FreeTrackedMemory( sampleRates ); /* MEM */
538 PaHost_FreeTrackedMemory( deviceInfo ); /* MEM */
539
540 return NULL;
541 }
542 /*************************************************************************
543 * Returns recommended device ID.
544 * On the PC, the recommended device can be specified by the user by
545 * setting an environment variable. For example, to use device #1.
546 *
547 * set PA_RECOMMENDED_OUTPUT_DEVICE=1
548 *
549 * The user should first determine the available device ID by using
550 * the supplied application "pa_devs".
551 */
552 #define PA_ENV_BUF_SIZE (32)
553 #define PA_REC_IN_DEV_ENV_NAME ("PA_RECOMMENDED_INPUT_DEVICE")
554 #define PA_REC_OUT_DEV_ENV_NAME ("PA_RECOMMENDED_OUTPUT_DEVICE")
PaHost_GetEnvDefaultDeviceID(char * envName)555 static PaDeviceID PaHost_GetEnvDefaultDeviceID( char *envName )
556 {
557 DWORD hresult;
558 char envbuf[PA_ENV_BUF_SIZE];
559 PaDeviceID recommendedID = paNoDevice;
560
561 /* Let user determine default device by setting environment variable. */
562 hresult = GetEnvironmentVariable( envName, envbuf, PA_ENV_BUF_SIZE );
563 if( (hresult > 0) && (hresult < PA_ENV_BUF_SIZE) )
564 {
565 recommendedID = atoi( envbuf );
566 }
567 return recommendedID;
568 }
569 /**********************************************************************
570 * Check for environment variable, else query devices and use result.
571 */
Pa_GetDefaultInputDeviceID(void)572 PaDeviceID Pa_GetDefaultInputDeviceID( void )
573 {
574 PaDeviceID result;
575
576 result = PaHost_GetEnvDefaultDeviceID( PA_REC_IN_DEV_ENV_NAME );
577 if( result == paNoDevice || result < 0 || result >= sNumInputDevices )
578 {
579 result = sDefaultInputDeviceID;
580 }
581 return result;
582 }
Pa_GetDefaultOutputDeviceID(void)583 PaDeviceID Pa_GetDefaultOutputDeviceID( void )
584 {
585 PaDeviceID result;
586
587 result = PaHost_GetEnvDefaultDeviceID( PA_REC_OUT_DEV_ENV_NAME );
588 if( result == paNoDevice || result < sNumInputDevices || result >= sNumDevices )
589 {
590 result = sDefaultOutputDeviceID;
591 }
592 return result;
593 }
594 /**********************************************************************
595 * Initialize Host dependant part of API.
596 */
PaHost_Init(void)597 PaError PaHost_Init( void )
598 {
599
600 #if PA_TRACK_MEMORY
601 PRINT(("PaHost_Init: sNumAllocations = %d\n", sNumAllocations ));
602 #endif
603
604 #if PA_SIMULATE_UNDERFLOW
605 PRINT(("WARNING - Underflow Simulation Enabled - Expect a Big Glitch!!!\n"));
606 #endif
607
608
609 Pa_InitializeNumDevices();
610
611 return Pa_AllocateDevicePtrs();
612 }
613
614 /**********************************************************************
615 * Check WAVE buffers to see if they are done.
616 * Fill any available output buffers and use any available
617 * input buffers by calling user callback.
618 *
619 * This routine will loop until:
620 * user callback returns !=0 OR
621 * all output buffers are filled OR
622 * past->past_StopSoon is set OR
623 * an error occurs when calling WMME.
624 *
625 * Returns >0 when user requests a stop, <0 on error.
626 *
627 */
Pa_TimeSlice(internalPortAudioStream * stream)628 static PaError Pa_TimeSlice( internalPortAudioStream *stream )
629 {
630 PaError result = paNoError;
631 MMRESULT mmresult;
632 char *inBufPtr;
633 char *outBufPtr;
634 int gotInput = 0;
635 int gotOutput = 0;
636 int i;
637 int buffersProcessed = 0;
638 int done = 0;
639 PaWMMEStreamData *wmmeStreamData = (PaWMMEStreamData *) stream->past_DeviceData;
640
641 if( wmmeStreamData == NULL ) return paInternalError;
642
643 stream->past_NumCallbacks += 1;
644 #if PA_TRACE_RUN
645 AddTraceMessage("Pa_TimeSlice: past_NumCallbacks ", stream->past_NumCallbacks );
646 #endif
647
648 /* JM20020118 - prevent hung thread when buffers underflow. */
649 /* while( !done ) /* BAD */
650 while( !done && !stream->past_StopSoon ) /* GOOD */
651 {
652 #if PA_SIMULATE_UNDERFLOW
653 if(gUnderCallbackCounter++ == UNDER_SLEEP_AT)
654 {
655 Sleep(UNDER_SLEEP_FOR);
656 }
657 #endif
658
659 /* If we are using output, then we need an empty output buffer. */
660 gotOutput = 0;
661 outBufPtr = NULL;
662 if( stream->past_NumOutputChannels > 0 )
663 {
664 if((wmmeStreamData->outputBuffers[ wmmeStreamData->currentOutputBuffer ].dwFlags & WHDR_DONE) == 0)
665 {
666 break; /* If none empty then bail and try again later. */
667 }
668 else
669 {
670 outBufPtr = wmmeStreamData->outputBuffers[ wmmeStreamData->currentOutputBuffer ].lpData;
671 gotOutput = 1;
672 }
673 }
674 /* Use an input buffer if one is available. */
675 gotInput = 0;
676 inBufPtr = NULL;
677 if( ( stream->past_NumInputChannels > 0 ) &&
678 (wmmeStreamData->inputBuffers[ wmmeStreamData->currentInputBuffer ].dwFlags & WHDR_DONE) )
679 {
680 inBufPtr = wmmeStreamData->inputBuffers[ wmmeStreamData->currentInputBuffer ].lpData;
681 gotInput = 1;
682 #if PA_TRACE_RUN
683 AddTraceMessage("Pa_TimeSlice: got input buffer at ", (int)inBufPtr );
684 AddTraceMessage("Pa_TimeSlice: got input buffer # ", wmmeStreamData->currentInputBuffer );
685 #endif
686
687 }
688 /* If we can't do anything then bail out. */
689 if( !gotInput && !gotOutput ) break;
690 buffersProcessed += 1;
691 /* Each Wave buffer contains multiple user buffers so do them all now. */
692 /* Base Usage on time it took to process one host buffer. */
693 Pa_StartUsageCalculation( stream );
694 for( i=0; i<wmmeStreamData->userBuffersPerHostBuffer; i++ )
695 {
696 if( done )
697 {
698 if( gotOutput )
699 {
700 /* Clear remainder of wave buffer if we are waiting for stop. */
701 AddTraceMessage("Pa_TimeSlice: zero rest of wave buffer ", i );
702 memset( outBufPtr, 0, wmmeStreamData->bytesPerUserOutputBuffer );
703 }
704 }
705 else
706 {
707 /* Convert 16 bit native data to user data and call user routine. */
708 result = Pa_CallConvertInt16( stream, (short *) inBufPtr, (short *) outBufPtr );
709 if( result != 0) done = 1;
710 }
711 if( gotInput ) inBufPtr += wmmeStreamData->bytesPerUserInputBuffer;
712 if( gotOutput) outBufPtr += wmmeStreamData->bytesPerUserOutputBuffer;
713 }
714 Pa_EndUsageCalculation( stream );
715 /* Send WAVE buffer to Wave Device to be refilled. */
716 if( gotInput )
717 {
718 mmresult = waveInAddBuffer( wmmeStreamData->hWaveIn,
719 &wmmeStreamData->inputBuffers[ wmmeStreamData->currentInputBuffer ],
720 sizeof(WAVEHDR) );
721 if( mmresult != MMSYSERR_NOERROR )
722 {
723 sPaHostError = mmresult;
724 result = paHostError;
725 break;
726 }
727 wmmeStreamData->currentInputBuffer = (wmmeStreamData->currentInputBuffer+1 >= wmmeStreamData->numHostBuffers) ?
728 0 : wmmeStreamData->currentInputBuffer+1;
729 }
730 /* Write WAVE buffer to Wave Device. */
731 if( gotOutput )
732 {
733 #if PA_TRACE_START_STOP
734 AddTraceMessage( "Pa_TimeSlice: writing buffer ", wmmeStreamData->currentOutputBuffer );
735 #endif
736 mmresult = waveOutWrite( wmmeStreamData->hWaveOut,
737 &wmmeStreamData->outputBuffers[ wmmeStreamData->currentOutputBuffer ],
738 sizeof(WAVEHDR) );
739 if( mmresult != MMSYSERR_NOERROR )
740 {
741 sPaHostError = mmresult;
742 result = paHostError;
743 break;
744 }
745 wmmeStreamData->currentOutputBuffer = (wmmeStreamData->currentOutputBuffer+1 >= wmmeStreamData->numHostBuffers) ?
746 0 : wmmeStreamData->currentOutputBuffer+1;
747 }
748
749 }
750
751 #if PA_TRACE_RUN
752 AddTraceMessage("Pa_TimeSlice: buffersProcessed ", buffersProcessed );
753 #endif
754 return (result != 0) ? result : done;
755 }
756
757 /*******************************************************************/
PaHost_BackgroundManager(internalPortAudioStream * stream)758 static PaError PaHost_BackgroundManager( internalPortAudioStream *stream )
759 {
760 PaError result = paNoError;
761 int i;
762 int numQueuedoutputBuffers = 0;
763 PaWMMEStreamData *wmmeStreamData = (PaWMMEStreamData *) stream->past_DeviceData;
764
765 /* Has someone asked us to abort by calling Pa_AbortStream()? */
766 if( stream->past_StopNow )
767 {
768 stream->past_IsActive = 0; /* Will cause thread to return. */
769 }
770 /* Has someone asked us to stop by calling Pa_StopStream()
771 * OR has a user callback returned '1' to indicate finished.
772 */
773 else if( stream->past_StopSoon )
774 {
775 /* Poll buffer and when all have played then exit thread. */
776 /* Count how many output buffers are queued. */
777 numQueuedoutputBuffers = 0;
778 if( stream->past_NumOutputChannels > 0 )
779 {
780 for( i=0; i<wmmeStreamData->numHostBuffers; i++ )
781 {
782 if( !( wmmeStreamData->outputBuffers[ i ].dwFlags & WHDR_DONE) )
783 {
784 #if PA_TRACE_START_STOP
785 AddTraceMessage( "PaHost_BackgroundManager: waiting for buffer ", i );
786 #endif
787 numQueuedoutputBuffers++;
788 }
789 }
790 }
791 #if PA_TRACE_START_STOP
792 AddTraceMessage( "PaHost_BackgroundManager: numQueuedoutputBuffers ", numQueuedoutputBuffers );
793 #endif
794 if( numQueuedoutputBuffers == 0 )
795 {
796 stream->past_IsActive = 0; /* Will cause thread to return. */
797 }
798 }
799 else
800 {
801 /* Process full input buffer and fill up empty output buffers. */
802 if( (result = Pa_TimeSlice( stream )) != 0)
803 {
804 /* User callback has asked us to stop. */
805 #if PA_TRACE_START_STOP
806 AddTraceMessage( "PaHost_BackgroundManager: TimeSlice() returned ", result );
807 #endif
808 stream->past_StopSoon = 1; /* Request that audio play out then stop. */
809 result = paNoError;
810 }
811 }
812
813 PaHost_UpdateStreamTime( wmmeStreamData );
814 return result;
815 }
816
817 #if PA_USE_TIMER_CALLBACK
818 /*******************************************************************/
Pa_TimerCallback(UINT uID,UINT uMsg,DWORD dwUser,DWORD dw1,DWORD dw2)819 static void CALLBACK Pa_TimerCallback(UINT uID, UINT uMsg, DWORD dwUser, DWORD dw1, DWORD dw2)
820 {
821 internalPortAudioStream *stream;
822 PaWMMEStreamData *wmmeStreamData;
823 PaError result;
824
825 stream = (internalPortAudioStream *) dwUser;
826 if( stream == NULL ) return;
827 wmmeStreamData = (PaWMMEStreamData *) stream->past_DeviceData;
828 if( wmmeStreamData == NULL ) return;
829 if( wmmeStreamData->ifInsideCallback )
830 {
831 if( wmmeStreamData->timerID != 0 )
832 {
833 timeKillEvent(wmmeStreamData->timerID); /* Stop callback timer. */
834 wmmeStreamData->timerID = 0;
835 }
836 return;
837 }
838 wmmeStreamData->ifInsideCallback = 1;
839 /* Manage flags and audio processing. */
840 result = PaHost_BackgroundManager( stream );
841 if( result != paNoError )
842 {
843 stream->past_IsActive = 0;
844 }
845 wmmeStreamData->ifInsideCallback = 0;
846 }
847 #else /* PA_USE_TIMER_CALLBACK */
848 /*******************************************************************/
WinMMPa_OutputThreadProc(void * pArg)849 static DWORD WINAPI WinMMPa_OutputThreadProc( void *pArg )
850 {
851 internalPortAudioStream *stream;
852 PaWMMEStreamData *wmmeStreamData;
853 HANDLE events[2];
854 int numEvents = 0;
855 DWORD result = 0;
856 DWORD waitResult;
857 DWORD numTimeouts = 0;
858 DWORD timeOut;
859 stream = (internalPortAudioStream *) pArg;
860 wmmeStreamData = (PaWMMEStreamData *) stream->past_DeviceData;
861 #if PA_TRACE_START_STOP
862 AddTraceMessage( "WinMMPa_OutputThreadProc: timeoutPeriod", timeoutPeriod );
863 AddTraceMessage( "WinMMPa_OutputThreadProc: past_NumUserBuffers", stream->past_NumUserBuffers );
864 #endif
865 /* Calculate timeOut as half the time it would take to play all buffers. */
866 timeOut = (DWORD) (500.0 * PaHost_GetTotalBufferFrames( stream ) / stream->past_SampleRate);
867 /* Get event(s) ready for wait. */
868 events[numEvents++] = wmmeStreamData->bufferEvent;
869 if( wmmeStreamData->abortEventInited ) events[numEvents++] = wmmeStreamData->abortEvent;
870 /* Stay in this thread as long as we are "active". */
871 while( stream->past_IsActive )
872 {
873 /*******************************************************************/
874 /******** WAIT here for an event from WMME or PA *******************/
875 /*******************************************************************/
876 waitResult = WaitForMultipleObjects( numEvents, events, FALSE, timeOut );
877 /* Error? */
878 if( waitResult == WAIT_FAILED )
879 {
880 sPaHostError = GetLastError();
881 result = paHostError;
882 stream->past_IsActive = 0;
883 }
884 /* Timeout? Don't stop. Just keep polling for DONE.*/
885 else if( waitResult == WAIT_TIMEOUT )
886 {
887 #if PA_TRACE_START_STOP
888 AddTraceMessage( "WinMMPa_OutputThreadProc: timed out ", numQueuedoutputBuffers );
889 #endif
890 numTimeouts += 1;
891 }
892 /* Manage flags and audio processing. */
893 result = PaHost_BackgroundManager( stream );
894 if( result != paNoError )
895 {
896 stream->past_IsActive = 0;
897 }
898 }
899 return result;
900 }
901 #endif
902
903 /*******************************************************************/
PaHost_OpenInputStream(internalPortAudioStream * stream)904 PaError PaHost_OpenInputStream( internalPortAudioStream *stream )
905 {
906 PaError result = paNoError;
907 MMRESULT mmresult;
908 PaWMMEStreamData *wmmeStreamData;
909 int i;
910 int inputMmId;
911 int bytesPerInputFrame;
912 WAVEFORMATEX wfx;
913 const PaDeviceInfo *deviceInfo;
914
915 wmmeStreamData = (PaWMMEStreamData *) stream->past_DeviceData;
916 DBUG(("PaHost_OpenStream: deviceID = 0x%x\n", stream->past_InputDeviceID));
917 deviceInfo = Pa_GetDeviceInfo( stream->past_InputDeviceID );
918 if( deviceInfo == NULL ) return paInternalError;
919
920 switch( deviceInfo->nativeSampleFormats )
921 {
922 case paInt32:
923 case paFloat32:
924 bytesPerInputFrame = sizeof(float) * stream->past_NumInputChannels;
925 break;
926 default:
927 bytesPerInputFrame = sizeof(short) * stream->past_NumInputChannels;
928 break;
929 }
930 wfx.wFormatTag = WAVE_FORMAT_PCM;
931 wfx.nChannels = (WORD) stream->past_NumInputChannels;
932 wfx.nSamplesPerSec = (DWORD) stream->past_SampleRate;
933 wfx.nAvgBytesPerSec = (DWORD)(bytesPerInputFrame * stream->past_SampleRate);
934 wfx.nBlockAlign = (WORD)bytesPerInputFrame;
935 wfx.wBitsPerSample = (WORD)((bytesPerInputFrame/stream->past_NumInputChannels) * 8);
936 wfx.cbSize = 0;
937 inputMmId = PaDeviceIdToWinId( stream->past_InputDeviceID );
938 #if PA_USE_TIMER_CALLBACK
939 mmresult = waveInOpen( &wmmeStreamData->hWaveIn, inputMmId, &wfx,
940 0, 0, CALLBACK_NULL );
941 #else
942 mmresult = waveInOpen( &wmmeStreamData->hWaveIn, inputMmId, &wfx,
943 (DWORD)wmmeStreamData->bufferEvent, (DWORD) stream, CALLBACK_EVENT );
944 #endif
945 if( mmresult != MMSYSERR_NOERROR )
946 {
947 ERR_RPT(("PortAudio: PaHost_OpenInputStream() failed!\n"));
948 result = paHostError;
949 sPaHostError = mmresult;
950 goto error;
951 }
952 /* Allocate an array to hold the buffer pointers. */
953 wmmeStreamData->inputBuffers = (WAVEHDR *) PaHost_AllocateTrackedMemory( sizeof(WAVEHDR)*wmmeStreamData->numHostBuffers ); /* MEM */
954 if( wmmeStreamData->inputBuffers == NULL )
955 {
956 result = paInsufficientMemory;
957 goto error;
958 }
959 /* Allocate each buffer. */
960 for( i=0; i<wmmeStreamData->numHostBuffers; i++ )
961 {
962 wmmeStreamData->inputBuffers[i].lpData = (char *)PaHost_AllocateTrackedMemory( wmmeStreamData->bytesPerHostInputBuffer ); /* MEM */
963 if( wmmeStreamData->inputBuffers[i].lpData == NULL )
964 {
965 result = paInsufficientMemory;
966 goto error;
967 }
968 wmmeStreamData->inputBuffers[i].dwBufferLength = wmmeStreamData->bytesPerHostInputBuffer;
969 wmmeStreamData->inputBuffers[i].dwUser = i;
970 if( ( mmresult = waveInPrepareHeader( wmmeStreamData->hWaveIn, &wmmeStreamData->inputBuffers[i], sizeof(WAVEHDR) )) != MMSYSERR_NOERROR )
971 {
972 result = paHostError;
973 sPaHostError = mmresult;
974 goto error;
975 }
976 }
977 return result;
978
979 error:
980 return result;
981 }
982 /*******************************************************************/
PaHost_OpenOutputStream(internalPortAudioStream * stream)983 PaError PaHost_OpenOutputStream( internalPortAudioStream *stream )
984 {
985 PaError result = paNoError;
986 MMRESULT mmresult;
987 PaWMMEStreamData *wmmeStreamData;
988 int i;
989 int outputMmID;
990 int bytesPerOutputFrame;
991 WAVEFORMATEX wfx;
992 const PaDeviceInfo *deviceInfo;
993
994 wmmeStreamData = (PaWMMEStreamData *) stream->past_DeviceData;
995 DBUG(("PaHost_OpenStream: deviceID = 0x%x\n", stream->past_OutputDeviceID));
996
997 deviceInfo = Pa_GetDeviceInfo( stream->past_OutputDeviceID );
998 if( deviceInfo == NULL ) return paInternalError;
999
1000 switch( deviceInfo->nativeSampleFormats )
1001 {
1002 case paInt32:
1003 case paFloat32:
1004 bytesPerOutputFrame = sizeof(float) * stream->past_NumOutputChannels;
1005 break;
1006 default:
1007 bytesPerOutputFrame = sizeof(short) * stream->past_NumOutputChannels;
1008 break;
1009 }
1010 wfx.wFormatTag = WAVE_FORMAT_PCM;
1011 wfx.nChannels = (WORD) stream->past_NumOutputChannels;
1012 wfx.nSamplesPerSec = (DWORD) stream->past_SampleRate;
1013 wfx.nAvgBytesPerSec = (DWORD)(bytesPerOutputFrame * stream->past_SampleRate);
1014 wfx.nBlockAlign = (WORD)bytesPerOutputFrame;
1015 wfx.wBitsPerSample = (WORD)((bytesPerOutputFrame/stream->past_NumOutputChannels) * 8);
1016 wfx.cbSize = 0;
1017 outputMmID = PaDeviceIdToWinId( stream->past_OutputDeviceID );
1018 #if PA_USE_TIMER_CALLBACK
1019 mmresult = waveOutOpen( &wmmeStreamData->hWaveOut, outputMmID, &wfx,
1020 0, 0, CALLBACK_NULL );
1021 #else
1022
1023 wmmeStreamData->abortEvent = CreateEvent( NULL, TRUE, FALSE, NULL );
1024 if( wmmeStreamData->abortEvent == NULL )
1025 {
1026 result = paHostError;
1027 sPaHostError = GetLastError();
1028 goto error;
1029 }
1030 wmmeStreamData->abortEventInited = 1;
1031 mmresult = waveOutOpen( &wmmeStreamData->hWaveOut, outputMmID, &wfx,
1032 (DWORD)wmmeStreamData->bufferEvent, (DWORD) stream, CALLBACK_EVENT );
1033 #endif
1034 if( mmresult != MMSYSERR_NOERROR )
1035 {
1036 ERR_RPT(("PortAudio: PaHost_OpenOutputStream() failed!\n"));
1037 result = paHostError;
1038 sPaHostError = mmresult;
1039 goto error;
1040 }
1041 /* Allocate an array to hold the buffer pointers. */
1042 wmmeStreamData->outputBuffers = (WAVEHDR *) PaHost_AllocateTrackedMemory( sizeof(WAVEHDR)*wmmeStreamData->numHostBuffers ); /* MEM */
1043 if( wmmeStreamData->outputBuffers == NULL )
1044 {
1045 result = paInsufficientMemory;
1046 goto error;
1047 }
1048 /* Allocate each buffer. */
1049 for( i=0; i<wmmeStreamData->numHostBuffers; i++ )
1050 {
1051 wmmeStreamData->outputBuffers[i].lpData = (char *) PaHost_AllocateTrackedMemory( wmmeStreamData->bytesPerHostOutputBuffer ); /* MEM */
1052 if( wmmeStreamData->outputBuffers[i].lpData == NULL )
1053 {
1054 result = paInsufficientMemory;
1055 goto error;
1056 }
1057 wmmeStreamData->outputBuffers[i].dwBufferLength = wmmeStreamData->bytesPerHostOutputBuffer;
1058 wmmeStreamData->outputBuffers[i].dwUser = i;
1059 if( (mmresult = waveOutPrepareHeader( wmmeStreamData->hWaveOut, &wmmeStreamData->outputBuffers[i], sizeof(WAVEHDR) )) != MMSYSERR_NOERROR )
1060 {
1061 result = paHostError;
1062 sPaHostError = mmresult;
1063 goto error;
1064 }
1065 }
1066 return result;
1067
1068 error:
1069 return result;
1070 }
1071 /*******************************************************************/
PaHost_GetTotalBufferFrames(internalPortAudioStream * stream)1072 PaError PaHost_GetTotalBufferFrames( internalPortAudioStream *stream )
1073 {
1074 PaWMMEStreamData *wmmeStreamData = (PaWMMEStreamData *) stream->past_DeviceData;
1075 return wmmeStreamData->numHostBuffers * wmmeStreamData->framesPerHostBuffer;
1076 }
1077 /*******************************************************************
1078 * Determine number of WAVE Buffers
1079 * and how many User Buffers we can put into each WAVE buffer.
1080 */
PaHost_CalcNumHostBuffers(internalPortAudioStream * stream)1081 static void PaHost_CalcNumHostBuffers( internalPortAudioStream *stream )
1082 {
1083 PaWMMEStreamData *wmmeStreamData = (PaWMMEStreamData *) stream->past_DeviceData;
1084 unsigned int minNumBuffers;
1085 int minframesPerHostBuffer;
1086 int maxframesPerHostBuffer;
1087 int minTotalFrames;
1088 int userBuffersPerHostBuffer;
1089 int framesPerHostBuffer;
1090 int numHostBuffers;
1091
1092 /* Calculate minimum and maximum sizes based on timing and sample rate. */
1093 minframesPerHostBuffer = (int) (PA_MIN_MSEC_PER_HOST_BUFFER * stream->past_SampleRate * 0.001);
1094 minframesPerHostBuffer = (minframesPerHostBuffer + 7) & ~7;
1095 DBUG(("PaHost_CalcNumHostBuffers: minframesPerHostBuffer = %d\n", minframesPerHostBuffer ));
1096 maxframesPerHostBuffer = (int) (PA_MAX_MSEC_PER_HOST_BUFFER * stream->past_SampleRate * 0.001);
1097 maxframesPerHostBuffer = (maxframesPerHostBuffer + 7) & ~7;
1098 DBUG(("PaHost_CalcNumHostBuffers: maxframesPerHostBuffer = %d\n", maxframesPerHostBuffer ));
1099 /* Determine number of user buffers based on minimum latency. */
1100 minNumBuffers = Pa_GetMinNumBuffers( stream->past_FramesPerUserBuffer, stream->past_SampleRate );
1101 stream->past_NumUserBuffers = ( minNumBuffers > stream->past_NumUserBuffers ) ? minNumBuffers : stream->past_NumUserBuffers;
1102 DBUG(("PaHost_CalcNumHostBuffers: min past_NumUserBuffers = %d\n", stream->past_NumUserBuffers ));
1103 minTotalFrames = stream->past_NumUserBuffers * stream->past_FramesPerUserBuffer;
1104 /* We cannot make the WAVE buffers too small because they may not get serviced quickly enough. */
1105 if( (int) stream->past_FramesPerUserBuffer < minframesPerHostBuffer )
1106 {
1107 userBuffersPerHostBuffer =
1108 (minframesPerHostBuffer + stream->past_FramesPerUserBuffer - 1) /
1109 stream->past_FramesPerUserBuffer;
1110 }
1111 else
1112 {
1113 userBuffersPerHostBuffer = 1;
1114 }
1115 framesPerHostBuffer = stream->past_FramesPerUserBuffer * userBuffersPerHostBuffer;
1116 /* Calculate number of WAVE buffers needed. Round up to cover minTotalFrames. */
1117 numHostBuffers = (minTotalFrames + framesPerHostBuffer - 1) / framesPerHostBuffer;
1118 /* Make sure we have anough WAVE buffers. */
1119 if( numHostBuffers < PA_MIN_NUM_HOST_BUFFERS)
1120 {
1121 numHostBuffers = PA_MIN_NUM_HOST_BUFFERS;
1122 }
1123 else if( (numHostBuffers > PA_MAX_NUM_HOST_BUFFERS) &&
1124 ((int) stream->past_FramesPerUserBuffer < (maxframesPerHostBuffer/2) ) )
1125 {
1126 /* If we have too many WAVE buffers, try to put more user buffers in a wave buffer. */
1127 while(numHostBuffers > PA_MAX_NUM_HOST_BUFFERS)
1128 {
1129 userBuffersPerHostBuffer += 1;
1130 framesPerHostBuffer = stream->past_FramesPerUserBuffer * userBuffersPerHostBuffer;
1131 numHostBuffers = (minTotalFrames + framesPerHostBuffer - 1) / framesPerHostBuffer;
1132 /* If we have gone too far, back up one. */
1133 if( (framesPerHostBuffer > maxframesPerHostBuffer) ||
1134 (numHostBuffers < PA_MAX_NUM_HOST_BUFFERS) )
1135 {
1136 userBuffersPerHostBuffer -= 1;
1137 framesPerHostBuffer = stream->past_FramesPerUserBuffer * userBuffersPerHostBuffer;
1138 numHostBuffers = (minTotalFrames + framesPerHostBuffer - 1) / framesPerHostBuffer;
1139 break;
1140 }
1141 }
1142 }
1143
1144 wmmeStreamData->userBuffersPerHostBuffer = userBuffersPerHostBuffer;
1145 wmmeStreamData->framesPerHostBuffer = framesPerHostBuffer;
1146 wmmeStreamData->numHostBuffers = numHostBuffers;
1147 DBUG(("PaHost_CalcNumHostBuffers: userBuffersPerHostBuffer = %d\n", wmmeStreamData->userBuffersPerHostBuffer ));
1148 DBUG(("PaHost_CalcNumHostBuffers: numHostBuffers = %d\n", wmmeStreamData->numHostBuffers ));
1149 DBUG(("PaHost_CalcNumHostBuffers: framesPerHostBuffer = %d\n", wmmeStreamData->framesPerHostBuffer ));
1150 DBUG(("PaHost_CalcNumHostBuffers: past_NumUserBuffers = %d\n", stream->past_NumUserBuffers ));
1151 }
1152 /*******************************************************************/
PaHost_OpenStream(internalPortAudioStream * stream)1153 PaError PaHost_OpenStream( internalPortAudioStream *stream )
1154 {
1155 PaError result = paNoError;
1156 PaWMMEStreamData *wmmeStreamData;
1157
1158 result = PaHost_AllocateWMMEStreamData( stream );
1159 if( result != paNoError ) return result;
1160
1161 wmmeStreamData = PaHost_GetWMMEStreamData( stream );
1162
1163 /* Figure out how user buffers fit into WAVE buffers. */
1164 PaHost_CalcNumHostBuffers( stream );
1165 {
1166 int msecLatency = (int) ((PaHost_GetTotalBufferFrames(stream) * 1000) / stream->past_SampleRate);
1167 DBUG(("PortAudio on WMME - Latency = %d frames, %d msec\n", PaHost_GetTotalBufferFrames(stream), msecLatency ));
1168 }
1169 InitializeCriticalSection( &wmmeStreamData->streamLock );
1170 wmmeStreamData->streamLockInited = 1;
1171
1172 #if (PA_USE_TIMER_CALLBACK == 0)
1173 wmmeStreamData->bufferEventInited = 0;
1174 wmmeStreamData->bufferEvent = CreateEvent( NULL, FALSE, FALSE, NULL );
1175 if( wmmeStreamData->bufferEvent == NULL )
1176 {
1177 result = paHostError;
1178 sPaHostError = GetLastError();
1179 goto error;
1180 }
1181 wmmeStreamData->bufferEventInited = 1;
1182 #endif /* (PA_USE_TIMER_CALLBACK == 0) */
1183 /* ------------------ OUTPUT */
1184 wmmeStreamData->bytesPerUserOutputBuffer = stream->past_FramesPerUserBuffer * stream->past_NumOutputChannels * sizeof(short);
1185 wmmeStreamData->bytesPerHostOutputBuffer = wmmeStreamData->userBuffersPerHostBuffer * wmmeStreamData->bytesPerUserOutputBuffer;
1186 if( (stream->past_OutputDeviceID != paNoDevice) && (stream->past_NumOutputChannels > 0) )
1187 {
1188 result = PaHost_OpenOutputStream( stream );
1189 if( result < 0 ) goto error;
1190 }
1191 /* ------------------ INPUT */
1192 wmmeStreamData->bytesPerUserInputBuffer = stream->past_FramesPerUserBuffer * stream->past_NumInputChannels * sizeof(short);
1193 wmmeStreamData->bytesPerHostInputBuffer = wmmeStreamData->userBuffersPerHostBuffer * wmmeStreamData->bytesPerUserInputBuffer;
1194 if( (stream->past_InputDeviceID != paNoDevice) && (stream->past_NumInputChannels > 0) )
1195 {
1196 result = PaHost_OpenInputStream( stream );
1197 if( result < 0 ) goto error;
1198 }
1199
1200 Pa_InitializeCpuUsageScalar( stream );
1201
1202 return result;
1203
1204 error:
1205 PaHost_CloseStream( stream );
1206 return result;
1207 }
1208 /*************************************************************************/
PaHost_StartOutput(internalPortAudioStream * stream)1209 PaError PaHost_StartOutput( internalPortAudioStream *stream )
1210 {
1211 PaError result = paNoError;
1212 MMRESULT mmresult;
1213 int i;
1214 PaWMMEStreamData *wmmeStreamData = PaHost_GetWMMEStreamData( stream );
1215
1216 if( wmmeStreamData == NULL ) return paInternalError;
1217
1218 if( stream->past_OutputDeviceID != paNoDevice )
1219 {
1220 if( (mmresult = waveOutPause( wmmeStreamData->hWaveOut )) != MMSYSERR_NOERROR )
1221 {
1222 result = paHostError;
1223 sPaHostError = mmresult;
1224 goto error;
1225 }
1226 for( i=0; i<wmmeStreamData->numHostBuffers; i++ )
1227 {
1228 ZeroMemory( wmmeStreamData->outputBuffers[i].lpData, wmmeStreamData->outputBuffers[i].dwBufferLength );
1229 mmresult = waveOutWrite( wmmeStreamData->hWaveOut, &wmmeStreamData->outputBuffers[i], sizeof(WAVEHDR) );
1230 if( mmresult != MMSYSERR_NOERROR )
1231 {
1232 result = paHostError;
1233 sPaHostError = mmresult;
1234 goto error;
1235 }
1236 stream->past_FrameCount += wmmeStreamData->framesPerHostBuffer;
1237 }
1238 wmmeStreamData->currentOutputBuffer = 0;
1239 if( (mmresult = waveOutRestart( wmmeStreamData->hWaveOut )) != MMSYSERR_NOERROR )
1240 {
1241 result = paHostError;
1242 sPaHostError = mmresult;
1243 goto error;
1244 }
1245 }
1246
1247 error:
1248 DBUG(("PaHost_StartOutput: wave returned mmresult = 0x%X.\n", mmresult));
1249 return result;
1250 }
1251 /*************************************************************************/
PaHost_StartInput(internalPortAudioStream * internalStream)1252 PaError PaHost_StartInput( internalPortAudioStream *internalStream )
1253 {
1254 PaError result = paNoError;
1255 MMRESULT mmresult;
1256 int i;
1257 PaWMMEStreamData *wmmeStreamData = PaHost_GetWMMEStreamData( internalStream );
1258
1259 if( wmmeStreamData == NULL ) return paInternalError;
1260
1261 if( internalStream->past_InputDeviceID != paNoDevice )
1262 {
1263 for( i=0; i<wmmeStreamData->numHostBuffers; i++ )
1264 {
1265 mmresult = waveInAddBuffer( wmmeStreamData->hWaveIn, &wmmeStreamData->inputBuffers[i], sizeof(WAVEHDR) );
1266 if( mmresult != MMSYSERR_NOERROR )
1267 {
1268 result = paHostError;
1269 sPaHostError = mmresult;
1270 goto error;
1271 }
1272 }
1273 wmmeStreamData->currentInputBuffer = 0;
1274 mmresult = waveInStart( wmmeStreamData->hWaveIn );
1275 DBUG(("Pa_StartStream: waveInStart returned = 0x%X.\n", mmresult));
1276 if( mmresult != MMSYSERR_NOERROR )
1277 {
1278 result = paHostError;
1279 sPaHostError = mmresult;
1280 goto error;
1281 }
1282 }
1283
1284 error:
1285 return result;
1286 }
1287 /*************************************************************************/
PaHost_StartEngine(internalPortAudioStream * stream)1288 PaError PaHost_StartEngine( internalPortAudioStream *stream )
1289 {
1290 PaError result = paNoError;
1291 PaWMMEStreamData *wmmeStreamData = PaHost_GetWMMEStreamData( stream );
1292 #if PA_USE_TIMER_CALLBACK
1293 int resolution;
1294 int bufsPerTimerCallback;
1295 int msecPerBuffer;
1296 #endif /* PA_USE_TIMER_CALLBACK */
1297
1298 if( wmmeStreamData == NULL ) return paInternalError;
1299
1300 stream->past_StopSoon = 0;
1301 stream->past_StopNow = 0;
1302 stream->past_IsActive = 1;
1303 wmmeStreamData->framesPlayed = 0.0;
1304 wmmeStreamData->lastPosition = 0;
1305 #if PA_TRACE_START_STOP
1306 AddTraceMessage( "PaHost_StartEngine: TimeSlice() returned ", result );
1307 #endif
1308 #if PA_USE_TIMER_CALLBACK
1309 /* Create timer that will wake us up so we can fill the DSound buffer. */
1310 bufsPerTimerCallback = wmmeStreamData->numHostBuffers/4;
1311 if( bufsPerTimerCallback < 1 ) bufsPerTimerCallback = 1;
1312 if( bufsPerTimerCallback < 1 ) bufsPerTimerCallback = 1;
1313 msecPerBuffer = (1000 * bufsPerTimerCallback *
1314 wmmeStreamData->userBuffersPerHostBuffer *
1315 internalStream->past_FramesPerUserBuffer ) / (int) internalStream->past_SampleRate;
1316 if( msecPerBuffer < 10 ) msecPerBuffer = 10;
1317 else if( msecPerBuffer > 100 ) msecPerBuffer = 100;
1318 resolution = msecPerBuffer/4;
1319 wmmeStreamData->timerID = timeSetEvent( msecPerBuffer, resolution,
1320 (LPTIMECALLBACK) Pa_TimerCallback,
1321 (DWORD) stream, TIME_PERIODIC );
1322 if( wmmeStreamData->timerID == 0 )
1323 {
1324 result = paHostError;
1325 sPaHostError = GetLastError();;
1326 goto error;
1327 }
1328 #else /* PA_USE_TIMER_CALLBACK */
1329 ResetEvent( wmmeStreamData->abortEvent );
1330 /* Create thread that waits for audio buffers to be ready for processing. */
1331 wmmeStreamData->engineThread = CreateThread( 0, 0, WinMMPa_OutputThreadProc, stream, 0, &wmmeStreamData->engineThreadID );
1332 if( wmmeStreamData->engineThread == NULL )
1333 {
1334 result = paHostError;
1335 sPaHostError = GetLastError();;
1336 goto error;
1337 }
1338 #if PA_TRACE_START_STOP
1339 AddTraceMessage( "PaHost_StartEngine: thread ", (int) wmmeStreamData->engineThread );
1340 #endif
1341 /* I used to pass the thread which was failing. I now pass GetCurrentProcess().
1342 * This fix could improve latency for some applications. It could also result in CPU
1343 * starvation if the callback did too much processing.
1344 * I also added result checks, so we might see more failures at initialization.
1345 * Thanks to Alberto di Bene for spotting this.
1346 */
1347 if( !SetPriorityClass( GetCurrentProcess(), HIGH_PRIORITY_CLASS ) ) /* PLB20010816 */
1348 {
1349 result = paHostError;
1350 sPaHostError = GetLastError();;
1351 goto error;
1352 }
1353 if( !SetThreadPriority( wmmeStreamData->engineThread, THREAD_PRIORITY_HIGHEST ) )
1354 {
1355 result = paHostError;
1356 sPaHostError = GetLastError();;
1357 goto error;
1358 }
1359 #endif
1360
1361 error:
1362 return result;
1363 }
1364 /*************************************************************************/
PaHost_StopEngine(internalPortAudioStream * internalStream,int abort)1365 PaError PaHost_StopEngine( internalPortAudioStream *internalStream, int abort )
1366 {
1367 int timeOut;
1368 PaWMMEStreamData *wmmeStreamData = PaHost_GetWMMEStreamData( internalStream );
1369
1370 if( wmmeStreamData == NULL ) return paNoError;
1371
1372 /* Tell background thread to stop generating more data and to let current data play out. */
1373 internalStream->past_StopSoon = 1;
1374 /* If aborting, tell background thread to stop NOW! */
1375 if( abort ) internalStream->past_StopNow = 1;
1376
1377 /* Calculate timeOut longer than longest time it could take to play all buffers. */
1378 timeOut = (DWORD) (1500.0 * PaHost_GetTotalBufferFrames( internalStream ) / internalStream->past_SampleRate);
1379 if( timeOut < MIN_TIMEOUT_MSEC ) timeOut = MIN_TIMEOUT_MSEC;
1380
1381 #if PA_USE_TIMER_CALLBACK
1382 if( (internalStream->past_OutputDeviceID != paNoDevice) &&
1383 internalStream->past_IsActive &&
1384 (wmmeStreamData->timerID != 0) )
1385 {
1386 /* Wait for IsActive to drop. */
1387 while( (internalStream->past_IsActive) && (timeOut > 0) )
1388 {
1389 Sleep(10);
1390 timeOut -= 10;
1391 }
1392 timeKillEvent( wmmeStreamData->timerID ); /* Stop callback timer. */
1393 wmmeStreamData->timerID = 0;
1394 }
1395 #else /* PA_USE_TIMER_CALLBACK */
1396 #if PA_TRACE_START_STOP
1397 AddTraceMessage( "PaHost_StopEngine: thread ", (int) wmmeStreamData->engineThread );
1398 #endif
1399 if( (internalStream->past_OutputDeviceID != paNoDevice) &&
1400 (internalStream->past_IsActive) &&
1401 (wmmeStreamData->engineThread != NULL) )
1402 {
1403 DWORD got;
1404 /* Tell background thread to stop generating more data and to let current data play out. */
1405 DBUG(("PaHost_StopEngine: waiting for background thread.\n"));
1406 got = WaitForSingleObject( wmmeStreamData->engineThread, timeOut );
1407 if( got == WAIT_TIMEOUT )
1408 {
1409 ERR_RPT(("PaHost_StopEngine: timed out while waiting for background thread to finish.\n"));
1410 return paTimedOut;
1411 }
1412 CloseHandle( wmmeStreamData->engineThread );
1413 wmmeStreamData->engineThread = NULL;
1414 }
1415 #endif /* PA_USE_TIMER_CALLBACK */
1416
1417 internalStream->past_IsActive = 0;
1418 return paNoError;
1419 }
1420 /*************************************************************************/
PaHost_StopInput(internalPortAudioStream * stream,int abort)1421 PaError PaHost_StopInput( internalPortAudioStream *stream, int abort )
1422 {
1423 MMRESULT mmresult;
1424 PaWMMEStreamData *wmmeStreamData = PaHost_GetWMMEStreamData( stream );
1425
1426 if( wmmeStreamData == NULL ) return paNoError; /* FIXME: why return paNoError? */
1427 (void) abort; /* unused parameter */
1428
1429 if( wmmeStreamData->hWaveIn != NULL )
1430 {
1431 mmresult = waveInReset( wmmeStreamData->hWaveIn );
1432 if( mmresult != MMSYSERR_NOERROR )
1433 {
1434 sPaHostError = mmresult;
1435 return paHostError;
1436 }
1437 }
1438 return paNoError;
1439 }
1440 /*************************************************************************/
PaHost_StopOutput(internalPortAudioStream * internalStream,int abort)1441 PaError PaHost_StopOutput( internalPortAudioStream *internalStream, int abort )
1442 {
1443 MMRESULT mmresult;
1444 PaWMMEStreamData *wmmeStreamData = PaHost_GetWMMEStreamData( internalStream );
1445
1446 if( wmmeStreamData == NULL ) return paNoError; /* FIXME: why return paNoError? */
1447 (void) abort; /* unused parameter */
1448
1449 #if PA_TRACE_START_STOP
1450 AddTraceMessage( "PaHost_StopOutput: hWaveOut ", (int) wmmeStreamData->hWaveOut );
1451 #endif
1452 if( wmmeStreamData->hWaveOut != NULL )
1453 {
1454 mmresult = waveOutReset( wmmeStreamData->hWaveOut );
1455 if( mmresult != MMSYSERR_NOERROR )
1456 {
1457 sPaHostError = mmresult;
1458 return paHostError;
1459 }
1460 }
1461 return paNoError;
1462 }
1463 /*******************************************************************/
PaHost_CloseStream(internalPortAudioStream * stream)1464 PaError PaHost_CloseStream( internalPortAudioStream *stream )
1465 {
1466 int i;
1467 PaWMMEStreamData *wmmeStreamData = PaHost_GetWMMEStreamData( stream );
1468
1469 if( stream == NULL ) return paBadStreamPtr;
1470 if( wmmeStreamData == NULL ) return paNoError; /* FIXME: why return no error? */
1471
1472 #if PA_TRACE_START_STOP
1473 AddTraceMessage( "PaHost_CloseStream: hWaveOut ", (int) wmmeStreamData->hWaveOut );
1474 #endif
1475 /* Free data and device for output. */
1476 if( wmmeStreamData->hWaveOut )
1477 {
1478 if( wmmeStreamData->outputBuffers )
1479 {
1480 for( i=0; i<wmmeStreamData->numHostBuffers; i++ )
1481 {
1482 waveOutUnprepareHeader( wmmeStreamData->hWaveOut, &wmmeStreamData->outputBuffers[i], sizeof(WAVEHDR) );
1483 PaHost_FreeTrackedMemory( wmmeStreamData->outputBuffers[i].lpData ); /* MEM */
1484 }
1485 PaHost_FreeTrackedMemory( wmmeStreamData->outputBuffers ); /* MEM */
1486 }
1487 waveOutClose( wmmeStreamData->hWaveOut );
1488 }
1489 /* Free data and device for input. */
1490 if( wmmeStreamData->hWaveIn )
1491 {
1492 if( wmmeStreamData->inputBuffers )
1493 {
1494 for( i=0; i<wmmeStreamData->numHostBuffers; i++ )
1495 {
1496 waveInUnprepareHeader( wmmeStreamData->hWaveIn, &wmmeStreamData->inputBuffers[i], sizeof(WAVEHDR) );
1497 PaHost_FreeTrackedMemory( wmmeStreamData->inputBuffers[i].lpData ); /* MEM */
1498 }
1499 PaHost_FreeTrackedMemory( wmmeStreamData->inputBuffers ); /* MEM */
1500 }
1501 waveInClose( wmmeStreamData->hWaveIn );
1502 }
1503 #if (PA_USE_TIMER_CALLBACK == 0)
1504 if( wmmeStreamData->abortEventInited ) CloseHandle( wmmeStreamData->abortEvent );
1505 if( wmmeStreamData->bufferEventInited ) CloseHandle( wmmeStreamData->bufferEvent );
1506 #endif
1507 if( wmmeStreamData->streamLockInited )
1508 DeleteCriticalSection( &wmmeStreamData->streamLock );
1509
1510 PaHost_FreeWMMEStreamData( stream );
1511
1512 return paNoError;
1513 }
1514 /*************************************************************************
1515 * Determine minimum number of buffers required for this host based
1516 * on minimum latency. Latency can be optionally set by user by setting
1517 * an environment variable. For example, to set latency to 200 msec, put:
1518 *
1519 * set PA_MIN_LATENCY_MSEC=200
1520 *
1521 * in the AUTOEXEC.BAT file and reboot.
1522 * If the environment variable is not set, then the latency will be determined
1523 * based on the OS. Windows NT has higher latency than Win95.
1524 */
1525 #define PA_LATENCY_ENV_NAME ("PA_MIN_LATENCY_MSEC")
Pa_GetMinNumBuffers(int framesPerBuffer,double sampleRate)1526 int Pa_GetMinNumBuffers( int framesPerBuffer, double sampleRate )
1527 {
1528 char envbuf[PA_ENV_BUF_SIZE];
1529 DWORD hresult;
1530 int minLatencyMsec = 0;
1531 double msecPerBuffer = (1000.0 * framesPerBuffer) / sampleRate;
1532 int minBuffers;
1533
1534 /* Let user determine minimal latency by setting environment variable. */
1535 hresult = GetEnvironmentVariable( PA_LATENCY_ENV_NAME, envbuf, PA_ENV_BUF_SIZE );
1536 if( (hresult > 0) && (hresult < PA_ENV_BUF_SIZE) )
1537 {
1538 minLatencyMsec = atoi( envbuf ); /* REVIEW: will we crash if the environment variable contains some nasty value? */
1539 }
1540 else
1541 {
1542 /* Set minimal latency based on whether NT or other OS.
1543 * NT has higher latency.
1544 */
1545 OSVERSIONINFO osvi;
1546 osvi.dwOSVersionInfoSize = sizeof( osvi );
1547 GetVersionEx( &osvi );
1548 DBUG(("PA - PlatformId = 0x%x\n", osvi.dwPlatformId ));
1549 DBUG(("PA - MajorVersion = 0x%x\n", osvi.dwMajorVersion ));
1550 DBUG(("PA - MinorVersion = 0x%x\n", osvi.dwMinorVersion ));
1551 /* Check for NT */
1552 if( (osvi.dwMajorVersion == 4) && (osvi.dwPlatformId == 2) )
1553 {
1554 minLatencyMsec = PA_WIN_NT_LATENCY;
1555 }
1556 else if(osvi.dwMajorVersion >= 5)
1557 {
1558 minLatencyMsec = PA_WIN_WDM_LATENCY;
1559 }
1560 else
1561 {
1562 minLatencyMsec = PA_WIN_9X_LATENCY;
1563 }
1564 #if PA_USE_HIGH_LATENCY
1565 PRINT(("PA - Minimum Latency set to %d msec!\n", minLatencyMsec ));
1566 #endif
1567
1568 }
1569 DBUG(("PA - Minimum Latency set to %d msec!\n", minLatencyMsec ));
1570 minBuffers = (int) (1.0 + ((double)minLatencyMsec / msecPerBuffer));
1571 if( minBuffers < 2 ) minBuffers = 2;
1572 return minBuffers;
1573 }
1574 /*************************************************************************
1575 * Cleanup device info.
1576 */
PaHost_Term(void)1577 PaError PaHost_Term( void )
1578 {
1579 int i;
1580
1581 if( sNumDevices > 0 )
1582 {
1583 if( sDevicePtrs != NULL )
1584 {
1585 for( i=0; i<sNumDevices; i++ )
1586 {
1587 if( sDevicePtrs[i] != NULL )
1588 {
1589 PaHost_FreeTrackedMemory( (char*)sDevicePtrs[i]->name ); /* MEM */
1590 PaHost_FreeTrackedMemory( (void*)sDevicePtrs[i]->sampleRates ); /* MEM */
1591 PaHost_FreeTrackedMemory( sDevicePtrs[i] ); /* MEM */
1592 }
1593 }
1594 PaHost_FreeTrackedMemory( sDevicePtrs ); /* MEM */
1595 sDevicePtrs = NULL;
1596 }
1597 sNumDevices = 0;
1598 }
1599
1600 #if PA_TRACK_MEMORY
1601 PRINT(("PaHost_Term: sNumAllocations = %d\n", sNumAllocations ));
1602 #endif
1603
1604 return paNoError;
1605 }
1606 /*************************************************************************/
Pa_Sleep(long msec)1607 void Pa_Sleep( long msec )
1608 {
1609 Sleep( msec );
1610 }
1611 /*************************************************************************
1612 FIXME: the following memory allocation routines should not be declared here
1613 * Allocate memory that can be accessed in real-time.
1614 * This may need to be held in physical memory so that it is not
1615 * paged to virtual memory.
1616 * This call MUST be balanced with a call to PaHost_FreeFastMemory().
1617 * Memory will be set to zero.
1618 */
PaHost_AllocateFastMemory(long numBytes)1619 void *PaHost_AllocateFastMemory( long numBytes )
1620 {
1621 return PaHost_AllocateTrackedMemory( numBytes ); /* FIXME - do we need physical memory? Use VirtualLock() */ /* MEM */
1622 }
1623 /*************************************************************************
1624 * Free memory that could be accessed in real-time.
1625 * This call MUST be balanced with a call to PaHost_AllocateFastMemory().
1626 */
PaHost_FreeFastMemory(void * addr,long numBytes)1627 void PaHost_FreeFastMemory( void *addr, long numBytes )
1628 {
1629 (void) numBytes; /* unused parameter */
1630
1631 PaHost_FreeTrackedMemory( addr ); /* MEM */
1632 }
1633
1634 /*************************************************************************
1635 * Track memory allocations to avoid leaks.
1636 */
PaHost_AllocateTrackedMemory(long numBytes)1637 static void *PaHost_AllocateTrackedMemory( long numBytes )
1638 {
1639 void *result = GlobalAlloc( GPTR, numBytes ); /* MEM */
1640
1641 #if PA_TRACK_MEMORY
1642 if( result != NULL ) sNumAllocations += 1;
1643 #endif
1644 return result;
1645 }
1646
PaHost_FreeTrackedMemory(void * addr)1647 static void PaHost_FreeTrackedMemory( void *addr )
1648 {
1649 if( addr != NULL )
1650 {
1651 GlobalFree( addr ); /* MEM */
1652 #if PA_TRACK_MEMORY
1653 sNumAllocations -= 1;
1654 #endif
1655 }
1656 }
1657
1658 /***********************************************************************/
PaHost_StreamActive(internalPortAudioStream * internalStream)1659 PaError PaHost_StreamActive( internalPortAudioStream *internalStream )
1660 {
1661 if( internalStream == NULL ) return paBadStreamPtr;
1662
1663 return (PaError) internalStream->past_IsActive;
1664 }
1665 /*************************************************************************
1666 * This must be called periodically because mmtime.u.sample
1667 * is a DWORD and can wrap and lose sync after a few hours.
1668 */
PaHost_UpdateStreamTime(PaWMMEStreamData * wmmeStreamData)1669 static PaError PaHost_UpdateStreamTime( PaWMMEStreamData *wmmeStreamData )
1670 {
1671 MMRESULT mmresult;
1672 MMTIME mmtime;
1673 mmtime.wType = TIME_SAMPLES;
1674
1675 if( wmmeStreamData->hWaveOut != NULL )
1676 {
1677 mmresult = waveOutGetPosition( wmmeStreamData->hWaveOut, &mmtime, sizeof(mmtime) );
1678 }
1679 else
1680 {
1681 mmresult = waveInGetPosition( wmmeStreamData->hWaveIn, &mmtime, sizeof(mmtime) );
1682 }
1683
1684 if( mmresult != MMSYSERR_NOERROR )
1685 {
1686 sPaHostError = mmresult;
1687 return paHostError;
1688 }
1689
1690 /* This data has two variables and is shared by foreground and background.
1691 * So we need to make it thread safe. */
1692 EnterCriticalSection( &wmmeStreamData->streamLock );
1693 wmmeStreamData->framesPlayed += ((long)mmtime.u.sample) - wmmeStreamData->lastPosition;
1694 wmmeStreamData->lastPosition = (long)mmtime.u.sample;
1695 LeaveCriticalSection( &wmmeStreamData->streamLock );
1696
1697 return paNoError;
1698 }
1699 /*************************************************************************/
Pa_StreamTime(PortAudioStream * stream)1700 PaTimestamp Pa_StreamTime( PortAudioStream *stream )
1701 {
1702 internalPortAudioStream *internalStream = PaHost_GetStreamRepresentation( stream );
1703 PaWMMEStreamData *wmmeStreamData = PaHost_GetWMMEStreamData( internalStream );
1704
1705 if( internalStream == NULL ) return paBadStreamPtr;
1706 if( wmmeStreamData == NULL ) return paInternalError;
1707
1708 PaHost_UpdateStreamTime( wmmeStreamData );
1709 return wmmeStreamData->framesPlayed;
1710 }
1711 /*************************************************************************/
1712
1713
1714
1715