dshowsrcwrapper: refactor device selection, filter creation, and caps retrieval

This allows a future GstDeviceProvider to more easily query devices and caps.
This commit is contained in:
Joshua M. Doe 2018-10-16 11:45:15 -04:00 committed by Nirbheek Chauhan
parent e70af38d4e
commit 912ff02a21
4 changed files with 330 additions and 111 deletions

View file

@ -23,10 +23,17 @@
#include "gstdshow.h" #include "gstdshow.h"
#include "gstdshowfakesink.h" #include "gstdshowfakesink.h"
#include "gstdshowvideosrc.h"
GST_DEBUG_CATEGORY_EXTERN (dshowsrcwrapper_debug); GST_DEBUG_CATEGORY_EXTERN (dshowsrcwrapper_debug);
#define GST_CAT_DEFAULT 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 const GUID MEDIASUBTYPE_I420
= { 0x30323449, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, = { 0x30323449, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B,
0x71} 0x71}
@ -238,9 +245,7 @@ gst_dshow_find_filter (CLSID input_majortype, CLSID input_subtype,
hres = property_bag->Read (L"FriendlyName", &varFriendlyName, NULL); hres = property_bag->Read (L"FriendlyName", &varFriendlyName, NULL);
if (hres == S_OK && varFriendlyName.bstrVal) { if (hres == S_OK && varFriendlyName.bstrVal) {
friendly_name = friendly_name = wchar_to_gchar (varFriendlyName.bstrVal);
g_utf16_to_utf8 ((const gunichar2 *) varFriendlyName.bstrVal,
wcslen (varFriendlyName.bstrVal), NULL, NULL, NULL);
if (friendly_name) if (friendly_name)
_strupr (friendly_name); _strupr (friendly_name);
SysFreeString (varFriendlyName.bstrVal); SysFreeString (varFriendlyName.bstrVal);
@ -288,6 +293,196 @@ clean:
return ret; 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 * gchar *
gst_dshow_getdevice_from_devicename (const GUID * device_category, 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, hres = CoCreateInstance (CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER,
IID_ICreateDevEnum, (void **) &devices_enum); IID_ICreateDevEnum, (void **) &devices_enum);
if (hres != S_OK) { if (hres != S_OK) {
/*error */ GST_ERROR ("Failed to create System Device Enumerator");
goto clean; goto clean;
} }
hres = devices_enum->CreateClassEnumerator (*device_category, hres = devices_enum->CreateClassEnumerator (*device_category,
&enum_moniker, 0); &enum_moniker, 0);
if (hres != S_OK || !enum_moniker) { if (hres != S_OK || !enum_moniker) {
/*error */ GST_ERROR ("Failed to create audio/video class device enumerator");
goto clean; goto clean;
} }
@ -330,9 +525,7 @@ gst_dshow_getdevice_from_devicename (const GUID * device_category,
hres = property_bag->Read (L"FriendlyName", &varFriendlyName, NULL); hres = property_bag->Read (L"FriendlyName", &varFriendlyName, NULL);
if (hres == S_OK && varFriendlyName.bstrVal) { if (hres == S_OK && varFriendlyName.bstrVal) {
gchar *friendly_name = gchar *friendly_name = wchar_to_gchar (varFriendlyName.bstrVal);
g_utf16_to_utf8 ((const gunichar2 *) varFriendlyName.bstrVal,
wcslen (varFriendlyName.bstrVal), NULL, NULL, NULL);
devidx++; devidx++;
GST_DEBUG ("Found device idx=%d: device-name='%s'", 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); *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; WCHAR *wszDisplayName = NULL;
hres = moniker->GetDisplayName (NULL, NULL, &wszDisplayName); hres = moniker->GetDisplayName (NULL, NULL, &wszDisplayName);
if (hres == S_OK && wszDisplayName) { if (hres == S_OK && wszDisplayName) {
*device_index = devidx; *device_index = devidx;
ret = g_utf16_to_utf8 ((const gunichar2 *) wszDisplayName, ret = wchar_to_gchar (wszDisplayName);
wcslen (wszDisplayName), NULL, NULL, NULL);
CoTaskMemFree (wszDisplayName); CoTaskMemFree (wszDisplayName);
} }
bfound = TRUE; bfound = TRUE;

View file

@ -32,6 +32,17 @@
#include <gst/gst.h> #include <gst/gst.h>
#include <gst/video/video.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 typedef struct _GstCapturePinMediaType
{ {
AM_MEDIA_TYPE *mediatype; AM_MEDIA_TYPE *mediatype;
@ -102,4 +113,15 @@ GstCaps *gst_dshow_new_video_caps (GstVideoFormat video_format,
/* configure the latency of the capture source */ /* configure the latency of the capture source */
bool gst_dshow_configure_latency (IPin *pCapturePin, guint bufSizeMS); 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_ */ #endif /* _GSTDSHOW_ */

View file

@ -89,13 +89,12 @@ static GstCaps *gst_dshowvideosrc_src_fixate (GstBaseSrc * bsrc, GstCaps * caps)
static GstFlowReturn gst_dshowvideosrc_create (GstPushSrc * psrc, static GstFlowReturn gst_dshowvideosrc_create (GstPushSrc * psrc,
GstBuffer ** buf); GstBuffer ** buf);
static gboolean gst_dshowvideosrc_create_capture_filter(GstDshowVideoSrc * src);
/*utils*/ /*utils*/
static GstCaps *gst_dshowvideosrc_getcaps_from_streamcaps (GstDshowVideoSrc * GstCaps *gst_dshowvideosrc_getcaps_from_streamcaps (IPin * pin,
src, IPin * pin); GList ** pins_mediatypes);
static GstCaps *gst_dshowvideosrc_getcaps_from_enum_mediatypes (GstDshowVideoSrc * GstCaps *gst_dshowvideosrc_getcaps_from_enum_mediatypes (IPin * pin,
src, IPin * pin); GList ** pins_mediatypes);
static gboolean gst_dshowvideosrc_push_buffer (guint8 * buffer, guint size, static gboolean gst_dshowvideosrc_push_buffer (guint8 * buffer, guint size,
gpointer src_object, GstClockTime duration); gpointer src_object, GstClockTime duration);
@ -362,104 +361,62 @@ gst_dshowvideosrc_get_caps (GstBaseSrc * basesrc, GstCaps * filter)
return NULL; return NULL;
} }
static gboolean GstCaps *
gst_dshowvideosrc_create_capture_filter(GstDshowVideoSrc * src) gst_dshowvideosrc_getcaps_from_capture_filter (IBaseFilter * filter,
GList ** pins_mediatypes)
{ {
HRESULT hres = S_OK; IPin *capture_pin = NULL;
IBindCtx *lpbc = NULL; IEnumPins *enumpins = NULL;
IMoniker *videom; HRESULT hres;
DWORD dwEaten; GstCaps *caps;
gunichar2 *unidevice = NULL;
/* device will be used first, then device-name, then device-index */ g_assert (filter);
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); caps = gst_caps_new_empty ();
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;
}
}
GST_DEBUG_OBJECT (src, "Opening device-index=%d, device-name='%s', device='%s'", /* get the capture pins supported types */
src->device_index, src->device_name, src->device); hres = filter->EnumPins (&enumpins);
if (SUCCEEDED (hres)) {
unidevice = while (enumpins->Next (1, &capture_pin, NULL) == S_OK) {
g_utf8_to_utf16 (src->device, strlen (src->device), NULL, NULL, NULL); IKsPropertySet *pKs = NULL;
if (!src->video_cap_filter) {
hres = CreateBindCtx (0, &lpbc);
if (SUCCEEDED (hres)) {
hres = hres =
MkParseDisplayName (lpbc, (LPCOLESTR) unidevice, &dwEaten, &videom); capture_pin->QueryInterface (IID_IKsPropertySet, (LPVOID *) & pKs);
if (SUCCEEDED (hres)) { if (SUCCEEDED (hres) && pKs) {
hres = videom->BindToObject (lpbc, NULL, IID_IBaseFilter, DWORD cbReturned;
(LPVOID *) & src->video_cap_filter); GUID pin_category;
videom->Release (); RPC_STATUS rpcstatus;
}
lpbc->Release ();
}
}
g_free (unidevice);
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 = hres =
capture_pin->QueryInterface (IID_IKsPropertySet, (LPVOID *) & pKs); pKs->Get (AMPROPSETID_Pin,
if (SUCCEEDED (hres) && pKs) { AMPROPERTY_PIN_CATEGORY, NULL, 0, &pin_category, sizeof (GUID),
DWORD cbReturned; &cbReturned);
GUID pin_category;
RPC_STATUS rpcstatus;
hres = /* we only want capture pins */
pKs->Get (AMPROPSETID_Pin, if (UuidCompare (&pin_category, (UUID *) & PIN_CATEGORY_CAPTURE,
AMPROPERTY_PIN_CATEGORY, NULL, 0, &pin_category, sizeof (GUID), &rpcstatus) == 0) {
&cbReturned); GstCaps *caps2;
caps2 = gst_dshowvideosrc_getcaps_from_streamcaps (capture_pin,
/* we only want capture pins */ pins_mediatypes);
if (UuidCompare (&pin_category, (UUID *) & PIN_CATEGORY_CAPTURE, if (caps2) {
&rpcstatus) == 0) { gst_caps_append (caps, caps2);
{ } else {
GstCaps *caps = caps2 = gst_dshowvideosrc_getcaps_from_enum_mediatypes (
gst_dshowvideosrc_getcaps_from_streamcaps (src, capture_pin); capture_pin, pins_mediatypes);
if (caps) { if (caps2) {
GST_DEBUG_OBJECT (src, "Caps supported by device: %" GST_PTR_FORMAT, caps); gst_caps_append (caps, caps2);
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->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 static GstStateChangeReturn
@ -509,8 +466,40 @@ gst_dshowvideosrc_start (GstBaseSrc * bsrc)
{ {
HRESULT hres = S_FALSE; HRESULT hres = S_FALSE;
GstDshowVideoSrc *src = GST_DSHOWVIDEOSRC (bsrc); GstDshowVideoSrc *src = GST_DSHOWVIDEOSRC (bsrc);
DshowDeviceEntry *device_entry;
gst_dshowvideosrc_create_capture_filter (src); 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 The filter graph now is created via the IGraphBuilder Interface
@ -600,6 +589,9 @@ gst_dshowvideosrc_start (GstBaseSrc * bsrc)
return TRUE; return TRUE;
error: error:
GST_ELEMENT_ERROR (src, RESOURCE, FAILED,
("Failed to build filter graph"), (NULL));
if (src->dshow_fakesink) { if (src->dshow_fakesink) {
src->dshow_fakesink->Release (); src->dshow_fakesink->Release ();
src->dshow_fakesink = NULL; src->dshow_fakesink = NULL;
@ -858,7 +850,12 @@ gst_dshowvideosrc_stop (GstBaseSrc * bsrc)
g_free (src->device); g_free (src->device);
src->device = NULL; src->device = NULL;
} }
if (src->video_cap_filter) {
src->video_cap_filter->Release ();
src->video_cap_filter = NULL;
}
return TRUE; return TRUE;
} }
@ -912,8 +909,8 @@ gst_dshowvideosrc_create (GstPushSrc * psrc, GstBuffer ** buf)
return GST_FLOW_OK; return GST_FLOW_OK;
} }
static GstCaps * GstCaps *
gst_dshowvideosrc_getcaps_from_streamcaps (GstDshowVideoSrc * src, IPin * pin) gst_dshowvideosrc_getcaps_from_streamcaps (IPin * pin, GList ** pins_mediatypes)
{ {
GstCaps *caps = NULL; GstCaps *caps = NULL;
HRESULT hres = S_OK; HRESULT hres = S_OK;
@ -981,8 +978,9 @@ gst_dshowvideosrc_getcaps_from_streamcaps (GstDshowVideoSrc * src, IPin * pin)
} }
if (mediacaps) { if (mediacaps) {
src->pins_mediatypes = if (pins_mediatypes != NULL) {
g_list_append (src->pins_mediatypes, pin_mediatype); *pins_mediatypes = g_list_append (*pins_mediatypes, pin_mediatype);
}
gst_caps_append (caps, mediacaps); gst_caps_append (caps, mediacaps);
} else { } else {
/* failed to convert dshow caps */ /* failed to convert dshow caps */
@ -1001,8 +999,8 @@ gst_dshowvideosrc_getcaps_from_streamcaps (GstDshowVideoSrc * src, IPin * pin)
return caps; return caps;
} }
static GstCaps * GstCaps *
gst_dshowvideosrc_getcaps_from_enum_mediatypes (GstDshowVideoSrc * src, IPin * pin) gst_dshowvideosrc_getcaps_from_enum_mediatypes (IPin * pin, GList ** pins_mediatypes)
{ {
GstCaps *caps = NULL; GstCaps *caps = NULL;
IEnumMediaTypes *enum_mediatypes = NULL; IEnumMediaTypes *enum_mediatypes = NULL;
@ -1036,8 +1034,9 @@ gst_dshowvideosrc_getcaps_from_enum_mediatypes (GstDshowVideoSrc * src, IPin * p
} }
if (mediacaps) { if (mediacaps) {
src->pins_mediatypes = if (pins_mediatypes != NULL) {
g_list_append (src->pins_mediatypes, pin_mediatype); *pins_mediatypes = g_list_append (*pins_mediatypes, pin_mediatype);
}
gst_caps_append (caps, mediacaps); gst_caps_append (caps, mediacaps);
} else { } else {
/* failed to convert dshow caps */ /* failed to convert dshow caps */

View file

@ -100,5 +100,10 @@ struct _GstDshowVideoSrcClass
GType gst_dshowvideosrc_get_type (void); GType gst_dshowvideosrc_get_type (void);
GstCaps * gst_dshowvideosrc_getcaps_from_capture_filter (IBaseFilter * filter,
GList ** pins_mediatypes);
G_END_DECLS G_END_DECLS
#endif /* __GST_DSHOWVIDEOSRC_H__ */ #endif /* __GST_DSHOWVIDEOSRC_H__ */