From c252dabbd6bc98b0f725d221e58431128d4eed07 Mon Sep 17 00:00:00 2001 From: Magnus Jedvert Date: Mon, 31 Aug 2015 13:19:03 +0200 Subject: [PATCH] CameraEnumerationAndroid: Make getSupportedFormats() an interface Enumerating camera capabilities in the deprecated android.hardware.Camera interface is really slow because of the need to open and release the camera. By making getSupportedFormats() an interface, we allow apps the opportunity to inject their own implementation, such as storing the supported formats offline in the device's internal storage. It will also be possible to add an implementation of getSupportedFormats() using the new android.hardware.Camera2 interface in a follow-up CL. R=tommi@webrtc.org Review URL: https://codereview.webrtc.org/1321903002 . Cr-Commit-Position: refs/heads/master@{#9819} --- .../org/webrtc/VideoCapturerAndroidTest.java | 6 +- .../org/webrtc/CameraEnumerationAndroid.java | 87 ++++----------- .../android/org/webrtc/CameraEnumerator.java | 102 ++++++++++++++++++ .../org/webrtc/VideoCapturerAndroid.java | 11 +- .../webrtc/java/jni/classreferenceholder.cc | 2 + talk/libjingle.gyp | 1 + 6 files changed, 129 insertions(+), 80 deletions(-) create mode 100644 talk/app/webrtc/java/android/org/webrtc/CameraEnumerator.java diff --git a/talk/app/webrtc/androidtests/src/org/webrtc/VideoCapturerAndroidTest.java b/talk/app/webrtc/androidtests/src/org/webrtc/VideoCapturerAndroidTest.java index 162fad1c3b..33b8c95986 100644 --- a/talk/app/webrtc/androidtests/src/org/webrtc/VideoCapturerAndroidTest.java +++ b/talk/app/webrtc/androidtests/src/org/webrtc/VideoCapturerAndroidTest.java @@ -276,8 +276,7 @@ public class VideoCapturerAndroidTest extends ActivityTestCase { FakeCapturerObserver observer = new FakeCapturerObserver(); String deviceName = CameraEnumerationAndroid.getDeviceName(0); - ArrayList formats = - CameraEnumerationAndroid.getSupportedFormats(0); + List formats = CameraEnumerationAndroid.getSupportedFormats(0); VideoCapturerAndroid capturer = VideoCapturerAndroid.create(deviceName, null); @@ -301,8 +300,7 @@ public class VideoCapturerAndroidTest extends ActivityTestCase { FakeCapturerObserver observer = new FakeCapturerObserver(); String deviceName = CameraEnumerationAndroid.getDeviceName(0); - ArrayList formats = - CameraEnumerationAndroid.getSupportedFormats(0); + List formats = CameraEnumerationAndroid.getSupportedFormats(0); VideoCapturerAndroid capturer = VideoCapturerAndroid.create(deviceName, null); diff --git a/talk/app/webrtc/java/android/org/webrtc/CameraEnumerationAndroid.java b/talk/app/webrtc/java/android/org/webrtc/CameraEnumerationAndroid.java index d6fa1274e4..136c232b7f 100644 --- a/talk/app/webrtc/java/android/org/webrtc/CameraEnumerationAndroid.java +++ b/talk/app/webrtc/java/android/org/webrtc/CameraEnumerationAndroid.java @@ -46,9 +46,23 @@ import java.util.List; @SuppressWarnings("deprecation") public class CameraEnumerationAndroid { private final static String TAG = "CameraEnumerationAndroid"; - // List of formats supported by all cameras. This list is filled once in order - // to be able to switch cameras. - public static List> supportedFormats; + // Synchronized on |CameraEnumerationAndroid.this|. + private static Enumerator enumerator = new CameraEnumerator(); + + public interface Enumerator { + /** + * Returns a list of supported CaptureFormats for the camera with index |cameraId|. + */ + List getSupportedFormats(int cameraId); + } + + public static synchronized void setEnumerator(Enumerator enumerator) { + CameraEnumerationAndroid.enumerator = enumerator; + } + + public static synchronized List getSupportedFormats(int cameraId) { + return enumerator.getSupportedFormats(cameraId); + } public static class CaptureFormat { public final int width; @@ -175,37 +189,8 @@ public class CameraEnumerationAndroid { return null; } - public static boolean initStatics() { - if (supportedFormats != null) - return true; - try { - Log.d(TAG, "Get supported formats."); - supportedFormats = - new ArrayList>(Camera.getNumberOfCameras()); - // Start requesting supported formats from camera with the highest index - // (back camera) first. If it fails then likely camera is in bad state. - for (int i = Camera.getNumberOfCameras() - 1; i >= 0; i--) { - ArrayList supportedFormat = getSupportedFormats(i); - if (supportedFormat.size() == 0) { - Log.e(TAG, "Fail to get supported formats for camera " + i); - supportedFormats = null; - return false; - } - supportedFormats.add(supportedFormat); - } - // Reverse the list since it is filled in reverse order. - Collections.reverse(supportedFormats); - Log.d(TAG, "Get supported formats done."); - return true; - } catch (Exception e) { - supportedFormats = null; - Log.e(TAG, "InitStatics failed",e); - } - return false; - } - public static String getSupportedFormatsAsJson(int id) throws JSONException { - List formats = supportedFormats.get(id); + List formats = getSupportedFormats(id); JSONArray json_formats = new JSONArray(); for (CaptureFormat format : formats) { JSONObject json_format = new JSONObject(); @@ -219,42 +204,6 @@ public class CameraEnumerationAndroid { return json_formats.toString(); } - // Returns a list of CaptureFormat for the camera with index id. - public static ArrayList getSupportedFormats(int id) { - ArrayList formatList = new ArrayList(); - - Camera camera; - try { - Log.d(TAG, "Opening camera " + id); - camera = Camera.open(id); - } catch (Exception e) { - Log.e(TAG, "Open camera failed on id " + id, e); - return formatList; - } - - try { - Camera.Parameters parameters; - parameters = camera.getParameters(); - // getSupportedPreviewFpsRange returns a sorted list. - List listFpsRange = parameters.getSupportedPreviewFpsRange(); - int[] range = {0, 0}; - if (listFpsRange != null) - range = listFpsRange.get(listFpsRange.size() -1); - - List supportedSizes = parameters.getSupportedPreviewSizes(); - for (Camera.Size size : supportedSizes) { - formatList.add(new CaptureFormat(size.width, size.height, - range[Camera.Parameters.PREVIEW_FPS_MIN_INDEX], - range[Camera.Parameters.PREVIEW_FPS_MAX_INDEX])); - } - } catch (Exception e) { - Log.e(TAG, "getSupportedFormats failed on id " + id, e); - } - camera.release(); - camera = null; - return formatList; - } - // Helper class for finding the closest supported format for the two functions below. private static abstract class ClosestComparator implements Comparator { // Difference between supported and requested parameter. diff --git a/talk/app/webrtc/java/android/org/webrtc/CameraEnumerator.java b/talk/app/webrtc/java/android/org/webrtc/CameraEnumerator.java new file mode 100644 index 0000000000..0e6b978c9d --- /dev/null +++ b/talk/app/webrtc/java/android/org/webrtc/CameraEnumerator.java @@ -0,0 +1,102 @@ +/* + * libjingle + * Copyright 2015 Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.webrtc; + +import android.hardware.Camera; +import android.os.SystemClock; +import android.util.Log; + +import org.webrtc.CameraEnumerationAndroid.CaptureFormat; + +import java.util.ArrayList; +import java.util.List; + +@SuppressWarnings("deprecation") +public class CameraEnumerator implements CameraEnumerationAndroid.Enumerator { + private final static String TAG = "CameraEnumerator"; + // Each entry contains the supported formats for corresponding camera index. The formats for all + // cameras are enumerated on the first call to getSupportedFormats(), and cached for future + // reference. + private List> cachedSupportedFormats; + + @Override + public List getSupportedFormats(int cameraId) { + synchronized (this) { + if (cachedSupportedFormats == null) { + cachedSupportedFormats = new ArrayList>(); + for (int i = 0; i < CameraEnumerationAndroid.getDeviceCount(); ++i) { + cachedSupportedFormats.add(enumerateFormats(i)); + } + } + } + return cachedSupportedFormats.get(cameraId); + } + + private List enumerateFormats(int cameraId) { + Log.d(TAG, "Get supported formats for camera index " + cameraId + "."); + final long startTimeMs = SystemClock.elapsedRealtime(); + final Camera.Parameters parameters; + Camera camera = null; + try { + Log.d(TAG, "Opening camera with index " + cameraId); + camera = Camera.open(cameraId); + parameters = camera.getParameters(); + } catch (RuntimeException e) { + Log.e(TAG, "Open camera failed on camera index " + cameraId, e); + return new ArrayList(); + } finally { + if (camera != null) { + camera.release(); + } + } + + final List formatList = new ArrayList(); + try { + int minFps = 0; + int maxFps = 0; + final List listFpsRange = parameters.getSupportedPreviewFpsRange(); + if (listFpsRange != null) { + // getSupportedPreviewFpsRange() returns a sorted list. Take the fps range + // corresponding to the highest fps. + final int[] range = listFpsRange.get(listFpsRange.size() - 1); + minFps = range[Camera.Parameters.PREVIEW_FPS_MIN_INDEX]; + maxFps = range[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]; + } + for (Camera.Size size : parameters.getSupportedPreviewSizes()) { + formatList.add(new CaptureFormat(size.width, size.height, minFps, maxFps)); + } + } catch (Exception e) { + Log.e(TAG, "getSupportedFormats() failed on camera index " + cameraId, e); + } + + final long endTimeMs = SystemClock.elapsedRealtime(); + Log.d(TAG, "Get supported formats for camera index " + cameraId + " done." + + " Time spent: " + (endTimeMs - startTimeMs) + " ms."); + return formatList; + } +} diff --git a/talk/app/webrtc/java/android/org/webrtc/VideoCapturerAndroid.java b/talk/app/webrtc/java/android/org/webrtc/VideoCapturerAndroid.java index d4251f6021..ffda916cbd 100644 --- a/talk/app/webrtc/java/android/org/webrtc/VideoCapturerAndroid.java +++ b/talk/app/webrtc/java/android/org/webrtc/VideoCapturerAndroid.java @@ -220,7 +220,7 @@ public class VideoCapturerAndroid extends VideoCapturer implements PreviewCallba } public synchronized List getSupportedFormats() { - return CameraEnumerationAndroid.supportedFormats.get(id); + return CameraEnumerationAndroid.getSupportedFormats(id); } // Return a list of timestamps for the frames that have been sent out, but not returned yet. @@ -234,14 +234,11 @@ public class VideoCapturerAndroid extends VideoCapturer implements PreviewCallba } // Called by native code. - // Enumerates resolution and frame rates for all cameras to be able to switch - // cameras. Initializes local variables for the camera named |deviceName| and - // starts a thread to be used for capturing. - // If deviceName is empty, the first available device is used in order to be - // compatible with the generic VideoCapturer class. + // Initializes local variables for the camera named |deviceName|. If |deviceName| is empty, the + // first available device is used in order to be compatible with the generic VideoCapturer class. synchronized boolean init(String deviceName) { Log.d(TAG, "init: " + deviceName); - if (deviceName == null || !CameraEnumerationAndroid.initStatics()) + if (deviceName == null) return false; boolean foundDevice = false; diff --git a/talk/app/webrtc/java/jni/classreferenceholder.cc b/talk/app/webrtc/java/jni/classreferenceholder.cc index 2c4f1e6358..520b5a7a19 100644 --- a/talk/app/webrtc/java/jni/classreferenceholder.cc +++ b/talk/app/webrtc/java/jni/classreferenceholder.cc @@ -71,6 +71,8 @@ ClassReferenceHolder::ClassReferenceHolder(JNIEnv* jni) { LoadClass(jni, "org/webrtc/IceCandidate"); #if defined(ANDROID) && !defined(WEBRTC_CHROMIUM_BUILD) LoadClass(jni, "android/graphics/SurfaceTexture"); + LoadClass(jni, "org/webrtc/CameraEnumerator"); + LoadClass(jni, "org/webrtc/CameraEnumerationAndroid"); LoadClass(jni, "org/webrtc/VideoCapturerAndroid"); LoadClass(jni, "org/webrtc/VideoCapturerAndroid$NativeObserver"); LoadClass(jni, "org/webrtc/EglBase"); diff --git a/talk/libjingle.gyp b/talk/libjingle.gyp index 5a61ca4ee8..339fa54695 100755 --- a/talk/libjingle.gyp +++ b/talk/libjingle.gyp @@ -141,6 +141,7 @@ # and include it here. 'android_java_files': [ 'app/webrtc/java/android/org/webrtc/CameraEnumerationAndroid.java', + 'app/webrtc/java/android/org/webrtc/CameraEnumerator.java', 'app/webrtc/java/android/org/webrtc/EglBase.java', 'app/webrtc/java/android/org/webrtc/GlRectDrawer.java', 'app/webrtc/java/android/org/webrtc/GlShader.java',