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