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 |