mediafoundation: Add support video capture on UWP app
authorSeungha Yang <seungha@centricular.com>
Wed, 20 May 2020 14:23:08 +0000 (23:23 +0900)
committerGStreamer Merge Bot <gitlab-merge-bot@gstreamer-foundation.org>
Mon, 25 May 2020 15:09:20 +0000 (15:09 +0000)
New video capture implementation using WinRT Media APIs for UWP app.
Due to the strict permission policy of UWP, device enumeration and
open should be done via new WinRT APIs and to get permission from users,
it will invoke permission dialog on UI.
Strictly saying, this implementation is not a part of MediaFoundation
but structurally it's very similar to MediaFoundation API.
So we can avoid some code duplication by adding this implementation
into MediaFoundation plugin.

This implementation requires UniversalApiContract version >= 6.0
which is part of Windows 10 version 1803 (Redstone 4)

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-bad/-/merge_requests/1241>

sys/mediafoundation/AsyncOperations.h [new file with mode: 0644]
sys/mediafoundation/gstmfcapturewinrt.cpp [new file with mode: 0644]
sys/mediafoundation/gstmfcapturewinrt.h [new file with mode: 0644]
sys/mediafoundation/gstmfdevice.c
sys/mediafoundation/gstmfsourceobject.c
sys/mediafoundation/gstmfvideosrc.c
sys/mediafoundation/mediacapturewrapper.cpp [new file with mode: 0644]
sys/mediafoundation/mediacapturewrapper.h [new file with mode: 0644]
sys/mediafoundation/meson.build
sys/mediafoundation/plugin.c

diff --git a/sys/mediafoundation/AsyncOperations.h b/sys/mediafoundation/AsyncOperations.h
new file mode 100644 (file)
index 0000000..4128860
--- /dev/null
@@ -0,0 +1,168 @@
+// MIT License
+//
+// Copyright (c) 2016 Microsoft Corporation
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+// Source taken from https://github.com/microsoft/MixedRealityCompanionKit
+
+#pragma once
+
+#include <wrl.h>
+#include <wrl\async.h>
+#include <Windows.System.Threading.h>
+#include <functional>
+
+template <typename TDelegate, typename TOperation, typename TLambda>
+HRESULT StartAsyncThen(_In_ TOperation* pOperation, _In_ TLambda&& tFunc)
+{
+    if (nullptr == pOperation)
+    {
+        return E_INVALIDARG;
+    }
+
+    auto spCallback = Microsoft::WRL::Callback<TDelegate>(
+        [tFunc](_In_ TOperation* pOperation, _In_ AsyncStatus status) -> HRESULT
+        {
+            HRESULT hr = S_OK;
+
+            // wrap the operation
+            if (status != AsyncStatus::Completed)
+            {
+                Microsoft::WRL::ComPtr<TOperation> spOperation(pOperation);
+                Microsoft::WRL::ComPtr<IAsyncInfo> spAsyncInfo;
+                hr = spOperation.As(&spAsyncInfo);
+                if (SUCCEEDED(hr))
+                {
+                    spAsyncInfo->get_ErrorCode(&hr);
+                }
+            }
+
+            return tFunc(hr, pOperation, status);
+        });
+
+    // start
+    return (nullptr != spCallback) ? pOperation->put_Completed(spCallback.Get()) : E_OUTOFMEMORY;
+}
+template <typename TLambda>
+HRESULT StartAsyncThen(_In_ ABI::Windows::Foundation::IAsyncAction* pOperation, _In_ TLambda&& tFunc)
+{
+    return StartAsyncThen<ABI::Windows::Foundation::IAsyncActionCompletedHandler, ABI::Windows::Foundation::IAsyncAction>(pOperation, static_cast<TLambda&&>(tFunc));
+}
+template <typename TProgress, typename TLambda>
+HRESULT StartAsyncThen(_In_ ABI::Windows::Foundation::IAsyncActionWithProgress<TProgress>* pOperation, _In_ TLambda&& tFunc)
+{
+    return StartAsyncThen<ABI::Windows::Foundation::IAsyncActionWithProgressCompletedHandler<TProgress>, Windows::Foundation::IAsyncActionWithProgress<TProgress>>(pOperation, static_cast<TLambda&&>(tFunc));
+}
+template <typename TResult, typename TLambda>
+HRESULT StartAsyncThen(_In_ ABI::Windows::Foundation::IAsyncOperation<TResult>* pOperation, _In_ TLambda&& tFunc)
+{
+    return StartAsyncThen<ABI::Windows::Foundation::IAsyncOperationCompletedHandler<TResult>, ABI::Windows::Foundation::IAsyncOperation<TResult>>(pOperation, static_cast<TLambda&&>(tFunc));
+}
+template <typename TResult, typename TProgress, typename TLambda>
+HRESULT StartAsyncThen(_In_ ABI::Windows::Foundation::IAsyncOperationWithProgress<TResult, TProgress>* pOperation, _In_ TLambda&& tFunc)
+{
+    return StartAsyncThen<ABI::Windows::Foundation::IAsyncOperationWithProgressCompletedHandler<TResult, TProgress>, ABI::Windows::Foundation::IAsyncOperationWithProgress<TResult, TProgress>>(pOperation, static_cast<TLambda&&>(tFunc));
+}
+
+
+// eg. TOperation   = IAsyncOperationWithProgress<UINT32, UINT32>
+// eg. THandler     = IAsyncOperationWithProgressCompletedHandler<UINT, UINT>
+template<typename TOperation, typename THandler>
+class AsyncEventDelegate
+    : public Microsoft::WRL::RuntimeClass
+    < Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::RuntimeClassType::Delegate>
+    , THandler
+    , Microsoft::WRL::FtmBase >
+{
+public:
+    AsyncEventDelegate()
+        : _completedEvent(CreateEventEx(nullptr, nullptr, 0, EVENT_ALL_ACCESS))
+    {
+        ComPtr<AsyncEventDelegate> spThis(this);
+        auto lambda = ([this, spThis](_In_ HRESULT hr, _In_ TOperation* pOperation)
+        {
+            SetEvent(_completedEvent.Get());
+        });
+        _func = std::move(lambda);
+    }
+
+    STDMETHOD(Invoke)(
+        _In_ TOperation* pOperation,
+        _In_ AsyncStatus status)
+    {
+        HRESULT hr = S_OK;
+
+        // if we completed successfully, then there is no need for getting hresult
+        if (status != AsyncStatus::Completed)
+        {
+            Microsoft::WRL::ComPtr<TOperation> spOperation(pOperation);
+            Microsoft::WRL::ComPtr<IAsyncInfo> spAsyncInfo;
+            if (SUCCEEDED(spOperation.As(&spAsyncInfo)))
+            {
+                spAsyncInfo->get_ErrorCode(&hr);
+            }
+        }
+
+        _func(hr, pOperation);
+
+        return S_OK;
+    }
+
+    STDMETHOD(SyncWait)(_In_ TOperation* pOperation, _In_ DWORD dwMilliseconds)
+    {
+        HRESULT hr = pOperation->put_Completed(this);
+        if (FAILED(hr))
+        {
+            return hr;
+        }
+
+        DWORD dwWait = WaitForSingleObjectEx(_completedEvent.Get(), dwMilliseconds, TRUE);
+        if (WAIT_IO_COMPLETION == dwWait || WAIT_OBJECT_0 == dwWait)
+            return S_OK;
+
+        return HRESULT_FROM_WIN32(GetLastError());
+    }
+
+private:
+    std::function<void(HRESULT, TOperation*)> _func;
+    Microsoft::WRL::Wrappers::Event _completedEvent;
+};
+template <typename TOperation, typename THandler>
+HRESULT SyncWait(_In_ TOperation* pOperation, _In_ DWORD dwMilliseconds)
+{
+    auto spCallback = Microsoft::WRL::Make<AsyncEventDelegate<TOperation, THandler>>();
+
+    return spCallback->SyncWait(pOperation, dwMilliseconds);
+}
+template <typename TResult>
+HRESULT SyncWait(_In_ ABI::Windows::Foundation::IAsyncAction* pOperation, _In_ DWORD dwMilliseconds = INFINITE)
+{
+    return SyncWait<ABI::Windows::Foundation::IAsyncAction, ABI::Windows::Foundation::IAsyncActionCompletedHandler>(pOperation, dwMilliseconds);
+}
+template <typename TResult>
+HRESULT SyncWait(_In_ ABI::Windows::Foundation::IAsyncOperation<TResult>* pOperation, _In_ DWORD dwMilliseconds = INFINITE)
+{
+    return SyncWait<ABI::Windows::Foundation::IAsyncOperation<TResult>, ABI::Windows::Foundation::IAsyncOperationCompletedHandler<TResult>>(pOperation, dwMilliseconds);
+}
+template <typename TResult, typename TProgress>
+HRESULT SyncWait(_In_ ABI::Windows::Foundation::IAsyncOperationWithProgress<TResult, TProgress>* pOperation, _In_ DWORD dwMilliseconds = INFINITE)
+{
+    return SyncWait<ABI::Windows::Foundation::IAsyncOperationWithProgress<TResult, TProgress>, ABI::Windows::Foundation::IAsyncOperationWithProgressCompletedHandler<TResult, TProgress>>(pOperation, dwMilliseconds);
+}
diff --git a/sys/mediafoundation/gstmfcapturewinrt.cpp b/sys/mediafoundation/gstmfcapturewinrt.cpp
new file mode 100644 (file)
index 0000000..b0515f9
--- /dev/null
@@ -0,0 +1,597 @@
+/* GStreamer
+ * Copyright (C) 2020 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/video/video.h>
+#include "gstmfcapturewinrt.h"
+#include "gstmfutils.h"
+#include "mediacapturewrapper.h"
+#include <memorybuffer.h>
+#include <memory>
+
+using namespace Microsoft::WRL;
+using namespace Microsoft::WRL::Wrappers;
+using namespace ABI::Windows::Media::MediaProperties;
+using namespace ABI::Windows::Graphics::Imaging;
+using namespace ABI::Windows::Foundation;
+
+extern "C" {
+GST_DEBUG_CATEGORY_EXTERN (gst_mf_source_object_debug);
+#define GST_CAT_DEFAULT gst_mf_source_object_debug
+}
+
+struct _GstMFCaptureWinRT
+{
+  GstMFSourceObject parent;
+
+  MediaCaptureWrapper *capture;
+
+  GThread *thread;
+  GMutex lock;
+  GCond cond;
+  GMainContext *context;
+  GMainLoop *loop;
+
+  /* protected by lock */
+  GQueue *queue;
+
+  GstCaps *supported_caps;
+  GstVideoInfo info;
+  gboolean flushing;
+  gboolean got_error;
+};
+
+static void gst_mf_capture_winrt_constructed (GObject * object);
+static void gst_mf_capture_winrt_finalize (GObject * object);
+
+static gboolean gst_mf_capture_winrt_start (GstMFSourceObject * object);
+static gboolean gst_mf_capture_winrt_stop  (GstMFSourceObject * object);
+static GstFlowReturn gst_mf_capture_winrt_fill (GstMFSourceObject * object,
+    GstBuffer * buffer);
+static gboolean gst_mf_capture_winrt_unlock (GstMFSourceObject * object);
+static gboolean gst_mf_capture_winrt_unlock_stop (GstMFSourceObject * object);
+static GstCaps * gst_mf_capture_winrt_get_caps (GstMFSourceObject * object);
+static gboolean gst_mf_capture_winrt_set_caps (GstMFSourceObject * object,
+    GstCaps * caps);
+static HRESULT gst_mf_capture_winrt_on_frame (ISoftwareBitmap * bitmap,
+    void * user_data);
+static HRESULT gst_mf_capture_winrt_on_failed (const std::string &error,
+    UINT32 error_code, void * user_data);
+
+static gpointer gst_mf_capture_winrt_thread_func (GstMFCaptureWinRT * self);
+
+#define gst_mf_capture_winrt_parent_class parent_class
+G_DEFINE_TYPE (GstMFCaptureWinRT, gst_mf_capture_winrt,
+    GST_TYPE_MF_SOURCE_OBJECT);
+
+static void
+gst_mf_capture_winrt_class_init (GstMFCaptureWinRTClass * klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+  GstMFSourceObjectClass *source_class = GST_MF_SOURCE_OBJECT_CLASS (klass);
+
+  gobject_class->constructed = gst_mf_capture_winrt_constructed;
+  gobject_class->finalize = gst_mf_capture_winrt_finalize;
+
+  source_class->start = GST_DEBUG_FUNCPTR (gst_mf_capture_winrt_start);
+  source_class->stop = GST_DEBUG_FUNCPTR (gst_mf_capture_winrt_stop);
+  source_class->fill = GST_DEBUG_FUNCPTR (gst_mf_capture_winrt_fill);
+  source_class->unlock = GST_DEBUG_FUNCPTR (gst_mf_capture_winrt_unlock);
+  source_class->unlock_stop =
+      GST_DEBUG_FUNCPTR (gst_mf_capture_winrt_unlock_stop);
+  source_class->get_caps = GST_DEBUG_FUNCPTR (gst_mf_capture_winrt_get_caps);
+  source_class->set_caps = GST_DEBUG_FUNCPTR (gst_mf_capture_winrt_set_caps);
+}
+
+static void
+gst_mf_capture_winrt_init (GstMFCaptureWinRT * self)
+{
+  self->queue = g_queue_new ();
+  g_mutex_init (&self->lock);
+  g_cond_init (&self->cond);
+}
+
+static void
+gst_mf_capture_winrt_constructed (GObject * object)
+{
+  GstMFCaptureWinRT *self = GST_MF_CAPTURE_WINRT (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 ("GstMFCaptureWinRT",
+      (GThreadFunc) gst_mf_capture_winrt_thread_func, self);
+  while (!g_main_loop_is_running (self->loop))
+    g_cond_wait (&self->cond, &self->lock);
+  g_mutex_unlock (&self->lock);
+
+done:
+  G_OBJECT_CLASS (parent_class)->constructed (object);
+}
+
+static void
+gst_mf_capture_winrt_finalize (GObject * object)
+{
+  GstMFCaptureWinRT *self = GST_MF_CAPTURE_WINRT (object);
+
+  g_main_loop_quit (self->loop);
+  g_thread_join (self->thread);
+  g_main_loop_unref (self->loop);
+  g_main_context_unref (self->context);
+
+  g_queue_free (self->queue);
+  gst_clear_caps (&self->supported_caps);
+  g_mutex_clear (&self->lock);
+  g_cond_clear (&self->cond);
+
+  G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static gboolean
+gst_mf_capture_winrt_main_loop_running_cb (GstMFCaptureWinRT * self)
+{
+  GST_DEBUG_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 gpointer
+gst_mf_capture_winrt_thread_func (GstMFCaptureWinRT * self)
+{
+  GstMFSourceObject *source = GST_MF_SOURCE_OBJECT (self);
+  HRESULT hr;
+  guint index;
+  GSource *idle_source;
+  std::shared_ptr<GstWinRTMediaFrameSourceGroup> target_group;
+  std::vector<GstWinRTMediaFrameSourceGroup> group_list;
+  MediaCaptureWrapperCallbacks callbacks;
+
+  RoInitializeWrapper init_wrapper (RO_INIT_MULTITHREADED);
+
+  self->capture = new MediaCaptureWrapper;
+  callbacks.frame_arrived = gst_mf_capture_winrt_on_frame;
+  callbacks.failed = gst_mf_capture_winrt_on_failed;
+  self->capture->RegisterCb (callbacks, self);
+
+  g_main_context_push_thread_default (self->context);
+
+  idle_source = g_idle_source_new ();
+  g_source_set_callback (idle_source,
+      (GSourceFunc) gst_mf_capture_winrt_main_loop_running_cb, self, NULL);
+  g_source_attach (idle_source, self->context);
+  g_source_unref (idle_source);
+
+  hr = self->capture->EnumrateFrameSourceGroup(group_list);
+
+#ifndef GST_DISABLE_GST_DEBUG
+  index = 0;
+  for (const auto& iter: group_list) {
+    GST_DEBUG_OBJECT (self, "device %d, name: \"%s\", path: \"%s\"",
+        index, iter.display_name_.c_str(), iter.id_.c_str());
+    index++;
+  }
+#endif
+
+  GST_DEBUG_OBJECT (self,
+      "Requested device index: %d, name: \"%s\", path \"%s\"",
+      source->device_index, GST_STR_NULL (source->device_name),
+      GST_STR_NULL (source->device_path));
+
+  index = 0;
+  for (const auto& iter: group_list) {
+    gboolean match;
+
+    if (source->device_path) {
+      match = g_ascii_strcasecmp (iter.id_.c_str(), source->device_path) == 0;
+    } else if (source->device_name) {
+      match = g_ascii_strcasecmp (iter.display_name_.c_str(),
+          source->device_name) == 0;
+    } else if (source->device_index >= 0) {
+      match = index == source->device_index;
+    } else {
+      /* pick the first entry */
+      match = TRUE;
+    }
+
+    if (match) {
+      target_group = std::make_shared<GstWinRTMediaFrameSourceGroup>(iter);
+      break;
+    }
+
+    index++;
+  }
+
+  if (!target_group) {
+    GST_WARNING_OBJECT (self, "No matching device");
+    goto run_loop;
+  }
+
+  self->capture->SetSourceGroup(*target_group);
+
+  for (auto iter: target_group->source_list_) {
+    if (!self->supported_caps)
+      self->supported_caps = gst_caps_ref (iter.caps_);
+    else
+      self->supported_caps =
+          gst_caps_merge (self->supported_caps, gst_caps_ref (iter.caps_));
+  }
+
+  GST_DEBUG_OBJECT (self, "Available output caps %" GST_PTR_FORMAT,
+      self->supported_caps);
+
+  source->opened = !!self->supported_caps;
+
+  if (source->opened) {
+    g_free (source->device_path);
+    source->device_path = g_strdup (target_group->id_.c_str());
+
+    g_free (source->device_name);
+    source->device_name = g_strdup (target_group->display_name_.c_str());
+
+    source->device_index = index;
+  }
+
+run_loop:
+  GST_DEBUG_OBJECT (self, "Starting main loop");
+  g_main_loop_run (self->loop);
+  GST_DEBUG_OBJECT (self, "Stopped main loop");
+
+  g_main_context_pop_thread_default (self->context);
+
+  gst_mf_capture_winrt_stop (source);
+
+  delete self->capture;
+  self->capture = NULL;
+
+  return NULL;
+}
+
+static gboolean
+gst_mf_capture_winrt_start (GstMFSourceObject * object)
+{
+  GstMFCaptureWinRT *self = GST_MF_CAPTURE_WINRT (object);
+  HRESULT hr;
+
+  if (!self->capture) {
+    GST_ERROR_OBJECT (self, "No capture object was configured");
+    return FALSE;
+  }
+
+  hr = self->capture->StartCapture();
+  if (!gst_mf_result (hr)) {
+    GST_ERROR_OBJECT (self, "Capture object doesn't want to start capture");
+    return FALSE;
+  }
+
+  return TRUE;
+}
+
+static gboolean
+gst_mf_capture_winrt_stop (GstMFSourceObject * object)
+{
+  GstMFCaptureWinRT *self = GST_MF_CAPTURE_WINRT (object);
+  HRESULT hr;
+
+  if (!self->capture) {
+    GST_ERROR_OBJECT (self, "No capture object was configured");
+    return FALSE;
+  }
+
+  hr = self->capture->StopCapture();
+
+  while (!g_queue_is_empty (self->queue)) {
+    IMFMediaBuffer *buffer = (IMFMediaBuffer *) g_queue_pop_head (self->queue);
+    buffer->Release ();
+  }
+
+  if (!gst_mf_result (hr)) {
+    GST_ERROR_OBJECT (self, "Capture object doesn't want to stop capture");
+    return FALSE;
+  }
+
+  return TRUE;
+}
+
+static HRESULT
+gst_mf_capture_winrt_on_frame (ISoftwareBitmap * bitmap,
+    void * user_data)
+{
+  GstMFCaptureWinRT *self = GST_MF_CAPTURE_WINRT (user_data);
+
+  g_mutex_lock (&self->lock);
+  if (self->flushing) {
+    g_mutex_unlock (&self->lock);
+    return S_OK;
+  }
+
+  g_queue_push_tail (self->queue, bitmap);
+  bitmap->AddRef ();
+
+  g_cond_broadcast (&self->cond);
+  g_mutex_unlock (&self->lock);
+
+  return S_OK;
+}
+
+static HRESULT
+gst_mf_capture_winrt_on_failed (const std::string &error,
+    UINT32 error_code, void * user_data)
+{
+  GstMFCaptureWinRT *self = GST_MF_CAPTURE_WINRT (user_data);
+
+  GST_DEBUG_OBJECT (self, "Have error %s (%d)", error.c_str(), error_code);
+
+  g_mutex_lock (&self->lock);
+  self->got_error = TRUE;
+  g_cond_broadcast (&self->cond);
+  g_mutex_unlock (&self->lock);
+
+  return S_OK;
+}
+
+static GstFlowReturn
+gst_mf_capture_winrt_fill (GstMFSourceObject * object, GstBuffer * buffer)
+{
+  GstMFCaptureWinRT *self = GST_MF_CAPTURE_WINRT (object);
+  GstFlowReturn ret = GST_FLOW_OK;
+  HRESULT hr;
+  GstVideoFrame frame;
+  BYTE *data;
+  UINT32 size;
+  gint i, j;
+  ComPtr<ISoftwareBitmap> bitmap;
+  ComPtr<IBitmapBuffer> bitmap_buffer;
+  ComPtr<IMemoryBuffer> mem_buf;
+  ComPtr<IMemoryBufferReference> mem_ref;
+  ComPtr<Windows::Foundation::IMemoryBufferByteAccess> byte_access;
+  INT32 plane_count;
+  BitmapPlaneDescription desc[GST_VIDEO_MAX_PLANES];
+
+  g_mutex_lock (&self->lock);
+  if (self->got_error) {
+    g_mutex_unlock (&self->lock);
+    return GST_FLOW_ERROR;
+  }
+
+  if (self->flushing) {
+    g_mutex_unlock (&self->lock);
+    return GST_FLOW_FLUSHING;
+  }
+
+  while (!self->flushing && !self->got_error && g_queue_is_empty (self->queue))
+    g_cond_wait (&self->cond, &self->lock);
+
+  if (self->got_error) {
+    g_mutex_unlock (&self->lock);
+    return GST_FLOW_ERROR;
+  }
+
+  if (self->flushing) {
+    g_mutex_unlock (&self->lock);
+    return GST_FLOW_FLUSHING;
+  }
+
+  bitmap.Attach ((ISoftwareBitmap *) g_queue_pop_head (self->queue));
+  g_mutex_unlock (&self->lock);
+
+  hr = bitmap->LockBuffer (BitmapBufferAccessMode::BitmapBufferAccessMode_Read,
+      &bitmap_buffer);
+  if (!gst_mf_result (hr)) {
+    GST_ERROR_OBJECT (self, "Cannot lock ISoftwareBitmap");
+    return GST_FLOW_ERROR;
+  }
+
+  hr = bitmap_buffer->GetPlaneCount (&plane_count);
+  if (!gst_mf_result (hr)) {
+    GST_ERROR_OBJECT (self, "Cannot get plane count");
+    return GST_FLOW_ERROR;
+  }
+
+  if (plane_count > GST_VIDEO_MAX_PLANES) {
+    GST_ERROR_OBJECT (self, "Invalid plane count %d", plane_count);
+    return GST_FLOW_ERROR;
+  }
+
+  if (plane_count != GST_VIDEO_INFO_N_PLANES (&self->info)) {
+    GST_ERROR_OBJECT (self, "Ambiguous plane count %d", plane_count);
+    return GST_FLOW_ERROR;
+  }
+
+  for (i = 0; i < plane_count; i++) {
+    hr = bitmap_buffer->GetPlaneDescription (i, &desc[i]);
+    if (!gst_mf_result (hr)) {
+      GST_ERROR_OBJECT (self, "Cannot get description for plane %d", i);
+      return GST_FLOW_ERROR;
+    }
+  }
+
+  hr = bitmap_buffer.As (&mem_buf);
+  if (!gst_mf_result (hr)) {
+    GST_ERROR_OBJECT (self, "Cannot get IMemoryBuffer");
+    return GST_FLOW_ERROR;
+  }
+
+  hr = mem_buf->CreateReference (&mem_ref);
+  if (!gst_mf_result (hr)) {
+    GST_ERROR_OBJECT (self, "Cannot get IMemoryBufferReference");
+    return GST_FLOW_ERROR;
+  }
+
+  hr = mem_ref.As(&byte_access);
+  if (!gst_mf_result (hr)) {
+    GST_ERROR_OBJECT (self, "Cannot get IMemoryBufferByteAccess");
+    return GST_FLOW_ERROR;
+  }
+
+  hr = byte_access->GetBuffer (&data, &size);
+  if (!gst_mf_result (hr)) {
+    GST_ERROR_OBJECT (self, "Cannot get raw buffer data");
+    return GST_FLOW_ERROR;
+  }
+
+  if (size < GST_VIDEO_INFO_SIZE (&self->info)) {
+    GST_ERROR_OBJECT (self, "Too small buffer size %d", size);
+    return GST_FLOW_ERROR;
+  }
+
+  if (!gst_video_frame_map (&frame, &self->info, buffer, GST_MAP_WRITE)) {
+    GST_ERROR_OBJECT (self, "Failed to map buffer");
+    return GST_FLOW_ERROR;
+  }
+
+  for (i = 0; i < GST_VIDEO_INFO_N_PLANES (&self->info); i++) {
+    guint8 *src, *dst;
+    gint src_stride, dst_stride;
+    gint width;
+
+    src = data + desc[i].StartIndex;
+    dst = (guint8 *) GST_VIDEO_FRAME_PLANE_DATA (&frame, i);
+
+    src_stride = desc[i].Stride;
+    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 (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);
+
+  return ret;
+}
+
+static gboolean
+gst_mf_capture_winrt_unlock (GstMFSourceObject * object)
+{
+  GstMFCaptureWinRT *self = GST_MF_CAPTURE_WINRT (object);
+
+  g_mutex_lock (&self->lock);
+  if (self->flushing) {
+    g_mutex_unlock (&self->lock);
+    return TRUE;
+  }
+
+  self->flushing = TRUE;
+  g_cond_broadcast (&self->cond);
+  g_mutex_unlock (&self->lock);
+
+  return TRUE;
+}
+
+static gboolean
+gst_mf_capture_winrt_unlock_stop (GstMFSourceObject * object)
+{
+  GstMFCaptureWinRT *self = GST_MF_CAPTURE_WINRT (object);
+
+  g_mutex_lock (&self->lock);
+  if (!self->flushing) {
+    g_mutex_unlock (&self->lock);
+    return TRUE;
+  }
+
+  self->flushing = FALSE;
+  g_cond_broadcast (&self->cond);
+  g_mutex_unlock (&self->lock);
+
+  return TRUE;
+}
+
+static GstCaps *
+gst_mf_capture_winrt_get_caps (GstMFSourceObject * object)
+{
+  GstMFCaptureWinRT *self = GST_MF_CAPTURE_WINRT (object);
+
+  if (self->supported_caps)
+    return gst_caps_ref (self->supported_caps);
+
+  return NULL;
+}
+
+static gboolean
+gst_mf_capture_winrt_set_caps (GstMFSourceObject * object, GstCaps * caps)
+{
+  GstMFCaptureWinRT *self = GST_MF_CAPTURE_WINRT (object);
+  std::vector<GstWinRTMediaDescription> desc_list;
+  HRESULT hr;
+  GstCaps *target_caps = NULL;
+
+  hr = self->capture->GetAvailableDescriptions(desc_list);
+  if (!gst_mf_result (hr) || desc_list.empty()) {
+    GST_ERROR_OBJECT (self, "No available media description");
+    return FALSE;
+  }
+
+  for (const auto& iter: desc_list) {
+    if (gst_caps_is_subset (iter.caps_, caps)) {
+      target_caps = gst_caps_ref (iter.caps_);
+      self->capture->SetMediaDescription(iter);
+      break;
+    }
+  }
+
+  if (!target_caps) {
+    GST_ERROR_OBJECT (self,
+        "Could not determine target media type with given caps %"
+        GST_PTR_FORMAT, caps);
+
+    return FALSE;
+  }
+
+  gst_video_info_from_caps (&self->info, target_caps);
+  gst_caps_unref (target_caps);
+
+  return TRUE;
+}
+
+GstMFSourceObject *
+gst_mf_capture_winrt_new (GstMFSourceType type, gint device_index,
+    const gchar * device_name, const gchar * device_path)
+{
+  GstMFSourceObject *self;
+
+  /* TODO: Add audio capture support */
+  g_return_val_if_fail (type == GST_MF_SOURCE_TYPE_VIDEO, NULL);
+
+  self = (GstMFSourceObject *) g_object_new (GST_TYPE_MF_CAPTURE_WINRT,
+      "source-type", type, "device-index", device_index, "device-name",
+      device_name, "device-path", device_path, NULL);
+
+  gst_object_ref_sink (self);
+
+  if (!self->opened) {
+    GST_WARNING_OBJECT (self, "Couldn't open device");
+    gst_object_unref (self);
+    return NULL;
+  }
+
+  return self;
+}
diff --git a/sys/mediafoundation/gstmfcapturewinrt.h b/sys/mediafoundation/gstmfcapturewinrt.h
new file mode 100644 (file)
index 0000000..2f4cf15
--- /dev/null
@@ -0,0 +1,39 @@
+/* GStreamer
+ * Copyright (C) 2020 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.
+ */
+
+#ifndef __GST_MF_CAPTURE_WINRT_H__
+#define __GST_MF_CAPTURE_WINRT_H__
+
+#include <gst/gst.h>
+#include "gstmfsourceobject.h"
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_MF_CAPTURE_WINRT (gst_mf_capture_winrt_get_type())
+G_DECLARE_FINAL_TYPE (GstMFCaptureWinRT, gst_mf_capture_winrt,
+    GST, MF_CAPTURE_WINRT, GstMFSourceObject);
+
+GstMFSourceObject * gst_mf_capture_winrt_new (GstMFSourceType type,
+                                              gint device_index,
+                                              const gchar * device_name,
+                                              const gchar * device_path);
+
+G_END_DECLS
+
+#endif /* __GST_MF_CAPTURE_WINRT_H__ */
\ No newline at end of file
index f11b7a8..71356be 100644 (file)
 
 #include "gstmfvideosrc.h"
 #include "gstmfutils.h"
+#if GST_MF_WINAPI_ONLY_APP
+#include "gstmfcapturewinrt.h"
+#else /* GST_MF_WINAPI_ONLY_APP */
 #include "gstmfsourcereader.h"
-#if HAVE_CAPTURE_ENGINE
+#if GST_MF_HAVE_CAPTURE_ENGINE
 #include "gstmfcaptureengine.h"
-#endif
+#endif /* GST_MF_HAVE_CAPTURE_ENGINE */
+#endif /* GST_MF_WINAPI_ONLY_APP */
 
 #include "gstmfdevice.h"
 
@@ -179,13 +183,16 @@ gst_mf_device_provider_probe (GstDeviceProvider * provider)
     gchar *device_name = NULL;
     gchar *device_path = NULL;
 
-
-#if HAVE_CAPTURE_ENGINE
-    obj = gst_mf_capture_engine_new (GST_MF_SOURCE_TYPE_VIDEO, i, NULL, NULL);
-#endif
-
+#if GST_MF_WINAPI_ONLY_APP
+    obj = gst_mf_capture_winrt_new (GST_MF_SOURCE_TYPE_VIDEO, i, NULL, NULL);
+#else /* !GST_MF_WINAPI_ONLY_APP */
+#if GST_MF_HAVE_CAPTURE_ENGINE
+    if (!obj)
+      obj = gst_mf_capture_engine_new (GST_MF_SOURCE_TYPE_VIDEO, i, NULL, NULL);
+#endif /* GST_MF_HAVE_CAPTURE_ENGINE */
     if (!obj)
       obj = gst_mf_source_reader_new (GST_MF_SOURCE_TYPE_VIDEO, i, NULL, NULL);
+#endif /* GST_MF_WINAPI_ONLY_APP  */
 
     if (!obj)
       break;
index 4fccb14..0bfbad1 100644 (file)
@@ -22,6 +22,8 @@
 #include "config.h"
 #endif
 
+#include "gstmfconfig.h"
+
 #include "gstmfsourceobject.h"
 
 GST_DEBUG_CATEGORY_EXTERN (gst_mf_source_object_debug);
@@ -81,10 +83,12 @@ static void gst_mf_source_object_get_property (GObject * object, guint prop_id,
 static void gst_mf_source_object_set_property (GObject * object, guint prop_id,
     const GValue * value, GParamSpec * pspec);
 
+#if !(GST_MF_WINAPI_ONLY_APP)
 static gpointer gst_mf_source_object_thread_func (GstMFSourceObject * self);
 static gboolean gst_mf_source_enum_device_activate (GstMFSourceObject * self,
     GstMFSourceType source_type, GList ** device_activates);
 static void gst_mf_device_activate_free (GstMFDeviceActivate * activate);
+#endif
 
 #define gst_mf_source_object_parent_class parent_class
 G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GstMFSourceObject, gst_mf_source_object,
@@ -132,17 +136,18 @@ gst_mf_source_object_init (GstMFSourceObject * self)
 
   g_mutex_init (&priv->lock);
   g_cond_init (&priv->cond);
-
-  priv->context = g_main_context_new ();
-  priv->loop = g_main_loop_new (priv->context, FALSE);
 }
 
 static void
 gst_mf_source_object_constructed (GObject * object)
 {
+#if !(GST_MF_WINAPI_ONLY_APP)
   GstMFSourceObject *self = GST_MF_SOURCE_OBJECT (object);
   GstMFSourceObjectPrivate *priv = self->priv;
 
+  priv->context = g_main_context_new ();
+  priv->loop = g_main_loop_new (priv->context, FALSE);
+
   /* Create a new thread to ensure that COM thread can be MTA thread */
   g_mutex_lock (&priv->lock);
   priv->thread = g_thread_new ("GstMFSourceObject",
@@ -151,6 +156,7 @@ gst_mf_source_object_constructed (GObject * object)
     g_cond_wait (&priv->cond, &priv->lock);
   g_mutex_unlock (&priv->lock);
 
+#endif
   G_OBJECT_CLASS (parent_class)->constructed (object);
 }
 
@@ -254,6 +260,92 @@ gst_mf_source_object_main_loop_running_cb (GstMFSourceObject * self)
   return G_SOURCE_REMOVE;
 }
 
+gboolean
+gst_mf_source_object_start (GstMFSourceObject * object)
+{
+  GstMFSourceObjectClass *klass;
+
+  g_return_val_if_fail (GST_IS_MF_SOURCE_OBJECT (object), FALSE);
+
+  klass = GST_MF_SOURCE_OBJECT_GET_CLASS (object);
+  g_assert (klass->start != NULL);
+
+  return klass->start (object);
+}
+
+gboolean
+gst_mf_source_object_stop (GstMFSourceObject * object)
+{
+  GstMFSourceObjectClass *klass;
+
+  g_return_val_if_fail (GST_IS_MF_SOURCE_OBJECT (object), FALSE);
+
+  klass = GST_MF_SOURCE_OBJECT_GET_CLASS (object);
+  g_assert (klass->stop != NULL);
+
+  return klass->stop (object);
+}
+
+GstFlowReturn
+gst_mf_source_object_fill (GstMFSourceObject * object, GstBuffer * buffer)
+{
+  GstMFSourceObjectClass *klass;
+
+  g_return_val_if_fail (GST_IS_MF_SOURCE_OBJECT (object), GST_FLOW_ERROR);
+  g_return_val_if_fail (GST_IS_BUFFER (buffer), GST_FLOW_ERROR);
+
+  klass = GST_MF_SOURCE_OBJECT_GET_CLASS (object);
+  g_assert (klass->fill != NULL);
+
+  return klass->fill (object, buffer);
+}
+
+void
+gst_mf_source_object_set_flushing (GstMFSourceObject * object,
+    gboolean flushing)
+{
+  GstMFSourceObjectClass *klass;
+
+  g_return_if_fail (GST_IS_MF_SOURCE_OBJECT (object));
+
+  klass = GST_MF_SOURCE_OBJECT_GET_CLASS (object);
+
+  if (flushing) {
+    if (klass->unlock)
+      klass->unlock (object);
+  } else {
+    if (klass->unlock_stop)
+      klass->unlock_stop (object);
+  }
+}
+
+gboolean
+gst_mf_source_object_set_caps (GstMFSourceObject * object, GstCaps * caps)
+{
+  GstMFSourceObjectClass *klass;
+
+  g_return_val_if_fail (GST_IS_MF_SOURCE_OBJECT (object), FALSE);
+
+  klass = GST_MF_SOURCE_OBJECT_GET_CLASS (object);
+  g_assert (klass->set_caps != NULL);
+
+  return klass->set_caps (object, caps);
+}
+
+GstCaps *
+gst_mf_source_object_get_caps (GstMFSourceObject * object)
+{
+  GstMFSourceObjectClass *klass;
+
+  g_return_val_if_fail (GST_IS_MF_SOURCE_OBJECT (object), NULL);
+
+  klass = GST_MF_SOURCE_OBJECT_GET_CLASS (object);
+  g_assert (klass->get_caps != NULL);
+
+  return klass->get_caps (object);
+}
+
+#if !(GST_MF_WINAPI_ONLY_APP)
 static gpointer
 gst_mf_source_object_thread_func (GstMFSourceObject * self)
 {
@@ -344,91 +436,6 @@ run_loop:
   return NULL;
 }
 
-gboolean
-gst_mf_source_object_start (GstMFSourceObject * object)
-{
-  GstMFSourceObjectClass *klass;
-
-  g_return_val_if_fail (GST_IS_MF_SOURCE_OBJECT (object), FALSE);
-
-  klass = GST_MF_SOURCE_OBJECT_GET_CLASS (object);
-  g_assert (klass->start != NULL);
-
-  return klass->start (object);
-}
-
-gboolean
-gst_mf_source_object_stop (GstMFSourceObject * object)
-{
-  GstMFSourceObjectClass *klass;
-
-  g_return_val_if_fail (GST_IS_MF_SOURCE_OBJECT (object), FALSE);
-
-  klass = GST_MF_SOURCE_OBJECT_GET_CLASS (object);
-  g_assert (klass->stop != NULL);
-
-  return klass->stop (object);
-}
-
-GstFlowReturn
-gst_mf_source_object_fill (GstMFSourceObject * object, GstBuffer * buffer)
-{
-  GstMFSourceObjectClass *klass;
-
-  g_return_val_if_fail (GST_IS_MF_SOURCE_OBJECT (object), GST_FLOW_ERROR);
-  g_return_val_if_fail (GST_IS_BUFFER (buffer), GST_FLOW_ERROR);
-
-  klass = GST_MF_SOURCE_OBJECT_GET_CLASS (object);
-  g_assert (klass->fill != NULL);
-
-  return klass->fill (object, buffer);
-}
-
-void
-gst_mf_source_object_set_flushing (GstMFSourceObject * object,
-    gboolean flushing)
-{
-  GstMFSourceObjectClass *klass;
-
-  g_return_if_fail (GST_IS_MF_SOURCE_OBJECT (object));
-
-  klass = GST_MF_SOURCE_OBJECT_GET_CLASS (object);
-
-  if (flushing) {
-    if (klass->unlock)
-      klass->unlock (object);
-  } else {
-    if (klass->unlock_stop)
-      klass->unlock_stop (object);
-  }
-}
-
-gboolean
-gst_mf_source_object_set_caps (GstMFSourceObject * object, GstCaps * caps)
-{
-  GstMFSourceObjectClass *klass;
-
-  g_return_val_if_fail (GST_IS_MF_SOURCE_OBJECT (object), FALSE);
-
-  klass = GST_MF_SOURCE_OBJECT_GET_CLASS (object);
-  g_assert (klass->set_caps != NULL);
-
-  return klass->set_caps (object, caps);
-}
-
-GstCaps *
-gst_mf_source_object_get_caps (GstMFSourceObject * object)
-{
-  GstMFSourceObjectClass *klass;
-
-  g_return_val_if_fail (GST_IS_MF_SOURCE_OBJECT (object), NULL);
-
-  klass = GST_MF_SOURCE_OBJECT_GET_CLASS (object);
-  g_assert (klass->get_caps != NULL);
-
-  return klass->get_caps (object);
-}
-
 static gboolean
 gst_mf_source_enum_device_activate (GstMFSourceObject * self,
     GstMFSourceType source_type, GList ** device_sources)
@@ -522,3 +529,4 @@ gst_mf_device_activate_free (GstMFDeviceActivate * activate)
   g_free (activate->path);
   g_free (activate);
 }
+#endif
index 33cbb8b..ad532db 100644 (file)
 
 #include "gstmfvideosrc.h"
 #include "gstmfutils.h"
+#if GST_MF_WINAPI_ONLY_APP
+#include "gstmfcapturewinrt.h"
+#else /* GST_MF_WINAPI_ONLY_APP */
 #include "gstmfsourcereader.h"
-#if HAVE_CAPTURE_ENGINE
+#if GST_MF_HAVE_CAPTURE_ENGINE
 #include "gstmfcaptureengine.h"
-#endif
+#endif /* GST_MF_HAVE_CAPTURE_ENGINE */
+#endif /* !GST_MF_WINAPI_ONLY_APP */
 #include <string.h>
 
 GST_DEBUG_CATEGORY (gst_mf_video_src_debug);
@@ -230,14 +234,19 @@ gst_mf_video_src_start (GstBaseSrc * src)
 
   GST_DEBUG_OBJECT (self, "Start");
 
-#if HAVE_CAPTURE_ENGINE
-  self->source = gst_mf_capture_engine_new (GST_MF_SOURCE_TYPE_VIDEO,
+#if GST_MF_WINAPI_ONLY_APP
+  self->source = gst_mf_capture_winrt_new (GST_MF_SOURCE_TYPE_VIDEO,
       self->device_index, self->device_name, self->device_path);
-#endif
-
+#else /* GST_MF_WINAPI_ONLY_APP */
+#if GST_MF_HAVE_CAPTURE_ENGINE
+  if (!self->source)
+    self->source = gst_mf_capture_engine_new (GST_MF_SOURCE_TYPE_VIDEO,
+        self->device_index, self->device_name, self->device_path);
+#endif /* GST_MF_HAVE_CAPTURE_ENGINE */
   if (!self->source)
     self->source = gst_mf_source_reader_new (GST_MF_SOURCE_TYPE_VIDEO,
         self->device_index, self->device_name, self->device_path);
+#endif /* GST_MF_WINAPI_ONLY_APP */
 
   self->first_pts = GST_CLOCK_TIME_NONE;
   self->n_frames = 0;
diff --git a/sys/mediafoundation/mediacapturewrapper.cpp b/sys/mediafoundation/mediacapturewrapper.cpp
new file mode 100644 (file)
index 0000000..f8c27bf
--- /dev/null
@@ -0,0 +1,1027 @@
+/* GStreamer
+ * Copyright (C) 2020 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/gst.h>
+#include "gstmfutils.h"
+#include "mediacapturewrapper.h"
+
+#include "AsyncOperations.h"
+#include <windows.ui.core.h>
+#include <locale>
+#include <codecvt>
+#include <string.h>
+
+using namespace ABI::Windows::ApplicationModel::Core;
+using namespace ABI::Windows::Foundation::Collections;
+using namespace ABI::Windows::Media::Devices;
+using namespace ABI::Windows::Media::MediaProperties;
+
+extern "C" {
+GST_DEBUG_CATEGORY_EXTERN (gst_mf_source_object_debug);
+#define GST_CAT_DEFAULT gst_mf_source_object_debug
+}
+
+static std::string
+convert_hstring_to_string (HString * hstr)
+{
+  const wchar_t *raw_hstr;
+
+  if (!hstr)
+    return std::string();
+
+  raw_hstr = hstr->GetRawBuffer (nullptr);
+  if (!raw_hstr)
+    return std::string();
+
+  std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t> converter;
+
+  return converter.to_bytes (raw_hstr);
+}
+
+static std::string
+gst_media_capture_subtype_to_video_format (const std::string &subtype)
+{
+  /* https://docs.microsoft.com/en-us/uwp/api/windows.media.mediaproperties.videoencodingproperties.subtype#Windows_Media_MediaProperties_VideoEncodingProperties_Subtype */
+  if (g_ascii_strcasecmp (subtype.c_str(), "RGB32") == 0)
+    return "BGRx";
+  else if (g_ascii_strcasecmp (subtype.c_str(), "ARGB32") == 0)
+    return "BGRA";
+  else if (g_ascii_strcasecmp (subtype.c_str(), "RGB24") == 0)
+    return "BGR";
+  else if (g_ascii_strcasecmp (subtype.c_str(), "NV12") == 0)
+    return "NV12";
+  else if (g_ascii_strcasecmp (subtype.c_str(), "YV12") == 0)
+    return "YV12";
+  else if (g_ascii_strcasecmp (subtype.c_str(), "IYUV") == 0)
+    return "I420";
+  /* FIXME: add more */
+
+  return std::string();
+}
+
+GstWinRTMediaDescription::GstWinRTMediaDescription()
+  : caps_(nullptr)
+{
+}
+
+GstWinRTMediaDescription::GstWinRTMediaDescription
+    (const GstWinRTMediaDescription& other)
+  : caps_(nullptr)
+{
+  if (other.source_id_.IsValid())
+    other.source_id_.CopyTo(source_id_.GetAddressOf());
+  if (other.subtype_.IsValid())
+    other.subtype_.CopyTo(subtype_.GetAddressOf());
+  gst_caps_replace (&caps_, other.caps_);
+}
+
+GstWinRTMediaDescription::~GstWinRTMediaDescription()
+{
+  Release();
+}
+
+void
+GstWinRTMediaDescription::Release()
+{
+  source_id_.Release();
+  subtype_.Release();
+  gst_clear_caps(&caps_);
+}
+
+bool
+GstWinRTMediaDescription::IsValid() const
+{
+  if (!source_id_.IsValid())
+    return false;
+  if (!subtype_.IsValid())
+    return false;
+  if (!caps_)
+    return false;
+
+  return true;
+}
+
+HRESULT
+GstWinRTMediaDescription::Fill(HString &source_id,
+    const ComPtr<IMediaCaptureVideoProfileMediaDescription>& desc)
+{
+  Release();
+
+  if (!source_id.IsValid()) {
+    GST_WARNING("Invalid source id");
+    return E_FAIL;
+  }
+
+  ComPtr<IMediaCaptureVideoProfileMediaDescription2> desc2;
+  UINT32 width = 0;
+  UINT32 height = 0;
+  DOUBLE framerate = 0;
+  gint fps_n = 0, fps_d = 1;
+  HString hstr_subtype;
+  std::string subtype;
+  std::string format;
+  GstCaps *caps;
+  HRESULT hr;
+
+  hr = desc.As (&desc2);
+  if (!gst_mf_result (hr))
+    return hr;
+
+  hr = desc->get_Width (&width);
+  if (!gst_mf_result (hr))
+    return hr;
+
+  hr = desc->get_Height (&height);
+  if (!gst_mf_result (hr))
+    return hr;
+
+  hr = desc->get_FrameRate (&framerate);
+  if (gst_mf_result (hr) && framerate > 0)
+    gst_util_double_to_fraction (framerate, &fps_n, &fps_d);
+
+  hr = desc2->get_Subtype (hstr_subtype.GetAddressOf ());
+  if (!gst_mf_result (hr))
+    return hr;
+
+  subtype = convert_hstring_to_string (&hstr_subtype);
+  if (subtype.empty())
+    return E_FAIL;
+
+  format = gst_media_capture_subtype_to_video_format (subtype);
+  if (format.empty()) {
+    GST_FIXME ("Unhandled subtype %s", subtype.c_str());
+    return E_FAIL;
+  }
+
+  caps = gst_caps_new_simple ("video/x-raw",
+      "format", G_TYPE_STRING, format.c_str(), "width", G_TYPE_INT, width,
+      "height", G_TYPE_INT, height, NULL);
+
+  if (fps_n > 0 && fps_d > 0)
+    gst_caps_set_simple (caps,
+        "framerate", GST_TYPE_FRACTION, fps_n, fps_d, NULL);
+
+  source_id.CopyTo (source_id_.GetAddressOf());
+  hstr_subtype.CopyTo (subtype_.GetAddressOf());
+  caps_ = caps;
+
+  return S_OK;
+}
+
+GstWinRTMediaFrameSourceGroup::GstWinRTMediaFrameSourceGroup()
+{
+}
+
+GstWinRTMediaFrameSourceGroup::GstWinRTMediaFrameSourceGroup
+    (const GstWinRTMediaFrameSourceGroup& other)
+{
+  id_ = other.id_;
+  display_name_ = other.display_name_;
+  source_group_ = other.source_group_;
+  source_list_ = other.source_list_;
+}
+
+GstWinRTMediaFrameSourceGroup::~GstWinRTMediaFrameSourceGroup()
+{
+  Release ();
+}
+
+void
+GstWinRTMediaFrameSourceGroup::Release()
+{
+  id_.clear ();
+  display_name_.clear ();
+  source_group_.Reset ();
+  source_list_.clear ();
+}
+
+bool
+GstWinRTMediaFrameSourceGroup::Contain(const GstWinRTMediaDescription& desc)
+{
+  if (!desc.IsValid())
+    return false;
+
+  if (source_list_.empty())
+    return false;
+
+  for (const auto& iter: source_list_) {
+    unsigned int dummy;
+    if (wcscmp (iter.source_id_.GetRawBuffer (&dummy),
+        desc.source_id_.GetRawBuffer (&dummy)))
+      continue;
+
+    if (wcscmp (iter.subtype_.GetRawBuffer (&dummy),
+        desc.subtype_.GetRawBuffer (&dummy)))
+      continue;
+
+    if (gst_caps_is_equal (iter.caps_, desc.caps_))
+      return true;
+  }
+
+  return false;
+}
+
+HRESULT
+GstWinRTMediaFrameSourceGroup::Fill
+    (const ComPtr<IMediaFrameSourceGroup> &source_group)
+{
+  HRESULT hr = S_OK;
+  HString hstr_id;
+  HString hstr_display_name;
+  ComPtr<IVectorView<MediaFrameSourceInfo*>> info_list;
+  UINT32 count = 0;
+
+  Release();
+
+  hr = source_group->get_Id(hstr_id.GetAddressOf());
+  if (!gst_mf_result(hr))
+    goto done;
+
+  id_ = convert_hstring_to_string (&hstr_id);
+  if (id_.empty()) {
+    GST_WARNING ("Emptry source group id");
+    hr = E_FAIL;
+    goto done;
+  }
+
+  hr = source_group->get_DisplayName (hstr_display_name.GetAddressOf());
+  if (!gst_mf_result (hr))
+    goto done;
+
+  display_name_ = convert_hstring_to_string (&hstr_display_name);
+  if (display_name_.empty()) {
+    GST_WARNING ("Empty display name");
+    hr = E_FAIL;
+    goto done;
+  }
+
+  hr = source_group->get_SourceInfos (&info_list);
+  if (!gst_mf_result (hr))
+    goto done;
+
+  hr = info_list->get_Size (&count);
+  if (!gst_mf_result (hr))
+    goto done;
+
+  if (count == 0) {
+    GST_WARNING ("No available source info");
+    hr = E_FAIL;
+    goto done;
+  }
+
+  source_group_ = source_group;
+
+  GST_DEBUG ("source-group has %d entries", count);
+
+  for (UINT32 i = 0; i < count; i++) {
+    ComPtr<IMediaFrameSourceInfo> info;
+    ComPtr<IMediaFrameSourceInfo2> info2;
+    ComPtr<IVectorView<MediaCaptureVideoProfileMediaDescription*>> desc_list;
+    MediaFrameSourceKind source_kind;
+    MediaStreamType source_type;
+    UINT32 desc_count = 0;
+    HString source_id;
+
+    hr = info_list->GetAt(i, &info);
+    if (!gst_mf_result (hr))
+      continue;
+
+    hr = info.As (&info2);
+    if (!gst_mf_result (hr))
+      continue;
+
+    hr = info->get_SourceKind (&source_kind);
+    if (!gst_mf_result (hr))
+      continue;
+
+    /* This can be depth, infrared or others */
+    /* FIXME: add audio support */
+    if (source_kind != MediaFrameSourceKind::MediaFrameSourceKind_Color) {
+      GST_FIXME ("source-info has non-color source kind %d",
+          (gint) source_kind);
+      continue;
+    }
+
+    hr = info->get_MediaStreamType (&source_type);
+    if (!gst_mf_result (hr))
+      continue;
+
+    /* FIXME: support audio */
+    if (source_type != MediaStreamType::MediaStreamType_VideoPreview &&
+        source_type != MediaStreamType::MediaStreamType_VideoRecord) {
+      continue;
+    }
+
+    hr = info->get_Id (source_id.GetAddressOf ());
+    if (!gst_mf_result (hr))
+      continue;
+
+    hr = info2->get_VideoProfileMediaDescription (&desc_list);
+    if (!gst_mf_result (hr))
+      continue;
+
+    hr = desc_list->get_Size (&desc_count);
+    if (!gst_mf_result (hr))
+      continue;
+
+    if (desc_count == 0) {
+      GST_WARNING("source-info has empty media description");
+      continue;
+    }
+
+    for (UINT32 j = 0; j < desc_count; j++) {
+      ComPtr<IMediaCaptureVideoProfileMediaDescription> desc;
+
+      hr = desc_list->GetAt (j, &desc);
+      if (!gst_mf_result (hr))
+        continue;
+
+      GstWinRTMediaDescription media_desc;
+      hr = media_desc.Fill(source_id, desc);
+      if (!gst_mf_result(hr))
+        continue;
+
+      source_list_.push_back(media_desc);
+    }
+  }
+
+done:
+  if (source_list_.empty()) {
+    GST_WARNING ("No usable source infos");
+    hr = E_FAIL;
+  }
+
+  if (!gst_mf_result(hr))
+    Release();
+
+  return hr;
+}
+
+MediaCaptureWrapper::MediaCaptureWrapper()
+  : user_data_(nullptr)
+{
+  user_cb_.frame_arrived = nullptr;
+  user_cb_.failed = nullptr;
+
+  /* Store CoreDispatecher if available */
+  findCoreDispatcher();
+}
+
+MediaCaptureWrapper::~MediaCaptureWrapper()
+{
+  stopCapture();
+
+  if (frame_reader_)
+    frame_reader_->remove_FrameArrived (token_frame_arrived_);
+
+  if (media_capture_)
+    media_capture_->remove_Failed (token_capture_failed_);
+}
+
+void
+MediaCaptureWrapper::RegisterCb (const MediaCaptureWrapperCallbacks &cb,
+    void * user_data)
+{
+  user_cb_.frame_arrived = cb.frame_arrived;
+  user_cb_.failed = cb.failed;
+  user_data_ = user_data;
+}
+
+HRESULT
+MediaCaptureWrapper::EnumrateFrameSourceGroup
+    (std::vector<GstWinRTMediaFrameSourceGroup> &group_list)
+{
+  HRESULT hr = S_OK;
+
+  if (dispatcher_) {
+    hr = runOnUIThread (INFINITE,
+      [this, &group_list] {
+          return enumrateFrameSourceGroup(group_list);
+      });
+
+  } else {
+    hr = enumrateFrameSourceGroup(group_list);
+  }
+
+  return hr;
+}
+
+HRESULT
+MediaCaptureWrapper::SetSourceGroup(const GstWinRTMediaFrameSourceGroup &group)
+{
+  if (group.source_group_ == nullptr) {
+    GST_WARNING ("Invalid MediaFrameSourceGroup");
+    return E_FAIL;
+  }
+
+  if (group.source_list_.empty()) {
+    GST_WARNING ("group doesn't include source lifo");
+    return E_FAIL;
+  }
+
+  source_group_ =
+      std::unique_ptr<GstWinRTMediaFrameSourceGroup>
+      (new GstWinRTMediaFrameSourceGroup(group));
+
+  return S_OK;
+}
+
+HRESULT
+MediaCaptureWrapper::SetMediaDescription(const GstWinRTMediaDescription &desc)
+{
+  /* Must be source group was specified before this */
+  if (source_group_ == nullptr) {
+    GST_WARNING ("No frame source group was specified");
+    return E_FAIL;
+  }
+
+  if (!desc.IsValid()) {
+    GST_WARNING("Invalid MediaDescription");
+    return E_FAIL;
+  }
+
+  if (!source_group_->Contain(desc)) {
+    GST_WARNING ("MediaDescription is not part of current source group");
+    return E_FAIL;
+  }
+
+  media_desc_ =
+      std::unique_ptr<GstWinRTMediaDescription>
+      (new GstWinRTMediaDescription(desc));
+
+  return S_OK;
+}
+
+HRESULT
+MediaCaptureWrapper::StartCapture()
+{
+  HRESULT hr = S_OK;
+
+  hr = openMediaCapture();
+  if (!gst_mf_result (hr))
+    return hr;
+
+  if (dispatcher_) {
+    hr = runOnUIThread (INFINITE,
+      [this] {
+          return startCapture();
+      });
+
+  } else {
+    hr = startCapture();
+  }
+
+  return S_OK;
+}
+
+HRESULT
+MediaCaptureWrapper::StopCapture()
+{
+  HRESULT hr = S_OK;
+
+  if (dispatcher_) {
+    hr = runOnUIThread (INFINITE,
+      [this] {
+          return stopCapture();
+      });
+
+  } else {
+    hr = stopCapture();
+  }
+
+  return S_OK;
+}
+
+HRESULT
+MediaCaptureWrapper::GetAvailableDescriptions
+    (std::vector<GstWinRTMediaDescription> &desc_list)
+{
+  desc_list.clear();
+
+  if (!source_group_) {
+    GST_WARNING ("No frame source group available");
+    return E_FAIL;
+  }
+
+  desc_list = source_group_->source_list_;
+
+  return S_OK;
+}
+
+HRESULT
+MediaCaptureWrapper::openMediaCapture()
+{
+  HRESULT hr;
+
+  if (frame_reader_) {
+    GST_INFO ("Frame reader was configured");
+    return S_OK;
+  }
+
+  if (source_group_ == nullptr) {
+    GST_WARNING ("No frame source group was specified");
+    return E_FAIL;
+  }
+
+  if (media_desc_ == nullptr) {
+    GST_WARNING ("No media description was specified");
+    return E_FAIL;
+  }
+
+  hr = mediaCaptureInitPre ();
+  if (!gst_mf_result (hr))
+    return hr;
+
+  /* Wait user action and resulting mediaCaptureInitPost */
+  std::unique_lock<std::mutex> Lock(lock_);
+  if (!init_done_)
+    cond_.wait (Lock);
+
+  return frame_reader_ ? S_OK : E_FAIL;
+}
+
+HRESULT
+MediaCaptureWrapper::mediaCaptureInitPre()
+{
+  ComPtr<IAsyncAction> async_action;
+  HRESULT hr;
+
+  auto work_item = Callback<Implements<RuntimeClassFlags<ClassicCom>,
+        IDispatchedHandler, FtmBase>>([this]{
+    ComPtr<IMediaCaptureInitializationSettings> settings;
+    ComPtr<IMediaCaptureInitializationSettings5> settings5;
+    ComPtr<IMediaCapture> media_capture;
+    ComPtr<IMediaCapture5> media_capture5;
+    ComPtr<IAsyncAction> init_async;
+    HRESULT hr;
+    HStringReference hstr_setting =
+        HStringReference (
+            RuntimeClass_Windows_Media_Capture_MediaCaptureInitializationSettings);
+    HStringReference hstr_capture =
+        HStringReference (RuntimeClass_Windows_Media_Capture_MediaCapture);
+    IMediaFrameSourceGroup * source_group =
+        source_group_->source_group_.Get();
+
+    hr = ActivateInstance (hstr_setting.Get(), &settings);
+    if (!gst_mf_result (hr))
+      return hr;
+
+    hr = settings->put_StreamingCaptureMode (
+        StreamingCaptureMode::StreamingCaptureMode_Video);
+    if (!gst_mf_result (hr))
+      return hr;
+
+    hr = settings.As (&settings5);
+    if (!gst_mf_result (hr))
+      return hr;
+
+    hr = settings5->put_SourceGroup (source_group);
+    if (!gst_mf_result (hr))
+      return hr;
+
+    /* TODO: support D3D11 memory */
+    hr = settings5->put_MemoryPreference (
+        MediaCaptureMemoryPreference::MediaCaptureMemoryPreference_Cpu);
+    if (!gst_mf_result (hr))
+      return hr;
+
+    hr = settings5.As (&settings);
+    if (!gst_mf_result (hr))
+      return hr;
+
+    hr = ActivateInstance (hstr_capture.Get(), &media_capture5);
+    if (!gst_mf_result (hr))
+      return hr;
+
+    hr = media_capture5.As (&media_capture);
+    if (!gst_mf_result (hr))
+      return hr;
+
+    hr = media_capture->InitializeWithSettingsAsync (settings.Get(), &init_async);
+    if (!gst_mf_result (hr))
+      return hr;
+
+    return StartAsyncThen(
+        init_async.Get(),
+        [this, init_async, media_capture](_In_ HRESULT hr,
+        _In_ IAsyncAction *asyncResult, _In_ AsyncStatus asyncStatus) -> HRESULT
+      {
+        return mediaCaptureInitPost (init_async, media_capture);
+      });
+  });
+
+  init_done_ = false;
+
+  if (dispatcher_) {
+    hr = dispatcher_->RunAsync(CoreDispatcherPriority_Normal, work_item.Get(),
+          &async_action);
+  } else {
+    hr = work_item->Invoke ();
+  }
+
+  return hr;
+}
+
+HRESULT
+MediaCaptureWrapper::mediaCaptureInitPost (ComPtr<IAsyncAction> init_async,
+    ComPtr<IMediaCapture> media_capture)
+{
+  std::unique_lock<std::mutex> Lock(lock_);
+  ComPtr<IMediaFrameSource> frame_source;
+  ComPtr<IMapView<HSTRING, MediaFrameSource*>> frameSources;
+  ComPtr<IMediaFrameSource> source;
+  ComPtr<IMediaFrameFormat> format;
+  ComPtr<IVectorView<MediaFrameFormat*>> formatList;
+  ComPtr<IMediaCapture5> media_capture5;
+  ComPtr<IAsyncAction> set_format_async;
+  ComPtr<IAsyncOperation<MediaFrameReader*>> create_reader_async;
+  ComPtr<IMediaFrameReader> frame_reader;
+  boolean has_key;
+  UINT32 count = 0;
+  GstVideoInfo videoInfo;
+  HRESULT hr;
+  ComPtr<ITypedEventHandler<MediaFrameReader*, MediaFrameArrivedEventArgs*>>
+      frame_arrived_handler = Callback<ITypedEventHandler<MediaFrameReader*,
+          MediaFrameArrivedEventArgs*>> ([&]
+          (IMediaFrameReader * reader, IMediaFrameArrivedEventArgs* args)
+            {
+                return onFrameArrived(reader, args);
+            }
+        );
+
+  GST_DEBUG ("InitializeWithSettingsAsync done");
+
+  hr = init_async->GetResults ();
+  if (!gst_mf_result (hr))
+    goto done;
+
+  if (!gst_video_info_from_caps (&videoInfo, media_desc_->caps_)) {
+    GST_WARNING ("Couldn't convert caps to videoinfo");
+    hr = E_FAIL;
+    goto done;
+  }
+
+  hr = media_capture.As (&media_capture5);
+  if (!gst_mf_result (hr))
+    goto done;
+
+  hr = media_capture5->get_FrameSources (&frameSources);
+  if (!gst_mf_result (hr))
+    goto done;
+
+  hr = frameSources->HasKey (media_desc_->source_id_.Get(), &has_key);
+  if (!gst_mf_result (hr))
+    goto done;
+
+  if (!has_key) {
+    GST_ERROR ("MediaFrameSource unavailable");
+    hr = E_FAIL;
+    goto done;
+  }
+
+  hr = frameSources->Lookup (media_desc_->source_id_.Get(), &source);
+  if (!gst_mf_result (hr))
+    goto done;
+
+  hr = source->get_SupportedFormats (&formatList);
+  if (!gst_mf_result (hr))
+    goto done;
+
+  hr = formatList->get_Size (&count);
+  if (!gst_mf_result (hr))
+    goto done;
+
+  if (count == 0) {
+    GST_ERROR ("No supported format object");
+    hr = E_FAIL;
+    goto done;
+  }
+
+  /* FIXME: support audio */
+  for (UINT32 i = 0; i < count; i++) {
+    ComPtr<IMediaFrameFormat> fmt;
+    ComPtr<IVideoMediaFrameFormat> videoFmt;
+    ComPtr<IMediaRatio> ratio;
+    HString subtype;
+    UINT32 width = 0;
+    UINT32 height = 0;
+
+    hr = formatList->GetAt (i, &fmt);
+    if (!gst_mf_result (hr))
+      continue;
+
+    hr = fmt->get_VideoFormat (&videoFmt);
+    if (!gst_mf_result (hr))
+      continue;
+
+    hr = videoFmt->get_Width (&width);
+    if (!gst_mf_result (hr))
+      continue;
+
+    hr = videoFmt->get_Height (&height);
+    if (!gst_mf_result (hr))
+      continue;
+
+    if (width != GST_VIDEO_INFO_WIDTH (&videoInfo))
+      continue;
+
+    if (height != GST_VIDEO_INFO_HEIGHT (&videoInfo))
+      continue;
+
+    /* TODO: check major type for audio */
+    hr = fmt->get_Subtype (subtype.GetAddressOf ());
+    if (!gst_mf_result (hr))
+      continue;
+
+    if (wcscmp (subtype.GetRawBuffer (nullptr),
+        media_desc_->subtype_.GetRawBuffer (nullptr)))
+      continue;
+
+    format = fmt;
+    break;
+  }
+
+  if (!format) {
+    GST_ERROR (
+        "Couldn't find matching IMediaFrameFormat interface");
+    hr = E_FAIL;
+    goto done;
+  }
+
+  hr = source->SetFormatAsync (format.Get (), &set_format_async);
+  if (!gst_mf_result (hr))
+    goto done;
+
+  hr = SyncWait<void>(set_format_async.Get ());
+  if (!gst_mf_result (hr))
+    goto done;
+
+  hr = set_format_async->GetResults ();
+  if (!gst_mf_result (hr))
+    goto done;
+
+  hr = media_capture5->CreateFrameReaderAsync (source.Get(),
+      &create_reader_async);
+  if (!gst_mf_result (hr))
+    goto done;
+
+  hr = SyncWait<MediaFrameReader*>(create_reader_async.Get());
+  if (!gst_mf_result (hr))
+    goto done;
+
+  hr = create_reader_async->GetResults(&frame_reader);
+  if (!gst_mf_result (hr))
+    goto done;
+
+  hr = frame_reader->add_FrameArrived (frame_arrived_handler.Get(),
+      &token_frame_arrived_);
+  if (!gst_mf_result (hr))
+    goto done;
+
+  hr = media_capture->add_Failed
+      (Callback<IMediaCaptureFailedEventHandler> ([this]
+          (IMediaCapture * capture, IMediaCaptureFailedEventArgs* args)
+            {
+                return onCaptureFailed(capture, args);
+            }
+        ).Get(),
+      &token_capture_failed_);
+
+  if (!gst_mf_result (hr))
+    goto done;
+
+  frame_reader_ = frame_reader;
+  media_capture_ = media_capture;
+
+done:
+  init_done_ = true;
+  cond_.notify_all();
+
+  return S_OK;
+}
+
+HRESULT
+MediaCaptureWrapper::startCapture()
+{
+  HRESULT hr;
+
+  if (!frame_reader_) {
+    GST_ERROR ("Frame reader wasn't configured");
+    return E_FAIL;
+  }
+
+  ComPtr<IAsyncOperation<MediaFrameReaderStartStatus>> start_async;
+  hr = frame_reader_->StartAsync (&start_async);
+  if (!gst_mf_result (hr))
+    return hr;
+
+  hr = SyncWait<MediaFrameReaderStartStatus>(start_async.Get());
+  if (!gst_mf_result (hr))
+    return hr;
+
+  MediaFrameReaderStartStatus reader_status;
+  hr = start_async->GetResults(&reader_status);
+  if (!gst_mf_result (hr))
+    return hr;
+
+  if (reader_status !=
+      MediaFrameReaderStartStatus::MediaFrameReaderStartStatus_Success) {
+    GST_ERROR ("Cannot start frame reader, status %d",
+        (gint) reader_status);
+    return E_FAIL;
+  }
+
+  return S_OK;
+}
+
+HRESULT
+MediaCaptureWrapper::stopCapture()
+{
+  HRESULT hr = S_OK;
+
+  if (frame_reader_) {
+    ComPtr<IAsyncAction> async_action;
+
+    hr = frame_reader_->StopAsync (&async_action);
+    if (gst_mf_result (hr))
+      hr = SyncWait<void>(async_action.Get ());
+  }
+
+  return hr;
+}
+
+HRESULT
+MediaCaptureWrapper::onFrameArrived(IMediaFrameReader *reader,
+    IMediaFrameArrivedEventArgs *args)
+{
+  HRESULT hr;
+  ComPtr<IMediaFrameReference> frame_ref;
+  ComPtr<IVideoMediaFrame> video_frame;
+  ComPtr<ISoftwareBitmap> bitmap;
+
+  hr = reader->TryAcquireLatestFrame (&frame_ref);
+  if (!gst_mf_result (hr))
+    return hr;
+
+  if (!frame_ref)
+    return S_OK;
+
+  hr = frame_ref->get_VideoMediaFrame (&video_frame);
+  if (!gst_mf_result (hr))
+    return hr;
+
+  hr = video_frame->get_SoftwareBitmap (&bitmap);
+  if (!gst_mf_result (hr) || !bitmap)
+    return hr;
+
+  /* nothing to do if no callback was installed */
+  if (!user_cb_.frame_arrived)
+    return S_OK;
+
+  return user_cb_.frame_arrived (bitmap.Get(), user_data_);
+}
+
+HRESULT
+MediaCaptureWrapper::onCaptureFailed(IMediaCapture *capture,
+    IMediaCaptureFailedEventArgs *args)
+{
+  HRESULT hr;
+  UINT32 error_code = 0;
+  HString hstr_error_msg;
+  std::string error_msg;
+
+  hr = args->get_Code (&error_code);
+  gst_mf_result (hr);
+
+  hr = args->get_Message (hstr_error_msg.GetAddressOf());
+  gst_mf_result (hr);
+
+  error_msg = convert_hstring_to_string (&hstr_error_msg);
+
+  GST_WARNING ("Have error %s (%d)", error_msg.c_str(), error_code);
+
+  if (user_cb_.failed)
+    user_cb_.failed (error_msg, error_code, user_data_);
+
+  return S_OK;
+}
+
+void
+MediaCaptureWrapper::findCoreDispatcher()
+{
+  HStringReference hstr_core_app =
+      HStringReference(RuntimeClass_Windows_ApplicationModel_Core_CoreApplication);
+  HRESULT hr;
+
+  ComPtr<ICoreApplication> core_app;
+  hr = GetActivationFactory (hstr_core_app.Get(), &core_app);
+  if (!gst_mf_result(hr))
+    return;
+
+  ComPtr<ICoreImmersiveApplication> core_immersive_app;
+  hr = core_app.As(&core_immersive_app);
+  if (!gst_mf_result(hr))
+    return;
+
+  ComPtr<ICoreApplicationView> core_app_view;
+  hr = core_immersive_app->get_MainView (&core_app_view);
+  if (!gst_mf_result(hr))
+    return;
+
+  ComPtr<ICoreWindow> core_window;
+  hr = core_app_view->get_CoreWindow (&core_window);
+  if (!gst_mf_result(hr))
+    return;
+
+  hr = core_window->get_Dispatcher (&dispatcher_);
+  if (!gst_mf_result(hr))
+    return;
+
+  GST_DEBUG("Main UI dispatcher is available");
+}
+
+HRESULT
+MediaCaptureWrapper::enumrateFrameSourceGroup
+    (std::vector<GstWinRTMediaFrameSourceGroup> &groupList)
+{
+  ComPtr<IMediaFrameSourceGroupStatics> frame_source_group_statics;
+  ComPtr<IAsyncOperation<IVectorView<MediaFrameSourceGroup*>*>> async_op;
+  ComPtr<IVectorView<MediaFrameSourceGroup*>> source_group_list;
+  HRESULT hr;
+  unsigned int cnt = 0;
+  HStringReference hstr_frame_source_group =
+      HStringReference (
+          RuntimeClass_Windows_Media_Capture_Frames_MediaFrameSourceGroup);
+
+  groupList.clear();
+
+  hr = GetActivationFactory (hstr_frame_source_group.Get(),
+                            &frame_source_group_statics);
+  if (!gst_mf_result(hr))
+    return hr;
+
+  hr = frame_source_group_statics->FindAllAsync (&async_op);
+  if (!gst_mf_result(hr))
+    return hr;
+
+  hr = SyncWait<IVectorView<MediaFrameSourceGroup*>*>(async_op.Get(), 5000);
+  if (!gst_mf_result(hr))
+    return hr;
+
+  hr = async_op->GetResults (&source_group_list);
+  if (!gst_mf_result(hr))
+    return hr;
+
+  hr = source_group_list->get_Size (&cnt);
+  if (!gst_mf_result(hr))
+    return hr;
+
+  if (cnt == 0) {
+    GST_WARNING ("No available source group");
+    return E_FAIL;
+  }
+
+  GST_DEBUG("Have %u source group", cnt);
+
+  for (unsigned int i = 0; i < cnt; i++) {
+    ComPtr<IMediaFrameSourceGroup> group;
+
+    hr = source_group_list->GetAt (i, &group);
+    if (!gst_mf_result(hr))
+      continue;
+
+    GstWinRTMediaFrameSourceGroup source_group;
+    hr = source_group.Fill(group);
+    if (!gst_mf_result (hr))
+      continue;
+
+    groupList.push_back (source_group);
+  }
+
+  if (groupList.empty ()) {
+    GST_WARNING("No available source group");
+    return E_FAIL;
+  }
+
+  return S_OK;
+}
diff --git a/sys/mediafoundation/mediacapturewrapper.h b/sys/mediafoundation/mediacapturewrapper.h
new file mode 100644 (file)
index 0000000..2d029a1
--- /dev/null
@@ -0,0 +1,217 @@
+/* GStreamer
+ * Copyright (C) 2020 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.
+ */
+
+#ifndef __GST_MEDIA_CAPTURE_WRAPPER_H__
+#define __GST_MEDIA_CAPTURE_WRAPPER_H__
+
+#include <gst/gst.h>
+#include <wrl.h>
+#include <wrl/wrappers/corewrappers.h>
+#include <windows.media.capture.h>
+#include <vector>
+#include <memory>
+#include <string>
+#include <functional>
+#include <mutex>
+#include <condition_variable>
+
+using namespace Microsoft::WRL;
+using namespace Microsoft::WRL::Wrappers;
+
+using namespace ABI::Windows::Foundation;
+using namespace ABI::Windows::UI::Core;
+using namespace ABI::Windows::Media::Capture;
+using namespace ABI::Windows::Media::Capture::Frames;
+using namespace ABI::Windows::Graphics::Imaging;
+
+/* Store Format info and its caps representation */
+class GstWinRTMediaDescription
+{
+public:
+  GstWinRTMediaDescription();
+  GstWinRTMediaDescription(const GstWinRTMediaDescription& other);
+  ~GstWinRTMediaDescription();
+  void Release();
+  bool IsValid() const;
+  HRESULT Fill(HString &source_id,
+               const ComPtr<IMediaCaptureVideoProfileMediaDescription>& desc);
+
+  GstWinRTMediaDescription& operator=(const GstWinRTMediaDescription& rhs)
+  {
+    if (this == &rhs)
+      return *this;
+
+    Release();
+    if (rhs.source_id_.IsValid())
+      rhs.source_id_.CopyTo(source_id_.GetAddressOf());
+    if (rhs.subtype_.IsValid())
+      rhs.subtype_.CopyTo(subtype_.GetAddressOf());
+    gst_caps_replace (&caps_, rhs.caps_);
+
+    return *this;
+  }
+
+public:
+  HString source_id_;
+  /* TODO: need to cover audio too */
+  HString subtype_;
+  /* Source ID which is mapped to MediaFormatSource */
+  GstCaps *caps_;
+};
+
+/* holds GstWinRTMediaFrameSourceInfo, corresponding to per device info */
+class GstWinRTMediaFrameSourceGroup
+{
+public:
+  GstWinRTMediaFrameSourceGroup();
+  GstWinRTMediaFrameSourceGroup(const GstWinRTMediaFrameSourceGroup& other);
+  ~GstWinRTMediaFrameSourceGroup();
+  void Release();
+  bool Contain(const GstWinRTMediaDescription &desc);
+  HRESULT Fill(const ComPtr<IMediaFrameSourceGroup> &source_group);
+
+  GstWinRTMediaFrameSourceGroup& operator=(const GstWinRTMediaFrameSourceGroup& rhs)
+  {
+    if (this == &rhs)
+      return *this;
+
+    Release();
+    id_ = rhs.id_;
+    display_name_ = rhs.display_name_;
+    source_group_ = rhs.source_group_;
+    source_list_ = rhs.source_list_;
+
+    return *this;
+  }
+
+public:
+  std::string id_;
+  std::string display_name_;
+  ComPtr<IMediaFrameSourceGroup> source_group_;
+  std::vector<GstWinRTMediaDescription> source_list_;
+};
+
+typedef struct
+{
+  HRESULT (*frame_arrived) (ISoftwareBitmap * bitmap, void * user_data);
+  HRESULT (*failed)        (const std::string &error,
+                            UINT32 error_code,
+                            void * user_data);
+} MediaCaptureWrapperCallbacks;
+class MediaCaptureWrapper
+{
+public:
+  MediaCaptureWrapper();
+  ~MediaCaptureWrapper();
+
+  void RegisterCb(const MediaCaptureWrapperCallbacks &cb,
+                  void * user_data);
+
+  /* Enumerating available source devices */
+  /* Fill enumerated device infos into list */
+  HRESULT EnumrateFrameSourceGroup(std::vector<GstWinRTMediaFrameSourceGroup> &group_list);
+  /* Select target device which should be one of enumerated be fore */
+  HRESULT SetSourceGroup(const GstWinRTMediaFrameSourceGroup &group);
+  /* Select target format (resolution, video format) to use */
+  HRESULT SetMediaDescription(const GstWinRTMediaDescription &desc);
+
+  /* Start and Stop capturing operation */
+  HRESULT StartCapture();
+  HRESULT StopCapture();
+  HRESULT GetAvailableDescriptions(std::vector<GstWinRTMediaDescription> &desc_list);
+
+private:
+  ComPtr<IMediaCapture> media_capture_;
+  ComPtr<IMediaFrameReader> frame_reader_;
+  ComPtr<ICoreDispatcher> dispatcher_;
+  bool init_done_;
+  std::mutex lock_;
+  std::condition_variable cond_;
+
+  EventRegistrationToken token_frame_arrived_;
+  EventRegistrationToken token_capture_failed_;
+
+  std::unique_ptr<GstWinRTMediaFrameSourceGroup> source_group_;
+  std::unique_ptr<GstWinRTMediaDescription> media_desc_;
+  MediaCaptureWrapperCallbacks user_cb_;
+  void *user_data_;
+
+private:
+  HRESULT openMediaCapture();
+  HRESULT mediaCaptureInitPre();
+  HRESULT mediaCaptureInitPost(ComPtr<IAsyncAction> init_async,
+                               ComPtr<IMediaCapture> media_capture);
+  HRESULT startCapture();
+  HRESULT stopCapture();
+  HRESULT onFrameArrived(IMediaFrameReader *reader,
+                         IMediaFrameArrivedEventArgs *args);
+  HRESULT onCaptureFailed(IMediaCapture *capture,
+                          IMediaCaptureFailedEventArgs *args);
+  void findCoreDispatcher();
+  static HRESULT enumrateFrameSourceGroup(std::vector<GstWinRTMediaFrameSourceGroup> &list);
+
+  template <typename CB>
+  HRESULT runOnUIThread(DWORD timeout, CB && cb)
+  {
+    ComPtr<IAsyncAction> asyncAction;
+    HRESULT hr;
+    HRESULT hr_callback;
+    boolean can_now;
+    DWORD wait_ret;
+
+    if (!dispatcher_)
+      return cb();
+
+    hr = dispatcher_->get_HasThreadAccess (&can_now);
+
+    if (FAILED (hr))
+      return hr;
+
+    if (can_now)
+      return cb ();
+
+    Event event (CreateEventEx (NULL, NULL, CREATE_EVENT_MANUAL_RESET,
+        EVENT_ALL_ACCESS));
+
+    if (!event.IsValid())
+      return E_FAIL;
+
+    auto handler =
+        Callback<Implements<RuntimeClassFlags<ClassicCom>,
+            IDispatchedHandler, FtmBase>>([&hr_callback, &cb, &event] {
+          hr_callback = cb ();
+          SetEvent (event.Get());
+          return S_OK;
+        });
+
+    hr = dispatcher_->RunAsync(CoreDispatcherPriority_Normal,
+        handler.Get(), &asyncAction);
+
+    if (FAILED (hr))
+      return hr;
+
+    wait_ret = WaitForSingleObject(event.Get(), timeout);
+    if (wait_ret != WAIT_OBJECT_0)
+      return E_FAIL;
+
+    return hr_callback;
+  }
+};
+
+#endif /* __GST_MEDIA_CAPTURE_WRAPPER_H__ */
\ No newline at end of file
index 3be068c..710ccb2 100644 (file)
@@ -5,13 +5,18 @@ mf_sources = [
   'gstmfvideoenc.cpp',
   'gstmfh264enc.cpp',
   'gstmfh265enc.cpp',
+  'gstmfvideosrc.c',
+  'gstmfsourceobject.c',
+  'gstmfdevice.c',
 ]
 
 mf_desktop_sources = [
-  'gstmfvideosrc.c',
-  'gstmfsourceobject.c',
   'gstmfsourcereader.cpp',
-  'gstmfdevice.c'
+]
+
+mf_app_sources = [
+  'gstmfcapturewinrt.cpp',
+  'mediacapturewrapper.cpp',
 ]
 
 mf_header_deps = [
@@ -25,8 +30,8 @@ mf_header_deps = [
 ]
 
 winapi_desktop = false
+winapi_app = false
 have_capture_engine = false
-extra_c_args = ['-DCOBJMACROS']
 mf_lib_deps = []
 mf_config = configuration_data()
 
@@ -47,6 +52,7 @@ mfplat_lib = cc.find_library('mfplat', required : mf_option)
 mfreadwrite_lib = cc.find_library('mfreadwrite', required : mf_option)
 mfuuid_lib = cc.find_library('mfuuid', required : mf_option)
 strmiids_lib = cc.find_library('strmiids', required : mf_option)
+runtimeobject_lib = cc.find_library('runtimeobject', required : false)
 
 have_mf_lib = mf_lib.found() and mfplat_lib.found() and mfreadwrite_lib.found() and mfuuid_lib.found() and strmiids_lib.found()
 if not have_mf_lib
@@ -80,7 +86,26 @@ winapi_desktop = cxx.compiles('''#include <winapifamily.h>
     dependencies: mf_lib_deps,
     name: 'checking if building for Win32')
 
-if winapi_desktop
+if runtimeobject_lib.found()
+  winapi_app = cxx.compiles('''#include <winapifamily.h>
+      #include <windows.applicationmodel.core.h>
+      #include <wrl.h>
+      #if !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP)
+      #error "not winrt"
+      #endif''',
+      dependencies: [mf_lib_deps, runtimeobject_lib],
+      name: 'checking if building for WinRT')
+endif
+
+if not winapi_desktop and not winapi_app
+  error('Neither Desktop partition nor App partition')
+endif
+
+winapi_app_only = winapi_app and not winapi_desktop
+if winapi_app_only
+  mf_sources += mf_app_sources
+  mf_lib_deps += [runtimeobject_lib]
+else
   mf_sources += mf_desktop_sources
   have_capture_engine = cc.has_header('mfcaptureengine.h')
   if have_capture_engine
@@ -88,7 +113,8 @@ if winapi_desktop
   endif
 endif
 
-mf_config.set10('HAVE_CAPTURE_ENGINE', have_capture_engine)
+mf_config.set10('GST_MF_HAVE_CAPTURE_ENGINE', have_capture_engine)
+mf_config.set10('GST_MF_WINAPI_ONLY_APP', winapi_app_only)
 
 configure_file(
   output: 'gstmfconfig.h',
@@ -97,7 +123,7 @@ configure_file(
 
 gstmediafoundation = library('gstmediafoundation',
   mf_sources,
-  c_args : gst_plugins_bad_args + extra_c_args,
+  c_args : gst_plugins_bad_args + ['-DCOBJMACROS'],
   cpp_args : gst_plugins_bad_args,
   include_directories : [configinc],
   dependencies : [gstbase_dep, gstvideo_dep, gstpbutils_dep] + mf_lib_deps,
index 189ed71..2d3312b 100644 (file)
 #include "config.h"
 #endif
 
+#include "gstmfconfig.h"
+
 #include <winapifamily.h>
 
 #include <gst/gst.h>
-#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
 #include "gstmfvideosrc.h"
 #include "gstmfdevice.h"
-#endif
 #include "gstmfutils.h"
 #include "gstmfh264enc.h"
 #include "gstmfh265enc.h"
 
 GST_DEBUG_CATEGORY (gst_mf_debug);
 GST_DEBUG_CATEGORY (gst_mf_utils_debug);
-#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
 GST_DEBUG_CATEGORY (gst_mf_source_object_debug);
-#endif
 GST_DEBUG_CATEGORY (gst_mf_transform_debug);
 
 #define GST_CAT_DEFAULT gst_mf_debug
@@ -46,14 +44,13 @@ static gboolean
 plugin_init (GstPlugin * plugin)
 {
   HRESULT hr;
+  GstRank rank = GST_RANK_SECONDARY;
 
   GST_DEBUG_CATEGORY_INIT (gst_mf_debug, "mf", 0, "media foundation");
   GST_DEBUG_CATEGORY_INIT (gst_mf_utils_debug,
       "mfutils", 0, "media foundation utility functions");
-#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
   GST_DEBUG_CATEGORY_INIT (gst_mf_source_object_debug,
       "mfsourceobject", 0, "mfsourceobject");
-#endif
   GST_DEBUG_CATEGORY_INIT (gst_mf_transform_debug,
       "mftransform", 0, "mftransform");
 
@@ -62,13 +59,16 @@ plugin_init (GstPlugin * plugin)
     GST_WARNING ("MFStartup failure, hr: 0x%x", hr);
     return TRUE;
   }
-#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
-  gst_element_register (plugin,
-      "mfvideosrc", GST_RANK_SECONDARY, GST_TYPE_MF_VIDEO_SRC);
-  gst_device_provider_register (plugin, "mfdeviceprovider",
-      GST_RANK_SECONDARY, GST_TYPE_MF_DEVICE_PROVIDER);
+
+  /* mfvideosrc should be primary rank for UWP */
+#if GST_MF_WINAPI_ONLY_APP
+  rank = GST_RANK_PRIMARY + 1;
 #endif
 
+  gst_element_register (plugin, "mfvideosrc", rank, GST_TYPE_MF_VIDEO_SRC);
+  gst_device_provider_register (plugin, "mfdeviceprovider",
+      rank, GST_TYPE_MF_DEVICE_PROVIDER);
+
   gst_mf_h264_enc_plugin_init (plugin, GST_RANK_SECONDARY);
   gst_mf_h265_enc_plugin_init (plugin, GST_RANK_SECONDARY);