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