1 /* 2 * libjingle 3 * Copyright 2015 Google Inc. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright notice, 9 * this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright notice, 11 * this list of conditions and the following disclaimer in the documentation 12 * and/or other materials provided with the distribution. 13 * 3. The name of the author may not be used to endorse or promote products 14 * derived from this software without specific prior written permission. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED 17 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 18 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO 19 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 20 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 */ 27 28 package org.webrtc; 29 30 import static java.lang.Math.abs; 31 import static java.lang.Math.ceil; 32 import android.graphics.ImageFormat; 33 34 import org.json.JSONArray; 35 import org.json.JSONException; 36 import org.json.JSONObject; 37 38 import org.webrtc.Logging; 39 40 import java.util.Collections; 41 import java.util.Comparator; 42 import java.util.List; 43 44 @SuppressWarnings("deprecation") 45 public class CameraEnumerationAndroid { 46 private final static String TAG = "CameraEnumerationAndroid"; 47 // Synchronized on |CameraEnumerationAndroid.this|. 48 private static Enumerator enumerator = new CameraEnumerator(); 49 50 public interface Enumerator { 51 /** 52 * Returns a list of supported CaptureFormats for the camera with index |cameraId|. 53 */ getSupportedFormats(int cameraId)54 List<CaptureFormat> getSupportedFormats(int cameraId); 55 } 56 setEnumerator(Enumerator enumerator)57 public static synchronized void setEnumerator(Enumerator enumerator) { 58 CameraEnumerationAndroid.enumerator = enumerator; 59 } 60 getSupportedFormats(int cameraId)61 public static synchronized List<CaptureFormat> getSupportedFormats(int cameraId) { 62 return enumerator.getSupportedFormats(cameraId); 63 } 64 65 public static class CaptureFormat { 66 public final int width; 67 public final int height; 68 public final int maxFramerate; 69 public final int minFramerate; 70 // TODO(hbos): If VideoCapturerAndroid.startCapture is updated to support 71 // other image formats then this needs to be updated and 72 // VideoCapturerAndroid.getSupportedFormats need to return CaptureFormats of 73 // all imageFormats. 74 public final int imageFormat = ImageFormat.NV21; 75 CaptureFormat(int width, int height, int minFramerate, int maxFramerate)76 public CaptureFormat(int width, int height, int minFramerate, 77 int maxFramerate) { 78 this.width = width; 79 this.height = height; 80 this.minFramerate = minFramerate; 81 this.maxFramerate = maxFramerate; 82 } 83 84 // Calculates the frame size of this capture format. frameSize()85 public int frameSize() { 86 return frameSize(width, height, imageFormat); 87 } 88 89 // Calculates the frame size of the specified image format. Currently only 90 // supporting ImageFormat.NV21. 91 // The size is width * height * number of bytes per pixel. 92 // http://developer.android.com/reference/android/hardware/Camera.html#addCallbackBuffer(byte[]) frameSize(int width, int height, int imageFormat)93 public static int frameSize(int width, int height, int imageFormat) { 94 if (imageFormat != ImageFormat.NV21) { 95 throw new UnsupportedOperationException("Don't know how to calculate " 96 + "the frame size of non-NV21 image formats."); 97 } 98 return (width * height * ImageFormat.getBitsPerPixel(imageFormat)) / 8; 99 } 100 101 @Override toString()102 public String toString() { 103 return width + "x" + height + "@[" + minFramerate + ":" + maxFramerate + "]"; 104 } 105 isSameFormat(final CaptureFormat that)106 public boolean isSameFormat(final CaptureFormat that) { 107 if (that == null) { 108 return false; 109 } 110 return width == that.width && height == that.height && maxFramerate == that.maxFramerate 111 && minFramerate == that.minFramerate; 112 } 113 } 114 115 // Returns device names that can be used to create a new VideoCapturerAndroid. getDeviceNames()116 public static String[] getDeviceNames() { 117 String[] names = new String[android.hardware.Camera.getNumberOfCameras()]; 118 for (int i = 0; i < android.hardware.Camera.getNumberOfCameras(); ++i) { 119 names[i] = getDeviceName(i); 120 } 121 return names; 122 } 123 124 // Returns number of cameras on device. getDeviceCount()125 public static int getDeviceCount() { 126 return android.hardware.Camera.getNumberOfCameras(); 127 } 128 129 // Returns the name of the camera with camera index. Returns null if the 130 // camera can not be used. getDeviceName(int index)131 public static String getDeviceName(int index) { 132 android.hardware.Camera.CameraInfo info = new android.hardware.Camera.CameraInfo(); 133 try { 134 android.hardware.Camera.getCameraInfo(index, info); 135 } catch (Exception e) { 136 Logging.e(TAG, "getCameraInfo failed on index " + index,e); 137 return null; 138 } 139 140 String facing = 141 (info.facing == android.hardware.Camera.CameraInfo.CAMERA_FACING_FRONT) ? "front" : "back"; 142 return "Camera " + index + ", Facing " + facing 143 + ", Orientation " + info.orientation; 144 } 145 146 // Returns the name of the front facing camera. Returns null if the 147 // camera can not be used or does not exist. getNameOfFrontFacingDevice()148 public static String getNameOfFrontFacingDevice() { 149 return getNameOfDevice(android.hardware.Camera.CameraInfo.CAMERA_FACING_FRONT); 150 } 151 152 // Returns the name of the back facing camera. Returns null if the 153 // camera can not be used or does not exist. getNameOfBackFacingDevice()154 public static String getNameOfBackFacingDevice() { 155 return getNameOfDevice(android.hardware.Camera.CameraInfo.CAMERA_FACING_BACK); 156 } 157 getSupportedFormatsAsJson(int id)158 public static String getSupportedFormatsAsJson(int id) throws JSONException { 159 List<CaptureFormat> formats = getSupportedFormats(id); 160 JSONArray json_formats = new JSONArray(); 161 for (CaptureFormat format : formats) { 162 JSONObject json_format = new JSONObject(); 163 json_format.put("width", format.width); 164 json_format.put("height", format.height); 165 json_format.put("framerate", (format.maxFramerate + 999) / 1000); 166 json_formats.put(json_format); 167 } 168 Logging.d(TAG, "Supported formats for camera " + id + ": " 169 + json_formats.toString(2)); 170 return json_formats.toString(); 171 } 172 173 // Helper class for finding the closest supported format for the two functions below. 174 private static abstract class ClosestComparator<T> implements Comparator<T> { 175 // Difference between supported and requested parameter. diff(T supportedParameter)176 abstract int diff(T supportedParameter); 177 178 @Override compare(T t1, T t2)179 public int compare(T t1, T t2) { 180 return diff(t1) - diff(t2); 181 } 182 } 183 getFramerateRange(android.hardware.Camera.Parameters parameters, final int framerate)184 public static int[] getFramerateRange(android.hardware.Camera.Parameters parameters, 185 final int framerate) { 186 List<int[]> listFpsRange = parameters.getSupportedPreviewFpsRange(); 187 if (listFpsRange.isEmpty()) { 188 Logging.w(TAG, "No supported preview fps range"); 189 return new int[]{0, 0}; 190 } 191 return Collections.min(listFpsRange, 192 new ClosestComparator<int[]>() { 193 @Override int diff(int[] range) { 194 final int maxFpsWeight = 10; 195 return range[android.hardware.Camera.Parameters.PREVIEW_FPS_MIN_INDEX] 196 + maxFpsWeight * abs(framerate 197 - range[android.hardware.Camera.Parameters.PREVIEW_FPS_MAX_INDEX]); 198 } 199 }); 200 } 201 202 public static android.hardware.Camera.Size getClosestSupportedSize( 203 List<android.hardware.Camera.Size> supportedSizes, final int requestedWidth, 204 final int requestedHeight) { 205 return Collections.min(supportedSizes, 206 new ClosestComparator<android.hardware.Camera.Size>() { 207 @Override int diff(android.hardware.Camera.Size size) { 208 return abs(requestedWidth - size.width) + abs(requestedHeight - size.height); 209 } 210 }); 211 } 212 213 private static String getNameOfDevice(int facing) { 214 final android.hardware.Camera.CameraInfo info = new android.hardware.Camera.CameraInfo(); 215 for (int i = 0; i < android.hardware.Camera.getNumberOfCameras(); ++i) { 216 try { 217 android.hardware.Camera.getCameraInfo(i, info); 218 if (info.facing == facing) { 219 return getDeviceName(i); 220 } 221 } catch (Exception e) { 222 Logging.e(TAG, "getCameraInfo() failed on index " + i, e); 223 } 224 } 225 return null; 226 } 227 } 228