summaryrefslogtreecommitdiff
path: root/libs/backends/portaudio
diff options
context:
space:
mode:
authorTim Mayberry <mojofunk@gmail.com>2015-08-28 08:53:10 +1000
committerTim Mayberry <mojofunk@gmail.com>2015-11-19 10:23:26 +1000
commitf1b336b01e28f499dcd73a1fd65236efe118eb7d (patch)
treec2437a4f8966396a768945e5376d6713b5b05845 /libs/backends/portaudio
parent6c85f976dee68f79e89fc211092fad6fde4b1bba (diff)
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.
Diffstat (limited to 'libs/backends/portaudio')
-rw-r--r--libs/backends/portaudio/portaudio_backend.cc233
-rw-r--r--libs/backends/portaudio/portaudio_backend.h31
-rw-r--r--libs/backends/portaudio/portaudio_io.cc43
-rw-r--r--libs/backends/portaudio/portaudio_io.h14
-rw-r--r--libs/backends/portaudio/wscript3
5 files changed, 320 insertions, 4 deletions
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 <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)
{
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<pthread_t> _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
@@ -745,6 +745,49 @@ PortAudioIO::pre_stream_open(int device_input,
}
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,
double sample_rate,
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'
]