/* GStreamer * Copyright (C) 2015 Руслан Ижбулатов * * ksdeviceprovider.c: Kernel Streaming device probing and monitoring * * 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. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "gstksvideosrc.h" #include "ksdeviceprovider.h" #include #include /* for DBT_* consts and [_]DEV_* structs */ #include /* for GUID_DEVCLASS_WCEUSBS */ #include /* for DIGCF_ALLCLASSES */ #include #include "kshelpers.h" #include "ksvideohelpers.h" GST_DEBUG_CATEGORY_EXTERN (gst_ks_debug); #define GST_CAT_DEFAULT gst_ks_debug static GstDevice *gst_ks_device_new (guint id, const gchar * device_name, GstCaps * caps, const gchar * device_path, GstKsDeviceType type); G_DEFINE_TYPE (GstKsDeviceProvider, gst_ks_device_provider, GST_TYPE_DEVICE_PROVIDER); static GList *gst_ks_device_provider_probe (GstDeviceProvider * provider); static gboolean gst_ks_device_provider_start (GstDeviceProvider * provider); static void gst_ks_device_provider_stop (GstDeviceProvider * provider); static void gst_ks_device_provider_class_init (GstKsDeviceProviderClass * klass) { GstDeviceProviderClass *dm_class = GST_DEVICE_PROVIDER_CLASS (klass); dm_class->probe = gst_ks_device_provider_probe; dm_class->start = gst_ks_device_provider_start; dm_class->stop = gst_ks_device_provider_stop; gst_device_provider_class_set_static_metadata (dm_class, "KernelStreaming Device Provider", "Sink/Source/Audio/Video", "List and provide KernelStreaming source and sink devices", "Руслан Ижбулатов "); } static void gst_ks_device_provider_init (GstKsDeviceProvider * self) { } static GstDevice * new_video_source (const KsDeviceEntry * info) { GstCaps *caps; HANDLE filter_handle; GList *media_types; GList *cur; g_assert (info->path != NULL); caps = gst_caps_new_empty (); filter_handle = CreateFile (info->path, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL); if (!ks_is_valid_handle (filter_handle)) goto error; media_types = ks_video_probe_filter_for_caps (filter_handle); for (cur = media_types; cur != NULL; cur = cur->next) { KsVideoMediaType *media_type = cur->data; gst_caps_append (caps, gst_caps_copy (media_type->translated_caps)); ks_video_media_type_free (media_type); } CloseHandle (filter_handle); g_list_free (media_types); return gst_ks_device_new (info->index, info->name, caps, info->path, GST_KS_DEVICE_TYPE_VIDEO_SOURCE); error: gst_caps_unref (caps); return NULL; } static GList * gst_ks_device_provider_probe (GstDeviceProvider * provider) { /*GstKsDeviceProvider *self = GST_KS_DEVICE_PROVIDER (provider); */ GList *devices, *cur; GList *result; result = NULL; devices = ks_enumerate_devices (&KSCATEGORY_VIDEO, &KSCATEGORY_CAPTURE); if (devices == NULL) return result; devices = ks_video_device_list_sort_cameras_first (devices); for (cur = devices; cur != NULL; cur = cur->next) { GstDevice *source; KsDeviceEntry *entry = cur->data; source = new_video_source (entry); if (source) result = g_list_prepend (result, gst_object_ref_sink (source)); ks_device_entry_free (entry); } result = g_list_reverse (result); g_list_free (devices); return result; } static const gchar * get_dev_type (DEV_BROADCAST_HDR * dev_msg_header) { switch (dev_msg_header->dbch_devicetype) { case DBT_DEVTYP_DEVICEINTERFACE: return "Device interface class"; case DBT_DEVTYP_HANDLE: return "Filesystem handle"; case DBT_DEVTYP_OEM: return "OEM or IHV device type"; case DBT_DEVTYP_PORT: return "Port device"; case DBT_DEVTYP_VOLUME: return "Logical volume"; default: return "Unknown device type"; } } #define KS_MSG_WINDOW_CLASS "gst_winks_device_msg_window" #define WM_QUITTHREAD (WM_USER + 0) static void unreg_msg_window_class (ATOM class_id, const char *class_name, HINSTANCE inst); static HDEVNOTIFY register_device_interface (GstKsDeviceProvider * self, GUID interface_class_guid, HWND window_handle) { DEV_BROADCAST_DEVICEINTERFACE notification_filter; HDEVNOTIFY notification_handle; DWORD error; memset (¬ification_filter, 0, sizeof (notification_filter)); notification_filter.dbcc_size = sizeof (notification_filter); notification_filter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE; notification_filter.dbcc_classguid = interface_class_guid; notification_handle = RegisterDeviceNotificationW (window_handle, ¬ification_filter, DEVICE_NOTIFY_WINDOW_HANDLE | DEVICE_NOTIFY_ALL_INTERFACE_CLASSES); error = GetLastError (); if (notification_handle == NULL) GST_ERROR_OBJECT (self, "Could not register for a device notification: %lu", error); return notification_handle; } static INT_PTR WINAPI msg_window_message_proc (HWND window_handle, UINT message, WPARAM wparam, LPARAM lparam) { LRESULT result; LONG_PTR user_data; GstKsDeviceProvider *self; PDEV_BROADCAST_DEVICEINTERFACE bcdi; DEV_BROADCAST_HDR *dev_msg_header; struct _DEV_BROADCAST_USERDEFINED *user_dev_msg_header; CREATESTRUCT *create_data; DWORD error; HINSTANCE inst; GstKsDevice *dev; GstDevice *source; GList *item; GstDeviceProvider *provider; GList *devices; gchar *guid_str; result = TRUE; switch (message) { case WM_CREATE: create_data = (CREATESTRUCT *) lparam; if (create_data->lpCreateParams == NULL) { /* DO SOMETHING!! */ } self = GST_KS_DEVICE_PROVIDER (create_data->lpCreateParams); SetLastError (0); SetWindowLongPtr (window_handle, GWLP_USERDATA, (LONG_PTR) self); error = GetLastError (); if (error != NO_ERROR) { GST_ERROR_OBJECT (self, "Could not attach user data to the message window: %lu", error); DestroyWindow (window_handle); inst = (HINSTANCE) GetModuleHandle (NULL); GST_OBJECT_LOCK (self); unreg_msg_window_class (self->message_window_class, KS_MSG_WINDOW_CLASS, inst); self->message_window_class = 0; GST_OBJECT_UNLOCK (self); } result = FALSE; break; case WM_DEVICECHANGE: GST_DEBUG ("WM_DEVICECHANGE for %x %x", (unsigned int) wparam, (unsigned int) lparam); user_data = GetWindowLongPtr (window_handle, GWLP_USERDATA); if (user_data == 0) break; self = GST_KS_DEVICE_PROVIDER (user_data); provider = GST_DEVICE_PROVIDER (self); dev_msg_header = (DEV_BROADCAST_HDR *) lparam; switch (wparam) { case DBT_CONFIGCHANGECANCELED: GST_DEBUG_OBJECT (self, "DBT_CONFIGCHANGECANCELED for %s", get_dev_type (dev_msg_header)); break; case DBT_CONFIGCHANGED: GST_DEBUG_OBJECT (self, "DBT_CONFIGCHANGED for %s", get_dev_type (dev_msg_header)); break; case DBT_CUSTOMEVENT: GST_DEBUG_OBJECT (self, "DBT_CUSTOMEVENT for %s", get_dev_type (dev_msg_header)); break; case DBT_DEVICEARRIVAL: GST_DEBUG_OBJECT (self, "DBT_DEVICEARRIVAL for %s", get_dev_type (dev_msg_header)); if (dev_msg_header->dbch_devicetype != DBT_DEVTYP_DEVICEINTERFACE) break; bcdi = (PDEV_BROADCAST_DEVICEINTERFACE) lparam; guid_str = ks_guid_to_string (&bcdi->dbcc_classguid); GST_INFO_OBJECT (self, "New device, class interface GUID %s, path %s", guid_str, bcdi->dbcc_name); g_free (guid_str); break; case DBT_DEVICEQUERYREMOVE: GST_DEBUG_OBJECT (self, "DBT_DEVICEQUERYREMOVE for %s", get_dev_type (dev_msg_header)); break; case DBT_DEVICEQUERYREMOVEFAILED: GST_DEBUG_OBJECT (self, "DBT_DEVICEQUERYREMOVEFAILED for %s", get_dev_type (dev_msg_header)); break; case DBT_DEVICEREMOVECOMPLETE: GST_DEBUG_OBJECT (self, "DBT_DEVICEREMOVECOMPLETE for %s", get_dev_type (dev_msg_header)); if (dev_msg_header->dbch_devicetype != DBT_DEVTYP_DEVICEINTERFACE) break; bcdi = (PDEV_BROADCAST_DEVICEINTERFACE) lparam; guid_str = ks_guid_to_string (&bcdi->dbcc_classguid); GST_INFO_OBJECT (self, "Removed device, class interface GUID %s, path %s", guid_str, bcdi->dbcc_name); g_free (guid_str); break; case DBT_DEVICEREMOVEPENDING: GST_DEBUG_OBJECT (self, "DBT_DEVICEREMOVEPENDING for %s", get_dev_type (dev_msg_header)); break; case DBT_DEVICETYPESPECIFIC: GST_DEBUG_OBJECT (self, "DBT_DEVICETYPESPECIFIC for %s", get_dev_type (dev_msg_header)); break; case DBT_DEVNODES_CHANGED: GST_DEBUG_OBJECT (self, "DBT_DEVNODES_CHANGED for %s", get_dev_type (dev_msg_header)); break; case DBT_QUERYCHANGECONFIG: GST_DEBUG_OBJECT (self, "DBT_QUERYCHANGECONFIG for %s", get_dev_type (dev_msg_header)); break; case DBT_USERDEFINED: user_dev_msg_header = (struct _DEV_BROADCAST_USERDEFINED *) lparam; dev_msg_header = (DEV_BROADCAST_HDR *) & user_dev_msg_header->dbud_dbh; GST_DEBUG_OBJECT (self, "DBT_USERDEFINED for %s: %s", get_dev_type (dev_msg_header), user_dev_msg_header->dbud_szName); break; default: break; } switch (wparam) { case DBT_DEVICEARRIVAL: if (dev_msg_header->dbch_devicetype != DBT_DEVTYP_DEVICEINTERFACE) break; bcdi = (PDEV_BROADCAST_DEVICEINTERFACE) lparam; /* Since both video and audio capture device declare KSCATEGORY_CAPTURE, we filter on KSCATEGORY_VIDEO here. To add audio support we should accept also KSCATEGORY_AUDIO. */ if (!IsEqualGUID (&bcdi->dbcc_classguid, &KSCATEGORY_VIDEO)) break; devices = ks_enumerate_devices (&bcdi->dbcc_classguid, &KSCATEGORY_CAPTURE); if (devices == NULL) break; source = NULL; for (item = devices; item != NULL; item = item->next) { KsDeviceEntry *entry = item->data; GST_DEBUG_OBJECT (self, "Listed device %s = %s", entry->name, entry->path); if ((source == NULL) && (g_ascii_strcasecmp (entry->path, bcdi->dbcc_name) == 0)) source = new_video_source (entry); /* Or audio source, not implemented yet */ ks_device_entry_free (entry); } if (source) gst_device_provider_device_add (GST_DEVICE_PROVIDER (self), source); g_list_free (devices); break; case DBT_DEVICEREMOVECOMPLETE: if (dev_msg_header->dbch_devicetype != DBT_DEVTYP_DEVICEINTERFACE) break; bcdi = (PDEV_BROADCAST_DEVICEINTERFACE) lparam; dev = NULL; GST_OBJECT_LOCK (self); for (item = provider->devices; item; item = item->next) { dev = item->data; if (g_ascii_strcasecmp (dev->path, bcdi->dbcc_name) == 0) { guid_str = gst_device_get_display_name (GST_DEVICE (dev)); GST_INFO_OBJECT (self, "Device matches to %s", guid_str); g_free (guid_str); gst_object_ref (dev); break; } dev = NULL; } GST_OBJECT_UNLOCK (self); if (dev) { gst_device_provider_device_remove (GST_DEVICE_PROVIDER (self), GST_DEVICE (dev)); gst_object_unref (dev); } break; default: break; } result = FALSE; break; case WM_DESTROY: PostQuitMessage (0); result = FALSE; break; case WM_QUITTHREAD: DestroyWindow (window_handle); result = FALSE; break; default: result = DefWindowProc (window_handle, message, wparam, lparam); break; } return result; } static ATOM reg_msg_window_class (const char *class_name, HINSTANCE inst) { WNDCLASSEXA classex; memset (&classex, 0, sizeof (classex)); classex.cbSize = sizeof (classex); classex.hInstance = inst; classex.lpfnWndProc = (WNDPROC) msg_window_message_proc; classex.lpszClassName = class_name; return RegisterClassExA (&classex); } static void unreg_msg_window_class (ATOM class_id, const char *class_name, HINSTANCE inst) { if (class_id != 0) UnregisterClassA ((LPCSTR) MAKELPARAM (class_id, 0), inst); else UnregisterClassA (class_name, inst); } static gpointer ks_provider_msg_window_thread (gpointer dat) { GstKsDeviceProvider *self; MSG msg; ATOM wnd_class; BOOL message_status; HINSTANCE inst; HANDLE msg_window = NULL; DWORD error; HDEVNOTIFY devnotify = NULL; g_return_val_if_fail (dat != NULL, NULL); self = GST_KS_DEVICE_PROVIDER (dat); GST_DEBUG_OBJECT (self, "Entering message window thread: %p", g_thread_self ()); GST_OBJECT_LOCK (self); wnd_class = self->message_window_class; GST_OBJECT_UNLOCK (self); inst = (HINSTANCE) GetModuleHandle (NULL); msg_window = CreateWindowExA (0, wnd_class != 0 ? (LPCSTR) MAKELPARAM (wnd_class, 0) : KS_MSG_WINDOW_CLASS, "", 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, inst, self); error = GetLastError (); if (msg_window == NULL) { GST_ERROR_OBJECT (self, "Could not create a message window: %lu", error); GST_OBJECT_LOCK (self); unreg_msg_window_class (wnd_class, KS_MSG_WINDOW_CLASS, inst); self->message_window_class = 0; SetEvent (self->wakeup_event); GST_OBJECT_UNLOCK (self); return NULL; } GST_OBJECT_LOCK (self); self->message_window = msg_window; devnotify = register_device_interface (self, GUID_DEVCLASS_WCEUSBS, msg_window); if (devnotify == NULL) { DestroyWindow (msg_window); unreg_msg_window_class (wnd_class, KS_MSG_WINDOW_CLASS, inst); self->message_window_class = 0; self->message_window = NULL; SetEvent (self->wakeup_event); GST_OBJECT_UNLOCK (self); return NULL; } self->device_notify_handle = devnotify; SetEvent (self->wakeup_event); GST_OBJECT_UNLOCK (self); while ((message_status = GetMessage (&msg, NULL, 0, 0)) != 0) { if (message_status < 0 || msg.message == WM_QUIT) break; TranslateMessage (&msg); DispatchMessage (&msg); } GST_DEBUG_OBJECT (self, "Exiting internal window thread: %p", g_thread_self ()); return NULL; } static gboolean gst_ks_device_provider_start (GstDeviceProvider * provider) { ATOM wnd_class = 0; HINSTANCE inst; HANDLE wakeup_event; HWND message_window; DWORD error; GList *devs; GList *dev; GstKsDeviceProvider *self = GST_KS_DEVICE_PROVIDER (provider); GST_OBJECT_LOCK (self); g_assert (self->message_window == NULL); GST_OBJECT_UNLOCK (self); /* We get notifications on *change*, so before we get to that, * we need to obtain a complete list of devices, which we will * watch for changes. */ devs = gst_ks_device_provider_probe (provider); for (dev = devs; dev; dev = dev->next) { if (dev->data) gst_device_provider_device_add (provider, (GstDevice *) dev->data); } g_list_free (devs); inst = (HINSTANCE) GetModuleHandle (NULL); wakeup_event = CreateEvent (NULL, TRUE, FALSE, NULL); error = GetLastError (); if (wakeup_event == NULL) { GST_OBJECT_LOCK (self); GST_ERROR_OBJECT (self, "Could not create a wakeup event: %lu", error); GST_OBJECT_UNLOCK (self); return FALSE; } wnd_class = reg_msg_window_class (KS_MSG_WINDOW_CLASS, inst); error = GetLastError (); if ((wnd_class == 0) && (error != ERROR_CLASS_ALREADY_EXISTS)) { GST_ERROR_OBJECT (self, "Could not register message window class: %lu", error); CloseHandle (wakeup_event); return FALSE; } GST_OBJECT_LOCK (self); self->message_window_class = wnd_class; self->wakeup_event = wakeup_event; self->message_thread = g_thread_new ("ks-device-provider-message-window-thread", (GThreadFunc) ks_provider_msg_window_thread, self); if (self->message_thread == NULL) { GST_ERROR_OBJECT (self, "Could not create message window thread"); unreg_msg_window_class (wnd_class, KS_MSG_WINDOW_CLASS, inst); self->message_window_class = 0; CloseHandle (self->wakeup_event); GST_OBJECT_UNLOCK (self); return FALSE; } GST_OBJECT_UNLOCK (self); if (WaitForSingleObject (wakeup_event, INFINITE) != WAIT_OBJECT_0) { GST_ERROR_OBJECT (self, "Failed to wait for the message thread to initialize"); } GST_OBJECT_LOCK (self); CloseHandle (self->wakeup_event); self->wakeup_event = NULL; message_window = self->message_window; GST_OBJECT_UNLOCK (self); if (message_window == NULL) return FALSE; return TRUE; } static void gst_ks_device_provider_stop (GstDeviceProvider * provider) { HINSTANCE inst; GThread *message_thread; GstKsDeviceProvider *self = GST_KS_DEVICE_PROVIDER (provider); GST_OBJECT_LOCK (self); g_assert (self->message_window != NULL); UnregisterDeviceNotification (self->device_notify_handle); self->device_notify_handle = NULL; PostMessage (self->message_window, WM_QUITTHREAD, 0, 0); message_thread = self->message_thread; GST_OBJECT_UNLOCK (self); g_thread_join (message_thread); GST_OBJECT_LOCK (self); self->message_window = NULL; self->message_thread = NULL; inst = (HINSTANCE) GetModuleHandle (NULL); unreg_msg_window_class (self->message_window_class, KS_MSG_WINDOW_CLASS, inst); self->message_window_class = 0; GST_OBJECT_UNLOCK (self); } enum { PROP_PATH = 1 }; G_DEFINE_TYPE (GstKsDevice, gst_ks_device, GST_TYPE_DEVICE); static void gst_ks_device_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec); static void gst_ks_device_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec); static void gst_ks_device_finalize (GObject * object); static GstElement *gst_ks_device_create_element (GstDevice * device, const gchar * name); static gboolean gst_ks_device_reconfigure_element (GstDevice * device, GstElement * element); static void gst_ks_device_class_init (GstKsDeviceClass * klass) { GstDeviceClass *dev_class = GST_DEVICE_CLASS (klass); GObjectClass *object_class = G_OBJECT_CLASS (klass); dev_class->create_element = gst_ks_device_create_element; dev_class->reconfigure_element = gst_ks_device_reconfigure_element; object_class->get_property = gst_ks_device_get_property; object_class->set_property = gst_ks_device_set_property; object_class->finalize = gst_ks_device_finalize; g_object_class_install_property (object_class, PROP_PATH, g_param_spec_string ("path", "System device path", "The system path to the device", "", G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); } static void gst_ks_device_init (GstKsDevice * device) { } static void gst_ks_device_finalize (GObject * object) { GstKsDevice *device = GST_KS_DEVICE (object); g_free (device->path); G_OBJECT_CLASS (gst_ks_device_parent_class)->finalize (object); } static GstElement * gst_ks_device_create_element (GstDevice * device, const gchar * name) { GstKsDevice *ks_dev = GST_KS_DEVICE (device); GstElement *elem; elem = gst_element_factory_make (ks_dev->element, name); g_object_set (elem, "device-path", ks_dev->path, NULL); return elem; } static gboolean gst_ks_device_reconfigure_element (GstDevice * device, GstElement * element) { GstKsDevice *ks_dev = GST_KS_DEVICE (device); if (!strcmp (ks_dev->element, "ksvideosrc")) { if (!GST_IS_KS_VIDEO_SRC (element)) return FALSE; /* } else if (!strcmp (ks_dev->element, "ksaudiosrc")) { if (!GST_IS_KS_AUDIO_SRC (element)) return FALSE; } else if (!strcmp (ks_dev->element, "ksaudiosink")) { if (!GST_IS_KS_AUDIO_SINK (element)) return FALSE; */ } else { g_assert_not_reached (); } g_object_set (element, "path", ks_dev->path, NULL); return TRUE; } static GstDevice * gst_ks_device_new (guint device_index, const gchar * device_name, GstCaps * caps, const gchar * device_path, GstKsDeviceType type) { GstKsDevice *gstdev; const gchar *element = NULL; const gchar *klass = NULL; g_return_val_if_fail (device_name, NULL); g_return_val_if_fail (device_path, NULL); g_return_val_if_fail (caps, NULL); switch (type) { case GST_KS_DEVICE_TYPE_VIDEO_SOURCE: element = "ksvideosrc"; klass = "Video/Source"; break; case GST_KS_DEVICE_TYPE_AUDIO_SOURCE: element = "ksaudiosrc"; klass = "Audio/Source"; break; case GST_KS_DEVICE_TYPE_AUDIO_SINK: element = "ksaudiosink"; klass = "Audio/Sink"; break; default: g_assert_not_reached (); break; } gstdev = g_object_new (GST_TYPE_KS_DEVICE, "display-name", device_name, "caps", caps, "device-class", klass, "path", device_path, NULL); gstdev->type = type; gstdev->device_index = device_index; gstdev->path = g_strdup (device_path); gstdev->element = element; return GST_DEVICE (gstdev); } static void gst_ks_device_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) { GstKsDevice *device; device = GST_KS_DEVICE_CAST (object); switch (prop_id) { case PROP_PATH: g_value_set_string (value, device->path); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gst_ks_device_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) { GstKsDevice *device; device = GST_KS_DEVICE_CAST (object); switch (prop_id) { case PROP_PATH: device->path = g_value_dup_string (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } }