pulsesink.c 52.6 KB
Newer Older
Wim Taymans's avatar
Wim Taymans committed
1
/*  GStreamer pulseaudio plugin
2
3
 *
 *  Copyright (c) 2004-2008 Lennart Poettering
Wim Taymans's avatar
Wim Taymans committed
4
 *            (c) 2009      Wim Taymans
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 *
 *  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.
 */

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

40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

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

#include <gst/base/gstbasesink.h>
#include <gst/gsttaglist.h>

#include "pulsesink.h"
#include "pulseutil.h"

GST_DEBUG_CATEGORY_EXTERN (pulse_debug);
#define GST_CAT_DEFAULT pulse_debug

56
57
58
59
60
/* according to
 * http://www.pulseaudio.org/ticket/314
 * we need pulse-0.9.12 to use sink volume properties
 */

61
62
63
64
enum
{
  PROP_SERVER = 1,
  PROP_DEVICE,
65
66
  PROP_DEVICE_NAME,
  PROP_VOLUME
67
68
};

Wim Taymans's avatar
Wim Taymans committed
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
#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;

/* 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;
93

Wim Taymans's avatar
Wim Taymans committed
94
  gchar *stream_name;
95

Wim Taymans's avatar
Wim Taymans committed
96
97
  pa_context *context;
  pa_stream *stream;
98

Wim Taymans's avatar
Wim Taymans committed
99
  pa_sample_spec sample_spec;
100
  gint64 offset;
101

Wim Taymans's avatar
Wim Taymans committed
102
103
104
105
106
  gboolean corked;
  gboolean in_commit;
  gboolean paused;
  guint required;
};
107

Wim Taymans's avatar
Wim Taymans committed
108
109
110
111
struct _GstPulseRingBufferClass
{
  GstRingBufferClass parent_class;
};
112

Wim Taymans's avatar
Wim Taymans committed
113
114
115
116
static void gst_pulseringbuffer_class_init (GstPulseRingBufferClass * klass);
static void gst_pulseringbuffer_init (GstPulseRingBuffer * ringbuffer,
    GstPulseRingBufferClass * klass);
static void gst_pulseringbuffer_finalize (GObject * object);
117

Wim Taymans's avatar
Wim Taymans committed
118
static GstRingBufferClass *ring_parent_class = NULL;
119

Wim Taymans's avatar
Wim Taymans committed
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
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);
static guint gst_pulseringbuffer_commit (GstRingBuffer * buf,
    guint64 * sample, guchar * data, gint in_samples, gint out_samples,
    gint * accum);

/* ringbuffer abstract base class */
static GType
gst_pulseringbuffer_get_type (void)
{
  static GType ringbuffer_type = 0;

  if (!ringbuffer_type) {
    static const GTypeInfo ringbuffer_info = {
      sizeof (GstPulseRingBufferClass),
      NULL,
      NULL,
      (GClassInitFunc) gst_pulseringbuffer_class_init,
      NULL,
      NULL,
      sizeof (GstPulseRingBuffer),
      0,
      (GInstanceInitFunc) gst_pulseringbuffer_init,
      NULL
    };

    ringbuffer_type =
        g_type_register_static (GST_TYPE_RING_BUFFER, "GstPulseSinkRingBuffer",
        &ringbuffer_info, 0);
  }
  return ringbuffer_type;
}
158

Wim Taymans's avatar
Wim Taymans committed
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
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);

  gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_pulseringbuffer_finalize);

  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);

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

Wim Taymans's avatar
Wim Taymans committed
188
189
190
191
192
193
194
static void
gst_pulseringbuffer_init (GstPulseRingBuffer * pbuf,
    GstPulseRingBufferClass * g_class)
{
  pbuf->stream_name = NULL;
  pbuf->context = NULL;
  pbuf->stream = NULL;
195

Wim Taymans's avatar
Wim Taymans committed
196
197
#if HAVE_PULSE_0_9_13
  pa_sample_spec_init (&pbuf->sample_spec);
198
#else
Wim Taymans's avatar
Wim Taymans committed
199
200
201
  pbuf->sample_spec.format = PA_SAMPLE_INVALID;
  pbuf->sample_spec.rate = 0;
  pbuf->sample_spec.channels = 0;
202
203
#endif

204
  pbuf->paused = FALSE;
Wim Taymans's avatar
Wim Taymans committed
205
206
  pbuf->corked = TRUE;
}
207

Wim Taymans's avatar
Wim Taymans committed
208
209
static void
gst_pulsering_destroy_stream (GstPulseRingBuffer * pbuf)
210
{
Wim Taymans's avatar
Wim Taymans committed
211
212
  if (pbuf->stream) {
    pa_stream_disconnect (pbuf->stream);
213

Wim Taymans's avatar
Wim Taymans committed
214
215
216
    /* 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);
217
218
    pa_stream_set_underflow_callback (pbuf->stream, NULL, NULL);
    pa_stream_set_overflow_callback (pbuf->stream, NULL, NULL);
219

Wim Taymans's avatar
Wim Taymans committed
220
221
222
223
224
225
    pa_stream_unref (pbuf->stream);
    pbuf->stream = NULL;
  }

  g_free (pbuf->stream_name);
  pbuf->stream_name = NULL;
226
227
228
}

static void
Wim Taymans's avatar
Wim Taymans committed
229
gst_pulsering_destroy_context (GstPulseRingBuffer * pbuf)
230
{
Wim Taymans's avatar
Wim Taymans committed
231
232
233
234
235
236
237
238
239
240
241
242
  gst_pulsering_destroy_stream (pbuf);

  if (pbuf->context) {
    pa_context_disconnect (pbuf->context);

    /* Make sure we don't get any further callbacks */
    pa_context_set_state_callback (pbuf->context, NULL, NULL);
    pa_context_set_subscribe_callback (pbuf->context, NULL, NULL);

    pa_context_unref (pbuf->context);
    pbuf->context = NULL;
  }
243
244
245
}

static void
Wim Taymans's avatar
Wim Taymans committed
246
gst_pulseringbuffer_finalize (GObject * object)
247
{
Wim Taymans's avatar
Wim Taymans committed
248
  GstPulseRingBuffer *ringbuffer;
249

Wim Taymans's avatar
Wim Taymans committed
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
  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)
{
  if (!pbuf->context
      || !PA_CONTEXT_IS_GOOD (pa_context_get_state (pbuf->context))
      || !pbuf->stream
      || !PA_STREAM_IS_GOOD (pa_stream_get_state (pbuf->stream))) {
    const gchar *err_str = pbuf->context ?
        pa_strerror (pa_context_errno (pbuf->context)) : NULL;

    GST_ELEMENT_ERROR (psink, RESOURCE, FAILED, ("Disconnected: %s",
            err_str), (NULL));
    return TRUE;
  }
  return FALSE;
272
273
}

274
static void
Wim Taymans's avatar
Wim Taymans committed
275
gst_pulsering_context_state_cb (pa_context * c, void *userdata)
276
{
Wim Taymans's avatar
Wim Taymans committed
277
278
279
  GstPulseSink *psink;
  GstPulseRingBuffer *pbuf;
  pa_context_state_t state;
280

Wim Taymans's avatar
Wim Taymans committed
281
282
  pbuf = GST_PULSERING_BUFFER_CAST (userdata);
  psink = GST_PULSESINK_CAST (GST_OBJECT_PARENT (pbuf));
283

Wim Taymans's avatar
Wim Taymans committed
284
285
  state = pa_context_get_state (c);
  GST_LOG_OBJECT (psink, "got new context state %d", state);
286

Wim Taymans's avatar
Wim Taymans committed
287
288
289
290
291
292
293
294
295
296
297
298
299
300
  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;

    case PA_CONTEXT_UNCONNECTED:
    case PA_CONTEXT_CONNECTING:
    case PA_CONTEXT_AUTHORIZING:
    case PA_CONTEXT_SETTING_NAME:
      break;
  }
301
302
}

Wim Taymans's avatar
Wim Taymans committed
303
#if HAVE_PULSE_0_9_12
304
static void
Wim Taymans's avatar
Wim Taymans committed
305
306
gst_pulsering_context_subscribe_cb (pa_context * c,
    pa_subscription_event_type_t t, uint32_t idx, void *userdata)
307
{
Wim Taymans's avatar
Wim Taymans committed
308
309
  GstPulseSink *psink;
  GstPulseRingBuffer *pbuf;
310

Wim Taymans's avatar
Wim Taymans committed
311
312
  pbuf = GST_PULSERING_BUFFER_CAST (userdata);
  psink = GST_PULSESINK_CAST (GST_OBJECT_PARENT (pbuf));
313

Wim Taymans's avatar
Wim Taymans committed
314
315
  GST_LOG_OBJECT (psink, "type %d, idx %u", t, idx);

Wim Taymans's avatar
Wim Taymans committed
316
317
  if (t != (PA_SUBSCRIPTION_EVENT_SINK_INPUT | PA_SUBSCRIPTION_EVENT_CHANGE) &&
      t != (PA_SUBSCRIPTION_EVENT_SINK_INPUT | PA_SUBSCRIPTION_EVENT_NEW))
318
    goto done;
319

320
  if (!pbuf->stream)
321
    goto done;
322

Wim Taymans's avatar
Wim Taymans committed
323
  if (idx != pa_stream_get_index (pbuf->stream))
324
    goto done;
325

Wim Taymans's avatar
Wim Taymans committed
326
327
328
329
330
  /* 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. */

331
done:
Wim Taymans's avatar
Wim Taymans committed
332
333
  /* inform streaming thread to notify */
  g_atomic_int_compare_and_exchange (&psink->notify, 0, 1);
334
}
Wim Taymans's avatar
Wim Taymans committed
335
#endif
336

Wim Taymans's avatar
Wim Taymans committed
337
338
339
340
/* 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)
341
{
Wim Taymans's avatar
Wim Taymans committed
342
343
344
345
  GstPulseSink *psink;
  GstPulseRingBuffer *pbuf;
  gchar *name;
  pa_mainloop_api *api;
346

Wim Taymans's avatar
Wim Taymans committed
347
348
  psink = GST_PULSESINK_CAST (GST_OBJECT_PARENT (buf));
  pbuf = GST_PULSERING_BUFFER_CAST (buf);
349

Wim Taymans's avatar
Wim Taymans committed
350
351
  g_assert (!pbuf->context);
  g_assert (!pbuf->stream);
352

Wim Taymans's avatar
Wim Taymans committed
353
  name = gst_pulse_client_name ();
354

Wim Taymans's avatar
Wim Taymans committed
355
  pa_threaded_mainloop_lock (psink->mainloop);
356

Wim Taymans's avatar
Wim Taymans committed
357
358
359
360
361
  /* get the mainloop api and create a context */
  GST_LOG_OBJECT (psink, "new context with name %s", GST_STR_NULL (name));
  api = pa_threaded_mainloop_get_api (psink->mainloop);
  if (!(pbuf->context = pa_context_new (api, name)))
    goto create_failed;
362

Wim Taymans's avatar
Wim Taymans committed
363
364
365
366
  /* register some essential callbacks */
  pa_context_set_state_callback (pbuf->context,
      gst_pulsering_context_state_cb, pbuf);
#if HAVE_PULSE_0_9_12
367
  pa_context_set_subscribe_callback (pbuf->context,
Wim Taymans's avatar
Wim Taymans committed
368
369
      gst_pulsering_context_subscribe_cb, pbuf);
#endif
370

Wim Taymans's avatar
Wim Taymans committed
371
372
373
374
375
376
  /* 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 (pbuf->context, psink->server, PA_CONTEXT_NOAUTOSPAWN,
          NULL) < 0)
    goto connect_failed;
377

Wim Taymans's avatar
Wim Taymans committed
378
379
  for (;;) {
    pa_context_state_t state;
380

Wim Taymans's avatar
Wim Taymans committed
381
    state = pa_context_get_state (pbuf->context);
382

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

Wim Taymans's avatar
Wim Taymans committed
385
386
    if (!PA_CONTEXT_IS_GOOD (state))
      goto connect_failed;
387

Wim Taymans's avatar
Wim Taymans committed
388
389
    if (state == PA_CONTEXT_READY)
      break;
390

Wim Taymans's avatar
Wim Taymans committed
391
392
393
394
    /* Wait until the context is ready */
    GST_LOG_OBJECT (psink, "waiting..");
    pa_threaded_mainloop_wait (psink->mainloop);
  }
395

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

Wim Taymans's avatar
Wim Taymans committed
398
399
  pa_threaded_mainloop_unlock (psink->mainloop);
  g_free (name);
400

Wim Taymans's avatar
Wim Taymans committed
401
  return TRUE;
402

Wim Taymans's avatar
Wim Taymans committed
403
404
405
406
  /* ERRORS */
unlock_and_fail:
  {
    gst_pulsering_destroy_context (pbuf);
407

Wim Taymans's avatar
Wim Taymans committed
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
    pa_threaded_mainloop_unlock (psink->mainloop);
    g_free (name);
    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",
            pa_strerror (pa_context_errno (pbuf->context))), (NULL));
    goto unlock_and_fail;
423
424
425
  }
}

Wim Taymans's avatar
Wim Taymans committed
426
427
428
/* close the device */
static gboolean
gst_pulseringbuffer_close_device (GstRingBuffer * buf)
429
{
Wim Taymans's avatar
Wim Taymans committed
430
431
  GstPulseSink *psink;
  GstPulseRingBuffer *pbuf;
432

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

Wim Taymans's avatar
Wim Taymans committed
436
  GST_LOG_OBJECT (psink, "closing device");
437

Wim Taymans's avatar
Wim Taymans committed
438
439
440
  pa_threaded_mainloop_lock (psink->mainloop);
  gst_pulsering_destroy_context (pbuf);
  pa_threaded_mainloop_unlock (psink->mainloop);
441

Wim Taymans's avatar
Wim Taymans committed
442
  GST_LOG_OBJECT (psink, "closed device");
443

Wim Taymans's avatar
Wim Taymans committed
444
  return TRUE;
445
446
447
}

static void
Wim Taymans's avatar
Wim Taymans committed
448
gst_pulsering_stream_state_cb (pa_stream * s, void *userdata)
449
{
Wim Taymans's avatar
Wim Taymans committed
450
451
452
  GstPulseSink *psink;
  GstPulseRingBuffer *pbuf;
  pa_stream_state_t state;
453

Wim Taymans's avatar
Wim Taymans committed
454
455
  pbuf = GST_PULSERING_BUFFER_CAST (userdata);
  psink = GST_PULSESINK_CAST (GST_OBJECT_PARENT (pbuf));
456

Wim Taymans's avatar
Wim Taymans committed
457
458
  state = pa_stream_get_state (s);
  GST_LOG_OBJECT (psink, "got new stream state %d", state);
459

Wim Taymans's avatar
Wim Taymans committed
460
461
462
463
464
465
466
467
468
469
  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;
470
  }
Wim Taymans's avatar
Wim Taymans committed
471
}
472

Wim Taymans's avatar
Wim Taymans committed
473
474
475
476
static void
gst_pulsering_stream_request_cb (pa_stream * s, size_t length, void *userdata)
{
  GstPulseSink *psink;
477
  GstRingBuffer *rbuf;
Wim Taymans's avatar
Wim Taymans committed
478
  GstPulseRingBuffer *pbuf;
479

480
  rbuf = GST_RING_BUFFER_CAST (userdata);
Wim Taymans's avatar
Wim Taymans committed
481
482
  pbuf = GST_PULSERING_BUFFER_CAST (userdata);
  psink = GST_PULSESINK_CAST (GST_OBJECT_PARENT (pbuf));
483

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

Stefan Kost's avatar
Stefan Kost committed
486
487
  if (pbuf->in_commit && (length >= rbuf->spec.segsize)) {
    /* only signal when we are waiting in the commit thread
488
     * and got request for atleast a segment */
Wim Taymans's avatar
Wim Taymans committed
489
490
    pa_threaded_mainloop_signal (psink->mainloop, 0);
  }
491
492
}

493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
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");
}

Wim Taymans's avatar
Wim Taymans committed
518
519
520
521
/* 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)
522
{
Wim Taymans's avatar
Wim Taymans committed
523
524
525
526
527
  GstPulseSink *psink;
  GstPulseRingBuffer *pbuf;
  pa_buffer_attr buf_attr;
  const pa_buffer_attr *buf_attr_ptr;
  pa_channel_map channel_map;
528
  pa_operation *o = NULL;
Wim Taymans's avatar
Wim Taymans committed
529
530
531
  pa_cvolume v, *pv;
  pa_stream_flags_t flags;
  const gchar *name;
532
533
  GstAudioClock *clock;
  gint64 time_offset;
534

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

Wim Taymans's avatar
Wim Taymans committed
538
539
540
541
  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;
542

Wim Taymans's avatar
Wim Taymans committed
543
  pa_threaded_mainloop_lock (psink->mainloop);
544

Wim Taymans's avatar
Wim Taymans committed
545
546
547
  /* we need a context and a no stream */
  g_assert (pbuf->context);
  g_assert (!pbuf->stream);
548

Wim Taymans's avatar
Wim Taymans committed
549
550
551
552
553
  /* enable event notifications */
  GST_LOG_OBJECT (psink, "subscribing to context events");
  if (!(o = pa_context_subscribe (pbuf->context,
              PA_SUBSCRIPTION_MASK_SINK_INPUT, NULL, NULL)))
    goto subscribe_failed;
554

Wim Taymans's avatar
Wim Taymans committed
555
  pa_operation_unref (o);
556

Wim Taymans's avatar
Wim Taymans committed
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
  /* 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);
  if (!(pbuf->stream = pa_stream_new (pbuf->context,
              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);
577
578
579
580
  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);
Wim Taymans's avatar
Wim Taymans committed
581

582
583
  /* buffering requirements. When setting prebuf to 0, the stream will not pause
   * when we cause an underrun, which causes time to continue. */
Wim Taymans's avatar
Wim Taymans committed
584
585
586
  memset (&buf_attr, 0, sizeof (buf_attr));
  buf_attr.tlength = spec->segtotal * spec->segsize;
  buf_attr.maxlength = buf_attr.tlength * 2;
587
  buf_attr.prebuf = 0;
Wim Taymans's avatar
Wim Taymans committed
588
  buf_attr.minreq = spec->segsize;
589

Wim Taymans's avatar
Wim Taymans committed
590
591
592
593
594
595
596
597
598
599
600
601
602
603
  GST_INFO_OBJECT (psink, "tlength:   %d", buf_attr.tlength);
  GST_INFO_OBJECT (psink, "maxlength: %d", buf_attr.maxlength);
  GST_INFO_OBJECT (psink, "prebuf:    %d", buf_attr.prebuf);
  GST_INFO_OBJECT (psink, "minreq:    %d", buf_attr.minreq);

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

Wim Taymans's avatar
Wim Taymans committed
605
  /* construct the flags */
606
  flags = PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE |
Wim Taymans's avatar
Wim Taymans committed
607
608
609
610
#if HAVE_PULSE_0_9_11
      PA_STREAM_ADJUST_LATENCY |
#endif
      PA_STREAM_START_CORKED;
611

Wim Taymans's avatar
Wim Taymans committed
612
613
  /* we always start corked (see flags above) */
  pbuf->corked = TRUE;
614

Wim Taymans's avatar
Wim Taymans committed
615
616
617
618
619
620
  /* 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,
          &buf_attr, flags, pv, NULL) < 0)
    goto connect_failed;
621

622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
  /* 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);
  time_offset = clock->abidata.ABI.time_offset;

  GST_LOG_OBJECT (psink, "got time offset %" GST_TIME_FORMAT,
      GST_TIME_ARGS (time_offset));

  /* calculate the sample offset for 0 */
  if (time_offset > 0)
    pbuf->offset = gst_util_uint64_scale_int (time_offset,
        pbuf->sample_spec.rate, GST_SECOND);
  else
    pbuf->offset = -gst_util_uint64_scale_int (-time_offset,
        pbuf->sample_spec.rate, GST_SECOND);
  GST_LOG_OBJECT (psink, "sample offset %" G_GINT64_FORMAT, pbuf->offset);

639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
  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);
  }
655

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

Wim Taymans's avatar
Wim Taymans committed
658
659
  /* get the actual buffering properties now */
  buf_attr_ptr = pa_stream_get_buffer_attr (pbuf->stream);
660

Wim Taymans's avatar
Wim Taymans committed
661
662
663
664
  GST_INFO_OBJECT (psink, "tlength:   %d", buf_attr_ptr->tlength);
  GST_INFO_OBJECT (psink, "maxlength: %d", buf_attr_ptr->maxlength);
  GST_INFO_OBJECT (psink, "prebuf:    %d", buf_attr_ptr->prebuf);
  GST_INFO_OBJECT (psink, "minreq:    %d", buf_attr_ptr->minreq);
665

Wim Taymans's avatar
Wim Taymans committed
666
667
  spec->segsize = buf_attr.minreq;
  spec->segtotal = buf_attr.tlength / spec->segsize;
668

Wim Taymans's avatar
Wim Taymans committed
669
  pa_threaded_mainloop_unlock (psink->mainloop);
670

Wim Taymans's avatar
Wim Taymans committed
671
  return TRUE;
672

Wim Taymans's avatar
Wim Taymans committed
673
674
675
676
677
  /* ERRORS */
unlock_and_fail:
  {
    gst_pulsering_destroy_stream (pbuf);
    pa_threaded_mainloop_unlock (psink->mainloop);
678

Wim Taymans's avatar
Wim Taymans committed
679
    return FALSE;
680
  }
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
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",
            pa_strerror (pa_context_errno (pbuf->context))), (NULL));
    goto unlock_and_fail;
  }
stream_failed:
  {
    GST_ELEMENT_ERROR (psink, RESOURCE, FAILED,
        ("Failed to create stream: %s",
            pa_strerror (pa_context_errno (pbuf->context))), (NULL));
    goto unlock_and_fail;
  }
connect_failed:
  {
    GST_ELEMENT_ERROR (psink, RESOURCE, FAILED,
        ("Failed to connect stream: %s",
            pa_strerror (pa_context_errno (pbuf->context))), (NULL));
    goto unlock_and_fail;
  }
}
709

Wim Taymans's avatar
Wim Taymans committed
710
711
712
713
714
715
/* free the stream that we acquired before */
static gboolean
gst_pulseringbuffer_release (GstRingBuffer * buf)
{
  GstPulseSink *psink;
  GstPulseRingBuffer *pbuf;
716

Wim Taymans's avatar
Wim Taymans committed
717
718
  psink = GST_PULSESINK_CAST (GST_OBJECT_PARENT (buf));
  pbuf = GST_PULSERING_BUFFER_CAST (buf);
719

Wim Taymans's avatar
Wim Taymans committed
720
721
722
  pa_threaded_mainloop_lock (psink->mainloop);
  gst_pulsering_destroy_stream (pbuf);
  pa_threaded_mainloop_unlock (psink->mainloop);
723

Wim Taymans's avatar
Wim Taymans committed
724
725
  return TRUE;
}
726

727
728
729
730
731
732
733
734
735
736
737
738
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
739
740
741
/* update the corked state of a stream, must be called with the mainloop
 * lock */
static gboolean
742
743
gst_pulsering_set_corked (GstPulseRingBuffer * pbuf, gboolean corked,
    gboolean wait)
744
{
Wim Taymans's avatar
Wim Taymans committed
745
746
747
  pa_operation *o = NULL;
  GstPulseSink *psink;
  gboolean res = FALSE;
748

Wim Taymans's avatar
Wim Taymans committed
749
  psink = GST_PULSESINK_CAST (GST_OBJECT_PARENT (pbuf));
750

Wim Taymans's avatar
Wim Taymans committed
751
752
  GST_DEBUG_OBJECT (psink, "setting corked state to %d", corked);
  if (pbuf->corked != corked) {
753
754
    if (!(o = pa_stream_cork (pbuf->stream, corked,
                gst_pulsering_success_cb, pbuf)))
Wim Taymans's avatar
Wim Taymans committed
755
      goto cork_failed;
756

757
    while (wait && pa_operation_get_state (o) == PA_OPERATION_RUNNING) {
Wim Taymans's avatar
Wim Taymans committed
758
759
760
761
762
      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
763
764
  } else {
    GST_DEBUG_OBJECT (psink, "skipping, already in requested state");
Wim Taymans's avatar
Wim Taymans committed
765
766
  }
  res = TRUE;
767

Wim Taymans's avatar
Wim Taymans committed
768
769
770
cleanup:
  if (o)
    pa_operation_unref (o);
771

Wim Taymans's avatar
Wim Taymans committed
772
  return res;
773

Wim Taymans's avatar
Wim Taymans committed
774
775
776
777
778
779
780
781
782
783
784
785
  /* ERRORS */
server_dead:
  {
    GST_DEBUG_OBJECT (psink, "the server is dead");
    goto cleanup;
  }
cork_failed:
  {
    GST_ELEMENT_ERROR (psink, RESOURCE, FAILED,
        ("pa_stream_cork() failed: %s",
            pa_strerror (pa_context_errno (pbuf->context))), (NULL));
    goto cleanup;
786
787
788
  }
}

789
/* start/resume playback ASAP, we don't uncork here but in the commit method */
Wim Taymans's avatar
Wim Taymans committed
790
791
static gboolean
gst_pulseringbuffer_start (GstRingBuffer * buf)
792
{
Wim Taymans's avatar
Wim Taymans committed
793
794
  GstPulseSink *psink;
  GstPulseRingBuffer *pbuf;
795

Wim Taymans's avatar
Wim Taymans committed
796
797
  pbuf = GST_PULSERING_BUFFER_CAST (buf);
  psink = GST_PULSESINK_CAST (GST_OBJECT_PARENT (pbuf));
798

Wim Taymans's avatar
Wim Taymans committed
799
  pa_threaded_mainloop_lock (psink->mainloop);
800
  GST_DEBUG_OBJECT (psink, "starting");
Wim Taymans's avatar
Wim Taymans committed
801
802
  pbuf->paused = FALSE;
  pa_threaded_mainloop_unlock (psink->mainloop);
803

804
  return TRUE;
Wim Taymans's avatar
Wim Taymans committed
805
}
806

Wim Taymans's avatar
Wim Taymans committed
807
808
809
810
811
812
813
814
815
816
817
818
/* 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);
819
  GST_DEBUG_OBJECT (psink, "pausing and corking");
Wim Taymans's avatar
Wim Taymans committed
820
821
  /* make sure the commit method stops writing */
  pbuf->paused = TRUE;
822
  res = gst_pulsering_set_corked (pbuf, TRUE, FALSE);
Wim Taymans's avatar
Wim Taymans committed
823
824
825
826
  if (pbuf->in_commit) {
    /* we are waiting in a commit, signal */
    GST_DEBUG_OBJECT (psink, "signal commit");
    pa_threaded_mainloop_signal (psink->mainloop, 0);
827
  }
Wim Taymans's avatar
Wim Taymans committed
828
829
830
  pa_threaded_mainloop_unlock (psink->mainloop);

  return res;
831
832
}

Wim Taymans's avatar
Wim Taymans committed
833
834
835
/* stop playback, we flush everything. */
static gboolean
gst_pulseringbuffer_stop (GstRingBuffer * buf)
836
{
Wim Taymans's avatar
Wim Taymans committed
837
838
839
840
  GstPulseSink *psink;
  GstPulseRingBuffer *pbuf;
  gboolean res = FALSE;
  pa_operation *o = NULL;
841

Wim Taymans's avatar
Wim Taymans committed
842
843
  pbuf = GST_PULSERING_BUFFER_CAST (buf);
  psink = GST_PULSESINK_CAST (GST_OBJECT_PARENT (pbuf));
844

Wim Taymans's avatar
Wim Taymans committed
845
846
  pa_threaded_mainloop_lock (psink->mainloop);
  pbuf->paused = TRUE;
847
  res = gst_pulsering_set_corked (pbuf, TRUE, TRUE);
Wim Taymans's avatar
Wim Taymans committed
848
849
850
851
852
  /* Inform anyone waiting in _commit() call that it shall wakeup */
  if (pbuf->in_commit) {
    GST_DEBUG_OBJECT (psink, "signal commit thread");
    pa_threaded_mainloop_signal (psink->mainloop, 0);
  }
853

Stefan Kost's avatar
Stefan Kost committed
854
855
856
857
858
859
860
861
862
863
864
  if (strcmp (psink->pa_version, "0.9.12")) {
    /* then try to flush, it's not fatal when this fails */
    GST_DEBUG_OBJECT (psink, "flushing");
    if ((o = pa_stream_flush (pbuf->stream, gst_pulsering_success_cb, pbuf))) {
      while (pa_operation_get_state (o) == PA_OPERATION_RUNNING) {
        GST_DEBUG_OBJECT (psink, "wait for completion");
        pa_threaded_mainloop_wait (psink->mainloop);
        if (gst_pulsering_is_dead (psink, pbuf))
          goto server_dead;
      }
      GST_DEBUG_OBJECT (psink, "flush completed");
Wim Taymans's avatar
Wim Taymans committed
865
866
867
    }
  }
  res = TRUE;
868

Wim Taymans's avatar
Wim Taymans committed
869
870
871
872
873
874
cleanup:
  if (o) {
    pa_operation_cancel (o);
    pa_operation_unref (o);
  }
  pa_threaded_mainloop_unlock (psink->mainloop);
875

Wim Taymans's avatar
Wim Taymans committed
876
  return res;
877

Wim Taymans's avatar
Wim Taymans committed
878
879
880
881
882
  /* ERRORS */
server_dead:
  {
    GST_DEBUG_OBJECT (psink, "the server is dead");
    goto cleanup;
883
884
885
  }
}

Wim Taymans's avatar
Wim Taymans committed
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
/* in_samples >= out_samples, rate > 1.0 */
#define FWD_UP_SAMPLES(s,se,d,de)               \
G_STMT_START {                                  \
  guint8 *sb = s, *db = d;                      \
  while (s <= se && d < de) {                   \
    memcpy (d, s, bps);                         \
    s += bps;                                   \
    *accum += outr;                             \
    if ((*accum << 1) >= inr) {                 \
      *accum -= inr;                            \
      d += bps;                                 \
    }                                           \
  }                                             \
  in_samples -= (s - sb)/bps;                   \
  out_samples -= (d - db)/bps;                  \
  GST_DEBUG ("fwd_up end %d/%d",*accum,*toprocess);     \
} G_STMT_END

/* out_samples > in_samples, for rates smaller than 1.0 */
#define FWD_DOWN_SAMPLES(s,se,d,de)             \
G_STMT_START {                                  \
  guint8 *sb = s, *db = d;                      \
  while (s <= se && d < de) {                   \
    memcpy (d, s, bps);                         \
    d += bps;                                   \
    *accum += inr;                              \
    if ((*accum << 1) >= outr) {                \
      *accum -= outr;                           \
      s += bps;                                 \
    }                                           \
  }                                             \
  in_samples -= (s - sb)/bps;                   \
  out_samples -= (d - db)/bps;                  \
  GST_DEBUG ("fwd_down end %d/%d",*accum,*toprocess);   \
} G_STMT_END

#define REV_UP_SAMPLES(s,se,d,de)               \
G_STMT_START {                                  \
  guint8 *sb = se, *db = d;                     \
  while (s <= se && d < de) {                   \
    memcpy (d, se, bps);                        \
    se -= bps;                                  \
    *accum += outr;                             \
    while ((*accum << 1) >= inr) {              \
      *accum -= inr;                            \
      d += bps;                                 \
    }                                           \
  }                                             \
  in_samples -= (sb - se)/bps;                  \
  out_samples -= (d - db)/bps;                  \
  GST_DEBUG ("rev_up end %d/%d",*accum,*toprocess);     \
} G_STMT_END

#define REV_DOWN_SAMPLES(s,se,d,de)             \
G_STMT_START {                                  \
  guint8 *sb = se, *db = d;                     \
  while (s <= se && d < de) {                   \
    memcpy (d, se, bps);                        \
    d += bps;                                   \
    *accum += inr;                              \
    while ((*accum << 1) >= outr) {             \
      *accum -= outr;                           \
      se -= bps;                                \
    }                                           \
  }                                             \
  in_samples -= (sb - se)/bps;                  \
  out_samples -= (d - db)/bps;                  \
  GST_DEBUG ("rev_down end %d/%d",*accum,*toprocess);   \
} G_STMT_END


/* our custom commit function because we write into the buffer of pulseaudio
 * instead of keeping our own buffer */
static guint
gst_pulseringbuffer_commit (GstRingBuffer * buf, guint64 * sample,
    guchar * data, gint in_samples, gint out_samples, gint * accum)
962
{
Wim Taymans's avatar
Wim Taymans committed
963
964
965
966
967
968
  GstPulseSink *psink;
  GstPulseRingBuffer *pbuf;
  guint result;
  guint8 *data_end;
  gboolean reverse;
  gint *toprocess;
969
  gint inr, outr, bps;
970
971
  gint64 offset;
  guint bufsize;
Wim Taymans's avatar
Wim Taymans committed
972
973
974

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

Wim Taymans's avatar
Wim Taymans committed
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
  /* FIXME post message rather than using a signal (as mixer interface) */
  if (g_atomic_int_compare_and_exchange (&psink->notify, 1, 0))
    g_object_notify (G_OBJECT (psink), "volume");

  /* make sure the ringbuffer is started */
  if (G_UNLIKELY (g_atomic_int_get (&buf->state) !=
          GST_RING_BUFFER_STATE_STARTED)) {
    /* see if we are allowed to start it */
    if (G_UNLIKELY (g_atomic_int_get (&buf->abidata.ABI.may_start) == FALSE))
      goto no_start;

    GST_DEBUG_OBJECT (buf, "start!");
    if (!gst_ring_buffer_start (buf))
      goto start_failed;
  }
991

Wim Taymans's avatar
Wim Taymans committed
992
993
994
  pa_threaded_mainloop_lock (psink->mainloop);
  GST_DEBUG_OBJECT (psink, "entering commit");
  pbuf->in_commit = TRUE;
995

Wim Taymans's avatar
Wim Taymans committed
996
  bps = buf->spec.bytes_per_sample;
997
  bufsize = buf->spec.segsize * buf->spec.segtotal;
998

Wim Taymans's avatar
Wim Taymans committed
999
1000
1001
  /* our toy resampler for trick modes */
  reverse = out_samples < 0;
  out_samples = ABS (out_samples);
1002

Wim Taymans's avatar
Wim Taymans committed
1003
1004
1005
1006
  if (in_samples >= out_samples)
    toprocess = &in_samples;
  else
    toprocess = &out_samples;
1007

Wim Taymans's avatar
Wim Taymans committed
1008
1009
  inr = in_samples - 1;
  outr = out_samples - 1;
1010

Wim Taymans's avatar
Wim Taymans committed
1011
1012
1013
  /* data_end points to the last sample we have to write, not past it. This is
   * needed to properly handle reverse playback: it points to the last sample. */
  data_end = data + (bps * inr);
1014

Wim Taymans's avatar
Wim Taymans committed
1015
1016
  if (pbuf->paused)
    goto was_paused;
1017

1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
  /* correct for sample offset against the internal clock */
  offset = *sample;
  if (pbuf->offset >= 0) {
    if (offset > pbuf->offset)
      offset -= pbuf->offset;
    else
      offset = 0;
  } else {
    if (offset > -pbuf->offset)
      offset += pbuf->offset;
    else
      offset = 0;
  }
1031
1032
  /* offset is in bytes */
  offset *= bps;
1033

Wim Taymans's avatar
Wim Taymans committed
1034
1035
1036
  while (*toprocess > 0) {
    size_t avail;
    guint towrite;
1037

1038
1039
1040
1041
    GST_LOG_OBJECT (psink,
        "need to write %d samples at offset %" G_GINT64_FORMAT, *toprocess,
        offset);

Wim Taymans's avatar
Wim Taymans committed
1042
    for (;;) {
1043
      /* FIXME, this is not quite right */
Wim Taymans's avatar
Wim Taymans committed
1044
1045
1046
      if ((avail = pa_stream_writable_size (pbuf->stream)) == (size_t) - 1)
        goto writable_size_failed;

1047
1048
1049
      /* We always try to satisfy a request for data */
      GST_LOG_OBJECT (psink, "writable bytes %" G_GSIZE_FORMAT, avail);

Wim Taymans's avatar
Wim Taymans committed
1050
1051
1052
      /* convert to samples, we can only deal with multiples of the
       * sample size */
      avail /= bps;
1053

Wim Taymans's avatar
Wim Taymans committed
1054
1055
1056
      if (avail > 0)
        break;

1057
1058
1059
1060
1061
1062
      /* see if we need to uncork because we have no free space */
      if (pbuf->corked) {
        if (!gst_pulsering_set_corked (pbuf, FALSE, FALSE))
          goto uncork_failed;
      }

Wim Taymans's avatar
Wim Taymans committed
1063
1064
1065
      /* we can't write a single byte, wait a bit */
      GST_LOG_OBJECT (psink, "waiting for free space");
      pa_threaded_mainloop_wait (psink->mainloop);
1066

Wim Taymans's avatar
Wim Taymans committed
1067
1068
      if (pbuf->paused)
        goto was_paused;
1069
1070
    }

Wim Taymans's avatar
Wim Taymans committed
1071
1072
1073
1074
1075
    if (avail > out_samples)
      avail = out_samples;

    towrite = avail * bps;

1076
1077
1078
    GST_LOG_OBJECT (psink, "writing %d samples at offset %" G_GUINT64_FORMAT,
        avail, offset);

Wim Taymans's avatar
Wim Taymans committed
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
    if (G_LIKELY (inr == outr && !reverse)) {
      /* no rate conversion, simply write out the samples */
      if (pa_stream_write (pbuf->stream, data, towrite, NULL, offset,
              PA_SEEK_ABSOLUTE) < 0)
        goto write_failed;

      data += towrite;
      in_samples -= avail;
      out_samples -= avail;
    } else {
      guint8 *dest, *d, *d_end;

      /* we need to allocate a temporary buffer to resample the data into,
       * FIXME, we should have a pulseaudio API to allocate this buffer for us
       * from the shared memory. */
      dest = d = g_malloc (towrite);
      d_end = d + towrite;

      if (!reverse) {
        if (inr >= outr)
          /* forward speed up */
          FWD_UP_SAMPLES (data, data_end, d, d_end);
        else
          /* forward slow down */
          FWD_DOWN_SAMPLES (data, data_end, d, d_end);
      } else {
        if (inr >= outr)
          /* reverse speed up */
          REV_UP_SAMPLES (data, data_end, d, d_end);
        else
          /* reverse slow down */
          REV_DOWN_SAMPLES (data, data_end, d, d_end);
      }
      /* see what we have left to write */
      towrite = (d - dest);
      if (pa_stream_write (pbuf->stream, dest, towrite,
              g_free, offset, PA_SEEK_ABSOLUTE) < 0)
        goto write_failed;
1117

Wim Taymans's avatar
Wim Taymans committed
1118
1119
1120
      avail = towrite / bps;
    }
    *sample += avail;
1121
    offset += avail * bps;
1122
1123
1124

    /* check if we need to uncork after writing the samples */
    if (pbuf->corked) {
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
      const pa_timing_info *info;

      if ((info = pa_stream_get_timing_info (pbuf->stream))) {
        GST_LOG_OBJECT (psink,
            "read_index at %" G_GUINT64_FORMAT ", offset %" G_GINT64_FORMAT,
            info->read_index, offset);

        /* we uncork when the read_index is too far behind the offset we need
         * to write to. */
        if (info->read_index + bufsize <= offset) {
          if (!gst_pulsering_set_corked (pbuf, FALSE, FALSE))
            goto uncork_failed;
        }
      } else {
        GST_LOG_OBJECT (psink, "no timing info available yet");
1140
1141
      }
    }
1142
  }
Wim Taymans's avatar
Wim Taymans committed
1143
1144
  /* we consumed all samples here */
  data = data_end + bps;
1145

Wim Taymans's avatar
Wim Taymans committed
1146
1147
  pbuf->in_commit = FALSE;
  pa_threaded_mainloop_unlock (psink->mainloop);
1148

Wim Taymans's avatar
Wim Taymans committed
1149
1150
1151
done:
  result = inr - ((data_end - data) / bps);
  GST_LOG_OBJECT (psink, "wrote %d samples", result);
1152

Wim Taymans's avatar
Wim Taymans committed
1153
  return result;
1154

Wim Taymans's avatar
Wim Taymans committed
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
  /* ERRORS */
unlock_and_fail:
  {
    pbuf->in_commit = FALSE;
    GST_LOG_OBJECT (psink, "we are reset");
    pa_threaded_mainloop_unlock (psink->mainloop);
    goto done;
  }
no_start:
  {
    GST_LOG_OBJECT (psink, "we can not start");
    return 0;
  }
start_failed:
  {
    GST_LOG_OBJECT (psink, "failed to start the ringbuffer");
    return 0;
  }
1173
1174
1175
1176
1177
1178
1179
uncork_failed:
  {
    pbuf->in_commit = FALSE;
    GST_ERROR_OBJECT (psink, "uncork failed");
    pa_threaded_mainloop_unlock (psink->mainloop);
    goto done;
  }
Wim Taymans's avatar
Wim Taymans committed
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
was_paused:
  {
    pbuf->in_commit = FALSE;
    GST_LOG_OBJECT (psink, "we are paused");
    pa_threaded_mainloop_unlock (psink->mainloop);
    goto done;
  }
writable_size_failed:
  {
    GST_ELEMENT_ERROR (psink, RESOURCE, FAILED,
        ("pa_stream_writable_size() failed: %s",
            pa_strerror (pa_context_errno (pbuf->context))), (NULL));
    goto unlock_and_fail;
  }
write_failed:
  {
    GST_ELEMENT_ERROR (psink, RESOURCE, FAILED,
        ("pa_stream_write() failed: %s",
            pa_strerror (pa_context_errno (pbuf->context))), (NULL));
    goto unlock_and_fail;
  }
1201
1202
}

Wim Taymans's avatar
Wim Taymans committed
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
static void gst_pulsesink_set_property (GObject * object, guint prop_id,
    const GValue * value, GParamSpec * pspec);
static void gst_pulsesink_get_property (GObject * object, guint prop_id,
    GValue * value, GParamSpec * pspec);
static void gst_pulsesink_finalize (GObject * object);

static gboolean gst_pulsesink_event (GstBaseSink * sink, GstEvent * event);

static void gst_pulsesink_init_interfaces (GType type);

#if (G_BYTE_ORDER == G_LITTLE_ENDIAN)
# define ENDIANNESS   "LITTLE_ENDIAN, BIG_ENDIAN"
#else
# define ENDIANNESS   "BIG_ENDIAN, LITTLE_ENDIAN"
#endif

GST_IMPLEMENT_PULSEPROBE_METHODS (GstPulseSink, gst_pulsesink);
GST_BOILERPLATE_FULL (GstPulseSink, gst_pulsesink, GstBaseAudioSink,
    GST_TYPE_BASE_AUDIO_SINK, gst_pulsesink_init_interfaces);

1223
static gboolean
Wim Taymans's avatar
Wim Taymans committed
1224
1225
gst_pulsesink_interface_supported (GstImplementsInterface *
    iface, GType interface_type)
1226
{
Wim Taymans's avatar
Wim Taymans committed
1227
  GstPulseSink *this = GST_PULSESINK_CAST (iface);
1228

Wim Taymans's avatar
Wim Taymans committed
1229
1230
  if (interface_type == GST_TYPE_PROPERTY_PROBE && this->probe)
    return TRUE;
1231

Wim Taymans's avatar
Wim Taymans committed
1232
  return FALSE;
1233
1234
}

Wim Taymans's avatar
Wim Taymans committed
1235
1236
static void
gst_pulsesink_implements_interface_init (GstImplementsInterfaceClass * klass)
1237
{
Wim Taymans's avatar
Wim Taymans committed
1238
1239
  klass->supported = gst_pulsesink_interface_supported;
}
1240

Wim Taymans's avatar
Wim Taymans committed
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
static void
gst_pulsesink_init_interfaces (GType type)
{
  static const GInterfaceInfo implements_iface_info = {
    (GInterfaceInitFunc) gst_pulsesink_implements_interface_init,
    NULL,
    NULL,
  };
  static const GInterfaceInfo probe_iface_info = {
    (GInterfaceInitFunc) gst_pulsesink_property_probe_interface_init,
    NULL,
    NULL,
  };
1254

Wim Taymans's avatar
Wim Taymans committed
1255
1256
1257
1258
1259
  g_type_add_interface_static (type, GST_TYPE_IMPLEMENTS_INTERFACE,
      &implements_iface_info);
  g_type_add_interface_static (type, GST_TYPE_PROPERTY_PROBE,
      &probe_iface_info);
}
1260

Wim Taymans's avatar
Wim Taymans committed
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279 <