summaryrefslogtreecommitdiffstats
path: root/tests/camera2
diff options
context:
space:
mode:
authorEino-Ville Talvala <etalvala@google.com>2013-07-01 18:47:09 -0700
committerEino-Ville Talvala <etalvala@google.com>2013-07-12 10:21:21 -0700
commit7d831712b37e802b83f89eaf16d05f754bed851d (patch)
treebbed4e9fd65b105d4e90c255247db28e9a9f5a58 /tests/camera2
parent2260585d3791ab9628b2755c96ce0c254d191395 (diff)
downloadhardware_libhardware-7d831712b37e802b83f89eaf16d05f754bed851d.zip
hardware_libhardware-7d831712b37e802b83f89eaf16d05f754bed851d.tar.gz
hardware_libhardware-7d831712b37e802b83f89eaf16d05f754bed851d.tar.bz2
Camera tests: Add variable burst test
By default, this test runs through a range of application-set exposure, frame duration, and sensitivity values, and verifies that the duration of capture, and in some cases the brightness, match what's expected. Optionally, it can use environment variables to specify the burst parameters, and to enable dumping the captured YUV images to flash for later debugging. Change-Id: I647d360344ef3684e99c86c369e96ac82e62fc96
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;