/*\r
* $Id: $\r
* Portable Audio I/O Library\r
- * Windows MME low level buffer parameters search\r
+ * Windows MME low level buffer user guided parameters search\r
*\r
* Copyright (c) 2010 Ross Bencina\r
*\r
*/\r
\r
#include <stdio.h>\r
+#include <time.h>\r
#include <math.h>\r
\r
+#define _WIN32_WINNT 0x0501 /* for GetNativeSystemInfo */ \r
#include <windows.h> /* required when using pa_win_wmme.h */\r
#include <mmsystem.h> /* required when using pa_win_wmme.h */\r
\r
#include "pa_win_wmme.h"\r
\r
\r
-#define SAMPLE_RATE (22050)\r
+#define DEFAULT_SAMPLE_RATE (44100.)\r
\r
#ifndef M_PI\r
#define M_PI (3.14159265)\r
#define CHANNEL_COUNT (2)\r
\r
\r
+/* seach parameters. we test all buffer counts in this range */\r
+#define MIN_WMME_BUFFER_COUNT (2)\r
+#define MAX_WMME_BUFFER_COUNT (12)\r
+\r
+\r
+/*******************************************************************/\r
+/* functions to query and print Windows version information */\r
+\r
+typedef BOOL (WINAPI *LPFN_ISWOW64PROCESS) (HANDLE, PBOOL);\r
+\r
+LPFN_ISWOW64PROCESS fnIsWow64Process;\r
+\r
+static BOOL IsWow64()\r
+{\r
+ BOOL bIsWow64 = FALSE;\r
+\r
+ //IsWow64Process is not available on all supported versions of Windows.\r
+ //Use GetModuleHandle to get a handle to the DLL that contains the function\r
+ //and GetProcAddress to get a pointer to the function if available.\r
+\r
+ fnIsWow64Process = (LPFN_ISWOW64PROCESS) GetProcAddress(\r
+ GetModuleHandle(TEXT("kernel32")),"IsWow64Process" );\r
+\r
+ if(NULL != fnIsWow64Process)\r
+ {\r
+ if (!fnIsWow64Process(GetCurrentProcess(),&bIsWow64))\r
+ {\r
+ //handle error\r
+ }\r
+ }\r
+ return bIsWow64;\r
+}\r
+\r
+static void printWindowsVersionInfo( FILE *fp )\r
+{\r
+ OSVERSIONINFOEX osVersionInfoEx;\r
+ SYSTEM_INFO systemInfo;\r
+ const char *osName = "Unknown";\r
+ const char *osProductType = "";\r
+ const char *processorArchitecture = "Unknown";\r
+\r
+ memset( &osVersionInfoEx, 0, sizeof(OSVERSIONINFOEX) );\r
+ osVersionInfoEx.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);\r
+ GetVersionEx( &osVersionInfoEx );\r
+\r
+ \r
+ if( osVersionInfoEx.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS ){\r
+ switch( osVersionInfoEx.dwMinorVersion ){\r
+ case 0: osName = "Windows 95"; break;\r
+ case 10: osName = "Windows 98"; break; // could also be SE (code I saw discriminated on osInfo.Version.Revision.ToString() == "2222A")\r
+ case 90: osName = "Windows Me"; break;\r
+ }\r
+ }else if( osVersionInfoEx.dwPlatformId == VER_PLATFORM_WIN32_NT ){\r
+ switch( osVersionInfoEx.dwMajorVersion ){\r
+ case 3: osName = "Windows NT 3.51"; break;\r
+ case 4: osName = "Windows NT 4.0"; break;\r
+ case 5: switch( osVersionInfoEx.dwMinorVersion ){\r
+ case 0: osName = "Windows 2000"; break;\r
+ case 1: osName = "Windows XP"; break;\r
+ case 2:\r
+ if( osVersionInfoEx.wSuiteMask & 0x00008000 /*VER_SUITE_WH_SERVER*/ ){\r
+ osName = "Windows Home Server";\r
+ }else{\r
+ if( osVersionInfoEx.wProductType == VER_NT_WORKSTATION ){\r
+ osName = "Windows XP Professional x64 Edition (?)";\r
+ }else{\r
+ if( GetSystemMetrics(/*SM_SERVERR2*/89) == 0 )\r
+ osName = "Windows Server 2003";\r
+ else\r
+ osName = "Windows Server 2003 R2";\r
+ }\r
+ }break;\r
+ }break;\r
+ case 6:switch( osVersionInfoEx.dwMinorVersion ){\r
+ case 0: \r
+ if( osVersionInfoEx.wProductType == VER_NT_WORKSTATION )\r
+ osName = "Windows Vista";\r
+ else\r
+ osName = "Windows Server 2008";\r
+ case 1: \r
+ if( osVersionInfoEx.wProductType == VER_NT_WORKSTATION )\r
+ osName = "Windows 7";\r
+ else\r
+ osName = "Windows Server 2008 R2";\r
+ }break;\r
+ }\r
+ }\r
+\r
+ if(osVersionInfoEx.dwMajorVersion == 4)\r
+ {\r
+ if(osVersionInfoEx.wProductType == VER_NT_WORKSTATION)\r
+ osProductType = "Workstation";\r
+ else if(osVersionInfoEx.wProductType == VER_NT_SERVER)\r
+ osProductType = "Server";\r
+ }\r
+ else if(osVersionInfoEx.dwMajorVersion == 5)\r
+ {\r
+ if(osVersionInfoEx.wProductType == VER_NT_WORKSTATION)\r
+ {\r
+ if((osVersionInfoEx.wSuiteMask & VER_SUITE_PERSONAL) == VER_SUITE_PERSONAL)\r
+ osProductType = "Home Edition"; // Windows XP Home Edition\r
+ else\r
+ osProductType = "Professional"; // Windows XP / Windows 2000 Professional\r
+ }\r
+ else if(osVersionInfoEx.wProductType == VER_NT_SERVER)\r
+ {\r
+ if(osVersionInfoEx.dwMinorVersion == 0) \r
+ {\r
+ if((osVersionInfoEx.wSuiteMask & VER_SUITE_DATACENTER) == VER_SUITE_DATACENTER)\r
+ osProductType = "Datacenter Server"; // Windows 2000 Datacenter Server\r
+ else if((osVersionInfoEx.wSuiteMask & VER_SUITE_ENTERPRISE) == VER_SUITE_ENTERPRISE)\r
+ osProductType = "Advanced Server"; // Windows 2000 Advanced Server\r
+ else\r
+ osProductType = "Server"; // Windows 2000 Server\r
+ }\r
+ }\r
+ else\r
+ {\r
+ if((osVersionInfoEx.wSuiteMask & VER_SUITE_DATACENTER) == VER_SUITE_DATACENTER)\r
+ osProductType = "Datacenter Edition"; // Windows Server 2003 Datacenter Edition\r
+ else if((osVersionInfoEx.wSuiteMask & VER_SUITE_ENTERPRISE) == VER_SUITE_ENTERPRISE)\r
+ osProductType = "Enterprise Edition"; // Windows Server 2003 Enterprise Edition\r
+ else if((osVersionInfoEx.wSuiteMask & VER_SUITE_BLADE) == VER_SUITE_BLADE)\r
+ osProductType = "Web Edition"; // Windows Server 2003 Web Edition\r
+ else\r
+ osProductType = "Standard Edition"; // Windows Server 2003 Standard Edition\r
+ }\r
+ }\r
+\r
+ memset( &systemInfo, 0, sizeof(SYSTEM_INFO) );\r
+ GetNativeSystemInfo( &systemInfo );\r
+\r
+ if( systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_INTEL )\r
+ processorArchitecture = "x86";\r
+ else if( systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64 )\r
+ processorArchitecture = "x64";\r
+ else if( systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_IA64 )\r
+ processorArchitecture = "Itanium";\r
+\r
+\r
+ fprintf( fp, "OS name and edition: %s %s\n", osName, osProductType );\r
+ fprintf( fp, "OS version: %d.%d.%d %S\n", osVersionInfoEx.dwMajorVersion, osVersionInfoEx.dwMinorVersion, osVersionInfoEx.dwBuildNumber, osVersionInfoEx.szCSDVersion );\r
+ fprintf( fp, "Processor architecture: %s\n", processorArchitecture );\r
+ fprintf( fp, "WoW64 process: %s\n", IsWow64() ? "Yes" : "No" );\r
+}\r
+\r
+static void printTimeAndDate( FILE *fp )\r
+{\r
+ struct tm *local;\r
+ time_t t;\r
+\r
+ t = time(NULL);\r
+ local = localtime(&t);\r
+ fprintf(fp, "Local time and date: %s", asctime(local));\r
+ local = gmtime(&t);\r
+ fprintf(fp, "UTC time and date: %s", asctime(local));\r
+}\r
+\r
+/*******************************************************************/\r
+\r
typedef struct\r
{\r
float sine[TABLE_SIZE];\r
double phase;\r
+ volatile int fadeIn;\r
+ volatile int fadeOut;\r
+ double amp;\r
}\r
paTestData;\r
\r
data->phase -= TABLE_SIZE;\r
}\r
\r
+ x *= data->amp;\r
+ if( data->fadeIn ){\r
+ data->amp += .001;\r
+ if( data->amp >= 1. )\r
+ data->fadeIn = 0;\r
+ }else if( data->fadeOut ){\r
+ if( data->amp > 0 )\r
+ data->amp -= .001;\r
+ }\r
+\r
for( j = 0; j < CHANNEL_COUNT; ++j ){\r
*out++ = x;\r
}\r
}\r
\r
- return paContinue;\r
+ if( data->amp > 0 )\r
+ return paContinue;\r
+ else\r
+ return paComplete;\r
}\r
\r
\r
#define NO 0\r
\r
\r
-static int playUntilKeyPress( int deviceIndex, int framesPerUserBuffer, int framesPerWmmeBuffer, int wmmeBufferCount )\r
+static int playUntilKeyPress( int deviceIndex, float sampleRate, int framesPerUserBuffer, int framesPerWmmeBuffer, int wmmeBufferCount )\r
{\r
PaStreamParameters outputParameters;\r
PaWinMmeStreamInfo wmmeStreamInfo;\r
&stream,\r
NULL, /* no input */\r
&outputParameters,\r
- SAMPLE_RATE,\r
+ sampleRate,\r
framesPerUserBuffer,\r
paClipOff | paPrimeOutputBuffersUsingStreamCallback, /* we won't output out of range samples so don't bother clipping them */\r
patestCallback,\r
&data );\r
if( err != paNoError ) goto error;\r
\r
+ data.amp = 0;\r
+ data.fadeIn = 1;\r
+ data.fadeOut = 0;\r
+\r
err = Pa_StartStream( stream );\r
if( err != paNoError ) goto error;\r
\r
\r
do{\r
- printf( "Trying buffer size %d.\nIf it sounds smooth press 'y', if it sounds bad press 'n'\n", framesPerWmmeBuffer );\r
+ printf( "Trying buffer size %d.\nIf it sounds smooth press 'y', if it sounds bad press 'n' ('q' to quit)\n", framesPerWmmeBuffer );\r
c = tolower(_getch());\r
+ if( c == 'q' ){\r
+ Pa_Terminate();\r
+ exit(0);\r
+ }\r
}while( c != 'y' && c != 'n' );\r
\r
- err = Pa_AbortStream( stream );\r
+ data.fadeOut = 1;\r
+ while( Pa_IsStreamActive(stream) == 1 )\r
+ Pa_Sleep( 100 );\r
+\r
+ err = Pa_StopStream( stream );\r
if( err != paNoError ) goto error;\r
\r
err = Pa_CloseStream( stream );\r
return err;\r
}\r
\r
-\r
/*******************************************************************/\r
+static void usage( int wmmeHostApiIndex )\r
+{\r
+ int i;\r
+\r
+ fprintf( stderr, "PortAudio WMME output latency user guided test\n" );\r
+ fprintf( stderr, "Usage: x.exe mme-device-index [sampleRate [min-buffer-count max-buffer-count]]\n" );\r
+ fprintf( stderr, "Invalid device index. Use one of these:\n" );\r
+ for( i=0; i < Pa_GetDeviceCount(); ++i ){\r
+\r
+ if( Pa_GetDeviceInfo(i)->hostApi == wmmeHostApiIndex )\r
+ fprintf( stderr, "%d (%s)\n", i, Pa_GetDeviceInfo(i)->name);\r
+ }\r
+ Pa_Terminate();\r
+ exit(-1);\r
+}\r
+\r
+/*\r
+ TODO: test at 22050 44100, 48000, 96000, mono and stereo and surround\r
+\r
+ TODO: could be testing with 80% CPU load\r
+\r
+ another thing to try would be setting the timeBeginPeriod granularity to 1ms and see if it changes the behavior\r
+*/\r
+\r
int main(int argc, char* argv[])\r
{\r
PaError err;\r
int i;\r
int deviceIndex;\r
int wmmeBufferCount, wmmeBufferSize, smallestWorkingBufferSize;\r
+ int smallestWorkingBufferingLatencyFrames;\r
int min, max, mid;\r
int testResult;\r
FILE *resultsFp;\r
- OSVERSIONINFO windowsVersion;\r
+ int wmmeHostApiIndex;\r
+ const PaHostApiInfo *wmmeHostApiInfo;\r
+ double sampleRate = DEFAULT_SAMPLE_RATE;\r
+ int wmmeMinBufferCount = MIN_WMME_BUFFER_COUNT;\r
+ int wmmeMaxBufferCount = MAX_WMME_BUFFER_COUNT;\r
\r
err = Pa_Initialize();\r
if( err != paNoError ) goto error;\r
\r
- /*\r
- TODO: print an index of devices and ask the user to select one\r
+ wmmeHostApiIndex = Pa_HostApiTypeIdToHostApiIndex( paMME );\r
+ wmmeHostApiInfo = Pa_GetHostApiInfo( wmmeHostApiIndex );\r
\r
- */\r
+ if( argc > 5 )\r
+ usage(wmmeHostApiIndex);\r
\r
- deviceIndex = Pa_GetHostApiInfo( Pa_HostApiTypeIdToHostApiIndex( paMME ) )->defaultOutputDevice;\r
- if( argc == 2 ){\r
- sscanf( argv[1], "%d", &deviceIndex );\r
+ deviceIndex = wmmeHostApiInfo->defaultOutputDevice;\r
+ if( argc >= 2 ){\r
+ deviceIndex = -1;\r
+ if( sscanf( argv[1], "%d", &deviceIndex ) != 1 )\r
+ usage(wmmeHostApiIndex);\r
+ if( deviceIndex < 0 || deviceIndex >= Pa_GetDeviceCount() || Pa_GetDeviceInfo(deviceIndex)->hostApi != wmmeHostApiIndex ){\r
+ usage(wmmeHostApiIndex);\r
+ }\r
}\r
\r
- printf( "using device id %d (%s)\n", deviceIndex, Pa_GetDeviceInfo(deviceIndex)->name );\r
+ printf( "Using device id %d (%s)\n", deviceIndex, Pa_GetDeviceInfo(deviceIndex)->name );\r
+\r
+ if( argc >= 3 ){\r
+ if( sscanf( argv[2], "%lf", &sampleRate ) != 1 )\r
+ usage(wmmeHostApiIndex);\r
+ }\r
+\r
+ printf( "Testing with sample rate %f.\n", (float)sampleRate );\r
+\r
+ if( argc == 4 ){\r
+ if( sscanf( argv[3], "%d", &wmmeMinBufferCount ) != 1 )\r
+ usage(wmmeHostApiIndex);\r
+ wmmeMaxBufferCount = wmmeMinBufferCount;\r
+ }\r
+\r
+ if( argc == 5 ){\r
+ if( sscanf( argv[3], "%d", &wmmeMinBufferCount ) != 1 )\r
+ usage(wmmeHostApiIndex);\r
+ if( sscanf( argv[4], "%d", &wmmeMaxBufferCount ) != 1 )\r
+ usage(wmmeHostApiIndex);\r
+ }\r
+\r
+ printf( "Testing buffer counts from %d to %d\n", wmmeMinBufferCount, wmmeMaxBufferCount );\r
+\r
\r
/* initialise sinusoidal wavetable */\r
for( i=0; i<TABLE_SIZE; i++ )\r
\r
data.phase = 0;\r
\r
-\r
resultsFp = fopen( "results.txt", "at" );\r
- fprintf( resultsFp, "*** WMME smallest working buffer sizes\n" );\r
+ fprintf( resultsFp, "*** WMME smallest working output buffer sizes\n" );\r
\r
- memset( &windowsVersion, 0, sizeof(OSVERSIONINFO) );\r
- windowsVersion.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);\r
- GetVersionEx( &windowsVersion );\r
-\r
- fprintf( resultsFp, "windows version: %d.%d.%d %S\n", windowsVersion.dwMajorVersion, windowsVersion.dwMinorVersion, windowsVersion.dwBuildNumber, windowsVersion.szCSDVersion );\r
- fprintf( resultsFp, "audio device: %s\n", Pa_GetDeviceInfo( deviceIndex )->name );\r
+ printTimeAndDate( resultsFp );\r
+ printWindowsVersionInfo( resultsFp );\r
\r
+ fprintf( resultsFp, "audio device: %s\n", Pa_GetDeviceInfo( deviceIndex )->name );\r
+ fflush( resultsFp );\r
\r
- /*\r
- TODO: test at 22050 44100, 48000, 96000, mono and stereo and surround\r
-\r
- TODO: should be testing with 80% CPU load\r
-\r
-\r
- another thing to try would be setting the timeBeginPeriod granularity to 1ms and see if it changes the behavior\r
- */\r
-\r
- printf( "testing with sample rate %f.\n", (float)SAMPLE_RATE );\r
- fprintf( resultsFp, "sample rate: %f\n", (float)SAMPLE_RATE );\r
- fprintf( resultsFp, "buffer count, smallest working size (frames)\n" );\r
+ fprintf( resultsFp, "Sample rate: %f\n", (float)sampleRate );\r
+ fprintf( resultsFp, "Buffer count, Smallest working buffer size (frames), Smallest working buffering latency (frames), Smallest working buffering latency (Seconds)\n" );\r
\r
- for( wmmeBufferCount = 2; wmmeBufferCount < 13; ++wmmeBufferCount ){\r
+ for( wmmeBufferCount = wmmeMinBufferCount; wmmeBufferCount <= wmmeMaxBufferCount; ++wmmeBufferCount ){ // TODO print out range we're going to try before starting\r
\r
- \r
- printf( "testing with %d buffers...\n", wmmeBufferCount );\r
+ printf( "Test %d of %d\n", (wmmeBufferCount - wmmeMinBufferCount) + 1, (wmmeMaxBufferCount-wmmeMinBufferCount) + 1 );\r
+ printf( "Testing with %d buffers...\n", wmmeBufferCount );\r
\r
/*\r
Binary search after Niklaus Wirth\r
from http://en.wikipedia.org/wiki/Binary_search_algorithm#The_algorithm\r
*/\r
min = 1;\r
- max = 8192; /* we assume that this size works */\r
+ max = (int)((sampleRate * .3) / (wmmeBufferCount-1)); //8192; /* we assume that this size works 300ms */\r
smallestWorkingBufferSize = 0;\r
\r
do{\r
mid = min + ((max - min) / 2);\r
\r
wmmeBufferSize = mid;\r
- testResult = playUntilKeyPress( deviceIndex, wmmeBufferSize, wmmeBufferSize, wmmeBufferCount );\r
+ testResult = playUntilKeyPress( deviceIndex, sampleRate, wmmeBufferSize, wmmeBufferSize, wmmeBufferCount );\r
\r
if( testResult == YES ){\r
max = mid - 1;\r
\r
}while( (min <= max) && (testResult == YES || testResult == NO) );\r
\r
- printf( "smallest working buffer size for %d buffers is: %d\n", wmmeBufferCount, smallestWorkingBufferSize );\r
+ smallestWorkingBufferingLatencyFrames = smallestWorkingBufferSize * (wmmeBufferCount - 1);\r
\r
- fprintf( resultsFp, "%d, %d\n", wmmeBufferCount, smallestWorkingBufferSize );\r
+ printf( "Smallest working buffer size for %d buffers is: %d\n", wmmeBufferCount, smallestWorkingBufferSize );\r
+ printf( "Corresponding to buffering latency of %d frames, or %f seconds.\n", smallestWorkingBufferingLatencyFrames, smallestWorkingBufferingLatencyFrames / sampleRate );\r
+\r
+ fprintf( resultsFp, "%d, %d, %d, %f\n", wmmeBufferCount, smallestWorkingBufferSize, smallestWorkingBufferingLatencyFrames, smallestWorkingBufferingLatencyFrames / sampleRate );\r
fflush( resultsFp );\r
}\r
\r
fprintf( resultsFp, "###\n" );\r
-\r
fclose( resultsFp );\r
\r
Pa_Terminate();\r