/* GStreamer * Copyright (C) 2006 Wim Taymans <wim@fluendo.com> * * gstjackaudioclient.c: jack audio client implementation * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include <string.h> #include "gstjackaudioclient.h" #include "gstjack.h" #include <gst/glib-compat-private.h> GST_DEBUG_CATEGORY_STATIC (gst_jack_audio_client_debug); #define GST_CAT_DEFAULT gst_jack_audio_client_debug static void jack_log_error (const gchar * msg) { GST_ERROR ("%s", msg); } static void jack_info_error (const gchar * msg) { GST_INFO ("%s", msg); } void gst_jack_audio_client_init (void) { GST_DEBUG_CATEGORY_INIT (gst_jack_audio_client_debug, "jackclient", 0, "jackclient helpers"); jack_set_error_function (jack_log_error); jack_set_info_function (jack_info_error); } /* a list of global connections indexed by id and server. */ G_LOCK_DEFINE_STATIC (connections_lock); static GList *connections; /* the connection to a server */ typedef struct { gint refcount; GMutex lock; GCond flush_cond; /* id/server pair and the connection */ gchar *id; gchar *server; jack_client_t *client; /* lists of GstJackAudioClients */ gint n_clients; GList *src_clients; GList *sink_clients; /* transport state handling */ gint cur_ts; GstState transport_state; } GstJackAudioConnection; /* an object sharing a jack_client_t connection. */ struct _GstJackAudioClient { GstJackAudioConnection *conn; GstJackClientType type; gboolean active; gboolean deactivate; gboolean server_down; JackShutdownCallback shutdown; JackProcessCallback process; JackBufferSizeCallback buffer_size; JackSampleRateCallback sample_rate; gpointer user_data; }; typedef struct { jack_nframes_t nframes; gpointer user_data; } JackCB; static gboolean jack_handle_transport_change (GstJackAudioClient * client, GstState state) { GstObject *obj = GST_OBJECT_PARENT (client->user_data); guint mode; g_object_get (obj, "transport", &mode, NULL); if ((mode & GST_JACK_TRANSPORT_SLAVE) && (GST_STATE (obj) != state)) { GST_INFO_OBJECT (obj, "requesting state change: %s", gst_element_state_get_name (state)); gst_element_post_message (GST_ELEMENT (obj), gst_message_new_request_state (obj, state)); return TRUE; } return FALSE; } static int jack_process_cb (jack_nframes_t nframes, void *arg) { GstJackAudioConnection *conn = (GstJackAudioConnection *) arg; GList *walk; int res = 0; jack_transport_state_t ts = jack_transport_query (conn->client, NULL); if (ts != conn->cur_ts) { conn->cur_ts = ts; switch (ts) { case JackTransportStopped: GST_DEBUG ("transport state is 'stopped'"); conn->transport_state = GST_STATE_PAUSED; break; case JackTransportStarting: GST_DEBUG ("transport state is 'starting'"); conn->transport_state = GST_STATE_READY; break; case JackTransportRolling: GST_DEBUG ("transport state is 'rolling'"); conn->transport_state = GST_STATE_PLAYING; break; default: break; } GST_DEBUG ("num of clients: src=%d, sink=%d", g_list_length (conn->src_clients), g_list_length (conn->sink_clients)); } g_mutex_lock (&conn->lock); /* call sources first, then sinks. Sources will either push data into the * ringbuffer of the sinks, which will then pull the data out of it, or * sinks will pull the data from the sources. */ for (walk = conn->src_clients; walk; walk = g_list_next (walk)) { GstJackAudioClient *client = (GstJackAudioClient *) walk->data; /* only call active clients */ if ((client->active || client->deactivate) && client->process) { res = client->process (nframes, client->user_data); if (client->deactivate) { client->deactivate = FALSE; g_cond_signal (&conn->flush_cond); } } } for (walk = conn->sink_clients; walk; walk = g_list_next (walk)) { GstJackAudioClient *client = (GstJackAudioClient *) walk->data; /* only call active clients */ if ((client->active || client->deactivate) && client->process) { res = client->process (nframes, client->user_data); if (client->deactivate) { client->deactivate = FALSE; g_cond_signal (&conn->flush_cond); } } } /* handle transport state requisition, do sinks first, stop after the first * element that handled it */ if (conn->transport_state != GST_STATE_VOID_PENDING) { for (walk = conn->sink_clients; walk; walk = g_list_next (walk)) { if (jack_handle_transport_change ((GstJackAudioClient *) walk->data, conn->transport_state)) { conn->transport_state = GST_STATE_VOID_PENDING; break; } } } if (conn->transport_state != GST_STATE_VOID_PENDING) { for (walk = conn->src_clients; walk; walk = g_list_next (walk)) { if (jack_handle_transport_change ((GstJackAudioClient *) walk->data, conn->transport_state)) { conn->transport_state = GST_STATE_VOID_PENDING; break; } } } g_mutex_unlock (&conn->lock); return res; } static void jack_shutdown_cb (void *arg) { GstJackAudioConnection *conn = (GstJackAudioConnection *) arg; GList *walk; GST_DEBUG ("disconnect client %s from server %s", conn->id, GST_STR_NULL (conn->server)); g_mutex_lock (&conn->lock); for (walk = conn->src_clients; walk; walk = g_list_next (walk)) { GstJackAudioClient *client = (GstJackAudioClient *) walk->data; client->server_down = TRUE; g_cond_signal (&conn->flush_cond); if (client->shutdown) client->shutdown (client->user_data); } for (walk = conn->sink_clients; walk; walk = g_list_next (walk)) { GstJackAudioClient *client = (GstJackAudioClient *) walk->data; client->server_down = TRUE; g_cond_signal (&conn->flush_cond); if (client->shutdown) client->shutdown (client->user_data); } g_mutex_unlock (&conn->lock); } /* we error out */ static int jack_sample_rate_cb (jack_nframes_t nframes, void *arg) { jack_shutdown_cb (arg); return 0; } /* we error out */ static int jack_buffer_size_cb (jack_nframes_t nframes, void *arg) { jack_shutdown_cb (arg); return 0; } typedef struct { const gchar *id; const gchar *server; } FindData; static gint connection_find (GstJackAudioConnection * conn, FindData * data) { /* id's must match */ if (strcmp (conn->id, data->id)) return 1; /* both the same or NULL */ if (conn->server == data->server) return 0; /* we cannot compare NULL */ if (conn->server == NULL || data->server == NULL) return 1; if (strcmp (conn->server, data->server)) return 1; return 0; } /* make a connection with @id and @server. Returns NULL on failure with the * status set. */ static GstJackAudioConnection * gst_jack_audio_make_connection (const gchar * id, const gchar * server, jack_client_t * jclient, jack_status_t * status) { GstJackAudioConnection *conn; jack_options_t options; gint res; *status = 0; GST_DEBUG ("new client %s, connecting to server %s", id, GST_STR_NULL (server)); /* never start a server */ options = JackNoStartServer; /* if we have a servername, use it */ if (server != NULL) options |= JackServerName; /* open the client */ if (jclient == NULL) jclient = jack_client_open (id, options, status, server); if (jclient == NULL) goto could_not_open; /* now create object */ conn = g_new (GstJackAudioConnection, 1); conn->refcount = 1; g_mutex_init (&conn->lock); g_cond_init (&conn->flush_cond); conn->id = g_strdup (id); conn->server = g_strdup (server); conn->client = jclient; conn->n_clients = 0; conn->src_clients = NULL; conn->sink_clients = NULL; conn->cur_ts = -1; conn->transport_state = GST_STATE_VOID_PENDING; /* set our callbacks */ jack_set_process_callback (jclient, jack_process_cb, conn); /* these callbacks cause us to error */ jack_set_buffer_size_callback (jclient, jack_buffer_size_cb, conn); jack_set_sample_rate_callback (jclient, jack_sample_rate_cb, conn); jack_on_shutdown (jclient, jack_shutdown_cb, conn); /* all callbacks are set, activate the client */ GST_INFO ("activate jack_client %p", jclient); if ((res = jack_activate (jclient))) goto could_not_activate; GST_DEBUG ("opened connection %p", conn); return conn; /* ERRORS */ could_not_open: { GST_DEBUG ("failed to open jack client, %d", *status); return NULL; } could_not_activate: { GST_ERROR ("Could not activate client (%d)", res); *status = JackFailure; g_mutex_clear (&conn->lock); g_free (conn->id); g_free (conn->server); g_free (conn); return NULL; } } static GstJackAudioConnection * gst_jack_audio_get_connection (const gchar * id, const gchar * server, jack_client_t * jclient, jack_status_t * status) { GstJackAudioConnection *conn; GList *found; FindData data; GST_DEBUG ("getting connection for id %s, server %s", id, GST_STR_NULL (server)); data.id = id; data.server = server; G_LOCK (connections_lock); found = g_list_find_custom (connections, &data, (GCompareFunc) connection_find); if (found != NULL && jclient != NULL) { /* we found it, increase refcount and return it */ conn = (GstJackAudioConnection *) found->data; conn->refcount++; GST_DEBUG ("found connection %p", conn); } else { /* make new connection */ conn = gst_jack_audio_make_connection (id, server, jclient, status); if (conn != NULL) { GST_DEBUG ("created connection %p", conn); /* add to list on success */ connections = g_list_prepend (connections, conn); } else { GST_WARNING ("could not create connection"); } } G_UNLOCK (connections_lock); return conn; } static void gst_jack_audio_unref_connection (GstJackAudioConnection * conn) { gint res; gboolean zero; GST_DEBUG ("unref connection %p refcnt %d", conn, conn->refcount); G_LOCK (connections_lock); conn->refcount--; if ((zero = (conn->refcount == 0))) { GST_DEBUG ("closing connection %p", conn); /* remove from list, we can release the mutex after removing the connection * from the list because after that, nobody can access the connection anymore. */ connections = g_list_remove (connections, conn); } G_UNLOCK (connections_lock); /* if we are zero, close and cleanup the connection */ if (zero) { /* don't use conn->lock here. two reasons: * * 1) its not necessary: jack_deactivate() will not return until the JACK thread * associated with this connection is cleaned up by a thread join, hence * no more callbacks can occur or be in progress. * * 2) it would deadlock anyway, because jack_deactivate() will sleep * waiting for the JACK thread, and can thus cause deadlock in * jack_process_cb() */ GST_INFO ("deactivate jack_client %p", conn->client); if ((res = jack_deactivate (conn->client))) { /* we only warn, this means the server is probably shut down and the client * is gone anyway. */ GST_WARNING ("Could not deactivate Jack client (%d)", res); } /* close connection */ if ((res = jack_client_close (conn->client))) { /* we assume the client is gone. */ GST_WARNING ("close failed (%d)", res); } /* free resources */ g_mutex_clear (&conn->lock); g_cond_clear (&conn->flush_cond); g_free (conn->id); g_free (conn->server); g_free (conn); } } static void gst_jack_audio_connection_add_client (GstJackAudioConnection * conn, GstJackAudioClient * client) { g_mutex_lock (&conn->lock); switch (client->type) { case GST_JACK_CLIENT_SOURCE: conn->src_clients = g_list_append (conn->src_clients, client); conn->n_clients++; break; case GST_JACK_CLIENT_SINK: conn->sink_clients = g_list_append (conn->sink_clients, client); conn->n_clients++; break; default: g_warning ("trying to add unknown client type"); break; } g_mutex_unlock (&conn->lock); } static void gst_jack_audio_connection_remove_client (GstJackAudioConnection * conn, GstJackAudioClient * client) { g_mutex_lock (&conn->lock); switch (client->type) { case GST_JACK_CLIENT_SOURCE: conn->src_clients = g_list_remove (conn->src_clients, client); conn->n_clients--; break; case GST_JACK_CLIENT_SINK: conn->sink_clients = g_list_remove (conn->sink_clients, client); conn->n_clients--; break; default: g_warning ("trying to remove unknown client type"); break; } g_mutex_unlock (&conn->lock); } /** * gst_jack_audio_client_get: * @id: the client id * @server: the server to connect to or NULL for the default server * @type: the client type * @shutdown: a callback when the jack server shuts down * @process: a callback when samples are available * @buffer_size: a callback when the buffer_size changes * @sample_rate: a callback when the sample_rate changes * @user_data: user data passed to the callbacks * @status: pointer to hold the jack status code in case of errors * * Get the jack client connection for @id and @server. Connections to the same * @id and @server will receive the same physical Jack client connection and * will therefore be scheduled in the same process callback. * * Returns: a #GstJackAudioClient. */ GstJackAudioClient * gst_jack_audio_client_new (const gchar * id, const gchar * server, jack_client_t * jclient, GstJackClientType type, void (*shutdown) (void *arg), JackProcessCallback process, JackBufferSizeCallback buffer_size, JackSampleRateCallback sample_rate, gpointer user_data, jack_status_t * status) { GstJackAudioClient *client; GstJackAudioConnection *conn; g_return_val_if_fail (id != NULL, NULL); g_return_val_if_fail (status != NULL, NULL); /* first get a connection for the id/server pair */ conn = gst_jack_audio_get_connection (id, server, jclient, status); if (conn == NULL) goto no_connection; GST_INFO ("new client %s", id); /* make new client using the connection */ client = g_new (GstJackAudioClient, 1); client->active = client->deactivate = FALSE; client->conn = conn; client->type = type; client->shutdown = shutdown; client->process = process; client->buffer_size = buffer_size; client->sample_rate = sample_rate; client->user_data = user_data; client->server_down = FALSE; /* add the client to the connection */ gst_jack_audio_connection_add_client (conn, client); return client; /* ERRORS */ no_connection: { GST_DEBUG ("Could not get server connection (%d)", *status); return NULL; } } /** * gst_jack_audio_client_free: * @client: a #GstJackAudioClient * * Free the resources used by @client. */ void gst_jack_audio_client_free (GstJackAudioClient * client) { GstJackAudioConnection *conn; g_return_if_fail (client != NULL); GST_INFO ("free client"); conn = client->conn; /* remove from connection first so that it's not scheduled anymore after this * call */ gst_jack_audio_connection_remove_client (conn, client); gst_jack_audio_unref_connection (conn); g_free (client); } /** * gst_jack_audio_client_get_client: * @client: a #GstJackAudioClient * * Get the jack audio client for @client. This function is used to perform * operations on the jack server from this client. * * Returns: The jack audio client. */ jack_client_t * gst_jack_audio_client_get_client (GstJackAudioClient * client) { g_return_val_if_fail (client != NULL, NULL); /* no lock needed, the connection and the client does not change * once the client is created. */ return client->conn->client; } /** * gst_jack_audio_client_set_active: * @client: a #GstJackAudioClient * @active: new mode for the client * * Activate or deactivate @client. When a client is activated it will receive * callbacks when data should be processed. * * Returns: 0 if all ok. */ gint gst_jack_audio_client_set_active (GstJackAudioClient * client, gboolean active) { g_return_val_if_fail (client != NULL, -1); /* make sure that we are not dispatching the client */ g_mutex_lock (&client->conn->lock); if (client->active && !active) { /* we need to process once more to flush the port */ client->deactivate = TRUE; /* need to wait for process_cb run once more */ while (client->deactivate && !client->server_down) g_cond_wait (&client->conn->flush_cond, &client->conn->lock); } client->active = active; g_mutex_unlock (&client->conn->lock); return 0; } /** * gst_jack_audio_client_get_transport_state: * @client: a #GstJackAudioClient * * Check the current transport state. The client can use this to request a state * change from the application. * * Returns: the state, %GST_STATE_VOID_PENDING for no change in the transport * state */ GstState gst_jack_audio_client_get_transport_state (GstJackAudioClient * client) { GstState state = client->conn->transport_state; client->conn->transport_state = GST_STATE_VOID_PENDING; return state; } /** * gst_jack_audio_client_get_port_names_from_string: * @jclient: a jack_client_t handle * @port_names: comma-separated jack port name(s) * @port_flags: JackPortFlags * * Returns: a newly-allocated %NULL-terminated array of strings or %NULL * if @port_names contains invalid port name. Use g_strfreev() to free it. */ gchar ** gst_jack_audio_client_get_port_names_from_string (jack_client_t * jclient, const gchar * port_names, gint port_flags) { gchar **p = NULL; guint i, len; g_return_val_if_fail (jclient != NULL, NULL); if (!port_names) return NULL; p = g_strsplit (port_names, ",", 0); len = g_strv_length (p); if (len < 1) goto invalid; for (i = 0; i < len; i++) { jack_port_t *port = jack_port_by_name (jclient, p[i]); int flags; if (!port) { GST_WARNING ("Couldn't get jack port by name %s", p[i]); goto invalid; } flags = jack_port_flags (port); if ((flags & port_flags) != port_flags) { GST_WARNING ("Port flags 0x%x doesn't match expected flags 0x%x", flags, port_flags); goto invalid; } } return p; invalid: g_strfreev (p); return NULL; }