1 /*
2  * Copyright (C) 2008 Ole André Vadla Ravnås <ole.andre.ravnas@tandberg.com>
3  *               2009 Andres Colubri <andres.colubri@gmail.com>
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Library General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Library General Public License for more details.
14  *
15  * You should have received a copy of the GNU Library General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18  * Boston, MA 02110-1301, USA.
19  */
20 
21 /**
22  * SECTION:element-ksvideosrc
23  * @title: ksvideosrc
24  *
25  * Provides low-latency video capture from WDM cameras on Windows.
26  *
27  * ## Example pipelines
28  * |[
29  * gst-launch-1.0 -v ksvideosrc do-stats=TRUE ! videoconvert ! dshowvideosink
30  * ]| Capture from a camera and render using dshowvideosink.
31  * |[
32  * gst-launch-1.0 -v ksvideosrc do-stats=TRUE ! image/jpeg, width=640, height=480
33  * ! jpegdec ! videoconvert ! dshowvideosink
34  * ]| Capture from an MJPEG camera and render using dshowvideosink.
35  *
36  */
37 
38 #ifdef HAVE_CONFIG_H
39 # include <config.h>
40 #endif
41 
42 #include "gstksvideosrc.h"
43 
44 #include "gstksclock.h"
45 #include "gstksvideodevice.h"
46 #include "kshelpers.h"
47 #include "ksvideohelpers.h"
48 #include "ksdeviceprovider.h"
49 
50 #define DEFAULT_DEVICE_PATH     NULL
51 #define DEFAULT_DEVICE_NAME     NULL
52 #define DEFAULT_DEVICE_INDEX    -1
53 #define DEFAULT_DO_STATS        FALSE
54 #define DEFAULT_ENABLE_QUIRKS   TRUE
55 
56 enum
57 {
58   PROP_0,
59   PROP_DEVICE_PATH,
60   PROP_DEVICE_NAME,
61   PROP_DEVICE_INDEX,
62   PROP_DO_STATS,
63   PROP_FPS,
64   PROP_ENABLE_QUIRKS,
65 };
66 
67 GST_DEBUG_CATEGORY (gst_ks_debug);
68 #define GST_CAT_DEFAULT gst_ks_debug
69 
70 #define KS_WORKER_LOCK(priv) g_mutex_lock (&priv->worker_lock)
71 #define KS_WORKER_UNLOCK(priv) g_mutex_unlock (&priv->worker_lock)
72 #define KS_WORKER_WAIT(priv) \
73     g_cond_wait (&priv->worker_notify_cond, &priv->worker_lock)
74 #define KS_WORKER_NOTIFY(priv) g_cond_signal (&priv->worker_notify_cond)
75 #define KS_WORKER_WAIT_FOR_RESULT(priv) \
76     g_cond_wait (&priv->worker_result_cond, &priv->worker_lock)
77 #define KS_WORKER_NOTIFY_RESULT(priv) \
78     g_cond_signal (&priv->worker_result_cond)
79 
80 typedef enum
81 {
82   KS_WORKER_STATE_STARTING,
83   KS_WORKER_STATE_READY,
84   KS_WORKER_STATE_STOPPING,
85   KS_WORKER_STATE_ERROR,
86 } KsWorkerState;
87 
88 struct _GstKsVideoSrcPrivate
89 {
90   /* Properties */
91   gchar *device_path;
92   gchar *device_name;
93   gint device_index;
94   gboolean do_stats;
95   gboolean enable_quirks;
96 
97   /* State */
98   GstKsClock *ksclock;
99   GstKsVideoDevice *device;
100 
101   gboolean running;
102 
103   /* Worker thread */
104   GThread *worker_thread;
105   GMutex worker_lock;
106   GCond worker_notify_cond;
107   GCond worker_result_cond;
108   KsWorkerState worker_state;
109 
110   GstCaps *worker_pending_caps;
111   gboolean worker_setcaps_result;
112 
113   gboolean worker_pending_run;
114   gboolean worker_run_result;
115 
116   gulong worker_error_code;
117 
118   /* Statistics */
119   GstClockTime last_sampling;
120   guint count;
121   guint fps;
122 };
123 
124 #define GST_KS_VIDEO_SRC_GET_PRIVATE(o) ((o)->priv)
125 
126 static void gst_ks_video_src_finalize (GObject * object);
127 static void gst_ks_video_src_get_property (GObject * object, guint prop_id,
128     GValue * value, GParamSpec * pspec);
129 static void gst_ks_video_src_set_property (GObject * object, guint prop_id,
130     const GValue * value, GParamSpec * pspec);
131 
132 G_GNUC_UNUSED static GArray
133     * gst_ks_video_src_get_device_name_values (GstKsVideoSrc * self);
134 static void gst_ks_video_src_reset (GstKsVideoSrc * self);
135 
136 static GstStateChangeReturn gst_ks_video_src_change_state (GstElement * element,
137     GstStateChange transition);
138 static gboolean gst_ks_video_src_set_clock (GstElement * element,
139     GstClock * clock);
140 
141 static GstCaps *gst_ks_video_src_get_caps (GstBaseSrc * basesrc,
142     GstCaps * filter);
143 static gboolean gst_ks_video_src_set_caps (GstBaseSrc * basesrc,
144     GstCaps * caps);
145 static GstCaps *gst_ks_video_src_fixate (GstBaseSrc * basesrc, GstCaps * caps);
146 static gboolean gst_ks_video_src_query (GstBaseSrc * basesrc, GstQuery * query);
147 static gboolean gst_ks_video_src_unlock (GstBaseSrc * basesrc);
148 static gboolean gst_ks_video_src_unlock_stop (GstBaseSrc * basesrc);
149 
150 static GstFlowReturn gst_ks_video_src_create (GstPushSrc * pushsrc,
151     GstBuffer ** buffer);
152 static GstBuffer *gst_ks_video_src_alloc_buffer (guint size, guint alignment,
153     gpointer user_data);
154 
155 G_DEFINE_TYPE_WITH_PRIVATE (GstKsVideoSrc, gst_ks_video_src, GST_TYPE_PUSH_SRC);
156 
157 static GstKsVideoSrcClass *parent_class = NULL;
158 
159 static void
gst_ks_video_src_class_init(GstKsVideoSrcClass * klass)160 gst_ks_video_src_class_init (GstKsVideoSrcClass * klass)
161 {
162   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
163   GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass);
164   GstBaseSrcClass *gstbasesrc_class = GST_BASE_SRC_CLASS (klass);
165   GstPushSrcClass *gstpushsrc_class = GST_PUSH_SRC_CLASS (klass);
166 
167   parent_class = g_type_class_peek_parent (klass);
168 
169   gst_element_class_set_static_metadata (gstelement_class, "KsVideoSrc",
170       "Source/Video/Hardware",
171       "Stream data from a video capture device through Windows kernel streaming",
172       "Ole André Vadla Ravnås <ole.andre.ravnas@tandberg.com>\n"
173       "Haakon Sporsheim <hakon.sporsheim@tandberg.com>\n"
174       "Andres Colubri <andres.colubri@gmail.com>");
175 
176   gst_element_class_add_pad_template (gstelement_class,
177       gst_pad_template_new ("src", GST_PAD_SRC, GST_PAD_ALWAYS,
178           ks_video_get_all_caps ()));
179 
180   gobject_class->finalize = gst_ks_video_src_finalize;
181   gobject_class->get_property = gst_ks_video_src_get_property;
182   gobject_class->set_property = gst_ks_video_src_set_property;
183 
184   gstelement_class->change_state =
185       GST_DEBUG_FUNCPTR (gst_ks_video_src_change_state);
186   gstelement_class->set_clock = GST_DEBUG_FUNCPTR (gst_ks_video_src_set_clock);
187 
188   gstbasesrc_class->get_caps = GST_DEBUG_FUNCPTR (gst_ks_video_src_get_caps);
189   gstbasesrc_class->set_caps = GST_DEBUG_FUNCPTR (gst_ks_video_src_set_caps);
190   gstbasesrc_class->fixate = GST_DEBUG_FUNCPTR (gst_ks_video_src_fixate);
191   gstbasesrc_class->query = GST_DEBUG_FUNCPTR (gst_ks_video_src_query);
192   gstbasesrc_class->unlock = GST_DEBUG_FUNCPTR (gst_ks_video_src_unlock);
193   gstbasesrc_class->unlock_stop =
194       GST_DEBUG_FUNCPTR (gst_ks_video_src_unlock_stop);
195 
196   gstpushsrc_class->create = GST_DEBUG_FUNCPTR (gst_ks_video_src_create);
197 
198   g_object_class_install_property (gobject_class, PROP_DEVICE_PATH,
199       g_param_spec_string ("device-path", "Device Path",
200           "The device path", DEFAULT_DEVICE_PATH,
201           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
202   g_object_class_install_property (gobject_class, PROP_DEVICE_NAME,
203       g_param_spec_string ("device-name", "Device Name",
204           "The human-readable device name", DEFAULT_DEVICE_NAME,
205           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
206   g_object_class_install_property (gobject_class, PROP_DEVICE_INDEX,
207       g_param_spec_int ("device-index", "Device Index",
208           "The zero-based device index", -1, G_MAXINT, DEFAULT_DEVICE_INDEX,
209           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
210   g_object_class_install_property (gobject_class, PROP_DO_STATS,
211       g_param_spec_boolean ("do-stats", "Enable statistics",
212           "Enable logging of statistics", DEFAULT_DO_STATS,
213           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
214   g_object_class_install_property (gobject_class, PROP_FPS,
215       g_param_spec_int ("fps", "Frames per second",
216           "Last measured framerate, if statistics are enabled",
217           -1, G_MAXINT, -1, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
218   g_object_class_install_property (gobject_class, PROP_ENABLE_QUIRKS,
219       g_param_spec_boolean ("enable-quirks", "Enable quirks",
220           "Enable driver-specific quirks", DEFAULT_ENABLE_QUIRKS,
221           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
222 }
223 
224 static void
gst_ks_video_src_init(GstKsVideoSrc * self)225 gst_ks_video_src_init (GstKsVideoSrc * self)
226 {
227   GstBaseSrc *basesrc = GST_BASE_SRC (self);
228   GstKsVideoSrcPrivate *priv;
229 
230   self->priv = gst_ks_video_src_get_instance_private (self);
231   priv = GST_KS_VIDEO_SRC_GET_PRIVATE (self);
232 
233   gst_base_src_set_live (basesrc, TRUE);
234   gst_base_src_set_format (basesrc, GST_FORMAT_TIME);
235 
236   gst_ks_video_src_reset (self);
237 
238   priv->device_path = DEFAULT_DEVICE_PATH;
239   priv->device_name = DEFAULT_DEVICE_NAME;
240   priv->device_index = DEFAULT_DEVICE_INDEX;
241   priv->do_stats = DEFAULT_DO_STATS;
242   priv->enable_quirks = DEFAULT_ENABLE_QUIRKS;
243 }
244 
245 static void
gst_ks_video_src_finalize(GObject * object)246 gst_ks_video_src_finalize (GObject * object)
247 {
248   GstKsVideoSrc *self = GST_KS_VIDEO_SRC (object);
249   GstKsVideoSrcPrivate *priv = GST_KS_VIDEO_SRC_GET_PRIVATE (self);
250 
251   g_free (priv->device_name);
252   g_free (priv->device_path);
253 
254   G_OBJECT_CLASS (parent_class)->finalize (object);
255 }
256 
257 static void
gst_ks_video_src_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)258 gst_ks_video_src_get_property (GObject * object, guint prop_id,
259     GValue * value, GParamSpec * pspec)
260 {
261   GstKsVideoSrc *self = GST_KS_VIDEO_SRC (object);
262   GstKsVideoSrcPrivate *priv = GST_KS_VIDEO_SRC_GET_PRIVATE (self);
263 
264   switch (prop_id) {
265     case PROP_DEVICE_PATH:
266       g_value_set_string (value, priv->device_path);
267       break;
268     case PROP_DEVICE_NAME:
269       g_value_set_string (value, priv->device_name);
270       break;
271     case PROP_DEVICE_INDEX:
272       g_value_set_int (value, priv->device_index);
273       break;
274     case PROP_DO_STATS:
275       GST_OBJECT_LOCK (object);
276       g_value_set_boolean (value, priv->do_stats);
277       GST_OBJECT_UNLOCK (object);
278       break;
279     case PROP_FPS:
280       GST_OBJECT_LOCK (object);
281       g_value_set_int (value, priv->fps);
282       GST_OBJECT_UNLOCK (object);
283       break;
284     case PROP_ENABLE_QUIRKS:
285       g_value_set_boolean (value, priv->enable_quirks);
286       break;
287     default:
288       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
289       break;
290   }
291 }
292 
293 static void
gst_ks_video_src_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)294 gst_ks_video_src_set_property (GObject * object, guint prop_id,
295     const GValue * value, GParamSpec * pspec)
296 {
297   GstKsVideoSrc *self = GST_KS_VIDEO_SRC (object);
298   GstKsVideoSrcPrivate *priv = GST_KS_VIDEO_SRC_GET_PRIVATE (self);
299 
300   switch (prop_id) {
301     case PROP_DEVICE_PATH:
302     {
303       const gchar *device_path = g_value_get_string (value);
304       g_free (priv->device_path);
305       priv->device_path = NULL;
306       if (device_path && strlen (device_path) != 0)
307         priv->device_path = g_value_dup_string (value);
308     }
309       break;
310     case PROP_DEVICE_NAME:
311     {
312       const gchar *device_name = g_value_get_string (value);
313       g_free (priv->device_name);
314       priv->device_name = NULL;
315       if (device_name && strlen (device_name) != 0)
316         priv->device_name = g_strdup (device_name);
317     }
318       break;
319     case PROP_DEVICE_INDEX:
320       priv->device_index = g_value_get_int (value);
321       break;
322     case PROP_DO_STATS:
323       GST_OBJECT_LOCK (object);
324       priv->do_stats = g_value_get_boolean (value);
325       GST_OBJECT_UNLOCK (object);
326       break;
327     case PROP_ENABLE_QUIRKS:
328       priv->enable_quirks = g_value_get_boolean (value);
329       break;
330     default:
331       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
332       break;
333   }
334 }
335 
336 static void
gst_ks_video_src_reset(GstKsVideoSrc * self)337 gst_ks_video_src_reset (GstKsVideoSrc * self)
338 {
339   GstKsVideoSrcPrivate *priv = GST_KS_VIDEO_SRC_GET_PRIVATE (self);
340 
341   /* Reset statistics */
342   priv->last_sampling = GST_CLOCK_TIME_NONE;
343   priv->count = 0;
344   priv->fps = -1;
345 
346   priv->running = FALSE;
347 }
348 
349 static void
gst_ks_video_src_apply_driver_quirks(GstKsVideoSrc * self)350 gst_ks_video_src_apply_driver_quirks (GstKsVideoSrc * self)
351 {
352   HMODULE mod;
353 
354   /*
355    * Logitech's driver software injects the following DLL into all processes
356    * spawned. This DLL does some nasty tricks, sitting in between the
357    * application and the low-level ntdll API (NtCreateFile, NtClose,
358    * NtDeviceIoControlFile, NtDuplicateObject, etc.), making all sorts
359    * of assumptions.
360    *
361    * The only regression that this quirk causes is that the video effects
362    * feature doesn't work.
363    */
364   mod = GetModuleHandle ("LVPrcInj.dll");
365   if (mod != NULL) {
366     GST_DEBUG_OBJECT (self, "Logitech DLL detected, neutralizing it");
367 
368     /*
369      * We know that no-one's actually keeping this handle around to decrement
370      * its reference count, so we'll take care of that job. The DLL's DllMain
371      * implementation takes care of rolling back changes when it gets unloaded,
372      * so this seems to be the cleanest and most future-proof way that we can
373      * get rid of it...
374      */
375     FreeLibrary (mod);
376 
377     /* Paranoia: verify that it's no longer there */
378     mod = GetModuleHandle ("LVPrcInj.dll");
379     if (mod != NULL)
380       GST_WARNING_OBJECT (self, "failed to neutralize Logitech DLL");
381   }
382 }
383 
384 /*FIXME: when we have a devices API replacement */
385 G_GNUC_UNUSED static GArray *
gst_ks_video_src_get_device_name_values(GstKsVideoSrc * self)386 gst_ks_video_src_get_device_name_values (GstKsVideoSrc * self)
387 {
388   GList *devices, *cur;
389   GArray *array = g_array_new (TRUE, TRUE, sizeof (GValue));
390 
391   devices = ks_enumerate_devices (&KSCATEGORY_VIDEO, &KSCATEGORY_CAPTURE);
392   if (devices == NULL)
393     return array;
394 
395   devices = ks_video_device_list_sort_cameras_first (devices);
396 
397   for (cur = devices; cur != NULL; cur = cur->next) {
398     GValue value = { 0, };
399     KsDeviceEntry *entry = cur->data;
400 
401     g_value_init (&value, G_TYPE_STRING);
402     g_value_set_string (&value, entry->name);
403     g_array_append_val (array, value);
404     g_value_unset (&value);
405 
406     ks_device_entry_free (entry);
407   }
408 
409   g_list_free (devices);
410   return array;
411 }
412 
413 static gboolean
gst_ks_video_src_open_device(GstKsVideoSrc * self)414 gst_ks_video_src_open_device (GstKsVideoSrc * self)
415 {
416   GstKsVideoSrcPrivate *priv = GST_KS_VIDEO_SRC_GET_PRIVATE (self);
417   GstKsVideoDevice *device = NULL;
418   GList *devices, *cur;
419 
420   g_assert (priv->device == NULL);
421 
422   devices = ks_enumerate_devices (&KSCATEGORY_VIDEO, &KSCATEGORY_CAPTURE);
423   if (devices == NULL)
424     goto error_no_devices;
425 
426   devices = ks_video_device_list_sort_cameras_first (devices);
427 
428   for (cur = devices; cur != NULL; cur = cur->next) {
429     KsDeviceEntry *entry = cur->data;
430 
431     GST_DEBUG_OBJECT (self, "device %d: name='%s' path='%s'",
432         entry->index, entry->name, entry->path);
433   }
434 
435   for (cur = devices; cur != NULL; cur = cur->next) {
436     KsDeviceEntry *entry = cur->data;
437     gboolean match;
438 
439     if (device != NULL) {
440       ks_device_entry_free (entry);
441       continue;
442     }
443     if (priv->device_path != NULL) {
444       match = g_ascii_strcasecmp (entry->path, priv->device_path) == 0;
445     } else if (priv->device_name != NULL) {
446       match = g_ascii_strcasecmp (entry->name, priv->device_name) == 0;
447     } else if (priv->device_index >= 0) {
448       match = entry->index == priv->device_index;
449     } else {
450       match = TRUE;             /* pick the first entry */
451     }
452 
453     if (match) {
454       priv->ksclock = g_object_new (GST_TYPE_KS_CLOCK, NULL);
455       if (priv->ksclock != NULL && gst_ks_clock_open (priv->ksclock)) {
456         GstClock *clock = GST_ELEMENT_CLOCK (self);
457         if (clock != NULL)
458           gst_ks_clock_provide_master_clock (priv->ksclock, clock);
459       } else {
460         GST_WARNING_OBJECT (self, "failed to create/open KsClock");
461         g_object_unref (priv->ksclock);
462         priv->ksclock = NULL;
463       }
464 
465       device = gst_ks_video_device_new (entry->path, priv->ksclock,
466           gst_ks_video_src_alloc_buffer, self);
467     }
468 
469     ks_device_entry_free (entry);
470   }
471 
472   g_list_free (devices);
473 
474   if (device == NULL)
475     goto error_no_match;
476 
477   if (!gst_ks_video_device_open (device))
478     goto error_open;
479 
480   priv->device = device;
481 
482   return TRUE;
483 
484   /* ERRORS */
485 error_no_devices:
486   {
487     GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND,
488         ("No video capture devices found"), (NULL));
489     return FALSE;
490   }
491 error_no_match:
492   {
493     if (priv->device_path != NULL) {
494       GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND,
495           ("Specified video capture device with path '%s' not found",
496               priv->device_path), (NULL));
497     } else if (priv->device_name != NULL) {
498       GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND,
499           ("Specified video capture device with name '%s' not found",
500               priv->device_name), (NULL));
501     } else {
502       GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND,
503           ("Specified video capture device with index %d not found",
504               priv->device_index), (NULL));
505     }
506     return FALSE;
507   }
508 error_open:
509   {
510     GST_ELEMENT_ERROR (self, RESOURCE, OPEN_READ,
511         ("Failed to open device"), (NULL));
512     g_object_unref (device);
513     return FALSE;
514   }
515 }
516 
517 static void
gst_ks_video_src_close_device(GstKsVideoSrc * self)518 gst_ks_video_src_close_device (GstKsVideoSrc * self)
519 {
520   GstKsVideoSrcPrivate *priv = GST_KS_VIDEO_SRC_GET_PRIVATE (self);
521 
522   g_assert (priv->device != NULL);
523 
524   gst_ks_video_device_close (priv->device);
525   g_object_unref (priv->device);
526   priv->device = NULL;
527 
528   if (priv->ksclock != NULL) {
529     gst_ks_clock_close (priv->ksclock);
530     g_object_unref (priv->ksclock);
531     priv->ksclock = NULL;
532   }
533 
534   gst_ks_video_src_reset (self);
535 }
536 
537 /*
538  * Worker thread that takes care of starting, configuring and stopping things.
539  *
540  * This is needed because Logitech's driver software injects a DLL that
541  * intercepts API functions like NtCreateFile, NtClose, NtDeviceIoControlFile
542  * and NtDuplicateObject so that they can provide in-place video effects to
543  * existing applications. Their assumption is that at least one thread tainted
544  * by their code stays around for the lifetime of the capture.
545  */
546 static gpointer
gst_ks_video_src_worker_func(gpointer data)547 gst_ks_video_src_worker_func (gpointer data)
548 {
549   GstKsVideoSrc *self = data;
550   GstKsVideoSrcPrivate *priv = GST_KS_VIDEO_SRC_GET_PRIVATE (self);
551 
552   if (!gst_ks_video_src_open_device (self))
553     goto open_failed;
554 
555   KS_WORKER_LOCK (priv);
556   priv->worker_state = KS_WORKER_STATE_READY;
557   KS_WORKER_NOTIFY_RESULT (priv);
558 
559   while (priv->worker_state != KS_WORKER_STATE_STOPPING) {
560     KS_WORKER_WAIT (priv);
561 
562     if (priv->worker_pending_caps != NULL) {
563       priv->worker_setcaps_result =
564           gst_ks_video_device_set_caps (priv->device,
565           priv->worker_pending_caps);
566 
567       priv->worker_pending_caps = NULL;
568       KS_WORKER_NOTIFY_RESULT (priv);
569     } else if (priv->worker_pending_run) {
570       if (priv->ksclock != NULL)
571         gst_ks_clock_start (priv->ksclock);
572       priv->worker_run_result = gst_ks_video_device_set_state (priv->device,
573           KSSTATE_RUN, &priv->worker_error_code);
574 
575       priv->worker_pending_run = FALSE;
576       KS_WORKER_NOTIFY_RESULT (priv);
577     }
578   }
579 
580   KS_WORKER_UNLOCK (priv);
581 
582   gst_ks_video_src_close_device (self);
583 
584   return NULL;
585 
586   /* ERRORS */
587 open_failed:
588   {
589     KS_WORKER_LOCK (priv);
590     priv->worker_state = KS_WORKER_STATE_ERROR;
591     KS_WORKER_NOTIFY_RESULT (priv);
592     KS_WORKER_UNLOCK (priv);
593 
594     return NULL;
595   }
596 }
597 
598 static gboolean
gst_ks_video_src_start_worker(GstKsVideoSrc * self)599 gst_ks_video_src_start_worker (GstKsVideoSrc * self)
600 {
601   GstKsVideoSrcPrivate *priv = GST_KS_VIDEO_SRC_GET_PRIVATE (self);
602   gboolean result;
603 
604   g_mutex_init (&priv->worker_lock);
605   g_cond_init (&priv->worker_notify_cond);
606   g_cond_init (&priv->worker_result_cond);
607 
608   priv->worker_pending_caps = NULL;
609   priv->worker_pending_run = FALSE;
610 
611   priv->worker_state = KS_WORKER_STATE_STARTING;
612   priv->worker_thread =
613       g_thread_new ("ks-worker", gst_ks_video_src_worker_func, self);
614 
615   KS_WORKER_LOCK (priv);
616   while (priv->worker_state < KS_WORKER_STATE_READY)
617     KS_WORKER_WAIT_FOR_RESULT (priv);
618   result = priv->worker_state == KS_WORKER_STATE_READY;
619   KS_WORKER_UNLOCK (priv);
620 
621   return result;
622 }
623 
624 static void
gst_ks_video_src_stop_worker(GstKsVideoSrc * self)625 gst_ks_video_src_stop_worker (GstKsVideoSrc * self)
626 {
627   GstKsVideoSrcPrivate *priv = GST_KS_VIDEO_SRC_GET_PRIVATE (self);
628 
629   KS_WORKER_LOCK (priv);
630   priv->worker_state = KS_WORKER_STATE_STOPPING;
631   KS_WORKER_NOTIFY (priv);
632   KS_WORKER_UNLOCK (priv);
633 
634   g_thread_join (priv->worker_thread);
635   priv->worker_thread = NULL;
636 
637   g_cond_clear (&priv->worker_result_cond);
638   g_cond_clear (&priv->worker_notify_cond);
639   g_mutex_clear (&priv->worker_lock);
640 }
641 
642 static GstStateChangeReturn
gst_ks_video_src_change_state(GstElement * element,GstStateChange transition)643 gst_ks_video_src_change_state (GstElement * element, GstStateChange transition)
644 {
645   GstKsVideoSrc *self = GST_KS_VIDEO_SRC (element);
646   GstKsVideoSrcPrivate *priv = GST_KS_VIDEO_SRC_GET_PRIVATE (self);
647   GstStateChangeReturn ret;
648 
649   switch (transition) {
650     case GST_STATE_CHANGE_NULL_TO_READY:
651       if (priv->enable_quirks)
652         gst_ks_video_src_apply_driver_quirks (self);
653       if (!gst_ks_video_src_start_worker (self))
654         goto open_failed;
655       break;
656     default:
657       break;
658   }
659 
660   ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
661 
662   switch (transition) {
663     case GST_STATE_CHANGE_READY_TO_NULL:
664       gst_ks_video_src_stop_worker (self);
665       break;
666     default:
667       break;
668   }
669 
670   return ret;
671 
672   /* ERRORS */
673 open_failed:
674   {
675     gst_ks_video_src_stop_worker (self);
676     return GST_STATE_CHANGE_FAILURE;
677   }
678 }
679 
680 static gboolean
gst_ks_video_src_set_clock(GstElement * element,GstClock * clock)681 gst_ks_video_src_set_clock (GstElement * element, GstClock * clock)
682 {
683   GstKsVideoSrc *self = GST_KS_VIDEO_SRC (element);
684   GstKsVideoSrcPrivate *priv = GST_KS_VIDEO_SRC_GET_PRIVATE (self);
685 
686   GST_OBJECT_LOCK (element);
687   if (clock != NULL && priv->ksclock != NULL)
688     gst_ks_clock_provide_master_clock (priv->ksclock, clock);
689   GST_OBJECT_UNLOCK (element);
690 
691   return GST_ELEMENT_CLASS (parent_class)->set_clock (element, clock);
692 }
693 
694 static GstCaps *
gst_ks_video_src_get_caps(GstBaseSrc * basesrc,GstCaps * filter)695 gst_ks_video_src_get_caps (GstBaseSrc * basesrc, GstCaps * filter)
696 {
697   GstKsVideoSrc *self = GST_KS_VIDEO_SRC (basesrc);
698   GstKsVideoSrcPrivate *priv = GST_KS_VIDEO_SRC_GET_PRIVATE (self);
699 
700   if (priv->device != NULL)
701     return gst_ks_video_device_get_available_caps (priv->device);
702   else
703     return NULL;                /* BaseSrc will return template caps */
704 }
705 
706 static gboolean
gst_ks_video_src_set_caps(GstBaseSrc * basesrc,GstCaps * caps)707 gst_ks_video_src_set_caps (GstBaseSrc * basesrc, GstCaps * caps)
708 {
709   GstKsVideoSrc *self = GST_KS_VIDEO_SRC (basesrc);
710   GstKsVideoSrcPrivate *priv = GST_KS_VIDEO_SRC_GET_PRIVATE (self);
711 
712   if (priv->device == NULL)
713     return FALSE;
714 
715   KS_WORKER_LOCK (priv);
716   priv->worker_pending_caps = caps;
717   KS_WORKER_NOTIFY (priv);
718   while (priv->worker_pending_caps == caps)
719     KS_WORKER_WAIT_FOR_RESULT (priv);
720   KS_WORKER_UNLOCK (priv);
721 
722   GST_DEBUG ("Result is %d", priv->worker_setcaps_result);
723   return priv->worker_setcaps_result;
724 }
725 
726 static GstCaps *
gst_ks_video_src_fixate(GstBaseSrc * basesrc,GstCaps * caps)727 gst_ks_video_src_fixate (GstBaseSrc * basesrc, GstCaps * caps)
728 {
729   GstStructure *structure;
730   GstCaps *fixated_caps;
731   gint i;
732 
733   fixated_caps = gst_caps_make_writable (caps);
734 
735   for (i = 0; i < gst_caps_get_size (fixated_caps); ++i) {
736     structure = gst_caps_get_structure (fixated_caps, i);
737     gst_structure_fixate_field_nearest_int (structure, "width", G_MAXINT);
738     gst_structure_fixate_field_nearest_int (structure, "height", G_MAXINT);
739     gst_structure_fixate_field_nearest_fraction (structure, "framerate",
740         G_MAXINT, 1);
741   }
742 
743   return gst_caps_fixate (fixated_caps);
744 }
745 
746 static gboolean
gst_ks_video_src_query(GstBaseSrc * basesrc,GstQuery * query)747 gst_ks_video_src_query (GstBaseSrc * basesrc, GstQuery * query)
748 {
749   GstKsVideoSrc *self = GST_KS_VIDEO_SRC (basesrc);
750   GstKsVideoSrcPrivate *priv = GST_KS_VIDEO_SRC_GET_PRIVATE (self);
751   gboolean result = FALSE;
752 
753   switch (GST_QUERY_TYPE (query)) {
754     case GST_QUERY_LATENCY:{
755       GstClockTime min_latency, max_latency;
756 
757       if (priv->device == NULL)
758         goto beach;
759 
760       result = gst_ks_video_device_get_latency (priv->device, &min_latency,
761           &max_latency);
762       if (!result)
763         goto beach;
764 
765       GST_DEBUG_OBJECT (self, "reporting latency of min %" GST_TIME_FORMAT
766           " max %" GST_TIME_FORMAT,
767           GST_TIME_ARGS (min_latency), GST_TIME_ARGS (max_latency));
768 
769       gst_query_set_latency (query, TRUE, min_latency, max_latency);
770       break;
771     }
772     default:
773       result = GST_BASE_SRC_CLASS (parent_class)->query (basesrc, query);
774       break;
775   }
776 
777 beach:
778   return result;
779 }
780 
781 static gboolean
gst_ks_video_src_unlock(GstBaseSrc * basesrc)782 gst_ks_video_src_unlock (GstBaseSrc * basesrc)
783 {
784   GstKsVideoSrc *self = GST_KS_VIDEO_SRC (basesrc);
785   GstKsVideoSrcPrivate *priv = GST_KS_VIDEO_SRC_GET_PRIVATE (self);
786 
787   GST_DEBUG_OBJECT (self, "%s", G_STRFUNC);
788 
789   gst_ks_video_device_cancel (priv->device);
790   return TRUE;
791 }
792 
793 static gboolean
gst_ks_video_src_unlock_stop(GstBaseSrc * basesrc)794 gst_ks_video_src_unlock_stop (GstBaseSrc * basesrc)
795 {
796   GstKsVideoSrc *self = GST_KS_VIDEO_SRC (basesrc);
797   GstKsVideoSrcPrivate *priv = GST_KS_VIDEO_SRC_GET_PRIVATE (self);
798 
799   GST_DEBUG_OBJECT (self, "%s", G_STRFUNC);
800 
801   gst_ks_video_device_cancel_stop (priv->device);
802   return TRUE;
803 }
804 
805 static gboolean
gst_ks_video_src_timestamp_buffer(GstKsVideoSrc * self,GstBuffer * buf,GstClockTime presentation_time)806 gst_ks_video_src_timestamp_buffer (GstKsVideoSrc * self, GstBuffer * buf,
807     GstClockTime presentation_time)
808 {
809   GstKsVideoSrcPrivate *priv = GST_KS_VIDEO_SRC_GET_PRIVATE (self);
810   GstClockTime duration;
811   GstClock *clock;
812   GstClockTime timestamp, base_time;
813 
814   /* Don't timestamp muxed streams */
815   if (gst_ks_video_device_stream_is_muxed (priv->device)) {
816     duration = timestamp = GST_CLOCK_TIME_NONE;
817     goto timestamp;
818   }
819 
820   duration = gst_ks_video_device_get_duration (priv->device);
821 
822   GST_OBJECT_LOCK (self);
823   clock = GST_ELEMENT_CLOCK (self);
824   if (clock != NULL) {
825     gst_object_ref (clock);
826     base_time = GST_ELEMENT (self)->base_time;
827   } else {
828     timestamp = GST_CLOCK_TIME_NONE;
829   }
830   GST_OBJECT_UNLOCK (self);
831 
832   if (clock != NULL) {
833     /* The time according to the current clock */
834     timestamp = gst_clock_get_time (clock) - base_time;
835     if (timestamp > duration)
836       timestamp -= duration;
837     else
838       timestamp = 0;
839 
840     gst_object_unref (clock);
841     clock = NULL;
842   }
843 
844 timestamp:
845   GST_BUFFER_PTS (buf) = timestamp;
846   GST_BUFFER_DTS (buf) = GST_CLOCK_TIME_NONE;
847   GST_BUFFER_DURATION (buf) = duration;
848 
849   return TRUE;
850 }
851 
852 static void
gst_ks_video_src_update_statistics(GstKsVideoSrc * self)853 gst_ks_video_src_update_statistics (GstKsVideoSrc * self)
854 {
855   GstKsVideoSrcPrivate *priv = GST_KS_VIDEO_SRC_GET_PRIVATE (self);
856   GstClock *clock;
857 
858   GST_OBJECT_LOCK (self);
859   clock = GST_ELEMENT_CLOCK (self);
860   if (clock != NULL)
861     gst_object_ref (clock);
862   GST_OBJECT_UNLOCK (self);
863 
864   if (clock != NULL) {
865     GstClockTime now = gst_clock_get_time (clock);
866     gst_object_unref (clock);
867 
868     priv->count++;
869 
870     if (GST_CLOCK_TIME_IS_VALID (priv->last_sampling)) {
871       if (now - priv->last_sampling >= GST_SECOND) {
872         GST_OBJECT_LOCK (self);
873         priv->fps = priv->count;
874         GST_OBJECT_UNLOCK (self);
875 
876         g_object_notify (G_OBJECT (self), "fps");
877 
878         priv->last_sampling = now;
879         priv->count = 0;
880       }
881     } else {
882       priv->last_sampling = now;
883     }
884   }
885 }
886 
887 static GstFlowReturn
gst_ks_video_src_create(GstPushSrc * pushsrc,GstBuffer ** buf)888 gst_ks_video_src_create (GstPushSrc * pushsrc, GstBuffer ** buf)
889 {
890   GstKsVideoSrc *self = GST_KS_VIDEO_SRC (pushsrc);
891   GstKsVideoSrcPrivate *priv = GST_KS_VIDEO_SRC_GET_PRIVATE (self);
892   GstFlowReturn result;
893   GstClockTime presentation_time;
894   gulong error_code;
895   gchar *error_str;
896 
897   g_assert (priv->device != NULL);
898 
899   if (!gst_ks_video_device_has_caps (priv->device))
900     goto error_no_caps;
901 
902   if (G_UNLIKELY (!priv->running)) {
903     KS_WORKER_LOCK (priv);
904     priv->worker_pending_run = TRUE;
905     KS_WORKER_NOTIFY (priv);
906     while (priv->worker_pending_run)
907       KS_WORKER_WAIT_FOR_RESULT (priv);
908     priv->running = priv->worker_run_result;
909     error_code = priv->worker_error_code;
910     KS_WORKER_UNLOCK (priv);
911 
912     if (!priv->running)
913       goto error_start_capture;
914   }
915 
916   do {
917     if (*buf != NULL) {
918       gst_buffer_unref (*buf);
919       *buf = NULL;
920     }
921 
922     result = gst_ks_video_device_read_frame (priv->device, buf,
923         &presentation_time, &error_code, &error_str);
924     if (G_UNLIKELY (result != GST_FLOW_OK))
925       goto error_read_frame;
926   }
927   while (!gst_ks_video_src_timestamp_buffer (self, *buf, presentation_time));
928 
929   if (G_UNLIKELY (priv->do_stats))
930     gst_ks_video_src_update_statistics (self);
931 
932   if (!gst_ks_video_device_postprocess_frame (priv->device, buf)) {
933     GST_ELEMENT_ERROR (self, RESOURCE, FAILED, ("Postprocessing failed"),
934         ("Postprocessing failed"));
935     return GST_FLOW_ERROR;
936   }
937 
938   return GST_FLOW_OK;
939 
940   /* ERRORS */
941 error_no_caps:
942   {
943     GST_ELEMENT_ERROR (self, CORE, NEGOTIATION,
944         ("not negotiated"), ("maybe setcaps failed?"));
945 
946     return GST_FLOW_ERROR;
947   }
948 error_start_capture:
949   {
950     const gchar *debug_str = "failed to change pin state to KSSTATE_RUN";
951 
952     switch (error_code) {
953       case ERROR_FILE_NOT_FOUND:
954         GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND,
955             ("failed to start capture (device unplugged)"), ("%s", debug_str));
956         break;
957       case ERROR_NO_SYSTEM_RESOURCES:
958         GST_ELEMENT_ERROR (self, RESOURCE, BUSY,
959             ("failed to start capture (device already in use)"), ("%s",
960                 debug_str));
961         break;
962       default:
963         GST_ELEMENT_ERROR (self, RESOURCE, FAILED,
964             ("failed to start capture (0x%08x)", (guint) error_code), ("%s",
965                 debug_str));
966         break;
967     }
968 
969     return GST_FLOW_ERROR;
970   }
971 error_read_frame:
972   {
973     if (result == GST_FLOW_ERROR) {
974       if (error_str != NULL) {
975         GST_ELEMENT_ERROR (self, RESOURCE, READ,
976             ("read failed: %s [0x%08x]", error_str, (guint) error_code),
977             ("gst_ks_video_device_read_frame failed"));
978       }
979     } else if (result == GST_FLOW_CUSTOM_ERROR) {
980       GST_ELEMENT_ERROR (self, RESOURCE, READ,
981           ("read failed"), ("gst_ks_video_device_read_frame failed"));
982     }
983 
984     g_free (error_str);
985 
986     return result;
987   }
988 }
989 
990 static GstBuffer *
gst_ks_video_src_alloc_buffer(guint size,guint alignment,gpointer user_data)991 gst_ks_video_src_alloc_buffer (guint size, guint alignment, gpointer user_data)
992 {
993   GstKsVideoSrc *self = GST_KS_VIDEO_SRC (user_data);
994   GstBuffer *buf;
995   GstAllocationParams params = { 0, alignment - 1, 0, 0, };
996 
997   buf = gst_buffer_new_allocate (NULL, size, &params);
998   if (buf == NULL)
999     goto error_alloc_buffer;
1000 
1001   return buf;
1002 
1003 error_alloc_buffer:
1004   {
1005     GST_ELEMENT_ERROR (self, CORE, PAD, ("alloc_buffer failed"), (NULL));
1006 
1007     return NULL;
1008   }
1009 }
1010 
1011 static gboolean
plugin_init(GstPlugin * plugin)1012 plugin_init (GstPlugin * plugin)
1013 {
1014   GST_DEBUG_CATEGORY_INIT (gst_ks_debug, "ksvideosrc",
1015       0, "Kernel streaming video source");
1016 
1017   if (!gst_element_register (plugin, "ksvideosrc",
1018           GST_RANK_PRIMARY, GST_TYPE_KS_VIDEO_SRC))
1019     return FALSE;
1020 
1021   if (!gst_device_provider_register (plugin, "ksdeviceprovider",
1022           GST_RANK_PRIMARY, GST_TYPE_KS_DEVICE_PROVIDER))
1023     return FALSE;
1024 
1025   return TRUE;
1026 }
1027 
1028 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
1029     GST_VERSION_MINOR,
1030     winks,
1031     "Windows kernel streaming plugin",
1032     plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)
1033