mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-08 18:39:54 +00:00
117fa7c3e4
Add log handlers for jack that write to the gst debug log. This avoids spamming the console when e.g. using autoaudiosink, having the jack elements installed, but not running jack.
629 lines
17 KiB
C
629 lines
17 KiB
C
/* 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;
|
|
|
|
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;
|
|
}
|
|
|
|
/* we error out */
|
|
static int
|
|
jack_sample_rate_cb (jack_nframes_t nframes, void *arg)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
/* we error out */
|
|
static int
|
|
jack_buffer_size_cb (jack_nframes_t nframes, void *arg)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
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;
|
|
|
|
if (client->shutdown)
|
|
client->shutdown (client->user_data);
|
|
}
|
|
for (walk = conn->sink_clients; walk; walk = g_list_next (walk)) {
|
|
GstJackAudioClient *client = (GstJackAudioClient *) walk->data;
|
|
|
|
if (client->shutdown)
|
|
client->shutdown (client->user_data);
|
|
}
|
|
g_mutex_unlock (&conn->lock);
|
|
}
|
|
|
|
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;
|
|
|
|
/* 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 deactive @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)
|
|
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;
|
|
}
|