From: rossb Date: Thu, 24 Nov 2011 18:11:33 +0000 (+0000) Subject: Implemented host-API specific mechanism for setting DirectSound buffer sizes in frame... X-Git-Tag: pa_stable_v19_20140130_r1919~97 X-Git-Url: https://andrewgundersen.net/repos?a=commitdiff_plain;h=e996fc8ff6bc78415920ece963805590da19829a;p=portaudio Implemented host-API specific mechanism for setting DirectSound buffer sizes in frames. resolves #129 --- diff --git a/include/pa_win_ds.h b/include/pa_win_ds.h index 2410658..aff8c33 100644 --- a/include/pa_win_ds.h +++ b/include/pa_win_ds.h @@ -61,24 +61,21 @@ typedef struct PaWinDirectSoundStreamInfo{ PaHostApiTypeId hostApiType; /**< paDirectSound */ unsigned long version; /**< 2 */ - unsigned long flags; - - /* low-level latency setting support - Control the size of host buffers in order to set latency. They will - be used instead of the generic parameters to Pa_OpenStream() if - flags contains the paWinDirectSoundUseLowLevelLatencyParameters - flag. - - If PaWinDirectSoundStreamInfo structures with paWinDirectSoundUseLowLevelLatencyParameters - are supplied for both input and output in a full duplex stream, then the - input and output framesPerBuffer must be the same, or the larger of the - two must be a multiple of the smaller, otherwise a - paIncompatibleHostApiSpecificStreamInfo error will be returned from - Pa_OpenStream(). + unsigned long flags; /**< enable other features of this struct */ + + /** + low-level latency setting support + Sets the size of the DirectSound host buffer. + When flags contains the paWinDirectSoundUseLowLevelLatencyParameters + this size will be used instead of interpreting the generic latency + parameters to Pa_OpenStream(). If the flag is not set this value is ignored. + + If the stream is a full duplex stream the implementation requires that + the values of framesPerBuffer for input and output match (if both are specified). */ - unsigned long framesPerBuffer; /* NOT IMPLEMENTED see http://www.portaudio.com/trac/ticket/129 */ + unsigned long framesPerBuffer; - /* + /** support for WAVEFORMATEXTENSIBLE channel masks. If flags contains paWinDirectSoundUseChannelMask this allows you to specify which speakers to address in a multichannel stream. Constants for channelMask diff --git a/src/hostapi/dsound/pa_win_ds.c b/src/hostapi/dsound/pa_win_ds.c index 77408c8..ef68c01 100644 --- a/src/hostapi/dsound/pa_win_ds.c +++ b/src/hostapi/dsound/pa_win_ds.c @@ -152,6 +152,13 @@ PA_THREAD_FUNC ProcessingThreadProc( void *pArg ); #define PA_DS_WIN_WDM_DEFAULT_LATENCY_ (.120) +/* we allow the polling period to range between 1 and 100ms. + prior to August 2011 we limited the minimum polling period to 10ms. +*/ +#define PA_DS_MINIMUM_POLLING_PERIOD_SECONDS (0.001) /* 1ms */ +#define PA_DS_MAXIMUM_POLLING_PERIOD_SECONDS (0.100) /* 100ms */ +#define PA_DS_POLLING_JITTER_SECONDS (0.001) /* 1ms */ + #define SECONDS_PER_MSEC (0.001) #define MSECS_PER_SECOND (1000) @@ -1336,6 +1343,13 @@ static PaError ValidateWinDirectSoundSpecificStreamInfo( { return paIncompatibleHostApiSpecificStreamInfo; } + + if( streamInfo->flags & paWinDirectSoundUseLowLevelLatencyParameters ) + { + if( streamInfo->framesPerBuffer <= 0 ) + return paIncompatibleHostApiSpecificStreamInfo; + + } } return paNoError; @@ -1541,7 +1555,13 @@ static HRESULT InitFullDuplexInputOutputBuffers( PaWinDsStream *stream, #endif /* PAWIN_USE_DIRECTSOUNDFULLDUPLEXCREATE */ -static HRESULT InitInputBuffer( PaWinDsStream *stream, PaWinDsDeviceInfo *device, PaSampleFormat sampleFormat, unsigned long nFrameRate, WORD nChannels, int bytesPerBuffer, PaWinWaveFormatChannelMask channelMask ) +static HRESULT InitInputBuffer( PaWinDsStream *stream, + PaWinDsDeviceInfo *device, + PaSampleFormat sampleFormat, + unsigned long nFrameRate, + WORD nChannels, + int bytesPerBuffer, + PaWinWaveFormatChannelMask channelMask ) { DSCBUFFERDESC captureDesc; PaWinWaveFormat waveFormat; @@ -1582,7 +1602,10 @@ static HRESULT InitInputBuffer( PaWinDsStream *stream, PaWinDsDeviceInfo *device } -static HRESULT InitOutputBuffer( PaWinDsStream *stream, PaWinDsDeviceInfo *device, PaSampleFormat sampleFormat, unsigned long nFrameRate, WORD nChannels, int bytesPerBuffer, PaWinWaveFormatChannelMask channelMask ) +static HRESULT InitOutputBuffer( PaWinDsStream *stream, PaWinDsDeviceInfo *device, + PaSampleFormat sampleFormat, unsigned long nFrameRate, + WORD nChannels, int bytesPerBuffer, + PaWinWaveFormatChannelMask channelMask ) { HRESULT result; HWND hWnd; @@ -1680,12 +1703,9 @@ static void CalculateBufferSettings( unsigned long *hostBufferSizeFrames, unsigned long suggestedOutputLatencyFrames, double sampleRate, unsigned long userFramesPerBuffer ) { - /* we allow the polling period to range between 1 and 100ms. - prior to August 2011 we limited the minimum polling period to 10ms. - */ - unsigned long minimumPollingPeriodFrames = sampleRate / 1000; /* 1ms */ - unsigned long maximumPollingPeriodFrames = sampleRate / 10; /* 100ms */ - unsigned long pollingJitterFrames = sampleRate / 1000; /* 1ms */ + unsigned long minimumPollingPeriodFrames = sampleRate * PA_DS_MINIMUM_POLLING_PERIOD_SECONDS; + unsigned long maximumPollingPeriodFrames = sampleRate * PA_DS_MAXIMUM_POLLING_PERIOD_SECONDS; + unsigned long pollingJitterFrames = sampleRate * PA_DS_POLLING_JITTER_SECONDS; if( userFramesPerBuffer == paFramesPerBufferUnspecified ) { @@ -1747,6 +1767,23 @@ static void CalculateBufferSettings( unsigned long *hostBufferSizeFrames, } +static void CalculatePollingPeriodFrames( unsigned long hostBufferSizeFrames, + unsigned long *pollingPeriodFrames, + double sampleRate, unsigned long userFramesPerBuffer ) +{ + unsigned long minimumPollingPeriodFrames = sampleRate * PA_DS_MINIMUM_POLLING_PERIOD_SECONDS; + unsigned long maximumPollingPeriodFrames = sampleRate * PA_DS_MAXIMUM_POLLING_PERIOD_SECONDS; + unsigned long pollingJitterFrames = sampleRate * PA_DS_POLLING_JITTER_SECONDS; + + *pollingPeriodFrames = max( max(1, userFramesPerBuffer / 4), hostBufferSizeFrames / 16 ); + + if( *pollingPeriodFrames > maximumPollingPeriodFrames ) + { + *pollingPeriodFrames = maximumPollingPeriodFrames; + } +} + + static void SetStreamInfoLatencies( PaWinDsStream *stream, unsigned long userFramesPerBuffer, unsigned long pollingPeriodFrames, @@ -1808,6 +1845,8 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, int inputChannelCount, outputChannelCount; PaSampleFormat inputSampleFormat, outputSampleFormat; PaSampleFormat hostInputSampleFormat, hostOutputSampleFormat; + int userRequestedHostInputBufferSizeFrames = 0; + int userRequestedHostOutputBufferSizeFrames = 0; unsigned long suggestedInputLatencyFrames, suggestedOutputLatencyFrames; PaWinDirectSoundStreamInfo *inputStreamInfo, *outputStreamInfo; PaWinWaveFormatChannelMask inputChannelMask, outputChannelMask; @@ -1840,6 +1879,9 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, result = ValidateWinDirectSoundSpecificStreamInfo( inputParameters, inputStreamInfo ); if( result != paNoError ) return result; + if( inputStreamInfo && inputStreamInfo->flags & paWinDirectSoundUseLowLevelLatencyParameters ) + userRequestedHostInputBufferSizeFrames = inputStreamInfo->framesPerBuffer; + if( inputStreamInfo && inputStreamInfo->flags & paWinDirectSoundUseChannelMask ) inputChannelMask = inputStreamInfo->channelMask; else @@ -1877,6 +1919,9 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, result = ValidateWinDirectSoundSpecificStreamInfo( outputParameters, outputStreamInfo ); if( result != paNoError ) return result; + if( outputStreamInfo && outputStreamInfo->flags & paWinDirectSoundUseLowLevelLatencyParameters ) + userRequestedHostOutputBufferSizeFrames = outputStreamInfo->framesPerBuffer; + if( outputStreamInfo && outputStreamInfo->flags & paWinDirectSoundUseChannelMask ) outputChannelMask = outputStreamInfo->channelMask; else @@ -1889,6 +1934,16 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, suggestedOutputLatencyFrames = 0; } + /* + If low level host buffer size is specified for both input and output + the current code requires the sizes to match. + */ + + if( (userRequestedHostInputBufferSizeFrames > 0 && userRequestedHostOutputBufferSizeFrames > 0) + && userRequestedHostInputBufferSizeFrames != userRequestedHostOutputBufferSizeFrames ) + return paIncompatibleHostApiSpecificStreamInfo; + + /* IMPLEMENT ME: @@ -2027,14 +2082,34 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, /* set up i/o parameters */ - CalculateBufferSettings( &stream->hostBufferSizeFrames, &pollingPeriodFrames, - /* isFullDuplex = */ (inputParameters && outputParameters), - suggestedInputLatencyFrames, - suggestedOutputLatencyFrames, - sampleRate, framesPerBuffer ); + if( userRequestedHostInputBufferSizeFrames > 0 || userRequestedHostOutputBufferSizeFrames > 0 ) + { + /* use low level parameters */ + + /* since we use the same host buffer size for input and output + we choose the highest user specified value. + */ + stream->hostBufferSizeFrames = max( userRequestedHostInputBufferSizeFrames, userRequestedHostOutputBufferSizeFrames ); + + CalculatePollingPeriodFrames( + stream->hostBufferSizeFrames, &pollingPeriodFrames, + sampleRate, framesPerBuffer ); + } + else + { + CalculateBufferSettings( &stream->hostBufferSizeFrames, &pollingPeriodFrames, + /* isFullDuplex = */ (inputParameters && outputParameters), + suggestedInputLatencyFrames, + suggestedOutputLatencyFrames, + sampleRate, framesPerBuffer ); + } stream->pollingPeriodSeconds = pollingPeriodFrames / sampleRate; + DBUG(("DirectSound host buffer size frames: %d, polling period seconds: %f, @ sr: %f\n", + stream->hostBufferSizeFrames, stream->pollingPeriodSeconds, sampleRate )); + + /* ------------------ OUTPUT */ if( outputParameters ) { diff --git a/test/patest_dsound_low_level_latency_params.c b/test/patest_dsound_low_level_latency_params.c new file mode 100644 index 0000000..24e2066 --- /dev/null +++ b/test/patest_dsound_low_level_latency_params.c @@ -0,0 +1,187 @@ +/* + * $Id: $ + * Portable Audio I/O Library + * Windows DirectSound low level buffer parameters test + * + * Copyright (c) 2011 Ross Bencina + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * The text above constitutes the entire PortAudio license; however, + * the PortAudio community also makes the following non-binding requests: + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. It is also + * requested that these non-binding requests be included along with the + * license above. + */ + +#include +#include + +#include "portaudio.h" +#include "pa_win_ds.h" + +#define NUM_SECONDS (6) +#define SAMPLE_RATE (44100) + +#define DSOUND_FRAMES_PER_HOST_BUFFER (256*2) //(440*10) + +#define FRAMES_PER_BUFFER 256 + +#ifndef M_PI +#define M_PI (3.14159265) +#endif + +#define TABLE_SIZE (2048) + +#define CHANNEL_COUNT (2) + + +typedef struct +{ + float sine[TABLE_SIZE]; + double phase; +} +paTestData; + +/* This routine will be called by the PortAudio engine when audio is needed. +** It may called at interrupt level on some machines so don't do anything +** that could mess up the system like calling malloc() or free(). +*/ +static int patestCallback( const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + unsigned long i,j; + + (void) timeInfo; /* Prevent unused variable warnings. */ + (void) statusFlags; + (void) inputBuffer; + + for( i=0; isine[(int)data->phase]; + data->phase += 20; + if( data->phase >= TABLE_SIZE ){ + data->phase -= TABLE_SIZE; + } + + for( j = 0; j < CHANNEL_COUNT; ++j ){ + *out++ = x; + } + } + + return paContinue; +} + +/*******************************************************************/ +int main(int argc, char* argv[]) +{ + PaStreamParameters outputParameters; + PaWinDirectSoundStreamInfo dsoundStreamInfo; + PaStream *stream; + PaError err; + paTestData data; + int i; + int deviceIndex; + + printf("PortAudio Test: output a sine blip on each channel. SR = %d, BufSize = %d, Chans = %d\n", SAMPLE_RATE, FRAMES_PER_BUFFER, CHANNEL_COUNT); + + err = Pa_Initialize(); + if( err != paNoError ) goto error; + + deviceIndex = Pa_GetHostApiInfo( Pa_HostApiTypeIdToHostApiIndex( paDirectSound ) )->defaultOutputDevice; + if( argc == 2 ){ + sscanf( argv[1], "%d", &deviceIndex ); + } + + printf( "using device id %d (%s)\n", deviceIndex, Pa_GetDeviceInfo(deviceIndex)->name ); + + /* initialise sinusoidal wavetable */ + for( i=0; idefaultLowOutputLatency;*/ + outputParameters.hostApiSpecificStreamInfo = NULL; + + dsoundStreamInfo.size = sizeof(PaWinDirectSoundStreamInfo); + dsoundStreamInfo.hostApiType = paDirectSound; + dsoundStreamInfo.version = 2; + dsoundStreamInfo.flags = paWinDirectSoundUseLowLevelLatencyParameters; + dsoundStreamInfo.framesPerBuffer = DSOUND_FRAMES_PER_HOST_BUFFER; + outputParameters.hostApiSpecificStreamInfo = &dsoundStreamInfo; + + + if( Pa_IsFormatSupported( 0, &outputParameters, SAMPLE_RATE ) == paFormatIsSupported ){ + printf( "Pa_IsFormatSupported reports device will support %d channels.\n", CHANNEL_COUNT ); + }else{ + printf( "Pa_IsFormatSupported reports device will not support %d channels.\n", CHANNEL_COUNT ); + } + + err = Pa_OpenStream( + &stream, + NULL, /* no input */ + &outputParameters, + SAMPLE_RATE, + FRAMES_PER_BUFFER, + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + patestCallback, + &data ); + if( err != paNoError ) goto error; + + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + + printf("Play for %d seconds.\n", NUM_SECONDS ); + Pa_Sleep( NUM_SECONDS * 1000 ); + + err = Pa_StopStream( stream ); + if( err != paNoError ) goto error; + + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + + Pa_Terminate(); + printf("Test finished.\n"); + + return err; +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return err; +} +