diff options
Diffstat (limited to 'pd/portaudio_v18/pa_win_wmme')
-rw-r--r-- | pd/portaudio_v18/pa_win_wmme/Makefile.cygwin | 34 | ||||
-rw-r--r-- | pd/portaudio_v18/pa_win_wmme/pa_win_wmme.c | 1714 |
2 files changed, 1748 insertions, 0 deletions
diff --git a/pd/portaudio_v18/pa_win_wmme/Makefile.cygwin b/pd/portaudio_v18/pa_win_wmme/Makefile.cygwin new file mode 100644 index 00000000..5cb4acef --- /dev/null +++ b/pd/portaudio_v18/pa_win_wmme/Makefile.cygwin @@ -0,0 +1,34 @@ + +# Makefile for PortAudio on cygwin +# Contributed by Bill Eldridge on 6/13/2001 + +ARCH= pa_win_wmme + +TESTS:= $(wildcard pa_tests/pa*.c pa_tests/debug*.c) + +.c.o: + -gcc -c -I./pa_common $< -o $*.o + -gcc $*.o -o $*.exe -L/usr/local/lib -L$(ARCH) -lportaudio.dll -lwinmm + +all: sharedlib tests + +sharedlib: ./pa_common/pa_lib.c + gcc -c -I./pa_common pa_common/pa_lib.c -o pa_common/pa_lib.o + gcc -c -I./pa_common pa_win_wmme/pa_win_wmme.c -o pa_win_wmme/pa_win_wmme.o + dlltool --export-all --output-def pa_win_wmme/pa_lib.def pa_common/pa_lib.o pa_win_wmme/pa_win_wmme.o + gcc -shared -Wl,--enable-auto-image-base -o pa_win_wmme/portaudio.dll -Wl,--out-implib=pa_win_wmme/libportaudio.dll.a pa_win_wmme/pa_lib.def pa_common/pa_lib.o pa_win_wmme/pa_win_wmme.o -L/usr/lib/w32api -lwinmm + cp pa_win_wmme/portaudio.dll /usr/local/bin + +tests: $(TESTS:.c=.o) + +sine: + gcc -c -I./pa_common pa_tests/patest_sine.c -o pa_tests/patest_sine.o + gcc pa_tests/patest_sine.o -o pa_tests/patest_sine.exe -L/usr/local/lib -lportaudio.dll -lwinmm + +clean: + -rm ./pa_tests/*.exe + +nothing: + gcc pa_tests/patest_sine.o -L/usr/lib/w32api -L./pa_win_wmme -lportaudio.dll -lwinmm + + diff --git a/pd/portaudio_v18/pa_win_wmme/pa_win_wmme.c b/pd/portaudio_v18/pa_win_wmme/pa_win_wmme.c new file mode 100644 index 00000000..9dc03826 --- /dev/null +++ b/pd/portaudio_v18/pa_win_wmme/pa_win_wmme.c @@ -0,0 +1,1714 @@ +/* + * $Id: pa_win_wmme.c,v 1.6.4.3 2003/04/28 17:43:48 philburk Exp $ + * pa_win_wmme.c + * Implementation of PortAudio for Windows MultiMedia Extensions (WMME) + * + * PortAudio Portable Real-Time Audio Library + * Latest Version at: http://www.portaudio.com + * + * Authors: Ross Bencina and Phil Burk + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +/* + All memory allocations and frees are marked with MEM for quick review. +*/ + +/* Modification History: + PLB = Phil Burk + JM = Julien Maillard + RDB = Ross Bencina + PLB20010402 - sDevicePtrs now allocates based on sizeof(pointer) + PLB20010413 - check for excessive numbers of channels + PLB20010422 - apply Mike Berry's changes for CodeWarrior on PC + including condition including of memory.h, + and explicit typecasting on memory allocation + PLB20010802 - use GlobalAlloc for sDevicesPtr instead of PaHost_AllocFastMemory + PLB20010816 - pass process instead of thread to SetPriorityClass() + PLB20010927 - use number of frames instead of real-time for CPULoad calculation. + JM20020118 - prevent hung thread when buffers underflow. + PLB20020321 - detect Win XP versus NT, 9x; fix DBUG typo; removed init of CurrentCount + RDB20020411 - various renaming cleanups, factored streamData alloc and cpu usage init + RDB20020417 - stopped counting WAVE_MAPPER when there were no real devices + refactoring, renaming and fixed a few edge case bugs + PLB20020612 - added 8000.0 Hz to custom sampling rates array +*/ +#pragma warning (disable: 4115) +#include <stdio.h> +#include <stdlib.h> +#include <math.h> +#include <windows.h> +#include <mmsystem.h> +#include <process.h> +/* PLB20010422 - "memory.h" doesn't work on CodeWarrior for PC. Thanks Mike Berry for the mod. */ +#ifndef __MWERKS__ +#include <malloc.h> +#include <memory.h> +#endif /* __MWERKS__ */ +#include "portaudio.h" +#include "pa_host.h" +#include "pa_trace.h" + +/************************************************* Constants ********/ +#define PA_TRACK_MEMORY (0) + +#define PA_USE_TIMER_CALLBACK (0) /* Select between two options for background task. 0=thread, 1=timer */ +/* Switches for debugging. */ +#define PA_SIMULATE_UNDERFLOW (0) /* Set to one to force an underflow of the output buffer. */ + +/* To trace program, enable TRACE_REALTIME_EVENTS in pa_trace.h */ +#define PA_TRACE_RUN (0) +#define PA_TRACE_START_STOP (1) + +#define PA_USE_HIGH_LATENCY (0) /* For debugging glitches. */ + +#if PA_USE_HIGH_LATENCY + #define PA_MIN_MSEC_PER_HOST_BUFFER (100) + #define PA_MAX_MSEC_PER_HOST_BUFFER (300) /* Do not exceed unless user buffer exceeds */ + #define PA_MIN_NUM_HOST_BUFFERS (4) + #define PA_MAX_NUM_HOST_BUFFERS (16) /* OK to exceed if necessary */ + #define PA_WIN_9X_LATENCY (400) +#else + #define PA_MIN_MSEC_PER_HOST_BUFFER (10) + #define PA_MAX_MSEC_PER_HOST_BUFFER (100) /* Do not exceed unless user buffer exceeds */ + #define PA_MIN_NUM_HOST_BUFFERS (3) + #define PA_MAX_NUM_HOST_BUFFERS (16) /* OK to exceed if necessary */ + #define PA_WIN_9X_LATENCY (200) +#endif +#define MIN_TIMEOUT_MSEC (1000) + +/* +** Use higher latency for NT because it is even worse at real-time +** operation than Win9x. +*/ +#define PA_WIN_NT_LATENCY (PA_WIN_9X_LATENCY * 2) +#define PA_WIN_WDM_LATENCY (PA_WIN_9X_LATENCY) + +#if PA_SIMULATE_UNDERFLOW +static gUnderCallbackCounter = 0; +#define UNDER_SLEEP_AT (40) +#define UNDER_SLEEP_FOR (500) +#endif + +#define PRINT(x) { printf x; fflush(stdout); } +#define ERR_RPT(x) PRINT(x) +#define DBUG(x) /* PRINT(x) */ +#define DBUGX(x) /* PRINT(x) */ +/************************************************* Definitions ********/ +/************************************************************** + * Structure for internal host specific stream data. + * This is allocated on a per stream basis. + */ +typedef struct PaWMMEStreamData +{ + /* Input -------------- */ + HWAVEIN hWaveIn; + WAVEHDR *inputBuffers; + int currentInputBuffer; + int bytesPerHostInputBuffer; + int bytesPerUserInputBuffer; /* native buffer size in bytes */ + /* Output -------------- */ + HWAVEOUT hWaveOut; + WAVEHDR *outputBuffers; + int currentOutputBuffer; + int bytesPerHostOutputBuffer; + int bytesPerUserOutputBuffer; /* native buffer size in bytes */ + /* Run Time -------------- */ + PaTimestamp framesPlayed; + long lastPosition; /* used to track frames played. */ + /* For measuring CPU utilization. */ + LARGE_INTEGER entryCount; + double inverseTicksPerHostBuffer; + /* Init Time -------------- */ + int numHostBuffers; + int framesPerHostBuffer; + int userBuffersPerHostBuffer; + CRITICAL_SECTION streamLock; /* Mutext to prevent threads from colliding. */ + INT streamLockInited; +#if PA_USE_TIMER_CALLBACK + BOOL ifInsideCallback; /* Test for reentrancy. */ + MMRESULT timerID; +#else + HANDLE abortEvent; + int abortEventInited; + HANDLE bufferEvent; + int bufferEventInited; + HANDLE engineThread; + DWORD engineThreadID; +#endif +} +PaWMMEStreamData; +/************************************************* Shared Data ********/ +/* FIXME - put Mutex around this shared data. */ +static int sNumInputDevices = 0; +static int sNumOutputDevices = 0; +static int sNumDevices = 0; +static PaDeviceInfo **sDevicePtrs = NULL; +static int sDefaultInputDeviceID = paNoDevice; +static int sDefaultOutputDeviceID = paNoDevice; +static int sPaHostError = 0; +static const char sMapperSuffixInput[] = " - Input"; +static const char sMapperSuffixOutput[] = " - Output"; + +#if PA_TRACK_MEMORY +static int sNumAllocations = 0; +#endif + +/************************************************* Macros ********/ +/* Convert external PA ID to an internal ID that includes WAVE_MAPPER */ +#define PaDeviceIdToWinId(id) (((id) < sNumInputDevices) ? (id - 1) : (id - sNumInputDevices - 1)) +/************************************************* Prototypes **********/ + +void Pa_InitializeNumDevices( void ); +PaError Pa_AllocateDevicePtrs( void ); + +static void CALLBACK Pa_TimerCallback(UINT uID, UINT uMsg, + DWORD dwUser, DWORD dw1, DWORD dw2); +PaError PaHost_GetTotalBufferFrames( internalPortAudioStream *past ); +static PaError PaHost_UpdateStreamTime( PaWMMEStreamData *wmmeStreamData ); +static PaError PaHost_BackgroundManager( internalPortAudioStream *past ); + +static void *PaHost_AllocateTrackedMemory( long numBytes ); +static void PaHost_FreeTrackedMemory( void *addr ); + +/*******************************************************************/ +static PaError PaHost_AllocateWMMEStreamData( internalPortAudioStream *stream ) +{ + PaError result = paNoError; + PaWMMEStreamData *wmmeStreamData; + + wmmeStreamData = (PaWMMEStreamData *) PaHost_AllocateFastMemory(sizeof(PaWMMEStreamData)); /* MEM */ + if( wmmeStreamData == NULL ) + { + result = paInsufficientMemory; + goto error; + } + memset( wmmeStreamData, 0, sizeof(PaWMMEStreamData) ); + stream->past_DeviceData = (void *) wmmeStreamData; + + return result; + +error: + return result; +} + +/*******************************************************************/ +static void PaHost_FreeWMMEStreamData( internalPortAudioStream *internalStream ) +{ + PaWMMEStreamData *wmmeStreamData = (PaWMMEStreamData *) internalStream->past_DeviceData; + + PaHost_FreeFastMemory( wmmeStreamData, sizeof(PaWMMEStreamData) ); /* MEM */ + internalStream->past_DeviceData = NULL; +} +/*************************************************************************/ +static PaWMMEStreamData* PaHost_GetWMMEStreamData( internalPortAudioStream* internalStream ) +{ + PaWMMEStreamData *result = NULL; + + if( internalStream != NULL ) + { + result = (PaWMMEStreamData *) internalStream->past_DeviceData; + } + return result; +} +/********************************* BEGIN CPU UTILIZATION MEASUREMENT ****/ +/* FIXME: the cpu usage code should be factored out into a common module */ +static void Pa_InitializeCpuUsageScalar( internalPortAudioStream *stream ) +{ + PaWMMEStreamData *wmmeStreamData = (PaWMMEStreamData *) stream->past_DeviceData; + + LARGE_INTEGER frequency; + if( QueryPerformanceFrequency( &frequency ) == 0 ) + { + wmmeStreamData->inverseTicksPerHostBuffer = 0.0; + } + else + { + wmmeStreamData->inverseTicksPerHostBuffer = stream->past_SampleRate / + ( (double)frequency.QuadPart * stream->past_FramesPerUserBuffer * wmmeStreamData->userBuffersPerHostBuffer ); + DBUG(("inverseTicksPerHostBuffer = %g\n", wmmeStreamData->inverseTicksPerHostBuffer )); + } +} +static void Pa_StartUsageCalculation( internalPortAudioStream *stream ) +{ + PaWMMEStreamData *wmmeStreamData = (PaWMMEStreamData *) stream->past_DeviceData; + + if( wmmeStreamData == NULL ) return; + /* Query system timer for usage analysis and to prevent overuse of CPU. */ + QueryPerformanceCounter( &wmmeStreamData->entryCount ); +} +static void Pa_EndUsageCalculation( internalPortAudioStream *stream ) +{ + LARGE_INTEGER CurrentCount; + PaWMMEStreamData *wmmeStreamData = (PaWMMEStreamData *) stream->past_DeviceData; + + if( wmmeStreamData == NULL ) return; + /* + * Measure CPU utilization during this callback. Note that this calculation + * assumes that we had the processor the whole time. + */ +#define LOWPASS_COEFFICIENT_0 (0.9) +#define LOWPASS_COEFFICIENT_1 (0.99999 - LOWPASS_COEFFICIENT_0) + if( QueryPerformanceCounter( &CurrentCount ) ) + { + LONGLONG InsideCount = CurrentCount.QuadPart - wmmeStreamData->entryCount.QuadPart; + double newUsage = InsideCount * wmmeStreamData->inverseTicksPerHostBuffer; + stream->past_Usage = (LOWPASS_COEFFICIENT_0 * stream->past_Usage) + + (LOWPASS_COEFFICIENT_1 * newUsage); + } +} +/****************************************** END CPU UTILIZATION *******/ + +static void Pa_InitializeNumDevices( void ) +{ + sNumInputDevices = waveInGetNumDevs(); + if( sNumInputDevices > 0 ) + { + sNumInputDevices += 1; /* add one extra for the WAVE_MAPPER */ + sDefaultInputDeviceID = 0; + } + else + { + sDefaultInputDeviceID = paNoDevice; + } + + sNumOutputDevices = waveOutGetNumDevs(); + if( sNumOutputDevices > 0 ) + { + sNumOutputDevices += 1; /* add one extra for the WAVE_MAPPER */ + sDefaultOutputDeviceID = sNumInputDevices; + } + else + { + sDefaultOutputDeviceID = paNoDevice; + } + + sNumDevices = sNumInputDevices + sNumOutputDevices; +} + +static PaError Pa_AllocateDevicePtrs( void ) +{ + int numBytes; + int i; + + /* Allocate structures to hold device info. */ + /* PLB20010402 - was allocating too much memory. */ + /* numBytes = sNumDevices * sizeof(PaDeviceInfo); // PLB20010402 */ + + if( sNumDevices > 0 ) + { + numBytes = sNumDevices * sizeof(PaDeviceInfo *); /* PLB20010402 */ + sDevicePtrs = (PaDeviceInfo **) PaHost_AllocateTrackedMemory( numBytes ); /* MEM */ + if( sDevicePtrs == NULL ) return paInsufficientMemory; + + for( i = 0; i < sNumDevices; i++ ) + sDevicePtrs[i] = NULL; /* RDB20020417 explicitly set each ptr to NULL */ + } + else + { + sDevicePtrs = NULL; + } + + return paNoError; +} +/*************************************************************************/ +long Pa_GetHostError() +{ + return sPaHostError; +} +/*************************************************************************/ +int Pa_CountDevices() +{ + if( PaHost_IsInitialized() ) + return sNumDevices; + else + return 0; +} +/************************************************************************* + * If a PaDeviceInfo structure has not already been created, + * then allocate one and fill it in for the selected device. + * + * We create one extra input and one extra output device for the WAVE_MAPPER. + * [Does anyone know how to query the default device and get its name?] + */ +const PaDeviceInfo* Pa_GetDeviceInfo( PaDeviceID id ) +{ +#define NUM_STANDARDSAMPLINGRATES 3 /* 11025, 22050, 44100 */ + static DWORD customSamplingRates[] = { 8000, 32000, 48000, 64000, 88200, 96000 }; +#define NUM_CUSTOMSAMPLINGRATES (sizeof(customSamplingRates)/sizeof(DWORD)) +#define MAX_NUMSAMPLINGRATES (NUM_STANDARDSAMPLINGRATES+NUM_CUSTOMSAMPLINGRATES) + + PaDeviceInfo *deviceInfo; + double *sampleRates; /* non-const ptr */ + int i; + char *s; + + DBUG(( "Pa_GetDeviceInfo( %d )\n", id )); + if( id < 0 || id >= sNumDevices ) + return NULL; + if( sDevicePtrs[ id ] != NULL ) + { + return sDevicePtrs[ id ]; + } + deviceInfo = (PaDeviceInfo *)PaHost_AllocateTrackedMemory( sizeof(PaDeviceInfo) ); /* MEM */ + if( deviceInfo == NULL ) return NULL; + deviceInfo->structVersion = 1; + deviceInfo->maxInputChannels = 0; + deviceInfo->maxOutputChannels = 0; + deviceInfo->numSampleRates = 0; + sampleRates = (double*)PaHost_AllocateTrackedMemory( MAX_NUMSAMPLINGRATES * sizeof(double) ); /* MEM */ + deviceInfo->sampleRates = sampleRates; + deviceInfo->nativeSampleFormats = paInt16; /* should query for higher bit depths below */ + if( id < sNumInputDevices ) + { + /* input device */ + int inputMmID = PaDeviceIdToWinId(id); + WAVEINCAPS wic; + if( waveInGetDevCaps( inputMmID, &wic, sizeof( WAVEINCAPS ) ) != MMSYSERR_NOERROR ) + goto error; + + /* Append I/O suffix to WAVE_MAPPER device. */ + if( inputMmID == WAVE_MAPPER ) + { + s = (char *) PaHost_AllocateTrackedMemory( strlen( wic.szPname ) + 1 + sizeof(sMapperSuffixInput) ); /* MEM */ + strcpy( s, wic.szPname ); + strcat( s, sMapperSuffixInput ); + } + else + { + s = (char *) PaHost_AllocateTrackedMemory( strlen( wic.szPname ) + 1 ); /* MEM */ + strcpy( s, wic.szPname ); + } + deviceInfo->name = s; + deviceInfo->maxInputChannels = wic.wChannels; + DBUG(( "Pa_GetDeviceInfo: input %s, maxChannels = %d\n", deviceInfo->name, deviceInfo->maxInputChannels )); + /* Sometimes a device can return a rediculously large number of channels. + * This happened with an SBLive card on a Windows ME box. + * If that happens, then force it to 2 channels. PLB20010413 + */ + if( (deviceInfo->maxInputChannels < 1) || (deviceInfo->maxInputChannels > 256) ) + { + ERR_RPT(("Pa_GetDeviceInfo: Num input channels reported as %d! Changed to 2.\n", deviceInfo->maxOutputChannels )); + deviceInfo->maxInputChannels = 2; + } + /* Add a sample rate to the list if we can do stereo 16 bit at that rate + * based on the format flags. */ + if( wic.dwFormats & WAVE_FORMAT_1M16 ||wic.dwFormats & WAVE_FORMAT_1S16 ) + sampleRates[ deviceInfo->numSampleRates++ ] = 11025.; + if( wic.dwFormats & WAVE_FORMAT_2M16 ||wic.dwFormats & WAVE_FORMAT_2S16 ) + sampleRates[ deviceInfo->numSampleRates++ ] = 22050.; + if( wic.dwFormats & WAVE_FORMAT_4M16 ||wic.dwFormats & WAVE_FORMAT_4S16 ) + sampleRates[ deviceInfo->numSampleRates++ ] = 44100.; + /* Add a sample rate to the list if we can do stereo 16 bit at that rate + * based on opening the device successfully. */ + for( i=0; i < NUM_CUSTOMSAMPLINGRATES; i++ ) + { + WAVEFORMATEX wfx; + wfx.wFormatTag = WAVE_FORMAT_PCM; + wfx.nSamplesPerSec = customSamplingRates[i]; + wfx.wBitsPerSample = 16; + wfx.cbSize = 0; /* ignored */ + wfx.nChannels = (WORD)deviceInfo->maxInputChannels; + wfx.nAvgBytesPerSec = wfx.nChannels * wfx.nSamplesPerSec * sizeof(short); + wfx.nBlockAlign = (WORD)(wfx.nChannels * sizeof(short)); + if( waveInOpen( NULL, inputMmID, &wfx, 0, 0, WAVE_FORMAT_QUERY ) == MMSYSERR_NOERROR ) + { + sampleRates[ deviceInfo->numSampleRates++ ] = customSamplingRates[i]; + } + } + + } + else if( id - sNumInputDevices < sNumOutputDevices ) + { + /* output device */ + int outputMmID = PaDeviceIdToWinId(id); + WAVEOUTCAPS woc; + if( waveOutGetDevCaps( outputMmID, &woc, sizeof( WAVEOUTCAPS ) ) != MMSYSERR_NOERROR ) + goto error; + /* Append I/O suffix to WAVE_MAPPER device. */ + if( outputMmID == WAVE_MAPPER ) + { + s = (char *) PaHost_AllocateTrackedMemory( strlen( woc.szPname ) + 1 + sizeof(sMapperSuffixOutput) ); /* MEM */ + strcpy( s, woc.szPname ); + strcat( s, sMapperSuffixOutput ); + } + else + { + s = (char *) PaHost_AllocateTrackedMemory( strlen( woc.szPname ) + 1 ); /* MEM */ + strcpy( s, woc.szPname ); + } + deviceInfo->name = s; + deviceInfo->maxOutputChannels = woc.wChannels; + DBUG(( "Pa_GetDeviceInfo: output %s, maxChannels = %d\n", deviceInfo->name, deviceInfo->maxOutputChannels )); + /* Sometimes a device can return a rediculously large number of channels. + * This happened with an SBLive card on a Windows ME box. + * It also happens on Win XP! + */ + if( (deviceInfo->maxOutputChannels < 1) || (deviceInfo->maxOutputChannels > 256) ) + { +#if 1 + deviceInfo->maxOutputChannels = 2; +#else + /* If channel max is goofy, then query for max channels. PLB20020228 + * This doesn't seem to help. Disable code for now. Remove it later. + */ + ERR_RPT(("Pa_GetDeviceInfo: Num output channels reported as %d!", deviceInfo->maxOutputChannels )); + deviceInfo->maxOutputChannels = 0; + /* Attempt to find the correct maximum by querying the device. */ + for( i=2; i<16; i += 2 ) + { + WAVEFORMATEX wfx; + wfx.wFormatTag = WAVE_FORMAT_PCM; + wfx.nSamplesPerSec = 44100; + wfx.wBitsPerSample = 16; + wfx.cbSize = 0; /* ignored */ + wfx.nChannels = (WORD) i; + wfx.nAvgBytesPerSec = wfx.nChannels * wfx.nSamplesPerSec * sizeof(short); + wfx.nBlockAlign = (WORD)(wfx.nChannels * sizeof(short)); + if( waveOutOpen( NULL, outputMmID, &wfx, 0, 0, WAVE_FORMAT_QUERY ) == MMSYSERR_NOERROR ) + { + deviceInfo->maxOutputChannels = i; + } + else + { + break; + } + } +#endif + ERR_RPT((" Changed to %d.\n", deviceInfo->maxOutputChannels )); + } + + /* Add a sample rate to the list if we can do stereo 16 bit at that rate + * based on the format flags. */ + if( woc.dwFormats & WAVE_FORMAT_1M16 ||woc.dwFormats & WAVE_FORMAT_1S16 ) + sampleRates[ deviceInfo->numSampleRates++ ] = 11025.; + if( woc.dwFormats & WAVE_FORMAT_2M16 ||woc.dwFormats & WAVE_FORMAT_2S16 ) + sampleRates[ deviceInfo->numSampleRates++ ] = 22050.; + if( woc.dwFormats & WAVE_FORMAT_4M16 ||woc.dwFormats & WAVE_FORMAT_4S16 ) + sampleRates[ deviceInfo->numSampleRates++ ] = 44100.; + + /* Add a sample rate to the list if we can do stereo 16 bit at that rate + * based on opening the device successfully. */ + for( i=0; i < NUM_CUSTOMSAMPLINGRATES; i++ ) + { + WAVEFORMATEX wfx; + wfx.wFormatTag = WAVE_FORMAT_PCM; + wfx.nSamplesPerSec = customSamplingRates[i]; + wfx.wBitsPerSample = 16; + wfx.cbSize = 0; /* ignored */ + wfx.nChannels = (WORD)deviceInfo->maxOutputChannels; + wfx.nAvgBytesPerSec = wfx.nChannels * wfx.nSamplesPerSec * sizeof(short); + wfx.nBlockAlign = (WORD)(wfx.nChannels * sizeof(short)); + DBUG(( "Pa_GetDeviceInfo: waveOutOpen( ... WAVE_FORMAT_QUERY at SR = %d\n", customSamplingRates[i] )); + if( waveOutOpen( NULL, outputMmID, &wfx, 0, 0, WAVE_FORMAT_QUERY ) == MMSYSERR_NOERROR ) + { + sampleRates[ deviceInfo->numSampleRates++ ] = customSamplingRates[i]; + } + } + } + DBUG(( "Pa_GetDeviceInfo: done.\n" )); + sDevicePtrs[ id ] = deviceInfo; + return deviceInfo; + +error: + PaHost_FreeTrackedMemory( sampleRates ); /* MEM */ + PaHost_FreeTrackedMemory( deviceInfo ); /* MEM */ + + return NULL; +} +/************************************************************************* + * Returns recommended device ID. + * On the PC, the recommended device can be specified by the user by + * setting an environment variable. For example, to use device #1. + * + * set PA_RECOMMENDED_OUTPUT_DEVICE=1 + * + * The user should first determine the available device ID by using + * the supplied application "pa_devs". + */ +#define PA_ENV_BUF_SIZE (32) +#define PA_REC_IN_DEV_ENV_NAME ("PA_RECOMMENDED_INPUT_DEVICE") +#define PA_REC_OUT_DEV_ENV_NAME ("PA_RECOMMENDED_OUTPUT_DEVICE") +static PaDeviceID PaHost_GetEnvDefaultDeviceID( char *envName ) +{ + DWORD hresult; + char envbuf[PA_ENV_BUF_SIZE]; + PaDeviceID recommendedID = paNoDevice; + + /* Let user determine default device by setting environment variable. */ + hresult = GetEnvironmentVariable( envName, envbuf, PA_ENV_BUF_SIZE ); + if( (hresult > 0) && (hresult < PA_ENV_BUF_SIZE) ) + { + recommendedID = atoi( envbuf ); + } + return recommendedID; +} +/********************************************************************** + * Check for environment variable, else query devices and use result. + */ +PaDeviceID Pa_GetDefaultInputDeviceID( void ) +{ + PaDeviceID result; + + result = PaHost_GetEnvDefaultDeviceID( PA_REC_IN_DEV_ENV_NAME ); + if( result == paNoDevice || result < 0 || result >= sNumInputDevices ) + { + result = sDefaultInputDeviceID; + } + return result; +} +PaDeviceID Pa_GetDefaultOutputDeviceID( void ) +{ + PaDeviceID result; + + result = PaHost_GetEnvDefaultDeviceID( PA_REC_OUT_DEV_ENV_NAME ); + if( result == paNoDevice || result < sNumInputDevices || result >= sNumDevices ) + { + result = sDefaultOutputDeviceID; + } + return result; +} +/********************************************************************** + * Initialize Host dependant part of API. + */ +PaError PaHost_Init( void ) +{ + +#if PA_TRACK_MEMORY + PRINT(("PaHost_Init: sNumAllocations = %d\n", sNumAllocations )); +#endif + +#if PA_SIMULATE_UNDERFLOW + PRINT(("WARNING - Underflow Simulation Enabled - Expect a Big Glitch!!!\n")); +#endif + + + Pa_InitializeNumDevices(); + + return Pa_AllocateDevicePtrs(); +} + +/********************************************************************** + * Check WAVE buffers to see if they are done. + * Fill any available output buffers and use any available + * input buffers by calling user callback. + * + * This routine will loop until: + * user callback returns !=0 OR + * all output buffers are filled OR + * past->past_StopSoon is set OR + * an error occurs when calling WMME. + * + * Returns >0 when user requests a stop, <0 on error. + * + */ +static PaError Pa_TimeSlice( internalPortAudioStream *stream ) +{ + PaError result = paNoError; + MMRESULT mmresult; + char *inBufPtr; + char *outBufPtr; + int gotInput = 0; + int gotOutput = 0; + int i; + int buffersProcessed = 0; + int done = 0; + PaWMMEStreamData *wmmeStreamData = (PaWMMEStreamData *) stream->past_DeviceData; + + if( wmmeStreamData == NULL ) return paInternalError; + + stream->past_NumCallbacks += 1; +#if PA_TRACE_RUN + AddTraceMessage("Pa_TimeSlice: past_NumCallbacks ", stream->past_NumCallbacks ); +#endif + + /* JM20020118 - prevent hung thread when buffers underflow. */ + /* while( !done ) /* BAD */ + while( !done && !stream->past_StopSoon ) /* GOOD */ + { +#if PA_SIMULATE_UNDERFLOW + if(gUnderCallbackCounter++ == UNDER_SLEEP_AT) + { + Sleep(UNDER_SLEEP_FOR); + } +#endif + + /* If we are using output, then we need an empty output buffer. */ + gotOutput = 0; + outBufPtr = NULL; + if( stream->past_NumOutputChannels > 0 ) + { + if((wmmeStreamData->outputBuffers[ wmmeStreamData->currentOutputBuffer ].dwFlags & WHDR_DONE) == 0) + { + break; /* If none empty then bail and try again later. */ + } + else + { + outBufPtr = wmmeStreamData->outputBuffers[ wmmeStreamData->currentOutputBuffer ].lpData; + gotOutput = 1; + } + } + /* Use an input buffer if one is available. */ + gotInput = 0; + inBufPtr = NULL; + if( ( stream->past_NumInputChannels > 0 ) && + (wmmeStreamData->inputBuffers[ wmmeStreamData->currentInputBuffer ].dwFlags & WHDR_DONE) ) + { + inBufPtr = wmmeStreamData->inputBuffers[ wmmeStreamData->currentInputBuffer ].lpData; + gotInput = 1; +#if PA_TRACE_RUN + AddTraceMessage("Pa_TimeSlice: got input buffer at ", (int)inBufPtr ); + AddTraceMessage("Pa_TimeSlice: got input buffer # ", wmmeStreamData->currentInputBuffer ); +#endif + + } + /* If we can't do anything then bail out. */ + if( !gotInput && !gotOutput ) break; + buffersProcessed += 1; + /* Each Wave buffer contains multiple user buffers so do them all now. */ + /* Base Usage on time it took to process one host buffer. */ + Pa_StartUsageCalculation( stream ); + for( i=0; i<wmmeStreamData->userBuffersPerHostBuffer; i++ ) + { + if( done ) + { + if( gotOutput ) + { + /* Clear remainder of wave buffer if we are waiting for stop. */ + AddTraceMessage("Pa_TimeSlice: zero rest of wave buffer ", i ); + memset( outBufPtr, 0, wmmeStreamData->bytesPerUserOutputBuffer ); + } + } + else + { + /* Convert 16 bit native data to user data and call user routine. */ + result = Pa_CallConvertInt16( stream, (short *) inBufPtr, (short *) outBufPtr ); + if( result != 0) done = 1; + } + if( gotInput ) inBufPtr += wmmeStreamData->bytesPerUserInputBuffer; + if( gotOutput) outBufPtr += wmmeStreamData->bytesPerUserOutputBuffer; + } + Pa_EndUsageCalculation( stream ); + /* Send WAVE buffer to Wave Device to be refilled. */ + if( gotInput ) + { + mmresult = waveInAddBuffer( wmmeStreamData->hWaveIn, + &wmmeStreamData->inputBuffers[ wmmeStreamData->currentInputBuffer ], + sizeof(WAVEHDR) ); + if( mmresult != MMSYSERR_NOERROR ) + { + sPaHostError = mmresult; + result = paHostError; + break; + } + wmmeStreamData->currentInputBuffer = (wmmeStreamData->currentInputBuffer+1 >= wmmeStreamData->numHostBuffers) ? + 0 : wmmeStreamData->currentInputBuffer+1; + } + /* Write WAVE buffer to Wave Device. */ + if( gotOutput ) + { +#if PA_TRACE_START_STOP + AddTraceMessage( "Pa_TimeSlice: writing buffer ", wmmeStreamData->currentOutputBuffer ); +#endif + mmresult = waveOutWrite( wmmeStreamData->hWaveOut, + &wmmeStreamData->outputBuffers[ wmmeStreamData->currentOutputBuffer ], + sizeof(WAVEHDR) ); + if( mmresult != MMSYSERR_NOERROR ) + { + sPaHostError = mmresult; + result = paHostError; + break; + } + wmmeStreamData->currentOutputBuffer = (wmmeStreamData->currentOutputBuffer+1 >= wmmeStreamData->numHostBuffers) ? + 0 : wmmeStreamData->currentOutputBuffer+1; + } + + } + +#if PA_TRACE_RUN + AddTraceMessage("Pa_TimeSlice: buffersProcessed ", buffersProcessed ); +#endif + return (result != 0) ? result : done; +} + +/*******************************************************************/ +static PaError PaHost_BackgroundManager( internalPortAudioStream *stream ) +{ + PaError result = paNoError; + int i; + int numQueuedoutputBuffers = 0; + PaWMMEStreamData *wmmeStreamData = (PaWMMEStreamData *) stream->past_DeviceData; + + /* Has someone asked us to abort by calling Pa_AbortStream()? */ + if( stream->past_StopNow ) + { + stream->past_IsActive = 0; /* Will cause thread to return. */ + } + /* Has someone asked us to stop by calling Pa_StopStream() + * OR has a user callback returned '1' to indicate finished. + */ + else if( stream->past_StopSoon ) + { + /* Poll buffer and when all have played then exit thread. */ + /* Count how many output buffers are queued. */ + numQueuedoutputBuffers = 0; + if( stream->past_NumOutputChannels > 0 ) + { + for( i=0; i<wmmeStreamData->numHostBuffers; i++ ) + { + if( !( wmmeStreamData->outputBuffers[ i ].dwFlags & WHDR_DONE) ) + { +#if PA_TRACE_START_STOP + AddTraceMessage( "PaHost_BackgroundManager: waiting for buffer ", i ); +#endif + numQueuedoutputBuffers++; + } + } + } +#if PA_TRACE_START_STOP + AddTraceMessage( "PaHost_BackgroundManager: numQueuedoutputBuffers ", numQueuedoutputBuffers ); +#endif + if( numQueuedoutputBuffers == 0 ) + { + stream->past_IsActive = 0; /* Will cause thread to return. */ + } + } + else + { + /* Process full input buffer and fill up empty output buffers. */ + if( (result = Pa_TimeSlice( stream )) != 0) + { + /* User callback has asked us to stop. */ +#if PA_TRACE_START_STOP + AddTraceMessage( "PaHost_BackgroundManager: TimeSlice() returned ", result ); +#endif + stream->past_StopSoon = 1; /* Request that audio play out then stop. */ + result = paNoError; + } + } + + PaHost_UpdateStreamTime( wmmeStreamData ); + return result; +} + +#if PA_USE_TIMER_CALLBACK +/*******************************************************************/ +static void CALLBACK Pa_TimerCallback(UINT uID, UINT uMsg, DWORD dwUser, DWORD dw1, DWORD dw2) +{ + internalPortAudioStream *stream; + PaWMMEStreamData *wmmeStreamData; + PaError result; + + stream = (internalPortAudioStream *) dwUser; + if( stream == NULL ) return; + wmmeStreamData = (PaWMMEStreamData *) stream->past_DeviceData; + if( wmmeStreamData == NULL ) return; + if( wmmeStreamData->ifInsideCallback ) + { + if( wmmeStreamData->timerID != 0 ) + { + timeKillEvent(wmmeStreamData->timerID); /* Stop callback timer. */ + wmmeStreamData->timerID = 0; + } + return; + } + wmmeStreamData->ifInsideCallback = 1; + /* Manage flags and audio processing. */ + result = PaHost_BackgroundManager( stream ); + if( result != paNoError ) + { + stream->past_IsActive = 0; + } + wmmeStreamData->ifInsideCallback = 0; +} +#else /* PA_USE_TIMER_CALLBACK */ +/*******************************************************************/ +static DWORD WINAPI WinMMPa_OutputThreadProc( void *pArg ) +{ + internalPortAudioStream *stream; + PaWMMEStreamData *wmmeStreamData; + HANDLE events[2]; + int numEvents = 0; + DWORD result = 0; + DWORD waitResult; + DWORD numTimeouts = 0; + DWORD timeOut; + stream = (internalPortAudioStream *) pArg; + wmmeStreamData = (PaWMMEStreamData *) stream->past_DeviceData; +#if PA_TRACE_START_STOP + AddTraceMessage( "WinMMPa_OutputThreadProc: timeoutPeriod", timeoutPeriod ); + AddTraceMessage( "WinMMPa_OutputThreadProc: past_NumUserBuffers", stream->past_NumUserBuffers ); +#endif + /* Calculate timeOut as half the time it would take to play all buffers. */ + timeOut = (DWORD) (500.0 * PaHost_GetTotalBufferFrames( stream ) / stream->past_SampleRate); + /* Get event(s) ready for wait. */ + events[numEvents++] = wmmeStreamData->bufferEvent; + if( wmmeStreamData->abortEventInited ) events[numEvents++] = wmmeStreamData->abortEvent; + /* Stay in this thread as long as we are "active". */ + while( stream->past_IsActive ) + { + /*******************************************************************/ + /******** WAIT here for an event from WMME or PA *******************/ + /*******************************************************************/ + waitResult = WaitForMultipleObjects( numEvents, events, FALSE, timeOut ); + /* Error? */ + if( waitResult == WAIT_FAILED ) + { + sPaHostError = GetLastError(); + result = paHostError; + stream->past_IsActive = 0; + } + /* Timeout? Don't stop. Just keep polling for DONE.*/ + else if( waitResult == WAIT_TIMEOUT ) + { +#if PA_TRACE_START_STOP + AddTraceMessage( "WinMMPa_OutputThreadProc: timed out ", numQueuedoutputBuffers ); +#endif + numTimeouts += 1; + } + /* Manage flags and audio processing. */ + result = PaHost_BackgroundManager( stream ); + if( result != paNoError ) + { + stream->past_IsActive = 0; + } + } + return result; +} +#endif + +/*******************************************************************/ +PaError PaHost_OpenInputStream( internalPortAudioStream *stream ) +{ + PaError result = paNoError; + MMRESULT mmresult; + PaWMMEStreamData *wmmeStreamData; + int i; + int inputMmId; + int bytesPerInputFrame; + WAVEFORMATEX wfx; + const PaDeviceInfo *deviceInfo; + + wmmeStreamData = (PaWMMEStreamData *) stream->past_DeviceData; + DBUG(("PaHost_OpenStream: deviceID = 0x%x\n", stream->past_InputDeviceID)); + deviceInfo = Pa_GetDeviceInfo( stream->past_InputDeviceID ); + if( deviceInfo == NULL ) return paInternalError; + + switch( deviceInfo->nativeSampleFormats ) + { + case paInt32: + case paFloat32: + bytesPerInputFrame = sizeof(float) * stream->past_NumInputChannels; + break; + default: + bytesPerInputFrame = sizeof(short) * stream->past_NumInputChannels; + break; + } + wfx.wFormatTag = WAVE_FORMAT_PCM; + wfx.nChannels = (WORD) stream->past_NumInputChannels; + wfx.nSamplesPerSec = (DWORD) stream->past_SampleRate; + wfx.nAvgBytesPerSec = (DWORD)(bytesPerInputFrame * stream->past_SampleRate); + wfx.nBlockAlign = (WORD)bytesPerInputFrame; + wfx.wBitsPerSample = (WORD)((bytesPerInputFrame/stream->past_NumInputChannels) * 8); + wfx.cbSize = 0; + inputMmId = PaDeviceIdToWinId( stream->past_InputDeviceID ); +#if PA_USE_TIMER_CALLBACK + mmresult = waveInOpen( &wmmeStreamData->hWaveIn, inputMmId, &wfx, + 0, 0, CALLBACK_NULL ); +#else + mmresult = waveInOpen( &wmmeStreamData->hWaveIn, inputMmId, &wfx, + (DWORD)wmmeStreamData->bufferEvent, (DWORD) stream, CALLBACK_EVENT ); +#endif + if( mmresult != MMSYSERR_NOERROR ) + { + ERR_RPT(("PortAudio: PaHost_OpenInputStream() failed!\n")); + result = paHostError; + sPaHostError = mmresult; + goto error; + } + /* Allocate an array to hold the buffer pointers. */ + wmmeStreamData->inputBuffers = (WAVEHDR *) PaHost_AllocateTrackedMemory( sizeof(WAVEHDR)*wmmeStreamData->numHostBuffers ); /* MEM */ + if( wmmeStreamData->inputBuffers == NULL ) + { + result = paInsufficientMemory; + goto error; + } + /* Allocate each buffer. */ + for( i=0; i<wmmeStreamData->numHostBuffers; i++ ) + { + wmmeStreamData->inputBuffers[i].lpData = (char *)PaHost_AllocateTrackedMemory( wmmeStreamData->bytesPerHostInputBuffer ); /* MEM */ + if( wmmeStreamData->inputBuffers[i].lpData == NULL ) + { + result = paInsufficientMemory; + goto error; + } + wmmeStreamData->inputBuffers[i].dwBufferLength = wmmeStreamData->bytesPerHostInputBuffer; + wmmeStreamData->inputBuffers[i].dwUser = i; + if( ( mmresult = waveInPrepareHeader( wmmeStreamData->hWaveIn, &wmmeStreamData->inputBuffers[i], sizeof(WAVEHDR) )) != MMSYSERR_NOERROR ) + { + result = paHostError; + sPaHostError = mmresult; + goto error; + } + } + return result; + +error: + return result; +} +/*******************************************************************/ +PaError PaHost_OpenOutputStream( internalPortAudioStream *stream ) +{ + PaError result = paNoError; + MMRESULT mmresult; + PaWMMEStreamData *wmmeStreamData; + int i; + int outputMmID; + int bytesPerOutputFrame; + WAVEFORMATEX wfx; + const PaDeviceInfo *deviceInfo; + + wmmeStreamData = (PaWMMEStreamData *) stream->past_DeviceData; + DBUG(("PaHost_OpenStream: deviceID = 0x%x\n", stream->past_OutputDeviceID)); + + deviceInfo = Pa_GetDeviceInfo( stream->past_OutputDeviceID ); + if( deviceInfo == NULL ) return paInternalError; + + switch( deviceInfo->nativeSampleFormats ) + { + case paInt32: + case paFloat32: + bytesPerOutputFrame = sizeof(float) * stream->past_NumOutputChannels; + break; + default: + bytesPerOutputFrame = sizeof(short) * stream->past_NumOutputChannels; + break; + } + wfx.wFormatTag = WAVE_FORMAT_PCM; + wfx.nChannels = (WORD) stream->past_NumOutputChannels; + wfx.nSamplesPerSec = (DWORD) stream->past_SampleRate; + wfx.nAvgBytesPerSec = (DWORD)(bytesPerOutputFrame * stream->past_SampleRate); + wfx.nBlockAlign = (WORD)bytesPerOutputFrame; + wfx.wBitsPerSample = (WORD)((bytesPerOutputFrame/stream->past_NumOutputChannels) * 8); + wfx.cbSize = 0; + outputMmID = PaDeviceIdToWinId( stream->past_OutputDeviceID ); +#if PA_USE_TIMER_CALLBACK + mmresult = waveOutOpen( &wmmeStreamData->hWaveOut, outputMmID, &wfx, + 0, 0, CALLBACK_NULL ); +#else + + wmmeStreamData->abortEvent = CreateEvent( NULL, TRUE, FALSE, NULL ); + if( wmmeStreamData->abortEvent == NULL ) + { + result = paHostError; + sPaHostError = GetLastError(); + goto error; + } + wmmeStreamData->abortEventInited = 1; + mmresult = waveOutOpen( &wmmeStreamData->hWaveOut, outputMmID, &wfx, + (DWORD)wmmeStreamData->bufferEvent, (DWORD) stream, CALLBACK_EVENT ); +#endif + if( mmresult != MMSYSERR_NOERROR ) + { + ERR_RPT(("PortAudio: PaHost_OpenOutputStream() failed!\n")); + result = paHostError; + sPaHostError = mmresult; + goto error; + } + /* Allocate an array to hold the buffer pointers. */ + wmmeStreamData->outputBuffers = (WAVEHDR *) PaHost_AllocateTrackedMemory( sizeof(WAVEHDR)*wmmeStreamData->numHostBuffers ); /* MEM */ + if( wmmeStreamData->outputBuffers == NULL ) + { + result = paInsufficientMemory; + goto error; + } + /* Allocate each buffer. */ + for( i=0; i<wmmeStreamData->numHostBuffers; i++ ) + { + wmmeStreamData->outputBuffers[i].lpData = (char *) PaHost_AllocateTrackedMemory( wmmeStreamData->bytesPerHostOutputBuffer ); /* MEM */ + if( wmmeStreamData->outputBuffers[i].lpData == NULL ) + { + result = paInsufficientMemory; + goto error; + } + wmmeStreamData->outputBuffers[i].dwBufferLength = wmmeStreamData->bytesPerHostOutputBuffer; + wmmeStreamData->outputBuffers[i].dwUser = i; + if( (mmresult = waveOutPrepareHeader( wmmeStreamData->hWaveOut, &wmmeStreamData->outputBuffers[i], sizeof(WAVEHDR) )) != MMSYSERR_NOERROR ) + { + result = paHostError; + sPaHostError = mmresult; + goto error; + } + } + return result; + +error: + return result; +} +/*******************************************************************/ +PaError PaHost_GetTotalBufferFrames( internalPortAudioStream *stream ) +{ + PaWMMEStreamData *wmmeStreamData = (PaWMMEStreamData *) stream->past_DeviceData; + return wmmeStreamData->numHostBuffers * wmmeStreamData->framesPerHostBuffer; +} +/******************************************************************* + * Determine number of WAVE Buffers + * and how many User Buffers we can put into each WAVE buffer. + */ +static void PaHost_CalcNumHostBuffers( internalPortAudioStream *stream ) +{ + PaWMMEStreamData *wmmeStreamData = (PaWMMEStreamData *) stream->past_DeviceData; + unsigned int minNumBuffers; + int minframesPerHostBuffer; + int maxframesPerHostBuffer; + int minTotalFrames; + int userBuffersPerHostBuffer; + int framesPerHostBuffer; + int numHostBuffers; + + /* Calculate minimum and maximum sizes based on timing and sample rate. */ + minframesPerHostBuffer = (int) (PA_MIN_MSEC_PER_HOST_BUFFER * stream->past_SampleRate * 0.001); + minframesPerHostBuffer = (minframesPerHostBuffer + 7) & ~7; + DBUG(("PaHost_CalcNumHostBuffers: minframesPerHostBuffer = %d\n", minframesPerHostBuffer )); + maxframesPerHostBuffer = (int) (PA_MAX_MSEC_PER_HOST_BUFFER * stream->past_SampleRate * 0.001); + maxframesPerHostBuffer = (maxframesPerHostBuffer + 7) & ~7; + DBUG(("PaHost_CalcNumHostBuffers: maxframesPerHostBuffer = %d\n", maxframesPerHostBuffer )); + /* Determine number of user buffers based on minimum latency. */ + minNumBuffers = Pa_GetMinNumBuffers( stream->past_FramesPerUserBuffer, stream->past_SampleRate ); + stream->past_NumUserBuffers = ( minNumBuffers > stream->past_NumUserBuffers ) ? minNumBuffers : stream->past_NumUserBuffers; + DBUG(("PaHost_CalcNumHostBuffers: min past_NumUserBuffers = %d\n", stream->past_NumUserBuffers )); + minTotalFrames = stream->past_NumUserBuffers * stream->past_FramesPerUserBuffer; + /* We cannot make the WAVE buffers too small because they may not get serviced quickly enough. */ + if( (int) stream->past_FramesPerUserBuffer < minframesPerHostBuffer ) + { + userBuffersPerHostBuffer = + (minframesPerHostBuffer + stream->past_FramesPerUserBuffer - 1) / + stream->past_FramesPerUserBuffer; + } + else + { + userBuffersPerHostBuffer = 1; + } + framesPerHostBuffer = stream->past_FramesPerUserBuffer * userBuffersPerHostBuffer; + /* Calculate number of WAVE buffers needed. Round up to cover minTotalFrames. */ + numHostBuffers = (minTotalFrames + framesPerHostBuffer - 1) / framesPerHostBuffer; + /* Make sure we have anough WAVE buffers. */ + if( numHostBuffers < PA_MIN_NUM_HOST_BUFFERS) + { + numHostBuffers = PA_MIN_NUM_HOST_BUFFERS; + } + else if( (numHostBuffers > PA_MAX_NUM_HOST_BUFFERS) && + ((int) stream->past_FramesPerUserBuffer < (maxframesPerHostBuffer/2) ) ) + { + /* If we have too many WAVE buffers, try to put more user buffers in a wave buffer. */ + while(numHostBuffers > PA_MAX_NUM_HOST_BUFFERS) + { + userBuffersPerHostBuffer += 1; + framesPerHostBuffer = stream->past_FramesPerUserBuffer * userBuffersPerHostBuffer; + numHostBuffers = (minTotalFrames + framesPerHostBuffer - 1) / framesPerHostBuffer; + /* If we have gone too far, back up one. */ + if( (framesPerHostBuffer > maxframesPerHostBuffer) || + (numHostBuffers < PA_MAX_NUM_HOST_BUFFERS) ) + { + userBuffersPerHostBuffer -= 1; + framesPerHostBuffer = stream->past_FramesPerUserBuffer * userBuffersPerHostBuffer; + numHostBuffers = (minTotalFrames + framesPerHostBuffer - 1) / framesPerHostBuffer; + break; + } + } + } + + wmmeStreamData->userBuffersPerHostBuffer = userBuffersPerHostBuffer; + wmmeStreamData->framesPerHostBuffer = framesPerHostBuffer; + wmmeStreamData->numHostBuffers = numHostBuffers; + DBUG(("PaHost_CalcNumHostBuffers: userBuffersPerHostBuffer = %d\n", wmmeStreamData->userBuffersPerHostBuffer )); + DBUG(("PaHost_CalcNumHostBuffers: numHostBuffers = %d\n", wmmeStreamData->numHostBuffers )); + DBUG(("PaHost_CalcNumHostBuffers: framesPerHostBuffer = %d\n", wmmeStreamData->framesPerHostBuffer )); + DBUG(("PaHost_CalcNumHostBuffers: past_NumUserBuffers = %d\n", stream->past_NumUserBuffers )); +} +/*******************************************************************/ +PaError PaHost_OpenStream( internalPortAudioStream *stream ) +{ + PaError result = paNoError; + PaWMMEStreamData *wmmeStreamData; + + result = PaHost_AllocateWMMEStreamData( stream ); + if( result != paNoError ) return result; + + wmmeStreamData = PaHost_GetWMMEStreamData( stream ); + + /* Figure out how user buffers fit into WAVE buffers. */ + PaHost_CalcNumHostBuffers( stream ); + { + int msecLatency = (int) ((PaHost_GetTotalBufferFrames(stream) * 1000) / stream->past_SampleRate); + DBUG(("PortAudio on WMME - Latency = %d frames, %d msec\n", PaHost_GetTotalBufferFrames(stream), msecLatency )); + } + InitializeCriticalSection( &wmmeStreamData->streamLock ); + wmmeStreamData->streamLockInited = 1; + +#if (PA_USE_TIMER_CALLBACK == 0) + wmmeStreamData->bufferEventInited = 0; + wmmeStreamData->bufferEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); + if( wmmeStreamData->bufferEvent == NULL ) + { + result = paHostError; + sPaHostError = GetLastError(); + goto error; + } + wmmeStreamData->bufferEventInited = 1; +#endif /* (PA_USE_TIMER_CALLBACK == 0) */ + /* ------------------ OUTPUT */ + wmmeStreamData->bytesPerUserOutputBuffer = stream->past_FramesPerUserBuffer * stream->past_NumOutputChannels * sizeof(short); + wmmeStreamData->bytesPerHostOutputBuffer = wmmeStreamData->userBuffersPerHostBuffer * wmmeStreamData->bytesPerUserOutputBuffer; + if( (stream->past_OutputDeviceID != paNoDevice) && (stream->past_NumOutputChannels > 0) ) + { + result = PaHost_OpenOutputStream( stream ); + if( result < 0 ) goto error; + } + /* ------------------ INPUT */ + wmmeStreamData->bytesPerUserInputBuffer = stream->past_FramesPerUserBuffer * stream->past_NumInputChannels * sizeof(short); + wmmeStreamData->bytesPerHostInputBuffer = wmmeStreamData->userBuffersPerHostBuffer * wmmeStreamData->bytesPerUserInputBuffer; + if( (stream->past_InputDeviceID != paNoDevice) && (stream->past_NumInputChannels > 0) ) + { + result = PaHost_OpenInputStream( stream ); + if( result < 0 ) goto error; + } + + Pa_InitializeCpuUsageScalar( stream ); + + return result; + +error: + PaHost_CloseStream( stream ); + return result; +} +/*************************************************************************/ +PaError PaHost_StartOutput( internalPortAudioStream *stream ) +{ + PaError result = paNoError; + MMRESULT mmresult; + int i; + PaWMMEStreamData *wmmeStreamData = PaHost_GetWMMEStreamData( stream ); + + if( wmmeStreamData == NULL ) return paInternalError; + + if( stream->past_OutputDeviceID != paNoDevice ) + { + if( (mmresult = waveOutPause( wmmeStreamData->hWaveOut )) != MMSYSERR_NOERROR ) + { + result = paHostError; + sPaHostError = mmresult; + goto error; + } + for( i=0; i<wmmeStreamData->numHostBuffers; i++ ) + { + ZeroMemory( wmmeStreamData->outputBuffers[i].lpData, wmmeStreamData->outputBuffers[i].dwBufferLength ); + mmresult = waveOutWrite( wmmeStreamData->hWaveOut, &wmmeStreamData->outputBuffers[i], sizeof(WAVEHDR) ); + if( mmresult != MMSYSERR_NOERROR ) + { + result = paHostError; + sPaHostError = mmresult; + goto error; + } + stream->past_FrameCount += wmmeStreamData->framesPerHostBuffer; + } + wmmeStreamData->currentOutputBuffer = 0; + if( (mmresult = waveOutRestart( wmmeStreamData->hWaveOut )) != MMSYSERR_NOERROR ) + { + result = paHostError; + sPaHostError = mmresult; + goto error; + } + } + +error: + DBUG(("PaHost_StartOutput: wave returned mmresult = 0x%X.\n", mmresult)); + return result; +} +/*************************************************************************/ +PaError PaHost_StartInput( internalPortAudioStream *internalStream ) +{ + PaError result = paNoError; + MMRESULT mmresult; + int i; + PaWMMEStreamData *wmmeStreamData = PaHost_GetWMMEStreamData( internalStream ); + + if( wmmeStreamData == NULL ) return paInternalError; + + if( internalStream->past_InputDeviceID != paNoDevice ) + { + for( i=0; i<wmmeStreamData->numHostBuffers; i++ ) + { + mmresult = waveInAddBuffer( wmmeStreamData->hWaveIn, &wmmeStreamData->inputBuffers[i], sizeof(WAVEHDR) ); + if( mmresult != MMSYSERR_NOERROR ) + { + result = paHostError; + sPaHostError = mmresult; + goto error; + } + } + wmmeStreamData->currentInputBuffer = 0; + mmresult = waveInStart( wmmeStreamData->hWaveIn ); + DBUG(("Pa_StartStream: waveInStart returned = 0x%X.\n", mmresult)); + if( mmresult != MMSYSERR_NOERROR ) + { + result = paHostError; + sPaHostError = mmresult; + goto error; + } + } + +error: + return result; +} +/*************************************************************************/ +PaError PaHost_StartEngine( internalPortAudioStream *stream ) +{ + PaError result = paNoError; + PaWMMEStreamData *wmmeStreamData = PaHost_GetWMMEStreamData( stream ); +#if PA_USE_TIMER_CALLBACK + int resolution; + int bufsPerTimerCallback; + int msecPerBuffer; +#endif /* PA_USE_TIMER_CALLBACK */ + + if( wmmeStreamData == NULL ) return paInternalError; + + stream->past_StopSoon = 0; + stream->past_StopNow = 0; + stream->past_IsActive = 1; + wmmeStreamData->framesPlayed = 0.0; + wmmeStreamData->lastPosition = 0; +#if PA_TRACE_START_STOP + AddTraceMessage( "PaHost_StartEngine: TimeSlice() returned ", result ); +#endif +#if PA_USE_TIMER_CALLBACK + /* Create timer that will wake us up so we can fill the DSound buffer. */ + bufsPerTimerCallback = wmmeStreamData->numHostBuffers/4; + if( bufsPerTimerCallback < 1 ) bufsPerTimerCallback = 1; + if( bufsPerTimerCallback < 1 ) bufsPerTimerCallback = 1; + msecPerBuffer = (1000 * bufsPerTimerCallback * + wmmeStreamData->userBuffersPerHostBuffer * + internalStream->past_FramesPerUserBuffer ) / (int) internalStream->past_SampleRate; + if( msecPerBuffer < 10 ) msecPerBuffer = 10; + else if( msecPerBuffer > 100 ) msecPerBuffer = 100; + resolution = msecPerBuffer/4; + wmmeStreamData->timerID = timeSetEvent( msecPerBuffer, resolution, + (LPTIMECALLBACK) Pa_TimerCallback, + (DWORD) stream, TIME_PERIODIC ); + if( wmmeStreamData->timerID == 0 ) + { + result = paHostError; + sPaHostError = GetLastError();; + goto error; + } +#else /* PA_USE_TIMER_CALLBACK */ + ResetEvent( wmmeStreamData->abortEvent ); + /* Create thread that waits for audio buffers to be ready for processing. */ + wmmeStreamData->engineThread = CreateThread( 0, 0, WinMMPa_OutputThreadProc, stream, 0, &wmmeStreamData->engineThreadID ); + if( wmmeStreamData->engineThread == NULL ) + { + result = paHostError; + sPaHostError = GetLastError();; + goto error; + } +#if PA_TRACE_START_STOP + AddTraceMessage( "PaHost_StartEngine: thread ", (int) wmmeStreamData->engineThread ); +#endif + /* I used to pass the thread which was failing. I now pass GetCurrentProcess(). + * This fix could improve latency for some applications. It could also result in CPU + * starvation if the callback did too much processing. + * I also added result checks, so we might see more failures at initialization. + * Thanks to Alberto di Bene for spotting this. + */ + if( !SetPriorityClass( GetCurrentProcess(), HIGH_PRIORITY_CLASS ) ) /* PLB20010816 */ + { + result = paHostError; + sPaHostError = GetLastError();; + goto error; + } + if( !SetThreadPriority( wmmeStreamData->engineThread, THREAD_PRIORITY_HIGHEST ) ) + { + result = paHostError; + sPaHostError = GetLastError();; + goto error; + } +#endif + +error: + return result; +} +/*************************************************************************/ +PaError PaHost_StopEngine( internalPortAudioStream *internalStream, int abort ) +{ + int timeOut; + PaWMMEStreamData *wmmeStreamData = PaHost_GetWMMEStreamData( internalStream ); + + if( wmmeStreamData == NULL ) return paNoError; + + /* Tell background thread to stop generating more data and to let current data play out. */ + internalStream->past_StopSoon = 1; + /* If aborting, tell background thread to stop NOW! */ + if( abort ) internalStream->past_StopNow = 1; + + /* Calculate timeOut longer than longest time it could take to play all buffers. */ + timeOut = (DWORD) (1500.0 * PaHost_GetTotalBufferFrames( internalStream ) / internalStream->past_SampleRate); + if( timeOut < MIN_TIMEOUT_MSEC ) timeOut = MIN_TIMEOUT_MSEC; + +#if PA_USE_TIMER_CALLBACK + if( (internalStream->past_OutputDeviceID != paNoDevice) && + internalStream->past_IsActive && + (wmmeStreamData->timerID != 0) ) + { + /* Wait for IsActive to drop. */ + while( (internalStream->past_IsActive) && (timeOut > 0) ) + { + Sleep(10); + timeOut -= 10; + } + timeKillEvent( wmmeStreamData->timerID ); /* Stop callback timer. */ + wmmeStreamData->timerID = 0; + } +#else /* PA_USE_TIMER_CALLBACK */ +#if PA_TRACE_START_STOP + AddTraceMessage( "PaHost_StopEngine: thread ", (int) wmmeStreamData->engineThread ); +#endif + if( (internalStream->past_OutputDeviceID != paNoDevice) && + (internalStream->past_IsActive) && + (wmmeStreamData->engineThread != NULL) ) + { + DWORD got; + /* Tell background thread to stop generating more data and to let current data play out. */ + DBUG(("PaHost_StopEngine: waiting for background thread.\n")); + got = WaitForSingleObject( wmmeStreamData->engineThread, timeOut ); + if( got == WAIT_TIMEOUT ) + { + ERR_RPT(("PaHost_StopEngine: timed out while waiting for background thread to finish.\n")); + return paTimedOut; + } + CloseHandle( wmmeStreamData->engineThread ); + wmmeStreamData->engineThread = NULL; + } +#endif /* PA_USE_TIMER_CALLBACK */ + + internalStream->past_IsActive = 0; + return paNoError; +} +/*************************************************************************/ +PaError PaHost_StopInput( internalPortAudioStream *stream, int abort ) +{ + MMRESULT mmresult; + PaWMMEStreamData *wmmeStreamData = PaHost_GetWMMEStreamData( stream ); + + if( wmmeStreamData == NULL ) return paNoError; /* FIXME: why return paNoError? */ + (void) abort; /* unused parameter */ + + if( wmmeStreamData->hWaveIn != NULL ) + { + mmresult = waveInReset( wmmeStreamData->hWaveIn ); + if( mmresult != MMSYSERR_NOERROR ) + { + sPaHostError = mmresult; + return paHostError; + } + } + return paNoError; +} +/*************************************************************************/ +PaError PaHost_StopOutput( internalPortAudioStream *internalStream, int abort ) +{ + MMRESULT mmresult; + PaWMMEStreamData *wmmeStreamData = PaHost_GetWMMEStreamData( internalStream ); + + if( wmmeStreamData == NULL ) return paNoError; /* FIXME: why return paNoError? */ + (void) abort; /* unused parameter */ + +#if PA_TRACE_START_STOP + AddTraceMessage( "PaHost_StopOutput: hWaveOut ", (int) wmmeStreamData->hWaveOut ); +#endif + if( wmmeStreamData->hWaveOut != NULL ) + { + mmresult = waveOutReset( wmmeStreamData->hWaveOut ); + if( mmresult != MMSYSERR_NOERROR ) + { + sPaHostError = mmresult; + return paHostError; + } + } + return paNoError; +} +/*******************************************************************/ +PaError PaHost_CloseStream( internalPortAudioStream *stream ) +{ + int i; + PaWMMEStreamData *wmmeStreamData = PaHost_GetWMMEStreamData( stream ); + + if( stream == NULL ) return paBadStreamPtr; + if( wmmeStreamData == NULL ) return paNoError; /* FIXME: why return no error? */ + +#if PA_TRACE_START_STOP + AddTraceMessage( "PaHost_CloseStream: hWaveOut ", (int) wmmeStreamData->hWaveOut ); +#endif + /* Free data and device for output. */ + if( wmmeStreamData->hWaveOut ) + { + if( wmmeStreamData->outputBuffers ) + { + for( i=0; i<wmmeStreamData->numHostBuffers; i++ ) + { + waveOutUnprepareHeader( wmmeStreamData->hWaveOut, &wmmeStreamData->outputBuffers[i], sizeof(WAVEHDR) ); + PaHost_FreeTrackedMemory( wmmeStreamData->outputBuffers[i].lpData ); /* MEM */ + } + PaHost_FreeTrackedMemory( wmmeStreamData->outputBuffers ); /* MEM */ + } + waveOutClose( wmmeStreamData->hWaveOut ); + } + /* Free data and device for input. */ + if( wmmeStreamData->hWaveIn ) + { + if( wmmeStreamData->inputBuffers ) + { + for( i=0; i<wmmeStreamData->numHostBuffers; i++ ) + { + waveInUnprepareHeader( wmmeStreamData->hWaveIn, &wmmeStreamData->inputBuffers[i], sizeof(WAVEHDR) ); + PaHost_FreeTrackedMemory( wmmeStreamData->inputBuffers[i].lpData ); /* MEM */ + } + PaHost_FreeTrackedMemory( wmmeStreamData->inputBuffers ); /* MEM */ + } + waveInClose( wmmeStreamData->hWaveIn ); + } +#if (PA_USE_TIMER_CALLBACK == 0) + if( wmmeStreamData->abortEventInited ) CloseHandle( wmmeStreamData->abortEvent ); + if( wmmeStreamData->bufferEventInited ) CloseHandle( wmmeStreamData->bufferEvent ); +#endif + if( wmmeStreamData->streamLockInited ) + DeleteCriticalSection( &wmmeStreamData->streamLock ); + + PaHost_FreeWMMEStreamData( stream ); + + return paNoError; +} +/************************************************************************* + * Determine minimum number of buffers required for this host based + * on minimum latency. Latency can be optionally set by user by setting + * an environment variable. For example, to set latency to 200 msec, put: + * + * set PA_MIN_LATENCY_MSEC=200 + * + * in the AUTOEXEC.BAT file and reboot. + * If the environment variable is not set, then the latency will be determined + * based on the OS. Windows NT has higher latency than Win95. + */ +#define PA_LATENCY_ENV_NAME ("PA_MIN_LATENCY_MSEC") +int Pa_GetMinNumBuffers( int framesPerBuffer, double sampleRate ) +{ + char envbuf[PA_ENV_BUF_SIZE]; + DWORD hresult; + int minLatencyMsec = 0; + double msecPerBuffer = (1000.0 * framesPerBuffer) / sampleRate; + int minBuffers; + + /* Let user determine minimal latency by setting environment variable. */ + hresult = GetEnvironmentVariable( PA_LATENCY_ENV_NAME, envbuf, PA_ENV_BUF_SIZE ); + if( (hresult > 0) && (hresult < PA_ENV_BUF_SIZE) ) + { + minLatencyMsec = atoi( envbuf ); /* REVIEW: will we crash if the environment variable contains some nasty value? */ + } + else + { + /* Set minimal latency based on whether NT or other OS. + * NT has higher latency. + */ + OSVERSIONINFO osvi; + osvi.dwOSVersionInfoSize = sizeof( osvi ); + GetVersionEx( &osvi ); + DBUG(("PA - PlatformId = 0x%x\n", osvi.dwPlatformId )); + DBUG(("PA - MajorVersion = 0x%x\n", osvi.dwMajorVersion )); + DBUG(("PA - MinorVersion = 0x%x\n", osvi.dwMinorVersion )); + /* Check for NT */ + if( (osvi.dwMajorVersion == 4) && (osvi.dwPlatformId == 2) ) + { + minLatencyMsec = PA_WIN_NT_LATENCY; + } + else if(osvi.dwMajorVersion >= 5) + { + minLatencyMsec = PA_WIN_WDM_LATENCY; + } + else + { + minLatencyMsec = PA_WIN_9X_LATENCY; + } +#if PA_USE_HIGH_LATENCY + PRINT(("PA - Minimum Latency set to %d msec!\n", minLatencyMsec )); +#endif + + } + DBUG(("PA - Minimum Latency set to %d msec!\n", minLatencyMsec )); + minBuffers = (int) (1.0 + ((double)minLatencyMsec / msecPerBuffer)); + if( minBuffers < 2 ) minBuffers = 2; + return minBuffers; +} +/************************************************************************* + * Cleanup device info. + */ +PaError PaHost_Term( void ) +{ + int i; + + if( sNumDevices > 0 ) + { + if( sDevicePtrs != NULL ) + { + for( i=0; i<sNumDevices; i++ ) + { + if( sDevicePtrs[i] != NULL ) + { + PaHost_FreeTrackedMemory( (char*)sDevicePtrs[i]->name ); /* MEM */ + PaHost_FreeTrackedMemory( (void*)sDevicePtrs[i]->sampleRates ); /* MEM */ + PaHost_FreeTrackedMemory( sDevicePtrs[i] ); /* MEM */ + } + } + PaHost_FreeTrackedMemory( sDevicePtrs ); /* MEM */ + sDevicePtrs = NULL; + } + sNumDevices = 0; + } + +#if PA_TRACK_MEMORY + PRINT(("PaHost_Term: sNumAllocations = %d\n", sNumAllocations )); +#endif + + return paNoError; +} +/*************************************************************************/ +void Pa_Sleep( long msec ) +{ + Sleep( msec ); +} +/************************************************************************* +FIXME: the following memory allocation routines should not be declared here + * Allocate memory that can be accessed in real-time. + * This may need to be held in physical memory so that it is not + * paged to virtual memory. + * This call MUST be balanced with a call to PaHost_FreeFastMemory(). + * Memory will be set to zero. + */ +void *PaHost_AllocateFastMemory( long numBytes ) +{ + return PaHost_AllocateTrackedMemory( numBytes ); /* FIXME - do we need physical memory? Use VirtualLock() */ /* MEM */ +} +/************************************************************************* + * Free memory that could be accessed in real-time. + * This call MUST be balanced with a call to PaHost_AllocateFastMemory(). + */ +void PaHost_FreeFastMemory( void *addr, long numBytes ) +{ + (void) numBytes; /* unused parameter */ + + PaHost_FreeTrackedMemory( addr ); /* MEM */ +} + +/************************************************************************* + * Track memory allocations to avoid leaks. + */ +static void *PaHost_AllocateTrackedMemory( long numBytes ) +{ + void *result = GlobalAlloc( GPTR, numBytes ); /* MEM */ + +#if PA_TRACK_MEMORY + if( result != NULL ) sNumAllocations += 1; +#endif + return result; +} + +static void PaHost_FreeTrackedMemory( void *addr ) +{ + if( addr != NULL ) + { + GlobalFree( addr ); /* MEM */ +#if PA_TRACK_MEMORY + sNumAllocations -= 1; +#endif + } +} + +/***********************************************************************/ +PaError PaHost_StreamActive( internalPortAudioStream *internalStream ) +{ + if( internalStream == NULL ) return paBadStreamPtr; + + return (PaError) internalStream->past_IsActive; +} +/************************************************************************* + * This must be called periodically because mmtime.u.sample + * is a DWORD and can wrap and lose sync after a few hours. + */ +static PaError PaHost_UpdateStreamTime( PaWMMEStreamData *wmmeStreamData ) +{ + MMRESULT mmresult; + MMTIME mmtime; + mmtime.wType = TIME_SAMPLES; + + if( wmmeStreamData->hWaveOut != NULL ) + { + mmresult = waveOutGetPosition( wmmeStreamData->hWaveOut, &mmtime, sizeof(mmtime) ); + } + else + { + mmresult = waveInGetPosition( wmmeStreamData->hWaveIn, &mmtime, sizeof(mmtime) ); + } + + if( mmresult != MMSYSERR_NOERROR ) + { + sPaHostError = mmresult; + return paHostError; + } + + /* This data has two variables and is shared by foreground and background. + * So we need to make it thread safe. */ + EnterCriticalSection( &wmmeStreamData->streamLock ); + wmmeStreamData->framesPlayed += ((long)mmtime.u.sample) - wmmeStreamData->lastPosition; + wmmeStreamData->lastPosition = (long)mmtime.u.sample; + LeaveCriticalSection( &wmmeStreamData->streamLock ); + + return paNoError; +} +/*************************************************************************/ +PaTimestamp Pa_StreamTime( PortAudioStream *stream ) +{ + internalPortAudioStream *internalStream = PaHost_GetStreamRepresentation( stream ); + PaWMMEStreamData *wmmeStreamData = PaHost_GetWMMEStreamData( internalStream ); + + if( internalStream == NULL ) return paBadStreamPtr; + if( wmmeStreamData == NULL ) return paInternalError; + + PaHost_UpdateStreamTime( wmmeStreamData ); + return wmmeStreamData->framesPlayed; +} +/*************************************************************************/ + + + |