/* -*- 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 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 DecoderBenchmark::GetScore( const nsACString& aDecoderName, const nsACString& aKey) { if (NS_IsMainThread()) { return Get(aDecoderName, aKey); } RefPtr self = this; const nsCString decoderName(aDecoderName); const nsCString key(aKey); return InvokeAsync( GetMainThreadSerialEventTarget(), __func__, [self, decoderName, key] { return self->Get(decoderName, key); }); } RefPtr 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(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(i)); return std::move(level); } level.AppendInt(static_cast(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 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 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(); return bench->GetScore(aBenchInfo.mContentType, KeyUtil::CreateKey(aBenchInfo)); } static nsTHashMap DecoderVersionTable() { nsTHashMap 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 rv = GetMainThreadEventTarget()->Dispatch(NS_NewRunnableFunction( "DecoderBenchmark::CheckVersion", [name, version]() { BenchmarkStorageChild::Instance()->SendCheckVersion(name, version); })); MOZ_ASSERT(NS_SUCCEEDED(rv)); } } // namespace mozilla