1 /* 2 * Copyright 2015 The WebRTC project authors. All Rights Reserved. 3 * 4 * Use of this source code is governed by a BSD-style license 5 * that can be found in the LICENSE file in the root of the source 6 * tree. An additional intellectual property rights grant can be found 7 * in the file PATENTS. All contributing project authors may 8 * be found in the AUTHORS file in the root of the source tree. 9 */ 10 11 package org.webrtc; 12 13 import android.annotation.TargetApi; 14 import android.content.Context; 15 import android.graphics.Rect; 16 import android.graphics.SurfaceTexture; 17 import android.hardware.camera2.CameraCharacteristics; 18 import android.hardware.camera2.CameraManager; 19 import android.hardware.camera2.CameraMetadata; 20 import android.hardware.camera2.params.StreamConfigurationMap; 21 import android.os.Build; 22 import android.os.SystemClock; 23 import androidx.annotation.Nullable; 24 import android.util.AndroidException; 25 import android.util.Range; 26 import java.util.ArrayList; 27 import java.util.HashMap; 28 import java.util.List; 29 import java.util.Map; 30 import org.webrtc.CameraEnumerationAndroid.CaptureFormat; 31 32 @TargetApi(21) 33 public class Camera2Enumerator implements CameraEnumerator { 34 private final static String TAG = "Camera2Enumerator"; 35 private final static double NANO_SECONDS_PER_SECOND = 1.0e9; 36 37 // Each entry contains the supported formats for a given camera index. The formats are enumerated 38 // lazily in getSupportedFormats(), and cached for future reference. 39 private static final Map<String, List<CaptureFormat>> cachedSupportedFormats = 40 new HashMap<String, List<CaptureFormat>>(); 41 42 final Context context; 43 @Nullable final CameraManager cameraManager; 44 Camera2Enumerator(Context context)45 public Camera2Enumerator(Context context) { 46 this.context = context; 47 this.cameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE); 48 } 49 50 @Override getDeviceNames()51 public String[] getDeviceNames() { 52 try { 53 return cameraManager.getCameraIdList(); 54 // On Android OS pre 4.4.2, a class will not load because of VerifyError if it contains a 55 // catch statement with an Exception from a newer API, even if the code is never executed. 56 // https://code.google.com/p/android/issues/detail?id=209129 57 } catch (/* CameraAccessException */ AndroidException e) { 58 Logging.e(TAG, "Camera access exception: " + e); 59 return new String[] {}; 60 } 61 } 62 63 @Override isFrontFacing(String deviceName)64 public boolean isFrontFacing(String deviceName) { 65 CameraCharacteristics characteristics = getCameraCharacteristics(deviceName); 66 67 return characteristics != null 68 && characteristics.get(CameraCharacteristics.LENS_FACING) 69 == CameraMetadata.LENS_FACING_FRONT; 70 } 71 72 @Override isBackFacing(String deviceName)73 public boolean isBackFacing(String deviceName) { 74 CameraCharacteristics characteristics = getCameraCharacteristics(deviceName); 75 76 return characteristics != null 77 && characteristics.get(CameraCharacteristics.LENS_FACING) 78 == CameraMetadata.LENS_FACING_BACK; 79 } 80 81 @Override isInfrared(String deviceName)82 public boolean isInfrared(String deviceName) { 83 CameraCharacteristics characteristics = getCameraCharacteristics(deviceName); 84 85 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { 86 Integer colors = characteristics.get(CameraCharacteristics.SENSOR_INFO_COLOR_FILTER_ARRANGEMENT); 87 return colors != null && colors.equals(CameraCharacteristics.SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_NIR); 88 } 89 90 return false; 91 } 92 93 @Nullable 94 @Override getSupportedFormats(String deviceName)95 public List<CaptureFormat> getSupportedFormats(String deviceName) { 96 return getSupportedFormats(context, deviceName); 97 } 98 99 @Override createCapturer( String deviceName, CameraVideoCapturer.CameraEventsHandler eventsHandler)100 public CameraVideoCapturer createCapturer( 101 String deviceName, CameraVideoCapturer.CameraEventsHandler eventsHandler) { 102 return new Camera2Capturer(context, deviceName, eventsHandler); 103 } 104 getCameraCharacteristics(String deviceName)105 private @Nullable CameraCharacteristics getCameraCharacteristics(String deviceName) { 106 try { 107 return cameraManager.getCameraCharacteristics(deviceName); 108 // On Android OS pre 4.4.2, a class will not load because of VerifyError if it contains a 109 // catch statement with an Exception from a newer API, even if the code is never executed. 110 // https://code.google.com/p/android/issues/detail?id=209129 111 } catch (/* CameraAccessException */ AndroidException e) { 112 Logging.e(TAG, "Camera access exception: " + e); 113 return null; 114 } 115 } 116 117 /** 118 * Checks if API is supported and all cameras have better than legacy support. 119 */ isSupported(Context context)120 public static boolean isSupported(Context context) { 121 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { 122 return false; 123 } 124 125 CameraManager cameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE); 126 try { 127 String[] cameraIds = cameraManager.getCameraIdList(); 128 for (String id : cameraIds) { 129 CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(id); 130 if (characteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL) 131 == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY) { 132 return false; 133 } 134 } 135 // On Android OS pre 4.4.2, a class will not load because of VerifyError if it contains a 136 // catch statement with an Exception from a newer API, even if the code is never executed. 137 // https://code.google.com/p/android/issues/detail?id=209129 138 } catch (/* CameraAccessException */ AndroidException e) { 139 Logging.e(TAG, "Camera access exception: " + e); 140 return false; 141 } 142 return true; 143 } 144 getFpsUnitFactor(Range<Integer>[] fpsRanges)145 static int getFpsUnitFactor(Range<Integer>[] fpsRanges) { 146 if (fpsRanges.length == 0) { 147 return 1000; 148 } 149 return fpsRanges[0].getUpper() < 1000 ? 1000 : 1; 150 } 151 getSupportedSizes(CameraCharacteristics cameraCharacteristics)152 static List<Size> getSupportedSizes(CameraCharacteristics cameraCharacteristics) { 153 final StreamConfigurationMap streamMap = 154 cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); 155 final int supportLevel = 156 cameraCharacteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL); 157 158 final android.util.Size[] nativeSizes = streamMap.getOutputSizes(SurfaceTexture.class); 159 final List<Size> sizes = convertSizes(nativeSizes); 160 161 // Video may be stretched pre LMR1 on legacy implementations. 162 // Filter out formats that have different aspect ratio than the sensor array. 163 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP_MR1 164 && supportLevel == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY) { 165 final Rect activeArraySize = 166 cameraCharacteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE); 167 final ArrayList<Size> filteredSizes = new ArrayList<Size>(); 168 169 for (Size size : sizes) { 170 if (activeArraySize.width() * size.height == activeArraySize.height() * size.width) { 171 filteredSizes.add(size); 172 } 173 } 174 175 return filteredSizes; 176 } else { 177 return sizes; 178 } 179 } 180 181 @Nullable getSupportedFormats(Context context, String cameraId)182 static List<CaptureFormat> getSupportedFormats(Context context, String cameraId) { 183 return getSupportedFormats( 184 (CameraManager) context.getSystemService(Context.CAMERA_SERVICE), cameraId); 185 } 186 187 @Nullable getSupportedFormats(CameraManager cameraManager, String cameraId)188 static List<CaptureFormat> getSupportedFormats(CameraManager cameraManager, String cameraId) { 189 synchronized (cachedSupportedFormats) { 190 if (cachedSupportedFormats.containsKey(cameraId)) { 191 return cachedSupportedFormats.get(cameraId); 192 } 193 194 Logging.d(TAG, "Get supported formats for camera index " + cameraId + "."); 195 final long startTimeMs = SystemClock.elapsedRealtime(); 196 197 final CameraCharacteristics cameraCharacteristics; 198 try { 199 cameraCharacteristics = cameraManager.getCameraCharacteristics(cameraId); 200 } catch (Exception ex) { 201 Logging.e(TAG, "getCameraCharacteristics(): " + ex); 202 return new ArrayList<CaptureFormat>(); 203 } 204 205 final StreamConfigurationMap streamMap = 206 cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); 207 208 Range<Integer>[] fpsRanges = 209 cameraCharacteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES); 210 List<CaptureFormat.FramerateRange> framerateRanges = 211 convertFramerates(fpsRanges, getFpsUnitFactor(fpsRanges)); 212 List<Size> sizes = getSupportedSizes(cameraCharacteristics); 213 214 int defaultMaxFps = 0; 215 for (CaptureFormat.FramerateRange framerateRange : framerateRanges) { 216 defaultMaxFps = Math.max(defaultMaxFps, framerateRange.max); 217 } 218 219 final List<CaptureFormat> formatList = new ArrayList<CaptureFormat>(); 220 for (Size size : sizes) { 221 long minFrameDurationNs = 0; 222 try { 223 minFrameDurationNs = streamMap.getOutputMinFrameDuration( 224 SurfaceTexture.class, new android.util.Size(size.width, size.height)); 225 } catch (Exception e) { 226 // getOutputMinFrameDuration() is not supported on all devices. Ignore silently. 227 } 228 final int maxFps = (minFrameDurationNs == 0) 229 ? defaultMaxFps 230 : (int) Math.round(NANO_SECONDS_PER_SECOND / minFrameDurationNs) * 1000; 231 formatList.add(new CaptureFormat(size.width, size.height, 0, maxFps)); 232 Logging.d(TAG, "Format: " + size.width + "x" + size.height + "@" + maxFps); 233 } 234 235 cachedSupportedFormats.put(cameraId, formatList); 236 final long endTimeMs = SystemClock.elapsedRealtime(); 237 Logging.d(TAG, "Get supported formats for camera index " + cameraId + " done." 238 + " Time spent: " + (endTimeMs - startTimeMs) + " ms."); 239 return formatList; 240 } 241 } 242 243 // Convert from android.util.Size to Size. convertSizes(android.util.Size[] cameraSizes)244 private static List<Size> convertSizes(android.util.Size[] cameraSizes) { 245 final List<Size> sizes = new ArrayList<Size>(); 246 for (android.util.Size size : cameraSizes) { 247 sizes.add(new Size(size.getWidth(), size.getHeight())); 248 } 249 return sizes; 250 } 251 252 // Convert from android.util.Range<Integer> to CaptureFormat.FramerateRange. convertFramerates( Range<Integer>[] arrayRanges, int unitFactor)253 static List<CaptureFormat.FramerateRange> convertFramerates( 254 Range<Integer>[] arrayRanges, int unitFactor) { 255 final List<CaptureFormat.FramerateRange> ranges = new ArrayList<CaptureFormat.FramerateRange>(); 256 for (Range<Integer> range : arrayRanges) { 257 ranges.add(new CaptureFormat.FramerateRange( 258 range.getLower() * unitFactor, range.getUpper() * unitFactor)); 259 } 260 return ranges; 261 } 262 } 263