| 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 |