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