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