#include <sys/time.h>
#endif
+#ifdef COMPILER_MINGW
+#include <sys/time.h>
+#endif
+
#include <glibmm.h>
#include "portaudio_backend.h"
, _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)
- , m_max_deviation_us(0)
+ , _cycle_count(0)
+ , _total_deviation_us(0)
+ , _max_deviation_us(0)
, _input_audio_device("")
, _output_audio_device("")
, _midi_driver_option(get_standard_device_name(DeviceNone))
{
_instance_name = s_instance_name;
pthread_mutex_init (&_port_callback_mutex, 0);
+ pthread_mutex_init (&_freewheel_mutex, 0);
+ pthread_cond_init (&_freewheel_signal, 0);
_pcmio = new PortAudioIO ();
_midiio = new WinMMEMidiIO ();
delete _midiio; _midiio = 0;
pthread_mutex_destroy (&_port_callback_mutex);
+ pthread_mutex_destroy (&_freewheel_mutex);
+ pthread_cond_destroy (&_freewheel_signal);
}
/* AUDIOBACKEND API */
/* State Control */
-static void * pthread_process (void *arg)
+static void * blocking_thread_func (void *arg)
{
PortAudioBackend *d = static_cast<PortAudioBackend *>(arg);
- d->main_blocking_process_thread ();
+ d->blocking_process_thread ();
pthread_exit (0);
return 0;
}
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:
_midiio->start(); // triggers port discovery, callback coremidi_rediscover()
}
- m_cycle_timer.set_samplerate(_samplerate);
- m_cycle_timer.set_samples_per_cycle(_samples_per_period);
+ _cycle_timer.set_samplerate(_samplerate);
+ _cycle_timer.set_samples_per_cycle(_samples_per_period);
- m_dsp_calc.set_max_time_us (m_cycle_timer.get_length_us());
+ _dsp_calc.set_max_time_us (_cycle_timer.get_length_us());
DEBUG_MIDI ("Registering MIDI ports\n");
_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;
+
+ _dsp_calc.set_start_timestamp_us (PBD::get_microseconds());
+
+ if (_run && _freewheel && !_freewheel_ack) {
+ // acknowledge freewheeling; hand-over thread ID
+ pthread_mutex_lock (&_freewheel_mutex);
+ if (_freewheel) {
+ DEBUG_AUDIO("Setting _freewheel_ack = true;\n");
+ _freewheel_ack = true;
+ }
+ DEBUG_AUDIO("Signalling freewheel thread\n");
+ pthread_cond_signal (&_freewheel_signal);
+ pthread_mutex_unlock (&_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;
+ }
+
+ bool in_main_thread = pthread_equal(_main_thread, pthread_self());
+
+ if (_reinit_thread_callback || !in_main_thread) {
+ _reinit_thread_callback = false;
+ _main_thread = pthread_self();
+ AudioEngine::thread_init_callback (this);
+ }
+
+ return blocking_process_main (input, output);
+}
+
bool
PortAudioBackend::start_blocking_process_thread ()
{
if (_realtime_pthread_create (SCHED_FIFO, -20, 100000,
- &_main_blocking_thread, pthread_process, this))
+ &_main_blocking_thread, blocking_thread_func, this))
{
- if (pthread_create (&_main_blocking_thread, NULL, pthread_process, this))
+ if (pthread_create (&_main_blocking_thread, NULL, blocking_thread_func, this))
{
DEBUG_AUDIO("Failed to create main audio thread\n");
_run = false;
_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(&_pthread_freewheel, NULL, freewheel_thread, this)) {
+ DEBUG_AUDIO("Failed to create main audio thread\n");
+ return false;
+ }
+
+ int timeout = 5000;
+ while (!_freewheel_thread_active && --timeout > 0) { Glib::usleep (1000); }
+
+ if (timeout == 0 || !_freewheel_thread_active) {
+ DEBUG_AUDIO("Failed to start freewheel thread\n");
+ return false;
+ }
+ return true;
+}
+
+bool
+PortAudioBackend::stop_freewheel_process_thread ()
+{
+ void *status;
+
+ if (!_freewheel_thread_active) {
+ return true;
+ }
+
+ DEBUG_AUDIO("Signaling freewheel thread to stop\n");
+
+ pthread_mutex_lock (&_freewheel_mutex);
+ pthread_cond_signal (&_freewheel_signal);
+ pthread_mutex_unlock (&_freewheel_mutex);
+
+ if (pthread_join (_pthread_freewheel, &status) != 0) {
+ DEBUG_AUDIO("Failed to stop freewheel thread\n");
+ return false;
+ }
+
+ return true;
+}
+
+void*
+PortAudioBackend::freewheel_process_thread()
+{
+ _freewheel_thread_active = true;
+
+ bool first_run = false;
+
+ pthread_mutex_lock (&_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 (&_freewheel_signal, &_freewheel_mutex, &ts);
+ continue;
+ }
+
+ if (first_run) {
+ // tell the engine we're ready to GO.
+ engine.freewheel_callback (_freewheeling);
+ first_run = false;
+ _main_thread = pthread_self();
+ AudioEngine::thread_init_callback (this);
+ _midiio->set_enabled(false);
+ }
+
+ if (!blocking_process_freewheel()) {
+ break;
+ }
+ }
+
+ pthread_mutex_unlock (&_freewheel_mutex);
+
+ _freewheel_thread_active = false;
+
+ if (_run) {
+ // engine.process_callback() returner error
+ engine.halted_callback("CoreAudio Freehweeling aborted.");
+ }
+ return 0;
+}
+
int
PortAudioBackend::freewheel (bool onoff)
{
return 0;
}
_freewheeling = onoff;
+
+ if (0 == pthread_mutex_trylock (&_freewheel_mutex)) {
+ pthread_cond_signal (&_freewheel_signal);
+ pthread_mutex_unlock (&_freewheel_mutex);
+ }
return 0;
}
if (!_active || !_run || _freewheeling || _freewheel) {
return 0;
}
- if (!m_cycle_timer.valid()) {
+ if (!_cycle_timer.valid()) {
return 0;
}
- return m_cycle_timer.samples_since_cycle_start (PBD::get_microseconds());
+ return _cycle_timer.samples_since_cycle_start (PBD::get_microseconds());
}
int
bool
PortAudioBackend::in_process_thread ()
{
+#ifdef USE_BLOCKING_API
if (pthread_equal (_main_blocking_thread, pthread_self()) != 0) {
return true;
}
+#else
+ if (pthread_equal (_main_thread, pthread_self()) != 0) {
+ return true;
+ }
+#endif
for (std::vector<pthread_t>::const_iterator i = _threads.begin (); i != _threads.end (); ++i)
{
const uint32_t a_ins = _n_inputs;
const uint32_t a_out = _n_outputs;
- // XXX PA reported stream latencies don't match measurements
- const uint32_t portaudio_reported_input_latency = _samples_per_period ; // _pcmio->capture_latency();
- const uint32_t portaudio_reported_output_latency = /* _samples_per_period + */ _pcmio->playback_latency();
+ uint32_t capture_latency = 0;
+ uint32_t playback_latency = 0;
+
+ // guard against erroneous latency values
+ if (_pcmio->capture_latency() > _samples_per_period) {
+ capture_latency = _pcmio->capture_latency() - _samples_per_period;
+ }
+ if (_pcmio->playback_latency() > _samples_per_period) {
+ playback_latency = _pcmio->playback_latency() - _samples_per_period;
+ }
/* audio ports */
- lr.min = lr.max = portaudio_reported_input_latency + (_measure_latency ? 0 : _systemic_audio_input_latency);
+ lr.min = lr.max = capture_latency + (_measure_latency ? 0 : _systemic_audio_input_latency);
for (uint32_t i = 0; i < a_ins; ++i) {
char tmp[64];
snprintf(tmp, sizeof(tmp), "system:capture_%d", i+1);
_system_inputs.push_back (audio_port);
}
- lr.min = lr.max = portaudio_reported_output_latency + (_measure_latency ? 0 : _systemic_audio_output_latency);
+ lr.min = lr.max = playback_latency + (_measure_latency ? 0 : _systemic_audio_output_latency);
for (uint32_t i = 0; i < a_out; ++i) {
char tmp[64];
snprintf(tmp, sizeof(tmp), "system:playback_%d", i+1);
void *
-PortAudioBackend::main_blocking_process_thread ()
+PortAudioBackend::blocking_process_thread ()
{
AudioEngine::thread_init_callback (this);
_active = true;
int64_t min_elapsed_us = 1000000;
int64_t max_elapsed_us = 0;
- m_dsp_calc.set_start_timestamp_us (PBD::get_microseconds());
+ _dsp_calc.set_start_timestamp_us (PBD::get_microseconds());
i = 0;
/* Copy input audio data into input port buffers */
_samples_per_period * sizeof(Sample));
}
- m_last_cycle_start = m_cycle_timer.get_start();
- m_cycle_timer.reset_start(PBD::get_microseconds());
- m_cycle_count++;
+ _last_cycle_start = _cycle_timer.get_start();
+ _cycle_timer.reset_start(PBD::get_microseconds());
+ _cycle_count++;
- uint64_t cycle_diff_us = (m_cycle_timer.get_start() - m_last_cycle_start);
- int64_t deviation_us = (cycle_diff_us - m_cycle_timer.get_length_us());
- m_total_deviation_us += ::llabs(deviation_us);
- m_max_deviation_us =
- std::max(m_max_deviation_us, (uint64_t)::llabs(deviation_us));
+ uint64_t cycle_diff_us = (_cycle_timer.get_start() - _last_cycle_start);
+ int64_t deviation_us = (cycle_diff_us - _cycle_timer.get_length_us());
+ _total_deviation_us += ::llabs(deviation_us);
+ _max_deviation_us =
+ std::max(_max_deviation_us, (uint64_t)::llabs(deviation_us));
- if ((m_cycle_count % 1000) == 0) {
- uint64_t mean_deviation_us = m_total_deviation_us / m_cycle_count;
+ if ((_cycle_count % 1000) == 0) {
+ uint64_t mean_deviation_us = _total_deviation_us / _cycle_count;
DEBUG_TIMING(string_compose("Mean avg cycle deviation: %1(ms), max %2(ms)\n",
mean_deviation_us * 1e-3,
- m_max_deviation_us * 1e-3));
+ _max_deviation_us * 1e-3));
}
- if (::llabs(deviation_us) > m_cycle_timer.get_length_us()) {
+ if (::llabs(deviation_us) > _cycle_timer.get_length_us()) {
DEBUG_TIMING(
string_compose("time between process(ms): %1, Est(ms): %2, Dev(ms): %3\n",
cycle_diff_us * 1e-3,
- m_cycle_timer.get_length_us() * 1e-3,
+ _cycle_timer.get_length_us() * 1e-3,
deviation_us * 1e-3));
}
_processed_samples += _samples_per_period;
/* calculate DSP load */
- m_dsp_calc.set_stop_timestamp_us (PBD::get_microseconds());
- _dsp_load = m_dsp_calc.get_dsp_load();
+ _dsp_calc.set_stop_timestamp_us (PBD::get_microseconds());
+ _dsp_load = _dsp_calc.get_dsp_load();
DEBUG_TIMING(string_compose("DSP Load: %1\n", _dsp_load));
- max_elapsed_us = std::max(m_dsp_calc.elapsed_time_us(), max_elapsed_us);
- min_elapsed_us = std::min(m_dsp_calc.elapsed_time_us(), min_elapsed_us);
- if ((m_cycle_count % 1000) == 0) {
+ max_elapsed_us = std::max(_dsp_calc.elapsed_time_us(), max_elapsed_us);
+ min_elapsed_us = std::min(_dsp_calc.elapsed_time_us(), min_elapsed_us);
+ if ((_cycle_count % 1000) == 0) {
DEBUG_TIMING(string_compose("Elapsed process time(usecs) max: %1, min: %2\n",
max_elapsed_us,
min_elapsed_us));
uint8_t data[256];
size_t size = sizeof(data);
while (_midiio->dequeue_input_event(i,
- m_cycle_timer.get_start(),
- m_cycle_timer.get_next_start(),
+ _cycle_timer.get_start(),
+ _cycle_timer.get_next_start(),
timestamp,
data,
size)) {
- sample_offset = m_cycle_timer.samples_since_cycle_start(timestamp);
+ sample_offset = _cycle_timer.samples_since_cycle_start(timestamp);
midi_event_put(mbuf, sample_offset, data, size);
DEBUG_MIDI(string_compose("Dequeuing incoming MIDI data for device: %1 "
"sample_offset: %2 timestamp: %3, size: %4\n",
for (PortMidiBuffer::const_iterator mit = src->begin(); mit != src->end();
++mit) {
uint64_t timestamp =
- m_cycle_timer.timestamp_from_sample_offset((*mit)->timestamp());
+ _cycle_timer.timestamp_from_sample_offset((*mit)->timestamp());
DEBUG_MIDI(string_compose("Queuing outgoing MIDI data for device: "
"%1 sample_offset: %2 timestamp: %3, size: %4\n",
_midiio->get_outputs()[i]->name(),