1 // Copyright 2017 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/video_decode_perf_history.h"
7 #include "base/format_macros.h"
8 #include "base/functional/bind.h"
9 #include "base/functional/callback.h"
10 #include "base/logging.h"
11 #include "base/memory/ptr_util.h"
12 #include "base/metrics/field_trial_params.h"
13 #include "base/strings/stringprintf.h"
14 #include "base/task/bind_post_task.h"
15 #include "base/task/single_thread_task_runner.h"
16 #include "media/base/key_systems.h"
17 #include "media/base/media_switches.h"
18 #include "media/base/video_codecs.h"
19 #include "media/capabilities/learning_helper.h"
20 #include "media/mojo/mojom/media_types.mojom.h"
21 #include "services/metrics/public/cpp/ukm_builders.h"
22 #include "services/metrics/public/cpp/ukm_recorder.h"
28 const double kMaxSmoothDroppedFramesPercentParamDefault = .05;
32 const char VideoDecodePerfHistory::kMaxSmoothDroppedFramesPercentParamName[] =
36 VideoDecodePerfHistory::kEmeMaxSmoothDroppedFramesPercentParamName[] =
37 "eme_smooth_threshold";
40 double VideoDecodePerfHistory::GetMaxSmoothDroppedFramesPercent(bool is_eme) {
41 double threshold = base::GetFieldTrialParamByFeatureAsDouble(
42 kMediaCapabilitiesWithParameters, kMaxSmoothDroppedFramesPercentParamName,
43 kMaxSmoothDroppedFramesPercentParamDefault);
45 // For EME, the precedence of overrides is:
46 // 1. EME specific override, |k*Eme*MaxSmoothDroppedFramesPercentParamName
47 // 2. Non-EME override, |kMaxSmoothDroppedFramesPercentParamName|
48 // 3. |kMaxSmoothDroppedFramesPercentParamDefault|
50 threshold = base::GetFieldTrialParamByFeatureAsDouble(
51 kMediaCapabilitiesWithParameters,
52 kEmeMaxSmoothDroppedFramesPercentParamName, threshold);
59 base::FieldTrialParams VideoDecodePerfHistory::GetFieldTrialParams() {
60 base::FieldTrialParams actual_trial_params;
62 const bool result = base::GetFieldTrialParamsByFeature(
63 kMediaCapabilitiesWithParameters, &actual_trial_params);
66 return actual_trial_params;
69 VideoDecodePerfHistory::VideoDecodePerfHistory(
70 std::unique_ptr<VideoDecodeStatsDB> db,
71 learning::FeatureProviderFactoryCB feature_factory_cb)
73 db_init_status_(UNINITIALIZED),
74 feature_factory_cb_(std::move(feature_factory_cb)) {
78 // If the local learning experiment is enabled, then also create
79 // |learning_helper_| to send data to it.
80 if (base::FeatureList::IsEnabled(kMediaLearningExperiment))
81 learning_helper_ = std::make_unique<LearningHelper>(feature_factory_cb_);
84 VideoDecodePerfHistory::~VideoDecodePerfHistory() {
86 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
89 void VideoDecodePerfHistory::BindReceiver(
90 mojo::PendingReceiver<mojom::VideoDecodePerfHistory> receiver) {
92 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
93 receivers_.Add(this, std::move(receiver));
96 void VideoDecodePerfHistory::InitDatabase() {
98 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
100 if (db_init_status_ == PENDING)
103 // DB should be initialized only once! We hand out references to the
104 // initialized DB via GetVideoDecodeStatsDB(). Dependents expect DB to remain
105 // initialized during their lifetime.
106 DCHECK_EQ(db_init_status_, UNINITIALIZED);
108 db_->Initialize(base::BindOnce(&VideoDecodePerfHistory::OnDatabaseInit,
109 weak_ptr_factory_.GetWeakPtr()));
110 db_init_status_ = PENDING;
113 void VideoDecodePerfHistory::OnDatabaseInit(bool success) {
114 DVLOG(2) << __func__ << " " << success;
115 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
116 DCHECK_EQ(db_init_status_, PENDING);
118 db_init_status_ = success ? COMPLETE : FAILED;
120 // Post all the deferred API calls as if they're just now coming in.
121 for (auto& deferred_call : init_deferred_api_calls_) {
122 base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
123 FROM_HERE, std::move(deferred_call));
125 init_deferred_api_calls_.clear();
128 void VideoDecodePerfHistory::GetPerfInfo(mojom::PredictionFeaturesPtr features,
129 GetPerfInfoCallback got_info_cb) {
130 DVLOG(3) << __func__;
131 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
133 DCHECK_NE(features->profile, VIDEO_CODEC_PROFILE_UNKNOWN);
134 DCHECK_GT(features->frames_per_sec, 0);
135 DCHECK(features->video_size.width() > 0 && features->video_size.height() > 0);
137 if (db_init_status_ == FAILED) {
138 // Optimistically claim perf is both smooth and power efficient.
139 std::move(got_info_cb).Run(true, true);
143 // Defer this request until the DB is initialized.
144 if (db_init_status_ != COMPLETE) {
145 init_deferred_api_calls_.push_back(base::BindOnce(
146 &VideoDecodePerfHistory::GetPerfInfo, weak_ptr_factory_.GetWeakPtr(),
147 std::move(features), std::move(got_info_cb)));
152 VideoDecodeStatsDB::VideoDescKey video_key =
153 VideoDecodeStatsDB::VideoDescKey::MakeBucketedKey(
154 features->profile, features->video_size, features->frames_per_sec,
155 features->key_system, features->use_hw_secure_codecs);
158 video_key, base::BindOnce(&VideoDecodePerfHistory::OnGotStatsForRequest,
159 weak_ptr_factory_.GetWeakPtr(), video_key,
160 std::move(got_info_cb)));
163 void VideoDecodePerfHistory::AssessStats(
164 const VideoDecodeStatsDB::VideoDescKey& key,
165 const VideoDecodeStatsDB::DecodeStatsEntry* stats,
167 bool* is_power_efficient) {
168 // TODO(chcunningham/mlamouri): Refactor database API to give us nearby
169 // stats whenever we don't have a perfect match. If higher
170 // resolutions/frame rates are known to be smooth, we can report this as
171 /// smooth. If lower resolutions/frames are known to be janky, we can assume
172 // this will be janky.
174 // No stats? Lets be optimistic.
175 if (!stats || stats->frames_decoded == 0) {
176 *is_power_efficient = true;
181 double percent_dropped =
182 static_cast<double>(stats->frames_dropped) / stats->frames_decoded;
183 double percent_power_efficient =
184 static_cast<double>(stats->frames_power_efficient) /
185 stats->frames_decoded;
187 *is_power_efficient =
188 percent_power_efficient >= kMinPowerEfficientDecodedFramePercent;
190 *is_smooth = percent_dropped <=
191 GetMaxSmoothDroppedFramesPercent(!key.key_system.empty());
194 void VideoDecodePerfHistory::OnGotStatsForRequest(
195 const VideoDecodeStatsDB::VideoDescKey& video_key,
196 GetPerfInfoCallback got_info_cb,
197 bool database_success,
198 std::unique_ptr<VideoDecodeStatsDB::DecodeStatsEntry> stats) {
199 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
201 DCHECK_EQ(db_init_status_, COMPLETE);
203 bool is_power_efficient = false;
204 bool is_smooth = false;
205 double percent_dropped = 0;
206 double percent_power_efficient = 0;
208 AssessStats(video_key, stats.get(), &is_smooth, &is_power_efficient);
210 if (stats && stats->frames_decoded) {
211 DCHECK(database_success);
213 static_cast<double>(stats->frames_dropped) / stats->frames_decoded;
214 percent_power_efficient =
215 static_cast<double>(stats->frames_power_efficient) /
216 stats->frames_decoded;
220 << base::StringPrintf(
221 " profile:%s size:%s fps:%d --> ",
222 GetProfileName(video_key.codec_profile).c_str(),
223 video_key.size.ToString().c_str(), video_key.frame_rate)
225 ? base::StringPrintf(
226 "smooth:%d frames_decoded:%" PRIu64 " pcnt_dropped:%f"
227 " pcnt_power_efficent:%f",
228 is_smooth, stats->frames_decoded, percent_dropped,
229 percent_power_efficient)
230 : (database_success ? "no info" : "query FAILED"));
232 std::move(got_info_cb).Run(is_smooth, is_power_efficient);
235 VideoDecodePerfHistory::SaveCallback VideoDecodePerfHistory::GetSaveCallback() {
236 return base::BindRepeating(&VideoDecodePerfHistory::SavePerfRecord,
237 weak_ptr_factory_.GetWeakPtr());
240 void VideoDecodePerfHistory::SavePerfRecord(ukm::SourceId source_id,
241 learning::FeatureValue origin,
243 mojom::PredictionFeatures features,
244 mojom::PredictionTargets targets,
246 base::OnceClosure save_done_cb) {
247 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
250 << base::StringPrintf(
251 " profile:%s size:%s fps:%f decoded:%d dropped:%d efficient:%d",
252 GetProfileName(features.profile).c_str(),
253 features.video_size.ToString().c_str(), features.frames_per_sec,
254 targets.frames_decoded, targets.frames_dropped,
255 targets.frames_power_efficient);
257 if (db_init_status_ == FAILED) {
258 DVLOG(3) << __func__ << " Can't save stats. No DB!";
262 // Defer this request until the DB is initialized.
263 if (db_init_status_ != COMPLETE) {
264 init_deferred_api_calls_.push_back(base::BindOnce(
265 &VideoDecodePerfHistory::SavePerfRecord, weak_ptr_factory_.GetWeakPtr(),
266 source_id, origin, is_top_frame, std::move(features),
267 std::move(targets), player_id, std::move(save_done_cb)));
272 VideoDecodeStatsDB::VideoDescKey video_key =
273 VideoDecodeStatsDB::VideoDescKey::MakeBucketedKey(
274 features.profile, features.video_size, features.frames_per_sec,
275 features.key_system, features.use_hw_secure_codecs);
276 VideoDecodeStatsDB::DecodeStatsEntry new_stats(
277 targets.frames_decoded, targets.frames_dropped,
278 targets.frames_power_efficient);
280 if (learning_helper_)
281 learning_helper_->AppendStats(video_key, origin, new_stats);
283 // Get past perf info and report UKM metrics before saving this record.
286 base::BindOnce(&VideoDecodePerfHistory::OnGotStatsForSave,
287 weak_ptr_factory_.GetWeakPtr(), source_id, is_top_frame,
288 player_id, video_key, new_stats, std::move(save_done_cb)));
291 void VideoDecodePerfHistory::OnGotStatsForSave(
292 ukm::SourceId source_id,
295 const VideoDecodeStatsDB::VideoDescKey& video_key,
296 const VideoDecodeStatsDB::DecodeStatsEntry& new_stats,
297 base::OnceClosure save_done_cb,
299 std::unique_ptr<VideoDecodeStatsDB::DecodeStatsEntry> past_stats) {
300 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
301 DCHECK_EQ(db_init_status_, COMPLETE);
304 DVLOG(3) << __func__ << " FAILED! Aborting save.";
306 std::move(save_done_cb).Run();
310 ReportUkmMetrics(source_id, is_top_frame, player_id, video_key, new_stats,
313 db_->AppendDecodeStats(
314 video_key, new_stats,
315 base::BindOnce(&VideoDecodePerfHistory::OnSaveDone,
316 weak_ptr_factory_.GetWeakPtr(), std::move(save_done_cb)));
319 void VideoDecodePerfHistory::OnSaveDone(base::OnceClosure save_done_cb,
321 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
323 // TODO(chcunningham): Monitor UMA. Experiment with re-initializing DB to
324 // remedy IO failures.
325 DVLOG(3) << __func__ << (success ? " succeeded" : " FAILED!");
327 // Don't bother to bubble success. Its not actionable for upper layers. Also,
328 // save_done_cb only used for test sequencing, where DB should always behave
329 // (or fail the test).
331 std::move(save_done_cb).Run();
334 void VideoDecodePerfHistory::ReportUkmMetrics(
335 ukm::SourceId source_id,
338 const VideoDecodeStatsDB::VideoDescKey& video_key,
339 const VideoDecodeStatsDB::DecodeStatsEntry& new_stats,
340 VideoDecodeStatsDB::DecodeStatsEntry* past_stats) {
341 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
343 // UKM may be unavailable in content_shell or other non-chrome/ builds; it
344 // may also be unavailable if browser shutdown has started; so this may be a
345 // nullptr. If it's unavailable, UKM reporting will be skipped.
346 ukm::UkmRecorder* ukm_recorder = ukm::UkmRecorder::Get();
350 ukm::builders::Media_VideoDecodePerfRecord builder(source_id);
351 builder.SetVideo_InTopFrame(is_top_frame);
352 builder.SetVideo_PlayerID(player_id);
354 builder.SetVideo_CodecProfile(video_key.codec_profile);
355 builder.SetVideo_FramesPerSecond(video_key.frame_rate);
356 builder.SetVideo_NaturalHeight(video_key.size.height());
357 builder.SetVideo_NaturalWidth(video_key.size.width());
359 if (!video_key.key_system.empty()) {
360 builder.SetVideo_EME_KeySystem(GetKeySystemIntForUKM(video_key.key_system));
361 builder.SetVideo_EME_UseHwSecureCodecs(video_key.use_hw_secure_codecs);
364 bool past_is_smooth = false;
365 bool past_is_efficient = false;
366 AssessStats(video_key, past_stats, &past_is_smooth, &past_is_efficient);
367 builder.SetPerf_ApiWouldClaimIsSmooth(past_is_smooth);
368 builder.SetPerf_ApiWouldClaimIsPowerEfficient(past_is_efficient);
370 builder.SetPerf_PastVideoFramesDecoded(past_stats->frames_decoded);
371 builder.SetPerf_PastVideoFramesDropped(past_stats->frames_dropped);
372 builder.SetPerf_PastVideoFramesPowerEfficient(
373 past_stats->frames_power_efficient);
375 builder.SetPerf_PastVideoFramesDecoded(0);
376 builder.SetPerf_PastVideoFramesDropped(0);
377 builder.SetPerf_PastVideoFramesPowerEfficient(0);
380 bool new_is_smooth = false;
381 bool new_is_efficient = false;
382 AssessStats(video_key, &new_stats, &new_is_smooth, &new_is_efficient);
383 builder.SetPerf_RecordIsSmooth(new_is_smooth);
384 builder.SetPerf_RecordIsPowerEfficient(new_is_efficient);
385 builder.SetPerf_VideoFramesDecoded(new_stats.frames_decoded);
386 builder.SetPerf_VideoFramesDropped(new_stats.frames_dropped);
387 builder.SetPerf_VideoFramesPowerEfficient(new_stats.frames_power_efficient);
389 builder.Record(ukm_recorder);
392 void VideoDecodePerfHistory::ClearHistory(base::OnceClosure clear_done_cb) {
393 DVLOG(2) << __func__;
394 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
396 // If we have a learning helper, then replace it. This will erase any data
397 // that it currently has.
398 if (learning_helper_)
399 learning_helper_ = std::make_unique<LearningHelper>(feature_factory_cb_);
401 if (db_init_status_ == FAILED) {
402 DVLOG(3) << __func__ << " Can't clear history - No DB!";
403 std::move(clear_done_cb).Run();
407 // Defer this request until the DB is initialized.
408 if (db_init_status_ != COMPLETE) {
409 init_deferred_api_calls_.push_back(base::BindOnce(
410 &VideoDecodePerfHistory::ClearHistory, weak_ptr_factory_.GetWeakPtr(),
411 std::move(clear_done_cb)));
416 db_->ClearStats(base::BindOnce(&VideoDecodePerfHistory::OnClearedHistory,
417 weak_ptr_factory_.GetWeakPtr(),
418 std::move(clear_done_cb)));
421 void VideoDecodePerfHistory::OnClearedHistory(base::OnceClosure clear_done_cb) {
422 DVLOG(2) << __func__;
423 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
425 std::move(clear_done_cb).Run();
428 void VideoDecodePerfHistory::GetVideoDecodeStatsDB(GetCB get_db_cb) {
429 DVLOG(3) << __func__;
431 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
433 if (db_init_status_ == FAILED) {
434 std::move(get_db_cb).Run(nullptr);
438 // Defer this request until the DB is initialized.
439 if (db_init_status_ != COMPLETE) {
440 init_deferred_api_calls_.push_back(
441 base::BindOnce(&VideoDecodePerfHistory::GetVideoDecodeStatsDB,
442 weak_ptr_factory_.GetWeakPtr(), std::move(get_db_cb)));
447 // DB is already initialized. base::BindPostTaskToCurrentDefault to avoid
449 std::move(base::BindPostTaskToCurrentDefault(std::move(get_db_cb)))