summaryrefslogtreecommitdiff
path: root/src/com/android/devcamera/Api2Camera.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/devcamera/Api2Camera.java')
-rw-r--r--src/com/android/devcamera/Api2Camera.java808
1 files changed, 808 insertions, 0 deletions
diff --git a/src/com/android/devcamera/Api2Camera.java b/src/com/android/devcamera/Api2Camera.java
new file mode 100644
index 0000000..1c61cb0
--- /dev/null
+++ b/src/com/android/devcamera/Api2Camera.java
@@ -0,0 +1,808 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.devcamera;
+
+import android.content.Context;
+import android.graphics.ImageFormat;
+import android.graphics.SurfaceTexture;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraCaptureSession;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraManager;
+import android.hardware.camera2.CameraMetadata;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.TotalCaptureResult;
+import android.hardware.camera2.params.Face;
+import android.hardware.camera2.params.InputConfiguration;
+import android.media.Image;
+import android.media.ImageReader;
+import android.media.ImageWriter;
+import android.media.MediaActionSound;
+import android.opengl.GLES11Ext;
+import android.opengl.GLES20;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.SystemClock;
+import android.util.Log;
+import android.util.Size;
+import android.view.Surface;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+import javax.microedition.khronos.opengles.GL10;
+
+
+/**
+ * Api2Camera : a camera2 implementation
+ *
+ * The goal here is to make the simplest possible API2 camera,
+ * where individual streams and capture options (e.g. edge enhancement,
+ * noise reduction, face detection) can be toggled on and off.
+ *
+ */
+
+public class Api2Camera implements CameraInterface, SurfaceTexture.OnFrameAvailableListener {
+ private static final String TAG = "DevCamera_API2";
+
+ // Nth frame to log; put 10^6 if you don't want logging.
+ private static int LOG_NTH_FRAME = 30;
+ // Log dropped frames. There are a log on Angler MDA32.
+ private static boolean LOG_DROPPED_FRAMES = true;
+
+ // IMPORTANT: Only one of these can be true:
+ private static boolean SECOND_YUV_IMAGEREADER_STREAM = true;
+ private static boolean SECOND_SURFACE_TEXTURE_STREAM = false;
+
+ // Enable raw stream if available.
+ private static boolean RAW_STREAM_ENABLE = true;
+ // Use JPEG ImageReader and YUV ImageWriter if reprocessing is available
+ private static final boolean USE_REPROCESSING_IF_AVAIL = true;
+
+ // Whether we are continuously taking pictures, or not.
+ boolean mIsBursting = false;
+ // Last total capture result
+ TotalCaptureResult mLastTotalCaptureResult;
+
+ // ImageReader/Writer buffer sizes.
+ private static final int YUV1_IMAGEREADER_SIZE = 8;
+ private static final int YUV2_IMAGEREADER_SIZE = 8;
+ private static final int RAW_IMAGEREADER_SIZE = 8;
+ private static final int IMAGEWRITER_SIZE = 2;
+
+ private CameraInfoCache mCameraInfoCache;
+ private CameraManager mCameraManager;
+ private CameraCaptureSession mCurrentCaptureSession;
+ private MediaActionSound mMediaActionSound = new MediaActionSound();
+
+ MyCameraCallback mMyCameraCallback;
+
+ // Generally everything running on this thread & this module is *not thread safe*.
+ private HandlerThread mOpsThread;
+ private Handler mOpsHandler;
+ private HandlerThread mInitThread;
+ private Handler mInitHandler;
+ private HandlerThread mJpegListenerThread;
+ private Handler mJpegListenerHandler;
+
+ Context mContext;
+ boolean mCameraIsFront;
+ SurfaceTexture mSurfaceTexture;
+ Surface mSurfaceTextureSurface;
+
+ private boolean mFirstFrameArrived;
+ private ImageReader mYuv1ImageReader;
+ private int mYuv1ImageCounter;
+ // Handle to last received Image: allows ZSL to be implemented.
+ private Image mYuv1LastReceivedImage = null;
+ // Time at which reprocessing request went in (right now we are doing one at a time).
+ private long mReprocessingRequestNanoTime;
+
+ private ImageReader mJpegImageReader;
+ private ImageReader mYuv2ImageReader;
+ private int mYuv2ImageCounter;
+ private ImageReader mRawImageReader;
+ private int mRawImageCounter;
+
+ // Starting the preview requires each of these 3 to be true/non-null:
+ volatile private Surface mPreviewSurface;
+ volatile private CameraDevice mCameraDevice;
+ volatile boolean mAllThingsInitialized = false;
+
+ /**
+ * Constructor.
+ */
+ public Api2Camera(Context context, boolean useFrontCamera) {
+ mContext = context;
+ mCameraIsFront = useFrontCamera;
+ mCameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
+ mCameraInfoCache = new CameraInfoCache(mCameraManager, useFrontCamera);
+
+ // Create thread and handler for camera operations.
+ mOpsThread = new HandlerThread("CameraOpsThread");
+ mOpsThread.start();
+ mOpsHandler = new Handler(mOpsThread.getLooper());
+
+ // Create thread and handler for slow initialization operations.
+ // Don't want to use camera operations thread because we want to time camera open carefully.
+ mInitThread = new HandlerThread("CameraInitThread");
+ mInitThread.start();
+ mInitHandler = new Handler(mInitThread.getLooper());
+ mInitHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ InitializeAllTheThings();
+ mAllThingsInitialized = true;
+ Log.v(TAG, "STARTUP_REQUIREMENT ImageReader initialization done.");
+ tryToStartCaptureSession();
+ }
+ });
+
+ // Set initial Noise and Edge modes.
+ if (mCameraInfoCache.IS_BULLHEAD || mCameraInfoCache.IS_ANGLER) {
+ // YUV streams.
+ mCaptureNoiseIndex = 4 /*ZSL*/ % mCameraInfoCache.noiseModes.length;
+ mCaptureEdgeIndex = 3 /*ZSL*/ % mCameraInfoCache.edgeModes.length;
+ // Reprocessing.
+ mReprocessingNoiseIndex = 2 /*High Quality*/ % mCameraInfoCache.noiseModes.length;
+ mReprocessingEdgeIndex = 2 /*HIgh Quality*/ % mCameraInfoCache.edgeModes.length;
+ }
+ }
+
+ // Ugh, why is this stuff so slow?
+ private void InitializeAllTheThings() {
+
+ // Thread to handle returned JPEGs.
+ mJpegListenerThread = new HandlerThread("CameraJpegThread");
+ mJpegListenerThread.start();
+ mJpegListenerHandler = new Handler(mJpegListenerThread.getLooper());
+
+ // Create ImageReader to receive JPEG image buffers via reprocessing.
+ mJpegImageReader = ImageReader.newInstance(
+ mCameraInfoCache.getYuvStream1Size().getWidth(),
+ mCameraInfoCache.getYuvStream1Size().getHeight(),
+ ImageFormat.JPEG,
+ 2);
+ mJpegImageReader.setOnImageAvailableListener(mJpegImageListener, mJpegListenerHandler);
+
+ // Create ImageReader to receive YUV image buffers.
+ mYuv1ImageReader = ImageReader.newInstance(
+ mCameraInfoCache.getYuvStream1Size().getWidth(),
+ mCameraInfoCache.getYuvStream1Size().getHeight(),
+ ImageFormat.YUV_420_888,
+ YUV1_IMAGEREADER_SIZE);
+ mYuv1ImageReader.setOnImageAvailableListener(mYuv1ImageListener, mOpsHandler);
+
+ if (SECOND_YUV_IMAGEREADER_STREAM) {
+ // Create ImageReader to receive YUV image buffers.
+ mYuv2ImageReader = ImageReader.newInstance(
+ mCameraInfoCache.getYuvStream2Size().getWidth(),
+ mCameraInfoCache.getYuvStream2Size().getHeight(),
+ ImageFormat.YUV_420_888,
+ YUV2_IMAGEREADER_SIZE);
+ mYuv2ImageReader.setOnImageAvailableListener(mYuv2ImageListener, mOpsHandler);
+ }
+
+ if (SECOND_SURFACE_TEXTURE_STREAM) {
+ int[] textures = new int[1];
+ // generate one texture pointer and bind it as an external texture.
+ GLES20.glGenTextures(1, textures, 0);
+ GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textures[0]);
+ // No mip-mapping with camera source.
+ GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
+ GL10.GL_TEXTURE_MIN_FILTER,
+ GL10.GL_LINEAR);
+ GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
+ GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
+ // Clamp to edge is only option.
+ GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
+ GL10.GL_TEXTURE_WRAP_S, GL10.GL_CLAMP_TO_EDGE);
+ GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
+ GL10.GL_TEXTURE_WRAP_T, GL10.GL_CLAMP_TO_EDGE);
+
+ int texture_id = textures[0];
+ mSurfaceTexture = new SurfaceTexture(texture_id);
+ mSurfaceTexture.setDefaultBufferSize(320, 240);
+ mSurfaceTexture.setOnFrameAvailableListener(this);
+ mSurfaceTextureSurface = new Surface(mSurfaceTexture);
+ }
+
+ if (RAW_STREAM_ENABLE && mCameraInfoCache.rawAvailable()) {
+ // Create ImageReader to receive thumbnail sized YUV image buffers.
+ mRawImageReader = ImageReader.newInstance(
+ mCameraInfoCache.getRawStreamSize().getWidth(),
+ mCameraInfoCache.getRawStreamSize().getHeight(),
+ mCameraInfoCache.getRawFormat(),
+ RAW_IMAGEREADER_SIZE);
+ mRawImageReader.setOnImageAvailableListener(mRawImageListener, mOpsHandler);
+ }
+
+ // Load click sound.
+ mMediaActionSound.load(MediaActionSound.SHUTTER_CLICK);
+
+ }
+
+ public void setCallback(MyCameraCallback callback) {
+ mMyCameraCallback = callback;
+ }
+
+ public void triggerAFScan() {
+ Log.v(TAG, "AF trigger");
+ issuePreviewCaptureRequest(true);
+ }
+
+ public void setCAF() {
+ Log.v(TAG, "run CAF");
+ issuePreviewCaptureRequest(false);
+ }
+
+ public void takePicture() {
+ mMediaActionSound.play(MediaActionSound.SHUTTER_CLICK);
+ mOpsHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ runReprocessing();
+ }
+ });
+ }
+
+ public void onFrameAvailable (SurfaceTexture surfaceTexture) {
+ Log.v(TAG, " onFrameAvailable(SurfaceTexture)");
+ }
+
+ public void setBurst(boolean go) {
+ // if false to true transition.
+ if (go && !mIsBursting) {
+ takePicture();
+ }
+ mIsBursting = go;
+ }
+
+ public boolean isRawAvailable() {
+ return mCameraInfoCache.rawAvailable();
+ }
+
+ public boolean isReprocessingAvailable() {
+ return mCameraInfoCache.reprocessingAvailable();
+ }
+
+ @Override
+ public Size getPreviewSize() {
+ return mCameraInfoCache.getPreviewSize();
+ }
+
+ @Override
+ public void openCamera() {
+ // If API2 FULL mode is not available, display toast, do nothing.
+ if (!mCameraInfoCache.isCamera2FullModeAvailable()) {
+ mMyCameraCallback.noCamera2Full();
+ if (!mCameraInfoCache.IS_NEXUS_6) {
+ return;
+ }
+ }
+
+ Log.v(TAG, "Opening camera " + mCameraInfoCache.getCameraId());
+ mOpsHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ CameraTimer.t_open_start = SystemClock.elapsedRealtime();
+ try {
+ mCameraManager.openCamera(mCameraInfoCache.getCameraId(), mCameraStateCallback, null);
+ } catch (CameraAccessException e) {
+ Log.e(TAG, "Unable to openCamera().");
+ }
+ }
+ });
+ }
+
+ @Override
+ public void closeCamera() {
+ // TODO: We are stalling main thread now which is bad.
+ Log.v(TAG, "Closing camera " + mCameraInfoCache.getCameraId());
+ if (mCameraDevice != null) {
+ try {
+ mCurrentCaptureSession.abortCaptures();
+ } catch (CameraAccessException e) {
+ Log.e(TAG, "Could not abortCaptures().");
+ }
+ mCameraDevice.close();
+ }
+ mCurrentCaptureSession = null;
+ Log.v(TAG, "Done closing camera " + mCameraInfoCache.getCameraId());
+ }
+
+ public void startPreview(final Surface surface) {
+ Log.v(TAG, "STARTUP_REQUIREMENT preview Surface ready.");
+ mPreviewSurface = surface;
+ tryToStartCaptureSession();
+ }
+
+ private CameraDevice.StateCallback mCameraStateCallback = new LoggingCallbacks.DeviceStateCallback() {
+ @Override
+ public void onOpened(CameraDevice camera) {
+ CameraTimer.t_open_end = SystemClock.elapsedRealtime();
+ mCameraDevice = camera;
+ Log.v(TAG, "STARTUP_REQUIREMENT Done opening camera " + mCameraInfoCache.getCameraId() +
+ ". HAL open took: (" + (CameraTimer.t_open_end - CameraTimer.t_open_start) + " ms)");
+
+ super.onOpened(camera);
+ tryToStartCaptureSession();
+ }
+ };
+
+ private void tryToStartCaptureSession() {
+ if (mCameraDevice != null && mAllThingsInitialized && mPreviewSurface != null) {
+ mOpsHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ // It used to be: this needed to be posted on a Handler.
+ startCaptureSession();
+ }
+ });
+ }
+ }
+
+ // Create CameraCaptureSession. Callback will start repeating request with current parameters.
+ private void startCaptureSession() {
+ CameraTimer.t_session_go = SystemClock.elapsedRealtime();
+
+ Log.v(TAG, "Configuring session..");
+ List<Surface> outputSurfaces = new ArrayList<Surface>(3);
+
+ outputSurfaces.add(mPreviewSurface);
+ Log.v(TAG, " .. added SurfaceView " + mCameraInfoCache.getPreviewSize().getWidth() +
+ " x " + mCameraInfoCache.getPreviewSize().getHeight());
+
+ outputSurfaces.add(mYuv1ImageReader.getSurface());
+ Log.v(TAG, " .. added YUV ImageReader " + mCameraInfoCache.getYuvStream1Size().getWidth() +
+ " x " + mCameraInfoCache.getYuvStream1Size().getHeight());
+
+ if (SECOND_YUV_IMAGEREADER_STREAM) {
+ outputSurfaces.add(mYuv2ImageReader.getSurface());
+ Log.v(TAG, " .. added YUV ImageReader " + mCameraInfoCache.getYuvStream2Size().getWidth() +
+ " x " + mCameraInfoCache.getYuvStream2Size().getHeight());
+ }
+
+ if (SECOND_SURFACE_TEXTURE_STREAM) {
+ outputSurfaces.add(mSurfaceTextureSurface);
+ Log.v(TAG, " .. added SurfaceTexture");
+ }
+
+ if (RAW_STREAM_ENABLE && mCameraInfoCache.rawAvailable()) {
+ outputSurfaces.add(mRawImageReader.getSurface());
+ Log.v(TAG, " .. added Raw ImageReader " + mCameraInfoCache.getRawStreamSize().getWidth() +
+ " x " + mCameraInfoCache.getRawStreamSize().getHeight());
+ }
+
+ if (USE_REPROCESSING_IF_AVAIL && mCameraInfoCache.reprocessingAvailable()) {
+ outputSurfaces.add(mJpegImageReader.getSurface());
+ Log.v(TAG, " .. added JPEG ImageReader " + mCameraInfoCache.getJpegStreamSize().getWidth() +
+ " x " + mCameraInfoCache.getJpegStreamSize().getHeight());
+ }
+
+ try {
+ if (USE_REPROCESSING_IF_AVAIL && mCameraInfoCache.reprocessingAvailable()) {
+ InputConfiguration inputConfig = new InputConfiguration(mCameraInfoCache.getYuvStream1Size().getWidth(),
+ mCameraInfoCache.getYuvStream1Size().getHeight(), ImageFormat.YUV_420_888);
+ mCameraDevice.createReprocessableCaptureSession(inputConfig, outputSurfaces,
+ mSessionStateCallback, null);
+ Log.v(TAG, " Call to createReprocessableCaptureSession complete.");
+ } else {
+ mCameraDevice.createCaptureSession(outputSurfaces, mSessionStateCallback, null);
+ Log.v(TAG, " Call to createCaptureSession complete.");
+ }
+
+ } catch (CameraAccessException e) {
+ Log.e(TAG, "Error configuring ISP.");
+ }
+ }
+
+ ImageWriter mImageWriter;
+
+ private CameraCaptureSession.StateCallback mSessionStateCallback = new LoggingCallbacks.SessionStateCallback() {
+ @Override
+ public void onReady(CameraCaptureSession session) {
+ Log.v(TAG, "capture session onReady(). HAL capture session took: (" + (SystemClock.elapsedRealtime() - CameraTimer.t_session_go) + " ms)");
+ mCurrentCaptureSession = session;
+ issuePreviewCaptureRequest(false);
+
+ if (session.isReprocessable()) {
+ mImageWriter = ImageWriter.newInstance(session.getInputSurface(), IMAGEWRITER_SIZE);
+ mImageWriter.setOnImageReleasedListener(
+ new ImageWriter.OnImageReleasedListener() {
+ @Override
+ public void onImageReleased(ImageWriter writer) {
+ Log.v(TAG, "ImageWriter.OnImageReleasedListener onImageReleased()");
+ }
+ }, null);
+ Log.v(TAG, "Created ImageWriter.");
+ }
+ super.onReady(session);
+ }
+ };
+
+ // Variables to hold capture flow state.
+ private boolean mCaptureYuv1 = false;
+ private boolean mCaptureYuv2 = false;
+ private boolean mCaptureRaw = false;
+ private int mCaptureNoiseIndex = CaptureRequest.NOISE_REDUCTION_MODE_OFF;
+ private int mCaptureEdgeIndex = CaptureRequest.EDGE_MODE_OFF;
+ private boolean mCaptureFace = false;
+ // Variables to hold reprocessing state.
+ private int mReprocessingNoiseIndex = CaptureRequest.NOISE_REDUCTION_MODE_OFF;
+ private int mReprocessingEdgeIndex = CaptureRequest.EDGE_MODE_OFF;
+
+
+ public void setCaptureFlow(Boolean yuv1, Boolean yuv2, Boolean raw10, Boolean nr, Boolean edge, Boolean face) {
+ if (yuv1 != null) mCaptureYuv1 = yuv1;
+ if (yuv2 != null) mCaptureYuv2 = yuv2;
+ if (raw10 != null) mCaptureRaw = raw10 && RAW_STREAM_ENABLE;
+ if (nr) {
+ mCaptureNoiseIndex = ++mCaptureNoiseIndex % mCameraInfoCache.noiseModes.length;
+ }
+ if (edge) {
+ mCaptureEdgeIndex = ++mCaptureEdgeIndex % mCameraInfoCache.edgeModes.length;
+ }
+ if (face != null) mCaptureFace = face;
+ mMyCameraCallback.setNoiseEdgeText(
+ "NR " + noiseModeToString(mCameraInfoCache.noiseModes[mCaptureNoiseIndex]),
+ "Edge " + edgeModeToString(mCameraInfoCache.edgeModes[mCaptureEdgeIndex])
+ );
+
+ if (mCurrentCaptureSession != null) {
+ issuePreviewCaptureRequest(false);
+ }
+ }
+
+ public void setReprocessingFlow(Boolean nr, Boolean edge) {
+ if (nr) {
+ mReprocessingNoiseIndex = ++mReprocessingNoiseIndex % mCameraInfoCache.noiseModes.length;
+ }
+ if (edge) {
+ mReprocessingEdgeIndex = ++mReprocessingEdgeIndex % mCameraInfoCache.edgeModes.length;
+ }
+ mMyCameraCallback.setNoiseEdgeTextForReprocessing(
+ "NR " + noiseModeToString(mCameraInfoCache.noiseModes[mReprocessingNoiseIndex]),
+ "Edge " + edgeModeToString(mCameraInfoCache.edgeModes[mReprocessingEdgeIndex])
+ );
+ }
+
+ public void issuePreviewCaptureRequest(boolean AFtrigger) {
+ CameraTimer.t_burst = SystemClock.elapsedRealtime();
+ Log.v(TAG, "issuePreviewCaptureRequest...");
+ try {
+ CaptureRequest.Builder b1 = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+ b1.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_USE_SCENE_MODE);
+ b1.set(CaptureRequest.CONTROL_SCENE_MODE, CameraMetadata.CONTROL_SCENE_MODE_FACE_PRIORITY);
+ if (AFtrigger) {
+ b1.set(CaptureRequest.CONTROL_AF_MODE, CameraMetadata.CONTROL_AF_MODE_AUTO);
+ } else {
+ b1.set(CaptureRequest.CONTROL_AF_MODE, CameraMetadata.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
+ }
+
+ b1.set(CaptureRequest.NOISE_REDUCTION_MODE, mCameraInfoCache.noiseModes[mCaptureNoiseIndex]);
+ b1.set(CaptureRequest.EDGE_MODE, mCameraInfoCache.edgeModes[mCaptureEdgeIndex]);
+ b1.set(CaptureRequest.STATISTICS_FACE_DETECT_MODE, mCaptureFace ? mCameraInfoCache.bestFaceDetectionMode() : CaptureRequest.STATISTICS_FACE_DETECT_MODE_OFF);
+
+ Log.v(TAG, " .. NR=" + mCaptureNoiseIndex + " Edge=" + mCaptureEdgeIndex + " Face=" + mCaptureFace);
+
+ if (mCaptureYuv1) {
+ b1.addTarget(mYuv1ImageReader.getSurface());
+ Log.v(TAG, " .. YUV1 on");
+ }
+
+ if (mCaptureRaw) {
+ b1.addTarget(mRawImageReader.getSurface());
+ }
+
+ b1.addTarget(mPreviewSurface);
+
+ if (mCaptureYuv2) {
+ if (SECOND_SURFACE_TEXTURE_STREAM) {
+ b1.addTarget(mSurfaceTextureSurface);
+ }
+ if (SECOND_YUV_IMAGEREADER_STREAM) {
+ b1.addTarget(mYuv2ImageReader.getSurface());
+ }
+ Log.v(TAG, " .. YUV2 on");
+ }
+
+ if (AFtrigger) {
+ b1.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_START);
+ mCurrentCaptureSession.capture(b1.build(), mCaptureCallback, mOpsHandler);
+ b1.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_IDLE);
+ }
+ mCurrentCaptureSession.setRepeatingRequest(b1.build(), mCaptureCallback, mOpsHandler);
+ } catch (CameraAccessException e) {
+ Log.e(TAG, "Could not access camera for issuePreviewCaptureRequest.");
+ }
+ }
+
+ void runReprocessing() {
+ if (mYuv1LastReceivedImage == null) {
+ Log.e(TAG, "No YUV Image available.");
+ return;
+ }
+ mImageWriter.queueInputImage(mYuv1LastReceivedImage);
+ Log.v(TAG, " Sent YUV1 image to ImageWriter.queueInputImage()");
+ try {
+ CaptureRequest.Builder b1 = mCameraDevice.createReprocessCaptureRequest(mLastTotalCaptureResult);
+ // Portrait.
+ b1.set(CaptureRequest.JPEG_ORIENTATION, 90);
+ b1.set(CaptureRequest.JPEG_QUALITY, (byte) 95);
+ b1.set(CaptureRequest.NOISE_REDUCTION_MODE, mCameraInfoCache.noiseModes[mReprocessingNoiseIndex]);
+ b1.set(CaptureRequest.EDGE_MODE, mCameraInfoCache.edgeModes[mReprocessingEdgeIndex]);
+ b1.addTarget(mJpegImageReader.getSurface());
+ mCurrentCaptureSession.capture(b1.build(), mReprocessingCaptureCallback, mOpsHandler);
+ mReprocessingRequestNanoTime = System.nanoTime();
+ } catch (CameraAccessException e) {
+ Log.e(TAG, "Could not access camera for issuePreviewCaptureRequest.");
+ }
+ mYuv1LastReceivedImage = null;
+ Log.v(TAG, " Reprocessing request submitted.");
+ }
+
+
+ /*********************************
+ * onImageAvailable() processing *
+ *********************************/
+
+ ImageReader.OnImageAvailableListener mYuv1ImageListener =
+ new ImageReader.OnImageAvailableListener() {
+ @Override
+ public void onImageAvailable(ImageReader reader) {
+ Image img = reader.acquireLatestImage();
+ if (img == null) {
+ Log.e(TAG, "Null image returned YUV1");
+ return;
+ }
+ if (mYuv1LastReceivedImage != null) {
+ mYuv1LastReceivedImage.close();
+ }
+ mYuv1LastReceivedImage = img;
+ if (++mYuv1ImageCounter % LOG_NTH_FRAME == 0) {
+ Log.v(TAG, "YUV1 buffer available, Frame #=" + mYuv1ImageCounter + " w=" + img.getWidth() + " h=" + img.getHeight() + " time=" + img.getTimestamp());
+ }
+
+ }
+ };
+
+
+ ImageReader.OnImageAvailableListener mJpegImageListener =
+ new ImageReader.OnImageAvailableListener() {
+ @Override
+ public void onImageAvailable(ImageReader reader) {
+ Image img = reader.acquireLatestImage();
+ if (img == null) {
+ Log.e(TAG, "Null image returned JPEG");
+ return;
+ }
+ Image.Plane plane0 = img.getPlanes()[0];
+ final ByteBuffer buffer = plane0.getBuffer();
+ long dt = System.nanoTime() - mReprocessingRequestNanoTime;
+ Log.v(TAG, String.format("JPEG buffer available, w=%d h=%d time=%d size=%d dt=%.1f ms ISO=%d",
+ img.getWidth(), img.getHeight(), img.getTimestamp(), buffer.capacity(), 0.000001 * dt, mLastIso));
+ // Save JPEG on the utility thread,
+ final byte[] jpegBuf;
+ if (buffer.hasArray()) {
+ jpegBuf = buffer.array();
+ } else {
+ jpegBuf = new byte[buffer.capacity()];
+ buffer.get(jpegBuf);
+ }
+ mMyCameraCallback.jpegAvailable(jpegBuf, img.getWidth(), img.getHeight());
+ img.close();
+
+ // take (reprocess) another picture right away if bursting.
+ if (mIsBursting) {
+ takePicture();
+ }
+ }
+ };
+
+
+ ImageReader.OnImageAvailableListener mYuv2ImageListener =
+ new ImageReader.OnImageAvailableListener() {
+ @Override
+ public void onImageAvailable(ImageReader reader) {
+ Image img = reader.acquireLatestImage();
+ if (img == null) {
+ Log.e(TAG, "Null image returned YUV2");
+ } else {
+ if (++mYuv2ImageCounter % LOG_NTH_FRAME == 0) {
+ Log.v(TAG, "YUV2 buffer available, Frame #=" + mYuv2ImageCounter + " w=" + img.getWidth() + " h=" + img.getHeight() + " time=" + img.getTimestamp());
+ }
+ img.close();
+ }
+ }
+ };
+
+
+ ImageReader.OnImageAvailableListener mRawImageListener =
+ new ImageReader.OnImageAvailableListener() {
+ @Override
+ public void onImageAvailable(ImageReader reader) {
+ final Image img = reader.acquireLatestImage();
+ if (img == null) {
+ Log.e(TAG, "Null image returned RAW");
+ } else {
+ if (++mRawImageCounter % LOG_NTH_FRAME == 0) {
+ Image.Plane plane0 = img.getPlanes()[0];
+ final ByteBuffer buffer = plane0.getBuffer();
+ Log.v(TAG, "Raw buffer available, Frame #=" + mRawImageCounter + "w=" + img.getWidth()
+ + " h=" + img.getHeight()
+ + " format=" + CameraDeviceReport.getFormatName(img.getFormat())
+ + " time=" + img.getTimestamp()
+ + " size=" + buffer.capacity()
+ + " getRowStride()=" + plane0.getRowStride());
+ }
+ img.close();
+ }
+ }
+ };
+
+ /*************************************
+ * CaptureResult metadata processing *
+ *************************************/
+
+ private CameraCaptureSession.CaptureCallback mCaptureCallback = new LoggingCallbacks.SessionCaptureCallback() {
+ @Override
+ public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result) {
+ if (!mFirstFrameArrived) {
+ mFirstFrameArrived = true;
+ long now = SystemClock.elapsedRealtime();
+ long dt = now - CameraTimer.t0;
+ long camera_dt = now - CameraTimer.t_session_go + CameraTimer.t_open_end - CameraTimer.t_open_start;
+ long repeating_req_dt = now - CameraTimer.t_burst;
+ Log.v(TAG, "App control to first frame: (" + dt + " ms)");
+ Log.v(TAG, "HAL request to first frame: (" + repeating_req_dt + " ms) " + " Total HAL wait: (" + camera_dt + " ms)");
+ mMyCameraCallback.receivedFirstFrame();
+ mMyCameraCallback.performanceDataAvailable((int) dt, (int) camera_dt, null);
+ }
+ publishFrameData(result);
+ // Used for reprocessing.
+ mLastTotalCaptureResult = result;
+ super.onCaptureCompleted(session, request, result);
+ }
+ };
+
+ // Reprocessing capture completed.
+ private CameraCaptureSession.CaptureCallback mReprocessingCaptureCallback = new LoggingCallbacks.SessionCaptureCallback() {
+ @Override
+ public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result) {
+ Log.v(TAG, "Reprocessing onCaptureCompleted()");
+ }
+ };
+
+ private static double SHORT_LOG_EXPOSURE = Math.log10(1000000000 / 10000); // 1/10000 second
+ private static double LONG_LOG_EXPOSURE = Math.log10(1000000000 / 10); // 1/10 second
+ public int FPS_CALC_LOOKBACK = 15;
+ private LinkedList<Long> mFrameTimes = new LinkedList<Long>();
+
+ private void publishFrameData(TotalCaptureResult result) {
+ // Faces.
+ final Face[] faces = result.get(CaptureResult.STATISTICS_FACES);
+ NormalizedFace[] newFaces = new NormalizedFace[faces.length];
+ if (faces.length > 0) {
+ int offX = mCameraInfoCache.faceOffsetX();
+ int offY = mCameraInfoCache.faceOffsetY();
+ int dX = mCameraInfoCache.activeAreaWidth() - 2 * offX;
+ int dY = mCameraInfoCache.activeAreaHeight() - 2 * offY;
+ if (mCameraInfoCache.IS_NEXUS_6 && mCameraIsFront) {
+ // Front camera on Nexus 6 is currently 16 x 9 cropped to 4 x 3.
+ // TODO: Generalize this.
+ int cropOffset = dX / 8;
+ dX -= 2 * cropOffset;
+ offX += cropOffset;
+ }
+ int orientation = mCameraInfoCache.sensorOrientation();
+ for (int i = 0; i < faces.length; ++i) {
+ newFaces[i] = new NormalizedFace(faces[i], dX, dY, offX, offY);
+ if (mCameraIsFront && orientation == 90) {
+ newFaces[i].mirrorInY();
+ }
+ if (mCameraIsFront && orientation == 270) {
+ newFaces[i].mirrorInX();
+ }
+ if (!mCameraIsFront && orientation == 270) {
+ newFaces[i].mirrorInX();
+ newFaces[i].mirrorInY();
+ }
+ }
+ }
+
+ // Normalized lens and exposure coordinates.
+ double rm = Math.log10(result.get(CaptureResult.SENSOR_EXPOSURE_TIME));
+ float normExposure = (float) ((rm - SHORT_LOG_EXPOSURE) / (LONG_LOG_EXPOSURE - SHORT_LOG_EXPOSURE));
+ float normLensPos = (mCameraInfoCache.getDiopterHi() - result.get(CaptureResult.LENS_FOCUS_DISTANCE)) / (mCameraInfoCache.getDiopterHi() - mCameraInfoCache.getDiopterLow());
+ mLastIso = result.get(CaptureResult.SENSOR_SENSITIVITY);
+
+ // Update frame arrival history.
+ mFrameTimes.add(result.get(CaptureResult.SENSOR_TIMESTAMP));
+ if (mFrameTimes.size() > FPS_CALC_LOOKBACK) {
+ mFrameTimes.removeFirst();
+ }
+
+ // Frame drop detector
+ {
+ float frameDuration = result.get(CaptureResult.SENSOR_FRAME_DURATION);
+ if (mFrameTimes.size() > 1) {
+ long dt = result.get(CaptureResult.SENSOR_TIMESTAMP) - mFrameTimes.get(mFrameTimes.size()-2);
+ if (dt > 3 * frameDuration / 2 && LOG_DROPPED_FRAMES) {
+ float drops = (dt * 1f / frameDuration) - 1f;
+ Log.e(TAG, String.format("dropped %.2f frames", drops));
+ mMyCameraCallback.performanceDataAvailable(null, null, drops);
+ }
+ }
+ }
+
+ // FPS calc.
+ float fps = 0;
+ if (mFrameTimes.size() > 1) {
+ long dt = mFrameTimes.getLast() - mFrameTimes.getFirst();
+ fps = (mFrameTimes.size() - 1) * 1000000000f / dt;
+ fps = (float) Math.floor(fps + 0.1); // round to nearest whole number, ish.
+ }
+
+ // Do callback.
+ if (mMyCameraCallback != null) {
+ mMyCameraCallback.frameDataAvailable(newFaces, normExposure, normLensPos, fps,
+ (int) mLastIso, result.get(CaptureResult.CONTROL_AF_STATE), result.get(CaptureResult.CONTROL_AE_STATE), result.get(CaptureResult.CONTROL_AWB_STATE));
+ } else {
+ Log.v(TAG, "mMyCameraCallbacks is null!!.");
+ }
+ }
+
+ long mLastIso = 0;
+
+ /*********************
+ * UTILITY FUNCTIONS *
+ *********************/
+
+ private static String edgeModeToString(int mode) {
+ switch (mode) {
+ case CaptureRequest.EDGE_MODE_OFF:
+ return "OFF";
+ case CaptureRequest.EDGE_MODE_FAST:
+ return "FAST";
+ case CaptureRequest.EDGE_MODE_HIGH_QUALITY:
+ return "HiQ";
+ case 3:
+ return "ZSL";
+ }
+ return Integer.toString(mode);
+ }
+
+
+ private static String noiseModeToString(int mode) {
+ switch (mode) {
+ case CaptureRequest.NOISE_REDUCTION_MODE_OFF:
+ return "OFF";
+ case CaptureRequest.NOISE_REDUCTION_MODE_FAST:
+ return "FAST";
+ case CaptureRequest.NOISE_REDUCTION_MODE_HIGH_QUALITY:
+ return "HiQ";
+ case 3:
+ return "MIN";
+ case 4:
+ return "ZSL";
+ }
+ return Integer.toString(mode);
+ }
+}