Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(463)

Side by Side Diff: media/base/android/java/src/org/chromium/media/VideoCapture.java

Issue 11860002: Add video capture on Android. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src/
Patch Set: code review and rebase Created 7 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2013 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.graphics.SurfaceTexture.OnFrameAvailableListener;
11 import android.hardware.Camera;
12 import android.hardware.Camera.PreviewCallback;
13 import android.opengl.GLES20;
14 import android.util.Log;
15 import android.view.Surface;
16 import android.view.WindowManager;
17
18 import java.io.IOException;
19 import java.lang.RuntimeException;
20 import java.util.concurrent.locks.ReentrantLock;
21 import java.util.Iterator;
22 import java.util.List;
23
24 import org.chromium.base.CalledByNative;
25 import org.chromium.base.JNINamespace;
26
27 @JNINamespace("media")
28 public class VideoCapture implements PreviewCallback, OnFrameAvailableListener {
29 static class CaptureCapability {
30 public int mWidth = 0;
31 public int mHeight = 0;
32 public int mDesiredFps = 0;
33 }
34
35 private Camera mCamera;
36 public ReentrantLock mPreviewBufferLock = new ReentrantLock();
37 private int mPixelFormat = ImageFormat.YV12;
38 private Context mContext = null;
39 // True when native code has started capture.
40 private boolean mIsRunning = false;
41
42 private static final int NUM_CAPTURE_BUFFERS = 3;
43 private int mExpectedFrameSize = 0;
44 private int mId = 0;
45 // Native callback context variable.
46 private int mNativeVideoCaptureDeviceAndroid = 0;
47 private int[] mGlTextures = null;
48 private SurfaceTexture mSurfaceTexture = null;
49 private static final int GL_TEXTURE_EXTERNAL_OES = 0x8D65;
50
51 private int mCameraOrientation = 0;
52 private int mCameraFacing = 0;
53 private int mDeviceOrientation = 0;
54
55 CaptureCapability mCurrentCapability = null;
56 private static final String TAG = "VideoCapture";
57
58 // Returns an instance of VideoCapture.
59 @CalledByNative
60 public static VideoCapture createVideoCapture(Context context, int id,
61 int nativeVideoCaptureDeviceAndroid) {
62 return new VideoCapture(context, id, nativeVideoCaptureDeviceAndroid);
63 }
64
65 public VideoCapture(Context context, int id,
66 int nativeVideoCaptureDeviceAndroid) {
67 mContext = context;
68 mId = id;
69 mNativeVideoCaptureDeviceAndroid = nativeVideoCaptureDeviceAndroid;
70 }
71
72 // Returns 0 on success, -1 otherwise.
Ami GONE FROM CHROMIUM 2013/01/30 19:46:23 Return boolean instead, true on success?
wjia(left Chromium) 2013/02/06 00:45:34 Done.
73 @CalledByNative
74 public int allocate(int width, int height, int frameRate) {
75 Log.d(TAG, "allocate: requested width=" + width +
76 ", height=" + height + ", frameRate=" + frameRate);
77 try {
78 mCamera = Camera.open(mId);
79 Camera.CameraInfo camera_info = new Camera.CameraInfo();
80 Camera.getCameraInfo(mId, camera_info);
81 mCameraOrientation = camera_info.orientation;
82 mCameraFacing = camera_info.facing;
83 mDeviceOrientation = getDeviceOrientation();
84 Log.d(TAG, "allocate: device orientation=" + mDeviceOrientation +
85 ", camera orientation=" + mCameraOrientation +
86 ", facing=" + mCameraFacing);
87
88 Camera.Parameters parameters = mCamera.getParameters();
89
90 // Calculate fps.
91 List<int[]> listFpsRange = parameters.getSupportedPreviewFpsRange();
92 int frameRateInMs = frameRate * 1000;
93 boolean fpsIsSupported = false;
94 int fpsMin = 0;
95 int fpsMax = 0;
96 Iterator itFpsRange = listFpsRange.iterator();
97 while (itFpsRange.hasNext()) {
98 int[] fpsRange = (int[])itFpsRange.next();
99 if (fpsRange[0] <= frameRateInMs &&
100 frameRateInMs <= fpsRange[1]) {
101 fpsIsSupported = true;
102 fpsMin = fpsRange[0];
103 fpsMax = fpsRange[1];
104 break;
105 }
106 }
107
108 if (!fpsIsSupported) {
109 Log.e(TAG, "allocate: fps " + frameRate + " is not supported");
110 return -1;
111 }
112
113 mCurrentCapability = new CaptureCapability();
114 mCurrentCapability.mDesiredFps = frameRate;
115
116 // Calculate size.
117 List<Camera.Size> listCameraSize =
118 parameters.getSupportedPreviewSizes();
119 int minDiff = Integer.MAX_VALUE;
120 int matchedWidth = width;
121 int matchedHeight = height;
122 Iterator itCameraSize = listCameraSize.iterator();
123 while (itCameraSize.hasNext()) {
124 Camera.Size size = (Camera.Size)itCameraSize.next();
125 int diff = Math.abs(size.width - width) +
126 Math.abs(size.height - height);
127 Log.d(TAG, "allocate: support resolution (" +
128 size.width + ", " + size.height + "), diff=" + diff);
129 if (diff < minDiff) {
130 minDiff = diff;
131 matchedWidth = size.width;
132 matchedHeight = size.height;
133 }
134 }
135 mCurrentCapability.mWidth = matchedWidth;
136 mCurrentCapability.mHeight = matchedHeight;
137 Log.d(TAG, "allocate: matched width=" + matchedWidth +
138 ", height=" + matchedHeight);
139
140 parameters.setPreviewSize(matchedWidth, matchedHeight);
141 parameters.setPreviewFormat(mPixelFormat);
142 parameters.setPreviewFpsRange(fpsMin, fpsMax);
143 mCamera.setParameters(parameters);
144
145 // Set SurfaceTexture.
146 mGlTextures = new int[1];
147 // Generate one texture pointer and bind it as an external texture.
148 GLES20.glGenTextures(1, mGlTextures, 0);
149 GLES20.glBindTexture(GL_TEXTURE_EXTERNAL_OES, mGlTextures[0]);
150 // No mip-mapping with camera source.
151 GLES20.glTexParameterf(GL_TEXTURE_EXTERNAL_OES,
152 GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
153 GLES20.glTexParameterf(GL_TEXTURE_EXTERNAL_OES,
154 GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
155 // Clamp to edge is only option.
156 GLES20.glTexParameteri(GL_TEXTURE_EXTERNAL_OES,
157 GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
158 GLES20.glTexParameteri(GL_TEXTURE_EXTERNAL_OES,
159 GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
160
161 mSurfaceTexture = new SurfaceTexture(mGlTextures[0]);
162 mSurfaceTexture.setOnFrameAvailableListener(null);
163
164 mCamera.setPreviewTexture(mSurfaceTexture);
165
166 int bufSize = matchedWidth * matchedHeight *
167 ImageFormat.getBitsPerPixel(mPixelFormat) / 8;
168 for (int i = 0; i < NUM_CAPTURE_BUFFERS; i++) {
169 byte[] buffer = new byte[bufSize];
170 mCamera.addCallbackBuffer(buffer);
171 }
172 mExpectedFrameSize = bufSize;
173 } catch (IOException ex) {
174 Log.e(TAG, "allocate: IOException");
175 return -1;
176 } catch (RuntimeException ex) {
177 Log.e(TAG, "allocate: RuntimeException");
178 return -1;
179 }
180
181 return 0;
182 }
183
184 @CalledByNative
185 public int queryWidth() {
186 return mCurrentCapability.mWidth;
187 }
188
189 @CalledByNative
190 public int queryHeight() {
191 return mCurrentCapability.mHeight;
192 }
193
194 @CalledByNative
195 public int queryFrameRate() {
196 return mCurrentCapability.mDesiredFps;
197 }
198
199 @CalledByNative
200 public int startCapture() {
201 if (mCamera == null) {
202 Log.e(TAG, "startCapture: camera is null");
203 return -1;
204 }
205
206 mPreviewBufferLock.lock();
207 try {
208 if (mIsRunning) {
209 return 0;
210 }
211 mIsRunning = true;
212 } finally {
213 mPreviewBufferLock.unlock();
214 }
215 mCamera.setPreviewCallbackWithBuffer(this);
216 mCamera.startPreview();
217 return 0;
218 }
219
220 @CalledByNative
221 public int stopCapture() {
222 if (mCamera == null) {
223 Log.d(TAG, "stopCapture: camera is null");
224 return 0;
225 }
226
227 mPreviewBufferLock.lock();
228 try {
229 if (!mIsRunning) {
230 return 0;
231 }
232 mIsRunning = false;
233 } finally {
234 mPreviewBufferLock.unlock();
235 }
236
237 mCamera.stopPreview();
238 mCamera.setPreviewCallbackWithBuffer(null);
239 return 0;
240 }
241
242 @CalledByNative
243 public void deallocate() {
244 if (mCamera == null)
245 return;
246
247 stopCapture();
248 try {
249 mCamera.setPreviewTexture(null);
250 mSurfaceTexture.setOnFrameAvailableListener(null);
251 GLES20.glDeleteTextures(1, mGlTextures, 0);
252 mCurrentCapability = null;
253 mCamera.release();
254 mCamera = null;
255 } catch (Exception ex) {
256 Log.e(TAG, "deallocate: failed to deallocate camera");
257 return;
258 }
259 }
260
261 @Override
262 public void onPreviewFrame(byte[] data, Camera camera) {
263 mPreviewBufferLock.lock();
264 try {
265 if (!mIsRunning) {
266 return;
267 }
268 if (data.length == mExpectedFrameSize) {
269 int rotation = getDeviceOrientation();
270 if (rotation != mDeviceOrientation) {
271 mDeviceOrientation = rotation;
272 Log.d(TAG,
273 "onPreviewFrame: device orientation=" +
274 mDeviceOrientation + ", camera orientation=" +
275 mCameraOrientation);
276 }
277 boolean flipVertical = false;
278 boolean flipHorizontal = false;
279 if (mCameraFacing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
280 rotation = (mCameraOrientation + rotation) % 360;
281 rotation = (360 - rotation) % 360;
282 flipHorizontal = (rotation == 180 || rotation == 0);
283 flipVertical = !flipHorizontal;
284 } else {
285 rotation = (mCameraOrientation - rotation + 360) % 360;
286 }
287 nativeOnFrameAvailable(mNativeVideoCaptureDeviceAndroid,
288 data, mExpectedFrameSize,
289 rotation, flipVertical, flipHorizontal);
290 }
291 } finally {
292 mPreviewBufferLock.unlock();
293 if (camera != null) {
294 camera.addCallbackBuffer(data);
295 }
296 }
297 }
298
299 // TODO(wjia): investigate whether reading from texture could give better
300 // performance and frame rate.
301 @Override
302 public void onFrameAvailable(SurfaceTexture surfaceTexture) { }
303
304 private native void nativeOnFrameAvailable(int nativeVideoCaptureDeviceAndro id,
305 byte[] data, int length, int rotation,
306 boolean flipVertical, boolean flipHorizontal);
307
308 private int getDeviceOrientation() {
309 int orientation = 0;
310 if (mContext != null) {
311 WindowManager wm = (WindowManager)mContext.getSystemService(
312 Context.WINDOW_SERVICE);
313 switch(wm.getDefaultDisplay().getRotation()) {
314 case Surface.ROTATION_90:
315 orientation = 90;
316 break;
317 case Surface.ROTATION_180:
318 orientation = 180;
319 break;
320 case Surface.ROTATION_270:
321 orientation = 270;
322 break;
323 case Surface.ROTATION_0:
324 default:
325 orientation = 0;
326 break;
327 }
328 }
329 return orientation;
330 }
331 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698