chrome_webrtc_video_quality_browsertest.cc revision 58537e28ecd584eab876aee8be7156509866d23a
10ae28bd5885b5daa526898fcf7c323dc2c3e1963Angus Kong// Copyright 2013 The Chromium Authors. All rights reserved.
21d2624a10e2c559f8ba9ef89eaa30832c0a83a96Sascha Haeberling// Use of this source code is governed by a BSD-style license that can be
30ae28bd5885b5daa526898fcf7c323dc2c3e1963Angus Kong// found in the LICENSE file.
40ae28bd5885b5daa526898fcf7c323dc2c3e1963Angus Kong
50ae28bd5885b5daa526898fcf7c323dc2c3e1963Angus Kong#include "base/environment.h"
60ae28bd5885b5daa526898fcf7c323dc2c3e1963Angus Kong#include "base/file_util.h"
70ae28bd5885b5daa526898fcf7c323dc2c3e1963Angus Kong#include "base/path_service.h"
80ae28bd5885b5daa526898fcf7c323dc2c3e1963Angus Kong#include "base/process/launch.h"
90ae28bd5885b5daa526898fcf7c323dc2c3e1963Angus Kong#include "base/strings/string_split.h"
100ae28bd5885b5daa526898fcf7c323dc2c3e1963Angus Kong#include "base/strings/stringprintf.h"
110ae28bd5885b5daa526898fcf7c323dc2c3e1963Angus Kong#include "base/test/test_timeouts.h"
120ae28bd5885b5daa526898fcf7c323dc2c3e1963Angus Kong#include "base/time/time.h"
130ae28bd5885b5daa526898fcf7c323dc2c3e1963Angus Kong#include "chrome/browser/chrome_notification_types.h"
140ae28bd5885b5daa526898fcf7c323dc2c3e1963Angus Kong#include "chrome/browser/infobars/infobar.h"
150ae28bd5885b5daa526898fcf7c323dc2c3e1963Angus Kong#include "chrome/browser/infobars/infobar_service.h"
160ae28bd5885b5daa526898fcf7c323dc2c3e1963Angus Kong#include "chrome/browser/media/media_stream_infobar_delegate.h"
170ae28bd5885b5daa526898fcf7c323dc2c3e1963Angus Kong#include "chrome/browser/media/webrtc_browsertest_base.h"
180ae28bd5885b5daa526898fcf7c323dc2c3e1963Angus Kong#include "chrome/browser/media/webrtc_browsertest_common.h"
190ae28bd5885b5daa526898fcf7c323dc2c3e1963Angus Kong#include "chrome/browser/profiles/profile.h"
200ae28bd5885b5daa526898fcf7c323dc2c3e1963Angus Kong#include "chrome/browser/ui/browser.h"
210ae28bd5885b5daa526898fcf7c323dc2c3e1963Angus Kong#include "chrome/browser/ui/browser_tabstrip.h"
220ae28bd5885b5daa526898fcf7c323dc2c3e1963Angus Kong#include "chrome/browser/ui/tabs/tab_strip_model.h"
230ae28bd5885b5daa526898fcf7c323dc2c3e1963Angus Kong#include "chrome/common/chrome_switches.h"
240ae28bd5885b5daa526898fcf7c323dc2c3e1963Angus Kong#include "chrome/test/base/in_process_browser_test.h"
250ae28bd5885b5daa526898fcf7c323dc2c3e1963Angus Kong#include "chrome/test/base/ui_test_utils.h"
260ae28bd5885b5daa526898fcf7c323dc2c3e1963Angus Kong#include "chrome/test/ui/ui_test.h"
270ae28bd5885b5daa526898fcf7c323dc2c3e1963Angus Kong#include "content/public/browser/notification_service.h"
280ae28bd5885b5daa526898fcf7c323dc2c3e1963Angus Kong#include "content/public/test/browser_test_utils.h"
290ae28bd5885b5daa526898fcf7c323dc2c3e1963Angus Kong#include "net/test/embedded_test_server/embedded_test_server.h"
300ae28bd5885b5daa526898fcf7c323dc2c3e1963Angus Kong#include "net/test/python_utils.h"
310ae28bd5885b5daa526898fcf7c323dc2c3e1963Angus Kong#include "testing/perf/perf_test.h"
320ae28bd5885b5daa526898fcf7c323dc2c3e1963Angus Kong
330ae28bd5885b5daa526898fcf7c323dc2c3e1963Angus Kongstatic const base::FilePath::CharType kFrameAnalyzerExecutable[] =
340ae28bd5885b5daa526898fcf7c323dc2c3e1963Angus Kong#if defined(OS_WIN)
350ae28bd5885b5daa526898fcf7c323dc2c3e1963Angus Kong    FILE_PATH_LITERAL("frame_analyzer.exe");
360ae28bd5885b5daa526898fcf7c323dc2c3e1963Angus Kong#else
370ae28bd5885b5daa526898fcf7c323dc2c3e1963Angus Kong    FILE_PATH_LITERAL("frame_analyzer");
380ae28bd5885b5daa526898fcf7c323dc2c3e1963Angus Kong#endif
390ae28bd5885b5daa526898fcf7c323dc2c3e1963Angus Kong
4079397c21138f54fcff6ec067b44b847f1f7e0e98Carlos Hernandezstatic const base::FilePath::CharType kArgbToI420ConverterExecutable[] =
410ae28bd5885b5daa526898fcf7c323dc2c3e1963Angus Kong#if defined(OS_WIN)
420ae28bd5885b5daa526898fcf7c323dc2c3e1963Angus Kong    FILE_PATH_LITERAL("rgba_to_i420_converter.exe");
4379397c21138f54fcff6ec067b44b847f1f7e0e98Carlos Hernandez#else
4479397c21138f54fcff6ec067b44b847f1f7e0e98Carlos Hernandez    FILE_PATH_LITERAL("rgba_to_i420_converter");
4579397c21138f54fcff6ec067b44b847f1f7e0e98Carlos Hernandez#endif
461d2624a10e2c559f8ba9ef89eaa30832c0a83a96Sascha Haeberling
471d2624a10e2c559f8ba9ef89eaa30832c0a83a96Sascha Haeberlingstatic const char kHomeEnvName[] =
480ae28bd5885b5daa526898fcf7c323dc2c3e1963Angus Kong#if defined(OS_WIN)
490ae28bd5885b5daa526898fcf7c323dc2c3e1963Angus Kong    "HOMEPATH";
500ae28bd5885b5daa526898fcf7c323dc2c3e1963Angus Kong#else
510ae28bd5885b5daa526898fcf7c323dc2c3e1963Angus Kong    "HOME";
520ae28bd5885b5daa526898fcf7c323dc2c3e1963Angus Kong#endif
530ae28bd5885b5daa526898fcf7c323dc2c3e1963Angus Kong
540ae28bd5885b5daa526898fcf7c323dc2c3e1963Angus Kong// The working dir should be in the user's home folder.
550ae28bd5885b5daa526898fcf7c323dc2c3e1963Angus Kongstatic const base::FilePath::CharType kWorkingDirName[] =
560ae28bd5885b5daa526898fcf7c323dc2c3e1963Angus Kong    FILE_PATH_LITERAL("webrtc_video_quality");
570ae28bd5885b5daa526898fcf7c323dc2c3e1963Angus Kongstatic const base::FilePath::CharType kReferenceYuvFileName[] =
580ae28bd5885b5daa526898fcf7c323dc2c3e1963Angus Kong    FILE_PATH_LITERAL("reference_video.yuv");
591d2624a10e2c559f8ba9ef89eaa30832c0a83a96Sascha Haeberlingstatic const base::FilePath::CharType kCapturedYuvFileName[] =
60    FILE_PATH_LITERAL("captured_video.yuv");
61static const base::FilePath::CharType kStatsFileName[] =
62    FILE_PATH_LITERAL("stats.txt");
63static const char kMainWebrtcTestHtmlPage[] =
64    "/webrtc/webrtc_jsep01_test.html";
65static const char kCapturingWebrtcHtmlPage[] =
66    "/webrtc/webrtc_video_quality_test.html";
67static const int kVgaWidth = 640;
68static const int kVgaHeight = 480;
69
70// If you change the port number, don't forget to modify video_extraction.js
71// too!
72static const char kPyWebSocketPortNumber[] = "12221";
73
74// Test the video quality of the WebRTC output.
75//
76// Prerequisites: This test case must run on a machine with a virtual webcam
77// that plays video from the reference file located in <the running user's home
78// folder>/kWorkingDirName/kReferenceYuvFileName.
79//
80// You must also compile the chromium_builder_webrtc target before you run this
81// test to get all the tools built.
82//
83// The external compare_videos.py script also depends on two external
84// executables which must be located in the PATH when running this test.
85// * zxing (see the CPP version at https://code.google.com/p/zxing)
86// * ffmpeg 0.11.1 or compatible version (see http://www.ffmpeg.org)
87//
88// The test case will launch a custom binary (peerconnection_server) which will
89// allow two WebRTC clients to find each other.
90//
91// The test also runs several other custom binaries - rgba_to_i420 converter and
92// frame_analyzer. Both tools can be found under third_party/webrtc/tools. The
93// test also runs a stand alone Python implementation of a WebSocket server
94// (pywebsocket) and a barcode_decoder script.
95class WebrtcVideoQualityBrowserTest : public WebRtcTestBase {
96 public:
97  WebrtcVideoQualityBrowserTest()
98      : pywebsocket_server_(0),
99        environment_(base::Environment::Create()) {}
100
101  virtual void SetUpInProcessBrowserTestFixture() OVERRIDE {
102    PeerConnectionServerRunner::KillAllPeerConnectionServersOnCurrentSystem();
103  }
104
105  virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
106    // TODO(phoglund): check that user actually has the requisite devices and
107    // print a nice message if not; otherwise the test just times out which can
108    // be confusing.
109    // This test expects real device handling and requires a real webcam / audio
110    // device; it will not work with fake devices.
111    EXPECT_FALSE(
112        command_line->HasSwitch(switches::kUseFakeDeviceForMediaStream))
113        << "You cannot run this test with fake devices.";
114
115    // The video playback will not work without a GPU, so force its use here.
116    command_line->AppendSwitch(switches::kUseGpuInTests);
117  }
118
119  bool HasAllRequiredResources() {
120    if (!base::PathExists(GetWorkingDir())) {
121      LOG(ERROR) << "Cannot find the working directory for the reference video "
122          "and the temporary files:" << GetWorkingDir().value();
123      return false;
124    }
125    base::FilePath reference_file =
126        GetWorkingDir().Append(kReferenceYuvFileName);
127    if (!base::PathExists(reference_file)) {
128      LOG(ERROR) << "Cannot find the reference file to be used for video "
129          << "quality comparison: " << reference_file.value();
130      return false;
131    }
132    return true;
133  }
134
135  bool StartPyWebSocketServer() {
136    base::FilePath path_pywebsocket_dir =
137        GetSourceDir().Append(FILE_PATH_LITERAL("third_party/pywebsocket/src"));
138    base::FilePath pywebsocket_server = path_pywebsocket_dir.Append(
139        FILE_PATH_LITERAL("mod_pywebsocket/standalone.py"));
140    base::FilePath path_to_data_handler =
141        GetSourceDir().Append(FILE_PATH_LITERAL("chrome/test/functional"));
142
143    if (!base::PathExists(pywebsocket_server)) {
144      LOG(ERROR) << "Missing pywebsocket server.";
145      return false;
146    }
147    if (!base::PathExists(path_to_data_handler)) {
148      LOG(ERROR) << "Missing data handler for pywebsocket server.";
149      return false;
150    }
151
152    AppendToPythonPath(path_pywebsocket_dir);
153
154    // Note: don't append switches to this command since it will mess up the
155    // -u in the python invocation!
156    CommandLine pywebsocket_command(CommandLine::NO_PROGRAM);
157    EXPECT_TRUE(GetPythonCommand(&pywebsocket_command));
158
159    pywebsocket_command.AppendArgPath(pywebsocket_server);
160    pywebsocket_command.AppendArg("-p");
161    pywebsocket_command.AppendArg(kPyWebSocketPortNumber);
162    pywebsocket_command.AppendArg("-d");
163    pywebsocket_command.AppendArgPath(path_to_data_handler);
164
165    LOG(INFO) << "Running " << pywebsocket_command.GetCommandLineString();
166    return base::LaunchProcess(pywebsocket_command, base::LaunchOptions(),
167                               &pywebsocket_server_);
168  }
169
170  bool ShutdownPyWebSocketServer() {
171    return base::KillProcess(pywebsocket_server_, 0, false);
172  }
173
174  // Convenience method which executes the provided javascript in the context
175  // of the provided web contents and returns what it evaluated to.
176  std::string ExecuteJavascript(const std::string& javascript,
177                                content::WebContents* tab_contents) {
178    std::string result;
179    EXPECT_TRUE(content::ExecuteScriptAndExtractString(
180        tab_contents, javascript, &result));
181    return result;
182  }
183
184  // Ensures we didn't get any errors asynchronously (e.g. while no javascript
185  // call from this test was outstanding).
186  // TODO(phoglund): this becomes obsolete when we switch to communicating with
187  // the DOM message queue.
188  void AssertNoAsynchronousErrors(content::WebContents* tab_contents) {
189    EXPECT_EQ("ok-no-errors",
190              ExecuteJavascript("getAnyTestFailures()", tab_contents));
191  }
192
193  // The peer connection server lets our two tabs find each other and talk to
194  // each other (e.g. it is the application-specific "signaling solution").
195  void ConnectToPeerConnectionServer(const std::string peer_name,
196                                     content::WebContents* tab_contents) {
197    std::string javascript = base::StringPrintf(
198        "connect('http://localhost:8888', '%s');", peer_name.c_str());
199    EXPECT_EQ("ok-connected", ExecuteJavascript(javascript, tab_contents));
200  }
201
202  void EstablishCall(content::WebContents* from_tab,
203                     content::WebContents* to_tab) {
204    EXPECT_EQ("ok-peerconnection-created",
205              ExecuteJavascript("preparePeerConnection()", from_tab));
206    EXPECT_EQ("ok-added", ExecuteJavascript("addLocalStream()", from_tab));
207    EXPECT_EQ("ok-negotiating", ExecuteJavascript("negotiateCall()", from_tab));
208
209    // Ensure the call gets up on both sides.
210    EXPECT_TRUE(PollingWaitUntil(
211        "getPeerConnectionReadyState()", "active", from_tab));
212    EXPECT_TRUE(PollingWaitUntil(
213        "getPeerConnectionReadyState()", "active", to_tab));
214  }
215
216  void HangUp(content::WebContents* from_tab) {
217    EXPECT_EQ("ok-call-hung-up", ExecuteJavascript("hangUp()", from_tab));
218  }
219
220  void WaitUntilHangupVerified(content::WebContents* tab_contents) {
221    EXPECT_TRUE(PollingWaitUntil(
222        "getPeerConnectionReadyState()", "no-peer-connection", tab_contents));
223  }
224
225  // Runs the RGBA to I420 converter on the video in |capture_video_filename|,
226  // which should contain frames of size |width| x |height|.
227  //
228  // The rgba_to_i420_converter is part of the webrtc_test_tools target which
229  // should be build prior to running this test. The resulting binary should
230  // live next to Chrome.
231  bool RunARGBtoI420Converter(int width,
232                              int height,
233                              const base::FilePath& captured_video_filename) {
234    base::FilePath path_to_converter = base::MakeAbsoluteFilePath(
235        GetBrowserDir().Append(kArgbToI420ConverterExecutable));
236
237    if (!base::PathExists(path_to_converter)) {
238      LOG(ERROR) << "Missing ARGB->I420 converter: should be in "
239          << path_to_converter.value();
240      return false;
241    }
242
243    CommandLine converter_command(path_to_converter);
244    converter_command.AppendSwitchPath("--frames_dir", GetWorkingDir());
245    converter_command.AppendSwitchPath("--output_file",
246                                       captured_video_filename);
247    converter_command.AppendSwitchASCII("--width",
248                                        base::StringPrintf("%d", width));
249    converter_command.AppendSwitchASCII("--height",
250                                        base::StringPrintf("%d", height));
251
252    // We produce an output file that will later be used as an input to the
253    // barcode decoder and frame analyzer tools.
254    LOG(INFO) << "Running " << converter_command.GetCommandLineString();
255    std::string result;
256    bool ok = base::GetAppOutput(converter_command, &result);
257    LOG(INFO) << "Output was:\n\n" << result;
258    return ok;
259  }
260
261  // Compares the |captured_video_filename| with the |reference_video_filename|.
262  //
263  // The barcode decoder decodes the captured video containing barcodes overlaid
264  // into every frame of the video (produced by rgba_to_i420_converter). It
265  // produces a set of PNG images and a |stats_file| that maps each captured
266  // frame to a frame in the reference video. The frames should be of size
267  // |width| x |height|. The output of compare_videos.py is returned.
268  bool CompareVideos(int width,
269                     int height,
270                     const base::FilePath& captured_video_filename,
271                     const base::FilePath& reference_video_filename,
272                     const base::FilePath& stats_file,
273                     std::string* result) {
274
275    base::FilePath path_to_analyzer = base::MakeAbsoluteFilePath(
276        GetBrowserDir().Append(kFrameAnalyzerExecutable));
277    base::FilePath path_to_compare_script = GetSourceDir().Append(
278        FILE_PATH_LITERAL("third_party/webrtc/tools/compare_videos.py"));
279
280    if (!base::PathExists(path_to_analyzer)) {
281      LOG(ERROR) << "Missing frame analyzer: should be in "
282          << path_to_analyzer.value();
283      return false;
284    }
285    if (!base::PathExists(path_to_compare_script)) {
286      LOG(ERROR) << "Missing video compare script: should be in "
287          << path_to_compare_script.value();
288      return false;
289    }
290
291    // Note: don't append switches to this command since it will mess up the
292    // -u in the python invocation!
293    CommandLine compare_command(CommandLine::NO_PROGRAM);
294    EXPECT_TRUE(GetPythonCommand(&compare_command));
295
296    compare_command.AppendArgPath(path_to_compare_script);
297    compare_command.AppendArg("--ref_video");
298    compare_command.AppendArgPath(reference_video_filename);
299    compare_command.AppendArg("--test_video");
300    compare_command.AppendArgPath(captured_video_filename);
301    compare_command.AppendArg("--frame_analyzer");
302    compare_command.AppendArgPath(path_to_analyzer);
303    compare_command.AppendArg("--yuv_frame_width");
304    compare_command.AppendArg(base::StringPrintf("%d", width));
305    compare_command.AppendArg("--yuv_frame_height");
306    compare_command.AppendArg(base::StringPrintf("%d", height));
307    compare_command.AppendArg("--stats_file");
308    compare_command.AppendArgPath(stats_file);
309
310    LOG(INFO) << "Running " << compare_command.GetCommandLineString();
311    bool ok = base::GetAppOutput(compare_command, result);
312    LOG(INFO) << "Output was:\n\n" << *result;
313    return ok;
314  }
315
316  // Processes the |frame_analyzer_output| for the different frame counts.
317  //
318  // The frame analyzer outputs additional information about the number of
319  // unique frames captured, The max number of repeated frames in a sequence and
320  // the max number of skipped frames. These values are then written to the Perf
321  // Graph. (Note: Some of the repeated or skipped frames will probably be due
322  // to the imperfection of JavaScript timers).
323  void PrintFramesCountPerfResults(std::string frame_analyzer_output) {
324    size_t unique_frames_pos =
325        frame_analyzer_output.rfind("Unique_frames_count");
326    EXPECT_NE(unique_frames_pos, std::string::npos)
327        << "Missing Unique_frames_count in frame analyzer output:\n"
328        << frame_analyzer_output;
329
330    std::string unique_frame_counts =
331        frame_analyzer_output.substr(unique_frames_pos);
332    // TODO(phoglund): Fix ESTATS result to not have this silly newline.
333    std::replace(
334        unique_frame_counts.begin(), unique_frame_counts.end(), '\n', ' ');
335
336    std::vector<std::pair<std::string, std::string> > key_values;
337    base::SplitStringIntoKeyValuePairs(
338        unique_frame_counts, ':', ' ', &key_values);
339    std::vector<std::pair<std::string, std::string> >::const_iterator iter;
340    for (iter = key_values.begin(); iter != key_values.end(); ++iter) {
341      const std::pair<std::string, std::string>& key_value = *iter;
342      perf_test::PrintResult(
343          key_value.first, "", "VGA", key_value.second, "", false);
344    }
345  }
346
347  // Processes the |frame_analyzer_output| to extract the PSNR and SSIM values.
348  //
349  // The frame analyzer produces PSNR and SSIM results for every unique frame
350  // that has been captured. This method forms a list of all the psnr and ssim
351  // values and passes it to PrintResultList() for printing on the Perf Graph.
352  void PrintPsnrAndSsimPerfResults(std::string frame_analyzer_output) {
353    size_t stats_start = frame_analyzer_output.find("BSTATS");
354    EXPECT_NE(stats_start, std::string::npos)
355        << "Missing BSTATS in frame analyzer output:\n"
356        << frame_analyzer_output;
357    size_t stats_end = frame_analyzer_output.find("ESTATS");
358    EXPECT_NE(stats_end, std::string::npos)
359        << "Missing ESTATS in frame analyzer output:\n"
360        << frame_analyzer_output;
361
362    stats_start += std::string("BSTATS").size();
363    std::string psnr_ssim_stats =
364        frame_analyzer_output.substr(stats_start, stats_end - stats_start);
365
366    // PSNR and SSIM values aren't really key-value pairs but it is convenient
367    // to parse them as such.
368    // TODO(phoglund): make the format more convenient so we need less
369    // processing here.
370    std::vector<std::pair<std::string, std::string> > psnr_ssim_entries;
371    base::SplitStringIntoKeyValuePairs(
372        psnr_ssim_stats, ' ', ';', &psnr_ssim_entries);
373
374    std::string psnr_value_list;
375    std::string ssim_value_list;
376    std::vector<std::pair<std::string, std::string> >::const_iterator iter;
377    for (iter = psnr_ssim_entries.begin(); iter != psnr_ssim_entries.end();
378         ++iter) {
379      const std::pair<std::string, std::string>& psnr_and_ssim = *iter;
380      psnr_value_list.append(psnr_and_ssim.first).append(",");
381      ssim_value_list.append(psnr_and_ssim.second).append(",");
382    }
383
384    // Nuke last comma.
385    ASSERT_GT(psnr_value_list.size(), 0u) << "Received no valid PSNR values.";
386    ASSERT_GT(ssim_value_list.size(), 0u) << "Received no valid SSIM values.";
387    psnr_value_list.erase(psnr_value_list.size() - 1);
388    ssim_value_list.erase(ssim_value_list.size() - 1);
389
390    perf_test::PrintResultList("PSNR", "", "VGA", psnr_value_list, "dB", false);
391    perf_test::PrintResultList("SSIM", "", "VGA", ssim_value_list, "", false);
392  }
393
394  base::FilePath GetWorkingDir() {
395    std::string home_dir;
396    environment_->GetVar(kHomeEnvName, &home_dir);
397    base::FilePath::StringType native_home_dir(home_dir.begin(),
398                                               home_dir.end());
399    return base::FilePath(native_home_dir).Append(kWorkingDirName);
400  }
401
402  PeerConnectionServerRunner peerconnection_server_;
403
404 private:
405  base::FilePath GetSourceDir() {
406    base::FilePath source_dir;
407    PathService::Get(base::DIR_SOURCE_ROOT, &source_dir);
408    return source_dir;
409  }
410
411  base::FilePath GetBrowserDir() {
412    base::FilePath browser_dir;
413    EXPECT_TRUE(PathService::Get(base::DIR_MODULE, &browser_dir));
414    return browser_dir;
415  }
416
417  base::ProcessHandle pywebsocket_server_;
418  scoped_ptr<base::Environment> environment_;
419};
420
421IN_PROC_BROWSER_TEST_F(WebrtcVideoQualityBrowserTest,
422                       MANUAL_TestVGAVideoQuality) {
423  ASSERT_TRUE(HasAllRequiredResources());
424  ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
425  ASSERT_TRUE(StartPyWebSocketServer());
426  ASSERT_TRUE(peerconnection_server_.Start());
427
428  ui_test_utils::NavigateToURL(
429      browser(), embedded_test_server()->GetURL(kMainWebrtcTestHtmlPage));
430  content::WebContents* left_tab =
431      browser()->tab_strip_model()->GetActiveWebContents();
432  GetUserMediaAndAccept(left_tab);
433
434  chrome::AddBlankTabAt(browser(), -1, true);
435  content::WebContents* right_tab =
436      browser()->tab_strip_model()->GetActiveWebContents();
437  ui_test_utils::NavigateToURL(
438      browser(), embedded_test_server()->GetURL(kCapturingWebrtcHtmlPage));
439  GetUserMediaAndAccept(right_tab);
440
441  ConnectToPeerConnectionServer("peer 1", left_tab);
442  ConnectToPeerConnectionServer("peer 2", right_tab);
443
444  EstablishCall(left_tab, right_tab);
445
446  AssertNoAsynchronousErrors(left_tab);
447  AssertNoAsynchronousErrors(right_tab);
448
449  // Poll slower here to avoid flooding the log with messages: capturing and
450  // sending frames take quite a bit of time.
451  int polling_interval_msec = 1000;
452
453  EXPECT_TRUE(PollingWaitUntil(
454      "doneFrameCapturing()", "done-capturing", right_tab,
455      polling_interval_msec));
456
457  HangUp(left_tab);
458  WaitUntilHangupVerified(left_tab);
459  WaitUntilHangupVerified(right_tab);
460
461  AssertNoAsynchronousErrors(left_tab);
462  AssertNoAsynchronousErrors(right_tab);
463
464  EXPECT_TRUE(PollingWaitUntil(
465      "haveMoreFramesToSend()", "no-more-frames", right_tab,
466      polling_interval_msec));
467
468  RunARGBtoI420Converter(
469      kVgaWidth, kVgaHeight, GetWorkingDir().Append(kCapturedYuvFileName));
470  std::string output;
471  ASSERT_TRUE(
472      CompareVideos(kVgaWidth,
473                    kVgaHeight,
474                    GetWorkingDir().Append(kCapturedYuvFileName),
475                    GetWorkingDir().Append(kReferenceYuvFileName),
476                    GetWorkingDir().Append(kStatsFileName),
477                    &output));
478
479  PrintFramesCountPerfResults(output);
480  PrintPsnrAndSsimPerfResults(output);
481
482  ASSERT_TRUE(peerconnection_server_.Stop());
483  ASSERT_TRUE(ShutdownPyWebSocketServer());
484}
485