Fix typo in previous.
[rtaudio-cdist.git] / RtAudio.cpp
index 5284bdb329b145fca84af16dc16f74c7417bf89d..9b527ef6e085b0fb0cc4602922d56f4132512291 100644 (file)
@@ -46,6 +46,7 @@
 #include <cstring>\r
 #include <climits>\r
 #include <algorithm>\r
+#include <cmath>\r
 \r
 // Static variable definitions.\r
 const unsigned int RtApi::MAX_SAMPLE_RATES = 14;\r
@@ -408,14 +409,16 @@ double RtApi :: getStreamTime( void )
   struct timeval then;\r
   struct timeval now;\r
 \r
-  if ( stream_.state != STREAM_RUNNING || stream_.streamTime == 0.0 )\r
+  // If lastTickTimestamp is 0 it means we haven't had a "last tick" since\r
+  // we started the stream.\r
+  if ( stream_.state != STREAM_RUNNING || (stream_.lastTickTimestamp.tv_sec == 0 && stream_.lastTickTimestamp.tv_usec == 0) )\r
     return stream_.streamTime;\r
 \r
   gettimeofday( &now, NULL );\r
   then = stream_.lastTickTimestamp;\r
   return stream_.streamTime +\r
     ((now.tv_sec + 0.000001 * now.tv_usec) -\r
-     (then.tv_sec + 0.000001 * then.tv_usec));     \r
+     (then.tv_sec + 0.000001 * then.tv_usec));\r
 #else\r
   return stream_.streamTime;\r
 #endif\r
@@ -436,6 +439,14 @@ unsigned int RtApi :: getStreamSampleRate( void )
  return stream_.sampleRate;\r
 }\r
 \r
+void RtApi :: startStream( void )\r
+{\r
+#if defined( HAVE_GETTIMEOFDAY )\r
+  stream_.lastTickTimestamp.tv_sec = 0;\r
+  stream_.lastTickTimestamp.tv_usec = 0;\r
+#endif\r
+}\r
+\r
 \r
 // *************************************************** //\r
 //\r
@@ -1475,6 +1486,7 @@ void RtApiCore :: closeStream( void )
 void RtApiCore :: startStream( void )\r
 {\r
   verifyStream();\r
+  RtApi::startStream();\r
   if ( stream_.state == STREAM_RUNNING ) {\r
     errorText_ = "RtApiCore::startStream(): the stream is already running!";\r
     error( RtAudioError::WARNING );\r
@@ -1829,7 +1841,7 @@ bool RtApiCore :: callbackEvent( AudioDeviceID deviceId,
           channelsLeft -= streamChannels;\r
         }\r
       }\r
-      \r
+\r
       if ( stream_.doConvertBuffer[1] ) { // convert from our internal "device" buffer\r
         convertBuffer( stream_.userBuffer[1],\r
                        stream_.deviceBuffer,\r
@@ -2427,6 +2439,7 @@ void RtApiJack :: closeStream( void )
 void RtApiJack :: startStream( void )\r
 {\r
   verifyStream();\r
+  RtApi::startStream();\r
   if ( stream_.state == STREAM_RUNNING ) {\r
     errorText_ = "RtApiJack::startStream(): the stream is already running!";\r
     error( RtAudioError::WARNING );\r
@@ -2716,7 +2729,7 @@ RtApiAsio :: RtApiAsio()
   // CoInitialize beforehand, but it must be for appartment threading\r
   // (in which case, CoInitilialize will return S_FALSE here).\r
   coInitialized_ = false;\r
-  HRESULT hr = CoInitialize( NULL ); \r
+  HRESULT hr = CoInitialize( NULL );\r
   if ( FAILED(hr) ) {\r
     errorText_ = "RtApiAsio::ASIO requires a single-threaded appartment. Call CoInitializeEx(0,COINIT_APARTMENTTHREADED)";\r
     error( RtAudioError::WARNING );\r
@@ -3167,7 +3180,7 @@ bool RtApiAsio :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne
     errorText_ = errorStream_.str();\r
     goto error;\r
   }\r
-  buffersAllocated = true;  \r
+  buffersAllocated = true;\r
   stream_.state = STREAM_STOPPED;\r
 \r
   // Set flags for buffer conversion.\r
@@ -3306,6 +3319,7 @@ bool stopThreadCalled = false;
 void RtApiAsio :: startStream()\r
 {\r
   verifyStream();\r
+  RtApi::startStream();\r
   if ( stream_.state == STREAM_RUNNING ) {\r
     errorText_ = "RtApiAsio::startStream(): the stream is already running!";\r
     error( RtAudioError::WARNING );\r
@@ -3641,13 +3655,13 @@ static long asioMessages( long selector, long value, void* /*message*/, double*
 \r
 static const char* getAsioErrorString( ASIOError result )\r
 {\r
-  struct Messages \r
+  struct Messages\r
   {\r
     ASIOError value;\r
     const char*message;\r
   };\r
 \r
-  static const Messages m[] = \r
+  static const Messages m[] =\r
     {\r
       {   ASE_NotPresent,    "Hardware input or output is not present or available." },\r
       {   ASE_HWMalfunction,  "Hardware is malfunctioning." },\r
@@ -3682,7 +3696,7 @@ static const char* getAsioErrorString( ASIOError result )
 #include <audioclient.h>\r
 #include <avrt.h>\r
 #include <mmdeviceapi.h>\r
-#include <functiondiscoverykeys_devpkey.h>\r
+#include <FunctionDiscoveryKeys_devpkey.h>\r
 \r
 //=============================================================================\r
 \r
@@ -4317,6 +4331,7 @@ void RtApiWasapi::closeStream( void )
 void RtApiWasapi::startStream( void )\r
 {\r
   verifyStream();\r
+  RtApi::startStream();\r
 \r
   if ( stream_.state == STREAM_RUNNING ) {\r
     errorText_ = "RtApiWasapi::startStream: The stream is already running.";\r
@@ -5189,7 +5204,7 @@ Exit:
 #if defined(__WINDOWS_DS__) // Windows DirectSound API\r
 \r
 // Modified by Robin Davies, October 2005\r
-// - Improvements to DirectX pointer chasing. \r
+// - Improvements to DirectX pointer chasing.\r
 // - Bug fix for non-power-of-two Asio granularity used by Edirol PCR-A30.\r
 // - Auto-call CoInitialize for DSOUND and ASIO platforms.\r
 // Various revisions for RtAudio 4.0 by Gary Scavone, April 2007\r
@@ -5229,7 +5244,7 @@ struct DsHandle {
   void *id[2];\r
   void *buffer[2];\r
   bool xrun[2];\r
-  UINT bufferPointer[2];  \r
+  UINT bufferPointer[2];\r
   DWORD dsBufferSize[2];\r
   DWORD dsPointerLeadTime[2]; // the number of bytes ahead of the safe pointer to lead by.\r
   HANDLE condition;\r
@@ -6066,6 +6081,8 @@ void RtApiDs :: closeStream()
 void RtApiDs :: startStream()\r
 {\r
   verifyStream();\r
+  RtApi::startStream();\r
+\r
   if ( stream_.state == STREAM_RUNNING ) {\r
     errorText_ = "RtApiDs::startStream(): the stream is already running!";\r
     error( RtAudioError::WARNING );\r
@@ -6077,7 +6094,7 @@ void RtApiDs :: startStream()
   // Increase scheduler frequency on lesser windows (a side-effect of\r
   // increasing timer accuracy).  On greater windows (Win2K or later),\r
   // this is already in effect.\r
-  timeBeginPeriod( 1 ); \r
+  timeBeginPeriod( 1 );\r
 \r
   buffersRolling = false;\r
   duplexPrerollBytes = 0;\r
@@ -6398,7 +6415,7 @@ void RtApiDs :: callbackEvent()
   }\r
 \r
   if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) {\r
-    \r
+\r
     LPDIRECTSOUNDBUFFER dsBuffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0];\r
 \r
     if ( handle->drainCounter > 1 ) { // write zeros to the output stream\r
@@ -6465,7 +6482,7 @@ void RtApiDs :: callbackEvent()
     }\r
 \r
     if ( dsPointerBetween( nextWritePointer, safeWritePointer, currentWritePointer, dsBufferSize )\r
-         || dsPointerBetween( endWrite, safeWritePointer, currentWritePointer, dsBufferSize ) ) { \r
+         || dsPointerBetween( endWrite, safeWritePointer, currentWritePointer, dsBufferSize ) ) {\r
       // We've strayed into the forbidden zone ... resync the read pointer.\r
       handle->xrun[0] = true;\r
       nextWritePointer = safeWritePointer + handle->dsPointerLeadTime[0] - bufferBytes;\r
@@ -6539,14 +6556,14 @@ void RtApiDs :: callbackEvent()
     if ( safeReadPointer < (DWORD)nextReadPointer ) safeReadPointer += dsBufferSize; // unwrap offset\r
     DWORD endRead = nextReadPointer + bufferBytes;\r
 \r
-    // Handling depends on whether we are INPUT or DUPLEX. \r
+    // Handling depends on whether we are INPUT or DUPLEX.\r
     // If we're in INPUT mode then waiting is a good thing. If we're in DUPLEX mode,\r
     // then a wait here will drag the write pointers into the forbidden zone.\r
-    // \r
-    // In DUPLEX mode, rather than wait, we will back off the read pointer until \r
-    // it's in a safe position. This causes dropouts, but it seems to be the only \r
-    // practical way to sync up the read and write pointers reliably, given the \r
-    // the very complex relationship between phase and increment of the read and write \r
+    //\r
+    // In DUPLEX mode, rather than wait, we will back off the read pointer until\r
+    // it's in a safe position. This causes dropouts, but it seems to be the only\r
+    // practical way to sync up the read and write pointers reliably, given the\r
+    // the very complex relationship between phase and increment of the read and write\r
     // pointers.\r
     //\r
     // In order to minimize audible dropouts in DUPLEX mode, we will\r
@@ -6597,7 +6614,7 @@ void RtApiDs :: callbackEvent()
           error( RtAudioError::SYSTEM_ERROR );\r
           return;\r
         }\r
-      \r
+\r
         if ( safeReadPointer < (DWORD)nextReadPointer ) safeReadPointer += dsBufferSize; // unwrap offset\r
       }\r
     }\r
@@ -7743,6 +7760,7 @@ void RtApiAlsa :: startStream()
   // This method calls snd_pcm_prepare if the device isn't already in that state.\r
 \r
   verifyStream();\r
+  RtApi::startStream();\r
   if ( stream_.state == STREAM_RUNNING ) {\r
     errorText_ = "RtApiAlsa::startStream(): the stream is already running!";\r
     error( RtAudioError::WARNING );\r
@@ -7807,7 +7825,7 @@ void RtApiAlsa :: stopStream()
   AlsaHandle *apiInfo = (AlsaHandle *) stream_.apiHandle;\r
   snd_pcm_t **handle = (snd_pcm_t **) apiInfo->handles;\r
   if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) {\r
-    if ( apiInfo->synchronized ) \r
+    if ( apiInfo->synchronized )\r
       result = snd_pcm_drop( handle[0] );\r
     else\r
       result = snd_pcm_drain( handle[0] );\r
@@ -8096,6 +8114,7 @@ static void *alsaCallbackHandler( void *ptr )
 \r
 #include <pulse/error.h>\r
 #include <pulse/simple.h>\r
+#include <pulse/pulseaudio.h>\r
 #include <cstdio>\r
 \r
 static const unsigned int SUPPORTED_SAMPLERATES[] = { 8000, 16000, 22050, 32000,\r
@@ -8132,8 +8151,33 @@ unsigned int RtApiPulse::getDeviceCount( void )
   return 1;\r
 }\r
 \r
+void RtApiPulse::sinkInfoCallback(pa_context*, const pa_sink_info* info, int, void* arg)\r
+{\r
+  RtApiPulse* api = (RtApiPulse *) arg;\r
+  if (info) {\r
+    api->channels_ = info->sample_spec.channels;\r
+  }\r
+  pa_threaded_mainloop_signal(api->mainloop_, 0);\r
+}\r
+\r
+void RtApiPulse::contextStateCallback(pa_context* c, void* arg)\r
+{\r
+  pa_threaded_mainloop* mainloop = (pa_threaded_mainloop*) arg;\r
+\r
+  switch (pa_context_get_state(c)) {\r
+  case PA_CONTEXT_READY:\r
+  case PA_CONTEXT_TERMINATED:\r
+  case PA_CONTEXT_FAILED:\r
+    pa_threaded_mainloop_signal(mainloop, 0);\r
+    break;\r
+  default:\r
+    break;\r
+  }\r
+}\r
+\r
 RtAudio::DeviceInfo RtApiPulse::getDeviceInfo( unsigned int /*device*/ )\r
 {\r
+  /* Set up some defaults in case we crash and burn */\r
   RtAudio::DeviceInfo info;\r
   info.probed = true;\r
   info.name = "PulseAudio";\r
@@ -8149,6 +8193,72 @@ RtAudio::DeviceInfo RtApiPulse::getDeviceInfo( unsigned int /*device*/ )
   info.preferredSampleRate = 48000;\r
   info.nativeFormats = RTAUDIO_SINT16 | RTAUDIO_SINT32 | RTAUDIO_FLOAT32;\r
 \r
+  /* Get the number of output channels from pulseaudio.  A simple task, you say?\r
+     "What is your mainloop?" */\r
+  mainloop_ = pa_threaded_mainloop_new();\r
+  if (!mainloop_) {\r
+    return info;\r
+  }\r
+\r
+  pa_threaded_mainloop_start(mainloop_);\r
+  pa_threaded_mainloop_lock(mainloop_);\r
+\r
+  /* "And what is your context?" */\r
+  pa_context* context = pa_context_new(pa_threaded_mainloop_get_api(mainloop_), "RtAudio");\r
+  if (!context) {\r
+    pa_threaded_mainloop_unlock(mainloop_);\r
+    pa_threaded_mainloop_stop(mainloop_);\r
+    pa_threaded_mainloop_free(mainloop_);\r
+    mainloop_ = 0;\r
+    return info;\r
+  }\r
+\r
+  pa_context_set_state_callback(context, contextStateCallback, mainloop_);\r
+\r
+  pa_context_connect(context, 0, (pa_context_flags_t) 0, 0);\r
+\r
+  /* "And what is your favourite colour?" */\r
+  int connected = 0;\r
+  pa_context_state_t state = pa_context_get_state(context);\r
+  for (; !connected; state = pa_context_get_state(context)) {\r
+    switch (state) {\r
+    case PA_CONTEXT_READY:\r
+      connected = 1;\r
+      continue;\r
+    case PA_CONTEXT_FAILED:\r
+    case PA_CONTEXT_TERMINATED:\r
+      /* Blue! No, I mean red! */\r
+      pa_threaded_mainloop_unlock(mainloop_);\r
+      pa_context_disconnect(context);\r
+      pa_context_unref(context);\r
+      pa_threaded_mainloop_stop(mainloop_);\r
+      pa_threaded_mainloop_free(mainloop_);\r
+      mainloop_ = 0;\r
+      return info;\r
+    default:\r
+      pa_threaded_mainloop_wait(mainloop_);\r
+      break;\r
+    }\r
+  }\r
+\r
+  pa_operation* op = pa_context_get_sink_info_by_index(context, 0, sinkInfoCallback, this);\r
+\r
+  if (op) {\r
+    pa_operation_unref(op);\r
+  }\r
+\r
+  pa_threaded_mainloop_wait(mainloop_);\r
+  pa_threaded_mainloop_unlock(mainloop_);\r
+\r
+  pa_context_disconnect(context);\r
+  pa_context_unref(context);\r
+\r
+  pa_threaded_mainloop_stop(mainloop_);\r
+  pa_threaded_mainloop_free(mainloop_);\r
+  mainloop_ = 0;\r
+\r
+  info.outputChannels = channels_;\r
+\r
   return info;\r
 }\r
 \r
@@ -8275,7 +8385,7 @@ void RtApiPulse::callbackEvent( void )
     else\r
       bytes = stream_.nUserChannels[INPUT] * stream_.bufferSize *\r
         formatBytes( stream_.userFormat );\r
-            \r
+\r
     if ( pa_simple_read( pah->s_rec, pulse_in, bytes, &pa_error ) < 0 ) {\r
       errorStream_ << "RtApiPulse::callbackEvent: audio read error, " <<\r
         pa_strerror( pa_error ) << ".";\r
@@ -8293,12 +8403,22 @@ void RtApiPulse::callbackEvent( void )
   MUTEX_UNLOCK( &stream_.mutex );\r
   RtApi::tickStreamTime();\r
 \r
+  if (pah->s_play) {\r
+    int e = 0;\r
+    pa_usec_t const lat = pa_simple_get_latency(pah->s_play, &e);\r
+    if (e == 0) {\r
+      stream_.latency[0] = lat * stream_.sampleRate / 1000000;\r
+    }\r
+  }\r
+\r
   if ( doStopStream == 1 )\r
     stopStream();\r
 }\r
 \r
 void RtApiPulse::startStream( void )\r
 {\r
+  RtApi::startStream();\r
+\r
   PulseAudioHandle *pah = static_cast<PulseAudioHandle *>( stream_.apiHandle );\r
 \r
   if ( stream_.state == STREAM_CLOSED ) {\r
@@ -8402,10 +8522,6 @@ bool RtApiPulse::probeDeviceOpen( unsigned int device, StreamMode mode,
 \r
   if ( device != 0 ) return false;\r
   if ( mode != INPUT && mode != OUTPUT ) return false;\r
-  if ( channels != 1 && channels != 2 ) {\r
-    errorText_ = "RtApiPulse::probeDeviceOpen: unsupported number of channels.";\r
-    return false;\r
-  }\r
   ss.channels = channels;\r
 \r
   if ( firstChannel != 0 ) return false;\r
@@ -8517,7 +8633,6 @@ bool RtApiPulse::probeDeviceOpen( unsigned int device, StreamMode mode,
     pa_buffer_attr buffer_attr;\r
     buffer_attr.fragsize = bufferBytes;\r
     buffer_attr.maxlength = -1;\r
-\r
     pah->s_rec = pa_simple_new( NULL, streamName.c_str(), PA_STREAM_RECORD, NULL, "Record", &ss, NULL, &buffer_attr, &error );\r
     if ( !pah->s_rec ) {\r
       errorText_ = "RtApiPulse::probeDeviceOpen: error connecting input to PulseAudio server.";\r
@@ -8525,7 +8640,38 @@ bool RtApiPulse::probeDeviceOpen( unsigned int device, StreamMode mode,
     }\r
     break;\r
   case OUTPUT:\r
-    pah->s_play = pa_simple_new( NULL, streamName.c_str(), PA_STREAM_PLAYBACK, NULL, "Playback", &ss, NULL, NULL, &error );\r
+    /* XXX: hard-coded for DCP-o-matic */\r
+    pa_channel_map map;\r
+    pa_channel_map_init(&map);\r
+    /* XXX: need to check 7.1 */\r
+    map.channels = channels;\r
+\r
+    if (channels > 0) {\r
+      map.map[0] = PA_CHANNEL_POSITION_FRONT_LEFT;\r
+    }\r
+    if (channels > 1) {\r
+      map.map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT;\r
+    }\r
+    if (channels > 2) {\r
+      map.map[2] = PA_CHANNEL_POSITION_FRONT_CENTER;\r
+    }\r
+    if (channels > 3) {\r
+      map.map[3] = PA_CHANNEL_POSITION_LFE;\r
+    }\r
+    if (channels > 4) {\r
+      map.map[4] = PA_CHANNEL_POSITION_REAR_LEFT;\r
+    }\r
+    if (channels > 5) {\r
+      map.map[5] = PA_CHANNEL_POSITION_REAR_RIGHT;\r
+    }\r
+    if (channels > 6) {\r
+      map.map[6] = PA_CHANNEL_POSITION_SIDE_LEFT;\r
+    }\r
+    if (channels > 7) {\r
+      map.map[7] = PA_CHANNEL_POSITION_SIDE_RIGHT;\r
+    }\r
+\r
+    pah->s_play = pa_simple_new( NULL, streamName.c_str(), PA_STREAM_PLAYBACK, NULL, "Playback", &ss, &map, NULL, &error );\r
     if ( !pah->s_play ) {\r
       errorText_ = "RtApiPulse::probeDeviceOpen: error connecting output to PulseAudio server.";\r
       goto error;\r
@@ -8553,7 +8699,7 @@ bool RtApiPulse::probeDeviceOpen( unsigned int device, StreamMode mode,
 \r
   stream_.state = STREAM_STOPPED;\r
   return true;\r
\r
+\r
  error:\r
   if ( pah && stream_.callbackInfo.isRunning ) {\r
     pthread_cond_destroy( &pah->runnable_cv );\r
@@ -9237,6 +9383,7 @@ void RtApiOss :: closeStream()
 void RtApiOss :: startStream()\r
 {\r
   verifyStream();\r
+  RtApi::startStream();\r
   if ( stream_.state == STREAM_RUNNING ) {\r
     errorText_ = "RtApiOss::startStream(): the stream is already running!";\r
     error( RtAudioError::WARNING );\r
@@ -10228,4 +10375,3 @@ void RtApi :: byteSwapBuffer( char *buffer, unsigned int samples, RtAudioFormat
   // End:\r
   //\r
   // vim: et sts=2 sw=2\r
-\r