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}