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