From f1b336b01e28f499dcd73a1fd65236efe118eb7d Mon Sep 17 00:00:00 2001 From: Tim Mayberry Date: Fri, 28 Aug 2015 08:53:10 +1000 Subject: Add support for callback API to portaudio backend but keep blocking API as default Don't use the callback API for now until further and wider testing. --- libs/backends/portaudio/portaudio_backend.cc | 233 +++++++++++++++++++++++++++ libs/backends/portaudio/portaudio_backend.h | 31 ++++ libs/backends/portaudio/portaudio_io.cc | 43 +++++ libs/backends/portaudio/portaudio_io.h | 14 +- libs/backends/portaudio/wscript | 3 +- 5 files changed, 320 insertions(+), 4 deletions(-) (limited to 'libs/backends/portaudio') diff --git a/libs/backends/portaudio/portaudio_backend.cc b/libs/backends/portaudio/portaudio_backend.cc index 3329deee7e..b6736ca1b0 100644 --- a/libs/backends/portaudio/portaudio_backend.cc +++ b/libs/backends/portaudio/portaudio_backend.cc @@ -24,6 +24,10 @@ #include #endif +#ifdef COMPILER_MINGW +#include +#endif + #include #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(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(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::const_iterator i = _threads.begin (); i != _threads.end (); ++i) { diff --git a/libs/backends/portaudio/portaudio_backend.h b/libs/backends/portaudio/portaudio_backend.h index b0d148701d..0433f120ee 100644 --- a/libs/backends/portaudio/portaudio_backend.h +++ b/libs/backends/portaudio/portaudio_backend.h @@ -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 _threads; diff --git a/libs/backends/portaudio/portaudio_io.cc b/libs/backends/portaudio/portaudio_io.cc index 678a59f4f3..f3276cc7ea 100644 --- a/libs/backends/portaudio/portaudio_io.cc +++ b/libs/backends/portaudio/portaudio_io.cc @@ -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, diff --git a/libs/backends/portaudio/portaudio_io.h b/libs/backends/portaudio/portaudio_io.h index bc0da78df8..e08490d0b5 100644 --- a/libs/backends/portaudio/portaudio_io.h +++ b/libs/backends/portaudio/portaudio_io.h @@ -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); diff --git a/libs/backends/portaudio/wscript b/libs/backends/portaudio/wscript index 0d679a1563..4f3395f93e 100644 --- a/libs/backends/portaudio/wscript +++ b/libs/backends/portaudio/wscript @@ -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' ] -- cgit v1.2.3