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 = characteristics.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 // To reduce duplicate tests, only additionally test hidden physical cameras 388 // with different focal length compared to the default focal length of the 389 // logical camera. 390 float[] physicalFocalLengths = physicalChar.get( 391 CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS); 392 if (defaultFocalLength != physicalFocalLengths[0]) { 393 outList.mCameraIds.add(physicalId); 394 outList.mCameraIdCombos.add(id + ":" + physicalId); 395 } 396 } 397 398 } 399 } catch (CameraAccessException e) { 400 Logt.e(TAG, 401 "Received error from camera service while checking device capabilities: " + e); 402 throw new ItsException("Failed to get device ID list", e); 403 } 404 return outList; 405 } 406 getLogicalCameraDefaultFocalLength(CameraManager manager, String cameraId)407 public static float getLogicalCameraDefaultFocalLength(CameraManager manager, 408 String cameraId) throws ItsException { 409 BlockingCameraManager blockingManager = new BlockingCameraManager(manager); 410 BlockingStateCallback listener = new BlockingStateCallback(); 411 HandlerThread cameraThread = new HandlerThread("ItsUtilThread"); 412 cameraThread.start(); 413 Handler cameraHandler = new Handler(cameraThread.getLooper()); 414 CameraDevice camera = null; 415 float defaultFocalLength = 0.0f; 416 417 try { 418 camera = blockingManager.openCamera(cameraId, listener, cameraHandler); 419 CaptureRequest.Builder previewBuilder = 420 camera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); 421 defaultFocalLength = previewBuilder.get(CaptureRequest.LENS_FOCAL_LENGTH); 422 } catch (Exception e) { 423 throw new ItsException("Failed to query default focal length for logical camera", e); 424 } finally { 425 if (camera != null) { 426 camera.close(); 427 } 428 if (cameraThread != null) { 429 cameraThread.quitSafely(); 430 } 431 } 432 return defaultFocalLength; 433 } 434 } 435