Skip to content

Commit 7e68ca3

Browse files
dechampsRossBencina
authored andcommitted
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]. Nevertheless, it's difficult to be sure, so this code checks the Windows version it's running on and preserves the old behavior (i.e. always use Int16) on pre-Vista Windows. [1]: https://learn.microsoft.com/en-us/windows-hardware/drivers/audio/background-of-non-pcm-support#waveout-api Fixes #139
1 parent 929e2e8 commit 7e68ca3

File tree

1 file changed

+21
-12
lines changed

1 file changed

+21
-12
lines changed

src/hostapi/wmme/pa_win_wmme.c

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,11 @@ static void PaMme_SetLastSystemError( DWORD errorCode )
321321
PaMme_SetLastSystemError( errorCode )
322322

323323

324+
static int PaMme_IsWindowsVistaOrGreater() {
325+
return LOBYTE(LOWORD(GetVersion())) >= 6;
326+
}
327+
328+
324329
/* PaError returning wrappers for some commonly used win32 functions
325330
note that we allow passing a null ptr to have no effect.
326331
*/
@@ -1771,7 +1776,7 @@ static void InitializeSingleDirectionHandlesAndBuffers( PaWinMmeSingleDirectionH
17711776
static PaError InitializeWaveHandles( PaWinMmeHostApiRepresentation *winMmeHostApi,
17721777
PaWinMmeSingleDirectionHandlesAndBuffers *handlesAndBuffers,
17731778
unsigned long winMmeSpecificFlags,
1774-
unsigned long bytesPerHostSample,
1779+
PaSampleFormat hostSampleFormat,
17751780
double sampleRate, PaWinMmeDeviceAndChannelCount *devices,
17761781
unsigned int deviceCount, PaWinWaveFormatChannelMask channelMask, int isInput );
17771782
static PaError TerminateWaveHandles( PaWinMmeSingleDirectionHandlesAndBuffers *handlesAndBuffers, int isInput, int currentlyProcessingAnError );
@@ -1796,14 +1801,13 @@ static void InitializeSingleDirectionHandlesAndBuffers( PaWinMmeSingleDirectionH
17961801
static PaError InitializeWaveHandles( PaWinMmeHostApiRepresentation *winMmeHostApi,
17971802
PaWinMmeSingleDirectionHandlesAndBuffers *handlesAndBuffers,
17981803
unsigned long winMmeSpecificFlags,
1799-
unsigned long bytesPerHostSample,
1804+
PaSampleFormat hostSampleFormat,
18001805
double sampleRate, PaWinMmeDeviceAndChannelCount *devices,
18011806
unsigned int deviceCount, PaWinWaveFormatChannelMask channelMask, int isInput )
18021807
{
18031808
PaError result;
18041809
MMRESULT mmresult;
18051810
signed int i, j;
1806-
PaSampleFormat sampleFormat;
18071811
int waveFormatTag;
18081812

18091813
/* for error cleanup we expect that InitializeSingleDirectionHandlesAndBuffers()
@@ -1832,9 +1836,7 @@ static PaError InitializeWaveHandles( PaWinMmeHostApiRepresentation *winMmeHostA
18321836
((HWAVEOUT*)handlesAndBuffers->waveHandles)[i] = 0;
18331837
}
18341838

1835-
/* @todo at the moment we only use 16 bit sample format */
1836-
sampleFormat = paInt16;
1837-
waveFormatTag = SampleFormatAndWinWmmeSpecificFlagsToLinearWaveFormatTag( sampleFormat, winMmeSpecificFlags );
1839+
waveFormatTag = SampleFormatAndWinWmmeSpecificFlagsToLinearWaveFormatTag( hostSampleFormat, winMmeSpecificFlags );
18381840

18391841
for( i = 0; i < (signed int)deviceCount; ++i )
18401842
{
@@ -1852,14 +1854,14 @@ static PaError InitializeWaveHandles( PaWinMmeHostApiRepresentation *winMmeHostA
18521854
if this fails we fall back to WAVEFORMATEX */
18531855

18541856
PaWin_InitializeWaveFormatExtensible( &waveFormat, devices[i].channelCount,
1855-
sampleFormat, waveFormatTag, sampleRate, channelMask );
1857+
hostSampleFormat, waveFormatTag, sampleRate, channelMask );
18561858
break;
18571859

18581860
case 1:
18591861
/* retry with WAVEFORMATEX */
18601862

18611863
PaWin_InitializeWaveFormatEx( &waveFormat, devices[i].channelCount,
1862-
sampleFormat, waveFormatTag, sampleRate );
1864+
hostSampleFormat, waveFormatTag, sampleRate );
18631865
break;
18641866
}
18651867

@@ -2329,6 +2331,13 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
23292331
unsigned long outputDeviceCount = 0; /* contains all devices and channel counts as local host api ids, even when PaWinMmeUseMultipleDevices is not used */
23302332
char throttleProcessingThreadOnOverload = 1;
23312333

2334+
/* On Windows Vista and greater, MME will accept any format that can be represented
2335+
in WAVEFORMATEX and will internally convert if and when necessary.
2336+
On older Windows versions, the story is less clear, so we restrict ourselves to
2337+
Int16 for maximum compatibility.
2338+
*/
2339+
const PaSampleFormat kNativeFormats = PaMme_IsWindowsVistaOrGreater() ?
2340+
paUInt8 | paInt16 | paInt24 | paInt32 | paFloat32 : paInt16;
23322341

23332342
if( inputParameters )
23342343
{
@@ -2356,7 +2365,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
23562365
if( result != paNoError ) return result;
23572366

23582367
hostInputSampleFormat =
2359-
PaUtil_SelectClosestAvailableFormat( paInt16 /* native formats */, inputSampleFormat );
2368+
PaUtil_SelectClosestAvailableFormat( kNativeFormats, inputSampleFormat );
23602369

23612370
if( inputDeviceCount != 1 ){
23622371
/* always use direct speakers when using multi-device multichannel mode */
@@ -2406,7 +2415,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
24062415
if( result != paNoError ) return result;
24072416

24082417
hostOutputSampleFormat =
2409-
PaUtil_SelectClosestAvailableFormat( paInt16 /* native formats */, outputSampleFormat );
2418+
PaUtil_SelectClosestAvailableFormat( kNativeFormats, outputSampleFormat );
24102419

24112420
if( outputDeviceCount != 1 ){
24122421
/* always use direct speakers when using multi-device multichannel mode */
@@ -2549,7 +2558,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
25492558
{
25502559
result = InitializeWaveHandles( winMmeHostApi, &stream->input,
25512560
winMmeSpecificInputFlags,
2552-
stream->bufferProcessor.bytesPerHostInputSample, sampleRate,
2561+
hostInputSampleFormat, sampleRate,
25532562
inputDevices, inputDeviceCount, inputChannelMask, 1 /* isInput */ );
25542563
if( result != paNoError ) goto error;
25552564
}
@@ -2558,7 +2567,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
25582567
{
25592568
result = InitializeWaveHandles( winMmeHostApi, &stream->output,
25602569
winMmeSpecificOutputFlags,
2561-
stream->bufferProcessor.bytesPerHostOutputSample, sampleRate,
2570+
hostOutputSampleFormat, sampleRate,
25622571
outputDevices, outputDeviceCount, outputChannelMask, 0 /* isInput */ );
25632572
if( result != paNoError ) goto error;
25642573
}

0 commit comments

Comments
 (0)