pulsesink.c 74.5 KB
Newer Older
1
2
/*-*- Mode: C; c-basic-offset: 2 -*-*/

Wim Taymans's avatar
Wim Taymans committed
3
/*  GStreamer pulseaudio plugin
4
5
 *
 *  Copyright (c) 2004-2008 Lennart Poettering
Wim Taymans's avatar
Wim Taymans committed
6
 *            (c) 2009      Wim Taymans
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 *
 *  gst-pulse is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as
 *  published by the Free Software Foundation; either version 2.1 of the
 *  License, or (at your option) any later version.
 *
 *  gst-pulse 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
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with gst-pulse; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 *  USA.
 */

24
25
26
27
/**
 * SECTION:element-pulsesink
 * @see_also: pulsesrc, pulsemixer
 *
28
29
30
 * This element outputs audio to a
 * <ulink href="http://www.pulseaudio.org">PulseAudio sound server</ulink>.
 *
31
32
 * <refsect2>
 * <title>Example pipelines</title>
33
 * |[
34
 * gst-launch -v filesrc location=sine.ogg ! oggdemux ! vorbisdec ! audioconvert ! audioresample ! pulsesink
35
36
 * ]| Play an Ogg/Vorbis file.
 * |[
37
 * gst-launch -v audiotestsrc ! audioconvert ! volume volume=0.4 ! pulsesink
38
 * ]| Play a 440Hz sine wave.
39
40
41
 * </refsect2>
 */

42
43
44
45
46
47
48
49
50
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <string.h>
#include <stdio.h>

#include <gst/base/gstbasesink.h>
#include <gst/gsttaglist.h>
51
#include <gst/interfaces/streamvolume.h>
52
#include <gst/gst-i18n-plugin.h>
53

54
55
#include <gst/pbutils/pbutils.h>        /* only used for GST_PLUGINS_BASE_VERSION_* */

56
57
58
59
60
61
#include "pulsesink.h"
#include "pulseutil.h"

GST_DEBUG_CATEGORY_EXTERN (pulse_debug);
#define GST_CAT_DEFAULT pulse_debug

62
63
64
65
66
/* according to
 * http://www.pulseaudio.org/ticket/314
 * we need pulse-0.9.12 to use sink volume properties
 */

67
68
69
70
#define DEFAULT_SERVER          NULL
#define DEFAULT_DEVICE          NULL
#define DEFAULT_DEVICE_NAME     NULL
#define DEFAULT_VOLUME          1.0
71
#define DEFAULT_MUTE            FALSE
72
#define MAX_VOLUME              10.0
73

74
75
enum
{
76
77
  PROP_0,
  PROP_SERVER,
78
  PROP_DEVICE,
79
  PROP_DEVICE_NAME,
80
  PROP_VOLUME,
81
  PROP_MUTE,
82
  PROP_LAST
83
84
};

Wim Taymans's avatar
Wim Taymans committed
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
#define GST_TYPE_PULSERING_BUFFER        \
        (gst_pulseringbuffer_get_type())
#define GST_PULSERING_BUFFER(obj)        \
        (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_PULSERING_BUFFER,GstPulseRingBuffer))
#define GST_PULSERING_BUFFER_CLASS(klass) \
        (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_PULSERING_BUFFER,GstPulseRingBufferClass))
#define GST_PULSERING_BUFFER_GET_CLASS(obj) \
        (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_PULSERING_BUFFER, GstPulseRingBufferClass))
#define GST_PULSERING_BUFFER_CAST(obj)        \
        ((GstPulseRingBuffer *)obj)
#define GST_IS_PULSERING_BUFFER(obj)     \
        (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_PULSERING_BUFFER))
#define GST_IS_PULSERING_BUFFER_CLASS(klass)\
        (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_PULSERING_BUFFER))

typedef struct _GstPulseRingBuffer GstPulseRingBuffer;
typedef struct _GstPulseRingBufferClass GstPulseRingBufferClass;

103
104
105
106
107
108
109
110
111
112
113
114
115
typedef struct _GstPulseContext GstPulseContext;

struct _GstPulseContext
{
  pa_context *context;
  GSList *ring_buffers;
};

/* Store the PA contexts in a hash table to allow easy sharing among
 * multiple instances of the sink. Keys are $context_name@$server_name
 * (strings) and values should be GstPulseContext pointers. */
static GHashTable *gst_pulse_shared_contexts;

Wim Taymans's avatar
Wim Taymans committed
116
117
118
119
120
121
/* We keep a custom ringbuffer that is backed up by data allocated by
 * pulseaudio. We must also overide the commit function to write into
 * pulseaudio memory instead. */
struct _GstPulseRingBuffer
{
  GstRingBuffer object;
122

123
  gchar *context_name;
Wim Taymans's avatar
Wim Taymans committed
124
  gchar *stream_name;
125

Wim Taymans's avatar
Wim Taymans committed
126
  pa_stream *stream;
127

Wim Taymans's avatar
Wim Taymans committed
128
  pa_sample_spec sample_spec;
129

130
131
132
133
134
135
136
137
#ifdef HAVE_PULSE_0_9_16
  void *m_data;
  size_t m_towrite;
  size_t m_writable;
  gint64 m_offset;
  gint64 m_lastoffset;
#endif

138
139
140
  gboolean corked:1;
  gboolean in_commit:1;
  gboolean paused:1;
Wim Taymans's avatar
Wim Taymans committed
141
142
143
144
145
};
struct _GstPulseRingBufferClass
{
  GstRingBufferClass parent_class;
};
146

147
static GType gst_pulseringbuffer_get_type (void);
Wim Taymans's avatar
Wim Taymans committed
148
static void gst_pulseringbuffer_finalize (GObject * object);
149

Wim Taymans's avatar
Wim Taymans committed
150
static GstRingBufferClass *ring_parent_class = NULL;
151

Wim Taymans's avatar
Wim Taymans committed
152
153
154
155
156
157
158
159
static gboolean gst_pulseringbuffer_open_device (GstRingBuffer * buf);
static gboolean gst_pulseringbuffer_close_device (GstRingBuffer * buf);
static gboolean gst_pulseringbuffer_acquire (GstRingBuffer * buf,
    GstRingBufferSpec * spec);
static gboolean gst_pulseringbuffer_release (GstRingBuffer * buf);
static gboolean gst_pulseringbuffer_start (GstRingBuffer * buf);
static gboolean gst_pulseringbuffer_pause (GstRingBuffer * buf);
static gboolean gst_pulseringbuffer_stop (GstRingBuffer * buf);
160
static void gst_pulseringbuffer_clear (GstRingBuffer * buf);
Wim Taymans's avatar
Wim Taymans committed
161
162
163
164
static guint gst_pulseringbuffer_commit (GstRingBuffer * buf,
    guint64 * sample, guchar * data, gint in_samples, gint out_samples,
    gint * accum);

165
G_DEFINE_TYPE (GstPulseRingBuffer, gst_pulseringbuffer, GST_TYPE_RING_BUFFER);
166

167
168
169
170
171
172
173
174
175
static GMutex *pa_ring_buffer_mutex = NULL;
static void
gst_pulseringbuffer_init_contexts (void)
{
  g_assert (pa_ring_buffer_mutex == NULL);
  pa_ring_buffer_mutex = g_mutex_new ();
  gst_pulse_shared_contexts = g_hash_table_new (g_str_hash, g_str_equal);
}

Wim Taymans's avatar
Wim Taymans committed
176
177
178
179
180
181
182
183
184
185
186
static void
gst_pulseringbuffer_class_init (GstPulseRingBufferClass * klass)
{
  GObjectClass *gobject_class;
  GstRingBufferClass *gstringbuffer_class;

  gobject_class = (GObjectClass *) klass;
  gstringbuffer_class = (GstRingBufferClass *) klass;

  ring_parent_class = g_type_class_peek_parent (klass);

187
  gobject_class->finalize = gst_pulseringbuffer_finalize;
Wim Taymans's avatar
Wim Taymans committed
188
189
190
191
192
193
194
195
196
197
198
199
200

  gstringbuffer_class->open_device =
      GST_DEBUG_FUNCPTR (gst_pulseringbuffer_open_device);
  gstringbuffer_class->close_device =
      GST_DEBUG_FUNCPTR (gst_pulseringbuffer_close_device);
  gstringbuffer_class->acquire =
      GST_DEBUG_FUNCPTR (gst_pulseringbuffer_acquire);
  gstringbuffer_class->release =
      GST_DEBUG_FUNCPTR (gst_pulseringbuffer_release);
  gstringbuffer_class->start = GST_DEBUG_FUNCPTR (gst_pulseringbuffer_start);
  gstringbuffer_class->pause = GST_DEBUG_FUNCPTR (gst_pulseringbuffer_pause);
  gstringbuffer_class->resume = GST_DEBUG_FUNCPTR (gst_pulseringbuffer_start);
  gstringbuffer_class->stop = GST_DEBUG_FUNCPTR (gst_pulseringbuffer_stop);
201
202
  gstringbuffer_class->clear_all =
      GST_DEBUG_FUNCPTR (gst_pulseringbuffer_clear);
Wim Taymans's avatar
Wim Taymans committed
203
204
205

  gstringbuffer_class->commit = GST_DEBUG_FUNCPTR (gst_pulseringbuffer_commit);
}
206

Wim Taymans's avatar
Wim Taymans committed
207
static void
208
gst_pulseringbuffer_init (GstPulseRingBuffer * pbuf)
Wim Taymans's avatar
Wim Taymans committed
209
210
211
{
  pbuf->stream_name = NULL;
  pbuf->stream = NULL;
212

213
#ifdef HAVE_PULSE_0_9_13
Wim Taymans's avatar
Wim Taymans committed
214
  pa_sample_spec_init (&pbuf->sample_spec);
215
#else
Wim Taymans's avatar
Wim Taymans committed
216
217
218
  pbuf->sample_spec.format = PA_SAMPLE_INVALID;
  pbuf->sample_spec.rate = 0;
  pbuf->sample_spec.channels = 0;
219
220
#endif

221
222
223
224
225
226
227
228
#ifdef HAVE_PULSE_0_9_16
  pbuf->m_data = NULL;
  pbuf->m_towrite = 0;
  pbuf->m_writable = 0;
  pbuf->m_offset = 0;
  pbuf->m_lastoffset = 0;
#endif

Wim Taymans's avatar
Wim Taymans committed
229
  pbuf->corked = TRUE;
230
231
  pbuf->in_commit = FALSE;
  pbuf->paused = FALSE;
Wim Taymans's avatar
Wim Taymans committed
232
}
233

Wim Taymans's avatar
Wim Taymans committed
234
235
static void
gst_pulsering_destroy_stream (GstPulseRingBuffer * pbuf)
236
{
Wim Taymans's avatar
Wim Taymans committed
237
  if (pbuf->stream) {
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252

#ifdef HAVE_PULSE_0_9_16
    if (pbuf->m_data) {
      /* drop shm memory buffer */
      pa_stream_cancel_write (pbuf->stream);

      /* reset internal variables */
      pbuf->m_data = NULL;
      pbuf->m_towrite = 0;
      pbuf->m_writable = 0;
      pbuf->m_offset = 0;
      pbuf->m_lastoffset = 0;
    }
#endif

Wim Taymans's avatar
Wim Taymans committed
253
    pa_stream_disconnect (pbuf->stream);
254

Wim Taymans's avatar
Wim Taymans committed
255
256
257
    /* Make sure we don't get any further callbacks */
    pa_stream_set_state_callback (pbuf->stream, NULL, NULL);
    pa_stream_set_write_callback (pbuf->stream, NULL, NULL);
258
259
    pa_stream_set_underflow_callback (pbuf->stream, NULL, NULL);
    pa_stream_set_overflow_callback (pbuf->stream, NULL, NULL);
260

Wim Taymans's avatar
Wim Taymans committed
261
262
263
264
265
266
    pa_stream_unref (pbuf->stream);
    pbuf->stream = NULL;
  }

  g_free (pbuf->stream_name);
  pbuf->stream_name = NULL;
267
268
}

269
270
271
272
273
274
275
276
277
278
279
280
281
static GstPulseContext *
gst_pulsering_get_context (GstPulseRingBuffer * pbuf)
{
  GstPulseContext *pctx;
  GstPulseSink *psink;

  g_mutex_lock (pa_ring_buffer_mutex);
  psink = GST_PULSESINK_CAST (GST_OBJECT_PARENT (pbuf));
  pctx = g_hash_table_lookup (gst_pulse_shared_contexts, pbuf->context_name);
  g_mutex_unlock (pa_ring_buffer_mutex);
  return pctx;
}

282
static void
Wim Taymans's avatar
Wim Taymans committed
283
gst_pulsering_destroy_context (GstPulseRingBuffer * pbuf)
284
{
285
286
287
288
289
290
291
292
  GstPulseContext *pctx;
  GstPulseSink *psink;

  g_mutex_lock (pa_ring_buffer_mutex);
  psink = GST_PULSESINK_CAST (GST_OBJECT_PARENT (pbuf));

  pctx = g_hash_table_lookup (gst_pulse_shared_contexts, pbuf->context_name);

Wim Taymans's avatar
Wim Taymans committed
293
294
  gst_pulsering_destroy_stream (pbuf);

295
296
297
298
  if (pctx) {
    pctx->ring_buffers = g_slist_remove (pctx->ring_buffers, pbuf);
    if (!g_slist_length (pctx->ring_buffers)) {
      pa_context_disconnect (pctx->context);
Wim Taymans's avatar
Wim Taymans committed
299

300
301
      /* Make sure we don't get any further callbacks */
      pa_context_set_state_callback (pctx->context, NULL, NULL);
302
#ifdef HAVE_PULSE_0_9_12
303
      pa_context_set_subscribe_callback (pctx->context, NULL, NULL);
304
#endif
Wim Taymans's avatar
Wim Taymans committed
305

306
307
308
      pa_context_unref (pctx->context);
      g_hash_table_remove (gst_pulse_shared_contexts, pbuf->context_name);
      g_free (pbuf->context_name);
309
      g_slice_free (GstPulseContext, pctx);
310
    }
Wim Taymans's avatar
Wim Taymans committed
311
  }
312
  g_mutex_unlock (pa_ring_buffer_mutex);
313
314
315
}

static void
Wim Taymans's avatar
Wim Taymans committed
316
gst_pulseringbuffer_finalize (GObject * object)
317
{
Wim Taymans's avatar
Wim Taymans committed
318
  GstPulseRingBuffer *ringbuffer;
319

Wim Taymans's avatar
Wim Taymans committed
320
321
322
323
324
325
326
327
328
329
  ringbuffer = GST_PULSERING_BUFFER_CAST (object);

  gst_pulsering_destroy_context (ringbuffer);

  G_OBJECT_CLASS (ring_parent_class)->finalize (object);
}

static gboolean
gst_pulsering_is_dead (GstPulseSink * psink, GstPulseRingBuffer * pbuf)
{
330
331
332
333
334
335
336
337
338
  GstPulseContext *pctx = gst_pulsering_get_context (pbuf);

  if (!pctx) {
    GST_ELEMENT_ERROR (psink, RESOURCE, FAILED, ("Disconnected"), (NULL));
    return TRUE;
  }

  if (!pctx->context
      || !PA_CONTEXT_IS_GOOD (pa_context_get_state (pctx->context))
Wim Taymans's avatar
Wim Taymans committed
339
340
      || !pbuf->stream
      || !PA_STREAM_IS_GOOD (pa_stream_get_state (pbuf->stream))) {
341
342
    const gchar *err_str =
        pctx->context ? pa_strerror (pa_context_errno (pctx->context)) : NULL;
Wim Taymans's avatar
Wim Taymans committed
343
344
345
346
347
    GST_ELEMENT_ERROR (psink, RESOURCE, FAILED, ("Disconnected: %s",
            err_str), (NULL));
    return TRUE;
  }
  return FALSE;
348
349
}

350
static void
Wim Taymans's avatar
Wim Taymans committed
351
gst_pulsering_context_state_cb (pa_context * c, void *userdata)
352
{
Wim Taymans's avatar
Wim Taymans committed
353
354
  GstPulseSink *psink;
  pa_context_state_t state;
355

356
357
  GstPulseContext *pctx = (GstPulseContext *) userdata;
  GSList *walk;
358

Wim Taymans's avatar
Wim Taymans committed
359
  state = pa_context_get_state (c);
360

361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
  for (walk = pctx->ring_buffers; walk; walk = g_slist_next (walk)) {
    GstPulseRingBuffer *pbuf = (GstPulseRingBuffer *) walk->data;
    psink = GST_PULSESINK_CAST (GST_OBJECT_PARENT (pbuf));
    GST_LOG_OBJECT (psink, "got new context state %d", state);

    /* psink can be null when we are shutting down and the ringbuffer is already
     * unparented */
    if (psink == NULL)
      continue;

    switch (state) {
      case PA_CONTEXT_READY:
      case PA_CONTEXT_TERMINATED:
      case PA_CONTEXT_FAILED:
        GST_LOG_OBJECT (psink, "signaling");
        pa_threaded_mainloop_signal (psink->mainloop, 0);
        break;
Wim Taymans's avatar
Wim Taymans committed
378

379
380
381
382
383
384
      case PA_CONTEXT_UNCONNECTED:
      case PA_CONTEXT_CONNECTING:
      case PA_CONTEXT_AUTHORIZING:
      case PA_CONTEXT_SETTING_NAME:
        break;
    }
Wim Taymans's avatar
Wim Taymans committed
385
  }
386
387
}

388
#ifdef HAVE_PULSE_0_9_12
389
static void
Wim Taymans's avatar
Wim Taymans committed
390
391
gst_pulsering_context_subscribe_cb (pa_context * c,
    pa_subscription_event_type_t t, uint32_t idx, void *userdata)
392
{
Wim Taymans's avatar
Wim Taymans committed
393
  GstPulseSink *psink;
394
395
  GstPulseContext *pctx = (GstPulseContext *) userdata;
  GSList *walk;
Wim Taymans's avatar
Wim Taymans committed
396

Wim Taymans's avatar
Wim Taymans committed
397
398
  if (t != (PA_SUBSCRIPTION_EVENT_SINK_INPUT | PA_SUBSCRIPTION_EVENT_CHANGE) &&
      t != (PA_SUBSCRIPTION_EVENT_SINK_INPUT | PA_SUBSCRIPTION_EVENT_NEW))
399
    return;
400

401
402
403
  for (walk = pctx->ring_buffers; walk; walk = g_slist_next (walk)) {
    GstPulseRingBuffer *pbuf = (GstPulseRingBuffer *) walk->data;
    psink = GST_PULSESINK_CAST (GST_OBJECT_PARENT (pbuf));
404

405
406
407
408
    GST_LOG_OBJECT (psink, "type %d, idx %u", t, idx);

    if (!pbuf->stream)
      continue;
409

410
411
    if (idx != pa_stream_get_index (pbuf->stream))
      continue;
Wim Taymans's avatar
Wim Taymans committed
412

413
414
415
416
417
418
419
420
    /* Actually this event is also triggered when other properties of
     * the stream change that are unrelated to the volume. However it is
     * probably cheaper to signal the change here and check for the
     * volume when the GObject property is read instead of querying it always. */

    /* inform streaming thread to notify */
    g_atomic_int_compare_and_exchange (&psink->notify, 0, 1);
  }
421
}
Wim Taymans's avatar
Wim Taymans committed
422
#endif
423

Wim Taymans's avatar
Wim Taymans committed
424
425
426
427
/* will be called when the device should be opened. In this case we will connect
 * to the server. We should not try to open any streams in this state. */
static gboolean
gst_pulseringbuffer_open_device (GstRingBuffer * buf)
428
{
Wim Taymans's avatar
Wim Taymans committed
429
430
  GstPulseSink *psink;
  GstPulseRingBuffer *pbuf;
431
  GstPulseContext *pctx;
Wim Taymans's avatar
Wim Taymans committed
432
  pa_mainloop_api *api;
433

Wim Taymans's avatar
Wim Taymans committed
434
435
  psink = GST_PULSESINK_CAST (GST_OBJECT_PARENT (buf));
  pbuf = GST_PULSERING_BUFFER_CAST (buf);
436

Wim Taymans's avatar
Wim Taymans committed
437
  g_assert (!pbuf->stream);
438

439
440
  pbuf->context_name = g_strdup_printf ("%s@%s", gst_pulse_client_name (),
      GST_STR_NULL (psink->server));
441

Wim Taymans's avatar
Wim Taymans committed
442
  pa_threaded_mainloop_lock (psink->mainloop);
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
  g_mutex_lock (pa_ring_buffer_mutex);

  pctx = g_hash_table_lookup (gst_pulse_shared_contexts, pbuf->context_name);
  if (pctx == NULL) {
    pctx = g_slice_new0 (GstPulseContext);
    /* get the mainloop api and create a context */
    GST_LOG_OBJECT (psink, "new context with name %s",
        GST_STR_NULL (pbuf->context_name));
    api = pa_threaded_mainloop_get_api (psink->mainloop);
    if (!(pctx->context = pa_context_new (api, pbuf->context_name)))
      goto create_failed;

    pctx->ring_buffers = g_slist_append (pctx->ring_buffers, pbuf);
    g_hash_table_insert (gst_pulse_shared_contexts, pbuf->context_name,
        (gpointer) pctx);
    /* register some essential callbacks */
    pa_context_set_state_callback (pctx->context,
        gst_pulsering_context_state_cb, pctx);
461
#ifdef HAVE_PULSE_0_9_12
462
463
    pa_context_set_subscribe_callback (pctx->context,
        gst_pulsering_context_subscribe_cb, pctx);
Wim Taymans's avatar
Wim Taymans committed
464
#endif
465

466
467
468
469
470
471
472
473
474
475
476
477
478
479
    /* try to connect to the server and wait for completioni, we don't want to
     * autospawn a deamon */
    GST_LOG_OBJECT (psink, "connect to server %s",
        GST_STR_NULL (psink->server));
    if (pa_context_connect (pctx->context, psink->server,
            PA_CONTEXT_NOAUTOSPAWN, NULL) < 0)
      goto connect_failed;


  } else {
    GST_LOG_OBJECT (psink, "reusing shared pulseaudio context with name %s",
        GST_STR_NULL (pbuf->context_name));
    pctx->ring_buffers = g_slist_append (pctx->ring_buffers, pbuf);
  }
480

Wim Taymans's avatar
Wim Taymans committed
481
482
  for (;;) {
    pa_context_state_t state;
483

484
    state = pa_context_get_state (pctx->context);
485

Wim Taymans's avatar
Wim Taymans committed
486
    GST_LOG_OBJECT (psink, "context state is now %d", state);
487

Wim Taymans's avatar
Wim Taymans committed
488
489
    if (!PA_CONTEXT_IS_GOOD (state))
      goto connect_failed;
490

Wim Taymans's avatar
Wim Taymans committed
491
492
    if (state == PA_CONTEXT_READY)
      break;
493

Wim Taymans's avatar
Wim Taymans committed
494
495
496
497
    /* Wait until the context is ready */
    GST_LOG_OBJECT (psink, "waiting..");
    pa_threaded_mainloop_wait (psink->mainloop);
  }
498

Wim Taymans's avatar
Wim Taymans committed
499
  GST_LOG_OBJECT (psink, "opened the device");
500

501
  g_mutex_unlock (pa_ring_buffer_mutex);
Wim Taymans's avatar
Wim Taymans committed
502
  pa_threaded_mainloop_unlock (psink->mainloop);
503

Wim Taymans's avatar
Wim Taymans committed
504
  return TRUE;
505

Wim Taymans's avatar
Wim Taymans committed
506
507
508
  /* ERRORS */
unlock_and_fail:
  {
509
    g_mutex_unlock (pa_ring_buffer_mutex);
Wim Taymans's avatar
Wim Taymans committed
510
    gst_pulsering_destroy_context (pbuf);
511

Wim Taymans's avatar
Wim Taymans committed
512
513
514
515
516
517
518
519
520
521
522
523
    pa_threaded_mainloop_unlock (psink->mainloop);
    return FALSE;
  }
create_failed:
  {
    GST_ELEMENT_ERROR (psink, RESOURCE, FAILED,
        ("Failed to create context"), (NULL));
    goto unlock_and_fail;
  }
connect_failed:
  {
    GST_ELEMENT_ERROR (psink, RESOURCE, FAILED, ("Failed to connect: %s",
524
            pa_strerror (pa_context_errno (pctx->context))), (NULL));
Wim Taymans's avatar
Wim Taymans committed
525
    goto unlock_and_fail;
526
527
528
  }
}

Wim Taymans's avatar
Wim Taymans committed
529
530
531
/* close the device */
static gboolean
gst_pulseringbuffer_close_device (GstRingBuffer * buf)
532
{
Wim Taymans's avatar
Wim Taymans committed
533
534
  GstPulseSink *psink;
  GstPulseRingBuffer *pbuf;
535

Wim Taymans's avatar
Wim Taymans committed
536
537
  pbuf = GST_PULSERING_BUFFER_CAST (buf);
  psink = GST_PULSESINK_CAST (GST_OBJECT_PARENT (buf));
538

Wim Taymans's avatar
Wim Taymans committed
539
  GST_LOG_OBJECT (psink, "closing device");
540

Wim Taymans's avatar
Wim Taymans committed
541
542
543
  pa_threaded_mainloop_lock (psink->mainloop);
  gst_pulsering_destroy_context (pbuf);
  pa_threaded_mainloop_unlock (psink->mainloop);
544

Wim Taymans's avatar
Wim Taymans committed
545
  GST_LOG_OBJECT (psink, "closed device");
546

Wim Taymans's avatar
Wim Taymans committed
547
  return TRUE;
548
549
550
}

static void
Wim Taymans's avatar
Wim Taymans committed
551
gst_pulsering_stream_state_cb (pa_stream * s, void *userdata)
552
{
Wim Taymans's avatar
Wim Taymans committed
553
554
555
  GstPulseSink *psink;
  GstPulseRingBuffer *pbuf;
  pa_stream_state_t state;
556

Wim Taymans's avatar
Wim Taymans committed
557
558
  pbuf = GST_PULSERING_BUFFER_CAST (userdata);
  psink = GST_PULSESINK_CAST (GST_OBJECT_PARENT (pbuf));
559

Wim Taymans's avatar
Wim Taymans committed
560
561
  state = pa_stream_get_state (s);
  GST_LOG_OBJECT (psink, "got new stream state %d", state);
562

Wim Taymans's avatar
Wim Taymans committed
563
564
565
566
567
568
569
570
571
572
  switch (state) {
    case PA_STREAM_READY:
    case PA_STREAM_FAILED:
    case PA_STREAM_TERMINATED:
      GST_LOG_OBJECT (psink, "signaling");
      pa_threaded_mainloop_signal (psink->mainloop, 0);
      break;
    case PA_STREAM_UNCONNECTED:
    case PA_STREAM_CREATING:
      break;
573
  }
Wim Taymans's avatar
Wim Taymans committed
574
}
575

Wim Taymans's avatar
Wim Taymans committed
576
577
578
579
static void
gst_pulsering_stream_request_cb (pa_stream * s, size_t length, void *userdata)
{
  GstPulseSink *psink;
580
  GstRingBuffer *rbuf;
Wim Taymans's avatar
Wim Taymans committed
581
  GstPulseRingBuffer *pbuf;
582

583
  rbuf = GST_RING_BUFFER_CAST (userdata);
Wim Taymans's avatar
Wim Taymans committed
584
585
  pbuf = GST_PULSERING_BUFFER_CAST (userdata);
  psink = GST_PULSESINK_CAST (GST_OBJECT_PARENT (pbuf));
586

Wim Taymans's avatar
Wim Taymans committed
587
  GST_LOG_OBJECT (psink, "got request for length %" G_GSIZE_FORMAT, length);
588

Stefan Kost's avatar
Stefan Kost committed
589
590
  if (pbuf->in_commit && (length >= rbuf->spec.segsize)) {
    /* only signal when we are waiting in the commit thread
591
     * and got request for atleast a segment */
Wim Taymans's avatar
Wim Taymans committed
592
593
    pa_threaded_mainloop_signal (psink->mainloop, 0);
  }
594
595
}

596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
static void
gst_pulsering_stream_underflow_cb (pa_stream * s, void *userdata)
{
  GstPulseSink *psink;
  GstPulseRingBuffer *pbuf;

  pbuf = GST_PULSERING_BUFFER_CAST (userdata);
  psink = GST_PULSESINK_CAST (GST_OBJECT_PARENT (pbuf));

  GST_WARNING_OBJECT (psink, "Got underflow");
}

static void
gst_pulsering_stream_overflow_cb (pa_stream * s, void *userdata)
{
  GstPulseSink *psink;
  GstPulseRingBuffer *pbuf;

  pbuf = GST_PULSERING_BUFFER_CAST (userdata);
  psink = GST_PULSESINK_CAST (GST_OBJECT_PARENT (pbuf));

  GST_WARNING_OBJECT (psink, "Got overflow");
}

620
621
622
623
624
625
static void
gst_pulsering_stream_latency_cb (pa_stream * s, void *userdata)
{
  GstPulseSink *psink;
  GstPulseRingBuffer *pbuf;
  const pa_timing_info *info;
626
  pa_usec_t sink_usec;
627
628
629
630
631
632

  info = pa_stream_get_timing_info (s);

  pbuf = GST_PULSERING_BUFFER_CAST (userdata);
  psink = GST_PULSESINK_CAST (GST_OBJECT_PARENT (pbuf));

633
634
635
636
  if (!info) {
    GST_LOG_OBJECT (psink, "latency update (information unknown)");
    return;
  }
637
#ifdef HAVE_PULSE_0_9_11
638
639
640
641
642
  sink_usec = info->configured_sink_usec;
#else
  sink_usec = 0;
#endif

643
644
  GST_LOG_OBJECT (psink,
      "latency_update, %" G_GUINT64_FORMAT ", %d:%" G_GINT64_FORMAT ", %d:%"
Wim Taymans's avatar
Wim Taymans committed
645
      G_GUINT64_FORMAT ", %" G_GUINT64_FORMAT ", %" G_GUINT64_FORMAT,
646
647
      GST_TIMEVAL_TO_TIME (info->timestamp), info->write_index_corrupt,
      info->write_index, info->read_index_corrupt, info->read_index,
648
      info->sink_usec, sink_usec);
649
650
}

651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
static void
gst_pulsering_stream_suspended_cb (pa_stream * p, void *userdata)
{
  GstPulseSink *psink;
  GstPulseRingBuffer *pbuf;

  pbuf = GST_PULSERING_BUFFER_CAST (userdata);
  psink = GST_PULSESINK_CAST (GST_OBJECT_PARENT (pbuf));

  if (pa_stream_is_suspended (p))
    GST_DEBUG_OBJECT (psink, "stream suspended");
  else
    GST_DEBUG_OBJECT (psink, "stream resumed");
}

666
#ifdef HAVE_PULSE_0_9_11
667
668
669
670
671
672
673
674
675
676
677
678
679
static void
gst_pulsering_stream_started_cb (pa_stream * p, void *userdata)
{
  GstPulseSink *psink;
  GstPulseRingBuffer *pbuf;

  pbuf = GST_PULSERING_BUFFER_CAST (userdata);
  psink = GST_PULSESINK_CAST (GST_OBJECT_PARENT (pbuf));

  GST_DEBUG_OBJECT (psink, "stream started");
}
#endif

680
#ifdef HAVE_PULSE_0_9_15
Wim Taymans's avatar
Wim Taymans committed
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
static void
gst_pulsering_stream_event_cb (pa_stream * p, const char *name,
    pa_proplist * pl, void *userdata)
{
  GstPulseSink *psink;
  GstPulseRingBuffer *pbuf;

  pbuf = GST_PULSERING_BUFFER_CAST (userdata);
  psink = GST_PULSESINK_CAST (GST_OBJECT_PARENT (pbuf));

  if (!strcmp (name, PA_STREAM_EVENT_REQUEST_CORK)) {
    /* the stream wants to PAUSE, post a message for the application. */
    GST_DEBUG_OBJECT (psink, "got request for CORK");
    gst_element_post_message (GST_ELEMENT_CAST (psink),
        gst_message_new_request_state (GST_OBJECT_CAST (psink),
            GST_STATE_PAUSED));

  } else if (!strcmp (name, PA_STREAM_EVENT_REQUEST_UNCORK)) {
    GST_DEBUG_OBJECT (psink, "got request for UNCORK");
    gst_element_post_message (GST_ELEMENT_CAST (psink),
        gst_message_new_request_state (GST_OBJECT_CAST (psink),
            GST_STATE_PLAYING));
  } else {
    GST_DEBUG_OBJECT (psink, "got unknown event %s", name);
  }
}
#endif

Wim Taymans's avatar
Wim Taymans committed
709
710
711
712
/* This method should create a new stream of the given @spec. No playback should
 * start yet so we start in the corked state. */
static gboolean
gst_pulseringbuffer_acquire (GstRingBuffer * buf, GstRingBufferSpec * spec)
713
{
Wim Taymans's avatar
Wim Taymans committed
714
715
  GstPulseSink *psink;
  GstPulseRingBuffer *pbuf;
716
  GstPulseContext *pctx;
717
718
  pa_buffer_attr wanted;
  const pa_buffer_attr *actual;
Wim Taymans's avatar
Wim Taymans committed
719
  pa_channel_map channel_map;
720
  pa_operation *o = NULL;
721
#ifdef HAVE_PULSE_0_9_20
722
723
724
  pa_cvolume v;
#endif
  pa_cvolume *pv = NULL;
Wim Taymans's avatar
Wim Taymans committed
725
726
  pa_stream_flags_t flags;
  const gchar *name;
727
  GstAudioClock *clock;
728

Wim Taymans's avatar
Wim Taymans committed
729
730
  psink = GST_PULSESINK_CAST (GST_OBJECT_PARENT (buf));
  pbuf = GST_PULSERING_BUFFER_CAST (buf);
731

Wim Taymans's avatar
Wim Taymans committed
732
733
734
735
  GST_LOG_OBJECT (psink, "creating sample spec");
  /* convert the gstreamer sample spec to the pulseaudio format */
  if (!gst_pulse_fill_sample_spec (spec, &pbuf->sample_spec))
    goto invalid_spec;
736

Wim Taymans's avatar
Wim Taymans committed
737
  pa_threaded_mainloop_lock (psink->mainloop);
738

Wim Taymans's avatar
Wim Taymans committed
739
  /* we need a context and a no stream */
740
  pctx = gst_pulsering_get_context (pbuf);
Wim Taymans's avatar
Wim Taymans committed
741
  g_assert (!pbuf->stream);
742

Wim Taymans's avatar
Wim Taymans committed
743
744
  /* enable event notifications */
  GST_LOG_OBJECT (psink, "subscribing to context events");
745
  if (!(o = pa_context_subscribe (pctx->context,
Wim Taymans's avatar
Wim Taymans committed
746
747
              PA_SUBSCRIPTION_MASK_SINK_INPUT, NULL, NULL)))
    goto subscribe_failed;
748

Wim Taymans's avatar
Wim Taymans committed
749
  pa_operation_unref (o);
750

Wim Taymans's avatar
Wim Taymans committed
751
752
753
754
755
756
757
758
759
760
761
  /* initialize the channel map */
  gst_pulse_gst_to_channel_map (&channel_map, spec);

  /* find a good name for the stream */
  if (psink->stream_name)
    name = psink->stream_name;
  else
    name = "Playback Stream";

  /* create a stream */
  GST_LOG_OBJECT (psink, "creating stream with name %s", name);
762
  if (!(pbuf->stream = pa_stream_new (pctx->context,
Wim Taymans's avatar
Wim Taymans committed
763
764
765
766
767
768
769
770
              name, &pbuf->sample_spec, &channel_map)))
    goto stream_failed;

  /* install essential callbacks */
  pa_stream_set_state_callback (pbuf->stream,
      gst_pulsering_stream_state_cb, pbuf);
  pa_stream_set_write_callback (pbuf->stream,
      gst_pulsering_stream_request_cb, pbuf);
771
772
773
774
  pa_stream_set_underflow_callback (pbuf->stream,
      gst_pulsering_stream_underflow_cb, pbuf);
  pa_stream_set_overflow_callback (pbuf->stream,
      gst_pulsering_stream_overflow_cb, pbuf);
775
776
  pa_stream_set_latency_update_callback (pbuf->stream,
      gst_pulsering_stream_latency_cb, pbuf);
777
778
  pa_stream_set_suspended_callback (pbuf->stream,
      gst_pulsering_stream_suspended_cb, pbuf);
779
#ifdef HAVE_PULSE_0_9_11
780
781
782
  pa_stream_set_started_callback (pbuf->stream,
      gst_pulsering_stream_started_cb, pbuf);
#endif
783
#ifdef HAVE_PULSE_0_9_15
Wim Taymans's avatar
Wim Taymans committed
784
785
786
  pa_stream_set_event_callback (pbuf->stream,
      gst_pulsering_stream_event_cb, pbuf);
#endif
Wim Taymans's avatar
Wim Taymans committed
787

788
789
  /* buffering requirements. When setting prebuf to 0, the stream will not pause
   * when we cause an underrun, which causes time to continue. */
790
791
792
793
  memset (&wanted, 0, sizeof (wanted));
  wanted.tlength = spec->segtotal * spec->segsize;
  wanted.maxlength = -1;
  wanted.prebuf = 0;
794
  wanted.minreq = spec->segsize;
795

796
797
798
799
  GST_INFO_OBJECT (psink, "tlength:   %d", wanted.tlength);
  GST_INFO_OBJECT (psink, "maxlength: %d", wanted.maxlength);
  GST_INFO_OBJECT (psink, "prebuf:    %d", wanted.prebuf);
  GST_INFO_OBJECT (psink, "minreq:    %d", wanted.minreq);
Wim Taymans's avatar
Wim Taymans committed
800

801
#ifdef HAVE_PULSE_0_9_20
Wim Taymans's avatar
Wim Taymans committed
802
803
804
805
806
807
808
809
810
  /* configure volume when we changed it, else we leave the default */
  if (psink->volume_set) {
    GST_LOG_OBJECT (psink, "have volume of %f", psink->volume);
    pv = &v;
    gst_pulse_cvolume_from_linear (pv, pbuf->sample_spec.channels,
        psink->volume);
  } else {
    pv = NULL;
  }
811
#endif
812

Wim Taymans's avatar
Wim Taymans committed
813
  /* construct the flags */
814
  flags = PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE |
815
#ifdef HAVE_PULSE_0_9_11
Wim Taymans's avatar
Wim Taymans committed
816
817
818
      PA_STREAM_ADJUST_LATENCY |
#endif
      PA_STREAM_START_CORKED;
819

820
#ifdef HAVE_PULSE_0_9_12
821
822
823
824
  if (psink->mute_set && psink->mute)
    flags |= PA_STREAM_START_MUTED;
#endif

Wim Taymans's avatar
Wim Taymans committed
825
826
  /* we always start corked (see flags above) */
  pbuf->corked = TRUE;
827

Wim Taymans's avatar
Wim Taymans committed
828
829
830
831
  /* try to connect now */
  GST_LOG_OBJECT (psink, "connect for playback to device %s",
      GST_STR_NULL (psink->device));
  if (pa_stream_connect_playback (pbuf->stream, psink->device,
832
          &wanted, flags, pv, NULL) < 0)
Wim Taymans's avatar
Wim Taymans committed
833
    goto connect_failed;
834

835
836
837
838
  /* our clock will now start from 0 again */
  clock = GST_AUDIO_CLOCK (GST_BASE_AUDIO_SINK (psink)->provided_clock);
  gst_audio_clock_reset (clock, 0);

839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
  for (;;) {
    pa_stream_state_t state;

    state = pa_stream_get_state (pbuf->stream);

    GST_LOG_OBJECT (psink, "stream state is now %d", state);

    if (!PA_STREAM_IS_GOOD (state))
      goto connect_failed;

    if (state == PA_STREAM_READY)
      break;

    /* Wait until the stream is ready */
    pa_threaded_mainloop_wait (psink->mainloop);
  }
855

856
857
858
859
  /* After we passed the volume off of to PA we never want to set it
     again, since it is PA's job to save/restore volumes.  */
  psink->volume_set = psink->mute_set = FALSE;

Wim Taymans's avatar
Wim Taymans committed
860
  GST_LOG_OBJECT (psink, "stream is acquired now");
861

Wim Taymans's avatar
Wim Taymans committed
862
  /* get the actual buffering properties now */
863
  actual = pa_stream_get_buffer_attr (pbuf->stream);
864

865
866
867
868
869
870
  GST_INFO_OBJECT (psink, "tlength:   %d (wanted: %d)", actual->tlength,
      wanted.tlength);
  GST_INFO_OBJECT (psink, "maxlength: %d", actual->maxlength);
  GST_INFO_OBJECT (psink, "prebuf:    %d", actual->prebuf);
  GST_INFO_OBJECT (psink, "minreq:    %d (wanted %d)", actual->minreq,
      wanted.minreq);
871

872
873
  spec->segsize = actual->minreq;
  spec->segtotal = actual->tlength / spec->segsize;
874

Wim Taymans's avatar
Wim Taymans committed
875
  pa_threaded_mainloop_unlock (psink->mainloop);
876

Wim Taymans's avatar
Wim Taymans committed
877
  return TRUE;
878

Wim Taymans's avatar
Wim Taymans committed
879
880
881
882
883
  /* ERRORS */
unlock_and_fail:
  {
    gst_pulsering_destroy_stream (pbuf);
    pa_threaded_mainloop_unlock (psink->mainloop);
884

Wim Taymans's avatar
Wim Taymans committed
885
    return FALSE;
886
  }
Wim Taymans's avatar
Wim Taymans committed
887
888
889
890
891
892
893
894
895
896
invalid_spec:
  {
    GST_ELEMENT_ERROR (psink, RESOURCE, SETTINGS,
        ("Invalid sample specification."), (NULL));
    return FALSE;
  }
subscribe_failed:
  {
    GST_ELEMENT_ERROR (psink, RESOURCE, FAILED,
        ("pa_context_subscribe() failed: %s",
897
            pa_strerror (pa_context_errno (pctx->context))), (NULL));
Wim Taymans's avatar
Wim Taymans committed
898
899
900
901
902
903
    goto unlock_and_fail;
  }
stream_failed:
  {
    GST_ELEMENT_ERROR (psink, RESOURCE, FAILED,
        ("Failed to create stream: %s",
904
            pa_strerror (pa_context_errno (pctx->context))), (NULL));
Wim Taymans's avatar
Wim Taymans committed
905
906
907
908
909
910
    goto unlock_and_fail;
  }
connect_failed:
  {
    GST_ELEMENT_ERROR (psink, RESOURCE, FAILED,
        ("Failed to connect stream: %s",
911
            pa_strerror (pa_context_errno (pctx->context))), (NULL));
Wim Taymans's avatar
Wim Taymans committed
912
913
914
    goto unlock_and_fail;
  }
}
915

Wim Taymans's avatar
Wim Taymans committed
916
917
918
919
920
921
/* free the stream that we acquired before */
static gboolean
gst_pulseringbuffer_release (GstRingBuffer * buf)
{
  GstPulseSink *psink;
  GstPulseRingBuffer *pbuf;
922

Wim Taymans's avatar
Wim Taymans committed
923
924
  psink = GST_PULSESINK_CAST (GST_OBJECT_PARENT (buf));
  pbuf = GST_PULSERING_BUFFER_CAST (buf);
925

Wim Taymans's avatar
Wim Taymans committed
926
927
928
  pa_threaded_mainloop_lock (psink->mainloop);
  gst_pulsering_destroy_stream (pbuf);
  pa_threaded_mainloop_unlock (psink->mainloop);
929

Wim Taymans's avatar
Wim Taymans committed
930
931
  return TRUE;
}
932

933
934
935
936
937
938
939
940
941
942
943
944
static void
gst_pulsering_success_cb (pa_stream * s, int success, void *userdata)
{
  GstPulseRingBuffer *pbuf;
  GstPulseSink *psink;

  pbuf = GST_PULSERING_BUFFER_CAST (userdata);
  psink = GST_PULSESINK_CAST (GST_OBJECT_PARENT (pbuf));

  pa_threaded_mainloop_signal (psink->mainloop, 0);
}

Wim Taymans's avatar
Wim Taymans committed
945
946
947
/* update the corked state of a stream, must be called with the mainloop
 * lock */
static gboolean
948
949
gst_pulsering_set_corked (GstPulseRingBuffer * pbuf, gboolean corked,
    gboolean wait)
950
{
Wim Taymans's avatar
Wim Taymans committed
951
952
  pa_operation *o = NULL;
  GstPulseSink *psink;
953
  GstPulseContext *pctx = NULL;
Wim Taymans's avatar
Wim Taymans committed
954
  gboolean res = FALSE;
955

Wim Taymans's avatar
Wim Taymans committed
956
  psink = GST_PULSESINK_CAST (GST_OBJECT_PARENT (pbuf));
957

Wim Taymans's avatar
Wim Taymans committed
958
959
  GST_DEBUG_OBJECT (psink, "setting corked state to %d", corked);
  if (pbuf->corked != corked) {
960
961
    if (!(o = pa_stream_cork (pbuf->stream, corked,
                gst_pulsering_success_cb, pbuf)))
Wim Taymans's avatar
Wim Taymans committed
962
      goto cork_failed;
963

964
    while (wait && pa_operation_get_state (o) == PA_OPERATION_RUNNING) {
Wim Taymans's avatar
Wim Taymans committed
965
966
967
968
969
      pa_threaded_mainloop_wait (psink->mainloop);
      if (gst_pulsering_is_dead (psink, pbuf))
        goto server_dead;
    }
    pbuf->corked = corked;
Stefan Kost's avatar
Stefan Kost committed
970
971
  } else {
    GST_DEBUG_OBJECT (psink, "skipping, already in requested state");
Wim Taymans's avatar
Wim Taymans committed
972
973
  }
  res = TRUE;
974

Wim Taymans's avatar
Wim Taymans committed
975
976
977
cleanup:
  if (o)
    pa_operation_unref (o);
978

Wim Taymans's avatar
Wim Taymans committed
979
  return res;
980

Wim Taymans's avatar
Wim Taymans committed
981
982
983
984
985
986
987
988
  /* ERRORS */
server_dead:
  {
    GST_DEBUG_OBJECT (psink, "the server is dead");
    goto cleanup;
  }
cork_failed:
  {
989
    pctx = gst_pulsering_get_context (pbuf);
Wim Taymans's avatar
Wim Taymans committed
990
991
    GST_ELEMENT_ERROR (psink, RESOURCE, FAILED,
        ("pa_stream_cork() failed: %s",
992
            pa_strerror (pa_context_errno (pctx->context))), (NULL));
Wim Taymans's avatar
Wim Taymans committed
993
    goto cleanup;
994
995
996
  }
}

997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
static void
gst_pulseringbuffer_clear (GstRingBuffer * buf)
{
  GstPulseSink *psink;
  GstPulseRingBuffer *pbuf;
  pa_operation *o = NULL;

  pbuf = GST_PULSERING_BUFFER_CAST (buf);
  psink = GST_PULSESINK_CAST (GST_OBJECT_PARENT (pbuf));

  pa_threaded_mainloop_lock (psink->mainloop);
  GST_DEBUG_OBJECT (psink, "clearing");
  if (pbuf->stream) {
    /* don't wait for the flush to complete */
    if ((o = pa_stream_flush (pbuf->stream, NULL, pbuf)))
      pa_operation_unref (o);
  }
  pa_threaded_mainloop_unlock (psink->mainloop);
}

1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
static void
mainloop_enter_defer_cb (pa_mainloop_api * api, void *userdata)
{
  GstPulseSink *pulsesink = GST_PULSESINK (userdata);
  GstMessage *message;
  GValue val = { 0 };

  g_value_init (&val, G_TYPE_POINTER);
  g_value_set_pointer (&val, g_thread_self ());

  GST_DEBUG_OBJECT (pulsesink, "posting ENTER stream status");
  message = gst_message_new_stream_status (GST_OBJECT (pulsesink),
      GST_STREAM_STATUS_TYPE_ENTER, GST_ELEMENT (pulsesink));
  gst_message_set_stream_status_object (message, &val);
1031

1032
1033
1034
1035
1036
1037
1038
  gst_element_post_message (GST_ELEMENT (pulsesink), message);

  /* signal the waiter */
  pulsesink->pa_defer_ran = TRUE;
  pa_threaded_mainloop_signal (pulsesink->mainloop, 0);
}

1039
/* start/resume playback ASAP, we don't uncork here but in the commit method */
Wim Taymans's avatar
Wim Taymans committed
1040
1041
static gboolean
gst_pulseringbuffer_start (GstRingBuffer * buf)
1042
{
Wim Taymans's avatar
Wim Taymans committed
1043
1044
  GstPulseSink *psink;
  GstPulseRingBuffer *pbuf;
1045

Wim Taymans's avatar
Wim Taymans committed
1046
1047
  pbuf = GST_PULSERING_BUFFER_CAST (buf);
  psink = GST_PULSESINK_CAST (GST_OBJECT_PARENT (pbuf));
1048

Wim Taymans's avatar
Wim Taymans committed
1049
  pa_threaded_mainloop_lock (psink->mainloop);
1050
1051
1052
1053
1054
1055

  GST_DEBUG_OBJECT (psink, "scheduling stream status");
  psink->pa_defer_ran = FALSE;
  pa_mainloop_api_once (pa_threaded_mainloop_get_api (psink->mainloop),
      mainloop_enter_defer_cb, psink);

1056
  GST_DEBUG_OBJECT (psink, "starting");
Wim Taymans's avatar
Wim Taymans committed
1057
  pbuf->paused = FALSE;
1058
  gst_pulsering_set_corked (pbuf, FALSE, FALSE);
Wim Taymans's avatar
Wim Taymans committed
1059
  pa_threaded_mainloop_unlock (psink->mainloop);
1060

1061
  return TRUE;
Wim Taymans's avatar
Wim Taymans committed
1062
}
1063

Wim Taymans's avatar
Wim Taymans committed
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
/* pause/stop playback ASAP */
static gboolean
gst_pulseringbuffer_pause (GstRingBuffer * buf)
{
  GstPulseSink *psink;
  GstPulseRingBuffer *pbuf;
  gboolean res;

  pbuf = GST_PULSERING_BUFFER_CAST (buf);
  psink = GST_PULSESINK_CAST (GST_OBJECT_PARENT (pbuf));

  pa_threaded_mainloop_lock (psink->mainloop);
1076
  GST_DEBUG_OBJECT (psink, "pausing and corking");
Wim Taymans's avatar
Wim Taymans committed
1077
1078
  /* make sure the commit method stops writing */
  pbuf->paused = TRUE;
1079
  res = gst_pulsering_set_corked (pbuf, TRUE, FALSE);
Wim Taymans's avatar
Wim Taymans committed
1080
1081
1082
1083
  if (pbuf->in_commit) {
    /* we are waiting in a commit, signal */
    GST_DEBUG_OBJECT (psink, "signal commit");
    pa_threaded_mainloop_signal (psink->mainloop, 0);
1084
  }
Wim Taymans's avatar
Wim Taymans committed
1085
1086
1087
  pa_threaded_mainloop_unlock (psink->mainloop);

  return res;
1088
1089
}

1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
static void
mainloop_leave_defer_cb (pa_mainloop_api * api, v