Index: media/base/android/java/src/org/chromium/media/VideoCaptureCamera2.java |
diff --git a/media/base/android/java/src/org/chromium/media/VideoCaptureCamera2.java b/media/base/android/java/src/org/chromium/media/VideoCaptureCamera2.java |
deleted file mode 100644 |
index 0c66ede2ce363518717c80cbd6c153d7287113ed..0000000000000000000000000000000000000000 |
--- a/media/base/android/java/src/org/chromium/media/VideoCaptureCamera2.java |
+++ /dev/null |
@@ -1,503 +0,0 @@ |
-// Copyright 2014 The Chromium Authors. All rights reserved. |
-// Use of this source code is governed by a BSD-style license that can be |
-// found in the LICENSE file. |
- |
-package org.chromium.media; |
- |
-import android.annotation.TargetApi; |
-import android.content.Context; |
-import android.graphics.ImageFormat; |
-import android.hardware.camera2.CameraAccessException; |
-import android.hardware.camera2.CameraCaptureSession; |
-import android.hardware.camera2.CameraCharacteristics; |
-import android.hardware.camera2.CameraDevice; |
-import android.hardware.camera2.CameraManager; |
-import android.hardware.camera2.CameraMetadata; |
-import android.hardware.camera2.CaptureRequest; |
-import android.hardware.camera2.params.StreamConfigurationMap; |
-import android.media.Image; |
-import android.media.ImageReader; |
-import android.os.Build; |
-import android.os.Handler; |
-import android.os.HandlerThread; |
-import android.util.Size; |
-import android.view.Surface; |
- |
-import org.chromium.base.Log; |
-import org.chromium.base.annotations.JNINamespace; |
- |
-import java.nio.ByteBuffer; |
-import java.util.ArrayList; |
-import java.util.List; |
- |
-/** |
- * This class implements Video Capture using Camera2 API, introduced in Android |
- * API 21 (L Release). Capture takes place in the current Looper, while pixel |
- * download takes place in another thread used by ImageReader. A number of |
- * static methods are provided to retrieve information on current system cameras |
- * and their capabilities, using android.hardware.camera2.CameraManager. |
- **/ |
-@JNINamespace("media") |
-@TargetApi(Build.VERSION_CODES.LOLLIPOP) |
-public class VideoCaptureCamera2 extends VideoCapture { |
- |
- // Inner class to extend a CameraDevice state change listener. |
- private class CrStateListener extends CameraDevice.StateCallback { |
- @Override |
- public void onOpened(CameraDevice cameraDevice) { |
- mCameraDevice = cameraDevice; |
- changeCameraStateAndNotify(CameraState.CONFIGURING); |
- if (!createCaptureObjects()) { |
- changeCameraStateAndNotify(CameraState.STOPPED); |
- nativeOnError(mNativeVideoCaptureDeviceAndroid, |
- "Error configuring camera"); |
- } |
- } |
- |
- @Override |
- public void onDisconnected(CameraDevice cameraDevice) { |
- cameraDevice.close(); |
- mCameraDevice = null; |
- changeCameraStateAndNotify(CameraState.STOPPED); |
- } |
- |
- @Override |
- public void onError(CameraDevice cameraDevice, int error) { |
- cameraDevice.close(); |
- mCameraDevice = null; |
- changeCameraStateAndNotify(CameraState.STOPPED); |
- nativeOnError(mNativeVideoCaptureDeviceAndroid, |
- "Camera device error " + Integer.toString(error)); |
- } |
- }; |
- |
- // Inner class to extend a Capture Session state change listener. |
- private class CrCaptureSessionListener extends CameraCaptureSession.StateCallback { |
- @Override |
- public void onConfigured(CameraCaptureSession cameraCaptureSession) { |
- Log.d(TAG, "onConfigured"); |
- mCaptureSession = cameraCaptureSession; |
- createCaptureRequest(); |
- changeCameraStateAndNotify(CameraState.STARTED); |
- } |
- |
- @Override |
- public void onConfigureFailed(CameraCaptureSession cameraCaptureSession) { |
- // TODO(mcasas): When signalling error, C++ will tear us down. Is there need for |
- // cleanup? |
- changeCameraStateAndNotify(CameraState.STOPPED); |
- nativeOnError(mNativeVideoCaptureDeviceAndroid, |
- "Camera session configuration error"); |
- } |
- }; |
- |
- // Internal class implementing the ImageReader listener. Gets pinged when a |
- // new frame is been captured and downloaded to memory-backed buffers. |
- private class CrImageReaderListener implements ImageReader.OnImageAvailableListener { |
- @Override |
- public void onImageAvailable(ImageReader reader) { |
- Image image = null; |
- try { |
- image = reader.acquireLatestImage(); |
- if (image == null) return; |
- if (image.getFormat() != ImageFormat.YUV_420_888 |
- || image.getPlanes().length != 3) { |
- Log.e(TAG, "Unexpected image format: %d or #planes: %d", |
- image.getFormat(), image.getPlanes().length); |
- return; |
- } |
- |
- if (reader.getWidth() != image.getWidth() |
- || reader.getHeight() != image.getHeight()) { |
- throw new IllegalStateException("ImageReader size " + reader.getWidth() + "x" |
- + reader.getHeight() + " did not match Image size " + image.getWidth() |
- + "x" + image.getHeight()); |
- } |
- readImageIntoBuffer(image, mCapturedData); |
- nativeOnFrameAvailable(mNativeVideoCaptureDeviceAndroid, |
- mCapturedData, |
- mCapturedData.length, |
- getCameraRotation()); |
- } catch (IllegalStateException ex) { |
- Log.e(TAG, "acquireLatestImage():" + ex); |
- return; |
- } finally { |
- if (image != null) { |
- image.close(); |
- } |
- } |
- } |
- }; |
- |
- private byte[] mCapturedData; |
- |
- private CameraDevice mCameraDevice = null; |
- private CaptureRequest.Builder mPreviewBuilder = null; |
- private CameraCaptureSession mCaptureSession = null; |
- private ImageReader mImageReader = null; |
- |
- private static final double kNanoSecondsToFps = 1.0E-9; |
- private static final String TAG = "cr.media"; |
- |
- private static enum CameraState {OPENING, CONFIGURING, STARTED, STOPPED} |
- private CameraState mCameraState = CameraState.STOPPED; |
- private final Object mCameraStateLock = new Object(); |
- |
- // Service function to grab CameraCharacteristics and handle exceptions. |
- private static CameraCharacteristics getCameraCharacteristics(Context appContext, int id) { |
- final CameraManager manager = |
- (CameraManager) appContext.getSystemService(Context.CAMERA_SERVICE); |
- try { |
- return manager.getCameraCharacteristics(Integer.toString(id)); |
- } catch (CameraAccessException ex) { |
- Log.e(TAG, "getNumberOfCameras: getCameraIdList(): " + ex); |
- } |
- return null; |
- } |
- |
- private boolean createCaptureObjects() { |
- Log.d(TAG, "createCaptureObjects"); |
- if (mCameraDevice == null) return false; |
- |
- // Create an ImageReader and plug a thread looper into it to have |
- // readback take place on its own thread. |
- final int maxImages = 2; |
- mImageReader = ImageReader.newInstance(mCaptureFormat.getWidth(), |
- mCaptureFormat.getHeight(), |
- mCaptureFormat.getPixelFormat(), |
- maxImages); |
- HandlerThread thread = new HandlerThread("CameraPreview"); |
- thread.start(); |
- final Handler backgroundHandler = new Handler(thread.getLooper()); |
- final CrImageReaderListener imageReaderListener = new CrImageReaderListener(); |
- mImageReader.setOnImageAvailableListener(imageReaderListener, |
- backgroundHandler); |
- |
- // The Preview template specifically means "high frame rate is given |
- // priority over the highest-quality post-processing". |
- try { |
- mPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); |
- } catch (CameraAccessException ex) { |
- Log.e(TAG, "createCaptureRequest: " + ex); |
- return false; |
- } catch (IllegalArgumentException ex) { |
- Log.e(TAG, "createCaptureRequest: " + ex); |
- return false; |
- } catch (SecurityException ex) { |
- Log.e(TAG, "createCaptureRequest: " + ex); |
- return false; |
- } |
- if (mPreviewBuilder == null) { |
- Log.e(TAG, "mPreviewBuilder error"); |
- return false; |
- } |
- // Construct an ImageReader Surface and plug it into our CaptureRequest.Builder. |
- mPreviewBuilder.addTarget(mImageReader.getSurface()); |
- |
- // A series of configuration options in the PreviewBuilder |
- mPreviewBuilder.set(CaptureRequest.CONTROL_MODE, |
- CameraMetadata.CONTROL_MODE_AUTO); |
- mPreviewBuilder.set(CaptureRequest.NOISE_REDUCTION_MODE, |
- CameraMetadata.NOISE_REDUCTION_MODE_FAST); |
- mPreviewBuilder.set(CaptureRequest.EDGE_MODE, CameraMetadata.EDGE_MODE_FAST); |
- mPreviewBuilder.set(CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE, |
- CameraMetadata.CONTROL_VIDEO_STABILIZATION_MODE_ON); |
- // SENSOR_EXPOSURE_TIME ? |
- |
- List<Surface> surfaceList = new ArrayList<Surface>(1); |
- surfaceList.add(mImageReader.getSurface()); |
- final CrCaptureSessionListener captureSessionListener = new CrCaptureSessionListener(); |
- try { |
- mCameraDevice.createCaptureSession(surfaceList, captureSessionListener, null); |
- } catch (CameraAccessException ex) { |
- Log.e(TAG, "createCaptureSession: " + ex); |
- return false; |
- } catch (IllegalArgumentException ex) { |
- Log.e(TAG, "createCaptureSession: " + ex); |
- return false; |
- } catch (SecurityException ex) { |
- Log.e(TAG, "createCaptureSession: " + ex); |
- return false; |
- } |
- // Wait for trigger on CrCaptureSessionListener.onConfigured(); |
- return true; |
- } |
- |
- private boolean createCaptureRequest() { |
- Log.d(TAG, "createCaptureRequest"); |
- try { |
- // This line triggers the capture. No |listener| is registered, so |
- // we will not get notified of capture events, instead, ImageReader |
- // will trigger every time a downloaded image is ready. Since |
- //|handler| is null, we'll work on the current Thread Looper. |
- mCaptureSession.setRepeatingRequest(mPreviewBuilder.build(), null, null); |
- } catch (CameraAccessException ex) { |
- Log.e(TAG, "setRepeatingRequest: " + ex); |
- return false; |
- } catch (IllegalArgumentException ex) { |
- Log.e(TAG, "setRepeatingRequest: " + ex); |
- return false; |
- } catch (SecurityException ex) { |
- Log.e(TAG, "setRepeatingRequest: " + ex); |
- return false; |
- } |
- // Now wait for trigger on CrImageReaderListener.onImageAvailable(); |
- return true; |
- } |
- |
- private static void readImageIntoBuffer(Image image, byte[] data) { |
- final int imageWidth = image.getWidth(); |
- final int imageHeight = image.getHeight(); |
- final Image.Plane[] planes = image.getPlanes(); |
- |
- int offset = 0; |
- for (int plane = 0; plane < planes.length; ++plane) { |
- final ByteBuffer buffer = planes[plane].getBuffer(); |
- final int rowStride = planes[plane].getRowStride(); |
- // Experimentally, U and V planes have |pixelStride| = 2, which |
- // essentially means they are packed. That's silly, because we are |
- // forced to unpack here. |
- final int pixelStride = planes[plane].getPixelStride(); |
- final int planeWidth = (plane == 0) ? imageWidth : imageWidth / 2; |
- final int planeHeight = (plane == 0) ? imageHeight : imageHeight / 2; |
- |
- if (pixelStride == 1 && rowStride == planeWidth) { |
- // Copy whole plane from buffer into |data| at once. |
- buffer.get(data, offset, planeWidth * planeHeight); |
- offset += planeWidth * planeHeight; |
- } else { |
- // Copy pixels one by one respecting pixelStride and rowStride. |
- byte[] rowData = new byte[rowStride]; |
- for (int row = 0; row < planeHeight - 1; ++row) { |
- buffer.get(rowData, 0, rowStride); |
- for (int col = 0; col < planeWidth; ++col) { |
- data[offset++] = rowData[col * pixelStride]; |
- } |
- } |
- |
- // Last row is special in some devices and may not contain the full |
- // |rowStride| bytes of data. See http://crbug.com/458701. |
- buffer.get(rowData, 0, Math.min(rowStride, buffer.remaining())); |
- for (int col = 0; col < planeWidth; ++col) { |
- data[offset++] = rowData[col * pixelStride]; |
- } |
- } |
- } |
- } |
- |
- private void changeCameraStateAndNotify(CameraState state) { |
- synchronized (mCameraStateLock) { |
- mCameraState = state; |
- mCameraStateLock.notifyAll(); |
- } |
- } |
- |
- static boolean isLegacyDevice(Context appContext, int id) { |
- final CameraCharacteristics cameraCharacteristics = |
- getCameraCharacteristics(appContext, id); |
- return cameraCharacteristics != null |
- && cameraCharacteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL) |
- == CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY; |
- } |
- |
- static int getNumberOfCameras(Context appContext) { |
- final CameraManager manager = |
- (CameraManager) appContext.getSystemService(Context.CAMERA_SERVICE); |
- try { |
- return manager.getCameraIdList().length; |
- } catch (CameraAccessException ex) { |
- Log.e(TAG, "getNumberOfCameras: getCameraIdList(): " + ex); |
- return 0; |
- } |
- } |
- |
- static int getCaptureApiType(int id, Context appContext) { |
- final CameraCharacteristics cameraCharacteristics = |
- getCameraCharacteristics(appContext, id); |
- if (cameraCharacteristics == null) { |
- return CaptureApiType.API_TYPE_UNKNOWN; |
- } |
- |
- final int supportedHWLevel = cameraCharacteristics.get( |
- CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL); |
- switch (supportedHWLevel) { |
- case CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY: |
- return CaptureApiType.API2_LEGACY; |
- case CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_FULL: |
- return CaptureApiType.API2_FULL; |
- case CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED: |
- return CaptureApiType.API2_LIMITED; |
- default: |
- return CaptureApiType.API2_LEGACY; |
- } |
- } |
- |
- static String getName(int id, Context appContext) { |
- final CameraCharacteristics cameraCharacteristics = |
- getCameraCharacteristics(appContext, id); |
- if (cameraCharacteristics == null) return null; |
- final int facing = cameraCharacteristics.get(CameraCharacteristics.LENS_FACING); |
- return "camera2 " + id + ", facing " |
- + ((facing == CameraCharacteristics.LENS_FACING_FRONT) ? "front" : "back"); |
- } |
- |
- static VideoCaptureFormat[] getDeviceSupportedFormats(Context appContext, int id) { |
- final CameraCharacteristics cameraCharacteristics = |
- getCameraCharacteristics(appContext, id); |
- if (cameraCharacteristics == null) return null; |
- |
- final int[] capabilities = cameraCharacteristics.get( |
- CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES); |
- // Per-format frame rate via getOutputMinFrameDuration() is only available if the |
- // property REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR is set. |
- boolean minFrameDurationAvailable = false; |
- for (int cap : capabilities) { |
- if (cap == CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR) { |
- minFrameDurationAvailable = true; |
- break; |
- } |
- } |
- |
- ArrayList<VideoCaptureFormat> formatList = new ArrayList<VideoCaptureFormat>(); |
- final StreamConfigurationMap streamMap = |
- cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); |
- final int[] formats = streamMap.getOutputFormats(); |
- for (int format : formats) { |
- final Size[] sizes = streamMap.getOutputSizes(format); |
- if (sizes == null) continue; |
- for (Size size : sizes) { |
- double minFrameRate = 0.0f; |
- if (minFrameDurationAvailable) { |
- final long minFrameDuration = streamMap.getOutputMinFrameDuration(format, size); |
- minFrameRate = (minFrameDuration == 0) |
- ? 0.0f |
- : (1.0 / kNanoSecondsToFps * minFrameDuration); |
- } else { |
- // TODO(mcasas): find out where to get the info from in this case. |
- // Hint: perhaps using SCALER_AVAILABLE_PROCESSED_MIN_DURATIONS. |
- minFrameRate = 0.0; |
- } |
- formatList.add(new VideoCaptureFormat( |
- size.getWidth(), size.getHeight(), (int) minFrameRate, 0)); |
- } |
- } |
- return formatList.toArray(new VideoCaptureFormat[formatList.size()]); |
- } |
- |
- VideoCaptureCamera2(Context context, |
- int id, |
- long nativeVideoCaptureDeviceAndroid) { |
- super(context, id, nativeVideoCaptureDeviceAndroid); |
- } |
- |
- @Override |
- public boolean allocate(int width, int height, int frameRate) { |
- Log.d(TAG, "allocate: requested (%d x %d) @%dfps", width, height, frameRate); |
- synchronized (mCameraStateLock) { |
- if (mCameraState == CameraState.OPENING || mCameraState == CameraState.CONFIGURING) { |
- Log.e(TAG, "allocate() invoked while Camera is busy opening/configuring."); |
- return false; |
- } |
- } |
- final CameraCharacteristics cameraCharacteristics = getCameraCharacteristics(mContext, mId); |
- final StreamConfigurationMap streamMap = |
- cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); |
- |
- // Find closest supported size. |
- final Size[] supportedSizes = streamMap.getOutputSizes(ImageFormat.YUV_420_888); |
- if (supportedSizes == null) return false; |
- Size closestSupportedSize = null; |
- int minDiff = Integer.MAX_VALUE; |
- for (Size size : supportedSizes) { |
- final int diff = |
- Math.abs(size.getWidth() - width) + Math.abs(size.getHeight() - height); |
- if (diff < minDiff) { |
- minDiff = diff; |
- closestSupportedSize = size; |
- } |
- } |
- if (minDiff == Integer.MAX_VALUE) { |
- Log.e(TAG, "No supported resolutions."); |
- return false; |
- } |
- Log.d(TAG, "allocate: matched (%d x %d)", closestSupportedSize.getWidth(), |
- closestSupportedSize.getHeight()); |
- |
- // |mCaptureFormat| is also used to configure the ImageReader. |
- mCaptureFormat = new VideoCaptureFormat(closestSupportedSize.getWidth(), |
- closestSupportedSize.getHeight(), frameRate, ImageFormat.YUV_420_888); |
- int expectedFrameSize = mCaptureFormat.mWidth * mCaptureFormat.mHeight |
- * ImageFormat.getBitsPerPixel(mCaptureFormat.mPixelFormat) / 8; |
- mCapturedData = new byte[expectedFrameSize]; |
- mCameraNativeOrientation = |
- cameraCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION); |
- // TODO(mcasas): The following line is correct for N5 with prerelease Build, |
- // but NOT for N7 with a dev Build. Figure out which one to support. |
- mInvertDeviceOrientationReadings = |
- cameraCharacteristics.get(CameraCharacteristics.LENS_FACING) |
- == CameraCharacteristics.LENS_FACING_BACK; |
- return true; |
- } |
- |
- @Override |
- public boolean startCapture() { |
- Log.d(TAG, "startCapture"); |
- changeCameraStateAndNotify(CameraState.OPENING); |
- final CameraManager manager = |
- (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE); |
- final Handler mainHandler = new Handler(mContext.getMainLooper()); |
- final CrStateListener stateListener = new CrStateListener(); |
- try { |
- manager.openCamera(Integer.toString(mId), stateListener, mainHandler); |
- } catch (CameraAccessException ex) { |
- Log.e(TAG, "allocate: manager.openCamera: " + ex); |
- return false; |
- } catch (IllegalArgumentException ex) { |
- Log.e(TAG, "allocate: manager.openCamera: " + ex); |
- return false; |
- } catch (SecurityException ex) { |
- Log.e(TAG, "allocate: manager.openCamera: " + ex); |
- return false; |
- } |
- |
- return true; |
- } |
- |
- @Override |
- public boolean stopCapture() { |
- Log.d(TAG, "stopCapture"); |
- |
- // With Camera2 API, the capture is started asynchronously, which will cause problem if |
- // stopCapture comes too quickly. Without stopping the previous capture properly, the next |
- // startCapture will fail and make Chrome no-responding. So wait camera to be STARTED. |
- synchronized (mCameraStateLock) { |
- while (mCameraState != CameraState.STARTED && mCameraState != CameraState.STOPPED) { |
- try { |
- mCameraStateLock.wait(); |
- } catch (InterruptedException ex) { |
- Log.e(TAG, "CaptureStartedEvent: " + ex); |
- } |
- } |
- if (mCameraState == CameraState.STOPPED) return true; |
- } |
- |
- try { |
- mCaptureSession.abortCaptures(); |
- } catch (CameraAccessException ex) { |
- Log.e(TAG, "abortCaptures: " + ex); |
- return false; |
- } catch (IllegalStateException ex) { |
- Log.e(TAG, "abortCaptures: " + ex); |
- return false; |
- } |
- if (mCameraDevice == null) return false; |
- mCameraDevice.close(); |
- changeCameraStateAndNotify(CameraState.STOPPED); |
- return true; |
- } |
- |
- @Override |
- public void deallocate() { |
- Log.d(TAG, "deallocate"); |
- } |
-} |