gstjackaudioclient.c 14.3 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
/* 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., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

#include <string.h>

#include "gstjackaudioclient.h"

26 27
#include <gst/glib-compat-private.h>

28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
GST_DEBUG_CATEGORY_STATIC (gst_jack_audio_client_debug);
#define GST_CAT_DEFAULT gst_jack_audio_client_debug

void
gst_jack_audio_client_init (void)
{
  GST_DEBUG_CATEGORY_INIT (gst_jack_audio_client_debug, "jackclient", 0,
      "jackclient helpers");
}

/* 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;
47
  GCond *flush_cond;
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66

  /* 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;
} GstJackAudioConnection;

/* an object sharing a jack_client_t connection. */
struct _GstJackAudioClient
{
  GstJackAudioConnection *conn;

  GstJackClientType type;
  gboolean active;
67
  gboolean deactivate;
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98

  void (*shutdown) (void *arg);
  JackProcessCallback process;
  JackBufferSizeCallback buffer_size;
  JackSampleRateCallback sample_rate;
  gpointer user_data;
};

typedef jack_default_audio_sample_t sample_t;

typedef struct
{
  jack_nframes_t nframes;
  gpointer user_data;
} JackCB;

static int
jack_process_cb (jack_nframes_t nframes, void *arg)
{
  GstJackAudioConnection *conn = (GstJackAudioConnection *) arg;
  GList *walk;
  int res = 0;

  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 */
99
    if ((client->active || client->deactivate) && client->process) {
100
      res = client->process (nframes, client->user_data);
101 102 103 104 105
      if (client->deactivate) {
        client->deactivate = FALSE;
        g_cond_signal (conn->flush_cond);
      }
    }
106 107 108 109 110
  }
  for (walk = conn->sink_clients; walk; walk = g_list_next (walk)) {
    GstJackAudioClient *client = (GstJackAudioClient *) walk->data;

    /* only call active clients */
111
    if ((client->active || client->deactivate) && client->process) {
112
      res = client->process (nframes, client->user_data);
113 114 115 116 117
      if (client->deactivate) {
        client->deactivate = FALSE;
        g_cond_signal (conn->flush_cond);
      }
    }
118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
  }
  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;

144 145 146
  GST_DEBUG ("disconnect client %s from server %s", conn->id,
      GST_STR_NULL (conn->server));

147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193
  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,
Tristan Matthews's avatar
Tristan Matthews committed
194
    jack_client_t * jclient, jack_status_t * status)
195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210
{
  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 */
Tristan Matthews's avatar
Tristan Matthews committed
211 212
  if (jclient == NULL)
    jclient = jack_client_open (id, options, status, server);
213 214 215 216 217 218 219
  if (jclient == NULL)
    goto could_not_open;

  /* now create object */
  conn = g_new (GstJackAudioConnection, 1);
  conn->refcount = 1;
  conn->lock = g_mutex_new ();
220
  conn->flush_cond = g_cond_new ();
221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262
  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;

  /* 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 */
  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_free (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,
Tristan Matthews's avatar
Tristan Matthews committed
263
    jack_client_t * jclient, jack_status_t * status)
264 265 266 267 268 269 270 271 272 273 274 275 276 277
{
  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);
Tristan Matthews's avatar
Tristan Matthews committed
278
  if (found != NULL && jclient != NULL) {
279 280 281 282 283 284 285
    /* we found it, increase refcount and return it */
    conn = (GstJackAudioConnection *) found->data;
    conn->refcount++;

    GST_DEBUG ("found connection %p", conn);
  } else {
    /* make new connection */
Tristan Matthews's avatar
Tristan Matthews committed
286
    conn = gst_jack_audio_make_connection (id, server, jclient, status);
287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303
    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;
304
  gboolean zero;
305

306
  GST_DEBUG ("unref connection %p refcnt %d", conn, conn->refcount);
307 308 309

  G_LOCK (connections_lock);
  conn->refcount--;
310
  if ((zero = (conn->refcount == 0))) {
311
    GST_DEBUG ("closing connection %p", conn);
312 313
    /* remove from list, we can release the mutex after removing the connection
     * from the list because after that, nobody can access the connection anymore. */
314
    connections = g_list_remove (connections, conn);
315 316
  }
  G_UNLOCK (connections_lock);
317

318 319 320 321 322 323 324 325 326 327 328 329
  /* 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()
     */
330 331 332 333 334 335 336 337 338 339 340 341 342
    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_free (conn->lock);
343
    g_cond_free (conn->flush_cond);
344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411
    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,
Tristan Matthews's avatar
Tristan Matthews committed
412 413 414 415
    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)
416 417 418 419 420 421 422 423
{
  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 */
Tristan Matthews's avatar
Tristan Matthews committed
424
  conn = gst_jack_audio_get_connection (id, server, jclient, status);
425 426 427
  if (conn == NULL)
    goto no_connection;

428 429
  GST_INFO ("new client %s", id);

430 431
  /* make new client using the connection */
  client = g_new (GstJackAudioClient, 1);
432
  client->active = client->deactivate = FALSE;
433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466
  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);

467 468
  GST_INFO ("free client");

469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514
  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);
515 516 517 518 519 520 521 522
  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);
  }
523 524 525 526 527
  client->active = active;
  g_mutex_unlock (client->conn->lock);

  return 0;
}