From 10977977730415518802eb37c570af4ac18138de Mon Sep 17 00:00:00 2001 From: Gary Scavone Date: Thu, 22 Aug 2019 18:24:11 -0400 Subject: [PATCH] Modified CoreAudio cleanup in case of error during initialization, fixed duplex streamtime bug for single device in CoreAudio, updated playsaw.cpp with interrupt handler instead of cin.get() function. --- RtAudio.cpp | 135 +++++++++++++++++++++++----------------------- tests/duplex.cpp | 31 +++++------ tests/playsaw.cpp | 26 ++++++--- 3 files changed, 100 insertions(+), 92 deletions(-) diff --git a/RtAudio.cpp b/RtAudio.cpp index d0827fd..d01c4e2 100644 --- a/RtAudio.cpp +++ b/RtAudio.cpp @@ -393,7 +393,6 @@ RtAudioErrorType RtApi :: openStream( RtAudio::StreamParameters *oParams, result = probeDeviceOpen( iParams->deviceId, INPUT, iChannels, iParams->firstChannel, sampleRate, format, bufferFrames, options ); if ( result == false ) { - if ( oChannels > 0 ) closeStream(); return error( RTAUDIO_SYSTEM_ERROR ); } } @@ -537,9 +536,11 @@ struct CoreHandle { pthread_cond_t condition; int drainCounter; // Tracks callback counts when draining bool internalDrain; // Indicates if stop is initiated from callback or not. + bool xrunListenerAdded[2]; + bool disconnectListenerAdded[2]; CoreHandle() - :deviceBuffer(0), drainCounter(0), internalDrain(false) { nStreams[0] = 1; nStreams[1] = 1; id[0] = 0; id[1] = 0; xrun[0] = false; xrun[1] = false; } + :deviceBuffer(0), drainCounter(0), internalDrain(false) { nStreams[0] = 1; nStreams[1] = 1; id[0] = 0; id[1] = 0; procId[0] = 0; procId[1] = 0; xrun[0] = false; xrun[1] = false; xrunListenerAdded[0] = false; xrunListenerAdded[1] = false; disconnectListenerAdded[0] = false; disconnectListenerAdded[1] = false; } }; RtApiCore:: RtApiCore() @@ -1406,7 +1407,7 @@ bool RtApiCore :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne } if ( mode == INPUT && stream_.mode == OUTPUT && stream_.device[0] == device ) - // Only one callback procedure per device. + // Only one callback procedure and property listener per device. stream_.mode = DUPLEX; else { #if defined( MAC_OS_X_VERSION_10_5 ) && ( MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 ) @@ -1424,51 +1425,34 @@ bool RtApiCore :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne stream_.mode = DUPLEX; else stream_.mode = mode; - } - // Setup the device property listener for over/underload. - property.mSelector = kAudioDeviceProcessorOverload; - property.mScope = kAudioObjectPropertyScopeGlobal; - result = AudioObjectAddPropertyListener( id, &property, xrunListener, (void *) handle ); + // Setup the device property listener for over/underload. + property.mSelector = kAudioDeviceProcessorOverload; + property.mScope = kAudioObjectPropertyScopeGlobal; + result = AudioObjectAddPropertyListener( id, &property, xrunListener, (void *) handle ); if ( result != noErr ) { - errorStream_ << "RtApiCore::probeDeviceOpen: system error setting xrun listener for device (" << device << ")."; - errorText_ = errorStream_.str(); - goto error; - } + errorStream_ << "RtApiCore::probeDeviceOpen: system error setting xrun listener for device (" << device << ")."; + errorText_ = errorStream_.str(); + goto error; + } + handle->xrunListenerAdded[mode] = true; - // Setup a listener to detect a possible device disconnect. - property.mSelector = kAudioDevicePropertyDeviceIsAlive; - result = AudioObjectAddPropertyListener( id , &property, disconnectListener, (void *) &stream_.callbackInfo ); - if ( result != noErr ) { - AudioObjectRemovePropertyListener( id, &property, xrunListener, (void *) handle ); - errorStream_ << "RtApiCore::probeDeviceOpen: system error setting disconnect listener for device (" << device << ")."; - errorText_ = errorStream_.str(); - goto error; + // Setup a listener to detect a possible device disconnect. + property.mSelector = kAudioDevicePropertyDeviceIsAlive; + result = AudioObjectAddPropertyListener( id , &property, disconnectListener, (void *) &stream_.callbackInfo ); + if ( result != noErr ) { + AudioObjectRemovePropertyListener( id, &property, xrunListener, (void *) handle ); + errorStream_ << "RtApiCore::probeDeviceOpen: system error setting disconnect listener for device (" << device << ")."; + errorText_ = errorStream_.str(); + goto error; + } + handle->disconnectListenerAdded[mode] = true; } return SUCCESS; error: - if ( handle ) { - pthread_cond_destroy( &handle->condition ); - delete handle; - stream_.apiHandle = 0; - } - - for ( int i=0; i<2; i++ ) { - if ( stream_.userBuffer[i] ) { - free( stream_.userBuffer[i] ); - stream_.userBuffer[i] = 0; - } - } - - if ( stream_.deviceBuffer ) { - free( stream_.deviceBuffer ); - stream_.deviceBuffer = 0; - } - - clearStreamInfo(); - //stream_.state = STREAM_CLOSED; + closeStream(); // this should safely clear out procedures, listeners and memory, even for duplex stream return FAILURE; } @@ -1482,53 +1466,67 @@ void RtApiCore :: closeStream( void ) CoreHandle *handle = (CoreHandle *) stream_.apiHandle; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { - if (handle) { + if ( handle ) { AudioObjectPropertyAddress property = { kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; - property.mSelector = kAudioDeviceProcessorOverload; - if (AudioObjectRemovePropertyListener( handle->id[0], &property, xrunListener, (void *) handle ) != noErr) { - errorText_ = "RtApiCore::closeStream(): error removing xrun property listener!"; - error( RTAUDIO_WARNING ); + if ( handle->xrunListenerAdded[0] ) { + property.mSelector = kAudioDeviceProcessorOverload; + if (AudioObjectRemovePropertyListener( handle->id[0], &property, xrunListener, (void *) handle ) != noErr) { + errorText_ = "RtApiCore::closeStream(): error removing xrun property listener!"; + error( RTAUDIO_WARNING ); + } } - property.mSelector = kAudioDevicePropertyDeviceIsAlive; - if (AudioObjectRemovePropertyListener( handle->id[0], &property, disconnectListener, (void *) &stream_.callbackInfo ) != noErr) { - errorText_ = "RtApiCore::closeStream(): error removing disconnect property listener!"; - error( RTAUDIO_WARNING ); + if ( handle->disconnectListenerAdded[0] ) { + property.mSelector = kAudioDevicePropertyDeviceIsAlive; + if (AudioObjectRemovePropertyListener( handle->id[0], &property, disconnectListener, (void *) &stream_.callbackInfo ) != noErr) { + errorText_ = "RtApiCore::closeStream(): error removing disconnect property listener!"; + error( RTAUDIO_WARNING ); + } } - } - if ( stream_.state == STREAM_RUNNING ) - AudioDeviceStop( handle->id[0], callbackHandler ); + + if ( stream_.state == STREAM_RUNNING ) + AudioDeviceStop( handle->id[0], callbackHandler ); + #if defined( MAC_OS_X_VERSION_10_5 ) && ( MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 ) - AudioDeviceDestroyIOProcID( handle->id[0], handle->procId[0] ); + if ( handle->procId[0] ) + AudioDeviceDestroyIOProcID( handle->id[0], handle->procId[0] ); #else - // deprecated in favor of AudioDeviceDestroyIOProcID() - AudioDeviceRemoveIOProc( handle->id[0], callbackHandler ); + // deprecated in favor of AudioDeviceDestroyIOProcID() + AudioDeviceRemoveIOProc( handle->id[0], callbackHandler ); #endif + } } if ( stream_.mode == INPUT || ( stream_.mode == DUPLEX && stream_.device[0] != stream_.device[1] ) ) { - if (handle) { + if ( handle ) { AudioObjectPropertyAddress property = { kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; - property.mSelector = kAudioDeviceProcessorOverload; - if (AudioObjectRemovePropertyListener( handle->id[1], &property, xrunListener, (void *) handle ) != noErr) { - errorText_ = "RtApiCore::closeStream(): error removing xrun property listener!"; - error( RTAUDIO_WARNING ); + if ( handle->xrunListenerAdded[1] ) { + property.mSelector = kAudioDeviceProcessorOverload; + if (AudioObjectRemovePropertyListener( handle->id[1], &property, xrunListener, (void *) handle ) != noErr) { + errorText_ = "RtApiCore::closeStream(): error removing xrun property listener!"; + error( RTAUDIO_WARNING ); + } } - property.mSelector = kAudioDevicePropertyDeviceIsAlive; - if (AudioObjectRemovePropertyListener( handle->id[1], &property, disconnectListener, (void *) &stream_.callbackInfo ) != noErr) { - errorText_ = "RtApiCore::closeStream(): error removing disconnect property listener!"; - error( RTAUDIO_WARNING ); + + if ( handle->disconnectListenerAdded[0] ) { + property.mSelector = kAudioDevicePropertyDeviceIsAlive; + if (AudioObjectRemovePropertyListener( handle->id[1], &property, disconnectListener, (void *) &stream_.callbackInfo ) != noErr) { + errorText_ = "RtApiCore::closeStream(): error removing disconnect property listener!"; + error( RTAUDIO_WARNING ); + } } } + if ( stream_.state == STREAM_RUNNING ) AudioDeviceStop( handle->id[1], callbackHandler ); #if defined( MAC_OS_X_VERSION_10_5 ) && ( MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 ) - AudioDeviceDestroyIOProcID( handle->id[1], handle->procId[1] ); + if ( handle->procId[1] ) + AudioDeviceDestroyIOProcID( handle->id[1], handle->procId[1] ); #else // deprecated in favor of AudioDeviceDestroyIOProcID() AudioDeviceRemoveIOProc( handle->id[1], callbackHandler ); @@ -1947,7 +1945,12 @@ bool RtApiCore :: callbackEvent( AudioDeviceID deviceId, unlock: // Make sure to only tick duplex stream time once if using two devices - if ( stream_.mode != DUPLEX || (stream_.mode == DUPLEX && handle->id[0] != handle->id[1] && deviceId == handle->id[0] ) ) + if ( stream_.mode == DUPLEX ) { + if ( handle->id[0] == handle->id[1] ) // same device, only one callback + RtApi::tickStreamTime(); + else if ( deviceId == handle->id[0] ) + RtApi::tickStreamTime(); // only tick on the output callback + } else RtApi::tickStreamTime(); return SUCCESS; diff --git a/tests/duplex.cpp b/tests/duplex.cpp index c5abc3b..33ab282 100644 --- a/tests/duplex.cpp +++ b/tests/duplex.cpp @@ -1,7 +1,7 @@ /******************************************/ /* duplex.cpp - by Gary P. Scavone, 2006-2007. + by Gary P. Scavone, 2006-2019. This program opens a duplex stream and passes input directly through to the output. @@ -109,36 +109,29 @@ int main( int argc, char *argv[] ) iParams.deviceId = adac.getDefaultInputDevice(); if ( oDevice == 0 ) oParams.deviceId = adac.getDefaultOutputDevice(); - + RtAudio::StreamOptions options; //options.flags |= RTAUDIO_NONINTERLEAVED; bufferBytes = bufferFrames * channels * sizeof( MY_TYPE ); - try { - adac.openStream( &oParams, &iParams, FORMAT, fs, &bufferFrames, &inout, (void *)&bufferBytes, &options ); - } - catch ( RtAudioError& e ) { - std::cout << '\n' << e.getMessage() << '\n' << std::endl; - exit( 1 ); + if ( adac.openStream( &oParams, &iParams, FORMAT, fs, &bufferFrames, &inout, (void *)&bufferBytes, &options ) ) { + goto cleanup; } + if ( adac.isStreamOpen() == false ) goto cleanup; + // Test RtAudio functionality for reporting latency. std::cout << "\nStream latency = " << adac.getStreamLatency() << " frames" << std::endl; - try { - adac.startStream(); + if ( adac.startStream() ) goto cleanup; - char input; - std::cout << "\nRunning ... press to quit (buffer frames = " << bufferFrames << ").\n"; - std::cin.get(input); + char input; + std::cout << "\nRunning ... press to quit (buffer frames = " << bufferFrames << ").\n"; + std::cin.get(input); - // Stop the stream. + // Stop the stream. + if ( adac.isStreamRunning() ) adac.stopStream(); - } - catch ( RtAudioError& e ) { - std::cout << '\n' << e.getMessage() << '\n' << std::endl; - goto cleanup; - } cleanup: if ( adac.isStreamOpen() ) adac.closeStream(); diff --git a/tests/playsaw.cpp b/tests/playsaw.cpp index f678d1e..a9c7891 100644 --- a/tests/playsaw.cpp +++ b/tests/playsaw.cpp @@ -1,7 +1,7 @@ /******************************************/ /* playsaw.cpp - by Gary P. Scavone, 2006 + by Gary P. Scavone, 2006-2019. This program will output sawtooth waveforms of different frequencies on each channel. @@ -11,6 +11,7 @@ #include "RtAudio.h" #include #include +#include /* typedef char MY_TYPE; @@ -49,6 +50,10 @@ typedef double MY_TYPE; #define SLEEP( milliseconds ) usleep( (unsigned long) (milliseconds * 1000.0) ) #endif +// Interrupt handler function +bool done; +static void finish( int /*ignore*/ ){ done = true; } + #define BASE_RATE 0.005 #define TIME 1.0 @@ -173,8 +178,9 @@ int main( int argc, char *argv[] ) double *data = (double *) calloc( channels, sizeof( double ) ); - // Tell RtAudio to output all messages, even warnings. //dac.setErrorCallback( &errorCallback ); // could use if not set via constructor + + // Tell RtAudio to output all messages, even warnings. dac.showWarnings( true ); // Set our stream parameters for output only. @@ -200,6 +206,8 @@ int main( int argc, char *argv[] ) goto cleanup; if ( dac.isStreamOpen() == false ) goto cleanup; + //std::cout << "Stream latency = " << dac.getStreamLatency() << "\n" << std::endl; + // Stream is open ... now start it. if ( dac.startStream() ) goto cleanup; @@ -207,13 +215,17 @@ int main( int argc, char *argv[] ) while ( dac.isStreamRunning() == true ) SLEEP( 100 ); } else { - char input; - //std::cout << "Stream latency = " << dac.getStreamLatency() << "\n" << std::endl; - std::cout << "\nPlaying ... press to quit (buffer size = " << bufferFrames << ").\n"; - std::cin.get( input ); + std::cout << "\nPlaying ... quit with Ctrl-C (buffer size = " << bufferFrames << ").\n"; + + // Install an interrupt handler function. + done = false; + (void) signal(SIGINT, finish); + + while ( !done && dac.isStreamRunning() ) SLEEP( 100 ); // Block released ... stop the stream - dac.stopStream(); // or could call dac.abortStream(); + if ( dac.isStreamRunning() ) + dac.stopStream(); // or could call dac.abortStream(); } cleanup: -- 2.30.2