dshowsrcwrapper: refactor device selection, filter creation, and caps retrieval
authorJoshua M. Doe <oss@nvl.army.mil>
Tue, 16 Oct 2018 15:45:15 +0000 (11:45 -0400)
committerNirbheek Chauhan <nirbheek.chauhan@gmail.com>
Fri, 9 Nov 2018 09:52:40 +0000 (09:52 +0000)
This allows a future GstDeviceProvider to more easily query devices and caps.

sys/dshowsrcwrapper/gstdshow.cpp
sys/dshowsrcwrapper/gstdshow.h
sys/dshowsrcwrapper/gstdshowvideosrc.cpp
sys/dshowsrcwrapper/gstdshowvideosrc.h

index a5aded7..24a241a 100644 (file)
 
 #include "gstdshow.h"
 #include "gstdshowfakesink.h"
+#include "gstdshowvideosrc.h"
 
 GST_DEBUG_CATEGORY_EXTERN (dshowsrcwrapper_debug);
 #define GST_CAT_DEFAULT dshowsrcwrapper_debug
 
+gchar *
+wchar_to_gchar (WCHAR * w)
+{
+  return g_utf16_to_utf8 ((const gunichar2 *) w, wcslen (w), NULL, NULL, NULL);
+}
+
 const GUID MEDIASUBTYPE_I420
     = { 0x30323449, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B,
     0x71}
@@ -238,9 +245,7 @@ gst_dshow_find_filter (CLSID input_majortype, CLSID input_subtype,
 
       hres = property_bag->Read (L"FriendlyName", &varFriendlyName, NULL);
       if (hres == S_OK && varFriendlyName.bstrVal) {
-        friendly_name =
-            g_utf16_to_utf8 ((const gunichar2 *) varFriendlyName.bstrVal,
-            wcslen (varFriendlyName.bstrVal), NULL, NULL, NULL);
+        friendly_name = wchar_to_gchar (varFriendlyName.bstrVal);
         if (friendly_name)
           _strupr (friendly_name);
         SysFreeString (varFriendlyName.bstrVal);
@@ -288,6 +293,196 @@ clean:
   return ret;
 }
 
+void
+gst_dshow_device_entry_free (DshowDeviceEntry * entry)
+{
+  if (entry) {
+    g_free (entry->device);
+    entry->device = NULL;
+    g_free (entry->device_name);
+    entry->device_name = NULL;
+    if (entry->caps) {
+      gst_caps_unref (entry->caps);
+      entry->caps = NULL;
+    }
+    if (entry->moniker) {
+      entry->moniker->Release ();
+      entry->moniker = NULL;
+    }
+  }
+}
+
+void
+gst_dshow_device_list_free (GList * devices)
+{
+  GList *cur;
+
+  for (cur = devices; cur != NULL; cur = cur->next)
+    gst_dshow_device_entry_free ((DshowDeviceEntry *) cur->data);
+
+  g_list_free (devices);
+}
+
+GList *
+gst_dshow_enumerate_devices (const GUID * device_category, gboolean getcaps)
+{
+  GList *result = NULL;
+  ICreateDevEnum *devices_enum = NULL;
+  IEnumMoniker *enum_moniker = NULL;
+  IMoniker *moniker = NULL;
+  HRESULT hres = S_FALSE;
+  ULONG fetched;
+  gint devidx = -1;
+
+  hres = CoCreateInstance (CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER,
+      IID_ICreateDevEnum, (void **) &devices_enum);
+  if (hres != S_OK) {
+    GST_ERROR ("Failed to create System Device Enumerator");
+    goto clean;
+  }
+
+  hres = devices_enum->CreateClassEnumerator (*device_category,
+      &enum_moniker, 0);
+  if (hres != S_OK || !enum_moniker) {
+    GST_ERROR ("Failed to create audio/video class device enumerator");
+    goto clean;
+  }
+
+  enum_moniker->Reset ();
+
+  while (enum_moniker->Next (1, &moniker, &fetched) == S_OK) {
+    IPropertyBag *property_bag = NULL;
+    hres = moniker->BindToStorage (NULL, NULL, IID_IPropertyBag,
+        (void **) &property_bag);
+    if (SUCCEEDED (hres) && property_bag) {
+      VARIANT varFriendlyName;
+      VariantInit (&varFriendlyName);
+
+      hres = property_bag->Read (L"FriendlyName", &varFriendlyName, NULL);
+      if (hres == S_OK && varFriendlyName.bstrVal) {
+        gchar *friendly_name = wchar_to_gchar (varFriendlyName.bstrVal);
+
+        devidx++;
+        GST_DEBUG ("Found device idx=%d: device-name='%s'",
+            devidx, friendly_name);
+
+        WCHAR *wszDisplayName = NULL;
+        hres = moniker->GetDisplayName (NULL, NULL, &wszDisplayName);
+        if (hres == S_OK && wszDisplayName) {
+          DshowDeviceEntry *entry = g_new0 (DshowDeviceEntry, 1);
+          gchar *device_path = NULL;
+          GstCaps *caps = NULL;
+
+          device_path = wchar_to_gchar (wszDisplayName);
+          CoTaskMemFree (wszDisplayName);
+
+          /* getting caps can be slow, so make it optional when enumerating */
+          if (getcaps) {
+            IBindCtx *lpbc = NULL;
+            hres = CreateBindCtx (0, &lpbc);
+            if (SUCCEEDED (hres)) {
+              IBaseFilter *video_cap_filter = NULL;
+              hres = moniker->BindToObject (lpbc, NULL, IID_IBaseFilter,
+                (LPVOID *) & video_cap_filter);
+              if (video_cap_filter) {
+                caps = gst_dshowvideosrc_getcaps_from_capture_filter (video_cap_filter, NULL);
+                video_cap_filter->Release ();
+              }
+              lpbc->Release ();
+            }
+          }
+
+          entry->device = device_path;
+          entry->device_name = friendly_name;
+          entry->device_index = devidx;
+          entry->caps = caps;
+          entry->moniker = moniker;
+          moniker = NULL;
+          result = g_list_append (result, entry);
+        } else {
+          g_free (friendly_name);
+        }
+        SysFreeString (varFriendlyName.bstrVal);
+      }
+      property_bag->Release ();
+    }
+    if (moniker) {
+      moniker->Release ();
+    }
+  }
+
+clean:
+  if (enum_moniker) {
+    enum_moniker->Release ();
+  }
+
+  if (devices_enum) {
+    devices_enum->Release ();
+  }
+
+  return result;
+}
+
+DshowDeviceEntry *
+gst_dshow_select_device (const GUID * device_category,
+    const gchar * device, const gchar * device_name, const gint device_index)
+{
+  GList *devices = NULL;
+  GList *item = NULL;
+  DshowDeviceEntry *selected = NULL;
+
+  GST_DEBUG ("Trying to select device-index=%d, device-name='%s', device='%s'",
+    device_index, device_name, device);
+
+  devices = gst_dshow_enumerate_devices (&CLSID_VideoInputDeviceCategory, FALSE);
+
+  for (item = devices; item != NULL; item = item->next) {
+    DshowDeviceEntry *entry = (DshowDeviceEntry *) item->data;
+
+    /* device will be used first, then device-name, then device-index */
+    if (device && g_strcmp0 (device, entry->device) == 0) {
+      selected = entry;
+      break;
+    } else if (device_name && g_strcmp0 (device_name, entry->device_name) == 0) {
+      selected = entry;
+      break;
+    } else if (device_index == entry->device_index) {
+      selected = entry;
+      break;
+    }
+  }
+
+  if (selected) {
+    devices = g_list_remove (devices, selected);
+    GST_DEBUG ("Selected device-index=%d, device-name='%s', device='%s'",
+      selected->device_index, selected->device_name, selected->device);
+  } else {
+    GST_DEBUG ("No matching device found");
+  }
+
+  gst_dshow_device_list_free (devices);
+
+  return selected;
+}
+
+IBaseFilter *
+gst_dshow_create_capture_filter (IMoniker *moniker)
+{
+  HRESULT hres = S_OK;
+  IBindCtx *lpbc = NULL;
+  IBaseFilter *video_cap_filter = NULL;
+
+  g_assert (moniker != NULL);
+
+  hres = CreateBindCtx (0, &lpbc);
+  if (SUCCEEDED (hres)) {
+    hres = moniker->BindToObject (lpbc, NULL, IID_IBaseFilter,
+        (LPVOID *) & video_cap_filter);
+    lpbc->Release ();
+  }
+
+  return video_cap_filter;
+}
 
 gchar *
 gst_dshow_getdevice_from_devicename (const GUID * device_category,
@@ -305,14 +500,14 @@ gst_dshow_getdevice_from_devicename (const GUID * device_category,
   hres = CoCreateInstance (CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER,
       IID_ICreateDevEnum, (void **) &devices_enum);
   if (hres != S_OK) {
-    /*error */
+    GST_ERROR ("Failed to create System Device Enumerator");
     goto clean;
   }
 
   hres = devices_enum->CreateClassEnumerator (*device_category,
       &enum_moniker, 0);
   if (hres != S_OK || !enum_moniker) {
-    /*error */
+    GST_ERROR ("Failed to create audio/video class device enumerator");
     goto clean;
   }
 
@@ -330,9 +525,7 @@ gst_dshow_getdevice_from_devicename (const GUID * device_category,
 
       hres = property_bag->Read (L"FriendlyName", &varFriendlyName, NULL);
       if (hres == S_OK && varFriendlyName.bstrVal) {
-        gchar *friendly_name =
-            g_utf16_to_utf8 ((const gunichar2 *) varFriendlyName.bstrVal,
-            wcslen (varFriendlyName.bstrVal), NULL, NULL, NULL);
+        gchar *friendly_name = wchar_to_gchar (varFriendlyName.bstrVal);
 
         devidx++;
         GST_DEBUG ("Found device idx=%d: device-name='%s'",
@@ -343,13 +536,13 @@ gst_dshow_getdevice_from_devicename (const GUID * device_category,
           *device_name = g_strdup (friendly_name);
         }
 
-        if ((*device_name && **device_name) && _stricmp (*device_name, friendly_name) == 0) {
+        if ((*device_name && **device_name)
+            && _stricmp (*device_name, friendly_name) == 0) {
           WCHAR *wszDisplayName = NULL;
           hres = moniker->GetDisplayName (NULL, NULL, &wszDisplayName);
           if (hres == S_OK && wszDisplayName) {
             *device_index = devidx;
-            ret = g_utf16_to_utf8 ((const gunichar2 *) wszDisplayName,
-                wcslen (wszDisplayName), NULL, NULL, NULL);
+            ret = wchar_to_gchar (wszDisplayName);
             CoTaskMemFree (wszDisplayName);
           }
           bfound = TRUE;
index aff5d0a..2814e37 100644 (file)
 #include <gst/gst.h>
 #include <gst/video/video.h>
 
+typedef struct _DshowDeviceEntry DshowDeviceEntry;
+
+struct _DshowDeviceEntry
+{
+  gchar *device;
+  gchar *device_name;
+  gint device_index;
+  GstCaps *caps;
+  IMoniker *moniker;
+};
+
 typedef struct _GstCapturePinMediaType
 {
   AM_MEDIA_TYPE *mediatype;
@@ -102,4 +113,15 @@ GstCaps *gst_dshow_new_video_caps (GstVideoFormat video_format,
 /* configure the latency of the capture source */
 bool gst_dshow_configure_latency (IPin *pCapturePin, guint bufSizeMS);
 
+/* enumerate devices of a given category (i.e., audio or video) */
+void gst_dshow_device_entry_free (DshowDeviceEntry *entry);
+void gst_dshow_device_list_free (GList * devices);
+GList * gst_dshow_enumerate_devices (const GUID * device_category, gboolean getcaps);
+
+DshowDeviceEntry * gst_dshow_select_device (const GUID * device_category,
+    const gchar *device, const gchar *device_name, const gint device_index);
+
+/* create capture filter from moniker */
+IBaseFilter *gst_dshow_create_capture_filter (IMoniker *moniker);
+
 #endif /* _GSTDSHOW_ */
index 691df59..4127d53 100644 (file)
@@ -89,13 +89,12 @@ static GstCaps *gst_dshowvideosrc_src_fixate (GstBaseSrc * bsrc, GstCaps * caps)
 static GstFlowReturn gst_dshowvideosrc_create (GstPushSrc * psrc,
     GstBuffer ** buf);
 
-static gboolean gst_dshowvideosrc_create_capture_filter(GstDshowVideoSrc * src);
-
 /*utils*/
-static GstCaps *gst_dshowvideosrc_getcaps_from_streamcaps (GstDshowVideoSrc *
-    src, IPin * pin);
-static GstCaps *gst_dshowvideosrc_getcaps_from_enum_mediatypes (GstDshowVideoSrc *
-    src, IPin * pin);
+GstCaps *gst_dshowvideosrc_getcaps_from_streamcaps (IPin * pin,
+    GList ** pins_mediatypes);
+GstCaps *gst_dshowvideosrc_getcaps_from_enum_mediatypes (IPin * pin,
+    GList ** pins_mediatypes);
+
 static gboolean gst_dshowvideosrc_push_buffer (guint8 * buffer, guint size,
     gpointer src_object, GstClockTime duration);
 
@@ -362,104 +361,62 @@ gst_dshowvideosrc_get_caps (GstBaseSrc * basesrc, GstCaps * filter)
   return NULL;
 }
 
-static gboolean
-gst_dshowvideosrc_create_capture_filter(GstDshowVideoSrc * src)
+GstCaps *
+gst_dshowvideosrc_getcaps_from_capture_filter (IBaseFilter * filter,
+    GList ** pins_mediatypes)
 {
-  HRESULT hres = S_OK;
-  IBindCtx *lpbc = NULL;
-  IMoniker *videom;
-  DWORD dwEaten;
-  gunichar2 *unidevice = NULL;
-
-  /* device will be used first, then device-name, then device-index */
-  if (!src->device || src->device[0] == '\0') {
-    GST_DEBUG_OBJECT (src, "No device set, will enumerate to match device-name or device-index");
-
-    g_free (src->device);
-    src->device =
-        gst_dshow_getdevice_from_devicename (&CLSID_VideoInputDeviceCategory,
-        &src->device_name, &src->device_index);
-    if (!src->device) {
-      GST_ERROR ("No video device found.");
-      return FALSE;
-    }
-  }
+  IPin *capture_pin = NULL;
+  IEnumPins *enumpins = NULL;
+  HRESULT hres;
+  GstCaps *caps;
 
-  GST_DEBUG_OBJECT (src, "Opening device-index=%d, device-name='%s', device='%s'",
-      src->device_index, src->device_name, src->device);
+  g_assert (filter);
 
-  unidevice =
-      g_utf8_to_utf16 (src->device, strlen (src->device), NULL, NULL, NULL);
+  caps = gst_caps_new_empty ();
 
-  if (!src->video_cap_filter) {
-    hres = CreateBindCtx (0, &lpbc);
-    if (SUCCEEDED (hres)) {
+  /* get the capture pins supported types */
+  hres = filter->EnumPins (&enumpins);
+  if (SUCCEEDED (hres)) {
+    while (enumpins->Next (1, &capture_pin, NULL) == S_OK) {
+      IKsPropertySet *pKs = NULL;
       hres =
-          MkParseDisplayName (lpbc, (LPCOLESTR) unidevice, &dwEaten, &videom);
-      if (SUCCEEDED (hres)) {
-        hres = videom->BindToObject (lpbc, NULL, IID_IBaseFilter,
-            (LPVOID *) & src->video_cap_filter);
-        videom->Release ();
-      }
-      lpbc->Release ();
-    }
-  }
-
-  g_free (unidevice);
+          capture_pin->QueryInterface (IID_IKsPropertySet, (LPVOID *) & pKs);
+      if (SUCCEEDED (hres) && pKs) {
+        DWORD cbReturned;
+        GUID pin_category;
+        RPC_STATUS rpcstatus;
 
-  if (!src->caps) {
-    src->caps = gst_caps_new_empty ();
-  }
-
-  if (src->video_cap_filter && gst_caps_is_empty (src->caps)) {
-    /* get the capture pins supported types */
-    IPin *capture_pin = NULL;
-    IEnumPins *enumpins = NULL;
-    HRESULT hres;
-
-    hres = src->video_cap_filter->EnumPins (&enumpins);
-    if (SUCCEEDED (hres)) {
-      while (enumpins->Next (1, &capture_pin, NULL) == S_OK) {
-        IKsPropertySet *pKs = NULL;
         hres =
-            capture_pin->QueryInterface (IID_IKsPropertySet, (LPVOID *) & pKs);
-        if (SUCCEEDED (hres) && pKs) {
-          DWORD cbReturned;
-          GUID pin_category;
-          RPC_STATUS rpcstatus;
-
-          hres =
-              pKs->Get (AMPROPSETID_Pin,
-              AMPROPERTY_PIN_CATEGORY, NULL, 0, &pin_category, sizeof (GUID),
-              &cbReturned);
-
-          /* we only want capture pins */
-          if (UuidCompare (&pin_category, (UUID *) & PIN_CATEGORY_CAPTURE,
-                  &rpcstatus) == 0) {
-            {
-              GstCaps *caps =
-                  gst_dshowvideosrc_getcaps_from_streamcaps (src, capture_pin);
-              if (caps) {
-                GST_DEBUG_OBJECT (src, "Caps supported by device: %" GST_PTR_FORMAT, caps);
-                gst_caps_append (src->caps, caps);
-              } else {
-                caps = gst_dshowvideosrc_getcaps_from_enum_mediatypes (src, capture_pin);
-                if (caps) {
-                  GST_DEBUG_OBJECT (src, "Caps supported by device: %" GST_PTR_FORMAT, caps);
-                  gst_caps_append (src->caps, caps);
-                }
-              }
+            pKs->Get (AMPROPSETID_Pin,
+            AMPROPERTY_PIN_CATEGORY, NULL, 0, &pin_category, sizeof (GUID),
+            &cbReturned);
+
+        /* we only want capture pins */
+        if (UuidCompare (&pin_category, (UUID *) & PIN_CATEGORY_CAPTURE,
+                &rpcstatus) == 0) {
+          GstCaps *caps2;
+          caps2 = gst_dshowvideosrc_getcaps_from_streamcaps (capture_pin,
+              pins_mediatypes);
+          if (caps2) {
+            gst_caps_append (caps, caps2);
+          } else {
+            caps2 = gst_dshowvideosrc_getcaps_from_enum_mediatypes (
+                capture_pin, pins_mediatypes);
+            if (caps2) {
+              gst_caps_append (caps, caps2);
             }
           }
-          pKs->Release ();
         }
-        capture_pin->Release ();
+        pKs->Release ();
       }
-      enumpins->Release ();
+      capture_pin->Release ();
     }
+    enumpins->Release ();
   }
 
-  return TRUE;
+  GST_DEBUG ("Device supports these caps: %" GST_PTR_FORMAT, caps);
+
+  return caps;
 }
 
 static GstStateChangeReturn
@@ -509,8 +466,40 @@ gst_dshowvideosrc_start (GstBaseSrc * bsrc)
 {
   HRESULT hres = S_FALSE;
   GstDshowVideoSrc *src = GST_DSHOWVIDEOSRC (bsrc);
-  
-  gst_dshowvideosrc_create_capture_filter (src);
+  DshowDeviceEntry *device_entry;
+  IMoniker *moniker = NULL;
+
+  device_entry = gst_dshow_select_device (&CLSID_VideoInputDeviceCategory,
+      src->device, src->device_name, src->device_index);
+  if (device_entry == NULL) {
+    GST_ELEMENT_ERROR (src, RESOURCE, FAILED, ("Failed to find device"), (NULL));
+    return FALSE;
+  }
+
+  g_free (src->device);
+  g_free (src->device_name);
+  src->device = g_strdup (device_entry->device);
+  src->device_name = g_strdup (device_entry->device_name);
+  src->device_index = device_entry->device_index;
+  moniker = device_entry->moniker;
+  device_entry->moniker = NULL;
+  gst_dshow_device_entry_free (device_entry);
+
+  src->video_cap_filter = gst_dshow_create_capture_filter (moniker);
+  moniker->Release ();
+  if (src->video_cap_filter == NULL) {
+    GST_ELEMENT_ERROR (src, RESOURCE, FAILED,
+        ("Failed to create capture filter for device"), (NULL));
+    return FALSE;
+  }
+
+  src->caps = gst_dshowvideosrc_getcaps_from_capture_filter (
+      src->video_cap_filter, (GList**)&src->pins_mediatypes);
+  if (gst_caps_is_empty (src->caps)) {
+    GST_ELEMENT_ERROR (src, RESOURCE, FAILED,
+        ("Failed to get any caps from devce"), (NULL));
+    return FALSE;
+  }
 
   /*
   The filter graph now is created via the IGraphBuilder Interface   
@@ -600,6 +589,9 @@ gst_dshowvideosrc_start (GstBaseSrc * bsrc)
   return TRUE;
 
 error:
+  GST_ELEMENT_ERROR (src, RESOURCE, FAILED,
+     ("Failed to build filter graph"), (NULL));
+
   if (src->dshow_fakesink) {
     src->dshow_fakesink->Release ();
     src->dshow_fakesink = NULL;
@@ -858,7 +850,12 @@ gst_dshowvideosrc_stop (GstBaseSrc * bsrc)
     g_free (src->device);
     src->device = NULL;
   }
-  
+
+  if (src->video_cap_filter) {
+    src->video_cap_filter->Release ();
+    src->video_cap_filter = NULL;
+  }
+
   return TRUE;
 }
 
@@ -912,8 +909,8 @@ gst_dshowvideosrc_create (GstPushSrc * psrc, GstBuffer ** buf)
   return GST_FLOW_OK;
 }
 
-static GstCaps *
-gst_dshowvideosrc_getcaps_from_streamcaps (GstDshowVideoSrc * src, IPin * pin)
+GstCaps *
+gst_dshowvideosrc_getcaps_from_streamcaps (IPin * pin, GList ** pins_mediatypes)
 {
   GstCaps *caps = NULL;
   HRESULT hres = S_OK;
@@ -981,8 +978,9 @@ gst_dshowvideosrc_getcaps_from_streamcaps (GstDshowVideoSrc * src, IPin * pin)
       }
 
       if (mediacaps) {
-        src->pins_mediatypes =
-            g_list_append (src->pins_mediatypes, pin_mediatype);
+        if (pins_mediatypes != NULL) {
+          *pins_mediatypes = g_list_append (*pins_mediatypes, pin_mediatype);
+        }
         gst_caps_append (caps, mediacaps);
       } else {
         /* failed to convert dshow caps */
@@ -1001,8 +999,8 @@ gst_dshowvideosrc_getcaps_from_streamcaps (GstDshowVideoSrc * src, IPin * pin)
   return caps;
 }
 
-static GstCaps *
-gst_dshowvideosrc_getcaps_from_enum_mediatypes (GstDshowVideoSrc * src, IPin * pin)
+GstCaps *
+gst_dshowvideosrc_getcaps_from_enum_mediatypes (IPin * pin, GList ** pins_mediatypes)
 {
   GstCaps *caps = NULL;
   IEnumMediaTypes *enum_mediatypes = NULL;
@@ -1036,8 +1034,9 @@ gst_dshowvideosrc_getcaps_from_enum_mediatypes (GstDshowVideoSrc * src, IPin * p
        }
 
     if (mediacaps) {
-      src->pins_mediatypes =
-          g_list_append (src->pins_mediatypes, pin_mediatype);
+      if (pins_mediatypes != NULL) {
+        *pins_mediatypes = g_list_append (*pins_mediatypes, pin_mediatype);
+      }
       gst_caps_append (caps, mediacaps);
     } else {
       /* failed to convert dshow caps */
index c18271f..3faa599 100644 (file)
@@ -100,5 +100,10 @@ struct _GstDshowVideoSrcClass
 
 GType gst_dshowvideosrc_get_type (void);
 
+
+GstCaps * gst_dshowvideosrc_getcaps_from_capture_filter (IBaseFilter * filter,
+    GList ** pins_mediatypes);
+
+
 G_END_DECLS
 #endif /* __GST_DSHOWVIDEOSRC_H__ */