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.
9 #include "base/functional/bind.h"
10 #include "base/memory/ptr_util.h"
11 #include "base/run_loop.h"
12 #include "base/strings/string_number_conversions.h"
13 #include "base/test/bind.h"
14 #include "base/test/scoped_feature_list.h"
15 #include "base/test/task_environment.h"
16 #include "components/ukm/test_ukm_recorder.h"
17 #include "media/base/key_systems.h"
18 #include "media/base/media_switches.h"
19 #include "media/capabilities/video_decode_stats_db.h"
20 #include "media/mojo/mojom/media_types.mojom.h"
21 #include "media/mojo/services/test_helpers.h"
22 #include "media/mojo/services/video_decode_perf_history.h"
23 #include "services/metrics/public/cpp/ukm_builders.h"
24 #include "testing/gmock/include/gmock/gmock.h"
25 #include "testing/gtest/include/gtest/gtest.h"
27 #include "url/origin.h"
29 using UkmEntry = ukm::builders::Media_VideoDecodePerfRecord;
30 using testing::IsNull;
35 // Aliases for readability.
36 const bool kIsSmooth = true;
37 const bool kIsNotSmooth = false;
38 const bool kIsPowerEfficient = true;
39 const bool kIsNotPowerEfficient = false;
40 const bool kIsTopFrame = true;
41 const uint64_t kPlayerId = 1234u;
47 class FakeVideoDecodeStatsDB : public VideoDecodeStatsDB {
49 FakeVideoDecodeStatsDB() = default;
50 ~FakeVideoDecodeStatsDB() override = default;
52 // Call CompleteInitialize(...) to run |init_cb| callback.
53 void Initialize(base::OnceCallback<void(bool)> init_cb) override {
54 EXPECT_FALSE(!!pendnding_init_cb_);
55 pendnding_init_cb_ = std::move(init_cb);
58 // Completes fake initialization, running |init_cb| with the supplied value
60 void CompleteInitialize(bool success) {
61 DVLOG(2) << __func__ << " running with success = " << success;
62 EXPECT_TRUE(!!pendnding_init_cb_);
63 std::move(pendnding_init_cb_).Run(success);
66 // Simple hooks to fail the next calls to AppendDecodeStats() and
67 // GetDecodeStats(). Will be reset to false after the call.
68 void set_fail_next_append(bool fail_append) {
69 fail_next_append_ = fail_append;
71 void set_fail_next_get(bool fail_get) { fail_next_get_ = fail_get; }
73 void AppendDecodeStats(const VideoDescKey& key,
74 const DecodeStatsEntry& new_entry,
75 AppendDecodeStatsCB append_done_cb) override {
76 if (fail_next_append_) {
77 fail_next_append_ = false;
78 std::move(append_done_cb).Run(false);
82 std::string key_str = key.Serialize();
83 if (entries_.find(key_str) == entries_.end()) {
84 entries_.emplace(std::make_pair(key_str, new_entry));
86 const DecodeStatsEntry& known_entry = entries_.at(key_str);
87 entries_.at(key_str) = DecodeStatsEntry(
88 known_entry.frames_decoded + new_entry.frames_decoded,
89 known_entry.frames_dropped + new_entry.frames_dropped,
90 known_entry.frames_power_efficient +
91 new_entry.frames_power_efficient);
94 std::move(append_done_cb).Run(true);
97 void GetDecodeStats(const VideoDescKey& key,
98 GetDecodeStatsCB get_stats_cb) override {
100 fail_next_get_ = false;
101 std::move(get_stats_cb).Run(false, nullptr);
105 auto entry_it = entries_.find(key.Serialize());
106 if (entry_it == entries_.end()) {
107 std::move(get_stats_cb).Run(true, nullptr);
109 std::move(get_stats_cb)
110 .Run(true, std::make_unique<DecodeStatsEntry>(entry_it->second));
114 void ClearStats(base::OnceClosure clear_done_cb) override {
116 std::move(clear_done_cb).Run();
120 friend class VideoDecodePerfHistoryTest;
122 // Private method for immediate retrieval of stats for test helpers.
123 std::unique_ptr<DecodeStatsEntry> GetDecodeStatsSync(
124 const VideoDescKey& key) {
125 auto entry_it = entries_.find(key.Serialize());
126 if (entry_it == entries_.end()) {
129 return std::make_unique<DecodeStatsEntry>(entry_it->second);
133 bool fail_next_append_ = false;
134 bool fail_next_get_ = false;
136 std::map<std::string, DecodeStatsEntry> entries_;
138 base::OnceCallback<void(bool)> pendnding_init_cb_;
141 class VideoDecodePerfHistoryTest : public testing::Test {
143 // Indicates what type of UKM verification should be performed upon saving
144 // new stats to the perf history.
145 enum class UkmVerifcation {
150 void SetUp() override {
151 test_recorder_ = std::make_unique<ukm::TestAutoSetUkmRecorder>();
152 perf_history_ = std::make_unique<VideoDecodePerfHistory>(
153 std::make_unique<FakeVideoDecodeStatsDB>());
156 void TearDown() override { perf_history_.reset(); }
158 FakeVideoDecodeStatsDB* GetFakeDB() {
159 return static_cast<FakeVideoDecodeStatsDB*>(perf_history_->db_.get());
162 void PreInitializeDB(bool initialize_success) {
163 // Invoke private method to start initialization. Usually invoked by first
164 // API call requiring DB access.
165 perf_history_->InitDatabase();
166 // Complete initialization by firing callback from our fake DB.
167 GetFakeDB()->CompleteInitialize(initialize_success);
170 double GetMaxSmoothDroppedFramesPercent(bool is_eme = false) {
171 return VideoDecodePerfHistory::GetMaxSmoothDroppedFramesPercent(is_eme);
174 static base::FieldTrialParams GetFieldTrialParams() {
175 return VideoDecodePerfHistory::GetFieldTrialParams();
178 // Tests may set this as the callback for VideoDecodePerfHistory::GetPerfInfo
179 // to check the results of the call.
180 MOCK_METHOD2(MockGetPerfInfoCB,
181 void(bool is_smooth, bool is_power_efficient));
183 // Tests should EXPECT_CALL this method prior to ClearHistory() to know that
184 // the operation has completed.
185 MOCK_METHOD0(MockOnClearedHistory, void());
187 MOCK_METHOD1(MockGetVideoDecodeStatsDBCB, void(VideoDecodeStatsDB* db));
189 // Internal check that the UKM verification is complete before the test exits.
190 MOCK_METHOD0(UkmVerifyDoneCb, void());
192 void SavePerfRecord(UkmVerifcation ukm_verification,
193 const url::Origin& origin,
195 mojom::PredictionFeatures features,
196 mojom::PredictionTargets targets,
197 uint64_t player_id) {
198 // Manually associate URL with |source_id|. In production this happens
199 // externally at a higher layer.
200 const ukm::SourceId source_id = test_recorder_->GetNewSourceID();
201 test_recorder_->UpdateSourceURL(source_id, origin.GetURL());
203 // Use save callback to verify UKM reporting.
204 base::OnceClosure save_done_cb;
205 switch (ukm_verification) {
206 case UkmVerifcation::kSaveTriggersUkm: {
207 // Expect UKM report with given properties upon successful save. Use
208 // old stats values from DB to set expectations about what API would
209 // have claimed pre-save.
210 std::unique_ptr<DecodeStatsEntry> old_stats =
211 GetStatsForFeatures(features);
213 base::BindOnce(&VideoDecodePerfHistoryTest::VerifyLastUkmReport,
214 base::Unretained(this), origin, is_top_frame,
215 features, targets, player_id, std::move(old_stats));
218 case UkmVerifcation::kNoUkmsAdded: {
219 // Expect no additional UKM entries upon failing to save. Capture the
220 // current entry count before save to verify no new entries are added.
221 size_t current_num_entries =
222 test_recorder_->GetEntriesByName(UkmEntry::kEntryName).size();
223 save_done_cb = base::BindOnce(
224 &VideoDecodePerfHistoryTest::VerifyNoUkmReportForFailedSave,
225 base::Unretained(this), current_num_entries);
230 // Check that the UKM verification is complete before the test exits.
231 EXPECT_CALL(*this, UkmVerifyDoneCb());
233 perf_history_->GetSaveCallback().Run(
234 source_id, learning::FeatureValue(kOrigin.host()), is_top_frame,
235 features, targets, player_id, std::move(save_done_cb));
239 using VideoDescKey = VideoDecodeStatsDB::VideoDescKey;
240 using DecodeStatsEntry = VideoDecodeStatsDB::DecodeStatsEntry;
242 // Private helper to public test methods. This bypasses VPH::GetPerfInfo()
243 // to synchronously grab stats from the fake DB. Tests should instead use
244 // the VPH::GetPerfInfo().
245 std::unique_ptr<DecodeStatsEntry> GetStatsForFeatures(
246 mojom::PredictionFeatures features) {
247 VideoDecodeStatsDB::VideoDescKey video_key =
248 VideoDecodeStatsDB::VideoDescKey::MakeBucketedKey(
249 features.profile, features.video_size, features.frames_per_sec,
250 features.key_system, features.use_hw_secure_codecs);
251 return GetFakeDB()->GetDecodeStatsSync(video_key);
254 // Lookup up the most recent recorded entry and verify its properties against
256 void VerifyLastUkmReport(const url::Origin& origin,
258 mojom::PredictionFeatures features,
259 mojom::PredictionTargets new_targets,
261 std::unique_ptr<DecodeStatsEntry> old_stats) {
262 #define EXPECT_UKM(name, value) \
263 test_recorder_->ExpectEntryMetric(entry, name, value)
264 #define EXPECT_NO_UKM(name) \
265 EXPECT_FALSE(test_recorder_->EntryHasMetric(entry, name))
267 const auto& entries =
268 test_recorder_->GetEntriesByName(UkmEntry::kEntryName);
269 ASSERT_GE(entries.size(), 1U);
270 auto* entry = entries.back();
272 // Verify stream properties. Make a key to ensure we check bucketed values.
273 VideoDecodeStatsDB::VideoDescKey key =
274 VideoDecodeStatsDB::VideoDescKey::MakeBucketedKey(
275 features.profile, features.video_size, features.frames_per_sec,
276 features.key_system, features.use_hw_secure_codecs);
277 test_recorder_->ExpectEntrySourceHasUrl(entry, origin.GetURL());
278 EXPECT_UKM(UkmEntry::kVideo_InTopFrameName, is_top_frame);
279 EXPECT_UKM(UkmEntry::kVideo_PlayerIDName, player_id);
280 EXPECT_UKM(UkmEntry::kVideo_CodecProfileName, key.codec_profile);
281 EXPECT_UKM(UkmEntry::kVideo_FramesPerSecondName, key.frame_rate);
282 EXPECT_UKM(UkmEntry::kVideo_NaturalHeightName, key.size.height());
283 EXPECT_UKM(UkmEntry::kVideo_NaturalWidthName, key.size.width());
284 if (key.key_system.empty()) {
285 EXPECT_NO_UKM(UkmEntry::kVideo_EME_KeySystemName);
286 EXPECT_NO_UKM(UkmEntry::kVideo_EME_UseHwSecureCodecsName);
288 EXPECT_UKM(UkmEntry::kVideo_EME_KeySystemName,
289 GetKeySystemIntForUKM(key.key_system));
290 EXPECT_UKM(UkmEntry::kVideo_EME_UseHwSecureCodecsName,
291 key.use_hw_secure_codecs);
294 // Verify past stats.
295 bool past_is_smooth = false;
296 bool past_is_efficient = false;
297 perf_history_->AssessStats(key, old_stats.get(), &past_is_smooth,
299 EXPECT_UKM(UkmEntry::kPerf_ApiWouldClaimIsSmoothName, past_is_smooth);
300 EXPECT_UKM(UkmEntry::kPerf_ApiWouldClaimIsPowerEfficientName,
302 // Zero it out to make verification readable.
304 old_stats = std::make_unique<DecodeStatsEntry>(0, 0, 0);
305 EXPECT_UKM(UkmEntry::kPerf_PastVideoFramesDecodedName,
306 old_stats->frames_decoded);
307 EXPECT_UKM(UkmEntry::kPerf_PastVideoFramesDroppedName,
308 old_stats->frames_dropped);
309 EXPECT_UKM(UkmEntry::kPerf_PastVideoFramesPowerEfficientName,
310 old_stats->frames_power_efficient);
312 // Verify latest stats.
313 VideoDecodeStatsDB::DecodeStatsEntry new_stats(
314 new_targets.frames_decoded, new_targets.frames_dropped,
315 new_targets.frames_power_efficient);
316 bool new_is_smooth = false;
317 bool new_is_efficient = false;
318 perf_history_->AssessStats(key, &new_stats, &new_is_smooth,
320 EXPECT_UKM(UkmEntry::kPerf_RecordIsSmoothName, new_is_smooth);
321 EXPECT_UKM(UkmEntry::kPerf_RecordIsPowerEfficientName, new_is_efficient);
322 EXPECT_UKM(UkmEntry::kPerf_VideoFramesDecodedName,
323 new_stats.frames_decoded);
324 EXPECT_UKM(UkmEntry::kPerf_VideoFramesDroppedName,
325 new_stats.frames_dropped);
326 EXPECT_UKM(UkmEntry::kPerf_VideoFramesPowerEfficientName,
327 new_stats.frames_power_efficient);
335 void VerifyNoUkmReportForFailedSave(size_t expected_num_ukm_entries) {
336 // Verify no new UKM entries appear after failed save.
337 const auto& entries =
338 test_recorder_->GetEntriesByName(UkmEntry::kEntryName);
339 EXPECT_EQ(expected_num_ukm_entries, entries.size());
344 static constexpr double kMinPowerEfficientDecodedFramePercent =
345 VideoDecodePerfHistory::kMinPowerEfficientDecodedFramePercent;
347 base::test::TaskEnvironment task_environment_;
349 std::unique_ptr<ukm::TestAutoSetUkmRecorder> test_recorder_;
351 // The VideoDecodeStatsReporter being tested.
352 std::unique_ptr<VideoDecodePerfHistory> perf_history_;
354 const url::Origin kOrigin = url::Origin::Create(GURL("http://example.com"));
357 struct PerfHistoryTestParams {
358 const bool defer_initialize;
359 const std::string key_system;
360 const bool use_hw_secure_codecs;
363 // When bool param is true, tests should wait until the end to run
364 // GetFakeDB()->CompleteInitialize(). Otherwise run PreInitializeDB() at the
366 class VideoDecodePerfHistoryParamTest
367 : public testing::WithParamInterface<PerfHistoryTestParams>,
368 public VideoDecodePerfHistoryTest {};
370 TEST_P(VideoDecodePerfHistoryParamTest, GetPerfInfo_Smooth) {
371 // NOTE: The when the DB initialization is deferred, All EXPECT_CALLs are then
372 // delayed until we db_->CompleteInitialize(). testing::InSequence enforces
373 // that EXPECT_CALLs arrive in top-to-bottom order.
374 PerfHistoryTestParams params = GetParam();
375 testing::InSequence dummy;
377 // Complete initialization in advance of API calls when not asked to defer.
378 if (!params.defer_initialize)
379 PreInitializeDB(/* success */ true);
381 // First add 2 records to the history. The second record has a higher frame
382 // rate and a higher number of dropped frames such that it is "not smooth".
383 const VideoCodecProfile kKnownProfile = VP9PROFILE_PROFILE0;
384 const gfx::Size kKownSize(100, 200);
385 const int kSmoothFrameRate = 30;
386 const int kNotSmoothFrameRate = 90;
387 const int kFramesDecoded = 1000;
388 const int kNotPowerEfficientFramesDecoded = 0;
389 // Sets the ratio of dropped frames to barely qualify as smooth.
390 const int kSmoothFramesDropped =
391 kFramesDecoded * GetMaxSmoothDroppedFramesPercent();
392 // Set the ratio of dropped frames to barely qualify as NOT smooth.
393 const int kNotSmoothFramesDropped =
394 kFramesDecoded * GetMaxSmoothDroppedFramesPercent() + 1;
397 SavePerfRecord(UkmVerifcation::kSaveTriggersUkm, kOrigin, kIsTopFrame,
398 MakeFeatures(kKnownProfile, kKownSize, kSmoothFrameRate,
399 params.key_system, params.use_hw_secure_codecs),
400 MakeTargets(kFramesDecoded, kSmoothFramesDropped,
401 kNotPowerEfficientFramesDecoded),
403 SavePerfRecord(UkmVerifcation::kSaveTriggersUkm, kOrigin, kIsTopFrame,
404 MakeFeatures(kKnownProfile, kKownSize, kNotSmoothFrameRate,
405 params.key_system, params.use_hw_secure_codecs),
406 MakeTargets(kFramesDecoded, kNotSmoothFramesDropped,
407 kNotPowerEfficientFramesDecoded),
410 // Verify perf history returns is_smooth = true for the smooth entry.
411 EXPECT_CALL(*this, MockGetPerfInfoCB(kIsSmooth, kIsNotPowerEfficient));
412 perf_history_->GetPerfInfo(
413 MakeFeaturesPtr(kKnownProfile, kKownSize, kSmoothFrameRate,
414 params.key_system, params.use_hw_secure_codecs),
415 base::BindOnce(&VideoDecodePerfHistoryParamTest::MockGetPerfInfoCB,
416 base::Unretained(this)));
418 // Verify perf history returns is_smooth = false for the NOT smooth entry.
419 EXPECT_CALL(*this, MockGetPerfInfoCB(kIsNotSmooth, kIsNotPowerEfficient));
420 perf_history_->GetPerfInfo(
421 MakeFeaturesPtr(kKnownProfile, kKownSize, kNotSmoothFrameRate,
422 params.key_system, params.use_hw_secure_codecs),
423 base::BindOnce(&VideoDecodePerfHistoryParamTest::MockGetPerfInfoCB,
424 base::Unretained(this)));
426 // Verify perf history optimistically returns is_smooth = true when no entry
427 // can be found with the given configuration.
428 const VideoCodecProfile kUnknownProfile = VP9PROFILE_PROFILE2;
429 EXPECT_CALL(*this, MockGetPerfInfoCB(kIsSmooth, kIsPowerEfficient));
430 perf_history_->GetPerfInfo(
431 MakeFeaturesPtr(kUnknownProfile, kKownSize, kNotSmoothFrameRate),
432 base::BindOnce(&VideoDecodePerfHistoryTest::MockGetPerfInfoCB,
433 base::Unretained(this)));
435 // Complete successful deferred DB initialization (see comment at top of test)
436 if (params.defer_initialize) {
437 GetFakeDB()->CompleteInitialize(true);
439 // Allow initialize-deferred API calls to complete.
440 task_environment_.RunUntilIdle();
444 TEST_P(VideoDecodePerfHistoryParamTest, GetPerfInfo_PowerEfficient) {
445 // NOTE: The when the DB initialization is deferred, All EXPECT_CALLs are then
446 // delayed until we db_->CompleteInitialize(). testing::InSequence enforces
447 // that EXPECT_CALLs arrive in top-to-bottom order.
448 PerfHistoryTestParams params = GetParam();
449 testing::InSequence dummy;
451 // Complete initialization in advance of API calls when not asked to defer.
452 if (!params.defer_initialize)
453 PreInitializeDB(/* success */ true);
455 // First add 3 records to the history:
456 // - the first has a high number of power efficiently decoded frames;
457 // - the second has a low number of power efficiently decoded frames;
458 // - the third is similar to the first with a high number of dropped frames.
459 const VideoCodecProfile kPowerEfficientProfile = VP9PROFILE_PROFILE0;
460 const VideoCodecProfile kNotPowerEfficientProfile = VP8PROFILE_ANY;
461 const gfx::Size kKownSize(100, 200);
462 const int kSmoothFrameRate = 30;
463 const int kNotSmoothFrameRate = 90;
464 const int kFramesDecoded = 1000;
465 const int kPowerEfficientFramesDecoded =
466 kFramesDecoded * kMinPowerEfficientDecodedFramePercent;
467 const int kNotPowerEfficientFramesDecoded =
468 kFramesDecoded * kMinPowerEfficientDecodedFramePercent - 1;
469 // Sets the ratio of dropped frames to barely qualify as smooth.
470 const int kSmoothFramesDropped =
471 kFramesDecoded * GetMaxSmoothDroppedFramesPercent();
472 // Set the ratio of dropped frames to barely qualify as NOT smooth.
473 const int kNotSmoothFramesDropped =
474 kFramesDecoded * GetMaxSmoothDroppedFramesPercent() + 1;
478 UkmVerifcation::kSaveTriggersUkm, kOrigin, kIsTopFrame,
479 MakeFeatures(kPowerEfficientProfile, kKownSize, kSmoothFrameRate,
480 params.key_system, params.use_hw_secure_codecs),
481 MakeTargets(kFramesDecoded, kSmoothFramesDropped,
482 kPowerEfficientFramesDecoded),
485 UkmVerifcation::kSaveTriggersUkm, kOrigin, kIsTopFrame,
486 MakeFeatures(kNotPowerEfficientProfile, kKownSize, kSmoothFrameRate,
487 params.key_system, params.use_hw_secure_codecs),
488 MakeTargets(kFramesDecoded, kSmoothFramesDropped,
489 kNotPowerEfficientFramesDecoded),
492 UkmVerifcation::kSaveTriggersUkm, kOrigin, kIsTopFrame,
493 MakeFeatures(kPowerEfficientProfile, kKownSize, kNotSmoothFrameRate,
494 params.key_system, params.use_hw_secure_codecs),
495 MakeTargets(kFramesDecoded, kNotSmoothFramesDropped,
496 kPowerEfficientFramesDecoded),
499 // Verify perf history returns is_smooth = true, is_power_efficient = true.
500 EXPECT_CALL(*this, MockGetPerfInfoCB(kIsSmooth, kIsPowerEfficient));
501 perf_history_->GetPerfInfo(
502 MakeFeaturesPtr(kPowerEfficientProfile, kKownSize, kSmoothFrameRate,
503 params.key_system, params.use_hw_secure_codecs),
504 base::BindOnce(&VideoDecodePerfHistoryTest::MockGetPerfInfoCB,
505 base::Unretained(this)));
507 // Verify perf history returns is_smooth = true, is_power_efficient = false.
508 EXPECT_CALL(*this, MockGetPerfInfoCB(kIsSmooth, kIsNotPowerEfficient));
509 perf_history_->GetPerfInfo(
510 MakeFeaturesPtr(kNotPowerEfficientProfile, kKownSize, kSmoothFrameRate,
511 params.key_system, params.use_hw_secure_codecs),
512 base::BindOnce(&VideoDecodePerfHistoryTest::MockGetPerfInfoCB,
513 base::Unretained(this)));
515 // Verify perf history returns is_smooth = false, is_power_efficient = true.
516 EXPECT_CALL(*this, MockGetPerfInfoCB(kIsNotSmooth, kIsPowerEfficient));
517 perf_history_->GetPerfInfo(
518 MakeFeaturesPtr(kPowerEfficientProfile, kKownSize, kNotSmoothFrameRate,
519 params.key_system, params.use_hw_secure_codecs),
520 base::BindOnce(&VideoDecodePerfHistoryTest::MockGetPerfInfoCB,
521 base::Unretained(this)));
523 // Verify perf history optimistically returns is_smooth = true and
524 // is_power_efficient = true when no entry can be found with the given
526 const VideoCodecProfile kUnknownProfile = VP9PROFILE_PROFILE2;
527 EXPECT_CALL(*this, MockGetPerfInfoCB(kIsSmooth, kIsPowerEfficient));
528 perf_history_->GetPerfInfo(
529 MakeFeaturesPtr(kUnknownProfile, kKownSize, kNotSmoothFrameRate,
530 params.key_system, params.use_hw_secure_codecs),
531 base::BindOnce(&VideoDecodePerfHistoryParamTest::MockGetPerfInfoCB,
532 base::Unretained(this)));
534 // Complete successful deferred DB initialization (see comment at top of test)
535 if (params.defer_initialize) {
536 GetFakeDB()->CompleteInitialize(true);
538 // Allow initialize-deferred API calls to complete.
539 task_environment_.RunUntilIdle();
543 TEST_P(VideoDecodePerfHistoryParamTest, GetPerfInfo_FailedInitialize) {
544 PerfHistoryTestParams params = GetParam();
545 // Fail initialization in advance of API calls when not asked to defer.
546 if (!params.defer_initialize)
547 PreInitializeDB(/* success */ false);
549 const VideoCodecProfile kProfile = VP9PROFILE_PROFILE0;
550 const gfx::Size kSize(100, 200);
551 const int kFrameRate = 30;
553 // When initialization fails, callback should optimistically claim both smooth
554 // and power efficient performance.
555 EXPECT_CALL(*this, MockGetPerfInfoCB(kIsSmooth, kIsPowerEfficient));
556 perf_history_->GetPerfInfo(
557 MakeFeaturesPtr(kProfile, kSize, kFrameRate, params.key_system,
558 params.use_hw_secure_codecs),
559 base::BindOnce(&VideoDecodePerfHistoryParamTest::MockGetPerfInfoCB,
560 base::Unretained(this)));
562 // Fail deferred DB initialization (see comment at top of test).
563 if (params.defer_initialize) {
564 GetFakeDB()->CompleteInitialize(false);
566 // Allow initialize-deferred API calls to complete.
567 task_environment_.RunUntilIdle();
571 TEST_P(VideoDecodePerfHistoryParamTest, AppendAndDestroyStats) {
572 // NOTE: The when the DB initialization is deferred, All EXPECT_CALLs are then
573 // delayed until we db_->CompleteInitialize(). testing::InSequence enforces
574 // that EXPECT_CALLs arrive in top-to-bottom order.
575 PerfHistoryTestParams params = GetParam();
576 testing::InSequence dummy;
578 // Complete initialization in advance of API calls when not asked to defer.
579 if (!params.defer_initialize)
580 PreInitializeDB(/* success */ true);
582 // Add a simple record to the history.
583 const VideoCodecProfile kProfile = VP9PROFILE_PROFILE0;
584 const gfx::Size kSize(100, 200);
585 const int kFrameRate = 30;
586 const int kFramesDecoded = 1000;
587 const int kManyFramesDropped = kFramesDecoded / 2;
588 const int kFramesPowerEfficient = kFramesDecoded;
590 UkmVerifcation::kSaveTriggersUkm, kOrigin, kIsTopFrame,
591 MakeFeatures(kProfile, kSize, kFrameRate, params.key_system,
592 params.use_hw_secure_codecs),
593 MakeTargets(kFramesDecoded, kManyFramesDropped, kFramesPowerEfficient),
596 // Verify its there before we ClearHistory(). Note that perf is NOT smooth.
597 EXPECT_CALL(*this, MockGetPerfInfoCB(kIsNotSmooth, kIsPowerEfficient));
598 perf_history_->GetPerfInfo(
599 MakeFeaturesPtr(kProfile, kSize, kFrameRate, params.key_system,
600 params.use_hw_secure_codecs),
601 base::BindOnce(&VideoDecodePerfHistoryParamTest::MockGetPerfInfoCB,
602 base::Unretained(this)));
604 // Initiate async clearing of history.
605 EXPECT_CALL(*this, MockOnClearedHistory());
606 perf_history_->ClearHistory(
607 base::BindOnce(&VideoDecodePerfHistoryParamTest::MockOnClearedHistory,
608 base::Unretained(this)));
610 // Verify record we added above is no longer present.
611 // SUBTLE: The PerfHistory will optimistically respond kIsSmooth when no data
612 // is found. So the signal that the entry was removed is the CB now claims
613 // "smooth" when it claimed NOT smooth just moments before.
614 EXPECT_CALL(*this, MockGetPerfInfoCB(kIsSmooth, kIsPowerEfficient));
615 perf_history_->GetPerfInfo(
616 MakeFeaturesPtr(kProfile, kSize, kFrameRate, params.key_system,
617 params.use_hw_secure_codecs),
618 base::BindOnce(&VideoDecodePerfHistoryParamTest::MockGetPerfInfoCB,
619 base::Unretained(this)));
621 // Complete successful deferred DB initialization (see comment at top of test)
622 if (params.defer_initialize) {
623 GetFakeDB()->CompleteInitialize(true);
625 // Allow initialize-deferred API calls to complete.
626 task_environment_.RunUntilIdle();
630 TEST_P(VideoDecodePerfHistoryParamTest, GetVideoDecodeStatsDB) {
631 // NOTE: The when the DB initialization is deferred, All EXPECT_CALLs are then
632 // delayed until we db_->CompleteInitialize(). testing::InSequence enforces
633 // that EXPECT_CALLs arrive in top-to-bottom order.
634 PerfHistoryTestParams params = GetParam();
635 testing::InSequence dummy;
637 // Complete initialization in advance of API calls when not asked to defer.
638 if (!params.defer_initialize)
639 PreInitializeDB(/* success */ true);
641 // Request a pointer to VideoDecodeStatsDB and verify the callback.
642 EXPECT_CALL(*this, MockGetVideoDecodeStatsDBCB(_))
643 .WillOnce([&](const auto* db_ptr) {
644 // Not able to simply use a matcher because the DB does not exist at the
645 // time we setup the EXPECT_CALL.
646 EXPECT_EQ(GetFakeDB(), db_ptr);
649 perf_history_->GetVideoDecodeStatsDB(
650 base::BindOnce(&VideoDecodePerfHistoryTest::MockGetVideoDecodeStatsDBCB,
651 base::Unretained(this)));
653 task_environment_.RunUntilIdle();
655 // Complete successful deferred DB initialization (see comment at top of test)
656 if (params.defer_initialize) {
657 GetFakeDB()->CompleteInitialize(true);
659 // Allow initialize-deferred API calls to complete.
660 task_environment_.RunUntilIdle();
664 TEST_P(VideoDecodePerfHistoryParamTest,
665 GetVideoDecodeStatsDB_FailedInitialize) {
666 // NOTE: The when the DB initialization is deferred, All EXPECT_CALLs are then
667 // delayed until we db_->CompleteInitialize(). testing::InSequence enforces
668 // that EXPECT_CALLs arrive in top-to-bottom order.
669 PerfHistoryTestParams params = GetParam();
670 testing::InSequence dummy;
672 // Complete initialization in advance of API calls when not asked to defer.
673 if (!params.defer_initialize)
674 PreInitializeDB(/* success */ false);
676 // Request a pointer to VideoDecodeStatsDB and verify the callback provides
677 // a nullptr due to failed initialization.
678 EXPECT_CALL(*this, MockGetVideoDecodeStatsDBCB(IsNull()));
679 perf_history_->GetVideoDecodeStatsDB(
680 base::BindOnce(&VideoDecodePerfHistoryTest::MockGetVideoDecodeStatsDBCB,
681 base::Unretained(this)));
683 task_environment_.RunUntilIdle();
685 // Complete failed deferred DB initialization (see comment at top of test)
686 if (params.defer_initialize) {
687 GetFakeDB()->CompleteInitialize(false);
689 // Allow initialize-deferred API calls to complete.
690 task_environment_.RunUntilIdle();
694 TEST_P(VideoDecodePerfHistoryParamTest, FailedDatabaseGetForAppend) {
695 // NOTE: The when the DB initialization is deferred, All EXPECT_CALLs are then
696 // delayed until we db_->CompleteInitialize(). testing::InSequence enforces
697 // that EXPECT_CALLs arrive in top-to-bottom order.
698 PerfHistoryTestParams params = GetParam();
700 // Complete initialization in advance of API calls when not asked to defer.
701 if (!params.defer_initialize)
702 PreInitializeDB(/* success */ true);
704 // Create a record that is neither smooth nor power efficient. After we fail
705 // to save this record we should find smooth = power_efficient = true (the
706 // default for no-data-found).
707 const VideoCodecProfile kProfile = VP9PROFILE_PROFILE0;
708 const gfx::Size kSize(100, 200);
709 const int kFrameRate = 30;
710 const int kFramesDecoded = 1000;
711 const int kFramesDropped =
712 kFramesDecoded * GetMaxSmoothDroppedFramesPercent() + 1;
713 const int kFramesPowerEfficient = 0;
715 // Fail the "get" step of the save (we always get existing stats prior to
716 // save for UKM reporting).
717 GetFakeDB()->set_fail_next_get(true);
719 // Attempt (and fail) the save. UKM report depends on successful retrieval
720 // of stats from the DB, so no UKM reporting should occur here.
722 UkmVerifcation::kNoUkmsAdded, kOrigin, kIsTopFrame,
723 MakeFeatures(kProfile, kSize, kFrameRate, params.key_system,
724 params.use_hw_secure_codecs),
725 MakeTargets(kFramesDecoded, kFramesDropped, kFramesPowerEfficient),
728 // Verify perf history still returns is_smooth = power_efficient = true since
729 // no data was successfully saved for the given configuration.
730 EXPECT_CALL(*this, MockGetPerfInfoCB(kIsSmooth, kIsPowerEfficient));
731 perf_history_->GetPerfInfo(
732 MakeFeaturesPtr(kProfile, kSize, kFrameRate, params.key_system,
733 params.use_hw_secure_codecs),
734 base::BindOnce(&VideoDecodePerfHistoryTest::MockGetPerfInfoCB,
735 base::Unretained(this)));
737 // Complete successful deferred DB initialization (see comment at top of test)
738 if (params.defer_initialize) {
739 GetFakeDB()->CompleteInitialize(true);
741 // Allow initialize-deferred API calls to complete.
742 task_environment_.RunUntilIdle();
746 TEST_P(VideoDecodePerfHistoryParamTest, FailedDatabaseAppend) {
747 // NOTE: The when the DB initialization is deferred, All EXPECT_CALLs are then
748 // delayed until we db_->CompleteInitialize(). testing::InSequence enforces
749 // that EXPECT_CALLs arrive in top-to-bottom order.
750 PerfHistoryTestParams params = GetParam();
752 // Complete initialization in advance of API calls when not asked to defer.
753 if (!params.defer_initialize)
754 PreInitializeDB(/* success */ true);
756 // Force the DB to fail on the next append.
757 GetFakeDB()->set_fail_next_append(true);
759 // Create a record that is neither smooth nor power efficient. After we fail
760 // to save this record we should find smooth = power_efficient = true (the
761 // default for no-data-found).
762 const VideoCodecProfile kProfile = VP9PROFILE_PROFILE0;
763 const gfx::Size kSize(100, 200);
764 const int kFrameRate = 30;
765 const int kFramesDecoded = 1000;
766 const int kFramesDropped =
767 kFramesDecoded * GetMaxSmoothDroppedFramesPercent() + 1;
768 const int kFramesPowerEfficient = 0;
770 // Attempt (and fail) the save. Note that we still expect UKM to be reported
771 // because we successfully retrieved stats from the DB, we just fail to append
772 // the new stats. UKM reporting occurs between retrieval and appending.
774 UkmVerifcation::kSaveTriggersUkm, kOrigin, kIsTopFrame,
775 MakeFeatures(kProfile, kSize, kFrameRate, params.key_system,
776 params.use_hw_secure_codecs),
777 MakeTargets(kFramesDecoded, kFramesDropped, kFramesPowerEfficient),
780 // Verify perf history still returns is_smooth = power_efficient = true since
781 // no data was successfully saved for the given configuration.
782 EXPECT_CALL(*this, MockGetPerfInfoCB(kIsSmooth, kIsPowerEfficient));
783 perf_history_->GetPerfInfo(
784 MakeFeaturesPtr(kProfile, kSize, kFrameRate, params.key_system,
785 params.use_hw_secure_codecs),
786 base::BindOnce(&VideoDecodePerfHistoryTest::MockGetPerfInfoCB,
787 base::Unretained(this)));
789 // Complete successful deferred DB initialization (see comment at top of test)
790 if (params.defer_initialize) {
791 GetFakeDB()->CompleteInitialize(true);
793 // Allow initialize-deferred API calls to complete.
794 task_environment_.RunUntilIdle();
798 // Tests that the feature parameters are used to override constants for the
799 // Media Capabilities feature.
800 // To avoid race conditions when setting the parameter, the test sets it when
801 // starting and make sure the values recorded to the DB wouldn't be smooth per
802 // the default value.
803 TEST_P(VideoDecodePerfHistoryParamTest,
804 SmoothThresholdFinchOverride_NoEmeOverride) {
805 base::test::ScopedFeatureList scoped_feature_list;
807 // EME and non EME threshold should initially be the same (neither is
809 double previous_smooth_dropped_frames_threshold =
810 GetMaxSmoothDroppedFramesPercent(false /* is_eme */);
811 EXPECT_EQ(previous_smooth_dropped_frames_threshold,
812 GetMaxSmoothDroppedFramesPercent(true /* is_eme */));
814 double new_smooth_dropped_frames_threshold =
815 previous_smooth_dropped_frames_threshold / 2;
817 ASSERT_LT(new_smooth_dropped_frames_threshold,
818 previous_smooth_dropped_frames_threshold);
820 // Override field trial.
821 base::FieldTrialParams trial_params;
823 [VideoDecodePerfHistory::kMaxSmoothDroppedFramesPercentParamName] =
824 base::NumberToString(new_smooth_dropped_frames_threshold);
825 scoped_feature_list.InitAndEnableFeatureWithParameters(
826 media::kMediaCapabilitiesWithParameters, trial_params);
828 EXPECT_EQ(GetFieldTrialParams(), trial_params);
830 // Non EME threshold is overridden.
831 EXPECT_EQ(new_smooth_dropped_frames_threshold,
832 GetMaxSmoothDroppedFramesPercent(false /* is_eme */));
834 // EME threshold is also implicitly overridden (we didn't set an EME specific
835 // value, so it should defer to the non-EME override).
836 EXPECT_EQ(new_smooth_dropped_frames_threshold,
837 GetMaxSmoothDroppedFramesPercent(true /* is_eme */));
839 // NOTE: The when the DB initialization is deferred, All EXPECT_CALLs are then
840 // delayed until we db_->CompleteInitialize(). testing::InSequence enforces
841 // that EXPECT_CALLs arrive in top-to-bottom order.
842 PerfHistoryTestParams params = GetParam();
843 testing::InSequence dummy;
845 // Complete initialization in advance of API calls when not asked to defer.
846 if (!params.defer_initialize)
847 PreInitializeDB(/* success */ true);
849 // First add 2 records to the history. The second record has a higher frame
850 // rate and a higher number of dropped frames such that it is "not smooth".
851 const VideoCodecProfile kKnownProfile = VP9PROFILE_PROFILE0;
852 const gfx::Size kKownSize(100, 200);
853 const int kSmoothFrameRatePrevious = 30;
854 const int kSmoothFrameRateNew = 90;
855 const int kFramesDecoded = 1000;
856 const int kNotPowerEfficientFramesDecoded = 0;
858 // Sets the ratio of dropped frames to qualify as smooth per the default
860 const int kSmoothFramesDroppedPrevious =
861 kFramesDecoded * previous_smooth_dropped_frames_threshold;
862 // Sets the ratio of dropped frames to quality as smooth per the new
864 const int kSmoothFramesDroppedNew =
865 kFramesDecoded * new_smooth_dropped_frames_threshold;
869 UkmVerifcation::kSaveTriggersUkm, kOrigin, kIsTopFrame,
870 MakeFeatures(kKnownProfile, kKownSize, kSmoothFrameRatePrevious,
871 params.key_system, params.use_hw_secure_codecs),
872 MakeTargets(kFramesDecoded, kSmoothFramesDroppedPrevious,
873 kNotPowerEfficientFramesDecoded),
876 SavePerfRecord(UkmVerifcation::kSaveTriggersUkm, kOrigin, kIsTopFrame,
877 MakeFeatures(kKnownProfile, kKownSize, kSmoothFrameRateNew,
878 params.key_system, params.use_hw_secure_codecs),
879 MakeTargets(kFramesDecoded, kSmoothFramesDroppedNew,
880 kNotPowerEfficientFramesDecoded),
883 // Verify perf history returns is_smooth = false for entry that would be
884 // smooth per previous smooth threshold.
885 EXPECT_CALL(*this, MockGetPerfInfoCB(kIsNotSmooth, kIsNotPowerEfficient));
886 perf_history_->GetPerfInfo(
887 MakeFeaturesPtr(kKnownProfile, kKownSize, kSmoothFrameRatePrevious,
888 params.key_system, params.use_hw_secure_codecs),
889 base::BindOnce(&VideoDecodePerfHistoryParamTest::MockGetPerfInfoCB,
890 base::Unretained(this)));
892 // Verify perf history returns is_smooth = true for entry that would be
893 // smooth per new smooth theshold.
894 EXPECT_CALL(*this, MockGetPerfInfoCB(kIsSmooth, kIsNotPowerEfficient));
895 perf_history_->GetPerfInfo(
896 MakeFeaturesPtr(kKnownProfile, kKownSize, kSmoothFrameRateNew,
897 params.key_system, params.use_hw_secure_codecs),
898 base::BindOnce(&VideoDecodePerfHistoryParamTest::MockGetPerfInfoCB,
899 base::Unretained(this)));
901 // Complete successful deferred DB initialization (see comment at top of test)
902 if (params.defer_initialize) {
903 GetFakeDB()->CompleteInitialize(true);
905 // Allow initialize-deferred API calls to complete.
906 task_environment_.RunUntilIdle();
910 TEST_P(VideoDecodePerfHistoryParamTest,
911 SmoothThresholdFinchOverride_WithEmeOverride) {
912 base::test::ScopedFeatureList scoped_feature_list;
914 // EME and non EME threshold should initially be the same (neither is
916 double previous_smooth_dropped_frames_threshold =
917 GetMaxSmoothDroppedFramesPercent(false /* is_eme */);
918 EXPECT_EQ(previous_smooth_dropped_frames_threshold,
919 GetMaxSmoothDroppedFramesPercent(true /* is_eme */));
921 double new_CLEAR_smooth_dropped_frames_threshold =
922 previous_smooth_dropped_frames_threshold / 2;
923 double new_EME_smooth_dropped_frames_threshold =
924 previous_smooth_dropped_frames_threshold / 3;
926 ASSERT_LT(new_CLEAR_smooth_dropped_frames_threshold,
927 previous_smooth_dropped_frames_threshold);
928 ASSERT_LT(new_EME_smooth_dropped_frames_threshold,
929 new_CLEAR_smooth_dropped_frames_threshold);
931 // Override field trial.
932 base::FieldTrialParams trial_params;
934 [VideoDecodePerfHistory::kMaxSmoothDroppedFramesPercentParamName] =
935 base::NumberToString(new_CLEAR_smooth_dropped_frames_threshold);
937 [VideoDecodePerfHistory::kEmeMaxSmoothDroppedFramesPercentParamName] =
938 base::NumberToString(new_EME_smooth_dropped_frames_threshold);
940 scoped_feature_list.InitAndEnableFeatureWithParameters(
941 media::kMediaCapabilitiesWithParameters, trial_params);
943 EXPECT_EQ(GetFieldTrialParams(), trial_params);
945 // Both thresholds should be overridden.
946 EXPECT_EQ(new_CLEAR_smooth_dropped_frames_threshold,
947 GetMaxSmoothDroppedFramesPercent(false /* is_eme */));
948 EXPECT_EQ(new_EME_smooth_dropped_frames_threshold,
949 GetMaxSmoothDroppedFramesPercent(true /* is_eme */));
951 // NOTE: The when the DB initialization is deferred, All EXPECT_CALLs are then
952 // delayed until we db_->CompleteInitialize(). testing::InSequence enforces
953 // that EXPECT_CALLs arrive in top-to-bottom order.
954 PerfHistoryTestParams params = GetParam();
955 testing::InSequence dummy;
957 // Complete initialization in advance of API calls when not asked to defer.
958 if (!params.defer_initialize)
959 PreInitializeDB(/* success */ true);
961 // First add 2 records to the history. The second record has a higher frame
962 // rate and a higher number of dropped frames such that it is "not smooth".
963 const VideoCodecProfile kKnownProfile = VP9PROFILE_PROFILE0;
964 const gfx::Size kKownSize(100, 200);
965 const int kSmoothFrameRatePrevious = 30;
966 const int kSmoothFrameRateNew = 90;
967 const int kFramesDecoded = 1000;
968 const int kNotPowerEfficientFramesDecoded = 0;
970 // Sets the ratio of dropped frames to qualify as NOT smooth. For CLEAR, use
971 // the previous smooth threshold. For EME, use the new CLEAR threshold to
972 // verify that the EME threshold is lower than CLEAR.
973 const int kSmoothFramesDroppedPrevious =
974 params.key_system.empty()
975 ? kFramesDecoded * previous_smooth_dropped_frames_threshold
976 : kFramesDecoded * new_CLEAR_smooth_dropped_frames_threshold;
977 // Sets the ratio of dropped frames to quality as smooth per the new threshold
978 // depending on whether the key indicates this record is EME.
979 const int kSmoothFramesDroppedNew =
980 params.key_system.empty()
981 ? kFramesDecoded * new_CLEAR_smooth_dropped_frames_threshold
982 : kFramesDecoded * new_EME_smooth_dropped_frames_threshold;
986 UkmVerifcation::kSaveTriggersUkm, kOrigin, kIsTopFrame,
987 MakeFeatures(kKnownProfile, kKownSize, kSmoothFrameRatePrevious,
988 params.key_system, params.use_hw_secure_codecs),
989 MakeTargets(kFramesDecoded, kSmoothFramesDroppedPrevious,
990 kNotPowerEfficientFramesDecoded),
993 SavePerfRecord(UkmVerifcation::kSaveTriggersUkm, kOrigin, kIsTopFrame,
994 MakeFeatures(kKnownProfile, kKownSize, kSmoothFrameRateNew,
995 params.key_system, params.use_hw_secure_codecs),
996 MakeTargets(kFramesDecoded, kSmoothFramesDroppedNew,
997 kNotPowerEfficientFramesDecoded),
1000 // Verify perf history returns is_smooth = false for entry that would be
1001 // smooth per previous smooth threshold.
1002 EXPECT_CALL(*this, MockGetPerfInfoCB(kIsNotSmooth, kIsNotPowerEfficient));
1003 perf_history_->GetPerfInfo(
1004 MakeFeaturesPtr(kKnownProfile, kKownSize, kSmoothFrameRatePrevious,
1005 params.key_system, params.use_hw_secure_codecs),
1006 base::BindOnce(&VideoDecodePerfHistoryParamTest::MockGetPerfInfoCB,
1007 base::Unretained(this)));
1009 // Verify perf history returns is_smooth = true for entry that would be
1010 // smooth per new smooth threshold.
1011 EXPECT_CALL(*this, MockGetPerfInfoCB(kIsSmooth, kIsNotPowerEfficient));
1012 perf_history_->GetPerfInfo(
1013 MakeFeaturesPtr(kKnownProfile, kKownSize, kSmoothFrameRateNew,
1014 params.key_system, params.use_hw_secure_codecs),
1015 base::BindOnce(&VideoDecodePerfHistoryParamTest::MockGetPerfInfoCB,
1016 base::Unretained(this)));
1018 // Complete successful deferred DB initialization (see comment at top of test)
1019 if (params.defer_initialize) {
1020 GetFakeDB()->CompleteInitialize(true);
1022 // Allow initialize-deferred API calls to complete.
1023 task_environment_.RunUntilIdle();
1027 const PerfHistoryTestParams kPerfHistoryTestParams[] = {
1030 {true, "com.widevine.alpha", false},
1031 {true, "com.widevine.alpha", true},
1034 INSTANTIATE_TEST_SUITE_P(VaryDBInitTiming,
1035 VideoDecodePerfHistoryParamTest,
1036 ::testing::ValuesIn(kPerfHistoryTestParams));
1039 // The following test are not parameterized. They instead always hard code
1040 // deferred initialization.
1043 TEST_F(VideoDecodePerfHistoryTest, ClearHistoryTriggersSuccessfulInitialize) {
1044 // Clear the DB. Completion callback shouldn't fire until initialize
1046 EXPECT_CALL(*this, MockOnClearedHistory()).Times(0);
1047 perf_history_->ClearHistory(
1048 base::BindOnce(&VideoDecodePerfHistoryParamTest::MockOnClearedHistory,
1049 base::Unretained(this)));
1051 // Give completion callback a chance to fire. Confirm it did not fire.
1052 task_environment_.RunUntilIdle();
1053 testing::Mock::VerifyAndClearExpectations(this);
1055 // Expect completion callback after we successfully initialize.
1056 EXPECT_CALL(*this, MockOnClearedHistory());
1057 GetFakeDB()->CompleteInitialize(true);
1059 // Give deferred callback a chance to fire.
1060 task_environment_.RunUntilIdle();
1063 TEST_F(VideoDecodePerfHistoryTest, ClearHistoryTriggersFailedInitialize) {
1064 // Clear the DB. Completion callback shouldn't fire until initialize
1066 EXPECT_CALL(*this, MockOnClearedHistory()).Times(0);
1067 perf_history_->ClearHistory(
1068 base::BindOnce(&VideoDecodePerfHistoryParamTest::MockOnClearedHistory,
1069 base::Unretained(this)));
1071 // Give completion callback a chance to fire. Confirm it did not fire.
1072 task_environment_.RunUntilIdle();
1073 testing::Mock::VerifyAndClearExpectations(this);
1075 // Expect completion callback after completing initialize. "Failure" is still
1076 // a form of completion.
1077 EXPECT_CALL(*this, MockOnClearedHistory());
1078 GetFakeDB()->CompleteInitialize(false);
1080 // Give deferred callback a chance to fire.
1081 task_environment_.RunUntilIdle();
1084 } // namespace media