From: rossb Date: Fri, 20 Aug 2010 11:47:02 +0000 (+0000) Subject: Replaced broken PaStreamCallbackTimeInfo calculations with calculation based on CoreA... X-Git-Tag: pa_stable_v19_20110326_r1647~93 X-Git-Url: https://andrewgundersen.net/repos?a=commitdiff_plain;h=c0d24d39059bedfbba0a2cbfecb59eec225633b0;p=portaudio Replaced broken PaStreamCallbackTimeInfo calculations with calculation based on CoreAudio ioProc timestamps. Notes: 1. inputBufferAdcTime is not computed correctly for all cases (eg full duplex with/without ring buffer). 2. This patch needs to be reviewed for the case where device and AUHAL sample rates differ (ie SRC is used) and if input and output sample rates differ. See FIXMES --- diff --git a/src/hostapi/coreaudio/pa_mac_core.c b/src/hostapi/coreaudio/pa_mac_core.c index bd50973..c5ffb55 100644 --- a/src/hostapi/coreaudio/pa_mac_core.c +++ b/src/hostapi/coreaudio/pa_mac_core.c @@ -249,7 +249,6 @@ static PaError AbortStream( PaStream *stream ); static PaError IsStreamStopped( PaStream *s ); static PaError IsStreamActive( PaStream *stream ); static PaTime GetStreamTime( PaStream *stream ); -static void setStreamStartTime( PaStream *stream ); static OSStatus AudioIOProc( void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, @@ -798,6 +797,69 @@ static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, return paFormatIsSupported; } + +static void UpdateReciprocalOfActualOutputSampleRateFromDeviceProperty( PaMacCoreStream *stream ) +{ + /* FIXME: not sure if this should be the sample rate of the output device or the output unit */ + Float64 actualOutputSampleRate = stream->outDeviceSampleRate; + UInt32 propSize = sizeof(Float64); + OSStatus osErr = AudioDeviceGetProperty( stream->outputDevice, 0, /* isInput = */ FALSE, kAudioDevicePropertyActualSampleRate, &propSize, &actualOutputSampleRate); + if( osErr != noErr || actualOutputSampleRate < .01 ) // avoid divide by zero if there's an error + actualOutputSampleRate = stream->outDeviceSampleRate; + + stream->recipricalOfActualOutputSampleRate = 1. / actualOutputSampleRate; +} + +static OSStatus AudioDevicePropertyActualSampleRateListenerProc( AudioDeviceID inDevice, UInt32 inChannel, Boolean isInput, AudioDevicePropertyID inPropertyID, void *inClientData ) +{ + PaMacCoreStream *stream = (PaMacCoreStream*)inClientData; + + pthread_mutex_lock( &stream->timingInformationMutex ); + UpdateReciprocalOfActualOutputSampleRateFromDeviceProperty( stream ); + pthread_mutex_unlock( &stream->timingInformationMutex ); +} + +static void UpdateOutputLatencySamplesFromDeviceProperty( PaMacCoreStream *stream ) +{ + UInt32 deviceOutputLatencySamples = 0; + UInt32 propSize = sizeof(UInt32); + OSStatus osErr = AudioDeviceGetProperty( stream->outputDevice, 0, /* isInput= */ FALSE, kAudioDevicePropertyLatency, &propSize, &deviceOutputLatencySamples); + if( osErr != noErr ) + deviceOutputLatencySamples = 0; + + stream->deviceOutputLatencySamples = deviceOutputLatencySamples; +} + +static OSStatus AudioDevicePropertyOutputLatencySamplesListenerProc( AudioDeviceID inDevice, UInt32 inChannel, Boolean isInput, AudioDevicePropertyID inPropertyID, void *inClientData ) +{ + PaMacCoreStream *stream = (PaMacCoreStream*)inClientData; + + pthread_mutex_lock( &stream->timingInformationMutex ); + UpdateOutputLatencySamplesFromDeviceProperty( stream ); + pthread_mutex_unlock( &stream->timingInformationMutex ); +} + +static void UpdateInputLatencySamplesFromDeviceProperty( PaMacCoreStream *stream ) +{ + UInt32 deviceInputLatencySamples = 0; + UInt32 propSize = sizeof(UInt32); + OSStatus osErr = AudioDeviceGetProperty( stream->inputDevice, 0, /* isInput= */ TRUE, kAudioDevicePropertyLatency, &propSize, &deviceInputLatencySamples); + if( osErr != noErr ) + deviceInputLatencySamples = 0; + + stream->deviceInputLatencySamples = deviceInputLatencySamples; +} + +static OSStatus AudioDevicePropertyInputLatencySamplesListenerProc( AudioDeviceID inDevice, UInt32 inChannel, Boolean isInput, AudioDevicePropertyID inPropertyID, void *inClientData ) +{ + PaMacCoreStream *stream = (PaMacCoreStream*)inClientData; + + pthread_mutex_lock( &stream->timingInformationMutex ); + UpdateInputLatencySamplesFromDeviceProperty( stream ); + pthread_mutex_unlock( &stream->timingInformationMutex ); +} + + static PaError OpenAndSetupOneAudioUnit( const PaMacCoreStream *stream, const PaStreamParameters *inStreamParams, @@ -1356,6 +1418,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, stream->inputFramesPerBuffer = 0; stream->outputFramesPerBuffer = 0; stream->bufferProcessorIsInitialized = FALSE; + stream->timingInformationMutexIsInitialized = 0; /* assert( streamCallback ) ; */ /* only callback mode is implemented */ if( streamCallback ) @@ -1656,7 +1719,39 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, stream->userInChan = inputChannelCount; stream->userOutChan = outputChannelCount; - stream->isTimeSet = FALSE; + pthread_mutex_init( &stream->timingInformationMutex, NULL ); + stream->timingInformationMutexIsInitialized = 1; + + if( stream->outputUnit ) { + UpdateReciprocalOfActualOutputSampleRateFromDeviceProperty( stream ); + stream->recipricalOfActualOutputSampleRate_ioProcCopy = stream->recipricalOfActualOutputSampleRate; + + AudioDeviceAddPropertyListener( stream->outputDevice, 0, /* isInput = */ FALSE, kAudioDevicePropertyActualSampleRate, + AudioDevicePropertyActualSampleRateListenerProc, stream ); + + UpdateOutputLatencySamplesFromDeviceProperty( stream ); + stream->deviceOutputLatencySamples_ioProcCopy = stream->deviceOutputLatencySamples; + + AudioDeviceAddPropertyListener( stream->outputDevice, 0, /* isInput = */ FALSE, kAudioDevicePropertyLatency, + AudioDevicePropertyOutputLatencySamplesListenerProc, stream ); + + }else{ + stream->recipricalOfActualOutputSampleRate = 1.; + stream->recipricalOfActualOutputSampleRate_ioProcCopy = 0.; + stream->deviceOutputLatencySamples_ioProcCopy = 0; + } + + if( stream->inputUnit ) { + UpdateInputLatencySamplesFromDeviceProperty( stream ); + stream->deviceInputLatencySamples_ioProcCopy = stream->deviceInputLatencySamples; + + AudioDeviceAddPropertyListener( stream->inputDevice, 0, /* isInput = */ TRUE, kAudioDevicePropertyLatency, + AudioDevicePropertyInputLatencySamplesListenerProc, stream ); + }else{ + stream->deviceInputLatencySamples = 0; + stream->deviceInputLatencySamples_ioProcCopy = 0; + } + stream->state = STOPPED; stream->xrunFlags = 0; @@ -1669,56 +1764,12 @@ error: return result; } -PaTime GetStreamTime( PaStream *s ) -{ - /* FIXME: I am not at all sure this timing info stuff is right. - patest_sine_time reports negative latencies, which is wierd.*/ - PaMacCoreStream *stream = (PaMacCoreStream*)s; - AudioTimeStamp timeStamp; - - VVDBUG(("GetStreamTime()\n")); - - if ( !stream->isTimeSet ) - return (PaTime)0; - - if ( stream->outputDevice ) { - AudioDeviceGetCurrentTime( stream->outputDevice, &timeStamp); - return (PaTime)(timeStamp.mSampleTime - stream->startTime.mSampleTime)/stream->outDeviceSampleRate; - } else if ( stream->inputDevice ) { - AudioDeviceGetCurrentTime( stream->inputDevice, &timeStamp); - return (PaTime)(timeStamp.mSampleTime - stream->startTime.mSampleTime)/stream->inDeviceSampleRate; - } else { - return (PaTime)0; - } -} - -static void setStreamStartTime( PaStream *stream ) -{ - /* FIXME: I am not at all sure this timing info stuff is right. - patest_sine_time reports negative latencies, which is wierd.*/ - PaMacCoreStream *s = (PaMacCoreStream *) stream; - VVDBUG(("setStreamStartTime()\n")); - if( s->outputDevice ) - AudioDeviceGetCurrentTime( s->outputDevice, &s->startTime); - else if( s->inputDevice ) - AudioDeviceGetCurrentTime( s->inputDevice, &s->startTime); - else - bzero( &s->startTime, sizeof( s->startTime ) ); - - //FIXME: we need a memory barier here - - s->isTimeSet = TRUE; -} +#define HOST_TIME_TO_PA_TIME( x ) ( AudioConvertHostTimeToNanos( (x) ) * 1.0E-09) /* convert to nanoseconds and then to seconds */ -static PaTime TimeStampToSecs(PaMacCoreStream *stream, const AudioTimeStamp* timeStamp) +PaTime GetStreamTime( PaStream *s ) { - VVDBUG(("TimeStampToSecs()\n")); - //printf( "ATS: %lu, %g, %g\n", timeStamp->mFlags, timeStamp->mSampleTime, timeStamp->mRateScalar ); - if (timeStamp->mFlags & kAudioTimeStampSampleTimeValid) - return (timeStamp->mSampleTime / stream->sampleRate); - else - return 0; + return HOST_TIME_TO_PA_TIME( AudioGetCurrentHostTime() ); } #define RING_BUFFER_EMPTY (1000) @@ -1799,24 +1850,68 @@ static OSStatus AudioIOProc( void *inRefCon, } ----------------------------------------------------------------- */ - if( !stream->isTimeSet ) - setStreamStartTime( stream ); - - if( isRender ) { - AudioTimeStamp currentTime; - timeInfo.outputBufferDacTime = TimeStampToSecs(stream, inTimeStamp); - AudioDeviceGetCurrentTime(stream->outputDevice, ¤tTime); - timeInfo.currentTime = TimeStampToSecs(stream, ¤tTime); - } - if( isRender && stream->inputUnit == stream->outputUnit ) - timeInfo.inputBufferAdcTime = TimeStampToSecs(stream, inTimeStamp); - if( !isRender ) { - AudioTimeStamp currentTime; - timeInfo.inputBufferAdcTime = TimeStampToSecs(stream, inTimeStamp); - AudioDeviceGetCurrentTime(stream->inputDevice, ¤tTime); - timeInfo.currentTime = TimeStampToSecs(stream, ¤tTime); - } - + /* compute PaStreamCallbackTimeInfo */ + + if( pthread_mutex_trylock( &stream->timingInformationMutex ) == 0 ){ + /* snapshot the ioproc copy of timing information */ + stream->deviceOutputLatencySamples_ioProcCopy = stream->deviceOutputLatencySamples; + stream->recipricalOfActualOutputSampleRate_ioProcCopy = stream->recipricalOfActualOutputSampleRate; + stream->deviceInputLatencySamples_ioProcCopy = stream->deviceInputLatencySamples; + pthread_mutex_unlock( &stream->timingInformationMutex ); + } + + /* For timeInfo.currentTime we could calculate current time backwards from the HAL audio + output time to give a more accurate impression of the current timeslice but it doesn't + seem worth it at the moment since other PA host APIs don't do any better. + */ + timeInfo.currentTime = HOST_TIME_TO_PA_TIME( AudioGetCurrentHostTime() ); + + /* + For an input HAL AU, inTimeStamp is the time the samples are received from the hardware, + for an output HAL AU inTimeStamp is the time the samples are sent to the hardware. + PA expresses timestamps in terms of when the samples enter the ADC or leave the DAC + so we add or subtract kAudioDevicePropertyLatency below. + */ + + /* FIXME: not sure what to do below if the host timestamps aren't valid (kAudioTimeStampHostTimeValid isn't set) + Could ask on CA mailing list if it is possible for it not to be set. If so, could probably grab a now timestamp + at the top and compute from there (modulo scheduling jitter) or ask on mailing list for other options. */ + + if( isRender ) + { + if( stream->inputUnit ) /* full duplex */ + { + if( stream->inputUnit == stream->outputUnit ) /* full duplex AUHAL IOProc */ + { + /* FIXME: review. i'm not sure this computation of inputBufferAdcTime is correct for a full-duplex AUHAL */ + timeInfo.inputBufferAdcTime = HOST_TIME_TO_PA_TIME(inTimeStamp->mHostTime) + - stream->deviceInputLatencySamples_ioProcCopy * stream->recipricalOfActualOutputSampleRate_ioProcCopy; // FIXME should be using input sample rate here? + timeInfo.outputBufferDacTime = HOST_TIME_TO_PA_TIME(inTimeStamp->mHostTime) + + stream->deviceOutputLatencySamples_ioProcCopy * stream->recipricalOfActualOutputSampleRate_ioProcCopy; + } + else /* full duplex with ring-buffer from a separate input AUHAL ioproc */ + { + /* FIXME: review. this computation of inputBufferAdcTime is definitely wrong since it doesn't take the ring buffer latency into account */ + timeInfo.inputBufferAdcTime = HOST_TIME_TO_PA_TIME(inTimeStamp->mHostTime) + - stream->deviceInputLatencySamples_ioProcCopy * stream->recipricalOfActualOutputSampleRate_ioProcCopy; // FIXME should be using input sample rate here? + timeInfo.outputBufferDacTime = HOST_TIME_TO_PA_TIME(inTimeStamp->mHostTime) + + stream->deviceOutputLatencySamples_ioProcCopy * stream->recipricalOfActualOutputSampleRate_ioProcCopy; + } + } + else /* output only */ + { + timeInfo.inputBufferAdcTime = 0; + timeInfo.outputBufferDacTime = HOST_TIME_TO_PA_TIME(inTimeStamp->mHostTime) + + stream->deviceOutputLatencySamples_ioProcCopy * stream->recipricalOfActualOutputSampleRate_ioProcCopy; + } + } + else /* input only */ + { + timeInfo.inputBufferAdcTime = HOST_TIME_TO_PA_TIME(inTimeStamp->mHostTime) + - stream->deviceInputLatencySamples_ioProcCopy * stream->recipricalOfActualOutputSampleRate_ioProcCopy; // FIXME should be using input sample rate here? + timeInfo.outputBufferDacTime = 0; + } + //printf( "---%g, %g, %g\n", timeInfo.inputBufferAdcTime, timeInfo.currentTime, timeInfo.outputBufferDacTime ); if( isRender && stream->inputUnit == stream->outputUnit @@ -2128,7 +2223,6 @@ static OSStatus AudioIOProc( void *inRefCon, case paContinue: break; case paComplete: case paAbort: - stream->isTimeSet = FALSE; stream->state = CALLBACK_STOPPED ; if( stream->outputUnit ) AudioOutputUnitStop(stream->outputUnit); @@ -2157,6 +2251,19 @@ static PaError CloseStream( PaStream* s ) VDBUG( ( "Closing stream.\n" ) ); if( stream ) { + + if( stream->outputUnit ) { + AudioDeviceRemovePropertyListener( stream->outputDevice, 0, /* isInput = */ FALSE, kAudioDevicePropertyActualSampleRate, + AudioDevicePropertyActualSampleRateListenerProc ); + AudioDeviceRemovePropertyListener( stream->outputDevice, 0, /* isInput = */ FALSE, kAudioDevicePropertyLatency, + AudioDevicePropertyOutputLatencySamplesListenerProc ); + } + + if( stream->inputUnit ) { + AudioDeviceRemovePropertyListener( stream->inputDevice, 0, /* isInput = */ TRUE, kAudioDevicePropertyLatency, + AudioDevicePropertyInputLatencySamplesListenerProc ); + } + if( stream->outputUnit ) { int count = removeFromXRunListenerList( stream ); if( count == 0 ) @@ -2203,6 +2310,10 @@ static PaError CloseStream( PaStream* s ) return result; if( stream->bufferProcessorIsInitialized ) PaUtil_TerminateBufferProcessor( &stream->bufferProcessor ); + + if( stream->timingInformationMutexIsInitialized ) + pthread_mutex_destroy( &stream->timingInformationMutex ); + PaUtil_TerminateStreamRepresentation( &stream->streamRepresentation ); PaUtil_FreeMemory( stream ); } @@ -2232,10 +2343,7 @@ static PaError StartStream( PaStream *s ) if( stream->outputUnit && stream->outputUnit != stream->inputUnit ) { ERR_WRAP( AudioOutputUnitStart(stream->outputUnit) ); } - - //setStreamStartTime( stream ); - //stream->isTimeSet = TRUE; - + return paNoError; #undef ERR_WRAP } @@ -2266,7 +2374,6 @@ static PaError StopStream( PaStream *s ) waitUntilBlioWriteBufferIsFlushed( &stream->blio ); VDBUG( ( "Stopping stream.\n" ) ); - stream->isTimeSet = FALSE; stream->state = STOPPING; #define ERR_WRAP(mac_err) do { result = mac_err ; if ( result != noErr ) return ERR(result) ; } while(0) @@ -2314,10 +2421,6 @@ static PaError StopStream( PaStream *s ) if( paErr ) return paErr; -/* - //stream->isTimeSet = FALSE; -*/ - VDBUG( ( "Stream Stopped.\n" ) ); return paNoError; #undef ERR_WRAP diff --git a/src/hostapi/coreaudio/pa_mac_core_internal.h b/src/hostapi/coreaudio/pa_mac_core_internal.h index 1797cba..9277321 100644 --- a/src/hostapi/coreaudio/pa_mac_core_internal.h +++ b/src/hostapi/coreaudio/pa_mac_core_internal.h @@ -142,7 +142,6 @@ typedef struct PaMacCoreStream AudioTimeStamp startTime; /* FIXME: instead of volatile, these should be properly memory barriered */ volatile PaStreamCallbackFlags xrunFlags; - volatile bool isTimeSet; volatile enum { STOPPED = 0, /* playback is completely stopped, and the user has called StopStream(). */ @@ -159,6 +158,18 @@ typedef struct PaMacCoreStream //these may be different from the stream sample rate due to SR conversion: double outDeviceSampleRate; double inDeviceSampleRate; + + /* data updated by main thread and notifications, protected by timingInformationMutex */ + int timingInformationMutexIsInitialized; + pthread_mutex_t timingInformationMutex; + Float64 recipricalOfActualOutputSampleRate; + UInt32 deviceOutputLatencySamples; + UInt32 deviceInputLatencySamples; + + /* while the io proc is active, the following values are only accessed and manipulated by the ioproc */ + Float64 recipricalOfActualOutputSampleRate_ioProcCopy; + UInt32 deviceOutputLatencySamples_ioProcCopy; + UInt32 deviceInputLatencySamples_ioProcCopy; } PaMacCoreStream;