summaryrefslogtreecommitdiff
path: root/chromium/chrome/browser/media/webrtc/webrtc_video_display_perf_browsertest.cc
blob: 1b90baade1ab7908b1bd34e10de4a9eb70dde496 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <algorithm>

#include "base/json/json_reader.h"
#include "base/strings/string_tokenizer.h"
#include "base/strings/stringprintf.h"
#include "base/test/trace_event_analyzer.h"
#include "build/build_config.h"
#include "chrome/browser/media/webrtc/webrtc_browsertest_base.h"
#include "chrome/browser/media/webrtc/webrtc_browsertest_common.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_tabstrip.h"
#include "chrome/test/base/tracing.h"
#include "chrome/test/base/ui_test_utils.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 "content/public/test/browser_test_utils.h"
#include "media/base/media_switches.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "testing/perf/perf_result_reporter.h"
#include "third_party/blink/public/common/features.h"
#include "ui/gl/gl_switches.h"

using trace_analyzer::TraceEvent;
using trace_analyzer::TraceEventVector;
using trace_analyzer::Query;

namespace {

// Trace events.
static const char kStartRenderEventName[] =
    "RemoteVideoSourceDelegate::RenderFrame";
static const char kEnqueueFrameEventName[] =
    "WebMediaPlayerMSCompositor::EnqueueFrame";
static const char kSetFrameEventName[] =
    "WebMediaPlayerMSCompositor::SetCurrentFrame";
static const char kGetFrameEventName[] =
    "WebMediaPlayerMSCompositor::GetCurrentFrame";
static const char kVideoResourceEventName[] =
    "VideoResourceUpdater::ObtainFrameResources";
static const char kVsyncEventName[] = "Display::DrawAndSwap";

// VideoFrameSubmitter dumps the delay from the handover of a decoded remote
// VideoFrame from webrtc to the moment the OS acknowledges the swap buffers.
static const char kVideoFrameSubmitterEventName[] = "VideoFrameSubmitter";

static const char kEventMatchKey[] = "Timestamp";
static const char kMainWebrtcTestHtmlPage[] =
    "/webrtc/webrtc_video_display_perf_test.html";

constexpr char kMetricPrefixVideoDisplayPerf[] = "WebRtcVideoDisplayPerf.";
constexpr char kMetricSkippedFramesPercent[] = "skipped_frames";
constexpr char kMetricPassingToRenderAlgoLatencyUs[] =
    "passing_to_render_algorithm_latency";
constexpr char kMetricRenderAlgoLatencyUs[] = "render_algorithm_latency";
constexpr char kMetricCompositorPickingFrameLatencyUs[] =
    "compositor_picking_frame_latency";
constexpr char kMetricCompositorResourcePreparationLatencyUs[] =
    "compositor_resource_preparation_latency";
constexpr char kMetricVsyncLatencyUs[] = "vsync_latency";
constexpr char kMetricTotalControlledLatencyUs[] = "total_controlled_latency";
constexpr char kMetricTotalLatencyUs[] = "total_latency";
constexpr char kMetricPostDecodeToRasterLatencyUs[] =
    "post_decode_to_raster_latency";
constexpr char kMetricWebRtcDecodeLatencyUs[] = "webrtc_decode_latency";

perf_test::PerfResultReporter SetUpReporter(const std::string& story) {
  perf_test::PerfResultReporter reporter(kMetricPrefixVideoDisplayPerf, story);
  reporter.RegisterImportantMetric(kMetricSkippedFramesPercent, "percent");
  reporter.RegisterImportantMetric(kMetricPassingToRenderAlgoLatencyUs, "us");
  reporter.RegisterImportantMetric(kMetricRenderAlgoLatencyUs, "us");
  reporter.RegisterImportantMetric(kMetricCompositorPickingFrameLatencyUs,
                                   "us");
  reporter.RegisterImportantMetric(
      kMetricCompositorResourcePreparationLatencyUs, "us");
  reporter.RegisterImportantMetric(kMetricVsyncLatencyUs, "us");
  reporter.RegisterImportantMetric(kMetricTotalControlledLatencyUs, "us");
  reporter.RegisterImportantMetric(kMetricTotalLatencyUs, "us");
  reporter.RegisterImportantMetric(kMetricPostDecodeToRasterLatencyUs, "us");
  reporter.RegisterImportantMetric(kMetricWebRtcDecodeLatencyUs, "us");
  return reporter;
}

struct VideoDisplayPerfTestConfig {
  int width;
  int height;
  int fps;
  bool disable_render_smoothness_algorithm;
};

std::string VectorToString(const std::vector<double>& values) {
  std::string ret = "";
  for (double val : values) {
    ret += base::StringPrintf("%.0lf,", val);
  }
  // Strip of trailing comma.
  return ret.substr(0, ret.length() - 1);
}

void FindEvents(trace_analyzer::TraceAnalyzer* analyzer,
                const std::string& event_name,
                const Query& base_query,
                TraceEventVector* events) {
  Query query = Query::EventNameIs(event_name) && base_query;
  analyzer->FindEvents(query, events);
}

void AssociateEvents(trace_analyzer::TraceAnalyzer* analyzer,
                     const std::vector<std::string>& event_names,
                     const std::string& match_string,
                     const Query& base_query) {
  for (size_t i = 0; i < event_names.size() - 1; ++i) {
    Query begin = Query::EventNameIs(event_names[i]);
    Query end = Query::EventNameIs(event_names[i + 1]);
    Query match(Query::EventArg(match_string) == Query::OtherArg(match_string));
    analyzer->AssociateEvents(begin, end, base_query && match);
  }
}

content::WebContents* OpenWebrtcInternalsTab(Browser* browser) {
  chrome::AddTabAt(browser, GURL(), -1, true);
  ui_test_utils::NavigateToURL(browser, GURL("chrome://webrtc-internals"));
  return browser->tab_strip_model()->GetActiveWebContents();
}

std::vector<double> ParseGoogMaxDecodeFromWebrtcInternalsTab(
    const std::string& webrtc_internals_stats_json) {
  std::vector<double> goog_decode_ms;

  std::unique_ptr<base::Value> parsed_json =
      base::JSONReader::ReadDeprecated(webrtc_internals_stats_json);
  base::DictionaryValue* dictionary = nullptr;
  if (!parsed_json.get() || !parsed_json->GetAsDictionary(&dictionary))
    return goog_decode_ms;
  ignore_result(parsed_json.release());

  // |dictionary| should have exactly two entries, one per ssrc.
  if (!dictionary || dictionary->size() != 2u)
    return goog_decode_ms;

  // Only a given |dictionary| entry will have a "stats" entry that has a key
  // that ends with "recv-googMaxDecodeMs" inside (it will start with the ssrc
  // id, but we don't care about that). Then collect the string of "values" out
  // of that key and convert those into the |goog_decode_ms| vector of doubles.
  for (const auto& dictionary_entry : *dictionary) {
    for (const auto& ssrc_entry : dictionary_entry.second->DictItems()) {
      if (ssrc_entry.first != "stats")
        continue;

      for (const auto& stat_entry : ssrc_entry.second.DictItems()) {
        if (!base::EndsWith(stat_entry.first, "recv-googMaxDecodeMs",
                            base::CompareCase::SENSITIVE)) {
          continue;
        }
        base::Value* values_entry = stat_entry.second.FindKey({"values"});
        if (!values_entry)
          continue;
        base::StringTokenizer values_tokenizer(values_entry->GetString(),
                                               "[,]");
        while (values_tokenizer.GetNext()) {
          if (values_tokenizer.token_is_delim())
            continue;
          goog_decode_ms.push_back(atof(values_tokenizer.token().c_str()) *
                                   base::Time::kMicrosecondsPerMillisecond);
        }
      }
    }
  }
  return goog_decode_ms;
}

}  // anonymous namespace

// Tests the performance of Chrome displaying remote video.
//
// This test creates a WebRTC peer connection between two tabs and measures the
// trace events listed in the beginning of this file on the tab receiving
// remote video. In order to cut down from the encode cost, the tab receiving
// remote video does not send any video to its peer.
//
// This test traces certain categories for a period of time. It follows the
// lifetime of a single video frame by synchronizing on the timestamps values
// attached to trace events. Then, it calculates the duration and related stats.
class WebRtcVideoDisplayPerfBrowserTest
    : public WebRtcTestBase,
      public testing::WithParamInterface<
          std::tuple<gfx::Size /* resolution */,
                     int /* fps */,
                     bool /* disable_render_smoothness_algorithm */>> {
 public:
  WebRtcVideoDisplayPerfBrowserTest() {
    const auto& params = GetParam();
    const gfx::Size& resolution = std::get<0>(params);
    test_config_ = {resolution.width(), resolution.height(),
                    std::get<1>(params), std::get<2>(params)};
  }

  void SetUpInProcessBrowserTestFixture() override {
    DetectErrorsInJavaScript();
  }

  void SetUpCommandLine(base::CommandLine* command_line) override {
    command_line->AppendSwitch(switches::kUseFakeUIForMediaStream);
    command_line->AppendSwitchASCII(
        switches::kUseFakeDeviceForMediaStream,
        base::StringPrintf("fps=%d", test_config_.fps));
    if (test_config_.disable_render_smoothness_algorithm)
      command_line->AppendSwitch(switches::kDisableRTCSmoothnessAlgorithm);
    command_line->AppendSwitch(switches::kUseGpuInTests);
  }

  void TestVideoDisplayPerf(const std::string& video_codec) {
    ASSERT_TRUE(embedded_test_server()->Start());
    // chrome:webrtc-internals doesn't start tracing anything until the
    // connection(s) are up.
    content::WebContents* webrtc_internals_tab =
        OpenWebrtcInternalsTab(browser());
    EXPECT_TRUE(content::ExecuteScript(
        webrtc_internals_tab,
        "currentGetStatsMethod = OPTION_GETSTATS_LEGACY"));

    content::WebContents* left_tab =
        OpenPageAndGetUserMediaInNewTabWithConstraints(
            embedded_test_server()->GetURL(kMainWebrtcTestHtmlPage),
            base::StringPrintf(
                "{audio: true, video: {mandatory: {minWidth: %d, maxWidth: %d, "
                "minHeight: %d, maxHeight: %d}}}",
                test_config_.width, test_config_.width, test_config_.height,
                test_config_.height));
    content::WebContents* right_tab =
        OpenPageAndGetUserMediaInNewTabWithConstraints(
            embedded_test_server()->GetURL(kMainWebrtcTestHtmlPage),
            "{audio: true, video: false}");
    const int process_id =
        right_tab->GetRenderViewHost()->GetProcess()->GetProcess().Pid();

    const std::string disable_cpu_adaptation_constraint(
        "{'optional': [{'googCpuOveruseDetection': false}]}");
    SetupPeerconnectionWithConstraintsAndLocalStream(
        left_tab, disable_cpu_adaptation_constraint);
    SetupPeerconnectionWithConstraintsAndLocalStream(
        right_tab, disable_cpu_adaptation_constraint);

    if (!video_codec.empty()) {
      constexpr bool kPreferHwVideoCodec = true;
      SetDefaultVideoCodec(left_tab, video_codec, kPreferHwVideoCodec);
      SetDefaultVideoCodec(right_tab, video_codec, kPreferHwVideoCodec);
    }
    NegotiateCall(left_tab, right_tab);

    StartDetectingVideo(right_tab, "remote-view");
    WaitForVideoToPlay(right_tab);
    // Run the connection a bit to ramp up.
    test::SleepInJavascript(left_tab, 10000);

    ASSERT_TRUE(tracing::BeginTracing("media,viz,webrtc"));
    // Run the connection for 5 seconds to collect metrics.
    test::SleepInJavascript(left_tab, 5000);

    const std::string webrtc_internals_stats_json = ExecuteJavascript(
        "window.domAutomationController.send("
        "    JSON.stringify(peerConnectionDataStore));",
        webrtc_internals_tab);
    webrtc_decode_latencies_ =
        ParseGoogMaxDecodeFromWebrtcInternalsTab(webrtc_internals_stats_json);
    chrome::CloseWebContents(browser(), webrtc_internals_tab, false);

    std::string json_events;
    ASSERT_TRUE(tracing::EndTracing(&json_events));
    std::unique_ptr<trace_analyzer::TraceAnalyzer> analyzer(
        trace_analyzer::TraceAnalyzer::Create(json_events));
    analyzer->AssociateAsyncBeginEndEvents();

    HangUp(left_tab);
    HangUp(right_tab);
    chrome::CloseWebContents(browser(), left_tab, false);
    chrome::CloseWebContents(browser(), right_tab, false);

    ASSERT_TRUE(CalculatePerfResults(analyzer.get(), process_id));
    PrintResults(video_codec);
  }

 private:
  bool CalculatePerfResults(trace_analyzer::TraceAnalyzer* analyzer,
                            int render_process_id) {
    Query match_process_id = Query::EventPidIs(render_process_id);
    const std::vector<std::string> chain_of_events = {
        kStartRenderEventName, kEnqueueFrameEventName, kSetFrameEventName,
        kGetFrameEventName, kVideoResourceEventName};
    AssociateEvents(analyzer, chain_of_events,
                    kEventMatchKey, match_process_id);

    TraceEventVector start_render_events;
    FindEvents(analyzer, kStartRenderEventName, match_process_id,
               &start_render_events);
    if (start_render_events.empty())
      return false;

    // We are only interested in vsync events coming after the first render
    // event. Earlier ones are already missed.
    Query after_first_render_event =
        Query::EventTime() >
        Query::Double(start_render_events.front()->timestamp);
    TraceEventVector vsync_events;
    FindEvents(analyzer, kVsyncEventName, after_first_render_event,
               &vsync_events);
    if (vsync_events.empty())
      return false;

    size_t found_vsync_index = 0;
    size_t skipped_frame_count = 0;
    for (const auto* event : start_render_events) {
      const double start = event->timestamp;

      const TraceEvent* enqueue_frame_event = event->other_event;
      if (!enqueue_frame_event) {
        skipped_frame_count++;
        continue;
      }
      const double enqueue_frame_duration =
          enqueue_frame_event->timestamp - start;

      const TraceEvent* set_frame_event = enqueue_frame_event->other_event;
      if (!set_frame_event) {
        skipped_frame_count++;
        continue;
      }
      const double set_frame_duration =
          set_frame_event->timestamp - enqueue_frame_event->timestamp;

      const TraceEvent* get_frame_event = set_frame_event->other_event;
      if (!get_frame_event) {
        skipped_frame_count++;
        continue;
      }
      const double get_frame_duration =
          get_frame_event->timestamp - set_frame_event->timestamp;

      const TraceEvent* video_resource_event = get_frame_event->other_event;
      if (!video_resource_event) {
        skipped_frame_count++;
        continue;
      }
      const double resource_ready_duration =
          video_resource_event->timestamp - get_frame_event->timestamp;

      // We try to find the closest vsync event after video resource is ready.
      const bool found_vsync = FindFirstOf(
          vsync_events,
          Query::EventTime() > Query::Double(video_resource_event->timestamp +
                                             video_resource_event->duration),
          found_vsync_index, &found_vsync_index);
      if (!found_vsync) {
        skipped_frame_count++;
        continue;
      }
      const double vsync_duration = vsync_events[found_vsync_index]->timestamp -
                                    video_resource_event->timestamp;
      const double total_duration =
          vsync_events[found_vsync_index]->timestamp - start;

      enqueue_frame_durations_.push_back(enqueue_frame_duration);
      set_frame_durations_.push_back(set_frame_duration);
      get_frame_durations_.push_back(get_frame_duration);
      resource_ready_durations_.push_back(resource_ready_duration);
      vsync_durations_.push_back(vsync_duration);
      total_controlled_durations_.push_back(total_duration -
                                            set_frame_duration);
      total_durations_.push_back(total_duration);
    }

    if (start_render_events.size() == skipped_frame_count)
      return false;

    // Calculate the percentage by dividing by the number of frames received.
    skipped_frame_percentage_ =
        100.0 * skipped_frame_count / start_render_events.size();

    // |kVideoFrameSubmitterEventName| is in itself an ASYNC latency measurement
    // from the point where the remote video decode is available (i.e.
    // kStartRenderEventName) until the platform-dependent swap buffers, so by
    // definition is larger than the |total_duration|.
    TraceEventVector video_frame_submitter_events;
    analyzer->FindEvents(Query::MatchAsyncBeginWithNext() &&
                             Query::EventNameIs(kVideoFrameSubmitterEventName),
                         &video_frame_submitter_events);
    for (const auto* event : video_frame_submitter_events) {
      // kVideoFrameSubmitterEventName is divided into a BEGIN, a PAST and an
      // END steps. AssociateAsyncBeginEndEvents paired BEGIN with PAST, but we
      // have to get to the END. Note that if there's no intermediate PAST, it
      // means this wasn't a remote feed VideoFrame, we should not have those in
      // this test. If there's no END, then tracing was cut short.
      if (!event->has_other_event() ||
          event->other_event->phase != TRACE_EVENT_PHASE_ASYNC_STEP_PAST ||
          !event->other_event->has_other_event()) {
        continue;
      }
      const auto begin = event->timestamp;
      const auto end = event->other_event->other_event->timestamp;
      video_frame_submmitter_latencies_.push_back(end - begin);
    }

    return true;
  }

  void PrintResults(const std::string& video_codec) {
    std::string smoothness_indicator =
        test_config_.disable_render_smoothness_algorithm ? "_DisableSmoothness"
                                                         : "";
    std::string story = base::StringPrintf(
        "%s_%dp%df%s", video_codec.c_str(), test_config_.height,
        test_config_.fps, smoothness_indicator.c_str());
    auto reporter = SetUpReporter(story);
    reporter.AddResult(kMetricSkippedFramesPercent,
                       base::StringPrintf("%.2lf", skipped_frame_percentage_));
    // We identify intervals in a way that can help us easily bisect the source
    // of added latency in case of a regression. From these intervals, "Render
    // Algorithm" can take random amount of times based on the vsync cycle it is
    // closest to. Therefore, "Total Controlled Latency" refers to the total
    // times without that section for semi-consistent results.
    reporter.AddResultList(kMetricPassingToRenderAlgoLatencyUs,
                           VectorToString(enqueue_frame_durations_));
    reporter.AddResultList(kMetricRenderAlgoLatencyUs,
                           VectorToString(set_frame_durations_));
    reporter.AddResultList(kMetricCompositorPickingFrameLatencyUs,
                           VectorToString(get_frame_durations_));
    reporter.AddResultList(kMetricCompositorResourcePreparationLatencyUs,
                           VectorToString(resource_ready_durations_));
    reporter.AddResultList(kMetricVsyncLatencyUs,
                           VectorToString(vsync_durations_));
    reporter.AddResultList(kMetricTotalControlledLatencyUs,
                           VectorToString(total_controlled_durations_));
    reporter.AddResultList(kMetricTotalLatencyUs,
                           VectorToString(total_durations_));

    reporter.AddResultList(kMetricPostDecodeToRasterLatencyUs,
                           VectorToString(video_frame_submmitter_latencies_));
    reporter.AddResultList(kMetricWebRtcDecodeLatencyUs,
                           VectorToString(webrtc_decode_latencies_));
  }

  VideoDisplayPerfTestConfig test_config_;
  // Containers for test results.
  double skipped_frame_percentage_ = 0;
  std::vector<double> enqueue_frame_durations_;
  std::vector<double> set_frame_durations_;
  std::vector<double> get_frame_durations_;
  std::vector<double> resource_ready_durations_;
  std::vector<double> vsync_durations_;
  std::vector<double> total_controlled_durations_;
  std::vector<double> total_durations_;

  // These two put together represent the whole delay from encoded video frames
  // to OS swap buffers call (or callback, depending on the platform).
  std::vector<double> video_frame_submmitter_latencies_;
  std::vector<double> webrtc_decode_latencies_;
};

// TODO(https://crbug.com/993020): Fix flakes on Windows bots.
#if defined(OS_WIN)
#define MAYBE_WebRtcVideoDisplayPerfBrowserTests \
  DISABLED_WebRtcVideoDisplayPerfBrowserTests
#else
#define MAYBE_WebRtcVideoDisplayPerfBrowserTests \
  WebRtcVideoDisplayPerfBrowserTests
#endif
INSTANTIATE_TEST_SUITE_P(MAYBE_WebRtcVideoDisplayPerfBrowserTests,
                         WebRtcVideoDisplayPerfBrowserTest,
                         testing::Combine(testing::Values(gfx::Size(1280, 720),
                                                          gfx::Size(1920,
                                                                    1080)),
                                          testing::Values(30, 60),
                                          testing::Bool()));

IN_PROC_BROWSER_TEST_P(WebRtcVideoDisplayPerfBrowserTest,
                       MANUAL_TestVideoDisplayPerfVP9) {
  TestVideoDisplayPerf("VP9");
}

#if BUILDFLAG(RTC_USE_H264)
IN_PROC_BROWSER_TEST_P(WebRtcVideoDisplayPerfBrowserTest,
                       MANUAL_TestVideoDisplayPerfH264) {
  if (!base::FeatureList::IsEnabled(
          blink::features::kWebRtcH264WithOpenH264FFmpeg)) {
    LOG(WARNING) << "Run-time feature WebRTC-H264WithOpenH264FFmpeg disabled. "
                    "Skipping WebRtcVideoDisplayPerfBrowserTest.MANUAL_"
                    "TestVideoDisplayPerfH264 "
                    "(test \"OK\")";
    return;
  }
  TestVideoDisplayPerf("H264");
}
#endif  // BUILDFLAG(RTC_USE_H264)