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