]> Repos - portaudio/commitdiff
Implemented host-API specific mechanism for setting DirectSound buffer sizes in frame...
authorrossb <rossb@0f58301d-fd10-0410-b4af-bbb618454e57>
Thu, 24 Nov 2011 18:11:33 +0000 (18:11 +0000)
committerrossb <rossb@0f58301d-fd10-0410-b4af-bbb618454e57>
Thu, 24 Nov 2011 18:11:33 +0000 (18:11 +0000)
include/pa_win_ds.h
src/hostapi/dsound/pa_win_ds.c
test/patest_dsound_low_level_latency_params.c [new file with mode: 0644]

index 2410658a010facc6cfe703011bc66b838c69e043..aff8c3337d7a1ffe0075ee9feec948960a165143 100644 (file)
@@ -61,24 +61,21 @@ typedef struct PaWinDirectSoundStreamInfo{
     PaHostApiTypeId hostApiType;    /**< paDirectSound */\r
     unsigned long version;          /**< 2 */\r
 \r
-    unsigned long flags;\r
-\r
-    /* low-level latency setting support\r
-       Control the size of host buffers in order to set latency. They will \r
-        be used instead of the generic parameters to Pa_OpenStream() if \r
-        flags contains the paWinDirectSoundUseLowLevelLatencyParameters\r
-        flag.\r
-\r
-        If PaWinDirectSoundStreamInfo structures with paWinDirectSoundUseLowLevelLatencyParameters\r
-        are supplied for both input and output in a full duplex stream, then the\r
-        input and output framesPerBuffer must be the same, or the larger of the\r
-        two must be a multiple of the smaller, otherwise a\r
-        paIncompatibleHostApiSpecificStreamInfo error will be returned from\r
-        Pa_OpenStream().\r
+    unsigned long flags;            /**< enable other features of this struct */\r
+\r
+    /** \r
+       low-level latency setting support\r
+       Sets the size of the DirectSound host buffer.\r
+       When flags contains the paWinDirectSoundUseLowLevelLatencyParameters\r
+       this size will be used instead of interpreting the generic latency \r
+       parameters to Pa_OpenStream(). If the flag is not set this value is ignored.\r
+\r
+       If the stream is a full duplex stream the implementation requires that\r
+       the values of framesPerBuffer for input and output match (if both are specified).\r
     */\r
-    unsigned long framesPerBuffer;  /* NOT IMPLEMENTED see http://www.portaudio.com/trac/ticket/129 */\r
+    unsigned long framesPerBuffer;\r
 \r
-    /*\r
+    /**\r
         support for WAVEFORMATEXTENSIBLE channel masks. If flags contains\r
         paWinDirectSoundUseChannelMask this allows you to specify which speakers \r
         to address in a multichannel stream. Constants for channelMask\r
index 77408c8364dda7492f4d65c4ef2ac9ff2b15838d..ef68c01cf199fc00ba48a02011716d62d6cd1716 100644 (file)
@@ -152,6 +152,13 @@ PA_THREAD_FUNC ProcessingThreadProc( void *pArg );
 
 #define PA_DS_WIN_WDM_DEFAULT_LATENCY_    (.120)
 
+/* we allow the polling period to range between 1 and 100ms.
+   prior to August 2011 we limited the minimum polling period to 10ms.
+*/
+#define PA_DS_MINIMUM_POLLING_PERIOD_SECONDS    (0.001) /* 1ms */
+#define PA_DS_MAXIMUM_POLLING_PERIOD_SECONDS    (0.100) /* 100ms */
+#define PA_DS_POLLING_JITTER_SECONDS            (0.001) /* 1ms */
+
 #define SECONDS_PER_MSEC      (0.001)
 #define MSECS_PER_SECOND       (1000)
 
@@ -1336,6 +1343,13 @@ static PaError ValidateWinDirectSoundSpecificStreamInfo(
            {
                return paIncompatibleHostApiSpecificStreamInfo;
            }
+
+        if( streamInfo->flags & paWinDirectSoundUseLowLevelLatencyParameters )
+        {
+            if( streamInfo->framesPerBuffer <= 0 )
+                return paIncompatibleHostApiSpecificStreamInfo;
+
+        }
        }
 
        return paNoError;
@@ -1541,7 +1555,13 @@ static HRESULT InitFullDuplexInputOutputBuffers( PaWinDsStream *stream,
 #endif /* PAWIN_USE_DIRECTSOUNDFULLDUPLEXCREATE */
 
 
-static HRESULT InitInputBuffer( PaWinDsStream *stream, PaWinDsDeviceInfo *device, PaSampleFormat sampleFormat, unsigned long nFrameRate, WORD nChannels, int bytesPerBuffer, PaWinWaveFormatChannelMask channelMask )
+static HRESULT InitInputBuffer( PaWinDsStream *stream, 
+                               PaWinDsDeviceInfo *device, 
+                               PaSampleFormat sampleFormat, 
+                               unsigned long nFrameRate, 
+                               WORD nChannels, 
+                               int bytesPerBuffer, 
+                               PaWinWaveFormatChannelMask channelMask )
 {
     DSCBUFFERDESC  captureDesc;
     PaWinWaveFormat waveFormat;
@@ -1582,7 +1602,10 @@ static HRESULT InitInputBuffer( PaWinDsStream *stream, PaWinDsDeviceInfo *device
 }
 
 
-static HRESULT InitOutputBuffer( PaWinDsStream *stream, PaWinDsDeviceInfo *device, PaSampleFormat sampleFormat, unsigned long nFrameRate, WORD nChannels, int bytesPerBuffer, PaWinWaveFormatChannelMask channelMask )
+static HRESULT InitOutputBuffer( PaWinDsStream *stream, PaWinDsDeviceInfo *device, 
+                                PaSampleFormat sampleFormat, unsigned long nFrameRate, 
+                                WORD nChannels, int bytesPerBuffer, 
+                                PaWinWaveFormatChannelMask channelMask )
 {
     HRESULT        result;
     HWND           hWnd;
@@ -1680,12 +1703,9 @@ static void CalculateBufferSettings( unsigned long *hostBufferSizeFrames,
                                     unsigned long suggestedOutputLatencyFrames,
                                     double sampleRate, unsigned long userFramesPerBuffer )
 {
-    /* we allow the polling period to range between 1 and 100ms.
-       prior to August 2011 we limited the minimum polling period to 10ms.
-    */
-    unsigned long minimumPollingPeriodFrames = sampleRate / 1000; /* 1ms */
-    unsigned long maximumPollingPeriodFrames = sampleRate / 10; /* 100ms */ 
-    unsigned long pollingJitterFrames = sampleRate / 1000; /* 1ms */
+    unsigned long minimumPollingPeriodFrames = sampleRate * PA_DS_MINIMUM_POLLING_PERIOD_SECONDS;
+    unsigned long maximumPollingPeriodFrames = sampleRate * PA_DS_MAXIMUM_POLLING_PERIOD_SECONDS;
+    unsigned long pollingJitterFrames = sampleRate * PA_DS_POLLING_JITTER_SECONDS;
     
     if( userFramesPerBuffer == paFramesPerBufferUnspecified )
     {
@@ -1747,6 +1767,23 @@ static void CalculateBufferSettings( unsigned long *hostBufferSizeFrames,
 }
 
 
+static void CalculatePollingPeriodFrames( unsigned long hostBufferSizeFrames, 
+                                    unsigned long *pollingPeriodFrames,
+                                    double sampleRate, unsigned long userFramesPerBuffer )
+{
+    unsigned long minimumPollingPeriodFrames = sampleRate * PA_DS_MINIMUM_POLLING_PERIOD_SECONDS;
+    unsigned long maximumPollingPeriodFrames = sampleRate * PA_DS_MAXIMUM_POLLING_PERIOD_SECONDS;
+    unsigned long pollingJitterFrames = sampleRate * PA_DS_POLLING_JITTER_SECONDS;
+
+    *pollingPeriodFrames = max( max(1, userFramesPerBuffer / 4), hostBufferSizeFrames / 16 );
+
+    if( *pollingPeriodFrames > maximumPollingPeriodFrames )
+    {
+        *pollingPeriodFrames = maximumPollingPeriodFrames;
+    }
+}
+
+
 static void SetStreamInfoLatencies( PaWinDsStream *stream, 
                                    unsigned long userFramesPerBuffer, 
                                    unsigned long pollingPeriodFrames,
@@ -1808,6 +1845,8 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
     int inputChannelCount, outputChannelCount;
     PaSampleFormat inputSampleFormat, outputSampleFormat;
     PaSampleFormat hostInputSampleFormat, hostOutputSampleFormat;
+    int userRequestedHostInputBufferSizeFrames = 0;
+    int userRequestedHostOutputBufferSizeFrames = 0;
     unsigned long suggestedInputLatencyFrames, suggestedOutputLatencyFrames;
     PaWinDirectSoundStreamInfo *inputStreamInfo, *outputStreamInfo;
     PaWinWaveFormatChannelMask inputChannelMask, outputChannelMask;
@@ -1840,6 +1879,9 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
                result = ValidateWinDirectSoundSpecificStreamInfo( inputParameters, inputStreamInfo );
                if( result != paNoError ) return result;
 
+        if( inputStreamInfo && inputStreamInfo->flags & paWinDirectSoundUseLowLevelLatencyParameters )
+            userRequestedHostInputBufferSizeFrames = inputStreamInfo->framesPerBuffer;
+
         if( inputStreamInfo && inputStreamInfo->flags & paWinDirectSoundUseChannelMask )
             inputChannelMask = inputStreamInfo->channelMask;
         else
@@ -1877,6 +1919,9 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
                result = ValidateWinDirectSoundSpecificStreamInfo( outputParameters, outputStreamInfo );
                if( result != paNoError ) return result;   
 
+        if( outputStreamInfo && outputStreamInfo->flags & paWinDirectSoundUseLowLevelLatencyParameters )
+            userRequestedHostOutputBufferSizeFrames = outputStreamInfo->framesPerBuffer;
+
         if( outputStreamInfo && outputStreamInfo->flags & paWinDirectSoundUseChannelMask )
             outputChannelMask = outputStreamInfo->channelMask;
         else
@@ -1889,6 +1934,16 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
         suggestedOutputLatencyFrames = 0;
     }
 
+    /*
+        If low level host buffer size is specified for both input and output
+        the current code requires the sizes to match.
+    */
+
+    if( (userRequestedHostInputBufferSizeFrames > 0 && userRequestedHostOutputBufferSizeFrames > 0)
+            && userRequestedHostInputBufferSizeFrames != userRequestedHostOutputBufferSizeFrames )
+        return paIncompatibleHostApiSpecificStreamInfo;
+
+
 
     /*
         IMPLEMENT ME:
@@ -2027,14 +2082,34 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
 
         /* set up i/o parameters */
 
-        CalculateBufferSettings( &stream->hostBufferSizeFrames, &pollingPeriodFrames,
-                /* isFullDuplex = */ (inputParameters && outputParameters),
-                suggestedInputLatencyFrames,
-                suggestedOutputLatencyFrames, 
-                sampleRate, framesPerBuffer );
+        if( userRequestedHostInputBufferSizeFrames > 0 || userRequestedHostOutputBufferSizeFrames > 0 )
+        {
+            /* use low level parameters */
+
+            /* since we use the same host buffer size for input and output
+               we choose the highest user specified value.
+            */
+            stream->hostBufferSizeFrames = max( userRequestedHostInputBufferSizeFrames, userRequestedHostOutputBufferSizeFrames );
+
+            CalculatePollingPeriodFrames( 
+                    stream->hostBufferSizeFrames, &pollingPeriodFrames,
+                    sampleRate, framesPerBuffer );
+        }
+        else
+        {
+            CalculateBufferSettings( &stream->hostBufferSizeFrames, &pollingPeriodFrames,
+                    /* isFullDuplex = */ (inputParameters && outputParameters),
+                    suggestedInputLatencyFrames,
+                    suggestedOutputLatencyFrames, 
+                    sampleRate, framesPerBuffer );
+        }
 
         stream->pollingPeriodSeconds = pollingPeriodFrames / sampleRate;
 
+        DBUG(("DirectSound host buffer size frames: %d, polling period seconds: %f, @ sr: %f\n", 
+                stream->hostBufferSizeFrames, stream->pollingPeriodSeconds, sampleRate ));
+
+
         /* ------------------ OUTPUT */
         if( outputParameters )
         {
diff --git a/test/patest_dsound_low_level_latency_params.c b/test/patest_dsound_low_level_latency_params.c
new file mode 100644 (file)
index 0000000..24e2066
--- /dev/null
@@ -0,0 +1,187 @@
+/*\r
+ * $Id: $\r
+ * Portable Audio I/O Library\r
+ * Windows DirectSound low level buffer parameters test\r
+ *\r
+ * Copyright (c) 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 <math.h>\r
+\r
+#include "portaudio.h"\r
+#include "pa_win_ds.h"\r
+\r
+#define NUM_SECONDS         (6)\r
+#define SAMPLE_RATE         (44100)\r
+\r
+#define DSOUND_FRAMES_PER_HOST_BUFFER  (256*2) //(440*10)\r
+\r
+#define FRAMES_PER_BUFFER   256\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
+typedef struct\r
+{\r
+    float sine[TABLE_SIZE];\r
+       double phase;\r
+}\r
+paTestData;\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 += 20;\r
+        if( data->phase >= TABLE_SIZE ){\r
+                       data->phase -= TABLE_SIZE;\r
+               }\r
+\r
+               for( j = 0; j < CHANNEL_COUNT; ++j ){\r
+            *out++ = x;\r
+               }\r
+       }\r
+    \r
+    return paContinue;\r
+}\r
+\r
+/*******************************************************************/\r
+int main(int argc, char* argv[])\r
+{\r
+    PaStreamParameters outputParameters;\r
+    PaWinDirectSoundStreamInfo dsoundStreamInfo;\r
+    PaStream *stream;\r
+    PaError err;\r
+    paTestData data;\r
+    int i;\r
+    int deviceIndex;\r
+\r
+    printf("PortAudio Test: output a sine blip on each channel. SR = %d, BufSize = %d, Chans = %d\n", SAMPLE_RATE, FRAMES_PER_BUFFER, CHANNEL_COUNT);\r
+\r
+    err = Pa_Initialize();\r
+    if( err != paNoError ) goto error;\r
+\r
+       deviceIndex = Pa_GetHostApiInfo( Pa_HostApiTypeIdToHostApiIndex( paDirectSound ) )->defaultOutputDevice;\r
+       if( argc == 2 ){\r
+               sscanf( argv[1], "%d", &deviceIndex );\r
+       }\r
+\r
+       printf( "using device id %d (%s)\n", deviceIndex, Pa_GetDeviceInfo(deviceIndex)->name );\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
+    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
+    dsoundStreamInfo.size = sizeof(PaWinDirectSoundStreamInfo);\r
+    dsoundStreamInfo.hostApiType = paDirectSound; \r
+    dsoundStreamInfo.version = 2;\r
+    dsoundStreamInfo.flags = paWinDirectSoundUseLowLevelLatencyParameters;\r
+    dsoundStreamInfo.framesPerBuffer = DSOUND_FRAMES_PER_HOST_BUFFER;\r
+    outputParameters.hostApiSpecificStreamInfo = &dsoundStreamInfo;\r
+   \r
+\r
+       if( Pa_IsFormatSupported( 0, &outputParameters, SAMPLE_RATE ) == paFormatIsSupported  ){\r
+               printf( "Pa_IsFormatSupported reports device will support %d channels.\n", CHANNEL_COUNT );\r
+       }else{\r
+               printf( "Pa_IsFormatSupported reports device will not support %d channels.\n", CHANNEL_COUNT );\r
+       }\r
+\r
+    err = Pa_OpenStream(\r
+              &stream,\r
+              NULL, /* no input */\r
+              &outputParameters,\r
+              SAMPLE_RATE,\r
+              FRAMES_PER_BUFFER,\r
+              paClipOff,      /* 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
+    err = Pa_StartStream( stream );\r
+    if( err != paNoError ) goto error;\r
+\r
+    printf("Play for %d seconds.\n", NUM_SECONDS );\r
+    Pa_Sleep( NUM_SECONDS * 1000 );\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
+    Pa_Terminate();\r
+    printf("Test finished.\n");\r
+    \r
+    return err;\r
+error:\r
+    Pa_Terminate();\r
+    fprintf( stderr, "An error occured 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