diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraOptions.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraOptions.java index 5dec1450..1e50ae4b 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraOptions.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraOptions.java @@ -177,14 +177,8 @@ public class CameraOptions { 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); - } - // Check for torch specifically since the Mapper flash support is not so good. - // If OFF works, it means we have AE_MODE_OFF or AE_MODE_ON. This means we can use - // the torch control. - if (supportedFlash.contains(Flash.OFF)) { - supportedFlash.add(Flash.TORCH); + Set flashes = mapper.unmapFlash(aeMode); + supportedFlash.addAll(flashes); } } 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 553c5e45..0b0aa33d 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java @@ -19,6 +19,7 @@ import android.location.Location; import android.media.Image; import android.media.ImageReader; import android.os.Build; +import android.util.Pair; import android.util.Rational; import android.view.Surface; import android.view.SurfaceHolder; @@ -60,6 +61,7 @@ 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; @@ -871,31 +873,25 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv private boolean applyFlash(@NonNull CaptureRequest.Builder builder, @NonNull Flash oldFlash) { if (mCameraOptions.supports(mFlash)) { - List modes = mMapper.map(mFlash); - int[] availableModes = readCharacteristic(CameraCharacteristics.CONTROL_AE_AVAILABLE_MODES, new int[]{}); - for (int mode : modes) { - for (int availableMode : availableModes) { - if (mode == availableMode) { - LOG.i("applyFlash: setting CONTROL_AE_MODE to", mode); - builder.set(CaptureRequest.CONTROL_AE_MODE, mode); - if (mFlash == Flash.TORCH) { - builder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_TORCH); - LOG.i("applyFlash: setting FLASH_MODE to", CaptureRequest.FLASH_MODE_TORCH); - } else if (mFlash == Flash.OFF) { - builder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF); - LOG.i("applyFlash: setting FLASH_MODE to", CaptureRequest.FLASH_MODE_OFF); - } else { // Go to OFF anyway, the AE mode will deal with flash. - builder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF); - LOG.i("applyFlash: setting FLASH_MODE to", CaptureRequest.FLASH_MODE_OFF); - } - - // On some devices, switching from TORCH/OFF to AUTO/ON is not immediately - // reflected (for example, torch stays active) unless we do as follows. - // It's just a way to wake up the AE routine. - builder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, - CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START); - return true; - } + int[] availableAeModesArray = readCharacteristic( + CameraCharacteristics.CONTROL_AE_AVAILABLE_MODES, new int[]{}); + List availableAeModes = new ArrayList<>(); + for (int mode : availableAeModesArray) { availableAeModes.add(mode); } + + List> pairs = mMapper.map(mFlash); + for (Pair pair : pairs) { + if (availableAeModes.contains(pair.first)) { + LOG.i("applyFlash: setting CONTROL_AE_MODE to", pair.first); + LOG.i("applyFlash: setting FLASH_MODE to", pair.second); + builder.set(CaptureRequest.CONTROL_AE_MODE, pair.first); + builder.set(CaptureRequest.FLASH_MODE, pair.second); + + // On some devices, switching from TORCH/OFF to AUTO/ON is not immediately + // reflected (for example, torch stays active) unless we do as follows. + // It's just a way to wake up the AE routine. + builder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, + CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START); + return true; } } } @@ -948,7 +944,7 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv private boolean applyWhiteBalance(@NonNull CaptureRequest.Builder builder, @NonNull WhiteBalance oldWhiteBalance) { if (mCameraOptions.supports(mWhiteBalance)) { - Integer whiteBalance = mMapper.map(mWhiteBalance); + int whiteBalance = mMapper.map(mWhiteBalance); builder.set(CaptureRequest.CONTROL_AWB_MODE, whiteBalance); return true; } @@ -976,7 +972,7 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv private boolean applyHdr(@NonNull CaptureRequest.Builder builder, @NonNull Hdr oldHdr) { if (mCameraOptions.supports(mHdr)) { - Integer hdr = mMapper.map(mHdr); + int hdr = mMapper.map(mHdr); builder.set(CaptureRequest.CONTROL_SCENE_MODE, hdr); return true; } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/mappers/Camera2Mapper.java b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/mappers/Camera2Mapper.java index 1ee2396f..7e1ee4fb 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/mappers/Camera2Mapper.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/mappers/Camera2Mapper.java @@ -3,8 +3,10 @@ package com.otaliastudios.cameraview.engine.mappers; import android.hardware.Camera; import android.hardware.camera2.CameraCharacteristics; import android.os.Build; +import android.util.Pair; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import com.otaliastudios.cameraview.controls.Control; @@ -14,212 +16,153 @@ import com.otaliastudios.cameraview.controls.Flash; import com.otaliastudios.cameraview.controls.Hdr; import com.otaliastudios.cameraview.controls.WhiteBalance; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; +import java.util.Map; +import java.util.Set; /** * A Mapper maps camera engine constants to CameraView constants. */ @RequiresApi(Build.VERSION_CODES.LOLLIPOP) -public abstract class Camera2Mapper { +public class Camera2Mapper { - private static com.otaliastudios.cameraview.engine.mappers.Camera2Mapper sInstance; + private static Camera2Mapper sInstance; - public static com.otaliastudios.cameraview.engine.mappers.Camera2Mapper get() { - if (sInstance == null) sInstance = new Camera2Mapper(); + public static Camera2Mapper get() { + if (sInstance == null) { + sInstance = new Camera2Mapper(); + } return sInstance; } - public static com.otaliastudios.cameraview.engine.mappers.Camera2Mapper get(@NonNull Engine engine) { - if (engine == Engine.CAMERA1) { - if (sInstance == null) sInstance = new Camera1Mapper(); - return sInstance; - } else if (engine == Engine.CAMERA2 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - if (CAMERA2 == null) CAMERA2 = new Camera2Mapper(); - return CAMERA2; - } else { - throw new IllegalArgumentException("Unknown engine or unsupported API level."); - } + private static final Map FACING = new HashMap<>(); + private static final Map WB = new HashMap<>(); + private static final Map HDR = new HashMap<>(); + + static { + 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 */); } private Camera2Mapper() {} - public abstract T map(Flash flash); - - public abstract T map(Facing facing); - - public abstract T map(WhiteBalance whiteBalance); - - public abstract T map(Hdr hdr); - - public abstract Flash unmapFlash(T cameraConstant); - - public abstract Facing unmapFacing(T cameraConstant); - - public abstract WhiteBalance unmapWhiteBalance(T cameraConstant); - - public abstract Hdr unmapHdr(T cameraConstant); - - @SuppressWarnings("WeakerAccess") - protected C reverseLookup(HashMap map, T object) { - for (C value : map.keySet()) { - if (object.equals(map.get(value))) { - return value; + @NonNull + public List> map(@NonNull Flash flash) { + List> result = new ArrayList<>(); + switch (flash) { + case ON: { + result.add(new Pair<>( + CameraCharacteristics.CONTROL_AE_MODE_ON_ALWAYS_FLASH, + CameraCharacteristics.FLASH_MODE_OFF)); + break; } - } - 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; - } + case AUTO: { + result.add(new Pair<>( + CameraCharacteristics.CONTROL_AE_MODE_ON_AUTO_FLASH, + CameraCharacteristics.FLASH_MODE_OFF)); + result.add(new Pair<>( + CameraCharacteristics.CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE, + CameraCharacteristics.FLASH_MODE_OFF)); + break; } - } - return null; - } - - @SuppressWarnings("unchecked") - private static class Camera1Mapper extends com.otaliastudios.cameraview.engine.mappers.Camera2Mapper { - - private static final HashMap FLASH = new HashMap<>(); - private static final HashMap WB = new HashMap<>(); - private static final HashMap FACING = new HashMap<>(); - private static final HashMap HDR = new HashMap<>(); - - static { - FLASH.put(Flash.OFF, Camera.Parameters.FLASH_MODE_OFF); - FLASH.put(Flash.ON, Camera.Parameters.FLASH_MODE_ON); - FLASH.put(Flash.AUTO, Camera.Parameters.FLASH_MODE_AUTO); - FLASH.put(Flash.TORCH, Camera.Parameters.FLASH_MODE_TORCH); - FACING.put(Facing.BACK, Camera.CameraInfo.CAMERA_FACING_BACK); - FACING.put(Facing.FRONT, Camera.CameraInfo.CAMERA_FACING_FRONT); - WB.put(WhiteBalance.AUTO, Camera.Parameters.WHITE_BALANCE_AUTO); - WB.put(WhiteBalance.INCANDESCENT, Camera.Parameters.WHITE_BALANCE_INCANDESCENT); - WB.put(WhiteBalance.FLUORESCENT, Camera.Parameters.WHITE_BALANCE_FLUORESCENT); - WB.put(WhiteBalance.DAYLIGHT, Camera.Parameters.WHITE_BALANCE_DAYLIGHT); - WB.put(WhiteBalance.CLOUDY, Camera.Parameters.WHITE_BALANCE_CLOUDY_DAYLIGHT); - HDR.put(Hdr.OFF, Camera.Parameters.SCENE_MODE_AUTO); - if (Build.VERSION.SDK_INT >= 17) { - HDR.put(Hdr.ON, Camera.Parameters.SCENE_MODE_HDR); - } else { - HDR.put(Hdr.ON, "hdr"); + case OFF: { + result.add(new Pair<>( + CameraCharacteristics.CONTROL_AE_MODE_ON, + CameraCharacteristics.FLASH_MODE_OFF)); + result.add(new Pair<>( + CameraCharacteristics.CONTROL_AE_MODE_OFF, + CameraCharacteristics.FLASH_MODE_OFF)); + break; + } + case TORCH: { + // When AE_MODE is ON or OFF, we can finally use the flash mode + // low level control to either turn flash off or open the torch + result.add(new Pair<>( + CameraCharacteristics.CONTROL_AE_MODE_ON, + CameraCharacteristics.FLASH_MODE_TORCH)); + result.add(new Pair<>( + CameraCharacteristics.CONTROL_AE_MODE_OFF, + CameraCharacteristics.FLASH_MODE_TORCH)); + break; } } - - @Override - public T map(Flash flash) { - return (T) FLASH.get(flash); - } - - @Override - public T map(Facing facing) { - return (T) FACING.get(facing); - } - - @Override - public T map(WhiteBalance whiteBalance) { - return (T) WB.get(whiteBalance); - } - - @Override - public T map(Hdr hdr) { - return (T) HDR.get(hdr); - } - - @Override - public Flash unmapFlash(T cameraConstant) { - return reverseLookup(FLASH, (String) cameraConstant); - } - - @Override - public Facing unmapFacing(T cameraConstant) { - return reverseLookup(FACING, (Integer) cameraConstant); - } - - @Override - public WhiteBalance unmapWhiteBalance(T cameraConstant) { - return reverseLookup(WB, (String) cameraConstant); - } - - @Override - public Hdr unmapHdr(T cameraConstant) { - return reverseLookup(HDR, (String) cameraConstant); - } + return result; } - @SuppressWarnings("unchecked") - @RequiresApi(Build.VERSION_CODES.LOLLIPOP) - private static class Camera2Mapper extends com.otaliastudios.cameraview.engine.mappers.Camera2Mapper { - - 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 { - // OFF and TORCH have also a second condition - to CameraCharacteristics.CONTROL_FLASH_MODE - but that does not - // fit into the Mapper interface. TODO review this - FLASH.put(Flash.OFF, Arrays.asList(CameraCharacteristics.CONTROL_AE_MODE_ON, CameraCharacteristics.CONTROL_AE_MODE_OFF)); - FLASH.put(Flash.TORCH, 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 */); - } - - @Override - public T map(Flash flash) { - return (T) FLASH.get(flash); - } + public int map(@NonNull Facing facing) { + //noinspection ConstantConditions + return FACING.get(facing); + } - @Override - public T map(Facing facing) { - return (T) FACING.get(facing); - } + public int map(@NonNull WhiteBalance whiteBalance) { + //noinspection ConstantConditions + return WB.get(whiteBalance); + } - @Override - public T map(WhiteBalance whiteBalance) { - return (T) WB.get(whiteBalance); - } + public int map(@NonNull Hdr hdr) { + //noinspection ConstantConditions + return HDR.get(hdr); + } - @Override - public T map(Hdr hdr) { - return (T) HDR.get(hdr); + @NonNull + public Set unmapFlash(int cameraConstant) { + Set result = new HashSet<>(); + switch (cameraConstant) { + case CameraCharacteristics.CONTROL_AE_MODE_OFF: + case CameraCharacteristics.CONTROL_AE_MODE_ON: { + result.add(Flash.OFF); + result.add(Flash.TORCH); + break; + } + case CameraCharacteristics.CONTROL_AE_MODE_ON_ALWAYS_FLASH: { + result.add(Flash.ON); + break; + } + case CameraCharacteristics.CONTROL_AE_MODE_ON_AUTO_FLASH: + case CameraCharacteristics.CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE: { + result.add(Flash.AUTO); + break; + } + case CameraCharacteristics.CONTROL_AE_MODE_ON_EXTERNAL_FLASH: + default: break; // we don't support external flash } + return result; + } - @Override - public Flash unmapFlash(T cameraConstant) { - return reverseListLookup(FLASH, (Integer) cameraConstant); - } + @Nullable + public Facing unmapFacing(int cameraConstant) { + return reverseLookup(FACING, cameraConstant); + } - @Override - public Facing unmapFacing(T cameraConstant) { - return reverseLookup(FACING, (Integer) cameraConstant); - } + @Nullable + public WhiteBalance unmapWhiteBalance(int cameraConstant) { + return reverseLookup(WB, cameraConstant); + } - @Override - public WhiteBalance unmapWhiteBalance(T cameraConstant) { - return reverseLookup(WB, (Integer) cameraConstant); - } + @Nullable + public Hdr unmapHdr(int cameraConstant) { + return reverseLookup(HDR, cameraConstant); + } - @Override - public Hdr unmapHdr(T cameraConstant) { - return reverseLookup(HDR, (Integer) cameraConstant); + @Nullable + private C reverseLookup(@NonNull Map map, @NonNull T object) { + for (C value : map.keySet()) { + if (object.equals(map.get(value))) { + return value; + } } + return null; } }