diff --git a/AUTHORS b/AUTHORS index 0369a23..591609f 100644 --- a/AUTHORS +++ b/AUTHORS @@ -14,7 +14,7 @@ Paul Davis was the principal author of the JACK API and of the sample implementation contained here. Andy Wingo provided many small patches Fernando Pablo Lopez-Lezcano contributed the capabilities-based code. Jeremy Hall, Steve Harris, and Martin Boer contributed sample clients -and utilities. Jack O'Quin contributed documentation and minor -development work. +and utilities. Jack O'Quin contributed new transport interfaces and +documentation. Many others have contributed patches and/or test results. diff --git a/TODO b/TODO index 39a4178..3747ecb 100644 --- a/TODO +++ b/TODO @@ -18,6 +18,7 @@ TODO for 1.0 (owner) - ensure that UST/MSC pairs work for transport API - resolve helper thread design question? +- API to change buffer size (joq) TODO post-1.0 @@ -25,7 +26,6 @@ TODO post-1.0 TODO general (owner) -- define transport info struct contents (paul) - don't build static libraries of drivers and ip-clients (kaiv) - add explanation of protocol versioning to README.developers (kaiv) - add Paul's graph/subgraph explanation to the design doc (kaiv) @@ -59,5 +59,6 @@ CLOSED (date,who,comment) - call time client before all others (2003/01/28, kaiv, another solution) - finalize discussion on transport API, and implement (2003/08/04, joq) - whether to hide the transport.h structs from clients (2003/08/04, joq) +- define transport info struct contents (2003/08/13, joq) ----------------------------------------------------------------------- diff --git a/configure.in b/configure.in index ca3a80a..b9e5265 100644 --- a/configure.in +++ b/configure.in @@ -13,8 +13,8 @@ dnl micro version = incremented when implementation-only dnl changes are made dnl --- JACK_MAJOR_VERSION=0 -JACK_MINOR_VERSION=76 -JACK_MICRO_VERSION=7 +JACK_MINOR_VERSION=77 +JACK_MICRO_VERSION=0 dnl --- @@ -25,7 +25,7 @@ dnl made to the way libjack communicates with jackd dnl that would break applications linked with an older dnl version of libjack. dnl --- -JACK_PROTOCOL_VERSION=7 +JACK_PROTOCOL_VERSION=8 dnl --- dnl HOWTO: updating the libjack interface version @@ -42,7 +42,7 @@ dnl slacker than this, and closer to those for the JACK version dnl number. dnl --- JACK_API_CURRENT=0 -JACK_API_REVISION=20 +JACK_API_REVISION=21 JACK_API_AGE=0 AC_SUBST(JACK_MAJOR_VERSION) diff --git a/example-clients/Makefile.am b/example-clients/Makefile.am index ae24c2e..36cc2fc 100644 --- a/example-clients/Makefile.am +++ b/example-clients/Makefile.am @@ -34,7 +34,7 @@ bin_PROGRAMS = jack_load \ jack_metro \ jack_showtime \ jack_lsp \ - jackrec \ + $(JACKREC) \ $(JACK_TRANSPORT) if HAVE_SNDFILE diff --git a/jack/engine.h b/jack/engine.h index 10f8829..e8f9dab 100644 --- a/jack/engine.h +++ b/jack/engine.h @@ -24,6 +24,9 @@ #include #include +#define VERBOSE(engine,format,args...) \ + if ((engine)->verbose) fprintf (stderr, format, ## args) + struct _jack_driver; struct _jack_client_internal; struct _jack_port_internal; diff --git a/jack/internal.h b/jack/internal.h index fe45771..8fedfea 100644 --- a/jack/internal.h +++ b/jack/internal.h @@ -98,9 +98,11 @@ typedef struct { jack_transport_state_t transport_state; volatile transport_command_t transport_cmd; + transport_command_t previous_cmd; /* previous transport_cmd */ jack_position_t current_time; /* position for current cycle */ jack_position_t pending_time; /* position for next cycle */ jack_position_t request_time; /* latest requested position */ + jack_unique_t prev_request; /* previous request unique ID */ int new_pos; /* new position this cycle */ unsigned long sync_clients; /* number of is_slowsync clients */ unsigned long sync_remain; /* number of them with sync_poll */ diff --git a/jack/transport.h b/jack/transport.h index 7c67081..1440b5a 100644 --- a/jack/transport.h +++ b/jack/transport.h @@ -41,6 +41,8 @@ typedef enum { } jack_transport_state_t; +typedef unsigned long long jack_unique_t; /**< Unique ID (opaque) */ + /** * Optional struct jack_position_t fields. */ @@ -58,7 +60,8 @@ typedef enum { */ typedef struct { - /* these two cannot be set from clients: the server sets them */ + /* these three cannot be set from clients: the server sets them */ + jack_unique_t unique_1; /**< unique ID */ jack_time_t usecs; /**< monotonic, free-rolling */ jack_nframes_t frame_rate; /**< current frame rate (per second) */ @@ -81,9 +84,8 @@ typedef struct { * the existing structure size and offsets are preserved. */ int padding[14]; - /* When (guard_usecs == usecs) the entire structure is consistent. - * This is set by server. */ - jack_time_t guard_usecs; /**< guard copy of usecs */ + /* When (unique_1 == unique_2) the contents are consistent. */ + jack_unique_t unique_2; /**< unique ID */ } jack_position_t; diff --git a/jackd/transengine.c b/jackd/transengine.c index 96f1150..0ce9d9b 100644 --- a/jackd/transengine.c +++ b/jackd/transengine.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include "transengine.h" @@ -44,8 +45,11 @@ jack_sync_poll_new (jack_engine_t *engine, jack_client_internal_t *client) } // JOQ: I don't like doing this here... - if (engine->control->transport_state == JackTransportRolling) + if (engine->control->transport_state == JackTransportRolling) { engine->control->transport_state = JackTransportStarting; + VERBOSE (engine, "force transport state to Starting\n"); + } + VERBOSE (engine, "polling sync client %lu\n", client->control->id); } /* stop polling a specific slow-sync client @@ -58,6 +62,8 @@ jack_sync_poll_exit (jack_engine_t *engine, jack_client_internal_t *client) client->control->sync_poll = 0; client->control->sync_new = 0; engine->control->sync_remain--; + VERBOSE (engine, "sync poll interrupted for client %lu\n", + client->control->id); } client->control->is_slowsync = 0; engine->control->sync_clients--; @@ -84,6 +90,9 @@ jack_sync_poll_stop (jack_engine_t *engine) //JOQ: check invariant for debugging... assert (poll_count == engine->control->sync_remain); + VERBOSE (engine, + "sync poll halted with %ld clients and %llu usecs remaining\n", + engine->control->sync_remain, engine->control->sync_time_left); engine->control->sync_remain = 0; engine->control->sync_time_left = 0; } @@ -110,6 +119,9 @@ jack_sync_poll_start (jack_engine_t *engine) assert (sync_count == engine->control->sync_clients); engine->control->sync_remain = engine->control->sync_clients; engine->control->sync_time_left = engine->control->sync_timeout; + VERBOSE (engine, "transport Starting, sync poll of %ld clients " + "for %llu usecs\n", engine->control->sync_remain, + engine->control->sync_time_left); } /* check for sync timeout */ @@ -124,12 +136,13 @@ jack_sync_timeout (jack_engine_t *engine) /* compare carefully, jack_time_t is unsigned */ if (ectl->sync_time_left > buf_usecs) { ectl->sync_time_left -= buf_usecs; - return 0; /* continue */ + return FALSE; } /* timed out */ + VERBOSE (engine, "transport sync timeout\n"); ectl->sync_time_left = 0; - return 1; + return TRUE; } /**************** subroutines used by engine.c ****************/ @@ -212,10 +225,12 @@ jack_transport_init (jack_engine_t *engine) engine->timebase_client = NULL; ectl->transport_state = JackTransportStopped; - ectl->transport_cmd = TransportCommandNone; + ectl->transport_cmd = TransportCommandStop; + ectl->previous_cmd = TransportCommandStop; memset (&ectl->current_time, 0, sizeof(ectl->current_time)); memset (&ectl->pending_time, 0, sizeof(ectl->pending_time)); memset (&ectl->request_time, 0, sizeof(ectl->request_time)); + ectl->prev_request = 0; ectl->new_pos = 0; ectl->sync_remain = 0; ectl->sync_clients = 0; @@ -235,6 +250,7 @@ jack_transport_client_exit (jack_engine_t *engine, engine->timebase_client = NULL; engine->control->current_time.valid = 0; engine->control->pending_time.valid = 0; + VERBOSE (engine, "timebase master exit\n"); } if (client->control->is_slowsync) @@ -327,43 +343,44 @@ jack_transport_cycle_end (jack_engine_t *engine) ectl->current_time.frame + ectl->buffer_size; } - /* Handle latest asynchronous requests from the last cycle. - * - * This should ideally use an atomic swap, since commands can - * arrive at any time. There is a small timing window during - * which a request could be ignored inadvertently. Since - * another could have arrived in the previous moment and - * replaced it anyway, we won't bother with . - */ + /* Handle any new transport command from the last cycle. */ cmd = ectl->transport_cmd; - // JOQ: may be able to close the window by eliminating this - // store, but watch out below... - ectl->transport_cmd = TransportCommandNone; - - if (ectl->request_time.usecs) { - /* request_time could change during this copy */ + if (cmd != ectl->previous_cmd) { + ectl->previous_cmd = cmd; + VERBOSE (engine, "transport command: %s\n", + (cmd == TransportCommandStart? "START": "STOP")); + } else + cmd = TransportCommandNone; + + /* See if a position request arrived during the last cycle. + * The request_time could change during the guarded copy. If + * so, we'll handle it now, but mistake it for a new request + * in the following cycle. That may cause an extra sync poll + * cycle, but should work. */ + if (ectl->request_time.unique_1 != ectl->prev_request) { + ectl->prev_request = ectl->request_time.unique_1; jack_transport_copy_position(&ectl->request_time, &ectl->pending_time); - ectl->request_time.usecs = 0; /* empty request buffer */ ectl->new_pos = 1; + VERBOSE (engine, "new transport postition: %lu, id=0x%llx\n", + ectl->pending_time.frame, ectl->pending_time.unique_1); } else ectl->new_pos = 0; /* Promote pending_time to current_time. Maintain the usecs * and frame_rate values, clients may not set them. */ - ectl->pending_time.guard_usecs = - ectl->pending_time.usecs = ectl->current_time.usecs; + ectl->pending_time.usecs = ectl->current_time.usecs; ectl->pending_time.frame_rate = ectl->current_time.frame_rate; ectl->current_time = ectl->pending_time; /* check sync results from previous cycle */ if (ectl->transport_state == JackTransportStarting) { if ((ectl->sync_remain == 0) || - (jack_sync_timeout(engine))) + (jack_sync_timeout(engine))) { ectl->transport_state = JackTransportRolling; - - - + VERBOSE (engine, "transport Rolling, %lld usec" + " left for poll\n", ectl->sync_time_left); + } } /* state transition switch */ @@ -376,21 +393,38 @@ jack_transport_cycle_end (jack_engine_t *engine) jack_sync_poll_start(engine); } else { ectl->transport_state = JackTransportRolling; + VERBOSE (engine, "transport Rolling\n"); } } break; case JackTransportStarting: - case JackTransportRolling: if (cmd == TransportCommandStop) { ectl->transport_state = JackTransportStopped; - jack_sync_poll_stop(engine); + VERBOSE (engine, "transport Stopped\n"); + if (ectl->sync_remain) + jack_sync_poll_stop(engine); } else if (ectl->new_pos) { if (ectl->sync_clients) { ectl->transport_state = JackTransportStarting; jack_sync_poll_start(engine); } else { ectl->transport_state = JackTransportRolling; + VERBOSE (engine, "transport Rolling\n"); + } + } + break; + + case JackTransportRolling: + if (cmd == TransportCommandStop) { + ectl->transport_state = JackTransportStopped; + VERBOSE (engine, "transport Stopped\n"); + if (ectl->sync_remain) + jack_sync_poll_stop(engine); + } else if (ectl->new_pos) { + if (ectl->sync_clients) { + ectl->transport_state = JackTransportStarting; + jack_sync_poll_start(engine); } } break; @@ -406,8 +440,7 @@ jack_transport_cycle_end (jack_engine_t *engine) void jack_transport_cycle_start (jack_engine_t *engine, jack_time_t time) { - engine->control->current_time.guard_usecs = - engine->control->current_time.usecs = time; + engine->control->current_time.usecs = time; } /* on SetSyncTimeout request */ @@ -416,5 +449,6 @@ jack_transport_set_sync_timeout (jack_engine_t *engine, jack_time_t usecs) { engine->control->sync_timeout = usecs; + VERBOSE (engine, "new sync timeout: %llu usecs\n", usecs); return 0; } diff --git a/libjack/transclient.c b/libjack/transclient.c index 23974ea..f856f9b 100644 --- a/libjack/transclient.c +++ b/libjack/transclient.c @@ -23,12 +23,52 @@ #include #include #include +#include #include #include "local.h" /********************* Internal functions *********************/ +/* generate a unique non-zero ID, different for each call */ +jack_unique_t +jack_generate_unique_id (jack_control_t *ectl) +{ + /* The jack_unique_t is an opaque type. Its structure is only + * known here. We use the least significant word of the CPU + * cycle counter. For SMP, I would like to include the + * current thread ID, since only one thread runs on a CPU at a + * time. + * + * But, pthread_self() is broken on my Debian GNU/Linux + * system, it always seems to return 16384. That's useless. + * So, I'm using the process ID instead. With Linux 2.4 and + * Linuxthreads there is an LWP for each thread so this works, + * but is not portable. :-( + */ + volatile union { + jack_unique_t unique; + struct { + pid_t pid; + unsigned long cycle; + } field; + } id; + + id.field.cycle = (unsigned long) get_cycles(); + id.field.pid = getpid(); + + // JOQ: Alternatively, we could keep a sequence number in + // shared memory, using to increment it. I + // really like the simplicity of that approach. But I hate + // forcing JACK to either depend on that pesky header file, or + // maintain its own like ardour does. + + // fprintf (stderr, "unique ID 0x%llx, process %d, cycle 0x%lx\n", + // id.unique, id.field.pid, id.field.cycle); + + return id.unique; +} + static inline void jack_read_frame_time (const jack_client_t *client, jack_frame_timer_t *copy) { @@ -56,7 +96,7 @@ void jack_transport_copy_position (jack_position_t *from, jack_position_t *to) { int tries = 0; - long timeout = 1000000; + long timeout = 1000; do { /* throttle the busy wait if we don't get the answer @@ -67,30 +107,24 @@ jack_transport_copy_position (jack_position_t *from, jack_position_t *to) /* debug code to avoid system hangs... */ if (--timeout == 0) { - jack_error("infinte loop copying position"); + jack_error("hung in loop copying position"); abort(); } } *to = *from; tries++; - } while (to->usecs != to->guard_usecs); + } while (to->unique_1 != to->unique_2); } static inline int jack_transport_request_new_pos (jack_client_t *client, jack_position_t *pos) { - jack_control_t *eng = client->engine; - - // JOQ: I don't think using guard_usecs is good enough. On - // faster machines we could get another request in the same - // microsecond. Better to use something like get_cycles(). - // SMP further complicates the issue. It may require a CPU id - // in addition to the cycle counter for uniqueness. + jack_control_t *ectl = client->engine; /* carefully copy requested postion into shared memory */ - pos->guard_usecs = pos->usecs = jack_get_microseconds(); - jack_transport_copy_position (pos, &eng->request_time); + pos->unique_1 = pos->unique_2 = jack_generate_unique_id(ectl); + jack_transport_copy_position (pos, &ectl->request_time); return 0; } @@ -102,20 +136,20 @@ void jack_call_sync_client (jack_client_t *client) { jack_client_control_t *control = client->control; - jack_control_t *eng = client->engine; + jack_control_t *ectl = client->engine; /* Make sure we are still slow-sync; is_slowsync is set in a * critical section; sync_cb is not. */ - if ((eng->new_pos || control->sync_poll || control->sync_new) && + if ((ectl->new_pos || control->sync_poll || control->sync_new) && control->is_slowsync) { - if (control->sync_cb (eng->transport_state, - &eng->current_time, + if (control->sync_cb (ectl->transport_state, + &ectl->current_time, control->sync_arg)) { if (control->sync_poll) { control->sync_poll = 0; - eng->sync_remain--; + ectl->sync_remain--; } } control->sync_new = 0; @@ -126,8 +160,8 @@ void jack_call_timebase_master (jack_client_t *client) { jack_client_control_t *control = client->control; - jack_control_t *eng = client->engine; - int new_pos = eng->new_pos; + jack_control_t *ectl = client->engine; + int new_pos = ectl->new_pos; /* Make sure we're still the master. Test is_timebase, which * is set in a critical section; timebase_cb is not. */ @@ -138,12 +172,12 @@ jack_call_timebase_master (jack_client_t *client) new_pos = 1; } - if ((eng->transport_state == JackTransportRolling) || + if ((ectl->transport_state == JackTransportRolling) || new_pos) { - control->timebase_cb (eng->transport_state, + control->timebase_cb (ectl->transport_state, control->nframes, - &eng->pending_time, + &ectl->pending_time, new_pos, control->timebase_arg); } @@ -164,10 +198,10 @@ jack_nframes_t jack_frames_since_cycle_start (const jack_client_t *client) { float usecs; - jack_control_t *eng = client->engine; + jack_control_t *ectl = client->engine; - usecs = jack_get_microseconds() - eng->current_time.usecs; - return (jack_nframes_t) floor ((((float) eng->current_time.frame_rate) + usecs = jack_get_microseconds() - ectl->current_time.usecs; + return (jack_nframes_t) floor ((((float) ectl->current_time.frame_rate) / 1000000.0f) * usecs); } @@ -177,13 +211,13 @@ jack_frame_time (const jack_client_t *client) jack_frame_timer_t current; float usecs; jack_nframes_t elapsed; - jack_control_t *eng = client->engine; + jack_control_t *ectl = client->engine; jack_read_frame_time (client, ¤t); usecs = jack_get_microseconds() - current.stamp; elapsed = (jack_nframes_t) - floor ((((float) eng->current_time.frame_rate) + floor ((((float) ectl->current_time.frame_rate) / 1000000.0f) * usecs); return current.frames + elapsed; @@ -299,11 +333,11 @@ jack_transport_locate (jack_client_t *client, jack_nframes_t frame) jack_transport_state_t jack_transport_query (jack_client_t *client, jack_position_t *pos) { - jack_control_t *eng = client->engine; + jack_control_t *ectl = client->engine; /* the guarded copy makes this function work in any thread */ - jack_transport_copy_position (&eng->current_time, pos); - return eng->transport_state; + jack_transport_copy_position (&ectl->current_time, pos); + return ectl->transport_state; } int @@ -352,30 +386,30 @@ void jack_get_transport_info (jack_client_t *client, jack_transport_info_t *info) { - jack_control_t *eng = client->engine; + jack_control_t *ectl = client->engine; /* check that this is the process thread */ - if (client->thread_id != pthread_self()) { + if (!pthread_equal(client->thread_id, pthread_self())) { jack_error("Invalid thread for jack_get_transport_info()."); abort(); /* kill this client */ } - info->usecs = eng->current_time.usecs; - info->frame_rate = eng->current_time.frame_rate; - info->transport_state = eng->transport_state; - info->frame = eng->current_time.frame; - info->valid = (eng->current_time.valid | + info->usecs = ectl->current_time.usecs; + info->frame_rate = ectl->current_time.frame_rate; + info->transport_state = ectl->transport_state; + info->frame = ectl->current_time.frame; + info->valid = (ectl->current_time.valid | JackTransportState | JackTransportPosition); if (info->valid & JackTransportBBT) { - info->bar = eng->current_time.bar; - info->beat = eng->current_time.beat; - info->tick = eng->current_time.tick; - info->bar_start_tick = eng->current_time.bar_start_tick; - info->beats_per_bar = eng->current_time.beats_per_bar; - info->beat_type = eng->current_time.beat_type; - info->ticks_per_beat = eng->current_time.ticks_per_beat; - info->beats_per_minute = eng->current_time.beats_per_minute; + info->bar = ectl->current_time.bar; + info->beat = ectl->current_time.beat; + info->tick = ectl->current_time.tick; + info->bar_start_tick = ectl->current_time.bar_start_tick; + info->beats_per_bar = ectl->current_time.beats_per_bar; + info->beat_type = ectl->current_time.beat_type; + info->ticks_per_beat = ectl->current_time.ticks_per_beat; + info->beats_per_minute = ectl->current_time.beats_per_minute; } } @@ -385,7 +419,7 @@ void jack_set_transport_info (jack_client_t *client, jack_transport_info_t *info) { - jack_control_t *eng = client->engine; + jack_control_t *ectl = client->engine; if (!client->control->is_timebase) { /* not timebase master? */ if (first_error) @@ -400,37 +434,37 @@ jack_set_transport_info (jack_client_t *client, } /* check that this is the process thread */ - if (client->thread_id != pthread_self()) { + if (!pthread_equal(client->thread_id, pthread_self())) { jack_error ("Invalid thread for jack_set_transport_info()."); abort(); /* kill this client */ } /* is there a new state? */ if ((info->valid & JackTransportState) && - (info->transport_state != eng->transport_state)) { + (info->transport_state != ectl->transport_state)) { if (info->transport_state == JackTransportStopped) - eng->transport_cmd = TransportCommandStop; + ectl->transport_cmd = TransportCommandStop; else if (info->transport_state == JackTransportRolling) - eng->transport_cmd = TransportCommandStart; + ectl->transport_cmd = TransportCommandStart; /* silently ignore anything else */ } if (info->valid & JackTransportPosition) - eng->pending_time.frame = info->frame; + ectl->pending_time.frame = info->frame; else - eng->pending_time.frame = eng->current_time.frame; + ectl->pending_time.frame = ectl->current_time.frame; - eng->pending_time.valid = (info->valid & JACK_POSITION_MASK); + ectl->pending_time.valid = (info->valid & JACK_POSITION_MASK); if (info->valid & JackTransportBBT) { - eng->pending_time.bar = info->bar; - eng->pending_time.beat = info->beat; - eng->pending_time.tick = info->tick; - eng->pending_time.bar_start_tick = info->bar_start_tick; - eng->pending_time.beats_per_bar = info->beats_per_bar; - eng->pending_time.beat_type = info->beat_type; - eng->pending_time.ticks_per_beat = info->ticks_per_beat; - eng->pending_time.beats_per_minute = info->beats_per_minute; + ectl->pending_time.bar = info->bar; + ectl->pending_time.beat = info->beat; + ectl->pending_time.tick = info->tick; + ectl->pending_time.bar_start_tick = info->bar_start_tick; + ectl->pending_time.beats_per_bar = info->beats_per_bar; + ectl->pending_time.beat_type = info->beat_type; + ectl->pending_time.ticks_per_beat = info->ticks_per_beat; + ectl->pending_time.beats_per_minute = info->beats_per_minute; } }