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.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 
getRaw16OutputSizes(CameraCharacteristics ccs)94     public static Size[] getRaw16OutputSizes(CameraCharacteristics ccs)
95             throws ItsException {
96         return getOutputSizes(ccs, ImageFormat.RAW_SENSOR);
97     }
98 
getRaw10OutputSizes(CameraCharacteristics ccs)99     public static Size[] getRaw10OutputSizes(CameraCharacteristics ccs)
100             throws ItsException {
101         return getOutputSizes(ccs, ImageFormat.RAW10);
102     }
103 
getRaw12OutputSizes(CameraCharacteristics ccs)104     public static Size[] getRaw12OutputSizes(CameraCharacteristics ccs)
105             throws ItsException {
106         return getOutputSizes(ccs, ImageFormat.RAW12);
107     }
108 
getJpegOutputSizes(CameraCharacteristics ccs)109     public static Size[] getJpegOutputSizes(CameraCharacteristics ccs)
110             throws ItsException {
111         return getOutputSizes(ccs, ImageFormat.JPEG);
112     }
113 
getYuvOutputSizes(CameraCharacteristics ccs)114     public static Size[] getYuvOutputSizes(CameraCharacteristics ccs)
115             throws ItsException {
116         return getOutputSizes(ccs, ImageFormat.YUV_420_888);
117     }
118 
getMaxOutputSize(CameraCharacteristics ccs, int format)119     public static Size getMaxOutputSize(CameraCharacteristics ccs, int format)
120             throws ItsException {
121         return getMaxSize(getOutputSizes(ccs, format));
122     }
123 
getOutputSizes(CameraCharacteristics ccs, int format)124     private static Size[] getOutputSizes(CameraCharacteristics ccs, int format)
125             throws ItsException {
126         StreamConfigurationMap configMap = ccs.get(
127                 CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
128         if (configMap == null) {
129             throw new ItsException("Failed to get stream config");
130         }
131         Size[] normalSizes = configMap.getOutputSizes(format);
132         Size[] slowSizes = configMap.getHighResolutionOutputSizes(format);
133         Size[] allSizes = null;
134         if (normalSizes != null && slowSizes != null) {
135             allSizes = new Size[normalSizes.length + slowSizes.length];
136             System.arraycopy(normalSizes, 0, allSizes, 0,
137                     normalSizes.length);
138             System.arraycopy(slowSizes, 0, allSizes, normalSizes.length,
139                     slowSizes.length);
140         } else if (normalSizes != null) {
141             allSizes = normalSizes;
142         } else if (slowSizes != null) {
143             allSizes = slowSizes;
144         }
145         return allSizes;
146     }
147 
getMaxSize(Size[] sizes)148     public static Size getMaxSize(Size[] sizes) {
149         if (sizes == null || sizes.length == 0) {
150             throw new IllegalArgumentException("sizes was empty");
151         }
152 
153         Size maxSize = sizes[0];
154         for (int i = 1; i < sizes.length; i++) {
155             if (sizes[i].getWidth() * sizes[i].getHeight() >
156                     maxSize.getWidth() * maxSize.getHeight()) {
157                 maxSize = sizes[i];
158             }
159         }
160 
161         return maxSize;
162     }
163 
getDataFromImage(Image image)164     public static byte[] getDataFromImage(Image image)
165             throws ItsException {
166         int format = image.getFormat();
167         int width = image.getWidth();
168         int height = image.getHeight();
169         byte[] data = null;
170 
171         // Read image data
172         Plane[] planes = image.getPlanes();
173 
174         // Check image validity
175         if (!checkAndroidImageFormat(image)) {
176             throw new ItsException(
177                     "Invalid image format passed to getDataFromImage: " + image.getFormat());
178         }
179 
180         if (format == ImageFormat.JPEG) {
181             // JPEG doesn't have pixelstride and rowstride, treat it as 1D buffer.
182             ByteBuffer buffer = planes[0].getBuffer();
183             data = new byte[buffer.capacity()];
184             buffer.get(data);
185             return data;
186         } else if (format == ImageFormat.YUV_420_888 || format == ImageFormat.RAW_SENSOR
187                 || format == ImageFormat.RAW10 || format == ImageFormat.RAW12) {
188             int offset = 0;
189             data = new byte[width * height * ImageFormat.getBitsPerPixel(format) / 8];
190             int maxRowSize = planes[0].getRowStride();
191             for (int i = 0; i < planes.length; i++) {
192                 if (maxRowSize < planes[i].getRowStride()) {
193                     maxRowSize = planes[i].getRowStride();
194                 }
195             }
196             byte[] rowData = new byte[maxRowSize];
197             for (int i = 0; i < planes.length; i++) {
198                 ByteBuffer buffer = planes[i].getBuffer();
199                 int rowStride = planes[i].getRowStride();
200                 int pixelStride = planes[i].getPixelStride();
201                 int bytesPerPixel = ImageFormat.getBitsPerPixel(format) / 8;
202                 Logt.i(TAG, String.format(
203                         "Reading image: fmt %d, plane %d, w %d, h %d, rowStride %d, pixStride %d",
204                         format, i, width, height, rowStride, pixelStride));
205                 // For multi-planar yuv images, assuming yuv420 with 2x2 chroma subsampling.
206                 int w = (i == 0) ? width : width / 2;
207                 int h = (i == 0) ? height : height / 2;
208                 for (int row = 0; row < h; row++) {
209                     if (pixelStride == bytesPerPixel) {
210                         // Special case: optimized read of the entire row
211                         int length = w * bytesPerPixel;
212                         buffer.get(data, offset, length);
213                         // Advance buffer the remainder of the row stride
214                         if (row < h - 1) {
215                             buffer.position(buffer.position() + rowStride - length);
216                         }
217                         offset += length;
218                     } else {
219                         // Generic case: should work for any pixelStride but slower.
220                         // Use intermediate buffer to avoid read byte-by-byte from
221                         // DirectByteBuffer, which is very bad for performance.
222                         // Also need avoid access out of bound by only reading the available
223                         // bytes in the bytebuffer.
224                         int readSize = rowStride;
225                         if (buffer.remaining() < readSize) {
226                             readSize = buffer.remaining();
227                         }
228                         buffer.get(rowData, 0, readSize);
229                         if (pixelStride >= 1) {
230                             for (int col = 0; col < w; col++) {
231                                 data[offset++] = rowData[col * pixelStride];
232                             }
233                         } else {
234                             // PixelStride of 0 can mean pixel isn't a multiple of 8 bits, for
235                             // example with RAW10. Just copy the buffer, dropping any padding at
236                             // the end of the row.
237                             int length = (w * ImageFormat.getBitsPerPixel(format)) / 8;
238                             System.arraycopy(rowData,0,data,offset,length);
239                             offset += length;
240                         }
241                     }
242                 }
243             }
244             Logt.i(TAG, String.format("Done reading image, format %d", format));
245             return data;
246         } else {
247             throw new ItsException("Unsupported image format: " + format);
248         }
249     }
250 
checkAndroidImageFormat(Image image)251     private static boolean checkAndroidImageFormat(Image image) {
252         int format = image.getFormat();
253         Plane[] planes = image.getPlanes();
254         switch (format) {
255             case ImageFormat.YUV_420_888:
256             case ImageFormat.NV21:
257             case ImageFormat.YV12:
258                 return 3 == planes.length;
259             case ImageFormat.RAW_SENSOR:
260             case ImageFormat.RAW10:
261             case ImageFormat.RAW12:
262             case ImageFormat.JPEG:
263                 return 1 == planes.length;
264             default:
265                 return false;
266         }
267     }
268 }
269