| /* | |
| * Copyright 2015 The WebRTC project authors. All Rights Reserved. | |
| * | |
| * Use of this source code is governed by a BSD-style license | |
| * that can be found in the LICENSE file in the root of the source | |
| * tree. An additional intellectual property rights grant can be found | |
| * in the file PATENTS. All contributing project authors may | |
| * be found in the AUTHORS file in the root of the source tree. | |
| */ | |
| package org.webrtc; | |
| import android.annotation.TargetApi; | |
| import android.content.Context; | |
| import android.graphics.Rect; | |
| import android.graphics.SurfaceTexture; | |
| 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.os.SystemClock; | |
| import android.util.AndroidException; | |
| import android.util.Range; | |
| import java.util.ArrayList; | |
| import java.util.HashMap; | |
| import java.util.List; | |
| import java.util.Map; | |
| import javax.annotation.Nullable; | |
| import org.webrtc.CameraEnumerationAndroid.CaptureFormat; | |
| @TargetApi(21) | |
| public class Camera2Enumerator implements CameraEnumerator { | |
| private final static String TAG = "Camera2Enumerator"; | |
| private final static double NANO_SECONDS_PER_SECOND = 1.0e9; | |
| // Each entry contains the supported formats for a given camera index. The formats are enumerated | |
| // lazily in getSupportedFormats(), and cached for future reference. | |
| private static final Map<String, List<CaptureFormat>> cachedSupportedFormats = | |
| new HashMap<String, List<CaptureFormat>>(); | |
| final Context context; | |
| @Nullable final CameraManager cameraManager; | |
| public Camera2Enumerator(Context context) { | |
| this.context = context; | |
| this.cameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE); | |
| } | |
| @Override | |
| public String[] getDeviceNames() { | |
| try { | |
| return cameraManager.getCameraIdList(); | |
| // On Android OS pre 4.4.2, a class will not load because of VerifyError if it contains a | |
| // catch statement with an Exception from a newer API, even if the code is never executed. | |
| // https://code.google.com/p/android/issues/detail?id=209129 | |
| } catch (/* CameraAccessException */ AndroidException e) { | |
| Logging.e(TAG, "Camera access exception: " + e); | |
| return new String[] {}; | |
| } | |
| } | |
| @Override | |
| public boolean isFrontFacing(String deviceName) { | |
| CameraCharacteristics characteristics = getCameraCharacteristics(deviceName); | |
| return characteristics != null | |
| && characteristics.get(CameraCharacteristics.LENS_FACING) | |
| == CameraMetadata.LENS_FACING_FRONT; | |
| } | |
| @Override | |
| public boolean isBackFacing(String deviceName) { | |
| CameraCharacteristics characteristics = getCameraCharacteristics(deviceName); | |
| return characteristics != null | |
| && characteristics.get(CameraCharacteristics.LENS_FACING) | |
| == CameraMetadata.LENS_FACING_BACK; | |
| } | |
| @Override | |
| public List<CaptureFormat> getSupportedFormats(String deviceName) { | |
| return getSupportedFormats(context, deviceName); | |
| } | |
| @Override | |
| public CameraVideoCapturer createCapturer( | |
| String deviceName, CameraVideoCapturer.CameraEventsHandler eventsHandler) { | |
| return new Camera2Capturer(context, deviceName, eventsHandler); | |
| } | |
| private @Nullable CameraCharacteristics getCameraCharacteristics(String deviceName) { | |
| try { | |
| return cameraManager.getCameraCharacteristics(deviceName); | |
| // On Android OS pre 4.4.2, a class will not load because of VerifyError if it contains a | |
| // catch statement with an Exception from a newer API, even if the code is never executed. | |
| // https://code.google.com/p/android/issues/detail?id=209129 | |
| } catch (/* CameraAccessException */ AndroidException e) { | |
| Logging.e(TAG, "Camera access exception: " + e); | |
| return null; | |
| } | |
| } | |
| /** | |
| * Checks if API is supported and all cameras have better than legacy support. | |
| */ | |
| public static boolean isSupported(Context context) { | |
| if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { | |
| return false; | |
| } | |
| CameraManager cameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE); | |
| try { | |
| String[] cameraIds = cameraManager.getCameraIdList(); | |
| for (String id : cameraIds) { | |
| CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(id); | |
| if (characteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL) | |
| == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY) { | |
| return false; | |
| } | |
| } | |
| // On Android OS pre 4.4.2, a class will not load because of VerifyError if it contains a | |
| // catch statement with an Exception from a newer API, even if the code is never executed. | |
| // https://code.google.com/p/android/issues/detail?id=209129 | |
| } catch (/* CameraAccessException */ AndroidException e) { | |
| Logging.e(TAG, "Camera access exception: " + e); | |
| return false; | |
| } | |
| return true; | |
| } | |
| static int getFpsUnitFactor(Range<Integer>[] fpsRanges) { | |
| if (fpsRanges.length == 0) { | |
| return 1000; | |
| } | |
| return fpsRanges[0].getUpper() < 1000 ? 1000 : 1; | |
| } | |
| static List<Size> getSupportedSizes(CameraCharacteristics cameraCharacteristics) { | |
| final StreamConfigurationMap streamMap = | |
| cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); | |
| final int supportLevel = | |
| cameraCharacteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL); | |
| final android.util.Size[] nativeSizes = streamMap.getOutputSizes(SurfaceTexture.class); | |
| final List<Size> sizes = convertSizes(nativeSizes); | |
| // Video may be stretched pre LMR1 on legacy implementations. | |
| // Filter out formats that have different aspect ratio than the sensor array. | |
| if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP_MR1 | |
| && supportLevel == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY) { | |
| final Rect activeArraySize = | |
| cameraCharacteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE); | |
| final ArrayList<Size> filteredSizes = new ArrayList<Size>(); | |
| for (Size size : sizes) { | |
| if (activeArraySize.width() * size.height == activeArraySize.height() * size.width) { | |
| filteredSizes.add(size); | |
| } | |
| } | |
| return filteredSizes; | |
| } else { | |
| return sizes; | |
| } | |
| } | |
| static List<CaptureFormat> getSupportedFormats(Context context, String cameraId) { | |
| return getSupportedFormats( | |
| (CameraManager) context.getSystemService(Context.CAMERA_SERVICE), cameraId); | |
| } | |
| static List<CaptureFormat> getSupportedFormats(CameraManager cameraManager, String cameraId) { | |
| synchronized (cachedSupportedFormats) { | |
| if (cachedSupportedFormats.containsKey(cameraId)) { | |
| return cachedSupportedFormats.get(cameraId); | |
| } | |
| Logging.d(TAG, "Get supported formats for camera index " + cameraId + "."); | |
| final long startTimeMs = SystemClock.elapsedRealtime(); | |
| final CameraCharacteristics cameraCharacteristics; | |
| try { | |
| cameraCharacteristics = cameraManager.getCameraCharacteristics(cameraId); | |
| } catch (Exception ex) { | |
| Logging.e(TAG, "getCameraCharacteristics(): " + ex); | |
| return new ArrayList<CaptureFormat>(); | |
| } | |
| final StreamConfigurationMap streamMap = | |
| cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); | |
| Range<Integer>[] fpsRanges = | |
| cameraCharacteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES); | |
| List<CaptureFormat.FramerateRange> framerateRanges = | |
| convertFramerates(fpsRanges, getFpsUnitFactor(fpsRanges)); | |
| List<Size> sizes = getSupportedSizes(cameraCharacteristics); | |
| int defaultMaxFps = 0; | |
| for (CaptureFormat.FramerateRange framerateRange : framerateRanges) { | |
| defaultMaxFps = Math.max(defaultMaxFps, framerateRange.max); | |
| } | |
| final List<CaptureFormat> formatList = new ArrayList<CaptureFormat>(); | |
| for (Size size : sizes) { | |
| long minFrameDurationNs = 0; | |
| try { | |
| minFrameDurationNs = streamMap.getOutputMinFrameDuration( | |
| SurfaceTexture.class, new android.util.Size(size.width, size.height)); | |
| } catch (Exception e) { | |
| // getOutputMinFrameDuration() is not supported on all devices. Ignore silently. | |
| } | |
| final int maxFps = (minFrameDurationNs == 0) | |
| ? defaultMaxFps | |
| : (int) Math.round(NANO_SECONDS_PER_SECOND / minFrameDurationNs) * 1000; | |
| formatList.add(new CaptureFormat(size.width, size.height, 0, maxFps)); | |
| Logging.d(TAG, "Format: " + size.width + "x" + size.height + "@" + maxFps); | |
| } | |
| cachedSupportedFormats.put(cameraId, formatList); | |
| final long endTimeMs = SystemClock.elapsedRealtime(); | |
| Logging.d(TAG, "Get supported formats for camera index " + cameraId + " done." | |
| + " Time spent: " + (endTimeMs - startTimeMs) + " ms."); | |
| return formatList; | |
| } | |
| } | |
| // Convert from android.util.Size to Size. | |
| private static List<Size> convertSizes(android.util.Size[] cameraSizes) { | |
| final List<Size> sizes = new ArrayList<Size>(); | |
| for (android.util.Size size : cameraSizes) { | |
| sizes.add(new Size(size.getWidth(), size.getHeight())); | |
| } | |
| return sizes; | |
| } | |
| // Convert from android.util.Range<Integer> to CaptureFormat.FramerateRange. | |
| static List<CaptureFormat.FramerateRange> convertFramerates( | |
| Range<Integer>[] arrayRanges, int unitFactor) { | |
| final List<CaptureFormat.FramerateRange> ranges = new ArrayList<CaptureFormat.FramerateRange>(); | |
| for (Range<Integer> range : arrayRanges) { | |
| ranges.add(new CaptureFormat.FramerateRange( | |
| range.getLower() * unitFactor, range.getUpper() * unitFactor)); | |
| } | |
| return ranges; | |
| } | |
| } |