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