diff --git a/example-clients/midi_latency_test.c b/example-clients/midi_latency_test.c index feb24609..f5b274f7 100644 --- a/example-clients/midi_latency_test.c +++ b/example-clients/midi_latency_test.c @@ -85,6 +85,8 @@ const char *SOURCE_SIGNAL_SEMAPHORE = "signal_semaphore"; const char *SOURCE_WAIT_SEMAPHORE = "wait_semaphore"; jack_client_t *client; +semaphore_t connect_semaphore; +volatile int connections_established; const char *error_message; const char *error_source; jack_nframes_t highest_latency; @@ -111,6 +113,8 @@ char *program_name; jack_port_t *remote_in_port; jack_port_t *remote_out_port; size_t samples; +const char *target_in_port_name; +const char *target_out_port_name; int timeout; jack_nframes_t total_latency; jack_time_t total_latency_time; @@ -133,6 +137,11 @@ set_process_error(const char *source, const char *message); static int signal_semaphore(semaphore_t semaphore); +static jack_port_t * +update_connection(jack_port_t *remote_port, int connected, + jack_port_t *local_port, jack_port_t *current_port, + const char *target_name); + static int wait_semaphore(semaphore_t semaphore, int block); @@ -217,6 +226,53 @@ handle_info(const char *message) /* Suppress info */ } +static void +handle_port_connection_change(jack_port_id_t port_id_1, + jack_port_id_t port_id_2, int connected, + void *arg) +{ + jack_port_t *port_1; + jack_port_t *port_2; + if ((remote_in_port != NULL) && (remote_out_port != NULL)) { + return; + } + port_1 = jack_port_by_id(client, port_id_1); + port_2 = jack_port_by_id(client, port_id_2); + + /* The 'update_connection' call is not RT-safe. It calls + 'jack_port_get_connections' and 'jack_free'. This might be a problem + with JACK 1, as this callback runs in the process thread in JACK 1. */ + + if (port_1 == in_port) { + remote_in_port = update_connection(port_2, connected, in_port, + remote_in_port, + target_in_port_name); + } else if (port_2 == in_port) { + remote_in_port = update_connection(port_1, connected, in_port, + remote_in_port, + target_in_port_name); + } else if (port_1 == out_port) { + remote_out_port = update_connection(port_2, connected, out_port, + remote_out_port, + target_out_port_name); + } else if (port_2 == out_port) { + remote_out_port = update_connection(port_1, connected, out_port, + remote_out_port, + target_out_port_name); + } + if ((remote_in_port != NULL) && (remote_out_port != NULL)) { + connections_established = 1; + if (! signal_semaphore(connect_semaphore)) { + /* Sigh ... */ + die("post_semaphore", get_semaphore_error()); + } + if (! signal_semaphore(init_semaphore)) { + /* Sigh ... */ + die("post_semaphore", get_semaphore_error()); + } + } +} + static int handle_process(jack_nframes_t frames, void *arg) { @@ -342,6 +398,10 @@ static void handle_signal(int sig) { process_state = -2; + if (! signal_semaphore(connect_semaphore)) { + /* Sigh ... */ + die(SOURCE_SIGNAL_SEMAPHORE, get_semaphore_error()); + } if (! signal_semaphore(process_semaphore)) { /* Sigh ... */ die(SOURCE_SIGNAL_SEMAPHORE, get_semaphore_error()); @@ -364,12 +424,14 @@ output_error(const char *source, const char *message) static void output_usage(void) { - fprintf(stderr, "Usage: %s [options] out-port-name in-port-name\n\n" + fprintf(stderr, "Usage: %s [options] [out-port-name in-port-name]\n\n" "\t-h, --help print program usage\n" - "\t-m, --message-size=size set size of MIDI messages to send\n" - "\t-s, --samples=n number of MIDI messages to send\n" - "\t-t, --timeout=seconds wait time before giving up on message\n" - "\n", program_name); + "\t-m, --message-size=size set size of MIDI messages to send " + "(default: 3)\n" + "\t-s, --samples=n number of MIDI messages to send " + "(default: 1024)\n" + "\t-t, --timeout=seconds message timeout (default: 5)\n\n", + program_name); } static unsigned long @@ -445,6 +507,46 @@ signal_semaphore(semaphore_t semaphore) } +static jack_port_t * +update_connection(jack_port_t *remote_port, int connected, + jack_port_t *local_port, jack_port_t *current_port, + const char *target_name) +{ + if (connected) { + if (current_port) { + return current_port; + } + if (target_name) { + if (strcmp(target_name, jack_port_name(remote_port))) { + return NULL; + } + } + return remote_port; + } + if (! strcmp(jack_port_name(remote_port), jack_port_name(current_port))) { + const char **port_names; + if (target_name) { + return NULL; + } + port_names = jack_port_get_connections(local_port); + if (port_names == NULL) { + return NULL; + } + + /* If a connected port is disconnected and other ports are still + connected, then we take the first port name in the array and use it + as our remote port. It's a dumb implementation. */ + current_port = jack_port_by_name(client, port_names[0]); + + jack_free(port_names); + if (current_port == NULL) { + /* Sigh */ + die("jack_port_by_name", "failed to get port by name"); + } + } + return current_port; +} + static int wait_semaphore(semaphore_t semaphore, int block) { @@ -494,11 +596,15 @@ main(int argc, char **argv) {"samples", 1, NULL, 's'}, {"timeout", 1, NULL, 't'} }; + size_t name_arg_count; char *option_string = "hm:s:t:"; int show_usage = 0; + connections_established = 0; error_message = NULL; message_size = 3; program_name = argv[0]; + remote_in_port = 0; + remote_out_port = 0; samples = 1024; timeout = 5; @@ -536,7 +642,17 @@ main(int argc, char **argv) } } parse_port_names: - if ((argc - optind) != 2) { + name_arg_count = argc - optind; + switch (name_arg_count) { + case 2: + target_in_port_name = argv[optind + 1]; + target_out_port_name = argv[optind]; + break; + case 0: + target_in_port_name = 0; + target_out_port_name = 0; + break; + default: output_usage(); return EXIT_FAILURE; } @@ -599,18 +715,6 @@ main(int argc, char **argv) error_source = "jack_client_open"; goto free_message_2; } - remote_in_port = jack_port_by_name(client, argv[optind + 1]); - if (remote_in_port == NULL) { - error_message = "invalid port name"; - error_source = argv[optind + 1]; - goto close_client; - } - remote_out_port = jack_port_by_name(client, argv[optind]); - if (remote_out_port == NULL) { - error_message = "invalid port name"; - error_source = argv[optind]; - goto close_client; - } in_port = jack_port_register(client, "in", JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0); if (in_port == NULL) { @@ -635,16 +739,29 @@ main(int argc, char **argv) error_source = "jack_set_xrun_callback"; goto unregister_out_port; } + if (jack_set_port_connect_callback(client, handle_port_connection_change, + NULL)) { + error_message = "failed to set port connection callback"; + error_source = "jack_set_port_connect_callback"; + goto unregister_out_port; + } jack_on_shutdown(client, handle_shutdown, NULL); jack_set_info_function(handle_info); process_state = 0; - init_semaphore = create_semaphore(0); - if (init_semaphore == NULL) { + + connect_semaphore = create_semaphore(0); + if (connect_semaphore == NULL) { error_message = get_semaphore_error(); error_source = "create_semaphore"; goto unregister_out_port; } - process_semaphore = create_semaphore(1); + init_semaphore = create_semaphore(1); + if (init_semaphore == NULL) { + error_message = get_semaphore_error(); + error_source = "create_semaphore"; + goto destroy_connect_semaphore;; + } + process_semaphore = create_semaphore(2); if (process_semaphore == NULL) { error_message = get_semaphore_error(); error_source = "create_semaphore"; @@ -655,33 +772,39 @@ main(int argc, char **argv) error_source = "jack_activate"; goto destroy_process_semaphore; } - if (jack_connect(client, jack_port_name(out_port), - jack_port_name(remote_out_port))) { - error_message = "could not connect MIDI out port"; - error_source = "jack_connect"; - goto deactivate_client; - } - if (jack_connect(client, jack_port_name(remote_in_port), - jack_port_name(in_port))) { - error_message = "could not connect MIDI in port"; - error_source = "jack_connect"; - goto deactivate_client; - } - if (! signal_semaphore(init_semaphore)) { - error_message = get_semaphore_error(); - error_source = "post_semaphore"; - goto deactivate_client; + if (name_arg_count) { + if (jack_connect(client, jack_port_name(out_port), + target_out_port_name)) { + error_message = "could not connect MIDI out port"; + error_source = "jack_connect"; + goto deactivate_client; + } + if (jack_connect(client, target_in_port_name, + jack_port_name(in_port))) { + error_message = "could not connect MIDI in port"; + error_source = "jack_connect"; + goto deactivate_client; + } } if (! register_signal_handler(handle_signal)) { error_message = strerror(errno); error_source = "register_signal_handler"; goto deactivate_client; } - if (wait_semaphore(process_semaphore, 1) == -1) { + printf("Waiting for connections ...\n"); + if (wait_semaphore(connect_semaphore, 1) == -1) { error_message = get_semaphore_error(); error_source = "wait_semaphore"; goto deactivate_client; } + if (connections_established) { + printf("Waiting for test completion ...\n\n"); + if (wait_semaphore(process_semaphore, 1) == -1) { + error_message = get_semaphore_error(); + error_source = "wait_semaphore"; + goto deactivate_client; + } + } if (! register_signal_handler(SIG_DFL)) { error_message = strerror(errno); error_source = "register_signal_handler"; @@ -766,24 +889,20 @@ main(int argc, char **argv) } deactivate_client: jack_deactivate(client); - - /* Output this information after deactivation to prevent two threads - from accessing data at the same time. */ - if (process_state != 2) { - printf("\nMessages sent: %d\nMessages received: %d\n", messages_sent, - messages_received); - } + printf("\nMessages sent: %d\nMessages received: %d\n", messages_sent, + messages_received); if (unexpected_messages) { printf("Unexpected messages received: %d\n", unexpected_messages); } if (xrun_count) { printf("Xruns: %d\n", xrun_count); } - destroy_process_semaphore: - destroy_semaphore(process_semaphore, 1); + destroy_semaphore(process_semaphore, 2); destroy_init_semaphore: - destroy_semaphore(init_semaphore, 0); + destroy_semaphore(init_semaphore, 1); + destroy_connect_semaphore: + destroy_semaphore(connect_semaphore, 0); unregister_out_port: jack_port_unregister(client, out_port); unregister_in_port: