diff options
Diffstat (limited to 'src/com/android/devcamera/Api2Camera.java')
-rw-r--r-- | src/com/android/devcamera/Api2Camera.java | 808 |
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); + } +} |