| /* |
| * Copyright (C) 2016 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.android.devcamera; |
| |
| import android.graphics.ImageFormat; |
| import android.graphics.Rect; |
| import android.hardware.camera2.CameraCharacteristics; |
| import android.hardware.camera2.CameraManager; |
| import android.hardware.camera2.CameraMetadata; |
| import android.hardware.camera2.params.StreamConfigurationMap; |
| import android.os.Build; |
| import android.util.Log; |
| import android.util.Size; |
| import android.util.SizeF; |
| |
| /** |
| * Caches (static) information about the first/main camera. |
| * Convenience functions represent data from CameraCharacteristics. |
| */ |
| |
| public class CameraInfoCache { |
| private static final String TAG = "DevCamera_CAMINFO"; |
| |
| public static final boolean IS_NEXUS_6 = "shamu".equalsIgnoreCase(Build.DEVICE); |
| |
| public int[] noiseModes; |
| public int[] edgeModes; |
| |
| private CameraCharacteristics mCameraCharacteristics; |
| private String mCameraId; |
| private Size mLargestYuvSize; |
| private Size mLargestJpegSize; |
| private Size mRawSize; |
| private Rect mActiveArea; |
| private Integer mSensorOrientation; |
| private Integer mRawFormat; |
| private int mBestFaceMode; |
| private int mHardwareLevel; |
| |
| /** |
| * Constructor. |
| */ |
| public CameraInfoCache(CameraManager cameraMgr, boolean useFrontCamera) { |
| String[] cameralist; |
| try { |
| cameralist = cameraMgr.getCameraIdList(); |
| for (String id : cameralist) { |
| mCameraCharacteristics = cameraMgr.getCameraCharacteristics(id); |
| Integer facing = mCameraCharacteristics.get(CameraCharacteristics.LENS_FACING); |
| if (facing == (useFrontCamera ? CameraMetadata.LENS_FACING_FRONT : CameraMetadata.LENS_FACING_BACK)) { |
| mCameraId = id; |
| break; |
| } |
| } |
| } catch (Exception e) { |
| Log.e(TAG, "ERROR: Could not get camera ID list / no camera information is available: " + e); |
| return; |
| } |
| // Should have mCameraId as this point. |
| if (mCameraId == null) { |
| Log.e(TAG, "ERROR: Could not find a suitable rear or front camera."); |
| return; |
| } |
| |
| // Store YUV_420_888, JPEG, Raw info |
| StreamConfigurationMap map = mCameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); |
| int[] formats = map.getOutputFormats(); |
| long lowestStall = Long.MAX_VALUE; |
| for (int i = 0; i < formats.length; i++) { |
| if (formats[i] == ImageFormat.YUV_420_888) { |
| mLargestYuvSize = returnLargestSize(map.getOutputSizes(formats[i])); |
| } |
| if (formats[i] == ImageFormat.JPEG) { |
| mLargestJpegSize = returnLargestSize(map.getOutputSizes(formats[i])); |
| } |
| if (formats[i] == ImageFormat.RAW10 || formats[i] == ImageFormat.RAW_SENSOR) { // TODO: Add RAW12 |
| Size size = returnLargestSize(map.getOutputSizes(formats[i])); |
| long stall = map.getOutputStallDuration(formats[i], size); |
| if (stall < lowestStall) { |
| mRawFormat = formats[i]; |
| mRawSize = size; |
| lowestStall = stall; |
| } |
| } |
| } |
| |
| mActiveArea = mCameraCharacteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE); |
| |
| // Compute best face mode. |
| int[] faceModes = mCameraCharacteristics.get(CameraCharacteristics.STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES); |
| for (int i=0; i<faceModes.length; i++) { |
| if (faceModes[i] > mBestFaceMode) { |
| mBestFaceMode = faceModes[i]; |
| } |
| } |
| edgeModes = mCameraCharacteristics.get(CameraCharacteristics.EDGE_AVAILABLE_EDGE_MODES); |
| noiseModes = mCameraCharacteristics.get(CameraCharacteristics.NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES); |
| |
| // Misc stuff. |
| mHardwareLevel = mCameraCharacteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL); |
| |
| mSensorOrientation = mCameraCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION); |
| } |
| |
| boolean supportedModesContains(int[] modes, int mode) { |
| for (int m : modes) { |
| if (m == mode) return true; |
| } |
| return false; |
| } |
| |
| public int sensorOrientation() { |
| return mSensorOrientation; |
| } |
| |
| public boolean isCamera2FullModeAvailable() { |
| return isHardwareLevelAtLeast(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL); |
| } |
| |
| public boolean isHardwareLevelAtLeast(int level) { |
| // Special-case LEGACY since it has numerical value 2 |
| if (level == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY) { |
| // All devices are at least LEGACY |
| return true; |
| } |
| if (mHardwareLevel == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY) { |
| // Since level isn't LEGACY |
| return false; |
| } |
| // All other levels can be compared numerically |
| return mHardwareLevel >= level; |
| } |
| |
| public boolean isCapabilitySupported(int capability) { |
| int[] caps = mCameraCharacteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES); |
| for (int c: caps) { |
| if (c == capability) return true; |
| } |
| return false; |
| } |
| |
| public float getDiopterLow() { |
| return 0f; // Infinity |
| } |
| |
| public float getDiopterHi() { |
| Float minFocusDistance = |
| mCameraCharacteristics.get(CameraCharacteristics.LENS_INFO_MINIMUM_FOCUS_DISTANCE); |
| // LEGACY devices don't report this, but they won't report focus distance anyway, so just |
| // default to zero |
| return (minFocusDistance == null) ? 0.0f : minFocusDistance; |
| } |
| |
| /** |
| * Calculate camera device horizontal and vertical fields of view. |
| * |
| * @return horizontal and vertical field of view, in degrees. |
| */ |
| public float[] getFieldOfView() { |
| float[] availableFocalLengths = |
| mCameraCharacteristics.get(CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS); |
| float focalLength = 4.5f; // mm, default from Nexus 6P |
| if (availableFocalLengths == null || availableFocalLengths.length == 0) { |
| Log.e(TAG, "No focal length reported by camera device, assuming default " + |
| focalLength); |
| } else { |
| focalLength = availableFocalLengths[0]; |
| } |
| SizeF physicalSize = |
| mCameraCharacteristics.get(CameraCharacteristics.SENSOR_INFO_PHYSICAL_SIZE); |
| if (physicalSize == null) { |
| physicalSize = new SizeF(6.32f, 4.69f); // mm, default from Nexus 6P |
| Log.e(TAG, "No physical sensor dimensions reported by camera device, assuming default " |
| + physicalSize); |
| } |
| |
| // Only active array is actually visible, so calculate fraction of physicalSize that it takes up |
| Size pixelArraySize = mCameraCharacteristics.get(CameraCharacteristics.SENSOR_INFO_PIXEL_ARRAY_SIZE); |
| Rect activeArraySize = mCameraCharacteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE); |
| |
| float activeWidthFraction = activeArraySize.width() / (float) pixelArraySize.getWidth(); |
| float activeHeightFraction = activeArraySize.height() / (float) pixelArraySize.getHeight(); |
| |
| // Simple rectilinear lens field of view formula: |
| // angle of view = 2 * arctan ( active size / (2 * focal length) ) |
| float[] fieldOfView = new float[2]; |
| fieldOfView[0] = (float) Math.toDegrees( |
| 2 * Math.atan(physicalSize.getWidth() * activeWidthFraction / 2 / focalLength)); |
| fieldOfView[1] = (float) Math.toDegrees( |
| 2 * Math.atan(physicalSize.getHeight() * activeHeightFraction / 2 / focalLength)); |
| |
| return fieldOfView; |
| } |
| /** |
| * Private utility function. |
| */ |
| private Size returnLargestSize(Size[] sizes) { |
| Size largestSize = null; |
| int area = 0; |
| for (int j = 0; j < sizes.length; j++) { |
| if (sizes[j].getHeight() * sizes[j].getWidth() > area) { |
| area = sizes[j].getHeight() * sizes[j].getWidth(); |
| largestSize = sizes[j]; |
| } |
| } |
| return largestSize; |
| } |
| |
| public int bestFaceDetectionMode() { |
| return mBestFaceMode; |
| } |
| |
| public int faceOffsetX() { |
| return (mActiveArea.width() - mLargestYuvSize.getWidth()) / 2; |
| } |
| |
| public int faceOffsetY() { |
| return (mActiveArea.height() - mLargestYuvSize.getHeight()) / 2; |
| } |
| |
| public int activeAreaWidth() { |
| return mActiveArea.width(); |
| } |
| |
| public int activeAreaHeight() { |
| return mActiveArea.height(); |
| } |
| |
| public Rect getActiveAreaRect() { |
| return mActiveArea; |
| } |
| |
| public String getCameraId() { |
| return mCameraId; |
| } |
| |
| public Size getPreviewSize() { |
| float aspect = mLargestYuvSize.getWidth() / mLargestYuvSize.getHeight(); |
| aspect = aspect > 1f ? aspect : 1f / aspect; |
| if (aspect > 1.6) { |
| return new Size(1920, 1080); // TODO: Check available resolutions. |
| } |
| if (isHardwareLevelAtLeast(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3)) { |
| // Bigger preview size for more advanced devices |
| return new Size(1440, 1080); |
| } |
| return new Size(1280, 960); // TODO: Check available resolutions. |
| } |
| |
| public Size getJpegStreamSize() { |
| return mLargestJpegSize; |
| } |
| |
| public Size getYuvStream1Size() { |
| return mLargestYuvSize; |
| } |
| |
| public Size getYuvStream2Size() { |
| return new Size(320, 240); |
| } |
| |
| public boolean rawAvailable() { |
| return mRawSize != null; |
| } |
| public boolean isYuvReprocessingAvailable() { |
| return isCapabilitySupported( |
| CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_YUV_REPROCESSING); |
| } |
| |
| public Integer getRawFormat() { |
| return mRawFormat; |
| } |
| |
| public Size getRawStreamSize() { |
| return mRawSize; |
| } |
| |
| } |