gstplaysink.c 178 KB
Newer Older
Wim Taymans's avatar
Wim Taymans committed
1
2
/* GStreamer
 * Copyright (C) <2007> Wim Taymans <wim.taymans@gmail.com>
3
 * Copyright (C) <2011> Sebastian Dröge <sebastian.droege@collabora.co.uk>
Wim Taymans's avatar
Wim Taymans committed
4
5
6
7
8
9
10
11
12
13
14
15
16
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
Tim-Philipp Müller's avatar
Tim-Philipp Müller committed
17
18
 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
Wim Taymans's avatar
Wim Taymans committed
19
20
21
22
23
24
25
26
27
28
29
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <string.h>
#include <gst/gst.h>

#include <gst/gst-i18n-plugin.h>
#include <gst/pbutils/pbutils.h>
30
#include <gst/video/video.h>
31
32
33
#include <gst/audio/streamvolume.h>
#include <gst/video/colorbalance.h>
#include <gst/video/videooverlay.h>
34
#include <gst/video/navigation.h>
Wim Taymans's avatar
Wim Taymans committed
35
36

#include "gstplaysink.h"
37
#include "gststreamsynchronizer.h"
38
39
#include "gstplaysinkvideoconvert.h"
#include "gstplaysinkaudioconvert.h"
Wim Taymans's avatar
Wim Taymans committed
40
41
42
43
44
45

GST_DEBUG_CATEGORY_STATIC (gst_play_sink_debug);
#define GST_CAT_DEFAULT gst_play_sink_debug

#define VOLUME_MAX_DOUBLE 10.0

46
#define DEFAULT_FLAGS             GST_PLAY_FLAG_AUDIO | GST_PLAY_FLAG_VIDEO | GST_PLAY_FLAG_TEXT | \
47
                                  GST_PLAY_FLAG_SOFT_VOLUME | GST_PLAY_FLAG_SOFT_COLORBALANCE
48

49
#define GST_PLAY_CHAIN(c) ((GstPlayChain *)(c))
50

51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
/* enum types */
/**
 * GstPlaySinkSendEventMode:
 * @MODE_DEFAULT: default GstBin's send_event handling
 * @MODE_FIRST: send event only to the first sink that return true
 *
 * Send event handling to use
 */
typedef enum
{
  MODE_DEFAULT = 0,
  MODE_FIRST = 1
} GstPlaySinkSendEventMode;


#define GST_TYPE_PLAY_SINK_SEND_EVENT_MODE (gst_play_sink_send_event_mode_get_type ())
static GType
gst_play_sink_send_event_mode_get_type (void)
{
  static GType gtype = 0;

  if (gtype == 0) {
    static const GEnumValue values[] = {
      {MODE_DEFAULT, "Default GstBin's send_event handling (default)",
          "default"},
      {MODE_FIRST, "Sends the event to sinks until the first one handles it",
          "first"},
      {0, NULL, NULL}
    };

    gtype = g_enum_register_static ("GstPlaySinkSendEventMode", values);
  }
  return gtype;
}

Wim Taymans's avatar
Wim Taymans committed
86
87
88
89
90
91
92
93
/* holds the common data fields for the audio and video pipelines. We keep them
 * in a structure to more easily have all the info available. */
typedef struct
{
  GstPlaySink *playsink;
  GstElement *bin;
  gboolean added;
  gboolean activated;
94
  gboolean raw;
Wim Taymans's avatar
Wim Taymans committed
95
96
97
98
99
} GstPlayChain;

typedef struct
{
  GstPlayChain chain;
100
  GstPad *sinkpad;
101
  GstElement *queue;
102
  GstElement *filter_conv;
103
  GstElement *filter;
Wim Taymans's avatar
Wim Taymans committed
104
  GstElement *conv;
105
  GstElement *volume;           /* element with the volume property */
106
  gboolean sink_volume;         /* if the volume was provided by the sink */
107
108
  gulong notify_volume_id;
  gulong notify_mute_id;
Wim Taymans's avatar
Wim Taymans committed
109
  GstElement *sink;
Wim Taymans's avatar
Wim Taymans committed
110
  GstElement *ts_offset;
Wim Taymans's avatar
Wim Taymans committed
111
112
} GstPlayAudioChain;

113
114
115
116
117
118
119
120
typedef struct
{
  GstPlayChain chain;
  GstPad *sinkpad, *srcpad;
  GstElement *conv;
  GstElement *deinterlace;
} GstPlayVideoDeinterlaceChain;

Wim Taymans's avatar
Wim Taymans committed
121
122
123
typedef struct
{
  GstPlayChain chain;
124
  GstPad *sinkpad;
Wim Taymans's avatar
Wim Taymans committed
125
  GstElement *queue;
126
  GstElement *filter_conv;
127
  GstElement *filter;
128
  GstElement *conv;
Wim Taymans's avatar
Wim Taymans committed
129
  GstElement *sink;
130
  gboolean async;
Wim Taymans's avatar
Wim Taymans committed
131
  GstElement *ts_offset;
Wim Taymans's avatar
Wim Taymans committed
132
133
} GstPlayVideoChain;

134
135
136
typedef struct
{
  GstPlayChain chain;
137
  GstPad *sinkpad;
138
139
140
  GstElement *queue;
  GstElement *conv;
  GstElement *resample;
141
142
  GstPad *blockpad;             /* srcpad of queue, used for blocking the vis */
  GstPad *vispeerpad;           /* srcpad of resample, used for unlinking the vis */
143
  GstPad *vissinkpad;           /* visualisation sinkpad, */
144
  GstElement *vis;
145
146
147
  GstPad *vissrcpad;            /* visualisation srcpad, */
  GstPad *srcpad;               /* outgoing srcpad, used to connect to the next
                                 * chain */
148
149
} GstPlayVisChain;

150
151
152
153
typedef struct
{
  GstPlayChain chain;
  GstPad *sinkpad;
154
155
  GstElement *queue;
  GstElement *identity;
156
157
158
159
160
  GstElement *overlay;
  GstPad *videosinkpad;
  GstPad *textsinkpad;
  GstPad *srcpad;               /* outgoing srcpad, used to connect to the next
                                 * chain */
161
  GstElement *sink;             /* custom sink to receive subtitle buffers */
162
163
} GstPlayTextChain;

164
#define GST_PLAY_SINK_GET_LOCK(playsink) (&((GstPlaySink *)playsink)->lock)
165
166
#define GST_PLAY_SINK_LOCK(playsink)     G_STMT_START { \
  GST_LOG_OBJECT (playsink, "locking from thread %p", g_thread_self ()); \
Wim Taymans's avatar
Wim Taymans committed
167
  g_rec_mutex_lock (GST_PLAY_SINK_GET_LOCK (playsink)); \
168
169
170
171
  GST_LOG_OBJECT (playsink, "locked from thread %p", g_thread_self ()); \
} G_STMT_END
#define GST_PLAY_SINK_UNLOCK(playsink)   G_STMT_START { \
  GST_LOG_OBJECT (playsink, "unlocking from thread %p", g_thread_self ()); \
Wim Taymans's avatar
Wim Taymans committed
172
  g_rec_mutex_unlock (GST_PLAY_SINK_GET_LOCK (playsink)); \
173
} G_STMT_END
174

175
176
177
178
179
180
181
182
183
184
185
186
187
#define PENDING_FLAG_SET(playsink, flagtype) \
  ((playsink->pending_blocked_pads) |= (1 << flagtype))
#define PENDING_FLAG_UNSET(playsink, flagtype) \
  ((playsink->pending_blocked_pads) &= ~(1 << flagtype))
#define PENDING_FLAG_IS_SET(playsink, flagtype) \
  ((playsink->pending_blocked_pads) & (1 << flagtype))
#define PENDING_VIDEO_BLOCK(playsink) \
  ((playsink->pending_blocked_pads) & (1 << GST_PLAY_SINK_TYPE_VIDEO_RAW | 1 << GST_PLAY_SINK_TYPE_VIDEO))
#define PENDING_AUDIO_BLOCK(playsink) \
  ((playsink->pending_blocked_pads) & (1 << GST_PLAY_SINK_TYPE_AUDIO_RAW | 1 << GST_PLAY_SINK_TYPE_AUDIO))
#define PENDING_TEXT_BLOCK(playsink) \
  PENDING_FLAG_IS_SET(playsink, GST_PLAY_SINK_TYPE_TEXT)

Wim Taymans's avatar
Wim Taymans committed
188
189
190
191
struct _GstPlaySink
{
  GstBin bin;

Wim Taymans's avatar
Wim Taymans committed
192
  GRecMutex lock;
193

194
  gboolean async_pending;
Jan Schmidt's avatar
Jan Schmidt committed
195
  gboolean need_async_start;
196

197
  GstPlayFlags flags;
Wim Taymans's avatar
Wim Taymans committed
198

199
200
  GstStreamSynchronizer *stream_synchronizer;

201
  /* chains */
202
  GstPlayAudioChain *audiochain;
203
  GstPlayVideoDeinterlaceChain *videodeinterlacechain;
204
205
206
  GstPlayVideoChain *videochain;
  GstPlayVisChain *vischain;
  GstPlayTextChain *textchain;
Wim Taymans's avatar
Wim Taymans committed
207

208
  /* audio */
Wim Taymans's avatar
Wim Taymans committed
209
  GstPad *audio_pad;
210
  gboolean audio_pad_raw;
211
  gboolean audio_pad_blocked;
212
213
  GstPad *audio_srcpad_stream_synchronizer;
  GstPad *audio_sinkpad_stream_synchronizer;
214
215
  GstElement *audio_ssync_queue;
  GstPad *audio_ssync_queue_sinkpad;
216
  gulong audio_block_id;
217
  gulong audio_notify_caps_id;
218
  /* audio tee */
219
220
221
222
  GstElement *audio_tee;
  GstPad *audio_tee_sink;
  GstPad *audio_tee_asrc;
  GstPad *audio_tee_vissrc;
223
  /* video */
Wim Taymans's avatar
Wim Taymans committed
224
  GstPad *video_pad;
225
  gboolean video_pad_raw;
226
  gboolean video_pad_blocked;
227
228
  GstPad *video_srcpad_stream_synchronizer;
  GstPad *video_sinkpad_stream_synchronizer;
229
  gulong video_block_id;
230
  gulong video_notify_caps_id;
231
  /* text */
Wim Taymans's avatar
Wim Taymans committed
232
  GstPad *text_pad;
233
  gboolean text_pad_blocked;
234
235
  GstPad *text_srcpad_stream_synchronizer;
  GstPad *text_sinkpad_stream_synchronizer;
236
  gulong text_block_id;
237

238
239
  gulong vis_pad_block_id;

240
241
  guint32 pending_blocked_pads;

Wim Taymans's avatar
Wim Taymans committed
242
243
244
  /* properties */
  GstElement *audio_sink;
  GstElement *video_sink;
245
246
  GstElement *audio_filter;
  GstElement *video_filter;
Wim Taymans's avatar
Wim Taymans committed
247
  GstElement *visualisation;
248
  GstElement *text_sink;
249
  gdouble volume;
250
  gboolean mute;
Wim Taymans's avatar
Wim Taymans committed
251
  gchar *font_desc;             /* font description */
252
  gchar *subtitle_encoding;     /* subtitle encoding */
Wim Taymans's avatar
Wim Taymans committed
253
  guint connection_speed;       /* connection speed in bits/sec (0 = unknown) */
254
  guint count;
255
  gboolean volume_changed;      /* volume/mute changed while no audiochain */
256
  gboolean mute_changed;        /* ... has been created yet */
Wim Taymans's avatar
Wim Taymans committed
257
  gint64 av_offset;
258
  GstPlaySinkSendEventMode send_event_mode;
259
  gboolean force_aspect_ratio;
260

261
262
263
264
265
266
267
268
  /* videooverlay proxy interface */
  GstVideoOverlay *overlay_element;     /* protected with LOCK */
  gboolean overlay_handle_set;
  guintptr overlay_handle;
  gboolean overlay_render_rectangle_set;
  gint overlay_x, overlay_y, overlay_width, overlay_height;
  gboolean overlay_handle_events_set;
  gboolean overlay_handle_events;
269
270
271
272
273

  /* colorbalance proxy interface */
  GstColorBalance *colorbalance_element;
  GList *colorbalance_channels; /* CONTRAST, BRIGHTNESS, HUE, SATURATION */
  gint colorbalance_values[4];
274
  gulong colorbalance_value_changed_id;
275

276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
  /* sending audio/video flushes break stream changes when the pipeline
   * is paused and played again in 0.10 */
#if 0
  gboolean video_custom_flush_finished;
  gboolean video_ignore_wrong_state;
  gboolean video_pending_flush;

  gboolean audio_custom_flush_finished;
  gboolean audio_ignore_wrong_state;
  gboolean audio_pending_flush;
#endif

  gboolean text_custom_flush_finished;
  gboolean text_ignore_wrong_state;
  gboolean text_pending_flush;
Wim Taymans's avatar
Wim Taymans committed
291
292
293
294
295
};

struct _GstPlaySinkClass
{
  GstBinClass parent_class;
296
297

    gboolean (*reconfigure) (GstPlaySink * playsink);
298

299
  GstSample *(*convert_sample) (GstPlaySink * playsink, GstCaps * caps);
Wim Taymans's avatar
Wim Taymans committed
300
301
};

302
303
304

static GstStaticPadTemplate audiotemplate =
GST_STATIC_PAD_TEMPLATE ("audio_sink",
305
306
307
    GST_PAD_SINK,
    GST_PAD_REQUEST,
    GST_STATIC_CAPS_ANY);
308
309
static GstStaticPadTemplate videotemplate =
GST_STATIC_PAD_TEMPLATE ("video_sink",
310
311
312
    GST_PAD_SINK,
    GST_PAD_REQUEST,
    GST_STATIC_CAPS_ANY);
313
static GstStaticPadTemplate texttemplate = GST_STATIC_PAD_TEMPLATE ("text_sink",
314
315
316
    GST_PAD_SINK,
    GST_PAD_REQUEST,
    GST_STATIC_CAPS_ANY);
317
318
319
320

/* FIXME 0.11: Remove */
static GstStaticPadTemplate audiorawtemplate =
GST_STATIC_PAD_TEMPLATE ("audio_raw_sink",
321
322
323
    GST_PAD_SINK,
    GST_PAD_REQUEST,
    GST_STATIC_CAPS_ANY);
324
325
static GstStaticPadTemplate videorawtemplate =
GST_STATIC_PAD_TEMPLATE ("video_raw_sink",
326
327
328
    GST_PAD_SINK,
    GST_PAD_REQUEST,
    GST_STATIC_CAPS_ANY);
Wim Taymans's avatar
Wim Taymans committed
329

330

Wim Taymans's avatar
Wim Taymans committed
331
332
333
334
/* props */
enum
{
  PROP_0,
335
  PROP_FLAGS,
336
337
338
  PROP_MUTE,
  PROP_VOLUME,
  PROP_FONT_DESC,
339
  PROP_SUBTITLE_ENCODING,
340
  PROP_VIS_PLUGIN,
341
  PROP_SAMPLE,
Wim Taymans's avatar
Wim Taymans committed
342
  PROP_AV_OFFSET,
343
  PROP_VIDEO_SINK,
344
345
  PROP_AUDIO_SINK,
  PROP_TEXT_SINK,
346
  PROP_SEND_EVENT_MODE,
347
  PROP_FORCE_ASPECT_RATIO,
348
  PROP_VIDEO_FILTER,
349
  PROP_AUDIO_FILTER
Wim Taymans's avatar
Wim Taymans committed
350
351
352
353
354
355
356
357
358
};

/* signals */
enum
{
  LAST_SIGNAL
};

static void gst_play_sink_dispose (GObject * object);
359
static void gst_play_sink_finalize (GObject * object);
360
361
362
363
static void gst_play_sink_set_property (GObject * object, guint prop_id,
    const GValue * value, GParamSpec * spec);
static void gst_play_sink_get_property (GObject * object, guint prop_id,
    GValue * value, GParamSpec * spec);
Wim Taymans's avatar
Wim Taymans committed
364

365
static GstPad *gst_play_sink_request_new_pad (GstElement * element,
366
    GstPadTemplate * templ, const gchar * name, const GstCaps * caps);
367
368
static void gst_play_sink_release_request_pad (GstElement * element,
    GstPad * pad);
Wim Taymans's avatar
Wim Taymans committed
369
370
371
372
373
static gboolean gst_play_sink_send_event (GstElement * element,
    GstEvent * event);
static GstStateChangeReturn gst_play_sink_change_state (GstElement * element,
    GstStateChange transition);

374
375
static void gst_play_sink_handle_message (GstBin * bin, GstMessage * message);

376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
/* sending audio/video flushes break stream changes when the pipeline
 * is paused and played again in 0.10 */
#if 0
static gboolean gst_play_sink_video_sink_event (GstPad * pad, GstEvent * event);
static GstFlowReturn gst_play_sink_video_sink_chain (GstPad * pad,
    GstBuffer * buffer);
static gboolean gst_play_sink_audio_sink_event (GstPad * pad, GstEvent * event);
static GstFlowReturn gst_play_sink_audio_sink_chain (GstPad * pad,
    GstBuffer * buffer);
#endif
static gboolean gst_play_sink_text_sink_event (GstPad * pad, GstObject * parent,
    GstEvent * event);
static GstFlowReturn gst_play_sink_text_sink_chain (GstPad * pad,
    GstObject * parent, GstBuffer * buffer);

391
392
393
394
395
static void notify_volume_cb (GObject * object, GParamSpec * pspec,
    GstPlaySink * playsink);
static void notify_mute_cb (GObject * object, GParamSpec * pspec,
    GstPlaySink * playsink);

Wim Taymans's avatar
Wim Taymans committed
396
397
static void update_av_offset (GstPlaySink * playsink);

398
399
static gboolean gst_play_sink_do_reconfigure (GstPlaySink * playsink);

400
401
static GQuark _playsink_reset_segment_event_marker_id = 0;

Wim Taymans's avatar
Wim Taymans committed
402
403
/* static guint gst_play_sink_signals[LAST_SIGNAL] = { 0 }; */

404
static void gst_play_sink_overlay_init (gpointer g_iface,
405
    gpointer g_iface_data);
406
407
static void gst_play_sink_navigation_init (gpointer g_iface,
    gpointer g_iface_data);
408
409
410
static void gst_play_sink_colorbalance_init (gpointer g_iface,
    gpointer g_iface_data);

411
412
413
414
415
416
static void
_do_init (GType type)
{
  static const GInterfaceInfo svol_info = {
    NULL, NULL, NULL
  };
417
418
  static const GInterfaceInfo ov_info = {
    gst_play_sink_overlay_init,
419
420
    NULL, NULL
  };
421
422
423
424
  static const GInterfaceInfo nav_info = {
    gst_play_sink_navigation_init,
    NULL, NULL
  };
425
426
427
428
  static const GInterfaceInfo col_info = {
    gst_play_sink_colorbalance_init,
    NULL, NULL
  };
429
430

  g_type_add_interface_static (type, GST_TYPE_STREAM_VOLUME, &svol_info);
431
  g_type_add_interface_static (type, GST_TYPE_VIDEO_OVERLAY, &ov_info);
432
  g_type_add_interface_static (type, GST_TYPE_NAVIGATION, &nav_info);
433
  g_type_add_interface_static (type, GST_TYPE_COLOR_BALANCE, &col_info);
434
435
436
437
}

G_DEFINE_TYPE_WITH_CODE (GstPlaySink, gst_play_sink, GST_TYPE_BIN,
    _do_init (g_define_type_id));
Wim Taymans's avatar
Wim Taymans committed
438
439
440
441
442
443
444
445
446
447
448
449

static void
gst_play_sink_class_init (GstPlaySinkClass * klass)
{
  GObjectClass *gobject_klass;
  GstElementClass *gstelement_klass;
  GstBinClass *gstbin_klass;

  gobject_klass = (GObjectClass *) klass;
  gstelement_klass = (GstElementClass *) klass;
  gstbin_klass = (GstBinClass *) klass;

450
451
452
453
  gobject_klass->dispose = gst_play_sink_dispose;
  gobject_klass->finalize = gst_play_sink_finalize;
  gobject_klass->set_property = gst_play_sink_set_property;
  gobject_klass->get_property = gst_play_sink_get_property;
454
455
456
457
458
459
460
461
462
463
464


  /**
   * GstPlaySink:flags
   *
   * Control the behaviour of playsink.
   */
  g_object_class_install_property (gobject_klass, PROP_FLAGS,
      g_param_spec_flags ("flags", "Flags", "Flags to control behaviour",
          GST_TYPE_PLAY_FLAGS, DEFAULT_FLAGS,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
Wim Taymans's avatar
Wim Taymans committed
465

466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
  /**
   * GstPlaySink:volume:
   *
   * Get or set the current audio stream volume. 1.0 means 100%,
   * 0.0 means mute. This uses a linear volume scale.
   *
   */
  g_object_class_install_property (gobject_klass, PROP_VOLUME,
      g_param_spec_double ("volume", "Volume", "The audio volume, 1.0=100%",
          0.0, VOLUME_MAX_DOUBLE, 1.0,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
  g_object_class_install_property (gobject_klass, PROP_MUTE,
      g_param_spec_boolean ("mute", "Mute",
          "Mute the audio channel without changing the volume", FALSE,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
  g_object_class_install_property (gobject_klass, PROP_FONT_DESC,
      g_param_spec_string ("subtitle-font-desc",
          "Subtitle font description",
          "Pango font description of font "
          "to be used for subtitle rendering", NULL,
          G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
487
488
489
490
491
492
493
  g_object_class_install_property (gobject_klass, PROP_SUBTITLE_ENCODING,
      g_param_spec_string ("subtitle-encoding", "subtitle encoding",
          "Encoding to assume if input subtitles are not in UTF-8 encoding. "
          "If not set, the GST_SUBTITLE_ENCODING environment variable will "
          "be checked for an encoding to use. If that is not set either, "
          "ISO-8859-15 will be assumed.", NULL,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
494
495
496
497
  g_object_class_install_property (gobject_klass, PROP_VIS_PLUGIN,
      g_param_spec_object ("vis-plugin", "Vis plugin",
          "the visualization element to use (NULL = default)",
          GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
498
  /**
499
   * GstPlaySink:sample:
500
   *
501
502
   * Get the currently rendered or prerolled sample in the video sink.
   * The #GstCaps in the sample will describe the format of the buffer.
503
   */
504
505
506
507
  g_object_class_install_property (gobject_klass, PROP_SAMPLE,
      g_param_spec_boxed ("sample", "Sample",
          "The last sample (NULL = no video available)",
          GST_TYPE_SAMPLE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
Wim Taymans's avatar
Wim Taymans committed
508
509
510
511
512
513
514
515
516
517
518
519
  /**
   * GstPlaySink:av-offset:
   *
   * Control the synchronisation offset between the audio and video streams.
   * Positive values make the audio ahead of the video and negative values make
   * the audio go behind the video.
   */
  g_object_class_install_property (gobject_klass, PROP_AV_OFFSET,
      g_param_spec_int64 ("av-offset", "AV Offset",
          "The synchronisation offset between audio and video in nanoseconds",
          G_MININT64, G_MAXINT64, 0,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
520

521
522
523
524
525
526
527
  /**
   * GstPlaySink:video-filter:
   *
   * Set the video filter element/bin to use. Will apply on a best-effort basis
   * unless GST_PLAY_FLAG_FORCE_FILTERS is set. playsink must be in
   * %GST_STATE_NULL
   */
528
  g_object_class_install_property (gobject_klass, PROP_VIDEO_FILTER,
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
      g_param_spec_object ("video-filter", "Video filter",
          "the video filter(s) to apply, if possible",
          GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
  /**
   * GstPlaySink:audio-filter:
   *
   * Set the audio filter element/bin to use. Will apply on a best-effort basis
   * unless GST_PLAY_FLAG_FORCE_FILTERS is set. playsink must be in
   * %GST_STATE_NULL
   */
  g_object_class_install_property (gobject_klass, PROP_AUDIO_FILTER,
      g_param_spec_object ("audio-filter", "Audio filter",
          "the audio filter(s) to apply, if possible",
          GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

544
545
546
547
548
549
550
551
552
553
  /**
   * GstPlaySink:video-sink:
   *
   * Set the used video sink element. NULL will use the default sink. playsink
   * must be in %GST_STATE_NULL
   */
  g_object_class_install_property (gobject_klass, PROP_VIDEO_SINK,
      g_param_spec_object ("video-sink", "Video Sink",
          "the video output element to use (NULL = default sink)",
          GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
554
555
556
557
558
559
560
561
562
563
  /**
   * GstPlaySink:audio-sink:
   *
   * Set the used audio sink element. NULL will use the default sink. playsink
   * must be in %GST_STATE_NULL
   */
  g_object_class_install_property (gobject_klass, PROP_AUDIO_SINK,
      g_param_spec_object ("audio-sink", "Audio Sink",
          "the audio output element to use (NULL = default sink)",
          GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
564

565
566
567
568
569
570
571
572
  /**
   * GstPlaySink:text-sink:
   *
   * Set the used text sink element. NULL will use the default sink. playsink
   * must be in %GST_STATE_NULL
   */
  g_object_class_install_property (gobject_klass, PROP_TEXT_SINK,
      g_param_spec_object ("text-sink", "Text sink",
573
          "the text output element to use (NULL = default subtitleoverlay)",
574
575
          GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

576
577
578
579
580
581
582
583
584
585
586
587
  /**
   * GstPlaySink::send-event-mode:
   *
   * Sets the handling method used for events received from send_event
   * function. The default is %MODE_DEFAULT, that uses %GstBin's default
   * handling (push the event to all internal sinks).
   */
  g_object_class_install_property (gobject_klass, PROP_SEND_EVENT_MODE,
      g_param_spec_enum ("send-event-mode", "Send event mode",
          "How to send events received in send_event function",
          GST_TYPE_PLAY_SINK_SEND_EVENT_MODE, MODE_DEFAULT,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
588

589
590
591
592
593
594
595
596
597
598
  /**
   * GstPlaySink::force-aspect-ratio:
   *
   * Requests the video sink to enforce the video display aspect ratio.
   */
  g_object_class_install_property (gobject_klass, PROP_FORCE_ASPECT_RATIO,
      g_param_spec_boolean ("force-aspect-ratio", "Force Aspect Ratio",
          "When enabled, scaling will respect original aspect ratio", TRUE,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

599
600
  g_signal_new ("reconfigure", G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET (GstPlaySinkClass,
601
          reconfigure), NULL, NULL, g_cclosure_marshal_generic, G_TYPE_BOOLEAN,
602
      0, G_TYPE_NONE);
603
  /**
604
   * GstPlaySink::convert-sample
605
   * @playsink: a #GstPlaySink
606
   * @caps: the target format of the sample
607
   *
608
   * Action signal to retrieve the currently playing video sample in the format
609
610
   * specified by @caps.
   * If @caps is %NULL, no conversion will be performed and this function is
611
   * equivalent to the #GstPlaySink::sample property.
612
   *
613
614
615
   * Returns: a #GstSample of the current video sample converted to #caps.
   * The caps in the sample will describe the final layout of the buffer data.
   * %NULL is returned when no current sample can be retrieved or when the
616
617
   * conversion failed.
   */
618
  g_signal_new ("convert-sample", G_TYPE_FROM_CLASS (klass),
619
      G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
620
      G_STRUCT_OFFSET (GstPlaySinkClass, convert_sample), NULL, NULL,
621
      g_cclosure_marshal_generic, GST_TYPE_SAMPLE, 1, GST_TYPE_CAPS);
622

623
624
625
626
627
628
629
630
631
632
  gst_element_class_add_pad_template (gstelement_klass,
      gst_static_pad_template_get (&audiorawtemplate));
  gst_element_class_add_pad_template (gstelement_klass,
      gst_static_pad_template_get (&audiotemplate));
  gst_element_class_add_pad_template (gstelement_klass,
      gst_static_pad_template_get (&videorawtemplate));
  gst_element_class_add_pad_template (gstelement_klass,
      gst_static_pad_template_get (&videotemplate));
  gst_element_class_add_pad_template (gstelement_klass,
      gst_static_pad_template_get (&texttemplate));
633
  gst_element_class_set_static_metadata (gstelement_klass, "Player Sink",
634
635
636
      "Generic/Bin/Sink",
      "Convenience sink for multiple streams",
      "Wim Taymans <wim.taymans@gmail.com>");
Wim Taymans's avatar
Wim Taymans committed
637
638
639
640

  gstelement_klass->change_state =
      GST_DEBUG_FUNCPTR (gst_play_sink_change_state);
  gstelement_klass->send_event = GST_DEBUG_FUNCPTR (gst_play_sink_send_event);
641
642
643
644
  gstelement_klass->request_new_pad =
      GST_DEBUG_FUNCPTR (gst_play_sink_request_new_pad);
  gstelement_klass->release_pad =
      GST_DEBUG_FUNCPTR (gst_play_sink_release_request_pad);
Wim Taymans's avatar
Wim Taymans committed
645

646
647
  gstbin_klass->handle_message =
      GST_DEBUG_FUNCPTR (gst_play_sink_handle_message);
648

649
  klass->reconfigure = GST_DEBUG_FUNCPTR (gst_play_sink_reconfigure);
650
  klass->convert_sample = GST_DEBUG_FUNCPTR (gst_play_sink_convert_sample);
651
652
653

  _playsink_reset_segment_event_marker_id =
      g_quark_from_static_string ("gst-playsink-reset-segment-event-marker");
654
655
656

  g_type_class_ref (GST_TYPE_STREAM_SYNCHRONIZER);
  g_type_class_ref (GST_TYPE_COLOR_BALANCE_CHANNEL);
Wim Taymans's avatar
Wim Taymans committed
657
658
659
}

static void
660
gst_play_sink_init (GstPlaySink * playsink)
Wim Taymans's avatar
Wim Taymans committed
661
{
662
663
  GstColorBalanceChannel *channel;

Wim Taymans's avatar
Wim Taymans committed
664
  /* init groups */
665
666
667
  playsink->video_sink = NULL;
  playsink->audio_sink = NULL;
  playsink->visualisation = NULL;
668
  playsink->text_sink = NULL;
669
670
  playsink->volume = 1.0;
  playsink->font_desc = NULL;
671
  playsink->subtitle_encoding = NULL;
672
  playsink->flags = DEFAULT_FLAGS;
673
  playsink->send_event_mode = MODE_DEFAULT;
674
  playsink->force_aspect_ratio = TRUE;
675

676
677
678
679
680
  playsink->stream_synchronizer =
      g_object_new (GST_TYPE_STREAM_SYNCHRONIZER, NULL);
  gst_bin_add (GST_BIN_CAST (playsink),
      GST_ELEMENT_CAST (playsink->stream_synchronizer));

Wim Taymans's avatar
Wim Taymans committed
681
  g_rec_mutex_init (&playsink->lock);
Wim Taymans's avatar
Wim Taymans committed
682
  GST_OBJECT_FLAG_SET (playsink, GST_ELEMENT_FLAG_SINK);
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
709
710
711
712
713
714
715
716
717
718
719
720
721
722

  channel =
      GST_COLOR_BALANCE_CHANNEL (g_object_new (GST_TYPE_COLOR_BALANCE_CHANNEL,
          NULL));
  channel->label = g_strdup ("CONTRAST");
  channel->min_value = -1000;
  channel->max_value = 1000;
  playsink->colorbalance_channels =
      g_list_append (playsink->colorbalance_channels, channel);
  playsink->colorbalance_values[0] = 0;

  channel =
      GST_COLOR_BALANCE_CHANNEL (g_object_new (GST_TYPE_COLOR_BALANCE_CHANNEL,
          NULL));
  channel->label = g_strdup ("BRIGHTNESS");
  channel->min_value = -1000;
  channel->max_value = 1000;
  playsink->colorbalance_channels =
      g_list_append (playsink->colorbalance_channels, channel);
  playsink->colorbalance_values[1] = 0;

  channel =
      GST_COLOR_BALANCE_CHANNEL (g_object_new (GST_TYPE_COLOR_BALANCE_CHANNEL,
          NULL));
  channel->label = g_strdup ("HUE");
  channel->min_value = -1000;
  channel->max_value = 1000;
  playsink->colorbalance_channels =
      g_list_append (playsink->colorbalance_channels, channel);
  playsink->colorbalance_values[2] = 0;

  channel =
      GST_COLOR_BALANCE_CHANNEL (g_object_new (GST_TYPE_COLOR_BALANCE_CHANNEL,
          NULL));
  channel->label = g_strdup ("SATURATION");
  channel->min_value = -1000;
  channel->max_value = 1000;
  playsink->colorbalance_channels =
      g_list_append (playsink->colorbalance_channels, channel);
  playsink->colorbalance_values[3] = 0;
Wim Taymans's avatar
Wim Taymans committed
723
724
}

725
static void
726
disconnect_audio_chain (GstPlayAudioChain * chain, GstPlaySink * playsink)
727
728
{
  if (chain) {
729
730
731
    if (chain->notify_volume_id)
      g_signal_handler_disconnect (chain->volume, chain->notify_volume_id);
    if (chain->notify_mute_id)
732
      g_signal_handler_disconnect (chain->volume, chain->notify_mute_id);
733
    chain->notify_volume_id = chain->notify_mute_id = 0;
734
735
736
  }
}

737
738
739
740
741
742
743
744
745
746
static void
free_chain (GstPlayChain * chain)
{
  if (chain) {
    if (chain->bin)
      gst_object_unref (chain->bin);
    g_free (chain);
  }
}

747
748
749
750
751
752
753
754
755
756
757
758
static void
gst_play_sink_remove_audio_ssync_queue (GstPlaySink * playsink)
{
  if (playsink->audio_ssync_queue) {
    gst_element_set_state (playsink->audio_ssync_queue, GST_STATE_NULL);
    gst_object_unref (playsink->audio_ssync_queue_sinkpad);
    gst_bin_remove (GST_BIN_CAST (playsink), playsink->audio_ssync_queue);
    playsink->audio_ssync_queue = NULL;
    playsink->audio_ssync_queue_sinkpad = NULL;
  }
}

Wim Taymans's avatar
Wim Taymans committed
759
760
761
static void
gst_play_sink_dispose (GObject * object)
{
762
  GstPlaySink *playsink;
Wim Taymans's avatar
Wim Taymans committed
763

764
  playsink = GST_PLAY_SINK (object);
Wim Taymans's avatar
Wim Taymans committed
765

766
767
768
769
770
771
772
773
774
775
  if (playsink->audio_filter != NULL) {
    gst_element_set_state (playsink->audio_filter, GST_STATE_NULL);
    gst_object_unref (playsink->audio_filter);
    playsink->audio_filter = NULL;
  }
  if (playsink->video_filter != NULL) {
    gst_element_set_state (playsink->video_filter, GST_STATE_NULL);
    gst_object_unref (playsink->video_filter);
    playsink->video_filter = NULL;
  }
776
777
778
779
  if (playsink->audio_sink != NULL) {
    gst_element_set_state (playsink->audio_sink, GST_STATE_NULL);
    gst_object_unref (playsink->audio_sink);
    playsink->audio_sink = NULL;
Wim Taymans's avatar
Wim Taymans committed
780
  }
781
782
783
784
  if (playsink->video_sink != NULL) {
    gst_element_set_state (playsink->video_sink, GST_STATE_NULL);
    gst_object_unref (playsink->video_sink);
    playsink->video_sink = NULL;
Wim Taymans's avatar
Wim Taymans committed
785
  }
786
787
788
789
  if (playsink->visualisation != NULL) {
    gst_element_set_state (playsink->visualisation, GST_STATE_NULL);
    gst_object_unref (playsink->visualisation);
    playsink->visualisation = NULL;
Wim Taymans's avatar
Wim Taymans committed
790
  }
791
792
793
794
795
  if (playsink->text_sink != NULL) {
    gst_element_set_state (playsink->text_sink, GST_STATE_NULL);
    gst_object_unref (playsink->text_sink);
    playsink->text_sink = NULL;
  }
796

797
798
  free_chain ((GstPlayChain *) playsink->videodeinterlacechain);
  playsink->videodeinterlacechain = NULL;
799
800
801
802
803
804
805
806
807
  free_chain ((GstPlayChain *) playsink->videochain);
  playsink->videochain = NULL;
  free_chain ((GstPlayChain *) playsink->audiochain);
  playsink->audiochain = NULL;
  free_chain ((GstPlayChain *) playsink->vischain);
  playsink->vischain = NULL;
  free_chain ((GstPlayChain *) playsink->textchain);
  playsink->textchain = NULL;

808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
  if (playsink->audio_tee_sink) {
    gst_object_unref (playsink->audio_tee_sink);
    playsink->audio_tee_sink = NULL;
  }

  if (playsink->audio_tee_vissrc) {
    gst_element_release_request_pad (playsink->audio_tee,
        playsink->audio_tee_vissrc);
    gst_object_unref (playsink->audio_tee_vissrc);
    playsink->audio_tee_vissrc = NULL;
  }

  if (playsink->audio_tee_asrc) {
    gst_element_release_request_pad (playsink->audio_tee,
        playsink->audio_tee_asrc);
    gst_object_unref (playsink->audio_tee_asrc);
    playsink->audio_tee_asrc = NULL;
  }

827
828
  g_free (playsink->font_desc);
  playsink->font_desc = NULL;
Wim Taymans's avatar
Wim Taymans committed
829

830
831
832
  g_free (playsink->subtitle_encoding);
  playsink->subtitle_encoding = NULL;

833
834
  playsink->stream_synchronizer = NULL;

835
836
837
838
839
  g_list_foreach (playsink->colorbalance_channels, (GFunc) gst_object_unref,
      NULL);
  g_list_free (playsink->colorbalance_channels);
  playsink->colorbalance_channels = NULL;

840
  G_OBJECT_CLASS (gst_play_sink_parent_class)->dispose (object);
Wim Taymans's avatar
Wim Taymans committed
841
842
}

843
844
845
846
847
848
849
static void
gst_play_sink_finalize (GObject * object)
{
  GstPlaySink *playsink;

  playsink = GST_PLAY_SINK (object);

Wim Taymans's avatar
Wim Taymans committed
850
  g_rec_mutex_clear (&playsink->lock);
851

852
  G_OBJECT_CLASS (gst_play_sink_parent_class)->finalize (object);
853
854
}

855
void
856
857
gst_play_sink_set_sink (GstPlaySink * playsink, GstPlaySinkType type,
    GstElement * sink)
Wim Taymans's avatar
Wim Taymans committed
858
{
859
  GstElement **elem = NULL, *old = NULL;
860

861
862
  GST_LOG ("Setting sink %" GST_PTR_FORMAT " as sink type %d", sink, type);

863
  GST_PLAY_SINK_LOCK (playsink);
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
  switch (type) {
    case GST_PLAY_SINK_TYPE_AUDIO:
    case GST_PLAY_SINK_TYPE_AUDIO_RAW:
      elem = &playsink->audio_sink;
      break;
    case GST_PLAY_SINK_TYPE_VIDEO:
    case GST_PLAY_SINK_TYPE_VIDEO_RAW:
      elem = &playsink->video_sink;
      break;
    case GST_PLAY_SINK_TYPE_TEXT:
      elem = &playsink->text_sink;
      break;
    default:
      break;
  }
  if (elem) {
    old = *elem;
881
    if (sink)
882
      gst_object_ref_sink (sink);
883
    *elem = sink;
884
885
886
  }
  GST_PLAY_SINK_UNLOCK (playsink);

887
  if (old) {
888
889
    /* Set the old sink to NULL if it is not used any longer */
    if (old != sink && !GST_OBJECT_PARENT (old))
890
      gst_element_set_state (old, GST_STATE_NULL);
891
    gst_object_unref (old);
892
  }
893
894
895
}

GstElement *
896
gst_play_sink_get_sink (GstPlaySink * playsink, GstPlaySinkType type)
897
898
{
  GstElement *result = NULL;
899
  GstElement *elem = NULL, *chainp = NULL;
900
901

  GST_PLAY_SINK_LOCK (playsink);
902
903
  switch (type) {
    case GST_PLAY_SINK_TYPE_AUDIO:
904
    case GST_PLAY_SINK_TYPE_AUDIO_RAW:
905
906
907
908
909
910
911
912
    {
      GstPlayAudioChain *chain;
      if ((chain = (GstPlayAudioChain *) playsink->audiochain))
        chainp = chain->sink;
      elem = playsink->audio_sink;
      break;
    }
    case GST_PLAY_SINK_TYPE_VIDEO:
913
    case GST_PLAY_SINK_TYPE_VIDEO_RAW:
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
    {
      GstPlayVideoChain *chain;
      if ((chain = (GstPlayVideoChain *) playsink->videochain))
        chainp = chain->sink;
      elem = playsink->video_sink;
      break;
    }
    case GST_PLAY_SINK_TYPE_TEXT:
    {
      GstPlayTextChain *chain;
      if ((chain = (GstPlayTextChain *) playsink->textchain))
        chainp = chain->sink;
      elem = playsink->text_sink;
      break;
    }
    default:
      break;
  }
  if (chainp) {
    /* we have an active chain with a sink, get the sink */
    result = gst_object_ref (chainp);
935
936
  }
  /* nothing found, return last configured sink */
937
938
  if (result == NULL && elem)
    result = gst_object_ref (elem);
939
940
941
  GST_PLAY_SINK_UNLOCK (playsink);

  return result;
Wim Taymans's avatar
Wim Taymans committed
942
943
}

944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
void
gst_play_sink_set_filter (GstPlaySink * playsink, GstPlaySinkType type,
    GstElement * filter)
{
  GstElement **elem = NULL, *old = NULL;

  GST_LOG_OBJECT (playsink,
      "Setting filter %" GST_PTR_FORMAT " as filter type %d", filter, type);

  GST_PLAY_SINK_LOCK (playsink);
  switch (type) {
    case GST_PLAY_SINK_TYPE_AUDIO:
    case GST_PLAY_SINK_TYPE_AUDIO_RAW:
      elem = &playsink->audio_filter;
      break;
    case GST_PLAY_SINK_TYPE_VIDEO:
    case GST_PLAY_SINK_TYPE_VIDEO_RAW:
      elem = &playsink->video_filter;
      break;
    default:
      break;
  }
  if (elem) {
    old = *elem;
    if (filter)
      gst_object_ref_sink (filter);
    *elem = filter;
  }
  GST_PLAY_SINK_UNLOCK (playsink);

  if (old) {
    /* Set the old filter to NULL if it is not used any longer */
    if (old != filter && !GST_OBJECT_PARENT (old))
      gst_element_set_state (old, GST_STATE_NULL);
    gst_object_unref (old);
  }
}

GstElement *
gst_play_sink_get_filter (GstPlaySink * playsink, GstPlaySinkType type)
{
  GstElement *result = NULL;
  GstElement *elem = NULL, *chainp = NULL;

  GST_PLAY_SINK_LOCK (playsink);
  switch (type) {
990
    case GST_PLAY_SINK_TYPE_AUDIO:
991
992
993
994
995
996
997
998
    case GST_PLAY_SINK_TYPE_AUDIO_RAW:
    {
      GstPlayAudioChain *chain;
      if ((chain = (GstPlayAudioChain *) playsink->audiochain))
        chainp = chain->filter;
      elem = playsink->audio_filter;
      break;
    }
999
    case GST_PLAY_SINK_TYPE_VIDEO:
1000
    case GST_PLAY_SINK_TYPE_VIDEO_RAW: