Camera: Detect session parameter updates and re-configure camera

Camera clients are allowed to change session parameters during
active session, however some camera devices might not be able
to apply them without additional stream configuration. To resolve
this, detect whether any of the available session parameter changes
in the queued requests and re-configure the camera before trying
to apply it.

Bug: 64450664
Test: Manual, Camera CTS
Change-Id: I005d6b7d6c6b27d4b5bac4b0d0809c7c019af9a4
gugelfrei
Emilian Peev 7 years ago
parent 65a438c642
commit ac3ce6c30c

@ -194,8 +194,14 @@ status_t Camera3Device::initializeCommonLocked() {
mTagMonitor.initialize(mVendorTagId);
Vector<int32_t> sessionParamKeys;
camera_metadata_entry_t sessionKeysEntry = mDeviceInfo.find(
ANDROID_REQUEST_AVAILABLE_SESSION_KEYS);
if (sessionKeysEntry.count > 0) {
sessionParamKeys.insertArrayAt(sessionKeysEntry.data.i32, 0, sessionKeysEntry.count);
}
/** Start up request queue thread */
mRequestThread = new RequestThread(this, mStatusTracker, mInterface);
mRequestThread = new RequestThread(this, mStatusTracker, mInterface, sessionParamKeys);
res = mRequestThread->run(String8::format("C3Dev-%s-ReqQueue", mId.string()).string());
if (res != OK) {
SET_ERR_L("Unable to start request queue thread: %s (%d)",
@ -1104,7 +1110,7 @@ sp<Camera3Device::CaptureRequest> Camera3Device::setUpRequestLocked(
if (mStatus == STATUS_UNCONFIGURED || mNeedConfig) {
// This point should only be reached via API1 (API2 must explicitly call configureStreams)
// so unilaterally select normal operating mode.
res = configureStreamsLocked(CAMERA3_STREAM_CONFIGURATION_NORMAL_MODE, mSessionParams);
res = filterParamsAndConfigureLocked(request, CAMERA3_STREAM_CONFIGURATION_NORMAL_MODE);
// Stream configuration failed. Client might try other configuraitons.
if (res != OK) {
CLOGE("Can't set up streams: %s (%d)", strerror(-res), res);
@ -1508,11 +1514,20 @@ status_t Camera3Device::configureStreams(const CameraMetadata& sessionParams, in
Mutex::Autolock il(mInterfaceLock);
Mutex::Autolock l(mLock);
return filterParamsAndConfigureLocked(sessionParams, operatingMode);
}
status_t Camera3Device::filterParamsAndConfigureLocked(const CameraMetadata& sessionParams,
int operatingMode) {
//Filter out any incoming session parameters
const CameraMetadata params(sessionParams);
CameraMetadata filteredParams;
camera_metadata_entry_t availableSessionKeys = mDeviceInfo.find(
ANDROID_REQUEST_AVAILABLE_SESSION_KEYS);
CameraMetadata filteredParams(availableSessionKeys.count);
camera_metadata_t *meta = const_cast<camera_metadata_t *>(
filteredParams.getAndLock());
set_camera_metadata_vendor_id(meta, mVendorTagId);
filteredParams.unlock(meta);
if (availableSessionKeys.count > 0) {
for (size_t i = 0; i < availableSessionKeys.count; i++) {
camera_metadata_ro_entry entry = params.find(
@ -2203,10 +2218,47 @@ void Camera3Device::cancelStreamsConfigurationLocked() {
// properly clean things up
internalUpdateStatusLocked(STATUS_UNCONFIGURED);
mNeedConfig = true;
res = mPreparerThread->resume();
if (res != OK) {
ALOGE("%s: Camera %s: Preparer thread failed to resume!", __FUNCTION__, mId.string());
}
}
bool Camera3Device::reconfigureCamera(const CameraMetadata& sessionParams) {
ATRACE_CALL();
bool ret = false;
Mutex::Autolock il(mInterfaceLock);
nsecs_t maxExpectedDuration = getExpectedInFlightDuration();
Mutex::Autolock l(mLock);
auto rc = internalPauseAndWaitLocked(maxExpectedDuration);
if (rc == NO_ERROR) {
mNeedConfig = true;
rc = configureStreamsLocked(mOperatingMode, sessionParams, /*notifyRequestThread*/ false);
if (rc == NO_ERROR) {
ret = true;
mPauseStateNotify = false;
//Moving to active state while holding 'mLock' is important.
//There could be pending calls to 'create-/deleteStream' which
//will trigger another stream configuration while the already
//present streams end up with outstanding buffers that will
//not get drained.
internalUpdateStatusLocked(STATUS_ACTIVE);
} else {
setErrorStateLocked("%s: Failed to re-configure camera: %d",
__FUNCTION__, rc);
}
} else {
ALOGE("%s: Failed to pause streaming: %d", __FUNCTION__, rc);
}
return ret;
}
status_t Camera3Device::configureStreamsLocked(int operatingMode,
const CameraMetadata& sessionParams) {
const CameraMetadata& sessionParams, bool notifyRequestThread) {
ATRACE_CALL();
status_t res;
@ -2247,6 +2299,8 @@ status_t Camera3Device::configureStreamsLocked(int operatingMode,
// Start configuring the streams
ALOGV("%s: Camera %s: Starting stream configuration", __FUNCTION__, mId.string());
mPreparerThread->pause();
camera3_stream_configuration config;
config.operation_mode = mOperatingMode;
config.num_streams = (mInputStream != NULL) + mOutputStreams.size();
@ -2338,7 +2392,9 @@ status_t Camera3Device::configureStreamsLocked(int operatingMode,
// Request thread needs to know to avoid using repeat-last-settings protocol
// across configure_streams() calls
mRequestThread->configurationComplete(mIsConstrainedHighSpeedConfiguration);
if (notifyRequestThread) {
mRequestThread->configurationComplete(mIsConstrainedHighSpeedConfiguration, sessionParams);
}
char value[PROPERTY_VALUE_MAX];
property_get("camera.fifo.disable", value, "0");
@ -2376,6 +2432,12 @@ status_t Camera3Device::configureStreamsLocked(int operatingMode,
// tear down the deleted streams after configure streams.
mDeletedStreams.clear();
auto rc = mPreparerThread->resume();
if (rc != OK) {
SET_ERR_L("%s: Camera %s: Preparer thread failed to resume!", __FUNCTION__, mId.string());
return rc;
}
return OK;
}
@ -3747,7 +3809,7 @@ void Camera3Device::HalInterface::onBufferFreed(
Camera3Device::RequestThread::RequestThread(wp<Camera3Device> parent,
sp<StatusTracker> statusTracker,
sp<HalInterface> interface) :
sp<HalInterface> interface, const Vector<int32_t>& sessionParamKeys) :
Thread(/*canCallJava*/false),
mParent(parent),
mStatusTracker(statusTracker),
@ -3764,7 +3826,9 @@ Camera3Device::RequestThread::RequestThread(wp<Camera3Device> parent,
mRepeatingLastFrameNumber(
hardware::camera2::ICameraDeviceUser::NO_IN_FLIGHT_REPEATING_FRAMES),
mPrepareVideoStream(false),
mRequestLatency(kRequestLatencyBinSize) {
mRequestLatency(kRequestLatencyBinSize),
mSessionParamKeys(sessionParamKeys),
mLatestSessionParams(sessionParamKeys.size()) {
mStatusId = statusTracker->addComponent();
}
@ -3777,10 +3841,12 @@ void Camera3Device::RequestThread::setNotificationListener(
mListener = listener;
}
void Camera3Device::RequestThread::configurationComplete(bool isConstrainedHighSpeed) {
void Camera3Device::RequestThread::configurationComplete(bool isConstrainedHighSpeed,
const CameraMetadata& sessionParams) {
ATRACE_CALL();
Mutex::Autolock l(mRequestLock);
mReconfigured = true;
mLatestSessionParams = sessionParams;
// Prepare video stream for high speed recording.
mPrepareVideoStream = isConstrainedHighSpeed;
}
@ -4191,6 +4257,52 @@ nsecs_t Camera3Device::RequestThread::calculateMaxExpectedDuration(const camera_
return maxExpectedDuration;
}
bool Camera3Device::RequestThread::updateSessionParameters(const CameraMetadata& settings) {
ATRACE_CALL();
bool updatesDetected = false;
for (auto tag : mSessionParamKeys) {
camera_metadata_ro_entry entry = settings.find(tag);
camera_metadata_entry lastEntry = mLatestSessionParams.find(tag);
if (entry.count > 0) {
bool isDifferent = false;
if (lastEntry.count > 0) {
// Have a last value, compare to see if changed
if (lastEntry.type == entry.type &&
lastEntry.count == entry.count) {
// Same type and count, compare values
size_t bytesPerValue = camera_metadata_type_size[lastEntry.type];
size_t entryBytes = bytesPerValue * lastEntry.count;
int cmp = memcmp(entry.data.u8, lastEntry.data.u8, entryBytes);
if (cmp != 0) {
isDifferent = true;
}
} else {
// Count or type has changed
isDifferent = true;
}
} else {
// No last entry, so always consider to be different
isDifferent = true;
}
if (isDifferent) {
ALOGV("%s: Session parameter tag id %d changed", __FUNCTION__, tag);
mLatestSessionParams.update(entry);
updatesDetected = true;
}
} else if (lastEntry.count > 0) {
// Value has been removed
ALOGV("%s: Session parameter tag id %d removed", __FUNCTION__, tag);
mLatestSessionParams.erase(tag);
updatesDetected = true;
}
}
return updatesDetected;
}
bool Camera3Device::RequestThread::threadLoop() {
ATRACE_CALL();
status_t res;
@ -4217,6 +4329,49 @@ bool Camera3Device::RequestThread::threadLoop() {
latestRequestId = NAME_NOT_FOUND;
}
// 'mNextRequests' will at this point contain either a set of HFR batched requests
// or a single request from streaming or burst. In either case the first element
// should contain the latest camera settings that we need to check for any session
// parameter updates.
if (updateSessionParameters(mNextRequests[0].captureRequest->mSettings)) {
res = OK;
//Input stream buffers are already acquired at this point so an input stream
//will not be able to move to idle state unless we force it.
if (mNextRequests[0].captureRequest->mInputStream != nullptr) {
res = mNextRequests[0].captureRequest->mInputStream->forceToIdle();
if (res != OK) {
ALOGE("%s: Failed to force idle input stream: %d", __FUNCTION__, res);
cleanUpFailedRequests(/*sendRequestError*/ false);
return false;
}
}
if (res == OK) {
sp<StatusTracker> statusTracker = mStatusTracker.promote();
if (statusTracker != 0) {
statusTracker->markComponentIdle(mStatusId, Fence::NO_FENCE);
sp<Camera3Device> parent = mParent.promote();
if (parent != nullptr) {
mReconfigured |= parent->reconfigureCamera(mLatestSessionParams);
}
statusTracker->markComponentActive(mStatusId);
setPaused(false);
}
if (mNextRequests[0].captureRequest->mInputStream != nullptr) {
mNextRequests[0].captureRequest->mInputStream->restoreConfiguredState();
if (res != OK) {
ALOGE("%s: Failed to restore configured input stream: %d", __FUNCTION__, res);
cleanUpFailedRequests(/*sendRequestError*/ false);
return false;
}
}
}
}
// Prepare a batch of HAL requests and output buffers.
res = prepareHalRequests();
if (res == TIMED_OUT) {
@ -4980,7 +5135,7 @@ status_t Camera3Device::RequestThread::addDummyTriggerIds(
Camera3Device::PreparerThread::PreparerThread() :
Thread(/*canCallJava*/false), mListener(nullptr),
mActive(false), mCancelNow(false) {
mActive(false), mCancelNow(false), mCurrentMaxCount(0), mCurrentPrepareComplete(false) {
}
Camera3Device::PreparerThread::~PreparerThread() {
@ -5031,18 +5186,101 @@ status_t Camera3Device::PreparerThread::prepare(int maxCount, sp<Camera3StreamIn
}
// queue up the work
mPendingStreams.push_back(stream);
mPendingStreams.emplace(maxCount, stream);
ALOGV("%s: Stream %d queued for preparing", __FUNCTION__, stream->getId());
return OK;
}
void Camera3Device::PreparerThread::pause() {
ATRACE_CALL();
Mutex::Autolock l(mLock);
std::unordered_map<int, sp<camera3::Camera3StreamInterface> > pendingStreams;
pendingStreams.insert(mPendingStreams.begin(), mPendingStreams.end());
sp<camera3::Camera3StreamInterface> currentStream = mCurrentStream;
int currentMaxCount = mCurrentMaxCount;
mPendingStreams.clear();
mCancelNow = true;
while (mActive) {
auto res = mThreadActiveSignal.waitRelative(mLock, kActiveTimeout);
if (res == TIMED_OUT) {
ALOGE("%s: Timed out waiting on prepare thread!", __FUNCTION__);
return;
} else if (res != OK) {
ALOGE("%s: Encountered an error: %d waiting on prepare thread!", __FUNCTION__, res);
return;
}
}
//Check whether the prepare thread was able to complete the current
//stream. In case work is still pending emplace it along with the rest
//of the streams in the pending list.
if (currentStream != nullptr) {
if (!mCurrentPrepareComplete) {
pendingStreams.emplace(currentMaxCount, currentStream);
}
}
mPendingStreams.insert(pendingStreams.begin(), pendingStreams.end());
for (const auto& it : mPendingStreams) {
it.second->cancelPrepare();
}
}
status_t Camera3Device::PreparerThread::resume() {
ATRACE_CALL();
status_t res;
Mutex::Autolock l(mLock);
sp<NotificationListener> listener = mListener.promote();
if (mActive) {
ALOGE("%s: Trying to resume an already active prepare thread!", __FUNCTION__);
return NO_INIT;
}
auto it = mPendingStreams.begin();
for (; it != mPendingStreams.end();) {
res = it->second->startPrepare(it->first);
if (res == OK) {
if (listener != NULL) {
listener->notifyPrepared(it->second->getId());
}
it = mPendingStreams.erase(it);
} else if (res != NOT_ENOUGH_DATA) {
ALOGE("%s: Unable to start preparer stream: %d (%s)", __FUNCTION__,
res, strerror(-res));
it = mPendingStreams.erase(it);
} else {
it++;
}
}
if (mPendingStreams.empty()) {
return OK;
}
res = Thread::run("C3PrepThread", PRIORITY_BACKGROUND);
if (res != OK) {
ALOGE("%s: Unable to start preparer stream: %d (%s)",
__FUNCTION__, res, strerror(-res));
return res;
}
mCancelNow = false;
mActive = true;
ALOGV("%s: Preparer stream started", __FUNCTION__);
return OK;
}
status_t Camera3Device::PreparerThread::clear() {
ATRACE_CALL();
Mutex::Autolock l(mLock);
for (const auto& stream : mPendingStreams) {
stream->cancelPrepare();
for (const auto& it : mPendingStreams) {
it.second->cancelPrepare();
}
mPendingStreams.clear();
mCancelNow = true;
@ -5067,12 +5305,15 @@ bool Camera3Device::PreparerThread::threadLoop() {
// threadLoop _must not_ re-acquire mLock after it sets mActive to false; would
// cause deadlock with prepare()'s requestExitAndWait triggered by !mActive.
mActive = false;
mThreadActiveSignal.signal();
return false;
}
// Get next stream to prepare
auto it = mPendingStreams.begin();
mCurrentStream = *it;
mCurrentStream = it->second;
mCurrentMaxCount = it->first;
mCurrentPrepareComplete = false;
mPendingStreams.erase(it);
ATRACE_ASYNC_BEGIN("stream prepare", mCurrentStream->getId());
ALOGV("%s: Preparing stream %d", __FUNCTION__, mCurrentStream->getId());
@ -5107,6 +5348,7 @@ bool Camera3Device::PreparerThread::threadLoop() {
ATRACE_ASYNC_END("stream prepare", mCurrentStream->getId());
mCurrentStream.clear();
mCurrentPrepareComplete = true;
return true;
}

@ -551,12 +551,24 @@ class Camera3Device :
sp<CaptureRequest> createCaptureRequest(const CameraMetadata &request,
const SurfaceMap &surfaceMap);
/**
* Internally re-configure camera device using new session parameters.
* This will get triggered by the request thread.
*/
bool reconfigureCamera(const CameraMetadata& sessionParams);
/**
* Filter stream session parameters and configure camera HAL.
*/
status_t filterParamsAndConfigureLocked(const CameraMetadata& sessionParams,
int operatingMode);
/**
* Take the currently-defined set of streams and configure the HAL to use
* them. This is a long-running operation (may be several hundered ms).
*/
status_t configureStreamsLocked(int operatingMode,
const CameraMetadata& sessionParams);
const CameraMetadata& sessionParams, bool notifyRequestThread = true);
/**
* Cancel stream configuration that did not finish successfully.
@ -655,7 +667,7 @@ class Camera3Device :
RequestThread(wp<Camera3Device> parent,
sp<camera3::StatusTracker> statusTracker,
sp<HalInterface> interface);
sp<HalInterface> interface, const Vector<int32_t>& sessionParamKeys);
~RequestThread();
void setNotificationListener(wp<NotificationListener> listener);
@ -663,7 +675,8 @@ class Camera3Device :
/**
* Call after stream (re)-configuration is completed.
*/
void configurationComplete(bool isConstrainedHighSpeed);
void configurationComplete(bool isConstrainedHighSpeed,
const CameraMetadata& sessionParams);
/**
* Set or clear the list of repeating requests. Does not block
@ -812,6 +825,12 @@ class Camera3Device :
// Calculate the expected maximum duration for a request
nsecs_t calculateMaxExpectedDuration(const camera_metadata_t *request);
// Check and update latest session parameters based on the current request settings.
bool updateSessionParameters(const CameraMetadata& settings);
// Re-configure camera using the latest session parameters.
bool reconfigureCamera();
wp<Camera3Device> mParent;
wp<camera3::StatusTracker> mStatusTracker;
sp<HalInterface> mInterface;
@ -869,6 +888,9 @@ class Camera3Device :
static const int32_t kRequestLatencyBinSize = 40; // in ms
CameraLatencyHistogram mRequestLatency;
Vector<int32_t> mSessionParamKeys;
CameraMetadata mLatestSessionParams;
};
sp<RequestThread> mRequestThread;
@ -1006,21 +1028,34 @@ class Camera3Device :
*/
status_t clear();
/**
* Pause all preparation activities
*/
void pause();
/**
* Resume preparation activities
*/
status_t resume();
private:
Mutex mLock;
Condition mThreadActiveSignal;
virtual bool threadLoop();
// Guarded by mLock
wp<NotificationListener> mListener;
List<sp<camera3::Camera3StreamInterface> > mPendingStreams;
std::unordered_map<int, sp<camera3::Camera3StreamInterface> > mPendingStreams;
bool mActive;
bool mCancelNow;
// Only accessed by threadLoop and the destructor
sp<camera3::Camera3StreamInterface> mCurrentStream;
int mCurrentMaxCount;
bool mCurrentPrepareComplete;
};
sp<PreparerThread> mPreparerThread;

@ -140,6 +140,75 @@ android_dataspace Camera3Stream::getOriginalDataSpace() const {
return mOriginalDataSpace;
}
status_t Camera3Stream::forceToIdle() {
ATRACE_CALL();
Mutex::Autolock l(mLock);
status_t res;
switch (mState) {
case STATE_ERROR:
case STATE_CONSTRUCTED:
case STATE_IN_CONFIG:
case STATE_PREPARING:
case STATE_IN_RECONFIG:
ALOGE("%s: Invalid state: %d", __FUNCTION__, mState);
res = NO_INIT;
break;
case STATE_CONFIGURED:
if (hasOutstandingBuffersLocked()) {
sp<StatusTracker> statusTracker = mStatusTracker.promote();
if (statusTracker != 0) {
statusTracker->markComponentIdle(mStatusId, Fence::NO_FENCE);
}
}
mState = STATE_IN_IDLE;
res = OK;
break;
default:
ALOGE("%s: Unknown state %d", __FUNCTION__, mState);
res = NO_INIT;
}
return res;
}
status_t Camera3Stream::restoreConfiguredState() {
ATRACE_CALL();
Mutex::Autolock l(mLock);
status_t res;
switch (mState) {
case STATE_ERROR:
case STATE_CONSTRUCTED:
case STATE_IN_CONFIG:
case STATE_PREPARING:
case STATE_IN_RECONFIG:
case STATE_CONFIGURED:
ALOGE("%s: Invalid state: %d", __FUNCTION__, mState);
res = NO_INIT;
break;
case STATE_IN_IDLE:
if (hasOutstandingBuffersLocked()) {
sp<StatusTracker> statusTracker = mStatusTracker.promote();
if (statusTracker != 0) {
statusTracker->markComponentActive(mStatusId);
}
}
mState = STATE_CONFIGURED;
res = OK;
break;
default:
ALOGE("%s: Unknown state %d", __FUNCTION__, mState);
res = NO_INIT;
}
return res;
}
camera3_stream* Camera3Stream::startConfiguration() {
ATRACE_CALL();
Mutex::Autolock l(mLock);
@ -150,6 +219,7 @@ camera3_stream* Camera3Stream::startConfiguration() {
ALOGE("%s: In error state", __FUNCTION__);
return NULL;
case STATE_CONSTRUCTED:
case STATE_IN_IDLE:
// OK
break;
case STATE_IN_CONFIG:
@ -179,6 +249,11 @@ camera3_stream* Camera3Stream::startConfiguration() {
return NULL;
}
if (mState == STATE_IN_IDLE) {
// Skip configuration.
return this;
}
// Stop tracking if currently doing so
if (mStatusId != StatusTracker::NO_STATUS_ID) {
sp<StatusTracker> statusTracker = mStatusTracker.promote();
@ -219,6 +294,9 @@ status_t Camera3Stream::finishConfiguration() {
ALOGE("%s: Cannot finish configuration that hasn't been started",
__FUNCTION__);
return INVALID_OPERATION;
case STATE_IN_IDLE:
//Skip configuration in this state
return OK;
default:
ALOGE("%s: Unknown state", __FUNCTION__);
return INVALID_OPERATION;
@ -267,6 +345,7 @@ status_t Camera3Stream::cancelConfiguration() {
return INVALID_OPERATION;
case STATE_IN_CONFIG:
case STATE_IN_RECONFIG:
case STATE_IN_IDLE:
// OK
break;
case STATE_CONSTRUCTED:
@ -282,7 +361,9 @@ status_t Camera3Stream::cancelConfiguration() {
mUsage = mOldUsage;
camera3_stream::max_buffers = mOldMaxBuffers;
mState = (mState == STATE_IN_RECONFIG) ? STATE_CONFIGURED : STATE_CONSTRUCTED;
mState = ((mState == STATE_IN_RECONFIG) || (mState == STATE_IN_IDLE)) ? STATE_CONFIGURED :
STATE_CONSTRUCTED;
return OK;
}

@ -68,6 +68,12 @@ namespace camera3 {
* duration. In this state, only prepareNextBuffer() and cancelPrepare()
* may be called.
*
* STATE_IN_IDLE: This is a temporary state only intended to be used for input
* streams and only for the case where we need to re-configure the camera device
* while the input stream has an outstanding buffer. All other streams should not
* be able to switch to this state. For them this is invalid and should be handled
* as an unknown state.
*
* Transition table:
*
* <none> => STATE_CONSTRUCTED:
@ -98,6 +104,11 @@ namespace camera3 {
* all stream buffers, or cancelPrepare is called.
* STATE_CONFIGURED => STATE_ABANDONED:
* When the buffer queue of the stream is abandoned.
* STATE_CONFIGURED => STATE_IN_IDLE:
* Only for an input stream which has an outstanding buffer.
* STATE_IN_IDLE => STATE_CONFIGURED:
* After the internal re-configuration, the input should revert back to
* the configured state.
*
* Status Tracking:
* Each stream is tracked by StatusTracker as a separate component,
@ -108,7 +119,9 @@ namespace camera3 {
*
* - ACTIVE: One or more buffers have been handed out (with #getBuffer).
* - IDLE: All buffers have been returned (with #returnBuffer), and their
* respective release_fence(s) have been signaled.
* respective release_fence(s) have been signaled. The only exception to this
* rule is an input stream that moves to "STATE_IN_IDLE" during internal
* re-configuration.
*
* A typical use case is output streams. When the HAL has any buffers
* dequeued, the stream is marked ACTIVE. When the HAL returns all buffers
@ -386,6 +399,19 @@ class Camera3Stream :
*/
bool isAbandoned() const;
/**
* Switch a configured stream with possibly outstanding buffers in idle
* state. Configuration for such streams will be skipped assuming there
* are no changes to the stream parameters.
*/
status_t forceToIdle();
/**
* Restore a forced idle stream to configured state, marking it active
* in case it contains outstanding buffers.
*/
status_t restoreConfiguredState();
protected:
const int mId;
/**
@ -414,7 +440,8 @@ class Camera3Stream :
STATE_IN_RECONFIG,
STATE_CONFIGURED,
STATE_PREPARING,
STATE_ABANDONED
STATE_ABANDONED,
STATE_IN_IDLE
} mState;
mutable Mutex mLock;

Loading…
Cancel
Save