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.content.Context; 14 import android.graphics.Rect; 15 import android.graphics.SurfaceTexture; 16 import android.hardware.camera2.CameraAccessException; 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 android.util.Range; 24 import androidx.annotation.Nullable; 25 import java.util.ArrayList; 26 import java.util.Collections; 27 import java.util.HashMap; 28 import java.util.List; 29 import java.util.Map; 30 import org.webrtc.CameraEnumerationAndroid.CaptureFormat; 31 32 public class Camera2Enumerator implements CameraEnumerator { 33 private final static String TAG = "Camera2Enumerator"; 34 private final static double NANO_SECONDS_PER_SECOND = 1.0e9; 35 36 // Each entry contains the supported formats for a given camera index. The formats are enumerated 37 // lazily in getSupportedFormats(), and cached for future reference. 38 private static final Map<String, List<CaptureFormat>> cachedSupportedFormats = 39 new HashMap<String, List<CaptureFormat>>(); 40 41 final Context context; 42 @Nullable final CameraManager cameraManager; 43 Camera2Enumerator(Context context)44 public Camera2Enumerator(Context context) { 45 this.context = context; 46 this.cameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE); 47 } 48 49 @Override getDeviceNames()50 public String[] getDeviceNames() { 51 try { 52 return cameraManager.getCameraIdList(); 53 } catch (CameraAccessException e) { 54 Logging.e(TAG, "Camera access exception", e); 55 return new String[] {}; 56 } 57 } 58 59 @Override isFrontFacing(String deviceName)60 public boolean isFrontFacing(String deviceName) { 61 CameraCharacteristics characteristics = getCameraCharacteristics(deviceName); 62 63 return characteristics != null 64 && characteristics.get(CameraCharacteristics.LENS_FACING) 65 == CameraMetadata.LENS_FACING_FRONT; 66 } 67 68 @Override isBackFacing(String deviceName)69 public boolean isBackFacing(String deviceName) { 70 CameraCharacteristics characteristics = getCameraCharacteristics(deviceName); 71 72 return characteristics != null 73 && characteristics.get(CameraCharacteristics.LENS_FACING) 74 == CameraMetadata.LENS_FACING_BACK; 75 } 76 77 @Nullable 78 @Override getSupportedFormats(String deviceName)79 public List<CaptureFormat> getSupportedFormats(String deviceName) { 80 return getSupportedFormats(context, deviceName); 81 } 82 83 @Override createCapturer( String deviceName, CameraVideoCapturer.CameraEventsHandler eventsHandler)84 public CameraVideoCapturer createCapturer( 85 String deviceName, CameraVideoCapturer.CameraEventsHandler eventsHandler) { 86 return new Camera2Capturer(context, deviceName, eventsHandler); 87 } 88 getCameraCharacteristics(String deviceName)89 private @Nullable CameraCharacteristics getCameraCharacteristics(String deviceName) { 90 try { 91 return cameraManager.getCameraCharacteristics(deviceName); 92 } catch (CameraAccessException | RuntimeException e) { 93 Logging.e(TAG, "Camera access exception", e); 94 return null; 95 } 96 } 97 98 /** 99 * Checks if API is supported and all cameras have better than legacy support. 100 */ isSupported(Context context)101 public static boolean isSupported(Context context) { 102 CameraManager cameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE); 103 try { 104 String[] cameraIds = cameraManager.getCameraIdList(); 105 for (String id : cameraIds) { 106 CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(id); 107 if (characteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL) 108 == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY) { 109 return false; 110 } 111 } 112 } catch (CameraAccessException | RuntimeException e) { 113 Logging.e(TAG, "Failed to check if camera2 is supported", e); 114 return false; 115 } 116 return true; 117 } 118 getFpsUnitFactor(Range<Integer>[] fpsRanges)119 static int getFpsUnitFactor(Range<Integer>[] fpsRanges) { 120 if (fpsRanges.length == 0) { 121 return 1000; 122 } 123 return fpsRanges[0].getUpper() < 1000 ? 1000 : 1; 124 } 125 getSupportedSizes(CameraCharacteristics cameraCharacteristics)126 static List<Size> getSupportedSizes(CameraCharacteristics cameraCharacteristics) { 127 final StreamConfigurationMap streamMap = 128 cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); 129 final int supportLevel = 130 cameraCharacteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL); 131 132 final android.util.Size[] nativeSizes = streamMap.getOutputSizes(SurfaceTexture.class); 133 final List<Size> sizes = convertSizes(nativeSizes); 134 135 // Video may be stretched pre LMR1 on legacy implementations. 136 // Filter out formats that have different aspect ratio than the sensor array. 137 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP_MR1 138 && supportLevel == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY) { 139 final Rect activeArraySize = 140 cameraCharacteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE); 141 final ArrayList<Size> filteredSizes = new ArrayList<Size>(); 142 143 for (Size size : sizes) { 144 if (activeArraySize.width() * size.height == activeArraySize.height() * size.width) { 145 filteredSizes.add(size); 146 } 147 } 148 149 return filteredSizes; 150 } else { 151 return sizes; 152 } 153 } 154 155 @Nullable getSupportedFormats(Context context, String cameraId)156 static List<CaptureFormat> getSupportedFormats(Context context, String cameraId) { 157 return getSupportedFormats( 158 (CameraManager) context.getSystemService(Context.CAMERA_SERVICE), cameraId); 159 } 160 161 @Nullable getSupportedFormats(CameraManager cameraManager, String cameraId)162 static List<CaptureFormat> getSupportedFormats(CameraManager cameraManager, String cameraId) { 163 synchronized (cachedSupportedFormats) { 164 if (cachedSupportedFormats.containsKey(cameraId)) { 165 return cachedSupportedFormats.get(cameraId); 166 } 167 168 Logging.d(TAG, "Get supported formats for camera index " + cameraId + "."); 169 final long startTimeMs = SystemClock.elapsedRealtime(); 170 171 final CameraCharacteristics cameraCharacteristics; 172 try { 173 cameraCharacteristics = cameraManager.getCameraCharacteristics(cameraId); 174 } catch (Exception ex) { 175 Logging.e(TAG, "getCameraCharacteristics()", ex); 176 return new ArrayList<CaptureFormat>(); 177 } 178 179 final StreamConfigurationMap streamMap = 180 cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); 181 182 Range<Integer>[] fpsRanges = 183 cameraCharacteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES); 184 List<CaptureFormat.FramerateRange> framerateRanges = 185 convertFramerates(fpsRanges, getFpsUnitFactor(fpsRanges)); 186 List<Size> sizes = getSupportedSizes(cameraCharacteristics); 187 188 int defaultMaxFps = 0; 189 for (CaptureFormat.FramerateRange framerateRange : framerateRanges) { 190 defaultMaxFps = Math.max(defaultMaxFps, framerateRange.max); 191 } 192 193 final List<CaptureFormat> formatList = new ArrayList<CaptureFormat>(); 194 for (Size size : sizes) { 195 long minFrameDurationNs = 0; 196 try { 197 minFrameDurationNs = streamMap.getOutputMinFrameDuration( 198 SurfaceTexture.class, new android.util.Size(size.width, size.height)); 199 } catch (Exception e) { 200 // getOutputMinFrameDuration() is not supported on all devices. Ignore silently. 201 } 202 final int maxFps = (minFrameDurationNs == 0) 203 ? defaultMaxFps 204 : (int) Math.round(NANO_SECONDS_PER_SECOND / minFrameDurationNs) * 1000; 205 formatList.add(new CaptureFormat(size.width, size.height, 0, maxFps)); 206 Logging.d(TAG, "Format: " + size.width + "x" + size.height + "@" + maxFps); 207 } 208 209 cachedSupportedFormats.put(cameraId, formatList); 210 final long endTimeMs = SystemClock.elapsedRealtime(); 211 Logging.d(TAG, "Get supported formats for camera index " + cameraId + " done." 212 + " Time spent: " + (endTimeMs - startTimeMs) + " ms."); 213 return formatList; 214 } 215 } 216 217 // Convert from android.util.Size to Size. convertSizes(android.util.Size[] cameraSizes)218 private static List<Size> convertSizes(android.util.Size[] cameraSizes) { 219 if (cameraSizes == null || cameraSizes.length == 0) { 220 return Collections.emptyList(); 221 } 222 final List<Size> sizes = new ArrayList<>(cameraSizes.length); 223 for (android.util.Size size : cameraSizes) { 224 sizes.add(new Size(size.getWidth(), size.getHeight())); 225 } 226 return sizes; 227 } 228 229 // Convert from android.util.Range<Integer> to CaptureFormat.FramerateRange. convertFramerates( Range<Integer>[] arrayRanges, int unitFactor)230 static List<CaptureFormat.FramerateRange> convertFramerates( 231 Range<Integer>[] arrayRanges, int unitFactor) { 232 final List<CaptureFormat.FramerateRange> ranges = new ArrayList<CaptureFormat.FramerateRange>(); 233 for (Range<Integer> range : arrayRanges) { 234 ranges.add(new CaptureFormat.FramerateRange( 235 range.getLower() * unitFactor, range.getUpper() * unitFactor)); 236 } 237 return ranges; 238 } 239 } 240