blob: d084f8340679a9c85b35288a431a54ea101b727e [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
Sam Linf2f66002017-02-27 21:08:11 -080027import string
Ruben Brunk370e2432014-10-14 18:33:23 -070028
29class ItsSession(object):
30 """Controls a device over adb to run ITS scripts.
31
32 The script importing this module (on the host machine) prepares JSON
33 objects encoding CaptureRequests, specifying sets of parameters to use
Chien-Yu Chen682faa22014-10-22 17:34:44 -070034 when capturing an image using the Camera2 APIs. This class encapsulates
Ruben Brunk370e2432014-10-14 18:33:23 -070035 sending the requests to the device, monitoring the device's progress, and
36 copying the resultant captures back to the host machine when done. TCP
37 forwarded over adb is the transport mechanism used.
38
39 The device must have CtsVerifier.apk installed.
40
41 Attributes:
42 sock: The open socket.
43 """
44
Chien-Yu Chen1da23132015-07-22 15:24:41 -070045 # Open a connection to localhost:<host_port>, forwarded to port 6000 on the
46 # device. <host_port> is determined at run-time to support multiple
47 # connected devices.
Ruben Brunk370e2432014-10-14 18:33:23 -070048 IPADDR = '127.0.0.1'
Chien-Yu Chen1da23132015-07-22 15:24:41 -070049 REMOTE_PORT = 6000
Ruben Brunk370e2432014-10-14 18:33:23 -070050 BUFFER_SIZE = 4096
51
Chien-Yu Chen1da23132015-07-22 15:24:41 -070052 # LOCK_PORT is used as a mutex lock to protect the list of forwarded ports
53 # among all processes. The script assumes LOCK_PORT is available and will
54 # try to use ports between CLIENT_PORT_START and
55 # CLIENT_PORT_START+MAX_NUM_PORTS-1 on host for ITS sessions.
56 CLIENT_PORT_START = 6000
57 MAX_NUM_PORTS = 100
58 LOCK_PORT = CLIENT_PORT_START + MAX_NUM_PORTS
59
Ruben Brunk370e2432014-10-14 18:33:23 -070060 # Seconds timeout on each socket operation.
Yin-Chia Yehea1c1a62016-07-12 15:29:30 -070061 SOCK_TIMEOUT = 20.0
Yin-Chia Yehc9d554d2016-06-01 17:30:32 -070062 # Additional timeout in seconds when ITS service is doing more complicated
63 # operations, for example: issuing warmup requests before actual capture.
64 EXTRA_SOCK_TIMEOUT = 5.0
65
Yin-Chia Yeh39f8f4b2015-07-17 12:29:31 -070066 SEC_TO_NSEC = 1000*1000*1000.0
Ruben Brunk370e2432014-10-14 18:33:23 -070067
68 PACKAGE = 'com.android.cts.verifier.camera.its'
69 INTENT_START = 'com.android.cts.verifier.camera.its.START'
70 ACTION_ITS_RESULT = 'com.android.cts.verifier.camera.its.ACTION_ITS_RESULT'
Yin-Chia Yeh02eeffb2016-08-16 15:28:03 -070071 EXTRA_VERSION = 'camera.its.extra.VERSION'
72 CURRENT_ITS_VERSION = '1.0' # version number to sync with CtsVerifier
Yin-Chia Yehab98ada2015-03-05 13:28:53 -080073 EXTRA_CAMERA_ID = 'camera.its.extra.CAMERA_ID'
Yin-Chia Yeh02eeffb2016-08-16 15:28:03 -070074 EXTRA_RESULTS = 'camera.its.extra.RESULTS'
Sam Linf2f66002017-02-27 21:08:11 -080075 ITS_TEST_ACTIVITY = 'com.android.cts.verifier/.camera.its.ItsTestActivity'
Yin-Chia Yeh02eeffb2016-08-16 15:28:03 -070076
77 RESULT_PASS = 'PASS'
78 RESULT_FAIL = 'FAIL'
79 RESULT_NOT_EXECUTED = 'NOT_EXECUTED'
80 RESULT_VALUES = {RESULT_PASS, RESULT_FAIL, RESULT_NOT_EXECUTED}
81 RESULT_KEY = 'result'
82 SUMMARY_KEY = 'summary'
Ruben Brunk370e2432014-10-14 18:33:23 -070083
Chien-Yu Chen1da23132015-07-22 15:24:41 -070084 adb = "adb -d"
85 device_id = ""
Ruben Brunk370e2432014-10-14 18:33:23 -070086
87 # Definitions for some of the common output format options for do_capture().
88 # Each gets images of full resolution for each requested format.
89 CAP_RAW = {"format":"raw"}
90 CAP_DNG = {"format":"dng"}
91 CAP_YUV = {"format":"yuv"}
92 CAP_JPEG = {"format":"jpeg"}
93 CAP_RAW_YUV = [{"format":"raw"}, {"format":"yuv"}]
94 CAP_DNG_YUV = [{"format":"dng"}, {"format":"yuv"}]
95 CAP_RAW_JPEG = [{"format":"raw"}, {"format":"jpeg"}]
96 CAP_DNG_JPEG = [{"format":"dng"}, {"format":"jpeg"}]
97 CAP_YUV_JPEG = [{"format":"yuv"}, {"format":"jpeg"}]
98 CAP_RAW_YUV_JPEG = [{"format":"raw"}, {"format":"yuv"}, {"format":"jpeg"}]
99 CAP_DNG_YUV_JPEG = [{"format":"dng"}, {"format":"yuv"}, {"format":"jpeg"}]
100
Lu75f22fc2016-02-19 10:54:07 -0800101 # Predefine camera props. Save props extracted from the function,
102 # "get_camera_properties".
103 props = None
104
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700105 # Initialize the socket port for the host to forward requests to the device.
106 # This method assumes localhost's LOCK_PORT is available and will try to
107 # use ports between CLIENT_PORT_START and CLIENT_PORT_START+MAX_NUM_PORTS-1
108 def __init_socket_port(self):
109 NUM_RETRIES = 100
110 RETRY_WAIT_TIME_SEC = 0.05
Ruben Brunk370e2432014-10-14 18:33:23 -0700111
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700112 # Bind a socket to use as mutex lock
113 socket_lock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
114 for i in range(NUM_RETRIES):
115 try:
116 socket_lock.bind((ItsSession.IPADDR, ItsSession.LOCK_PORT))
117 break
118 except socket.error:
119 if i == NUM_RETRIES - 1:
120 raise its.error.Error(self.device_id,
121 "acquiring socket lock timed out")
122 else:
123 time.sleep(RETRY_WAIT_TIME_SEC)
124
125 # Check if a port is already assigned to the device.
126 command = "adb forward --list"
127 proc = subprocess.Popen(command.split(), stdout=subprocess.PIPE)
128 output, error = proc.communicate()
129
130 port = None
131 used_ports = []
132 for line in output.split(os.linesep):
133 # each line should be formatted as:
134 # "<device_id> tcp:<host_port> tcp:<remote_port>"
135 forward_info = line.split()
136 if len(forward_info) >= 3 and \
137 len(forward_info[1]) > 4 and forward_info[1][:4] == "tcp:" and \
138 len(forward_info[2]) > 4 and forward_info[2][:4] == "tcp:":
139 local_p = int(forward_info[1][4:])
140 remote_p = int(forward_info[2][4:])
141 if forward_info[0] == self.device_id and \
142 remote_p == ItsSession.REMOTE_PORT:
143 port = local_p
144 break;
145 else:
146 used_ports.append(local_p)
147
148 # Find the first available port if no port is assigned to the device.
149 if port is None:
150 for p in range(ItsSession.CLIENT_PORT_START,
151 ItsSession.CLIENT_PORT_START +
152 ItsSession.MAX_NUM_PORTS):
153 if p not in used_ports:
154 # Try to run "adb forward" with the port
155 command = "%s forward tcp:%d tcp:%d" % \
156 (self.adb, p, self.REMOTE_PORT)
157 proc = subprocess.Popen(command.split(),
158 stdout=subprocess.PIPE,
159 stderr=subprocess.PIPE)
160 output, error = proc.communicate()
161
162 # Check if there is no error
163 if error is None or error.find("error") < 0:
164 port = p
165 break
166
167 if port is None:
168 raise its.error.Error(self.device_id, " cannot find an available " +
169 "port")
170
171 # Release the socket as mutex unlock
172 socket_lock.close()
173
174 # Connect to the socket
175 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
176 self.sock.connect((self.IPADDR, port))
177 self.sock.settimeout(self.SOCK_TIMEOUT)
178
179 # Reboot the device if needed and wait for the service to be ready for
180 # connection.
181 def __wait_for_service(self):
Ruben Brunk370e2432014-10-14 18:33:23 -0700182 # This also includes the optional reboot handling: if the user
183 # provides a "reboot" or "reboot=N" arg, then reboot the device,
184 # waiting for N seconds (default 30) before returning.
185 for s in sys.argv[1:]:
186 if s[:6] == "reboot":
187 duration = 30
188 if len(s) > 7 and s[6] == "=":
189 duration = int(s[7:])
190 print "Rebooting device"
Lu75f22fc2016-02-19 10:54:07 -0800191 _run("%s reboot" % (self.adb))
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700192 _run("%s wait-for-device" % (self.adb))
Ruben Brunk370e2432014-10-14 18:33:23 -0700193 time.sleep(duration)
194 print "Reboot complete"
195
Yin-Chia Yeh23a7b422016-03-25 14:48:47 -0700196 # Flush logcat so following code won't be misled by previous
197 # 'ItsService ready' log.
198 _run('%s logcat -c' % (self.adb))
199 time.sleep(1)
200
Ruben Brunk370e2432014-10-14 18:33:23 -0700201 # TODO: Figure out why "--user 0" is needed, and fix the problem.
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700202 _run('%s shell am force-stop --user 0 %s' % (self.adb, self.PACKAGE))
Ruben Brunk370e2432014-10-14 18:33:23 -0700203 _run(('%s shell am startservice --user 0 -t text/plain '
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700204 '-a %s') % (self.adb, self.INTENT_START))
Ruben Brunk370e2432014-10-14 18:33:23 -0700205
206 # Wait until the socket is ready to accept a connection.
207 proc = subprocess.Popen(
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700208 self.adb.split() + ["logcat"],
Ruben Brunk370e2432014-10-14 18:33:23 -0700209 stdout=subprocess.PIPE)
210 logcat = proc.stdout
211 while True:
212 line = logcat.readline().strip()
213 if line.find('ItsService ready') >= 0:
214 break
215 proc.kill()
216
Ruben Brunk370e2432014-10-14 18:33:23 -0700217 def __init__(self):
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700218 # Initialize device id and adb command.
219 self.device_id = get_device_id()
220 self.adb = "adb -s " + self.device_id
221
222 self.__wait_for_service()
223 self.__init_socket_port()
224
Ruben Brunk370e2432014-10-14 18:33:23 -0700225 self.__close_camera()
226 self.__open_camera()
227
228 def __del__(self):
229 if hasattr(self, 'sock') and self.sock:
230 self.__close_camera()
231 self.sock.close()
232
233 def __enter__(self):
234 return self
235
236 def __exit__(self, type, value, traceback):
237 return False
238
239 def __read_response_from_socket(self):
240 # Read a line (newline-terminated) string serialization of JSON object.
241 chars = []
242 while len(chars) == 0 or chars[-1] != '\n':
243 ch = self.sock.recv(1)
244 if len(ch) == 0:
245 # Socket was probably closed; otherwise don't get empty strings
246 raise its.error.Error('Problem with socket on device side')
247 chars.append(ch)
248 line = ''.join(chars)
249 jobj = json.loads(line)
250 # Optionally read a binary buffer of a fixed size.
251 buf = None
252 if jobj.has_key("bufValueSize"):
253 n = jobj["bufValueSize"]
254 buf = bytearray(n)
255 view = memoryview(buf)
256 while n > 0:
257 nbytes = self.sock.recv_into(view, n)
258 view = view[nbytes:]
259 n -= nbytes
260 buf = numpy.frombuffer(buf, dtype=numpy.uint8)
261 return jobj, buf
262
263 def __open_camera(self):
264 # Get the camera ID to open as an argument.
265 camera_id = 0
266 for s in sys.argv[1:]:
267 if s[:7] == "camera=" and len(s) > 7:
268 camera_id = int(s[7:])
269 cmd = {"cmdName":"open", "cameraId":camera_id}
270 self.sock.send(json.dumps(cmd) + "\n")
271 data,_ = self.__read_response_from_socket()
272 if data['tag'] != 'cameraOpened':
273 raise its.error.Error('Invalid command response')
274
275 def __close_camera(self):
276 cmd = {"cmdName":"close"}
277 self.sock.send(json.dumps(cmd) + "\n")
278 data,_ = self.__read_response_from_socket()
279 if data['tag'] != 'cameraClosed':
280 raise its.error.Error('Invalid command response')
281
282 def do_vibrate(self, pattern):
283 """Cause the device to vibrate to a specific pattern.
284
285 Args:
286 pattern: Durations (ms) for which to turn on or off the vibrator.
287 The first value indicates the number of milliseconds to wait
288 before turning the vibrator on. The next value indicates the
289 number of milliseconds for which to keep the vibrator on
290 before turning it off. Subsequent values alternate between
291 durations in milliseconds to turn the vibrator off or to turn
292 the vibrator on.
293
294 Returns:
295 Nothing.
296 """
297 cmd = {}
298 cmd["cmdName"] = "doVibrate"
299 cmd["pattern"] = pattern
300 self.sock.send(json.dumps(cmd) + "\n")
301 data,_ = self.__read_response_from_socket()
302 if data['tag'] != 'vibrationStarted':
303 raise its.error.Error('Invalid command response')
304
305 def start_sensor_events(self):
306 """Start collecting sensor events on the device.
307
308 See get_sensor_events for more info.
309
310 Returns:
311 Nothing.
312 """
313 cmd = {}
314 cmd["cmdName"] = "startSensorEvents"
315 self.sock.send(json.dumps(cmd) + "\n")
316 data,_ = self.__read_response_from_socket()
317 if data['tag'] != 'sensorEventsStarted':
318 raise its.error.Error('Invalid command response')
319
320 def get_sensor_events(self):
321 """Get a trace of all sensor events on the device.
322
323 The trace starts when the start_sensor_events function is called. If
324 the test runs for a long time after this call, then the device's
325 internal memory can fill up. Calling get_sensor_events gets all events
326 from the device, and then stops the device from collecting events and
327 clears the internal buffer; to start again, the start_sensor_events
328 call must be used again.
329
330 Events from the accelerometer, compass, and gyro are returned; each
331 has a timestamp and x,y,z values.
332
333 Note that sensor events are only produced if the device isn't in its
334 standby mode (i.e.) if the screen is on.
335
336 Returns:
337 A Python dictionary with three keys ("accel", "mag", "gyro") each
338 of which maps to a list of objects containing "time","x","y","z"
339 keys.
340 """
341 cmd = {}
342 cmd["cmdName"] = "getSensorEvents"
343 self.sock.send(json.dumps(cmd) + "\n")
Yin-Chia Yehf16d6872016-06-06 13:54:42 -0700344 timeout = self.SOCK_TIMEOUT + self.EXTRA_SOCK_TIMEOUT
345 self.sock.settimeout(timeout)
Ruben Brunk370e2432014-10-14 18:33:23 -0700346 data,_ = self.__read_response_from_socket()
347 if data['tag'] != 'sensorEvents':
348 raise its.error.Error('Invalid command response')
Yin-Chia Yehf16d6872016-06-06 13:54:42 -0700349 self.sock.settimeout(self.SOCK_TIMEOUT)
Ruben Brunk370e2432014-10-14 18:33:23 -0700350 return data['objValue']
351
Yin-Chia Yehab98ada2015-03-05 13:28:53 -0800352 def get_camera_ids(self):
353 """Get a list of camera device Ids that can be opened.
354
355 Returns:
356 a list of camera ID string
357 """
358 cmd = {}
359 cmd["cmdName"] = "getCameraIds"
360 self.sock.send(json.dumps(cmd) + "\n")
361 data,_ = self.__read_response_from_socket()
362 if data['tag'] != 'cameraIds':
363 raise its.error.Error('Invalid command response')
364 return data['objValue']['cameraIdArray']
365
Ruben Brunk370e2432014-10-14 18:33:23 -0700366 def get_camera_properties(self):
367 """Get the camera properties object for the device.
368
369 Returns:
370 The Python dictionary object for the CameraProperties object.
371 """
372 cmd = {}
373 cmd["cmdName"] = "getCameraProperties"
374 self.sock.send(json.dumps(cmd) + "\n")
375 data,_ = self.__read_response_from_socket()
376 if data['tag'] != 'cameraProperties':
377 raise its.error.Error('Invalid command response')
Lu75f22fc2016-02-19 10:54:07 -0800378 self.props = data['objValue']['cameraProperties']
Ruben Brunk370e2432014-10-14 18:33:23 -0700379 return data['objValue']['cameraProperties']
380
381 def do_3a(self, regions_ae=[[0,0,1,1,1]],
382 regions_awb=[[0,0,1,1,1]],
383 regions_af=[[0,0,1,1,1]],
384 do_ae=True, do_awb=True, do_af=True,
385 lock_ae=False, lock_awb=False,
Zhijun Heeb3ff472014-11-20 13:47:11 -0800386 get_results=False,
387 ev_comp=0):
Ruben Brunk370e2432014-10-14 18:33:23 -0700388 """Perform a 3A operation on the device.
389
390 Triggers some or all of AE, AWB, and AF, and returns once they have
391 converged. Uses the vendor 3A that is implemented inside the HAL.
392
393 Throws an assertion if 3A fails to converge.
394
395 Args:
396 regions_ae: List of weighted AE regions.
397 regions_awb: List of weighted AWB regions.
398 regions_af: List of weighted AF regions.
399 do_ae: Trigger AE and wait for it to converge.
400 do_awb: Wait for AWB to converge.
401 do_af: Trigger AF and wait for it to converge.
402 lock_ae: Request AE lock after convergence, and wait for it.
403 lock_awb: Request AWB lock after convergence, and wait for it.
404 get_results: Return the 3A results from this function.
Zhijun Heeb3ff472014-11-20 13:47:11 -0800405 ev_comp: An EV compensation value to use when running AE.
Ruben Brunk370e2432014-10-14 18:33:23 -0700406
407 Region format in args:
408 Arguments are lists of weighted regions; each weighted region is a
409 list of 5 values, [x,y,w,h, wgt], and each argument is a list of
410 these 5-value lists. The coordinates are given as normalized
411 rectangles (x,y,w,h) specifying the region. For example:
412 [[0.0, 0.0, 1.0, 0.5, 5], [0.0, 0.5, 1.0, 0.5, 10]].
413 Weights are non-negative integers.
414
415 Returns:
416 Five values are returned if get_results is true::
417 * AE sensitivity; None if do_ae is False
418 * AE exposure time; None if do_ae is False
419 * AWB gains (list); None if do_awb is False
420 * AWB transform (list); None if do_awb is false
421 * AF focus position; None if do_af is false
422 Otherwise, it returns five None values.
423 """
424 print "Running vendor 3A on device"
425 cmd = {}
426 cmd["cmdName"] = "do3A"
427 cmd["regions"] = {"ae": sum(regions_ae, []),
428 "awb": sum(regions_awb, []),
429 "af": sum(regions_af, [])}
430 cmd["triggers"] = {"ae": do_ae, "af": do_af}
431 if lock_ae:
432 cmd["aeLock"] = True
433 if lock_awb:
434 cmd["awbLock"] = True
Zhijun Heeb3ff472014-11-20 13:47:11 -0800435 if ev_comp != 0:
436 cmd["evComp"] = ev_comp
Ruben Brunk370e2432014-10-14 18:33:23 -0700437 self.sock.send(json.dumps(cmd) + "\n")
438
439 # Wait for each specified 3A to converge.
440 ae_sens = None
441 ae_exp = None
442 awb_gains = None
443 awb_transform = None
444 af_dist = None
445 converged = False
446 while True:
447 data,_ = self.__read_response_from_socket()
448 vals = data['strValue'].split()
449 if data['tag'] == 'aeResult':
450 ae_sens, ae_exp = [int(i) for i in vals]
451 elif data['tag'] == 'afResult':
452 af_dist = float(vals[0])
453 elif data['tag'] == 'awbResult':
454 awb_gains = [float(f) for f in vals[:4]]
455 awb_transform = [float(f) for f in vals[4:]]
456 elif data['tag'] == '3aConverged':
457 converged = True
458 elif data['tag'] == '3aDone':
459 break
460 else:
461 raise its.error.Error('Invalid command response')
462 if converged and not get_results:
463 return None,None,None,None,None
464 if (do_ae and ae_sens == None or do_awb and awb_gains == None
465 or do_af and af_dist == None or not converged):
466 raise its.error.Error('3A failed to converge')
467 return ae_sens, ae_exp, awb_gains, awb_transform, af_dist
468
Yin-Chia Yeh9e5d9ac2016-03-30 12:04:12 -0700469 def do_capture(self, cap_request,
470 out_surfaces=None, reprocess_format=None, repeat_request=None):
Ruben Brunk370e2432014-10-14 18:33:23 -0700471 """Issue capture request(s), and read back the image(s) and metadata.
472
473 The main top-level function for capturing one or more images using the
474 device. Captures a single image if cap_request is a single object, and
475 captures a burst if it is a list of objects.
476
Yin-Chia Yeh9e5d9ac2016-03-30 12:04:12 -0700477 The optional repeat_request field can be used to assign a repeating
478 request list ran in background for 3 seconds to warm up the capturing
479 pipeline before start capturing. The repeat_requests will be ran on a
480 640x480 YUV surface without sending any data back. The caller needs to
481 make sure the stream configuration defined by out_surfaces and
482 repeat_request are valid or do_capture may fail because device does not
483 support such stream configuration.
484
Ruben Brunk370e2432014-10-14 18:33:23 -0700485 The out_surfaces field can specify the width(s), height(s), and
486 format(s) of the captured image. The formats may be "yuv", "jpeg",
Timothy Knight67d8ec92015-08-31 13:14:46 -0700487 "dng", "raw", "raw10", "raw12", or "rawStats". The default is a YUV420
488 frame ("yuv") corresponding to a full sensor frame.
Ruben Brunk370e2432014-10-14 18:33:23 -0700489
490 Note that one or more surfaces can be specified, allowing a capture to
491 request images back in multiple formats (e.g.) raw+yuv, raw+jpeg,
492 yuv+jpeg, raw+yuv+jpeg. If the size is omitted for a surface, the
493 default is the largest resolution available for the format of that
494 surface. At most one output surface can be specified for a given format,
495 and raw+dng, raw10+dng, and raw+raw10 are not supported as combinations.
496
Chien-Yu Chen4e1e2cc2015-06-08 17:46:52 -0700497 If reprocess_format is not None, for each request, an intermediate
498 buffer of the given reprocess_format will be captured from camera and
499 the intermediate buffer will be reprocessed to the output surfaces. The
500 following settings will be turned off when capturing the intermediate
501 buffer and will be applied when reprocessing the intermediate buffer.
502 1. android.noiseReduction.mode
503 2. android.edge.mode
504 3. android.reprocess.effectiveExposureFactor
505
506 Supported reprocess format are "yuv" and "private". Supported output
507 surface formats when reprocessing is enabled are "yuv" and "jpeg".
508
Ruben Brunk370e2432014-10-14 18:33:23 -0700509 Example of a single capture request:
510
511 {
512 "android.sensor.exposureTime": 100*1000*1000,
513 "android.sensor.sensitivity": 100
514 }
515
516 Example of a list of capture requests:
517
518 [
519 {
520 "android.sensor.exposureTime": 100*1000*1000,
521 "android.sensor.sensitivity": 100
522 },
523 {
524 "android.sensor.exposureTime": 100*1000*1000,
525 "android.sensor.sensitivity": 200
526 }
527 ]
528
529 Examples of output surface specifications:
530
531 {
532 "width": 640,
533 "height": 480,
534 "format": "yuv"
535 }
536
537 [
538 {
539 "format": "jpeg"
540 },
541 {
542 "format": "raw"
543 }
544 ]
545
546 The following variables defined in this class are shortcuts for
547 specifying one or more formats where each output is the full size for
548 that format; they can be used as values for the out_surfaces arguments:
549
550 CAP_RAW
551 CAP_DNG
552 CAP_YUV
553 CAP_JPEG
554 CAP_RAW_YUV
555 CAP_DNG_YUV
556 CAP_RAW_JPEG
557 CAP_DNG_JPEG
558 CAP_YUV_JPEG
559 CAP_RAW_YUV_JPEG
560 CAP_DNG_YUV_JPEG
561
Chien-Yu Chen682faa22014-10-22 17:34:44 -0700562 If multiple formats are specified, then this function returns multiple
Ruben Brunk370e2432014-10-14 18:33:23 -0700563 capture objects, one for each requested format. If multiple formats and
564 multiple captures (i.e. a burst) are specified, then this function
565 returns multiple lists of capture objects. In both cases, the order of
566 the returned objects matches the order of the requested formats in the
567 out_surfaces parameter. For example:
568
569 yuv_cap = do_capture( req1 )
570 yuv_cap = do_capture( req1, yuv_fmt )
571 yuv_cap, raw_cap = do_capture( req1, [yuv_fmt,raw_fmt] )
572 yuv_caps = do_capture( [req1,req2], yuv_fmt )
573 yuv_caps, raw_caps = do_capture( [req1,req2], [yuv_fmt,raw_fmt] )
574
Timothy Knight67d8ec92015-08-31 13:14:46 -0700575 The "rawStats" format processes the raw image and returns a new image
576 of statistics from the raw image. The format takes additional keys,
577 "gridWidth" and "gridHeight" which are size of grid cells in a 2D grid
578 of the raw image. For each grid cell, the mean and variance of each raw
579 channel is computed, and the do_capture call returns two 4-element float
580 images of dimensions (rawWidth / gridWidth, rawHeight / gridHeight),
581 concatenated back-to-back, where the first iamge contains the 4-channel
Timothy Knightff3032b2017-02-01 15:10:51 -0800582 means and the second contains the 4-channel variances. Note that only
583 pixels in the active array crop region are used; pixels outside this
584 region (for example optical black rows) are cropped out before the
585 gridding and statistics computation is performed.
Timothy Knight67d8ec92015-08-31 13:14:46 -0700586
587 For the rawStats format, if the gridWidth is not provided then the raw
588 image width is used as the default, and similarly for gridHeight. With
589 this, the following is an example of a output description that computes
590 the mean and variance across each image row:
591
592 {
593 "gridHeight": 1,
594 "format": "rawStats"
595 }
596
Ruben Brunk370e2432014-10-14 18:33:23 -0700597 Args:
598 cap_request: The Python dict/list specifying the capture(s), which
599 will be converted to JSON and sent to the device.
600 out_surfaces: (Optional) specifications of the output image formats
601 and sizes to use for each capture.
Chien-Yu Chen4e1e2cc2015-06-08 17:46:52 -0700602 reprocess_format: (Optional) The reprocessing format. If not None,
603 reprocessing will be enabled.
Ruben Brunk370e2432014-10-14 18:33:23 -0700604
605 Returns:
606 An object, list of objects, or list of lists of objects, where each
607 object contains the following fields:
608 * data: the image data as a numpy array of bytes.
609 * width: the width of the captured image.
610 * height: the height of the captured image.
Timothy Knight67d8ec92015-08-31 13:14:46 -0700611 * format: image the format, in [
612 "yuv","jpeg","raw","raw10","raw12","rawStats","dng"].
Chien-Yu Chen682faa22014-10-22 17:34:44 -0700613 * metadata: the capture result object (Python dictionary).
Ruben Brunk370e2432014-10-14 18:33:23 -0700614 """
615 cmd = {}
Chien-Yu Chen4e1e2cc2015-06-08 17:46:52 -0700616 if reprocess_format != None:
617 cmd["cmdName"] = "doReprocessCapture"
618 cmd["reprocessFormat"] = reprocess_format
619 else:
620 cmd["cmdName"] = "doCapture"
Yin-Chia Yeh9e5d9ac2016-03-30 12:04:12 -0700621
622 if repeat_request is not None and reprocess_format is not None:
623 raise its.error.Error('repeating request + reprocessing is not supported')
624
625 if repeat_request is None:
626 cmd["repeatRequests"] = []
627 elif not isinstance(repeat_request, list):
628 cmd["repeatRequests"] = [repeat_request]
629 else:
630 cmd["repeatRequests"] = repeat_request
631
Ruben Brunk370e2432014-10-14 18:33:23 -0700632 if not isinstance(cap_request, list):
633 cmd["captureRequests"] = [cap_request]
634 else:
635 cmd["captureRequests"] = cap_request
636 if out_surfaces is not None:
637 if not isinstance(out_surfaces, list):
638 cmd["outputSurfaces"] = [out_surfaces]
639 else:
640 cmd["outputSurfaces"] = out_surfaces
Lu75f22fc2016-02-19 10:54:07 -0800641 formats = [c["format"] if "format" in c else "yuv"
Ruben Brunk370e2432014-10-14 18:33:23 -0700642 for c in cmd["outputSurfaces"]]
643 formats = [s if s != "jpg" else "jpeg" for s in formats]
644 else:
Yin-Chia Yeh409f2e92016-03-16 18:55:31 -0700645 max_yuv_size = its.objects.get_available_output_sizes(
646 "yuv", self.props)[0]
Ruben Brunk370e2432014-10-14 18:33:23 -0700647 formats = ['yuv']
Yin-Chia Yeh409f2e92016-03-16 18:55:31 -0700648 cmd["outputSurfaces"] = [{"format": "yuv",
649 "width" : max_yuv_size[0],
650 "height": max_yuv_size[1]}]
Ruben Brunk370e2432014-10-14 18:33:23 -0700651 ncap = len(cmd["captureRequests"])
652 nsurf = 1 if out_surfaces is None else len(cmd["outputSurfaces"])
Lu7c6f52e2015-12-15 14:42:18 -0800653 # Only allow yuv output to multiple targets
654 yuv_surfaces = [s for s in cmd["outputSurfaces"] if s["format"]=="yuv"]
655 n_yuv = len(yuv_surfaces)
656 # Compute the buffer size of YUV targets
Lu75f22fc2016-02-19 10:54:07 -0800657 yuv_maxsize_1d = 0
658 for s in yuv_surfaces:
659 if not ("width" in s and "height" in s):
660 if self.props is None:
661 raise its.error.Error('Camera props are unavailable')
662 yuv_maxsize_2d = its.objects.get_available_output_sizes(
663 "yuv", self.props)[0]
664 yuv_maxsize_1d = yuv_maxsize_2d[0] * yuv_maxsize_2d[1] * 3 / 2
665 break
666 yuv_sizes = [c["width"]*c["height"]*3/2
667 if "width" in c and "height" in c
668 else yuv_maxsize_1d
669 for c in yuv_surfaces]
Lu7c6f52e2015-12-15 14:42:18 -0800670 # Currently we don't pass enough metadta from ItsService to distinguish
671 # different yuv stream of same buffer size
672 if len(yuv_sizes) != len(set(yuv_sizes)):
673 raise its.error.Error(
674 'ITS does not support yuv outputs of same buffer size')
Ruben Brunk370e2432014-10-14 18:33:23 -0700675 if len(formats) > len(set(formats)):
Lu7c6f52e2015-12-15 14:42:18 -0800676 if n_yuv != len(formats) - len(set(formats)) + 1:
677 raise its.error.Error('Duplicate format requested')
678
Timothy Knight67d8ec92015-08-31 13:14:46 -0700679 raw_formats = 0;
680 raw_formats += 1 if "dng" in formats else 0
681 raw_formats += 1 if "raw" in formats else 0
682 raw_formats += 1 if "raw10" in formats else 0
683 raw_formats += 1 if "raw12" in formats else 0
684 raw_formats += 1 if "rawStats" in formats else 0
685 if raw_formats > 1:
Ruben Brunk370e2432014-10-14 18:33:23 -0700686 raise its.error.Error('Different raw formats not supported')
Yin-Chia Yeh39f8f4b2015-07-17 12:29:31 -0700687
688 # Detect long exposure time and set timeout accordingly
689 longest_exp_time = 0
690 for req in cmd["captureRequests"]:
691 if "android.sensor.exposureTime" in req and \
692 req["android.sensor.exposureTime"] > longest_exp_time:
693 longest_exp_time = req["android.sensor.exposureTime"]
694
695 extended_timeout = longest_exp_time / self.SEC_TO_NSEC + \
696 self.SOCK_TIMEOUT
Yin-Chia Yehc9d554d2016-06-01 17:30:32 -0700697 if repeat_request:
698 extended_timeout += self.EXTRA_SOCK_TIMEOUT
Yin-Chia Yeh39f8f4b2015-07-17 12:29:31 -0700699 self.sock.settimeout(extended_timeout)
700
Ruben Brunk370e2432014-10-14 18:33:23 -0700701 print "Capturing %d frame%s with %d format%s [%s]" % (
702 ncap, "s" if ncap>1 else "", nsurf, "s" if nsurf>1 else "",
703 ",".join(formats))
704 self.sock.send(json.dumps(cmd) + "\n")
705
706 # Wait for ncap*nsurf images and ncap metadata responses.
707 # Assume that captures come out in the same order as requested in
Chien-Yu Chen682faa22014-10-22 17:34:44 -0700708 # the burst, however individual images of different formats can come
Ruben Brunk370e2432014-10-14 18:33:23 -0700709 # out in any order for that capture.
710 nbufs = 0
Lu7c6f52e2015-12-15 14:42:18 -0800711 bufs = {"raw":[], "raw10":[], "raw12":[],
Timothy Knight67d8ec92015-08-31 13:14:46 -0700712 "rawStats":[], "dng":[], "jpeg":[]}
Lu7c6f52e2015-12-15 14:42:18 -0800713 yuv_bufs = {size:[] for size in yuv_sizes}
Ruben Brunk370e2432014-10-14 18:33:23 -0700714 mds = []
715 widths = None
716 heights = None
717 while nbufs < ncap*nsurf or len(mds) < ncap:
718 jsonObj,buf = self.__read_response_from_socket()
Lu7c6f52e2015-12-15 14:42:18 -0800719 if jsonObj['tag'] in ['jpegImage', 'rawImage', \
Timothy Knight67d8ec92015-08-31 13:14:46 -0700720 'raw10Image', 'raw12Image', 'rawStatsImage', 'dngImage'] \
721 and buf is not None:
Ruben Brunk370e2432014-10-14 18:33:23 -0700722 fmt = jsonObj['tag'][:-5]
723 bufs[fmt].append(buf)
724 nbufs += 1
Lu7c6f52e2015-12-15 14:42:18 -0800725 elif jsonObj['tag'] == 'yuvImage':
726 buf_size = numpy.product(buf.shape)
727 yuv_bufs[buf_size].append(buf)
728 nbufs += 1
Ruben Brunk370e2432014-10-14 18:33:23 -0700729 elif jsonObj['tag'] == 'captureResults':
730 mds.append(jsonObj['objValue']['captureResult'])
731 outputs = jsonObj['objValue']['outputs']
732 widths = [out['width'] for out in outputs]
733 heights = [out['height'] for out in outputs]
734 else:
735 # Just ignore other tags
736 None
737 rets = []
738 for j,fmt in enumerate(formats):
739 objs = []
740 for i in range(ncap):
741 obj = {}
Ruben Brunk370e2432014-10-14 18:33:23 -0700742 obj["width"] = widths[j]
743 obj["height"] = heights[j]
744 obj["format"] = fmt
745 obj["metadata"] = mds[i]
Lu7c6f52e2015-12-15 14:42:18 -0800746 if fmt == 'yuv':
747 buf_size = widths[j] * heights[j] * 3 / 2
748 obj["data"] = yuv_bufs[buf_size][i]
749 else:
750 obj["data"] = bufs[fmt][i]
Ruben Brunk370e2432014-10-14 18:33:23 -0700751 objs.append(obj)
752 rets.append(objs if ncap>1 else objs[0])
Yin-Chia Yeh39f8f4b2015-07-17 12:29:31 -0700753 self.sock.settimeout(self.SOCK_TIMEOUT)
Ruben Brunk370e2432014-10-14 18:33:23 -0700754 return rets if len(rets)>1 else rets[0]
755
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700756def get_device_id():
757 """ Return the ID of the device that the test is running on.
758
759 Return the device ID provided in the command line if it's connected. If no
760 device ID is provided in the command line and there is only one device
761 connected, return the device ID by parsing the result of "adb devices".
Clemenz Portmannf7f23ee2016-08-18 13:00:59 -0700762 Also, if the environment variable ANDROID_SERIAL is set, use it as device
763 id. When both ANDROID_SERIAL and device argument present, device argument
764 takes priority.
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700765
766 Raise an exception if no device is connected; or the device ID provided in
767 the command line is not connected; or no device ID is provided in the
Clemenz Portmannf7f23ee2016-08-18 13:00:59 -0700768 command line or environment variable and there are more than 1 device
769 connected.
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700770
771 Returns:
772 Device ID string.
773 """
774 device_id = None
Clemenz Portmannf7f23ee2016-08-18 13:00:59 -0700775
776 # Check if device id is set in env
777 if "ANDROID_SERIAL" in os.environ:
778 device_id = os.environ["ANDROID_SERIAL"]
779
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700780 for s in sys.argv[1:]:
781 if s[:7] == "device=" and len(s) > 7:
782 device_id = str(s[7:])
783
784 # Get a list of connected devices
785 devices = []
786 command = "adb devices"
787 proc = subprocess.Popen(command.split(), stdout=subprocess.PIPE)
788 output, error = proc.communicate()
789 for line in output.split(os.linesep):
790 device_info = line.split()
791 if len(device_info) == 2 and device_info[1] == "device":
792 devices.append(device_info[0])
793
794 if len(devices) == 0:
795 raise its.error.Error("No device is connected!")
796 elif device_id is not None and device_id not in devices:
797 raise its.error.Error(device_id + " is not connected!")
798 elif device_id is None and len(devices) >= 2:
799 raise its.error.Error("More than 1 device are connected. " +
800 "Use device=<device_id> to specify a device to test.")
801 elif len(devices) == 1:
802 device_id = devices[0]
803
804 return device_id
805
Yin-Chia Yeh02eeffb2016-08-16 15:28:03 -0700806def report_result(device_id, camera_id, results):
Timothy Knighted076002014-10-23 16:12:26 -0700807 """Send a pass/fail result to the device, via an intent.
808
809 Args:
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700810 device_id: The ID string of the device to report the results to.
Timothy Knighted076002014-10-23 16:12:26 -0700811 camera_id: The ID string of the camera for which to report pass/fail.
Yin-Chia Yeh02eeffb2016-08-16 15:28:03 -0700812 results: a dictionary contains all ITS scenes as key and result/summary
813 of current ITS run. See test_report_result unit test for
814 an example.
Timothy Knighted076002014-10-23 16:12:26 -0700815 Returns:
816 Nothing.
817 """
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700818 adb = "adb -s " + device_id
Sam Linf2f66002017-02-27 21:08:11 -0800819
820 # Start ItsTestActivity to prevent flaky
821 cmd = "%s shell am start %s --activity-brought-to-front" % (adb, ItsSession.ITS_TEST_ACTIVITY)
822 _run(cmd)
823
Yin-Chia Yeh02eeffb2016-08-16 15:28:03 -0700824 # Validate/process results argument
825 for scene in results:
826 result_key = ItsSession.RESULT_KEY
827 summary_key = ItsSession.SUMMARY_KEY
828 if result_key not in results[scene]:
829 raise its.error.Error('ITS result not found for ' + scene)
830 if results[scene][result_key] not in ItsSession.RESULT_VALUES:
831 raise its.error.Error('Unknown ITS result for %s: %s' % (
832 scene, results[result_key]))
833 if summary_key in results[scene]:
834 device_summary_path = "/sdcard/its_camera%s_%s.txt" % (
835 camera_id, scene)
836 _run("%s push %s %s" % (
837 adb, results[scene][summary_key], device_summary_path))
838 results[scene][summary_key] = device_summary_path
Sam Linf2f66002017-02-27 21:08:11 -0800839
Yin-Chia Yeh02eeffb2016-08-16 15:28:03 -0700840 json_results = json.dumps(results)
841 cmd = "%s shell am broadcast -a %s --es %s %s --es %s %s --es %s \'%s\'" % (
842 adb, ItsSession.ACTION_ITS_RESULT,
843 ItsSession.EXTRA_VERSION, ItsSession.CURRENT_ITS_VERSION,
844 ItsSession.EXTRA_CAMERA_ID, camera_id,
845 ItsSession.EXTRA_RESULTS, json_results)
846 if len(cmd) > 4095:
847 print "ITS command string might be too long! len:", len(cmd)
848 _run(cmd)
Ruben Brunk370e2432014-10-14 18:33:23 -0700849
Sam Linf2f66002017-02-27 21:08:11 -0800850def get_device_fingerprint(device_id):
851 """ Return the Build FingerPrint of the device that the test is running on.
852
853 Returns:
854 Device Build Fingerprint string.
855 """
856 device_bfp = None
857
858 # Get a list of connected devices
859
860 com = ('adb -s %s shell getprop | grep ro.build.fingerprint' % device_id)
861 proc = subprocess.Popen(com.split(), stdout=subprocess.PIPE)
862 output, error = proc.communicate()
863 assert error is None
864
865 lst = string.split( \
866 string.replace( \
867 string.replace( \
868 string.replace(output,
869 '\n', ''), '[', ''), ']', ''), \
870 ' ')
871
872 if lst[0].find('ro.build.fingerprint') != -1:
873 device_bfp = lst[1]
874
875 return device_bfp
876
Ruben Brunk370e2432014-10-14 18:33:23 -0700877def _run(cmd):
878 """Replacement for os.system, with hiding of stdout+stderr messages.
879 """
880 with open(os.devnull, 'wb') as devnull:
881 subprocess.check_call(
882 cmd.split(), stdout=devnull, stderr=subprocess.STDOUT)
883
884class __UnitTest(unittest.TestCase):
885 """Run a suite of unit tests on this module.
886 """
887
Yin-Chia Yeh02eeffb2016-08-16 15:28:03 -0700888 """
889 # TODO: this test currently needs connected device to pass
890 # Need to remove that dependency before enabling the test
891 def test_report_result(self):
892 device_id = get_device_id()
893 camera_id = "1"
894 result_key = ItsSession.RESULT_KEY
895 results = {"scene0":{result_key:"PASS"},
896 "scene1":{result_key:"PASS"},
897 "scene2":{result_key:"PASS"},
898 "scene3":{result_key:"PASS"},
899 "sceneNotExist":{result_key:"FAIL"}}
900 report_result(device_id, camera_id, results)
901 """
Ruben Brunk370e2432014-10-14 18:33:23 -0700902
903if __name__ == '__main__':
904 unittest.main()
905