/* * 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 static java.lang.Math.abs; import android.graphics.ImageFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; @SuppressWarnings("deprecation") public class CameraEnumerationAndroid { private final static String TAG = "CameraEnumerationAndroid"; static final ArrayList COMMON_RESOLUTIONS = new ArrayList(Arrays.asList( // 0, Unknown resolution new Size(160, 120), // 1, QQVGA new Size(240, 160), // 2, HQVGA new Size(320, 240), // 3, QVGA new Size(400, 240), // 4, WQVGA new Size(480, 320), // 5, HVGA new Size(640, 360), // 6, nHD new Size(640, 480), // 7, VGA new Size(768, 480), // 8, WVGA new Size(854, 480), // 9, FWVGA new Size(800, 600), // 10, SVGA new Size(960, 540), // 11, qHD new Size(960, 640), // 12, DVGA new Size(1024, 576), // 13, WSVGA new Size(1024, 600), // 14, WVSGA new Size(1280, 720), // 15, HD new Size(1280, 1024), // 16, SXGA new Size(1920, 1080), // 17, Full HD new Size(1920, 1440), // 18, Full HD 4:3 new Size(2560, 1440), // 19, QHD new Size(3840, 2160) // 20, UHD )); public static class CaptureFormat { // Class to represent a framerate range. The framerate varies because of lightning conditions. // The values are multiplied by 1000, so 1000 represents one frame per second. public static class FramerateRange { public int min; public int max; public FramerateRange(int min, int max) { this.min = min; this.max = max; } @Override public String toString() { return "[" + (min / 1000.0f) + ":" + (max / 1000.0f) + "]"; } @Override public boolean equals(Object other) { if (!(other instanceof FramerateRange)) { return false; } final FramerateRange otherFramerate = (FramerateRange) other; return min == otherFramerate.min && max == otherFramerate.max; } @Override public int hashCode() { // Use prime close to 2^16 to avoid collisions for normal values less than 2^16. return 1 + 65537 * min + max; } } public final int width; public final int height; public final FramerateRange framerate; // TODO(hbos): If VideoCapturer.startCapture is updated to support other image formats then this // needs to be updated and VideoCapturer.getSupportedFormats need to return CaptureFormats of // all imageFormats. public final int imageFormat = ImageFormat.NV21; public CaptureFormat(int width, int height, int minFramerate, int maxFramerate) { this.width = width; this.height = height; this.framerate = new FramerateRange(minFramerate, maxFramerate); } public CaptureFormat(int width, int height, FramerateRange framerate) { this.width = width; this.height = height; this.framerate = framerate; } // 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.NV21. // The size is width * height * number of bytes per pixel. // http://developer.android.com/reference/android/hardware/Camera.html#addCallbackBuffer(byte[]) public static int frameSize(int width, int height, int imageFormat) { if (imageFormat != ImageFormat.NV21) { throw new UnsupportedOperationException("Don't know how to calculate " + "the frame size of non-NV21 image formats."); } return (width * height * ImageFormat.getBitsPerPixel(imageFormat)) / 8; } @Override public String toString() { return width + "x" + height + "@" + framerate; } @Override public boolean equals(Object other) { if (!(other instanceof CaptureFormat)) { return false; } final CaptureFormat otherFormat = (CaptureFormat) other; return width == otherFormat.width && height == otherFormat.height && framerate.equals(otherFormat.framerate); } @Override public int hashCode() { return 1 + (width * 65497 + height) * 251 + framerate.hashCode(); } } // Helper class for finding the closest supported format for the two functions below. It creates a // comparator based on the difference to some requested parameters, where the element with the // minimum difference is the element that is closest to the requested parameters. private static abstract class ClosestComparator implements Comparator { // Difference between supported and requested parameter. abstract int diff(T supportedParameter); @Override public int compare(T t1, T t2) { return diff(t1) - diff(t2); } } // Prefer a fps range with an upper bound close to `framerate`. Also prefer a fps range with a low // lower bound, to allow the framerate to fluctuate based on lightning conditions. public static CaptureFormat.FramerateRange getClosestSupportedFramerateRange( List supportedFramerates, final int requestedFps) { return Collections.min( supportedFramerates, new ClosestComparator() { // Progressive penalty if the upper bound is further away than `MAX_FPS_DIFF_THRESHOLD` // from requested. private static final int MAX_FPS_DIFF_THRESHOLD = 5000; private static final int MAX_FPS_LOW_DIFF_WEIGHT = 1; private static final int MAX_FPS_HIGH_DIFF_WEIGHT = 3; // Progressive penalty if the lower bound is bigger than `MIN_FPS_THRESHOLD`. private static final int MIN_FPS_THRESHOLD = 8000; private static final int MIN_FPS_LOW_VALUE_WEIGHT = 1; private static final int MIN_FPS_HIGH_VALUE_WEIGHT = 4; // Use one weight for small `value` less than `threshold`, and another weight above. private int progressivePenalty(int value, int threshold, int lowWeight, int highWeight) { return (value < threshold) ? value * lowWeight : threshold * lowWeight + (value - threshold) * highWeight; } @Override int diff(CaptureFormat.FramerateRange range) { final int minFpsError = progressivePenalty( range.min, MIN_FPS_THRESHOLD, MIN_FPS_LOW_VALUE_WEIGHT, MIN_FPS_HIGH_VALUE_WEIGHT); final int maxFpsError = progressivePenalty(Math.abs(requestedFps * 1000 - range.max), MAX_FPS_DIFF_THRESHOLD, MAX_FPS_LOW_DIFF_WEIGHT, MAX_FPS_HIGH_DIFF_WEIGHT); return minFpsError + maxFpsError; } }); } public static Size getClosestSupportedSize( List supportedSizes, final int requestedWidth, final int requestedHeight) { return Collections.min(supportedSizes, new ClosestComparator() { @Override int diff(Size size) { return abs(requestedWidth - size.width) + abs(requestedHeight - size.height); } }); } // Helper method for camera classes. static void reportCameraResolution(Histogram histogram, Size resolution) { int index = COMMON_RESOLUTIONS.indexOf(resolution); // 0 is reserved for unknown resolution, so add 1. // indexOf returns -1 for unknown resolutions so it becomes 0 automatically. histogram.addSample(index + 1); } }