2 * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
14 * You should have received a copy of the GNU Library General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17 * Boston, MA 02111-1307, USA.
27 #include <gst/gst-i18n-plugin.h>
29 #include "gstplaybasebin.h"
31 GST_DEBUG_CATEGORY_STATIC (gst_play_bin_debug);
32 #define GST_CAT_DEFAULT gst_play_bin_debug
34 #define GST_TYPE_PLAY_BIN (gst_play_bin_get_type())
35 #define GST_PLAY_BIN(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_PLAY_BIN,GstPlayBin))
36 #define GST_PLAY_BIN_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_PLAY_BIN,GstPlayBinClass))
37 #define GST_IS_PLAY_BIN(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_PLAY_BIN))
38 #define GST_IS_PLAY_BIN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_PLAY_BIN))
40 #define VOLUME_MAX_DOUBLE 10.0
41 #define CONNECTION_SPEED_DEFAULT 0
43 typedef struct _GstPlayBin GstPlayBin;
44 typedef struct _GstPlayBinClass GstPlayBinClass;
48 GstPlayBaseBin parent;
50 /* the configurable elements */
52 GstElement *audio_sink;
53 GstElement *video_sink;
54 GstElement *visualisation;
55 GstElement *pending_visualisation;
56 GstElement *volume_element;
57 GstElement *textoverlay_element;
60 /* these are the currently active sinks */
63 /* the last captured frame for snapshots */
66 /* our cache for the sinks */
69 /* font description */
72 /* connection speed in bits/sec (0 = unknown) */
73 guint connection_speed;
76 struct _GstPlayBinClass
78 GstPlayBaseBinClass parent_class;
100 static void gst_play_bin_class_init (GstPlayBinClass * klass);
101 static void gst_play_bin_init (GstPlayBin * play_bin);
102 static void gst_play_bin_dispose (GObject * object);
104 static gboolean setup_sinks (GstPlayBaseBin * play_base_bin,
105 GstPlayBaseGroup * group);
106 static void remove_sinks (GstPlayBin * play_bin);
108 static void gst_play_bin_set_property (GObject * object, guint prop_id,
109 const GValue * value, GParamSpec * spec);
110 static void gst_play_bin_get_property (GObject * object, guint prop_id,
111 GValue * value, GParamSpec * spec);
113 static gboolean gst_play_bin_send_event (GstElement * element,
115 static GstStateChangeReturn gst_play_bin_change_state (GstElement * element,
116 GstStateChange transition);
117 static void gst_play_bin_handle_message (GstBin * bin, GstMessage * message);
119 static GstElementClass *parent_class;
121 //static guint gst_play_bin_signals[LAST_SIGNAL] = { 0 };
123 static const GstElementDetails gst_play_bin_details =
124 GST_ELEMENT_DETAILS ("Player Bin",
125 "Generic/Bin/Player",
126 "Autoplug and play media from an uri",
127 "Wim Taymans <wim@fluendo.com>");
130 gst_play_bin_get_type (void)
132 static GType gst_play_bin_type = 0;
134 if (!gst_play_bin_type) {
135 static const GTypeInfo gst_play_bin_info = {
136 sizeof (GstPlayBinClass),
139 (GClassInitFunc) gst_play_bin_class_init,
144 (GInstanceInitFunc) gst_play_bin_init,
148 gst_play_bin_type = g_type_register_static (GST_TYPE_PLAY_BASE_BIN,
149 "GstPlayBin", &gst_play_bin_info, 0);
152 return gst_play_bin_type;
156 gst_play_bin_class_init (GstPlayBinClass * klass)
158 GObjectClass *gobject_klass;
159 GstElementClass *gstelement_klass;
160 GstBinClass *gstbin_klass;
161 GstPlayBaseBinClass *playbasebin_klass;
163 gobject_klass = (GObjectClass *) klass;
164 gstelement_klass = (GstElementClass *) klass;
165 gstbin_klass = (GstBinClass *) klass;
166 playbasebin_klass = (GstPlayBaseBinClass *) klass;
168 parent_class = g_type_class_peek_parent (klass);
170 gobject_klass->set_property = gst_play_bin_set_property;
171 gobject_klass->get_property = gst_play_bin_get_property;
173 g_object_class_install_property (gobject_klass, ARG_VIDEO_SINK,
174 g_param_spec_object ("video-sink", "Video Sink",
175 "the video output element to use (NULL = default sink)",
176 GST_TYPE_ELEMENT, G_PARAM_READWRITE));
177 g_object_class_install_property (gobject_klass, ARG_AUDIO_SINK,
178 g_param_spec_object ("audio-sink", "Audio Sink",
179 "the audio output element to use (NULL = default sink)",
180 GST_TYPE_ELEMENT, G_PARAM_READWRITE));
181 g_object_class_install_property (gobject_klass, ARG_VIS_PLUGIN,
182 g_param_spec_object ("vis-plugin", "Vis plugin",
183 "the visualization element to use (NULL = none)",
184 GST_TYPE_ELEMENT, G_PARAM_READWRITE));
185 g_object_class_install_property (gobject_klass, ARG_VOLUME,
186 g_param_spec_double ("volume", "volume", "volume",
187 0.0, VOLUME_MAX_DOUBLE, 1.0, G_PARAM_READWRITE));
188 g_object_class_install_property (gobject_klass, ARG_FRAME,
189 gst_param_spec_mini_object ("frame", "Frame",
190 "The last frame (NULL = no video available)",
191 GST_TYPE_BUFFER, G_PARAM_READABLE));
192 g_object_class_install_property (gobject_klass, ARG_FONT_DESC,
193 g_param_spec_string ("subtitle-font-desc",
194 "Subtitle font description",
195 "Pango font description of font "
196 "to be used for subtitle rendering", NULL, G_PARAM_WRITABLE));
198 * GstPlayBin:connection-speed
200 * Network connection speed in kbps (0 = unknown)
204 g_object_class_install_property (gobject_klass, ARG_CONNECTION_SPEED,
205 g_param_spec_uint ("connection-speed", "Connection Speed",
206 "Network connection speed in kbps (0 = unknown)",
207 0, G_MAXUINT, CONNECTION_SPEED_DEFAULT, G_PARAM_READWRITE));
209 gobject_klass->dispose = GST_DEBUG_FUNCPTR (gst_play_bin_dispose);
211 gst_element_class_set_details (gstelement_klass, &gst_play_bin_details);
213 gstelement_klass->change_state =
214 GST_DEBUG_FUNCPTR (gst_play_bin_change_state);
215 gstelement_klass->send_event = GST_DEBUG_FUNCPTR (gst_play_bin_send_event);
217 gstbin_klass->handle_message =
218 GST_DEBUG_FUNCPTR (gst_play_bin_handle_message);
220 playbasebin_klass->setup_output_pads = setup_sinks;
224 gst_play_bin_init (GstPlayBin * play_bin)
226 play_bin->video_sink = NULL;
227 play_bin->audio_sink = NULL;
228 play_bin->visualisation = NULL;
229 play_bin->pending_visualisation = NULL;
230 play_bin->volume_element = NULL;
231 play_bin->textoverlay_element = NULL;
232 play_bin->volume = 1.0;
233 play_bin->sinks = NULL;
234 play_bin->frame = NULL;
235 play_bin->font_desc = NULL;
236 play_bin->cache = g_hash_table_new_full (g_str_hash, g_str_equal,
237 NULL, (GDestroyNotify) gst_object_unref);
241 gst_play_bin_dispose (GObject * object)
243 GstPlayBin *play_bin;
245 play_bin = GST_PLAY_BIN (object);
247 if (play_bin->cache != NULL) {
248 remove_sinks (play_bin);
249 g_hash_table_destroy (play_bin->cache);
250 play_bin->cache = NULL;
253 if (play_bin->audio_sink != NULL) {
254 gst_element_set_state (play_bin->audio_sink, GST_STATE_NULL);
255 gst_object_unref (play_bin->audio_sink);
256 play_bin->audio_sink = NULL;
258 if (play_bin->video_sink != NULL) {
259 gst_element_set_state (play_bin->video_sink, GST_STATE_NULL);
260 gst_object_unref (play_bin->video_sink);
261 play_bin->video_sink = NULL;
263 if (play_bin->visualisation != NULL) {
264 gst_element_set_state (play_bin->visualisation, GST_STATE_NULL);
265 gst_object_unref (play_bin->visualisation);
266 play_bin->visualisation = NULL;
268 if (play_bin->pending_visualisation != NULL) {
269 gst_element_set_state (play_bin->pending_visualisation, GST_STATE_NULL);
270 gst_object_unref (play_bin->pending_visualisation);
271 play_bin->pending_visualisation = NULL;
273 if (play_bin->textoverlay_element != NULL) {
274 gst_object_unref (play_bin->textoverlay_element);
275 play_bin->textoverlay_element = NULL;
277 g_free (play_bin->font_desc);
278 play_bin->font_desc = NULL;
280 G_OBJECT_CLASS (parent_class)->dispose (object);
284 gst_play_bin_vis_unblocked (GstPad * tee_pad, gboolean blocked,
291 gst_play_bin_vis_blocked (GstPad * tee_pad, gboolean blocked,
294 GstPlayBin *play_bin = GST_PLAY_BIN (user_data);
295 GstBin *vis_bin = NULL;
296 GstPad *vis_sink_pad = NULL, *vis_src_pad = NULL, *vqueue_pad = NULL;
299 /* We want to disable visualisation */
300 if (!GST_IS_ELEMENT (play_bin->pending_visualisation)) {
301 /* Set visualisation element to READY */
302 gst_element_set_state (play_bin->visualisation, GST_STATE_READY);
307 GST_BIN (gst_object_get_parent (GST_OBJECT (play_bin->visualisation)));
309 if (!GST_IS_BIN (vis_bin) || !GST_IS_PAD (tee_pad)) {
313 vis_src_pad = gst_element_get_pad (play_bin->visualisation, "src");
314 vis_sink_pad = gst_pad_get_peer (tee_pad);
316 /* Can be fakesink */
317 if (GST_IS_PAD (vis_src_pad)) {
318 vqueue_pad = gst_pad_get_peer (vis_src_pad);
321 if (!GST_IS_PAD (vis_sink_pad)) {
325 /* Check the bin's state */
326 GST_OBJECT_LOCK (vis_bin);
327 bin_state = GST_STATE (vis_bin);
328 GST_OBJECT_UNLOCK (vis_bin);
331 gst_pad_unlink (tee_pad, vis_sink_pad);
332 gst_object_unref (vis_sink_pad);
335 if (GST_IS_PAD (vqueue_pad)) {
336 gst_pad_unlink (vis_src_pad, vqueue_pad);
337 gst_object_unref (vis_src_pad);
341 /* Remove from vis_bin */
342 gst_bin_remove (vis_bin, play_bin->visualisation);
343 /* Set state to NULL */
344 gst_element_set_state (play_bin->visualisation, GST_STATE_NULL);
345 /* And loose our ref */
346 gst_object_unref (play_bin->visualisation);
348 if (play_bin->pending_visualisation) {
349 /* Ref this new visualisation element before adding to the bin */
350 gst_object_ref (play_bin->pending_visualisation);
351 /* Add the new one */
352 gst_bin_add (vis_bin, play_bin->pending_visualisation);
353 /* Synchronizing state */
354 gst_element_set_state (play_bin->pending_visualisation, bin_state);
356 vis_sink_pad = gst_element_get_pad (play_bin->pending_visualisation,
358 vis_src_pad = gst_element_get_pad (play_bin->pending_visualisation, "src");
360 if (!GST_IS_PAD (vis_sink_pad) || !GST_IS_PAD (vis_src_pad)) {
365 gst_pad_link (tee_pad, vis_sink_pad);
366 gst_pad_link (vis_src_pad, vqueue_pad);
370 gst_object_unref (play_bin->visualisation);
371 play_bin->visualisation = play_bin->pending_visualisation;
372 play_bin->pending_visualisation = NULL;
376 gst_object_unref (vis_sink_pad);
379 gst_object_unref (vis_src_pad);
382 gst_object_unref (vqueue_pad);
385 gst_object_unref (vis_bin);
388 /* Unblock the pad */
389 gst_pad_set_blocked_async (tee_pad, FALSE, gst_play_bin_vis_unblocked,
394 gst_play_bin_set_property (GObject * object, guint prop_id,
395 const GValue * value, GParamSpec * pspec)
397 GstPlayBin *play_bin;
399 play_bin = GST_PLAY_BIN (object);
403 if (play_bin->video_sink != NULL) {
404 gst_object_unref (play_bin->video_sink);
406 play_bin->video_sink = g_value_get_object (value);
407 if (play_bin->video_sink != NULL) {
408 gst_object_ref (play_bin->video_sink);
409 gst_object_sink (GST_OBJECT (play_bin->video_sink));
411 /* when changing the videosink, we just remove the
412 * video pipeline from the cache so that it will be
413 * regenerated with the new sink element */
414 g_hash_table_remove (play_bin->cache, "vbin");
417 if (play_bin->audio_sink != NULL) {
418 gst_object_unref (play_bin->audio_sink);
420 play_bin->audio_sink = g_value_get_object (value);
421 if (play_bin->audio_sink != NULL) {
422 gst_object_ref (play_bin->audio_sink);
423 gst_object_sink (GST_OBJECT (play_bin->audio_sink));
425 g_hash_table_remove (play_bin->cache, "abin");
429 /* Do we already have a visualisation change pending ? */
430 if (play_bin->pending_visualisation) {
431 gst_object_unref (play_bin->pending_visualisation);
432 play_bin->pending_visualisation = g_value_get_object (value);
434 if (play_bin->pending_visualisation) {
435 gst_object_ref (play_bin->pending_visualisation);
436 gst_object_sink (GST_OBJECT (play_bin->pending_visualisation));
439 play_bin->pending_visualisation = g_value_get_object (value);
442 if (play_bin->pending_visualisation) {
443 gst_object_ref (play_bin->pending_visualisation);
444 gst_object_sink (GST_OBJECT (play_bin->pending_visualisation));
447 /* Was there a visualisation already set ? */
448 if (play_bin->visualisation != NULL) {
449 GstBin *vis_bin = NULL;
452 GST_BIN (gst_object_get_parent (GST_OBJECT (play_bin->
455 /* Check if the visualisation is already in a bin */
456 if (GST_IS_BIN (vis_bin)) {
457 GstPad *vis_sink_pad = NULL, *tee_pad = NULL;
459 /* Now get tee pad and block it async */
460 vis_sink_pad = gst_element_get_pad (play_bin->visualisation,
462 if (!GST_IS_PAD (vis_sink_pad)) {
465 tee_pad = gst_pad_get_peer (vis_sink_pad);
466 if (!GST_IS_PAD (tee_pad)) {
470 /* Block with callback */
471 gst_pad_set_blocked_async (tee_pad, TRUE, gst_play_bin_vis_blocked,
475 gst_object_unref (vis_sink_pad);
478 gst_object_unref (tee_pad);
480 gst_object_unref (vis_bin);
482 play_bin->visualisation = play_bin->pending_visualisation;
483 play_bin->pending_visualisation = NULL;
486 play_bin->visualisation = play_bin->pending_visualisation;
487 play_bin->pending_visualisation = NULL;
493 play_bin->volume = g_value_get_double (value);
494 if (play_bin->volume_element) {
495 g_object_set (G_OBJECT (play_bin->volume_element), "volume",
496 play_bin->volume, NULL);
500 g_free (play_bin->font_desc);
501 play_bin->font_desc = g_strdup (g_value_get_string (value));
502 if (play_bin->textoverlay_element) {
503 g_object_set (G_OBJECT (play_bin->textoverlay_element),
504 "font-desc", g_value_get_string (value), NULL);
507 case ARG_CONNECTION_SPEED:
508 play_bin->connection_speed = g_value_get_uint (value) * 1000;
511 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
517 gst_play_bin_get_property (GObject * object, guint prop_id, GValue * value,
520 GstPlayBin *play_bin;
522 play_bin = GST_PLAY_BIN (object);
526 g_value_set_object (value, play_bin->video_sink);
529 g_value_set_object (value, play_bin->audio_sink);
532 g_value_set_object (value, play_bin->visualisation);
535 g_value_set_double (value, play_bin->volume);
538 gst_value_set_mini_object (value, GST_MINI_OBJECT (play_bin->frame));
540 case ARG_CONNECTION_SPEED:
541 g_value_set_uint (value, play_bin->connection_speed / 1000);
544 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
549 /* signal fired when the identity has received a new buffer. This is used for
550 * making screenshots.
553 handoff (GstElement * identity, GstBuffer * frame, gpointer data)
555 GstPlayBin *play_bin = GST_PLAY_BIN (data);
556 GstBuffer **frame_p = &play_bin->frame;
558 gst_mini_object_replace ((GstMiniObject **) frame_p,
559 GST_MINI_OBJECT_CAST (frame));
561 /* applications need to know the buffer caps,
562 * make sure they are always set on the frame */
563 if (GST_BUFFER_CAPS (play_bin->frame) == NULL) {
566 if ((pad = gst_element_get_pad (identity, "sink"))) {
567 gst_buffer_set_caps (play_bin->frame, GST_PAD_CAPS (pad));
568 gst_object_unref (pad);
573 /* make the element (bin) that contains the elements needed to perform
574 * video display. We connect a handoff signal to identity so that we
575 * can grab snapshots. Identity's sinkpad is ghosted to vbin.
577 * +-------------------------------------------------------------+
579 * | +--------+ +----------+ +----------+ +---------+ |
580 * | |identity| |colorspace| |videoscale| |videosink| |
581 * | +-sink src-sink src-sink src-sink | |
582 * | | +---+----+ +----------+ +----------+ +---------+ |
584 * +----------|--------------------------------------------------+
587 /* FIXME: this might return NULL if no videosink was found, handle
590 gen_video_element (GstPlayBin * play_bin)
597 GstElement *identity;
600 /* first see if we have it in the cache */
601 element = g_hash_table_lookup (play_bin->cache, "vbin");
602 if (element != NULL) {
606 if (play_bin->video_sink) {
607 sink = play_bin->video_sink;
609 sink = gst_element_factory_make ("autovideosink", "videosink");
611 sink = gst_element_factory_make ("xvimagesink", "videosink");
613 /* FIXME: this warrants adding a CORE error category for missing
614 * elements/plugins */
616 GST_ELEMENT_ERROR (play_bin, CORE, MISSING_PLUGIN,
617 (_("Both autovideosink and xvimagesink elements are missing.")),
622 gst_object_ref (sink);
623 g_hash_table_insert (play_bin->cache, "video_sink", sink);
626 element = gst_bin_new ("vbin");
627 identity = gst_element_factory_make ("identity", "id");
628 g_object_set (identity, "silent", TRUE, NULL);
629 g_signal_connect (identity, "handoff", G_CALLBACK (handoff), play_bin);
630 gst_bin_add (GST_BIN (element), identity);
631 conv = gst_element_factory_make ("ffmpegcolorspace", "vconv");
634 scale = gst_element_factory_make ("videoscale", "vscale");
637 gst_bin_add (GST_BIN (element), conv);
638 gst_bin_add (GST_BIN (element), scale);
639 gst_bin_add (GST_BIN (element), sink);
640 gst_element_link_pads (identity, "src", conv, "sink");
641 gst_element_link_pads (conv, "src", scale, "sink");
642 gst_element_link_pads (scale, "src", sink, "sink");
644 pad = gst_element_get_pad (identity, "sink");
645 gst_element_add_pad (element, gst_ghost_pad_new ("sink", pad));
646 gst_object_unref (pad);
648 gst_element_set_state (element, GST_STATE_READY);
650 /* since we're gonna add it to a bin but don't want to lose it,
651 * we keep a reference. */
652 gst_object_ref (element);
653 g_hash_table_insert (play_bin->cache, "vbin", element);
659 GST_ELEMENT_ERROR (play_bin, CORE, MISSING_PLUGIN,
660 (_("Missing element '%s' - check your GStreamer installation."),
661 "ffmpegcolorspace"), (NULL));
662 gst_object_unref (element);
668 GST_ELEMENT_ERROR (play_bin, CORE, MISSING_PLUGIN,
669 (_("Missing element '%s' - check your GStreamer installation."),
670 "videoscale"), ("possibly a liboil version mismatch?"));
671 gst_object_unref (element);
676 /* make an element for playback of video with subtitles embedded.
678 * +--------------------------------------------------+
679 * | tbin +-------------+ |
680 * | +-----+ | textoverlay | +------+ |
681 * | | csp | +--video_sink | | vbin | |
682 * video_sink-sink src+ +-text_sink src-sink | |
683 * | +-----+ | +-------------+ +------+ |
684 * text_sink-------------+ |
685 * +--------------------------------------------------+
689 gen_text_element (GstPlayBin * play_bin)
691 GstElement *element, *csp, *overlay, *vbin;
695 element = gst_bin_new ("textbin");
698 overlay = gst_element_factory_make ("textoverlay", "overlay");
700 /* Create the video rendering bin */
701 vbin = gen_video_element (play_bin);
703 /* If no overlay return the video bin */
705 GST_WARNING ("No overlay (pango) element, subtitles disabled");
709 /* Set some parameters */
710 g_object_set (G_OBJECT (overlay),
711 "halign", "center", "valign", "bottom", NULL);
712 if (play_bin->font_desc) {
713 g_object_set (G_OBJECT (overlay), "font-desc", play_bin->font_desc, NULL);
717 play_bin->textoverlay_element = GST_ELEMENT (gst_object_ref (overlay));
719 /* we know this will succeed, as the video bin already created one before */
720 csp = gst_element_factory_make ("ffmpegcolorspace", "subtitlecsp");
722 /* Add our elements */
723 gst_bin_add_many (GST_BIN (element), csp, overlay, vbin, NULL);
726 gst_element_link_pads (csp, "src", overlay, "video_sink");
727 gst_element_link_pads (overlay, "src", vbin, "sink");
729 /* Add ghost pads on the subtitle bin */
730 pad = gst_element_get_pad (overlay, "text_sink");
731 gst_element_add_pad (element, gst_ghost_pad_new ("text_sink", pad));
732 gst_object_unref (pad);
734 pad = gst_element_get_pad (csp, "sink");
735 gst_element_add_pad (element, gst_ghost_pad_new ("sink", pad));
736 gst_object_unref (pad);
738 /* Set state to READY */
739 gst_element_set_state (element, GST_STATE_READY);
744 /* make the element (bin) that contains the elements needed to perform
747 * +-------------------------------------------------------------+
749 * | +---------+ +----------+ +---------+ +---------+ |
750 * | |audioconv| |audioscale| | volume | |audiosink| |
751 * | +-sink src-sink src-sink src-sink | |
752 * | | +---------+ +----------+ +---------+ +---------+ |
754 * +-------------------------------------------------------------+
758 gen_audio_element (GstPlayBin * play_bin)
767 element = g_hash_table_lookup (play_bin->cache, "abin");
768 if (element != NULL) {
771 element = gst_bin_new ("abin");
772 conv = gst_element_factory_make ("audioconvert", "aconv");
774 goto no_audioconvert;
776 scale = gst_element_factory_make ("audioresample", "aresample");
778 goto no_audioresample;
780 volume = gst_element_factory_make ("volume", "volume");
781 g_object_set (G_OBJECT (volume), "volume", play_bin->volume, NULL);
782 play_bin->volume_element = volume;
784 if (play_bin->audio_sink) {
785 sink = play_bin->audio_sink;
787 sink = gst_element_factory_make ("autoaudiosink", "audiosink");
789 sink = gst_element_factory_make ("alsasink", "audiosink");
792 GST_ELEMENT_ERROR (play_bin, CORE, MISSING_PLUGIN,
793 (_("Both autoaudiosink and alsasink elements are missing.")), (NULL));
796 play_bin->audio_sink = GST_ELEMENT (gst_object_ref (sink));
799 gst_object_ref (sink);
800 g_hash_table_insert (play_bin->cache, "audio_sink", sink);
802 gst_bin_add (GST_BIN (element), conv);
803 gst_bin_add (GST_BIN (element), scale);
804 gst_bin_add (GST_BIN (element), volume);
805 gst_bin_add (GST_BIN (element), sink);
807 gst_element_link_pads (conv, "src", scale, "sink");
808 gst_element_link_pads (scale, "src", volume, "sink");
809 gst_element_link_pads (volume, "src", sink, "sink");
811 pad = gst_element_get_pad (conv, "sink");
812 gst_element_add_pad (element, gst_ghost_pad_new ("sink", pad));
813 gst_object_unref (pad);
815 gst_element_set_state (element, GST_STATE_READY);
817 /* since we're gonna add it to a bin but don't want to lose it,
818 * we keep a reference. */
819 gst_object_ref (element);
820 g_hash_table_insert (play_bin->cache, "abin", element);
826 GST_ELEMENT_ERROR (play_bin, CORE, MISSING_PLUGIN,
827 (_("Missing element '%s' - check your GStreamer installation."),
828 "audioconvert"), ("possibly a liboil version mismatch?"));
829 gst_object_unref (element);
835 GST_ELEMENT_ERROR (play_bin, CORE, MISSING_PLUGIN,
836 (_("Missing element '%s' - check your GStreamer installation."),
837 "audioresample"), ("possibly a liboil version mismatch?"));
838 gst_object_unref (element);
843 /* make the element (bin) that contains the elements needed to perform
844 * visualisation ouput. The idea is to split the audio using tee, then
845 * sending the output to the regular audio bin and the other output to
846 * the vis plugin that transforms it into a video that is rendered with the
847 * normal video bin. The video bin is run in a thread to make sure it does
848 * not block the audio playback pipeline.
850 * +--------------------------------------------------------------------+
852 * | +------+ +--------+ +----------------+ |
853 * | | tee | | aqueue | | abin ... | |
854 * | +-sink src-sink src-sink | |
855 * | | | | +--------+ +----------------+ |
857 * | | | | +------+ +---------+ +------+ +-----------+ |
858 * | | | | |vqueue| |audioconv| | vis | | vbin ... | |
859 * | | | src-sink src-sink src-sink src-sink | |
860 * | | | | +------+ +---------+ +------+ +-----------+ |
864 +---------------------------------------------------------------------+
867 gen_vis_element (GstPlayBin * play_bin)
875 GstElement *vqueue, *aqueue;
878 asink = gen_audio_element (play_bin);
881 vsink = gen_video_element (play_bin);
883 gst_object_unref (asink);
887 element = gst_bin_new ("visbin");
888 tee = gst_element_factory_make ("tee", "tee");
890 vqueue = gst_element_factory_make ("queue", "vqueue");
891 aqueue = gst_element_factory_make ("queue", "aqueue");
893 gst_bin_add (GST_BIN (element), asink);
894 gst_bin_add (GST_BIN (element), vqueue);
895 gst_bin_add (GST_BIN (element), aqueue);
896 gst_bin_add (GST_BIN (element), vsink);
897 gst_bin_add (GST_BIN (element), tee);
899 conv = gst_element_factory_make ("audioconvert", "aconv");
901 goto no_audioconvert;
903 if (play_bin->visualisation) {
904 gst_object_ref (play_bin->visualisation);
905 vis = play_bin->visualisation;
907 vis = gst_element_factory_make ("goom", "vis");
910 gst_bin_add (GST_BIN (element), conv);
911 gst_bin_add (GST_BIN (element), vis);
913 gst_element_link_pads (vqueue, "src", conv, "sink");
914 gst_element_link_pads (conv, "src", vis, "sink");
915 gst_element_link_pads (vis, "src", vsink, "sink");
917 pad = gst_element_get_pad (aqueue, "sink");
918 rpad = gst_element_get_request_pad (tee, "src%d");
919 gst_pad_link (rpad, pad);
920 gst_object_unref (rpad);
921 gst_object_unref (pad);
922 gst_element_link_pads (aqueue, "src", asink, "sink");
924 pad = gst_element_get_pad (vqueue, "sink");
925 rpad = gst_element_get_request_pad (tee, "src%d");
926 gst_pad_link (rpad, pad);
927 gst_object_unref (rpad);
928 gst_object_unref (pad);
930 pad = gst_element_get_pad (tee, "sink");
931 gst_element_add_pad (element, gst_ghost_pad_new ("sink", pad));
932 gst_object_unref (pad);
938 GST_ELEMENT_ERROR (play_bin, CORE, MISSING_PLUGIN,
939 (_("Missing element '%s' - check your GStreamer installation."),
940 "audioconvert"), ("possibly a liboil version mismatch?"));
941 gst_object_unref (element);
946 /* get rid of all installed sinks */
948 remove_sinks (GstPlayBin * play_bin)
955 GST_DEBUG ("removesinks");
956 element = g_hash_table_lookup (play_bin->cache, "abin");
957 if (element != NULL) {
958 parent = gst_element_get_parent (element);
959 if (parent != NULL) {
960 /* we remove the element from the parent so that
961 * there is no unwanted state change when the parent
963 play_bin->sinks = g_list_remove (play_bin->sinks, element);
964 gst_element_set_state (element, GST_STATE_NULL);
965 gst_bin_remove (GST_BIN (parent), element);
966 gst_object_unref (parent);
968 pad = gst_element_get_pad (element, "sink");
970 peer = gst_pad_get_peer (pad);
972 gst_pad_unlink (peer, pad);
973 gst_object_unref (peer);
975 gst_object_unref (pad);
978 element = g_hash_table_lookup (play_bin->cache, "vbin");
979 if (element != NULL) {
980 parent = gst_element_get_parent (element);
981 if (parent != NULL) {
982 play_bin->sinks = g_list_remove (play_bin->sinks, element);
983 gst_element_set_state (element, GST_STATE_NULL);
984 gst_bin_remove (GST_BIN (parent), element);
985 gst_object_unref (parent);
987 pad = gst_element_get_pad (element, "sink");
989 peer = gst_pad_get_peer (pad);
991 gst_pad_unlink (peer, pad);
992 gst_object_unref (peer);
994 gst_object_unref (pad);
998 for (sinks = play_bin->sinks; sinks; sinks = g_list_next (sinks)) {
999 GstElement *element = GST_ELEMENT (sinks->data);
1003 pad = gst_element_get_pad (element, "sink");
1005 GST_LOG ("removing sink %p", element);
1007 peer = gst_pad_get_peer (pad);
1009 gst_pad_unlink (peer, pad);
1010 gst_object_unref (peer);
1012 gst_object_unref (pad);
1014 gst_element_set_state (element, GST_STATE_NULL);
1015 gst_bin_remove (GST_BIN (play_bin), element);
1017 g_list_free (play_bin->sinks);
1018 play_bin->sinks = NULL;
1020 /* FIXME: this is probably some refcounting problem */
1021 if (play_bin->visualisation && GST_OBJECT_PARENT (play_bin->visualisation)) {
1022 gst_element_set_state (play_bin->visualisation, GST_STATE_NULL);
1023 gst_bin_remove (GST_BIN (GST_OBJECT_PARENT (play_bin->visualisation)),
1024 play_bin->visualisation);
1027 if (play_bin->frame) {
1028 gst_buffer_unref (play_bin->frame);
1029 play_bin->frame = NULL;
1032 if (play_bin->textoverlay_element) {
1033 gst_object_unref (play_bin->textoverlay_element);
1034 play_bin->textoverlay_element = NULL;
1038 /* loop over the streams and set up the pipeline to play this
1039 * media file. First we count the number of audio and video streams.
1040 * If there is no video stream but there exists an audio stream,
1041 * we install a visualisation pipeline.
1043 * Also make sure to only connect the first audio and video pad. FIXME
1044 * this should eventually be handled with a tuner interface so that
1045 * one can switch the streams.
1048 add_sink (GstPlayBin * play_bin, GstElement * sink, GstPad * srcpad,
1049 GstPad * subtitle_pad)
1052 GstPadLinkReturn linkres;
1054 GstStateChangeReturn stateret;
1056 g_return_val_if_fail (sink != NULL, FALSE);
1057 /* this is only for debugging */
1058 parent = gst_pad_get_parent_element (srcpad);
1060 GST_DEBUG ("Adding sink with state %d (parent: %d, peer: %d)",
1061 GST_STATE (sink), GST_STATE (play_bin), GST_STATE (parent));
1062 gst_object_unref (parent);
1065 /* bring it to the PAUSED state so we can link to the peer without
1066 * breaking the flow */
1067 if ((stateret = gst_element_set_state (sink, GST_STATE_PAUSED)) ==
1068 GST_STATE_CHANGE_FAILURE)
1071 gst_bin_add (GST_BIN (play_bin), sink);
1073 /* we found a sink for this stream, now try to install it */
1074 sinkpad = gst_element_get_pad (sink, "sink");
1075 linkres = gst_pad_link (srcpad, sinkpad);
1076 gst_object_unref (sinkpad);
1078 /* try to link the pad of the sink to the stream */
1079 if (GST_PAD_LINK_FAILED (linkres))
1082 if (GST_IS_PAD (subtitle_pad)) {
1083 sinkpad = gst_element_get_pad (sink, "text_sink");
1084 linkres = gst_pad_link (subtitle_pad, sinkpad);
1085 gst_object_unref (sinkpad);
1088 /* try to link the subtitle pad of the sink to the stream */
1089 if (GST_PAD_LINK_FAILED (linkres)) {
1090 goto subtitle_failed;
1093 /* we got the sink succesfully linked, now keep the sink
1094 * in our internal list */
1095 play_bin->sinks = g_list_prepend (play_bin->sinks, sink);
1102 GST_DEBUG_OBJECT (play_bin, "state change failure when adding sink");
1110 /* could not link this stream */
1111 caps = gst_pad_get_caps (srcpad);
1112 capsstr = gst_caps_to_string (caps);
1113 g_warning ("could not link %s: %d", capsstr, linkres);
1114 GST_DEBUG_OBJECT (play_bin,
1115 "link failed when adding sink, caps %s, reason %d", capsstr, linkres);
1117 gst_caps_unref (caps);
1119 gst_element_set_state (sink, GST_STATE_NULL);
1120 gst_bin_remove (GST_BIN (play_bin), sink);
1128 /* could not link this stream */
1129 caps = gst_pad_get_caps (subtitle_pad);
1130 capsstr = gst_caps_to_string (caps);
1131 GST_DEBUG_OBJECT (play_bin,
1132 "subtitle link failed when adding sink, caps %s, reason %d", capsstr,
1135 gst_caps_unref (caps);
1142 setup_sinks (GstPlayBaseBin * play_base_bin, GstPlayBaseGroup * group)
1144 GstPlayBin *play_bin = GST_PLAY_BIN (play_base_bin);
1145 GList *streaminfo = NULL, *s;
1146 gboolean need_vis = FALSE;
1147 gboolean need_text = FALSE;
1148 GstPad *textsrcpad = NULL, *pad = NULL;
1150 gboolean res = TRUE;
1152 /* get rid of existing sinks */
1153 if (play_bin->sinks) {
1154 remove_sinks (play_bin);
1156 GST_DEBUG_OBJECT (play_base_bin, "setupsinks");
1158 /* find out what to do */
1159 if (group->type[GST_STREAM_TYPE_VIDEO - 1].npads > 0 &&
1160 group->type[GST_STREAM_TYPE_TEXT - 1].npads > 0) {
1162 } else if (group->type[GST_STREAM_TYPE_VIDEO - 1].npads == 0 &&
1163 group->type[GST_STREAM_TYPE_AUDIO - 1].npads > 0 &&
1164 play_bin->visualisation != NULL) {
1168 /* now actually connect everything */
1169 g_object_get (G_OBJECT (play_base_bin), "stream-info", &streaminfo, NULL);
1170 for (s = streaminfo; s; s = g_list_next (s)) {
1171 GObject *obj = G_OBJECT (s->data);
1175 g_object_get (obj, "type", &type, NULL);
1176 g_object_get (obj, "object", &object, NULL);
1180 if (group->type[GST_STREAM_TYPE_AUDIO - 1].npads > 0) {
1182 sink = gen_vis_element (play_bin);
1184 sink = gen_audio_element (play_bin);
1188 pad = gst_element_get_pad (group->type[GST_STREAM_TYPE_AUDIO - 1].preroll,
1190 res = add_sink (play_bin, sink, pad, NULL);
1191 gst_object_unref (pad);
1195 if (group->type[GST_STREAM_TYPE_VIDEO - 1].npads > 0) {
1197 GstObject *parent = NULL, *grandparent = NULL;
1198 GstPad *ghost = NULL;
1200 sink = gen_text_element (play_bin);
1202 gst_element_get_pad (group->type[GST_STREAM_TYPE_TEXT - 1].preroll,
1204 /* This pad is from subtitle-bin, we need to create a ghost pad to have
1205 common grandparents */
1206 parent = gst_object_get_parent (GST_OBJECT (textsrcpad));
1208 GST_WARNING_OBJECT (textsrcpad, "subtitle pad has no parent !");
1209 gst_object_unref (textsrcpad);
1214 grandparent = gst_object_get_parent (parent);
1216 GST_WARNING_OBJECT (textsrcpad, "subtitle pad has no grandparent !");
1217 gst_object_unref (parent);
1218 gst_object_unref (textsrcpad);
1223 /* We ghost the pad on subtitle_bin only, if the text pad is from the
1224 media demuxer we keep it as it is */
1225 if (!GST_IS_PLAY_BIN (grandparent)) {
1226 GST_DEBUG_OBJECT (textsrcpad, "this subtitle pad is from a subtitle "
1227 "file, ghosting to a suitable hierarchy");
1228 ghost = gst_ghost_pad_new ("text_src", textsrcpad);
1229 if (!GST_IS_PAD (ghost)) {
1230 GST_WARNING_OBJECT (textsrcpad, "failed creating ghost pad for "
1232 gst_object_unref (parent);
1233 gst_object_unref (grandparent);
1234 gst_object_unref (textsrcpad);
1239 if (gst_element_add_pad (GST_ELEMENT (grandparent), ghost)) {
1240 gst_object_unref (textsrcpad);
1241 textsrcpad = gst_object_ref (ghost);
1243 GST_WARNING_OBJECT (ghost, "failed adding ghost pad on subtitle-bin");
1244 gst_object_unref (ghost);
1245 gst_object_unref (textsrcpad);
1249 GST_DEBUG_OBJECT (textsrcpad, "this subtitle pad is from the demuxer "
1250 "no changes to hierarchy needed");
1253 gst_object_unref (parent);
1254 gst_object_unref (grandparent);
1256 sink = gen_video_element (play_bin);
1261 pad = gst_element_get_pad (group->type[GST_STREAM_TYPE_VIDEO - 1].preroll,
1263 res = add_sink (play_bin, sink, pad, textsrcpad);
1264 gst_object_unref (pad);
1266 gst_object_unref (textsrcpad);
1270 /* remove the sinks now, pipeline get_state will now wait for the
1271 * sinks to preroll */
1272 if (play_bin->fakesink) {
1273 gst_element_set_state (play_bin->fakesink, GST_STATE_NULL);
1274 gst_bin_remove (GST_BIN (play_bin), play_bin->fakesink);
1275 play_bin->fakesink = NULL;
1281 /* Send an event to our sinks until one of them works; don't then send to the
1282 * remaining sinks (unlike GstBin)
1285 gst_play_bin_send_event_to_sink (GstPlayBin * play_bin, GstEvent * event)
1287 GList *sinks = play_bin->sinks;
1288 gboolean res = TRUE;
1291 GstElement *sink = GST_ELEMENT_CAST (sinks->data);
1293 gst_event_ref (event);
1294 if ((res = gst_element_send_event (sink, event))) {
1295 GST_DEBUG_OBJECT (play_bin,
1296 "Sent event succesfully to sink %" GST_PTR_FORMAT, sink);
1299 GST_DEBUG_OBJECT (play_bin,
1300 "Event failed when sent to sink %" GST_PTR_FORMAT, sink);
1302 sinks = g_list_next (sinks);
1305 gst_event_unref (event);
1311 do_playbin_seek (GstElement * element, GstEvent * event)
1316 gboolean was_playing = FALSE;
1319 gst_event_parse_seek (event, &rate, NULL, &flags, NULL, NULL, NULL, NULL);
1321 flush = flags & GST_SEEK_FLAG_FLUSH;
1326 /* need to call _get_state() since a bin state is only updated
1327 * with this call. */
1328 gst_element_get_state (element, &state, NULL, 0);
1329 was_playing = state == GST_STATE_PLAYING;
1332 gst_element_set_state (element, GST_STATE_PAUSED);
1333 gst_element_get_state (element, NULL, NULL, 50 * GST_MSECOND);
1337 GST_DEBUG_OBJECT (element, "Sending seek event to a sink");
1338 res = gst_play_bin_send_event_to_sink (GST_PLAY_BIN (element), event);
1341 /* need to reset the stream time to 0 after a flushing seek */
1343 gst_pipeline_set_new_stream_time (GST_PIPELINE (element), 0);
1346 /* and continue playing */
1347 gst_element_set_state (element, GST_STATE_PLAYING);
1352 /* We only want to send the event to a single sink (overriding GstBin's
1353 * behaviour), but we want to keep GstPipeline's behaviour - wrapping seek
1354 * events appropriately. So, this is a messy duplication of code. */
1356 gst_play_bin_send_event (GstElement * element, GstEvent * event)
1358 gboolean res = FALSE;
1359 GstEventType event_type = GST_EVENT_TYPE (event);
1362 switch (event_type) {
1363 case GST_EVENT_SEEK:
1364 res = do_playbin_seek (element, event);
1367 res = gst_play_bin_send_event_to_sink (GST_PLAY_BIN (element), event);
1375 value_list_append_structure_list (GValue * list_val, GstStructure ** first,
1376 GList * structure_list)
1380 for (l = structure_list; l != NULL; l = l->next) {
1381 GValue val = { 0, };
1384 *first = gst_structure_copy ((GstStructure *) l->data);
1386 g_value_init (&val, GST_TYPE_STRUCTURE);
1387 g_value_take_boxed (&val, gst_structure_copy ((GstStructure *) l->data));
1388 gst_value_list_append_value (list_val, &val);
1389 g_value_unset (&val);
1393 /* if it's a redirect message with multiple redirect locations we might
1394 * want to pick a different 'best' location depending on the required
1395 * bitrates and the connection speed */
1397 gst_play_bin_handle_redirect_message (GstPlayBin * playbin, GstMessage * msg)
1399 const GValue *locations_list, *location_val;
1400 GstMessage *new_msg;
1401 GstStructure *new_structure = NULL;
1402 GList *l_good = NULL, *l_neutral = NULL, *l_bad = NULL;
1403 GValue new_list = { 0, };
1406 GST_DEBUG_OBJECT (playbin, "redirect message: %" GST_PTR_FORMAT, msg);
1407 GST_DEBUG_OBJECT (playbin, "connection speed: %u", playbin->connection_speed);
1409 if (playbin->connection_speed == 0 || msg->structure == NULL)
1412 locations_list = gst_structure_get_value (msg->structure, "locations");
1413 if (locations_list == NULL)
1416 size = gst_value_list_get_size (locations_list);
1420 /* maintain existing order as much as possible, just sort references
1421 * with too high a bitrate to the end (the assumption being that if
1422 * bitrates are given they are given for all interesting streams and
1423 * that the you-need-at-least-version-xyz redirect has the same bitrate
1424 * as the lowest referenced redirect alternative) */
1425 for (i = 0; i < size; ++i) {
1426 const GstStructure *s;
1429 location_val = gst_value_list_get_value (locations_list, i);
1430 s = (const GstStructure *) g_value_get_boxed (location_val);
1431 if (!gst_structure_get_int (s, "minimum-bitrate", &bitrate) || bitrate <= 0) {
1432 GST_DEBUG_OBJECT (playbin, "no bitrate: %" GST_PTR_FORMAT, s);
1433 l_neutral = g_list_append (l_neutral, (gpointer) s);
1434 } else if (bitrate > playbin->connection_speed) {
1435 GST_DEBUG_OBJECT (playbin, "bitrate too high: %" GST_PTR_FORMAT, s);
1436 l_bad = g_list_append (l_bad, (gpointer) s);
1437 } else if (bitrate <= playbin->connection_speed) {
1438 GST_DEBUG_OBJECT (playbin, "bitrate OK: %" GST_PTR_FORMAT, s);
1439 l_good = g_list_append (l_good, (gpointer) s);
1443 g_value_init (&new_list, GST_TYPE_LIST);
1444 value_list_append_structure_list (&new_list, &new_structure, l_good);
1445 value_list_append_structure_list (&new_list, &new_structure, l_neutral);
1446 value_list_append_structure_list (&new_list, &new_structure, l_bad);
1447 gst_structure_set_value (new_structure, "locations", &new_list);
1448 g_value_unset (&new_list);
1450 g_list_free (l_good);
1451 g_list_free (l_neutral);
1452 g_list_free (l_bad);
1454 new_msg = gst_message_new_element (msg->src, new_structure);
1455 gst_message_unref (msg);
1457 GST_DEBUG_OBJECT (playbin, "new redirect message: %" GST_PTR_FORMAT, new_msg);
1462 gst_play_bin_handle_message (GstBin * bin, GstMessage * msg)
1464 if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_ELEMENT && msg->structure != NULL
1465 && gst_structure_has_name (msg->structure, "redirect")) {
1466 msg = gst_play_bin_handle_redirect_message (GST_PLAY_BIN (bin), msg);
1469 GST_BIN_CLASS (parent_class)->handle_message (bin, msg);
1472 static GstStateChangeReturn
1473 gst_play_bin_change_state (GstElement * element, GstStateChange transition)
1475 GstStateChangeReturn ret;
1476 GstPlayBin *play_bin;
1478 play_bin = GST_PLAY_BIN (element);
1481 switch (transition) {
1482 case GST_STATE_CHANGE_READY_TO_PAUSED:
1483 /* this really is the easiest way to make the state change return
1484 * ASYNC until we added the sinks */
1485 if (!play_bin->fakesink) {
1486 play_bin->fakesink = gst_element_factory_make ("fakesink", "test");
1487 gst_bin_add (GST_BIN (play_bin), play_bin->fakesink);
1494 ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
1495 if (ret == GST_STATE_CHANGE_FAILURE)
1498 switch (transition) {
1499 case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
1500 /* Set audio sink state to NULL to release the sound device,
1501 * but only if we own it (else we might be in chain-transition). */
1502 //if (play_bin->audio_sink != NULL &&
1503 // GST_STATE (play_bin->audio_sink) == GST_STATE_PAUSED) {
1504 // gst_element_set_state (play_bin->audio_sink, GST_STATE_NULL);
1507 case GST_STATE_CHANGE_PAUSED_TO_READY:
1508 /* Check for NULL because the state transition may be done by
1509 * gst_bin_dispose which is called by gst_play_bin_dispose, and in that
1510 * case, we don't want to run remove_sinks.
1511 * FIXME: should the NULL test be done in remove_sinks? Should we just
1512 * set the state to NULL in gst_play_bin_dispose?
1514 if (play_bin->cache != NULL) {
1515 remove_sinks (play_bin);
1517 if (play_bin->fakesink) {
1518 gst_element_set_state (play_bin->fakesink, GST_STATE_NULL);
1519 gst_bin_remove (GST_BIN (play_bin), play_bin->fakesink);
1520 play_bin->fakesink = NULL;
1531 plugin_init (GstPlugin * plugin)
1533 GST_DEBUG_CATEGORY_INIT (gst_play_bin_debug, "playbin", 0, "play bin");
1536 GST_DEBUG ("binding text domain %s to locale dir %s", GETTEXT_PACKAGE,
1538 bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
1539 #endif /* ENABLE_NLS */
1541 return gst_element_register (plugin, "playbin", GST_RANK_NONE,
1545 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
1548 "player bin", plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME,