/* * Clutter-GStreamer. * * GStreamer integration library for Clutter. * * Authored By Lionel Landwerlin * Bastian Winkler * * Copyright (C) 2013 Intel Corporation * Copyright (C) 2013 Bastian Winkler * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ /** * SECTION:clutter-gst-content * @short_description: A #ClutterContent for displaying video frames. * * #ClutterGstContent implements the #ClutterContent interface. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "clutter-gst-content.h" #include "clutter-gst-private.h" #include "clutter-gst-marshal.h" static void content_iface_init (ClutterContentIface *iface); G_DEFINE_TYPE_WITH_CODE (ClutterGstContent, clutter_gst_content, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_CONTENT, content_iface_init)) #define CLUTTER_GST_CONTENT_GET_PRIVATE(obj)\ (G_TYPE_INSTANCE_GET_PRIVATE ((obj), \ CLUTTER_GST_TYPE_CONTENT, \ ClutterGstContentPrivate)) struct _ClutterGstContentPrivate { ClutterGstVideoSink *sink; ClutterGstPlayer *player; ClutterGstFrame *current_frame; ClutterGstOverlays *overlays; gboolean paint_frame; gboolean paint_overlays; }; enum { PROP_0, PROP_FRAME, PROP_SINK, PROP_PLAYER, PROP_PAINT_FRAME, PROP_PAINT_OVERLAYS, PROP_LAST }; static GParamSpec *props[PROP_LAST]; enum { SIZE_CHANGE, LAST_SIGNAL }; static guint signals[LAST_SIGNAL]; /**/ gboolean clutter_gst_content_get_paint_frame (ClutterGstContent *self) { return self->priv->paint_frame; } static void clutter_gst_content_set_paint_frame (ClutterGstContent *self, gboolean value) { if (self->priv->paint_frame == value) return; self->priv->paint_frame = value; clutter_content_invalidate (CLUTTER_CONTENT (self)); } gboolean clutter_gst_content_get_paint_overlays (ClutterGstContent *self) { return self->priv->paint_overlays; } static void clutter_gst_content_set_paint_overlays (ClutterGstContent *self, gboolean value) { if (self->priv->paint_overlays == value) return; self->priv->paint_overlays = value; clutter_content_invalidate (CLUTTER_CONTENT (self)); } static gboolean clutter_gst_content_has_painting_content (ClutterGstContent *self) { ClutterGstContentPrivate *priv = self->priv; if (priv->paint_frame && priv->current_frame) return TRUE; if (priv->paint_overlays && priv->overlays && priv->overlays->overlays->len > 0) return TRUE; return FALSE; } /**/ static void update_frame (ClutterGstContent *self, ClutterGstFrame *new_frame) { ClutterGstContentPrivate *priv = self->priv; ClutterGstFrame *old_frame; old_frame = priv->current_frame; priv->current_frame = g_boxed_copy (CLUTTER_GST_TYPE_FRAME, new_frame); if (old_frame) { new_frame->resolution.par_n = old_frame->resolution.par_n; new_frame->resolution.par_d = old_frame->resolution.par_d; } if (!old_frame || (new_frame->resolution.width != old_frame->resolution.width || new_frame->resolution.height != old_frame->resolution.height)) { g_signal_emit (self, signals[SIZE_CHANGE], 0, new_frame->resolution.width, new_frame->resolution.height); } if (old_frame) g_boxed_free (CLUTTER_GST_TYPE_FRAME, old_frame); } static void update_overlays (ClutterGstContent *self, ClutterGstOverlays *new_overlays) { ClutterGstContentPrivate *priv = self->priv; if (priv->overlays != NULL) { g_boxed_free (CLUTTER_GST_TYPE_OVERLAYS, priv->overlays); priv->overlays = NULL; } if (new_overlays != NULL) priv->overlays = g_boxed_copy (CLUTTER_GST_TYPE_OVERLAYS, new_overlays); } static void _new_frame_from_pipeline (ClutterGstVideoSink *sink, ClutterGstContent *self) { update_frame (self, clutter_gst_video_sink_get_frame (sink)); if (CLUTTER_GST_CONTENT_GET_CLASS (self)->has_painting_content (self)) clutter_content_invalidate (CLUTTER_CONTENT (self)); } static void _new_overlays_from_pipeline (ClutterGstVideoSink *sink, ClutterGstContent *self) { update_overlays (self, clutter_gst_video_sink_get_overlays (sink)); if (CLUTTER_GST_CONTENT_GET_CLASS (self)->has_painting_content (self)) clutter_content_invalidate (CLUTTER_CONTENT (self)); } static void _pixel_aspect_ratio_changed (ClutterGstVideoSink *sink, GParamSpec *pspec, ClutterGstContent *self) { clutter_gst_frame_update_pixel_aspect_ratio (self->priv->current_frame, sink); } static void content_set_sink (ClutterGstContent *self, ClutterGstVideoSink *sink, gboolean set_from_player); static void content_set_player (ClutterGstContent *self, ClutterGstPlayer *player) { ClutterGstContentPrivate *priv = self->priv; if (priv->player == player) return; if (priv->player) g_clear_object (&priv->player); if (player) { priv->player = g_object_ref_sink (player); content_set_sink (self, clutter_gst_player_get_video_sink (player), TRUE); } else content_set_sink (self, NULL, TRUE); g_object_notify (G_OBJECT (self), "player"); } static void content_set_sink (ClutterGstContent *self, ClutterGstVideoSink *sink, gboolean set_from_player) { ClutterGstContentPrivate *priv = self->priv; if (priv->sink == sink) return; if (!set_from_player) content_set_player (self, NULL); if (priv->sink) { g_signal_handlers_disconnect_by_func (priv->sink, _new_frame_from_pipeline, self); g_signal_handlers_disconnect_by_func (priv->sink, _pixel_aspect_ratio_changed, self); g_clear_object (&priv->sink); } if (sink) { priv->sink = g_object_ref_sink (sink); g_signal_connect (priv->sink, "new-frame", G_CALLBACK (_new_frame_from_pipeline), self); g_signal_connect (priv->sink, "new-overlays", G_CALLBACK (_new_overlays_from_pipeline), self); g_signal_connect (priv->sink, "notify::pixel-aspect-ratio", G_CALLBACK (_pixel_aspect_ratio_changed), self); if (clutter_gst_video_sink_is_ready (priv->sink)) { update_frame (self, clutter_gst_video_sink_get_frame (priv->sink)); update_overlays (self, clutter_gst_video_sink_get_overlays (priv->sink)); } } g_object_notify (G_OBJECT (self), "sink"); } static void content_set_frame (ClutterGstContent *self, ClutterGstFrame *frame) { ClutterGstContentPrivate *priv = self->priv; if (!frame) { if (priv->current_frame) { g_boxed_free (CLUTTER_GST_TYPE_FRAME, priv->current_frame); priv->current_frame = NULL; clutter_content_invalidate (CLUTTER_CONTENT (self)); } return; } update_frame (self, frame); if (CLUTTER_GST_CONTENT_GET_CLASS (self)->has_painting_content (self)) clutter_content_invalidate (CLUTTER_CONTENT (self)); } static gboolean clutter_gst_content_get_preferred_size (ClutterContent *content, gfloat *width, gfloat *height) { ClutterGstContentPrivate *priv = CLUTTER_GST_CONTENT (content)->priv; if (!priv->current_frame) return FALSE; if (width) *width = priv->current_frame->resolution.width; if (height) *height = priv->current_frame->resolution.height; return TRUE; } static void clutter_gst_content_paint_content (ClutterContent *content, ClutterActor *actor, ClutterPaintNode *root) { ClutterGstContent *self = CLUTTER_GST_CONTENT (content); ClutterGstContentPrivate *priv = self->priv; ClutterActorBox box; ClutterPaintNode *node; ClutterContentRepeat repeat; guint8 paint_opacity; clutter_actor_get_content_box (actor, &box); paint_opacity = clutter_actor_get_paint_opacity (actor); /* No content: paint background color */ if (!CLUTTER_GST_CONTENT_GET_CLASS (self)->has_painting_content (self)) { ClutterColor color; clutter_actor_get_background_color (actor, &color); color.alpha = paint_opacity; node = clutter_color_node_new (&color); clutter_paint_node_set_name (node, "IdleVideo"); clutter_paint_node_add_child (root, node); clutter_paint_node_unref (node); return; } repeat = clutter_actor_get_content_repeat (actor); if (priv->paint_frame && priv->current_frame) { cogl_pipeline_set_color4ub (priv->current_frame->pipeline, paint_opacity, paint_opacity, paint_opacity, paint_opacity); node = clutter_pipeline_node_new (priv->current_frame->pipeline); clutter_paint_node_set_name (node, "Video"); if (repeat == CLUTTER_REPEAT_NONE) clutter_paint_node_add_rectangle (node, &box); else { float t_w = 1.f, t_h = 1.f; if ((repeat & CLUTTER_REPEAT_X_AXIS) != FALSE) t_w = (box.x2 - box.x1) / priv->current_frame->resolution.width; if ((repeat & CLUTTER_REPEAT_Y_AXIS) != FALSE) t_h = (box.y2 - box.y1) / priv->current_frame->resolution.height; clutter_paint_node_add_texture_rectangle (node, &box, 0.f, 0.f, t_w, t_h); } clutter_paint_node_add_child (root, node); clutter_paint_node_unref (node); } if (priv->paint_overlays && priv->overlays) { guint i; for (i = 0; i < priv->overlays->overlays->len; i++) { ClutterGstOverlay *overlay = g_ptr_array_index (priv->overlays->overlays, i); gfloat box_width = clutter_actor_box_get_width (&box), box_height = clutter_actor_box_get_height (&box); ClutterActorBox obox = { overlay->position.x1 * box_width / priv->current_frame->resolution.width, overlay->position.y1 * box_height / priv->current_frame->resolution.height, overlay->position.x2 * box_width / priv->current_frame->resolution.width, overlay->position.y2 * box_height / priv->current_frame->resolution.height }; cogl_pipeline_set_color4ub (overlay->pipeline, paint_opacity, paint_opacity, paint_opacity, paint_opacity); node = clutter_pipeline_node_new (overlay->pipeline); clutter_paint_node_set_name (node, "VideoOverlay"); clutter_paint_node_add_texture_rectangle (node, &obox, 0, 0, 1, 1); clutter_paint_node_add_child (root, node); clutter_paint_node_unref (node); } } } static void content_iface_init (ClutterContentIface *iface) { iface->get_preferred_size = clutter_gst_content_get_preferred_size; iface->paint_content = clutter_gst_content_paint_content; } static void clutter_gst_content_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { ClutterGstContent *self = CLUTTER_GST_CONTENT (object); switch (prop_id) { case PROP_FRAME: content_set_frame (self, g_value_get_boxed (value)); break; case PROP_SINK: content_set_sink (self, g_value_get_object (value), FALSE); break; case PROP_PLAYER: content_set_player (self, CLUTTER_GST_PLAYER (g_value_get_object (value))); break; case PROP_PAINT_FRAME: clutter_gst_content_set_paint_frame (self, g_value_get_boolean (value)); break; case PROP_PAINT_OVERLAYS: clutter_gst_content_set_paint_overlays (self, g_value_get_boolean (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void clutter_gst_content_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { ClutterGstContentPrivate *priv = CLUTTER_GST_CONTENT (object)->priv; switch (prop_id) { case PROP_FRAME: g_value_set_boxed (value, priv->current_frame); break; case PROP_SINK: g_value_set_object (value, priv->sink); break; case PROP_PLAYER: g_value_set_object (value, priv->player); break; case PROP_PAINT_FRAME: g_value_set_boolean (value, priv->paint_frame); break; case PROP_PAINT_OVERLAYS: g_value_set_boolean (value, priv->paint_overlays); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void clutter_gst_content_dispose (GObject *object) { ClutterGstContentPrivate *priv = CLUTTER_GST_CONTENT (object)->priv; g_clear_object (&priv->sink); if (priv->current_frame) { g_boxed_free (CLUTTER_GST_TYPE_FRAME, priv->current_frame); priv->current_frame = NULL; } G_OBJECT_CLASS (clutter_gst_content_parent_class)->dispose (object); } static void clutter_gst_content_finalize (GObject *object) { G_OBJECT_CLASS (clutter_gst_content_parent_class)->finalize (object); } static void clutter_gst_content_class_init (ClutterGstContentClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); gobject_class->set_property = clutter_gst_content_set_property; gobject_class->get_property = clutter_gst_content_get_property; gobject_class->dispose = clutter_gst_content_dispose; gobject_class->finalize = clutter_gst_content_finalize; klass->has_painting_content = clutter_gst_content_has_painting_content; g_type_class_add_private (klass, sizeof (ClutterGstContentPrivate)); props[PROP_PLAYER] = g_param_spec_object ("player", "ClutterGst Player", "ClutterGst Player", G_TYPE_OBJECT, G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE); props[PROP_FRAME] = g_param_spec_boxed ("frame", "ClutterGst Frame", "ClutterGst Frame", CLUTTER_GST_TYPE_FRAME, G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE); props[PROP_SINK] = g_param_spec_object ("sink", "Cogl Video Sink", "Cogl Video Sink", CLUTTER_GST_TYPE_VIDEO_SINK, G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE); props[PROP_PAINT_FRAME] = g_param_spec_boolean ("paint-frame", "Paint Video Overlays", "Paint Video Overlays", TRUE, G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE); props[PROP_PAINT_OVERLAYS] = g_param_spec_boolean ("paint-overlays", "Paint Video Overlays", "Paint Video Overlays", TRUE, G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE); g_object_class_install_properties (gobject_class, PROP_LAST, props); /** * ClutterGstContent::size-change: * @content: the #ClutterGstContent instance that received the signal * @width: new width of the frames * @height: new height of the frames * * The ::size-change signal is emitted each time the video size changes. */ signals[SIZE_CHANGE] = g_signal_new ("size-change", CLUTTER_GST_TYPE_CONTENT, G_SIGNAL_RUN_LAST, 0, NULL, NULL, _clutter_gst_marshal_VOID__INT_INT, G_TYPE_NONE, 2, G_TYPE_INT, G_TYPE_INT); } static void clutter_gst_content_init (ClutterGstContent *self) { ClutterGstContentPrivate *priv; self->priv = priv = CLUTTER_GST_CONTENT_GET_PRIVATE (self); content_set_sink (self, CLUTTER_GST_VIDEO_SINK (clutter_gst_create_video_sink ()), FALSE); priv->paint_frame = TRUE; priv->paint_overlays = TRUE; } /** * clutter_gst_content_new: * * Returns: (transfer full): a new #ClutterGstContent instance */ ClutterContent * clutter_gst_content_new (void) { return g_object_new (CLUTTER_GST_TYPE_CONTENT, NULL); } /** * clutter_gst_content_new_with_sink: * * Returns: (transfer full): a new #ClutterGstContent instance * * Since: 3.0 */ ClutterContent * clutter_gst_content_new_with_sink (ClutterGstVideoSink *sink) { return g_object_new (CLUTTER_GST_TYPE_CONTENT, "sink", sink, NULL); } /** * clutter_gst_content_get_frame: * @self: A #ClutterGstContent * * Returns: (transfer none): The #ClutterGstFrame currently attached to @self. * * Since: 3.0 */ ClutterGstFrame * clutter_gst_content_get_frame (ClutterGstContent *self) { g_return_val_if_fail (CLUTTER_GST_IS_CONTENT (self), NULL); return self->priv->current_frame; } /** * clutter_gst_content_set_frame: * @self: A #ClutterGstContent * @frame: A #ClutterGstFrame * * Set the current frame. * * Since: 3.0 */ void clutter_gst_content_set_frame (ClutterGstContent *self, ClutterGstFrame *frame) { g_return_if_fail (CLUTTER_GST_IS_CONTENT (self)); content_set_frame (self, frame); } /** * clutter_gst_content_get_overlays: * @self: A #ClutterGstContent * * Returns: (transfer none): The #ClutterGstOverlays currently attached to @self. * * Since: 3.0 */ ClutterGstOverlays * clutter_gst_content_get_overlays (ClutterGstContent *self) { g_return_val_if_fail (CLUTTER_GST_IS_CONTENT (self), NULL); return self->priv->overlays; } /** * clutter_gst_content_get_sink: * @self: A #ClutterGstContent * * Returns: (transfer none): The #ClutterGstVideoSink currently attached to @self. * * Since: 3.0 */ ClutterGstVideoSink * clutter_gst_content_get_sink (ClutterGstContent *self) { g_return_val_if_fail (CLUTTER_GST_IS_CONTENT (self), NULL); return self->priv->sink; } /** * clutter_gst_content_set_sink: * @self: A #ClutterGstContent * @sink: A #ClutterGstVideoSink or %NULL * * Since: 3.0 */ void clutter_gst_content_set_sink (ClutterGstContent *self, ClutterGstVideoSink *sink) { g_return_if_fail (CLUTTER_GST_IS_CONTENT (self)); g_return_if_fail (sink == NULL || CLUTTER_GST_IS_VIDEO_SINK (sink)); content_set_sink (self, sink, FALSE); } /** * clutter_gst_content_get_player: * @self: A #ClutterGstContent * * Returns: (transfer none): The #ClutterGstPlayer currently attached to @self. * * Since: 3.0 */ ClutterGstPlayer * clutter_gst_content_get_player (ClutterGstContent *self) { g_return_val_if_fail (CLUTTER_GST_IS_CONTENT (self), NULL); return self->priv->player; } /** * clutter_gst_content_set_player: * @self: A #ClutterGstContent * @player: A #ClutterGstPlayer or %NULL * * Since: 3.0 */ void clutter_gst_content_set_player (ClutterGstContent *self, ClutterGstPlayer *player) { g_return_if_fail (CLUTTER_GST_IS_CONTENT (self)); g_return_if_fail (player == NULL || CLUTTER_GST_IS_PLAYER (player)); content_set_player (self, player); }