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 */
51 G_DEFINE_TYPE (GESTimelinePipeline, ges_timeline_pipeline, GST_TYPE_PIPELINE);
53 struct _GESTimelinePipelinePrivate
55 GESTimeline *timeline;
57 GstElement *encodebin;
58 /* Note : urisink is only created when a URI has been provided */
61 GESPipelineFlags mode;
65 GstEncodingProfile *profile;
68 static GstStateChangeReturn ges_timeline_pipeline_change_state (GstElement *
69 element, GstStateChange transition);
71 static OutputChain *get_output_chain_for_track (GESTimelinePipeline * self,
73 static OutputChain *new_output_chain_for_track (GESTimelinePipeline * self,
75 static gboolean play_sink_multiple_seeks_send_event (GstElement * element,
79 ges_timeline_pipeline_dispose (GObject * object)
81 GESTimelinePipeline *self = GES_TIMELINE_PIPELINE (object);
83 if (self->priv->playsink) {
84 if (self->priv->mode & (TIMELINE_MODE_PREVIEW))
85 gst_bin_remove (GST_BIN (object), self->priv->playsink);
87 gst_object_unref (self->priv->playsink);
88 self->priv->playsink = NULL;
91 if (self->priv->encodebin) {
92 if (self->priv->mode & (TIMELINE_MODE_RENDER | TIMELINE_MODE_SMART_RENDER))
93 gst_bin_remove (GST_BIN (object), self->priv->encodebin);
95 gst_object_unref (self->priv->encodebin);
96 self->priv->encodebin = NULL;
99 if (self->priv->profile) {
100 gst_encoding_profile_unref (self->priv->profile);
101 self->priv->profile = NULL;
104 G_OBJECT_CLASS (ges_timeline_pipeline_parent_class)->dispose (object);
108 ges_timeline_pipeline_class_init (GESTimelinePipelineClass * klass)
110 GObjectClass *object_class = G_OBJECT_CLASS (klass);
111 GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
113 g_type_class_add_private (klass, sizeof (GESTimelinePipelinePrivate));
115 object_class->dispose = ges_timeline_pipeline_dispose;
117 element_class->change_state =
118 GST_DEBUG_FUNCPTR (ges_timeline_pipeline_change_state);
120 /* TODO : Add state_change handlers
121 * Don't change state if we don't have a timeline */
125 ges_timeline_pipeline_init (GESTimelinePipeline * self)
127 GstElementClass *playsinkclass;
129 GST_INFO_OBJECT (self, "Creating new 'playsink'");
130 self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
131 GES_TYPE_TIMELINE_PIPELINE, GESTimelinePipelinePrivate);
133 self->priv->playsink =
134 gst_element_factory_make ("playsink", "internal-sinks");
135 self->priv->encodebin =
136 gst_element_factory_make ("encodebin", "internal-encodebin");
137 /* Limit encodebin buffering to 1 buffer since we know the various
138 * stream fed to it are decoupled already */
139 g_object_set (self->priv->encodebin, "queue-buffers-max", (guint32) 1,
140 "queue-bytes-max", (guint32) 0, "queue-time-max", (guint64) 0,
141 "avoid-reencoding", TRUE, NULL);
143 if (G_UNLIKELY (self->priv->playsink == NULL))
145 if (G_UNLIKELY (self->priv->encodebin == NULL))
148 /* TODO : Remove this hack once we depend on gst-p-base 0.10.37 */
149 /* HACK : Intercept events going through playsink */
150 playsinkclass = GST_ELEMENT_GET_CLASS (self->priv->playsink);
151 /* Replace playsink's GstBin::send_event with our own */
152 playsinkclass->send_event = play_sink_multiple_seeks_send_event;
154 ges_timeline_pipeline_set_mode (self, DEFAULT_TIMELINE_MODE);
160 GST_ERROR_OBJECT (self, "Can't create playsink instance !");
165 GST_ERROR_OBJECT (self, "Can't create encodebin instance !");
171 * ges_timeline_pipeline_new:
173 * Creates a new conveninence #GESTimelinePipeline.
175 * Returns: the new #GESTimelinePipeline.
177 GESTimelinePipeline *
178 ges_timeline_pipeline_new (void)
180 return g_object_new (GES_TYPE_TIMELINE_PIPELINE, NULL);
183 #define TRACK_COMPATIBLE_PROFILE(tracktype, profile) \
184 ( (GST_IS_ENCODING_AUDIO_PROFILE (profile) && (tracktype) == GES_TRACK_TYPE_AUDIO) || \
185 (GST_IS_ENCODING_VIDEO_PROFILE (profile) && (tracktype) == GES_TRACK_TYPE_VIDEO))
188 ges_timeline_pipeline_update_caps (GESTimelinePipeline * self)
190 GList *ltrack, *tracks, *lstream;
192 if (!self->priv->profile)
195 GST_DEBUG ("Updating track caps");
197 tracks = ges_timeline_get_tracks (self->priv->timeline);
199 /* Take each stream of the encoding profile and find a matching
200 * track to set the caps on */
201 for (ltrack = tracks; ltrack; ltrack = ltrack->next) {
202 GESTrack *track = (GESTrack *) ltrack->data;
205 allstreams = (GList *)
206 gst_encoding_container_profile_get_profiles (
207 (GstEncodingContainerProfile *) self->priv->profile);
209 /* Find a matching stream setting */
210 for (lstream = allstreams; lstream; lstream = lstream->next) {
211 GstEncodingProfile *prof = (GstEncodingProfile *) lstream->data;
213 if (TRACK_COMPATIBLE_PROFILE (track->type, prof)) {
214 if (self->priv->mode == TIMELINE_MODE_SMART_RENDER) {
215 GstCaps *ocaps, *rcaps;
217 GST_DEBUG ("Smart Render mode, setting input caps");
218 ocaps = gst_encoding_profile_get_input_caps (prof);
219 if (track->type == GES_TRACK_TYPE_AUDIO)
220 rcaps = gst_caps_new_empty_simple ("audio/x-raw");
222 rcaps = gst_caps_new_empty_simple ("video/x-raw");
223 gst_caps_append (ocaps, rcaps);
224 ges_track_set_caps (track, ocaps);
226 GstCaps *caps = NULL;
228 /* Raw preview or rendering mode */
229 if (track->type == GES_TRACK_TYPE_VIDEO)
230 caps = gst_caps_new_empty_simple ("video/x-raw");
231 else if (track->type == GES_TRACK_TYPE_AUDIO)
232 caps = gst_caps_new_empty_simple ("audio/x-raw");
235 ges_track_set_caps (track, caps);
236 gst_caps_unref (caps);
243 g_object_unref (track);
247 g_list_free (tracks);
249 GST_DEBUG ("Done updating caps");
254 static GstStateChangeReturn
255 ges_timeline_pipeline_change_state (GstElement * element,
256 GstStateChange transition)
258 GESTimelinePipeline *self;
259 GstStateChangeReturn ret;
261 self = GES_TIMELINE_PIPELINE (element);
263 switch (transition) {
264 case GST_STATE_CHANGE_READY_TO_PAUSED:
265 if (G_UNLIKELY (self->priv->timeline == NULL)) {
266 GST_ERROR_OBJECT (element,
267 "No GESTimeline set on the pipeline, cannot play !");
268 ret = GST_STATE_CHANGE_FAILURE;
272 mode & (TIMELINE_MODE_RENDER | TIMELINE_MODE_SMART_RENDER))
273 GST_DEBUG ("rendering => Updating pipeline caps");
274 if (!ges_timeline_pipeline_update_caps (self)) {
275 GST_ERROR_OBJECT (element, "Error setting the caps for rendering");
276 ret = GST_STATE_CHANGE_FAILURE;
279 /* Set caps on all tracks according to profile if present */
280 /* FIXME : Add a new SMART_RENDER mode to avoid decoding */
287 GST_ELEMENT_CLASS (ges_timeline_pipeline_parent_class)->change_state
288 (element, transition);
295 new_output_chain_for_track (GESTimelinePipeline * self, GESTrack * track)
299 chain = g_new0 (OutputChain, 1);
300 chain->track = track;
306 get_output_chain_for_track (GESTimelinePipeline * self, GESTrack * track)
310 for (tmp = self->priv->chains; tmp; tmp = tmp->next) {
311 OutputChain *chain = (OutputChain *) tmp->data;
312 if (chain->track == track)
319 /* Fetches a compatible pad on the target element which isn't already
322 get_compatible_unlinked_pad (GstElement * element, GstPad * pad)
326 gboolean done = FALSE;
328 GValue paditem = { 0, };
330 if (G_UNLIKELY (pad == NULL))
333 GST_DEBUG ("element : %s, pad %s:%s",
334 GST_ELEMENT_NAME (element), GST_DEBUG_PAD_NAME (pad));
336 if (GST_PAD_DIRECTION (pad) == GST_PAD_SRC)
337 pads = gst_element_iterate_sink_pads (element);
339 pads = gst_element_iterate_src_pads (element);
340 srccaps = gst_pad_query_caps (pad, NULL);
342 GST_DEBUG ("srccaps %" GST_PTR_FORMAT, srccaps);
345 switch (gst_iterator_next (pads, &paditem)) {
346 case GST_ITERATOR_OK:
348 GstPad *testpad = g_value_get_object (&paditem);
350 if (!gst_pad_is_linked (testpad)) {
351 GstCaps *sinkcaps = gst_pad_query_caps (testpad, NULL);
353 GST_DEBUG ("sinkccaps %" GST_PTR_FORMAT, sinkcaps);
355 if (gst_caps_can_intersect (srccaps, sinkcaps)) {
356 res = gst_object_ref (testpad);
359 gst_caps_unref (sinkcaps);
361 g_value_reset (&paditem);
364 case GST_ITERATOR_DONE:
365 case GST_ITERATOR_ERROR:
368 case GST_ITERATOR_RESYNC:
369 gst_iterator_resync (pads);
373 g_value_reset (&paditem);
374 gst_iterator_free (pads);
375 gst_caps_unref (srccaps);
381 GST_ERROR ("No pad to check against");
386 static GstPadProbeReturn
387 pad_blocked (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
390 GST_DEBUG_OBJECT (pad, "blocked callback, blocked");
391 return GST_PAD_PROBE_OK;
395 pad_added_cb (GstElement * timeline, GstPad * pad, GESTimelinePipeline * self)
401 gboolean reconfigured = FALSE;
403 caps = gst_pad_query_caps (pad, NULL);
405 GST_DEBUG_OBJECT (self, "new pad %s:%s , caps:%" GST_PTR_FORMAT,
406 GST_DEBUG_PAD_NAME (pad), caps);
408 gst_caps_unref (caps);
410 track = ges_timeline_get_track_for_pad (self->priv->timeline, pad);
412 if (G_UNLIKELY (!track)) {
413 GST_WARNING_OBJECT (self, "Couldn't find coresponding track !");
417 /* Don't connect track if it's not going to be used */
418 if (track->type == GES_TRACK_TYPE_VIDEO &&
419 !(self->priv->mode & TIMELINE_MODE_PREVIEW_VIDEO) &&
420 !(self->priv->mode & TIMELINE_MODE_RENDER) &&
421 !(self->priv->mode & TIMELINE_MODE_SMART_RENDER)) {
422 GST_DEBUG_OBJECT (self, "Video track... but we don't need it. Not linking");
424 if (track->type == GES_TRACK_TYPE_AUDIO &&
425 !(self->priv->mode & TIMELINE_MODE_PREVIEW_AUDIO) &&
426 !(self->priv->mode & TIMELINE_MODE_RENDER) &&
427 !(self->priv->mode & TIMELINE_MODE_SMART_RENDER)) {
428 GST_DEBUG_OBJECT (self, "Audio track... but we don't need it. Not linking");
431 /* Get an existing chain or create it */
432 if (!(chain = get_output_chain_for_track (self, track)))
433 chain = new_output_chain_for_track (self, track);
437 chain->tee = gst_element_factory_make ("tee", NULL);
438 gst_bin_add (GST_BIN_CAST (self), chain->tee);
439 gst_element_sync_state_with_parent (chain->tee);
441 /* Linking pad to tee */
442 sinkpad = gst_element_get_static_pad (chain->tee, "sink");
443 gst_pad_link_full (pad, sinkpad, GST_PAD_LINK_CHECK_NOTHING);
444 gst_object_unref (sinkpad);
446 /* Connect playsink */
447 if (self->priv->mode & TIMELINE_MODE_PREVIEW) {
448 const gchar *sinkpad_name;
451 GST_DEBUG_OBJECT (self, "Connecting to playsink");
453 switch (track->type) {
454 case GES_TRACK_TYPE_VIDEO:
455 sinkpad_name = "video_sink";
457 case GES_TRACK_TYPE_AUDIO:
458 sinkpad_name = "audio_sink";
460 case GES_TRACK_TYPE_TEXT:
461 sinkpad_name = "text_sink";
464 GST_WARNING_OBJECT (self, "Can't handle tracks of type %d yet",
469 /* Request a sinkpad from playsink */
470 if (G_UNLIKELY (!(sinkpad =
471 gst_element_get_request_pad (self->priv->playsink,
473 GST_ERROR_OBJECT (self, "Couldn't get a pad from the playsink !");
477 tmppad = gst_element_get_request_pad (chain->tee, "src%d");
478 if (G_UNLIKELY (gst_pad_link_full (tmppad, sinkpad,
479 GST_PAD_LINK_CHECK_NOTHING) != GST_PAD_LINK_OK)) {
480 GST_ERROR_OBJECT (self, "Couldn't link track pad to playsink");
481 gst_object_unref (tmppad);
484 chain->blocked_pad = tmppad;
485 GST_DEBUG_OBJECT (tmppad, "blocking pad");
486 chain->probe_id = gst_pad_add_probe (tmppad, GST_PAD_PROBE_TYPE_BLOCK,
487 pad_blocked, NULL, NULL);
489 GST_DEBUG ("Reconfiguring playsink");
491 /* reconfigure playsink */
492 g_signal_emit_by_name (self->priv->playsink, "reconfigure", &reconfigured);
493 GST_DEBUG ("'reconfigure' returned %d", reconfigured);
495 /* We still hold a reference on the sinkpad */
496 chain->playsinkpad = sinkpad;
499 /* Connect to encodebin */
500 if (self->priv->mode & (TIMELINE_MODE_RENDER | TIMELINE_MODE_SMART_RENDER)) {
502 GST_DEBUG_OBJECT (self, "Connecting to encodebin");
504 if (!chain->encodebinpad) {
505 /* Check for unused static pads */
506 sinkpad = get_compatible_unlinked_pad (self->priv->encodebin, pad);
508 if (sinkpad == NULL) {
509 GstCaps *caps = gst_pad_query_caps (pad, NULL);
511 /* If no compatible static pad is available, request a pad */
512 g_signal_emit_by_name (self->priv->encodebin, "request-pad", caps,
514 gst_caps_unref (caps);
516 if (G_UNLIKELY (sinkpad == NULL)) {
517 GST_ERROR_OBJECT (self, "Couldn't get a pad from encodebin !");
521 chain->encodebinpad = sinkpad;
524 tmppad = gst_element_get_request_pad (chain->tee, "src%d");
525 if (G_UNLIKELY (gst_pad_link_full (tmppad,
527 GST_PAD_LINK_CHECK_NOTHING) != GST_PAD_LINK_OK)) {
528 GST_WARNING_OBJECT (self, "Couldn't link track pad to playsink");
531 gst_object_unref (tmppad);
535 /* If chain wasn't already present, insert it in list */
536 if (!get_output_chain_for_track (self, track))
537 self->priv->chains = g_list_append (self->priv->chains, chain);
545 gst_bin_remove (GST_BIN_CAST (self), chain->tee);
548 gst_object_unref (sinkpad);
554 pad_removed_cb (GstElement * timeline, GstPad * pad, GESTimelinePipeline * self)
560 GST_DEBUG_OBJECT (self, "pad removed %s:%s", GST_DEBUG_PAD_NAME (pad));
562 if (G_UNLIKELY (!(track =
563 ges_timeline_get_track_for_pad (self->priv->timeline, pad)))) {
564 GST_WARNING_OBJECT (self, "Couldn't find coresponding track !");
568 if (G_UNLIKELY (!(chain = get_output_chain_for_track (self, track)))) {
569 GST_DEBUG_OBJECT (self, "Pad wasn't used");
573 /* Unlink encodebin */
574 if (chain->encodebinpad) {
575 peer = gst_pad_get_peer (chain->encodebinpad);
576 gst_pad_unlink (peer, chain->encodebinpad);
577 gst_object_unref (peer);
578 gst_element_release_request_pad (self->priv->encodebin,
579 chain->encodebinpad);
582 /* Unlink playsink */
583 if (chain->playsinkpad) {
584 peer = gst_pad_get_peer (chain->playsinkpad);
585 gst_pad_unlink (peer, chain->playsinkpad);
586 gst_object_unref (peer);
587 gst_element_release_request_pad (self->priv->playsink, chain->playsinkpad);
588 gst_object_unref (chain->playsinkpad);
591 if (chain->blocked_pad) {
592 GST_DEBUG_OBJECT (chain->blocked_pad, "unblocking pad");
593 gst_pad_remove_probe (chain->blocked_pad, chain->probe_id);
594 gst_object_unref (chain->blocked_pad);
595 chain->blocked_pad = NULL;
599 /* Unlike/remove tee */
600 peer = gst_element_get_static_pad (chain->tee, "sink");
601 gst_pad_unlink (pad, peer);
602 gst_object_unref (peer);
603 gst_element_set_state (chain->tee, GST_STATE_NULL);
604 gst_bin_remove (GST_BIN (self), chain->tee);
606 self->priv->chains = g_list_remove (self->priv->chains, chain);
613 no_more_pads_cb (GstElement * timeline, GESTimelinePipeline * self)
617 GST_DEBUG ("received no-more-pads");
618 for (tmp = self->priv->chains; tmp; tmp = g_list_next (tmp)) {
619 OutputChain *chain = (OutputChain *) tmp->data;
621 if (chain->blocked_pad) {
622 GST_DEBUG_OBJECT (chain->blocked_pad, "unblocking pad");
623 gst_pad_remove_probe (chain->blocked_pad, chain->probe_id);
625 /* do we need to unref and NULL the pad here? */
631 * ges_timeline_pipeline_add_timeline:
632 * @pipeline: a #GESTimelinePipeline
633 * @timeline: the #GESTimeline to set on the @pipeline.
635 * Sets the timeline to use in this pipeline.
637 * The reference to the @timeline will be stolen by the @pipeline.
639 * Returns: TRUE if the @timeline could be successfully set on the @pipeline,
643 ges_timeline_pipeline_add_timeline (GESTimelinePipeline * pipeline,
644 GESTimeline * timeline)
646 g_return_val_if_fail (pipeline->priv->timeline == NULL, FALSE);
647 g_return_val_if_fail (timeline != NULL, FALSE);
649 GST_DEBUG ("pipeline:%p, timeline:%p", timeline, pipeline);
651 if (G_UNLIKELY (!gst_bin_add (GST_BIN_CAST (pipeline),
652 GST_ELEMENT (timeline)))) {
655 pipeline->priv->timeline = timeline;
657 /* Connect to pipeline */
658 g_signal_connect (timeline, "pad-added", (GCallback) pad_added_cb, pipeline);
659 g_signal_connect (timeline, "pad-removed", (GCallback) pad_removed_cb,
661 g_signal_connect (timeline, "no-more-pads", (GCallback) no_more_pads_cb,
668 * ges_timeline_pipeline_set_render_settings:
669 * @pipeline: a #GESTimelinePipeline
670 * @output_uri: the URI to which the timeline will be rendered
671 * @profile: the #GstEncodingProfile to use to render the timeline.
673 * Specify where the pipeline shall be rendered and with what settings.
675 * A copy of @profile and @output_uri will be done internally, the caller can
676 * safely free those values afterwards.
678 * This method must be called before setting the pipeline mode to
679 * #TIMELINE_MODE_RENDER
681 * Returns: %TRUE if the settings were aknowledged properly, else %FALSE
684 ges_timeline_pipeline_set_render_settings (GESTimelinePipeline * pipeline,
685 gchar * output_uri, GstEncodingProfile * profile)
687 /* Clear previous URI sink if it existed */
688 /* FIXME : We should figure out if it was added to the pipeline,
689 * and if so, remove it. */
690 if (pipeline->priv->urisink) {
691 g_object_unref (pipeline->priv->urisink);
692 pipeline->priv->urisink = NULL;
695 pipeline->priv->urisink =
696 gst_element_make_from_uri (GST_URI_SINK, output_uri, "urisink");
697 if (G_UNLIKELY (pipeline->priv->urisink == NULL)) {
698 GST_ERROR_OBJECT (pipeline, "Couldn't not create sink for URI %s",
703 if (pipeline->priv->profile)
704 gst_encoding_profile_unref (pipeline->priv->profile);
705 g_object_set (pipeline->priv->encodebin, "avoid-reencoding",
706 !(!(pipeline->priv->mode & TIMELINE_MODE_SMART_RENDER)), NULL);
707 g_object_set (pipeline->priv->encodebin, "profile", profile, NULL);
708 pipeline->priv->profile =
709 (GstEncodingProfile *) gst_encoding_profile_ref (profile);
715 * ges_timeline_pipeline_set_mode:
716 * @pipeline: a #GESTimelinePipeline
717 * @mode: the #GESPipelineFlags to use
719 * switches the @pipeline to the specified @mode. The default mode when
720 * creating a #GESTimelinePipeline is #TIMELINE_MODE_PREVIEW.
722 * Note: The @pipeline will be set to #GST_STATE_NULL during this call due to
723 * the internal changes that happen. The caller will therefore have to
724 * set the @pipeline to the requested state after calling this method.
726 * Returns: %TRUE if the mode was properly set, else %FALSE.
729 ges_timeline_pipeline_set_mode (GESTimelinePipeline * pipeline,
730 GESPipelineFlags mode)
732 GST_DEBUG_OBJECT (pipeline, "current mode : %d, mode : %d",
733 pipeline->priv->mode, mode);
735 /* fast-path, nothing to change */
736 if (mode == pipeline->priv->mode)
739 /* FIXME: It would be nice if we are only (de)activating preview
740 * modes to not set the whole pipeline to NULL, but instead just
741 * do the proper (un)linking to playsink. */
743 /* Switch pipeline to NULL since we're changing the configuration */
744 gst_element_set_state (GST_ELEMENT_CAST (pipeline), GST_STATE_NULL);
746 /* remove no-longer needed components */
747 if (pipeline->priv->mode & TIMELINE_MODE_PREVIEW &&
748 !(mode & TIMELINE_MODE_PREVIEW)) {
749 /* Disable playsink */
750 GST_DEBUG ("Disabling playsink");
751 g_object_ref (pipeline->priv->playsink);
752 gst_bin_remove (GST_BIN_CAST (pipeline), pipeline->priv->playsink);
754 if ((pipeline->priv->mode &
755 (TIMELINE_MODE_RENDER | TIMELINE_MODE_SMART_RENDER)) &&
756 !(mode & (TIMELINE_MODE_RENDER | TIMELINE_MODE_SMART_RENDER))) {
757 /* Disable render bin */
758 GST_DEBUG ("Disabling rendering bin");
759 g_object_ref (pipeline->priv->encodebin);
760 g_object_ref (pipeline->priv->urisink);
761 gst_bin_remove_many (GST_BIN_CAST (pipeline),
762 pipeline->priv->encodebin, pipeline->priv->urisink, NULL);
765 /* Add new elements */
766 if (!(pipeline->priv->mode & TIMELINE_MODE_PREVIEW) &&
767 (mode & TIMELINE_MODE_PREVIEW)) {
769 GST_DEBUG ("Adding playsink");
771 if (!gst_bin_add (GST_BIN_CAST (pipeline), pipeline->priv->playsink)) {
772 GST_ERROR_OBJECT (pipeline, "Couldn't add playsink");
776 if (!(pipeline->priv->mode &
777 (TIMELINE_MODE_RENDER | TIMELINE_MODE_SMART_RENDER)) &&
778 (mode & (TIMELINE_MODE_RENDER | TIMELINE_MODE_SMART_RENDER))) {
779 /* Adding render bin */
780 GST_DEBUG ("Adding render bin");
782 if (G_UNLIKELY (pipeline->priv->urisink == NULL)) {
783 GST_ERROR_OBJECT (pipeline, "Output URI not set !");
786 if (!gst_bin_add (GST_BIN_CAST (pipeline), pipeline->priv->encodebin)) {
787 GST_ERROR_OBJECT (pipeline, "Couldn't add encodebin");
790 if (!gst_bin_add (GST_BIN_CAST (pipeline), pipeline->priv->urisink)) {
791 GST_ERROR_OBJECT (pipeline, "Couldn't add URI sink");
794 g_object_set (pipeline->priv->encodebin, "avoid-reencoding",
795 !(!(mode & TIMELINE_MODE_SMART_RENDER)), NULL);
797 gst_element_link_pads_full (pipeline->priv->encodebin, "src",
798 pipeline->priv->urisink, "sink", GST_PAD_LINK_CHECK_NOTHING);
803 * If we are rendering, set playsink to sync=False,
804 * If we are NOT rendering, set playsink to sync=TRUE */
806 pipeline->priv->mode = mode;
812 * ges_timeline_pipeline_get_thumbnail:
813 * @self: a #GESTimelinePipeline in %GST_STATE_PLAYING or %GST_STATE_PAUSED
814 * @caps: (transfer none): caps specifying current format. Use %GST_CAPS_ANY
817 * Returns a #GstSample with the currently playing image in the format specified by
818 * caps. The caller should free the sample with #gst_sample_unref when finished. If ANY
819 * caps are specified, the information will be returned in the whatever format
820 * is currently used by the sink. This information can be retrieve from caps
821 * associated with the buffer.
823 * Returns: (transfer full): a #GstSample or %NULL
827 ges_timeline_pipeline_get_thumbnail (GESTimelinePipeline * self, GstCaps * caps)
831 sink = self->priv->playsink;
834 GST_WARNING ("thumbnailing can only be done if we have a playsink");
838 return ges_play_sink_convert_frame (sink, caps);
842 * ges_timeline_pipeline_save_thumbnail:
843 * @self: a #GESTimelinePipeline in %GST_STATE_PLAYING or %GST_STATE_PAUSED
844 * @width: the requested width or -1 for native size
845 * @height: the requested height or -1 for native size
846 * @format: a string specifying the desired mime type (for example,
848 * @location: the path to save the thumbnail
850 * Saves the current frame to the specified @location.
852 * Returns: %TRUE if the thumbnail was properly save, else %FALSE.
854 /* FIXME 0.11: save_thumbnail should have a GError parameter */
856 ges_timeline_pipeline_save_thumbnail (GESTimelinePipeline * self, int width, int
857 height, const gchar * format, const gchar * location)
865 caps = gst_caps_from_string (format);
868 gst_caps_set_simple (caps, "width", G_TYPE_INT, width, NULL);
871 gst_caps_set_simple (caps, "height", G_TYPE_INT, height, NULL);
873 if (!(sample = ges_timeline_pipeline_get_thumbnail (self, caps))) {
874 gst_caps_unref (caps);
878 b = gst_sample_get_buffer (sample);
879 if (gst_buffer_map (b, &map_info, GST_MAP_READ)) {
882 if (!g_file_set_contents (location, (const char *) map_info.data,
883 map_info.size, &err)) {
884 GST_WARNING ("Could not save thumbnail: %s", err->message);
890 gst_caps_unref (caps);
891 gst_buffer_unmap (b, &map_info);
892 gst_buffer_unref (b);
893 gst_sample_unref (sample);
899 * ges_timeline_pipeline_get_thumbnail_rgb24:
900 * @self: a #GESTimelinePipeline in %GST_STATE_PLAYING or %GST_STATE_PAUSED
901 * @width: the requested width or -1 for native size
902 * @height: the requested height or -1 for native size
904 * A convenience method for @ges_timeline_pipeline_get_thumbnail which
905 * returns a buffer in 24-bit RGB, optionally scaled to the specified width
906 * and height. If -1 is specified for either dimension, it will be left at
907 * native size. You can retreive this information from the caps associated
910 * The caller is responsible for unreffing the returned sample with
913 * Returns: (transfer full): a #GstSample or %NULL
917 ges_timeline_pipeline_get_thumbnail_rgb24 (GESTimelinePipeline * self,
918 gint width, gint height)
923 caps = gst_caps_new_simple ("video/x-raw", "format", G_TYPE_STRING,
927 gst_caps_set_simple (caps, "width", G_TYPE_INT, (gint) width, NULL);
930 gst_caps_set_simple (caps, "height", G_TYPE_INT, (gint) height, NULL);
932 ret = ges_timeline_pipeline_get_thumbnail (self, caps);
933 gst_caps_unref (caps);
938 * ges_timeline_pipeline_preview_get_video_sink:
939 * @self: a #GESTimelinePipeline
941 * Obtains a pointer to playsink's video sink element that is used for
942 * displaying video when the #GESTimelinePipeline is in %TIMELINE_MODE_PREVIEW
944 * The caller is responsible for unreffing the returned element with
947 * Returns: (transfer full): a pointer to the playsink video sink #GstElement
950 ges_timeline_pipeline_preview_get_video_sink (GESTimelinePipeline * self)
954 g_object_get (self->priv->playsink, "video-sink", &sink, NULL);
960 * ges_timeline_pipeline_preview_set_video_sink:
961 * @self: a #GESTimelinePipeline in %GST_STATE_NULL
962 * @sink: (transfer none): a video sink #GstElement
964 * Sets playsink's video sink element that is used for displaying video when
965 * the #GESTimelinePipeline is in %TIMELINE_MODE_PREVIEW
968 ges_timeline_pipeline_preview_set_video_sink (GESTimelinePipeline * self,
971 g_object_set (self->priv->playsink, "video-sink", sink, NULL);
975 * ges_timeline_pipeline_preview_get_audio_sink:
976 * @self: a #GESTimelinePipeline
978 * Obtains a pointer to playsink's audio sink element that is used for
979 * displaying audio when the #GESTimelinePipeline is in %TIMELINE_MODE_PREVIEW
981 * The caller is responsible for unreffing the returned element with
984 * Returns: (transfer full): a pointer to the playsink audio sink #GstElement
987 ges_timeline_pipeline_preview_get_audio_sink (GESTimelinePipeline * self)
991 g_object_get (self->priv->playsink, "audio-sink", &sink, NULL);
997 * ges_timeline_pipeline_preview_set_audio_sink:
998 * @self: a #GESTimelinePipeline in %GST_STATE_NULL
999 * @sink: (transfer none): a audio sink #GstElement
1001 * Sets playsink's audio sink element that is used for displaying audio when
1002 * the #GESTimelinePipeline is in %TIMELINE_MODE_PREVIEW
1005 ges_timeline_pipeline_preview_set_audio_sink (GESTimelinePipeline * self,
1008 g_object_set (self->priv->playsink, "audio-sink", sink, NULL);
1013 play_sink_multiple_seeks_send_event (GstElement * element, GstEvent * event)
1015 GstElementClass *klass = GST_ELEMENT_GET_CLASS (element);
1017 GST_DEBUG ("%s", GST_EVENT_TYPE_NAME (event));
1020 GST_ELEMENT_CLASS (g_type_class_peek_parent (klass))->send_event (element,