1 /*
2  * Copyright (C) 2013 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.cts.verifier.camera.its;
18 
19 import android.content.Context;
20 import android.graphics.ImageFormat;
21 import android.graphics.Rect;
22 import android.hardware.camera2.CameraAccessException;
23 import android.hardware.camera2.CameraDevice;
24 import android.hardware.camera2.CameraCharacteristics;
25 import android.hardware.camera2.CameraManager;
26 import android.hardware.camera2.CaptureRequest;
27 import android.hardware.camera2.CaptureResult;
28 import android.hardware.camera2.params.MeteringRectangle;
29 import android.hardware.camera2.params.StreamConfigurationMap;
30 import android.media.Image;
31 import android.media.Image.Plane;
32 import android.net.Uri;
33 import android.os.Environment;
34 import android.os.Handler;
35 import android.os.HandlerThread;
36 import android.util.Log;
37 import android.util.Size;
38 
39 import com.android.ex.camera2.blocking.BlockingCameraManager;
40 import com.android.ex.camera2.blocking.BlockingCameraManager.BlockingOpenException;
41 import com.android.ex.camera2.blocking.BlockingStateCallback;
42 
43 import org.json.JSONArray;
44 import org.json.JSONObject;
45 
46 import java.nio.ByteBuffer;
47 import java.nio.charset.Charset;
48 import java.util.ArrayList;
49 import java.util.Arrays;
50 import java.util.concurrent.Semaphore;
51 import java.util.List;
52 import java.util.Set;
53 
54 
55 public class ItsUtils {
56     public static final String TAG = ItsUtils.class.getSimpleName();
57     // The tokenizer must be the same as CAMERA_ID_TOKENIZER in device.py
58     public static final String CAMERA_ID_TOKENIZER = ".";
59 
jsonToByteBuffer(JSONObject jsonObj)60     public static ByteBuffer jsonToByteBuffer(JSONObject jsonObj) {
61         return ByteBuffer.wrap(jsonObj.toString().getBytes(Charset.defaultCharset()));
62     }
63 
getJsonWeightedRectsFromArray( JSONArray a, boolean normalized, int width, int height)64     public static MeteringRectangle[] getJsonWeightedRectsFromArray(
65             JSONArray a, boolean normalized, int width, int height)
66             throws ItsException {
67         try {
68             // Returns [x0,y0,x1,y1,wgt,  x0,y0,x1,y1,wgt,  x0,y0,x1,y1,wgt,  ...]
69             assert(a.length() % 5 == 0);
70             MeteringRectangle[] ma = new MeteringRectangle[a.length() / 5];
71             for (int i = 0; i < a.length(); i += 5) {
72                 int x,y,w,h;
73                 if (normalized) {
74                     x = (int)Math.floor(a.getDouble(i+0) * width + 0.5f);
75                     y = (int)Math.floor(a.getDouble(i+1) * height + 0.5f);
76                     w = (int)Math.floor(a.getDouble(i+2) * width + 0.5f);
77                     h = (int)Math.floor(a.getDouble(i+3) * height + 0.5f);
78                 } else {
79                     x = a.getInt(i+0);
80                     y = a.getInt(i+1);
81                     w = a.getInt(i+2);
82                     h = a.getInt(i+3);
83                 }
84                 x = Math.max(x, 0);
85                 y = Math.max(y, 0);
86                 w = Math.min(w, width-x);
87                 h = Math.min(h, height-y);
88                 int wgt = a.getInt(i+4);
89                 ma[i/5] = new MeteringRectangle(x,y,w,h,wgt);
90             }
91             return ma;
92         } catch (org.json.JSONException e) {
93             throw new ItsException("JSON error: ", e);
94         }
95     }
96 
getOutputSpecs(JSONObject jsonObjTop)97     public static JSONArray getOutputSpecs(JSONObject jsonObjTop)
98             throws ItsException {
99         try {
100             if (jsonObjTop.has("outputSurfaces")) {
101                 return jsonObjTop.getJSONArray("outputSurfaces");
102             }
103             return null;
104         } catch (org.json.JSONException e) {
105             throw new ItsException("JSON error: ", e);
106         }
107     }
108 
getRaw16OutputSizes(CameraCharacteristics ccs)109     public static Size[] getRaw16OutputSizes(CameraCharacteristics ccs)
110             throws ItsException {
111         return getOutputSizes(ccs, ImageFormat.RAW_SENSOR);
112     }
113 
getRaw10OutputSizes(CameraCharacteristics ccs)114     public static Size[] getRaw10OutputSizes(CameraCharacteristics ccs)
115             throws ItsException {
116         return getOutputSizes(ccs, ImageFormat.RAW10);
117     }
118 
getRaw12OutputSizes(CameraCharacteristics ccs)119     public static Size[] getRaw12OutputSizes(CameraCharacteristics ccs)
120             throws ItsException {
121         return getOutputSizes(ccs, ImageFormat.RAW12);
122     }
123 
getJpegOutputSizes(CameraCharacteristics ccs)124     public static Size[] getJpegOutputSizes(CameraCharacteristics ccs)
125             throws ItsException {
126         return getOutputSizes(ccs, ImageFormat.JPEG);
127     }
128 
getYuvOutputSizes(CameraCharacteristics ccs)129     public static Size[] getYuvOutputSizes(CameraCharacteristics ccs)
130             throws ItsException {
131         return getOutputSizes(ccs, ImageFormat.YUV_420_888);
132     }
133 
getY8OutputSizes(CameraCharacteristics ccs)134     public static Size[] getY8OutputSizes(CameraCharacteristics ccs)
135             throws ItsException {
136         return getOutputSizes(ccs, ImageFormat.Y8);
137     }
138 
getMaxOutputSize(CameraCharacteristics ccs, int format)139     public static Size getMaxOutputSize(CameraCharacteristics ccs, int format)
140             throws ItsException {
141         return getMaxSize(getOutputSizes(ccs, format));
142     }
143 
getActiveArrayCropRegion(CameraCharacteristics ccs)144     public static Rect getActiveArrayCropRegion(CameraCharacteristics ccs) {
145         return ccs.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
146     }
147 
getOutputSizes(CameraCharacteristics ccs, int format)148     private static Size[] getOutputSizes(CameraCharacteristics ccs, int format)
149             throws ItsException {
150         StreamConfigurationMap configMap = ccs.get(
151                 CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
152         if (configMap == null) {
153             throw new ItsException("Failed to get stream config");
154         }
155         Size[] normalSizes = configMap.getOutputSizes(format);
156         Size[] slowSizes = configMap.getHighResolutionOutputSizes(format);
157         Size[] allSizes = null;
158         if (normalSizes != null && slowSizes != null) {
159             allSizes = new Size[normalSizes.length + slowSizes.length];
160             System.arraycopy(normalSizes, 0, allSizes, 0,
161                     normalSizes.length);
162             System.arraycopy(slowSizes, 0, allSizes, normalSizes.length,
163                     slowSizes.length);
164         } else if (normalSizes != null) {
165             allSizes = normalSizes;
166         } else if (slowSizes != null) {
167             allSizes = slowSizes;
168         }
169         return allSizes;
170     }
171 
getMaxSize(Size[] sizes)172     public static Size getMaxSize(Size[] sizes) {
173         if (sizes == null || sizes.length == 0) {
174             throw new IllegalArgumentException("sizes was empty");
175         }
176 
177         Size maxSize = sizes[0];
178         int maxArea = maxSize.getWidth() * maxSize.getHeight();
179         for (int i = 1; i < sizes.length; i++) {
180             int area = sizes[i].getWidth() * sizes[i].getHeight();
181             if (area > maxArea ||
182                     (area == maxArea && sizes[i].getWidth() > maxSize.getWidth())) {
183                 maxSize = sizes[i];
184                 maxArea = area;
185             }
186         }
187 
188         return maxSize;
189     }
190 
getDataFromImage(Image image, Semaphore quota)191     public static byte[] getDataFromImage(Image image, Semaphore quota)
192             throws ItsException {
193         int format = image.getFormat();
194         int width = image.getWidth();
195         int height = image.getHeight();
196         byte[] data = null;
197 
198         // Read image data
199         Plane[] planes = image.getPlanes();
200 
201         // Check image validity
202         if (!checkAndroidImageFormat(image)) {
203             throw new ItsException(
204                     "Invalid image format passed to getDataFromImage: " + image.getFormat());
205         }
206 
207         if (format == ImageFormat.JPEG) {
208             // JPEG doesn't have pixelstride and rowstride, treat it as 1D buffer.
209             ByteBuffer buffer = planes[0].getBuffer();
210             if (quota != null) {
211                 try {
212                     Logt.i(TAG, "Start waiting for quota Semaphore");
213                     quota.acquire(buffer.capacity());
214                     Logt.i(TAG, "Acquired quota Semaphore. Start reading image");
215                 } catch (java.lang.InterruptedException e) {
216                     Logt.e(TAG, "getDataFromImage error acquiring memory quota. Interrupted", e);
217                 }
218             }
219             data = new byte[buffer.capacity()];
220             buffer.get(data);
221             Logt.i(TAG, "Done reading jpeg image");
222             return data;
223         } else if (format == ImageFormat.YUV_420_888 || format == ImageFormat.RAW_SENSOR
224                 || format == ImageFormat.RAW10 || format == ImageFormat.RAW12
225                 || format == ImageFormat.Y8) {
226             int offset = 0;
227             int dataSize = width * height * ImageFormat.getBitsPerPixel(format) / 8;
228             if (quota != null) {
229                 try {
230                     Logt.i(TAG, "Start waiting for quota Semaphore");
231                     quota.acquire(dataSize);
232                     Logt.i(TAG, "Acquired quota Semaphore. Start reading image");
233                 } catch (java.lang.InterruptedException e) {
234                     Logt.e(TAG, "getDataFromImage error acquiring memory quota. Interrupted", e);
235                 }
236             }
237             data = new byte[dataSize];
238             int maxRowSize = planes[0].getRowStride();
239             for (int i = 0; i < planes.length; i++) {
240                 if (maxRowSize < planes[i].getRowStride()) {
241                     maxRowSize = planes[i].getRowStride();
242                 }
243             }
244             byte[] rowData = new byte[maxRowSize];
245             for (int i = 0; i < planes.length; i++) {
246                 ByteBuffer buffer = planes[i].getBuffer();
247                 int rowStride = planes[i].getRowStride();
248                 int pixelStride = planes[i].getPixelStride();
249                 int bytesPerPixel = ImageFormat.getBitsPerPixel(format) / 8;
250                 Logt.i(TAG, String.format(
251                         "Reading image: fmt %d, plane %d, w %d, h %d," +
252                         "rowStride %d, pixStride %d, bytesPerPixel %d",
253                         format, i, width, height, rowStride, pixelStride, bytesPerPixel));
254                 // For multi-planar yuv images, assuming yuv420 with 2x2 chroma subsampling.
255                 int w = (i == 0) ? width : width / 2;
256                 int h = (i == 0) ? height : height / 2;
257                 for (int row = 0; row < h; row++) {
258                     if (pixelStride == bytesPerPixel) {
259                         // Special case: optimized read of the entire row
260                         int length = w * bytesPerPixel;
261                         buffer.get(data, offset, length);
262                         // Advance buffer the remainder of the row stride
263                         if (row < h - 1) {
264                             buffer.position(buffer.position() + rowStride - length);
265                         }
266                         offset += length;
267                     } else {
268                         // Generic case: should work for any pixelStride but slower.
269                         // Use intermediate buffer to avoid read byte-by-byte from
270                         // DirectByteBuffer, which is very bad for performance.
271                         // Also need avoid access out of bound by only reading the available
272                         // bytes in the bytebuffer.
273                         int readSize = rowStride;
274                         if (buffer.remaining() < readSize) {
275                             readSize = buffer.remaining();
276                         }
277                         buffer.get(rowData, 0, readSize);
278                         if (pixelStride >= 1) {
279                             for (int col = 0; col < w; col++) {
280                                 data[offset++] = rowData[col * pixelStride];
281                             }
282                         } else {
283                             // PixelStride of 0 can mean pixel isn't a multiple of 8 bits, for
284                             // example with RAW10. Just copy the buffer, dropping any padding at
285                             // the end of the row.
286                             int length = (w * ImageFormat.getBitsPerPixel(format)) / 8;
287                             System.arraycopy(rowData,0,data,offset,length);
288                             offset += length;
289                         }
290                     }
291                 }
292             }
293             Logt.i(TAG, String.format("Done reading image, format %d", format));
294             return data;
295         } else {
296             throw new ItsException("Unsupported image format: " + format);
297         }
298     }
299 
checkAndroidImageFormat(Image image)300     private static boolean checkAndroidImageFormat(Image image) {
301         int format = image.getFormat();
302         Plane[] planes = image.getPlanes();
303         switch (format) {
304             case ImageFormat.YUV_420_888:
305             case ImageFormat.NV21:
306             case ImageFormat.YV12:
307                 return 3 == planes.length;
308             case ImageFormat.RAW_SENSOR:
309             case ImageFormat.RAW10:
310             case ImageFormat.RAW12:
311             case ImageFormat.JPEG:
312             case ImageFormat.Y8:
313                 return 1 == planes.length;
314             default:
315                 return false;
316         }
317     }
318 
319     public static class ItsCameraIdList {
320         // Short form camera Ids (including both CameraIdList and hidden physical cameras
321         public List<String> mCameraIds;
322         // Camera Id combos (ids from CameraIdList, and hidden physical camera Ids
323         // in the form of [logical camera id]:[hidden physical camera id]
324         public List<String> mCameraIdCombos;
325     }
326 
getItsCompatibleCameraIds(CameraManager manager)327     public static ItsCameraIdList getItsCompatibleCameraIds(CameraManager manager)
328             throws ItsException {
329         if (manager == null) {
330             throw new IllegalArgumentException("CameraManager is null");
331         }
332 
333         ItsCameraIdList outList = new ItsCameraIdList();
334         outList.mCameraIds = new ArrayList<String>();
335         outList.mCameraIdCombos = new ArrayList<String>();
336         try {
337             String[] cameraIds = manager.getCameraIdList();
338             for (String id : cameraIds) {
339                 CameraCharacteristics characteristics = manager.getCameraCharacteristics(id);
340                 int[] actualCapabilities = characteristics.get(
341                         CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
342                 boolean haveBC = false;
343                 boolean isMultiCamera = false;
344                 final int BACKWARD_COMPAT =
345                         CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE;
346                 final int LOGICAL_MULTI_CAMERA =
347                         CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA;
348                 for (int capability : actualCapabilities) {
349                     if (capability == BACKWARD_COMPAT) {
350                         haveBC = true;
351                     }
352                     if (capability == LOGICAL_MULTI_CAMERA) {
353                         isMultiCamera = true;
354                     }
355                 }
356 
357                 // Skip devices that does not support BACKWARD_COMPATIBLE capability
358                 if (!haveBC) continue;
359 
360                 int hwLevel = characteristics.get(
361                         CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
362                 if (hwLevel == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY ||
363                         hwLevel == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL) {
364                     // Skip LEGACY and EXTERNAL devices
365                     continue;
366                 }
367                 outList.mCameraIds.add(id);
368                 outList.mCameraIdCombos.add(id);
369 
370                 // Only add hidden physical cameras for multi-camera.
371                 if (!isMultiCamera) continue;
372 
373                 float defaultFocalLength = getLogicalCameraDefaultFocalLength(manager, id);
374                 Set<String> physicalIds = characteristics.getPhysicalCameraIds();
375                 for (String physicalId : physicalIds) {
376                     if (Arrays.asList(cameraIds).contains(physicalId)) continue;
377 
378                     CameraCharacteristics physicalChar =
379                             manager.getCameraCharacteristics(physicalId);
380                     hwLevel = physicalChar.get(
381                             CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
382                     if (hwLevel == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY ||
383                             hwLevel ==
384                             CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL) {
385                         // Skip LEGACY and EXTERNAL devices
386                         continue;
387                     }
388 
389                     int[] physicalActualCapabilities = physicalChar.get(
390                             CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
391                     boolean physicalHaveBC = false;
392                     for (int capability : physicalActualCapabilities) {
393                         if (capability == BACKWARD_COMPAT) {
394                             physicalHaveBC = true;
395                             break;
396                         }
397                     }
398                     if (!physicalHaveBC) {
399                         continue;
400                     }
401                     // To reduce duplicate tests, only additionally test hidden physical cameras
402                     // with different focal length compared to the default focal length of the
403                     // logical camera.
404                     float[] physicalFocalLengths = physicalChar.get(
405                             CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS);
406                     if (defaultFocalLength != physicalFocalLengths[0]) {
407                         outList.mCameraIds.add(physicalId);
408                         outList.mCameraIdCombos.add(id + CAMERA_ID_TOKENIZER + physicalId);
409                     }
410                 }
411 
412             }
413         } catch (CameraAccessException e) {
414             Logt.e(TAG,
415                     "Received error from camera service while checking device capabilities: " + e);
416             throw new ItsException("Failed to get device ID list", e);
417         }
418         return outList;
419     }
420 
getLogicalCameraDefaultFocalLength(CameraManager manager, String cameraId)421     public static float getLogicalCameraDefaultFocalLength(CameraManager manager,
422             String cameraId) throws ItsException {
423         BlockingCameraManager blockingManager = new BlockingCameraManager(manager);
424         BlockingStateCallback listener = new BlockingStateCallback();
425         HandlerThread cameraThread = new HandlerThread("ItsUtilThread");
426         cameraThread.start();
427         Handler cameraHandler = new Handler(cameraThread.getLooper());
428         CameraDevice camera = null;
429         float defaultFocalLength = 0.0f;
430 
431         try {
432             camera = blockingManager.openCamera(cameraId, listener, cameraHandler);
433             CaptureRequest.Builder previewBuilder =
434                     camera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
435             defaultFocalLength = previewBuilder.get(CaptureRequest.LENS_FOCAL_LENGTH);
436         } catch (Exception e) {
437             throw new ItsException("Failed to query default focal length for logical camera", e);
438         } finally {
439             if (camera != null) {
440                 camera.close();
441             }
442             if (cameraThread != null) {
443                 cameraThread.quitSafely();
444             }
445         }
446         return defaultFocalLength;
447     }
448 }
449