1 /* 2 * Copyright 2015 The WebRTC project authors. All Rights Reserved. 3 * 4 * Use of this source code is governed by a BSD-style license 5 * that can be found in the LICENSE file in the root of the source 6 * tree. An additional intellectual property rights grant can be found 7 * in the file PATENTS. All contributing project authors may 8 * be found in the AUTHORS file in the root of the source tree. 9 */ 10 11 package org.webrtc; 12 13 import static java.lang.Math.abs; 14 15 import android.graphics.ImageFormat; 16 import java.util.ArrayList; 17 import java.util.Arrays; 18 import java.util.Collections; 19 import java.util.Comparator; 20 import java.util.List; 21 22 @SuppressWarnings("deprecation") 23 public class CameraEnumerationAndroid { 24 private final static String TAG = "CameraEnumerationAndroid"; 25 26 static final ArrayList<Size> COMMON_RESOLUTIONS = new ArrayList<Size>(Arrays.asList( 27 // 0, Unknown resolution 28 new Size(160, 120), // 1, QQVGA 29 new Size(240, 160), // 2, HQVGA 30 new Size(320, 240), // 3, QVGA 31 new Size(400, 240), // 4, WQVGA 32 new Size(480, 320), // 5, HVGA 33 new Size(640, 360), // 6, nHD 34 new Size(640, 480), // 7, VGA 35 new Size(768, 480), // 8, WVGA 36 new Size(854, 480), // 9, FWVGA 37 new Size(800, 600), // 10, SVGA 38 new Size(960, 540), // 11, qHD 39 new Size(960, 640), // 12, DVGA 40 new Size(1024, 576), // 13, WSVGA 41 new Size(1024, 600), // 14, WVSGA 42 new Size(1280, 720), // 15, HD 43 new Size(1280, 1024), // 16, SXGA 44 new Size(1920, 1080), // 17, Full HD 45 new Size(1920, 1440), // 18, Full HD 4:3 46 new Size(2560, 1440), // 19, QHD 47 new Size(3840, 2160) // 20, UHD 48 )); 49 50 public static class CaptureFormat { 51 // Class to represent a framerate range. The framerate varies because of lightning conditions. 52 // The values are multiplied by 1000, so 1000 represents one frame per second. 53 public static class FramerateRange { 54 public int min; 55 public int max; 56 FramerateRange(int min, int max)57 public FramerateRange(int min, int max) { 58 this.min = min; 59 this.max = max; 60 } 61 62 @Override toString()63 public String toString() { 64 return "[" + (min / 1000.0f) + ":" + (max / 1000.0f) + "]"; 65 } 66 67 @Override equals(Object other)68 public boolean equals(Object other) { 69 if (!(other instanceof FramerateRange)) { 70 return false; 71 } 72 final FramerateRange otherFramerate = (FramerateRange) other; 73 return min == otherFramerate.min && max == otherFramerate.max; 74 } 75 76 @Override hashCode()77 public int hashCode() { 78 // Use prime close to 2^16 to avoid collisions for normal values less than 2^16. 79 return 1 + 65537 * min + max; 80 } 81 } 82 83 public final int width; 84 public final int height; 85 public final FramerateRange framerate; 86 87 // TODO(hbos): If VideoCapturer.startCapture is updated to support other image formats then this 88 // needs to be updated and VideoCapturer.getSupportedFormats need to return CaptureFormats of 89 // all imageFormats. 90 public final int imageFormat = ImageFormat.NV21; 91 CaptureFormat(int width, int height, int minFramerate, int maxFramerate)92 public CaptureFormat(int width, int height, int minFramerate, int maxFramerate) { 93 this.width = width; 94 this.height = height; 95 this.framerate = new FramerateRange(minFramerate, maxFramerate); 96 } 97 CaptureFormat(int width, int height, FramerateRange framerate)98 public CaptureFormat(int width, int height, FramerateRange framerate) { 99 this.width = width; 100 this.height = height; 101 this.framerate = framerate; 102 } 103 104 // Calculates the frame size of this capture format. frameSize()105 public int frameSize() { 106 return frameSize(width, height, imageFormat); 107 } 108 109 // Calculates the frame size of the specified image format. Currently only 110 // supporting ImageFormat.NV21. 111 // The size is width * height * number of bytes per pixel. 112 // http://developer.android.com/reference/android/hardware/Camera.html#addCallbackBuffer(byte[]) frameSize(int width, int height, int imageFormat)113 public static int frameSize(int width, int height, int imageFormat) { 114 if (imageFormat != ImageFormat.NV21) { 115 throw new UnsupportedOperationException("Don't know how to calculate " 116 + "the frame size of non-NV21 image formats."); 117 } 118 return (width * height * ImageFormat.getBitsPerPixel(imageFormat)) / 8; 119 } 120 121 @Override toString()122 public String toString() { 123 return width + "x" + height + "@" + framerate; 124 } 125 126 @Override equals(Object other)127 public boolean equals(Object other) { 128 if (!(other instanceof CaptureFormat)) { 129 return false; 130 } 131 final CaptureFormat otherFormat = (CaptureFormat) other; 132 return width == otherFormat.width && height == otherFormat.height 133 && framerate.equals(otherFormat.framerate); 134 } 135 136 @Override hashCode()137 public int hashCode() { 138 return 1 + (width * 65497 + height) * 251 + framerate.hashCode(); 139 } 140 } 141 142 // Helper class for finding the closest supported format for the two functions below. It creates a 143 // comparator based on the difference to some requested parameters, where the element with the 144 // minimum difference is the element that is closest to the requested parameters. 145 private static abstract class ClosestComparator<T> implements Comparator<T> { 146 // Difference between supported and requested parameter. diff(T supportedParameter)147 abstract int diff(T supportedParameter); 148 149 @Override compare(T t1, T t2)150 public int compare(T t1, T t2) { 151 return diff(t1) - diff(t2); 152 } 153 } 154 155 // Prefer a fps range with an upper bound close to `framerate`. Also prefer a fps range with a low 156 // lower bound, to allow the framerate to fluctuate based on lightning conditions. getClosestSupportedFramerateRange( List<CaptureFormat.FramerateRange> supportedFramerates, final int requestedFps)157 public static CaptureFormat.FramerateRange getClosestSupportedFramerateRange( 158 List<CaptureFormat.FramerateRange> supportedFramerates, final int requestedFps) { 159 return Collections.min( 160 supportedFramerates, new ClosestComparator<CaptureFormat.FramerateRange>() { 161 // Progressive penalty if the upper bound is further away than `MAX_FPS_DIFF_THRESHOLD` 162 // from requested. 163 private static final int MAX_FPS_DIFF_THRESHOLD = 5000; 164 private static final int MAX_FPS_LOW_DIFF_WEIGHT = 1; 165 private static final int MAX_FPS_HIGH_DIFF_WEIGHT = 3; 166 167 // Progressive penalty if the lower bound is bigger than `MIN_FPS_THRESHOLD`. 168 private static final int MIN_FPS_THRESHOLD = 8000; 169 private static final int MIN_FPS_LOW_VALUE_WEIGHT = 1; 170 private static final int MIN_FPS_HIGH_VALUE_WEIGHT = 4; 171 172 // Use one weight for small `value` less than `threshold`, and another weight above. 173 private int progressivePenalty(int value, int threshold, int lowWeight, int highWeight) { 174 return (value < threshold) ? value * lowWeight 175 : threshold * lowWeight + (value - threshold) * highWeight; 176 } 177 178 @Override 179 int diff(CaptureFormat.FramerateRange range) { 180 final int minFpsError = progressivePenalty( 181 range.min, MIN_FPS_THRESHOLD, MIN_FPS_LOW_VALUE_WEIGHT, MIN_FPS_HIGH_VALUE_WEIGHT); 182 final int maxFpsError = progressivePenalty(Math.abs(requestedFps * 1000 - range.max), 183 MAX_FPS_DIFF_THRESHOLD, MAX_FPS_LOW_DIFF_WEIGHT, MAX_FPS_HIGH_DIFF_WEIGHT); 184 return minFpsError + maxFpsError; 185 } 186 }); 187 } 188 getClosestSupportedSize( List<Size> supportedSizes, final int requestedWidth, final int requestedHeight)189 public static Size getClosestSupportedSize( 190 List<Size> supportedSizes, final int requestedWidth, final int requestedHeight) { 191 return Collections.min(supportedSizes, new ClosestComparator<Size>() { 192 @Override 193 int diff(Size size) { 194 return abs(requestedWidth - size.width) + abs(requestedHeight - size.height); 195 } 196 }); 197 } 198 199 // Helper method for camera classes. 200 static void reportCameraResolution(Histogram histogram, Size resolution) { 201 int index = COMMON_RESOLUTIONS.indexOf(resolution); 202 // 0 is reserved for unknown resolution, so add 1. 203 // indexOf returns -1 for unknown resolutions so it becomes 0 automatically. 204 histogram.addSample(index + 1); 205 } 206 } 207