]> Repos - portaudio/commitdiff
wasapi:
authordmitrykos <dmitrykos@0f58301d-fd10-0410-b4af-bbb618454e57>
Tue, 6 Jul 2010 07:28:48 +0000 (07:28 +0000)
committerdmitrykos <dmitrykos@0f58301d-fd10-0410-b4af-bbb618454e57>
Tue, 6 Jul 2010 07:28:48 +0000 (07:28 +0000)
 - reimplemented suggestedLatency and framesPerBuffer parameters in Pa_OpenStream call to opearate by the following formula - 'hostBufferFrames = userFramesPerBuffer + max(userFramesPerBuffer, (suggestedLatency * sampleRate))' as discussed on PA mailing-lists
 - it is now possible to achieve 5.33ms latency in Polling & Exclusive mode (WOW64 or Native)
 - Polling method for output will benefit from direct WASAPI buffer pointer exposure to a user space through a callback (no copying)

src/hostapi/wasapi/pa_win_wasapi.c

index 1518424d5e8053cd3536bbe96fb749f4c2235d84..f5dd0ae83ea2926e1ca29c2fe201714206f049b4 100644 (file)
@@ -479,6 +479,11 @@ typedef struct PaWasapiSubStream
        UINT32               streamFlags; // AUDCLNT_STREAMFLAGS_EVENTCALLBACK, ...\r
        UINT32               flags;\r
 \r
+       // Buffers\r
+       UINT32               buffers;                   //!< number of buffers used (from host side)\r
+       UINT32               framesPerBuffer;   //!< number of frames per 1 buffer\r
+       BOOL                 userBufferAndHostMatch;\r
+\r
        // Used by blocking interface:\r
        UINT32               prevTime;  // time ms between calls of WriteStream\r
        UINT32               prevSleep; // time ms to sleep from frames written in previous call\r
@@ -521,6 +526,9 @@ typedef struct PaWasapiStream
        // event handles for event-driven processing mode\r
        HANDLE event[S_COUNT];\r
 \r
+       // buffer mode\r
+       PaUtilHostBufferSizeMode bufferMode;\r
+\r
        // must be volatile to avoid race condition on user query while\r
        // thread is being started\r
     volatile BOOL running;\r
@@ -1920,14 +1928,23 @@ static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi,
     return paFormatIsSupported;\r
 }\r
 \r
+// ------------------------------------------------------------------------------------------\r
+static PaUint32 PaUtil_GetFramesPerHostBuffer(PaUint32 userFramesPerBuffer, PaTime suggestedLatency, double sampleRate, PaUint32 TimerJitterMs)\r
+{\r
+       PaUint32 frames = userFramesPerBuffer + max( userFramesPerBuffer, (PaUint32)(suggestedLatency * sampleRate) );\r
+    frames += (PaUint32)((sampleRate * 0.001) * TimerJitterMs);\r
+       return frames;\r
+}\r
+\r
 // ------------------------------------------------------------------------------------------\r
 static HRESULT CreateAudioClient(PaWasapiSubStream *pSubStream, PaWasapiDeviceInfo *info,\r
        const PaStreamParameters *params, UINT32 framesPerLatency, double sampleRate, UINT32 streamFlags,\r
-       BOOL blocking, BOOL output, PaError *pa_error)\r
+       BOOL blocking, BOOL output, BOOL full_duplex, PaError *pa_error)\r
 {\r
        PaError error;\r
     HRESULT hr                                 = S_OK;\r
     UINT32 nFrames                             = 0;\r
+       UINT32 userFramesPerBuffer  = framesPerLatency;\r
     IAudioClient *pAudioClient = NULL;\r
        double suggestedLatency     = 0.0;\r
 \r
@@ -1972,12 +1989,42 @@ static HRESULT CreateAudioClient(PaWasapiSubStream *pSubStream, PaWasapiDeviceIn
                }\r
        }\r
 \r
+#if 0\r
        // Correct latency to default device period (in Exclusive mode this is 10ms) if user selected 0\r
-       //suggestedLatency = (params->suggestedLatency < 0.001 ? nano100ToSeconds(info->DefaultDevicePeriod) : params->suggestedLatency);\r
        suggestedLatency = params->suggestedLatency;\r
-\r
-       // Add latency frames\r
+       // Add suggestd latency\r
        framesPerLatency += MakeFramesFromHns(SecondsTonano100(suggestedLatency), pSubStream->wavex.Format.nSamplesPerSec);\r
+#else\r
+       // Calculate host buffer size\r
+       if ((pSubStream->shareMode != AUDCLNT_SHAREMODE_EXCLUSIVE) && \r
+               (!streamFlags || ((streamFlags & AUDCLNT_STREAMFLAGS_EVENTCALLBACK) == 0)))\r
+       {\r
+               framesPerLatency = PaUtil_GetFramesPerHostBuffer(framesPerLatency, \r
+                       params->suggestedLatency, pSubStream->wavex.Format.nSamplesPerSec, 0/*,\r
+                       (streamFlags & AUDCLNT_STREAMFLAGS_EVENTCALLBACK ? 0 : 1)*/);\r
+       }\r
+       else\r
+       {\r
+               // do nothing, work 1:1 with buffer (only polling allows to work with >1)\r
+       }\r
+#endif\r
+\r
+       // Count buffers (must be at least 1)\r
+       pSubStream->buffers = (userFramesPerBuffer ? framesPerLatency / userFramesPerBuffer : 0);\r
+       if (pSubStream->buffers == 0)\r
+               pSubStream->buffers = 1;\r
+\r
+       // Determine amount of buffers to be used:\r
+       // - Exclusive mode does not allow >1 buffers be used, e.g. GetBuffer call must acquire\r
+       //   max buffer size and it all must be processed.\r
+       // - Full-duplex mode will lead to period difference, thus only 1.\r
+       // - Input mode, only 1, as WASAPI allows extraction of only 1 packet.\r
+       // - For Shared mode we use safe double buffering (2 buffers).\r
+       if ((pSubStream->shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE) || full_duplex/* && output*/)\r
+       {\r
+               pSubStream->userBufferAndHostMatch = 1;\r
+               pSubStream->buffers = 1;\r
+       }\r
 \r
        // Avoid 0 frames\r
        if (framesPerLatency == 0)\r
@@ -2127,6 +2174,9 @@ static const REFERENCE_TIME MAX_BUFFER_POLL_DURATION  = 2000 * 10000;
                        }\r
                }\r
 \r
+               // Reset buffers count to single\r
+               pSubStream->buffers = 1;\r
+\r
                // Calculate period\r
                pSubStream->period = MakeHnsPeriod(nFrames, pSubStream->wavex.Format.nSamplesPerSec);\r
 \r
@@ -2266,7 +2316,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
                }\r
 \r
                // Choose processing mode\r
-               stream->in.streamFlags = AUDCLNT_STREAMFLAGS_EVENTCALLBACK;\r
+               stream->in.streamFlags = (stream->in.shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE ? AUDCLNT_STREAMFLAGS_EVENTCALLBACK : 0);\r
                if (paWasapi->useWOW64Workaround)\r
                        stream->in.streamFlags = 0; // polling interface\r
                else\r
@@ -2281,7 +2331,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
 \r
                // Create Audio client\r
                hr = CreateAudioClient(&stream->in, info, inputParameters, framesPerBuffer/*framesPerLatency*/,\r
-                       sampleRate, stream->in.streamFlags, (streamCallback == NULL), FALSE, &result);\r
+                       sampleRate, stream->in.streamFlags, (streamCallback == NULL), FALSE, fullDuplex, &result);\r
         if (hr != S_OK)\r
                {\r
             LogHostError(hr);\r
@@ -2338,13 +2388,17 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
         // Number of frames that are required at each period\r
                stream->in.framesPerHostCallback = maxBufferSize;\r
 \r
+               // Calculate frames per single buffer, if buffers > 1 then always framesPerBuffer\r
+               stream->in.framesPerBuffer = \r
+                       (stream->in.userBufferAndHostMatch ? stream->in.framesPerHostCallback : framesPerBuffer);\r
+\r
                // Calculate buffer latency\r
                buffer_latency = (PaTime)maxBufferSize / stream->in.wavex.Format.nSamplesPerSec;\r
 \r
                // Append buffer latency to interface latency in shared mode (see GetStreamLatency notes)\r
                stream->in.latency_seconds += buffer_latency;\r
 \r
-               PRINT(("WASAPI::OpenStream(input): framesPerHostCallback[ %d ] latency[ %.02fms ] exclusive[ %s ] wow64_fix[ %s ]\n", (UINT32)stream->in.framesPerHostCallback, (float)(stream->in.latency_seconds*1000.0f), (stream->in.shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE ? "YES" : "NO"), (paWasapi->useWOW64Workaround ? "YES" : "NO")));\r
+               PRINT(("WASAPI::OpenStream(input): framesPerUser[ %d ] framesPerHost[ %d ] latency[ %.02fms ] exclusive[ %s ] wow64_fix[ %s ]\n", (UINT32)framesPerBuffer, (UINT32)stream->in.framesPerHostCallback, (float)(stream->in.latency_seconds*1000.0f), (stream->in.shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE ? "YES" : "NO"), (paWasapi->useWOW64Workaround ? "YES" : "NO")));\r
        }\r
     else\r
     {\r
@@ -2380,7 +2434,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
                }\r
 \r
                // Choose processing mode\r
-               stream->out.streamFlags = AUDCLNT_STREAMFLAGS_EVENTCALLBACK;\r
+               stream->out.streamFlags = (stream->out.shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE ? AUDCLNT_STREAMFLAGS_EVENTCALLBACK : 0);\r
                if (paWasapi->useWOW64Workaround)\r
                        stream->out.streamFlags = 0; // polling interface\r
                else\r
@@ -2395,7 +2449,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
 \r
                // Create Audio client\r
                hr = CreateAudioClient(&stream->out, info, outputParameters, framesPerBuffer/*framesPerLatency*/,\r
-                       sampleRate, stream->out.streamFlags, (streamCallback == NULL), TRUE, &result);\r
+                       sampleRate, stream->out.streamFlags, (streamCallback == NULL), TRUE, fullDuplex, &result);\r
         if (hr != S_OK)\r
                {\r
             LogHostError(hr);\r
@@ -2452,13 +2506,17 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
         // Number of frames that are required at each period\r
                stream->out.framesPerHostCallback = maxBufferSize;\r
 \r
+               // Calculate frames per single buffer, if buffers > 1 then always framesPerBuffer\r
+               stream->out.framesPerBuffer = \r
+                       (stream->out.userBufferAndHostMatch ? stream->out.framesPerHostCallback : framesPerBuffer);\r
+\r
                // Calculate buffer latency\r
                buffer_latency = (PaTime)maxBufferSize / stream->out.wavex.Format.nSamplesPerSec;\r
 \r
                // Append buffer latency to interface latency in shared mode (see GetStreamLatency notes)\r
                stream->out.latency_seconds += buffer_latency;\r
 \r
-               PRINT(("WASAPI::OpenStream(output): framesPerHostCallback[ %d ] latency[ %.02fms ] exclusive[ %s ] wow64_fix[ %s ]\n", (UINT32)stream->out.framesPerHostCallback, (float)(stream->out.latency_seconds*1000.0f), (stream->out.shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE ? "YES" : "NO"), (paWasapi->useWOW64Workaround ? "YES" : "NO")));\r
+               PRINT(("WASAPI::OpenStream(output): framesPerUser[ %d ] framesPerHost[ %d ] latency[ %.02fms ] exclusive[ %s ] wow64_fix[ %s ]\n", (UINT32)framesPerBuffer, (UINT32)stream->out.framesPerHostCallback, (float)(stream->out.latency_seconds*1000.0f), (stream->out.shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE ? "YES" : "NO"), (paWasapi->useWOW64Workaround ? "YES" : "NO")));\r
        }\r
     else\r
     {\r
@@ -2525,20 +2583,23 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
                }*/\r
        }\r
 \r
-       framesPerHostCallback = (outputParameters) ? stream->out.framesPerHostCallback:\r
-               stream->in.framesPerHostCallback;\r
+       // Calculate frames per host for processor\r
+       framesPerHostCallback = (outputParameters ? stream->out.framesPerBuffer : stream->in.framesPerBuffer);\r
 \r
        // Choose correct mode of buffer processing:\r
        // Exclusive/Shared non paWinWasapiPolling mode: paUtilFixedHostBufferSize - always fixed\r
-       // Exclusive/Shared paWinWasapiPolling mode: paUtilBoundedHostBufferSize - may vary\r
+       // Exclusive/Shared paWinWasapiPolling mode: paUtilBoundedHostBufferSize - may vary for Exclusive or Full-duplex\r
        bufferMode = paUtilFixedHostBufferSize;\r
-       if (inputParameters /*&& // !!! WASAPI IAudioCaptureClient::GetBuffer extracts not number of frames but 1 packet, thus we always must adapt\r
-               (!stream->in.streamFlags || ((stream->in.streamFlags & AUDCLNT_STREAMFLAGS_EVENTCALLBACK) == 0))*/)\r
+       if (inputParameters) // !!! WASAPI IAudioCaptureClient::GetBuffer extracts not number of frames but 1 packet, thus we always must adapt\r
                bufferMode = paUtilBoundedHostBufferSize;\r
        else\r
-       if (outputParameters &&\r
-               (!stream->out.streamFlags || ((stream->out.streamFlags & AUDCLNT_STREAMFLAGS_EVENTCALLBACK) == 0)))\r
-               bufferMode = paUtilBoundedHostBufferSize;\r
+       if (outputParameters)\r
+       {\r
+               if ((stream->out.buffers == 1) && \r
+                       (!stream->out.streamFlags || ((stream->out.streamFlags & AUDCLNT_STREAMFLAGS_EVENTCALLBACK) == 0)))\r
+                       bufferMode = paUtilBoundedHostBufferSize;\r
+       }\r
+       stream->bufferMode = bufferMode;\r
 \r
     // Initialize buffer processor\r
     result =  PaUtil_InitializeBufferProcessor(\r
@@ -2565,12 +2626,12 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
        // Set Input latency\r
     stream->streamRepresentation.streamInfo.inputLatency =\r
             PaUtil_GetBufferProcessorInputLatency(&stream->bufferProcessor)\r
-                       + ((inputParameters)?stream->in.latency_seconds :0);\r
+                       + ((inputParameters)?stream->in.latency_seconds : 0);\r
 \r
        // Set Output latency\r
     stream->streamRepresentation.streamInfo.outputLatency =\r
             PaUtil_GetBufferProcessorOutputLatency(&stream->bufferProcessor)\r
-                       + ((outputParameters)?stream->out.latency_seconds :0);\r
+                       + ((outputParameters)?stream->out.latency_seconds : 0);\r
 \r
        // Set SR\r
     stream->streamRepresentation.streamInfo.sampleRate = sampleRate;\r
@@ -3542,7 +3603,7 @@ PA_THREAD_FUNC ProcThreadEvent(void *param)
                }\r
 \r
                // Preload buffer before start\r
-               if ((hr = ProcessOutputBuffer(stream, processor, stream->out.framesPerHostCallback)) != S_OK)\r
+               if ((hr = ProcessOutputBuffer(stream, processor, stream->out.framesPerBuffer)) != S_OK)\r
                {\r
                        LogHostError(hr);\r
                        goto thread_error;\r
@@ -3593,7 +3654,7 @@ PA_THREAD_FUNC ProcThreadEvent(void *param)
                case WAIT_OBJECT_0 + S_OUTPUT: {\r
             if (stream->rclient == NULL)\r
                 break;\r
-                       ProcessOutputBuffer(stream, processor, stream->out.framesPerHostCallback);\r
+                       ProcessOutputBuffer(stream, processor, stream->out.framesPerBuffer);\r
                        break; }\r
                }\r
        }\r
@@ -3652,14 +3713,26 @@ PA_THREAD_FUNC ProcThreadPoll(void *param)
 \r
        // Calculate the actual duration of the allocated buffer.\r
        DWORD sleep_ms     = 0;\r
-       DWORD sleep_ms_in  = GetFramesSleepTime(stream->in.framesPerHostCallback, stream->in.wavex.Format.nSamplesPerSec);\r
-       DWORD sleep_ms_out = GetFramesSleepTime(stream->out.framesPerHostCallback, stream->out.wavex.Format.nSamplesPerSec);\r
+       DWORD sleep_ms_in  = GetFramesSleepTime(stream->in.framesPerBuffer, stream->in.wavex.Format.nSamplesPerSec);\r
+       DWORD sleep_ms_out = GetFramesSleepTime(stream->out.framesPerBuffer, stream->out.wavex.Format.nSamplesPerSec);\r
+       \r
+       // Adjust polling time\r
+       if (stream->bufferMode != paUtilFixedHostBufferSize)\r
+       {\r
+               sleep_ms_in  = GetFramesSleepTime(stream->bufferProcessor.framesPerUserBuffer, stream->in.wavex.Format.nSamplesPerSec);\r
+               sleep_ms_out = GetFramesSleepTime(stream->bufferProcessor.framesPerUserBuffer, stream->out.wavex.Format.nSamplesPerSec);\r
+       }\r
+\r
+       // Choose smallest      \r
        if ((sleep_ms_in != 0) && (sleep_ms_out != 0))\r
                sleep_ms = min(sleep_ms_in, sleep_ms_out);\r
        else\r
        {\r
                sleep_ms = (sleep_ms_in ? sleep_ms_in : sleep_ms_out);\r
        }\r
+       // Make sure not 0\r
+       if (sleep_ms == 0)\r
+               sleep_ms = 1;\r
 \r
     // Setup data processors\r
     defaultProcessor.processor = WaspiHostProcessingLoop;\r
@@ -3706,7 +3779,7 @@ PA_THREAD_FUNC ProcThreadPoll(void *param)
                // when in full-duplex mode as it requires input processing as well\r
                if (!PA_WASAPI__IS_FULLDUPLEX(stream))\r
                {\r
-                       if ((hr = ProcessOutputBuffer(stream, processor, stream->out.framesPerHostCallback)) != S_OK)\r
+                       if ((hr = ProcessOutputBuffer(stream, processor, stream->out.framesPerBuffer)) != S_OK)\r
                        {\r
                                LogHostError(hr);\r
                                goto thread_error;\r
@@ -3757,10 +3830,18 @@ PA_THREAD_FUNC ProcThreadPoll(void *param)
                                                LogHostError(hr);\r
                                                break;\r
                                        }\r
-\r
+                                       \r
                                        // output\r
-                                       if (frames != 0)\r
-                                               ProcessOutputBuffer(stream, processor, frames);\r
+                                       if (stream->bufferMode == paUtilFixedHostBufferSize)\r
+                                       {\r
+                                               if (frames >= stream->out.framesPerBuffer)\r
+                                                       ProcessOutputBuffer(stream, processor, stream->out.framesPerBuffer);\r
+                                       }\r
+                                       else\r
+                                       {\r
+                                               if (frames != 0)\r
+                                                       ProcessOutputBuffer(stream, processor, frames);\r
+                                       }\r
 \r
                                        break; }\r
                                }\r