mfvideosrc: Add support for DirectShow capture filter
authorSeungha Yang <seungha@centricular.com>
Sat, 5 Nov 2022 16:53:46 +0000 (01:53 +0900)
committerSeungha Yang <seungha@centricular.com>
Mon, 7 Nov 2022 16:33:14 +0000 (01:33 +0900)
Adding DirecShow video capture filter mode, in addition
to existing MediaFoundation and WinRT(UWP) mode, to support
DirectShow only filters (not KS driver compatible)
such as custom virtual camera filters.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/3350>

subprojects/gst-plugins-bad/sys/mediafoundation/gstmfcapturedshow.cpp [new file with mode: 0644]
subprojects/gst-plugins-bad/sys/mediafoundation/gstmfcapturedshow.h [new file with mode: 0644]
subprojects/gst-plugins-bad/sys/mediafoundation/gstmfdevice.cpp
subprojects/gst-plugins-bad/sys/mediafoundation/gstmfsourceobject.cpp
subprojects/gst-plugins-bad/sys/mediafoundation/gstmfsourceobject.h
subprojects/gst-plugins-bad/sys/mediafoundation/gstmfsourcereader.cpp
subprojects/gst-plugins-bad/sys/mediafoundation/gstmfvideosrc.cpp
subprojects/gst-plugins-bad/sys/mediafoundation/meson.build

diff --git a/subprojects/gst-plugins-bad/sys/mediafoundation/gstmfcapturedshow.cpp b/subprojects/gst-plugins-bad/sys/mediafoundation/gstmfcapturedshow.cpp
new file mode 100644 (file)
index 0000000..9bb8f97
--- /dev/null
@@ -0,0 +1,1420 @@
+/* GStreamer
+ * Copyright (C) 2022 Seungha Yang <seungha@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <gst/base/base.h>
+#include <gst/video/video.h>
+#include "gstmfcapturedshow.h"
+#include <string.h>
+#include <wrl.h>
+#include <dshow.h>
+#include <objidl.h>
+#include <string>
+#include <locale>
+#include <codecvt>
+#include <vector>
+#include <algorithm>
+
+/* *INDENT-OFF* */
+using namespace Microsoft::WRL;
+/* *INDENT-ON* */
+
+GST_DEBUG_CATEGORY_EXTERN (gst_mf_source_object_debug);
+#define GST_CAT_DEFAULT gst_mf_source_object_debug
+
+DEFINE_GUID (MEDIASUBTYPE_I420, 0x30323449, 0x0000, 0x0010, 0x80, 0x00, 0x00,
+    0xAA, 0x00, 0x38, 0x9B, 0x71);
+
+/* From qedit.h */
+DEFINE_GUID (CLSID_SampleGrabber, 0xc1f400A0, 0x3f08, 0x11d3, 0x9f, 0x0b, 0x00,
+    0x60, 0x08, 0x03, 0x9e, 0x37);
+
+DEFINE_GUID (CLSID_NullRenderer, 0xc1f400a4, 0x3f08, 0x11d3, 0x9f, 0x0b, 0x00,
+    0x60, 0x08, 0x03, 0x9e, 0x37);
+
+/* *INDENT-OFF* */
+struct DECLSPEC_UUID("0579154a-2b53-4994-b0d0-e773148eff85")
+ISampleGrabberCB : public IUnknown
+{
+  virtual HRESULT STDMETHODCALLTYPE SampleCB(
+      double SampleTime,
+      IMediaSample *pSample) = 0;
+
+  virtual HRESULT STDMETHODCALLTYPE BufferCB(
+      double SampleTime,
+      BYTE *pBuffer,
+      LONG BufferLen) = 0;
+};
+
+struct DECLSPEC_UUID("6b652fff-11fe-4fce-92ad-0266b5d7c78f")
+ISampleGrabber : public IUnknown
+{
+  virtual HRESULT STDMETHODCALLTYPE SetOneShot(
+      BOOL OneShot) = 0;
+
+  virtual HRESULT STDMETHODCALLTYPE SetMediaType(
+      const AM_MEDIA_TYPE *pType) = 0;
+
+  virtual HRESULT STDMETHODCALLTYPE GetConnectedMediaType(
+      AM_MEDIA_TYPE *pType) = 0;
+
+  virtual HRESULT STDMETHODCALLTYPE SetBufferSamples(
+      BOOL BufferThem) = 0;
+
+  virtual HRESULT STDMETHODCALLTYPE GetCurrentBuffer(
+      LONG *pBufferSize,
+      LONG *pBuffer) = 0;
+
+  virtual HRESULT STDMETHODCALLTYPE GetCurrentSample(
+      IMediaSample **ppSample) = 0;
+
+  virtual HRESULT STDMETHODCALLTYPE SetCallback(
+      ISampleGrabberCB *pCallback,
+      LONG WhichMethodToCallback) = 0;
+};
+
+typedef void (*OnBufferCB) (double sample_time,
+                            BYTE * buffer,
+                            LONG len,
+                            gpointer user_data);
+
+class DECLSPEC_UUID("bfae6598-5df6-11ed-9b6a-0242ac120002")
+IGstMFSampleGrabberCB : public ISampleGrabberCB
+{
+public:
+  static HRESULT
+  CreateInstance (OnBufferCB callback, gpointer user_data,
+      IGstMFSampleGrabberCB ** cb)
+  {
+    IGstMFSampleGrabberCB *self =
+        new IGstMFSampleGrabberCB (callback, user_data);
+
+    if (!self)
+      return E_OUTOFMEMORY;
+
+    *cb = self;
+
+    return S_OK;
+  }
+
+  STDMETHODIMP_ (ULONG)
+  AddRef (void)
+  {
+    return InterlockedIncrement (&ref_count_);
+  }
+
+  STDMETHODIMP_ (ULONG)
+  Release (void)
+  {
+    ULONG ref_count;
+
+    ref_count = InterlockedDecrement (&ref_count_);
+
+    if (ref_count == 0)
+      delete this;
+
+    return ref_count;
+  }
+
+  STDMETHODIMP
+  QueryInterface (REFIID riid, void ** object)
+  {
+    if (riid == __uuidof (IUnknown)) {
+      *object = static_cast<IUnknown *>
+          (static_cast<IGstMFSampleGrabberCB *> (this));
+    } else if (riid == __uuidof (ISampleGrabberCB)) {
+      *object = static_cast<ISampleGrabberCB *>
+          (static_cast<IGstMFSampleGrabberCB *> (this));
+    } else if (riid == __uuidof (IGstMFSampleGrabberCB)) {
+      *object = this;
+    } else {
+      *object = nullptr;
+      return E_NOINTERFACE;
+    }
+
+    AddRef ();
+
+    return S_OK;
+  }
+
+  STDMETHODIMP
+  SampleCB (double SampleTime, IMediaSample *pSample)
+  {
+    return E_NOTIMPL;
+  }
+
+  STDMETHODIMP
+  BufferCB (double SampleTime, BYTE *pBuffer, LONG BufferLen)
+  {
+    if (callback_)
+      callback_ (SampleTime, pBuffer, BufferLen, user_data_);
+
+    return E_NOTIMPL;
+  }
+
+private:
+  IGstMFSampleGrabberCB (OnBufferCB callback, gpointer user_data)
+    : callback_ (callback), user_data_ (user_data), ref_count_ (1)
+  {
+  }
+
+  virtual ~IGstMFSampleGrabberCB (void)
+  {
+  }
+
+private:
+  OnBufferCB callback_;
+  gpointer user_data_;
+  ULONG ref_count_;
+};
+
+struct GStMFDShowMoniker
+{
+  GStMFDShowMoniker ()
+  {
+  }
+
+  GStMFDShowMoniker (ComPtr<IMoniker> m, const std::string &d, const std::string & n,
+      const std::string p, guint i)
+  {
+    moniker = m;
+    desc = d;
+    name = n;
+    path = p;
+    index = i;
+  }
+
+  GStMFDShowMoniker (const GStMFDShowMoniker & other)
+  {
+    moniker = other.moniker;
+    desc = other.desc;
+    name = other.name;
+    path = other.path;
+    index = other.index;
+  }
+
+  ComPtr<IMoniker> moniker;
+  std::string desc;
+  std::string name;
+  std::string path;
+  guint index = 0;
+};
+
+static void
+ClearMediaType (AM_MEDIA_TYPE * type)
+{
+  if (type->cbFormat && type->pbFormat)
+    CoTaskMemFree (type->pbFormat);
+
+  if (type->pUnk)
+    type->pUnk->Release ();
+}
+
+static void
+FreeMediaType (AM_MEDIA_TYPE * type)
+{
+  if (!type)
+    return;
+
+  ClearMediaType (type);
+  CoTaskMemFree (type);
+}
+
+static inline std::string
+convert_to_string (const wchar_t * wstr)
+{
+  std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t> conv;
+
+  return conv.to_bytes (wstr);
+}
+
+static HRESULT
+FindFirstPin (IBaseFilter * filter, PIN_DIRECTION dir, IPin ** pin)
+{
+  ComPtr < IEnumPins > enum_pins;
+  HRESULT hr;
+  PIN_DIRECTION direction;
+
+  hr = filter->EnumPins (&enum_pins);
+  if (!gst_mf_result (hr))
+    return hr;
+
+  do {
+    ComPtr < IPin > tmp;
+
+    hr = enum_pins->Next (1, &tmp, nullptr);
+    if (hr != S_OK)
+      return hr;
+
+    hr = tmp->QueryDirection (&direction);
+    if (!gst_mf_result (hr) || direction != dir)
+      continue;
+
+    *pin = tmp.Detach ();
+    return S_OK;
+  } while (hr == S_OK);
+
+  return E_FAIL;
+}
+
+struct GstMFDShowPinInfo
+{
+  GstMFDShowPinInfo ()
+  {
+  }
+
+  GstMFDShowPinInfo (const std::wstring & id, GstCaps *c, gint i,
+      gboolean top_down)
+  {
+    pin_id = id;
+    caps = c;
+    index = i;
+    top_down_image = top_down;
+  }
+
+  GstMFDShowPinInfo (const GstMFDShowPinInfo & other)
+  {
+    pin_id = other.pin_id;
+    if (other.caps)
+      caps = gst_caps_ref (other.caps);
+    index = other.index;
+    top_down_image = other.top_down_image;
+  }
+
+  ~GstMFDShowPinInfo ()
+  {
+    if (caps)
+      gst_caps_unref (caps);
+  }
+
+  bool operator< (const GstMFDShowPinInfo & other)
+  {
+    return gst_mf_source_object_caps_compare (caps, other.caps) < 0;
+  }
+
+  GstMFDShowPinInfo & operator= (const GstMFDShowPinInfo & other)
+  {
+    gst_clear_caps (&caps);
+
+    pin_id = other.pin_id;
+    if (other.caps)
+      caps = gst_caps_ref (other.caps);
+    index = other.index;
+    top_down_image = other.top_down_image;
+
+    return *this;
+  }
+
+  std::wstring pin_id;
+  GstCaps *caps = nullptr;
+  gint index = 0;
+  gboolean top_down_image = TRUE;
+};
+
+struct GstMFCaptureDShowInner
+{
+  ~GstMFCaptureDShowInner()
+  {
+    if (grabber)
+      grabber->SetCallback (nullptr, 0);
+  }
+
+  std::vector<GstMFDShowPinInfo> pin_infos;
+  ComPtr <IFilterGraph> graph;
+  ComPtr <IMediaControl> control;
+  ComPtr <IBaseFilter> capture;
+  ComPtr <ISampleGrabber> grabber;
+  ComPtr <IBaseFilter> fakesink;
+  GstMFDShowPinInfo selected_pin_info;
+};
+/* *INDENT-ON* */
+
+enum CAPTURE_STATE
+{
+  CAPTURE_STATE_STOPPED,
+  CAPTURE_STATE_RUNNING,
+  CAPTURE_STATE_ERROR,
+};
+
+struct _GstMFCaptureDShow
+{
+  GstMFSourceObject parent;
+
+  GThread *thread;
+  GMutex lock;
+  GCond cond;
+  GMainContext *context;
+  GMainLoop *loop;
+
+  GstMFCaptureDShowInner *inner;
+  GstBufferPool *pool;
+
+  /* protected by lock */
+  GQueue sample_queue;
+  CAPTURE_STATE state;
+
+  GstCaps *supported_caps;
+  GstCaps *selected_caps;
+  GstVideoInfo info;
+
+  gboolean top_down_image;
+  gboolean flushing;
+};
+
+static void gst_mf_capture_dshow_constructed (GObject * object);
+static void gst_mf_capture_dshow_finalize (GObject * object);
+
+static gboolean gst_mf_capture_dshow_start (GstMFSourceObject * object);
+static gboolean gst_mf_capture_dshow_stop (GstMFSourceObject * object);
+static GstFlowReturn
+gst_mf_capture_dshow_get_sample (GstMFSourceObject * object,
+    GstSample ** sample);
+static gboolean gst_mf_capture_dshow_unlock (GstMFSourceObject * object);
+static gboolean gst_mf_capture_dshow_unlock_stop (GstMFSourceObject * object);
+static GstCaps *gst_mf_capture_dshow_get_caps (GstMFSourceObject * object);
+static gboolean gst_mf_capture_dshow_set_caps (GstMFSourceObject * object,
+    GstCaps * caps);
+
+static gpointer gst_mf_capture_dshow_thread_func (GstMFCaptureDShow * self);
+static void gst_mf_capture_dshow_on_buffer (double sample_time,
+    BYTE * buffer, LONG buffer_len, gpointer user_data);
+
+#define gst_mf_capture_dshow_parent_class parent_class
+G_DEFINE_TYPE (GstMFCaptureDShow, gst_mf_capture_dshow,
+    GST_TYPE_MF_SOURCE_OBJECT);
+
+static void
+gst_mf_capture_dshow_class_init (GstMFCaptureDShowClass * klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+  GstMFSourceObjectClass *source_class = GST_MF_SOURCE_OBJECT_CLASS (klass);
+
+  gobject_class->constructed = gst_mf_capture_dshow_constructed;
+  gobject_class->finalize = gst_mf_capture_dshow_finalize;
+
+  source_class->start = GST_DEBUG_FUNCPTR (gst_mf_capture_dshow_start);
+  source_class->stop = GST_DEBUG_FUNCPTR (gst_mf_capture_dshow_stop);
+  source_class->get_sample =
+      GST_DEBUG_FUNCPTR (gst_mf_capture_dshow_get_sample);
+  source_class->unlock = GST_DEBUG_FUNCPTR (gst_mf_capture_dshow_unlock);
+  source_class->unlock_stop =
+      GST_DEBUG_FUNCPTR (gst_mf_capture_dshow_unlock_stop);
+  source_class->get_caps = GST_DEBUG_FUNCPTR (gst_mf_capture_dshow_get_caps);
+  source_class->set_caps = GST_DEBUG_FUNCPTR (gst_mf_capture_dshow_set_caps);
+}
+
+static void
+gst_mf_capture_dshow_init (GstMFCaptureDShow * self)
+{
+  g_mutex_init (&self->lock);
+  g_cond_init (&self->cond);
+  g_queue_init (&self->sample_queue);
+
+  self->state = CAPTURE_STATE_STOPPED;
+}
+
+static void
+gst_mf_capture_dshow_constructed (GObject * object)
+{
+  GstMFCaptureDShow *self = GST_MF_CAPTURE_DSHOW (object);
+
+  self->context = g_main_context_new ();
+  self->loop = g_main_loop_new (self->context, FALSE);
+
+  /* Create a new thread to ensure that COM thread can be MTA thread */
+  g_mutex_lock (&self->lock);
+  self->thread = g_thread_new ("GstMFCaptureDShow",
+      (GThreadFunc) gst_mf_capture_dshow_thread_func, self);
+  while (!g_main_loop_is_running (self->loop))
+    g_cond_wait (&self->cond, &self->lock);
+  g_mutex_unlock (&self->lock);
+
+  G_OBJECT_CLASS (parent_class)->constructed (object);
+}
+
+static void
+gst_mf_capture_dshow_finalize (GObject * object)
+{
+  GstMFCaptureDShow *self = GST_MF_CAPTURE_DSHOW (object);
+
+  g_main_loop_quit (self->loop);
+  g_thread_join (self->thread);
+  g_main_loop_unref (self->loop);
+  g_main_context_unref (self->context);
+
+  gst_clear_caps (&self->supported_caps);
+  gst_clear_caps (&self->selected_caps);
+  g_queue_clear_full (&self->sample_queue, (GDestroyNotify) gst_sample_unref);
+  g_mutex_clear (&self->lock);
+  g_cond_clear (&self->cond);
+
+  G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static gboolean
+gst_mf_capture_dshow_start (GstMFSourceObject * object)
+{
+  GstMFCaptureDShow *self = GST_MF_CAPTURE_DSHOW (object);
+  GstMFCaptureDShowInner *inner = self->inner;
+  HRESULT hr;
+  ComPtr < IAMStreamConfig > config;
+  ComPtr < IBaseFilter > grabber;
+  ComPtr < IPin > output;
+  ComPtr < IPin > input;
+  AM_MEDIA_TYPE *type = nullptr;
+  VIDEO_STREAM_CONFIG_CAPS config_caps;
+  GstMFDShowPinInfo & selected = inner->selected_pin_info;
+  GstStructure *pool_config;
+
+  if (!selected.caps) {
+    GST_ERROR_OBJECT (self, "No selected pin");
+    return FALSE;
+  }
+
+  gst_video_info_from_caps (&self->info, selected.caps);
+  gst_clear_caps (&self->selected_caps);
+  self->selected_caps = gst_caps_ref (selected.caps);
+  self->top_down_image = selected.top_down_image;
+
+  /* Get pin and mediainfo of capture filter */
+  hr = inner->capture->FindPin (selected.pin_id.c_str (), &output);
+  if (!gst_mf_result (hr)) {
+    GST_ERROR_OBJECT (self, "Could not find output pin of capture filter");
+    return FALSE;
+  }
+
+  hr = output.As (&config);
+  if (!gst_mf_result (hr)) {
+    GST_ERROR_OBJECT (self, "Could not get IAMStreamConfig interface");
+    return FALSE;
+  }
+
+  hr = config->GetStreamCaps (selected.index, &type, (BYTE *) & config_caps);
+  if (!gst_mf_result (hr) || !type) {
+    GST_ERROR_OBJECT (self, "Could not get type from pin");
+    return FALSE;
+  }
+
+  hr = inner->grabber.As (&grabber);
+  if (!gst_mf_result (hr)) {
+    FreeMediaType (type);
+    return FALSE;
+  }
+
+  /* Find input pint of grabber */
+  hr = FindFirstPin (grabber.Get (), PINDIR_INPUT, &input);
+  if (!gst_mf_result (hr)) {
+    GST_WARNING_OBJECT (self, "Couldn't get input pin from grabber");
+    FreeMediaType (type);
+    return FALSE;
+  }
+
+  hr = inner->graph->ConnectDirect (output.Get (), input.Get (), type);
+  if (!gst_mf_result (hr)) {
+    GST_WARNING_OBJECT (self, "Could not connect capture and grabber");
+    FreeMediaType (type);
+    return FALSE;
+  }
+
+  /* Link grabber and fakesink here */
+  input = nullptr;
+  output = nullptr;
+  hr = FindFirstPin (grabber.Get (), PINDIR_OUTPUT, &output);
+  if (!gst_mf_result (hr)) {
+    GST_WARNING_OBJECT (self, "Couldn't get output pin from grabber");
+    FreeMediaType (type);
+    return FALSE;
+  }
+
+  hr = FindFirstPin (inner->fakesink.Get (), PINDIR_INPUT, &input);
+  if (!gst_mf_result (hr)) {
+    GST_WARNING_OBJECT (self, "Couldn't get input pin from fakesink");
+    FreeMediaType (type);
+    return FALSE;
+  }
+
+  hr = inner->graph->ConnectDirect (output.Get (), input.Get (), type);
+  FreeMediaType (type);
+  if (!gst_mf_result (hr)) {
+    GST_WARNING_OBJECT (self, "Could not connect grabber and fakesink");
+    return FALSE;
+  }
+
+  self->pool = gst_video_buffer_pool_new ();
+  pool_config = gst_buffer_pool_get_config (self->pool);
+  gst_buffer_pool_config_add_option (pool_config,
+      GST_BUFFER_POOL_OPTION_VIDEO_META);
+  gst_buffer_pool_config_set_params (pool_config, selected.caps,
+      GST_VIDEO_INFO_SIZE (&self->info), 0, 0);
+  if (!gst_buffer_pool_set_config (self->pool, pool_config)) {
+    GST_ERROR_OBJECT (self, "Couldn not set buffer pool config");
+    gst_clear_object (&self->pool);
+    return FALSE;
+  }
+
+  if (!gst_buffer_pool_set_active (self->pool, TRUE)) {
+    GST_ERROR_OBJECT (self, "Couldn't activate pool");
+    gst_clear_object (&self->pool);
+    return FALSE;
+  }
+
+  self->state = CAPTURE_STATE_RUNNING;
+
+  hr = inner->control->Run ();
+  if (!gst_mf_result (hr)) {
+    GST_ERROR_OBJECT (self, "Couldn't start graph");
+    self->state = CAPTURE_STATE_ERROR;
+    g_cond_broadcast (&self->cond);
+    gst_buffer_pool_set_active (self->pool, FALSE);
+    gst_clear_object (&self->pool);
+    return FALSE;
+  }
+
+  return TRUE;
+}
+
+static gboolean
+gst_mf_capture_dshow_stop (GstMFSourceObject * object)
+{
+  GstMFCaptureDShow *self = GST_MF_CAPTURE_DSHOW (object);
+  GstMFCaptureDShowInner *inner = self->inner;
+
+  GST_DEBUG_OBJECT (self, "Stop");
+
+  g_mutex_lock (&self->lock);
+  self->state = CAPTURE_STATE_STOPPED;
+  g_cond_broadcast (&self->cond);
+  g_mutex_unlock (&self->lock);
+
+  if (inner->control)
+    inner->control->Stop ();
+
+  if (self->pool)
+    gst_buffer_pool_set_active (self->pool, FALSE);
+
+  return TRUE;
+}
+
+static GstFlowReturn
+gst_mf_capture_dshow_get_sample (GstMFSourceObject * object,
+    GstSample ** sample)
+{
+  GstMFCaptureDShow *self = GST_MF_CAPTURE_DSHOW (object);
+
+  g_mutex_lock (&self->lock);
+  while (g_queue_is_empty (&self->sample_queue) && !self->flushing &&
+      self->state == CAPTURE_STATE_RUNNING) {
+    g_cond_wait (&self->cond, &self->lock);
+  }
+
+  if (self->flushing) {
+    g_mutex_unlock (&self->lock);
+    return GST_FLOW_FLUSHING;
+  }
+
+  if (self->state == CAPTURE_STATE_ERROR) {
+    g_mutex_unlock (&self->lock);
+    return GST_FLOW_ERROR;
+  }
+
+  *sample = (GstSample *) g_queue_pop_head (&self->sample_queue);
+  g_mutex_unlock (&self->lock);
+
+  return GST_FLOW_OK;
+}
+
+static gboolean
+gst_mf_capture_dshow_unlock (GstMFSourceObject * object)
+{
+  GstMFCaptureDShow *self = GST_MF_CAPTURE_DSHOW (object);
+
+  GST_DEBUG_OBJECT (self, "Unlock");
+
+  g_mutex_lock (&self->lock);
+  self->flushing = TRUE;
+  g_cond_broadcast (&self->cond);
+  g_mutex_unlock (&self->lock);
+
+  return TRUE;
+}
+
+static gboolean
+gst_mf_capture_dshow_unlock_stop (GstMFSourceObject * object)
+{
+  GstMFCaptureDShow *self = GST_MF_CAPTURE_DSHOW (object);
+
+  GST_DEBUG_OBJECT (self, "Unlock Stop");
+
+  g_mutex_lock (&self->lock);
+  self->flushing = FALSE;
+  g_cond_broadcast (&self->cond);
+  g_mutex_unlock (&self->lock);
+
+  return TRUE;
+}
+
+static GstCaps *
+gst_mf_capture_dshow_get_caps (GstMFSourceObject * object)
+{
+  GstMFCaptureDShow *self = GST_MF_CAPTURE_DSHOW (object);
+  GstCaps *caps = nullptr;
+
+  g_mutex_lock (&self->lock);
+  if (self->selected_caps) {
+    caps = gst_caps_ref (self->selected_caps);
+  } else if (self->supported_caps) {
+    caps = gst_caps_ref (self->supported_caps);
+  }
+  g_mutex_unlock (&self->lock);
+
+  return caps;
+}
+
+static gboolean
+gst_mf_capture_dshow_set_caps (GstMFSourceObject * object, GstCaps * caps)
+{
+  GstMFCaptureDShow *self = GST_MF_CAPTURE_DSHOW (object);
+  GstMFCaptureDShowInner *inner = self->inner;
+  GstMFDShowPinInfo pin_info;
+
+  /* *INDENT-OFF* */
+  for (const auto & iter: inner->pin_infos) {
+    if (gst_caps_can_intersect (iter.caps, caps)) {
+      pin_info = iter;
+      break;
+    }
+  }
+  /* *INDENT-ON* */
+
+  if (!pin_info.caps) {
+    GST_ERROR_OBJECT (self, "Could not determine target pin with given caps %"
+        GST_PTR_FORMAT, caps);
+    return FALSE;
+  }
+
+  GST_DEBUG_OBJECT (self, "Selecting caps %" GST_PTR_FORMAT " for caps %"
+      GST_PTR_FORMAT, pin_info.caps, caps);
+
+  inner->selected_pin_info = pin_info;
+
+  return TRUE;
+}
+
+static gboolean
+gst_mf_capture_dshow_main_loop_running_cb (GstMFCaptureDShow * self)
+{
+  GST_INFO_OBJECT (self, "Main loop running now");
+
+  g_mutex_lock (&self->lock);
+  g_cond_signal (&self->cond);
+  g_mutex_unlock (&self->lock);
+
+  return G_SOURCE_REMOVE;
+}
+
+static GstVideoFormat
+subtype_to_format (REFGUID subtype)
+{
+  if (subtype == MEDIASUBTYPE_MJPG)
+    return GST_VIDEO_FORMAT_ENCODED;
+  else if (subtype == MEDIASUBTYPE_RGB555)
+    return GST_VIDEO_FORMAT_RGB15;
+  else if (subtype == MEDIASUBTYPE_RGB565)
+    return GST_VIDEO_FORMAT_RGB16;
+  else if (subtype == MEDIASUBTYPE_RGB24)
+    return GST_VIDEO_FORMAT_BGR;
+  else if (subtype == MEDIASUBTYPE_RGB32)
+    return GST_VIDEO_FORMAT_BGRx;
+  else if (subtype == MEDIASUBTYPE_ARGB32)
+    return GST_VIDEO_FORMAT_BGRA;
+  else if (subtype == MEDIASUBTYPE_AYUV)
+    return GST_VIDEO_FORMAT_VUYA;
+  else if (subtype == MEDIASUBTYPE_YUY2)
+    return GST_VIDEO_FORMAT_YUY2;
+  else if (subtype == MEDIASUBTYPE_UYVY)
+    return GST_VIDEO_FORMAT_UYVY;
+  else if (subtype == MEDIASUBTYPE_YV12)
+    return GST_VIDEO_FORMAT_YV12;
+  else if (subtype == MEDIASUBTYPE_NV12)
+    return GST_VIDEO_FORMAT_NV12;
+  else if (subtype == MEDIASUBTYPE_I420)
+    return GST_VIDEO_FORMAT_I420;
+  else if (subtype == MEDIASUBTYPE_IYUV)
+    return GST_VIDEO_FORMAT_I420;
+
+  return GST_VIDEO_FORMAT_UNKNOWN;
+}
+
+static GstCaps *
+media_type_to_caps (AM_MEDIA_TYPE * type, gboolean * top_down_image)
+{
+  gint fps_n = 0;
+  gint fps_d = 1;
+  GstVideoFormat format;
+  VIDEOINFOHEADER *header;
+  GstCaps *caps;
+
+  if (!type)
+    return nullptr;
+
+  if (type->majortype != MEDIATYPE_Video ||
+      type->formattype != FORMAT_VideoInfo) {
+    return nullptr;
+  }
+
+  format = subtype_to_format (type->subtype);
+  if (format == GST_VIDEO_FORMAT_UNKNOWN ||
+      /* TODO: support jpeg */
+      format == GST_VIDEO_FORMAT_ENCODED) {
+    return nullptr;
+  }
+
+  if (!type->pbFormat || type->cbFormat < sizeof (VIDEOINFOHEADER))
+    return nullptr;
+
+  header = (VIDEOINFOHEADER *) type->pbFormat;
+  if (header->bmiHeader.biWidth <= 0 || header->bmiHeader.biHeight <= 0) {
+    return nullptr;
+  }
+
+  if (header->AvgTimePerFrame > 0) {
+    /* 100ns unit */
+    gst_video_guess_framerate ((GstClockTime) header->AvgTimePerFrame * 100,
+        &fps_n, &fps_d);
+  }
+
+  if (top_down_image) {
+    const GstVideoFormatInfo *finfo = gst_video_format_get_info (format);
+    if (GST_VIDEO_FORMAT_INFO_IS_RGB (finfo) && header->bmiHeader.biHeight < 0) {
+      *top_down_image = FALSE;
+    } else {
+      *top_down_image = TRUE;
+    }
+  }
+
+  caps = gst_caps_new_empty_simple ("video/x-raw");
+  gst_caps_set_simple (caps, "format", G_TYPE_STRING,
+      gst_video_format_to_string (format),
+      "width", G_TYPE_INT, (gint) header->bmiHeader.biWidth,
+      "height", G_TYPE_INT, (gint) header->bmiHeader.biHeight,
+      "framerate", GST_TYPE_FRACTION, fps_n, fps_d, nullptr);
+
+  return caps;
+}
+
+static gboolean
+gst_mf_capture_dshow_open (GstMFCaptureDShow * self, IMoniker * moniker)
+{
+  ComPtr < IBaseFilter > capture;
+  ComPtr < IEnumPins > pin_list;
+  ComPtr < IFilterGraph > graph;
+  ComPtr < IMediaFilter > filter;
+  ComPtr < IMediaControl > control;
+  GstMFCaptureDShowInner *inner = self->inner;
+  HRESULT hr;
+  PIN_DIRECTION direction;
+
+  hr = CoCreateInstance (CLSID_FilterGraph, nullptr, CLSCTX_INPROC_SERVER,
+      IID_PPV_ARGS (&graph));
+  if (!gst_mf_result (hr)) {
+    GST_WARNING_OBJECT (self, "Could not get IGraphBuilder interface");
+    return FALSE;
+  }
+
+  hr = graph.As (&filter);
+  if (!gst_mf_result (hr)) {
+    GST_WARNING_OBJECT (self, "Could not get IMediaFilter interface");
+    return FALSE;
+  }
+
+  /* Make graph work as if sync=false */
+  filter->SetSyncSource (nullptr);
+
+  hr = graph.As (&control);
+  if (!gst_mf_result (hr)) {
+    GST_WARNING_OBJECT (self, "Could not get IMediaControl interface");
+    return FALSE;
+  }
+
+  hr = moniker->BindToObject (nullptr, nullptr, IID_PPV_ARGS (&capture));
+  if (!gst_mf_result (hr)) {
+    GST_WARNING_OBJECT (self, "Could not bind capture object");
+    return FALSE;
+  }
+
+  hr = graph->AddFilter (capture.Get (), L"CaptureFilter");
+  if (!gst_mf_result (hr)) {
+    GST_WARNING_OBJECT (self, "Could not add capture filter to graph");
+    return FALSE;
+  }
+
+  ComPtr < IBaseFilter > grabber;
+  hr = inner->grabber.As (&grabber);
+  if (!gst_mf_result (hr)) {
+    GST_ERROR_OBJECT (self, "Could not get IBaseFilter interface from grabber");
+    return FALSE;
+  }
+
+  hr = graph->AddFilter (grabber.Get (), L"SampleGrabber");
+  if (!gst_mf_result (hr)) {
+    GST_ERROR_OBJECT (self, "Could not add grabber filter to graph");
+    return FALSE;
+  }
+
+  hr = graph->AddFilter (inner->fakesink.Get (), L"FakeSink");
+  if (!gst_mf_result (hr)) {
+    GST_ERROR_OBJECT (self, "Could not add fakesink filter to graph");
+    return FALSE;
+  }
+
+  hr = capture->EnumPins (&pin_list);
+  if (!gst_mf_result (hr)) {
+    GST_WARNING_OBJECT (self, "Could not get pin enumerator");
+    return FALSE;
+  }
+
+  /* Enumerates pins and media types */
+  do {
+    ComPtr < IPin > pin;
+    std::wstring id;
+    std::string id_str;
+    WCHAR *pin_id = nullptr;
+    GUID category = GUID_NULL;
+    DWORD returned = 0;
+
+    hr = pin_list->Next (1, &pin, nullptr);
+    if (hr != S_OK)
+      break;
+
+    hr = pin->QueryDirection (&direction);
+    if (!gst_mf_result (hr) || direction != PINDIR_OUTPUT)
+      continue;
+
+    hr = pin->QueryId (&pin_id);
+    if (!gst_mf_result (hr) || !pin_id)
+      continue;
+
+    id_str = convert_to_string (pin_id);
+    id = pin_id;
+    CoTaskMemFree (pin_id);
+
+    ComPtr < IKsPropertySet > prop;
+    hr = pin.As (&prop);
+    if (!gst_mf_result (hr))
+      continue;
+
+    prop->Get (AMPROPSETID_Pin, AMPROPERTY_PIN_CATEGORY, nullptr, 0,
+        &category, sizeof (GUID), &returned);
+
+    if (category == GUID_NULL) {
+      GST_INFO_OBJECT (self, "Unknown category, keep checking");
+    } else if (category == PIN_CATEGORY_CAPTURE) {
+      GST_INFO_OBJECT (self, "Found capture pin");
+    } else if (category == PIN_CATEGORY_PREVIEW) {
+      GST_INFO_OBJECT (self, "Found preview pin");
+    } else {
+      continue;
+    }
+
+    ComPtr < IAMStreamConfig > config;
+    hr = pin.As (&config);
+    if (!gst_mf_result (hr))
+      continue;
+
+    int count = 0;
+    int size = 0;
+    hr = config->GetNumberOfCapabilities (&count, &size);
+    if (!gst_mf_result (hr) || count == 0 ||
+        size != sizeof (VIDEO_STREAM_CONFIG_CAPS)) {
+      continue;
+    }
+
+    for (int i = 0; i < count; i++) {
+      AM_MEDIA_TYPE *type = nullptr;
+      VIDEO_STREAM_CONFIG_CAPS config_caps;
+      GstCaps *caps;
+      gboolean top_down = TRUE;
+
+      hr = config->GetStreamCaps (i, &type, (BYTE *) & config_caps);
+      if (!gst_mf_result (hr) || !type) {
+        GST_WARNING_OBJECT (self, "Couldn't get caps for index %d", i);
+        continue;
+      }
+
+      caps = media_type_to_caps (type, &top_down);
+      if (!caps) {
+        GST_WARNING_OBJECT (self,
+            "Couldn't convert type to caps for index %d", i);
+        FreeMediaType (type);
+        continue;
+      }
+
+      GST_LOG_OBJECT (self, "Adding caps for pin id \"%s\", index %d, caps %"
+          GST_PTR_FORMAT, id_str.c_str (), i, caps);
+
+      inner->pin_infos.emplace_back (id, caps, i, top_down);
+      FreeMediaType (type);
+    }
+  } while (hr == S_OK);
+
+  if (inner->pin_infos.empty ()) {
+    GST_WARNING_OBJECT (self, "Couldn't get pin information");
+    return FALSE;
+  }
+
+  std::sort (inner->pin_infos.begin (), inner->pin_infos.end ());
+
+  self->supported_caps = gst_caps_new_empty ();
+  /* *INDENT-OFF* */
+  for (const auto & iter : inner->pin_infos)
+    gst_caps_append (self->supported_caps, gst_caps_ref (iter.caps));
+  /* *INDENT-ON* */
+
+  GST_DEBUG_OBJECT (self, "Available output caps %" GST_PTR_FORMAT,
+      self->supported_caps);
+
+  inner->graph = graph;
+  inner->control = control;
+  inner->capture = capture;
+
+  return TRUE;
+}
+
+static gboolean
+gst_mf_dshow_enum_device (GstMFCaptureDShow * self,
+    GstMFSourceType source_type, std::vector < GStMFDShowMoniker > &dev_list)
+{
+  HRESULT hr;
+  ComPtr < ICreateDevEnum > dev_enum;
+  ComPtr < IEnumMoniker > enum_moniker;
+
+  hr = CoCreateInstance (CLSID_SystemDeviceEnum, nullptr, CLSCTX_INPROC_SERVER,
+      IID_PPV_ARGS (&dev_enum));
+  if (!gst_mf_result (hr))
+    return FALSE;
+
+  switch (source_type) {
+    case GST_MF_SOURCE_TYPE_VIDEO:
+      /* directshow native filter only */
+      hr = dev_enum->CreateClassEnumerator (CLSID_VideoInputDeviceCategory,
+          &enum_moniker, CDEF_DEVMON_FILTER);
+      break;
+    default:
+      GST_ERROR_OBJECT (self, "Unknown source type %d", source_type);
+      return FALSE;
+  }
+
+  if (!gst_mf_result (hr))
+    return FALSE;
+
+  for (guint i = 0;; i++) {
+    ComPtr < IMoniker > moniker;
+    ComPtr < IPropertyBag > prop_bag;
+    WCHAR *display_name = nullptr;
+    VARIANT var;
+    std::string desc;
+    std::string name;
+    std::string path;
+
+    hr = enum_moniker->Next (1, &moniker, nullptr);
+    if (hr != S_OK)
+      break;
+
+    hr = moniker->BindToStorage (nullptr, nullptr, IID_PPV_ARGS (&prop_bag));
+    if (!gst_mf_result (hr))
+      continue;
+
+    VariantInit (&var);
+    hr = prop_bag->Read (L"Description", &var, nullptr);
+    if (SUCCEEDED (hr)) {
+      desc = convert_to_string (var.bstrVal);
+      VariantClear (&var);
+    }
+
+    hr = prop_bag->Read (L"FriendlyName", &var, nullptr);
+    if (SUCCEEDED (hr)) {
+      name = convert_to_string (var.bstrVal);
+      VariantClear (&var);
+    }
+
+    if (desc.empty () && name.empty ()) {
+      desc = "Unknown capture device";
+      name = "Unknown capture device";
+      GST_WARNING_OBJECT (self, "Unknown device desc/name");
+    }
+
+    if (desc.empty ())
+      desc = name;
+    else if (name.empty ())
+      name = desc;
+
+    hr = moniker->GetDisplayName (nullptr, nullptr, &display_name);
+    if (!gst_mf_result (hr) || !display_name)
+      continue;
+
+    path = convert_to_string (display_name);
+    CoTaskMemFree (display_name);
+
+    dev_list.push_back ( {
+        moniker, desc, name, path, i}
+    );
+  }
+
+  if (dev_list.empty ())
+    return FALSE;
+
+  return TRUE;
+}
+
+/* *INDENT-OFF* */
+static gpointer
+gst_mf_capture_dshow_thread_func (GstMFCaptureDShow * self)
+{
+  GstMFSourceObject *object = GST_MF_SOURCE_OBJECT (self);
+  GSource *source;
+
+  CoInitializeEx (nullptr, COINIT_MULTITHREADED);
+
+  g_main_context_push_thread_default (self->context);
+
+  self->inner = new GstMFCaptureDShowInner ();
+
+  source = g_idle_source_new ();
+  g_source_set_callback (source,
+      (GSourceFunc) gst_mf_capture_dshow_main_loop_running_cb, self, nullptr);
+  g_source_attach (source, self->context);
+  g_source_unref (source);
+
+  {
+    std::vector<GStMFDShowMoniker> device_list;
+    GStMFDShowMoniker selected;
+    HRESULT hr;
+
+    if (!gst_mf_dshow_enum_device (self, object->source_type, device_list)) {
+      GST_WARNING_OBJECT (self, "No available video capture device");
+      goto run_loop;
+    }
+
+    for (const auto & iter : device_list) {
+      GST_DEBUG_OBJECT (self, "device %d, name: \"%s\", path: \"%s\"",
+          iter.index, iter.name.c_str (), iter.path.c_str ());
+    }
+
+    GST_DEBUG_OBJECT (self,
+        "Requested device index: %d, name: \"%s\", path \"%s\"",
+        object->device_index, GST_STR_NULL (object->device_name),
+        GST_STR_NULL (object->device_path));
+
+    for (const auto & iter : device_list) {
+      bool match = false;
+
+      if (object->device_path) {
+        match = (g_ascii_strcasecmp (iter.path.c_str (),
+            object->device_path) == 0);
+      } else if (object->device_name) {
+        match = (g_ascii_strcasecmp (iter.name.c_str (),
+            object->device_name) == 0);
+      } else if (object->device_index >= 0) {
+        match = iter.index == (guint) object->device_index;
+      } else {
+        /* pick the first entry */
+        match = TRUE;
+      }
+
+      if (match) {
+        selected = iter;
+        break;
+      }
+    }
+
+    if (selected.moniker) {
+      ComPtr<ISampleGrabber> grabber;
+      ComPtr<IBaseFilter> fakesink;
+      ComPtr<IGstMFSampleGrabberCB> cb;
+
+      /* Make sure ISampleGrabber and NullRenderer are available,
+       * MS may want to drop the the legacy implementations */
+      hr = CoCreateInstance (CLSID_SampleGrabber, nullptr, CLSCTX_INPROC_SERVER,
+          IID_PPV_ARGS (&grabber));
+      if (!gst_mf_result (hr)) {
+        GST_WARNING_OBJECT (self, "ISampleGrabber interface is not available");
+        goto run_loop;
+      }
+
+      grabber->SetBufferSamples (FALSE);
+      grabber->SetOneShot (FALSE);
+
+      hr = IGstMFSampleGrabberCB::CreateInstance (gst_mf_capture_dshow_on_buffer,
+        self, &cb);
+      if (!gst_mf_result (hr)) {
+        GST_WARNING_OBJECT (self, "Could not create callback object");
+        goto run_loop;
+      }
+
+      hr = grabber->SetCallback (cb.Get (), 1);
+      if (!gst_mf_result (hr)) {
+        GST_WARNING_OBJECT (self, "Could not set sample callback");
+        goto run_loop;
+      }
+
+      hr = CoCreateInstance (CLSID_NullRenderer, nullptr, CLSCTX_INPROC_SERVER,
+          IID_PPV_ARGS (&fakesink));
+      if (!gst_mf_result (hr)) {
+        GST_WARNING_OBJECT (self, "NullRenderer interface is not available");
+        goto run_loop;
+      }
+
+      self->inner->grabber = grabber;
+      self->inner->fakesink = fakesink;
+
+      object->opened =
+          gst_mf_capture_dshow_open (self, selected.moniker.Get ());
+
+      g_free (object->device_path);
+      object->device_path = g_strdup (selected.path.c_str());
+
+      g_free (object->device_name);
+      object->device_name = g_strdup (selected.name.c_str());
+
+      object->device_index = selected.index;
+    }
+  }
+
+run_loop:
+  GST_DEBUG_OBJECT (self, "Starting main loop");
+  g_main_loop_run (self->loop);
+  GST_DEBUG_OBJECT (self, "Stopped main loop");
+
+  gst_mf_capture_dshow_stop (object);
+  delete self->inner;
+
+  if (self->pool) {
+    gst_buffer_pool_set_active (self->pool, FALSE);
+    gst_clear_object (&self->pool);
+  }
+
+  g_main_context_pop_thread_default (self->context);
+
+  CoUninitialize ();
+
+  return nullptr;
+}
+/* *INDENT-ON* */
+
+static void
+gst_mf_capture_dshow_on_buffer (double sample_time, BYTE * data, LONG len,
+    gpointer user_data)
+{
+  GstMFCaptureDShow *self = GST_MF_CAPTURE_DSHOW (user_data);
+  GstFlowReturn ret;
+  GstClockTime time;
+  GstVideoFrame frame;
+  AM_MEDIA_TYPE type;
+  HRESULT hr;
+  GstBuffer *buf = nullptr;
+  GstCaps *caps = nullptr;
+  GstSample *sample;
+
+  if (!data) {
+    GST_WARNING_OBJECT (self, "Null data");
+    return;
+  }
+
+  memset (&type, 0, sizeof (AM_MEDIA_TYPE));
+  g_mutex_lock (&self->lock);
+  if (self->flushing || self->state != CAPTURE_STATE_RUNNING) {
+    GST_DEBUG_OBJECT (self, "Not running state");
+    g_mutex_unlock (&self->lock);
+    return;
+  }
+
+  hr = self->inner->grabber->GetConnectedMediaType (&type);
+  if (!gst_mf_result (hr)) {
+    GST_ERROR_OBJECT (self, "Couldn't get connected media type");
+    goto error;
+  }
+
+  caps = media_type_to_caps (&type, &self->top_down_image);
+  ClearMediaType (&type);
+  if (!caps) {
+    GST_ERROR_OBJECT (self, "Couldn't get caps from connected type");
+    goto error;
+  }
+
+  if (!gst_caps_is_equal (caps, self->selected_caps)) {
+    GstBufferPool *pool;
+    GstStructure *pool_config;
+    GstVideoInfo info;
+
+    if (!gst_video_info_from_caps (&info, caps)) {
+      GST_ERROR_OBJECT (self, "Couldn't get video info from caps");
+      gst_caps_unref (caps);
+      goto error;
+    }
+
+    GST_WARNING_OBJECT (self, "Caps change %" GST_PTR_FORMAT " -> %"
+        GST_PTR_FORMAT, self->selected_caps, caps);
+
+    gst_clear_caps (&self->selected_caps);
+    self->selected_caps = gst_caps_ref (caps);
+    self->info = info;
+
+    pool = gst_video_buffer_pool_new ();
+    pool_config = gst_buffer_pool_get_config (self->pool);
+    gst_buffer_pool_config_add_option (pool_config,
+        GST_BUFFER_POOL_OPTION_VIDEO_META);
+    gst_buffer_pool_config_set_params (pool_config, caps,
+        GST_VIDEO_INFO_SIZE (&self->info), 0, 0);
+    if (!gst_buffer_pool_set_config (pool, pool_config)) {
+      GST_ERROR_OBJECT (self, "Couldn not set buffer pool config");
+      gst_object_unref (pool);
+      goto error;
+    }
+
+    if (!gst_buffer_pool_set_active (pool, TRUE)) {
+      GST_ERROR_OBJECT (self, "Couldn't activate pool");
+      gst_object_unref (pool);
+      goto error;
+    }
+
+    if (self->pool) {
+      gst_buffer_pool_set_active (self->pool, FALSE);
+      gst_object_unref (self->pool);
+    }
+
+    self->pool = pool;
+  } else {
+    gst_clear_caps (&caps);
+  }
+
+  if (len < GST_VIDEO_INFO_SIZE (&self->info)) {
+    GST_ERROR_OBJECT (self, "Too small size %d < %d",
+        (gint) len, GST_VIDEO_INFO_SIZE (&self->info));
+    goto error;
+  }
+
+  time = gst_mf_source_object_get_running_time (GST_MF_SOURCE_OBJECT (self));
+  ret = gst_buffer_pool_acquire_buffer (self->pool, &buf, nullptr);
+  if (ret != GST_FLOW_OK) {
+    GST_WARNING_OBJECT (self, "Could not acquire buffer");
+    goto error;
+  }
+
+  if (!gst_video_frame_map (&frame, &self->info, buf, GST_MAP_WRITE)) {
+    GST_ERROR_OBJECT (self, "Could not map buffer");
+    goto error;
+  }
+
+  if (!self->top_down_image) {
+    guint8 *src, *dst;
+    gint src_stride, dst_stride;
+    gint width, height;
+
+    /* must be single plane RGB */
+    width = GST_VIDEO_INFO_COMP_WIDTH (&self->info, 0)
+        * GST_VIDEO_INFO_COMP_PSTRIDE (&self->info, 0);
+    height = GST_VIDEO_INFO_HEIGHT (&self->info);
+
+    src_stride = GST_VIDEO_INFO_PLANE_STRIDE (&self->info, 0);
+    dst_stride = GST_VIDEO_FRAME_PLANE_STRIDE (&frame, 0);
+
+    /* This is bottom up image, should copy lines in reverse order */
+    src = data + src_stride * (height - 1);
+    dst = (guint8 *) GST_VIDEO_FRAME_PLANE_DATA (&frame, 0);
+
+    for (guint i = 0; i < height; i++) {
+      memcpy (dst, src, width);
+      src -= src_stride;
+      dst += dst_stride;
+    }
+  } else {
+    for (guint i = 0; i < GST_VIDEO_INFO_N_PLANES (&self->info); i++) {
+      guint8 *src, *dst;
+      gint src_stride, dst_stride;
+      gint width;
+
+      src = data + GST_VIDEO_INFO_PLANE_OFFSET (&self->info, i);
+      dst = (guint8 *) GST_VIDEO_FRAME_PLANE_DATA (&frame, i);
+
+      src_stride = GST_VIDEO_INFO_PLANE_STRIDE (&self->info, i);
+      dst_stride = GST_VIDEO_FRAME_PLANE_STRIDE (&frame, i);
+      width = GST_VIDEO_INFO_COMP_WIDTH (&self->info, i)
+          * GST_VIDEO_INFO_COMP_PSTRIDE (&self->info, i);
+
+      for (guint j = 0; j < GST_VIDEO_INFO_COMP_HEIGHT (&self->info, i); j++) {
+        memcpy (dst, src, width);
+        src += src_stride;
+        dst += dst_stride;
+      }
+    }
+  }
+
+  gst_video_frame_unmap (&frame);
+
+  GST_BUFFER_PTS (buf) = time;
+  GST_BUFFER_DTS (buf) = GST_CLOCK_TIME_NONE;
+
+  sample = gst_sample_new (buf, caps, nullptr, nullptr);
+  gst_clear_caps (&caps);
+  gst_buffer_unref (buf);
+  g_queue_push_tail (&self->sample_queue, sample);
+  /* Drop old buffers */
+  while (g_queue_get_length (&self->sample_queue) > 30) {
+    sample = (GstSample *) g_queue_pop_head (&self->sample_queue);
+    GST_INFO_OBJECT (self, "Dropping old sample %p", sample);
+    gst_sample_unref (sample);
+  }
+  g_cond_broadcast (&self->cond);
+  g_mutex_unlock (&self->lock);
+
+  return;
+
+error:
+  gst_clear_buffer (&buf);
+  gst_clear_caps (&caps);
+  self->state = CAPTURE_STATE_ERROR;
+  g_cond_signal (&self->cond);
+  g_mutex_unlock (&self->lock);
+}
+
+GstMFSourceObject *
+gst_mf_capture_dshow_new (GstMFSourceType type, gint device_index,
+    const gchar * device_name, const gchar * device_path)
+{
+  GstMFSourceObject *self;
+
+  g_return_val_if_fail (type == GST_MF_SOURCE_TYPE_VIDEO, nullptr);
+
+  self = (GstMFSourceObject *) g_object_new (GST_TYPE_MF_CAPTURE_DSHOW,
+      "source-type", type, "device-index", device_index, "device-name",
+      device_name, "device-path", device_path, nullptr);
+
+  gst_object_ref_sink (self);
+
+  if (!self->opened) {
+    GST_DEBUG_OBJECT (self, "Couldn't open device");
+    gst_object_unref (self);
+    return nullptr;
+  }
+
+  return self;
+}
diff --git a/subprojects/gst-plugins-bad/sys/mediafoundation/gstmfcapturedshow.h b/subprojects/gst-plugins-bad/sys/mediafoundation/gstmfcapturedshow.h
new file mode 100644 (file)
index 0000000..f22f41e
--- /dev/null
@@ -0,0 +1,36 @@
+/* GStreamer
+ * Copyright (C) 2022 Seungha Yang <seungha@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#pragma once
+
+#include <gst/gst.h>
+#include "gstmfsourceobject.h"
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_MF_CAPTURE_DSHOW (gst_mf_capture_dshow_get_type())
+G_DECLARE_FINAL_TYPE (GstMFCaptureDShow, gst_mf_capture_dshow,
+    GST, MF_CAPTURE_DSHOW, GstMFSourceObject);
+
+GstMFSourceObject * gst_mf_capture_dshow_new (GstMFSourceType type,
+                                              gint device_index,
+                                              const gchar * device_name,
+                                              const gchar * device_path);
+
+G_END_DECLS
index c2f699f..6fe17b4 100644 (file)
@@ -31,6 +31,7 @@
 
 #if GST_MF_WINAPI_DESKTOP
 #include "gstwin32devicewatcher.h"
+#include "gstmfcapturedshow.h"
 
 #ifndef INITGUID
 #include <initguid.h>
@@ -265,11 +266,11 @@ gst_mf_device_provider_finalize (GObject * object)
   G_OBJECT_CLASS (gst_mf_device_provider_parent_class)->finalize (object);
 }
 
-static GList *
-gst_mf_device_provider_probe (GstDeviceProvider * provider)
+static void
+gst_mf_device_provider_probe_internal (GstDeviceProvider * provider,
+    gboolean try_dshow, GList ** list)
 {
   GstMFDeviceProvider *self = GST_MF_DEVICE_PROVIDER (provider);
-  GList *list = nullptr;
   gint i;
 
   for (i = 0;; i++) {
@@ -280,8 +281,18 @@ gst_mf_device_provider_probe (GstDeviceProvider * provider)
     gchar *device_name = nullptr;
     gchar *device_path = nullptr;
 
+#if GST_MF_WINAPI_DESKTOP
+    if (try_dshow) {
+      obj = gst_mf_capture_dshow_new (GST_MF_SOURCE_TYPE_VIDEO, i,
+          nullptr, nullptr);
+    } else {
+      obj = gst_mf_source_object_new (GST_MF_SOURCE_TYPE_VIDEO,
+          i, nullptr, nullptr, nullptr);
+    }
+#else
     obj = gst_mf_source_object_new (GST_MF_SOURCE_TYPE_VIDEO,
         i, nullptr, nullptr, nullptr);
+#endif
     if (!obj)
       break;
 
@@ -314,7 +325,7 @@ gst_mf_device_provider_probe (GstDeviceProvider * provider)
         "display-name", device_name, "caps", caps,
         "device-class", "Source/Video", "properties", props, nullptr);
 
-    list = g_list_append (list, device);
+    *list = g_list_append (*list, device);
 
   next:
     if (caps)
@@ -325,6 +336,17 @@ gst_mf_device_provider_probe (GstDeviceProvider * provider)
     g_free (device_name);
     gst_object_unref (obj);
   }
+}
+
+static GList *
+gst_mf_device_provider_probe (GstDeviceProvider * provider)
+{
+  GList *list = nullptr;
+
+  gst_mf_device_provider_probe_internal (provider, FALSE, &list);
+#if GST_MF_WINAPI_DESKTOP
+  gst_mf_device_provider_probe_internal (provider, TRUE, &list);
+#endif
 
   return list;
 }
index 8772038..9e71286 100644 (file)
@@ -232,6 +232,21 @@ gst_mf_source_object_create (GstMFSourceObject * object, GstBuffer ** buffer)
   return klass->create (object, buffer);
 }
 
+GstFlowReturn
+gst_mf_source_object_get_sample (GstMFSourceObject * object,
+    GstSample ** sample)
+{
+  GstMFSourceObjectClass *klass;
+
+  g_return_val_if_fail (GST_IS_MF_SOURCE_OBJECT (object), GST_FLOW_ERROR);
+  g_return_val_if_fail (sample != nullptr, GST_FLOW_ERROR);
+
+  klass = GST_MF_SOURCE_OBJECT_GET_CLASS (object);
+  g_assert (klass->get_sample != nullptr);
+
+  return klass->get_sample (object, sample);
+}
+
 void
 gst_mf_source_object_set_flushing (GstMFSourceObject * object,
     gboolean flushing)
index 8cc09a4..9f29d02 100644 (file)
@@ -72,6 +72,9 @@ struct _GstMFSourceObjectClass
   GstFlowReturn (*create)      (GstMFSourceObject * object,
                                 GstBuffer ** buffer);
 
+  GstFlowReturn (*get_sample)  (GstMFSourceObject * object,
+                                GstSample ** sample);
+
   gboolean      (*unlock)      (GstMFSourceObject * object);
 
   gboolean      (*unlock_stop) (GstMFSourceObject * object);
@@ -96,6 +99,10 @@ GstFlowReturn   gst_mf_source_object_fill         (GstMFSourceObject * object,
 GstFlowReturn   gst_mf_source_object_create       (GstMFSourceObject * object,
                                                    GstBuffer ** buffer);
 
+/* DirectShow filter */
+GstFlowReturn   gst_mf_source_object_get_sample   (GstMFSourceObject * object,
+                                                   GstSample ** sample);
+
 void            gst_mf_source_object_set_flushing (GstMFSourceObject * object,
                                                    gboolean flushing);
 
index d4d7e50..c8f4de4 100644 (file)
@@ -990,7 +990,7 @@ gst_mf_source_reader_new (GstMFSourceType type, gint device_index,
   gst_object_ref_sink (self);
 
   if (!self->opened) {
-    GST_WARNING_OBJECT (self, "Couldn't open device");
+    GST_DEBUG_OBJECT (self, "Couldn't open device");
     gst_object_unref (self);
     return nullptr;
   }
index efd137d..d909d07 100644 (file)
 #include "gstmfsourceobject.h"
 #include <string.h>
 
+#if GST_MF_WINAPI_DESKTOP
+#include "gstmfcapturedshow.h"
+#endif
+
 GST_DEBUG_CATEGORY (gst_mf_video_src_debug);
 #define GST_CAT_DEFAULT gst_mf_video_src_debug
 
@@ -76,6 +80,8 @@ struct _GstMFVideoSrc
   guint64 n_frames;
   GstClockTime latency;
 
+  gboolean use_dshow;
+
   /* properties */
   gchar *device_path;
   gchar *device_name;
@@ -268,6 +274,15 @@ gst_mf_video_src_start (GstBaseSrc * src)
 
   self->n_frames = 0;
   self->latency = 0;
+  self->use_dshow = FALSE;
+
+  if (!self->source) {
+#if GST_MF_WINAPI_DESKTOP
+    self->use_dshow = TRUE;
+    self->source = gst_mf_capture_dshow_new (GST_MF_SOURCE_TYPE_VIDEO,
+        self->device_index, self->device_name, self->device_path);
+#endif
+  }
 
   if (!self->source) {
     GST_ERROR_OBJECT (self, "Couldn't create capture object");
@@ -415,6 +430,7 @@ gst_mf_video_src_create (GstPushSrc * pushsrc, GstBuffer ** buffer)
   GstMFVideoSrc *self = GST_MF_VIDEO_SRC (pushsrc);
   GstFlowReturn ret = GST_FLOW_OK;
   GstBuffer *buf = nullptr;
+  GstSample *sample = nullptr;
   GstClock *clock;
   GstClockTime running_time = GST_CLOCK_TIME_NONE;
   GstClockTimeDiff diff;
@@ -429,7 +445,9 @@ gst_mf_video_src_create (GstPushSrc * pushsrc, GstBuffer ** buffer)
     self->started = TRUE;
   }
 
-  if (GST_VIDEO_INFO_FORMAT (&self->info) != GST_VIDEO_FORMAT_ENCODED) {
+  if (self->use_dshow) {
+    ret = gst_mf_source_object_get_sample (self->source, &sample);
+  } else if (GST_VIDEO_INFO_FORMAT (&self->info) != GST_VIDEO_FORMAT_ENCODED) {
     ret = GST_BASE_SRC_CLASS (parent_class)->alloc (GST_BASE_SRC (self), 0,
         GST_VIDEO_INFO_SIZE (&self->info), &buf);
 
@@ -444,6 +462,23 @@ gst_mf_video_src_create (GstPushSrc * pushsrc, GstBuffer ** buffer)
   if (ret != GST_FLOW_OK)
     return ret;
 
+  /* DirectShow capture object will set caps if it's got updated */
+  if (sample) {
+    if (gst_sample_get_caps (sample)) {
+      if (!gst_base_src_negotiate (GST_BASE_SRC (self))) {
+        GST_ERROR_OBJECT (self, "Failed to negotiate with new caps");
+        gst_sample_unref (sample);
+        return GST_FLOW_NOT_NEGOTIATED;
+      } else {
+        GST_DEBUG_OBJECT (self, "Renegotiated");
+      }
+    }
+
+    buf = gst_sample_get_buffer (sample);
+    gst_buffer_ref (buf);
+    gst_sample_unref (sample);
+  }
+
   GST_BUFFER_OFFSET (buf) = self->n_frames;
   GST_BUFFER_OFFSET_END (buf) = GST_BUFFER_OFFSET (buf) + 1;
   self->n_frames++;
index ae3fc96..6b9a059 100644 (file)
@@ -20,6 +20,7 @@ mf_sources = [
 ]
 
 mf_desktop_sources = [
+  'gstmfcapturedshow.cpp',
   'gstmfsourcereader.cpp',
   'gstwin32devicewatcher.cpp',
 ]