1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "base/command_line.h"
6 #include "base/environment.h"
7 #include "base/file_util.h"
8 #include "base/path_service.h"
9 #include "base/process/launch.h"
10 #include "base/strings/string_split.h"
11 #include "base/strings/stringprintf.h"
12 #include "base/test/test_timeouts.h"
13 #include "base/time/time.h"
14 #include "chrome/browser/chrome_notification_types.h"
15 #include "chrome/browser/infobars/infobar_service.h"
16 #include "chrome/browser/media/media_stream_infobar_delegate.h"
17 #include "chrome/browser/media/webrtc_browsertest_base.h"
18 #include "chrome/browser/media/webrtc_browsertest_common.h"
19 #include "chrome/browser/profiles/profile.h"
20 #include "chrome/browser/ui/browser.h"
21 #include "chrome/browser/ui/browser_tabstrip.h"
22 #include "chrome/browser/ui/tabs/tab_strip_model.h"
23 #include "chrome/common/chrome_switches.h"
24 #include "chrome/test/base/in_process_browser_test.h"
25 #include "chrome/test/base/ui_test_utils.h"
26 #include "components/infobars/core/infobar.h"
27 #include "content/public/browser/notification_service.h"
28 #include "content/public/test/browser_test_utils.h"
29 #include "media/base/media_switches.h"
30 #include "net/test/embedded_test_server/embedded_test_server.h"
31 #include "net/test/python_utils.h"
32 #include "testing/perf/perf_test.h"
33 #include "ui/gl/gl_switches.h"
35 // For fine-grained suppression on flaky tests.
37 #include "base/win/windows_version.h"
40 static const base::FilePath::CharType kFrameAnalyzerExecutable
[] =
42 FILE_PATH_LITERAL("frame_analyzer.exe");
44 FILE_PATH_LITERAL("frame_analyzer");
47 static const base::FilePath::CharType kArgbToI420ConverterExecutable
[] =
49 FILE_PATH_LITERAL("rgba_to_i420_converter.exe");
51 FILE_PATH_LITERAL("rgba_to_i420_converter");
54 static const char kHomeEnvName
[] =
61 // The working dir should be in the user's home folder.
62 static const base::FilePath::CharType kWorkingDirName
[] =
63 FILE_PATH_LITERAL("webrtc_video_quality");
64 static const base::FilePath::CharType kCapturedYuvFileName
[] =
65 FILE_PATH_LITERAL("captured_video.yuv");
66 static const base::FilePath::CharType kStatsFileName
[] =
67 FILE_PATH_LITERAL("stats.txt");
68 static const char kMainWebrtcTestHtmlPage
[] =
69 "/webrtc/webrtc_jsep01_test.html";
71 // If you change the port number, don't forget to modify video_extraction.js
73 static const char kPyWebSocketPortNumber
[] = "12221";
75 static const struct VideoQualityTestConfig
{
76 const char* test_name
;
79 const char* capture_page
;
80 const base::FilePath::CharType
* reference_video
;
81 const char* constraints
;
82 } kVideoConfigurations
[] = {
84 "/webrtc/webrtc_video_quality_test.html",
85 test::kReferenceFileName360p
,
86 WebRtcTestBase::kAudioVideoCallConstraints360p
},
88 "/webrtc/webrtc_video_quality_test_hd.html",
89 test::kReferenceFileName720p
,
90 WebRtcTestBase::kAudioVideoCallConstraints720p
},
93 // Test the video quality of the WebRTC output.
95 // Prerequisites: This test case must run on a machine with a chrome playing
96 // the video from the reference files located in GetReferenceFilesDir().
97 // The file kReferenceY4mFileName.kY4mFileExtension is played using a
98 // FileVideoCaptureDevice and its sibling with kYuvFileExtension is used for
101 // You must also compile the chromium_builder_webrtc target before you run this
102 // test to get all the tools built.
104 // The external compare_videos.py script also depends on two external
105 // executables which must be located in the PATH when running this test.
106 // * zxing (see the CPP version at https://code.google.com/p/zxing)
107 // * ffmpeg 0.11.1 or compatible version (see http://www.ffmpeg.org)
109 // The test case will launch a custom binary (peerconnection_server) which will
110 // allow two WebRTC clients to find each other.
112 // The test also runs several other custom binaries - rgba_to_i420 converter and
113 // frame_analyzer. Both tools can be found under third_party/webrtc/tools. The
114 // test also runs a stand alone Python implementation of a WebSocket server
115 // (pywebsocket) and a barcode_decoder script.
116 class WebRtcVideoQualityBrowserTest
: public WebRtcTestBase
,
117 public testing::WithParamInterface
<VideoQualityTestConfig
> {
119 WebRtcVideoQualityBrowserTest()
120 : pywebsocket_server_(0),
121 environment_(base::Environment::Create()) {
122 test_config_
= GetParam();
125 virtual void SetUpInProcessBrowserTestFixture() OVERRIDE
{
126 test::PeerConnectionServerRunner::KillAllPeerConnectionServers();
127 DetectErrorsInJavaScript(); // Look for errors in our rather complex js.
130 virtual void SetUpCommandLine(CommandLine
* command_line
) OVERRIDE
{
131 // Set up the command line option with the expected file name. We will check
132 // its existence in HasAllRequiredResources().
133 webrtc_reference_video_y4m_
= test::GetReferenceFilesDir()
134 .Append(test_config_
.reference_video
)
135 .AddExtension(test::kY4mFileExtension
);
136 command_line
->AppendSwitchPath(switches::kUseFileForFakeVideoCapture
,
137 webrtc_reference_video_y4m_
);
138 command_line
->AppendSwitch(switches::kUseFakeDeviceForMediaStream
);
140 // The video playback will not work without a GPU, so force its use here.
141 command_line
->AppendSwitch(switches::kUseGpuInTests
);
144 bool HasAllRequiredResources() {
145 if (!base::PathExists(GetWorkingDir())) {
146 LOG(ERROR
) << "Cannot find the working directory for the temporary "
147 "files:" << GetWorkingDir().value();
151 // Ensure we have the required input files.
152 return test::HasReferenceFilesInCheckout();
155 bool StartPyWebSocketServer() {
156 base::FilePath path_pywebsocket_dir
=
157 GetSourceDir().Append(FILE_PATH_LITERAL("third_party/pywebsocket/src"));
158 base::FilePath pywebsocket_server
= path_pywebsocket_dir
.Append(
159 FILE_PATH_LITERAL("mod_pywebsocket/standalone.py"));
160 base::FilePath path_to_data_handler
=
161 GetSourceDir().Append(FILE_PATH_LITERAL("chrome/test/data/webrtc/wsh"));
163 if (!base::PathExists(pywebsocket_server
)) {
164 LOG(ERROR
) << "Missing pywebsocket server.";
167 if (!base::PathExists(path_to_data_handler
)) {
168 LOG(ERROR
) << "Missing data handler for pywebsocket server.";
172 AppendToPythonPath(path_pywebsocket_dir
);
174 // Note: don't append switches to this command since it will mess up the
175 // -u in the python invocation!
176 CommandLine
pywebsocket_command(CommandLine::NO_PROGRAM
);
177 EXPECT_TRUE(GetPythonCommand(&pywebsocket_command
));
179 pywebsocket_command
.AppendArgPath(pywebsocket_server
);
180 pywebsocket_command
.AppendArg("-p");
181 pywebsocket_command
.AppendArg(kPyWebSocketPortNumber
);
182 pywebsocket_command
.AppendArg("-d");
183 pywebsocket_command
.AppendArgPath(path_to_data_handler
);
185 VLOG(0) << "Running " << pywebsocket_command
.GetCommandLineString();
186 return base::LaunchProcess(pywebsocket_command
, base::LaunchOptions(),
187 &pywebsocket_server_
);
190 bool ShutdownPyWebSocketServer() {
191 return base::KillProcess(pywebsocket_server_
, 0, false);
194 // Runs the RGBA to I420 converter on the video in |capture_video_filename|,
195 // which should contain frames of size |width| x |height|.
197 // The rgba_to_i420_converter is part of the webrtc_test_tools target which
198 // should be build prior to running this test. The resulting binary should
199 // live next to Chrome.
200 bool RunARGBtoI420Converter(int width
,
202 const base::FilePath
& captured_video_filename
) {
203 base::FilePath path_to_converter
= base::MakeAbsoluteFilePath(
204 GetBrowserDir().Append(kArgbToI420ConverterExecutable
));
206 if (!base::PathExists(path_to_converter
)) {
207 LOG(ERROR
) << "Missing ARGB->I420 converter: should be in "
208 << path_to_converter
.value();
212 CommandLine
converter_command(path_to_converter
);
213 converter_command
.AppendSwitchPath("--frames_dir", GetWorkingDir());
214 converter_command
.AppendSwitchPath("--output_file",
215 captured_video_filename
);
216 converter_command
.AppendSwitchASCII("--width",
217 base::StringPrintf("%d", width
));
218 converter_command
.AppendSwitchASCII("--height",
219 base::StringPrintf("%d", height
));
220 converter_command
.AppendSwitchASCII("--delete_frames", "true");
222 // We produce an output file that will later be used as an input to the
223 // barcode decoder and frame analyzer tools.
224 VLOG(0) << "Running " << converter_command
.GetCommandLineString();
226 bool ok
= base::GetAppOutput(converter_command
, &result
);
227 VLOG(0) << "Output was:\n\n" << result
;
231 // Compares the |captured_video_filename| with the |reference_video_filename|.
233 // The barcode decoder decodes the captured video containing barcodes overlaid
234 // into every frame of the video (produced by rgba_to_i420_converter). It
235 // produces a set of PNG images and a |stats_file| that maps each captured
236 // frame to a frame in the reference video. The frames should be of size
237 // |width| x |height|.
238 // All measurements calculated are printed as perf parsable numbers to stdout.
239 bool CompareVideosAndPrintResult(
240 const char* test_label
,
243 const base::FilePath
& captured_video_filename
,
244 const base::FilePath
& reference_video_filename
,
245 const base::FilePath
& stats_file
) {
247 base::FilePath path_to_analyzer
= base::MakeAbsoluteFilePath(
248 GetBrowserDir().Append(kFrameAnalyzerExecutable
));
249 base::FilePath path_to_compare_script
= GetSourceDir().Append(
250 FILE_PATH_LITERAL("third_party/webrtc/tools/compare_videos.py"));
252 if (!base::PathExists(path_to_analyzer
)) {
253 LOG(ERROR
) << "Missing frame analyzer: should be in "
254 << path_to_analyzer
.value();
257 if (!base::PathExists(path_to_compare_script
)) {
258 LOG(ERROR
) << "Missing video compare script: should be in "
259 << path_to_compare_script
.value();
263 // Note: don't append switches to this command since it will mess up the
264 // -u in the python invocation!
265 CommandLine
compare_command(CommandLine::NO_PROGRAM
);
266 EXPECT_TRUE(GetPythonCommand(&compare_command
));
268 compare_command
.AppendArgPath(path_to_compare_script
);
269 compare_command
.AppendArg(base::StringPrintf("--label=%s", test_label
));
270 compare_command
.AppendArg("--ref_video");
271 compare_command
.AppendArgPath(reference_video_filename
);
272 compare_command
.AppendArg("--test_video");
273 compare_command
.AppendArgPath(captured_video_filename
);
274 compare_command
.AppendArg("--frame_analyzer");
275 compare_command
.AppendArgPath(path_to_analyzer
);
276 compare_command
.AppendArg("--yuv_frame_width");
277 compare_command
.AppendArg(base::StringPrintf("%d", width
));
278 compare_command
.AppendArg("--yuv_frame_height");
279 compare_command
.AppendArg(base::StringPrintf("%d", height
));
280 compare_command
.AppendArg("--stats_file");
281 compare_command
.AppendArgPath(stats_file
);
283 VLOG(0) << "Running " << compare_command
.GetCommandLineString();
285 bool ok
= base::GetAppOutput(compare_command
, &output
);
286 // Print to stdout to ensure the perf numbers are parsed properly by the
288 printf("Output was:\n\n%s\n", output
.c_str());
292 base::FilePath
GetWorkingDir() {
293 std::string home_dir
;
294 environment_
->GetVar(kHomeEnvName
, &home_dir
);
295 base::FilePath::StringType
native_home_dir(home_dir
.begin(),
297 return base::FilePath(native_home_dir
).Append(kWorkingDirName
);
301 test::PeerConnectionServerRunner peerconnection_server_
;
302 VideoQualityTestConfig test_config_
;
305 base::FilePath
GetSourceDir() {
306 base::FilePath source_dir
;
307 PathService::Get(base::DIR_SOURCE_ROOT
, &source_dir
);
311 base::FilePath
GetBrowserDir() {
312 base::FilePath browser_dir
;
313 EXPECT_TRUE(PathService::Get(base::DIR_MODULE
, &browser_dir
));
317 base::ProcessHandle pywebsocket_server_
;
318 scoped_ptr
<base::Environment
> environment_
;
319 base::FilePath webrtc_reference_video_y4m_
;
322 INSTANTIATE_TEST_CASE_P(
323 WebRtcVideoQualityBrowserTests
,
324 WebRtcVideoQualityBrowserTest
,
325 testing::ValuesIn(kVideoConfigurations
));
327 IN_PROC_BROWSER_TEST_P(WebRtcVideoQualityBrowserTest
,
328 MANUAL_TestVideoQuality
) {
331 // Fails on XP. http://crbug.com/353078
332 if (base::win::GetVersion() <= base::win::VERSION_XP
)
336 ASSERT_GE(TestTimeouts::action_max_timeout().InSeconds(), 150) <<
337 "This is a long-running test; you must specify "
338 "--ui-test-action-max-timeout to have a value of at least 150000.";
339 ASSERT_TRUE(HasAllRequiredResources());
340 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
341 ASSERT_TRUE(StartPyWebSocketServer());
342 ASSERT_TRUE(peerconnection_server_
.Start());
344 content::WebContents
* left_tab
=
345 OpenPageAndGetUserMediaInNewTabWithConstraints(
346 embedded_test_server()->GetURL(kMainWebrtcTestHtmlPage
),
347 test_config_
.constraints
);
348 content::WebContents
* right_tab
=
349 OpenPageAndGetUserMediaInNewTabWithConstraints(
350 embedded_test_server()->GetURL(test_config_
.capture_page
),
351 test_config_
.constraints
);
353 EstablishCall(left_tab
, right_tab
);
355 // Poll slower here to avoid flooding the log with messages: capturing and
356 // sending frames take quite a bit of time.
357 int polling_interval_msec
= 1000;
359 EXPECT_TRUE(test::PollingWaitUntil(
360 "doneFrameCapturing()", "done-capturing", right_tab
,
361 polling_interval_msec
));
364 WaitUntilHangupVerified(left_tab
);
365 WaitUntilHangupVerified(right_tab
);
367 EXPECT_TRUE(test::PollingWaitUntil(
368 "haveMoreFramesToSend()", "no-more-frames", right_tab
,
369 polling_interval_msec
));
371 // Shut everything down to avoid having the javascript race with the analysis
372 // tools. For instance, dont have console log printouts interleave with the
373 // RESULT lines from the analysis tools (crbug.com/323200).
374 ASSERT_TRUE(peerconnection_server_
.Stop());
375 ASSERT_TRUE(ShutdownPyWebSocketServer());
377 chrome::CloseWebContents(browser(), left_tab
, false);
378 chrome::CloseWebContents(browser(), right_tab
, false);
380 RunARGBtoI420Converter(
381 test_config_
.width
, test_config_
.height
,
382 GetWorkingDir().Append(kCapturedYuvFileName
));
383 ASSERT_TRUE(CompareVideosAndPrintResult(
384 test_config_
.test_name
,
387 GetWorkingDir().Append(kCapturedYuvFileName
),
388 test::GetReferenceFilesDir()
389 .Append(test_config_
.reference_video
)
390 .AddExtension(test::kYuvFileExtension
),
391 GetWorkingDir().Append(kStatsFileName
)));