OLD | NEW |
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
5 | 5 |
6 import os | 6 import os |
7 import subprocess | 7 import subprocess |
8 import sys | 8 import sys |
9 | 9 |
10 import pyauto_functional | 10 import pyauto_functional |
(...skipping 12 matching lines...) Expand all Loading... |
23 _REFERENCE_YUV_FILE = os.path.join(_WORKING_DIR, 'reference_video.yuv') | 23 _REFERENCE_YUV_FILE = os.path.join(_WORKING_DIR, 'reference_video.yuv') |
24 | 24 |
25 # The YUV file is the file produced by rgba_to_i420_converter. | 25 # The YUV file is the file produced by rgba_to_i420_converter. |
26 _OUTPUT_YUV_FILE = os.path.join(_WORKING_DIR, 'captured_video.yuv') | 26 _OUTPUT_YUV_FILE = os.path.join(_WORKING_DIR, 'captured_video.yuv') |
27 | 27 |
28 | 28 |
29 class MissingRequiredToolException(Exception): | 29 class MissingRequiredToolException(Exception): |
30 pass | 30 pass |
31 | 31 |
32 | 32 |
| 33 class FailedToRunToolException(Exception): |
| 34 pass |
| 35 |
| 36 |
33 class WebrtcVideoQualityTest(webrtc_test_base.WebrtcTestBase): | 37 class WebrtcVideoQualityTest(webrtc_test_base.WebrtcTestBase): |
34 """Test the video quality of the WebRTC output. | 38 """Test the video quality of the WebRTC output. |
35 | 39 |
36 Prerequisites: This test case must run on a machine with a virtual webcam that | 40 Prerequisites: This test case must run on a machine with a virtual webcam that |
37 plays video from the reference file located in the location defined by | 41 plays video from the reference file located in the location defined by |
38 _REFERENCE_YUV_FILE. You must also compile the peerconnection_server target | 42 _REFERENCE_YUV_FILE. You must also compile the chromium_builder_webrtc target |
39 before you run this test. | 43 before you run this test to get all the tools built. |
| 44 The external compare_videos.py script also depends on two external executables |
| 45 which must be located in the PATH when running this test. |
| 46 * zxing (see the CPP version at https://code.google.com/p/zxing) |
| 47 * ffmpeg 0.11.1 or compatible version (see http://www.ffmpeg.org) |
40 | 48 |
41 The test case will launch a custom binary (peerconnection_server) which will | 49 The test case will launch a custom binary (peerconnection_server) which will |
42 allow two WebRTC clients to find each other. | 50 allow two WebRTC clients to find each other. |
43 | 51 |
44 The test also runs several other custom binaries - rgba_to_i420 converter and | 52 The test also runs several other custom binaries - rgba_to_i420 converter and |
45 frame_analyzer. Both tools can be found under third_party/webrtc/tools. The | 53 frame_analyzer. Both tools can be found under third_party/webrtc/tools. The |
46 test also runs a stand alone Python implementation of a WebSocket server | 54 test also runs a stand alone Python implementation of a WebSocket server |
47 (pywebsocket) and a barcode_decoder script. | 55 (pywebsocket) and a barcode_decoder script. |
48 """ | 56 """ |
49 | 57 |
(...skipping 133 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
183 no_more_frames = self.WaitUntil( | 191 no_more_frames = self.WaitUntil( |
184 function=lambda: self.ExecuteJavascript('haveMoreFramesToSend()', | 192 function=lambda: self.ExecuteJavascript('haveMoreFramesToSend()', |
185 tab_index=1), | 193 tab_index=1), |
186 expect_retval='no-more-frames', retry_sleep=1, timeout=150) | 194 expect_retval='no-more-frames', retry_sleep=1, timeout=150) |
187 self.assertTrue(no_more_frames, | 195 self.assertTrue(no_more_frames, |
188 msg='Timed out while waiting for frames to send.') | 196 msg='Timed out while waiting for frames to send.') |
189 | 197 |
190 self.assertTrue(self._RunRGBAToI420Converter(width, height)) | 198 self.assertTrue(self._RunRGBAToI420Converter(width, height)) |
191 | 199 |
192 stats_file = os.path.join(_WORKING_DIR, 'pyauto_stats.txt') | 200 stats_file = os.path.join(_WORKING_DIR, 'pyauto_stats.txt') |
193 self.assertTrue(self._RunBarcodeDecoder(width, height, _OUTPUT_YUV_FILE, | 201 analysis_result = self._CompareVideos(width, height, _OUTPUT_YUV_FILE, |
194 stats_file)) | 202 reference_yuv, stats_file) |
195 | |
196 analysis_result = self._RunFrameAnalyzer(width, height, reference_yuv, | |
197 _OUTPUT_YUV_FILE, stats_file) | |
198 self._ProcessPsnrAndSsimOutput(analysis_result) | 203 self._ProcessPsnrAndSsimOutput(analysis_result) |
199 self._ProcessFramesCountOutput(analysis_result) | 204 self._ProcessFramesCountOutput(analysis_result) |
200 | 205 |
201 def _StartPywebsocketServer(self): | 206 def _StartPywebsocketServer(self): |
202 """Starts the pywebsocket server.""" | 207 """Starts the pywebsocket server.""" |
203 print 'Starting pywebsocket server.' | 208 print 'Starting pywebsocket server.' |
204 | 209 |
205 # Pywebsocket source directory. | 210 # Pywebsocket source directory. |
206 path_pyws_dir = os.path.join(pyauto_paths.GetThirdPartyDir(), 'pywebsocket', | 211 path_pyws_dir = os.path.join(pyauto_paths.GetThirdPartyDir(), 'pywebsocket', |
207 'src') | 212 'src') |
(...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
266 # barcode decoder and frame analyzer tools. | 271 # barcode decoder and frame analyzer tools. |
267 start_cmd = [path_to_rgba_converter, '--frames_dir=%s' % _WORKING_DIR, | 272 start_cmd = [path_to_rgba_converter, '--frames_dir=%s' % _WORKING_DIR, |
268 '--output_file=%s' % _OUTPUT_YUV_FILE, '--width=%d' % width, | 273 '--output_file=%s' % _OUTPUT_YUV_FILE, '--width=%d' % width, |
269 '--height=%d' % height, '--delete_frames'] | 274 '--height=%d' % height, '--delete_frames'] |
270 print 'Start command: ', ' '.join(start_cmd) | 275 print 'Start command: ', ' '.join(start_cmd) |
271 rgba_converter = subprocess.Popen(start_cmd, stdout=sys.stdout, | 276 rgba_converter = subprocess.Popen(start_cmd, stdout=sys.stdout, |
272 stderr=sys.stderr) | 277 stderr=sys.stderr) |
273 rgba_converter.wait() | 278 rgba_converter.wait() |
274 return rgba_converter.returncode == 0 | 279 return rgba_converter.returncode == 0 |
275 | 280 |
276 def _RunBarcodeDecoder(self, width, height, captured_video_filename, | 281 def _CompareVideos(self, width, height, captured_video_filename, |
277 stats_filename): | 282 reference_video_filename, stats_filename): |
278 """Runs the barcode decoder script. | 283 """Compares the captured video with the reference video. |
279 | 284 |
280 The barcode decoder decodes the captured video containing barcodes overlaid | 285 The barcode decoder decodes the captured video containing barcodes overlaid |
281 into every frame of the video (produced by rgba_to_i420_converter). It | 286 into every frame of the video (produced by rgba_to_i420_converter). It |
282 produces a set of PNG images and a stats file that describes the relation | 287 produces a set of PNG images and a stats file that describes the relation |
283 between the filenames and the (decoded) frame number of each frame. | 288 between the filenames and the (decoded) frame number of each frame. |
284 | 289 |
285 The script depends on an external executable which is a part of the Zxing | |
286 barcode library, which must be located in the PATH when running this test. | |
287 | |
288 Args: | 290 Args: |
289 width(int): The frames width of the video to be decoded. | 291 width(int): The frames width of the video to be decoded. |
290 height(int): The frames height of the video to be decoded. | 292 height(int): The frames height of the video to be decoded. |
291 captured_video_filename(string): The captured video file we want to | 293 captured_video_filename(string): The captured video file we want to |
292 extract frame images and decode frame numbers from. | 294 extract frame images and decode frame numbers from. |
| 295 reference_video_filename(string): The reference video file we want to |
| 296 compare the captured video quality with. |
293 stats_filename(string): Filename for the output file containing | 297 stats_filename(string): Filename for the output file containing |
294 data that shows the relation between each frame filename and the | 298 data that shows the relation between each frame filename and the |
295 reference file's frame numbers. | 299 reference file's frame numbers. |
296 | 300 |
297 Returns: | 301 Returns: |
298 (bool): True if the decoding was successful, False otherwise. | 302 (string): The output of the script. |
299 """ | |
300 path_to_decoder = os.path.join(pyauto_paths.GetThirdPartyDir(), 'webrtc', | |
301 'tools', 'barcode_tools', | |
302 'barcode_decoder.py') | |
303 if not os.path.exists(path_to_decoder): | |
304 raise MissingRequiredToolException( | |
305 'Could not locate the barcode decoder script! The barcode decoder ' | |
306 'decodes the barcodes overlaid on top of every frame of the captured ' | |
307 'video.') | |
308 python_interp = sys.executable | |
309 start_cmd = [python_interp, path_to_decoder, | |
310 '--yuv_file=%s' % captured_video_filename, | |
311 '--yuv_frame_width=%d' % width, | |
312 '--yuv_frame_height=%d' % height, | |
313 '--stats_file=%s' % stats_filename] | |
314 print 'Start command: ', ' '.join(start_cmd) | |
315 | 303 |
316 barcode_decoder = subprocess.Popen(start_cmd, stdout=sys.stdout, | 304 Raises: |
317 stderr=sys.stderr) | 305 FailedToRunToolException: If the script fails to run. |
318 barcode_decoder.wait() | |
319 return barcode_decoder.returncode == 0 | |
320 | |
321 def _RunFrameAnalyzer(self, width, height, reference_video_file, | |
322 captured_video_file, stats_file): | |
323 """Runs the frame analyzer tool for PSNR and SSIM analysis. | |
324 | |
325 The frame analyzer is also part of the webrtc_test_tools. It should be | |
326 built before running this test. We assume that the binary will end up next | |
327 to Chrome. | |
328 | |
329 Frame analyzer prints its output to the standard output from where it has to | |
330 be read and processed. | |
331 | |
332 Args: | |
333 width(int): The width of the video frames to be analyzed. | |
334 height(int): The height of the video frames to be analyzed. | |
335 reference_video_file(string): Filename of the video to be used as a | |
336 reference during the analysis. | |
337 captured_video_file(string): Filename for the video containing the | |
338 captured frames. | |
339 stats_file(string): Filename for the file that contains frame | |
340 synchronization data for the captured frames. | |
341 | |
342 Returns: | |
343 (string): The output from the frame_analyzer. | |
344 """ | 306 """ |
345 path_to_analyzer = os.path.join(self.BrowserPath(), 'frame_analyzer') | 307 path_to_analyzer = os.path.join(self.BrowserPath(), 'frame_analyzer') |
346 path_to_analyzer = os.path.abspath(path_to_analyzer) | 308 path_to_analyzer = os.path.abspath(path_to_analyzer) |
347 | |
348 path_to_analyzer = self.BinPathForPlatform(path_to_analyzer) | 309 path_to_analyzer = self.BinPathForPlatform(path_to_analyzer) |
349 | 310 |
350 if not os.path.exists(path_to_analyzer): | 311 path_to_compare_script = os.path.join(pyauto_paths.GetThirdPartyDir(), |
351 raise webrtc_test_base.MissingRequiredBinaryException( | 312 'webrtc', 'tools', |
352 'Could not locate frame_analyzer! Did you build the ' | 313 'compare_videos.py') |
353 'webrtc_test_tools target?') | 314 if not os.path.exists(path_to_compare_script): |
| 315 raise MissingRequiredToolException('Cannot find the script at %s' % |
| 316 path_to_compare_script) |
| 317 python_interp = sys.executable |
| 318 cmd = [ |
| 319 python_interp, |
| 320 path_to_compare_script, |
| 321 '--ref_video=%s' % reference_video_filename, |
| 322 '--test_video=%s' % captured_video_filename, |
| 323 '--frame_analyzer=%s' % path_to_analyzer, |
| 324 '--yuv_frame_width=%d' % width, |
| 325 '--yuv_frame_height=%d' % height, |
| 326 '--stats_file=%s' % stats_filename, |
| 327 ] |
| 328 print 'Start command: ', ' '.join(cmd) |
354 | 329 |
355 start_cmd = [path_to_analyzer, '--reference_file=%s' % reference_video_file, | 330 compare_videos = subprocess.Popen(cmd, stdout=subprocess.PIPE, |
356 '--test_file=%s' % captured_video_file, | 331 stderr=subprocess.PIPE) |
357 '--stats_file=%s' % stats_file, | 332 output, error = compare_videos.communicate() |
358 '--width=%d' % width, '--height=%d' % height] | 333 if compare_videos.returncode != 0: |
359 print 'Start command: ', ' '.join(start_cmd) | 334 raise FailedToRunToolException('Failed to run compare videos script!') |
360 | 335 |
361 frame_analyzer = subprocess.Popen(start_cmd, stdout=subprocess.PIPE, | |
362 stderr=subprocess.PIPE) | |
363 output, error = frame_analyzer.communicate() | |
364 if error: | |
365 print 'Error: ', error | |
366 return 'BSTATS undef undef; ESTATS' | |
367 return output | 336 return output |
368 | 337 |
369 def _ProcessFramesCountOutput(self, output): | 338 def _ProcessFramesCountOutput(self, output): |
370 """Processes the analyzer output for the different frame counts. | 339 """Processes the analyzer output for the different frame counts. |
371 | 340 |
372 The frame analyzer outputs additional information about the number of unique | 341 The frame analyzer outputs additional information about the number of unique |
373 frames captured, The max number of repeated frames in a sequence and the | 342 frames captured, The max number of repeated frames in a sequence and the |
374 max number of skipped frames. These values are then written to the Perf | 343 max number of skipped frames. These values are then written to the Perf |
375 Graph. (Note: Some of the repeated or skipped frames will probably be due to | 344 Graph. (Note: Some of the repeated or skipped frames will probably be due to |
376 the imperfection of JavaScript timers.) | 345 the imperfection of JavaScript timers.) |
(...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
423 entry = item.split(' ') | 392 entry = item.split(' ') |
424 psnr.append(float(entry[0])) | 393 psnr.append(float(entry[0])) |
425 ssim.append(float(entry[1])) | 394 ssim.append(float(entry[1])) |
426 | 395 |
427 pyauto_utils.PrintPerfResult('PSNR', 'VGA', psnr, '') | 396 pyauto_utils.PrintPerfResult('PSNR', 'VGA', psnr, '') |
428 pyauto_utils.PrintPerfResult('SSIM', 'VGA', ssim, '') | 397 pyauto_utils.PrintPerfResult('SSIM', 'VGA', ssim, '') |
429 | 398 |
430 | 399 |
431 if __name__ == '__main__': | 400 if __name__ == '__main__': |
432 pyauto_functional.Main() | 401 pyauto_functional.Main() |
OLD | NEW |