diff --git a/libs/backends/coreaudio/coreaudio_backend.cc b/libs/backends/coreaudio/coreaudio_backend.cc index d69f512257..315c10adaf 100644 --- a/libs/backends/coreaudio/coreaudio_backend.cc +++ b/libs/backends/coreaudio/coreaudio_backend.cc @@ -105,6 +105,8 @@ CoreAudioBackend::CoreAudioBackend (AudioEngine& e, AudioBackendInfo& info) _instance_name = s_instance_name; pthread_mutex_init (&_port_callback_mutex, 0); pthread_mutex_init (&_process_callback_mutex, 0); + pthread_mutex_init (&_freewheel_mutex, 0); + pthread_cond_init (&_freewheel_signal, 0); _pcmio = new CoreAudioPCM (); _midiio = new CoreMidiIo (); @@ -119,6 +121,8 @@ CoreAudioBackend::~CoreAudioBackend () delete _midiio; _midiio = 0; pthread_mutex_destroy (&_port_callback_mutex); pthread_mutex_destroy (&_process_callback_mutex); + pthread_mutex_destroy (&_freewheel_mutex); + pthread_cond_destroy (&_freewheel_signal); } /* AUDIOBACKEND API */ @@ -502,7 +506,7 @@ CoreAudioBackend::_start (bool for_latency_measurement) if (_midi_driver_option == _("CoreMidi")) { _midiio->set_enabled(true); _midiio->set_port_changed_callback(midi_port_change, this); - _midiio->start(); + _midiio->start(); // triggers port discovery, callback coremidi_rediscover() } if (register_system_audio_ports()) { @@ -520,8 +524,6 @@ CoreAudioBackend::_start (bool for_latency_measurement) return -1; } - engine.reconnect_ports (); - if (pthread_create (&_freeewheel_thread, NULL, pthread_freewheel, this)) { PBD::error << _("CoreAudioBackend: failed to create process thread.") << endmsg; @@ -553,8 +555,16 @@ CoreAudioBackend::_start (bool for_latency_measurement) _run = false; return -1; } - _preinit = false; + + engine.reconnect_ports (); + + // force an initial registration_callback() & latency re-compute + _port_change_flag = true; + pre_process (); + + // all systems go. _pcmio->set_xrun_callback (xrun_callback_ptr, this); + _preinit = false; return 0; } @@ -572,6 +582,10 @@ CoreAudioBackend::stop () _midiio->set_port_changed_callback(NULL, NULL); _midiio->stop(); + pthread_mutex_lock (&_freewheel_mutex); + pthread_cond_signal (&_freewheel_signal); + pthread_mutex_unlock (&_freewheel_mutex); + if (pthread_join (_freeewheel_thread, &status)) { PBD::error << _("CoreAudioBackend: failed to terminate.") << endmsg; return -1; @@ -592,6 +606,11 @@ CoreAudioBackend::freewheel (bool onoff) return 0; } _freewheeling = onoff; + // wake up freewheeling thread + if (0 == pthread_mutex_trylock (&_freewheel_mutex)) { + pthread_cond_signal (&_freewheel_signal); + pthread_mutex_unlock (&_freewheel_mutex); + } return 0; } @@ -882,8 +901,8 @@ CoreAudioBackend::register_system_audio_ports() { LatencyRange lr; - const int a_ins = _n_inputs; - const int a_out = _n_outputs; + const uint32_t a_ins = _n_inputs; + const uint32_t a_out = _n_outputs; const uint32_t coreaudio_reported_input_latency = _pcmio->get_latency(name_to_id(_audio_device), true); const uint32_t coreaudio_reported_output_latency = _pcmio->get_latency(name_to_id(_audio_device), false); @@ -896,7 +915,7 @@ CoreAudioBackend::register_system_audio_ports() /* audio ports */ lr.min = lr.max = coreaudio_reported_input_latency + (_measure_latency ? 0 : _systemic_audio_input_latency); - for (int i = 0; i < a_ins; ++i) { + for (uint32_t i = 0; i < a_ins; ++i) { char tmp[64]; snprintf(tmp, sizeof(tmp), "system:capture_%s", _pcmio->cached_port_name(i, true).c_str()); PortHandle p = add_port(std::string(tmp), DataType::AUDIO, static_cast(IsOutput | IsPhysical | IsTerminal)); @@ -906,7 +925,7 @@ CoreAudioBackend::register_system_audio_ports() } lr.min = lr.max = coreaudio_reported_output_latency + (_measure_latency ? 0 : _systemic_audio_output_latency); - for (int i = 0; i < a_out; ++i) { + for (uint32_t i = 0; i < a_out; ++i) { char tmp[64]; snprintf(tmp, sizeof(tmp), "system:playback_%s", _pcmio->cached_port_name(i, false).c_str()); PortHandle p = add_port(std::string(tmp), DataType::AUDIO, static_cast(IsInput | IsPhysical | IsTerminal)); @@ -1357,7 +1376,7 @@ CoreAudioBackend::get_buffer (PortEngine::PortHandle port, pframes_t nframes) } void -CoreAudioBackend::post_process () +CoreAudioBackend::pre_process () { bool connections_changed = false; bool ports_changed = false; @@ -1394,38 +1413,63 @@ CoreAudioBackend::freewheel_thread () { _active_fw = true; bool first_run = false; + /* Freewheeling - use for export. The first call to + * engine.process_callback() after engine.freewheel_callback will + * if the first export cycle. + * For reliable precise export timing, the calls need to be in sync. + * + * Furthermore we need to make sure the registered process thread + * is correct. + * + * _freewheeling = GUI thread state as set by ::freewheel() + * _freewheel = in sync here (export thread) + */ + pthread_mutex_lock (&_freewheel_mutex); while (_run) { // check if we should run, if (_freewheeling != _freewheel) { if (!_freewheeling) { - // handshake w/ coreaudio - _reinit_thread_callback = true; - _freewheel_ack = false; + // prepare leaving freewheeling mode + _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; } - - engine.freewheel_callback (_freewheeling); - first_run = true; - _freewheel = _freewheeling; } if (!_freewheel || !_freewheel_ack) { - // TODO use a pthread sync/sleep - Glib::usleep(200000); + // 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; + 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); } - post_process(); + // process port updates first in every cycle. + pre_process(); + // prevent coreaudio device changes pthread_mutex_lock (&_process_callback_mutex); - // Freewheelin' + + /* Freewheelin' */ + + // clear input buffers for (std::vector::const_iterator it = _system_inputs.begin (); it != _system_inputs.end (); ++it) { memset ((*it)->get_buffer (_samples_per_period), 0, _samples_per_period * sizeof (Sample)); } @@ -1443,9 +1487,12 @@ CoreAudioBackend::freewheel_thread () Glib::usleep (100); // don't hog cpu } + pthread_mutex_unlock (&_freewheel_mutex); + _active_fw = false; if (_run) { + // engine.process_callback() returner error engine.halted_callback("CoreAudio Freehweeling aborted."); } return 0; @@ -1460,14 +1507,21 @@ CoreAudioBackend::process_callback () _active_ca = true; if (_run && _freewheel && !_freewheel_ack) { - _freewheel_ack = true; + // acknowledge freewheeling; hand-over thread ID + pthread_mutex_lock (&_freewheel_mutex); + if (_freewheel) _freewheel_ack = true; + pthread_cond_signal (&_freewheel_signal); + pthread_mutex_unlock (&_freewheel_mutex); } if (!_run || _freewheel || _preinit) { + // NB if we return 1, the output is + // zeroed by the coreaudio callback return 1; } if (pthread_mutex_trylock (&_process_callback_mutex)) { + // block while devices are added/removed return 1; } @@ -1475,13 +1529,10 @@ CoreAudioBackend::process_callback () _reinit_thread_callback = false; _main_thread = pthread_self(); AudioEngine::thread_init_callback (this); - - manager.registration_callback(); - manager.graph_order_callback(); } /* port-connection change */ - post_process(); + pre_process(); const uint32_t n_samples = _pcmio->n_samples(); diff --git a/libs/backends/coreaudio/coreaudio_backend.h b/libs/backends/coreaudio/coreaudio_backend.h index ecc7107c71..542d1ac746 100644 --- a/libs/backends/coreaudio/coreaudio_backend.h +++ b/libs/backends/coreaudio/coreaudio_backend.h @@ -300,7 +300,7 @@ class CoreAudioBackend : public AudioBackend { void* get_buffer (PortHandle, pframes_t); void* freewheel_thread (); - void post_process (); + void pre_process (); void coremidi_rediscover (); private: @@ -317,8 +317,12 @@ class CoreAudioBackend : public AudioBackend { bool _freewheel_ack; bool _reinit_thread_callback; bool _measure_latency; + pthread_mutex_t _process_callback_mutex; + pthread_mutex_t _freewheel_mutex; + pthread_cond_t _freewheel_signal; + static std::vector _midi_options; static std::vector _audio_device_status; static std::vector _midi_device_status;