summaryrefslogtreecommitdiffstats
path: root/tests/camera2
diff options
context:
space:
mode:
Diffstat (limited to 'tests/camera2')
-rw-r--r--tests/camera2/CameraBurstTests.cpp430
-rw-r--r--tests/camera2/CameraStreamFixture.h77
2 files changed, 506 insertions, 1 deletions
diff --git a/tests/camera2/CameraBurstTests.cpp b/tests/camera2/CameraBurstTests.cpp
index 5c4b6e7..b71cfd1 100644
--- a/tests/camera2/CameraBurstTests.cpp
+++ b/tests/camera2/CameraBurstTests.cpp
@@ -19,13 +19,14 @@
#define LOG_TAG "CameraBurstTest"
//#define LOG_NDEBUG 0
#include <utils/Log.h>
+#include <utils/Timers.h>
#include <cmath>
#include "CameraStreamFixture.h"
#include "TestExtensions.h"
-#define CAMERA_FRAME_TIMEOUT 1000000000 //nsecs (1 secs)
+#define CAMERA_FRAME_TIMEOUT 1000000000LL //nsecs (1 secs)
#define CAMERA_HEAP_COUNT 2 //HALBUG: 1 means registerBuffers fails
#define CAMERA_BURST_DEBUGGING 0
#define CAMERA_FRAME_BURST_COUNT 10
@@ -37,6 +38,10 @@
#define CAMERA_EXPOSURE_FORMAT CAMERA_STREAM_AUTO_CPU_FORMAT
#define CAMERA_EXPOSURE_STARTING 100000 // 1/10ms, up to 51.2ms with 10 steps
+#define USEC 1000LL // in ns
+#define MSEC 1000000LL // in ns
+#define SEC 1000000000LL // in ns
+
#if CAMERA_BURST_DEBUGGING
#define dout std::cout
#else
@@ -122,6 +127,23 @@ public:
return acc;
}
+
+ // Parses a comma-separated string list into a Vector
+ template<typename T>
+ void ParseList(const char *src, Vector<T> &list) {
+ std::istringstream s(src);
+ while (!s.eof()) {
+ char c = s.peek();
+ if (c == ',' || c == ' ') {
+ s.ignore(1, EOF);
+ continue;
+ }
+ T val;
+ s >> val;
+ list.push_back(val);
+ }
+ }
+
};
TEST_F(CameraBurstTest, ManualExposureControl) {
@@ -257,6 +279,412 @@ TEST_F(CameraBurstTest, ManualExposureControl) {
<< " times over each consecutive frame as the exposure is doubled";
}
+/**
+ * This test varies exposure time, frame duration, and sensitivity for a
+ * burst of captures. It picks values by default, but the selection can be
+ * overridden with the environment variables
+ * CAMERA2_TEST_VARIABLE_BURST_EXPOSURE_TIMES
+ * CAMERA2_TEST_VARIABLE_BURST_FRAME_DURATIONS
+ * CAMERA2_TEST_VARIABLE_BURST_SENSITIVITIES
+ * which must all be a list of comma-separated values, and each list must be
+ * the same length. In addition, if the environment variable
+ * CAMERA2_TEST_VARIABLE_BURST_DUMP_FRAMES
+ * is set to 1, then the YUV buffers are dumped into files named
+ * "camera2_test_variable_burst_frame_NNN.yuv"
+ *
+ * For example:
+ * $ setenv CAMERA2_TEST_VARIABLE_BURST_EXPOSURE_TIMES 10000000,20000000
+ * $ setenv CAMERA2_TEST_VARIABLE_BURST_FRAME_DURATIONS 40000000,40000000
+ * $ setenv CAMERA2_TEST_VARIABLE_BURST_SENSITIVITIES 200,100
+ * $ setenv CAMERA2_TEST_VARIABLE_BURST_DUMP_FRAMES 1
+ * $ /data/nativetest/camera2_test/camera2_test --gtest_filter="*VariableBurst"
+ */
+TEST_F(CameraBurstTest, VariableBurst) {
+
+ TEST_EXTENSION_FORKING_INIT;
+
+ // Bounds for checking frame duration is within range
+ const nsecs_t DURATION_UPPER_BOUND = 10 * MSEC;
+ const nsecs_t DURATION_LOWER_BOUND = 20 * MSEC;
+
+ // Threshold for considering two captures to have equivalent exposure value,
+ // as a ratio of the smaller EV to the larger EV.
+ const float EV_MATCH_BOUND = 0.95;
+ // Bound for two captures with equivalent exp values to have the same
+ // measured brightness, in 0-255 luminance.
+ const float BRIGHTNESS_MATCH_BOUND = 5;
+
+ // Environment variables to look for to override test settings
+ const char *expEnv = "CAMERA2_TEST_VARIABLE_BURST_EXPOSURE_TIMES";
+ const char *durationEnv = "CAMERA2_TEST_VARIABLE_BURST_FRAME_DURATIONS";
+ const char *sensitivityEnv = "CAMERA2_TEST_VARIABLE_BURST_SENSITIVITIES";
+ const char *dumpFrameEnv = "CAMERA2_TEST_VARIABLE_BURST_DUMP_FRAMES";
+
+ // Range of valid exposure times, in nanoseconds
+ int64_t minExp = 0, maxExp = 0;
+ // List of valid sensor sensitivities
+ Vector<int32_t> sensitivities;
+ // Range of valid frame durations, in nanoseconds
+ int64_t minDuration = 0, maxDuration = 0;
+
+ {
+ camera_metadata_ro_entry exposureTimeRange =
+ GetStaticEntry(ANDROID_SENSOR_INFO_EXPOSURE_TIME_RANGE);
+
+ EXPECT_EQ(2u, exposureTimeRange.count) << "Bad exposure time range tag."
+ "Using default values";
+ if (exposureTimeRange.count == 2) {
+ minExp = exposureTimeRange.data.i64[0];
+ maxExp = exposureTimeRange.data.i64[1];
+ }
+
+ EXPECT_LT(0, minExp) << "Minimum exposure time is 0";
+ EXPECT_LT(0, maxExp) << "Maximum exposure time is 0";
+ EXPECT_LE(minExp, maxExp) << "Minimum exposure is greater than maximum";
+
+ if (minExp == 0) {
+ minExp = 1 * MSEC; // Fallback minimum exposure time
+ }
+
+ if (maxExp == 0) {
+ maxExp = 10 * SEC; // Fallback maximum exposure time
+ }
+ }
+
+ dout << "Stream size is " << mWidth << " x " << mHeight << std::endl;
+ dout << "Valid exposure range is: " <<
+ minExp << " - " << maxExp << " ns " << std::endl;
+
+ {
+ camera_metadata_ro_entry availableSensitivities =
+ GetStaticEntry(ANDROID_SENSOR_INFO_AVAILABLE_SENSITIVITIES);
+
+ EXPECT_LT(0u, availableSensitivities.count) << "No sensitivities listed."
+ "Falling back to default set.";
+ sensitivities.appendArray(availableSensitivities.data.i32,
+ availableSensitivities.count);
+ if (availableSensitivities.count == 0) {
+ sensitivities.push_back(100);
+ sensitivities.push_back(200);
+ sensitivities.push_back(400);
+ sensitivities.push_back(800);
+ }
+ }
+
+ dout << "Available sensitivities: ";
+ for (size_t i = 0; i < sensitivities.size(); i++) {
+ dout << sensitivities[i] << " ";
+ }
+ dout << std::endl;
+
+ {
+ camera_metadata_ro_entry availableProcessedSizes =
+ GetStaticEntry(ANDROID_SCALER_AVAILABLE_PROCESSED_SIZES);
+
+ camera_metadata_ro_entry availableProcessedMinFrameDurations =
+ GetStaticEntry(ANDROID_SCALER_AVAILABLE_PROCESSED_MIN_DURATIONS);
+
+ EXPECT_EQ(availableProcessedSizes.count,
+ availableProcessedMinFrameDurations.count * 2) <<
+ "The number of minimum frame durations doesn't match the number of "
+ "available sizes. Using fallback values";
+
+ if (availableProcessedSizes.count ==
+ availableProcessedMinFrameDurations.count * 2) {
+ bool gotSize = false;
+ for (size_t i = 0; i < availableProcessedSizes.count; i += 2) {
+ if (availableProcessedSizes.data.i32[i] == mWidth &&
+ availableProcessedSizes.data.i32[i+1] == mHeight) {
+ gotSize = true;
+ minDuration = availableProcessedMinFrameDurations.data.i64[i/2];
+ }
+ }
+ EXPECT_TRUE(gotSize) << "Can't find stream size in list of "
+ "available sizes: " << mWidth << ", " << mHeight;
+ }
+ if (minDuration == 0) {
+ minDuration = 1 * SEC / 30; // Fall back to 30 fps as minimum duration
+ }
+
+ ASSERT_LT(0, minDuration);
+
+ camera_metadata_ro_entry maxFrameDuration =
+ GetStaticEntry(ANDROID_SENSOR_INFO_MAX_FRAME_DURATION);
+
+ EXPECT_EQ(1u, maxFrameDuration.count) << "No valid maximum frame duration";
+
+ if (maxFrameDuration.count == 1) {
+ maxDuration = maxFrameDuration.data.i64[0];
+ }
+
+ EXPECT_GT(0, maxDuration) << "Max duration is 0 or not given, using fallback";
+
+ if (maxDuration == 0) {
+ maxDuration = 10 * SEC; // Fall back to 10 seconds as max duration
+ }
+
+ }
+ dout << "Available frame duration range for configured stream size: "
+ << minDuration << " - " << maxDuration << " ns" << std::endl;
+
+ // Get environment variables if set
+ const char *expVal = getenv(expEnv);
+ const char *durationVal = getenv(durationEnv);
+ const char *sensitivityVal = getenv(sensitivityEnv);
+
+ bool gotExp = (expVal != NULL);
+ bool gotDuration = (durationVal != NULL);
+ bool gotSensitivity = (sensitivityVal != NULL);
+
+ // All or none must be provided if using override envs
+ ASSERT_TRUE( (gotDuration && gotExp && gotSensitivity) ||
+ (!gotDuration && !gotExp && !gotSensitivity) ) <<
+ "Incomplete set of environment variable overrides provided";
+
+ Vector<int64_t> expList, durationList;
+ Vector<int32_t> sensitivityList;
+ if (gotExp) {
+ ParseList(expVal, expList);
+ ParseList(durationVal, durationList);
+ ParseList(sensitivityVal, sensitivityList);
+
+ ASSERT_TRUE(
+ (expList.size() == durationList.size()) &&
+ (durationList.size() == sensitivityList.size())) <<
+ "Mismatched sizes in env lists, or parse error";
+
+ dout << "Using burst list from environment with " << expList.size() <<
+ " captures" << std::endl;
+ } else {
+ // Create a default set of controls based on the available ranges
+
+ int64_t e;
+ int64_t d;
+ int32_t s;
+
+ // Exposure ramp
+
+ e = minExp;
+ d = minDuration;
+ s = sensitivities[0];
+ while (e < maxExp) {
+ expList.push_back(e);
+ durationList.push_back(d);
+ sensitivityList.push_back(s);
+ e = e * 2;
+ }
+ e = maxExp;
+ expList.push_back(e);
+ durationList.push_back(d);
+ sensitivityList.push_back(s);
+
+ // Duration ramp
+
+ e = 30 * MSEC;
+ d = minDuration;
+ s = sensitivities[0];
+ while (d < maxDuration) {
+ // make sure exposure <= frame duration
+ expList.push_back(e > d ? d : e);
+ durationList.push_back(d);
+ sensitivityList.push_back(s);
+ d = d * 2;
+ }
+
+ // Sensitivity ramp
+
+ e = 30 * MSEC;
+ d = 30 * MSEC;
+ d = d > minDuration ? d : minDuration;
+ for (size_t i = 0; i < sensitivities.size(); i++) {
+ expList.push_back(e);
+ durationList.push_back(d);
+ sensitivityList.push_back(sensitivities[i]);
+ }
+
+ // Constant-EV ramp, duration == exposure
+
+ e = 30 * MSEC; // at ISO 100
+ for (size_t i = 0; i < sensitivities.size(); i++) {
+ int64_t e_adj = e * 100 / sensitivities[i];
+ expList.push_back(e_adj);
+ durationList.push_back(e_adj > minDuration ? e_adj : minDuration);
+ sensitivityList.push_back(sensitivities[i]);
+ }
+
+ dout << "Default burst sequence created with " << expList.size() <<
+ " entries" << std::endl;
+ }
+
+ // Validate the list, but warn only
+ for (size_t i = 0; i < expList.size(); i++) {
+ EXPECT_GE(maxExp, expList[i])
+ << "Capture " << i << " exposure too long: " << expList[i];
+ EXPECT_LE(minExp, expList[i])
+ << "Capture " << i << " exposure too short: " << expList[i];
+ EXPECT_GE(maxDuration, durationList[i])
+ << "Capture " << i << " duration too long: " << durationList[i];
+ EXPECT_LE(minDuration, durationList[i])
+ << "Capture " << i << " duration too short: " << durationList[i];
+ bool validSensitivity = false;
+ for (size_t j = 0; j < sensitivities.size(); j++) {
+ if (sensitivityList[i] == sensitivities[j]) {
+ validSensitivity = true;
+ break;
+ }
+ }
+ EXPECT_TRUE(validSensitivity)
+ << "Capture " << i << " sensitivity not in list: " << sensitivityList[i];
+ }
+
+ // Check if debug yuv dumps are requested
+
+ bool dumpFrames = false;
+ {
+ const char *frameDumpVal = getenv(dumpFrameEnv);
+ if (frameDumpVal != NULL) {
+ if (frameDumpVal[0] == '1') dumpFrames = true;
+ }
+ }
+
+ dout << "Dumping YUV frames " <<
+ (dumpFrames ? "enabled, not checking timing" : "disabled") << std::endl;
+
+ // Create a base preview request, turning off all 3A
+ CameraMetadata previewRequest;
+ ASSERT_EQ(OK, mDevice->createDefaultRequest(CAMERA2_TEMPLATE_PREVIEW,
+ &previewRequest));
+ {
+ Vector<uint8_t> outputStreamIds;
+ outputStreamIds.push(mStreamId);
+ ASSERT_EQ(OK, previewRequest.update(ANDROID_REQUEST_OUTPUT_STREAMS,
+ outputStreamIds));
+
+ // Disable all 3A routines
+ uint8_t cmOff = static_cast<uint8_t>(ANDROID_CONTROL_MODE_OFF);
+ ASSERT_EQ(OK, previewRequest.update(ANDROID_CONTROL_MODE,
+ &cmOff, 1));
+
+ int requestId = 1;
+ ASSERT_EQ(OK, previewRequest.update(ANDROID_REQUEST_ID,
+ &requestId, 1));
+ }
+
+ // Submit capture requests
+
+ for (size_t i = 0; i < expList.size(); ++i) {
+ CameraMetadata tmpRequest = previewRequest;
+ ASSERT_EQ(OK, tmpRequest.update(ANDROID_SENSOR_EXPOSURE_TIME,
+ &expList[i], 1));
+ ASSERT_EQ(OK, tmpRequest.update(ANDROID_SENSOR_FRAME_DURATION,
+ &durationList[i], 1));
+ ASSERT_EQ(OK, tmpRequest.update(ANDROID_SENSOR_SENSITIVITY,
+ &sensitivityList[i], 1));
+ ALOGV("Submitting capture %d with exposure %lld, frame duration %lld, sensitivity %d",
+ i, expList[i], durationList[i], sensitivityList[i]);
+ dout << "Capture request " << i <<
+ ": exposure is " << (expList[i]/1e6f) << " ms" <<
+ ", frame duration is " << (durationList[i]/1e6f) << " ms" <<
+ ", sensitivity is " << sensitivityList[i] <<
+ std::endl;
+ ASSERT_EQ(OK, mDevice->capture(tmpRequest));
+ }
+
+ Vector<float> brightnesses;
+ Vector<nsecs_t> captureTimes;
+ brightnesses.setCapacity(expList.size());
+ captureTimes.setCapacity(expList.size());
+
+ // Get each frame (metadata) and then the buffer. Calculate brightness.
+ for (size_t i = 0; i < expList.size(); ++i) {
+
+ ALOGV("Reading request %d", i);
+ dout << "Waiting for capture " << i << ": " <<
+ " exposure " << (expList[i]/1e6f) << " ms," <<
+ " frame duration " << (durationList[i]/1e6f) << " ms," <<
+ " sensitivity " << sensitivityList[i] <<
+ std::endl;
+
+ // Set wait limit based on expected frame duration, or minimum timeout
+ int64_t waitLimit = CAMERA_FRAME_TIMEOUT;
+ if (expList[i] * 2 > waitLimit) waitLimit = expList[i] * 2;
+ if (durationList[i] * 2 > waitLimit) waitLimit = durationList[i] * 2;
+
+ ASSERT_EQ(OK, mDevice->waitForNextFrame(waitLimit));
+ ALOGV("Reading capture request-1 %d", i);
+ CameraMetadata frameMetadata;
+ ASSERT_EQ(OK, mDevice->getNextFrame(&frameMetadata));
+ ALOGV("Reading capture request-2 %d", i);
+
+ ASSERT_EQ(OK, mFrameListener->waitForFrame(CAMERA_FRAME_TIMEOUT));
+ ALOGV("We got the frame now");
+
+ captureTimes.push_back(systemTime());
+
+ CpuConsumer::LockedBuffer imgBuffer;
+ ASSERT_EQ(OK, mCpuConsumer->lockNextBuffer(&imgBuffer));
+
+ int underexposed, overexposed;
+ float avgBrightness = 0;
+ long long brightness = TotalBrightness(imgBuffer, &underexposed,
+ &overexposed);
+ int numValidPixels = mWidth * mHeight - (underexposed + overexposed);
+ if (numValidPixels != 0) {
+ avgBrightness = brightness * 1.0f / numValidPixels;
+ } else if (underexposed < overexposed) {
+ avgBrightness = 255;
+ }
+
+ ALOGV("Total brightness for frame %d was %lld (underexposed %d, "
+ "overexposed %d), avg %f", i, brightness, underexposed,
+ overexposed, avgBrightness);
+ dout << "Average brightness (frame " << i << ") was " << avgBrightness
+ << " (underexposed " << underexposed << ", overexposed "
+ << overexposed << ")" << std::endl;
+ brightnesses.push_back(avgBrightness);
+
+ if (i != 0) {
+ float prevEv = static_cast<float>(expList[i - 1]) * sensitivityList[i - 1];
+ float currentEv = static_cast<float>(expList[i]) * sensitivityList[i];
+ float evRatio = (prevEv > currentEv) ? (currentEv / prevEv) :
+ (prevEv / currentEv);
+ if ( evRatio > EV_MATCH_BOUND ) {
+ EXPECT_LT( fabs(brightnesses[i] - brightnesses[i - 1]),
+ BRIGHTNESS_MATCH_BOUND) <<
+ "Capture brightness different from previous, even though "
+ "they have the same EV value. Ev now: " << currentEv <<
+ ", previous: " << prevEv << ". Brightness now: " <<
+ brightnesses[i] << ", previous: " << brightnesses[i-1];
+ }
+ // Only check timing if not saving to disk, since that slows things
+ // down substantially
+ if (!dumpFrames) {
+ nsecs_t timeDelta = captureTimes[i] - captureTimes[i-1];
+ nsecs_t expectedDelta = expList[i] > durationList[i] ?
+ expList[i] : durationList[i];
+ EXPECT_LT(timeDelta, expectedDelta + DURATION_UPPER_BOUND) <<
+ "Capture took " << timeDelta << " ns to receive, but expected"
+ " frame duration was " << expectedDelta << " ns.";
+ EXPECT_GT(timeDelta, expectedDelta - DURATION_LOWER_BOUND) <<
+ "Capture took " << timeDelta << " ns to receive, but expected"
+ " frame duration was " << expectedDelta << " ns.";
+ dout << "Time delta from previous frame: " << timeDelta / 1e6 <<
+ " ms. Expected " << expectedDelta / 1e6 << " ms" << std::endl;
+ }
+ }
+
+ if (dumpFrames) {
+ String8 dumpName =
+ String8::format("/data/local/tmp/camera2_test_variable_burst_frame_%03d.yuv", i);
+ dout << " Writing YUV dump to " << dumpName << std::endl;
+ DumpYuvToFile(dumpName, imgBuffer);
+ }
+
+ ASSERT_EQ(OK, mCpuConsumer->unlockBuffer(imgBuffer));
+ }
+
+}
+
}
}
}
diff --git a/tests/camera2/CameraStreamFixture.h b/tests/camera2/CameraStreamFixture.h
index a4dc4a8..3d614db 100644
--- a/tests/camera2/CameraStreamFixture.h
+++ b/tests/camera2/CameraStreamFixture.h
@@ -19,6 +19,7 @@
#include <gtest/gtest.h>
#include <iostream>
+#include <fstream>
#include <gui/CpuConsumer.h>
#include <gui/Surface.h>
@@ -29,6 +30,8 @@
#include "CameraModuleFixture.h"
#include "TestExtensions.h"
+#define ALIGN(x, mask) ( ((x) + (mask) - 1) & ~((mask) - 1) )
+
namespace android {
namespace camera2 {
namespace tests {
@@ -194,6 +197,80 @@ protected:
return format;
}
+ void DumpYuvToFile(const String8 &fileName, const CpuConsumer::LockedBuffer &img) {
+ uint8_t *dataCb, *dataCr;
+ uint32_t stride;
+ uint32_t chromaStride;
+ uint32_t chromaStep;
+
+ switch (img.format) {
+ case HAL_PIXEL_FORMAT_YCbCr_420_888:
+ stride = img.stride;
+ chromaStride = img.chromaStride;
+ chromaStep = img.chromaStep;
+ dataCb = img.dataCb;
+ dataCr = img.dataCr;
+ break;
+ case HAL_PIXEL_FORMAT_YCrCb_420_SP:
+ stride = img.width;
+ chromaStride = img.width;
+ chromaStep = 2;
+ dataCr = img.data + img.width * img.height;
+ dataCb = dataCr + 1;
+ break;
+ case HAL_PIXEL_FORMAT_YV12:
+ stride = img.stride;
+ chromaStride = ALIGN(img.width / 2, 16);
+ chromaStep = 1;
+ dataCr = img.data + img.stride * img.height;
+ dataCb = dataCr + chromaStride * img.height/2;
+ break;
+ default:
+ ALOGE("Unknown format %d, not dumping", img.format);
+ return;
+ }
+
+ // Write Y
+ FILE *yuvFile = fopen(fileName.string(), "w");
+
+ size_t bytes;
+
+ for (size_t y = 0; y < img.height; ++y) {
+ bytes = fwrite(
+ reinterpret_cast<const char*>(img.data + stride * y),
+ 1, img.width, yuvFile);
+ if (bytes != img.width) {
+ ALOGE("Unable to write to file %s", fileName.string());
+ fclose(yuvFile);
+ return;
+ }
+ }
+
+ // Write Cb/Cr
+ uint8_t *src = dataCb;
+ for (int c = 0; c < 2; ++c) {
+ for (size_t y = 0; y < img.height / 2; ++y) {
+ uint8_t *px = src + y * chromaStride;
+ if (chromaStep != 1) {
+ for (size_t x = 0; x < img.width / 2; ++x) {
+ fputc(*px, yuvFile);
+ px += chromaStep;
+ }
+ } else {
+ bytes = fwrite(reinterpret_cast<const char*>(px),
+ 1, img.width / 2, yuvFile);
+ if (bytes != img.width / 2) {
+ ALOGE("Unable to write to file %s", fileName.string());
+ fclose(yuvFile);
+ return;
+ }
+ }
+ }
+ src = dataCr;
+ }
+ fclose(yuvFile);
+ }
+
int mWidth;
int mHeight;