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);
181 mCamera.setParameters(parameters);
182 } catch (RuntimeException ex) {
183 Log.e(TAG, "setParameters: " + ex);
187 // Set SurfaceTexture. Android Capture needs a SurfaceTexture even if
188 // it is not going to be used.
189 mGlTextures = new int[1];
190 // Generate one texture pointer and bind it as an external texture.
191 GLES20.glGenTextures(1, mGlTextures, 0);
192 GLES20.glBindTexture(GL_TEXTURE_EXTERNAL_OES, mGlTextures[0]);
193 // No mip-mapping with camera source.
194 GLES20.glTexParameterf(GL_TEXTURE_EXTERNAL_OES,
195 GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
196 GLES20.glTexParameterf(GL_TEXTURE_EXTERNAL_OES,
197 GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
198 // Clamp to edge is only option.
199 GLES20.glTexParameteri(GL_TEXTURE_EXTERNAL_OES,
200 GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
201 GLES20.glTexParameteri(GL_TEXTURE_EXTERNAL_OES,
202 GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
204 mSurfaceTexture = new SurfaceTexture(mGlTextures[0]);
205 mSurfaceTexture.setOnFrameAvailableListener(null);
207 mCamera.setPreviewTexture(mSurfaceTexture);
208 } catch (IOException ex) {
209 Log.e(TAG, "allocate: " + ex);
218 public int startCapture() {
219 if (mCamera == null) {
220 Log.e(TAG, "startCapture: camera is null");
224 mPreviewBufferLock.lock();
231 mPreviewBufferLock.unlock();
233 setPreviewCallback(this);
235 mCamera.startPreview();
236 } catch (RuntimeException ex) {
237 Log.e(TAG, "startCapture: Camera.startPreview: " + ex);
244 public int stopCapture() {
245 if (mCamera == null) {
246 Log.e(TAG, "stopCapture: camera is null");
250 mPreviewBufferLock.lock();
257 mPreviewBufferLock.unlock();
260 mCamera.stopPreview();
261 setPreviewCallback(null);
266 public void deallocate() {
272 mCamera.setPreviewTexture(null);
273 if (mGlTextures != null)
274 GLES20.glDeleteTextures(1, mGlTextures, 0);
275 mCaptureFormat = null;
278 } catch (IOException ex) {
279 Log.e(TAG, "deallocate: failed to deallocate camera, " + ex);
284 // Local hook to allow derived classes to fill capture format and modify
285 // camera parameters as they see fit.
286 abstract void setCaptureParameters(
290 Camera.Parameters cameraParameters);
292 // Local hook to allow derived classes to configure and plug capture
293 // buffers if needed.
294 abstract void allocateBuffers();
296 // Local method to be overriden with the particular setPreviewCallback to be
297 // used in the implementations.
298 abstract void setPreviewCallback(Camera.PreviewCallback cb);
301 public int queryWidth() {
302 return mCaptureFormat.mWidth;
306 public int queryHeight() {
307 return mCaptureFormat.mHeight;
311 public int queryFrameRate() {
312 return mCaptureFormat.mFramerate;
316 public int getColorspace() {
317 switch (mCaptureFormat.mPixelFormat) {
318 case ImageFormat.YV12:
319 return AndroidImageFormatList.ANDROID_IMAGEFORMAT_YV12;
320 case ImageFormat.NV21:
321 return AndroidImageFormatList.ANDROID_IMAGEFORMAT_NV21;
322 case ImageFormat.UNKNOWN:
324 return AndroidImageFormatList.ANDROID_IMAGEFORMAT_UNKNOWN;
328 protected int getDeviceOrientation() {
330 if (mContext != null) {
331 WindowManager wm = (WindowManager) mContext.getSystemService(
332 Context.WINDOW_SERVICE);
333 switch(wm.getDefaultDisplay().getRotation()) {
334 case Surface.ROTATION_90:
337 case Surface.ROTATION_180:
340 case Surface.ROTATION_270:
343 case Surface.ROTATION_0:
352 // Method for VideoCapture implementations to call back native code.
353 public native void nativeOnFrameAvailable(
354 long nativeVideoCaptureDeviceAndroid,
359 protected static Camera.Parameters getCameraParameters(Camera camera) {
360 Camera.Parameters parameters;
362 parameters = camera.getParameters();
363 } catch (RuntimeException ex) {
364 Log.e(TAG, "getCameraParameters: Camera.getParameters: " + ex);
371 private Camera.CameraInfo getCameraInfo(int id) {
372 Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
374 Camera.getCameraInfo(id, cameraInfo);
375 } catch (RuntimeException ex) {
376 Log.e(TAG, "getCameraInfo: Camera.getCameraInfo: " + ex);