1 /* 2 * Copyright (C) 2014 ZXing authors 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.google.zxing.client.android.camera; 18 19 import android.graphics.Point; 20 import android.graphics.Rect; 21 import android.hardware.Camera; 22 import android.os.Build; 23 import android.util.Log; 24 25 import java.util.Arrays; 26 import java.util.Collection; 27 import java.util.Collections; 28 import java.util.Iterator; 29 import java.util.List; 30 import java.util.regex.Pattern; 31 32 /** 33 * Utility methods for configuring the Android camera. 34 * 35 * @author Sean Owen 36 */ 37 @SuppressWarnings("deprecation") // camera APIs 38 public final class CameraConfigurationUtils { 39 40 private static final String TAG = "CameraConfiguration"; 41 42 private static final Pattern SEMICOLON = Pattern.compile(";"); 43 44 private static final int MIN_PREVIEW_PIXELS = 480 * 320; // normal screen 45 private static final float MAX_EXPOSURE_COMPENSATION = 1.5f; 46 private static final float MIN_EXPOSURE_COMPENSATION = 0.0f; 47 private static final double MAX_ASPECT_DISTORTION = 0.15; 48 private static final int MIN_FPS = 10; 49 private static final int MAX_FPS = 20; 50 private static final int AREA_PER_1000 = 400; 51 CameraConfigurationUtils()52 private CameraConfigurationUtils() { 53 } 54 setFocus(Camera.Parameters parameters, boolean autoFocus, boolean disableContinuous, boolean safeMode)55 public static void setFocus(Camera.Parameters parameters, 56 boolean autoFocus, 57 boolean disableContinuous, 58 boolean safeMode) { 59 List<String> supportedFocusModes = parameters.getSupportedFocusModes(); 60 String focusMode = null; 61 if (autoFocus) { 62 if (safeMode || disableContinuous) { 63 focusMode = findSettableValue("focus mode", 64 supportedFocusModes, 65 Camera.Parameters.FOCUS_MODE_AUTO); 66 } else { 67 focusMode = findSettableValue("focus mode", 68 supportedFocusModes, 69 Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE, 70 Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO, 71 Camera.Parameters.FOCUS_MODE_AUTO); 72 } 73 } 74 // Maybe selected auto-focus but not available, so fall through here: 75 if (!safeMode && focusMode == null) { 76 focusMode = findSettableValue("focus mode", 77 supportedFocusModes, 78 Camera.Parameters.FOCUS_MODE_MACRO, 79 Camera.Parameters.FOCUS_MODE_EDOF); 80 } 81 if (focusMode != null) { 82 if (focusMode.equals(parameters.getFocusMode())) { 83 Log.i(TAG, "Focus mode already set to " + focusMode); 84 } else { 85 parameters.setFocusMode(focusMode); 86 } 87 } 88 } 89 setTorch(Camera.Parameters parameters, boolean on)90 public static void setTorch(Camera.Parameters parameters, boolean on) { 91 List<String> supportedFlashModes = parameters.getSupportedFlashModes(); 92 String flashMode; 93 if (on) { 94 flashMode = findSettableValue("flash mode", 95 supportedFlashModes, 96 Camera.Parameters.FLASH_MODE_TORCH, 97 Camera.Parameters.FLASH_MODE_ON); 98 } else { 99 flashMode = findSettableValue("flash mode", 100 supportedFlashModes, 101 Camera.Parameters.FLASH_MODE_OFF); 102 } 103 if (flashMode != null) { 104 if (flashMode.equals(parameters.getFlashMode())) { 105 Log.i(TAG, "Flash mode already set to " + flashMode); 106 } else { 107 Log.i(TAG, "Setting flash mode to " + flashMode); 108 parameters.setFlashMode(flashMode); 109 } 110 } 111 } 112 setBestExposure(Camera.Parameters parameters, boolean lightOn)113 public static void setBestExposure(Camera.Parameters parameters, boolean lightOn) { 114 int minExposure = parameters.getMinExposureCompensation(); 115 int maxExposure = parameters.getMaxExposureCompensation(); 116 float step = parameters.getExposureCompensationStep(); 117 if ((minExposure != 0 || maxExposure != 0) && step > 0.0f) { 118 // Set low when light is on 119 float targetCompensation = lightOn ? MIN_EXPOSURE_COMPENSATION : MAX_EXPOSURE_COMPENSATION; 120 int compensationSteps = Math.round(targetCompensation / step); 121 float actualCompensation = step * compensationSteps; 122 // Clamp value: 123 compensationSteps = Math.max(Math.min(compensationSteps, maxExposure), minExposure); 124 if (parameters.getExposureCompensation() == compensationSteps) { 125 Log.i(TAG, "Exposure compensation already set to " + compensationSteps + " / " + actualCompensation); 126 } else { 127 Log.i(TAG, "Setting exposure compensation to " + compensationSteps + " / " + actualCompensation); 128 parameters.setExposureCompensation(compensationSteps); 129 } 130 } else { 131 Log.i(TAG, "Camera does not support exposure compensation"); 132 } 133 } 134 setBestPreviewFPS(Camera.Parameters parameters)135 public static void setBestPreviewFPS(Camera.Parameters parameters) { 136 setBestPreviewFPS(parameters, MIN_FPS, MAX_FPS); 137 } 138 setBestPreviewFPS(Camera.Parameters parameters, int minFPS, int maxFPS)139 public static void setBestPreviewFPS(Camera.Parameters parameters, int minFPS, int maxFPS) { 140 List<int[]> supportedPreviewFpsRanges = parameters.getSupportedPreviewFpsRange(); 141 Log.i(TAG, "Supported FPS ranges: " + toString(supportedPreviewFpsRanges)); 142 if (supportedPreviewFpsRanges != null && !supportedPreviewFpsRanges.isEmpty()) { 143 int[] suitableFPSRange = null; 144 for (int[] fpsRange : supportedPreviewFpsRanges) { 145 int thisMin = fpsRange[Camera.Parameters.PREVIEW_FPS_MIN_INDEX]; 146 int thisMax = fpsRange[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]; 147 if (thisMin >= minFPS * 1000 && thisMax <= maxFPS * 1000) { 148 suitableFPSRange = fpsRange; 149 break; 150 } 151 } 152 if (suitableFPSRange == null) { 153 Log.i(TAG, "No suitable FPS range?"); 154 } else { 155 int[] currentFpsRange = new int[2]; 156 parameters.getPreviewFpsRange(currentFpsRange); 157 if (Arrays.equals(currentFpsRange, suitableFPSRange)) { 158 Log.i(TAG, "FPS range already set to " + Arrays.toString(suitableFPSRange)); 159 } else { 160 Log.i(TAG, "Setting FPS range to " + Arrays.toString(suitableFPSRange)); 161 parameters.setPreviewFpsRange(suitableFPSRange[Camera.Parameters.PREVIEW_FPS_MIN_INDEX], 162 suitableFPSRange[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]); 163 } 164 } 165 } 166 } 167 setFocusArea(Camera.Parameters parameters)168 public static void setFocusArea(Camera.Parameters parameters) { 169 if (parameters.getMaxNumFocusAreas() > 0) { 170 Log.i(TAG, "Old focus areas: " + toString(parameters.getFocusAreas())); 171 List<Camera.Area> middleArea = buildMiddleArea(); 172 Log.i(TAG, "Setting focus area to : " + toString(middleArea)); 173 parameters.setFocusAreas(middleArea); 174 } else { 175 Log.i(TAG, "Device does not support focus areas"); 176 } 177 } 178 setMetering(Camera.Parameters parameters)179 public static void setMetering(Camera.Parameters parameters) { 180 if (parameters.getMaxNumMeteringAreas() > 0) { 181 Log.i(TAG, "Old metering areas: " + parameters.getMeteringAreas()); 182 List<Camera.Area> middleArea = buildMiddleArea(); 183 Log.i(TAG, "Setting metering area to : " + toString(middleArea)); 184 parameters.setMeteringAreas(middleArea); 185 } else { 186 Log.i(TAG, "Device does not support metering areas"); 187 } 188 } 189 buildMiddleArea()190 private static List<Camera.Area> buildMiddleArea() { 191 return Collections.singletonList( 192 new Camera.Area(new Rect(-AREA_PER_1000, -AREA_PER_1000, AREA_PER_1000, AREA_PER_1000), 1)); 193 } 194 setVideoStabilization(Camera.Parameters parameters)195 public static void setVideoStabilization(Camera.Parameters parameters) { 196 if (parameters.isVideoStabilizationSupported()) { 197 if (parameters.getVideoStabilization()) { 198 Log.i(TAG, "Video stabilization already enabled"); 199 } else { 200 Log.i(TAG, "Enabling video stabilization..."); 201 parameters.setVideoStabilization(true); 202 } 203 } else { 204 Log.i(TAG, "This device does not support video stabilization"); 205 } 206 } 207 setBarcodeSceneMode(Camera.Parameters parameters)208 public static void setBarcodeSceneMode(Camera.Parameters parameters) { 209 if (Camera.Parameters.SCENE_MODE_BARCODE.equals(parameters.getSceneMode())) { 210 Log.i(TAG, "Barcode scene mode already set"); 211 return; 212 } 213 String sceneMode = findSettableValue("scene mode", 214 parameters.getSupportedSceneModes(), 215 Camera.Parameters.SCENE_MODE_BARCODE); 216 if (sceneMode != null) { 217 parameters.setSceneMode(sceneMode); 218 } 219 } 220 setZoom(Camera.Parameters parameters, double targetZoomRatio)221 public static void setZoom(Camera.Parameters parameters, double targetZoomRatio) { 222 if (parameters.isZoomSupported()) { 223 Integer zoom = indexOfClosestZoom(parameters, targetZoomRatio); 224 if (zoom == null) { 225 return; 226 } 227 if (parameters.getZoom() == zoom) { 228 Log.i(TAG, "Zoom is already set to " + zoom); 229 } else { 230 Log.i(TAG, "Setting zoom to " + zoom); 231 parameters.setZoom(zoom); 232 } 233 } else { 234 Log.i(TAG, "Zoom is not supported"); 235 } 236 } 237 indexOfClosestZoom(Camera.Parameters parameters, double targetZoomRatio)238 private static Integer indexOfClosestZoom(Camera.Parameters parameters, double targetZoomRatio) { 239 List<Integer> ratios = parameters.getZoomRatios(); 240 Log.i(TAG, "Zoom ratios: " + ratios); 241 int maxZoom = parameters.getMaxZoom(); 242 if (ratios == null || ratios.isEmpty() || ratios.size() != maxZoom + 1) { 243 Log.w(TAG, "Invalid zoom ratios!"); 244 return null; 245 } 246 double target100 = 100.0 * targetZoomRatio; 247 double smallestDiff = Double.POSITIVE_INFINITY; 248 int closestIndex = 0; 249 for (int i = 0; i < ratios.size(); i++) { 250 double diff = Math.abs(ratios.get(i) - target100); 251 if (diff < smallestDiff) { 252 smallestDiff = diff; 253 closestIndex = i; 254 } 255 } 256 Log.i(TAG, "Chose zoom ratio of " + (ratios.get(closestIndex) / 100.0)); 257 return closestIndex; 258 } 259 setInvertColor(Camera.Parameters parameters)260 public static void setInvertColor(Camera.Parameters parameters) { 261 if (Camera.Parameters.EFFECT_NEGATIVE.equals(parameters.getColorEffect())) { 262 Log.i(TAG, "Negative effect already set"); 263 return; 264 } 265 String colorMode = findSettableValue("color effect", 266 parameters.getSupportedColorEffects(), 267 Camera.Parameters.EFFECT_NEGATIVE); 268 if (colorMode != null) { 269 parameters.setColorEffect(colorMode); 270 } 271 } 272 findBestPreviewSizeValue(Camera.Parameters parameters, Point screenResolution)273 public static Point findBestPreviewSizeValue(Camera.Parameters parameters, Point screenResolution) { 274 275 List<Camera.Size> rawSupportedSizes = parameters.getSupportedPreviewSizes(); 276 if (rawSupportedSizes == null) { 277 Log.w(TAG, "Device returned no supported preview sizes; using default"); 278 Camera.Size defaultSize = parameters.getPreviewSize(); 279 if (defaultSize == null) { 280 throw new IllegalStateException("Parameters contained no preview size!"); 281 } 282 return new Point(defaultSize.width, defaultSize.height); 283 } 284 285 if (Log.isLoggable(TAG, Log.INFO)) { 286 StringBuilder previewSizesString = new StringBuilder(); 287 for (Camera.Size size : rawSupportedSizes) { 288 previewSizesString.append(size.width).append('x').append(size.height).append(' '); 289 } 290 Log.i(TAG, "Supported preview sizes: " + previewSizesString); 291 } 292 293 double screenAspectRatio = screenResolution.x / (double) screenResolution.y; 294 295 // Find a suitable size, with max resolution 296 int maxResolution = 0; 297 Camera.Size maxResPreviewSize = null; 298 for (Camera.Size size : rawSupportedSizes) { 299 int realWidth = size.width; 300 int realHeight = size.height; 301 int resolution = realWidth * realHeight; 302 if (resolution < MIN_PREVIEW_PIXELS) { 303 continue; 304 } 305 306 boolean isCandidatePortrait = realWidth < realHeight; 307 int maybeFlippedWidth = isCandidatePortrait ? realHeight : realWidth; 308 int maybeFlippedHeight = isCandidatePortrait ? realWidth : realHeight; 309 double aspectRatio = maybeFlippedWidth / (double) maybeFlippedHeight; 310 double distortion = Math.abs(aspectRatio - screenAspectRatio); 311 if (distortion > MAX_ASPECT_DISTORTION) { 312 continue; 313 } 314 315 if (maybeFlippedWidth == screenResolution.x && maybeFlippedHeight == screenResolution.y) { 316 Point exactPoint = new Point(realWidth, realHeight); 317 Log.i(TAG, "Found preview size exactly matching screen size: " + exactPoint); 318 return exactPoint; 319 } 320 321 // Resolution is suitable; record the one with max resolution 322 if (resolution > maxResolution) { 323 maxResolution = resolution; 324 maxResPreviewSize = size; 325 } 326 } 327 328 // If no exact match, use largest preview size. This was not a great idea on older devices because 329 // of the additional computation needed. We're likely to get here on newer Android 4+ devices, where 330 // the CPU is much more powerful. 331 if (maxResPreviewSize != null) { 332 Point largestSize = new Point(maxResPreviewSize.width, maxResPreviewSize.height); 333 Log.i(TAG, "Using largest suitable preview size: " + largestSize); 334 return largestSize; 335 } 336 337 // If there is nothing at all suitable, return current preview size 338 Camera.Size defaultPreview = parameters.getPreviewSize(); 339 if (defaultPreview == null) { 340 throw new IllegalStateException("Parameters contained no preview size!"); 341 } 342 Point defaultSize = new Point(defaultPreview.width, defaultPreview.height); 343 Log.i(TAG, "No suitable preview sizes, using default: " + defaultSize); 344 return defaultSize; 345 } 346 findSettableValue(String name, Collection<String> supportedValues, String... desiredValues)347 private static String findSettableValue(String name, 348 Collection<String> supportedValues, 349 String... desiredValues) { 350 Log.i(TAG, "Requesting " + name + " value from among: " + Arrays.toString(desiredValues)); 351 Log.i(TAG, "Supported " + name + " values: " + supportedValues); 352 if (supportedValues != null) { 353 for (String desiredValue : desiredValues) { 354 if (supportedValues.contains(desiredValue)) { 355 Log.i(TAG, "Can set " + name + " to: " + desiredValue); 356 return desiredValue; 357 } 358 } 359 } 360 Log.i(TAG, "No supported values match"); 361 return null; 362 } 363 toString(Collection<int[]> arrays)364 private static String toString(Collection<int[]> arrays) { 365 if (arrays == null || arrays.isEmpty()) { 366 return "[]"; 367 } 368 StringBuilder buffer = new StringBuilder(); 369 buffer.append('['); 370 Iterator<int[]> it = arrays.iterator(); 371 while (it.hasNext()) { 372 buffer.append(Arrays.toString(it.next())); 373 if (it.hasNext()) { 374 buffer.append(", "); 375 } 376 } 377 buffer.append(']'); 378 return buffer.toString(); 379 } 380 toString(Iterable<Camera.Area> areas)381 private static String toString(Iterable<Camera.Area> areas) { 382 if (areas == null) { 383 return null; 384 } 385 StringBuilder result = new StringBuilder(); 386 for (Camera.Area area : areas) { 387 result.append(area.rect).append(':').append(area.weight).append(' '); 388 } 389 return result.toString(); 390 } 391 collectStats(Camera.Parameters parameters)392 public static String collectStats(Camera.Parameters parameters) { 393 return collectStats(parameters.flatten()); 394 } 395 collectStats(CharSequence flattenedParams)396 public static String collectStats(CharSequence flattenedParams) { 397 StringBuilder result = new StringBuilder(1000); 398 appendStat(result, "BOARD", Build.BOARD); 399 appendStat(result, "BRAND", Build.BRAND); 400 appendStat(result, "CPU_ABI", Build.CPU_ABI); 401 appendStat(result, "DEVICE", Build.DEVICE); 402 appendStat(result, "DISPLAY", Build.DISPLAY); 403 appendStat(result, "FINGERPRINT", Build.FINGERPRINT); 404 appendStat(result, "HOST", Build.HOST); 405 appendStat(result, "ID", Build.ID); 406 appendStat(result, "MANUFACTURER", Build.MANUFACTURER); 407 appendStat(result, "MODEL", Build.MODEL); 408 appendStat(result, "PRODUCT", Build.PRODUCT); 409 appendStat(result, "TAGS", Build.TAGS); 410 appendStat(result, "TIME", Build.TIME); 411 appendStat(result, "TYPE", Build.TYPE); 412 appendStat(result, "USER", Build.USER); 413 appendStat(result, "VERSION.CODENAME", Build.VERSION.CODENAME); 414 appendStat(result, "VERSION.INCREMENTAL", Build.VERSION.INCREMENTAL); 415 appendStat(result, "VERSION.RELEASE", Build.VERSION.RELEASE); 416 appendStat(result, "VERSION.SDK_INT", Build.VERSION.SDK_INT); 417 418 if (flattenedParams != null) { 419 String[] params = SEMICOLON.split(flattenedParams); 420 Arrays.sort(params); 421 for (String param : params) { 422 result.append(param).append('\n'); 423 } 424 } 425 426 return result.toString(); 427 } 428 appendStat(StringBuilder builder, String stat, Object value)429 private static void appendStat(StringBuilder builder, String stat, Object value) { 430 builder.append(stat).append('=').append(value).append('\n'); 431 } 432 433 } 434