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