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