1# Copyright (c) 2014 The Chromium OS 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 5import datetime, logging, os, time, shutil 6 7from autotest_lib.client.bin import test, utils 8from autotest_lib.client.common_lib import error 9from autotest_lib.client.common_lib import file_utils 10from autotest_lib.client.common_lib import sequence_utils 11from autotest_lib.client.common_lib.cros import chrome 12from autotest_lib.client.cros import constants as cros_constants 13from autotest_lib.client.cros.chameleon import chameleon 14from autotest_lib.client.cros.chameleon import chameleon_port_finder 15from autotest_lib.client.cros.chameleon import chameleon_video_capturer 16from autotest_lib.client.cros.image_comparison import publisher 17from autotest_lib.client.cros.video import constants 18from autotest_lib.client.cros.video import frame_checksum_utils 19from autotest_lib.client.cros.video import native_html5_player 20from autotest_lib.client.cros.video import helper_logger 21from autotest_lib.client.cros.multimedia import local_facade_factory 22 23 24class video_GlitchDetection(test.test): 25 """ 26 Video playback test using image comparison. 27 28 Captures frames using chameleon and compares them to known golden frames. 29 30 If frames don't match, upload to GS for viewing later. 31 32 """ 33 version = 2 34 35 @helper_logger.video_log_wrapper 36 def run_once(self, source_path, codec, resolution, host, args, 37 collect_only = False): 38 39 board = utils.get_current_board() 40 41 shutil.copy2(constants.VIDEO_HTML_FILEPATH, self.bindir) 42 43 file_utils.make_leaf_dir(constants.TEST_DIR) 44 45 with chrome.Chrome( 46 extra_browser_args=helper_logger.chrome_vmodule_flag(), 47 extension_paths = [cros_constants.DISPLAY_TEST_EXTENSION], 48 autotest_ext=True, 49 init_network_controller=True) as cr: 50 51 cr.browser.platform.SetHTTPServerDirectories(self.bindir) 52 html_fullpath = os.path.join(self.bindir, 'video.html') 53 player = native_html5_player.NativeHtml5Player( 54 tab = cr.browser.tabs[0], 55 full_url = cr.browser.platform.http_server.UrlOf(html_fullpath), 56 video_id = 'video', 57 video_src_path = source_path, 58 event_timeout = 120) 59 60 chameleon_board = chameleon.create_chameleon_board(host.hostname, 61 args) 62 display_facade = local_facade_factory.LocalFacadeFactory( 63 cr).create_display_facade() 64 65 finder = chameleon_port_finder.ChameleonVideoInputFinder( 66 chameleon_board, display_facade) 67 68 ports = finder.find_all_ports().connected 69 70 logging.debug(finder) 71 72 if not ports: 73 raise error.TestError(finder) 74 75 capturer = chameleon_video_capturer.ChameleonVideoCapturer( 76 ports[0], display_facade) 77 78 with capturer: 79 player.load_video() 80 81 player.verify_video_can_play() 82 83 display_facade.move_to_display( 84 display_facade.get_first_external_display_id()) 85 display_facade.set_fullscreen(True) 86 # HACK: Unset and reset fullscreen. There is a bug in Chrome 87 # that fails to move the window to a correct position. 88 # Resetting fullscren helps, check http://crbug.com/574284 89 display_facade.set_fullscreen(False) 90 display_facade.set_fullscreen(True) 91 time.sleep(5) 92 93 box = (0, 0, constants.DESIRED_WIDTH, constants.DESIRED_HEIGHT) 94 95 #TODO: mussa, Revisit once crbug/580736 is fixed 96 for n in xrange(constants.NUM_CAPTURE_TRIES): 97 logging.debug('Trying to capture frames. TRY #%d', n + 1) 98 raw_test_checksums = capturer.capture_only( 99 player, max_frame_count = constants.FCOUNT, 100 box = box) 101 102 raw_test_checksums = [tuple(checksum) for checksum in 103 raw_test_checksums] 104 105 overreach_counts = self.overreach_frame_counts( 106 raw_test_checksums, 107 constants.MAX_FRAME_REPEAT_COUNT) 108 109 if not overreach_counts: # no checksums exceeded threshold 110 break 111 112 player.pause() 113 player.seek_to(datetime.timedelta(seconds=0)) 114 115 else: 116 msg = ('Framecount overreach detected even after %d ' 117 'tries. Checksums: %s' % (constants.NUM_CAPTURE_TRIES, 118 overreach_counts)) 119 raise error.TestFail(msg) 120 121 122 # produces unique checksums mapped to their occur. indices 123 test_checksum_indices = frame_checksum_utils.checksum_indices( 124 raw_test_checksums) 125 126 test_checksums = test_checksum_indices.keys() 127 128 test_indices = test_checksum_indices.values() 129 130 golden_checksums_filepath = os.path.join( 131 constants.TEST_DIR, 132 constants.GOLDEN_CHECKSUMS_FILENAME) 133 134 if collect_only: 135 capturer.write_images(test_indices, constants.TEST_DIR, 136 constants.IMAGE_FORMAT) 137 138 logging.debug("Write golden checksum file to %s", 139 golden_checksums_filepath) 140 141 with open(golden_checksums_filepath, "w+") as f: 142 for checksum in test_checksums: 143 f.write(' '.join([str(i) for i in checksum]) + '\n') 144 return 145 146 golden_checksums_remote_filepath = os.path.join( 147 constants.GOLDEN_CHECKSUM_REMOTE_BASE_DIR, 148 board, 149 codec + '_' + resolution, 150 constants.GOLDEN_CHECKSUMS_FILENAME) 151 152 file_utils.download_file(golden_checksums_remote_filepath, 153 golden_checksums_filepath) 154 155 golden_checksums = self.read_checksum_file( 156 golden_checksums_filepath) 157 158 golden_checksum_count = len(golden_checksums) 159 test_checksum_count = len(test_checksums) 160 161 eps = constants.MAX_DIFF_TOTAL_FCOUNT 162 if golden_checksum_count - test_checksum_count > eps: 163 msg = ('Expecting about %d checksums, received %d. ' 164 'Allowed delta is %d') % ( 165 golden_checksum_count, 166 test_checksum_count, 167 eps) 168 raise error.TestFail(msg) 169 170 # Some frames might be missing during either golden frame 171 # collection or during a test run. Using LCS ensures we 172 # ignore a few missing frames while comparing test vs golden 173 174 lcs_len = sequence_utils.lcs_length(golden_checksums, 175 test_checksums) 176 177 missing_frames_count = len(golden_checksums) - lcs_len 178 unknown_frames_count = len(test_checksums) - lcs_len 179 180 msg = ('# of matching frames : %d. # of missing frames : %d. ' 181 '# of unknown test frames : %d. Max allowed # of ' 182 'missing frames : %d. # of golden frames : %d. # of ' 183 'test_checksums : %d' %(lcs_len, missing_frames_count, 184 unknown_frames_count, 185 constants.MAX_NONMATCHING_FCOUNT, 186 len(golden_checksums), 187 len(test_checksums))) 188 logging.debug(msg) 189 190 if (missing_frames_count + unknown_frames_count > 191 constants.MAX_NONMATCHING_FCOUNT): 192 unknown_frames = set(test_checksums) - set(golden_checksums) 193 194 store_indices = [test_checksum_indices[c] for c in 195 unknown_frames] 196 197 paths = capturer.write_images(store_indices, 198 constants.TEST_DIR, 199 constants.IMAGE_FORMAT) 200 201 path_publish = publisher.ImageDiffPublisher(self.resultsdir) 202 path_publish.publish_paths(paths, self.tagged_testname) 203 204 raise error.TestFail("Too many non-matching frames") 205 206 207 def overreach_frame_counts(self, checksums, max_frame_repeat_count): 208 """ 209 Checks that captured frames have not exceed the max repeat count. 210 211 @param checksums: list of frame checksums received from chameleon. 212 @param max_frame_repeat_count: int. max allowed count. 213 @return : dictionary, checksums and their counts 214 215 """ 216 217 logging.debug("Verify no frame is repeated more than %d", 218 max_frame_repeat_count) 219 220 counts = frame_checksum_utils.checksum_counts(checksums) 221 overreach_counts = {} 222 223 for k, v in counts.iteritems(): 224 logging.debug("%s : %s", k, v) 225 if v > max_frame_repeat_count: 226 overreach_counts[k] = v 227 228 return overreach_counts 229 230 231 232 def read_checksum_file(self, path): 233 """ 234 Reads the golden checksum file. Each line in file has the format 235 w x y z where w x y z is a chameleon frame checksum 236 @param path: complete path to the golden checksum file. 237 @returns a list of 4-tuples (w x y z). 238 239 """ 240 checksums = [] 241 with open(path) as f: 242 for line in f: 243 checksum = tuple(int(val) for val in line.split()) 244 checksums.append(checksum) 245 return checksums 246