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.CameraDevice; 23 import android.hardware.camera2.CameraCharacteristics; 24 import android.hardware.camera2.CaptureRequest; 25 import android.hardware.camera2.CaptureResult; 26 import android.hardware.camera2.params.MeteringRectangle; 27 import android.hardware.camera2.params.StreamConfigurationMap; 28 import android.media.Image; 29 import android.media.Image.Plane; 30 import android.net.Uri; 31 import android.os.Environment; 32 import android.util.Log; 33 import android.util.Size; 34 35 import org.json.JSONArray; 36 import org.json.JSONObject; 37 38 import java.nio.ByteBuffer; 39 import java.nio.charset.Charset; 40 import java.util.ArrayList; 41 import java.util.concurrent.Semaphore; 42 import java.util.List; 43 44 45 public class ItsUtils { 46 public static final String TAG = ItsUtils.class.getSimpleName(); 47 48 public static ByteBuffer jsonToByteBuffer(JSONObject jsonObj) { 49 return ByteBuffer.wrap(jsonObj.toString().getBytes(Charset.defaultCharset())); 50 } 51 52 public static MeteringRectangle[] getJsonWeightedRectsFromArray( 53 JSONArray a, boolean normalized, int width, int height) 54 throws ItsException { 55 try { 56 // Returns [x0,y0,x1,y1,wgt, x0,y0,x1,y1,wgt, x0,y0,x1,y1,wgt, ...] 57 assert(a.length() % 5 == 0); 58 MeteringRectangle[] ma = new MeteringRectangle[a.length() / 5]; 59 for (int i = 0; i < a.length(); i += 5) { 60 int x,y,w,h; 61 if (normalized) { 62 x = (int)Math.floor(a.getDouble(i+0) * width + 0.5f); 63 y = (int)Math.floor(a.getDouble(i+1) * height + 0.5f); 64 w = (int)Math.floor(a.getDouble(i+2) * width + 0.5f); 65 h = (int)Math.floor(a.getDouble(i+3) * height + 0.5f); 66 } else { 67 x = a.getInt(i+0); 68 y = a.getInt(i+1); 69 w = a.getInt(i+2); 70 h = a.getInt(i+3); 71 } 72 x = Math.max(x, 0); 73 y = Math.max(y, 0); 74 w = Math.min(w, width-x); 75 h = Math.min(h, height-y); 76 int wgt = a.getInt(i+4); 77 ma[i/5] = new MeteringRectangle(x,y,w,h,wgt); 78 } 79 return ma; 80 } catch (org.json.JSONException e) { 81 throw new ItsException("JSON error: ", e); 82 } 83 } 84 85 public static JSONArray getOutputSpecs(JSONObject jsonObjTop) 86 throws ItsException { 87 try { 88 if (jsonObjTop.has("outputSurfaces")) { 89 return jsonObjTop.getJSONArray("outputSurfaces"); 90 } 91 return null; 92 } catch (org.json.JSONException e) { 93 throw new ItsException("JSON error: ", e); 94 } 95 } 96 97 public static Size[] getRaw16OutputSizes(CameraCharacteristics ccs) 98 throws ItsException { 99 return getOutputSizes(ccs, ImageFormat.RAW_SENSOR); 100 } 101 102 public static Size[] getRaw10OutputSizes(CameraCharacteristics ccs) 103 throws ItsException { 104 return getOutputSizes(ccs, ImageFormat.RAW10); 105 } 106 107 public static Size[] getRaw12OutputSizes(CameraCharacteristics ccs) 108 throws ItsException { 109 return getOutputSizes(ccs, ImageFormat.RAW12); 110 } 111 112 public static Size[] getJpegOutputSizes(CameraCharacteristics ccs) 113 throws ItsException { 114 return getOutputSizes(ccs, ImageFormat.JPEG); 115 } 116 117 public static Size[] getYuvOutputSizes(CameraCharacteristics ccs) 118 throws ItsException { 119 return getOutputSizes(ccs, ImageFormat.YUV_420_888); 120 } 121 122 public static Size getMaxOutputSize(CameraCharacteristics ccs, int format) 123 throws ItsException { 124 return getMaxSize(getOutputSizes(ccs, format)); 125 } 126 127 public static Rect getActiveArrayCropRegion(CameraCharacteristics ccs) { 128 return ccs.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE); 129 } 130 131 private static Size[] getOutputSizes(CameraCharacteristics ccs, int format) 132 throws ItsException { 133 StreamConfigurationMap configMap = ccs.get( 134 CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); 135 if (configMap == null) { 136 throw new ItsException("Failed to get stream config"); 137 } 138 Size[] normalSizes = configMap.getOutputSizes(format); 139 Size[] slowSizes = configMap.getHighResolutionOutputSizes(format); 140 Size[] allSizes = null; 141 if (normalSizes != null && slowSizes != null) { 142 allSizes = new Size[normalSizes.length + slowSizes.length]; 143 System.arraycopy(normalSizes, 0, allSizes, 0, 144 normalSizes.length); 145 System.arraycopy(slowSizes, 0, allSizes, normalSizes.length, 146 slowSizes.length); 147 } else if (normalSizes != null) { 148 allSizes = normalSizes; 149 } else if (slowSizes != null) { 150 allSizes = slowSizes; 151 } 152 return allSizes; 153 } 154 155 public static Size getMaxSize(Size[] sizes) { 156 if (sizes == null || sizes.length == 0) { 157 throw new IllegalArgumentException("sizes was empty"); 158 } 159 160 Size maxSize = sizes[0]; 161 int maxArea = maxSize.getWidth() * maxSize.getHeight(); 162 for (int i = 1; i < sizes.length; i++) { 163 int area = sizes[i].getWidth() * sizes[i].getHeight(); 164 if (area > maxArea || 165 (area == maxArea && sizes[i].getWidth() > maxSize.getWidth())) { 166 maxSize = sizes[i]; 167 maxArea = area; 168 } 169 } 170 171 return maxSize; 172 } 173 174 public static byte[] getDataFromImage(Image image, Semaphore quota) 175 throws ItsException { 176 int format = image.getFormat(); 177 int width = image.getWidth(); 178 int height = image.getHeight(); 179 byte[] data = null; 180 181 // Read image data 182 Plane[] planes = image.getPlanes(); 183 184 // Check image validity 185 if (!checkAndroidImageFormat(image)) { 186 throw new ItsException( 187 "Invalid image format passed to getDataFromImage: " + image.getFormat()); 188 } 189 190 if (format == ImageFormat.JPEG) { 191 // JPEG doesn't have pixelstride and rowstride, treat it as 1D buffer. 192 ByteBuffer buffer = planes[0].getBuffer(); 193 if (quota != null) { 194 try { 195 Logt.i(TAG, "Start waiting for quota Semaphore"); 196 quota.acquire(buffer.capacity()); 197 Logt.i(TAG, "Acquired quota Semaphore. Start reading image"); 198 } catch (java.lang.InterruptedException e) { 199 Logt.e(TAG, "getDataFromImage error acquiring memory quota. Interrupted", e); 200 } 201 } 202 data = new byte[buffer.capacity()]; 203 buffer.get(data); 204 Logt.i(TAG, "Done reading jpeg image, format %d"); 205 return data; 206 } else if (format == ImageFormat.YUV_420_888 || format == ImageFormat.RAW_SENSOR 207 || format == ImageFormat.RAW10 || format == ImageFormat.RAW12) { 208 int offset = 0; 209 int dataSize = width * height * ImageFormat.getBitsPerPixel(format) / 8; 210 if (quota != null) { 211 try { 212 Logt.i(TAG, "Start waiting for quota Semaphore"); 213 quota.acquire(dataSize); 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[dataSize]; 220 int maxRowSize = planes[0].getRowStride(); 221 for (int i = 0; i < planes.length; i++) { 222 if (maxRowSize < planes[i].getRowStride()) { 223 maxRowSize = planes[i].getRowStride(); 224 } 225 } 226 byte[] rowData = new byte[maxRowSize]; 227 for (int i = 0; i < planes.length; i++) { 228 ByteBuffer buffer = planes[i].getBuffer(); 229 int rowStride = planes[i].getRowStride(); 230 int pixelStride = planes[i].getPixelStride(); 231 int bytesPerPixel = ImageFormat.getBitsPerPixel(format) / 8; 232 Logt.i(TAG, String.format( 233 "Reading image: fmt %d, plane %d, w %d, h %d," + 234 "rowStride %d, pixStride %d, bytesPerPixel %d", 235 format, i, width, height, rowStride, pixelStride, bytesPerPixel)); 236 // For multi-planar yuv images, assuming yuv420 with 2x2 chroma subsampling. 237 int w = (i == 0) ? width : width / 2; 238 int h = (i == 0) ? height : height / 2; 239 for (int row = 0; row < h; row++) { 240 if (pixelStride == bytesPerPixel) { 241 // Special case: optimized read of the entire row 242 int length = w * bytesPerPixel; 243 buffer.get(data, offset, length); 244 // Advance buffer the remainder of the row stride 245 if (row < h - 1) { 246 buffer.position(buffer.position() + rowStride - length); 247 } 248 offset += length; 249 } else { 250 // Generic case: should work for any pixelStride but slower. 251 // Use intermediate buffer to avoid read byte-by-byte from 252 // DirectByteBuffer, which is very bad for performance. 253 // Also need avoid access out of bound by only reading the available 254 // bytes in the bytebuffer. 255 int readSize = rowStride; 256 if (buffer.remaining() < readSize) { 257 readSize = buffer.remaining(); 258 } 259 buffer.get(rowData, 0, readSize); 260 if (pixelStride >= 1) { 261 for (int col = 0; col < w; col++) { 262 data[offset++] = rowData[col * pixelStride]; 263 } 264 } else { 265 // PixelStride of 0 can mean pixel isn't a multiple of 8 bits, for 266 // example with RAW10. Just copy the buffer, dropping any padding at 267 // the end of the row. 268 int length = (w * ImageFormat.getBitsPerPixel(format)) / 8; 269 System.arraycopy(rowData,0,data,offset,length); 270 offset += length; 271 } 272 } 273 } 274 } 275 Logt.i(TAG, String.format("Done reading image, format %d", format)); 276 return data; 277 } else { 278 throw new ItsException("Unsupported image format: " + format); 279 } 280 } 281 282 private static boolean checkAndroidImageFormat(Image image) { 283 int format = image.getFormat(); 284 Plane[] planes = image.getPlanes(); 285 switch (format) { 286 case ImageFormat.YUV_420_888: 287 case ImageFormat.NV21: 288 case ImageFormat.YV12: 289 return 3 == planes.length; 290 case ImageFormat.RAW_SENSOR: 291 case ImageFormat.RAW10: 292 case ImageFormat.RAW12: 293 case ImageFormat.JPEG: 294 return 1 == planes.length; 295 default: 296 return false; 297 } 298 } 299 } 300