| Ruben Brunk | 370e243 | 2014-10-14 18:33:23 -0700 | [diff] [blame] | 1 | # Copyright 2013 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 |  | 
|  | 15 | import os | 
|  | 16 | import os.path | 
|  | 17 | import sys | 
|  | 18 | import re | 
|  | 19 | import json | 
|  | 20 | import tempfile | 
|  | 21 | import time | 
|  | 22 | import unittest | 
|  | 23 | import subprocess | 
|  | 24 | import math | 
|  | 25 |  | 
|  | 26 | def int_to_rational(i): | 
|  | 27 | """Function to convert Python integers to Camera2 rationals. | 
|  | 28 |  | 
|  | 29 | Args: | 
|  | 30 | i: Python integer or list of integers. | 
|  | 31 |  | 
|  | 32 | Returns: | 
|  | 33 | Python dictionary or list of dictionaries representing the given int(s) | 
|  | 34 | as rationals with denominator=1. | 
|  | 35 | """ | 
|  | 36 | if isinstance(i, list): | 
|  | 37 | return [{"numerator":val, "denominator":1} for val in i] | 
|  | 38 | else: | 
|  | 39 | return {"numerator":i, "denominator":1} | 
|  | 40 |  | 
|  | 41 | def float_to_rational(f, denom=128): | 
|  | 42 | """Function to convert Python floats to Camera2 rationals. | 
|  | 43 |  | 
|  | 44 | Args: | 
|  | 45 | f: Python float or list of floats. | 
|  | 46 | denom: (Optonal) the denominator to use in the output rationals. | 
|  | 47 |  | 
|  | 48 | Returns: | 
|  | 49 | Python dictionary or list of dictionaries representing the given | 
|  | 50 | float(s) as rationals. | 
|  | 51 | """ | 
|  | 52 | if isinstance(f, list): | 
|  | 53 | return [{"numerator":math.floor(val*denom+0.5), "denominator":denom} | 
|  | 54 | for val in f] | 
|  | 55 | else: | 
|  | 56 | return {"numerator":math.floor(f*denom+0.5), "denominator":denom} | 
|  | 57 |  | 
|  | 58 | def rational_to_float(r): | 
|  | 59 | """Function to convert Camera2 rational objects to Python floats. | 
|  | 60 |  | 
|  | 61 | Args: | 
|  | 62 | r: Rational or list of rationals, as Python dictionaries. | 
|  | 63 |  | 
|  | 64 | Returns: | 
|  | 65 | Float or list of floats. | 
|  | 66 | """ | 
|  | 67 | if isinstance(r, list): | 
|  | 68 | return [float(val["numerator"]) / float(val["denominator"]) | 
|  | 69 | for val in r] | 
|  | 70 | else: | 
|  | 71 | return float(r["numerator"]) / float(r["denominator"]) | 
|  | 72 |  | 
|  | 73 | def manual_capture_request(sensitivity, exp_time, linear_tonemap=False): | 
|  | 74 | """Return a capture request with everything set to manual. | 
|  | 75 |  | 
|  | 76 | Uses identity/unit color correction, and the default tonemap curve. | 
|  | 77 | Optionally, the tonemap can be specified as being linear. | 
|  | 78 |  | 
|  | 79 | Args: | 
|  | 80 | sensitivity: The sensitivity value to populate the request with. | 
|  | 81 | exp_time: The exposure time, in nanoseconds, to populate the request | 
|  | 82 | with. | 
|  | 83 | linear_tonemap: [Optional] whether a linear tonemap should be used | 
|  | 84 | in this request. | 
|  | 85 |  | 
|  | 86 | Returns: | 
|  | 87 | The default manual capture request, ready to be passed to the | 
|  | 88 | its.device.do_capture function. | 
|  | 89 | """ | 
|  | 90 | req = { | 
| Yin-Chia Yeh | 3657ae3 | 2014-11-13 15:06:22 -0800 | [diff] [blame] | 91 | "android.control.captureIntent": 6, | 
| Ruben Brunk | 370e243 | 2014-10-14 18:33:23 -0700 | [diff] [blame] | 92 | "android.control.mode": 0, | 
|  | 93 | "android.control.aeMode": 0, | 
|  | 94 | "android.control.awbMode": 0, | 
|  | 95 | "android.control.afMode": 0, | 
|  | 96 | "android.control.effectMode": 0, | 
|  | 97 | "android.sensor.frameDuration": 0, | 
|  | 98 | "android.sensor.sensitivity": sensitivity, | 
|  | 99 | "android.sensor.exposureTime": exp_time, | 
|  | 100 | "android.colorCorrection.mode": 0, | 
|  | 101 | "android.colorCorrection.transform": | 
|  | 102 | int_to_rational([1,0,0, 0,1,0, 0,0,1]), | 
|  | 103 | "android.colorCorrection.gains": [1,1,1,1], | 
|  | 104 | "android.tonemap.mode": 1, | 
| Yin-Chia Yeh | 3657ae3 | 2014-11-13 15:06:22 -0800 | [diff] [blame] | 105 | "android.shading.mode": 1 | 
| Ruben Brunk | 370e243 | 2014-10-14 18:33:23 -0700 | [diff] [blame] | 106 | } | 
|  | 107 | if linear_tonemap: | 
|  | 108 | req["android.tonemap.mode"] = 0 | 
|  | 109 | req["android.tonemap.curveRed"] = [0.0,0.0, 1.0,1.0] | 
|  | 110 | req["android.tonemap.curveGreen"] = [0.0,0.0, 1.0,1.0] | 
|  | 111 | req["android.tonemap.curveBlue"] = [0.0,0.0, 1.0,1.0] | 
|  | 112 | return req | 
|  | 113 |  | 
|  | 114 | def auto_capture_request(): | 
|  | 115 | """Return a capture request with everything set to auto. | 
|  | 116 | """ | 
|  | 117 | return { | 
|  | 118 | "android.control.mode": 1, | 
|  | 119 | "android.control.aeMode": 1, | 
|  | 120 | "android.control.awbMode": 1, | 
|  | 121 | "android.control.afMode": 1, | 
|  | 122 | "android.colorCorrection.mode": 1, | 
|  | 123 | "android.tonemap.mode": 1, | 
|  | 124 | } | 
|  | 125 |  | 
| Yin-Chia Yeh | d077602 | 2014-11-24 13:14:07 -0800 | [diff] [blame] | 126 | def fastest_auto_capture_request(props): | 
|  | 127 | """Return an auto capture request for the fastest capture. | 
|  | 128 |  | 
|  | 129 | Args: | 
|  | 130 | props: the object returned from its.device.get_camera_properties(). | 
|  | 131 |  | 
|  | 132 | Returns: | 
|  | 133 | A capture request with everything set to auto and all filters that | 
|  | 134 | may slow down capture set to OFF or FAST if possible | 
|  | 135 | """ | 
|  | 136 | req = auto_capture_request() | 
|  | 137 | turn_slow_filters_off(props, req) | 
|  | 138 |  | 
|  | 139 | return req | 
|  | 140 |  | 
| Yin-Chia Yeh | d511ded | 2015-09-03 14:46:57 -0700 | [diff] [blame] | 141 | def get_available_output_sizes(fmt, props, max_size=None, match_ar_size=None): | 
| Ruben Brunk | 370e243 | 2014-10-14 18:33:23 -0700 | [diff] [blame] | 142 | """Return a sorted list of available output sizes for a given format. | 
|  | 143 |  | 
|  | 144 | Args: | 
|  | 145 | fmt: the output format, as a string in ["jpg", "yuv", "raw"]. | 
|  | 146 | props: the object returned from its.device.get_camera_properties(). | 
| Yin-Chia Yeh | d511ded | 2015-09-03 14:46:57 -0700 | [diff] [blame] | 147 | max_size: (Optional) A (w,h) tuple. | 
|  | 148 | Sizes larger than max_size (either w or h)  will be discarded. | 
|  | 149 | match_ar_size: (Optional) A (w,h) tuple. | 
|  | 150 | Sizes not matching the aspect ratio of match_ar_size will be | 
|  | 151 | discarded. | 
| Ruben Brunk | 370e243 | 2014-10-14 18:33:23 -0700 | [diff] [blame] | 152 |  | 
|  | 153 | Returns: | 
|  | 154 | A sorted list of (w,h) tuples (sorted large-to-small). | 
|  | 155 | """ | 
| Yin-Chia Yeh | d511ded | 2015-09-03 14:46:57 -0700 | [diff] [blame] | 156 | AR_TOLERANCE = 0.03 | 
| Ruben Brunk | 370e243 | 2014-10-14 18:33:23 -0700 | [diff] [blame] | 157 | fmt_codes = {"raw":0x20, "raw10":0x25, "yuv":0x23, "jpg":0x100, "jpeg":0x100} | 
|  | 158 | configs = props['android.scaler.streamConfigurationMap']\ | 
|  | 159 | ['availableStreamConfigurations'] | 
|  | 160 | fmt_configs = [cfg for cfg in configs if cfg['format'] == fmt_codes[fmt]] | 
|  | 161 | out_configs = [cfg for cfg in fmt_configs if cfg['input'] == False] | 
|  | 162 | out_sizes = [(cfg['width'],cfg['height']) for cfg in out_configs] | 
| Yin-Chia Yeh | d511ded | 2015-09-03 14:46:57 -0700 | [diff] [blame] | 163 | if max_size: | 
|  | 164 | out_sizes = [s for s in out_sizes if | 
|  | 165 | s[0] <= max_size[0] and s[1] <= max_size[1]] | 
|  | 166 | if match_ar_size: | 
|  | 167 | ar = match_ar_size[0] / float(match_ar_size[1]) | 
|  | 168 | out_sizes = [s for s in out_sizes if | 
|  | 169 | abs(ar - s[0] / float(s[1])) <= AR_TOLERANCE] | 
| Ruben Brunk | 370e243 | 2014-10-14 18:33:23 -0700 | [diff] [blame] | 170 | out_sizes.sort(reverse=True) | 
|  | 171 | return out_sizes | 
|  | 172 |  | 
| Yin-Chia Yeh | 3657ae3 | 2014-11-13 15:06:22 -0800 | [diff] [blame] | 173 | def set_filter_off_or_fast_if_possible(props, req, available_modes, filter): | 
| Unsuk Jung | ff9926e | 2015-09-12 19:50:47 +0000 | [diff] [blame^] | 174 | """Check and set controlKey to off or fast in req. | 
| Yin-Chia Yeh | 3657ae3 | 2014-11-13 15:06:22 -0800 | [diff] [blame] | 175 |  | 
|  | 176 | Args: | 
|  | 177 | props: the object returned from its.device.get_camera_properties(). | 
| Unsuk Jung | ff9926e | 2015-09-12 19:50:47 +0000 | [diff] [blame^] | 178 | req: the input request. filter will be set to OFF or FAST if possible. | 
| Yin-Chia Yeh | 3657ae3 | 2014-11-13 15:06:22 -0800 | [diff] [blame] | 179 | available_modes: the key to check available modes. | 
|  | 180 | filter: the filter key | 
|  | 181 |  | 
|  | 182 | Returns: | 
| Unsuk Jung | ff9926e | 2015-09-12 19:50:47 +0000 | [diff] [blame^] | 183 | Nothing. | 
| Yin-Chia Yeh | 3657ae3 | 2014-11-13 15:06:22 -0800 | [diff] [blame] | 184 | """ | 
|  | 185 | if props.has_key(available_modes): | 
|  | 186 | if 0 in props[available_modes]: | 
|  | 187 | req[filter] = 0 | 
|  | 188 | elif 1 in props[available_modes]: | 
|  | 189 | req[filter] = 1 | 
|  | 190 |  | 
| Yin-Chia Yeh | d077602 | 2014-11-24 13:14:07 -0800 | [diff] [blame] | 191 | def turn_slow_filters_off(props, req): | 
|  | 192 | """Turn filters that may slow FPS down to OFF or FAST in input request. | 
|  | 193 |  | 
|  | 194 | This function modifies the request argument, such that filters that may | 
|  | 195 | reduce the frames-per-second throughput of the camera device will be set to | 
|  | 196 | OFF or FAST if possible. | 
|  | 197 |  | 
|  | 198 | Args: | 
|  | 199 | props: the object returned from its.device.get_camera_properties(). | 
|  | 200 | req: the input request. | 
|  | 201 |  | 
|  | 202 | Returns: | 
|  | 203 | Nothing. | 
|  | 204 | """ | 
|  | 205 | set_filter_off_or_fast_if_possible(props, req, | 
|  | 206 | "android.noiseReduction.availableNoiseReductionModes", | 
|  | 207 | "android.noiseReduction.mode") | 
|  | 208 | set_filter_off_or_fast_if_possible(props, req, | 
|  | 209 | "android.colorCorrection.availableAberrationModes", | 
|  | 210 | "android.colorCorrection.aberrationMode") | 
| Itaru Hanada | 87dcd9c | 2015-07-28 21:36:31 +0900 | [diff] [blame] | 211 | if props.has_key("android.request.availableCharacteristicsKeys"): | 
|  | 212 | hot_pixel_modes = 393217 in props["android.request.availableCharacteristicsKeys"] | 
|  | 213 | edge_modes = 196610 in props["android.request.availableCharacteristicsKeys"] | 
|  | 214 | if props.has_key("android.request.availableRequestKeys"): | 
|  | 215 | hot_pixel_mode = 393216 in props["android.request.availableRequestKeys"] | 
|  | 216 | edge_mode = 196608 in props["android.request.availableRequestKeys"] | 
|  | 217 | if hot_pixel_modes and hot_pixel_mode: | 
|  | 218 | set_filter_off_or_fast_if_possible(props, req, | 
|  | 219 | "android.hotPixel.availableHotPixelModes", | 
|  | 220 | "android.hotPixel.mode") | 
|  | 221 | if edge_modes and edge_mode: | 
|  | 222 | set_filter_off_or_fast_if_possible(props, req, | 
|  | 223 | "android.edge.availableEdgeModes", | 
|  | 224 | "android.edge.mode") | 
| Yin-Chia Yeh | d077602 | 2014-11-24 13:14:07 -0800 | [diff] [blame] | 225 |  | 
| Ruben Brunk | 370e243 | 2014-10-14 18:33:23 -0700 | [diff] [blame] | 226 | def get_fastest_manual_capture_settings(props): | 
|  | 227 | """Return a capture request and format spec for the fastest capture. | 
|  | 228 |  | 
|  | 229 | Args: | 
|  | 230 | props: the object returned from its.device.get_camera_properties(). | 
|  | 231 |  | 
|  | 232 | Returns: | 
|  | 233 | Two values, the first is a capture request, and the second is an output | 
|  | 234 | format specification, for the fastest possible (legal) capture that | 
|  | 235 | can be performed on this device (with the smallest output size). | 
|  | 236 | """ | 
|  | 237 | fmt = "yuv" | 
|  | 238 | size = get_available_output_sizes(fmt, props)[-1] | 
|  | 239 | out_spec = {"format":fmt, "width":size[0], "height":size[1]} | 
|  | 240 | s = min(props['android.sensor.info.sensitivityRange']) | 
|  | 241 | e = min(props['android.sensor.info.exposureTimeRange']) | 
|  | 242 | req = manual_capture_request(s,e) | 
| Yin-Chia Yeh | 3657ae3 | 2014-11-13 15:06:22 -0800 | [diff] [blame] | 243 |  | 
| Unsuk Jung | ff9926e | 2015-09-12 19:50:47 +0000 | [diff] [blame^] | 244 | turn_slow_filters_off(props, req) | 
| Yin-Chia Yeh | 3657ae3 | 2014-11-13 15:06:22 -0800 | [diff] [blame] | 245 |  | 
| Ruben Brunk | 370e243 | 2014-10-14 18:33:23 -0700 | [diff] [blame] | 246 | return req, out_spec | 
|  | 247 |  | 
| Chien-Yu Chen | 0feea4a | 2014-10-20 11:04:11 -0700 | [diff] [blame] | 248 | def get_max_digital_zoom(props): | 
|  | 249 | """Returns the maximum amount of zooming possible by the camera device. | 
|  | 250 |  | 
|  | 251 | Args: | 
|  | 252 | props: the object returned from its.device.get_camera_properties(). | 
|  | 253 |  | 
|  | 254 | Return: | 
|  | 255 | A float indicating the maximum amount of zooming possible by the | 
|  | 256 | camera device. | 
|  | 257 | """ | 
|  | 258 |  | 
|  | 259 | maxz = 1.0 | 
|  | 260 |  | 
|  | 261 | if props.has_key("android.scaler.availableMaxDigitalZoom"): | 
|  | 262 | maxz = props["android.scaler.availableMaxDigitalZoom"] | 
|  | 263 |  | 
|  | 264 | return maxz | 
|  | 265 |  | 
|  | 266 |  | 
| Ruben Brunk | 370e243 | 2014-10-14 18:33:23 -0700 | [diff] [blame] | 267 | class __UnitTest(unittest.TestCase): | 
|  | 268 | """Run a suite of unit tests on this module. | 
|  | 269 | """ | 
|  | 270 |  | 
|  | 271 | def test_int_to_rational(self): | 
|  | 272 | """Unit test for int_to_rational. | 
|  | 273 | """ | 
|  | 274 | self.assertEqual(int_to_rational(10), | 
|  | 275 | {"numerator":10,"denominator":1}) | 
|  | 276 | self.assertEqual(int_to_rational([1,2]), | 
|  | 277 | [{"numerator":1,"denominator":1}, | 
|  | 278 | {"numerator":2,"denominator":1}]) | 
|  | 279 |  | 
|  | 280 | def test_float_to_rational(self): | 
|  | 281 | """Unit test for float_to_rational. | 
|  | 282 | """ | 
|  | 283 | self.assertEqual(float_to_rational(0.5001, 64), | 
|  | 284 | {"numerator":32, "denominator":64}) | 
|  | 285 |  | 
|  | 286 | def test_rational_to_float(self): | 
|  | 287 | """Unit test for rational_to_float. | 
|  | 288 | """ | 
|  | 289 | self.assertTrue( | 
|  | 290 | abs(rational_to_float({"numerator":32,"denominator":64})-0.5) | 
|  | 291 | < 0.0001) | 
|  | 292 |  | 
|  | 293 | if __name__ == '__main__': | 
|  | 294 | unittest.main() | 
|  | 295 |  |