blob: 4f35b7aadec012c6f75c791e478d524395369218 [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
Shuzhen Wang80822a92018-01-29 09:27:42 -080028import unicodedata
Ruben Brunk370e2432014-10-14 18:33:23 -070029
Clemenz Portmanna2815fa2017-05-23 08:31:19 -070030CMD_DELAY = 1 # seconds
31
32
Ruben Brunk370e2432014-10-14 18:33:23 -070033class ItsSession(object):
34 """Controls a device over adb to run ITS scripts.
35
36 The script importing this module (on the host machine) prepares JSON
37 objects encoding CaptureRequests, specifying sets of parameters to use
Chien-Yu Chen682faa22014-10-22 17:34:44 -070038 when capturing an image using the Camera2 APIs. This class encapsulates
Ruben Brunk370e2432014-10-14 18:33:23 -070039 sending the requests to the device, monitoring the device's progress, and
40 copying the resultant captures back to the host machine when done. TCP
41 forwarded over adb is the transport mechanism used.
42
43 The device must have CtsVerifier.apk installed.
44
45 Attributes:
46 sock: The open socket.
47 """
48
Chien-Yu Chen1da23132015-07-22 15:24:41 -070049 # Open a connection to localhost:<host_port>, forwarded to port 6000 on the
50 # device. <host_port> is determined at run-time to support multiple
51 # connected devices.
Ruben Brunk370e2432014-10-14 18:33:23 -070052 IPADDR = '127.0.0.1'
Chien-Yu Chen1da23132015-07-22 15:24:41 -070053 REMOTE_PORT = 6000
Ruben Brunk370e2432014-10-14 18:33:23 -070054 BUFFER_SIZE = 4096
55
Chien-Yu Chen1da23132015-07-22 15:24:41 -070056 # LOCK_PORT is used as a mutex lock to protect the list of forwarded ports
57 # among all processes. The script assumes LOCK_PORT is available and will
58 # try to use ports between CLIENT_PORT_START and
59 # CLIENT_PORT_START+MAX_NUM_PORTS-1 on host for ITS sessions.
60 CLIENT_PORT_START = 6000
61 MAX_NUM_PORTS = 100
62 LOCK_PORT = CLIENT_PORT_START + MAX_NUM_PORTS
63
Ruben Brunk370e2432014-10-14 18:33:23 -070064 # Seconds timeout on each socket operation.
Yin-Chia Yehea1c1a62016-07-12 15:29:30 -070065 SOCK_TIMEOUT = 20.0
Yin-Chia Yehc9d554d2016-06-01 17:30:32 -070066 # Additional timeout in seconds when ITS service is doing more complicated
67 # operations, for example: issuing warmup requests before actual capture.
68 EXTRA_SOCK_TIMEOUT = 5.0
69
Yin-Chia Yeh39f8f4b2015-07-17 12:29:31 -070070 SEC_TO_NSEC = 1000*1000*1000.0
Ruben Brunk370e2432014-10-14 18:33:23 -070071
72 PACKAGE = 'com.android.cts.verifier.camera.its'
73 INTENT_START = 'com.android.cts.verifier.camera.its.START'
74 ACTION_ITS_RESULT = 'com.android.cts.verifier.camera.its.ACTION_ITS_RESULT'
Yin-Chia Yeh02eeffb2016-08-16 15:28:03 -070075 EXTRA_VERSION = 'camera.its.extra.VERSION'
76 CURRENT_ITS_VERSION = '1.0' # version number to sync with CtsVerifier
Yin-Chia Yehab98ada2015-03-05 13:28:53 -080077 EXTRA_CAMERA_ID = 'camera.its.extra.CAMERA_ID'
Yin-Chia Yeh02eeffb2016-08-16 15:28:03 -070078 EXTRA_RESULTS = 'camera.its.extra.RESULTS'
Sam Linf2f66002017-02-27 21:08:11 -080079 ITS_TEST_ACTIVITY = 'com.android.cts.verifier/.camera.its.ItsTestActivity'
Yin-Chia Yeh02eeffb2016-08-16 15:28:03 -070080
81 RESULT_PASS = 'PASS'
82 RESULT_FAIL = 'FAIL'
83 RESULT_NOT_EXECUTED = 'NOT_EXECUTED'
84 RESULT_VALUES = {RESULT_PASS, RESULT_FAIL, RESULT_NOT_EXECUTED}
85 RESULT_KEY = 'result'
86 SUMMARY_KEY = 'summary'
Ruben Brunk370e2432014-10-14 18:33:23 -070087
Chien-Yu Chen1da23132015-07-22 15:24:41 -070088 adb = "adb -d"
89 device_id = ""
Ruben Brunk370e2432014-10-14 18:33:23 -070090
91 # Definitions for some of the common output format options for do_capture().
92 # Each gets images of full resolution for each requested format.
93 CAP_RAW = {"format":"raw"}
94 CAP_DNG = {"format":"dng"}
95 CAP_YUV = {"format":"yuv"}
96 CAP_JPEG = {"format":"jpeg"}
97 CAP_RAW_YUV = [{"format":"raw"}, {"format":"yuv"}]
98 CAP_DNG_YUV = [{"format":"dng"}, {"format":"yuv"}]
99 CAP_RAW_JPEG = [{"format":"raw"}, {"format":"jpeg"}]
100 CAP_DNG_JPEG = [{"format":"dng"}, {"format":"jpeg"}]
101 CAP_YUV_JPEG = [{"format":"yuv"}, {"format":"jpeg"}]
102 CAP_RAW_YUV_JPEG = [{"format":"raw"}, {"format":"yuv"}, {"format":"jpeg"}]
103 CAP_DNG_YUV_JPEG = [{"format":"dng"}, {"format":"yuv"}, {"format":"jpeg"}]
104
Lu75f22fc2016-02-19 10:54:07 -0800105 # Predefine camera props. Save props extracted from the function,
106 # "get_camera_properties".
107 props = None
108
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700109 # Initialize the socket port for the host to forward requests to the device.
110 # This method assumes localhost's LOCK_PORT is available and will try to
111 # use ports between CLIENT_PORT_START and CLIENT_PORT_START+MAX_NUM_PORTS-1
112 def __init_socket_port(self):
113 NUM_RETRIES = 100
114 RETRY_WAIT_TIME_SEC = 0.05
Ruben Brunk370e2432014-10-14 18:33:23 -0700115
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700116 # Bind a socket to use as mutex lock
117 socket_lock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
118 for i in range(NUM_RETRIES):
119 try:
120 socket_lock.bind((ItsSession.IPADDR, ItsSession.LOCK_PORT))
121 break
Clemenz Portmannb0e76152018-02-01 09:12:57 -0800122 except socket.error or socket.timeout:
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700123 if i == NUM_RETRIES - 1:
124 raise its.error.Error(self.device_id,
Clemenz Portmannb0e76152018-02-01 09:12:57 -0800125 "socket lock returns error")
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700126 else:
127 time.sleep(RETRY_WAIT_TIME_SEC)
128
129 # Check if a port is already assigned to the device.
130 command = "adb forward --list"
131 proc = subprocess.Popen(command.split(), stdout=subprocess.PIPE)
132 output, error = proc.communicate()
133
134 port = None
135 used_ports = []
136 for line in output.split(os.linesep):
137 # each line should be formatted as:
138 # "<device_id> tcp:<host_port> tcp:<remote_port>"
139 forward_info = line.split()
140 if len(forward_info) >= 3 and \
141 len(forward_info[1]) > 4 and forward_info[1][:4] == "tcp:" and \
142 len(forward_info[2]) > 4 and forward_info[2][:4] == "tcp:":
143 local_p = int(forward_info[1][4:])
144 remote_p = int(forward_info[2][4:])
145 if forward_info[0] == self.device_id and \
146 remote_p == ItsSession.REMOTE_PORT:
147 port = local_p
148 break;
149 else:
150 used_ports.append(local_p)
151
152 # Find the first available port if no port is assigned to the device.
153 if port is None:
154 for p in range(ItsSession.CLIENT_PORT_START,
155 ItsSession.CLIENT_PORT_START +
156 ItsSession.MAX_NUM_PORTS):
157 if p not in used_ports:
158 # Try to run "adb forward" with the port
159 command = "%s forward tcp:%d tcp:%d" % \
160 (self.adb, p, self.REMOTE_PORT)
161 proc = subprocess.Popen(command.split(),
162 stdout=subprocess.PIPE,
163 stderr=subprocess.PIPE)
164 output, error = proc.communicate()
165
166 # Check if there is no error
167 if error is None or error.find("error") < 0:
168 port = p
169 break
170
171 if port is None:
172 raise its.error.Error(self.device_id, " cannot find an available " +
173 "port")
174
175 # Release the socket as mutex unlock
176 socket_lock.close()
177
178 # Connect to the socket
179 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
180 self.sock.connect((self.IPADDR, port))
181 self.sock.settimeout(self.SOCK_TIMEOUT)
182
183 # Reboot the device if needed and wait for the service to be ready for
184 # connection.
185 def __wait_for_service(self):
Ruben Brunk370e2432014-10-14 18:33:23 -0700186 # This also includes the optional reboot handling: if the user
187 # provides a "reboot" or "reboot=N" arg, then reboot the device,
188 # waiting for N seconds (default 30) before returning.
189 for s in sys.argv[1:]:
190 if s[:6] == "reboot":
191 duration = 30
192 if len(s) > 7 and s[6] == "=":
193 duration = int(s[7:])
194 print "Rebooting device"
Lu75f22fc2016-02-19 10:54:07 -0800195 _run("%s reboot" % (self.adb))
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700196 _run("%s wait-for-device" % (self.adb))
Ruben Brunk370e2432014-10-14 18:33:23 -0700197 time.sleep(duration)
198 print "Reboot complete"
199
Yin-Chia Yeh23a7b422016-03-25 14:48:47 -0700200 # Flush logcat so following code won't be misled by previous
201 # 'ItsService ready' log.
202 _run('%s logcat -c' % (self.adb))
203 time.sleep(1)
204
Ruben Brunk370e2432014-10-14 18:33:23 -0700205 # TODO: Figure out why "--user 0" is needed, and fix the problem.
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700206 _run('%s shell am force-stop --user 0 %s' % (self.adb, self.PACKAGE))
Clemenz Portmanna2815fa2017-05-23 08:31:19 -0700207 _run(('%s shell am start --user 0 '
208 'com.android.cts.verifier/.camera.its.ItsTestActivity '
209 '--activity-brought-to-front') % self.adb)
210 time.sleep(CMD_DELAY)
Yin-Chia Yeh6bdae902018-01-02 15:17:13 -0800211 _run(('%s shell am start-foreground-service --user 0 -t text/plain '
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700212 '-a %s') % (self.adb, self.INTENT_START))
Ruben Brunk370e2432014-10-14 18:33:23 -0700213
214 # Wait until the socket is ready to accept a connection.
215 proc = subprocess.Popen(
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700216 self.adb.split() + ["logcat"],
Ruben Brunk370e2432014-10-14 18:33:23 -0700217 stdout=subprocess.PIPE)
218 logcat = proc.stdout
219 while True:
220 line = logcat.readline().strip()
221 if line.find('ItsService ready') >= 0:
222 break
223 proc.kill()
224
Ruben Brunk370e2432014-10-14 18:33:23 -0700225 def __init__(self):
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700226 # Initialize device id and adb command.
227 self.device_id = get_device_id()
228 self.adb = "adb -s " + self.device_id
229
230 self.__wait_for_service()
231 self.__init_socket_port()
232
Ruben Brunk370e2432014-10-14 18:33:23 -0700233 self.__close_camera()
234 self.__open_camera()
235
236 def __del__(self):
237 if hasattr(self, 'sock') and self.sock:
238 self.__close_camera()
239 self.sock.close()
240
241 def __enter__(self):
242 return self
243
244 def __exit__(self, type, value, traceback):
245 return False
246
247 def __read_response_from_socket(self):
248 # Read a line (newline-terminated) string serialization of JSON object.
249 chars = []
250 while len(chars) == 0 or chars[-1] != '\n':
251 ch = self.sock.recv(1)
252 if len(ch) == 0:
253 # Socket was probably closed; otherwise don't get empty strings
254 raise its.error.Error('Problem with socket on device side')
255 chars.append(ch)
256 line = ''.join(chars)
257 jobj = json.loads(line)
258 # Optionally read a binary buffer of a fixed size.
259 buf = None
260 if jobj.has_key("bufValueSize"):
261 n = jobj["bufValueSize"]
262 buf = bytearray(n)
263 view = memoryview(buf)
264 while n > 0:
265 nbytes = self.sock.recv_into(view, n)
266 view = view[nbytes:]
267 n -= nbytes
268 buf = numpy.frombuffer(buf, dtype=numpy.uint8)
269 return jobj, buf
270
271 def __open_camera(self):
272 # Get the camera ID to open as an argument.
273 camera_id = 0
274 for s in sys.argv[1:]:
275 if s[:7] == "camera=" and len(s) > 7:
276 camera_id = int(s[7:])
277 cmd = {"cmdName":"open", "cameraId":camera_id}
278 self.sock.send(json.dumps(cmd) + "\n")
279 data,_ = self.__read_response_from_socket()
280 if data['tag'] != 'cameraOpened':
281 raise its.error.Error('Invalid command response')
282
283 def __close_camera(self):
284 cmd = {"cmdName":"close"}
285 self.sock.send(json.dumps(cmd) + "\n")
286 data,_ = self.__read_response_from_socket()
287 if data['tag'] != 'cameraClosed':
288 raise its.error.Error('Invalid command response')
289
290 def do_vibrate(self, pattern):
291 """Cause the device to vibrate to a specific pattern.
292
293 Args:
294 pattern: Durations (ms) for which to turn on or off the vibrator.
295 The first value indicates the number of milliseconds to wait
296 before turning the vibrator on. The next value indicates the
297 number of milliseconds for which to keep the vibrator on
298 before turning it off. Subsequent values alternate between
299 durations in milliseconds to turn the vibrator off or to turn
300 the vibrator on.
301
302 Returns:
303 Nothing.
304 """
305 cmd = {}
306 cmd["cmdName"] = "doVibrate"
307 cmd["pattern"] = pattern
308 self.sock.send(json.dumps(cmd) + "\n")
309 data,_ = self.__read_response_from_socket()
310 if data['tag'] != 'vibrationStarted':
311 raise its.error.Error('Invalid command response')
312
313 def start_sensor_events(self):
314 """Start collecting sensor events on the device.
315
316 See get_sensor_events for more info.
317
318 Returns:
319 Nothing.
320 """
321 cmd = {}
322 cmd["cmdName"] = "startSensorEvents"
323 self.sock.send(json.dumps(cmd) + "\n")
324 data,_ = self.__read_response_from_socket()
325 if data['tag'] != 'sensorEventsStarted':
326 raise its.error.Error('Invalid command response')
327
328 def get_sensor_events(self):
329 """Get a trace of all sensor events on the device.
330
331 The trace starts when the start_sensor_events function is called. If
332 the test runs for a long time after this call, then the device's
333 internal memory can fill up. Calling get_sensor_events gets all events
334 from the device, and then stops the device from collecting events and
335 clears the internal buffer; to start again, the start_sensor_events
336 call must be used again.
337
338 Events from the accelerometer, compass, and gyro are returned; each
339 has a timestamp and x,y,z values.
340
341 Note that sensor events are only produced if the device isn't in its
342 standby mode (i.e.) if the screen is on.
343
344 Returns:
345 A Python dictionary with three keys ("accel", "mag", "gyro") each
346 of which maps to a list of objects containing "time","x","y","z"
347 keys.
348 """
349 cmd = {}
350 cmd["cmdName"] = "getSensorEvents"
351 self.sock.send(json.dumps(cmd) + "\n")
Yin-Chia Yehf16d6872016-06-06 13:54:42 -0700352 timeout = self.SOCK_TIMEOUT + self.EXTRA_SOCK_TIMEOUT
353 self.sock.settimeout(timeout)
Ruben Brunk370e2432014-10-14 18:33:23 -0700354 data,_ = self.__read_response_from_socket()
355 if data['tag'] != 'sensorEvents':
356 raise its.error.Error('Invalid command response')
Yin-Chia Yehf16d6872016-06-06 13:54:42 -0700357 self.sock.settimeout(self.SOCK_TIMEOUT)
Ruben Brunk370e2432014-10-14 18:33:23 -0700358 return data['objValue']
359
Yin-Chia Yehab98ada2015-03-05 13:28:53 -0800360 def get_camera_ids(self):
361 """Get a list of camera device Ids that can be opened.
362
363 Returns:
364 a list of camera ID string
365 """
366 cmd = {}
367 cmd["cmdName"] = "getCameraIds"
368 self.sock.send(json.dumps(cmd) + "\n")
369 data,_ = self.__read_response_from_socket()
370 if data['tag'] != 'cameraIds':
371 raise its.error.Error('Invalid command response')
372 return data['objValue']['cameraIdArray']
373
Ruben Brunk370e2432014-10-14 18:33:23 -0700374 def get_camera_properties(self):
375 """Get the camera properties object for the device.
376
377 Returns:
378 The Python dictionary object for the CameraProperties object.
379 """
380 cmd = {}
381 cmd["cmdName"] = "getCameraProperties"
382 self.sock.send(json.dumps(cmd) + "\n")
383 data,_ = self.__read_response_from_socket()
384 if data['tag'] != 'cameraProperties':
385 raise its.error.Error('Invalid command response')
Lu75f22fc2016-02-19 10:54:07 -0800386 self.props = data['objValue']['cameraProperties']
Ruben Brunk370e2432014-10-14 18:33:23 -0700387 return data['objValue']['cameraProperties']
388
389 def do_3a(self, regions_ae=[[0,0,1,1,1]],
390 regions_awb=[[0,0,1,1,1]],
391 regions_af=[[0,0,1,1,1]],
392 do_ae=True, do_awb=True, do_af=True,
393 lock_ae=False, lock_awb=False,
Zhijun Heeb3ff472014-11-20 13:47:11 -0800394 get_results=False,
395 ev_comp=0):
Ruben Brunk370e2432014-10-14 18:33:23 -0700396 """Perform a 3A operation on the device.
397
398 Triggers some or all of AE, AWB, and AF, and returns once they have
399 converged. Uses the vendor 3A that is implemented inside the HAL.
Clemenz Portmann3c97e772017-03-23 13:22:06 -0700400 Note: do_awb is always enabled regardless of do_awb flag
Ruben Brunk370e2432014-10-14 18:33:23 -0700401
402 Throws an assertion if 3A fails to converge.
403
404 Args:
405 regions_ae: List of weighted AE regions.
406 regions_awb: List of weighted AWB regions.
407 regions_af: List of weighted AF regions.
408 do_ae: Trigger AE and wait for it to converge.
409 do_awb: Wait for AWB to converge.
410 do_af: Trigger AF and wait for it to converge.
411 lock_ae: Request AE lock after convergence, and wait for it.
412 lock_awb: Request AWB lock after convergence, and wait for it.
413 get_results: Return the 3A results from this function.
Zhijun Heeb3ff472014-11-20 13:47:11 -0800414 ev_comp: An EV compensation value to use when running AE.
Ruben Brunk370e2432014-10-14 18:33:23 -0700415
416 Region format in args:
417 Arguments are lists of weighted regions; each weighted region is a
418 list of 5 values, [x,y,w,h, wgt], and each argument is a list of
419 these 5-value lists. The coordinates are given as normalized
420 rectangles (x,y,w,h) specifying the region. For example:
421 [[0.0, 0.0, 1.0, 0.5, 5], [0.0, 0.5, 1.0, 0.5, 10]].
422 Weights are non-negative integers.
423
424 Returns:
425 Five values are returned if get_results is true::
426 * AE sensitivity; None if do_ae is False
427 * AE exposure time; None if do_ae is False
Clemenz Portmann3c97e772017-03-23 13:22:06 -0700428 * AWB gains (list);
429 * AWB transform (list);
Ruben Brunk370e2432014-10-14 18:33:23 -0700430 * AF focus position; None if do_af is false
431 Otherwise, it returns five None values.
432 """
433 print "Running vendor 3A on device"
434 cmd = {}
435 cmd["cmdName"] = "do3A"
436 cmd["regions"] = {"ae": sum(regions_ae, []),
437 "awb": sum(regions_awb, []),
438 "af": sum(regions_af, [])}
439 cmd["triggers"] = {"ae": do_ae, "af": do_af}
440 if lock_ae:
441 cmd["aeLock"] = True
442 if lock_awb:
443 cmd["awbLock"] = True
Zhijun Heeb3ff472014-11-20 13:47:11 -0800444 if ev_comp != 0:
445 cmd["evComp"] = ev_comp
Ruben Brunk370e2432014-10-14 18:33:23 -0700446 self.sock.send(json.dumps(cmd) + "\n")
447
448 # Wait for each specified 3A to converge.
449 ae_sens = None
450 ae_exp = None
451 awb_gains = None
452 awb_transform = None
453 af_dist = None
454 converged = False
455 while True:
456 data,_ = self.__read_response_from_socket()
457 vals = data['strValue'].split()
458 if data['tag'] == 'aeResult':
Clemenz Portmann3c97e772017-03-23 13:22:06 -0700459 if do_ae:
460 ae_sens, ae_exp = [int(i) for i in vals]
Ruben Brunk370e2432014-10-14 18:33:23 -0700461 elif data['tag'] == 'afResult':
Clemenz Portmann3c97e772017-03-23 13:22:06 -0700462 if do_af:
463 af_dist = float(vals[0])
Ruben Brunk370e2432014-10-14 18:33:23 -0700464 elif data['tag'] == 'awbResult':
465 awb_gains = [float(f) for f in vals[:4]]
466 awb_transform = [float(f) for f in vals[4:]]
467 elif data['tag'] == '3aConverged':
468 converged = True
469 elif data['tag'] == '3aDone':
470 break
471 else:
472 raise its.error.Error('Invalid command response')
473 if converged and not get_results:
474 return None,None,None,None,None
475 if (do_ae and ae_sens == None or do_awb and awb_gains == None
476 or do_af and af_dist == None or not converged):
477 raise its.error.Error('3A failed to converge')
478 return ae_sens, ae_exp, awb_gains, awb_transform, af_dist
479
Yin-Chia Yeh9e5d9ac2016-03-30 12:04:12 -0700480 def do_capture(self, cap_request,
481 out_surfaces=None, reprocess_format=None, repeat_request=None):
Ruben Brunk370e2432014-10-14 18:33:23 -0700482 """Issue capture request(s), and read back the image(s) and metadata.
483
484 The main top-level function for capturing one or more images using the
485 device. Captures a single image if cap_request is a single object, and
486 captures a burst if it is a list of objects.
487
Yin-Chia Yeh9e5d9ac2016-03-30 12:04:12 -0700488 The optional repeat_request field can be used to assign a repeating
489 request list ran in background for 3 seconds to warm up the capturing
490 pipeline before start capturing. The repeat_requests will be ran on a
491 640x480 YUV surface without sending any data back. The caller needs to
492 make sure the stream configuration defined by out_surfaces and
493 repeat_request are valid or do_capture may fail because device does not
494 support such stream configuration.
495
Ruben Brunk370e2432014-10-14 18:33:23 -0700496 The out_surfaces field can specify the width(s), height(s), and
497 format(s) of the captured image. The formats may be "yuv", "jpeg",
Timothy Knight67d8ec92015-08-31 13:14:46 -0700498 "dng", "raw", "raw10", "raw12", or "rawStats". The default is a YUV420
499 frame ("yuv") corresponding to a full sensor frame.
Ruben Brunk370e2432014-10-14 18:33:23 -0700500
Shuzhen Wang80822a92018-01-29 09:27:42 -0800501 Optionally the out_surfaces field can specify physical camera id(s) if the
502 current camera device is a logical multi-camera. The physical camera id
503 must refer to a physical camera backing this logical camera device. And
504 only "yuv", "raw", "raw10", "raw12" support the physical camera id field.
505
506 Currently only 2 physical streams with the same format are supported, one
507 from each physical camera:
508 - yuv physical streams of the same size.
509 - raw physical streams with the same or different sizes, depending on
510 device capability. (Different physical cameras may have different raw sizes).
511
Ruben Brunk370e2432014-10-14 18:33:23 -0700512 Note that one or more surfaces can be specified, allowing a capture to
513 request images back in multiple formats (e.g.) raw+yuv, raw+jpeg,
514 yuv+jpeg, raw+yuv+jpeg. If the size is omitted for a surface, the
515 default is the largest resolution available for the format of that
516 surface. At most one output surface can be specified for a given format,
517 and raw+dng, raw10+dng, and raw+raw10 are not supported as combinations.
518
Chien-Yu Chen4e1e2cc2015-06-08 17:46:52 -0700519 If reprocess_format is not None, for each request, an intermediate
520 buffer of the given reprocess_format will be captured from camera and
521 the intermediate buffer will be reprocessed to the output surfaces. The
522 following settings will be turned off when capturing the intermediate
523 buffer and will be applied when reprocessing the intermediate buffer.
524 1. android.noiseReduction.mode
525 2. android.edge.mode
526 3. android.reprocess.effectiveExposureFactor
527
528 Supported reprocess format are "yuv" and "private". Supported output
529 surface formats when reprocessing is enabled are "yuv" and "jpeg".
530
Ruben Brunk370e2432014-10-14 18:33:23 -0700531 Example of a single capture request:
532
533 {
534 "android.sensor.exposureTime": 100*1000*1000,
535 "android.sensor.sensitivity": 100
536 }
537
538 Example of a list of capture requests:
539
540 [
541 {
542 "android.sensor.exposureTime": 100*1000*1000,
543 "android.sensor.sensitivity": 100
544 },
545 {
546 "android.sensor.exposureTime": 100*1000*1000,
547 "android.sensor.sensitivity": 200
548 }
549 ]
550
551 Examples of output surface specifications:
552
553 {
554 "width": 640,
555 "height": 480,
556 "format": "yuv"
557 }
558
559 [
560 {
561 "format": "jpeg"
562 },
563 {
564 "format": "raw"
565 }
566 ]
567
568 The following variables defined in this class are shortcuts for
569 specifying one or more formats where each output is the full size for
570 that format; they can be used as values for the out_surfaces arguments:
571
572 CAP_RAW
573 CAP_DNG
574 CAP_YUV
575 CAP_JPEG
576 CAP_RAW_YUV
577 CAP_DNG_YUV
578 CAP_RAW_JPEG
579 CAP_DNG_JPEG
580 CAP_YUV_JPEG
581 CAP_RAW_YUV_JPEG
582 CAP_DNG_YUV_JPEG
583
Chien-Yu Chen682faa22014-10-22 17:34:44 -0700584 If multiple formats are specified, then this function returns multiple
Ruben Brunk370e2432014-10-14 18:33:23 -0700585 capture objects, one for each requested format. If multiple formats and
586 multiple captures (i.e. a burst) are specified, then this function
587 returns multiple lists of capture objects. In both cases, the order of
588 the returned objects matches the order of the requested formats in the
589 out_surfaces parameter. For example:
590
591 yuv_cap = do_capture( req1 )
592 yuv_cap = do_capture( req1, yuv_fmt )
593 yuv_cap, raw_cap = do_capture( req1, [yuv_fmt,raw_fmt] )
594 yuv_caps = do_capture( [req1,req2], yuv_fmt )
595 yuv_caps, raw_caps = do_capture( [req1,req2], [yuv_fmt,raw_fmt] )
596
Timothy Knight67d8ec92015-08-31 13:14:46 -0700597 The "rawStats" format processes the raw image and returns a new image
598 of statistics from the raw image. The format takes additional keys,
599 "gridWidth" and "gridHeight" which are size of grid cells in a 2D grid
600 of the raw image. For each grid cell, the mean and variance of each raw
601 channel is computed, and the do_capture call returns two 4-element float
602 images of dimensions (rawWidth / gridWidth, rawHeight / gridHeight),
603 concatenated back-to-back, where the first iamge contains the 4-channel
Timothy Knightff3032b2017-02-01 15:10:51 -0800604 means and the second contains the 4-channel variances. Note that only
605 pixels in the active array crop region are used; pixels outside this
606 region (for example optical black rows) are cropped out before the
607 gridding and statistics computation is performed.
Timothy Knight67d8ec92015-08-31 13:14:46 -0700608
609 For the rawStats format, if the gridWidth is not provided then the raw
610 image width is used as the default, and similarly for gridHeight. With
611 this, the following is an example of a output description that computes
612 the mean and variance across each image row:
613
614 {
615 "gridHeight": 1,
616 "format": "rawStats"
617 }
618
Ruben Brunk370e2432014-10-14 18:33:23 -0700619 Args:
620 cap_request: The Python dict/list specifying the capture(s), which
621 will be converted to JSON and sent to the device.
622 out_surfaces: (Optional) specifications of the output image formats
623 and sizes to use for each capture.
Chien-Yu Chen4e1e2cc2015-06-08 17:46:52 -0700624 reprocess_format: (Optional) The reprocessing format. If not None,
625 reprocessing will be enabled.
Ruben Brunk370e2432014-10-14 18:33:23 -0700626
627 Returns:
628 An object, list of objects, or list of lists of objects, where each
629 object contains the following fields:
630 * data: the image data as a numpy array of bytes.
631 * width: the width of the captured image.
632 * height: the height of the captured image.
Timothy Knight67d8ec92015-08-31 13:14:46 -0700633 * format: image the format, in [
634 "yuv","jpeg","raw","raw10","raw12","rawStats","dng"].
Chien-Yu Chen682faa22014-10-22 17:34:44 -0700635 * metadata: the capture result object (Python dictionary).
Ruben Brunk370e2432014-10-14 18:33:23 -0700636 """
637 cmd = {}
Chien-Yu Chen4e1e2cc2015-06-08 17:46:52 -0700638 if reprocess_format != None:
639 cmd["cmdName"] = "doReprocessCapture"
640 cmd["reprocessFormat"] = reprocess_format
641 else:
642 cmd["cmdName"] = "doCapture"
Yin-Chia Yeh9e5d9ac2016-03-30 12:04:12 -0700643
644 if repeat_request is not None and reprocess_format is not None:
645 raise its.error.Error('repeating request + reprocessing is not supported')
646
647 if repeat_request is None:
648 cmd["repeatRequests"] = []
649 elif not isinstance(repeat_request, list):
650 cmd["repeatRequests"] = [repeat_request]
651 else:
652 cmd["repeatRequests"] = repeat_request
653
Ruben Brunk370e2432014-10-14 18:33:23 -0700654 if not isinstance(cap_request, list):
655 cmd["captureRequests"] = [cap_request]
656 else:
657 cmd["captureRequests"] = cap_request
658 if out_surfaces is not None:
659 if not isinstance(out_surfaces, list):
660 cmd["outputSurfaces"] = [out_surfaces]
661 else:
662 cmd["outputSurfaces"] = out_surfaces
Lu75f22fc2016-02-19 10:54:07 -0800663 formats = [c["format"] if "format" in c else "yuv"
Ruben Brunk370e2432014-10-14 18:33:23 -0700664 for c in cmd["outputSurfaces"]]
665 formats = [s if s != "jpg" else "jpeg" for s in formats]
666 else:
Yin-Chia Yeh409f2e92016-03-16 18:55:31 -0700667 max_yuv_size = its.objects.get_available_output_sizes(
668 "yuv", self.props)[0]
Ruben Brunk370e2432014-10-14 18:33:23 -0700669 formats = ['yuv']
Yin-Chia Yeh409f2e92016-03-16 18:55:31 -0700670 cmd["outputSurfaces"] = [{"format": "yuv",
671 "width" : max_yuv_size[0],
672 "height": max_yuv_size[1]}]
Shuzhen Wang80822a92018-01-29 09:27:42 -0800673
674 # Figure out requested physical camera ids, physical and logical
675 # streams.
676 physical_cam_ids = {}
677 physical_buffers = {}
678 physical_cam_format = None
679 logical_cam_formats = []
680 for i,s in enumerate(cmd["outputSurfaces"]):
681 if "format" in s and s["format"] in ["yuv", "raw", "raw10", "raw12"]:
682 if "physicalCamera" in s:
683 if physical_cam_format is not None and s["format"] != physical_cam_format:
684 raise its.error.Error('ITS does not support capturing multiple ' +
685 'physical formats yet')
686 physical_cam_ids[i] = s["physicalCamera"]
687 physical_buffers[s["physicalCamera"]] = []
688 physical_cam_format = s["format"]
689 else:
690 logical_cam_formats.append(s["format"])
691 else:
692 logical_cam_formats.append(s["format"])
693
Ruben Brunk370e2432014-10-14 18:33:23 -0700694 ncap = len(cmd["captureRequests"])
695 nsurf = 1 if out_surfaces is None else len(cmd["outputSurfaces"])
Lu7c6f52e2015-12-15 14:42:18 -0800696 # Only allow yuv output to multiple targets
Shuzhen Wang80822a92018-01-29 09:27:42 -0800697 logical_yuv_surfaces = [s for s in cmd["outputSurfaces"] if s["format"]=="yuv"\
698 and "physicalCamera" not in s]
699 n_yuv = len(logical_yuv_surfaces)
Lu7c6f52e2015-12-15 14:42:18 -0800700 # Compute the buffer size of YUV targets
Lu75f22fc2016-02-19 10:54:07 -0800701 yuv_maxsize_1d = 0
Shuzhen Wang80822a92018-01-29 09:27:42 -0800702 for s in logical_yuv_surfaces:
Lu75f22fc2016-02-19 10:54:07 -0800703 if not ("width" in s and "height" in s):
704 if self.props is None:
705 raise its.error.Error('Camera props are unavailable')
706 yuv_maxsize_2d = its.objects.get_available_output_sizes(
707 "yuv", self.props)[0]
708 yuv_maxsize_1d = yuv_maxsize_2d[0] * yuv_maxsize_2d[1] * 3 / 2
709 break
710 yuv_sizes = [c["width"]*c["height"]*3/2
711 if "width" in c and "height" in c
712 else yuv_maxsize_1d
Shuzhen Wang80822a92018-01-29 09:27:42 -0800713 for c in logical_yuv_surfaces]
Lu7c6f52e2015-12-15 14:42:18 -0800714 # Currently we don't pass enough metadta from ItsService to distinguish
715 # different yuv stream of same buffer size
716 if len(yuv_sizes) != len(set(yuv_sizes)):
717 raise its.error.Error(
718 'ITS does not support yuv outputs of same buffer size')
Shuzhen Wang80822a92018-01-29 09:27:42 -0800719 if len(logical_cam_formats) > len(set(logical_cam_formats)):
720 if n_yuv != len(logical_cam_formats) - len(set(logical_cam_formats)) + 1:
Lu7c6f52e2015-12-15 14:42:18 -0800721 raise its.error.Error('Duplicate format requested')
722
Timothy Knight67d8ec92015-08-31 13:14:46 -0700723 raw_formats = 0;
724 raw_formats += 1 if "dng" in formats else 0
725 raw_formats += 1 if "raw" in formats else 0
726 raw_formats += 1 if "raw10" in formats else 0
727 raw_formats += 1 if "raw12" in formats else 0
728 raw_formats += 1 if "rawStats" in formats else 0
729 if raw_formats > 1:
Ruben Brunk370e2432014-10-14 18:33:23 -0700730 raise its.error.Error('Different raw formats not supported')
Yin-Chia Yeh39f8f4b2015-07-17 12:29:31 -0700731
732 # Detect long exposure time and set timeout accordingly
733 longest_exp_time = 0
734 for req in cmd["captureRequests"]:
735 if "android.sensor.exposureTime" in req and \
736 req["android.sensor.exposureTime"] > longest_exp_time:
737 longest_exp_time = req["android.sensor.exposureTime"]
738
739 extended_timeout = longest_exp_time / self.SEC_TO_NSEC + \
740 self.SOCK_TIMEOUT
Yin-Chia Yehc9d554d2016-06-01 17:30:32 -0700741 if repeat_request:
742 extended_timeout += self.EXTRA_SOCK_TIMEOUT
Yin-Chia Yeh39f8f4b2015-07-17 12:29:31 -0700743 self.sock.settimeout(extended_timeout)
744
Ruben Brunk370e2432014-10-14 18:33:23 -0700745 print "Capturing %d frame%s with %d format%s [%s]" % (
746 ncap, "s" if ncap>1 else "", nsurf, "s" if nsurf>1 else "",
747 ",".join(formats))
748 self.sock.send(json.dumps(cmd) + "\n")
749
750 # Wait for ncap*nsurf images and ncap metadata responses.
751 # Assume that captures come out in the same order as requested in
Chien-Yu Chen682faa22014-10-22 17:34:44 -0700752 # the burst, however individual images of different formats can come
Ruben Brunk370e2432014-10-14 18:33:23 -0700753 # out in any order for that capture.
754 nbufs = 0
Lu7c6f52e2015-12-15 14:42:18 -0800755 bufs = {"raw":[], "raw10":[], "raw12":[],
Timothy Knight67d8ec92015-08-31 13:14:46 -0700756 "rawStats":[], "dng":[], "jpeg":[]}
Lu7c6f52e2015-12-15 14:42:18 -0800757 yuv_bufs = {size:[] for size in yuv_sizes}
Ruben Brunk370e2432014-10-14 18:33:23 -0700758 mds = []
Shuzhen Wang96ecf752018-01-30 10:33:31 -0800759 physical_mds = []
Ruben Brunk370e2432014-10-14 18:33:23 -0700760 widths = None
761 heights = None
762 while nbufs < ncap*nsurf or len(mds) < ncap:
763 jsonObj,buf = self.__read_response_from_socket()
Lu7c6f52e2015-12-15 14:42:18 -0800764 if jsonObj['tag'] in ['jpegImage', 'rawImage', \
Timothy Knight67d8ec92015-08-31 13:14:46 -0700765 'raw10Image', 'raw12Image', 'rawStatsImage', 'dngImage'] \
766 and buf is not None:
Ruben Brunk370e2432014-10-14 18:33:23 -0700767 fmt = jsonObj['tag'][:-5]
768 bufs[fmt].append(buf)
769 nbufs += 1
Lu7c6f52e2015-12-15 14:42:18 -0800770 elif jsonObj['tag'] == 'yuvImage':
771 buf_size = numpy.product(buf.shape)
772 yuv_bufs[buf_size].append(buf)
773 nbufs += 1
Ruben Brunk370e2432014-10-14 18:33:23 -0700774 elif jsonObj['tag'] == 'captureResults':
775 mds.append(jsonObj['objValue']['captureResult'])
Shuzhen Wang96ecf752018-01-30 10:33:31 -0800776 physical_mds.append(jsonObj['objValue']['physicalResults'])
Ruben Brunk370e2432014-10-14 18:33:23 -0700777 outputs = jsonObj['objValue']['outputs']
778 widths = [out['width'] for out in outputs]
779 heights = [out['height'] for out in outputs]
780 else:
Shuzhen Wang80822a92018-01-29 09:27:42 -0800781 tagString = unicodedata.normalize('NFKD', jsonObj['tag']).encode('ascii', 'ignore');
782 for x in ['rawImage', 'raw10Image', 'raw12Image', 'yuvImage']:
783 if (tagString.startswith(x)):
784 physicalId = jsonObj['tag'][len(x):];
785 if physicalId in physical_cam_ids.values():
786 physical_buffers[physicalId].append(buf)
787 nbufs += 1
Ruben Brunk370e2432014-10-14 18:33:23 -0700788 rets = []
789 for j,fmt in enumerate(formats):
790 objs = []
791 for i in range(ncap):
792 obj = {}
Ruben Brunk370e2432014-10-14 18:33:23 -0700793 obj["width"] = widths[j]
794 obj["height"] = heights[j]
795 obj["format"] = fmt
Shuzhen Wang96ecf752018-01-30 10:33:31 -0800796 if j in physical_cam_ids:
797 for physical_md in physical_mds[i]:
798 if physical_cam_ids[j] in physical_md:
799 obj["metadata"] = physical_md[physical_cam_ids[j]]
800 break
801 else:
802 obj["metadata"] = mds[i]
Shuzhen Wang80822a92018-01-29 09:27:42 -0800803
804 if j in physical_cam_ids:
805 obj["data"] = physical_buffers[physical_cam_ids[j]][i]
806 elif fmt == 'yuv':
Lu7c6f52e2015-12-15 14:42:18 -0800807 buf_size = widths[j] * heights[j] * 3 / 2
808 obj["data"] = yuv_bufs[buf_size][i]
809 else:
810 obj["data"] = bufs[fmt][i]
Ruben Brunk370e2432014-10-14 18:33:23 -0700811 objs.append(obj)
812 rets.append(objs if ncap>1 else objs[0])
Yin-Chia Yeh39f8f4b2015-07-17 12:29:31 -0700813 self.sock.settimeout(self.SOCK_TIMEOUT)
Ruben Brunk370e2432014-10-14 18:33:23 -0700814 return rets if len(rets)>1 else rets[0]
815
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700816def get_device_id():
817 """ Return the ID of the device that the test is running on.
818
819 Return the device ID provided in the command line if it's connected. If no
820 device ID is provided in the command line and there is only one device
821 connected, return the device ID by parsing the result of "adb devices".
Clemenz Portmannf7f23ee2016-08-18 13:00:59 -0700822 Also, if the environment variable ANDROID_SERIAL is set, use it as device
823 id. When both ANDROID_SERIAL and device argument present, device argument
824 takes priority.
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700825
826 Raise an exception if no device is connected; or the device ID provided in
827 the command line is not connected; or no device ID is provided in the
Clemenz Portmannf7f23ee2016-08-18 13:00:59 -0700828 command line or environment variable and there are more than 1 device
829 connected.
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700830
831 Returns:
832 Device ID string.
833 """
834 device_id = None
Clemenz Portmannf7f23ee2016-08-18 13:00:59 -0700835
836 # Check if device id is set in env
837 if "ANDROID_SERIAL" in os.environ:
838 device_id = os.environ["ANDROID_SERIAL"]
839
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700840 for s in sys.argv[1:]:
841 if s[:7] == "device=" and len(s) > 7:
842 device_id = str(s[7:])
843
844 # Get a list of connected devices
845 devices = []
846 command = "adb devices"
847 proc = subprocess.Popen(command.split(), stdout=subprocess.PIPE)
848 output, error = proc.communicate()
849 for line in output.split(os.linesep):
850 device_info = line.split()
851 if len(device_info) == 2 and device_info[1] == "device":
852 devices.append(device_info[0])
853
854 if len(devices) == 0:
855 raise its.error.Error("No device is connected!")
856 elif device_id is not None and device_id not in devices:
857 raise its.error.Error(device_id + " is not connected!")
858 elif device_id is None and len(devices) >= 2:
859 raise its.error.Error("More than 1 device are connected. " +
860 "Use device=<device_id> to specify a device to test.")
861 elif len(devices) == 1:
862 device_id = devices[0]
863
864 return device_id
865
Yin-Chia Yeh02eeffb2016-08-16 15:28:03 -0700866def report_result(device_id, camera_id, results):
Timothy Knighted076002014-10-23 16:12:26 -0700867 """Send a pass/fail result to the device, via an intent.
868
869 Args:
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700870 device_id: The ID string of the device to report the results to.
Timothy Knighted076002014-10-23 16:12:26 -0700871 camera_id: The ID string of the camera for which to report pass/fail.
Yin-Chia Yeh02eeffb2016-08-16 15:28:03 -0700872 results: a dictionary contains all ITS scenes as key and result/summary
873 of current ITS run. See test_report_result unit test for
874 an example.
Timothy Knighted076002014-10-23 16:12:26 -0700875 Returns:
876 Nothing.
877 """
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700878 adb = "adb -s " + device_id
Sam Linf2f66002017-02-27 21:08:11 -0800879
880 # Start ItsTestActivity to prevent flaky
881 cmd = "%s shell am start %s --activity-brought-to-front" % (adb, ItsSession.ITS_TEST_ACTIVITY)
882 _run(cmd)
883
Yin-Chia Yeh02eeffb2016-08-16 15:28:03 -0700884 # Validate/process results argument
885 for scene in results:
886 result_key = ItsSession.RESULT_KEY
887 summary_key = ItsSession.SUMMARY_KEY
888 if result_key not in results[scene]:
889 raise its.error.Error('ITS result not found for ' + scene)
890 if results[scene][result_key] not in ItsSession.RESULT_VALUES:
891 raise its.error.Error('Unknown ITS result for %s: %s' % (
892 scene, results[result_key]))
893 if summary_key in results[scene]:
894 device_summary_path = "/sdcard/its_camera%s_%s.txt" % (
895 camera_id, scene)
896 _run("%s push %s %s" % (
897 adb, results[scene][summary_key], device_summary_path))
898 results[scene][summary_key] = device_summary_path
Sam Linf2f66002017-02-27 21:08:11 -0800899
Yin-Chia Yeh02eeffb2016-08-16 15:28:03 -0700900 json_results = json.dumps(results)
901 cmd = "%s shell am broadcast -a %s --es %s %s --es %s %s --es %s \'%s\'" % (
902 adb, ItsSession.ACTION_ITS_RESULT,
903 ItsSession.EXTRA_VERSION, ItsSession.CURRENT_ITS_VERSION,
904 ItsSession.EXTRA_CAMERA_ID, camera_id,
905 ItsSession.EXTRA_RESULTS, json_results)
906 if len(cmd) > 4095:
907 print "ITS command string might be too long! len:", len(cmd)
908 _run(cmd)
Ruben Brunk370e2432014-10-14 18:33:23 -0700909
Sam Linf2f66002017-02-27 21:08:11 -0800910def get_device_fingerprint(device_id):
911 """ Return the Build FingerPrint of the device that the test is running on.
912
913 Returns:
914 Device Build Fingerprint string.
915 """
916 device_bfp = None
917
918 # Get a list of connected devices
919
920 com = ('adb -s %s shell getprop | grep ro.build.fingerprint' % device_id)
921 proc = subprocess.Popen(com.split(), stdout=subprocess.PIPE)
922 output, error = proc.communicate()
923 assert error is None
924
925 lst = string.split( \
926 string.replace( \
927 string.replace( \
928 string.replace(output,
929 '\n', ''), '[', ''), ']', ''), \
930 ' ')
931
932 if lst[0].find('ro.build.fingerprint') != -1:
933 device_bfp = lst[1]
934
935 return device_bfp
936
Ruben Brunk370e2432014-10-14 18:33:23 -0700937def _run(cmd):
938 """Replacement for os.system, with hiding of stdout+stderr messages.
939 """
940 with open(os.devnull, 'wb') as devnull:
941 subprocess.check_call(
942 cmd.split(), stdout=devnull, stderr=subprocess.STDOUT)
943
944class __UnitTest(unittest.TestCase):
945 """Run a suite of unit tests on this module.
946 """
947
Yin-Chia Yeh02eeffb2016-08-16 15:28:03 -0700948 """
949 # TODO: this test currently needs connected device to pass
950 # Need to remove that dependency before enabling the test
951 def test_report_result(self):
952 device_id = get_device_id()
953 camera_id = "1"
954 result_key = ItsSession.RESULT_KEY
955 results = {"scene0":{result_key:"PASS"},
956 "scene1":{result_key:"PASS"},
957 "scene2":{result_key:"PASS"},
958 "scene3":{result_key:"PASS"},
959 "sceneNotExist":{result_key:"FAIL"}}
960 report_result(device_id, camera_id, results)
961 """
Ruben Brunk370e2432014-10-14 18:33:23 -0700962
963if __name__ == '__main__':
964 unittest.main()
965