From c980279fa4ab6bdf782cb3c7c6832caea71c3ee6 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Ole=20Andr=C3=A9=20Vadla=20Ravn=C3=A5s?= Date: Sun, 24 Aug 2008 22:05:48 +0000 Subject: [PATCH] New plugin for low-latency video capture on Windows (#519935). Original commit message from CVS: * configure.ac: * sys/Makefile.am: * sys/winks/Makefile.am: * sys/winks/gstksclock.c: * sys/winks/gstksclock.h: * sys/winks/gstksvideodevice.c: * sys/winks/gstksvideodevice.h: * sys/winks/gstksvideosrc.c: * sys/winks/gstksvideosrc.h: * sys/winks/kshelpers.c: * sys/winks/kshelpers.h: * sys/winks/ksvideohelpers.c: * sys/winks/ksvideohelpers.h: New plugin for low-latency video capture on Windows (#519935). Uses Kernel Streaming, the lowest level API for doing video capture on Windows (more or less just raw ioctls). --- ChangeLog | 20 + configure.ac | 1 + sys/Makefile.am | 2 +- sys/winks/Makefile.am | 9 + sys/winks/gstksclock.c | 356 ++++++++++++++ sys/winks/gstksclock.h | 69 +++ sys/winks/gstksvideodevice.c | 1092 ++++++++++++++++++++++++++++++++++++++++++ sys/winks/gstksvideodevice.h | 76 +++ sys/winks/gstksvideosrc.c | 816 +++++++++++++++++++++++++++++++ sys/winks/gstksvideosrc.h | 55 +++ sys/winks/kshelpers.c | 471 ++++++++++++++++++ sys/winks/kshelpers.h | 61 +++ sys/winks/ksvideohelpers.c | 585 ++++++++++++++++++++++ sys/winks/ksvideohelpers.h | 64 +++ 14 files changed, 3676 insertions(+), 1 deletion(-) create mode 100644 sys/winks/Makefile.am create mode 100644 sys/winks/gstksclock.c create mode 100644 sys/winks/gstksclock.h create mode 100644 sys/winks/gstksvideodevice.c create mode 100644 sys/winks/gstksvideodevice.h create mode 100644 sys/winks/gstksvideosrc.c create mode 100644 sys/winks/gstksvideosrc.h create mode 100644 sys/winks/kshelpers.c create mode 100644 sys/winks/kshelpers.h create mode 100644 sys/winks/ksvideohelpers.c create mode 100644 sys/winks/ksvideohelpers.h diff --git a/ChangeLog b/ChangeLog index 7bd16ae..6cd32dc 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,25 @@ 2008-08-24 Ole André Vadla RavnÃ¥s + * configure.ac: + * sys/Makefile.am: + * sys/winks/Makefile.am: + * sys/winks/gstksclock.c: + * sys/winks/gstksclock.h: + * sys/winks/gstksvideodevice.c: + * sys/winks/gstksvideodevice.h: + * sys/winks/gstksvideosrc.c: + * sys/winks/gstksvideosrc.h: + * sys/winks/kshelpers.c: + * sys/winks/kshelpers.h: + * sys/winks/ksvideohelpers.c: + * sys/winks/ksvideohelpers.h: + New plugin for low-latency video capture on Windows (#519935). + + Uses Kernel Streaming, the lowest level API for doing video capture + on Windows (more or less just raw ioctls). + +2008-08-24 Ole André Vadla RavnÃ¥s + * gst/pcapparse/gstpcapparse.c: * sys/winscreencap/gstdx9screencapsrc.c: * sys/winscreencap/gstgdiscreencapsrc.c: diff --git a/configure.ac b/configure.ac index 6402672..aa4b39b 100644 --- a/configure.ac +++ b/configure.ac @@ -1298,6 +1298,7 @@ sys/oss4/Makefile sys/qtwrapper/Makefile sys/vcd/Makefile sys/wininet/Makefile +sys/winks/Makefile sys/winscreencap/Makefile examples/Makefile examples/app/Makefile diff --git a/sys/Makefile.am b/sys/Makefile.am index c03750a..2a8f04d 100644 --- a/sys/Makefile.am +++ b/sys/Makefile.am @@ -61,5 +61,5 @@ endif SUBDIRS = $(ACM_DIR) $(DVB_DIR) $(FBDEV_DIR) $(OSS4_DIR) $(QT_DIR) $(VCD_DIR) $(WININET_DIR) DIST_SUBDIRS = acmenc dvb fbdev dshowdecwrapper dshowsrcwrapper dshowvideosink \ - oss4 qtwrapper vcd wininet winscreencap + oss4 qtwrapper vcd wininet winks winscreencap diff --git a/sys/winks/Makefile.am b/sys/winks/Makefile.am new file mode 100644 index 0000000..a89eef3 --- /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 0000000..ad3b3ec --- /dev/null +++ b/sys/winks/gstksclock.c @@ -0,0 +1,356 @@ +/* + * Copyright (C) 2008 Ole André Vadla RavnÃ¥s + * + * 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 0000000..42a043f --- /dev/null +++ b/sys/winks/gstksclock.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2008 Ole André Vadla RavnÃ¥s + * + * 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 +#include + +#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 0000000..8b385e7 --- /dev/null +++ b/sys/winks/gstksvideodevice.c @@ -0,0 +1,1092 @@ +/* + * Copyright (C) 2008 Ole André Vadla RavnÃ¥s + * + * 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 0000000..99024e1 --- /dev/null +++ b/sys/winks/gstksvideodevice.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2008 Ole André Vadla RavnÃ¥s + * + * 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 + +#include +#include + +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 0000000..9fd1388 --- /dev/null +++ b/sys/winks/gstksvideosrc.c @@ -0,0 +1,816 @@ +/* + * Copyright (C) 2008 Ole André Vadla RavnÃ¥s + * + * 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. + * + * + * Example pipelines + * |[ + * 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. + * + */ + +#include "gstksvideosrc.h" + +#ifdef HAVE_CONFIG_H +# include +#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 \n" + "Haakon Sporsheim " + }; + + 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 0000000..a236c3a --- /dev/null +++ b/sys/winks/gstksvideosrc.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2008 Ole André Vadla RavnÃ¥s + * + * 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 + +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 0000000..d1aa274 --- /dev/null +++ b/sys/winks/kshelpers.c @@ -0,0 +1,471 @@ +/* + * Copyright (C) 2008 Ole André Vadla Ravnås + * + * 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 +#include + +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 0000000..360fe40 --- /dev/null +++ b/sys/winks/kshelpers.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2008 Ole André Vadla Ravnås + * + * 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 +#include +#include + +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 0000000..261f418 --- /dev/null +++ b/sys/winks/ksvideohelpers.c @@ -0,0 +1,585 @@ +/* + * Copyright (C) 2007 Haakon Sporsheim + * 2008 Ole André Vadla Ravnås + * + * 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 +#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 0000000..7be539a --- /dev/null +++ b/sys/winks/ksvideohelpers.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2007 Haakon Sporsheim + * 2008 Ole André Vadla Ravnås + * + * 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 +#include +#include +#include + +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__ */ -- 2.7.4