parent
e1139e4ecb
commit
ef879c2bbb
@ -0,0 +1,154 @@ |
||||
package com.otaliastudios.cameraview.engine; |
||||
|
||||
import android.hardware.camera2.CameraCharacteristics; |
||||
import android.hardware.camera2.CaptureRequest; |
||||
import android.hardware.camera2.CaptureResult; |
||||
import android.hardware.camera2.TotalCaptureResult; |
||||
import android.os.Build; |
||||
|
||||
import androidx.annotation.NonNull; |
||||
import androidx.annotation.RequiresApi; |
||||
|
||||
import com.otaliastudios.cameraview.CameraLogger; |
||||
import com.otaliastudios.cameraview.engine.locking.AutoExposure; |
||||
import com.otaliastudios.cameraview.engine.locking.AutoFocus; |
||||
import com.otaliastudios.cameraview.engine.locking.AutoWhiteBalance; |
||||
import com.otaliastudios.cameraview.engine.locking.Parameter; |
||||
|
||||
/** |
||||
* Helps Camera2-based engines to perform 3A locking and unlocking. |
||||
* Users are required to: |
||||
* |
||||
* - Call {@link #lock(CaptureResult)} to start |
||||
* - Call {@link #onCapture(CaptureResult)} when they have partial or total results, as long as the |
||||
* locker is still in a locking operation, which can be checked through {@link #isLocking()} ()} |
||||
* - Call {@link #unlock()} to reset the locked parameters if needed. |
||||
*/ |
||||
@RequiresApi(Build.VERSION_CODES.LOLLIPOP) |
||||
public class Locker { |
||||
|
||||
/** |
||||
* The locker callback. |
||||
*/ |
||||
public interface Callback extends Parameter.LockingChangeCallback { |
||||
|
||||
/** |
||||
* Notifies that locking has ended. No action is required for implementors. |
||||
* From now on, {@link #isLocking()} will return false. |
||||
* @param success success |
||||
*/ |
||||
void onLocked(boolean success); |
||||
|
||||
/** |
||||
* Notifies that locking has been undone. From now on, this locker instance |
||||
* is done, although in theory it could be reused by calling |
||||
* {@link #lock(CaptureResult)} again. |
||||
*/ |
||||
void onUnlocked(); |
||||
|
||||
/** |
||||
* Returns the currently used builder. This can change while a locking |
||||
* operation happens, so the locker will never cache this value. |
||||
* It is the engine responsibility to copy over values to the new builder |
||||
* when it changes. |
||||
* @return a builder |
||||
*/ |
||||
@NonNull |
||||
CaptureRequest.Builder getLockingBuilder(); |
||||
} |
||||
|
||||
private static final String TAG = Locker.class.getSimpleName(); |
||||
private static final CameraLogger LOG = CameraLogger.create(TAG); |
||||
private static final int FORCED_END_DELAY = 2500; |
||||
|
||||
private final CameraCharacteristics mCharacteristics; |
||||
private final Callback mCallback; |
||||
|
||||
private boolean mIsLocking; |
||||
private Parameter mAutoFocus; |
||||
private Parameter mAutoWhiteBalance; |
||||
private Parameter mAutoExposure; |
||||
private long mLockingStartTime; |
||||
|
||||
/** |
||||
* Creates a new locker. |
||||
* @param characteristics the camera characteristics |
||||
* @param callback the callback |
||||
*/ |
||||
@SuppressWarnings("WeakerAccess") |
||||
public Locker(@NonNull CameraCharacteristics characteristics, |
||||
@NonNull Callback callback) { |
||||
mCharacteristics = characteristics; |
||||
mCallback = callback; |
||||
mAutoFocus = new AutoFocus(callback); |
||||
mAutoExposure = new AutoExposure(callback); |
||||
mAutoWhiteBalance = new AutoWhiteBalance(callback); |
||||
} |
||||
|
||||
/** |
||||
* Locks 3A values. |
||||
* @param lastResult the last result |
||||
*/ |
||||
@SuppressWarnings("WeakerAccess") |
||||
public void lock(@NonNull CaptureResult lastResult) { |
||||
mIsLocking = true; |
||||
mLockingStartTime = System.currentTimeMillis(); |
||||
mAutoFocus.lock(mCharacteristics, mCallback.getLockingBuilder(), lastResult); |
||||
mAutoWhiteBalance.lock(mCharacteristics, mCallback.getLockingBuilder(), lastResult); |
||||
mAutoExposure.lock(mCharacteristics, mCallback.getLockingBuilder(), lastResult); |
||||
} |
||||
|
||||
/** |
||||
* True if we're locking. False if we're not, for example |
||||
* if {@link #lock(CaptureResult)} was never called. |
||||
* @return true if locking |
||||
*/ |
||||
@SuppressWarnings("WeakerAccess") |
||||
public boolean isLocking() { |
||||
return mIsLocking; |
||||
} |
||||
|
||||
/** |
||||
* Should be called when we have partial or total CaptureResults, |
||||
* but only while {@link #isLocking()} returns true. |
||||
* @param result result |
||||
*/ |
||||
@SuppressWarnings("WeakerAccess") |
||||
public void onCapture(@NonNull CaptureResult result) { |
||||
if (!mIsLocking) return; // We're not interested in results anymore
|
||||
if (!(result instanceof TotalCaptureResult)) return; // Let's ignore these, contents are missing/wrong
|
||||
|
||||
if (!mAutoFocus.isLocked()) mAutoFocus.onCapture(mCallback.getLockingBuilder(), result); |
||||
if (!mAutoExposure.isLocked()) mAutoExposure.onCapture(mCallback.getLockingBuilder(), result); |
||||
if (!mAutoWhiteBalance.isLocked()) mAutoWhiteBalance.onCapture(mCallback.getLockingBuilder(), result); |
||||
if (mAutoFocus.isLocked() && mAutoExposure.isLocked() && mAutoWhiteBalance.isLocked()) { |
||||
LOG.i("onCapture:", "all Parameters have converged. Dispatching onMeteringEnd"); |
||||
boolean success = mAutoFocus.isSuccessful() |
||||
&& mAutoExposure.isSuccessful() |
||||
&& mAutoWhiteBalance.isSuccessful(); |
||||
dispatchEnd(success); |
||||
} else if (System.currentTimeMillis() - mLockingStartTime >= FORCED_END_DELAY) { |
||||
LOG.i("onCapture:", "FORCED_END_DELAY was reached. Some Parameter is stuck. Forcing end."); |
||||
dispatchEnd(false); |
||||
} |
||||
} |
||||
|
||||
private void dispatchEnd(boolean success) { |
||||
mCallback.onLocked(success); |
||||
mIsLocking = false; |
||||
} |
||||
|
||||
/** |
||||
* Should be called to unlock. |
||||
* Note that {@link Callback#onUnlocked()} will be called immediately, |
||||
* we're not waiting for the results. |
||||
*/ |
||||
@SuppressWarnings("WeakerAccess") |
||||
public void unlock() { |
||||
LOG.i("Unlocking."); |
||||
mAutoFocus.unlock(mCharacteristics, mCallback.getLockingBuilder()); |
||||
mAutoExposure.unlock(mCharacteristics, mCallback.getLockingBuilder()); |
||||
mAutoWhiteBalance.unlock(mCharacteristics, mCallback.getLockingBuilder()); |
||||
mCallback.onUnlocked(); |
||||
} |
||||
} |
@ -0,0 +1,91 @@ |
||||
package com.otaliastudios.cameraview.engine.locking; |
||||
|
||||
import android.hardware.camera2.CameraCharacteristics; |
||||
import android.hardware.camera2.CaptureRequest; |
||||
import android.hardware.camera2.CaptureResult; |
||||
import android.os.Build; |
||||
|
||||
import androidx.annotation.NonNull; |
||||
import androidx.annotation.RequiresApi; |
||||
|
||||
import com.otaliastudios.cameraview.CameraLogger; |
||||
|
||||
@RequiresApi(Build.VERSION_CODES.LOLLIPOP) |
||||
public class AutoExposure extends Parameter { |
||||
|
||||
private static final String TAG = AutoExposure.class.getSimpleName(); |
||||
private static final CameraLogger LOG = CameraLogger.create(TAG); |
||||
|
||||
public AutoExposure(@NonNull LockingChangeCallback callback) { |
||||
super(callback); |
||||
} |
||||
|
||||
@Override |
||||
protected boolean checkSupportsLocking(@NonNull CameraCharacteristics characteristics, |
||||
@NonNull CaptureRequest.Builder builder) { |
||||
boolean isNotLegacy = readCharacteristic(characteristics, |
||||
CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL, -1) != |
||||
CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY; |
||||
// Not sure we should check aeMode as well, probably all aeModes support locking,
|
||||
// but this should not be a big issue since we're not even using different AE modes.
|
||||
Integer aeMode = builder.get(CaptureRequest.CONTROL_AE_MODE); |
||||
boolean isAEOn = aeMode != null && |
||||
(aeMode == CameraCharacteristics.CONTROL_AE_MODE_ON |
||||
|| aeMode == CameraCharacteristics.CONTROL_AE_MODE_ON_ALWAYS_FLASH |
||||
|| aeMode == CameraCharacteristics.CONTROL_AE_MODE_ON_AUTO_FLASH |
||||
|| aeMode == CameraCharacteristics.CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE |
||||
|| aeMode == 5 /* CameraCharacteristics.CONTROL_AE_MODE_ON_EXTERNAL_FLASH, API 28 */); |
||||
boolean result = isNotLegacy && isAEOn; |
||||
LOG.i("checkSupportsProcessing:", result); |
||||
return result; |
||||
} |
||||
|
||||
@Override |
||||
protected boolean checkShouldSkip(@NonNull CaptureResult lastResult) { |
||||
Integer aeState = lastResult.get(CaptureResult.CONTROL_AE_STATE); |
||||
boolean result = aeState != null && aeState == CaptureResult.CONTROL_AE_STATE_LOCKED; |
||||
LOG.i("checkShouldSkip:", result); |
||||
return result; |
||||
} |
||||
|
||||
@Override |
||||
protected void onLock(@NonNull CameraCharacteristics characteristics, |
||||
@NonNull CaptureRequest.Builder builder) { |
||||
builder.set(CaptureRequest.CONTROL_AE_LOCK, true); |
||||
notifyBuilderChanged(); |
||||
} |
||||
|
||||
@Override |
||||
public void processCapture(@NonNull CaptureResult result) { |
||||
Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE); |
||||
LOG.i("processCapture:", "aeState:", aeState); |
||||
if (aeState == null) return; |
||||
switch (aeState) { |
||||
case CaptureRequest.CONTROL_AE_STATE_LOCKED: { |
||||
isLocked = true; |
||||
isSuccessful = false; |
||||
break; |
||||
} |
||||
case CaptureRequest.CONTROL_AE_STATE_PRECAPTURE: |
||||
case CaptureRequest.CONTROL_AE_STATE_CONVERGED: |
||||
case CaptureRequest.CONTROL_AE_STATE_INACTIVE: |
||||
case CaptureRequest.CONTROL_AE_STATE_SEARCHING: |
||||
case CaptureRequest.CONTROL_AE_STATE_FLASH_REQUIRED: { |
||||
// Wait...
|
||||
break; |
||||
} |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
protected void onLocked(@NonNull CaptureRequest.Builder builder) { |
||||
// Do nothing
|
||||
} |
||||
|
||||
@Override |
||||
protected void onUnlock(@NonNull CameraCharacteristics characteristics, |
||||
@NonNull CaptureRequest.Builder builder) { |
||||
builder.set(CaptureRequest.CONTROL_AE_LOCK, false); |
||||
notifyBuilderChanged(); |
||||
} |
||||
} |
@ -0,0 +1,101 @@ |
||||
package com.otaliastudios.cameraview.engine.locking; |
||||
|
||||
import android.hardware.camera2.CameraCharacteristics; |
||||
import android.hardware.camera2.CaptureRequest; |
||||
import android.hardware.camera2.CaptureResult; |
||||
import android.os.Build; |
||||
|
||||
import androidx.annotation.NonNull; |
||||
import androidx.annotation.Nullable; |
||||
import androidx.annotation.RequiresApi; |
||||
|
||||
import com.otaliastudios.cameraview.CameraLogger; |
||||
|
||||
import java.util.List; |
||||
|
||||
@RequiresApi(Build.VERSION_CODES.LOLLIPOP) |
||||
public class AutoFocus extends Parameter { |
||||
|
||||
private static final String TAG = AutoFocus.class.getSimpleName(); |
||||
private static final CameraLogger LOG = CameraLogger.create(TAG); |
||||
|
||||
private Integer oldAfMode; |
||||
|
||||
public AutoFocus(@NonNull LockingChangeCallback callback) { |
||||
super(callback); |
||||
} |
||||
|
||||
@Override |
||||
protected boolean checkSupportsLocking(@NonNull CameraCharacteristics characteristics, |
||||
@NonNull CaptureRequest.Builder builder) { |
||||
// We'll lock by changing the AF mode to AUTO.
|
||||
// In that mode, AF won't change unless someone starts a trigger operation.
|
||||
int[] modes = readCharacteristic(characteristics, |
||||
CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES, new int[]{}); |
||||
for (int mode : modes) { |
||||
if (mode == CameraCharacteristics.CONTROL_AF_MODE_AUTO) { |
||||
return true; |
||||
} |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
@Override |
||||
protected boolean checkShouldSkip(@NonNull CaptureResult lastResult) { |
||||
Integer afState = lastResult.get(CaptureResult.CONTROL_AF_STATE); |
||||
boolean afStateOk = afState != null && |
||||
(afState == CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED || |
||||
afState == CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED); |
||||
oldAfMode = lastResult.get(CaptureResult.CONTROL_AF_MODE); |
||||
boolean afModeOk = oldAfMode != null && oldAfMode == CaptureResult.CONTROL_AF_MODE_AUTO; |
||||
boolean result = afStateOk && afModeOk; |
||||
LOG.i("checkShouldSkip:", result); |
||||
return result; |
||||
} |
||||
|
||||
@Override |
||||
protected void onLock(@NonNull CameraCharacteristics characteristics, |
||||
@NonNull CaptureRequest.Builder builder) { |
||||
builder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_AUTO); |
||||
builder.set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_CANCEL); |
||||
notifyBuilderChanged(); |
||||
} |
||||
|
||||
@Override |
||||
public void processCapture(@NonNull CaptureResult result) { |
||||
Integer afState = result.get(CaptureResult.CONTROL_AF_STATE); |
||||
Integer afMode = result.get(CaptureResult.CONTROL_AF_MODE); |
||||
LOG.i("onCapture:", "afState:", afState, "afMode:", afMode); |
||||
if (afState == null || afMode == null) return; |
||||
if (afMode != CaptureResult.CONTROL_AF_MODE_AUTO) return; |
||||
switch (afState) { |
||||
case CaptureRequest.CONTROL_AF_STATE_FOCUSED_LOCKED: |
||||
case CaptureRequest.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED: |
||||
case CaptureRequest.CONTROL_AF_STATE_INACTIVE: |
||||
case CaptureRequest.CONTROL_AF_STATE_PASSIVE_FOCUSED: |
||||
case CaptureRequest.CONTROL_AF_STATE_PASSIVE_UNFOCUSED: { |
||||
isLocked = true; |
||||
isSuccessful = true; |
||||
break; |
||||
} |
||||
case CaptureRequest.CONTROL_AF_STATE_ACTIVE_SCAN: |
||||
case CaptureRequest.CONTROL_AF_STATE_PASSIVE_SCAN: { |
||||
// Wait...
|
||||
break; |
||||
} |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
protected void onLocked(@NonNull CaptureRequest.Builder builder) { |
||||
// Do nothing.
|
||||
} |
||||
|
||||
@Override |
||||
protected void onUnlock(@NonNull CameraCharacteristics characteristics, |
||||
@NonNull CaptureRequest.Builder builder) { |
||||
builder.set(CaptureRequest.CONTROL_AF_MODE, oldAfMode); |
||||
notifyBuilderChanged(); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,80 @@ |
||||
package com.otaliastudios.cameraview.engine.locking; |
||||
|
||||
import android.hardware.camera2.CameraCharacteristics; |
||||
import android.hardware.camera2.CaptureRequest; |
||||
import android.hardware.camera2.CaptureResult; |
||||
import android.os.Build; |
||||
|
||||
import androidx.annotation.NonNull; |
||||
import androidx.annotation.RequiresApi; |
||||
|
||||
import com.otaliastudios.cameraview.CameraLogger; |
||||
|
||||
@RequiresApi(Build.VERSION_CODES.LOLLIPOP) |
||||
public class AutoWhiteBalance extends Parameter { |
||||
|
||||
private static final String TAG = AutoWhiteBalance.class.getSimpleName(); |
||||
private static final CameraLogger LOG = CameraLogger.create(TAG); |
||||
|
||||
public AutoWhiteBalance(@NonNull LockingChangeCallback callback) { |
||||
super(callback); |
||||
} |
||||
|
||||
@Override |
||||
protected boolean checkSupportsLocking(@NonNull CameraCharacteristics characteristics, |
||||
@NonNull CaptureRequest.Builder builder) { |
||||
boolean isNotLegacy = readCharacteristic(characteristics, |
||||
CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL, -1) != |
||||
CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY; |
||||
Integer awbMode = builder.get(CaptureRequest.CONTROL_AWB_MODE); |
||||
boolean result = isNotLegacy && awbMode != null && awbMode == CaptureRequest.CONTROL_AWB_MODE_AUTO; |
||||
LOG.i("checkSupportsLocking:", result); |
||||
return result; |
||||
} |
||||
|
||||
@Override |
||||
protected boolean checkShouldSkip(@NonNull CaptureResult lastResult) { |
||||
Integer awbState = lastResult.get(CaptureResult.CONTROL_AWB_STATE); |
||||
boolean result = awbState != null && awbState == CaptureRequest.CONTROL_AWB_STATE_LOCKED; |
||||
LOG.i("checkShouldSkip:", result); |
||||
return result; |
||||
} |
||||
|
||||
@Override |
||||
protected void onLock(@NonNull CameraCharacteristics characteristics, |
||||
@NonNull CaptureRequest.Builder builder) { |
||||
builder.set(CaptureRequest.CONTROL_AWB_LOCK, true); |
||||
notifyBuilderChanged(); |
||||
} |
||||
|
||||
@Override |
||||
public void processCapture(@NonNull CaptureResult result) { |
||||
Integer awbState = result.get(CaptureResult.CONTROL_AWB_STATE); |
||||
LOG.i("processCapture:", "awbState:", awbState); |
||||
if (awbState == null) return; |
||||
switch (awbState) { |
||||
case CaptureRequest.CONTROL_AWB_STATE_LOCKED: { |
||||
isLocked = true; |
||||
isSuccessful = false; |
||||
break; |
||||
} |
||||
case CaptureRequest.CONTROL_AWB_STATE_CONVERGED: |
||||
case CaptureRequest.CONTROL_AWB_STATE_INACTIVE: |
||||
case CaptureRequest.CONTROL_AWB_STATE_SEARCHING: { |
||||
// Wait...
|
||||
break; |
||||
} |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
protected void onLocked(@NonNull CaptureRequest.Builder builder) { |
||||
// Do nothing
|
||||
} |
||||
|
||||
@Override |
||||
protected void onUnlock(@NonNull CameraCharacteristics characteristics, @NonNull CaptureRequest.Builder builder) { |
||||
builder.set(CaptureRequest.CONTROL_AWB_LOCK, false); |
||||
notifyBuilderChanged(); |
||||
} |
||||
} |
@ -0,0 +1,103 @@ |
||||
package com.otaliastudios.cameraview.engine.locking; |
||||
|
||||
import android.hardware.camera2.CameraCharacteristics; |
||||
import android.hardware.camera2.CaptureRequest; |
||||
import android.hardware.camera2.CaptureResult; |
||||
import android.hardware.camera2.params.MeteringRectangle; |
||||
import android.os.Build; |
||||
|
||||
import androidx.annotation.NonNull; |
||||
import androidx.annotation.Nullable; |
||||
import androidx.annotation.RequiresApi; |
||||
|
||||
import java.util.List; |
||||
|
||||
@RequiresApi(Build.VERSION_CODES.LOLLIPOP) |
||||
public abstract class Parameter { |
||||
|
||||
public interface LockingChangeCallback { |
||||
void onLockingChange(); |
||||
} |
||||
|
||||
@SuppressWarnings("WeakerAccess") |
||||
protected boolean isSuccessful; |
||||
|
||||
@SuppressWarnings("WeakerAccess") |
||||
protected boolean isLocked; |
||||
|
||||
private LockingChangeCallback callback; |
||||
private boolean shouldSkip; |
||||
private boolean supportsLocking; |
||||
|
||||
@SuppressWarnings("WeakerAccess") |
||||
protected Parameter(@NonNull LockingChangeCallback callback) { |
||||
this.callback = callback; |
||||
} |
||||
|
||||
@SuppressWarnings("WeakerAccess") |
||||
@NonNull |
||||
protected <T> T readCharacteristic(@NonNull CameraCharacteristics characteristics, |
||||
@NonNull CameraCharacteristics.Key<T> key, |
||||
@NonNull T fallback) { |
||||
T value = characteristics.get(key); |
||||
return value == null ? fallback : value; |
||||
} |
||||
|
||||
@SuppressWarnings("WeakerAccess") |
||||
protected void notifyBuilderChanged() { |
||||
callback.onLockingChange(); |
||||
} |
||||
|
||||
public final boolean isLocked() { |
||||
// A non supported parameter should always appear as metered
|
||||
return isLocked || !supportsLocking || shouldSkip; |
||||
} |
||||
|
||||
public final boolean isSuccessful() { |
||||
// A non supported parameter should always appear as successful
|
||||
return isSuccessful || !supportsLocking || shouldSkip; |
||||
} |
||||
|
||||
public final void lock(@NonNull CameraCharacteristics characteristics, |
||||
@NonNull CaptureRequest.Builder builder, |
||||
@NonNull CaptureResult lastResult) { |
||||
isSuccessful = false; |
||||
isLocked = false; |
||||
shouldSkip = checkShouldSkip(lastResult); |
||||
supportsLocking = checkSupportsLocking(characteristics, builder); |
||||
if (!shouldSkip && supportsLocking) { |
||||
onLock(characteristics, builder); |
||||
} |
||||
} |
||||
|
||||
public final void onCapture(@NonNull CaptureRequest.Builder builder, |
||||
@NonNull CaptureResult result) { |
||||
if (isLocked()) return; |
||||
processCapture(result); |
||||
if (isLocked()) onLocked(builder); |
||||
} |
||||
|
||||
public final void unlock(@NonNull CameraCharacteristics characteristics, |
||||
@NonNull CaptureRequest.Builder builder) { |
||||
if (supportsLocking) { |
||||
// Not checking shouldSkip. Though we skipped locking, we're
|
||||
// now asked to unlock, so do it.
|
||||
onUnlock(characteristics, builder); |
||||
} |
||||
} |
||||
|
||||
protected abstract boolean checkSupportsLocking(@NonNull CameraCharacteristics characteristics, |
||||
@NonNull CaptureRequest.Builder builder); |
||||
|
||||
protected abstract boolean checkShouldSkip(@NonNull CaptureResult lastResult); |
||||
|
||||
protected abstract void onLock(@NonNull CameraCharacteristics characteristics, |
||||
@NonNull CaptureRequest.Builder builder); |
||||
|
||||
protected abstract void processCapture(@NonNull CaptureResult result); |
||||
|
||||
protected abstract void onLocked(@NonNull CaptureRequest.Builder builder); |
||||
|
||||
protected abstract void onUnlock(@NonNull CameraCharacteristics characteristics, |
||||
@NonNull CaptureRequest.Builder builder); |
||||
} |
Loading…
Reference in new issue