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