Bug 1595994 - P7: Add Supports messages to PRemoteDecoderManager. r=kamidphish,nika,ipc-reviewers

Add a synchronous Supports message to the IPDL PRemoteDecoderManager protocol so
decoder modules can query for playback support in the actual process that will
attempt to do the decoding.

Depends on D54878

Differential Revision: https://phabricator.services.mozilla.com/D54879
This commit is contained in:
Dan Glastonbury 2020-10-20 23:26:27 +00:00
parent f8343f2b4d
commit a70a2c09f1
13 changed files with 253 additions and 91 deletions

View File

@ -6,19 +6,19 @@
#include "DecoderDoctorDiagnostics.h"
#include "mozilla/dom/DecoderDoctorNotificationBinding.h"
#include "VideoUtils.h"
#include "mozilla/Logging.h"
#include "mozilla/Preferences.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/dom/DecoderDoctorNotificationBinding.h"
#include "mozilla/dom/Document.h"
#include "nsContentUtils.h"
#include "nsGkAtoms.h"
#include "mozilla/dom/Document.h"
#include "nsIObserverService.h"
#include "nsIScriptError.h"
#include "nsITimer.h"
#include "nsPluginHost.h"
#include "nsPrintfCString.h"
#include "VideoUtils.h"
#if defined(MOZ_FFMPEG)
# include "FFmpegRuntimeLinker.h"
@ -878,7 +878,11 @@ void DecoderDoctorDiagnostics::StoreFormatDiagnostics(dom::Document* aDocument,
}
mFormat = aFormat;
mCanPlay = aCanPlay;
if (aCanPlay) {
mFlags -= Flags::CanPlay;
} else {
mFlags += Flags::CanPlay;
}
// StoreDiagnostics should only be called once, after all data is available,
// so it is safe to std::move() from this object.
@ -1101,20 +1105,20 @@ nsCString DecoderDoctorDiagnostics::GetDescription() const {
case eFormatSupportCheck:
s = "format='";
s += NS_ConvertUTF16toUTF8(mFormat).get();
s += mCanPlay ? "', can play" : "', cannot play";
if (mVideoNotSupported) {
s += mFlags.contains(Flags::CanPlay) ? "', can play" : "', cannot play";
if (mFlags.contains(Flags::VideoNotSupported)) {
s += ", but video format not supported";
}
if (mAudioNotSupported) {
if (mFlags.contains(Flags::AudioNotSupported)) {
s += ", but audio format not supported";
}
if (mWMFFailedToLoad) {
if (mFlags.contains(Flags::WMFFailedToLoad)) {
s += ", Windows platform decoder failed to load";
}
if (mFFmpegFailedToLoad) {
if (mFlags.contains(Flags::FFmpegFailedToLoad)) {
s += ", Linux platform decoder failed to load";
}
if (mGMPPDMFailedToStartup) {
if (mFlags.contains(Flags::GMPPDMFailedToStartup)) {
s += ", GMP PDM failed to startup";
} else if (!mGMP.IsEmpty()) {
s += ", Using GMP '";

View File

@ -8,6 +8,9 @@
#define DecoderDoctorDiagnostics_h_
#include "MediaResult.h"
#include "mozilla/DefineEnum.h"
#include "mozilla/EnumSet.h"
#include "mozilla/EnumTypeTraits.h"
#include "nsString.h"
namespace mozilla {
@ -40,6 +43,8 @@ struct DecoderDoctorEvent {
// This class' methods must be called from the main thread.
class DecoderDoctorDiagnostics {
friend struct IPC::ParamTraits<mozilla::DecoderDoctorDiagnostics>;
public:
// Store the diagnostic information collected so far on a document for a
// given format. All diagnostics for a document will be analyzed together
@ -77,20 +82,32 @@ class DecoderDoctorDiagnostics {
// Methods to record diagnostic information:
MOZ_DEFINE_ENUM_CLASS_AT_CLASS_SCOPE(
Flags, (CanPlay, WMFFailedToLoad, FFmpegFailedToLoad,
GMPPDMFailedToStartup, VideoNotSupported, AudioNotSupported));
using FlagsSet = mozilla::EnumSet<Flags>;
const nsAString& Format() const { return mFormat; }
bool CanPlay() const { return mCanPlay; }
bool CanPlay() const { return mFlags.contains(Flags::CanPlay); }
void SetWMFFailedToLoad() { mWMFFailedToLoad = true; }
bool DidWMFFailToLoad() const { return mWMFFailedToLoad; }
void SetFailureFlags(const FlagsSet& aFlags) { mFlags = aFlags; }
void SetWMFFailedToLoad() { mFlags += Flags::WMFFailedToLoad; }
bool DidWMFFailToLoad() const {
return mFlags.contains(Flags::WMFFailedToLoad);
}
void SetFFmpegFailedToLoad() { mFFmpegFailedToLoad = true; }
bool DidFFmpegFailToLoad() const { return mFFmpegFailedToLoad; }
void SetFFmpegFailedToLoad() { mFlags += Flags::FFmpegFailedToLoad; }
bool DidFFmpegFailToLoad() const {
return mFlags.contains(Flags::FFmpegFailedToLoad);
}
void SetGMPPDMFailedToStartup() { mGMPPDMFailedToStartup = true; }
bool DidGMPPDMFailToStartup() const { return mGMPPDMFailedToStartup; }
void SetGMPPDMFailedToStartup() { mFlags += Flags::GMPPDMFailedToStartup; }
bool DidGMPPDMFailToStartup() const {
return mFlags.contains(Flags::GMPPDMFailedToStartup);
}
void SetVideoNotSupported() { mVideoNotSupported = true; }
void SetAudioNotSupported() { mAudioNotSupported = true; }
void SetVideoNotSupported() { mFlags += Flags::VideoNotSupported; }
void SetAudioNotSupported() { mFlags += Flags::AudioNotSupported; }
void SetGMP(const nsACString& aGMP) { mGMP = aGMP; }
const nsACString& GMP() const { return mGMP; }
@ -115,14 +132,7 @@ class DecoderDoctorDiagnostics {
DiagnosticsType mDiagnosticsType = eUnsaved;
nsString mFormat;
// True if there is at least one decoder that can play that format.
bool mCanPlay = false;
bool mWMFFailedToLoad = false;
bool mFFmpegFailedToLoad = false;
bool mGMPPDMFailedToStartup = false;
bool mVideoNotSupported = false;
bool mAudioNotSupported = false;
FlagsSet mFlags;
nsCString mGMP;
nsString mKeySystem;
@ -135,6 +145,14 @@ class DecoderDoctorDiagnostics {
nsString mDecodeIssueMediaSrc;
};
// Used for IPDL serialization.
// The 'value' have to be the biggest enum from DecoderDoctorDiagnostics::Flags.
template <>
struct MaxEnumValue<::mozilla::DecoderDoctorDiagnostics::Flags> {
static constexpr unsigned int value =
static_cast<unsigned int>(DecoderDoctorDiagnostics::sFlagsCount);
};
} // namespace mozilla
#endif

View File

@ -7,8 +7,10 @@
#ifndef mozilla_dom_media_MediaIPCUtils_h
#define mozilla_dom_media_MediaIPCUtils_h
#include "DecoderDoctorDiagnostics.h"
#include "PlatformDecoderModule.h"
#include "ipc/IPCMessageUtils.h"
#include "mozilla/EnumSet.h"
#include "mozilla/GfxMessageUtils.h"
#include "mozilla/gfx/Rect.h"
@ -191,6 +193,59 @@ struct ParamTraits<mozilla::MediaResult> {
};
};
template <>
struct ParamTraits<mozilla::DecoderDoctorDiagnostics> {
typedef mozilla::DecoderDoctorDiagnostics paramType;
static void Write(Message* aMsg, const paramType& aParam) {
WriteParam(aMsg, aParam.mDiagnosticsType);
WriteParam(aMsg, aParam.mFormat);
WriteParam(aMsg, aParam.mFlags);
WriteParam(aMsg, aParam.mEvent);
}
static bool Read(const Message* aMsg, PickleIterator* aIter,
paramType* aResult) {
if (ReadParam(aMsg, aIter, &aResult->mDiagnosticsType) &&
ReadParam(aMsg, aIter, &aResult->mFormat) &&
ReadParam(aMsg, aIter, &aResult->mFlags) &&
ReadParam(aMsg, aIter, &aResult->mEvent)) {
return true;
}
return false;
};
};
template <>
struct ParamTraits<mozilla::DecoderDoctorDiagnostics::DiagnosticsType>
: public ContiguousEnumSerializerInclusive<
mozilla::DecoderDoctorDiagnostics::DiagnosticsType,
mozilla::DecoderDoctorDiagnostics::DiagnosticsType::eUnsaved,
mozilla::DecoderDoctorDiagnostics::DiagnosticsType::eDecodeWarning> {
};
template <>
struct ParamTraits<mozilla::DecoderDoctorEvent> {
typedef mozilla::DecoderDoctorEvent paramType;
static void Write(Message* aMsg, const paramType& aParam) {
int domain = aParam.mDomain;
WriteParam(aMsg, domain);
WriteParam(aMsg, aParam.mResult);
}
static bool Read(const Message* aMsg, PickleIterator* aIter,
paramType* aResult) {
int domain = 0;
if (ReadParam(aMsg, aIter, &domain) &&
ReadParam(aMsg, aIter, &aResult->mResult)) {
aResult->mDomain = paramType::Domain(domain);
return true;
}
return false;
};
};
} // namespace IPC
#endif // mozilla_dom_media_MediaIPCUtils_h

View File

@ -12,6 +12,7 @@ using VideoInfo from "MediaInfo.h";
using AudioInfo from "MediaInfo.h";
using struct mozilla::layers::TextureFactoryIdentifier from "mozilla/layers/CompositorTypes.h";
using mozilla::CreateDecoderParams::OptionSet from "PlatformDecoderModule.h";
using mozilla::DecoderDoctorDiagnostics from "DecoderDoctorDiagnostics.h";
namespace mozilla {
@ -32,6 +33,11 @@ sync protocol PRemoteDecoderManager
manages PRemoteDecoder;
parent:
sync Supports(RemoteDecoderInfoIPDL info,
TextureFactoryIdentifier? identifier)
returns (bool success,
DecoderDoctorDiagnostics diagnostics);
sync PRemoteDecoder(RemoteDecoderInfoIPDL info,
OptionSet options,
TextureFactoryIdentifier? identifier)

View File

@ -63,6 +63,13 @@ ShutdownObserver::Observe(nsISupports* aSubject, const char* aTopic,
StaticRefPtr<ShutdownObserver> sObserver;
static Maybe<layers::TextureFactoryIdentifier> MaybeTextureFactoryIdentifier(
const SupportDecoderParams& aParams) {
return aParams.mKnowsCompositor
? Some(aParams.mKnowsCompositor->GetTextureFactoryIdentifier())
: Nothing();
}
/* static */
void RemoteDecoderManagerChild::InitializeThread() {
MOZ_ASSERT(NS_IsMainThread());
@ -174,6 +181,40 @@ nsISerialEventTarget* RemoteDecoderManagerChild::GetManagerThread() {
return *remoteDecoderManagerThread;
}
/* static */
bool RemoteDecoderManagerChild::Supports(
RemoteDecodeIn aLocation, const SupportDecoderParams& aParams,
DecoderDoctorDiagnostics* aDiagnostics) {
bool supports = false;
DecoderDoctorDiagnostics diagnostics;
nsCOMPtr<nsISerialEventTarget> managerThread = GetManagerThread();
if (managerThread) {
RefPtr<Runnable> task =
NS_NewRunnableFunction("RemoteDecoderManager::Supports", [&]() {
auto* rdm = GetSingleton(aLocation);
const auto& trackInfo = aParams.mConfig;
if (trackInfo.GetAsVideoInfo()) {
VideoDecoderInfoIPDL info(*trackInfo.GetAsVideoInfo(),
aParams.mRate.mValue);
Unused << rdm->SendSupports(info,
MaybeTextureFactoryIdentifier(aParams),
&supports, &diagnostics);
} else if (trackInfo.GetAsAudioInfo()) {
Unused << rdm->SendSupports(*trackInfo.GetAsAudioInfo(), Nothing(),
&supports, &diagnostics);
}
});
SyncRunnable::DispatchToThread(managerThread, task);
}
if (aDiagnostics) {
*aDiagnostics = diagnostics;
}
return supports;
}
/* static */
already_AddRefed<MediaDataDecoder>
RemoteDecoderManagerChild::CreateAudioDecoder(

View File

@ -30,6 +30,9 @@ class RemoteDecoderManagerChild final
static RemoteDecoderManagerChild* GetSingleton(RemoteDecodeIn aLocation);
// Can be called from any thread.
static bool Supports(RemoteDecodeIn aLocation,
const SupportDecoderParams& aParams,
DecoderDoctorDiagnostics* aDiagnostics);
static already_AddRefed<MediaDataDecoder> CreateAudioDecoder(
const CreateDecoderParams& aParams);
static already_AddRefed<MediaDataDecoder> CreateVideoDecoder(

View File

@ -196,7 +196,9 @@ PRemoteDecoderParent* RemoteDecoderManagerParent::AllocPRemoteDecoderParent(
this, decoderInfo.videoInfo(), decoderInfo.framerate(), aOptions,
aIdentifier, sRemoteDecoderManagerParentThread, decodeTaskQueue,
aSuccess, aErrorDescription);
} else if (aRemoteDecoderInfo.type() == RemoteDecoderInfoIPDL::TAudioInfo) {
}
if (aRemoteDecoderInfo.type() == RemoteDecoderInfoIPDL::TAudioInfo) {
return new RemoteAudioDecoderParent(
this, aRemoteDecoderInfo.get_AudioInfo(), aOptions,
sRemoteDecoderManagerParentThread, decodeTaskQueue, aSuccess,
@ -225,6 +227,40 @@ void RemoteDecoderManagerParent::Open(
void RemoteDecoderManagerParent::ActorDealloc() { Release(); }
mozilla::ipc::IPCResult RemoteDecoderManagerParent::RecvSupports(
const RemoteDecoderInfoIPDL& aInfo,
const Maybe<layers::TextureFactoryIdentifier>& aIdentifier, bool* aSuccess,
DecoderDoctorDiagnostics* aDiagnostics) {
auto& factory = EnsurePDMFactory();
MediaResult error(NS_OK);
// It's important to initialize aSuccess because it is passed uninitialized
// which will cause an assert on the receiving side.
*aSuccess = false;
if (aInfo.type() == RemoteDecoderInfoIPDL::TAudioInfo) {
SupportDecoderParams params(aInfo.get_AudioInfo(), &error);
*aSuccess = factory.Supports(params, aDiagnostics);
} else if (aInfo.type() == RemoteDecoderInfoIPDL::TVideoDecoderInfoIPDL) {
RefPtr<KnowsCompositorVideo> knowsCompositor;
if (aIdentifier) {
// Check to see if we have a direct PVideoBridge connection to the
// destination process specified in aIdentifier, and create a
// KnowsCompositor representing that connection if so. If this fails, then
// we fall back to returning the decoded frames directly via Output().
knowsCompositor =
KnowsCompositorVideo::TryCreateForIdentifier(*aIdentifier);
}
const VideoDecoderInfoIPDL& info = aInfo.get_VideoDecoderInfoIPDL();
SupportDecoderParams params(info.videoInfo(), knowsCompositor,
media::VideoFrameRate(info.framerate()),
&error);
*aSuccess = factory.Supports(params, aDiagnostics);
}
return IPC_OK();
}
mozilla::ipc::IPCResult RemoteDecoderManagerParent::RecvReadback(
const SurfaceDescriptorGPUVideo& aSD, SurfaceDescriptor* aResult) {
const SurfaceDescriptorRemoteDecoder& sd = aSD;

View File

@ -61,6 +61,11 @@ class RemoteDecoderManagerParent final
bool* aSuccess, nsCString* aErrorDescription);
bool DeallocPRemoteDecoderParent(PRemoteDecoderParent* actor);
mozilla::ipc::IPCResult RecvSupports(
const RemoteDecoderInfoIPDL& aInfo,
const Maybe<layers::TextureFactoryIdentifier>& aIdentifier,
bool* aSuccess, DecoderDoctorDiagnostics* aDiagnostics);
mozilla::ipc::IPCResult RecvReadback(const SurfaceDescriptorGPUVideo& aSD,
SurfaceDescriptor* aResult);
mozilla::ipc::IPCResult RecvDeallocateSurfaceDescriptorGPUVideo(

View File

@ -33,41 +33,33 @@ using namespace layers; // for PlanarYCbCrData and BufferRecycleBin
using namespace ipc;
using namespace gfx;
class KnowsCompositorVideo : public layers::KnowsCompositor {
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(KnowsCompositorVideo, override)
layers::TextureForwarder* KnowsCompositorVideo::GetTextureForwarder() {
auto* vbc = VideoBridgeChild::GetSingleton();
return (vbc && vbc->CanSend()) ? vbc : nullptr;
}
layers::LayersIPCActor* KnowsCompositorVideo::GetLayersIPCActor() {
return GetTextureForwarder();
}
layers::TextureForwarder* GetTextureForwarder() override {
auto* vbc = VideoBridgeChild::GetSingleton();
return (vbc && vbc->CanSend()) ? vbc : nullptr;
}
layers::LayersIPCActor* GetLayersIPCActor() override {
return GetTextureForwarder();
/* static */ already_AddRefed<KnowsCompositorVideo>
KnowsCompositorVideo::TryCreateForIdentifier(
const layers::TextureFactoryIdentifier& aIdentifier) {
VideoBridgeChild* child = VideoBridgeChild::GetSingleton();
if (!child) {
return nullptr;
}
static already_AddRefed<KnowsCompositorVideo> TryCreateForIdentifier(
const layers::TextureFactoryIdentifier& aIdentifier) {
VideoBridgeChild* child = VideoBridgeChild::GetSingleton();
if (!child) {
return nullptr;
}
// The RDD process will never use hardware decoding since it's
// sandboxed, so don't bother trying to create a sync object.
TextureFactoryIdentifier ident = aIdentifier;
if (XRE_IsRDDProcess()) {
ident.mSyncHandle = 0;
}
RefPtr<KnowsCompositorVideo> knowsCompositor = new KnowsCompositorVideo();
knowsCompositor->IdentifyTextureHost(ident);
return knowsCompositor.forget();
// The RDD process will never use hardware decoding since it's
// sandboxed, so don't bother trying to create a sync object.
TextureFactoryIdentifier ident = aIdentifier;
if (XRE_IsRDDProcess()) {
ident.mSyncHandle = 0;
}
private:
KnowsCompositorVideo() = default;
virtual ~KnowsCompositorVideo() = default;
};
RefPtr<KnowsCompositorVideo> knowsCompositor = new KnowsCompositorVideo();
knowsCompositor->IdentifyTextureHost(ident);
return knowsCompositor.forget();
}
RemoteVideoDecoderChild::RemoteVideoDecoderChild(RemoteDecodeIn aLocation)
: RemoteDecoderChild(aLocation == RemoteDecodeIn::GpuProcess),

View File

@ -17,7 +17,20 @@ class BufferRecycleBin;
namespace mozilla {
class KnowsCompositorVideo;
class KnowsCompositorVideo : public layers::KnowsCompositor {
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(KnowsCompositorVideo, override)
layers::TextureForwarder* GetTextureForwarder() override;
layers::LayersIPCActor* GetLayersIPCActor() override;
static already_AddRefed<KnowsCompositorVideo> TryCreateForIdentifier(
const layers::TextureFactoryIdentifier& aIdentifier);
private:
KnowsCompositorVideo() = default;
virtual ~KnowsCompositorVideo() = default;
};
using mozilla::ipc::IPCResult;

View File

@ -209,15 +209,7 @@ already_AddRefed<MediaDataDecoder> PDMFactory::CreateDecoder(
if (diagnostics) {
// If libraries failed to load, the following loop over mCurrentPDMs
// will not even try to use them. So we record failures now.
if (mWMFFailedToLoad) {
diagnostics->SetWMFFailedToLoad();
}
if (mFFmpegFailedToLoad) {
diagnostics->SetFFmpegFailedToLoad();
}
if (mGMPPDMFailedToStartup) {
diagnostics->SetGMPPDMFailedToStartup();
}
diagnostics->SetFailureFlags(mFailureFlags);
}
for (auto& current : mCurrentPDMs) {
@ -376,12 +368,15 @@ void PDMFactory::CreateDefaultPDMs() {
#ifdef XP_WIN
if (StaticPrefs::media_wmf_enabled() && !IsWin7AndPre2000Compatible()) {
RefPtr<PlatformDecoderModule> m = MakeAndAddRef<WMFDecoderModule>();
RefPtr<WMFDecoderModule> m = MakeAndAddRef<WMFDecoderModule>();
StartupPDM(MakeAndAddRef<GpuDecoderModule>(m));
mWMFFailedToLoad = !StartupPDM(m.forget());
if (!StartupPDM(m.forget())) {
mFailureFlags += DecoderDoctorDiagnostics::Flags::WMFFailedToLoad;
}
} else {
mWMFFailedToLoad =
StaticPrefs::media_decoder_doctor_wmf_disabled_is_failure();
if (StaticPrefs::media_decoder_doctor_wmf_disabled_is_failure()) {
mFailureFlags += DecoderDoctorDiagnostics::Flags::WMFFailedToLoad;
}
}
#endif
#ifdef MOZ_APPLEMEDIA
@ -398,9 +393,10 @@ void PDMFactory::CreateDefaultPDMs() {
}
#endif
#ifdef MOZ_FFMPEG
mFFmpegFailedToLoad = StaticPrefs::media_ffmpeg_enabled()
? !CreateAndStartupPDM<FFmpegRuntimeLinker>()
: false;
if (StaticPrefs::media_ffmpeg_enabled() &&
!CreateAndStartupPDM<FFmpegRuntimeLinker>()) {
mFailureFlags += DecoderDoctorDiagnostics::Flags::FFmpegFailedToLoad;
}
#endif
#ifdef MOZ_WIDGET_ANDROID
if (StaticPrefs::media_android_media_codec_enabled()) {
@ -411,9 +407,10 @@ void PDMFactory::CreateDefaultPDMs() {
CreateAndStartupPDM<AgnosticDecoderModule>();
mGMPPDMFailedToStartup = StaticPrefs::media_gmp_decoder_enabled()
? !CreateAndStartupPDM<GMPDecoderModule>()
: false;
if (StaticPrefs::media_gmp_decoder_enabled() &&
!CreateAndStartupPDM<GMPDecoderModule>()) {
mFailureFlags += DecoderDoctorDiagnostics::Flags::GMPPDMFailedToStartup;
}
}
void PDMFactory::CreateNullPDM() {
@ -441,15 +438,7 @@ already_AddRefed<PlatformDecoderModule> PDMFactory::GetDecoderModule(
if (aDiagnostics) {
// If libraries failed to load, the following loop over mCurrentPDMs
// will not even try to use them. So we record failures now.
if (mWMFFailedToLoad) {
aDiagnostics->SetWMFFailedToLoad();
}
if (mFFmpegFailedToLoad) {
aDiagnostics->SetFFmpegFailedToLoad();
}
if (mGMPPDMFailedToStartup) {
aDiagnostics->SetGMPPDMFailedToStartup();
}
aDiagnostics->SetFailureFlags(mFailureFlags);
}
RefPtr<PlatformDecoderModule> pdm;

View File

@ -7,6 +7,7 @@
#if !defined(PDMFactory_h_)
# define PDMFactory_h_
# include "DecoderDoctorDiagnostics.h"
# include "PlatformDecoderModule.h"
# include "mozilla/StaticMutex.h"
@ -14,7 +15,6 @@ class CDMProxy;
namespace mozilla {
class DecoderDoctorDiagnostics;
class PDMFactoryImpl;
template <class T>
class StaticAutoPtr;
@ -78,9 +78,7 @@ class PDMFactory final {
RefPtr<PlatformDecoderModule> mEMEPDM;
RefPtr<PlatformDecoderModule> mNullPDM;
bool mWMFFailedToLoad = false;
bool mFFmpegFailedToLoad = false;
bool mGMPPDMFailedToStartup = false;
DecoderDoctorDiagnostics::FlagsSet mFailureFlags;
friend class RemoteVideoDecoderParent;
static void EnsureInit();

View File

@ -884,6 +884,8 @@ description = legacy sync IPC - please add detailed description
description = legacy sync IPC - please add detailed description
[PRemoteDecoderManager::PRemoteDecoder]
description = See Bug 1505976 - investigate changing to async instead of matching GPU pattern
[PRemoteDecoderManager::Supports]
description = See Bug 1672072 - We will always need a synchronous call in the worse case scenario, follow-up calls will be cached; however it could be made fully asynchronous under some circumstances..
[PContent::LaunchRDDProcess]
description = See Bug 1518344 - investigate using async for PContent::LaunchRDDProcess
[PRemoteDecoderManager::Readback]