blob: 169758fb94d0e76fbdba95ceae1753c6fb7e8c63 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.media;
import android.content.Context;
import android.graphics.ImageFormat;
import android.util.Log;
import java.util.ArrayList;
import java.util.List;
/**
* This class extends the VideoCaptureCamera base class for manipulating normal
* video capture devices in Android, including receiving copies of preview
* frames via Java-allocated buffers. It also includes class BuggyDeviceHack to
* deal with troublesome devices.
**/
@SuppressWarnings("deprecation")
public class VideoCaptureAndroid extends VideoCaptureCamera {
// Some devices don't support YV12 format correctly, even with JELLY_BEAN or
// newer OS. To work around the issues on those devices, we have to request
// NV21. Some other devices have troubles with certain capture resolutions
// under a given one: for those, the resolution is swapped with a known
// good. Both are supposed to be temporary hacks.
private static class BuggyDeviceHack {
private static class IdAndSizes {
IdAndSizes(String model, String device, int minWidth, int minHeight) {
mModel = model;
mDevice = device;
mMinWidth = minWidth;
mMinHeight = minHeight;
}
public final String mModel;
public final String mDevice;
public final int mMinWidth;
public final int mMinHeight;
}
private static final IdAndSizes CAPTURESIZE_BUGGY_DEVICE_LIST[] = {
new IdAndSizes("Nexus 7", "flo", 640, 480)
};
private static final String[] COLORSPACE_BUGGY_DEVICE_LIST = {
"SAMSUNG-SGH-I747",
"ODROID-U2",
};
static void applyMinDimensions(CaptureFormat format) {
// NOTE: this can discard requested aspect ratio considerations.
for (IdAndSizes buggyDevice : CAPTURESIZE_BUGGY_DEVICE_LIST) {
if (buggyDevice.mModel.contentEquals(android.os.Build.MODEL)
&& buggyDevice.mDevice.contentEquals(android.os.Build.DEVICE)) {
format.mWidth = (buggyDevice.mMinWidth > format.mWidth)
? buggyDevice.mMinWidth : format.mWidth;
format.mHeight = (buggyDevice.mMinHeight > format.mHeight)
? buggyDevice.mMinHeight : format.mHeight;
}
}
}
static int getImageFormat() {
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.JELLY_BEAN) {
return ImageFormat.NV21;
}
for (String buggyDevice : COLORSPACE_BUGGY_DEVICE_LIST) {
if (buggyDevice.contentEquals(android.os.Build.MODEL)) {
return ImageFormat.NV21;
}
}
return ImageFormat.YV12;
}
}
private int mExpectedFrameSize;
private static final int NUM_CAPTURE_BUFFERS = 3;
private static final String TAG = "VideoCaptureAndroid";
static int getNumberOfCameras() {
return android.hardware.Camera.getNumberOfCameras();
}
static String getName(int id) {
android.hardware.Camera.CameraInfo cameraInfo = VideoCaptureCamera.getCameraInfo(id);
if (cameraInfo == null) return null;
return "camera " + id + ", facing " + (cameraInfo.facing
== android.hardware.Camera.CameraInfo.CAMERA_FACING_FRONT ? "front" : "back");
}
static CaptureFormat[] getDeviceSupportedFormats(int id) {
android.hardware.Camera camera;
try {
camera = android.hardware.Camera.open(id);
} catch (RuntimeException ex) {
Log.e(TAG, "Camera.open: " + ex);
return null;
}
android.hardware.Camera.Parameters parameters = getCameraParameters(camera);
if (parameters == null) {
return null;
}
ArrayList<CaptureFormat> formatList = new ArrayList<CaptureFormat>();
// getSupportedPreview{Formats,FpsRange,PreviewSizes}() returns Lists
// with at least one element, but when the camera is in bad state, they
// can return null pointers; in that case we use a 0 entry, so we can
// retrieve as much information as possible.
List<Integer> pixelFormats = parameters.getSupportedPreviewFormats();
if (pixelFormats == null) {
pixelFormats = new ArrayList<Integer>();
}
if (pixelFormats.size() == 0) {
pixelFormats.add(ImageFormat.UNKNOWN);
}
for (Integer previewFormat : pixelFormats) {
int pixelFormat = AndroidImageFormat.UNKNOWN;
if (previewFormat == ImageFormat.YV12) {
pixelFormat = AndroidImageFormat.YV12;
} else if (previewFormat == ImageFormat.NV21) {
continue;
}
List<int[]> listFpsRange = parameters.getSupportedPreviewFpsRange();
if (listFpsRange == null) {
listFpsRange = new ArrayList<int[]>();
}
if (listFpsRange.size() == 0) {
listFpsRange.add(new int[] {0, 0});
}
for (int[] fpsRange : listFpsRange) {
List<android.hardware.Camera.Size> supportedSizes =
parameters.getSupportedPreviewSizes();
if (supportedSizes == null) {
supportedSizes = new ArrayList<android.hardware.Camera.Size>();
}
if (supportedSizes.size() == 0) {
supportedSizes.add(camera.new Size(0, 0));
}
for (android.hardware.Camera.Size size : supportedSizes) {
formatList.add(new CaptureFormat(size.width,
size.height,
(fpsRange[1] + 999) / 1000,
pixelFormat));
}
}
}
camera.release();
return formatList.toArray(new CaptureFormat[formatList.size()]);
}
VideoCaptureAndroid(Context context,
int id,
long nativeVideoCaptureDeviceAndroid) {
super(context, id, nativeVideoCaptureDeviceAndroid);
}
@Override
protected void setCaptureParameters(
int width,
int height,
int frameRate,
android.hardware.Camera.Parameters cameraParameters) {
mCaptureFormat = new CaptureFormat(
width, height, frameRate, BuggyDeviceHack.getImageFormat());
// Hack to avoid certain capture resolutions under a minimum one,
// see http://crbug.com/305294.
BuggyDeviceHack.applyMinDimensions(mCaptureFormat);
}
@Override
protected void allocateBuffers() {
mExpectedFrameSize = mCaptureFormat.mWidth * mCaptureFormat.mHeight
* ImageFormat.getBitsPerPixel(mCaptureFormat.mPixelFormat) / 8;
for (int i = 0; i < NUM_CAPTURE_BUFFERS; i++) {
byte[] buffer = new byte[mExpectedFrameSize];
mCamera.addCallbackBuffer(buffer);
}
}
@Override
protected void setPreviewCallback(android.hardware.Camera.PreviewCallback cb) {
mCamera.setPreviewCallbackWithBuffer(cb);
}
@Override
public void onPreviewFrame(byte[] data, android.hardware.Camera camera) {
mPreviewBufferLock.lock();
try {
if (!mIsRunning) {
return;
}
if (data.length == mExpectedFrameSize) {
int rotation = getDeviceOrientation();
if (rotation != mDeviceOrientation) {
mDeviceOrientation = rotation;
}
if (mCameraFacing == android.hardware.Camera.CameraInfo.CAMERA_FACING_BACK) {
rotation = 360 - rotation;
}
rotation = (mCameraOrientation + rotation) % 360;
nativeOnFrameAvailable(mNativeVideoCaptureDeviceAndroid,
data, mExpectedFrameSize, rotation);
}
} finally {
mPreviewBufferLock.unlock();
if (camera != null) {
camera.addCallbackBuffer(data);
}
}
}
// TODO(wjia): investigate whether reading from texture could give better
// performance and frame rate, using onFrameAvailable().
}