]> Repos - portaudio/commitdiff
wasapi:
authordmitrykos <dmitrykos@0f58301d-fd10-0410-b4af-bbb618454e57>
Wed, 24 Mar 2010 19:08:38 +0000 (19:08 +0000)
committerdmitrykos <dmitrykos@0f58301d-fd10-0410-b4af-bbb618454e57>
Wed, 24 Mar 2010 19:08:38 +0000 (19:08 +0000)
 - fixed bug when Exclusive mode was wrongly opening Stereo stream if Mono stream was requested (note: WASAPI device usually does not support Mono in Exclusive mode), resulted in distorted sound
 - implemented workaround for WASAPI limitation - in Exclusive mode Mono stream is not supported: now PA creates internal audio device in Stereo mode, and after callback call Mono data is mixed into Stereo output, this workaround is not supported for Blocking mode. all this is done transparent for user, automatically.

src/hostapi/wasapi/pa_win_wasapi.c

index b1974ddbcbdc3b44a0ef6789fb847b773b689d9c..71930d53028431bad642abf3c2206358de7788d6 100644 (file)
@@ -320,6 +320,9 @@ enum { S_INPUT = 0, S_OUTPUT, S_COUNT };
 #define SAFE_CLOSE(h) if ((h) != NULL) { CloseHandle((h)); (h) = NULL; }\r
 #define SAFE_RELEASE(punk) if ((punk) != NULL) { (punk)->lpVtbl->Release((punk)); (punk) = NULL; }\r
 \r
+// Mixer function\r
+typedef void (*MixMonoToStereoF) (void *__to, void *__from, UINT32 count);\r
+\r
 // AVRT is the new "multimedia schedulling stuff"\r
 typedef BOOL   (WINAPI *FAvRtCreateThreadOrderingGroup)  (PHANDLE,PLARGE_INTEGER,GUID*,PLARGE_INTEGER);\r
 typedef BOOL   (WINAPI *FAvRtDeleteThreadOrderingGroup)  (HANDLE);\r
@@ -473,6 +476,12 @@ typedef struct PaWasapiSubStream
        // 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
+\r
+       // Used for Mono >> Stereo workaround, if driver does not support it\r
+       // (in Exclusive mode WASAPI usually refuses to operate with Mono (1-ch)\r
+       void                *monoBuffer;         //!< pointer to buffer\r
+       UINT32               monoBufferSize; //!< buffer size in bytes\r
+       MixMonoToStereoF     monoMixer;          //!< pointer to mixer function\r
 }\r
 PaWasapiSubStream;\r
 \r
@@ -839,6 +848,65 @@ static BOOL UseWOW64VistaWorkaround()
        return (IsWow64() && (GetWindowsVersion() == WINDOWS_VISTA_SERVER2008));\r
 }\r
 \r
+// ------------------------------------------------------------------------------------------\r
+#define _WASAPI_MONO_TO_STEREO_MIXER(TYPE)\\r
+       TYPE * __restrict to   = __to;\\r
+       TYPE * __restrict from = __from;\\r
+       TYPE * __restrict end  = from + count;\\r
+       while (from != end)\\r
+       {\\r
+               *to ++ = *from;\\r
+               *to ++ = *from;\\r
+               ++ from;\\r
+       }\r
+\r
+// ------------------------------------------------------------------------------------------\r
+static void _MixMonoToStereo_8(void *__to, void *__from, UINT32 count)\r
+{\r
+       _WASAPI_MONO_TO_STEREO_MIXER(BYTE);\r
+}\r
+\r
+// ------------------------------------------------------------------------------------------\r
+static void _MixMonoToStereo_16(void *__to, void *__from, UINT32 count)\r
+{\r
+       _WASAPI_MONO_TO_STEREO_MIXER(short);\r
+}\r
+\r
+// ------------------------------------------------------------------------------------------\r
+static void _MixMonoToStereo_24(void *__to, void *__from, UINT32 count)\r
+{\r
+#pragma pack(push, 1)\r
+       typedef struct wasapi_int24 { BYTE d[3]; } wasapi_int24; //<< 24 bit value, packed to 1 byte to avoid padding\r
+#pragma pack(pop)\r
+       _WASAPI_MONO_TO_STEREO_MIXER(wasapi_int24);\r
+}\r
+\r
+// ------------------------------------------------------------------------------------------\r
+static void _MixMonoToStereo_32(void *__to, void *__from, UINT32 count)\r
+{\r
+       _WASAPI_MONO_TO_STEREO_MIXER(int);\r
+}\r
+\r
+// ------------------------------------------------------------------------------------------\r
+static void _MixMonoToStereo_32f(void *__to, void *__from, UINT32 count)\r
+{\r
+       _WASAPI_MONO_TO_STEREO_MIXER(float);\r
+}\r
+\r
+// ------------------------------------------------------------------------------------------\r
+static MixMonoToStereoF _GetMonoToStereoMixer(PaSampleFormat format)\r
+{\r
+       switch (format)\r
+       {\r
+       case paUInt8:   return _MixMonoToStereo_8;\r
+       case paInt16:   return _MixMonoToStereo_16;\r
+       case paInt24:   return _MixMonoToStereo_24;\r
+       case paInt32:   return _MixMonoToStereo_32;\r
+       case paFloat32: return _MixMonoToStereo_32f;\r
+       }\r
+       return NULL;\r
+}\r
+\r
 // ------------------------------------------------------------------------------------------\r
 PaError PaWasapi_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex hostApiIndex )\r
 {\r
@@ -1159,13 +1227,13 @@ PaError PaWasapi_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiInd
 \r
     SAFE_RELEASE(pEndPoints);\r
 \r
-       PRINT(("WASAPI: initialized ok"));\r
+       PRINT(("WASAPI: initialized ok\n"));\r
 \r
     return paNoError;\r
 \r
 error:\r
 \r
-       PRINT(("WASAPI: failed %s error[%d|%s]", __FUNCTION__, result, Pa_GetErrorText(result)));\r
+       PRINT(("WASAPI: failed %s error[%d|%s]\n", __FUNCTION__, result, Pa_GetErrorText(result)));\r
 \r
     SAFE_RELEASE(pEndPoints);\r
 \r
@@ -1555,24 +1623,20 @@ static PaError GetClosestFormat(IAudioClient *myClient, double sampleRate,
        }\r
        else\r
        {\r
-               //it doesnt suggest anything?? ok lets show it the MENU!\r
-               int i;\r
-\r
 #define FORMATTESTS 3\r
-static const int BestToWorst[FORMATTESTS]={ paFloat32, paInt24, paInt16 };\r
+static const int BestToWorst[FORMATTESTS] = { paFloat32, paInt24, paInt16 };\r
 \r
-               //ok fun time as with pa_win_mme, we know only a refusal of the user-requested\r
-               //sampleRate+num Channel is disastrous, as the portaudio buffer processor converts between anything\r
-               //so lets only use the number\r
+               // try selecting suitable sample type\r
+               int i;\r
                for (i = 0; i < FORMATTESTS; ++i)\r
                {\r
                        WAVEFORMATEXTENSIBLE ext = { 0 };\r
-                       wasapiFillWFEXT(&ext,BestToWorst[i],sampleRate,params->channelCount);\r
+                       wasapiFillWFEXT(&ext, BestToWorst[i], sampleRate, params->channelCount);\r
 \r
                        hr = IAudioClient_IsFormatSupported(myClient, shareMode, &ext.Format, (shareMode == AUDCLNT_SHAREMODE_SHARED ? &sharedClosestMatch : NULL));\r
                        if (hr == S_OK)\r
                        {\r
-                               memcpy(outWavex,&ext,sizeof(WAVEFORMATEXTENSIBLE));\r
+                               memcpy(outWavex, &ext, sizeof(WAVEFORMATEXTENSIBLE));\r
                                answer = paFormatIsSupported;\r
                                break;\r
                        }\r
@@ -1580,22 +1644,20 @@ static const int BestToWorst[FORMATTESTS]={ paFloat32, paInt24, paInt16 };
 \r
                if (answer != paFormatIsSupported)\r
                {\r
-                       // try MIX format?\r
-                       // why did it HAVE to come to this ....\r
-                       WAVEFORMATEX pcm16WaveFormat = { 0 };\r
-                       pcm16WaveFormat.wFormatTag              = WAVE_FORMAT_PCM;\r
-                       pcm16WaveFormat.nChannels               = 2;\r
-                       pcm16WaveFormat.nSamplesPerSec  = (DWORD)sampleRate;\r
-                       pcm16WaveFormat.nBlockAlign             = 4;\r
-                       pcm16WaveFormat.nAvgBytesPerSec = pcm16WaveFormat.nSamplesPerSec*pcm16WaveFormat.nBlockAlign;\r
-                       pcm16WaveFormat.wBitsPerSample  = 16;\r
-                       pcm16WaveFormat.cbSize                  = 0;\r
-\r
-                       hr = IAudioClient_IsFormatSupported(myClient, shareMode, &pcm16WaveFormat, (shareMode == AUDCLNT_SHAREMODE_SHARED ? &sharedClosestMatch : NULL));\r
-                       if (hr == S_OK)\r
+                       // If mono, then driver does not support 1 channel, we use internal workaround\r
+                       // of tiny software mixing functionality, e.g. we provide to user buffer for 1 channel\r
+                       // but then mix into 2 of device buffer\r
+                       if (params->channelCount == 1)\r
                        {\r
-                               memcpy(outWavex,&pcm16WaveFormat,sizeof(WAVEFORMATEX));\r
-                               answer = paFormatIsSupported;\r
+                               WAVEFORMATEXTENSIBLE stereo = { 0 };\r
+                               wasapiFillWFEXT(&stereo, params->sampleFormat, sampleRate, 2);\r
+\r
+                               hr = IAudioClient_IsFormatSupported(myClient, shareMode, &stereo.Format, (shareMode == AUDCLNT_SHAREMODE_SHARED ? &sharedClosestMatch : NULL));\r
+                               if (hr == S_OK)\r
+                               {\r
+                                       memcpy(outWavex, &stereo, sizeof(WAVEFORMATEXTENSIBLE));\r
+                                       answer = paFormatIsSupported;\r
+                               }\r
                        }\r
                }\r
 \r
@@ -1754,7 +1816,7 @@ static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi,
 // ------------------------------------------------------------------------------------------\r
 static HRESULT CreateAudioClient(PaWasapiSubStream *pSubStream, PaWasapiDeviceInfo *info,\r
        const PaStreamParameters *params, UINT32 framesPerLatency, double sampleRate, UINT32 streamFlags,\r
-       PaError *pa_error)\r
+       BOOL blocking, PaError *pa_error)\r
 {\r
        PaError error;\r
     HRESULT hr                                 = S_OK;\r
@@ -1782,6 +1844,18 @@ static HRESULT CreateAudioClient(PaWasapiSubStream *pSubStream, PaWasapiDeviceIn
                return AUDCLNT_E_UNSUPPORTED_FORMAT;\r
        }\r
 \r
+       // Check for Mono >> Stereo workaround\r
+       if ((params->channelCount == 1) && (pSubStream->wavex.Format.nChannels == 2))\r
+       {\r
+               if (blocking)\r
+                       return AUDCLNT_E_UNSUPPORTED_FORMAT; // fail, blocking mode not supported\r
+\r
+               // select mixer\r
+               pSubStream->monoMixer = _GetMonoToStereoMixer(params->sampleFormat);\r
+               if (pSubStream->monoMixer == NULL)\r
+                       return AUDCLNT_E_UNSUPPORTED_FORMAT; // fail, no mixer for format\r
+       }\r
+\r
        // Add latency frames\r
        framesPerLatency += MakeFramesFromHns(SecondsTonano100(params->suggestedLatency), pSubStream->wavex.Format.nSamplesPerSec);\r
 \r
@@ -1844,6 +1918,18 @@ static HRESULT CreateAudioClient(PaWasapiSubStream *pSubStream, PaWasapiDeviceIn
                if (GetClosestFormat(pAudioClient, sampleRate, params, pSubStream->shareMode, &pSubStream->wavex) != paFormatIsSupported)\r
                        return AUDCLNT_E_UNSUPPORTED_FORMAT;\r
 \r
+               // Check for Mono >> Stereo workaround\r
+               if ((params->channelCount == 1) && (pSubStream->wavex.Format.nChannels == 2))\r
+               {\r
+                       if (blocking)\r
+                               return AUDCLNT_E_UNSUPPORTED_FORMAT; // fail, blocking mode not supported\r
+\r
+                       // select mixer\r
+                       pSubStream->monoMixer = _GetMonoToStereoMixer(params->sampleFormat);\r
+                       if (pSubStream->monoMixer == NULL)\r
+                               return AUDCLNT_E_UNSUPPORTED_FORMAT; // fail, no mixer for format\r
+               }\r
+\r
                // Calculate period\r
                pSubStream->period = MakeHnsPeriod(nFrames, pSubStream->wavex.Format.nSamplesPerSec);\r
 \r
@@ -1983,7 +2069,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
 \r
                // Create Audio client\r
                hr = CreateAudioClient(&stream->in, info, inputParameters, 0/*framesPerLatency*/,\r
-                       sampleRate, stream->in.streamFlags, &result);\r
+                       sampleRate, stream->in.streamFlags, (streamCallback == NULL), &result);\r
         if (hr != S_OK)\r
                {\r
             LogHostError(hr);\r
@@ -2046,7 +2132,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
                // Append buffer latency to interface latency in shared mode (see GetStreamLatency notes)\r
                stream->in.latency_seconds += buffer_latency;\r
 \r
-               PRINT(("PaWASAPIOpenStream(input): framesPerHostCallback[ %d ] latency[ %.02fms ]\n", (UINT32)stream->in.framesPerHostCallback, (float)(stream->in.latency_seconds*1000.0f)));\r
+               PRINT(("PaWASAPIOpenStream(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->useVistaWOW64Workaround ? "YES" : "NO")));\r
        }\r
     else\r
     {\r
@@ -2092,7 +2178,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
 \r
                // Create Audio client\r
                hr = CreateAudioClient(&stream->out, info, outputParameters, 0/*framesPerLatency*/,\r
-                       sampleRate, stream->out.streamFlags, &result);\r
+                       sampleRate, stream->out.streamFlags, (streamCallback == NULL), &result);\r
         if (hr != S_OK)\r
                {\r
             LogHostError(hr);\r
@@ -2155,7 +2241,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
                // Append buffer latency to interface latency in shared mode (see GetStreamLatency notes)\r
                stream->out.latency_seconds += buffer_latency;\r
 \r
-               PRINT(("PaWASAPIOpenStream(output): framesPerHostCallback[ %d ] latency[ %.02fms ]\n", (UINT32)stream->out.framesPerHostCallback, (float)(stream->out.latency_seconds*1000.0f)));\r
+               PRINT(("PaWASAPIOpenStream(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->useVistaWOW64Workaround ? "YES" : "NO")));\r
        }\r
     else\r
     {\r
@@ -2306,6 +2392,9 @@ static PaError CloseStream( PaStream* s )
        SAFE_CLOSE(stream->hBlockingOpStreamRD);\r
        SAFE_CLOSE(stream->hBlockingOpStreamWR);\r
 \r
+       free(stream->in.monoBuffer);\r
+       free(stream->out.monoBuffer);\r
+\r
     PaUtil_TerminateBufferProcessor(&stream->bufferProcessor);\r
     PaUtil_TerminateStreamRepresentation(&stream->streamRepresentation);\r
     PaUtil_FreeMemory(stream);\r
@@ -2954,7 +3043,29 @@ static HRESULT FillOutputBuffer(PaWasapiStream *stream, PaWasapiHostProcessor *p
        }\r
 \r
        // Process data\r
-       processor[S_OUTPUT].processor(NULL, 0, data, frames, processor[S_OUTPUT].userData);\r
+       if (stream->out.monoMixer != NULL)\r
+       {\r
+#define __DIV_8(v)  ((v) >> 3) //!< (v / 8)\r
+\r
+               // expand buffer (one way only for better performancedue to no calls to realloc)\r
+               UINT32 mono_frames_size = frames * __DIV_8(stream->out.wavex.Format.wBitsPerSample);\r
+               if (mono_frames_size > stream->out.monoBufferSize)\r
+               {\r
+                       stream->out.monoBuffer = realloc(stream->out.monoBuffer, mono_frames_size);\r
+               }\r
+\r
+               // feed to user\r
+               processor[S_OUTPUT].processor(NULL, 0, (BYTE *)stream->out.monoBuffer, frames, processor[S_OUTPUT].userData);\r
+\r
+               // mix 1 to 2 channels\r
+               stream->out.monoMixer(data, stream->out.monoBuffer, frames);\r
+\r
+#undef __DIV_8\r
+       }\r
+       else\r
+       {\r
+               processor[S_OUTPUT].processor(NULL, 0, data, frames, processor[S_OUTPUT].userData);\r
+       }\r
 \r
        // Release buffer\r
        if ((hr = IAudioRenderClient_ReleaseBuffer(stream->rclient, frames, 0)) != S_OK)\r
@@ -2987,8 +3098,30 @@ static HRESULT PollInputBuffer(PaWasapiStream *stream, PaWasapiHostProcessor *pr
                // if (flags & AUDCLNT_BUFFERFLAGS_SILENT)\r
                //      data = NULL;\r
 \r
-               // Process data\r
-        processor[S_INPUT].processor(data, frames, NULL, 0, processor[S_INPUT].userData);\r
+               // Process data\r
+               if (stream->in.monoMixer != NULL)\r
+               {\r
+#define __DIV_8(v)  ((v) >> 3) //!< (v / 8)\r
+\r
+                       // expand buffer (one way only for better performancedue to no calls to realloc)\r
+                       UINT32 mono_frames_size = frames * __DIV_8(stream->in.wavex.Format.wBitsPerSample);\r
+                       if (mono_frames_size > stream->in.monoBufferSize)\r
+                       {\r
+                               stream->in.monoBuffer = realloc(stream->in.monoBuffer, mono_frames_size);\r
+                       }\r
+\r
+                       // feed to user\r
+                       processor[S_INPUT].processor((BYTE *)stream->in.monoBuffer, frames, NULL, 0, processor[S_INPUT].userData);\r
+\r
+                       // mix 1 to 2 channels\r
+                       stream->in.monoMixer(data, stream->in.monoBuffer, frames);\r
+\r
+#undef __DIV_8\r
+               }\r
+               else\r
+               {\r
+                       processor[S_INPUT].processor(data, frames, NULL, 0, processor[S_INPUT].userData);\r
+               }\r
 \r
                // Release buffer\r
                if ((hr = IAudioCaptureClient_ReleaseBuffer(stream->cclient, frames)) != S_OK)\r