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