summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--sys/v4l2/gstv4l2sink.c90
-rw-r--r--sys/v4l2/gstv4l2sink.h2
-rw-r--r--sys/v4l2/gstv4l2xoverlay.c149
-rw-r--r--sys/v4l2/gstv4l2xoverlay.h4
4 files changed, 231 insertions, 14 deletions
diff --git a/sys/v4l2/gstv4l2sink.c b/sys/v4l2/gstv4l2sink.c
index 9fce498fd..8652a9fde 100644
--- a/sys/v4l2/gstv4l2sink.c
+++ b/sys/v4l2/gstv4l2sink.c
@@ -33,6 +33,18 @@
* |[
* gst-launch videotestsrc ! v4l2sink device=/dev/video1
* ]| This pipeline displays a test pattern on /dev/video1
+ * |[
+ * gst-launch -v videotestsrc ! navigationtest ! v4l2sink
+ * ]| A pipeline to test navigation events.
+ * While moving the mouse pointer over the test signal you will see a black box
+ * following the mouse pointer. If you press the mouse button somewhere on the
+ * video and release it somewhere else a green box will appear where you pressed
+ * the button and a red one where you released it. (The navigationtest element
+ * is part of gst-plugins-good.) You can observe here that even if the images
+ * are scaled through hardware the pointer coordinates are converted back to the
+ * original video frame geometry so that the box can be drawn to the correct
+ * position. This also handles borders correctly, limiting coordinates to the
+ * image area
* </refsect2>
*/
@@ -94,6 +106,7 @@ gst_v4l2sink_iface_supported (GstImplementsInterface * iface, GType iface_type)
g_assert (iface_type == GST_TYPE_TUNER ||
#ifdef HAVE_XVIDEO
iface_type == GST_TYPE_X_OVERLAY ||
+ iface_type == GST_TYPE_NAVIGATION ||
#endif
iface_type == GST_TYPE_COLOR_BALANCE ||
iface_type == GST_TYPE_VIDEO_ORIENTATION);
@@ -102,8 +115,10 @@ gst_v4l2sink_iface_supported (GstImplementsInterface * iface, GType iface_type)
return FALSE;
#ifdef HAVE_XVIDEO
- if (iface_type == GST_TYPE_X_OVERLAY && !GST_V4L2_IS_OVERLAY (v4l2object))
- return FALSE;
+ if (!GST_V4L2_IS_OVERLAY (v4l2object)) {
+ if (iface_type == GST_TYPE_X_OVERLAY || iface_type == GST_TYPE_NAVIGATION)
+ return FALSE;
+ }
#endif
return TRUE;
@@ -118,6 +133,16 @@ gst_v4l2sink_interface_init (GstImplementsInterfaceClass * klass)
klass->supported = gst_v4l2sink_iface_supported;
}
+#ifdef HAVE_XVIDEO
+static void gst_v4l2sink_navigation_send_event (GstNavigation * navigation,
+ GstStructure * structure);
+static void
+gst_v4l2sink_navigation_init (GstNavigationInterface * iface)
+{
+ iface->send_event = gst_v4l2sink_navigation_send_event;
+}
+#endif
+
static void
gst_v4l2sink_init_interfaces (GType type)
{
@@ -137,6 +162,11 @@ gst_v4l2sink_init_interfaces (GType type)
NULL,
NULL,
};
+ static const GInterfaceInfo v4l2_navigation_info = {
+ (GInterfaceInitFunc) gst_v4l2sink_navigation_init,
+ NULL,
+ NULL,
+ };
#endif
static const GInterfaceInfo v4l2_colorbalance_info = {
(GInterfaceInitFunc) gst_v4l2sink_color_balance_interface_init,
@@ -159,6 +189,8 @@ gst_v4l2sink_init_interfaces (GType type)
g_type_add_interface_static (type, GST_TYPE_TUNER, &v4l2_tuner_info);
#ifdef HAVE_XVIDEO
g_type_add_interface_static (type, GST_TYPE_X_OVERLAY, &v4l2_xoverlay_info);
+ g_type_add_interface_static (type,
+ GST_TYPE_NAVIGATION, &v4l2_navigation_info);
#endif
g_type_add_interface_static (type,
GST_TYPE_COLOR_BALANCE, &v4l2_colorbalance_info);
@@ -195,7 +227,6 @@ static GstFlowReturn gst_v4l2sink_buffer_alloc (GstBaseSink * bsink,
static GstFlowReturn gst_v4l2sink_show_frame (GstBaseSink * bsink,
GstBuffer * buf);
-
static void
gst_v4l2sink_base_init (gpointer g_class)
{
@@ -709,6 +740,15 @@ gst_v4l2sink_set_caps (GstBaseSink * bsink, GstCaps * caps)
return FALSE;
}
+ v4l2sink->video_width = w;
+ v4l2sink->video_height = h;
+
+ /* TODO: videosink width/height should be scaled according to
+ * pixel-aspect-ratio
+ */
+ GST_VIDEO_SINK_WIDTH (v4l2sink) = w;
+ GST_VIDEO_SINK_HEIGHT (v4l2sink) = h;
+
v4l2sink->current_caps = gst_caps_ref (caps);
return TRUE;
@@ -866,3 +906,47 @@ gst_v4l2sink_show_frame (GstBaseSink * bsink, GstBuffer * buf)
return GST_FLOW_OK;
}
+
+#ifdef HAVE_XVIDEO
+static void
+gst_v4l2sink_navigation_send_event (GstNavigation * navigation,
+ GstStructure * structure)
+{
+ GstV4l2Sink *v4l2sink = GST_V4L2SINK (navigation);
+ GstV4l2Xv *xv = v4l2sink->v4l2object->xv;
+ GstPad *peer;
+
+ if (!xv)
+ return;
+
+ if ((peer = gst_pad_get_peer (GST_VIDEO_SINK_PAD (v4l2sink)))) {
+ GstVideoRectangle rect;
+ gdouble x, y, xscale = 1.0, yscale = 1.0;
+
+ gst_v4l2_xoverlay_get_render_rect (v4l2sink->v4l2object, &rect);
+
+ /* We calculate scaling using the original video frames geometry to
+ * include pixel aspect ratio scaling.
+ */
+ xscale = (gdouble) v4l2sink->video_width / rect.w;
+ yscale = (gdouble) v4l2sink->video_height / rect.h;
+
+ /* Converting pointer coordinates to the non scaled geometry */
+ if (gst_structure_get_double (structure, "pointer_x", &x)) {
+ x = MIN (x, rect.x + rect.w);
+ x = MAX (x - rect.x, 0);
+ gst_structure_set (structure, "pointer_x", G_TYPE_DOUBLE,
+ (gdouble) x * xscale, NULL);
+ }
+ if (gst_structure_get_double (structure, "pointer_y", &y)) {
+ y = MIN (y, rect.y + rect.h);
+ y = MAX (y - rect.y, 0);
+ gst_structure_set (structure, "pointer_y", G_TYPE_DOUBLE,
+ (gdouble) y * yscale, NULL);
+ }
+
+ gst_pad_send_event (peer, gst_event_new_navigation (structure));
+ gst_object_unref (peer);
+ }
+}
+#endif
diff --git a/sys/v4l2/gstv4l2sink.h b/sys/v4l2/gstv4l2sink.h
index 07a32bf3c..8fe82221a 100644
--- a/sys/v4l2/gstv4l2sink.h
+++ b/sys/v4l2/gstv4l2sink.h
@@ -60,6 +60,8 @@ struct _GstV4l2Sink {
guint32 num_buffers;
guint32 min_queued_bufs;
+ gint video_width, video_height; /* original (unscaled) video w/h */
+
/*
* field to store requested overlay and crop top/left/width/height props:
* note, could maybe be combined with 'vwin' field in GstV4l2Object?
diff --git a/sys/v4l2/gstv4l2xoverlay.c b/sys/v4l2/gstv4l2xoverlay.c
index d11b8a35a..2ad563124 100644
--- a/sys/v4l2/gstv4l2xoverlay.c
+++ b/sys/v4l2/gstv4l2xoverlay.c
@@ -33,6 +33,8 @@
#include <X11/extensions/Xvlib.h>
#include <sys/stat.h>
+#include <gst/interfaces/navigation.h>
+
#include "gstv4l2xoverlay.h"
#include "gstv4l2object.h"
#include "v4l2_calls.h"
@@ -43,7 +45,7 @@ struct _GstV4l2Xv
{
Display *dpy;
gint port, idle_id, event_id;
- GMutex *mutex;
+ GMutex *mutex; /* to serialize calls to X11 */
};
GST_DEBUG_CATEGORY_STATIC (v4l2xv_debug);
@@ -175,15 +177,53 @@ gst_v4l2_xoverlay_stop (GstV4l2Object * v4l2object)
gst_v4l2_xoverlay_close (v4l2object);
}
+/* should be called with mutex held */
+static gboolean
+get_render_rect (GstV4l2Object * v4l2object, GstVideoRectangle *rect)
+{
+ GstV4l2Xv *v4l2xv = v4l2object->xv;
+ if (v4l2xv && v4l2xv->dpy && v4l2object->xwindow_id) {
+ XWindowAttributes attr;
+ XGetWindowAttributes (v4l2xv->dpy, v4l2object->xwindow_id, &attr);
+ /* this is where we'd add support to maintain aspect ratio */
+ rect->x = 0;
+ rect->y = 0;
+ rect->w = attr.width;
+ rect->h = attr.height;
+ return TRUE;
+ } else {
+ return FALSE;
+ }
+}
+
+gboolean
+gst_v4l2_xoverlay_get_render_rect (GstV4l2Object *v4l2object,
+ GstVideoRectangle *rect)
+{
+ GstV4l2Xv *v4l2xv = v4l2object->xv;
+ gboolean ret = FALSE;
+ if (v4l2xv) {
+ g_mutex_lock (v4l2xv->mutex);
+ ret = get_render_rect (v4l2object, rect);
+ g_mutex_unlock (v4l2xv->mutex);
+ }
+ return ret;
+}
+
static void
update_geometry (GstV4l2Object * v4l2object)
{
GstV4l2Xv *v4l2xv = v4l2object->xv;
- XWindowAttributes attr;
- XGetWindowAttributes (v4l2xv->dpy, v4l2object->xwindow_id, &attr);
+ GstVideoRectangle rect;
+ if (!get_render_rect (v4l2object, &rect))
+ return;
+ /* note: we don't pass in valid video x/y/w/h.. currently the xserver
+ * doesn't need to know these, as they come from v4l2 by setting the
+ * crop..
+ */
XvPutVideo (v4l2xv->dpy, v4l2xv->port, v4l2object->xwindow_id,
DefaultGC (v4l2xv->dpy, DefaultScreen (v4l2xv->dpy)),
- 0, 0, attr.width, attr.height, 0, 0, attr.width, attr.height);
+ 0, 0, rect.w, rect.h, rect.x, rect.y, rect.w, rect.h);
}
static gboolean
@@ -221,6 +261,92 @@ event_refresh (gpointer data)
g_mutex_lock (v4l2xv->mutex);
+ /* If the element supports navigation, collect the relavent input
+ * events and push them upstream as navigation events
+ */
+ if (GST_IS_NAVIGATION (v4l2object->element)) {
+ guint pointer_x = 0, pointer_y = 0;
+ gboolean pointer_moved = FALSE;
+
+ /* We get all pointer motion events, only the last position is
+ * interesting.
+ */
+ while (XCheckWindowEvent (v4l2xv->dpy, v4l2object->xwindow_id,
+ PointerMotionMask, &e)) {
+ switch (e.type) {
+ case MotionNotify:
+ pointer_x = e.xmotion.x;
+ pointer_y = e.xmotion.y;
+ pointer_moved = TRUE;
+ break;
+ default:
+ break;
+ }
+ }
+ if (pointer_moved) {
+ GST_DEBUG_OBJECT (v4l2object->element,
+ "pointer moved over window at %d,%d", pointer_x, pointer_y);
+ g_mutex_unlock (v4l2xv->mutex);
+ gst_navigation_send_mouse_event (GST_NAVIGATION (v4l2object->element),
+ "mouse-move", 0, e.xbutton.x, e.xbutton.y);
+ g_mutex_lock (v4l2xv->mutex);
+ }
+
+ /* We get all events on our window to throw them upstream
+ */
+ while (XCheckWindowEvent (v4l2xv->dpy, v4l2object->xwindow_id,
+ KeyPressMask | KeyReleaseMask |
+ ButtonPressMask | ButtonReleaseMask, &e)) {
+ KeySym keysym;
+ const char *key_str = NULL;
+
+ g_mutex_unlock (v4l2xv->mutex);
+
+ switch (e.type) {
+ case ButtonPress:
+ GST_DEBUG_OBJECT (v4l2object->element,
+ "button %d pressed over window at %d,%d",
+ e.xbutton.button, e.xbutton.x, e.xbutton.y);
+ gst_navigation_send_mouse_event (
+ GST_NAVIGATION (v4l2object->element),
+ "mouse-button-press", e.xbutton.button,
+ e.xbutton.x, e.xbutton.y);
+ break;
+ case ButtonRelease:
+ GST_DEBUG_OBJECT (v4l2object->element,
+ "button %d released over window at %d,%d",
+ e.xbutton.button, e.xbutton.x, e.xbutton.y);
+ gst_navigation_send_mouse_event (
+ GST_NAVIGATION (v4l2object->element),
+ "mouse-button-release", e.xbutton.button,
+ e.xbutton.x, e.xbutton.y);
+ break;
+ case KeyPress:
+ case KeyRelease:
+ g_mutex_lock (v4l2xv->mutex);
+ keysym = XKeycodeToKeysym (v4l2xv->dpy, e.xkey.keycode, 0);
+ if (keysym != NoSymbol) {
+ key_str = XKeysymToString (keysym);
+ } else {
+ key_str = "unknown";
+ }
+ g_mutex_unlock (v4l2xv->mutex);
+ GST_DEBUG_OBJECT (v4l2object->element,
+ "key %d pressed over window at %d,%d (%s)",
+ e.xkey.keycode, e.xkey.x, e.xkey.y, key_str);
+ gst_navigation_send_key_event (
+ GST_NAVIGATION (v4l2object->element),
+ e.type == KeyPress ? "key-press" : "key-release", key_str);
+ break;
+ default:
+ GST_DEBUG_OBJECT (v4l2object->element,
+ "unhandled X event (%d)", e.type);
+ }
+
+ g_mutex_lock (v4l2xv->mutex);
+ }
+ }
+
/* Handle ConfigureNotify */
while (XCheckWindowEvent (v4l2xv->dpy, v4l2object->xwindow_id,
StructureNotifyMask, &e)) {
@@ -312,6 +438,7 @@ gst_v4l2_xoverlay_prepare_xwindow_id (GstV4l2Object * v4l2object,
GstV4l2Xv *v4l2xv;
Window win;
int width, height;
+ long event_mask;
if (!v4l2object->xv && GST_V4L2_IS_OPEN (v4l2object))
gst_v4l2_xoverlay_open (v4l2object);
@@ -338,13 +465,13 @@ gst_v4l2_xoverlay_prepare_xwindow_id (GstV4l2Object * v4l2object,
GST_DEBUG_OBJECT (v4l2object->element, "win=%lu", win);
- /* @todo add mouse events for all windows, and button events for self
- * created windows, and hook up to navigation interface.. note that at
- * least some of the events we want to handle regardless of whether it
- * is a self created window or not.. such as mouse/button events in
- * order to implement navigation interface?
- */
- XSelectInput (v4l2xv->dpy, win, ExposureMask | StructureNotifyMask);
+ event_mask = ExposureMask | StructureNotifyMask;
+ if (GST_IS_NAVIGATION (v4l2object->element)) {
+ event_mask |= PointerMotionMask |
+ KeyPressMask | KeyReleaseMask |
+ ButtonPressMask | ButtonReleaseMask;
+ }
+ XSelectInput (v4l2xv->dpy, win, event_mask);
v4l2xv->event_id = g_timeout_add (45, event_refresh, v4l2object);
XMapRaised (v4l2xv->dpy, win);
diff --git a/sys/v4l2/gstv4l2xoverlay.h b/sys/v4l2/gstv4l2xoverlay.h
index 6af5c2218..1a0930683 100644
--- a/sys/v4l2/gstv4l2xoverlay.h
+++ b/sys/v4l2/gstv4l2xoverlay.h
@@ -28,6 +28,8 @@
#include <gst/gst.h>
#include <gst/interfaces/xoverlay.h>
+#include <gst/interfaces/navigation.h>
+#include <gst/video/gstvideosink.h> /* for GstVideoRectange */
#include "gstv4l2object.h"
@@ -35,6 +37,8 @@ G_BEGIN_DECLS
void gst_v4l2_xoverlay_start (GstV4l2Object *v4l2object);
void gst_v4l2_xoverlay_stop (GstV4l2Object *v4l2object);
+gboolean gst_v4l2_xoverlay_get_render_rect (GstV4l2Object *v4l2object,
+ GstVideoRectangle *rect);
void gst_v4l2_xoverlay_interface_init (GstXOverlayClass * klass);
void gst_v4l2_xoverlay_set_window_handle (GstV4l2Object * v4l2object,