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:6000, forwarded to port 6000 on the device.
     45     # TODO: Support multiple devices running over different TCP ports.
     46     IPADDR = '127.0.0.1'
     47     PORT = 6000
     48     BUFFER_SIZE = 4096
     49 
     50     # Seconds timeout on each socket operation.
     51     SOCK_TIMEOUT = 10.0
     52 
     53     PACKAGE = 'com.android.cts.verifier.camera.its'
     54     INTENT_START = 'com.android.cts.verifier.camera.its.START'
     55     ACTION_ITS_RESULT = 'com.android.cts.verifier.camera.its.ACTION_ITS_RESULT'
     56     EXTRA_SUCCESS = 'camera.its.extra.SUCCESS'
     57 
     58     # TODO: Handle multiple connected devices.
     59     ADB = "adb -d"
     60 
     61     # Definitions for some of the common output format options for do_capture().
     62     # Each gets images of full resolution for each requested format.
     63     CAP_RAW = {"format":"raw"}
     64     CAP_DNG = {"format":"dng"}
     65     CAP_YUV = {"format":"yuv"}
     66     CAP_JPEG = {"format":"jpeg"}
     67     CAP_RAW_YUV = [{"format":"raw"}, {"format":"yuv"}]
     68     CAP_DNG_YUV = [{"format":"dng"}, {"format":"yuv"}]
     69     CAP_RAW_JPEG = [{"format":"raw"}, {"format":"jpeg"}]
     70     CAP_DNG_JPEG = [{"format":"dng"}, {"format":"jpeg"}]
     71     CAP_YUV_JPEG = [{"format":"yuv"}, {"format":"jpeg"}]
     72     CAP_RAW_YUV_JPEG = [{"format":"raw"}, {"format":"yuv"}, {"format":"jpeg"}]
     73     CAP_DNG_YUV_JPEG = [{"format":"dng"}, {"format":"yuv"}, {"format":"jpeg"}]
     74 
     75     # Method to handle the case where the service isn't already running.
     76     # This occurs when a test is invoked directly from the command line, rather
     77     # than as a part of a separate test harness which is setting up the device
     78     # and the TCP forwarding.
     79     def __pre_init(self):
     80 
     81         # This also includes the optional reboot handling: if the user
     82         # provides a "reboot" or "reboot=N" arg, then reboot the device,
     83         # waiting for N seconds (default 30) before returning.
     84         for s in sys.argv[1:]:
     85             if s[:6] == "reboot":
     86                 duration = 30
     87                 if len(s) > 7 and s[6] == "=":
     88                     duration = int(s[7:])
     89                 print "Rebooting device"
     90                 _run("%s reboot" % (ItsSession.ADB));
     91                 _run("%s wait-for-device" % (ItsSession.ADB))
     92                 time.sleep(duration)
     93                 print "Reboot complete"
     94 
     95         # TODO: Figure out why "--user 0" is needed, and fix the problem.
     96         _run('%s shell am force-stop --user 0 %s' % (ItsSession.ADB, self.PACKAGE))
     97         _run(('%s shell am startservice --user 0 -t text/plain '
     98               '-a %s') % (ItsSession.ADB, self.INTENT_START))
     99 
    100         # Wait until the socket is ready to accept a connection.
    101         proc = subprocess.Popen(
    102                 ItsSession.ADB.split() + ["logcat"],
    103                 stdout=subprocess.PIPE)
    104         logcat = proc.stdout
    105         while True:
    106             line = logcat.readline().strip()
    107             if line.find('ItsService ready') >= 0:
    108                 break
    109         proc.kill()
    110 
    111         # Setup the TCP-over-ADB forwarding.
    112         _run('%s forward tcp:%d tcp:%d' % (ItsSession.ADB,self.PORT,self.PORT))
    113 
    114     def __init__(self):
    115         if "noinit" not in sys.argv:
    116             self.__pre_init()
    117         self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    118         self.sock.connect((self.IPADDR, self.PORT))
    119         self.sock.settimeout(self.SOCK_TIMEOUT)
    120         self.__close_camera()
    121         self.__open_camera()
    122 
    123     def __del__(self):
    124         if hasattr(self, 'sock') and self.sock:
    125             self.__close_camera()
    126             self.sock.close()
    127 
    128     def __enter__(self):
    129         return self
    130 
    131     def __exit__(self, type, value, traceback):
    132         return False
    133 
    134     def __read_response_from_socket(self):
    135         # Read a line (newline-terminated) string serialization of JSON object.
    136         chars = []
    137         while len(chars) == 0 or chars[-1] != '\n':
    138             ch = self.sock.recv(1)
    139             if len(ch) == 0:
    140                 # Socket was probably closed; otherwise don't get empty strings
    141                 raise its.error.Error('Problem with socket on device side')
    142             chars.append(ch)
    143         line = ''.join(chars)
    144         jobj = json.loads(line)
    145         # Optionally read a binary buffer of a fixed size.
    146         buf = None
    147         if jobj.has_key("bufValueSize"):
    148             n = jobj["bufValueSize"]
    149             buf = bytearray(n)
    150             view = memoryview(buf)
    151             while n > 0:
    152                 nbytes = self.sock.recv_into(view, n)
    153                 view = view[nbytes:]
    154                 n -= nbytes
    155             buf = numpy.frombuffer(buf, dtype=numpy.uint8)
    156         return jobj, buf
    157 
    158     def __open_camera(self):
    159         # Get the camera ID to open as an argument.
    160         camera_id = 0
    161         for s in sys.argv[1:]:
    162             if s[:7] == "camera=" and len(s) > 7:
    163                 camera_id = int(s[7:])
    164         cmd = {"cmdName":"open", "cameraId":camera_id}
    165         self.sock.send(json.dumps(cmd) + "\n")
    166         data,_ = self.__read_response_from_socket()
    167         if data['tag'] != 'cameraOpened':
    168             raise its.error.Error('Invalid command response')
    169 
    170     def __close_camera(self):
    171         cmd = {"cmdName":"close"}
    172         self.sock.send(json.dumps(cmd) + "\n")
    173         data,_ = self.__read_response_from_socket()
    174         if data['tag'] != 'cameraClosed':
    175             raise its.error.Error('Invalid command response')
    176 
    177     def do_vibrate(self, pattern):
    178         """Cause the device to vibrate to a specific pattern.
    179 
    180         Args:
    181             pattern: Durations (ms) for which to turn on or off the vibrator.
    182                 The first value indicates the number of milliseconds to wait
    183                 before turning the vibrator on. The next value indicates the
    184                 number of milliseconds for which to keep the vibrator on
    185                 before turning it off. Subsequent values alternate between
    186                 durations in milliseconds to turn the vibrator off or to turn
    187                 the vibrator on.
    188 
    189         Returns:
    190             Nothing.
    191         """
    192         cmd = {}
    193         cmd["cmdName"] = "doVibrate"
    194         cmd["pattern"] = pattern
    195         self.sock.send(json.dumps(cmd) + "\n")
    196         data,_ = self.__read_response_from_socket()
    197         if data['tag'] != 'vibrationStarted':
    198             raise its.error.Error('Invalid command response')
    199 
    200     def start_sensor_events(self):
    201         """Start collecting sensor events on the device.
    202 
    203         See get_sensor_events for more info.
    204 
    205         Returns:
    206             Nothing.
    207         """
    208         cmd = {}
    209         cmd["cmdName"] = "startSensorEvents"
    210         self.sock.send(json.dumps(cmd) + "\n")
    211         data,_ = self.__read_response_from_socket()
    212         if data['tag'] != 'sensorEventsStarted':
    213             raise its.error.Error('Invalid command response')
    214 
    215     def get_sensor_events(self):
    216         """Get a trace of all sensor events on the device.
    217 
    218         The trace starts when the start_sensor_events function is called. If
    219         the test runs for a long time after this call, then the device's
    220         internal memory can fill up. Calling get_sensor_events gets all events
    221         from the device, and then stops the device from collecting events and
    222         clears the internal buffer; to start again, the start_sensor_events
    223         call must be used again.
    224 
    225         Events from the accelerometer, compass, and gyro are returned; each
    226         has a timestamp and x,y,z values.
    227 
    228         Note that sensor events are only produced if the device isn't in its
    229         standby mode (i.e.) if the screen is on.
    230 
    231         Returns:
    232             A Python dictionary with three keys ("accel", "mag", "gyro") each
    233             of which maps to a list of objects containing "time","x","y","z"
    234             keys.
    235         """
    236         cmd = {}
    237         cmd["cmdName"] = "getSensorEvents"
    238         self.sock.send(json.dumps(cmd) + "\n")
    239         data,_ = self.__read_response_from_socket()
    240         if data['tag'] != 'sensorEvents':
    241             raise its.error.Error('Invalid command response')
    242         return data['objValue']
    243 
    244     def get_camera_properties(self):
    245         """Get the camera properties object for the device.
    246 
    247         Returns:
    248             The Python dictionary object for the CameraProperties object.
    249         """
    250         cmd = {}
    251         cmd["cmdName"] = "getCameraProperties"
    252         self.sock.send(json.dumps(cmd) + "\n")
    253         data,_ = self.__read_response_from_socket()
    254         if data['tag'] != 'cameraProperties':
    255             raise its.error.Error('Invalid command response')
    256         return data['objValue']['cameraProperties']
    257 
    258     def do_3a(self, regions_ae=[[0,0,1,1,1]],
    259                     regions_awb=[[0,0,1,1,1]],
    260                     regions_af=[[0,0,1,1,1]],
    261                     do_ae=True, do_awb=True, do_af=True,
    262                     lock_ae=False, lock_awb=False,
    263                     get_results=False,
    264                     ev_comp=0):
    265         """Perform a 3A operation on the device.
    266 
    267         Triggers some or all of AE, AWB, and AF, and returns once they have
    268         converged. Uses the vendor 3A that is implemented inside the HAL.
    269 
    270         Throws an assertion if 3A fails to converge.
    271 
    272         Args:
    273             regions_ae: List of weighted AE regions.
    274             regions_awb: List of weighted AWB regions.
    275             regions_af: List of weighted AF regions.
    276             do_ae: Trigger AE and wait for it to converge.
    277             do_awb: Wait for AWB to converge.
    278             do_af: Trigger AF and wait for it to converge.
    279             lock_ae: Request AE lock after convergence, and wait for it.
    280             lock_awb: Request AWB lock after convergence, and wait for it.
    281             get_results: Return the 3A results from this function.
    282             ev_comp: An EV compensation value to use when running AE.
    283 
    284         Region format in args:
    285             Arguments are lists of weighted regions; each weighted region is a
    286             list of 5 values, [x,y,w,h, wgt], and each argument is a list of
    287             these 5-value lists. The coordinates are given as normalized
    288             rectangles (x,y,w,h) specifying the region. For example:
    289                 [[0.0, 0.0, 1.0, 0.5, 5], [0.0, 0.5, 1.0, 0.5, 10]].
    290             Weights are non-negative integers.
    291 
    292         Returns:
    293             Five values are returned if get_results is true::
    294             * AE sensitivity; None if do_ae is False
    295             * AE exposure time; None if do_ae is False
    296             * AWB gains (list); None if do_awb is False
    297             * AWB transform (list); None if do_awb is false
    298             * AF focus position; None if do_af is false
    299             Otherwise, it returns five None values.
    300         """
    301         print "Running vendor 3A on device"
    302         cmd = {}
    303         cmd["cmdName"] = "do3A"
    304         cmd["regions"] = {"ae": sum(regions_ae, []),
    305                           "awb": sum(regions_awb, []),
    306                           "af": sum(regions_af, [])}
    307         cmd["triggers"] = {"ae": do_ae, "af": do_af}
    308         if lock_ae:
    309             cmd["aeLock"] = True
    310         if lock_awb:
    311             cmd["awbLock"] = True
    312         if ev_comp != 0:
    313             cmd["evComp"] = ev_comp
    314         self.sock.send(json.dumps(cmd) + "\n")
    315 
    316         # Wait for each specified 3A to converge.
    317         ae_sens = None
    318         ae_exp = None
    319         awb_gains = None
    320         awb_transform = None
    321         af_dist = None
    322         converged = False
    323         while True:
    324             data,_ = self.__read_response_from_socket()
    325             vals = data['strValue'].split()
    326             if data['tag'] == 'aeResult':
    327                 ae_sens, ae_exp = [int(i) for i in vals]
    328             elif data['tag'] == 'afResult':
    329                 af_dist = float(vals[0])
    330             elif data['tag'] == 'awbResult':
    331                 awb_gains = [float(f) for f in vals[:4]]
    332                 awb_transform = [float(f) for f in vals[4:]]
    333             elif data['tag'] == '3aConverged':
    334                 converged = True
    335             elif data['tag'] == '3aDone':
    336                 break
    337             else:
    338                 raise its.error.Error('Invalid command response')
    339         if converged and not get_results:
    340             return None,None,None,None,None
    341         if (do_ae and ae_sens == None or do_awb and awb_gains == None
    342                 or do_af and af_dist == None or not converged):
    343             raise its.error.Error('3A failed to converge')
    344         return ae_sens, ae_exp, awb_gains, awb_transform, af_dist
    345 
    346     def do_capture(self, cap_request, out_surfaces=None):
    347         """Issue capture request(s), and read back the image(s) and metadata.
    348 
    349         The main top-level function for capturing one or more images using the
    350         device. Captures a single image if cap_request is a single object, and
    351         captures a burst if it is a list of objects.
    352 
    353         The out_surfaces field can specify the width(s), height(s), and
    354         format(s) of the captured image. The formats may be "yuv", "jpeg",
    355         "dng", "raw", or "raw10". The default is a YUV420 frame ("yuv")
    356         corresponding to a full sensor frame.
    357 
    358         Note that one or more surfaces can be specified, allowing a capture to
    359         request images back in multiple formats (e.g.) raw+yuv, raw+jpeg,
    360         yuv+jpeg, raw+yuv+jpeg. If the size is omitted for a surface, the
    361         default is the largest resolution available for the format of that
    362         surface. At most one output surface can be specified for a given format,
    363         and raw+dng, raw10+dng, and raw+raw10 are not supported as combinations.
    364 
    365         Example of a single capture request:
    366 
    367             {
    368                 "android.sensor.exposureTime": 100*1000*1000,
    369                 "android.sensor.sensitivity": 100
    370             }
    371 
    372         Example of a list of capture requests:
    373 
    374             [
    375                 {
    376                     "android.sensor.exposureTime": 100*1000*1000,
    377                     "android.sensor.sensitivity": 100
    378                 },
    379                 {
    380                     "android.sensor.exposureTime": 100*1000*1000,
    381                     "android.sensor.sensitivity": 200
    382                 }
    383             ]
    384 
    385         Examples of output surface specifications:
    386 
    387             {
    388                 "width": 640,
    389                 "height": 480,
    390                 "format": "yuv"
    391             }
    392 
    393             [
    394                 {
    395                     "format": "jpeg"
    396                 },
    397                 {
    398                     "format": "raw"
    399                 }
    400             ]
    401 
    402         The following variables defined in this class are shortcuts for
    403         specifying one or more formats where each output is the full size for
    404         that format; they can be used as values for the out_surfaces arguments:
    405 
    406             CAP_RAW
    407             CAP_DNG
    408             CAP_YUV
    409             CAP_JPEG
    410             CAP_RAW_YUV
    411             CAP_DNG_YUV
    412             CAP_RAW_JPEG
    413             CAP_DNG_JPEG
    414             CAP_YUV_JPEG
    415             CAP_RAW_YUV_JPEG
    416             CAP_DNG_YUV_JPEG
    417 
    418         If multiple formats are specified, then this function returns multiple
    419         capture objects, one for each requested format. If multiple formats and
    420         multiple captures (i.e. a burst) are specified, then this function
    421         returns multiple lists of capture objects. In both cases, the order of
    422         the returned objects matches the order of the requested formats in the
    423         out_surfaces parameter. For example:
    424 
    425             yuv_cap            = do_capture( req1                           )
    426             yuv_cap            = do_capture( req1,        yuv_fmt           )
    427             yuv_cap,  raw_cap  = do_capture( req1,        [yuv_fmt,raw_fmt] )
    428             yuv_caps           = do_capture( [req1,req2], yuv_fmt           )
    429             yuv_caps, raw_caps = do_capture( [req1,req2], [yuv_fmt,raw_fmt] )
    430 
    431         Args:
    432             cap_request: The Python dict/list specifying the capture(s), which
    433                 will be converted to JSON and sent to the device.
    434             out_surfaces: (Optional) specifications of the output image formats
    435                 and sizes to use for each capture.
    436 
    437         Returns:
    438             An object, list of objects, or list of lists of objects, where each
    439             object contains the following fields:
    440             * data: the image data as a numpy array of bytes.
    441             * width: the width of the captured image.
    442             * height: the height of the captured image.
    443             * format: image the format, in ["yuv","jpeg","raw","raw10","dng"].
    444             * metadata: the capture result object (Python dictionary).
    445         """
    446         cmd = {}
    447         cmd["cmdName"] = "doCapture"
    448         if not isinstance(cap_request, list):
    449             cmd["captureRequests"] = [cap_request]
    450         else:
    451             cmd["captureRequests"] = cap_request
    452         if out_surfaces is not None:
    453             if not isinstance(out_surfaces, list):
    454                 cmd["outputSurfaces"] = [out_surfaces]
    455             else:
    456                 cmd["outputSurfaces"] = out_surfaces
    457             formats = [c["format"] if c.has_key("format") else "yuv"
    458                        for c in cmd["outputSurfaces"]]
    459             formats = [s if s != "jpg" else "jpeg" for s in formats]
    460         else:
    461             formats = ['yuv']
    462         ncap = len(cmd["captureRequests"])
    463         nsurf = 1 if out_surfaces is None else len(cmd["outputSurfaces"])
    464         if len(formats) > len(set(formats)):
    465             raise its.error.Error('Duplicate format requested')
    466         if "dng" in formats and "raw" in formats or \
    467                 "dng" in formats and "raw10" in formats or \
    468                 "raw" in formats and "raw10" in formats:
    469             raise its.error.Error('Different raw formats not supported')
    470         print "Capturing %d frame%s with %d format%s [%s]" % (
    471                   ncap, "s" if ncap>1 else "", nsurf, "s" if nsurf>1 else "",
    472                   ",".join(formats))
    473         self.sock.send(json.dumps(cmd) + "\n")
    474 
    475         # Wait for ncap*nsurf images and ncap metadata responses.
    476         # Assume that captures come out in the same order as requested in
    477         # the burst, however individual images of different formats can come
    478         # out in any order for that capture.
    479         nbufs = 0
    480         bufs = {"yuv":[], "raw":[], "raw10":[], "dng":[], "jpeg":[]}
    481         mds = []
    482         widths = None
    483         heights = None
    484         while nbufs < ncap*nsurf or len(mds) < ncap:
    485             jsonObj,buf = self.__read_response_from_socket()
    486             if jsonObj['tag'] in ['jpegImage', 'yuvImage', 'rawImage', \
    487                     'raw10Image', 'dngImage'] and buf is not None:
    488                 fmt = jsonObj['tag'][:-5]
    489                 bufs[fmt].append(buf)
    490                 nbufs += 1
    491             elif jsonObj['tag'] == 'captureResults':
    492                 mds.append(jsonObj['objValue']['captureResult'])
    493                 outputs = jsonObj['objValue']['outputs']
    494                 widths = [out['width'] for out in outputs]
    495                 heights = [out['height'] for out in outputs]
    496             else:
    497                 # Just ignore other tags
    498                 None
    499         rets = []
    500         for j,fmt in enumerate(formats):
    501             objs = []
    502             for i in range(ncap):
    503                 obj = {}
    504                 obj["data"] = bufs[fmt][i]
    505                 obj["width"] = widths[j]
    506                 obj["height"] = heights[j]
    507                 obj["format"] = fmt
    508                 obj["metadata"] = mds[i]
    509                 objs.append(obj)
    510             rets.append(objs if ncap>1 else objs[0])
    511         return rets if len(rets)>1 else rets[0]
    512 
    513 def report_result(camera_id, success):
    514     """Send a pass/fail result to the device, via an intent.
    515 
    516     Args:
    517         camera_id: The ID string of the camera for which to report pass/fail.
    518         success: Boolean, indicating if the result was pass or fail.
    519 
    520     Returns:
    521         Nothing.
    522     """
    523     resultstr = "%s=%s" % (camera_id, 'True' if success else 'False')
    524     _run(('%s shell am broadcast '
    525           '-a %s --es %s %s') % (ItsSession.ADB, ItsSession.ACTION_ITS_RESULT,
    526           ItsSession.EXTRA_SUCCESS, resultstr))
    527 
    528 
    529 def _run(cmd):
    530     """Replacement for os.system, with hiding of stdout+stderr messages.
    531     """
    532     with open(os.devnull, 'wb') as devnull:
    533         subprocess.check_call(
    534                 cmd.split(), stdout=devnull, stderr=subprocess.STDOUT)
    535 
    536 class __UnitTest(unittest.TestCase):
    537     """Run a suite of unit tests on this module.
    538     """
    539 
    540     # TODO: Add some unit tests.
    541     None
    542 
    543 if __name__ == '__main__':
    544     unittest.main()
    545 
    546