From aa3f97c3efc995457ab584c278c7e7a46893a77b Mon Sep 17 00:00:00 2001 From: Chris Pearce Date: Fri, 5 May 2017 13:55:14 +1200 Subject: [PATCH] Bug 1362212 - Move canPlayType telemetry to an idle service observer off main thread. r=gerald Currently we call HTMLMediaElement.canPlayType() in a JS function called shortly after startup in order to collect telemetry as to how many of our users don't have functioning decoders. Unfortunately, HTMLMediaElement.canPlayType() checks whether we can play a codec by instantiating a decoder, and this requires us to load the system decoding libraries from disk. This requires disk I/O, which can cause jank. We have some BHR reports showing that canPlayType can hang for > 8 seconds to back this up. So move the collection of this telemetry to an idle service observer, so that we only collect this when the user is idle, and do it on a non-main thread so it is less likely to cause jank. MozReview-Commit-ID: HJQawmRxz --HG-- extra : rebase_source : f5a8596fd9de770abd20e1a3e8ac0bcbb5b48599 --- dom/media/MediaDecoder.cpp | 103 ++++++++++++++++++++++++++++++++++ dom/media/MediaPrefs.h | 2 + dom/media/fmp4/MP4Decoder.cpp | 2 +- 3 files changed, 106 insertions(+), 1 deletion(-) diff --git a/dom/media/MediaDecoder.cpp b/dom/media/MediaDecoder.cpp index 588d07a58db5..487141bd7aec 100644 --- a/dom/media/MediaDecoder.cpp +++ b/dom/media/MediaDecoder.cpp @@ -33,10 +33,15 @@ #include "mozilla/Telemetry.h" #include "Layers.h" #include "mozilla/layers/ShadowLayers.h" +#include "nsIIdleService.h" +#include "MP4Decoder.h" #ifdef MOZ_ANDROID_OMX #include "AndroidBridge.h" #endif +#ifdef XP_WIN +#include "Objbase.h" +#endif using namespace mozilla::dom; using namespace mozilla::layers; @@ -125,10 +130,108 @@ LazyLogModule gMediaTimerLog("MediaTimer"); constexpr TimeUnit MediaDecoder::DEFAULT_NEXT_FRAME_AVAILABLE_BUFFERED; +class MediaIdleListener : public nsIObserver +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + +private: + virtual ~MediaIdleListener() {} +}; + +NS_IMPL_ISUPPORTS(MediaIdleListener, nsIObserver) + +const unsigned long sMediaTelemetryIdleSeconds = 30u; + +NS_IMETHODIMP +MediaIdleListener::Observe(nsISupports*, + const char* aTopic, + const char16_t* aData) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(XRE_IsParentProcess()); + + if (!NS_LITERAL_CSTRING(OBSERVER_TOPIC_IDLE).EqualsASCII(aTopic)) { + return NS_OK; + } + + // Collect telemetry about whether the user's machine can decode AAC + // and H.264. We do this off main thread, as determining whether we + // can create a decoder requires us to load decoding libraries, which + // requires disk I/O, and we don't want to be blocking the main thread + // on the chrome process for I/O. + + nsresult rv; + nsCOMPtr idleService = + do_GetService("@mozilla.org/widget/idleservice;1", &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return NS_OK; + } + + RefPtr self = this; + rv = idleService->RemoveIdleObserver(self, sMediaTelemetryIdleSeconds); + if (NS_WARN_IF(NS_FAILED(rv))) { + return NS_OK; + } + + RefPtr thread; + rv = NS_NewNamedThread("MediaTelemetry", getter_AddRefs(thread)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return NS_OK; + } + + thread->Dispatch( + NS_NewRunnableFunction([thread]() { +#if XP_WIN + // Windows Media Foundation requires MSCOM to be inited. + HRESULT hr = CoInitializeEx(0, COINIT_MULTITHREADED); + MOZ_ASSERT(hr == S_OK); +#endif + bool aac = MP4Decoder::IsSupportedType( + MediaContainerType(MEDIAMIMETYPE("audio/mp4")), nullptr); + bool h264 = MP4Decoder::IsSupportedType( + MediaContainerType(MEDIAMIMETYPE("video/mp4")), nullptr); + + AbstractThread::MainThread()->Dispatch( + NS_NewRunnableFunction([thread, aac, h264]() { + MOZ_LOG(gMediaDecoderLog, LogLevel::Debug, ("MediaTelemetry aac=%d h264=%d", aac, h264)); + Telemetry::Accumulate( + Telemetry::HistogramID::VIDEO_CAN_CREATE_AAC_DECODER, aac); + Telemetry::Accumulate( + Telemetry::HistogramID::VIDEO_CAN_CREATE_H264_DECODER, h264); + thread->AsyncShutdown(); + })); + }), + NS_DISPATCH_NORMAL); + + return NS_OK; +} + void MediaDecoder::InitStatics() { MOZ_ASSERT(NS_IsMainThread()); + MOZ_LOG(gMediaDecoderLog, LogLevel::Debug, ("%s", __func__)); + + if (!XRE_IsParentProcess()) { + return; + } + + // Add an idle listener so that we can record some telemetry when the + // user is idle. + nsresult rv; + nsCOMPtr idleService = + do_GetService("@mozilla.org/widget/idleservice;1", &rv); + if (NS_WARN_IF(NS_FAILED((rv)))) { + return; + } + + RefPtr listener = new MediaIdleListener(); + rv = idleService->AddIdleObserver(listener, sMediaTelemetryIdleSeconds); + if (NS_WARN_IF(NS_FAILED((rv)))) { + return; + } } NS_IMPL_ISUPPORTS(MediaMemoryTracker, nsIMemoryReporter) diff --git a/dom/media/MediaPrefs.h b/dom/media/MediaPrefs.h index d86082e331be..f447c1bb0513 100644 --- a/dom/media/MediaPrefs.h +++ b/dom/media/MediaPrefs.h @@ -181,6 +181,8 @@ private: DECL_MEDIA_PREF("media.rust.mp4parser", EnableRustMP4Parser, bool, false); #endif + DECL_MEDIA_PREF("media.mp4.enabled", MP4Enabled, bool, false); + // Error/warning handling, Decoder Doctor DECL_MEDIA_PREF("media.playback.warnings-as-errors", MediaWarningsAsErrors, bool, false); DECL_MEDIA_PREF("media.playback.warnings-as-errors.stagefright-vs-rust", diff --git a/dom/media/fmp4/MP4Decoder.cpp b/dom/media/fmp4/MP4Decoder.cpp index 4c1f6582fd47..3a95117fe326 100644 --- a/dom/media/fmp4/MP4Decoder.cpp +++ b/dom/media/fmp4/MP4Decoder.cpp @@ -181,7 +181,7 @@ MP4Decoder::IsAAC(const nsACString& aMimeType) bool MP4Decoder::IsEnabled() { - return Preferences::GetBool("media.mp4.enabled", true); + return MediaPrefs::MP4Enabled(); } // sTestH264ExtraData represents the content of the avcC atom found in