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