зеркало из https://github.com/mozilla/gecko-dev.git
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 --HG-- extra : moz-landing-system : lando
This commit is contained in:
Родитель
f862c70f8d
Коммит
9643048b75
|
@ -44,6 +44,49 @@ namespace IPC {
|
|||
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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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",
|
||||
[&, child]() {
|
||||
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
|
|
@ -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
|
|
@ -13,6 +13,7 @@ namespace mozilla {
|
|||
|
||||
class GpuDecoderModule;
|
||||
class IRemoteDecoderChild;
|
||||
class RemoteDecoderModule;
|
||||
class RemoteMediaDataDecoder;
|
||||
|
||||
DDLoggedTypeCustomNameAndBase(RemoteMediaDataDecoder,
|
||||
|
@ -29,6 +30,7 @@ class RemoteMediaDataDecoder
|
|||
{
|
||||
public:
|
||||
friend class GpuDecoderModule;
|
||||
friend class RemoteDecoderModule;
|
||||
|
||||
// MediaDataDecoder
|
||||
RefPtr<InitPromise> Init() override;
|
||||
|
|
|
@ -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(std::move(mDecodedData), __func__);
|
||||
mDecodedData = MediaDataDecoder::DecodedData();
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
mozilla::ipc::IPCResult
|
||||
RemoteVideoDecoderChild::RecvDrainComplete()
|
||||
{
|
||||
AssertOnManagerThread();
|
||||
mDrainPromise.ResolveIfExists(std::move(mDecodedData), __func__);
|
||||
mDecodedData = MediaDataDecoder::DecodedData();
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
mozilla::ipc::IPCResult
|
||||
RemoteVideoDecoderChild::RecvError(const nsresult& aError)
|
||||
{
|
||||
AssertOnManagerThread();
|
||||
mDecodedData = MediaDataDecoder::DecodedData();
|
||||
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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -7,12 +7,17 @@
|
|||
|
||||
IPDL_SOURCES += [
|
||||
'PMediaDecoderParams.ipdlh',
|
||||
'PRemoteDecoderManager.ipdl',
|
||||
'PRemoteVideoDecoder.ipdl',
|
||||
'PVideoDecoder.ipdl',
|
||||
'PVideoDecoderManager.ipdl',
|
||||
]
|
||||
|
||||
EXPORTS.mozilla += [
|
||||
'GpuDecoderModule.h',
|
||||
'RemoteDecoderManagerChild.h',
|
||||
'RemoteDecoderManagerParent.h',
|
||||
'RemoteDecoderModule.h',
|
||||
'RemoteMediaDataDecoder.h',
|
||||
]
|
||||
|
||||
|
@ -24,7 +29,12 @@ EXPORTS.mozilla.dom += [
|
|||
|
||||
SOURCES += [
|
||||
'GpuDecoderModule.cpp',
|
||||
'RemoteDecoderManagerChild.cpp',
|
||||
'RemoteDecoderManagerParent.cpp',
|
||||
'RemoteDecoderModule.cpp',
|
||||
'RemoteMediaDataDecoder.cpp',
|
||||
'RemoteVideoDecoderChild.cpp',
|
||||
'RemoteVideoDecoderParent.cpp',
|
||||
'VideoDecoderChild.cpp',
|
||||
'VideoDecoderManagerChild.cpp',
|
||||
'VideoDecoderManagerParent.cpp',
|
||||
|
|
|
@ -35,6 +35,7 @@ class ImageContainer;
|
|||
|
||||
class GpuDecoderModule;
|
||||
class MediaDataDecoder;
|
||||
class RemoteDecoderModule;
|
||||
class TaskQueue;
|
||||
class CDMProxy;
|
||||
|
||||
|
@ -213,6 +214,7 @@ protected:
|
|||
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.
|
||||
|
|
|
@ -914,6 +914,8 @@ description =
|
|||
description =
|
||||
[PGMPVideoEncoder::NeedShmem]
|
||||
description =
|
||||
[PRemoteDecoderManager::PRemoteVideoDecoder]
|
||||
description = See Bug 1505976 - investigate changing to async instead of matching GPU pattern
|
||||
[PVideoDecoderManager::PVideoDecoder]
|
||||
description =
|
||||
[PVideoDecoderManager::Readback]
|
||||
|
|
Загрузка…
Ссылка в новой задаче