• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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