145a6bb98182 — Chris Cannam 3 years ago
Return error string on startup if specific (or only) implementation failed
M README +1 -3
@@ 4,11 4,9 @@ bqaudioio
 
 A small library wrapping various audio record / playback APIs in C++.
 
-Status: A bit messy.
-
 C++ standard required: C++11
 
-Copyright 2007-2016 Particular Programs Ltd.  Under a permissive
+Copyright 2007-2017 Particular Programs Ltd.  Under a permissive
 BSD/MIT-style licence: see the file COPYING for details.
 
 

          
M bqaudioio/AudioFactory.h +28 -10
@@ 82,12 82,18 @@ public:
      *
      * Return a null pointer if the requested device could not be
      * opened, or, in the case where no preference was stated, if no
-     * device could be opened.
+     * device could be opened. An error string may also be returned
+     * through the errorString argument. (The error string will
+     * generally be returned only if a specific implementation was
+     * requested or if only one implementation is available; otherwise
+     * we don't know which of the failed implementations to return an
+     * error from.)
      */
     static SystemAudioIO *
-    createCallbackIO(ApplicationRecordTarget *,
-                     ApplicationPlaybackSource *,
-                     Preference);
+    createCallbackIO(ApplicationRecordTarget *recordTarget,
+                     ApplicationPlaybackSource *playSource,
+                     Preference preference,
+                     std::string &errorString);
 
     /**
      * Open the audio driver in record-only mode using the given

          
@@ 100,11 106,17 @@ public:
      *
      * Return a null pointer if the requested device could not be
      * opened, or, in the case where no preference was stated, if no
-     * device could be opened.
+     * device could be opened. An error string may also be returned
+     * through the errorString argument. (The error string will
+     * generally be returned only if a specific implementation was
+     * requested or if only one implementation is available; otherwise
+     * we don't know which of the failed implementations to return an
+     * error from.)
      */
     static SystemRecordSource *
-    createCallbackRecordSource(ApplicationRecordTarget *,
-                               Preference);
+    createCallbackRecordSource(ApplicationRecordTarget *recordTarget,
+                               Preference preference,
+                               std::string &errorString);
     
     /**
      * Open the audio driver in playback-only mode using the given

          
@@ 117,11 129,17 @@ public:
      *
      * Return a null pointer if the requested device could not be
      * opened, or, in the case where no preference was stated, if no
-     * device could be opened.
+     * device could be opened. An error string may also be returned
+     * through the errorString argument. (The error string will
+     * generally be returned only if a specific implementation was
+     * requested or if only one implementation is available; otherwise
+     * we don't know which of the failed implementations to return an
+     * error from.)
      */
     static SystemPlaybackTarget *
-    createCallbackPlayTarget(ApplicationPlaybackSource *,
-                             Preference);
+    createCallbackPlayTarget(ApplicationPlaybackSource *playSource,
+                             Preference preference,
+                             std::string &errorString);
 
 private:
     AudioFactory(const AudioFactory &)=delete;

          
M src/AudioFactory.cpp +38 -14
@@ 144,17 144,22 @@ static SystemAudioIO *
 createIO(Mode mode,
          ApplicationRecordTarget *target,
          ApplicationPlaybackSource *source,
-         AudioFactory::Preference preference)
+         AudioFactory::Preference preference,
+         std::string &errorString)
 {
-    SystemAudioIO *io = 0;
+    string startupError;
+    int implementationsTried = 0;
     
 #ifdef HAVE_JACK
     if (preference.implementation == "" || preference.implementation == "jack") {
-        io = new JACKAudioIO(mode, target, source,
-                             preference.recordDevice, preference.playbackDevice);
+        ++implementationsTried;
+        JACKAudioIO *io = new JACKAudioIO(mode, target, source,
+                                          preference.recordDevice,
+                                          preference.playbackDevice);
         if (io->isOK()) return io;
         else {
             std::cerr << "WARNING: AudioFactory::createCallbackIO: Failed to open JACK I/O" << std::endl;
+            startupError = io->getStartupErrorString();
             delete io;
         }
     }

          
@@ 162,11 167,14 @@ createIO(Mode mode,
 
 #ifdef HAVE_LIBPULSE
     if (preference.implementation == "" || preference.implementation == "pulse") {
-        io = new PulseAudioIO(mode, target, source,
-                              preference.recordDevice, preference.playbackDevice);
+        ++implementationsTried;
+        PulseAudioIO *io = new PulseAudioIO(mode, target, source,
+                                            preference.recordDevice,
+                                            preference.playbackDevice);
         if (io->isOK()) return io;
         else {
             std::cerr << "WARNING: AudioFactory::createCallbackIO: Failed to open PulseAudio I/O" << std::endl;
+            startupError = io->getStartupErrorString();
             delete io;
         }
     }

          
@@ 174,52 182,68 @@ createIO(Mode mode,
 
 #ifdef HAVE_PORTAUDIO
     if (preference.implementation == "" || preference.implementation == "port") {
-        io = new PortAudioIO(mode, target, source,
-                             preference.recordDevice, preference.playbackDevice);
+        ++implementationsTried;
+        PortAudioIO *io = new PortAudioIO(mode, target, source,
+                                          preference.recordDevice,
+                                          preference.playbackDevice);
         if (io->isOK()) return io;
         else {
             std::cerr << "WARNING: AudioFactory::createCallbackIO: Failed to open PortAudio I/O" << std::endl;
+            startupError = io->getStartupErrorString();
             delete io;
         }
     }
 #endif
 
+    if (implementationsTried == 0) {
+        if (preference.implementation == "") {
+            errorString = "No audio drivers compiled in";
+        } else {
+            errorString = "Requested audio driver is not compiled in";
+        }
+    } else if (implementationsTried == 1) {
+        errorString = startupError;
+    }
+    
     std::cerr << "WARNING: AudioFactory::createIO: No suitable implementation available" << std::endl;
     return nullptr;
 }
 
 SystemPlaybackTarget *
 AudioFactory::createCallbackPlayTarget(ApplicationPlaybackSource *source,
-                                       Preference preference)
+                                       Preference preference,
+                                       std::string &errorString)
 {
     if (!source) {
         throw std::logic_error("ApplicationPlaybackSource must be provided");
     }
 
-    return createIO(Mode::Playback, 0, source, preference);
+    return createIO(Mode::Playback, 0, source, preference, errorString);
 }
 
 SystemRecordSource *
 AudioFactory::createCallbackRecordSource(ApplicationRecordTarget *target,
-                                         Preference preference)
+                                         Preference preference,
+                                         std::string &errorString)
 {
     if (!target) {
         throw std::logic_error("ApplicationRecordTarget must be provided");
     }
 
-    return createIO(Mode::Record, target, 0, preference);
+    return createIO(Mode::Record, target, 0, preference, errorString);
 }
 
 SystemAudioIO *
 AudioFactory::createCallbackIO(ApplicationRecordTarget *target,
                                ApplicationPlaybackSource *source,
-                               Preference preference)
+                               Preference preference,
+                               std::string &errorString)
 {
     if (!target || !source) {
         throw std::logic_error("ApplicationRecordTarget and ApplicationPlaybackSource must both be provided");
     }
 
-    return createIO(Mode::Duplex, target, source, preference);
+    return createIO(Mode::Duplex, target, source, preference, errorString);
 }
 
 }

          
M src/JACKAudioIO.cpp +5 -4
@@ 99,8 99,8 @@ JACKAudioIO::JACKAudioIO(Mode mode,
     JackStatus status = JackStatus(0);
     m_client = jack_client_open(clientName.c_str(), options, &status);
     if (!m_client) {
-        cerr << "ERROR: JACKPlaybackTarget: Failed to connect to JACK server"
-             << endl;
+        m_startupError = "Failed to connect to JACK server";
+        cerr << "ERROR: JACKPlaybackTarget: " << m_startupError << endl;
         return;
     }
 

          
@@ 111,8 111,9 @@ JACKAudioIO::JACKAudioIO(Mode mode,
     jack_set_process_callback(m_client, processStatic, this);
 
     if (jack_activate(m_client)) {
-	cerr << "ERROR: JACKAudioIO: Failed to activate JACK client"
-		  << endl;
+        m_startupError = "Failed to activate JACK client";
+	cerr << "ERROR: JACKAudioIO: " << m_startupError << endl;
+        return;
     }
 
     bool connectRecord = (recordDevice != noConnectionName);

          
M src/JACKAudioIO.h +3 -0
@@ 66,6 66,8 @@ public:
     virtual void resume() {}
     
     virtual double getCurrentTime() const;
+
+    std::string getStartupErrorString() const { return m_startupError; }
     
 protected:
     void setup(bool connectRecord, bool connectPlayback);

          
@@ 82,6 84,7 @@ protected:
     jack_nframes_t              m_bufferSize;
     jack_nframes_t              m_sampleRate;
     std::mutex                  m_mutex;
+    std::string                 m_startupError;
 
     JACKAudioIO(const JACKAudioIO &)=delete;
     JACKAudioIO &operator=(const JACKAudioIO &)=delete;

          
M src/PortAudioIO.cpp +7 -4
@@ 341,8 341,9 @@ PortAudioIO::PortAudioIO(Mode mode,
     }
     
     if (err != paNoError) {
-	cerr << "ERROR: PortAudioIO: Failed to open PortAudio stream: "
-             << Pa_GetErrorText(err) << endl;
+        m_startupError = "Failed to open PortAudio stream: ";
+        m_startupError += Pa_GetErrorText(err);
+	cerr << "ERROR: PortAudioIO: " << m_startupError << endl;
 	m_stream = 0;
         deinitialise();
 	return;

          
@@ 381,7 382,9 @@ PortAudioIO::PortAudioIO(Mode mode,
     err = Pa_StartStream(m_stream);
 
     if (err != paNoError) {
-	cerr << "ERROR: PortAudioIO: Failed to start PortAudio stream" << endl;
+	m_startupError = "Failed to start PortAudio stream: ";
+        m_startupError += Pa_GetErrorText(err);
+        cerr << "ERROR: PortAudioIO: " << m_startupError << endl;
 	Pa_CloseStream(m_stream);
 	m_stream = 0;
         deinitialise();

          
@@ 476,7 479,7 @@ PortAudioIO::suspend()
     if (m_suspended || !m_stream) return;
     PaError err = Pa_StopStream(m_stream);
     if (err != paNoError) {
-        cerr << "ERROR: PortAudioIO: Failed to abort PortAudio stream" << endl;
+        cerr << "ERROR: PortAudioIO: Failed to stop PortAudio stream" << endl;
     }
     m_suspended = true;
 #ifdef DEBUG_AUDIO_PORT_AUDIO_IO

          
M src/PortAudioIO.h +8 -5
@@ 61,13 61,15 @@ public:
     static std::vector<std::string> getRecordDeviceNames();
     static std::vector<std::string> getPlaybackDeviceNames();
     
-    virtual bool isSourceOK() const;
-    virtual bool isTargetOK() const;
+    virtual bool isSourceOK() const override;
+    virtual bool isTargetOK() const override;
+
+    virtual double getCurrentTime() const override;
 
-    virtual double getCurrentTime() const;
+    virtual void suspend() override;
+    virtual void resume() override;
 
-    virtual void suspend();
-    virtual void resume();
+    std::string getStartupErrorString() const { return m_startupError; }
     
 protected:
     int process(const void *input, void *output, unsigned long frames,

          
@@ 98,6 100,7 @@ protected:
     bool m_suspended;
     float **m_buffers;
     int m_bufferChannels;
+    std::string m_startupError;
 
     PortAudioIO(const PortAudioIO &)=delete;
     PortAudioIO &operator=(const PortAudioIO &)=delete;

          
M src/PulseAudioIO.cpp +4 -2
@@ 102,7 102,8 @@ PulseAudioIO::PulseAudioIO(Mode mode,
 
     m_loop = pa_mainloop_new();
     if (!m_loop) {
-        cerr << "ERROR: PulseAudioIO: Failed to create main loop" << endl;
+        m_startupError = "Failed to create PulseAudio main loop";
+        cerr << "ERROR: PulseAudioIO: " << m_startupError << endl;
         return;
     }
 

          
@@ 155,7 156,8 @@ PulseAudioIO::PulseAudioIO(Mode mode,
 
     m_context = pa_context_new(m_api, m_name.c_str());
     if (!m_context) {
-        cerr << "ERROR: PulseAudioIO: Failed to create context object" << endl;
+        m_startupError = "Failed to create PulseAudio context object";
+        cerr << "ERROR: PulseAudioIO: " << m_startupError << endl;
         return;
     }
 

          
M src/PulseAudioIO.h +4 -0
@@ 74,6 74,8 @@ public:
     virtual void suspend();
     virtual void resume();
 
+    std::string getStartupErrorString() const { return m_startupError; }
+
 protected:
     void streamWrite(int);
     void streamRead(int);

          
@@ 121,6 123,8 @@ protected:
 
     bool m_suspended;
 
+    std::string m_startupError;
+
     void checkBufferCapacity(int nframes);
     
     PulseAudioIO(const PulseAudioIO &)=delete;