Upstream version 9.38.198.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         try {
181             mCamera.setParameters(parameters);
182         } catch (RuntimeException ex) {
183             Log.e(TAG, "setParameters: " + ex);
184             return false;
185         }
186
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);
203
204         mSurfaceTexture = new SurfaceTexture(mGlTextures[0]);
205         mSurfaceTexture.setOnFrameAvailableListener(null);
206         try {
207             mCamera.setPreviewTexture(mSurfaceTexture);
208         } catch (IOException ex) {
209             Log.e(TAG, "allocate: " + ex);
210             return false;
211         }
212
213         allocateBuffers();
214         return true;
215     }
216
217     @CalledByNative
218     public int startCapture() {
219         if (mCamera == null) {
220             Log.e(TAG, "startCapture: camera is null");
221             return -1;
222         }
223
224         mPreviewBufferLock.lock();
225         try {
226             if (mIsRunning) {
227                 return 0;
228             }
229             mIsRunning = true;
230         } finally {
231             mPreviewBufferLock.unlock();
232         }
233         setPreviewCallback(this);
234         try {
235             mCamera.startPreview();
236         } catch (RuntimeException ex) {
237             Log.e(TAG, "startCapture: Camera.startPreview: " + ex);
238             return -1;
239         }
240         return 0;
241     }
242
243     @CalledByNative
244     public int stopCapture() {
245         if (mCamera == null) {
246             Log.e(TAG, "stopCapture: camera is null");
247             return 0;
248         }
249
250         mPreviewBufferLock.lock();
251         try {
252             if (!mIsRunning) {
253                 return 0;
254             }
255             mIsRunning = false;
256         } finally {
257             mPreviewBufferLock.unlock();
258         }
259
260         mCamera.stopPreview();
261         setPreviewCallback(null);
262         return 0;
263     }
264
265     @CalledByNative
266     public void deallocate() {
267         if (mCamera == null)
268             return;
269
270         stopCapture();
271         try {
272             mCamera.setPreviewTexture(null);
273             if (mGlTextures != null)
274                 GLES20.glDeleteTextures(1, mGlTextures, 0);
275             mCaptureFormat = null;
276             mCamera.release();
277             mCamera = null;
278         } catch (IOException ex) {
279             Log.e(TAG, "deallocate: failed to deallocate camera, " + ex);
280             return;
281         }
282     }
283
284     // Local hook to allow derived classes to fill capture format and modify
285     // camera parameters as they see fit.
286     abstract void setCaptureParameters(
287             int width,
288             int height,
289             int frameRate,
290             Camera.Parameters cameraParameters);
291
292     // Local hook to allow derived classes to configure and plug capture
293     // buffers if needed.
294     abstract void allocateBuffers();
295
296     // Local method to be overriden with the particular setPreviewCallback to be
297     // used in the implementations.
298     abstract void setPreviewCallback(Camera.PreviewCallback cb);
299
300     @CalledByNative
301     public int queryWidth() {
302         return mCaptureFormat.mWidth;
303     }
304
305     @CalledByNative
306     public int queryHeight() {
307         return mCaptureFormat.mHeight;
308     }
309
310     @CalledByNative
311     public int queryFrameRate() {
312         return mCaptureFormat.mFramerate;
313     }
314
315     @CalledByNative
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:
323             default:
324                 return AndroidImageFormatList.ANDROID_IMAGEFORMAT_UNKNOWN;
325         }
326     }
327
328     protected int getDeviceOrientation() {
329         int orientation = 0;
330         if (mContext != null) {
331             WindowManager wm = (WindowManager) mContext.getSystemService(
332                     Context.WINDOW_SERVICE);
333             switch(wm.getDefaultDisplay().getRotation()) {
334                 case Surface.ROTATION_90:
335                     orientation = 90;
336                     break;
337                 case Surface.ROTATION_180:
338                     orientation = 180;
339                     break;
340                 case Surface.ROTATION_270:
341                     orientation = 270;
342                     break;
343                 case Surface.ROTATION_0:
344                 default:
345                     orientation = 0;
346                     break;
347             }
348         }
349         return orientation;
350     }
351
352     // Method for VideoCapture implementations to call back native code.
353     public native void nativeOnFrameAvailable(
354             long nativeVideoCaptureDeviceAndroid,
355             byte[] data,
356             int length,
357             int rotation);
358
359     protected static Camera.Parameters getCameraParameters(Camera camera) {
360         Camera.Parameters parameters;
361         try {
362             parameters = camera.getParameters();
363         } catch (RuntimeException ex) {
364             Log.e(TAG, "getCameraParameters: Camera.getParameters: " + ex);
365             camera.release();
366             return null;
367         }
368         return parameters;
369     }
370
371     private Camera.CameraInfo getCameraInfo(int id) {
372         Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
373         try {
374             Camera.getCameraInfo(id, cameraInfo);
375         } catch (RuntimeException ex) {
376             Log.e(TAG, "getCameraInfo: Camera.getCameraInfo: " + ex);
377             return null;
378         }
379         return cameraInfo;
380     }
381 }