[M120 Migration][MM][CAPI] Fix the logic for media using capi player.
[platform/framework/web/chromium-efl.git] / media / mojo / services / webrtc_video_perf_history.cc
1 // Copyright 2022 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "media/mojo/services/webrtc_video_perf_history.h"
6
7 #include <math.h>
8
9 #include "base/format_macros.h"
10 #include "base/functional/bind.h"
11 #include "base/functional/callback.h"
12 #include "base/logging.h"
13 #include "base/memory/ptr_util.h"
14 #include "base/metrics/field_trial_params.h"
15 #include "base/metrics/histogram_functions.h"
16 #include "base/metrics/histogram_macros.h"
17 #include "base/strings/stringprintf.h"
18 #include "base/task/bind_post_task.h"
19 #include "base/task/single_thread_task_runner.h"
20 #include "base/time/time.h"
21 #include "media/base/media_switches.h"
22 #include "media/base/video_codecs.h"
23 #include "media/capabilities/bucket_utility.h"
24 #include "media/mojo/mojom/media_types.mojom.h"
25
26 namespace media {
27
28 namespace {
29 constexpr float kSmoothnessThresholdDecodeDefault = 1.0f;
30 constexpr float kSmoothnessThresholdEncodeDefault = 1.0f;
31 constexpr float kSmoothDecisionRatioThresholdDefault = 0.5f;
32 // Field trial parameter names.
33 constexpr char kSmoothnessThresholdDecodeParamName[] =
34     "smoothness_threshold_decode";
35 constexpr char kSmoothnessThresholdEncodeParamName[] =
36     "smoothness_threshold_encode";
37 constexpr char kSmoothDecisionRatioThresholdParamName[] =
38     "smooth_decision_ratio_threshold";
39
40 // These values are persisted to logs. Entries should not be renumbered and
41 // numeric values should never be reused.
42 enum class SmoothVolatility {
43   kNotSmooth2NotSmooth = 0,
44   kSmooth2NotSmooth = 1,
45   kNotSmooth2Smooth = 2,
46   kSmooth2Smooth = 3,
47   kMaxValue = kSmooth2Smooth,
48 };
49
50 // These values are persisted to logs. Entries should not be renumbered and
51 // numeric values should never be reused.
52 enum class SmoothPrediction {
53   kSmoothByDefault = 0,
54   kSmoothFromData = 1,
55   kNotSmoothFromData = 2,
56   kImplicitlySmooth = 3,
57   kSmoothOverride = 4,
58   kImplicitlyNotSmooth = 5,
59   kMaxValue = kImplicitlyNotSmooth,
60 };
61
62 // These values are persisted to logs. Entries should not be renumbered and
63 // numeric values should never be reused.
64 enum class LimitedCodecProfile {
65   kOther = 0,
66   kH264 = 1,
67   kVP8 = 2,
68   kVP9Profile0 = 3,
69   kVP9Profile2 = 4,
70   kAv1 = 5,
71   kMaxValue = kAv1,
72 };
73
74 LimitedCodecProfile LimitedCodecProfileFromCodecProfile(
75     VideoCodecProfile codec_profile) {
76   if (codec_profile >= H264PROFILE_MIN && codec_profile <= H264PROFILE_MAX) {
77     return LimitedCodecProfile::kH264;
78   }
79   if (codec_profile >= VP8PROFILE_MIN && codec_profile <= VP8PROFILE_MAX) {
80     return LimitedCodecProfile::kVP8;
81   }
82   if (codec_profile == VP9PROFILE_PROFILE0) {
83     return LimitedCodecProfile::kVP9Profile0;
84   }
85   if (codec_profile == VP9PROFILE_PROFILE2) {
86     return LimitedCodecProfile::kVP9Profile2;
87   }
88   if (codec_profile >= AV1PROFILE_MIN && codec_profile <= AV1PROFILE_MAX) {
89     return LimitedCodecProfile::kAv1;
90   }
91   return LimitedCodecProfile::kOther;
92 }
93
94 constexpr SmoothVolatility SmoothVolatilityFromBool(bool smooth_before,
95                                                     bool smooth_after) {
96   if (smooth_before && smooth_after)
97     return SmoothVolatility::kSmooth2Smooth;
98   if (smooth_before && !smooth_after)
99     return SmoothVolatility::kSmooth2NotSmooth;
100   if (!smooth_before && smooth_after)
101     return SmoothVolatility::kNotSmooth2Smooth;
102   return SmoothVolatility::kNotSmooth2NotSmooth;
103 }
104
105 // Returns a UMA index for logging. The index corresponds to the key and the
106 // outcome of the smoothness prediction. Each bit in the index has the
107 // following meaning:
108 // bit | 12   11   10  |  9    8    7    6  |  5  |  4  |  3    2    1    0  |
109 //     |   pixels ix   |    codec profile   | res |is_hw| smooth prediction  |
110 int UmaSmoothPredictionData(const WebrtcVideoStatsDB::VideoDescKey& key,
111                             SmoothPrediction prediction) {
112   static_assert(static_cast<int>(SmoothPrediction::kMaxValue) < (1 << 4));
113   static_assert(static_cast<int>(LimitedCodecProfile::kMaxValue) < (1 << 4));
114   return GetWebrtcPixelsBucketIndex(key.pixels) << 10 |
115          static_cast<int>(
116              LimitedCodecProfileFromCodecProfile(key.codec_profile))
117              << 6 |
118          key.hardware_accelerated << 4 | static_cast<int>(prediction);
119 }
120
121 void ReportUmaSmoothPredictionData(const WebrtcVideoStatsDB::VideoDescKey& key,
122                                    SmoothPrediction prediction) {
123   std::string uma_name =
124       base::StringPrintf("Media.WebrtcVideoPerfHistory.SmoothPrediction.%s",
125                          (key.is_decode_stats ? "Decode" : "Encode"));
126   base::UmaHistogramSparse(uma_name, UmaSmoothPredictionData(key, prediction));
127 }
128
129 bool PredictSmoothFromStats(const WebrtcVideoStatsDB::VideoStats& stats,
130                             int frames_per_second,
131                             float threshold) {
132   // `kMillisecondsPerSecond` / `frames_per_second` is the maximum average
133   // number of ms that the processing can take in order to keep up with the fps.
134   return stats.p99_processing_time_ms /
135              (static_cast<float>(base::Time::kMillisecondsPerSecond) /
136               frames_per_second) <
137          threshold;
138 }
139
140 constexpr int MakeBucketedFramerate(int framerate) {
141   // Quantize the framerate to the closest of the two framerates.
142   constexpr int kFramerateBuckets[] = {30, 60};
143   constexpr int kFramerateThreshold =
144       (kFramerateBuckets[0] + kFramerateBuckets[1]) / 2;
145   return framerate < kFramerateThreshold ? kFramerateBuckets[0]
146                                          : kFramerateBuckets[1];
147 }
148
149 bool AreFeaturesInvalid(
150     const media::mojom::WebrtcPredictionFeatures& features) {
151   return features.video_pixels <= 0 ||
152          features.video_pixels > WebrtcVideoStatsDB::kPixelsAbsoluteMaxValue ||
153          features.profile < VIDEO_CODEC_PROFILE_MIN ||
154          features.profile > VIDEO_CODEC_PROFILE_MAX ||
155          features.profile == VIDEO_CODEC_PROFILE_UNKNOWN;
156 }
157
158 bool IsFramesPerSecondInvalid(int frames_per_second) {
159   // The min/max check of `frames_per_second` is only to filter out number that
160   // are completely out of range. The frame rate will be bucketed later on in
161   // the code.
162   constexpr int kMaxFramesPerSecond = 1000;
163   return frames_per_second <= 0 || frames_per_second > kMaxFramesPerSecond;
164 }
165
166 bool AreVideoStatsInvalid(const media::mojom::WebrtcVideoStats& video_stats) {
167   return video_stats.frames_processed <
168              WebrtcVideoStatsDB::kFramesProcessedMinValue ||
169          video_stats.frames_processed >
170              WebrtcVideoStatsDB::kFramesProcessedMaxValue ||
171          video_stats.key_frames_processed > video_stats.frames_processed ||
172          isnan(video_stats.p99_processing_time_ms) ||
173          video_stats.p99_processing_time_ms <
174              WebrtcVideoStatsDB::kP99ProcessingTimeMinValueMs ||
175          video_stats.p99_processing_time_ms >
176              WebrtcVideoStatsDB::kP99ProcessingTimeMaxValueMs;
177 }
178
179 }  // namespace
180
181 WebrtcVideoPerfHistory::WebrtcVideoPerfHistory(
182     std::unique_ptr<WebrtcVideoStatsDB> db)
183     : db_(std::move(db)) {
184   DVLOG(2) << __func__;
185   DCHECK(db_);
186 }
187
188 WebrtcVideoPerfHistory::~WebrtcVideoPerfHistory() {
189   DVLOG(2) << __func__;
190   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
191 }
192
193 void WebrtcVideoPerfHistory::BindReceiver(
194     mojo::PendingReceiver<media::mojom::WebrtcVideoPerfHistory> receiver) {
195   DVLOG(3) << __func__;
196   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
197   receivers_.Add(this, std::move(receiver));
198 }
199
200 void WebrtcVideoPerfHistory::InitDatabase() {
201   DVLOG(2) << __func__;
202   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
203
204   if (db_init_status_ == PENDING)
205     return;
206
207   // DB should be initialized only once! We hand out references to the
208   // initialized DB via GetWebrtcVideoStatsDB(). Dependents expect DB to remain
209   // initialized during their lifetime.
210   DCHECK_EQ(db_init_status_, UNINITIALIZED);
211
212   db_init_status_ = PENDING;
213   db_->Initialize(base::BindOnce(&WebrtcVideoPerfHistory::OnDatabaseInit,
214                                  weak_ptr_factory_.GetWeakPtr()));
215 }
216
217 void WebrtcVideoPerfHistory::OnDatabaseInit(bool success) {
218   DVLOG(2) << __func__ << " " << success;
219   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
220   DCHECK_EQ(db_init_status_, PENDING);
221
222   db_init_status_ = success ? COMPLETE : FAILED;
223
224   // Post all the deferred API calls as if they're just now coming in.
225   for (auto& deferred_call : init_deferred_api_calls_) {
226     base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
227         FROM_HERE, std::move(deferred_call));
228   }
229   init_deferred_api_calls_.clear();
230 }
231
232 void WebrtcVideoPerfHistory::GetPerfInfo(
233     media::mojom::WebrtcPredictionFeaturesPtr features,
234     int frames_per_second,
235     GetPerfInfoCallback got_info_cb) {
236   DVLOG(3) << __func__;
237   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
238
239   if (db_init_status_ == FAILED) {
240     // Optimistically claim perf is smooth.
241     std::move(got_info_cb).Run(true);
242     return;
243   }
244
245   // Defer this request until the DB is initialized.
246   if (db_init_status_ != COMPLETE) {
247     init_deferred_api_calls_.push_back(base::BindOnce(
248         &WebrtcVideoPerfHistory::GetPerfInfo, weak_ptr_factory_.GetWeakPtr(),
249         std::move(features), frames_per_second, std::move(got_info_cb)));
250     InitDatabase();
251     return;
252   }
253
254   // `features` and `frames_per_second` are coming over the mojo interface and
255   // may be compromised. They must be sanity checked before they are used.
256   if (AreFeaturesInvalid(*features) ||
257       IsFramesPerSecondInvalid(frames_per_second)) {
258     // Something must have happened if the features are not valid, perf is not
259     // smooth in this case.
260     std::move(got_info_cb).Run(false);
261     return;
262   }
263
264   // Log the requested codec profile. Useful in determining if the number of
265   // tracked profiles should be changed.
266   UMA_HISTOGRAM_ENUMERATION(
267       "Media.WebrtcVideoPerfHistory.GetPerfInfoCodecProfile", features->profile,
268       media::VIDEO_CODEC_PROFILE_MAX + 1);
269
270   WebrtcVideoStatsDB::VideoDescKey video_key =
271       WebrtcVideoStatsDB::VideoDescKey::MakeBucketedKey(
272           features->is_decode_stats, features->profile,
273           features->hardware_accelerated, features->video_pixels);
274
275   if (video_key.codec_profile == VIDEO_CODEC_PROFILE_UNKNOWN) {
276     // This is a codec profile that is not tracked. Return smooth=true.
277     DVLOG(2) << __func__
278              << base::StringPrintf(
279                     " The specified codec profile (%s) is not tracked. "
280                     "Returning default value.",
281                     GetProfileName(features->profile).c_str());
282     std::move(got_info_cb).Run(true);
283     return;
284   }
285
286   int frames_per_second_bucketed = MakeBucketedFramerate(frames_per_second);
287
288   db_->GetVideoStatsCollection(
289       video_key,
290       base::BindOnce(&WebrtcVideoPerfHistory::OnGotStatsCollectionForRequest,
291                      weak_ptr_factory_.GetWeakPtr(), video_key,
292                      frames_per_second_bucketed, std::move(got_info_cb)));
293 }
294
295 void WebrtcVideoPerfHistory::OnGotStatsCollectionForRequest(
296     const WebrtcVideoStatsDB::VideoDescKey& video_key,
297     int frames_per_second,
298     GetPerfInfoCallback got_info_cb,
299     bool database_success,
300     absl::optional<WebrtcVideoStatsDB::VideoStatsCollection> stats_collection) {
301   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
302   DCHECK(got_info_cb);
303   DCHECK_EQ(db_init_status_, COMPLETE);
304
305   // Be optimistic if there's no data.
306   bool is_smooth = true;
307   SmoothPrediction prediction = SmoothPrediction::kSmoothByDefault;
308   if (stats_collection) {
309     // Create a vector filled with smoothness
310     // predictions for all entries in the collection. `specific_key_index`
311     // will point to the entry corresponding to the requested `video_key`. If
312     // there is no entry corresponding to `video_key` an absl::nullopt will be
313     // inserted as a placeholder.
314     std::vector<absl::optional<bool>> smooth_per_pixel;
315     absl::optional<size_t> specific_key_index;
316     for (auto const& [key_index, video_stats_entry] : *stats_collection) {
317       if (key_index >= video_key.pixels && !specific_key_index) {
318         specific_key_index = smooth_per_pixel.size();
319         if (key_index > video_key.pixels) {
320           // No exact match found, insert a nullopt.
321           smooth_per_pixel.push_back(absl::nullopt);
322         }
323       }
324       smooth_per_pixel.push_back(PredictSmooth(
325           video_key.is_decode_stats, video_stats_entry, frames_per_second));
326     }
327     if (!specific_key_index) {
328       // Pixels for the specific key is higher than any pixels number that
329       // exists in the database.
330       specific_key_index = smooth_per_pixel.size();
331       smooth_per_pixel.push_back(absl::nullopt);
332     }
333
334     if (smooth_per_pixel[*specific_key_index].has_value()) {
335       prediction = smooth_per_pixel[*specific_key_index].value()
336                        ? SmoothPrediction::kSmoothFromData
337                        : SmoothPrediction::kNotSmoothFromData;
338     }
339
340     // Traverse from highest pixels value to lowest and propagate smooth=true,
341     // override smooth=false.
342     absl::optional<bool> previous_entry;
343     for (auto it = smooth_per_pixel.rbegin(); it != smooth_per_pixel.rend();
344          ++it) {
345       if (previous_entry.has_value() && previous_entry.value()) {
346         if (!it->has_value()) {
347           // Fill empty slot.
348           prediction = SmoothPrediction::kImplicitlySmooth;
349           *it = previous_entry;
350         } else if (!it->value()) {
351           // Override (because smooth=true has precedence over smooth=false) and
352           // log this since it's anomalous.
353           prediction = SmoothPrediction::kSmoothOverride;
354           *it = previous_entry;
355         }
356       }
357       previous_entry = *it;
358     }
359
360     // Traverse from lowest to highest pixels value and propagate smooth=false
361     // if there are empty slots.
362     previous_entry.reset();
363     for (auto& it : smooth_per_pixel) {
364       if (previous_entry.has_value() && !previous_entry.value()) {
365         if (!it.has_value()) {
366           // Fill empty slot.
367           prediction = SmoothPrediction::kImplicitlyNotSmooth;
368           it = previous_entry;
369         }
370       }
371       previous_entry = it;
372     }
373
374     DCHECK(specific_key_index);
375     if (smooth_per_pixel[*specific_key_index].has_value()) {
376       is_smooth = smooth_per_pixel[*specific_key_index].value();
377     }
378   }
379
380   ReportUmaSmoothPredictionData(video_key, prediction);
381
382   DVLOG(3) << __func__
383            << base::StringPrintf(
384                   " is_decode:%d profile:%s pixels:%d hw:%d --> ",
385                   video_key.is_decode_stats,
386                   GetProfileName(video_key.codec_profile).c_str(),
387                   video_key.pixels, video_key.hardware_accelerated)
388            << (stats_collection
389                    ? base::StringPrintf("smooth:%d entries:%zu prediction:%d",
390                                         is_smooth, stats_collection->size(),
391                                         static_cast<int>(prediction))
392                    : (database_success ? "no info" : "query FAILED"));
393
394   std::move(got_info_cb).Run(is_smooth);
395 }
396
397 WebrtcVideoPerfHistory::SaveCallback WebrtcVideoPerfHistory::GetSaveCallback() {
398   return base::BindRepeating(&WebrtcVideoPerfHistory::SavePerfRecord,
399                              weak_ptr_factory_.GetWeakPtr());
400 }
401
402 void WebrtcVideoPerfHistory::SavePerfRecord(
403     media::mojom::WebrtcPredictionFeatures features,
404     media::mojom::WebrtcVideoStats video_stats,
405     base::OnceClosure save_done_cb) {
406   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
407
408   DVLOG(3) << __func__
409            << base::StringPrintf(
410                   " profile:%d size:%d hw:%d processed:%u "
411                   "p99_processing_time:%.2f",
412                   features.profile, features.video_pixels,
413                   features.hardware_accelerated, video_stats.frames_processed,
414                   video_stats.p99_processing_time_ms);
415
416   if (db_init_status_ == FAILED) {
417     DVLOG(3) << __func__ << " Can't save stats. No DB!";
418     return;
419   }
420
421   // `features` and `video_stats` are coming over the mojo interface and may be
422   // compromised. They must be sanity checked before they are used.
423   if (AreFeaturesInvalid(features) || AreVideoStatsInvalid(video_stats)) {
424     DVLOG(3) << __func__ << " features or video_stats are invalid.";
425     return;
426   }
427
428   // Defer this request until the DB is initialized.
429   if (db_init_status_ != COMPLETE) {
430     init_deferred_api_calls_.push_back(base::BindOnce(
431         &WebrtcVideoPerfHistory::SavePerfRecord, weak_ptr_factory_.GetWeakPtr(),
432         std::move(features), std::move(video_stats), std::move(save_done_cb)));
433     InitDatabase();
434     return;
435   }
436
437   // Don't save entries with pixel sizes that are outside the specified
438   // range.
439   if (features.video_pixels < WebrtcVideoStatsDB::kPixelsMinValueToSave ||
440       features.video_pixels > WebrtcVideoStatsDB::kPixelsMaxValueToSave) {
441     DVLOG(3) << __func__
442              << " video_pixels is out of range and won't be stored.";
443     return;
444   }
445
446   // Log the codec profile. Useful in determining if the number of tracked
447   // profiles should be changed.
448   UMA_HISTOGRAM_ENUMERATION(
449       "Media.WebrtcVideoPerfHistory.SavePerfRecordCodecProfile",
450       features.profile, media::VIDEO_CODEC_PROFILE_MAX + 1);
451
452   WebrtcVideoStatsDB::VideoDescKey video_key =
453       WebrtcVideoStatsDB::VideoDescKey::MakeBucketedKey(
454           features.is_decode_stats, features.profile,
455           features.hardware_accelerated, features.video_pixels);
456
457   if (video_key.codec_profile == VIDEO_CODEC_PROFILE_UNKNOWN) {
458     DVLOG(3) << __func__
459              << " codec profile is not tracked and won't be stored.";
460     return;
461   }
462
463   WebrtcVideoStatsDB::VideoStats new_stats(video_stats.frames_processed,
464                                            video_stats.key_frames_processed,
465                                            video_stats.p99_processing_time_ms);
466
467   // Get past perf info and report UMA metrics before saving this record.
468   db_->GetVideoStats(video_key,
469                      base::BindOnce(&WebrtcVideoPerfHistory::OnGotStatsForSave,
470                                     weak_ptr_factory_.GetWeakPtr(), video_key,
471                                     new_stats, std::move(save_done_cb)));
472 }
473
474 void WebrtcVideoPerfHistory::OnGotStatsForSave(
475     const WebrtcVideoStatsDB::VideoDescKey& video_key,
476     const WebrtcVideoStatsDB::VideoStats& new_stats,
477     base::OnceClosure save_done_cb,
478     bool success,
479     absl::optional<WebrtcVideoStatsDB::VideoStatsEntry> past_stats) {
480   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
481   DCHECK_EQ(db_init_status_, COMPLETE);
482
483   if (!success) {
484     DVLOG(3) << __func__ << " FAILED! Aborting save.";
485     if (save_done_cb)
486       std::move(save_done_cb).Run();
487     return;
488   }
489
490   if (past_stats && !past_stats->empty()) {
491     ReportUmaMetricsOnSave(video_key.is_decode_stats, new_stats, *past_stats);
492   }
493
494   db_->AppendVideoStats(
495       video_key, new_stats,
496       base::BindOnce(&WebrtcVideoPerfHistory::OnSaveDone,
497                      weak_ptr_factory_.GetWeakPtr(), std::move(save_done_cb)));
498 }
499
500 void WebrtcVideoPerfHistory::OnSaveDone(base::OnceClosure save_done_cb,
501                                         bool success) {
502   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
503   DVLOG(3) << __func__ << (success ? " succeeded" : " FAILED!");
504
505   // Don't bother to bubble success. It's not actionable for upper layers.
506   // Also, `save_done_cb` only used for test sequencing, where DB should
507   // always behave (or fail the test).
508   if (save_done_cb)
509     std::move(save_done_cb).Run();
510 }
511
512 void WebrtcVideoPerfHistory::ClearHistory(base::OnceClosure clear_done_cb) {
513   DVLOG(2) << __func__;
514   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
515
516   if (db_init_status_ == FAILED) {
517     DVLOG(3) << __func__ << " Can't clear history - No DB!";
518     std::move(clear_done_cb).Run();
519     return;
520   }
521
522   // Defer this request until the DB is initialized.
523   if (db_init_status_ != COMPLETE) {
524     init_deferred_api_calls_.push_back(base::BindOnce(
525         &WebrtcVideoPerfHistory::ClearHistory, weak_ptr_factory_.GetWeakPtr(),
526         std::move(clear_done_cb)));
527     InitDatabase();
528     return;
529   }
530
531   db_->ClearStats(base::BindOnce(&WebrtcVideoPerfHistory::OnClearedHistory,
532                                  weak_ptr_factory_.GetWeakPtr(),
533                                  std::move(clear_done_cb)));
534 }
535
536 void WebrtcVideoPerfHistory::OnClearedHistory(base::OnceClosure clear_done_cb) {
537   DVLOG(2) << __func__;
538   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
539
540   std::move(clear_done_cb).Run();
541 }
542
543 void WebrtcVideoPerfHistory::GetWebrtcVideoStatsDB(GetCB get_db_cb) {
544   DVLOG(3) << __func__;
545   DCHECK(get_db_cb);
546   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
547
548   if (db_init_status_ == FAILED) {
549     std::move(get_db_cb).Run(nullptr);
550     return;
551   }
552
553   // Defer this request until the DB is initialized.
554   if (db_init_status_ != COMPLETE) {
555     init_deferred_api_calls_.push_back(
556         base::BindOnce(&WebrtcVideoPerfHistory::GetWebrtcVideoStatsDB,
557                        weak_ptr_factory_.GetWeakPtr(), std::move(get_db_cb)));
558     InitDatabase();
559     return;
560   }
561
562   // DB is already initialized. base::BindPostTaskToCurrentDefault to avoid
563   // reentrancy.
564   std::move(base::BindPostTaskToCurrentDefault(std::move(get_db_cb)))
565       .Run(db_.get());
566 }
567
568 // static
569 bool WebrtcVideoPerfHistory::PredictSmooth(
570     bool is_decode,
571     const WebrtcVideoStatsDB::VideoStatsEntry& stats_entry,
572     int frames_per_second) {
573   // No stats? Lets be optimistic.
574   if (stats_entry.empty()) {
575     return true;
576   }
577
578   const float kSmoothnessThreshold = GetSmoothnessThreshold(is_decode);
579   float smooth_count = 0;
580   for (auto const& stats : stats_entry) {
581     smooth_count +=
582         PredictSmoothFromStats(stats, frames_per_second, kSmoothnessThreshold);
583   }
584   return smooth_count / stats_entry.size() >= GetSmoothDecisionRatioThreshold();
585 }
586
587 // static
588 bool WebrtcVideoPerfHistory::PredictSmoothAfterUpdate(
589     bool is_decode,
590     const WebrtcVideoStatsDB::VideoStats& new_stats,
591     const WebrtcVideoStatsDB::VideoStatsEntry& past_stats_entry,
592     int frames_per_second) {
593   const int kMaxEntriesPerConfig = WebrtcVideoStatsDB::GetMaxEntriesPerConfig();
594   float kSmoothnessThreshold = GetSmoothnessThreshold(is_decode);
595   // Start with the new stats.
596   float smooth_count = PredictSmoothFromStats(new_stats, frames_per_second,
597                                               kSmoothnessThreshold);
598   int total_count = 1;
599   // Continue with existing stats up to the max count.
600   for (auto const& stats : past_stats_entry) {
601     smooth_count +=
602         PredictSmoothFromStats(stats, frames_per_second, kSmoothnessThreshold);
603     ++total_count;
604     if (total_count >= kMaxEntriesPerConfig) {
605       break;
606     }
607   }
608
609   return smooth_count / total_count >= GetSmoothDecisionRatioThreshold();
610 }
611
612 // static
613 void WebrtcVideoPerfHistory::ReportUmaMetricsOnSave(
614     bool is_decode_stats,
615     const WebrtcVideoStatsDB::VideoStats& new_stats,
616     const WebrtcVideoStatsDB::VideoStatsEntry& past_stats_entry) {
617   constexpr int kFramesPerSecondToTest[] = {30, 60};
618
619   for (auto& frames_per_second : kFramesPerSecondToTest) {
620     bool smooth_before_save =
621         PredictSmooth(is_decode_stats, past_stats_entry, frames_per_second);
622     bool smooth_after_save = PredictSmoothAfterUpdate(
623         is_decode_stats, new_stats, past_stats_entry, frames_per_second);
624     std::string uma_name = base::StringPrintf(
625         "Media.WebrtcVideoPerfHistory.SmoothVolatility.%s.%dfps",
626         (is_decode_stats ? "Decode" : "Encode"), frames_per_second);
627     base::UmaHistogramEnumeration(
628         uma_name,
629         SmoothVolatilityFromBool(smooth_before_save, smooth_after_save));
630   }
631 }
632
633 // static
634 float WebrtcVideoPerfHistory::GetSmoothnessThreshold(bool is_decode) {
635   return is_decode ? base::GetFieldTrialParamByFeatureAsDouble(
636                          kWebrtcMediaCapabilitiesParameters,
637                          kSmoothnessThresholdDecodeParamName,
638                          kSmoothnessThresholdDecodeDefault)
639                    : base::GetFieldTrialParamByFeatureAsDouble(
640                          kWebrtcMediaCapabilitiesParameters,
641                          kSmoothnessThresholdEncodeParamName,
642                          kSmoothnessThresholdEncodeDefault);
643 }
644
645 // static
646 float WebrtcVideoPerfHistory::GetSmoothDecisionRatioThreshold() {
647   return base::GetFieldTrialParamByFeatureAsDouble(
648       kWebrtcMediaCapabilitiesParameters,
649       kSmoothDecisionRatioThresholdParamName,
650       kSmoothDecisionRatioThresholdDefault);
651 }
652
653 }  // namespace media