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 its.error |
| 16 | import os |
| 17 | import os.path |
| 18 | import sys |
| 19 | import re |
| 20 | import json |
| 21 | import time |
| 22 | import unittest |
| 23 | import socket |
| 24 | import subprocess |
| 25 | import hashlib |
| 26 | import numpy |
Sam Lin | f2f6600 | 2017-02-27 21:08:11 -0800 | [diff] [blame] | 27 | import string |
Shuzhen Wang | 80822a9 | 2018-01-29 09:27:42 -0800 | [diff] [blame] | 28 | import unicodedata |
Ruben Brunk | 370e243 | 2014-10-14 18:33:23 -0700 | [diff] [blame] | 29 | |
Clemenz Portmann | a2815fa | 2017-05-23 08:31:19 -0700 | [diff] [blame] | 30 | |
Ruben Brunk | 370e243 | 2014-10-14 18:33:23 -0700 | [diff] [blame] | 31 | class ItsSession(object): |
| 32 | """Controls a device over adb to run ITS scripts. |
| 33 | |
| 34 | The script importing this module (on the host machine) prepares JSON |
| 35 | objects encoding CaptureRequests, specifying sets of parameters to use |
Chien-Yu Chen | 682faa2 | 2014-10-22 17:34:44 -0700 | [diff] [blame] | 36 | when capturing an image using the Camera2 APIs. This class encapsulates |
Ruben Brunk | 370e243 | 2014-10-14 18:33:23 -0700 | [diff] [blame] | 37 | sending the requests to the device, monitoring the device's progress, and |
| 38 | copying the resultant captures back to the host machine when done. TCP |
| 39 | forwarded over adb is the transport mechanism used. |
| 40 | |
| 41 | The device must have CtsVerifier.apk installed. |
| 42 | |
| 43 | Attributes: |
| 44 | sock: The open socket. |
| 45 | """ |
| 46 | |
Chien-Yu Chen | 1da2313 | 2015-07-22 15:24:41 -0700 | [diff] [blame] | 47 | # Open a connection to localhost:<host_port>, forwarded to port 6000 on the |
| 48 | # device. <host_port> is determined at run-time to support multiple |
| 49 | # connected devices. |
Ruben Brunk | 370e243 | 2014-10-14 18:33:23 -0700 | [diff] [blame] | 50 | IPADDR = '127.0.0.1' |
Chien-Yu Chen | 1da2313 | 2015-07-22 15:24:41 -0700 | [diff] [blame] | 51 | REMOTE_PORT = 6000 |
Ruben Brunk | 370e243 | 2014-10-14 18:33:23 -0700 | [diff] [blame] | 52 | BUFFER_SIZE = 4096 |
| 53 | |
Chien-Yu Chen | 1da2313 | 2015-07-22 15:24:41 -0700 | [diff] [blame] | 54 | # LOCK_PORT is used as a mutex lock to protect the list of forwarded ports |
| 55 | # among all processes. The script assumes LOCK_PORT is available and will |
| 56 | # try to use ports between CLIENT_PORT_START and |
| 57 | # CLIENT_PORT_START+MAX_NUM_PORTS-1 on host for ITS sessions. |
| 58 | CLIENT_PORT_START = 6000 |
| 59 | MAX_NUM_PORTS = 100 |
| 60 | LOCK_PORT = CLIENT_PORT_START + MAX_NUM_PORTS |
| 61 | |
Ruben Brunk | 370e243 | 2014-10-14 18:33:23 -0700 | [diff] [blame] | 62 | # Seconds timeout on each socket operation. |
Yin-Chia Yeh | ea1c1a6 | 2016-07-12 15:29:30 -0700 | [diff] [blame] | 63 | SOCK_TIMEOUT = 20.0 |
Yin-Chia Yeh | c9d554d | 2016-06-01 17:30:32 -0700 | [diff] [blame] | 64 | # Additional timeout in seconds when ITS service is doing more complicated |
| 65 | # operations, for example: issuing warmup requests before actual capture. |
| 66 | EXTRA_SOCK_TIMEOUT = 5.0 |
| 67 | |
Yin-Chia Yeh | 39f8f4b | 2015-07-17 12:29:31 -0700 | [diff] [blame] | 68 | SEC_TO_NSEC = 1000*1000*1000.0 |
Ruben Brunk | 370e243 | 2014-10-14 18:33:23 -0700 | [diff] [blame] | 69 | |
| 70 | PACKAGE = 'com.android.cts.verifier.camera.its' |
| 71 | INTENT_START = 'com.android.cts.verifier.camera.its.START' |
| 72 | ACTION_ITS_RESULT = 'com.android.cts.verifier.camera.its.ACTION_ITS_RESULT' |
Yin-Chia Yeh | 02eeffb | 2016-08-16 15:28:03 -0700 | [diff] [blame] | 73 | EXTRA_VERSION = 'camera.its.extra.VERSION' |
| 74 | CURRENT_ITS_VERSION = '1.0' # version number to sync with CtsVerifier |
Yin-Chia Yeh | ab98ada | 2015-03-05 13:28:53 -0800 | [diff] [blame] | 75 | EXTRA_CAMERA_ID = 'camera.its.extra.CAMERA_ID' |
Yin-Chia Yeh | 02eeffb | 2016-08-16 15:28:03 -0700 | [diff] [blame] | 76 | EXTRA_RESULTS = 'camera.its.extra.RESULTS' |
Sam Lin | f2f6600 | 2017-02-27 21:08:11 -0800 | [diff] [blame] | 77 | ITS_TEST_ACTIVITY = 'com.android.cts.verifier/.camera.its.ItsTestActivity' |
Yin-Chia Yeh | 02eeffb | 2016-08-16 15:28:03 -0700 | [diff] [blame] | 78 | |
| 79 | RESULT_PASS = 'PASS' |
| 80 | RESULT_FAIL = 'FAIL' |
| 81 | RESULT_NOT_EXECUTED = 'NOT_EXECUTED' |
| 82 | RESULT_VALUES = {RESULT_PASS, RESULT_FAIL, RESULT_NOT_EXECUTED} |
| 83 | RESULT_KEY = 'result' |
| 84 | SUMMARY_KEY = 'summary' |
Ruben Brunk | 370e243 | 2014-10-14 18:33:23 -0700 | [diff] [blame] | 85 | |
Chien-Yu Chen | 1da2313 | 2015-07-22 15:24:41 -0700 | [diff] [blame] | 86 | adb = "adb -d" |
| 87 | device_id = "" |
Ruben Brunk | 370e243 | 2014-10-14 18:33:23 -0700 | [diff] [blame] | 88 | |
| 89 | # Definitions for some of the common output format options for do_capture(). |
| 90 | # Each gets images of full resolution for each requested format. |
| 91 | CAP_RAW = {"format":"raw"} |
| 92 | CAP_DNG = {"format":"dng"} |
| 93 | CAP_YUV = {"format":"yuv"} |
| 94 | CAP_JPEG = {"format":"jpeg"} |
| 95 | CAP_RAW_YUV = [{"format":"raw"}, {"format":"yuv"}] |
| 96 | CAP_DNG_YUV = [{"format":"dng"}, {"format":"yuv"}] |
| 97 | CAP_RAW_JPEG = [{"format":"raw"}, {"format":"jpeg"}] |
| 98 | CAP_DNG_JPEG = [{"format":"dng"}, {"format":"jpeg"}] |
| 99 | CAP_YUV_JPEG = [{"format":"yuv"}, {"format":"jpeg"}] |
| 100 | CAP_RAW_YUV_JPEG = [{"format":"raw"}, {"format":"yuv"}, {"format":"jpeg"}] |
| 101 | CAP_DNG_YUV_JPEG = [{"format":"dng"}, {"format":"yuv"}, {"format":"jpeg"}] |
| 102 | |
Lu | 75f22fc | 2016-02-19 10:54:07 -0800 | [diff] [blame] | 103 | # Predefine camera props. Save props extracted from the function, |
| 104 | # "get_camera_properties". |
| 105 | props = None |
| 106 | |
Chien-Yu Chen | 1da2313 | 2015-07-22 15:24:41 -0700 | [diff] [blame] | 107 | # Initialize the socket port for the host to forward requests to the device. |
| 108 | # This method assumes localhost's LOCK_PORT is available and will try to |
| 109 | # use ports between CLIENT_PORT_START and CLIENT_PORT_START+MAX_NUM_PORTS-1 |
| 110 | def __init_socket_port(self): |
| 111 | NUM_RETRIES = 100 |
| 112 | RETRY_WAIT_TIME_SEC = 0.05 |
Ruben Brunk | 370e243 | 2014-10-14 18:33:23 -0700 | [diff] [blame] | 113 | |
Chien-Yu Chen | 1da2313 | 2015-07-22 15:24:41 -0700 | [diff] [blame] | 114 | # Bind a socket to use as mutex lock |
| 115 | socket_lock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) |
| 116 | for i in range(NUM_RETRIES): |
| 117 | try: |
| 118 | socket_lock.bind((ItsSession.IPADDR, ItsSession.LOCK_PORT)) |
| 119 | break |
Clemenz Portmann | b0e7615 | 2018-02-01 09:12:57 -0800 | [diff] [blame] | 120 | except socket.error or socket.timeout: |
Chien-Yu Chen | 1da2313 | 2015-07-22 15:24:41 -0700 | [diff] [blame] | 121 | if i == NUM_RETRIES - 1: |
| 122 | raise its.error.Error(self.device_id, |
Clemenz Portmann | b0e7615 | 2018-02-01 09:12:57 -0800 | [diff] [blame] | 123 | "socket lock returns error") |
Chien-Yu Chen | 1da2313 | 2015-07-22 15:24:41 -0700 | [diff] [blame] | 124 | else: |
| 125 | time.sleep(RETRY_WAIT_TIME_SEC) |
| 126 | |
| 127 | # Check if a port is already assigned to the device. |
| 128 | command = "adb forward --list" |
| 129 | proc = subprocess.Popen(command.split(), stdout=subprocess.PIPE) |
| 130 | output, error = proc.communicate() |
| 131 | |
| 132 | port = None |
| 133 | used_ports = [] |
| 134 | for line in output.split(os.linesep): |
| 135 | # each line should be formatted as: |
| 136 | # "<device_id> tcp:<host_port> tcp:<remote_port>" |
| 137 | forward_info = line.split() |
| 138 | if len(forward_info) >= 3 and \ |
| 139 | len(forward_info[1]) > 4 and forward_info[1][:4] == "tcp:" and \ |
| 140 | len(forward_info[2]) > 4 and forward_info[2][:4] == "tcp:": |
| 141 | local_p = int(forward_info[1][4:]) |
| 142 | remote_p = int(forward_info[2][4:]) |
| 143 | if forward_info[0] == self.device_id and \ |
| 144 | remote_p == ItsSession.REMOTE_PORT: |
| 145 | port = local_p |
| 146 | break; |
| 147 | else: |
| 148 | used_ports.append(local_p) |
| 149 | |
| 150 | # Find the first available port if no port is assigned to the device. |
| 151 | if port is None: |
| 152 | for p in range(ItsSession.CLIENT_PORT_START, |
| 153 | ItsSession.CLIENT_PORT_START + |
| 154 | ItsSession.MAX_NUM_PORTS): |
| 155 | if p not in used_ports: |
| 156 | # Try to run "adb forward" with the port |
| 157 | command = "%s forward tcp:%d tcp:%d" % \ |
| 158 | (self.adb, p, self.REMOTE_PORT) |
| 159 | proc = subprocess.Popen(command.split(), |
| 160 | stdout=subprocess.PIPE, |
| 161 | stderr=subprocess.PIPE) |
| 162 | output, error = proc.communicate() |
| 163 | |
| 164 | # Check if there is no error |
| 165 | if error is None or error.find("error") < 0: |
| 166 | port = p |
| 167 | break |
| 168 | |
| 169 | if port is None: |
| 170 | raise its.error.Error(self.device_id, " cannot find an available " + |
| 171 | "port") |
| 172 | |
| 173 | # Release the socket as mutex unlock |
| 174 | socket_lock.close() |
| 175 | |
| 176 | # Connect to the socket |
| 177 | self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) |
| 178 | self.sock.connect((self.IPADDR, port)) |
| 179 | self.sock.settimeout(self.SOCK_TIMEOUT) |
| 180 | |
| 181 | # Reboot the device if needed and wait for the service to be ready for |
| 182 | # connection. |
| 183 | def __wait_for_service(self): |
Ruben Brunk | 370e243 | 2014-10-14 18:33:23 -0700 | [diff] [blame] | 184 | # This also includes the optional reboot handling: if the user |
| 185 | # provides a "reboot" or "reboot=N" arg, then reboot the device, |
| 186 | # waiting for N seconds (default 30) before returning. |
| 187 | for s in sys.argv[1:]: |
| 188 | if s[:6] == "reboot": |
| 189 | duration = 30 |
| 190 | if len(s) > 7 and s[6] == "=": |
| 191 | duration = int(s[7:]) |
| 192 | print "Rebooting device" |
Lu | 75f22fc | 2016-02-19 10:54:07 -0800 | [diff] [blame] | 193 | _run("%s reboot" % (self.adb)) |
Chien-Yu Chen | 1da2313 | 2015-07-22 15:24:41 -0700 | [diff] [blame] | 194 | _run("%s wait-for-device" % (self.adb)) |
Ruben Brunk | 370e243 | 2014-10-14 18:33:23 -0700 | [diff] [blame] | 195 | time.sleep(duration) |
| 196 | print "Reboot complete" |
| 197 | |
Yin-Chia Yeh | 23a7b42 | 2016-03-25 14:48:47 -0700 | [diff] [blame] | 198 | # Flush logcat so following code won't be misled by previous |
| 199 | # 'ItsService ready' log. |
| 200 | _run('%s logcat -c' % (self.adb)) |
| 201 | time.sleep(1) |
| 202 | |
Ruben Brunk | 370e243 | 2014-10-14 18:33:23 -0700 | [diff] [blame] | 203 | # TODO: Figure out why "--user 0" is needed, and fix the problem. |
Chien-Yu Chen | 1da2313 | 2015-07-22 15:24:41 -0700 | [diff] [blame] | 204 | _run('%s shell am force-stop --user 0 %s' % (self.adb, self.PACKAGE)) |
Yin-Chia Yeh | 6bdae90 | 2018-01-02 15:17:13 -0800 | [diff] [blame] | 205 | _run(('%s shell am start-foreground-service --user 0 -t text/plain ' |
Chien-Yu Chen | 1da2313 | 2015-07-22 15:24:41 -0700 | [diff] [blame] | 206 | '-a %s') % (self.adb, self.INTENT_START)) |
Ruben Brunk | 370e243 | 2014-10-14 18:33:23 -0700 | [diff] [blame] | 207 | |
| 208 | # Wait until the socket is ready to accept a connection. |
| 209 | proc = subprocess.Popen( |
Chien-Yu Chen | 1da2313 | 2015-07-22 15:24:41 -0700 | [diff] [blame] | 210 | self.adb.split() + ["logcat"], |
Ruben Brunk | 370e243 | 2014-10-14 18:33:23 -0700 | [diff] [blame] | 211 | stdout=subprocess.PIPE) |
| 212 | logcat = proc.stdout |
| 213 | while True: |
| 214 | line = logcat.readline().strip() |
| 215 | if line.find('ItsService ready') >= 0: |
| 216 | break |
| 217 | proc.kill() |
| 218 | |
Clemenz Portmann | 9f852a0 | 2018-10-31 19:52:25 -0700 | [diff] [blame] | 219 | def __init__(self, camera_id=None): |
| 220 | self._camera_id = camera_id |
| 221 | |
Yin-Chia Yeh | 191cbf8 | 2018-05-08 10:33:33 -0700 | [diff] [blame] | 222 | def __enter__(self): |
Chien-Yu Chen | 1da2313 | 2015-07-22 15:24:41 -0700 | [diff] [blame] | 223 | # Initialize device id and adb command. |
| 224 | self.device_id = get_device_id() |
| 225 | self.adb = "adb -s " + self.device_id |
| 226 | |
| 227 | self.__wait_for_service() |
| 228 | self.__init_socket_port() |
| 229 | |
Ruben Brunk | 370e243 | 2014-10-14 18:33:23 -0700 | [diff] [blame] | 230 | self.__close_camera() |
Clemenz Portmann | 9f852a0 | 2018-10-31 19:52:25 -0700 | [diff] [blame] | 231 | self.__open_camera(self._camera_id) |
Ruben Brunk | 370e243 | 2014-10-14 18:33:23 -0700 | [diff] [blame] | 232 | return self |
| 233 | |
| 234 | def __exit__(self, type, value, traceback): |
Yin-Chia Yeh | 191cbf8 | 2018-05-08 10:33:33 -0700 | [diff] [blame] | 235 | if hasattr(self, 'sock') and self.sock: |
| 236 | self.__close_camera() |
| 237 | self.sock.close() |
Ruben Brunk | 370e243 | 2014-10-14 18:33:23 -0700 | [diff] [blame] | 238 | return False |
| 239 | |
| 240 | def __read_response_from_socket(self): |
| 241 | # Read a line (newline-terminated) string serialization of JSON object. |
| 242 | chars = [] |
| 243 | while len(chars) == 0 or chars[-1] != '\n': |
| 244 | ch = self.sock.recv(1) |
| 245 | if len(ch) == 0: |
| 246 | # Socket was probably closed; otherwise don't get empty strings |
| 247 | raise its.error.Error('Problem with socket on device side') |
| 248 | chars.append(ch) |
| 249 | line = ''.join(chars) |
| 250 | jobj = json.loads(line) |
| 251 | # Optionally read a binary buffer of a fixed size. |
| 252 | buf = None |
| 253 | if jobj.has_key("bufValueSize"): |
| 254 | n = jobj["bufValueSize"] |
| 255 | buf = bytearray(n) |
| 256 | view = memoryview(buf) |
| 257 | while n > 0: |
| 258 | nbytes = self.sock.recv_into(view, n) |
| 259 | view = view[nbytes:] |
| 260 | n -= nbytes |
| 261 | buf = numpy.frombuffer(buf, dtype=numpy.uint8) |
| 262 | return jobj, buf |
| 263 | |
Clemenz Portmann | 9f852a0 | 2018-10-31 19:52:25 -0700 | [diff] [blame] | 264 | def __open_camera(self, camera_id): |
| 265 | # Get the camera ID to open if it is an argument as a single camera. |
| 266 | # This allows passing camera=# to individual tests at command line |
| 267 | # and camera=#,#,# or an no camera argv with tools/run_all_tests.py. |
| 268 | if not camera_id: |
| 269 | camera_id = 0 |
| 270 | for s in sys.argv[1:]: |
| 271 | if s[:7] == "camera=" and len(s) > 7: |
| 272 | camera_ids = s[7:].split(",") |
| 273 | if len(camera_ids) == 1: |
| 274 | camera_id = camera_ids[0] |
Ruben Brunk | 370e243 | 2014-10-14 18:33:23 -0700 | [diff] [blame] | 275 | cmd = {"cmdName":"open", "cameraId":camera_id} |
| 276 | self.sock.send(json.dumps(cmd) + "\n") |
| 277 | data,_ = self.__read_response_from_socket() |
| 278 | if data['tag'] != 'cameraOpened': |
| 279 | raise its.error.Error('Invalid command response') |
| 280 | |
| 281 | def __close_camera(self): |
| 282 | cmd = {"cmdName":"close"} |
| 283 | self.sock.send(json.dumps(cmd) + "\n") |
| 284 | data,_ = self.__read_response_from_socket() |
| 285 | if data['tag'] != 'cameraClosed': |
| 286 | raise its.error.Error('Invalid command response') |
| 287 | |
| 288 | def do_vibrate(self, pattern): |
| 289 | """Cause the device to vibrate to a specific pattern. |
| 290 | |
| 291 | Args: |
| 292 | pattern: Durations (ms) for which to turn on or off the vibrator. |
| 293 | The first value indicates the number of milliseconds to wait |
| 294 | before turning the vibrator on. The next value indicates the |
| 295 | number of milliseconds for which to keep the vibrator on |
| 296 | before turning it off. Subsequent values alternate between |
| 297 | durations in milliseconds to turn the vibrator off or to turn |
| 298 | the vibrator on. |
| 299 | |
| 300 | Returns: |
| 301 | Nothing. |
| 302 | """ |
| 303 | cmd = {} |
| 304 | cmd["cmdName"] = "doVibrate" |
| 305 | cmd["pattern"] = pattern |
| 306 | self.sock.send(json.dumps(cmd) + "\n") |
| 307 | data,_ = self.__read_response_from_socket() |
| 308 | if data['tag'] != 'vibrationStarted': |
| 309 | raise its.error.Error('Invalid command response') |
| 310 | |
Daniel Hung-yu Wu | 130411c | 2018-03-30 15:05:18 +0800 | [diff] [blame] | 311 | def get_sensors(self): |
| 312 | """Get all sensors on the device. |
| 313 | |
| 314 | Returns: |
| 315 | A Python dictionary that returns keys and booleans for each sensor. |
| 316 | """ |
| 317 | cmd = {} |
| 318 | cmd["cmdName"] = "checkSensorExistence" |
| 319 | self.sock.send(json.dumps(cmd) + "\n") |
| 320 | data,_ = self.__read_response_from_socket() |
| 321 | if data['tag'] != 'sensorExistence': |
| 322 | raise its.error.Error('Invalid command response') |
| 323 | return data['objValue'] |
| 324 | |
Ruben Brunk | 370e243 | 2014-10-14 18:33:23 -0700 | [diff] [blame] | 325 | def start_sensor_events(self): |
| 326 | """Start collecting sensor events on the device. |
| 327 | |
| 328 | See get_sensor_events for more info. |
| 329 | |
| 330 | Returns: |
| 331 | Nothing. |
| 332 | """ |
| 333 | cmd = {} |
| 334 | cmd["cmdName"] = "startSensorEvents" |
| 335 | self.sock.send(json.dumps(cmd) + "\n") |
| 336 | data,_ = self.__read_response_from_socket() |
| 337 | if data['tag'] != 'sensorEventsStarted': |
| 338 | raise its.error.Error('Invalid command response') |
| 339 | |
| 340 | def get_sensor_events(self): |
| 341 | """Get a trace of all sensor events on the device. |
| 342 | |
| 343 | The trace starts when the start_sensor_events function is called. If |
| 344 | the test runs for a long time after this call, then the device's |
| 345 | internal memory can fill up. Calling get_sensor_events gets all events |
| 346 | from the device, and then stops the device from collecting events and |
| 347 | clears the internal buffer; to start again, the start_sensor_events |
| 348 | call must be used again. |
| 349 | |
| 350 | Events from the accelerometer, compass, and gyro are returned; each |
| 351 | has a timestamp and x,y,z values. |
| 352 | |
| 353 | Note that sensor events are only produced if the device isn't in its |
| 354 | standby mode (i.e.) if the screen is on. |
| 355 | |
| 356 | Returns: |
| 357 | A Python dictionary with three keys ("accel", "mag", "gyro") each |
| 358 | of which maps to a list of objects containing "time","x","y","z" |
| 359 | keys. |
| 360 | """ |
| 361 | cmd = {} |
| 362 | cmd["cmdName"] = "getSensorEvents" |
| 363 | self.sock.send(json.dumps(cmd) + "\n") |
Yin-Chia Yeh | f16d687 | 2016-06-06 13:54:42 -0700 | [diff] [blame] | 364 | timeout = self.SOCK_TIMEOUT + self.EXTRA_SOCK_TIMEOUT |
| 365 | self.sock.settimeout(timeout) |
Ruben Brunk | 370e243 | 2014-10-14 18:33:23 -0700 | [diff] [blame] | 366 | data,_ = self.__read_response_from_socket() |
| 367 | if data['tag'] != 'sensorEvents': |
| 368 | raise its.error.Error('Invalid command response') |
Yin-Chia Yeh | f16d687 | 2016-06-06 13:54:42 -0700 | [diff] [blame] | 369 | self.sock.settimeout(self.SOCK_TIMEOUT) |
Ruben Brunk | 370e243 | 2014-10-14 18:33:23 -0700 | [diff] [blame] | 370 | return data['objValue'] |
| 371 | |
Yin-Chia Yeh | ab98ada | 2015-03-05 13:28:53 -0800 | [diff] [blame] | 372 | def get_camera_ids(self): |
| 373 | """Get a list of camera device Ids that can be opened. |
| 374 | |
| 375 | Returns: |
| 376 | a list of camera ID string |
| 377 | """ |
| 378 | cmd = {} |
| 379 | cmd["cmdName"] = "getCameraIds" |
| 380 | self.sock.send(json.dumps(cmd) + "\n") |
| 381 | data,_ = self.__read_response_from_socket() |
| 382 | if data['tag'] != 'cameraIds': |
| 383 | raise its.error.Error('Invalid command response') |
| 384 | return data['objValue']['cameraIdArray'] |
| 385 | |
Ruben Brunk | 370e243 | 2014-10-14 18:33:23 -0700 | [diff] [blame] | 386 | def get_camera_properties(self): |
| 387 | """Get the camera properties object for the device. |
| 388 | |
| 389 | Returns: |
| 390 | The Python dictionary object for the CameraProperties object. |
| 391 | """ |
| 392 | cmd = {} |
| 393 | cmd["cmdName"] = "getCameraProperties" |
| 394 | self.sock.send(json.dumps(cmd) + "\n") |
| 395 | data,_ = self.__read_response_from_socket() |
| 396 | if data['tag'] != 'cameraProperties': |
| 397 | raise its.error.Error('Invalid command response') |
Lu | 75f22fc | 2016-02-19 10:54:07 -0800 | [diff] [blame] | 398 | self.props = data['objValue']['cameraProperties'] |
Ruben Brunk | 370e243 | 2014-10-14 18:33:23 -0700 | [diff] [blame] | 399 | return data['objValue']['cameraProperties'] |
| 400 | |
Yin-Chia Yeh | b1801f9 | 2018-02-14 16:08:12 -0800 | [diff] [blame] | 401 | def get_camera_properties_by_id(self, camera_id): |
| 402 | """Get the camera properties object for device with camera_id |
| 403 | |
| 404 | Args: |
| 405 | camera_id: The ID string of the camera |
| 406 | |
| 407 | Returns: |
| 408 | The Python dictionary object for the CameraProperties object. Empty |
| 409 | if no such device exists. |
| 410 | |
| 411 | """ |
| 412 | cmd = {} |
| 413 | cmd["cmdName"] = "getCameraPropertiesById" |
| 414 | cmd["cameraId"] = camera_id |
| 415 | self.sock.send(json.dumps(cmd) + "\n") |
| 416 | data,_ = self.__read_response_from_socket() |
| 417 | if data['tag'] != 'cameraProperties': |
| 418 | raise its.error.Error('Invalid command response') |
| 419 | return data['objValue']['cameraProperties'] |
| 420 | |
Ruben Brunk | 370e243 | 2014-10-14 18:33:23 -0700 | [diff] [blame] | 421 | def do_3a(self, regions_ae=[[0,0,1,1,1]], |
| 422 | regions_awb=[[0,0,1,1,1]], |
| 423 | regions_af=[[0,0,1,1,1]], |
| 424 | do_ae=True, do_awb=True, do_af=True, |
| 425 | lock_ae=False, lock_awb=False, |
Zhijun He | eb3ff47 | 2014-11-20 13:47:11 -0800 | [diff] [blame] | 426 | get_results=False, |
Clemenz Portmann | 4c86905 | 2018-03-27 13:31:09 -0700 | [diff] [blame] | 427 | ev_comp=0, mono_camera=False): |
Ruben Brunk | 370e243 | 2014-10-14 18:33:23 -0700 | [diff] [blame] | 428 | """Perform a 3A operation on the device. |
| 429 | |
| 430 | Triggers some or all of AE, AWB, and AF, and returns once they have |
| 431 | converged. Uses the vendor 3A that is implemented inside the HAL. |
Clemenz Portmann | 3c97e77 | 2017-03-23 13:22:06 -0700 | [diff] [blame] | 432 | Note: do_awb is always enabled regardless of do_awb flag |
Ruben Brunk | 370e243 | 2014-10-14 18:33:23 -0700 | [diff] [blame] | 433 | |
| 434 | Throws an assertion if 3A fails to converge. |
| 435 | |
| 436 | Args: |
| 437 | regions_ae: List of weighted AE regions. |
| 438 | regions_awb: List of weighted AWB regions. |
| 439 | regions_af: List of weighted AF regions. |
| 440 | do_ae: Trigger AE and wait for it to converge. |
| 441 | do_awb: Wait for AWB to converge. |
| 442 | do_af: Trigger AF and wait for it to converge. |
| 443 | lock_ae: Request AE lock after convergence, and wait for it. |
| 444 | lock_awb: Request AWB lock after convergence, and wait for it. |
| 445 | get_results: Return the 3A results from this function. |
Zhijun He | eb3ff47 | 2014-11-20 13:47:11 -0800 | [diff] [blame] | 446 | ev_comp: An EV compensation value to use when running AE. |
Clemenz Portmann | 4c86905 | 2018-03-27 13:31:09 -0700 | [diff] [blame] | 447 | mono_camera: Boolean for monochrome camera. |
Ruben Brunk | 370e243 | 2014-10-14 18:33:23 -0700 | [diff] [blame] | 448 | |
| 449 | Region format in args: |
| 450 | Arguments are lists of weighted regions; each weighted region is a |
| 451 | list of 5 values, [x,y,w,h, wgt], and each argument is a list of |
| 452 | these 5-value lists. The coordinates are given as normalized |
| 453 | rectangles (x,y,w,h) specifying the region. For example: |
| 454 | [[0.0, 0.0, 1.0, 0.5, 5], [0.0, 0.5, 1.0, 0.5, 10]]. |
| 455 | Weights are non-negative integers. |
| 456 | |
| 457 | Returns: |
| 458 | Five values are returned if get_results is true:: |
| 459 | * AE sensitivity; None if do_ae is False |
| 460 | * AE exposure time; None if do_ae is False |
Clemenz Portmann | 3c97e77 | 2017-03-23 13:22:06 -0700 | [diff] [blame] | 461 | * AWB gains (list); |
| 462 | * AWB transform (list); |
Ruben Brunk | 370e243 | 2014-10-14 18:33:23 -0700 | [diff] [blame] | 463 | * AF focus position; None if do_af is false |
| 464 | Otherwise, it returns five None values. |
| 465 | """ |
| 466 | print "Running vendor 3A on device" |
| 467 | cmd = {} |
| 468 | cmd["cmdName"] = "do3A" |
| 469 | cmd["regions"] = {"ae": sum(regions_ae, []), |
| 470 | "awb": sum(regions_awb, []), |
| 471 | "af": sum(regions_af, [])} |
| 472 | cmd["triggers"] = {"ae": do_ae, "af": do_af} |
| 473 | if lock_ae: |
| 474 | cmd["aeLock"] = True |
| 475 | if lock_awb: |
| 476 | cmd["awbLock"] = True |
Zhijun He | eb3ff47 | 2014-11-20 13:47:11 -0800 | [diff] [blame] | 477 | if ev_comp != 0: |
| 478 | cmd["evComp"] = ev_comp |
Ruben Brunk | 370e243 | 2014-10-14 18:33:23 -0700 | [diff] [blame] | 479 | self.sock.send(json.dumps(cmd) + "\n") |
| 480 | |
| 481 | # Wait for each specified 3A to converge. |
| 482 | ae_sens = None |
| 483 | ae_exp = None |
| 484 | awb_gains = None |
| 485 | awb_transform = None |
| 486 | af_dist = None |
| 487 | converged = False |
| 488 | while True: |
| 489 | data,_ = self.__read_response_from_socket() |
| 490 | vals = data['strValue'].split() |
| 491 | if data['tag'] == 'aeResult': |
Clemenz Portmann | 3c97e77 | 2017-03-23 13:22:06 -0700 | [diff] [blame] | 492 | if do_ae: |
| 493 | ae_sens, ae_exp = [int(i) for i in vals] |
Ruben Brunk | 370e243 | 2014-10-14 18:33:23 -0700 | [diff] [blame] | 494 | elif data['tag'] == 'afResult': |
Clemenz Portmann | 3c97e77 | 2017-03-23 13:22:06 -0700 | [diff] [blame] | 495 | if do_af: |
| 496 | af_dist = float(vals[0]) |
Ruben Brunk | 370e243 | 2014-10-14 18:33:23 -0700 | [diff] [blame] | 497 | elif data['tag'] == 'awbResult': |
| 498 | awb_gains = [float(f) for f in vals[:4]] |
| 499 | awb_transform = [float(f) for f in vals[4:]] |
| 500 | elif data['tag'] == '3aConverged': |
| 501 | converged = True |
| 502 | elif data['tag'] == '3aDone': |
| 503 | break |
| 504 | else: |
| 505 | raise its.error.Error('Invalid command response') |
| 506 | if converged and not get_results: |
| 507 | return None,None,None,None,None |
Clemenz Portmann | 4c86905 | 2018-03-27 13:31:09 -0700 | [diff] [blame] | 508 | if (do_ae and ae_sens == None or (not mono_camera and do_awb and awb_gains == None) |
Ruben Brunk | 370e243 | 2014-10-14 18:33:23 -0700 | [diff] [blame] | 509 | or do_af and af_dist == None or not converged): |
Clemenz Portmann | 4c86905 | 2018-03-27 13:31:09 -0700 | [diff] [blame] | 510 | |
Ruben Brunk | 370e243 | 2014-10-14 18:33:23 -0700 | [diff] [blame] | 511 | raise its.error.Error('3A failed to converge') |
| 512 | return ae_sens, ae_exp, awb_gains, awb_transform, af_dist |
| 513 | |
Yin-Chia Yeh | 9e5d9ac | 2016-03-30 12:04:12 -0700 | [diff] [blame] | 514 | def do_capture(self, cap_request, |
| 515 | out_surfaces=None, reprocess_format=None, repeat_request=None): |
Ruben Brunk | 370e243 | 2014-10-14 18:33:23 -0700 | [diff] [blame] | 516 | """Issue capture request(s), and read back the image(s) and metadata. |
| 517 | |
| 518 | The main top-level function for capturing one or more images using the |
| 519 | device. Captures a single image if cap_request is a single object, and |
| 520 | captures a burst if it is a list of objects. |
| 521 | |
Yin-Chia Yeh | 9e5d9ac | 2016-03-30 12:04:12 -0700 | [diff] [blame] | 522 | The optional repeat_request field can be used to assign a repeating |
| 523 | request list ran in background for 3 seconds to warm up the capturing |
| 524 | pipeline before start capturing. The repeat_requests will be ran on a |
| 525 | 640x480 YUV surface without sending any data back. The caller needs to |
| 526 | make sure the stream configuration defined by out_surfaces and |
| 527 | repeat_request are valid or do_capture may fail because device does not |
| 528 | support such stream configuration. |
| 529 | |
Ruben Brunk | 370e243 | 2014-10-14 18:33:23 -0700 | [diff] [blame] | 530 | The out_surfaces field can specify the width(s), height(s), and |
| 531 | format(s) of the captured image. The formats may be "yuv", "jpeg", |
Timothy Knight | 67d8ec9 | 2015-08-31 13:14:46 -0700 | [diff] [blame] | 532 | "dng", "raw", "raw10", "raw12", or "rawStats". The default is a YUV420 |
| 533 | frame ("yuv") corresponding to a full sensor frame. |
Ruben Brunk | 370e243 | 2014-10-14 18:33:23 -0700 | [diff] [blame] | 534 | |
Shuzhen Wang | 80822a9 | 2018-01-29 09:27:42 -0800 | [diff] [blame] | 535 | Optionally the out_surfaces field can specify physical camera id(s) if the |
| 536 | current camera device is a logical multi-camera. The physical camera id |
| 537 | must refer to a physical camera backing this logical camera device. And |
| 538 | only "yuv", "raw", "raw10", "raw12" support the physical camera id field. |
| 539 | |
| 540 | Currently only 2 physical streams with the same format are supported, one |
| 541 | from each physical camera: |
| 542 | - yuv physical streams of the same size. |
| 543 | - raw physical streams with the same or different sizes, depending on |
| 544 | device capability. (Different physical cameras may have different raw sizes). |
| 545 | |
Ruben Brunk | 370e243 | 2014-10-14 18:33:23 -0700 | [diff] [blame] | 546 | Note that one or more surfaces can be specified, allowing a capture to |
| 547 | request images back in multiple formats (e.g.) raw+yuv, raw+jpeg, |
| 548 | yuv+jpeg, raw+yuv+jpeg. If the size is omitted for a surface, the |
| 549 | default is the largest resolution available for the format of that |
| 550 | surface. At most one output surface can be specified for a given format, |
| 551 | and raw+dng, raw10+dng, and raw+raw10 are not supported as combinations. |
| 552 | |
Chien-Yu Chen | 4e1e2cc | 2015-06-08 17:46:52 -0700 | [diff] [blame] | 553 | If reprocess_format is not None, for each request, an intermediate |
| 554 | buffer of the given reprocess_format will be captured from camera and |
| 555 | the intermediate buffer will be reprocessed to the output surfaces. The |
| 556 | following settings will be turned off when capturing the intermediate |
| 557 | buffer and will be applied when reprocessing the intermediate buffer. |
| 558 | 1. android.noiseReduction.mode |
| 559 | 2. android.edge.mode |
| 560 | 3. android.reprocess.effectiveExposureFactor |
| 561 | |
| 562 | Supported reprocess format are "yuv" and "private". Supported output |
| 563 | surface formats when reprocessing is enabled are "yuv" and "jpeg". |
| 564 | |
Ruben Brunk | 370e243 | 2014-10-14 18:33:23 -0700 | [diff] [blame] | 565 | Example of a single capture request: |
| 566 | |
| 567 | { |
| 568 | "android.sensor.exposureTime": 100*1000*1000, |
| 569 | "android.sensor.sensitivity": 100 |
| 570 | } |
| 571 | |
| 572 | Example of a list of capture requests: |
| 573 | |
| 574 | [ |
| 575 | { |
| 576 | "android.sensor.exposureTime": 100*1000*1000, |
| 577 | "android.sensor.sensitivity": 100 |
| 578 | }, |
| 579 | { |
| 580 | "android.sensor.exposureTime": 100*1000*1000, |
| 581 | "android.sensor.sensitivity": 200 |
| 582 | } |
| 583 | ] |
| 584 | |
| 585 | Examples of output surface specifications: |
| 586 | |
| 587 | { |
| 588 | "width": 640, |
| 589 | "height": 480, |
| 590 | "format": "yuv" |
| 591 | } |
| 592 | |
| 593 | [ |
| 594 | { |
| 595 | "format": "jpeg" |
| 596 | }, |
| 597 | { |
| 598 | "format": "raw" |
| 599 | } |
| 600 | ] |
| 601 | |
| 602 | The following variables defined in this class are shortcuts for |
| 603 | specifying one or more formats where each output is the full size for |
| 604 | that format; they can be used as values for the out_surfaces arguments: |
| 605 | |
| 606 | CAP_RAW |
| 607 | CAP_DNG |
| 608 | CAP_YUV |
| 609 | CAP_JPEG |
| 610 | CAP_RAW_YUV |
| 611 | CAP_DNG_YUV |
| 612 | CAP_RAW_JPEG |
| 613 | CAP_DNG_JPEG |
| 614 | CAP_YUV_JPEG |
| 615 | CAP_RAW_YUV_JPEG |
| 616 | CAP_DNG_YUV_JPEG |
| 617 | |
Chien-Yu Chen | 682faa2 | 2014-10-22 17:34:44 -0700 | [diff] [blame] | 618 | If multiple formats are specified, then this function returns multiple |
Ruben Brunk | 370e243 | 2014-10-14 18:33:23 -0700 | [diff] [blame] | 619 | capture objects, one for each requested format. If multiple formats and |
| 620 | multiple captures (i.e. a burst) are specified, then this function |
| 621 | returns multiple lists of capture objects. In both cases, the order of |
| 622 | the returned objects matches the order of the requested formats in the |
| 623 | out_surfaces parameter. For example: |
| 624 | |
| 625 | yuv_cap = do_capture( req1 ) |
| 626 | yuv_cap = do_capture( req1, yuv_fmt ) |
| 627 | yuv_cap, raw_cap = do_capture( req1, [yuv_fmt,raw_fmt] ) |
| 628 | yuv_caps = do_capture( [req1,req2], yuv_fmt ) |
| 629 | yuv_caps, raw_caps = do_capture( [req1,req2], [yuv_fmt,raw_fmt] ) |
| 630 | |
Timothy Knight | 67d8ec9 | 2015-08-31 13:14:46 -0700 | [diff] [blame] | 631 | The "rawStats" format processes the raw image and returns a new image |
| 632 | of statistics from the raw image. The format takes additional keys, |
| 633 | "gridWidth" and "gridHeight" which are size of grid cells in a 2D grid |
| 634 | of the raw image. For each grid cell, the mean and variance of each raw |
| 635 | channel is computed, and the do_capture call returns two 4-element float |
| 636 | images of dimensions (rawWidth / gridWidth, rawHeight / gridHeight), |
| 637 | concatenated back-to-back, where the first iamge contains the 4-channel |
Timothy Knight | ff3032b | 2017-02-01 15:10:51 -0800 | [diff] [blame] | 638 | means and the second contains the 4-channel variances. Note that only |
| 639 | pixels in the active array crop region are used; pixels outside this |
| 640 | region (for example optical black rows) are cropped out before the |
| 641 | gridding and statistics computation is performed. |
Timothy Knight | 67d8ec9 | 2015-08-31 13:14:46 -0700 | [diff] [blame] | 642 | |
| 643 | For the rawStats format, if the gridWidth is not provided then the raw |
| 644 | image width is used as the default, and similarly for gridHeight. With |
| 645 | this, the following is an example of a output description that computes |
| 646 | the mean and variance across each image row: |
| 647 | |
| 648 | { |
| 649 | "gridHeight": 1, |
| 650 | "format": "rawStats" |
| 651 | } |
| 652 | |
Ruben Brunk | 370e243 | 2014-10-14 18:33:23 -0700 | [diff] [blame] | 653 | Args: |
| 654 | cap_request: The Python dict/list specifying the capture(s), which |
| 655 | will be converted to JSON and sent to the device. |
| 656 | out_surfaces: (Optional) specifications of the output image formats |
| 657 | and sizes to use for each capture. |
Chien-Yu Chen | 4e1e2cc | 2015-06-08 17:46:52 -0700 | [diff] [blame] | 658 | reprocess_format: (Optional) The reprocessing format. If not None, |
| 659 | reprocessing will be enabled. |
Ruben Brunk | 370e243 | 2014-10-14 18:33:23 -0700 | [diff] [blame] | 660 | |
| 661 | Returns: |
| 662 | An object, list of objects, or list of lists of objects, where each |
| 663 | object contains the following fields: |
| 664 | * data: the image data as a numpy array of bytes. |
| 665 | * width: the width of the captured image. |
| 666 | * height: the height of the captured image. |
Timothy Knight | 67d8ec9 | 2015-08-31 13:14:46 -0700 | [diff] [blame] | 667 | * format: image the format, in [ |
| 668 | "yuv","jpeg","raw","raw10","raw12","rawStats","dng"]. |
Chien-Yu Chen | 682faa2 | 2014-10-22 17:34:44 -0700 | [diff] [blame] | 669 | * metadata: the capture result object (Python dictionary). |
Ruben Brunk | 370e243 | 2014-10-14 18:33:23 -0700 | [diff] [blame] | 670 | """ |
| 671 | cmd = {} |
Chien-Yu Chen | 4e1e2cc | 2015-06-08 17:46:52 -0700 | [diff] [blame] | 672 | if reprocess_format != None: |
| 673 | cmd["cmdName"] = "doReprocessCapture" |
| 674 | cmd["reprocessFormat"] = reprocess_format |
| 675 | else: |
| 676 | cmd["cmdName"] = "doCapture" |
Yin-Chia Yeh | 9e5d9ac | 2016-03-30 12:04:12 -0700 | [diff] [blame] | 677 | |
| 678 | if repeat_request is not None and reprocess_format is not None: |
| 679 | raise its.error.Error('repeating request + reprocessing is not supported') |
| 680 | |
| 681 | if repeat_request is None: |
| 682 | cmd["repeatRequests"] = [] |
| 683 | elif not isinstance(repeat_request, list): |
| 684 | cmd["repeatRequests"] = [repeat_request] |
| 685 | else: |
| 686 | cmd["repeatRequests"] = repeat_request |
| 687 | |
Ruben Brunk | 370e243 | 2014-10-14 18:33:23 -0700 | [diff] [blame] | 688 | if not isinstance(cap_request, list): |
| 689 | cmd["captureRequests"] = [cap_request] |
| 690 | else: |
| 691 | cmd["captureRequests"] = cap_request |
| 692 | if out_surfaces is not None: |
| 693 | if not isinstance(out_surfaces, list): |
| 694 | cmd["outputSurfaces"] = [out_surfaces] |
| 695 | else: |
| 696 | cmd["outputSurfaces"] = out_surfaces |
Lu | 75f22fc | 2016-02-19 10:54:07 -0800 | [diff] [blame] | 697 | formats = [c["format"] if "format" in c else "yuv" |
Ruben Brunk | 370e243 | 2014-10-14 18:33:23 -0700 | [diff] [blame] | 698 | for c in cmd["outputSurfaces"]] |
| 699 | formats = [s if s != "jpg" else "jpeg" for s in formats] |
| 700 | else: |
Yin-Chia Yeh | 409f2e9 | 2016-03-16 18:55:31 -0700 | [diff] [blame] | 701 | max_yuv_size = its.objects.get_available_output_sizes( |
| 702 | "yuv", self.props)[0] |
Ruben Brunk | 370e243 | 2014-10-14 18:33:23 -0700 | [diff] [blame] | 703 | formats = ['yuv'] |
Yin-Chia Yeh | 409f2e9 | 2016-03-16 18:55:31 -0700 | [diff] [blame] | 704 | cmd["outputSurfaces"] = [{"format": "yuv", |
| 705 | "width" : max_yuv_size[0], |
| 706 | "height": max_yuv_size[1]}] |
Shuzhen Wang | 80822a9 | 2018-01-29 09:27:42 -0800 | [diff] [blame] | 707 | |
| 708 | # Figure out requested physical camera ids, physical and logical |
| 709 | # streams. |
| 710 | physical_cam_ids = {} |
| 711 | physical_buffers = {} |
| 712 | physical_cam_format = None |
| 713 | logical_cam_formats = [] |
| 714 | for i,s in enumerate(cmd["outputSurfaces"]): |
| 715 | if "format" in s and s["format"] in ["yuv", "raw", "raw10", "raw12"]: |
| 716 | if "physicalCamera" in s: |
| 717 | if physical_cam_format is not None and s["format"] != physical_cam_format: |
| 718 | raise its.error.Error('ITS does not support capturing multiple ' + |
| 719 | 'physical formats yet') |
| 720 | physical_cam_ids[i] = s["physicalCamera"] |
| 721 | physical_buffers[s["physicalCamera"]] = [] |
| 722 | physical_cam_format = s["format"] |
| 723 | else: |
| 724 | logical_cam_formats.append(s["format"]) |
| 725 | else: |
| 726 | logical_cam_formats.append(s["format"]) |
| 727 | |
Ruben Brunk | 370e243 | 2014-10-14 18:33:23 -0700 | [diff] [blame] | 728 | ncap = len(cmd["captureRequests"]) |
| 729 | nsurf = 1 if out_surfaces is None else len(cmd["outputSurfaces"]) |
Lu | 7c6f52e | 2015-12-15 14:42:18 -0800 | [diff] [blame] | 730 | # Only allow yuv output to multiple targets |
Shuzhen Wang | 80822a9 | 2018-01-29 09:27:42 -0800 | [diff] [blame] | 731 | logical_yuv_surfaces = [s for s in cmd["outputSurfaces"] if s["format"]=="yuv"\ |
| 732 | and "physicalCamera" not in s] |
| 733 | n_yuv = len(logical_yuv_surfaces) |
Lu | 7c6f52e | 2015-12-15 14:42:18 -0800 | [diff] [blame] | 734 | # Compute the buffer size of YUV targets |
Lu | 75f22fc | 2016-02-19 10:54:07 -0800 | [diff] [blame] | 735 | yuv_maxsize_1d = 0 |
Shuzhen Wang | 80822a9 | 2018-01-29 09:27:42 -0800 | [diff] [blame] | 736 | for s in logical_yuv_surfaces: |
Lu | 75f22fc | 2016-02-19 10:54:07 -0800 | [diff] [blame] | 737 | if not ("width" in s and "height" in s): |
| 738 | if self.props is None: |
| 739 | raise its.error.Error('Camera props are unavailable') |
| 740 | yuv_maxsize_2d = its.objects.get_available_output_sizes( |
| 741 | "yuv", self.props)[0] |
| 742 | yuv_maxsize_1d = yuv_maxsize_2d[0] * yuv_maxsize_2d[1] * 3 / 2 |
| 743 | break |
| 744 | yuv_sizes = [c["width"]*c["height"]*3/2 |
| 745 | if "width" in c and "height" in c |
| 746 | else yuv_maxsize_1d |
Shuzhen Wang | 80822a9 | 2018-01-29 09:27:42 -0800 | [diff] [blame] | 747 | for c in logical_yuv_surfaces] |
Lu | 7c6f52e | 2015-12-15 14:42:18 -0800 | [diff] [blame] | 748 | # Currently we don't pass enough metadta from ItsService to distinguish |
| 749 | # different yuv stream of same buffer size |
| 750 | if len(yuv_sizes) != len(set(yuv_sizes)): |
| 751 | raise its.error.Error( |
| 752 | 'ITS does not support yuv outputs of same buffer size') |
Shuzhen Wang | 80822a9 | 2018-01-29 09:27:42 -0800 | [diff] [blame] | 753 | if len(logical_cam_formats) > len(set(logical_cam_formats)): |
| 754 | if n_yuv != len(logical_cam_formats) - len(set(logical_cam_formats)) + 1: |
Lu | 7c6f52e | 2015-12-15 14:42:18 -0800 | [diff] [blame] | 755 | raise its.error.Error('Duplicate format requested') |
| 756 | |
Timothy Knight | 67d8ec9 | 2015-08-31 13:14:46 -0700 | [diff] [blame] | 757 | raw_formats = 0; |
| 758 | raw_formats += 1 if "dng" in formats else 0 |
| 759 | raw_formats += 1 if "raw" in formats else 0 |
| 760 | raw_formats += 1 if "raw10" in formats else 0 |
| 761 | raw_formats += 1 if "raw12" in formats else 0 |
| 762 | raw_formats += 1 if "rawStats" in formats else 0 |
| 763 | if raw_formats > 1: |
Ruben Brunk | 370e243 | 2014-10-14 18:33:23 -0700 | [diff] [blame] | 764 | raise its.error.Error('Different raw formats not supported') |
Yin-Chia Yeh | 39f8f4b | 2015-07-17 12:29:31 -0700 | [diff] [blame] | 765 | |
| 766 | # Detect long exposure time and set timeout accordingly |
| 767 | longest_exp_time = 0 |
| 768 | for req in cmd["captureRequests"]: |
| 769 | if "android.sensor.exposureTime" in req and \ |
| 770 | req["android.sensor.exposureTime"] > longest_exp_time: |
| 771 | longest_exp_time = req["android.sensor.exposureTime"] |
| 772 | |
| 773 | extended_timeout = longest_exp_time / self.SEC_TO_NSEC + \ |
| 774 | self.SOCK_TIMEOUT |
Yin-Chia Yeh | c9d554d | 2016-06-01 17:30:32 -0700 | [diff] [blame] | 775 | if repeat_request: |
| 776 | extended_timeout += self.EXTRA_SOCK_TIMEOUT |
Yin-Chia Yeh | 39f8f4b | 2015-07-17 12:29:31 -0700 | [diff] [blame] | 777 | self.sock.settimeout(extended_timeout) |
| 778 | |
Ruben Brunk | 370e243 | 2014-10-14 18:33:23 -0700 | [diff] [blame] | 779 | print "Capturing %d frame%s with %d format%s [%s]" % ( |
| 780 | ncap, "s" if ncap>1 else "", nsurf, "s" if nsurf>1 else "", |
| 781 | ",".join(formats)) |
| 782 | self.sock.send(json.dumps(cmd) + "\n") |
| 783 | |
| 784 | # Wait for ncap*nsurf images and ncap metadata responses. |
| 785 | # Assume that captures come out in the same order as requested in |
Chien-Yu Chen | 682faa2 | 2014-10-22 17:34:44 -0700 | [diff] [blame] | 786 | # the burst, however individual images of different formats can come |
Ruben Brunk | 370e243 | 2014-10-14 18:33:23 -0700 | [diff] [blame] | 787 | # out in any order for that capture. |
| 788 | nbufs = 0 |
Lu | 7c6f52e | 2015-12-15 14:42:18 -0800 | [diff] [blame] | 789 | bufs = {"raw":[], "raw10":[], "raw12":[], |
Timothy Knight | 67d8ec9 | 2015-08-31 13:14:46 -0700 | [diff] [blame] | 790 | "rawStats":[], "dng":[], "jpeg":[]} |
Lu | 7c6f52e | 2015-12-15 14:42:18 -0800 | [diff] [blame] | 791 | yuv_bufs = {size:[] for size in yuv_sizes} |
Ruben Brunk | 370e243 | 2014-10-14 18:33:23 -0700 | [diff] [blame] | 792 | mds = [] |
Shuzhen Wang | 96ecf75 | 2018-01-30 10:33:31 -0800 | [diff] [blame] | 793 | physical_mds = [] |
Ruben Brunk | 370e243 | 2014-10-14 18:33:23 -0700 | [diff] [blame] | 794 | widths = None |
| 795 | heights = None |
| 796 | while nbufs < ncap*nsurf or len(mds) < ncap: |
| 797 | jsonObj,buf = self.__read_response_from_socket() |
Lu | 7c6f52e | 2015-12-15 14:42:18 -0800 | [diff] [blame] | 798 | if jsonObj['tag'] in ['jpegImage', 'rawImage', \ |
Timothy Knight | 67d8ec9 | 2015-08-31 13:14:46 -0700 | [diff] [blame] | 799 | 'raw10Image', 'raw12Image', 'rawStatsImage', 'dngImage'] \ |
| 800 | and buf is not None: |
Ruben Brunk | 370e243 | 2014-10-14 18:33:23 -0700 | [diff] [blame] | 801 | fmt = jsonObj['tag'][:-5] |
| 802 | bufs[fmt].append(buf) |
| 803 | nbufs += 1 |
Lu | 7c6f52e | 2015-12-15 14:42:18 -0800 | [diff] [blame] | 804 | elif jsonObj['tag'] == 'yuvImage': |
| 805 | buf_size = numpy.product(buf.shape) |
| 806 | yuv_bufs[buf_size].append(buf) |
| 807 | nbufs += 1 |
Ruben Brunk | 370e243 | 2014-10-14 18:33:23 -0700 | [diff] [blame] | 808 | elif jsonObj['tag'] == 'captureResults': |
| 809 | mds.append(jsonObj['objValue']['captureResult']) |
Shuzhen Wang | 96ecf75 | 2018-01-30 10:33:31 -0800 | [diff] [blame] | 810 | physical_mds.append(jsonObj['objValue']['physicalResults']) |
Ruben Brunk | 370e243 | 2014-10-14 18:33:23 -0700 | [diff] [blame] | 811 | outputs = jsonObj['objValue']['outputs'] |
| 812 | widths = [out['width'] for out in outputs] |
| 813 | heights = [out['height'] for out in outputs] |
| 814 | else: |
Shuzhen Wang | 80822a9 | 2018-01-29 09:27:42 -0800 | [diff] [blame] | 815 | tagString = unicodedata.normalize('NFKD', jsonObj['tag']).encode('ascii', 'ignore'); |
| 816 | for x in ['rawImage', 'raw10Image', 'raw12Image', 'yuvImage']: |
| 817 | if (tagString.startswith(x)): |
| 818 | physicalId = jsonObj['tag'][len(x):]; |
| 819 | if physicalId in physical_cam_ids.values(): |
| 820 | physical_buffers[physicalId].append(buf) |
| 821 | nbufs += 1 |
Ruben Brunk | 370e243 | 2014-10-14 18:33:23 -0700 | [diff] [blame] | 822 | rets = [] |
| 823 | for j,fmt in enumerate(formats): |
| 824 | objs = [] |
| 825 | for i in range(ncap): |
| 826 | obj = {} |
Ruben Brunk | 370e243 | 2014-10-14 18:33:23 -0700 | [diff] [blame] | 827 | obj["width"] = widths[j] |
| 828 | obj["height"] = heights[j] |
| 829 | obj["format"] = fmt |
Shuzhen Wang | 96ecf75 | 2018-01-30 10:33:31 -0800 | [diff] [blame] | 830 | if j in physical_cam_ids: |
| 831 | for physical_md in physical_mds[i]: |
| 832 | if physical_cam_ids[j] in physical_md: |
| 833 | obj["metadata"] = physical_md[physical_cam_ids[j]] |
| 834 | break |
| 835 | else: |
| 836 | obj["metadata"] = mds[i] |
Shuzhen Wang | 80822a9 | 2018-01-29 09:27:42 -0800 | [diff] [blame] | 837 | |
| 838 | if j in physical_cam_ids: |
| 839 | obj["data"] = physical_buffers[physical_cam_ids[j]][i] |
| 840 | elif fmt == 'yuv': |
Lu | 7c6f52e | 2015-12-15 14:42:18 -0800 | [diff] [blame] | 841 | buf_size = widths[j] * heights[j] * 3 / 2 |
| 842 | obj["data"] = yuv_bufs[buf_size][i] |
| 843 | else: |
| 844 | obj["data"] = bufs[fmt][i] |
Ruben Brunk | 370e243 | 2014-10-14 18:33:23 -0700 | [diff] [blame] | 845 | objs.append(obj) |
| 846 | rets.append(objs if ncap>1 else objs[0]) |
Yin-Chia Yeh | 39f8f4b | 2015-07-17 12:29:31 -0700 | [diff] [blame] | 847 | self.sock.settimeout(self.SOCK_TIMEOUT) |
Ruben Brunk | 370e243 | 2014-10-14 18:33:23 -0700 | [diff] [blame] | 848 | return rets if len(rets)>1 else rets[0] |
| 849 | |
Chien-Yu Chen | 1da2313 | 2015-07-22 15:24:41 -0700 | [diff] [blame] | 850 | def get_device_id(): |
| 851 | """ Return the ID of the device that the test is running on. |
| 852 | |
| 853 | Return the device ID provided in the command line if it's connected. If no |
| 854 | device ID is provided in the command line and there is only one device |
| 855 | connected, return the device ID by parsing the result of "adb devices". |
Clemenz Portmann | f7f23ee | 2016-08-18 13:00:59 -0700 | [diff] [blame] | 856 | Also, if the environment variable ANDROID_SERIAL is set, use it as device |
| 857 | id. When both ANDROID_SERIAL and device argument present, device argument |
| 858 | takes priority. |
Chien-Yu Chen | 1da2313 | 2015-07-22 15:24:41 -0700 | [diff] [blame] | 859 | |
| 860 | Raise an exception if no device is connected; or the device ID provided in |
| 861 | the command line is not connected; or no device ID is provided in the |
Clemenz Portmann | f7f23ee | 2016-08-18 13:00:59 -0700 | [diff] [blame] | 862 | command line or environment variable and there are more than 1 device |
| 863 | connected. |
Chien-Yu Chen | 1da2313 | 2015-07-22 15:24:41 -0700 | [diff] [blame] | 864 | |
| 865 | Returns: |
| 866 | Device ID string. |
| 867 | """ |
| 868 | device_id = None |
Clemenz Portmann | f7f23ee | 2016-08-18 13:00:59 -0700 | [diff] [blame] | 869 | |
| 870 | # Check if device id is set in env |
| 871 | if "ANDROID_SERIAL" in os.environ: |
| 872 | device_id = os.environ["ANDROID_SERIAL"] |
| 873 | |
Chien-Yu Chen | 1da2313 | 2015-07-22 15:24:41 -0700 | [diff] [blame] | 874 | for s in sys.argv[1:]: |
| 875 | if s[:7] == "device=" and len(s) > 7: |
| 876 | device_id = str(s[7:]) |
| 877 | |
| 878 | # Get a list of connected devices |
| 879 | devices = [] |
| 880 | command = "adb devices" |
| 881 | proc = subprocess.Popen(command.split(), stdout=subprocess.PIPE) |
| 882 | output, error = proc.communicate() |
| 883 | for line in output.split(os.linesep): |
| 884 | device_info = line.split() |
| 885 | if len(device_info) == 2 and device_info[1] == "device": |
| 886 | devices.append(device_info[0]) |
| 887 | |
| 888 | if len(devices) == 0: |
| 889 | raise its.error.Error("No device is connected!") |
| 890 | elif device_id is not None and device_id not in devices: |
| 891 | raise its.error.Error(device_id + " is not connected!") |
| 892 | elif device_id is None and len(devices) >= 2: |
| 893 | raise its.error.Error("More than 1 device are connected. " + |
| 894 | "Use device=<device_id> to specify a device to test.") |
| 895 | elif len(devices) == 1: |
| 896 | device_id = devices[0] |
| 897 | |
| 898 | return device_id |
| 899 | |
Yin-Chia Yeh | 02eeffb | 2016-08-16 15:28:03 -0700 | [diff] [blame] | 900 | def report_result(device_id, camera_id, results): |
Timothy Knight | ed07600 | 2014-10-23 16:12:26 -0700 | [diff] [blame] | 901 | """Send a pass/fail result to the device, via an intent. |
| 902 | |
| 903 | Args: |
Chien-Yu Chen | 1da2313 | 2015-07-22 15:24:41 -0700 | [diff] [blame] | 904 | device_id: The ID string of the device to report the results to. |
Timothy Knight | ed07600 | 2014-10-23 16:12:26 -0700 | [diff] [blame] | 905 | camera_id: The ID string of the camera for which to report pass/fail. |
Yin-Chia Yeh | 02eeffb | 2016-08-16 15:28:03 -0700 | [diff] [blame] | 906 | results: a dictionary contains all ITS scenes as key and result/summary |
| 907 | of current ITS run. See test_report_result unit test for |
| 908 | an example. |
Timothy Knight | ed07600 | 2014-10-23 16:12:26 -0700 | [diff] [blame] | 909 | Returns: |
| 910 | Nothing. |
| 911 | """ |
Yin-Chia Yeh | 89dfdda | 2018-04-26 08:15:10 -0700 | [diff] [blame] | 912 | ACTIVITY_START_WAIT = 1.5 # seconds |
Chien-Yu Chen | 1da2313 | 2015-07-22 15:24:41 -0700 | [diff] [blame] | 913 | adb = "adb -s " + device_id |
Sam Lin | f2f6600 | 2017-02-27 21:08:11 -0800 | [diff] [blame] | 914 | |
Yin-Chia Yeh | 89dfdda | 2018-04-26 08:15:10 -0700 | [diff] [blame] | 915 | # Start ItsTestActivity to receive test results |
Sam Lin | f2f6600 | 2017-02-27 21:08:11 -0800 | [diff] [blame] | 916 | cmd = "%s shell am start %s --activity-brought-to-front" % (adb, ItsSession.ITS_TEST_ACTIVITY) |
| 917 | _run(cmd) |
Yin-Chia Yeh | 89dfdda | 2018-04-26 08:15:10 -0700 | [diff] [blame] | 918 | time.sleep(ACTIVITY_START_WAIT) |
Sam Lin | f2f6600 | 2017-02-27 21:08:11 -0800 | [diff] [blame] | 919 | |
Yin-Chia Yeh | 02eeffb | 2016-08-16 15:28:03 -0700 | [diff] [blame] | 920 | # Validate/process results argument |
| 921 | for scene in results: |
| 922 | result_key = ItsSession.RESULT_KEY |
| 923 | summary_key = ItsSession.SUMMARY_KEY |
| 924 | if result_key not in results[scene]: |
| 925 | raise its.error.Error('ITS result not found for ' + scene) |
| 926 | if results[scene][result_key] not in ItsSession.RESULT_VALUES: |
| 927 | raise its.error.Error('Unknown ITS result for %s: %s' % ( |
| 928 | scene, results[result_key])) |
| 929 | if summary_key in results[scene]: |
| 930 | device_summary_path = "/sdcard/its_camera%s_%s.txt" % ( |
| 931 | camera_id, scene) |
| 932 | _run("%s push %s %s" % ( |
| 933 | adb, results[scene][summary_key], device_summary_path)) |
| 934 | results[scene][summary_key] = device_summary_path |
Sam Lin | f2f6600 | 2017-02-27 21:08:11 -0800 | [diff] [blame] | 935 | |
Yin-Chia Yeh | 02eeffb | 2016-08-16 15:28:03 -0700 | [diff] [blame] | 936 | json_results = json.dumps(results) |
| 937 | cmd = "%s shell am broadcast -a %s --es %s %s --es %s %s --es %s \'%s\'" % ( |
| 938 | adb, ItsSession.ACTION_ITS_RESULT, |
| 939 | ItsSession.EXTRA_VERSION, ItsSession.CURRENT_ITS_VERSION, |
| 940 | ItsSession.EXTRA_CAMERA_ID, camera_id, |
| 941 | ItsSession.EXTRA_RESULTS, json_results) |
| 942 | if len(cmd) > 4095: |
| 943 | print "ITS command string might be too long! len:", len(cmd) |
| 944 | _run(cmd) |
Ruben Brunk | 370e243 | 2014-10-14 18:33:23 -0700 | [diff] [blame] | 945 | |
Yin-Chia Yeh | 89dfdda | 2018-04-26 08:15:10 -0700 | [diff] [blame] | 946 | def adb_log(device_id, msg): |
| 947 | """Send a log message to adb logcat |
| 948 | |
| 949 | Args: |
| 950 | device_id: The ID string of the adb device |
| 951 | msg: the message string to be send to logcat |
| 952 | |
| 953 | Returns: |
| 954 | Nothing. |
| 955 | """ |
| 956 | adb = "adb -s " + device_id |
| 957 | cmd = "%s shell log -p i -t \"ItsTestHost\" %s" % (adb, msg) |
| 958 | _run(cmd) |
| 959 | |
Sam Lin | f2f6600 | 2017-02-27 21:08:11 -0800 | [diff] [blame] | 960 | def get_device_fingerprint(device_id): |
| 961 | """ Return the Build FingerPrint of the device that the test is running on. |
| 962 | |
| 963 | Returns: |
| 964 | Device Build Fingerprint string. |
| 965 | """ |
| 966 | device_bfp = None |
| 967 | |
| 968 | # Get a list of connected devices |
| 969 | |
| 970 | com = ('adb -s %s shell getprop | grep ro.build.fingerprint' % device_id) |
| 971 | proc = subprocess.Popen(com.split(), stdout=subprocess.PIPE) |
| 972 | output, error = proc.communicate() |
| 973 | assert error is None |
| 974 | |
| 975 | lst = string.split( \ |
| 976 | string.replace( \ |
| 977 | string.replace( \ |
| 978 | string.replace(output, |
| 979 | '\n', ''), '[', ''), ']', ''), \ |
| 980 | ' ') |
| 981 | |
| 982 | if lst[0].find('ro.build.fingerprint') != -1: |
| 983 | device_bfp = lst[1] |
| 984 | |
| 985 | return device_bfp |
| 986 | |
Ruben Brunk | 370e243 | 2014-10-14 18:33:23 -0700 | [diff] [blame] | 987 | def _run(cmd): |
| 988 | """Replacement for os.system, with hiding of stdout+stderr messages. |
| 989 | """ |
| 990 | with open(os.devnull, 'wb') as devnull: |
| 991 | subprocess.check_call( |
| 992 | cmd.split(), stdout=devnull, stderr=subprocess.STDOUT) |
| 993 | |
| 994 | class __UnitTest(unittest.TestCase): |
| 995 | """Run a suite of unit tests on this module. |
| 996 | """ |
| 997 | |
Yin-Chia Yeh | 02eeffb | 2016-08-16 15:28:03 -0700 | [diff] [blame] | 998 | """ |
| 999 | # TODO: this test currently needs connected device to pass |
| 1000 | # Need to remove that dependency before enabling the test |
| 1001 | def test_report_result(self): |
| 1002 | device_id = get_device_id() |
| 1003 | camera_id = "1" |
| 1004 | result_key = ItsSession.RESULT_KEY |
| 1005 | results = {"scene0":{result_key:"PASS"}, |
| 1006 | "scene1":{result_key:"PASS"}, |
| 1007 | "scene2":{result_key:"PASS"}, |
| 1008 | "scene3":{result_key:"PASS"}, |
| 1009 | "sceneNotExist":{result_key:"FAIL"}} |
| 1010 | report_result(device_id, camera_id, results) |
| 1011 | """ |
Ruben Brunk | 370e243 | 2014-10-14 18:33:23 -0700 | [diff] [blame] | 1012 | |
| 1013 | if __name__ == '__main__': |
| 1014 | unittest.main() |
| 1015 | |