1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "DecoderBenchmark.h"
8 #include "mozilla/BenchmarkStorageChild.h"
9 #include "mozilla/media/MediaUtils.h"
10 #include "mozilla/StaticPrefs_media.h"
11 
12 namespace mozilla {
13 
StoreScore(const nsACString & aDecoderName,const nsACString & aKey,RefPtr<FrameStatistics> aStats)14 void DecoderBenchmark::StoreScore(const nsACString& aDecoderName,
15                                   const nsACString& aKey,
16                                   RefPtr<FrameStatistics> aStats) {
17   FrameStatisticsData statsData = aStats->GetFrameStatisticsData();
18   uint64_t totalFrames = FrameStatistics::GetTotalFrames(statsData);
19   uint64_t droppedFrames = FrameStatistics::GetDroppedFrames(statsData);
20 
21   MOZ_ASSERT(droppedFrames <= totalFrames);
22   MOZ_ASSERT(totalFrames >= mLastTotalFrames);
23   MOZ_ASSERT(droppedFrames >= mLastDroppedFrames);
24 
25   uint64_t diffTotalFrames = totalFrames - mLastTotalFrames;
26   uint64_t diffDroppedFrames = droppedFrames - mLastDroppedFrames;
27 
28   /* Update now in case the method returns at the if check bellow. */
29   mLastTotalFrames = totalFrames;
30   mLastDroppedFrames = droppedFrames;
31 
32   /* A minimum number of 10 frames is required to store the score. */
33   if (diffTotalFrames < 10) {
34     return;
35   }
36 
37   int32_t percentage =
38       100 - 100 * float(diffDroppedFrames) / float(diffTotalFrames);
39 
40   MOZ_ASSERT(percentage >= 0);
41 
42   Put(aDecoderName, aKey, percentage);
43 }
44 
Put(const nsACString & aDecoderName,const nsACString & aKey,int32_t aValue)45 void DecoderBenchmark::Put(const nsACString& aDecoderName,
46                            const nsACString& aKey, int32_t aValue) {
47   MOZ_ASSERT(NS_IsMainThread());
48   const nsCString name(aDecoderName);
49   const nsCString key(aKey);
50   BenchmarkStorageChild::Instance()->SendPut(name, key, aValue);
51 }
52 
GetScore(const nsACString & aDecoderName,const nsACString & aKey)53 RefPtr<BenchmarkScorePromise> DecoderBenchmark::GetScore(
54     const nsACString& aDecoderName, const nsACString& aKey) {
55   if (NS_IsMainThread()) {
56     return Get(aDecoderName, aKey);
57   }
58 
59   RefPtr<DecoderBenchmark> self = this;
60   const nsCString decoderName(aDecoderName);
61   const nsCString key(aKey);
62   return InvokeAsync(
63       GetMainThreadSerialEventTarget(), __func__,
64       [self, decoderName, key] { return self->Get(decoderName, key); });
65 }
66 
Get(const nsACString & aDecoderName,const nsACString & aKey)67 RefPtr<BenchmarkScorePromise> DecoderBenchmark::Get(
68     const nsACString& aDecoderName, const nsACString& aKey) {
69   MOZ_ASSERT(NS_IsMainThread());
70 
71   const nsCString name(aDecoderName);
72   const nsCString key(aKey);
73   return BenchmarkStorageChild::Instance()->SendGet(name, key)->Then(
74       GetCurrentSerialEventTarget(), __func__,
75       [](int32_t aResult) {
76         return BenchmarkScorePromise::CreateAndResolve(aResult, __func__);
77       },
78       [](ipc::ResponseRejectReason&&) {
79         return BenchmarkScorePromise::CreateAndReject(NS_ERROR_FAILURE,
80                                                       __func__);
81       });
82 }
83 
84 /* The key string consists of the video properties resolution, framerate,
85  * and bitdepth. There are various levels for each of them. The key is
86  * formated by the closest level, for example, a video with width=1920,
87  * height=1080, frameRate=24, and bitdepth=8bit will have the key:
88  * "ResolutionLevel5-FrameRateLevel1-8bit". */
89 
90 #define NELEMS(x) (sizeof(x) / sizeof((x)[0]))
91 
92 /* Keep them sorted */
93 const uint32_t PixelLevels[] = {
94     /* 256x144   =*/36864,
95     /* 426x240   =*/102240,
96     /* 640x360   =*/230400,
97     /* 854x480   =*/409920,
98     /* 1280x720  =*/921600,
99     /* 1920x1080 =*/2073600,
100     /* 2560x1440 =*/3686400,
101     /* 3840x2160 =*/8294400,
102 };
103 const size_t PixelLevelsSize = NELEMS(PixelLevels);
104 
105 const uint32_t FrameRateLevels[] = {
106     15, 24, 30, 50, 60,
107 };
108 const size_t FrameRateLevelsSize = NELEMS(FrameRateLevels);
109 
110 /* static */
FindLevel(const uint32_t aLevels[],const size_t length,uint32_t aValue)111 nsCString KeyUtil::FindLevel(const uint32_t aLevels[], const size_t length,
112                              uint32_t aValue) {
113   MOZ_ASSERT(aValue);
114   if (aValue <= aLevels[0]) {
115     return "Level0"_ns;
116   }
117   nsAutoCString level("Level");
118   size_t lastIndex = length - 1;
119   if (aValue >= aLevels[lastIndex]) {
120     level.AppendInt(static_cast<uint32_t>(lastIndex));
121     return std::move(level);
122   }
123   for (size_t i = 0; i < lastIndex; ++i) {
124     if (aValue >= aLevels[i + 1]) {
125       continue;
126     }
127     if (aValue - aLevels[i] < aLevels[i + 1] - aValue) {
128       level.AppendInt(static_cast<uint32_t>(i));
129       return std::move(level);
130     }
131     level.AppendInt(static_cast<uint32_t>(i + 1));
132     return std::move(level);
133   }
134   MOZ_CRASH("Array is not sorted");
135   return ""_ns;
136 }
137 
138 /* static */
BitDepthToStr(uint8_t aBitDepth)139 nsCString KeyUtil::BitDepthToStr(uint8_t aBitDepth) {
140   switch (aBitDepth) {
141     case 8:  // ColorDepth::COLOR_8
142       return "-8bit"_ns;
143     case 10:  // ColorDepth::COLOR_10
144     case 12:  // ColorDepth::COLOR_12
145     case 16:  // ColorDepth::COLOR_16
146       return "-non8bit"_ns;
147   }
148   MOZ_ASSERT_UNREACHABLE("invalid color depth value");
149   return ""_ns;
150 }
151 
152 /* static */
CreateKey(const DecoderBenchmarkInfo & aBenchInfo)153 nsCString KeyUtil::CreateKey(const DecoderBenchmarkInfo& aBenchInfo) {
154   nsAutoCString key("Resolution");
155   key.Append(FindLevel(PixelLevels, PixelLevelsSize,
156                        aBenchInfo.mWidth * aBenchInfo.mHeight));
157 
158   key.Append("-FrameRate");
159   key.Append(
160       FindLevel(FrameRateLevels, FrameRateLevelsSize, aBenchInfo.mFrameRate));
161 
162   key.Append(BitDepthToStr(aBenchInfo.mBitDepth));
163 
164   return std::move(key);
165 }
166 
Store(const DecoderBenchmarkInfo & aBenchInfo,RefPtr<FrameStatistics> aStats)167 void DecoderBenchmark::Store(const DecoderBenchmarkInfo& aBenchInfo,
168                              RefPtr<FrameStatistics> aStats) {
169   if (!XRE_IsContentProcess()) {
170     NS_WARNING(
171         "Storing a benchmark is only allowed only from the content process.");
172     return;
173   }
174   StoreScore(aBenchInfo.mContentType, KeyUtil::CreateKey(aBenchInfo), aStats);
175 }
176 
177 /* static */
Get(const DecoderBenchmarkInfo & aBenchInfo)178 RefPtr<BenchmarkScorePromise> DecoderBenchmark::Get(
179     const DecoderBenchmarkInfo& aBenchInfo) {
180   if (!XRE_IsContentProcess()) {
181     NS_WARNING(
182         "Getting a benchmark is only allowed only from the content process.");
183     return BenchmarkScorePromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
184   }
185   // There is no need for any of the data members to query the database, thus
186   // it can be a static method.
187   auto bench = MakeRefPtr<DecoderBenchmark>();
188   return bench->GetScore(aBenchInfo.mContentType,
189                          KeyUtil::CreateKey(aBenchInfo));
190 }
191 
DecoderVersionTable()192 static nsTHashMap<nsCStringHashKey, int32_t> DecoderVersionTable() {
193   nsTHashMap<nsCStringHashKey, int32_t> decoderVersionTable;
194 
195   /*
196    * For the decoders listed here, the benchmark version number will be checked.
197    * If the version number does not exist in the database or is different than
198    * the version number listed here, all the benchmark entries for this decoder
199    * will be erased. An example of assigning the version number `1` for AV1
200    * decoder is:
201    *
202    * decoderVersionTable.InsertOrUpdate("video/av1"_ns, 1);
203    *
204    * For the decoders not listed here the `CheckVersion` method exits early, to
205    * avoid sending unecessary IPC messages.
206    */
207 
208   return decoderVersionTable;
209 }
210 
211 /* static */
CheckVersion(const nsACString & aDecoderName)212 void DecoderBenchmark::CheckVersion(const nsACString& aDecoderName) {
213   if (!XRE_IsContentProcess()) {
214     NS_WARNING(
215         "Checking version is only allowed only from the content process.");
216     return;
217   }
218 
219   if (!StaticPrefs::media_mediacapabilities_from_database()) {
220     return;
221   }
222 
223   nsCString name(aDecoderName);
224   int32_t version;
225   if (!DecoderVersionTable().Get(name, &version)) {
226     // A version is not set for that decoder ignore.
227     return;
228   }
229 
230   if (NS_IsMainThread()) {
231     BenchmarkStorageChild::Instance()->SendCheckVersion(name, version);
232     return;
233   }
234 
235   DebugOnly<nsresult> rv =
236       GetMainThreadEventTarget()->Dispatch(NS_NewRunnableFunction(
237           "DecoderBenchmark::CheckVersion", [name, version]() {
238             BenchmarkStorageChild::Instance()->SendCheckVersion(name, version);
239           }));
240   MOZ_ASSERT(NS_SUCCEEDED(rv));
241 }
242 
243 }  // namespace mozilla
244