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;
78 static GParamSpec *properties[PROP_LAST];
80 static GstStateChangeReturn ges_timeline_pipeline_change_state (GstElement *
81 element, GstStateChange transition);
83 static OutputChain *get_output_chain_for_track (GESTimelinePipeline * self,
85 static OutputChain *new_output_chain_for_track (GESTimelinePipeline * self,
87 static gboolean play_sink_multiple_seeks_send_event (GstElement * element,
91 ges_timeline_pipeline_get_property (GObject * object, guint property_id,
92 GValue * value, GParamSpec * pspec)
94 GESTimelinePipeline *self = GES_TIMELINE_PIPELINE (object);
96 switch (property_id) {
98 g_object_get_property (G_OBJECT (self->priv->playsink), "audio-sink",
101 case PROP_VIDEO_SINK:
102 g_object_get_property (G_OBJECT (self->priv->playsink), "video-sink",
106 g_value_set_object (value, self->priv->timeline);
109 g_value_set_flags (value, self->priv->mode);
112 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
117 ges_timeline_pipeline_set_property (GObject * object, guint property_id,
118 const GValue * value, GParamSpec * pspec)
120 GESTimelinePipeline *self = GES_TIMELINE_PIPELINE (object);
122 switch (property_id) {
123 case PROP_AUDIO_SINK:
124 g_object_set_property (G_OBJECT (self->priv->playsink), "audio-sink",
127 case PROP_VIDEO_SINK:
128 g_object_set_property (G_OBJECT (self->priv->playsink), "video-sink",
132 ges_timeline_pipeline_add_timeline (GES_TIMELINE_PIPELINE (object),
133 g_value_get_object(value));
136 ges_timeline_pipeline_set_mode (GES_TIMELINE_PIPELINE (object),
137 g_value_get_flags(value));
140 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
145 ges_timeline_pipeline_dispose (GObject * object)
147 GESTimelinePipeline *self = GES_TIMELINE_PIPELINE (object);
149 if (self->priv->playsink) {
150 if (self->priv->mode & (TIMELINE_MODE_PREVIEW))
151 gst_bin_remove (GST_BIN (object), self->priv->playsink);
153 gst_object_unref (self->priv->playsink);
154 self->priv->playsink = NULL;
157 if (self->priv->encodebin) {
158 if (self->priv->mode & (TIMELINE_MODE_RENDER | TIMELINE_MODE_SMART_RENDER))
159 gst_bin_remove (GST_BIN (object), self->priv->encodebin);
161 gst_object_unref (self->priv->encodebin);
162 self->priv->encodebin = NULL;
165 if (self->priv->profile) {
166 gst_encoding_profile_unref (self->priv->profile);
167 self->priv->profile = NULL;
170 G_OBJECT_CLASS (ges_timeline_pipeline_parent_class)->dispose (object);
174 ges_timeline_pipeline_class_init (GESTimelinePipelineClass * klass)
176 GObjectClass *object_class = G_OBJECT_CLASS (klass);
177 GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
179 g_type_class_add_private (klass, sizeof (GESTimelinePipelinePrivate));
181 object_class->dispose = ges_timeline_pipeline_dispose;
182 object_class->get_property = ges_timeline_pipeline_get_property;
183 object_class->set_property = ges_timeline_pipeline_set_property;
186 * GESTimelinePipeline:audio-sink
188 * Audio sink for the preview.
190 properties[PROP_AUDIO_SINK] = g_param_spec_object ("audio-sink", "Audio Sink",
191 "Audio sink for the preview.",
192 GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
193 g_object_class_install_property (object_class, PROP_AUDIO_SINK,
194 properties[PROP_AUDIO_SINK]);
197 * GESTimelinePipeline:video-sink
199 * Video sink for the preview.
201 properties[PROP_VIDEO_SINK] = g_param_spec_object ("video-sink", "Video Sink",
202 "Video sink for the preview.",
203 GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
204 g_object_class_install_property (object_class, PROP_VIDEO_SINK,
205 properties[PROP_VIDEO_SINK]);
208 * GESTimelinePipeline:timeline
210 * Timeline to use in this pipeline. See also
211 * ges_timeline_pipeline_add_timeline() for more info.
213 properties[PROP_TIMELINE] = g_param_spec_object ("timeline", "Timeline",
214 "Timeline to use in this pipeline. See also "
215 "ges_timeline_pipeline_add_timeline() for more info.",
216 GES_TYPE_TIMELINE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
217 g_object_class_install_property (object_class, PROP_TIMELINE,
218 properties[PROP_TIMELINE]);
221 * GESTimelinePipeline:mode
223 * Pipeline mode. See ges_timeline_pipeline_set_mode() for more
226 properties[PROP_MODE] = g_param_spec_flags ("mode", "Mode",
227 "Pipeline mode. See ges_timeline_pipeline_set_mode() for more info.",
228 GES_TYPE_PIPELINE_FLAGS, DEFAULT_TIMELINE_MODE,
229 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
230 g_object_class_install_property (object_class, PROP_MODE,
231 properties[PROP_MODE]);
233 element_class->change_state =
234 GST_DEBUG_FUNCPTR (ges_timeline_pipeline_change_state);
236 /* TODO : Add state_change handlers
237 * Don't change state if we don't have a timeline */
241 ges_timeline_pipeline_init (GESTimelinePipeline * self)
243 GstElementClass *playsinkclass;
245 GST_INFO_OBJECT (self, "Creating new 'playsink'");
246 self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
247 GES_TYPE_TIMELINE_PIPELINE, GESTimelinePipelinePrivate);
249 self->priv->playsink =
250 gst_element_factory_make ("playsink", "internal-sinks");
251 self->priv->encodebin =
252 gst_element_factory_make ("encodebin", "internal-encodebin");
253 /* Limit encodebin buffering to 1 buffer since we know the various
254 * stream fed to it are decoupled already */
255 g_object_set (self->priv->encodebin, "queue-buffers-max", (guint) 1,
256 "queue-bytes-max", (guint) 0, "queue-time-max", (guint64) 0,
257 "avoid-reencoding", TRUE, NULL);
259 if (G_UNLIKELY (self->priv->playsink == NULL))
261 if (G_UNLIKELY (self->priv->encodebin == NULL))
264 /* TODO : Remove this hack once we depend on gst-p-base 0.10.37 */
265 /* HACK : Intercept events going through playsink */
266 playsinkclass = GST_ELEMENT_GET_CLASS (self->priv->playsink);
267 /* Replace playsink's GstBin::send_event with our own */
268 playsinkclass->send_event = play_sink_multiple_seeks_send_event;
270 ges_timeline_pipeline_set_mode (self, DEFAULT_TIMELINE_MODE);
276 GST_ERROR_OBJECT (self, "Can't create playsink instance !");
281 GST_ERROR_OBJECT (self, "Can't create encodebin instance !");
287 * ges_timeline_pipeline_new:
289 * Creates a new conveninence #GESTimelinePipeline.
291 * Returns: the new #GESTimelinePipeline.
293 GESTimelinePipeline *
294 ges_timeline_pipeline_new (void)
296 return g_object_new (GES_TYPE_TIMELINE_PIPELINE, NULL);
299 #define TRACK_COMPATIBLE_PROFILE(tracktype, profile) \
300 ( (GST_IS_ENCODING_AUDIO_PROFILE (profile) && (tracktype) == GES_TRACK_TYPE_AUDIO) || \
301 (GST_IS_ENCODING_VIDEO_PROFILE (profile) && (tracktype) == GES_TRACK_TYPE_VIDEO))
304 ges_timeline_pipeline_update_caps (GESTimelinePipeline * self)
306 GList *ltrack, *tracks, *lstream;
308 if (!self->priv->profile)
311 GST_DEBUG ("Updating track caps");
313 tracks = ges_timeline_get_tracks (self->priv->timeline);
315 /* Take each stream of the encoding profile and find a matching
316 * track to set the caps on */
317 for (ltrack = tracks; ltrack; ltrack = ltrack->next) {
318 GESTrack *track = (GESTrack *) ltrack->data;
321 allstreams = (GList *)
322 gst_encoding_container_profile_get_profiles (
323 (GstEncodingContainerProfile *) self->priv->profile);
325 /* Find a matching stream setting */
326 for (lstream = allstreams; lstream; lstream = lstream->next) {
327 GstEncodingProfile *prof = (GstEncodingProfile *) lstream->data;
329 if (TRACK_COMPATIBLE_PROFILE (track->type, prof)) {
330 if (self->priv->mode == TIMELINE_MODE_SMART_RENDER) {
331 GstCaps *ocaps, *rcaps;
333 GST_DEBUG ("Smart Render mode, setting input caps");
334 ocaps = gst_encoding_profile_get_input_caps (prof);
335 if (track->type == GES_TRACK_TYPE_AUDIO)
336 rcaps = gst_caps_new_empty_simple ("audio/x-raw");
338 rcaps = gst_caps_new_empty_simple ("video/x-raw");
339 gst_caps_append (ocaps, rcaps);
340 ges_track_set_caps (track, ocaps);
342 GstCaps *caps = NULL;
344 /* Raw preview or rendering mode */
345 if (track->type == GES_TRACK_TYPE_VIDEO)
346 caps = gst_caps_new_empty_simple ("video/x-raw");
347 else if (track->type == GES_TRACK_TYPE_AUDIO)
348 caps = gst_caps_new_empty_simple ("audio/x-raw");
351 ges_track_set_caps (track, caps);
352 gst_caps_unref (caps);
359 g_object_unref (track);
363 g_list_free (tracks);
365 GST_DEBUG ("Done updating caps");
370 static GstStateChangeReturn
371 ges_timeline_pipeline_change_state (GstElement * element,
372 GstStateChange transition)
374 GESTimelinePipeline *self;
375 GstStateChangeReturn ret;
377 self = GES_TIMELINE_PIPELINE (element);
379 switch (transition) {
380 case GST_STATE_CHANGE_READY_TO_PAUSED:
381 if (G_UNLIKELY (self->priv->timeline == NULL)) {
382 GST_ERROR_OBJECT (element,
383 "No GESTimeline set on the pipeline, cannot play !");
384 ret = GST_STATE_CHANGE_FAILURE;
388 mode & (TIMELINE_MODE_RENDER | TIMELINE_MODE_SMART_RENDER))
389 GST_DEBUG ("rendering => Updating pipeline caps");
390 if (!ges_timeline_pipeline_update_caps (self)) {
391 GST_ERROR_OBJECT (element, "Error setting the caps for rendering");
392 ret = GST_STATE_CHANGE_FAILURE;
395 /* Set caps on all tracks according to profile if present */
396 /* FIXME : Add a new SMART_RENDER mode to avoid decoding */
403 GST_ELEMENT_CLASS (ges_timeline_pipeline_parent_class)->change_state
404 (element, transition);
411 new_output_chain_for_track (GESTimelinePipeline * self, GESTrack * track)
415 chain = g_new0 (OutputChain, 1);
416 chain->track = track;
422 get_output_chain_for_track (GESTimelinePipeline * self, GESTrack * track)
426 for (tmp = self->priv->chains; tmp; tmp = tmp->next) {
427 OutputChain *chain = (OutputChain *) tmp->data;
428 if (chain->track == track)
435 /* Fetches a compatible pad on the target element which isn't already
438 get_compatible_unlinked_pad (GstElement * element, GstPad * pad)
442 gboolean done = FALSE;
444 GValue paditem = { 0, };
446 if (G_UNLIKELY (pad == NULL))
449 GST_DEBUG ("element : %s, pad %s:%s",
450 GST_ELEMENT_NAME (element), GST_DEBUG_PAD_NAME (pad));
452 if (GST_PAD_DIRECTION (pad) == GST_PAD_SRC)
453 pads = gst_element_iterate_sink_pads (element);
455 pads = gst_element_iterate_src_pads (element);
456 srccaps = gst_pad_query_caps (pad, NULL);
458 GST_DEBUG ("srccaps %" GST_PTR_FORMAT, srccaps);
461 switch (gst_iterator_next (pads, &paditem)) {
462 case GST_ITERATOR_OK:
464 GstPad *testpad = g_value_get_object (&paditem);
466 if (!gst_pad_is_linked (testpad)) {
467 GstCaps *sinkcaps = gst_pad_query_caps (testpad, NULL);
469 GST_DEBUG ("sinkccaps %" GST_PTR_FORMAT, sinkcaps);
471 if (gst_caps_can_intersect (srccaps, sinkcaps)) {
472 res = gst_object_ref (testpad);
475 gst_caps_unref (sinkcaps);
477 g_value_reset (&paditem);
480 case GST_ITERATOR_DONE:
481 case GST_ITERATOR_ERROR:
484 case GST_ITERATOR_RESYNC:
485 gst_iterator_resync (pads);
489 g_value_reset (&paditem);
490 gst_iterator_free (pads);
491 gst_caps_unref (srccaps);
497 GST_ERROR ("No pad to check against");
502 static GstPadProbeReturn
503 pad_blocked (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
506 GST_DEBUG_OBJECT (pad, "blocked callback, blocked");
507 return GST_PAD_PROBE_OK;
511 pad_added_cb (GstElement * timeline, GstPad * pad, GESTimelinePipeline * self)
517 gboolean reconfigured = FALSE;
519 caps = gst_pad_query_caps (pad, NULL);
521 GST_DEBUG_OBJECT (self, "new pad %s:%s , caps:%" GST_PTR_FORMAT,
522 GST_DEBUG_PAD_NAME (pad), caps);
524 gst_caps_unref (caps);
526 track = ges_timeline_get_track_for_pad (self->priv->timeline, pad);
528 if (G_UNLIKELY (!track)) {
529 GST_WARNING_OBJECT (self, "Couldn't find coresponding track !");
533 /* Don't connect track if it's not going to be used */
534 if (track->type == GES_TRACK_TYPE_VIDEO &&
535 !(self->priv->mode & TIMELINE_MODE_PREVIEW_VIDEO) &&
536 !(self->priv->mode & TIMELINE_MODE_RENDER) &&
537 !(self->priv->mode & TIMELINE_MODE_SMART_RENDER)) {
538 GST_DEBUG_OBJECT (self, "Video track... but we don't need it. Not linking");
540 if (track->type == GES_TRACK_TYPE_AUDIO &&
541 !(self->priv->mode & TIMELINE_MODE_PREVIEW_AUDIO) &&
542 !(self->priv->mode & TIMELINE_MODE_RENDER) &&
543 !(self->priv->mode & TIMELINE_MODE_SMART_RENDER)) {
544 GST_DEBUG_OBJECT (self, "Audio track... but we don't need it. Not linking");
547 /* Get an existing chain or create it */
548 if (!(chain = get_output_chain_for_track (self, track)))
549 chain = new_output_chain_for_track (self, track);
553 chain->tee = gst_element_factory_make ("tee", NULL);
554 gst_bin_add (GST_BIN_CAST (self), chain->tee);
555 gst_element_sync_state_with_parent (chain->tee);
557 /* Linking pad to tee */
558 sinkpad = gst_element_get_static_pad (chain->tee, "sink");
559 gst_pad_link_full (pad, sinkpad, GST_PAD_LINK_CHECK_NOTHING);
560 gst_object_unref (sinkpad);
562 /* Connect playsink */
563 if (self->priv->mode & TIMELINE_MODE_PREVIEW) {
564 const gchar *sinkpad_name;
567 GST_DEBUG_OBJECT (self, "Connecting to playsink");
569 switch (track->type) {
570 case GES_TRACK_TYPE_VIDEO:
571 sinkpad_name = "video_sink";
573 case GES_TRACK_TYPE_AUDIO:
574 sinkpad_name = "audio_sink";
576 case GES_TRACK_TYPE_TEXT:
577 sinkpad_name = "text_sink";
580 GST_WARNING_OBJECT (self, "Can't handle tracks of type %d yet",
585 /* Request a sinkpad from playsink */
586 if (G_UNLIKELY (!(sinkpad =
587 gst_element_get_request_pad (self->priv->playsink,
589 GST_ERROR_OBJECT (self, "Couldn't get a pad from the playsink !");
593 tmppad = gst_element_get_request_pad (chain->tee, "src_%u");
594 if (G_UNLIKELY (gst_pad_link_full (tmppad, sinkpad,
595 GST_PAD_LINK_CHECK_NOTHING) != GST_PAD_LINK_OK)) {
596 GST_ERROR_OBJECT (self, "Couldn't link track pad to playsink");
597 gst_object_unref (tmppad);
600 chain->blocked_pad = tmppad;
601 GST_DEBUG_OBJECT (tmppad, "blocking pad");
602 chain->probe_id = gst_pad_add_probe (tmppad,
603 GST_PAD_PROBE_TYPE_BLOCK_DOWNSTREAM, pad_blocked, NULL, NULL);
605 GST_DEBUG ("Reconfiguring playsink");
607 /* reconfigure playsink */
608 g_signal_emit_by_name (self->priv->playsink, "reconfigure", &reconfigured);
609 GST_DEBUG ("'reconfigure' returned %d", reconfigured);
611 /* We still hold a reference on the sinkpad */
612 chain->playsinkpad = sinkpad;
615 /* Connect to encodebin */
616 if (self->priv->mode & (TIMELINE_MODE_RENDER | TIMELINE_MODE_SMART_RENDER)) {
618 GST_DEBUG_OBJECT (self, "Connecting to encodebin");
620 if (!chain->encodebinpad) {
621 /* Check for unused static pads */
622 sinkpad = get_compatible_unlinked_pad (self->priv->encodebin, pad);
624 if (sinkpad == NULL) {
625 GstCaps *caps = gst_pad_query_caps (pad, NULL);
627 /* If no compatible static pad is available, request a pad */
628 g_signal_emit_by_name (self->priv->encodebin, "request-pad", caps,
630 gst_caps_unref (caps);
632 if (G_UNLIKELY (sinkpad == NULL)) {
633 GST_ERROR_OBJECT (self, "Couldn't get a pad from encodebin !");
637 chain->encodebinpad = sinkpad;
640 tmppad = gst_element_get_request_pad (chain->tee, "src_%u");
641 if (G_UNLIKELY (gst_pad_link_full (tmppad,
643 GST_PAD_LINK_CHECK_NOTHING) != GST_PAD_LINK_OK)) {
644 GST_WARNING_OBJECT (self, "Couldn't link track pad to playsink");
647 gst_object_unref (tmppad);
651 /* If chain wasn't already present, insert it in list */
652 if (!get_output_chain_for_track (self, track))
653 self->priv->chains = g_list_append (self->priv->chains, chain);
661 gst_bin_remove (GST_BIN_CAST (self), chain->tee);
664 gst_object_unref (sinkpad);
670 pad_removed_cb (GstElement * timeline, GstPad * pad, GESTimelinePipeline * self)
676 GST_DEBUG_OBJECT (self, "pad removed %s:%s", GST_DEBUG_PAD_NAME (pad));
678 if (G_UNLIKELY (!(track =
679 ges_timeline_get_track_for_pad (self->priv->timeline, pad)))) {
680 GST_WARNING_OBJECT (self, "Couldn't find coresponding track !");
684 if (G_UNLIKELY (!(chain = get_output_chain_for_track (self, track)))) {
685 GST_DEBUG_OBJECT (self, "Pad wasn't used");
689 /* Unlink encodebin */
690 if (chain->encodebinpad) {
691 peer = gst_pad_get_peer (chain->encodebinpad);
692 gst_pad_unlink (peer, chain->encodebinpad);
693 gst_object_unref (peer);
694 gst_element_release_request_pad (self->priv->encodebin,
695 chain->encodebinpad);
698 /* Unlink playsink */
699 if (chain->playsinkpad) {
700 peer = gst_pad_get_peer (chain->playsinkpad);
701 gst_pad_unlink (peer, chain->playsinkpad);
702 gst_object_unref (peer);
703 gst_element_release_request_pad (self->priv->playsink, chain->playsinkpad);
704 gst_object_unref (chain->playsinkpad);
707 if (chain->blocked_pad) {
708 GST_DEBUG_OBJECT (chain->blocked_pad, "unblocking pad");
709 gst_pad_remove_probe (chain->blocked_pad, chain->probe_id);
710 gst_object_unref (chain->blocked_pad);
711 chain->blocked_pad = NULL;
715 /* Unlike/remove tee */
716 peer = gst_element_get_static_pad (chain->tee, "sink");
717 gst_pad_unlink (pad, peer);
718 gst_object_unref (peer);
719 gst_element_set_state (chain->tee, GST_STATE_NULL);
720 gst_bin_remove (GST_BIN (self), chain->tee);
722 self->priv->chains = g_list_remove (self->priv->chains, chain);
729 no_more_pads_cb (GstElement * timeline, GESTimelinePipeline * self)
733 GST_DEBUG ("received no-more-pads");
734 for (tmp = self->priv->chains; tmp; tmp = g_list_next (tmp)) {
735 OutputChain *chain = (OutputChain *) tmp->data;
737 if (chain->blocked_pad) {
738 GST_DEBUG_OBJECT (chain->blocked_pad, "unblocking pad");
739 gst_pad_remove_probe (chain->blocked_pad, chain->probe_id);
740 gst_object_unref (chain->blocked_pad);
741 chain->blocked_pad = NULL;
748 * ges_timeline_pipeline_add_timeline:
749 * @pipeline: a #GESTimelinePipeline
750 * @timeline: the #GESTimeline to set on the @pipeline.
752 * Sets the timeline to use in this pipeline.
754 * The reference to the @timeline will be stolen by the @pipeline.
756 * Returns: TRUE if the @timeline could be successfully set on the @pipeline,
760 ges_timeline_pipeline_add_timeline (GESTimelinePipeline * pipeline,
761 GESTimeline * timeline)
763 g_return_val_if_fail (pipeline->priv->timeline == NULL, FALSE);
764 g_return_val_if_fail (timeline != NULL, FALSE);
766 GST_DEBUG ("pipeline:%p, timeline:%p", timeline, pipeline);
768 if (G_UNLIKELY (!gst_bin_add (GST_BIN_CAST (pipeline),
769 GST_ELEMENT (timeline)))) {
772 pipeline->priv->timeline = timeline;
774 /* Connect to pipeline */
775 g_signal_connect (timeline, "pad-added", (GCallback) pad_added_cb, pipeline);
776 g_signal_connect (timeline, "pad-removed", (GCallback) pad_removed_cb,
778 g_signal_connect (timeline, "no-more-pads", (GCallback) no_more_pads_cb,
785 * ges_timeline_pipeline_set_render_settings:
786 * @pipeline: a #GESTimelinePipeline
787 * @output_uri: the URI to which the timeline will be rendered
788 * @profile: the #GstEncodingProfile to use to render the timeline.
790 * Specify where the pipeline shall be rendered and with what settings.
792 * A copy of @profile and @output_uri will be done internally, the caller can
793 * safely free those values afterwards.
795 * This method must be called before setting the pipeline mode to
796 * #TIMELINE_MODE_RENDER
798 * Returns: %TRUE if the settings were aknowledged properly, else %FALSE
801 ges_timeline_pipeline_set_render_settings (GESTimelinePipeline * pipeline,
802 const gchar * output_uri, GstEncodingProfile * profile)
806 /* Clear previous URI sink if it existed */
807 /* FIXME : We should figure out if it was added to the pipeline,
808 * and if so, remove it. */
809 if (pipeline->priv->urisink) {
810 g_object_unref (pipeline->priv->urisink);
811 pipeline->priv->urisink = NULL;
814 pipeline->priv->urisink =
815 gst_element_make_from_uri (GST_URI_SINK, output_uri, "urisink", &err);
816 if (G_UNLIKELY (pipeline->priv->urisink == NULL)) {
817 GST_ERROR_OBJECT (pipeline, "Couldn't not create sink for URI %s: '%s'",
819 && err->message) ? err->message : "failed to create element"));
820 g_clear_error (&err);
824 if (pipeline->priv->profile)
825 gst_encoding_profile_unref (pipeline->priv->profile);
826 g_object_set (pipeline->priv->encodebin, "avoid-reencoding",
827 !(!(pipeline->priv->mode & TIMELINE_MODE_SMART_RENDER)), NULL);
828 g_object_set (pipeline->priv->encodebin, "profile", profile, NULL);
829 pipeline->priv->profile =
830 (GstEncodingProfile *) gst_encoding_profile_ref (profile);
836 * ges_timeline_pipeline_set_mode:
837 * @pipeline: a #GESTimelinePipeline
838 * @mode: the #GESPipelineFlags to use
840 * switches the @pipeline to the specified @mode. The default mode when
841 * creating a #GESTimelinePipeline is #TIMELINE_MODE_PREVIEW.
843 * Note: The @pipeline will be set to #GST_STATE_NULL during this call due to
844 * the internal changes that happen. The caller will therefore have to
845 * set the @pipeline to the requested state after calling this method.
847 * Returns: %TRUE if the mode was properly set, else %FALSE.
850 ges_timeline_pipeline_set_mode (GESTimelinePipeline * pipeline,
851 GESPipelineFlags mode)
853 GST_DEBUG_OBJECT (pipeline, "current mode : %d, mode : %d",
854 pipeline->priv->mode, mode);
856 /* fast-path, nothing to change */
857 if (mode == pipeline->priv->mode)
860 /* FIXME: It would be nice if we are only (de)activating preview
861 * modes to not set the whole pipeline to NULL, but instead just
862 * do the proper (un)linking to playsink. */
864 /* Switch pipeline to NULL since we're changing the configuration */
865 gst_element_set_state (GST_ELEMENT_CAST (pipeline), GST_STATE_NULL);
867 /* remove no-longer needed components */
868 if (pipeline->priv->mode & TIMELINE_MODE_PREVIEW &&
869 !(mode & TIMELINE_MODE_PREVIEW)) {
870 /* Disable playsink */
871 GST_DEBUG ("Disabling playsink");
872 g_object_ref (pipeline->priv->playsink);
873 gst_bin_remove (GST_BIN_CAST (pipeline), pipeline->priv->playsink);
875 if ((pipeline->priv->mode &
876 (TIMELINE_MODE_RENDER | TIMELINE_MODE_SMART_RENDER)) &&
877 !(mode & (TIMELINE_MODE_RENDER | TIMELINE_MODE_SMART_RENDER))) {
878 /* Disable render bin */
879 GST_DEBUG ("Disabling rendering bin");
880 g_object_ref (pipeline->priv->encodebin);
881 g_object_ref (pipeline->priv->urisink);
882 gst_bin_remove_many (GST_BIN_CAST (pipeline),
883 pipeline->priv->encodebin, pipeline->priv->urisink, NULL);
886 /* Add new elements */
887 if (!(pipeline->priv->mode & TIMELINE_MODE_PREVIEW) &&
888 (mode & TIMELINE_MODE_PREVIEW)) {
890 GST_DEBUG ("Adding playsink");
892 if (!gst_bin_add (GST_BIN_CAST (pipeline), pipeline->priv->playsink)) {
893 GST_ERROR_OBJECT (pipeline, "Couldn't add playsink");
897 if (!(pipeline->priv->mode &
898 (TIMELINE_MODE_RENDER | TIMELINE_MODE_SMART_RENDER)) &&
899 (mode & (TIMELINE_MODE_RENDER | TIMELINE_MODE_SMART_RENDER))) {
900 /* Adding render bin */
901 GST_DEBUG ("Adding render bin");
903 if (G_UNLIKELY (pipeline->priv->urisink == NULL)) {
904 GST_ERROR_OBJECT (pipeline, "Output URI not set !");
907 if (!gst_bin_add (GST_BIN_CAST (pipeline), pipeline->priv->encodebin)) {
908 GST_ERROR_OBJECT (pipeline, "Couldn't add encodebin");
911 if (!gst_bin_add (GST_BIN_CAST (pipeline), pipeline->priv->urisink)) {
912 GST_ERROR_OBJECT (pipeline, "Couldn't add URI sink");
915 g_object_set (pipeline->priv->encodebin, "avoid-reencoding",
916 !(!(mode & TIMELINE_MODE_SMART_RENDER)), NULL);
918 gst_element_link_pads_full (pipeline->priv->encodebin, "src",
919 pipeline->priv->urisink, "sink", GST_PAD_LINK_CHECK_NOTHING);
924 * If we are rendering, set playsink to sync=False,
925 * If we are NOT rendering, set playsink to sync=TRUE */
927 pipeline->priv->mode = mode;
933 * ges_timeline_pipeline_get_thumbnail:
934 * @self: a #GESTimelinePipeline in %GST_STATE_PLAYING or %GST_STATE_PAUSED
935 * @caps: (transfer none): caps specifying current format. Use %GST_CAPS_ANY
938 * Returns a #GstSample with the currently playing image in the format specified by
939 * caps. The caller should free the sample with #gst_sample_unref when finished. If ANY
940 * caps are specified, the information will be returned in the whatever format
941 * is currently used by the sink. This information can be retrieve from caps
942 * associated with the buffer.
944 * Returns: (transfer full): a #GstSample or %NULL
948 ges_timeline_pipeline_get_thumbnail (GESTimelinePipeline * self, GstCaps * caps)
952 sink = self->priv->playsink;
955 GST_WARNING ("thumbnailing can only be done if we have a playsink");
959 return ges_play_sink_convert_frame (sink, caps);
963 * ges_timeline_pipeline_save_thumbnail:
964 * @self: a #GESTimelinePipeline in %GST_STATE_PLAYING or %GST_STATE_PAUSED
965 * @width: the requested width or -1 for native size
966 * @height: the requested height or -1 for native size
967 * @format: a string specifying the desired mime type (for example,
969 * @location: the path to save the thumbnail
971 * Saves the current frame to the specified @location.
973 * Returns: %TRUE if the thumbnail was properly save, else %FALSE.
975 /* FIXME 0.11: save_thumbnail should have a GError parameter */
977 ges_timeline_pipeline_save_thumbnail (GESTimelinePipeline * self, int width, int
978 height, const gchar * format, const gchar * location)
986 caps = gst_caps_from_string (format);
989 gst_caps_set_simple (caps, "width", G_TYPE_INT, width, NULL);
992 gst_caps_set_simple (caps, "height", G_TYPE_INT, height, NULL);
994 if (!(sample = ges_timeline_pipeline_get_thumbnail (self, caps))) {
995 gst_caps_unref (caps);
999 b = gst_sample_get_buffer (sample);
1000 if (gst_buffer_map (b, &map_info, GST_MAP_READ)) {
1003 if (!g_file_set_contents (location, (const char *) map_info.data,
1004 map_info.size, &err)) {
1005 GST_WARNING ("Could not save thumbnail: %s", err->message);
1011 gst_caps_unref (caps);
1012 gst_buffer_unmap (b, &map_info);
1013 gst_buffer_unref (b);
1014 gst_sample_unref (sample);
1020 * ges_timeline_pipeline_get_thumbnail_rgb24:
1021 * @self: a #GESTimelinePipeline in %GST_STATE_PLAYING or %GST_STATE_PAUSED
1022 * @width: the requested width or -1 for native size
1023 * @height: the requested height or -1 for native size
1025 * A convenience method for @ges_timeline_pipeline_get_thumbnail which
1026 * returns a buffer in 24-bit RGB, optionally scaled to the specified width
1027 * and height. If -1 is specified for either dimension, it will be left at
1028 * native size. You can retreive this information from the caps associated
1031 * The caller is responsible for unreffing the returned sample with
1032 * #gst_sample_unref.
1034 * Returns: (transfer full): a #GstSample or %NULL
1038 ges_timeline_pipeline_get_thumbnail_rgb24 (GESTimelinePipeline * self,
1039 gint width, gint height)
1044 caps = gst_caps_new_simple ("video/x-raw", "format", G_TYPE_STRING,
1048 gst_caps_set_simple (caps, "width", G_TYPE_INT, (gint) width, NULL);
1051 gst_caps_set_simple (caps, "height", G_TYPE_INT, (gint) height, NULL);
1053 ret = ges_timeline_pipeline_get_thumbnail (self, caps);
1054 gst_caps_unref (caps);
1059 * ges_timeline_pipeline_preview_get_video_sink:
1060 * @self: a #GESTimelinePipeline
1062 * Obtains a pointer to playsink's video sink element that is used for
1063 * displaying video when the #GESTimelinePipeline is in %TIMELINE_MODE_PREVIEW
1065 * The caller is responsible for unreffing the returned element with
1066 * #gst_object_unref.
1068 * Returns: (transfer full): a pointer to the playsink video sink #GstElement
1071 ges_timeline_pipeline_preview_get_video_sink (GESTimelinePipeline * self)
1073 GstElement *sink = NULL;
1075 g_object_get (self->priv->playsink, "video-sink", &sink, NULL);
1081 * ges_timeline_pipeline_preview_set_video_sink:
1082 * @self: a #GESTimelinePipeline in %GST_STATE_NULL
1083 * @sink: (transfer none): a video sink #GstElement
1085 * Sets playsink's video sink element that is used for displaying video when
1086 * the #GESTimelinePipeline is in %TIMELINE_MODE_PREVIEW
1089 ges_timeline_pipeline_preview_set_video_sink (GESTimelinePipeline * self,
1092 g_object_set (self->priv->playsink, "video-sink", sink, NULL);
1096 * ges_timeline_pipeline_preview_get_audio_sink:
1097 * @self: a #GESTimelinePipeline
1099 * Obtains a pointer to playsink's audio sink element that is used for
1100 * displaying audio when the #GESTimelinePipeline is in %TIMELINE_MODE_PREVIEW
1102 * The caller is responsible for unreffing the returned element with
1103 * #gst_object_unref.
1105 * Returns: (transfer full): a pointer to the playsink audio sink #GstElement
1108 ges_timeline_pipeline_preview_get_audio_sink (GESTimelinePipeline * self)
1110 GstElement *sink = NULL;
1112 g_object_get (self->priv->playsink, "audio-sink", &sink, NULL);
1118 * ges_timeline_pipeline_preview_set_audio_sink:
1119 * @self: a #GESTimelinePipeline in %GST_STATE_NULL
1120 * @sink: (transfer none): a audio sink #GstElement
1122 * Sets playsink's audio sink element that is used for displaying audio when
1123 * the #GESTimelinePipeline is in %TIMELINE_MODE_PREVIEW
1126 ges_timeline_pipeline_preview_set_audio_sink (GESTimelinePipeline * self,
1129 g_object_set (self->priv->playsink, "audio-sink", sink, NULL);
1134 play_sink_multiple_seeks_send_event (GstElement * element, GstEvent * event)
1136 GstElementClass *klass = GST_ELEMENT_GET_CLASS (element);
1138 GST_DEBUG ("%s", GST_EVENT_TYPE_NAME (event));
1141 GST_ELEMENT_CLASS (g_type_class_peek_parent (klass))->send_event (element,