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.camera2.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.List; 41 42 public class ItsUtils { 43 public static final String TAG = ItsUtils.class.getSimpleName(); 44 jsonToByteBuffer(JSONObject jsonObj)45 public static ByteBuffer jsonToByteBuffer(JSONObject jsonObj) { 46 return ByteBuffer.wrap(jsonObj.toString().getBytes(Charset.defaultCharset())); 47 } 48 getJsonWeightedRectsFromArray( JSONArray a, boolean normalized, int width, int height)49 public static MeteringRectangle[] getJsonWeightedRectsFromArray( 50 JSONArray a, boolean normalized, int width, int height) 51 throws ItsException { 52 try { 53 // Returns [x0,y0,x1,y1,wgt, x0,y0,x1,y1,wgt, x0,y0,x1,y1,wgt, ...] 54 assert(a.length() % 5 == 0); 55 MeteringRectangle[] ma = new MeteringRectangle[a.length() / 5]; 56 for (int i = 0; i < a.length(); i += 5) { 57 int x,y,w,h; 58 if (normalized) { 59 x = (int)Math.floor(a.getDouble(i+0) * width + 0.5f); 60 y = (int)Math.floor(a.getDouble(i+1) * height + 0.5f); 61 w = (int)Math.floor(a.getDouble(i+2) * width + 0.5f); 62 h = (int)Math.floor(a.getDouble(i+3) * height + 0.5f); 63 } else { 64 x = a.getInt(i+0); 65 y = a.getInt(i+1); 66 w = a.getInt(i+2); 67 h = a.getInt(i+3); 68 } 69 x = Math.max(x, 0); 70 y = Math.max(y, 0); 71 w = Math.min(w, width-x); 72 h = Math.min(h, height-y); 73 int wgt = a.getInt(i+4); 74 ma[i/5] = new MeteringRectangle(x,y,w,h,wgt); 75 } 76 return ma; 77 } catch (org.json.JSONException e) { 78 throw new ItsException("JSON error: ", e); 79 } 80 } 81 getOutputSpecs(JSONObject jsonObjTop)82 public static JSONArray getOutputSpecs(JSONObject jsonObjTop) 83 throws ItsException { 84 try { 85 if (jsonObjTop.has("outputSurfaces")) { 86 return jsonObjTop.getJSONArray("outputSurfaces"); 87 } 88 return null; 89 } catch (org.json.JSONException e) { 90 throw new ItsException("JSON error: ", e); 91 } 92 } 93 getRawOutputSizes(CameraCharacteristics ccs)94 public static Size[] getRawOutputSizes(CameraCharacteristics ccs) 95 throws ItsException { 96 return getOutputSizes(ccs, ImageFormat.RAW_SENSOR); 97 } 98 getJpegOutputSizes(CameraCharacteristics ccs)99 public static Size[] getJpegOutputSizes(CameraCharacteristics ccs) 100 throws ItsException { 101 return getOutputSizes(ccs, ImageFormat.JPEG); 102 } 103 getYuvOutputSizes(CameraCharacteristics ccs)104 public static Size[] getYuvOutputSizes(CameraCharacteristics ccs) 105 throws ItsException { 106 return getOutputSizes(ccs, ImageFormat.YUV_420_888); 107 } 108 getOutputSizes(CameraCharacteristics ccs, int format)109 private static Size[] getOutputSizes(CameraCharacteristics ccs, int format) 110 throws ItsException { 111 StreamConfigurationMap configMap = ccs.get( 112 CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); 113 if (configMap == null) { 114 throw new ItsException("Failed to get stream config"); 115 } 116 return configMap.getOutputSizes(format); 117 } 118 getDataFromImage(Image image)119 public static byte[] getDataFromImage(Image image) 120 throws ItsException { 121 int format = image.getFormat(); 122 int width = image.getWidth(); 123 int height = image.getHeight(); 124 byte[] data = null; 125 126 // Read image data 127 Plane[] planes = image.getPlanes(); 128 129 // Check image validity 130 if (!checkAndroidImageFormat(image)) { 131 throw new ItsException( 132 "Invalid image format passed to getDataFromImage: " + image.getFormat()); 133 } 134 135 if (format == ImageFormat.JPEG) { 136 // JPEG doesn't have pixelstride and rowstride, treat it as 1D buffer. 137 ByteBuffer buffer = planes[0].getBuffer(); 138 data = new byte[buffer.capacity()]; 139 buffer.get(data); 140 return data; 141 } else if (format == ImageFormat.YUV_420_888 || format == ImageFormat.RAW_SENSOR 142 || format == ImageFormat.RAW10) { 143 int offset = 0; 144 data = new byte[width * height * ImageFormat.getBitsPerPixel(format) / 8]; 145 byte[] rowData = new byte[planes[0].getRowStride()]; 146 for (int i = 0; i < planes.length; i++) { 147 ByteBuffer buffer = planes[i].getBuffer(); 148 int rowStride = planes[i].getRowStride(); 149 int pixelStride = planes[i].getPixelStride(); 150 int bytesPerPixel = ImageFormat.getBitsPerPixel(format) / 8; 151 Logt.i(TAG, String.format( 152 "Reading image: fmt %d, plane %d, w %d, h %d, rowStride %d, pixStride %d", 153 format, i, width, height, rowStride, pixelStride)); 154 // For multi-planar yuv images, assuming yuv420 with 2x2 chroma subsampling. 155 int w = (i == 0) ? width : width / 2; 156 int h = (i == 0) ? height : height / 2; 157 for (int row = 0; row < h; row++) { 158 if (pixelStride == bytesPerPixel) { 159 // Special case: optimized read of the entire row 160 int length = w * bytesPerPixel; 161 buffer.get(data, offset, length); 162 // Advance buffer the remainder of the row stride 163 buffer.position(buffer.position() + rowStride - length); 164 offset += length; 165 } else { 166 // Generic case: should work for any pixelStride but slower. 167 // Use intermediate buffer to avoid read byte-by-byte from 168 // DirectByteBuffer, which is very bad for performance. 169 // Also need avoid access out of bound by only reading the available 170 // bytes in the bytebuffer. 171 int readSize = rowStride; 172 if (buffer.remaining() < readSize) { 173 readSize = buffer.remaining(); 174 } 175 buffer.get(rowData, 0, readSize); 176 if (pixelStride >= 1) { 177 for (int col = 0; col < w; col++) { 178 data[offset++] = rowData[col * pixelStride]; 179 } 180 } else { 181 // PixelStride of 0 can mean pixel isn't a multiple of 8 bits, for 182 // example with RAW10. Just copy the buffer, dropping any padding at 183 // the end of the row. 184 int length = (w * ImageFormat.getBitsPerPixel(format)) / 8; 185 System.arraycopy(rowData,0,data,offset,length); 186 offset += length; 187 } 188 } 189 } 190 } 191 Logt.i(TAG, String.format("Done reading image, format %d", format)); 192 return data; 193 } else { 194 throw new ItsException("Unsupported image format: " + format); 195 } 196 } 197 checkAndroidImageFormat(Image image)198 private static boolean checkAndroidImageFormat(Image image) { 199 int format = image.getFormat(); 200 Plane[] planes = image.getPlanes(); 201 switch (format) { 202 case ImageFormat.YUV_420_888: 203 case ImageFormat.NV21: 204 case ImageFormat.YV12: 205 return 3 == planes.length; 206 case ImageFormat.RAW_SENSOR: 207 case ImageFormat.RAW10: 208 case ImageFormat.JPEG: 209 return 1 == planes.length; 210 default: 211 return false; 212 } 213 } 214 } 215 216