ItsUtils.java revision 12339cdd90b2c98eb4f7adf794ddca8de53992b8
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 17package com.android.camera2.its; 18 19import android.content.Context; 20import android.graphics.ImageFormat; 21import android.hardware.camera2.CameraDevice; 22import android.hardware.camera2.CameraProperties; 23import android.hardware.camera2.CaptureRequest; 24import android.hardware.camera2.CaptureResult; 25import android.media.Image; 26import android.media.Image.Plane; 27import android.net.Uri; 28import android.os.Environment; 29import android.util.Log; 30 31import org.json.JSONArray; 32import org.json.JSONObject; 33 34import java.io.File; 35import java.io.FileInputStream; 36import java.io.FileNotFoundException; 37import java.io.FileOutputStream; 38import java.io.IOException; 39import java.nio.ByteBuffer; 40import java.nio.channels.FileChannel; 41import java.nio.charset.Charset; 42import java.util.List; 43 44public class ItsUtils { 45 public static final String TAG = ItsUtils.class.getSimpleName(); 46 47 // The externally visible (over adb) base path for the files that are saved by this app 48 // to the external media. Currently hardcoded to "/sdcard", which can work on any device 49 // by creating a symlink to the actual mount location. 50 // TODO: Fix this, by querying mount/vold to get the actual externally visible path. 51 public static final String EXT_VISIBLE_BASE_PATH = "/sdcard"; 52 53 // State related to output files created by the script. 54 public static final String DEFAULT_CAPTURE_DIR = "its"; 55 public static final String DEFAULT_IMAGE_DIR = "captures"; 56 public static final String FILE_PREFIX = "IMG_"; 57 public static final String JPEG_SUFFIX = ".jpg"; 58 public static final String YUV_SUFFIX = ".yuv"; 59 public static final String METADATA_SUFFIX = ".json"; 60 61 // The indent amount to use when printing the JSON objects out as strings. 62 private static final int PPRINT_JSON_INDENT = 2; 63 64 public static void storeCameraProperties(CameraProperties props, 65 File file) 66 throws ItsException { 67 try { 68 JSONObject jsonObj = new JSONObject(); 69 jsonObj.put("cameraProperties", ItsSerializer.serialize(props)); 70 storeJsonObject(jsonObj, file); 71 } catch (org.json.JSONException e) { 72 throw new ItsException("JSON error: ", e); 73 } 74 } 75 76 public static void storeResults(CameraProperties props, 77 CaptureRequest request, 78 CaptureResult result, 79 File file) 80 throws ItsException { 81 try { 82 JSONObject jsonObj = new JSONObject(); 83 jsonObj.put("cameraProperties", ItsSerializer.serialize(props)); 84 jsonObj.put("captureRequest", ItsSerializer.serialize(request)); 85 jsonObj.put("captureResult", ItsSerializer.serialize(result)); 86 storeJsonObject(jsonObj, file); 87 } catch (org.json.JSONException e) { 88 throw new ItsException("JSON error: ", e); 89 } 90 } 91 92 public static void storeJsonObject(JSONObject jsonObj, File file) 93 throws ItsException { 94 ByteBuffer buf = null; 95 try { 96 buf = ByteBuffer.wrap(jsonObj.toString(PPRINT_JSON_INDENT). 97 getBytes(Charset.defaultCharset())); 98 } catch (org.json.JSONException e) { 99 throw new ItsException("JSON error: ", e); 100 } 101 FileChannel channel = null; 102 try { 103 channel = new FileOutputStream(file, false).getChannel(); 104 channel.write(buf); 105 channel.close(); 106 } catch (FileNotFoundException e) { 107 throw new ItsException("Failed to write file: " + file.toString() + ": ", e); 108 } catch (IOException e) { 109 throw new ItsException("Failed to write file: " + file.toString() + ": ", e); 110 } 111 } 112 113 public static List<CaptureRequest> loadRequestList(CameraDevice device, Uri uri) 114 throws ItsException { 115 return ItsSerializer.deserializeRequestList(device, loadJsonFile(uri)); 116 } 117 118 public static JSONObject loadJsonFile(Uri uri) throws ItsException { 119 FileInputStream input = null; 120 try { 121 input = new FileInputStream(uri.getPath()); 122 byte[] fileData = new byte[input.available()]; 123 input.read(fileData); 124 input.close(); 125 String text = new String(fileData, Charset.defaultCharset()); 126 return new JSONObject(text); 127 } catch (FileNotFoundException e) { 128 throw new ItsException("Failed to read file: " + uri.toString() + ": ", e); 129 } catch (org.json.JSONException e) { 130 throw new ItsException("JSON error: ", e); 131 } catch (IOException e) { 132 throw new ItsException("Failed to read file: " + uri.toString() + ": ", e); 133 } 134 } 135 136 public static int[] getJsonRectFromArray( 137 JSONArray a, boolean normalized, int width, int height) 138 throws ItsException { 139 try { 140 // Returns [x,y,w,h] 141 if (normalized) { 142 return new int[]{(int)Math.floor(a.getDouble(0) * width + 0.5f), 143 (int)Math.floor(a.getDouble(1) * height + 0.5f), 144 (int)Math.floor(a.getDouble(2) * width + 0.5f), 145 (int)Math.floor(a.getDouble(3) * height + 0.5f) }; 146 } else { 147 return new int[]{a.getInt(0), 148 a.getInt(1), 149 a.getInt(2), 150 a.getInt(3) }; 151 } 152 } catch (org.json.JSONException e) { 153 throw new ItsException("JSON error: ", e); 154 } 155 } 156 157 public static int getCallbacksPerCapture(int format) 158 throws ItsException { 159 // Regardless of the format, there is one callback for the CaptureResult object; this 160 // prepares the output metadata file. 161 int n = 1; 162 163 switch (format) { 164 case ImageFormat.YUV_420_888: 165 case ImageFormat.JPEG: 166 // A single output image callback is made, with either the JPEG or the YUV data. 167 n += 1; 168 break; 169 170 default: 171 throw new ItsException("Unsupported format: " + format); 172 } 173 174 return n; 175 } 176 177 public static JSONObject getOutputSpecs(Uri uri) 178 throws ItsException { 179 FileInputStream input = null; 180 try { 181 input = new FileInputStream(uri.getPath()); 182 byte[] fileData = new byte[input.available()]; 183 input.read(fileData); 184 input.close(); 185 String text = new String(fileData, Charset.defaultCharset()); 186 JSONObject jsonObjTop = new JSONObject(text); 187 if (jsonObjTop.has("outputSurface")) { 188 return jsonObjTop.getJSONObject("outputSurface"); 189 } 190 return null; 191 } catch (FileNotFoundException e) { 192 throw new ItsException("Failed to read file: " + uri.toString() + ": ", e); 193 } catch (IOException e) { 194 throw new ItsException("Failed to read file: " + uri.toString() + ": ", e); 195 } catch (org.json.JSONException e) { 196 throw new ItsException("JSON error: ", e); 197 } 198 } 199 200 public static boolean isExternalStorageWritable() { 201 String state = Environment.getExternalStorageState(); 202 if (Environment.MEDIA_MOUNTED.equals(state)) { 203 return true; 204 } 205 return false; 206 } 207 208 public static File getStorageDirectory(Context context, String dirName) 209 throws ItsException { 210 if (!isExternalStorageWritable()) { 211 throw new ItsException( 212 "External storage is not writable, cannot save capture image"); 213 } 214 File file = Environment.getExternalStorageDirectory(); 215 if (file == null) { 216 throw new ItsException("No external storage available"); 217 } 218 File newDir = new File(file, dirName); 219 newDir.mkdirs(); 220 if (!newDir.isDirectory()) { 221 throw new ItsException("Could not create directory: " + dirName); 222 } 223 return newDir; 224 } 225 226 public static String getExternallyVisiblePath(Context context, String path) 227 throws ItsException { 228 File file = Environment.getExternalStorageDirectory(); 229 if (file == null) { 230 throw new ItsException("No external storage available"); 231 } 232 String base = file.toString(); 233 String newPath = path.replaceFirst(base, EXT_VISIBLE_BASE_PATH); 234 if (newPath == null) { 235 throw new ItsException("Error getting external path: " + path); 236 } 237 return newPath; 238 } 239 240 public static String getJpegFileName(long fileNumber) { 241 return String.format("%s%016x%s", FILE_PREFIX, fileNumber, JPEG_SUFFIX); 242 } 243 public static String getYuvFileName(long fileNumber) { 244 return String.format("%s%016x%s", FILE_PREFIX, fileNumber, YUV_SUFFIX); 245 } 246 public static String getMetadataFileName(long fileNumber) { 247 return String.format("%s%016x%s", FILE_PREFIX, fileNumber, METADATA_SUFFIX); 248 } 249 250 public static byte[] getDataFromImage(Image image) 251 throws ItsException { 252 int format = image.getFormat(); 253 int width = image.getWidth(); 254 int height = image.getHeight(); 255 int rowStride, pixelStride; 256 byte[] data = null; 257 258 // Read image data 259 Plane[] planes = image.getPlanes(); 260 261 // Check image validity 262 if (!checkAndroidImageFormat(image)) { 263 throw new ItsException( 264 "Invalid image format passed to getDataFromImage: " + image.getFormat()); 265 } 266 267 if (format == ImageFormat.JPEG) { 268 // JPEG doesn't have pixelstride and rowstride, treat it as 1D buffer. 269 ByteBuffer buffer = planes[0].getBuffer(); 270 data = new byte[buffer.capacity()]; 271 buffer.get(data); 272 return data; 273 } else if (format == ImageFormat.YUV_420_888) { 274 int offset = 0; 275 data = new byte[width * height * ImageFormat.getBitsPerPixel(format) / 8]; 276 byte[] rowData = new byte[planes[0].getRowStride()]; 277 for (int i = 0; i < planes.length; i++) { 278 ByteBuffer buffer = planes[i].getBuffer(); 279 rowStride = planes[i].getRowStride(); 280 pixelStride = planes[i].getPixelStride(); 281 // For multi-planar yuv images, assuming yuv420 with 2x2 chroma subsampling. 282 int w = (i == 0) ? width : width / 2; 283 int h = (i == 0) ? height : height / 2; 284 for (int row = 0; row < h; row++) { 285 int bytesPerPixel = ImageFormat.getBitsPerPixel(format) / 8; 286 if (pixelStride == bytesPerPixel) { 287 // Special case: optimized read of the entire row 288 int length = w * bytesPerPixel; 289 buffer.get(data, offset, length); 290 // Advance buffer the remainder of the row stride 291 buffer.position(buffer.position() + rowStride - length); 292 offset += length; 293 } else { 294 // Generic case: should work for any pixelStride but slower. 295 // Use use intermediate buffer to avoid read byte-by-byte from 296 // DirectByteBuffer, which is very bad for performance 297 buffer.get(rowData, 0, rowStride); 298 for (int col = 0; col < w; col++) { 299 data[offset++] = rowData[col * pixelStride]; 300 } 301 } 302 } 303 } 304 return data; 305 } else { 306 throw new ItsException("Unsupported image format: " + format); 307 } 308 } 309 310 private static boolean checkAndroidImageFormat(Image image) { 311 int format = image.getFormat(); 312 Plane[] planes = image.getPlanes(); 313 switch (format) { 314 case ImageFormat.YUV_420_888: 315 case ImageFormat.NV21: 316 case ImageFormat.YV12: 317 return 3 == planes.length; 318 case ImageFormat.JPEG: 319 return 1 == planes.length; 320 default: 321 return false; 322 } 323 } 324 325 public static File getOutputFile(Context context, String name) 326 throws ItsException { 327 File dir = getStorageDirectory(context, DEFAULT_CAPTURE_DIR + '/' + DEFAULT_IMAGE_DIR); 328 if (dir == null) { 329 throw new ItsException("Could not output file"); 330 } 331 return new File(dir, name); 332 } 333 334 public static String writeImageToFile(Context context, ByteBuffer buf, String name) 335 throws ItsException { 336 File imgFile = getOutputFile(context, name); 337 if (imgFile == null) { 338 throw new ItsException("Failed to get path: " + name); 339 } 340 FileChannel channel = null; 341 try { 342 channel = new FileOutputStream(imgFile, false).getChannel(); 343 channel.write(buf); 344 channel.close(); 345 } catch (FileNotFoundException e) { 346 throw new ItsException("Failed to write file: " + imgFile.toString(), e); 347 } catch (IOException e) { 348 throw new ItsException("Failed to write file: " + imgFile.toString(), e); 349 } 350 return getExternallyVisiblePath(context, imgFile.toString()); 351 } 352} 353 354