Commit 0f8d7b3c authored by Dale Curtis's avatar Dale Curtis Committed by Commit Bot

Add decoded and dropped frames to watch time reporting.

Since we have been unable to join on the VideoDecoderStats table due to
processing times and lack of cross correlation per resolution, metrics
leadership has asked we add this field directly to watch time
calculations.

This adds a new callback to the WatchTimeReporter for pulling the
PipelineStatistics object (just like VideoDecodeStatsReporter does)
and uses that to collect an initial and then subsequent relative values
as watch time reporting ticks along.

BUG=1002567
TEST=updated unittests
R=chcunningham

Change-Id: I3cf3a3bf779021e9677d8b511a8066efdeeb492b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1869341
Commit-Queue: Dale Curtis <dalecurtis@chromium.org>
Reviewed-by: default avatarRobert Kaplow <rkaplow@chromium.org>
Reviewed-by: default avatarChrome Cunningham <chcunningham@chromium.org>
Reviewed-by: default avatarKinuko Yasuda <kinuko@chromium.org>
Cr-Commit-Position: refs/heads/master@{#709240}
parent fc17d782
...@@ -21,13 +21,13 @@ class WatchTimeInterceptor : public mojom::WatchTimeRecorder { ...@@ -21,13 +21,13 @@ class WatchTimeInterceptor : public mojom::WatchTimeRecorder {
MOCK_METHOD2(RecordWatchTime, void(WatchTimeKey, base::TimeDelta)); MOCK_METHOD2(RecordWatchTime, void(WatchTimeKey, base::TimeDelta));
MOCK_METHOD1(FinalizeWatchTime, void(const std::vector<WatchTimeKey>&)); MOCK_METHOD1(FinalizeWatchTime, void(const std::vector<WatchTimeKey>&));
MOCK_METHOD1(OnError, void(PipelineStatus)); MOCK_METHOD1(OnError, void(PipelineStatus));
MOCK_METHOD1(UpdateUnderflowCount, void(int32_t count));
MOCK_METHOD2(UpdateUnderflowDuration, void(int32_t, base::TimeDelta));
MOCK_METHOD1(
UpdateSecondaryProperties,
void(mojom::SecondaryPlaybackPropertiesPtr secondary_properties));
MOCK_METHOD1(SetAutoplayInitiated, void(bool)); MOCK_METHOD1(SetAutoplayInitiated, void(bool));
MOCK_METHOD1(OnDurationChanged, void(base::TimeDelta)); MOCK_METHOD1(OnDurationChanged, void(base::TimeDelta));
MOCK_METHOD2(UpdateVideoDecodeStats, void(uint32_t, uint32_t));
MOCK_METHOD1(UpdateUnderflowCount, void(int32_t));
MOCK_METHOD2(UpdateUnderflowDuration, void(int32_t, base::TimeDelta));
MOCK_METHOD1(UpdateSecondaryProperties,
void(mojom::SecondaryPlaybackPropertiesPtr));
}; };
class WatchTimeComponentTest : public testing::Test { class WatchTimeComponentTest : public testing::Test {
......
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
#include "base/bind.h" #include "base/bind.h"
#include "base/power_monitor/power_monitor.h" #include "base/power_monitor/power_monitor.h"
#include "base/time/time.h" #include "base/time/time.h"
#include "media/base/pipeline_status.h"
#include "media/base/timestamp_constants.h" #include "media/base/timestamp_constants.h"
#include "media/base/watch_time_keys.h" #include "media/base/watch_time_keys.h"
...@@ -47,6 +48,7 @@ WatchTimeReporter::WatchTimeReporter( ...@@ -47,6 +48,7 @@ WatchTimeReporter::WatchTimeReporter(
mojom::PlaybackPropertiesPtr properties, mojom::PlaybackPropertiesPtr properties,
const gfx::Size& natural_size, const gfx::Size& natural_size,
GetMediaTimeCB get_media_time_cb, GetMediaTimeCB get_media_time_cb,
GetPipelineStatsCB get_pipeline_stats_cb,
mojom::MediaMetricsProvider* provider, mojom::MediaMetricsProvider* provider,
scoped_refptr<base::SequencedTaskRunner> task_runner, scoped_refptr<base::SequencedTaskRunner> task_runner,
const base::TickClock* tick_clock) const base::TickClock* tick_clock)
...@@ -55,6 +57,7 @@ WatchTimeReporter::WatchTimeReporter( ...@@ -55,6 +57,7 @@ WatchTimeReporter::WatchTimeReporter(
false /* is_muted */, false /* is_muted */,
natural_size, natural_size,
std::move(get_media_time_cb), std::move(get_media_time_cb),
std::move(get_pipeline_stats_cb),
provider, provider,
task_runner, task_runner,
tick_clock) {} tick_clock) {}
...@@ -65,6 +68,7 @@ WatchTimeReporter::WatchTimeReporter( ...@@ -65,6 +68,7 @@ WatchTimeReporter::WatchTimeReporter(
bool is_muted, bool is_muted,
const gfx::Size& natural_size, const gfx::Size& natural_size,
GetMediaTimeCB get_media_time_cb, GetMediaTimeCB get_media_time_cb,
GetPipelineStatsCB get_pipeline_stats_cb,
mojom::MediaMetricsProvider* provider, mojom::MediaMetricsProvider* provider,
scoped_refptr<base::SequencedTaskRunner> task_runner, scoped_refptr<base::SequencedTaskRunner> task_runner,
const base::TickClock* tick_clock) const base::TickClock* tick_clock)
...@@ -72,9 +76,11 @@ WatchTimeReporter::WatchTimeReporter( ...@@ -72,9 +76,11 @@ WatchTimeReporter::WatchTimeReporter(
is_background_(is_background), is_background_(is_background),
is_muted_(is_muted), is_muted_(is_muted),
get_media_time_cb_(std::move(get_media_time_cb)), get_media_time_cb_(std::move(get_media_time_cb)),
get_pipeline_stats_cb_(std::move(get_pipeline_stats_cb)),
reporting_timer_(tick_clock), reporting_timer_(tick_clock),
natural_size_(natural_size) { natural_size_(natural_size) {
DCHECK(get_media_time_cb_); DCHECK(get_media_time_cb_);
DCHECK(get_pipeline_stats_cb_);
DCHECK(properties_->has_audio || properties_->has_video); DCHECK(properties_->has_audio || properties_->has_video);
DCHECK_EQ(is_background, properties_->is_background); DCHECK_EQ(is_background, properties_->is_background);
...@@ -114,7 +120,8 @@ WatchTimeReporter::WatchTimeReporter( ...@@ -114,7 +120,8 @@ WatchTimeReporter::WatchTimeReporter(
prop_copy->is_background = true; prop_copy->is_background = true;
background_reporter_.reset(new WatchTimeReporter( background_reporter_.reset(new WatchTimeReporter(
std::move(prop_copy), true /* is_background */, false /* is_muted */, std::move(prop_copy), true /* is_background */, false /* is_muted */,
natural_size_, get_media_time_cb_, provider, task_runner, tick_clock)); natural_size_, get_media_time_cb_, get_pipeline_stats_cb_, provider,
task_runner, tick_clock));
// Muted watch time is only reported for audio+video playback. // Muted watch time is only reported for audio+video playback.
if (!properties_->has_video || !properties_->has_audio) if (!properties_->has_video || !properties_->has_audio)
...@@ -126,7 +133,8 @@ WatchTimeReporter::WatchTimeReporter( ...@@ -126,7 +133,8 @@ WatchTimeReporter::WatchTimeReporter(
prop_copy->is_muted = true; prop_copy->is_muted = true;
muted_reporter_.reset(new WatchTimeReporter( muted_reporter_.reset(new WatchTimeReporter(
std::move(prop_copy), false /* is_background */, true /* is_muted */, std::move(prop_copy), false /* is_background */, true /* is_muted */,
natural_size_, get_media_time_cb_, provider, task_runner, tick_clock)); natural_size_, get_media_time_cb_, get_pipeline_stats_cb_, provider,
task_runner, tick_clock));
} }
WatchTimeReporter::~WatchTimeReporter() { WatchTimeReporter::~WatchTimeReporter() {
...@@ -404,6 +412,11 @@ void WatchTimeReporter::MaybeStartReportingTimer( ...@@ -404,6 +412,11 @@ void WatchTimeReporter::MaybeStartReportingTimer(
if (!should_start) if (!should_start)
return; return;
if (properties_->has_video) {
initial_stats_ = get_pipeline_stats_cb_.Run();
last_stats_ = PipelineStatistics();
}
ResetUnderflowState(); ResetUnderflowState();
base_component_->OnReportingStarted(start_timestamp); base_component_->OnReportingStarted(start_timestamp);
power_component_->OnReportingStarted(start_timestamp); power_component_->OnReportingStarted(start_timestamp);
...@@ -492,6 +505,24 @@ void WatchTimeReporter::RecordWatchTime() { ...@@ -492,6 +505,24 @@ void WatchTimeReporter::RecordWatchTime() {
} }
} }
if (properties_->has_video) {
auto stats = get_pipeline_stats_cb_.Run();
DCHECK_GE(stats.video_frames_decoded, initial_stats_.video_frames_decoded);
DCHECK_GE(stats.video_frames_dropped, initial_stats_.video_frames_dropped);
// Offset the stats based on where they were when we started reporting.
stats.video_frames_decoded -= initial_stats_.video_frames_decoded;
stats.video_frames_dropped -= initial_stats_.video_frames_dropped;
// Only send updates.
if (last_stats_.video_frames_decoded != stats.video_frames_decoded ||
last_stats_.video_frames_dropped != stats.video_frames_dropped) {
recorder_->UpdateVideoDecodeStats(stats.video_frames_decoded,
stats.video_frames_dropped);
last_stats_ = stats;
}
}
// Record watch time for all components. // Record watch time for all components.
base_component_->RecordWatchTime(current_timestamp); base_component_->RecordWatchTime(current_timestamp);
power_component_->RecordWatchTime(current_timestamp); power_component_->RecordWatchTime(current_timestamp);
......
...@@ -59,6 +59,7 @@ class MEDIA_BLINK_EXPORT WatchTimeReporter : base::PowerObserver { ...@@ -59,6 +59,7 @@ class MEDIA_BLINK_EXPORT WatchTimeReporter : base::PowerObserver {
public: public:
using DisplayType = blink::WebMediaPlayer::DisplayType; using DisplayType = blink::WebMediaPlayer::DisplayType;
using GetMediaTimeCB = base::RepeatingCallback<base::TimeDelta(void)>; using GetMediaTimeCB = base::RepeatingCallback<base::TimeDelta(void)>;
using GetPipelineStatsCB = base::Callback<PipelineStatistics(void)>;
// Constructor for the reporter; all requested metadata should be fully known // Constructor for the reporter; all requested metadata should be fully known
// before attempting construction as incorrect values will result in the wrong // before attempting construction as incorrect values will result in the wrong
...@@ -82,6 +83,7 @@ class MEDIA_BLINK_EXPORT WatchTimeReporter : base::PowerObserver { ...@@ -82,6 +83,7 @@ class MEDIA_BLINK_EXPORT WatchTimeReporter : base::PowerObserver {
WatchTimeReporter(mojom::PlaybackPropertiesPtr properties, WatchTimeReporter(mojom::PlaybackPropertiesPtr properties,
const gfx::Size& natural_size, const gfx::Size& natural_size,
GetMediaTimeCB get_media_time_cb, GetMediaTimeCB get_media_time_cb,
GetPipelineStatsCB get_pipeline_stats_cb,
mojom::MediaMetricsProvider* provider, mojom::MediaMetricsProvider* provider,
scoped_refptr<base::SequencedTaskRunner> task_runner, scoped_refptr<base::SequencedTaskRunner> task_runner,
const base::TickClock* tick_clock = nullptr); const base::TickClock* tick_clock = nullptr);
...@@ -160,6 +162,7 @@ class MEDIA_BLINK_EXPORT WatchTimeReporter : base::PowerObserver { ...@@ -160,6 +162,7 @@ class MEDIA_BLINK_EXPORT WatchTimeReporter : base::PowerObserver {
bool is_muted, bool is_muted,
const gfx::Size& natural_size, const gfx::Size& natural_size,
GetMediaTimeCB get_media_time_cb, GetMediaTimeCB get_media_time_cb,
GetPipelineStatsCB get_pipeline_stats_cb,
mojom::MediaMetricsProvider* provider, mojom::MediaMetricsProvider* provider,
scoped_refptr<base::SequencedTaskRunner> task_runner, scoped_refptr<base::SequencedTaskRunner> task_runner,
const base::TickClock* tick_clock); const base::TickClock* tick_clock);
...@@ -202,6 +205,7 @@ class MEDIA_BLINK_EXPORT WatchTimeReporter : base::PowerObserver { ...@@ -202,6 +205,7 @@ class MEDIA_BLINK_EXPORT WatchTimeReporter : base::PowerObserver {
const bool is_background_; const bool is_background_;
const bool is_muted_; const bool is_muted_;
const GetMediaTimeCB get_media_time_cb_; const GetMediaTimeCB get_media_time_cb_;
const GetPipelineStatsCB get_pipeline_stats_cb_;
mojom::WatchTimeRecorderPtr recorder_; mojom::WatchTimeRecorderPtr recorder_;
// The amount of time between each UpdateWatchTime(); this is the frequency by // The amount of time between each UpdateWatchTime(); this is the frequency by
...@@ -233,6 +237,9 @@ class MEDIA_BLINK_EXPORT WatchTimeReporter : base::PowerObserver { ...@@ -233,6 +237,9 @@ class MEDIA_BLINK_EXPORT WatchTimeReporter : base::PowerObserver {
}; };
std::vector<UnderflowEvent> pending_underflow_events_; std::vector<UnderflowEvent> pending_underflow_events_;
PipelineStatistics initial_stats_;
PipelineStatistics last_stats_;
// The various components making up WatchTime. If the |base_component_| is // The various components making up WatchTime. If the |base_component_| is
// finalized, all reporting will be stopped and finalized using its ending // finalized, all reporting will be stopped and finalized using its ending
// timestamp. // timestamp.
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
#include "base/threading/thread_task_runner_handle.h" #include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h" #include "base/time/time.h"
#include "media/base/mock_media_log.h" #include "media/base/mock_media_log.h"
#include "media/base/video_codecs.h" #include "media/base/pipeline_status.h"
#include "media/base/watch_time_keys.h" #include "media/base/watch_time_keys.h"
#include "media/blink/watch_time_reporter.h" #include "media/blink/watch_time_reporter.h"
#include "media/mojo/mojom/media_metrics_provider.mojom.h" #include "media/mojo/mojom/media_metrics_provider.mojom.h"
...@@ -223,6 +223,12 @@ class WatchTimeReporterTest ...@@ -223,6 +223,12 @@ class WatchTimeReporterTest
parent_->OnDurationChanged(duration); parent_->OnDurationChanged(duration);
} }
void UpdateVideoDecodeStats(uint32_t video_frames_decoded,
uint32_t video_frames_dropped) override {
parent_->OnUpdateVideoDecodeStats(video_frames_decoded,
video_frames_dropped);
}
private: private:
WatchTimeReporterTest* parent_; WatchTimeReporterTest* parent_;
...@@ -299,10 +305,18 @@ class WatchTimeReporterTest ...@@ -299,10 +305,18 @@ class WatchTimeReporterTest
initial_video_size, initial_video_size,
base::BindRepeating(&WatchTimeReporterTest::GetCurrentMediaTime, base::BindRepeating(&WatchTimeReporterTest::GetCurrentMediaTime,
base::Unretained(this)), base::Unretained(this)),
base::BindRepeating(&WatchTimeReporterTest::GetPipelineStatistics,
base::Unretained(this)),
&fake_metrics_provider_, &fake_metrics_provider_,
blink::scheduler::GetSequencedTaskRunnerForTesting(), blink::scheduler::GetSequencedTaskRunnerForTesting(),
task_runner_->GetMockTickClock())); task_runner_->GetMockTickClock()));
reporting_interval_ = wtr_->reporting_interval_; reporting_interval_ = wtr_->reporting_interval_;
// Most tests don't care about this.
EXPECT_CALL(*this, GetPipelineStatistics())
.WillRepeatedly(testing::Return(PipelineStatistics()));
EXPECT_CALL(*this, OnUpdateVideoDecodeStats(_, _))
.Times(testing::AnyNumber());
} }
void CycleReportingTimer() { void CycleReportingTimer() {
...@@ -593,6 +607,7 @@ class WatchTimeReporterTest ...@@ -593,6 +607,7 @@ class WatchTimeReporterTest
} }
MOCK_METHOD0(GetCurrentMediaTime, base::TimeDelta()); MOCK_METHOD0(GetCurrentMediaTime, base::TimeDelta());
MOCK_METHOD0(GetPipelineStatistics, PipelineStatistics());
MOCK_METHOD0(OnWatchTimeFinalized, void(void)); MOCK_METHOD0(OnWatchTimeFinalized, void(void));
MOCK_METHOD0(OnPowerWatchTimeFinalized, void(void)); MOCK_METHOD0(OnPowerWatchTimeFinalized, void(void));
...@@ -606,6 +621,7 @@ class WatchTimeReporterTest ...@@ -606,6 +621,7 @@ class WatchTimeReporterTest
void(mojom::SecondaryPlaybackPropertiesPtr)); void(mojom::SecondaryPlaybackPropertiesPtr));
MOCK_METHOD1(OnSetAutoplayInitiated, void(bool)); MOCK_METHOD1(OnSetAutoplayInitiated, void(bool));
MOCK_METHOD1(OnDurationChanged, void(base::TimeDelta)); MOCK_METHOD1(OnDurationChanged, void(base::TimeDelta));
MOCK_METHOD2(OnUpdateVideoDecodeStats, void(uint32_t, uint32_t));
const bool has_video_; const bool has_video_;
const bool has_audio_; const bool has_audio_;
...@@ -693,6 +709,75 @@ TEST_P(WatchTimeReporterTest, WatchTimeReporterBasic) { ...@@ -693,6 +709,75 @@ TEST_P(WatchTimeReporterTest, WatchTimeReporterBasic) {
.WillOnce(testing::Return(kWatchTimeEarly)) .WillOnce(testing::Return(kWatchTimeEarly))
.WillRepeatedly(testing::Return(kWatchTimeLate)); .WillRepeatedly(testing::Return(kWatchTimeLate));
Initialize(true, true, kSizeJustRight); Initialize(true, true, kSizeJustRight);
PipelineStatistics stats;
stats.video_frames_decoded = 10;
stats.video_frames_dropped = 2;
if (has_video_) {
EXPECT_CALL(*this, GetPipelineStatistics())
.WillOnce(testing::Return(PipelineStatistics()))
.WillRepeatedly(testing::Return(stats));
EXPECT_CALL(*this, OnUpdateVideoDecodeStats(stats.video_frames_decoded,
stats.video_frames_dropped));
}
wtr_->OnPlaying();
EXPECT_TRUE(IsMonitoring());
EXPECT_WATCH_TIME(Ac, kWatchTimeEarly);
EXPECT_WATCH_TIME(All, kWatchTimeEarly);
EXPECT_WATCH_TIME(Eme, kWatchTimeEarly);
EXPECT_WATCH_TIME(Mse, kWatchTimeEarly);
EXPECT_WATCH_TIME(NativeControlsOff, kWatchTimeEarly);
EXPECT_WATCH_TIME_IF_VIDEO(DisplayInline, kWatchTimeEarly);
CycleReportingTimer();
wtr_->OnUnderflow();
constexpr base::TimeDelta kUnderflowDuration =
base::TimeDelta::FromMilliseconds(250);
wtr_->OnUnderflowComplete(kUnderflowDuration);
wtr_->OnUnderflow();
EXPECT_WATCH_TIME(Ac, kWatchTimeLate);
EXPECT_WATCH_TIME(All, kWatchTimeLate);
EXPECT_WATCH_TIME(Eme, kWatchTimeLate);
EXPECT_WATCH_TIME(Mse, kWatchTimeLate);
EXPECT_WATCH_TIME(NativeControlsOff, kWatchTimeLate);
EXPECT_WATCH_TIME_IF_VIDEO(DisplayInline, kWatchTimeLate);
EXPECT_CALL(*this, OnUnderflowUpdate(2));
EXPECT_CALL(*this, OnUnderflowDurationUpdate(1, kUnderflowDuration));
CycleReportingTimer();
EXPECT_WATCH_TIME_FINALIZED();
wtr_.reset();
}
TEST_P(WatchTimeReporterTest, WatchTimeReporterStatsOffsetCorrectly) {
constexpr base::TimeDelta kWatchTimeEarly = base::TimeDelta::FromSeconds(5);
constexpr base::TimeDelta kWatchTimeLate = base::TimeDelta::FromSeconds(10);
EXPECT_CALL(*this, GetCurrentMediaTime())
.WillOnce(testing::Return(base::TimeDelta()))
.WillOnce(testing::Return(kWatchTimeEarly))
.WillRepeatedly(testing::Return(kWatchTimeLate));
Initialize(true, true, kSizeJustRight);
PipelineStatistics initial_stats;
initial_stats.video_frames_decoded = 10;
initial_stats.video_frames_dropped = 2;
PipelineStatistics stats;
stats.video_frames_decoded = 17;
stats.video_frames_dropped = 7;
if (has_video_) {
EXPECT_CALL(*this, GetPipelineStatistics())
.WillOnce(testing::Return(initial_stats))
.WillRepeatedly(testing::Return(stats));
EXPECT_CALL(
*this,
OnUpdateVideoDecodeStats(
stats.video_frames_decoded - initial_stats.video_frames_decoded,
stats.video_frames_dropped - initial_stats.video_frames_dropped));
}
wtr_->OnPlaying(); wtr_->OnPlaying();
EXPECT_TRUE(IsMonitoring()); EXPECT_TRUE(IsMonitoring());
...@@ -997,9 +1082,7 @@ TEST_P(WatchTimeReporterTest, WatchTimeReporterNoUnderflowDoubleReport) { ...@@ -997,9 +1082,7 @@ TEST_P(WatchTimeReporterTest, WatchTimeReporterNoUnderflowDoubleReport) {
wtr_->OnUnderflowComplete(kUnderflowDuration); wtr_->OnUnderflowComplete(kUnderflowDuration);
EXPECT_CALL(*this, OnUnderflowDurationUpdate(1, kUnderflowDuration)); EXPECT_CALL(*this, OnUnderflowDurationUpdate(1, kUnderflowDuration));
// Muted watch time shouldn't finalize until destruction. EXPECT_WATCH_TIME_FINALIZED();
if (has_audio_ && has_video_)
EXPECT_WATCH_TIME_FINALIZED();
wtr_.reset(); wtr_.reset();
} }
......
...@@ -3149,6 +3149,8 @@ void WebMediaPlayerImpl::CreateWatchTimeReporter() { ...@@ -3149,6 +3149,8 @@ void WebMediaPlayerImpl::CreateWatchTimeReporter() {
pipeline_metadata_.natural_size, pipeline_metadata_.natural_size,
base::BindRepeating(&WebMediaPlayerImpl::GetCurrentTimeInternal, base::BindRepeating(&WebMediaPlayerImpl::GetCurrentTimeInternal,
base::Unretained(this)), base::Unretained(this)),
base::BindRepeating(&WebMediaPlayerImpl::GetPipelineStatistics,
base::Unretained(this)),
media_metrics_provider_.get(), media_metrics_provider_.get(),
frame_->GetTaskRunner(blink::TaskType::kInternalMedia))); frame_->GetTaskRunner(blink::TaskType::kInternalMedia)));
watch_time_reporter_->OnVolumeChange(volume_); watch_time_reporter_->OnVolumeChange(volume_);
......
...@@ -83,6 +83,10 @@ interface WatchTimeRecorder { ...@@ -83,6 +83,10 @@ interface WatchTimeRecorder {
// 10s, and 15s will become 20s. May be called any number of times. // 10s, and 15s will become 20s. May be called any number of times.
OnDurationChanged(mojo_base.mojom.TimeDelta duration); OnDurationChanged(mojo_base.mojom.TimeDelta duration);
// Updates the total number of frames decoded and dropped. As with other
// values, these are absolute and not relative since the last call.
UpdateVideoDecodeStats(uint32 frames_decoded, uint32 frames_dropped);
// Indicates that an underflow event has occurred while collecting watch time. // Indicates that an underflow event has occurred while collecting watch time.
// Used to report mean values for rebuffering metrics. As with watch time, // Used to report mean values for rebuffering metrics. As with watch time,
// this is an absolute count and not relative since the last call. // this is an absolute count and not relative since the last call.
......
...@@ -243,8 +243,11 @@ void WatchTimeRecorder::FinalizeWatchTime( ...@@ -243,8 +243,11 @@ void WatchTimeRecorder::FinalizeWatchTime(
last_record.total_underflow_count += underflow_count_; last_record.total_underflow_count += underflow_count_;
last_record.total_completed_underflow_count += completed_underflow_count_; last_record.total_completed_underflow_count += completed_underflow_count_;
last_record.total_underflow_duration += underflow_duration_; last_record.total_underflow_duration += underflow_duration_;
last_record.total_video_frames_decoded += video_frames_decoded_;
last_record.total_video_frames_dropped += video_frames_dropped_;
} }
video_frames_decoded_ = video_frames_dropped_ = 0;
underflow_count_ = completed_underflow_count_ = 0; underflow_count_ = completed_underflow_count_ = 0;
underflow_duration_ = base::TimeDelta(); underflow_duration_ = base::TimeDelta();
watch_time_info_.clear(); watch_time_info_.clear();
...@@ -306,13 +309,16 @@ void WatchTimeRecorder::UpdateSecondaryProperties( ...@@ -306,13 +309,16 @@ void WatchTimeRecorder::UpdateSecondaryProperties(
last_record.total_underflow_count += underflow_count_; last_record.total_underflow_count += underflow_count_;
last_record.total_completed_underflow_count += completed_underflow_count_; last_record.total_completed_underflow_count += completed_underflow_count_;
last_record.total_underflow_duration += underflow_duration_; last_record.total_underflow_duration += underflow_duration_;
last_record.total_video_frames_decoded += video_frames_decoded_;
last_record.total_video_frames_dropped += video_frames_dropped_;
// If we flushed any watch time or underflow counts which hadn't been // If we flushed any watch time or underflow counts which hadn't been
// finalized we'll need to ensure the eventual Finalize() correctly accounts // finalized we'll need to ensure the eventual Finalize() correctly accounts
// for those values at the time of the secondary property update. // for those values at the time of the secondary property update.
last_record_was_unfinalized = last_record_was_unfinalized =
!watch_time_info_.empty() || underflow_count_ || !watch_time_info_.empty() || underflow_count_ ||
completed_underflow_count_ || !underflow_duration_.is_zero(); completed_underflow_count_ || !underflow_duration_.is_zero() ||
video_frames_decoded_ || video_frames_dropped_;
} }
ukm_records_.emplace_back(std::move(secondary_properties)); ukm_records_.emplace_back(std::move(secondary_properties));
...@@ -336,6 +342,8 @@ void WatchTimeRecorder::UpdateSecondaryProperties( ...@@ -336,6 +342,8 @@ void WatchTimeRecorder::UpdateSecondaryProperties(
last_record.total_underflow_count = -underflow_count_; last_record.total_underflow_count = -underflow_count_;
last_record.total_completed_underflow_count = -completed_underflow_count_; last_record.total_completed_underflow_count = -completed_underflow_count_;
last_record.total_underflow_duration = -underflow_duration_; last_record.total_underflow_duration = -underflow_duration_;
last_record.total_video_frames_decoded = -video_frames_decoded_;
last_record.total_video_frames_dropped = -video_frames_dropped_;
for (auto& kv : watch_time_info_) for (auto& kv : watch_time_info_)
last_record.aggregate_watch_time_info[kv.first] = -kv.second; last_record.aggregate_watch_time_info[kv.first] = -kv.second;
} }
...@@ -350,6 +358,12 @@ void WatchTimeRecorder::OnDurationChanged(base::TimeDelta duration) { ...@@ -350,6 +358,12 @@ void WatchTimeRecorder::OnDurationChanged(base::TimeDelta duration) {
duration_ = duration; duration_ = duration;
} }
void WatchTimeRecorder::UpdateVideoDecodeStats(uint32_t video_frames_decoded,
uint32_t video_frames_dropped) {
video_frames_decoded_ = video_frames_decoded;
video_frames_dropped_ = video_frames_dropped;
}
void WatchTimeRecorder::UpdateUnderflowCount(int32_t total_count) { void WatchTimeRecorder::UpdateUnderflowCount(int32_t total_count) {
underflow_count_ = total_count; underflow_count_ = total_count;
} }
...@@ -496,6 +510,8 @@ void WatchTimeRecorder::RecordUkmPlaybackData() { ...@@ -496,6 +510,8 @@ void WatchTimeRecorder::RecordUkmPlaybackData() {
ukm_record.total_completed_underflow_count); ukm_record.total_completed_underflow_count);
builder.SetCompletedRebuffersDuration( builder.SetCompletedRebuffersDuration(
ukm_record.total_underflow_duration.InMilliseconds()); ukm_record.total_underflow_duration.InMilliseconds());
builder.SetVideoFramesDecoded(ukm_record.total_video_frames_decoded);
builder.SetVideoFramesDropped(ukm_record.total_video_frames_dropped);
builder.SetVideoNaturalWidth( builder.SetVideoNaturalWidth(
ukm_record.secondary_properties->natural_size.width()); ukm_record.secondary_properties->natural_size.width());
builder.SetVideoNaturalHeight( builder.SetVideoNaturalHeight(
......
...@@ -38,6 +38,8 @@ class MEDIA_MOJO_EXPORT WatchTimeRecorder : public mojom::WatchTimeRecorder { ...@@ -38,6 +38,8 @@ class MEDIA_MOJO_EXPORT WatchTimeRecorder : public mojom::WatchTimeRecorder {
mojom::SecondaryPlaybackPropertiesPtr secondary_properties) override; mojom::SecondaryPlaybackPropertiesPtr secondary_properties) override;
void SetAutoplayInitiated(bool value) override; void SetAutoplayInitiated(bool value) override;
void OnDurationChanged(base::TimeDelta duration) override; void OnDurationChanged(base::TimeDelta duration) override;
void UpdateVideoDecodeStats(uint32_t video_frames_decoded,
uint32_t video_frames_dropped) override;
void UpdateUnderflowCount(int32_t total_count) override; void UpdateUnderflowCount(int32_t total_count) override;
void UpdateUnderflowDuration(int32_t total_completed_count, void UpdateUnderflowDuration(int32_t total_completed_count,
base::TimeDelta total_duration) override; base::TimeDelta total_duration) override;
...@@ -95,12 +97,18 @@ class MEDIA_MOJO_EXPORT WatchTimeRecorder : public mojom::WatchTimeRecorder { ...@@ -95,12 +97,18 @@ class MEDIA_MOJO_EXPORT WatchTimeRecorder : public mojom::WatchTimeRecorder {
int total_underflow_count = 0; int total_underflow_count = 0;
int total_completed_underflow_count = 0; int total_completed_underflow_count = 0;
base::TimeDelta total_underflow_duration; base::TimeDelta total_underflow_duration;
uint32_t total_video_frames_decoded = 0;
uint32_t total_video_frames_dropped = 0;
}; };
// List of all watch time segments. A new entry is added for every secondary // List of all watch time segments. A new entry is added for every secondary
// property update. // property update.
std::vector<WatchTimeUkmRecord> ukm_records_; std::vector<WatchTimeUkmRecord> ukm_records_;
uint32_t video_frames_decoded_ = 0;
uint32_t video_frames_dropped_ = 0;
int underflow_count_ = 0; int underflow_count_ = 0;
int completed_underflow_count_ = 0; int completed_underflow_count_ = 0;
base::TimeDelta underflow_duration_; base::TimeDelta underflow_duration_;
......
...@@ -660,6 +660,7 @@ TEST_F(WatchTimeRecorderTest, BasicUkmAudioVideoWithExtras) { ...@@ -660,6 +660,7 @@ TEST_F(WatchTimeRecorderTest, BasicUkmAudioVideoWithExtras) {
constexpr base::TimeDelta kUnderflowDuration = constexpr base::TimeDelta kUnderflowDuration =
base::TimeDelta::FromMilliseconds(500); base::TimeDelta::FromMilliseconds(500);
wtr_->UpdateUnderflowDuration(2, kUnderflowDuration); wtr_->UpdateUnderflowDuration(2, kUnderflowDuration);
wtr_->UpdateVideoDecodeStats(10, 2);
wtr_->OnError(PIPELINE_ERROR_DECODE); wtr_->OnError(PIPELINE_ERROR_DECODE);
secondary_properties->audio_decoder_name = "MojoAudioDecoder"; secondary_properties->audio_decoder_name = "MojoAudioDecoder";
...@@ -722,6 +723,8 @@ TEST_F(WatchTimeRecorderTest, BasicUkmAudioVideoWithExtras) { ...@@ -722,6 +723,8 @@ TEST_F(WatchTimeRecorderTest, BasicUkmAudioVideoWithExtras) {
EXPECT_UKM(UkmEntry::kCompletedRebuffersCountName, 2); EXPECT_UKM(UkmEntry::kCompletedRebuffersCountName, 2);
EXPECT_UKM(UkmEntry::kCompletedRebuffersDurationName, EXPECT_UKM(UkmEntry::kCompletedRebuffersDurationName,
kUnderflowDuration.InMilliseconds()); kUnderflowDuration.InMilliseconds());
EXPECT_UKM(UkmEntry::kVideoFramesDecodedName, 10);
EXPECT_UKM(UkmEntry::kVideoFramesDroppedName, 2);
EXPECT_UKM(UkmEntry::kVideoNaturalWidthName, EXPECT_UKM(UkmEntry::kVideoNaturalWidthName,
secondary_properties->natural_size.width()); secondary_properties->natural_size.width());
EXPECT_UKM(UkmEntry::kVideoNaturalHeightName, EXPECT_UKM(UkmEntry::kVideoNaturalHeightName,
...@@ -1006,6 +1009,10 @@ TEST_F(WatchTimeRecorderTest, MultipleSecondaryPropertiesNoFinalize) { ...@@ -1006,6 +1009,10 @@ TEST_F(WatchTimeRecorderTest, MultipleSecondaryPropertiesNoFinalize) {
wtr_->UpdateUnderflowCount(kUnderflowCount1); wtr_->UpdateUnderflowCount(kUnderflowCount1);
wtr_->UpdateUnderflowDuration(kUnderflowCount1, kUnderflowDuration); wtr_->UpdateUnderflowDuration(kUnderflowCount1, kUnderflowDuration);
constexpr int kDecodedFrameCount1 = 10;
constexpr int kDroppedFrameCount1 = 2;
wtr_->UpdateVideoDecodeStats(kDecodedFrameCount1, kDroppedFrameCount1);
mojom::SecondaryPlaybackPropertiesPtr secondary_properties2 = mojom::SecondaryPlaybackPropertiesPtr secondary_properties2 =
mojom::SecondaryPlaybackProperties::New( mojom::SecondaryPlaybackProperties::New(
kCodecAAC, kCodecH264, H264PROFILE_MAIN, "FFmpegAudioDecoder", kCodecAAC, kCodecH264, H264PROFILE_MAIN, "FFmpegAudioDecoder",
...@@ -1024,6 +1031,11 @@ TEST_F(WatchTimeRecorderTest, MultipleSecondaryPropertiesNoFinalize) { ...@@ -1024,6 +1031,11 @@ TEST_F(WatchTimeRecorderTest, MultipleSecondaryPropertiesNoFinalize) {
wtr_->OnError(PIPELINE_ERROR_DECODE); wtr_->OnError(PIPELINE_ERROR_DECODE);
wtr_->OnDurationChanged(base::TimeDelta::FromSeconds(5125)); wtr_->OnDurationChanged(base::TimeDelta::FromSeconds(5125));
constexpr int kDecodedFrameCount2 = 20;
constexpr int kDroppedFrameCount2 = 10;
wtr_->UpdateVideoDecodeStats(kDecodedFrameCount1 + kDecodedFrameCount2,
kDroppedFrameCount1 + kDroppedFrameCount2);
wtr_.reset(); wtr_.reset();
base::RunLoop().RunUntilIdle(); base::RunLoop().RunUntilIdle();
...@@ -1057,6 +1069,8 @@ TEST_F(WatchTimeRecorderTest, MultipleSecondaryPropertiesNoFinalize) { ...@@ -1057,6 +1069,8 @@ TEST_F(WatchTimeRecorderTest, MultipleSecondaryPropertiesNoFinalize) {
EXPECT_UKM(UkmEntry::kCompletedRebuffersCountName, kUnderflowCount1); EXPECT_UKM(UkmEntry::kCompletedRebuffersCountName, kUnderflowCount1);
EXPECT_UKM(UkmEntry::kCompletedRebuffersDurationName, EXPECT_UKM(UkmEntry::kCompletedRebuffersDurationName,
kUnderflowDuration.InMilliseconds()); kUnderflowDuration.InMilliseconds());
EXPECT_UKM(UkmEntry::kVideoFramesDecodedName, kDecodedFrameCount1);
EXPECT_UKM(UkmEntry::kVideoFramesDroppedName, kDroppedFrameCount1);
EXPECT_UKM(UkmEntry::kAudioCodecName, secondary_properties1->audio_codec); EXPECT_UKM(UkmEntry::kAudioCodecName, secondary_properties1->audio_codec);
EXPECT_UKM(UkmEntry::kVideoCodecName, secondary_properties1->video_codec); EXPECT_UKM(UkmEntry::kVideoCodecName, secondary_properties1->video_codec);
EXPECT_UKM(UkmEntry::kVideoCodecProfileName, EXPECT_UKM(UkmEntry::kVideoCodecProfileName,
...@@ -1081,6 +1095,9 @@ TEST_F(WatchTimeRecorderTest, MultipleSecondaryPropertiesNoFinalize) { ...@@ -1081,6 +1095,9 @@ TEST_F(WatchTimeRecorderTest, MultipleSecondaryPropertiesNoFinalize) {
EXPECT_UKM(UkmEntry::kVideoDecoderNameName, 2); EXPECT_UKM(UkmEntry::kVideoDecoderNameName, 2);
EXPECT_UKM(UkmEntry::kRebuffersCountName, kUnderflowCount2); EXPECT_UKM(UkmEntry::kRebuffersCountName, kUnderflowCount2);
EXPECT_UKM(UkmEntry::kCompletedRebuffersCountName, 0); EXPECT_UKM(UkmEntry::kCompletedRebuffersCountName, 0);
EXPECT_UKM(UkmEntry::kVideoFramesDecodedName, kDecodedFrameCount2);
EXPECT_UKM(UkmEntry::kVideoFramesDroppedName, kDroppedFrameCount2);
EXPECT_UKM(UkmEntry::kCompletedRebuffersDurationName, 0); EXPECT_UKM(UkmEntry::kCompletedRebuffersDurationName, 0);
EXPECT_UKM(UkmEntry::kAudioCodecName, secondary_properties2->audio_codec); EXPECT_UKM(UkmEntry::kAudioCodecName, secondary_properties2->audio_codec);
EXPECT_UKM(UkmEntry::kVideoCodecName, secondary_properties2->video_codec); EXPECT_UKM(UkmEntry::kVideoCodecName, secondary_properties2->video_codec);
...@@ -1117,6 +1134,10 @@ TEST_F(WatchTimeRecorderTest, MultipleSecondaryPropertiesNoFinalizeNo2ndWT) { ...@@ -1117,6 +1134,10 @@ TEST_F(WatchTimeRecorderTest, MultipleSecondaryPropertiesNoFinalizeNo2ndWT) {
wtr_->UpdateUnderflowCount(kUnderflowCount1); wtr_->UpdateUnderflowCount(kUnderflowCount1);
wtr_->UpdateUnderflowDuration(kUnderflowCount1, kUnderflowDuration); wtr_->UpdateUnderflowDuration(kUnderflowCount1, kUnderflowDuration);
constexpr int kDecodedFrameCount1 = 10;
constexpr int kDroppedFrameCount1 = 2;
wtr_->UpdateVideoDecodeStats(kDecodedFrameCount1, kDroppedFrameCount1);
mojom::SecondaryPlaybackPropertiesPtr secondary_properties2 = mojom::SecondaryPlaybackPropertiesPtr secondary_properties2 =
mojom::SecondaryPlaybackProperties::New( mojom::SecondaryPlaybackProperties::New(
kCodecAAC, kCodecH264, H264PROFILE_MAIN, "FFmpegAudioDecoder", kCodecAAC, kCodecH264, H264PROFILE_MAIN, "FFmpegAudioDecoder",
...@@ -1158,6 +1179,8 @@ TEST_F(WatchTimeRecorderTest, MultipleSecondaryPropertiesNoFinalizeNo2ndWT) { ...@@ -1158,6 +1179,8 @@ TEST_F(WatchTimeRecorderTest, MultipleSecondaryPropertiesNoFinalizeNo2ndWT) {
EXPECT_UKM(UkmEntry::kCompletedRebuffersCountName, kUnderflowCount1); EXPECT_UKM(UkmEntry::kCompletedRebuffersCountName, kUnderflowCount1);
EXPECT_UKM(UkmEntry::kCompletedRebuffersDurationName, EXPECT_UKM(UkmEntry::kCompletedRebuffersDurationName,
kUnderflowDuration.InMilliseconds()); kUnderflowDuration.InMilliseconds());
EXPECT_UKM(UkmEntry::kVideoFramesDecodedName, kDecodedFrameCount1);
EXPECT_UKM(UkmEntry::kVideoFramesDroppedName, kDroppedFrameCount1);
EXPECT_UKM(UkmEntry::kAudioCodecName, secondary_properties1->audio_codec); EXPECT_UKM(UkmEntry::kAudioCodecName, secondary_properties1->audio_codec);
EXPECT_UKM(UkmEntry::kVideoCodecName, secondary_properties1->video_codec); EXPECT_UKM(UkmEntry::kVideoCodecName, secondary_properties1->video_codec);
EXPECT_UKM(UkmEntry::kVideoCodecProfileName, EXPECT_UKM(UkmEntry::kVideoCodecProfileName,
...@@ -1181,6 +1204,8 @@ TEST_F(WatchTimeRecorderTest, MultipleSecondaryPropertiesNoFinalizeNo2ndWT) { ...@@ -1181,6 +1204,8 @@ TEST_F(WatchTimeRecorderTest, MultipleSecondaryPropertiesNoFinalizeNo2ndWT) {
EXPECT_UKM(UkmEntry::kRebuffersCountName, 0); EXPECT_UKM(UkmEntry::kRebuffersCountName, 0);
EXPECT_UKM(UkmEntry::kCompletedRebuffersCountName, 0); EXPECT_UKM(UkmEntry::kCompletedRebuffersCountName, 0);
EXPECT_UKM(UkmEntry::kCompletedRebuffersDurationName, 0); EXPECT_UKM(UkmEntry::kCompletedRebuffersDurationName, 0);
EXPECT_UKM(UkmEntry::kVideoFramesDecodedName, 0);
EXPECT_UKM(UkmEntry::kVideoFramesDroppedName, 0);
EXPECT_UKM(UkmEntry::kAudioCodecName, secondary_properties2->audio_codec); EXPECT_UKM(UkmEntry::kAudioCodecName, secondary_properties2->audio_codec);
EXPECT_UKM(UkmEntry::kVideoCodecName, secondary_properties2->video_codec); EXPECT_UKM(UkmEntry::kVideoCodecName, secondary_properties2->video_codec);
EXPECT_UKM(UkmEntry::kVideoCodecProfileName, EXPECT_UKM(UkmEntry::kVideoCodecProfileName,
...@@ -1216,6 +1241,10 @@ TEST_F(WatchTimeRecorderTest, MultipleSecondaryPropertiesWithFinalize) { ...@@ -1216,6 +1241,10 @@ TEST_F(WatchTimeRecorderTest, MultipleSecondaryPropertiesWithFinalize) {
wtr_->UpdateUnderflowCount(kUnderflowCount1); wtr_->UpdateUnderflowCount(kUnderflowCount1);
wtr_->UpdateUnderflowDuration(kUnderflowCount1, kUnderflowDuration); wtr_->UpdateUnderflowDuration(kUnderflowCount1, kUnderflowDuration);
constexpr int kDecodedFrameCount1 = 10;
constexpr int kDroppedFrameCount1 = 2;
wtr_->UpdateVideoDecodeStats(kDecodedFrameCount1, kDroppedFrameCount1);
// Force a finalize here so that the there is no unfinalized watch time at the // Force a finalize here so that the there is no unfinalized watch time at the
// time of the secondary property update. // time of the secondary property update.
wtr_->FinalizeWatchTime({}); wtr_->FinalizeWatchTime({});
...@@ -1267,6 +1296,8 @@ TEST_F(WatchTimeRecorderTest, MultipleSecondaryPropertiesWithFinalize) { ...@@ -1267,6 +1296,8 @@ TEST_F(WatchTimeRecorderTest, MultipleSecondaryPropertiesWithFinalize) {
EXPECT_UKM(UkmEntry::kCompletedRebuffersCountName, kUnderflowCount1); EXPECT_UKM(UkmEntry::kCompletedRebuffersCountName, kUnderflowCount1);
EXPECT_UKM(UkmEntry::kCompletedRebuffersDurationName, EXPECT_UKM(UkmEntry::kCompletedRebuffersDurationName,
kUnderflowDuration.InMilliseconds()); kUnderflowDuration.InMilliseconds());
EXPECT_UKM(UkmEntry::kVideoFramesDecodedName, kDecodedFrameCount1);
EXPECT_UKM(UkmEntry::kVideoFramesDroppedName, kDroppedFrameCount1);
EXPECT_UKM(UkmEntry::kAudioCodecName, secondary_properties1->audio_codec); EXPECT_UKM(UkmEntry::kAudioCodecName, secondary_properties1->audio_codec);
EXPECT_UKM(UkmEntry::kVideoCodecName, secondary_properties1->video_codec); EXPECT_UKM(UkmEntry::kVideoCodecName, secondary_properties1->video_codec);
EXPECT_UKM(UkmEntry::kVideoCodecProfileName, EXPECT_UKM(UkmEntry::kVideoCodecProfileName,
...@@ -1292,6 +1323,8 @@ TEST_F(WatchTimeRecorderTest, MultipleSecondaryPropertiesWithFinalize) { ...@@ -1292,6 +1323,8 @@ TEST_F(WatchTimeRecorderTest, MultipleSecondaryPropertiesWithFinalize) {
EXPECT_UKM(UkmEntry::kRebuffersCountName, kUnderflowCount2); EXPECT_UKM(UkmEntry::kRebuffersCountName, kUnderflowCount2);
EXPECT_UKM(UkmEntry::kCompletedRebuffersCountName, 0); EXPECT_UKM(UkmEntry::kCompletedRebuffersCountName, 0);
EXPECT_UKM(UkmEntry::kCompletedRebuffersDurationName, 0); EXPECT_UKM(UkmEntry::kCompletedRebuffersDurationName, 0);
EXPECT_UKM(UkmEntry::kVideoFramesDecodedName, 0);
EXPECT_UKM(UkmEntry::kVideoFramesDroppedName, 0);
EXPECT_UKM(UkmEntry::kAudioCodecName, secondary_properties2->audio_codec); EXPECT_UKM(UkmEntry::kAudioCodecName, secondary_properties2->audio_codec);
EXPECT_UKM(UkmEntry::kVideoCodecName, secondary_properties2->video_codec); EXPECT_UKM(UkmEntry::kVideoCodecName, secondary_properties2->video_codec);
EXPECT_UKM(UkmEntry::kVideoCodecProfileName, EXPECT_UKM(UkmEntry::kVideoCodecProfileName,
......
...@@ -3817,6 +3817,17 @@ be describing additional metrics about the same event. ...@@ -3817,6 +3817,17 @@ be describing additional metrics about the same event.
if the video track is unencrypted. if the video track is unencrypted.
</summary> </summary>
</metric> </metric>
<metric name="VideoFramesDecoded">
<summary>
Integer count of the video frames decoded in this record.
</summary>
</metric>
<metric name="VideoFramesDropped">
<summary>
Integer count of the video frames dropped in this record. Should not
exceed VideoFramesDecoded.
</summary>
</metric>
<metric name="VideoNaturalHeight"> <metric name="VideoNaturalHeight">
<summary> <summary>
Integer value indicating the natural height of the playback. Integer value indicating the natural height of the playback.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment