diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraOptions.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraOptions.java index 00b8ebb2..43be7394 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraOptions.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraOptions.java @@ -6,6 +6,7 @@ import android.hardware.Camera; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CameraManager; +import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.params.StreamConfigurationMap; import android.media.CamcorderProfile; import android.media.ImageReader; @@ -102,7 +103,10 @@ public class CameraOptions { } } + // zoom zoomSupported = params.isZoomSupported(); + + // autofocus autoFocusSupported = params.getSupportedFocusModes().contains(Camera.Parameters.FOCUS_MODE_AUTO); // Exposure correction @@ -158,6 +162,50 @@ public class CameraOptions { } } + // WB + int[] awbModes = cameraCharacteristics.get(CameraCharacteristics.CONTROL_AWB_AVAILABLE_MODES); + //noinspection ConstantConditions + for (int awbMode : awbModes) { + WhiteBalance value = mapper.unmapWhiteBalance(awbMode); + if (value != null) supportedWhiteBalance.add(value); + } + + // Flash + supportedFlash.add(Flash.OFF); + Boolean hasFlash = cameraCharacteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE); + if (hasFlash != null && hasFlash) { + int[] aeModes = cameraCharacteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_MODES); + //noinspection ConstantConditions + for (int aeMode : aeModes) { + Flash value = mapper.unmapFlash(aeMode); + if (value != null) supportedFlash.add(value); + } + } + + // HDR + supportedHdr.add(Hdr.OFF); + int[] sceneModes = cameraCharacteristics.get(CameraCharacteristics.CONTROL_AVAILABLE_SCENE_MODES); + //noinspection ConstantConditions + for (int sceneMode : sceneModes) { + Hdr value = mapper.unmapHdr(sceneMode); + if (value != null) supportedHdr.add(value); + } + + // TODO zoom + + // autofocus + int[] afModes = cameraCharacteristics.get(CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES); + autoFocusSupported = false; + //noinspection ConstantConditions + for (int afMode : afModes) { + if (afMode == CameraCharacteristics.CONTROL_AF_MODE_AUTO) { + autoFocusSupported = true; + } + } + + // TODO exposure correction + + // Picture Sizes StreamConfigurationMap streamMap = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); if (streamMap == null) throw new RuntimeException("StreamConfigurationMap is null. Should not happen."); diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java index 522454c5..6e7dadab 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java @@ -42,7 +42,6 @@ import com.otaliastudios.cameraview.video.Full2VideoRecorder; import com.otaliastudios.cameraview.video.SnapshotVideoRecorder; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; @@ -53,7 +52,10 @@ import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import androidx.annotation.WorkerThread; -// TODO parameters +// TODO fix flash, it's not that simple +// TODO zoom +// TODO exposure correction +// TODO autofocus // TODO pictures @RequiresApi(Build.VERSION_CODES.LOLLIPOP) public class Camera2Engine extends CameraEngine { @@ -64,6 +66,7 @@ public class Camera2Engine extends CameraEngine { private final CameraManager mManager; private String mCameraId; private CameraDevice mCamera; + private CameraCharacteristics mCameraCharacteristics; private CameraCaptureSession mSession; private CaptureRequest.Builder mPreviewStreamRequestBuilder; private CaptureRequest mPreviewStreamRequest; @@ -198,16 +201,10 @@ public class Camera2Engine extends CameraEngine { // Set parameters that might have been set before the camera was opened. try { LOG.i("createCamera:", "Applying default parameters."); - CameraCharacteristics characteristics = mManager.getCameraCharacteristics(mCameraId); + mCameraCharacteristics = mManager.getCameraCharacteristics(mCameraId); mCameraOptions = new CameraOptions(mManager, mCameraId, flip(REF_SENSOR, REF_VIEW)); - // applyDefaultFocus(params); TODO - // applyFlash(params, Flash.OFF); - // applyLocation(params, null); - // applyWhiteBalance(params, WhiteBalance.AUTO); - // applyHdr(params, Hdr.OFF); - // applyPlaySounds(mPlaySounds); - // params.setRecordingHint(mMode == Mode.VIDEO); - // mCamera.setParameters(params); + mPreviewStreamRequestBuilder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); + applyAll(mPreviewStreamRequestBuilder); } catch (CameraAccessException e) { task.trySetException(createCameraException(e)); return; @@ -294,7 +291,6 @@ public class Camera2Engine extends CameraEngine { if (captureSurface != null) outputSurfaces.add(captureSurface); try { - mPreviewStreamRequestBuilder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); mPreviewStreamRequestBuilder.addTarget(mPreviewStreamSurface); // null handler means using the current looper which is totally ok. @@ -394,8 +390,8 @@ public class Camera2Engine extends CameraEngine { mFullVideoPersistentSurface.release(); mFullVideoPersistentSurface = null; } + mPreviewStreamRequestBuilder.removeTarget(mPreviewStreamSurface); mPreviewStreamSurface = null; - mPreviewStreamRequestBuilder = null; mPreviewStreamSize = null; mCaptureSize = null; mSession.close(); @@ -418,6 +414,7 @@ public class Camera2Engine extends CameraEngine { mCamera = null; mCameraOptions = null; mVideoRecorder = null; + mPreviewStreamRequestBuilder = null; LOG.w("onStopEngine:", "Returning."); return Tasks.forResult(null); } @@ -472,6 +469,7 @@ public class Camera2Engine extends CameraEngine { Full2VideoRecorder recorder = (Full2VideoRecorder) mVideoRecorder; try { CaptureRequest.Builder builder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_RECORD); + applyAll(builder); //noinspection ConstantConditions builder.addTarget(recorder.getInputSurface()); builder.addTarget(mPreviewStreamSurface); @@ -541,54 +539,202 @@ public class Camera2Engine extends CameraEngine { //endregion + //region Parameters - @Override - public void onBufferAvailable(@NonNull byte[] buffer) { + private void syncStream() { + if (getPreviewState() == STATE_STARTED) { + try { + // TODO if we are capturing a video or a picture, this is not what we should do! + mPreviewStreamRequest = mPreviewStreamRequestBuilder.build(); + mSession.setRepeatingRequest(mPreviewStreamRequest, null, null); + } catch (Exception e) { + throw new CameraException(e, CameraException.REASON_DISCONNECTED); + } + } + } + private void applyAll(@NonNull CaptureRequest.Builder builder) { + builder.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO); + applyDefaultFocus(builder); + applyFlash(builder, Flash.OFF); + applyLocation(builder, null); + applyWhiteBalance(builder, WhiteBalance.AUTO); + applyHdr(builder, Hdr.OFF); } - @Override - public void setZoom(float zoom, @Nullable PointF[] points, boolean notify) { + private void applyDefaultFocus(@NonNull CaptureRequest.Builder builder) { + int[] modesArray = readCharacteristic(mCameraCharacteristics, CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES, new int[]{}); + List modes = new ArrayList<>(); + for (int mode : modesArray) { modes.add(mode); } + if (mMode == Mode.VIDEO && + modes.contains(CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_VIDEO)) { + builder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_VIDEO); + return; + } + + if (modes.contains(CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE)) { + builder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); + return; + } + if (modes.contains(CaptureRequest.CONTROL_AF_MODE_AUTO)) { + builder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_AUTO); + return; + } + + if (modes.contains(CaptureRequest.CONTROL_AF_MODE_OFF)) { + builder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_OFF); + builder.set(CaptureRequest.LENS_FOCUS_DISTANCE, 0F); + //noinspection UnnecessaryReturnStatement + return; + } } @Override - public void setExposureCorrection(float EVvalue, @NonNull float[] bounds, @Nullable PointF[] points, boolean notify) { + public void setFlash(@NonNull Flash flash) { + final Flash old = mFlash; + mFlash = flash; + mHandler.run(new Runnable() { + @Override + public void run() { + if (getEngineState() == STATE_STARTED) { + if (applyFlash(mPreviewStreamRequestBuilder, old)) syncStream(); + } + mFlashOp.end(null); + } + }); + } + /** + * This sets the CONTROL_AE_MODE to either: + * - {@link CaptureRequest#CONTROL_AE_MODE_ON} + * - {@link CaptureRequest#CONTROL_AE_MODE_ON_AUTO_FLASH} + * - {@link CaptureRequest#CONTROL_AE_MODE_ON_ALWAYS_FLASH} + */ + private boolean applyFlash(@NonNull CaptureRequest.Builder builder, + @NonNull Flash oldFlash) { + if (mCameraOptions.supports(mFlash)) { + List modes = mMapper.map(mFlash); + int[] availableModes = readCharacteristic(mCameraCharacteristics, CameraCharacteristics.CONTROL_AE_AVAILABLE_MODES, new int[]{}); + for (int mode : modes) { + for (int availableMode : availableModes) { + if (mode == availableMode) { + builder.set(CaptureRequest.CONTROL_AE_MODE, mode); + return true; + } + } + } + } + mFlash = oldFlash; + return false; } @Override - public void setFlash(@NonNull Flash flash) { + public void setLocation(@Nullable Location location) { + final Location old = mLocation; + mLocation = location; + mHandler.run(new Runnable() { + @Override + public void run() { + if (getEngineState() == STATE_STARTED) { + if (applyLocation(mPreviewStreamRequestBuilder, old)) syncStream(); + } + mLocationOp.end(null); + } + }); + } + private boolean applyLocation(@NonNull CaptureRequest.Builder builder, + @SuppressWarnings("unused") @Nullable Location oldLocation) { + if (mLocation != null) { + builder.set(CaptureRequest.JPEG_GPS_LOCATION, mLocation); + } + return true; } @Override public void setWhiteBalance(@NonNull WhiteBalance whiteBalance) { + final WhiteBalance old = mWhiteBalance; + mWhiteBalance = whiteBalance; + mHandler.run(new Runnable() { + @Override + public void run() { + if (getEngineState() == STATE_STARTED) { + if (applyWhiteBalance(mPreviewStreamRequestBuilder, old)) syncStream(); + } + mWhiteBalanceOp.end(null); + } + }); + } + private boolean applyWhiteBalance(@NonNull CaptureRequest.Builder builder, + @NonNull WhiteBalance oldWhiteBalance) { + if (mCameraOptions.supports(mWhiteBalance)) { + Integer whiteBalance = mMapper.map(mWhiteBalance); + builder.set(CaptureRequest.CONTROL_AWB_MODE, whiteBalance); + return true; + } + mWhiteBalance = oldWhiteBalance; + return false; } @Override public void setHdr(@NonNull Hdr hdr) { + final Hdr old = mHdr; + mHdr = hdr; + mHandler.run(new Runnable() { + @Override + public void run() { + if (getEngineState() == STATE_STARTED) { + if (applyHdr(mPreviewStreamRequestBuilder, old)) syncStream(); + } + mHdrOp.end(null); + } + }); + } + + private boolean applyHdr( @NonNull CaptureRequest.Builder builder, @NonNull Hdr oldHdr) { + if (mCameraOptions.supports(mHdr)) { + Integer hdr = mMapper.map(mHdr); + builder.set(CaptureRequest.CONTROL_SCENE_MODE, hdr); + return true; + } + mHdr = oldHdr; + return false; } @Override - public void setLocation(@Nullable Location location) { + public void setPlaySounds(boolean playSounds) { + mPlaySounds = playSounds; + mPlaySoundsOp.end(null); + } + + //endregion + + + @Override + public void onBufferAvailable(@NonNull byte[] buffer) { } @Override - public void takePicture(@NonNull PictureResult.Stub stub) { + public void setZoom(float zoom, @Nullable PointF[] points, boolean notify) { } @Override - public void startAutoFocus(@Nullable Gesture gesture, @NonNull PointF point) { + public void setExposureCorrection(float EVvalue, @NonNull float[] bounds, @Nullable PointF[] points, boolean notify) { } @Override - public void setPlaySounds(boolean playSounds) { + public void takePicture(@NonNull PictureResult.Stub stub) { + + } + + @Override + public void startAutoFocus(@Nullable Gesture gesture, @NonNull PointF point) { } } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraEngine.java b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraEngine.java index f5806c86..65a5f961 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraEngine.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraEngine.java @@ -1,7 +1,9 @@ package com.otaliastudios.cameraview.engine; import android.content.Context; +import android.graphics.Camera; import android.graphics.PointF; +import android.hardware.camera2.CameraCharacteristics; import android.location.Location; diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Mapper.java b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Mapper.java index d72c57ed..b1f2261c 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Mapper.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Mapper.java @@ -4,13 +4,17 @@ import android.hardware.Camera; import android.hardware.camera2.CameraCharacteristics; import android.os.Build; +import com.otaliastudios.cameraview.controls.Control; import com.otaliastudios.cameraview.controls.Engine; import com.otaliastudios.cameraview.controls.Facing; import com.otaliastudios.cameraview.controls.Flash; import com.otaliastudios.cameraview.controls.Hdr; import com.otaliastudios.cameraview.controls.WhiteBalance; +import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; +import java.util.List; import androidx.annotation.NonNull; import androidx.annotation.RequiresApi; @@ -54,8 +58,8 @@ public abstract class Mapper { public abstract Hdr unmapHdr(T cameraConstant); @SuppressWarnings("WeakerAccess") - protected T reverseLookup(HashMap map, Object object) { - for (T value : map.keySet()) { + protected C reverseLookup(HashMap map, T object) { + for (C value : map.keySet()) { if (object.equals(map.get(value))) { return value; } @@ -63,6 +67,20 @@ public abstract class Mapper { return null; } + @SuppressWarnings("WeakerAccess") + protected C reverseListLookup(HashMap> map, T object) { + for (C value : map.keySet()) { + List list = map.get(value); + if (list == null) continue; + for (T candidate : list) { + if (object.equals(candidate)) { + return value; + } + } + } + return null; + } + @SuppressWarnings("unchecked") private static class Camera1Mapper extends Mapper { @@ -113,22 +131,22 @@ public abstract class Mapper { @Override public Flash unmapFlash(T cameraConstant) { - return reverseLookup(FLASH, cameraConstant); + return reverseLookup(FLASH, (String) cameraConstant); } @Override public Facing unmapFacing(T cameraConstant) { - return reverseLookup(FACING, cameraConstant); + return reverseLookup(FACING, (Integer) cameraConstant); } @Override public WhiteBalance unmapWhiteBalance(T cameraConstant) { - return reverseLookup(WB, cameraConstant); + return reverseLookup(WB, (String) cameraConstant); } @Override public Hdr unmapHdr(T cameraConstant) { - return reverseLookup(HDR, cameraConstant); + return reverseLookup(HDR, (String) cameraConstant); } } @@ -136,16 +154,30 @@ public abstract class Mapper { @RequiresApi(Build.VERSION_CODES.LOLLIPOP) private static class Camera2Mapper extends Mapper { + private static final HashMap> FLASH = new HashMap<>(); private static final HashMap FACING = new HashMap<>(); + private static final HashMap WB = new HashMap<>(); + private static final HashMap HDR = new HashMap<>(); static { + FLASH.put(Flash.OFF, Arrays.asList(CameraCharacteristics.CONTROL_AE_MODE_ON, CameraCharacteristics.CONTROL_AE_MODE_OFF)); + FLASH.put(Flash.AUTO, Arrays.asList(CameraCharacteristics.CONTROL_AE_MODE_ON_AUTO_FLASH, CameraCharacteristics.CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE)); + FLASH.put(Flash.ON, Collections.singletonList(CameraCharacteristics.CONTROL_AE_MODE_ON_ALWAYS_FLASH)); FACING.put(Facing.BACK, CameraCharacteristics.LENS_FACING_BACK); FACING.put(Facing.FRONT, CameraCharacteristics.LENS_FACING_FRONT); + WB.put(WhiteBalance.AUTO, CameraCharacteristics.CONTROL_AWB_MODE_AUTO); + WB.put(WhiteBalance.CLOUDY, CameraCharacteristics.CONTROL_AWB_MODE_CLOUDY_DAYLIGHT); + WB.put(WhiteBalance.DAYLIGHT, CameraCharacteristics.CONTROL_AWB_MODE_DAYLIGHT); + WB.put(WhiteBalance.FLUORESCENT, CameraCharacteristics.CONTROL_AWB_MODE_FLUORESCENT); + WB.put(WhiteBalance.INCANDESCENT, CameraCharacteristics.CONTROL_AWB_MODE_INCANDESCENT); + HDR.put(Hdr.OFF, CameraCharacteristics.CONTROL_SCENE_MODE_DISABLED); + HDR.put(Hdr.ON, 18 /* CameraCharacteristics.CONTROL_SCENE_MODE_HDR */); } + @SuppressWarnings("ConstantConditions") @Override public T map(Flash flash) { - return null; + return (T) FLASH.get(flash); } @Override @@ -155,32 +187,32 @@ public abstract class Mapper { @Override public T map(WhiteBalance whiteBalance) { - return null; + return (T) WB.get(whiteBalance); } @Override public T map(Hdr hdr) { - return null; + return (T) HDR.get(hdr); } @Override public Flash unmapFlash(T cameraConstant) { - return null; + return reverseListLookup(FLASH, (Integer) cameraConstant); } @Override public Facing unmapFacing(T cameraConstant) { - return reverseLookup(FACING, cameraConstant); + return reverseLookup(FACING, (Integer) cameraConstant); } @Override public WhiteBalance unmapWhiteBalance(T cameraConstant) { - return null; + return reverseLookup(WB, (Integer) cameraConstant); } @Override public Hdr unmapHdr(T cameraConstant) { - return null; + return reverseLookup(HDR, (Integer) cameraConstant); } } }