• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.MULTIMEDIA_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