1# Copyright 2015 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 logging
6import os
7
8from autotest_lib.client.bin import utils
9from autotest_lib.client.common_lib import error
10
11
12class ChameleonVideoCapturer(object):
13    """
14    Wraps around chameleon APIs to provide an easy way to capture video frames.
15
16    """
17
18
19    def __init__(self, chameleon_port, display_facade,
20                 timeout_get_all_frames_s=60):
21
22        self.chameleon_port = chameleon_port
23        self.display_facade = display_facade
24        self.timeout_get_all_frames_s = timeout_get_all_frames_s
25        self._checksums = []
26
27        self.was_plugged = None
28
29
30    def __enter__(self):
31        self.was_plugged = self.chameleon_port.plugged
32
33        if not self.was_plugged:
34            self.chameleon_port.plug()
35            self.chameleon_port.wait_video_input_stable()
36
37        return self
38
39
40    def capture(self, player, max_frame_count, box=None):
41        """
42        Captures frames upto max_frame_count, saves the image with filename
43        same as the index of the frame in the frame buffer.
44
45        @param player: object, VimeoPlayer or NativeHTML5Player
46        @param max_frame_count: int, maximum total number of frames to capture.
47        @param box: int tuple, left, upper, right, lower pixel coordinates.
48                    Defines the rectangular boundary within which to compare.
49        @return: list of paths of captured images.
50
51        """
52
53        self.capture_only(player, max_frame_count, box)
54        # each checksum should be saved with a filename that is its index
55        ind_paths = {i : str(i) for i in self.checksums}
56        return self.write_images(ind_paths)
57
58
59    def capture_only(self, player, max_frame_count, box=None):
60        """
61        Asynchronously begins capturing video frames. Stops capturing when the
62        number of frames captured is equal or more than max_frame_count. Does
63        save the images, gets only the checksums.
64
65        @param player: VimeoPlayer or NativeHTML5Player.
66        @param max_frame_count: int, the maximum number of frames we want.
67        @param box: int tuple, left, upper, right, lower pixel coordinates.
68                    Defines the rectangular boundary within which to compare.
69        @return: list of checksums
70
71        """
72
73        if not box:
74            box = self.box
75
76        self.chameleon_port.start_capturing_video(box)
77
78        player.play()
79
80        error_msg = "Expected current time to be > 1 seconds"
81
82        utils.poll_for_condition(lambda : player.currentTime() > 1,
83                                 timeout=5,
84                                 sleep_interval=0.01,
85                                 exception=error.TestError(error_msg))
86
87        error_msg = "Couldn't get the right number of frames"
88
89        utils.poll_for_condition(
90                lambda: self.chameleon_port.get_captured_frame_count() >=
91                        max_frame_count,
92                error.TestError(error_msg),
93                self.timeout_get_all_frames_s,
94                sleep_interval=0.01)
95
96        self.chameleon_port.stop_capturing_video()
97
98        self.checksums = self.chameleon_port.get_captured_checksums()
99        count = self.chameleon_port.get_captured_frame_count()
100
101        # Due to the polling and asychronous calls we might get too many frames
102        # cap at max
103        del self.checksums[max_frame_count:]
104
105        logging.debug("***# of frames received %s", count)
106        logging.debug("Checksums before chopping repeated ones")
107        for c in self.checksums:
108            logging.debug(c)
109
110        # Find the first frame that is different from previous ones. This
111        # represents the start of 'interesting' frames
112        first_index = 0
113        for i in xrange(1, count):
114            if self.checksums[0] != self.checksums[i]:
115                first_index = i
116                break
117
118        logging.debug("*** First interesting frame at index = %s", first_index)
119        self.checksums = self.checksums[first_index:]
120        return self.checksums
121
122
123
124    def write_images(self, frame_indices, dest_dir, image_format):
125        """
126        Saves frames of given indices to disk. The filename of the frame will be
127        index in the list.
128        @param frame_indices: list of frame indices to save.
129        @param dest_dir: path to the desired destination dir.
130        @param image_format: string, format to save the image as. e.g; PNG
131        @return: list of file paths
132
133        """
134        if type(frame_indices) is not list:
135            frame_indices = [frame_indices]
136
137        test_images = []
138        curr_checksum = None
139        for i, frame_index in enumerate(frame_indices):
140            path = os.path.join(dest_dir, str(i) + '.' + image_format)
141            # previous is what was current in the previous iteration
142            prev_checksum = curr_checksum
143            curr_checksum = self.checksums[frame_index]
144            if curr_checksum == prev_checksum:
145                logging.debug("Image the same as previous image, copy it.")
146            else:
147                logging.debug("Read frame %d, store as %s.", i, path)
148                curr_img = self.chameleon_port.read_captured_frame(frame_index)
149            curr_img.save(path)
150            test_images.append(path)
151        return test_images
152
153
154    def __exit__(self, exc_type, exc_val, exc_tb):
155        if not self.was_plugged:
156            self.chameleon_port.unplug()