VideoCapture.java revision e5d81f57cb97b3b6b7fccc9c5610d21eb81db09d
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.graphics.SurfaceTexture;
10import android.hardware.Camera;
11import android.hardware.Camera.PreviewCallback;
12import android.opengl.GLES20;
13import android.util.Log;
14import android.view.Surface;
15import android.view.WindowManager;
16
17import org.chromium.base.CalledByNative;
18import org.chromium.base.JNINamespace;
19
20import java.io.IOException;
21import java.util.List;
22import java.util.concurrent.locks.ReentrantLock;
23
24/**
25 * Video Capture Device base class to interface to native Chromium.
26 **/
27@JNINamespace("media")
28public 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}