Modified CoreAudio cleanup in case of error during initialization, fixed duplex strea...
authorGary Scavone <gary@music.mcgill.ca>
Thu, 22 Aug 2019 22:24:11 +0000 (18:24 -0400)
committerGary Scavone <gary@music.mcgill.ca>
Thu, 22 Aug 2019 22:24:11 +0000 (18:24 -0400)
RtAudio.cpp
tests/duplex.cpp
tests/playsaw.cpp

index d0827fd606f86b777e707f757e9509bfe1aab484..d01c4e2fdc3b71fdc53671381df2818e18489370 100644 (file)
@@ -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;
index c5abc3b60a5401e3999fa4cd5901310194899124..33ab282b328e1c5a560a43fb57f3c968529fbaa2 100644 (file)
@@ -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 <enter> to quit (buffer frames = " << bufferFrames << ").\n";
-    std::cin.get(input);
+  char input;
+  std::cout << "\nRunning ... press <enter> 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();
index f678d1ef25ca3acd10d2256eea6b69b7a51a2a47..a9c7891b7b097a453e4a71fc03ea45accdc39b06 100644 (file)
@@ -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 <iostream>
 #include <cstdlib>
+#include <signal.h>
 
 /*
 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 <enter> 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: