Merge "Camera: Add support for testing partial results" into oc-dr1-dev

This commit is contained in:
TreeHugger Robot 2017-06-26 18:42:46 +00:00 committed by Android (Google) Code Review
commit c60ec1c2ac
2 changed files with 353 additions and 92 deletions

View file

@ -30,7 +30,8 @@ cc_test {
"libcamera_metadata",
"libbinder",
"libgui",
"libui"
"libui",
"libfmq",
],
static_libs: [
"VtsHalHidlTargetTestBase",

View file

@ -15,29 +15,33 @@
*/
#define LOG_TAG "camera_hidl_hal_test"
#include <VtsHalHidlTargetTestBase.h>
#include <chrono>
#include <mutex>
#include <regex>
#include <unordered_map>
#include <condition_variable>
#include <inttypes.h>
#include <android/hardware/camera/device/1.0/ICameraDevice.h>
#include <android/hardware/camera/device/3.2/ICameraDevice.h>
#include <android/hardware/camera/provider/2.4/ICameraProvider.h>
#include <android/log.h>
#include <binder/MemoryHeapBase.h>
#include <CameraMetadata.h>
#include <CameraParameters.h>
#include <fmq/MessageQueue.h>
#include <grallocusage/GrallocUsageConversion.h>
#include <gui/BufferItemConsumer.h>
#include <gui/BufferQueue.h>
#include <gui/Surface.h>
#include <hardware/gralloc.h>
#include <hardware/gralloc1.h>
#include <inttypes.h>
#include <system/camera.h>
#include <system/camera_metadata.h>
#include <ui/GraphicBuffer.h>
#include <utils/Errors.h>
#include <chrono>
#include <condition_variable>
#include <mutex>
#include <regex>
#include <unordered_map>
#include <CameraParameters.h>
#include "system/camera_metadata.h"
#include <VtsHalHidlTargetTestBase.h>
using ::android::hardware::Return;
using ::android::hardware::Void;
@ -90,6 +94,9 @@ using ::android::hardware::camera::device::V1_0::CameraFrameMetadata;
using ::android::hardware::camera::device::V1_0::ICameraDevicePreviewCallback;
using ::android::hardware::camera::device::V1_0::FrameCallbackFlag;
using ::android::hardware::camera::device::V1_0::HandleTimestampMessage;
using ::android::hardware::MessageQueue;
using ::android::hardware::kSynchronizedReadWrite;
using ResultMetadataQueue = MessageQueue<uint8_t, kSynchronizedReadWrite>;
const char kCameraLegacyServiceName[] = "legacy/0";
const uint32_t kMaxPreviewWidth = 1920;
@ -252,9 +259,7 @@ struct PreviewWindowCb : public ICameraDevicePreviewCallback {
size_t result = 1;
result = 31 * result + buf->numFds;
result = 31 * result + buf->numInts;
int length = buf->numFds + buf->numInts;
for (int i = 0; i < length; i++) {
for (int i = 0; i < buf->numFds; i++) {
result = 31 * result + buf->data[i];
}
return result;
@ -264,10 +269,8 @@ struct PreviewWindowCb : public ICameraDevicePreviewCallback {
struct BufferComparator {
bool operator()(const buffer_handle_t& buf1,
const buffer_handle_t& buf2) const {
if ((buf1->numFds == buf2->numFds) &&
(buf1->numInts == buf2->numInts)) {
int length = buf1->numFds + buf1->numInts;
for (int i = 0; i < length; i++) {
if (buf1->numFds == buf2->numFds) {
for (int i = 0; i < buf1->numFds; i++) {
if (buf1->data[i] != buf2->data[i]) {
return false;
}
@ -551,7 +554,9 @@ public:
const AvailableStream *previewThreshold,
sp<ICameraDeviceSession> *session /*out*/,
Stream *previewStream /*out*/,
HalStreamConfiguration *halStreamConfig /*out*/);
HalStreamConfiguration *halStreamConfig /*out*/,
bool *supportsPartialResults /*out*/,
uint32_t *partialResultCount /*out*/);
static Status getAvailableOutputStreams(camera_metadata_t *staticMeta,
std::vector<AvailableStream> &outputStreams,
const AvailableStream *threshold = nullptr);
@ -568,11 +573,75 @@ public:
CameraParameters &cameraParams, const char *mode) ;
protected:
// In-flight queue for tracking completion of capture requests.
struct InFlightRequest {
// Set by notify() SHUTTER call.
nsecs_t shutterTimestamp;
bool errorCodeValid;
ErrorCode errorCode;
//Is partial result supported
bool usePartialResult;
//Partial result count expected
uint32_t numPartialResults;
// Message queue
std::shared_ptr<ResultMetadataQueue> resultQueue;
// Set by process_capture_result call with valid metadata
bool haveResultMetadata;
// Decremented by calls to process_capture_result with valid output
// and input buffers
ssize_t numBuffersLeft;
// A 64bit integer to index the frame number associated with this result.
int64_t frameNumber;
// The partial result count (index) for this capture result.
int32_t partialResultCount;
// For buffer drop errors, the stream ID for the stream that lost a buffer.
// Otherwise -1.
int32_t errorStreamId;
// If this request has any input buffer
bool hasInputBuffer;
// Result metadata
::android::hardware::camera::common::V1_0::helper::CameraMetadata collectedResult;
// Buffers are added by process_capture_result when output buffers
// return from HAL but framework.
::android::Vector<StreamBuffer> resultOutputBuffers;
InFlightRequest(ssize_t numBuffers, bool hasInput,
bool partialResults, uint32_t partialCount,
std::shared_ptr<ResultMetadataQueue> queue = nullptr) :
shutterTimestamp(0),
errorCodeValid(false),
errorCode(ErrorCode::ERROR_BUFFER),
usePartialResult(partialResults),
numPartialResults(partialCount),
resultQueue(queue),
haveResultMetadata(false),
numBuffersLeft(numBuffers),
frameNumber(0),
partialResultCount(0),
errorStreamId(-1),
hasInputBuffer(hasInput) {}
};
// Map from frame number to the in-flight request state
typedef ::android::KeyedVector<uint32_t, InFlightRequest*> InFlightMap;
std::mutex mLock; // Synchronize access to member variables
std::condition_variable mResultCondition; // Condition variable for incoming results
uint32_t mResultFrameNumber; // Expected result frame number
std::vector<StreamBuffer> mResultBuffers; // Holds stream buffers from capture result
std::vector<ErrorMsg> mErrors; // Holds incoming error notifications
InFlightMap mInflightMap; // Map of all inflight requests
DataCallbackMsg mDataMessageTypeReceived; // Most recent message type received through data callbacks
uint32_t mVideoBufferIndex; // Buffer index of the most recent video buffer
uint32_t mVideoData; // Buffer data of the most recent video buffer
@ -695,45 +764,173 @@ Return<void> CameraHidlTest::DeviceCb::processCaptureResult(
return Void();
}
bool notify = false;
std::unique_lock<std::mutex> l(mParent->mLock);
const CaptureResult& result = results[0];
for (size_t i = 0 ; i < results.size(); i++) {
uint32_t frameNumber = results[i].frameNumber;
if(mParent->mResultFrameNumber != result.frameNumber) {
ALOGE("%s: Unexpected frame number! Expected: %u received: %u",
__func__, mParent->mResultFrameNumber, result.frameNumber);
ADD_FAILURE();
if ((results[i].result.size() == 0) &&
(results[i].outputBuffers.size() == 0) &&
(results[i].inputBuffer.buffer == nullptr) &&
(results[i].fmqResultSize == 0)) {
ALOGE("%s: No result data provided by HAL for frame %d result count: %d",
__func__, frameNumber, (int) results[i].fmqResultSize);
ADD_FAILURE();
break;
}
ssize_t idx = mParent->mInflightMap.indexOfKey(frameNumber);
if (::android::NAME_NOT_FOUND == idx) {
ALOGE("%s: Unexpected frame number! received: %u",
__func__, frameNumber);
ADD_FAILURE();
break;
}
bool isPartialResult = false;
bool hasInputBufferInRequest = false;
InFlightRequest *request = mParent->mInflightMap.editValueAt(idx);
::android::hardware::camera::device::V3_2::CameraMetadata resultMetadata;
size_t resultSize = 0;
if (results[i].fmqResultSize > 0) {
resultMetadata.resize(results[i].fmqResultSize);
if (request->resultQueue == nullptr) {
ADD_FAILURE();
break;
}
if (!request->resultQueue->read(resultMetadata.data(),
results[i].fmqResultSize)) {
ALOGE("%s: Frame %d: Cannot read camera metadata from fmq,"
"size = %" PRIu64, __func__, frameNumber,
results[i].fmqResultSize);
ADD_FAILURE();
break;
}
resultSize = resultMetadata.size();
} else if (results[i].result.size() > 0) {
resultMetadata.setToExternal(const_cast<uint8_t *>(
results[i].result.data()), results[i].result.size());
resultSize = resultMetadata.size();
}
if (!request->usePartialResult && (resultSize > 0) &&
(results[i].partialResult != 1)) {
ALOGE("%s: Result is malformed for frame %d: partial_result %u "
"must be 1 if partial result is not supported", __func__,
frameNumber, results[i].partialResult);
ADD_FAILURE();
break;
}
if (results[i].partialResult != 0) {
request->partialResultCount = results[i].partialResult;
}
// Check if this result carries only partial metadata
if (request->usePartialResult && (resultSize > 0)) {
if ((results[i].partialResult > request->numPartialResults) ||
(results[i].partialResult < 1)) {
ALOGE("%s: Result is malformed for frame %d: partial_result %u"
" must be in the range of [1, %d] when metadata is "
"included in the result", __func__, frameNumber,
results[i].partialResult, request->numPartialResults);
ADD_FAILURE();
break;
}
request->collectedResult.append(
reinterpret_cast<const camera_metadata_t*>(
resultMetadata.data()));
isPartialResult =
(results[i].partialResult < request->numPartialResults);
}
hasInputBufferInRequest = request->hasInputBuffer;
// Did we get the (final) result metadata for this capture?
if ((resultSize > 0) && !isPartialResult) {
if (request->haveResultMetadata) {
ALOGE("%s: Called multiple times with metadata for frame %d",
__func__, frameNumber);
ADD_FAILURE();
break;
}
request->haveResultMetadata = true;
request->collectedResult.sort();
}
uint32_t numBuffersReturned = results[i].outputBuffers.size();
if (results[i].inputBuffer.buffer != nullptr) {
if (hasInputBufferInRequest) {
numBuffersReturned += 1;
} else {
ALOGW("%s: Input buffer should be NULL if there is no input"
" buffer sent in the request", __func__);
}
}
request->numBuffersLeft -= numBuffersReturned;
if (request->numBuffersLeft < 0) {
ALOGE("%s: Too many buffers returned for frame %d", __func__,
frameNumber);
ADD_FAILURE();
break;
}
request->resultOutputBuffers.appendArray(results[i].outputBuffers.data(),
results[i].outputBuffers.size());
// If shutter event is received notify the pending threads.
if (request->shutterTimestamp != 0) {
notify = true;
}
}
size_t resultLength = result.outputBuffers.size();
for (size_t i = 0; i < resultLength; i++) {
mParent->mResultBuffers.push_back(result.outputBuffers[i]);
}
// TODO(epeev): Handle partial results in case client supports them and
// verify the result against request settings.
l.unlock();
mParent->mResultCondition.notify_one();
if (notify) {
mParent->mResultCondition.notify_one();
}
return Void();
}
Return<void> CameraHidlTest::DeviceCb::notify(
const hidl_vec<NotifyMsg>& messages) {
const NotifyMsg& message = messages[0];
std::lock_guard<std::mutex> l(mParent->mLock);
if (MsgType::ERROR == message.type) {
{
std::lock_guard<std::mutex> l(mParent->mLock);
mParent->mErrors.push_back(message.msg.error);
for (size_t i = 0; i < messages.size(); i++) {
ssize_t idx = mParent->mInflightMap.indexOfKey(
messages[i].msg.shutter.frameNumber);
if (::android::NAME_NOT_FOUND == idx) {
ALOGE("%s: Unexpected frame number! received: %u",
__func__, messages[i].msg.shutter.frameNumber);
ADD_FAILURE();
break;
}
InFlightRequest *r = mParent->mInflightMap.editValueAt(idx);
if ((ErrorCode::ERROR_REQUEST == message.msg.error.errorCode)
|| (ErrorCode::ERROR_BUFFER == message.msg.error.errorCode)) {
mParent->mResultCondition.notify_one();
switch(messages[i].type) {
case MsgType::ERROR:
if (ErrorCode::ERROR_DEVICE == messages[i].msg.error.errorCode) {
ALOGE("%s: Camera reported serious device error",
__func__);
ADD_FAILURE();
} else {
r->errorCodeValid = true;
r->errorCode = messages[i].msg.error.errorCode;
r->errorStreamId = messages[i].msg.error.errorStreamId;
}
break;
case MsgType::SHUTTER:
r->shutterTimestamp = messages[i].msg.shutter.timestamp;
break;
default:
ALOGE("%s: Unsupported notify message %d", __func__,
messages[i].type);
ADD_FAILURE();
break;
}
}
mParent->mResultCondition.notify_one();
return Void();
}
@ -2467,9 +2664,31 @@ TEST_F(CameraHidlTest, processCaptureRequestPreview) {
Stream previewStream;
HalStreamConfiguration halStreamConfig;
sp<ICameraDeviceSession> session;
bool supportsPartialResults = false;
uint32_t partialResultCount = 0;
configurePreviewStream(name, env, &previewThreshold,
&session /*out*/, &previewStream /*out*/,
&halStreamConfig /*out*/);
&halStreamConfig /*out*/, &supportsPartialResults /*out*/,
&partialResultCount/*out*/);
std::shared_ptr<ResultMetadataQueue> resultQueue;
auto resultQueueRet = session->getCaptureResultMetadataQueue(
[&resultQueue](const auto& descriptor) {
resultQueue = std::make_shared<ResultMetadataQueue>(
descriptor);
if (!resultQueue->isValid() ||
resultQueue->availableToWrite() <= 0) {
ALOGE("%s: HAL returns empty result metadata fmq,"
" not use it", __func__);
resultQueue = nullptr;
// Don't use the queue onwards.
}
});
ASSERT_TRUE(resultQueueRet.isOk());
ASSERT_NE(nullptr, resultQueue);
InFlightRequest inflightReq = {1, false, supportsPartialResults,
partialResultCount, resultQueue};
RequestTemplate reqTemplate = RequestTemplate::PREVIEW;
Return<void> ret;
@ -2498,8 +2717,8 @@ TEST_F(CameraHidlTest, processCaptureRequestPreview) {
{
std::unique_lock<std::mutex> l(mLock);
mResultBuffers.clear();
mResultFrameNumber = frameNumber;
mInflightMap.clear();
mInflightMap.add(frameNumber, &inflightReq);
}
Status status = Status::INTERNAL_ERROR;
@ -2518,22 +2737,28 @@ TEST_F(CameraHidlTest, processCaptureRequestPreview) {
{
std::unique_lock<std::mutex> l(mLock);
while (0 == mResultBuffers.size()) {
while (!inflightReq.errorCodeValid &&
((0 < inflightReq.numBuffersLeft) ||
(!inflightReq.haveResultMetadata))) {
auto timeout = std::chrono::system_clock::now() +
std::chrono::seconds(kStreamBufferTimeoutSec);
ASSERT_NE(std::cv_status::timeout,
mResultCondition.wait_until(l, timeout));
}
ASSERT_EQ(BufferStatus::OK, mResultBuffers[0].status);
ASSERT_EQ(previewStream.id, mResultBuffers[0].streamId);
ASSERT_FALSE(inflightReq.errorCodeValid);
ASSERT_NE(inflightReq.resultOutputBuffers.size(), 0u);
ASSERT_EQ(previewStream.id,
inflightReq.resultOutputBuffers[0].streamId);
request.frameNumber++;
//Empty settings should be supported after the first call
//for repeating requests.
request.settings.setToExternal(nullptr, 0, true);
mResultBuffers.clear();
mResultFrameNumber++;
mInflightMap.clear();
inflightReq = {1, false, supportsPartialResults,
partialResultCount, resultQueue};
mInflightMap.add(request.frameNumber, &inflightReq);
}
returnStatus = session->processCaptureRequest(
@ -2549,14 +2774,19 @@ TEST_F(CameraHidlTest, processCaptureRequestPreview) {
{
std::unique_lock<std::mutex> l(mLock);
while (0 == mResultBuffers.size()) {
while (!inflightReq.errorCodeValid &&
((0 < inflightReq.numBuffersLeft) ||
(!inflightReq.haveResultMetadata))) {
auto timeout = std::chrono::system_clock::now() +
std::chrono::seconds(kStreamBufferTimeoutSec);
ASSERT_NE(std::cv_status::timeout,
mResultCondition.wait_until(l, timeout));
}
ASSERT_EQ(BufferStatus::OK, mResultBuffers[0].status);
ASSERT_EQ(previewStream.id, mResultBuffers[0].streamId);
ASSERT_FALSE(inflightReq.errorCodeValid);
ASSERT_NE(inflightReq.resultOutputBuffers.size(), 0u);
ASSERT_EQ(previewStream.id,
inflightReq.resultOutputBuffers[0].streamId);
}
ret = session->close();
@ -2582,9 +2812,12 @@ TEST_F(CameraHidlTest, processCaptureRequestInvalidSinglePreview) {
Stream previewStream;
HalStreamConfiguration halStreamConfig;
sp<ICameraDeviceSession> session;
bool supportsPartialResults = false;
uint32_t partialResultCount = 0;
configurePreviewStream(name, env, &previewThreshold,
&session /*out*/, &previewStream /*out*/,
&halStreamConfig /*out*/);
&halStreamConfig /*out*/, &supportsPartialResults /*out*/,
&partialResultCount /*out*/);
sp<GraphicBuffer> gb = new GraphicBuffer(
previewStream.width, previewStream.height,
@ -2640,9 +2873,12 @@ TEST_F(CameraHidlTest, processCaptureRequestInvalidBuffer) {
Stream previewStream;
HalStreamConfiguration halStreamConfig;
sp<ICameraDeviceSession> session;
bool supportsPartialResults = false;
uint32_t partialResultCount = 0;
configurePreviewStream(name, env, &previewThreshold,
&session /*out*/, &previewStream /*out*/,
&halStreamConfig /*out*/);
&halStreamConfig /*out*/, &supportsPartialResults/*out*/,
&partialResultCount /*out*/);
RequestTemplate reqTemplate = RequestTemplate::PREVIEW;
Return<void> ret;
@ -2695,10 +2931,31 @@ TEST_F(CameraHidlTest, flushPreviewRequest) {
Stream previewStream;
HalStreamConfiguration halStreamConfig;
sp<ICameraDeviceSession> session;
bool supportsPartialResults = false;
uint32_t partialResultCount = 0;
configurePreviewStream(name, env, &previewThreshold,
&session /*out*/, &previewStream /*out*/,
&halStreamConfig /*out*/);
&halStreamConfig /*out*/, &supportsPartialResults /*out*/,
&partialResultCount /*out*/);
std::shared_ptr<ResultMetadataQueue> resultQueue;
auto resultQueueRet = session->getCaptureResultMetadataQueue(
[&resultQueue](const auto& descriptor) {
resultQueue = std::make_shared<ResultMetadataQueue>(
descriptor);
if (!resultQueue->isValid() ||
resultQueue->availableToWrite() <= 0) {
ALOGE("%s: HAL returns empty result metadata fmq,"
" not use it", __func__);
resultQueue = nullptr;
// Don't use the queue onwards.
}
});
ASSERT_TRUE(resultQueueRet.isOk());
ASSERT_NE(nullptr, resultQueue);
InFlightRequest inflightReq = {1, false, supportsPartialResults,
partialResultCount, resultQueue};
RequestTemplate reqTemplate = RequestTemplate::PREVIEW;
Return<void> ret;
ret = session->constructDefaultRequestSettings(reqTemplate,
@ -2726,9 +2983,8 @@ TEST_F(CameraHidlTest, flushPreviewRequest) {
{
std::unique_lock<std::mutex> l(mLock);
mResultBuffers.clear();
mErrors.clear();
mResultFrameNumber = frameNumber;
mInflightMap.clear();
mInflightMap.add(frameNumber, &inflightReq);
}
Status status = Status::INTERNAL_ERROR;
@ -2752,32 +3008,30 @@ TEST_F(CameraHidlTest, flushPreviewRequest) {
{
std::unique_lock<std::mutex> l(mLock);
while ((0 == mResultBuffers.size()) && (0 == mErrors.size())) {
while (!inflightReq.errorCodeValid &&
((0 < inflightReq.numBuffersLeft) ||
(!inflightReq.haveResultMetadata))) {
auto timeout = std::chrono::system_clock::now() +
std::chrono::seconds(kStreamBufferTimeoutSec);
ASSERT_NE(std::cv_status::timeout,
mResultCondition.wait_until(l, timeout));
}
if (mErrors.empty()) {
ASSERT_EQ(BufferStatus::OK, mResultBuffers[0].status);
ASSERT_EQ(previewStream.id, mResultBuffers[0].streamId);
if (!inflightReq.errorCodeValid) {
ASSERT_NE(inflightReq.resultOutputBuffers.size(), 0u);
ASSERT_EQ(previewStream.id,
inflightReq.resultOutputBuffers[0].streamId);
} else {
for (auto &error : mErrors) {
switch (error.errorCode) {
case ErrorCode::ERROR_REQUEST:
case ErrorCode::ERROR_RESULT:
//Expected
break;
case ErrorCode::ERROR_BUFFER:
//Expected as well
ASSERT_EQ(frameNumber, error.frameNumber);
ASSERT_EQ(previewStream.id, error.errorStreamId);
break;
case ErrorCode::ERROR_DEVICE:
default:
FAIL() <<"Unexpected error:" << static_cast<uint32_t> (error.errorCode);
}
switch (inflightReq.errorCode) {
case ErrorCode::ERROR_REQUEST:
case ErrorCode::ERROR_RESULT:
case ErrorCode::ERROR_BUFFER:
//Expected
break;
case ErrorCode::ERROR_DEVICE:
default:
FAIL() << "Unexpected error:" << static_cast<uint32_t> (
inflightReq.errorCode);
}
}
}
@ -2801,16 +3055,12 @@ TEST_F(CameraHidlTest, flushEmpty) {
Stream previewStream;
HalStreamConfiguration halStreamConfig;
sp<ICameraDeviceSession> session;
bool supportsPartialResults = false;
uint32_t partialResultCount = 0;
configurePreviewStream(name, env, &previewThreshold,
&session /*out*/, &previewStream /*out*/,
&halStreamConfig /*out*/);
{
std::unique_lock<std::mutex> l(mLock);
mResultBuffers.clear();
mErrors.clear();
mResultFrameNumber = 0;
}
&halStreamConfig /*out*/, &supportsPartialResults /*out*/,
&partialResultCount /*out*/);
Return<Status> returnStatus = session->flush();
ASSERT_TRUE(returnStatus.isOk());
@ -2822,8 +3072,6 @@ TEST_F(CameraHidlTest, flushEmpty) {
std::chrono::milliseconds(kEmptyFlushTimeoutMSec);
ASSERT_EQ(std::cv_status::timeout,
mResultCondition.wait_until(l, timeout));
ASSERT_TRUE(mErrors.empty());
ASSERT_TRUE(mResultBuffers.empty());
}
Return<void> ret = session->close();
@ -3021,11 +3269,15 @@ void CameraHidlTest::configurePreviewStream(const std::string &name,
const AvailableStream *previewThreshold,
sp<ICameraDeviceSession> *session /*out*/,
Stream *previewStream /*out*/,
HalStreamConfiguration *halStreamConfig /*out*/) {
HalStreamConfiguration *halStreamConfig /*out*/,
bool *supportsPartialResults /*out*/,
uint32_t *partialResultCount /*out*/) {
ASSERT_NE(nullptr, env);
ASSERT_NE(nullptr, session);
ASSERT_NE(nullptr, previewStream);
ASSERT_NE(nullptr, halStreamConfig);
ASSERT_NE(nullptr, supportsPartialResults);
ASSERT_NE(nullptr, partialResultCount);
std::vector<AvailableStream> outputPreviewStreams;
::android::sp<ICameraDevice> device3_2;
@ -3063,6 +3315,14 @@ void CameraHidlTest::configurePreviewStream(const std::string &name,
});
ASSERT_TRUE(ret.isOk());
camera_metadata_ro_entry entry;
auto status = find_camera_metadata_ro_entry(staticMeta,
ANDROID_REQUEST_PARTIAL_RESULT_COUNT, &entry);
if ((0 == status) && (entry.count > 0)) {
*partialResultCount = entry.data.i32[0];
*supportsPartialResults = (*partialResultCount > 1);
}
outputPreviewStreams.clear();
auto rc = getAvailableOutputStreams(staticMeta,
outputPreviewStreams, previewThreshold);