From 21a51f4bea2b8310417a4de03a3d9fab5072cdaf Mon Sep 17 00:00:00 2001 From: Etienne Dechamps Date: Sun, 12 Mar 2023 17:35:01 +0000 Subject: [PATCH] mme: don't restrict the host buffer to 16-bit Currently, the MME Host API code only creates 16-bit integer MME buffers. All audio data provided by the user is therefore converted by PortAudio to and from 16-bit, regardless of the user buffer format. This basically makes it impossible to pass 24-bit audio through the MME Host API. If the user buffer format is not 16-bit, this also causes pointless conversions to take place, *even if the hardware is running at 16-bit*, because modern Windows versions (Vista+) convert the data to floating point behind the scenes before it is handed off to the hardware. This can lead to silly situations where 32-bit float samples from the user are (lossily) converted to 16-bit by PortAudio, then ended off to Windows via MME, only to be converted back to 32-bit float again, before finally being converted to the format the hardware device is configured to use. This can easily lead to two layers of 16-bit dithering (one from PortAudio, and one from Windows) being piled on top of each other, resulting in an elevated noise floor. This commit fixes this problem by configuring the MME buffers to use the same format as the user buffer. This should stop PortAudio from converting samples in all cases except paInt8, which is not supported by WAVEFORMATEX (only paUInt8 is). This is pretty much the same idea as #774. The new code assumes that MME will accept whatever format we throw at it. I feel confident that this is always true on Vista+ regardless of hardware, because starting from Vista MME does not access hardware directly - it always goes through the Windows Audio Engine which supports all formats in both directions (I verified this using paloopback). On pre-Vista Windows, this should still work all the way back to Windows 98 SE, because MME goes through KMixer which supports all formats [1]. [1]: https://learn.microsoft.com/en-us/windows-hardware/drivers/audio/background-of-non-pcm-support#waveout-api Fixes #139 --- src/hostapi/wmme/pa_win_wmme.c | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/src/hostapi/wmme/pa_win_wmme.c b/src/hostapi/wmme/pa_win_wmme.c index 0f205eec8..b6fcc8d3e 100644 --- a/src/hostapi/wmme/pa_win_wmme.c +++ b/src/hostapi/wmme/pa_win_wmme.c @@ -321,6 +321,11 @@ static void PaMme_SetLastSystemError( DWORD errorCode ) PaMme_SetLastSystemError( errorCode ) +static int PaMme_IsWindowsVistaOrGreater() { + return LOBYTE(LOWORD(GetVersion())) >= 6; +} + + /* PaError returning wrappers for some commonly used win32 functions note that we allow passing a null ptr to have no effect. */ @@ -1771,7 +1776,7 @@ static void InitializeSingleDirectionHandlesAndBuffers( PaWinMmeSingleDirectionH static PaError InitializeWaveHandles( PaWinMmeHostApiRepresentation *winMmeHostApi, PaWinMmeSingleDirectionHandlesAndBuffers *handlesAndBuffers, unsigned long winMmeSpecificFlags, - unsigned long bytesPerHostSample, + PaSampleFormat hostSampleFormat, double sampleRate, PaWinMmeDeviceAndChannelCount *devices, unsigned int deviceCount, PaWinWaveFormatChannelMask channelMask, int isInput ); static PaError TerminateWaveHandles( PaWinMmeSingleDirectionHandlesAndBuffers *handlesAndBuffers, int isInput, int currentlyProcessingAnError ); @@ -1796,14 +1801,13 @@ static void InitializeSingleDirectionHandlesAndBuffers( PaWinMmeSingleDirectionH static PaError InitializeWaveHandles( PaWinMmeHostApiRepresentation *winMmeHostApi, PaWinMmeSingleDirectionHandlesAndBuffers *handlesAndBuffers, unsigned long winMmeSpecificFlags, - unsigned long bytesPerHostSample, + PaSampleFormat hostSampleFormat, double sampleRate, PaWinMmeDeviceAndChannelCount *devices, unsigned int deviceCount, PaWinWaveFormatChannelMask channelMask, int isInput ) { PaError result; MMRESULT mmresult; signed int i, j; - PaSampleFormat sampleFormat; int waveFormatTag; /* for error cleanup we expect that InitializeSingleDirectionHandlesAndBuffers() @@ -1832,9 +1836,7 @@ static PaError InitializeWaveHandles( PaWinMmeHostApiRepresentation *winMmeHostA ((HWAVEOUT*)handlesAndBuffers->waveHandles)[i] = 0; } - /* @todo at the moment we only use 16 bit sample format */ - sampleFormat = paInt16; - waveFormatTag = SampleFormatAndWinWmmeSpecificFlagsToLinearWaveFormatTag( sampleFormat, winMmeSpecificFlags ); + waveFormatTag = SampleFormatAndWinWmmeSpecificFlagsToLinearWaveFormatTag( hostSampleFormat, winMmeSpecificFlags ); for( i = 0; i < (signed int)deviceCount; ++i ) { @@ -1852,14 +1854,14 @@ static PaError InitializeWaveHandles( PaWinMmeHostApiRepresentation *winMmeHostA if this fails we fall back to WAVEFORMATEX */ PaWin_InitializeWaveFormatExtensible( &waveFormat, devices[i].channelCount, - sampleFormat, waveFormatTag, sampleRate, channelMask ); + hostSampleFormat, waveFormatTag, sampleRate, channelMask ); break; case 1: /* retry with WAVEFORMATEX */ PaWin_InitializeWaveFormatEx( &waveFormat, devices[i].channelCount, - sampleFormat, waveFormatTag, sampleRate ); + hostSampleFormat, waveFormatTag, sampleRate ); break; } @@ -2329,6 +2331,13 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, unsigned long outputDeviceCount = 0; /* contains all devices and channel counts as local host api ids, even when PaWinMmeUseMultipleDevices is not used */ char throttleProcessingThreadOnOverload = 1; + /* On Windows Vista and greater, MME will accept any format that can be represented + in WAVEFORMATEX and will internally convert if and when necessary. + On older Windows versions, the story is less clear, so we restrict ourselves to + Int16 for maximum compatibility. + */ + const PaSampleFormat kNativeFormats = PaMme_IsWindowsVistaOrGreater() ? + paUInt8 | paInt16 | paInt24 | paInt32 | paFloat32 : paInt16; if( inputParameters ) { @@ -2356,7 +2365,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, if( result != paNoError ) return result; hostInputSampleFormat = - PaUtil_SelectClosestAvailableFormat( paInt16 /* native formats */, inputSampleFormat ); + PaUtil_SelectClosestAvailableFormat( kNativeFormats, inputSampleFormat ); if( inputDeviceCount != 1 ){ /* always use direct speakers when using multi-device multichannel mode */ @@ -2406,7 +2415,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, if( result != paNoError ) return result; hostOutputSampleFormat = - PaUtil_SelectClosestAvailableFormat( paInt16 /* native formats */, outputSampleFormat ); + PaUtil_SelectClosestAvailableFormat( kNativeFormats, outputSampleFormat ); if( outputDeviceCount != 1 ){ /* always use direct speakers when using multi-device multichannel mode */ @@ -2549,7 +2558,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, { result = InitializeWaveHandles( winMmeHostApi, &stream->input, winMmeSpecificInputFlags, - stream->bufferProcessor.bytesPerHostInputSample, sampleRate, + hostInputSampleFormat, sampleRate, inputDevices, inputDeviceCount, inputChannelMask, 1 /* isInput */ ); if( result != paNoError ) goto error; } @@ -2558,7 +2567,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, { result = InitializeWaveHandles( winMmeHostApi, &stream->output, winMmeSpecificOutputFlags, - stream->bufferProcessor.bytesPerHostOutputSample, sampleRate, + hostOutputSampleFormat, sampleRate, outputDevices, outputDeviceCount, outputChannelMask, 0 /* isInput */ ); if( result != paNoError ) goto error; }