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.opengl.GLES20; 11import android.util.Log; 12import android.view.Surface; 13import android.view.WindowManager; 14 15import org.chromium.base.CalledByNative; 16import org.chromium.base.JNINamespace; 17 18import java.io.IOException; 19import java.util.List; 20import java.util.concurrent.locks.ReentrantLock; 21 22/** 23 * Video Capture Device base class to interface to native Chromium. 24 **/ 25@JNINamespace("media") 26@SuppressWarnings("deprecation") 27public abstract class VideoCapture implements android.hardware.Camera.PreviewCallback { 28 29 protected static class CaptureFormat { 30 int mWidth; 31 int mHeight; 32 final int mFramerate; 33 final int mPixelFormat; 34 35 public CaptureFormat( 36 int width, int height, int framerate, int pixelformat) { 37 mWidth = width; 38 mHeight = height; 39 mFramerate = framerate; 40 mPixelFormat = pixelformat; 41 } 42 43 public int getWidth() { 44 return mWidth; 45 } 46 47 public int getHeight() { 48 return mHeight; 49 } 50 51 public int getFramerate() { 52 return mFramerate; 53 } 54 55 public int getPixelFormat() { 56 return mPixelFormat; 57 } 58 } 59 60 protected android.hardware.Camera mCamera; 61 protected CaptureFormat mCaptureFormat = null; 62 // Lock to mutually exclude execution of OnPreviewFrame {start/stop}Capture. 63 protected ReentrantLock mPreviewBufferLock = new ReentrantLock(); 64 protected Context mContext = null; 65 // True when native code has started capture. 66 protected boolean mIsRunning = false; 67 68 protected int mId; 69 // Native callback context variable. 70 protected long mNativeVideoCaptureDeviceAndroid; 71 protected int[] mGlTextures = null; 72 protected SurfaceTexture mSurfaceTexture = null; 73 protected static final int GL_TEXTURE_EXTERNAL_OES = 0x8D65; 74 75 protected int mCameraOrientation; 76 protected int mCameraFacing; 77 protected int mDeviceOrientation; 78 private static final String TAG = "VideoCapture"; 79 80 VideoCapture(Context context, 81 int id, 82 long nativeVideoCaptureDeviceAndroid) { 83 mContext = context; 84 mId = id; 85 mNativeVideoCaptureDeviceAndroid = nativeVideoCaptureDeviceAndroid; 86 } 87 88 @CalledByNative 89 boolean allocate(int width, int height, int frameRate) { 90 Log.d(TAG, "allocate: requested (" + width + "x" + height + ")@" + 91 frameRate + "fps"); 92 try { 93 mCamera = android.hardware.Camera.open(mId); 94 } catch (RuntimeException ex) { 95 Log.e(TAG, "allocate: Camera.open: " + ex); 96 return false; 97 } 98 99 android.hardware.Camera.CameraInfo cameraInfo = getCameraInfo(mId); 100 if (cameraInfo == null) { 101 mCamera.release(); 102 mCamera = null; 103 return false; 104 } 105 106 mCameraOrientation = cameraInfo.orientation; 107 mCameraFacing = cameraInfo.facing; 108 mDeviceOrientation = getDeviceOrientation(); 109 Log.d(TAG, "allocate: orientation dev=" + mDeviceOrientation + 110 ", cam=" + mCameraOrientation + ", facing=" + mCameraFacing); 111 112 android.hardware.Camera.Parameters parameters = getCameraParameters(mCamera); 113 if (parameters == null) { 114 mCamera = null; 115 return false; 116 } 117 118 // getSupportedPreviewFpsRange() returns a List with at least one 119 // element, but when camera is in bad state, it can return null pointer. 120 List<int[]> listFpsRange = parameters.getSupportedPreviewFpsRange(); 121 if (listFpsRange == null || listFpsRange.size() == 0) { 122 Log.e(TAG, "allocate: no fps range found"); 123 return false; 124 } 125 int frameRateInMs = frameRate * 1000; 126 // Use the first range as default. 127 int[] fpsMinMax = listFpsRange.get(0); 128 int newFrameRate = (fpsMinMax[0] + 999) / 1000; 129 for (int[] fpsRange : listFpsRange) { 130 if (fpsRange[0] <= frameRateInMs && frameRateInMs <= fpsRange[1]) { 131 fpsMinMax = fpsRange; 132 newFrameRate = frameRate; 133 break; 134 } 135 } 136 frameRate = newFrameRate; 137 Log.d(TAG, "allocate: fps set to " + frameRate); 138 139 // Calculate size. 140 List<android.hardware.Camera.Size> listCameraSize = 141 parameters.getSupportedPreviewSizes(); 142 int minDiff = Integer.MAX_VALUE; 143 int matchedWidth = width; 144 int matchedHeight = height; 145 for (android.hardware.Camera.Size size : listCameraSize) { 146 int diff = Math.abs(size.width - width) + 147 Math.abs(size.height - height); 148 Log.d(TAG, "allocate: supported (" + 149 size.width + ", " + size.height + "), diff=" + diff); 150 // TODO(wjia): Remove this hack (forcing width to be multiple 151 // of 32) by supporting stride in video frame buffer. 152 // Right now, VideoCaptureController requires compact YV12 153 // (i.e., with no padding). 154 if (diff < minDiff && (size.width % 32 == 0)) { 155 minDiff = diff; 156 matchedWidth = size.width; 157 matchedHeight = size.height; 158 } 159 } 160 if (minDiff == Integer.MAX_VALUE) { 161 Log.e(TAG, "allocate: can not find a multiple-of-32 resolution"); 162 return false; 163 } 164 Log.d(TAG, "allocate: matched (" + matchedWidth + "x" + matchedHeight + ")"); 165 166 if (parameters.isVideoStabilizationSupported()) { 167 Log.d(TAG, "Image stabilization supported, currently: " + 168 parameters.getVideoStabilization() + ", setting it."); 169 parameters.setVideoStabilization(true); 170 } else { 171 Log.d(TAG, "Image stabilization not supported."); 172 } 173 174 setCaptureParameters(matchedWidth, matchedHeight, frameRate, parameters); 175 parameters.setPreviewSize(mCaptureFormat.mWidth, 176 mCaptureFormat.mHeight); 177 parameters.setPreviewFpsRange(fpsMinMax[0], fpsMinMax[1]); 178 parameters.setPreviewFormat(mCaptureFormat.mPixelFormat); 179 try { 180 mCamera.setParameters(parameters); 181 } catch (RuntimeException ex) { 182 Log.e(TAG, "setParameters: " + ex); 183 return false; 184 } 185 186 // Set SurfaceTexture. Android Capture needs a SurfaceTexture even if 187 // it is not going to be used. 188 mGlTextures = new int[1]; 189 // Generate one texture pointer and bind it as an external texture. 190 GLES20.glGenTextures(1, mGlTextures, 0); 191 GLES20.glBindTexture(GL_TEXTURE_EXTERNAL_OES, mGlTextures[0]); 192 // No mip-mapping with camera source. 193 GLES20.glTexParameterf(GL_TEXTURE_EXTERNAL_OES, 194 GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR); 195 GLES20.glTexParameterf(GL_TEXTURE_EXTERNAL_OES, 196 GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR); 197 // Clamp to edge is only option. 198 GLES20.glTexParameteri(GL_TEXTURE_EXTERNAL_OES, 199 GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); 200 GLES20.glTexParameteri(GL_TEXTURE_EXTERNAL_OES, 201 GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); 202 203 mSurfaceTexture = new SurfaceTexture(mGlTextures[0]); 204 mSurfaceTexture.setOnFrameAvailableListener(null); 205 try { 206 mCamera.setPreviewTexture(mSurfaceTexture); 207 } catch (IOException ex) { 208 Log.e(TAG, "allocate: " + ex); 209 return false; 210 } 211 212 allocateBuffers(); 213 return true; 214 } 215 216 @CalledByNative 217 public int startCapture() { 218 if (mCamera == null) { 219 Log.e(TAG, "startCapture: camera is null"); 220 return -1; 221 } 222 223 mPreviewBufferLock.lock(); 224 try { 225 if (mIsRunning) { 226 return 0; 227 } 228 mIsRunning = true; 229 } finally { 230 mPreviewBufferLock.unlock(); 231 } 232 setPreviewCallback(this); 233 try { 234 mCamera.startPreview(); 235 } catch (RuntimeException ex) { 236 Log.e(TAG, "startCapture: Camera.startPreview: " + ex); 237 return -1; 238 } 239 return 0; 240 } 241 242 @CalledByNative 243 public int stopCapture() { 244 if (mCamera == null) { 245 Log.e(TAG, "stopCapture: camera is null"); 246 return 0; 247 } 248 249 mPreviewBufferLock.lock(); 250 try { 251 if (!mIsRunning) { 252 return 0; 253 } 254 mIsRunning = false; 255 } finally { 256 mPreviewBufferLock.unlock(); 257 } 258 259 mCamera.stopPreview(); 260 setPreviewCallback(null); 261 return 0; 262 } 263 264 @CalledByNative 265 public void deallocate() { 266 if (mCamera == null) 267 return; 268 269 stopCapture(); 270 try { 271 mCamera.setPreviewTexture(null); 272 if (mGlTextures != null) 273 GLES20.glDeleteTextures(1, mGlTextures, 0); 274 mCaptureFormat = null; 275 mCamera.release(); 276 mCamera = null; 277 } catch (IOException ex) { 278 Log.e(TAG, "deallocate: failed to deallocate camera, " + ex); 279 return; 280 } 281 } 282 283 // Local hook to allow derived classes to fill capture format and modify 284 // camera parameters as they see fit. 285 abstract void setCaptureParameters( 286 int width, 287 int height, 288 int frameRate, 289 android.hardware.Camera.Parameters cameraParameters); 290 291 // Local hook to allow derived classes to configure and plug capture 292 // buffers if needed. 293 abstract void allocateBuffers(); 294 295 // Local method to be overriden with the particular setPreviewCallback to be 296 // used in the implementations. 297 abstract void setPreviewCallback(android.hardware.Camera.PreviewCallback cb); 298 299 @CalledByNative 300 public int queryWidth() { 301 return mCaptureFormat.mWidth; 302 } 303 304 @CalledByNative 305 public int queryHeight() { 306 return mCaptureFormat.mHeight; 307 } 308 309 @CalledByNative 310 public int queryFrameRate() { 311 return mCaptureFormat.mFramerate; 312 } 313 314 @CalledByNative 315 public int getColorspace() { 316 switch (mCaptureFormat.mPixelFormat) { 317 case ImageFormat.YV12: 318 return AndroidImageFormatList.ANDROID_IMAGEFORMAT_YV12; 319 case ImageFormat.NV21: 320 return AndroidImageFormatList.ANDROID_IMAGEFORMAT_NV21; 321 case ImageFormat.UNKNOWN: 322 default: 323 return AndroidImageFormatList.ANDROID_IMAGEFORMAT_UNKNOWN; 324 } 325 } 326 327 protected int getDeviceOrientation() { 328 int orientation = 0; 329 if (mContext != null) { 330 WindowManager wm = (WindowManager) mContext.getSystemService( 331 Context.WINDOW_SERVICE); 332 switch(wm.getDefaultDisplay().getRotation()) { 333 case Surface.ROTATION_90: 334 orientation = 90; 335 break; 336 case Surface.ROTATION_180: 337 orientation = 180; 338 break; 339 case Surface.ROTATION_270: 340 orientation = 270; 341 break; 342 case Surface.ROTATION_0: 343 default: 344 orientation = 0; 345 break; 346 } 347 } 348 return orientation; 349 } 350 351 // Method for VideoCapture implementations to call back native code. 352 public native void nativeOnFrameAvailable( 353 long nativeVideoCaptureDeviceAndroid, 354 byte[] data, 355 int length, 356 int rotation); 357 358 protected static android.hardware.Camera.Parameters getCameraParameters( 359 android.hardware.Camera camera) { 360 android.hardware.Camera.Parameters parameters; 361 try { 362 parameters = camera.getParameters(); 363 } catch (RuntimeException ex) { 364 Log.e(TAG, "getCameraParameters: android.hardware.Camera.getParameters: " + ex); 365 camera.release(); 366 return null; 367 } 368 return parameters; 369 } 370 371 private android.hardware.Camera.CameraInfo getCameraInfo(int id) { 372 android.hardware.Camera.CameraInfo cameraInfo = new android.hardware.Camera.CameraInfo(); 373 try { 374 android.hardware.Camera.getCameraInfo(id, cameraInfo); 375 } catch (RuntimeException ex) { 376 Log.e(TAG, "getCameraInfo: android.hardware.Camera.getCameraInfo: " + ex); 377 return null; 378 } 379 return cameraInfo; 380 } 381} 382