blob: 26af162eeef9659fa4c1ee7319e874ca0c735a5a [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
Clemenz Portmanna2815fa2017-05-23 08:31:19 -070029CMD_DELAY = 1 # seconds
30
31
Ruben Brunk370e2432014-10-14 18:33:23 -070032class ItsSession(object):
33 """Controls a device over adb to run ITS scripts.
34
35 The script importing this module (on the host machine) prepares JSON
36 objects encoding CaptureRequests, specifying sets of parameters to use
Chien-Yu Chen682faa22014-10-22 17:34:44 -070037 when capturing an image using the Camera2 APIs. This class encapsulates
Ruben Brunk370e2432014-10-14 18:33:23 -070038 sending the requests to the device, monitoring the device's progress, and
39 copying the resultant captures back to the host machine when done. TCP
40 forwarded over adb is the transport mechanism used.
41
42 The device must have CtsVerifier.apk installed.
43
44 Attributes:
45 sock: The open socket.
46 """
47
Chien-Yu Chen1da23132015-07-22 15:24:41 -070048 # Open a connection to localhost:<host_port>, forwarded to port 6000 on the
49 # device. <host_port> is determined at run-time to support multiple
50 # connected devices.
Ruben Brunk370e2432014-10-14 18:33:23 -070051 IPADDR = '127.0.0.1'
Chien-Yu Chen1da23132015-07-22 15:24:41 -070052 REMOTE_PORT = 6000
Ruben Brunk370e2432014-10-14 18:33:23 -070053 BUFFER_SIZE = 4096
54
Chien-Yu Chen1da23132015-07-22 15:24:41 -070055 # LOCK_PORT is used as a mutex lock to protect the list of forwarded ports
56 # among all processes. The script assumes LOCK_PORT is available and will
57 # try to use ports between CLIENT_PORT_START and
58 # CLIENT_PORT_START+MAX_NUM_PORTS-1 on host for ITS sessions.
59 CLIENT_PORT_START = 6000
60 MAX_NUM_PORTS = 100
61 LOCK_PORT = CLIENT_PORT_START + MAX_NUM_PORTS
62
Ruben Brunk370e2432014-10-14 18:33:23 -070063 # Seconds timeout on each socket operation.
Yin-Chia Yehea1c1a62016-07-12 15:29:30 -070064 SOCK_TIMEOUT = 20.0
Yin-Chia Yehc9d554d2016-06-01 17:30:32 -070065 # Additional timeout in seconds when ITS service is doing more complicated
66 # operations, for example: issuing warmup requests before actual capture.
67 EXTRA_SOCK_TIMEOUT = 5.0
68
Yin-Chia Yeh39f8f4b2015-07-17 12:29:31 -070069 SEC_TO_NSEC = 1000*1000*1000.0
Ruben Brunk370e2432014-10-14 18:33:23 -070070
71 PACKAGE = 'com.android.cts.verifier.camera.its'
72 INTENT_START = 'com.android.cts.verifier.camera.its.START'
73 ACTION_ITS_RESULT = 'com.android.cts.verifier.camera.its.ACTION_ITS_RESULT'
Yin-Chia Yeh02eeffb2016-08-16 15:28:03 -070074 EXTRA_VERSION = 'camera.its.extra.VERSION'
75 CURRENT_ITS_VERSION = '1.0' # version number to sync with CtsVerifier
Yin-Chia Yehab98ada2015-03-05 13:28:53 -080076 EXTRA_CAMERA_ID = 'camera.its.extra.CAMERA_ID'
Yin-Chia Yeh02eeffb2016-08-16 15:28:03 -070077 EXTRA_RESULTS = 'camera.its.extra.RESULTS'
Sam Linf2f66002017-02-27 21:08:11 -080078 ITS_TEST_ACTIVITY = 'com.android.cts.verifier/.camera.its.ItsTestActivity'
Yin-Chia Yeh02eeffb2016-08-16 15:28:03 -070079
80 RESULT_PASS = 'PASS'
81 RESULT_FAIL = 'FAIL'
82 RESULT_NOT_EXECUTED = 'NOT_EXECUTED'
83 RESULT_VALUES = {RESULT_PASS, RESULT_FAIL, RESULT_NOT_EXECUTED}
84 RESULT_KEY = 'result'
85 SUMMARY_KEY = 'summary'
Ruben Brunk370e2432014-10-14 18:33:23 -070086
Chien-Yu Chen1da23132015-07-22 15:24:41 -070087 adb = "adb -d"
88 device_id = ""
Ruben Brunk370e2432014-10-14 18:33:23 -070089
90 # Definitions for some of the common output format options for do_capture().
91 # Each gets images of full resolution for each requested format.
92 CAP_RAW = {"format":"raw"}
93 CAP_DNG = {"format":"dng"}
94 CAP_YUV = {"format":"yuv"}
95 CAP_JPEG = {"format":"jpeg"}
96 CAP_RAW_YUV = [{"format":"raw"}, {"format":"yuv"}]
97 CAP_DNG_YUV = [{"format":"dng"}, {"format":"yuv"}]
98 CAP_RAW_JPEG = [{"format":"raw"}, {"format":"jpeg"}]
99 CAP_DNG_JPEG = [{"format":"dng"}, {"format":"jpeg"}]
100 CAP_YUV_JPEG = [{"format":"yuv"}, {"format":"jpeg"}]
101 CAP_RAW_YUV_JPEG = [{"format":"raw"}, {"format":"yuv"}, {"format":"jpeg"}]
102 CAP_DNG_YUV_JPEG = [{"format":"dng"}, {"format":"yuv"}, {"format":"jpeg"}]
103
Lu75f22fc2016-02-19 10:54:07 -0800104 # Predefine camera props. Save props extracted from the function,
105 # "get_camera_properties".
106 props = None
107
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700108 # Initialize the socket port for the host to forward requests to the device.
109 # This method assumes localhost's LOCK_PORT is available and will try to
110 # use ports between CLIENT_PORT_START and CLIENT_PORT_START+MAX_NUM_PORTS-1
111 def __init_socket_port(self):
112 NUM_RETRIES = 100
113 RETRY_WAIT_TIME_SEC = 0.05
Ruben Brunk370e2432014-10-14 18:33:23 -0700114
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700115 # Bind a socket to use as mutex lock
116 socket_lock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
117 for i in range(NUM_RETRIES):
118 try:
119 socket_lock.bind((ItsSession.IPADDR, ItsSession.LOCK_PORT))
120 break
121 except socket.error:
122 if i == NUM_RETRIES - 1:
123 raise its.error.Error(self.device_id,
124 "acquiring socket lock timed out")
125 else:
126 time.sleep(RETRY_WAIT_TIME_SEC)
127
128 # Check if a port is already assigned to the device.
129 command = "adb forward --list"
130 proc = subprocess.Popen(command.split(), stdout=subprocess.PIPE)
131 output, error = proc.communicate()
132
133 port = None
134 used_ports = []
135 for line in output.split(os.linesep):
136 # each line should be formatted as:
137 # "<device_id> tcp:<host_port> tcp:<remote_port>"
138 forward_info = line.split()
139 if len(forward_info) >= 3 and \
140 len(forward_info[1]) > 4 and forward_info[1][:4] == "tcp:" and \
141 len(forward_info[2]) > 4 and forward_info[2][:4] == "tcp:":
142 local_p = int(forward_info[1][4:])
143 remote_p = int(forward_info[2][4:])
144 if forward_info[0] == self.device_id and \
145 remote_p == ItsSession.REMOTE_PORT:
146 port = local_p
147 break;
148 else:
149 used_ports.append(local_p)
150
151 # Find the first available port if no port is assigned to the device.
152 if port is None:
153 for p in range(ItsSession.CLIENT_PORT_START,
154 ItsSession.CLIENT_PORT_START +
155 ItsSession.MAX_NUM_PORTS):
156 if p not in used_ports:
157 # Try to run "adb forward" with the port
158 command = "%s forward tcp:%d tcp:%d" % \
159 (self.adb, p, self.REMOTE_PORT)
160 proc = subprocess.Popen(command.split(),
161 stdout=subprocess.PIPE,
162 stderr=subprocess.PIPE)
163 output, error = proc.communicate()
164
165 # Check if there is no error
166 if error is None or error.find("error") < 0:
167 port = p
168 break
169
170 if port is None:
171 raise its.error.Error(self.device_id, " cannot find an available " +
172 "port")
173
174 # Release the socket as mutex unlock
175 socket_lock.close()
176
177 # Connect to the socket
178 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
179 self.sock.connect((self.IPADDR, port))
180 self.sock.settimeout(self.SOCK_TIMEOUT)
181
182 # Reboot the device if needed and wait for the service to be ready for
183 # connection.
184 def __wait_for_service(self):
Ruben Brunk370e2432014-10-14 18:33:23 -0700185 # This also includes the optional reboot handling: if the user
186 # provides a "reboot" or "reboot=N" arg, then reboot the device,
187 # waiting for N seconds (default 30) before returning.
188 for s in sys.argv[1:]:
189 if s[:6] == "reboot":
190 duration = 30
191 if len(s) > 7 and s[6] == "=":
192 duration = int(s[7:])
193 print "Rebooting device"
Lu75f22fc2016-02-19 10:54:07 -0800194 _run("%s reboot" % (self.adb))
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700195 _run("%s wait-for-device" % (self.adb))
Ruben Brunk370e2432014-10-14 18:33:23 -0700196 time.sleep(duration)
197 print "Reboot complete"
198
Yin-Chia Yeh23a7b422016-03-25 14:48:47 -0700199 # Flush logcat so following code won't be misled by previous
200 # 'ItsService ready' log.
201 _run('%s logcat -c' % (self.adb))
202 time.sleep(1)
203
Ruben Brunk370e2432014-10-14 18:33:23 -0700204 # TODO: Figure out why "--user 0" is needed, and fix the problem.
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700205 _run('%s shell am force-stop --user 0 %s' % (self.adb, self.PACKAGE))
Clemenz Portmanna2815fa2017-05-23 08:31:19 -0700206 _run(('%s shell am start --user 0 '
207 'com.android.cts.verifier/.camera.its.ItsTestActivity '
208 '--activity-brought-to-front') % self.adb)
209 time.sleep(CMD_DELAY)
Ruben Brunk370e2432014-10-14 18:33:23 -0700210 _run(('%s shell am startservice --user 0 -t text/plain '
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700211 '-a %s') % (self.adb, self.INTENT_START))
Ruben Brunk370e2432014-10-14 18:33:23 -0700212
213 # Wait until the socket is ready to accept a connection.
214 proc = subprocess.Popen(
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700215 self.adb.split() + ["logcat"],
Ruben Brunk370e2432014-10-14 18:33:23 -0700216 stdout=subprocess.PIPE)
217 logcat = proc.stdout
218 while True:
219 line = logcat.readline().strip()
220 if line.find('ItsService ready') >= 0:
221 break
222 proc.kill()
223
Ruben Brunk370e2432014-10-14 18:33:23 -0700224 def __init__(self):
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700225 # Initialize device id and adb command.
226 self.device_id = get_device_id()
227 self.adb = "adb -s " + self.device_id
228
229 self.__wait_for_service()
230 self.__init_socket_port()
231
Ruben Brunk370e2432014-10-14 18:33:23 -0700232 self.__close_camera()
233 self.__open_camera()
234
235 def __del__(self):
236 if hasattr(self, 'sock') and self.sock:
237 self.__close_camera()
238 self.sock.close()
239
240 def __enter__(self):
241 return self
242
243 def __exit__(self, type, value, traceback):
244 return False
245
246 def __read_response_from_socket(self):
247 # Read a line (newline-terminated) string serialization of JSON object.
248 chars = []
249 while len(chars) == 0 or chars[-1] != '\n':
250 ch = self.sock.recv(1)
251 if len(ch) == 0:
252 # Socket was probably closed; otherwise don't get empty strings
253 raise its.error.Error('Problem with socket on device side')
254 chars.append(ch)
255 line = ''.join(chars)
256 jobj = json.loads(line)
257 # Optionally read a binary buffer of a fixed size.
258 buf = None
259 if jobj.has_key("bufValueSize"):
260 n = jobj["bufValueSize"]
261 buf = bytearray(n)
262 view = memoryview(buf)
263 while n > 0:
264 nbytes = self.sock.recv_into(view, n)
265 view = view[nbytes:]
266 n -= nbytes
267 buf = numpy.frombuffer(buf, dtype=numpy.uint8)
268 return jobj, buf
269
270 def __open_camera(self):
271 # Get the camera ID to open as an argument.
272 camera_id = 0
273 for s in sys.argv[1:]:
274 if s[:7] == "camera=" and len(s) > 7:
275 camera_id = int(s[7:])
276 cmd = {"cmdName":"open", "cameraId":camera_id}
277 self.sock.send(json.dumps(cmd) + "\n")
278 data,_ = self.__read_response_from_socket()
279 if data['tag'] != 'cameraOpened':
280 raise its.error.Error('Invalid command response')
281
282 def __close_camera(self):
283 cmd = {"cmdName":"close"}
284 self.sock.send(json.dumps(cmd) + "\n")
285 data,_ = self.__read_response_from_socket()
286 if data['tag'] != 'cameraClosed':
287 raise its.error.Error('Invalid command response')
288
289 def do_vibrate(self, pattern):
290 """Cause the device to vibrate to a specific pattern.
291
292 Args:
293 pattern: Durations (ms) for which to turn on or off the vibrator.
294 The first value indicates the number of milliseconds to wait
295 before turning the vibrator on. The next value indicates the
296 number of milliseconds for which to keep the vibrator on
297 before turning it off. Subsequent values alternate between
298 durations in milliseconds to turn the vibrator off or to turn
299 the vibrator on.
300
301 Returns:
302 Nothing.
303 """
304 cmd = {}
305 cmd["cmdName"] = "doVibrate"
306 cmd["pattern"] = pattern
307 self.sock.send(json.dumps(cmd) + "\n")
308 data,_ = self.__read_response_from_socket()
309 if data['tag'] != 'vibrationStarted':
310 raise its.error.Error('Invalid command response')
311
312 def start_sensor_events(self):
313 """Start collecting sensor events on the device.
314
315 See get_sensor_events for more info.
316
317 Returns:
318 Nothing.
319 """
320 cmd = {}
321 cmd["cmdName"] = "startSensorEvents"
322 self.sock.send(json.dumps(cmd) + "\n")
323 data,_ = self.__read_response_from_socket()
324 if data['tag'] != 'sensorEventsStarted':
325 raise its.error.Error('Invalid command response')
326
327 def get_sensor_events(self):
328 """Get a trace of all sensor events on the device.
329
330 The trace starts when the start_sensor_events function is called. If
331 the test runs for a long time after this call, then the device's
332 internal memory can fill up. Calling get_sensor_events gets all events
333 from the device, and then stops the device from collecting events and
334 clears the internal buffer; to start again, the start_sensor_events
335 call must be used again.
336
337 Events from the accelerometer, compass, and gyro are returned; each
338 has a timestamp and x,y,z values.
339
340 Note that sensor events are only produced if the device isn't in its
341 standby mode (i.e.) if the screen is on.
342
343 Returns:
344 A Python dictionary with three keys ("accel", "mag", "gyro") each
345 of which maps to a list of objects containing "time","x","y","z"
346 keys.
347 """
348 cmd = {}
349 cmd["cmdName"] = "getSensorEvents"
350 self.sock.send(json.dumps(cmd) + "\n")
Yin-Chia Yehf16d6872016-06-06 13:54:42 -0700351 timeout = self.SOCK_TIMEOUT + self.EXTRA_SOCK_TIMEOUT
352 self.sock.settimeout(timeout)
Ruben Brunk370e2432014-10-14 18:33:23 -0700353 data,_ = self.__read_response_from_socket()
354 if data['tag'] != 'sensorEvents':
355 raise its.error.Error('Invalid command response')
Yin-Chia Yehf16d6872016-06-06 13:54:42 -0700356 self.sock.settimeout(self.SOCK_TIMEOUT)
Ruben Brunk370e2432014-10-14 18:33:23 -0700357 return data['objValue']
358
Yin-Chia Yehab98ada2015-03-05 13:28:53 -0800359 def get_camera_ids(self):
360 """Get a list of camera device Ids that can be opened.
361
362 Returns:
363 a list of camera ID string
364 """
365 cmd = {}
366 cmd["cmdName"] = "getCameraIds"
367 self.sock.send(json.dumps(cmd) + "\n")
368 data,_ = self.__read_response_from_socket()
369 if data['tag'] != 'cameraIds':
370 raise its.error.Error('Invalid command response')
371 return data['objValue']['cameraIdArray']
372
Ruben Brunk370e2432014-10-14 18:33:23 -0700373 def get_camera_properties(self):
374 """Get the camera properties object for the device.
375
376 Returns:
377 The Python dictionary object for the CameraProperties object.
378 """
379 cmd = {}
380 cmd["cmdName"] = "getCameraProperties"
381 self.sock.send(json.dumps(cmd) + "\n")
382 data,_ = self.__read_response_from_socket()
383 if data['tag'] != 'cameraProperties':
384 raise its.error.Error('Invalid command response')
Lu75f22fc2016-02-19 10:54:07 -0800385 self.props = data['objValue']['cameraProperties']
Ruben Brunk370e2432014-10-14 18:33:23 -0700386 return data['objValue']['cameraProperties']
387
388 def do_3a(self, regions_ae=[[0,0,1,1,1]],
389 regions_awb=[[0,0,1,1,1]],
390 regions_af=[[0,0,1,1,1]],
391 do_ae=True, do_awb=True, do_af=True,
392 lock_ae=False, lock_awb=False,
Zhijun Heeb3ff472014-11-20 13:47:11 -0800393 get_results=False,
394 ev_comp=0):
Ruben Brunk370e2432014-10-14 18:33:23 -0700395 """Perform a 3A operation on the device.
396
397 Triggers some or all of AE, AWB, and AF, and returns once they have
398 converged. Uses the vendor 3A that is implemented inside the HAL.
Clemenz Portmann3c97e772017-03-23 13:22:06 -0700399 Note: do_awb is always enabled regardless of do_awb flag
Ruben Brunk370e2432014-10-14 18:33:23 -0700400
401 Throws an assertion if 3A fails to converge.
402
403 Args:
404 regions_ae: List of weighted AE regions.
405 regions_awb: List of weighted AWB regions.
406 regions_af: List of weighted AF regions.
407 do_ae: Trigger AE and wait for it to converge.
408 do_awb: Wait for AWB to converge.
409 do_af: Trigger AF and wait for it to converge.
410 lock_ae: Request AE lock after convergence, and wait for it.
411 lock_awb: Request AWB lock after convergence, and wait for it.
412 get_results: Return the 3A results from this function.
Zhijun Heeb3ff472014-11-20 13:47:11 -0800413 ev_comp: An EV compensation value to use when running AE.
Ruben Brunk370e2432014-10-14 18:33:23 -0700414
415 Region format in args:
416 Arguments are lists of weighted regions; each weighted region is a
417 list of 5 values, [x,y,w,h, wgt], and each argument is a list of
418 these 5-value lists. The coordinates are given as normalized
419 rectangles (x,y,w,h) specifying the region. For example:
420 [[0.0, 0.0, 1.0, 0.5, 5], [0.0, 0.5, 1.0, 0.5, 10]].
421 Weights are non-negative integers.
422
423 Returns:
424 Five values are returned if get_results is true::
425 * AE sensitivity; None if do_ae is False
426 * AE exposure time; None if do_ae is False
Clemenz Portmann3c97e772017-03-23 13:22:06 -0700427 * AWB gains (list);
428 * AWB transform (list);
Ruben Brunk370e2432014-10-14 18:33:23 -0700429 * AF focus position; None if do_af is false
430 Otherwise, it returns five None values.
431 """
432 print "Running vendor 3A on device"
433 cmd = {}
434 cmd["cmdName"] = "do3A"
435 cmd["regions"] = {"ae": sum(regions_ae, []),
436 "awb": sum(regions_awb, []),
437 "af": sum(regions_af, [])}
438 cmd["triggers"] = {"ae": do_ae, "af": do_af}
439 if lock_ae:
440 cmd["aeLock"] = True
441 if lock_awb:
442 cmd["awbLock"] = True
Zhijun Heeb3ff472014-11-20 13:47:11 -0800443 if ev_comp != 0:
444 cmd["evComp"] = ev_comp
Ruben Brunk370e2432014-10-14 18:33:23 -0700445 self.sock.send(json.dumps(cmd) + "\n")
446
447 # Wait for each specified 3A to converge.
448 ae_sens = None
449 ae_exp = None
450 awb_gains = None
451 awb_transform = None
452 af_dist = None
453 converged = False
454 while True:
455 data,_ = self.__read_response_from_socket()
456 vals = data['strValue'].split()
457 if data['tag'] == 'aeResult':
Clemenz Portmann3c97e772017-03-23 13:22:06 -0700458 if do_ae:
459 ae_sens, ae_exp = [int(i) for i in vals]
Ruben Brunk370e2432014-10-14 18:33:23 -0700460 elif data['tag'] == 'afResult':
Clemenz Portmann3c97e772017-03-23 13:22:06 -0700461 if do_af:
462 af_dist = float(vals[0])
Ruben Brunk370e2432014-10-14 18:33:23 -0700463 elif data['tag'] == 'awbResult':
464 awb_gains = [float(f) for f in vals[:4]]
465 awb_transform = [float(f) for f in vals[4:]]
466 elif data['tag'] == '3aConverged':
467 converged = True
468 elif data['tag'] == '3aDone':
469 break
470 else:
471 raise its.error.Error('Invalid command response')
472 if converged and not get_results:
473 return None,None,None,None,None
474 if (do_ae and ae_sens == None or do_awb and awb_gains == None
475 or do_af and af_dist == None or not converged):
476 raise its.error.Error('3A failed to converge')
477 return ae_sens, ae_exp, awb_gains, awb_transform, af_dist
478
Yin-Chia Yeh9e5d9ac2016-03-30 12:04:12 -0700479 def do_capture(self, cap_request,
480 out_surfaces=None, reprocess_format=None, repeat_request=None):
Ruben Brunk370e2432014-10-14 18:33:23 -0700481 """Issue capture request(s), and read back the image(s) and metadata.
482
483 The main top-level function for capturing one or more images using the
484 device. Captures a single image if cap_request is a single object, and
485 captures a burst if it is a list of objects.
486
Yin-Chia Yeh9e5d9ac2016-03-30 12:04:12 -0700487 The optional repeat_request field can be used to assign a repeating
488 request list ran in background for 3 seconds to warm up the capturing
489 pipeline before start capturing. The repeat_requests will be ran on a
490 640x480 YUV surface without sending any data back. The caller needs to
491 make sure the stream configuration defined by out_surfaces and
492 repeat_request are valid or do_capture may fail because device does not
493 support such stream configuration.
494
Ruben Brunk370e2432014-10-14 18:33:23 -0700495 The out_surfaces field can specify the width(s), height(s), and
496 format(s) of the captured image. The formats may be "yuv", "jpeg",
Timothy Knight67d8ec92015-08-31 13:14:46 -0700497 "dng", "raw", "raw10", "raw12", or "rawStats". The default is a YUV420
498 frame ("yuv") corresponding to a full sensor frame.
Ruben Brunk370e2432014-10-14 18:33:23 -0700499
500 Note that one or more surfaces can be specified, allowing a capture to
501 request images back in multiple formats (e.g.) raw+yuv, raw+jpeg,
502 yuv+jpeg, raw+yuv+jpeg. If the size is omitted for a surface, the
503 default is the largest resolution available for the format of that
504 surface. At most one output surface can be specified for a given format,
505 and raw+dng, raw10+dng, and raw+raw10 are not supported as combinations.
506
Chien-Yu Chen4e1e2cc2015-06-08 17:46:52 -0700507 If reprocess_format is not None, for each request, an intermediate
508 buffer of the given reprocess_format will be captured from camera and
509 the intermediate buffer will be reprocessed to the output surfaces. The
510 following settings will be turned off when capturing the intermediate
511 buffer and will be applied when reprocessing the intermediate buffer.
512 1. android.noiseReduction.mode
513 2. android.edge.mode
514 3. android.reprocess.effectiveExposureFactor
515
516 Supported reprocess format are "yuv" and "private". Supported output
517 surface formats when reprocessing is enabled are "yuv" and "jpeg".
518
Ruben Brunk370e2432014-10-14 18:33:23 -0700519 Example of a single capture request:
520
521 {
522 "android.sensor.exposureTime": 100*1000*1000,
523 "android.sensor.sensitivity": 100
524 }
525
526 Example of a list of capture requests:
527
528 [
529 {
530 "android.sensor.exposureTime": 100*1000*1000,
531 "android.sensor.sensitivity": 100
532 },
533 {
534 "android.sensor.exposureTime": 100*1000*1000,
535 "android.sensor.sensitivity": 200
536 }
537 ]
538
539 Examples of output surface specifications:
540
541 {
542 "width": 640,
543 "height": 480,
544 "format": "yuv"
545 }
546
547 [
548 {
549 "format": "jpeg"
550 },
551 {
552 "format": "raw"
553 }
554 ]
555
556 The following variables defined in this class are shortcuts for
557 specifying one or more formats where each output is the full size for
558 that format; they can be used as values for the out_surfaces arguments:
559
560 CAP_RAW
561 CAP_DNG
562 CAP_YUV
563 CAP_JPEG
564 CAP_RAW_YUV
565 CAP_DNG_YUV
566 CAP_RAW_JPEG
567 CAP_DNG_JPEG
568 CAP_YUV_JPEG
569 CAP_RAW_YUV_JPEG
570 CAP_DNG_YUV_JPEG
571
Chien-Yu Chen682faa22014-10-22 17:34:44 -0700572 If multiple formats are specified, then this function returns multiple
Ruben Brunk370e2432014-10-14 18:33:23 -0700573 capture objects, one for each requested format. If multiple formats and
574 multiple captures (i.e. a burst) are specified, then this function
575 returns multiple lists of capture objects. In both cases, the order of
576 the returned objects matches the order of the requested formats in the
577 out_surfaces parameter. For example:
578
579 yuv_cap = do_capture( req1 )
580 yuv_cap = do_capture( req1, yuv_fmt )
581 yuv_cap, raw_cap = do_capture( req1, [yuv_fmt,raw_fmt] )
582 yuv_caps = do_capture( [req1,req2], yuv_fmt )
583 yuv_caps, raw_caps = do_capture( [req1,req2], [yuv_fmt,raw_fmt] )
584
Timothy Knight67d8ec92015-08-31 13:14:46 -0700585 The "rawStats" format processes the raw image and returns a new image
586 of statistics from the raw image. The format takes additional keys,
587 "gridWidth" and "gridHeight" which are size of grid cells in a 2D grid
588 of the raw image. For each grid cell, the mean and variance of each raw
589 channel is computed, and the do_capture call returns two 4-element float
590 images of dimensions (rawWidth / gridWidth, rawHeight / gridHeight),
591 concatenated back-to-back, where the first iamge contains the 4-channel
Timothy Knightff3032b2017-02-01 15:10:51 -0800592 means and the second contains the 4-channel variances. Note that only
593 pixels in the active array crop region are used; pixels outside this
594 region (for example optical black rows) are cropped out before the
595 gridding and statistics computation is performed.
Timothy Knight67d8ec92015-08-31 13:14:46 -0700596
597 For the rawStats format, if the gridWidth is not provided then the raw
598 image width is used as the default, and similarly for gridHeight. With
599 this, the following is an example of a output description that computes
600 the mean and variance across each image row:
601
602 {
603 "gridHeight": 1,
604 "format": "rawStats"
605 }
606
Ruben Brunk370e2432014-10-14 18:33:23 -0700607 Args:
608 cap_request: The Python dict/list specifying the capture(s), which
609 will be converted to JSON and sent to the device.
610 out_surfaces: (Optional) specifications of the output image formats
611 and sizes to use for each capture.
Chien-Yu Chen4e1e2cc2015-06-08 17:46:52 -0700612 reprocess_format: (Optional) The reprocessing format. If not None,
613 reprocessing will be enabled.
Ruben Brunk370e2432014-10-14 18:33:23 -0700614
615 Returns:
616 An object, list of objects, or list of lists of objects, where each
617 object contains the following fields:
618 * data: the image data as a numpy array of bytes.
619 * width: the width of the captured image.
620 * height: the height of the captured image.
Timothy Knight67d8ec92015-08-31 13:14:46 -0700621 * format: image the format, in [
622 "yuv","jpeg","raw","raw10","raw12","rawStats","dng"].
Chien-Yu Chen682faa22014-10-22 17:34:44 -0700623 * metadata: the capture result object (Python dictionary).
Ruben Brunk370e2432014-10-14 18:33:23 -0700624 """
625 cmd = {}
Chien-Yu Chen4e1e2cc2015-06-08 17:46:52 -0700626 if reprocess_format != None:
627 cmd["cmdName"] = "doReprocessCapture"
628 cmd["reprocessFormat"] = reprocess_format
629 else:
630 cmd["cmdName"] = "doCapture"
Yin-Chia Yeh9e5d9ac2016-03-30 12:04:12 -0700631
632 if repeat_request is not None and reprocess_format is not None:
633 raise its.error.Error('repeating request + reprocessing is not supported')
634
635 if repeat_request is None:
636 cmd["repeatRequests"] = []
637 elif not isinstance(repeat_request, list):
638 cmd["repeatRequests"] = [repeat_request]
639 else:
640 cmd["repeatRequests"] = repeat_request
641
Ruben Brunk370e2432014-10-14 18:33:23 -0700642 if not isinstance(cap_request, list):
643 cmd["captureRequests"] = [cap_request]
644 else:
645 cmd["captureRequests"] = cap_request
646 if out_surfaces is not None:
647 if not isinstance(out_surfaces, list):
648 cmd["outputSurfaces"] = [out_surfaces]
649 else:
650 cmd["outputSurfaces"] = out_surfaces
Lu75f22fc2016-02-19 10:54:07 -0800651 formats = [c["format"] if "format" in c else "yuv"
Ruben Brunk370e2432014-10-14 18:33:23 -0700652 for c in cmd["outputSurfaces"]]
653 formats = [s if s != "jpg" else "jpeg" for s in formats]
654 else:
Yin-Chia Yeh409f2e92016-03-16 18:55:31 -0700655 max_yuv_size = its.objects.get_available_output_sizes(
656 "yuv", self.props)[0]
Ruben Brunk370e2432014-10-14 18:33:23 -0700657 formats = ['yuv']
Yin-Chia Yeh409f2e92016-03-16 18:55:31 -0700658 cmd["outputSurfaces"] = [{"format": "yuv",
659 "width" : max_yuv_size[0],
660 "height": max_yuv_size[1]}]
Ruben Brunk370e2432014-10-14 18:33:23 -0700661 ncap = len(cmd["captureRequests"])
662 nsurf = 1 if out_surfaces is None else len(cmd["outputSurfaces"])
Lu7c6f52e2015-12-15 14:42:18 -0800663 # Only allow yuv output to multiple targets
664 yuv_surfaces = [s for s in cmd["outputSurfaces"] if s["format"]=="yuv"]
665 n_yuv = len(yuv_surfaces)
666 # Compute the buffer size of YUV targets
Lu75f22fc2016-02-19 10:54:07 -0800667 yuv_maxsize_1d = 0
668 for s in yuv_surfaces:
669 if not ("width" in s and "height" in s):
670 if self.props is None:
671 raise its.error.Error('Camera props are unavailable')
672 yuv_maxsize_2d = its.objects.get_available_output_sizes(
673 "yuv", self.props)[0]
674 yuv_maxsize_1d = yuv_maxsize_2d[0] * yuv_maxsize_2d[1] * 3 / 2
675 break
676 yuv_sizes = [c["width"]*c["height"]*3/2
677 if "width" in c and "height" in c
678 else yuv_maxsize_1d
679 for c in yuv_surfaces]
Lu7c6f52e2015-12-15 14:42:18 -0800680 # Currently we don't pass enough metadta from ItsService to distinguish
681 # different yuv stream of same buffer size
682 if len(yuv_sizes) != len(set(yuv_sizes)):
683 raise its.error.Error(
684 'ITS does not support yuv outputs of same buffer size')
Ruben Brunk370e2432014-10-14 18:33:23 -0700685 if len(formats) > len(set(formats)):
Lu7c6f52e2015-12-15 14:42:18 -0800686 if n_yuv != len(formats) - len(set(formats)) + 1:
687 raise its.error.Error('Duplicate format requested')
688
Timothy Knight67d8ec92015-08-31 13:14:46 -0700689 raw_formats = 0;
690 raw_formats += 1 if "dng" in formats else 0
691 raw_formats += 1 if "raw" in formats else 0
692 raw_formats += 1 if "raw10" in formats else 0
693 raw_formats += 1 if "raw12" in formats else 0
694 raw_formats += 1 if "rawStats" in formats else 0
695 if raw_formats > 1:
Ruben Brunk370e2432014-10-14 18:33:23 -0700696 raise its.error.Error('Different raw formats not supported')
Yin-Chia Yeh39f8f4b2015-07-17 12:29:31 -0700697
698 # Detect long exposure time and set timeout accordingly
699 longest_exp_time = 0
700 for req in cmd["captureRequests"]:
701 if "android.sensor.exposureTime" in req and \
702 req["android.sensor.exposureTime"] > longest_exp_time:
703 longest_exp_time = req["android.sensor.exposureTime"]
704
705 extended_timeout = longest_exp_time / self.SEC_TO_NSEC + \
706 self.SOCK_TIMEOUT
Yin-Chia Yehc9d554d2016-06-01 17:30:32 -0700707 if repeat_request:
708 extended_timeout += self.EXTRA_SOCK_TIMEOUT
Yin-Chia Yeh39f8f4b2015-07-17 12:29:31 -0700709 self.sock.settimeout(extended_timeout)
710
Ruben Brunk370e2432014-10-14 18:33:23 -0700711 print "Capturing %d frame%s with %d format%s [%s]" % (
712 ncap, "s" if ncap>1 else "", nsurf, "s" if nsurf>1 else "",
713 ",".join(formats))
714 self.sock.send(json.dumps(cmd) + "\n")
715
716 # Wait for ncap*nsurf images and ncap metadata responses.
717 # Assume that captures come out in the same order as requested in
Chien-Yu Chen682faa22014-10-22 17:34:44 -0700718 # the burst, however individual images of different formats can come
Ruben Brunk370e2432014-10-14 18:33:23 -0700719 # out in any order for that capture.
720 nbufs = 0
Lu7c6f52e2015-12-15 14:42:18 -0800721 bufs = {"raw":[], "raw10":[], "raw12":[],
Timothy Knight67d8ec92015-08-31 13:14:46 -0700722 "rawStats":[], "dng":[], "jpeg":[]}
Lu7c6f52e2015-12-15 14:42:18 -0800723 yuv_bufs = {size:[] for size in yuv_sizes}
Ruben Brunk370e2432014-10-14 18:33:23 -0700724 mds = []
725 widths = None
726 heights = None
727 while nbufs < ncap*nsurf or len(mds) < ncap:
728 jsonObj,buf = self.__read_response_from_socket()
Lu7c6f52e2015-12-15 14:42:18 -0800729 if jsonObj['tag'] in ['jpegImage', 'rawImage', \
Timothy Knight67d8ec92015-08-31 13:14:46 -0700730 'raw10Image', 'raw12Image', 'rawStatsImage', 'dngImage'] \
731 and buf is not None:
Ruben Brunk370e2432014-10-14 18:33:23 -0700732 fmt = jsonObj['tag'][:-5]
733 bufs[fmt].append(buf)
734 nbufs += 1
Lu7c6f52e2015-12-15 14:42:18 -0800735 elif jsonObj['tag'] == 'yuvImage':
736 buf_size = numpy.product(buf.shape)
737 yuv_bufs[buf_size].append(buf)
738 nbufs += 1
Ruben Brunk370e2432014-10-14 18:33:23 -0700739 elif jsonObj['tag'] == 'captureResults':
740 mds.append(jsonObj['objValue']['captureResult'])
741 outputs = jsonObj['objValue']['outputs']
742 widths = [out['width'] for out in outputs]
743 heights = [out['height'] for out in outputs]
744 else:
745 # Just ignore other tags
746 None
747 rets = []
748 for j,fmt in enumerate(formats):
749 objs = []
750 for i in range(ncap):
751 obj = {}
Ruben Brunk370e2432014-10-14 18:33:23 -0700752 obj["width"] = widths[j]
753 obj["height"] = heights[j]
754 obj["format"] = fmt
755 obj["metadata"] = mds[i]
Lu7c6f52e2015-12-15 14:42:18 -0800756 if fmt == 'yuv':
757 buf_size = widths[j] * heights[j] * 3 / 2
758 obj["data"] = yuv_bufs[buf_size][i]
759 else:
760 obj["data"] = bufs[fmt][i]
Ruben Brunk370e2432014-10-14 18:33:23 -0700761 objs.append(obj)
762 rets.append(objs if ncap>1 else objs[0])
Yin-Chia Yeh39f8f4b2015-07-17 12:29:31 -0700763 self.sock.settimeout(self.SOCK_TIMEOUT)
Ruben Brunk370e2432014-10-14 18:33:23 -0700764 return rets if len(rets)>1 else rets[0]
765
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700766def get_device_id():
767 """ Return the ID of the device that the test is running on.
768
769 Return the device ID provided in the command line if it's connected. If no
770 device ID is provided in the command line and there is only one device
771 connected, return the device ID by parsing the result of "adb devices".
Clemenz Portmannf7f23ee2016-08-18 13:00:59 -0700772 Also, if the environment variable ANDROID_SERIAL is set, use it as device
773 id. When both ANDROID_SERIAL and device argument present, device argument
774 takes priority.
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700775
776 Raise an exception if no device is connected; or the device ID provided in
777 the command line is not connected; or no device ID is provided in the
Clemenz Portmannf7f23ee2016-08-18 13:00:59 -0700778 command line or environment variable and there are more than 1 device
779 connected.
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700780
781 Returns:
782 Device ID string.
783 """
784 device_id = None
Clemenz Portmannf7f23ee2016-08-18 13:00:59 -0700785
786 # Check if device id is set in env
787 if "ANDROID_SERIAL" in os.environ:
788 device_id = os.environ["ANDROID_SERIAL"]
789
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700790 for s in sys.argv[1:]:
791 if s[:7] == "device=" and len(s) > 7:
792 device_id = str(s[7:])
793
794 # Get a list of connected devices
795 devices = []
796 command = "adb devices"
797 proc = subprocess.Popen(command.split(), stdout=subprocess.PIPE)
798 output, error = proc.communicate()
799 for line in output.split(os.linesep):
800 device_info = line.split()
801 if len(device_info) == 2 and device_info[1] == "device":
802 devices.append(device_info[0])
803
804 if len(devices) == 0:
805 raise its.error.Error("No device is connected!")
806 elif device_id is not None and device_id not in devices:
807 raise its.error.Error(device_id + " is not connected!")
808 elif device_id is None and len(devices) >= 2:
809 raise its.error.Error("More than 1 device are connected. " +
810 "Use device=<device_id> to specify a device to test.")
811 elif len(devices) == 1:
812 device_id = devices[0]
813
814 return device_id
815
Yin-Chia Yeh02eeffb2016-08-16 15:28:03 -0700816def report_result(device_id, camera_id, results):
Timothy Knighted076002014-10-23 16:12:26 -0700817 """Send a pass/fail result to the device, via an intent.
818
819 Args:
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700820 device_id: The ID string of the device to report the results to.
Timothy Knighted076002014-10-23 16:12:26 -0700821 camera_id: The ID string of the camera for which to report pass/fail.
Yin-Chia Yeh02eeffb2016-08-16 15:28:03 -0700822 results: a dictionary contains all ITS scenes as key and result/summary
823 of current ITS run. See test_report_result unit test for
824 an example.
Timothy Knighted076002014-10-23 16:12:26 -0700825 Returns:
826 Nothing.
827 """
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700828 adb = "adb -s " + device_id
Sam Linf2f66002017-02-27 21:08:11 -0800829
830 # Start ItsTestActivity to prevent flaky
831 cmd = "%s shell am start %s --activity-brought-to-front" % (adb, ItsSession.ITS_TEST_ACTIVITY)
832 _run(cmd)
833
Yin-Chia Yeh02eeffb2016-08-16 15:28:03 -0700834 # Validate/process results argument
835 for scene in results:
836 result_key = ItsSession.RESULT_KEY
837 summary_key = ItsSession.SUMMARY_KEY
838 if result_key not in results[scene]:
839 raise its.error.Error('ITS result not found for ' + scene)
840 if results[scene][result_key] not in ItsSession.RESULT_VALUES:
841 raise its.error.Error('Unknown ITS result for %s: %s' % (
842 scene, results[result_key]))
843 if summary_key in results[scene]:
844 device_summary_path = "/sdcard/its_camera%s_%s.txt" % (
845 camera_id, scene)
846 _run("%s push %s %s" % (
847 adb, results[scene][summary_key], device_summary_path))
848 results[scene][summary_key] = device_summary_path
Sam Linf2f66002017-02-27 21:08:11 -0800849
Yin-Chia Yeh02eeffb2016-08-16 15:28:03 -0700850 json_results = json.dumps(results)
851 cmd = "%s shell am broadcast -a %s --es %s %s --es %s %s --es %s \'%s\'" % (
852 adb, ItsSession.ACTION_ITS_RESULT,
853 ItsSession.EXTRA_VERSION, ItsSession.CURRENT_ITS_VERSION,
854 ItsSession.EXTRA_CAMERA_ID, camera_id,
855 ItsSession.EXTRA_RESULTS, json_results)
856 if len(cmd) > 4095:
857 print "ITS command string might be too long! len:", len(cmd)
858 _run(cmd)
Ruben Brunk370e2432014-10-14 18:33:23 -0700859
Sam Linf2f66002017-02-27 21:08:11 -0800860def get_device_fingerprint(device_id):
861 """ Return the Build FingerPrint of the device that the test is running on.
862
863 Returns:
864 Device Build Fingerprint string.
865 """
866 device_bfp = None
867
868 # Get a list of connected devices
869
870 com = ('adb -s %s shell getprop | grep ro.build.fingerprint' % device_id)
871 proc = subprocess.Popen(com.split(), stdout=subprocess.PIPE)
872 output, error = proc.communicate()
873 assert error is None
874
875 lst = string.split( \
876 string.replace( \
877 string.replace( \
878 string.replace(output,
879 '\n', ''), '[', ''), ']', ''), \
880 ' ')
881
882 if lst[0].find('ro.build.fingerprint') != -1:
883 device_bfp = lst[1]
884
885 return device_bfp
886
Ruben Brunk370e2432014-10-14 18:33:23 -0700887def _run(cmd):
888 """Replacement for os.system, with hiding of stdout+stderr messages.
889 """
890 with open(os.devnull, 'wb') as devnull:
891 subprocess.check_call(
892 cmd.split(), stdout=devnull, stderr=subprocess.STDOUT)
893
894class __UnitTest(unittest.TestCase):
895 """Run a suite of unit tests on this module.
896 """
897
Yin-Chia Yeh02eeffb2016-08-16 15:28:03 -0700898 """
899 # TODO: this test currently needs connected device to pass
900 # Need to remove that dependency before enabling the test
901 def test_report_result(self):
902 device_id = get_device_id()
903 camera_id = "1"
904 result_key = ItsSession.RESULT_KEY
905 results = {"scene0":{result_key:"PASS"},
906 "scene1":{result_key:"PASS"},
907 "scene2":{result_key:"PASS"},
908 "scene3":{result_key:"PASS"},
909 "sceneNotExist":{result_key:"FAIL"}}
910 report_result(device_id, camera_id, results)
911 """
Ruben Brunk370e2432014-10-14 18:33:23 -0700912
913if __name__ == '__main__':
914 unittest.main()
915