Bug 1471535 - pt7 - Add remote decoding in dom/media/ipc alongside gpu decoding. r=jya,mattwoodrow,jld
☠☠ backed out by f6d29b57e6ba ☠ ☠
authorMichael Froman <mfroman@mozilla.com>
Fri, 09 Nov 2018 00:12:46 +0000
changeset 445368 a32288737e57ffba27e3e7dffcbc0c263298104d
parent 445367 a094c1ac3afebc09939c3ec8e4e8d3b6a455970d
child 445369 1a991ac2e1f8db3b9410d1cea09686ebfc38ad1b
push id35015
push userdluca@mozilla.com
push dateFri, 09 Nov 2018 17:45:20 +0000
treeherdermozilla-central@2f1158e5e0ce [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjya, mattwoodrow, jld
bugs1471535
milestone65.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1471535 - pt7 - Add remote decoding in dom/media/ipc alongside gpu decoding. r=jya,mattwoodrow,jld Depends on D8488 Differential Revision: https://phabricator.services.mozilla.com/D8489
dom/media/ipc/MediaIPCUtils.h
dom/media/ipc/PRemoteDecoderManager.ipdl
dom/media/ipc/PRemoteVideoDecoder.ipdl
dom/media/ipc/RemoteDecoderManagerChild.cpp
dom/media/ipc/RemoteDecoderManagerChild.h
dom/media/ipc/RemoteDecoderManagerParent.cpp
dom/media/ipc/RemoteDecoderManagerParent.h
dom/media/ipc/RemoteDecoderModule.cpp
dom/media/ipc/RemoteDecoderModule.h
dom/media/ipc/RemoteMediaDataDecoder.h
dom/media/ipc/RemoteVideoDecoderChild.cpp
dom/media/ipc/RemoteVideoDecoderChild.h
dom/media/ipc/RemoteVideoDecoderParent.cpp
dom/media/ipc/RemoteVideoDecoderParent.h
dom/media/ipc/moz.build
dom/media/platforms/PlatformDecoderModule.h
ipc/ipdl/sync-messages.ini
--- a/dom/media/ipc/MediaIPCUtils.h
+++ b/dom/media/ipc/MediaIPCUtils.h
@@ -39,11 +39,54 @@ namespace IPC {
           ReadParam(aMsg, aIter, &aResult->mImage) &&
           ReadParam(aMsg, aIter, &imageRect)) {
         aResult->SetImageRect(imageRect);
         return true;
       }
       return false;
     }
   };
+
+  template<>
+  struct ParamTraits<mozilla::AudioInfo>
+  {
+    typedef mozilla::AudioInfo paramType;
+
+    static void Write(Message* aMsg, const paramType& aParam)
+    {
+      // TrackInfo
+      WriteParam(aMsg, aParam.mMimeType);
+
+      // AudioInfo
+      WriteParam(aMsg, aParam.mRate);
+      WriteParam(aMsg, aParam.mChannels);
+      WriteParam(aMsg, aParam.mChannelMap);
+      WriteParam(aMsg, aParam.mBitDepth);
+      WriteParam(aMsg, aParam.mProfile);
+      WriteParam(aMsg, aParam.mExtendedProfile);
+    }
+
+    static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
+    {
+      if (ReadParam(aMsg, aIter, &aResult->mMimeType) &&
+          ReadParam(aMsg, aIter, &aResult->mRate) &&
+          ReadParam(aMsg, aIter, &aResult->mChannels) &&
+          ReadParam(aMsg, aIter, &aResult->mChannelMap) &&
+          ReadParam(aMsg, aIter, &aResult->mBitDepth) &&
+          ReadParam(aMsg, aIter, &aResult->mProfile) &&
+          ReadParam(aMsg, aIter, &aResult->mExtendedProfile)) {
+        return true;
+      }
+      return false;
+    }
+  };
+
+  template<>
+  struct ParamTraits<mozilla::MediaDataDecoder::ConversionRequired>
+    : public ContiguousEnumSerializerInclusive<
+        mozilla::MediaDataDecoder::ConversionRequired,
+        mozilla::MediaDataDecoder::ConversionRequired(0),
+        mozilla::MediaDataDecoder::ConversionRequired(
+          mozilla::MediaDataDecoder::ConversionRequired::kNeedAnnexB)> {};
+
 } // namespace IPC
 
 #endif // mozilla_dom_media_MediaIPCUtils_h
new file mode 100644
--- /dev/null
+++ b/dom/media/ipc/PRemoteDecoderManager.ipdl
@@ -0,0 +1,27 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PRemoteVideoDecoder;
+include "mozilla/dom/MediaIPCUtils.h";
+
+using VideoInfo from "MediaInfo.h";
+using AudioInfo from "MediaInfo.h";
+using mozilla::CreateDecoderParams::OptionSet from "PlatformDecoderModule.h";
+
+namespace mozilla {
+
+sync protocol PRemoteDecoderManager
+{
+  manages PRemoteVideoDecoder;
+
+parent:
+  sync PRemoteVideoDecoder(VideoInfo info,
+                           float framerate,
+                           OptionSet options)
+         returns (bool success,
+                  nsCString aErrorDescription);
+};
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/media/ipc/PRemoteVideoDecoder.ipdl
@@ -0,0 +1,60 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include "mozilla/dom/MediaIPCUtils.h";
+
+include protocol PRemoteDecoderManager;
+include PMediaDecoderParams;
+include LayersSurfaces;
+using mozilla::MediaDataDecoder::ConversionRequired from "PlatformDecoderModule.h";
+
+namespace mozilla {
+
+struct RemoteVideoDataIPDL
+{
+  MediaDataIPDL base;
+  IntSize display;
+  IntSize frameSize;
+  SurfaceDescriptorBuffer sdBuffer;
+  int32_t frameID;
+};
+
+// This protocol provides a way to use MediaDataDecoder across processes.
+// The parent side currently is only implemented to work with
+// RemoteDecoderModule.
+// The child side runs in the content process, and the parent side runs
+// in the RDD process. We run a separate IPDL thread for both sides.
+async protocol PRemoteVideoDecoder
+{
+  manager PRemoteDecoderManager;
+parent:
+  async Init();
+
+  async Input(MediaRawDataIPDL data);
+
+  async Flush();
+  async Drain();
+  async Shutdown();
+
+  async SetSeekThreshold(int64_t time);
+
+  async __delete__();
+
+child:
+  async InitComplete(nsCString decoderDescription,
+                     ConversionRequired conversion);
+  async InitFailed(nsresult reason);
+
+  async FlushComplete();
+
+  // Each output includes a SurfaceDescriptorBuffer that represents the decoded
+  // frame.
+  async VideoOutput(RemoteVideoDataIPDL data);
+  async InputExhausted();
+  async DrainComplete();
+  async Error(nsresult error);
+};
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/media/ipc/RemoteDecoderManagerChild.cpp
@@ -0,0 +1,151 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=99: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "RemoteDecoderManagerChild.h"
+
+#include "base/task.h"
+
+#include "RemoteVideoDecoderChild.h"
+
+namespace mozilla {
+
+// Only modified on the main-thread
+StaticRefPtr<nsIThread> sRemoteDecoderManagerChildThread;
+StaticRefPtr<AbstractThread> sRemoteDecoderManagerChildAbstractThread;
+
+// Only accessed from sRemoteDecoderManagerChildThread
+static StaticRefPtr<RemoteDecoderManagerChild> sRemoteDecoderManagerChild;
+
+/* static */ void
+RemoteDecoderManagerChild::InitializeThread()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (!sRemoteDecoderManagerChildThread) {
+    RefPtr<nsIThread> childThread;
+    nsresult rv = NS_NewNamedThread("RemVidChild", getter_AddRefs(childThread));
+    NS_ENSURE_SUCCESS_VOID(rv);
+    sRemoteDecoderManagerChildThread = childThread;
+
+    sRemoteDecoderManagerChildAbstractThread =
+      AbstractThread::CreateXPCOMThreadWrapper(childThread, false);
+  }
+}
+
+/* static */ void
+RemoteDecoderManagerChild::InitForContent(
+    Endpoint<PRemoteDecoderManagerChild>&& aVideoManager)
+{
+  InitializeThread();
+  sRemoteDecoderManagerChildThread->Dispatch(
+      NewRunnableFunction("InitForContentRunnable",
+                          &Open,
+                          std::move(aVideoManager)),
+      NS_DISPATCH_NORMAL);
+}
+
+/* static */ void
+RemoteDecoderManagerChild::Shutdown()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (sRemoteDecoderManagerChildThread) {
+    sRemoteDecoderManagerChildThread->Dispatch(
+        NS_NewRunnableFunction("dom::RemoteDecoderManagerChild::Shutdown",
+                               []() {
+                                 if (sRemoteDecoderManagerChild &&
+                                     sRemoteDecoderManagerChild->CanSend()) {
+                                   sRemoteDecoderManagerChild->Close();
+                                   sRemoteDecoderManagerChild = nullptr;
+                                 }
+                               }),
+        NS_DISPATCH_NORMAL);
+
+    sRemoteDecoderManagerChildAbstractThread = nullptr;
+    sRemoteDecoderManagerChildThread->Shutdown();
+    sRemoteDecoderManagerChildThread = nullptr;
+  }
+}
+
+/* static */ RemoteDecoderManagerChild*
+RemoteDecoderManagerChild::GetSingleton()
+{
+  MOZ_ASSERT(NS_GetCurrentThread() == GetManagerThread());
+  return sRemoteDecoderManagerChild;
+}
+
+/* static */ nsIThread*
+RemoteDecoderManagerChild::GetManagerThread()
+{
+  return sRemoteDecoderManagerChildThread;
+}
+
+/* static */ AbstractThread*
+RemoteDecoderManagerChild::GetManagerAbstractThread()
+{
+  return sRemoteDecoderManagerChildAbstractThread;
+}
+
+PRemoteVideoDecoderChild*
+RemoteDecoderManagerChild::AllocPRemoteVideoDecoderChild(
+    const VideoInfo& /* not used */,
+    const float& /* not used */,
+    const CreateDecoderParams::OptionSet& /* not used */,
+    bool* /* not used */,
+    nsCString* /* not used */)
+{
+  return new RemoteVideoDecoderChild();
+}
+
+bool
+RemoteDecoderManagerChild::DeallocPRemoteVideoDecoderChild(
+    PRemoteVideoDecoderChild* actor)
+{
+  RemoteVideoDecoderChild* child = static_cast<RemoteVideoDecoderChild*>(actor);
+  child->IPDLActorDestroyed();
+  return true;
+}
+
+void
+RemoteDecoderManagerChild::Open(
+    Endpoint<PRemoteDecoderManagerChild>&& aEndpoint)
+{
+  sRemoteDecoderManagerChild = nullptr;
+  if (aEndpoint.IsValid()) {
+    RefPtr<RemoteDecoderManagerChild> manager = new RemoteDecoderManagerChild();
+    if (aEndpoint.Bind(manager)) {
+      sRemoteDecoderManagerChild = manager;
+      manager->InitIPDL();
+    }
+  }
+}
+
+void
+RemoteDecoderManagerChild::InitIPDL()
+{
+  mCanSend = true;
+  mIPDLSelfRef = this;
+}
+
+void
+RemoteDecoderManagerChild::ActorDestroy(ActorDestroyReason aWhy)
+{
+  mCanSend = false;
+}
+
+void
+RemoteDecoderManagerChild::DeallocPRemoteDecoderManagerChild()
+{
+  mIPDLSelfRef = nullptr;
+}
+
+bool
+RemoteDecoderManagerChild::CanSend()
+{
+  MOZ_ASSERT(NS_GetCurrentThread() == GetManagerThread());
+  return mCanSend;
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/media/ipc/RemoteDecoderManagerChild.h
@@ -0,0 +1,63 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=99: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef include_dom_media_ipc_RemoteDecoderManagerChild_h
+#define include_dom_media_ipc_RemoteDecoderManagerChild_h
+#include "mozilla/PRemoteDecoderManagerChild.h"
+
+namespace mozilla {
+
+class RemoteDecoderManagerChild final : public PRemoteDecoderManagerChild
+{
+public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RemoteDecoderManagerChild)
+
+  // Can only be called from the manager thread
+  static RemoteDecoderManagerChild* GetSingleton();
+
+  // Can be called from any thread.
+  static nsIThread* GetManagerThread();
+  static AbstractThread* GetManagerAbstractThread();
+
+  // Main thread only
+  static void InitForContent(
+                  Endpoint<PRemoteDecoderManagerChild>&& aVideoManager);
+  static void Shutdown();
+
+  bool CanSend();
+
+protected:
+  void InitIPDL();
+
+  void ActorDestroy(ActorDestroyReason aWhy) override;
+  void DeallocPRemoteDecoderManagerChild() override;
+
+  PRemoteVideoDecoderChild* AllocPRemoteVideoDecoderChild(
+                                const VideoInfo& aVideoInfo,
+                                const float& aFramerate,
+                                const CreateDecoderParams::OptionSet& aOptions,
+                                bool* aSuccess,
+                                nsCString* aErrorDescription) override;
+  bool DeallocPRemoteVideoDecoderChild(
+           PRemoteVideoDecoderChild* actor) override;
+
+private:
+  // Main thread only
+  static void InitializeThread();
+
+  RemoteDecoderManagerChild() = default;
+  ~RemoteDecoderManagerChild() = default;
+
+  static void Open(Endpoint<PRemoteDecoderManagerChild>&& aEndpoint);
+
+  RefPtr<RemoteDecoderManagerChild> mIPDLSelfRef;
+
+  // Should only ever be accessed on the manager thread.
+  bool mCanSend = false;
+};
+
+} // namespace mozilla
+
+#endif // include_dom_media_ipc_RemoteDecoderManagerChild_h
new file mode 100644
--- /dev/null
+++ b/dom/media/ipc/RemoteDecoderManagerParent.cpp
@@ -0,0 +1,216 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=99: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "RemoteDecoderManagerParent.h"
+
+#if XP_WIN
+#include <objbase.h>
+#endif
+
+#include "RemoteVideoDecoderParent.h"
+#include "VideoUtils.h" // for MediaThreadType
+
+namespace mozilla {
+
+StaticRefPtr<nsIThread> sRemoteDecoderManagerParentThread;
+StaticRefPtr<TaskQueue> sRemoteDecoderManagerTaskQueue;
+
+class RemoteDecoderManagerThreadHolder
+{
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RemoteDecoderManagerThreadHolder)
+
+public:
+  RemoteDecoderManagerThreadHolder() { }
+
+private:
+  ~RemoteDecoderManagerThreadHolder()
+  {
+    NS_DispatchToMainThread(NS_NewRunnableFunction(
+      "dom::RemoteDecoderManagerThreadHolder::~RemoteDecoderManagerThreadHolder",
+      []() {
+        sRemoteDecoderManagerParentThread->Shutdown();
+        sRemoteDecoderManagerParentThread = nullptr;
+      }));
+  }
+};
+
+StaticRefPtr<RemoteDecoderManagerThreadHolder>
+    sRemoteDecoderManagerParentThreadHolder;
+
+class RemoteDecoderManagerThreadShutdownObserver : public nsIObserver
+{
+  virtual ~RemoteDecoderManagerThreadShutdownObserver() = default;
+public:
+  RemoteDecoderManagerThreadShutdownObserver() = default;
+
+  NS_DECL_ISUPPORTS
+
+  NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic,
+                     const char16_t* aData) override
+  {
+    MOZ_ASSERT(strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0);
+
+    RemoteDecoderManagerParent::ShutdownThreads();
+    return NS_OK;
+  }
+};
+NS_IMPL_ISUPPORTS(RemoteDecoderManagerThreadShutdownObserver, nsIObserver);
+
+bool
+RemoteDecoderManagerParent::StartupThreads()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (sRemoteDecoderManagerParentThread) {
+    return true;
+  }
+
+  nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
+  if (!observerService) {
+    return false;
+  }
+
+  RefPtr<nsIThread> managerThread;
+  nsresult rv =
+      NS_NewNamedThread("RemVidParent", getter_AddRefs(managerThread));
+  if (NS_FAILED(rv)) {
+    return false;
+  }
+  sRemoteDecoderManagerParentThread = managerThread;
+  sRemoteDecoderManagerParentThreadHolder =
+      new RemoteDecoderManagerThreadHolder();
+#if XP_WIN
+  sRemoteDecoderManagerParentThread->Dispatch(
+      NS_NewRunnableFunction(
+          "RemoteDecoderManagerParent::StartupThreads",
+          []() {
+            DebugOnly<HRESULT> hr = CoInitializeEx(0, COINIT_MULTITHREADED);
+            MOZ_ASSERT(SUCCEEDED(hr));
+          }),
+      NS_DISPATCH_NORMAL);
+#endif
+
+  sRemoteDecoderManagerTaskQueue = new TaskQueue(
+      managerThread.forget(),
+      "RemoteDecoderManagerParent::sRemoteDecoderManagerTaskQueue");
+
+  auto* obs = new RemoteDecoderManagerThreadShutdownObserver();
+  observerService->AddObserver(obs, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+  return true;
+}
+
+void
+RemoteDecoderManagerParent::ShutdownThreads()
+{
+  sRemoteDecoderManagerTaskQueue = nullptr;
+
+  sRemoteDecoderManagerParentThreadHolder = nullptr;
+  while (sRemoteDecoderManagerParentThread) {
+    NS_ProcessNextEvent(nullptr, true);
+  }
+}
+
+bool
+RemoteDecoderManagerParent::OnManagerThread()
+{
+  return NS_GetCurrentThread() == sRemoteDecoderManagerParentThread;
+}
+
+bool
+RemoteDecoderManagerParent::CreateForContent(
+    Endpoint<PRemoteDecoderManagerParent>&& aEndpoint)
+{
+  MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_RDD);
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (!StartupThreads()) {
+    return false;
+  }
+
+  RefPtr<RemoteDecoderManagerParent> parent =
+    new RemoteDecoderManagerParent(sRemoteDecoderManagerParentThreadHolder);
+
+  RefPtr<Runnable> task =
+    NewRunnableMethod<Endpoint<PRemoteDecoderManagerParent>&&>(
+      "dom::RemoteDecoderManagerParent::Open",
+      parent,
+      &RemoteDecoderManagerParent::Open,
+      std::move(aEndpoint));
+  sRemoteDecoderManagerParentThread->Dispatch(task.forget(),
+                                              NS_DISPATCH_NORMAL);
+  return true;
+}
+
+RemoteDecoderManagerParent::RemoteDecoderManagerParent(
+    RemoteDecoderManagerThreadHolder* aHolder)
+ : mThreadHolder(aHolder)
+{
+  MOZ_COUNT_CTOR(RemoteDecoderManagerParent);
+}
+
+RemoteDecoderManagerParent::~RemoteDecoderManagerParent()
+{
+  MOZ_COUNT_DTOR(RemoteDecoderManagerParent);
+}
+
+void
+RemoteDecoderManagerParent::ActorDestroy(
+    mozilla::ipc::IProtocol::ActorDestroyReason)
+{
+  mThreadHolder = nullptr;
+}
+
+PRemoteVideoDecoderParent*
+RemoteDecoderManagerParent::AllocPRemoteVideoDecoderParent(
+    const VideoInfo& aVideoInfo,
+    const float& aFramerate,
+    const CreateDecoderParams::OptionSet& aOptions,
+    bool* aSuccess,
+    nsCString* aErrorDescription)
+{
+  RefPtr<TaskQueue> decodeTaskQueue = new TaskQueue(
+      GetMediaThreadPool(MediaThreadType::PLATFORM_DECODER),
+      "RemoteVideoDecoderParent::mDecodeTaskQueue");
+
+  auto* parent = new RemoteVideoDecoderParent(this,
+                                              aVideoInfo,
+                                              aFramerate,
+                                              aOptions,
+                                              sRemoteDecoderManagerTaskQueue,
+                                              decodeTaskQueue,
+                                              aSuccess,
+                                              aErrorDescription);
+
+  return parent;
+}
+
+bool
+RemoteDecoderManagerParent::DeallocPRemoteVideoDecoderParent(
+    PRemoteVideoDecoderParent* actor)
+{
+  RemoteVideoDecoderParent* parent =
+      static_cast<RemoteVideoDecoderParent*>(actor);
+  parent->Destroy();
+  return true;
+}
+
+void
+RemoteDecoderManagerParent::Open(
+    Endpoint<PRemoteDecoderManagerParent>&& aEndpoint)
+{
+  if (!aEndpoint.Bind(this)) {
+    // We can't recover from this.
+    MOZ_CRASH("Failed to bind RemoteDecoderManagerParent to endpoint");
+  }
+  AddRef();
+}
+
+void
+RemoteDecoderManagerParent::DeallocPRemoteDecoderManagerParent()
+{
+  Release();
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/media/ipc/RemoteDecoderManagerParent.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=99: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef include_dom_media_ipc_RemoteDecoderManagerParent_h
+#define include_dom_media_ipc_RemoteDecoderManagerParent_h
+#include "mozilla/PRemoteDecoderManagerParent.h"
+
+namespace mozilla {
+
+class RemoteDecoderManagerThreadHolder;
+
+class RemoteDecoderManagerParent final : public PRemoteDecoderManagerParent
+{
+public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RemoteDecoderManagerParent)
+
+  static bool CreateForContent(
+                  Endpoint<PRemoteDecoderManagerParent>&& aEndpoint);
+
+  static bool StartupThreads();
+  static void ShutdownThreads();
+
+  bool OnManagerThread();
+
+protected:
+  PRemoteVideoDecoderParent* AllocPRemoteVideoDecoderParent(
+                                 const VideoInfo& aVideoInfo,
+                                 const float& aFramerate,
+                                 const CreateDecoderParams::OptionSet& aOptions,
+                                 bool* aSuccess,
+                                 nsCString* aErrorDescription) override;
+  bool DeallocPRemoteVideoDecoderParent(
+           PRemoteVideoDecoderParent* actor) override;
+
+  void ActorDestroy(mozilla::ipc::IProtocol::ActorDestroyReason) override;
+
+  void DeallocPRemoteDecoderManagerParent() override;
+
+private:
+  explicit RemoteDecoderManagerParent(
+               RemoteDecoderManagerThreadHolder* aThreadHolder);
+  ~RemoteDecoderManagerParent();
+
+  void Open(Endpoint<PRemoteDecoderManagerParent>&& aEndpoint);
+
+  RefPtr<RemoteDecoderManagerThreadHolder> mThreadHolder;
+};
+
+} // namespace mozilla
+
+#endif // include_dom_media_ipc_RemoteDecoderManagerParent_h
new file mode 100644
--- /dev/null
+++ b/dom/media/ipc/RemoteDecoderModule.cpp
@@ -0,0 +1,89 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=99: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "RemoteDecoderModule.h"
+
+#include "base/thread.h"
+#include "mozilla/layers/SynchronousTask.h"
+#include "mozilla/StaticPrefs.h"
+
+#ifdef MOZ_AV1
+#include "AOMDecoder.h"
+#endif
+#include "RemoteDecoderManagerChild.h"
+#include "RemoteMediaDataDecoder.h"
+#include "RemoteVideoDecoderChild.h"
+
+namespace mozilla {
+
+using base::Thread;
+using namespace ipc;
+using namespace layers;
+using namespace gfx;
+
+nsresult
+RemoteDecoderModule::Startup()
+{
+  if (!RemoteDecoderManagerChild::GetManagerThread()) {
+    return NS_ERROR_FAILURE;
+  }
+  return NS_OK;
+}
+
+bool
+RemoteDecoderModule::SupportsMimeType(
+                                const nsACString& aMimeType,
+                                DecoderDoctorDiagnostics* aDiagnostics) const
+{
+  bool supports = false;
+
+#ifdef MOZ_AV1
+  if (StaticPrefs::MediaAv1Enabled()) {
+    supports |= AOMDecoder::IsAV1(aMimeType);
+  }
+#endif
+  MOZ_LOG(sPDMLog, LogLevel::Debug, ("Sandbox decoder %s requested type",
+        supports ? "supports" : "rejects"));
+  return supports;
+}
+
+already_AddRefed<MediaDataDecoder>
+RemoteDecoderModule::CreateVideoDecoder(const CreateDecoderParams& aParams)
+{
+  RemoteVideoDecoderChild* child = new RemoteVideoDecoderChild();
+  RefPtr<RemoteMediaDataDecoder> object =
+      new RemoteMediaDataDecoder(
+          child,
+          RemoteDecoderManagerChild::GetManagerThread(),
+          RemoteDecoderManagerChild::GetManagerAbstractThread());
+
+  // (per Matt Woodrow) We can't use NS_DISPATCH_SYNC here since that
+  // can spin the event loop while it waits.
+  SynchronousTask task("InitIPDL");
+  MediaResult result(NS_OK);
+  RemoteDecoderManagerChild::GetManagerThread()->Dispatch(
+    NS_NewRunnableFunction(
+      "dom::RemoteDecoderModule::CreateVideoDecoder",
+      [&]() {
+        AutoCompleteTask complete(&task);
+        result = child->InitIPDL(
+          aParams.VideoConfig(),
+          aParams.mRate.mValue,
+          aParams.mOptions);
+      }),
+    NS_DISPATCH_NORMAL);
+  task.Wait();
+
+  if (NS_FAILED(result)) {
+    if (aParams.mError) {
+      *aParams.mError = result;
+    }
+    return nullptr;
+  }
+
+  return object.forget();
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/media/ipc/RemoteDecoderModule.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=99: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef include_dom_media_ipc_RemoteDecoderModule_h
+#define include_dom_media_ipc_RemoteDecoderModule_h
+#include "PlatformDecoderModule.h"
+
+namespace mozilla {
+
+// A PDM implementation that creates a RemoteMediaDataDecoder (a
+// MediaDataDecoder) that proxies to a RemoteVideoDecoderChild.
+// A decoder child will talk to its respective decoder parent
+// (RemoteVideoDecoderParent) on the RDD process.
+class RemoteDecoderModule : public PlatformDecoderModule
+{
+public:
+  RemoteDecoderModule() = default;
+
+  nsresult Startup() override;
+
+  bool SupportsMimeType(const nsACString& aMimeType,
+                        DecoderDoctorDiagnostics* aDiagnostics) const override;
+
+  already_AddRefed<MediaDataDecoder> CreateVideoDecoder(
+    const CreateDecoderParams& aParams) override;
+
+  already_AddRefed<MediaDataDecoder> CreateAudioDecoder(
+    const CreateDecoderParams& aParams) override
+  {
+    return nullptr;
+  }
+};
+
+} // namespace mozilla
+
+#endif // include_dom_media_ipc_RemoteDecoderModule_h
--- a/dom/media/ipc/RemoteMediaDataDecoder.h
+++ b/dom/media/ipc/RemoteMediaDataDecoder.h
@@ -8,32 +8,34 @@
 #include "PlatformDecoderModule.h"
 
 #include "MediaData.h"
 
 namespace mozilla {
 
 class GpuDecoderModule;
 class IRemoteDecoderChild;
+class RemoteDecoderModule;
 class RemoteMediaDataDecoder;
 
 DDLoggedTypeCustomNameAndBase(RemoteMediaDataDecoder,
                               RemoteMediaDataDecoder,
                               MediaDataDecoder);
 
 // A MediaDataDecoder implementation that proxies through IPDL
 // to a 'real' decoder in the GPU or RDD process.
 // All requests get forwarded to a *DecoderChild instance that
 // operates solely on the provided manager and abstract manager threads.
 class RemoteMediaDataDecoder
   : public MediaDataDecoder
   , public DecoderDoctorLifeLogger<RemoteMediaDataDecoder>
 {
 public:
   friend class GpuDecoderModule;
+  friend class RemoteDecoderModule;
 
   // MediaDataDecoder
   RefPtr<InitPromise> Init() override;
   RefPtr<DecodePromise> Decode(MediaRawData* aSample) override;
   RefPtr<DecodePromise> Drain() override;
   RefPtr<FlushPromise> Flush() override;
   RefPtr<ShutdownPromise> Shutdown() override;
   bool IsHardwareAccelerated(nsACString& aFailureReason) const override;
new file mode 100644
--- /dev/null
+++ b/dom/media/ipc/RemoteVideoDecoderChild.cpp
@@ -0,0 +1,365 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=99: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "RemoteVideoDecoderChild.h"
+
+#include "base/thread.h"
+#include "mozilla/layers/ImageDataSerializer.h"
+
+#include "ImageContainer.h" // for PlanarYCbCrData and BufferRecycleBin
+#include "RemoteDecoderManagerChild.h"
+
+namespace mozilla {
+
+using base::Thread;
+using namespace layers; // for PlanarYCbCrData and BufferRecycleBin
+
+RemoteVideoDecoderChild::RemoteVideoDecoderChild()
+  : mThread(RemoteDecoderManagerChild::GetManagerThread())
+  , mCanSend(false)
+  , mInitialized(false)
+  , mIsHardwareAccelerated(false)
+  , mConversion(MediaDataDecoder::ConversionRequired::kNeedNone)
+  , mBufferRecycleBin(new BufferRecycleBin)
+{
+}
+
+RemoteVideoDecoderChild::~RemoteVideoDecoderChild()
+{
+  AssertOnManagerThread();
+  mInitPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
+}
+
+RefPtr<mozilla::layers::Image>
+RemoteVideoDecoderChild::DeserializeImage(
+  const SurfaceDescriptorBuffer& aSdBuffer,
+  const IntSize& aPicSize)
+{
+  MOZ_ASSERT(aSdBuffer.desc().type() == BufferDescriptor::TYCbCrDescriptor);
+  if (aSdBuffer.desc().type() != BufferDescriptor::TYCbCrDescriptor) {
+    return nullptr;
+  }
+  const YCbCrDescriptor& descriptor = aSdBuffer.desc().get_YCbCrDescriptor();
+
+  uint8_t* buffer = nullptr;
+  const MemoryOrShmem& memOrShmem = aSdBuffer.data();
+  switch (memOrShmem.type()) {
+    case MemoryOrShmem::Tuintptr_t:
+      buffer = reinterpret_cast<uint8_t*>(memOrShmem.get_uintptr_t());
+      break;
+    case MemoryOrShmem::TShmem:
+      buffer = memOrShmem.get_Shmem().get<uint8_t>();
+      break;
+    default:
+      MOZ_ASSERT(false, "Unknown MemoryOrShmem type");
+  }
+  if (!buffer) {
+    return nullptr;
+  }
+
+  PlanarYCbCrData pData;
+  pData.mYSize = descriptor.ySize();
+  pData.mYStride = descriptor.yStride();
+  pData.mCbCrSize = descriptor.cbCrSize();
+  pData.mCbCrStride = descriptor.cbCrStride();
+  // default mYSkip, mCbSkip, mCrSkip because not held in YCbCrDescriptor
+  pData.mYSkip = pData.mCbSkip = pData.mCrSkip = 0;
+  // default mPicX, mPicY because not held in YCbCrDescriptor
+  pData.mPicX = pData.mPicY = 0;
+  pData.mPicSize = aPicSize;
+  pData.mStereoMode = descriptor.stereoMode();
+  pData.mColorDepth = descriptor.colorDepth();
+  pData.mYUVColorSpace = descriptor.yUVColorSpace();
+  pData.mYChannel = ImageDataSerializer::GetYChannel(buffer, descriptor);
+  pData.mCbChannel = ImageDataSerializer::GetCbChannel(buffer, descriptor);
+  pData.mCrChannel = ImageDataSerializer::GetCrChannel(buffer, descriptor);
+
+  // images coming from AOMDecoder are RecyclingPlanarYCbCrImages.
+  RefPtr<RecyclingPlanarYCbCrImage> image =
+      new RecyclingPlanarYCbCrImage(mBufferRecycleBin);
+  image->CopyData(pData);
+
+  switch (memOrShmem.type()) {
+    case MemoryOrShmem::Tuintptr_t:
+      delete [] reinterpret_cast<uint8_t*>(memOrShmem.get_uintptr_t());
+      break;
+    case MemoryOrShmem::TShmem:
+      DeallocShmem(memOrShmem.get_Shmem());
+      break;
+    default:
+      MOZ_ASSERT(false, "Unknown MemoryOrShmem type");
+  }
+
+  return image;
+}
+
+mozilla::ipc::IPCResult
+RemoteVideoDecoderChild::RecvVideoOutput(const RemoteVideoDataIPDL& aData)
+{
+  AssertOnManagerThread();
+
+  RefPtr<Image> image = DeserializeImage(aData.sdBuffer(), aData.frameSize());
+
+  RefPtr<VideoData> video = VideoData::CreateFromImage(
+      aData.display(),
+      aData.base().offset(),
+      media::TimeUnit::FromMicroseconds(aData.base().time()),
+      media::TimeUnit::FromMicroseconds(aData.base().duration()),
+      image,
+      aData.base().keyframe(),
+      media::TimeUnit::FromMicroseconds(aData.base().timecode()));
+
+  mDecodedData.AppendElement(std::move(video));
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+RemoteVideoDecoderChild::RecvInputExhausted()
+{
+  AssertOnManagerThread();
+  mDecodePromise.ResolveIfExists(mDecodedData, __func__);
+  mDecodedData.Clear();
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+RemoteVideoDecoderChild::RecvDrainComplete()
+{
+  AssertOnManagerThread();
+  mDrainPromise.ResolveIfExists(mDecodedData, __func__);
+  mDecodedData.Clear();
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+RemoteVideoDecoderChild::RecvError(const nsresult& aError)
+{
+  AssertOnManagerThread();
+  mDecodedData.Clear();
+  mDecodePromise.RejectIfExists(aError, __func__);
+  mDrainPromise.RejectIfExists(aError, __func__);
+  mFlushPromise.RejectIfExists(aError, __func__);
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+RemoteVideoDecoderChild::RecvInitComplete(const nsCString& aDecoderDescription,
+                                          const ConversionRequired& aConversion)
+{
+  AssertOnManagerThread();
+  mInitPromise.ResolveIfExists(TrackInfo::kVideoTrack, __func__);
+  mInitialized = true;
+  mDescription = aDecoderDescription;
+  mConversion = aConversion;
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+RemoteVideoDecoderChild::RecvInitFailed(const nsresult& aReason)
+{
+  AssertOnManagerThread();
+  mInitPromise.RejectIfExists(aReason, __func__);
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+RemoteVideoDecoderChild::RecvFlushComplete()
+{
+  AssertOnManagerThread();
+  mFlushPromise.ResolveIfExists(true, __func__);
+  return IPC_OK();
+}
+
+void
+RemoteVideoDecoderChild::ActorDestroy(ActorDestroyReason aWhy)
+{
+  mCanSend = false;
+}
+
+MediaResult
+RemoteVideoDecoderChild::InitIPDL(
+  const VideoInfo& aVideoInfo,
+  float aFramerate,
+  const CreateDecoderParams::OptionSet& aOptions)
+{
+  RefPtr<RemoteDecoderManagerChild> manager =
+      RemoteDecoderManagerChild::GetSingleton();
+
+  // The manager isn't available because RemoteDecoderManagerChild has been
+  // initialized with null end points and we don't want to decode video on RDD
+  // process anymore. Return false here so that we can fallback to other PDMs.
+  if (!manager) {
+    return MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+                       RESULT_DETAIL("RemoteDecoderManager is not available."));
+  }
+
+  if (!manager->CanSend()) {
+    return MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+                       RESULT_DETAIL("RemoteDecoderManager unable to send."));
+  }
+
+  mIPDLSelfRef = this;
+  bool success = false;
+  nsCString errorDescription;
+  if (manager->SendPRemoteVideoDecoderConstructor(this,
+                                                  aVideoInfo,
+                                                  aFramerate,
+                                                  aOptions,
+                                                  &success,
+                                                  &errorDescription)) {
+    mCanSend = true;
+  }
+
+  return success ? MediaResult(NS_OK) :
+                   MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, errorDescription);
+}
+
+void
+RemoteVideoDecoderChild::DestroyIPDL()
+{
+  if (mCanSend) {
+    PRemoteVideoDecoderChild::Send__delete__(this);
+  }
+}
+
+void
+RemoteVideoDecoderChild::IPDLActorDestroyed()
+{
+  mIPDLSelfRef = nullptr;
+}
+
+// MediaDataDecoder methods
+
+RefPtr<MediaDataDecoder::InitPromise>
+RemoteVideoDecoderChild::Init()
+{
+  AssertOnManagerThread();
+
+  if (!mIPDLSelfRef || !mCanSend) {
+    return MediaDataDecoder::InitPromise::CreateAndReject(
+        NS_ERROR_DOM_MEDIA_DECODE_ERR, __func__);
+  }
+
+  SendInit();
+
+  return mInitPromise.Ensure(__func__);
+}
+
+RefPtr<MediaDataDecoder::DecodePromise>
+RemoteVideoDecoderChild::Decode(MediaRawData* aSample)
+{
+  AssertOnManagerThread();
+
+  if (!mCanSend) {
+    return MediaDataDecoder::DecodePromise::CreateAndReject(
+        NS_ERROR_DOM_MEDIA_DECODE_ERR, __func__);
+  }
+
+  // TODO: It would be nice to add an allocator method to
+  // MediaDataDecoder so that the demuxer could write directly
+  // into shmem rather than requiring a copy here.
+  Shmem buffer;
+  if (!AllocShmem(aSample->Size(), Shmem::SharedMemory::TYPE_BASIC, &buffer)) {
+    return MediaDataDecoder::DecodePromise::CreateAndReject(
+        NS_ERROR_DOM_MEDIA_DECODE_ERR, __func__);
+  }
+
+  memcpy(buffer.get<uint8_t>(), aSample->Data(), aSample->Size());
+
+  MediaRawDataIPDL sample(MediaDataIPDL(aSample->mOffset,
+                                        aSample->mTime.ToMicroseconds(),
+                                        aSample->mTimecode.ToMicroseconds(),
+                                        aSample->mDuration.ToMicroseconds(),
+                                        aSample->mFrames,
+                                        aSample->mKeyframe),
+                          buffer);
+  SendInput(sample);
+  return mDecodePromise.Ensure(__func__);
+}
+
+RefPtr<MediaDataDecoder::FlushPromise>
+RemoteVideoDecoderChild::Flush()
+{
+  AssertOnManagerThread();
+  mDecodePromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
+  mDrainPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
+  if (!mCanSend) {
+    return MediaDataDecoder::FlushPromise::CreateAndReject(
+        NS_ERROR_DOM_MEDIA_DECODE_ERR, __func__);
+  }
+  SendFlush();
+  return mFlushPromise.Ensure(__func__);
+}
+
+RefPtr<MediaDataDecoder::DecodePromise>
+RemoteVideoDecoderChild::Drain()
+{
+  AssertOnManagerThread();
+  if (!mCanSend) {
+    return MediaDataDecoder::DecodePromise::CreateAndReject(
+        NS_ERROR_DOM_MEDIA_DECODE_ERR, __func__);
+  }
+  SendDrain();
+  return mDrainPromise.Ensure(__func__);
+}
+
+void
+RemoteVideoDecoderChild::Shutdown()
+{
+  AssertOnManagerThread();
+  mInitPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
+  if (mCanSend) {
+    SendShutdown();
+  }
+  mInitialized = false;
+}
+
+bool
+RemoteVideoDecoderChild::IsHardwareAccelerated(nsACString& aFailureReason) const
+{
+  AssertOnManagerThread();
+  aFailureReason = mHardwareAcceleratedReason;
+  return mIsHardwareAccelerated;
+}
+
+nsCString
+RemoteVideoDecoderChild::GetDescriptionName() const
+{
+  AssertOnManagerThread();
+  return mDescription;
+}
+
+void
+RemoteVideoDecoderChild::SetSeekThreshold(const media::TimeUnit& aTime)
+{
+  AssertOnManagerThread();
+  if (mCanSend) {
+    SendSetSeekThreshold(aTime.ToMicroseconds());
+  }
+}
+
+MediaDataDecoder::ConversionRequired
+RemoteVideoDecoderChild::NeedsConversion() const
+{
+  AssertOnManagerThread();
+  return mConversion;
+}
+
+void
+RemoteVideoDecoderChild::AssertOnManagerThread() const
+{
+  MOZ_ASSERT(NS_GetCurrentThread() == mThread);
+}
+
+RemoteDecoderManagerChild*
+RemoteVideoDecoderChild::GetManager()
+{
+  if (!mCanSend) {
+    return nullptr;
+  }
+  return static_cast<RemoteDecoderManagerChild*>(Manager());
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/media/ipc/RemoteVideoDecoderChild.h
@@ -0,0 +1,94 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=99: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef include_dom_media_ipc_RemoteVideoDecoderChild_h
+#define include_dom_media_ipc_RemoteVideoDecoderChild_h
+#include "mozilla/PRemoteVideoDecoderChild.h"
+#include "IRemoteDecoderChild.h"
+
+#include "MediaResult.h"
+
+namespace mozilla {
+namespace layers {
+class BufferRecycleBin;
+}
+}
+
+namespace mozilla {
+
+class RemoteDecoderManagerChild;
+using mozilla::MediaDataDecoder;
+
+class RemoteVideoDecoderChild final : public PRemoteVideoDecoderChild
+                                    , public IRemoteDecoderChild
+{
+public:
+  explicit RemoteVideoDecoderChild();
+
+  // PRemoteVideoDecoderChild
+  mozilla::ipc::IPCResult RecvVideoOutput(
+                              const RemoteVideoDataIPDL& aData) override;
+  mozilla::ipc::IPCResult RecvInputExhausted() override;
+  mozilla::ipc::IPCResult RecvDrainComplete() override;
+  mozilla::ipc::IPCResult RecvError(const nsresult& aError) override;
+  mozilla::ipc::IPCResult RecvInitComplete(
+                              const nsCString& aDecoderDescription,
+                              const ConversionRequired& aConversion) override;
+  mozilla::ipc::IPCResult RecvInitFailed(const nsresult& aReason) override;
+  mozilla::ipc::IPCResult RecvFlushComplete() override;
+
+  void ActorDestroy(ActorDestroyReason aWhy) override;
+
+  // IRemoteDecoderChild
+  RefPtr<MediaDataDecoder::InitPromise> Init() override;
+  RefPtr<MediaDataDecoder::DecodePromise> Decode(MediaRawData* aSample) override;
+  RefPtr<MediaDataDecoder::DecodePromise> Drain() override;
+  RefPtr<MediaDataDecoder::FlushPromise> Flush() override;
+  void Shutdown() override;
+  bool IsHardwareAccelerated(nsACString& aFailureReason) const override;
+  nsCString GetDescriptionName() const override;
+  void SetSeekThreshold(const media::TimeUnit& aTime) override;
+  MediaDataDecoder::ConversionRequired NeedsConversion() const override;
+  void DestroyIPDL() override;
+
+  MOZ_IS_CLASS_INIT
+  MediaResult InitIPDL(const VideoInfo& aVideoInfo,
+                       float aFramerate,
+                       const CreateDecoderParams::OptionSet& aOptions);
+
+  // Called from IPDL when our actor has been destroyed
+  void IPDLActorDestroyed();
+
+  RemoteDecoderManagerChild* GetManager();
+
+private:
+  ~RemoteVideoDecoderChild();
+
+  void AssertOnManagerThread() const;
+  RefPtr<mozilla::layers::Image> DeserializeImage(
+                                     const SurfaceDescriptorBuffer& sdBuffer,
+                                     const IntSize& aPicSize);
+
+  RefPtr<RemoteVideoDecoderChild> mIPDLSelfRef;
+  RefPtr<nsIThread> mThread;
+
+  MozPromiseHolder<MediaDataDecoder::InitPromise> mInitPromise;
+  MozPromiseHolder<MediaDataDecoder::DecodePromise> mDecodePromise;
+  MozPromiseHolder<MediaDataDecoder::DecodePromise> mDrainPromise;
+  MozPromiseHolder<MediaDataDecoder::FlushPromise> mFlushPromise;
+
+  nsCString mHardwareAcceleratedReason;
+  nsCString mDescription;
+  bool mCanSend;
+  bool mInitialized;
+  bool mIsHardwareAccelerated;
+  MediaDataDecoder::ConversionRequired mConversion;
+  MediaDataDecoder::DecodedData mDecodedData;
+  RefPtr<mozilla::layers::BufferRecycleBin> mBufferRecycleBin;
+};
+
+} // namespace mozilla
+
+#endif // include_dom_media_ipc_RemoteVideoDecoderChild_h
new file mode 100644
--- /dev/null
+++ b/dom/media/ipc/RemoteVideoDecoderParent.cpp
@@ -0,0 +1,261 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=99: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "RemoteVideoDecoderParent.h"
+
+#include "mozilla/Unused.h"
+
+#ifdef MOZ_AV1
+#include "AOMDecoder.h"
+#endif
+#include "ImageContainer.h"
+#include "RemoteDecoderManagerParent.h"
+#include "RemoteDecoderModule.h"
+
+namespace mozilla {
+
+using media::TimeUnit;
+using namespace layers; // for PlanarYCbCrImage and BufferRecycleBin
+
+RemoteVideoDecoderParent::RemoteVideoDecoderParent(
+  RemoteDecoderManagerParent* aParent,
+  const VideoInfo& aVideoInfo,
+  float aFramerate,
+  const CreateDecoderParams::OptionSet& aOptions,
+  TaskQueue* aManagerTaskQueue,
+  TaskQueue* aDecodeTaskQueue,
+  bool* aSuccess,
+  nsCString* aErrorDescription)
+  : mParent(aParent)
+  , mManagerTaskQueue(aManagerTaskQueue)
+  , mDecodeTaskQueue(aDecodeTaskQueue)
+  , mDestroyed(false)
+  , mVideoInfo(aVideoInfo)
+{
+  MOZ_COUNT_CTOR(RemoteVideoDecoderParent);
+  MOZ_ASSERT(OnManagerThread());
+  // We hold a reference to ourselves to keep us alive until IPDL
+  // explictly destroys us. There may still be refs held by
+  // tasks, but no new ones should be added after we're
+  // destroyed.
+  mIPDLSelfRef = this;
+
+  CreateDecoderParams params(mVideoInfo);
+  params.mTaskQueue = mDecodeTaskQueue;
+  params.mImageContainer = new layers::ImageContainer();
+  params.mRate = CreateDecoderParams::VideoFrameRate(aFramerate);
+  params.mOptions = aOptions;
+  MediaResult error(NS_OK);
+  params.mError = &error;
+
+#ifdef MOZ_AV1
+  if (AOMDecoder::IsAV1(params.mConfig.mMimeType)) {
+    mDecoder = new AOMDecoder(params);
+  }
+#endif
+
+  if (NS_FAILED(error)) {
+    MOZ_ASSERT(aErrorDescription);
+    *aErrorDescription = error.Description();
+  }
+
+  *aSuccess = !!mDecoder;
+}
+
+RemoteVideoDecoderParent::~RemoteVideoDecoderParent()
+{
+  MOZ_COUNT_DTOR(RemoteVideoDecoderParent);
+}
+
+void
+RemoteVideoDecoderParent::Destroy()
+{
+  MOZ_ASSERT(OnManagerThread());
+  mDestroyed = true;
+  mIPDLSelfRef = nullptr;
+}
+
+mozilla::ipc::IPCResult
+RemoteVideoDecoderParent::RecvInit()
+{
+  MOZ_ASSERT(OnManagerThread());
+  RefPtr<RemoteVideoDecoderParent> self = this;
+  mDecoder->Init()->Then(mManagerTaskQueue, __func__,
+    [self] (TrackInfo::TrackType aTrack) {
+      MOZ_ASSERT(aTrack == TrackInfo::kVideoTrack);
+      if (self->mDecoder) {
+        Unused << self->SendInitComplete(self->mDecoder->GetDescriptionName(),
+                                         self->mDecoder->NeedsConversion());
+      }
+    },
+    [self] (MediaResult aReason) {
+      if (!self->mDestroyed) {
+        Unused << self->SendInitFailed(aReason);
+      }
+    });
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+RemoteVideoDecoderParent::RecvInput(const MediaRawDataIPDL& aData)
+{
+  MOZ_ASSERT(OnManagerThread());
+  // XXX: This copies the data into a buffer owned by the MediaRawData. Ideally
+  // we'd just take ownership of the shmem.
+  RefPtr<MediaRawData> data = new MediaRawData(aData.buffer().get<uint8_t>(),
+                                               aData.buffer().Size<uint8_t>());
+  if (aData.buffer().Size<uint8_t>() && !data->Data()) {
+    // OOM
+    Error(NS_ERROR_OUT_OF_MEMORY);
+    return IPC_OK();
+  }
+  data->mOffset = aData.base().offset();
+  data->mTime = TimeUnit::FromMicroseconds(aData.base().time());
+  data->mTimecode = TimeUnit::FromMicroseconds(aData.base().timecode());
+  data->mDuration = TimeUnit::FromMicroseconds(aData.base().duration());
+  data->mKeyframe = aData.base().keyframe();
+
+  DeallocShmem(aData.buffer());
+
+  RefPtr<RemoteVideoDecoderParent> self = this;
+  mDecoder->Decode(data)->Then(
+    mManagerTaskQueue, __func__,
+    [self, this](const MediaDataDecoder::DecodedData& aResults) {
+      if (mDestroyed) {
+        return;
+      }
+      ProcessDecodedData(aResults);
+      Unused << SendInputExhausted();
+    },
+    [self](const MediaResult& aError) { self->Error(aError); });
+  return IPC_OK();
+}
+
+void
+RemoteVideoDecoderParent::ProcessDecodedData(
+                              const MediaDataDecoder::DecodedData& aData)
+{
+  MOZ_ASSERT(OnManagerThread());
+
+  for (const auto& data : aData) {
+    MOZ_ASSERT(data->mType == MediaData::VIDEO_DATA,
+                "Can only decode videos using RemoteVideoDecoderParent!");
+    VideoData* video = static_cast<VideoData*>(data.get());
+
+    MOZ_ASSERT(video->mImage, "Decoded video must output a layer::Image to "
+                              "be used with RemoteVideoDecoderParent");
+
+    PlanarYCbCrImage* image =
+        static_cast<PlanarYCbCrImage*>(video->mImage.get());
+
+    SurfaceDescriptorBuffer sdBuffer;
+    Shmem buffer;
+    if (AllocShmem(image->GetDataSize(),
+                   Shmem::SharedMemory::TYPE_BASIC, &buffer) &&
+        image->GetDataSize() == buffer.Size<uint8_t>()) {
+      sdBuffer.data() = buffer;
+      image->BuildSurfaceDescriptorBuffer(sdBuffer);
+    }
+
+    RemoteVideoDataIPDL output(
+      MediaDataIPDL(data->mOffset, data->mTime.ToMicroseconds(),
+                    data->mTimecode.ToMicroseconds(),
+                    data->mDuration.ToMicroseconds(),
+                    data->mFrames, data->mKeyframe),
+      video->mDisplay,
+      image->GetSize(),
+      sdBuffer,
+      video->mFrameID);
+    Unused << SendVideoOutput(output);
+  }
+}
+
+mozilla::ipc::IPCResult
+RemoteVideoDecoderParent::RecvFlush()
+{
+  MOZ_ASSERT(!mDestroyed);
+  MOZ_ASSERT(OnManagerThread());
+  RefPtr<RemoteVideoDecoderParent> self = this;
+  mDecoder->Flush()->Then(
+    mManagerTaskQueue, __func__,
+    [self]() {
+      if (!self->mDestroyed) {
+        Unused << self->SendFlushComplete();
+      }
+    },
+    [self](const MediaResult& aError) { self->Error(aError); });
+
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+RemoteVideoDecoderParent::RecvDrain()
+{
+  MOZ_ASSERT(!mDestroyed);
+  MOZ_ASSERT(OnManagerThread());
+  RefPtr<RemoteVideoDecoderParent> self = this;
+  mDecoder->Drain()->Then(
+    mManagerTaskQueue, __func__,
+    [self, this](const MediaDataDecoder::DecodedData& aResults) {
+      if (!mDestroyed) {
+        ProcessDecodedData(aResults);
+        Unused << SendDrainComplete();
+      }
+    },
+    [self](const MediaResult& aError) { self->Error(aError); });
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+RemoteVideoDecoderParent::RecvShutdown()
+{
+  MOZ_ASSERT(!mDestroyed);
+  MOZ_ASSERT(OnManagerThread());
+  if (mDecoder) {
+    mDecoder->Shutdown();
+  }
+  mDecoder = nullptr;
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+RemoteVideoDecoderParent::RecvSetSeekThreshold(const int64_t& aTime)
+{
+  MOZ_ASSERT(!mDestroyed);
+  MOZ_ASSERT(OnManagerThread());
+  mDecoder->SetSeekThreshold(TimeUnit::FromMicroseconds(aTime));
+  return IPC_OK();
+}
+
+void
+RemoteVideoDecoderParent::ActorDestroy(ActorDestroyReason aWhy)
+{
+  MOZ_ASSERT(!mDestroyed);
+  MOZ_ASSERT(OnManagerThread());
+  if (mDecoder) {
+    mDecoder->Shutdown();
+    mDecoder = nullptr;
+  }
+  if (mDecodeTaskQueue) {
+    mDecodeTaskQueue->BeginShutdown();
+  }
+}
+
+void
+RemoteVideoDecoderParent::Error(const MediaResult& aError)
+{
+  MOZ_ASSERT(OnManagerThread());
+  if (!mDestroyed) {
+    Unused << SendError(aError);
+  }
+}
+
+bool
+RemoteVideoDecoderParent::OnManagerThread()
+{
+  return mParent->OnManagerThread();
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/media/ipc/RemoteVideoDecoderParent.h
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=99: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef include_dom_media_ipc_RemoteVideoDecoderParent_h
+#define include_dom_media_ipc_RemoteVideoDecoderParent_h
+#include "mozilla/PRemoteVideoDecoderParent.h"
+
+namespace mozilla {
+
+class RemoteDecoderManagerParent;
+
+class RemoteVideoDecoderParent final : public PRemoteVideoDecoderParent
+{
+public:
+  // We refcount this class since the task queue can have runnables
+  // that reference us.
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RemoteVideoDecoderParent)
+
+  RemoteVideoDecoderParent(RemoteDecoderManagerParent* aParent,
+                           const VideoInfo& aVideoInfo,
+                           float aFramerate,
+                           const CreateDecoderParams::OptionSet& aOptions,
+                           TaskQueue* aManagerTaskQueue,
+                           TaskQueue* aDecodeTaskQueue,
+                           bool* aSuccess,
+                           nsCString* aErrorDescription);
+
+  void Destroy();
+
+  // PRemoteVideoDecoderParent
+  mozilla::ipc::IPCResult RecvInit() override;
+  mozilla::ipc::IPCResult RecvInput(const MediaRawDataIPDL& aData) override;
+  mozilla::ipc::IPCResult RecvFlush() override;
+  mozilla::ipc::IPCResult RecvDrain() override;
+  mozilla::ipc::IPCResult RecvShutdown() override;
+  mozilla::ipc::IPCResult RecvSetSeekThreshold(const int64_t& aTime) override;
+
+  void ActorDestroy(ActorDestroyReason aWhy) override;
+
+private:
+  bool OnManagerThread();
+  void Error(const MediaResult& aError);
+
+  ~RemoteVideoDecoderParent();
+  void ProcessDecodedData(const MediaDataDecoder::DecodedData& aData);
+
+  RefPtr<RemoteDecoderManagerParent> mParent;
+  RefPtr<RemoteVideoDecoderParent> mIPDLSelfRef;
+  RefPtr<TaskQueue> mManagerTaskQueue;
+  RefPtr<TaskQueue> mDecodeTaskQueue;
+  RefPtr<MediaDataDecoder> mDecoder;
+
+  // Can only be accessed from the manager thread
+  bool mDestroyed;
+  VideoInfo mVideoInfo;
+};
+
+} // namespace mozilla
+
+#endif // include_dom_media_ipc_RemoteVideoDecoderParent_h
--- a/dom/media/ipc/moz.build
+++ b/dom/media/ipc/moz.build
@@ -2,34 +2,44 @@
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 
 IPDL_SOURCES += [
     'PMediaDecoderParams.ipdlh',
+    'PRemoteDecoderManager.ipdl',
+    'PRemoteVideoDecoder.ipdl',
     'PVideoDecoder.ipdl',
     'PVideoDecoderManager.ipdl',
 ]
 
 EXPORTS.mozilla += [
     'GpuDecoderModule.h',
+    'RemoteDecoderManagerChild.h',
+    'RemoteDecoderManagerParent.h',
+    'RemoteDecoderModule.h',
     'RemoteMediaDataDecoder.h',
 ]
 
 EXPORTS.mozilla.dom += [
     'MediaIPCUtils.h',
     'VideoDecoderManagerChild.h',
     'VideoDecoderManagerParent.h',
 ]
 
 SOURCES += [
     'GpuDecoderModule.cpp',
+    'RemoteDecoderManagerChild.cpp',
+    'RemoteDecoderManagerParent.cpp',
+    'RemoteDecoderModule.cpp',
     'RemoteMediaDataDecoder.cpp',
+    'RemoteVideoDecoderChild.cpp',
+    'RemoteVideoDecoderParent.cpp',
     'VideoDecoderChild.cpp',
     'VideoDecoderManagerChild.cpp',
     'VideoDecoderManagerParent.cpp',
     'VideoDecoderParent.cpp',
 ]
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
--- a/dom/media/platforms/PlatformDecoderModule.h
+++ b/dom/media/platforms/PlatformDecoderModule.h
@@ -30,16 +30,17 @@ class MediaRawData;
 class DecoderDoctorDiagnostics;
 
 namespace layers {
 class ImageContainer;
 } // namespace layers
 
 class GpuDecoderModule;
 class MediaDataDecoder;
+class RemoteDecoderModule;
 class TaskQueue;
 class CDMProxy;
 
 static LazyLogModule sPDMLog("PlatformDecoderModule");
 
 struct MOZ_STACK_CLASS CreateDecoderParams final
 {
   explicit CreateDecoderParams(const TrackInfo& aConfig) : mConfig(aConfig) { }
@@ -208,16 +209,17 @@ public:
 protected:
   PlatformDecoderModule() { }
   virtual ~PlatformDecoderModule() { }
 
   friend class MediaChangeMonitor;
   friend class PDMFactory;
   friend class GpuDecoderModule;
   friend class EMEDecoderModule;
+  friend class RemoteDecoderModule;
 
   // Indicates if the PlatformDecoderModule supports decoding of aColorDepth.
   // Should override this method when the platform can support color depth != 8.
   virtual bool SupportsColorDepth(gfx::ColorDepth aColorDepth,
                                   DecoderDoctorDiagnostics* aDiagnostics) const
   {
     return aColorDepth == gfx::ColorDepth::COLOR_8;
   }
--- a/ipc/ipdl/sync-messages.ini
+++ b/ipc/ipdl/sync-messages.ini
@@ -909,16 +909,18 @@ description =
 [PGMPService::LaunchGMPForNodeId]
 description =
 [PGMPService::GetGMPNodeId]
 description =
 [PGMPVideoDecoder::NeedShmem]
 description =
 [PGMPVideoEncoder::NeedShmem]
 description =
+[PRemoteDecoderManager::PRemoteVideoDecoder]
+description = See Bug 1505976 - investigate changing to async instead of matching GPU pattern
 [PVideoDecoderManager::PVideoDecoder]
 description =
 [PVideoDecoderManager::Readback]
 description =
 [PBackgroundStorage::Preload]
 description =
 [PRemoteSpellcheckEngine::Check]
 description =