• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2022 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#      http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14"""Verifies that preview FPS reaches minimum under low light conditions."""
15
16
17import logging
18import math
19import os.path
20
21from mobly import test_runner
22import numpy as np
23
24import its_base_test
25import camera_properties_utils
26import image_processing_utils
27import its_session_utils
28import lighting_control_utils
29import video_processing_utils
30
31_NAME = os.path.splitext(os.path.basename(__file__))[0]
32_PREVIEW_RECORDING_DURATION_SECONDS = 10
33_MAX_VAR_FRAME_DELTA = 0.001  # variance of frame deltas, units: seconds^2
34_FPS_ATOL = 0.5
35_DARKNESS_ATOL = 0.1
36
37
38class PreviewMinFrameRateTest(its_base_test.ItsBaseTest):
39  """Tests preview frame rate under dark lighting conditions.
40
41  Takes preview recording under dark conditions while setting
42  CONTROL_AE_TARGET_FPS_RANGE, and checks that the
43  recording's frame rate is at the minimum of the requested FPS range.
44  """
45
46  def test_video_min_frame_rate(self):
47    with its_session_utils.ItsSession(
48        device_id=self.dut.serial,
49        camera_id=self.camera_id,
50        hidden_physical_id=self.hidden_physical_id) as cam:
51      props = cam.get_camera_properties()
52      props = cam.override_with_hidden_physical_camera_props(props)
53
54      # check SKIP conditions
55      vendor_api_level = its_session_utils.get_vendor_api_level(self.dut.serial)
56      camera_properties_utils.skip_unless(
57          vendor_api_level >= its_session_utils.ANDROID14_API_LEVEL)
58
59      # determine acceptable ranges
60      fps_ranges = camera_properties_utils.get_ae_target_fps_ranges(props)
61      ae_target_fps_range = camera_properties_utils.get_fps_range_to_test(
62          fps_ranges)
63
64      # establish connection with lighting controller
65      arduino_serial_port = lighting_control_utils.lighting_control(
66          self.lighting_cntl, self.lighting_ch)
67
68      # turn OFF lights to darken scene
69      lighting_control_utils.set_lighting_state(
70          arduino_serial_port, self.lighting_ch, 'OFF')
71
72      # turn OFF tablet to darken scene
73      if self.tablet:
74        lighting_control_utils.turn_off_device(self.tablet)
75
76      logging.debug('Taking preview recording in darkened scene.')
77      # determine camera capabilities for preview
78      preview_sizes = cam.get_supported_preview_sizes(
79          self.camera_id)
80      logging.debug('Camera supported preview sizes: %s', preview_sizes)
81      preview_size = preview_sizes[-1]  # choose largest available size
82      logging.debug('Doing 3A to ensure AE convergence')
83      cam.do_3a(do_af=False)
84      logging.debug('Testing preview recording FPS for size: %s', preview_size)
85      preview_recording_obj = cam.do_preview_recording(
86          preview_size, _PREVIEW_RECORDING_DURATION_SECONDS, stabilize=False,
87          zoom_ratio=None,
88          ae_target_fps_min=ae_target_fps_range[0],
89          ae_target_fps_max=ae_target_fps_range[1])
90      logging.debug('preview_recording_obj: %s', preview_recording_obj)
91
92      # pull the video recording file from the device.
93      self.dut.adb.pull([preview_recording_obj['recordedOutputPath'],
94                         self.log_path])
95      logging.debug('Recorded preview video is available at: %s',
96                    self.log_path)
97      preview_file_name = preview_recording_obj[
98          'recordedOutputPath'].split('/')[-1]
99      logging.debug('preview_file_name: %s', preview_file_name)
100      preview_file_name_with_path = os.path.join(
101          self.log_path, preview_file_name)
102      preview_frame_rate = video_processing_utils.get_average_frame_rate(
103          preview_file_name_with_path)
104      errors = []
105      if not math.isclose(
106          preview_frame_rate, ae_target_fps_range[0], abs_tol=_FPS_ATOL):
107        errors.append(
108            f'Preview frame rate was {preview_frame_rate}, '
109            f'expected to be {ae_target_fps_range[0]}, '
110            f'ATOL: {_FPS_ATOL}.'
111        )
112      frame_deltas = np.array(video_processing_utils.get_frame_deltas(
113          preview_file_name_with_path))
114      frame_delta_avg = np.average(frame_deltas)
115      frame_delta_var = np.var(frame_deltas)
116      logging.debug('Delta avg: %.4f, delta var: %.4f',
117                    frame_delta_avg, frame_delta_var)
118      if frame_delta_var > _MAX_VAR_FRAME_DELTA:
119        errors.append(
120            f'Preview frame delta variance {frame_delta_var} too large, '
121            f'maximum allowed: {_MAX_VAR_FRAME_DELTA}.'
122        )
123      if errors:
124        raise AssertionError('\n'.join(errors))
125
126      last_key_frame = video_processing_utils.extract_key_frames_from_video(
127          self.log_path, preview_file_name)[-1]
128      logging.debug('Confirming video brightness in frame %s is low enough.',
129                    last_key_frame)
130      last_image = image_processing_utils.convert_image_to_numpy_array(
131          os.path.join(self.log_path, last_key_frame)) / 255
132      rgb = np.average(last_image, axis=(0, 1))
133      if not all(math.isclose(x, 0, abs_tol=_DARKNESS_ATOL) for x in rgb):
134        raise AssertionError(f'Last frame: {rgb}, expected: (0, 0, 0), '
135                             f'ATOL: {_DARKNESS_ATOL}')
136
137if __name__ == '__main__':
138  test_runner.main()
139