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.
5 #include "media/mojo/services/webrtc_video_perf_history.h"
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"
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";
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,
47 kMaxValue = kSmooth2Smooth,
50 // These values are persisted to logs. Entries should not be renumbered and
51 // numeric values should never be reused.
52 enum class SmoothPrediction {
55 kNotSmoothFromData = 2,
56 kImplicitlySmooth = 3,
58 kImplicitlyNotSmooth = 5,
59 kMaxValue = kImplicitlyNotSmooth,
62 // These values are persisted to logs. Entries should not be renumbered and
63 // numeric values should never be reused.
64 enum class LimitedCodecProfile {
74 LimitedCodecProfile LimitedCodecProfileFromCodecProfile(
75 VideoCodecProfile codec_profile) {
76 if (codec_profile >= H264PROFILE_MIN && codec_profile <= H264PROFILE_MAX) {
77 return LimitedCodecProfile::kH264;
79 if (codec_profile >= VP8PROFILE_MIN && codec_profile <= VP8PROFILE_MAX) {
80 return LimitedCodecProfile::kVP8;
82 if (codec_profile == VP9PROFILE_PROFILE0) {
83 return LimitedCodecProfile::kVP9Profile0;
85 if (codec_profile == VP9PROFILE_PROFILE2) {
86 return LimitedCodecProfile::kVP9Profile2;
88 if (codec_profile >= AV1PROFILE_MIN && codec_profile <= AV1PROFILE_MAX) {
89 return LimitedCodecProfile::kAv1;
91 return LimitedCodecProfile::kOther;
94 constexpr SmoothVolatility SmoothVolatilityFromBool(bool smooth_before,
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;
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 |
116 LimitedCodecProfileFromCodecProfile(key.codec_profile))
118 key.hardware_accelerated << 4 | static_cast<int>(prediction);
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));
129 bool PredictSmoothFromStats(const WebrtcVideoStatsDB::VideoStats& stats,
130 int frames_per_second,
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) /
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];
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;
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
162 constexpr int kMaxFramesPerSecond = 1000;
163 return frames_per_second <= 0 || frames_per_second > kMaxFramesPerSecond;
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;
181 WebrtcVideoPerfHistory::WebrtcVideoPerfHistory(
182 std::unique_ptr<WebrtcVideoStatsDB> db)
183 : db_(std::move(db)) {
184 DVLOG(2) << __func__;
188 WebrtcVideoPerfHistory::~WebrtcVideoPerfHistory() {
189 DVLOG(2) << __func__;
190 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
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));
200 void WebrtcVideoPerfHistory::InitDatabase() {
201 DVLOG(2) << __func__;
202 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
204 if (db_init_status_ == PENDING)
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);
212 db_init_status_ = PENDING;
213 db_->Initialize(base::BindOnce(&WebrtcVideoPerfHistory::OnDatabaseInit,
214 weak_ptr_factory_.GetWeakPtr()));
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);
222 db_init_status_ = success ? COMPLETE : FAILED;
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));
229 init_deferred_api_calls_.clear();
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_);
239 if (db_init_status_ == FAILED) {
240 // Optimistically claim perf is smooth.
241 std::move(got_info_cb).Run(true);
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)));
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);
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);
270 WebrtcVideoStatsDB::VideoDescKey video_key =
271 WebrtcVideoStatsDB::VideoDescKey::MakeBucketedKey(
272 features->is_decode_stats, features->profile,
273 features->hardware_accelerated, features->video_pixels);
275 if (video_key.codec_profile == VIDEO_CODEC_PROFILE_UNKNOWN) {
276 // This is a codec profile that is not tracked. Return smooth=true.
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);
286 int frames_per_second_bucketed = MakeBucketedFramerate(frames_per_second);
288 db_->GetVideoStatsCollection(
290 base::BindOnce(&WebrtcVideoPerfHistory::OnGotStatsCollectionForRequest,
291 weak_ptr_factory_.GetWeakPtr(), video_key,
292 frames_per_second_bucketed, std::move(got_info_cb)));
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_);
303 DCHECK_EQ(db_init_status_, COMPLETE);
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);
324 smooth_per_pixel.push_back(PredictSmooth(
325 video_key.is_decode_stats, video_stats_entry, frames_per_second));
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);
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;
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();
345 if (previous_entry.has_value() && previous_entry.value()) {
346 if (!it->has_value()) {
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;
357 previous_entry = *it;
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()) {
367 prediction = SmoothPrediction::kImplicitlyNotSmooth;
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();
380 ReportUmaSmoothPredictionData(video_key, prediction);
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)
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"));
394 std::move(got_info_cb).Run(is_smooth);
397 WebrtcVideoPerfHistory::SaveCallback WebrtcVideoPerfHistory::GetSaveCallback() {
398 return base::BindRepeating(&WebrtcVideoPerfHistory::SavePerfRecord,
399 weak_ptr_factory_.GetWeakPtr());
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_);
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);
416 if (db_init_status_ == FAILED) {
417 DVLOG(3) << __func__ << " Can't save stats. No DB!";
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.";
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)));
437 // Don't save entries with pixel sizes that are outside the specified
439 if (features.video_pixels < WebrtcVideoStatsDB::kPixelsMinValueToSave ||
440 features.video_pixels > WebrtcVideoStatsDB::kPixelsMaxValueToSave) {
442 << " video_pixels is out of range and won't be stored.";
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);
452 WebrtcVideoStatsDB::VideoDescKey video_key =
453 WebrtcVideoStatsDB::VideoDescKey::MakeBucketedKey(
454 features.is_decode_stats, features.profile,
455 features.hardware_accelerated, features.video_pixels);
457 if (video_key.codec_profile == VIDEO_CODEC_PROFILE_UNKNOWN) {
459 << " codec profile is not tracked and won't be stored.";
463 WebrtcVideoStatsDB::VideoStats new_stats(video_stats.frames_processed,
464 video_stats.key_frames_processed,
465 video_stats.p99_processing_time_ms);
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)));
474 void WebrtcVideoPerfHistory::OnGotStatsForSave(
475 const WebrtcVideoStatsDB::VideoDescKey& video_key,
476 const WebrtcVideoStatsDB::VideoStats& new_stats,
477 base::OnceClosure save_done_cb,
479 absl::optional<WebrtcVideoStatsDB::VideoStatsEntry> past_stats) {
480 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
481 DCHECK_EQ(db_init_status_, COMPLETE);
484 DVLOG(3) << __func__ << " FAILED! Aborting save.";
486 std::move(save_done_cb).Run();
490 if (past_stats && !past_stats->empty()) {
491 ReportUmaMetricsOnSave(video_key.is_decode_stats, new_stats, *past_stats);
494 db_->AppendVideoStats(
495 video_key, new_stats,
496 base::BindOnce(&WebrtcVideoPerfHistory::OnSaveDone,
497 weak_ptr_factory_.GetWeakPtr(), std::move(save_done_cb)));
500 void WebrtcVideoPerfHistory::OnSaveDone(base::OnceClosure save_done_cb,
502 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
503 DVLOG(3) << __func__ << (success ? " succeeded" : " FAILED!");
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).
509 std::move(save_done_cb).Run();
512 void WebrtcVideoPerfHistory::ClearHistory(base::OnceClosure clear_done_cb) {
513 DVLOG(2) << __func__;
514 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
516 if (db_init_status_ == FAILED) {
517 DVLOG(3) << __func__ << " Can't clear history - No DB!";
518 std::move(clear_done_cb).Run();
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)));
531 db_->ClearStats(base::BindOnce(&WebrtcVideoPerfHistory::OnClearedHistory,
532 weak_ptr_factory_.GetWeakPtr(),
533 std::move(clear_done_cb)));
536 void WebrtcVideoPerfHistory::OnClearedHistory(base::OnceClosure clear_done_cb) {
537 DVLOG(2) << __func__;
538 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
540 std::move(clear_done_cb).Run();
543 void WebrtcVideoPerfHistory::GetWebrtcVideoStatsDB(GetCB get_db_cb) {
544 DVLOG(3) << __func__;
546 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
548 if (db_init_status_ == FAILED) {
549 std::move(get_db_cb).Run(nullptr);
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)));
562 // DB is already initialized. base::BindPostTaskToCurrentDefault to avoid
564 std::move(base::BindPostTaskToCurrentDefault(std::move(get_db_cb)))
569 bool WebrtcVideoPerfHistory::PredictSmooth(
571 const WebrtcVideoStatsDB::VideoStatsEntry& stats_entry,
572 int frames_per_second) {
573 // No stats? Lets be optimistic.
574 if (stats_entry.empty()) {
578 const float kSmoothnessThreshold = GetSmoothnessThreshold(is_decode);
579 float smooth_count = 0;
580 for (auto const& stats : stats_entry) {
582 PredictSmoothFromStats(stats, frames_per_second, kSmoothnessThreshold);
584 return smooth_count / stats_entry.size() >= GetSmoothDecisionRatioThreshold();
588 bool WebrtcVideoPerfHistory::PredictSmoothAfterUpdate(
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);
599 // Continue with existing stats up to the max count.
600 for (auto const& stats : past_stats_entry) {
602 PredictSmoothFromStats(stats, frames_per_second, kSmoothnessThreshold);
604 if (total_count >= kMaxEntriesPerConfig) {
609 return smooth_count / total_count >= GetSmoothDecisionRatioThreshold();
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};
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(
629 SmoothVolatilityFromBool(smooth_before_save, smooth_after_save));
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);
646 float WebrtcVideoPerfHistory::GetSmoothDecisionRatioThreshold() {
647 return base::GetFieldTrialParamByFeatureAsDouble(
648 kWebrtcMediaCapabilitiesParameters,
649 kSmoothDecisionRatioThresholdParamName,
650 kSmoothDecisionRatioThresholdDefault);