Try to support > 2 output channels with PulseAudio.
authorCarl Hetherington <cth@carlh.net>
Mon, 12 Nov 2018 22:54:01 +0000 (22:54 +0000)
committerCarl Hetherington <cth@carlh.net>
Mon, 12 Nov 2018 22:54:01 +0000 (22:54 +0000)
RtAudio.cpp
RtAudio.h

index 539cdc273a069c2424c8ea2030575bc4e75d1665..f2bc5d140f8b3fe4a4b5859e58fa30a71c597d1a 100644 (file)
@@ -418,7 +418,7 @@ double RtApi :: getStreamTime( void )
   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
@@ -1841,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
@@ -2729,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
@@ -3180,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
@@ -3655,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
@@ -4332,7 +4332,7 @@ void RtApiWasapi::startStream( void )
 {\r
   verifyStream();\r
   RtApi::startStream();\r
-  \r
+\r
   if ( stream_.state == STREAM_RUNNING ) {\r
     errorText_ = "RtApiWasapi::startStream: The stream is already running.";\r
     error( RtAudioError::WARNING );\r
@@ -5204,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
@@ -5244,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
@@ -6082,7 +6082,7 @@ void RtApiDs :: startStream()
 {\r
   verifyStream();\r
   RtApi::startStream();\r
-  \r
+\r
   if ( stream_.state == STREAM_RUNNING ) {\r
     errorText_ = "RtApiDs::startStream(): the stream is already running!";\r
     error( RtAudioError::WARNING );\r
@@ -6094,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
@@ -6415,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
@@ -6482,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
@@ -6556,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
@@ -6614,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
@@ -7825,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
@@ -8114,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
@@ -8150,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
@@ -8167,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
@@ -8293,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
@@ -8326,7 +8418,7 @@ void RtApiPulse::callbackEvent( void )
 void RtApiPulse::startStream( void )\r
 {\r
   RtApi::startStream();\r
-  \r
+\r
   PulseAudioHandle *pah = static_cast<PulseAudioHandle *>( stream_.apiHandle );\r
 \r
   if ( stream_.state == STREAM_CLOSED ) {\r
@@ -8430,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
@@ -8581,7 +8669,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
@@ -10257,4 +10345,3 @@ void RtApi :: byteSwapBuffer( char *buffer, unsigned int samples, RtAudioFormat
   // End:\r
   //\r
   // vim: et sts=2 sw=2\r
-\r
index 78fca0c779700bc34d83bba2c23fdcc2e72274ae..ba880f3e38bd5101857a35c3d157126c91b1be15 100644 (file)
--- a/RtAudio.h
+++ b/RtAudio.h
@@ -111,7 +111,7 @@ static const RtAudioFormat RTAUDIO_FLOAT64 = 0x20; // Normalized between plus/mi
     open the input and/or output stream device(s) for exclusive use.
     Note that this is not possible with all supported audio APIs.
 
-    If the RTAUDIO_SCHEDULE_REALTIME flag is set, RtAudio will attempt 
+    If the RTAUDIO_SCHEDULE_REALTIME flag is set, RtAudio will attempt
     to select realtime scheduling (round-robin) for the callback thread.
 
     If the RTAUDIO_ALSA_USE_DEFAULT flag is set, RtAudio will attempt to
@@ -215,7 +215,7 @@ class RtAudioError : public std::exception
 
   //! The constructor.
   RtAudioError( const std::string& message, Type type = RtAudioError::UNSPECIFIED ) throw() : message_(message), type_(type) {}
+
   //! The destructor.
   virtual ~RtAudioError( void ) throw() {}
 
@@ -341,7 +341,7 @@ class RtAudio
     open the input and/or output stream device(s) for exclusive use.
     Note that this is not possible with all supported audio APIs.
 
-    If the RTAUDIO_SCHEDULE_REALTIME flag is set, RtAudio will attempt 
+    If the RTAUDIO_SCHEDULE_REALTIME flag is set, RtAudio will attempt
     to select realtime scheduling (round-robin) for the callback thread.
     The \c priority parameter will only be used if the RTAUDIO_SCHEDULE_REALTIME
     flag is set. It defines the thread's realtime priority.
@@ -410,7 +410,7 @@ class RtAudio
   /*!
     This function performs a system query of available devices each time it
     is called, thus supporting devices connected \e after instantiation. If
-    a system error occurs during processing, a warning will be issued. 
+    a system error occurs during processing, a warning will be issued.
   */
   unsigned int getDeviceCount( void ) throw();
 
@@ -477,7 +477,7 @@ class RtAudio
            from within the callback function.
     \param options An optional pointer to a structure containing various
            global stream options, including a list of OR'ed RtAudioStreamFlags
-           and a suggested number of stream buffers that can be used to 
+           and a suggested number of stream buffers that can be used to
            control stream latency.  More buffers typically result in more
            robust performance, though at a cost of greater latency.  If a
            value of zero is specified, a system-specific median value is
@@ -785,7 +785,7 @@ protected:
     "warning" message is reported and FAILURE is returned. A
     successful probe is indicated by a return value of SUCCESS.
   */
-  virtual bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, 
+  virtual bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels,
                                 unsigned int firstChannel, unsigned int sampleRate,
                                 RtAudioFormat format, unsigned int *bufferSize,
                                 RtAudio::StreamOptions *options );
@@ -877,7 +877,7 @@ public:
 
   private:
 
-  bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, 
+  bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels,
                         unsigned int firstChannel, unsigned int sampleRate,
                         RtAudioFormat format, unsigned int *bufferSize,
                         RtAudio::StreamOptions *options );
@@ -911,7 +911,7 @@ public:
 
   private:
 
-  bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, 
+  bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels,
                         unsigned int firstChannel, unsigned int sampleRate,
                         RtAudioFormat format, unsigned int *bufferSize,
                         RtAudio::StreamOptions *options );
@@ -947,7 +947,7 @@ public:
   std::vector<RtAudio::DeviceInfo> devices_;
   void saveDeviceInfo( void );
   bool coInitialized_;
-  bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, 
+  bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels,
                         unsigned int firstChannel, unsigned int sampleRate,
                         RtAudioFormat format, unsigned int *bufferSize,
                         RtAudio::StreamOptions *options );
@@ -986,7 +986,7 @@ public:
   bool buffersRolling;
   long duplexPrerollBytes;
   std::vector<struct DsDevice> dsDevices;
-  bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, 
+  bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels,
                         unsigned int firstChannel, unsigned int sampleRate,
                         RtAudioFormat format, unsigned int *bufferSize,
                         RtAudio::StreamOptions *options );
@@ -1057,7 +1057,7 @@ public:
 
   std::vector<RtAudio::DeviceInfo> devices_;
   void saveDeviceInfo( void );
-  bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, 
+  bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels,
                         unsigned int firstChannel, unsigned int sampleRate,
                         RtAudioFormat format, unsigned int *bufferSize,
                         RtAudio::StreamOptions *options );
@@ -1067,9 +1067,14 @@ public:
 
 #if defined(__LINUX_PULSE__)
 
+struct pa_context;
+struct pa_sink_info;
+struct pa_threaded_mainloop;
+
 class RtApiPulse: public RtApi
 {
 public:
+  RtApiPulse() : mainloop_(0), channels_(2) {}
   ~RtApiPulse();
   RtAudio::Api getCurrentApi() { return RtAudio::LINUX_PULSE; }
   unsigned int getDeviceCount( void );
@@ -1093,6 +1098,11 @@ public:
                         unsigned int firstChannel, unsigned int sampleRate,
                         RtAudioFormat format, unsigned int *bufferSize,
                         RtAudio::StreamOptions *options );
+
+  static void sinkInfoCallback(pa_context* c, const pa_sink_info* info, int eol, void* arg);
+  static void contextStateCallback(pa_context* c, void* arg);
+  pa_threaded_mainloop* mainloop_;
+  int channels_;
 };
 
 #endif
@@ -1121,7 +1131,7 @@ public:
 
   private:
 
-  bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, 
+  bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels,
                         unsigned int firstChannel, unsigned int sampleRate,
                         RtAudioFormat format, unsigned int *bufferSize,
                         RtAudio::StreamOptions *options );
@@ -1146,7 +1156,7 @@ public:
 
   private:
 
-  bool probeDeviceOpen( unsigned int /*device*/, StreamMode /*mode*/, unsigned int /*channels*/, 
+  bool probeDeviceOpen( unsigned int /*device*/, StreamMode /*mode*/, unsigned int /*channels*/,
                         unsigned int /*firstChannel*/, unsigned int /*sampleRate*/,
                         RtAudioFormat /*format*/, unsigned int * /*bufferSize*/,
                         RtAudio::StreamOptions * /*options*/ ) { return false; }