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.
5 package org.chromium.media;
7 import android.content.Context;
8 import android.graphics.ImageFormat;
9 import android.graphics.SurfaceTexture;
10 import android.hardware.Camera;
11 import android.hardware.Camera.PreviewCallback;
12 import android.opengl.GLES20;
13 import android.util.Log;
14 import android.view.Surface;
15 import android.view.WindowManager;
17 import org.chromium.base.CalledByNative;
18 import org.chromium.base.JNINamespace;
20 import java.io.IOException;
21 import java.util.List;
22 import java.util.concurrent.locks.ReentrantLock;
25 * Video Capture Device base class to interface to native Chromium.
27 @JNINamespace("media")
28 public abstract class VideoCapture implements PreviewCallback {
30 protected static class CaptureFormat {
34 final int mPixelFormat;
37 int width, int height, int framerate, int pixelformat) {
40 mFramerate = framerate;
41 mPixelFormat = pixelformat;
44 public int getWidth() {
48 public int getHeight() {
52 public int getFramerate() {
56 public int getPixelFormat() {
61 protected Camera mCamera;
62 protected CaptureFormat mCaptureFormat = null;
63 // Lock to mutually exclude execution of OnPreviewFrame {start/stop}Capture.
64 protected ReentrantLock mPreviewBufferLock = new ReentrantLock();
65 protected Context mContext = null;
66 // True when native code has started capture.
67 protected boolean mIsRunning = false;
70 // Native callback context variable.
71 protected long mNativeVideoCaptureDeviceAndroid;
72 protected int[] mGlTextures = null;
73 protected SurfaceTexture mSurfaceTexture = null;
74 protected static final int GL_TEXTURE_EXTERNAL_OES = 0x8D65;
76 protected int mCameraOrientation;
77 protected int mCameraFacing;
78 protected int mDeviceOrientation;
79 private static final String TAG = "VideoCapture";
81 VideoCapture(Context context,
83 long nativeVideoCaptureDeviceAndroid) {
86 mNativeVideoCaptureDeviceAndroid = nativeVideoCaptureDeviceAndroid;
90 boolean allocate(int width, int height, int frameRate) {
91 Log.d(TAG, "allocate: requested (" + width + "x" + height + ")@" +
94 mCamera = Camera.open(mId);
95 } catch (RuntimeException ex) {
96 Log.e(TAG, "allocate: Camera.open: " + ex);
100 Camera.CameraInfo cameraInfo = getCameraInfo(mId);
101 if (cameraInfo == null) {
107 mCameraOrientation = cameraInfo.orientation;
108 mCameraFacing = cameraInfo.facing;
109 mDeviceOrientation = getDeviceOrientation();
110 Log.d(TAG, "allocate: orientation dev=" + mDeviceOrientation +
111 ", cam=" + mCameraOrientation + ", facing=" + mCameraFacing);
113 Camera.Parameters parameters = getCameraParameters(mCamera);
114 if (parameters == null) {
119 // getSupportedPreviewFpsRange() returns a List with at least one
120 // element, but when camera is in bad state, it can return null pointer.
121 List<int[]> listFpsRange = parameters.getSupportedPreviewFpsRange();
122 if (listFpsRange == null || listFpsRange.size() == 0) {
123 Log.e(TAG, "allocate: no fps range found");
126 int frameRateInMs = frameRate * 1000;
127 // Use the first range as default.
128 int[] fpsMinMax = listFpsRange.get(0);
129 int newFrameRate = (fpsMinMax[0] + 999) / 1000;
130 for (int[] fpsRange : listFpsRange) {
131 if (fpsRange[0] <= frameRateInMs && frameRateInMs <= fpsRange[1]) {
132 fpsMinMax = fpsRange;
133 newFrameRate = frameRate;
137 frameRate = newFrameRate;
138 Log.d(TAG, "allocate: fps set to " + frameRate);
141 List<Camera.Size> listCameraSize =
142 parameters.getSupportedPreviewSizes();
143 int minDiff = Integer.MAX_VALUE;
144 int matchedWidth = width;
145 int matchedHeight = height;
146 for (Camera.Size size : listCameraSize) {
147 int diff = Math.abs(size.width - width) +
148 Math.abs(size.height - height);
149 Log.d(TAG, "allocate: supported (" +
150 size.width + ", " + size.height + "), diff=" + diff);
151 // TODO(wjia): Remove this hack (forcing width to be multiple
152 // of 32) by supporting stride in video frame buffer.
153 // Right now, VideoCaptureController requires compact YV12
154 // (i.e., with no padding).
155 if (diff < minDiff && (size.width % 32 == 0)) {
157 matchedWidth = size.width;
158 matchedHeight = size.height;
161 if (minDiff == Integer.MAX_VALUE) {
162 Log.e(TAG, "allocate: can not find a multiple-of-32 resolution");
165 Log.d(TAG, "allocate: matched (" + matchedWidth + "x" + matchedHeight + ")");
167 if (parameters.isVideoStabilizationSupported()) {
168 Log.d(TAG, "Image stabilization supported, currently: " +
169 parameters.getVideoStabilization() + ", setting it.");
170 parameters.setVideoStabilization(true);
172 Log.d(TAG, "Image stabilization not supported.");
175 setCaptureParameters(matchedWidth, matchedHeight, frameRate, parameters);
176 parameters.setPreviewSize(mCaptureFormat.mWidth,
177 mCaptureFormat.mHeight);
178 parameters.setPreviewFpsRange(fpsMinMax[0], fpsMinMax[1]);
179 parameters.setPreviewFormat(mCaptureFormat.mPixelFormat);
180 mCamera.setParameters(parameters);
182 // Set SurfaceTexture. Android Capture needs a SurfaceTexture even if
183 // it is not going to be used.
184 mGlTextures = new int[1];
185 // Generate one texture pointer and bind it as an external texture.
186 GLES20.glGenTextures(1, mGlTextures, 0);
187 GLES20.glBindTexture(GL_TEXTURE_EXTERNAL_OES, mGlTextures[0]);
188 // No mip-mapping with camera source.
189 GLES20.glTexParameterf(GL_TEXTURE_EXTERNAL_OES,
190 GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
191 GLES20.glTexParameterf(GL_TEXTURE_EXTERNAL_OES,
192 GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
193 // Clamp to edge is only option.
194 GLES20.glTexParameteri(GL_TEXTURE_EXTERNAL_OES,
195 GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
196 GLES20.glTexParameteri(GL_TEXTURE_EXTERNAL_OES,
197 GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
199 mSurfaceTexture = new SurfaceTexture(mGlTextures[0]);
200 mSurfaceTexture.setOnFrameAvailableListener(null);
202 mCamera.setPreviewTexture(mSurfaceTexture);
203 } catch (IOException ex) {
204 Log.e(TAG, "allocate: " + ex);
213 public int startCapture() {
214 if (mCamera == null) {
215 Log.e(TAG, "startCapture: camera is null");
219 mPreviewBufferLock.lock();
226 mPreviewBufferLock.unlock();
228 setPreviewCallback(this);
230 mCamera.startPreview();
231 } catch (RuntimeException ex) {
232 Log.e(TAG, "startCapture: Camera.startPreview: " + ex);
239 public int stopCapture() {
240 if (mCamera == null) {
241 Log.e(TAG, "stopCapture: camera is null");
245 mPreviewBufferLock.lock();
252 mPreviewBufferLock.unlock();
255 mCamera.stopPreview();
256 setPreviewCallback(null);
261 public void deallocate() {
267 mCamera.setPreviewTexture(null);
268 if (mGlTextures != null)
269 GLES20.glDeleteTextures(1, mGlTextures, 0);
270 mCaptureFormat = null;
273 } catch (IOException ex) {
274 Log.e(TAG, "deallocate: failed to deallocate camera, " + ex);
279 // Local hook to allow derived classes to fill capture format and modify
280 // camera parameters as they see fit.
281 abstract void setCaptureParameters(
285 Camera.Parameters cameraParameters);
287 // Local hook to allow derived classes to configure and plug capture
288 // buffers if needed.
289 abstract void allocateBuffers();
291 // Local method to be overriden with the particular setPreviewCallback to be
292 // used in the implementations.
293 abstract void setPreviewCallback(Camera.PreviewCallback cb);
296 public int queryWidth() {
297 return mCaptureFormat.mWidth;
301 public int queryHeight() {
302 return mCaptureFormat.mHeight;
306 public int queryFrameRate() {
307 return mCaptureFormat.mFramerate;
311 public int getColorspace() {
312 switch (mCaptureFormat.mPixelFormat) {
313 case ImageFormat.YV12:
314 return AndroidImageFormatList.ANDROID_IMAGEFORMAT_YV12;
315 case ImageFormat.NV21:
316 return AndroidImageFormatList.ANDROID_IMAGEFORMAT_NV21;
317 case ImageFormat.UNKNOWN:
319 return AndroidImageFormatList.ANDROID_IMAGEFORMAT_UNKNOWN;
323 protected int getDeviceOrientation() {
325 if (mContext != null) {
326 WindowManager wm = (WindowManager) mContext.getSystemService(
327 Context.WINDOW_SERVICE);
328 switch(wm.getDefaultDisplay().getRotation()) {
329 case Surface.ROTATION_90:
332 case Surface.ROTATION_180:
335 case Surface.ROTATION_270:
338 case Surface.ROTATION_0:
347 // Method for VideoCapture implementations to call back native code.
348 public native void nativeOnFrameAvailable(
349 long nativeVideoCaptureDeviceAndroid,
354 protected static Camera.Parameters getCameraParameters(Camera camera) {
355 Camera.Parameters parameters;
357 parameters = camera.getParameters();
358 } catch (RuntimeException ex) {
359 Log.e(TAG, "getCameraParameters: Camera.getParameters: " + ex);
366 private Camera.CameraInfo getCameraInfo(int id) {
367 Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
369 Camera.getCameraInfo(id, cameraInfo);
370 } catch (RuntimeException ex) {
371 Log.e(TAG, "getCameraInfo: Camera.getCameraInfo: " + ex);