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.
This commit is contained in:
parent
6c85f976de
commit
f1b336b01e
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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'
|
||||
]
|
||||
|
|
Loading…
Reference in New Issue
Block a user