• 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 capture_request_utils
27import image_processing_utils
28import its_session_utils
29import lighting_control_utils
30import opencv_processing_utils
31import video_processing_utils
32
33_NAME = os.path.splitext(os.path.basename(__file__))[0]
34_PREVIEW_RECORDING_DURATION_SECONDS = 10
35_CONTROL_AE_ANTIBANDING_MODE_OFF = 0
36_MAX_VAR_FRAME_DELTA = 0.001  # variance of frame deltas, units: seconds^2
37_FPS_ATOL = 1
38_DARKNESS_ATOL = 0.1 * 255  # openCV uses [0:255] images
39
40
41class PreviewMinFrameRateTest(its_base_test.ItsBaseTest):
42  """Tests preview frame rate under dark lighting conditions.
43
44  Takes preview recording under dark conditions while setting
45  CONTROL_AE_TARGET_FPS_RANGE, and checks that the
46  recording's frame rate is at the minimum of the requested FPS range.
47  """
48
49  def test_preview_min_frame_rate(self):
50    with its_session_utils.ItsSession(
51        device_id=self.dut.serial,
52        camera_id=self.camera_id,
53        hidden_physical_id=self.hidden_physical_id) as cam:
54      props = cam.get_camera_properties()
55      props = cam.override_with_hidden_physical_camera_props(props)
56
57      # check SKIP conditions
58      first_api_level = its_session_utils.get_first_api_level(self.dut.serial)
59      camera_properties_utils.skip_unless(
60          first_api_level >= its_session_utils.ANDROID14_API_LEVEL)
61
62      # determine acceptable ranges
63      fps_ranges = camera_properties_utils.get_ae_target_fps_ranges(props)
64      ae_target_fps_range = camera_properties_utils.get_fps_range_to_test(
65          fps_ranges)
66
67      # establish connection with lighting controller
68      arduino_serial_port = lighting_control_utils.lighting_control(
69          self.lighting_cntl, self.lighting_ch)
70
71      # turn OFF lights to darken scene
72      lighting_control_utils.set_lighting_state(
73          arduino_serial_port, self.lighting_ch, 'OFF')
74
75      # turn OFF DUT to reduce reflections
76      lighting_control_utils.turn_off_device_screen(self.dut)
77
78      # Validate lighting
79      cam.do_3a(do_af=False)
80      cap = cam.do_capture(
81          capture_request_utils.auto_capture_request(), cam.CAP_YUV)
82      y_plane, _, _ = image_processing_utils.convert_capture_to_planes(cap)
83      # In the sensor fusion rig, there is no tablet, so tablet_state is OFF.
84      its_session_utils.validate_lighting(
85          y_plane, self.scene, state='OFF', tablet_state='OFF',
86          log_path=self.log_path)
87      # Check for flickering frequency
88      scene_flicker_freq = cap['metadata']['android.statistics.sceneFlicker']
89      logging.debug('Detected flickering frequency: %d', scene_flicker_freq)
90      logging.debug('Taking preview recording in darkened scene.')
91      # determine camera capabilities for preview
92      preview_sizes = cam.get_supported_preview_sizes(
93          self.camera_id)
94      supported_video_sizes = cam.get_supported_video_sizes_capped(
95          self.camera_id)
96      max_video_size = supported_video_sizes[-1]  # largest available size
97      logging.debug('Camera supported video sizes: %s', supported_video_sizes)
98
99      preview_size = preview_sizes[-1]  # choose largest available size
100      if preview_size <= max_video_size:
101        logging.debug('preview_size is supported by video encoder')
102      else:
103        preview_size = max_video_size
104      logging.debug('Doing 3A to ensure AE convergence')
105      cam.do_3a(do_af=False)
106      logging.debug('Testing preview recording FPS for size: %s', preview_size)
107      antibanding_mode_to_set = scene_flicker_freq
108      if _CONTROL_AE_ANTIBANDING_MODE_OFF in props.get(
109          'android.control.aeAvailableAntibandingModes', []):
110        antibanding_mode_to_set = _CONTROL_AE_ANTIBANDING_MODE_OFF
111      preview_recording_obj = cam.do_preview_recording(
112          preview_size, _PREVIEW_RECORDING_DURATION_SECONDS, stabilize=False,
113          zoom_ratio=None,
114          ae_target_fps_min=ae_target_fps_range[0],
115          ae_target_fps_max=ae_target_fps_range[1],
116          antibanding_mode=antibanding_mode_to_set
117      )
118      logging.debug('preview_recording_obj: %s', preview_recording_obj)
119
120      # turn lights back ON
121      lighting_control_utils.set_lighting_state(
122          arduino_serial_port, self.lighting_ch, 'ON')
123
124      # pull the video recording file from the device.
125      self.dut.adb.pull([preview_recording_obj['recordedOutputPath'],
126                         self.log_path])
127      logging.debug('Recorded preview video is available at: %s',
128                    self.log_path)
129      preview_file_name = preview_recording_obj[
130          'recordedOutputPath'].split('/')[-1]
131      logging.debug('preview_file_name: %s', preview_file_name)
132      preview_file_name_with_path = os.path.join(
133          self.log_path, preview_file_name)
134      preview_frame_rate = video_processing_utils.get_avg_frame_rate(
135          preview_file_name_with_path)
136      errors = []
137      if not math.isclose(
138          preview_frame_rate, ae_target_fps_range[0], abs_tol=_FPS_ATOL):
139        errors.append(
140            f'Preview frame rate was {preview_frame_rate:.3f}. '
141            f'Expected to be {ae_target_fps_range[0]}, ATOL: {_FPS_ATOL}.'
142        )
143      frame_deltas = np.array(video_processing_utils.get_frame_deltas(
144          preview_file_name_with_path))
145      frame_delta_avg = np.average(frame_deltas)
146      frame_delta_var = np.var(frame_deltas)
147      logging.debug('Delta avg: %.4f, delta var: %.4f',
148                    frame_delta_avg, frame_delta_var)
149      if frame_delta_var > _MAX_VAR_FRAME_DELTA:
150        errors.append(
151            f'Preview frame delta variance {frame_delta_var:.3f} too large, '
152            f'maximum allowed: {_MAX_VAR_FRAME_DELTA}.'
153        )
154      if errors:
155        raise AssertionError('\n'.join(errors))
156
157      last_key_frame = video_processing_utils.extract_key_frames_from_video(
158          self.log_path, preview_file_name)[-1]
159      logging.debug('Confirming video brightness in frame %s is low enough.',
160                    last_key_frame)
161      last_image = image_processing_utils.convert_image_to_numpy_array(
162          os.path.join(self.log_path, last_key_frame))
163      y_avg = np.average(
164          opencv_processing_utils.convert_to_y(last_image, 'RGB')
165      )
166      logging.debug('Last frame y avg: %.4f', y_avg)
167      if not math.isclose(y_avg, 0, abs_tol=_DARKNESS_ATOL):
168        raise AssertionError(f'Last frame y average: {y_avg}, expected: 0, '
169                             f'ATOL: {_DARKNESS_ATOL}')
170
171if __name__ == '__main__':
172  test_runner.main()
173