| OLD | NEW |
| (Empty) |
| 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 <stddef.h> | |
| 6 | |
| 7 #include "base/base64.h" | |
| 8 #include "base/command_line.h" | |
| 9 #include "base/environment.h" | |
| 10 #include "base/files/file.h" | |
| 11 #include "base/files/file_util.h" | |
| 12 #include "base/files/scoped_temp_dir.h" | |
| 13 #include "base/path_service.h" | |
| 14 #include "base/process/launch.h" | |
| 15 #include "base/strings/string_number_conversions.h" | |
| 16 #include "base/strings/string_split.h" | |
| 17 #include "base/strings/stringprintf.h" | |
| 18 #include "base/test/test_timeouts.h" | |
| 19 #include "base/time/time.h" | |
| 20 #include "build/build_config.h" | |
| 21 #include "chrome/browser/chrome_notification_types.h" | |
| 22 #include "chrome/browser/infobars/infobar_service.h" | |
| 23 #include "chrome/browser/media/webrtc_browsertest_base.h" | |
| 24 #include "chrome/browser/media/webrtc_browsertest_common.h" | |
| 25 #include "chrome/browser/profiles/profile.h" | |
| 26 #include "chrome/browser/ui/browser.h" | |
| 27 #include "chrome/browser/ui/browser_tabstrip.h" | |
| 28 #include "chrome/browser/ui/tabs/tab_strip_model.h" | |
| 29 #include "chrome/common/chrome_switches.h" | |
| 30 #include "chrome/test/base/in_process_browser_test.h" | |
| 31 #include "components/infobars/core/infobar.h" | |
| 32 #include "content/public/browser/notification_service.h" | |
| 33 #include "content/public/common/feature_h264_with_openh264_ffmpeg.h" | |
| 34 #include "content/public/common/features.h" | |
| 35 #include "content/public/test/browser_test_utils.h" | |
| 36 #include "media/base/media_switches.h" | |
| 37 #include "net/test/embedded_test_server/embedded_test_server.h" | |
| 38 #include "net/test/python_utils.h" | |
| 39 #include "testing/perf/perf_test.h" | |
| 40 #include "ui/gl/gl_switches.h" | |
| 41 | |
| 42 namespace { | |
| 43 std::string MakeLabel(const char* test_name, const std::string& video_codec) { | |
| 44 std::string codec_label = video_codec.empty() ? "" : "_" + video_codec; | |
| 45 return base::StringPrintf("%s%s", test_name, codec_label.c_str()); | |
| 46 } | |
| 47 } // namespace | |
| 48 | |
| 49 static const base::FilePath::CharType kFrameAnalyzerExecutable[] = | |
| 50 #if defined(OS_WIN) | |
| 51 FILE_PATH_LITERAL("frame_analyzer.exe"); | |
| 52 #else | |
| 53 FILE_PATH_LITERAL("frame_analyzer"); | |
| 54 #endif | |
| 55 | |
| 56 static const base::FilePath::CharType kArgbToI420ConverterExecutable[] = | |
| 57 #if defined(OS_WIN) | |
| 58 FILE_PATH_LITERAL("rgba_to_i420_converter.exe"); | |
| 59 #else | |
| 60 FILE_PATH_LITERAL("rgba_to_i420_converter"); | |
| 61 #endif | |
| 62 | |
| 63 static const base::FilePath::CharType kCapturedYuvFileName[] = | |
| 64 FILE_PATH_LITERAL("captured_video.yuv"); | |
| 65 static const base::FilePath::CharType kStatsFileName[] = | |
| 66 FILE_PATH_LITERAL("stats.txt"); | |
| 67 static const char kMainWebrtcTestHtmlPage[] = | |
| 68 "/webrtc/webrtc_jsep01_test.html"; | |
| 69 static const char kCapturingWebrtcHtmlPage[] = | |
| 70 "/webrtc/webrtc_video_quality_test.html"; | |
| 71 | |
| 72 static const struct VideoQualityTestConfig { | |
| 73 const char* test_name; | |
| 74 int width; | |
| 75 int height; | |
| 76 const base::FilePath::CharType* reference_video; | |
| 77 const char* constraints; | |
| 78 } kVideoConfigurations[] = { | |
| 79 { "360p", 640, 360, | |
| 80 test::kReferenceFileName360p, | |
| 81 WebRtcTestBase::kAudioVideoCallConstraints360p }, | |
| 82 { "720p", 1280, 720, | |
| 83 test::kReferenceFileName720p, | |
| 84 WebRtcTestBase::kAudioVideoCallConstraints720p }, | |
| 85 }; | |
| 86 | |
| 87 // Test the video quality of the WebRTC output. | |
| 88 // | |
| 89 // Prerequisites: This test case must run on a machine with a chrome playing | |
| 90 // the video from the reference files located in GetReferenceFilesDir(). | |
| 91 // The file kReferenceY4mFileName.kY4mFileExtension is played using a | |
| 92 // FileVideoCaptureDevice and its sibling with kYuvFileExtension is used for | |
| 93 // comparison. | |
| 94 // | |
| 95 // You must also compile the chromium_builder_webrtc target before you run this | |
| 96 // test to get all the tools built. | |
| 97 // | |
| 98 // The external compare_videos.py script also depends on two external | |
| 99 // executables which must be located in the PATH when running this test. | |
| 100 // * zxing (see the CPP version at https://code.google.com/p/zxing) | |
| 101 // * ffmpeg 0.11.1 or compatible version (see http://www.ffmpeg.org) | |
| 102 // | |
| 103 // The test runs several custom binaries - rgba_to_i420 converter and | |
| 104 // frame_analyzer. Both tools can be found under third_party/webrtc/tools. The | |
| 105 // test also runs a stand alone Python implementation of a WebSocket server | |
| 106 // (pywebsocket) and a barcode_decoder script. | |
| 107 class WebRtcVideoQualityBrowserTest : public WebRtcTestBase, | |
| 108 public testing::WithParamInterface<VideoQualityTestConfig> { | |
| 109 public: | |
| 110 WebRtcVideoQualityBrowserTest() | |
| 111 : environment_(base::Environment::Create()) { | |
| 112 test_config_ = GetParam(); | |
| 113 } | |
| 114 | |
| 115 void SetUpInProcessBrowserTestFixture() override { | |
| 116 DetectErrorsInJavaScript(); // Look for errors in our rather complex js. | |
| 117 | |
| 118 ASSERT_TRUE(temp_working_dir_.CreateUniqueTempDir()); | |
| 119 } | |
| 120 | |
| 121 void SetUpCommandLine(base::CommandLine* command_line) override { | |
| 122 // Set up the command line option with the expected file name. We will check | |
| 123 // its existence in HasAllRequiredResources(). | |
| 124 webrtc_reference_video_y4m_ = test::GetReferenceFilesDir() | |
| 125 .Append(test_config_.reference_video) | |
| 126 .AddExtension(test::kY4mFileExtension); | |
| 127 command_line->AppendSwitchPath(switches::kUseFileForFakeVideoCapture, | |
| 128 webrtc_reference_video_y4m_); | |
| 129 command_line->AppendSwitch(switches::kUseFakeDeviceForMediaStream); | |
| 130 | |
| 131 // The video playback will not work without a GPU, so force its use here. | |
| 132 command_line->AppendSwitch(switches::kUseGpuInTests); | |
| 133 } | |
| 134 | |
| 135 // Writes all frames we've captured so far by grabbing them from the | |
| 136 // javascript and writing them to the temporary work directory. | |
| 137 void WriteCapturedFramesToWorkingDir(content::WebContents* capturing_tab) { | |
| 138 int num_frames = 0; | |
| 139 std::string response = | |
| 140 ExecuteJavascript("getTotalNumberCapturedFrames()", capturing_tab); | |
| 141 ASSERT_TRUE(base::StringToInt(response, &num_frames)) << | |
| 142 "Failed to retrieve frame count: got " << response; | |
| 143 ASSERT_NE(0, num_frames) << "Failed to capture any frames."; | |
| 144 | |
| 145 for (int i = 0; i < num_frames; i++) { | |
| 146 std::string base64_encoded_frame = | |
| 147 ExecuteJavascript(base::StringPrintf("getOneCapturedFrame(%d)", i), | |
| 148 capturing_tab); | |
| 149 std::string decoded_frame; | |
| 150 ASSERT_TRUE(base::Base64Decode(base64_encoded_frame, &decoded_frame)) | |
| 151 << "Failed to decode frame data '" << base64_encoded_frame << "'."; | |
| 152 | |
| 153 std::string file_name = base::StringPrintf("frame_%04d", i); | |
| 154 base::File frame_file(GetWorkingDir().AppendASCII(file_name), | |
| 155 base::File::FLAG_CREATE | base::File::FLAG_WRITE); | |
| 156 size_t written = frame_file.Write(0, decoded_frame.c_str(), | |
| 157 decoded_frame.length()); | |
| 158 ASSERT_EQ(decoded_frame.length(), written); | |
| 159 } | |
| 160 } | |
| 161 | |
| 162 // Runs the RGBA to I420 converter on the video in |capture_video_filename|, | |
| 163 // which should contain frames of size |width| x |height|. | |
| 164 // | |
| 165 // The rgba_to_i420_converter is part of the webrtc_test_tools target which | |
| 166 // should be build prior to running this test. The resulting binary should | |
| 167 // live next to Chrome. | |
| 168 bool RunARGBtoI420Converter(int width, | |
| 169 int height, | |
| 170 const base::FilePath& captured_video_filename) { | |
| 171 base::FilePath path_to_converter = | |
| 172 GetBrowserDir().Append(kArgbToI420ConverterExecutable); | |
| 173 | |
| 174 if (!base::PathExists(path_to_converter)) { | |
| 175 LOG(ERROR) << "Missing ARGB->I420 converter: should be in " | |
| 176 << path_to_converter.value() | |
| 177 << ". Try building the chromium_builder_webrtc target."; | |
| 178 return false; | |
| 179 } | |
| 180 | |
| 181 base::CommandLine converter_command(path_to_converter); | |
| 182 converter_command.AppendSwitchPath("--frames_dir", GetWorkingDir()); | |
| 183 converter_command.AppendSwitchPath("--output_file", | |
| 184 captured_video_filename); | |
| 185 converter_command.AppendSwitchASCII("--width", base::IntToString(width)); | |
| 186 converter_command.AppendSwitchASCII("--height", base::IntToString(height)); | |
| 187 converter_command.AppendSwitchASCII("--delete_frames", "true"); | |
| 188 | |
| 189 // We produce an output file that will later be used as an input to the | |
| 190 // barcode decoder and frame analyzer tools. | |
| 191 DVLOG(0) << "Running " << converter_command.GetCommandLineString(); | |
| 192 std::string result; | |
| 193 bool ok = base::GetAppOutput(converter_command, &result); | |
| 194 DVLOG(0) << "Output was:\n\n" << result; | |
| 195 return ok; | |
| 196 } | |
| 197 | |
| 198 // Compares the |captured_video_filename| with the |reference_video_filename|. | |
| 199 // | |
| 200 // The barcode decoder decodes the captured video containing barcodes overlaid | |
| 201 // into every frame of the video (produced by rgba_to_i420_converter). It | |
| 202 // produces a set of PNG images and a |stats_file| that maps each captured | |
| 203 // frame to a frame in the reference video. The frames should be of size | |
| 204 // |width| x |height|. | |
| 205 // All measurements calculated are printed as perf parsable numbers to stdout. | |
| 206 bool CompareVideosAndPrintResult( | |
| 207 const std::string& test_label, | |
| 208 int width, | |
| 209 int height, | |
| 210 const base::FilePath& captured_video_filename, | |
| 211 const base::FilePath& reference_video_filename, | |
| 212 const base::FilePath& stats_file) { | |
| 213 | |
| 214 base::FilePath path_to_analyzer = base::MakeAbsoluteFilePath( | |
| 215 GetBrowserDir().Append(kFrameAnalyzerExecutable)); | |
| 216 base::FilePath path_to_compare_script = GetSourceDir().Append( | |
| 217 FILE_PATH_LITERAL("third_party/webrtc/tools/compare_videos.py")); | |
| 218 | |
| 219 if (!base::PathExists(path_to_analyzer)) { | |
| 220 LOG(ERROR) << "Missing frame analyzer: should be in " | |
| 221 << path_to_analyzer.value() | |
| 222 << ". Try building the chromium_builder_webrtc target."; | |
| 223 return false; | |
| 224 } | |
| 225 if (!base::PathExists(path_to_compare_script)) { | |
| 226 LOG(ERROR) << "Missing video compare script: should be in " | |
| 227 << path_to_compare_script.value(); | |
| 228 return false; | |
| 229 } | |
| 230 | |
| 231 base::FilePath path_to_zxing = test::GetToolForPlatform("zxing"); | |
| 232 if (!base::PathExists(path_to_zxing)) { | |
| 233 LOG(ERROR) << "Missing zxing: should be in " << path_to_zxing.value(); | |
| 234 return false; | |
| 235 } | |
| 236 base::FilePath path_to_ffmpeg = test::GetToolForPlatform("ffmpeg"); | |
| 237 if (!base::PathExists(path_to_ffmpeg)) { | |
| 238 LOG(ERROR) << "Missing ffmpeg: should be in " << path_to_ffmpeg.value(); | |
| 239 return false; | |
| 240 } | |
| 241 | |
| 242 // Note: don't append switches to this command since it will mess up the | |
| 243 // -u in the python invocation! | |
| 244 base::CommandLine compare_command(base::CommandLine::NO_PROGRAM); | |
| 245 EXPECT_TRUE(GetPythonCommand(&compare_command)); | |
| 246 | |
| 247 compare_command.AppendArgPath(path_to_compare_script); | |
| 248 compare_command.AppendArg("--label=" + test_label); | |
| 249 compare_command.AppendArg("--ref_video"); | |
| 250 compare_command.AppendArgPath(reference_video_filename); | |
| 251 compare_command.AppendArg("--test_video"); | |
| 252 compare_command.AppendArgPath(captured_video_filename); | |
| 253 compare_command.AppendArg("--frame_analyzer"); | |
| 254 compare_command.AppendArgPath(path_to_analyzer); | |
| 255 compare_command.AppendArg("--yuv_frame_width"); | |
| 256 compare_command.AppendArg(base::IntToString(width)); | |
| 257 compare_command.AppendArg("--yuv_frame_height"); | |
| 258 compare_command.AppendArg(base::IntToString(height)); | |
| 259 compare_command.AppendArg("--zxing_path"); | |
| 260 compare_command.AppendArgPath(path_to_zxing); | |
| 261 compare_command.AppendArg("--ffmpeg_path"); | |
| 262 compare_command.AppendArgPath(path_to_ffmpeg); | |
| 263 compare_command.AppendArg("--stats_file"); | |
| 264 compare_command.AppendArgPath(stats_file); | |
| 265 | |
| 266 DVLOG(0) << "Running " << compare_command.GetCommandLineString(); | |
| 267 std::string output; | |
| 268 bool ok = base::GetAppOutput(compare_command, &output); | |
| 269 | |
| 270 // Print to stdout to ensure the perf numbers are parsed properly by the | |
| 271 // buildbot step. The tool should print a handful RESULT lines. | |
| 272 printf("Output was:\n\n%s\n", output.c_str()); | |
| 273 bool has_result_lines = output.find("RESULT") != std::string::npos; | |
| 274 if (!ok || !has_result_lines) { | |
| 275 LOG(ERROR) << "Failed to compare videos; see output above to see what " | |
| 276 << "the error was."; | |
| 277 return false; | |
| 278 } | |
| 279 return true; | |
| 280 } | |
| 281 | |
| 282 void TestVideoQuality(const std::string& video_codec) { | |
| 283 ASSERT_GE(TestTimeouts::action_max_timeout().InSeconds(), 150) | |
| 284 << "This is a long-running test; you must specify " | |
| 285 "--ui-test-action-max-timeout to have a value of at least 150000."; | |
| 286 ASSERT_TRUE(test::HasReferenceFilesInCheckout()); | |
| 287 ASSERT_TRUE(embedded_test_server()->Start()); | |
| 288 | |
| 289 content::WebContents* left_tab = | |
| 290 OpenPageAndGetUserMediaInNewTabWithConstraints( | |
| 291 embedded_test_server()->GetURL(kMainWebrtcTestHtmlPage), | |
| 292 test_config_.constraints); | |
| 293 content::WebContents* right_tab = | |
| 294 OpenPageAndGetUserMediaInNewTabWithConstraints( | |
| 295 embedded_test_server()->GetURL(kCapturingWebrtcHtmlPage), | |
| 296 test_config_.constraints); | |
| 297 | |
| 298 SetupPeerconnectionWithLocalStream(left_tab); | |
| 299 SetupPeerconnectionWithLocalStream(right_tab); | |
| 300 | |
| 301 NegotiateCall(left_tab, right_tab, video_codec); | |
| 302 | |
| 303 // Poll slower here to avoid flooding the log with messages: capturing and | |
| 304 // sending frames take quite a bit of time. | |
| 305 int polling_interval_msec = 1000; | |
| 306 | |
| 307 EXPECT_TRUE(test::PollingWaitUntil("doneFrameCapturing()", "done-capturing", | |
| 308 right_tab, polling_interval_msec)); | |
| 309 | |
| 310 HangUp(left_tab); | |
| 311 | |
| 312 WriteCapturedFramesToWorkingDir(right_tab); | |
| 313 | |
| 314 // Shut everything down to avoid having the javascript race with the | |
| 315 // analysis tools. For instance, dont have console log printouts interleave | |
| 316 // with the RESULT lines from the analysis tools (crbug.com/323200). | |
| 317 chrome::CloseWebContents(browser(), left_tab, false); | |
| 318 chrome::CloseWebContents(browser(), right_tab, false); | |
| 319 | |
| 320 ASSERT_TRUE( | |
| 321 RunARGBtoI420Converter(test_config_.width, test_config_.height, | |
| 322 GetWorkingDir().Append(kCapturedYuvFileName))); | |
| 323 | |
| 324 ASSERT_TRUE(CompareVideosAndPrintResult( | |
| 325 MakeLabel(test_config_.test_name, video_codec), test_config_.width, | |
| 326 test_config_.height, GetWorkingDir().Append(kCapturedYuvFileName), | |
| 327 test::GetReferenceFilesDir() | |
| 328 .Append(test_config_.reference_video) | |
| 329 .AddExtension(test::kYuvFileExtension), | |
| 330 GetWorkingDir().Append(kStatsFileName))); | |
| 331 } | |
| 332 | |
| 333 protected: | |
| 334 VideoQualityTestConfig test_config_; | |
| 335 | |
| 336 base::FilePath GetWorkingDir() { return temp_working_dir_.path(); } | |
| 337 | |
| 338 private: | |
| 339 base::FilePath GetSourceDir() { | |
| 340 base::FilePath source_dir; | |
| 341 PathService::Get(base::DIR_SOURCE_ROOT, &source_dir); | |
| 342 return source_dir; | |
| 343 } | |
| 344 | |
| 345 base::FilePath GetBrowserDir() { | |
| 346 base::FilePath browser_dir; | |
| 347 EXPECT_TRUE(PathService::Get(base::DIR_MODULE, &browser_dir)); | |
| 348 return browser_dir; | |
| 349 } | |
| 350 | |
| 351 scoped_ptr<base::Environment> environment_; | |
| 352 base::FilePath webrtc_reference_video_y4m_; | |
| 353 base::ScopedTempDir temp_working_dir_; | |
| 354 }; | |
| 355 | |
| 356 INSTANTIATE_TEST_CASE_P( | |
| 357 WebRtcVideoQualityBrowserTests, | |
| 358 WebRtcVideoQualityBrowserTest, | |
| 359 testing::ValuesIn(kVideoConfigurations)); | |
| 360 | |
| 361 // The video codec name is now appended to the test label (e.g. '720p_VP8'). | |
| 362 // TODO(asapersson): Keep test below using the default video codec (which do | |
| 363 // not have the codec name appended ('720p')) until new tests have been | |
| 364 // running for some time. | |
| 365 IN_PROC_BROWSER_TEST_P(WebRtcVideoQualityBrowserTest, | |
| 366 MANUAL_TestVideoQualityDefault) { | |
| 367 TestVideoQuality(""); | |
| 368 } | |
| 369 | |
| 370 IN_PROC_BROWSER_TEST_P(WebRtcVideoQualityBrowserTest, | |
| 371 MANUAL_TestVideoQualityVp8) { | |
| 372 TestVideoQuality("VP8"); | |
| 373 } | |
| 374 | |
| 375 IN_PROC_BROWSER_TEST_P(WebRtcVideoQualityBrowserTest, | |
| 376 MANUAL_TestVideoQualityVp9) { | |
| 377 TestVideoQuality("VP9"); | |
| 378 } | |
| 379 | |
| 380 #if BUILDFLAG(RTC_USE_H264) | |
| 381 | |
| 382 IN_PROC_BROWSER_TEST_P(WebRtcVideoQualityBrowserTest, | |
| 383 MANUAL_TestVideoQualityH264) { | |
| 384 // Only run test if run-time feature corresponding to |rtc_use_h264| is on. | |
| 385 if (!base::FeatureList::IsEnabled(content::kWebRtcH264WithOpenH264FFmpeg)) { | |
| 386 LOG(WARNING) << "Run-time feature WebRTC-H264WithOpenH264FFmpeg disabled. " | |
| 387 "Skipping WebRtcVideoQualityBrowserTest.MANUAL_TestVideoQualityH264 " | |
| 388 "(test \"OK\")"; | |
| 389 return; | |
| 390 } | |
| 391 TestVideoQuality("H264"); | |
| 392 } | |
| 393 | |
| 394 #endif // BUILDFLAG(RTC_USE_H264) | |
| OLD | NEW |