gstreamer/sys/winks/gstksvideosrc.c
Руслан Ижбулатов a31855d618 GstDeviceProvider implementation for WIN Kernel Streaming plugin
gst_ks_device_provider_probe() is a no-braier, just runs ks_enumerate_devices()
and reports the results.

Monitoring is a bit more tricky. We have to create a dummy message-processing
window and register device change notifications for it.

As kernel streaming can (and should) be used for audio capture and audio
playback, this change also has certain placeholders for such.

https://bugzilla.gnome.org/show_bug.cgi?id=747757
2015-04-28 20:24:26 -04:00

1092 lines
32 KiB
C

/*
* Copyright (C) 2008 Ole André Vadla Ravnås <ole.andre.ravnas@tandberg.com>
* 2009 Andres Colubri <andres.colubri@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
/**
* SECTION:element-ksvideosrc
*
* Provides low-latency video capture from WDM cameras on Windows.
*
* <refsect2>
* <title>Example pipelines</title>
* |[
* gst-launch -v ksvideosrc do-stats=TRUE ! ffmpegcolorspace ! dshowvideosink
* ]| Capture from a camera and render using dshowvideosink.
* |[
* gst-launch -v ksvideosrc do-stats=TRUE ! image/jpeg, width=640, height=480
* ! jpegdec ! ffmpegcolorspace ! dshowvideosink
* ]| Capture from an MJPEG camera and render using dshowvideosink.
* </refsect2>
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include "gstksvideosrc.h"
#include "gstksclock.h"
#include "gstksvideodevice.h"
#include "kshelpers.h"
#include "ksvideohelpers.h"
#include "ksdeviceprovider.h"
#define DEFAULT_DEVICE_PATH NULL
#define DEFAULT_DEVICE_NAME NULL
#define DEFAULT_DEVICE_INDEX -1
#define DEFAULT_DO_STATS FALSE
#define DEFAULT_ENABLE_QUIRKS TRUE
enum
{
PROP_0,
PROP_DEVICE_PATH,
PROP_DEVICE_NAME,
PROP_DEVICE_INDEX,
PROP_DO_STATS,
PROP_FPS,
PROP_ENABLE_QUIRKS,
};
GST_DEBUG_CATEGORY (gst_ks_debug);
#define GST_CAT_DEFAULT gst_ks_debug
#define KS_WORKER_LOCK(priv) g_mutex_lock (&priv->worker_lock)
#define KS_WORKER_UNLOCK(priv) g_mutex_unlock (&priv->worker_lock)
#define KS_WORKER_WAIT(priv) \
g_cond_wait (&priv->worker_notify_cond, &priv->worker_lock)
#define KS_WORKER_NOTIFY(priv) g_cond_signal (&priv->worker_notify_cond)
#define KS_WORKER_WAIT_FOR_RESULT(priv) \
g_cond_wait (&priv->worker_result_cond, &priv->worker_lock)
#define KS_WORKER_NOTIFY_RESULT(priv) \
g_cond_signal (&priv->worker_result_cond)
typedef enum
{
KS_WORKER_STATE_STARTING,
KS_WORKER_STATE_READY,
KS_WORKER_STATE_STOPPING,
KS_WORKER_STATE_ERROR,
} KsWorkerState;
struct _GstKsVideoSrcPrivate
{
/* Properties */
gchar *device_path;
gchar *device_name;
gint device_index;
gboolean do_stats;
gboolean enable_quirks;
/* State */
GstKsClock *ksclock;
GstKsVideoDevice *device;
guint64 offset;
GstClockTime prev_ts;
gboolean running;
/* Worker thread */
GThread *worker_thread;
GMutex worker_lock;
GCond worker_notify_cond;
GCond worker_result_cond;
KsWorkerState worker_state;
GstCaps *worker_pending_caps;
gboolean worker_setcaps_result;
gboolean worker_pending_run;
gboolean worker_run_result;
gulong worker_error_code;
/* Statistics */
GstClockTime last_sampling;
guint count;
guint fps;
};
#define GST_KS_VIDEO_SRC_GET_PRIVATE(o) ((o)->priv)
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);
G_GNUC_UNUSED static GArray
* gst_ks_video_src_get_device_name_values (GstKsVideoSrc * self);
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,
GstCaps * filter);
static gboolean gst_ks_video_src_set_caps (GstBaseSrc * basesrc,
GstCaps * caps);
static GstCaps *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);
static GstBuffer *gst_ks_video_src_alloc_buffer (guint size, guint alignment,
gpointer user_data);
G_DEFINE_TYPE (GstKsVideoSrc, gst_ks_video_src, GST_TYPE_PUSH_SRC);
static GstKsVideoSrcClass *parent_class = NULL;
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));
parent_class = g_type_class_peek_parent (klass);
gst_element_class_set_static_metadata (gstelement_class, "KsVideoSrc",
"Source/Video",
"Stream data from a video capture device through Windows kernel streaming",
"Ole André Vadla Ravnås <ole.andre.ravnas@tandberg.com>\n"
"Haakon Sporsheim <hakon.sporsheim@tandberg.com>\n"
"Andres Colubri <andres.colubri@gmail.com>");
gst_element_class_add_pad_template (gstelement_class,
gst_pad_template_new ("src", GST_PAD_SRC, GST_PAD_ALWAYS,
ks_video_get_all_caps ()));
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_PARAM_STATIC_STRINGS));
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_PARAM_STATIC_STRINGS));
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_PARAM_STATIC_STRINGS));
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_PARAM_STATIC_STRINGS));
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 | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_ENABLE_QUIRKS,
g_param_spec_boolean ("enable-quirks", "Enable quirks",
"Enable driver-specific quirks", DEFAULT_ENABLE_QUIRKS,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
}
static void
gst_ks_video_src_init (GstKsVideoSrc * self)
{
GstBaseSrc *basesrc = GST_BASE_SRC (self);
GstKsVideoSrcPrivate *priv;
self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GST_TYPE_KS_VIDEO_SRC,
GstKsVideoSrcPrivate);
priv = GST_KS_VIDEO_SRC_GET_PRIVATE (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->do_stats = DEFAULT_DO_STATS;
priv->enable_quirks = DEFAULT_ENABLE_QUIRKS;
}
static void
gst_ks_video_src_finalize (GObject * object)
{
GstKsVideoSrc *self = GST_KS_VIDEO_SRC (object);
GstKsVideoSrcPrivate *priv = GST_KS_VIDEO_SRC_GET_PRIVATE (self);
g_free (priv->device_name);
g_free (priv->device_path);
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_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;
case PROP_ENABLE_QUIRKS:
g_value_set_boolean (value, priv->enable_quirks);
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:
{
const gchar *device_name = g_value_get_string (value);
g_free (priv->device_name);
priv->device_name = NULL;
if (device_name && strlen (device_name) != 0)
priv->device_name = g_strdup (device_name);
}
break;
case PROP_DEVICE_INDEX:
priv->device_index = g_value_get_int (value);
break;
case PROP_DO_STATS:
GST_OBJECT_LOCK (object);
priv->do_stats = g_value_get_boolean (value);
GST_OBJECT_UNLOCK (object);
break;
case PROP_ENABLE_QUIRKS:
priv->enable_quirks = g_value_get_boolean (value);
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;
priv->running = FALSE;
}
static void
gst_ks_video_src_apply_driver_quirks (GstKsVideoSrc * self)
{
HMODULE mod;
/*
* Logitech's driver software injects the following DLL into all processes
* spawned. This DLL does some nasty tricks, sitting in between the
* application and the low-level ntdll API (NtCreateFile, NtClose,
* NtDeviceIoControlFile, NtDuplicateObject, etc.), making all sorts
* of assumptions.
*
* The only regression that this quirk causes is that the video effects
* feature doesn't work.
*/
mod = GetModuleHandle ("LVPrcInj.dll");
if (mod != NULL) {
GST_DEBUG_OBJECT (self, "Logitech DLL detected, neutralizing it");
/*
* We know that no-one's actually keeping this handle around to decrement
* its reference count, so we'll take care of that job. The DLL's DllMain
* implementation takes care of rolling back changes when it gets unloaded,
* so this seems to be the cleanest and most future-proof way that we can
* get rid of it...
*/
FreeLibrary (mod);
/* Paranoia: verify that it's no longer there */
mod = GetModuleHandle ("LVPrcInj.dll");
if (mod != NULL)
GST_WARNING_OBJECT (self, "failed to neutralize Logitech DLL");
}
}
/*FIXME: when we have a devices API replacement */
G_GNUC_UNUSED static GArray *
gst_ks_video_src_get_device_name_values (GstKsVideoSrc * self)
{
GList *devices, *cur;
GArray *array = g_array_new (TRUE, TRUE, sizeof (GValue));
devices = ks_enumerate_devices (&KSCATEGORY_VIDEO, &KSCATEGORY_CAPTURE);
if (devices == NULL)
return array;
devices = ks_video_device_list_sort_cameras_first (devices);
for (cur = devices; cur != NULL; cur = cur->next) {
GValue value = { 0, };
KsDeviceEntry *entry = cur->data;
g_value_init (&value, G_TYPE_STRING);
g_value_set_string (&value, entry->name);
g_array_append_val (array, value);
g_value_unset (&value);
ks_device_entry_free (entry);
}
g_list_free (devices);
return array;
}
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, &KSCATEGORY_CAPTURE);
if (devices == NULL)
goto error_no_devices;
devices = ks_video_device_list_sort_cameras_first (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; cur = cur->next) {
KsDeviceEntry *entry = cur->data;
gboolean match;
if (device != NULL) {
ks_device_entry_free (entry);
continue;
}
if (priv->device_path != NULL) {
match = g_ascii_strcasecmp (entry->path, priv->device_path) == 0;
} else if (priv->device_name != NULL) {
match = g_ascii_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 = g_object_new (GST_TYPE_KS_CLOCK, NULL);
if (priv->ksclock != NULL && gst_ks_clock_open (priv->ksclock)) {
GstClock *clock = GST_ELEMENT_CLOCK (self);
if (clock != NULL)
gst_ks_clock_provide_master_clock (priv->ksclock, clock);
} else {
GST_WARNING_OBJECT (self, "failed to create/open KsClock");
g_object_unref (priv->ksclock);
priv->ksclock = NULL;
}
device = gst_ks_video_device_new (entry->path, priv->ksclock,
gst_ks_video_src_alloc_buffer, self);
}
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, NOT_FOUND,
("Specified video capture device with path '%s' not found",
priv->device_path), (NULL));
} else if (priv->device_name != NULL) {
GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND,
("Specified video capture device with name '%s' not found",
priv->device_name), (NULL));
} else {
GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND,
("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);
}
/*
* Worker thread that takes care of starting, configuring and stopping things.
*
* This is needed because Logitech's driver software injects a DLL that
* intercepts API functions like NtCreateFile, NtClose, NtDeviceIoControlFile
* and NtDuplicateObject so that they can provide in-place video effects to
* existing applications. Their assumption is that at least one thread tainted
* by their code stays around for the lifetime of the capture.
*/
static gpointer
gst_ks_video_src_worker_func (gpointer data)
{
GstKsVideoSrc *self = data;
GstKsVideoSrcPrivate *priv = GST_KS_VIDEO_SRC_GET_PRIVATE (self);
if (!gst_ks_video_src_open_device (self))
goto open_failed;
KS_WORKER_LOCK (priv);
priv->worker_state = KS_WORKER_STATE_READY;
KS_WORKER_NOTIFY_RESULT (priv);
while (priv->worker_state != KS_WORKER_STATE_STOPPING) {
KS_WORKER_WAIT (priv);
if (priv->worker_pending_caps != NULL) {
priv->worker_setcaps_result =
gst_ks_video_device_set_caps (priv->device,
priv->worker_pending_caps);
priv->worker_pending_caps = NULL;
KS_WORKER_NOTIFY_RESULT (priv);
} else if (priv->worker_pending_run) {
if (priv->ksclock != NULL)
gst_ks_clock_start (priv->ksclock);
priv->worker_run_result = gst_ks_video_device_set_state (priv->device,
KSSTATE_RUN, &priv->worker_error_code);
priv->worker_pending_run = FALSE;
KS_WORKER_NOTIFY_RESULT (priv);
}
}
KS_WORKER_UNLOCK (priv);
gst_ks_video_src_close_device (self);
return NULL;
/* ERRORS */
open_failed:
{
KS_WORKER_LOCK (priv);
priv->worker_state = KS_WORKER_STATE_ERROR;
KS_WORKER_NOTIFY_RESULT (priv);
KS_WORKER_UNLOCK (priv);
return NULL;
}
}
static gboolean
gst_ks_video_src_start_worker (GstKsVideoSrc * self)
{
GstKsVideoSrcPrivate *priv = GST_KS_VIDEO_SRC_GET_PRIVATE (self);
gboolean result;
g_mutex_init (&priv->worker_lock);
g_cond_init (&priv->worker_notify_cond);
g_cond_init (&priv->worker_result_cond);
priv->worker_pending_caps = NULL;
priv->worker_pending_run = FALSE;
priv->worker_state = KS_WORKER_STATE_STARTING;
priv->worker_thread =
g_thread_new ("ks-worker", gst_ks_video_src_worker_func, self);
KS_WORKER_LOCK (priv);
while (priv->worker_state < KS_WORKER_STATE_READY)
KS_WORKER_WAIT_FOR_RESULT (priv);
result = priv->worker_state == KS_WORKER_STATE_READY;
KS_WORKER_UNLOCK (priv);
return result;
}
static void
gst_ks_video_src_stop_worker (GstKsVideoSrc * self)
{
GstKsVideoSrcPrivate *priv = GST_KS_VIDEO_SRC_GET_PRIVATE (self);
KS_WORKER_LOCK (priv);
priv->worker_state = KS_WORKER_STATE_STOPPING;
KS_WORKER_NOTIFY (priv);
KS_WORKER_UNLOCK (priv);
g_thread_join (priv->worker_thread);
priv->worker_thread = NULL;
g_cond_clear (&priv->worker_result_cond);
g_cond_clear (&priv->worker_notify_cond);
g_mutex_clear (&priv->worker_lock);
}
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 (priv->enable_quirks)
gst_ks_video_src_apply_driver_quirks (self);
if (!gst_ks_video_src_start_worker (self))
goto open_failed;
break;
default:
break;
}
ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
switch (transition) {
case GST_STATE_CHANGE_READY_TO_NULL:
gst_ks_video_src_stop_worker (self);
break;
default:
break;
}
return ret;
/* ERRORS */
open_failed:
{
gst_ks_video_src_stop_worker (self);
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 (clock != NULL && priv->ksclock != NULL)
gst_ks_clock_provide_master_clock (priv->ksclock, clock);
GST_OBJECT_UNLOCK (element);
return GST_ELEMENT_CLASS (parent_class)->set_clock (element, clock);
}
static GstCaps *
gst_ks_video_src_get_caps (GstBaseSrc * basesrc, GstCaps * filter)
{
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;
KS_WORKER_LOCK (priv);
priv->worker_pending_caps = caps;
KS_WORKER_NOTIFY (priv);
while (priv->worker_pending_caps == caps)
KS_WORKER_WAIT_FOR_RESULT (priv);
KS_WORKER_UNLOCK (priv);
GST_DEBUG ("Result is %d", priv->worker_setcaps_result);
return priv->worker_setcaps_result;
}
static GstCaps *
gst_ks_video_src_fixate (GstBaseSrc * basesrc, GstCaps * caps)
{
GstStructure *structure;
GstCaps *fixated_caps;
gint i;
fixated_caps = gst_caps_make_writable (caps);
for (i = 0; i < gst_caps_get_size (fixated_caps); ++i) {
structure = gst_caps_get_structure (fixated_caps, i);
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);
}
return gst_caps_fixate (fixated_caps);
}
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)) {
/*
* We don't use this for anything yet, need to ponder how to deal
* with pins that use an internal clock and timestamp from 0.
*/
GstClockTimeDiff diff = GST_CLOCK_DIFF (presentation_time, timestamp);
GST_DEBUG_OBJECT (self, "diff between gst and driver 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_INFO_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;
GST_INFO_OBJECT (self, "lost %d frame%s, setting discont flag",
lost, (lost > 1) ? "s" : "");
GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DISCONT);
} else if (delta_offset == 0) { /* overproduction, skip this frame */
GST_INFO_OBJECT (self, "skipping frame");
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_PTS (buf) = timestamp;
GST_BUFFER_DTS (buf) = GST_CLOCK_TIME_NONE;
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 ** buf)
{
GstKsVideoSrc *self = GST_KS_VIDEO_SRC (pushsrc);
GstKsVideoSrcPrivate *priv = GST_KS_VIDEO_SRC_GET_PRIVATE (self);
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;
if (G_UNLIKELY (!priv->running)) {
KS_WORKER_LOCK (priv);
priv->worker_pending_run = TRUE;
KS_WORKER_NOTIFY (priv);
while (priv->worker_pending_run)
KS_WORKER_WAIT_FOR_RESULT (priv);
priv->running = priv->worker_run_result;
error_code = priv->worker_error_code;
KS_WORKER_UNLOCK (priv);
if (!priv->running)
goto error_start_capture;
}
do {
if (*buf != NULL) {
gst_buffer_unref (*buf);
*buf = NULL;
}
result = gst_ks_video_device_read_frame (priv->device, buf,
&presentation_time, &error_code, &error_str);
if (G_UNLIKELY (result != GST_FLOW_OK))
goto error_read_frame;
}
while (!gst_ks_video_src_timestamp_buffer (self, *buf, presentation_time));
if (G_UNLIKELY (priv->do_stats))
gst_ks_video_src_update_statistics (self);
if (!gst_ks_video_device_postprocess_frame (priv->device, *buf)) {
GST_ELEMENT_ERROR (self, RESOURCE, FAILED, ("Postprocessing failed"),
("Postprocessing failed"));
return GST_FLOW_ERROR;
}
return GST_FLOW_OK;
/* ERRORS */
error_no_caps:
{
GST_ELEMENT_ERROR (self, CORE, NEGOTIATION,
("not negotiated"), ("maybe setcaps failed?"));
return GST_FLOW_ERROR;
}
error_start_capture:
{
const gchar *debug_str = "failed to change pin state to KSSTATE_RUN";
switch (error_code) {
case ERROR_FILE_NOT_FOUND:
GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND,
("failed to start capture (device unplugged)"), ("%s", debug_str));
break;
case ERROR_NO_SYSTEM_RESOURCES:
GST_ELEMENT_ERROR (self, RESOURCE, BUSY,
("failed to start capture (device already in use)"), ("%s",
debug_str));
break;
default:
GST_ELEMENT_ERROR (self, RESOURCE, FAILED,
("failed to start capture (0x%08x)", (guint) error_code), ("%s",
debug_str));
break;
}
return GST_FLOW_ERROR;
}
error_read_frame:
{
if (result == GST_FLOW_ERROR) {
if (error_str != NULL) {
GST_ELEMENT_ERROR (self, RESOURCE, READ,
("read failed: %s [0x%08x]", error_str, (guint) error_code),
("gst_ks_video_device_read_frame failed"));
}
} else if (result == GST_FLOW_CUSTOM_ERROR) {
GST_ELEMENT_ERROR (self, RESOURCE, READ,
("read failed"), ("gst_ks_video_device_read_frame failed"));
}
g_free (error_str);
return result;
}
}
static GstBuffer *
gst_ks_video_src_alloc_buffer (guint size, guint alignment, gpointer user_data)
{
GstKsVideoSrc *self = GST_KS_VIDEO_SRC (user_data);
GstBuffer *buf;
GstAllocationParams params = { 0, alignment - 1, 0, 0, };
buf = gst_buffer_new_allocate (NULL, size, &params);
if (buf == NULL)
goto error_alloc_buffer;
return buf;
error_alloc_buffer:
{
GST_ELEMENT_ERROR (self, CORE, PAD, ("alloc_buffer failed"), (NULL));
return NULL;
}
}
static gboolean
plugin_init (GstPlugin * plugin)
{
GST_DEBUG_CATEGORY_INIT (gst_ks_debug, "ksvideosrc",
0, "Kernel streaming video source");
if (!gst_element_register (plugin, "ksvideosrc",
GST_RANK_NONE, GST_TYPE_KS_VIDEO_SRC))
return FALSE;
if (!gst_device_provider_register (plugin, "ksdeviceprovider",
GST_RANK_PRIMARY, GST_TYPE_KS_DEVICE_PROVIDER))
return FALSE;
return TRUE;
}
GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
GST_VERSION_MINOR,
winks,
"Windows kernel streaming plugin",
plugin_init, VERSION, "LGPL", "GStreamer", "http://gstreamer.net/")