Add support for callback API to portaudio backend but keep blocking API as default
authorTim Mayberry <mojofunk@gmail.com>
Thu, 27 Aug 2015 22:53:10 +0000 (08:53 +1000)
committerTim Mayberry <mojofunk@gmail.com>
Thu, 19 Nov 2015 00:23:26 +0000 (10:23 +1000)
Don't use the callback API for now until further and wider testing.

libs/backends/portaudio/portaudio_backend.cc
libs/backends/portaudio/portaudio_backend.h
libs/backends/portaudio/portaudio_io.cc
libs/backends/portaudio/portaudio_io.h
libs/backends/portaudio/wscript

index 3329deee7ebf72c75f55764cb3c3951bd81faf8c..b6736ca1b01bb0e9014b9969f985a055d94441e4 100644 (file)
 #include <sys/time.h>
 #endif
 
+#ifdef COMPILER_MINGW
+#include <sys/time.h>
+#endif
+
 #include <glibmm.h>
 
 #include "portaudio_backend.h"
@@ -63,6 +67,9 @@ PortAudioBackend::PortAudioBackend (AudioEngine& e, AudioBackendInfo& info)
        , _run (false)
        , _active (false)
        , _freewheel (false)
+       , _freewheeling (false)
+       , _freewheel_ack (false)
+       , _reinit_thread_callback (false)
        , _measure_latency (false)
        , m_cycle_count(0)
        , m_total_deviation_us(0)
@@ -82,6 +89,8 @@ PortAudioBackend::PortAudioBackend (AudioEngine& e, AudioBackendInfo& info)
 {
        _instance_name = s_instance_name;
        pthread_mutex_init (&_port_callback_mutex, 0);
+       pthread_mutex_init (&m_freewheel_mutex, 0);
+       pthread_cond_init (&m_freewheel_signal, 0);
 
        _pcmio = new PortAudioIO ();
        _midiio = new WinMMEMidiIO ();
@@ -93,6 +102,8 @@ PortAudioBackend::~PortAudioBackend ()
        delete _midiio; _midiio = 0;
 
        pthread_mutex_destroy (&_port_callback_mutex);
+       pthread_mutex_destroy (&m_freewheel_mutex);
+       pthread_cond_destroy (&m_freewheel_signal);
 }
 
 /* AUDIOBACKEND API */
@@ -471,11 +482,22 @@ PortAudioBackend::_start (bool for_latency_measurement)
 
        PaErrorCode err = paNoError;
 
+#ifdef USE_BLOCKING_API
        err = _pcmio->open_blocking_stream(name_to_id(_input_audio_device),
                                           name_to_id(_output_audio_device),
                                           _samplerate,
                                           _samples_per_period);
 
+#else
+       err = _pcmio->open_callback_stream(name_to_id(_input_audio_device),
+                                          name_to_id(_output_audio_device),
+                                          _samplerate,
+                                          _samples_per_period,
+                                          portaudio_callback,
+                                          this);
+
+#endif
+
        // reintepret Portaudio error messages
        switch (err) {
        case paNoError:
@@ -557,13 +579,92 @@ PortAudioBackend::_start (bool for_latency_measurement)
        _run = true;
        _port_change_flag = false;
 
+#ifdef USE_BLOCKING_API
        if (!start_blocking_process_thread()) {
                return ProcessThreadStartError;
        }
+#else
+       if (_pcmio->start_stream() != paNoError) {
+               DEBUG_AUDIO("Unable to start stream\n");
+               return AudioDeviceOpenError;
+       }
+
+       if (!start_freewheel_process_thread()) {
+               DEBUG_AUDIO("Unable to start freewheel thread\n");
+               stop();
+               return ProcessThreadStartError;
+       }
+#endif
 
        return NoError;
 }
 
+int
+PortAudioBackend::portaudio_callback(const void* input,
+                                     void* output,
+                                     unsigned long frame_count,
+                                     const PaStreamCallbackTimeInfo* time_info,
+                                     PaStreamCallbackFlags status_flags,
+                                     void* user_data)
+{
+       PortAudioBackend* pa_backend = static_cast<PortAudioBackend*>(user_data);
+
+       if (!pa_backend->process_callback((const float*)input,
+                                         (float*)output,
+                                         frame_count,
+                                         time_info,
+                                         status_flags)) {
+               return paAbort;
+       }
+       return paContinue;
+}
+
+bool
+PortAudioBackend::process_callback(const float* input,
+                                   float* output,
+                                   uint32_t frame_count,
+                                   const PaStreamCallbackTimeInfo* timeInfo,
+                                   PaStreamCallbackFlags statusFlags)
+{
+       _active = true;
+
+       m_dsp_calc.set_start_timestamp_us (PBD::get_microseconds());
+
+       if (_run && _freewheel && !_freewheel_ack) {
+               // acknowledge freewheeling; hand-over thread ID
+               pthread_mutex_lock (&m_freewheel_mutex);
+               if (_freewheel) {
+                       DEBUG_AUDIO("Setting _freewheel_ack = true;\n");
+                       _freewheel_ack = true;
+               }
+               DEBUG_AUDIO("Signalling freewheel thread\n");
+               pthread_cond_signal (&m_freewheel_signal);
+               pthread_mutex_unlock (&m_freewheel_mutex);
+       }
+
+       if (statusFlags & paInputUnderflow ||
+               statusFlags & paInputOverflow ||
+               statusFlags & paOutputUnderflow ||
+               statusFlags & paOutputOverflow ) {
+               DEBUG_AUDIO("PortAudio: Xrun\n");
+               engine.Xrun();
+               return true;
+       }
+
+       if (!_run || _freewheel) {
+               memset(output, 0, frame_count * sizeof(float) * _system_outputs.size());
+               return true;
+       }
+
+       if (_reinit_thread_callback || m_main_thread != pthread_self()) {
+               _reinit_thread_callback = false;
+               m_main_thread = pthread_self();
+               AudioEngine::thread_init_callback (this);
+       }
+
+       return blocking_process_main (input, output);
+}
+
 bool
 PortAudioBackend::start_blocking_process_thread ()
 {
@@ -618,15 +719,136 @@ PortAudioBackend::stop ()
 
        _run = false;
 
+#ifdef USE_BLOCKING_API
        if (!stop_blocking_process_thread ()) {
                return -1;
        }
+#else
+       _pcmio->close_stream ();
+       _active = false;
+
+       if (!stop_freewheel_process_thread ()) {
+               return -1;
+       }
+
+#endif
 
        unregister_ports();
 
        return (_active == false) ? 0 : -1;
 }
 
+static void* freewheel_thread(void* arg)
+{
+       PortAudioBackend* d = static_cast<PortAudioBackend*>(arg);
+       d->freewheel_process_thread ();
+       pthread_exit (0);
+       return 0;
+}
+
+bool
+PortAudioBackend::start_freewheel_process_thread ()
+{
+       if (pthread_create(&m_pthread_freewheel, NULL, freewheel_thread, this)) {
+               DEBUG_AUDIO("Failed to create main audio thread\n");
+               return false;
+       }
+
+       int timeout = 5000;
+       while (!m_freewheel_thread_active && --timeout > 0) { Glib::usleep (1000); }
+
+       if (timeout == 0 || !m_freewheel_thread_active) {
+               DEBUG_AUDIO("Failed to start freewheel thread\n");
+               return false;
+       }
+       return true;
+}
+
+bool
+PortAudioBackend::stop_freewheel_process_thread ()
+{
+       void *status;
+
+       if (!m_freewheel_thread_active) {
+               return true;
+       }
+
+       DEBUG_AUDIO("Signaling freewheel thread to stop\n");
+
+       pthread_mutex_lock (&m_freewheel_mutex);
+       pthread_cond_signal (&m_freewheel_signal);
+       pthread_mutex_unlock (&m_freewheel_mutex);
+
+       if (pthread_join (m_pthread_freewheel, &status) != 0) {
+               DEBUG_AUDIO("Failed to stop freewheel thread\n");
+               return false;
+       }
+
+       return true;
+}
+
+void*
+PortAudioBackend::freewheel_process_thread()
+{
+       m_freewheel_thread_active = true;
+
+       bool first_run = false;
+
+       pthread_mutex_lock (&m_freewheel_mutex);
+
+       while(_run) {
+               // check if we should run,
+               if (_freewheeling != _freewheel) {
+                       if (!_freewheeling) {
+                               DEBUG_AUDIO("Leaving freewheel\n");
+                               _freewheel = false; // first mark as disabled
+                               _reinit_thread_callback = true; // hand over _main_thread
+                               _freewheel_ack = false; // prepare next handshake
+                               _midiio->set_enabled(true);
+                       } else {
+                               first_run = true;
+                               _freewheel = true;
+                       }
+               }
+
+               if (!_freewheel || !_freewheel_ack) {
+                       // wait for a change, we use a timed wait to
+                       // terminate early in case some error sets _run = 0
+                       struct timeval tv;
+                       struct timespec ts;
+                       gettimeofday (&tv, NULL);
+                       ts.tv_sec = tv.tv_sec + 3;
+                       ts.tv_nsec = 0;
+                       DEBUG_AUDIO("Waiting for freewheel change\n");
+                       pthread_cond_timedwait (&m_freewheel_signal, &m_freewheel_mutex, &ts);
+                       continue;
+               }
+
+               if (first_run) {
+                       // tell the engine we're ready to GO.
+                       engine.freewheel_callback (_freewheeling);
+                       first_run = false;
+                       m_main_thread = pthread_self();
+                       AudioEngine::thread_init_callback (this);
+                       _midiio->set_enabled(false);
+               }
+
+               if (!blocking_process_freewheel()) {
+                       break;
+               }
+       }
+
+       pthread_mutex_unlock (&m_freewheel_mutex);
+
+       m_freewheel_thread_active = false;
+
+       if (_run) {
+               // engine.process_callback() returner error
+               engine.halted_callback("CoreAudio Freehweeling aborted.");
+       }
+       return 0;
+}
+
 int
 PortAudioBackend::freewheel (bool onoff)
 {
@@ -634,6 +856,11 @@ PortAudioBackend::freewheel (bool onoff)
                return 0;
        }
        _freewheeling = onoff;
+
+       if (0 == pthread_mutex_trylock (&m_freewheel_mutex)) {
+               pthread_cond_signal (&m_freewheel_signal);
+               pthread_mutex_unlock (&m_freewheel_mutex);
+       }
        return 0;
 }
 
@@ -803,9 +1030,15 @@ PortAudioBackend::join_process_threads ()
 bool
 PortAudioBackend::in_process_thread ()
 {
+#ifdef USE_BLOCKING_API
        if (pthread_equal (_main_blocking_thread, pthread_self()) != 0) {
                return true;
        }
+#else
+       if (pthread_equal (m_main_thread, pthread_self()) != 0) {
+               return true;
+       }
+#endif
 
        for (std::vector<pthread_t>::const_iterator i = _threads.begin (); i != _threads.end (); ++i)
        {
index b0d148701d1f8383eb5f98e8c5a49b96b6d014b8..0433f120ee7b4623e1d4df1b01e83b2ac74545f3 100644 (file)
@@ -320,6 +320,8 @@ class PortAudioBackend : public AudioBackend {
 
                void* main_blocking_process_thread ();
 
+               void* freewheel_process_thread ();
+
        private: // Methods
                bool start_blocking_process_thread ();
                bool stop_blocking_process_thread ();
@@ -334,6 +336,22 @@ class PortAudioBackend : public AudioBackend {
                bool engine_halted ();
                bool running ();
 
+               static int portaudio_callback(const void* input,
+                                         void* output,
+                                         unsigned long frameCount,
+                                         const PaStreamCallbackTimeInfo* timeInfo,
+                                         PaStreamCallbackFlags statusFlags,
+                                         void* userData);
+
+               bool process_callback(const float* input,
+                                 float* output,
+                                 uint32_t frame_count,
+                                 const PaStreamCallbackTimeInfo* timeInfo,
+                                 PaStreamCallbackFlags statusFlags);
+
+               bool start_freewheel_process_thread ();
+               bool stop_freewheel_process_thread ();
+
                static bool set_mmcss_pro_audio (HANDLE* task_handle);
                static bool reset_mmcss (HANDLE task_handle);
 
@@ -346,10 +364,17 @@ class PortAudioBackend : public AudioBackend {
                bool  _active; /* is running, process thread */
                bool  _freewheel;
                bool  _freewheeling;
+               bool  _freewheel_ack;
+               bool  _reinit_thread_callback;
                bool  _measure_latency;
 
                ARDOUR::DSPLoadCalculator m_dsp_calc;
 
+               bool m_freewheel_thread_active;
+
+               pthread_mutex_t m_freewheel_mutex;
+               pthread_cond_t m_freewheel_signal;
+
                uint64_t m_cycle_count;
                uint64_t m_total_deviation_us;
                uint64_t m_max_deviation_us;
@@ -387,6 +412,12 @@ class PortAudioBackend : public AudioBackend {
                /* blocking thread */
                pthread_t _main_blocking_thread;
 
+               /* main thread in callback mode(or fw thread when running) */
+               pthread_t m_main_thread;
+
+               /* freewheel thread in callback mode */
+               pthread_t m_pthread_freewheel;
+
                /* process threads */
                static void* portaudio_process_thread (void *);
                std::vector<pthread_t> _threads;
index 678a59f4f3f5a5820dbe71f13895d2011be78bd4..f3276cc7eaa4e37d27b4ed5457bd5ce59382f2c4 100644 (file)
@@ -744,6 +744,49 @@ PortAudioIO::pre_stream_open(int device_input,
        return paNoError;
 }
 
+PaErrorCode
+PortAudioIO::open_callback_stream(int device_input,
+                                  int device_output,
+                                  double sample_rate,
+                                  uint32_t samples_per_period,
+                                  PaStreamCallback* callback,
+                                  void* data)
+{
+       PaStreamParameters inputParam;
+       PaStreamParameters outputParam;
+
+       PaErrorCode error_code =
+           pre_stream_open(device_input, inputParam, device_output, outputParam);
+
+       if (error_code != paNoError) return error_code;
+
+       PaError err = paNoError;
+
+       DEBUG_AUDIO ("Open Callback Stream\n");
+
+       err = Pa_OpenStream(&_stream,
+                           _capture_channels > 0 ? &inputParam : NULL,
+                           _playback_channels > 0 ? &outputParam : NULL,
+                           sample_rate,
+                           samples_per_period,
+                           paDitherOff,
+                           callback,
+                           data);
+
+       if (err != paNoError) {
+               DEBUG_AUDIO ("PortAudio failed to start stream.\n");
+               return paInternalError;
+       }
+
+       if (!set_sample_rate_and_latency_from_stream()) {
+               DEBUG_AUDIO ("PortAudio failed to query stream information.\n");
+               close_stream();
+               return paInternalError;
+       }
+
+       return paNoError;
+}
+
 PaErrorCode
 PortAudioIO::open_blocking_stream(int device_input,
                                   int device_output,
index bc0da78df89d77955492e705cb99a9ecd525cbb8..e08490d0b574bc3ce7a34394128b1b4d204e8b82 100644 (file)
@@ -70,9 +70,17 @@ public:
        void launch_control_app (int device_id);
 
        PaErrorCode open_blocking_stream(int device_input,
-                                      int device_output,
-                                      double sample_rate,
-                                      uint32_t samples_per_period);
+                                        int device_output,
+                                        double sample_rate,
+                                        uint32_t samples_per_period);
+
+       PaErrorCode open_callback_stream(int device_input,
+                                        int device_output,
+                                        double sample_rate,
+                                        uint32_t samples_per_period,
+                                        PaStreamCallback* callback,
+                                        void* data);
+
        PaErrorCode start_stream(void);
 
        PaErrorCode close_stream(void);
index 0d679a156368d82aac1bd504f1f08e04bae1d56b..4f3395f93e7d80bded5cf314c9521c4e7b9a5e41 100644 (file)
@@ -36,5 +36,6 @@ def build(bld):
     obj.install_path  = os.path.join(bld.env['LIBDIR'], 'backends')
     obj.defines = ['PACKAGE="' + I18N_PACKAGE + '"',
                    'ARDOURBACKEND_DLL_EXPORTS',
-                   'USE_MMCSS_THREAD_PRIORITIES'
+                   'USE_MMCSS_THREAD_PRIORITIES',
+                   'USE_BLOCKING_API'
                   ]