blob: 741baeeb1ade321768d37de4e5317fd1ef27ff3e [file] [log] [blame]
Ruben Brunk370e2432014-10-14 18:33:23 -07001# Copyright 2013 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15import its.error
16import os
17import os.path
18import sys
19import re
20import json
21import time
22import unittest
23import socket
24import subprocess
25import hashlib
26import numpy
27
28class ItsSession(object):
29 """Controls a device over adb to run ITS scripts.
30
31 The script importing this module (on the host machine) prepares JSON
32 objects encoding CaptureRequests, specifying sets of parameters to use
Chien-Yu Chen682faa22014-10-22 17:34:44 -070033 when capturing an image using the Camera2 APIs. This class encapsulates
Ruben Brunk370e2432014-10-14 18:33:23 -070034 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
Chien-Yu Chen1da23132015-07-22 15:24:41 -070044 # 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.
Ruben Brunk370e2432014-10-14 18:33:23 -070047 IPADDR = '127.0.0.1'
Chien-Yu Chen1da23132015-07-22 15:24:41 -070048 REMOTE_PORT = 6000
Ruben Brunk370e2432014-10-14 18:33:23 -070049 BUFFER_SIZE = 4096
50
Chien-Yu Chen1da23132015-07-22 15:24:41 -070051 # 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
Ruben Brunk370e2432014-10-14 18:33:23 -070059 # Seconds timeout on each socket operation.
60 SOCK_TIMEOUT = 10.0
Yin-Chia Yeh39f8f4b2015-07-17 12:29:31 -070061 SEC_TO_NSEC = 1000*1000*1000.0
Ruben Brunk370e2432014-10-14 18:33:23 -070062
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'
Yin-Chia Yehab98ada2015-03-05 13:28:53 -080066 EXTRA_CAMERA_ID = 'camera.its.extra.CAMERA_ID'
Ruben Brunk370e2432014-10-14 18:33:23 -070067 EXTRA_SUCCESS = 'camera.its.extra.SUCCESS'
Yin-Chia Yehab98ada2015-03-05 13:28:53 -080068 EXTRA_SUMMARY = 'camera.its.extra.SUMMARY'
Ruben Brunk370e2432014-10-14 18:33:23 -070069
Chien-Yu Chen1da23132015-07-22 15:24:41 -070070 adb = "adb -d"
71 device_id = ""
Ruben Brunk370e2432014-10-14 18:33:23 -070072
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
Lu75f22fc2016-02-19 10:54:07 -080087 # Predefine camera props. Save props extracted from the function,
88 # "get_camera_properties".
89 props = None
90
Chien-Yu Chen1da23132015-07-22 15:24:41 -070091 # Initialize the socket port for the host to forward requests to the device.
92 # This method assumes localhost's LOCK_PORT is available and will try to
93 # use ports between CLIENT_PORT_START and CLIENT_PORT_START+MAX_NUM_PORTS-1
94 def __init_socket_port(self):
95 NUM_RETRIES = 100
96 RETRY_WAIT_TIME_SEC = 0.05
Ruben Brunk370e2432014-10-14 18:33:23 -070097
Chien-Yu Chen1da23132015-07-22 15:24:41 -070098 # Bind a socket to use as mutex lock
99 socket_lock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
100 for i in range(NUM_RETRIES):
101 try:
102 socket_lock.bind((ItsSession.IPADDR, ItsSession.LOCK_PORT))
103 break
104 except socket.error:
105 if i == NUM_RETRIES - 1:
106 raise its.error.Error(self.device_id,
107 "acquiring socket lock timed out")
108 else:
109 time.sleep(RETRY_WAIT_TIME_SEC)
110
111 # Check if a port is already assigned to the device.
112 command = "adb forward --list"
113 proc = subprocess.Popen(command.split(), stdout=subprocess.PIPE)
114 output, error = proc.communicate()
115
116 port = None
117 used_ports = []
118 for line in output.split(os.linesep):
119 # each line should be formatted as:
120 # "<device_id> tcp:<host_port> tcp:<remote_port>"
121 forward_info = line.split()
122 if len(forward_info) >= 3 and \
123 len(forward_info[1]) > 4 and forward_info[1][:4] == "tcp:" and \
124 len(forward_info[2]) > 4 and forward_info[2][:4] == "tcp:":
125 local_p = int(forward_info[1][4:])
126 remote_p = int(forward_info[2][4:])
127 if forward_info[0] == self.device_id and \
128 remote_p == ItsSession.REMOTE_PORT:
129 port = local_p
130 break;
131 else:
132 used_ports.append(local_p)
133
134 # Find the first available port if no port is assigned to the device.
135 if port is None:
136 for p in range(ItsSession.CLIENT_PORT_START,
137 ItsSession.CLIENT_PORT_START +
138 ItsSession.MAX_NUM_PORTS):
139 if p not in used_ports:
140 # Try to run "adb forward" with the port
141 command = "%s forward tcp:%d tcp:%d" % \
142 (self.adb, p, self.REMOTE_PORT)
143 proc = subprocess.Popen(command.split(),
144 stdout=subprocess.PIPE,
145 stderr=subprocess.PIPE)
146 output, error = proc.communicate()
147
148 # Check if there is no error
149 if error is None or error.find("error") < 0:
150 port = p
151 break
152
153 if port is None:
154 raise its.error.Error(self.device_id, " cannot find an available " +
155 "port")
156
157 # Release the socket as mutex unlock
158 socket_lock.close()
159
160 # Connect to the socket
161 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
162 self.sock.connect((self.IPADDR, port))
163 self.sock.settimeout(self.SOCK_TIMEOUT)
164
165 # Reboot the device if needed and wait for the service to be ready for
166 # connection.
167 def __wait_for_service(self):
Ruben Brunk370e2432014-10-14 18:33:23 -0700168 # This also includes the optional reboot handling: if the user
169 # provides a "reboot" or "reboot=N" arg, then reboot the device,
170 # waiting for N seconds (default 30) before returning.
171 for s in sys.argv[1:]:
172 if s[:6] == "reboot":
173 duration = 30
174 if len(s) > 7 and s[6] == "=":
175 duration = int(s[7:])
176 print "Rebooting device"
Lu75f22fc2016-02-19 10:54:07 -0800177 _run("%s reboot" % (self.adb))
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700178 _run("%s wait-for-device" % (self.adb))
Ruben Brunk370e2432014-10-14 18:33:23 -0700179 time.sleep(duration)
180 print "Reboot complete"
181
182 # TODO: Figure out why "--user 0" is needed, and fix the problem.
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700183 _run('%s shell am force-stop --user 0 %s' % (self.adb, self.PACKAGE))
Ruben Brunk370e2432014-10-14 18:33:23 -0700184 _run(('%s shell am startservice --user 0 -t text/plain '
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700185 '-a %s') % (self.adb, self.INTENT_START))
Ruben Brunk370e2432014-10-14 18:33:23 -0700186
187 # Wait until the socket is ready to accept a connection.
188 proc = subprocess.Popen(
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700189 self.adb.split() + ["logcat"],
Ruben Brunk370e2432014-10-14 18:33:23 -0700190 stdout=subprocess.PIPE)
191 logcat = proc.stdout
192 while True:
193 line = logcat.readline().strip()
194 if line.find('ItsService ready') >= 0:
195 break
196 proc.kill()
197
Ruben Brunk370e2432014-10-14 18:33:23 -0700198 def __init__(self):
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700199 # Initialize device id and adb command.
200 self.device_id = get_device_id()
201 self.adb = "adb -s " + self.device_id
202
203 self.__wait_for_service()
204 self.__init_socket_port()
205
Ruben Brunk370e2432014-10-14 18:33:23 -0700206 self.__close_camera()
207 self.__open_camera()
208
209 def __del__(self):
210 if hasattr(self, 'sock') and self.sock:
211 self.__close_camera()
212 self.sock.close()
213
214 def __enter__(self):
215 return self
216
217 def __exit__(self, type, value, traceback):
218 return False
219
220 def __read_response_from_socket(self):
221 # Read a line (newline-terminated) string serialization of JSON object.
222 chars = []
223 while len(chars) == 0 or chars[-1] != '\n':
224 ch = self.sock.recv(1)
225 if len(ch) == 0:
226 # Socket was probably closed; otherwise don't get empty strings
227 raise its.error.Error('Problem with socket on device side')
228 chars.append(ch)
229 line = ''.join(chars)
230 jobj = json.loads(line)
231 # Optionally read a binary buffer of a fixed size.
232 buf = None
233 if jobj.has_key("bufValueSize"):
234 n = jobj["bufValueSize"]
235 buf = bytearray(n)
236 view = memoryview(buf)
237 while n > 0:
238 nbytes = self.sock.recv_into(view, n)
239 view = view[nbytes:]
240 n -= nbytes
241 buf = numpy.frombuffer(buf, dtype=numpy.uint8)
242 return jobj, buf
243
244 def __open_camera(self):
245 # Get the camera ID to open as an argument.
246 camera_id = 0
247 for s in sys.argv[1:]:
248 if s[:7] == "camera=" and len(s) > 7:
249 camera_id = int(s[7:])
250 cmd = {"cmdName":"open", "cameraId":camera_id}
251 self.sock.send(json.dumps(cmd) + "\n")
252 data,_ = self.__read_response_from_socket()
253 if data['tag'] != 'cameraOpened':
254 raise its.error.Error('Invalid command response')
255
256 def __close_camera(self):
257 cmd = {"cmdName":"close"}
258 self.sock.send(json.dumps(cmd) + "\n")
259 data,_ = self.__read_response_from_socket()
260 if data['tag'] != 'cameraClosed':
261 raise its.error.Error('Invalid command response')
262
263 def do_vibrate(self, pattern):
264 """Cause the device to vibrate to a specific pattern.
265
266 Args:
267 pattern: Durations (ms) for which to turn on or off the vibrator.
268 The first value indicates the number of milliseconds to wait
269 before turning the vibrator on. The next value indicates the
270 number of milliseconds for which to keep the vibrator on
271 before turning it off. Subsequent values alternate between
272 durations in milliseconds to turn the vibrator off or to turn
273 the vibrator on.
274
275 Returns:
276 Nothing.
277 """
278 cmd = {}
279 cmd["cmdName"] = "doVibrate"
280 cmd["pattern"] = pattern
281 self.sock.send(json.dumps(cmd) + "\n")
282 data,_ = self.__read_response_from_socket()
283 if data['tag'] != 'vibrationStarted':
284 raise its.error.Error('Invalid command response')
285
286 def start_sensor_events(self):
287 """Start collecting sensor events on the device.
288
289 See get_sensor_events for more info.
290
291 Returns:
292 Nothing.
293 """
294 cmd = {}
295 cmd["cmdName"] = "startSensorEvents"
296 self.sock.send(json.dumps(cmd) + "\n")
297 data,_ = self.__read_response_from_socket()
298 if data['tag'] != 'sensorEventsStarted':
299 raise its.error.Error('Invalid command response')
300
301 def get_sensor_events(self):
302 """Get a trace of all sensor events on the device.
303
304 The trace starts when the start_sensor_events function is called. If
305 the test runs for a long time after this call, then the device's
306 internal memory can fill up. Calling get_sensor_events gets all events
307 from the device, and then stops the device from collecting events and
308 clears the internal buffer; to start again, the start_sensor_events
309 call must be used again.
310
311 Events from the accelerometer, compass, and gyro are returned; each
312 has a timestamp and x,y,z values.
313
314 Note that sensor events are only produced if the device isn't in its
315 standby mode (i.e.) if the screen is on.
316
317 Returns:
318 A Python dictionary with three keys ("accel", "mag", "gyro") each
319 of which maps to a list of objects containing "time","x","y","z"
320 keys.
321 """
322 cmd = {}
323 cmd["cmdName"] = "getSensorEvents"
324 self.sock.send(json.dumps(cmd) + "\n")
325 data,_ = self.__read_response_from_socket()
326 if data['tag'] != 'sensorEvents':
327 raise its.error.Error('Invalid command response')
328 return data['objValue']
329
Yin-Chia Yehab98ada2015-03-05 13:28:53 -0800330 def get_camera_ids(self):
331 """Get a list of camera device Ids that can be opened.
332
333 Returns:
334 a list of camera ID string
335 """
336 cmd = {}
337 cmd["cmdName"] = "getCameraIds"
338 self.sock.send(json.dumps(cmd) + "\n")
339 data,_ = self.__read_response_from_socket()
340 if data['tag'] != 'cameraIds':
341 raise its.error.Error('Invalid command response')
342 return data['objValue']['cameraIdArray']
343
Ruben Brunk370e2432014-10-14 18:33:23 -0700344 def get_camera_properties(self):
345 """Get the camera properties object for the device.
346
347 Returns:
348 The Python dictionary object for the CameraProperties object.
349 """
350 cmd = {}
351 cmd["cmdName"] = "getCameraProperties"
352 self.sock.send(json.dumps(cmd) + "\n")
353 data,_ = self.__read_response_from_socket()
354 if data['tag'] != 'cameraProperties':
355 raise its.error.Error('Invalid command response')
Lu75f22fc2016-02-19 10:54:07 -0800356 self.props = data['objValue']['cameraProperties']
Ruben Brunk370e2432014-10-14 18:33:23 -0700357 return data['objValue']['cameraProperties']
358
359 def do_3a(self, regions_ae=[[0,0,1,1,1]],
360 regions_awb=[[0,0,1,1,1]],
361 regions_af=[[0,0,1,1,1]],
362 do_ae=True, do_awb=True, do_af=True,
363 lock_ae=False, lock_awb=False,
Zhijun Heeb3ff472014-11-20 13:47:11 -0800364 get_results=False,
365 ev_comp=0):
Ruben Brunk370e2432014-10-14 18:33:23 -0700366 """Perform a 3A operation on the device.
367
368 Triggers some or all of AE, AWB, and AF, and returns once they have
369 converged. Uses the vendor 3A that is implemented inside the HAL.
370
371 Throws an assertion if 3A fails to converge.
372
373 Args:
374 regions_ae: List of weighted AE regions.
375 regions_awb: List of weighted AWB regions.
376 regions_af: List of weighted AF regions.
377 do_ae: Trigger AE and wait for it to converge.
378 do_awb: Wait for AWB to converge.
379 do_af: Trigger AF and wait for it to converge.
380 lock_ae: Request AE lock after convergence, and wait for it.
381 lock_awb: Request AWB lock after convergence, and wait for it.
382 get_results: Return the 3A results from this function.
Zhijun Heeb3ff472014-11-20 13:47:11 -0800383 ev_comp: An EV compensation value to use when running AE.
Ruben Brunk370e2432014-10-14 18:33:23 -0700384
385 Region format in args:
386 Arguments are lists of weighted regions; each weighted region is a
387 list of 5 values, [x,y,w,h, wgt], and each argument is a list of
388 these 5-value lists. The coordinates are given as normalized
389 rectangles (x,y,w,h) specifying the region. For example:
390 [[0.0, 0.0, 1.0, 0.5, 5], [0.0, 0.5, 1.0, 0.5, 10]].
391 Weights are non-negative integers.
392
393 Returns:
394 Five values are returned if get_results is true::
395 * AE sensitivity; None if do_ae is False
396 * AE exposure time; None if do_ae is False
397 * AWB gains (list); None if do_awb is False
398 * AWB transform (list); None if do_awb is false
399 * AF focus position; None if do_af is false
400 Otherwise, it returns five None values.
401 """
402 print "Running vendor 3A on device"
403 cmd = {}
404 cmd["cmdName"] = "do3A"
405 cmd["regions"] = {"ae": sum(regions_ae, []),
406 "awb": sum(regions_awb, []),
407 "af": sum(regions_af, [])}
408 cmd["triggers"] = {"ae": do_ae, "af": do_af}
409 if lock_ae:
410 cmd["aeLock"] = True
411 if lock_awb:
412 cmd["awbLock"] = True
Zhijun Heeb3ff472014-11-20 13:47:11 -0800413 if ev_comp != 0:
414 cmd["evComp"] = ev_comp
Ruben Brunk370e2432014-10-14 18:33:23 -0700415 self.sock.send(json.dumps(cmd) + "\n")
416
417 # Wait for each specified 3A to converge.
418 ae_sens = None
419 ae_exp = None
420 awb_gains = None
421 awb_transform = None
422 af_dist = None
423 converged = False
424 while True:
425 data,_ = self.__read_response_from_socket()
426 vals = data['strValue'].split()
427 if data['tag'] == 'aeResult':
428 ae_sens, ae_exp = [int(i) for i in vals]
429 elif data['tag'] == 'afResult':
430 af_dist = float(vals[0])
431 elif data['tag'] == 'awbResult':
432 awb_gains = [float(f) for f in vals[:4]]
433 awb_transform = [float(f) for f in vals[4:]]
434 elif data['tag'] == '3aConverged':
435 converged = True
436 elif data['tag'] == '3aDone':
437 break
438 else:
439 raise its.error.Error('Invalid command response')
440 if converged and not get_results:
441 return None,None,None,None,None
442 if (do_ae and ae_sens == None or do_awb and awb_gains == None
443 or do_af and af_dist == None or not converged):
444 raise its.error.Error('3A failed to converge')
445 return ae_sens, ae_exp, awb_gains, awb_transform, af_dist
446
Chien-Yu Chen4e1e2cc2015-06-08 17:46:52 -0700447 def do_capture(self, cap_request, out_surfaces=None, reprocess_format=None):
Ruben Brunk370e2432014-10-14 18:33:23 -0700448 """Issue capture request(s), and read back the image(s) and metadata.
449
450 The main top-level function for capturing one or more images using the
451 device. Captures a single image if cap_request is a single object, and
452 captures a burst if it is a list of objects.
453
454 The out_surfaces field can specify the width(s), height(s), and
455 format(s) of the captured image. The formats may be "yuv", "jpeg",
Timothy Knight67d8ec92015-08-31 13:14:46 -0700456 "dng", "raw", "raw10", "raw12", or "rawStats". The default is a YUV420
457 frame ("yuv") corresponding to a full sensor frame.
Ruben Brunk370e2432014-10-14 18:33:23 -0700458
459 Note that one or more surfaces can be specified, allowing a capture to
460 request images back in multiple formats (e.g.) raw+yuv, raw+jpeg,
461 yuv+jpeg, raw+yuv+jpeg. If the size is omitted for a surface, the
462 default is the largest resolution available for the format of that
463 surface. At most one output surface can be specified for a given format,
464 and raw+dng, raw10+dng, and raw+raw10 are not supported as combinations.
465
Chien-Yu Chen4e1e2cc2015-06-08 17:46:52 -0700466 If reprocess_format is not None, for each request, an intermediate
467 buffer of the given reprocess_format will be captured from camera and
468 the intermediate buffer will be reprocessed to the output surfaces. The
469 following settings will be turned off when capturing the intermediate
470 buffer and will be applied when reprocessing the intermediate buffer.
471 1. android.noiseReduction.mode
472 2. android.edge.mode
473 3. android.reprocess.effectiveExposureFactor
474
475 Supported reprocess format are "yuv" and "private". Supported output
476 surface formats when reprocessing is enabled are "yuv" and "jpeg".
477
Ruben Brunk370e2432014-10-14 18:33:23 -0700478 Example of a single capture request:
479
480 {
481 "android.sensor.exposureTime": 100*1000*1000,
482 "android.sensor.sensitivity": 100
483 }
484
485 Example of a list of capture requests:
486
487 [
488 {
489 "android.sensor.exposureTime": 100*1000*1000,
490 "android.sensor.sensitivity": 100
491 },
492 {
493 "android.sensor.exposureTime": 100*1000*1000,
494 "android.sensor.sensitivity": 200
495 }
496 ]
497
498 Examples of output surface specifications:
499
500 {
501 "width": 640,
502 "height": 480,
503 "format": "yuv"
504 }
505
506 [
507 {
508 "format": "jpeg"
509 },
510 {
511 "format": "raw"
512 }
513 ]
514
515 The following variables defined in this class are shortcuts for
516 specifying one or more formats where each output is the full size for
517 that format; they can be used as values for the out_surfaces arguments:
518
519 CAP_RAW
520 CAP_DNG
521 CAP_YUV
522 CAP_JPEG
523 CAP_RAW_YUV
524 CAP_DNG_YUV
525 CAP_RAW_JPEG
526 CAP_DNG_JPEG
527 CAP_YUV_JPEG
528 CAP_RAW_YUV_JPEG
529 CAP_DNG_YUV_JPEG
530
Chien-Yu Chen682faa22014-10-22 17:34:44 -0700531 If multiple formats are specified, then this function returns multiple
Ruben Brunk370e2432014-10-14 18:33:23 -0700532 capture objects, one for each requested format. If multiple formats and
533 multiple captures (i.e. a burst) are specified, then this function
534 returns multiple lists of capture objects. In both cases, the order of
535 the returned objects matches the order of the requested formats in the
536 out_surfaces parameter. For example:
537
538 yuv_cap = do_capture( req1 )
539 yuv_cap = do_capture( req1, yuv_fmt )
540 yuv_cap, raw_cap = do_capture( req1, [yuv_fmt,raw_fmt] )
541 yuv_caps = do_capture( [req1,req2], yuv_fmt )
542 yuv_caps, raw_caps = do_capture( [req1,req2], [yuv_fmt,raw_fmt] )
543
Timothy Knight67d8ec92015-08-31 13:14:46 -0700544 The "rawStats" format processes the raw image and returns a new image
545 of statistics from the raw image. The format takes additional keys,
546 "gridWidth" and "gridHeight" which are size of grid cells in a 2D grid
547 of the raw image. For each grid cell, the mean and variance of each raw
548 channel is computed, and the do_capture call returns two 4-element float
549 images of dimensions (rawWidth / gridWidth, rawHeight / gridHeight),
550 concatenated back-to-back, where the first iamge contains the 4-channel
551 means and the second contains the 4-channel variances.
552
553 For the rawStats format, if the gridWidth is not provided then the raw
554 image width is used as the default, and similarly for gridHeight. With
555 this, the following is an example of a output description that computes
556 the mean and variance across each image row:
557
558 {
559 "gridHeight": 1,
560 "format": "rawStats"
561 }
562
Ruben Brunk370e2432014-10-14 18:33:23 -0700563 Args:
564 cap_request: The Python dict/list specifying the capture(s), which
565 will be converted to JSON and sent to the device.
566 out_surfaces: (Optional) specifications of the output image formats
567 and sizes to use for each capture.
Chien-Yu Chen4e1e2cc2015-06-08 17:46:52 -0700568 reprocess_format: (Optional) The reprocessing format. If not None,
569 reprocessing will be enabled.
Ruben Brunk370e2432014-10-14 18:33:23 -0700570
571 Returns:
572 An object, list of objects, or list of lists of objects, where each
573 object contains the following fields:
574 * data: the image data as a numpy array of bytes.
575 * width: the width of the captured image.
576 * height: the height of the captured image.
Timothy Knight67d8ec92015-08-31 13:14:46 -0700577 * format: image the format, in [
578 "yuv","jpeg","raw","raw10","raw12","rawStats","dng"].
Chien-Yu Chen682faa22014-10-22 17:34:44 -0700579 * metadata: the capture result object (Python dictionary).
Ruben Brunk370e2432014-10-14 18:33:23 -0700580 """
581 cmd = {}
Chien-Yu Chen4e1e2cc2015-06-08 17:46:52 -0700582 if reprocess_format != None:
583 cmd["cmdName"] = "doReprocessCapture"
584 cmd["reprocessFormat"] = reprocess_format
585 else:
586 cmd["cmdName"] = "doCapture"
Ruben Brunk370e2432014-10-14 18:33:23 -0700587 if not isinstance(cap_request, list):
588 cmd["captureRequests"] = [cap_request]
589 else:
590 cmd["captureRequests"] = cap_request
591 if out_surfaces is not None:
592 if not isinstance(out_surfaces, list):
593 cmd["outputSurfaces"] = [out_surfaces]
594 else:
595 cmd["outputSurfaces"] = out_surfaces
Lu75f22fc2016-02-19 10:54:07 -0800596 formats = [c["format"] if "format" in c else "yuv"
Ruben Brunk370e2432014-10-14 18:33:23 -0700597 for c in cmd["outputSurfaces"]]
598 formats = [s if s != "jpg" else "jpeg" for s in formats]
599 else:
600 formats = ['yuv']
601 ncap = len(cmd["captureRequests"])
602 nsurf = 1 if out_surfaces is None else len(cmd["outputSurfaces"])
Lu7c6f52e2015-12-15 14:42:18 -0800603 # Only allow yuv output to multiple targets
604 yuv_surfaces = [s for s in cmd["outputSurfaces"] if s["format"]=="yuv"]
605 n_yuv = len(yuv_surfaces)
606 # Compute the buffer size of YUV targets
Lu75f22fc2016-02-19 10:54:07 -0800607 yuv_maxsize_1d = 0
608 for s in yuv_surfaces:
609 if not ("width" in s and "height" in s):
610 if self.props is None:
611 raise its.error.Error('Camera props are unavailable')
612 yuv_maxsize_2d = its.objects.get_available_output_sizes(
613 "yuv", self.props)[0]
614 yuv_maxsize_1d = yuv_maxsize_2d[0] * yuv_maxsize_2d[1] * 3 / 2
615 break
616 yuv_sizes = [c["width"]*c["height"]*3/2
617 if "width" in c and "height" in c
618 else yuv_maxsize_1d
619 for c in yuv_surfaces]
Lu7c6f52e2015-12-15 14:42:18 -0800620 # Currently we don't pass enough metadta from ItsService to distinguish
621 # different yuv stream of same buffer size
622 if len(yuv_sizes) != len(set(yuv_sizes)):
623 raise its.error.Error(
624 'ITS does not support yuv outputs of same buffer size')
Ruben Brunk370e2432014-10-14 18:33:23 -0700625 if len(formats) > len(set(formats)):
Lu7c6f52e2015-12-15 14:42:18 -0800626 if n_yuv != len(formats) - len(set(formats)) + 1:
627 raise its.error.Error('Duplicate format requested')
628
Timothy Knight67d8ec92015-08-31 13:14:46 -0700629 raw_formats = 0;
630 raw_formats += 1 if "dng" in formats else 0
631 raw_formats += 1 if "raw" in formats else 0
632 raw_formats += 1 if "raw10" in formats else 0
633 raw_formats += 1 if "raw12" in formats else 0
634 raw_formats += 1 if "rawStats" in formats else 0
635 if raw_formats > 1:
Ruben Brunk370e2432014-10-14 18:33:23 -0700636 raise its.error.Error('Different raw formats not supported')
Yin-Chia Yeh39f8f4b2015-07-17 12:29:31 -0700637
638 # Detect long exposure time and set timeout accordingly
639 longest_exp_time = 0
640 for req in cmd["captureRequests"]:
641 if "android.sensor.exposureTime" in req and \
642 req["android.sensor.exposureTime"] > longest_exp_time:
643 longest_exp_time = req["android.sensor.exposureTime"]
644
645 extended_timeout = longest_exp_time / self.SEC_TO_NSEC + \
646 self.SOCK_TIMEOUT
647 self.sock.settimeout(extended_timeout)
648
Ruben Brunk370e2432014-10-14 18:33:23 -0700649 print "Capturing %d frame%s with %d format%s [%s]" % (
650 ncap, "s" if ncap>1 else "", nsurf, "s" if nsurf>1 else "",
651 ",".join(formats))
652 self.sock.send(json.dumps(cmd) + "\n")
653
654 # Wait for ncap*nsurf images and ncap metadata responses.
655 # Assume that captures come out in the same order as requested in
Chien-Yu Chen682faa22014-10-22 17:34:44 -0700656 # the burst, however individual images of different formats can come
Ruben Brunk370e2432014-10-14 18:33:23 -0700657 # out in any order for that capture.
658 nbufs = 0
Lu7c6f52e2015-12-15 14:42:18 -0800659 bufs = {"raw":[], "raw10":[], "raw12":[],
Timothy Knight67d8ec92015-08-31 13:14:46 -0700660 "rawStats":[], "dng":[], "jpeg":[]}
Lu7c6f52e2015-12-15 14:42:18 -0800661 yuv_bufs = {size:[] for size in yuv_sizes}
Ruben Brunk370e2432014-10-14 18:33:23 -0700662 mds = []
663 widths = None
664 heights = None
665 while nbufs < ncap*nsurf or len(mds) < ncap:
666 jsonObj,buf = self.__read_response_from_socket()
Lu7c6f52e2015-12-15 14:42:18 -0800667 if jsonObj['tag'] in ['jpegImage', 'rawImage', \
Timothy Knight67d8ec92015-08-31 13:14:46 -0700668 'raw10Image', 'raw12Image', 'rawStatsImage', 'dngImage'] \
669 and buf is not None:
Ruben Brunk370e2432014-10-14 18:33:23 -0700670 fmt = jsonObj['tag'][:-5]
671 bufs[fmt].append(buf)
672 nbufs += 1
Lu7c6f52e2015-12-15 14:42:18 -0800673 elif jsonObj['tag'] == 'yuvImage':
674 buf_size = numpy.product(buf.shape)
675 yuv_bufs[buf_size].append(buf)
676 nbufs += 1
Ruben Brunk370e2432014-10-14 18:33:23 -0700677 elif jsonObj['tag'] == 'captureResults':
678 mds.append(jsonObj['objValue']['captureResult'])
679 outputs = jsonObj['objValue']['outputs']
680 widths = [out['width'] for out in outputs]
681 heights = [out['height'] for out in outputs]
682 else:
683 # Just ignore other tags
684 None
685 rets = []
686 for j,fmt in enumerate(formats):
687 objs = []
688 for i in range(ncap):
689 obj = {}
Ruben Brunk370e2432014-10-14 18:33:23 -0700690 obj["width"] = widths[j]
691 obj["height"] = heights[j]
692 obj["format"] = fmt
693 obj["metadata"] = mds[i]
Lu7c6f52e2015-12-15 14:42:18 -0800694 if fmt == 'yuv':
695 buf_size = widths[j] * heights[j] * 3 / 2
696 obj["data"] = yuv_bufs[buf_size][i]
697 else:
698 obj["data"] = bufs[fmt][i]
Ruben Brunk370e2432014-10-14 18:33:23 -0700699 objs.append(obj)
700 rets.append(objs if ncap>1 else objs[0])
Yin-Chia Yeh39f8f4b2015-07-17 12:29:31 -0700701 self.sock.settimeout(self.SOCK_TIMEOUT)
Ruben Brunk370e2432014-10-14 18:33:23 -0700702 return rets if len(rets)>1 else rets[0]
703
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700704def get_device_id():
705 """ Return the ID of the device that the test is running on.
706
707 Return the device ID provided in the command line if it's connected. If no
708 device ID is provided in the command line and there is only one device
709 connected, return the device ID by parsing the result of "adb devices".
710
711 Raise an exception if no device is connected; or the device ID provided in
712 the command line is not connected; or no device ID is provided in the
713 command line and there are more than 1 device connected.
714
715 Returns:
716 Device ID string.
717 """
718 device_id = None
719 for s in sys.argv[1:]:
720 if s[:7] == "device=" and len(s) > 7:
721 device_id = str(s[7:])
722
723 # Get a list of connected devices
724 devices = []
725 command = "adb devices"
726 proc = subprocess.Popen(command.split(), stdout=subprocess.PIPE)
727 output, error = proc.communicate()
728 for line in output.split(os.linesep):
729 device_info = line.split()
730 if len(device_info) == 2 and device_info[1] == "device":
731 devices.append(device_info[0])
732
733 if len(devices) == 0:
734 raise its.error.Error("No device is connected!")
735 elif device_id is not None and device_id not in devices:
736 raise its.error.Error(device_id + " is not connected!")
737 elif device_id is None and len(devices) >= 2:
738 raise its.error.Error("More than 1 device are connected. " +
739 "Use device=<device_id> to specify a device to test.")
740 elif len(devices) == 1:
741 device_id = devices[0]
742
743 return device_id
744
745def report_result(device_id, camera_id, success, summary_path=None):
Timothy Knighted076002014-10-23 16:12:26 -0700746 """Send a pass/fail result to the device, via an intent.
747
748 Args:
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700749 device_id: The ID string of the device to report the results to.
Timothy Knighted076002014-10-23 16:12:26 -0700750 camera_id: The ID string of the camera for which to report pass/fail.
751 success: Boolean, indicating if the result was pass or fail.
Yin-Chia Yehab98ada2015-03-05 13:28:53 -0800752 summary_path: (Optional) path to ITS summary file on host PC
Timothy Knighted076002014-10-23 16:12:26 -0700753
754 Returns:
755 Nothing.
756 """
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700757 adb = "adb -s " + device_id
Yin-Chia Yehab98ada2015-03-05 13:28:53 -0800758 device_summary_path = "/sdcard/camera_" + camera_id + "_its_summary.txt"
759 if summary_path is not None:
760 _run("%s push %s %s" % (
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700761 adb, summary_path, device_summary_path))
Yin-Chia Yehab98ada2015-03-05 13:28:53 -0800762 _run("%s shell am broadcast -a %s --es %s %s --es %s %s --es %s %s" % (
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700763 adb, ItsSession.ACTION_ITS_RESULT,
Yin-Chia Yehab98ada2015-03-05 13:28:53 -0800764 ItsSession.EXTRA_CAMERA_ID, camera_id,
765 ItsSession.EXTRA_SUCCESS, 'True' if success else 'False',
766 ItsSession.EXTRA_SUMMARY, device_summary_path))
767 else:
768 _run("%s shell am broadcast -a %s --es %s %s --es %s %s --es %s %s" % (
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700769 adb, ItsSession.ACTION_ITS_RESULT,
Yin-Chia Yehab98ada2015-03-05 13:28:53 -0800770 ItsSession.EXTRA_CAMERA_ID, camera_id,
771 ItsSession.EXTRA_SUCCESS, 'True' if success else 'False',
772 ItsSession.EXTRA_SUMMARY, "null"))
Ruben Brunk370e2432014-10-14 18:33:23 -0700773
774def _run(cmd):
775 """Replacement for os.system, with hiding of stdout+stderr messages.
776 """
777 with open(os.devnull, 'wb') as devnull:
778 subprocess.check_call(
779 cmd.split(), stdout=devnull, stderr=subprocess.STDOUT)
780
781class __UnitTest(unittest.TestCase):
782 """Run a suite of unit tests on this module.
783 """
784
785 # TODO: Add some unit tests.
786 None
787
788if __name__ == '__main__':
789 unittest.main()
790