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