diff options
Diffstat (limited to 'tests/camera2')
-rw-r--r-- | tests/camera2/CameraBurstTests.cpp | 430 | ||||
-rw-r--r-- | tests/camera2/CameraStreamFixture.h | 77 |
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 ee30dcc..195e764 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 { @@ -195,6 +198,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; |