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