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