| /* |
| * Copyright 2015 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 android.hardware.camera2.cts; |
| |
| import static android.hardware.camera2.cts.CameraTestUtils.*; |
| |
| import android.graphics.ImageFormat; |
| import android.media.Image; |
| import android.media.ImageReader; |
| import android.media.ImageWriter; |
| import android.hardware.camera2.CameraCharacteristics; |
| import android.hardware.camera2.CameraDevice; |
| import android.hardware.camera2.CaptureFailure; |
| import android.hardware.camera2.CaptureRequest; |
| import android.hardware.camera2.CaptureResult; |
| import android.hardware.camera2.TotalCaptureResult; |
| import android.hardware.camera2.cts.helpers.StaticMetadata; |
| import android.hardware.camera2.cts.helpers.StaticMetadata.CheckLevel; |
| import android.hardware.camera2.cts.testcases.Camera2SurfaceViewTestCase; |
| import android.hardware.camera2.params.InputConfiguration; |
| import android.util.Log; |
| import android.util.Size; |
| import android.view.Surface; |
| import android.view.SurfaceHolder; |
| |
| import com.android.ex.camera2.blocking.BlockingSessionCallback; |
| |
| import java.util.Arrays; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| /** |
| * <p>Tests for Reprocess API.</p> |
| */ |
| public class ReprocessCaptureTest extends Camera2SurfaceViewTestCase { |
| private static final String TAG = "ReprocessCaptureTest"; |
| private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE); |
| private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); |
| private static final int CAPTURE_TIMEOUT_FRAMES = 100; |
| private static final int CAPTURE_TIMEOUT_MS = 3000; |
| private static final int WAIT_FOR_SURFACE_CHANGE_TIMEOUT_MS = 1000; |
| private static final int CAPTURE_TEMPLATE = CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG; |
| private static final int ZSL_TEMPLATE = CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG; |
| private static final int NUM_REPROCESS_TEST_LOOP = 3; |
| private static final int NUM_REPROCESS_CAPTURES = 3; |
| private static final int NUM_REPROCESS_BURST = 3; |
| private int mDumpFrameCount = 0; |
| |
| // The image reader for the first regular capture |
| private ImageReader mFirstImageReader; |
| // The image reader for the reprocess capture |
| private ImageReader mSecondImageReader; |
| // A flag indicating whether the regular capture and the reprocess capture share the same image |
| // reader. If it's true, mFirstImageReader should be used for regular and reprocess outputs. |
| private boolean mShareOneImageReader; |
| private SimpleImageReaderListener mFirstImageReaderListener; |
| private SimpleImageReaderListener mSecondImageReaderListener; |
| private Surface mInputSurface; |
| private ImageWriter mImageWriter; |
| private SimpleImageWriterListener mImageWriterListener; |
| |
| private enum CaptureTestCase { |
| SINGLE_SHOT, |
| BURST, |
| MIXED_BURST, |
| ABORT_CAPTURE, |
| TIMESTAMPS, |
| JPEG_EXIF, |
| REQUEST_KEYS, |
| } |
| |
| /** |
| * Test YUV_420_888 -> YUV_420_888 with maximal supported sizes |
| */ |
| public void testBasicYuvToYuvReprocessing() throws Exception { |
| for (String id : mCameraIds) { |
| if (!isYuvReprocessSupported(id)) { |
| continue; |
| } |
| |
| // YUV_420_888 -> YUV_420_888 must be supported. |
| testBasicReprocessing(id, ImageFormat.YUV_420_888, ImageFormat.YUV_420_888); |
| } |
| } |
| |
| /** |
| * Test YUV_420_888 -> JPEG with maximal supported sizes |
| */ |
| public void testBasicYuvToJpegReprocessing() throws Exception { |
| for (String id : mCameraIds) { |
| if (!isYuvReprocessSupported(id)) { |
| continue; |
| } |
| |
| // YUV_420_888 -> JPEG must be supported. |
| testBasicReprocessing(id, ImageFormat.YUV_420_888, ImageFormat.JPEG); |
| } |
| } |
| |
| /** |
| * Test OPAQUE -> YUV_420_888 with maximal supported sizes |
| */ |
| public void testBasicOpaqueToYuvReprocessing() throws Exception { |
| for (String id : mCameraIds) { |
| if (!isOpaqueReprocessSupported(id)) { |
| continue; |
| } |
| |
| // Opaque -> YUV_420_888 must be supported. |
| testBasicReprocessing(id, ImageFormat.PRIVATE, ImageFormat.YUV_420_888); |
| } |
| } |
| |
| /** |
| * Test OPAQUE -> JPEG with maximal supported sizes |
| */ |
| public void testBasicOpaqueToJpegReprocessing() throws Exception { |
| for (String id : mCameraIds) { |
| if (!isOpaqueReprocessSupported(id)) { |
| continue; |
| } |
| |
| // OPAQUE -> JPEG must be supported. |
| testBasicReprocessing(id, ImageFormat.PRIVATE, ImageFormat.JPEG); |
| } |
| } |
| |
| /** |
| * Test all supported size and format combinations. |
| */ |
| public void testReprocessingSizeFormat() throws Exception { |
| for (String id : mCameraIds) { |
| if (!isYuvReprocessSupported(id) && !isOpaqueReprocessSupported(id)) { |
| continue; |
| } |
| |
| try { |
| // open Camera device |
| openDevice(id); |
| // no preview |
| testReprocessingAllCombinations(id, /*previewSize*/null, |
| CaptureTestCase.SINGLE_SHOT); |
| } finally { |
| closeDevice(); |
| } |
| } |
| } |
| |
| /** |
| * Test all supported size and format combinations with preview. |
| */ |
| public void testReprocessingSizeFormatWithPreview() throws Exception { |
| for (String id : mCameraIds) { |
| if (!isYuvReprocessSupported(id) && !isOpaqueReprocessSupported(id)) { |
| continue; |
| } |
| |
| try { |
| // open Camera device |
| openDevice(id); |
| testReprocessingAllCombinations(id, mOrderedPreviewSizes.get(0), |
| CaptureTestCase.SINGLE_SHOT); |
| } finally { |
| closeDevice(); |
| } |
| } |
| } |
| |
| /** |
| * Test recreating reprocessing sessions. |
| */ |
| public void testRecreateReprocessingSessions() throws Exception { |
| for (String id : mCameraIds) { |
| if (!isYuvReprocessSupported(id) && !isOpaqueReprocessSupported(id)) { |
| continue; |
| } |
| |
| try { |
| openDevice(id); |
| |
| // Test supported input/output formats with the largest sizes. |
| int[] inputFormats = |
| mStaticInfo.getAvailableFormats(StaticMetadata.StreamDirection.Input); |
| for (int inputFormat : inputFormats) { |
| int[] reprocessOutputFormats = |
| mStaticInfo.getValidOutputFormatsForInput(inputFormat); |
| for (int reprocessOutputFormat : reprocessOutputFormats) { |
| Size maxInputSize = |
| getMaxSize(inputFormat, StaticMetadata.StreamDirection.Input); |
| Size maxReprocessOutputSize = getMaxSize(reprocessOutputFormat, |
| StaticMetadata.StreamDirection.Output); |
| |
| for (int i = 0; i < NUM_REPROCESS_TEST_LOOP; i++) { |
| testReprocess(id, maxInputSize, inputFormat, maxReprocessOutputSize, |
| reprocessOutputFormat, |
| /* previewSize */null, NUM_REPROCESS_CAPTURES); |
| } |
| } |
| } |
| } finally { |
| closeDevice(); |
| } |
| } |
| } |
| |
| /** |
| * Verify issuing cross session capture requests is invalid. |
| */ |
| public void testCrossSessionCaptureException() throws Exception { |
| for (String id : mCameraIds) { |
| // Test one supported input format -> JPEG |
| int inputFormat; |
| int reprocessOutputFormat = ImageFormat.JPEG; |
| |
| if (isOpaqueReprocessSupported(id)) { |
| inputFormat = ImageFormat.PRIVATE; |
| } else if (isYuvReprocessSupported(id)) { |
| inputFormat = ImageFormat.YUV_420_888; |
| } else { |
| continue; |
| } |
| |
| openDevice(id); |
| |
| // Test the largest sizes |
| Size inputSize = |
| getMaxSize(inputFormat, StaticMetadata.StreamDirection.Input); |
| Size reprocessOutputSize = |
| getMaxSize(reprocessOutputFormat, StaticMetadata.StreamDirection.Output); |
| |
| try { |
| if (VERBOSE) { |
| Log.v(TAG, "testCrossSessionCaptureException: cameraId: " + id + |
| " inputSize: " + inputSize + " inputFormat: " + inputFormat + |
| " reprocessOutputSize: " + reprocessOutputSize + |
| " reprocessOutputFormat: " + reprocessOutputFormat); |
| } |
| |
| setupImageReaders(inputSize, inputFormat, reprocessOutputSize, |
| reprocessOutputFormat, /*maxImages*/1); |
| setupReprocessableSession(/*previewSurface*/null, /*numImageWriterImages*/1); |
| |
| TotalCaptureResult result = submitCaptureRequest(mFirstImageReader.getSurface(), |
| /*inputResult*/null); |
| Image image = mFirstImageReaderListener.getImage(CAPTURE_TIMEOUT_MS); |
| |
| // queue the image to image writer |
| mImageWriter.queueInputImage(image); |
| |
| // recreate the session |
| closeReprossibleSession(); |
| setupReprocessableSession(/*previewSurface*/null, /*numImageWriterImages*/1); |
| try { |
| TotalCaptureResult reprocessResult; |
| // issue and wait on reprocess capture request |
| reprocessResult = submitCaptureRequest( |
| getReprocessOutputImageReader().getSurface(), result); |
| fail("Camera " + id + ": should get IllegalArgumentException for cross " + |
| "session reprocess captrue."); |
| } catch (IllegalArgumentException e) { |
| // expected |
| if (DEBUG) { |
| Log.d(TAG, "Camera " + id + ": get IllegalArgumentException for cross " + |
| "session reprocess capture as expected: " + e.getMessage()); |
| } |
| } |
| } finally { |
| closeReprossibleSession(); |
| closeImageReaders(); |
| closeDevice(); |
| } |
| } |
| } |
| |
| /** |
| * Test burst reprocessing captures with and without preview. |
| */ |
| public void testBurstReprocessing() throws Exception { |
| for (String id : mCameraIds) { |
| if (!isYuvReprocessSupported(id) && !isOpaqueReprocessSupported(id)) { |
| continue; |
| } |
| |
| try { |
| // open Camera device |
| openDevice(id); |
| // no preview |
| testReprocessingAllCombinations(id, /*previewSize*/null, CaptureTestCase.BURST); |
| // with preview |
| testReprocessingAllCombinations(id, mOrderedPreviewSizes.get(0), |
| CaptureTestCase.BURST); |
| } finally { |
| closeDevice(); |
| } |
| } |
| } |
| |
| /** |
| * Test burst captures mixed with regular and reprocess captures with and without preview. |
| */ |
| public void testMixedBurstReprocessing() throws Exception { |
| for (String id : mCameraIds) { |
| if (!isYuvReprocessSupported(id) && !isOpaqueReprocessSupported(id)) { |
| continue; |
| } |
| |
| try { |
| // open Camera device |
| openDevice(id); |
| // no preview |
| testReprocessingAllCombinations(id, /*previewSize*/null, |
| CaptureTestCase.MIXED_BURST); |
| // with preview |
| testReprocessingAllCombinations(id, mOrderedPreviewSizes.get(0), |
| CaptureTestCase.MIXED_BURST); |
| } finally { |
| closeDevice(); |
| } |
| } |
| } |
| |
| /** |
| * Test aborting reprocess capture requests of the largest input and output sizes for each |
| * supported format. |
| */ |
| public void testReprocessAbort() throws Exception { |
| for (String id : mCameraIds) { |
| if (!isYuvReprocessSupported(id) && !isOpaqueReprocessSupported(id)) { |
| continue; |
| } |
| |
| try { |
| // open Camera device |
| openDevice(id); |
| |
| int[] supportedInputFormats = |
| mStaticInfo.getAvailableFormats(StaticMetadata.StreamDirection.Input); |
| for (int inputFormat : supportedInputFormats) { |
| int[] supportedReprocessOutputFormats = |
| mStaticInfo.getValidOutputFormatsForInput(inputFormat); |
| for (int reprocessOutputFormat : supportedReprocessOutputFormats) { |
| testReprocessingMaxSizes(id, inputFormat, reprocessOutputFormat, |
| /*previewSize*/null, CaptureTestCase.ABORT_CAPTURE); |
| } |
| } |
| } finally { |
| closeDevice(); |
| } |
| } |
| } |
| |
| /** |
| * Test reprocess timestamps for the largest input and output sizes for each supported format. |
| */ |
| public void testReprocessTimestamps() throws Exception { |
| for (String id : mCameraIds) { |
| if (!isYuvReprocessSupported(id) && !isOpaqueReprocessSupported(id)) { |
| continue; |
| } |
| |
| try { |
| // open Camera device |
| openDevice(id); |
| |
| int[] supportedInputFormats = |
| mStaticInfo.getAvailableFormats(StaticMetadata.StreamDirection.Input); |
| for (int inputFormat : supportedInputFormats) { |
| int[] supportedReprocessOutputFormats = |
| mStaticInfo.getValidOutputFormatsForInput(inputFormat); |
| for (int reprocessOutputFormat : supportedReprocessOutputFormats) { |
| testReprocessingMaxSizes(id, inputFormat, reprocessOutputFormat, |
| /*previewSize*/null, CaptureTestCase.TIMESTAMPS); |
| } |
| } |
| } finally { |
| closeDevice(); |
| } |
| } |
| } |
| |
| /** |
| * Test reprocess jpeg output's exif data for the largest input and output sizes for each |
| * supported format. |
| */ |
| public void testReprocessJpegExif() throws Exception { |
| for (String id : mCameraIds) { |
| if (!isYuvReprocessSupported(id) && !isOpaqueReprocessSupported(id)) { |
| continue; |
| } |
| |
| try { |
| // open Camera device |
| openDevice(id); |
| |
| int[] supportedInputFormats = |
| mStaticInfo.getAvailableFormats(StaticMetadata.StreamDirection.Input); |
| |
| for (int inputFormat : supportedInputFormats) { |
| int[] supportedReprocessOutputFormats = |
| mStaticInfo.getValidOutputFormatsForInput(inputFormat); |
| |
| for (int reprocessOutputFormat : supportedReprocessOutputFormats) { |
| if (reprocessOutputFormat == ImageFormat.JPEG) { |
| testReprocessingMaxSizes(id, inputFormat, ImageFormat.JPEG, |
| /*previewSize*/null, CaptureTestCase.JPEG_EXIF); |
| } |
| } |
| } |
| } finally { |
| closeDevice(); |
| } |
| } |
| } |
| |
| public void testReprocessRequestKeys() throws Exception { |
| for (String id : mCameraIds) { |
| if (!isYuvReprocessSupported(id) && !isOpaqueReprocessSupported(id)) { |
| continue; |
| } |
| |
| try { |
| // open Camera device |
| openDevice(id); |
| |
| int[] supportedInputFormats = |
| mStaticInfo.getAvailableFormats(StaticMetadata.StreamDirection.Input); |
| for (int inputFormat : supportedInputFormats) { |
| int[] supportedReprocessOutputFormats = |
| mStaticInfo.getValidOutputFormatsForInput(inputFormat); |
| for (int reprocessOutputFormat : supportedReprocessOutputFormats) { |
| testReprocessingMaxSizes(id, inputFormat, reprocessOutputFormat, |
| /*previewSize*/null, CaptureTestCase.REQUEST_KEYS); |
| } |
| } |
| } finally { |
| closeDevice(); |
| } |
| } |
| } |
| |
| /** |
| * Test the input format and output format with the largest input and output sizes. |
| */ |
| private void testBasicReprocessing(String cameraId, int inputFormat, |
| int reprocessOutputFormat) throws Exception { |
| try { |
| openDevice(cameraId); |
| |
| testReprocessingMaxSizes(cameraId, inputFormat, reprocessOutputFormat, |
| /* previewSize */null, CaptureTestCase.SINGLE_SHOT); |
| } finally { |
| closeDevice(); |
| } |
| } |
| |
| /** |
| * Test the input format and output format with the largest input and output sizes for a |
| * certain test case. |
| */ |
| private void testReprocessingMaxSizes(String cameraId, int inputFormat, |
| int reprocessOutputFormat, Size previewSize, CaptureTestCase captureTestCase) |
| throws Exception { |
| Size maxInputSize = getMaxSize(inputFormat, StaticMetadata.StreamDirection.Input); |
| Size maxReprocessOutputSize = |
| getMaxSize(reprocessOutputFormat, StaticMetadata.StreamDirection.Output); |
| |
| switch (captureTestCase) { |
| case SINGLE_SHOT: |
| testReprocess(cameraId, maxInputSize, inputFormat, maxReprocessOutputSize, |
| reprocessOutputFormat, previewSize, NUM_REPROCESS_CAPTURES); |
| break; |
| case ABORT_CAPTURE: |
| testReprocessAbort(cameraId, maxInputSize, inputFormat, maxReprocessOutputSize, |
| reprocessOutputFormat); |
| break; |
| case TIMESTAMPS: |
| testReprocessTimestamps(cameraId, maxInputSize, inputFormat, maxReprocessOutputSize, |
| reprocessOutputFormat); |
| break; |
| case JPEG_EXIF: |
| testReprocessJpegExif(cameraId, maxInputSize, inputFormat, maxReprocessOutputSize); |
| break; |
| case REQUEST_KEYS: |
| testReprocessRequestKeys(cameraId, maxInputSize, inputFormat, |
| maxReprocessOutputSize, reprocessOutputFormat); |
| break; |
| default: |
| throw new IllegalArgumentException("Invalid test case"); |
| } |
| } |
| |
| /** |
| * Test all input format, input size, output format, and output size combinations. |
| */ |
| private void testReprocessingAllCombinations(String cameraId, Size previewSize, |
| CaptureTestCase captureTestCase) throws Exception { |
| |
| int[] supportedInputFormats = |
| mStaticInfo.getAvailableFormats(StaticMetadata.StreamDirection.Input); |
| for (int inputFormat : supportedInputFormats) { |
| Size[] supportedInputSizes = |
| mStaticInfo.getAvailableSizesForFormatChecked(inputFormat, |
| StaticMetadata.StreamDirection.Input); |
| |
| for (Size inputSize : supportedInputSizes) { |
| int[] supportedReprocessOutputFormats = |
| mStaticInfo.getValidOutputFormatsForInput(inputFormat); |
| |
| for (int reprocessOutputFormat : supportedReprocessOutputFormats) { |
| Size[] supportedReprocessOutputSizes = |
| mStaticInfo.getAvailableSizesForFormatChecked(reprocessOutputFormat, |
| StaticMetadata.StreamDirection.Output); |
| |
| for (Size reprocessOutputSize : supportedReprocessOutputSizes) { |
| switch (captureTestCase) { |
| case SINGLE_SHOT: |
| testReprocess(cameraId, inputSize, inputFormat, |
| reprocessOutputSize, reprocessOutputFormat, previewSize, |
| NUM_REPROCESS_CAPTURES); |
| break; |
| case BURST: |
| testReprocessBurst(cameraId, inputSize, inputFormat, |
| reprocessOutputSize, reprocessOutputFormat, previewSize, |
| NUM_REPROCESS_BURST); |
| break; |
| case MIXED_BURST: |
| testReprocessMixedBurst(cameraId, inputSize, inputFormat, |
| reprocessOutputSize, reprocessOutputFormat, previewSize, |
| NUM_REPROCESS_BURST); |
| break; |
| default: |
| throw new IllegalArgumentException("Invalid test case"); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * Test burst that is mixed with regular and reprocess capture requests. |
| */ |
| private void testReprocessMixedBurst(String cameraId, Size inputSize, int inputFormat, |
| Size reprocessOutputSize, int reprocessOutputFormat, Size previewSize, |
| int numBurst) throws Exception { |
| if (VERBOSE) { |
| Log.v(TAG, "testReprocessMixedBurst: cameraId: " + cameraId + " inputSize: " + |
| inputSize + " inputFormat: " + inputFormat + " reprocessOutputSize: " + |
| reprocessOutputSize + " reprocessOutputFormat: " + reprocessOutputFormat + |
| " previewSize: " + previewSize + " numBurst: " + numBurst); |
| } |
| |
| boolean enablePreview = (previewSize != null); |
| ImageResultHolder[] imageResultHolders = new ImageResultHolder[0]; |
| |
| try { |
| // totalNumBurst = number of regular burst + number of reprocess burst. |
| int totalNumBurst = numBurst * 2; |
| |
| if (enablePreview) { |
| updatePreviewSurface(previewSize); |
| } else { |
| mPreviewSurface = null; |
| } |
| |
| setupImageReaders(inputSize, inputFormat, reprocessOutputSize, reprocessOutputFormat, |
| totalNumBurst); |
| setupReprocessableSession(mPreviewSurface, /*numImageWriterImages*/numBurst); |
| |
| if (enablePreview) { |
| startPreview(mPreviewSurface); |
| } |
| |
| // Prepare an array of booleans indicating each capture's type (regular or reprocess) |
| boolean[] isReprocessCaptures = new boolean[totalNumBurst]; |
| for (int i = 0; i < totalNumBurst; i++) { |
| if ((i & 1) == 0) { |
| isReprocessCaptures[i] = true; |
| } else { |
| isReprocessCaptures[i] = false; |
| } |
| } |
| |
| imageResultHolders = doMixedReprocessBurstCapture(isReprocessCaptures); |
| for (ImageResultHolder holder : imageResultHolders) { |
| Image reprocessedImage = holder.getImage(); |
| TotalCaptureResult result = holder.getTotalCaptureResult(); |
| |
| mCollector.expectImageProperties("testReprocessMixedBurst", reprocessedImage, |
| reprocessOutputFormat, reprocessOutputSize, |
| result.get(CaptureResult.SENSOR_TIMESTAMP)); |
| |
| if (DEBUG) { |
| Log.d(TAG, String.format("camera %s in %dx%d %d out %dx%d %d", |
| cameraId, inputSize.getWidth(), inputSize.getHeight(), inputFormat, |
| reprocessOutputSize.getWidth(), reprocessOutputSize.getHeight(), |
| reprocessOutputFormat)); |
| dumpImage(reprocessedImage, |
| "/testReprocessMixedBurst_camera" + cameraId + "_" + mDumpFrameCount); |
| mDumpFrameCount++; |
| } |
| } |
| } finally { |
| for (ImageResultHolder holder : imageResultHolders) { |
| holder.getImage().close(); |
| } |
| closeReprossibleSession(); |
| closeImageReaders(); |
| } |
| } |
| |
| /** |
| * Test burst of reprocess capture requests. |
| */ |
| private void testReprocessBurst(String cameraId, Size inputSize, int inputFormat, |
| Size reprocessOutputSize, int reprocessOutputFormat, Size previewSize, |
| int numBurst) throws Exception { |
| if (VERBOSE) { |
| Log.v(TAG, "testReprocessBurst: cameraId: " + cameraId + " inputSize: " + |
| inputSize + " inputFormat: " + inputFormat + " reprocessOutputSize: " + |
| reprocessOutputSize + " reprocessOutputFormat: " + reprocessOutputFormat + |
| " previewSize: " + previewSize + " numBurst: " + numBurst); |
| } |
| |
| boolean enablePreview = (previewSize != null); |
| ImageResultHolder[] imageResultHolders = new ImageResultHolder[0]; |
| |
| try { |
| if (enablePreview) { |
| updatePreviewSurface(previewSize); |
| } else { |
| mPreviewSurface = null; |
| } |
| |
| setupImageReaders(inputSize, inputFormat, reprocessOutputSize, reprocessOutputFormat, |
| numBurst); |
| setupReprocessableSession(mPreviewSurface, numBurst); |
| |
| if (enablePreview) { |
| startPreview(mPreviewSurface); |
| } |
| |
| imageResultHolders = doReprocessBurstCapture(numBurst); |
| for (ImageResultHolder holder : imageResultHolders) { |
| Image reprocessedImage = holder.getImage(); |
| TotalCaptureResult result = holder.getTotalCaptureResult(); |
| |
| mCollector.expectImageProperties("testReprocessBurst", reprocessedImage, |
| reprocessOutputFormat, reprocessOutputSize, |
| result.get(CaptureResult.SENSOR_TIMESTAMP)); |
| |
| if (DEBUG) { |
| Log.d(TAG, String.format("camera %s in %dx%d %d out %dx%d %d", |
| cameraId, inputSize.getWidth(), inputSize.getHeight(), inputFormat, |
| reprocessOutputSize.getWidth(), reprocessOutputSize.getHeight(), |
| reprocessOutputFormat)); |
| dumpImage(reprocessedImage, |
| "/testReprocessBurst_camera" + cameraId + "_" + mDumpFrameCount); |
| mDumpFrameCount++; |
| } |
| } |
| } finally { |
| for (ImageResultHolder holder : imageResultHolders) { |
| holder.getImage().close(); |
| } |
| closeReprossibleSession(); |
| closeImageReaders(); |
| } |
| } |
| |
| /** |
| * Test a sequences of reprocess capture requests. |
| */ |
| private void testReprocess(String cameraId, Size inputSize, int inputFormat, |
| Size reprocessOutputSize, int reprocessOutputFormat, Size previewSize, |
| int numReprocessCaptures) throws Exception { |
| if (VERBOSE) { |
| Log.v(TAG, "testReprocess: cameraId: " + cameraId + " inputSize: " + |
| inputSize + " inputFormat: " + inputFormat + " reprocessOutputSize: " + |
| reprocessOutputSize + " reprocessOutputFormat: " + reprocessOutputFormat + |
| " previewSize: " + previewSize); |
| } |
| |
| boolean enablePreview = (previewSize != null); |
| |
| try { |
| if (enablePreview) { |
| updatePreviewSurface(previewSize); |
| } else { |
| mPreviewSurface = null; |
| } |
| |
| setupImageReaders(inputSize, inputFormat, reprocessOutputSize, reprocessOutputFormat, |
| /*maxImages*/1); |
| setupReprocessableSession(mPreviewSurface, /*numImageWriterImages*/1); |
| |
| if (enablePreview) { |
| startPreview(mPreviewSurface); |
| } |
| |
| for (int i = 0; i < numReprocessCaptures; i++) { |
| ImageResultHolder imageResultHolder = null; |
| |
| try { |
| imageResultHolder = doReprocessCapture(); |
| Image reprocessedImage = imageResultHolder.getImage(); |
| TotalCaptureResult result = imageResultHolder.getTotalCaptureResult(); |
| |
| mCollector.expectImageProperties("testReprocess", reprocessedImage, |
| reprocessOutputFormat, reprocessOutputSize, |
| result.get(CaptureResult.SENSOR_TIMESTAMP)); |
| |
| if (DEBUG) { |
| Log.d(TAG, String.format("camera %s in %dx%d %d out %dx%d %d", |
| cameraId, inputSize.getWidth(), inputSize.getHeight(), inputFormat, |
| reprocessOutputSize.getWidth(), reprocessOutputSize.getHeight(), |
| reprocessOutputFormat)); |
| |
| dumpImage(reprocessedImage, |
| "/testReprocess_camera" + cameraId + "_" + mDumpFrameCount); |
| mDumpFrameCount++; |
| } |
| } finally { |
| if (imageResultHolder != null) { |
| imageResultHolder.getImage().close(); |
| } |
| } |
| } |
| } finally { |
| closeReprossibleSession(); |
| closeImageReaders(); |
| } |
| } |
| |
| /** |
| * Test aborting a burst reprocess capture and multiple single reprocess captures. |
| */ |
| private void testReprocessAbort(String cameraId, Size inputSize, int inputFormat, |
| Size reprocessOutputSize, int reprocessOutputFormat) throws Exception { |
| if (VERBOSE) { |
| Log.v(TAG, "testReprocessAbort: cameraId: " + cameraId + " inputSize: " + |
| inputSize + " inputFormat: " + inputFormat + " reprocessOutputSize: " + |
| reprocessOutputSize + " reprocessOutputFormat: " + reprocessOutputFormat); |
| } |
| |
| try { |
| setupImageReaders(inputSize, inputFormat, reprocessOutputSize, reprocessOutputFormat, |
| NUM_REPROCESS_CAPTURES); |
| setupReprocessableSession(/*previewSurface*/null, NUM_REPROCESS_CAPTURES); |
| |
| // Test two cases: submitting reprocess requests one by one and in a burst. |
| boolean submitInBursts[] = {false, true}; |
| for (boolean submitInBurst : submitInBursts) { |
| // Prepare reprocess capture requests. |
| ArrayList<CaptureRequest> reprocessRequests = |
| new ArrayList<>(NUM_REPROCESS_CAPTURES); |
| |
| for (int i = 0; i < NUM_REPROCESS_CAPTURES; i++) { |
| TotalCaptureResult result = submitCaptureRequest(mFirstImageReader.getSurface(), |
| /*inputResult*/null); |
| |
| mImageWriter.queueInputImage( |
| mFirstImageReaderListener.getImage(CAPTURE_TIMEOUT_MS)); |
| CaptureRequest.Builder builder = mCamera.createReprocessCaptureRequest(result); |
| builder.addTarget(getReprocessOutputImageReader().getSurface()); |
| reprocessRequests.add(builder.build()); |
| } |
| |
| SimpleCaptureCallback captureCallback = new SimpleCaptureCallback(); |
| |
| // Submit reprocess capture requests. |
| if (submitInBurst) { |
| mSession.captureBurst(reprocessRequests, captureCallback, mHandler); |
| } else { |
| for (CaptureRequest request : reprocessRequests) { |
| mSession.capture(request, captureCallback, mHandler); |
| } |
| } |
| |
| // Abort after getting the first result |
| TotalCaptureResult reprocessResult = |
| captureCallback.getTotalCaptureResultForRequest(reprocessRequests.get(0), |
| CAPTURE_TIMEOUT_FRAMES); |
| mSession.abortCaptures(); |
| |
| // Wait until the session is ready again. |
| mSessionListener.getStateWaiter().waitForState( |
| BlockingSessionCallback.SESSION_READY, SESSION_CLOSE_TIMEOUT_MS); |
| |
| // Gather all failed requests. |
| ArrayList<CaptureFailure> failures = |
| captureCallback.getCaptureFailures(NUM_REPROCESS_CAPTURES - 1); |
| ArrayList<CaptureRequest> failedRequests = new ArrayList<>(); |
| for (CaptureFailure failure : failures) { |
| failedRequests.add(failure.getRequest()); |
| } |
| |
| // For each request that didn't fail must have a valid result. |
| for (int i = 1; i < reprocessRequests.size(); i++) { |
| CaptureRequest request = reprocessRequests.get(i); |
| if (!failedRequests.contains(request)) { |
| captureCallback.getTotalCaptureResultForRequest(request, |
| CAPTURE_TIMEOUT_FRAMES); |
| } |
| } |
| |
| // Drain the image reader listeners. |
| mFirstImageReaderListener.drain(); |
| if (!mShareOneImageReader) { |
| mSecondImageReaderListener.drain(); |
| } |
| |
| // Make sure all input surfaces are released. |
| for (int i = 0; i < NUM_REPROCESS_CAPTURES; i++) { |
| mImageWriterListener.waitForImageReleased(CAPTURE_TIMEOUT_MS); |
| } |
| } |
| } finally { |
| closeReprossibleSession(); |
| closeImageReaders(); |
| } |
| } |
| |
| /** |
| * Test timestamps for reprocess requests. Reprocess request's shutter timestamp, result's |
| * sensor timestamp, and output image's timestamp should match the reprocess input's timestamp. |
| */ |
| private void testReprocessTimestamps(String cameraId, Size inputSize, int inputFormat, |
| Size reprocessOutputSize, int reprocessOutputFormat) throws Exception { |
| if (VERBOSE) { |
| Log.v(TAG, "testReprocessTimestamps: cameraId: " + cameraId + " inputSize: " + |
| inputSize + " inputFormat: " + inputFormat + " reprocessOutputSize: " + |
| reprocessOutputSize + " reprocessOutputFormat: " + reprocessOutputFormat); |
| } |
| |
| try { |
| setupImageReaders(inputSize, inputFormat, reprocessOutputSize, reprocessOutputFormat, |
| NUM_REPROCESS_CAPTURES); |
| setupReprocessableSession(/*previewSurface*/null, NUM_REPROCESS_CAPTURES); |
| |
| // Prepare reprocess capture requests. |
| ArrayList<CaptureRequest> reprocessRequests = new ArrayList<>(NUM_REPROCESS_CAPTURES); |
| ArrayList<Long> expectedTimestamps = new ArrayList<>(NUM_REPROCESS_CAPTURES); |
| |
| for (int i = 0; i < NUM_REPROCESS_CAPTURES; i++) { |
| TotalCaptureResult result = submitCaptureRequest(mFirstImageReader.getSurface(), |
| /*inputResult*/null); |
| |
| mImageWriter.queueInputImage( |
| mFirstImageReaderListener.getImage(CAPTURE_TIMEOUT_MS)); |
| CaptureRequest.Builder builder = mCamera.createReprocessCaptureRequest(result); |
| builder.addTarget(getReprocessOutputImageReader().getSurface()); |
| reprocessRequests.add(builder.build()); |
| // Reprocess result's timestamp should match input image's timestamp. |
| expectedTimestamps.add(result.get(CaptureResult.SENSOR_TIMESTAMP)); |
| } |
| |
| // Submit reprocess requests. |
| SimpleCaptureCallback captureCallback = new SimpleCaptureCallback(); |
| mSession.captureBurst(reprocessRequests, captureCallback, mHandler); |
| |
| // Verify we get the expected timestamps. |
| for (int i = 0; i < reprocessRequests.size(); i++) { |
| captureCallback.waitForCaptureStart(reprocessRequests.get(i), |
| expectedTimestamps.get(i), CAPTURE_TIMEOUT_FRAMES); |
| } |
| |
| TotalCaptureResult[] reprocessResults = |
| captureCallback.getTotalCaptureResultsForRequests(reprocessRequests, |
| CAPTURE_TIMEOUT_FRAMES); |
| |
| for (int i = 0; i < expectedTimestamps.size(); i++) { |
| // Verify the result timestamps match the input image's timestamps. |
| long expected = expectedTimestamps.get(i); |
| long timestamp = reprocessResults[i].get(CaptureResult.SENSOR_TIMESTAMP); |
| assertEquals("Reprocess result timestamp (" + timestamp + ") doesn't match input " + |
| "image's timestamp (" + expected + ")", expected, timestamp); |
| |
| // Verify the reprocess output image timestamps match the input image's timestamps. |
| Image image = getReprocessOutputImageReaderListener().getImage(CAPTURE_TIMEOUT_MS); |
| timestamp = image.getTimestamp(); |
| image.close(); |
| |
| assertEquals("Reprocess output timestamp (" + timestamp + ") doesn't match input " + |
| "image's timestamp (" + expected + ")", expected, timestamp); |
| } |
| |
| // Make sure all input surfaces are released. |
| for (int i = 0; i < NUM_REPROCESS_CAPTURES; i++) { |
| mImageWriterListener.waitForImageReleased(CAPTURE_TIMEOUT_MS); |
| } |
| } finally { |
| closeReprossibleSession(); |
| closeImageReaders(); |
| } |
| } |
| |
| /** |
| * Test JPEG tags for reprocess requests. Reprocess result's JPEG tags and JPEG image's tags |
| * match reprocess request's JPEG tags. |
| */ |
| private void testReprocessJpegExif(String cameraId, Size inputSize, int inputFormat, |
| Size reprocessOutputSize) throws Exception { |
| if (VERBOSE) { |
| Log.v(TAG, "testReprocessJpegExif: cameraId: " + cameraId + " inputSize: " + |
| inputSize + " inputFormat: " + inputFormat + " reprocessOutputSize: " + |
| reprocessOutputSize); |
| } |
| |
| Size[] thumbnailSizes = mStaticInfo.getAvailableThumbnailSizesChecked(); |
| Size[] testThumbnailSizes = new Size[EXIF_TEST_DATA.length]; |
| Arrays.fill(testThumbnailSizes, thumbnailSizes[thumbnailSizes.length - 1]); |
| // Make sure thumbnail size (0, 0) is covered. |
| testThumbnailSizes[0] = new Size(0, 0); |
| |
| try { |
| setupImageReaders(inputSize, inputFormat, reprocessOutputSize, ImageFormat.JPEG, |
| EXIF_TEST_DATA.length); |
| setupReprocessableSession(/*previewSurface*/null, EXIF_TEST_DATA.length); |
| |
| // Prepare reprocess capture requests. |
| ArrayList<CaptureRequest> reprocessRequests = new ArrayList<>(EXIF_TEST_DATA.length); |
| |
| for (int i = 0; i < EXIF_TEST_DATA.length; i++) { |
| TotalCaptureResult result = submitCaptureRequest(mFirstImageReader.getSurface(), |
| /*inputResult*/null); |
| mImageWriter.queueInputImage( |
| mFirstImageReaderListener.getImage(CAPTURE_TIMEOUT_MS)); |
| |
| CaptureRequest.Builder builder = mCamera.createReprocessCaptureRequest(result); |
| builder.addTarget(getReprocessOutputImageReader().getSurface()); |
| |
| // set jpeg keys |
| setJpegKeys(builder, EXIF_TEST_DATA[i], testThumbnailSizes[i], mCollector); |
| reprocessRequests.add(builder.build()); |
| } |
| |
| // Submit reprocess requests. |
| SimpleCaptureCallback captureCallback = new SimpleCaptureCallback(); |
| mSession.captureBurst(reprocessRequests, captureCallback, mHandler); |
| |
| TotalCaptureResult[] reprocessResults = |
| captureCallback.getTotalCaptureResultsForRequests(reprocessRequests, |
| CAPTURE_TIMEOUT_FRAMES); |
| |
| for (int i = 0; i < EXIF_TEST_DATA.length; i++) { |
| // Verify output image's and result's JPEG EXIF data. |
| Image image = getReprocessOutputImageReaderListener().getImage(CAPTURE_TIMEOUT_MS); |
| verifyJpegKeys(image, reprocessResults[i], reprocessOutputSize, |
| testThumbnailSizes[i], EXIF_TEST_DATA[i], mStaticInfo, mCollector); |
| image.close(); |
| |
| } |
| } finally { |
| closeReprossibleSession(); |
| closeImageReaders(); |
| } |
| } |
| |
| |
| |
| /** |
| * Test the following keys in reprocess results match the keys in reprocess requests: |
| * 1. EDGE_MODE |
| * 2. NOISE_REDUCTION_MODE |
| * 3. REPROCESS_EFFECTIVE_EXPOSURE_FACTOR (only for YUV reprocess) |
| */ |
| private void testReprocessRequestKeys(String cameraId, Size inputSize, int inputFormat, |
| Size reprocessOutputSize, int reprocessOutputFormat) throws Exception { |
| if (VERBOSE) { |
| Log.v(TAG, "testReprocessRequestKeys: cameraId: " + cameraId + " inputSize: " + |
| inputSize + " inputFormat: " + inputFormat + " reprocessOutputSize: " + |
| reprocessOutputSize + " reprocessOutputFormat: " + reprocessOutputFormat); |
| } |
| |
| final Integer[] EDGE_MODES = {CaptureRequest.EDGE_MODE_FAST, |
| CaptureRequest.EDGE_MODE_HIGH_QUALITY, CaptureRequest.EDGE_MODE_OFF, |
| CaptureRequest.EDGE_MODE_ZERO_SHUTTER_LAG}; |
| final Integer[] NR_MODES = {CaptureRequest.NOISE_REDUCTION_MODE_HIGH_QUALITY, |
| CaptureRequest.NOISE_REDUCTION_MODE_OFF, |
| CaptureRequest.NOISE_REDUCTION_MODE_ZERO_SHUTTER_LAG, |
| CaptureRequest.NOISE_REDUCTION_MODE_FAST}; |
| final Float[] EFFECTIVE_EXP_FACTORS = {null, 1.0f, 2.5f, 4.0f}; |
| int numFrames = EDGE_MODES.length; |
| |
| try { |
| setupImageReaders(inputSize, inputFormat, reprocessOutputSize, reprocessOutputFormat, |
| numFrames); |
| setupReprocessableSession(/*previewSurface*/null, numFrames); |
| |
| // Prepare reprocess capture requests. |
| ArrayList<CaptureRequest> reprocessRequests = new ArrayList<>(numFrames); |
| |
| for (int i = 0; i < numFrames; i++) { |
| TotalCaptureResult result = submitCaptureRequest(mFirstImageReader.getSurface(), |
| /*inputResult*/null); |
| mImageWriter.queueInputImage( |
| mFirstImageReaderListener.getImage(CAPTURE_TIMEOUT_MS)); |
| |
| CaptureRequest.Builder builder = mCamera.createReprocessCaptureRequest(result); |
| builder.addTarget(getReprocessOutputImageReader().getSurface()); |
| |
| // Set reprocess request keys |
| builder.set(CaptureRequest.EDGE_MODE, EDGE_MODES[i]); |
| builder.set(CaptureRequest.NOISE_REDUCTION_MODE, NR_MODES[i]); |
| if (inputFormat == ImageFormat.YUV_420_888) { |
| builder.set(CaptureRequest.REPROCESS_EFFECTIVE_EXPOSURE_FACTOR, |
| EFFECTIVE_EXP_FACTORS[i]); |
| } |
| reprocessRequests.add(builder.build()); |
| } |
| |
| // Submit reprocess requests. |
| SimpleCaptureCallback captureCallback = new SimpleCaptureCallback(); |
| mSession.captureBurst(reprocessRequests, captureCallback, mHandler); |
| |
| TotalCaptureResult[] reprocessResults = |
| captureCallback.getTotalCaptureResultsForRequests(reprocessRequests, |
| CAPTURE_TIMEOUT_FRAMES); |
| |
| for (int i = 0; i < numFrames; i++) { |
| // Verify result's keys |
| Integer resultEdgeMode = reprocessResults[i].get(CaptureResult.EDGE_MODE); |
| Integer resultNoiseReductionMode = |
| reprocessResults[i].get(CaptureResult.NOISE_REDUCTION_MODE); |
| |
| assertEquals("Reprocess result edge mode (" + resultEdgeMode + |
| ") doesn't match requested edge mode (" + EDGE_MODES[i] + ")", |
| resultEdgeMode, EDGE_MODES[i]); |
| assertEquals("Reprocess result noise reduction mode (" + resultNoiseReductionMode + |
| ") doesn't match requested noise reduction mode (" + |
| NR_MODES[i] + ")", resultNoiseReductionMode, |
| NR_MODES[i]); |
| |
| if (inputFormat == ImageFormat.YUV_420_888) { |
| Float resultEffectiveExposureFactor = reprocessResults[i].get( |
| CaptureResult.REPROCESS_EFFECTIVE_EXPOSURE_FACTOR); |
| assertEquals("Reprocess effective exposure factor (" + |
| resultEffectiveExposureFactor + ") doesn't match requested " + |
| "effective exposure factor (" + EFFECTIVE_EXP_FACTORS[i] + ")", |
| resultEffectiveExposureFactor, EFFECTIVE_EXP_FACTORS[i]); |
| } |
| } |
| } finally { |
| closeReprossibleSession(); |
| closeImageReaders(); |
| } |
| } |
| |
| /** |
| * Set up two image readers: one for regular capture (used for reprocess input) and one for |
| * reprocess capture. |
| */ |
| private void setupImageReaders(Size inputSize, int inputFormat, Size reprocessOutputSize, |
| int reprocessOutputFormat, int maxImages) { |
| |
| mShareOneImageReader = false; |
| // If the regular output and reprocess output have the same size and format, |
| // they can share one image reader. |
| if (inputFormat == reprocessOutputFormat && |
| inputSize.equals(reprocessOutputSize)) { |
| maxImages *= 2; |
| mShareOneImageReader = true; |
| } |
| // create an ImageReader for the regular capture |
| mFirstImageReaderListener = new SimpleImageReaderListener(); |
| mFirstImageReader = makeImageReader(inputSize, inputFormat, maxImages, |
| mFirstImageReaderListener, mHandler); |
| |
| if (!mShareOneImageReader) { |
| // create an ImageReader for the reprocess capture |
| mSecondImageReaderListener = new SimpleImageReaderListener(); |
| mSecondImageReader = makeImageReader(reprocessOutputSize, reprocessOutputFormat, |
| maxImages, mSecondImageReaderListener, mHandler); |
| } |
| } |
| |
| /** |
| * Close two image readers. |
| */ |
| private void closeImageReaders() { |
| CameraTestUtils.closeImageReader(mFirstImageReader); |
| mFirstImageReader = null; |
| CameraTestUtils.closeImageReader(mSecondImageReader); |
| mSecondImageReader = null; |
| } |
| |
| /** |
| * Get the ImageReader for reprocess output. |
| */ |
| private ImageReader getReprocessOutputImageReader() { |
| if (mShareOneImageReader) { |
| return mFirstImageReader; |
| } else { |
| return mSecondImageReader; |
| } |
| } |
| |
| private SimpleImageReaderListener getReprocessOutputImageReaderListener() { |
| if (mShareOneImageReader) { |
| return mFirstImageReaderListener; |
| } else { |
| return mSecondImageReaderListener; |
| } |
| } |
| |
| /** |
| * Set up a reprocessable session and create an ImageWriter with the sessoin's input surface. |
| */ |
| private void setupReprocessableSession(Surface previewSurface, int numImageWriterImages) |
| throws Exception { |
| // create a reprocessable capture session |
| List<Surface> outSurfaces = new ArrayList<Surface>(); |
| outSurfaces.add(mFirstImageReader.getSurface()); |
| if (!mShareOneImageReader) { |
| outSurfaces.add(mSecondImageReader.getSurface()); |
| } |
| if (previewSurface != null) { |
| outSurfaces.add(previewSurface); |
| } |
| |
| InputConfiguration inputConfig = new InputConfiguration(mFirstImageReader.getWidth(), |
| mFirstImageReader.getHeight(), mFirstImageReader.getImageFormat()); |
| String inputConfigString = inputConfig.toString(); |
| if (VERBOSE) { |
| Log.v(TAG, "InputConfiguration: " + inputConfigString); |
| } |
| assertTrue(String.format("inputConfig is wrong: %dx%d format %d. Expect %dx%d format %d", |
| inputConfig.getWidth(), inputConfig.getHeight(), inputConfig.getFormat(), |
| mFirstImageReader.getWidth(), mFirstImageReader.getHeight(), |
| mFirstImageReader.getImageFormat()), |
| inputConfig.getWidth() == mFirstImageReader.getWidth() && |
| inputConfig.getHeight() == mFirstImageReader.getHeight() && |
| inputConfig.getFormat() == mFirstImageReader.getImageFormat()); |
| |
| mSessionListener = new BlockingSessionCallback(); |
| mSession = configureReprocessableCameraSession(mCamera, inputConfig, outSurfaces, |
| mSessionListener, mHandler); |
| |
| // create an ImageWriter |
| mInputSurface = mSession.getInputSurface(); |
| mImageWriter = ImageWriter.newInstance(mInputSurface, |
| numImageWriterImages); |
| |
| mImageWriterListener = new SimpleImageWriterListener(mImageWriter); |
| mImageWriter.setOnImageReleasedListener(mImageWriterListener, mHandler); |
| } |
| |
| /** |
| * Close the reprocessable session and ImageWriter. |
| */ |
| private void closeReprossibleSession() { |
| mInputSurface = null; |
| |
| if (mSession != null) { |
| mSession.close(); |
| mSession = null; |
| } |
| |
| if (mImageWriter != null) { |
| mImageWriter.close(); |
| mImageWriter = null; |
| } |
| } |
| |
| /** |
| * Do one reprocess capture. |
| */ |
| private ImageResultHolder doReprocessCapture() throws Exception { |
| return doReprocessBurstCapture(/*numBurst*/1)[0]; |
| } |
| |
| /** |
| * Do a burst of reprocess captures. |
| */ |
| private ImageResultHolder[] doReprocessBurstCapture(int numBurst) throws Exception { |
| boolean[] isReprocessCaptures = new boolean[numBurst]; |
| for (int i = 0; i < numBurst; i++) { |
| isReprocessCaptures[i] = true; |
| } |
| |
| return doMixedReprocessBurstCapture(isReprocessCaptures); |
| } |
| |
| /** |
| * Do a burst of captures that are mixed with regular and reprocess captures. |
| * |
| * @param isReprocessCaptures An array whose elements indicate whether it's a reprocess capture |
| * request. If the element is true, it represents a reprocess capture |
| * request. If the element is false, it represents a regular capture |
| * request. The size of the array is the number of capture requests |
| * in the burst. |
| */ |
| private ImageResultHolder[] doMixedReprocessBurstCapture(boolean[] isReprocessCaptures) |
| throws Exception { |
| if (isReprocessCaptures == null || isReprocessCaptures.length <= 0) { |
| throw new IllegalArgumentException("isReprocessCaptures must have at least 1 capture."); |
| } |
| |
| TotalCaptureResult[] results = new TotalCaptureResult[isReprocessCaptures.length]; |
| for (int i = 0; i < isReprocessCaptures.length; i++) { |
| // submit a capture and get the result if this entry is a reprocess capture. |
| if (isReprocessCaptures[i]) { |
| results[i] = submitCaptureRequest(mFirstImageReader.getSurface(), |
| /*inputResult*/null); |
| mImageWriter.queueInputImage( |
| mFirstImageReaderListener.getImage(CAPTURE_TIMEOUT_MS)); |
| } |
| } |
| |
| Surface[] outputSurfaces = new Surface[isReprocessCaptures.length]; |
| for (int i = 0; i < isReprocessCaptures.length; i++) { |
| outputSurfaces[i] = getReprocessOutputImageReader().getSurface(); |
| } |
| |
| TotalCaptureResult[] finalResults = submitMixedCaptureBurstRequest(outputSurfaces, results); |
| |
| ImageResultHolder[] holders = new ImageResultHolder[isReprocessCaptures.length]; |
| for (int i = 0; i < isReprocessCaptures.length; i++) { |
| Image image = getReprocessOutputImageReaderListener().getImage(CAPTURE_TIMEOUT_MS); |
| holders[i] = new ImageResultHolder(image, finalResults[i]); |
| } |
| |
| return holders; |
| } |
| |
| /** |
| * Start preview without a listener. |
| */ |
| private void startPreview(Surface previewSurface) throws Exception { |
| CaptureRequest.Builder builder = mCamera.createCaptureRequest(ZSL_TEMPLATE); |
| builder.addTarget(previewSurface); |
| mSession.setRepeatingRequest(builder.build(), null, mHandler); |
| } |
| |
| /** |
| * Issue a capture request and return the result. If inputResult is null, it's a regular |
| * request. Otherwise, it's a reprocess request. |
| */ |
| private TotalCaptureResult submitCaptureRequest(Surface output, |
| TotalCaptureResult inputResult) throws Exception { |
| Surface[] outputs = new Surface[1]; |
| outputs[0] = output; |
| TotalCaptureResult[] inputResults = new TotalCaptureResult[1]; |
| inputResults[0] = inputResult; |
| |
| return submitMixedCaptureBurstRequest(outputs, inputResults)[0]; |
| } |
| |
| /** |
| * Submit a burst request mixed with regular and reprocess requests. |
| * |
| * @param outputs An array of output surfaces. One output surface will be used in one request |
| * so the length of the array is the number of requests in a burst request. |
| * @param inputResults An array of input results. If it's null, all requests are regular |
| * requests. If an element is null, that element represents a regular |
| * request. If an element if not null, that element represents a reprocess |
| * request. |
| * |
| */ |
| private TotalCaptureResult[] submitMixedCaptureBurstRequest(Surface[] outputs, |
| TotalCaptureResult[] inputResults) throws Exception { |
| if (outputs == null || outputs.length <= 0) { |
| throw new IllegalArgumentException("outputs must have at least 1 surface"); |
| } else if (inputResults != null && inputResults.length != outputs.length) { |
| throw new IllegalArgumentException("The lengths of outputs and inputResults " + |
| "don't match"); |
| } |
| |
| int numReprocessCaptures = 0; |
| SimpleCaptureCallback captureCallback = new SimpleCaptureCallback(); |
| ArrayList<CaptureRequest> captureRequests = new ArrayList<>(outputs.length); |
| |
| // Prepare a list of capture requests. Whether it's a regular or reprocess capture request |
| // is based on inputResults array. |
| for (int i = 0; i < outputs.length; i++) { |
| CaptureRequest.Builder builder; |
| boolean isReprocess = (inputResults != null && inputResults[i] != null); |
| if (isReprocess) { |
| builder = mCamera.createReprocessCaptureRequest(inputResults[i]); |
| numReprocessCaptures++; |
| } else { |
| builder = mCamera.createCaptureRequest(CAPTURE_TEMPLATE); |
| } |
| builder.addTarget(outputs[i]); |
| CaptureRequest request = builder.build(); |
| assertTrue("Capture request reprocess type " + request.isReprocess() + " is wrong.", |
| request.isReprocess() == isReprocess); |
| |
| captureRequests.add(request); |
| } |
| |
| if (captureRequests.size() == 1) { |
| mSession.capture(captureRequests.get(0), captureCallback, mHandler); |
| } else { |
| mSession.captureBurst(captureRequests, captureCallback, mHandler); |
| } |
| |
| TotalCaptureResult[] results; |
| if (numReprocessCaptures == 0 || numReprocessCaptures == outputs.length) { |
| results = new TotalCaptureResult[outputs.length]; |
| // If the requests are not mixed, they should come in order. |
| for (int i = 0; i < results.length; i++){ |
| results[i] = captureCallback.getTotalCaptureResultForRequest( |
| captureRequests.get(i), CAPTURE_TIMEOUT_FRAMES); |
| } |
| } else { |
| // If the requests are mixed, they may not come in order. |
| results = captureCallback.getTotalCaptureResultsForRequests( |
| captureRequests, CAPTURE_TIMEOUT_FRAMES * captureRequests.size()); |
| } |
| |
| // make sure all input surfaces are released. |
| for (int i = 0; i < numReprocessCaptures; i++) { |
| mImageWriterListener.waitForImageReleased(CAPTURE_TIMEOUT_MS); |
| } |
| |
| return results; |
| } |
| |
| private Size getMaxSize(int format, StaticMetadata.StreamDirection direction) { |
| Size[] sizes = mStaticInfo.getAvailableSizesForFormatChecked(format, direction); |
| return getAscendingOrderSizes(Arrays.asList(sizes), /*ascending*/false).get(0); |
| } |
| |
| private boolean isYuvReprocessSupported(String cameraId) throws Exception { |
| return isReprocessSupported(cameraId, ImageFormat.YUV_420_888); |
| } |
| |
| private boolean isOpaqueReprocessSupported(String cameraId) throws Exception { |
| return isReprocessSupported(cameraId, ImageFormat.PRIVATE); |
| } |
| |
| private void dumpImage(Image image, String name) { |
| String filename = DEBUG_FILE_NAME_BASE + name; |
| switch(image.getFormat()) { |
| case ImageFormat.JPEG: |
| filename += ".jpg"; |
| break; |
| case ImageFormat.NV16: |
| case ImageFormat.NV21: |
| case ImageFormat.YUV_420_888: |
| filename += ".yuv"; |
| break; |
| default: |
| filename += "." + image.getFormat(); |
| break; |
| } |
| |
| Log.d(TAG, "dumping an image to " + filename); |
| dumpFile(filename , getDataFromImage(image)); |
| } |
| |
| /** |
| * A class that holds an Image and a TotalCaptureResult. |
| */ |
| private static class ImageResultHolder { |
| private final Image mImage; |
| private final TotalCaptureResult mResult; |
| |
| public ImageResultHolder(Image image, TotalCaptureResult result) { |
| mImage = image; |
| mResult = result; |
| } |
| |
| public Image getImage() { |
| return mImage; |
| } |
| |
| public TotalCaptureResult getTotalCaptureResult() { |
| return mResult; |
| } |
| } |
| } |