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