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