jmcore: one JACK client per pair

This reduces latency because JACK client is run only once per cycle
This commit is contained in:
Nedko Arnaudov 2010-04-11 13:36:26 +03:00
parent da63d80b84
commit 919e01fd8b
1 changed files with 85 additions and 196 deletions

281
jmcore.c
View File

@ -40,17 +40,14 @@ extern const struct dbus_interface_descriptor g_interface;
static const char * g_dbus_unique_name; static const char * g_dbus_unique_name;
static dbus_object_path g_object; static dbus_object_path g_object;
static bool g_quit; static bool g_quit;
static jack_client_t * g_jack_client;
static bool g_jack_client_died;
pthread_mutex_t g_mutex; struct list_head g_pairs;
struct list_head g_new_pairs;
struct list_head g_active_pairs;
struct port_pair struct port_pair
{ {
struct list_head siblings; struct list_head siblings;
bool active; jack_client_t * client;
bool dead;
bool midi; bool midi;
jack_port_t * input_port; jack_port_t * input_port;
jack_port_t * output_port; jack_port_t * output_port;
@ -58,94 +55,66 @@ struct port_pair
char * output_port_name; char * output_port_name;
}; };
#define pair_ptr ((struct port_pair *)arg)
void shutdown_callback(void * arg) void shutdown_callback(void * arg)
{ {
g_jack_client_died = true; pair_ptr->dead = true;
} }
int process_callback(jack_nframes_t nframes, void * arg) int process_callback(jack_nframes_t nframes, void * arg)
{ {
struct list_head * node_ptr;
struct list_head * temp_node_ptr;
struct port_pair * pair_ptr;
bool locked;
void * input; void * input;
void * output; void * output;
jack_midi_event_t midi_event; jack_midi_event_t midi_event;
jack_nframes_t midi_event_index; jack_nframes_t midi_event_index;
locked = pthread_mutex_trylock(&g_mutex) == 0; input = jack_port_get_buffer(pair_ptr->input_port, nframes);
output = jack_port_get_buffer(pair_ptr->output_port, nframes);
if (locked) if (!pair_ptr->midi)
{ {
while (!list_empty(&g_new_pairs)) memcpy(output, input, nframes * sizeof(jack_default_audio_sample_t));
{
node_ptr = g_new_pairs.next;
list_del(node_ptr);
list_add_tail(node_ptr, &g_active_pairs);
pair_ptr = list_entry(node_ptr, struct port_pair, siblings);
pair_ptr->active = true;
}
} }
else
list_for_each_safe(node_ptr, temp_node_ptr, &g_active_pairs)
{ {
pair_ptr = list_entry(node_ptr, struct port_pair, siblings); jack_midi_clear_buffer(output);
if (locked && !pair_ptr->active) midi_event_index = 0;
while (jack_midi_event_get(&midi_event, input, midi_event_index) == 0)
{ {
list_del_init(node_ptr); jack_midi_event_write(output, midi_event.time, midi_event.buffer, midi_event.size);
continue; midi_event_index++;
} }
input = jack_port_get_buffer(pair_ptr->input_port, nframes);
output = jack_port_get_buffer(pair_ptr->output_port, nframes);
if (!pair_ptr->midi)
{
memcpy(output, input, nframes * sizeof(jack_default_audio_sample_t));
}
else
{
jack_midi_clear_buffer(output);
midi_event_index = 0;
while (jack_midi_event_get(&midi_event, input, midi_event_index) == 0)
{
jack_midi_event_write(output, midi_event.time, midi_event.buffer, midi_event.size);
midi_event_index++;
}
}
}
if (locked)
{
pthread_mutex_unlock(&g_mutex);
} }
return 0; return 0;
} }
static void free_pair(struct port_pair * pair_ptr) #undef pair_ptr
{
if (g_jack_client != NULL)
{
jack_port_unregister(g_jack_client, pair_ptr->input_port);
jack_port_unregister(g_jack_client, pair_ptr->output_port);
}
static void destroy_pair(struct port_pair * pair_ptr)
{
list_del(&pair_ptr->siblings);
jack_client_close(pair_ptr->client);
free(pair_ptr->input_port_name); free(pair_ptr->input_port_name);
free(pair_ptr->output_port_name); free(pair_ptr->output_port_name);
free(pair_ptr); free(pair_ptr);
} }
static void eat_pair_list(struct list_head * list) static void bury_zombie_pairs(void)
{ {
struct list_head * node_ptr; struct list_head * node_ptr;
struct list_head * temp_node_ptr;
struct port_pair * pair_ptr;
while (!list_empty(list)) list_for_each_safe(node_ptr, temp_node_ptr, &g_pairs)
{ {
node_ptr = list->next; pair_ptr = list_entry(node_ptr, struct port_pair, siblings);
list_del(node_ptr); if (pair_ptr->dead)
free_pair(list_entry(node_ptr, struct port_pair, siblings)); {
log_info("Bury zombie '%s':'%s'", pair_ptr->input_port_name, pair_ptr->output_port_name);
destroy_pair(pair_ptr);
}
} }
} }
@ -243,16 +212,7 @@ int main(int argc, char ** argv)
{ {
int ret; int ret;
INIT_LIST_HEAD(&g_new_pairs); INIT_LIST_HEAD(&g_pairs);
INIT_LIST_HEAD(&g_active_pairs);
ret = pthread_mutex_init(&g_mutex, NULL);
if (ret != 0)
{
log_error("Mutex initialization failed with %d.", ret);
ret = 1;
goto exit;
}
install_term_signal_handler(SIGTERM, false); install_term_signal_handler(SIGTERM, false);
install_term_signal_handler(SIGINT, true); install_term_signal_handler(SIGINT, true);
@ -260,74 +220,36 @@ int main(int argc, char ** argv)
if (!connect_dbus()) if (!connect_dbus())
{ {
log_error("Failed to connect to D-Bus"); log_error("Failed to connect to D-Bus");
ret = 1; return 1;
goto destroy_mutex;
} }
while (!g_quit) while (!g_quit)
{ {
dbus_connection_read_write_dispatch(g_dbus_connection, 50); dbus_connection_read_write_dispatch(g_dbus_connection, 50);
bury_zombie_pairs();
} }
ret = 0; ret = 0;
eat_pair_list(&g_new_pairs); while (!list_empty(&g_pairs))
eat_pair_list(&g_active_pairs);
if (g_jack_client != NULL)
{ {
jack_deactivate(g_jack_client); destroy_pair(list_entry(g_pairs.next, struct port_pair, siblings));
jack_client_close(g_jack_client);
} }
disconnect_dbus(); disconnect_dbus();
destroy_mutex: return 0;
pthread_mutex_destroy(&g_mutex);
exit:
return ret;
} }
static void jmcore_connect_to_jack(struct dbus_method_call * call_ptr) /***************************************************************************/
/* D-Bus interface implementation */
static void jmcore_get_pid(struct dbus_method_call * call_ptr)
{ {
int ret; dbus_int64_t pid;
const char * jack_client_name;
if (g_jack_client != NULL) pid = getpid();
{
lash_dbus_error(call_ptr, LASH_DBUS_ERROR_GENERIC, "JACK server is already connected");
return;
}
g_jack_client = jack_client_open("jmcore", JackNoStartServer, NULL); method_return_new_single(call_ptr, DBUS_TYPE_INT64, &pid);
if (g_jack_client == NULL)
{
lash_dbus_error(call_ptr, LASH_DBUS_ERROR_GENERIC, "Cannot connect to JACK server");
return;
}
ret = jack_set_process_callback(g_jack_client, process_callback, NULL);
if (ret != 0)
{
lash_dbus_error(call_ptr, LASH_DBUS_ERROR_GENERIC, "JACK process callback setup failed");
jack_client_close(g_jack_client);
g_jack_client = NULL;
return;
}
jack_on_shutdown(g_jack_client, shutdown_callback, NULL);
ret = jack_activate(g_jack_client);
if (ret != 0)
{
lash_dbus_error(call_ptr, LASH_DBUS_ERROR_GENERIC, "JACK client activation failed");
jack_client_close(g_jack_client);
g_jack_client = NULL;
return;
}
jack_client_name = jack_get_client_name(g_jack_client);
method_return_new_single(call_ptr, DBUS_TYPE_STRING, &jack_client_name);
} }
static void jmcore_create(struct dbus_method_call * call_ptr) static void jmcore_create(struct dbus_method_call * call_ptr)
@ -336,12 +258,7 @@ static void jmcore_create(struct dbus_method_call * call_ptr)
const char * input; const char * input;
const char * output; const char * output;
struct port_pair * pair_ptr; struct port_pair * pair_ptr;
int ret;
if (g_jack_client == NULL || g_jack_client_died)
{
lash_dbus_error(call_ptr, LASH_DBUS_ERROR_GENERIC, "JACK server is not connected");
goto exit;
}
dbus_error_init(&g_dbus_error); dbus_error_init(&g_dbus_error);
if (!dbus_message_get_args( if (!dbus_message_get_args(
@ -378,52 +295,59 @@ static void jmcore_create(struct dbus_method_call * call_ptr)
goto free_input_name; goto free_input_name;
} }
pair_ptr->input_port = jack_port_register(g_jack_client, input, midi ? JACK_DEFAULT_MIDI_TYPE : JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0); pair_ptr->client = jack_client_open("jmcore", JackNoStartServer, NULL);
if (pair_ptr->client == NULL)
{
lash_dbus_error(call_ptr, LASH_DBUS_ERROR_GENERIC, "Cannot connect to JACK server");
goto free_output_name;
}
pair_ptr->midi = midi;
pair_ptr->dead = false;
ret = jack_set_process_callback(pair_ptr->client, process_callback, pair_ptr);
if (ret != 0)
{
lash_dbus_error(call_ptr, LASH_DBUS_ERROR_GENERIC, "JACK process callback setup failed");
goto close_client;
}
jack_on_shutdown(pair_ptr->client, shutdown_callback, pair_ptr);
pair_ptr->input_port = jack_port_register(pair_ptr->client, input, midi ? JACK_DEFAULT_MIDI_TYPE : JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0);
if (pair_ptr->input_port == NULL) if (pair_ptr->input_port == NULL)
{ {
lash_dbus_error(call_ptr, LASH_DBUS_ERROR_GENERIC, "Port '%s' registration failed.", input); lash_dbus_error(call_ptr, LASH_DBUS_ERROR_GENERIC, "Port '%s' registration failed.", input);
goto free_output_name; goto free_output_name;
} }
pair_ptr->output_port = jack_port_register(g_jack_client, output, midi ? JACK_DEFAULT_MIDI_TYPE : JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); pair_ptr->output_port = jack_port_register(pair_ptr->client, output, midi ? JACK_DEFAULT_MIDI_TYPE : JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
if (pair_ptr->input_port == NULL) if (pair_ptr->input_port == NULL)
{ {
lash_dbus_error(call_ptr, LASH_DBUS_ERROR_GENERIC, "Port '%s' registration failed.", output); lash_dbus_error(call_ptr, LASH_DBUS_ERROR_GENERIC, "Port '%s' registration failed.", output);
goto unregister_input_port; goto unregister_input_port;
} }
pair_ptr->midi = midi; list_add_tail(&pair_ptr->siblings, &g_pairs);
pair_ptr->active = false; ret = jack_activate(pair_ptr->client);
pthread_mutex_lock(&g_mutex); if (ret != 0)
list_add_tail(&pair_ptr->siblings, &g_new_pairs);
pthread_mutex_unlock(&g_mutex);
/* wait pair to become active */
pthread_mutex_lock(&g_mutex);
while (!pair_ptr->active)
{ {
if (g_jack_client_died) lash_dbus_error(call_ptr, LASH_DBUS_ERROR_GENERIC, "JACK client activation failed");
{ goto remove_from_list;
lash_dbus_error(call_ptr, LASH_DBUS_ERROR_GENERIC, "JACK server died.");
list_del(&pair_ptr->siblings);
pthread_mutex_unlock(&g_mutex);
goto unregister_output_port;
}
pthread_mutex_unlock(&g_mutex);
usleep(50);
pthread_mutex_lock(&g_mutex);
} }
pthread_mutex_unlock(&g_mutex);
method_return_new_void(call_ptr); method_return_new_void(call_ptr);
goto exit; goto exit;
unregister_output_port: remove_from_list:
jack_port_unregister(g_jack_client, pair_ptr->output_port); list_del(&pair_ptr->siblings);
//unregister_output_port:
jack_port_unregister(pair_ptr->client, pair_ptr->output_port);
unregister_input_port: unregister_input_port:
jack_port_unregister(g_jack_client, pair_ptr->input_port); jack_port_unregister(pair_ptr->client, pair_ptr->input_port);
close_client:
jack_client_close(pair_ptr->client);
free_output_name: free_output_name:
free(pair_ptr->output_port_name); free(pair_ptr->output_port_name);
free_input_name: free_input_name:
@ -440,12 +364,6 @@ static void jmcore_destroy(struct dbus_method_call * call_ptr)
struct list_head * node_ptr; struct list_head * node_ptr;
struct port_pair * pair_ptr; struct port_pair * pair_ptr;
if (g_jack_client == NULL || g_jack_client_died)
{
lash_dbus_error(call_ptr, LASH_DBUS_ERROR_GENERIC, "JACK server is not connected");
return;
}
dbus_error_init(&g_dbus_error); dbus_error_init(&g_dbus_error);
if (!dbus_message_get_args(call_ptr->message, &g_dbus_error, DBUS_TYPE_STRING, &port, DBUS_TYPE_INVALID)) if (!dbus_message_get_args(call_ptr->message, &g_dbus_error, DBUS_TYPE_STRING, &port, DBUS_TYPE_INVALID))
{ {
@ -454,49 +372,20 @@ static void jmcore_destroy(struct dbus_method_call * call_ptr)
return; return;
} }
pthread_mutex_lock(&g_mutex); list_for_each(node_ptr, &g_pairs)
list_for_each(node_ptr, &g_active_pairs)
{ {
pair_ptr = list_entry(node_ptr, struct port_pair, siblings); pair_ptr = list_entry(node_ptr, struct port_pair, siblings);
if (strcmp(pair_ptr->input_port_name, port) == 0 || if (strcmp(pair_ptr->input_port_name, port) == 0 ||
strcmp(pair_ptr->output_port_name, port) == 0) strcmp(pair_ptr->output_port_name, port) == 0)
{ {
goto found; destroy_pair(pair_ptr);
method_return_new_void(call_ptr);
return;
} }
} }
pthread_mutex_unlock(&g_mutex);
lash_dbus_error(call_ptr, LASH_DBUS_ERROR_INVALID_ARGS, "port '%s' not found.", port); lash_dbus_error(call_ptr, LASH_DBUS_ERROR_INVALID_ARGS, "port '%s' not found.", port);
return; return;
found:
pair_ptr->active = false;
pthread_mutex_unlock(&g_mutex);
/* wait pair to become non-active */
pthread_mutex_lock(&g_mutex);
while (!list_empty(&pair_ptr->siblings))
{
if (g_jack_client_died)
{
lash_dbus_error(call_ptr, LASH_DBUS_ERROR_GENERIC, "JACK server died.");
pthread_mutex_unlock(&g_mutex);
if (list_empty(&pair_ptr->siblings))
{
free_pair(pair_ptr);
}
return;
}
pthread_mutex_unlock(&g_mutex);
usleep(50);
pthread_mutex_lock(&g_mutex);
}
pthread_mutex_unlock(&g_mutex);
free_pair(pair_ptr);
method_return_new_void(call_ptr);
} }
static void jmcore_exit(struct dbus_method_call * call_ptr) static void jmcore_exit(struct dbus_method_call * call_ptr)
@ -506,8 +395,8 @@ static void jmcore_exit(struct dbus_method_call * call_ptr)
method_return_new_void(call_ptr); method_return_new_void(call_ptr);
} }
METHOD_ARGS_BEGIN(connect_to_jack, "Connect to JACK server") METHOD_ARGS_BEGIN(get_pid, "Get process ID")
METHOD_ARG_DESCRIBE_OUT("jack_client_name", "s", "JACK client name") METHOD_ARG_DESCRIBE_OUT("process_id", DBUS_TYPE_INT64_AS_STRING, "Process ID")
METHOD_ARGS_END METHOD_ARGS_END
METHOD_ARGS_BEGIN(create, "Create port pair") METHOD_ARGS_BEGIN(create, "Create port pair")
@ -524,7 +413,7 @@ METHOD_ARGS_BEGIN(exit, "Tell jmcore D-Bus service to exit")
METHOD_ARGS_END METHOD_ARGS_END
METHODS_BEGIN METHODS_BEGIN
METHOD_DESCRIBE(connect_to_jack, jmcore_connect_to_jack) METHOD_DESCRIBE(get_pid, jmcore_get_pid)
METHOD_DESCRIBE(create, jmcore_create) METHOD_DESCRIBE(create, jmcore_create)
METHOD_DESCRIBE(destroy, jmcore_destroy) METHOD_DESCRIBE(destroy, jmcore_destroy)
METHOD_DESCRIBE(exit, jmcore_exit) METHOD_DESCRIBE(exit, jmcore_exit)