93bb207733efc4f63873ca28b2de1bfc9e08c79f
[platform/upstream/gstreamer.git] / sys / v4l2 / gstv4l2sink.c
1 /* GStreamer
2  *
3  * Copyright (C) 2009 Texas Instruments, Inc - http://www.ti.com/
4  *
5  * Description: V4L2 sink element
6  *  Created on: Jul 2, 2009
7  *      Author: Rob Clark <rob@ti.com>
8  *
9  * This library is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Library General Public
11  * License as published by the Free Software Foundation; either
12  * version 2 of the License, or (at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Library General Public License for more details.
18  *
19  * You should have received a copy of the GNU Library General Public
20  * License along with this library; if not, write to the
21  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
22  * Boston, MA 02111-1307, USA.
23  */
24
25 /**
26  * SECTION:element-v4l2sink
27  *
28  * v4l2sink can be used to display video to v4l2 devices (screen overlays
29  * provided by the graphics hardware, tv-out, etc)
30  *
31  * <refsect2>
32  * <title>Example launch lines</title>
33  * |[
34  * gst-launch videotestsrc ! v4l2sink device=/dev/video1
35  * ]| This pipeline displays a test pattern on /dev/video1
36  * |[
37  * gst-launch -v videotestsrc ! navigationtest ! v4l2sink
38  * ]| A pipeline to test navigation events.
39  * While moving the mouse pointer over the test signal you will see a black box
40  * following the mouse pointer. If you press the mouse button somewhere on the
41  * video and release it somewhere else a green box will appear where you pressed
42  * the button and a red one where you released it. (The navigationtest element
43  * is part of gst-plugins-good.) You can observe here that even if the images
44  * are scaled through hardware the pointer coordinates are converted back to the
45  * original video frame geometry so that the box can be drawn to the correct
46  * position. This also handles borders correctly, limiting coordinates to the
47  * image area
48  * </refsect2>
49  */
50
51
52 #ifdef HAVE_CONFIG_H
53 #include <config.h>
54 #endif
55
56 #include "gst/video/gstmetavideo.h"
57
58 #include "gstv4l2colorbalance.h"
59 #include "gstv4l2tuner.h"
60 #ifdef HAVE_XVIDEO
61 #include "gstv4l2videooverlay.h"
62 #endif
63 #include "gstv4l2vidorient.h"
64
65 #include "gstv4l2sink.h"
66 #include "gst/gst-i18n-plugin.h"
67
68 #include <string.h>
69
70 GST_DEBUG_CATEGORY (v4l2sink_debug);
71 #define GST_CAT_DEFAULT v4l2sink_debug
72
73 #define DEFAULT_PROP_DEVICE   "/dev/video1"
74
75 enum
76 {
77   PROP_0,
78   V4L2_STD_OBJECT_PROPS,
79   PROP_OVERLAY_TOP,
80   PROP_OVERLAY_LEFT,
81   PROP_OVERLAY_WIDTH,
82   PROP_OVERLAY_HEIGHT,
83   PROP_CROP_TOP,
84   PROP_CROP_LEFT,
85   PROP_CROP_WIDTH,
86   PROP_CROP_HEIGHT,
87 };
88
89
90 GST_IMPLEMENT_V4L2_PROBE_METHODS (GstV4l2SinkClass, gst_v4l2sink);
91 GST_IMPLEMENT_V4L2_COLOR_BALANCE_METHODS (GstV4l2Sink, gst_v4l2sink);
92 GST_IMPLEMENT_V4L2_TUNER_METHODS (GstV4l2Sink, gst_v4l2sink);
93 #ifdef HAVE_XVIDEO
94 GST_IMPLEMENT_V4L2_VIDEO_OVERLAY_METHODS (GstV4l2Sink, gst_v4l2sink);
95 #endif
96 GST_IMPLEMENT_V4L2_VIDORIENT_METHODS (GstV4l2Sink, gst_v4l2sink);
97
98 #ifdef HAVE_XVIDEO
99 static void gst_v4l2sink_navigation_send_event (GstNavigation * navigation,
100     GstStructure * structure);
101 static void
102 gst_v4l2sink_navigation_init (GstNavigationInterface * iface)
103 {
104   iface->send_event = gst_v4l2sink_navigation_send_event;
105 }
106 #endif
107
108 #define gst_v4l2sink_parent_class parent_class
109 G_DEFINE_TYPE_WITH_CODE (GstV4l2Sink, gst_v4l2sink, GST_TYPE_VIDEO_SINK,
110     G_IMPLEMENT_INTERFACE (GST_TYPE_TUNER, gst_v4l2sink_tuner_interface_init);
111 #ifdef HAVE_XVIDEO
112     G_IMPLEMENT_INTERFACE (GST_TYPE_VIDEO_OVERLAY,
113         gst_v4l2sink_video_overlay_interface_init);
114     G_IMPLEMENT_INTERFACE (GST_TYPE_NAVIGATION, gst_v4l2sink_navigation_init);
115 #endif
116     G_IMPLEMENT_INTERFACE (GST_TYPE_COLOR_BALANCE,
117         gst_v4l2sink_color_balance_interface_init);
118     G_IMPLEMENT_INTERFACE (GST_TYPE_VIDEO_ORIENTATION,
119         gst_v4l2sink_video_orientation_interface_init);
120     G_IMPLEMENT_INTERFACE (GST_TYPE_PROPERTY_PROBE,
121         gst_v4l2sink_property_probe_interface_init));
122
123
124 static void gst_v4l2sink_dispose (GObject * object);
125 static void gst_v4l2sink_finalize (GstV4l2Sink * v4l2sink);
126
127 /* GObject methods: */
128 static void gst_v4l2sink_set_property (GObject * object, guint prop_id,
129     const GValue * value, GParamSpec * pspec);
130 static void gst_v4l2sink_get_property (GObject * object, guint prop_id,
131     GValue * value, GParamSpec * pspec);
132
133 /* GstElement methods: */
134 static GstStateChangeReturn gst_v4l2sink_change_state (GstElement * element,
135     GstStateChange transition);
136
137 /* GstBaseSink methods: */
138 static gboolean gst_v4l2sink_setup_allocation (GstBaseSink * bsink,
139     GstQuery * query);
140 static GstCaps *gst_v4l2sink_get_caps (GstBaseSink * bsink, GstCaps * filter);
141 static gboolean gst_v4l2sink_set_caps (GstBaseSink * bsink, GstCaps * caps);
142 #if 0
143 static GstFlowReturn gst_v4l2sink_buffer_alloc (GstBaseSink * bsink,
144     guint64 offset, guint size, GstCaps * caps, GstBuffer ** buf);
145 #endif
146 static GstFlowReturn gst_v4l2sink_show_frame (GstBaseSink * bsink,
147     GstBuffer * buf);
148
149 static void
150 gst_v4l2sink_class_init (GstV4l2SinkClass * klass)
151 {
152   GObjectClass *gobject_class;
153   GstElementClass *element_class;
154   GstBaseSinkClass *basesink_class;
155
156   gobject_class = G_OBJECT_CLASS (klass);
157   element_class = GST_ELEMENT_CLASS (klass);
158   basesink_class = GST_BASE_SINK_CLASS (klass);
159
160   gobject_class->dispose = gst_v4l2sink_dispose;
161   gobject_class->finalize = (GObjectFinalizeFunc) gst_v4l2sink_finalize;
162   gobject_class->set_property = gst_v4l2sink_set_property;
163   gobject_class->get_property = gst_v4l2sink_get_property;
164
165   element_class->change_state = gst_v4l2sink_change_state;
166
167   gst_v4l2_object_install_properties_helper (gobject_class,
168       DEFAULT_PROP_DEVICE);
169
170   g_object_class_install_property (gobject_class, PROP_OVERLAY_TOP,
171       g_param_spec_int ("overlay-top", "Overlay top",
172           "The topmost (y) coordinate of the video overlay; top left corner of screen is 0,0",
173           G_MININT, G_MAXINT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
174   g_object_class_install_property (gobject_class, PROP_OVERLAY_LEFT,
175       g_param_spec_int ("overlay-left", "Overlay left",
176           "The leftmost (x) coordinate of the video overlay; top left corner of screen is 0,0",
177           G_MININT, G_MAXINT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
178   g_object_class_install_property (gobject_class, PROP_OVERLAY_WIDTH,
179       g_param_spec_uint ("overlay-width", "Overlay width",
180           "The width of the video overlay; default is equal to negotiated image width",
181           0, G_MAXUINT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
182   g_object_class_install_property (gobject_class, PROP_OVERLAY_HEIGHT,
183       g_param_spec_uint ("overlay-height", "Overlay height",
184           "The height of the video overlay; default is equal to negotiated image height",
185           0, G_MAXUINT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
186
187   g_object_class_install_property (gobject_class, PROP_CROP_TOP,
188       g_param_spec_int ("crop-top", "Crop top",
189           "The topmost (y) coordinate of the video crop; top left corner of image is 0,0",
190           0x80000000, 0x7fffffff, 0, G_PARAM_READWRITE));
191   g_object_class_install_property (gobject_class, PROP_CROP_LEFT,
192       g_param_spec_int ("crop-left", "Crop left",
193           "The leftmost (x) coordinate of the video crop; top left corner of image is 0,0",
194           0x80000000, 0x7fffffff, 0, G_PARAM_READWRITE));
195   g_object_class_install_property (gobject_class, PROP_CROP_WIDTH,
196       g_param_spec_uint ("crop-width", "Crop width",
197           "The width of the video crop; default is equal to negotiated image width",
198           0, 0xffffffff, 0, G_PARAM_READWRITE));
199   g_object_class_install_property (gobject_class, PROP_CROP_HEIGHT,
200       g_param_spec_uint ("crop-height", "Crop height",
201           "The height of the video crop; default is equal to negotiated image height",
202           0, 0xffffffff, 0, G_PARAM_READWRITE));
203
204   gst_element_class_set_details_simple (element_class,
205       "Video (video4linux2) Sink", "Sink/Video",
206       "Displays frames on a video4linux2 device", "Rob Clark <rob@ti.com>,");
207
208   gst_element_class_add_pad_template (element_class,
209       gst_pad_template_new ("sink", GST_PAD_SINK, GST_PAD_ALWAYS,
210           gst_v4l2_object_get_all_caps ()));
211
212   basesink_class->get_caps = GST_DEBUG_FUNCPTR (gst_v4l2sink_get_caps);
213   basesink_class->set_caps = GST_DEBUG_FUNCPTR (gst_v4l2sink_set_caps);
214   basesink_class->setup_allocation =
215       GST_DEBUG_FUNCPTR (gst_v4l2sink_setup_allocation);
216   basesink_class->render = GST_DEBUG_FUNCPTR (gst_v4l2sink_show_frame);
217
218   klass->v4l2_class_devices = NULL;
219
220   GST_DEBUG_CATEGORY_INIT (v4l2sink_debug, "v4l2sink", 0, "V4L2 sink element");
221
222 }
223
224 static void
225 gst_v4l2sink_init (GstV4l2Sink * v4l2sink)
226 {
227   v4l2sink->v4l2object = gst_v4l2_object_new (GST_ELEMENT (v4l2sink),
228       V4L2_BUF_TYPE_VIDEO_OUTPUT, DEFAULT_PROP_DEVICE,
229       gst_v4l2_get_output, gst_v4l2_set_output, NULL);
230
231   /* same default value for video output device as is used for
232    * v4l2src/capture is no good..  so lets set a saner default
233    * (which can be overridden by the one creating the v4l2sink
234    * after the constructor returns)
235    */
236   g_object_set (v4l2sink, "device", "/dev/video1", NULL);
237
238   v4l2sink->probed_caps = NULL;
239
240   v4l2sink->overlay_fields_set = 0;
241   v4l2sink->crop_fields_set = 0;
242 }
243
244
245 static void
246 gst_v4l2sink_dispose (GObject * object)
247 {
248   GstV4l2Sink *v4l2sink = GST_V4L2SINK (object);
249
250   if (v4l2sink->probed_caps) {
251     gst_caps_unref (v4l2sink->probed_caps);
252   }
253
254   G_OBJECT_CLASS (parent_class)->dispose (object);
255 }
256
257
258 static void
259 gst_v4l2sink_finalize (GstV4l2Sink * v4l2sink)
260 {
261   gst_v4l2_object_destroy (v4l2sink->v4l2object);
262
263   G_OBJECT_CLASS (parent_class)->finalize ((GObject *) (v4l2sink));
264 }
265
266
267 /*
268  * flags to indicate which overlay/crop properties the user has set (and
269  * therefore which ones should override the defaults from the driver)
270  */
271 enum
272 {
273   RECT_TOP_SET = 0x01,
274   RECT_LEFT_SET = 0x02,
275   RECT_WIDTH_SET = 0x04,
276   RECT_HEIGHT_SET = 0x08
277 };
278
279 static void
280 gst_v4l2sink_sync_overlay_fields (GstV4l2Sink * v4l2sink)
281 {
282   if (!v4l2sink->overlay_fields_set)
283     return;
284
285   if (GST_V4L2_IS_OPEN (v4l2sink->v4l2object)) {
286
287     gint fd = v4l2sink->v4l2object->video_fd;
288     struct v4l2_format format;
289
290     memset (&format, 0x00, sizeof (struct v4l2_format));
291     format.type = V4L2_BUF_TYPE_VIDEO_OVERLAY;
292
293     if (v4l2_ioctl (fd, VIDIOC_G_FMT, &format) < 0) {
294       GST_WARNING_OBJECT (v4l2sink, "VIDIOC_G_FMT failed");
295       return;
296     }
297
298     GST_DEBUG_OBJECT (v4l2sink,
299         "setting overlay: overlay_fields_set=0x%02x, top=%d, left=%d, width=%d, height=%d",
300         v4l2sink->overlay_fields_set,
301         v4l2sink->overlay.top, v4l2sink->overlay.left,
302         v4l2sink->overlay.width, v4l2sink->overlay.height);
303
304     if (v4l2sink->overlay_fields_set & RECT_TOP_SET)
305       format.fmt.win.w.top = v4l2sink->overlay.top;
306     if (v4l2sink->overlay_fields_set & RECT_LEFT_SET)
307       format.fmt.win.w.left = v4l2sink->overlay.left;
308     if (v4l2sink->overlay_fields_set & RECT_WIDTH_SET)
309       format.fmt.win.w.width = v4l2sink->overlay.width;
310     if (v4l2sink->overlay_fields_set & RECT_HEIGHT_SET)
311       format.fmt.win.w.height = v4l2sink->overlay.height;
312
313     if (v4l2_ioctl (fd, VIDIOC_S_FMT, &format) < 0) {
314       GST_WARNING_OBJECT (v4l2sink, "VIDIOC_S_FMT failed");
315       return;
316     }
317
318     v4l2sink->overlay_fields_set = 0;
319     v4l2sink->overlay = format.fmt.win.w;
320   }
321 }
322
323 static void
324 gst_v4l2sink_sync_crop_fields (GstV4l2Sink * v4l2sink)
325 {
326   if (!v4l2sink->crop_fields_set)
327     return;
328
329   if (GST_V4L2_IS_OPEN (v4l2sink->v4l2object)) {
330
331     gint fd = v4l2sink->v4l2object->video_fd;
332     struct v4l2_crop crop;
333
334     memset (&crop, 0x00, sizeof (struct v4l2_crop));
335     crop.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
336
337     if (v4l2_ioctl (fd, VIDIOC_G_CROP, &crop) < 0) {
338       GST_WARNING_OBJECT (v4l2sink, "VIDIOC_G_CROP failed");
339       return;
340     }
341
342     GST_DEBUG_OBJECT (v4l2sink,
343         "setting crop: crop_fields_set=0x%02x, top=%d, left=%d, width=%d, height=%d",
344         v4l2sink->crop_fields_set,
345         v4l2sink->crop.top, v4l2sink->crop.left,
346         v4l2sink->crop.width, v4l2sink->crop.height);
347
348     if (v4l2sink->crop_fields_set & RECT_TOP_SET)
349       crop.c.top = v4l2sink->crop.top;
350     if (v4l2sink->crop_fields_set & RECT_LEFT_SET)
351       crop.c.left = v4l2sink->crop.left;
352     if (v4l2sink->crop_fields_set & RECT_WIDTH_SET)
353       crop.c.width = v4l2sink->crop.width;
354     if (v4l2sink->crop_fields_set & RECT_HEIGHT_SET)
355       crop.c.height = v4l2sink->crop.height;
356
357     if (v4l2_ioctl (fd, VIDIOC_S_CROP, &crop) < 0) {
358       GST_WARNING_OBJECT (v4l2sink, "VIDIOC_S_CROP failed");
359       return;
360     }
361
362     v4l2sink->crop_fields_set = 0;
363     v4l2sink->crop = crop.c;
364   }
365 }
366
367
368 static void
369 gst_v4l2sink_set_property (GObject * object,
370     guint prop_id, const GValue * value, GParamSpec * pspec)
371 {
372   GstV4l2Sink *v4l2sink = GST_V4L2SINK (object);
373
374   if (!gst_v4l2_object_set_property_helper (v4l2sink->v4l2object,
375           prop_id, value, pspec)) {
376     switch (prop_id) {
377       case PROP_OVERLAY_TOP:
378         v4l2sink->overlay.top = g_value_get_int (value);
379         v4l2sink->overlay_fields_set |= RECT_TOP_SET;
380         gst_v4l2sink_sync_overlay_fields (v4l2sink);
381         break;
382       case PROP_OVERLAY_LEFT:
383         v4l2sink->overlay.left = g_value_get_int (value);
384         v4l2sink->overlay_fields_set |= RECT_LEFT_SET;
385         gst_v4l2sink_sync_overlay_fields (v4l2sink);
386         break;
387       case PROP_OVERLAY_WIDTH:
388         v4l2sink->overlay.width = g_value_get_uint (value);
389         v4l2sink->overlay_fields_set |= RECT_WIDTH_SET;
390         gst_v4l2sink_sync_overlay_fields (v4l2sink);
391         break;
392       case PROP_OVERLAY_HEIGHT:
393         v4l2sink->overlay.height = g_value_get_uint (value);
394         v4l2sink->overlay_fields_set |= RECT_HEIGHT_SET;
395         gst_v4l2sink_sync_overlay_fields (v4l2sink);
396         break;
397       case PROP_CROP_TOP:
398         v4l2sink->crop.top = g_value_get_int (value);
399         v4l2sink->crop_fields_set |= RECT_TOP_SET;
400         gst_v4l2sink_sync_crop_fields (v4l2sink);
401         break;
402       case PROP_CROP_LEFT:
403         v4l2sink->crop.left = g_value_get_int (value);
404         v4l2sink->crop_fields_set |= RECT_LEFT_SET;
405         gst_v4l2sink_sync_crop_fields (v4l2sink);
406         break;
407       case PROP_CROP_WIDTH:
408         v4l2sink->crop.width = g_value_get_uint (value);
409         v4l2sink->crop_fields_set |= RECT_WIDTH_SET;
410         gst_v4l2sink_sync_crop_fields (v4l2sink);
411         break;
412       case PROP_CROP_HEIGHT:
413         v4l2sink->crop.height = g_value_get_uint (value);
414         v4l2sink->crop_fields_set |= RECT_HEIGHT_SET;
415         gst_v4l2sink_sync_crop_fields (v4l2sink);
416         break;
417       default:
418         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
419         break;
420     }
421   }
422 }
423
424
425 static void
426 gst_v4l2sink_get_property (GObject * object,
427     guint prop_id, GValue * value, GParamSpec * pspec)
428 {
429   GstV4l2Sink *v4l2sink = GST_V4L2SINK (object);
430
431   if (!gst_v4l2_object_get_property_helper (v4l2sink->v4l2object,
432           prop_id, value, pspec)) {
433     switch (prop_id) {
434       case PROP_OVERLAY_TOP:
435         g_value_set_int (value, v4l2sink->overlay.top);
436         break;
437       case PROP_OVERLAY_LEFT:
438         g_value_set_int (value, v4l2sink->overlay.left);
439         break;
440       case PROP_OVERLAY_WIDTH:
441         g_value_set_uint (value, v4l2sink->overlay.width);
442         break;
443       case PROP_OVERLAY_HEIGHT:
444         g_value_set_uint (value, v4l2sink->overlay.height);
445         break;
446       case PROP_CROP_TOP:
447         g_value_set_int (value, v4l2sink->crop.top);
448         break;
449       case PROP_CROP_LEFT:
450         g_value_set_int (value, v4l2sink->crop.left);
451         break;
452       case PROP_CROP_WIDTH:
453         g_value_set_uint (value, v4l2sink->crop.width);
454         break;
455       case PROP_CROP_HEIGHT:
456         g_value_set_uint (value, v4l2sink->crop.height);
457         break;
458       default:
459         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
460         break;
461     }
462   }
463 }
464
465 static GstStateChangeReturn
466 gst_v4l2sink_change_state (GstElement * element, GstStateChange transition)
467 {
468   GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
469   GstV4l2Sink *v4l2sink = GST_V4L2SINK (element);
470
471   GST_DEBUG_OBJECT (v4l2sink, "%d -> %d",
472       GST_STATE_TRANSITION_CURRENT (transition),
473       GST_STATE_TRANSITION_NEXT (transition));
474
475   switch (transition) {
476     case GST_STATE_CHANGE_NULL_TO_READY:
477       /* open the device */
478       if (!gst_v4l2_object_open (v4l2sink->v4l2object))
479         return GST_STATE_CHANGE_FAILURE;
480       break;
481     default:
482       break;
483   }
484
485   ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
486
487   switch (transition) {
488     case GST_STATE_CHANGE_PAUSED_TO_READY:
489       if (!gst_v4l2_object_stop (v4l2sink->v4l2object))
490         return GST_STATE_CHANGE_FAILURE;
491       break;
492     case GST_STATE_CHANGE_READY_TO_NULL:
493       /* we need to call stop here too */
494       if (!gst_v4l2_object_stop (v4l2sink->v4l2object))
495         return GST_STATE_CHANGE_FAILURE;
496       /* close the device */
497       if (!gst_v4l2_object_close (v4l2sink->v4l2object))
498         return GST_STATE_CHANGE_FAILURE;
499       break;
500     default:
501       break;
502   }
503
504   return ret;
505 }
506
507
508 static GstCaps *
509 gst_v4l2sink_get_caps (GstBaseSink * bsink, GstCaps * filter)
510 {
511   GstV4l2Sink *v4l2sink = GST_V4L2SINK (bsink);
512   GstCaps *ret;
513   GSList *walk;
514   GSList *formats;
515
516   if (!GST_V4L2_IS_OPEN (v4l2sink->v4l2object)) {
517     /* FIXME: copy? */
518     GST_DEBUG_OBJECT (v4l2sink, "device is not open");
519     return
520         gst_caps_copy (gst_pad_get_pad_template_caps (GST_BASE_SINK_PAD
521             (v4l2sink)));
522   }
523
524   if (v4l2sink->probed_caps == NULL) {
525     formats = gst_v4l2_object_get_format_list (v4l2sink->v4l2object);
526
527     ret = gst_caps_new_empty ();
528
529     for (walk = formats; walk; walk = walk->next) {
530       struct v4l2_fmtdesc *format;
531
532       GstStructure *template;
533
534       format = (struct v4l2_fmtdesc *) walk->data;
535
536       template = gst_v4l2_object_v4l2fourcc_to_structure (format->pixelformat);
537
538       if (template) {
539         GstCaps *tmp;
540
541         tmp =
542             gst_v4l2_object_probe_caps_for_format (v4l2sink->v4l2object,
543             format->pixelformat, template);
544         if (tmp)
545           gst_caps_append (ret, tmp);
546
547         gst_structure_free (template);
548       } else {
549         GST_DEBUG_OBJECT (v4l2sink, "unknown format %u", format->pixelformat);
550       }
551     }
552     v4l2sink->probed_caps = ret;
553   }
554
555   if (filter) {
556     ret =
557         gst_caps_intersect_full (filter, v4l2sink->probed_caps,
558         GST_CAPS_INTERSECT_FIRST);
559   } else {
560     ret = gst_caps_ref (v4l2sink->probed_caps);
561   }
562
563   GST_INFO_OBJECT (v4l2sink, "probed caps: %p", ret);
564   LOG_CAPS (v4l2sink, ret);
565
566   return ret;
567 }
568
569 static gboolean
570 gst_v4l2sink_set_caps (GstBaseSink * bsink, GstCaps * caps)
571 {
572   GstV4l2Sink *v4l2sink = GST_V4L2SINK (bsink);
573   GstV4l2Object *obj = v4l2sink->v4l2object;
574
575   LOG_CAPS (v4l2sink, caps);
576
577   if (!GST_V4L2_IS_OPEN (v4l2sink->v4l2object)) {
578     GST_DEBUG_OBJECT (v4l2sink, "device is not open");
579     return FALSE;
580   }
581
582   if (!gst_v4l2_object_stop (obj))
583     goto stop_failed;
584
585   if (!gst_v4l2_object_set_format (v4l2sink->v4l2object, caps))
586     goto invalid_format;
587
588   gst_v4l2sink_sync_overlay_fields (v4l2sink);
589   gst_v4l2sink_sync_crop_fields (v4l2sink);
590
591 #ifdef HAVE_XVIDEO
592   gst_v4l2_video_overlay_prepare_window_handle (v4l2sink->v4l2object, TRUE);
593 #endif
594
595   GST_INFO_OBJECT (v4l2sink, "outputting buffers via mmap()");
596
597   v4l2sink->video_width = GST_V4L2_WIDTH (v4l2sink->v4l2object);
598   v4l2sink->video_height = GST_V4L2_HEIGHT (v4l2sink->v4l2object);
599
600   /* TODO: videosink width/height should be scaled according to
601    * pixel-aspect-ratio
602    */
603   GST_VIDEO_SINK_WIDTH (v4l2sink) = v4l2sink->video_width;
604   GST_VIDEO_SINK_HEIGHT (v4l2sink) = v4l2sink->video_height;
605
606   return TRUE;
607
608   /* ERRORS */
609 stop_failed:
610   {
611     GST_DEBUG_OBJECT (v4l2sink, "failed to stop streaming");
612     return FALSE;
613   }
614 invalid_format:
615   {
616     /* error already posted */
617     GST_DEBUG_OBJECT (v4l2sink, "can't set format");
618     return FALSE;
619   }
620 }
621
622 static gboolean
623 gst_v4l2sink_setup_allocation (GstBaseSink * bsink, GstQuery * query)
624 {
625   GstV4l2Sink *v4l2sink = GST_V4L2SINK (bsink);
626   GstV4l2Object *obj = v4l2sink->v4l2object;
627   GstBufferPool *pool;
628   guint size = 0;
629   GstCaps *caps;
630   gboolean need_pool;
631
632   gst_query_parse_allocation (query, &caps, &need_pool);
633
634   if (caps == NULL)
635     goto no_caps;
636
637   if ((pool = obj->pool))
638     gst_object_ref (pool);
639
640   if (pool != NULL) {
641     const GstCaps *pcaps;
642     GstStructure *config;
643
644     /* we had a pool, check caps */
645     config = gst_buffer_pool_get_config (pool);
646     gst_buffer_pool_config_get (config, &pcaps, &size, NULL, NULL, NULL, NULL);
647
648     GST_DEBUG_OBJECT (v4l2sink,
649         "we had a pool with caps %" GST_PTR_FORMAT, pcaps);
650     if (!gst_caps_is_equal (caps, pcaps)) {
651       gst_object_unref (pool);
652       goto different_caps;
653     }
654   }
655   /* we need at least 2 buffers to operate */
656   gst_query_set_allocation_params (query, size, 2, 0, 0, 0, pool);
657
658   /* we also support various metadata */
659   gst_query_add_allocation_meta (query, GST_META_API_VIDEO);
660   gst_query_add_allocation_meta (query, GST_META_API_VIDEO_CROP);
661
662   if (pool)
663     gst_object_unref (pool);
664
665   return TRUE;
666
667   /* ERRORS */
668 no_caps:
669   {
670     GST_DEBUG_OBJECT (v4l2sink, "no caps specified");
671     return FALSE;
672   }
673 different_caps:
674   {
675     /* different caps, we can't use this pool */
676     GST_DEBUG_OBJECT (v4l2sink, "pool has different caps");
677     return FALSE;
678   }
679 }
680
681 /* called after A/V sync to render frame */
682 static GstFlowReturn
683 gst_v4l2sink_show_frame (GstBaseSink * bsink, GstBuffer * buf)
684 {
685   GstFlowReturn ret;
686   GstV4l2Sink *v4l2sink = GST_V4L2SINK (bsink);
687   GstV4l2Object *obj = v4l2sink->v4l2object;
688
689   GST_DEBUG_OBJECT (v4l2sink, "render buffer: %p", buf);
690
691   if (G_UNLIKELY (obj->pool == NULL))
692     goto not_negotiated;
693
694   ret =
695       gst_v4l2_buffer_pool_process (GST_V4L2_BUFFER_POOL_CAST (obj->pool), buf);
696
697   return ret;
698
699   /* ERRORS */
700 not_negotiated:
701   {
702     GST_ERROR_OBJECT (bsink, "not negotiated");
703     return GST_FLOW_NOT_NEGOTIATED;
704   }
705 }
706
707 #ifdef HAVE_XVIDEO
708 static void
709 gst_v4l2sink_navigation_send_event (GstNavigation * navigation,
710     GstStructure * structure)
711 {
712   GstV4l2Sink *v4l2sink = GST_V4L2SINK (navigation);
713   GstV4l2Xv *xv = v4l2sink->v4l2object->xv;
714   GstPad *peer;
715
716   if (!xv)
717     return;
718
719   if ((peer = gst_pad_get_peer (GST_VIDEO_SINK_PAD (v4l2sink)))) {
720     GstVideoRectangle rect;
721     gdouble x, y, xscale = 1.0, yscale = 1.0;
722
723     gst_v4l2_video_overlay_get_render_rect (v4l2sink->v4l2object, &rect);
724
725     /* We calculate scaling using the original video frames geometry to
726      * include pixel aspect ratio scaling.
727      */
728     xscale = (gdouble) v4l2sink->video_width / rect.w;
729     yscale = (gdouble) v4l2sink->video_height / rect.h;
730
731     /* Converting pointer coordinates to the non scaled geometry */
732     if (gst_structure_get_double (structure, "pointer_x", &x)) {
733       x = MIN (x, rect.x + rect.w);
734       x = MAX (x - rect.x, 0);
735       gst_structure_set (structure, "pointer_x", G_TYPE_DOUBLE,
736           (gdouble) x * xscale, NULL);
737     }
738     if (gst_structure_get_double (structure, "pointer_y", &y)) {
739       y = MIN (y, rect.y + rect.h);
740       y = MAX (y - rect.y, 0);
741       gst_structure_set (structure, "pointer_y", G_TYPE_DOUBLE,
742           (gdouble) y * yscale, NULL);
743     }
744
745     gst_pad_send_event (peer, gst_event_new_navigation (structure));
746     gst_object_unref (peer);
747   }
748 }
749 #endif