summaryrefslogtreecommitdiffstats
path: root/dom/media/mediacapabilities
diff options
context:
space:
mode:
Diffstat (limited to 'dom/media/mediacapabilities')
-rw-r--r--dom/media/mediacapabilities/BenchmarkStorageChild.cpp34
-rw-r--r--dom/media/mediacapabilities/BenchmarkStorageChild.h28
-rw-r--r--dom/media/mediacapabilities/BenchmarkStorageParent.cpp130
-rw-r--r--dom/media/mediacapabilities/BenchmarkStorageParent.h43
-rw-r--r--dom/media/mediacapabilities/DecoderBenchmark.cpp243
-rw-r--r--dom/media/mediacapabilities/DecoderBenchmark.h77
-rw-r--r--dom/media/mediacapabilities/KeyValueStorage.cpp234
-rw-r--r--dom/media/mediacapabilities/KeyValueStorage.h48
-rw-r--r--dom/media/mediacapabilities/MediaCapabilities.cpp658
-rw-r--r--dom/media/mediacapabilities/MediaCapabilities.h104
-rw-r--r--dom/media/mediacapabilities/PBenchmarkStorage.ipdl23
-rw-r--r--dom/media/mediacapabilities/moz.build32
12 files changed, 1654 insertions, 0 deletions
diff --git a/dom/media/mediacapabilities/BenchmarkStorageChild.cpp b/dom/media/mediacapabilities/BenchmarkStorageChild.cpp
new file mode 100644
index 0000000000..9482f5bffe
--- /dev/null
+++ b/dom/media/mediacapabilities/BenchmarkStorageChild.cpp
@@ -0,0 +1,34 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "BenchmarkStorageChild.h"
+#include "mozilla/dom/ContentChild.h"
+
+namespace mozilla {
+
+static PBenchmarkStorageChild* sChild = nullptr;
+
+/* static */
+PBenchmarkStorageChild* BenchmarkStorageChild::Instance() {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!sChild) {
+ sChild = new BenchmarkStorageChild();
+ PContentChild* contentChild = dom::ContentChild::GetSingleton();
+ MOZ_ASSERT(contentChild);
+ contentChild->SendPBenchmarkStorageConstructor();
+ }
+ MOZ_ASSERT(sChild);
+ return sChild;
+}
+
+BenchmarkStorageChild::~BenchmarkStorageChild() {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (sChild == this) {
+ sChild = nullptr;
+ }
+}
+
+} // namespace mozilla
diff --git a/dom/media/mediacapabilities/BenchmarkStorageChild.h b/dom/media/mediacapabilities/BenchmarkStorageChild.h
new file mode 100644
index 0000000000..c53571e13b
--- /dev/null
+++ b/dom/media/mediacapabilities/BenchmarkStorageChild.h
@@ -0,0 +1,28 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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_mediacapabilities_BenchmarkStorageChild_h
+#define include_dom_media_mediacapabilities_BenchmarkStorageChild_h
+
+#include "mozilla/PBenchmarkStorageChild.h"
+
+namespace mozilla {
+
+class BenchmarkStorageChild : public PBenchmarkStorageChild {
+ public:
+ /* Singleton class to avoid recreating the protocol every time we need access
+ * to the storage. */
+ static PBenchmarkStorageChild* Instance();
+
+ ~BenchmarkStorageChild();
+
+ private:
+ BenchmarkStorageChild() = default;
+};
+
+} // namespace mozilla
+
+#endif // include_dom_media_mediacapabilities_BenchmarkStorageChild_h
diff --git a/dom/media/mediacapabilities/BenchmarkStorageParent.cpp b/dom/media/mediacapabilities/BenchmarkStorageParent.cpp
new file mode 100644
index 0000000000..64ae6ddd44
--- /dev/null
+++ b/dom/media/mediacapabilities/BenchmarkStorageParent.cpp
@@ -0,0 +1,130 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "BenchmarkStorageParent.h"
+#include "KeyValueStorage.h"
+
+namespace mozilla {
+
+/* Moving average window size. */
+const int32_t AVG_WINDOW = 20;
+/* Calculate the moving average for the new value aValue for the current window
+ * aWindow and the already existing average aAverage. When the method returns
+ * aAverage will contain the new average and aWindow will contain the new
+ * window.*/
+void BenchmarkStorageParent::MovingAverage(int32_t& aAverage, int32_t& aWindow,
+ const int32_t aValue) {
+ if (aWindow < AVG_WINDOW) {
+ aAverage = (aAverage * aWindow + aValue) / (aWindow + 1);
+ aWindow++;
+ return;
+ }
+ MOZ_ASSERT(aWindow == AVG_WINDOW);
+ aAverage = (aAverage - aAverage / aWindow) + (aValue / aWindow);
+}
+
+/* In order to decrease the number of times the database is accessed when the
+ * moving average is stored or retrieved we use the same value to store _both_
+ * the window and the average. The range of the average is limited since it is
+ * a percentage (0-100), and the range of the window is limited
+ * (1-20). Thus the number that is stored in the database is in the form
+ * (digits): WWAAA. For example, the value stored when an average(A) of 88
+ * corresponds to a window(W) 7 is 7088. The average of 100 that corresponds to
+ * a window of 20 is 20100. The following methods are helpers to extract or
+ * construct the stored value according to the above. */
+
+/* Stored value will be in the form WWAAA(19098). We need to extract the window
+ * (19) and the average score (98). The aValue will
+ * be parsed, the aWindow will contain the window (of the moving average) and
+ * the return value will contain the average itself. */
+int32_t BenchmarkStorageParent::ParseStoredValue(int32_t aValue,
+ int32_t& aWindow) {
+ MOZ_ASSERT(aValue > 999);
+ MOZ_ASSERT(aValue < 100000);
+
+ int32_t score = aValue % 1000;
+ aWindow = (aValue / 1000) % 100;
+ return score;
+}
+
+int32_t BenchmarkStorageParent::PrepareStoredValue(int32_t aScore,
+ int32_t aWindow) {
+ MOZ_ASSERT(aScore >= 0);
+ MOZ_ASSERT(aScore <= 100);
+ MOZ_ASSERT(aWindow > 0);
+ MOZ_ASSERT(aWindow < 21);
+
+ return aWindow * 1000 + aScore;
+}
+
+BenchmarkStorageParent::BenchmarkStorageParent()
+ : mStorage(new KeyValueStorage) {}
+
+IPCResult BenchmarkStorageParent::RecvPut(const nsCString& aDbName,
+ const nsCString& aKey,
+ const int32_t& aValue) {
+ // In order to calculate and store the new moving average, we need to get the
+ // stored value and window first, to calculate the new score and window, and
+ // then to store the new aggregated value.
+ mStorage->Get(aDbName, aKey)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [storage = mStorage, aDbName, aKey, aValue](int32_t aResult) {
+ int32_t window = 0;
+ int32_t average = 0;
+ if (aResult >= 0) {
+ // The key found.
+ average = ParseStoredValue(aResult, window);
+ }
+ MovingAverage(average, window, aValue);
+ int32_t newValue = PrepareStoredValue(average, window);
+ // Avoid storing if the values are the same. This is an optimization
+ // to minimize the disk usage.
+ if (aResult != newValue) {
+ storage->Put(aDbName, aKey, newValue);
+ }
+ },
+ [](nsresult rv) { /*do nothing*/ });
+
+ return IPC_OK();
+}
+
+IPCResult BenchmarkStorageParent::RecvGet(const nsCString& aDbName,
+ const nsCString& aKey,
+ GetResolver&& aResolve) {
+ mStorage->Get(aDbName, aKey)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [aResolve](int32_t aResult) {
+ int32_t window = 0; // not used
+ aResolve(aResult < 0 ? -1 : ParseStoredValue(aResult, window));
+ },
+ [aResolve](nsresult rv) { aResolve(-1); });
+
+ return IPC_OK();
+}
+
+IPCResult BenchmarkStorageParent::RecvCheckVersion(const nsCString& aDbName,
+ int32_t aVersion) {
+ mStorage->Get(aDbName, "Version"_ns)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [storage = mStorage, aDbName, aVersion](int32_t aResult) {
+ if (aVersion != aResult) {
+ storage->Clear(aDbName)->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [storage, aDbName, aVersion](bool) {
+ storage->Put(aDbName, "Version"_ns, aVersion);
+ },
+ [](nsresult rv) { /*do nothing*/ });
+ }
+ },
+ [](nsresult rv) { /*do nothing*/ });
+
+ return IPC_OK();
+}
+
+}; // namespace mozilla
diff --git a/dom/media/mediacapabilities/BenchmarkStorageParent.h b/dom/media/mediacapabilities/BenchmarkStorageParent.h
new file mode 100644
index 0000000000..e9c8f9dd9b
--- /dev/null
+++ b/dom/media/mediacapabilities/BenchmarkStorageParent.h
@@ -0,0 +1,43 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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_mediacapabilities_BenchmarkStorageParent_h
+#define include_dom_media_mediacapabilities_BenchmarkStorageParent_h
+
+#include "mozilla/PBenchmarkStorageParent.h"
+
+namespace mozilla {
+class KeyValueStorage;
+
+using mozilla::ipc::IPCResult;
+
+class BenchmarkStorageParent : public PBenchmarkStorageParent {
+ friend class PBenchmarkStorageParent;
+
+ public:
+ BenchmarkStorageParent();
+
+ IPCResult RecvPut(const nsCString& aDbName, const nsCString& aKey,
+ const int32_t& aValue);
+
+ IPCResult RecvGet(const nsCString& aDbName, const nsCString& aKey,
+ GetResolver&& aResolve);
+
+ IPCResult RecvCheckVersion(const nsCString& aDbName, int32_t aVersion);
+
+ /* Helper methods exposed here to be tested via gtest. */
+ static void MovingAverage(int32_t& aAverage, int32_t& aWindow,
+ const int32_t aValue);
+ static int32_t ParseStoredValue(int32_t aValue, int32_t& aWindow);
+ static int32_t PrepareStoredValue(int32_t aScore, int32_t aWindow);
+
+ private:
+ RefPtr<KeyValueStorage> mStorage;
+};
+
+} // namespace mozilla
+
+#endif // include_dom_media_mediacapabilities_BenchmarkStorageParent_h
diff --git a/dom/media/mediacapabilities/DecoderBenchmark.cpp b/dom/media/mediacapabilities/DecoderBenchmark.cpp
new file mode 100644
index 0000000000..b5c9a2a693
--- /dev/null
+++ b/dom/media/mediacapabilities/DecoderBenchmark.cpp
@@ -0,0 +1,243 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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 "DecoderBenchmark.h"
+#include "mozilla/BenchmarkStorageChild.h"
+#include "mozilla/media/MediaUtils.h"
+#include "mozilla/StaticPrefs_media.h"
+
+namespace mozilla {
+
+void DecoderBenchmark::StoreScore(const nsACString& aDecoderName,
+ const nsACString& aKey,
+ RefPtr<FrameStatistics> aStats) {
+ FrameStatisticsData statsData = aStats->GetFrameStatisticsData();
+ uint64_t totalFrames = FrameStatistics::GetTotalFrames(statsData);
+ uint64_t droppedFrames = FrameStatistics::GetDroppedFrames(statsData);
+
+ MOZ_ASSERT(droppedFrames <= totalFrames);
+ MOZ_ASSERT(totalFrames >= mLastTotalFrames);
+ MOZ_ASSERT(droppedFrames >= mLastDroppedFrames);
+
+ uint64_t diffTotalFrames = totalFrames - mLastTotalFrames;
+ uint64_t diffDroppedFrames = droppedFrames - mLastDroppedFrames;
+
+ /* Update now in case the method returns at the if check bellow. */
+ mLastTotalFrames = totalFrames;
+ mLastDroppedFrames = droppedFrames;
+
+ /* A minimum number of 10 frames is required to store the score. */
+ if (diffTotalFrames < 10) {
+ return;
+ }
+
+ int32_t percentage =
+ 100 - 100 * float(diffDroppedFrames) / float(diffTotalFrames);
+
+ MOZ_ASSERT(percentage >= 0);
+
+ Put(aDecoderName, aKey, percentage);
+}
+
+void DecoderBenchmark::Put(const nsACString& aDecoderName,
+ const nsACString& aKey, int32_t aValue) {
+ MOZ_ASSERT(NS_IsMainThread());
+ const nsCString name(aDecoderName);
+ const nsCString key(aKey);
+ BenchmarkStorageChild::Instance()->SendPut(name, key, aValue);
+}
+
+RefPtr<BenchmarkScorePromise> DecoderBenchmark::GetScore(
+ const nsACString& aDecoderName, const nsACString& aKey) {
+ if (NS_IsMainThread()) {
+ return Get(aDecoderName, aKey);
+ }
+
+ RefPtr<DecoderBenchmark> self = this;
+ const nsCString decoderName(aDecoderName);
+ const nsCString key(aKey);
+ return InvokeAsync(
+ GetMainThreadSerialEventTarget(), __func__,
+ [self, decoderName, key] { return self->Get(decoderName, key); });
+}
+
+RefPtr<BenchmarkScorePromise> DecoderBenchmark::Get(
+ const nsACString& aDecoderName, const nsACString& aKey) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ const nsCString name(aDecoderName);
+ const nsCString key(aKey);
+ return BenchmarkStorageChild::Instance()->SendGet(name, key)->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [](int32_t aResult) {
+ return BenchmarkScorePromise::CreateAndResolve(aResult, __func__);
+ },
+ [](ipc::ResponseRejectReason&&) {
+ return BenchmarkScorePromise::CreateAndReject(NS_ERROR_FAILURE,
+ __func__);
+ });
+}
+
+/* The key string consists of the video properties resolution, framerate,
+ * and bitdepth. There are various levels for each of them. The key is
+ * formated by the closest level, for example, a video with width=1920,
+ * height=1080, frameRate=24, and bitdepth=8bit will have the key:
+ * "ResolutionLevel5-FrameRateLevel1-8bit". */
+
+#define NELEMS(x) (sizeof(x) / sizeof((x)[0]))
+
+/* Keep them sorted */
+const uint32_t PixelLevels[] = {
+ /* 256x144 =*/36864,
+ /* 426x240 =*/102240,
+ /* 640x360 =*/230400,
+ /* 854x480 =*/409920,
+ /* 1280x720 =*/921600,
+ /* 1920x1080 =*/2073600,
+ /* 2560x1440 =*/3686400,
+ /* 3840x2160 =*/8294400,
+};
+const size_t PixelLevelsSize = NELEMS(PixelLevels);
+
+const uint32_t FrameRateLevels[] = {
+ 15, 24, 30, 50, 60,
+};
+const size_t FrameRateLevelsSize = NELEMS(FrameRateLevels);
+
+/* static */
+nsCString KeyUtil::FindLevel(const uint32_t aLevels[], const size_t length,
+ uint32_t aValue) {
+ MOZ_ASSERT(aValue);
+ if (aValue <= aLevels[0]) {
+ return "Level0"_ns;
+ }
+ nsAutoCString level("Level");
+ size_t lastIndex = length - 1;
+ if (aValue >= aLevels[lastIndex]) {
+ level.AppendInt(static_cast<uint32_t>(lastIndex));
+ return std::move(level);
+ }
+ for (size_t i = 0; i < lastIndex; ++i) {
+ if (aValue >= aLevels[i + 1]) {
+ continue;
+ }
+ if (aValue - aLevels[i] < aLevels[i + 1] - aValue) {
+ level.AppendInt(static_cast<uint32_t>(i));
+ return std::move(level);
+ }
+ level.AppendInt(static_cast<uint32_t>(i + 1));
+ return std::move(level);
+ }
+ MOZ_CRASH("Array is not sorted");
+ return ""_ns;
+}
+
+/* static */
+nsCString KeyUtil::BitDepthToStr(uint8_t aBitDepth) {
+ switch (aBitDepth) {
+ case 8: // ColorDepth::COLOR_8
+ return "-8bit"_ns;
+ case 10: // ColorDepth::COLOR_10
+ case 12: // ColorDepth::COLOR_12
+ case 16: // ColorDepth::COLOR_16
+ return "-non8bit"_ns;
+ }
+ MOZ_ASSERT_UNREACHABLE("invalid color depth value");
+ return ""_ns;
+}
+
+/* static */
+nsCString KeyUtil::CreateKey(const DecoderBenchmarkInfo& aBenchInfo) {
+ nsAutoCString key("Resolution");
+ key.Append(FindLevel(PixelLevels, PixelLevelsSize,
+ aBenchInfo.mWidth * aBenchInfo.mHeight));
+
+ key.Append("-FrameRate");
+ key.Append(
+ FindLevel(FrameRateLevels, FrameRateLevelsSize, aBenchInfo.mFrameRate));
+
+ key.Append(BitDepthToStr(aBenchInfo.mBitDepth));
+
+ return std::move(key);
+}
+
+void DecoderBenchmark::Store(const DecoderBenchmarkInfo& aBenchInfo,
+ RefPtr<FrameStatistics> aStats) {
+ if (!XRE_IsContentProcess()) {
+ NS_WARNING(
+ "Storing a benchmark is only allowed only from the content process.");
+ return;
+ }
+ StoreScore(aBenchInfo.mContentType, KeyUtil::CreateKey(aBenchInfo), aStats);
+}
+
+/* static */
+RefPtr<BenchmarkScorePromise> DecoderBenchmark::Get(
+ const DecoderBenchmarkInfo& aBenchInfo) {
+ if (!XRE_IsContentProcess()) {
+ NS_WARNING(
+ "Getting a benchmark is only allowed only from the content process.");
+ return BenchmarkScorePromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
+ }
+ // There is no need for any of the data members to query the database, thus
+ // it can be a static method.
+ auto bench = MakeRefPtr<DecoderBenchmark>();
+ return bench->GetScore(aBenchInfo.mContentType,
+ KeyUtil::CreateKey(aBenchInfo));
+}
+
+static nsTHashMap<nsCStringHashKey, int32_t> DecoderVersionTable() {
+ nsTHashMap<nsCStringHashKey, int32_t> decoderVersionTable;
+
+ /*
+ * For the decoders listed here, the benchmark version number will be checked.
+ * If the version number does not exist in the database or is different than
+ * the version number listed here, all the benchmark entries for this decoder
+ * will be erased. An example of assigning the version number `1` for AV1
+ * decoder is:
+ *
+ * decoderVersionTable.InsertOrUpdate("video/av1"_ns, 1);
+ *
+ * For the decoders not listed here the `CheckVersion` method exits early, to
+ * avoid sending unecessary IPC messages.
+ */
+
+ return decoderVersionTable;
+}
+
+/* static */
+void DecoderBenchmark::CheckVersion(const nsACString& aDecoderName) {
+ if (!XRE_IsContentProcess()) {
+ NS_WARNING(
+ "Checking version is only allowed only from the content process.");
+ return;
+ }
+
+ if (!StaticPrefs::media_mediacapabilities_from_database()) {
+ return;
+ }
+
+ nsCString name(aDecoderName);
+ int32_t version;
+ if (!DecoderVersionTable().Get(name, &version)) {
+ // A version is not set for that decoder ignore.
+ return;
+ }
+
+ if (NS_IsMainThread()) {
+ BenchmarkStorageChild::Instance()->SendCheckVersion(name, version);
+ return;
+ }
+
+ DebugOnly<nsresult> rv =
+ GetMainThreadSerialEventTarget()->Dispatch(NS_NewRunnableFunction(
+ "DecoderBenchmark::CheckVersion", [name, version]() {
+ BenchmarkStorageChild::Instance()->SendCheckVersion(name, version);
+ }));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+}
+
+} // namespace mozilla
diff --git a/dom/media/mediacapabilities/DecoderBenchmark.h b/dom/media/mediacapabilities/DecoderBenchmark.h
new file mode 100644
index 0000000000..af730fcef3
--- /dev/null
+++ b/dom/media/mediacapabilities/DecoderBenchmark.h
@@ -0,0 +1,77 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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 MOZILLA_DECODER_BENCHMARK_H
+#define MOZILLA_DECODER_BENCHMARK_H
+
+#include "FrameStatistics.h"
+#include "mozilla/BenchmarkStorageChild.h"
+#include "mozilla/KeyValueStorage.h"
+
+namespace mozilla {
+
+typedef KeyValueStorage::GetPromise BenchmarkScorePromise;
+
+struct DecoderBenchmarkInfo final {
+ const nsCString mContentType;
+ const int32_t mWidth;
+ const int32_t mHeight;
+ const int32_t mFrameRate;
+ const uint32_t mBitDepth;
+};
+
+class DecoderBenchmark final {
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DecoderBenchmark)
+
+ public:
+ void Store(const DecoderBenchmarkInfo& aBenchInfo,
+ RefPtr<FrameStatistics> aStats);
+
+ static RefPtr<BenchmarkScorePromise> Get(
+ const DecoderBenchmarkInfo& aBenchInfo);
+
+ /* For the specific decoder, specified by aDecoderName, it compares the
+ * version number, from a static list of versions, to the version number
+ * found in the database. If those numbers are different all benchmark
+ * entries for that decoder are deleted. */
+ static void CheckVersion(const nsACString& aDecoderName);
+
+ private:
+ void StoreScore(const nsACString& aDecoderName, const nsACString& aKey,
+ RefPtr<FrameStatistics> aStats);
+
+ RefPtr<BenchmarkScorePromise> GetScore(const nsACString& aDecoderName,
+ const nsACString& aKey);
+
+ void Put(const nsACString& aDecoderName, const nsACString& aKey,
+ int32_t aValue);
+
+ RefPtr<BenchmarkScorePromise> Get(const nsACString& aDecoderName,
+ const nsACString& aKey);
+ ~DecoderBenchmark() = default;
+
+ // Keep the last TotalFrames and DroppedFrames from FrameStatistics.
+ // FrameStatistics keep an ever-increasing counter across the entire video and
+ // even when there are resolution changes. This code is called whenever there
+ // is a resolution change and we need to calculate the benchmark since the
+ // last call.
+ uint64_t mLastTotalFrames = 0;
+ uint64_t mLastDroppedFrames = 0;
+};
+
+class KeyUtil {
+ public:
+ static nsCString CreateKey(const DecoderBenchmarkInfo& aBenchInfo);
+
+ private:
+ static nsCString BitDepthToStr(uint8_t aBitDepth);
+ static nsCString FindLevel(const uint32_t aLevels[], const size_t length,
+ uint32_t aValue);
+};
+
+} // namespace mozilla
+
+#endif // MOZILLA_DECODER_BENCHMARK_H
diff --git a/dom/media/mediacapabilities/KeyValueStorage.cpp b/dom/media/mediacapabilities/KeyValueStorage.cpp
new file mode 100644
index 0000000000..f0ac0aad7d
--- /dev/null
+++ b/dom/media/mediacapabilities/KeyValueStorage.cpp
@@ -0,0 +1,234 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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 "KeyValueStorage.h"
+
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsVariant.h"
+
+namespace mozilla {
+
+class DatabaseCallback final : public nsIKeyValueDatabaseCallback {
+ public:
+ NS_DECL_ISUPPORTS
+
+ explicit DatabaseCallback(RefPtr<nsIKeyValueDatabase>& aDatabase)
+ : mDatabase(aDatabase) {}
+ NS_IMETHOD Resolve(nsIKeyValueDatabase* aDatabase) override {
+ if (!aDatabase) {
+ mResultPromise.Reject(NS_ERROR_FAILURE, __func__);
+ }
+ mDatabase = aDatabase;
+ mResultPromise.Resolve(true, __func__);
+ return NS_OK;
+ }
+ NS_IMETHOD Reject(const nsACString&) override {
+ mResultPromise.Reject(NS_ERROR_FAILURE, __func__);
+ return NS_OK;
+ }
+ RefPtr<GenericPromise> Ensure() { return mResultPromise.Ensure(__func__); }
+
+ protected:
+ ~DatabaseCallback() = default;
+ RefPtr<nsIKeyValueDatabase>& mDatabase;
+ MozPromiseHolder<GenericPromise> mResultPromise;
+};
+NS_IMPL_ISUPPORTS(DatabaseCallback, nsIKeyValueDatabaseCallback);
+
+RefPtr<GenericPromise> KeyValueStorage::Init() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!mDatabaseName.IsEmpty());
+ nsresult rv;
+
+ nsCOMPtr<nsIFile> profileDir;
+ rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
+ getter_AddRefs(profileDir));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return GenericPromise::CreateAndReject(rv, __func__);
+ }
+ MOZ_ASSERT(profileDir);
+
+ rv = profileDir->AppendNative("mediacapabilities"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return GenericPromise::CreateAndReject(rv, __func__);
+ }
+
+ rv = profileDir->Create(nsIFile::DIRECTORY_TYPE, 0700);
+ if (rv != NS_ERROR_FILE_ALREADY_EXISTS && NS_WARN_IF(NS_FAILED(rv))) {
+ return GenericPromise::CreateAndReject(rv, __func__);
+ }
+
+ nsCOMPtr<nsIKeyValueService> keyValueService =
+ do_GetService("@mozilla.org/key-value-service;1", &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return GenericPromise::CreateAndReject(rv, __func__);
+ }
+ MOZ_ASSERT(keyValueService);
+
+ auto callback = MakeRefPtr<DatabaseCallback>(mDatabase);
+
+ nsString path;
+ profileDir->GetPath(path);
+ keyValueService->GetOrCreate(callback, NS_ConvertUTF16toUTF8(path),
+ mDatabaseName);
+ return callback->Ensure();
+}
+
+class VoidCallback final : public nsIKeyValueVoidCallback {
+ public:
+ NS_DECL_ISUPPORTS
+
+ explicit VoidCallback(const RefPtr<KeyValueStorage>& aOwner)
+ : nsIKeyValueVoidCallback(), mOwner(aOwner) {}
+
+ NS_IMETHOD Resolve() override {
+ mResultPromise.Resolve(true, __func__);
+ return NS_OK;
+ }
+ NS_IMETHOD Reject(const nsACString&) override {
+ mResultPromise.Reject(NS_ERROR_FAILURE, __func__);
+ return NS_OK;
+ }
+ RefPtr<GenericPromise> Ensure(const char* aMethodName) {
+ return mResultPromise.Ensure(aMethodName);
+ }
+
+ protected:
+ ~VoidCallback() = default;
+ MozPromiseHolder<GenericPromise> mResultPromise;
+ RefPtr<KeyValueStorage> mOwner;
+};
+NS_IMPL_ISUPPORTS(VoidCallback, nsIKeyValueVoidCallback);
+
+RefPtr<GenericPromise> KeyValueStorage::Put(const nsACString& aKey,
+ int32_t aValue) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mDatabase);
+ MOZ_ASSERT(!mDatabaseName.IsEmpty());
+
+ auto value = MakeRefPtr<nsVariant>();
+ nsresult rv = value->SetAsInt32(aValue);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return GenericPromise::CreateAndReject(rv, __func__);
+ }
+
+ auto callback = MakeRefPtr<VoidCallback>(this);
+ rv = mDatabase->Put(callback, aKey, value);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return GenericPromise::CreateAndReject(rv, __func__);
+ }
+ return callback->Ensure(__func__);
+}
+
+RefPtr<GenericPromise> KeyValueStorage::Put(const nsACString& aName,
+ const nsACString& aKey,
+ int32_t aValue) {
+ if (!mDatabase || !mDatabaseName.Equals(aName)) {
+ mDatabaseName = aName;
+ RefPtr<KeyValueStorage> self = this;
+ const nsCString key(aKey);
+ return Init()->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [self, key, aValue](bool) { return self->Put(key, aValue); },
+ [](nsresult rv) {
+ return GenericPromise::CreateAndReject(rv, __func__);
+ });
+ }
+ return Put(aKey, aValue);
+}
+
+class GetValueCallback final : public nsIKeyValueVariantCallback {
+ public:
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD Resolve(nsIVariant* aResult) override {
+ int32_t value = 0;
+ Unused << aResult->GetAsInt32(&value);
+ mResultPromise.Resolve(value, __func__);
+ return NS_OK;
+ }
+ NS_IMETHOD Reject(const nsACString&) override {
+ mResultPromise.Reject(NS_ERROR_FAILURE, __func__);
+ return NS_OK;
+ }
+ RefPtr<KeyValueStorage::GetPromise> Ensure() {
+ return mResultPromise.Ensure(__func__);
+ }
+
+ protected:
+ ~GetValueCallback() = default;
+
+ private:
+ MozPromiseHolder<KeyValueStorage::GetPromise> mResultPromise;
+};
+NS_IMPL_ISUPPORTS(GetValueCallback, nsIKeyValueVariantCallback);
+
+RefPtr<KeyValueStorage::GetPromise> KeyValueStorage::Get(
+ const nsACString& aKey) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mDatabase);
+ MOZ_ASSERT(!mDatabaseName.IsEmpty());
+
+ RefPtr<nsVariant> defaultValue = new nsVariant;
+ nsresult rv = defaultValue->SetAsInt32(-1);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return KeyValueStorage::GetPromise::CreateAndReject(rv, __func__);
+ }
+
+ auto callback = MakeRefPtr<GetValueCallback>();
+ rv = mDatabase->Get(callback, aKey, defaultValue);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return KeyValueStorage::GetPromise::CreateAndReject(rv, __func__);
+ }
+ return callback->Ensure();
+}
+
+RefPtr<KeyValueStorage::GetPromise> KeyValueStorage::Get(
+ const nsACString& aName, const nsACString& aKey) {
+ if (!mDatabase || !mDatabaseName.Equals(aName)) {
+ mDatabaseName = aName;
+ RefPtr<KeyValueStorage> self = this;
+ const nsCString key(aKey);
+ return Init()->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [self, key](bool) { return self->Get(key); },
+ [](nsresult rv) {
+ return KeyValueStorage::GetPromise::CreateAndReject(rv, __func__);
+ });
+ }
+ return Get(aKey);
+}
+
+RefPtr<GenericPromise> KeyValueStorage::Clear() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mDatabase);
+ MOZ_ASSERT(!mDatabaseName.IsEmpty());
+
+ auto callback = MakeRefPtr<VoidCallback>(this);
+ nsresult rv = mDatabase->Clear(callback);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return GenericPromise::CreateAndReject(rv, __func__);
+ }
+ return callback->Ensure(__func__);
+}
+
+RefPtr<GenericPromise> KeyValueStorage::Clear(const nsACString& aName) {
+ if (!mDatabase || !mDatabaseName.Equals(aName)) {
+ mDatabaseName = aName;
+ RefPtr<KeyValueStorage> self = this;
+ return Init()->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [self](bool) { return self->Clear(); },
+ [](nsresult rv) {
+ return GenericPromise::CreateAndReject(rv, __func__);
+ });
+ }
+ return Clear();
+}
+
+} // namespace mozilla
diff --git a/dom/media/mediacapabilities/KeyValueStorage.h b/dom/media/mediacapabilities/KeyValueStorage.h
new file mode 100644
index 0000000000..60d72a6c6d
--- /dev/null
+++ b/dom/media/mediacapabilities/KeyValueStorage.h
@@ -0,0 +1,48 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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 MOZILLA_KEY_VALUE_STORAGE_H
+#define MOZILLA_KEY_VALUE_STORAGE_H
+
+#include "mozilla/MozPromise.h"
+#include "nsIKeyValue.h"
+
+namespace mozilla {
+
+/* A wrapper class around kv store service, which allows storing a pair of key
+ * value permanently. The class must be used from the parent process, where
+ * there is no sandbox because it requires access to the directory that the
+ * database is located. */
+class KeyValueStorage final {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(KeyValueStorage)
+
+ /* Store permanently the value in the Key. */
+ RefPtr<GenericPromise> Put(const nsACString& aName, const nsACString& aKey,
+ int32_t aValue);
+ /* Get the value stored in the aKey. If the aKey does not exist the promise is
+ * resolved with the value -1. */
+ typedef MozPromise<int32_t, nsresult, true> GetPromise;
+ RefPtr<GetPromise> Get(const nsACString& aName, const nsACString& aKey);
+
+ /* Clear all the key/value pairs from the aName database. */
+ RefPtr<GenericPromise> Clear(const nsACString& aName);
+
+ private:
+ /* Create, if doesn't exist, and initialize the database with a given name. */
+ RefPtr<GenericPromise> Init();
+ RefPtr<GenericPromise> Put(const nsACString& aKey, int32_t aValue);
+ RefPtr<GetPromise> Get(const nsACString& aKey);
+ RefPtr<GenericPromise> Clear();
+ ~KeyValueStorage() = default;
+
+ RefPtr<nsIKeyValueDatabase> mDatabase;
+ nsCString mDatabaseName;
+};
+
+} // namespace mozilla
+
+#endif // MOZILLA_KEY_VALUE_STORAGE_H
diff --git a/dom/media/mediacapabilities/MediaCapabilities.cpp b/dom/media/mediacapabilities/MediaCapabilities.cpp
new file mode 100644
index 0000000000..904e9f4f54
--- /dev/null
+++ b/dom/media/mediacapabilities/MediaCapabilities.cpp
@@ -0,0 +1,658 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "MediaCapabilities.h"
+
+#include <inttypes.h>
+
+#include <utility>
+
+#include "AllocationPolicy.h"
+#include "Benchmark.h"
+#include "DecoderBenchmark.h"
+#include "DecoderTraits.h"
+#include "MediaInfo.h"
+#include "MediaRecorder.h"
+#include "PDMFactory.h"
+#include "VPXDecoder.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/SchedulerGroup.h"
+#include "mozilla/StaticPrefs_media.h"
+#include "mozilla/TaskQueue.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/DOMMozPromiseRequestHolder.h"
+#include "mozilla/dom/MediaCapabilitiesBinding.h"
+#include "mozilla/dom/MediaSource.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/WorkerPrivate.h"
+#include "mozilla/dom/WorkerRef.h"
+#include "mozilla/layers/KnowsCompositor.h"
+#include "nsContentUtils.h"
+#include "WindowRenderer.h"
+
+static mozilla::LazyLogModule sMediaCapabilitiesLog("MediaCapabilities");
+
+#define LOG(msg, ...) \
+ DDMOZ_LOG(sMediaCapabilitiesLog, LogLevel::Debug, msg, ##__VA_ARGS__)
+
+namespace mozilla::dom {
+
+static nsCString VideoConfigurationToStr(const VideoConfiguration* aConfig) {
+ if (!aConfig) {
+ return nsCString();
+ }
+
+ nsCString hdrMetaType(
+ aConfig->mHdrMetadataType.WasPassed()
+ ? HdrMetadataTypeValues::GetString(aConfig->mHdrMetadataType.Value())
+ : "?");
+
+ nsCString colorGamut(
+ aConfig->mColorGamut.WasPassed()
+ ? ColorGamutValues::GetString(aConfig->mColorGamut.Value())
+ : "?");
+
+ nsCString transferFunction(aConfig->mTransferFunction.WasPassed()
+ ? TransferFunctionValues::GetString(
+ aConfig->mTransferFunction.Value())
+ : "?");
+
+ auto str = nsPrintfCString(
+ "[contentType:%s width:%d height:%d bitrate:%" PRIu64
+ " framerate:%lf hasAlphaChannel:%s hdrMetadataType:%s colorGamut:%s "
+ "transferFunction:%s scalabilityMode:%s]",
+ NS_ConvertUTF16toUTF8(aConfig->mContentType).get(), aConfig->mWidth,
+ aConfig->mHeight, aConfig->mBitrate, aConfig->mFramerate,
+ aConfig->mHasAlphaChannel.WasPassed()
+ ? aConfig->mHasAlphaChannel.Value() ? "true" : "false"
+ : "?",
+ hdrMetaType.get(), colorGamut.get(), transferFunction.get(),
+ aConfig->mScalabilityMode.WasPassed()
+ ? NS_ConvertUTF16toUTF8(aConfig->mScalabilityMode.Value()).get()
+ : "?");
+ return std::move(str);
+}
+
+static nsCString AudioConfigurationToStr(const AudioConfiguration* aConfig) {
+ if (!aConfig) {
+ return nsCString();
+ }
+ auto str = nsPrintfCString(
+ "[contentType:%s channels:%s bitrate:%" PRIu64 " samplerate:%d]",
+ NS_ConvertUTF16toUTF8(aConfig->mContentType).get(),
+ aConfig->mChannels.WasPassed()
+ ? NS_ConvertUTF16toUTF8(aConfig->mChannels.Value()).get()
+ : "?",
+ aConfig->mBitrate.WasPassed() ? aConfig->mBitrate.Value() : 0,
+ aConfig->mSamplerate.WasPassed() ? aConfig->mSamplerate.Value() : 0);
+ return std::move(str);
+}
+
+static nsCString MediaCapabilitiesInfoToStr(
+ const MediaCapabilitiesInfo* aInfo) {
+ if (!aInfo) {
+ return nsCString();
+ }
+ auto str = nsPrintfCString("[supported:%s smooth:%s powerEfficient:%s]",
+ aInfo->Supported() ? "true" : "false",
+ aInfo->Smooth() ? "true" : "false",
+ aInfo->PowerEfficient() ? "true" : "false");
+ return std::move(str);
+}
+
+static nsCString MediaDecodingConfigurationToStr(
+ const MediaDecodingConfiguration& aConfig) {
+ nsCString str;
+ str += "["_ns;
+ if (aConfig.mVideo.WasPassed()) {
+ str += "video:"_ns + VideoConfigurationToStr(&aConfig.mVideo.Value());
+ if (aConfig.mAudio.WasPassed()) {
+ str += " "_ns;
+ }
+ }
+ if (aConfig.mAudio.WasPassed()) {
+ str += "audio:"_ns + AudioConfigurationToStr(&aConfig.mAudio.Value());
+ }
+ str += "]"_ns;
+ return str;
+}
+
+MediaCapabilities::MediaCapabilities(nsIGlobalObject* aParent)
+ : mParent(aParent) {}
+
+already_AddRefed<Promise> MediaCapabilities::DecodingInfo(
+ const MediaDecodingConfiguration& aConfiguration, ErrorResult& aRv) {
+ RefPtr<Promise> promise = Promise::Create(mParent, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ // If configuration is not a valid MediaConfiguration, return a Promise
+ // rejected with a TypeError.
+ if (!aConfiguration.mVideo.WasPassed() &&
+ !aConfiguration.mAudio.WasPassed()) {
+ aRv.ThrowTypeError<MSG_MISSING_REQUIRED_DICTIONARY_MEMBER>(
+ "'audio' or 'video' member of argument of "
+ "MediaCapabilities.decodingInfo");
+ return nullptr;
+ }
+
+ LOG("Processing %s", MediaDecodingConfigurationToStr(aConfiguration).get());
+
+ bool supported = true;
+ Maybe<MediaContainerType> videoContainer;
+ Maybe<MediaContainerType> audioContainer;
+
+ // If configuration.video is present and is not a valid video configuration,
+ // return a Promise rejected with a TypeError.
+ if (aConfiguration.mVideo.WasPassed()) {
+ videoContainer = CheckVideoConfiguration(aConfiguration.mVideo.Value());
+ if (!videoContainer) {
+ aRv.ThrowTypeError<MSG_INVALID_MEDIA_VIDEO_CONFIGURATION>();
+ return nullptr;
+ }
+
+ // We have a video configuration and it is valid. Check if it is supported.
+ supported &=
+ aConfiguration.mType == MediaDecodingType::File
+ ? CheckTypeForFile(aConfiguration.mVideo.Value().mContentType)
+ : CheckTypeForMediaSource(
+ aConfiguration.mVideo.Value().mContentType);
+ }
+ if (aConfiguration.mAudio.WasPassed()) {
+ audioContainer = CheckAudioConfiguration(aConfiguration.mAudio.Value());
+ if (!audioContainer) {
+ aRv.ThrowTypeError<MSG_INVALID_MEDIA_AUDIO_CONFIGURATION>();
+ return nullptr;
+ }
+ // We have an audio configuration and it is valid. Check if it is supported.
+ supported &=
+ aConfiguration.mType == MediaDecodingType::File
+ ? CheckTypeForFile(aConfiguration.mAudio.Value().mContentType)
+ : CheckTypeForMediaSource(
+ aConfiguration.mAudio.Value().mContentType);
+ }
+
+ if (!supported) {
+ auto info = MakeUnique<MediaCapabilitiesInfo>(
+ false /* supported */, false /* smooth */, false /* power efficient */);
+ LOG("%s -> %s", MediaDecodingConfigurationToStr(aConfiguration).get(),
+ MediaCapabilitiesInfoToStr(info.get()).get());
+ promise->MaybeResolve(std::move(info));
+ return promise.forget();
+ }
+
+ nsTArray<UniquePtr<TrackInfo>> tracks;
+ if (aConfiguration.mVideo.WasPassed()) {
+ MOZ_ASSERT(videoContainer.isSome(), "configuration is valid and supported");
+ auto videoTracks = DecoderTraits::GetTracksInfo(*videoContainer);
+ // If the MIME type does not imply a codec, the string MUST
+ // also have one and only one parameter that is named codecs with a value
+ // describing a single media codec. Otherwise, it MUST contain no
+ // parameters.
+ if (videoTracks.Length() != 1) {
+ promise->MaybeRejectWithTypeError<MSG_NO_CODECS_PARAMETER>(
+ videoContainer->OriginalString());
+ return promise.forget();
+ }
+ MOZ_DIAGNOSTIC_ASSERT(videoTracks.ElementAt(0),
+ "must contain a valid trackinfo");
+ // If the type refers to an audio codec, reject now.
+ if (videoTracks[0]->GetType() != TrackInfo::kVideoTrack) {
+ promise
+ ->MaybeRejectWithTypeError<MSG_INVALID_MEDIA_VIDEO_CONFIGURATION>();
+ return promise.forget();
+ }
+ tracks.AppendElements(std::move(videoTracks));
+ }
+ if (aConfiguration.mAudio.WasPassed()) {
+ MOZ_ASSERT(audioContainer.isSome(), "configuration is valid and supported");
+ auto audioTracks = DecoderTraits::GetTracksInfo(*audioContainer);
+ // If the MIME type does not imply a codec, the string MUST
+ // also have one and only one parameter that is named codecs with a value
+ // describing a single media codec. Otherwise, it MUST contain no
+ // parameters.
+ if (audioTracks.Length() != 1) {
+ promise->MaybeRejectWithTypeError<MSG_NO_CODECS_PARAMETER>(
+ audioContainer->OriginalString());
+ return promise.forget();
+ }
+ MOZ_DIAGNOSTIC_ASSERT(audioTracks.ElementAt(0),
+ "must contain a valid trackinfo");
+ // If the type refers to a video codec, reject now.
+ if (audioTracks[0]->GetType() != TrackInfo::kAudioTrack) {
+ promise
+ ->MaybeRejectWithTypeError<MSG_INVALID_MEDIA_AUDIO_CONFIGURATION>();
+ return promise.forget();
+ }
+ tracks.AppendElements(std::move(audioTracks));
+ }
+
+ using CapabilitiesPromise = MozPromise<MediaCapabilitiesInfo, MediaResult,
+ /* IsExclusive = */ true>;
+ nsTArray<RefPtr<CapabilitiesPromise>> promises;
+
+ RefPtr<TaskQueue> taskQueue =
+ TaskQueue::Create(GetMediaThreadPool(MediaThreadType::PLATFORM_DECODER),
+ "MediaCapabilities::TaskQueue");
+ for (auto&& config : tracks) {
+ TrackInfo::TrackType type =
+ config->IsVideo() ? TrackInfo::kVideoTrack : TrackInfo::kAudioTrack;
+
+ MOZ_ASSERT(type == TrackInfo::kAudioTrack ||
+ videoContainer->ExtendedType().GetFramerate().isSome(),
+ "framerate is a required member of VideoConfiguration");
+
+ if (type == TrackInfo::kAudioTrack) {
+ // There's no need to create an audio decoder has we only want to know if
+ // such codec is supported. We do need to call the PDMFactory::Supports
+ // API outside the main thread to get accurate results.
+ promises.AppendElement(
+ InvokeAsync(taskQueue, __func__, [config = std::move(config)]() {
+ RefPtr<PDMFactory> pdm = new PDMFactory();
+ SupportDecoderParams params{*config};
+ if (pdm->Supports(params, nullptr /* decoder doctor */) ==
+ media::DecodeSupport::Unsupported) {
+ return CapabilitiesPromise::CreateAndReject(NS_ERROR_FAILURE,
+ __func__);
+ }
+ return CapabilitiesPromise::CreateAndResolve(
+ MediaCapabilitiesInfo(true /* supported */, true /* smooth */,
+ true /* power efficient */),
+ __func__);
+ }));
+ continue;
+ }
+
+ // On Windows, the MediaDataDecoder expects to be created on a thread
+ // supporting MTA, which the main thread doesn't. So we use our task queue
+ // to create such decoder and perform initialization.
+
+ RefPtr<layers::KnowsCompositor> compositor = GetCompositor();
+ float frameRate =
+ static_cast<float>(videoContainer->ExtendedType().GetFramerate().ref());
+ const bool shouldResistFingerprinting =
+ mParent->ShouldResistFingerprinting(RFPTarget::Unknown);
+
+ // clang-format off
+ promises.AppendElement(InvokeAsync(
+ taskQueue, __func__,
+ [taskQueue, frameRate, shouldResistFingerprinting, compositor,
+ config = std::move(config)]() mutable -> RefPtr<CapabilitiesPromise> {
+ // MediaDataDecoder keeps a reference to the config object, so we must
+ // keep it alive until the decoder has been shutdown.
+ static Atomic<uint32_t> sTrackingIdCounter(0);
+ TrackingId trackingId(TrackingId::Source::MediaCapabilities,
+ sTrackingIdCounter++,
+ TrackingId::TrackAcrossProcesses::Yes);
+ CreateDecoderParams params{
+ *config, compositor,
+ CreateDecoderParams::VideoFrameRate(frameRate),
+ TrackInfo::kVideoTrack, Some(std::move(trackingId))};
+ // We want to ensure that all decoder's queries are occurring only
+ // once at a time as it can quickly exhaust the system resources
+ // otherwise.
+ static RefPtr<AllocPolicy> sVideoAllocPolicy = [&taskQueue]() {
+ SchedulerGroup::Dispatch(
+ TaskCategory::Other,
+ NS_NewRunnableFunction(
+ "MediaCapabilities::AllocPolicy:Video", []() {
+ ClearOnShutdown(&sVideoAllocPolicy,
+ ShutdownPhase::XPCOMShutdownThreads);
+ }));
+ return new SingleAllocPolicy(TrackInfo::TrackType::kVideoTrack,
+ taskQueue);
+ }();
+ return AllocationWrapper::CreateDecoder(params, sVideoAllocPolicy)
+ ->Then(
+ taskQueue, __func__,
+ [taskQueue, frameRate, shouldResistFingerprinting,
+ config = std::move(config)](
+ AllocationWrapper::AllocateDecoderPromise::
+ ResolveOrRejectValue&& aValue) mutable {
+ if (aValue.IsReject()) {
+ return CapabilitiesPromise::CreateAndReject(
+ std::move(aValue.RejectValue()), __func__);
+ }
+ RefPtr<MediaDataDecoder> decoder =
+ std::move(aValue.ResolveValue());
+ // We now query the decoder to determine if it's power
+ // efficient.
+ RefPtr<CapabilitiesPromise> p = decoder->Init()->Then(
+ taskQueue, __func__,
+ [taskQueue, decoder, frameRate,
+ shouldResistFingerprinting,
+ config = std::move(config)](
+ MediaDataDecoder::InitPromise::
+ ResolveOrRejectValue&& aValue) mutable {
+ RefPtr<CapabilitiesPromise> p;
+ if (aValue.IsReject()) {
+ p = CapabilitiesPromise::CreateAndReject(
+ std::move(aValue.RejectValue()), __func__);
+ } else if (shouldResistFingerprinting) {
+ p = CapabilitiesPromise::CreateAndResolve(
+ MediaCapabilitiesInfo(true /* supported */,
+ true /* smooth */, false /* power efficient */),
+ __func__);
+ } else {
+ MOZ_ASSERT(config->IsVideo());
+ if (StaticPrefs::media_mediacapabilities_from_database()) {
+ nsAutoCString reason;
+ bool powerEfficient =
+ decoder->IsHardwareAccelerated(reason);
+
+ int32_t videoFrameRate = std::clamp<int32_t>(frameRate, 1, INT32_MAX);
+
+ DecoderBenchmarkInfo benchmarkInfo{
+ config->mMimeType,
+ config->GetAsVideoInfo()->mImage.width,
+ config->GetAsVideoInfo()->mImage.height,
+ videoFrameRate, 8};
+
+ p = DecoderBenchmark::Get(benchmarkInfo)->Then(
+ GetMainThreadSerialEventTarget(),
+ __func__,
+ [powerEfficient](int32_t score) {
+ // score < 0 means no entry found.
+ bool smooth = score < 0 || score >
+ StaticPrefs::
+ media_mediacapabilities_drop_threshold();
+ return CapabilitiesPromise::
+ CreateAndResolve(
+ MediaCapabilitiesInfo(
+ true, smooth,
+ powerEfficient),
+ __func__);
+ },
+ [](nsresult rv) {
+ return CapabilitiesPromise::
+ CreateAndReject(rv, __func__);
+ });
+ } else if (config->GetAsVideoInfo()->mImage.height < 480) {
+ // Assume that we can do stuff at 480p or less in
+ // a power efficient manner and smoothly. If
+ // greater than 480p we assume that if the video
+ // decoding is hardware accelerated it will be
+ // smooth and power efficient, otherwise we use
+ // the benchmark to estimate
+ p = CapabilitiesPromise::CreateAndResolve(
+ MediaCapabilitiesInfo(true, true, true),
+ __func__);
+ } else {
+ nsAutoCString reason;
+ bool smooth = true;
+ bool powerEfficient =
+ decoder->IsHardwareAccelerated(reason);
+ if (!powerEfficient &&
+ VPXDecoder::IsVP9(config->mMimeType)) {
+ smooth = VP9Benchmark::IsVP9DecodeFast(
+ true /* default */);
+ uint32_t fps =
+ VP9Benchmark::MediaBenchmarkVp9Fps();
+ if (!smooth && fps > 0) {
+ // The VP9 estimizer decode a 1280x720 video.
+ // Let's adjust the result for the resolution
+ // and frame rate of what we actually want. If
+ // the result is twice that we need we assume
+ // it will be smooth.
+ const auto& videoConfig =
+ *config->GetAsVideoInfo();
+ double needed = ((1280.0 * 720.0) /
+ (videoConfig.mImage.width *
+ videoConfig.mImage.height) *
+ fps) /
+ frameRate;
+ smooth = needed > 2;
+ }
+ }
+
+ p = CapabilitiesPromise::CreateAndResolve(
+ MediaCapabilitiesInfo(true /* supported */,
+ smooth, powerEfficient),
+ __func__);
+ }
+ }
+ MOZ_ASSERT(p.get(), "the promise has been created");
+ // Let's keep alive the decoder and the config object
+ // until the decoder has shutdown.
+ decoder->Shutdown()->Then(
+ taskQueue, __func__,
+ [taskQueue, decoder, config = std::move(config)](
+ const ShutdownPromise::ResolveOrRejectValue&
+ aValue) {});
+ return p;
+ });
+ return p;
+ });
+ }));
+ // clang-format on
+ }
+
+ auto holder = MakeRefPtr<
+ DOMMozPromiseRequestHolder<CapabilitiesPromise::AllPromiseType>>(mParent);
+ RefPtr<nsISerialEventTarget> targetThread;
+ RefPtr<StrongWorkerRef> workerRef;
+
+ if (NS_IsMainThread()) {
+ targetThread = mParent->AbstractMainThreadFor(TaskCategory::Other);
+ } else {
+ WorkerPrivate* wp = GetCurrentThreadWorkerPrivate();
+ MOZ_ASSERT(wp, "Must be called from a worker thread");
+ targetThread = wp->HybridEventTarget();
+ workerRef = StrongWorkerRef::Create(
+ wp, "MediaCapabilities", [holder, targetThread]() {
+ MOZ_ASSERT(targetThread->IsOnCurrentThread());
+ holder->DisconnectIfExists();
+ });
+ if (NS_WARN_IF(!workerRef)) {
+ // The worker is shutting down.
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+ }
+
+ MOZ_ASSERT(targetThread);
+
+ // this is only captured for use with the LOG macro.
+ RefPtr<MediaCapabilities> self = this;
+
+ CapabilitiesPromise::All(targetThread, promises)
+ ->Then(targetThread, __func__,
+ [promise, tracks = std::move(tracks), workerRef, holder,
+ aConfiguration, self,
+ this](CapabilitiesPromise::AllPromiseType::ResolveOrRejectValue&&
+ aValue) {
+ holder->Complete();
+ if (aValue.IsReject()) {
+ auto info = MakeUnique<MediaCapabilitiesInfo>(
+ false /* supported */, false /* smooth */,
+ false /* power efficient */);
+ LOG("%s -> %s",
+ MediaDecodingConfigurationToStr(aConfiguration).get(),
+ MediaCapabilitiesInfoToStr(info.get()).get());
+ promise->MaybeResolve(std::move(info));
+ return;
+ }
+ bool powerEfficient = true;
+ bool smooth = true;
+ for (auto&& capability : aValue.ResolveValue()) {
+ smooth &= capability.Smooth();
+ powerEfficient &= capability.PowerEfficient();
+ }
+ auto info = MakeUnique<MediaCapabilitiesInfo>(
+ true /* supported */, smooth, powerEfficient);
+ LOG("%s -> %s",
+ MediaDecodingConfigurationToStr(aConfiguration).get(),
+ MediaCapabilitiesInfoToStr(info.get()).get());
+ promise->MaybeResolve(std::move(info));
+ })
+ ->Track(*holder);
+
+ return promise.forget();
+}
+
+already_AddRefed<Promise> MediaCapabilities::EncodingInfo(
+ const MediaEncodingConfiguration& aConfiguration, ErrorResult& aRv) {
+ RefPtr<Promise> promise = Promise::Create(mParent, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ // If configuration is not a valid MediaConfiguration, return a Promise
+ // rejected with a TypeError.
+ if (!aConfiguration.mVideo.WasPassed() &&
+ !aConfiguration.mAudio.WasPassed()) {
+ aRv.ThrowTypeError<MSG_MISSING_REQUIRED_DICTIONARY_MEMBER>(
+ "'audio' or 'video' member of argument of "
+ "MediaCapabilities.encodingInfo");
+ return nullptr;
+ }
+
+ bool supported = true;
+
+ // If configuration.video is present and is not a valid video configuration,
+ // return a Promise rejected with a TypeError.
+ if (aConfiguration.mVideo.WasPassed()) {
+ if (!CheckVideoConfiguration(aConfiguration.mVideo.Value())) {
+ aRv.ThrowTypeError<MSG_INVALID_MEDIA_VIDEO_CONFIGURATION>();
+ return nullptr;
+ }
+ // We have a video configuration and it is valid. Check if it is supported.
+ supported &=
+ CheckTypeForEncoder(aConfiguration.mVideo.Value().mContentType);
+ }
+ if (aConfiguration.mAudio.WasPassed()) {
+ if (!CheckAudioConfiguration(aConfiguration.mAudio.Value())) {
+ aRv.ThrowTypeError<MSG_INVALID_MEDIA_AUDIO_CONFIGURATION>();
+ return nullptr;
+ }
+ // We have an audio configuration and it is valid. Check if it is supported.
+ supported &=
+ CheckTypeForEncoder(aConfiguration.mAudio.Value().mContentType);
+ }
+
+ auto info = MakeUnique<MediaCapabilitiesInfo>(supported, supported, false);
+ promise->MaybeResolve(std::move(info));
+
+ return promise.forget();
+}
+
+Maybe<MediaContainerType> MediaCapabilities::CheckVideoConfiguration(
+ const VideoConfiguration& aConfig) const {
+ Maybe<MediaExtendedMIMEType> container = MakeMediaExtendedMIMEType(aConfig);
+ if (!container) {
+ return Nothing();
+ }
+ // A valid video MIME type is a string that is a valid media MIME type and for
+ // which the type per [RFC7231] is either video or application.
+ if (!container->Type().HasVideoMajorType() &&
+ !container->Type().HasApplicationMajorType()) {
+ return Nothing();
+ }
+
+ // If the MIME type does not imply a codec, the string MUST also have one and
+ // only one parameter that is named codecs with a value describing a single
+ // media codec. Otherwise, it MUST contain no parameters.
+ // TODO (nsIMOMEHeaderParam doesn't provide backend to count number of
+ // parameters)
+
+ return Some(MediaContainerType(std::move(*container)));
+}
+
+Maybe<MediaContainerType> MediaCapabilities::CheckAudioConfiguration(
+ const AudioConfiguration& aConfig) const {
+ Maybe<MediaExtendedMIMEType> container = MakeMediaExtendedMIMEType(aConfig);
+ if (!container) {
+ return Nothing();
+ }
+ // A valid audio MIME type is a string that is valid media MIME type and for
+ // which the type per [RFC7231] is either audio or application.
+ if (!container->Type().HasAudioMajorType() &&
+ !container->Type().HasApplicationMajorType()) {
+ return Nothing();
+ }
+
+ // If the MIME type does not imply a codec, the string MUST also have one and
+ // only one parameter that is named codecs with a value describing a single
+ // media codec. Otherwise, it MUST contain no parameters.
+ // TODO (nsIMOMEHeaderParam doesn't provide backend to count number of
+ // parameters)
+
+ return Some(MediaContainerType(std::move(*container)));
+}
+
+bool MediaCapabilities::CheckTypeForMediaSource(const nsAString& aType) {
+ IgnoredErrorResult rv;
+ MediaSource::IsTypeSupported(aType, nullptr /* DecoderDoctorDiagnostics */,
+ rv);
+
+ return !rv.Failed();
+}
+
+bool MediaCapabilities::CheckTypeForFile(const nsAString& aType) {
+ Maybe<MediaContainerType> containerType = MakeMediaContainerType(aType);
+ if (!containerType) {
+ return false;
+ }
+
+ return DecoderTraits::CanHandleContainerType(
+ *containerType, nullptr /* DecoderDoctorDiagnostics */) !=
+ CANPLAY_NO;
+}
+
+bool MediaCapabilities::CheckTypeForEncoder(const nsAString& aType) {
+ return MediaRecorder::IsTypeSupported(aType);
+}
+
+already_AddRefed<layers::KnowsCompositor> MediaCapabilities::GetCompositor() {
+ nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(GetParentObject());
+ if (NS_WARN_IF(!window)) {
+ return nullptr;
+ }
+
+ nsCOMPtr<Document> doc = window->GetExtantDoc();
+ if (NS_WARN_IF(!doc)) {
+ return nullptr;
+ }
+ WindowRenderer* renderer = nsContentUtils::WindowRendererForDocument(doc);
+ if (NS_WARN_IF(!renderer)) {
+ return nullptr;
+ }
+ RefPtr<layers::KnowsCompositor> knows = renderer->AsKnowsCompositor();
+ if (NS_WARN_IF(!knows)) {
+ return nullptr;
+ }
+ return knows->GetForMedia().forget();
+}
+
+bool MediaCapabilities::Enabled(JSContext* aCx, JSObject* aGlobal) {
+ return StaticPrefs::media_media_capabilities_enabled();
+}
+
+JSObject* MediaCapabilities::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return MediaCapabilities_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaCapabilities)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(MediaCapabilities)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(MediaCapabilities)
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(MediaCapabilities, mParent)
+
+// MediaCapabilitiesInfo
+bool MediaCapabilitiesInfo::WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto,
+ JS::MutableHandle<JSObject*> aReflector) {
+ return MediaCapabilitiesInfo_Binding::Wrap(aCx, this, aGivenProto,
+ aReflector);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/media/mediacapabilities/MediaCapabilities.h b/dom/media/mediacapabilities/MediaCapabilities.h
new file mode 100644
index 0000000000..b31e12e8ab
--- /dev/null
+++ b/dom/media/mediacapabilities/MediaCapabilities.h
@@ -0,0 +1,104 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 mozilla_dom_MediaCapabilities_h_
+#define mozilla_dom_MediaCapabilities_h_
+
+#include "DDLoggedTypeTraits.h"
+#include "js/TypeDecls.h"
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/dom/NonRefcountedDOMObject.h"
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsISupports.h"
+#include "nsStringFwd.h"
+#include "nsWrapperCache.h"
+
+class nsIGlobalObject;
+
+namespace mozilla {
+class ErrorResult;
+class MediaContainerType;
+
+namespace layers {
+class KnowsCompositor;
+}
+namespace dom {
+class MediaCapabilities;
+} // namespace dom
+DDLoggedTypeName(dom::MediaCapabilities);
+
+namespace dom {
+
+struct MediaDecodingConfiguration;
+struct MediaEncodingConfiguration;
+struct AudioConfiguration;
+struct VideoConfiguration;
+class Promise;
+
+class MediaCapabilities final : public nsISupports, public nsWrapperCache {
+ public:
+ // Ref counting and cycle collection
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(MediaCapabilities)
+
+ // WebIDL Methods
+ already_AddRefed<Promise> DecodingInfo(
+ const MediaDecodingConfiguration& aConfiguration, ErrorResult& aRv);
+ already_AddRefed<Promise> EncodingInfo(
+ const MediaEncodingConfiguration& aConfiguration, ErrorResult& aRv);
+ // End WebIDL Methods
+
+ explicit MediaCapabilities(nsIGlobalObject* aParent);
+
+ nsIGlobalObject* GetParentObject() const { return mParent; }
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ static bool Enabled(JSContext* aCx, JSObject* aGlobal);
+
+ private:
+ virtual ~MediaCapabilities() = default;
+ Maybe<MediaContainerType> CheckVideoConfiguration(
+ const VideoConfiguration& aConfig) const;
+ Maybe<MediaContainerType> CheckAudioConfiguration(
+ const AudioConfiguration& aConfig) const;
+ bool CheckTypeForMediaSource(const nsAString& aType);
+ bool CheckTypeForFile(const nsAString& aType);
+ bool CheckTypeForEncoder(const nsAString& aType);
+ already_AddRefed<layers::KnowsCompositor> GetCompositor();
+ nsCOMPtr<nsIGlobalObject> mParent;
+};
+
+class MediaCapabilitiesInfo final : public NonRefcountedDOMObject {
+ public:
+ // WebIDL methods
+ bool Supported() const { return mSupported; }
+ bool Smooth() const { return mSmooth; }
+ bool PowerEfficient() const { return mPowerEfficient; }
+ // End WebIDL methods
+
+ MediaCapabilitiesInfo(bool aSupported, bool aSmooth, bool aPowerEfficient)
+ : mSupported(aSupported),
+ mSmooth(aSmooth),
+ mPowerEfficient(aPowerEfficient) {}
+
+ bool WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto,
+ JS::MutableHandle<JSObject*> aReflector);
+
+ private:
+ bool mSupported;
+ bool mSmooth;
+ bool mPowerEfficient;
+};
+
+} // namespace dom
+
+} // namespace mozilla
+
+#endif /* mozilla_dom_MediaCapabilities_h_ */
diff --git a/dom/media/mediacapabilities/PBenchmarkStorage.ipdl b/dom/media/mediacapabilities/PBenchmarkStorage.ipdl
new file mode 100644
index 0000000000..220d83f978
--- /dev/null
+++ b/dom/media/mediacapabilities/PBenchmarkStorage.ipdl
@@ -0,0 +1,23 @@
+/* -*- Mode: C++; tab-width: 2; 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 PContent;
+
+namespace mozilla {
+
+[ManualDealloc]
+async protocol PBenchmarkStorage
+{
+ manager PContent;
+
+parent:
+ async Put(nsCString aDbName, nsCString aKey, int32_t aValue);
+ async Get(nsCString aDbName, nsCString aKey) returns(int32_t aValue);
+ async CheckVersion(nsCString aDbName, int32_t aVersion);
+ async __delete__();
+
+};
+
+} // namespace mozilla
diff --git a/dom/media/mediacapabilities/moz.build b/dom/media/mediacapabilities/moz.build
new file mode 100644
index 0000000000..bafd3de59c
--- /dev/null
+++ b/dom/media/mediacapabilities/moz.build
@@ -0,0 +1,32 @@
+# 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/.
+
+EXPORTS.mozilla.dom += [
+ "MediaCapabilities.h",
+]
+
+EXPORTS.mozilla += [
+ "BenchmarkStorageChild.h",
+ "BenchmarkStorageParent.h",
+ "KeyValueStorage.h",
+]
+
+EXPORTS += [
+ "DecoderBenchmark.h",
+]
+
+UNIFIED_SOURCES += [
+ "BenchmarkStorageChild.cpp",
+ "BenchmarkStorageParent.cpp",
+ "DecoderBenchmark.cpp",
+ "KeyValueStorage.cpp",
+ "MediaCapabilities.cpp",
+]
+
+IPDL_SOURCES += ["PBenchmarkStorage.ipdl"]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"