chrome_webrtc_video_quality_browsertest.cc revision a1401311d1ab56c4ed0a474bd38c108f75cb0cd9
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. 4 5#include "base/environment.h" 6#include "base/file_util.h" 7#include "base/path_service.h" 8#include "base/process/launch.h" 9#include "base/strings/string_split.h" 10#include "base/strings/stringprintf.h" 11#include "base/test/test_timeouts.h" 12#include "base/time/time.h" 13#include "chrome/browser/chrome_notification_types.h" 14#include "chrome/browser/infobars/infobar.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 "chrome/test/ui/ui_test.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" 34 35// For fine-grained suppression on flaky tests. 36#if defined(OS_WIN) 37#include "base/win/windows_version.h" 38#endif 39 40static const base::FilePath::CharType kFrameAnalyzerExecutable[] = 41#if defined(OS_WIN) 42 FILE_PATH_LITERAL("frame_analyzer.exe"); 43#else 44 FILE_PATH_LITERAL("frame_analyzer"); 45#endif 46 47static const base::FilePath::CharType kArgbToI420ConverterExecutable[] = 48#if defined(OS_WIN) 49 FILE_PATH_LITERAL("rgba_to_i420_converter.exe"); 50#else 51 FILE_PATH_LITERAL("rgba_to_i420_converter"); 52#endif 53 54static const char kHomeEnvName[] = 55#if defined(OS_WIN) 56 "HOMEPATH"; 57#else 58 "HOME"; 59#endif 60 61// The working dir should be in the user's home folder. 62static const base::FilePath::CharType kWorkingDirName[] = 63 FILE_PATH_LITERAL("webrtc_video_quality"); 64static const base::FilePath::CharType kReferenceVideosDirName[] = 65 FILE_PATH_LITERAL("webrtc.DEPS/webrtc_videos"); 66static const base::FilePath::CharType kReferenceFileName360p[] = 67 FILE_PATH_LITERAL("reference_video_640x360_30fps"); 68static const base::FilePath::CharType kYuvFileExtension[] = 69 FILE_PATH_LITERAL("yuv"); 70static const base::FilePath::CharType kY4mFileExtension[] = 71 FILE_PATH_LITERAL("y4m"); 72static const base::FilePath::CharType kCapturedYuvFileName[] = 73 FILE_PATH_LITERAL("captured_video.yuv"); 74static const base::FilePath::CharType kStatsFileName[] = 75 FILE_PATH_LITERAL("stats.txt"); 76static const char kMainWebrtcTestHtmlPage[] = 77 "/webrtc/webrtc_jsep01_test.html"; 78static const char kCapturingWebrtcHtmlPage[] = 79 "/webrtc/webrtc_video_quality_test.html"; 80static const int k360pWidth = 640; 81static const int k360pHeight = 360; 82 83// If you change the port number, don't forget to modify video_extraction.js 84// too! 85static const char kPyWebSocketPortNumber[] = "12221"; 86 87const char kAdviseOnGclientSolution[] = 88 "You need to add this solution to your .gclient to run this test:\n" 89 "{\n" 90 " \"name\" : \"webrtc.DEPS\",\n" 91 " \"url\" : \"svn://svn.chromium.org/chrome/trunk/deps/" 92 "third_party/webrtc/webrtc.DEPS\",\n" 93 "}"; 94 95// Test the video quality of the WebRTC output. 96// 97// Prerequisites: This test case must run on a machine with a chrome playing 98// the video from the reference files located int GetReferenceVideosDir(). 99// The file kReferenceY4mFileName.kY4mFileExtension is played using a 100// FileVideoCaptureDevice and its sibling with kYuvFileExtension is used for 101// comparison. 102// 103// You must also compile the chromium_builder_webrtc target before you run this 104// test to get all the tools built. 105// 106// The external compare_videos.py script also depends on two external 107// executables which must be located in the PATH when running this test. 108// * zxing (see the CPP version at https://code.google.com/p/zxing) 109// * ffmpeg 0.11.1 or compatible version (see http://www.ffmpeg.org) 110// 111// The test case will launch a custom binary (peerconnection_server) which will 112// allow two WebRTC clients to find each other. 113// 114// The test also runs several other custom binaries - rgba_to_i420 converter and 115// frame_analyzer. Both tools can be found under third_party/webrtc/tools. The 116// test also runs a stand alone Python implementation of a WebSocket server 117// (pywebsocket) and a barcode_decoder script. 118class WebRtcVideoQualityBrowserTest : public WebRtcTestBase { 119 public: 120 WebRtcVideoQualityBrowserTest() 121 : pywebsocket_server_(0), 122 environment_(base::Environment::Create()) {} 123 124 virtual void SetUpInProcessBrowserTestFixture() OVERRIDE { 125 PeerConnectionServerRunner::KillAllPeerConnectionServersOnCurrentSystem(); 126 DetectErrorsInJavaScript(); // Look for errors in our rather complex js. 127 } 128 129 virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE { 130 // Set up the command line option with the expected file name. We will check 131 // its existence in HasAllRequiredResources(). 132 webrtc_reference_video_y4m_ = GetReferenceVideosDir() 133 .Append(kReferenceFileName360p).AddExtension(kY4mFileExtension); 134 command_line->AppendSwitchPath(switches::kUseFileForFakeVideoCapture, 135 webrtc_reference_video_y4m_); 136 command_line->AppendSwitch(switches::kUseFakeDeviceForMediaStream); 137 138 // The video playback will not work without a GPU, so force its use here. 139 command_line->AppendSwitch(switches::kUseGpuInTests); 140 } 141 142 bool HasAllRequiredResources() { 143 if (!base::PathExists(GetWorkingDir())) { 144 LOG(ERROR) << "Cannot find the working directory for the temporary " 145 "files:" << GetWorkingDir().value(); 146 return false; 147 } 148 if (!base::PathExists(GetReferenceVideosDir())) { 149 LOG(ERROR) << "Cannot find the working directory for the reference video " 150 "files, expected at " << GetReferenceVideosDir().value() << ". " << 151 kAdviseOnGclientSolution; 152 return false; 153 } 154 base::FilePath webrtc_reference_video_yuv = GetReferenceVideosDir() 155 .Append(kReferenceFileName360p).AddExtension(kYuvFileExtension); 156 if (!base::PathExists(webrtc_reference_video_yuv)) { 157 LOG(ERROR) << "Missing YUV reference video to be used for quality" 158 << " comparison, expected at " << webrtc_reference_video_yuv.value() 159 << ". " << kAdviseOnGclientSolution; 160 return false; 161 } 162 if (!base::PathExists(webrtc_reference_video_y4m_)) { 163 LOG(ERROR) << "Missing Y4M reference video to be used for quality" 164 << " comparison, expected at "<< webrtc_reference_video_y4m_.value() 165 << ". " << kAdviseOnGclientSolution; 166 return false; 167 } 168 return true; 169 } 170 171 bool StartPyWebSocketServer() { 172 base::FilePath path_pywebsocket_dir = 173 GetSourceDir().Append(FILE_PATH_LITERAL("third_party/pywebsocket/src")); 174 base::FilePath pywebsocket_server = path_pywebsocket_dir.Append( 175 FILE_PATH_LITERAL("mod_pywebsocket/standalone.py")); 176 base::FilePath path_to_data_handler = 177 GetSourceDir().Append(FILE_PATH_LITERAL("chrome/test/functional")); 178 179 if (!base::PathExists(pywebsocket_server)) { 180 LOG(ERROR) << "Missing pywebsocket server."; 181 return false; 182 } 183 if (!base::PathExists(path_to_data_handler)) { 184 LOG(ERROR) << "Missing data handler for pywebsocket server."; 185 return false; 186 } 187 188 AppendToPythonPath(path_pywebsocket_dir); 189 190 // Note: don't append switches to this command since it will mess up the 191 // -u in the python invocation! 192 CommandLine pywebsocket_command(CommandLine::NO_PROGRAM); 193 EXPECT_TRUE(GetPythonCommand(&pywebsocket_command)); 194 195 pywebsocket_command.AppendArgPath(pywebsocket_server); 196 pywebsocket_command.AppendArg("-p"); 197 pywebsocket_command.AppendArg(kPyWebSocketPortNumber); 198 pywebsocket_command.AppendArg("-d"); 199 pywebsocket_command.AppendArgPath(path_to_data_handler); 200 201 VLOG(0) << "Running " << pywebsocket_command.GetCommandLineString(); 202 return base::LaunchProcess(pywebsocket_command, base::LaunchOptions(), 203 &pywebsocket_server_); 204 } 205 206 bool ShutdownPyWebSocketServer() { 207 return base::KillProcess(pywebsocket_server_, 0, false); 208 } 209 210 // Runs the RGBA to I420 converter on the video in |capture_video_filename|, 211 // which should contain frames of size |width| x |height|. 212 // 213 // The rgba_to_i420_converter is part of the webrtc_test_tools target which 214 // should be build prior to running this test. The resulting binary should 215 // live next to Chrome. 216 bool RunARGBtoI420Converter(int width, 217 int height, 218 const base::FilePath& captured_video_filename) { 219 base::FilePath path_to_converter = base::MakeAbsoluteFilePath( 220 GetBrowserDir().Append(kArgbToI420ConverterExecutable)); 221 222 if (!base::PathExists(path_to_converter)) { 223 LOG(ERROR) << "Missing ARGB->I420 converter: should be in " 224 << path_to_converter.value(); 225 return false; 226 } 227 228 CommandLine converter_command(path_to_converter); 229 converter_command.AppendSwitchPath("--frames_dir", GetWorkingDir()); 230 converter_command.AppendSwitchPath("--output_file", 231 captured_video_filename); 232 converter_command.AppendSwitchASCII("--width", 233 base::StringPrintf("%d", width)); 234 converter_command.AppendSwitchASCII("--height", 235 base::StringPrintf("%d", height)); 236 237 // We produce an output file that will later be used as an input to the 238 // barcode decoder and frame analyzer tools. 239 VLOG(0) << "Running " << converter_command.GetCommandLineString(); 240 std::string result; 241 bool ok = base::GetAppOutput(converter_command, &result); 242 VLOG(0) << "Output was:\n\n" << result; 243 return ok; 244 } 245 246 // Compares the |captured_video_filename| with the |reference_video_filename|. 247 // 248 // The barcode decoder decodes the captured video containing barcodes overlaid 249 // into every frame of the video (produced by rgba_to_i420_converter). It 250 // produces a set of PNG images and a |stats_file| that maps each captured 251 // frame to a frame in the reference video. The frames should be of size 252 // |width| x |height|. 253 // All measurements calculated are printed as perf parsable numbers to stdout. 254 bool CompareVideosAndPrintResult( 255 int width, 256 int height, 257 const base::FilePath& captured_video_filename, 258 const base::FilePath& reference_video_filename, 259 const base::FilePath& stats_file) { 260 261 base::FilePath path_to_analyzer = base::MakeAbsoluteFilePath( 262 GetBrowserDir().Append(kFrameAnalyzerExecutable)); 263 base::FilePath path_to_compare_script = GetSourceDir().Append( 264 FILE_PATH_LITERAL("third_party/webrtc/tools/compare_videos.py")); 265 266 if (!base::PathExists(path_to_analyzer)) { 267 LOG(ERROR) << "Missing frame analyzer: should be in " 268 << path_to_analyzer.value(); 269 return false; 270 } 271 if (!base::PathExists(path_to_compare_script)) { 272 LOG(ERROR) << "Missing video compare script: should be in " 273 << path_to_compare_script.value(); 274 return false; 275 } 276 277 // Note: don't append switches to this command since it will mess up the 278 // -u in the python invocation! 279 CommandLine compare_command(CommandLine::NO_PROGRAM); 280 EXPECT_TRUE(GetPythonCommand(&compare_command)); 281 282 compare_command.AppendArgPath(path_to_compare_script); 283 compare_command.AppendArg("--label=360p"); 284 compare_command.AppendArg("--ref_video"); 285 compare_command.AppendArgPath(reference_video_filename); 286 compare_command.AppendArg("--test_video"); 287 compare_command.AppendArgPath(captured_video_filename); 288 compare_command.AppendArg("--frame_analyzer"); 289 compare_command.AppendArgPath(path_to_analyzer); 290 compare_command.AppendArg("--yuv_frame_width"); 291 compare_command.AppendArg(base::StringPrintf("%d", width)); 292 compare_command.AppendArg("--yuv_frame_height"); 293 compare_command.AppendArg(base::StringPrintf("%d", height)); 294 compare_command.AppendArg("--stats_file"); 295 compare_command.AppendArgPath(stats_file); 296 297 VLOG(0) << "Running " << compare_command.GetCommandLineString(); 298 std::string output; 299 bool ok = base::GetAppOutput(compare_command, &output); 300 // Print to stdout to ensure the perf numbers are parsed properly by the 301 // buildbot step. 302 printf("Output was:\n\n%s\n", output.c_str()); 303 return ok; 304 } 305 306 base::FilePath GetWorkingDir() { 307 std::string home_dir; 308 environment_->GetVar(kHomeEnvName, &home_dir); 309 base::FilePath::StringType native_home_dir(home_dir.begin(), 310 home_dir.end()); 311 return base::FilePath(native_home_dir).Append(kWorkingDirName); 312 } 313 314 base::FilePath GetReferenceVideosDir() { 315 // FilePath does not tolerate relative paths, and we want to hang the 316 // kReferenceVideosDirName at the same level as Chromium codebase, so we 317 // need to subtract the trailing .../src manually from the path. 318 const size_t src_token_length = 3u; 319 const base::FilePath::StringType src_token(FILE_PATH_LITERAL("src")); 320 base::FilePath::StringType path = GetSourceDir().value(); 321 DCHECK_GT(path.size(), src_token_length); 322 std::size_t found = path.rfind(src_token); 323 if (found != std::string::npos) 324 path.replace(found, 325 src_token_length, 326 base::FilePath::StringType(FILE_PATH_LITERAL(""))); 327 return base::FilePath(path).Append(kReferenceVideosDirName); 328 } 329 330 PeerConnectionServerRunner peerconnection_server_; 331 332 private: 333 base::FilePath GetSourceDir() { 334 base::FilePath source_dir; 335 PathService::Get(base::DIR_SOURCE_ROOT, &source_dir); 336 return source_dir; 337 } 338 339 base::FilePath GetBrowserDir() { 340 base::FilePath browser_dir; 341 EXPECT_TRUE(PathService::Get(base::DIR_MODULE, &browser_dir)); 342 return browser_dir; 343 } 344 345 base::ProcessHandle pywebsocket_server_; 346 scoped_ptr<base::Environment> environment_; 347 base::FilePath webrtc_reference_video_y4m_; 348}; 349 350IN_PROC_BROWSER_TEST_F(WebRtcVideoQualityBrowserTest, 351 MANUAL_TestVGAVideoQuality) { 352#if defined(OS_WIN) 353 // Fails on XP. http://crbug.com/353078 354 if (base::win::GetVersion() <= base::win::VERSION_XP) 355 return; 356#endif 357 358 ASSERT_GE(TestTimeouts::action_max_timeout().InSeconds(), 150) << 359 "This is a long-running test; you must specify " 360 "--ui-test-action-max-timeout to have a value of at least 150000."; 361 362 ASSERT_TRUE(HasAllRequiredResources()); 363 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady()); 364 ASSERT_TRUE(StartPyWebSocketServer()); 365 ASSERT_TRUE(peerconnection_server_.Start()); 366 367 content::WebContents* left_tab = 368 OpenPageAndGetUserMediaInNewTabWithConstraints( 369 embedded_test_server()->GetURL(kMainWebrtcTestHtmlPage), 370 kAudioVideoCallConstraints360p); 371 content::WebContents* right_tab = 372 OpenPageAndGetUserMediaInNewTabWithConstraints( 373 embedded_test_server()->GetURL(kCapturingWebrtcHtmlPage), 374 kAudioVideoCallConstraints360p); 375 376 EstablishCall(left_tab, right_tab); 377 378 // Poll slower here to avoid flooding the log with messages: capturing and 379 // sending frames take quite a bit of time. 380 int polling_interval_msec = 1000; 381 382 EXPECT_TRUE(PollingWaitUntil( 383 "doneFrameCapturing()", "done-capturing", right_tab, 384 polling_interval_msec)); 385 386 HangUp(left_tab); 387 WaitUntilHangupVerified(left_tab); 388 WaitUntilHangupVerified(right_tab); 389 390 EXPECT_TRUE(PollingWaitUntil( 391 "haveMoreFramesToSend()", "no-more-frames", right_tab, 392 polling_interval_msec)); 393 394 // Shut everything down to avoid having the javascript race with the analysis 395 // tools. For instance, dont have console log printouts interleave with the 396 // RESULT lines from the analysis tools (crbug.com/323200). 397 ASSERT_TRUE(peerconnection_server_.Stop()); 398 ASSERT_TRUE(ShutdownPyWebSocketServer()); 399 400 chrome::CloseWebContents(browser(), left_tab, false); 401 chrome::CloseWebContents(browser(), right_tab, false); 402 403 RunARGBtoI420Converter( 404 k360pWidth, k360pHeight, GetWorkingDir().Append(kCapturedYuvFileName)); 405 ASSERT_TRUE(CompareVideosAndPrintResult( 406 k360pWidth, 407 k360pHeight, 408 GetWorkingDir().Append(kCapturedYuvFileName), 409 GetReferenceVideosDir().Append(kReferenceFileName360p).AddExtension( 410 kYuvFileExtension), 411 GetWorkingDir().Append(kStatsFileName))); 412} 413