blob: 220b1d9267c1a9ba83d6708582a1627e78f4b791 [file] [log] [blame]
/*
* 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 static junit.framework.Assert.*;
import static org.junit.Assume.assumeNotNull;
import static org.junit.Assume.assumeTrue;
import android.graphics.ImageFormat;
import android.graphics.SurfaceTexture;
import android.hardware.DataSpace;
import android.hardware.HardwareBuffer;
import android.hardware.SyncFence;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.cts.CameraTestUtils.SimpleCaptureCallback;
import android.hardware.camera2.cts.testcases.Camera2AndroidTestCase;
import android.media.Image;
import android.media.Image.Plane;
import android.media.ImageReader;
import android.media.ImageWriter;
import android.util.Log;
import android.util.Size;
import android.view.Surface;
import com.android.cts.hardware.SyncFenceUtil;
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;
/**
* <p>
* Basic test for ImageWriter APIs. ImageWriter takes the images produced by
* camera (via ImageReader), then the data is consumed by either camera input
* interface or ImageReader.
* </p>
*/
@RunWith(Parameterized.class)
public class ImageWriterTest extends Camera2AndroidTestCase {
private static final String TAG = "ImageWriterTest";
private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
// Max number of images can be accessed simultaneously from ImageReader.
private static final int MAX_NUM_IMAGES = 3;
private static final int CAMERA_PRIVATE_FORMAT = ImageFormat.PRIVATE;
private static final int BUFFER_WIDTH = 640;
private static final int BUFFER_HEIGHT = 480;
private ImageReader mReaderForWriter;
private ImageWriter mWriter;
@Override
public void tearDown() throws Exception {
try {
closeImageReader(mReaderForWriter);
} finally {
mReaderForWriter = null;
if (mWriter != null) {
mWriter.close();
mWriter = null;
}
}
super.tearDown();
}
/**
* <p>
* Basic YUV420_888 format ImageWriter ImageReader test that checks the
* images produced by camera can be passed correctly by ImageWriter.
* </p>
* <p>
* {@link ImageReader} reads the images produced by {@link CameraDevice}.
* The images are then passed to ImageWriter, which produces new images that
* are consumed by the second image reader. The images from first
* ImageReader should be identical with the images from the second
* ImageReader. This validates the basic image input interface of the
* ImageWriter. Below is the data path tested:
* <li>Explicit data copy: Dequeue an image from ImageWriter, copy the image
* data from first ImageReader into this image, then queue this image back
* to ImageWriter. This validates the ImageWriter explicit buffer copy
* interface.</li>
* </p>
*/
@Test
public void testYuvImageWriterReaderOperation() throws Exception {
for (String id : mCameraIdsUnderTest) {
try {
Log.i(TAG, "Testing Camera " + id);
if (!mAllStaticInfo.get(id).isColorOutputSupported()) {
Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
continue;
}
openDevice(id);
readerWriterFormatTestByCamera(ImageFormat.YUV_420_888, false);
} finally {
closeDevice(id);
}
}
}
/**
* <p>
* Similar to testYuvImageWriterReaderOperation, but use the alternative
* factory method of ImageReader and ImageWriter.
* </p>
*/
@Test
public void testYuvImageWriterReaderOperationAlt() throws Exception {
for (String id : mCameraIdsUnderTest) {
try {
Log.i(TAG, "Testing Camera " + id);
if (!mAllStaticInfo.get(id).isColorOutputSupported()) {
Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
continue;
}
openDevice(id);
readerWriterFormatTestByCamera(ImageFormat.YUV_420_888, true);
} finally {
closeDevice(id);
}
}
}
@Test
public void testAbandonedSurfaceExceptions() throws Exception {
final int READER_WIDTH = 1920;
final int READER_HEIGHT = 1080;
final int READER_FORMAT = ImageFormat.YUV_420_888;
// Verify that if the image writer's input surface is abandoned, dequeueing an image
// throws IllegalStateException
ImageReader reader = ImageReader.newInstance(READER_WIDTH, READER_HEIGHT, READER_FORMAT,
MAX_NUM_IMAGES);
ImageWriter writer = ImageWriter.newInstance(reader.getSurface(), MAX_NUM_IMAGES);
// Close image reader to abandon the input surface.
reader.close();
Image image;
try {
image = writer.dequeueInputImage();
fail("Should get an IllegalStateException");
} catch (IllegalStateException e) {
// Expected
} finally {
writer.close();
}
// Verify that if the image writer's input surface is abandoned, queueing an image
// throws IllegalStateException
reader = ImageReader.newInstance(READER_WIDTH, READER_HEIGHT, READER_FORMAT,
MAX_NUM_IMAGES);
writer = ImageWriter.newInstance(reader.getSurface(), MAX_NUM_IMAGES);
image = writer.dequeueInputImage();
// Close image reader to abandon the input surface.
reader.close();
try {
writer.queueInputImage(image);
fail("Should get an IllegalStateException");
} catch (IllegalStateException e) {
// Expected
} finally {
writer.close();
}
}
@Test
public void testWriterReaderBlobFormats() throws Exception {
int[] READER_TEST_FORMATS = {ImageFormat.JPEG, ImageFormat.DEPTH_JPEG,
ImageFormat.HEIC, ImageFormat.DEPTH_POINT_CLOUD};
for (int format : READER_TEST_FORMATS) {
ImageReader reader = ImageReader.newInstance(640, 480, format, 1 /*maxImages*/);
ImageWriter writer = ImageWriter.newInstance(reader.getSurface(), 1 /*maxImages*/);
writer.close();
reader.close();
}
}
@Test
public void testWriterFormatOverride() throws Exception {
int[] TEXTURE_TEST_FORMATS = {ImageFormat.YV12, ImageFormat.YUV_420_888};
SurfaceTexture texture = new SurfaceTexture(false);
texture.setDefaultBufferSize(BUFFER_WIDTH, BUFFER_HEIGHT);
Surface surface = new Surface(texture);
// Make sure that the default newInstance is still valid.
ImageWriter defaultWriter = ImageWriter.newInstance(surface, MAX_NUM_IMAGES);
Image defaultImage = defaultWriter.dequeueInputImage();
defaultWriter.close();
for (int format : TEXTURE_TEST_FORMATS) {
// Override default buffer format of Surface texture to test format
ImageWriter writer = ImageWriter.newInstance(surface, MAX_NUM_IMAGES, format);
Image image = writer.dequeueInputImage();
Log.i(TAG, "testing format " + format + ", got input image format " +
image.getFormat());
assertTrue(image.getFormat() == format);
writer.close();
}
}
@Test
public void testWriterWithImageFormatOverride() throws Exception {
final int imageReaderFormat = ImageFormat.YUV_420_888;
final int imageWriterFormat = ImageFormat.YV12;
final int dataSpace = DataSpace.DATASPACE_JFIF;
final int hardwareBufferFormat = ImageFormat.YV12;
try (
ImageReader reader = new ImageReader
.Builder(BUFFER_WIDTH, BUFFER_HEIGHT)
.setImageFormat(imageReaderFormat)
.build();
ImageWriter writer = new ImageWriter
.Builder(reader.getSurface())
.setImageFormat(imageWriterFormat)
.build();
Image outputImage = writer.dequeueInputImage()
) {
assertEquals(1, reader.getMaxImages());
assertEquals(imageReaderFormat, reader.getImageFormat());
assertEquals(dataSpace, reader.getDataSpace());
assertEquals(HardwareBuffer.USAGE_CPU_READ_OFTEN, reader.getUsage());
assertEquals(dataSpace, writer.getDataSpace());
assertEquals(imageWriterFormat, writer.getFormat());
assertEquals(BUFFER_WIDTH, outputImage.getWidth());
assertEquals(BUFFER_HEIGHT, outputImage.getHeight());
assertEquals(imageWriterFormat, outputImage.getFormat());
assertEquals(dataSpace, outputImage.getDataSpace());
}
}
@Test
public void testWriterBuilderDefault() throws Exception {
try (
ImageReader reader = new ImageReader
.Builder(BUFFER_WIDTH, BUFFER_HEIGHT)
.setImageFormat(ImageFormat.HEIC)
.setDefaultHardwareBufferFormat(HardwareBuffer.RGBA_8888)
.setDefaultDataSpace(DataSpace.DATASPACE_BT709)
.build();
ImageWriter writer = new ImageWriter
.Builder(reader.getSurface())
.build();
Image outputImage = writer.dequeueInputImage()
) {
assertEquals(1, reader.getMaxImages()); // default maxImages
assertEquals(HardwareBuffer.USAGE_CPU_READ_OFTEN, reader.getUsage()); // default usage
assertEquals(HardwareBuffer.RGBA_8888, reader.getHardwareBufferFormat());
assertEquals(DataSpace.DATASPACE_BT709, reader.getDataSpace());
assertEquals(BUFFER_WIDTH, outputImage.getWidth());
assertEquals(BUFFER_HEIGHT, outputImage.getHeight());
assertEquals(HardwareBuffer.RGBA_8888, outputImage.getFormat());
}
}
@Test
public void testWriterBuilderSetImageFormatAndSize() throws Exception {
SurfaceTexture texture = new SurfaceTexture(false);
texture.setDefaultBufferSize(BUFFER_WIDTH, BUFFER_HEIGHT);
Surface surface = new Surface(texture);
final int imageWriterWidth = 20;
final int imageWriterHeight = 50;
long usage = HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE;
try (
ImageWriter writer = new ImageWriter
.Builder(surface)
.setWidthAndHeight(imageWriterWidth, imageWriterHeight)
.setMaxImages(MAX_NUM_IMAGES)
.setImageFormat(ImageFormat.YV12)
.setUsage(usage)
.build();
Image image = writer.dequeueInputImage()
) {
// ImageFormat.YV12 HAL dataspace is DataSpace.DATASPACE_JFIF
assertEquals(imageWriterWidth, writer.getWidth());
assertEquals(imageWriterHeight, writer.getHeight());
assertEquals(MAX_NUM_IMAGES, writer.getMaxImages());
assertEquals(DataSpace.DATASPACE_JFIF, writer.getDataSpace());
assertEquals(usage, writer.getUsage());
assertEquals(DataSpace.DATASPACE_JFIF, image.getDataSpace());
assertEquals(ImageFormat.YV12, image.getFormat());
assertEquals(imageWriterWidth, image.getWidth());
}
}
@Test
public void testWriterBuilderSetHardwareBufferFormatAndDataSpace() throws Exception {
SurfaceTexture texture = new SurfaceTexture(false);
texture.setDefaultBufferSize(BUFFER_WIDTH, BUFFER_HEIGHT);
Surface surface = new Surface(texture);
long usage = HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE | HardwareBuffer.USAGE_GPU_COLOR_OUTPUT;
try (
ImageWriter writer = new ImageWriter
.Builder(surface)
.setImageFormat(ImageFormat.YV12)
.setHardwareBufferFormat(HardwareBuffer.RGBA_8888)
.setDataSpace(DataSpace.DATASPACE_BT709)
.setUsage(usage)
.build();
Image image = writer.dequeueInputImage()
) {
assertEquals(BUFFER_WIDTH, writer.getWidth());
assertEquals(BUFFER_HEIGHT, writer.getHeight());
assertEquals(DataSpace.DATASPACE_BT709, writer.getDataSpace());
assertEquals(HardwareBuffer.RGBA_8888, writer.getHardwareBufferFormat());
assertEquals(usage, writer.getUsage());
assertEquals(DataSpace.DATASPACE_BT709, image.getDataSpace());
assertEquals(BUFFER_WIDTH, image.getWidth());
assertEquals(BUFFER_HEIGHT, image.getHeight());
}
}
@Test
public void testGetFence() throws Exception {
try (
ImageReader reader = new ImageReader
.Builder(20, 45)
.setMaxImages(2)
.setImageFormat(ImageFormat.YUV_420_888)
.build();
ImageWriter writer = new ImageWriter
.Builder(reader.getSurface())
.build();
Image outputImage = writer.dequeueInputImage()
) {
assertEquals(false, outputImage.getFence().isValid());
}
}
@Test
public void testSetFence() throws Exception {
SyncFence fence = SyncFenceUtil.createUselessFence();
assumeNotNull(fence);
SurfaceTexture texture = new SurfaceTexture(false);
texture.setDefaultBufferSize(BUFFER_WIDTH, BUFFER_HEIGHT);
Surface surface = new Surface(texture);
// fence may not be valid on cuttlefish using swiftshader
assumeTrue(fence.isValid());
try (
ImageWriter writer = new ImageWriter
.Builder(surface)
.build();
Image outputImage = writer.dequeueInputImage()
) {
outputImage.setFence(fence);
assertEquals(fence.getSignalTime(), outputImage.getFence().getSignalTime());
}
}
@Test
public void testGetPlanesAndFence() throws Exception {
try (
ImageReader reader = new ImageReader
.Builder(BUFFER_WIDTH, BUFFER_HEIGHT)
.build();
ImageWriter writer = new ImageWriter
.Builder(reader.getSurface())
.build();
Image outputImage = writer.dequeueInputImage();
) {
outputImage.getPlanes();
assertEquals(false, outputImage.getFence().isValid());
}
}
private void readerWriterFormatTestByCamera(int format, boolean altFactoryMethod)
throws Exception {
List<Size> sizes = getSortedSizesForFormat(mCamera.getId(), mCameraManager, format, null);
Size maxSize = sizes.get(0);
if (VERBOSE) {
Log.v(TAG, "Testing size " + maxSize);
}
// Create ImageReader for camera output.
SimpleImageReaderListener listenerForCamera = new SimpleImageReaderListener();
if (altFactoryMethod) {
createDefaultImageReader(maxSize, format, MAX_NUM_IMAGES,
HardwareBuffer.USAGE_CPU_READ_OFTEN, listenerForCamera);
} else {
createDefaultImageReader(maxSize, format, MAX_NUM_IMAGES, listenerForCamera);
}
if (VERBOSE) {
Log.v(TAG, "Created camera output ImageReader");
}
// Create ImageReader for ImageWriter output
SimpleImageReaderListener listenerForWriter = new SimpleImageReaderListener();
if (altFactoryMethod) {
mReaderForWriter = createImageReader(
maxSize, format, MAX_NUM_IMAGES,
HardwareBuffer.USAGE_CPU_READ_OFTEN, listenerForWriter);
} else {
mReaderForWriter = createImageReader(
maxSize, format, MAX_NUM_IMAGES, listenerForWriter);
}
if (VERBOSE) {
Log.v(TAG, "Created ImageWriter output ImageReader");
}
// Create ImageWriter
Surface surface = mReaderForWriter.getSurface();
assertNotNull("Surface from ImageReader shouldn't be null", surface);
if (altFactoryMethod) {
mWriter = ImageWriter.newInstance(surface, MAX_NUM_IMAGES, format);
} else {
mWriter = ImageWriter.newInstance(surface, MAX_NUM_IMAGES);
}
SimpleImageWriterListener writerImageListener = new SimpleImageWriterListener(mWriter);
mWriter.setOnImageReleasedListener(writerImageListener, mHandler);
// Start capture: capture 2 images.
List<Surface> outputSurfaces = new ArrayList<Surface>();
outputSurfaces.add(mReader.getSurface());
CaptureRequest.Builder requestBuilder = prepareCaptureRequestForSurfaces(outputSurfaces,
CameraDevice.TEMPLATE_PREVIEW);
SimpleCaptureCallback captureListener = new SimpleCaptureCallback();
// Capture 1st image.
startCapture(requestBuilder.build(), /*repeating*/false, captureListener, mHandler);
// Capture 2nd image.
startCapture(requestBuilder.build(), /*repeating*/false, captureListener, mHandler);
if (VERBOSE) {
Log.v(TAG, "Submitted 2 captures");
}
// Image from the first ImageReader.
Image cameraImage = null;
// ImageWriter input image.
Image inputImage = null;
// Image from the second ImageReader.
Image outputImage = null;
assertTrue("ImageWriter max images should be " + MAX_NUM_IMAGES,
mWriter.getMaxImages() == MAX_NUM_IMAGES);
if (format == CAMERA_PRIVATE_FORMAT) {
assertTrue("First ImageReader format should be PRIVATE",
mReader.getImageFormat() == CAMERA_PRIVATE_FORMAT);
assertTrue("Second ImageReader should be PRIVATE",
mReaderForWriter.getImageFormat() == CAMERA_PRIVATE_FORMAT);
assertTrue("Format of first ImageReader should be PRIVATE",
mReader.getImageFormat() == CAMERA_PRIVATE_FORMAT);
assertTrue(" Format of second ImageReader should be PRIVATE",
mReaderForWriter.getImageFormat() == CAMERA_PRIVATE_FORMAT);
assertTrue(" Format of ImageWriter should be PRIVATE",
mWriter.getFormat() == CAMERA_PRIVATE_FORMAT);
// Validate 2 images
validateOpaqueImages(maxSize, listenerForCamera, listenerForWriter, captureListener,
/*numImages*/2, writerImageListener);
} else {
// Test case 1: Explicit data copy, only applicable for explicit formats.
// Get 1st image from first ImageReader, and copy the data to ImageWrtier input image
cameraImage = listenerForCamera.getImage(CAPTURE_IMAGE_TIMEOUT_MS);
inputImage = mWriter.dequeueInputImage();
inputImage.setTimestamp(cameraImage.getTimestamp());
if (VERBOSE) {
Log.v(TAG, "Image is being copied");
}
imageCopy(cameraImage, inputImage);
if (VERBOSE) {
Log.v(TAG, "Image copy is done");
}
mCollector.expectTrue(
"ImageWriter 1st input image should match camera 1st output image",
isImageStronglyEqual(inputImage, cameraImage));
if (DEBUG) {
String inputFileName = mDebugFileNameBase + "/" + maxSize + "_image1_input.yuv";
dumpFile(inputFileName, getDataFromImage(inputImage));
}
// Image should be closed after queueInputImage call
Plane closedPlane = inputImage.getPlanes()[0];
ByteBuffer closedBuffer = closedPlane.getBuffer();
mWriter.queueInputImage(inputImage);
imageInvalidAccessTestAfterClose(inputImage, closedPlane, closedBuffer);
outputImage = listenerForWriter.getImage(CAPTURE_IMAGE_TIMEOUT_MS);
mCollector.expectTrue("ImageWriter 1st output image should match 1st camera image",
isImageStronglyEqual(cameraImage, outputImage));
if (DEBUG) {
String img1FileName = mDebugFileNameBase + "/" + maxSize + "_image1_camera.yuv";
String outputImg1FileName = mDebugFileNameBase + "/" + maxSize
+ "_image1_output.yuv";
dumpFile(img1FileName, getDataFromImage(cameraImage));
dumpFile(outputImg1FileName, getDataFromImage(outputImage));
}
// No need to close inputImage, as it is sent to the surface after queueInputImage;
cameraImage.close();
outputImage.close();
// Make sure ImageWriter listener callback is fired.
writerImageListener.waitForImageReleased(CAPTURE_IMAGE_TIMEOUT_MS);
// Test case 2: Directly inject the image into ImageWriter: works for all formats.
// Get 2nd image and queue it directly to ImageWrier
cameraImage = listenerForCamera.getImage(CAPTURE_IMAGE_TIMEOUT_MS);
// make a copy of image1 data, as it will be closed after queueInputImage;
byte[] img1Data = getDataFromImage(cameraImage);
if (DEBUG) {
String img2FileName = mDebugFileNameBase + "/" + maxSize + "_image2_camera.yuv";
dumpFile(img2FileName, img1Data);
}
// Image should be closed after queueInputImage call
closedPlane = cameraImage.getPlanes()[0];
closedBuffer = closedPlane.getBuffer();
mWriter.queueInputImage(cameraImage);
imageInvalidAccessTestAfterClose(cameraImage, closedPlane, closedBuffer);
outputImage = listenerForWriter.getImage(CAPTURE_IMAGE_TIMEOUT_MS);
byte[] outputImageData = getDataFromImage(outputImage);
mCollector.expectTrue("ImageWriter 2nd output image should match camera "
+ "2nd output image", Arrays.equals(img1Data, outputImageData));
if (DEBUG) {
String outputImgFileName = mDebugFileNameBase + "/" + maxSize +
"_image2_output.yuv";
dumpFile(outputImgFileName, outputImageData);
}
// No need to close inputImage, as it is sent to the surface after queueInputImage;
outputImage.close();
// Make sure ImageWriter listener callback is fired.
writerImageListener.waitForImageReleased(CAPTURE_IMAGE_TIMEOUT_MS);
}
stopCapture(/*fast*/false);
mReader.close();
mReader = null;
mReaderForWriter.close();
mReaderForWriter = null;
mWriter.close();
mWriter = null;
}
private void validateOpaqueImages(Size maxSize, SimpleImageReaderListener listenerForCamera,
SimpleImageReaderListener listenerForWriter, SimpleCaptureCallback captureListener,
int numImages, SimpleImageWriterListener writerListener) throws Exception {
Image cameraImage;
Image outputImage;
for (int i = 0; i < numImages; i++) {
cameraImage = listenerForCamera.getImage(CAPTURE_IMAGE_TIMEOUT_MS);
CaptureResult result = captureListener.getCaptureResult(CAPTURE_IMAGE_TIMEOUT_MS);
validateOpaqueImage(cameraImage, "Opaque image " + i + "from camera: ", maxSize,
result);
mWriter.queueInputImage(cameraImage);
// Image should be closed after queueInputImage
imageInvalidAccessTestAfterClose(cameraImage,
/*closedPlane*/null, /*closedBuffer*/null);
outputImage = listenerForWriter.getImage(CAPTURE_IMAGE_TIMEOUT_MS);
validateOpaqueImage(outputImage, "First Opaque image output by ImageWriter: ",
maxSize, result);
outputImage.close();
writerListener.waitForImageReleased(CAPTURE_IMAGE_TIMEOUT_MS);
}
}
private void validateOpaqueImage(Image image, String msg, Size imageSize,
CaptureResult result) {
assertNotNull("Opaque image Capture result should not be null", result != null);
mCollector.expectImageProperties(msg + "Opaque ", image, CAMERA_PRIVATE_FORMAT,
imageSize, result.get(CaptureResult.SENSOR_TIMESTAMP));
mCollector.expectTrue(msg + "Opaque image number planes should be zero",
image.getPlanes().length == 0);
}
}