• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*  GStreamer video sink base class
2  *  Copyright (C) <2003> Julien Moutte <julien@moutte.net>
3  *  Copyright (C) <2009> Tim-Philipp Müller <tim centricular net>
4  *
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.
9  *
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.
14  *
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., 51 Franklin St, Fifth Floor,
18  * Boston, MA 02110-1301, USA.
19  */
20 
21 /**
22  * SECTION:gstvideosink
23  * @title: GstVideoSink
24  * @short_description: Base class for video sinks
25  *
26  * Provides useful functions and a base class for video sinks.
27  *
28  * GstVideoSink will configure the default base sink to drop frames that
29  * arrive later than 20ms as this is considered the default threshold for
30  * observing out-of-sync frames.
31  *
32  */
33 
34 #ifdef HAVE_CONFIG_H
35 #include "config.h"
36 #endif
37 
38 #include "gstvideosink.h"
39 
40 enum
41 {
42   PROP_SHOW_PREROLL_FRAME = 1
43 };
44 
45 #define DEFAULT_SHOW_PREROLL_FRAME TRUE
46 
47 struct _GstVideoSinkPrivate
48 {
49   GstVideoInfo info;
50   gboolean show_preroll_frame;  /* ATOMIC */
51 };
52 
53 G_DEFINE_TYPE_WITH_PRIVATE (GstVideoSink, gst_video_sink, GST_TYPE_BASE_SINK);
54 
55 #ifndef GST_DISABLE_GST_DEBUG
56 #define GST_CAT_DEFAULT gst_video_sink_ensure_debug_category()
57 
58 static GstDebugCategory *
gst_video_sink_ensure_debug_category(void)59 gst_video_sink_ensure_debug_category (void)
60 {
61   static gsize cat_gonce = 0;
62 
63   if (g_once_init_enter (&cat_gonce)) {
64     GstDebugCategory *cat = NULL;
65 
66     GST_DEBUG_CATEGORY_INIT (cat, "videosink", 0, "GstVideoSink");
67 
68     g_once_init_leave (&cat_gonce, (gsize) cat);
69   }
70 
71   return (GstDebugCategory *) cat_gonce;
72 }
73 #endif /* GST_DISABLE_GST_DEBUG */
74 
75 static GstBaseSinkClass *parent_class = NULL;
76 
77 static void gst_video_sink_set_property (GObject * object, guint prop_id,
78     const GValue * value, GParamSpec * pspec);
79 static void gst_video_sink_get_property (GObject * object, guint prop_id,
80     GValue * value, GParamSpec * pspec);
81 
82 static GstStateChangeReturn gst_video_sink_change_state (GstElement * element,
83     GstStateChange transition);
84 static GstFlowReturn gst_video_sink_show_preroll_frame (GstBaseSink * bsink,
85     GstBuffer * buf);
86 static GstFlowReturn gst_video_sink_show_frame (GstBaseSink * bsink,
87     GstBuffer * buf);
88 static gboolean gst_video_sink_set_caps (GstBaseSink * bsink, GstCaps * caps);
89 static void gst_video_sink_get_times (GstBaseSink * bsink, GstBuffer * buffer,
90     GstClockTime * start, GstClockTime * end);
91 
92 /**
93  * gst_video_sink_center_rect:
94  * @src: the #GstVideoRectangle describing the source area
95  * @dst: the #GstVideoRectangle describing the destination area
96  * @result: (out caller-allocates): a pointer to a #GstVideoRectangle which will receive the result area
97  * @scaling: a #gboolean indicating if scaling should be applied or not
98  *
99  * Deprecated: 1.20: Use gst_video_center_rect() instead.
100  */
101 void
gst_video_sink_center_rect(GstVideoRectangle src,GstVideoRectangle dst,GstVideoRectangle * result,gboolean scaling)102 gst_video_sink_center_rect (GstVideoRectangle src, GstVideoRectangle dst,
103     GstVideoRectangle * result, gboolean scaling)
104 {
105   gst_video_center_rect (&src, &dst, result, scaling);
106 }
107 
108 /**
109  * gst_video_center_rect:
110  * @src: a pointer to #GstVideoRectangle describing the source area
111  * @dst: a pointer to #GstVideoRectangle describing the destination area
112  * @result: (out caller-allocates): a pointer to a #GstVideoRectangle which will receive the result area
113  * @scaling: a #gboolean indicating if scaling should be applied or not
114  *
115  * Takes @src rectangle and position it at the center of @dst rectangle with or
116  * without @scaling. It handles clipping if the @src rectangle is bigger than
117  * the @dst one and @scaling is set to FALSE.
118  *
119  * Since: 1.20
120  */
121 void
gst_video_center_rect(const GstVideoRectangle * src,const GstVideoRectangle * dst,GstVideoRectangle * result,gboolean scaling)122 gst_video_center_rect (const GstVideoRectangle * src,
123     const GstVideoRectangle * dst, GstVideoRectangle * result, gboolean scaling)
124 {
125   g_return_if_fail (src != NULL);
126   g_return_if_fail (dst != NULL);
127   g_return_if_fail (result != NULL);
128 
129   if (!scaling) {
130     result->w = MIN (src->w, dst->w);
131     result->h = MIN (src->h, dst->h);
132     result->x = dst->x + (dst->w - result->w) / 2;
133     result->y = dst->y + (dst->h - result->h) / 2;
134   } else {
135     gdouble src_ratio, dst_ratio;
136 
137     g_return_if_fail (src->h != 0);
138     g_return_if_fail (dst->h != 0);
139 
140     src_ratio = (gdouble) src->w / src->h;
141     dst_ratio = (gdouble) dst->w / dst->h;
142 
143     if (src_ratio > dst_ratio) {
144       result->w = dst->w;
145       result->h = dst->w / src_ratio;
146       result->x = dst->x;
147       result->y = dst->y + (dst->h - result->h) / 2;
148     } else if (src_ratio < dst_ratio) {
149       result->w = dst->h * src_ratio;
150       result->h = dst->h;
151       result->x = dst->x + (dst->w - result->w) / 2;
152       result->y = dst->y;
153     } else {
154       result->x = dst->x;
155       result->y = dst->y;
156       result->w = dst->w;
157       result->h = dst->h;
158     }
159   }
160 
161   GST_DEBUG ("source is %dx%d dest is %dx%d, result is %dx%d with x,y %dx%d",
162       src->w, src->h, dst->w, dst->h,
163       result->w, result->h, result->x, result->y);
164 }
165 
166 /* Initing stuff */
167 
168 static void
gst_video_sink_init(GstVideoSink * videosink)169 gst_video_sink_init (GstVideoSink * videosink)
170 {
171   videosink->width = 0;
172   videosink->height = 0;
173 
174   /* 20ms is more than enough, 80-130ms is noticeable */
175   gst_base_sink_set_processing_deadline (GST_BASE_SINK (videosink),
176       15 * GST_MSECOND);
177   gst_base_sink_set_max_lateness (GST_BASE_SINK (videosink), 5 * GST_MSECOND);
178   gst_base_sink_set_qos_enabled (GST_BASE_SINK (videosink), TRUE);
179 
180   videosink->priv = gst_video_sink_get_instance_private (videosink);
181 }
182 
183 static void
gst_video_sink_class_init(GstVideoSinkClass * klass)184 gst_video_sink_class_init (GstVideoSinkClass * klass)
185 {
186   GstElementClass *element_class = (GstElementClass *) klass;
187   GstBaseSinkClass *basesink_class = (GstBaseSinkClass *) klass;
188   GObjectClass *gobject_class = (GObjectClass *) klass;
189 
190   parent_class = g_type_class_peek_parent (klass);
191 
192   gobject_class->set_property = gst_video_sink_set_property;
193   gobject_class->get_property = gst_video_sink_get_property;
194 
195   /**
196    * GstVideoSink:show-preroll-frame:
197    *
198    * Whether to show video frames during preroll. If set to %FALSE, video
199    * frames will only be rendered in PLAYING state.
200    */
201   g_object_class_install_property (gobject_class, PROP_SHOW_PREROLL_FRAME,
202       g_param_spec_boolean ("show-preroll-frame", "Show preroll frame",
203           "Whether to render video frames during preroll",
204           DEFAULT_SHOW_PREROLL_FRAME,
205           G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
206 
207   element_class->change_state = GST_DEBUG_FUNCPTR (gst_video_sink_change_state);
208 
209   basesink_class->render = GST_DEBUG_FUNCPTR (gst_video_sink_show_frame);
210   basesink_class->preroll =
211       GST_DEBUG_FUNCPTR (gst_video_sink_show_preroll_frame);
212   basesink_class->set_caps = GST_DEBUG_FUNCPTR (gst_video_sink_set_caps);
213   basesink_class->get_times = GST_DEBUG_FUNCPTR (gst_video_sink_get_times);
214 }
215 
216 static GstStateChangeReturn
gst_video_sink_change_state(GstElement * element,GstStateChange transition)217 gst_video_sink_change_state (GstElement * element, GstStateChange transition)
218 {
219   GstVideoSink *vsink;
220 
221   vsink = GST_VIDEO_SINK_CAST (element);
222 
223   switch (transition) {
224     case GST_STATE_CHANGE_READY_TO_PAUSED:
225       gst_video_info_init (&vsink->priv->info);
226       break;
227     default:
228       break;
229   }
230 
231   return GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
232 }
233 
234 static gboolean
gst_video_sink_set_caps(GstBaseSink * bsink,GstCaps * caps)235 gst_video_sink_set_caps (GstBaseSink * bsink, GstCaps * caps)
236 {
237   GstVideoSink *vsink;
238   GstVideoSinkClass *klass;
239   GstVideoInfo info;
240 
241   vsink = GST_VIDEO_SINK_CAST (bsink);
242   klass = GST_VIDEO_SINK_GET_CLASS (vsink);
243 
244   if (!gst_video_info_from_caps (&info, caps)) {
245     GST_ERROR_OBJECT (bsink, "Failed to parse caps %" GST_PTR_FORMAT, caps);
246     return FALSE;
247   }
248 
249   GST_DEBUG_OBJECT (bsink, "Setting caps %" GST_PTR_FORMAT, caps);
250   vsink->priv->info = info;
251 
252   if (klass->set_info)
253     return klass->set_info (vsink, caps, &vsink->priv->info);
254 
255   return TRUE;
256 }
257 
258 static void
gst_video_sink_get_times(GstBaseSink * bsink,GstBuffer * buffer,GstClockTime * start,GstClockTime * end)259 gst_video_sink_get_times (GstBaseSink * bsink, GstBuffer * buffer,
260     GstClockTime * start, GstClockTime * end)
261 {
262   GstVideoSink *vsink;
263   GstClockTime timestamp;
264 
265   vsink = GST_VIDEO_SINK_CAST (bsink);
266 
267   timestamp = GST_BUFFER_DTS_OR_PTS (buffer);
268   if (GST_CLOCK_TIME_IS_VALID (timestamp)) {
269     *start = timestamp;
270     if (GST_BUFFER_DURATION_IS_VALID (buffer)) {
271       *end = timestamp + GST_BUFFER_DURATION (buffer);
272     } else if (vsink->priv->info.fps_n > 0) {
273       *end = timestamp +
274           gst_util_uint64_scale_int (GST_SECOND, vsink->priv->info.fps_d,
275           vsink->priv->info.fps_n);
276     }
277   }
278 }
279 
280 static GstFlowReturn
gst_video_sink_show_preroll_frame(GstBaseSink * bsink,GstBuffer * buf)281 gst_video_sink_show_preroll_frame (GstBaseSink * bsink, GstBuffer * buf)
282 {
283   GstVideoSinkClass *klass;
284   GstVideoSink *vsink;
285   gboolean do_show;
286 
287   vsink = GST_VIDEO_SINK_CAST (bsink);
288   klass = GST_VIDEO_SINK_GET_CLASS (vsink);
289 
290   do_show = g_atomic_int_get (&vsink->priv->show_preroll_frame);
291 
292   if (G_UNLIKELY (!do_show)) {
293     GST_DEBUG_OBJECT (bsink, "not rendering frame with ts=%" GST_TIME_FORMAT
294         ", preroll rendering disabled",
295         GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)));
296   }
297 
298   if (klass->show_frame == NULL || !do_show) {
299     if (parent_class->preroll != NULL)
300       return parent_class->preroll (bsink, buf);
301     else
302       return GST_FLOW_OK;
303   }
304 
305   GST_LOG_OBJECT (bsink, "rendering frame, ts=%" GST_TIME_FORMAT,
306       GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)));
307 
308   return klass->show_frame (GST_VIDEO_SINK_CAST (bsink), buf);
309 }
310 
311 static GstFlowReturn
gst_video_sink_show_frame(GstBaseSink * bsink,GstBuffer * buf)312 gst_video_sink_show_frame (GstBaseSink * bsink, GstBuffer * buf)
313 {
314   GstVideoSinkClass *klass;
315 
316   klass = GST_VIDEO_SINK_GET_CLASS (bsink);
317 
318   if (klass->show_frame == NULL) {
319     if (parent_class->render != NULL)
320       return parent_class->render (bsink, buf);
321     else
322       return GST_FLOW_OK;
323   }
324 
325   GST_LOG_OBJECT (bsink, "rendering frame, ts=%" GST_TIME_FORMAT,
326       GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)));
327 
328   return klass->show_frame (GST_VIDEO_SINK_CAST (bsink), buf);
329 }
330 
331 static void
gst_video_sink_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)332 gst_video_sink_set_property (GObject * object, guint prop_id,
333     const GValue * value, GParamSpec * pspec)
334 {
335   GstVideoSink *vsink;
336 
337   vsink = GST_VIDEO_SINK (object);
338 
339   switch (prop_id) {
340     case PROP_SHOW_PREROLL_FRAME:
341       g_atomic_int_set (&vsink->priv->show_preroll_frame,
342           g_value_get_boolean (value));
343       break;
344     default:
345       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
346       break;
347   }
348 }
349 
350 static void
gst_video_sink_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)351 gst_video_sink_get_property (GObject * object, guint prop_id,
352     GValue * value, GParamSpec * pspec)
353 {
354   GstVideoSink *vsink;
355 
356   vsink = GST_VIDEO_SINK (object);
357 
358   switch (prop_id) {
359     case PROP_SHOW_PREROLL_FRAME:
360       g_value_set_boolean (value,
361           g_atomic_int_get (&vsink->priv->show_preroll_frame));
362       break;
363     default:
364       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
365       break;
366   }
367 }
368