diff --git a/RtAudio.cpp b/RtAudio.cpp index b7459be0..b4cf6daa 100644 --- a/RtAudio.cpp +++ b/RtAudio.cpp @@ -128,6 +128,8 @@ class RtApiCore: public RtApi RtAudioErrorType stopStream( void ) override; RtAudioErrorType abortStream( void ) override; + void streamSampleRateUpdated( int sampleRate ); + // This function is intended for internal use only. It must be // public because it is called by an internal callback handler, // which is not a member of RtAudio. External use of this function @@ -575,7 +577,7 @@ void RtAudio :: openRtApi( RtAudio::Api api ) #endif } -RtAudio :: RtAudio( RtAudio::Api api, RtAudioErrorCallback&& errorCallback ) +RtAudio :: RtAudio( RtAudio::Api api, RtAudioErrorCallback&& errorCallback, RtAudioStreamUpdateCallback&& updateCallback ) { rtapi_ = 0; @@ -586,6 +588,7 @@ RtAudio :: RtAudio( RtAudio::Api api, RtAudioErrorCallback&& errorCallback ) if ( rtapi_ ) { if ( errorCallback ) rtapi_->setErrorCallback( errorCallback ); + if ( updateCallback ) rtapi_->setStreamUpdateCallback (updateCallback ); return; } @@ -610,6 +613,7 @@ RtAudio :: RtAudio( RtAudio::Api api, RtAudioErrorCallback&& errorCallback ) if ( rtapi_ ) { if ( errorCallback ) rtapi_->setErrorCallback( errorCallback ); + if ( updateCallback ) rtapi_->setStreamUpdateCallback (updateCallback ); return; } @@ -1092,19 +1096,36 @@ unsigned int RtApiCore :: getDefaultInputDevice( void ) } // If a device used in an open stream is disconnected, close the stream. -static OSStatus streamDisconnectListener( AudioObjectID /*id*/, - UInt32 nAddresses, - const AudioObjectPropertyAddress properties[], - void* infoPointer ) +static OSStatus streamPropertyChangeListener( AudioObjectID id, + UInt32 nAddresses, + const AudioObjectPropertyAddress properties[], + void* infoPointer ) { + CallbackInfo *info = (CallbackInfo *) infoPointer; + RtApiCore *object = (RtApiCore *) info->object; + for ( UInt32 i=0; iobject; info->deviceDisconnected = true; object->closeStream(); return kAudioHardwareUnspecifiedError; } + if ( properties[i].mSelector == kAudioDevicePropertyNominalSampleRate ) + { + AudioObjectPropertyAddress property; + + property.mSelector = kAudioDevicePropertyNominalSampleRate; + property.mScope = kAudioObjectPropertyScopeGlobal; + property.mElement = kAudioObjectPropertyElementMaster; + + Float64 sampleRate; + UInt32 dataSize = sizeof( Float64 ); + + auto result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &sampleRate ); + + if ( result == noErr) + object->streamSampleRateUpdated( static_cast (sampleRate) ); + } } return kAudioHardwareNoError; @@ -1921,8 +1942,8 @@ bool RtApiCore :: probeDeviceOpen( unsigned int deviceId, StreamMode mode, unsig handle->xrunListenerAdded[mode] = true; // Setup a listener to detect a possible device disconnect. - property.mSelector = kAudioDevicePropertyDeviceIsAlive; - result = AudioObjectAddPropertyListener( id , &property, streamDisconnectListener, (void *) &stream_.callbackInfo ); + property.mSelector = kAudioObjectPropertySelectorWildcard; + result = AudioObjectAddPropertyListener( id , &property, streamPropertyChangeListener, (void *) &stream_.callbackInfo ); if ( result != noErr ) { AudioObjectRemovePropertyListener( id, &property, xrunListener, (void *) handle ); errorStream_ << "RtApiCore::probeDeviceOpen: system error setting disconnect listener for device (" << deviceId << ")."; @@ -1961,8 +1982,8 @@ void RtApiCore :: closeStream( void ) } } if ( handle->disconnectListenerAdded[0] ) { - property.mSelector = kAudioDevicePropertyDeviceIsAlive; - if (AudioObjectRemovePropertyListener( handle->id[0], &property, streamDisconnectListener, (void *) &stream_.callbackInfo ) != noErr) { + property.mSelector = kAudioObjectPropertySelectorWildcard; + if (AudioObjectRemovePropertyListener( handle->id[0], &property, streamPropertyChangeListener, (void *) &stream_.callbackInfo ) != noErr) { errorText_ = "RtApiCore::closeStream(): error removing disconnect property listener!"; error( RTAUDIO_WARNING ); } @@ -1997,8 +2018,8 @@ void RtApiCore :: closeStream( void ) } if ( handle->disconnectListenerAdded[1] ) { - property.mSelector = kAudioDevicePropertyDeviceIsAlive; - if (AudioObjectRemovePropertyListener( handle->id[1], &property, streamDisconnectListener, (void *) &stream_.callbackInfo ) != noErr) { + property.mSelector = kAudioObjectPropertySelectorWildcard; + if (AudioObjectRemovePropertyListener( handle->id[1], &property, streamPropertyChangeListener, (void *) &stream_.callbackInfo ) != noErr) { errorText_ = "RtApiCore::closeStream(): error removing disconnect property listener!"; error( RTAUDIO_WARNING ); } @@ -2174,6 +2195,15 @@ RtAudioErrorType RtApiCore :: abortStream( void ) return stopStream(); } +void RtApiCore :: streamSampleRateUpdated( int sampleRate ) +{ + if ( sampleRate != stream_.sampleRate) { + stream_.sampleRate = sampleRate; + if ( streamUpdateCallback_ != nullptr ) + streamUpdateCallback_(); + } +} + // This function will be called by a spawned thread when the user // callback function signals that the stream should be stopped or // aborted. It is better to handle it this way because the diff --git a/RtAudio.h b/RtAudio.h index e767ddb2..fe5b8b47 100644 --- a/RtAudio.h +++ b/RtAudio.h @@ -249,6 +249,10 @@ typedef std::function RtAudioErrorCallback; +//! RtAudio streamUpdate callback function prototype. +typedef std::function + RtAudioStreamUpdateCallback; + // **************************************************************** // // // RtAudio class declaration. @@ -430,7 +434,9 @@ class RTAUDIO_DLL_PUBLIC RtAudio An optional errorCallback function can be specified to subsequently receive warning and error messages. */ - RtAudio( RtAudio::Api api=UNSPECIFIED, RtAudioErrorCallback&& errorCallback=0 ); + RtAudio( RtAudio::Api api=UNSPECIFIED, + RtAudioErrorCallback&& errorCallback=0, + RtAudioStreamUpdateCallback&& updateCallback=0 ); //! The destructor. /*! @@ -632,6 +638,9 @@ class RTAUDIO_DLL_PUBLIC RtAudio //! Set a client-defined function that will be invoked when an error or warning occurs. void setErrorCallback( RtAudioErrorCallback errorCallback ); + //! Set a client-defined function that will be invoked if the stream properties change + void setStreamUpdateCallback( RtAudioStreamUpdateCallback updateCallback ); + //! Specify whether warning messages should be output or not. /*! The default behaviour is for warning messages to be output, @@ -772,6 +781,7 @@ class RTAUDIO_DLL_PUBLIC RtApi bool isStreamOpen( void ) const { return stream_.state != STREAM_CLOSED; } bool isStreamRunning( void ) const { return stream_.state == STREAM_RUNNING; } void setErrorCallback( RtAudioErrorCallback errorCallback ) { errorCallback_ = errorCallback; } + void setStreamUpdateCallback( RtAudioStreamUpdateCallback updateCallback ) { streamUpdateCallback_ = updateCallback; } void showWarnings( bool value ) { showWarnings_ = value; } @@ -848,6 +858,7 @@ class RTAUDIO_DLL_PUBLIC RtApi std::ostringstream errorStream_; std::string errorText_; RtAudioErrorCallback errorCallback_; + RtAudioStreamUpdateCallback streamUpdateCallback_; bool showWarnings_; std::vector deviceList_; unsigned int currentDeviceId_; @@ -925,6 +936,7 @@ inline unsigned int RtAudio :: getStreamSampleRate( void ) { return rtapi_->getS inline double RtAudio :: getStreamTime( void ) { return rtapi_->getStreamTime(); } inline void RtAudio :: setStreamTime( double time ) { return rtapi_->setStreamTime( time ); } inline void RtAudio :: setErrorCallback( RtAudioErrorCallback errorCallback ) { rtapi_->setErrorCallback( errorCallback ); } +inline void RtAudio :: setStreamUpdateCallback( RtAudioStreamUpdateCallback updateCallback ) { rtapi_->setStreamUpdateCallback ( updateCallback ); } inline void RtAudio :: showWarnings( bool value ) { rtapi_->showWarnings( value ); } #endif