1 /*
2 * GStreamer
3 * Copyright (C) 2015 Thiago Santos <thiagoss@osg.samsung.com>
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 /**
23 * SECTION:element-digitalzoom
24 * @title: digitalzoom
25 *
26 * Does digital zooming by cropping and scaling an image.
27 *
28 * It is a bin that contains the internal pipeline:
29 * videocrop ! videoscale ! capsfilter
30 *
31 * It keeps monitoring the input caps and when it is set/updated
32 * the capsfilter gets set the same caps to guarantee that the same
33 * input resolution is provided as output.
34 *
35 * Exposes the 'zoom' property as a float to allow setting the amount
36 * of zoom desired. Zooming is done in the center.
37 */
38
39 #ifdef HAVE_CONFIG_H
40 # include <config.h>
41 #endif
42
43 #include <gst/gst-i18n-plugin.h>
44 #include "gstdigitalzoom.h"
45
46 enum
47 {
48 PROP_0,
49 PROP_ZOOM
50 };
51
52 GST_DEBUG_CATEGORY (digital_zoom_debug);
53 #define GST_CAT_DEFAULT digital_zoom_debug
54
55 #define gst_digital_zoom_parent_class parent_class
56 G_DEFINE_TYPE (GstDigitalZoom, gst_digital_zoom, GST_TYPE_BIN);
57
58 static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
59 GST_PAD_SRC,
60 GST_PAD_ALWAYS,
61 GST_STATIC_CAPS_ANY);
62
63 static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
64 GST_PAD_SINK,
65 GST_PAD_ALWAYS,
66 GST_STATIC_CAPS_ANY);
67
68 static void
gst_digital_zoom_update_crop(GstDigitalZoom * self,GstCaps * caps)69 gst_digital_zoom_update_crop (GstDigitalZoom * self, GstCaps * caps)
70 {
71 gint w2_crop = 0, h2_crop = 0;
72 gint left = 0;
73 gint right = 0;
74 gint top = 0;
75 gint bottom = 0;
76 gint width, height;
77 gfloat zoom;
78 GstStructure *structure;
79
80 if (caps == NULL || gst_caps_is_any (caps)) {
81 g_object_set (self->capsfilter, "caps", NULL, NULL);
82 return;
83 }
84
85 structure = gst_caps_get_structure (caps, 0);
86 gst_structure_get (structure, "width", G_TYPE_INT, &width, "height",
87 G_TYPE_INT, &height, NULL);
88
89 zoom = self->zoom;
90
91 if (self->videocrop) {
92 /* Update capsfilters to apply the zoom */
93 GST_INFO_OBJECT (self, "zoom: %f, orig size: %dx%d", zoom, width, height);
94
95 if (zoom != 1.0) {
96 w2_crop = (width - (gint) (width * 1.0 / zoom)) / 2;
97 h2_crop = (height - (gint) (height * 1.0 / zoom)) / 2;
98
99 left += w2_crop;
100 right += w2_crop;
101 top += h2_crop;
102 bottom += h2_crop;
103
104 /* force number of pixels cropped from left to be even, to avoid slow code
105 * path on videoscale */
106 left &= 0xFFFE;
107 }
108
109 GST_INFO_OBJECT (self,
110 "sw cropping: left:%d, right:%d, top:%d, bottom:%d", left, right, top,
111 bottom);
112
113 g_object_set (self->videocrop, "left", left, "right", right, "top",
114 top, "bottom", bottom, NULL);
115 }
116 }
117
118 static void
gst_digital_zoom_update_zoom(GstDigitalZoom * self)119 gst_digital_zoom_update_zoom (GstDigitalZoom * self)
120 {
121 GstCaps *caps = NULL;
122
123 if (!self->elements_created)
124 return;
125
126 g_object_get (self->capsfilter, "caps", &caps, NULL);
127 if (caps) {
128 gst_digital_zoom_update_crop (self, caps);
129 gst_caps_unref (caps);
130 }
131 }
132
133 static void
gst_digital_zoom_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)134 gst_digital_zoom_set_property (GObject * object,
135 guint prop_id, const GValue * value, GParamSpec * pspec)
136 {
137 GstDigitalZoom *self = GST_DIGITAL_ZOOM_CAST (object);
138
139 switch (prop_id) {
140 case PROP_ZOOM:
141 self->zoom = g_value_get_float (value);
142 GST_DEBUG_OBJECT (self, "Setting zoom: %f", self->zoom);
143 gst_digital_zoom_update_zoom (self);
144 break;
145 default:
146 G_OBJECT_WARN_INVALID_PROPERTY_ID (self, prop_id, pspec);
147 break;
148 }
149 }
150
151 static void
gst_digital_zoom_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)152 gst_digital_zoom_get_property (GObject * object,
153 guint prop_id, GValue * value, GParamSpec * pspec)
154 {
155 GstDigitalZoom *self = GST_DIGITAL_ZOOM_CAST (object);
156
157 switch (prop_id) {
158 case PROP_ZOOM:
159 g_value_set_float (value, self->zoom);
160 break;
161 default:
162 G_OBJECT_WARN_INVALID_PROPERTY_ID (self, prop_id, pspec);
163 break;
164 }
165 }
166
167 static gboolean
gst_digital_zoom_sink_query(GstPad * sink,GstObject * parent,GstQuery * query)168 gst_digital_zoom_sink_query (GstPad * sink, GstObject * parent,
169 GstQuery * query)
170 {
171 GstDigitalZoom *self = GST_DIGITAL_ZOOM_CAST (parent);
172 switch (GST_QUERY_TYPE (query)) {
173 /* for caps related queries we want to skip videocrop ! videoscale
174 * as the digital zoom preserves input dimensions */
175 case GST_QUERY_CAPS:
176 case GST_QUERY_ACCEPT_CAPS:
177 if (self->elements_created)
178 return gst_pad_peer_query (self->srcpad, query);
179 /* fall through */
180 default:
181 return gst_pad_query_default (sink, parent, query);
182 }
183 }
184
185 static gboolean
gst_digital_zoom_src_query(GstPad * sink,GstObject * parent,GstQuery * query)186 gst_digital_zoom_src_query (GstPad * sink, GstObject * parent, GstQuery * query)
187 {
188 GstDigitalZoom *self = GST_DIGITAL_ZOOM_CAST (parent);
189 switch (GST_QUERY_TYPE (query)) {
190 /* for caps related queries we want to skip videocrop ! videoscale
191 * as the digital zoom preserves input dimensions */
192 case GST_QUERY_CAPS:
193 case GST_QUERY_ACCEPT_CAPS:
194 if (self->elements_created)
195 return gst_pad_peer_query (self->sinkpad, query);
196 /* fall through */
197 default:
198 return gst_pad_query_default (sink, parent, query);
199 }
200 }
201
202 static gboolean
gst_digital_zoom_sink_event(GstPad * sink,GstObject * parent,GstEvent * event)203 gst_digital_zoom_sink_event (GstPad * sink, GstObject * parent,
204 GstEvent * event)
205 {
206 gboolean ret;
207 gboolean is_caps;
208 GstDigitalZoom *self = GST_DIGITAL_ZOOM_CAST (parent);
209 GstCaps *old_caps = NULL;
210 GstCaps *caps = NULL;
211
212 is_caps = GST_EVENT_TYPE (event) == GST_EVENT_CAPS;
213
214 if (is_caps) {
215 gst_event_parse_caps (event, &caps);
216 g_object_get (self->capsfilter, "caps", &old_caps, NULL);
217 g_object_set (self->capsfilter, "caps", caps, NULL);
218 gst_digital_zoom_update_crop (self, caps);
219 }
220
221 ret = gst_pad_event_default (sink, parent, event);
222
223 if (is_caps) {
224 if (!ret) {
225 gst_digital_zoom_update_crop (self, old_caps);
226 g_object_set (self->capsfilter, "caps", old_caps, NULL);
227 }
228
229 if (old_caps)
230 gst_caps_unref (old_caps);
231 }
232
233 return ret;
234 }
235
236 static void
gst_digital_zoom_dispose(GObject * object)237 gst_digital_zoom_dispose (GObject * object)
238 {
239 GstDigitalZoom *self = GST_DIGITAL_ZOOM_CAST (object);
240
241 if (self->capsfilter_sinkpad) {
242 gst_object_unref (self->capsfilter_sinkpad);
243 self->capsfilter_sinkpad = NULL;
244 }
245
246 G_OBJECT_CLASS (parent_class)->dispose (object);
247 }
248
249 static void
gst_digital_zoom_init(GstDigitalZoom * self)250 gst_digital_zoom_init (GstDigitalZoom * self)
251 {
252 GstPadTemplate *tmpl;
253
254 tmpl = gst_static_pad_template_get (&src_template);
255 self->srcpad = gst_ghost_pad_new_no_target_from_template ("src", tmpl);
256 gst_element_add_pad (GST_ELEMENT (self), self->srcpad);
257 gst_object_unref (tmpl);
258
259 tmpl = gst_static_pad_template_get (&sink_template);
260 self->sinkpad = gst_ghost_pad_new_no_target_from_template ("sink", tmpl);
261 gst_element_add_pad (GST_ELEMENT (self), self->sinkpad);
262 gst_object_unref (tmpl);
263
264 gst_pad_set_event_function (self->sinkpad,
265 GST_DEBUG_FUNCPTR (gst_digital_zoom_sink_event));
266 gst_pad_set_query_function (self->sinkpad,
267 GST_DEBUG_FUNCPTR (gst_digital_zoom_sink_query));
268
269 gst_pad_set_query_function (self->srcpad,
270 GST_DEBUG_FUNCPTR (gst_digital_zoom_src_query));
271
272 self->zoom = 1;
273 }
274
275 static GstElement *
zoom_create_element(GstDigitalZoom * self,const gchar * element_name,const gchar * name)276 zoom_create_element (GstDigitalZoom * self, const gchar * element_name,
277 const gchar * name)
278 {
279 GstElement *element;
280 element = gst_element_factory_make (element_name, name);
281 if (element == NULL) {
282 GST_ELEMENT_ERROR (self, CORE, MISSING_PLUGIN,
283 (_("Missing element '%s' - check your GStreamer installation."),
284 element_name), (NULL));
285 }
286 return element;
287 }
288
289 static gboolean
gst_digital_zoom_create_elements(GstDigitalZoom * self)290 gst_digital_zoom_create_elements (GstDigitalZoom * self)
291 {
292 GstPad *pad;
293
294 if (self->elements_created)
295 return TRUE;
296
297 self->videocrop = zoom_create_element (self, "videocrop", "zoom-videocrop");
298 if (self->videocrop == NULL)
299 return FALSE;
300 if (!gst_bin_add (GST_BIN_CAST (self), self->videocrop))
301 return FALSE;
302
303 self->videoscale =
304 zoom_create_element (self, "videoscale", "zoom-videoscale");
305 if (self->videoscale == NULL)
306 return FALSE;
307 if (!gst_bin_add (GST_BIN_CAST (self), self->videoscale))
308 return FALSE;
309
310 self->capsfilter =
311 zoom_create_element (self, "capsfilter", "zoom-capsfilter");
312 if (self->capsfilter == NULL)
313 return FALSE;
314 if (!gst_bin_add (GST_BIN_CAST (self), self->capsfilter))
315 return FALSE;
316
317 if (!gst_element_link_pads_full (self->videocrop, "src", self->videoscale,
318 "sink", GST_PAD_LINK_CHECK_CAPS))
319 return FALSE;
320 if (!gst_element_link_pads_full (self->videoscale, "src", self->capsfilter,
321 "sink", GST_PAD_LINK_CHECK_CAPS))
322 return FALSE;
323
324 pad = gst_element_get_static_pad (self->videocrop, "sink");
325 gst_ghost_pad_set_target (GST_GHOST_PAD (self->sinkpad), pad);
326 gst_object_unref (pad);
327
328 pad = gst_element_get_static_pad (self->capsfilter, "src");
329 gst_ghost_pad_set_target (GST_GHOST_PAD (self->srcpad), pad);
330 gst_object_unref (pad);
331
332 self->capsfilter_sinkpad =
333 gst_element_get_static_pad (self->capsfilter, "sink");
334
335 self->elements_created = TRUE;
336 return TRUE;
337 }
338
339 static GstStateChangeReturn
gst_digital_zoom_change_state(GstElement * element,GstStateChange trans)340 gst_digital_zoom_change_state (GstElement * element, GstStateChange trans)
341 {
342 GstDigitalZoom *self = GST_DIGITAL_ZOOM_CAST (element);
343
344 switch (trans) {
345 case GST_STATE_CHANGE_NULL_TO_READY:
346 if (!gst_digital_zoom_create_elements (self)) {
347 return GST_STATE_CHANGE_FAILURE;
348 }
349 break;
350 default:
351 break;
352 }
353
354 return GST_ELEMENT_CLASS (parent_class)->change_state (element, trans);
355 }
356
357 static void
gst_digital_zoom_class_init(GstDigitalZoomClass * klass)358 gst_digital_zoom_class_init (GstDigitalZoomClass * klass)
359 {
360 GObjectClass *gobject_class;
361 GstElementClass *gstelement_class;
362
363 gobject_class = G_OBJECT_CLASS (klass);
364 gstelement_class = GST_ELEMENT_CLASS (klass);
365
366 gobject_class->dispose = gst_digital_zoom_dispose;
367 gobject_class->set_property = gst_digital_zoom_set_property;
368 gobject_class->get_property = gst_digital_zoom_get_property;
369
370 /* g_object_class_install_property .... */
371 g_object_class_install_property (gobject_class, PROP_ZOOM,
372 g_param_spec_float ("zoom", "Zoom",
373 "Digital zoom level to be used", 1.0, G_MAXFLOAT, 1.0,
374 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
375 gstelement_class->change_state = gst_digital_zoom_change_state;
376
377 GST_DEBUG_CATEGORY_INIT (digital_zoom_debug, "digitalzoom",
378 0, "digital zoom");
379
380 gst_element_class_add_static_pad_template (gstelement_class, &sink_template);
381 gst_element_class_add_static_pad_template (gstelement_class, &src_template);
382
383 gst_element_class_set_static_metadata (gstelement_class,
384 "Digital zoom bin", "Generic/Video",
385 "Digital zoom bin", "Thiago Santos <thiagoss@osg.samsung.com>");
386 }
387