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