Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / media / base / android / java / src / org / chromium / media / VideoCapture.java
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
5 package org.chromium.media;
6
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;
16
17 import org.chromium.base.CalledByNative;
18 import org.chromium.base.JNINamespace;
19
20 import java.io.IOException;
21 import java.util.List;
22 import java.util.concurrent.locks.ReentrantLock;
23
24 /**
25  * Video Capture Device base class to interface to native Chromium.
26  **/
27 @JNINamespace("media")
28 public abstract class VideoCapture implements PreviewCallback {
29
30     protected static class CaptureFormat {
31         int mWidth;
32         int mHeight;
33         final int mFramerate;
34         final int mPixelFormat;
35
36         public CaptureFormat(
37                 int width, int height, int framerate, int pixelformat) {
38             mWidth = width;
39             mHeight = height;
40             mFramerate = framerate;
41             mPixelFormat = pixelformat;
42         }
43
44         public int getWidth() {
45             return mWidth;
46         }
47
48         public int getHeight() {
49             return mHeight;
50         }
51
52         public int getFramerate() {
53             return mFramerate;
54         }
55
56         public int getPixelFormat() {
57             return mPixelFormat;
58         }
59     }
60
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;
68
69     protected int mId;
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;
75
76     protected int mCameraOrientation;
77     protected int mCameraFacing;
78     protected int mDeviceOrientation;
79     private static final String TAG = "VideoCapture";
80
81     VideoCapture(Context context,
82                  int id,
83                  long nativeVideoCaptureDeviceAndroid) {
84         mContext = context;
85         mId = id;
86         mNativeVideoCaptureDeviceAndroid = nativeVideoCaptureDeviceAndroid;
87     }
88
89     @CalledByNative
90     boolean allocate(int width, int height, int frameRate) {
91         Log.d(TAG, "allocate: requested (" + width + "x" + height + ")@" +
92                 frameRate + "fps");
93         try {
94             mCamera = Camera.open(mId);
95         } catch (RuntimeException ex) {
96             Log.e(TAG, "allocate: Camera.open: " + ex);
97             return false;
98         }
99
100         Camera.CameraInfo cameraInfo = getCameraInfo(mId);
101         if (cameraInfo == null) {
102             mCamera.release();
103             mCamera = null;
104             return false;
105         }
106
107         mCameraOrientation = cameraInfo.orientation;
108         mCameraFacing = cameraInfo.facing;
109         mDeviceOrientation = getDeviceOrientation();
110         Log.d(TAG, "allocate: orientation dev=" + mDeviceOrientation +
111                   ", cam=" + mCameraOrientation + ", facing=" + mCameraFacing);
112
113         Camera.Parameters parameters = getCameraParameters(mCamera);
114         if (parameters == null) {
115             mCamera = null;
116             return false;
117         }
118
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");
124             return false;
125         }
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;
134                 break;
135             }
136         }
137         frameRate = newFrameRate;
138         Log.d(TAG, "allocate: fps set to " + frameRate);
139
140         // Calculate size.
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)) {
156                 minDiff = diff;
157                 matchedWidth = size.width;
158                 matchedHeight = size.height;
159             }
160         }
161         if (minDiff == Integer.MAX_VALUE) {
162             Log.e(TAG, "allocate: can not find a multiple-of-32 resolution");
163             return false;
164         }
165         Log.d(TAG, "allocate: matched (" + matchedWidth + "x" + matchedHeight + ")");
166
167         if (parameters.isVideoStabilizationSupported()) {
168             Log.d(TAG, "Image stabilization supported, currently: " +
169                   parameters.getVideoStabilization() + ", setting it.");
170             parameters.setVideoStabilization(true);
171         } else {
172             Log.d(TAG, "Image stabilization not supported.");
173         }
174
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);
181
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);
198
199         mSurfaceTexture = new SurfaceTexture(mGlTextures[0]);
200         mSurfaceTexture.setOnFrameAvailableListener(null);
201         try {
202             mCamera.setPreviewTexture(mSurfaceTexture);
203         } catch (IOException ex) {
204             Log.e(TAG, "allocate: " + ex);
205             return false;
206         }
207
208         allocateBuffers();
209         return true;
210     }
211
212     @CalledByNative
213     public int startCapture() {
214         if (mCamera == null) {
215             Log.e(TAG, "startCapture: camera is null");
216             return -1;
217         }
218
219         mPreviewBufferLock.lock();
220         try {
221             if (mIsRunning) {
222                 return 0;
223             }
224             mIsRunning = true;
225         } finally {
226             mPreviewBufferLock.unlock();
227         }
228         setPreviewCallback(this);
229         try {
230             mCamera.startPreview();
231         } catch (RuntimeException ex) {
232             Log.e(TAG, "startCapture: Camera.startPreview: " + ex);
233             return -1;
234         }
235         return 0;
236     }
237
238     @CalledByNative
239     public int stopCapture() {
240         if (mCamera == null) {
241             Log.e(TAG, "stopCapture: camera is null");
242             return 0;
243         }
244
245         mPreviewBufferLock.lock();
246         try {
247             if (!mIsRunning) {
248                 return 0;
249             }
250             mIsRunning = false;
251         } finally {
252             mPreviewBufferLock.unlock();
253         }
254
255         mCamera.stopPreview();
256         setPreviewCallback(null);
257         return 0;
258     }
259
260     @CalledByNative
261     public void deallocate() {
262         if (mCamera == null)
263             return;
264
265         stopCapture();
266         try {
267             mCamera.setPreviewTexture(null);
268             if (mGlTextures != null)
269                 GLES20.glDeleteTextures(1, mGlTextures, 0);
270             mCaptureFormat = null;
271             mCamera.release();
272             mCamera = null;
273         } catch (IOException ex) {
274             Log.e(TAG, "deallocate: failed to deallocate camera, " + ex);
275             return;
276         }
277     }
278
279     // Local hook to allow derived classes to fill capture format and modify
280     // camera parameters as they see fit.
281     abstract void setCaptureParameters(
282             int width,
283             int height,
284             int frameRate,
285             Camera.Parameters cameraParameters);
286
287     // Local hook to allow derived classes to configure and plug capture
288     // buffers if needed.
289     abstract void allocateBuffers();
290
291     // Local method to be overriden with the particular setPreviewCallback to be
292     // used in the implementations.
293     abstract void setPreviewCallback(Camera.PreviewCallback cb);
294
295     @CalledByNative
296     public int queryWidth() {
297         return mCaptureFormat.mWidth;
298     }
299
300     @CalledByNative
301     public int queryHeight() {
302         return mCaptureFormat.mHeight;
303     }
304
305     @CalledByNative
306     public int queryFrameRate() {
307         return mCaptureFormat.mFramerate;
308     }
309
310     @CalledByNative
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:
318             default:
319                 return AndroidImageFormatList.ANDROID_IMAGEFORMAT_UNKNOWN;
320         }
321     }
322
323     protected int getDeviceOrientation() {
324         int orientation = 0;
325         if (mContext != null) {
326             WindowManager wm = (WindowManager) mContext.getSystemService(
327                     Context.WINDOW_SERVICE);
328             switch(wm.getDefaultDisplay().getRotation()) {
329                 case Surface.ROTATION_90:
330                     orientation = 90;
331                     break;
332                 case Surface.ROTATION_180:
333                     orientation = 180;
334                     break;
335                 case Surface.ROTATION_270:
336                     orientation = 270;
337                     break;
338                 case Surface.ROTATION_0:
339                 default:
340                     orientation = 0;
341                     break;
342             }
343         }
344         return orientation;
345     }
346
347     // Method for VideoCapture implementations to call back native code.
348     public native void nativeOnFrameAvailable(
349             long nativeVideoCaptureDeviceAndroid,
350             byte[] data,
351             int length,
352             int rotation);
353
354     protected static Camera.Parameters getCameraParameters(Camera camera) {
355         Camera.Parameters parameters;
356         try {
357             parameters = camera.getParameters();
358         } catch (RuntimeException ex) {
359             Log.e(TAG, "getCameraParameters: Camera.getParameters: " + ex);
360             camera.release();
361             return null;
362         }
363         return parameters;
364     }
365
366     private Camera.CameraInfo getCameraInfo(int id) {
367         Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
368         try {
369             Camera.getCameraInfo(id, cameraInfo);
370         } catch (RuntimeException ex) {
371             Log.e(TAG, "getCameraInfo: Camera.getCameraInfo: " + ex);
372             return null;
373         }
374         return cameraInfo;
375     }
376 }