| OLD | NEW |
| (Empty) |
| 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.annotation.TargetApi; | |
| 8 import android.content.Context; | |
| 9 import android.graphics.SurfaceTexture; | |
| 10 import android.opengl.GLES20; | |
| 11 import android.os.Build; | |
| 12 | |
| 13 import org.chromium.base.Log; | |
| 14 import org.chromium.base.annotations.JNINamespace; | |
| 15 | |
| 16 import java.io.IOException; | |
| 17 import java.util.List; | |
| 18 import java.util.concurrent.locks.ReentrantLock; | |
| 19 | |
| 20 /** | |
| 21 * Video Capture Device extension of VideoCapture to provide common functionalit
y | |
| 22 * for capture using android.hardware.Camera API (deprecated in API 21). Normal | |
| 23 * Android and Tango devices are extensions of this class. | |
| 24 **/ | |
| 25 @JNINamespace("media") | |
| 26 @SuppressWarnings("deprecation") | |
| 27 //TODO: is this class only used on ICS MR1 (or some later version) and above? | |
| 28 @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) | |
| 29 public abstract class VideoCaptureCamera extends VideoCapture | |
| 30 implements android.hardware.Camera.PreviewCallback { | |
| 31 | |
| 32 protected android.hardware.Camera mCamera; | |
| 33 // Lock to mutually exclude execution of OnPreviewFrame() and {start/stop}Ca
pture(). | |
| 34 protected ReentrantLock mPreviewBufferLock = new ReentrantLock(); | |
| 35 // True when native code has started capture. | |
| 36 protected boolean mIsRunning = false; | |
| 37 | |
| 38 protected int[] mGlTextures = null; | |
| 39 protected SurfaceTexture mSurfaceTexture = null; | |
| 40 protected static final int GL_TEXTURE_EXTERNAL_OES = 0x8D65; | |
| 41 | |
| 42 private static final String TAG = "cr.media"; | |
| 43 | |
| 44 protected static android.hardware.Camera.CameraInfo getCameraInfo(int id) { | |
| 45 android.hardware.Camera.CameraInfo cameraInfo = | |
| 46 new android.hardware.Camera.CameraInfo(); | |
| 47 try { | |
| 48 android.hardware.Camera.getCameraInfo(id, cameraInfo); | |
| 49 } catch (RuntimeException ex) { | |
| 50 Log.e(TAG, "getCameraInfo: Camera.getCameraInfo: " + ex); | |
| 51 return null; | |
| 52 } | |
| 53 return cameraInfo; | |
| 54 } | |
| 55 | |
| 56 protected static android.hardware.Camera.Parameters getCameraParameters( | |
| 57 android.hardware.Camera camera) { | |
| 58 android.hardware.Camera.Parameters parameters; | |
| 59 try { | |
| 60 parameters = camera.getParameters(); | |
| 61 } catch (RuntimeException ex) { | |
| 62 Log.e(TAG, "getCameraParameters: android.hardware.Camera.getParamete
rs: " + ex); | |
| 63 if (camera != null) camera.release(); | |
| 64 return null; | |
| 65 } | |
| 66 return parameters; | |
| 67 } | |
| 68 | |
| 69 VideoCaptureCamera(Context context, | |
| 70 int id, | |
| 71 long nativeVideoCaptureDeviceAndroid) { | |
| 72 super(context, id, nativeVideoCaptureDeviceAndroid); | |
| 73 } | |
| 74 | |
| 75 @Override | |
| 76 public boolean allocate(int width, int height, int frameRate) { | |
| 77 Log.d(TAG, "allocate: requested (%d x %d) @%dfps", width, height, frameR
ate); | |
| 78 try { | |
| 79 mCamera = android.hardware.Camera.open(mId); | |
| 80 } catch (RuntimeException ex) { | |
| 81 Log.e(TAG, "allocate: Camera.open: " + ex); | |
| 82 return false; | |
| 83 } | |
| 84 | |
| 85 android.hardware.Camera.CameraInfo cameraInfo = VideoCaptureCamera.getCa
meraInfo(mId); | |
| 86 if (cameraInfo == null) { | |
| 87 mCamera.release(); | |
| 88 mCamera = null; | |
| 89 return false; | |
| 90 } | |
| 91 mCameraNativeOrientation = cameraInfo.orientation; | |
| 92 // For Camera API, the readings of back-facing camera need to be inverte
d. | |
| 93 mInvertDeviceOrientationReadings = | |
| 94 (cameraInfo.facing == android.hardware.Camera.CameraInfo.CAMERA_
FACING_BACK); | |
| 95 Log.d(TAG, "allocate: Rotation dev=%d, cam=%d, facing back? %s", getDevi
ceRotation(), | |
| 96 mCameraNativeOrientation, mInvertDeviceOrientationReadings); | |
| 97 | |
| 98 android.hardware.Camera.Parameters parameters = getCameraParameters(mCam
era); | |
| 99 if (parameters == null) { | |
| 100 mCamera = null; | |
| 101 return false; | |
| 102 } | |
| 103 | |
| 104 // getSupportedPreviewFpsRange() returns a List with at least one | |
| 105 // element, but when camera is in bad state, it can return null pointer. | |
| 106 List<int[]> listFpsRange = parameters.getSupportedPreviewFpsRange(); | |
| 107 if (listFpsRange == null || listFpsRange.size() == 0) { | |
| 108 Log.e(TAG, "allocate: no fps range found"); | |
| 109 return false; | |
| 110 } | |
| 111 // API fps ranges are scaled up x1000 to avoid floating point. | |
| 112 int frameRateScaled = frameRate * 1000; | |
| 113 // Use the first range as the default chosen range. | |
| 114 int[] chosenFpsRange = listFpsRange.get(0); | |
| 115 int frameRateNearest = Math.abs(frameRateScaled - chosenFpsRange[0]) | |
| 116 < Math.abs(frameRateScaled - chosenFpsRange[1]) | |
| 117 ? chosenFpsRange[0] : chosenFpsRange[1]; | |
| 118 int chosenFrameRate = (frameRateNearest + 999) / 1000; | |
| 119 int fpsRangeSize = Integer.MAX_VALUE; | |
| 120 for (int[] fpsRange : listFpsRange) { | |
| 121 if (fpsRange[0] <= frameRateScaled && frameRateScaled <= fpsRange[1] | |
| 122 && (fpsRange[1] - fpsRange[0]) <= fpsRangeSize) { | |
| 123 chosenFpsRange = fpsRange; | |
| 124 chosenFrameRate = frameRate; | |
| 125 fpsRangeSize = fpsRange[1] - fpsRange[0]; | |
| 126 } | |
| 127 } | |
| 128 Log.d(TAG, "allocate: fps set to %d, [%d-%d]", chosenFrameRate, | |
| 129 chosenFpsRange[0], chosenFpsRange[1]); | |
| 130 | |
| 131 // Calculate size. | |
| 132 List<android.hardware.Camera.Size> listCameraSize = | |
| 133 parameters.getSupportedPreviewSizes(); | |
| 134 int minDiff = Integer.MAX_VALUE; | |
| 135 int matchedWidth = width; | |
| 136 int matchedHeight = height; | |
| 137 for (android.hardware.Camera.Size size : listCameraSize) { | |
| 138 int diff = Math.abs(size.width - width) | |
| 139 + Math.abs(size.height - height); | |
| 140 Log.d(TAG, "allocate: supported (%d, %d), diff=%d", size.width, size
.height, diff); | |
| 141 // TODO(wjia): Remove this hack (forcing width to be multiple | |
| 142 // of 32) by supporting stride in video frame buffer. | |
| 143 // Right now, VideoCaptureController requires compact YV12 | |
| 144 // (i.e., with no padding). | |
| 145 if (diff < minDiff && (size.width % 32 == 0)) { | |
| 146 minDiff = diff; | |
| 147 matchedWidth = size.width; | |
| 148 matchedHeight = size.height; | |
| 149 } | |
| 150 } | |
| 151 if (minDiff == Integer.MAX_VALUE) { | |
| 152 Log.e(TAG, "allocate: can not find a multiple-of-32 resolution"); | |
| 153 return false; | |
| 154 } | |
| 155 Log.d(TAG, "allocate: matched (%d x %d)", matchedWidth, matchedHeight); | |
| 156 | |
| 157 if (parameters.isVideoStabilizationSupported()) { | |
| 158 Log.d(TAG, "Image stabilization supported, currently: " | |
| 159 + parameters.getVideoStabilization() + ", setting it."); | |
| 160 parameters.setVideoStabilization(true); | |
| 161 } else { | |
| 162 Log.d(TAG, "Image stabilization not supported."); | |
| 163 } | |
| 164 | |
| 165 if (parameters.getSupportedFocusModes().contains( | |
| 166 android.hardware.Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO))
{ | |
| 167 parameters.setFocusMode(android.hardware.Camera.Parameters.FOCUS_MOD
E_CONTINUOUS_VIDEO); | |
| 168 } else { | |
| 169 Log.d(TAG, "Continuous focus mode not supported."); | |
| 170 } | |
| 171 | |
| 172 setCaptureParameters(matchedWidth, matchedHeight, chosenFrameRate, param
eters); | |
| 173 parameters.setPictureSize(matchedWidth, matchedHeight); | |
| 174 parameters.setPreviewSize(matchedWidth, matchedHeight); | |
| 175 parameters.setPreviewFpsRange(chosenFpsRange[0], chosenFpsRange[1]); | |
| 176 parameters.setPreviewFormat(mCaptureFormat.mPixelFormat); | |
| 177 try { | |
| 178 mCamera.setParameters(parameters); | |
| 179 } catch (RuntimeException ex) { | |
| 180 Log.e(TAG, "setParameters: " + ex); | |
| 181 return false; | |
| 182 } | |
| 183 | |
| 184 // Set SurfaceTexture. Android Capture needs a SurfaceTexture even if | |
| 185 // it is not going to be used. | |
| 186 mGlTextures = new int[1]; | |
| 187 // Generate one texture pointer and bind it as an external texture. | |
| 188 GLES20.glGenTextures(1, mGlTextures, 0); | |
| 189 GLES20.glBindTexture(GL_TEXTURE_EXTERNAL_OES, mGlTextures[0]); | |
| 190 // No mip-mapping with camera source. | |
| 191 GLES20.glTexParameterf(GL_TEXTURE_EXTERNAL_OES, | |
| 192 GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR); | |
| 193 GLES20.glTexParameterf(GL_TEXTURE_EXTERNAL_OES, | |
| 194 GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR); | |
| 195 // Clamp to edge is only option. | |
| 196 GLES20.glTexParameteri(GL_TEXTURE_EXTERNAL_OES, | |
| 197 GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); | |
| 198 GLES20.glTexParameteri(GL_TEXTURE_EXTERNAL_OES, | |
| 199 GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); | |
| 200 | |
| 201 mSurfaceTexture = new SurfaceTexture(mGlTextures[0]); | |
| 202 mSurfaceTexture.setOnFrameAvailableListener(null); | |
| 203 try { | |
| 204 mCamera.setPreviewTexture(mSurfaceTexture); | |
| 205 } catch (IOException ex) { | |
| 206 Log.e(TAG, "allocate: " + ex); | |
| 207 return false; | |
| 208 } | |
| 209 | |
| 210 allocateBuffers(); | |
| 211 return true; | |
| 212 } | |
| 213 | |
| 214 @Override | |
| 215 public boolean startCapture() { | |
| 216 if (mCamera == null) { | |
| 217 Log.e(TAG, "startCapture: camera is null"); | |
| 218 return false; | |
| 219 } | |
| 220 | |
| 221 mPreviewBufferLock.lock(); | |
| 222 try { | |
| 223 if (mIsRunning) { | |
| 224 return true; | |
| 225 } | |
| 226 mIsRunning = true; | |
| 227 } finally { | |
| 228 mPreviewBufferLock.unlock(); | |
| 229 } | |
| 230 setPreviewCallback(this); | |
| 231 try { | |
| 232 mCamera.startPreview(); | |
| 233 } catch (RuntimeException ex) { | |
| 234 Log.e(TAG, "startCapture: Camera.startPreview: " + ex); | |
| 235 return false; | |
| 236 } | |
| 237 return true; | |
| 238 } | |
| 239 | |
| 240 @Override | |
| 241 public boolean stopCapture() { | |
| 242 if (mCamera == null) { | |
| 243 Log.e(TAG, "stopCapture: camera is null"); | |
| 244 return true; | |
| 245 } | |
| 246 | |
| 247 mPreviewBufferLock.lock(); | |
| 248 try { | |
| 249 if (!mIsRunning) { | |
| 250 return true; | |
| 251 } | |
| 252 mIsRunning = false; | |
| 253 } finally { | |
| 254 mPreviewBufferLock.unlock(); | |
| 255 } | |
| 256 | |
| 257 mCamera.stopPreview(); | |
| 258 setPreviewCallback(null); | |
| 259 return true; | |
| 260 } | |
| 261 | |
| 262 @Override | |
| 263 public void deallocate() { | |
| 264 if (mCamera == null) return; | |
| 265 | |
| 266 stopCapture(); | |
| 267 try { | |
| 268 mCamera.setPreviewTexture(null); | |
| 269 if (mGlTextures != null) 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 configure and plug capture | |
| 280 // buffers if needed. | |
| 281 abstract void allocateBuffers(); | |
| 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 method to be overriden with the particular setPreviewCallback to be | |
| 292 // used in the implementations. | |
| 293 abstract void setPreviewCallback(android.hardware.Camera.PreviewCallback cb)
; | |
| 294 } | |
| OLD | NEW |