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