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