From d74ea7b610a6ae54582eb59c5d1b4c56a8e5e31b Mon Sep 17 00:00:00 2001 From: dmitrykos Date: Mon, 10 Dec 2018 10:50:31 +0200 Subject: [PATCH] wasapi: get channels count and sample rate from a mix format (IAudioClient::GetMixFormat) for a PaDeviceInfo to circumvent rare cases when WASAPI does not accept format provided PKEY_AudioEngine_DeviceFormat due to a wrong channel count (bug and solution reported by Etienne Dechamps (edechamps), discussion: http://app.assembla.com/spaces/portaudio/git/merge_requests/7024281), new PaWasapi_GetDeviceMixFormat API to get mix format provided by IAudioClient::GetMixFormat --- include/pa_win_wasapi.h | 22 ++++++++-- src/hostapi/wasapi/pa_win_wasapi.c | 64 +++++++++++++++++++++++++----- 2 files changed, 73 insertions(+), 13 deletions(-) diff --git a/include/pa_win_wasapi.h b/include/pa_win_wasapi.h index 3c5d792..a899bf9 100644 --- a/include/pa_win_wasapi.h +++ b/include/pa_win_wasapi.h @@ -320,7 +320,7 @@ PaError PaWasapi_GetAudioClient( PaStream *pStream, void **pAudioClient, int bOu PaError PaWasapi_UpdateDeviceList(); -/** Returns current sound format of the device assigned to the opened stream. Format is represented by +/** Returns current audio format of the device assigned to the opened stream. Format is represented by PaWinWaveFormat or WAVEFORMATEXTENSIBLE structure. Use this function to reconfirm format if PA's processor is overriden and paWinWasapiRedirectHostProcessor flag is specified. @@ -336,8 +336,9 @@ PaError PaWasapi_UpdateDeviceList(); int PaWasapi_GetDeviceCurrentFormat( PaStream *pStream, void *pFormat, unsigned int nFormatSize, int bOutput ); -/** Returns default sound format for device. Format is represented by PaWinWaveFormat or - WAVEFORMATEXTENSIBLE structure. +/** Returns default audio format for device in Shared Mode. Format is represented by + PaWinWaveFormat or WAVEFORMATEXTENSIBLE structure and obtained by getting + the device property with a PKEY_AudioEngine_DeviceFormat key. @param pFormat Pointer to PaWinWaveFormat or WAVEFORMATEXTENSIBLE structure. @param nFormatSize Size of PaWinWaveFormat or WAVEFORMATEXTENSIBLE structure in bytes. @@ -350,6 +351,21 @@ int PaWasapi_GetDeviceCurrentFormat( PaStream *pStream, void *pFormat, unsigned int PaWasapi_GetDeviceDefaultFormat( void *pFormat, unsigned int nFormatSize, PaDeviceIndex nDevice ); +/** Returns mix audio format for device in Shared Mode. Format is represented by + PaWinWaveFormat or WAVEFORMATEXTENSIBLE structureand obtained by + IAudioClient::GetMixFormat. + + @param pFormat Pointer to PaWinWaveFormat or WAVEFORMATEXTENSIBLE structure. + @param nFormatSize Size of PaWinWaveFormat or WAVEFORMATEXTENSIBLE structure in bytes. + @param nDevice Device index. + + @return Non-negative value indicating the number of bytes copied into format decriptor + or, a PaErrorCode (which are always negative) if PortAudio is not initialized + or an error is encountered. +*/ +int PaWasapi_GetDeviceMixFormat( void *pFormat, unsigned int nFormatSize, PaDeviceIndex nDevice ); + + /** Returns device role (PaWasapiDeviceRole enum). @param nDevice Device index. diff --git a/src/hostapi/wasapi/pa_win_wasapi.c b/src/hostapi/wasapi/pa_win_wasapi.c index 32e6d2d..b95c777 100644 --- a/src/hostapi/wasapi/pa_win_wasapi.c +++ b/src/hostapi/wasapi/pa_win_wasapi.c @@ -422,6 +422,9 @@ typedef struct PaWasapiDeviceInfo // Default format (setup through Control Panel by user) WAVEFORMATEXTENSIBLE DefaultFormat; + // Mix format (internal format used by WASAPI audio engine) + WAVEFORMATEXTENSIBLE MixFormat; + // Fields filled from IMMEndpoint'sGetDataFlow EDataFlow flow; @@ -1586,8 +1589,6 @@ static PaError CreateDeviceList(PaWasapiHostApiRepresentation *paWasapi, PaHostA #ifndef PA_WINRT IMMDeviceCollection* pEndPoints = NULL; IMMDeviceEnumerator *pEnumerator = NULL; -#else - WAVEFORMATEX *mixFormat; #endif IAudioClient *tmpClient; @@ -1866,9 +1867,20 @@ static PaError CreateDeviceList(PaWasapiHostApiRepresentation *paWasapi, PaHostA hr = S_OK; } + // Get mix format + { + WAVEFORMATEX *mixFormat; + + hr = IAudioClient_GetMixFormat(tmpClient, &mixFormat); + if (SUCCEEDED(hr)) + { + memcpy(&paWasapi->devInfo[i].MixFormat, mixFormat, min(sizeof(paWasapi->devInfo[i].MixFormat), (sizeof(*mixFormat) + mixFormat->cbSize))); + CoTaskMemFree(mixFormat); + } + } + + // Register WINRT device #ifdef PA_WINRT - // Get mix format which will treat as default device format - hr = IAudioClient_GetMixFormat(tmpClient, &mixFormat); if (SUCCEEDED(hr)) { // Default device @@ -1880,9 +1892,8 @@ static PaError CreateDeviceList(PaWasapiHostApiRepresentation *paWasapi, PaHostA // State paWasapi->devInfo[i].state = DEVICE_STATE_ACTIVE; - // Default format - memcpy(&paWasapi->devInfo[i].DefaultFormat, mixFormat, min(sizeof(paWasapi->devInfo[i].DefaultFormat), (sizeof(*mixFormat) + mixFormat->cbSize))); - CoTaskMemFree(mixFormat); + // Default format is always a mix format + paWasapi->devInfo[i].DefaultFormat = paWasapi->devInfo[i].MixFormat; // Form-factor paWasapi->devInfo[i].formFactor = UnknownFormFactor; @@ -1917,18 +1928,18 @@ static PaError CreateDeviceList(PaWasapiHostApiRepresentation *paWasapi, PaHostA // we can now fill in portaudio device data deviceInfo->maxInputChannels = 0; deviceInfo->maxOutputChannels = 0; - deviceInfo->defaultSampleRate = paWasapi->devInfo[i].DefaultFormat.Format.nSamplesPerSec; + deviceInfo->defaultSampleRate = paWasapi->devInfo[i].MixFormat.Format.nSamplesPerSec; switch (paWasapi->devInfo[i].flow) { case eRender: { - deviceInfo->maxOutputChannels = paWasapi->devInfo[i].DefaultFormat.Format.nChannels; + deviceInfo->maxOutputChannels = paWasapi->devInfo[i].MixFormat.Format.nChannels; deviceInfo->defaultHighOutputLatency = nano100ToSeconds(paWasapi->devInfo[i].DefaultDevicePeriod); deviceInfo->defaultLowOutputLatency = nano100ToSeconds(paWasapi->devInfo[i].MinimumDevicePeriod); PA_DEBUG(("WASAPI:%d| def.SR[%d] max.CH[%d] latency{hi[%f] lo[%f]}\n", i, (UINT32)deviceInfo->defaultSampleRate, deviceInfo->maxOutputChannels, (float)deviceInfo->defaultHighOutputLatency, (float)deviceInfo->defaultLowOutputLatency)); break;} case eCapture: { - deviceInfo->maxInputChannels = paWasapi->devInfo[i].DefaultFormat.Format.nChannels; + deviceInfo->maxInputChannels = paWasapi->devInfo[i].MixFormat.Format.nChannels; deviceInfo->defaultHighInputLatency = nano100ToSeconds(paWasapi->devInfo[i].DefaultDevicePeriod); deviceInfo->defaultLowInputLatency = nano100ToSeconds(paWasapi->devInfo[i].MinimumDevicePeriod); PA_DEBUG(("WASAPI:%d| def.SR[%d] max.CH[%d] latency{hi[%f] lo[%f]}\n", i, (UINT32)deviceInfo->defaultSampleRate, @@ -2223,6 +2234,39 @@ int PaWasapi_GetDeviceDefaultFormat( void *pFormat, unsigned int nFormatSize, Pa return size; } +// ------------------------------------------------------------------------------------------ +int PaWasapi_GetDeviceMixFormat( void *pFormat, unsigned int nFormatSize, PaDeviceIndex nDevice ) +{ + PaError ret; + PaWasapiHostApiRepresentation *paWasapi; + UINT32 size; + PaDeviceIndex index; + + if (pFormat == NULL) + return paBadBufferPtr; + if (nFormatSize <= 0) + return paBufferTooSmall; + + // Get API + paWasapi = _GetHostApi(&ret); + if (paWasapi == NULL) + return ret; + + // Get device index + ret = PaUtil_DeviceIndexToHostApiDeviceIndex(&index, nDevice, &paWasapi->inheritedHostApiRep); + if (ret != paNoError) + return ret; + + // Validate index + if ((UINT32)index >= paWasapi->deviceCount) + return paInvalidDevice; + + size = min(nFormatSize, (UINT32)sizeof(paWasapi->devInfo[ index ].MixFormat)); + memcpy(pFormat, &paWasapi->devInfo[ index ].MixFormat, size); + + return size; +} + // ------------------------------------------------------------------------------------------ int PaWasapi_GetDeviceRole( PaDeviceIndex nDevice ) { -- 2.43.0