Skip to content

Commit

Permalink
mme: don't restrict the host buffer to 16-bit
Browse files Browse the repository at this point in the history
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
PortAudio#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 PortAudio#139
  • Loading branch information
dechamps committed Mar 22, 2023
1 parent 68e963a commit 8431d7d
Showing 1 changed file with 8 additions and 9 deletions.
17 changes: 8 additions & 9 deletions src/hostapi/wmme/pa_win_wmme.c
Original file line number Diff line number Diff line change
Expand Up @@ -1771,7 +1771,7 @@ static void InitializeSingleDirectionHandlesAndBuffers( PaWinMmeSingleDirectionH
static PaError InitializeWaveHandles( PaWinMmeHostApiRepresentation *winMmeHostApi,
PaWinMmeSingleDirectionHandlesAndBuffers *handlesAndBuffers,
unsigned long winMmeSpecificFlags,
unsigned long bytesPerHostSample,
PaSampleFormat sampleFormat,
double sampleRate, PaWinMmeDeviceAndChannelCount *devices,
unsigned int deviceCount, PaWinWaveFormatChannelMask channelMask, int isInput );
static PaError TerminateWaveHandles( PaWinMmeSingleDirectionHandlesAndBuffers *handlesAndBuffers, int isInput, int currentlyProcessingAnError );
Expand All @@ -1796,14 +1796,13 @@ static void InitializeSingleDirectionHandlesAndBuffers( PaWinMmeSingleDirectionH
static PaError InitializeWaveHandles( PaWinMmeHostApiRepresentation *winMmeHostApi,
PaWinMmeSingleDirectionHandlesAndBuffers *handlesAndBuffers,
unsigned long winMmeSpecificFlags,
unsigned long bytesPerHostSample,
PaSampleFormat sampleFormat,
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()
Expand Down Expand Up @@ -1832,8 +1831,6 @@ 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 );

for( i = 0; i < (signed int)deviceCount; ++i )
Expand Down Expand Up @@ -2329,6 +2326,8 @@ 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;

/* These are all the formats that can be represented in WAVEFORMATEX */
const PaSampleFormat kNativeFormats = paUInt8 | paInt16 | paInt24 | paInt32 | paFloat32;

if( inputParameters )
{
Expand Down Expand Up @@ -2356,7 +2355,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 */
Expand Down Expand Up @@ -2406,7 +2405,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 */
Expand Down Expand Up @@ -2549,7 +2548,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;
}
Expand All @@ -2558,7 +2557,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;
}
Expand Down

0 comments on commit 8431d7d

Please sign in to comment.