/* * 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.support.annotation.Nullable; 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 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> cachedSupportedFormats = new HashMap>(); 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; } @Nullable @Override public List 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 | RuntimeException e) { Logging.e(TAG, "Failed to check if camera2 is supported", e); return false; } return true; } static int getFpsUnitFactor(Range[] fpsRanges) { if (fpsRanges.length == 0) { return 1000; } return fpsRanges[0].getUpper() < 1000 ? 1000 : 1; } static List 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 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 filteredSizes = new ArrayList(); for (Size size : sizes) { if (activeArraySize.width() * size.height == activeArraySize.height() * size.width) { filteredSizes.add(size); } } return filteredSizes; } else { return sizes; } } @Nullable static List getSupportedFormats(Context context, String cameraId) { return getSupportedFormats( (CameraManager) context.getSystemService(Context.CAMERA_SERVICE), cameraId); } @Nullable static List 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(); } final StreamConfigurationMap streamMap = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); Range[] fpsRanges = cameraCharacteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES); List framerateRanges = convertFramerates(fpsRanges, getFpsUnitFactor(fpsRanges)); List sizes = getSupportedSizes(cameraCharacteristics); int defaultMaxFps = 0; for (CaptureFormat.FramerateRange framerateRange : framerateRanges) { defaultMaxFps = Math.max(defaultMaxFps, framerateRange.max); } final List formatList = new ArrayList(); 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 convertSizes(android.util.Size[] cameraSizes) { final List sizes = new ArrayList(); for (android.util.Size size : cameraSizes) { sizes.add(new Size(size.getWidth(), size.getHeight())); } return sizes; } // Convert from android.util.Range to CaptureFormat.FramerateRange. static List convertFramerates( Range[] arrayRanges, int unitFactor) { final List ranges = new ArrayList(); for (Range range : arrayRanges) { ranges.add(new CaptureFormat.FramerateRange( range.getLower() * unitFactor, range.getUpper() * unitFactor)); } return ranges; } }