summaryrefslogtreecommitdiffstats
path: root/sys/winks
diff options
context:
space:
mode:
Diffstat (limited to 'sys/winks')
-rw-r--r--sys/winks/Makefile.am9
-rw-r--r--sys/winks/gstksclock.c356
-rw-r--r--sys/winks/gstksclock.h69
-rw-r--r--sys/winks/gstksvideodevice.c1092
-rw-r--r--sys/winks/gstksvideodevice.h76
-rw-r--r--sys/winks/gstksvideosrc.c816
-rw-r--r--sys/winks/gstksvideosrc.h55
-rw-r--r--sys/winks/kshelpers.c471
-rw-r--r--sys/winks/kshelpers.h61
-rw-r--r--sys/winks/ksvideohelpers.c585
-rw-r--r--sys/winks/ksvideohelpers.h64
11 files changed, 3654 insertions, 0 deletions
diff --git a/sys/winks/Makefile.am b/sys/winks/Makefile.am
new file mode 100644
index 00000000..a89eef31
--- /dev/null
+++ b/sys/winks/Makefile.am
@@ -0,0 +1,9 @@
+# This plugin isn't buildable with autotools at this point in time, so just
+# ensure everything's listed in EXTRA_DIST
+
+EXTRA_DIST = \
+ gstksclock.c gstksclock.h \
+ gstksvideodevice.c gstksvideodevice.h \
+ gstksvideosrc.c gstksvideosrc.h \
+ kshelpers.c kshelpers.h \
+ ksvideohelpers.c ksvideohelpers.h
diff --git a/sys/winks/gstksclock.c b/sys/winks/gstksclock.c
new file mode 100644
index 00000000..ad3b3ecb
--- /dev/null
+++ b/sys/winks/gstksclock.c
@@ -0,0 +1,356 @@
+/*
+ * Copyright (C) 2008 Ole André Vadla Ravnås <ole.andre.ravnas@tandberg.com>
+ *
+ * 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.
+ */
+
+#include "gstksclock.h"
+
+#include "kshelpers.h"
+
+GST_DEBUG_CATEGORY_EXTERN (gst_ks_debug);
+#define GST_CAT_DEFAULT gst_ks_debug
+
+typedef struct
+{
+ GMutex *mutex;
+ GCond *client_cond;
+ GCond *worker_cond;
+
+ HANDLE clock_handle;
+
+ gboolean open;
+ gboolean closing;
+ KSSTATE state;
+
+ GThread *worker_thread;
+ gboolean worker_running;
+ gboolean worker_initialized;
+
+ GstClock *master_clock;
+} GstKsClockPrivate;
+
+#define GST_KS_CLOCK_GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), GST_TYPE_KS_CLOCK, \
+ GstKsClockPrivate))
+
+#define GST_KS_CLOCK_LOCK() g_mutex_lock (priv->mutex)
+#define GST_KS_CLOCK_UNLOCK() g_mutex_unlock (priv->mutex)
+
+static void gst_ks_clock_dispose (GObject * object);
+static void gst_ks_clock_finalize (GObject * object);
+
+GST_BOILERPLATE (GstKsClock, gst_ks_clock, GObject, G_TYPE_OBJECT);
+
+static void
+gst_ks_clock_base_init (gpointer gclass)
+{
+}
+
+static void
+gst_ks_clock_class_init (GstKsClockClass * klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (GstKsClockPrivate));
+
+ gobject_class->dispose = gst_ks_clock_dispose;
+ gobject_class->finalize = gst_ks_clock_finalize;
+}
+
+static void
+gst_ks_clock_init (GstKsClock * self, GstKsClockClass * gclass)
+{
+ GstKsClockPrivate *priv = GST_KS_CLOCK_GET_PRIVATE (self);
+
+ priv->mutex = g_mutex_new ();
+ priv->client_cond = g_cond_new ();
+ priv->worker_cond = g_cond_new ();
+
+ priv->clock_handle = INVALID_HANDLE_VALUE;
+
+ priv->open = FALSE;
+ priv->closing = FALSE;
+ priv->state = KSSTATE_STOP;
+
+ priv->worker_thread = NULL;
+ priv->worker_running = FALSE;
+ priv->worker_initialized = FALSE;
+
+ priv->master_clock = NULL;
+}
+
+static void
+gst_ks_clock_dispose (GObject * object)
+{
+ GstKsClock *self = GST_KS_CLOCK (object);
+ GstKsClockPrivate *priv = GST_KS_CLOCK_GET_PRIVATE (self);
+
+ g_assert (!priv->open);
+
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+gst_ks_clock_finalize (GObject * object)
+{
+ GstKsClock *self = GST_KS_CLOCK (object);
+ GstKsClockPrivate *priv = GST_KS_CLOCK_GET_PRIVATE (self);
+
+ g_cond_free (priv->worker_cond);
+ g_cond_free (priv->client_cond);
+ g_mutex_free (priv->mutex);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void gst_ks_clock_close_unlocked (GstKsClock * self);
+
+gboolean
+gst_ks_clock_open (GstKsClock * self)
+{
+ GstKsClockPrivate *priv = GST_KS_CLOCK_GET_PRIVATE (self);
+ gboolean ret = FALSE;
+ GList *devices;
+ KsDeviceEntry *device;
+ KSSTATE state;
+
+ GST_KS_CLOCK_LOCK ();
+
+ g_assert (!priv->open);
+
+ priv->state = KSSTATE_STOP;
+
+ devices = ks_enumerate_devices (&KSCATEGORY_CLOCK);
+ if (devices == NULL)
+ goto error;
+
+ device = devices->data;
+
+ priv->clock_handle = CreateFile (device->path, GENERIC_READ | GENERIC_WRITE,
+ 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
+ NULL);
+ if (!ks_is_valid_handle (priv->clock_handle))
+ goto error;
+
+ state = KSSTATE_STOP;
+ if (!ks_object_set_property (priv->clock_handle, KSPROPSETID_Clock,
+ KSPROPERTY_CLOCK_STATE, &state, sizeof (state)))
+ goto error;
+
+ ks_device_list_free (devices);
+ priv->open = TRUE;
+
+ GST_KS_CLOCK_UNLOCK ();
+ return TRUE;
+
+error:
+ ks_device_list_free (devices);
+ gst_ks_clock_close_unlocked (self);
+
+ GST_KS_CLOCK_UNLOCK ();
+ return FALSE;
+}
+
+static gboolean
+gst_ks_clock_set_state_unlocked (GstKsClock * self, KSSTATE state)
+{
+ GstKsClockPrivate *priv = GST_KS_CLOCK_GET_PRIVATE (self);
+ KSSTATE initial_state;
+ gint addend;
+
+ g_assert (priv->open);
+
+ if (state == priv->state)
+ return TRUE;
+
+ initial_state = priv->state;
+ addend = (state > priv->state) ? 1 : -1;
+
+ GST_DEBUG ("Initiating clock state change from %s to %s",
+ ks_state_to_string (priv->state), ks_state_to_string (state));
+
+ while (priv->state != state) {
+ KSSTATE next_state = priv->state + addend;
+
+ GST_DEBUG ("Changing clock state from %s to %s",
+ ks_state_to_string (priv->state), ks_state_to_string (next_state));
+
+ if (ks_object_set_property (priv->clock_handle, KSPROPSETID_Clock,
+ KSPROPERTY_CLOCK_STATE, &next_state, sizeof (next_state))) {
+ priv->state = next_state;
+
+ GST_DEBUG ("Changed clock state to %s", ks_state_to_string (priv->state));
+ } else {
+ GST_WARNING ("Failed to change clock state to %s",
+ ks_state_to_string (next_state));
+ return FALSE;
+ }
+ }
+
+ GST_DEBUG ("Finished clock state change from %s to %s",
+ ks_state_to_string (initial_state), ks_state_to_string (state));
+
+ return TRUE;
+}
+
+static void
+gst_ks_clock_close_unlocked (GstKsClock * self)
+{
+ GstKsClockPrivate *priv = GST_KS_CLOCK_GET_PRIVATE (self);
+
+ if (priv->closing)
+ return;
+
+ priv->closing = TRUE;
+
+ if (priv->worker_thread != NULL) {
+ priv->worker_running = FALSE;
+ g_cond_signal (priv->worker_cond);
+
+ GST_KS_CLOCK_UNLOCK ();
+ g_thread_join (priv->worker_thread);
+ priv->worker_thread = NULL;
+ GST_KS_CLOCK_LOCK ();
+ }
+
+ gst_ks_clock_set_state_unlocked (self, KSSTATE_STOP);
+
+ if (ks_is_valid_handle (priv->clock_handle)) {
+ CloseHandle (priv->clock_handle);
+ priv->clock_handle = INVALID_HANDLE_VALUE;
+ }
+
+ if (priv->master_clock != NULL) {
+ gst_object_unref (priv->master_clock);
+ priv->master_clock = NULL;
+ }
+
+ priv->open = FALSE;
+ priv->closing = FALSE;
+}
+
+void
+gst_ks_clock_close (GstKsClock * self)
+{
+ GstKsClockPrivate *priv = GST_KS_CLOCK_GET_PRIVATE (self);
+
+ GST_KS_CLOCK_LOCK ();
+ gst_ks_clock_close_unlocked (self);
+ GST_KS_CLOCK_UNLOCK ();
+}
+
+HANDLE
+gst_ks_clock_get_handle (GstKsClock * self)
+{
+ GstKsClockPrivate *priv = GST_KS_CLOCK_GET_PRIVATE (self);
+ HANDLE handle;
+
+ GST_KS_CLOCK_LOCK ();
+ g_assert (priv->open);
+ handle = priv->clock_handle;
+ GST_KS_CLOCK_UNLOCK ();
+
+ return handle;
+}
+
+void
+gst_ks_clock_prepare (GstKsClock * self)
+{
+ GstKsClockPrivate *priv = GST_KS_CLOCK_GET_PRIVATE (self);
+
+ GST_KS_CLOCK_LOCK ();
+ if (priv->state < KSSTATE_PAUSE)
+ gst_ks_clock_set_state_unlocked (self, KSSTATE_PAUSE);
+ GST_KS_CLOCK_UNLOCK ();
+}
+
+static gpointer
+gst_ks_clock_worker_thread_func (gpointer data)
+{
+ GstKsClock *self = GST_KS_CLOCK (data);
+ GstKsClockPrivate *priv = GST_KS_CLOCK_GET_PRIVATE (self);
+
+ GST_KS_CLOCK_LOCK ();
+
+ gst_ks_clock_set_state_unlocked (self, KSSTATE_RUN);
+
+ while (priv->worker_running) {
+ GTimeVal next_wakeup;
+
+ if (priv->master_clock != NULL) {
+ GstClockTime now = gst_clock_get_time (priv->master_clock);
+ now /= 100;
+
+ if (!ks_object_set_property (priv->clock_handle, KSPROPSETID_Clock,
+ KSPROPERTY_CLOCK_TIME, &now, sizeof (now))) {
+ GST_WARNING ("Failed to sync clock");
+ }
+ }
+
+ if (!priv->worker_initialized) {
+ priv->worker_initialized = TRUE;
+ g_cond_signal (priv->client_cond);
+ }
+
+ g_get_current_time (&next_wakeup);
+ next_wakeup.tv_sec += 1;
+ g_cond_timed_wait (priv->worker_cond, priv->mutex, &next_wakeup);
+ }
+
+ priv->worker_initialized = FALSE;
+ GST_KS_CLOCK_UNLOCK ();
+
+ return NULL;
+}
+
+void
+gst_ks_clock_start (GstKsClock * self)
+{
+ GstKsClockPrivate *priv = GST_KS_CLOCK_GET_PRIVATE (self);
+
+ GST_KS_CLOCK_LOCK ();
+
+ if (priv->worker_thread == NULL) {
+ priv->worker_running = TRUE;
+ priv->worker_initialized = FALSE;
+
+ priv->worker_thread =
+ g_thread_create_full (gst_ks_clock_worker_thread_func, self, 0, TRUE,
+ TRUE, G_THREAD_PRIORITY_HIGH, NULL);
+ }
+
+ while (!priv->worker_initialized)
+ g_cond_wait (priv->client_cond, priv->mutex);
+
+ GST_KS_CLOCK_UNLOCK ();
+}
+
+void
+gst_ks_clock_provide_master_clock (GstKsClock * self, GstClock * master_clock)
+{
+ GstKsClockPrivate *priv = GST_KS_CLOCK_GET_PRIVATE (self);
+
+ GST_KS_CLOCK_LOCK ();
+
+ gst_object_ref (master_clock);
+ if (priv->master_clock != NULL)
+ gst_object_unref (priv->master_clock);
+ priv->master_clock = master_clock;
+ g_cond_signal (priv->worker_cond);
+
+ GST_KS_CLOCK_UNLOCK ();
+}
diff --git a/sys/winks/gstksclock.h b/sys/winks/gstksclock.h
new file mode 100644
index 00000000..42a043f1
--- /dev/null
+++ b/sys/winks/gstksclock.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2008 Ole André Vadla Ravnås <ole.andre.ravnas@tandberg.com>
+ *
+ * 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.
+ */
+
+#ifndef __GST_KS_CLOCK_H__
+#define __GST_KS_CLOCK_H__
+
+#include <gst/gst.h>
+#include <windows.h>
+
+#include "ksvideohelpers.h"
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_KS_CLOCK \
+ (gst_ks_clock_get_type ())
+#define GST_KS_CLOCK(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_KS_CLOCK, GstKsClock))
+#define GST_KS_CLOCK_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_KS_CLOCK, GstKsClockClass))
+#define GST_IS_KS_CLOCK(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_KS_CLOCK))
+#define GST_IS_KS_CLOCK_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_KS_CLOCK))
+
+typedef struct _GstKsClock GstKsClock;
+typedef struct _GstKsClockClass GstKsClockClass;
+
+struct _GstKsClock
+{
+ GObject parent;
+};
+
+struct _GstKsClockClass
+{
+ GObjectClass parent_class;
+};
+
+GType gst_ks_clock_get_type (void);
+
+gboolean gst_ks_clock_open (GstKsClock * self);
+void gst_ks_clock_close (GstKsClock * self);
+
+HANDLE gst_ks_clock_get_handle (GstKsClock * self);
+
+void gst_ks_clock_prepare (GstKsClock * self);
+void gst_ks_clock_start (GstKsClock * self);
+
+void gst_ks_clock_provide_master_clock (GstKsClock * self,
+ GstClock * master_clock);
+
+G_END_DECLS
+
+#endif /* __GST_KS_CLOCK_H__ */
diff --git a/sys/winks/gstksvideodevice.c b/sys/winks/gstksvideodevice.c
new file mode 100644
index 00000000..8b385e7e
--- /dev/null
+++ b/sys/winks/gstksvideodevice.c
@@ -0,0 +1,1092 @@
+/*
+ * Copyright (C) 2008 Ole André Vadla Ravnås <ole.andre.ravnas@tandberg.com>
+ *
+ * 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.
+ */
+
+#include "gstksvideodevice.h"
+
+#include "gstksclock.h"
+#include "kshelpers.h"
+#include "ksvideohelpers.h"
+
+#define READ_TIMEOUT (10 * 1000)
+#define MJPEG_MAX_PADDING 128
+#define MAX_OUTSTANDING_FRAMES 128
+#define BUFFER_ALIGNMENT 512
+
+#define DEFAULT_DEVICE_PATH NULL
+
+GST_DEBUG_CATEGORY_EXTERN (gst_ks_debug);
+#define GST_CAT_DEFAULT gst_ks_debug
+
+enum
+{
+ PROP_0,
+ PROP_CLOCK,
+ PROP_DEVICE_PATH,
+};
+
+typedef struct
+{
+ KSSTREAM_HEADER header;
+ KS_FRAME_INFO frame_info;
+} KSSTREAM_READ_PARAMS;
+
+typedef struct
+{
+ KSSTREAM_READ_PARAMS params;
+ guint8 *buf_unaligned;
+ guint8 *buf;
+ OVERLAPPED overlapped;
+} ReadRequest;
+
+typedef struct
+{
+ gboolean open;
+ KSSTATE state;
+
+ GstKsClock *clock;
+ gchar *dev_path;
+ HANDLE filter_handle;
+ GList *media_types;
+ GstCaps *cached_caps;
+ HANDLE cancel_event;
+
+ KsVideoMediaType *cur_media_type;
+ GstCaps *cur_fixed_caps;
+ guint width;
+ guint height;
+ guint fps_n;
+ guint fps_d;
+ guint8 *rgb_swap_buf;
+ gboolean is_mjpeg;
+
+ HANDLE pin_handle;
+
+ gboolean requests_submitted;
+ gulong num_requests;
+ GArray *requests;
+ GArray *request_events;
+} GstKsVideoDevicePrivate;
+
+#define GST_KS_VIDEO_DEVICE_GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), GST_TYPE_KS_VIDEO_DEVICE, \
+ GstKsVideoDevicePrivate))
+
+static void gst_ks_video_device_dispose (GObject * object);
+static void gst_ks_video_device_finalize (GObject * object);
+static void gst_ks_video_device_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec);
+static void gst_ks_video_device_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec);
+
+static void gst_ks_video_device_reset_caps (GstKsVideoDevice * self);
+
+GST_BOILERPLATE (GstKsVideoDevice, gst_ks_video_device, GObject, G_TYPE_OBJECT);
+
+static void
+gst_ks_video_device_base_init (gpointer gclass)
+{
+}
+
+static void
+gst_ks_video_device_class_init (GstKsVideoDeviceClass * klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (GstKsVideoDevicePrivate));
+
+ gobject_class->dispose = gst_ks_video_device_dispose;
+ gobject_class->finalize = gst_ks_video_device_finalize;
+ gobject_class->get_property = gst_ks_video_device_get_property;
+ gobject_class->set_property = gst_ks_video_device_set_property;
+
+ g_object_class_install_property (gobject_class, PROP_CLOCK,
+ g_param_spec_object ("clock", "Clock to use",
+ "Clock to use", GST_TYPE_KS_CLOCK,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property (gobject_class, PROP_DEVICE_PATH,
+ g_param_spec_string ("device-path", "Device Path",
+ "The device path", DEFAULT_DEVICE_PATH,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+gst_ks_video_device_init (GstKsVideoDevice * self,
+ GstKsVideoDeviceClass * gclass)
+{
+ GstKsVideoDevicePrivate *priv = GST_KS_VIDEO_DEVICE_GET_PRIVATE (self);
+
+ priv->open = FALSE;
+ priv->state = KSSTATE_STOP;
+}
+
+static void
+gst_ks_video_device_dispose (GObject * object)
+{
+ GstKsVideoDevice *self = GST_KS_VIDEO_DEVICE (object);
+ GstKsVideoDevicePrivate *priv = GST_KS_VIDEO_DEVICE_GET_PRIVATE (self);
+
+ gst_ks_video_device_reset_caps (self);
+ gst_ks_video_device_close (self);
+
+ if (priv->clock != NULL) {
+ g_object_unref (priv->clock);
+ priv->clock = NULL;
+ }
+
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+gst_ks_video_device_finalize (GObject * object)
+{
+ GstKsVideoDevice *self = GST_KS_VIDEO_DEVICE (object);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gst_ks_video_device_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec)
+{
+ GstKsVideoDevice *self = GST_KS_VIDEO_DEVICE (object);
+ GstKsVideoDevicePrivate *priv = GST_KS_VIDEO_DEVICE_GET_PRIVATE (self);
+
+ switch (prop_id) {
+ case PROP_CLOCK:
+ g_value_set_object (value, priv->clock);
+ break;
+ case PROP_DEVICE_PATH:
+ g_value_set_string (value, priv->dev_path);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_ks_video_device_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ GstKsVideoDevice *self = GST_KS_VIDEO_DEVICE (object);
+ GstKsVideoDevicePrivate *priv = GST_KS_VIDEO_DEVICE_GET_PRIVATE (self);
+
+ switch (prop_id) {
+ case PROP_CLOCK:
+ if (priv->clock != NULL)
+ g_object_unref (priv->clock);
+ priv->clock = g_value_dup_object (value);
+ break;
+ case PROP_DEVICE_PATH:
+ g_free (priv->dev_path);
+ priv->dev_path = g_value_dup_string (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_ks_video_device_parse_win32_error (const gchar * func_name,
+ DWORD error_code, gulong * ret_error_code, gchar ** ret_error_str)
+{
+ if (ret_error_code != NULL)
+ *ret_error_code = error_code;
+
+ if (ret_error_str != NULL) {
+ GString *message;
+ gchar buf[1480];
+ DWORD result;
+
+ message = g_string_sized_new (1600);
+ g_string_append_printf (message, "%s returned ", func_name);
+
+ result =
+ FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM |
+ FORMAT_MESSAGE_IGNORE_INSERTS, NULL, error_code, 0, buf, sizeof (buf),
+ NULL);
+ if (result != 0) {
+ g_string_append_printf (message, "0x%08x: %s", error_code,
+ g_strchomp (buf));
+ } else {
+ DWORD format_error_code = GetLastError ();
+
+ g_string_append_printf (message,
+ "<0x%08x (FormatMessage error code: %s)>", error_code,
+ (format_error_code == ERROR_MR_MID_NOT_FOUND)
+ ? "no system error message found"
+ : "failed to retrieve system error message");
+ }
+
+ *ret_error_str = message->str;
+ g_string_free (message, FALSE);
+ }
+}
+
+static void
+gst_ks_video_device_clear_buffers (GstKsVideoDevice * self)
+{
+ GstKsVideoDevicePrivate *priv = GST_KS_VIDEO_DEVICE_GET_PRIVATE (self);
+ guint i;
+
+ if (priv->requests == NULL)
+ return;
+
+ /* Cancel pending requests */
+ CancelIo (priv->pin_handle);
+
+ for (i = 0; i < priv->num_requests; i++) {
+ ReadRequest *req = &g_array_index (priv->requests, ReadRequest, i);
+ DWORD bytes_returned;
+
+ GetOverlappedResult (priv->pin_handle, &req->overlapped, &bytes_returned,
+ TRUE);
+ }
+
+ /* Clean up */
+ for (i = 0; i < priv->requests->len; i++) {
+ ReadRequest *req = &g_array_index (priv->requests, ReadRequest, i);
+ HANDLE ev = g_array_index (priv->request_events, HANDLE, i);
+
+ g_free (req->buf_unaligned);
+
+ if (ev)
+ CloseHandle (ev);
+ }
+
+ g_array_free (priv->requests, TRUE);
+ priv->requests = NULL;
+
+ g_array_free (priv->request_events, TRUE);
+ priv->request_events = NULL;
+}
+
+static void
+gst_ks_video_device_prepare_buffers (GstKsVideoDevice * self)
+{
+ GstKsVideoDevicePrivate *priv = GST_KS_VIDEO_DEVICE_GET_PRIVATE (self);
+ guint i;
+ guint frame_size;
+
+ g_assert (priv->cur_media_type != NULL);
+
+ gst_ks_video_device_clear_buffers (self);
+
+ priv->requests = g_array_sized_new (FALSE, TRUE, sizeof (ReadRequest),
+ priv->num_requests);
+ priv->request_events = g_array_sized_new (FALSE, TRUE, sizeof (HANDLE),
+ priv->num_requests + 1);
+
+ frame_size = gst_ks_video_device_get_frame_size (self);
+
+ for (i = 0; i < priv->num_requests; i++) {
+ ReadRequest req = { 0, };
+
+ req.buf_unaligned = g_malloc (frame_size + BUFFER_ALIGNMENT - 1);
+ req.buf = (guint8 *) (((gsize) req.buf_unaligned + BUFFER_ALIGNMENT - 1)
+ & ~(BUFFER_ALIGNMENT - 1));
+
+ req.overlapped.hEvent = CreateEvent (NULL, TRUE, FALSE, NULL);
+
+ g_array_append_val (priv->requests, req);
+ g_array_append_val (priv->request_events, req.overlapped.hEvent);
+ }
+
+ g_array_append_val (priv->request_events, priv->cancel_event);
+}
+
+static void
+gst_ks_video_device_dump_supported_property_sets (GstKsVideoDevice * self,
+ const gchar * obj_name, const GUID * propsets, gulong propsets_len)
+{
+ guint i;
+
+ GST_DEBUG ("%s supports %d property set%s", obj_name, propsets_len,
+ (propsets_len != 1) ? "s" : "");
+
+ for (i = 0; i < propsets_len; i++) {
+ gchar *propset_name = ks_property_set_to_string (&propsets[i]);
+ GST_DEBUG ("[%d] %s", i, propset_name);
+ g_free (propset_name);
+ }
+}
+
+gboolean
+gst_ks_video_device_open (GstKsVideoDevice * self)
+{
+ GstKsVideoDevicePrivate *priv = GST_KS_VIDEO_DEVICE_GET_PRIVATE (self);
+ GUID *propsets = NULL;
+ gulong propsets_len;
+ GList *cur;
+
+ g_assert (!priv->open);
+ g_assert (priv->dev_path != NULL);
+
+ /*
+ * Open the filter.
+ */
+ priv->filter_handle = CreateFile (priv->dev_path,
+ GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING,
+ FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL);
+ if (!ks_is_valid_handle (priv->filter_handle))
+ goto error;
+
+ /*
+ * Query the filter for supported property sets.
+ */
+ if (ks_object_get_supported_property_sets (priv->filter_handle, &propsets,
+ &propsets_len)) {
+ gst_ks_video_device_dump_supported_property_sets (self, "filter",
+ propsets, propsets_len);
+ g_free (propsets);
+ } else {
+ GST_DEBUG ("failed to query filter for supported property sets");
+ }
+
+ /*
+ * Probe for supported media types.
+ */
+ priv->media_types = ks_video_probe_filter_for_caps (priv->filter_handle);
+ priv->cached_caps = gst_caps_new_empty ();
+
+ for (cur = priv->media_types; cur != NULL; cur = cur->next) {
+ KsVideoMediaType *media_type = cur->data;
+
+ gst_caps_append (priv->cached_caps,
+ gst_caps_copy (media_type->translated_caps));
+
+#if 1
+ {
+ gchar *str;
+ str = gst_caps_to_string (media_type->translated_caps);
+ GST_DEBUG ("pin[%d]: found media type: %s", media_type->pin_id, str);
+ g_free (str);
+ }
+#endif
+ }
+
+ priv->cancel_event = CreateEvent (NULL, TRUE, FALSE, NULL);
+
+ priv->open = TRUE;
+
+ return TRUE;
+
+error:
+ g_free (priv->dev_path);
+ priv->dev_path = NULL;
+
+ return FALSE;
+}
+
+void
+gst_ks_video_device_close (GstKsVideoDevice * self)
+{
+ GstKsVideoDevicePrivate *priv = GST_KS_VIDEO_DEVICE_GET_PRIVATE (self);
+ GList *cur;
+
+ gst_ks_video_device_reset_caps (self);
+
+ g_free (priv->dev_path);
+ priv->dev_path = NULL;
+
+ if (ks_is_valid_handle (priv->filter_handle)) {
+ CloseHandle (priv->filter_handle);
+ priv->filter_handle = INVALID_HANDLE_VALUE;
+ }
+
+ for (cur = priv->media_types; cur != NULL; cur = cur->next) {
+ KsVideoMediaType *mt = cur->data;
+ ks_video_media_type_free (mt);
+ }
+
+ if (priv->media_types != NULL) {
+ g_list_free (priv->media_types);
+ priv->media_types = NULL;
+ }
+
+ if (priv->cached_caps != NULL) {
+ gst_caps_unref (priv->cached_caps);
+ priv->cached_caps = NULL;
+ }
+
+ if (ks_is_valid_handle (priv->cancel_event))
+ CloseHandle (priv->cancel_event);
+ priv->cancel_event = INVALID_HANDLE_VALUE;
+
+ priv->open = FALSE;
+}
+
+GstCaps *
+gst_ks_video_device_get_available_caps (GstKsVideoDevice * self)
+{
+ GstKsVideoDevicePrivate *priv = GST_KS_VIDEO_DEVICE_GET_PRIVATE (self);
+
+ g_assert (priv->open);
+
+ return gst_caps_ref (priv->cached_caps);
+}
+
+gboolean
+gst_ks_video_device_has_caps (GstKsVideoDevice * self)
+{
+ GstKsVideoDevicePrivate *priv = GST_KS_VIDEO_DEVICE_GET_PRIVATE (self);
+
+ return (priv->cur_media_type != NULL) ? TRUE : FALSE;
+}
+
+static HANDLE
+gst_ks_video_device_create_pin (GstKsVideoDevice * self,
+ KsVideoMediaType * media_type, gulong * num_outstanding)
+{
+ GstKsVideoDevicePrivate *priv = GST_KS_VIDEO_DEVICE_GET_PRIVATE (self);
+
+ HANDLE pin_handle = INVALID_HANDLE_VALUE;
+ KSPIN_CONNECT *pin_conn = NULL;
+ DWORD ret;
+
+ GUID *propsets = NULL;
+ gulong propsets_len;
+ gboolean supports_mem_transport = FALSE;
+
+ KSALLOCATOR_FRAMING *framing = NULL;
+ gulong framing_size = sizeof (KSALLOCATOR_FRAMING);
+ KSALLOCATOR_FRAMING_EX *framing_ex = NULL;
+ gulong alignment;
+
+ DWORD mem_transport;
+
+ /*
+ * Instantiate the pin.
+ */
+ pin_conn = ks_video_create_pin_conn_from_media_type (media_type);
+
+ GST_DEBUG ("calling KsCreatePin with pin_id = %d", media_type->pin_id);
+
+ ret = KsCreatePin (priv->filter_handle, pin_conn, GENERIC_READ, &pin_handle);
+ if (ret != ERROR_SUCCESS)
+ goto error_create_pin;
+
+ GST_DEBUG ("KsCreatePin succeeded, pin %p created", pin_handle);
+
+ g_free (pin_conn);
+ pin_conn = NULL;
+
+ /*
+ * Query the pin for supported property sets.
+ */
+ if (ks_object_get_supported_property_sets (pin_handle, &propsets,
+ &propsets_len)) {
+ guint i;
+
+ gst_ks_video_device_dump_supported_property_sets (self, "pin", propsets,
+ propsets_len);
+
+ for (i = 0; i < propsets_len; i++) {
+ if (IsEqualGUID (&propsets[i], &KSPROPSETID_MemoryTransport))
+ supports_mem_transport = TRUE;
+ }
+
+ g_free (propsets);
+ } else {
+ GST_DEBUG ("failed to query pin for supported property sets");
+ }
+
+ /*
+ * Figure out how many simultanous requests it prefers.
+ *
+ * This is really important as it depends on the driver and the device.
+ * Doing too few will result in poor capture performance, whilst doing too
+ * many will make some drivers crash really horribly and leave you with a
+ * BSOD. I've experienced the latter with older Logitech drivers.
+ */
+ *num_outstanding = 0;
+ alignment = 0;
+
+ if (ks_object_get_property (pin_handle, KSPROPSETID_Connection,
+ KSPROPERTY_CONNECTION_ALLOCATORFRAMING_EX, &framing_ex, NULL)) {
+ if (framing_ex->CountItems >= 1) {
+ *num_outstanding = framing_ex->FramingItem[0].Frames;
+ alignment = framing_ex->FramingItem[0].FileAlignment;
+ } else {
+ GST_DEBUG ("ignoring empty ALLOCATORFRAMING_EX");
+ }
+ } else {
+ GST_DEBUG ("query for ALLOCATORFRAMING_EX failed, trying "
+ "ALLOCATORFRAMING");
+
+ if (ks_object_get_property (pin_handle, KSPROPSETID_Connection,
+ KSPROPERTY_CONNECTION_ALLOCATORFRAMING, &framing, &framing_size)) {
+ *num_outstanding = framing->Frames;
+ alignment = framing->FileAlignment;
+ } else {
+ GST_DEBUG ("query for ALLOCATORFRAMING failed");
+ }
+ }
+
+ GST_DEBUG ("num_outstanding: %d alignment: 0x%08x", *num_outstanding,
+ alignment);
+
+ if (*num_outstanding == 0 || *num_outstanding > MAX_OUTSTANDING_FRAMES) {
+ GST_DEBUG ("setting number of allowable outstanding frames to 1");
+ *num_outstanding = 1;
+ }
+
+ g_free (framing);
+ g_free (framing_ex);
+
+ /*
+ * TODO: We also need to respect alignment, but for now we just align
+ * on FILE_512_BYTE_ALIGNMENT.
+ */
+
+ /* Set the memory transport to use. */
+ if (supports_mem_transport) {
+ mem_transport = 0; /* REVISIT: use the constant here */
+ if (!ks_object_set_property (pin_handle, KSPROPSETID_MemoryTransport,
+ KSPROPERTY_MEMORY_TRANSPORT, &mem_transport,
+ sizeof (mem_transport))) {
+ GST_DEBUG ("failed to set memory transport, sticking with the default");
+ }
+ }
+
+ /*
+ * Override the clock if we have one.
+ */
+ if (priv->clock != NULL) {
+ HANDLE clock_handle = gst_ks_clock_get_handle (priv->clock);
+
+ if (ks_object_set_property (pin_handle, KSPROPSETID_Stream,
+ KSPROPERTY_STREAM_MASTERCLOCK, &clock_handle,
+ sizeof (clock_handle))) {
+ gst_ks_clock_prepare (priv->clock);
+ } else {
+ GST_WARNING ("failed to set pin's master clock");
+ }
+ }
+
+ return pin_handle;
+
+ /* ERRORS */
+error_create_pin:
+ {
+ gchar *str;
+
+ gst_ks_video_device_parse_win32_error ("KsCreatePin", ret, NULL, &str);
+ GST_ERROR ("%s", str);
+ g_free (str);
+
+ goto beach;
+ }
+beach:
+ {
+ g_free (framing);
+ if (ks_is_valid_handle (pin_handle))
+ CloseHandle (pin_handle);
+ g_free (pin_conn);
+
+ return INVALID_HANDLE_VALUE;
+ }
+}
+
+static void
+gst_ks_video_device_close_current_pin (GstKsVideoDevice * self)
+{
+ GstKsVideoDevicePrivate *priv = GST_KS_VIDEO_DEVICE_GET_PRIVATE (self);
+
+ if (!ks_is_valid_handle (priv->pin_handle))
+ return;
+
+ gst_ks_video_device_set_state (self, KSSTATE_STOP);
+
+ CloseHandle (priv->pin_handle);
+ priv->pin_handle = INVALID_HANDLE_VALUE;
+}
+
+static void
+gst_ks_video_device_reset_caps (GstKsVideoDevice * self)
+{
+ GstKsVideoDevicePrivate *priv = GST_KS_VIDEO_DEVICE_GET_PRIVATE (self);
+
+ gst_ks_video_device_close_current_pin (self);
+
+ ks_video_media_type_free (priv->cur_media_type);
+ priv->cur_media_type = NULL;
+
+ priv->width = priv->height = priv->fps_n = priv->fps_d = 0;
+
+ g_free (priv->rgb_swap_buf);
+ priv->rgb_swap_buf = NULL;
+
+ if (priv->cur_fixed_caps != NULL) {
+ gst_caps_unref (priv->cur_fixed_caps);
+ priv->cur_fixed_caps = NULL;
+ }
+}
+
+gboolean
+gst_ks_video_device_set_caps (GstKsVideoDevice * self, GstCaps * caps)
+{
+ GstKsVideoDevicePrivate *priv = GST_KS_VIDEO_DEVICE_GET_PRIVATE (self);
+ GList *cur;
+ GstStructure *s;
+
+ /* State to be committed on success */
+ KsVideoMediaType *media_type = NULL;
+ guint width, height, fps_n, fps_d;
+ HANDLE pin_handle = INVALID_HANDLE_VALUE;
+
+ /* Reset? */
+ if (caps == NULL) {
+ gst_ks_video_device_reset_caps (self);
+ return TRUE;
+ }
+
+ /* Validate the caps */
+ if (!gst_caps_is_subset (caps, priv->cached_caps)) {
+ gchar *string_caps = gst_caps_to_string (caps);
+ gchar *string_c_caps = gst_caps_to_string (priv->cached_caps);
+
+ GST_ERROR ("caps (%s) is not a subset of device caps (%s)",
+ string_caps, string_c_caps);
+
+ g_free (string_caps);
+ g_free (string_c_caps);
+
+ goto error;
+ }
+
+ for (cur = priv->media_types; cur != NULL; cur = cur->next) {
+ KsVideoMediaType *mt = cur->data;
+
+ if (gst_caps_is_subset (caps, mt->translated_caps)) {
+ media_type = ks_video_media_type_dup (mt);
+ break;
+ }
+ }
+
+ if (media_type == NULL)
+ goto error;
+
+ s = gst_caps_get_structure (caps, 0);
+ if (!gst_structure_get_int (s, "width", &width) ||
+ !gst_structure_get_int (s, "height", &height) ||
+ !gst_structure_get_fraction (s, "framerate", &fps_n, &fps_d)) {
+ GST_ERROR ("Failed to get width/height/fps");
+ goto error;
+ }
+
+ if (!ks_video_fixate_media_type (media_type->range,
+ media_type->format, width, height, fps_n, fps_d))
+ goto error;
+
+ if (priv->cur_media_type != NULL) {
+ if (media_type->format_size == priv->cur_media_type->format_size &&
+ memcmp (media_type->format, priv->cur_media_type->format,
+ priv->cur_media_type->format_size) == 0) {
+ GST_DEBUG ("%s: re-using existing pin", G_STRFUNC);
+ goto same_caps;
+ } else {
+ GST_DEBUG ("%s: re-creating pin", G_STRFUNC);
+ }
+ }
+
+ gst_ks_video_device_close_current_pin (self);
+
+ pin_handle = gst_ks_video_device_create_pin (self, media_type,
+ &priv->num_requests);
+ if (!ks_is_valid_handle (pin_handle)) {
+ /* Re-create the old pin */
+ if (priv->cur_media_type != NULL)
+ priv->pin_handle = gst_ks_video_device_create_pin (self,
+ priv->cur_media_type, &priv->num_requests);
+ goto error;
+ }
+
+ /* Commit state: no turning back past this */
+ gst_ks_video_device_reset_caps (self);
+
+ priv->cur_media_type = media_type;
+ priv->width = width;
+ priv->height = height;
+ priv->fps_n = fps_n;
+ priv->fps_d = fps_d;
+
+ if (gst_structure_has_name (s, "video/x-raw-rgb"))
+ priv->rgb_swap_buf = g_malloc (media_type->sample_size / priv->height);
+ else
+ priv->rgb_swap_buf = NULL;
+
+ priv->is_mjpeg = gst_structure_has_name (s, "image/jpeg");
+
+ priv->pin_handle = pin_handle;
+
+ priv->cur_fixed_caps = gst_caps_copy (caps);
+
+ return TRUE;
+
+error:
+ {
+ ks_video_media_type_free (media_type);
+ return FALSE;
+ }
+same_caps:
+ {
+ ks_video_media_type_free (media_type);
+ return TRUE;
+ }
+}
+
+gboolean
+gst_ks_video_device_set_state (GstKsVideoDevice * self, KSSTATE state)
+{
+ GstKsVideoDevicePrivate *priv = GST_KS_VIDEO_DEVICE_GET_PRIVATE (self);
+ KSSTATE initial_state;
+ gint addend;
+
+ g_assert (priv->cur_media_type != NULL);
+
+ if (state == priv->state)
+ return TRUE;
+
+ initial_state = priv->state;
+ addend = (state > priv->state) ? 1 : -1;
+
+ GST_DEBUG ("Initiating pin state change from %s to %s",
+ ks_state_to_string (priv->state), ks_state_to_string (state));
+
+ while (priv->state != state) {
+ KSSTATE next_state = priv->state + addend;
+
+ GST_DEBUG ("Changing pin state from %s to %s",
+ ks_state_to_string (priv->state), ks_state_to_string (next_state));
+
+ if (ks_object_set_connection_state (priv->pin_handle, next_state)) {
+ priv->state = next_state;
+
+ GST_DEBUG ("Changed pin state to %s", ks_state_to_string (priv->state));
+
+ if (priv->state == KSSTATE_PAUSE && addend > 0)
+ gst_ks_video_device_prepare_buffers (self);
+ else if (priv->state == KSSTATE_ACQUIRE && addend < 0)
+ gst_ks_video_device_clear_buffers (self);
+ } else {
+ GST_WARNING ("Failed to change pin state to %s",
+ ks_state_to_string (next_state));
+ return FALSE;
+ }
+ }
+
+ GST_DEBUG ("Finished pin state change from %s to %s",
+ ks_state_to_string (initial_state), ks_state_to_string (state));
+
+ return TRUE;
+}
+
+guint
+gst_ks_video_device_get_frame_size (GstKsVideoDevice * self)
+{
+ GstKsVideoDevicePrivate *priv = GST_KS_VIDEO_DEVICE_GET_PRIVATE (self);
+
+ g_assert (priv->cur_media_type != NULL);
+
+ return priv->cur_media_type->sample_size;
+}
+
+GstClockTime
+gst_ks_video_device_get_duration (GstKsVideoDevice * self)
+{
+ GstKsVideoDevicePrivate *priv = GST_KS_VIDEO_DEVICE_GET_PRIVATE (self);
+
+ g_assert (priv->cur_media_type != NULL);
+
+ return gst_util_uint64_scale_int (GST_SECOND, priv->fps_d, priv->fps_n);
+}
+
+gboolean
+gst_ks_video_device_get_latency (GstKsVideoDevice * self,
+ GstClockTime * min_latency, GstClockTime * max_latency)
+{
+ GstKsVideoDevicePrivate *priv = GST_KS_VIDEO_DEVICE_GET_PRIVATE (self);
+
+ if (priv->cur_media_type == NULL)
+ return FALSE;
+
+ *min_latency =
+ gst_util_uint64_scale_int (GST_SECOND, priv->fps_d, priv->fps_n);
+ *max_latency = *min_latency;
+
+ return TRUE;
+}
+
+static gboolean
+gst_ks_video_device_request_frame (GstKsVideoDevice * self, ReadRequest * req,
+ gulong * error_code, gchar ** error_str)
+{
+ GstKsVideoDevicePrivate *priv = GST_KS_VIDEO_DEVICE_GET_PRIVATE (self);
+ HANDLE event;
+ KSSTREAM_READ_PARAMS *params;
+ BOOL success;
+ DWORD bytes_returned = 0;
+
+ /* Reset the OVERLAPPED structure */
+ event = req->overlapped.hEvent;
+ memset (&req->overlapped, 0, sizeof (OVERLAPPED));
+ req->overlapped.hEvent = event;
+
+ /* Fill out KSSTREAM_HEADER and KS_FRAME_INFO */
+ params = &req->params;
+ memset (params, 0, sizeof (KSSTREAM_READ_PARAMS));
+
+ params->header.Size = sizeof (KSSTREAM_HEADER) + sizeof (KS_FRAME_INFO);
+ params->header.PresentationTime.Numerator = 1;
+ params->header.PresentationTime.Denominator = 1;
+ params->header.FrameExtent = gst_ks_video_device_get_frame_size (self);
+ params->header.Data = req->buf;
+ params->frame_info.ExtendedHeaderSize = sizeof (KS_FRAME_INFO);
+
+ success = DeviceIoControl (priv->pin_handle, IOCTL_KS_READ_STREAM, NULL, 0,
+ params, params->header.Size, &bytes_returned, &req->overlapped);
+ if (!success && GetLastError () != ERROR_IO_PENDING)
+ goto error_ioctl;
+
+ return TRUE;
+
+ /* ERRORS */
+error_ioctl:
+ {
+ gst_ks_video_device_parse_win32_error ("DeviceIoControl", GetLastError (),
+ error_code, error_str);
+ return FALSE;
+ }
+}
+
+GstFlowReturn
+gst_ks_video_device_read_frame (GstKsVideoDevice * self, guint8 * buf,
+ gulong buf_size, gulong * bytes_read, GstClockTime * presentation_time,
+ gulong * error_code, gchar ** error_str)
+{
+ GstKsVideoDevicePrivate *priv = GST_KS_VIDEO_DEVICE_GET_PRIVATE (self);
+ guint req_idx;
+ DWORD wait_ret;
+ BOOL success;
+ DWORD bytes_returned;
+
+ g_assert (priv->cur_media_type != NULL);
+
+ /* Set the state if needed */
+ if (G_UNLIKELY (priv->state != KSSTATE_RUN)) {
+ if (priv->clock != NULL)
+ gst_ks_clock_start (priv->clock);
+
+ if (!gst_ks_video_device_set_state (self, KSSTATE_RUN))
+ goto error_set_state;
+ }
+
+ /* First time we're called, submit the requests. */
+ if (G_UNLIKELY (!priv->requests_submitted)) {
+ priv->requests_submitted = TRUE;
+
+ for (req_idx = 0; req_idx < priv->num_requests; req_idx++) {
+ ReadRequest *req = &g_array_index (priv->requests, ReadRequest, req_idx);
+
+ if (!gst_ks_video_device_request_frame (self, req, error_code, error_str))
+ goto error_request_failed;
+ }
+ }
+
+ do {
+ /* Wait for either a request to complete, a cancel or a timeout */
+ wait_ret = WaitForMultipleObjects (priv->request_events->len,
+ (HANDLE *) priv->request_events->data, FALSE, READ_TIMEOUT);
+ if (wait_ret == WAIT_TIMEOUT)
+ goto error_timeout;
+ else if (wait_ret == WAIT_FAILED)
+ goto error_wait;
+
+ /* Stopped? */
+ if (WaitForSingleObject (priv->cancel_event, 0) == WAIT_OBJECT_0)
+ goto error_cancel;
+
+ *bytes_read = 0;
+
+ /* Find the last ReadRequest that finished and get the result, immediately
+ * re-issuing each request that has completed. */
+ for (req_idx = wait_ret - WAIT_OBJECT_0;
+ req_idx < priv->num_requests; req_idx++) {
+ ReadRequest *req = &g_array_index (priv->requests, ReadRequest, req_idx);
+
+ /*
+ * Completed? WaitForMultipleObjects() returns the lowest index if
+ * multiple objects are in the signaled state, and we know that requests
+ * are processed one by one so there's no point in looking further once
+ * we've found the first that's non-signaled.
+ */
+ if (WaitForSingleObject (req->overlapped.hEvent, 0) != WAIT_OBJECT_0)
+ break;
+
+ success = GetOverlappedResult (priv->pin_handle, &req->overlapped,
+ &bytes_returned, TRUE);
+
+ ResetEvent (req->overlapped.hEvent);
+
+ if (success) {
+ /* Grab the frame data */
+ g_assert (buf_size >= req->params.header.DataUsed);
+ memcpy (buf, req->buf, req->params.header.DataUsed);
+ *bytes_read = req->params.header.DataUsed;
+ if (req->params.header.PresentationTime.Time != 0)
+ *presentation_time = req->params.header.PresentationTime.Time * 100;
+ else
+ *presentation_time = GST_CLOCK_TIME_NONE;
+
+ if (priv->is_mjpeg) {
+ /*
+ * Workaround for cameras/drivers that intermittently provide us with
+ * incomplete or corrupted MJPEG frames.
+ *
+ * Happens with for instance Microsoft LifeCam VX-7000.
+ */
+
+ gboolean valid = FALSE;
+ guint padding = 0;
+
+ /* JFIF SOI marker */
+ if (*bytes_read > MJPEG_MAX_PADDING
+ && buf[0] == 0xff && buf[1] == 0xd8) {
+ guint8 *p = buf + *bytes_read - 2;
+
+ /* JFIF EOI marker (but skip any padding) */
+ while (padding < MJPEG_MAX_PADDING - 1 - 2 && !valid) {
+ if (p[0] == 0xff && p[1] == 0xd9) {
+ valid = TRUE;
+ } else {
+ padding++;
+ p--;
+ }
+ }
+ }
+
+ if (valid)
+ *bytes_read -= padding;
+ else
+ *bytes_read = 0;
+ }
+ } else if (GetLastError () != ERROR_OPERATION_ABORTED)
+ goto error_get_result;
+
+ /* Submit a new request immediately */
+ if (!gst_ks_video_device_request_frame (self, req, error_code, error_str))
+ goto error_request_failed;
+ }
+ } while (*bytes_read == 0);
+
+ return GST_FLOW_OK;
+
+ /* ERRORS */
+error_set_state:
+ {
+ gst_ks_video_device_parse_win32_error ("gst_ks_video_device_set_state",
+ GetLastError (), error_code, error_str);
+
+ return GST_FLOW_ERROR;
+ }
+error_request_failed:
+ {
+ return GST_FLOW_ERROR;
+ }
+error_timeout:
+ {
+ GST_DEBUG ("IOCTL_KS_READ_STREAM timed out");
+
+ if (error_code != NULL)
+ *error_code = 0;
+ if (error_str != NULL)
+ *error_str = NULL;
+
+ return GST_FLOW_UNEXPECTED;
+ }
+error_wait:
+ {
+ gst_ks_video_device_parse_win32_error ("WaitForMultipleObjects",
+ GetLastError (), error_code, error_str);
+
+ return GST_FLOW_ERROR;
+ }
+error_cancel:
+ {
+ if (error_code != NULL)
+ *error_code = 0;
+ if (error_str != NULL)
+ *error_str = NULL;
+
+ return GST_FLOW_WRONG_STATE;
+ }
+error_get_result:
+ {
+ gst_ks_video_device_parse_win32_error ("GetOverlappedResult",
+ GetLastError (), error_code, error_str);
+
+ return GST_FLOW_ERROR;
+ }
+}
+
+void
+gst_ks_video_device_postprocess_frame (GstKsVideoDevice * self,
+ guint8 * buf, guint buf_size)
+{
+ GstKsVideoDevicePrivate *priv = GST_KS_VIDEO_DEVICE_GET_PRIVATE (self);
+
+ /* If it's RGB we need to flip the image */
+ if (priv->rgb_swap_buf != NULL) {
+ gint stride, line;
+ guint8 *dst, *src;
+
+ stride = buf_size / priv->height;
+ dst = buf;
+ src = buf + buf_size - stride;
+
+ for (line = 0; line < priv->height / 2; line++) {
+ memcpy (priv->rgb_swap_buf, dst, stride);
+
+ memcpy (dst, src, stride);
+ memcpy (src, priv->rgb_swap_buf, stride);
+
+ dst += stride;
+ src -= stride;
+ }
+ }
+}
+
+void
+gst_ks_video_device_cancel (GstKsVideoDevice * self)
+{
+ GstKsVideoDevicePrivate *priv = GST_KS_VIDEO_DEVICE_GET_PRIVATE (self);
+
+ SetEvent (priv->cancel_event);
+}
+
+void
+gst_ks_video_device_cancel_stop (GstKsVideoDevice * self)
+{
+ GstKsVideoDevicePrivate *priv = GST_KS_VIDEO_DEVICE_GET_PRIVATE (self);
+
+ ResetEvent (priv->cancel_event);
+}
diff --git a/sys/winks/gstksvideodevice.h b/sys/winks/gstksvideodevice.h
new file mode 100644
index 00000000..99024e15
--- /dev/null
+++ b/sys/winks/gstksvideodevice.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2008 Ole André Vadla Ravnås <ole.andre.ravnas@tandberg.com>
+ *
+ * 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.
+ */
+
+#ifndef __GST_KS_VIDEO_DEVICE_H__
+#define __GST_KS_VIDEO_DEVICE_H__
+
+#include <gst/gst.h>
+
+#include <windows.h>
+#include <ks.h>
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_KS_VIDEO_DEVICE \
+ (gst_ks_video_device_get_type ())
+#define GST_KS_VIDEO_DEVICE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_KS_VIDEO_DEVICE, GstKsVideoDevice))
+#define GST_KS_VIDEO_DEVICE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_KS_VIDEO_DEVICE, GstKsVideoDeviceClass))
+#define GST_IS_KS_VIDEO_DEVICE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_KS_VIDEO_DEVICE))
+#define GST_IS_KS_VIDEO_DEVICE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_KS_VIDEO_DEVICE))
+
+typedef struct _GstKsVideoDevice GstKsVideoDevice;
+typedef struct _GstKsVideoDeviceClass GstKsVideoDeviceClass;
+
+struct _GstKsVideoDevice
+{
+ GObject parent;
+};
+
+struct _GstKsVideoDeviceClass
+{
+ GObjectClass parent_class;
+};
+
+GType gst_ks_video_device_get_type (void);
+
+gboolean gst_ks_video_device_open (GstKsVideoDevice * self);
+void gst_ks_video_device_close (GstKsVideoDevice * self);
+
+GstCaps * gst_ks_video_device_get_available_caps (GstKsVideoDevice * self);
+gboolean gst_ks_video_device_has_caps (GstKsVideoDevice * self);
+gboolean gst_ks_video_device_set_caps (GstKsVideoDevice * self, GstCaps * caps);
+
+gboolean gst_ks_video_device_set_state (GstKsVideoDevice * self, KSSTATE state);
+
+guint gst_ks_video_device_get_frame_size (GstKsVideoDevice * self);
+GstClockTime gst_ks_video_device_get_duration (GstKsVideoDevice * self);
+gboolean gst_ks_video_device_get_latency (GstKsVideoDevice * self, GstClockTime * min_latency, GstClockTime * max_latency);
+
+GstFlowReturn gst_ks_video_device_read_frame (GstKsVideoDevice * self, guint8 * buf, gulong buf_size, gulong * bytes_read, GstClockTime * presentation_time, gulong * error_code, gchar ** error_str);
+void gst_ks_video_device_postprocess_frame (GstKsVideoDevice * self, guint8 * buf, guint buf_size);
+void gst_ks_video_device_cancel (GstKsVideoDevice * self);
+void gst_ks_video_device_cancel_stop (GstKsVideoDevice * self);
+
+G_END_DECLS
+
+#endif /* __GST_KS_VIDEO_DEVICE_H__ */
diff --git a/sys/winks/gstksvideosrc.c b/sys/winks/gstksvideosrc.c
new file mode 100644
index 00000000..9fd1388a
--- /dev/null
+++ b/sys/winks/gstksvideosrc.c
@@ -0,0 +1,816 @@
+/*
+ * Copyright (C) 2008 Ole André Vadla Ravnås <ole.andre.ravnas@tandberg.com>
+ *
+ * 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:element-ksvideosrc
+ *
+ * Provides low-latency video capture from WDM cameras on Windows.
+ *
+ * <refsect2>
+ * <title>Example pipelines</title>
+ * |[
+ * gst-launch -v ksvideosrc do-stats=TRUE ! ffmpegcolorspace ! dshowvideosink
+ * ]| Capture from a camera and render using dshowvideosink.
+ * |[
+ * gst-launch -v ksvideosrc do-stats=TRUE ! image/jpeg, width=640, height=480
+ * ! jpegdec ! ffmpegcolorspace ! dshowvideosink
+ * ]| Capture from an MJPEG camera and render using dshowvideosink.
+ * </refsect2>
+ */
+
+#include "gstksvideosrc.h"
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include "gstksclock.h"
+#include "gstksvideodevice.h"
+#include "kshelpers.h"
+#include "ksvideohelpers.h"
+
+#define ENABLE_CLOCK_DEBUG 0
+
+#define DEFAULT_DEVICE_PATH NULL
+#define DEFAULT_DEVICE_NAME NULL
+#define DEFAULT_DEVICE_INDEX -1
+#define DEFAULT_ENSLAVE_KSCLOCK FALSE
+#define DEFAULT_DO_STATS FALSE
+
+enum
+{
+ PROP_0,
+ PROP_DEVICE_PATH,
+ PROP_DEVICE_NAME,
+ PROP_DEVICE_INDEX,
+ PROP_ENSLAVE_KSCLOCK,
+ PROP_DO_STATS,
+ PROP_FPS,
+};
+
+GST_DEBUG_CATEGORY (gst_ks_debug);
+#define GST_CAT_DEFAULT gst_ks_debug
+
+typedef struct
+{
+ /* Properties */
+ gchar *device_path;
+ gchar *device_name;
+ gint device_index;
+ gboolean enslave_ksclock;
+ gboolean do_stats;
+
+ /* State */
+ GstKsClock *ksclock;
+ GstKsVideoDevice *device;
+
+ guint64 offset;
+ GstClockTime prev_ts;
+
+ /* Statistics */
+ GstClockTime last_sampling;
+ guint count;
+ guint fps;
+} GstKsVideoSrcPrivate;
+
+#define GST_KS_VIDEO_SRC_GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), GST_TYPE_KS_VIDEO_SRC, \
+ GstKsVideoSrcPrivate))
+
+static void gst_ks_video_src_dispose (GObject * object);
+static void gst_ks_video_src_finalize (GObject * object);
+static void gst_ks_video_src_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec);
+static void gst_ks_video_src_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec);
+
+static void gst_ks_video_src_reset (GstKsVideoSrc * self);
+
+static GstStateChangeReturn gst_ks_video_src_change_state (GstElement * element,
+ GstStateChange transition);
+static gboolean gst_ks_video_src_set_clock (GstElement * element,
+ GstClock * clock);
+
+static GstCaps *gst_ks_video_src_get_caps (GstBaseSrc * basesrc);
+static gboolean gst_ks_video_src_set_caps (GstBaseSrc * basesrc,
+ GstCaps * caps);
+static void gst_ks_video_src_fixate (GstBaseSrc * basesrc, GstCaps * caps);
+static gboolean gst_ks_video_src_query (GstBaseSrc * basesrc, GstQuery * query);
+static gboolean gst_ks_video_src_unlock (GstBaseSrc * basesrc);
+static gboolean gst_ks_video_src_unlock_stop (GstBaseSrc * basesrc);
+
+static GstFlowReturn gst_ks_video_src_create (GstPushSrc * pushsrc,
+ GstBuffer ** buffer);
+
+GST_BOILERPLATE (GstKsVideoSrc, gst_ks_video_src, GstPushSrc,
+ GST_TYPE_PUSH_SRC);
+
+static void
+gst_ks_video_src_base_init (gpointer gclass)
+{
+ GstElementClass *element_class = GST_ELEMENT_CLASS (gclass);
+ static GstElementDetails element_details = {
+ "KsVideoSrc",
+ "Source/Video",
+ "Stream data from a video capture device through Windows kernel streaming",
+ "Ole André Vadla Ravnås <ole.andre.ravnas@tandberg.com>\n"
+ "Haakon Sporsheim <hakon.sporsheim@tandberg.com>"
+ };
+
+ gst_element_class_set_details (element_class, &element_details);
+
+ gst_element_class_add_pad_template (element_class,
+ gst_pad_template_new ("src", GST_PAD_SRC, GST_PAD_ALWAYS,
+ ks_video_get_all_caps ()));
+}
+
+static void
+gst_ks_video_src_class_init (GstKsVideoSrcClass * klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass);
+ GstBaseSrcClass *gstbasesrc_class = GST_BASE_SRC_CLASS (klass);
+ GstPushSrcClass *gstpushsrc_class = GST_PUSH_SRC_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (GstKsVideoSrcPrivate));
+
+ gobject_class->dispose = gst_ks_video_src_dispose;
+ gobject_class->finalize = gst_ks_video_src_finalize;
+ gobject_class->get_property = gst_ks_video_src_get_property;
+ gobject_class->set_property = gst_ks_video_src_set_property;
+
+ gstelement_class->change_state =
+ GST_DEBUG_FUNCPTR (gst_ks_video_src_change_state);
+ gstelement_class->set_clock = GST_DEBUG_FUNCPTR (gst_ks_video_src_set_clock);
+
+ gstbasesrc_class->get_caps = GST_DEBUG_FUNCPTR (gst_ks_video_src_get_caps);
+ gstbasesrc_class->set_caps = GST_DEBUG_FUNCPTR (gst_ks_video_src_set_caps);
+ gstbasesrc_class->fixate = GST_DEBUG_FUNCPTR (gst_ks_video_src_fixate);
+ gstbasesrc_class->query = GST_DEBUG_FUNCPTR (gst_ks_video_src_query);
+ gstbasesrc_class->unlock = GST_DEBUG_FUNCPTR (gst_ks_video_src_unlock);
+ gstbasesrc_class->unlock_stop =
+ GST_DEBUG_FUNCPTR (gst_ks_video_src_unlock_stop);
+
+ gstpushsrc_class->create = GST_DEBUG_FUNCPTR (gst_ks_video_src_create);
+
+ g_object_class_install_property (gobject_class, PROP_DEVICE_PATH,
+ g_param_spec_string ("device-path", "Device Path",
+ "The device path", DEFAULT_DEVICE_PATH, G_PARAM_READWRITE));
+ g_object_class_install_property (gobject_class, PROP_DEVICE_NAME,
+ g_param_spec_string ("device-name", "Device Name",
+ "The human-readable device name", DEFAULT_DEVICE_NAME,
+ G_PARAM_READWRITE));
+ g_object_class_install_property (gobject_class, PROP_DEVICE_INDEX,
+ g_param_spec_int ("device-index", "Device Index",
+ "The zero-based device index", -1, G_MAXINT, DEFAULT_DEVICE_INDEX,
+ G_PARAM_READWRITE));
+ g_object_class_install_property (gobject_class, PROP_ENSLAVE_KSCLOCK,
+ g_param_spec_boolean ("enslave-ksclock", "Enslave the clock used by KS",
+ "Enslave the clocked used by Kernel Streaming",
+ DEFAULT_ENSLAVE_KSCLOCK, G_PARAM_READWRITE));
+ g_object_class_install_property (gobject_class, PROP_DO_STATS,
+ g_param_spec_boolean ("do-stats", "Enable statistics",
+ "Enable logging of statistics", DEFAULT_DO_STATS, G_PARAM_READWRITE));
+ g_object_class_install_property (gobject_class, PROP_FPS,
+ g_param_spec_int ("fps", "Frames per second",
+ "Last measured framerate, if statistics are enabled",
+ -1, G_MAXINT, -1, G_PARAM_READABLE));
+
+ GST_DEBUG_CATEGORY_INIT (gst_ks_debug, "ksvideosrc",
+ 0, "Kernel streaming video source");
+}
+
+static void
+gst_ks_video_src_init (GstKsVideoSrc * self, GstKsVideoSrcClass * gclass)
+{
+ GstKsVideoSrcPrivate *priv = GST_KS_VIDEO_SRC_GET_PRIVATE (self);
+ GstBaseSrc *basesrc = GST_BASE_SRC (self);
+
+ gst_base_src_set_live (basesrc, TRUE);
+ gst_base_src_set_format (basesrc, GST_FORMAT_TIME);
+
+ gst_ks_video_src_reset (self);
+
+ priv->device_path = DEFAULT_DEVICE_PATH;
+ priv->device_name = DEFAULT_DEVICE_NAME;
+ priv->device_index = DEFAULT_DEVICE_INDEX;
+ priv->enslave_ksclock = DEFAULT_ENSLAVE_KSCLOCK;
+ priv->do_stats = DEFAULT_DO_STATS;
+}
+
+static void
+gst_ks_video_src_dispose (GObject * object)
+{
+ GstKsVideoSrc *self = GST_KS_VIDEO_SRC (object);
+
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+gst_ks_video_src_finalize (GObject * object)
+{
+ GstKsVideoSrc *self = GST_KS_VIDEO_SRC (object);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gst_ks_video_src_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec)
+{
+ GstKsVideoSrc *self = GST_KS_VIDEO_SRC (object);
+ GstKsVideoSrcPrivate *priv = GST_KS_VIDEO_SRC_GET_PRIVATE (self);
+
+ switch (prop_id) {
+ case PROP_DEVICE_PATH:
+ g_value_set_string (value, priv->device_path);
+ break;
+ case PROP_DEVICE_NAME:
+ g_value_set_string (value, priv->device_name);
+ break;
+ case PROP_DEVICE_INDEX:
+ g_value_set_int (value, priv->device_index);
+ break;
+ case PROP_ENSLAVE_KSCLOCK:
+ g_value_set_boolean (value, priv->enslave_ksclock);
+ break;
+ case PROP_DO_STATS:
+ GST_OBJECT_LOCK (object);
+ g_value_set_boolean (value, priv->do_stats);
+ GST_OBJECT_UNLOCK (object);
+ break;
+ case PROP_FPS:
+ GST_OBJECT_LOCK (object);
+ g_value_set_int (value, priv->fps);
+ GST_OBJECT_UNLOCK (object);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_ks_video_src_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ GstKsVideoSrc *self = GST_KS_VIDEO_SRC (object);
+ GstKsVideoSrcPrivate *priv = GST_KS_VIDEO_SRC_GET_PRIVATE (self);
+
+ switch (prop_id) {
+ case PROP_DEVICE_PATH:
+ g_free (priv->device_path);
+ priv->device_path = g_value_dup_string (value);
+ break;
+ case PROP_DEVICE_NAME:
+ g_free (priv->device_name);
+ priv->device_name = g_value_dup_string (value);
+ break;
+ case PROP_DEVICE_INDEX:
+ priv->device_index = g_value_get_int (value);
+ break;
+ case PROP_ENSLAVE_KSCLOCK:
+ GST_OBJECT_LOCK (object);
+ if (priv->device == NULL)
+ priv->enslave_ksclock = g_value_get_boolean (value);
+ else
+ g_warning ("enslave-ksclock may only be changed while in NULL state");
+ GST_OBJECT_UNLOCK (object);
+ break;
+ case PROP_DO_STATS:
+ GST_OBJECT_LOCK (object);
+ priv->do_stats = g_value_get_boolean (value);
+ GST_OBJECT_UNLOCK (object);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_ks_video_src_reset (GstKsVideoSrc * self)
+{
+ GstKsVideoSrcPrivate *priv = GST_KS_VIDEO_SRC_GET_PRIVATE (self);
+
+ /* Reset statistics */
+ priv->last_sampling = GST_CLOCK_TIME_NONE;
+ priv->count = 0;
+ priv->fps = -1;
+
+ /* Reset timestamping state */
+ priv->offset = 0;
+ priv->prev_ts = GST_CLOCK_TIME_NONE;
+}
+
+static gboolean
+gst_ks_video_src_open_device (GstKsVideoSrc * self)
+{
+ GstKsVideoSrcPrivate *priv = GST_KS_VIDEO_SRC_GET_PRIVATE (self);
+ GstKsVideoDevice *device = NULL;
+ GList *devices, *cur;
+
+ g_assert (priv->device == NULL);
+
+ devices = ks_enumerate_devices (&KSCATEGORY_VIDEO);
+ if (devices == NULL)
+ goto error_no_devices;
+
+ for (cur = devices; cur != NULL; cur = cur->next) {
+ KsDeviceEntry *entry = cur->data;
+
+ GST_DEBUG_OBJECT (self, "device %d: name='%s' path='%s'",
+ entry->index, entry->name, entry->path);
+ }
+
+ for (cur = devices; cur != NULL && device == NULL; cur = cur->next) {
+ KsDeviceEntry *entry = cur->data;
+ gboolean match;
+
+ if (priv->device_path != NULL) {
+ match = g_strcasecmp (entry->path, priv->device_path) == 0;
+ } else if (priv->device_name != NULL) {
+ match = g_strcasecmp (entry->name, priv->device_name) == 0;
+ } else if (priv->device_index >= 0) {
+ match = entry->index == priv->device_index;
+ } else {
+ match = TRUE; /* pick the first entry */
+ }
+
+ if (match) {
+ priv->ksclock = NULL;
+
+ if (priv->enslave_ksclock) {
+ priv->ksclock = g_object_new (GST_TYPE_KS_CLOCK, NULL);
+ if (priv->ksclock != NULL && !gst_ks_clock_open (priv->ksclock)) {
+ g_object_unref (priv->ksclock);
+ priv->ksclock = NULL;
+ }
+
+ if (priv->ksclock == NULL)
+ GST_WARNING_OBJECT (self, "Failed to create/open KsClock");
+ }
+
+ device = g_object_new (GST_TYPE_KS_VIDEO_DEVICE,
+ "clock", priv->ksclock, "device-path", entry->path, NULL);
+ }
+
+ ks_device_entry_free (entry);
+ }
+
+ g_list_free (devices);
+
+ if (device == NULL)
+ goto error_no_match;
+
+ if (!gst_ks_video_device_open (device))
+ goto error_open;
+
+ priv->device = device;
+
+ return TRUE;
+
+ /* ERRORS */
+error_no_devices:
+ {
+ GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND,
+ ("No video capture devices found"), (NULL));
+ return FALSE;
+ }
+error_no_match:
+ {
+ if (priv->device_path != NULL) {
+ GST_ELEMENT_ERROR (self, RESOURCE, SETTINGS,
+ ("Specified video capture device with path '%s' not found",
+ priv->device_path), (NULL));
+ } else if (priv->device_name != NULL) {
+ GST_ELEMENT_ERROR (self, RESOURCE, SETTINGS,
+ ("Specified video capture device with name '%s' not found",
+ priv->device_name), (NULL));
+ } else {
+ GST_ELEMENT_ERROR (self, RESOURCE, SETTINGS,
+ ("Specified video capture device with index %d not found",
+ priv->device_index), (NULL));
+ }
+ return FALSE;
+ }
+error_open:
+ {
+ GST_ELEMENT_ERROR (self, RESOURCE, OPEN_READ,
+ ("Failed to open device"), (NULL));
+ g_object_unref (device);
+ return FALSE;
+ }
+}
+
+static void
+gst_ks_video_src_close_device (GstKsVideoSrc * self)
+{
+ GstKsVideoSrcPrivate *priv = GST_KS_VIDEO_SRC_GET_PRIVATE (self);
+
+ g_assert (priv->device != NULL);
+
+ gst_ks_video_device_close (priv->device);
+ g_object_unref (priv->device);
+ priv->device = NULL;
+
+ if (priv->ksclock != NULL) {
+ gst_ks_clock_close (priv->ksclock);
+ g_object_unref (priv->ksclock);
+ priv->ksclock = NULL;
+ }
+
+ gst_ks_video_src_reset (self);
+}
+
+static GstStateChangeReturn
+gst_ks_video_src_change_state (GstElement * element, GstStateChange transition)
+{
+ GstKsVideoSrc *self = GST_KS_VIDEO_SRC (element);
+ GstKsVideoSrcPrivate *priv = GST_KS_VIDEO_SRC_GET_PRIVATE (self);
+ GstStateChangeReturn ret;
+
+ switch (transition) {
+ case GST_STATE_CHANGE_NULL_TO_READY:
+ if (!gst_ks_video_src_open_device (self))
+ goto open_failed;
+ break;
+ }
+
+ ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
+
+ switch (transition) {
+ case GST_STATE_CHANGE_READY_TO_NULL:
+ gst_ks_video_src_close_device (self);
+ break;
+ }
+
+ return ret;
+
+ /* ERRORS */
+open_failed:
+ {
+ return GST_STATE_CHANGE_FAILURE;
+ }
+}
+
+static gboolean
+gst_ks_video_src_set_clock (GstElement * element, GstClock * clock)
+{
+ GstKsVideoSrc *self = GST_KS_VIDEO_SRC (element);
+ GstKsVideoSrcPrivate *priv = GST_KS_VIDEO_SRC_GET_PRIVATE (self);
+
+ GST_OBJECT_LOCK (element);
+ if (priv->ksclock != NULL)
+ gst_ks_clock_provide_master_clock (priv->ksclock, clock);
+ GST_OBJECT_UNLOCK (element);
+
+ return TRUE;
+}
+
+static GstCaps *
+gst_ks_video_src_get_caps (GstBaseSrc * basesrc)
+{
+ GstKsVideoSrc *self = GST_KS_VIDEO_SRC (basesrc);
+ GstKsVideoSrcPrivate *priv = GST_KS_VIDEO_SRC_GET_PRIVATE (self);
+
+ if (priv->device != NULL)
+ return gst_ks_video_device_get_available_caps (priv->device);
+ else
+ return NULL; /* BaseSrc will return template caps */
+}
+
+static gboolean
+gst_ks_video_src_set_caps (GstBaseSrc * basesrc, GstCaps * caps)
+{
+ GstKsVideoSrc *self = GST_KS_VIDEO_SRC (basesrc);
+ GstKsVideoSrcPrivate *priv = GST_KS_VIDEO_SRC_GET_PRIVATE (self);
+
+ if (priv->device == NULL)
+ return FALSE;
+
+ if (!gst_ks_video_device_set_caps (priv->device, caps))
+ return FALSE;
+
+ if (!gst_ks_video_device_set_state (priv->device, KSSTATE_PAUSE))
+ return FALSE;
+
+ return TRUE;
+}
+
+static void
+gst_ks_video_src_fixate (GstBaseSrc * basesrc, GstCaps * caps)
+{
+ GstStructure *structure = gst_caps_get_structure (caps, 0);
+
+ gst_structure_fixate_field_nearest_int (structure, "width", G_MAXINT);
+ gst_structure_fixate_field_nearest_int (structure, "height", G_MAXINT);
+ gst_structure_fixate_field_nearest_fraction (structure, "framerate",
+ G_MAXINT, 1);
+}
+
+static gboolean
+gst_ks_video_src_query (GstBaseSrc * basesrc, GstQuery * query)
+{
+ GstKsVideoSrc *self = GST_KS_VIDEO_SRC (basesrc);
+ GstKsVideoSrcPrivate *priv = GST_KS_VIDEO_SRC_GET_PRIVATE (self);
+ gboolean result = FALSE;
+
+ switch (GST_QUERY_TYPE (query)) {
+ case GST_QUERY_LATENCY:{
+ GstClockTime min_latency, max_latency;
+
+ if (priv->device == NULL)
+ goto beach;
+
+ result = gst_ks_video_device_get_latency (priv->device, &min_latency,
+ &max_latency);
+ if (!result)
+ goto beach;
+
+ GST_DEBUG_OBJECT (self, "reporting latency of min %" GST_TIME_FORMAT
+ " max %" GST_TIME_FORMAT,
+ GST_TIME_ARGS (min_latency), GST_TIME_ARGS (max_latency));
+
+ gst_query_set_latency (query, TRUE, min_latency, max_latency);
+ break;
+ }
+ default:
+ result = GST_BASE_SRC_CLASS (parent_class)->query (basesrc, query);
+ break;
+ }
+
+beach:
+ return result;
+}
+
+static gboolean
+gst_ks_video_src_unlock (GstBaseSrc * basesrc)
+{
+ GstKsVideoSrc *self = GST_KS_VIDEO_SRC (basesrc);
+ GstKsVideoSrcPrivate *priv = GST_KS_VIDEO_SRC_GET_PRIVATE (self);
+
+ GST_DEBUG_OBJECT (self, "%s", G_STRFUNC);
+
+ gst_ks_video_device_cancel (priv->device);
+ return TRUE;
+}
+
+static gboolean
+gst_ks_video_src_unlock_stop (GstBaseSrc * basesrc)
+{
+ GstKsVideoSrc *self = GST_KS_VIDEO_SRC (basesrc);
+ GstKsVideoSrcPrivate *priv = GST_KS_VIDEO_SRC_GET_PRIVATE (self);
+
+ GST_DEBUG_OBJECT (self, "%s", G_STRFUNC);
+
+ gst_ks_video_device_cancel_stop (priv->device);
+ return TRUE;
+}
+
+static gboolean
+gst_ks_video_src_timestamp_buffer (GstKsVideoSrc * self, GstBuffer * buf,
+ GstClockTime presentation_time)
+{
+ GstKsVideoSrcPrivate *priv = GST_KS_VIDEO_SRC_GET_PRIVATE (self);
+ GstClockTime duration;
+ GstClock *clock;
+ GstClockTime timestamp;
+
+ duration = gst_ks_video_device_get_duration (priv->device);
+
+ GST_OBJECT_LOCK (self);
+ clock = GST_ELEMENT_CLOCK (self);
+ if (clock != NULL) {
+ gst_object_ref (clock);
+ timestamp = GST_ELEMENT (self)->base_time;
+
+ if (GST_CLOCK_TIME_IS_VALID (presentation_time)) {
+ if (presentation_time > GST_ELEMENT (self)->base_time)
+ presentation_time -= GST_ELEMENT (self)->base_time;
+ else
+ presentation_time = 0;
+ }
+ } else {
+ timestamp = GST_CLOCK_TIME_NONE;
+ }
+ GST_OBJECT_UNLOCK (self);
+
+ if (clock != NULL) {
+
+ /* The time according to the current clock */
+ timestamp = gst_clock_get_time (clock) - timestamp;
+ if (timestamp > duration)
+ timestamp -= duration;
+ else
+ timestamp = 0;
+
+ if (GST_CLOCK_TIME_IS_VALID (presentation_time)) {
+ GstClockTimeDiff diff = GST_CLOCK_DIFF (timestamp, presentation_time);
+ GST_DEBUG_OBJECT (self, "Diff between our and the driver's timestamp: %"
+ G_GINT64_FORMAT, diff);
+ }
+
+ gst_object_unref (clock);
+ clock = NULL;
+
+ /* Unless it's the first frame, align the current timestamp on a multiple
+ * of duration since the previous */
+ if (GST_CLOCK_TIME_IS_VALID (priv->prev_ts)) {
+ GstClockTime delta;
+ guint delta_remainder, delta_offset;
+
+ /* REVISIT: I've seen this happen with the GstSystemClock on Windows,
+ * scary... */
+ if (timestamp < priv->prev_ts) {
+ GST_WARNING_OBJECT (self, "clock is ticking backwards");
+ return FALSE;
+ }
+
+ /* Round to a duration boundary */
+ delta = timestamp - priv->prev_ts;
+ delta_remainder = delta % duration;
+
+ if (delta_remainder < duration / 3)
+ timestamp -= delta_remainder;
+ else
+ timestamp += duration - delta_remainder;
+
+ /* How many frames are we off then? */
+ delta = timestamp - priv->prev_ts;
+ delta_offset = delta / duration;
+
+ if (delta_offset == 1) /* perfect */
+ GST_BUFFER_FLAG_UNSET (buf, GST_BUFFER_FLAG_DISCONT);
+ else if (delta_offset > 1) {
+ guint lost = delta_offset - 1;
+#if ENABLE_CLOCK_DEBUG
+ GST_INFO_OBJECT (self, "lost %d frame%s, setting discont flag",
+ lost, (lost > 1) ? "s" : "");
+#endif
+ GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DISCONT);
+ } else if (delta_offset == 0) { /* overproduction, skip this frame */
+#if ENABLE_CLOCK_DEBUG
+ GST_INFO_OBJECT (self, "skipping frame");
+#endif
+ return FALSE;
+ }
+
+ priv->offset += delta_offset;
+ }
+
+ priv->prev_ts = timestamp;
+ }
+
+ GST_BUFFER_OFFSET (buf) = priv->offset;
+ GST_BUFFER_OFFSET_END (buf) = GST_BUFFER_OFFSET (buf) + 1;
+ GST_BUFFER_TIMESTAMP (buf) = timestamp;
+ GST_BUFFER_DURATION (buf) = duration;
+
+ return TRUE;
+}
+
+static void
+gst_ks_video_src_update_statistics (GstKsVideoSrc * self)
+{
+ GstKsVideoSrcPrivate *priv = GST_KS_VIDEO_SRC_GET_PRIVATE (self);
+ GstClock *clock;
+
+ GST_OBJECT_LOCK (self);
+ clock = GST_ELEMENT_CLOCK (self);
+ if (clock != NULL)
+ gst_object_ref (clock);
+ GST_OBJECT_UNLOCK (self);
+
+ if (clock != NULL) {
+ GstClockTime now = gst_clock_get_time (clock);
+ gst_object_unref (clock);
+
+ priv->count++;
+
+ if (GST_CLOCK_TIME_IS_VALID (priv->last_sampling)) {
+ if (now - priv->last_sampling >= GST_SECOND) {
+ GST_OBJECT_LOCK (self);
+ priv->fps = priv->count;
+ GST_OBJECT_UNLOCK (self);
+
+ g_object_notify (G_OBJECT (self), "fps");
+
+ priv->last_sampling = now;
+ priv->count = 0;
+ }
+ } else {
+ priv->last_sampling = now;
+ }
+ }
+}
+
+static GstFlowReturn
+gst_ks_video_src_create (GstPushSrc * pushsrc, GstBuffer ** buffer)
+{
+ GstKsVideoSrc *self = GST_KS_VIDEO_SRC (pushsrc);
+ GstKsVideoSrcPrivate *priv = GST_KS_VIDEO_SRC_GET_PRIVATE (self);
+ guint buf_size;
+ GstCaps *caps;
+ GstBuffer *buf = NULL;
+ GstFlowReturn result;
+ GstClockTime presentation_time;
+ gulong error_code;
+ gchar *error_str;
+
+ g_assert (priv->device != NULL);
+
+ if (!gst_ks_video_device_has_caps (priv->device))
+ goto error_no_caps;
+
+ buf_size = gst_ks_video_device_get_frame_size (priv->device);
+ g_assert (buf_size);
+
+ caps = gst_pad_get_negotiated_caps (GST_BASE_SRC_PAD (self));
+ if (caps == NULL)
+ goto error_no_caps;
+ result = gst_pad_alloc_buffer (GST_BASE_SRC_PAD (self), priv->offset,
+ buf_size, caps, &buf);
+ gst_caps_unref (caps);
+ if (G_UNLIKELY (result != GST_FLOW_OK))
+ goto error_alloc_buffer;
+
+ do {
+ gulong bytes_read;
+
+ result = gst_ks_video_device_read_frame (priv->device,
+ GST_BUFFER_DATA (buf), buf_size, &bytes_read, &presentation_time,
+ &error_code, &error_str);
+ if (G_UNLIKELY (result != GST_FLOW_OK))
+ goto error_read_frame;
+
+ GST_BUFFER_SIZE (buf) = bytes_read;
+ }
+ while (!gst_ks_video_src_timestamp_buffer (self, buf, presentation_time));
+
+ if (G_UNLIKELY (priv->do_stats))
+ gst_ks_video_src_update_statistics (self);
+
+ gst_ks_video_device_postprocess_frame (priv->device,
+ GST_BUFFER_DATA (buf), GST_BUFFER_SIZE (buf));
+
+ *buffer = buf;
+ return GST_FLOW_OK;
+
+ /* ERRORS */
+error_no_caps:
+ {
+ GST_ELEMENT_ERROR (self, CORE, NEGOTIATION,
+ ("not negotiated"), ("maybe setcaps failed?"));
+
+ return GST_FLOW_ERROR;
+ }
+error_alloc_buffer:
+ {
+ GST_ELEMENT_ERROR (self, CORE, PAD, ("alloc_buffer failed"), (NULL));
+
+ return result;
+ }
+error_read_frame:
+ {
+ if (result != GST_FLOW_WRONG_STATE && result != GST_FLOW_UNEXPECTED) {
+ GST_ELEMENT_ERROR (self, RESOURCE, READ,
+ ("read failed: %s [0x%08x]", error_str, error_code),
+ ("gst_ks_video_device_read_frame failed"));
+ }
+
+ g_free (error_str);
+ gst_buffer_unref (buf);
+
+ return result;
+ }
+}
+
+static gboolean
+plugin_init (GstPlugin * plugin)
+{
+ return gst_element_register (plugin, "ksvideosrc",
+ GST_RANK_NONE, GST_TYPE_KS_VIDEO_SRC);
+}
+
+GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
+ GST_VERSION_MINOR,
+ "winks",
+ "Windows kernel streaming plugin",
+ plugin_init, VERSION, "LGPL", "GStreamer", "http://gstreamer.net/")
diff --git a/sys/winks/gstksvideosrc.h b/sys/winks/gstksvideosrc.h
new file mode 100644
index 00000000..a236c3ac
--- /dev/null
+++ b/sys/winks/gstksvideosrc.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2008 Ole André Vadla Ravnås <ole.andre.ravnas@tandberg.com>
+ *
+ * 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.
+ */
+
+#ifndef __GST_KS_VIDEO_SRC_H__
+#define __GST_KS_VIDEO_SRC_H__
+
+#include <gst/base/gstpushsrc.h>
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_KS_VIDEO_SRC \
+ (gst_ks_video_src_get_type ())
+#define GST_KS_VIDEO_SRC(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_KS_VIDEO_SRC, GstKsVideoSrc))
+#define GST_KS_VIDEO_SRC_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_KS_VIDEO_SRC, GstKsVideoSrcClass))
+#define GST_IS_KS_VIDEO_SRC(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_KS_VIDEO_SRC))
+#define GST_IS_KS_VIDEO_SRC_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_KS_VIDEO_SRC))
+
+typedef struct _GstKsVideoSrc GstKsVideoSrc;
+typedef struct _GstKsVideoSrcClass GstKsVideoSrcClass;
+
+struct _GstKsVideoSrc
+{
+ GstPushSrc push_src;
+};
+
+struct _GstKsVideoSrcClass
+{
+ GstPushSrcClass parent_class;
+};
+
+GType gst_ks_video_src_get_type (void);
+
+G_END_DECLS
+
+#endif /* __GST_KS_VIDEO_SRC_H__ */
diff --git a/sys/winks/kshelpers.c b/sys/winks/kshelpers.c
new file mode 100644
index 00000000..d1aa274e
--- /dev/null
+++ b/sys/winks/kshelpers.c
@@ -0,0 +1,471 @@
+/*
+ * Copyright (C) 2008 Ole André Vadla Ravnås <ole.andre.ravnas@tandberg.com>
+ *
+ * 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.
+ */
+
+#include "kshelpers.h"
+
+#include <ksmedia.h>
+#include <setupapi.h>
+
+GST_DEBUG_CATEGORY_EXTERN (gst_ks_debug);
+#define GST_CAT_DEFAULT gst_ks_debug
+
+gboolean
+ks_is_valid_handle (HANDLE h)
+{
+ return (h != INVALID_HANDLE_VALUE && h != NULL);
+}
+
+GList *
+ks_enumerate_devices (const GUID * category)
+{
+ GList *result = NULL;
+ HDEVINFO devinfo;
+ gint i;
+
+ devinfo = SetupDiGetClassDevsW (category, NULL, NULL,
+ DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
+ if (!ks_is_valid_handle (devinfo))
+ return NULL; /* no devices */
+
+ for (i = 0;; i++) {
+ BOOL success;
+ SP_DEVICE_INTERFACE_DATA if_data = { 0, };
+ SP_DEVICE_INTERFACE_DETAIL_DATA_W *if_detail_data;
+ DWORD if_detail_data_size;
+ SP_DEVINFO_DATA devinfo_data = { 0, };
+ DWORD req_size;
+
+ if_data.cbSize = sizeof (SP_DEVICE_INTERFACE_DATA);
+
+ success = SetupDiEnumDeviceInterfaces (devinfo, NULL, category, i,
+ &if_data);
+ if (!success) /* all devices enumerated? */
+ break;
+
+ if_detail_data_size = (MAX_PATH - 1) * sizeof (gunichar2);
+ if_detail_data = g_malloc0 (if_detail_data_size);
+ if_detail_data->cbSize = sizeof (SP_DEVICE_INTERFACE_DETAIL_DATA_W);
+
+ devinfo_data.cbSize = sizeof (SP_DEVINFO_DATA);
+
+ success = SetupDiGetDeviceInterfaceDetailW (devinfo, &if_data,
+ if_detail_data, if_detail_data_size, &req_size, &devinfo_data);
+ if (success) {
+ KsDeviceEntry *entry;
+ WCHAR buf[512];
+
+ entry = g_new0 (KsDeviceEntry, 1);
+ entry->index = i;
+ entry->path =
+ g_utf16_to_utf8 (if_detail_data->DevicePath, -1, NULL, NULL, NULL);
+
+ if (SetupDiGetDeviceRegistryPropertyW (devinfo, &devinfo_data,
+ SPDRP_FRIENDLYNAME, NULL, (BYTE *) buf, sizeof (buf), NULL)) {
+ entry->name = g_utf16_to_utf8 (buf, -1, NULL, NULL, NULL);
+ }
+
+ if (entry->name == NULL) {
+ if (SetupDiGetDeviceRegistryPropertyW (devinfo, &devinfo_data,
+ SPDRP_DEVICEDESC, NULL, (BYTE *) buf, sizeof (buf), NULL)) {
+ entry->name = g_utf16_to_utf8 (buf, -1, NULL, NULL, NULL);
+ }
+ }
+
+ if (entry->name != NULL)
+ result = g_list_prepend (result, entry);
+ else
+ ks_device_entry_free (entry);
+ }
+
+ g_free (if_detail_data);
+ }
+
+ SetupDiDestroyDeviceInfoList (devinfo);
+
+ return g_list_reverse (result);
+}
+
+void
+ks_device_entry_free (KsDeviceEntry * entry)
+{
+ if (entry == NULL)
+ return;
+
+ g_free (entry->path);
+ g_free (entry->name);
+
+ g_free (entry);
+}
+
+void
+ks_device_list_free (GList * devices)
+{
+ GList *cur;
+
+ for (cur = devices; cur != NULL; cur = cur->next)
+ ks_device_entry_free (cur->data);
+
+ g_list_free (devices);
+}
+
+static gboolean
+ks_sync_device_io_control (HANDLE device, gulong io_control_code,
+ gpointer in_buffer, gulong in_buffer_size, gpointer out_buffer,
+ gulong out_buffer_size, gulong * bytes_returned)
+{
+ OVERLAPPED overlapped = { 0, };
+ BOOL success;
+
+ overlapped.hEvent = CreateEvent (NULL, TRUE, FALSE, NULL);
+
+ success = DeviceIoControl (device, io_control_code, in_buffer,
+ in_buffer_size, out_buffer, out_buffer_size, bytes_returned, &overlapped);
+ if (!success && GetLastError () == ERROR_IO_PENDING)
+ success = GetOverlappedResult (device, &overlapped, bytes_returned, TRUE);
+
+ CloseHandle (overlapped.hEvent);
+
+ return success ? TRUE : FALSE;
+}
+
+gboolean
+ks_filter_get_pin_property (HANDLE filter_handle, gulong pin_id,
+ GUID prop_set, gulong prop_id, gpointer value, gulong value_size)
+{
+ KSP_PIN prop = { 0, };
+ DWORD bytes_returned = 0;
+
+ prop.PinId = pin_id;
+ prop.Property.Set = prop_set;
+ prop.Property.Id = prop_id;
+ prop.Property.Flags = KSPROPERTY_TYPE_GET;
+
+ return ks_sync_device_io_control (filter_handle, IOCTL_KS_PROPERTY, &prop,
+ sizeof (prop), value, value_size, &bytes_returned);
+}
+
+gboolean
+ks_filter_get_pin_property_multi (HANDLE filter_handle, gulong pin_id,
+ GUID prop_set, gulong prop_id, KSMULTIPLE_ITEM ** items)
+{
+ KSP_PIN prop = { 0, };
+ DWORD items_size = 0, bytes_written = 0;
+ gboolean ret;
+
+ *items = NULL;
+
+ prop.PinId = pin_id;
+ prop.Property.Set = prop_set;
+ prop.Property.Id = prop_id;
+ prop.Property.Flags = KSPROPERTY_TYPE_GET;
+
+ ret = ks_sync_device_io_control (filter_handle, IOCTL_KS_PROPERTY,
+ &prop.Property, sizeof (prop), NULL, 0, &items_size);
+ if (!ret) {
+ DWORD err = GetLastError ();
+ if (err != ERROR_INSUFFICIENT_BUFFER && err != ERROR_MORE_DATA)
+ goto error;
+ }
+
+ *items = g_malloc0 (items_size);
+
+ ret = ks_sync_device_io_control (filter_handle, IOCTL_KS_PROPERTY, &prop,
+ sizeof (prop), *items, items_size, &bytes_written);
+ if (!ret)
+ goto error;
+
+ return ret;
+
+error:
+ g_free (*items);
+ *items = NULL;
+
+ return FALSE;
+}
+
+gboolean
+ks_object_query_property (HANDLE handle, GUID prop_set, gulong prop_id,
+ gulong prop_flags, gpointer * value, gulong * value_size)
+{
+ KSPROPERTY prop = { 0, };
+ DWORD req_value_size = 0, bytes_written = 0;
+ gboolean ret;
+
+ *value = NULL;
+
+ prop.Set = prop_set;
+ prop.Id = prop_id;
+ prop.Flags = prop_flags;
+
+ if (value_size == NULL || *value_size == 0) {
+ ret = ks_sync_device_io_control (handle, IOCTL_KS_PROPERTY,
+ &prop, sizeof (prop), NULL, 0, &req_value_size);
+ if (!ret) {
+ DWORD err = GetLastError ();
+ if (err != ERROR_INSUFFICIENT_BUFFER && err != ERROR_MORE_DATA)
+ goto error;
+ }
+ } else {
+ req_value_size = *value_size;
+ }
+
+ *value = g_malloc0 (req_value_size);
+
+ ret = ks_sync_device_io_control (handle, IOCTL_KS_PROPERTY, &prop,
+ sizeof (prop), *value, req_value_size, &bytes_written);
+ if (!ret)
+ goto error;
+
+ if (value_size != NULL)
+ *value_size = bytes_written;
+
+ return ret;
+
+error:
+ g_free (*value);
+ *value = NULL;
+
+ if (value_size != NULL)
+ *value_size = 0;
+
+ return FALSE;
+}
+
+gboolean
+ks_object_get_property (HANDLE handle, GUID prop_set, gulong prop_id,
+ gpointer * value, gulong * value_size)
+{
+ return ks_object_query_property (handle, prop_set, prop_id,
+ KSPROPERTY_TYPE_GET, value, value_size);
+}
+
+gboolean
+ks_object_set_property (HANDLE handle, GUID prop_set, gulong prop_id,
+ gpointer value, gulong value_size)
+{
+ KSPROPERTY prop = { 0, };
+ DWORD bytes_returned;
+
+ prop.Set = prop_set;
+ prop.Id = prop_id;
+ prop.Flags = KSPROPERTY_TYPE_SET;
+
+ return ks_sync_device_io_control (handle, IOCTL_KS_PROPERTY, &prop,
+ sizeof (prop), value, value_size, &bytes_returned);
+}
+
+gboolean
+ks_object_get_supported_property_sets (HANDLE handle, GUID ** propsets,
+ gulong * len)
+{
+ gulong size = 0;
+
+ *propsets = NULL;
+ *len = 0;
+
+ if (ks_object_query_property (handle, GUID_NULL, 0,
+ KSPROPERTY_TYPE_SETSUPPORT, propsets, &size)) {
+ if (size % sizeof (GUID) == 0) {
+ *len = size / sizeof (GUID);
+ return TRUE;
+ }
+ }
+
+ g_free (*propsets);
+ *propsets = NULL;
+ *len = 0;
+ return FALSE;
+}
+
+gboolean
+ks_object_set_connection_state (HANDLE handle, KSSTATE state)
+{
+ return ks_object_set_property (handle, KSPROPSETID_Connection,
+ KSPROPERTY_CONNECTION_STATE, &state, sizeof (state));
+}
+
+const gchar *
+ks_state_to_string (KSSTATE state)
+{
+ switch (state) {
+ case KSSTATE_STOP:
+ return "KSSTATE_STOP";
+ case KSSTATE_ACQUIRE:
+ return "KSSTATE_ACQUIRE";
+ case KSSTATE_PAUSE:
+ return "KSSTATE_PAUSE";
+ case KSSTATE_RUN:
+ return "KSSTATE_RUN";
+ default:
+ g_assert_not_reached ();
+ }
+
+ return "UNKNOWN";
+}
+
+#define CHECK_OPTIONS_FLAG(flag) \
+ if (flags & KSSTREAM_HEADER_OPTIONSF_##flag)\
+ {\
+ if (str->len > 0)\
+ g_string_append (str, " | ");\
+ g_string_append (str, G_STRINGIFY (flag));\
+ flags &= ~KSSTREAM_HEADER_OPTIONSF_##flag;\
+ }
+
+gchar *
+ks_options_flags_to_string (gulong flags)
+{
+ gchar *ret;
+ GString *str;
+
+ str = g_string_sized_new (128);
+
+ CHECK_OPTIONS_FLAG (DATADISCONTINUITY);
+ CHECK_OPTIONS_FLAG (DURATIONVALID);
+ CHECK_OPTIONS_FLAG (ENDOFSTREAM);
+ CHECK_OPTIONS_FLAG (FLUSHONPAUSE);
+ CHECK_OPTIONS_FLAG (LOOPEDDATA);
+ CHECK_OPTIONS_FLAG (PREROLL);
+ CHECK_OPTIONS_FLAG (SPLICEPOINT);
+ CHECK_OPTIONS_FLAG (TIMEDISCONTINUITY);
+ CHECK_OPTIONS_FLAG (TIMEVALID);
+ CHECK_OPTIONS_FLAG (TYPECHANGED);
+ CHECK_OPTIONS_FLAG (VRAM_DATA_TRANSFER);
+ CHECK_OPTIONS_FLAG (BUFFEREDTRANSFER);
+
+ if (flags != 0)
+ g_string_append_printf (str, " | 0x%08x", flags);
+
+ ret = str->str;
+ g_string_free (str, FALSE);
+
+ return ret;
+}
+
+typedef struct
+{
+ const GUID guid;
+ const gchar *name;
+} KsPropertySetMapping;
+
+#ifndef STATIC_KSPROPSETID_GM
+#define STATIC_KSPROPSETID_GM \
+ 0xAF627536, 0xE719, 0x11D2, 0x8A, 0x1D, 0x00, 0x60, 0x97, 0xD2, 0xDF, 0x5D
+#endif
+#ifndef STATIC_KSPROPSETID_Jack
+#define STATIC_KSPROPSETID_Jack \
+ 0x4509F757, 0x2D46, 0x4637, 0x8E, 0x62, 0xCE, 0x7D, 0xB9, 0x44, 0xF5, 0x7B
+#endif
+
+#ifndef STATIC_PROPSETID_VIDCAP_SELECTOR
+#define STATIC_PROPSETID_VIDCAP_SELECTOR \
+ 0x1ABDAECA, 0x68B6, 0x4F83, 0x93, 0x71, 0xB4, 0x13, 0x90, 0x7C, 0x7B, 0x9F
+#endif
+#ifndef STATIC_PROPSETID_EXT_DEVICE
+#define STATIC_PROPSETID_EXT_DEVICE \
+ 0xB5730A90, 0x1A2C, 0x11cf, 0x8c, 0x23, 0x00, 0xAA, 0x00, 0x6B, 0x68, 0x14
+#endif
+#ifndef STATIC_PROPSETID_EXT_TRANSPORT
+#define STATIC_PROPSETID_EXT_TRANSPORT \
+ 0xA03CD5F0, 0x3045, 0x11cf, 0x8c, 0x44, 0x00, 0xAA, 0x00, 0x6B, 0x68, 0x14
+#endif
+#ifndef STATIC_PROPSETID_TIMECODE_READER
+#define STATIC_PROPSETID_TIMECODE_READER \
+ 0x9B496CE1, 0x811B, 0x11cf, 0x8C, 0x77, 0x00, 0xAA, 0x00, 0x6B, 0x68, 0x14
+#endif
+
+static const KsPropertySetMapping known_property_sets[] = {
+ {{STATIC_KSPROPSETID_General}, "General"},
+ {{STATIC_KSPROPSETID_MediaSeeking}, "MediaSeeking"},
+ {{STATIC_KSPROPSETID_Topology}, "Topology"},
+ {{STATIC_KSPROPSETID_GM}, "GM"},
+ {{STATIC_KSPROPSETID_Pin}, "Pin"},
+ {{STATIC_KSPROPSETID_Quality}, "Quality"},
+ {{STATIC_KSPROPSETID_Connection}, "Connection"},
+ {{STATIC_KSPROPSETID_MemoryTransport}, "MemoryTransport"},
+ {{STATIC_KSPROPSETID_StreamAllocator}, "StreamAllocator"},
+ {{STATIC_KSPROPSETID_StreamInterface}, "StreamInterface"},
+ {{STATIC_KSPROPSETID_Stream}, "Stream"},
+ {{STATIC_KSPROPSETID_Clock}, "Clock"},
+
+ {{STATIC_KSPROPSETID_DirectSound3DListener}, "DirectSound3DListener"},
+ {{STATIC_KSPROPSETID_DirectSound3DBuffer}, "DirectSound3DBuffer"},
+ {{STATIC_KSPROPSETID_Hrtf3d}, "Hrtf3d"},
+ {{STATIC_KSPROPSETID_Itd3d}, "Itd3d"},
+ {{STATIC_KSPROPSETID_Bibliographic}, "Bibliographic"},
+ {{STATIC_KSPROPSETID_TopologyNode}, "TopologyNode"},
+ {{STATIC_KSPROPSETID_RtAudio}, "RtAudio"},
+ {{STATIC_KSPROPSETID_DrmAudioStream}, "DrmAudioStream"},
+ {{STATIC_KSPROPSETID_Audio}, "Audio"},
+ {{STATIC_KSPROPSETID_Acoustic_Echo_Cancel}, "Acoustic_Echo_Cancel"},
+ {{STATIC_KSPROPSETID_Wave_Queued}, "Wave_Queued"},
+ {{STATIC_KSPROPSETID_Wave}, "Wave"},
+ {{STATIC_KSPROPSETID_WaveTable}, "WaveTable"},
+ {{STATIC_KSPROPSETID_Cyclic}, "Cyclic"},
+ {{STATIC_KSPROPSETID_Sysaudio}, "Sysaudio"},
+ {{STATIC_KSPROPSETID_Sysaudio_Pin}, "Sysaudio_Pin"},
+ {{STATIC_KSPROPSETID_AudioGfx}, "AudioGfx"},
+ {{STATIC_KSPROPSETID_Linear}, "Linear"},
+ {{STATIC_KSPROPSETID_Mpeg2Vid}, "Mpeg2Vid"},
+ {{STATIC_KSPROPSETID_AC3}, "AC3"},
+ {{STATIC_KSPROPSETID_AudioDecoderOut}, "AudioDecoderOut"},
+ {{STATIC_KSPROPSETID_DvdSubPic}, "DvdSubPic"},
+ {{STATIC_KSPROPSETID_CopyProt}, "CopyProt"},
+ {{STATIC_KSPROPSETID_VBICAP_PROPERTIES}, "VBICAP_PROPERTIES"},
+ {{STATIC_KSPROPSETID_VBICodecFiltering}, "VBICodecFiltering"},
+ {{STATIC_KSPROPSETID_VramCapture}, "VramCapture"},
+ {{STATIC_KSPROPSETID_OverlayUpdate}, "OverlayUpdate"},
+ {{STATIC_KSPROPSETID_VPConfig}, "VPConfig"},
+ {{STATIC_KSPROPSETID_VPVBIConfig}, "VPVBIConfig"},
+ {{STATIC_KSPROPSETID_TSRateChange}, "TSRateChange"},
+ {{STATIC_KSPROPSETID_Jack}, "Jack"},
+
+ {{STATIC_PROPSETID_ALLOCATOR_CONTROL}, "ALLOCATOR_CONTROL"},
+ {{STATIC_PROPSETID_VIDCAP_VIDEOPROCAMP}, "VIDCAP_VIDEOPROCAMP"},
+ {{STATIC_PROPSETID_VIDCAP_SELECTOR}, "VIDCAP_SELECTOR"},
+ {{STATIC_PROPSETID_TUNER}, "TUNER"},
+ {{STATIC_PROPSETID_VIDCAP_VIDEOENCODER}, "VIDCAP_VIDEOENCODER"},
+ {{STATIC_PROPSETID_VIDCAP_VIDEODECODER}, "VIDCAP_VIDEODECODER"},
+ {{STATIC_PROPSETID_VIDCAP_CAMERACONTROL}, "VIDCAP_CAMERACONTROL"},
+ {{STATIC_PROPSETID_EXT_DEVICE}, "EXT_DEVICE"},
+ {{STATIC_PROPSETID_EXT_TRANSPORT}, "EXT_TRANSPORT"},
+ {{STATIC_PROPSETID_TIMECODE_READER}, "TIMECODE_READER"},
+ {{STATIC_PROPSETID_VIDCAP_CROSSBAR}, "VIDCAP_CROSSBAR"},
+ {{STATIC_PROPSETID_VIDCAP_TVAUDIO}, "VIDCAP_TVAUDIO"},
+ {{STATIC_PROPSETID_VIDCAP_VIDEOCOMPRESSION}, "VIDCAP_VIDEOCOMPRESSION"},
+ {{STATIC_PROPSETID_VIDCAP_VIDEOCONTROL}, "VIDCAP_VIDEOCONTROL"},
+ {{STATIC_PROPSETID_VIDCAP_DROPPEDFRAMES}, "VIDCAP_DROPPEDFRAMES"},
+};
+
+gchar *
+ks_property_set_to_string (const GUID * guid)
+{
+ guint i;
+
+ for (i = 0;
+ i < sizeof (known_property_sets) / sizeof (known_property_sets[0]); i++) {
+ if (IsEqualGUID (guid, &known_property_sets[i].guid))
+ return g_strdup_printf ("KSPROPSETID_%s", known_property_sets[i].name);
+ }
+
+ return g_strdup_printf ("{%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}",
+ guid->Data1, guid->Data2, guid->Data3, guid->Data4[0], guid->Data4[1],
+ guid->Data4[2], guid->Data4[3], guid->Data4[4], guid->Data4[5],
+ guid->Data4[6], guid->Data4[7]);
+}
diff --git a/sys/winks/kshelpers.h b/sys/winks/kshelpers.h
new file mode 100644
index 00000000..360fe40e
--- /dev/null
+++ b/sys/winks/kshelpers.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2008 Ole André Vadla Ravnås <ole.andre.ravnas@tandberg.com>
+ *
+ * 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.
+ */
+
+#ifndef __KSHELPERS_H__
+#define __KSHELPERS_H__
+
+#include <glib.h>
+#include <windows.h>
+#include <ks.h>
+
+G_BEGIN_DECLS
+
+typedef struct _KsDeviceEntry KsDeviceEntry;
+
+struct _KsDeviceEntry
+{
+ guint index;
+ gchar * name;
+ gchar * path;
+};
+
+gboolean ks_is_valid_handle (HANDLE h);
+
+GList * ks_enumerate_devices (const GUID * category);
+void ks_device_entry_free (KsDeviceEntry * entry);
+void ks_device_list_free (GList * devices);
+
+gboolean ks_filter_get_pin_property (HANDLE filter_handle, gulong pin_id, GUID prop_set, gulong prop_id, gpointer value, gulong value_size);
+gboolean ks_filter_get_pin_property_multi (HANDLE filter_handle, gulong pin_id, GUID prop_set, gulong prop_id, KSMULTIPLE_ITEM ** items);
+
+gboolean ks_object_query_property (HANDLE handle, GUID prop_set, gulong prop_id, gulong prop_flags, gpointer * value, gulong * value_size);
+gboolean ks_object_get_property (HANDLE handle, GUID prop_set, gulong prop_id, gpointer * value, gulong * value_size);
+gboolean ks_object_set_property (HANDLE handle, GUID prop_set, gulong prop_id, gpointer value, gulong value_size);
+
+gboolean ks_object_get_supported_property_sets (HANDLE handle, GUID ** propsets, gulong * len);
+
+gboolean ks_object_set_connection_state (HANDLE handle, KSSTATE state);
+
+const gchar * ks_state_to_string (KSSTATE state);
+gchar * ks_options_flags_to_string (gulong flags);
+gchar * ks_property_set_to_string (const GUID * guid);
+
+G_END_DECLS
+
+#endif /* __KSHELPERS_H__ */
diff --git a/sys/winks/ksvideohelpers.c b/sys/winks/ksvideohelpers.c
new file mode 100644
index 00000000..261f4182
--- /dev/null
+++ b/sys/winks/ksvideohelpers.c
@@ -0,0 +1,585 @@
+/*
+ * Copyright (C) 2007 Haakon Sporsheim <hakon.sporsheim@tandberg.com>
+ * 2008 Ole André Vadla Ravnås <ole.andre.ravnas@tandberg.com>
+ *
+ * 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.
+ */
+
+#include "ksvideohelpers.h"
+
+#include <uuids.h>
+#include "kshelpers.h"
+
+GST_DEBUG_CATEGORY_EXTERN (gst_ks_debug);
+#define GST_CAT_DEFAULT gst_ks_debug
+
+static const GUID MEDIASUBTYPE_FOURCC =
+ { 0x0 /* FourCC here */ , 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xAA, 0x00,
+ 0x38, 0x9B, 0x71} };
+
+extern const GUID MEDIASUBTYPE_I420 =
+ { 0x30323449, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B,
+ 0x71} };
+
+static GstStructure *
+ks_video_format_to_structure (GUID subtype_guid, GUID format_guid)
+{
+ GstStructure *structure = NULL;
+
+ if (IsEqualGUID (&subtype_guid, &MEDIASUBTYPE_MJPG) || IsEqualGUID (&subtype_guid, &MEDIASUBTYPE_TVMJ) || /* FIXME: NOT tested */
+ IsEqualGUID (&subtype_guid, &MEDIASUBTYPE_WAKE) || /* FIXME: NOT tested */
+ IsEqualGUID (&subtype_guid, &MEDIASUBTYPE_CFCC) || /* FIXME: NOT tested */
+ IsEqualGUID (&subtype_guid, &MEDIASUBTYPE_IJPG)) { /* FIXME: NOT tested */
+ structure = gst_structure_new ("image/jpeg", NULL);
+ } else if (IsEqualGUID (&subtype_guid, &MEDIASUBTYPE_RGB555) || /* FIXME: NOT tested */
+ IsEqualGUID (&subtype_guid, &MEDIASUBTYPE_RGB565) || /* FIXME: NOT tested */
+ IsEqualGUID (&subtype_guid, &MEDIASUBTYPE_RGB24) || IsEqualGUID (&subtype_guid, &MEDIASUBTYPE_RGB32) || /* FIXME: NOT tested */
+ IsEqualGUID (&subtype_guid, &MEDIASUBTYPE_ARGB1555) || /* FIXME: NOT tested */
+ IsEqualGUID (&subtype_guid, &MEDIASUBTYPE_ARGB32) || /* FIXME: NOT tested */
+ IsEqualGUID (&subtype_guid, &MEDIASUBTYPE_ARGB4444)) { /* FIXME: NOT tested */
+ guint depth = 0, bpp = 0;
+ gint endianness = 0;
+ guint32 r_mask = 0, b_mask = 0, g_mask = 0;
+
+ if (IsEqualGUID (&subtype_guid, &MEDIASUBTYPE_RGB555)) {
+ bpp = 16;
+ depth = 15;
+ endianness = G_BIG_ENDIAN;
+ r_mask = 0x7c00;
+ g_mask = 0x03e0;
+ b_mask = 0x001f;
+ } else if (IsEqualGUID (&subtype_guid, &MEDIASUBTYPE_RGB565)) {
+ bpp = depth = 16;
+ endianness = G_BIG_ENDIAN;
+ r_mask = 0xf800;
+ g_mask = 0x07e0;
+ b_mask = 0x001f;
+ } else if (IsEqualGUID (&subtype_guid, &MEDIASUBTYPE_RGB24)) {
+ bpp = depth = 24;
+ endianness = G_BIG_ENDIAN;
+ r_mask = 0x0000ff;
+ g_mask = 0x00ff00;
+ b_mask = 0xff0000;
+ } else if (IsEqualGUID (&subtype_guid, &MEDIASUBTYPE_RGB32)) {
+ bpp = 32;
+ depth = 24;
+ endianness = G_BIG_ENDIAN;
+ r_mask = 0x000000ff;
+ g_mask = 0x0000ff00;
+ b_mask = 0x00ff0000;
+ /* FIXME: check
+ *r_mask = 0xff000000;
+ *g_mask = 0x00ff0000;
+ *b_mask = 0x0000ff00;
+ */
+ } else if (IsEqualGUID (&subtype_guid, &MEDIASUBTYPE_ARGB1555)) {
+ bpp = 16;
+ depth = 15;
+ endianness = G_BIG_ENDIAN;
+ r_mask = 0x7c00;
+ g_mask = 0x03e0;
+ b_mask = 0x001f;
+ } else if (IsEqualGUID (&subtype_guid, &MEDIASUBTYPE_ARGB32)) {
+ bpp = depth = 32;
+ endianness = G_BIG_ENDIAN;
+ r_mask = 0x000000ff;
+ g_mask = 0x0000ff00;
+ b_mask = 0x00ff0000;
+ /* FIXME: check
+ *r_mask = 0xff000000;
+ *g_mask = 0x00ff0000;
+ *b_mask = 0x0000ff00;
+ */
+ } else if (IsEqualGUID (&subtype_guid, &MEDIASUBTYPE_ARGB4444)) {
+ bpp = 16;
+ depth = 12;
+ endianness = G_BIG_ENDIAN;
+ r_mask = 0x0f00;
+ g_mask = 0x00f0;
+ b_mask = 0x000f;
+ //r_mask = 0x000f;
+ //g_mask = 0x00f0;
+ //b_mask = 0x0f00;
+ } else {
+ g_assert_not_reached ();
+ }
+
+ structure = gst_structure_new ("video/x-raw-rgb",
+ "bpp", G_TYPE_INT, bpp,
+ "depth", G_TYPE_INT, depth,
+ "red_mask", G_TYPE_INT, r_mask,
+ "green_mask", G_TYPE_INT, g_mask,
+ "blue_mask", G_TYPE_INT, b_mask,
+ "endianness", G_TYPE_INT, endianness, NULL);
+ } else if (IsEqualGUID (&subtype_guid, &MEDIASUBTYPE_dvsd)) {
+ if (IsEqualGUID (&format_guid, &FORMAT_DvInfo)) {
+ structure = gst_structure_new ("video/x-dv",
+ "systemstream", G_TYPE_BOOLEAN, TRUE, NULL);
+ } else if (IsEqualGUID (&format_guid, &FORMAT_VideoInfo)) {
+ structure = gst_structure_new ("video/x-dv",
+ "systemstream", G_TYPE_BOOLEAN, FALSE,
+ "format", GST_TYPE_FOURCC, GST_MAKE_FOURCC ('d', 'v', 's', 'd'),
+ NULL);
+ }
+ } else if (memcmp (&subtype_guid.Data2, &MEDIASUBTYPE_FOURCC.Data2,
+ sizeof (subtype_guid) - sizeof (subtype_guid.Data1)) == 0) {
+ guint8 *p = (guint8 *) & subtype_guid.Data1;
+
+ structure = gst_structure_new ("video/x-raw-yuv",
+ "format", GST_TYPE_FOURCC, GST_MAKE_FOURCC (p[0], p[1], p[2], p[3]),
+ NULL);
+ }
+
+ if (!structure) {
+ GST_DEBUG ("Unknown DirectShow Video GUID %08x-%04x-%04x-%04x-%08x%04x",
+ subtype_guid.Data1, subtype_guid.Data2, subtype_guid.Data3,
+ *(WORD *) subtype_guid.Data4, *(DWORD *) & subtype_guid.Data4[2],
+ *(WORD *) & subtype_guid.Data4[6]);
+ }
+
+ return structure;
+}
+
+static gboolean
+ks_video_append_video_stream_cfg_fields (GstStructure * structure,
+ const KS_VIDEO_STREAM_CONFIG_CAPS * vscc)
+{
+ g_return_val_if_fail (structure, FALSE);
+ g_return_val_if_fail (vscc, FALSE);
+
+ /* width */
+ if (vscc->MinOutputSize.cx == vscc->MaxOutputSize.cx) {
+ gst_structure_set (structure,
+ "width", G_TYPE_INT, vscc->MaxOutputSize.cx, NULL);
+ } else {
+ gst_structure_set (structure,
+ "width", GST_TYPE_INT_RANGE,
+ vscc->MinOutputSize.cx, vscc->MaxOutputSize.cx, NULL);
+ }
+
+ /* height */
+ if (vscc->MinOutputSize.cy == vscc->MaxOutputSize.cy) {
+ gst_structure_set (structure,
+ "height", G_TYPE_INT, vscc->MaxOutputSize.cy, NULL);
+ } else {
+ gst_structure_set (structure,
+ "height", GST_TYPE_INT_RANGE,
+ vscc->MinOutputSize.cy, vscc->MaxOutputSize.cy, NULL);
+ }
+
+ /* framerate */
+ if (vscc->MinFrameInterval == vscc->MaxFrameInterval) {
+ gst_structure_set (structure,
+ "framerate", GST_TYPE_FRACTION,
+ (gint) (10000000 / vscc->MaxFrameInterval), 1, NULL);
+ } else {
+ gst_structure_set (structure,
+ "framerate", GST_TYPE_FRACTION_RANGE,
+ (gint) (10000000 / vscc->MaxFrameInterval), 1,
+ (gint) (10000000 / vscc->MinFrameInterval), 1, NULL);
+ }
+
+ return TRUE;
+}
+
+KsVideoMediaType *
+ks_video_media_type_dup (KsVideoMediaType * media_type)
+{
+ KsVideoMediaType *result = g_new (KsVideoMediaType, 1);
+
+ memcpy (result, media_type, sizeof (KsVideoMediaType));
+
+ result->range = g_malloc (media_type->range->FormatSize);
+ memcpy ((gpointer) result->range, media_type->range,
+ media_type->range->FormatSize);
+
+ result->format = g_malloc (media_type->format_size);
+ memcpy (result->format, media_type->format, media_type->format_size);
+
+ result->translated_caps = gst_caps_ref (media_type->translated_caps);
+
+ return result;
+}
+
+void
+ks_video_media_type_free (KsVideoMediaType * media_type)
+{
+ if (media_type == NULL)
+ return;
+
+ g_free (media_type->format);
+
+ if (media_type->translated_caps != NULL)
+ gst_caps_unref (media_type->translated_caps);
+
+ g_free (media_type);
+}
+
+static GList *
+ks_video_media_type_list_remove_duplicates (GList * media_types)
+{
+ GList *master, *duplicates;
+
+ do {
+ GList *entry;
+
+ master = duplicates = NULL;
+
+ /* Find the first set of duplicates and their master */
+ for (entry = media_types; entry != NULL && duplicates == NULL;
+ entry = entry->next) {
+ KsVideoMediaType *mt = entry->data;
+ GList *other_entry;
+
+ for (other_entry = media_types; other_entry != NULL;
+ other_entry = other_entry->next) {
+ KsVideoMediaType *other_mt = other_entry->data;
+
+ if (other_mt == mt)
+ continue;
+
+ if (gst_caps_is_equal (mt->translated_caps, other_mt->translated_caps))
+ duplicates = g_list_prepend (duplicates, other_mt);
+ }
+
+ if (duplicates != NULL)
+ master = entry;
+ }
+
+ if (duplicates != NULL) {
+ KsVideoMediaType *selected_mt = master->data;
+
+ /*
+ * Pick a FORMAT_VideoInfo2 if present, if not we just stay with the
+ * first entry
+ */
+ for (entry = duplicates; entry != NULL; entry = entry->next) {
+ KsVideoMediaType *mt = entry->data;
+
+ if (IsEqualGUID (&mt->range->Specifier, &FORMAT_VideoInfo2)) {
+ ks_video_media_type_free (selected_mt);
+ selected_mt = mt;
+ } else {
+ ks_video_media_type_free (mt);
+ }
+
+ /* Remove the dupe from the main list */
+ media_types = g_list_remove (media_types, mt);
+ }
+
+ /* Update master node with the selected MediaType */
+ master->data = selected_mt;
+
+ g_list_free (duplicates);
+ }
+ }
+ while (master != NULL);
+
+ return media_types;
+}
+
+GList *
+ks_video_probe_filter_for_caps (HANDLE filter_handle)
+{
+ GList *ret = NULL;
+ gulong pin_count;
+ guint pin_id;
+
+ if (!ks_filter_get_pin_property (filter_handle, 0, KSPROPSETID_Pin,
+ KSPROPERTY_PIN_CTYPES, &pin_count, sizeof (pin_count)))
+ goto beach;
+
+ GST_DEBUG ("pin_count = %d", pin_count);
+
+ for (pin_id = 0; pin_id < pin_count; pin_id++) {
+ KSPIN_COMMUNICATION pin_comm;
+ KSPIN_DATAFLOW pin_flow;
+ GUID pin_cat;
+
+ if (!ks_filter_get_pin_property (filter_handle, pin_id, KSPROPSETID_Pin,
+ KSPROPERTY_PIN_COMMUNICATION, &pin_comm, sizeof (pin_comm)))
+ continue;
+
+ if (!ks_filter_get_pin_property (filter_handle, pin_id, KSPROPSETID_Pin,
+ KSPROPERTY_PIN_DATAFLOW, &pin_flow, sizeof (pin_flow)))
+ continue;
+
+ if (!ks_filter_get_pin_property (filter_handle, pin_id, KSPROPSETID_Pin,
+ KSPROPERTY_PIN_CATEGORY, &pin_cat, sizeof (pin_cat)))
+ continue;
+
+ GST_DEBUG ("pin[%d]: pin_comm=%d, pin_flow=%d", pin_id, pin_comm, pin_flow);
+
+ if (pin_flow == KSPIN_DATAFLOW_OUT &&
+ memcmp (&pin_cat, &PINNAME_CAPTURE, sizeof (GUID)) == 0) {
+ KSMULTIPLE_ITEM *items;
+
+ if (ks_filter_get_pin_property_multi (filter_handle, pin_id,
+ KSPROPSETID_Pin, KSPROPERTY_PIN_DATARANGES, &items)) {
+ KSDATARANGE *range = (KSDATARANGE *) (items + 1);
+ guint i;
+
+ for (i = 0; i < items->Count; i++) {
+ if (IsEqualGUID (&range->MajorFormat, &KSDATAFORMAT_TYPE_VIDEO)) {
+ KsVideoMediaType *entry;
+ gpointer src_vscc, src_format;
+ GstStructure *media_structure;
+
+ entry = g_new0 (KsVideoMediaType, 1);
+ entry->pin_id = pin_id;
+
+ entry->range = g_malloc (range->FormatSize);
+ memcpy ((gpointer) entry->range, range, range->FormatSize);
+
+ if (IsEqualGUID (&range->Specifier, &FORMAT_VideoInfo)) {
+ KS_DATARANGE_VIDEO *vr = (KS_DATARANGE_VIDEO *) entry->range;
+
+ src_vscc = &vr->ConfigCaps;
+ src_format = &vr->VideoInfoHeader;
+
+ entry->format_size = sizeof (vr->VideoInfoHeader);
+ entry->sample_size = vr->VideoInfoHeader.bmiHeader.biSizeImage;
+ } else if (IsEqualGUID (&range->Specifier, &FORMAT_VideoInfo2)) {
+ KS_DATARANGE_VIDEO2 *vr = (KS_DATARANGE_VIDEO2 *) entry->range;
+
+ src_vscc = &vr->ConfigCaps;
+ src_format = &vr->VideoInfoHeader;
+
+ entry->format_size = sizeof (vr->VideoInfoHeader);
+ entry->sample_size = vr->VideoInfoHeader.bmiHeader.biSizeImage;
+ } else if (IsEqualGUID (&range->Specifier, &FORMAT_MPEGVideo)) {
+ /* Untested and probably wrong... */
+ KS_DATARANGE_MPEG1_VIDEO *vr =
+ (KS_DATARANGE_MPEG1_VIDEO *) entry->range;
+
+ src_vscc = &vr->ConfigCaps;
+ src_format = &vr->VideoInfoHeader;
+
+ entry->format_size = sizeof (vr->VideoInfoHeader);
+ entry->sample_size =
+ vr->VideoInfoHeader.hdr.bmiHeader.biSizeImage;
+ } else if (IsEqualGUID (&range->Specifier, &FORMAT_MPEG2Video)) {
+ /* Untested and probably wrong... */
+ KS_DATARANGE_MPEG2_VIDEO *vr =
+ (KS_DATARANGE_MPEG2_VIDEO *) entry->range;
+
+ src_vscc = &vr->ConfigCaps;
+ src_format = &vr->VideoInfoHeader;
+
+ entry->format_size = sizeof (vr->VideoInfoHeader);
+ entry->sample_size =
+ vr->VideoInfoHeader.hdr.bmiHeader.biSizeImage;
+ } else
+ g_assert_not_reached ();
+
+ g_assert (entry->sample_size != 0);
+
+ memcpy ((gpointer) & entry->vscc, src_vscc, sizeof (entry->vscc));
+
+ entry->format = g_malloc (entry->format_size);
+ memcpy (entry->format, src_format, entry->format_size);
+
+ media_structure =
+ ks_video_format_to_structure (range->SubFormat,
+ range->MajorFormat);
+
+ if (media_structure == NULL) {
+ g_warning ("ks_video_format_to_structure returned NULL");
+ ks_video_media_type_free (entry);
+ entry = NULL;
+ } else if (ks_video_append_video_stream_cfg_fields (media_structure,
+ &entry->vscc)) {
+ entry->translated_caps = gst_caps_new_empty ();
+ gst_caps_append_structure (entry->translated_caps,
+ media_structure);
+ } else {
+ gst_structure_free (media_structure);
+ ks_video_media_type_free (entry);
+ entry = NULL;
+ }
+
+ if (entry != NULL)
+ ret = g_list_prepend (ret, entry);
+ }
+
+ /* REVISIT: Each KSDATARANGE should start on a 64-bit boundary */
+ range = (KSDATARANGE *) (((guchar *) range) + range->FormatSize);
+ }
+ }
+ }
+ }
+
+ if (ret != NULL) {
+ ret = g_list_reverse (ret);
+ ret = ks_video_media_type_list_remove_duplicates (ret);
+ }
+
+beach:
+ return ret;
+}
+
+KSPIN_CONNECT *
+ks_video_create_pin_conn_from_media_type (KsVideoMediaType * media_type)
+{
+ KSPIN_CONNECT *conn = NULL;
+ KSDATAFORMAT *format = NULL;
+ guint8 *vih;
+
+ conn = g_malloc0 (sizeof (KSPIN_CONNECT) + sizeof (KSDATAFORMAT) +
+ media_type->format_size);
+
+ conn->Interface.Set = KSINTERFACESETID_Standard;
+ conn->Interface.Id = KSINTERFACE_STANDARD_STREAMING;
+ conn->Interface.Flags = 0;
+
+ conn->Medium.Set = KSMEDIUMSETID_Standard;
+ conn->Medium.Id = KSMEDIUM_TYPE_ANYINSTANCE;
+ conn->Medium.Flags = 0;
+
+ conn->PinId = media_type->pin_id;
+ conn->PinToHandle = NULL;
+ conn->Priority.PriorityClass = KSPRIORITY_NORMAL;
+ conn->Priority.PrioritySubClass = 1;
+
+ format = (KSDATAFORMAT *) (conn + 1);
+ memcpy (format, media_type->range, sizeof (KSDATAFORMAT));
+ format->FormatSize = sizeof (KSDATAFORMAT) + media_type->format_size;
+
+ vih = (guint8 *) (format + 1);
+ memcpy (vih, media_type->format, media_type->format_size);
+
+ return conn;
+}
+
+gboolean
+ks_video_fixate_media_type (const KSDATARANGE * range,
+ guint8 * format, gint width, gint height, gint fps_n, gint fps_d)
+{
+ DWORD dwRate = (width * height * fps_n) / fps_d;
+
+ g_return_val_if_fail (format != NULL, FALSE);
+
+ if (IsEqualGUID (&range->Specifier, &FORMAT_VideoInfo)) {
+ KS_VIDEOINFOHEADER *vih = (KS_VIDEOINFOHEADER *) format;
+
+ vih->AvgTimePerFrame = gst_util_uint64_scale_int (10000000, fps_d, fps_n);
+ vih->dwBitRate = dwRate * vih->bmiHeader.biBitCount;
+
+ g_assert (vih->bmiHeader.biWidth == width);
+ g_assert (vih->bmiHeader.biHeight == height);
+ } else if (IsEqualGUID (&range->Specifier, &FORMAT_VideoInfo2)) {
+ KS_VIDEOINFOHEADER2 *vih = (KS_VIDEOINFOHEADER2 *) format;
+
+ vih->AvgTimePerFrame = gst_util_uint64_scale_int (10000000, fps_d, fps_n);
+ vih->dwBitRate = dwRate * vih->bmiHeader.biBitCount;
+
+ g_assert (vih->bmiHeader.biWidth == width);
+ g_assert (vih->bmiHeader.biHeight == height);
+ } else if (IsEqualGUID (&range->Specifier, &FORMAT_MPEGVideo)) {
+ KS_MPEG1VIDEOINFO *vih = (KS_MPEG1VIDEOINFO *) format;
+
+ vih->hdr.AvgTimePerFrame =
+ gst_util_uint64_scale_int (10000000, fps_d, fps_n);
+ vih->hdr.dwBitRate = dwRate * vih->hdr.bmiHeader.biBitCount;
+
+ /* FIXME: set height and width? */
+ g_assert (vih->hdr.bmiHeader.biWidth == width);
+ g_assert (vih->hdr.bmiHeader.biHeight == height);
+ } else if (IsEqualGUID (&range->Specifier, &FORMAT_MPEG2Video)) {
+ KS_MPEGVIDEOINFO2 *vih = (KS_MPEGVIDEOINFO2 *) format;
+
+ vih->hdr.AvgTimePerFrame =
+ gst_util_uint64_scale_int (10000000, fps_d, fps_n);
+ vih->hdr.dwBitRate = dwRate * vih->hdr.bmiHeader.biBitCount;
+
+ /* FIXME: set height and width? */
+ g_assert (vih->hdr.bmiHeader.biWidth == width);
+ g_assert (vih->hdr.bmiHeader.biHeight == height);
+ } else {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static GstStructure *
+ks_video_append_var_video_fields (GstStructure * structure)
+{
+ if (structure) {
+ gst_structure_set (structure,
+ "width", GST_TYPE_INT_RANGE, 1, G_MAXINT,
+ "height", GST_TYPE_INT_RANGE, 1, G_MAXINT,
+ "framerate", GST_TYPE_FRACTION_RANGE, 0, 1, G_MAXINT, 1, NULL);
+ }
+
+ return structure;
+}
+
+GstCaps *
+ks_video_get_all_caps (void)
+{
+ static GstCaps *caps = NULL;
+
+ if (caps == NULL) {
+ GstStructure *structure;
+ caps = gst_caps_new_empty ();
+
+ /* from Windows SDK 6.0 uuids.h */
+ /* RGB formats */
+ structure =
+ ks_video_append_var_video_fields (ks_video_format_to_structure
+ (MEDIASUBTYPE_RGB555, FORMAT_VideoInfo));
+ gst_caps_append_structure (caps, structure);
+
+ structure =
+ ks_video_append_var_video_fields (ks_video_format_to_structure
+ (MEDIASUBTYPE_RGB565, FORMAT_VideoInfo));
+ gst_caps_append_structure (caps, structure);
+
+ structure =
+ ks_video_append_var_video_fields (ks_video_format_to_structure
+ (MEDIASUBTYPE_RGB24, FORMAT_VideoInfo));
+ gst_caps_append_structure (caps, structure);
+
+ structure =
+ ks_video_append_var_video_fields (ks_video_format_to_structure
+ (MEDIASUBTYPE_RGB32, FORMAT_VideoInfo));
+ gst_caps_append_structure (caps, structure);
+
+ /* YUV formats */
+ structure =
+ ks_video_append_var_video_fields (gst_structure_new ("video/x-raw-yuv",
+ NULL));
+ gst_caps_append_structure (caps, structure);
+
+ /* Other formats */
+ structure =
+ ks_video_append_var_video_fields (ks_video_format_to_structure
+ (MEDIASUBTYPE_MJPG, FORMAT_VideoInfo));
+ gst_caps_append_structure (caps, structure);
+
+ structure =
+ ks_video_append_var_video_fields (ks_video_format_to_structure
+ (MEDIASUBTYPE_dvsd, FORMAT_VideoInfo));
+ gst_caps_append_structure (caps, structure);
+
+ structure = /* no variable video fields (width, height, framerate) */
+ ks_video_format_to_structure (MEDIASUBTYPE_dvsd, FORMAT_DvInfo);
+ gst_caps_append_structure (caps, structure);
+ }
+
+ return caps;
+}
diff --git a/sys/winks/ksvideohelpers.h b/sys/winks/ksvideohelpers.h
new file mode 100644
index 00000000..7be539ae
--- /dev/null
+++ b/sys/winks/ksvideohelpers.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2007 Haakon Sporsheim <hakon.sporsheim@tandberg.com>
+ * 2008 Ole André Vadla Ravnås <ole.andre.ravnas@tandberg.com>
+ *
+ * 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.
+ */
+
+#ifndef __KSVIDEOHELPERS_H__
+#define __KSVIDEOHELPERS_H__
+
+#include <gst/gst.h>
+#include <windows.h>
+#include <ks.h>
+#include <ksmedia.h>
+
+G_BEGIN_DECLS
+
+DEFINE_GUID(MEDIASUBTYPE_I420, 0x30323449, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71);
+
+typedef struct _KsVideoMediaType KsVideoMediaType;
+
+/**
+ * A structure that contain metadata about capabilities
+ * for both KS and GStreamer for video only.
+ */
+struct _KsVideoMediaType
+{
+ guint pin_id;
+
+ const KSDATARANGE * range;
+ const KS_VIDEO_STREAM_CONFIG_CAPS vscc;
+
+ guint8 * format;
+ guint format_size;
+
+ guint sample_size;
+
+ GstCaps * translated_caps;
+};
+
+KsVideoMediaType * ks_video_media_type_dup (KsVideoMediaType * media_type);
+void ks_video_media_type_free (KsVideoMediaType * media_type);
+GList * ks_video_probe_filter_for_caps (HANDLE filter_handle);
+KSPIN_CONNECT * ks_video_create_pin_conn_from_media_type (KsVideoMediaType * media_type);
+gboolean ks_video_fixate_media_type (const KSDATARANGE * range, guint8 * format, gint width, gint height, gint fps_n, gint fps_d);
+
+GstCaps * ks_video_get_all_caps (void);
+
+G_END_DECLS
+
+#endif /* __KSVIDEOHELPERS_H__ */