1// Copyright 2014 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5package org.chromium.media; 6 7import android.content.Context; 8import android.graphics.ImageFormat; 9import android.util.Log; 10 11import java.util.ArrayList; 12import java.util.List; 13 14/** 15 * This class extends the VideoCapture base class for manipulating normal video 16 * capture devices in Android, including receiving copies of preview frames via 17 * Java-allocated buffers. It also includes class BuggyDeviceHack to deal with 18 * troublesome devices. 19 **/ 20@SuppressWarnings("deprecation") 21public class VideoCaptureAndroid extends VideoCapture { 22 23 // Some devices don't support YV12 format correctly, even with JELLY_BEAN or 24 // newer OS. To work around the issues on those devices, we have to request 25 // NV21. Some other devices have troubles with certain capture resolutions 26 // under a given one: for those, the resolution is swapped with a known 27 // good. Both are supposed to be temporary hacks. 28 private static class BuggyDeviceHack { 29 private static class IdAndSizes { 30 IdAndSizes(String model, String device, int minWidth, int minHeight) { 31 mModel = model; 32 mDevice = device; 33 mMinWidth = minWidth; 34 mMinHeight = minHeight; 35 } 36 public final String mModel; 37 public final String mDevice; 38 public final int mMinWidth; 39 public final int mMinHeight; 40 } 41 42 private static final IdAndSizes CAPTURESIZE_BUGGY_DEVICE_LIST[] = { 43 new IdAndSizes("Nexus 7", "flo", 640, 480) 44 }; 45 46 private static final String[] COLORSPACE_BUGGY_DEVICE_LIST = { 47 "SAMSUNG-SGH-I747", 48 "ODROID-U2", 49 }; 50 51 static void applyMinDimensions(CaptureFormat format) { 52 // NOTE: this can discard requested aspect ratio considerations. 53 for (IdAndSizes buggyDevice : CAPTURESIZE_BUGGY_DEVICE_LIST) { 54 if (buggyDevice.mModel.contentEquals(android.os.Build.MODEL) && 55 buggyDevice.mDevice.contentEquals(android.os.Build.DEVICE)) { 56 format.mWidth = (buggyDevice.mMinWidth > format.mWidth) 57 ? buggyDevice.mMinWidth : format.mWidth; 58 format.mHeight = (buggyDevice.mMinHeight > format.mHeight) 59 ? buggyDevice.mMinHeight : format.mHeight; 60 } 61 } 62 } 63 64 static int getImageFormat() { 65 if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.JELLY_BEAN) { 66 return ImageFormat.NV21; 67 } 68 69 for (String buggyDevice : COLORSPACE_BUGGY_DEVICE_LIST) { 70 if (buggyDevice.contentEquals(android.os.Build.MODEL)) { 71 return ImageFormat.NV21; 72 } 73 } 74 return ImageFormat.YV12; 75 } 76 } 77 78 private int mExpectedFrameSize; 79 private static final int NUM_CAPTURE_BUFFERS = 3; 80 private static final String TAG = "VideoCaptureAndroid"; 81 82 static CaptureFormat[] getDeviceSupportedFormats(int id) { 83 android.hardware.Camera camera; 84 try { 85 camera = android.hardware.Camera.open(id); 86 } catch (RuntimeException ex) { 87 Log.e(TAG, "Camera.open: " + ex); 88 return null; 89 } 90 android.hardware.Camera.Parameters parameters = getCameraParameters(camera); 91 if (parameters == null) { 92 return null; 93 } 94 95 ArrayList<CaptureFormat> formatList = new ArrayList<CaptureFormat>(); 96 // getSupportedPreview{Formats,FpsRange,PreviewSizes}() returns Lists 97 // with at least one element, but when the camera is in bad state, they 98 // can return null pointers; in that case we use a 0 entry, so we can 99 // retrieve as much information as possible. 100 List<Integer> pixelFormats = parameters.getSupportedPreviewFormats(); 101 if (pixelFormats == null) { 102 pixelFormats = new ArrayList<Integer>(); 103 } 104 if (pixelFormats.size() == 0) { 105 pixelFormats.add(ImageFormat.UNKNOWN); 106 } 107 for (Integer previewFormat : pixelFormats) { 108 int pixelFormat = 109 AndroidImageFormatList.ANDROID_IMAGEFORMAT_UNKNOWN; 110 if (previewFormat == ImageFormat.YV12) { 111 pixelFormat = AndroidImageFormatList.ANDROID_IMAGEFORMAT_YV12; 112 } else if (previewFormat == ImageFormat.NV21) { 113 continue; 114 } 115 116 List<int[]> listFpsRange = parameters.getSupportedPreviewFpsRange(); 117 if (listFpsRange == null) { 118 listFpsRange = new ArrayList<int[]>(); 119 } 120 if (listFpsRange.size() == 0) { 121 listFpsRange.add(new int[] {0, 0}); 122 } 123 for (int[] fpsRange : listFpsRange) { 124 List<android.hardware.Camera.Size> supportedSizes = 125 parameters.getSupportedPreviewSizes(); 126 if (supportedSizes == null) { 127 supportedSizes = new ArrayList<android.hardware.Camera.Size>(); 128 } 129 if (supportedSizes.size() == 0) { 130 supportedSizes.add(camera.new Size(0, 0)); 131 } 132 for (android.hardware.Camera.Size size : supportedSizes) { 133 formatList.add(new CaptureFormat(size.width, 134 size.height, 135 (fpsRange[1] + 999) / 1000, 136 pixelFormat)); 137 } 138 } 139 } 140 camera.release(); 141 return formatList.toArray(new CaptureFormat[formatList.size()]); 142 } 143 144 VideoCaptureAndroid(Context context, 145 int id, 146 long nativeVideoCaptureDeviceAndroid) { 147 super(context, id, nativeVideoCaptureDeviceAndroid); 148 } 149 150 @Override 151 protected void setCaptureParameters( 152 int width, 153 int height, 154 int frameRate, 155 android.hardware.Camera.Parameters cameraParameters) { 156 mCaptureFormat = new CaptureFormat( 157 width, height, frameRate, BuggyDeviceHack.getImageFormat()); 158 // Hack to avoid certain capture resolutions under a minimum one, 159 // see http://crbug.com/305294. 160 BuggyDeviceHack.applyMinDimensions(mCaptureFormat); 161 } 162 163 @Override 164 protected void allocateBuffers() { 165 mExpectedFrameSize = mCaptureFormat.mWidth * mCaptureFormat.mHeight * 166 ImageFormat.getBitsPerPixel(mCaptureFormat.mPixelFormat) / 8; 167 for (int i = 0; i < NUM_CAPTURE_BUFFERS; i++) { 168 byte[] buffer = new byte[mExpectedFrameSize]; 169 mCamera.addCallbackBuffer(buffer); 170 } 171 } 172 173 @Override 174 protected void setPreviewCallback(android.hardware.Camera.PreviewCallback cb) { 175 mCamera.setPreviewCallbackWithBuffer(cb); 176 } 177 178 @Override 179 public void onPreviewFrame(byte[] data, android.hardware.Camera camera) { 180 mPreviewBufferLock.lock(); 181 try { 182 if (!mIsRunning) { 183 return; 184 } 185 if (data.length == mExpectedFrameSize) { 186 int rotation = getDeviceOrientation(); 187 if (rotation != mDeviceOrientation) { 188 mDeviceOrientation = rotation; 189 } 190 if (mCameraFacing == android.hardware.Camera.CameraInfo.CAMERA_FACING_BACK) { 191 rotation = 360 - rotation; 192 } 193 rotation = (mCameraOrientation + rotation) % 360; 194 nativeOnFrameAvailable(mNativeVideoCaptureDeviceAndroid, 195 data, mExpectedFrameSize, rotation); 196 } 197 } finally { 198 mPreviewBufferLock.unlock(); 199 if (camera != null) { 200 camera.addCallbackBuffer(data); 201 } 202 } 203 } 204 205 // TODO(wjia): investigate whether reading from texture could give better 206 // performance and frame rate, using onFrameAvailable(). 207} 208