media: MediaEngineExtension

This CL adds MediaEngineExtension to be used by
MediaFoundationRenderer. MediaEngineExtension implements
IMFMediaEngineExtension.

Explainer:
https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/master/Media/MFCdm/explainer.md
Design Doc:
https://docs.google.com/document/d/1kXMdmfsSKU3tcwTxG-wFU-MaEA6sN3by45PCGBV8zJU

Bug: 999747
Change-Id: I429687815567bc1e60ec4a7cfce5e3359e0c72a2
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2100008
Commit-Queue: Frank Li <frankli@microsoft.com>
Reviewed-by: Xiaohan Wang <xhwang@chromium.org>
Cr-Commit-Position: refs/heads/master@{#754330}
diff --git a/media/renderers/BUILD.gn b/media/renderers/BUILD.gn
index f18dedf4..7cd29bd 100644
--- a/media/renderers/BUILD.gn
+++ b/media/renderers/BUILD.gn
@@ -106,6 +106,8 @@
 if (is_win) {
   source_set("media_foundation_renderer") {
     sources = [
+      "win/media_engine_extension.cc",
+      "win/media_engine_extension.h",
       "win/media_engine_notify_impl.cc",
       "win/media_engine_notify_impl.h",
       "win/media_foundation_audio_stream.cc",
diff --git a/media/renderers/win/media_engine_extension.cc b/media/renderers/win/media_engine_extension.cc
new file mode 100644
index 0000000..1fb3147
--- /dev/null
+++ b/media/renderers/win/media_engine_extension.cc
@@ -0,0 +1,115 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/renderers/win/media_engine_extension.h"
+
+#include <mferror.h>
+
+#include "media/base/win/mf_helpers.h"
+
+namespace media {
+
+using Microsoft::WRL::ComPtr;
+
+MediaEngineExtension::MediaEngineExtension() = default;
+MediaEngineExtension::~MediaEngineExtension() = default;
+
+HRESULT MediaEngineExtension::RuntimeClassInitialize() {
+  DVLOG(1) << __func__ << ": this=" << this;
+  return S_OK;
+}
+
+HRESULT MediaEngineExtension::CanPlayType(BOOL is_audio_only,
+                                          BSTR mime_type,
+                                          MF_MEDIA_ENGINE_CANPLAY* result) {
+  // We use MF_MEDIA_ENGINE_EXTENSION to resolve as custom media source for
+  // MFMediaEngine, MIME types are not used.
+  *result = MF_MEDIA_ENGINE_CANPLAY_NOT_SUPPORTED;
+  return S_OK;
+}
+
+HRESULT MediaEngineExtension::BeginCreateObject(BSTR url_bstr,
+                                                IMFByteStream* byte_stream,
+                                                MF_OBJECT_TYPE type,
+                                                IUnknown** cancel_cookie,
+                                                IMFAsyncCallback* callback,
+                                                IUnknown* state) {
+  DVLOG(1) << __func__ << ": this=" << this << ",type=" << type;
+
+  if (cancel_cookie) {
+    // We don't support a cancel cookie.
+    *cancel_cookie = nullptr;
+  }
+  ComPtr<IUnknown> local_source;
+  {
+    base::AutoLock lock(lock_);
+    if (has_shutdown_) {
+      return MF_E_SHUTDOWN;
+    }
+    local_source = mf_media_source_;
+  }
+
+  if (type == MF_OBJECT_MEDIASOURCE) {
+    DVLOG(2) << "Begin to resolve mf_media_source_: this=" << this;
+    DCHECK(local_source) << "Media Source should have been set";
+
+    ComPtr<IMFAsyncResult> async_result;
+    RETURN_IF_FAILED(MFCreateAsyncResult(local_source.Get(), callback, state,
+                                         &async_result));
+    RETURN_IF_FAILED(async_result->SetStatus(S_OK));
+    pending_create_object_ = true;
+    // Invoke the callback synchronously since no outstanding work is required.
+    RETURN_IF_FAILED(callback->Invoke(async_result.Get()));
+  } else {
+    // MediaEngine will try to resolve with different |type|.
+    return MF_E_UNEXPECTED;
+  }
+
+  return S_OK;
+}
+
+HRESULT MediaEngineExtension::CancelObjectCreation(
+    __in IUnknown* cancel_cookie) {
+  DVLOG(1) << __func__ << ": this=" << this;
+
+  return MF_E_UNEXPECTED;
+}
+
+HRESULT MediaEngineExtension::EndCreateObject(__in IMFAsyncResult* result,
+                                              __deref_out IUnknown** ret_obj) {
+  DVLOG(1) << __func__ << ": this=" << this;
+
+  *ret_obj = nullptr;
+  if (!pending_create_object_)
+    return MF_E_UNEXPECTED;
+
+  DVLOG(2) << "End to resolve mf_media_source_: this=" << this;
+  RETURN_IF_FAILED(result->GetStatus());
+  RETURN_IF_FAILED(result->GetObject(ret_obj));
+  pending_create_object_ = false;
+  return S_OK;
+}
+
+HRESULT MediaEngineExtension::SetMediaSource(IUnknown* mf_media_source) {
+  DVLOG(1) << __func__ << ": this=" << this;
+
+  base::AutoLock lock(lock_);
+  if (has_shutdown_)
+    return MF_E_SHUTDOWN;
+  mf_media_source_ = mf_media_source;
+  return S_OK;
+}
+
+// Break cycles.
+void MediaEngineExtension::Shutdown() {
+  DVLOG(1) << __func__ << ": this=" << this;
+
+  base::AutoLock lock(lock_);
+  if (!has_shutdown_) {
+    mf_media_source_.Reset();
+    has_shutdown_ = true;
+  }
+}
+
+}  // namespace media
diff --git a/media/renderers/win/media_engine_extension.h b/media/renderers/win/media_engine_extension.h
new file mode 100644
index 0000000..cf57631
--- /dev/null
+++ b/media/renderers/win/media_engine_extension.h
@@ -0,0 +1,61 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MEDIA_RENDERERS_WIN_MEDIA_ENGINE_EXTENSION_H_
+#define MEDIA_RENDERERS_WIN_MEDIA_ENGINE_EXTENSION_H_
+
+#include <mfapi.h>
+#include <mfmediaengine.h>
+#include <wrl.h>
+
+#include "base/synchronization/lock.h"
+
+namespace media {
+
+// Implement IMFMediaEngineExtension to load media source into the
+// IMFMediaEngine. See details from:
+// https://docs.microsoft.com/en-us/windows/win32/api/mfmediaengine/nn-mfmediaengine-imfmediaengineextension.
+//
+class MediaEngineExtension
+    : public Microsoft::WRL::RuntimeClass<
+          Microsoft::WRL::RuntimeClassFlags<
+              Microsoft::WRL::RuntimeClassType::ClassicCom>,
+          IMFMediaEngineExtension> {
+ public:
+  MediaEngineExtension();
+  ~MediaEngineExtension() override;
+
+  HRESULT RuntimeClassInitialize();
+
+  // IMFMediaEngineExtension
+  IFACEMETHODIMP CanPlayType(BOOL is_audio_only,
+                             BSTR mime_type,
+                             MF_MEDIA_ENGINE_CANPLAY* result) override;
+  IFACEMETHODIMP BeginCreateObject(BSTR url_bstr,
+                                   IMFByteStream* byte_stream,
+                                   MF_OBJECT_TYPE type,
+                                   IUnknown** cancel_cookie,
+                                   IMFAsyncCallback* callback,
+                                   IUnknown* state) override;
+  IFACEMETHODIMP CancelObjectCreation(IUnknown* cancel_cookie) override;
+  IFACEMETHODIMP EndCreateObject(IMFAsyncResult* result,
+                                 IUnknown** ret_obj) override;
+
+  HRESULT SetMediaSource(IUnknown* mf_media_source);
+  void Shutdown();
+
+ private:
+  bool pending_create_object_ = false;
+
+  // Need a lock to ensure thread safe operation between IMFMediaEngineExtension
+  // method calls from MFMediaEngine threadpool thread and
+  // SetMediaSource/Shutdown from MediaFoundationRenderer calling thread.
+  base::Lock lock_;
+  bool has_shutdown_ GUARDED_BY(lock_) = false;
+  Microsoft::WRL::ComPtr<IUnknown> mf_media_source_ GUARDED_BY(lock_);
+};
+
+}  // namespace media
+
+#endif  // MEDIA_RENDERERS_WIN_MEDIA_ENGINE_EXTENSION_H_