1 /* GStreamer Editing Services
2 * Copyright (C) 2009 Edward Hervey <edward.hervey@collabora.co.uk>
3 * 2009 Nokia Corporation
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Library General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Library General Public License for more details.
15 * You should have received a copy of the GNU Library General Public
16 * License along with this library; if not, write to the
17 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18 * Boston, MA 02111-1307, USA.
22 * SECTION:ges-timeline-pipeline
23 * @short_description: Convenience #GstPipeline for editing.
25 * #GESTimelinePipeline allows developers to view and render #GESTimeline
26 * in a simple fashion.
27 * Its usage is inspired by the 'playbin' element from gst-plugins-base.
32 #include "ges-internal.h"
33 #include "ges-timeline-pipeline.h"
34 #include "ges-screenshot.h"
36 #define DEFAULT_TIMELINE_MODE TIMELINE_MODE_PREVIEW
38 /* Structure corresponding to a timeline - sink link */
44 GstPad *srcpad; /* Timeline source pad */
50 G_DEFINE_TYPE (GESTimelinePipeline, ges_timeline_pipeline, GST_TYPE_PIPELINE);
52 struct _GESTimelinePipelinePrivate
54 GESTimeline *timeline;
56 GstElement *encodebin;
57 /* Note : urisink is only created when a URI has been provided */
60 GESPipelineFlags mode;
64 GstEncodingProfile *profile;
67 static GstStateChangeReturn ges_timeline_pipeline_change_state (GstElement *
68 element, GstStateChange transition);
70 static OutputChain *get_output_chain_for_track (GESTimelinePipeline * self,
72 static OutputChain *new_output_chain_for_track (GESTimelinePipeline * self,
74 static gboolean play_sink_multiple_seeks_send_event (GstElement * element,
78 ges_timeline_pipeline_dispose (GObject * object)
80 GESTimelinePipeline *self = GES_TIMELINE_PIPELINE (object);
82 if (self->priv->playsink) {
83 if (self->priv->mode & (TIMELINE_MODE_PREVIEW))
84 gst_bin_remove (GST_BIN (object), self->priv->playsink);
86 gst_object_unref (self->priv->playsink);
87 self->priv->playsink = NULL;
90 if (self->priv->encodebin) {
91 if (self->priv->mode & (TIMELINE_MODE_RENDER | TIMELINE_MODE_SMART_RENDER))
92 gst_bin_remove (GST_BIN (object), self->priv->encodebin);
94 gst_object_unref (self->priv->encodebin);
95 self->priv->encodebin = NULL;
98 if (self->priv->profile) {
99 gst_encoding_profile_unref (self->priv->profile);
100 self->priv->profile = NULL;
103 G_OBJECT_CLASS (ges_timeline_pipeline_parent_class)->dispose (object);
107 ges_timeline_pipeline_class_init (GESTimelinePipelineClass * klass)
109 GObjectClass *object_class = G_OBJECT_CLASS (klass);
110 GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
112 g_type_class_add_private (klass, sizeof (GESTimelinePipelinePrivate));
114 object_class->dispose = ges_timeline_pipeline_dispose;
116 element_class->change_state =
117 GST_DEBUG_FUNCPTR (ges_timeline_pipeline_change_state);
119 /* TODO : Add state_change handlers
120 * Don't change state if we don't have a timeline */
124 ges_timeline_pipeline_init (GESTimelinePipeline * self)
126 GstElementClass *playsinkclass;
128 GST_INFO_OBJECT (self, "Creating new 'playsink'");
129 self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
130 GES_TYPE_TIMELINE_PIPELINE, GESTimelinePipelinePrivate);
132 self->priv->playsink =
133 gst_element_factory_make ("playsink", "internal-sinks");
134 self->priv->encodebin =
135 gst_element_factory_make ("encodebin", "internal-encodebin");
136 /* Limit encodebin buffering to 1 buffer since we know the various
137 * stream fed to it are decoupled already */
138 g_object_set (self->priv->encodebin, "queue-buffers-max", (guint32) 1,
139 "queue-bytes-max", (guint32) 0, "queue-time-max", (guint64) 0,
140 "avoid-reencoding", TRUE, NULL);
142 if (G_UNLIKELY (self->priv->playsink == NULL))
144 if (G_UNLIKELY (self->priv->encodebin == NULL))
147 /* TODO : Remove this hack once we depend on gst-p-base 0.10.37 */
148 /* HACK : Intercept events going through playsink */
149 playsinkclass = GST_ELEMENT_GET_CLASS (self->priv->playsink);
150 /* Replace playsink's GstBin::send_event with our own */
151 playsinkclass->send_event = play_sink_multiple_seeks_send_event;
153 ges_timeline_pipeline_set_mode (self, DEFAULT_TIMELINE_MODE);
159 GST_ERROR_OBJECT (self, "Can't create playsink instance !");
164 GST_ERROR_OBJECT (self, "Can't create encodebin instance !");
170 * ges_timeline_pipeline_new:
172 * Creates a new conveninence #GESTimelinePipeline.
174 * Returns: the new #GESTimelinePipeline.
176 GESTimelinePipeline *
177 ges_timeline_pipeline_new (void)
179 return g_object_new (GES_TYPE_TIMELINE_PIPELINE, NULL);
182 #define TRACK_COMPATIBLE_PROFILE(tracktype, profile) \
183 ( (GST_IS_ENCODING_AUDIO_PROFILE (profile) && (tracktype) == GES_TRACK_TYPE_AUDIO) || \
184 (GST_IS_ENCODING_VIDEO_PROFILE (profile) && (tracktype) == GES_TRACK_TYPE_VIDEO))
187 ges_timeline_pipeline_update_caps (GESTimelinePipeline * self)
189 GList *ltrack, *tracks, *lstream;
191 if (!self->priv->profile)
194 GST_DEBUG ("Updating track caps");
196 tracks = ges_timeline_get_tracks (self->priv->timeline);
198 /* Take each stream of the encoding profile and find a matching
199 * track to set the caps on */
200 for (ltrack = tracks; ltrack; ltrack = ltrack->next) {
201 GESTrack *track = (GESTrack *) ltrack->data;
204 allstreams = (GList *)
205 gst_encoding_container_profile_get_profiles (
206 (GstEncodingContainerProfile *) self->priv->profile);
208 /* Find a matching stream setting */
209 for (lstream = allstreams; lstream; lstream = lstream->next) {
210 GstEncodingProfile *prof = (GstEncodingProfile *) lstream->data;
212 if (TRACK_COMPATIBLE_PROFILE (track->type, prof)) {
213 if (self->priv->mode == TIMELINE_MODE_SMART_RENDER) {
214 GstCaps *ocaps, *rcaps;
216 GST_DEBUG ("Smart Render mode, setting input caps");
217 ocaps = gst_encoding_profile_get_input_caps (prof);
218 if (track->type == GES_TRACK_TYPE_AUDIO)
219 rcaps = gst_caps_from_string ("audio/x-raw-int;audio/x-raw-float");
221 rcaps = gst_caps_from_string ("video/x-raw-yuv;video/x-raw-rgb");
222 gst_caps_append (ocaps, rcaps);
223 ges_track_set_caps (track, ocaps);
225 GstCaps *caps = NULL;
227 /* Raw preview or rendering mode */
228 if (track->type == GES_TRACK_TYPE_VIDEO)
229 caps = gst_caps_from_string ("video/x-raw-yuv;video/x-raw-rgb");
230 else if (track->type == GES_TRACK_TYPE_AUDIO)
231 gst_caps_from_string ("audio/x-raw-int;audio/x-raw-float");
234 ges_track_set_caps (track, caps);
235 gst_caps_unref (caps);
242 g_object_unref (track);
246 g_list_free (tracks);
248 GST_DEBUG ("Done updating caps");
253 static GstStateChangeReturn
254 ges_timeline_pipeline_change_state (GstElement * element,
255 GstStateChange transition)
257 GESTimelinePipeline *self;
258 GstStateChangeReturn ret;
260 self = GES_TIMELINE_PIPELINE (element);
262 switch (transition) {
263 case GST_STATE_CHANGE_READY_TO_PAUSED:
264 if (G_UNLIKELY (self->priv->timeline == NULL)) {
265 GST_ERROR_OBJECT (element,
266 "No GESTimeline set on the pipeline, cannot play !");
267 ret = GST_STATE_CHANGE_FAILURE;
271 priv->mode & (TIMELINE_MODE_RENDER | TIMELINE_MODE_SMART_RENDER))
272 GST_DEBUG ("rendering => Updating pipeline caps");
273 if (!ges_timeline_pipeline_update_caps (self)) {
274 GST_ERROR_OBJECT (element, "Error setting the caps for rendering");
275 ret = GST_STATE_CHANGE_FAILURE;
278 /* Set caps on all tracks according to profile if present */
279 /* FIXME : Add a new SMART_RENDER mode to avoid decoding */
286 GST_ELEMENT_CLASS (ges_timeline_pipeline_parent_class)->change_state
287 (element, transition);
294 new_output_chain_for_track (GESTimelinePipeline * self, GESTrack * track)
298 chain = g_new0 (OutputChain, 1);
299 chain->track = track;
305 get_output_chain_for_track (GESTimelinePipeline * self, GESTrack * track)
309 for (tmp = self->priv->chains; tmp; tmp = tmp->next) {
310 OutputChain *chain = (OutputChain *) tmp->data;
311 if (chain->track == track)
318 /* Fetches a compatible pad on the target element which isn't already
321 get_compatible_unlinked_pad (GstElement * element, GstPad * pad)
325 gboolean done = FALSE;
328 if (G_UNLIKELY (pad == NULL))
331 GST_DEBUG ("element : %s, pad %s:%s",
332 GST_ELEMENT_NAME (element), GST_DEBUG_PAD_NAME (pad));
334 if (GST_PAD_DIRECTION (pad) == GST_PAD_SRC)
335 pads = gst_element_iterate_sink_pads (element);
337 pads = gst_element_iterate_src_pads (element);
338 srccaps = gst_pad_get_caps_reffed (pad);
340 GST_DEBUG ("srccaps %" GST_PTR_FORMAT, srccaps);
345 switch (gst_iterator_next (pads, &padptr)) {
346 case GST_ITERATOR_OK:
348 GstPad *testpad = (GstPad *) padptr;
350 if (gst_pad_is_linked (testpad)) {
351 gst_object_unref (testpad);
353 GstCaps *sinkcaps = gst_pad_get_caps_reffed (testpad);
355 GST_DEBUG ("sinkccaps %" GST_PTR_FORMAT, sinkcaps);
357 if (gst_caps_can_intersect (srccaps, sinkcaps)) {
361 gst_object_unref (testpad);
362 gst_caps_unref (sinkcaps);
366 case GST_ITERATOR_DONE:
367 case GST_ITERATOR_ERROR:
370 case GST_ITERATOR_RESYNC:
371 gst_iterator_resync (pads);
375 gst_iterator_free (pads);
376 gst_caps_unref (srccaps);
382 GST_ERROR ("No pad to check against");
388 pad_blocked (GstPad * pad, gboolean blocked, gpointer user_data)
391 GST_DEBUG_OBJECT (pad, "blocked callback, blocked: %d", blocked);
395 pad_added_cb (GstElement * timeline, GstPad * pad, GESTimelinePipeline * self)
400 gboolean reconfigured = FALSE;
402 GST_DEBUG_OBJECT (self, "new pad %s:%s , caps:%" GST_PTR_FORMAT,
403 GST_DEBUG_PAD_NAME (pad), GST_PAD_CAPS (pad));
405 if (G_UNLIKELY (!(track =
406 ges_timeline_get_track_for_pad (self->priv->timeline, pad)))) {
407 GST_WARNING_OBJECT (self, "Couldn't find coresponding track !");
411 /* Don't connect track if it's not going to be used */
412 if (track->type == GES_TRACK_TYPE_VIDEO &&
413 !(self->priv->mode & TIMELINE_MODE_PREVIEW_VIDEO) &&
414 !(self->priv->mode & TIMELINE_MODE_RENDER) &&
415 !(self->priv->mode & TIMELINE_MODE_SMART_RENDER)) {
416 GST_DEBUG_OBJECT (self, "Video track... but we don't need it. Not linking");
418 if (track->type == GES_TRACK_TYPE_AUDIO &&
419 !(self->priv->mode & TIMELINE_MODE_PREVIEW_AUDIO) &&
420 !(self->priv->mode & TIMELINE_MODE_RENDER) &&
421 !(self->priv->mode & TIMELINE_MODE_SMART_RENDER)) {
422 GST_DEBUG_OBJECT (self, "Audio track... but we don't need it. Not linking");
425 /* Get an existing chain or create it */
426 if (!(chain = get_output_chain_for_track (self, track)))
427 chain = new_output_chain_for_track (self, track);
431 chain->tee = gst_element_factory_make ("tee", NULL);
432 gst_bin_add (GST_BIN_CAST (self), chain->tee);
433 gst_element_sync_state_with_parent (chain->tee);
435 /* Linking pad to tee */
436 sinkpad = gst_element_get_static_pad (chain->tee, "sink");
437 gst_pad_link_full (pad, sinkpad, GST_PAD_LINK_CHECK_NOTHING);
438 gst_object_unref (sinkpad);
440 /* Connect playsink */
441 if (self->priv->mode & TIMELINE_MODE_PREVIEW) {
442 const gchar *sinkpad_name;
445 GST_DEBUG_OBJECT (self, "Connecting to playsink");
447 switch (track->type) {
448 case GES_TRACK_TYPE_VIDEO:
449 sinkpad_name = "video_sink";
451 case GES_TRACK_TYPE_AUDIO:
452 sinkpad_name = "audio_sink";
454 case GES_TRACK_TYPE_TEXT:
455 sinkpad_name = "text_sink";
458 GST_WARNING_OBJECT (self, "Can't handle tracks of type %d yet",
463 /* Request a sinkpad from playsink */
464 if (G_UNLIKELY (!(sinkpad =
465 gst_element_get_request_pad (self->priv->playsink,
467 GST_ERROR_OBJECT (self, "Couldn't get a pad from the playsink !");
471 tmppad = gst_element_get_request_pad (chain->tee, "src%d");
472 if (G_UNLIKELY (gst_pad_link_full (tmppad, sinkpad,
473 GST_PAD_LINK_CHECK_NOTHING) != GST_PAD_LINK_OK)) {
474 GST_ERROR_OBJECT (self, "Couldn't link track pad to playsink");
475 gst_object_unref (tmppad);
478 chain->blocked_pad = tmppad;
479 GST_DEBUG ("blocking pad %" GST_PTR_FORMAT, tmppad);
480 gst_pad_set_blocked_async (tmppad, TRUE, pad_blocked, NULL);
482 GST_DEBUG ("Reconfiguring playsink");
484 /* reconfigure playsink */
485 g_signal_emit_by_name (self->priv->playsink, "reconfigure", &reconfigured);
486 GST_DEBUG ("'reconfigure' returned %d", reconfigured);
488 /* We still hold a reference on the sinkpad */
489 chain->playsinkpad = sinkpad;
492 /* Connect to encodebin */
493 if (self->priv->mode & (TIMELINE_MODE_RENDER | TIMELINE_MODE_SMART_RENDER)) {
495 GST_DEBUG_OBJECT (self, "Connecting to encodebin");
497 if (!chain->encodebinpad) {
498 /* Check for unused static pads */
499 sinkpad = get_compatible_unlinked_pad (self->priv->encodebin, pad);
501 if (sinkpad == NULL) {
502 GstCaps *caps = gst_pad_get_caps_reffed (pad);
503 /* If no compatible static pad is available, request a pad */
504 g_signal_emit_by_name (self->priv->encodebin, "request-pad", caps,
506 gst_caps_unref (caps);
507 if (G_UNLIKELY (sinkpad == NULL)) {
508 GST_ERROR_OBJECT (self, "Couldn't get a pad from encodebin !");
512 chain->encodebinpad = sinkpad;
515 tmppad = gst_element_get_request_pad (chain->tee, "src%d");
516 if (G_UNLIKELY (gst_pad_link_full (tmppad,
518 GST_PAD_LINK_CHECK_NOTHING) != GST_PAD_LINK_OK)) {
519 GST_WARNING_OBJECT (self, "Couldn't link track pad to playsink");
522 gst_object_unref (tmppad);
526 /* If chain wasn't already present, insert it in list */
527 if (!get_output_chain_for_track (self, track))
528 self->priv->chains = g_list_append (self->priv->chains, chain);
536 gst_bin_remove (GST_BIN_CAST (self), chain->tee);
539 gst_object_unref (sinkpad);
545 pad_removed_cb (GstElement * timeline, GstPad * pad, GESTimelinePipeline * self)
551 GST_DEBUG_OBJECT (self, "pad removed %s:%s", GST_DEBUG_PAD_NAME (pad));
553 if (G_UNLIKELY (!(track =
554 ges_timeline_get_track_for_pad (self->priv->timeline, pad)))) {
555 GST_WARNING_OBJECT (self, "Couldn't find coresponding track !");
559 if (G_UNLIKELY (!(chain = get_output_chain_for_track (self, track)))) {
560 GST_DEBUG_OBJECT (self, "Pad wasn't used");
564 /* Unlink encodebin */
565 if (chain->encodebinpad) {
566 peer = gst_pad_get_peer (chain->encodebinpad);
567 gst_pad_unlink (peer, chain->encodebinpad);
568 gst_object_unref (peer);
569 gst_element_release_request_pad (self->priv->encodebin,
570 chain->encodebinpad);
573 /* Unlink playsink */
574 if (chain->playsinkpad) {
575 peer = gst_pad_get_peer (chain->playsinkpad);
576 gst_pad_unlink (peer, chain->playsinkpad);
577 gst_object_unref (peer);
578 gst_element_release_request_pad (self->priv->playsink, chain->playsinkpad);
579 gst_object_unref (chain->playsinkpad);
582 if (chain->blocked_pad) {
583 GST_DEBUG ("unblocking pad %" GST_PTR_FORMAT, chain->blocked_pad);
584 gst_pad_set_blocked_async (chain->blocked_pad, FALSE, pad_blocked, NULL);
585 gst_object_unref (chain->blocked_pad);
586 chain->blocked_pad = NULL;
589 /* Unlike/remove tee */
590 peer = gst_element_get_static_pad (chain->tee, "sink");
591 gst_pad_unlink (pad, peer);
592 gst_object_unref (peer);
593 gst_element_set_state (chain->tee, GST_STATE_NULL);
594 gst_bin_remove (GST_BIN (self), chain->tee);
596 self->priv->chains = g_list_remove (self->priv->chains, chain);
603 no_more_pads_cb (GstElement * timeline, GESTimelinePipeline * self)
607 GST_DEBUG ("received no-more-pads");
608 for (tmp = self->priv->chains; tmp; tmp = g_list_next (tmp)) {
609 OutputChain *chain = (OutputChain *) tmp->data;
611 if (chain->blocked_pad) {
612 GST_DEBUG ("unblocking pad %" GST_PTR_FORMAT, chain->blocked_pad);
613 gst_pad_set_blocked_async (chain->blocked_pad, FALSE, pad_blocked, NULL);
619 * ges_timeline_pipeline_add_timeline:
620 * @pipeline: a #GESTimelinePipeline
621 * @timeline: the #GESTimeline to set on the @pipeline.
623 * Sets the timeline to use in this pipeline.
625 * The reference to the @timeline will be stolen by the @pipeline.
627 * Returns: TRUE if the @timeline could be successfully set on the @pipeline,
631 ges_timeline_pipeline_add_timeline (GESTimelinePipeline * pipeline,
632 GESTimeline * timeline)
634 g_return_val_if_fail (pipeline->priv->timeline == NULL, FALSE);
635 g_return_val_if_fail (timeline != NULL, FALSE);
637 GST_DEBUG ("pipeline:%p, timeline:%p", timeline, pipeline);
639 if (G_UNLIKELY (!gst_bin_add (GST_BIN_CAST (pipeline),
640 GST_ELEMENT (timeline)))) {
643 pipeline->priv->timeline = timeline;
645 /* Connect to pipeline */
646 g_signal_connect (timeline, "pad-added", (GCallback) pad_added_cb, pipeline);
647 g_signal_connect (timeline, "pad-removed", (GCallback) pad_removed_cb,
649 g_signal_connect (timeline, "no-more-pads", (GCallback) no_more_pads_cb,
656 * ges_timeline_pipeline_set_render_settings:
657 * @pipeline: a #GESTimelinePipeline
658 * @output_uri: the URI to which the timeline will be rendered
659 * @profile: the #GstEncodingProfile to use to render the timeline.
661 * Specify where the pipeline shall be rendered and with what settings.
663 * A copy of @profile and @output_uri will be done internally, the caller can
664 * safely free those values afterwards.
666 * This method must be called before setting the pipeline mode to
667 * #TIMELINE_MODE_RENDER
669 * Returns: %TRUE if the settings were aknowledged properly, else %FALSE
672 ges_timeline_pipeline_set_render_settings (GESTimelinePipeline * pipeline,
673 gchar * output_uri, GstEncodingProfile * profile)
675 /* Clear previous URI sink if it existed */
676 /* FIXME : We should figure out if it was added to the pipeline,
677 * and if so, remove it. */
678 if (pipeline->priv->urisink) {
679 g_object_unref (pipeline->priv->urisink);
680 pipeline->priv->urisink = NULL;
683 pipeline->priv->urisink =
684 gst_element_make_from_uri (GST_URI_SINK, output_uri, "urisink");
685 if (G_UNLIKELY (pipeline->priv->urisink == NULL)) {
686 GST_ERROR_OBJECT (pipeline, "Couldn't not create sink for URI %s",
691 if (pipeline->priv->profile)
692 gst_encoding_profile_unref (pipeline->priv->profile);
693 g_object_set (pipeline->priv->encodebin, "avoid-reencoding",
694 !(!(pipeline->priv->mode & TIMELINE_MODE_SMART_RENDER)), NULL);
695 g_object_set (pipeline->priv->encodebin, "profile", profile, NULL);
696 pipeline->priv->profile =
697 (GstEncodingProfile *) gst_encoding_profile_ref (profile);
703 * ges_timeline_pipeline_set_mode:
704 * @pipeline: a #GESTimelinePipeline
705 * @mode: the #GESPipelineFlags to use
707 * switches the @pipeline to the specified @mode. The default mode when
708 * creating a #GESTimelinePipeline is #TIMELINE_MODE_PREVIEW.
710 * Note: The @pipeline will be set to #GST_STATE_NULL during this call due to
711 * the internal changes that happen. The caller will therefore have to
712 * set the @pipeline to the requested state after calling this method.
714 * Returns: %TRUE if the mode was properly set, else %FALSE.
717 ges_timeline_pipeline_set_mode (GESTimelinePipeline * pipeline,
718 GESPipelineFlags mode)
720 GST_DEBUG_OBJECT (pipeline, "current mode : %d, mode : %d",
721 pipeline->priv->mode, mode);
723 /* fast-path, nothing to change */
724 if (mode == pipeline->priv->mode)
727 /* FIXME: It would be nice if we are only (de)activating preview
728 * modes to not set the whole pipeline to NULL, but instead just
729 * do the proper (un)linking to playsink. */
731 /* Switch pipeline to NULL since we're changing the configuration */
732 gst_element_set_state (GST_ELEMENT_CAST (pipeline), GST_STATE_NULL);
734 /* remove no-longer needed components */
735 if (pipeline->priv->mode & TIMELINE_MODE_PREVIEW &&
736 !(mode & TIMELINE_MODE_PREVIEW)) {
737 /* Disable playsink */
738 GST_DEBUG ("Disabling playsink");
739 g_object_ref (pipeline->priv->playsink);
740 gst_bin_remove (GST_BIN_CAST (pipeline), pipeline->priv->playsink);
742 if ((pipeline->priv->mode &
743 (TIMELINE_MODE_RENDER | TIMELINE_MODE_SMART_RENDER)) &&
744 !(mode & (TIMELINE_MODE_RENDER | TIMELINE_MODE_SMART_RENDER))) {
745 /* Disable render bin */
746 GST_DEBUG ("Disabling rendering bin");
747 g_object_ref (pipeline->priv->encodebin);
748 g_object_ref (pipeline->priv->urisink);
749 gst_bin_remove_many (GST_BIN_CAST (pipeline),
750 pipeline->priv->encodebin, pipeline->priv->urisink, NULL);
753 /* Add new elements */
754 if (!(pipeline->priv->mode & TIMELINE_MODE_PREVIEW) &&
755 (mode & TIMELINE_MODE_PREVIEW)) {
757 GST_DEBUG ("Adding playsink");
759 if (!gst_bin_add (GST_BIN_CAST (pipeline), pipeline->priv->playsink)) {
760 GST_ERROR_OBJECT (pipeline, "Couldn't add playsink");
764 if (!(pipeline->priv->mode &
765 (TIMELINE_MODE_RENDER | TIMELINE_MODE_SMART_RENDER)) &&
766 (mode & (TIMELINE_MODE_RENDER | TIMELINE_MODE_SMART_RENDER))) {
767 /* Adding render bin */
768 GST_DEBUG ("Adding render bin");
770 if (G_UNLIKELY (pipeline->priv->urisink == NULL)) {
771 GST_ERROR_OBJECT (pipeline, "Output URI not set !");
774 if (!gst_bin_add (GST_BIN_CAST (pipeline), pipeline->priv->encodebin)) {
775 GST_ERROR_OBJECT (pipeline, "Couldn't add encodebin");
778 if (!gst_bin_add (GST_BIN_CAST (pipeline), pipeline->priv->urisink)) {
779 GST_ERROR_OBJECT (pipeline, "Couldn't add URI sink");
782 g_object_set (pipeline->priv->encodebin, "avoid-reencoding",
783 !(!(mode & TIMELINE_MODE_SMART_RENDER)), NULL);
785 gst_element_link_pads_full (pipeline->priv->encodebin, "src",
786 pipeline->priv->urisink, "sink", GST_PAD_LINK_CHECK_NOTHING);
791 * If we are rendering, set playsink to sync=False,
792 * If we are NOT rendering, set playsink to sync=TRUE */
794 pipeline->priv->mode = mode;
800 * ges_timeline_pipeline_get_thumbnail_buffer:
801 * @self: a #GESTimelinePipeline in %GST_STATE_PLAYING or %GST_STATE_PAUSED
802 * @caps: (transfer none): caps specifying current format. Use %GST_CAPS_ANY
805 * Returns a #GstBuffer with the currently playing in the format specified by
806 * caps. The caller should unref the #gst_buffer_unref when finished. If ANY
807 * caps are specified, the information will be returned in the whatever format
808 * is currently used by the sink. This information can be retrieve from caps
809 * associated with the buffer.
811 * Returns: (transfer full): a #GstBuffer or %NULL
815 ges_timeline_pipeline_get_thumbnail_buffer (GESTimelinePipeline * self,
821 sink = self->priv->playsink;
823 GST_WARNING ("thumbnailing can only be done if we have a playsink");
827 buf = ges_play_sink_convert_frame (sink, caps);
833 * ges_timeline_pipeline_save_thumbnail:
834 * @self: a #GESTimelinePipeline in %GST_STATE_PLAYING or %GST_STATE_PAUSED
835 * @width: the requested width or -1 for native size
836 * @height: the requested height or -1 for native size
837 * @format: a string specifying the desired mime type (for example,
839 * @location: the path to save the thumbnail
841 * Saves the current frame to the specified @location.
843 * Returns: %TRUE if the thumbnail was properly save, else %FALSE.
847 ges_timeline_pipeline_save_thumbnail (GESTimelinePipeline * self, int width, int
848 height, const gchar * format, const gchar * location)
855 caps = gst_caps_from_string (format);
858 gst_caps_set_simple (caps, "width", G_TYPE_INT, width, NULL);
861 gst_caps_set_simple (caps, "height", G_TYPE_INT, height, NULL);
863 if (!(b = ges_timeline_pipeline_get_thumbnail_buffer (self, caps))) {
864 gst_caps_unref (caps);
868 /* FIXME : Use standard glib methods */
869 fp = fopen (location, "w+");
870 if (!fwrite (GST_BUFFER_DATA (b), GST_BUFFER_SIZE (b), 1, fp) || ferror (fp)) {
875 gst_caps_unref (caps);
876 gst_buffer_unref (b);
881 * ges_timeline_pipeline_get_thumbnail_rgb24:
882 * @self: a #GESTimelinePipeline in %GST_STATE_PLAYING or %GST_STATE_PAUSED
883 * @width: the requested width or -1 for native size
884 * @height: the requested height or -1 for native size
886 * A convenience method for ges_timeline_pipeline_get_thumbnail_raw which
887 * returns a buffer in 24-bit RGB, optionally scaled to the specified width
888 * and height. If -1 is specified for either dimension, it will be left at
889 * native size. You can retreive this information from the caps associated
892 * The caller is responsible for unreffing the returned buffer with
895 * Returns: (transfer full): a #GstBuffer or %NULL
899 ges_timeline_pipeline_get_thumbnail_rgb24 (GESTimelinePipeline * self,
900 gint width, gint height)
905 caps = gst_caps_from_string ("video/x-raw-rgb,bpp=(int)24," "depth=(int)24");
908 gst_caps_set_simple (caps, "width", G_TYPE_INT, (gint) width, NULL);
911 gst_caps_set_simple (caps, "height", G_TYPE_INT, (gint) height, NULL);
913 ret = ges_timeline_pipeline_get_thumbnail_buffer (self, caps);
914 gst_caps_unref (caps);
919 * ges_timeline_pipeline_preview_get_video_sink:
920 * @self: a #GESTimelinePipeline
922 * Obtains a pointer to playsink's video sink element that is used for
923 * displaying video when the #GESTimelinePipeline is in %TIMELINE_MODE_PREVIEW
925 * The caller is responsible for unreffing the returned element with
928 * Returns: (transfer full): a pointer to the playsink video sink #GstElement
931 ges_timeline_pipeline_preview_get_video_sink (GESTimelinePipeline * self)
935 g_object_get (self->priv->playsink, "video-sink", &sink, NULL);
941 * ges_timeline_pipeline_preview_set_video_sink:
942 * @self: a #GESTimelinePipeline in %GST_STATE_NULL
943 * @sink: (transfer none): a video sink #GstElement
945 * Sets playsink's video sink element that is used for displaying video when
946 * the #GESTimelinePipeline is in %TIMELINE_MODE_PREVIEW
949 ges_timeline_pipeline_preview_set_video_sink (GESTimelinePipeline * self,
952 g_object_set (self->priv->playsink, "video-sink", sink, NULL);
956 * ges_timeline_pipeline_preview_get_audio_sink:
957 * @self: a #GESTimelinePipeline
959 * Obtains a pointer to playsink's audio sink element that is used for
960 * displaying audio when the #GESTimelinePipeline is in %TIMELINE_MODE_PREVIEW
962 * The caller is responsible for unreffing the returned element with
965 * Returns: (transfer full): a pointer to the playsink audio sink #GstElement
968 ges_timeline_pipeline_preview_get_audio_sink (GESTimelinePipeline * self)
972 g_object_get (self->priv->playsink, "audio-sink", &sink, NULL);
978 * ges_timeline_pipeline_preview_set_audio_sink:
979 * @self: a #GESTimelinePipeline in %GST_STATE_NULL
980 * @sink: (transfer none): a audio sink #GstElement
982 * Sets playsink's audio sink element that is used for displaying audio when
983 * the #GESTimelinePipeline is in %TIMELINE_MODE_PREVIEW
986 ges_timeline_pipeline_preview_set_audio_sink (GESTimelinePipeline * self,
989 g_object_set (self->priv->playsink, "audio-sink", sink, NULL);
994 play_sink_multiple_seeks_send_event (GstElement * element, GstEvent * event)
996 GstElementClass *klass = GST_ELEMENT_GET_CLASS (element);
998 GST_DEBUG ("%s", GST_EVENT_TYPE_NAME (event));
1001 GST_ELEMENT_CLASS (g_type_class_peek_parent (klass))->send_event (element,