| 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 kCapturedYuvFileName[] = | |
| 57 FILE_PATH_LITERAL("captured_video.yuv"); | |
| 58 static const base::FilePath::CharType kCapturedWebmFileName[] = | |
| 59 FILE_PATH_LITERAL("captured_video.webm"); | |
| 60 static const base::FilePath::CharType kStatsFileName[] = | |
| 61 FILE_PATH_LITERAL("stats.txt"); | |
| 62 static const char kMainWebrtcTestHtmlPage[] = | |
| 63 "/webrtc/webrtc_jsep01_test.html"; | |
| 64 static const char kCapturingWebrtcHtmlPage[] = | |
| 65 "/webrtc/webrtc_video_quality_test.html"; | |
| 66 | |
| 67 static const struct VideoQualityTestConfig { | |
| 68 const char* test_name; | |
| 69 int width; | |
| 70 int height; | |
| 71 const base::FilePath::CharType* reference_video; | |
| 72 const char* constraints; | |
| 73 } kVideoConfigurations[] = { | |
| 74 { "360p", 640, 360, | |
| 75 test::kReferenceFileName360p, | |
| 76 WebRtcTestBase::kAudioVideoCallConstraints360p }, | |
| 77 { "720p", 1280, 720, | |
| 78 test::kReferenceFileName720p, | |
| 79 WebRtcTestBase::kAudioVideoCallConstraints720p }, | |
| 80 }; | |
| 81 | |
| 82 // Test the video quality of the WebRTC output. | |
| 83 // | |
| 84 // Prerequisites: This test case must run on a machine with a chrome playing | |
| 85 // the video from the reference files located in GetReferenceFilesDir(). | |
| 86 // The file kReferenceY4mFileName.kY4mFileExtension is played using a | |
| 87 // FileVideoCaptureDevice and its sibling with kYuvFileExtension is used for | |
| 88 // comparison. | |
| 89 // | |
| 90 // You must also compile the chromium_builder_webrtc target before you run this | |
| 91 // test to get all the tools built. | |
| 92 // | |
| 93 // The external compare_videos.py script also depends on two external | |
| 94 // executables which must be located in the PATH when running this test. | |
| 95 // * zxing (see the CPP version at https://code.google.com/p/zxing) | |
| 96 // * ffmpeg 0.11.1 or compatible version (see http://www.ffmpeg.org) | |
| 97 // | |
| 98 // The test runs several custom binaries - rgba_to_i420 converter and | |
| 99 // frame_analyzer. Both tools can be found under third_party/webrtc/tools. The | |
| 100 // test also runs a stand alone Python implementation of a WebSocket server | |
| 101 // (pywebsocket) and a barcode_decoder script. | |
| 102 class WebRtcVideoQualityBrowserTest : public WebRtcTestBase, | |
| 103 public testing::WithParamInterface<VideoQualityTestConfig> { | |
| 104 public: | |
| 105 WebRtcVideoQualityBrowserTest() | |
| 106 : environment_(base::Environment::Create()) { | |
| 107 test_config_ = GetParam(); | |
| 108 } | |
| 109 | |
| 110 void SetUpInProcessBrowserTestFixture() override { | |
| 111 DetectErrorsInJavaScript(); // Look for errors in our rather complex js. | |
| 112 | |
| 113 ASSERT_TRUE(temp_working_dir_.CreateUniqueTempDir()); | |
| 114 } | |
| 115 | |
| 116 void SetUpCommandLine(base::CommandLine* command_line) override { | |
| 117 // Set up the command line option with the expected file name. We will check | |
| 118 // its existence in HasAllRequiredResources(). | |
| 119 webrtc_reference_video_y4m_ = test::GetReferenceFilesDir() | |
| 120 .Append(test_config_.reference_video) | |
| 121 .AddExtension(test::kY4mFileExtension); | |
| 122 command_line->AppendSwitchPath(switches::kUseFileForFakeVideoCapture, | |
| 123 webrtc_reference_video_y4m_); | |
| 124 command_line->AppendSwitch(switches::kUseFakeDeviceForMediaStream); | |
| 125 | |
| 126 // The video playback will not work without a GPU, so force its use here. | |
| 127 command_line->AppendSwitch(switches::kUseGpuInTests); | |
| 128 } | |
| 129 | |
| 130 // Writes the captured video to a webm file. | |
| 131 void WriteCapturedWebmVideo(content::WebContents* capturing_tab, | |
| 132 const base::FilePath& webm_video_filename) { | |
| 133 std::string base64_encoded_video = | |
| 134 ExecuteJavascript("getRecordedVideoAsBase64()", capturing_tab); | |
| 135 std::string recorded_video; | |
| 136 ASSERT_TRUE(base::Base64Decode(base64_encoded_video, &recorded_video)); | |
| 137 base::File video_file(webm_video_filename, | |
| 138 base::File::FLAG_CREATE | base::File::FLAG_WRITE); | |
| 139 size_t written = | |
| 140 video_file.Write(0, recorded_video.c_str(), recorded_video.length()); | |
| 141 ASSERT_EQ(recorded_video.length(), written); | |
| 142 } | |
| 143 | |
| 144 // Runs ffmpeg on the captured webm video and writes it to a yuv video file. | |
| 145 bool RunWebmToI420Converter(const base::FilePath& webm_video_filename, | |
| 146 const base::FilePath& yuv_video_filename, | |
| 147 const int width, | |
| 148 const int height) { | |
| 149 base::FilePath path_to_ffmpeg = test::GetToolForPlatform("ffmpeg"); | |
| 150 if (!base::PathExists(path_to_ffmpeg)) { | |
| 151 LOG(ERROR) << "Missing ffmpeg: should be in " << path_to_ffmpeg.value(); | |
| 152 return false; | |
| 153 } | |
| 154 | |
| 155 // Set up ffmpeg to output at a certain resolution (-s) and bitrate (-b:v). | |
| 156 // This is needed because WebRTC is free to start the call at a lower | |
| 157 // resolution before ramping up. Without these flags, ffmpeg would output a | |
| 158 // video in the inital lower resolution, causing the SSIM and PSNR results | |
| 159 // to become meaningless. | |
| 160 base::CommandLine ffmpeg_command(path_to_ffmpeg); | |
| 161 ffmpeg_command.AppendArg("-i"); | |
| 162 ffmpeg_command.AppendArgPath(webm_video_filename); | |
| 163 ffmpeg_command.AppendArg("-s"); | |
| 164 ffmpeg_command.AppendArg(base::StringPrintf("%dx%d", width, height)); | |
| 165 ffmpeg_command.AppendArg("-b:v"); | |
| 166 ffmpeg_command.AppendArg(base::StringPrintf("%d", 120 * width * height)); | |
| 167 ffmpeg_command.AppendArgPath(yuv_video_filename); | |
| 168 | |
| 169 // We produce an output file that will later be used as an input to the | |
| 170 // barcode decoder and frame analyzer tools. | |
| 171 DVLOG(0) << "Running " << ffmpeg_command.GetCommandLineString(); | |
| 172 std::string result; | |
| 173 bool ok = base::GetAppOutputAndError(ffmpeg_command, &result); | |
| 174 DVLOG(0) << "Output was:\n\n" << result; | |
| 175 return ok; | |
| 176 } | |
| 177 | |
| 178 // Compares the |captured_video_filename| with the |reference_video_filename|. | |
| 179 // | |
| 180 // The barcode decoder decodes the captured video containing barcodes overlaid | |
| 181 // into every frame of the video. It produces a set of PNG images and a | |
| 182 // |stats_file| that maps each captured frame to a frame in the reference | |
| 183 // video. The frames should be of size |width| x |height|. | |
| 184 // All measurements calculated are printed as perf parsable numbers to stdout. | |
| 185 bool CompareVideosAndPrintResult( | |
| 186 const std::string& test_label, | |
| 187 int width, | |
| 188 int height, | |
| 189 const base::FilePath& captured_video_filename, | |
| 190 const base::FilePath& reference_video_filename, | |
| 191 const base::FilePath& stats_file) { | |
| 192 | |
| 193 base::FilePath path_to_analyzer = base::MakeAbsoluteFilePath( | |
| 194 GetBrowserDir().Append(kFrameAnalyzerExecutable)); | |
| 195 base::FilePath path_to_compare_script = GetSourceDir().Append( | |
| 196 FILE_PATH_LITERAL("third_party/webrtc/tools/compare_videos.py")); | |
| 197 | |
| 198 if (!base::PathExists(path_to_analyzer)) { | |
| 199 LOG(ERROR) << "Missing frame analyzer: should be in " | |
| 200 << path_to_analyzer.value() | |
| 201 << ". Try building the chromium_builder_webrtc target."; | |
| 202 return false; | |
| 203 } | |
| 204 if (!base::PathExists(path_to_compare_script)) { | |
| 205 LOG(ERROR) << "Missing video compare script: should be in " | |
| 206 << path_to_compare_script.value(); | |
| 207 return false; | |
| 208 } | |
| 209 | |
| 210 base::FilePath path_to_zxing = test::GetToolForPlatform("zxing"); | |
| 211 if (!base::PathExists(path_to_zxing)) { | |
| 212 LOG(ERROR) << "Missing zxing: should be in " << path_to_zxing.value(); | |
| 213 return false; | |
| 214 } | |
| 215 base::FilePath path_to_ffmpeg = test::GetToolForPlatform("ffmpeg"); | |
| 216 if (!base::PathExists(path_to_ffmpeg)) { | |
| 217 LOG(ERROR) << "Missing ffmpeg: should be in " << path_to_ffmpeg.value(); | |
| 218 return false; | |
| 219 } | |
| 220 | |
| 221 // Note: don't append switches to this command since it will mess up the | |
| 222 // -u in the python invocation! | |
| 223 base::CommandLine compare_command(base::CommandLine::NO_PROGRAM); | |
| 224 EXPECT_TRUE(GetPythonCommand(&compare_command)); | |
| 225 | |
| 226 compare_command.AppendArgPath(path_to_compare_script); | |
| 227 compare_command.AppendArg("--label=" + test_label); | |
| 228 compare_command.AppendArg("--ref_video"); | |
| 229 compare_command.AppendArgPath(reference_video_filename); | |
| 230 compare_command.AppendArg("--test_video"); | |
| 231 compare_command.AppendArgPath(captured_video_filename); | |
| 232 compare_command.AppendArg("--frame_analyzer"); | |
| 233 compare_command.AppendArgPath(path_to_analyzer); | |
| 234 compare_command.AppendArg("--yuv_frame_width"); | |
| 235 compare_command.AppendArg(base::IntToString(width)); | |
| 236 compare_command.AppendArg("--yuv_frame_height"); | |
| 237 compare_command.AppendArg(base::IntToString(height)); | |
| 238 compare_command.AppendArg("--zxing_path"); | |
| 239 compare_command.AppendArgPath(path_to_zxing); | |
| 240 compare_command.AppendArg("--ffmpeg_path"); | |
| 241 compare_command.AppendArgPath(path_to_ffmpeg); | |
| 242 compare_command.AppendArg("--stats_file"); | |
| 243 compare_command.AppendArgPath(stats_file); | |
| 244 | |
| 245 DVLOG(0) << "Running " << compare_command.GetCommandLineString(); | |
| 246 std::string output; | |
| 247 bool ok = base::GetAppOutput(compare_command, &output); | |
| 248 | |
| 249 // Print to stdout to ensure the perf numbers are parsed properly by the | |
| 250 // buildbot step. The tool should print a handful RESULT lines. | |
| 251 printf("Output was:\n\n%s\n", output.c_str()); | |
| 252 bool has_result_lines = output.find("RESULT") != std::string::npos; | |
| 253 if (!ok || !has_result_lines) { | |
| 254 LOG(ERROR) << "Failed to compare videos; see output above to see what " | |
| 255 << "the error was."; | |
| 256 return false; | |
| 257 } | |
| 258 return true; | |
| 259 } | |
| 260 | |
| 261 void TestVideoQuality(const std::string& video_codec) { | |
| 262 ASSERT_GE(TestTimeouts::action_max_timeout().InSeconds(), 150) | |
| 263 << "This is a long-running test; you must specify " | |
| 264 "--ui-test-action-max-timeout to have a value of at least 150000."; | |
| 265 ASSERT_TRUE(test::HasReferenceFilesInCheckout()); | |
| 266 ASSERT_TRUE(embedded_test_server()->Start()); | |
| 267 | |
| 268 content::WebContents* left_tab = | |
| 269 OpenPageAndGetUserMediaInNewTabWithConstraints( | |
| 270 embedded_test_server()->GetURL(kMainWebrtcTestHtmlPage), | |
| 271 test_config_.constraints); | |
| 272 content::WebContents* right_tab = | |
| 273 OpenPageAndGetUserMediaInNewTabWithConstraints( | |
| 274 embedded_test_server()->GetURL(kCapturingWebrtcHtmlPage), | |
| 275 test_config_.constraints); | |
| 276 | |
| 277 SetupPeerconnectionWithLocalStream(left_tab); | |
| 278 SetupPeerconnectionWithLocalStream(right_tab); | |
| 279 | |
| 280 if (!video_codec.empty()) { | |
| 281 SetDefaultVideoCodec(left_tab, video_codec); | |
| 282 SetDefaultVideoCodec(right_tab, video_codec); | |
| 283 } | |
| 284 NegotiateCall(left_tab, right_tab); | |
| 285 | |
| 286 // Poll slower here to avoid flooding the log with messages: capturing and | |
| 287 // sending frames take quite a bit of time. | |
| 288 int polling_interval_msec = 1000; | |
| 289 | |
| 290 EXPECT_TRUE(test::PollingWaitUntil("doneFrameCapturing()", "done-capturing", | |
| 291 right_tab, polling_interval_msec)); | |
| 292 | |
| 293 HangUp(left_tab); | |
| 294 | |
| 295 WriteCapturedWebmVideo(right_tab, | |
| 296 GetWorkingDir().Append(kCapturedWebmFileName)); | |
| 297 | |
| 298 // Shut everything down to avoid having the javascript race with the | |
| 299 // analysis tools. For instance, dont have console log printouts interleave | |
| 300 // with the RESULT lines from the analysis tools (crbug.com/323200). | |
| 301 chrome::CloseWebContents(browser(), left_tab, false); | |
| 302 chrome::CloseWebContents(browser(), right_tab, false); | |
| 303 | |
| 304 RunWebmToI420Converter(GetWorkingDir().Append(kCapturedWebmFileName), | |
| 305 GetWorkingDir().Append(kCapturedYuvFileName), | |
| 306 test_config_.width, test_config_.height); | |
| 307 | |
| 308 ASSERT_TRUE(CompareVideosAndPrintResult( | |
| 309 MakeLabel(test_config_.test_name, video_codec), test_config_.width, | |
| 310 test_config_.height, GetWorkingDir().Append(kCapturedYuvFileName), | |
| 311 test::GetReferenceFilesDir() | |
| 312 .Append(test_config_.reference_video) | |
| 313 .AddExtension(test::kYuvFileExtension), | |
| 314 GetWorkingDir().Append(kStatsFileName))); | |
| 315 } | |
| 316 | |
| 317 protected: | |
| 318 VideoQualityTestConfig test_config_; | |
| 319 | |
| 320 base::FilePath GetWorkingDir() { return temp_working_dir_.path(); } | |
| 321 | |
| 322 private: | |
| 323 base::FilePath GetSourceDir() { | |
| 324 base::FilePath source_dir; | |
| 325 PathService::Get(base::DIR_SOURCE_ROOT, &source_dir); | |
| 326 return source_dir; | |
| 327 } | |
| 328 | |
| 329 base::FilePath GetBrowserDir() { | |
| 330 base::FilePath browser_dir; | |
| 331 EXPECT_TRUE(PathService::Get(base::DIR_MODULE, &browser_dir)); | |
| 332 return browser_dir; | |
| 333 } | |
| 334 | |
| 335 std::unique_ptr<base::Environment> environment_; | |
| 336 base::FilePath webrtc_reference_video_y4m_; | |
| 337 base::ScopedTempDir temp_working_dir_; | |
| 338 }; | |
| 339 | |
| 340 INSTANTIATE_TEST_CASE_P( | |
| 341 WebRtcVideoQualityBrowserTests, | |
| 342 WebRtcVideoQualityBrowserTest, | |
| 343 testing::ValuesIn(kVideoConfigurations)); | |
| 344 | |
| 345 IN_PROC_BROWSER_TEST_P(WebRtcVideoQualityBrowserTest, | |
| 346 MANUAL_TestVideoQualityVp8) { | |
| 347 TestVideoQuality("VP8"); | |
| 348 } | |
| 349 | |
| 350 IN_PROC_BROWSER_TEST_P(WebRtcVideoQualityBrowserTest, | |
| 351 MANUAL_TestVideoQualityVp9) { | |
| 352 TestVideoQuality("VP9"); | |
| 353 } | |
| 354 | |
| 355 #if BUILDFLAG(RTC_USE_H264) | |
| 356 | |
| 357 IN_PROC_BROWSER_TEST_P(WebRtcVideoQualityBrowserTest, | |
| 358 MANUAL_TestVideoQualityH264) { | |
| 359 // Only run test if run-time feature corresponding to |rtc_use_h264| is on. | |
| 360 if (!base::FeatureList::IsEnabled(content::kWebRtcH264WithOpenH264FFmpeg)) { | |
| 361 LOG(WARNING) << "Run-time feature WebRTC-H264WithOpenH264FFmpeg disabled. " | |
| 362 "Skipping WebRtcVideoQualityBrowserTest.MANUAL_TestVideoQualityH264 " | |
| 363 "(test \"OK\")"; | |
| 364 return; | |
| 365 } | |
| 366 TestVideoQuality("H264"); | |
| 367 } | |
| 368 | |
| 369 #endif // BUILDFLAG(RTC_USE_H264) | |
| OLD | NEW |