This source file includes following definitions.
- AsString
- Print
- AnalyzeJitter
- MapFrameTimes
- Analyze
- OnAudioFrame
- OnVideoFrame
- HasFlag
- IsGpuAvailable
- GetSuffixForTestFlags
- getfps
- GetFreeLocalPort
- SetUp
- SetUpCommandLine
- GetTraceEvents
- IndexEvents
- FindNextEvent
- OutputMeasurement
- AnalyzeLatency
- AnalyzeTraceDistance
- RunTest
- IN_PROC_BROWSER_TEST_P
#include <map>
#include <vector>
#include "base/basictypes.h"
#include "base/command_line.h"
#if defined(OS_MACOSX)
#include "base/mac/mac_util.h"
#endif
#include "base/strings/stringprintf.h"
#include "base/test/trace_event_analyzer.h"
#include "base/win/windows_version.h"
#include "chrome/browser/extensions/extension_apitest.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_test_message_listener.h"
#include "chrome/browser/extensions/tab_helper.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/fullscreen/fullscreen_controller.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/chrome_version_info.h"
#include "chrome/test/base/test_launcher_utils.h"
#include "chrome/test/base/test_switches.h"
#include "chrome/test/base/tracing.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/common/content_switches.h"
#include "extensions/common/feature_switch.h"
#include "extensions/common/features/base_feature_provider.h"
#include "extensions/common/features/complex_feature.h"
#include "extensions/common/features/feature.h"
#include "extensions/common/features/simple_feature.h"
#include "extensions/common/switches.h"
#include "media/base/video_frame.h"
#include "media/cast/cast_config.h"
#include "media/cast/cast_environment.h"
#include "media/cast/test/utility/audio_utility.h"
#include "media/cast/test/utility/barcode.h"
#include "media/cast/test/utility/default_config.h"
#include "media/cast/test/utility/in_process_receiver.h"
#include "media/cast/test/utility/standalone_cast_environment.h"
#include "media/cast/test/utility/udp_proxy.h"
#include "net/base/ip_endpoint.h"
#include "net/base/net_errors.h"
#include "net/base/net_util.h"
#include "net/base/rand_callback.h"
#include "net/udp/udp_socket.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/perf/perf_test.h"
#include "ui/compositor/compositor_switches.h"
#include "ui/gl/gl_switches.h"
namespace {
const char kExtensionId[] = "ddchlicdkolnonkihahngkmmmjnjlkkf";
static const size_t kSkipEvents = 3;
enum TestFlags {
kUseGpu = 1 << 0,
kDisableVsync = 1 << 1,
kSmallWindow = 1 << 2,
k24fps = 1 << 3,
k30fps = 1 << 4,
k60fps = 1 << 5,
kProxyWifi = 1 << 6,
kProxyEvil = 1 << 7,
};
struct TimeData {
TimeData(uint16 frame_no_, base::TimeTicks render_time_) :
frame_no(frame_no_),
render_time(render_time_) {
}
uint16 frame_no;
base::TimeTicks render_time;
};
class MeanAndError {
public:
MeanAndError() {}
explicit MeanAndError(const std::vector<double>& values) {
double sum = 0.0;
double sqr_sum = 0.0;
num_values = values.size();
if (num_values) {
for (size_t i = 0; i < num_values; i++) {
sum += values[i];
sqr_sum += values[i] * values[i];
}
mean = sum / num_values;
std_dev = sqrt(std::max(0.0, num_values * sqr_sum - sum * sum)) /
num_values;
}
}
std::string AsString() const {
return base::StringPrintf("%f,%f", mean, std_dev);
}
void Print(const std::string& measurement,
const std::string& modifier,
const std::string& trace,
const std::string& unit) {
if (num_values >= 20) {
perf_test::PrintResultMeanAndError(measurement,
modifier,
trace,
AsString(),
unit,
true);
} else {
LOG(ERROR) << "Not enough events for "
<< measurement << " " << modifier << " " << trace;
}
}
size_t num_values;
double mean;
double std_dev;
};
static MeanAndError AnalyzeJitter(const std::vector<TimeData>& data) {
CHECK_GT(data.size(), 1UL);
VLOG(0) << "Jitter analyzis on " << data.size() << " values.";
std::vector<double> deltas;
double sum = 0.0;
for (size_t i = 1; i < data.size(); i++) {
double delta = (data[i].render_time -
data[i - 1].render_time).InMillisecondsF();
deltas.push_back(delta);
sum += delta;
}
double mean = sum / deltas.size();
for (size_t i = 0; i < deltas.size(); i++) {
deltas[i] = fabs(mean - deltas[i]);
}
return MeanAndError(deltas);
}
class TestPatternReceiver : public media::cast::InProcessReceiver {
public:
explicit TestPatternReceiver(
const scoped_refptr<media::cast::CastEnvironment>& cast_environment,
const net::IPEndPoint& local_end_point)
: InProcessReceiver(cast_environment,
local_end_point,
net::IPEndPoint(),
media::cast::GetDefaultAudioReceiverConfig(),
media::cast::GetDefaultVideoReceiverConfig()) {
}
typedef std::map<uint16, base::TimeTicks> TimeMap;
void MapFrameTimes(const std::vector<TimeData>& events, TimeMap* map) {
for (size_t i = kSkipEvents; i < events.size(); i++) {
base::TimeTicks& frame_tick = (*map)[events[i].frame_no];
if (frame_tick.is_null()) {
frame_tick = events[i].render_time;
} else {
frame_tick = std::min(events[i].render_time, frame_tick);
}
}
}
void Analyze(const std::string& name, const std::string& modifier) {
TimeMap audio_frame_times, video_frame_times;
MapFrameTimes(audio_events_, &audio_frame_times);
MapFrameTimes(video_events_, &video_frame_times);
std::vector<double> deltas;
for (TimeMap::const_iterator i = audio_frame_times.begin();
i != audio_frame_times.end();
++i) {
TimeMap::const_iterator j = video_frame_times.find(i->first);
if (j != video_frame_times.end()) {
deltas.push_back((i->second - j->second).InMillisecondsF());
}
}
MeanAndError(deltas).Print(name, modifier, "av_sync", "ms");
AnalyzeJitter(audio_events_).Print(name, modifier, "audio_jitter", "ms");
AnalyzeJitter(video_events_).Print(name, modifier, "video_jitter", "ms");
}
private:
virtual void OnAudioFrame(scoped_ptr<media::cast::PcmAudioFrame> audio_frame,
const base::TimeTicks& playout_time) OVERRIDE {
CHECK(cast_env()->CurrentlyOn(media::cast::CastEnvironment::MAIN));
if (audio_frame->samples.empty()) {
NOTREACHED() << "OnAudioFrame called with no samples?!?";
return;
}
std::vector<int16> samples;
for (size_t i = 0;
i < audio_frame->samples.size();
i += audio_frame->channels) {
samples.push_back(audio_frame->samples[i]);
}
uint16 frame_no;
if (media::cast::DecodeTimestamp(samples, &frame_no)) {
audio_events_.push_back(TimeData(frame_no, playout_time));
} else {
VLOG(0) << "Failed to decode audio timestamp!";
}
}
virtual void OnVideoFrame(const scoped_refptr<media::VideoFrame>& video_frame,
const base::TimeTicks& render_time) OVERRIDE {
CHECK(cast_env()->CurrentlyOn(media::cast::CastEnvironment::MAIN));
TRACE_EVENT_INSTANT1(
"mirroring", "TestPatternReceiver::OnVideoFrame",
TRACE_EVENT_SCOPE_THREAD,
"render_time", render_time.ToInternalValue());
uint16 frame_no;
if (media::cast::test::DecodeBarcode(video_frame, &frame_no)) {
video_events_.push_back(TimeData(frame_no, render_time));
} else {
VLOG(0) << "Failed to decode barcode!";
}
}
std::vector<TimeData> audio_events_;
std::vector<TimeData> video_events_;
DISALLOW_COPY_AND_ASSIGN(TestPatternReceiver);
};
class CastV2PerformanceTest
: public ExtensionApiTest,
public testing::WithParamInterface<int> {
public:
CastV2PerformanceTest() {}
bool HasFlag(TestFlags flag) const {
return (GetParam() & flag) == flag;
}
bool IsGpuAvailable() const {
return CommandLine::ForCurrentProcess()->HasSwitch("enable-gpu");
}
std::string GetSuffixForTestFlags() {
std::string suffix;
if (HasFlag(kUseGpu))
suffix += "_gpu";
if (HasFlag(kDisableVsync))
suffix += "_novsync";
if (HasFlag(kSmallWindow))
suffix += "_small";
if (HasFlag(k24fps))
suffix += "_24fps";
if (HasFlag(k30fps))
suffix += "_30fps";
if (HasFlag(k60fps))
suffix += "_60fps";
if (HasFlag(kProxyWifi))
suffix += "_wifi";
if (HasFlag(kProxyEvil))
suffix += "_evil";
return suffix;
}
int getfps() {
if (HasFlag(k24fps))
return 24;
if (HasFlag(k30fps))
return 30;
if (HasFlag(k60fps))
return 60;
NOTREACHED();
return 0;
}
net::IPEndPoint GetFreeLocalPort() {
net::IPAddressNumber localhost;
localhost.push_back(127);
localhost.push_back(0);
localhost.push_back(0);
localhost.push_back(1);
scoped_ptr<net::UDPSocket> receive_socket(
new net::UDPSocket(net::DatagramSocket::DEFAULT_BIND,
net::RandIntCallback(),
NULL,
net::NetLog::Source()));
receive_socket->AllowAddressReuse();
CHECK_EQ(net::OK, receive_socket->Bind(net::IPEndPoint(localhost, 0)));
net::IPEndPoint endpoint;
CHECK_EQ(net::OK, receive_socket->GetLocalAddress(&endpoint));
return endpoint;
}
virtual void SetUp() OVERRIDE {
EnablePixelOutput();
ExtensionApiTest::SetUp();
}
virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
command_line->AppendSwitch(switches::kAllowFileAccessFromFiles);
if (HasFlag(kSmallWindow)) {
command_line->AppendSwitchASCII(switches::kWindowSize, "800,600");
} else {
command_line->AppendSwitchASCII(switches::kWindowSize, "2000,1500");
}
if (!HasFlag(kUseGpu)) {
command_line->AppendSwitch(switches::kDisableGpu);
} else {
command_line->AppendSwitch(switches::kForceCompositingMode);
}
if (HasFlag(kDisableVsync))
command_line->AppendSwitch(switches::kDisableGpuVsync);
command_line->AppendSwitchASCII(
extensions::switches::kWhitelistedExtensionID,
kExtensionId);
ExtensionApiTest::SetUpCommandLine(command_line);
}
void GetTraceEvents(trace_analyzer::TraceAnalyzer* analyzer,
const std::string& event_name,
trace_analyzer::TraceEventVector* events) {
trace_analyzer::Query query =
trace_analyzer::Query::EventNameIs(event_name) &&
(trace_analyzer::Query::EventPhaseIs(TRACE_EVENT_PHASE_BEGIN) ||
trace_analyzer::Query::EventPhaseIs(TRACE_EVENT_PHASE_ASYNC_BEGIN) ||
trace_analyzer::Query::EventPhaseIs(TRACE_EVENT_PHASE_FLOW_BEGIN) ||
trace_analyzer::Query::EventPhaseIs(TRACE_EVENT_PHASE_INSTANT));
analyzer->FindEvents(query, events);
}
typedef std::pair<std::string, double> EventMapKey;
typedef std::map<EventMapKey, const trace_analyzer::TraceEvent*> EventMap;
void IndexEvents(trace_analyzer::TraceAnalyzer* analyzer,
const std::string& event_name,
EventMap* event_map) {
trace_analyzer::TraceEventVector events;
GetTraceEvents(analyzer, event_name, &events);
for (size_t i = 0; i < events.size(); i++) {
std::map<std::string, double>::const_iterator j;
for (j = events[i]->arg_numbers.begin();
j != events[i]->arg_numbers.end();
++j) {
(*event_map)[*j] = events[i];
}
}
}
const trace_analyzer::TraceEvent* FindNextEvent(
const EventMap& event_map,
std::vector<const trace_analyzer::TraceEvent*> prev_events,
std::string key_name) {
EventMapKey key;
for (size_t i = prev_events.size(); i;) {
--i;
std::map<std::string, double>::const_iterator j =
prev_events[i]->arg_numbers.find(key_name);
if (j != prev_events[i]->arg_numbers.end()) {
key = *j;
break;
}
}
EventMap::const_iterator i = event_map.lower_bound(key);
if (i == event_map.end())
return NULL;
if (i->first.second == key.second)
return i->second;
if (key_name != "time_delta")
return NULL;
if (fabs(i->first.second - key.second) < 1000)
return i->second;
if (i == event_map.begin())
return NULL;
i--;
if (fabs(i->first.second - key.second) < 1000)
return i->second;
return NULL;
}
void OutputMeasurement(const std::string& test_name,
const std::vector<std::vector<double> > data,
const std::string& measurement_name,
int col_a,
int col_b) {
std::vector<double> tmp;
for (size_t i = 0; i < data.size(); i++) {
tmp.push_back((data[i][col_b] - data[i][col_a]) / 1000.0);
}
return MeanAndError(tmp).Print(test_name,
GetSuffixForTestFlags(),
measurement_name,
"ms");
}
void AnalyzeLatency(const std::string& test_name,
trace_analyzer::TraceAnalyzer* analyzer) {
EventMap onbuffer, sink, inserted, encoded, transmitted, decoded, done;
IndexEvents(analyzer, "OnBufferReceived", &onbuffer);
IndexEvents(analyzer, "MediaStreamVideoSink::OnVideoFrame", &sink);
IndexEvents(analyzer, "InsertRawVideoFrame", &inserted);
IndexEvents(analyzer, "VideoFrameEncoded", &encoded);
IndexEvents(analyzer, "PullEncodedVideoFrame", &transmitted);
IndexEvents(analyzer, "FrameDecoded", &decoded);
IndexEvents(analyzer, "TestPatternReceiver::OnVideoFrame", &done);
std::vector<std::pair<EventMap*, std::string> > event_maps;
event_maps.push_back(std::make_pair(&onbuffer, "timestamp"));
event_maps.push_back(std::make_pair(&sink, "time_delta"));
event_maps.push_back(std::make_pair(&inserted, "timestamp"));
event_maps.push_back(std::make_pair(&encoded, "rtp_timestamp"));
event_maps.push_back(std::make_pair(&transmitted, "rtp_timestamp"));
event_maps.push_back(std::make_pair(&decoded, "rtp_timestamp"));
event_maps.push_back(std::make_pair(&done, "render_time"));
trace_analyzer::TraceEventVector capture_events;
GetTraceEvents(analyzer, "Capture" , &capture_events);
std::vector<std::vector<double> > traced_frames;
for (size_t i = kSkipEvents; i < capture_events.size(); i++) {
std::vector<double> times;
const trace_analyzer::TraceEvent *event = capture_events[i];
times.push_back(event->timestamp);
event = event->other_event;
if (!event) {
continue;
}
times.push_back(event->timestamp);
std::vector<const trace_analyzer::TraceEvent*> prev_events;
prev_events.push_back(event);
for (size_t j = 0; j < event_maps.size(); j++) {
event = FindNextEvent(*event_maps[j].first,
prev_events,
event_maps[j].second);
if (!event) {
break;
}
prev_events.push_back(event);
times.push_back(event->timestamp);
}
if (event) {
traced_frames.push_back(times);
}
}
OutputMeasurement(test_name, traced_frames, "total_latency", 0, 8);
OutputMeasurement(test_name, traced_frames, "capture_duration", 0, 1);
OutputMeasurement(test_name, traced_frames, "send_to_renderer", 1, 3);
OutputMeasurement(test_name, traced_frames, "encode", 3, 5);
OutputMeasurement(test_name, traced_frames, "transmit", 5, 6);
OutputMeasurement(test_name, traced_frames, "decode", 6, 7);
OutputMeasurement(test_name, traced_frames, "cast_latency", 3, 8);
}
MeanAndError AnalyzeTraceDistance(trace_analyzer::TraceAnalyzer* analyzer,
const std::string& event_name) {
trace_analyzer::TraceEventVector events;
GetTraceEvents(analyzer, event_name, &events);
std::vector<double> deltas;
for (size_t i = kSkipEvents + 1; i < events.size(); ++i) {
double delta_micros = events[i]->timestamp - events[i - 1]->timestamp;
deltas.push_back(delta_micros / 1000.0);
}
return MeanAndError(deltas);
}
void RunTest(const std::string& test_name) {
if (HasFlag(kUseGpu) && !IsGpuAvailable()) {
LOG(WARNING) <<
"Test skipped: requires gpu. Pass --enable-gpu on the command "
"line if use of GPU is desired.";
return;
}
ASSERT_EQ(1,
(HasFlag(k24fps) ? 1 : 0) +
(HasFlag(k30fps) ? 1 : 0) +
(HasFlag(k60fps) ? 1 : 0));
net::IPEndPoint receiver_end_point = GetFreeLocalPort();
scoped_refptr<media::cast::StandaloneCastEnvironment> cast_environment(
new media::cast::StandaloneCastEnvironment);
TestPatternReceiver* const receiver =
new TestPatternReceiver(cast_environment, receiver_end_point);
receiver->Start();
scoped_ptr<media::cast::test::UDPProxy> udp_proxy;
if (HasFlag(kProxyWifi) || HasFlag(kProxyEvil)) {
net::IPEndPoint proxy_end_point = GetFreeLocalPort();
if (HasFlag(kProxyWifi)) {
udp_proxy = media::cast::test::UDPProxy::Create(
proxy_end_point,
receiver_end_point,
media::cast::test::WifiNetwork().Pass(),
media::cast::test::WifiNetwork().Pass(),
NULL);
} else if (HasFlag(kProxyEvil)) {
udp_proxy = media::cast::test::UDPProxy::Create(
proxy_end_point,
receiver_end_point,
media::cast::test::EvilNetwork().Pass(),
media::cast::test::EvilNetwork().Pass(),
NULL);
}
receiver_end_point = proxy_end_point;
}
std::string json_events;
ASSERT_TRUE(tracing::BeginTracing("test_fps,mirroring,cast_perf_test"));
const std::string page_url = base::StringPrintf(
"performance%d.html?port=%d",
getfps(),
receiver_end_point.port());
ASSERT_TRUE(RunExtensionSubtest("cast_streaming", page_url)) << message_;
ASSERT_TRUE(tracing::EndTracing(&json_events));
receiver->Stop();
cast_environment->Shutdown();
scoped_ptr<trace_analyzer::TraceAnalyzer> analyzer;
analyzer.reset(trace_analyzer::TraceAnalyzer::Create(json_events));
analyzer->AssociateAsyncBeginEndEvents();
MeanAndError sw_frame_data = AnalyzeTraceDistance(analyzer.get(),
"TestFrameTickSW");
MeanAndError frame_data = AnalyzeTraceDistance(analyzer.get(),
"TestFrameTickGPU");
if (frame_data.num_values == 0) {
frame_data = sw_frame_data;
}
EXPECT_GT(frame_data.num_values, 0UL);
frame_data.Print(test_name,
GetSuffixForTestFlags(),
"time_between_frames",
"ms");
MeanAndError capture_data = AnalyzeTraceDistance(analyzer.get(), "Capture");
capture_data.Print(test_name,
GetSuffixForTestFlags(),
"time_between_captures",
"ms");
receiver->Analyze(test_name, GetSuffixForTestFlags());
AnalyzeLatency(test_name, analyzer.get());
}
};
}
IN_PROC_BROWSER_TEST_P(CastV2PerformanceTest, Performance) {
RunTest("CastV2Performance");
}
INSTANTIATE_TEST_CASE_P(
,
CastV2PerformanceTest,
testing::Values(
kUseGpu | k24fps,
kUseGpu | k30fps,
kUseGpu | k60fps,
kUseGpu | k24fps | kDisableVsync,
kUseGpu | k30fps | kProxyWifi,
kUseGpu | k30fps | kProxyEvil));