| /* |
| * Copyright 2013 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 android.content.Context; |
| import android.graphics.ImageFormat; |
| import android.hardware.camera2.CameraCharacteristics; |
| import android.hardware.camera2.CameraDevice; |
| import android.hardware.camera2.CameraManager; |
| import android.hardware.camera2.CameraMetadata; |
| import android.hardware.camera2.CaptureRequest; |
| import android.hardware.camera2.CaptureResult; |
| import android.hardware.camera2.Size; |
| import static android.hardware.camera2.cts.CameraTestUtils.*; |
| import android.media.ImageReader; |
| import android.os.Handler; |
| import android.os.HandlerThread; |
| import android.test.AndroidTestCase; |
| import android.util.Log; |
| import android.view.Surface; |
| |
| import com.android.ex.camera2.blocking.BlockingStateListener; |
| import static com.android.ex.camera2.blocking.BlockingStateListener.*; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| public class CameraCaptureResultTest extends AndroidTestCase { |
| private static final String TAG = "CameraCaptureResultTest"; |
| private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE); |
| |
| private CameraManager mCameraManager; |
| private HandlerThread mHandlerThread; |
| private Handler mHandler; |
| private ImageReader mImageReader; |
| private Surface mSurface; |
| private BlockingStateListener mCameraListener; |
| |
| private static final int MAX_NUM_IMAGES = 5; |
| private static final int NUM_FRAMES_VERIFIED = 300; |
| private static final long WAIT_FOR_RESULT_TIMEOUT_MS = 3000; |
| |
| // List that includes all public keys from CaptureResult |
| List<CameraMetadata.Key<?>> mAllKeys; |
| |
| // List tracking the failed test keys. |
| List<CameraMetadata.Key<?>> mFailedKeys = new ArrayList<CameraMetadata.Key<?>>(); |
| |
| @Override |
| public void setContext(Context context) { |
| mAllKeys = getAllCaptureResultKeys(); |
| super.setContext(context); |
| } |
| |
| @Override |
| protected void setUp() throws Exception { |
| super.setUp(); |
| mCameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE); |
| assertNotNull("Can't connect to camera manager", mCameraManager); |
| mHandlerThread = new HandlerThread(TAG); |
| mHandlerThread.start(); |
| mHandler = new Handler(mHandlerThread.getLooper()); |
| mCameraListener = new BlockingStateListener(); |
| mFailedKeys.clear(); |
| } |
| |
| @Override |
| protected void tearDown() throws Exception { |
| mHandlerThread.quitSafely(); |
| super.tearDown(); |
| } |
| |
| /** |
| * <p> |
| * Basic non-null check test for multiple capture results. |
| * </p> |
| * <p> |
| * When capturing many frames, some camera devices may return some results that have null keys |
| * randomly, which is an API violation and could cause application crash randomly. This test |
| * runs a typical flexible yuv capture many times, and checks if there is any null entries in |
| * a capture result. |
| * </p> |
| */ |
| public void testCameraCaptureResultAllKeys() throws Exception { |
| /** |
| * Hardcode a key waiver list for the keys that are allowed to be null. |
| * FIXME: We need get ride of this list, see bug 11116270. |
| */ |
| List<CameraMetadata.Key<?>> waiverkeys = new ArrayList<CameraMetadata.Key<?>>(); |
| waiverkeys.add(CaptureResult.JPEG_GPS_COORDINATES); |
| waiverkeys.add(CaptureResult.JPEG_GPS_PROCESSING_METHOD); |
| waiverkeys.add(CaptureResult.JPEG_GPS_TIMESTAMP); |
| waiverkeys.add(CaptureResult.JPEG_ORIENTATION); |
| waiverkeys.add(CaptureResult.JPEG_QUALITY); |
| waiverkeys.add(CaptureResult.JPEG_THUMBNAIL_QUALITY); |
| waiverkeys.add(CaptureResult.JPEG_THUMBNAIL_SIZE); |
| waiverkeys.add(CaptureResult.SENSOR_TEMPERATURE); |
| |
| String[] ids = mCameraManager.getCameraIdList(); |
| for (int i = 0; i < ids.length; i++) { |
| CameraCharacteristics props = mCameraManager.getCameraCharacteristics(ids[i]); |
| assertNotNull("CameraCharacteristics shouldn't be null", props); |
| Integer hwLevel = props.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL); |
| if (hwLevel != CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL) { |
| continue; |
| } |
| // TODO: check for LIMITED keys |
| |
| CameraDevice camera = null; |
| try { |
| Size[] sizes = CameraTestUtils.getSupportedSizeForFormat( |
| ImageFormat.YUV_420_888, ids[i], mCameraManager); |
| CameraTestUtils.assertArrayNotEmpty(sizes, "Available sizes shouldn't be empty"); |
| createDefaultSurface(sizes[0]); |
| |
| if (VERBOSE) { |
| Log.v(TAG, "Testing camera " + ids[i] + "for size " + sizes[0].toString()); |
| } |
| |
| camera = CameraTestUtils.openCamera( |
| mCameraManager, ids[i], mCameraListener, mHandler); |
| assertNotNull( |
| String.format("Failed to open camera device %s", ids[i]), camera); |
| mCameraListener.waitForState(STATE_UNCONFIGURED, CAMERA_OPEN_TIMEOUT_MS); |
| |
| List<Surface> outputSurfaces = new ArrayList<Surface>(1); |
| outputSurfaces.add(mSurface); |
| camera.configureOutputs(outputSurfaces); |
| mCameraListener.waitForState(STATE_BUSY, CAMERA_BUSY_TIMEOUT_MS); |
| mCameraListener.waitForState(STATE_IDLE, CAMERA_IDLE_TIMEOUT_MS); |
| |
| CaptureRequest.Builder requestBuilder = |
| camera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); |
| assertNotNull("Failed to create capture request", requestBuilder); |
| requestBuilder.addTarget(mSurface); |
| |
| // Enable face detection if supported |
| byte[] faceModes = props.get( |
| CameraCharacteristics.STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES); |
| assertNotNull("Available face detection modes shouldn't be null", faceModes); |
| for (int m = 0; m < faceModes.length; m++) { |
| if (faceModes[m] == CameraMetadata.STATISTICS_FACE_DETECT_MODE_FULL) { |
| if (VERBOSE) { |
| Log.v(TAG, "testCameraCaptureResultAllKeys - " + |
| "setting facedetection mode to full"); |
| } |
| requestBuilder.set(CaptureRequest.STATISTICS_FACE_DETECT_MODE, |
| (int)faceModes[m]); |
| } |
| } |
| |
| // Enable lensShading mode, it should be supported by full mode device. |
| requestBuilder.set(CaptureRequest.STATISTICS_LENS_SHADING_MAP_MODE, |
| CameraMetadata.STATISTICS_LENS_SHADING_MAP_MODE_ON); |
| |
| SimpleCaptureListener captureListener = new SimpleCaptureListener(); |
| camera.setRepeatingRequest(requestBuilder.build(), captureListener, mHandler); |
| |
| for (int m = 0; m < NUM_FRAMES_VERIFIED; m++) { |
| if(VERBOSE) { |
| Log.v(TAG, "Testing frame " + m); |
| } |
| validateCaptureResult( |
| captureListener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS), |
| waiverkeys); |
| } |
| |
| // Stop repeat, wait for captures to complete, and disconnect from surfaces |
| camera.configureOutputs(/*outputs*/ null); |
| mCameraListener.waitForState(STATE_BUSY, CAMERA_BUSY_TIMEOUT_MS); |
| mCameraListener.waitForState(STATE_UNCONFIGURED, CAMERA_IDLE_TIMEOUT_MS); |
| // Camera has disconnected, clear out the reader |
| mSurface.release(); |
| mImageReader.close(); |
| } finally { |
| if (camera != null) { |
| camera.close(); |
| } |
| } |
| |
| } |
| } |
| |
| private void validateCaptureResult(CaptureResult result, |
| List<CameraMetadata.Key<?>> skippedKeys) throws Exception { |
| for (CameraMetadata.Key<?> key : mAllKeys) { |
| if (!skippedKeys.contains(key) && result.get(key) == null) { |
| mFailedKeys.add(key); |
| } |
| } |
| |
| StringBuffer failedKeyNames = new StringBuffer("Below Keys have null values:\n"); |
| for (CameraMetadata.Key<?> key : mFailedKeys) { |
| failedKeyNames.append(key.getName() + "\n"); |
| } |
| |
| assertTrue("Some keys have null values, " + failedKeyNames.toString(), |
| mFailedKeys.isEmpty()); |
| } |
| |
| private void createDefaultSurface(Size sz) { |
| mImageReader = |
| ImageReader.newInstance(sz.getWidth(), |
| sz.getHeight(), |
| ImageFormat.YUV_420_888, |
| MAX_NUM_IMAGES); |
| mImageReader.setOnImageAvailableListener(new ImageDropperListener(), mHandler); |
| mSurface = mImageReader.getSurface(); |
| } |
| |
| /** |
| * TODO: Use CameraCharacteristics.getAvailableCaptureResultKeys() once we can filter out |
| * @hide keys. |
| * |
| */ |
| |
| /*@O~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~ |
| * The key entries below this point are generated from metadata |
| * definitions in /system/media/camera/docs. Do not modify by hand or |
| * modify the comment blocks at the start or end. |
| *~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~*/ |
| |
| private static List<CameraMetadata.Key<?>> getAllCaptureResultKeys() { |
| ArrayList<CameraMetadata.Key<?>> resultKeys = new ArrayList<CameraMetadata.Key<?>>(); |
| resultKeys.add(CaptureResult.COLOR_CORRECTION_TRANSFORM); |
| resultKeys.add(CaptureResult.COLOR_CORRECTION_GAINS); |
| resultKeys.add(CaptureResult.CONTROL_AE_MODE); |
| resultKeys.add(CaptureResult.CONTROL_AE_REGIONS); |
| resultKeys.add(CaptureResult.CONTROL_AF_MODE); |
| resultKeys.add(CaptureResult.CONTROL_AF_REGIONS); |
| resultKeys.add(CaptureResult.CONTROL_AWB_MODE); |
| resultKeys.add(CaptureResult.CONTROL_AWB_REGIONS); |
| resultKeys.add(CaptureResult.CONTROL_MODE); |
| resultKeys.add(CaptureResult.CONTROL_AE_STATE); |
| resultKeys.add(CaptureResult.CONTROL_AF_STATE); |
| resultKeys.add(CaptureResult.CONTROL_AWB_STATE); |
| resultKeys.add(CaptureResult.EDGE_MODE); |
| resultKeys.add(CaptureResult.FLASH_MODE); |
| resultKeys.add(CaptureResult.FLASH_STATE); |
| resultKeys.add(CaptureResult.HOT_PIXEL_MODE); |
| resultKeys.add(CaptureResult.HOT_PIXEL_MAP); |
| resultKeys.add(CaptureResult.JPEG_GPS_COORDINATES); |
| resultKeys.add(CaptureResult.JPEG_GPS_PROCESSING_METHOD); |
| resultKeys.add(CaptureResult.JPEG_GPS_TIMESTAMP); |
| resultKeys.add(CaptureResult.JPEG_ORIENTATION); |
| resultKeys.add(CaptureResult.JPEG_QUALITY); |
| resultKeys.add(CaptureResult.JPEG_THUMBNAIL_QUALITY); |
| resultKeys.add(CaptureResult.JPEG_THUMBNAIL_SIZE); |
| resultKeys.add(CaptureResult.LENS_APERTURE); |
| resultKeys.add(CaptureResult.LENS_FILTER_DENSITY); |
| resultKeys.add(CaptureResult.LENS_FOCAL_LENGTH); |
| resultKeys.add(CaptureResult.LENS_FOCUS_DISTANCE); |
| resultKeys.add(CaptureResult.LENS_OPTICAL_STABILIZATION_MODE); |
| resultKeys.add(CaptureResult.LENS_FOCUS_RANGE); |
| resultKeys.add(CaptureResult.LENS_STATE); |
| resultKeys.add(CaptureResult.NOISE_REDUCTION_MODE); |
| resultKeys.add(CaptureResult.REQUEST_FRAME_COUNT); |
| resultKeys.add(CaptureResult.REQUEST_PIPELINE_DEPTH); |
| resultKeys.add(CaptureResult.SCALER_CROP_REGION); |
| resultKeys.add(CaptureResult.SENSOR_EXPOSURE_TIME); |
| resultKeys.add(CaptureResult.SENSOR_FRAME_DURATION); |
| resultKeys.add(CaptureResult.SENSOR_SENSITIVITY); |
| resultKeys.add(CaptureResult.SENSOR_TIMESTAMP); |
| resultKeys.add(CaptureResult.SENSOR_TEMPERATURE); |
| resultKeys.add(CaptureResult.SENSOR_REFERENCE_ILLUMINANT); |
| resultKeys.add(CaptureResult.SENSOR_CALIBRATION_TRANSFORM); |
| resultKeys.add(CaptureResult.SENSOR_COLOR_TRANSFORM); |
| resultKeys.add(CaptureResult.SENSOR_FORWARD_MATRIX); |
| resultKeys.add(CaptureResult.SENSOR_NEUTRAL_COLOR_POINT); |
| resultKeys.add(CaptureResult.SENSOR_PROFILE_HUE_SAT_MAP); |
| resultKeys.add(CaptureResult.SENSOR_PROFILE_TONE_CURVE); |
| resultKeys.add(CaptureResult.SENSOR_GREEN_SPLIT); |
| resultKeys.add(CaptureResult.SENSOR_TEST_PATTERN_MODE); |
| resultKeys.add(CaptureResult.SHADING_MODE); |
| resultKeys.add(CaptureResult.STATISTICS_FACE_DETECT_MODE); |
| resultKeys.add(CaptureResult.STATISTICS_LENS_SHADING_MAP); |
| resultKeys.add(CaptureResult.STATISTICS_SCENE_FLICKER); |
| resultKeys.add(CaptureResult.TONEMAP_CURVE_BLUE); |
| resultKeys.add(CaptureResult.TONEMAP_CURVE_GREEN); |
| resultKeys.add(CaptureResult.TONEMAP_CURVE_RED); |
| resultKeys.add(CaptureResult.TONEMAP_MODE); |
| resultKeys.add(CaptureResult.BLACK_LEVEL_LOCK); |
| |
| // Add STATISTICS_FACES key separately here because it is not |
| // defined in metadata xml file. |
| resultKeys.add(CaptureResult.STATISTICS_FACES); |
| |
| return resultKeys; |
| } |
| |
| /*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~ |
| * End generated code |
| *~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~O@*/ |
| } |