blob: d58e60080d3a83ab9ca52586c8d73ebcc0537e07 [file] [log] [blame]
/*
* 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 static android.hardware.camera2.cts.CameraTestUtils.CAMERA_IDLE_TIMEOUT_MS;
import static android.hardware.camera2.cts.CameraTestUtils.CAPTURE_RESULT_TIMEOUT_MS;
import static android.hardware.camera2.cts.CameraTestUtils.SESSION_READY_TIMEOUT_MS;
import static android.hardware.camera2.cts.CameraTestUtils.SimpleCaptureCallback;
import static android.hardware.camera2.cts.CameraTestUtils.SimpleImageReaderListener;
import static android.hardware.camera2.cts.CameraTestUtils.dumpFile;
import static android.hardware.camera2.cts.CameraTestUtils.getValueNotNull;
import static com.google.common.truth.Truth.assertWithMessage;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertTrue;
import static junit.framework.Assert.fail;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapRegionDecoder;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorSpace;
import android.graphics.ImageFormat;
import android.graphics.Matrix;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.RectF;
import android.hardware.DataSpace;
import android.hardware.HardwareBuffer;
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.cts.CameraTestUtils.ImageDropperListener;
import android.hardware.camera2.cts.helpers.StaticMetadata;
import android.hardware.camera2.cts.rs.BitmapUtils;
import android.hardware.camera2.cts.testcases.Camera2AndroidTestCase;
import android.hardware.camera2.params.DynamicRangeProfiles;
import android.hardware.camera2.params.OutputConfiguration;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.Image;
import android.media.Image.Plane;
import android.media.ImageReader;
import android.media.ImageWriter;
import android.os.Build;
import android.os.ConditionVariable;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.util.Log;
import android.util.Size;
import android.view.Surface;
import com.android.compatibility.common.util.PropertyUtil;
import com.android.ex.camera2.blocking.BlockingSessionCallback;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* <p>Basic test for ImageReader APIs. It uses CameraDevice as producer, camera
* sends the data to the surface provided by imageReader. Below image formats
* are tested:</p>
*
* <p>YUV_420_888: flexible YUV420, it is mandatory format for camera. </p>
* <p>JPEG: used for JPEG still capture, also mandatory format. </p>
* <p>Some invalid access test. </p>
* <p>TODO: Add more format tests? </p>
*/
@RunWith(Parameterized.class)
public class ImageReaderTest extends Camera2AndroidTestCase {
private static final String TAG = "ImageReaderTest";
private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
// Number of frame (for streaming requests) to be verified.
private static final int NUM_FRAME_VERIFIED = 2;
// Number of frame (for streaming requests) to be verified with log processing time.
private static final int NUM_LONG_PROCESS_TIME_FRAME_VERIFIED = 10;
// The time to hold each image for to simulate long processing time.
private static final int LONG_PROCESS_TIME_MS = 300;
// Max number of images can be accessed simultaneously from ImageReader.
private static final int MAX_NUM_IMAGES = 5;
// Max difference allowed between YUV and JPEG patches. This tolerance is intentionally very
// generous to avoid false positives due to punch/saturation operations vendors apply to the
// JPEG outputs.
private static final double IMAGE_DIFFERENCE_TOLERANCE = 40;
// Legacy level devices needs even larger tolerance because jpeg and yuv are not captured
// from the same frame in legacy mode.
private static final double IMAGE_DIFFERENCE_TOLERANCE_LEGACY = 60;
private SimpleImageListener mListener;
@Override
public void setUp() throws Exception {
super.setUp();
}
@Override
public void tearDown() throws Exception {
super.tearDown();
}
@Test
public void testFlexibleYuv() throws Exception {
for (String id : getCameraIdsUnderTest()) {
try {
Log.i(TAG, "Testing Camera " + id);
openDevice(id);
BufferFormatTestParam params = new BufferFormatTestParam(
ImageFormat.YUV_420_888, /*repeating*/true);
bufferFormatTestByCamera(params);
} finally {
closeDevice(id);
}
}
}
@Test
public void testDepth16() throws Exception {
for (String id : getCameraIdsUnderTest()) {
try {
Log.i(TAG, "Testing Camera " + id);
openDevice(id);
BufferFormatTestParam params = new BufferFormatTestParam(
ImageFormat.DEPTH16, /*repeating*/true);
bufferFormatTestByCamera(params);
} finally {
closeDevice(id);
}
}
}
@Test
public void testDepthPointCloud() throws Exception {
for (String id : getCameraIdsUnderTest()) {
try {
Log.i(TAG, "Testing Camera " + id);
openDevice(id);
BufferFormatTestParam params = new BufferFormatTestParam(
ImageFormat.DEPTH_POINT_CLOUD, /*repeating*/true);
bufferFormatTestByCamera(params);
} finally {
closeDevice(id);
}
}
}
@Test
public void testDynamicDepth() throws Exception {
for (String id : getCameraIdsUnderTest()) {
try {
openDevice(id);
BufferFormatTestParam params = new BufferFormatTestParam(
ImageFormat.DEPTH_JPEG, /*repeating*/true);
params.mCheckSession = true;
bufferFormatTestByCamera(params);
} finally {
closeDevice(id);
}
}
}
@Test
public void testY8() throws Exception {
for (String id : getCameraIdsUnderTest()) {
try {
Log.i(TAG, "Testing Camera " + id);
openDevice(id);
BufferFormatTestParam params = new BufferFormatTestParam(
ImageFormat.Y8, /*repeating*/true);
bufferFormatTestByCamera(params);
} finally {
closeDevice(id);
}
}
}
@Test
public void testJpeg() throws Exception {
for (String id : getCameraIdsUnderTest()) {
try {
Log.v(TAG, "Testing jpeg capture for Camera " + id);
openDevice(id);
BufferFormatTestParam params = new BufferFormatTestParam(
ImageFormat.JPEG, /*repeating*/false);
bufferFormatTestByCamera(params);
} finally {
closeDevice(id);
}
}
}
@Test
public void testRaw() throws Exception {
for (String id : getCameraIdsUnderTest()) {
try {
Log.v(TAG, "Testing raw capture for camera " + id);
openDevice(id);
BufferFormatTestParam params = new BufferFormatTestParam(
ImageFormat.RAW_SENSOR, /*repeating*/false);
bufferFormatTestByCamera(params);
} finally {
closeDevice(id);
}
}
}
@Test
public void testRawPrivate() throws Exception {
for (String id : getCameraIdsUnderTest()) {
try {
Log.v(TAG, "Testing raw capture for camera " + id);
openDevice(id);
BufferFormatTestParam params = new BufferFormatTestParam(
ImageFormat.RAW_PRIVATE, /*repeating*/false);
bufferFormatTestByCamera(params);
} finally {
closeDevice(id);
}
}
}
@Test
public void testP010() throws Exception {
for (String id : getCameraIdsUnderTest()) {
try {
Log.v(TAG, "Testing YUV P010 capture for Camera " + id);
openDevice(id);
if (!mStaticInfo.isCapabilitySupported(CameraCharacteristics.
REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT)) {
continue;
}
Set<Long> availableProfiles =
mStaticInfo.getAvailableDynamicRangeProfilesChecked();
assertFalse("Absent dynamic range profiles", availableProfiles.isEmpty());
assertTrue("HLG10 not present in the available dynamic range profiles",
availableProfiles.contains(DynamicRangeProfiles.HLG10));
BufferFormatTestParam params = new BufferFormatTestParam(
ImageFormat.YCBCR_P010, /*repeating*/false);
params.mDynamicRangeProfile = DynamicRangeProfiles.HLG10;
bufferFormatTestByCamera(params);
} finally {
closeDevice(id);
}
}
}
@Test
public void testDisplayP3Yuv() throws Exception {
for (String id : getCameraIdsUnderTest()) {
try {
if (!mAllStaticInfo.get(id).isCapabilitySupported(CameraCharacteristics
.REQUEST_AVAILABLE_CAPABILITIES_COLOR_SPACE_PROFILES)) {
continue;
}
Set<ColorSpace.Named> availableColorSpaces =
mAllStaticInfo.get(id).getAvailableColorSpacesChecked(
ImageFormat.YUV_420_888);
if (!availableColorSpaces.contains(ColorSpace.Named.DISPLAY_P3)) {
continue;
}
openDevice(id);
Log.v(TAG, "Testing Display P3 Yuv capture for Camera " + id);
BufferFormatTestParam params = new BufferFormatTestParam(
ImageFormat.YUV_420_888, /*repeating*/false);
params.mColorSpace = ColorSpace.Named.DISPLAY_P3;
params.mUseColorSpace = true;
bufferFormatTestByCamera(params);
} finally {
closeDevice(id);
}
}
}
@Test
public void testDisplayP3YuvRepeating() throws Exception {
for (String id : getCameraIdsUnderTest()) {
try {
if (!mAllStaticInfo.get(id).isCapabilitySupported(CameraCharacteristics
.REQUEST_AVAILABLE_CAPABILITIES_COLOR_SPACE_PROFILES)) {
continue;
}
Set<ColorSpace.Named> availableColorSpaces =
mAllStaticInfo.get(id).getAvailableColorSpacesChecked(
ImageFormat.YUV_420_888);
if (!availableColorSpaces.contains(ColorSpace.Named.DISPLAY_P3)) {
continue;
}
openDevice(id);
Log.v(TAG, "Testing repeating Display P3 Yuv capture for Camera " + id);
BufferFormatTestParam params = new BufferFormatTestParam(
ImageFormat.YUV_420_888, /*repeating*/true);
params.mColorSpace = ColorSpace.Named.DISPLAY_P3;
params.mUseColorSpace = true;
bufferFormatTestByCamera(params);
} finally {
closeDevice(id);
}
}
}
@Test
public void testDisplayP3Heic() throws Exception {
for (String id : getCameraIdsUnderTest()) {
try {
if (!mAllStaticInfo.get(id).isCapabilitySupported(CameraCharacteristics
.REQUEST_AVAILABLE_CAPABILITIES_COLOR_SPACE_PROFILES)) {
continue;
}
Set<ColorSpace.Named> availableColorSpaces =
mAllStaticInfo.get(id).getAvailableColorSpacesChecked(ImageFormat.HEIC);
if (!availableColorSpaces.contains(ColorSpace.Named.DISPLAY_P3)) {
continue;
}
openDevice(id);
Log.v(TAG, "Testing Display P3 HEIC capture for Camera " + id);
BufferFormatTestParam params = new BufferFormatTestParam(
ImageFormat.HEIC, /*repeating*/false);
params.mColorSpace = ColorSpace.Named.DISPLAY_P3;
params.mUseColorSpace = true;
bufferFormatTestByCamera(params);
} finally {
closeDevice(id);
}
}
}
@Test
public void testDisplayP3HeicRepeating() throws Exception {
for (String id : getCameraIdsUnderTest()) {
try {
if (!mAllStaticInfo.get(id).isCapabilitySupported(CameraCharacteristics
.REQUEST_AVAILABLE_CAPABILITIES_COLOR_SPACE_PROFILES)) {
continue;
}
Set<ColorSpace.Named> availableColorSpaces =
mAllStaticInfo.get(id).getAvailableColorSpacesChecked(ImageFormat.HEIC);
if (!availableColorSpaces.contains(ColorSpace.Named.DISPLAY_P3)) {
continue;
}
openDevice(id);
Log.v(TAG, "Testing repeating Display P3 HEIC capture for Camera " + id);
BufferFormatTestParam params = new BufferFormatTestParam(
ImageFormat.HEIC, /*repeating*/true);
params.mColorSpace = ColorSpace.Named.DISPLAY_P3;
params.mUseColorSpace = true;
bufferFormatTestByCamera(params);
} finally {
closeDevice(id);
}
}
}
@Test
public void testDisplayP3Jpeg() throws Exception {
for (String id : getCameraIdsUnderTest()) {
try {
if (!mAllStaticInfo.get(id).isCapabilitySupported(CameraCharacteristics
.REQUEST_AVAILABLE_CAPABILITIES_COLOR_SPACE_PROFILES)) {
continue;
}
Set<ColorSpace.Named> availableColorSpaces =
mAllStaticInfo.get(id).getAvailableColorSpacesChecked(ImageFormat.JPEG);
if (!availableColorSpaces.contains(ColorSpace.Named.DISPLAY_P3)) {
continue;
}
openDevice(id);
Log.v(TAG, "Testing Display P3 JPEG capture for Camera " + id);
BufferFormatTestParam params = new BufferFormatTestParam(
ImageFormat.JPEG, /*repeating*/false);
params.mColorSpace = ColorSpace.Named.DISPLAY_P3;
params.mUseColorSpace = true;
bufferFormatTestByCamera(params);
} finally {
closeDevice(id);
}
}
}
@Test
public void testDisplayP3JpegRepeating() throws Exception {
for (String id : getCameraIdsUnderTest()) {
try {
if (!mAllStaticInfo.get(id).isCapabilitySupported(CameraCharacteristics
.REQUEST_AVAILABLE_CAPABILITIES_COLOR_SPACE_PROFILES)) {
continue;
}
Set<ColorSpace.Named> availableColorSpaces =
mAllStaticInfo.get(id).getAvailableColorSpacesChecked(ImageFormat.JPEG);
if (!availableColorSpaces.contains(ColorSpace.Named.DISPLAY_P3)) {
continue;
}
openDevice(id);
Log.v(TAG, "Testing repeating Display P3 JPEG capture for Camera " + id);
BufferFormatTestParam params = new BufferFormatTestParam(
ImageFormat.JPEG, /*repeating*/true);
params.mColorSpace = ColorSpace.Named.DISPLAY_P3;
params.mUseColorSpace = true;
bufferFormatTestByCamera(params);
} finally {
closeDevice(id);
}
}
}
@Test
public void testSRGBJpeg() throws Exception {
for (String id : getCameraIdsUnderTest()) {
try {
if (!mAllStaticInfo.get(id).isCapabilitySupported(CameraCharacteristics
.REQUEST_AVAILABLE_CAPABILITIES_COLOR_SPACE_PROFILES)) {
continue;
}
Set<ColorSpace.Named> availableColorSpaces =
mAllStaticInfo.get(id).getAvailableColorSpacesChecked(ImageFormat.JPEG);
if (!availableColorSpaces.contains(ColorSpace.Named.SRGB)) {
continue;
}
openDevice(id);
Log.v(TAG, "Testing sRGB JPEG capture for Camera " + id);
BufferFormatTestParam params = new BufferFormatTestParam(
ImageFormat.JPEG, /*repeating*/false);
params.mColorSpace = ColorSpace.Named.SRGB;
params.mUseColorSpace = true;
bufferFormatTestByCamera(params);
} finally {
closeDevice(id);
}
}
}
@Test
public void testSRGBJpegRepeating() throws Exception {
for (String id : getCameraIdsUnderTest()) {
try {
if (!mAllStaticInfo.get(id).isCapabilitySupported(CameraCharacteristics
.REQUEST_AVAILABLE_CAPABILITIES_COLOR_SPACE_PROFILES)) {
continue;
}
Set<ColorSpace.Named> availableColorSpaces =
mAllStaticInfo.get(id).getAvailableColorSpacesChecked(ImageFormat.JPEG);
if (!availableColorSpaces.contains(ColorSpace.Named.SRGB)) {
continue;
}
openDevice(id);
Log.v(TAG, "Testing repeating sRGB JPEG capture for Camera " + id);
BufferFormatTestParam params = new BufferFormatTestParam(
ImageFormat.JPEG, /*repeating*/true);
params.mColorSpace = ColorSpace.Named.SRGB;
params.mUseColorSpace = true;
bufferFormatTestByCamera(params);
} finally {
closeDevice(id);
}
}
}
@Test
public void testJpegR() throws Exception {
for (String id : getCameraIdsUnderTest()) {
try {
if (!mAllStaticInfo.get(id).isJpegRSupported()) {
Log.i(TAG, "Camera " + id + " does not support Jpeg/R, skipping");
continue;
}
Log.v(TAG, "Testing Jpeg/R capture for Camera " + id);
assertTrue(mAllStaticInfo.get(id).isCapabilitySupported(CameraCharacteristics
.REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT));
openDevice(id);
BufferFormatTestParam params = new BufferFormatTestParam(
ImageFormat.JPEG_R, /*repeating*/false);
bufferFormatTestByCamera(params);
} finally {
closeDevice(id);
}
}
}
@Test
public void testJpegRDisplayP3() throws Exception {
for (String id : getCameraIdsUnderTest()) {
try {
if (!mAllStaticInfo.get(id).isJpegRSupported()) {
Log.i(TAG, "Camera " + id + " does not support Jpeg/R, skipping");
continue;
}
if (!mAllStaticInfo.get(id).isCapabilitySupported(CameraCharacteristics
.REQUEST_AVAILABLE_CAPABILITIES_COLOR_SPACE_PROFILES)) {
continue;
}
Set<ColorSpace.Named> availableColorSpaces =
mAllStaticInfo.get(id).getAvailableColorSpacesChecked(
ImageFormat.JPEG_R);
if (!availableColorSpaces.contains(ColorSpace.Named.DISPLAY_P3)) {
continue;
}
openDevice(id);
Log.v(TAG, "Testing Display P3 Jpeg/R capture for Camera " + id);
BufferFormatTestParam params = new BufferFormatTestParam(
ImageFormat.JPEG_R, /*repeating*/false);
params.mColorSpace = ColorSpace.Named.DISPLAY_P3;
params.mUseColorSpace = true;
params.mDynamicRangeProfile = DynamicRangeProfiles.HLG10;
bufferFormatTestByCamera(params);
} finally {
closeDevice(id);
}
}
}
@Test
public void testHeic() throws Exception {
for (String id : getCameraIdsUnderTest()) {
try {
Log.v(TAG, "Testing heic capture for Camera " + id);
openDevice(id);
BufferFormatTestParam params = new BufferFormatTestParam(
ImageFormat.HEIC, /*repeating*/false);
bufferFormatTestByCamera(params);
} finally {
closeDevice(id);
}
}
}
@Test
public void testRepeatingJpeg() throws Exception {
for (String id : getCameraIdsUnderTest()) {
try {
Log.v(TAG, "Testing repeating jpeg capture for Camera " + id);
openDevice(id);
BufferFormatTestParam params = new BufferFormatTestParam(
ImageFormat.JPEG, /*repeating*/true);
bufferFormatTestByCamera(params);
} finally {
closeDevice(id);
}
}
}
@Test
public void testRepeatingRaw() throws Exception {
for (String id : getCameraIdsUnderTest()) {
try {
Log.v(TAG, "Testing repeating raw capture for camera " + id);
openDevice(id);
BufferFormatTestParam params = new BufferFormatTestParam(
ImageFormat.RAW_SENSOR, /*repeating*/true);
bufferFormatTestByCamera(params);
} finally {
closeDevice(id);
}
}
}
@Test
public void testRepeatingRawPrivate() throws Exception {
for (String id : getCameraIdsUnderTest()) {
try {
Log.v(TAG, "Testing repeating raw capture for camera " + id);
openDevice(id);
BufferFormatTestParam params = new BufferFormatTestParam(
ImageFormat.RAW_PRIVATE, /*repeating*/true);
bufferFormatTestByCamera(params);
} finally {
closeDevice(id);
}
}
}
@Test
public void testRepeatingHeic() throws Exception {
for (String id : getCameraIdsUnderTest()) {
try {
Log.v(TAG, "Testing repeating heic capture for Camera " + id);
openDevice(id);
BufferFormatTestParam params = new BufferFormatTestParam(
ImageFormat.HEIC, /*repeating*/true);
bufferFormatTestByCamera(params);
} finally {
closeDevice(id);
}
}
}
@Test
public void testFlexibleYuvWithTimestampBase() throws Exception {
for (String id : getCameraIdsUnderTest()) {
try {
Log.i(TAG, "Testing Camera " + id);
openDevice(id);
BufferFormatTestParam params = new BufferFormatTestParam(
ImageFormat.YUV_420_888, /*repeating*/true);
params.mValidateImageData = false;
int[] timeBases = {OutputConfiguration.TIMESTAMP_BASE_SENSOR,
OutputConfiguration.TIMESTAMP_BASE_MONOTONIC,
OutputConfiguration.TIMESTAMP_BASE_REALTIME,
OutputConfiguration.TIMESTAMP_BASE_CHOREOGRAPHER_SYNCED};
for (int timeBase : timeBases) {
params.mTimestampBase = timeBase;
bufferFormatTestByCamera(params);
}
} finally {
closeDevice(id);
}
}
}
@Test
public void testLongProcessingRepeatingRaw() throws Exception {
for (String id : getCameraIdsUnderTest()) {
try {
Log.v(TAG, "Testing long processing on repeating raw for camera " + id);
if (!mAllStaticInfo.get(id).isCapabilitySupported(
CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR)) {
continue;
}
openDevice(id);
bufferFormatLongProcessingTimeTestByCamera(ImageFormat.RAW_SENSOR);
} finally {
closeDevice(id);
}
}
}
@Test
public void testLongProcessingRepeatingFlexibleYuv() throws Exception {
for (String id : getCameraIdsUnderTest()) {
try {
Log.v(TAG, "Testing long processing on repeating YUV for camera " + id);
if (!mAllStaticInfo.get(id).isCapabilitySupported(
CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR)) {
continue;
}
openDevice(id);
bufferFormatLongProcessingTimeTestByCamera(ImageFormat.YUV_420_888);
} finally {
closeDevice(id);
}
}
}
/**
* Test invalid access of image after an image is closed, further access
* of the image will get an IllegalStateException. The basic assumption of
* this test is that the ImageReader always gives direct byte buffer, which is always true
* for camera case. For if the produced image byte buffer is not direct byte buffer, there
* is no guarantee to get an ISE for this invalid access case.
*/
@Test
public void testInvalidAccessTest() throws Exception {
// Test byte buffer access after an image is released, it should throw ISE.
for (String id : getCameraIdsUnderTest()) {
try {
Log.v(TAG, "Testing invalid image access for Camera " + id);
openDevice(id);
invalidAccessTestAfterClose();
} finally {
closeDevice(id);
closeDefaultImageReader();
}
}
}
/**
* Test two image stream (YUV420_888 and JPEG) capture by using ImageReader.
*
* <p>Both stream formats are mandatory for Camera2 API</p>
*/
@Test
public void testYuvAndJpeg() throws Exception {
for (String id : getCameraIdsUnderTest()) {
try {
Log.v(TAG, "YUV and JPEG testing for camera " + id);
if (!mAllStaticInfo.get(id).isColorOutputSupported()) {
Log.i(TAG, "Camera " + id +
" does not support color outputs, skipping");
continue;
}
openDevice(id);
bufferFormatWithYuvTestByCamera(ImageFormat.JPEG);
} finally {
closeDevice(id);
}
}
}
/**
* Test two image stream (YUV420_888 and JPEG) capture by using ImageReader with the ImageReader
* factory method that has usage flag argument.
*
* <p>Both stream formats are mandatory for Camera2 API</p>
*/
@Test
public void testYuvAndJpegWithUsageFlag() throws Exception {
for (String id : getCameraIdsUnderTest()) {
try {
Log.v(TAG, "YUV and JPEG testing for camera " + id);
if (!mAllStaticInfo.get(id).isColorOutputSupported()) {
Log.i(TAG, "Camera " + id +
" does not support color outputs, skipping");
continue;
}
openDevice(id);
bufferFormatWithYuvTestByCamera(ImageFormat.JPEG, true);
} finally {
closeDevice(id);
}
}
}
@Test
public void testImageReaderBuilderSetHardwareBufferFormatAndDataSpace() throws Exception {
long usage = HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE | HardwareBuffer.USAGE_GPU_COLOR_OUTPUT;
try (
ImageReader reader = new ImageReader
.Builder(20, 45)
.setMaxImages(2)
.setDefaultHardwareBufferFormat(HardwareBuffer.RGBA_8888)
.setDefaultDataSpace(DataSpace.DATASPACE_BT709)
.setUsage(usage)
.build();
ImageWriter writer = ImageWriter.newInstance(reader.getSurface(), 1);
Image outputImage = writer.dequeueInputImage()
) {
assertEquals(2, reader.getMaxImages());
assertEquals(usage, reader.getUsage());
assertEquals(HardwareBuffer.RGBA_8888, reader.getHardwareBufferFormat());
assertEquals(20, outputImage.getWidth());
assertEquals(45, outputImage.getHeight());
assertEquals(HardwareBuffer.RGBA_8888, outputImage.getFormat());
}
}
@Test
public void testImageReaderBuilderWithBLOBAndHEIF() throws Exception {
long usage = HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE | HardwareBuffer.USAGE_GPU_COLOR_OUTPUT;
try (
ImageReader reader = new ImageReader
.Builder(20, 45)
.setMaxImages(2)
.setDefaultHardwareBufferFormat(HardwareBuffer.BLOB)
.setDefaultDataSpace(DataSpace.DATASPACE_HEIF)
.setUsage(usage)
.build();
ImageWriter writer = new ImageWriter.Builder(reader.getSurface()).build();
) {
assertEquals(2, reader.getMaxImages());
assertEquals(usage, reader.getUsage());
assertEquals(HardwareBuffer.BLOB, reader.getHardwareBufferFormat());
assertEquals(DataSpace.DATASPACE_HEIF, reader.getDataSpace());
// writer should have same dataspace/hardwarebuffer format as reader.
assertEquals(HardwareBuffer.BLOB, writer.getHardwareBufferFormat());
assertEquals(DataSpace.DATASPACE_HEIF, writer.getDataSpace());
// HEIC is the combination of HardwareBuffer.BLOB and Dataspace.DATASPACE_HEIF
assertEquals(ImageFormat.HEIC, writer.getFormat());
}
}
@Test
public void testImageReaderBuilderWithBLOBAndJpegR() throws Exception {
long usage = HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE | HardwareBuffer.USAGE_GPU_COLOR_OUTPUT;
try (
ImageReader reader = new ImageReader
.Builder(20, 45)
.setMaxImages(2)
.setDefaultHardwareBufferFormat(HardwareBuffer.BLOB)
.setDefaultDataSpace(DataSpace.DATASPACE_JPEG_R)
.setUsage(usage)
.build();
ImageWriter writer = new ImageWriter.Builder(reader.getSurface()).build();
) {
assertEquals(2, reader.getMaxImages());
assertEquals(usage, reader.getUsage());
assertEquals(HardwareBuffer.BLOB, reader.getHardwareBufferFormat());
assertEquals(DataSpace.DATASPACE_JPEG_R, reader.getDataSpace());
// writer should have same dataspace/hardwarebuffer format as reader.
assertEquals(HardwareBuffer.BLOB, writer.getHardwareBufferFormat());
assertEquals(DataSpace.DATASPACE_JPEG_R, writer.getDataSpace());
// Jpeg/R is the combination of HardwareBuffer.BLOB and Dataspace.DATASPACE_JPEG_R
assertEquals(ImageFormat.JPEG_R, writer.getFormat());
}
}
@Test
public void testImageReaderBuilderWithBLOBAndJFIF() throws Exception {
long usage = HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE | HardwareBuffer.USAGE_GPU_COLOR_OUTPUT;
try (
ImageReader reader = new ImageReader
.Builder(20, 45)
.setMaxImages(2)
.setDefaultHardwareBufferFormat(HardwareBuffer.BLOB)
.setDefaultDataSpace(DataSpace.DATASPACE_JFIF)
.setUsage(usage)
.build();
ImageWriter writer = new ImageWriter.Builder(reader.getSurface()).build();
) {
assertEquals(2, reader.getMaxImages());
assertEquals(usage, reader.getUsage());
assertEquals(HardwareBuffer.BLOB, reader.getHardwareBufferFormat());
assertEquals(DataSpace.DATASPACE_JFIF, reader.getDataSpace());
// writer should have same dataspace/hardwarebuffer format as reader.
assertEquals(HardwareBuffer.BLOB, writer.getHardwareBufferFormat());
assertEquals(DataSpace.DATASPACE_JFIF, writer.getDataSpace());
// JPEG is the combination of HardwareBuffer.BLOB and Dataspace.DATASPACE_JFIF
assertEquals(ImageFormat.JPEG, writer.getFormat());
}
}
@Test
public void testImageReaderBuilderImageFormatOverride() throws Exception {
try (
ImageReader reader = new ImageReader
.Builder(20, 45)
.setImageFormat(ImageFormat.HEIC)
.setDefaultHardwareBufferFormat(HardwareBuffer.RGB_888)
.setDefaultDataSpace(DataSpace.DATASPACE_BT709)
.build();
ImageWriter writer = ImageWriter.newInstance(reader.getSurface(), 1);
Image outputImage = writer.dequeueInputImage()
) {
assertEquals(1, reader.getMaxImages());
assertEquals(HardwareBuffer.USAGE_CPU_READ_OFTEN, reader.getUsage());
assertEquals(HardwareBuffer.RGB_888, reader.getHardwareBufferFormat());
assertEquals(DataSpace.DATASPACE_BT709, reader.getDataSpace());
assertEquals(20, outputImage.getWidth());
assertEquals(45, outputImage.getHeight());
assertEquals(HardwareBuffer.RGB_888, outputImage.getFormat());
}
}
@Test
public void testImageReaderBuilderSetImageFormat() throws Exception {
try (
ImageReader reader = new ImageReader
.Builder(20, 45)
.setMaxImages(2)
.setImageFormat(ImageFormat.YUV_420_888)
.build();
ImageWriter writer = ImageWriter.newInstance(reader.getSurface(), 1);
Image outputImage = writer.dequeueInputImage()
) {
assertEquals(2, reader.getMaxImages());
assertEquals(ImageFormat.YUV_420_888, reader.getImageFormat());
assertEquals(HardwareBuffer.USAGE_CPU_READ_OFTEN, reader.getUsage());
// ImageFormat.YUV_420_888 hal dataspace is DATASPACE_JFIF
assertEquals(DataSpace.DATASPACE_JFIF, reader.getDataSpace());
// writer should retrieve all info from reader's surface
assertEquals(DataSpace.DATASPACE_JFIF, writer.getDataSpace());
assertEquals(HardwareBuffer.YCBCR_420_888, writer.getHardwareBufferFormat());
assertEquals(20, outputImage.getWidth());
assertEquals(45, outputImage.getHeight());
assertEquals(ImageFormat.YUV_420_888, outputImage.getFormat());
}
}
/**
* Test two image stream (YUV420_888 and RAW_SENSOR) capture by using ImageReader.
*
*/
@Test
public void testImageReaderYuvAndRaw() throws Exception {
for (String id : getCameraIdsUnderTest()) {
try {
Log.v(TAG, "YUV and RAW testing for camera " + id);
if (!mAllStaticInfo.get(id).isColorOutputSupported()) {
Log.i(TAG, "Camera " + id +
" does not support color outputs, skipping");
continue;
}
openDevice(id);
bufferFormatWithYuvTestByCamera(ImageFormat.RAW_SENSOR);
} finally {
closeDevice(id);
}
}
}
/**
* If the camera device advertises the SECURE_IAMGE_DATA capability, test
* ImageFormat.PRIVATE + PROTECTED usage capture by using ImageReader with the
* ImageReader factory method that has usage flag argument, and uses a custom usage flag.
*/
@Test
public void testImageReaderPrivateWithProtectedUsageFlag() throws Exception {
for (String id : getCameraIdsUnderTest()) {
try {
Log.v(TAG, "Private format and protected usage testing for camera " + id);
List<String> testCameraIds = new ArrayList<>();
if (mAllStaticInfo.get(id).isCapabilitySupported(
CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_SECURE_IMAGE_DATA)) {
// Test the camera id without using physical camera
testCameraIds.add(null);
}
if (mAllStaticInfo.get(id).isLogicalMultiCamera()) {
Set<String> physicalIdsSet =
mAllStaticInfo.get(id).getCharacteristics().getPhysicalCameraIds();
for (String physicalId : physicalIdsSet) {
if (mAllStaticInfo.get(physicalId).isCapabilitySupported(
CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_SECURE_IMAGE_DATA)) {
testCameraIds.add(physicalId);
}
}
}
if (testCameraIds.isEmpty()) {
Log.i(TAG, "Camera " + id +
" does not support secure image data capability, skipping");
continue;
}
openDevice(id);
BufferFormatTestParam params = new BufferFormatTestParam(
ImageFormat.PRIVATE, /*repeating*/true);
params.mSetUsageFlag = true;
params.mUsageFlag = HardwareBuffer.USAGE_PROTECTED_CONTENT;
params.mRepeating = true;
params.mCheckSession = true;
params.mValidateImageData = false;
for (String testCameraId : testCameraIds) {
params.mPhysicalId = testCameraId;
bufferFormatTestByCamera(params);
}
} finally {
closeDevice(id);
}
}
}
/**
* Test two image stream (YUV420_888 and RAW_SENSOR) capture by using ImageReader with the
* ImageReader factory method that has usage flag argument.
*
*/
@Test
public void testImageReaderYuvAndRawWithUsageFlag() throws Exception {
for (String id : getCameraIdsUnderTest()) {
try {
Log.v(TAG, "YUV and RAW testing for camera " + id);
if (!mAllStaticInfo.get(id).isColorOutputSupported()) {
Log.i(TAG, "Camera " + id +
" does not support color outputs, skipping");
continue;
}
openDevice(id);
bufferFormatWithYuvTestByCamera(ImageFormat.RAW_SENSOR, true);
} finally {
closeDevice(id);
}
}
}
/**
* Check that the center patches for YUV and JPEG outputs for the same frame match for each YUV
* resolution and format supported.
*/
@Test
public void testAllOutputYUVResolutions() throws Exception {
Integer[] sessionStates = {BlockingSessionCallback.SESSION_READY,
BlockingSessionCallback.SESSION_CONFIGURE_FAILED};
for (String id : getCameraIdsUnderTest()) {
try {
Log.v(TAG, "Testing all YUV image resolutions for camera " + id);
if (!mAllStaticInfo.get(id).isColorOutputSupported()) {
Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
continue;
}
openDevice(id);
// Skip warmup on FULL mode devices.
int warmupCaptureNumber = (mStaticInfo.isHardwareLevelLegacy()) ?
MAX_NUM_IMAGES - 1 : 0;
// NV21 isn't supported by ImageReader.
final int[] YUVFormats = new int[] {ImageFormat.YUV_420_888, ImageFormat.YV12};
CameraCharacteristics.Key<StreamConfigurationMap> key =
CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP;
StreamConfigurationMap config = mStaticInfo.getValueFromKeyNonNull(key);
int[] supportedFormats = config.getOutputFormats();
List<Integer> supportedYUVFormats = new ArrayList<>();
for (int format : YUVFormats) {
if (CameraTestUtils.contains(supportedFormats, format)) {
supportedYUVFormats.add(format);
}
}
Size[] jpegSizes = mStaticInfo.getAvailableSizesForFormatChecked(ImageFormat.JPEG,
StaticMetadata.StreamDirection.Output);
assertFalse("JPEG output not supported for camera " + id +
", at least one JPEG output is required.", jpegSizes.length == 0);
Size maxJpegSize = CameraTestUtils.getMaxSize(jpegSizes);
Size maxPreviewSize = mOrderedPreviewSizes.get(0);
Size QCIF = new Size(176, 144);
Size FULL_HD = new Size(1920, 1080);
for (int format : supportedYUVFormats) {
Size[] targetCaptureSizes =
mStaticInfo.getAvailableSizesForFormatChecked(format,
StaticMetadata.StreamDirection.Output);
for (Size captureSz : targetCaptureSizes) {
if (VERBOSE) {
Log.v(TAG, "Testing yuv size " + captureSz + " and jpeg size "
+ maxJpegSize + " for camera " + mCamera.getId());
}
ImageReader jpegReader = null;
ImageReader yuvReader = null;
try {
// Create YUV image reader
SimpleImageReaderListener yuvListener = new SimpleImageReaderListener();
yuvReader = createImageReader(captureSz, format, MAX_NUM_IMAGES,
yuvListener);
Surface yuvSurface = yuvReader.getSurface();
// Create JPEG image reader
SimpleImageReaderListener jpegListener =
new SimpleImageReaderListener();
jpegReader = createImageReader(maxJpegSize,
ImageFormat.JPEG, MAX_NUM_IMAGES, jpegListener);
Surface jpegSurface = jpegReader.getSurface();
// Setup session
List<Surface> outputSurfaces = new ArrayList<Surface>();
outputSurfaces.add(yuvSurface);
outputSurfaces.add(jpegSurface);
createSession(outputSurfaces);
int state = mCameraSessionListener.getStateWaiter().waitForAnyOfStates(
Arrays.asList(sessionStates),
CameraTestUtils.SESSION_CONFIGURE_TIMEOUT_MS);
if (state == BlockingSessionCallback.SESSION_CONFIGURE_FAILED) {
if (captureSz.getWidth() > maxPreviewSize.getWidth() ||
captureSz.getHeight() > maxPreviewSize.getHeight()) {
Log.v(TAG, "Skip testing {yuv:" + captureSz
+ " ,jpeg:" + maxJpegSize + "} for camera "
+ mCamera.getId() +
" because full size jpeg + yuv larger than "
+ "max preview size (" + maxPreviewSize
+ ") is not supported");
continue;
} else if (captureSz.equals(QCIF) &&
((maxJpegSize.getWidth() > FULL_HD.getWidth()) ||
(maxJpegSize.getHeight() > FULL_HD.getHeight()))) {
Log.v(TAG, "Skip testing {yuv:" + captureSz
+ " ,jpeg:" + maxJpegSize + "} for camera "
+ mCamera.getId() +
" because QCIF + >Full_HD size is not supported");
continue;
} else {
fail("Camera " + mCamera.getId() +
":session configuration failed for {jpeg: " +
maxJpegSize + ", yuv: " + captureSz + "}");
}
}
// Warm up camera preview (mainly to give legacy devices time to do 3A).
CaptureRequest.Builder warmupRequest =
mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
warmupRequest.addTarget(yuvSurface);
assertNotNull("Fail to get CaptureRequest.Builder", warmupRequest);
SimpleCaptureCallback resultListener = new SimpleCaptureCallback();
for (int i = 0; i < warmupCaptureNumber; i++) {
startCapture(warmupRequest.build(), /*repeating*/false,
resultListener, mHandler);
}
for (int i = 0; i < warmupCaptureNumber; i++) {
resultListener.getCaptureResult(CAPTURE_WAIT_TIMEOUT_MS);
Image image = yuvListener.getImage(CAPTURE_WAIT_TIMEOUT_MS);
image.close();
}
// Capture image.
CaptureRequest.Builder mainRequest =
mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
for (Surface s : outputSurfaces) {
mainRequest.addTarget(s);
}
startCapture(mainRequest.build(), /*repeating*/false, resultListener,
mHandler);
// Verify capture result and images
resultListener.getCaptureResult(CAPTURE_WAIT_TIMEOUT_MS);
Image yuvImage = yuvListener.getImage(CAPTURE_WAIT_TIMEOUT_MS);
Image jpegImage = jpegListener.getImage(CAPTURE_WAIT_TIMEOUT_MS);
//Validate captured images.
CameraTestUtils.validateImage(yuvImage, captureSz.getWidth(),
captureSz.getHeight(), format, /*filePath*/null);
CameraTestUtils.validateImage(jpegImage, maxJpegSize.getWidth(),
maxJpegSize.getHeight(), ImageFormat.JPEG, /*filePath*/null);
// Compare the image centers.
RectF jpegDimens = new RectF(0, 0, jpegImage.getWidth(),
jpegImage.getHeight());
RectF yuvDimens = new RectF(0, 0, yuvImage.getWidth(),
yuvImage.getHeight());
// Find scale difference between YUV and JPEG output
Matrix m = new Matrix();
m.setRectToRect(yuvDimens, jpegDimens, Matrix.ScaleToFit.START);
RectF scaledYuv = new RectF();
m.mapRect(scaledYuv, yuvDimens);
float scale = scaledYuv.width() / yuvDimens.width();
final int PATCH_DIMEN = 40; // pixels in YUV
// Find matching square patch of pixels in YUV and JPEG output
RectF tempPatch = new RectF(0, 0, PATCH_DIMEN, PATCH_DIMEN);
tempPatch.offset(yuvDimens.centerX() - tempPatch.centerX(),
yuvDimens.centerY() - tempPatch.centerY());
Rect yuvPatch = new Rect();
tempPatch.roundOut(yuvPatch);
tempPatch.set(0, 0, PATCH_DIMEN * scale, PATCH_DIMEN * scale);
tempPatch.offset(jpegDimens.centerX() - tempPatch.centerX(),
jpegDimens.centerY() - tempPatch.centerY());
Rect jpegPatch = new Rect();
tempPatch.roundOut(jpegPatch);
// Decode center patches
int[] yuvColors = convertPixelYuvToRgba(yuvPatch.width(),
yuvPatch.height(), yuvPatch.left, yuvPatch.top, yuvImage);
Bitmap yuvBmap = Bitmap.createBitmap(yuvColors, yuvPatch.width(),
yuvPatch.height(), Bitmap.Config.ARGB_8888);
byte[] compressedJpegData = CameraTestUtils.getDataFromImage(jpegImage);
BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(
compressedJpegData, /*offset*/0, compressedJpegData.length,
/*isShareable*/true);
BitmapFactory.Options opt = new BitmapFactory.Options();
opt.inPreferredConfig = Bitmap.Config.ARGB_8888;
Bitmap fullSizeJpegBmap = decoder.decodeRegion(jpegPatch, opt);
Bitmap jpegBmap = Bitmap.createScaledBitmap(fullSizeJpegBmap,
yuvPatch.width(), yuvPatch.height(), /*filter*/true);
// Compare two patches using average of per-pixel differences
double difference = BitmapUtils.calcDifferenceMetric(yuvBmap, jpegBmap);
double tolerance = IMAGE_DIFFERENCE_TOLERANCE;
if (mStaticInfo.isHardwareLevelLegacy()) {
tolerance = IMAGE_DIFFERENCE_TOLERANCE_LEGACY;
}
Log.i(TAG, "Difference for resolution " + captureSz + " is: " +
difference);
if (difference > tolerance) {
// Dump files if running in verbose mode
if (DEBUG) {
String jpegFileName = mDebugFileNameBase + "/" + captureSz +
"_jpeg.jpg";
dumpFile(jpegFileName, jpegBmap);
String fullSizeJpegFileName = mDebugFileNameBase + "/" +
captureSz + "_full_jpeg.jpg";
dumpFile(fullSizeJpegFileName, compressedJpegData);
String yuvFileName = mDebugFileNameBase + "/" + captureSz +
"_yuv.jpg";
dumpFile(yuvFileName, yuvBmap);
String fullSizeYuvFileName = mDebugFileNameBase + "/" +
captureSz + "_full_yuv.jpg";
int[] fullYUVColors = convertPixelYuvToRgba(yuvImage.getWidth(),
yuvImage.getHeight(), 0, 0, yuvImage);
Bitmap fullYUVBmap = Bitmap.createBitmap(fullYUVColors,
yuvImage.getWidth(), yuvImage.getHeight(),
Bitmap.Config.ARGB_8888);
dumpFile(fullSizeYuvFileName, fullYUVBmap);
}
fail("Camera " + mCamera.getId() + ": YUV image at capture size "
+ captureSz + " and JPEG image at capture size "
+ maxJpegSize + " for the same frame are not similar,"
+ " center patches have difference metric of "
+ difference + ", tolerance is " + tolerance);
}
// Stop capture, delete the streams.
stopCapture(/*fast*/false);
yuvImage.close();
jpegImage.close();
yuvListener.drain();
jpegListener.drain();
} finally {
closeImageReader(jpegReader);
jpegReader = null;
closeImageReader(yuvReader);
yuvReader = null;
}
}
}
} finally {
closeDevice(id);
}
}
}
/**
* Test that images captured after discarding free buffers are valid.
*/
@Test
public void testDiscardFreeBuffers() throws Exception {
for (String id : getCameraIdsUnderTest()) {
try {
Log.v(TAG, "Testing discardFreeBuffers for Camera " + id);
openDevice(id);
discardFreeBuffersTestByCamera();
} finally {
closeDevice(id);
}
}
}
/** Tests that usage bits are preserved */
@Test
public void testUsageRespected() throws Exception {
final long REQUESTED_USAGE_BITS =
HardwareBuffer.USAGE_GPU_COLOR_OUTPUT | HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE;
ImageReader reader = ImageReader.newInstance(1, 1, PixelFormat.RGBA_8888, 1,
REQUESTED_USAGE_BITS);
Surface surface = reader.getSurface();
Canvas canvas = surface.lockHardwareCanvas();
canvas.drawColor(Color.RED);
surface.unlockCanvasAndPost(canvas);
Image image = null;
for (int i = 0; i < 100; i++) {
image = reader.acquireNextImage();
if (image != null) break;
Thread.sleep(10);
}
assertNotNull(image);
HardwareBuffer buffer = image.getHardwareBuffer();
assertNotNull(buffer);
// Mask off the upper vendor bits
int myBits = (int) (buffer.getUsage() & 0xFFFFFFF);
assertWithMessage("Usage bits %s did not contain requested usage bits %s", myBits,
REQUESTED_USAGE_BITS).that(myBits & REQUESTED_USAGE_BITS)
.isEqualTo(REQUESTED_USAGE_BITS);
}
private void testLandscapeToPortraitOverride(boolean overrideToPortrait) throws Exception {
if (!SystemProperties.getBoolean(CameraManager.LANDSCAPE_TO_PORTRAIT_PROP, false)) {
Log.i(TAG, "Landscape to portrait override not supported, skipping test");
return;
}
for (String id : getCameraIdsUnderTest()) {
CameraCharacteristics c = mCameraManager.getCameraCharacteristics(
id, /*overrideToPortrait*/false);
int[] modes = c.get(CameraCharacteristics.SCALER_AVAILABLE_ROTATE_AND_CROP_MODES);
boolean supportsRotateAndCrop = false;
for (int mode : modes) {
if (mode == CameraMetadata.SCALER_ROTATE_AND_CROP_90
|| mode == CameraMetadata.SCALER_ROTATE_AND_CROP_270) {
supportsRotateAndCrop = true;
break;
}
}
if (!supportsRotateAndCrop) {
Log.i(TAG, "Skipping non-rotate-and-crop cameraId " + id);
continue;
}
int sensorOrientation = c.get(CameraCharacteristics.SENSOR_ORIENTATION);
if (sensorOrientation != 0 && sensorOrientation != 180) {
Log.i(TAG, "Skipping portrait orientation sensor cameraId " + id);
continue;
}
Log.i(TAG, "Testing overrideToPortrait " + overrideToPortrait
+ " for Camera " + id);
if (overrideToPortrait) {
c = mCameraManager.getCameraCharacteristics(id, overrideToPortrait);
sensorOrientation = c.get(CameraCharacteristics.SENSOR_ORIENTATION);
assertTrue("SENSOR_ORIENTATION should imply portrait sensor.",
sensorOrientation == 90 || sensorOrientation == 270);
}
BufferFormatTestParam params = new BufferFormatTestParam(
ImageFormat.JPEG, /*repeating*/false);
params.mValidateImageData = true;
try {
openDevice(id, overrideToPortrait);
bufferFormatTestByCamera(params);
} finally {
closeDevice(id);
}
}
}
@Test
public void testLandscapeToPortraitOverrideEnabled() throws Exception {
testLandscapeToPortraitOverride(true);
}
@Test
public void testLandscapeToPortraitOverrideDisabled() throws Exception {
testLandscapeToPortraitOverride(false);
}
/**
* Convert a rectangular patch in a YUV image to an ARGB color array.
*
* @param w width of the patch.
* @param h height of the patch.
* @param wOffset offset of the left side of the patch.
* @param hOffset offset of the top of the patch.
* @param yuvImage a YUV image to select a patch from.
* @return the image patch converted to RGB as an ARGB color array.
*/
private static int[] convertPixelYuvToRgba(int w, int h, int wOffset, int hOffset,
Image yuvImage) {
final int CHANNELS = 3; // yuv
final float COLOR_RANGE = 255f;
assertTrue("Invalid argument to convertPixelYuvToRgba",
w > 0 && h > 0 && wOffset >= 0 && hOffset >= 0);
assertNotNull(yuvImage);
int imageFormat = yuvImage.getFormat();
assertTrue("YUV image must have YUV-type format",
imageFormat == ImageFormat.YUV_420_888 || imageFormat == ImageFormat.YV12 ||
imageFormat == ImageFormat.NV21);
int height = yuvImage.getHeight();
int width = yuvImage.getWidth();
Rect imageBounds = new Rect(/*left*/0, /*top*/0, /*right*/width, /*bottom*/height);
Rect crop = new Rect(/*left*/wOffset, /*top*/hOffset, /*right*/wOffset + w,
/*bottom*/hOffset + h);
assertTrue("Output rectangle" + crop + " must lie within image bounds " + imageBounds,
imageBounds.contains(crop));
Image.Plane[] planes = yuvImage.getPlanes();
Image.Plane yPlane = planes[0];
Image.Plane cbPlane = planes[1];
Image.Plane crPlane = planes[2];
ByteBuffer yBuf = yPlane.getBuffer();
int yPixStride = yPlane.getPixelStride();
int yRowStride = yPlane.getRowStride();
ByteBuffer cbBuf = cbPlane.getBuffer();
int cbPixStride = cbPlane.getPixelStride();
int cbRowStride = cbPlane.getRowStride();
ByteBuffer crBuf = crPlane.getBuffer();
int crPixStride = crPlane.getPixelStride();
int crRowStride = crPlane.getRowStride();
int[] output = new int[w * h];
// TODO: Optimize this with renderscript intrinsics
byte[] yRow = new byte[yPixStride * (w - 1) + 1];
byte[] cbRow = new byte[cbPixStride * (w / 2 - 1) + 1];
byte[] crRow = new byte[crPixStride * (w / 2 - 1) + 1];
yBuf.mark();
cbBuf.mark();
crBuf.mark();
int initialYPos = yBuf.position();
int initialCbPos = cbBuf.position();
int initialCrPos = crBuf.position();
int outputPos = 0;
for (int i = hOffset; i < hOffset + h; i++) {
yBuf.position(initialYPos + i * yRowStride + wOffset * yPixStride);
yBuf.get(yRow);
if ((i & 1) == (hOffset & 1)) {
cbBuf.position(initialCbPos + (i / 2) * cbRowStride + wOffset * cbPixStride / 2);
cbBuf.get(cbRow);
crBuf.position(initialCrPos + (i / 2) * crRowStride + wOffset * crPixStride / 2);
crBuf.get(crRow);
}
for (int j = 0, yPix = 0, crPix = 0, cbPix = 0; j < w; j++, yPix += yPixStride) {
float y = yRow[yPix] & 0xFF;
float cb = cbRow[cbPix] & 0xFF;
float cr = crRow[crPix] & 0xFF;
// convert YUV -> RGB (from JFIF's "Conversion to and from RGB" section)
int r = (int) Math.max(0.0f, Math.min(COLOR_RANGE, y + 1.402f * (cr - 128)));
int g = (int) Math.max(0.0f,
Math.min(COLOR_RANGE, y - 0.34414f * (cb - 128) - 0.71414f * (cr - 128)));
int b = (int) Math.max(0.0f, Math.min(COLOR_RANGE, y + 1.772f * (cb - 128)));
// Convert to ARGB pixel color (use opaque alpha)
output[outputPos++] = Color.rgb(r, g, b);
if ((j & 1) == 1) {
crPix += crPixStride;
cbPix += cbPixStride;
}
}
}
yBuf.rewind();
cbBuf.rewind();
crBuf.rewind();
return output;
}
/**
* Test capture a given format stream with yuv stream simultaneously.
*
* <p>Use fixed yuv size, varies targeted format capture size. Single capture is tested.</p>
*
* @param format The capture format to be tested along with yuv format.
*/
private void bufferFormatWithYuvTestByCamera(int format) throws Exception {
bufferFormatWithYuvTestByCamera(format, false);
}
/**
* Test capture a given format stream with yuv stream simultaneously.
*
* <p>Use fixed yuv size, varies targeted format capture size. Single capture is tested.</p>
*
* @param format The capture format to be tested along with yuv format.
* @param setUsageFlag The ImageReader factory method to be used (with or without specifying
* usage flag)
*/
private void bufferFormatWithYuvTestByCamera(int format, boolean setUsageFlag)
throws Exception {
if (format != ImageFormat.JPEG && format != ImageFormat.RAW_SENSOR
&& format != ImageFormat.YUV_420_888) {
throw new IllegalArgumentException("Unsupported format: " + format);
}
final int NUM_SINGLE_CAPTURE_TESTED = MAX_NUM_IMAGES - 1;
Size maxYuvSz = mOrderedPreviewSizes.get(0);
Size[] targetCaptureSizes = mStaticInfo.getAvailableSizesForFormatChecked(format,
StaticMetadata.StreamDirection.Output);
for (Size captureSz : targetCaptureSizes) {
if (VERBOSE) {
Log.v(TAG, "Testing yuv size " + maxYuvSz.toString() + " and capture size "
+ captureSz.toString() + " for camera " + mCamera.getId());
}
ImageReader captureReader = null;
ImageReader yuvReader = null;
try {
// Create YUV image reader
SimpleImageReaderListener yuvListener = new SimpleImageReaderListener();
if (setUsageFlag) {
yuvReader = createImageReader(maxYuvSz, ImageFormat.YUV_420_888, MAX_NUM_IMAGES,
HardwareBuffer.USAGE_CPU_READ_OFTEN, yuvListener);
} else {
yuvReader = createImageReader(maxYuvSz, ImageFormat.YUV_420_888, MAX_NUM_IMAGES,
yuvListener);
}
Surface yuvSurface = yuvReader.getSurface();
// Create capture image reader
SimpleImageReaderListener captureListener = new SimpleImageReaderListener();
if (setUsageFlag) {
captureReader = createImageReader(captureSz, format, MAX_NUM_IMAGES,
HardwareBuffer.USAGE_CPU_READ_OFTEN, captureListener);
} else {
captureReader = createImageReader(captureSz, format, MAX_NUM_IMAGES,
captureListener);
}
Surface captureSurface = captureReader.getSurface();
// Capture images.
List<Surface> outputSurfaces = new ArrayList<Surface>();
outputSurfaces.add(yuvSurface);
outputSurfaces.add(captureSurface);
CaptureRequest.Builder request = prepareCaptureRequestForSurfaces(outputSurfaces,
CameraDevice.TEMPLATE_PREVIEW);
SimpleCaptureCallback resultListener = new SimpleCaptureCallback();
for (int i = 0; i < NUM_SINGLE_CAPTURE_TESTED; i++) {
startCapture(request.build(), /*repeating*/false, resultListener, mHandler);
}
// Verify capture result and images
for (int i = 0; i < NUM_SINGLE_CAPTURE_TESTED; i++) {
resultListener.getCaptureResult(CAPTURE_WAIT_TIMEOUT_MS);
if (VERBOSE) {
Log.v(TAG, " Got the capture result back for " + i + "th capture");
}
Image yuvImage = yuvListener.getImage(CAPTURE_WAIT_TIMEOUT_MS);
if (VERBOSE) {
Log.v(TAG, " Got the yuv image back for " + i + "th capture");
}
Image captureImage = captureListener.getImage(CAPTURE_WAIT_TIMEOUT_MS);
if (VERBOSE) {
Log.v(TAG, " Got the capture image back for " + i + "th capture");
}
//Validate captured images.
CameraTestUtils.validateImage(yuvImage, maxYuvSz.getWidth(),
maxYuvSz.getHeight(), ImageFormat.YUV_420_888, /*filePath*/null);
CameraTestUtils.validateImage(captureImage, captureSz.getWidth(),
captureSz.getHeight(), format, /*filePath*/null);
yuvImage.close();
captureImage.close();
}
// Stop capture, delete the streams.
stopCapture(/*fast*/false);
} finally {
closeImageReader(captureReader);
captureReader = null;
closeImageReader(yuvReader);
yuvReader = null;
}
}
}
private void invalidAccessTestAfterClose() throws Exception {
final int FORMAT = mStaticInfo.isColorOutputSupported() ?
ImageFormat.YUV_420_888 : ImageFormat.DEPTH16;
Size[] availableSizes = mStaticInfo.getAvailableSizesForFormatChecked(FORMAT,
StaticMetadata.StreamDirection.Output);
Image img = null;
// Create ImageReader.
mListener = new SimpleImageListener();
createDefaultImageReader(availableSizes[0], FORMAT, MAX_NUM_IMAGES, mListener);
// Start capture.
CaptureRequest request = prepareCaptureRequest();
SimpleCaptureCallback listener = new SimpleCaptureCallback();
startCapture(request, /* repeating */false, listener, mHandler);
mListener.waitForAnyImageAvailable(CAPTURE_WAIT_TIMEOUT_MS);
img = mReader.acquireNextImage();
Plane firstPlane = img.getPlanes()[0];
ByteBuffer buffer = firstPlane.getBuffer();
img.close();
imageInvalidAccessTestAfterClose(img, firstPlane, buffer);
}
/**
* Test that images captured after discarding free buffers are valid.
*/
private void discardFreeBuffersTestByCamera() throws Exception {
final int FORMAT = mStaticInfo.isColorOutputSupported() ?
ImageFormat.YUV_420_888 : ImageFormat.DEPTH16;
final Size SIZE = mStaticInfo.getAvailableSizesForFormatChecked(FORMAT,
StaticMetadata.StreamDirection.Output)[0];
// Create ImageReader.
mListener = new SimpleImageListener();
createDefaultImageReader(SIZE, FORMAT, MAX_NUM_IMAGES, mListener);
// Start capture.
final boolean REPEATING = true;
final boolean SINGLE = false;
CaptureRequest request = prepareCaptureRequest();
SimpleCaptureCallback listener = new SimpleCaptureCallback();
startCapture(request, REPEATING, listener, mHandler);
// Validate images and capture results.
validateImage(SIZE, FORMAT, NUM_FRAME_VERIFIED, REPEATING, /*colorSpace*/ null);
validateCaptureResult(FORMAT, SIZE, listener, NUM_FRAME_VERIFIED);
// Discard free buffers.
mReader.discardFreeBuffers();
// Validate images and capture resulst again.
validateImage(SIZE, FORMAT, NUM_FRAME_VERIFIED, REPEATING, /*colorSpace*/ null);
validateCaptureResult(FORMAT, SIZE, listener, NUM_FRAME_VERIFIED);
// Stop repeating request in preparation for discardFreeBuffers
mCameraSession.stopRepeating();
mCameraSessionListener.getStateWaiter().waitForState(
BlockingSessionCallback.SESSION_READY, SESSION_READY_TIMEOUT_MS);
// Drain the reader queue and discard free buffers from the reader.
Image img = mReader.acquireLatestImage();
if (img != null) {
img.close();
}
mReader.discardFreeBuffers();
// Do a single capture for camera device to reallocate buffers
mListener.reset();
startCapture(request, SINGLE, listener, mHandler);
validateImage(SIZE, FORMAT, /*captureCount*/ 1, SINGLE, /*colorSpace*/ null);
}
private class BufferFormatTestParam {
public int mFormat;
public boolean mRepeating;
public boolean mSetUsageFlag = false;
public long mUsageFlag = HardwareBuffer.USAGE_CPU_READ_OFTEN;
public boolean mCheckSession = false;
public boolean mValidateImageData = true;
public String mPhysicalId = null;
public long mDynamicRangeProfile = DynamicRangeProfiles.STANDARD;
public ColorSpace.Named mColorSpace;
public boolean mUseColorSpace = false;
public int mTimestampBase = OutputConfiguration.TIMESTAMP_BASE_DEFAULT;
BufferFormatTestParam(int format, boolean repeating) {
mFormat = format;
mRepeating = repeating;
}
};
private void bufferFormatTestByCamera(BufferFormatTestParam params)
throws Exception {
int format = params.mFormat;
boolean setUsageFlag = params.mSetUsageFlag;
long usageFlag = params.mUsageFlag;
boolean repeating = params.mRepeating;
boolean validateImageData = params.mValidateImageData;
int timestampBase = params.mTimestampBase;
String physicalId = params.mPhysicalId;
StaticMetadata staticInfo;
if (physicalId == null) {
staticInfo = mStaticInfo;
} else {
staticInfo = mAllStaticInfo.get(physicalId);
}
Size[] availableSizes = staticInfo.getAvailableSizesForFormatChecked(format,
StaticMetadata.StreamDirection.Output);
boolean secureTest = setUsageFlag &&
((usageFlag & HardwareBuffer.USAGE_PROTECTED_CONTENT) != 0);
Size secureDataSize = null;
if (secureTest) {
secureDataSize = staticInfo.getCharacteristics().get(
CameraCharacteristics.SCALER_DEFAULT_SECURE_IMAGE_SIZE);
}
boolean validateTimestampBase = (timestampBase
!= OutputConfiguration.TIMESTAMP_BASE_DEFAULT);
Integer deviceTimestampSource = staticInfo.getCharacteristics().get(
CameraCharacteristics.SENSOR_INFO_TIMESTAMP_SOURCE);
// for each resolution, test imageReader:
for (Size sz : availableSizes) {
try {
// For secure mode test only test default secure data size if HAL advertises one.
if (secureDataSize != null && !secureDataSize.equals(sz)) {
continue;
}
if (VERBOSE) {
Log.v(TAG, "Testing size " + sz.toString() + " format " + format
+ " for camera " + mCamera.getId());
}
// Create ImageReader.
mListener = new SimpleImageListener();
if (setUsageFlag) {
createDefaultImageReader(sz, format, MAX_NUM_IMAGES, usageFlag, mListener);
} else {
createDefaultImageReader(sz, format, MAX_NUM_IMAGES, mListener);
}
// Don't queue up images if we won't validate them
if (!validateImageData && !validateTimestampBase) {
ImageDropperListener imageDropperListener = new ImageDropperListener();
mReader.setOnImageAvailableListener(imageDropperListener, mHandler);
}
if (params.mCheckSession) {
checkImageReaderSessionConfiguration(
"Camera capture session validation for format: " + format + "failed",
physicalId);
}
ArrayList<OutputConfiguration> outputConfigs = new ArrayList<>();
OutputConfiguration config = new OutputConfiguration(mReader.getSurface());
assertTrue("Default timestamp source must be DEFAULT",
config.getTimestampBase() == OutputConfiguration.TIMESTAMP_BASE_DEFAULT);
assertTrue("Default mirroring mode must be AUTO",
config.getMirrorMode() == OutputConfiguration.MIRROR_MODE_AUTO);
if (physicalId != null) {
config.setPhysicalCameraId(physicalId);
}
config.setDynamicRangeProfile(params.mDynamicRangeProfile);
config.setTimestampBase(params.mTimestampBase);
outputConfigs.add(config);
CaptureRequest request;
if (params.mUseColorSpace) {
request = prepareCaptureRequestForColorSpace(
outputConfigs, CameraDevice.TEMPLATE_PREVIEW, params.mColorSpace)
.build();
} else {
request = prepareCaptureRequestForConfigs(
outputConfigs, CameraDevice.TEMPLATE_PREVIEW).build();
}
SimpleCaptureCallback listener = new SimpleCaptureCallback();
startCapture(request, repeating, listener, mHandler);
int numFrameVerified = repeating ? NUM_FRAME_VERIFIED : 1;
if (validateTimestampBase) {
validateTimestamps(deviceTimestampSource, timestampBase, numFrameVerified,
listener, repeating);
}
if (validateImageData) {
// Validate images.
ColorSpace colorSpace = null;
if (params.mUseColorSpace) {
colorSpace = ColorSpace.get(params.mColorSpace);
}
validateImage(sz, format, numFrameVerified, repeating, colorSpace);
}
// Validate capture result.
validateCaptureResult(format, sz, listener, numFrameVerified);
// stop capture.
stopCapture(/*fast*/false);
} finally {
closeDefaultImageReader();
}
// Only test one size for non-default timestamp base.
if (timestampBase != OutputConfiguration.TIMESTAMP_BASE_DEFAULT) break;
}
}
private void bufferFormatLongProcessingTimeTestByCamera(int format)
throws Exception {
final int TEST_SENSITIVITY_VALUE = mStaticInfo.getSensitivityClampToRange(204);
final long TEST_EXPOSURE_TIME_NS = mStaticInfo.getExposureClampToRange(28000000);
final long EXPOSURE_TIME_ERROR_MARGIN_NS = 100000;
Size[] availableSizes = mStaticInfo.getAvailableSizesForFormatChecked(format,
StaticMetadata.StreamDirection.Output);
Size[] testSizes = getMinAndMaxSizes(availableSizes);
// for each resolution, test imageReader:
for (Size sz : testSizes) {
Log.v(TAG, "testing size " + sz.toString());
try {
if (VERBOSE) {
Log.v(TAG, "Testing long processing time: size " + sz.toString() + " format " +
format + " for camera " + mCamera.getId());
}
// Create ImageReader.
mListener = new SimpleImageListener();
createDefaultImageReader(sz, format, MAX_NUM_IMAGES, mListener);
// Setting manual controls
List<Surface> outputSurfaces = new ArrayList<Surface>();
outputSurfaces.add(mReader.getSurface());
CaptureRequest.Builder requestBuilder = prepareCaptureRequestForSurfaces(
outputSurfaces, CameraDevice.TEMPLATE_STILL_CAPTURE);
// Need to consume the SESSION_READY state because stopCapture() waits
// on an additional SESSION_READY state.
mCameraSessionListener.getStateWaiter().
waitForState(BlockingSessionCallback.SESSION_READY, CAMERA_IDLE_TIMEOUT_MS);
requestBuilder.set(
CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_OFF);
requestBuilder.set(CaptureRequest.CONTROL_AE_LOCK, true);
requestBuilder.set(CaptureRequest.CONTROL_AWB_LOCK, true);
requestBuilder.set(CaptureRequest.CONTROL_AE_MODE,
CaptureRequest.CONTROL_AE_MODE_OFF);
requestBuilder.set(CaptureRequest.CONTROL_AWB_MODE,
CaptureRequest.CONTROL_AWB_MODE_OFF);
requestBuilder.set(CaptureRequest.SENSOR_SENSITIVITY, TEST_SENSITIVITY_VALUE);
requestBuilder.set(CaptureRequest.SENSOR_EXPOSURE_TIME, TEST_EXPOSURE_TIME_NS);
SimpleCaptureCallback listener = new SimpleCaptureCallback();
startCapture(requestBuilder.build(), /*repeating*/true, listener, mHandler);
for (int i = 0; i < NUM_LONG_PROCESS_TIME_FRAME_VERIFIED; i++) {
mListener.waitForAnyImageAvailable(CAPTURE_WAIT_TIMEOUT_MS);
// Verify image.
Image img = mReader.acquireNextImage();
assertNotNull("Unable to acquire next image", img);
CameraTestUtils.validateImage(img, sz.getWidth(), sz.getHeight(), format,
mDebugFileNameBase);
// Verify the exposure time and iso match the requested values.
CaptureResult result = listener.getCaptureResult(CAPTURE_RESULT_TIMEOUT_MS);
long exposureTimeDiff = TEST_EXPOSURE_TIME_NS -
getValueNotNull(result, CaptureResult.SENSOR_EXPOSURE_TIME);
int sensitivityDiff = TEST_SENSITIVITY_VALUE -
getValueNotNull(result, CaptureResult.SENSOR_SENSITIVITY);
mCollector.expectTrue(
String.format("Long processing frame %d format %d size %s " +
"exposure time was %d expecting %d.", i, format, sz.toString(),
getValueNotNull(result, CaptureResult.SENSOR_EXPOSURE_TIME),
TEST_EXPOSURE_TIME_NS),
exposureTimeDiff < EXPOSURE_TIME_ERROR_MARGIN_NS &&
exposureTimeDiff >= 0);
mCollector.expectTrue(
String.format("Long processing frame %d format %d size %s " +
"sensitivity was %d expecting %d.", i, format, sz.toString(),
getValueNotNull(result, CaptureResult.SENSOR_SENSITIVITY),
TEST_SENSITIVITY_VALUE),
sensitivityDiff >= 0);
// Sleep to Simulate long porcessing before closing the image.
Thread.sleep(LONG_PROCESS_TIME_MS);
img.close();
}
// Stop capture.
// Drain the reader queue in case the full queue blocks
// HAL from delivering new results
ImageDropperListener imageDropperListener = new ImageDropperListener();
mReader.setOnImageAvailableListener(imageDropperListener, mHandler);
Image img = mReader.acquireLatestImage();
if (img != null) {
img.close();
}
stopCapture(/*fast*/true);
} finally {
closeDefaultImageReader();
}
}
}
/**
* Validate capture results.
*
* @param format The format of this capture.
* @param size The capture size.
* @param listener The capture listener to get capture result callbacks.
*/
private void validateCaptureResult(int format, Size size, SimpleCaptureCallback listener,
int numFrameVerified) {
for (int i = 0; i < numFrameVerified; i++) {
CaptureResult result = listener.getCaptureResult(CAPTURE_RESULT_TIMEOUT_MS);
// TODO: Update this to use availableResultKeys once shim supports this.
if (mStaticInfo.isCapabilitySupported(
CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_READ_SENSOR_SETTINGS)) {
StaticMetadata staticInfo = mStaticInfo;
boolean supportActivePhysicalIdConsistency =
PropertyUtil.getFirstApiLevel() >= Build.VERSION_CODES.S;
if (mStaticInfo.isLogicalMultiCamera() && supportActivePhysicalIdConsistency
&& mStaticInfo.isActivePhysicalCameraIdSupported()) {
String activePhysicalId =
result.get(CaptureResult.LOGICAL_MULTI_CAMERA_ACTIVE_PHYSICAL_ID);
staticInfo = mAllStaticInfo.get(activePhysicalId);
}
Long exposureTime = getValueNotNull(result, CaptureResult.SENSOR_EXPOSURE_TIME);
Integer sensitivity = getValueNotNull(result, CaptureResult.SENSOR_SENSITIVITY);
mCollector.expectInRange(
String.format(
"Capture for format %d, size %s exposure time is invalid.",
format, size.toString()),
exposureTime,
staticInfo.getExposureMinimumOrDefault(),
staticInfo.getExposureMaximumOrDefault()
);
mCollector.expectInRange(
String.format("Capture for format %d, size %s sensitivity is invalid.",
format, size.toString()),
sensitivity,
staticInfo.getSensitivityMinimumOrDefault(),
staticInfo.getSensitivityMaximumOrDefault()
);
}
// TODO: add more key validations.
}
}
private final class SimpleImageListener implements ImageReader.OnImageAvailableListener {
private final ConditionVariable imageAvailable = new ConditionVariable();
@Override
public void onImageAvailable(ImageReader reader) {
if (mReader != reader) {
return;
}
if (VERBOSE) Log.v(TAG, "new image available");
imageAvailable.open();
}
public void waitForAnyImageAvailable(long timeout) {
if (imageAvailable.block(timeout)) {
imageAvailable.close();
} else {
fail("wait for image available timed out after " + timeout + "ms");
}
}
public void closePendingImages() {
Image image = mReader.acquireLatestImage();
if (image != null) {
image.close();
}
}
public void reset() {
imageAvailable.close();
}
}
private void validateImage(Size sz, int format, int captureCount, boolean repeating,
ColorSpace colorSpace) throws Exception {
// TODO: Add more format here, and wrap each one as a function.
Image img;
final int MAX_RETRY_COUNT = 20;
int numImageVerified = 0;
int reTryCount = 0;
while (numImageVerified < captureCount) {
assertNotNull("Image listener is null", mListener);
if (VERBOSE) Log.v(TAG, "Waiting for an Image");
mListener.waitForAnyImageAvailable(CAPTURE_WAIT_TIMEOUT_MS);
if (repeating) {
/**
* Acquire the latest image in case the validation is slower than
* the image producing rate.
*/
img = mReader.acquireLatestImage();
/**
* Sometimes if multiple onImageAvailable callbacks being queued,
* acquireLatestImage will clear all buffer before corresponding callback is
* executed. Wait for a new frame in that case.
*/
if (img == null && reTryCount < MAX_RETRY_COUNT) {
reTryCount++;
continue;
}
} else {
img = mReader.acquireNextImage();
}
assertNotNull("Unable to acquire the latest image", img);
if (VERBOSE) Log.v(TAG, "Got the latest image");
CameraTestUtils.validateImage(img, sz.getWidth(), sz.getHeight(), format,
mDebugFileNameBase, colorSpace);
HardwareBuffer hwb = img.getHardwareBuffer();
assertNotNull("Unable to retrieve the Image's HardwareBuffer", hwb);
if (format == ImageFormat.DEPTH_JPEG) {
byte [] dynamicDepthBuffer = CameraTestUtils.getDataFromImage(img);
assertTrue("Dynamic depth validation failed!",
validateDynamicDepthNative(dynamicDepthBuffer));
}
if (VERBOSE) Log.v(TAG, "finish validation of image " + numImageVerified);
img.close();
numImageVerified++;
reTryCount = 0;
}
// Return all pending images to the ImageReader as the validateImage may
// take a while to return and there could be many images pending.
mListener.closePendingImages();
}
private void validateTimestamps(Integer deviceTimestampSource, int timestampBase,
int captureCount, SimpleCaptureCallback listener, boolean repeating) throws Exception {
Image img;
final int MAX_RETRY_COUNT = 20;
int numImageVerified = 0;
int retryCount = 0;
List<Long> imageTimestamps = new ArrayList<Long>();
assertNotNull("Image listener is null", mListener);
while (numImageVerified < captureCount) {
if (VERBOSE) Log.v(TAG, "Waiting for an Image");
mListener.waitForAnyImageAvailable(CAPTURE_WAIT_TIMEOUT_MS);
if (repeating) {
img = mReader.acquireNextImage();
if (img == null && retryCount < MAX_RETRY_COUNT) {
retryCount++;
continue;
}
} else {
img = mReader.acquireNextImage();
}
assertNotNull("Unable to acquire the latest image", img);
if (VERBOSE) {
Log.v(TAG, "Got the latest image with timestamp " + img.getTimestamp());
}
imageTimestamps.add(img.getTimestamp());
img.close();
numImageVerified++;
retryCount = 0;
}
List<Long> captureStartTimestamps = listener.getCaptureStartTimestamps(captureCount);
if (VERBOSE) {
Log.v(TAG, "deviceTimestampSource: " + deviceTimestampSource
+ ", timestampBase: " + timestampBase + ", captureStartTimestamps: "
+ captureStartTimestamps + ", imageTimestamps: " + imageTimestamps);
}
if (timestampBase == OutputConfiguration.TIMESTAMP_BASE_SENSOR
|| (timestampBase == OutputConfiguration.TIMESTAMP_BASE_MONOTONIC
&& deviceTimestampSource == CameraMetadata.SENSOR_INFO_TIMESTAMP_SOURCE_UNKNOWN)
|| (timestampBase == OutputConfiguration.TIMESTAMP_BASE_REALTIME
&& deviceTimestampSource == CameraMetadata.SENSOR_INFO_TIMESTAMP_SOURCE_REALTIME)) {
// Makes sure image timestamps match capture started timestamp
for (Long timestamp : imageTimestamps) {
mCollector.expectTrue("Image timestamp " + timestamp
+ " should match one of onCaptureStarted timestamps "
+ captureStartTimestamps,
captureStartTimestamps.contains(timestamp));
}
} else if (timestampBase == OutputConfiguration.TIMESTAMP_BASE_CHOREOGRAPHER_SYNCED) {
// Make sure that timestamp base is MONOTONIC. Do not strictly check against
// choreographer callback because there are cases camera framework doesn't use
// choreographer timestamp (when consumer is slower than camera for example).
final int TIMESTAMP_THRESHOLD_MILLIS = 3000; // 3 seconds
long monotonicTime = SystemClock.uptimeMillis();
for (Long timestamp : imageTimestamps) {
long timestampMs = TimeUnit.NANOSECONDS.toMillis(timestamp);
mCollector.expectTrue("Image timestamp " + timestampMs + " ms should be in the "
+ "same timebase as SystemClock.updateMillis " + monotonicTime
+ " ms when timestamp base is set to CHOREOGRAPHER synced",
Math.abs(timestampMs - monotonicTime) < TIMESTAMP_THRESHOLD_MILLIS);
}
}
// Return all pending images to the ImageReader as the validateImage may
// take a while to return and there could be many images pending.
mListener.closePendingImages();
}
/**
* Gets the list of test sizes to run the test on, given the array of available sizes.
* For ImageReaderTest, where the sizes are not the most relevant, it is sufficient to test with
* just the min and max size, which helps reduce test time significantly.
*/
private Size[] getMinAndMaxSizes(Size[] availableSizes) {
if (availableSizes.length <= 2) {
return availableSizes;
}
Size[] testSizes = new Size[2];
Size maxSize = availableSizes[0];
Size minSize = availableSizes[1];
for (Size size : availableSizes) {
if (size.getWidth() * size.getHeight() > maxSize.getWidth() * maxSize.getHeight()) {
maxSize = size;
}
if (size.getWidth() * size.getHeight() < minSize.getWidth() * minSize.getHeight()) {
minSize = size;
}
}
testSizes[0] = minSize;
testSizes[1] = maxSize;
return testSizes;
}
/** Load dynamic depth validation jni on initialization */
static {
System.loadLibrary("ctscamera2_jni");
}
/**
* Use the dynamic depth SDK to validate a dynamic depth file stored in the buffer.
*
* Returns false if the dynamic depth has validation errors. Validation warnings/errors
* will be printed to logcat.
*/
private static native boolean validateDynamicDepthNative(byte[] dynamicDepthBuffer);
}