blob: 3b59f532663ce5b25dcd981ceb9a4566036d0331 [file] [log] [blame]
/*
* Copyright 2021 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.graphics.ImageFormat;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.cts.CameraTestUtils;
import android.hardware.camera2.cts.helpers.StaticMetadata;
import android.hardware.camera2.cts.testcases.Camera2AndroidTestCase;
import android.hardware.camera2.params.DynamicRangeProfiles;
import android.hardware.camera2.params.OutputConfiguration;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult;
import android.media.Image;
import android.media.ImageReader;
import android.os.Build;
import android.os.ConditionVariable;
import android.platform.test.annotations.AppModeFull;
import android.util.Log;
import android.util.Range;
import android.util.Size;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import com.android.compatibility.common.util.CddTest;
import com.android.compatibility.common.util.PropertyUtil;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.Test;
import static android.hardware.camera2.cts.CameraTestUtils.CAPTURE_RESULT_TIMEOUT_MS;
import static android.hardware.camera2.cts.CameraTestUtils.SimpleCaptureCallback;
import static junit.framework.Assert.*;
/**
* <p>Basic test for image capture using CONTROL_ZOOM_RATIO. It uses CameraDevice as
* producer, and camera sends image data to an imageReader. Image formats
* being tested are JPEG and RAW.</p>
*/
@RunWith(Parameterized.class)
public class ZoomCaptureTest extends Camera2AndroidTestCase {
private static final String TAG = "ZoomCaptureTest";
private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
private SimpleImageListener mListener;
@Override
public void setUp() throws Exception {
super.setUp();
}
@Override
public void tearDown() throws Exception {
super.tearDown();
}
@Test
@AppModeFull(reason = "Instant apps can't access Test API")
public void testJpegZoomCapture() throws Exception {
for (String id : mCameraIdsUnderTest) {
try {
Log.v(TAG, "Testing jpeg zoom capture for Camera " + id);
openDevice(id);
bufferFormatZoomTestByCamera(ImageFormat.JPEG);
} finally {
closeDevice(id);
}
}
}
@Test
@AppModeFull(reason = "Instant apps can't access Test API")
public void testRawZoomCapture() throws Exception {
for (String id : mCameraIdsUnderTest) {
try {
Log.v(TAG, "Testing raw zoom capture for camera " + id);
openDevice(id);
bufferFormatZoomTestByCamera(ImageFormat.RAW_SENSOR);
} finally {
closeDevice(id);
}
}
}
/**
* 10-bit output capable logical devices must also support switching between the same physical
* cameras via the CONTROL_ZOOM_RATIO control, that the device supports switching between for
* 8-bit outputs.
*/
@CddTest(requirement="7.5/C-3-1")
@Test
@AppModeFull(reason = "Instant apps can't access Test API")
public void test10bitLogicalZoomCapture() throws Exception {
final int ZOOM_RATIO_STEPS = 100;
for (String id : mCameraIdsUnderTest) {
try {
Log.v(TAG, "Testing 10-bit logical camera zoom capture for id " + id);
openDevice(id);
boolean supports10BitOutput = mStaticInfo.isCapabilitySupported(
CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT);
if (!supports10BitOutput) {
Log.v(TAG, "No 10-bit output support, skipping id " + id);
continue;
}
if (!mStaticInfo.isColorOutputSupported()) {
Log.v(TAG, "No color output support, skipping id " + id);
continue;
}
if (!mStaticInfo.isLogicalMultiCamera()) {
Log.v(TAG, "No logical device, skipping id " + id);
continue;
}
Range<Float> zoomRatioRange = mStaticInfo.getZoomRatioRangeChecked();
ArrayList<Float> candidateZoomRatios = new ArrayList<>(ZOOM_RATIO_STEPS);
Float zoomStep =
(zoomRatioRange.getUpper() - zoomRatioRange.getLower()) / ZOOM_RATIO_STEPS;
for (int step = 0; step < (ZOOM_RATIO_STEPS - 1); step++) {
candidateZoomRatios.add(step, zoomRatioRange.getLower() + step * zoomStep);
}
candidateZoomRatios.add(ZOOM_RATIO_STEPS - 1, zoomRatioRange.getUpper());
Set<String> activePhysicalIdsSeen8bit = new HashSet<String>();
bufferFormatZoomTestByCamera(ImageFormat.YUV_420_888, candidateZoomRatios,
activePhysicalIdsSeen8bit /*activePhysicalIdsSeen*/);
Set<String> activePhysicalIdsSeen10bit = new HashSet<String>();
bufferFormatZoomTestByCamera(ImageFormat.YCBCR_P010, candidateZoomRatios,
activePhysicalIdsSeen10bit /*activePhysicalIdsSeen*/);
assertEquals("The physical ids seen when zooming with 8-bit output: " +
activePhysicalIdsSeen8bit + " must match with the 10-bit output: " +
activePhysicalIdsSeen10bit, activePhysicalIdsSeen8bit,
activePhysicalIdsSeen10bit);
} finally {
closeDevice(id);
}
}
}
private void bufferFormatZoomTestByCamera(int format) throws Exception {
Set<String> activePhysicalIdsSeen = new HashSet<String>();
bufferFormatZoomTestByCamera(format, CameraTestUtils.getCandidateZoomRatios(mStaticInfo),
activePhysicalIdsSeen /*/activePhysicalIdSeen*/);
}
private void bufferFormatZoomTestByCamera(int format, List<Float> candidateZoomRatios,
Set<String> activePhysicalIdsSeen) throws Exception {
Size[] availableSizes = mStaticInfo.getAvailableSizesForFormatChecked(format,
StaticMetadata.StreamDirection.Output);
if (availableSizes.length == 0) {
return;
}
Set<String> physicalCameraIds = null;
if (mStaticInfo.isLogicalMultiCamera()) {
physicalCameraIds = mStaticInfo.getCharacteristics().getPhysicalCameraIds();
}
try {
mListener = new SimpleImageListener();
// Pick the largest image size:
Size maxSize = CameraTestUtils.getMaxSize(availableSizes);
createDefaultImageReader(maxSize, format, 1, mListener);
checkImageReaderSessionConfiguration(
"Camera capture session validation for format: " + format + "failed");
ArrayList<OutputConfiguration> outputConfigs = new ArrayList<>();
OutputConfiguration config = new OutputConfiguration(mReader.getSurface());
if (format == ImageFormat.YCBCR_P010) {
config.setDynamicRangeProfile(DynamicRangeProfiles.HLG10);
}
outputConfigs.add(config);
CaptureRequest.Builder requestBuilder = prepareCaptureRequestForConfigs(
outputConfigs, CameraDevice.TEMPLATE_PREVIEW);
boolean checkActivePhysicalIdConsistency =
PropertyUtil.getFirstApiLevel() >= Build.VERSION_CODES.S;
for (Float zoomRatio : candidateZoomRatios) {
if (VERBOSE) {
Log.v(TAG, "Testing format " + format + " zoomRatio " + zoomRatio +
" for camera " + mCamera.getId());
}
requestBuilder.set(CaptureRequest.CONTROL_ZOOM_RATIO, zoomRatio);
CaptureRequest request = requestBuilder.build();
SimpleCaptureCallback listener = new SimpleCaptureCallback();
startCapture(request, false /*repeating*/, listener, mHandler);
// Validate images.
mListener.waitForAnyImageAvailable(CAPTURE_WAIT_TIMEOUT_MS);
Image img = mReader.acquireNextImage();
assertNotNull("Unable to acquire the latest image", img);
CameraTestUtils.validateImage(img, maxSize.getWidth(), maxSize.getHeight(), format,
mDebugFileNameBase);
img.close();
// Validate capture result.
if (mStaticInfo.isActivePhysicalCameraIdSupported()) {
CaptureResult result = listener.getCaptureResult(CAPTURE_RESULT_TIMEOUT_MS);
String activePhysicalId = result.get(
CaptureResult.LOGICAL_MULTI_CAMERA_ACTIVE_PHYSICAL_ID);
if (checkActivePhysicalIdConsistency) {
assertNotNull("Camera " + mCamera.getId() +
" result metadata must contain ACTIVE_PHYSICAL_ID",
activePhysicalId);
assertTrue("Camera " + mCamera.getId() + " must be logical " +
"camera if activePhysicalId exists in capture result",
physicalCameraIds != null && physicalCameraIds.size() != 0);
mCollector.expectTrue("Camera " + mCamera.getId() + " activePhysicalId " +
activePhysicalId + "must be among valid physical Ids " +
physicalCameraIds.toString(),
physicalCameraIds.contains(activePhysicalId));
activePhysicalIdsSeen.add(activePhysicalId);
}
}
}
// stop capture.
stopCapture(/*fast*/false);
if (activePhysicalIdsSeen.size() > 0 && format == ImageFormat.RAW_SENSOR) {
mCollector.expectTrue("Logical camera's activePhysicalCamera should not " +
" change at different zoom levels.", activePhysicalIdsSeen.size() == 1);
}
} finally {
closeDefaultImageReader();
}
}
private final class SimpleImageListener implements ImageReader.OnImageAvailableListener {
private final ConditionVariable imageAvailable = new ConditionVariable();
@Override
public void onImageAvailable(ImageReader reader) {
imageAvailable.open();
}
public void waitForAnyImageAvailable(long timeout) {
if (imageAvailable.block(timeout)) {
imageAvailable.close();
} else {
fail("wait for image available timed out after " + timeout + "ms");
}
}
}
}