1# Copyright 2023 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 advertised FPS from device as webcam.""" 15 16import logging 17import time 18 19import AVFoundation 20import Cocoa 21from libdispatch import dispatch_queue_create 22import objc 23 24 25objc.loadBundle('AVFoundation', 26 bundle_path=objc.pathForFramework('AVFoundation.framework'), 27 module_globals=globals()) 28 29_TOTAL_NUM_FRAMES = 0 30_DEVICE_NAME = 'android' # TODO b/277159494 31_FPS_TEST_DURATION = 10 # seconds 32 33 34class SampleBufferDelegate(Cocoa.NSObject): 35 """Notified upon every frame. Used to help calculate FPS. 36 """ 37 38 def captureOutput_didOutputSampleBuffer_fromConnection_(self, 39 output, 40 sample_buffer, 41 connection): 42 global _TOTAL_NUM_FRAMES 43 44 if sample_buffer: 45 _TOTAL_NUM_FRAMES += 1 46 47 48def initialize_device(): 49 """Initializes android webcam device. 50 51 Returns: 52 Returns the device if an android device is found, None otherwise 53 """ 54 devices = AVFoundation.AVCaptureDevice.devices() 55 56 res_device = None 57 for device in devices: 58 if (device.hasMediaType_(AVFoundation.AVMediaTypeVideo) and 59 (_DEVICE_NAME in device.localizedName().lower())): 60 res_device = device 61 logging.info('Using webcam %s', res_device.localizedName()) 62 break 63 64 return res_device 65 66 67def initialize_formats_and_resolutions(device): 68 """Initializes list of device advertised formats, resolutions and FPS. 69 70 Args: 71 device: the device from which the info will be retrieved 72 73 Returns: 74 Returns a list of the formats, resolutions, and frame rates 75 [ (format, [resolution, [frame rates] ] )] 76 """ 77 supported_formats = device.formats() 78 formats_and_resolutions = [] 79 80 for format_index, frmt in enumerate(supported_formats): 81 formats_and_resolutions.append((frmt, [])) 82 frame_rate_ranges = frmt.videoSupportedFrameRateRanges() 83 84 for frame_rate_range in frame_rate_ranges: 85 min_frame_rate = frame_rate_range.minFrameRate() 86 max_frame_rate = frame_rate_range.maxFrameRate() 87 default_frame_rate = ( 88 device.activeVideoMinFrameDuration().timescale / 89 device.activeVideoMinFrameDuration().value) 90 91 formats_and_resolutions[format_index][1].append(min_frame_rate + 1) 92 formats_and_resolutions[format_index][1].append(max_frame_rate) 93 formats_and_resolutions[format_index][1].append(default_frame_rate) 94 95 return formats_and_resolutions 96 97 98def setup_for_test_fps(device, supported_formats_and_resolutions): 99 """Configures device with format, resolution, and FPS to be tested. 100 101 Args: 102 device: device under test 103 supported_formats_and_resolutions: list containing supported device 104 configurations 105 106 Returns: 107 Returns a list of tuples, where the first element is the frame 108 rate that was being tested and the second element is the actual fps 109 """ 110 res = [] 111 112 delegate = SampleBufferDelegate.alloc().init() 113 session = AVFoundation.AVCaptureSession.alloc().init() 114 115 input_for_session = ( 116 AVFoundation.AVCaptureDeviceInput.deviceInputWithDevice_error_(device, 117 None)) 118 session.addInput_(input_for_session[0]) 119 120 video_output = AVFoundation.AVCaptureVideoDataOutput.alloc().init() 121 queue = dispatch_queue_create(b'1', None) 122 video_output.setSampleBufferDelegate_queue_(delegate, queue) 123 session.addOutput_(video_output) 124 125 session.startRunning() 126 127 for elem in supported_formats_and_resolutions: 128 frmt = elem[0] 129 frame_rates = elem[1] 130 131 for frame_rate in frame_rates: 132 global _TOTAL_NUM_FRAMES 133 _TOTAL_NUM_FRAMES = 0 # reset total num frames for every test 134 device.lockForConfiguration_(None) 135 device.setActiveFormat_(frmt) 136 device.setActiveVideoMinFrameDuration_( 137 AVFoundation.CMTimeMake(1, frame_rate)) 138 device.setActiveVideoMaxFrameDuration_( 139 AVFoundation.CMTimeMake(1, frame_rate)) 140 device.unlockForConfiguration() 141 142 res.append((frame_rate, test_fps())) 143 144 session.removeInput_(input_for_session) 145 session.removeOutput_(video_output) 146 session.stopRunning() 147 input_for_session[0].release() 148 video_output.release() 149 return res 150 151 152def test_fps(): 153 """Tests and returns test estimated fps. 154 155 Returns: 156 Test estimated fps 157 """ 158 start_time = time.time() 159 time.sleep(_FPS_TEST_DURATION) 160 end_time = time.time() 161 fps = _TOTAL_NUM_FRAMES / (end_time - start_time) 162 return fps 163 164 165def main(): 166 device = initialize_device() 167 168 if not device: 169 logging.error('Supported device not found!') 170 return [] 171 172 supported_formats_and_resolutions = initialize_formats_and_resolutions(device) 173 174 if not supported_formats_and_resolutions: 175 logging.error('Error retrieving formats and resolutions') 176 return [] 177 178 res = setup_for_test_fps(device, supported_formats_and_resolutions) 179 180 return res 181 182 183if __name__ == '__main__': 184 main() 185