/* * Copyright (C) 2013 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. */ #define LOG_TAG "CameraMultiStreamTest" //#define LOG_NDEBUG 0 #include "CameraStreamFixture.h" #include "TestExtensions.h" #include <gtest/gtest.h> #include <utils/Log.h> #include <utils/StrongPointer.h> #include <common/CameraDeviceBase.h> #include <hardware/hardware.h> #include <hardware/camera2.h> #include <gui/SurfaceComposerClient.h> #include <gui/Surface.h> #define DEFAULT_FRAME_DURATION 33000000LL // 33ms #define CAMERA_HEAP_COUNT 1 #define CAMERA_EXPOSURE_FORMAT CAMERA_STREAM_AUTO_CPU_FORMAT #define CAMERA_DISPLAY_FORMAT HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED #define CAMERA_MULTI_STREAM_DEBUGGING 0 #define CAMERA_FRAME_TIMEOUT 1000000000LL // nsecs (1 secs) #define PREVIEW_RENDERING_TIME_INTERVAL 200000 // in unit of us, 200ms #define TOLERANCE_MARGIN 0.01 // 1% tolerance margin for exposure sanity check. /* constants for display */ #define DISPLAY_BUFFER_HEIGHT 1024 #define DISPLAY_BUFFER_WIDTH 1024 #define DISPLAY_BUFFER_FORMAT PIXEL_FORMAT_RGB_888 // This test intends to test large preview size but less than 1080p. #define PREVIEW_WIDTH_CAP 1920 #define PREVIEW_HEIGHT_CAP 1080 // This test intends to test small metering burst size that is less than 640x480 #define METERING_WIDTH_CAP 640 #define METERING_HEIGHT_CAP 480 #define EXP_WAIT_MULTIPLIER 2 namespace android { namespace camera2 { namespace tests { static const CameraStreamParams DEFAULT_STREAM_PARAMETERS = { /*mFormat*/ CAMERA_EXPOSURE_FORMAT, /*mHeapCount*/ CAMERA_HEAP_COUNT }; static const CameraStreamParams DISPLAY_STREAM_PARAMETERS = { /*mFormat*/ CAMERA_DISPLAY_FORMAT, /*mHeapCount*/ CAMERA_HEAP_COUNT }; class CameraMultiStreamTest : public ::testing::Test, public CameraStreamFixture { public: CameraMultiStreamTest() : CameraStreamFixture(DEFAULT_STREAM_PARAMETERS) { TEST_EXTENSION_FORKING_CONSTRUCTOR; if (HasFatalFailure()) { return; } /** * Don't create default stream, each test is in charge of creating * its own streams. */ } ~CameraMultiStreamTest() { TEST_EXTENSION_FORKING_DESTRUCTOR; } sp<SurfaceComposerClient> mComposerClient; sp<SurfaceControl> mSurfaceControl; void CreateOnScreenSurface(sp<ANativeWindow>& surface) { mComposerClient = new SurfaceComposerClient; ASSERT_EQ(NO_ERROR, mComposerClient->initCheck()); mSurfaceControl = mComposerClient->createSurface( String8("CameraMultiStreamTest StreamingImage Surface"), DISPLAY_BUFFER_HEIGHT, DISPLAY_BUFFER_WIDTH, DISPLAY_BUFFER_FORMAT, 0); ASSERT_NE((void*)NULL, mSurfaceControl.get()); ASSERT_TRUE(mSurfaceControl->isValid()); SurfaceComposerClient::openGlobalTransaction(); ASSERT_EQ(NO_ERROR, mSurfaceControl->setLayer(0x7FFFFFFF)); ASSERT_EQ(NO_ERROR, mSurfaceControl->show()); SurfaceComposerClient::closeGlobalTransaction(); surface = mSurfaceControl->getSurface(); ASSERT_NE((void*)NULL, surface.get()); } struct Size { int32_t width; int32_t height; }; // Select minimal size by number of pixels. void GetMinSize(const int32_t* data, size_t count, Size* min, int32_t* idx) { ASSERT_NE((int32_t*)NULL, data); int32_t minIdx = 0; int32_t minSize = INT_MAX, tempSize; for (size_t i = 0; i < count; i+=2) { tempSize = data[i] * data[i+1]; if (minSize > tempSize) { minSize = tempSize; minIdx = i; } } min->width = data[minIdx]; min->height = data[minIdx + 1]; *idx = minIdx; } // Select maximal size by number of pixels. void GetMaxSize(const int32_t* data, size_t count, Size* max, int32_t* idx) { ASSERT_NE((int32_t*)NULL, data); int32_t maxIdx = 0; int32_t maxSize = INT_MIN, tempSize; for (size_t i = 0; i < count; i+=2) { tempSize = data[i] * data[i+1]; if (maxSize < tempSize) { maxSize = tempSize; maxIdx = i; } } max->width = data[maxIdx]; max->height = data[maxIdx + 1]; *idx = maxIdx; } // Cap size by number of pixels. Size CapSize(Size cap, Size input) { if (input.width * input.height > cap.width * cap.height) { return cap; } return input; } struct CameraStream : public RefBase { public: /** * Only initialize the variables here, do the ASSERT check in * SetUp function. To make this stream useful, the SetUp must * be called before using it. */ CameraStream( int width, int height, const sp<CameraDeviceBase>& device, CameraStreamParams param, sp<ANativeWindow> surface, bool useCpuConsumer) : mDevice(device), mWidth(width), mHeight(height) { mFormat = param.mFormat; if (useCpuConsumer) { sp<BufferQueue> bq = new BufferQueue(); mCpuConsumer = new CpuConsumer(bq, param.mHeapCount); mCpuConsumer->setName(String8( "CameraMultiStreamTest::mCpuConsumer")); mNativeWindow = new Surface(bq); } else { // Render the stream to screen. mCpuConsumer = NULL; mNativeWindow = surface; } mFrameListener = new FrameListener(); if (mCpuConsumer != 0) { mCpuConsumer->setFrameAvailableListener(mFrameListener); } } /** * Finally create camera stream, and do the ASSERT check, since we * can not do it in ctor. */ void SetUp() { ASSERT_EQ(OK, mDevice->createStream(mNativeWindow, mWidth, mHeight, mFormat, /*size (for jpegs)*/0, &mStreamId)); ASSERT_NE(-1, mStreamId); } int GetStreamId() { return mStreamId; } sp<CpuConsumer> GetConsumer() { return mCpuConsumer; } sp<FrameListener> GetFrameListener() { return mFrameListener; } protected: ~CameraStream() { if (mDevice.get()) { mDevice->waitUntilDrained(); mDevice->deleteStream(mStreamId); } // Clear producer before consumer. mNativeWindow.clear(); mCpuConsumer.clear(); } private: sp<FrameListener> mFrameListener; sp<CpuConsumer> mCpuConsumer; sp<ANativeWindow> mNativeWindow; sp<CameraDeviceBase> mDevice; int mStreamId; int mWidth; int mHeight; int mFormat; }; int64_t GetExposureValue(const CameraMetadata& metaData) { camera_metadata_ro_entry_t entry = metaData.find(ANDROID_SENSOR_EXPOSURE_TIME); EXPECT_EQ(1u, entry.count); if (entry.count == 1) { return entry.data.i64[0]; } return -1; } int32_t GetSensitivity(const CameraMetadata& metaData) { camera_metadata_ro_entry_t entry = metaData.find(ANDROID_SENSOR_SENSITIVITY); EXPECT_EQ(1u, entry.count); if (entry.count == 1) { return entry.data.i32[0]; } return -1; } int64_t GetFrameDuration(const CameraMetadata& metaData) { camera_metadata_ro_entry_t entry = metaData.find(ANDROID_SENSOR_FRAME_DURATION); EXPECT_EQ(1u, entry.count); if (entry.count == 1) { return entry.data.i64[0]; } return -1; } void CreateRequests(CameraMetadata& previewRequest, CameraMetadata& meteringRequest, CameraMetadata& captureRequest, int previewStreamId, int meteringStreamId, int captureStreamId) { int32_t requestId = 0; Vector<int32_t> previewStreamIds; previewStreamIds.push(previewStreamId); ASSERT_EQ(OK, mDevice->createDefaultRequest(CAMERA2_TEMPLATE_PREVIEW, &previewRequest)); ASSERT_EQ(OK, previewRequest.update(ANDROID_REQUEST_OUTPUT_STREAMS, previewStreamIds)); ASSERT_EQ(OK, previewRequest.update(ANDROID_REQUEST_ID, &requestId, 1)); // Create metering request, manual settings // Manual control: Disable 3A, noise reduction, edge sharping uint8_t cmOff = static_cast<uint8_t>(ANDROID_CONTROL_MODE_OFF); uint8_t nrOff = static_cast<uint8_t>(ANDROID_NOISE_REDUCTION_MODE_OFF); uint8_t sharpOff = static_cast<uint8_t>(ANDROID_EDGE_MODE_OFF); Vector<int32_t> meteringStreamIds; meteringStreamIds.push(meteringStreamId); ASSERT_EQ(OK, mDevice->createDefaultRequest( CAMERA2_TEMPLATE_PREVIEW, &meteringRequest)); ASSERT_EQ(OK, meteringRequest.update( ANDROID_REQUEST_OUTPUT_STREAMS, meteringStreamIds)); ASSERT_EQ(OK, meteringRequest.update( ANDROID_CONTROL_MODE, &cmOff, 1)); ASSERT_EQ(OK, meteringRequest.update( ANDROID_NOISE_REDUCTION_MODE, &nrOff, 1)); ASSERT_EQ(OK, meteringRequest.update( ANDROID_EDGE_MODE, &sharpOff, 1)); // Create capture request, manual settings Vector<int32_t> captureStreamIds; captureStreamIds.push(captureStreamId); ASSERT_EQ(OK, mDevice->createDefaultRequest( CAMERA2_TEMPLATE_PREVIEW, &captureRequest)); ASSERT_EQ(OK, captureRequest.update( ANDROID_REQUEST_OUTPUT_STREAMS, captureStreamIds)); ASSERT_EQ(OK, captureRequest.update( ANDROID_CONTROL_MODE, &cmOff, 1)); ASSERT_EQ(OK, captureRequest.update( ANDROID_NOISE_REDUCTION_MODE, &nrOff, 1)); ASSERT_EQ(OK, captureRequest.update( ANDROID_EDGE_MODE, &sharpOff, 1)); } sp<CameraStream> CreateStream( int width, int height, const sp<CameraDeviceBase>& device, CameraStreamParams param = DEFAULT_STREAM_PARAMETERS, sp<ANativeWindow> surface = NULL, bool useCpuConsumer = true) { param.mFormat = MapAutoFormat(param.mFormat); return new CameraStream(width, height, device, param, surface, useCpuConsumer); } void CaptureBurst(CameraMetadata& request, size_t requestCount, const Vector<int64_t>& exposures, const Vector<int32_t>& sensitivities, const sp<CameraStream>& stream, int64_t minFrameDuration, int32_t* requestIdStart) { ASSERT_EQ(OK, request.update(ANDROID_SENSOR_FRAME_DURATION, &minFrameDuration, 1)); // Submit a series of requests with the specified exposure/gain values. int32_t targetRequestId = *requestIdStart; for (size_t i = 0; i < requestCount; i++) { ASSERT_EQ(OK, request.update(ANDROID_REQUEST_ID, requestIdStart, 1)); ASSERT_EQ(OK, request.update(ANDROID_SENSOR_EXPOSURE_TIME, &exposures[i], 1)); ASSERT_EQ(OK, request.update(ANDROID_SENSOR_SENSITIVITY, &sensitivities[i], 1)); ASSERT_EQ(OK, mDevice->capture(request)); ALOGV("Submitting request with: id %d with exposure %lld, sensitivity %d", *requestIdStart, exposures[i], sensitivities[i]); if (CAMERA_MULTI_STREAM_DEBUGGING) { request.dump(STDOUT_FILENO); } (*requestIdStart)++; } // Get capture burst results. Vector<nsecs_t> captureBurstTimes; sp<CpuConsumer> consumer = stream->GetConsumer(); sp<FrameListener> listener = stream->GetFrameListener(); // Set wait limit based on expected frame duration. int64_t waitLimit = CAMERA_FRAME_TIMEOUT; for (size_t i = 0; i < requestCount; i++) { ALOGV("Reading request result %d", i); /** * Raise the timeout to be at least twice as long as the exposure * time. to avoid a false positive when the timeout is too short. */ if ((exposures[i] * EXP_WAIT_MULTIPLIER) > waitLimit) { waitLimit = exposures[i] * EXP_WAIT_MULTIPLIER; } CameraMetadata frameMetadata; int32_t resultRequestId; do { ASSERT_EQ(OK, mDevice->waitForNextFrame(waitLimit)); ASSERT_EQ(OK, mDevice->getNextFrame(&frameMetadata)); camera_metadata_entry_t resultEntry = frameMetadata.find(ANDROID_REQUEST_ID); ASSERT_EQ(1u, resultEntry.count); resultRequestId = resultEntry.data.i32[0]; if (CAMERA_MULTI_STREAM_DEBUGGING) { std::cout << "capture result req id: " << resultRequestId << std::endl; } } while (resultRequestId != targetRequestId); targetRequestId++; ALOGV("Got capture burst result for request %d", i); // Validate capture result if (CAMERA_MULTI_STREAM_DEBUGGING) { frameMetadata.dump(STDOUT_FILENO); } // TODO: Need revisit it to figure out an accurate margin. int64_t resultExposure = GetExposureValue(frameMetadata); int32_t resultSensitivity = GetSensitivity(frameMetadata); EXPECT_LE(sensitivities[i] * (1.0 - TOLERANCE_MARGIN), resultSensitivity); EXPECT_GE(sensitivities[i] * (1.0 + TOLERANCE_MARGIN), resultSensitivity); EXPECT_LE(exposures[i] * (1.0 - TOLERANCE_MARGIN), resultExposure); EXPECT_GE(exposures[i] * (1.0 + TOLERANCE_MARGIN), resultExposure); ASSERT_EQ(OK, listener->waitForFrame(waitLimit)); captureBurstTimes.push_back(systemTime()); CpuConsumer::LockedBuffer imgBuffer; ASSERT_EQ(OK, consumer->lockNextBuffer(&imgBuffer)); ALOGV("Got capture buffer for request %d", i); /** * TODO: Validate capture buffer. Current brightness calculation * is too slow, it also doesn't account for saturation effects, * which is quite common since we are going over a significant * range of EVs. we need figure out some reliable way to validate * buffer data. */ ASSERT_EQ(OK, consumer->unlockBuffer(imgBuffer)); if (i > 0) { nsecs_t timeDelta = captureBurstTimes[i] - captureBurstTimes[i-1]; EXPECT_GE(timeDelta, exposures[i]); } } } /** * Intentionally shadow default CreateStream function from base class, * because we don't want any test in this class to use the default * stream creation function. */ void CreateStream() { } }; /** * This test adds multiple stream use case test, basically, test 3 * streams: * * 1. Preview stream, with large size that is no bigger than 1080p * we render this stream to display and vary the exposure time for * for certain amount of time for visualization purpose. * * 2. Metering stream, with small size that is no bigger than VGA size. * a burst is issued for different exposure times and analog gains * (or analog gain implemented sensitivities) then check if the capture * result metadata matches the request. * * 3. Capture stream, this is basically similar as meterting stream, but * has large size, which is the largest supported JPEG capture size. * * This multiple stream test is to test if HAL supports: * * 1. Multiple streams like above, HAL should support at least 3 streams * concurrently: one preview stream, 2 other YUV stream. * * 2. Manual control(gain/exposure) of mutiple burst capture. */ TEST_F(CameraMultiStreamTest, MultiBurst) { TEST_EXTENSION_FORKING_INIT; camera_metadata_ro_entry availableProcessedSizes = GetStaticEntry(ANDROID_SCALER_AVAILABLE_PROCESSED_SIZES); ASSERT_EQ(0u, availableProcessedSizes.count % 2); ASSERT_GE(availableProcessedSizes.count, 2u); camera_metadata_ro_entry availableProcessedMinFrameDurations = GetStaticEntry(ANDROID_SCALER_AVAILABLE_PROCESSED_MIN_DURATIONS); EXPECT_EQ(availableProcessedSizes.count, availableProcessedMinFrameDurations.count * 2); camera_metadata_ro_entry availableJpegSizes = GetStaticEntry(ANDROID_SCALER_AVAILABLE_JPEG_SIZES); ASSERT_EQ(0u, availableJpegSizes.count % 2); ASSERT_GE(availableJpegSizes.count, 2u); camera_metadata_ro_entry hardwareLevel = GetStaticEntry(ANDROID_INFO_SUPPORTED_HARDWARE_LEVEL); ASSERT_EQ(1u, hardwareLevel.count); uint8_t level = hardwareLevel.data.u8[0]; ASSERT_GE(level, ANDROID_INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED); ASSERT_LE(level, ANDROID_INFO_SUPPORTED_HARDWARE_LEVEL_FULL); if (level == ANDROID_INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED) { const ::testing::TestInfo* const test_info = ::testing::UnitTest::GetInstance()->current_test_info(); std::cerr << "Skipping test " << test_info->test_case_name() << "." << test_info->name() << " because HAL hardware supported level is limited " << std::endl; return; } // Find the right sizes for preview, metering, and capture streams // assumes at least 2 entries in availableProcessedSizes. int64_t minFrameDuration = DEFAULT_FRAME_DURATION; Size processedMinSize, processedMaxSize, jpegMaxSize; const int32_t* data = availableProcessedSizes.data.i32; size_t count = availableProcessedSizes.count; int32_t minIdx, maxIdx; GetMinSize(data, count, &processedMinSize, &minIdx); GetMaxSize(data, count, &processedMaxSize, &maxIdx); ALOGV("Found processed max size: %dx%d, min size = %dx%d", processedMaxSize.width, processedMaxSize.height, processedMinSize.width, processedMinSize.height); if (availableProcessedSizes.count == availableProcessedMinFrameDurations.count * 2) { minFrameDuration = availableProcessedMinFrameDurations.data.i64[maxIdx / 2]; } EXPECT_GT(minFrameDuration, 0); if (minFrameDuration <= 0) { minFrameDuration = DEFAULT_FRAME_DURATION; } ALOGV("targeted minimal frame duration is: %lldns", minFrameDuration); data = &(availableJpegSizes.data.i32[0]); count = availableJpegSizes.count; GetMaxSize(data, count, &jpegMaxSize, &maxIdx); ALOGV("Found Jpeg size max idx = %d", maxIdx); // Max Jpeg size should be available in processed sizes. Use it for // YUV capture anyway. EXPECT_EQ(processedMaxSize.width, jpegMaxSize.width); EXPECT_EQ(processedMaxSize.height, jpegMaxSize.height); // Cap preview size. Size previewLimit = { PREVIEW_WIDTH_CAP, PREVIEW_HEIGHT_CAP }; // FIXME: need make sure the previewLimit is supported by HAL. Size previewSize = CapSize(previewLimit, processedMaxSize); // Cap Metering size. Size meteringLimit = { METERING_WIDTH_CAP, METERING_HEIGHT_CAP }; // Cap metering size to VGA (VGA is mandatory by CDD) Size meteringSize = CapSize(meteringLimit, processedMinSize); // Capture stream should be the max size of jpeg sizes. ALOGV("preview size: %dx%d, metering size: %dx%d, capture size: %dx%d", previewSize.width, previewSize.height, meteringSize.width, meteringSize.height, jpegMaxSize.width, jpegMaxSize.height); // Create streams // Preview stream: small resolution, render on the screen. sp<CameraStream> previewStream; { sp<ANativeWindow> surface; ASSERT_NO_FATAL_FAILURE(CreateOnScreenSurface(/*out*/surface)); previewStream = CreateStream( previewSize.width, previewSize.height, mDevice, DISPLAY_STREAM_PARAMETERS, surface, false); ASSERT_NE((void*)NULL, previewStream.get()); ASSERT_NO_FATAL_FAILURE(previewStream->SetUp()); } // Metering burst stream: small resolution yuv stream sp<CameraStream> meteringStream = CreateStream( meteringSize.width, meteringSize.height, mDevice); ASSERT_NE((void*)NULL, meteringStream.get()); ASSERT_NO_FATAL_FAILURE(meteringStream->SetUp()); // Capture burst stream: full resolution yuv stream sp<CameraStream> captureStream = CreateStream( jpegMaxSize.width, jpegMaxSize.height, mDevice); ASSERT_NE((void*)NULL, captureStream.get()); ASSERT_NO_FATAL_FAILURE(captureStream->SetUp()); // Create Preview request. CameraMetadata previewRequest, meteringRequest, captureRequest; ASSERT_NO_FATAL_FAILURE(CreateRequests(previewRequest, meteringRequest, captureRequest, previewStream->GetStreamId(), meteringStream->GetStreamId(), captureStream->GetStreamId())); // Start preview if (CAMERA_MULTI_STREAM_DEBUGGING) { previewRequest.dump(STDOUT_FILENO); } // Generate exposure and sensitivity lists camera_metadata_ro_entry exposureTimeRange = GetStaticEntry(ANDROID_SENSOR_INFO_EXPOSURE_TIME_RANGE); ASSERT_EQ(exposureTimeRange.count, 2u); int64_t minExp = exposureTimeRange.data.i64[0]; int64_t maxExp = exposureTimeRange.data.i64[1]; ASSERT_GT(maxExp, minExp); camera_metadata_ro_entry sensivityRange = GetStaticEntry(ANDROID_SENSOR_INFO_SENSITIVITY_RANGE); ASSERT_EQ(2u, sensivityRange.count); int32_t minSensitivity = sensivityRange.data.i32[0]; int32_t maxSensitivity = sensivityRange.data.i32[1]; camera_metadata_ro_entry maxAnalogSenEntry = GetStaticEntry(ANDROID_SENSOR_MAX_ANALOG_SENSITIVITY); EXPECT_EQ(1u, maxAnalogSenEntry.count); int32_t maxAnalogSensitivity = maxAnalogSenEntry.data.i32[0]; EXPECT_LE(maxAnalogSensitivity, maxSensitivity); // Only test the sensitivity implemented by analog gain. if (maxAnalogSensitivity > maxSensitivity) { // Fallback to maxSensitity maxAnalogSensitivity = maxSensitivity; } // sensitivity list, only include the sensitivities that are implemented // purely by analog gain if possible. Vector<int32_t> sensitivities; Vector<int64_t> exposures; count = (maxAnalogSensitivity - minSensitivity + 99) / 100; sensitivities.push_back(minSensitivity); for (size_t i = 1; i < count; i++) { sensitivities.push_back(minSensitivity + i * 100); } sensitivities.push_back(maxAnalogSensitivity); ALOGV("Sensitivity Range: min=%d, max=%d", minSensitivity, maxAnalogSensitivity); int64_t exp = minExp; while (exp < maxExp) { exposures.push_back(exp); exp *= 2; } // Sweep the exposure value for preview, just for visual inspection purpose. uint8_t cmOff = static_cast<uint8_t>(ANDROID_CONTROL_MODE_OFF); for (size_t i = 0; i < exposures.size(); i++) { ASSERT_EQ(OK, previewRequest.update( ANDROID_CONTROL_MODE, &cmOff, 1)); ASSERT_EQ(OK, previewRequest.update( ANDROID_SENSOR_EXPOSURE_TIME, &exposures[i], 1)); ALOGV("Submitting preview request %d with exposure %lld", i, exposures[i]); ASSERT_EQ(OK, mDevice->setStreamingRequest(previewRequest)); // Let preview run 200ms on screen for each exposure time. usleep(PREVIEW_RENDERING_TIME_INTERVAL); } size_t requestCount = sensitivities.size(); if (requestCount > exposures.size()) { requestCount = exposures.size(); } // To maintain the request id uniqueness (preview request id is 0), make burst capture start // request id 1 here. int32_t requestIdStart = 1; /** * Submit metering request, set default frame duration to minimal possible * value, we want the capture to run as fast as possible. HAL should adjust * the frame duration to minimal necessary value to support the requested * exposure value if exposure is larger than frame duration. */ CaptureBurst(meteringRequest, requestCount, exposures, sensitivities, meteringStream, minFrameDuration, &requestIdStart); /** * Submit capture request, set default frame duration to minimal possible * value, we want the capture to run as fast as possible. HAL should adjust * the frame duration to minimal necessary value to support the requested * exposure value if exposure is larger than frame duration. */ CaptureBurst(captureRequest, requestCount, exposures, sensitivities, captureStream, minFrameDuration, &requestIdStart); ASSERT_EQ(OK, mDevice->clearStreamingRequest()); } } } }