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