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