]> Repos - portaudio/commitdiff
added dsound find best latency params test. resolves #186
authorrossb <rossb@0f58301d-fd10-0410-b4af-bbb618454e57>
Thu, 24 Nov 2011 18:58:13 +0000 (18:58 +0000)
committerrossb <rossb@0f58301d-fd10-0410-b4af-bbb618454e57>
Thu, 24 Nov 2011 18:58:13 +0000 (18:58 +0000)
test/patest_dsound_find_best_latency_params.c [new file with mode: 0644]

diff --git a/test/patest_dsound_find_best_latency_params.c b/test/patest_dsound_find_best_latency_params.c
new file mode 100644 (file)
index 0000000..d56049d
--- /dev/null
@@ -0,0 +1,514 @@
+/*\r
+ * $Id: $\r
+ * Portable Audio I/O Library\r
+ * Windows DirectSound low level buffer user guided parameters search\r
+ *\r
+ * Copyright (c) 2010-2011 Ross Bencina\r
+ *\r
+ * Permission is hereby granted, free of charge, to any person obtaining\r
+ * a copy of this software and associated documentation files\r
+ * (the "Software"), to deal in the Software without restriction,\r
+ * including without limitation the rights to use, copy, modify, merge,\r
+ * publish, distribute, sublicense, and/or sell copies of the Software,\r
+ * and to permit persons to whom the Software is furnished to do so,\r
+ * subject to the following conditions:\r
+ *\r
+ * The above copyright notice and this permission notice shall be\r
+ * included in all copies or substantial portions of the Software.\r
+ *\r
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,\r
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\r
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\r
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR\r
+ * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF\r
+ * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\r
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\r
+ */\r
+\r
+/*\r
+ * The text above constitutes the entire PortAudio license; however, \r
+ * the PortAudio community also makes the following non-binding requests:\r
+ *\r
+ * Any person wishing to distribute modifications to the Software is\r
+ * requested to send the modifications to the original developer so that\r
+ * they can be incorporated into the canonical version. It is also \r
+ * requested that these non-binding requests be included along with the \r
+ * license above.\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>\r
+//#include <mmsystem.h>   /* required when using pa_win_wmme.h */\r
+\r
+#include <conio.h>      /* for _getch */\r
+\r
+\r
+#include "portaudio.h"\r
+#include "pa_win_ds.h"\r
+\r
+\r
+#define DEFAULT_SAMPLE_RATE             (44100.)\r
+\r
+#ifndef M_PI\r
+#define M_PI  (3.14159265)\r
+#endif\r
+\r
+#define TABLE_SIZE              (2048)\r
+\r
+#define CHANNEL_COUNT           (2)\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 98SE (I've seen code discriminate based \r
+                                                    // 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
+                            break;\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
+                    }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", \r
+                osVersionInfoEx.dwMajorVersion, osVersionInfoEx.dwMinorVersion, \r
+                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
+    double phaseIncrement;\r
+    volatile int fadeIn;\r
+    volatile int fadeOut;\r
+    double amp;\r
+}\r
+paTestData;\r
+\r
+static paTestData data;\r
+\r
+/* This routine will be called by the PortAudio engine when audio is needed.\r
+** It may called at interrupt level on some machines so don't do anything\r
+** that could mess up the system like calling malloc() or free().\r
+*/\r
+static int patestCallback( const void *inputBuffer, void *outputBuffer,\r
+                            unsigned long framesPerBuffer,\r
+                            const PaStreamCallbackTimeInfo* timeInfo,\r
+                            PaStreamCallbackFlags statusFlags,\r
+                            void *userData )\r
+{\r
+    paTestData *data = (paTestData*)userData;\r
+    float *out = (float*)outputBuffer;\r
+    unsigned long i,j;\r
+\r
+    (void) timeInfo; /* Prevent unused variable warnings. */\r
+    (void) statusFlags;\r
+    (void) inputBuffer;\r
+    \r
+    for( i=0; i<framesPerBuffer; i++ )\r
+    {\r
+        float x = data->sine[(int)data->phase];\r
+        data->phase += data->phaseIncrement;\r
+        if( data->phase >= TABLE_SIZE ){\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
+    if( data->amp > 0 )\r
+        return paContinue;\r
+    else\r
+        return paComplete;\r
+}\r
+\r
+\r
+#define YES     1\r
+#define NO      0\r
+\r
+\r
+static int playUntilKeyPress( int deviceIndex, float sampleRate, \r
+                             int framesPerUserBuffer, int framesPerDSoundBuffer )\r
+{\r
+    PaStreamParameters outputParameters;\r
+    PaWinDirectSoundStreamInfo directSoundStreamInfo;\r
+    PaStream *stream;\r
+    PaError err;\r
+    int c;\r
+\r
+    outputParameters.device = deviceIndex;\r
+    outputParameters.channelCount = CHANNEL_COUNT;\r
+    outputParameters.sampleFormat = paFloat32; /* 32 bit floating point processing */\r
+    outputParameters.suggestedLatency = 0; /*Pa_GetDeviceInfo( outputParameters.device )->defaultLowOutputLatency;*/\r
+    outputParameters.hostApiSpecificStreamInfo = NULL;\r
+\r
+    directSoundStreamInfo.size = sizeof(PaWinDirectSoundStreamInfo);\r
+    directSoundStreamInfo.hostApiType = paDirectSound; \r
+    directSoundStreamInfo.version = 2;\r
+    directSoundStreamInfo.flags = paWinDirectSoundUseLowLevelLatencyParameters;\r
+    directSoundStreamInfo.framesPerBuffer = framesPerDSoundBuffer;\r
+    outputParameters.hostApiSpecificStreamInfo = &directSoundStreamInfo;\r
+\r
+    err = Pa_OpenStream(\r
+              &stream,\r
+              NULL, /* no input */\r
+              &outputParameters,\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
+    data.phase = 0;\r
+    data.phaseIncrement = 15 + ((rand()%100) / 10); // randomise pitch\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 (without clicks or glitches) press 'y', if it sounds bad press 'n' ('q' to quit)\n", framesPerDSoundBuffer );\r
+        c = tolower(_getch());\r
+        if( c == 'q' ){\r
+            Pa_Terminate();\r
+            exit(0);\r
+        }\r
+    }while( c != 'y' && c != 'n' );\r
+\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
+    if( err != paNoError ) goto error;\r
+\r
+    return (c == 'y') ? YES : NO;\r
+\r
+error:\r
+    return err;\r
+}\r
+\r
+/*******************************************************************/\r
+static void usage( int dsoundHostApiIndex )\r
+{\r
+    int i;\r
+\r
+    fprintf( stderr, "PortAudio DirectSound output latency user guided test\n" );\r
+    fprintf( stderr, "Usage: x.exe dsound-device-index [sampleRate]\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 == dsoundHostApiIndex && Pa_GetDeviceInfo(i)->maxOutputChannels > 0  )\r
+            fprintf( stderr, "%d (%s)\n", i, Pa_GetDeviceInfo(i)->name );\r
+    }\r
+    Pa_Terminate();\r
+    exit(-1);\r
+}\r
+\r
+/*\r
+    ideas: \r
+        o- could be testing with 80% CPU load\r
+        o- could test with different channel counts\r
+*/\r
+\r
+int main(int argc, char* argv[])\r
+{\r
+    PaError err;\r
+    int i;\r
+    int deviceIndex;\r
+    int dsoundBufferSize, smallestWorkingBufferSize;\r
+    int smallestWorkingBufferingLatencyFrames;\r
+    int min, max, mid;\r
+    int testResult;\r
+    FILE *resultsFp;\r
+    int dsoundHostApiIndex;\r
+    const PaHostApiInfo *dsoundHostApiInfo;\r
+    double sampleRate = DEFAULT_SAMPLE_RATE;\r
+\r
+    err = Pa_Initialize();\r
+    if( err != paNoError ) goto error;\r
+\r
+    dsoundHostApiIndex = Pa_HostApiTypeIdToHostApiIndex( paDirectSound );\r
+    dsoundHostApiInfo = Pa_GetHostApiInfo( dsoundHostApiIndex );\r
+\r
+    if( argc > 3 )\r
+        usage(dsoundHostApiIndex);\r
+\r
+       deviceIndex = dsoundHostApiInfo->defaultOutputDevice;\r
+       if( argc >= 2 ){\r
+        deviceIndex = -1;\r
+               if( sscanf( argv[1], "%d", &deviceIndex ) != 1 )\r
+            usage(dsoundHostApiInfo);\r
+        if( deviceIndex < 0 || deviceIndex >= Pa_GetDeviceCount() || Pa_GetDeviceInfo(deviceIndex)->hostApi != dsoundHostApiIndex ){\r
+            usage(dsoundHostApiInfo);\r
+        }\r
+       }\r
+\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(dsoundHostApiIndex);\r
+    }\r
+\r
+    printf( "Testing with sample rate %f.\n", (float)sampleRate );\r
+\r
\r
+\r
+    /* initialise sinusoidal wavetable */\r
+    for( i=0; i<TABLE_SIZE; i++ )\r
+    {\r
+        data.sine[i] = (float) sin( ((double)i/(double)TABLE_SIZE) * M_PI * 2. );\r
+    }\r
+\r
+       data.phase = 0;\r
+\r
+    resultsFp = fopen( "results.txt", "at" );\r
+    fprintf( resultsFp, "*** DirectSound smallest working output buffer sizes\n" );\r
+\r
+    printTimeAndDate( resultsFp );\r
+    printWindowsVersionInfo( resultsFp );\r
+    \r
+    fprintf( resultsFp, "audio device: %s\n", Pa_GetDeviceInfo( deviceIndex )->name );\r
+    fflush( resultsFp );\r
+\r
+    fprintf( resultsFp, "Sample rate: %f\n", (float)sampleRate );\r
+    fprintf( resultsFp, "Smallest working buffer size (frames), Smallest working buffering latency (frames), Smallest working buffering latency (Seconds)\n" );\r
+\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 = (int)(sampleRate * .3);   /* we assume that this size works 300ms */\r
+    smallestWorkingBufferSize = 0;\r
+\r
+    do{\r
+        mid = min + ((max - min) / 2);\r
+\r
+        dsoundBufferSize = mid;\r
+        testResult = playUntilKeyPress( deviceIndex, sampleRate, 0, dsoundBufferSize );\r
+\r
+        if( testResult == YES ){\r
+            max = mid - 1;\r
+            smallestWorkingBufferSize = dsoundBufferSize;\r
+        }else{\r
+            min = mid + 1;\r
+        }\r
+         \r
+    }while( (min <= max) && (testResult == YES || testResult == NO) );\r
+\r
+    smallestWorkingBufferingLatencyFrames = smallestWorkingBufferSize; /* not strictly true, but we're using an unspecified callback size, so kind of */\r
+\r
+    printf( "Smallest working buffer size is: %d\n", smallestWorkingBufferSize );\r
+    printf( "Corresponding to buffering latency of %d frames, or %f seconds.\n", smallestWorkingBufferingLatencyFrames, smallestWorkingBufferingLatencyFrames / sampleRate );\r
+\r
+    fprintf( resultsFp, "%d, %d, %f\n", smallestWorkingBufferSize, smallestWorkingBufferingLatencyFrames, smallestWorkingBufferingLatencyFrames / sampleRate );\r
+    fflush( resultsFp );\r
+\r
+\r
+    /* power of 2 test. iterate to the smallest power of two that works */\r
+\r
+    smallestWorkingBufferSize = 0;\r
+    dsoundBufferSize = 64;\r
+\r
+    do{\r
+        testResult = playUntilKeyPress( deviceIndex, sampleRate, 0, dsoundBufferSize );\r
+\r
+        if( testResult == YES ){\r
+            smallestWorkingBufferSize = dsoundBufferSize;\r
+        }else{\r
+            dsoundBufferSize *= 2;\r
+        }\r
+\r
+    }while( (dsoundBufferSize <= (int)(sampleRate * .3)) && testResult == NO );\r
+    \r
+    smallestWorkingBufferingLatencyFrames = smallestWorkingBufferSize; /* not strictly true, but we're using an unspecified callback size, so kind of */\r
+\r
+    fprintf( resultsFp, "%d, %d, %f\n", smallestWorkingBufferSize, smallestWorkingBufferingLatencyFrames, smallestWorkingBufferingLatencyFrames / sampleRate );\r
+    fflush( resultsFp );\r
+\r
+\r
+    fprintf( resultsFp, "###\n" );\r
+    fclose( resultsFp );\r
+    \r
+    Pa_Terminate();\r
+    printf("Test finished.\n");\r
+    \r
+    return err;\r
+error:\r
+    Pa_Terminate();\r
+    fprintf( stderr, "An error occurred while using the PortAudio stream\n" );\r
+    fprintf( stderr, "Error number: %d\n", err );\r
+    fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) );\r
+    return err;\r
+}\r
+\r