Bug fix for previous bug fix for duplex stream time ticking.
[rtaudio.git] / RtAudio.cpp
index 33ae87b08f63cb0d55a42ca871b209de770051ef..912704e73d11c3b1c5f6a1e7aa9f056e3f697686 100644 (file)
@@ -7,10 +7,11 @@
     and OSS), Macintosh OS X (CoreAudio and Jack), and Windows
     (DirectSound, ASIO and WASAPI) operating systems.
 
+    RtAudio GitHub site: https://github.com/thestk/rtaudio
     RtAudio WWW site: http://www.music.mcgill.ca/~gary/rtaudio/
 
     RtAudio: realtime audio i/o C++ classes
-    Copyright (c) 2001-2017 Gary P. Scavone
+    Copyright (c) 2001-2019 Gary P. Scavone
 
     Permission is hereby granted, free of charge, to any person
     obtaining a copy of this software and associated documentation files
@@ -38,7 +39,7 @@
 */
 /************************************************************************/
 
-// RtAudio: Version 5.0.0
+// RtAudio: Version 5.1.0
 
 #include "RtAudio.h"
 #include <iostream>
@@ -113,6 +114,7 @@ const char* rtaudio_api_names[][2] = {
   { "ds"          , "DirectSound" },
   { "dummy"       , "Dummy" },
 };
+
 const unsigned int rtaudio_num_api_names = 
   sizeof(rtaudio_api_names)/sizeof(rtaudio_api_names[0]);
 
@@ -236,7 +238,7 @@ void RtAudio :: openRtApi( RtAudio::Api api )
 RtAudio :: RtAudio( RtAudio::Api api )
 {
   rtapi_ = 0;
-
+  
   if ( api != UNSPECIFIED ) {
     // Attempt to open the specified API.
     openRtApi( api );
@@ -259,9 +261,9 @@ RtAudio :: RtAudio( RtAudio::Api api )
   if ( rtapi_ ) return;
 
   // It should not be possible to get here because the preprocessor
-  // definition __RTAUDIO_DUMMY__ is automatically defined if no
-  // API-specific definitions are passed to the compiler. But just in
-  // case something weird happens, we'll thow an error.
+  // definition __RTAUDIO_DUMMY__ is automatically defined in RtAudio.h
+  // if no API-specific definitions are passed to the compiler. But just
+  // in case something weird happens, we'll thow an error.
   std::string errorText = "\nRtAudio: no compiled API support found ... critical error!!\n\n";
   throw( RtAudioError( errorText, RtAudioError::UNSPECIFIED ) );
 }
@@ -294,11 +296,7 @@ void RtAudio :: openStream( RtAudio::StreamParameters *outputParameters,
 
 RtApi :: RtApi()
 {
-  stream_.state = STREAM_CLOSED;
-  stream_.mode = UNINITIALIZED;
-  stream_.apiHandle = 0;
-  stream_.userBuffer[0] = 0;
-  stream_.userBuffer[1] = 0;
+  clearStreamInfo();
   MUTEX_INITIALIZE( &stream_.mutex );
   showWarnings_ = true;
   firstErrorOccurred_ = false;
@@ -437,15 +435,15 @@ void RtApi :: tickStreamTime( void )
 
   stream_.streamTime += ( stream_.bufferSize * 1.0 / stream_.sampleRate );
 
+  /*
 #if defined( HAVE_GETTIMEOFDAY )
   gettimeofday( &stream_.lastTickTimestamp, NULL );
 #endif
+  */
 }
 
 long RtApi :: getStreamLatency( void )
 {
-  verifyStream();
-
   long totalLatency = 0;
   if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX )
     totalLatency = stream_.latency[0];
@@ -455,10 +453,9 @@ long RtApi :: getStreamLatency( void )
   return totalLatency;
 }
 
+/*
 double RtApi :: getStreamTime( void )
 {
-  verifyStream();
-
 #if defined( HAVE_GETTIMEOFDAY )
   // Return a very accurate estimate of the stream time by
   // adding in the elapsed time since the last tick.
@@ -475,26 +472,31 @@ double RtApi :: getStreamTime( void )
      (then.tv_sec + 0.000001 * then.tv_usec));     
 #else
   return stream_.streamTime;
-#endif
+  #endif
 }
+*/
 
 void RtApi :: setStreamTime( double time )
 {
-  verifyStream();
+  //  verifyStream();
 
   if ( time >= 0.0 )
     stream_.streamTime = time;
+  /*
 #if defined( HAVE_GETTIMEOFDAY )
   gettimeofday( &stream_.lastTickTimestamp, NULL );
 #endif
+  */
 }
 
+/*
 unsigned int RtApi :: getStreamSampleRate( void )
 {
- verifyStream();
 verifyStream();
 
  return stream_.sampleRate;
 }
+*/
 
 
 // *************************************************** //
@@ -874,6 +876,13 @@ RtAudio::DeviceInfo RtApiCore :: getDeviceInfo( unsigned int device )
     return info;
   }
 
+  // Probe the currently configured sample rate
+  Float64 nominalRate;
+  dataSize = sizeof( Float64 );
+  property.mSelector = kAudioDevicePropertyNominalSampleRate;
+  result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &nominalRate );
+  if ( result == noErr ) info.currentSampleRate = (unsigned int) nominalRate;
+    
   // CoreAudio always uses 32-bit floating point data for PCM streams.
   // Thus, any other "physical" formats supported by the device are of
   // no interest to the client.
@@ -905,6 +914,24 @@ static OSStatus callbackHandler( AudioDeviceID inDevice,
     return kAudioHardwareNoError;
 }
 
+static OSStatus disconnectListener( AudioObjectID /*inDevice*/,
+                                    UInt32 nAddresses,
+                                    const AudioObjectPropertyAddress properties[],
+                                    void* infoPointer )
+{
+  for ( UInt32 i=0; i<nAddresses; i++ ) {
+    if ( properties[i].mSelector == kAudioDevicePropertyDeviceIsAlive ) {
+      CallbackInfo *info = (CallbackInfo *) infoPointer;
+      RtApiCore *object = (RtApiCore *) info->object;
+      info->deviceDisconnected = true;
+      object->closeStream();
+      return kAudioHardwareUnspecifiedError;
+    }
+  }
+  
+  return kAudioHardwareNoError;
+}
+
 static OSStatus xrunListener( AudioObjectID /*inDevice*/,
                               UInt32 nAddresses,
                               const AudioObjectPropertyAddress properties[],
@@ -923,20 +950,6 @@ static OSStatus xrunListener( AudioObjectID /*inDevice*/,
   return kAudioHardwareNoError;
 }
 
-static OSStatus rateListener( AudioObjectID inDevice,
-                              UInt32 /*nAddresses*/,
-                              const AudioObjectPropertyAddress /*properties*/[],
-                              void* ratePointer )
-{
-  Float64 *rate = (Float64 *) ratePointer;
-  UInt32 dataSize = sizeof( Float64 );
-  AudioObjectPropertyAddress property = { kAudioDevicePropertyNominalSampleRate,
-                                          kAudioObjectPropertyScopeGlobal,
-                                          kAudioObjectPropertyElementMaster };
-  AudioObjectGetPropertyData( inDevice, &property, 0, NULL, &dataSize, rate );
-  return kAudioHardwareNoError;
-}
-
 bool RtApiCore :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels,
                                    unsigned int firstChannel, unsigned int sampleRate,
                                    RtAudioFormat format, unsigned int *bufferSize,
@@ -1146,23 +1159,12 @@ bool RtApiCore :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne
     return FAILURE;
   }
 
-  // Only change the sample rate if off by more than 1 Hz.
+  // Only try to change the sample rate if off by more than 1 Hz.
   if ( fabs( nominalRate - (double)sampleRate ) > 1.0 ) {
 
-    // Set a property listener for the sample rate change
-    Float64 reportedRate = 0.0;
-    AudioObjectPropertyAddress tmp = { kAudioDevicePropertyNominalSampleRate, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };
-    result = AudioObjectAddPropertyListener( id, &tmp, rateListener, (void *) &reportedRate );
-    if ( result != noErr ) {
-      errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") setting sample rate property listener for device (" << device << ").";
-      errorText_ = errorStream_.str();
-      return FAILURE;
-    }
-
     nominalRate = (Float64) sampleRate;
     result = AudioObjectSetPropertyData( id, &property, 0, NULL, dataSize, &nominalRate );
     if ( result != noErr ) {
-      AudioObjectRemovePropertyListener( id, &tmp, rateListener, (void *) &reportedRate );
       errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") setting sample rate for device (" << device << ").";
       errorText_ = errorStream_.str();
       return FAILURE;
@@ -1170,16 +1172,15 @@ bool RtApiCore :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne
 
     // Now wait until the reported nominal rate is what we just set.
     UInt32 microCounter = 0;
+    Float64 reportedRate = 0.0;
     while ( reportedRate != nominalRate ) {
       microCounter += 5000;
-      if ( microCounter > 5000000 ) break;
+      if ( microCounter > 2000000 ) break;
       usleep( 5000 );
+      result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &reportedRate );
     }
 
-    // Remove the property listener.
-    AudioObjectRemovePropertyListener( id, &tmp, rateListener, (void *) &reportedRate );
-
-    if ( microCounter > 5000000 ) {
+    if ( microCounter > 2000000 ) {
       errorStream_ << "RtApiCore::probeDeviceOpen: timeout waiting for sample rate update for device (" << device << ").";
       errorText_ = errorStream_.str();
       return FAILURE;
@@ -1430,6 +1431,21 @@ bool RtApiCore :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne
   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;
+  }
+
+  // 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;
+  }
 
   return SUCCESS;
 
@@ -1452,7 +1468,8 @@ bool RtApiCore :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne
     stream_.deviceBuffer = 0;
   }
 
-  stream_.state = STREAM_CLOSED;
+  clearStreamInfo();
+  //stream_.state = STREAM_CLOSED;
   return FAILURE;
 }
 
@@ -1472,9 +1489,13 @@ void RtApiCore :: closeStream( void )
         kAudioObjectPropertyElementMaster };
 
       property.mSelector = kAudioDeviceProcessorOverload;
-      property.mScope = kAudioObjectPropertyScopeGlobal;
       if (AudioObjectRemovePropertyListener( handle->id[0], &property, xrunListener, (void *) handle ) != noErr) {
-        errorText_ = "RtApiCore::closeStream(): error removing property listener!";
+        errorText_ = "RtApiCore::closeStream(): error removing xrun property listener!";
+        error( RtAudioError::WARNING );
+      }
+      property.mSelector = kAudioDevicePropertyDeviceIsAlive;
+      if (AudioObjectRemovePropertyListener( handle->id[0], &property, disconnectListener, (void *) &stream_.callbackInfo ) != noErr) {
+        errorText_ = "RtApiCore::closeStream(): error removing disconnect property listener!";
         error( RtAudioError::WARNING );
       }
     }
@@ -1495,9 +1516,13 @@ void RtApiCore :: closeStream( void )
         kAudioObjectPropertyElementMaster };
 
       property.mSelector = kAudioDeviceProcessorOverload;
-      property.mScope = kAudioObjectPropertyScopeGlobal;
       if (AudioObjectRemovePropertyListener( handle->id[1], &property, xrunListener, (void *) handle ) != noErr) {
-        errorText_ = "RtApiCore::closeStream(): error removing property listener!";
+        errorText_ = "RtApiCore::closeStream(): error removing xrun property listener!";
+        error( RtAudioError::WARNING );
+      }
+      property.mSelector = kAudioDevicePropertyDeviceIsAlive;
+      if (AudioObjectRemovePropertyListener( handle->id[1], &property, disconnectListener, (void *) &stream_.callbackInfo ) != noErr) {
+        errorText_ = "RtApiCore::closeStream(): error removing disconnect property listener!";
         error( RtAudioError::WARNING );
       }
     }
@@ -1524,26 +1549,39 @@ void RtApiCore :: closeStream( void )
   }
 
   // Destroy pthread condition variable.
+  pthread_cond_signal( &handle->condition ); // signal condition variable in case stopStream is blocked
   pthread_cond_destroy( &handle->condition );
   delete handle;
   stream_.apiHandle = 0;
 
-  stream_.mode = UNINITIALIZED;
-  stream_.state = STREAM_CLOSED;
+  CallbackInfo *info = (CallbackInfo *) &stream_.callbackInfo;
+  if ( info->deviceDisconnected ) {
+    errorText_ = "RtApiCore: the stream device was disconnected (and closed)!";
+    error( RtAudioError::DEVICE_DISCONNECT );
+  }
+  
+  clearStreamInfo();
+  //stream_.mode = UNINITIALIZED;
+  //stream_.state = STREAM_CLOSED;
 }
 
 void RtApiCore :: startStream( void )
 {
-  verifyStream();
-  if ( stream_.state == STREAM_RUNNING ) {
-    errorText_ = "RtApiCore::startStream(): the stream is already running!";
+  //verifyStream();
+  if ( stream_.state != STREAM_STOPPED ) {
+    if ( stream_.state == STREAM_RUNNING )
+      errorText_ = "RtApiCore::startStream(): the stream is already running!";
+    else if ( stream_.state == STREAM_STOPPING || stream_.state == STREAM_CLOSED )
+      errorText_ = "RtApiCore::startStream(): the stream is stopping or closed!";
     error( RtAudioError::WARNING );
     return;
   }
 
+  /*
   #if defined( HAVE_GETTIMEOFDAY )
   gettimeofday( &stream_.lastTickTimestamp, NULL );
   #endif
+  */
 
   OSStatus result = noErr;
   CoreHandle *handle = (CoreHandle *) stream_.apiHandle;
@@ -1579,9 +1617,12 @@ void RtApiCore :: startStream( void )
 
 void RtApiCore :: stopStream( void )
 {
-  verifyStream();
-  if ( stream_.state == STREAM_STOPPED ) {
-    errorText_ = "RtApiCore::stopStream(): the stream is already stopped!";
+  //verifyStream();
+  if ( stream_.state != STREAM_RUNNING && stream_.state != STREAM_STOPPING ) {
+    if ( stream_.state == STREAM_STOPPED )
+      errorText_ = "RtApiCore::stopStream(): the stream is already stopped!";
+    else if ( stream_.state == STREAM_CLOSED )
+      errorText_ = "RtApiCore::stopStream(): the stream is closed!";
     error( RtAudioError::WARNING );
     return;
   }
@@ -1614,6 +1655,8 @@ void RtApiCore :: stopStream( void )
   }
 
   stream_.state = STREAM_STOPPED;
+  // set stream time to zero?
+  // and perhaps clear input user or device buffers?
 
  unlock:
   if ( result == noErr ) return;
@@ -1622,9 +1665,12 @@ void RtApiCore :: stopStream( void )
 
 void RtApiCore :: abortStream( void )
 {
-  verifyStream();
-  if ( stream_.state == STREAM_STOPPED ) {
-    errorText_ = "RtApiCore::abortStream(): the stream is already stopped!";
+  //verifyStream();
+  if ( stream_.state != STREAM_RUNNING ) {
+    if ( stream_.state == STREAM_STOPPED )
+      errorText_ = "RtApiCore::abortStream(): the stream is already stopped!";
+    else if ( stream_.state == STREAM_STOPPING || stream_.state == STREAM_CLOSED )
+      errorText_ = "RtApiCore::abortStream(): the stream is stopping or closed!";
     error( RtAudioError::WARNING );
     return;
   }
@@ -1632,6 +1678,7 @@ void RtApiCore :: abortStream( void )
   CoreHandle *handle = (CoreHandle *) stream_.apiHandle;
   handle->drainCounter = 2;
 
+  stream_.state = STREAM_STOPPING;
   stopStream();
 }
 
@@ -1696,8 +1743,6 @@ bool RtApiCore :: callbackEvent( AudioDeviceID deviceId,
     int cbReturnValue = callback( stream_.userBuffer[0], stream_.userBuffer[1],
                                   stream_.bufferSize, streamTime, status, info->userData );
     if ( cbReturnValue == 2 ) {
-      stream_.state = STREAM_STOPPING;
-      handle->drainCounter = 2;
       abortStream();
       return SUCCESS;
     }
@@ -1903,7 +1948,6 @@ bool RtApiCore :: callbackEvent( AudioDeviceID deviceId,
   }
 
  unlock:
-  //MUTEX_UNLOCK( &stream_.mutex );
 
   // 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] ) )
@@ -9942,30 +9986,16 @@ void RtApi :: error( RtAudioError::Type type )
 
   RtAudioErrorCallback errorCallback = (RtAudioErrorCallback) stream_.callbackInfo.errorCallback;
   if ( errorCallback ) {
-    // abortStream() can generate new error messages. Ignore them. Just keep original one.
-
-    if ( firstErrorOccurred_ )
-      return;
-
-    firstErrorOccurred_ = true;
     const std::string errorMessage = errorText_;
-
-    if ( type != RtAudioError::WARNING && stream_.state != STREAM_STOPPED) {
-      stream_.callbackInfo.isRunning = false; // exit from the thread
-      abortStream();
-    }
-
     errorCallback( type, errorMessage );
-    firstErrorOccurred_ = false;
-    return;
   }
-
-  if ( type == RtAudioError::WARNING && showWarnings_ == true )
-    std::cerr << '\n' << errorText_ << "\n\n";
-  else if ( type != RtAudioError::WARNING )
-    throw( RtAudioError( errorText_, type ) );
+  else {
+    if ( showWarnings_ == true )
+      std::cerr << '\n' << errorText_ << "\n\n";
+  }
 }
 
+/*
 void RtApi :: verifyStream()
 {
   if ( stream_.state == STREAM_CLOSED ) {
@@ -9973,6 +10003,7 @@ void RtApi :: verifyStream()
     error( RtAudioError::INVALID_USE );
   }
 }
+*/
 
 void RtApi :: clearStreamInfo()
 {