cannam@89: /* cannam@89: * $Id: $ cannam@89: * Portable Audio I/O Library cannam@89: * Windows MME low level buffer user guided parameters search cannam@89: * cannam@89: * Copyright (c) 2010 Ross Bencina cannam@89: * cannam@89: * Permission is hereby granted, free of charge, to any person obtaining cannam@89: * a copy of this software and associated documentation files cannam@89: * (the "Software"), to deal in the Software without restriction, cannam@89: * including without limitation the rights to use, copy, modify, merge, cannam@89: * publish, distribute, sublicense, and/or sell copies of the Software, cannam@89: * and to permit persons to whom the Software is furnished to do so, cannam@89: * subject to the following conditions: cannam@89: * cannam@89: * The above copyright notice and this permission notice shall be cannam@89: * included in all copies or substantial portions of the Software. cannam@89: * cannam@89: * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, cannam@89: * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF cannam@89: * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. cannam@89: * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR cannam@89: * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF cannam@89: * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION cannam@89: * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. cannam@89: */ cannam@89: cannam@89: /* cannam@89: * The text above constitutes the entire PortAudio license; however, cannam@89: * the PortAudio community also makes the following non-binding requests: cannam@89: * cannam@89: * Any person wishing to distribute modifications to the Software is cannam@89: * requested to send the modifications to the original developer so that cannam@89: * they can be incorporated into the canonical version. It is also cannam@89: * requested that these non-binding requests be included along with the cannam@89: * license above. cannam@89: */ cannam@89: cannam@89: #include cannam@89: #include cannam@89: #include cannam@89: cannam@89: #define _WIN32_WINNT 0x0501 /* for GetNativeSystemInfo */ cannam@89: #include /* required when using pa_win_wmme.h */ cannam@89: #include /* required when using pa_win_wmme.h */ cannam@89: cannam@89: #include /* for _getch */ cannam@89: cannam@89: cannam@89: #include "portaudio.h" cannam@89: #include "pa_win_wmme.h" cannam@89: cannam@89: cannam@89: #define DEFAULT_SAMPLE_RATE (44100.) cannam@89: cannam@89: #ifndef M_PI cannam@89: #define M_PI (3.14159265) cannam@89: #endif cannam@89: cannam@89: #define TABLE_SIZE (2048) cannam@89: cannam@89: #define CHANNEL_COUNT (2) cannam@89: cannam@89: cannam@89: /* seach parameters. we test all buffer counts in this range */ cannam@89: #define MIN_WMME_BUFFER_COUNT (2) cannam@89: #define MAX_WMME_BUFFER_COUNT (12) cannam@89: cannam@89: cannam@89: /*******************************************************************/ cannam@89: /* functions to query and print Windows version information */ cannam@89: cannam@89: typedef BOOL (WINAPI *LPFN_ISWOW64PROCESS) (HANDLE, PBOOL); cannam@89: cannam@89: LPFN_ISWOW64PROCESS fnIsWow64Process; cannam@89: cannam@89: static BOOL IsWow64() cannam@89: { cannam@89: BOOL bIsWow64 = FALSE; cannam@89: cannam@89: //IsWow64Process is not available on all supported versions of Windows. cannam@89: //Use GetModuleHandle to get a handle to the DLL that contains the function cannam@89: //and GetProcAddress to get a pointer to the function if available. cannam@89: cannam@89: fnIsWow64Process = (LPFN_ISWOW64PROCESS) GetProcAddress( cannam@89: GetModuleHandle(TEXT("kernel32")),"IsWow64Process" ); cannam@89: cannam@89: if(NULL != fnIsWow64Process) cannam@89: { cannam@89: if (!fnIsWow64Process(GetCurrentProcess(),&bIsWow64)) cannam@89: { cannam@89: //handle error cannam@89: } cannam@89: } cannam@89: return bIsWow64; cannam@89: } cannam@89: cannam@89: static void printWindowsVersionInfo( FILE *fp ) cannam@89: { cannam@89: OSVERSIONINFOEX osVersionInfoEx; cannam@89: SYSTEM_INFO systemInfo; cannam@89: const char *osName = "Unknown"; cannam@89: const char *osProductType = ""; cannam@89: const char *processorArchitecture = "Unknown"; cannam@89: cannam@89: memset( &osVersionInfoEx, 0, sizeof(OSVERSIONINFOEX) ); cannam@89: osVersionInfoEx.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); cannam@89: GetVersionEx( &osVersionInfoEx ); cannam@89: cannam@89: cannam@89: if( osVersionInfoEx.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS ){ cannam@89: switch( osVersionInfoEx.dwMinorVersion ){ cannam@89: case 0: osName = "Windows 95"; break; cannam@89: case 10: osName = "Windows 98"; break; // could also be 98SE (I've seen code discriminate based cannam@89: // on osInfo.Version.Revision.ToString() == "2222A") cannam@89: case 90: osName = "Windows Me"; break; cannam@89: } cannam@89: }else if( osVersionInfoEx.dwPlatformId == VER_PLATFORM_WIN32_NT ){ cannam@89: switch( osVersionInfoEx.dwMajorVersion ){ cannam@89: case 3: osName = "Windows NT 3.51"; break; cannam@89: case 4: osName = "Windows NT 4.0"; break; cannam@89: case 5: switch( osVersionInfoEx.dwMinorVersion ){ cannam@89: case 0: osName = "Windows 2000"; break; cannam@89: case 1: osName = "Windows XP"; break; cannam@89: case 2: cannam@89: if( osVersionInfoEx.wSuiteMask & 0x00008000 /*VER_SUITE_WH_SERVER*/ ){ cannam@89: osName = "Windows Home Server"; cannam@89: }else{ cannam@89: if( osVersionInfoEx.wProductType == VER_NT_WORKSTATION ){ cannam@89: osName = "Windows XP Professional x64 Edition (?)"; cannam@89: }else{ cannam@89: if( GetSystemMetrics(/*SM_SERVERR2*/89) == 0 ) cannam@89: osName = "Windows Server 2003"; cannam@89: else cannam@89: osName = "Windows Server 2003 R2"; cannam@89: } cannam@89: }break; cannam@89: }break; cannam@89: case 6:switch( osVersionInfoEx.dwMinorVersion ){ cannam@89: case 0: cannam@89: if( osVersionInfoEx.wProductType == VER_NT_WORKSTATION ) cannam@89: osName = "Windows Vista"; cannam@89: else cannam@89: osName = "Windows Server 2008"; cannam@89: break; cannam@89: case 1: cannam@89: if( osVersionInfoEx.wProductType == VER_NT_WORKSTATION ) cannam@89: osName = "Windows 7"; cannam@89: else cannam@89: osName = "Windows Server 2008 R2"; cannam@89: break; cannam@89: }break; cannam@89: } cannam@89: } cannam@89: cannam@89: if(osVersionInfoEx.dwMajorVersion == 4) cannam@89: { cannam@89: if(osVersionInfoEx.wProductType == VER_NT_WORKSTATION) cannam@89: osProductType = "Workstation"; cannam@89: else if(osVersionInfoEx.wProductType == VER_NT_SERVER) cannam@89: osProductType = "Server"; cannam@89: } cannam@89: else if(osVersionInfoEx.dwMajorVersion == 5) cannam@89: { cannam@89: if(osVersionInfoEx.wProductType == VER_NT_WORKSTATION) cannam@89: { cannam@89: if((osVersionInfoEx.wSuiteMask & VER_SUITE_PERSONAL) == VER_SUITE_PERSONAL) cannam@89: osProductType = "Home Edition"; // Windows XP Home Edition cannam@89: else cannam@89: osProductType = "Professional"; // Windows XP / Windows 2000 Professional cannam@89: } cannam@89: else if(osVersionInfoEx.wProductType == VER_NT_SERVER) cannam@89: { cannam@89: if(osVersionInfoEx.dwMinorVersion == 0) cannam@89: { cannam@89: if((osVersionInfoEx.wSuiteMask & VER_SUITE_DATACENTER) == VER_SUITE_DATACENTER) cannam@89: osProductType = "Datacenter Server"; // Windows 2000 Datacenter Server cannam@89: else if((osVersionInfoEx.wSuiteMask & VER_SUITE_ENTERPRISE) == VER_SUITE_ENTERPRISE) cannam@89: osProductType = "Advanced Server"; // Windows 2000 Advanced Server cannam@89: else cannam@89: osProductType = "Server"; // Windows 2000 Server cannam@89: } cannam@89: } cannam@89: else cannam@89: { cannam@89: if((osVersionInfoEx.wSuiteMask & VER_SUITE_DATACENTER) == VER_SUITE_DATACENTER) cannam@89: osProductType = "Datacenter Edition"; // Windows Server 2003 Datacenter Edition cannam@89: else if((osVersionInfoEx.wSuiteMask & VER_SUITE_ENTERPRISE) == VER_SUITE_ENTERPRISE) cannam@89: osProductType = "Enterprise Edition"; // Windows Server 2003 Enterprise Edition cannam@89: else if((osVersionInfoEx.wSuiteMask & VER_SUITE_BLADE) == VER_SUITE_BLADE) cannam@89: osProductType = "Web Edition"; // Windows Server 2003 Web Edition cannam@89: else cannam@89: osProductType = "Standard Edition"; // Windows Server 2003 Standard Edition cannam@89: } cannam@89: } cannam@89: cannam@89: memset( &systemInfo, 0, sizeof(SYSTEM_INFO) ); cannam@89: GetNativeSystemInfo( &systemInfo ); cannam@89: cannam@89: if( systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_INTEL ) cannam@89: processorArchitecture = "x86"; cannam@89: else if( systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64 ) cannam@89: processorArchitecture = "x64"; cannam@89: else if( systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_IA64 ) cannam@89: processorArchitecture = "Itanium"; cannam@89: cannam@89: cannam@89: fprintf( fp, "OS name and edition: %s %s\n", osName, osProductType ); cannam@89: fprintf( fp, "OS version: %d.%d.%d %S\n", cannam@89: osVersionInfoEx.dwMajorVersion, osVersionInfoEx.dwMinorVersion, cannam@89: osVersionInfoEx.dwBuildNumber, osVersionInfoEx.szCSDVersion ); cannam@89: fprintf( fp, "Processor architecture: %s\n", processorArchitecture ); cannam@89: fprintf( fp, "WoW64 process: %s\n", IsWow64() ? "Yes" : "No" ); cannam@89: } cannam@89: cannam@89: static void printTimeAndDate( FILE *fp ) cannam@89: { cannam@89: struct tm *local; cannam@89: time_t t; cannam@89: cannam@89: t = time(NULL); cannam@89: local = localtime(&t); cannam@89: fprintf(fp, "Local time and date: %s", asctime(local)); cannam@89: local = gmtime(&t); cannam@89: fprintf(fp, "UTC time and date: %s", asctime(local)); cannam@89: } cannam@89: cannam@89: /*******************************************************************/ cannam@89: cannam@89: typedef struct cannam@89: { cannam@89: float sine[TABLE_SIZE]; cannam@89: double phase; cannam@89: double phaseIncrement; cannam@89: volatile int fadeIn; cannam@89: volatile int fadeOut; cannam@89: double amp; cannam@89: } cannam@89: paTestData; cannam@89: cannam@89: static paTestData data; cannam@89: cannam@89: /* This routine will be called by the PortAudio engine when audio is needed. cannam@89: ** It may called at interrupt level on some machines so don't do anything cannam@89: ** that could mess up the system like calling malloc() or free(). cannam@89: */ cannam@89: static int patestCallback( const void *inputBuffer, void *outputBuffer, cannam@89: unsigned long framesPerBuffer, cannam@89: const PaStreamCallbackTimeInfo* timeInfo, cannam@89: PaStreamCallbackFlags statusFlags, cannam@89: void *userData ) cannam@89: { cannam@89: paTestData *data = (paTestData*)userData; cannam@89: float *out = (float*)outputBuffer; cannam@89: unsigned long i,j; cannam@89: cannam@89: (void) timeInfo; /* Prevent unused variable warnings. */ cannam@89: (void) statusFlags; cannam@89: (void) inputBuffer; cannam@89: cannam@89: for( i=0; isine[(int)data->phase]; cannam@89: data->phase += data->phaseIncrement; cannam@89: if( data->phase >= TABLE_SIZE ){ cannam@89: data->phase -= TABLE_SIZE; cannam@89: } cannam@89: cannam@89: x *= data->amp; cannam@89: if( data->fadeIn ){ cannam@89: data->amp += .001; cannam@89: if( data->amp >= 1. ) cannam@89: data->fadeIn = 0; cannam@89: }else if( data->fadeOut ){ cannam@89: if( data->amp > 0 ) cannam@89: data->amp -= .001; cannam@89: } cannam@89: cannam@89: for( j = 0; j < CHANNEL_COUNT; ++j ){ cannam@89: *out++ = x; cannam@89: } cannam@89: } cannam@89: cannam@89: if( data->amp > 0 ) cannam@89: return paContinue; cannam@89: else cannam@89: return paComplete; cannam@89: } cannam@89: cannam@89: cannam@89: #define YES 1 cannam@89: #define NO 0 cannam@89: cannam@89: cannam@89: static int playUntilKeyPress( int deviceIndex, float sampleRate, cannam@89: int framesPerUserBuffer, int framesPerWmmeBuffer, int wmmeBufferCount ) cannam@89: { cannam@89: PaStreamParameters outputParameters; cannam@89: PaWinMmeStreamInfo wmmeStreamInfo; cannam@89: PaStream *stream; cannam@89: PaError err; cannam@89: int c; cannam@89: cannam@89: outputParameters.device = deviceIndex; cannam@89: outputParameters.channelCount = CHANNEL_COUNT; cannam@89: outputParameters.sampleFormat = paFloat32; /* 32 bit floating point processing */ cannam@89: outputParameters.suggestedLatency = 0; /*Pa_GetDeviceInfo( outputParameters.device )->defaultLowOutputLatency;*/ cannam@89: outputParameters.hostApiSpecificStreamInfo = NULL; cannam@89: cannam@89: wmmeStreamInfo.size = sizeof(PaWinMmeStreamInfo); cannam@89: wmmeStreamInfo.hostApiType = paMME; cannam@89: wmmeStreamInfo.version = 1; cannam@89: wmmeStreamInfo.flags = paWinMmeUseLowLevelLatencyParameters | paWinMmeDontThrottleOverloadedProcessingThread; cannam@89: wmmeStreamInfo.framesPerBuffer = framesPerWmmeBuffer; cannam@89: wmmeStreamInfo.bufferCount = wmmeBufferCount; cannam@89: outputParameters.hostApiSpecificStreamInfo = &wmmeStreamInfo; cannam@89: cannam@89: err = Pa_OpenStream( cannam@89: &stream, cannam@89: NULL, /* no input */ cannam@89: &outputParameters, cannam@89: sampleRate, cannam@89: framesPerUserBuffer, cannam@89: paClipOff | paPrimeOutputBuffersUsingStreamCallback, /* we won't output out of range samples so don't bother clipping them */ cannam@89: patestCallback, cannam@89: &data ); cannam@89: if( err != paNoError ) goto error; cannam@89: cannam@89: data.amp = 0; cannam@89: data.fadeIn = 1; cannam@89: data.fadeOut = 0; cannam@89: data.phase = 0; cannam@89: data.phaseIncrement = 15 + ((rand()%100) / 10); // randomise pitch cannam@89: cannam@89: err = Pa_StartStream( stream ); cannam@89: if( err != paNoError ) goto error; cannam@89: cannam@89: cannam@89: do{ cannam@89: printf( "Trying buffer size %d.\nIf it sounds smooth (without clicks or glitches) press 'y', if it sounds bad press 'n' ('q' to quit)\n", framesPerWmmeBuffer ); cannam@89: c = tolower(_getch()); cannam@89: if( c == 'q' ){ cannam@89: Pa_Terminate(); cannam@89: exit(0); cannam@89: } cannam@89: }while( c != 'y' && c != 'n' ); cannam@89: cannam@89: data.fadeOut = 1; cannam@89: while( Pa_IsStreamActive(stream) == 1 ) cannam@89: Pa_Sleep( 100 ); cannam@89: cannam@89: err = Pa_StopStream( stream ); cannam@89: if( err != paNoError ) goto error; cannam@89: cannam@89: err = Pa_CloseStream( stream ); cannam@89: if( err != paNoError ) goto error; cannam@89: cannam@89: return (c == 'y') ? YES : NO; cannam@89: cannam@89: error: cannam@89: return err; cannam@89: } cannam@89: cannam@89: /*******************************************************************/ cannam@89: static void usage( int wmmeHostApiIndex ) cannam@89: { cannam@89: int i; cannam@89: cannam@89: fprintf( stderr, "PortAudio WMME output latency user guided test\n" ); cannam@89: fprintf( stderr, "Usage: x.exe mme-device-index [sampleRate [min-buffer-count max-buffer-count]]\n" ); cannam@89: fprintf( stderr, "Invalid device index. Use one of these:\n" ); cannam@89: for( i=0; i < Pa_GetDeviceCount(); ++i ){ cannam@89: cannam@89: if( Pa_GetDeviceInfo(i)->hostApi == wmmeHostApiIndex && Pa_GetDeviceInfo(i)->maxOutputChannels > 0 ) cannam@89: fprintf( stderr, "%d (%s)\n", i, Pa_GetDeviceInfo(i)->name ); cannam@89: } cannam@89: Pa_Terminate(); cannam@89: exit(-1); cannam@89: } cannam@89: cannam@89: /* cannam@89: ideas: cannam@89: o- could be testing with 80% CPU load cannam@89: o- could test with different channel counts cannam@89: */ cannam@89: cannam@89: int main(int argc, char* argv[]) cannam@89: { cannam@89: PaError err; cannam@89: int i; cannam@89: int deviceIndex; cannam@89: int wmmeBufferCount, wmmeBufferSize, smallestWorkingBufferSize; cannam@89: int smallestWorkingBufferingLatencyFrames; cannam@89: int min, max, mid; cannam@89: int testResult; cannam@89: FILE *resultsFp; cannam@89: int wmmeHostApiIndex; cannam@89: const PaHostApiInfo *wmmeHostApiInfo; cannam@89: double sampleRate = DEFAULT_SAMPLE_RATE; cannam@89: int wmmeMinBufferCount = MIN_WMME_BUFFER_COUNT; cannam@89: int wmmeMaxBufferCount = MAX_WMME_BUFFER_COUNT; cannam@89: cannam@89: err = Pa_Initialize(); cannam@89: if( err != paNoError ) goto error; cannam@89: cannam@89: wmmeHostApiIndex = Pa_HostApiTypeIdToHostApiIndex( paMME ); cannam@89: wmmeHostApiInfo = Pa_GetHostApiInfo( wmmeHostApiIndex ); cannam@89: cannam@89: if( argc > 5 ) cannam@89: usage(wmmeHostApiIndex); cannam@89: cannam@89: deviceIndex = wmmeHostApiInfo->defaultOutputDevice; cannam@89: if( argc >= 2 ){ cannam@89: deviceIndex = -1; cannam@89: if( sscanf( argv[1], "%d", &deviceIndex ) != 1 ) cannam@89: usage(wmmeHostApiIndex); cannam@89: if( deviceIndex < 0 || deviceIndex >= Pa_GetDeviceCount() || Pa_GetDeviceInfo(deviceIndex)->hostApi != wmmeHostApiIndex ){ cannam@89: usage(wmmeHostApiIndex); cannam@89: } cannam@89: } cannam@89: cannam@89: printf( "Using device id %d (%s)\n", deviceIndex, Pa_GetDeviceInfo(deviceIndex)->name ); cannam@89: cannam@89: if( argc >= 3 ){ cannam@89: if( sscanf( argv[2], "%lf", &sampleRate ) != 1 ) cannam@89: usage(wmmeHostApiIndex); cannam@89: } cannam@89: cannam@89: printf( "Testing with sample rate %f.\n", (float)sampleRate ); cannam@89: cannam@89: if( argc == 4 ){ cannam@89: if( sscanf( argv[3], "%d", &wmmeMinBufferCount ) != 1 ) cannam@89: usage(wmmeHostApiIndex); cannam@89: wmmeMaxBufferCount = wmmeMinBufferCount; cannam@89: } cannam@89: cannam@89: if( argc == 5 ){ cannam@89: if( sscanf( argv[3], "%d", &wmmeMinBufferCount ) != 1 ) cannam@89: usage(wmmeHostApiIndex); cannam@89: if( sscanf( argv[4], "%d", &wmmeMaxBufferCount ) != 1 ) cannam@89: usage(wmmeHostApiIndex); cannam@89: } cannam@89: cannam@89: printf( "Testing buffer counts from %d to %d\n", wmmeMinBufferCount, wmmeMaxBufferCount ); cannam@89: cannam@89: cannam@89: /* initialise sinusoidal wavetable */ cannam@89: for( i=0; iname ); cannam@89: fflush( resultsFp ); cannam@89: cannam@89: fprintf( resultsFp, "Sample rate: %f\n", (float)sampleRate ); cannam@89: fprintf( resultsFp, "Buffer count, Smallest working buffer size (frames), Smallest working buffering latency (frames), Smallest working buffering latency (Seconds)\n" ); cannam@89: cannam@89: for( wmmeBufferCount = wmmeMinBufferCount; wmmeBufferCount <= wmmeMaxBufferCount; ++wmmeBufferCount ){ cannam@89: cannam@89: printf( "Test %d of %d\n", (wmmeBufferCount - wmmeMinBufferCount) + 1, (wmmeMaxBufferCount-wmmeMinBufferCount) + 1 ); cannam@89: printf( "Testing with %d buffers...\n", wmmeBufferCount ); cannam@89: cannam@89: /* cannam@89: Binary search after Niklaus Wirth cannam@89: from http://en.wikipedia.org/wiki/Binary_search_algorithm#The_algorithm cannam@89: */ cannam@89: min = 1; cannam@89: max = (int)((sampleRate * .3) / (wmmeBufferCount-1)); //8192; /* we assume that this size works 300ms */ cannam@89: smallestWorkingBufferSize = 0; cannam@89: cannam@89: do{ cannam@89: mid = min + ((max - min) / 2); cannam@89: cannam@89: wmmeBufferSize = mid; cannam@89: testResult = playUntilKeyPress( deviceIndex, sampleRate, wmmeBufferSize, wmmeBufferSize, wmmeBufferCount ); cannam@89: cannam@89: if( testResult == YES ){ cannam@89: max = mid - 1; cannam@89: smallestWorkingBufferSize = wmmeBufferSize; cannam@89: }else{ cannam@89: min = mid + 1; cannam@89: } cannam@89: cannam@89: }while( (min <= max) && (testResult == YES || testResult == NO) ); cannam@89: cannam@89: smallestWorkingBufferingLatencyFrames = smallestWorkingBufferSize * (wmmeBufferCount - 1); cannam@89: cannam@89: printf( "Smallest working buffer size for %d buffers is: %d\n", wmmeBufferCount, smallestWorkingBufferSize ); cannam@89: printf( "Corresponding to buffering latency of %d frames, or %f seconds.\n", smallestWorkingBufferingLatencyFrames, smallestWorkingBufferingLatencyFrames / sampleRate ); cannam@89: cannam@89: fprintf( resultsFp, "%d, %d, %d, %f\n", wmmeBufferCount, smallestWorkingBufferSize, smallestWorkingBufferingLatencyFrames, smallestWorkingBufferingLatencyFrames / sampleRate ); cannam@89: fflush( resultsFp ); cannam@89: } cannam@89: cannam@89: fprintf( resultsFp, "###\n" ); cannam@89: fclose( resultsFp ); cannam@89: cannam@89: Pa_Terminate(); cannam@89: printf("Test finished.\n"); cannam@89: cannam@89: return err; cannam@89: error: cannam@89: Pa_Terminate(); cannam@89: fprintf( stderr, "An error occured while using the portaudio stream\n" ); cannam@89: fprintf( stderr, "Error number: %d\n", err ); cannam@89: fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); cannam@89: return err; cannam@89: } cannam@89: