Index: talk/app/webrtc/java/android/org/webrtc/CameraEnumerationAndroid.java |
diff --git a/talk/app/webrtc/java/android/org/webrtc/CameraEnumerationAndroid.java b/talk/app/webrtc/java/android/org/webrtc/CameraEnumerationAndroid.java |
new file mode 100644 |
index 0000000000000000000000000000000000000000..d6fa1274e415abc02311876adef05fb9de1d297d |
--- /dev/null |
+++ b/talk/app/webrtc/java/android/org/webrtc/CameraEnumerationAndroid.java |
@@ -0,0 +1,293 @@ |
+/* |
+ * 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 static java.lang.Math.abs; |
+import static java.lang.Math.ceil; |
+ |
+import android.hardware.Camera; |
+import android.util.Log; |
+import android.graphics.ImageFormat; |
+ |
+import org.json.JSONArray; |
+import org.json.JSONException; |
+import org.json.JSONObject; |
+ |
+import java.util.ArrayList; |
+import java.util.Collections; |
+import java.util.Comparator; |
+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<List<CaptureFormat>> supportedFormats; |
+ |
+ public static class CaptureFormat { |
+ public final int width; |
+ public final int height; |
+ public final int maxFramerate; |
+ public final int minFramerate; |
+ // TODO(hbos): If VideoCapturerAndroid.startCapture is updated to support |
+ // other image formats then this needs to be updated and |
+ // VideoCapturerAndroid.getSupportedFormats need to return CaptureFormats of |
+ // all imageFormats. |
+ public final int imageFormat = ImageFormat.YV12; |
+ |
+ public CaptureFormat(int width, int height, int minFramerate, |
+ int maxFramerate) { |
+ this.width = width; |
+ this.height = height; |
+ this.minFramerate = minFramerate; |
+ this.maxFramerate = maxFramerate; |
+ } |
+ |
+ // Calculates the frame size of this capture format. |
+ public int frameSize() { |
+ return frameSize(width, height, imageFormat); |
+ } |
+ |
+ // Calculates the frame size of the specified image format. Currently only |
+ // supporting ImageFormat.YV12. The YV12's stride is the closest rounded up |
+ // multiple of 16 of the width and width and height are always even. |
+ // Android guarantees this: |
+ // http://developer.android.com/reference/android/hardware/Camera.Parameters.html#setPreviewFormat%28int%29 |
+ public static int frameSize(int width, int height, int imageFormat) { |
+ if (imageFormat != ImageFormat.YV12) { |
+ throw new UnsupportedOperationException("Don't know how to calculate " |
+ + "the frame size of non-YV12 image formats."); |
+ } |
+ int yStride = roundUp(width, 16); |
+ int uvStride = roundUp(yStride / 2, 16); |
+ int ySize = yStride * height; |
+ int uvSize = uvStride * height / 2; |
+ return ySize + uvSize * 2; |
+ } |
+ |
+ // Rounds up |x| to the closest value that is a multiple of |alignment|. |
+ private static int roundUp(int x, int alignment) { |
+ return (int)ceil(x / (double)alignment) * alignment; |
+ } |
+ |
+ @Override |
+ public String toString() { |
+ return width + "x" + height + "@[" + minFramerate + ":" + maxFramerate + "]"; |
+ } |
+ |
+ @Override |
+ public boolean equals(Object that) { |
+ if (!(that instanceof CaptureFormat)) { |
+ return false; |
+ } |
+ final CaptureFormat c = (CaptureFormat) that; |
+ return width == c.width && height == c.height && maxFramerate == c.maxFramerate |
+ && minFramerate == c.minFramerate; |
+ } |
+ } |
+ |
+ // Returns device names that can be used to create a new VideoCapturerAndroid. |
+ public static String[] getDeviceNames() { |
+ String[] names = new String[Camera.getNumberOfCameras()]; |
+ for (int i = 0; i < Camera.getNumberOfCameras(); ++i) { |
+ names[i] = getDeviceName(i); |
+ } |
+ return names; |
+ } |
+ |
+ // Returns number of cameras on device. |
+ public static int getDeviceCount() { |
+ return Camera.getNumberOfCameras(); |
+ } |
+ |
+ // Returns the name of the camera with camera index. Returns null if the |
+ // camera can not be used. |
+ public static String getDeviceName(int index) { |
+ Camera.CameraInfo info = new Camera.CameraInfo(); |
+ try { |
+ Camera.getCameraInfo(index, info); |
+ } catch (Exception e) { |
+ Log.e(TAG, "getCameraInfo failed on index " + index,e); |
+ return null; |
+ } |
+ |
+ String facing = |
+ (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) ? "front" : "back"; |
+ return "Camera " + index + ", Facing " + facing |
+ + ", Orientation " + info.orientation; |
+ } |
+ |
+ // Returns the name of the front facing camera. Returns null if the |
+ // camera can not be used or does not exist. |
+ public static String getNameOfFrontFacingDevice() { |
+ for (int i = 0; i < Camera.getNumberOfCameras(); ++i) { |
+ Camera.CameraInfo info = new Camera.CameraInfo(); |
+ try { |
+ Camera.getCameraInfo(i, info); |
+ if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) |
+ return getDeviceName(i); |
+ } catch (Exception e) { |
+ Log.e(TAG, "getCameraInfo failed on index " + i, e); |
+ } |
+ } |
+ return null; |
+ } |
+ |
+ // Returns the name of the back facing camera. Returns null if the |
+ // camera can not be used or does not exist. |
+ public static String getNameOfBackFacingDevice() { |
+ for (int i = 0; i < Camera.getNumberOfCameras(); ++i) { |
+ Camera.CameraInfo info = new Camera.CameraInfo(); |
+ try { |
+ Camera.getCameraInfo(i, info); |
+ if (info.facing == Camera.CameraInfo.CAMERA_FACING_BACK) |
+ return getDeviceName(i); |
+ } catch (Exception e) { |
+ Log.e(TAG, "getCameraInfo failed on index " + i, e); |
+ } |
+ } |
+ return null; |
+ } |
+ |
+ public static boolean initStatics() { |
+ if (supportedFormats != null) |
+ return true; |
+ try { |
+ Log.d(TAG, "Get supported formats."); |
+ supportedFormats = |
+ new ArrayList<List<CaptureFormat>>(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<CaptureFormat> 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<CaptureFormat> formats = supportedFormats.get(id); |
+ JSONArray json_formats = new JSONArray(); |
+ for (CaptureFormat format : formats) { |
+ JSONObject json_format = new JSONObject(); |
+ json_format.put("width", format.width); |
+ json_format.put("height", format.height); |
+ json_format.put("framerate", (format.maxFramerate + 999) / 1000); |
+ json_formats.put(json_format); |
+ } |
+ Log.d(TAG, "Supported formats for camera " + id + ": " |
+ + json_formats.toString(2)); |
+ return json_formats.toString(); |
+ } |
+ |
+ // Returns a list of CaptureFormat for the camera with index id. |
+ public static ArrayList<CaptureFormat> getSupportedFormats(int id) { |
+ ArrayList<CaptureFormat> formatList = new ArrayList<CaptureFormat>(); |
+ |
+ 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<int[]> listFpsRange = parameters.getSupportedPreviewFpsRange(); |
+ int[] range = {0, 0}; |
+ if (listFpsRange != null) |
+ range = listFpsRange.get(listFpsRange.size() -1); |
+ |
+ List<Camera.Size> 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<T> implements Comparator<T> { |
+ // Difference between supported and requested parameter. |
+ abstract int diff(T supportedParameter); |
+ |
+ @Override |
+ public int compare(T t1, T t2) { |
+ return diff(t1) - diff(t2); |
+ } |
+ } |
+ |
+ public static int[] getFramerateRange(Camera.Parameters parameters, final int framerate) { |
+ List<int[]> listFpsRange = parameters.getSupportedPreviewFpsRange(); |
+ if (listFpsRange.isEmpty()) { |
+ Log.w(TAG, "No supported preview fps range"); |
+ return new int[]{0, 0}; |
+ } |
+ return Collections.min(listFpsRange, |
+ new ClosestComparator<int[]>() { |
+ @Override int diff(int[] range) { |
+ return abs(framerate - range[Camera.Parameters.PREVIEW_FPS_MIN_INDEX]) |
+ + abs(framerate - range[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]); |
+ } |
+ }); |
+ } |
+ |
+ public static Camera.Size getClosestSupportedSize( |
+ List<Camera.Size> supportedSizes, final int requestedWidth, final int requestedHeight) { |
+ return Collections.min(supportedSizes, |
+ new ClosestComparator<Camera.Size>() { |
+ @Override int diff(Camera.Size size) { |
+ return abs(requestedWidth - size.width) + abs(requestedHeight - size.height); |
+ } |
+ }); |
+ } |
+} |