1915 lines
54 KiB
C++
1915 lines
54 KiB
C++
// ------------------------------------------------------------------------
|
||
// 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 <config.h>
|
||
#endif
|
||
|
||
#include <algorithm> /* std::count() */
|
||
#include <iostream>
|
||
#include <string>
|
||
#include <utility>
|
||
|
||
#include <sys/time.h> /* gettimeofday() */
|
||
#include <errno.h> /* ETIMEDOUT */
|
||
#include <jack/jack.h>
|
||
|
||
#include <kvu_dbc.h>
|
||
#include <kvu_numtostr.h>
|
||
#include <kvu_procedure_timer.h>
|
||
#include <kvu_threads.h>
|
||
|
||
#include "eca-engine.h"
|
||
#include "eca-chainsetup.h"
|
||
#include "eca-logger.h"
|
||
|
||
#include <cstring>
|
||
|
||
/**
|
||
* 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<AUDIO_IO_JACK_MANAGER*>(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<unsigned long>(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<AUDIO_IO_JACK_MANAGER*>(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<AUDIO_IO_JACK_MANAGER*>(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<long int>(pos->frame + current->buffersize()) ||
|
||
current->jackslave_seekahead_target_rep - current->jackslave_seekahead_rep * current->buffersize() >
|
||
static_cast<long int>(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<AUDIO_IO_JACK_MANAGER*>(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<AUDIO_IO_JACK_MANAGER*>(arg);
|
||
|
||
/* this is guaranteed by libjack */
|
||
DBC_CHECK(current->buffersize_rep == static_cast<long int>(nframes));
|
||
|
||
/* FIXME: remove me! */
|
||
if (current->buffersize_rep != static_cast<long int>(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_default_audio_sample_t*>
|
||
(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_default_audio_sample_t*>
|
||
(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<AUDIO_IO_JACK_MANAGER*>(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_default_audio_sample_t*>
|
||
(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<AUDIO_IO_JACK_MANAGER*>(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<AUDIO_IO_JACK_MANAGER*>(arg);
|
||
|
||
ECA_LOG_MSG(ECA_LOGGER::user_objects,
|
||
"[callback] " + current->jackname_rep +
|
||
": setting srate to " + kvu_numtostr(nframes));
|
||
|
||
if (static_cast<long int>(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<AUDIO_IO_JACK_MANAGER*>(arg);
|
||
|
||
ECA_LOG_MSG(ECA_LOGGER::user_objects,
|
||
"[callback] " + current->jackname_rep +
|
||
": setting buffersize to " + kvu_numtostr(nframes));
|
||
|
||
if (static_cast<long int>(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<AUDIO_IO_JACK_MANAGER*>(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<eca_jack_port_data_t*>::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<eca_jack_node_t*>::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<const AUDIO_IO_JACK*>(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<AUDIO_IO_JACK*>(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<eca_jack_node_t*>::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<int> AUDIO_IO_JACK_MANAGER::get_object_list(void) const
|
||
{
|
||
list<int> object_list;
|
||
list<eca_jack_node_t*>::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<eca_jack_node_t*>::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<int> 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<int> 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<int> ol = get_object_list());
|
||
DBC_REQUIRE(std::count(ol.begin(), ol.end(), client_id) == 1);
|
||
// --
|
||
|
||
eca_jack_node_t* node = 0;
|
||
list<eca_jack_node_t*>::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<int> 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<int> 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<eca_jack_port_data*>::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<int> 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<int> 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<eca_jack_port_data*>::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<eca_jack_port_data*>::const_iterator p = node->ports.begin();
|
||
while(p != node->ports.end()) {
|
||
if (latency == -1) {
|
||
latency = (*p)->total_latency;
|
||
}
|
||
else {
|
||
if (static_cast<long int>((*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<int> 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<int> 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<string, int>::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<int> l = get_object_list(); std::count(l.begin(), l.end(), client_id) == 1
|
||
* @pre is_open() == true
|
||
* @post node->in_ports == 0 &&<26>node->out_ports == 0
|
||
*/
|
||
void AUDIO_IO_JACK_MANAGER::unregister_jack_ports(int client_id)
|
||
{
|
||
// ---
|
||
DBC_DECLARE(list<int> 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<eca_jack_port_data_t*>::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<eca_jack_port_data_t*>::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<jack_default_audio_sample_t*>(target_buffer);
|
||
eca_jack_node_t* node = get_node(client_id);
|
||
|
||
list<eca_jack_port_data*>::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<jack_default_audio_sample_t*>(target_buffer);
|
||
|
||
eca_jack_node_t* node = get_node(client_id);
|
||
list<eca_jack_port_data*>::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<long int>(jack_get_sample_rate(client_repp));
|
||
/* FIXME: add better control of allocated memory */
|
||
cb_allocated_frames_rep = buffersize_rep = static_cast<long int>(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<void*>(this));
|
||
jack_set_sample_rate_callback(client_repp, eca_jack_srate_cb, static_cast<void*>(this));
|
||
jack_set_buffer_size_callback(client_repp, eca_jack_bsize_cb, static_cast<void*>(this));
|
||
jack_on_shutdown(client_repp, eca_jack_shutdown_cb, static_cast<void*>(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<void*>(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<eca_jack_port_data*>::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<eca_jack_node_t*>::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<eca_jack_node_t*>::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"));
|
||
}
|