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