Skip to content

Commit

Permalink
[android] Measuring the performance of SbPlayer
Browse files Browse the repository at this point in the history
This CL includes the performance metrics as the following:
1. CPU usage: the percentage of time spent executing across all threads of the process. It could exceed 100% on multi-core systems.
2. Dropped frames: the number of dropped video frames.
3. Audio underrun count: the number of audio buffer to underflow and a potential audio glitch or pop.

b/315159208
  • Loading branch information
borongc committed Feb 1, 2024
1 parent bb9e01b commit 4c6c881
Show file tree
Hide file tree
Showing 15 changed files with 770 additions and 6 deletions.
42 changes: 37 additions & 5 deletions cobalt/base/statistics.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ inline int64_t DefaultSampleToValueFunc(int64_t dividend, int64_t divisor) {

} // namespace internal

typedef struct Percentiles {
int64_t percentile_25th;
int64_t median;
int64_t percentile_75th;
} Percentiles;

// Track the statistics of a series of generic samples reprensented as
// dividends and divisors in integer types. The value of a sample is calculated
// by `SampleToValueFunc` using its dividend and divisor.
Expand Down Expand Up @@ -78,6 +84,7 @@ class Statistics {
int64_t max() const { return 0; }

int64_t GetMedian() const { return 0; }
void GetPercentiles(Percentiles* out_percentiles) {}
};

#else // defined(COBALT_BUILD_TYPE_GOLD)
Expand Down Expand Up @@ -151,12 +158,29 @@ class Statistics {
samples_to_copy -= MaxSamples - first_sample_index_;
copy.insert(copy.end(), samples_, samples_ + samples_to_copy);
}
return GetNthPercentile(copy, number_of_samples_ / 2);
}

std::nth_element(copy.begin(), copy.begin() + number_of_samples_ / 2,
copy.end(), [](const Sample& left, const Sample& right) {
return GetSampleValue(left) < GetSampleValue(right);
});
return GetSampleValue(copy[number_of_samples_ / 2]);
void GetPercentiles(Percentiles* out_percentiles) {
if (number_of_samples_ == 0) {
return;
}
std::vector<Sample> copy;
copy.reserve(number_of_samples_);
if (first_sample_index_ + number_of_samples_ <= MaxSamples) {
copy.assign(samples_ + first_sample_index_,
samples_ + first_sample_index_ + number_of_samples_);
} else {
auto samples_to_copy = number_of_samples_;
copy.assign(samples_ + first_sample_index_, samples_ + MaxSamples);
samples_to_copy -= MaxSamples - first_sample_index_;
copy.insert(copy.end(), samples_, samples_ + samples_to_copy);
}
out_percentiles->percentile_25th =
GetNthPercentile(copy, number_of_samples_ / 4);
out_percentiles->median = GetNthPercentile(copy, number_of_samples_ / 2);
out_percentiles->percentile_75th =
GetNthPercentile(copy, number_of_samples_ * 3 / 4);
}

private:
Expand All @@ -173,6 +197,14 @@ class Statistics {
static_cast<int64_t>(sample.divisor));
}

static int GetNthPercentile(std::vector<Sample>& copy, int place) {
std::nth_element(copy.begin(), copy.begin() + place, copy.end(),
[](const Sample& left, const Sample& right) {
return GetSampleValue(left) < GetSampleValue(right);
});
return GetSampleValue(copy[place]);
}

bool first_sample_added_ = false;

int64_t accumulated_dividend_ = 0;
Expand Down
2 changes: 2 additions & 0 deletions cobalt/media/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ component("media") {
"base/sbplayer_bridge.h",
"base/sbplayer_interface.cc",
"base/sbplayer_interface.h",
"base/sbplayer_perf.cc",
"base/sbplayer_perf.h",
"base/sbplayer_pipeline.cc",
"base/sbplayer_pipeline.h",
"base/sbplayer_set_bounds_helper.cc",
Expand Down
42 changes: 42 additions & 0 deletions cobalt/media/base/sbplayer_bridge.cc
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#include "starboard/common/player.h"
#include "starboard/common/string.h"
#include "starboard/configuration.h"
#include "starboard/extension/player_perf.h"
#include "starboard/extension/player_set_max_video_input_size.h"
#include "starboard/memory.h"
#include "starboard/once.h"
Expand All @@ -45,6 +46,12 @@ using base::TimeDelta;
using starboard::FormatString;
using starboard::GetPlayerOutputModeName;

#if !defined(COBALT_BUILD_TYPE_GOLD)
constexpr bool kForceCPUUtilizationMeasure = true;
#else
constexpr bool kForceCPUUtilizationMeasure = false;
#endif // !defined(COBALT_BUILD_TYPE_GOLD)

class StatisticsWrapper {
public:
static StatisticsWrapper* GetInstance();
Expand Down Expand Up @@ -251,6 +258,7 @@ SbPlayerBridge::SbPlayerBridge(
decode_target_provider_(decode_target_provider),
max_video_capabilities_(max_video_capabilities),
max_video_input_size_(max_video_input_size),
perf_thread_("cpu_utilization_perf"),
cval_stats_(&interface->cval_stats_),
pipeline_identifier_(pipeline_identifier)
#if SB_HAS(PLAYER_WITH_URL)
Expand All @@ -274,6 +282,16 @@ SbPlayerBridge::SbPlayerBridge(
UpdateVideoConfig(video_config, video_mime_type);
}

const StarboardExtensionPlayerPerfApi* player_perf_extension =
static_cast<const StarboardExtensionPlayerPerfApi*>(
SbSystemGetExtension(kStarboardExtensionPlayerPerfName));
if (kForceCPUUtilizationMeasure && player_perf_extension &&
strcmp(player_perf_extension->name, kStarboardExtensionPlayerPerfName) ==
0 &&
player_perf_extension->version >= 1) {
perf_thread_.Start();
}

output_mode_ = ComputeSbPlayerOutputMode(default_output_mode);

CreatePlayer();
Expand All @@ -289,6 +307,19 @@ SbPlayerBridge::SbPlayerBridge(
SbPlayerBridge::~SbPlayerBridge() {
DCHECK(task_runner_->BelongsToCurrentThread());

const StarboardExtensionPlayerPerfApi* player_perf_extension =
static_cast<const StarboardExtensionPlayerPerfApi*>(
SbSystemGetExtension(kStarboardExtensionPlayerPerfName));
if (kForceCPUUtilizationMeasure && player_perf_extension &&
strcmp(player_perf_extension->name, kStarboardExtensionPlayerPerfName) ==
0 &&
player_perf_extension->version >= 1) {
if (sbplayer_perf_) {
sbplayer_perf_->Stop();
}
perf_thread_.Stop();
}

callback_helper_->ResetPlayer();
set_bounds_helper_->SetPlayerBridge(NULL);

Expand Down Expand Up @@ -794,6 +825,17 @@ void SbPlayerBridge::CreatePlayer() {
&SbPlayerBridge::DecoderStatusCB, &SbPlayerBridge::PlayerStatusCB,
&SbPlayerBridge::PlayerErrorCB, this,
get_decode_target_graphics_context_provider_func_.Run());
const StarboardExtensionPlayerPerfApi* player_perf_extension =
static_cast<const StarboardExtensionPlayerPerfApi*>(
SbSystemGetExtension(kStarboardExtensionPlayerPerfName));
if (kForceCPUUtilizationMeasure && player_perf_extension &&
strcmp(player_perf_extension->name, kStarboardExtensionPlayerPerfName) ==
0 &&
player_perf_extension->version >= 1) {
sbplayer_perf_ = new SbPlayerPerf(sbplayer_interface_, player_,
perf_thread_.task_runner());
sbplayer_perf_->Start();
}
cval_stats_->StopTimer(MediaTiming::SbPlayerCreate, pipeline_identifier_);

is_creating_player_ = false;
Expand Down
4 changes: 4 additions & 0 deletions cobalt/media/base/sbplayer_bridge.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include "cobalt/media/base/decode_target_provider.h"
#include "cobalt/media/base/decoder_buffer_cache.h"
#include "cobalt/media/base/sbplayer_interface.h"
#include "cobalt/media/base/sbplayer_perf.h"
#include "cobalt/media/base/sbplayer_set_bounds_helper.h"
#include "starboard/media.h"
#include "starboard/player.h"
Expand Down Expand Up @@ -325,6 +326,9 @@ class SbPlayerBridge {
bool pending_audio_eos_buffer_ = false;
bool pending_video_eos_buffer_ = false;

base::Thread perf_thread_;
SbPlayerPerf* sbplayer_perf_;

CValStats* cval_stats_;
std::string pipeline_identifier_;
};
Expand Down
189 changes: 189 additions & 0 deletions cobalt/media/base/sbplayer_perf.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
// Copyright 2024 The Cobalt Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "cobalt/media/base/sbplayer_perf.h"

#include "base/bind.h"
#include "base/logging.h"
#include "cobalt/base/statistics.h"
#include "starboard/extension/player_perf.h"
#include "starboard/once.h"
#include "starboard/player.h"
#include "starboard/system.h"

namespace cobalt {
namespace media {

namespace {

// Sampling every 1 second
const base::TimeDelta sampling_interval = base::Seconds(1);

// Measuring for 2 minutes
const int64_t kMaxMeasurementSamples = 20;

class StatisticsWrapper {
public:
static StatisticsWrapper* GetInstance();
base::Statistics<double, int, 1024> cpu_usage{"Media.CPUUsage"};
base::Statistics<double, int, 1024> decoded_speed{"Media.decoded_speed"};
};

} // namespace

SB_ONCE_INITIALIZE_FUNCTION(StatisticsWrapper, StatisticsWrapper::GetInstance);

void SbPlayerPerf::Start() {
task_runner_->PostTask(
FROM_HERE, base::Bind(&SbPlayerPerf::StartTask, base::Unretained(this)));
}

void SbPlayerPerf::StartTask() {
DCHECK(task_runner_->BelongsToCurrentThread());
start_timestamp_ = Time::Now();
timer_.Start(FROM_HERE, sampling_interval, this,
&SbPlayerPerf::GetPlatformIndependentCPUUsage);
}

void SbPlayerPerf::Stop() {
task_runner_->PostTask(
FROM_HERE, base::Bind(&SbPlayerPerf::StopTask, base::Unretained(this)));
}

void SbPlayerPerf::StopTask() {
DCHECK(task_runner_->BelongsToCurrentThread());
timer_.Stop();
}

void SbPlayerPerf::GetPlatformIndependentCPUUsage() {
DCHECK(task_runner_->BelongsToCurrentThread());

const StarboardExtensionPlayerPerfApi* player_perf_extension =
static_cast<const StarboardExtensionPlayerPerfApi*>(
SbSystemGetExtension(kStarboardExtensionPlayerPerfName));
if (player_perf_extension &&
strcmp(player_perf_extension->name, kStarboardExtensionPlayerPerfName) ==
0 &&
player_perf_extension->version >= 1) {
Time timestamp_now = Time::Now();
base::Percentiles cpu_out_percentiles, decoded_speed_out_percentiles;
double cpu_usage_perf =
player_perf_extension->GetPlatformIndependentCPUUsage();
#if SB_API_VERSION >= 15
SbPlayerInfo info;
#else // SB_API_VERSION >= 15
SbPlayerInfo2 info;
#endif // SB_API_VERSION >= 15
interface_->GetInfo(player_, &info);
if (cpu_usage_perf == 0.0 && data_count_ == 0 &&
decoded_frames_last_ == 0) {
// The first sample is 0, so skip it
decoded_frames_last_ = info.total_video_frames;
timestamp_last_ = timestamp_now;
return;
}
StatisticsWrapper::GetInstance()->cpu_usage.AddSample(cpu_usage_perf, 1);
int decoded_frames = info.total_video_frames - decoded_frames_last_;
TimeDelta interval = timestamp_now - timestamp_last_;
if (decoded_frames > 0 && interval.InSeconds() > 0) {
double decoded_speed = static_cast<double>(decoded_frames) /
static_cast<double>(interval.InSeconds());
StatisticsWrapper::GetInstance()->decoded_speed.AddSample(decoded_speed,
1);
}
decoded_frames_last_ = info.total_video_frames;
timestamp_last_ = timestamp_now;

data_count_ += 1;
if (data_count_ == kMaxMeasurementSamples) {
StatisticsWrapper::GetInstance()->cpu_usage.GetPercentiles(
&cpu_out_percentiles);
StatisticsWrapper::GetInstance()->decoded_speed.GetPercentiles(
&decoded_speed_out_percentiles);
double dropped_frame_percentage =
100.0 * static_cast<double>(info.dropped_video_frames) /
static_cast<double>(info.total_video_frames);
TimeDelta duration = Time::Now() - start_timestamp_;
LOG(ERROR)
<< "Brown Brand/model_name: " << sb_system_property_brand_name_ << "_"
<< sb_system_property_model_name_
<< ", platform_name: " << sb_system_property_platform_name_
<< ", num_of_processors: " << sb_number_of_processors_
<< ", duration: " << duration.InSeconds() << "s, audio_codec: "
<< player_perf_extension->GetCurrentMediaAudioCodecName()
<< ", video_codec: "
<< player_perf_extension->GetCurrentMediaVideoCodecName()
<< ", should_be_paused: "
<< player_perf_extension->GetCountShouldBePaused()
<< ", dropped_frames(%): " << dropped_frame_percentage
<< "%, dropped_frames: " << info.dropped_video_frames
<< ", total_frames: " << info.total_video_frames
<< ", audio_underrun_count: "
<< player_perf_extension->GetAudioUnderrunCount()
<< ", playback_rate: " << info.playback_rate
<< ", use_neon: " << player_perf_extension->IsSIMDEnabled()
<< ", force_tunnel_mode: "
<< player_perf_extension->IsForceTunnelMode()
<< ", CPU usage statistics(%): min: "
<< StatisticsWrapper::GetInstance()->cpu_usage.min()
<< "%, 25th_p: " << cpu_out_percentiles.percentile_25th
<< "%, 50th_p: " << cpu_out_percentiles.median
<< "%, 75th_p: " << cpu_out_percentiles.percentile_75th
<< "%, average: "
<< StatisticsWrapper::GetInstance()->cpu_usage.average()
<< "%, max: " << StatisticsWrapper::GetInstance()->cpu_usage.max()
<< "%, decoded_speed (fps): min: "
<< StatisticsWrapper::GetInstance()->decoded_speed.min()
<< ", 25th_p: " << decoded_speed_out_percentiles.percentile_25th
<< ", 50th_p: " << decoded_speed_out_percentiles.median
<< ", 75th_p: " << decoded_speed_out_percentiles.percentile_75th
<< ", average: "
<< StatisticsWrapper::GetInstance()->decoded_speed.average()
<< ", max: " << StatisticsWrapper::GetInstance()->decoded_speed.max();
}
}
}

std::string SbPlayerPerf::GetSbSystemProperty(SbSystemPropertyId property_id) {
char property[1024] = {0};
std::string result = "";
if (!SbSystemGetProperty(property_id, property,
SB_ARRAY_SIZE_INT(property))) {
DLOG(FATAL) << "Failed to get kSbSystemPropertyPlatformName.";
return result;
}
result = property;
return result;
}

SbPlayerPerf::SbPlayerPerf(
SbPlayerInterface* interface, SbPlayer player,
const scoped_refptr<base::SingleThreadTaskRunner>& task_runner)
: task_runner_(task_runner),
data_count_(0),
interface_(interface),
player_(player),
decoded_frames_last_(0),
sb_number_of_processors_(SbSystemGetNumberOfProcessors()),
sb_system_property_brand_name_(
GetSbSystemProperty(kSbSystemPropertyBrandName)),
sb_system_property_model_name_(
GetSbSystemProperty(kSbSystemPropertyModelName)),
sb_system_property_platform_name_(
GetSbSystemProperty(kSbSystemPropertyPlatformName)) {}

SbPlayerPerf::~SbPlayerPerf() {}

} // namespace media
} // namespace cobalt
Loading

0 comments on commit 4c6c881

Please sign in to comment.