// ------------------------------------------------------------------------ // audioio_jack_manager.cpp: Manager for JACK client objects // Copyright (C) 2001-2004,2008,2009 Kai Vehmanen // // Attributes: // eca-style-version: 3 // // References: // http://jackit.sourceforge.net/ // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // ------------------------------------------------------------------------ #ifdef HAVE_CONFIG_H #include #endif #include /* std::count() */ #include #include #include #include /* gettimeofday() */ #include /* ETIMEDOUT */ #include #include #include #include #include #include "eca-engine.h" #include "eca-chainsetup.h" #include "eca-logger.h" #include /** * Enable and disable features */ /* Debug control flow */ // #define DEBUG_CFLOW /* Profile callback execution */ // #define PROFILE_CALLBACK_EXECUTION /** * Local macro definitions */ #ifdef DEBUG_CFLOW #define DEBUG_CFLOW_STATEMENT(x) (x) #else #define DEBUG_CFLOW_STATEMENT(x) ((void)0) #endif #ifdef PROFILE_CALLBACK_EXECUTION #define PROFILE_CE_STATEMENT(x) (x) static PROCEDURE_TIMER profile_callback_timer; #else #define PROFILE_CE_STATEMENT(x) ((void)0) #endif /** * Prototypes for static functions */ static int eca_jack_process_callback(jack_nframes_t nframes, void *arg); #if ECA_JACK_TRANSPORT_API >= 3 static int eca_jack_sync_callback(jack_transport_state_t state, jack_position_t *pos, void *arg); static void eca_jack_sync_start_seek_to(jack_transport_state_t state, jack_position_t *pos, void *arg); static void eca_jack_sync_start_live_seek_to(jack_transport_state_t state, jack_position_t *pos, void *arg); static void eca_jack_process_timebase_slave(jack_nframes_t nframes, void *arg); #endif static void eca_jack_process_engine_iteration(jack_nframes_t nframes, void *arg); static void eca_jack_process_mute(jack_nframes_t nframes, void* arg); #ifdef PROFILE_CALLBACK_EXECUTION static void eca_jack_process_profile_pre(void); static void eca_jack_process_profile_post(void); #endif static int eca_jack_bsize_cb(jack_nframes_t nframes, void *arg); static int eca_jack_srate_cb(jack_nframes_t nframes, void *arg); static void eca_jack_shutdown_cb(void *arg); static std::string eca_get_jack_port_item(const char **ports, int item); #include "audioio_jack_manager.h" using std::cerr; using std::endl; using std::list; using std::string; using std::vector; /** * Implementations of static functions */ /** * How many ecasound JACK manager instances * can run at the same time (affects connection * setup time in some situations). */ const int AUDIO_IO_JACK_MANAGER::instance_limit = 8; /** * Context help: * J = originates from JACK callback * E = ----- " ------- engine thread (exec()) * C = ----- " ------- control/client thread */ #if ECA_JACK_TRANSPORT_API >= 3 /** * JACK sync callback function. Called when someone has * issued a state change request. * * context: J-level-0 */ static int eca_jack_sync_callback(jack_transport_state_t state, jack_position_t *pos, void *arg) { // DEBUG_CFLOW_STATEMENT(cerr << endl << "eca_jack_SYNC: entering..."); AUDIO_IO_JACK_MANAGER* current = static_cast(arg); int result = 1; /* ready for rolling */ if (current->exit_request_rep == 1 || current->shutdown_request_rep == 1) { DEBUG_CFLOW_STATEMENT(cerr << "eca_jack_SYNC: after exit/shutdown!!!" << endl); return 0; } /* try to get the driver lock; if it fails or connection * is not fully establish, skip this processing cycle */ int ret = pthread_mutex_trylock(¤t->engine_mod_lock_rep); if (ret == 0) { SAMPLE_SPECS::sample_pos_t enginepos = current->engine_repp->current_position_in_samples(); /* 1. engine locked for editing, do not touch! */ if (current->engine_repp->is_locked_for_editing() == true) { DEBUG_CFLOW_STATEMENT(cerr << "current->engine_repp->is_locked_for_editing() == true\n"); result = 0; } /* 2. transport stopped */ else if (state == JackTransportStopped) { DEBUG_CFLOW_STATEMENT(cerr << "eca_jack_SYNC: JACK stopped" << endl); /* 2.1 engine at correct place; report success */ if (enginepos == pos->frame) { result = 1; } /* 2.2 only start seek if engine is not already at correct place */ else if (current->jackslave_seekahead_target_rep == -1) { DEBUG_CFLOW_STATEMENT(cerr << "eca_jack_SYNC: stopped - starting seek to " << pos->frame << "." << endl); eca_jack_sync_start_seek_to(state, pos, arg); } result = 0; } /* 3. transport starting (or looping, all these states are fine to us, as is the case where state info is not available at all) */ else if (state == JackTransportStarting) { DEBUG_CFLOW_STATEMENT(cerr << "eca_jack_SYNC: JACK starting" << endl); /* 3.1 engine at correct position */ if (enginepos == pos->frame) { /* 3.1.1 engine ready for process callback; return positive */ if (current->engine_repp->is_prepared() && current->engine_repp->is_running()) { DEBUG_CFLOW_STATEMENT(cerr << "eca_jack_SYNC: JACK running; correct position, engine running\n"); result = 1; current->jackslave_seekahead_target_rep = -1; } /* 3.1.2 engine not ready for process callback; request start */ else { current->start_request_rep++; current->engine_repp->command(ECA_ENGINE::ep_start, 0.0f); result = 0; } } /* 3.2 engine at the wrong position but no seek target set; restart seek */ else if (current->jackslave_seekahead_target_rep == -1) { DEBUG_CFLOW_STATEMENT(cerr << "eca_jack_SYNC: starting - new seek to " << pos->frame << "." << endl); eca_jack_sync_start_seek_to(state, pos, arg); result = 0; } /* 3.3 engine at the wrong position; seek still ongoing */ else { DEBUG_CFLOW_STATEMENT(cerr << "eca_jack_SYNC: still seeking, pos=" << pos->frame << ", enginepos=" << enginepos << "." << endl); if (pos->frame != static_cast(current->jackslave_seekahead_target_rep)) { eca_jack_sync_start_seek_to(state, pos, arg); } result = 0; } } /* 4. slow-start timeout elapsed; transport forced to rolling */ else { DEBUG_CFLOW_STATEMENT(cerr << "eca_jack_SYNC: JACK running, forced, trying to catch up" << endl); eca_jack_sync_start_live_seek_to(state, pos, arg); } pthread_mutex_unlock(¤t->engine_mod_lock_rep); } else { DEBUG_CFLOW_STATEMENT(cerr << "eca_jack_SYNC: couldn't get lock" << endl); result = 0; } return result; } #endif #if ECA_JACK_TRANSPORT_API >= 3 /** * Helper function to start seeking to a new position. * * context: J-level-1 */ static void eca_jack_sync_start_seek_to(jack_transport_state_t state, jack_position_t *pos, void *arg) { // DEBUG_CFLOW_STATEMENT(cerr << endl << "eca_jack_sync_start_seek_to(): entering..."); AUDIO_IO_JACK_MANAGER* current = static_cast(arg); SAMPLE_SPECS::sample_pos_t enginepos = current->engine_repp->current_position_in_samples(); /* prepare for the next start by seeking to the correct position */ if (enginepos != pos->frame) { current->jackslave_seekahead_target_rep = pos->frame; current->engine_repp->command(ECA_ENGINE::ep_setpos_samples, current->jackslave_seekahead_target_rep); DEBUG_CFLOW_STATEMENT(cerr << "eca_jack_SYNC: seeking to " << pos->frame << endl); } current->engine_repp->command(ECA_ENGINE::ep_prepare, 0.0f); } #endif #if ECA_JACK_TRANSPORT_API >= 3 /** * Helper function to start forced (live-)seeking to a new * position. We have to be prepared to chase the timebase * master. * * context: J-level-1/2 */ static void eca_jack_sync_start_live_seek_to(jack_transport_state_t state, jack_position_t *pos, void *arg) { AUDIO_IO_JACK_MANAGER* current = static_cast(arg); SAMPLE_SPECS::sample_pos_t enginepos = current->engine_repp->current_position_in_samples(); if (current->is_running() != true) { /* transport rolling: engine not started; start it now */ if (current->engine_repp->status() != ECA_ENGINE::engine_status_finished && ((pos->frame <= current->engine_repp->connected_chainsetup()->length_in_samples()) || (current->engine_repp->is_finite_length() != true))) { /* conditions when we should start the engine * a. engine status not finished, AND... * a.1. transport position not beyond csetup length, OR... * a.2. csetup has infinite length */ if (current->engine_repp->is_prepared() == true && (enginepos == pos->frame)) { current->engine_repp->start_operation(); DEBUG_CFLOW_STATEMENT(cerr << "eca_jack_FORCESYNC: Starting engine (direct)\n"); } else { current->start_request_rep++; current->engine_repp->command(ECA_ENGINE::ep_start, 0.0f); DEBUG_CFLOW_STATEMENT(cerr << "eca_jack_FORCESYNC: Starting engine (cmdpipe)\n"); enginepos = -1; } } } else { DEBUG_CFLOW_STATEMENT(cerr << endl << "eca_jack_FORCESYNC: engine curpos '" << current->engine_repp->current_position_in_samples() << "' doesn't match JACK curpos '" << pos->frame << "'!" << endl); if ((pos->frame >= current->engine_repp->connected_chainsetup()->length_in_samples() && (current->engine_repp->is_finite_length() == true))) { DEBUG_CFLOW_STATEMENT(cerr << endl << "eca_jack_FORCESYNC: over max length" << endl); current->stop_request_rep++; current->engine_repp->command(ECA_ENGINE::ep_stop, 0.0f); } else if (current->jackslave_seekahead_target_rep == -1 || current->jackslave_seekahead_target_rep < static_cast(pos->frame + current->buffersize()) || current->jackslave_seekahead_target_rep - current->jackslave_seekahead_rep * current->buffersize() > static_cast(pos->frame + current->buffersize())) { /* note: we use seek-ahead to give time for the disk i/o subsystem to catch up for the next round, seek-ahead must be re-initialized if... a) seek-ahead target not set, b) we have missed the current seek-ahead target, or c) transport position has been rewinded (current seek-ahead target too far in the fututre) */ if (current->jackslave_seekahead_target_rep != -1) { /* previous seek has failed; try again with a longer look-ahead */ current->jackslave_seekahead_rep = (current->jackslave_seekahead_rep < (65536 / current->buffersize()) ? current->jackslave_seekahead_rep * 2 : (65536 / current->buffersize())); DEBUG_CFLOW_STATEMENT(cerr << "eca_jack_FORCESYNC: seek-ahead request failed; increasing seek-ahead to " << current->jackslave_seekahead_rep << endl); } current->jackslave_seekahead_target_rep = pos->frame + current->jackslave_seekahead_rep * current->buffersize(); current->engine_repp->command(ECA_ENGINE::ep_setpos_live_samples, current->jackslave_seekahead_target_rep); DEBUG_CFLOW_STATEMENT(cerr << "eca_jack_FORCESYNC: seek-ahead request sent; seeking to " << current->jackslave_seekahead_target_rep << endl); } else { /* engine is already seeking to a new pos */ DEBUG_CFLOW_STATEMENT(cerr << endl << "eca_jack_FORCESYNC: seek to new pos underway; " << pos->frame << " is transport-curpos." << endl); } } } #endif /** * Processes all registered JACK input and output ports. * This is the main callback function registered to * the JACK framework. * * context: J-level-0 */ static int eca_jack_process_callback(jack_nframes_t nframes, void *arg) { AUDIO_IO_JACK_MANAGER* current = static_cast(arg); PROFILE_CE_STATEMENT(eca_jack_process_profile_pre()); if (current->exit_request_rep == 1 || current->shutdown_request_rep == 1) { DEBUG_CFLOW_STATEMENT(cerr << "eca_jack_PROCESS: after shutdown/exit!!!" << endl); return 0; } if (current->engine_repp == 0) { DEBUG_CFLOW_STATEMENT(cerr << "eca_jack_PROCESS: after engine destructor!!!" << endl); return 0; } /* try to get the driver lock; if it fails or connection * is not fully establish, skip this processing cycle */ int ret = pthread_mutex_trylock(¤t->engine_mod_lock_rep); if (ret == 0) { // DEBUG_CFLOW_STATEMENT(cerr << "eca_jack_PROCESS: got lock" << endl); /* 1. transport control processing in "notransport" and "transport" mode */ if (current->mode_rep == AUDIO_IO_JACK_MANAGER::Transport_none || current->mode_rep == AUDIO_IO_JACK_MANAGER::Transport_send) { /* execute one engine iteration */ if (current->is_running() == true) { eca_jack_process_engine_iteration(nframes, current); } else { eca_jack_process_mute(nframes, current); } } #if ECA_JACK_TRANSPORT_API >= 3 else { /* 2. transport control processing in "slave" mode */ if (current->mode_rep == AUDIO_IO_JACK_MANAGER::Transport_send_receive || current->mode_rep == AUDIO_IO_JACK_MANAGER::Transport_receive) { eca_jack_process_timebase_slave(nframes, arg); } else { /* never reached */ } } #endif pthread_mutex_unlock(¤t->engine_mod_lock_rep); // DEBUG_CFLOW_STATEMENT(cerr << "eca_jack_PROCESS: released lock" << endl); } else { DEBUG_CFLOW_STATEMENT(cerr << "eca_jack_PROCESS: couldn't get lock; muting" << endl); eca_jack_process_mute(nframes, current); } PROFILE_CE_STATEMENT(eca_jack_process_profile_post()); return 0; } /** * Helper routine. Only called by eca_jack_process*() functions. * * context: J-level-1/ */ static void eca_jack_process_engine_iteration(jack_nframes_t nframes, void* arg) { AUDIO_IO_JACK_MANAGER* current = static_cast(arg); /* this is guaranteed by libjack */ DBC_CHECK(current->buffersize_rep == static_cast(nframes)); /* FIXME: remove me! */ if (current->buffersize_rep != static_cast(nframes)) { std::cerr << "JACK_MANAGER: invalid nframes! buffersize=" << current->buffersize_rep << ", nframes=" << nframes << std::endl; } if (current->engine_repp->status() != ECA_ENGINE::engine_status_finished) { /* 1. copy audio data from port input buffers to ecasound buffers */ for(size_t n = 0; n < current->inports_rep.size(); n++) { if (current->inports_rep[n]->cb_buffer != 0) { jack_default_audio_sample_t* in_cb_buffer = static_cast (jack_port_get_buffer(current->inports_rep[n]->jackport, nframes)); memcpy(current->inports_rep[n]->cb_buffer, in_cb_buffer, current->buffersize_rep * sizeof(jack_default_audio_sample_t)); } } // DEBUG_CFLOW_STATEMENT(cerr << endl << "eca_jack_PROCESS: engine_iter_in"); /* 2. execute one engine iteration */ current->engine_repp->engine_iteration(); // DEBUG_CFLOW_STATEMENT(cerr << endl << "eca_jack_PROCESS: engine_iter_out"); /* 3. copy data from ecasound buffers to port output buffers */ for(size_t n = 0; n < current->outports_rep.size(); n++) { if (current->outports_rep[n]->cb_buffer != 0) { jack_default_audio_sample_t* out_cb_buffer = static_cast (jack_port_get_buffer(current->outports_rep[n]->jackport, nframes)); memcpy(out_cb_buffer, current->outports_rep[n]->cb_buffer, current->buffersize_rep * sizeof(jack_default_audio_sample_t)); } } } else { /* 4. chainsetup finished, mute */ DEBUG_CFLOW_STATEMENT(cerr << "eca_jack_PROCESS: chainsetup finished, muting\n"); eca_jack_process_mute(nframes, current); } /* 5. update engine status based on the last iteration */ current->engine_repp->update_engine_state(); } /** * Helper routine. Only called by eca_jack_process*() functions. * * context: J-level-1/2 */ static void eca_jack_process_mute(jack_nframes_t nframes, void* arg) { AUDIO_IO_JACK_MANAGER* current = static_cast(arg); for(size_t n = 0; n < current->outports_rep.size(); n++) { if (current->outports_rep[n]->cb_buffer != 0) { jack_default_audio_sample_t* out_cb_buffer = static_cast (jack_port_get_buffer(current->outports_rep[n]->jackport, nframes)); memset(out_cb_buffer, 0, current->buffersize_rep * sizeof(jack_default_audio_sample_t)); } } } #if ECA_JACK_TRANSPORT_API >= 3 /** * Helper routine. Only called by eca_jack_process_callback() function. * * context: J-level-1 */ static void eca_jack_process_timebase_slave(jack_nframes_t nframes, void *arg) { AUDIO_IO_JACK_MANAGER* current = static_cast(arg); jack_transport_state_t jackstate; jack_position_t jackpos; bool need_mute = true; jackstate = jack_transport_query(current->client_repp, &jackpos); /* 1. engine locked for editing, do not touch! */ if (current->engine_repp->is_locked_for_editing() == true) { DEBUG_CFLOW_STATEMENT(cerr << "current->engine_repp->is_locked_for_editing() == true\n"); } /* 2. transport stopped or starting */ else if (jackstate == JackTransportStopped) { // DEBUG_CFLOW_STATEMENT(cerr << "eca_jack_process_timebase_slave(): JACK state stopped" << endl); ++current->j_stopped_rounds_rep; /* 2.1 transport stopped and no pending start requests */ if (current->is_running() == true && current->j_stopped_rounds_rep > 3) { DEBUG_CFLOW_STATEMENT(cerr << "eca_jack_PROCESS: running, request stop" << endl); current->stop_request_rep++; current->engine_repp->command(ECA_ENGINE::ep_stop, 0.0f); } } /* 3. transport stopped or starting */ else if (jackstate == JackTransportStarting) { DEBUG_CFLOW_STATEMENT(cerr << "eca_jack_process_timebase_slave(): JACK state starting" << endl); ++current->j_stopped_rounds_rep; } /* 4. transport rolling (or looping, both states are fine to us, as is the case where state info is not available at all) */ else { current->j_stopped_rounds_rep = 0; /* 4.1 engine not running for some odd reason; try a live-seek */ if (current->is_running() != true) { DEBUG_CFLOW_STATEMENT(cerr << "eca_jack_process_timebase_slave(): not running!" << endl); eca_jack_sync_start_live_seek_to(jackstate, &jackpos, arg); } /* 4.2 engine running normally */ else { SAMPLE_SPECS::sample_pos_t enginepos = current->engine_repp->current_position_in_samples(); /* 4.2.1 engine at correct position, run the engine cycle */ if (enginepos == jackpos.frame) { if (current->jackslave_seekahead_target_rep != -1) { // report only on the first time DEBUG_CFLOW_STATEMENT(cerr << "eca_jack_process_timebase_slave(): JACK running; correct position\n"); } /* execute engine iteration */ eca_jack_process_engine_iteration(nframes, current); current->jackslave_seekahead_target_rep = -1; need_mute = false; } /* 4.2.2 engine at wrong position for some odd reason; try a live-seek */ else { DEBUG_CFLOW_STATEMENT(cerr << endl << "eca_jack_process_timebase_slave(): engine curpos '" << current->engine_repp->current_position_in_samples() << "' doesn't match JACK curpos '" << jackpos.frame << "'!" << endl); eca_jack_sync_start_live_seek_to(jackstate, &jackpos, arg); } } } if (need_mute == true) { eca_jack_process_mute(nframes, current); } } #endif #ifdef PROFILE_CALLBACK_EXECUTION static void eca_jack_process_profile_pre(void) { profile_callback_timer.start(); DEBUG_CFLOW_STATEMENT(cerr << endl << "eca_jack_PROCESS: entry ----> "); } static void eca_jack_process_profile_post(void) { profile_callback_timer.stop(); DEBUG_CFLOW_STATEMENT(cerr << endl << "eca_jack_PROCESS: process out" << endl); if (profile_callback_timer.last_duration_seconds() > 0.005f) { cerr << "(audioio-jack-manager) event " << profile_callback_timer.event_count(); cerr << ", process() took " << profile_callback_timer.last_duration_seconds() * 1000; cerr << " msecs." << endl; } else { if (profile_callback_timer.event_count() < 5) { cerr << "(audioio-jack-manager) event " << profile_callback_timer.event_count(); cerr << ", process() took " << profile_callback_timer.last_duration_seconds() * 1000; cerr << " msecs." << endl; } } } #endif /* PROFILE_CALLBACK_EXECUTION */ /** * Changes current sampling rate. Callback function registered * to the JACK framework. * * @param nframes new engine sample rate * @param arg pointer to a client supplied structure * * @return zero on success, non-zero on error */ static int eca_jack_srate_cb(jack_nframes_t nframes, void *arg) { AUDIO_IO_JACK_MANAGER* current = static_cast(arg); ECA_LOG_MSG(ECA_LOGGER::user_objects, "[callback] " + current->jackname_rep + ": setting srate to " + kvu_numtostr(nframes)); if (static_cast(nframes) != current->srate_rep) { current->shutdown_request_rep = true; ECA_LOG_MSG(ECA_LOGGER::info, "Unable to adapt to the new samplerate received from JACK, shutting down."); } return 0; } /** * Callback function that is called when the engine * buffersize changes. * * context: J-level-0 * * @param nframes new engine buffer size * @param arg pointer to a client supplied structure * * @return zero on success, non-zero on error */ static int eca_jack_bsize_cb(jack_nframes_t nframes, void *arg) { AUDIO_IO_JACK_MANAGER* current = static_cast(arg); ECA_LOG_MSG(ECA_LOGGER::user_objects, "[callback] " + current->jackname_rep + ": setting buffersize to " + kvu_numtostr(nframes)); if (static_cast(nframes) != current->buffersize()) { // FIXME: leads into a segfault...? current->shutdown_request_rep = true; ECA_LOG_MSG(ECA_LOGGER::info, "Unable to adapt to the new buffersize received from JACK, shutting down."); } return 0; } /** * Shuts down the callback context. Callback function registered * to the JACK framework. * * context: J-level-0 */ static void eca_jack_shutdown_cb(void *arg) { AUDIO_IO_JACK_MANAGER* current = static_cast(arg); ECA_LOG_MSG(ECA_LOGGER::user_objects, "" + current->jackname_rep + ": [callback] jackd shutdown, stopping processing"); current->shutdown_request_rep = true; } /** * Implementations of non-static functions * * context: E-level-0 */ AUDIO_IO_JACK_MANAGER::AUDIO_IO_JACK_MANAGER(void) { ECA_LOG_MSG(ECA_LOGGER::system_objects, "constructor"); open_rep = false; activated_rep = false; open_clients_rep = 0; last_node_id_rep = 1; jackslave_seekahead_rep = 2; jackslave_seekahead_target_rep = -1; engine_repp = 0; jackname_rep = "ecasound"; pthread_cond_init(&exit_cond_rep, NULL); pthread_mutex_init(&exit_mutex_rep, NULL); pthread_mutex_init(&engine_mod_lock_rep, NULL); /* set default transport mode */ #if ECA_JACK_TRANSPORT_API >= 3 mode_rep = AUDIO_IO_JACK_MANAGER::Transport_send_receive; #else mode_rep = AUDIO_IO_JACK_MANAGER::Transport_none; #endif cb_allocated_frames_rep = 0; buffersize_rep = 0; } /** * Class destructor. * * context: E-level-0 */ AUDIO_IO_JACK_MANAGER::~AUDIO_IO_JACK_MANAGER(void) { ECA_LOG_MSG(ECA_LOGGER::system_objects, "destructor"); /* 1. close JACK connection */ if (is_open() == true) close_server_connection(); /* 2. clear input ports */ vector::iterator q = inports_rep.begin(); while(q != inports_rep.end()) { if ((*q)->cb_buffer != 0) { delete[] (*q)->cb_buffer; (*q)->cb_buffer = 0; } delete *q; ++q; } /* 3. clear output ports */ q = inports_rep.begin(); while(q != inports_rep.end()) { if ((*q)->cb_buffer != 0) { delete[] (*q)->cb_buffer; (*q)->cb_buffer = 0; } delete *q; ++q; } /* 4. clear objects */ list::iterator p = node_list_rep.begin(); while(p != node_list_rep.end()) { delete *p; ++p; } } /** * context: C-level-1 */ bool AUDIO_IO_JACK_MANAGER::is_managed_type(const AUDIO_IO* aobj) const { // --- DBC_REQUIRE(aobj != 0); // --- if (aobj->name() == "JACK interface") { DBC_CHECK(dynamic_cast(aobj) != 0); return(true); } return false; } /** * context: C-level-1 */ void AUDIO_IO_JACK_MANAGER::register_object(AUDIO_IO* aobj) { // --- DBC_REQUIRE(aobj != 0); DBC_REQUIRE(is_managed_type(aobj) == true); // --- ECA_LOG_MSG(ECA_LOGGER::system_objects, "register object " + aobj->label()); AUDIO_IO_JACK* jobj = static_cast(aobj); eca_jack_node_t* tmp = new eca_jack_node_t; tmp->aobj = jobj; tmp->origptr = aobj; tmp->client_id = last_node_id_rep; node_list_rep.push_back(tmp); jobj->set_manager(this, tmp->client_id); ++last_node_id_rep; // --- DBC_ENSURE(is_managed_type(aobj) == true); // --- } /** * context: C-level-0 */ int AUDIO_IO_JACK_MANAGER::get_object_id(const AUDIO_IO* aobj) const { // --- DBC_REQUIRE(is_managed_type(aobj) == true); // --- list::const_iterator p = node_list_rep.begin(); while(p != node_list_rep.end()) { if ((*p)->origptr == aobj) { ECA_LOG_MSG(ECA_LOGGER::system_objects, "found object id for aobj " + aobj->name() + ": " + kvu_numtostr((*p)->client_id)); return (*p)->client_id; } ++p; } return -1; } /** * context: C-level-0 */ list AUDIO_IO_JACK_MANAGER::get_object_list(void) const { list object_list; list::const_iterator p = node_list_rep.begin(); while(p != node_list_rep.end()) { object_list.push_back((*p)->client_id); ++p; } return object_list; } /** * Unregisters object previously registered with register_object() * from the manager. * * @param id unique identifier for managed objects; @see * get_object_id * * context: C-level-0 */ void AUDIO_IO_JACK_MANAGER::unregister_object(int id) { // --- DBC_DECLARE(unsigned int old_total_nodes = node_list_rep.size()); // --- ECA_LOG_MSG(ECA_LOGGER::system_objects, "unregister object "); list::iterator p = node_list_rep.begin(); while(p != node_list_rep.end()) { if ((*p)->client_id == id) { ECA_LOG_MSG(ECA_LOGGER::system_objects, "removing object " + (*p)->aobj->label()); (*p)->aobj->set_manager(0, -1); delete *p; node_list_rep.erase(p); break; } ++p; } // --- DBC_ENSURE(node_list_rep.size() == old_total_nodes - 1); DBC_DECLARE(list ol = get_object_list()); DBC_ENSURE(std::count(ol.begin(), ol.end(), id) == 0); // --- } /** * context: E-level-0 */ void AUDIO_IO_JACK_MANAGER::set_parameter(int param, std::string value) { switch(param) { case 1: { jackname_rep = value; ECA_LOG_MSG(ECA_LOGGER::user_objects, "client name set to '" + value + "'."); break; } case 2: { #if ECA_JACK_TRANSPORT_API >= 3 if (value == "notransport" || value == "streaming") { mode_rep = AUDIO_IO_JACK_MANAGER::Transport_none; ECA_LOG_MSG(ECA_LOGGER::user_objects, "'notransport' mode selected."); } else if (value == "send" || value == "master") { mode_rep = AUDIO_IO_JACK_MANAGER::Transport_send; ECA_LOG_MSG(ECA_LOGGER::user_objects, "'send' mode selected."); } else if (value == "sendrecv" || value == "slave") { mode_rep = AUDIO_IO_JACK_MANAGER::Transport_send_receive; ECA_LOG_MSG(ECA_LOGGER::user_objects, "'sendrecv' mode selected."); } else if (value == "recv") { mode_rep = AUDIO_IO_JACK_MANAGER::Transport_receive; ECA_LOG_MSG(ECA_LOGGER::user_objects, "'recv' mode selected."); } #else mode_rep = AUDIO_IO_JACK_MANAGER::Transport_none; if (value != "notransport") ECA_LOG_MSG(ECA_LOGGER::info, "WARNING: JACK transport support disabled at build time, using 'notransport'."); #endif /* ECA_JACK_TRANSPORT_API */ break; } } } /** * context: E-level-0 */ std::string AUDIO_IO_JACK_MANAGER::get_parameter(int param) const { switch(param) { case 1: { return jackname_rep; } case 2: { switch(mode_rep) { case AUDIO_IO_JACK_MANAGER::Transport_none: return "notransport"; case AUDIO_IO_JACK_MANAGER::Transport_receive: return "recv"; case AUDIO_IO_JACK_MANAGER::Transport_send: return "send"; case AUDIO_IO_JACK_MANAGER::Transport_send_receive: return "sendrecv"; default: return "notransport"; } break; } } return ""; } /** * If transport is stopped, request JACK to seek to * Ecasond's current position. Otherwise let Ecasound seek * to JACK's position. */ void AUDIO_IO_JACK_MANAGER::initial_seek(void) { #if ECA_JACK_TRANSPORT_API >= 3 jack_transport_state_t jackstate; jack_position_t jackpos; jackstate = jack_transport_query(client_repp, &jackpos); /* FIXME: is this a good idea...? might confuse some scripts */ if (jackstate == JackTransportStopped && (mode_rep == AUDIO_IO_JACK_MANAGER::Transport_send || mode_rep == AUDIO_IO_JACK_MANAGER::Transport_send_receive)) { jack_transport_locate(client_repp, engine_repp->current_position_in_samples()); } #endif } /** * context: E-level-0 */ int AUDIO_IO_JACK_MANAGER::exec(ECA_ENGINE* engine, ECA_CHAINSETUP* csetup) { int result = 0; ECA_LOG_MSG(ECA_LOGGER::system_objects, "driver exec"); engine_repp = engine; engine->init_engine_state(); shutdown_request_rep = false; exit_request_rep = false; stop_request_rep = 0; j_stopped_rounds_rep = 0; start_request_rep = 0; activate_server_connection(); if (is_connection_active() != true) { signal_exit(); } initial_seek(); while(true) { DEBUG_CFLOW_STATEMENT(cerr << "jack_exec: wait for commands" << endl); engine_repp->wait_for_commands(); DEBUG_CFLOW_STATEMENT(cerr << "jack_exec: wakes up; commands available" << endl); /* we must take the lock to ensure that * process callback does not run at the same time * * note: the RT-optimized command queue (VALUE_QUEUE_RT_C) is not * safe when accessed from more than 2 threads, but here it is ok * as we limit concurrent threads to two with engine_mod_lock_rep **/ SAMPLE_SPECS::sample_pos_t enginepos = engine_repp->current_position_in_samples(); pthread_mutex_lock(&engine_mod_lock_rep); engine_repp->check_command_queue(); if (exit_request_rep != true && enginepos != engine_repp->current_position_in_samples()) { /* seek requested */ #if ECA_JACK_TRANSPORT_API >= 3 if (mode_rep == AUDIO_IO_JACK_MANAGER::Transport_send || mode_rep == AUDIO_IO_JACK_MANAGER::Transport_send_receive) { if (engine_repp->current_position_in_samples() != jackslave_seekahead_target_rep) { DEBUG_CFLOW_STATEMENT(cerr << "jack_exec: seek requested to pos=" << engine_repp->current_position_in_samples() << "." << endl); jack_transport_locate(client_repp, engine_repp->current_position_in_samples()); } } #endif } pthread_mutex_unlock(&engine_mod_lock_rep); DEBUG_CFLOW_STATEMENT(cerr << "jack_exec: check_commands finished" << endl); /* case 1: external exit request */ if (exit_request_rep == true) { ECA_LOG_MSG(ECA_LOGGER::system_objects, "exit request in exec"); break; } /* case 2-i: engine finished and in batch mode -> exit */ if (engine_repp->status() == ECA_ENGINE::engine_status_finished && engine_repp->batch_mode() == true) { ECA_LOG_MSG(ECA_LOGGER::system_objects, "batch finished in exec, exit"); break; } /* case 2-ii: engine error occured -> exit */ else if (engine_repp->status() == ECA_ENGINE::engine_status_error) { ECA_LOG_MSG(ECA_LOGGER::system_objects, "engine error, exit"); break; } /* case 3: problems with jack callbacks -> exit */ if (shutdown_request_rep == true) { ECA_LOG_MSG(ECA_LOGGER::system_objects, "problems with JACK callbacks"); result = -1; break; } } DEBUG_CFLOW_STATEMENT(cerr << "jack_exec: out of exec" << endl); if (is_connection_active() == true) { deactivate_server_connection(); } DEBUG_CFLOW_STATEMENT(cerr << "jack_exec: deactivated" << endl); pthread_mutex_lock(&engine_mod_lock_rep); exit_request_rep = 0; engine_repp = 0; pthread_mutex_unlock(&engine_mod_lock_rep); /* signal exit() that we are done */ signal_exit(); DEBUG_CFLOW_STATEMENT(cerr << "jack_exec: exit" << endl); return result; } /** * Activate connection to the JACK server. * * context: E-level-0/3 * Can be called at the same time with * JACK callbacks, so proper locking * must be ensured (engine_mod_lock_rep taken * upon entry). * * @pre is_running() != true * @post is_running() == true * */ void AUDIO_IO_JACK_MANAGER::start(void) { // -- DBC_REQUIRE(is_running() != true); // -- ECA_LOG_MSG(ECA_LOGGER::system_objects, "driver start"); if (engine_repp->is_prepared() != true) engine_repp->prepare_operation(); engine_repp->start_operation(); #if ECA_JACK_TRANSPORT_API >= 3 if (start_request_rep > 0) { start_request_rep = 0; } else { if (mode_rep == AUDIO_IO_JACK_MANAGER::Transport_send || mode_rep == AUDIO_IO_JACK_MANAGER::Transport_send_receive) { jack_transport_start(client_repp); } } j_stopped_rounds_rep = 0; #endif // -- DBC_ENSURE(is_running() == true); // -- } /** * Signals that driver should stop operation. * Once stopped, driver must not call * any ECA_ENGINE functions. * * context: E-level-0/3 * Caller must ensure that JACK process callback * does not run at the same time (engine_mod_lock_rep * taken upon entry). */ void AUDIO_IO_JACK_MANAGER::stop(void) { ECA_LOG_MSG(ECA_LOGGER::system_objects, "driver stop"); #if ECA_JACK_TRANSPORT_API >= 3 if (stop_request_rep > 0) { stop_request_rep = 0; } else { if (mode_rep == AUDIO_IO_JACK_MANAGER::Transport_send || mode_rep == AUDIO_IO_JACK_MANAGER::Transport_send_receive) { jack_transport_stop(client_repp); } } #endif if (engine_repp->is_prepared() == true) engine_repp->stop_operation(); } /** * Activates connection to server. * * context: E-level-1 * * @pre is_connection_active() != true */ void AUDIO_IO_JACK_MANAGER::activate_server_connection(void) { // -- DBC_REQUIRE(is_connection_active() != true); // -- if (engine_repp->is_prepared() != true) engine_repp->prepare_operation(); ECA_LOG_MSG(ECA_LOGGER::system_objects, "jack_activate()"); if (jack_activate (client_repp)) { ECA_LOG_MSG(ECA_LOGGER::info, "Error! Cannot active client!"); activated_rep = false; } else { connect_all_nodes(); /* update port-specific latency values */ engine_repp->update_cache_latency_values(); activated_rep = true; } } /** * Disconnects all connected ports and then * deactives the client. * * context: E-level-1 * Caller must ensure that JACK process callback * does not rung at the same time. * * @pre is_connection_active() == true * @post is_connection_active() != true */ void AUDIO_IO_JACK_MANAGER::deactivate_server_connection(void) { // -- DBC_REQUIRE(is_connection_active() == true); // -- if (shutdown_request_rep != true) { /* no need to disconnect as deactivate does that for us */ // disconnect_all_nodes(); ECA_LOG_MSG(ECA_LOGGER::system_objects, "jack_deactivate() "); if (jack_deactivate (client_repp)) { ECA_LOG_MSG(ECA_LOGGER::info, "Error! Cannot deactive client!"); } } if (engine_repp->is_prepared() == true) engine_repp->stop_operation(); activated_rep = false; signal_stop(); // -- DBC_ENSURE(is_connection_active() != true); // -- } /** * Signals that driver should stop operation * and return from its exec() method. * * context: E-level-0/3 * Can be called at the same time with * JACK callbacks, so proper locking * must be ensured (caller must hold * lock against the callbacks). */ void AUDIO_IO_JACK_MANAGER::exit(void) { ECA_LOG_MSG(ECA_LOGGER::system_objects, "driver exit"); exit_request_rep = true; if (engine_repp->is_prepared() == true) engine_repp->stop_operation(); } /** * Returns a pointer to a 'eca_jack_node_t' structure * matching client 'client_id'. * * @pre list l = get_object_list(); std::count(l.begin(), l.end(), client_id) == 1 * @return non-zero pointer */ AUDIO_IO_JACK_MANAGER::eca_jack_node_t* AUDIO_IO_JACK_MANAGER::get_node(int client_id) { // -- DBC_DECLARE(list ol = get_object_list()); DBC_REQUIRE(std::count(ol.begin(), ol.end(), client_id) == 1); // -- eca_jack_node_t* node = 0; list::iterator p = node_list_rep.begin(); while(p != node_list_rep.end()) { if ((*p)->client_id == client_id) { node = *p; break; } ++p; } // -- DBC_ENSURE(node != 0); // -- return node; } /** * Sets up automatic port connection for client_id's port * 'portnum'. When jack client is activated, this port * is automatically connected to port 'portname'. The * direction of the connection is based on audio objects I/O mode * (@see AUDIO_IO::io_mode()). * * @pre list l = get_object_list(); std::count(l.begin(), l.end(), client_id) == 1 * @pre is_open() == true @ @pre portnum > 0 */ void AUDIO_IO_JACK_MANAGER::auto_connect_jack_port(int client_id, int portnum, const string& portname) { // --- DBC_DECLARE(list ol = get_object_list()); DBC_REQUIRE(std::count(ol.begin(), ol.end(), client_id) == 1); DBC_REQUIRE(is_open() == true); DBC_REQUIRE(portnum > 0); // --- ECA_LOG_MSG(ECA_LOGGER::system_objects, "auto-connect jack ports for client " + kvu_numtostr(client_id)); eca_jack_node_t* node = get_node(client_id); list::const_iterator p = node->ports.begin(); int n = 1; while(p != node->ports.end()) { if (n == portnum) { (*p)->autoconnect_string = portname; break; } ++n; ++p; } } static std::string eca_get_jack_port_item(const char **ports, int item) { int n = 0; while(ports != 0 && ports[n] != 0) { if (n + 1 == item) return string(ports[n]); n++; } return string(""); } /** * Sets up automatic port connections to matching ports of * client 'dst'. * * @pre list l = get_object_list(); std::count(l.begin(), l.end(), client_id) == 1 * @pre is_open() == true @ @pre portnum > 0 */ void AUDIO_IO_JACK_MANAGER::auto_connect_jack_port_client(int client_id, const string& dst, int channels) { // --- DBC_DECLARE(list ol = get_object_list()); DBC_REQUIRE(std::count(ol.begin(), ol.end(), client_id) == 1); DBC_REQUIRE(is_open() == true); DBC_REQUIRE(channels > 0); // --- const char** ports; ECA_LOG_MSG(ECA_LOGGER::user_objects, "Making autoconnection to ports matching: " + dst); eca_jack_node_t* node = get_node(client_id); list::const_iterator p = node->ports.begin(); int n = 1; while(p != node->ports.end()) { if (n <= channels) { ports = 0; if (node->aobj->io_mode() == AUDIO_IO::io_read) { ports = jack_get_ports (client_repp, dst.c_str(), NULL, JackPortIsOutput); } else { ports = jack_get_ports (client_repp, dst.c_str(), NULL, JackPortIsInput); } (*p)->autoconnect_string = eca_get_jack_port_item(ports, n); ECA_LOG_MSG(ECA_LOGGER::user_objects, "Making autoconnection to terminal port: " + (*p)->autoconnect_string + ", channel " + kvu_numtostr(n)); if (ports != NULL) free(ports); } else { break; } ++n; ++p; } } /** * Returns the total latency for ports of client * 'client_id'. If client ports have different latency * values, the worst-case latency is reported. */ long int AUDIO_IO_JACK_MANAGER::client_latency(int client_id) { eca_jack_node_t* node = get_node(client_id); long int latency = -1; list::const_iterator p = node->ports.begin(); while(p != node->ports.end()) { if (latency == -1) { latency = (*p)->total_latency; } else { if (static_cast((*p)->total_latency) > latency) { ECA_LOG_MSG(ECA_LOGGER::info, "warning! port latencies don't match for client " + kvu_numtostr(client_id)); latency = (*p)->total_latency; } } ++p; } return latency; } /** * Registers new JACK port for client 'client_id'. The direction of * the port is based on audio objects I/O mode (@see * AUDIO_IO::io_mode()). If 'portname' is a non-empty string, * the port will be automatically connected to the 'portname' * port once JACK client is activated. * * The final port names are of the form 'clientname:portprefix_N', * where N is 1...max_port. * * @pre list l = get_object_list(); std::count(l.begin(), l.end(), client_id) == 1 * @pre is_open() == true */ void AUDIO_IO_JACK_MANAGER::register_jack_ports(int client_id, int ports, const string& portprefix) { // --- DBC_DECLARE(list ol = get_object_list()); DBC_REQUIRE(std::count(ol.begin(), ol.end(), client_id) == 1); DBC_REQUIRE(is_open() == true); DBC_DECLARE(unsigned int old_port_count_vectors = inports_rep.size() + outports_rep.size()); // --- ECA_LOG_MSG(ECA_LOGGER::system_objects, "register jack ports for client " + kvu_numtostr(client_id)); eca_jack_node_t* node = get_node(client_id); for(int n = 0; n < ports; n++) { eca_jack_port_data_t* portdata = new eca_jack_port_data_t; portdata->jackport = 0; portdata->autoconnect_string = ""; portdata->total_latency = 0; portdata->cb_buffer = new jack_default_audio_sample_t [cb_allocated_frames_rep]; std::map::iterator it = port_numbers_rep.find(portprefix); if (it == port_numbers_rep.end()) { it = port_numbers_rep.insert(std::make_pair(portprefix, 0)).first; } string tport = portprefix + "_" + kvu_numtostr(++it->second); if (node->aobj->io_mode() == AUDIO_IO::io_read) { portdata->jackport = jack_port_register(client_repp, tport.c_str(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0); inports_rep.push_back(portdata); } else { portdata->jackport = jack_port_register(client_repp, tport.c_str(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); outports_rep.push_back(portdata); } node->ports.push_back(portdata); } // --- DBC_ENSURE(inports_rep.size() + outports_rep.size() == old_port_count_vectors + ports); // --- } /** * Unregisters all JACK ports for client 'client_id'. * * @pre list l = get_object_list(); std::count(l.begin(), l.end(), client_id) == 1 * @pre is_open() == true * @post node->in_ports == 0 && node->out_ports == 0 */ void AUDIO_IO_JACK_MANAGER::unregister_jack_ports(int client_id) { // --- DBC_DECLARE(list ol = get_object_list()); DBC_REQUIRE(std::count(ol.begin(), ol.end(), client_id) == 1); DBC_REQUIRE(is_open() == true); DBC_DECLARE(unsigned int old_node_port_count = get_node(client_id)->ports.size()); DBC_DECLARE(unsigned int old_port_count_vectors = inports_rep.size() + outports_rep.size()); // --- ECA_LOG_MSG(ECA_LOGGER::system_objects, "unregister all jack ports for client " + kvu_numtostr(client_id)); eca_jack_node_t* node = get_node(client_id); list::iterator p = node->ports.begin(); while(p != node->ports.end()) { /* 1. unregister port from JACK */ if (open_rep == true && (*p)->jackport != 0) { jack_port_unregister(client_repp, (*p)->jackport); } /* 2. delete the port from inports and outports vectors */ vector::iterator q = inports_rep.begin(); while(q != inports_rep.end()) { if (*p == *q) { inports_rep.erase(q); break; } ++q; } q = outports_rep.begin(); while(q != outports_rep.end()) { if (*p == *q) { outports_rep.erase(q); break; } ++q; } /* 3. delete sub-structures */ delete[] (*p)->cb_buffer; (*p)->cb_buffer = 0; /* 4. delete the actual port_data object */ delete *p; ++p; } /* 5. clear the whole node port list */ node->ports.clear(); // --- DBC_ENSURE(node->ports.size() == 0); DBC_ENSURE(inports_rep.size() + outports_rep.size() == old_port_count_vectors - old_node_port_count); // --- } void AUDIO_IO_JACK_MANAGER::open(int client_id) { ECA_LOG_MSG(ECA_LOGGER::system_objects, "open for client " + kvu_numtostr(client_id)); DBC_CHECK(shutdown_request_rep != true); /* only for the first client */ if (is_open() != true) { open_server_connection(); } ++open_clients_rep; } void AUDIO_IO_JACK_MANAGER::close(int client_id) { ECA_LOG_MSG(ECA_LOGGER::system_objects, "close for client " + kvu_numtostr(client_id)); DBC_CHECK(open_clients_rep > 0); /* only for the last client */ if (open_clients_rep == 1) { if (is_open() == true) close_server_connection(); } else ECA_LOG_MSG(ECA_LOGGER::user_objects, "Not yet closing JACK server connection as there are " + kvu_numtostr(open_clients_rep - 1) + " clients still active."); --open_clients_rep; } /** * Returns current buffersize in sample frames. * Always returns 0 if manager is not connected. */ long int AUDIO_IO_JACK_MANAGER::buffersize(void) const { if (is_open() != true) return 0; return buffersize_rep; } bool AUDIO_IO_JACK_MANAGER::is_running(void) const { if (engine_repp != 0) { return engine_repp->is_running(); } return false; } /** * Returns the current JACK engine sample rate. * Always returns 0 if manager is not connected. */ SAMPLE_SPECS::sample_rate_t AUDIO_IO_JACK_MANAGER::samples_per_second(void) const { if (is_open() != true) return 0; return srate_rep; } /** * context: J-E-C-level-3 */ long int AUDIO_IO_JACK_MANAGER::read_samples(int client_id, void* target_buffer, long int samples) { // DEBUG_CFLOW_STATEMENT(cerr << endl << "read_samples:" << client_id); jack_default_audio_sample_t* ptr = static_cast(target_buffer); eca_jack_node_t* node = get_node(client_id); list::const_iterator p = node->ports.begin(); while(p != node->ports.end()) { if ((*p)->cb_buffer != 0) { memcpy(ptr, (*p)->cb_buffer, buffersize_rep * sizeof(jack_default_audio_sample_t)); ptr += buffersize_rep; } ++p; } return buffersize_rep; } /** * context: J-E-C-level-3 */ void AUDIO_IO_JACK_MANAGER::write_samples(int client_id, void* target_buffer, long int samples) { // DEBUG_CFLOW_STATEMENT(cerr << endl << "write_samples:" << client_id); size_t sample_size = sizeof(jack_default_audio_sample_t); long int writesamples = (samples <= buffersize_rep) ? samples : buffersize_rep; jack_default_audio_sample_t* ptr = static_cast(target_buffer); eca_jack_node_t* node = get_node(client_id); list::const_iterator p = node->ports.begin(); while(p != node->ports.end()) { if ((*p)->cb_buffer != 0) { /* note: cb_buffer points to jack_default_audio_sample_t* */ memcpy((*p)->cb_buffer, ptr, writesamples * sample_size); ptr += writesamples; memset((*p)->cb_buffer + writesamples, 0, (buffersize_rep - writesamples) * sample_size); } ++p; } } /** * Opens connection to the JACK server. Sets * is_open() to 'true' if connection is * succesfully opened. * * @pre is_open() != true * * context: C-level-1 */ void AUDIO_IO_JACK_MANAGER::open_server_connection(void) { // -- DBC_REQUIRE(is_open() != true); // -- string client_name (jackname_rep); int n; for(n = 0; n < AUDIO_IO_JACK_MANAGER::instance_limit; n++) { client_repp = jack_client_new (client_name.c_str()); if (client_repp != 0) break; client_name = jackname_rep + "_" + kvu_numtostr(n + 2); } if (n != AUDIO_IO_JACK_MANAGER::instance_limit) { srate_rep = static_cast(jack_get_sample_rate(client_repp)); /* FIXME: add better control of allocated memory */ cb_allocated_frames_rep = buffersize_rep = static_cast(jack_get_buffer_size(client_repp)); shutdown_request_rep = false; jackslave_seekahead_rep = 4096 / buffersize_rep + 1; /* set callbacks */ jack_set_process_callback(client_repp, eca_jack_process_callback, static_cast(this)); jack_set_sample_rate_callback(client_repp, eca_jack_srate_cb, static_cast(this)); jack_set_buffer_size_callback(client_repp, eca_jack_bsize_cb, static_cast(this)); jack_on_shutdown(client_repp, eca_jack_shutdown_cb, static_cast(this)); #if ECA_JACK_TRANSPORT_API >= 3 if (mode_rep == AUDIO_IO_JACK_MANAGER::Transport_receive || mode_rep == AUDIO_IO_JACK_MANAGER::Transport_send_receive) { jack_set_sync_callback(client_repp, eca_jack_sync_callback, static_cast(this)); } #endif open_rep = true; #ifdef PROFILE_CALLBACK_EXECUTION profile_callback_timer.set_lower_bound_seconds(0.001f); profile_callback_timer.set_upper_bound_seconds(0.005f); #endif ECA_LOG_MSG(ECA_LOGGER::user_objects, "Succesfully opened JACK server connection."); } else { ECA_LOG_MSG(ECA_LOGGER::info, "Error! Cannot connect to JACK server!"); open_rep = false; } } /** * Closes connection to the JACK server. * * @pre is_open() == true * @post is_open() != true * * context: C-level-1 */ void AUDIO_IO_JACK_MANAGER::close_server_connection(void) { // -- DBC_REQUIRE(is_open() == true); // -- // FIXME: add proper unregistration // iterate over cids: unregister_jack_ports() if (shutdown_request_rep != true) { jack_client_close (client_repp); } else { shutdown_request_rep = false; } open_rep = false; port_numbers_rep.clear(); ECA_LOG_MSG(ECA_LOGGER::user_objects, "Succesfully closed JACK server connection."); #ifdef PROFILE_CALLBACK_EXECUTION cerr << profile_callback_timer.to_string() << endl; #endif // -- DBC_ENSURE(is_open() != true); DBC_REQUIRE(shutdown_request_rep != true); // -- } /** * Fetches total port latency information. * * context: E-level-5 */ void AUDIO_IO_JACK_MANAGER::get_total_port_latency(jack_client_t* client, eca_jack_port_data_t* ports) { ports->total_latency = jack_port_get_total_latency(client, ports->jackport); ECA_LOG_MSG(ECA_LOGGER::user_objects, "Total latency for port '" + string(jack_port_name(ports->jackport)) + "' is " + kvu_numtostr(ports->total_latency) + "."); } /** * Connects ports of node 'node'. * * @param node pointers to a node object * @param connect whether to connect (true) or disconnect (false) * * context: E-level-4 */ void AUDIO_IO_JACK_MANAGER::set_node_connection(eca_jack_node_t* node, bool connect) { list::iterator p = node->ports.begin(); while(p != node->ports.end()) { if ((*p)->cb_buffer != 0) { string ecaport = (*p)->autoconnect_string; if (ecaport.size() > 0) { string jackport (jack_port_name((*p)->jackport)); const string* fromport = &ecaport; const string* toport = &jackport; if (node->aobj->io_mode() != AUDIO_IO::io_read) { /* output object -> switch direction */ fromport = &jackport; toport = &ecaport; } if (connect == true) { ECA_LOG_MSG(ECA_LOGGER::system_objects, "Connecting JACK port " + *fromport + " to " + *toport); if (jack_connect (client_repp, fromport->c_str(), toport->c_str())) { ECA_LOG_MSG(ECA_LOGGER::info, "Error! Cannot make connection " + *fromport + " -> " + *toport + "."); } else { AUDIO_IO_JACK_MANAGER::get_total_port_latency(client_repp, *p); } } else { ECA_LOG_MSG(ECA_LOGGER::system_objects, "jack_port_disconnect()"); /* don't call jack_disconnect() if engine has shut down */ if (jack_disconnect(client_repp, fromport->c_str(), toport->c_str())) { ECA_LOG_MSG(ECA_LOGGER::info, "Error! Cannot disconnect " + *fromport + " -> " + *toport + "."); } } } } ++p; } } /** * Connects ports of all registered nodes. * * @see set_node_connection() * * context: E-level-3 */ void AUDIO_IO_JACK_MANAGER::connect_all_nodes(void) { if (shutdown_request_rep != true) { list::iterator p = node_list_rep.begin(); while(p != node_list_rep.end()) { set_node_connection(*p, true); ++p; } } else { if (is_open() == true) close_server_connection(); } } /** * Disconnects all ports of registered nodes. * * @see set_node_connection() * * context: E-level-3 */ void AUDIO_IO_JACK_MANAGER::disconnect_all_nodes(void) { list::iterator p = node_list_rep.begin(); while(p != node_list_rep.end()) { set_node_connection(*p, false); ++p; } } /** * Signals that exec() has exited. * * @see wait_for_exit(); * * context: E-level-1 */ void AUDIO_IO_JACK_MANAGER::signal_exit(void) { pthread_mutex_lock(&exit_mutex_rep); pthread_cond_signal(&exit_cond_rep); pthread_mutex_unlock(&exit_mutex_rep); } /** * Waits until exec() has exited. * * context: not in use */ void AUDIO_IO_JACK_MANAGER::wait_for_exit(void) { int ret = kvu_pthread_timed_wait(&exit_mutex_rep, &exit_cond_rep, 5); ECA_LOG_MSG(ECA_LOGGER::info, kvu_pthread_timed_wait_result(ret, "(audioio_jack_manager) wait_for_exit")); } /** * Signals that client has stopped. * * @see wait_for_stop() * * context: E-level-2 */ void AUDIO_IO_JACK_MANAGER::signal_stop(void) { pthread_mutex_lock(&exit_mutex_rep); pthread_cond_signal(&exit_cond_rep); pthread_mutex_unlock(&exit_mutex_rep); } /** * Waits until client has stopped (no more callbacks). * * context: not in use */ void AUDIO_IO_JACK_MANAGER::wait_for_stop(void) { int ret = kvu_pthread_timed_wait(&stop_mutex_rep, &stop_cond_rep, 5); ECA_LOG_MSG(ECA_LOGGER::info, kvu_pthread_timed_wait_result(ret, "(audioio_jack_manager) wait_for_stop")); }