From 8fe87b2f75eb4348642ec30b02e152dd741cc9f8 Mon Sep 17 00:00:00 2001 From: joq Date: Sun, 10 Oct 2004 04:18:21 +0000 Subject: [PATCH] [0.99.5] jack_client_open() first step git-svn-id: svn+ssh://jackaudio.org/trunk/jack@768 0c269be4-1314-0410-8aa9-9f06e86f4224 --- configure.in | 4 +- example-clients/simple_client.c | 115 +++++++++++++++++++++++--------- jack/internal.h | 13 ++-- jack/jack.h | 10 +-- jackd/engine.c | 94 ++++++++++++++++++++++---- libjack/.cvsignore | 1 + libjack/ChangeLog | 4 ++ libjack/client.c | 92 ++++++++++++++++++------- libjack/local.h | 3 +- 9 files changed, 253 insertions(+), 83 deletions(-) diff --git a/configure.in b/configure.in index b63197e..b47154e 100644 --- a/configure.in +++ b/configure.in @@ -15,7 +15,7 @@ dnl changes are made dnl --- JACK_MAJOR_VERSION=0 JACK_MINOR_VERSION=99 -JACK_MICRO_VERSION=4 +JACK_MICRO_VERSION=5 dnl --- dnl HOWTO: updating the jack protocol version @@ -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=13 +JACK_PROTOCOL_VERSION=14 dnl --- dnl HOWTO: updating the libjack interface version diff --git a/example-clients/simple_client.c b/example-clients/simple_client.c index 127f873..b0304b5 100644 --- a/example-clients/simple_client.c +++ b/example-clients/simple_client.c @@ -16,30 +16,52 @@ jack_port_t *input_port; jack_port_t *output_port; jack_client_t *client; +/* a simple state machine for this client */ +volatile enum { + Init, + Run, + Exit +} client_state = Init; + /** - * The process callback for this JACK application. - * It is called by JACK at the appropriate times. + * The process callback for this JACK application is called in a + * special realtime thread once for each audio cycle. + * + * This client follows a simple rule: when the JACK transport is + * running, copy the input port to the output. When it stops, exit. */ int process (jack_nframes_t nframes, void *arg) { - jack_default_audio_sample_t *out = (jack_default_audio_sample_t *) jack_port_get_buffer (output_port, nframes); - jack_default_audio_sample_t *in = (jack_default_audio_sample_t *) jack_port_get_buffer (input_port, nframes); + jack_default_audio_sample_t *in, *out; + jack_transport_state_t ts = jack_transport_query(client, NULL); + + if (ts == JackTransportRolling) { + + if (client_state == Init) + client_state = Run; - memcpy (out, in, sizeof (jack_default_audio_sample_t) * nframes); + in = jack_port_get_buffer (input_port, nframes); + out = jack_port_get_buffer (output_port, nframes); + memcpy (out, in, + sizeof (jack_default_audio_sample_t) * nframes); + + } else if (ts == JackTransportStopped) { + + if (client_state == Run) + client_state = Exit; + } return 0; } /** - * This is the shutdown callback for this JACK application. - * It is called by JACK if the server ever shuts down or + * JACK calls this shutdown_callback if the server ever shuts down or * decides to disconnect the client. */ void jack_shutdown (void *arg) { - exit (1); } @@ -47,17 +69,34 @@ int main (int argc, char *argv[]) { const char **ports; - - if (argc < 2) { - fprintf (stderr, "usage: jack_simple_client \n"); - return 1; + const char *client_name; + jack_status_t status; + + if (argc >= 2) { /* session name specified? */ + client_name = argv[1]; + } else { /* use basename of argv[0] */ + client_name = strrchr(argv[0], '/'); + if (client_name == 0) { + client_name = argv[0]; + } else { + client_name++; + } } - /* try to become a client of the JACK server */ + /* open a client connection to the JACK server */ - if ((client = jack_client_new (argv[1])) == 0) { - fprintf (stderr, "jack server not running?\n"); - return 1; + client = jack_client_open (client_name, 0, &status, NULL, NULL); + if (client == NULL) { + fprintf (stderr, "jack_client_open() failed, status = %d\n", + status); + exit (1); + } + if (status & JackServerStarted) { + fprintf (stderr, "JACK server started\n"); + } + if (status & JackNameNotUnique) { + client_name = jack_get_client_name(client); + fprintf (stderr, "unique name `%s' assigned\n", client_name); } /* tell the JACK server to call `process()' whenever @@ -81,25 +120,31 @@ main (int argc, char *argv[]) /* create two ports */ - input_port = jack_port_register (client, "input", JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0); - output_port = jack_port_register (client, "output", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); + input_port = jack_port_register (client, "input", + JACK_DEFAULT_AUDIO_TYPE, + JackPortIsInput, 0); + output_port = jack_port_register (client, "output", + JACK_DEFAULT_AUDIO_TYPE, + JackPortIsOutput, 0); - /* tell the JACK server that we are ready to roll */ + /* Tell the JACK server that we are ready to roll. Our + * process() callback will start running now. */ if (jack_activate (client)) { fprintf (stderr, "cannot activate client"); - return 1; + exit (1); } - /* connect the ports. Note: you can't do this before - the client is activated, because we can't allow - connections to be made to clients that aren't - running. + /* connect the ports. Note: you can't do this before the + client is activated, because we can't allow connections to + be made to clients that aren't running. */ - if ((ports = jack_get_ports (client, NULL, NULL, JackPortIsPhysical|JackPortIsOutput)) == NULL) { - fprintf(stderr, "Cannot find any physical capture ports\n"); - exit(1); + ports = jack_get_ports (client, NULL, NULL, + JackPortIsPhysical|JackPortIsOutput); + if (ports == NULL) { + fprintf(stderr, "no physical capture ports\n"); + exit (1); } if (jack_connect (client, ports[0], jack_port_name (input_port))) { @@ -108,9 +153,11 @@ main (int argc, char *argv[]) free (ports); - if ((ports = jack_get_ports (client, NULL, NULL, JackPortIsPhysical|JackPortIsInput)) == NULL) { - fprintf(stderr, "Cannot find any physical playback ports\n"); - exit(1); + ports = jack_get_ports (client, NULL, NULL, + JackPortIsPhysical|JackPortIsInput); + if (ports == NULL) { + fprintf(stderr, "no physical playback ports\n"); + exit (1); } if (jack_connect (client, jack_port_name (output_port), ports[0])) { @@ -119,10 +166,12 @@ main (int argc, char *argv[]) free (ports); - /* Since this is just a toy, run for a few seconds, then finish */ + /* keep running until the transport stops */ + + while (client_state != Exit) { + sleep (1); + } - sleep (10); jack_client_close (client); exit (0); } - diff --git a/jack/internal.h b/jack/internal.h index b018bf7..802a602 100644 --- a/jack/internal.h +++ b/jack/internal.h @@ -170,7 +170,7 @@ typedef volatile struct { volatile jack_client_id_t id; /* w: engine r: engine and client */ volatile jack_nframes_t nframes; /* w: engine r: client */ volatile jack_client_state_t state; /* w: engine and client r: engine */ - volatile int8_t name[JACK_CLIENT_NAME_SIZE]; + volatile char name[JACK_CLIENT_NAME_SIZE]; volatile ClientType type; /* w: engine r: engine and client */ volatile int8_t active; /* w: engine r: engine and client */ volatile int8_t dead; /* r/w: engine */ @@ -228,6 +228,7 @@ typedef struct { int32_t load; ClientType type; + jack_options_t options; char name[JACK_CLIENT_NAME_SIZE]; char object_path[PATH_MAX+1]; @@ -237,10 +238,10 @@ typedef struct { typedef struct { - int32_t status; - + int32_t status; /* messy name overloading */ uint32_t protocol_v; + jack_status_t open_status; /* used for open() */ jack_shm_info_t client_shm; jack_shm_info_t engine_shm; @@ -249,6 +250,8 @@ typedef struct { int32_t realtime; int32_t realtime_priority; + char name[JACK_CLIENT_NAME_SIZE]; /* unique name, if assigned */ + /* these two are valid only if the connect request was for type == ClientDriver. */ @@ -323,8 +326,8 @@ struct _jack_request { int32_t status; }; -/* per-client structure allocated in the server's address space - * its here because its not part of the engine structure. +/* Per-client structure allocated in the server's address space. + * It's here because its not part of the engine structure. */ typedef struct _jack_client_internal { diff --git a/jack/jack.h b/jack/jack.h index e74e0df..250394a 100644 --- a/jack/jack.h +++ b/jack/jack.h @@ -58,10 +58,10 @@ extern "C" { * possible concurrent server instances. If unspecified, "default" is * assumed. Server names are unique to each user. * - * @param start_command (if non-NULL) is a command line to use if the - * server was not running and must be started. Defining - * $JACK_NO_START_SERVER in the environment disables automatic server - * creation. + * @param server_command (if non-NULL) is a command line to start the + * server if it was not running. The @ref JackNoStartServer option + * disables automatic server creation, as does defining + * $JACK_NO_START_SERVER in the environment. * * @return Opaque client handle if successful. If this is NULL, the * open operation failed, and the caller is not a JACK client. @@ -70,7 +70,7 @@ jack_client_t *jack_client_open (const char *client_name, jack_options_t options, jack_status_t *status, const char *server_name, - const char *start_command); + const char *server_command); /** * Attempt to become an external client of the Jack server. diff --git a/jackd/engine.c b/jackd/engine.c index 47ae7ec..048ff38 100644 --- a/jackd/engine.c +++ b/jackd/engine.c @@ -1001,29 +1001,96 @@ jack_client_unload (jack_client_internal_t *client) } } +static jack_client_internal_t * +jack_client_lookup_name (jack_engine_t *engine, char *name) +{ + JSList *node; + + jack_lock_graph (engine); + for (node = engine->clients; node; node = jack_slist_next (node)) { + jack_client_internal_t *client = node->data; + + if (strncmp(name, (char *) client->control->name, + JACK_CLIENT_NAME_SIZE) == 0) { + jack_unlock_graph (engine); + return client; /* name exists */ + } + } + jack_unlock_graph (engine); + return NULL; /* not found */ +} + +/* generate a unique client name + * + * returns 0 if successful, updates name in place + */ +static inline int +jack_generate_unique_name (jack_engine_t *engine, char *name) +{ + int tens, ones; + int length = strlen (name); + + if (length > JACK_CLIENT_NAME_SIZE - 4) { + jack_error ("%s exists and is too long to make unique", name); + return 1; /* failure */ + } + + /* generate a unique name by appending "-01".."-99" */ + name[length++] = '-'; + tens = length++; + ones = length++; + name[tens] = '0'; + name[ones] = '1'; + name[length] = '\0'; + while (jack_client_lookup_name (engine, name)) { + if (name[ones] == '9') { + if (name[tens] == '9') { + jack_error ("client %s has 99 extra" + " instances already", name); + return 1; /* give up */ + } + name[tens]++; + name[ones] = '0'; + } else { + name[ones]++; + } + } + return 0; +} + static jack_client_internal_t * setup_client (jack_engine_t *engine, int client_fd, jack_client_connect_request_t *req, jack_client_connect_result_t *res) { - JSList *node; - jack_client_internal_t *client = NULL; + jack_client_internal_t *client; - for (node = engine->clients; node; node = jack_slist_next (node)) { - client = (jack_client_internal_t *) node->data; + /* Since this thread already holds the request_lock, no other + * new client will be created at the same time. So, testing a + * name for uniqueness is valid here. */ + + //JOQ: watch out for internal clients, they come here too + + if (jack_client_lookup_name (engine, req->name)) { - if (strncmp(req->name, (char*)client->control->name, - sizeof(req->name)) == 0) { - jack_error ("cannot create new client; %s already" - " exists", client->control->name); + res->open_status |= JackNameNotUnique; + + if (req->options & JackUseExactName) { + jack_error ("cannot create new client; %s already" + " exists", req->name); return NULL; } + + if (jack_generate_unique_name(engine, req->name)) { + return NULL; /* failure */ + } } - if ((client = jack_setup_client_control (engine, client_fd, req)) - == NULL) { + /* create a client struct for this client name */ + client = jack_setup_client_control (engine, client_fd, req); + if (client == NULL) { jack_error ("cannot create new client object"); - return 0; + return NULL; } VERBOSE (engine, "new client: %s, id = %" PRIu32 @@ -1036,6 +1103,7 @@ setup_client (jack_engine_t *engine, int client_fd, res->engine_shm = engine->control_shm; res->realtime = engine->control->real_time; res->realtime_priority = engine->rtpriority - 1; + strncpy (res->name, req->name, sizeof(res->name)); #ifdef JACK_USE_MACH_THREADS /* specific resources for server/client real-time thread @@ -1271,8 +1339,10 @@ handle_new_client (jack_engine_t *engine, int client_fd) jack_client_internal_t *client; jack_client_connect_request_t req; jack_client_connect_result_t res; - + + //JOQ: fix messy overloading of `status' res.status = 0; + res.open_status = 0; if (read (client_fd, &req, sizeof (req)) != sizeof (req)) { jack_error ("cannot read connection request from client"); diff --git a/libjack/.cvsignore b/libjack/.cvsignore index 09980ae..a992f83 100644 --- a/libjack/.cvsignore +++ b/libjack/.cvsignore @@ -3,4 +3,5 @@ Makefile Makefile.in *.lo +*.loT *.la diff --git a/libjack/ChangeLog b/libjack/ChangeLog index cc67a2c..da66857 100644 --- a/libjack/ChangeLog +++ b/libjack/ChangeLog @@ -1,3 +1,7 @@ +2004-10-08 Jack O'Quin + + * new API functions: jack_client_open(), jack_get_client_name() + 2004-09-15 Jack O'Quin * new API functions from diff --git a/libjack/client.c b/libjack/client.c index 5f5a964..fcab698 100644 --- a/libjack/client.c +++ b/libjack/client.c @@ -330,10 +330,11 @@ jack_handle_reorder (jack_client_t *client, jack_event_t *event) } static int -server_connect (int which) +server_connect (const char *server_name) { int fd; struct sockaddr_un addr; + int which = 0; if ((fd = socket (AF_UNIX, SOCK_STREAM, 0)) < 0) { jack_error ("cannot create client socket (%s)", @@ -341,6 +342,7 @@ server_connect (int which) return -1; } + //JOQ: use server_name as part of socket path addr.sun_family = AF_UNIX; snprintf (addr.sun_path, sizeof (addr.sun_path) - 1, "%s/jack_%d_%d", jack_server_dir, getuid (), which); @@ -406,7 +408,7 @@ server_event_connect (jack_client_t *client) /* Exec the JACK server in this process. Does not return. */ static void -_start_server (void) +_start_server (const char *server_command) { FILE* fp = 0; char filename[255]; @@ -420,6 +422,10 @@ _start_server (void) int good = 0; int ret; + //JOQ: + // if (start_command) { + // parse user-supplied command + // } else { snprintf(filename, 255, "%s/.jackdrc", getenv("HOME")); fp = fopen(filename, "r"); @@ -453,12 +459,12 @@ _start_server (void) #endif /* USE_CAPABILITIES */ } else { result = strcspn(arguments, " "); - command = (char*)malloc(result+1); + command = (char *) malloc(result+1); strncpy(command, arguments, result); command[result] = '\0'; } - argv = (char**)malloc(255); + argv = (char **) malloc (255); while(1) { /* insert -T into arguments */ @@ -491,12 +497,10 @@ _start_server (void) } int -start_server (void) +start_server (jack_options_t options, const char *server_command) { - /* Only fork() a server when $JACK_START_SERVER is defined and - * $JACK_NO_START_SERVER is not. */ - if (getenv("JACK_START_SERVER") == NULL || - getenv("JACK_NO_START_SERVER") != NULL) { + if ((options & JackNoStartServer) + || (getenv("JACK_NO_START_SERVER") != NULL)) { return 1; } @@ -513,7 +517,7 @@ start_server (void) case 0: /* child process */ switch (fork()) { case 0: /* grandchild process */ - _start_server(); + _start_server(server_command); _exit (99); /* exec failed */ case -1: _exit (98); @@ -531,7 +535,9 @@ start_server (void) static int jack_request_client (ClientType type, const char* client_name, const char* so_name, const char* so_data, - jack_client_connect_result_t *res, int *req_fd) + jack_client_connect_result_t *res, int *req_fd, + jack_options_t options, jack_status_t *status, + const char *server_name, const char *server_command) { jack_client_connect_request_t req; @@ -539,6 +545,8 @@ jack_request_client (ClientType type, const char* client_name, memset (&req, 0, sizeof (req)); + req.options = options; + if (strlen (client_name) >= sizeof (req.name)) { jack_error ("\"%s\" is too long to be used as a JACK client" " name.\n" @@ -563,9 +571,9 @@ jack_request_client (ClientType type, const char* client_name, return -1; } - if ((*req_fd = server_connect (0)) < 0) { + if ((*req_fd = server_connect (server_name)) < 0) { int trys; - if (start_server()) { + if (start_server(options, server_command)) { goto fail; } trys = 5; @@ -574,7 +582,8 @@ jack_request_client (ClientType type, const char* client_name, if (--trys < 0) { goto fail; } - } while ((*req_fd = server_connect (0)) < 0); + } while ((*req_fd = server_connect (server_name)) < 0); + *status |= JackServerStarted; } req.load = TRUE; @@ -603,6 +612,9 @@ jack_request_client (ClientType type, const char* client_name, goto fail; } + *status |= res->open_status; /* return server status bits */ + + //JOQ: fixme overloading confusion if (res->status) { jack_error ("could not attach as client " "(duplicate client name?)"); @@ -707,17 +719,27 @@ jack_attach_port_segment (jack_client_t *client, jack_port_type_id_t ptid) } jack_client_t * -jack_client_new (const char *client_name) +jack_client_open (const char *client_name, + jack_options_t options, + jack_status_t *status, + const char *server_name, + const char *server_command) { int req_fd = -1; int ev_fd = -1; jack_client_connect_result_t res; jack_client_t *client; jack_port_type_id_t ptid; + jack_status_t my_status; - /* external clients need this initialized; internal clients - will use the setup in the server's address space. - */ + if (status == NULL) /* no status from caller? */ + status = &my_status; /* use local status word */ + + *status = 0; + + /* External clients need this initialized. It is already set + * up in the server's address space for internal clients. + */ jack_init_time (); if (jack_initialize_shm ()) { @@ -726,17 +748,19 @@ jack_client_new (const char *client_name) } if (jack_request_client (ClientExternal, client_name, "", "", - &res, &req_fd)) { + &res, &req_fd, options, status, + server_name, server_command)) { return NULL; } client = jack_client_alloc (); - + strcpy (client->name, res.name); strcpy (client->fifo_prefix, res.fifo_prefix); client->request_fd = req_fd; - - client->pollfd[EVENT_POLL_INDEX].events = POLLIN|POLLERR|POLLHUP|POLLNVAL; - client->pollfd[WAIT_POLL_INDEX].events = POLLIN|POLLERR|POLLHUP|POLLNVAL; + client->pollfd[EVENT_POLL_INDEX].events = + POLLIN|POLLERR|POLLHUP|POLLNVAL; + client->pollfd[WAIT_POLL_INDEX].events = + POLLIN|POLLERR|POLLHUP|POLLNVAL; /* attach the engine control/info block */ client->engine_shm = res.engine_shm; @@ -824,13 +848,31 @@ jack_client_new (const char *client_name) return 0; } +jack_client_t * +jack_client_new (const char *client_name) +{ + jack_options_t options = JackUseExactName; + if (getenv("JACK_START_SERVER") == NULL) + options |= JackNoStartServer; + + return jack_client_open (client_name, options, NULL, NULL, NULL); +} + +char * +jack_get_client_name (jack_client_t *client) +{ + return client->name; +} + int jack_internal_client_new (const char *client_name, const char *so_name, const char *so_data) { jack_client_connect_result_t res; int req_fd; - return jack_request_client (ClientInternal, client_name, so_name, so_data, &res, &req_fd); + return jack_request_client (ClientInternal, client_name, so_name, + so_data, &res, &req_fd, + 0, NULL, NULL, NULL); } void @@ -842,7 +884,7 @@ jack_internal_client_close (const char *client_name) req.load = FALSE; snprintf (req.name, sizeof (req.name), "%s", client_name); - if ((fd = server_connect (0)) < 0) { + if ((fd = server_connect (NULL)) < 0) { return; } diff --git a/libjack/local.h b/libjack/local.h index 36f4b40..818e4e9 100644 --- a/libjack/local.h +++ b/libjack/local.h @@ -31,7 +31,8 @@ struct _jack_client { char thread_ok : 1; char first_active : 1; pthread_t thread_id; - + char name[JACK_CLIENT_NAME_SIZE]; + #ifdef JACK_USE_MACH_THREADS /* specific ressources for server/client real-time thread communication */ mach_port_t clienttask, bp, serverport, replyport;